diff --git a/.ci-operator.yaml b/.ci-operator.yaml index a3628cf240a63..1d88a59fdf378 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: name: release namespace: openshift - tag: rhel-9-release-golang-1.25-openshift-4.22 + tag: rhel-9-release-golang-1.26-openshift-5.0 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 73140ca463c54..27e9cf43def77 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,6 +13,7 @@ https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-k - -- [v1.35.2](#v1352) - - [Downloads for v1.35.2](#downloads-for-v1352) - - [Source Code](#source-code) - - [Client Binaries](#client-binaries) - - [Server Binaries](#server-binaries) - - [Node Binaries](#node-binaries) - - [Container Images](#container-images) - - [Changelog since v1.35.1](#changelog-since-v1351) - - [Changes by Kind](#changes-by-kind) - - [Feature](#feature) - - [Dependencies](#dependencies) - - [Added](#added) - - [Changed](#changed) - - [Removed](#removed) -- [v1.35.1](#v1351) - - [Downloads for v1.35.1](#downloads-for-v1351) - - [Source Code](#source-code-1) - - [Client Binaries](#client-binaries-1) - - [Server Binaries](#server-binaries-1) - - [Node Binaries](#node-binaries-1) - - [Container Images](#container-images-1) - - [Changelog since v1.35.0](#changelog-since-v1350) - - [Changes by Kind](#changes-by-kind-1) - - [Feature](#feature-1) - - [Bug or Regression](#bug-or-regression) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake) - - [Dependencies](#dependencies-1) - - [Added](#added-1) - - [Changed](#changed-1) - - [Removed](#removed-1) -- [v1.35.0](#v1350) - - [Downloads for v1.35.0](#downloads-for-v1350) - - [Source Code](#source-code-2) - - [Client Binaries](#client-binaries-2) - - [Server Binaries](#server-binaries-2) - - [Node Binaries](#node-binaries-2) - - [Container Images](#container-images-2) - - [Changelog since v1.34.0](#changelog-since-v1340) - - [Urgent Upgrade Notes](#urgent-upgrade-notes) - - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade) - - [Changes by Kind](#changes-by-kind-2) - - [Deprecation](#deprecation) - - [API Change](#api-change) - - [Feature](#feature-2) - - [Documentation](#documentation) - - [Bug or Regression](#bug-or-regression-1) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-1) - - [Dependencies](#dependencies-2) - - [Added](#added-2) - - [Changed](#changed-2) - - [Removed](#removed-2) -- [v1.35.0-rc.1](#v1350-rc1) - - [Downloads for v1.35.0-rc.1](#downloads-for-v1350-rc1) - - [Source Code](#source-code-3) - - [Client Binaries](#client-binaries-3) - - [Server Binaries](#server-binaries-3) - - [Node Binaries](#node-binaries-3) - - [Container Images](#container-images-3) - - [Changelog since v1.35.0-rc.0](#changelog-since-v1350-rc0) - - [Changes by Kind](#changes-by-kind-3) - - [Feature](#feature-3) - - [Bug or Regression](#bug-or-regression-2) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-2) - - [Dependencies](#dependencies-3) - - [Added](#added-3) - - [Changed](#changed-3) - - [Removed](#removed-3) -- [v1.35.0-rc.0](#v1350-rc0) - - [Downloads for v1.35.0-rc.0](#downloads-for-v1350-rc0) - - [Source Code](#source-code-4) - - [Client Binaries](#client-binaries-4) - - [Server Binaries](#server-binaries-4) - - [Node Binaries](#node-binaries-4) - - [Container Images](#container-images-4) - - [Changelog since v1.35.0-beta.0](#changelog-since-v1350-beta0) - - [Changes by Kind](#changes-by-kind-4) - - [Feature](#feature-4) - - [Bug or Regression](#bug-or-regression-3) - - [Dependencies](#dependencies-4) - - [Added](#added-4) - - [Changed](#changed-4) - - [Removed](#removed-4) -- [v1.35.0-beta.0](#v1350-beta0) - - [Downloads for v1.35.0-beta.0](#downloads-for-v1350-beta0) - - [Source Code](#source-code-5) - - [Client Binaries](#client-binaries-5) - - [Server Binaries](#server-binaries-5) - - [Node Binaries](#node-binaries-5) - - [Container Images](#container-images-5) - - [Changelog since v1.35.0-alpha.3](#changelog-since-v1350-alpha3) - - [Changes by Kind](#changes-by-kind-5) - - [API Change](#api-change-1) - - [Feature](#feature-5) - - [Bug or Regression](#bug-or-regression-4) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-3) - - [Dependencies](#dependencies-5) - - [Added](#added-5) - - [Changed](#changed-5) - - [Removed](#removed-5) -- [v1.35.0-alpha.3](#v1350-alpha3) - - [Downloads for v1.35.0-alpha.3](#downloads-for-v1350-alpha3) - - [Source Code](#source-code-6) - - [Client Binaries](#client-binaries-6) - - [Server Binaries](#server-binaries-6) - - [Node Binaries](#node-binaries-6) - - [Container Images](#container-images-6) - - [Changelog since v1.35.0-alpha.2](#changelog-since-v1350-alpha2) - - [Urgent Upgrade Notes](#urgent-upgrade-notes-1) - - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade-1) - - [Changes by Kind](#changes-by-kind-6) - - [API Change](#api-change-2) - - [Feature](#feature-6) - - [Bug or Regression](#bug-or-regression-5) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-4) - - [Dependencies](#dependencies-6) - - [Added](#added-6) - - [Changed](#changed-6) - - [Removed](#removed-6) -- [v1.35.0-alpha.2](#v1350-alpha2) - - [Downloads for v1.35.0-alpha.2](#downloads-for-v1350-alpha2) - - [Source Code](#source-code-7) - - [Client Binaries](#client-binaries-7) - - [Server Binaries](#server-binaries-7) - - [Node Binaries](#node-binaries-7) - - [Container Images](#container-images-7) - - [Changelog since v1.35.0-alpha.1](#changelog-since-v1350-alpha1) - - [Changes by Kind](#changes-by-kind-7) - - [Deprecation](#deprecation-1) - - [API Change](#api-change-3) - - [Feature](#feature-7) - - [Documentation](#documentation-1) - - [Bug or Regression](#bug-or-regression-6) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-5) - - [Dependencies](#dependencies-7) - - [Added](#added-7) - - [Changed](#changed-7) - - [Removed](#removed-7) -- [v1.35.0-alpha.1](#v1350-alpha1) - - [Downloads for v1.35.0-alpha.1](#downloads-for-v1350-alpha1) - - [Source Code](#source-code-8) - - [Client Binaries](#client-binaries-8) - - [Server Binaries](#server-binaries-8) - - [Node Binaries](#node-binaries-8) - - [Container Images](#container-images-8) - - [Changelog since v1.34.0](#changelog-since-v1340-1) - - [Changes by Kind](#changes-by-kind-8) - - [API Change](#api-change-4) - - [Feature](#feature-8) - - [Bug or Regression](#bug-or-regression-7) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-6) - - [Dependencies](#dependencies-8) - - [Added](#added-8) - - [Changed](#changed-8) - - [Removed](#removed-8) - - - -# v1.35.2 - - -## Downloads for v1.35.2 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes.tar.gz) | 0264ce8adcc15926e7a4701f4e25b9dd477d1f9242e05dcfd885d0803951ccafe8ada789961de7ad0e0a2813bca668b717a6c658ba905800dc86acd6da59854b -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-src.tar.gz) | 5489905c35cd35084130613a636423a2743a92d2a76047371398f1530700353c255b88eef99228b210f6a8590c18c2e5d5eb153ea8adc6ef6554e899fcc43f76 - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-darwin-amd64.tar.gz) | 570d84f1c248d543fe185751d029c6bc2f7a3644f9b7b45e49ca34b29adf7d03b07349f63d6104570a70e04ab68f5f05046bac7b84b9a2770bc52e953d192354 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-darwin-arm64.tar.gz) | eacc6860b6afecb15d9facdd69d6514c7234404ad7793b6203bb7b5efffbda67aaf2cd0dc0608d2f2debdf8b19e9767c4a9cab4ba2890fc021993a408df6a9d0 -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-linux-386.tar.gz) | a4b447153432fdc21e1c718edbdd763e94c57d786191c74d03172f56f17e141c164eb5586245720c6d48cb831de04200064e29f3efa7a4d3570a09037e020e56 -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-linux-amd64.tar.gz) | c16cad61183e84a8dd6247ba6b163a830dc9490a06e86f452d8406c8d877620688557695f89b70b87672bb4a8922889e8aea8ca8f085f7cf68c1cbee9db63c92 -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-linux-arm.tar.gz) | d3c2e63f752211e3fd86bb0cffd3dbbb2304da2da168fd6ab5e75d28543e7e40ad8472dac631d879f0654d9bf33d2d26f10d5fbd3b7cda8ef33baf5a39f32929 -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-linux-arm64.tar.gz) | 93b001ffeb829b3984270686ad254f395a2c87b0ff9c07a60ed86c47286de721b786983f199d3ea375c87f801d68cc397a8efa4b4d4bd816576102dd0d157fa3 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-linux-ppc64le.tar.gz) | 33920d2f3bf811a67281190dbe9d6efd7164d8aa4e6a26453330663b6e281ce69b464ed95214d354eee91d8804d7027a020acd84e1436ea8a00828ca0b4a1062 -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-linux-s390x.tar.gz) | e0410769c2a7054eac0f80c620bef093ea01231ed978bbc94688319c19b6d6b107d9a0bdef5d45f987eaa295658aa143bdb833910ba6e84c9d138dd275d7c7a0 -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-windows-386.tar.gz) | 480270e7bb0bf8912b193c6f884e516c6182e64a0e08720992e876f104ee21840ba726dd5ba785bed1c097740ba8414f7f444a028bb1fab4c7a502911c2b4400 -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-windows-amd64.tar.gz) | cfe1f65379dc52628fd6c1b2b75b13a7f1000bf6b9992ad689620157eb783ca8622a94e0b2a13343b05acd561952228956bb5e45411d135af9004717d97de259 -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-client-windows-arm64.tar.gz) | 13085cb4bf3c99276b4bece38cc17824020ca7708df35ae0d3e81feae01a416794088d36688958b8bbfdd8942c5b15d70dde234e1af4757c75a940818abaf71d - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-server-linux-amd64.tar.gz) | 73b7e15267995f488159628f27b1742199de97a673e0728bf9c539a06d9454d3e9725516bf5d6c05cea7d4c8b847fe44fe835d39e0d7eeedc453833918022296 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-server-linux-arm64.tar.gz) | 4a8e10c02e3667494bd9d73e5d8ecc30e3015ff3c95c97c68523c2a8cf87f3d735ae7448e172d0125d9692b26254ae4e9a394178b01d83b7d37781e8f0ee2e34 -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-server-linux-ppc64le.tar.gz) | 962b8d37eef1a267bc42196d7198bb4fa9b87f506b344af8c8a86c7e1480d26dcfa0d78f0c206379d293314d304c94a20218ed44fad763ea7c2667689f79d05b -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-server-linux-s390x.tar.gz) | 5d283e8d153892d124580aebfb7730d92435e7052ed8679d8f4ba7bdf7f04e4e300e5c8d54d8eb55db650fe1a70bb3e9d1167a4c96acf2c0581148f93238e6ce - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-node-linux-amd64.tar.gz) | 499a61b38dbcddb7105a89cac0f63e1d9a08178c051d693a8095de65939deb4e12ff4a11116d69ec69e14da0d929c8344bbb8ea8ad06cc65d749d327ee0c7c55 -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-node-linux-arm64.tar.gz) | 91cbbb1002d78a5c49a17c3492e8e3f933856e6e008c74ec0853a3183349f4fb7b093b40424df457890989eaf10752a3337dad3a2bf89db7b5fb02978f3ffb7d -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-node-linux-ppc64le.tar.gz) | 1dcef6733b943b8fd76011c98af680f27249a3d28a663d52c8ddd54f1ac847e068c84d10115683322ce55aed314f2af8f489fed63907d035713f40bb4e5b61a5 -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-node-linux-s390x.tar.gz) | cab84f226e8cc4d77f4b92c7a768115485705476783aa290606116aa1008dce62918ccda6c153f0ba14487c58c8b3dfffdeb67932e8d5a19d36831404f575cf4 -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.2/kubernetes-node-windows-amd64.tar.gz) | 0b6092275cb6027086909f871f64bac876a22c8d714b50d8250e16fa1d9afbcda32c22bec47f83b99716dcb902e210fc22976e7b8f42b7a7b24e7e9409ef876f - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.1 - -## Changes by Kind - -### Feature - -- Kubernetes is now built using Go 1.25.7 ([#136985](https://github.com/kubernetes/kubernetes/pull/136985), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] - -## Dependencies - -### Added -_Nothing has changed._ - -### Changed -_Nothing has changed._ - -### Removed -_Nothing has changed._ - - - -# v1.35.1 - - -## Downloads for v1.35.1 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes.tar.gz) | 4fa3904bfc29fd3a5d50398437ea7ce0169dfd5f72907e6ea93c33767f6b12aae47cecd714f4cf6897ce8424ec678d6d2dc7ce7ef67f6f098310006ecf799154 -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-src.tar.gz) | f85a71b78fdbf7e50b4d96c94122aaee551ecd9fabe75ee0a7e4a8ad775fbc90fcc739999712193bbfe6d6aa61e42651e4e8c4f267b612233987f7e582d3a14d - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-darwin-amd64.tar.gz) | f396d10f94feb611b685296df716ca7abef39e7ae289f4974071b2dcf9d62b4419f86458f58c7c134e018831c2b78d8cfc6df4f890a1b2ec46a2401fef45e30d -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-darwin-arm64.tar.gz) | 630c1354ef80e59bcd8a0189bc6d9d8a31393cf75734bb6c787f8441c658a0a65aab0c1149ac3220c060afead46eda6293853e944757865c8b7c8719dec2eb8c -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-linux-386.tar.gz) | 21d160042740086dc2ade136ceb31654946d8e9d4a6c6f4a1f6f31ebb75f911af2b3bfe5ffd869210cb03cffe72463d3399e6e6b8a5182ca2b6f5513f5a14e6d -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-linux-amd64.tar.gz) | 51a45687e5cc04620673d92cc31308ccfc60fa4fd4e0836369b32d98841d4617343dfb899e6c9dd4c665e85bf1fe55402ae234481a6d6bd9275fa1d89cfc676e -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-linux-arm.tar.gz) | b65d81ed23ad19277946f514d73513b6a92b81b9c3eb459d8a2dda3c22216aef9bdb7fc76f3e95972d3e35a3737c9c5a5cf71e1f31021da39fb04731acc8b883 -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-linux-arm64.tar.gz) | 5f29e3c44b5f6c98278664a3ee702fc17ad8f3347353030458aa065c5e19e4ce3ce5f826cdfc91b12dfa6b427877885a22516485dc135c880d7d42a781a8df7e -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-linux-ppc64le.tar.gz) | 8993cc5d0a0d2efb55123e18f2e4eba461ef9eba985fe5ec10263f65400222eabc2ad604e96e4797920f936f476e12df076e46a23b1077cda31c1c63b31367e8 -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-linux-s390x.tar.gz) | 00f37b0a1ffba386edce70f4fc1d5fa068b33599e4480e743ce1577755103c8aa128227056414d84ba4a9846daa53f49cbd11229b2ff415aafcf392edd751778 -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-windows-386.tar.gz) | c41af6b553943db31a1a1369952f334fffe82aa842b5a8fe07ae9fb83194b99dec873296a28c011d66362a7b1ef327716862ea3dd76fdd54649bb3ad0d337a6a -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-windows-amd64.tar.gz) | bfa6898b9c700e4675d8a5a188a89eff054f0f9e006ed92cfa6a07e938a137f4e41214fc9aaea5098ca5a922418bfb6b51849cc78e6c4fe71be7d49314e8bee9 -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-client-windows-arm64.tar.gz) | 54e1e880fd696bae8127272bec4e5516422c1ff51feffffbb20198b93016b9c01e6601760641493f4f2cb36c6df1430072623c10d1bd949ebde861bec12bf080 - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-server-linux-amd64.tar.gz) | 9fb232137d1549e4df83f1e1b900dcf8fb52d1626ba271f418b35cddf9402ba54b2185abd31b0c475cc79cf4e17448b1eadfe81ced5b6dc7ef85af389702e923 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-server-linux-arm64.tar.gz) | 63f13123e24b5dca0d460a8d2601f4edfb334d0aa56e555acb43c201e5634cf2d6e721557df984a2eff77ce09061e97ff68d2f412be4fdfbbd9c99a6a7f7551c -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-server-linux-ppc64le.tar.gz) | 101c329c3ece18b2cef49b778d820706ff13381a2320bd56ae4c330d758c9b8e624b2ac9cee1c6272f0d97c7db30e96ba0c5984bce4a48f5db497afe519179b1 -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-server-linux-s390x.tar.gz) | e65916720eea5b128e074a18c52b514505ab3d058a7fe147832a5f40a857c15d713698bfd8abf41068559fad64977036a5eaceabbe486be649486ce8e070cc86 - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-node-linux-amd64.tar.gz) | ac544535074180b9616d3a93b0e13ce8a3f5ea2ff287b605f3d7e0afc2a16ed3cb633ddf6bdb179782f44e9a07c7db14899a942f0ab23bcfea7bbf56e08c63d4 -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-node-linux-arm64.tar.gz) | d12d660ed1669549fe676ce7d38159325864cc23a628ce3f6f9c83f09a05c91274fb64df007bdbf5d32d640107025b2732e22c019e4d08ccdf2231afeed4562d -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-node-linux-ppc64le.tar.gz) | ab08fe52f40dda20e25a3bc58dc19b1061195bf25a9d5ff509e08fb3fa73d18e65d509265468feaa8848b25457524740aabf2afd15b8c5ae8027ce084bbfea94 -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-node-linux-s390x.tar.gz) | d56401c8cfc3fa462f7502f7fe4d7010964d515b4843de8aed9a4c31ec797feff276a0b4abfaca44051a145d97ccb5c08d09e33465d6992df3da7fedfba2a1ea -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.1/kubernetes-node-windows-amd64.tar.gz) | 30f0096ad831149cef4fcad17bbe9bf4e4f2b1a610d1981ed9aee0c73f9b9f7cba4dfd89643d916464cf6f62294265c633ae32015f740bc4e5a194448ba3b6d2 - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.0 - -## Changes by Kind - -### Feature - -- Kubeadm: when patching a Node object do not exit early on unknown (non-allowlisted) API errors. Instead, always retry within the duration of the polling for getting and patching a Node object. ([#136072](https://github.com/kubernetes/kubernetes/pull/136072), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubernetes is now built using Go 1.25.6 ([#136466](https://github.com/kubernetes/kubernetes/pull/136466), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] -- Kubernetes is now built with Go 1.25.6 ([#136258](https://github.com/kubernetes/kubernetes/pull/136258), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] - -### Bug or Regression - -- DRA: when scheduling many pods very rapidly, sometimes the same device was allocated twice for different ResourceClaims due races between data processing in different goroutines. Depending on whether DRA drivers check for this during NodePrepareResources (they should, but maybe not all implement this properly), the second pod using the same device then failed to start until the first one is done or (worse) ran in parallel. ([#136567](https://github.com/kubernetes/kubernetes/pull/136567), [@pohly](https://github.com/pohly)) [SIG Node, Scheduling and Testing] -- Dropped archived dependency: Removed the deprecated and archived dependency: github.com/pkg/errors in favor of standard Go error handling. - - Updated to latest HNS version: hnslib is updated to work with the latest HNS APIs, including support for ModifyLoadBalancerPolicy, which allows updating existing load balancer policies instead of delete-and-recreate flows. ([#135843](https://github.com/kubernetes/kubernetes/pull/135843), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] -- Fixed SELinux warning controller not to emit events for completed pods. ([#136098](https://github.com/kubernetes/kubernetes/pull/136098), [@jsafrane](https://github.com/jsafrane)) [SIG Apps, Storage and Testing] -- Fixed a 1.35.0 regression by disabling the `SchedulerAsyncAPICalls` feature gate. The regression was in scheduler performance, triggered by API client throttling. ([#135904](https://github.com/kubernetes/kubernetes/pull/135904), [@macsko](https://github.com/macsko)) [SIG Scheduling] -- Fixed a panic in `kubectl exec` when the terminal size queue delegate is uninitialized. ([#136280](https://github.com/kubernetes/kubernetes/pull/136280), [@seekskyworld](https://github.com/seekskyworld)) [SIG CLI] -- Fixed an issue in the Windows kube-proxy (winkernel) where IPv4 and IPv6 Service load balancers could be incorrectly shared, causing broken dual-stack Service behavior. The kube-proxy now tracks load balancers per IP family, enabling correct support for PreferDualStack and RequireDualStack Services on Windows nodes. ([#136373](https://github.com/kubernetes/kubernetes/pull/136373), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] -- Fixed kubelet logging to properly respect verbosity levels. Previously, some debug/info messages using V().Error() would always be printed regardless of the configured log verbosity. ([#136432](https://github.com/kubernetes/kubernetes/pull/136432), [@thc1006](https://github.com/thc1006)) [SIG Node] -- Fixes a 1.29 regression in the apiserver_watch_events_sizes metric to report total outgoing watch traffic again ([#135815](https://github.com/kubernetes/kubernetes/pull/135815), [@mborsz](https://github.com/mborsz)) [SIG API Machinery] -- Fixes a 1.34 regression starting pods with environment variables with a value containing `$` followed by a multi-byte character ([#136491](https://github.com/kubernetes/kubernetes/pull/136491), [@AutuSnow](https://github.com/AutuSnow)) [SIG Architecture and Node] -- Fixes a 1.34+ regression in ipvs and winkernel kube-proxy backends; these are now reverted back to their - pre-1.34 behavior of regularly rechecking all of their rules even when no - Services or EndpointSlices change. ([#136122](https://github.com/kubernetes/kubernetes/pull/136122), [@danwinship](https://github.com/danwinship)) [SIG Network, Testing and Windows] -- Kubeadm: fix a bug where kubeadm upgrade is failed if the content of the `kubeadm-flags.env` file is `KUBELET_KUBEADM_ARGS=""` ([#136131](https://github.com/kubernetes/kubernetes/pull/136131), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] -- Kubeadm: waiting for etcd learner member to be started before promoting during 'kubeadm join' ([#136348](https://github.com/kubernetes/kubernetes/pull/136348), [@dlipovetsky](https://github.com/dlipovetsky)) [SIG Cluster Lifecycle] -- Kubeadm: when applying the overrides provided by the user using "extraArgs", do not sort the resulted list of arguments alpha-numerically. Instead, only sort the list of default arguments and keep the list of overrides unsorted. This allows finer control for flags which have an order that matters, such as, "--service-account-issuer" for kube-apiserver. ([#135853](https://github.com/kubernetes/kubernetes/pull/135853), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubelet(dra): correctly handles multiple ResourceClaims even if one is already prepared ([#136463](https://github.com/kubernetes/kubernetes/pull/136463), [@rogowski-piotr](https://github.com/rogowski-piotr)) [SIG Node and Testing] -- StatefulSets should always count `.status.availableReplicas` at the correct time without a delay. This results in faster progress of StatefulSet rollout. ([#136097](https://github.com/kubernetes/kubernetes/pull/136097), [@atiratree](https://github.com/atiratree)) [SIG Apps] - -### Other (Cleanup or Flake) - -- NONE - NONE ([#136634](https://github.com/kubernetes/kubernetes/pull/136634), [@dims](https://github.com/dims)) [SIG Network and Testing] - -## Dependencies - -### Added -_Nothing has changed._ - -### Changed -- github.com/Microsoft/hnslib: [v0.1.1 → v0.1.2](https://github.com/Microsoft/hnslib/compare/v0.1.1...v0.1.2) - -### Removed -_Nothing has changed._ - - - -# v1.35.0 - -[Documentation](https://docs.k8s.io) - -## Downloads for v1.35.0 - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes.tar.gz) | `478ae8101675fa873a3ad84c81c91604e70bdb947e3379564907916c8a3a1d4a0b7d2077e1d2701f18f2509a6fce0997d93a441ef6d1a17a2e90fdffdd4c13ec` -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-src.tar.gz) | `dc9fc72736999bc40fdf28a7668c8e183effe135893c98f0773b0a50fe018c2f49156026c490f201def57645bf6172c81e07c1c6cb2d80bfb6b246c94fb4c5aa` - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-darwin-amd64.tar.gz) | `e7d510566442afd96dd3759764b573719469bb0ef00086d536bd7af0b8af29ddf150e6ece5ae95856daaaf7f2454f45755ac300648c692508e445aca7a8bd0de` -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-darwin-arm64.tar.gz) | `cd3b216a5418ef2eb00aeb74bf0ebae34c41aa16419bd5bbe5cbb5d394570a38f54c88294aaa5bd7c27ef28c4f1aee2b5658beb4cd025258b6bbd522e8d499bc` -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-linux-386.tar.gz) | `50250aefecc03afe5a6b1be8dffbd58efb4814fed2aae299ac3bbd3b32a40b47697897bafcc36f31f226c5fd2b185cb970e64674aa9ee60412e122128487598d` -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-linux-amd64.tar.gz) | `a1469924896411ab3365628b301d2bbacaf235908cea47308498c9c351a17462ab4154928ef6f91cee849ff52600e394f2abe70f5165371ccfe6638446699d2c` -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-linux-arm.tar.gz) | `df921ad2702a8bc90b8797d97e5ddba5d7d077d18f3b9e53a4594a432f628f52842ee5e26f70c16a82b4decf7c72cba1d04c43163c85026f9b0610fbde63e183` -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-linux-arm64.tar.gz) | `0b332e13c9bb52093f57c4f2ae4ab103bc7f51e4c5dad2859300e7ece09ef303a9345ed3aea4d050b287f52dd8ed8d7cf9185c9e40ea5cc900c8d34e63eec83d` -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-linux-ppc64le.tar.gz) | `07789dc2ec7e8439774d88437f0b1ee35d6b60a8bd23055b93dcf1461de5ae69aba0e0e99a0202892f6c70217388646e1592b087f048bb57e5ab10b1b0dfa956` -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-linux-s390x.tar.gz) | `6563b8d452d29e7f155563294478e39dba7311dd086cf9fb0bc62c94a139b7f5d81a5716880d8072cd864948988e68f2dcd607a8ec79e339224ed5f4bcd48dc9` -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-windows-386.tar.gz) | `522f96799bdaacdd1d10ab4c3a58d8fd86e45e6326c3b6538cc079ca951c28916bd1c8c9bb1d98f6257be0ba1ed91e97614407fe11a1c4bbea2c2052ba0feca7` -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-windows-amd64.tar.gz) | `149145263071c8e1a4d73efe4d1c868286e7cea37629f1c076d2f2683e6b63fb3387d867f3283c9950a3b5b830f005019fa03874e4d53dfa9ad489aaaa9f535b` -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-client-windows-arm64.tar.gz) | `2cffd56e01eaf24ace819cf9f4ef94187185978c8fa1192fd9d47236824ccfe745fe649d38c4351a016e0406bcfd1944178cb93af67b5e69015c04ab2ca5bf7c` - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-server-linux-amd64.tar.gz) | `23af53c49de841a0d5c19d9525d820cecc9d55367c132296a5f381d051438bf06dcddff3d0236df8ba6011a6aa5d0ffc31960d277c7f53a0ad98e66d6f8d6a0a` -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-server-linux-arm64.tar.gz) | `fd245273c6ace20abc893f868d678c4a24c0dbe7d5340087f852d245e59329e66f79afce489dc1b396908d2f005b132eca8d15a7664508fe923627bb2eddee18` -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-server-linux-ppc64le.tar.gz) | `68c48db8537c0470d2245740b8cdf3225efafc48a96646e369137e35931bd43324caf1394ee4b31774b0f43d44e6a4eaa5976186248a114d0e0feb2cb8953edc` -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-server-linux-s390x.tar.gz) | `dd71c4b5ab213452d41059772de3b0db2c71fc6f958280694b2c1b20151bded5b6beb1b03a40dc683ce2d587e9a8bbf3bf486b3965064945803af4f10557558e` - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-node-linux-amd64.tar.gz) | `179278fecb65d246443f58cef00ca2f2a9d0ac6fbdb310994f0ac7fca249f7bdc1c79ea7f3e5455c1e2d2460f5447d006bfa579f97b502ee7034b2a1927f934a` -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-node-linux-arm64.tar.gz) | `01178703c84e0f671770e53024e3cc53f540c0cf93b0804d35884a777c3e3bc44c44d62b6fd25204348986fa589969a9255c0ef04235a0bb9d5560b09867aa0b` -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-node-linux-ppc64le.tar.gz) | `05d1ae963d5c4a382d380cb4f4cdfa924fa8a311953b5eaefe66b8696cebf14bffb13bda8ea784ca5fa1dd073c82ee148faa9a50911449cefad16fe2e800d7c1` -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-node-linux-s390x.tar.gz) | `b7501e91153d062c7c545ef9900faf9b29826b6ff5ec5320f6a799d3d3b479f6ae79092909a1905e055b72dd540a9c8fb02b2d0655f6957cd0b4b7b2e9c18909` -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0/kubernetes-node-windows-amd64.tar.gz) | `f54c606e8ecc29b4ba4ef4570f679352f66cbae1f1bd4f49db5e18227b00ed0e6d8dd47422390fd2a3b87d837cf39dae58a260208096169a3aabef9e874c7586` - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.34.0 - -## Urgent Upgrade Notes - -### (No, really, you MUST read this before you upgrade) - -- ACTION REQUIRED: - - Removed the `--pod-infra-container-image` flag from `kubelet` command line. For `non-kubeadm` clusters, users must manually remove this flag from their `kubelet` configuration to prevent startup failures before upgrading `kubelet`. For `kubeadm` clusters, if users pass extra arguments to the `kubelet` like `--pod-infra-container-image`, it will be written to the `kubelet` env file during the `init` phase. `kubeadm` does not remove it during the `init` or `join` phase, so users must manually remove it from `extraArgs` in the `kubelet` configuration file. ([#133779](https://github.com/kubernetes/kubernetes/pull/133779), [@carlory](https://github.com/carlory)) - - ACTION REQUIRED: - - vendor: Updated `k8s.io/system-validators` to `v1.12.1`. The cgroups validator now throws an error instead of a warning if cgroups v1 is detected on the host and the provided KubeletVersion is `v1.35` or newer. - - kubeadm: Started using `k8s.io/system-validators` `v1.12.1` in `kubeadm` `v1.35`. During `kubeadm init`, `kubeadm join`, and `kubeadm upgrade`, the SystemVerification preflight check throws an error if cgroups v1 is detected and the detected `kubelet` version is `v1.35` or newer. For older versions of `kubelet`, a preflight warning is displayed. - - To allow cgroups v1 with `kubeadm` and `kubelet` version `v1.35` or newer, you must: - - Ignore the error from the SystemVerification preflight check by `kubeadm`. - - Edit the `kube-system/kubelet-config` ConfigMap and add the `failCgroupV1: false` field before upgrading. ([#134744](https://github.com/kubernetes/kubernetes/pull/134744), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle and Node] - -## Changes by Kind - -### Deprecation - -- ACTION REQUIRED: `failCgroupV1` will be set to true from 1.35. - This means that nodes will not start on a cgroup v1 by default. This puts cgroup v1 into a deprecated state. ([#134298](https://github.com/kubernetes/kubernetes/pull/134298), [@kannon92](https://github.com/kannon92)) -- Marked `ipvs` mode in kube-proxy as deprecated, which will be removed in a future version of Kubernetes. Users are encouraged to migrate to `nftables`. ([#134539](https://github.com/kubernetes/kubernetes/pull/134539), [@adrianmoisey](https://github.com/adrianmoisey)) - -### API Change - -- Added `ObservedGeneration` to CustomResourceDefinition conditions. ([#134984](https://github.com/kubernetes/kubernetes/pull/134984), [@michaelasp](https://github.com/michaelasp)) -- Added `WithOrigin` within `apis/core/validation` with adjusted tests. ([#132825](https://github.com/kubernetes/kubernetes/pull/132825), [@PatrickLaabs](https://github.com/PatrickLaabs)) -- Added scoring for the prioritized list feature so nodes that best satisfy the highest-ranked subrequests were chosen. ([#134711](https://github.com/kubernetes/kubernetes/pull/134711), [@mortent](https://github.com/mortent)) [SIG Node, Scheduling and Testing] -- Added the `--min-compatibility-version` flag to `kube-apiserver`, `kube-controller-manager`, and `kube-scheduler`. ([#133980](https://github.com/kubernetes/kubernetes/pull/133980), [@siyuanfoundation](https://github.com/siyuanfoundation)) [SIG API Machinery, Architecture, Cluster Lifecycle, Etcd, Scheduling and Testing] -- Added the `StorageVersionMigration` `v1beta1` API and removed the `v1alpha1` API. - - ACTION REQUIRED: The `v1alpha1` API is no longer supported. Users must remove any `v1alpha1` resources before upgrading. ([#134784](https://github.com/kubernetes/kubernetes/pull/134784), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Auth, Etcd and Testing] -- Added validation to ensure `log-flush-frequency` is a positive value, returning an error instead of causing a panic. ([#133540](https://github.com/kubernetes/kubernetes/pull/133540), [@BenTheElder](https://github.com/BenTheElder)) [SIG Architecture, Instrumentation, Network and Node] -- All containers are restarted when a source container in a restart policy rule exits. This alpha feature is gated behind `RestartAllContainersOnContainerExit`. ([#134345](https://github.com/kubernetes/kubernetes/pull/134345), [@yuanwang04](https://github.com/yuanwang04)) [SIG Apps, Node and Testing] -- CSI drivers can now opt in to receive service account tokens via the secrets field instead of volume context by setting `spec.serviceAccountTokenInSecrets: true` in the CSIDriver object. This prevents tokens from being exposed in logs and other outputs. The feature is gated by the `CSIServiceAccountTokenSecrets` feature gate (beta in `v1.35`). ([#134826](https://github.com/kubernetes/kubernetes/pull/134826), [@aramase](https://github.com/aramase)) [SIG API Machinery, Auth, Storage and Testing] -- Changed kuberc configuration schema. Two new optional fields added to kuberc configuration, `credPluginPolicy` and `credPluginAllowlist`. This is documented in [KEP-3104](https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/3104-introduce-kuberc/README.md#allowlist-design-details) and documentation is added to the website by [kubernetes/website#52877](https://github.com/kubernetes/website/pull/52877) ([#134870](https://github.com/kubernetes/kubernetes/pull/134870), [@pmengelbert](https://github.com/pmengelbert)) [SIG API Machinery, Architecture, Auth, CLI, Instrumentation and Testing] -- DRA device taints: `DeviceTaintRule` status provides information about the rule, including whether Pods still need to be evicted (`EvictionInProgress` condition). The newly added `None` effect can be used to preview what a `DeviceTaintRule` would do if it used the `NoExecute` effect and to taint devices (`device health`) without immediately affecting scheduling or running Pods. ([#134152](https://github.com/kubernetes/kubernetes/pull/134152), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Node, Release, Scheduling and Testing] -- DRA: The `DynamicResourceAllocation` feature gate for the core functionality (GA in `v1.34`) has now been locked to enabled-by-default and cannot be disabled anymore. ([#134452](https://github.com/kubernetes/kubernetes/pull/134452), [@pohly](https://github.com/pohly)) [SIG Auth, Node, Scheduling and Testing] -- Enabled `kubectl get -o kyaml` by default. To disable it, set `KUBECTL_KYAML=false`. ([#133327](https://github.com/kubernetes/kubernetes/pull/133327), [@thockin](https://github.com/thockin)) -- Enabled in-place resizing of pod-level resources. - - Added `Resources` in `PodStatus` to capture resources set in the pod-level cgroup. - - Added `AllocatedResources` in `PodStatus` to capture resources requested in the `PodSpec`. ([#132919](https://github.com/kubernetes/kubernetes/pull/132919), [@ndixita](https://github.com/ndixita)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Instrumentation, Node, Scheduling and Testing] -- Enabled the `NominatedNodeNameForExpectation` feature in kube-scheduler by default. - - Enabled the `ClearingNominatedNodeNameAfterBinding` feature in kube-apiserver by default. ([#135103](https://github.com/kubernetes/kubernetes/pull/135103), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG API Machinery, Apps, Architecture, Auth, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Etcd, Instrumentation, Network, Node, Scheduling, Storage and Testing] -- Enhanced discovery responses to merge API groups and resources from all peer apiservers when the `UnknownVersionInteroperabilityProxy` feature is enabled. ([#133648](https://github.com/kubernetes/kubernetes/pull/133648), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Auth, Cloud Provider, Node, Scheduling and Testing] -- Extended `core/v1` `Toleration` to support numeric comparison operators (`Gt`,`Lt`). ([#134665](https://github.com/kubernetes/kubernetes/pull/134665), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Node, Scheduling, Testing and Windows] -- Feature gate dependencies are now explicit, and validated at startup. A feature can no longer be enabled if it depends on a disabled feature. In particular, this means that `AllAlpha=true` will no longer work without enabling disabled-by-default beta features that are depended on (either with `AllBeta=true` or explicitly enumerating the disabled dependencies). ([#133697](https://github.com/kubernetes/kubernetes/pull/133697), [@tallclair](https://github.com/tallclair)) [SIG API Machinery, Architecture, Cluster Lifecycle and Node] -- Generated OpenAPI model packages for API types into `zz_generated.model_name.go` files, accessible via the `OpenAPIModelName()` function. This allows API authors to declare desired OpenAPI model packages instead of relying on the Go package path of API types. ([#131755](https://github.com/kubernetes/kubernetes/pull/131755), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling, Storage and Testing] -- Implemented constrained impersonation as described in [KEP-5284](https://kep.k8s.io/5284). ([#134803](https://github.com/kubernetes/kubernetes/pull/134803), [@enj](https://github.com/enj)) [SIG API Machinery, Auth and Testing] -- Introduced a new declarative validation tag `+k8s:customUnique` to control listmap uniqueness. ([#134279](https://github.com/kubernetes/kubernetes/pull/134279), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery and Auth] -- Introduced a structured and versioned `v1alpha1` response for the `statusz` endpoint. ([#134313](https://github.com/kubernetes/kubernetes/pull/134313), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation, Network, Node, Scheduling and Testing] -- Introduced a structured and versioned `v1alpha1` response format for the `flagz` endpoint. ([#134995](https://github.com/kubernetes/kubernetes/pull/134995), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Architecture, Instrumentation, Network, Node, Scheduling and Testing] -- Introduced the GangScheduling kube-scheduler plugin to support "all-or-nothing" scheduling using the `scheduling.k8s.io/v1alpha1` Workload API. ([#134722](https://github.com/kubernetes/kubernetes/pull/134722), [@macsko](https://github.com/macsko)) [SIG API Machinery, Apps, Auth, CLI, Etcd, Scheduling and Testing] -- Introduced the Node Declared Features capability (alpha), which includes: - - A new `Node.Status.DeclaredFeatures` field for publishing node-specific features. - - A `component-helpers` library for feature registration and inference. - - A `NodeDeclaredFeatures` scheduler plugin to match pods with nodes that provide required features. - - A `NodeDeclaredFeatureValidator` admission plugin to validate pod updates against a node's declared features. ([#133389](https://github.com/kubernetes/kubernetes/pull/133389), [@pravk03](https://github.com/pravk03)) [SIG API Machinery, Apps, Node, Release, Scheduling and Testing] -- Introduced the `scheduling.k8s.io/v1alpha1` Workload API to express workload-level scheduling requirements and allow the kube-scheduler to act on them. ([#134564](https://github.com/kubernetes/kubernetes/pull/134564), [@macsko](https://github.com/macsko)) [SIG API Machinery, Apps, CLI, Etcd, Scheduling and Testing] -- Introduced the alpha `MutableSchedulingDirectivesForSuspendedJobs` feature gate (disabled by default), which allows mutating a Job's scheduling directives while the Job is suspended. - It also updates the Job controller to clears the `status.startTime` field for suspended Jobs. ([#135104](https://github.com/kubernetes/kubernetes/pull/135104), [@mimowo](https://github.com/mimowo)) [SIG Apps and Testing] -- Kube-apiserver: Fixed a `v1.34` regression in `CustomResourceDefinition` handling that incorrectly warned about unrecognized formats on number and integer properties. ([#133896](https://github.com/kubernetes/kubernetes/pull/133896), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cloud Provider, Contributor Experience, Network, Node and Scheduling] -- Kube-apiserver: Fixed a possible panic validating a custom resource whose `CustomResourceDefinition` indicates a status subresource exists, but which does not define a `status` property in the `openAPIV3Schema`. ([#133721](https://github.com/kubernetes/kubernetes/pull/133721), [@fusida](https://github.com/fusida)) [SIG API Machinery, Apps, Architecture, Auth, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Etcd, Instrumentation, Network, Node, Release, Scheduling, Storage and Testing] -- Kubernetes API Go types removed runtime use of the `github.com/gogo/protobuf` library, and are no longer registered into the global gogo type registry. Kubernetes API Go types were not suitable for use with the `google.golang.org/protobuf` library, and no longer implement `ProtoMessage()` by default to avoid accidental incompatible use. If removal of these marker methods impacts your use, it can be re-enabled for one more release with a `kubernetes_protomessage_one_more_release` build tag, but will be removed in `v1.36`. ([#134256](https://github.com/kubernetes/kubernetes/pull/134256), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling and Storage] -- Made node affinity in Persistent Volume mutable. ([#134339](https://github.com/kubernetes/kubernetes/pull/134339), [@huww98](https://github.com/huww98)) [SIG API Machinery, Apps and Node] -- Moved the `ImagePullIntent` and `ImagePulledRecord` objects used by the kubelet to track image pulls to the `v1beta1` API version. ([#132579](https://github.com/kubernetes/kubernetes/pull/132579), [@stlaz](https://github.com/stlaz)) [SIG Auth and Node] -- Pod resize now only allows CPU and memory resources; other resource types are forbidden. ([#135084](https://github.com/kubernetes/kubernetes/pull/135084), [@tallclair](https://github.com/tallclair)) [SIG Apps, Node and Testing] -- Prevented Pods from being scheduled onto nodes that lack the required CSI driver. ([#135012](https://github.com/kubernetes/kubernetes/pull/135012), [@gnufied](https://github.com/gnufied)) [SIG API Machinery, Scheduling, Storage and Testing] -- Promoted HPA configurable tolerance to beta. The `HPAConfigurableTolerance` feature gate has now been enabled by default. ([#133128](https://github.com/kubernetes/kubernetes/pull/133128), [@jm-franc](https://github.com/jm-franc)) [SIG API Machinery and Autoscaling] -- Promoted ReplicaSet and Deployment `.status.terminatingReplicas` tracking to beta. The `DeploymentReplicaSetTerminatingReplicas` feature gate is now enabled by default. ([#133087](https://github.com/kubernetes/kubernetes/pull/133087), [@atiratree](https://github.com/atiratree)) [SIG API Machinery, Apps and Testing] -- Promoted `PodObservedGenerationTracking` to GA. ([#134948](https://github.com/kubernetes/kubernetes/pull/134948), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Apps, Node, Scheduling and Testing] -- Promoted the `JobManagedBy` feature to general availability. The `JobManagedBy` feature gate was locked to `true` and will be removed in a future Kubernetes release. ([#135080](https://github.com/kubernetes/kubernetes/pull/135080), [@dejanzele](https://github.com/dejanzele)) [SIG API Machinery, Apps and Testing] -- Promoted the `MaxUnavailableStatefulSet` feature to beta and enabling it by default. ([#133153](https://github.com/kubernetes/kubernetes/pull/133153), [@helayoty](https://github.com/helayoty)) [SIG API Machinery and Apps] -- Removed the `StrictCostEnforcementForVAP` and `StrictCostEnforcementForWebhooks` feature gates, which were locked since `v1.32`. ([#134994](https://github.com/kubernetes/kubernetes/pull/134994), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Auth, Node and Testing] -- Scheduler: Added the `bindingTimeout` argument to the DynamicResources plugin configuration, allowing customization of the wait duration in `PreBind` for device binding conditions. - Defaults to 10 minutes when `DRADeviceBindingConditions` and `DRAResourceClaimDeviceStatus` are both enabled. ([#134905](https://github.com/kubernetes/kubernetes/pull/134905), [@fj-naji](https://github.com/fj-naji)) [SIG Node and Scheduling] -- The DRA device taints and toleration feature received a separate feature gate, `DRADeviceTaintRules`, which controlled support for `DeviceTaintRules`. This allowed disabling it while keeping `DRADeviceTaints` enabled so that tainting via `ResourceSlices` continued to work. ([#135068](https://github.com/kubernetes/kubernetes/pull/135068), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Node, Scheduling and Testing] -- The Pod Certificates feature moved to beta. The `PodCertificateRequest` feature gate is set disabled by default. To use the feature, users must enable the certificates API groups in `v1beta1` and enable the `PodCertificateRequest` feature gate. The `UserAnnotations` field was added to the `PodCertificateProjection` API and the corresponding `UnverifiedUserAnnotations` field was added to the `PodCertificateRequest` API. ([#134624](https://github.com/kubernetes/kubernetes/pull/134624), [@yt2985](https://github.com/yt2985)) [SIG API Machinery, Apps, Auth, Etcd, Instrumentation, Node and Testing] -- The `KubeletEnsureSecretPulledImages` feature was promoted to Beta and enabled by default. ([#135228](https://github.com/kubernetes/kubernetes/pull/135228), [@aramase](https://github.com/aramase)) [SIG Auth, Node and Testing] -- The `PreferSameZone` and `PreferSameNode` values for the Service - `trafficDistribution` field graduated to general availability. The - `PreferClose` value is now deprecated in favor of the more explicit - `PreferSameZone`. ([#134457](https://github.com/kubernetes/kubernetes/pull/134457), [@danwinship](https://github.com/danwinship)) [SIG API Machinery, Apps, Network and Testing] -- Updated `ResourceQuota` to count device class requests within a `ResourceClaim` as two additional quotas when the `DRAExtendedResource` feature is enabled: - - `requests.deviceclass.resource.k8s.io/` is charged based on the worst-case number of devices requested. - - Device classes mapping to an extended resource now consume `requests.`. ([#134210](https://github.com/kubernetes/kubernetes/pull/134210), [@yliaog](https://github.com/yliaog)) [SIG API Machinery, Apps, Node, Scheduling and Testing] -- Updated storage version for `MutatingAdmissionPolicy` to `v1beta1`. ([#133715](https://github.com/kubernetes/kubernetes/pull/133715), [@cici37](https://github.com/cici37)) [SIG API Machinery, Etcd and Testing] -- Updated the Partitionable Devices feature to support referencing counter sets across ResourceSlices within the same resource pool. Devices from incomplete pools were no longer considered for allocation. This change introduced backwards-incompatible updates to the alpha feature, requiring any ResourceSlices using it to be removed before upgrading or downgrading between v1.34 and v1.35. ([#134189](https://github.com/kubernetes/kubernetes/pull/134189), [@mortent](https://github.com/mortent)) [SIG API Machinery, Node, Scheduling and Testing] -- Upgraded the `PodObservedGenerationTracking` feature to beta in `v1.34` and removed the alpha version description from the OpenAPI specification. ([#133883](https://github.com/kubernetes/kubernetes/pull/133883), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) - -### Feature - -- Added `k8s-short-name` and `k8s-long-name` format validation tags to enforce DNS label and DNS subdomain compliance. ([#133894](https://github.com/kubernetes/kubernetes/pull/133894), [@lalitc375](https://github.com/lalitc375)) -- Added `kubectl kuberc view` and `kubectl kuberc set` commands to perform operations against the `kuberc` file. ([#135003](https://github.com/kubernetes/kubernetes/pull/135003), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] -- Added `kubelet` stress test for pod cleanup when rejection due to `VolumeAttachmentLimitExceeded`. ([#133357](https://github.com/kubernetes/kubernetes/pull/133357), [@torredil](https://github.com/torredil)) [SIG Node and Storage] -- Added `paths` section to kubelet `statusz` endpoint. ([#133239](https://github.com/kubernetes/kubernetes/pull/133239), [@Peac36](https://github.com/Peac36)) -- Added a `source` label to the `resourceclaim_controller_resource_claims` metric. - Added the `scheduler_resourceclaim_creates_total` metric for `DRAExtendedResource`. ([#134523](https://github.com/kubernetes/kubernetes/pull/134523), [@bitoku](https://github.com/bitoku)) [SIG Apps, Instrumentation, Node and Scheduling] -- Added a counter metric `kubelet_image_manager_ensure_image_requests_total{present_locally, pull_policy, pull_required}` that exposes details about `kubelet` ensuring an image exists on the node. ([#132644](https://github.com/kubernetes/kubernetes/pull/132644), [@stlaz](https://github.com/stlaz)) [SIG Auth and Node] -- Added additional event emissions during Pod resizing to provide clearer visibility when a Pod’s resize status changes. ([#134825](https://github.com/kubernetes/kubernetes/pull/134825), [@natasha41575](https://github.com/natasha41575)) -- Added configurable per-device health check timeouts to the DRA health monitoring API. ([#135147](https://github.com/kubernetes/kubernetes/pull/135147), [@harche](https://github.com/harche)) [SIG Node] -- Added metrics for the `MaxUnavailable` feature in `StatefulSet`. ([#130951](https://github.com/kubernetes/kubernetes/pull/130951), [@Edwinhr716](https://github.com/Edwinhr716)) [SIG Apps and Instrumentation] -- Added paths section to scheduler `statusz` endpoint. ([#132606](https://github.com/kubernetes/kubernetes/pull/132606), [@Peac36](https://github.com/Peac36)) [SIG API Machinery, Architecture, Instrumentation, Network, Node, Scheduling and Testing] -- Added remote runtime and image `Close()` method to be able to close the connection. ([#133211](https://github.com/kubernetes/kubernetes/pull/133211), [@saschagrunert](https://github.com/saschagrunert)) [SIG Node] -- Added support for tracing in `kubectl` with the `--profile=trace` flag. ([#134709](https://github.com/kubernetes/kubernetes/pull/134709), [@tchap](https://github.com/tchap)) -- Added support for validating UUID format. ([#133948](https://github.com/kubernetes/kubernetes/pull/133948), [@lalitc375](https://github.com/lalitc375)) -- Added the `-n` flag as a shorthand for `--namespace` in the `kubectl config set-context` command. ([#134384](https://github.com/kubernetes/kubernetes/pull/134384), [@tchap](https://github.com/tchap)) [SIG CLI and Testing] -- Added the `ChangeContainerStatusOnKubeletRestart` feature gate, which defaults to disabled. When the feature gate is disabled, `kubelet` does not change the Pod status upon restart, and Pods do not re-run startup probes after the `kubelet` restarts. ([#134746](https://github.com/kubernetes/kubernetes/pull/134746), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node and Testing] -- Added the `CloudControllerManagerWatchBasedRoutesReconciliation` feature gate. ([#131220](https://github.com/kubernetes/kubernetes/pull/131220), [@lukasmetzner](https://github.com/lukasmetzner)) [SIG API Machinery and Cloud Provider] -- Added the `UserNamespacesHostNetworkSupport` feature gate. This gate is disabled by default, and when enabled, allowed `hostNetwork` pods to use user namespaces. ([#134893](https://github.com/kubernetes/kubernetes/pull/134893), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Apps, Node and Testing] -- After fixing regressions detected in `v1.34`, the `SchedulerAsyncAPICalls` feature gate was re-enabled by default. ([#135059](https://github.com/kubernetes/kubernetes/pull/135059), [@macsko](https://github.com/macsko)) -- Changed `WaitForNamedCacheSync` to `WaitForNamedCacheSyncWithContext`. ([#133904](https://github.com/kubernetes/kubernetes/pull/133904), [@aditigupta96](https://github.com/aditigupta96)) [SIG API Machinery, Apps, Auth and Network] -- DRA: the resource.k8s.io API now uses the v1 API version (introduced in 1.34) as default storage version. Downgrading to 1.33 is not supported. ([#133876](https://github.com/kubernetes/kubernetes/pull/133876), [@kei01234kei](https://github.com/kei01234kei)) [SIG API Machinery, Etcd and Testing] -- Enabled the `MutableCSINodeAllocatableCount` feature gate by default in beta. ([#134647](https://github.com/kubernetes/kubernetes/pull/134647), [@torredil](https://github.com/torredil)) -- Enabled the `WatchListClient` feature gate. ([#134180](https://github.com/kubernetes/kubernetes/pull/134180), [@p0lyn0mial](https://github.com/p0lyn0mial)) [SIG API Machinery, Apps, Auth, CLI, Instrumentation, Node and Testing] -- Enabled the feature gate `ContainerRestartRules` by default. The `ContainerRestartRules` feature has been promoted to beta. Fixed a bug in this feature that caused probes to continue to run even if the container has terminated and is not restartable. ([#134631](https://github.com/kubernetes/kubernetes/pull/134631), [@yuanwang04](https://github.com/yuanwang04)) -- Graduated the `PodTopologyLabelsAdmission` feature gate to Beta and enabled it by default. - Pods now receive `topology.kubernetes.io/zone` and `topology.kubernetes.io/region` labels automatically when their assigned Node has these labels. ([#135158](https://github.com/kubernetes/kubernetes/pull/135158), [@andrewsykim](https://github.com/andrewsykim)) -- Graduated the fine-grained supplemental groups policy (KEP-3619) to GA. ([#135088](https://github.com/kubernetes/kubernetes/pull/135088), [@everpeace](https://github.com/everpeace)) [SIG Node and Testing] -- Graduated the image volume source feature to Beta and enabled it by default. ([#135195](https://github.com/kubernetes/kubernetes/pull/135195), [@haircommander](https://github.com/haircommander)) [SIG Apps, Instrumentation, Node and Testing] -- Implemented opportunistic batching (KEP-5598) to optimize scheduling for pods with identical scheduling requirements. ([#135231](https://github.com/kubernetes/kubernetes/pull/135231), [@bwsalmon](https://github.com/bwsalmon)) [SIG Node, Scheduling, Storage and Testing] -- Implemented scoring for DRA-backed extended resources. ([#134058](https://github.com/kubernetes/kubernetes/pull/134058), [@bart0sh](https://github.com/bart0sh)) [SIG Node, Scheduling and Testing] -- Improved throughput in the `real-FIFO` queue used by `informers` and `controllers` by adding batch handling for processing watch events. ([#132240](https://github.com/kubernetes/kubernetes/pull/132240), [@yue9944882](https://github.com/yue9944882)) [SIG API Machinery, Scheduling and Storage] -- Introduced end-to-end tests to verify component invariant metrics across the entire test suite. ([#133394](https://github.com/kubernetes/kubernetes/pull/133394), [@BenTheElder](https://github.com/BenTheElder)) -- Introduced new kubelet metrics for the Ensure Secret Pulled Images KEP, including: - - `kubelet_imagemanager_ondisk_pullintents` for tracking pull intent records on disk - - `kubelet_imagemanager_ondisk_pulledrecords` for tracking pulled image records on disk - - `kubelet_imagemanager_image_mustpull_checks_total{result}` for counting image must-pull verification checks. ([#132812](https://github.com/kubernetes/kubernetes/pull/132812), [@stlaz](https://github.com/stlaz)) [SIG Auth and Node] -- Introduced the `--as-user-extra` persistent flag in `kubectl`, which allows passing extra arguments during impersonation. ([#134378](https://github.com/kubernetes/kubernetes/pull/134378), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] -- K8s.io/apimachinery: Introduced a helper function to compare `resourceVersion` strings between two objects of the same resource. ([#134330](https://github.com/kubernetes/kubernetes/pull/134330), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Auth, Instrumentation, Network, Node, Scheduling, Storage and Testing] -- KEP-5440: Enabled support for resizing resources while a Job is suspended. This feature is alpha. ([#132441](https://github.com/kubernetes/kubernetes/pull/132441), [@kannon92](https://github.com/kannon92)) [SIG Apps and Testing] -- Kube-apiserver: Made the subresources `pods/exec`, `pods/attach`, and `pods/portforward` require `create` permission for both SPDY and Websocket API requests. Previously, SPDY requests required `create` permission, but Websocket requests only required `get` permission. This change is gated by the `AuthorizePodWebsocketUpgradeCreatePermission` feature-gate, which is enabled by default. - - Before upgrading to 1.35, ensure any custom ClusterRoles and Roles intended to grant `pods/exec`, `pods/attach`, or `pods/portforward` permission include the `create` verb. ([#134577](https://github.com/kubernetes/kubernetes/pull/134577), [@seans3](https://github.com/seans3)) [SIG API Machinery, Auth, Node and Testing] -- Kubeadm: Added error printing during retries related to the `WaitForAllControlPlaneComponents` functionality at verbosity level 5. ([#134433](https://github.com/kubernetes/kubernetes/pull/134433), [@neolit123](https://github.com/neolit123)) -- Kubeadm: Added the `HTTPEndpoints` field to `ClusterConfiguration.Etcd.ExternalEtcd` to configure HTTP endpoints for etcd communication in v1beta4. This separates HTTP traffic (e.g., `/metrics`, `/health`) from gRPC traffic, improving access control. Mirrors etcd’s `--listen-client-http-urls` behavior; if not set, the `Endpoints` field handles both traffic types. ([#134890](https://github.com/kubernetes/kubernetes/pull/134890), [@SataQiu](https://github.com/SataQiu)) -- Kubeadm: Graduated the kubeadm-specific feature gate `ControlPlaneKubeletLocalMode` to GA and locked it to enabled by default. To opt out, patch the `server` field in `/etc/kubernetes/kubelet.conf`. Deprecated the subphase of `kubeadm join phase control-plane-join` called `etcd`, which is now hidden and replaced by subphase with identical functionality `etcd-join`. The `etcd` subphase will be removed in a future release. The subphase `kubelet-wait-bootstrap` of `kubeadm join` is no longer experimental and will now always run. ([#134106](https://github.com/kubernetes/kubernetes/pull/134106), [@neolit123](https://github.com/neolit123)) -- Kubernetes is now built using Go 1.25.1 ([#134095](https://github.com/kubernetes/kubernetes/pull/134095), [@dims](https://github.com/dims)) [SIG Release and Testing] -- Kubernetes is now built using Go 1.25.4 ([#135492](https://github.com/kubernetes/kubernetes/pull/135492), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] -- Kubernetes now uses Go Language Version 1.25, including https://go.dev/blog/container-aware-gomaxprocs ([#134120](https://github.com/kubernetes/kubernetes/pull/134120), [@BenTheElder](https://github.com/BenTheElder)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Release, Scheduling and Storage] -- Locked down the `AllowOverwriteTerminationGracePeriodSeconds` feature gate. ([#133792](https://github.com/kubernetes/kubernetes/pull/133792), [@HirazawaUi](https://github.com/HirazawaUi)) -- Locked the (generally available) feature gate `ExecProbeTimeout` to true. ([#134635](https://github.com/kubernetes/kubernetes/pull/134635), [@vivzbansal](https://github.com/vivzbansal)) [SIG Node and Testing] -- Metrics: Excluded `dryRun` requests from `apiserver_request_sli_duration_seconds`. ([#131092](https://github.com/kubernetes/kubernetes/pull/131092), [@aldudko](https://github.com/aldudko)) [SIG API Machinery and Instrumentation] -- Migrated validation in `resource.k8s.io` to declarative validation. - When the `DeclarativeValidation` feature gate is enabled, mismatches with existing validation are reported via metrics. - when `DeclarativeValidationTakeover` feature gate is enabled, declarative validation becomes the primary source of errors for migrated fields. ([#134072](https://github.com/kubernetes/kubernetes/pull/134072), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Apps and Auth] -- Moved the Pod Certificates feature to beta. Added `UserAnnotations` to the `PodCertificateProjection` API and `UnverifiedUserAnnotations` to the `PodCertificateRequest` API. The `PodCertificateRequest` feature gate remains disabled by default and requires enabling the v1beta1 certificates API groups. ([#134790](https://github.com/kubernetes/kubernetes/pull/134790), [@yt2985](https://github.com/yt2985)) [SIG Auth, Instrumentation and Testing] -- Promoted `ImageGCMaximumAge` to stable. ([#134736](https://github.com/kubernetes/kubernetes/pull/134736), [@haircommander](https://github.com/haircommander)) [SIG Node and Testing] -- Promoted `InPlacePodVerticalScaling` to GA. ([#134949](https://github.com/kubernetes/kubernetes/pull/134949), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Node and Scheduling] -- Promoted `kubectl` command headers to stable. ([#134777](https://github.com/kubernetes/kubernetes/pull/134777), [@soltysh](https://github.com/soltysh)) [SIG CLI and Testing] -- Promoted the `EnvFiles` feature gate to beta and is enabled by default. Additionally, the syntax specification for environment variables has been restricted to a subset of POSIX shell syntax (all variable values must be wrapped in single quotes). ([#134414](https://github.com/kubernetes/kubernetes/pull/134414), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node and Testing] -- Promoted the `HostnameOverride` feature gate to beta and enabled it by default. ([#134729](https://github.com/kubernetes/kubernetes/pull/134729), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Network and Node] -- Promoted the `KubeletCrashLoopBackOffMax` feature gate to beta and enabled it by default. ([#135044](https://github.com/kubernetes/kubernetes/pull/135044), [@hankfreund](https://github.com/hankfreund)) -- Selected a single device class deterministically when multiple device classes were available for an extended resource. ([#135037](https://github.com/kubernetes/kubernetes/pull/135037), [@yliaog](https://github.com/yliaog)) [SIG Node, Scheduling and Testing] -- The JWT authenticator in `kube-apiserver` now reports the following metrics when the `StructuredAuthenticationConfiguration` feature gate is enabled: - - `apiserver_authentication_jwt_authenticator_jwks_fetch_last_timestamp_seconds` - - `apiserver_authentication_jwt_authenticator_jwks_fetch_last_key_set_info`. ([#123642](https://github.com/kubernetes/kubernetes/pull/123642), [@aramase](https://github.com/aramase)) [SIG API Machinery, Auth and Testing] -- The scheduler now clears the `nominatedNodeName` field for Pods upon scheduling or binding failure. External components, such as Cluster Autoscaler and Karpenter, should not overwrite this field. ([#135007](https://github.com/kubernetes/kubernetes/pull/135007), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Scheduling and Testing] -- Updated `applyconfiguration-gen` to generate extract functions for all subresources. ([#132665](https://github.com/kubernetes/kubernetes/pull/132665), [@mrIncompetent](https://github.com/mrIncompetent)) -- Updated `applyconfiguration-gen` to preserve struct and field comments from source types in the generated code. ([#132663](https://github.com/kubernetes/kubernetes/pull/132663), [@mrIncompetent](https://github.com/mrIncompetent)) -- Updated `kubectl describe pods` to include the involved object’s `fieldPath` (e.g., container name) in event messages, providing better context for debugging multi-container Pods. Note: This changes the previous message format for events that include a `fieldPath`. ([#133627](https://github.com/kubernetes/kubernetes/pull/133627), [@itzPranshul](https://github.com/itzPranshul)) -- Updated sandbox ordering to use by attempt count or creation time. ([#130551](https://github.com/kubernetes/kubernetes/pull/130551), [@yylt](https://github.com/yylt)) -- Updated the Kubernetes build to use Go `1.25.4`. ([#135187](https://github.com/kubernetes/kubernetes/pull/135187), [@BenTheElder](https://github.com/BenTheElder)) -- Updated underlying images and dependencies to be compatible with Go version`1.25.3`. ([#134611](https://github.com/kubernetes/kubernetes/pull/134611), [@cpanato](https://github.com/cpanato)) [SIG Architecture, Cloud Provider, Etcd, Release, Storage and Testing] -- `kubeadm`: Added a preflight check `ContainerRuntimeVersion` to validate if the installed container runtime supports the `RuntimeConfig` gRPC method. If unsupported, `kubeadm` prints a warning message. - - Starting with Kubernetes `v1.36`, `kubelet` might refuse to start if the CRI runtime does not support this feature. More information can be found at the [Kubernetes blog](https://kubernetes.io/blog/2025/09/12/kubernetes-v1-34-cri-cgroup-driver-lookup-now-ga/). ([#134906](https://github.com/kubernetes/kubernetes/pull/134906), [@carlory](https://github.com/carlory)) - -- Kubernetes is now built using Go `1.25.5`. ([#135609](https://github.com/kubernetes/kubernetes/pull/135609), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] - -### Documentation - -- Promoted the `--chunk-size` flag to stable. The kubectl `describe`, `get`, `drain`, and `events` commands can use `--chunk-size` flag to set chunk size. ([#134481](https://github.com/kubernetes/kubernetes/pull/134481), [@soltysh](https://github.com/soltysh)) - -### Bug or Regression - -- Added support for Pods to reference the same `PersistentVolumeClaim` across multiple volumes. ([#122140](https://github.com/kubernetes/kubernetes/pull/122140), [@huww98](https://github.com/huww98)) [SIG Node, Storage and Testing] -- Added support for the `ShareID` field of the `DRAConsumableCapacity` feature in the Kubelet Plugin API. ([#134520](https://github.com/kubernetes/kubernetes/pull/134520), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node and Testing] -- Added the correct error when eviction is blocked due to the failSafe mechanism of the `DisruptionController`. ([#133097](https://github.com/kubernetes/kubernetes/pull/133097), [@kei01234kei](https://github.com/kei01234kei)) [SIG Apps and Node] -- Changed `kubectl exec` syntax to require `--` before the command. The form `kubectl exec [POD] [COMMAND]` is no longer supported; use `kubectl exec [POD] -- [COMMAND]` instead. ([#133841](https://github.com/kubernetes/kubernetes/pull/133841), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) -- DRA API: Fixed the `tolerations` field in exact and sub requests to drop properly when the `DRADeviceTaints` API is disabled. ([#132927](https://github.com/kubernetes/kubernetes/pull/132927), [@pohly](https://github.com/pohly)) -- DRA Device Taints: Fixed toleration of `NoExecute`. Prior to this enhancement, tolerating a `NoExecute` did not work because the scheduler did not inform the eviction controller about the toleration, so the scheduled pod got evicted almost immediately. ([#134479](https://github.com/kubernetes/kubernetes/pull/134479), [@pohly](https://github.com/pohly)) [SIG Apps, Node, Scheduling and Testing] -- Deprecated metrics will be hidden as per the metrics deprecation policy. https://kubernetes.io/docs/reference/using-api/deprecation-policy/#deprecating-a-metric . ([#133436](https://github.com/kubernetes/kubernetes/pull/133436), [@richabanker](https://github.com/richabanker)) [SIG Architecture, Instrumentation and Network] -- Disabled the `SchedulerAsyncAPICalls` feature gate to mitigate a bug where its interaction with asynchronous preemption could degrade `kube-scheduler` performance, especially under high `kube-apiserver` load. ([#134400](https://github.com/kubernetes/kubernetes/pull/134400), [@macsko](https://github.com/macsko)) -- Dropped `DeviceBindingConditions` fields when the `DRADeviceBindingConditions` feature gate is not enabled and not in use. ([#134964](https://github.com/kubernetes/kubernetes/pull/134964), [@sunya-ch](https://github.com/sunya-ch)) -- Extended resources requested by initContainers which are allocated using an automatic ResourceClaim now match the behavior of legacy device plugins, reusing the same resources requested by later sidecar initContainers or regular containers when possible, to minimize the total number of devices requested by the pod. ([#134882](https://github.com/kubernetes/kubernetes/pull/134882), [@yliaog](https://github.com/yliaog)) [SIG Apps, CLI, Node, Scheduling and Testing] -- Fixed SELinux warning controller not emitting events on some SELinux label conflicts. ([#133425](https://github.com/kubernetes/kubernetes/pull/133425), [@jsafrane](https://github.com/jsafrane)) [SIG Apps, Storage and Testing] -- Fixed `replicaCount` calculation exceeding max `int32`. ([#126979](https://github.com/kubernetes/kubernetes/pull/126979), [@omerap12](https://github.com/omerap12)) [SIG Apps and Autoscaling] -- Fixed a Windows kube-proxy (winkernel) issue where stale `RemoteEndpoints` - remained when a Deployment was referenced by multiple Services due to premature - clearing of the `terminatedEndpoints` map. ([#135146](https://github.com/kubernetes/kubernetes/pull/135146), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] -- Fixed a bug in `ValidatingAdmissionPolicy` where schemas with `additionalProperties: true` could cause the kube-controller-manager to crash with a nil pointer exception. ([#135155](https://github.com/kubernetes/kubernetes/pull/135155), [@jpbetz](https://github.com/jpbetz)) -- Fixed a bug in `kube-proxy` `nftables` mode (GA as of `v1.33`) which fails to determine if traffic originates from a local source on the node. The issue was caused by using the wrong meta `iif` instead of `iifname` for name based matches. ([#134024](https://github.com/kubernetes/kubernetes/pull/134024), [@jack4it](https://github.com/jack4it)) -- Fixed a bug in `kube-scheduler` where pending pod preemption caused preemptor pods to be retried more frequently. ([#134245](https://github.com/kubernetes/kubernetes/pull/134245), [@macsko](https://github.com/macsko)) [SIG Scheduling and Testing] -- Fixed a bug that caused apiservers to send an inappropriate Content-Type request header to authorization, token authentication, imagepolicy admission, and audit webhooks when the alpha client-go feature gate "ClientsPreferCBOR" is enabled. ([#132960](https://github.com/kubernetes/kubernetes/pull/132960), [@benluddy](https://github.com/benluddy)) [SIG API Machinery and Node] -- Fixed a bug that caused duplicate validation when updating `PersistentVolumeClaims`, `VolumeAttachments` and `VolumeAttributesClasses`. ([#132549](https://github.com/kubernetes/kubernetes/pull/132549), [@gavinkflam](https://github.com/gavinkflam)) -- Fixed a bug that caused duplicate validation when updating `Role` and `RoleBinding` resources. ([#132550](https://github.com/kubernetes/kubernetes/pull/132550), [@gavinkflam](https://github.com/gavinkflam)) -- Fixed a bug that prevented allocating the same device that was previously consuming the `CounterSet` when both `DRAConsumableCapacity` and `DRAPartitionableDevices` were enabled. ([#134103](https://github.com/kubernetes/kubernetes/pull/134103), [@sunya-ch](https://github.com/sunya-ch)) -- Fixed a bug that prevents scheduling the next pod when using the `DRAConsumableCapacity` feature. ([#133706](https://github.com/kubernetes/kubernetes/pull/133706), [@sunya-ch](https://github.com/sunya-ch)) -- Fixed a bug to prevent segmentation fault from occurring when updating deeply nested JSON fields. ([#134381](https://github.com/kubernetes/kubernetes/pull/134381), [@kon-angelo](https://github.com/kon-angelo)) [SIG API Machinery and CLI] -- Fixed a bug where 64-bit IPv6 `ServiceCIDRs` allocated addresses outside the subnet range. ([#134193](https://github.com/kubernetes/kubernetes/pull/134193), [@hoskeri](https://github.com/hoskeri)) -- Fixed a bug where Job status updates fail after resuming a Job that was previously started and suspended. - The error message was: `status.startTime: Required value: startTime cannot be removed for unsuspended job`. ([#134769](https://github.com/kubernetes/kubernetes/pull/134769), [@dejanzele](https://github.com/dejanzele)) [SIG Apps and Testing] -- Fixed a bug where `AllocationMode: All` would not succeed if a resource pool contained `ResourceSlices` that were not targeting the current node. ([#134466](https://github.com/kubernetes/kubernetes/pull/134466), [@mortent](https://github.com/mortent)) -- Fixed a bug where a deleted Pod in the binding phase continued to occupy space on the node in `kube-scheduler`. ([#134157](https://github.com/kubernetes/kubernetes/pull/134157), [@macsko](https://github.com/macsko)) [SIG Scheduling and Testing] -- Fixed a bug where high latency `kube-apiserver` caused scheduling throughput degradation. ([#134154](https://github.com/kubernetes/kubernetes/pull/134154), [@macsko](https://github.com/macsko)) -- Fixed a bug where the health of a DRA resource was not reported in the Pod status if the resource claim was generated from a template or used a different local name in the Pod spec. ([#134875](https://github.com/kubernetes/kubernetes/pull/134875), [@Jpsassine](https://github.com/Jpsassine)) [SIG Node and Testing] -- Fixed a long-standing issue where `kubelet` rejected Pods with `NodeAffinityFailed` due to a stale informer cache. ([#134445](https://github.com/kubernetes/kubernetes/pull/134445), [@natasha41575](https://github.com/natasha41575)) -- Fixed a panic in `kubectl api-resources` that occurred when the Discovery Client failed. ([#134833](https://github.com/kubernetes/kubernetes/pull/134833), [@rikatz](https://github.com/rikatz)) -- Fixed a possible data race during metrics registration. ([#134390](https://github.com/kubernetes/kubernetes/pull/134390), [@liggitt](https://github.com/liggitt)) [SIG Architecture and Instrumentation] -- Fixed a spurious `namespace not found` error in default `v1.30+` configurations when using `ValidatingAdmissionPolicy` or `MutatingAdmissionPolicy` to intercept namespaced objects in newly-created namespaces. ([#135359](https://github.com/kubernetes/kubernetes/pull/135359), [@liggitt](https://github.com/liggitt)) -- Fixed a startup probe race condition that caused main containers to remain stuck in "Initializing" state when sidecar containers with startup probes had failed initially but succeeded on restart in pods with `restartPolicy=Never`. ([#133072](https://github.com/kubernetes/kubernetes/pull/133072), [@AadiDev005](https://github.com/AadiDev005)) [SIG Node and Testing] -- Fixed an issue in asynchronous preemption: Scheduler now checks if preemption is ongoing for a Pod before initiating new preemption calls. ([#134730](https://github.com/kubernetes/kubernetes/pull/134730), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Scheduling and Testing] -- Fixed an issue that prevented restart policies and restart rules from being applied to static pods. ([#135031](https://github.com/kubernetes/kubernetes/pull/135031), [@yuanwang04](https://github.com/yuanwang04)) -- Fixed an issue where requests for a config `FromClass` in the `ResourceClaim` status were not referenced. ([#134793](https://github.com/kubernetes/kubernetes/pull/134793), [@LionelJouin](https://github.com/LionelJouin)) -- Fixed an issue where the `kubelet` `/configz` endpoint reported an incorrect value for `kubeletconfig.cgroupDriver` when the cgroup driver setting was received from the container runtime. ([#134743](https://github.com/kubernetes/kubernetes/pull/134743), [@marquiz](https://github.com/marquiz)) -- Fixed an issue where the default `serviceCIDR` controller did not log events because the event broadcaster was shutdown during initialization. ([#133338](https://github.com/kubernetes/kubernetes/pull/133338), [@aojea](https://github.com/aojea)) -- Fixed an issue with setting `distinctAttribute=nil` when the `DRAConsumableCapacity` feature gate is disabled. ([#134962](https://github.com/kubernetes/kubernetes/pull/134962), [@sunya-ch](https://github.com/sunya-ch)) -- Fixed broken shell completion for API resources. ([#133771](https://github.com/kubernetes/kubernetes/pull/133771), [@marckhouzam](https://github.com/marckhouzam)) -- Fixed incorrect behavior of preemptor pod when preemption of the victim takes long to complete. The preemptor pod should not be circling in scheduling cycles until preemption is finished. ([#134294](https://github.com/kubernetes/kubernetes/pull/134294), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Scheduling and Testing] -- Fixed missing `kubelet_volume_stats_*` metrics. ([#133890](https://github.com/kubernetes/kubernetes/pull/133890), [@huww98](https://github.com/huww98)) [SIG Instrumentation and Node] -- Fixed occasional schedule delays when a static `PersistentVolume` is created. ([#133929](https://github.com/kubernetes/kubernetes/pull/133929), [@huww98](https://github.com/huww98)) [SIG Scheduling and Storage] -- Fixed resource claims deallocation for extended resource when Pod completes. ([#134312](https://github.com/kubernetes/kubernetes/pull/134312), [@alaypatel07](https://github.com/alaypatel07)) [SIG Apps, Node and Testing] -- Fixed the kubelet to honor the `userNamespaces.idsPerPod` configuration, which was previously ignored. ([#133373](https://github.com/kubernetes/kubernetes/pull/133373), [@AkihiroSuda](https://github.com/AkihiroSuda)) [SIG Node and Testing] -- Fixed the replacement tag in APIs so it no longer acted as a selector for storage version. ([#135197](https://github.com/kubernetes/kubernetes/pull/135197), [@Jefftree](https://github.com/Jefftree)) -- Fixed validation error when `ConfigFlags` includes `CertFile` and/or `KeyFile` while the original configuration also contains `CertFileData` and/or `KeyFileData`. ([#133917](https://github.com/kubernetes/kubernetes/pull/133917), [@n2h9](https://github.com/n2h9)) [SIG API Machinery and CLI] -- Improved performance of `Endpoint` and `EndpointSlice` controllers when there are a large number of services in a single namespace by making pod-to-service lookup asynchronous. ([#134739](https://github.com/kubernetes/kubernetes/pull/134739), [@shyamjvs](https://github.com/shyamjvs)) [SIG Apps and Network] -- Improved the `FreeDiskSpaceFailed` warning event to provide more actionable details when image garbage collection fails to free enough disk space. Example: `Insufficient free disk space on the node's image filesystem (95.0% of 10.0 GiB used). Failed to free sufficient space by deleting unused images. Consider resizing the disk or deleting unused files.`. ([#132578](https://github.com/kubernetes/kubernetes/pull/132578), [@drigz](https://github.com/drigz)) -- Introduced support for using an implicit extended resource name derived from the device class (`deviceclass.resource.kubernetes.io/`) to request DRA devices matching that class. ([#133363](https://github.com/kubernetes/kubernetes/pull/133363), [@yliaog](https://github.com/yliaog)) [SIG Node, Scheduling and Testing] -- Kube-apiserver: Fixed a `v1.34` regression with spurious "Error getting keys" log messages. ([#133817](https://github.com/kubernetes/kubernetes/pull/133817), [@serathius](https://github.com/serathius)) [SIG API Machinery and Etcd] -- Kube-apiserver: Fixed a possible `v1.34` performance regression calculating object size statistics for resources not served from the watch cache, typically only `Events`. ([#133873](https://github.com/kubernetes/kubernetes/pull/133873), [@serathius](https://github.com/serathius)) [SIG API Machinery and Etcd] -- Kube-apiserver: Improved validation error messages for custom resources with CEL validation rules to include the value that failed validation. ([#132798](https://github.com/kubernetes/kubernetes/pull/132798), [@cbandy](https://github.com/cbandy)) -- Kube-apiserver: Made sure that when `--requestheader-client-ca-file` and `--client-ca-file` contain overlapping certificates, `--requestheader-allowed-names` must be specified so that regular client certificates cannot set authenticating proxy headers for arbitrary users. ([#131411](https://github.com/kubernetes/kubernetes/pull/131411), [@ballista01](https://github.com/ballista01)) [SIG API Machinery, Auth and Security] -- Kube-apiserver: Resolved an issue causing unnecessary warning log messages about enabled alpha APIs during API server startup. ([#135327](https://github.com/kubernetes/kubernetes/pull/135327), [@michaelasp](https://github.com/michaelasp)) -- Kube-controller-manager: Fixed a possible data race in the garbage collection controller. ([#134379](https://github.com/kubernetes/kubernetes/pull/134379), [@liggitt](https://github.com/liggitt)) [SIG API Machinery and Apps] -- Kube-controller-manager: Resolved potential issues handling pods with incorrect uids in their `ownerReference`. ([#134654](https://github.com/kubernetes/kubernetes/pull/134654), [@liggitt](https://github.com/liggitt)) -- Kubeadm: Added missing cluster-info context validation to prevent panics when the user has a malformed kubeconfig in the cluster-info ConfigMap that excludes a valid current context. ([#134715](https://github.com/kubernetes/kubernetes/pull/134715), [@neolit123](https://github.com/neolit123)) -- Kubeadm: Ensured waiting for `apiserver` uses a local client that doesn't reach to the control plane endpoint and instead reaches directly to the local API server endpoint. ([#134265](https://github.com/kubernetes/kubernetes/pull/134265), [@neolit123](https://github.com/neolit123)) -- Kubeadm: Fixed `KUBEADM_UPGRADE_DRYRUN_DIR` not honored in upgrade phase when writing kubelet config files. ([#134007](https://github.com/kubernetes/kubernetes/pull/134007), [@carlory](https://github.com/carlory)) -- Kubeadm: Fixed a bug where `ClusterConfiguration.APIServer.TimeoutForControlPlane` from `v1beta3` was not respected in newer kubeadm versions where `v1beta4` is the default. ([#133513](https://github.com/kubernetes/kubernetes/pull/133513), [@tom1299](https://github.com/tom1299)) -- Kubeadm: Fixed a bug where the node registration information for a given node was not fetched correctly during `kubeadm upgrade node` and the node name can end up being incorrect in cases where the node name is not the same as the host name. ([#134319](https://github.com/kubernetes/kubernetes/pull/134319), [@neolit123](https://github.com/neolit123)) -- Kubeadm: Fixed a preflight check that could fail hostname construction in IPv6 setups. ([#134588](https://github.com/kubernetes/kubernetes/pull/134588), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Auth, Cloud Provider, Cluster Lifecycle and Testing] -- Kubelet: Fixed a concurrent map write error when creating a pod with an empty volume while the `LocalStorageCapacityIsolationFSQuotaMonitoring` feature gate is enabled. ([#135174](https://github.com/kubernetes/kubernetes/pull/135174), [@carlory](https://github.com/carlory)) -- Kubelet: Fixed an internal deadlock that caused the connection to a DRA driver to become unusable after being idle for 30 minutes. ([#133926](https://github.com/kubernetes/kubernetes/pull/133926), [@pohly](https://github.com/pohly)) -- Made legacy watch calls (`ResourceVersion` = 0 or unset) that generate init-events weigh higher in `API Priority and Fairness (APF)` seat usage. Properly accounting for their cost protects the API server from CPU overload. Users might see increased throttling of such calls as a result. ([#134601](https://github.com/kubernetes/kubernetes/pull/134601), [@shyamjvs](https://github.com/shyamjvs)) -- Namespace is now included in the `--dry-run=client` output for `HorizontalPodAutoscaler (HPA)` objects. ([#134263](https://github.com/kubernetes/kubernetes/pull/134263), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] -- Populated `involvedObject.apiVersion` on Events created for Nodes and Pods. ([#134545](https://github.com/kubernetes/kubernetes/pull/134545), [@novahe](https://github.com/novahe)) [SIG Cloud Provider, Network, Node, Scalability and Testing] -- Promoted VAC API test to conformance. ([#133615](https://github.com/kubernetes/kubernetes/pull/133615), [@carlory](https://github.com/carlory)) [SIG Architecture, Storage and Testing] -- Removed `BlockOwnerDeletion` from `ResourceClaim` created from `ResourceClaimTemplate` and from `extendedResourceClaim` created by the `scheduler`. ([#134956](https://github.com/kubernetes/kubernetes/pull/134956), [@yliaog](https://github.com/yliaog)) [SIG Apps, Node and Scheduling] -- Removed an incorrect `SessionAffinity` warning that appeared when a headless service was created or updated. ([#134054](https://github.com/kubernetes/kubernetes/pull/134054), [@Peac36](https://github.com/Peac36)) -- Slow container runtime initialization no longer causes the System WatchDog to kill the kubelet. The Device Manager was treated as unhealthy until it began listening on its port. ([#135153](https://github.com/kubernetes/kubernetes/pull/135153), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) -- Typed workqueue now cleans up goroutines before shutting down ([#135072](https://github.com/kubernetes/kubernetes/pull/135072), [@Jefftree](https://github.com/Jefftree)) [SIG API Machinery] -- Updated `kubectl scale` to return a consistent error message when a specified resource is not found. Previously, it returned: `error: no objects passed to scale "" not found`. It now matches the format used by other commands (e.g., `kubectl get`): `Error from server (NotFound): "" not found`. ([#134017](https://github.com/kubernetes/kubernetes/pull/134017), [@mochizuki875](https://github.com/mochizuki875)) -- `kube-controller-manager`: Fixed a `v1.34` regression that triggered a spurious rollout of existing StatefulSets when upgrading the control plane from `v1.33` to `v1.34`. This fix is guarded by the `StatefulSetSemanticRevisionComparison` feature gate, which is enabled by default. ([#135017](https://github.com/kubernetes/kubernetes/pull/135017), [@liggitt](https://github.com/liggitt)) -- `kube-scheduler`: Pod statuses no longer include specific taint keys or values when scheduling fails due to untolerated taints. ([#134740](https://github.com/kubernetes/kubernetes/pull/134740), [@hoskeri](https://github.com/hoskeri)) -- Fixes a bug where `MutatingAdmissionPolicy` would fail to apply to objects with duplicate list items (like env vars). ([#135560](https://github.com/kubernetes/kubernetes/pull/135560), [@lalitc375](https://github.com/lalitc375) [SIG API Machinery] -- K8s.io/client-go: Fixes a regression in 1.34+ which prevented informers from using configured Transformer functions. ([#135580](https://github.com/kubernetes/kubernetes/pull/135580), [@serathius](https://github.com/serathius) [SIG API Machinery] - -### Other (Cleanup or Flake) - -- Added the `Step` field to the testing framework to allow volume expansion in configurable step sizes for tests. ([#134760](https://github.com/kubernetes/kubernetes/pull/134760), [@Rishita-Golla](https://github.com/Rishita-Golla)) [SIG Storage and Testing] -- Bumped addon manager to use `kubectl` version `v1.32.2`. ([#130548](https://github.com/kubernetes/kubernetes/pull/130548), [@Jefftree](https://github.com/Jefftree)) [SIG Cloud Provider, Scalability and Testing] -- Dropped support for `certificates/v1beta1` `CertificateSigningRequest` in `kubectl`. ([#134782](https://github.com/kubernetes/kubernetes/pull/134782), [@scaliby](https://github.com/scaliby)) -- Dropped support for `discovery/v1beta1` `EndpointSlice` in `kubectl`. ([#134913](https://github.com/kubernetes/kubernetes/pull/134913), [@scaliby](https://github.com/scaliby)) -- Dropped support for `networking/v1beta1` `Ingress` in `kubectl`. ([#135108](https://github.com/kubernetes/kubernetes/pull/135108), [@scaliby](https://github.com/scaliby)) -- Dropped support for `networking/v1beta1` `Ingress` in `kubectl`. ([#135176](https://github.com/kubernetes/kubernetes/pull/135176), [@scaliby](https://github.com/scaliby)) -- Dropped support for `policy/v1beta1` PodDisruptionBudget in kubectl. ([#134685](https://github.com/kubernetes/kubernetes/pull/134685), [@scaliby](https://github.com/scaliby)) -- Eliminated and prevented future use of the `md5` algorithm in favor of more appropriate hashing algorithms. ([#133511](https://github.com/kubernetes/kubernetes/pull/133511), [@BenTheElder](https://github.com/BenTheElder)) [SIG Apps, Architecture, CLI, Cluster Lifecycle, Network, Node, Security, Storage and Testing] -- Fixed `nfacct` test cases on s390x. ([#133603](https://github.com/kubernetes/kubernetes/pull/133603), [@saisindhuri91](https://github.com/saisindhuri91)) -- Fixed formatting of various Go API deprecations for `GoDoc` and `pkgsite`, and enabled a linter to detect misformatted deprecations. ([#133571](https://github.com/kubernetes/kubernetes/pull/133571), [@BenTheElder](https://github.com/BenTheElder)) [SIG API Machinery, Architecture, CLI, Instrumentation and Testing] -- Improved HPA performance when using container-specific resource metrics by optimizing container lookup logic to exit early once the target container is found, reducing unnecessary iterations through all containers in a pod. ([#133415](https://github.com/kubernetes/kubernetes/pull/133415), [@AadiDev005](https://github.com/AadiDev005)) [SIG Apps and Autoscaling] -- Increased the coverage to 89.8%. ([#132607](https://github.com/kubernetes/kubernetes/pull/132607), [@ylink-lfs](https://github.com/ylink-lfs)) -- Kube-apiserver: Fixed an issue where passing invalid `DeleteOptions` incorrectly returned a 500 status instead of 400. ([#133358](https://github.com/kubernetes/kubernetes/pull/133358), [@ostrain](https://github.com/ostrain)) -- Kubeadm: Updated the supported `etcd` version to `v3.5.23` for supported control plane versions `v1.31`, `v1.32`, and `v1.33`. ([#134692](https://github.com/kubernetes/kubernetes/pull/134692), [@joshjms](https://github.com/joshjms)) [SIG Cluster Lifecycle and Etcd] -- Kubeadm: stopped applying the `--pod-infra-container-image` flag for the kubelet. The flag has been deprecated and no longer served a purpose in the kubelet as the logic was migrated to CRI (Container Runtime Interface). During upgrade, kubeadm will attempt to remove the flag from the file `/var/lib/kubelet/kubeadm-flags.env`. ([#133778](https://github.com/kubernetes/kubernetes/pull/133778), [@carlory](https://github.com/carlory)) [SIG Cloud Provider and Cluster Lifecycle] -- Migrated the `CPUManager` to contextual logging. ([#125912](https://github.com/kubernetes/kubernetes/pull/125912), [@ffromani](https://github.com/ffromani)) -- Moved Types in `k/k/pkg/scheduler/framework`: - `Handle`, - `Plugin`, - `PreEnqueuePlugin`, `QueueSortPlugin`, `EnqueueExtensions`, `PreFilterExtensions`, `PreFilterPlugin`, `FilterPlugin`, `PostFilterPlugin`, `PreScorePlugin`, `ScorePlugin`, `ReservePlugin`, `PreBindPlugin`, `PostBindPlugin`, `PermitPlugin`, `BindPlugin`, - `PodActivator`, `PodNominator`, `PluginsRunner`, - `LessFunc`, `ScoreExtensions`, `NodeToStatusReader`, `NodeScoreList`, `NodeScore`, `NodePluginScores`, `PluginScore`, `NominatingMode`, `NominatingInfo`, `WaitingPod`, `PreFilterResult`, `PostFilterResult`, - `Extender`, - `NodeInfoLister`, `StorageInfoLister`, `SharedLister`, `ResourceSliceLister`, `DeviceClassLister`, `ResourceClaimTracker`, `SharedDRAManager` - - to package `k8s.io/kube-scheduler/framework`. Users should update import paths. The interfaces don't change. - - Type `Parallelizer` in `k/k/pkg/scheduler/framework/parallelism` has been split into interface `Parallelizer` (in `k8s.io/kube-scheduler/framework`) and `struct Parallelizer` (location unchanged in k/k). Plugin developers should update the import path to staging repo. ([#133172](https://github.com/kubernetes/kubernetes/pull/133172), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Node, Release, Scheduling, Storage and Testing] -- Moved the CPU Manager static policy option `strict-cpu-reservation` to the GA version. ([#134388](https://github.com/kubernetes/kubernetes/pull/134388), [@psasnal](https://github.com/psasnal)) -- Promoted the Topology Manager policy option `max-allowable-numa-nodes` to GA version. ([#134614](https://github.com/kubernetes/kubernetes/pull/134614), [@ffromani](https://github.com/ffromani)) -- Reduced event spam during volume operation errors in the Portworx in-tree driver. ([#135081](https://github.com/kubernetes/kubernetes/pull/135081), [@gohilankit](https://github.com/gohilankit)) -- Removed `rsync` as a dependency to build Kubernetes. ([#134656](https://github.com/kubernetes/kubernetes/pull/134656), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release and Testing] -- Removed container name from messages for container created and started events. ([#134043](https://github.com/kubernetes/kubernetes/pull/134043), [@HirazawaUi](https://github.com/HirazawaUi)) -- Removed deprecated gogo protocol definitions from `k8s.io/kubelet/pkg/apis/dra` in favor of `google.golang.org/protobuf`. ([#133026](https://github.com/kubernetes/kubernetes/pull/133026), [@saschagrunert](https://github.com/saschagrunert)) [SIG API Machinery and Node] -- Removed general available feature-gate `SizeMemoryBackedVolumes`. ([#133720](https://github.com/kubernetes/kubernetes/pull/133720), [@carlory](https://github.com/carlory)) [SIG Node, Storage and Testing] -- Removed the `ComponentSLIs` feature gate, as it was promoted to stable in the Kubernetes `v1.32` release. ([#133742](https://github.com/kubernetes/kubernetes/pull/133742), [@carlory](https://github.com/carlory)) [SIG Architecture and Instrumentation] -- Removed the `KUBECTL_OPENAPIV3_PATCH` environment variable, as aggregated discovery has been stable since `v1.30`. ([#134130](https://github.com/kubernetes/kubernetes/pull/134130), [@ardaguclu](https://github.com/ardaguclu)) -- Removed the `UserNamespacesPodSecurityStandards` feature gate. The minimum supported Kubernetes version for `kubelet` is now `v1.31`, so the gate is no longer needed. ([#132157](https://github.com/kubernetes/kubernetes/pull/132157), [@haircommander](https://github.com/haircommander)) [SIG Auth, Node and Testing] -- Removed the `VolumeAttributesClass` resource from the `storage.k8s.io/v1alpha1` API in `v1.35`. ([#134625](https://github.com/kubernetes/kubernetes/pull/134625), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Etcd, Storage and Testing] -- Specified the deprecated version of `apiserver_storage_objects` metric in metrics docs. ([#134028](https://github.com/kubernetes/kubernetes/pull/134028), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Etcd and Instrumentation] -- Substantially simplified building Kubernetes by making the process run a pre-built container image directly without running `rsyncd`. ([#134510](https://github.com/kubernetes/kubernetes/pull/134510), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release and Testing] -- Tests: Switched to https://go.dev/doc/go1.25#container-aware-gomaxprocs from `go.uber.org/automaxprocs`. ([#133492](https://github.com/kubernetes/kubernetes/pull/133492), [@BenTheElder](https://github.com/BenTheElder)) -- The `AggregatedDiscoveryRemoveBetaType` feature gate was deprecated and locked to `true`. ([#134230](https://github.com/kubernetes/kubernetes/pull/134230), [@Jefftree](https://github.com/Jefftree)) -- The `SystemdWatchdog` feature gate has been locked to default and will be removed in future release. The systemd watchdog functionality in `kubelet` can be enabled via systemd without any feature gate configuration. See the [systemd watchdog documentation](https://kubernetes.io/docs/reference/node/systemd-watchdog/) for more information. ([#134691](https://github.com/kubernetes/kubernetes/pull/134691), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) -- Updated CNI plugins to v1.8.0. ([#133837](https://github.com/kubernetes/kubernetes/pull/133837), [@saschagrunert](https://github.com/saschagrunert)) [SIG Cloud Provider, Node and Testing] -- Updated `etcd` to `v3.6.5`. ([#134251](https://github.com/kubernetes/kubernetes/pull/134251), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] -- Updated `kubectl auth reconcile` to retry reconciliation when a conflict error occurs. ([#133323](https://github.com/kubernetes/kubernetes/pull/133323), [@liggitt](https://github.com/liggitt)) [SIG Auth and CLI] -- Updated `kubectl get` and `kubectl describe` human-readable output to no longer show counts for referenced tokens and secrets. ([#117160](https://github.com/kubernetes/kubernetes/pull/117160), [@liggitt](https://github.com/liggitt)) [SIG CLI and Testing] -- Updated cri-tools to v1.34.0. ([#133636](https://github.com/kubernetes/kubernetes/pull/133636), [@saschagrunert](https://github.com/saschagrunert)) [SIG Cloud Provider] -- Updated the Go version of Kubernetes to `1.25.3`. ([#134598](https://github.com/kubernetes/kubernetes/pull/134598), [@BenTheElder](https://github.com/BenTheElder)) -- Updated the `/statusz` page for `kube-proxy` to include a list of exposed endpoints, making debugging and introspection easier. ([#133190](https://github.com/kubernetes/kubernetes/pull/133190), [@aman4433](https://github.com/aman4433)) [SIG Network and Node] -- Updated the `kubectl wait` command description by removing the `Experimental` prefix, as the command has been stable for a long time. ([#133731](https://github.com/kubernetes/kubernetes/pull/133731), [@ardaguclu](https://github.com/ardaguclu)) -- Updated the etcd client library to `v3.6.5`. ([#134780](https://github.com/kubernetes/kubernetes/pull/134780), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling and Storage] -- Updated the short description of the `kubectl wait` command by removing the `Experimental` prefix, as the command has been stable for a long time. ([#133907](https://github.com/kubernetes/kubernetes/pull/133907), [@ardaguclu](https://github.com/ardaguclu)) -- Upgraded CoreDNS to v1.12.4. ([#133968](https://github.com/kubernetes/kubernetes/pull/133968), [@yashsingh74](https://github.com/yashsingh74)) [SIG Cloud Provider and Cluster Lifecycle] -- Upgraded `CoreDNS` to `v1.12.3`. ([#132288](https://github.com/kubernetes/kubernetes/pull/132288), [@thevilledev](https://github.com/thevilledev)) [SIG Cloud Provider and Cluster Lifecycle] -- `kubeadm`: Removed the `WaitForAllControlPlaneComponents` feature gate, which graduated to GA in `v1.34` and was locked to enabled by default. ([#134781](https://github.com/kubernetes/kubernetes/pull/134781), [@neolit123](https://github.com/neolit123)) -- `kubeadm`: Updated the supported etcd version to `v3.5.24` for control plane versions `v1.32`, `v1.33`, and `v1.34`. ([#134779](https://github.com/kubernetes/kubernetes/pull/134779), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] -- `etcd: Update etcd to `v3.6.6`. (#135271, @bzsuni) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] -- Fix a bug in the kube-apiserver where a malformed Service without name can cause high CPU usage. The bug is present on the new Cluster IP allocators enabled with the feature MultiCIDRServiceAllocator (enabled by default since 1.33) - - -## Dependencies - -### Added -- cyphar.com/go-pathrs: v0.2.1 -- github.com/Masterminds/semver/v3: [v3.4.0](https://github.com/Masterminds/semver/tree/v3.4.0) -- github.com/gkampitakis/ciinfo: [v0.3.2](https://github.com/gkampitakis/ciinfo/tree/v0.3.2) -- github.com/gkampitakis/go-diff: [v1.3.2](https://github.com/gkampitakis/go-diff/tree/v1.3.2) -- github.com/gkampitakis/go-snaps: [v0.5.15](https://github.com/gkampitakis/go-snaps/tree/v0.5.15) -- github.com/goccy/go-yaml: [v1.18.0](https://github.com/goccy/go-yaml/tree/v1.18.0) -- github.com/joshdk/go-junit: [v1.0.0](https://github.com/joshdk/go-junit/tree/v1.0.0) -- github.com/maruel/natural: [v1.1.1](https://github.com/maruel/natural/tree/v1.1.1) -- github.com/mfridman/tparse: [v0.18.0](https://github.com/mfridman/tparse/tree/v0.18.0) -- github.com/moby/sys/atomicwriter: [v0.1.0](https://github.com/moby/sys/tree/atomicwriter/v0.1.0) -- github.com/tidwall/gjson: [v1.18.0](https://github.com/tidwall/gjson/tree/v1.18.0) -- github.com/tidwall/match: [v1.1.1](https://github.com/tidwall/match/tree/v1.1.1) -- github.com/tidwall/pretty: [v1.2.1](https://github.com/tidwall/pretty/tree/v1.2.1) -- github.com/tidwall/sjson: [v1.2.5](https://github.com/tidwall/sjson/tree/v1.2.5) -- go.uber.org/automaxprocs: v1.6.0 -- golang.org/x/tools/go/expect: v0.1.1-deprecated -- golang.org/x/tools/go/packages/packagestest: v0.1.1-deprecated - -### Changed -- cloud.google.com/go/compute/metadata: v0.6.0 → v0.7.0 -- github.com/aws/aws-sdk-go-v2/config: [v1.27.24 → v1.29.14](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.24...config/v1.29.14) -- github.com/aws/aws-sdk-go-v2/credentials: [v1.17.24 → v1.17.67](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.24...credentials/v1.17.67) -- github.com/aws/aws-sdk-go-v2/feature/ec2/imds: [v1.16.9 → v1.16.30](https://github.com/aws/aws-sdk-go-v2/compare/feature/ec2/imds/v1.16.9...feature/ec2/imds/v1.16.30) -- github.com/aws/aws-sdk-go-v2/internal/configsources: [v1.3.13 → v1.3.34](https://github.com/aws/aws-sdk-go-v2/compare/internal/configsources/v1.3.13...internal/configsources/v1.3.34) -- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2: [v2.6.13 → v2.6.34](https://github.com/aws/aws-sdk-go-v2/compare/internal/endpoints/v2/v2.6.13...internal/endpoints/v2/v2.6.34) -- github.com/aws/aws-sdk-go-v2/internal/ini: [v1.8.0 → v1.8.3](https://github.com/aws/aws-sdk-go-v2/compare/internal/ini/v1.8.0...internal/ini/v1.8.3) -- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding: [v1.11.3 → v1.12.3](https://github.com/aws/aws-sdk-go-v2/compare/service/internal/accept-encoding/v1.11.3...service/internal/accept-encoding/v1.12.3) -- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url: [v1.11.15 → v1.12.15](https://github.com/aws/aws-sdk-go-v2/compare/service/internal/presigned-url/v1.11.15...service/internal/presigned-url/v1.12.15) -- github.com/aws/aws-sdk-go-v2/service/sso: [v1.22.1 → v1.25.3](https://github.com/aws/aws-sdk-go-v2/compare/service/sso/v1.22.1...service/sso/v1.25.3) -- github.com/aws/aws-sdk-go-v2/service/ssooidc: [v1.26.2 → v1.30.1](https://github.com/aws/aws-sdk-go-v2/compare/service/ssooidc/v1.26.2...service/ssooidc/v1.30.1) -- github.com/aws/aws-sdk-go-v2/service/sts: [v1.30.1 → v1.33.19](https://github.com/aws/aws-sdk-go-v2/compare/service/sts/v1.30.1...service/sts/v1.33.19) -- github.com/aws/aws-sdk-go-v2: [v1.30.1 → v1.36.3](https://github.com/aws/aws-sdk-go-v2/compare/v1.30.1...v1.36.3) -- github.com/aws/smithy-go: [v1.20.3 → v1.22.3](https://github.com/aws/smithy-go/compare/v1.20.3...v1.22.3) -- github.com/containerd/containerd/api: [v1.8.0 → v1.9.0](https://github.com/containerd/containerd/compare/api/v1.8.0...api/v1.9.0) -- github.com/containerd/ttrpc: [v1.2.6 → v1.2.7](https://github.com/containerd/ttrpc/compare/v1.2.6...v1.2.7) -- github.com/containerd/typeurl/v2: [v2.2.2 → v2.2.3](https://github.com/containerd/typeurl/compare/v2.2.2...v2.2.3) -- github.com/coredns/corefile-migration: [v1.0.26 → v1.0.29](https://github.com/coredns/corefile-migration/compare/v1.0.26...v1.0.29) -- github.com/cyphar/filepath-securejoin: [v0.4.1 → v0.6.0](https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.6.0) -- github.com/docker/docker: [v26.1.4+incompatible → v28.2.2+incompatible](https://github.com/docker/docker/compare/v26.1.4...v28.2.2) -- github.com/go-logr/logr: [v1.4.2 → v1.4.3](https://github.com/go-logr/logr/compare/v1.4.2...v1.4.3) -- github.com/google/cadvisor: [v0.52.1 → v0.53.0](https://github.com/google/cadvisor/compare/v0.52.1...v0.53.0) -- github.com/google/pprof: [d1b30fe → 27863c8](https://github.com/google/pprof/compare/d1b30fe...27863c8) -- github.com/onsi/ginkgo/v2: [v2.21.0 → v2.27.2](https://github.com/onsi/ginkgo/compare/v2.21.0...v2.27.2) -- github.com/onsi/gomega: [v1.35.1 → v1.38.2](https://github.com/onsi/gomega/compare/v1.35.1...v1.38.2) -- github.com/opencontainers/cgroups: [v0.0.1 → v0.0.3](https://github.com/opencontainers/cgroups/compare/v0.0.1...v0.0.3) -- github.com/opencontainers/runc: [v1.2.5 → v1.3.0](https://github.com/opencontainers/runc/compare/v1.2.5...v1.3.0) -- github.com/opencontainers/runtime-spec: [v1.2.0 → v1.2.1](https://github.com/opencontainers/runtime-spec/compare/v1.2.0...v1.2.1) -- github.com/opencontainers/selinux: [v1.11.1 → v1.13.0](https://github.com/opencontainers/selinux/compare/v1.11.1...v1.13.0) -- github.com/prometheus/client_golang: [v1.22.0 → v1.23.2](https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.2) -- github.com/prometheus/client_model: [v0.6.1 → v0.6.2](https://github.com/prometheus/client_model/compare/v0.6.1...v0.6.2) -- github.com/prometheus/common: [v0.62.0 → v0.66.1](https://github.com/prometheus/common/compare/v0.62.0...v0.66.1) -- github.com/prometheus/procfs: [v0.15.1 → v0.16.1](https://github.com/prometheus/procfs/compare/v0.15.1...v0.16.1) -- github.com/rogpeppe/go-internal: [v1.13.1 → v1.14.1](https://github.com/rogpeppe/go-internal/compare/v1.13.1...v1.14.1) -- github.com/spf13/cobra: [v1.9.1 → v1.10.0](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.0) -- github.com/spf13/pflag: [v1.0.6 → v1.0.9](https://github.com/spf13/pflag/compare/v1.0.6...v1.0.9) -- github.com/stretchr/testify: [v1.10.0 → v1.11.1](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) -- go.etcd.io/bbolt: v1.4.2 → v1.4.3 -- go.etcd.io/etcd/api/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/client/pkg/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/client/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/pkg/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/server/v3: v3.6.4 → v3.6.5 -- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp: v0.58.0 → v0.61.0 -- go.opentelemetry.io/otel/metric: v1.35.0 → v1.36.0 -- go.opentelemetry.io/otel/sdk/metric: v1.34.0 → v1.36.0 -- go.opentelemetry.io/otel/sdk: v1.34.0 → v1.36.0 -- go.opentelemetry.io/otel/trace: v1.35.0 → v1.36.0 -- go.opentelemetry.io/otel: v1.35.0 → v1.36.0 -- go.yaml.in/yaml/v2: v2.4.2 → v2.4.3 -- golang.org/x/crypto: v0.36.0 → v0.45.0 -- golang.org/x/mod: v0.21.0 → v0.29.0 -- golang.org/x/net: v0.38.0 → v0.47.0 -- golang.org/x/oauth2: v0.27.0 → v0.30.0 -- golang.org/x/sync: v0.12.0 → v0.18.0 -- golang.org/x/sys: v0.31.0 → v0.38.0 -- golang.org/x/telemetry: bda5523 → 078029d -- golang.org/x/term: v0.30.0 → v0.37.0 -- golang.org/x/text: v0.23.0 → v0.31.0 -- golang.org/x/tools: v0.26.0 → v0.38.0 -- google.golang.org/genproto/googleapis/rpc: a0af3ef → 200df99 -- google.golang.org/grpc: v1.72.1 → v1.72.2 -- google.golang.org/protobuf: v1.36.5 → v1.36.8 -- gopkg.in/evanphx/json-patch.v4: v4.12.0 → v4.13.0 -- k8s.io/gengo/v2: 85fd79d → ec3ebc5 -- k8s.io/kube-openapi: f3f2b99 → 589584f -- k8s.io/system-validators: v1.10.1 → v1.12.1 -- k8s.io/utils: 4c0f3b2 → bc988d5 -- sigs.k8s.io/json: cfa47c3 → 2d32026 - -### Removed -- gopkg.in/yaml.v2: v2.4.0 - - - -# v1.35.0-rc.1 - - -## Downloads for v1.35.0-rc.1 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes.tar.gz) | 6b08badb1402c5a4d5e83e14bc64e464c7bd8bbdd9473ea1b501b7bf04ac9f53d2ac23a5f70761cc03024bd046c329253d2914af9225530755bcbc06d7459616 -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-src.tar.gz) | 35d2827a2bb7b01162c506d7a15392c72c6537f9f1570ade160bc286ad9a409e0830d32e7ea5ae8191bb6435859826d2d5ec56255a29fe279aee3517239cb9a6 - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-darwin-amd64.tar.gz) | 85f9f154296b0579444ee9ff43f74ad616ef52e453782da8dd10f5150d1fb6f1d71151b7525fe5784142f930fb9fe9bfbc395277b5a088d3bc892c9415e15611 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-darwin-arm64.tar.gz) | 2811a1b3901c9a82cb042e4b4c4bc4d75ea0d894b42e7f7c63b25b5df8d26b5ff2bfd0ee819b9cb8e946a2bac90ac4dfb649c692f054fb290e08516966384d0b -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-linux-386.tar.gz) | aed99699ba9f635d1e073061ab89b7ee80744cb56a4aa078ad6afc5670483db456fcc6a544bf34b95a4a3c8f25938898b093d7d279120063688f0406efaf37f9 -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-linux-amd64.tar.gz) | 607724204e2b3265f25d12cda5397c4943e8df4ae66efe2f5cf2582b7aa1c9fc9a60c61a193aef8ae1ee5b3d261146f5cc9956de873d82864979ef19c70c48c7 -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-linux-arm.tar.gz) | 378770b5529ce39fbdbb1d39c4a966ad96c03523411e5f830ac2e86e67e23518de534f148838903561de5a7db6f852f532ad7a0799514b72892fea0362f3c635 -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-linux-arm64.tar.gz) | 357f66e108d651d0a77d61f9f0da286f5c8d312d6f320174dab42cd6aae69ecdf24e081f5bdcbc6313d2688c2585009348766f7a0eed58f23f8eda898d3e7081 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-linux-ppc64le.tar.gz) | ac9d343138f01a1d6fa986ef684938686e7ac656f8b8b1b5c1a75894e2c98f7f36754eef15a95b20c0812c5a62d775a285c4e015b5eca9bc4e937b9f44b7a537 -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-linux-s390x.tar.gz) | b12631a0bb8c2f9e28ca855302a57ab4bffb2db4105668cf16635193cada26bd0aa63f80428835339f42eea13839d1990f171276697eb00f9cbe5d8edae434f0 -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-windows-386.tar.gz) | 5b1849523011be7f569f17f42250a98a2513caa7652c3055712bace9ba440ab4e38d4f5bceb6e89158f4f3008a836fcee765ef9af818ba6cb07296ef8467cf02 -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-windows-amd64.tar.gz) | fb95a54326565bcc3458fa0a876d5f457e5218eafa349f9cee3db93982c30482b8fe57d0f43a2be89e4816382a5efc97b95fae069bc2ed8f19b6ac253e46790a -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-client-windows-arm64.tar.gz) | 465a7fe0e87dd3dc0b22d3881bbe4c33e706ca53a512a981afd49bccdd5d638a818cabb84dbc5796b54ce97d045bbaf466c0cbf46f809bb5a31d64117386a0c7 - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-server-linux-amd64.tar.gz) | c6728d534c85e11a58dea5ca830541efde07f390586ca77f7f59308179b677f3f3f28492e1c26314adf79e800b7690a1b0d5ac5670d18282064bc4fbb4093b05 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-server-linux-arm64.tar.gz) | 6b9eb6d36179bef49b8f4f276f1ffe03552876e46c9477129b6e9a690c38e7343e0430e22fbbfaccab83c3879282e6994cdaca47a709267617a84e3143b4598e -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-server-linux-ppc64le.tar.gz) | 946b9b5d817437258a77e6b3fcf674c0c9adb0a5b4cc2aa3f68f8364c7916016d6811a9596221801f570018f3976ae456eb01a6ad871fb5461daebfd1c2ff589 -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-server-linux-s390x.tar.gz) | 5d5c22a1149d5c741b5c37710a97a84b2f9f45ccc9b8457408233a6a7c5fc20c3a2bbf92b68f135630308991cdc463559cdad6131fa6c9e0ad5cfdca7933cdc2 - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-node-linux-amd64.tar.gz) | 269725e2afd028e6eb8a3d12605789f6a5715a0bf9cf477df689bdc9f9b164f0fd32c3007b16ffee17c5234f6fff17ff9096396892e71155074a3499e9480088 -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-node-linux-arm64.tar.gz) | 37472ba5b63177bcb9430b761f1d4df459771cab2bf9448f69fa5abe0dc4f2e0a26bd6b9c8d747d37e05e8e0263736ca4150a624cecf03938aacd4e98929d03d -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-node-linux-ppc64le.tar.gz) | 25dce33f916a55d27f825902fc7776735a5ddfd0e86555b193d431eab54781d52fff9c41dafcf5a845e79f0153d515b014cb8a5c21179c82e96321851109195c -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-node-linux-s390x.tar.gz) | 16e22350e2871ea0d42668bb9c1487b2c5a6e6b436c78d1f986f68bc0312d340ab0285aca7f619322e3238cf9ee90f5425e49dc018e53069e5b0cd092bdd28a2 -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.1/kubernetes-node-windows-amd64.tar.gz) | 1b4aab8eb20bd974c7585ba55a5caeb43174afbef738e1bb99fd51fd228b800c943c4a8f0204ef935c733b15cab52ae0ae2a9a7da9299387be272932b1c54c9e - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.0-rc.0 - -## Changes by Kind - -### Feature - -- Kubernetes is now built using Go 1.25.5 ([#135609](https://github.com/kubernetes/kubernetes/pull/135609), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] - -### Bug or Regression - -- Fix a bug in the kube-apiserver where a malformed Service without name can cause high CPU usage. The bug is present on the new Cluster IP allocators enabled with the feature MultiCIDRServiceAllocator (enabled by default since 1.33) ([#135499](https://github.com/kubernetes/kubernetes/pull/135499), [@aojea](https://github.com/aojea)) [SIG Testing] -- Fixes a bug where MutatingAdmissionPolicy would fail to apply to objects with duplicate list items (like env vars). ([#135560](https://github.com/kubernetes/kubernetes/pull/135560), [@lalitc375](https://github.com/lalitc375)) [SIG API Machinery] -- K8s.io/client-go: Fixes a regression in 1.34+ which prevented informers from using configured Transformer functions ([#135580](https://github.com/kubernetes/kubernetes/pull/135580), [@serathius](https://github.com/serathius)) [SIG API Machinery] - -### Other (Cleanup or Flake) - -- Etcd: Update etcd to v3.6.6 ([#135271](https://github.com/kubernetes/kubernetes/pull/135271), [@bzsuni](https://github.com/bzsuni)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] - -## Dependencies - -### Added -_Nothing has changed._ - -### Changed -- golang.org/x/crypto: v0.41.0 → v0.45.0 -- golang.org/x/mod: v0.28.0 → v0.29.0 -- golang.org/x/net: v0.43.0 → v0.47.0 -- golang.org/x/sync: v0.17.0 → v0.18.0 -- golang.org/x/sys: v0.37.0 → v0.38.0 -- golang.org/x/telemetry: 1a19826 → 078029d -- golang.org/x/term: v0.36.0 → v0.37.0 -- golang.org/x/text: v0.29.0 → v0.31.0 -- golang.org/x/tools: v0.36.0 → v0.38.0 - -### Removed -_Nothing has changed._ - - - -# v1.35.0-rc.0 - - -## Downloads for v1.35.0-rc.0 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes.tar.gz) | ef5c549823abd93a260ef90bd282362015e8e7e4f079bb59acc3104191bed88eed98638726866ef9d5e56849cbe58b641e7ebaf0d247c0088deaae81141b4007 -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-src.tar.gz) | 906dbf942d446b9ffaa56dc31024dd496c609599a5391c81891ed2223c60017eec879de2d68d8ac5003e05c03cdaf5584186b3303291ce81885ec1875e24748c - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-darwin-amd64.tar.gz) | f771e62019fc1cba671015aca0efaf24492598d8200330672e61525a0cd56d406ce53bde56ed1bbdfac4b1ac09e8b4820ce9d612904cf9169f075010a81f3548 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-darwin-arm64.tar.gz) | 74c1b281351df048797db693cb0fe01dc4646d7884b1588e80184fb41ef83bb6016d095e0266eb7993fc2e75d54df6bcf4b8909f6c1e0a7245afb457a6f17516 -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-linux-386.tar.gz) | ca6b1747442056c10cbc838ffaca3ace45d7fc773183d526aaec728bb322370f2cc05b2b7304dc19434b40d1fb3b39f7e4eb1930cc9952ac27abccf260968737 -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-linux-amd64.tar.gz) | c9e5e3aa9df4e8c2f1253e4f72cdea94a8deae601b5df0b2cbdac4babdd2d2ac85191c908453bca40a2a0b44247dbb18ef1b5a7751c16660f9487391ed8dacad -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-linux-arm.tar.gz) | 167490337b040849d953de5aa32e3c5491f4d6225fc0c5c86ff6ef9820f61e189f91da26831ee2d910749b38331ec1a73ff820bc29497939935e1f1b3462bf9a -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-linux-arm64.tar.gz) | 21d7091f25a112e6f940bc1a047294a2c6cd03a8b52c3cfb7ac024460d21ad5ab0a5aea448ec705ed3c5f0619d512a132051eea492e7d7ae008716abbab51a24 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-linux-ppc64le.tar.gz) | b4a1b9c03f7de8e0e2c89b1c32fcd4347a287eb62fe21bc8b4d6abafc3ae30559173bd070b5ddcb94038bc7d980e09d946a131ef0191ce2febbadffd1621143e -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-linux-s390x.tar.gz) | 737916148885ade333cbd5c7f927d32e30bb6f6bfe78db8c3c00c05c82d0a6ea5255a6c85b256acb14dc2b322e79e0af6bfa4f2ee055625da0b7ba29f99c847b -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-windows-386.tar.gz) | aa6b19469d3268235012a4a6a2f3d6ee3d56734b083133c213fa7064afef6f3caecadf9a714b6905ba69c22f0103eb44135e51a00907e4ddd1856c14b988ea58 -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-windows-amd64.tar.gz) | 7d7c7edc9e67fecdb0d2a96992933acf2e4d68680ea20349af3feabf449072130de99a9aad6e03ca47806c1e10415de4fb69e56e8f73293fde96eb27bf02a71b -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-client-windows-arm64.tar.gz) | c5302ca43bbf8ba1f2f4d89f561edb53c4a6021d084593524a20418dff20c6f4f0052ad85a230961b208a40ca89d4daa64852e9730ce2350932e4d9e369f1621 - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-server-linux-amd64.tar.gz) | 5aa98c85cefbdc12c7816add8c5eb40438e39779fc96cd0606b21ab89c4d0af354168572f39787cc3f3ef471862d543b1804d433f76f52191490498aa4670255 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-server-linux-arm64.tar.gz) | 0942d2aee9638b32a976dc623269332d1f82feb0dc601e009fe97a8179cd23704fdd3ccecb3ecb51cea1abf57bee89d60707b1e69679580e1534ab4222820b01 -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-server-linux-ppc64le.tar.gz) | 76a9351112c6f40752f3c2526bcc3fbb100f7e577ca7746644087c386f4e38f01f12b6306a329ec3bcbfc33ce244db95fd3035cc2c37dc32920181f26b7cf78b -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-server-linux-s390x.tar.gz) | bb70ad37cd30db82afa9dbd2b6a221f7ed43d6ec54c8a22ff852c5ac5364f2abc7c30db26640d42ba3e265dff70607f5dde86075fe841fe13500c811000ba0bc - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-node-linux-amd64.tar.gz) | dcb41e49aed2c0c686c283a721a509d1d72f8cda4ad15216cdf6f65b48b7ba803a88a22aa2dc2bb35c4ffcc329f9184638423e3b0e0e5edd0142ed05922617d1 -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-node-linux-arm64.tar.gz) | e91bcc56eea9d07aa65b2e855d9b15c6b480c05bc9581cd3aed740d3fe712b064cd9a9dd1baea27e0678747179bd76752ccdf510cc4855764f38b78cf2d9ebdf -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-node-linux-ppc64le.tar.gz) | 7870ee543c2a131451eaaa7b62e7292045e5ee0d4cd592c385a81170d1787835883d58326adabe201df9f920d2bedc8a1c59714b541dabe4f8ee4b9491bbdd23 -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-node-linux-s390x.tar.gz) | d8265447f63f2466b0d890b3f37918b70cadec0a3e0682f5410414d5456403e992dc859129447b02980b544ddb43cb6feef421d95d29ec5c2bd3fba8811884ca -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-rc.0/kubernetes-node-windows-amd64.tar.gz) | 8cbb1c7fd68dc474fbb88baaf4f169d9adf3ee177a963be3927c51bc08ab19ca5f1cb6a20fc45b52eba8c95b8abdf884d9f140dfacf4b64bfc5f3bf3540cd481 - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.0-beta.0 - -## Changes by Kind - -### Feature - -- Kubernetes is now built using Go 1.25.4 ([#135492](https://github.com/kubernetes/kubernetes/pull/135492), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] - -### Bug or Regression - -- Fixes a spurious "namespace not found" error possible in default configurations in 1.30+ when using ValidatingAdmissionPolicy or MutatingAdmissionPolicy to intercept namespaced objects in newly-created namespaces ([#135359](https://github.com/kubernetes/kubernetes/pull/135359), [@liggitt](https://github.com/liggitt)) [SIG API Machinery] -- Kube-apiserver: Fixes spurious warning log messages about enabled alpha APIs while starting API server ([#135327](https://github.com/kubernetes/kubernetes/pull/135327), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] - -## Dependencies - -### Added -_Nothing has changed._ - -### Changed -_Nothing has changed._ - -### Removed -_Nothing has changed._ - - - -# v1.35.0-beta.0 - - -## Downloads for v1.35.0-beta.0 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes.tar.gz) | 17fae05597b73bf8ed2c14bfbc7d863e6ca470877be12a510cb354bcaf4fa5f9b15b3702e45d231efe9f4865f687bf8d1ace312b4e0a15442a14c9997f1caa07 -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-src.tar.gz) | a51fcd8dbe8097f1890931435bdeaf9c1aa31ae3c55ae6abeb504aa881c3e125ecb72af7518a9d3d38ffe67fbcffc6f1dd9e1e456218856ba2f25ec4d466f339 - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-darwin-amd64.tar.gz) | 50e6712a9d2a35d782ac0ddb22eb0799fdceca6c434c5ebe446e9f49bf9b7612cd3af3f31af211d8364d128d8b75f87ba9e61466aa7c6552416c50c14b08dd78 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-darwin-arm64.tar.gz) | c181381e2554d20b5ffbe024b33b8593800491bccceb98eaacc25fbc9ef44be4c360ec59603b857b730670c6cdcfe8d8428b790e1413d402f699c6579877ed4a -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-linux-386.tar.gz) | 26fcb99525560328c9ab1e856741e3eb6aecbb3fc8e9adf72daaeb0f6c57058e61989f696ad866a0f9b1a43098914933bed142077754e0bd82bd2054965fe44a -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-linux-amd64.tar.gz) | d6118e683ea4a64b1812a0eb0374678879a0b0b37868bd8b43517e5961a46b36f9addfbdbd84aa87e6dd4510afa644102140ec727bef3143788e2c97f23cc318 -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-linux-arm.tar.gz) | cafd385adecb9ed43201df1c5206f127177fbef0345972a5492c62eb4304c8666eafc1bffa9fb54f530d4258d8f7aaded83f08efaab5391bf92ac50a4d53122f -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-linux-arm64.tar.gz) | bb7d2281b2b9f02ae61a9607d932873a2dfd1ed551c79209a55feba88219093c8594c03c3915e87f7d847c6e59cf75625123dce8f367c34bb16d4e0ad5681f22 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-linux-ppc64le.tar.gz) | 198e2102eb0b24e6c6b406ceb8cce6803154c3e68bef5ace7583683cd49d09b6a8ecbd2ca8f1d105a6454b8550b1d6132e65e618dd0ac49c9d36494af24e3ecb -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-linux-s390x.tar.gz) | 6e81675b8b523aa1df9f4599548f92bc990a2dbe6ab4f6a4039477e237f2e6286a19e2786ba436bada30969cd5b2f3fd2fb8bc16a41943fcf6865f8b6b690f67 -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-windows-386.tar.gz) | 0a68cd18169b4f269766aaff195b07fef44418f6e108ce47f6ce445407a28c1ff27cde4ef015bb01affd2309044a5ef86392132112681edfd5fe2e1632e99862 -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-windows-amd64.tar.gz) | d64bba50e7878fb1bc89dd31e9c4a2796364d2226fcfb14964ca2783d18fe482824b6cd0a4b842076998db1794c6a5e33d78fd3bd2d5ae696ab438d4c7207114 -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-client-windows-arm64.tar.gz) | c1820e5be65918d6278ed8897059c5d87d8faa8640a5a7bab7f28822d2af19f1dbba650069a469b8eb955353d9c47a7f3098e9c7598f7f1272d08e4edba5bff8 - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-server-linux-amd64.tar.gz) | f57c7fb934e0261f71fa7f2e219730cc977367bd015a0572d4446f28c9a70e89f641d029d206d27238c7fa27ba166a6c1e81e129b583e8c1555513d5360fabf0 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-server-linux-arm64.tar.gz) | 1d5b399f921da76ba0f88c9c28a64880a7be32c017fefd961939c3538e0361cd08f1d7bb38b09982600c5d09711a89c13ee757363d3f10101ee0b1775c85f97e -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-server-linux-ppc64le.tar.gz) | 1b6dbe8765ad8f740e699b842447d513b3b4e0a685db8380a3f38fc89efd3b962b0ec3026e6df55ac4c3cadc0120577d34e4cf5c10311fd1d2bcde9bc47ba844 -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-server-linux-s390x.tar.gz) | dd0213e41f26158f3cb9a589ca68211d4d68ca2cc9346726361f609b896b4b8975274dd53d3e7561c1f61178bd9aef69c156a435b530def48c215e2d03d18414 - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-node-linux-amd64.tar.gz) | 7c43a88e1b86871d5f76d3d3fed4c458aedcb7d41f3dd944f08a24087b6e0703b2ce8c4d34ee2625de18d75b5bfbc36f5ab6da5158e69ba929cd1cf9f0a205cc -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-node-linux-arm64.tar.gz) | 0fbca961eead65de9401ff6211792f27f845003bb5d91655299b3d0c05dc805eca0ade4e0caf784d561734e1ba15ac1112efd92a439953835730a866c92f2205 -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-node-linux-ppc64le.tar.gz) | c9bbfcb37f32d267e00067b7334b5a961e875be5776a941ef84a2b2a9d8ee7f2bd97998a789b2180809a9869dbbfa52ef3b5c154447207a83c1de4fa07ed7b05 -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-node-linux-s390x.tar.gz) | 32b416cc48008e5c1b34af1205a880f8c43a194c264451222bb6281ffa894e4267d2ebdd49e4b08731a44274f5bd7c7ff54733dfd6aaa5df89ac57c12bc50073 -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-beta.0/kubernetes-node-windows-amd64.tar.gz) | 90eb6f5b268eacadeba1be60f2765740c77a65b3f2b357cec8814a225c26bb8a60341b1c515c0867abf119f3522c7d90281a5490ed7c8926c2ecb7c8ec0b4fe9 - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.0-alpha.3 - -## Changes by Kind - -### API Change - -- Add scoring for the prioritized list feature so that the node that can satisfy the best ranked subrequests are chosen. ([#134711](https://github.com/kubernetes/kubernetes/pull/134711), [@mortent](https://github.com/mortent)) [SIG Node, Scheduling and Testing] -- Allows restart all containers when the source container exits with a matching restart policy rule. This is an alpha feature behind feature gate RestartAllContainersOnContainerExit. ([#134345](https://github.com/kubernetes/kubernetes/pull/134345), [@yuanwang04](https://github.com/yuanwang04)) [SIG Apps, Node and Testing] -- Changed kuberc configuration schema. Two new optional fields added to kuberc configuration, `credPluginPolicy` and `credPluginAllowlist`. This is documented in [KEP-3104](https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/3104-introduce-kuberc/README.md#allowlist-design-details) and documentation is added to the website by [kubernetes/website#52877](https://github.com/kubernetes/website/pull/52877) ([#134870](https://github.com/kubernetes/kubernetes/pull/134870), [@pmengelbert](https://github.com/pmengelbert)) [SIG API Machinery, Architecture, Auth, CLI, Instrumentation and Testing] -- Enhanced discovery response to support merged API groups/resources from all peer apiservers when UnknownVersionInteroperabilityProxy feature is enabled ([#133648](https://github.com/kubernetes/kubernetes/pull/133648), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Auth, Cloud Provider, Node, Scheduling and Testing] -- Extend `core/v1 Toleration` to support numeric comparison operators (`Gt`, `Lt`). ([#134665](https://github.com/kubernetes/kubernetes/pull/134665), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Node, Scheduling, Testing and Windows] -- Features: NominatedNodeNameForExpectation in kube-scheduler and CleaeringNominatedNodeNameAfterBinding in kube-apiserver are now enabled by default. ([#135103](https://github.com/kubernetes/kubernetes/pull/135103), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG API Machinery, Apps, Architecture, Auth, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Etcd, Instrumentation, Network, Node, Scheduling, Storage and Testing] -- Implement changes to prevent pod scheduling to a node without CSI driver ([#135012](https://github.com/kubernetes/kubernetes/pull/135012), [@gnufied](https://github.com/gnufied)) [SIG API Machinery, Scheduling, Storage and Testing] -- Introduce scheduling.k8s.io/v1alpha1 Workload API to allow for expressing workload-level scheduling requirements and let kube-scheduler act on those. ([#134564](https://github.com/kubernetes/kubernetes/pull/134564), [@macsko](https://github.com/macsko)) [SIG API Machinery, Apps, CLI, Etcd, Scheduling and Testing] -- Introduce the alpha MutableSchedulingDirectivesForSuspendedJobs feature gate (disabled by default) which: - 1. allows to mutate Job's scheduling directives for suspended Jobs - 2. makes the Job controller to clear the status.startTime field for suspended Jobs ([#135104](https://github.com/kubernetes/kubernetes/pull/135104), [@mimowo](https://github.com/mimowo)) [SIG Apps and Testing] -- Introduced GangScheduling kube-scheduler plugin to enable "all-or-nothing" scheduling. Workload API in scheduling.k8s.io/v1alpha1 is used to express the desired policy. ([#134722](https://github.com/kubernetes/kubernetes/pull/134722), [@macsko](https://github.com/macsko)) [SIG API Machinery, Apps, Auth, CLI, Etcd, Scheduling and Testing] -- PV node affinity is now mutable. ([#134339](https://github.com/kubernetes/kubernetes/pull/134339), [@huww98](https://github.com/huww98)) [SIG API Machinery, Apps and Node] -- ResourceQuota now counts device class requests within a ResourceClaim object as consuming two additional quotas when the DRAExtendedResource feature is enabled: - - `requests.deviceclass.resource.k8s.io/` with a quantity equal to the worst case count of devices requested - - requests for device classes that map to an extended resource consume `requests.` ([#134210](https://github.com/kubernetes/kubernetes/pull/134210), [@yliaog](https://github.com/yliaog)) [SIG API Machinery, Apps, Node, Scheduling and Testing] -- The DRA device taints and toleration feature now has a separate feature gate, DRADeviceTaintRules, which controls whether support for DeviceTaintRules is enabled. It is possible to disable that and keep DRADeviceTaints enabled, in which case tainting by DRA drivers through ResourceSlices continues to work. ([#135068](https://github.com/kubernetes/kubernetes/pull/135068), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Node, Scheduling and Testing] -- The ImagePullIntent and ImagePulledRecord objects used by kubelet to store information about image pulls have been moved to the v1beta1 API version. ([#132579](https://github.com/kubernetes/kubernetes/pull/132579), [@stlaz](https://github.com/stlaz)) [SIG Auth and Node] -- The KubeletEnsureSecretPulledImages feature is now beta and enabled by default. ([#135228](https://github.com/kubernetes/kubernetes/pull/135228), [@aramase](https://github.com/aramase)) [SIG Auth, Node and Testing] -- This change adds a new alpha feature Node Declared Features, which includes: - - A new `Node.Status.DeclaredFeatures` field for Kubelet to publish node-specific features. - - A library in `component-helpers` for feature registration and inference. - - A scheduler plugin (`NodeDeclaredFeatures`) scheduler plugin to match pods with nodes that provide their required features. - - An admission plugin (`NodeDeclaredFeatureValidator`) to validate pod updates against a node's declared features. ([#133389](https://github.com/kubernetes/kubernetes/pull/133389), [@pravk03](https://github.com/pravk03)) [SIG API Machinery, Apps, Node, Release, Scheduling and Testing] -- This change allows In Place Resize of Pod Level Resources - - Add Resources in PodStatus to capture resources set at pod-level cgroup - - Add AllocatedResources in PodStatus to capture resources requested in the PodSpec ([#132919](https://github.com/kubernetes/kubernetes/pull/132919), [@ndixita](https://github.com/ndixita)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Instrumentation, Node, Scheduling and Testing] -- Updates to the Partitionable Devices feature which allows for referencing counter sets across different ResourceSlices within the same resource pool. - - Devices from incomplete pools are no longer considered for allocation. - - This contains backwards incompatible changes to the Partitionable Devices alpha feature, so any ResourceSlices that uses the feature should be removed prior to upgrading or downgrading between 1.34 and 1.35. ([#134189](https://github.com/kubernetes/kubernetes/pull/134189), [@mortent](https://github.com/mortent)) [SIG API Machinery, Node, Scheduling and Testing] - -### Feature - -- Add cloud-controller-manager feature gate CloudControllerManagerWatchBasedRoutesReconciliation ([#131220](https://github.com/kubernetes/kubernetes/pull/131220), [@lukasmetzner](https://github.com/lukasmetzner)) [SIG API Machinery and Cloud Provider] -- Add the `UserNamespacesHostNetworkSupport` feature gate. The feature gate defaults to disabled. When the feature gate is enabled, will allow `hostNetwork` pods to use `user namespace`. ([#134893](https://github.com/kubernetes/kubernetes/pull/134893), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Apps, Node and Testing] -- Added a new `source` label in `resourceclaim_controller_resource_claims`. - Added a new metrics for DRAExtendedResource `scheduler_resourceclaim_creates_total`. ([#134523](https://github.com/kubernetes/kubernetes/pull/134523), [@bitoku](https://github.com/bitoku)) [SIG Apps, Instrumentation, Node and Scheduling] -- Added configurable per-device health check timeouts to the DRA health monitoring API. ([#135147](https://github.com/kubernetes/kubernetes/pull/135147), [@harche](https://github.com/harche)) [SIG Node] -- Bump ImageGCMaximumAge to stable ([#134736](https://github.com/kubernetes/kubernetes/pull/134736), [@haircommander](https://github.com/haircommander)) [SIG Node and Testing] -- Enables the `WatchListClient` feature gate. ([#134180](https://github.com/kubernetes/kubernetes/pull/134180), [@p0lyn0mial](https://github.com/p0lyn0mial)) [SIG API Machinery, Apps, Auth, CLI, Instrumentation, Node and Testing] -- Graduate PodTopologyLabelsAdmission feature gate to Beta and on by default. - - Pods will now have labels `topology.kubernetes.io/zone` and `topology.kubernetes.io/region` by default if the assigned Node has these labels. ([#135158](https://github.com/kubernetes/kubernetes/pull/135158), [@andrewsykim](https://github.com/andrewsykim)) [SIG Node] -- Graduate image volume source to on by default Beta ([#135195](https://github.com/kubernetes/kubernetes/pull/135195), [@haircommander](https://github.com/haircommander)) [SIG Apps, Instrumentation, Node and Testing] -- Implement scoring for DRA-backed extended resources ([#134058](https://github.com/kubernetes/kubernetes/pull/134058), [@bart0sh](https://github.com/bart0sh)) [SIG Node, Scheduling and Testing] -- KEP-3619: fined-grained supplemental groups policy is graduated to GA. ([#135088](https://github.com/kubernetes/kubernetes/pull/135088), [@everpeace](https://github.com/everpeace)) [SIG Node and Testing] -- KEP-5440: Allow for resizing of resources while job is suspended. This feature is alpha. ([#132441](https://github.com/kubernetes/kubernetes/pull/132441), [@kannon92](https://github.com/kannon92)) [SIG Apps and Testing] -- KEP-5598 opportunistic batching is implemented to optimize scheduling for pods that have the same scheduling requirements. ([#135231](https://github.com/kubernetes/kubernetes/pull/135231), [@bwsalmon](https://github.com/bwsalmon)) [SIG Node, Scheduling, Storage and Testing] -- Kubeadm: Add `HTTPEndpoints` field to `ClusterConfiguration.Etcd.ExternalEtcd` that can be used to configure the HTTP endpoints for etcd communication in v1beta4. This field is used to separate the HTTP traffic (such as /metrics and /health endpoints) from the gRPC traffic handled by Endpoints. This separation allows for better access control, as HTTP endpoints can be exposed without exposing the primary gRPC interface. Corresponds to etcd's `--listen-client-http-urls` configuration. If not provided, Endpoints will be used for both gRPC and HTTP traffic. ([#134890](https://github.com/kubernetes/kubernetes/pull/134890), [@SataQiu](https://github.com/SataQiu)) [SIG Cluster Lifecycle] -- Kubernetes is now built with go 1.25.4 ([#135187](https://github.com/kubernetes/kubernetes/pull/135187), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] -- New metrics are introduced related to Ensure Secret Pulled Images KEP: - - kubelet_imagemanager_ondisk_pullintents - the number of pull intent records currently kept on disk - - kubelet_imagemanager_ondisk_pulledrecords - the number of image pulled records currently kept on disk - - kubelet_imagemanager_image_mustpull_checks_total{result} - the number for how many times an image was checked against the pull records and the results of those checks ([#132812](https://github.com/kubernetes/kubernetes/pull/132812), [@stlaz](https://github.com/stlaz)) [SIG Auth and Node] -- Pick one device class deterministically for extended resource when there are more than one ([#135037](https://github.com/kubernetes/kubernetes/pull/135037), [@yliaog](https://github.com/yliaog)) [SIG Node, Scheduling and Testing] -- Promoted the `EnvFiles` feature gate to beta and is enabled by default. Additionally, the syntax specification for environment variables has been restricted to a subset of POSIX shell syntax (all variable values must be wrapped in single quotes). ([#134414](https://github.com/kubernetes/kubernetes/pull/134414), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node and Testing] -- Promoted the `KubeletCrashLoopBackOffMax` feature gate to beta, it is now enabled by default. ([#135044](https://github.com/kubernetes/kubernetes/pull/135044), [@hankfreund](https://github.com/hankfreund)) [SIG Node] -- The Pod Certificates feature is moving to beta. The PodCertificateRequest feature gate is still set false by default. To use the feature, users will need to enable the certificates API groups in v1beta1 and enable the feature gate PodCertificateRequest. A new field UserAnnotations is added to the PodCertificateProjection API and the corresponding UnverifiedUserAnnotations is added to the PodCertificateRequest API. ([#134790](https://github.com/kubernetes/kubernetes/pull/134790), [@yt2985](https://github.com/yt2985)) [SIG Auth, Instrumentation and Testing] -- When resizing pods, more events will be emitted when the pod's resize status changes. ([#134825](https://github.com/kubernetes/kubernetes/pull/134825), [@natasha41575](https://github.com/natasha41575)) [SIG Node] - -### Bug or Regression - -- Extended resources requested by initContainers which are allocated using an automatic ResourceClaim now match the behavior of legacy device plugins, reusing the same resources requested by later sidecar initContainers or regular containers when possible, to minimize the total number of devices requested by the pod. ([#134882](https://github.com/kubernetes/kubernetes/pull/134882), [@yliaog](https://github.com/yliaog)) [SIG Apps, CLI, Node, Scheduling and Testing] -- Fix Windows kube-proxy (winkernel) issue where stale RemoteEndpoints remained - when a Deployment was referenced by multiple Services due to premature clearing - of the terminatedEndpoints map. ([#135146](https://github.com/kubernetes/kubernetes/pull/135146), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] -- Fix bug in ValidatingAdmissionPolicy where a object schema with additionalProperties:true would crash the kube-controller-manager with a nil pointer exception. ([#135155](https://github.com/kubernetes/kubernetes/pull/135155), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery] -- Fixes an issue that disallowed restart policies and restart rules on static pods. ([#135031](https://github.com/kubernetes/kubernetes/pull/135031), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node] -- Fixes the replacement tag in APIs to not be a selector for storage version ([#135197](https://github.com/kubernetes/kubernetes/pull/135197), [@Jefftree](https://github.com/Jefftree)) [SIG API Machinery] -- Kube-apiserver: Fixes spurious warning log messages about enabled alpha APIs while starting API server ([#135327](https://github.com/kubernetes/kubernetes/pull/135327), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] -- Kubelet: fix concurrent map write error when creating a pod with empty volume when the LocalStorageCapacityIsolationFSQuotaMonitoring feature-gate is enabled ([#135174](https://github.com/kubernetes/kubernetes/pull/135174), [@carlory](https://github.com/carlory)) [SIG Storage] -- Support ShareID of DRAConsumableCapacity feature in the Kubelet Plugin API ([#134520](https://github.com/kubernetes/kubernetes/pull/134520), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node and Testing] -- The slow initialization of container runtime will not cause System WatchDog to kill kubelet. Device Manager is not considered healthy before it attempted to start listening on the port. ([#135153](https://github.com/kubernetes/kubernetes/pull/135153), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Node] -- Typed workqueue now cleans up goroutines before shutting down ([#135072](https://github.com/kubernetes/kubernetes/pull/135072), [@Jefftree](https://github.com/Jefftree)) [SIG API Machinery] - -### Other (Cleanup or Flake) - -- AggregatedDiscoveryRemoveBetaType feature gate is deprecated and locked to True ([#134230](https://github.com/kubernetes/kubernetes/pull/134230), [@Jefftree](https://github.com/Jefftree)) [SIG API Machinery] -- Dropped support for networking/v1beta1 Ingress in kubectl ([#135176](https://github.com/kubernetes/kubernetes/pull/135176), [@scaliby](https://github.com/scaliby)) [SIG CLI] -- Dropped support for networking/v1beta1 IngressClass in kubectl ([#135108](https://github.com/kubernetes/kubernetes/pull/135108), [@scaliby](https://github.com/scaliby)) [SIG CLI] -- Upgrade CoreDNS to v1.12.4 ([#133968](https://github.com/kubernetes/kubernetes/pull/133968), [@yashsingh74](https://github.com/yashsingh74)) [SIG Cloud Provider and Cluster Lifecycle] - -## Dependencies - -### Added -- cyphar.com/go-pathrs: v0.2.1 - -### Changed -- github.com/coredns/corefile-migration: [v1.0.27 → v1.0.29](https://github.com/coredns/corefile-migration/compare/v1.0.27...v1.0.29) -- github.com/cyphar/filepath-securejoin: [v0.4.1 → v0.6.0](https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.6.0) -- github.com/opencontainers/selinux: [v1.11.1 → v1.13.0](https://github.com/opencontainers/selinux/compare/v1.11.1...v1.13.0) - -### Removed -_Nothing has changed._ - - - -# v1.35.0-alpha.3 - - -## Downloads for v1.35.0-alpha.3 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes.tar.gz) | 054e77631e6a17dcb1589e14aaf215672c054a3315de0e72fad066d5f4392ff09288dc0ead2e9667c65c3c7c770d81206abb94eaf2615b1ef0cc99fbf3a5c793 -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-src.tar.gz) | fe30a5b352bb1656d7306aec0f491fde6f874af7d749fa31fe75ac5035c98d3c63d95db1b0c0024b30c55eadf7b60a1c3513a343eff2d6b0793147112940c82b - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-darwin-amd64.tar.gz) | ca86f0ff39c9ee9ddf75674369cac952652afb3d36c11d8b761d00e9a6f9827adda24d87db6d936ab4ff54cd3d65afcc1e8b77868bc8054837d36cc9725a0fe8 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-darwin-arm64.tar.gz) | b5a6772bfd7fd59ad18d0ccd6cece28d316613c1364607bdcb6389b2be1e911297b8ea3fb4b0ced7c38e66be36bf3f42898e4a5fade67add6a29cc5caec0f449 -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-linux-386.tar.gz) | 8ecf519056385911fcec30039c8c3bf8537726c35ad9637602444dc6f1c5cc4f34fd2b924641b64b5a94b81935deff3a1445bc161fa3c3887a26b6a572e5a126 -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-linux-amd64.tar.gz) | 499d946c3baf4bf55cc12ae0166ebbd3ae2c0c383d0f0cabca18cdc843b101e4fe0a972117f01d59e6eb61056471bdf5ff7b1c124e42298a4d758c01f8d888dc -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-linux-arm.tar.gz) | fc240f0e23fad7578330cfb65ee271b3dfee099fd4fea3df6e5bd6cd5c50d8d398915d3c1dd735593b96ed1f3d30a800dd3ee6b1553c32bbc46428823ff68d6d -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-linux-arm64.tar.gz) | ee4a49a4c55d9fce0cddf8150fa506df2c498abb257bb87c773260c26dc32fcb14be97630a27a22dd7406207778c7111f95751dc80b5dfabdf0755757b9c7082 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-linux-ppc64le.tar.gz) | 758eab71ab6435a689ac081bad27270967b6f8a09532b2dbc1c45b16eb8cc9ee24d317c4c8adf4345c569e89a1edeea8fb6f1bf97f7f84604fc17a7459f9a59f -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-linux-s390x.tar.gz) | 13947dfc8a67de7805e2e0818452d287079f9f382c8e36e8501b0871c5083f3eef1ac0461ca3570abeb39f84391b75843236a98f56f17a75f01a3d88cbfc6998 -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-windows-386.tar.gz) | 813f670f33f20dfbec2dfd53136831f1117b5d172fa381fb1f69348d9f2e1cdda5eff2f807529924d1751d31011f3ba0a9dfd2e395114f8c289cbc3a262a207b -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-windows-amd64.tar.gz) | 6cfafe20404a2d6d8d7f9ed923eabc59360ba16454db8602de7aaaf3f40af7ff0429f54c3a34fd8c94d4a2e83bffeab29de5eed78f45f3cbe4027a8ae23a25c9 -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-client-windows-arm64.tar.gz) | dc4f018a9d7182c32f82727e42624f6b5883e944a730855ab0dd9ab9e8a5eea0766c5214ce5bf63c3bafc795d28bc335a94c9e50f1c4c80887c944780bb7811a - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-server-linux-amd64.tar.gz) | 741bc1b0cb536ae82284b299fbb27c466e7ce3b54ba879a40631c5c00d822bce76dbb927d51ccb50383f22e115c4f0a5d22d8157cd9ea69da797f2fae2229b50 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-server-linux-arm64.tar.gz) | 26d073a93c26511aa3ec2e47954193175a87426d6f489370cbc5d2cbc636e98785a8c065d3cee1e3fcd52f4ee2b37e3137ade65739704b4aa3582c41d9e69341 -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-server-linux-ppc64le.tar.gz) | 2ce67d08040129bc1d290faf63573f7e1881f2ec7eaf02a4a27cbd48285fc315ff336d245c63f6bb8dd5b2e82821beed731bf9e9f807a4d5a0fadac355413183 -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-server-linux-s390x.tar.gz) | 6b12151c9ab895a9c51f7e17b067d165b511f8c7e32c5ee2cb9924087314bcacd74826e1b18bccd1e06b85a9a3c26e151c38fed9a4f40794777bd06f68cb3e95 - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-node-linux-amd64.tar.gz) | f13943abe46974a701c1de6a20f76a2ade96db4795de7f6615680c3a360d602d5efca1d062c206f5154e3c3f504c0e51fda10e96ed31e20b3bd3d711be3600f8 -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-node-linux-arm64.tar.gz) | b7b664dde1dea0469dcab0a8f30032c583210d008621580930feb4a56353f9d51b732643fb41600febae3da3f2f17617c7914487539e4d7be8b4942c52219c85 -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-node-linux-ppc64le.tar.gz) | 38cc958f6bc855b9fb6da6dbe1dd4eda874916865b030b929eab5f4110fa9554d7531757992e54ad912ca41d7eee6f01e6a299132c023199d7751491ae5456da -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-node-linux-s390x.tar.gz) | c32b6d753f1c76bfcac7c37d5986d243cb5b7ad6bd01596b84d4262250ba31d875005302e8b92f7b8bac9ad30a85a6a60f99609fbbcceb0df1d08dfad8539488 -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.3/kubernetes-node-windows-amd64.tar.gz) | c5dfdcf501f39003ff818fa66c90e3874d9db21afc74ce9d6fe20de6f074d755ee0a90e18e47ffebda0da5685e9b42e8385f6e7e3d518b08a911c019686257d9 - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0-alpha.3](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0-alpha.3](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0-alpha.3](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0-alpha.3](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0-alpha.3](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0-alpha.3](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.0-alpha.2 - -## Urgent Upgrade Notes - -### (No, really, you MUST read this before you upgrade) - - - ACTION REQUIRED - - vendor: updated k8s.io/system-validators to v1.12.1. The cgroups validator will now throw an error instead of a warning if cgroups v1 is detected on the host and the provided KubeletVersion is 1.35 or newer. - - kubeadm: started using k8s.io/system-validators v1.12.1 in kubeadm 1.35. During `kubeadm init`, `kubeadm join` and `kubeadm upgrade`, the SystemVerification preflight check will throw an error if cgroups v1 is detected and if the detected kubelet version is 1.35 or newer. For older versions of kubelet, there will be just a preflight warning. - - To allow cgroups v1 with kubeadm and kubelet version 1.35 or newer, you must: - - Ignore the error from the SystemVerifcation preflight check by kubeadm. - - Edit the kube-system/kubelet-config ConfigMap and add the `failCgroupV1: false` field, before upgrading. ([#134744](https://github.com/kubernetes/kubernetes/pull/134744), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle and Node] - - Removed the `--pod-infra-container-image` flag from kubelet's command line. For non-kubeadm clusters, users must manually remove this flag from their kubelet configuration to prevent startup failures before they upgrade kubelet. ([#133779](https://github.com/kubernetes/kubernetes/pull/133779), [@carlory](https://github.com/carlory)) [SIG Node] - -## Changes by Kind - -### API Change - -- Add ObservedGeneration to CustomResourceDefinition Conditions. ([#134984](https://github.com/kubernetes/kubernetes/pull/134984), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] -- Add StorageVersionMigration v1beta1 api and remove the v1alpha API. - - Any use of the v1alpha1 api is no longer supported and - users must remove any v1alpha1 resources prior to upgrade. ([#134784](https://github.com/kubernetes/kubernetes/pull/134784), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Auth, Etcd and Testing] -- CSI drivers can now opt-in to receive service account tokens via the secrets field instead of volume context by setting `spec.serviceAccountTokenInSecrets: true` in the CSIDriver object. This prevents tokens from being exposed in logs and other outputs. The feature is gated by the `CSIServiceAccountTokenSecrets` feature gate (Beta in v1.35). ([#134826](https://github.com/kubernetes/kubernetes/pull/134826), [@aramase](https://github.com/aramase)) [SIG API Machinery, Auth, Storage and Testing] -- DRA device taints: DeviceTaintRule status provided information about the rule, in particular whether pods still need to be evicted ("EvictionInProgress" condition). The new "None" effect can be used to preview what a DeviceTaintRule would do if it used the "NoExecute" effect and to taint devices ("device health") without immediately affecting scheduling or running pods. ([#134152](https://github.com/kubernetes/kubernetes/pull/134152), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Node, Release, Scheduling and Testing] -- DRA: the DynamicResourceAllocation feature gate for the core functionality (GA in 1.34) is now locked to enabled-by-default and thus cannot be disabled anymore. ([#134452](https://github.com/kubernetes/kubernetes/pull/134452), [@pohly](https://github.com/pohly)) [SIG Auth, Node, Scheduling and Testing] -- Forbid adding resources other than CPU & memory on pod resize. ([#135084](https://github.com/kubernetes/kubernetes/pull/135084), [@tallclair](https://github.com/tallclair)) [SIG Apps, Node and Testing] -- Implement constrained impersonation as described in https://kep.k8s.io/5284 ([#134803](https://github.com/kubernetes/kubernetes/pull/134803), [@enj](https://github.com/enj)) [SIG API Machinery, Auth and Testing] -- Introduces a structured and versioned v1alpha1 response for flagz ([#134995](https://github.com/kubernetes/kubernetes/pull/134995), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Architecture, Instrumentation, Network, Node, Scheduling and Testing] -- Introduces a structured and versioned v1alpha1 response for statusz ([#134313](https://github.com/kubernetes/kubernetes/pull/134313), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation, Network, Node, Scheduling and Testing] -- New `--min-compatibility-version` flag for apiserver, kcm and kube scheduler ([#133980](https://github.com/kubernetes/kubernetes/pull/133980), [@siyuanfoundation](https://github.com/siyuanfoundation)) [SIG API Machinery, Architecture, Cluster Lifecycle, Etcd, Scheduling and Testing] -- Promote PodObservedGenerationTracking to GA. ([#134948](https://github.com/kubernetes/kubernetes/pull/134948), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Apps, Node, Scheduling and Testing] -- Promoted Job Managed By to general availability. The `JobManagedBy` feature gate is now locked to true, and will be removed in a future release of Kubernetes. ([#135080](https://github.com/kubernetes/kubernetes/pull/135080), [@dejanzele](https://github.com/dejanzele)) [SIG API Machinery, Apps and Testing] -- Promoted ReplicaSet and Deployment `.status.terminatingReplicas` tracking to beta. The `DeploymentReplicaSetTerminatingReplicas` feature gate is now enabled by default. ([#133087](https://github.com/kubernetes/kubernetes/pull/133087), [@atiratree](https://github.com/atiratree)) [SIG API Machinery, Apps and Testing] -- Scheduler: added a new `bindingTimeout` argument to the DynamicResources plugin configuration. - This allows customizing the wait duration in PreBind for device binding conditions. - Defaults to 10 minutes when DRADeviceBindingConditions and DRAResourceClaimDeviceStatus are both enabled. ([#134905](https://github.com/kubernetes/kubernetes/pull/134905), [@fj-naji](https://github.com/fj-naji)) [SIG Node and Scheduling] -- The Pod Certificates feature is moving to beta. The PodCertificateRequest feature gate is still set false by default. To use the feature, users will need to enable the certificates API groups in v1beta1 and enable the feature gate PodCertificateRequest. A new field UserAnnotations is added to the PodCertificateProjection API and the corresponding UnverifiedUserAnnotations is added to the PodCertificateRequest API. ([#134624](https://github.com/kubernetes/kubernetes/pull/134624), [@yt2985](https://github.com/yt2985)) [SIG API Machinery, Apps, Auth, Etcd, Instrumentation, Node and Testing] -- The StrictCostEnforcementForVAP and StrictCostEnforcementForWebhooks feature gates, locked on since 1.32, have been removed ([#134994](https://github.com/kubernetes/kubernetes/pull/134994), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Auth, Node and Testing] -- The `PreferSameZone` and `PreferSameNode` values for Service's - `trafficDistribution` field are now GA. The old value `PreferClose` is now - deprecated in favor of the more-explicit `PreferSameZone`. ([#134457](https://github.com/kubernetes/kubernetes/pull/134457), [@danwinship](https://github.com/danwinship)) [SIG API Machinery, Apps, Network and Testing] - -### Feature - -- Add the `ChangeContainerStatusOnKubeletRestart` feature gate. The feature gate defaults to disabled. When the feature gate is disabled, the kubelet does not change the pod status upon restart, and pods will not re-run startup probes after kubelet restart. ([#134746](https://github.com/kubernetes/kubernetes/pull/134746), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node and Testing] -- Added a new `source` label in `resourceclaim_controller_resource_claims`. - Added a new metrics for DRAExtendedResource `scheduler_resourceclaim_creates_total`. ([#134523](https://github.com/kubernetes/kubernetes/pull/134523), [@bitoku](https://github.com/bitoku)) [SIG Apps, Instrumentation, Node and Scheduling] -- Added support for tracing in kubectl with --profile=trace ([#134709](https://github.com/kubernetes/kubernetes/pull/134709), [@tchap](https://github.com/tchap)) [SIG CLI] -- Adding new kuberc view/set commands in kubectl to perform operations against kuberc file ([#135003](https://github.com/kubernetes/kubernetes/pull/135003), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] -- Enable MutableCSINodeAllocatableCount by default. ([#134647](https://github.com/kubernetes/kubernetes/pull/134647), [@torredil](https://github.com/torredil)) [SIG Storage] -- Improved throughput in the real-FIFO queue used by informer/controllers by adding batch handling for processing watch events. ([#132240](https://github.com/kubernetes/kubernetes/pull/132240), [@yue9944882](https://github.com/yue9944882)) [SIG API Machinery, Scheduling and Storage] -- Introducing new flag --as-user-extra persistent flag in kubectl that can be used to pass extra arguments during the impersonation ([#134378](https://github.com/kubernetes/kubernetes/pull/134378), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] -- Kube-apiserver: JWT authenticator now report the following metrics: - - apiserver_authentication_jwt_authenticator_jwks_fetch_last_timestamp_seconds - - apiserver_authentication_jwt_authenticator_jwks_fetch_last_key_set_info - - when StructuredAuthenticationConfiguration feature is enabled. ([#123642](https://github.com/kubernetes/kubernetes/pull/123642), [@aramase](https://github.com/aramase)) [SIG API Machinery, Auth and Testing] -- Kubeadm: added a new preflight check `ContainerRuntimeVersion ` to validate if the installed container runtime supports the RuntimeConfig gRPC method. If the container runtime does not support the RuntimeConfig gRPC method, kubeadm will print a warning message. - - Once Kubernetes 1.36 is released, the kubelet might refuse to start if the CRI runtime does not support this feature. More information can be found in https://kubernetes.io/blog/2025/09/12/kubernetes-v1-34-cri-cgroup-driver-lookup-now-ga/. ([#134906](https://github.com/kubernetes/kubernetes/pull/134906), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] -- New counter metric exposing details about kubelet ensuring an image exists on the node is added - `kubelet_image_manager_ensure_image_requests_total{present_locally, pull_policy, pull_required}` ([#132644](https://github.com/kubernetes/kubernetes/pull/132644), [@stlaz](https://github.com/stlaz)) [SIG Auth and Node] -- Promote InPlacePodVerticalScaling to GA. ([#134949](https://github.com/kubernetes/kubernetes/pull/134949), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Node and Scheduling] -- Promote Relaxed validation for Services names to beta (enabled by default) - - Promote `RelaxedServiceNameValidation` feature to beta (enabled by default) - The names of new Services names are validation with `NameIsDNSLabel()`, - relaxing the pre-existing validation. ([#134493](https://github.com/kubernetes/kubernetes/pull/134493), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Network] -- Promote kubectl command headers to stable ([#134777](https://github.com/kubernetes/kubernetes/pull/134777), [@soltysh](https://github.com/soltysh)) [SIG CLI and Testing] -- The SchedulerAsyncAPICalls feature gate has been re-enabled by default after fixing regressions detected in v1.34. ([#135059](https://github.com/kubernetes/kubernetes/pull/135059), [@macsko](https://github.com/macsko)) [SIG Scheduling] -- The scheduler clears the `nominatedNodeName` field for Pods upon scheduling or binding failure. External components, such as Cluster Autoscaler and Karpenter, should not overwrite this field. ([#135007](https://github.com/kubernetes/kubernetes/pull/135007), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Scheduling and Testing] - -### Bug or Regression - -- BlockOwnerDeletion is removed from resource claims created from resource claim templates, and extended resource claims created by scheduler ([#134956](https://github.com/kubernetes/kubernetes/pull/134956), [@yliaog](https://github.com/yliaog)) [SIG Apps, Node and Scheduling] -- Drop DeviceBindingConditions fields if the DRADeviceBindingConditions is not enabled and not in-use ([#134964](https://github.com/kubernetes/kubernetes/pull/134964), [@sunya-ch](https://github.com/sunya-ch)) -- Fix a very old issue where kubelet rejects pods with NodeAffinityFailed due to a stale informer cache. ([#134445](https://github.com/kubernetes/kubernetes/pull/134445), [@natasha41575](https://github.com/natasha41575)) [SIG Node] -- Fix issue in asynchronous preemption: Scheduler checks if preemption is ongoing for a pod before initiating new preemption calls ([#134730](https://github.com/kubernetes/kubernetes/pull/134730), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Scheduling and Testing] -- Fix panic on kubectl api-resources ([#134833](https://github.com/kubernetes/kubernetes/pull/134833), [@rikatz](https://github.com/rikatz)) [SIG CLI] -- Fix setting distinctAttribute=nil when DRAConsumableCapacity is disabled ([#134962](https://github.com/kubernetes/kubernetes/pull/134962), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node] -- Fix the bug which could result in Job status updates failing with the error: - status.startTime: Required value: startTime cannot be removed for unsuspended job - The error could be raised after a Job is resumed, if started and suspended previously. ([#134769](https://github.com/kubernetes/kubernetes/pull/134769), [@dejanzele](https://github.com/dejanzele)) [SIG Apps and Testing] -- Fix: The requests for a config FromClass in the status of a ResourceClaim were not referenced. ([#134793](https://github.com/kubernetes/kubernetes/pull/134793), [@LionelJouin](https://github.com/LionelJouin)) [SIG Node] -- Fixed a bug that caused a deleted pod staying in the binding phase to occupy space on the node in the kube-scheduler. ([#134157](https://github.com/kubernetes/kubernetes/pull/134157), [@macsko](https://github.com/macsko)) [SIG Scheduling and Testing] -- Fixed a bug that prevent allocating the same device that was previously consuming the CounterSet when enabling both DRAConsumableCapacity and DRAPartitionableDevices. ([#134103](https://github.com/kubernetes/kubernetes/pull/134103), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node] -- Fixed a bug where the health of a DRA resource was not reported in the Pod status if the resource claim was generated from a template or used a different local name in the pod spec. ([#134875](https://github.com/kubernetes/kubernetes/pull/134875), [@Jpsassine](https://github.com/Jpsassine)) [SIG Node and Testing] -- Fixes an issue where the kubelet /configz endpoint reported incorrect value for kubeletconfig.cgroupDriver when the cgroup driver setting is received from the container runtime. ([#134743](https://github.com/kubernetes/kubernetes/pull/134743), [@marquiz](https://github.com/marquiz)) [SIG Node] -- Fixes bug where AllocationMode: All would not succeed if a resource pool contained ResourceSlices that wasn't targeting the current node. ([#134466](https://github.com/kubernetes/kubernetes/pull/134466), [@mortent](https://github.com/mortent)) [SIG Node] -- Kube-controller-manager: Fixes a 1.34 regression, which triggered a spurious rollout of existing statefulsets when upgrading the control plane from 1.33 → 1.34. This fix is guarded by a `StatefulSetSemanticRevisionComparison` feature gate, which is enabled by default. ([#135017](https://github.com/kubernetes/kubernetes/pull/135017), [@liggitt](https://github.com/liggitt)) [SIG Apps] -- Kube-scheduler: Pod statuses no longer include specific taint keys or values when scheduling fails because of untolerated taints ([#134740](https://github.com/kubernetes/kubernetes/pull/134740), [@hoskeri](https://github.com/hoskeri)) [SIG Scheduling] -- Namespace is added to the output of dry-run=client of HPA object ([#134263](https://github.com/kubernetes/kubernetes/pull/134263), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] - -### Other (Cleanup or Flake) - -- Added a new filed `Step` in the testing framework to allow volume expansion in configurable step sizes for tests. ([#134760](https://github.com/kubernetes/kubernetes/pull/134760), [@Rishita-Golla](https://github.com/Rishita-Golla)) [SIG Storage and Testing] -- Dropped support for certificates/v1beta1 CertificateSigningRequest in kubectl ([#134782](https://github.com/kubernetes/kubernetes/pull/134782), [@scaliby](https://github.com/scaliby)) [SIG CLI] -- Dropped support for discovery/v1beta1 EndpointSlice in kubectl ([#134913](https://github.com/kubernetes/kubernetes/pull/134913), [@scaliby](https://github.com/scaliby)) [SIG CLI] -- Dropped support for networking/v1beta1 IngressClass in kubectl ([#135108](https://github.com/kubernetes/kubernetes/pull/135108), [@scaliby](https://github.com/scaliby)) [SIG CLI] -- Eliminate use of md5 and prevent future use of md5 in favor of more appropriate hashing algorithms. ([#133511](https://github.com/kubernetes/kubernetes/pull/133511), [@BenTheElder](https://github.com/BenTheElder)) [SIG Apps, Architecture, CLI, Cluster Lifecycle, Network, Node, Security, Storage and Testing] -- Kubeadm: removed the kubeadm-specific feature gate WaitForAllControlPlaneComponents which graduated to GA in 1.34 and was locked to enabled by default. ([#134781](https://github.com/kubernetes/kubernetes/pull/134781), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubeadm: updated the supported etcd version to v3.5.24 for supported control plane versions v1.32, v1.33, and v1.34. ([#134779](https://github.com/kubernetes/kubernetes/pull/134779), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] -- Migrate the cpumanager to contextual logging ([#125912](https://github.com/kubernetes/kubernetes/pull/125912), [@ffromani](https://github.com/ffromani)) [SIG Node] -- Removed the `UserNamespacesPodSecurityStandards` feature gate. The minimum supported Kubernetes version for a kubelet is now v1.31, so the gate is not needed. ([#132157](https://github.com/kubernetes/kubernetes/pull/132157), [@haircommander](https://github.com/haircommander)) [SIG Auth, Node and Testing] -- The FeatureGate SystemdWatchdog is locked to default and will be removed. The Systemd Watchdog functionality in kubelet can be turned on via Systemd without any feature gate set up. See https://kubernetes.io/docs/reference/node/systemd-watchdog/ for information. ([#134691](https://github.com/kubernetes/kubernetes/pull/134691), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Node] -- Updates the etcd client library to v3.6.5 ([#134780](https://github.com/kubernetes/kubernetes/pull/134780), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling and Storage] - -## Dependencies - -### Added -- github.com/Masterminds/semver/v3: [v3.4.0](https://github.com/Masterminds/semver/tree/v3.4.0) -- github.com/gkampitakis/ciinfo: [v0.3.2](https://github.com/gkampitakis/ciinfo/tree/v0.3.2) -- github.com/gkampitakis/go-diff: [v1.3.2](https://github.com/gkampitakis/go-diff/tree/v1.3.2) -- github.com/gkampitakis/go-snaps: [v0.5.15](https://github.com/gkampitakis/go-snaps/tree/v0.5.15) -- github.com/goccy/go-yaml: [v1.18.0](https://github.com/goccy/go-yaml/tree/v1.18.0) -- github.com/joshdk/go-junit: [v1.0.0](https://github.com/joshdk/go-junit/tree/v1.0.0) -- github.com/maruel/natural: [v1.1.1](https://github.com/maruel/natural/tree/v1.1.1) -- github.com/mfridman/tparse: [v0.18.0](https://github.com/mfridman/tparse/tree/v0.18.0) -- github.com/tidwall/gjson: [v1.18.0](https://github.com/tidwall/gjson/tree/v1.18.0) -- github.com/tidwall/match: [v1.1.1](https://github.com/tidwall/match/tree/v1.1.1) -- github.com/tidwall/pretty: [v1.2.1](https://github.com/tidwall/pretty/tree/v1.2.1) -- github.com/tidwall/sjson: [v1.2.5](https://github.com/tidwall/sjson/tree/v1.2.5) -- go.uber.org/automaxprocs: v1.6.0 - -### Changed -- github.com/google/pprof: [d1b30fe → 27863c8](https://github.com/google/pprof/compare/d1b30fe...27863c8) -- github.com/onsi/ginkgo/v2: [v2.21.0 → v2.27.2](https://github.com/onsi/ginkgo/compare/v2.21.0...v2.27.2) -- github.com/onsi/gomega: [v1.35.1 → v1.38.2](https://github.com/onsi/gomega/compare/v1.35.1...v1.38.2) -- github.com/rogpeppe/go-internal: [v1.13.1 → v1.14.1](https://github.com/rogpeppe/go-internal/compare/v1.13.1...v1.14.1) -- go.etcd.io/bbolt: v1.4.2 → v1.4.3 -- go.etcd.io/etcd/api/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/client/pkg/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/client/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/pkg/v3: v3.6.4 → v3.6.5 -- go.etcd.io/etcd/server/v3: v3.6.4 → v3.6.5 -- go.yaml.in/yaml/v2: v2.4.2 → v2.4.3 -- golang.org/x/mod: v0.27.0 → v0.28.0 -- golang.org/x/sync: v0.16.0 → v0.17.0 -- golang.org/x/sys: v0.35.0 → v0.37.0 -- golang.org/x/term: v0.34.0 → v0.36.0 -- golang.org/x/text: v0.28.0 → v0.29.0 -- k8s.io/system-validators: v1.11.1 → v1.12.1 -- k8s.io/utils: 4c0f3b2 → bc988d5 - -### Removed -_Nothing has changed._ - - - -# v1.35.0-alpha.2 - - -## Downloads for v1.35.0-alpha.2 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes.tar.gz) | acba342356249738a81bf6bc6de95e4a30097fdd0ebe956b8cd8a2b0715e3161930f7408bd3b1ca1e05c07de4359485cf887b278987366efef3caf9024e80c6d -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-src.tar.gz) | 6e9f58180f53e57ae6b462d4ab3a13f7cafc9bb9802f8af3254e9f3c78b9883103972dced5dd0796c9c8e4176fd8557754981a63fc4b5eb4fb0d07838027ac70 - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-darwin-amd64.tar.gz) | cb54b9aa876b327915048fa3d9a152abcde442d60cee750566339335b19c668f1d440f1dd79409137e7ee5d7e32e2d3c6e8b3fcaf7f4932b19508b483e3d4172 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-darwin-arm64.tar.gz) | 600f2922a818c9c750269695b9158892fcfdd1dd1311701033f93b396689c7d4625c24880598ea36ca3d1ff76be53dcdff911a96d8f337ec93847e340639a92b -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-linux-386.tar.gz) | bcc1d2c3b5577b22636b7c9aa515fb9944e586d5ae657e066e204388992bb1e9c94dd54ecc7feaaafe46c89943e5500366a26dac11ee2eb32ea3106daf1da51b -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-linux-amd64.tar.gz) | 4250b2063c70cd69b49a50a4a416a9bd5a4e7734ed8b9ccc1081ed12e23c30018c2be9dc377100eb14823bab26aa33670e92d7ba38588a2a0ca011c3d63ecbf5 -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-linux-arm.tar.gz) | 203825398afd6c697ac2fc13b126d7419b1c108362e6bb8a27eddef57e2845dd02735e4c48a5c2aa813f9e0ce24ee97ae94360cf50a9197fba53ba3ac736a50e -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-linux-arm64.tar.gz) | b6669df8c4e096d7ca435bfa481823b74e131907433fc7b7dbf6e6a699f2905a60c98e3c23c9321462ae3afdd707ffea3acf473a13905e63203cebefa80028c2 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-linux-ppc64le.tar.gz) | 37b0ce0d3dfa8dcd2222c63b6572e32ad1a7f07d4164de886b3eca04d4c655a3cc07786090eb24cc20f0bf641cae2efba7ab3c3cd2da5536575571db31aa89da -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-linux-s390x.tar.gz) | 3d7054c4b8d18501b535b0cd070bab316b7393bcb575fc869e2fde7190044b15a42e32dbea6aee64aef933ec1d8c7c11581c61bcb4829e710b26971b133180c3 -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-windows-386.tar.gz) | ae011d1aa7b41160d50b9cd9bc4fe2890bbc2ce2f2b6c63695ae20f36e93cbf189c32deafc0d99c46532917ba291f40965cd4038edcb5bb3a27cd66974dba539 -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-windows-amd64.tar.gz) | bcff5d410cea98ab7e83a66c545b4322d17055ed0b3c7acb110a757e6f0ee55aadfc0174c8c641511ac832024af5b2660f4e2be5c3076a12e0b862aa55a1d02f -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-client-windows-arm64.tar.gz) | 4fb60b2747b500f1139f590e436318fabdd692fc7d2de27be9667c1e5f9af3a6a67796fcd3c69b92e225a04ae92292d715c4c5e1a1437f1723e5bd16d30e5c59 - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-server-linux-amd64.tar.gz) | b29aaf01ad35edf7d24ac2a1d493c28a65941fd9f490bbcaeecfc418b1e26060f90e1677353ace6229ae1b8416f5080e116fcfb90732a7aab094761d9f1dadbe -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-server-linux-arm64.tar.gz) | fc8aeaec77c22d1cb777d9d626f6cbdc0bd178a29d1c125305592d4b40680c51d30d6ffeeb5754abd029c56ed2f49462a85799939f7ffc343f4816da3b9a2d20 -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-server-linux-ppc64le.tar.gz) | 61a70fa842e8afff5bdf3ab45a85b0bae183eb0e3910c440ca21520d3f03e0ee66ffbdd8b335b0d9ccfd2c77fb1a82e0f1480a267da6e6c10255c87465b12965 -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-server-linux-s390x.tar.gz) | 9275068613ddfaa163bbfaed5ae0c69dc2ca2031b3f42f990ed42995b14e7d5ed1bd5d49d3c3b7e95f7024a4434cb3518e05240b82d508f2be2c6d4971d3ab43 - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-node-linux-amd64.tar.gz) | 2200656cfe27817ec8bfc67564fba75c0afb582c75b2ce37734dff1c2757d142d45a24695c7e898b4663362f4058ca0ae8399ee485883833498cac9867caccdc -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-node-linux-arm64.tar.gz) | fe087958b7b7b7197132473508deffa90a740fffc2bf7a06c9a7c7df029394fd27a307efc6bb8003c6f95d9013f57ed577ec4a777881c44acba26a1ebc918ae5 -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-node-linux-ppc64le.tar.gz) | 621a7d6f7f3fcc382922f0912a5dd3f9587ec15992c65be806a18e4b3254895d42dc78ac2b1aab10a16dee1227ca315c1b5b35c27b29946c1337d548b799ddc7 -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-node-linux-s390x.tar.gz) | 16a9074fba6db7ef45b38ed5ea05ae9cd47a6388b01cfa551377de5bc1b720df3507b8bda974c1220d8a82394902192a4013754026f2d71732bb480743862c05 -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.2/kubernetes-node-windows-amd64.tar.gz) | 6a893a6a4ad7f664fea7536f22bd27d4f874e7568658f5863e156c290b4afee2ef566797c6e3dae86b4d706f219b8169da5e03ef61e565b7a4ba2123a6b43c5c - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.35.0-alpha.1 - -## Changes by Kind - -### Deprecation - -- FailCgroupV1 will be set to true from 1.35. - This means that nodes will not start on a cgroup v1 in our default behavior. - This is putting cgroup v1 into a deprecated state. ([#134298](https://github.com/kubernetes/kubernetes/pull/134298), [@kannon92](https://github.com/kannon92)) [SIG Node] -- Mark ipvs mode in kube-proxy as deprecated. ipvs mode in kube-proxy is deprecated and will be removed in a future version of Kubernetes. Users are encouraged to move to nftables. ([#134539](https://github.com/kubernetes/kubernetes/pull/134539), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Network] - -### API Change - -- Kube-apiserver: fix a possible panic validating a custom resource whose CustomResourceDefinition indicates a status subresource exists, but which does not define a `status` property in the `openAPIV3Schema` ([#133721](https://github.com/kubernetes/kubernetes/pull/133721), [@fusida](https://github.com/fusida)) [SIG API Machinery, Apps, Architecture, Auth, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Etcd, Instrumentation, Network, Node, Release, Scheduling, Storage and Testing] -- Kubernetes API Go types removed runtime use of the github.com/gogo/protobuf library, and are no longer registered into the global gogo type registry. Kubernetes API Go types were not suitable for use with the google.golang.org/protobuf library, and no longer implement `ProtoMessage()` by default to avoid accidental incompatible use. If removal of these marker methods impacts your use, it can be re-enabled for one more release with a `kubernetes_protomessage_one_more_release` build tag, but will be removed in 1.36. ([#134256](https://github.com/kubernetes/kubernetes/pull/134256), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling and Storage] -- Promoted HPA configurable tolerance to beta. The `HPAConfigurableTolerance` feature gate is now enabled by default. ([#133128](https://github.com/kubernetes/kubernetes/pull/133128), [@jm-franc](https://github.com/jm-franc)) [SIG API Machinery and Autoscaling] -- The MaxUnavailableStatefulSet feature is now beta and enabled by default. ([#133153](https://github.com/kubernetes/kubernetes/pull/133153), [@helayoty](https://github.com/helayoty)) [SIG API Machinery and Apps] - -### Feature - -- Enable the feature gate `ContainerRestartRules` by default. The ContainerRestartRules feature is promoted to beta. Fixing a bug in this feature that caused probes continue to run even if the container has terminated and is not restartable. ([#134631](https://github.com/kubernetes/kubernetes/pull/134631), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node] -- Kube-apiserver: the subresources `pods/exec`, `pods/attach`, and `pods/portforward` now require `create` permission for both SPDY and Websocket API requests. Previously, SPDY requests required `create` permission, but Websocket requests only required `get` permission. This change is gated by the `AuthorizePodWebsocketUpgradeCreatePermission` feature-gate, which is enabled by default. - - Before upgrading to 1.35, ensure any custom ClusterRoles and Roles intended to grant `pods/exec`, `pods/attach`, or `pods/portforward` permission include the `create` verb. ([#134577](https://github.com/kubernetes/kubernetes/pull/134577), [@seans3](https://github.com/seans3)) [SIG API Machinery, Auth, Node and Testing] -- Kubeadm: print the errors during retires related to the WaitForAllControlPlaneComponents functionality at verbosity level 5. ([#134433](https://github.com/kubernetes/kubernetes/pull/134433), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubernetes is now built using Go 1.25.3 ([#134611](https://github.com/kubernetes/kubernetes/pull/134611), [@cpanato](https://github.com/cpanato)) [SIG Architecture, Cloud Provider, Etcd, Release, Storage and Testing] -- Locked the (generally available) feature gate `ExecProbeTimeout` to true. ([#134635](https://github.com/kubernetes/kubernetes/pull/134635), [@vivzbansal](https://github.com/vivzbansal)) [SIG Node and Testing] -- Promoted the `HostnameOverride` feature gate to beta and is enabled by default. ([#134729](https://github.com/kubernetes/kubernetes/pull/134729), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Network and Node] - -### Documentation - -- Kubectl describe, get, drain and events have the ability to set chunk-size using --chunk-size flag, which is now officially stable. ([#134481](https://github.com/kubernetes/kubernetes/pull/134481), [@soltysh](https://github.com/soltysh)) [SIG CLI] - -### Bug or Regression - -- DRA API: the "tolerations" field in exact and sub requests now gets dropped properly when the DRADeviceTaints API is disabled. ([#132927](https://github.com/kubernetes/kubernetes/pull/132927), [@pohly](https://github.com/pohly)) -- DRA Device Taints: tolerating a NoExecute did not work because the scheduler did not inform the eviction controller about the toleration, so the scheduled pod got evicted almost immediately. ([#134479](https://github.com/kubernetes/kubernetes/pull/134479), [@pohly](https://github.com/pohly)) [SIG Apps, Node, Scheduling and Testing] -- Endpoints/endpointslice controllers perform much better when there are a large number of services in a single namespace ([#134739](https://github.com/kubernetes/kubernetes/pull/134739), [@shyamjvs](https://github.com/shyamjvs)) [SIG Apps and Network] -- Fixed a bug that prevents schedule next pod when using DRAConsumableCapacity feature. (#133705, @sunya-ch) ([#133706](https://github.com/kubernetes/kubernetes/pull/133706), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node] -- Fixed a bug where 64 bit IPv6 ServiceCIDRs allocated addresses outside the subnet range. ([#134193](https://github.com/kubernetes/kubernetes/pull/134193), [@hoskeri](https://github.com/hoskeri)) [SIG Network] -- Fixed a startup probe race condition that caused main containers to remain stuck in "Initializing" state when sidecar containers with startup probes failed initially but succeeded on restart in pods with restartPolicy=Never. ([#133072](https://github.com/kubernetes/kubernetes/pull/133072), [@AadiDev005](https://github.com/AadiDev005)) [SIG Node and Testing] -- Kube-apiserver: when --requestheader-client-ca-file and --client-ca-file contain overlapping certificates, --requestheader-allowed-names must be specified to ensure regular client certificates cannot set authenticating proxy headers for arbitrary users ([#131411](https://github.com/kubernetes/kubernetes/pull/131411), [@ballista01](https://github.com/ballista01)) [SIG API Machinery, Auth and Security] -- Kube-controller-manager: Resolves potential issues handling pods with incorrect uids in their ownerReference ([#134654](https://github.com/kubernetes/kubernetes/pull/134654), [@liggitt](https://github.com/liggitt)) [SIG Apps] -- Kubeadm: avoid panicing if the user has malformed the kubeconfig in the cluster-info config map to not include a valid current context. Include proper validation at the appropriate locations and throw errors instead. ([#134715](https://github.com/kubernetes/kubernetes/pull/134715), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubeadm: fixes a preflight check that can fail hostname construction in IPV6 setups ([#134588](https://github.com/kubernetes/kubernetes/pull/134588), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Auth, Cloud Provider, Cluster Lifecycle and Testing] -- Legacy watch calls (RV = 0 or unset) that generate init-events weigh higher in APF seat usage now. Properly accounting for their cost protects the API server from CPU overload. Users might see increased throttling of such calls as a result. ([#134601](https://github.com/kubernetes/kubernetes/pull/134601), [@shyamjvs](https://github.com/shyamjvs)) [SIG API Machinery] -- Prevent a segfault occurring when updating deeply nested JSON fields ([#134381](https://github.com/kubernetes/kubernetes/pull/134381), [@kon-angelo](https://github.com/kon-angelo)) [SIG API Machinery and CLI] -- The kubelet now honors the configuration userNamespaces.idsPerPod. Before it was ignored. ([#133373](https://github.com/kubernetes/kubernetes/pull/133373), [@AkihiroSuda](https://github.com/AkihiroSuda)) [SIG Node and Testing] - -### Other (Cleanup or Flake) - -- Building Kubernetes is now implemented by running a pre-built container image directly, without running rsyncd, and is substantially simplified. ([#134510](https://github.com/kubernetes/kubernetes/pull/134510), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release and Testing] -- CPU Manager static policy option `strict-cpu-reservation` moved to the GA version ([#134388](https://github.com/kubernetes/kubernetes/pull/134388), [@psasnal](https://github.com/psasnal)) [SIG Node] -- Dropped support for policy/v1beta1 PodDisruptionBudget in kubectl ([#134685](https://github.com/kubernetes/kubernetes/pull/134685), [@scaliby](https://github.com/scaliby)) [SIG CLI] -- Kubeadm: stoped applying the --pod-infra-container-image flag for the kubelet. The flag has been deprecated and no longer served a purpose in the kubelet as the logic was migrated to CRI. During upgrade, kubeadm will attempt to remove the flag from the file /var/lib/kubelet/kubeadm-flags.env. ([#133778](https://github.com/kubernetes/kubernetes/pull/133778), [@carlory](https://github.com/carlory)) [SIG Cloud Provider and Cluster Lifecycle] -- Kubeadm: updated the supported etcd version to v3.5.23 for supported control plane versions v1.31, v1.32, and v1.33. ([#134692](https://github.com/kubernetes/kubernetes/pull/134692), [@joshjms](https://github.com/joshjms)) [SIG Cluster Lifecycle and Etcd] -- Kubeadm: updated the supported etcd version to v3.5.24 for supported control plane versions v1.32, v1.33, and v1.34. ([#134779](https://github.com/kubernetes/kubernetes/pull/134779), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] -- Kubernetes is now built with go 1.25.3 ([#134598](https://github.com/kubernetes/kubernetes/pull/134598), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] -- Promote the Topology Manager policy option max-allowable-numa-nodes to GA ([#134614](https://github.com/kubernetes/kubernetes/pull/134614), [@ffromani](https://github.com/ffromani)) [SIG Node] -- Rsync is no longer required to build kubernetes. ([#134656](https://github.com/kubernetes/kubernetes/pull/134656), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release and Testing] -- The storage.k8s.io/v1alpha1 VolumeAttributesClass API is no longer served in 1.35 ([#134625](https://github.com/kubernetes/kubernetes/pull/134625), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Etcd, Storage and Testing] - -## Dependencies - -### Added -_Nothing has changed._ - -### Changed -_Nothing has changed._ - -### Removed -_Nothing has changed._ - - - -# v1.35.0-alpha.1 - - -## Downloads for v1.35.0-alpha.1 - - - -### Source Code - -filename | sha512 hash --------- | ----------- -[kubernetes.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes.tar.gz) | 1d6fb6a4c7f82fe04e56757b733c3fc4aac652f8c2113e79ddce83b6cbe0179404147b35ddbc18e1b60eb802acb3f6d884599fd573f3d16f0558ef7ddfb8aae2 -[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-src.tar.gz) | 364788bac4d405ac6180fe3cb7e3d847e7960fcb0532146b105270aeac2624ade2ff87370c5aa8f768eda07fd28e5e75f73afbdf9cc1b786827a0e123bdea561 - -### Client Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-darwin-amd64.tar.gz) | 1ba40849b104851d922bce32dc9306004e9b95cfadeff9ecfb65f779892009f9a70878b8efe96159088b1ad8c700bf19e58d68416dfdff7853660e6074dd3752 -[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-darwin-arm64.tar.gz) | 0c939895ad2d53f57e9137774eed99cbfbfa5f15d4276f5f55c4ea40b922a5f37ab375a065fdd330f5a1ddf452896f2a13621075b049f382ab42a65ea1085dac -[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-linux-386.tar.gz) | e98a9b2d5f1c8bec552be6353b623a8f12078befd968a662a933907d0ff72b0164fa8b38e4cbd4aae6191e28aedc19d996ec99298053d7bafc458f91580a7cfa -[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-linux-amd64.tar.gz) | 34ab1e9edf70c84fe58a223e91b0bf679e5d1273a2b6503a18a61a4bea79231948efe098f84e39f83dbfa2c5271aad8e9819aac104d6c3360c97e5c348b15be7 -[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-linux-arm.tar.gz) | 554f8240597e7eb8470c8e2b4bca33c06a0a91746831ef93f76b344f5c3d6226d4ef26cb59f127d1436ac091b7b79395320fe8a9b2acc512afd601989c138d4a -[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-linux-arm64.tar.gz) | 62c750654e898622aa87b2d81d4b0cbaf36614899f37181c2e3a6aa645d2270c4dedb7d6e7b974059a716ad144a5615407e6a9a4c03f761a512d37fdda796e50 -[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-linux-ppc64le.tar.gz) | bbe22979c4e300675dfa955ce9855b7b33b29119a9f78e58bc1b088dba2b8dedbf0b068092d42cd98dbc10cda1da317eae6414b91587593678ec76780ff575df -[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-linux-s390x.tar.gz) | b8360d0bf930149d360da2a95549b35cb7e14932ae8507d99e34d93729ae645bab203dfba325c74db13204e09e5ee032f887cdd67badfbf3bc8a08d71ccf9c3d -[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-windows-386.tar.gz) | cc935a74f30dcd1eaaeadc8f2353a9742ebc4a36b133342c6402b065750f4028a1a392bd5f7ea51533c2d799ff2bdb3d0f21493e7fabacb27289e58f011bb229 -[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-windows-amd64.tar.gz) | 119742103985be0cd4296b85aa2c713cdc510b9a9412706fdf88ca1c703f69338146efc5cf37168ab56e74576ad561ce37c3f500d29b63002139d11544b1b7cf -[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-client-windows-arm64.tar.gz) | 67f5fe6b14aa4c49acb6f706ec4a9e43b87f1e19555579895452183e0b2d2be2202f8d48622208ac5ef6e0fb9050d99bb7e1ed9e4e31e8fcac7c0b5e44787c39 - -### Server Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-server-linux-amd64.tar.gz) | a1c935db625766b02113087068fa1087a6b74e9f57ad72cc1d5d85e830c0569b9257746013053ee8dc89404940458c3bba00064666978ddff4df9f3cae0ae066 -[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-server-linux-arm64.tar.gz) | 5f87f8f46719af413fa864b7d91d5b95fa86adf27df63cf904470b15e844ebda4c802d7ec7cb4006b4e5a3780903d0436eb57a7e2f2b79b74cf5e7e8f65496b1 -[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-server-linux-ppc64le.tar.gz) | 9f2e9476ae3b95919c991dd0438b31eeded7c7f686948ef2d6227311dabc952e74f61d3638f224eb18e63a4fe4955b55a9e358032477b989800541617a8b5f6a -[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-server-linux-s390x.tar.gz) | 043311de9dad81d3774decc3aeeac48833d2d234a15ce4a2062fd9af778879afde5e2eb9fdec5f3641725f630e7c3bd845a348ad713b54275e77293941d4c8d0 - -### Node Binaries - -filename | sha512 hash --------- | ----------- -[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-node-linux-amd64.tar.gz) | 865b4ee818cd53bc91001f658243e6b6fd9464f17ef8dc0cc739586689f39998e5c47630df439ad43553d6830ae3a7375cc780c3a5e49f1422d35d77194efc35 -[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-node-linux-arm64.tar.gz) | fc7df94bc328817d20c59e1ab1371634bf3849141ad982c9d403b136d009c3ed9ee3f7a20659a0f93af7179e13e2c0835b80fefc677b8840f7bcbde0dabb4483 -[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-node-linux-ppc64le.tar.gz) | 8ec718436680766d9026b56ece5bff7a6bac9f63da0edf33843a7a6c255e1a1d22aecf260c1d5fc394c1e2f581931c5ae0acad80a0141fc8d4d7730bf04566b5 -[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-node-linux-s390x.tar.gz) | fff0562e3a89b4f9444ce83bb8cfc860bcd5178a2c1ba0f1404d87556f483d48241aa3f927f2534d811b1c554958c167f1e5199fa916ebfd9a754cef2f761139 -[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.0-alpha.1/kubernetes-node-windows-amd64.tar.gz) | 7774299a2b581a4ab2514d8dc95c1d0dff0652aa0518efbffa9ac39f5c510cb83d8a41a70e9b4e78f0b179e5a806402310dac161ff3a2398b97755938c225586 - -### Container Images - -All container images are available as manifest lists and support the described -architectures. It is also possible to pull a specific architecture directly by -adding the "-$ARCH" suffix to the container image name. - -name | architectures ----- | ------------- -[registry.k8s.io/conformance:v1.35.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) -[registry.k8s.io/kube-apiserver:v1.35.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) -[registry.k8s.io/kube-controller-manager:v1.35.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) -[registry.k8s.io/kube-proxy:v1.35.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) -[registry.k8s.io/kube-scheduler:v1.35.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) -[registry.k8s.io/kubectl:v1.35.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) - -## Changelog since v1.34.0 - -## Changes by Kind - -### API Change - -- Added WithOrigin within apis/core/validation with adjusted tests ([#132825](https://github.com/kubernetes/kubernetes/pull/132825), [@PatrickLaabs](https://github.com/PatrickLaabs)) [SIG Apps] -- Component-base: validate that log-flush-frequency is positive and return an error instead of panic-ing ([#133540](https://github.com/kubernetes/kubernetes/pull/133540), [@BenTheElder](https://github.com/BenTheElder)) [SIG Architecture, Instrumentation, Network and Node] -- Feature gate dependencies are now explicit, and validated at startup. A feature can no longer be enabled if it depends on a disabled feature. In particular, this means that `AllAlpha=true` will no longer work without enabling disabled-by-default beta features that are depended on (either with `AllBeta=true` or explicitly enumerating the disabled dependencies). ([#133697](https://github.com/kubernetes/kubernetes/pull/133697), [@tallclair](https://github.com/tallclair)) [SIG API Machinery, Architecture, Cluster Lifecycle and Node] -- In version 1.34, the PodObservedGenerationTracking feature has been upgraded to beta, and the description of the alpha version in the openapi has been removed. ([#133883](https://github.com/kubernetes/kubernetes/pull/133883), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG Apps] -- Introduce a new declarative validation tag +k8s:customUnique to control listmap uniqueness ([#134279](https://github.com/kubernetes/kubernetes/pull/134279), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery and Auth] -- Kube-apiserver: Fixed a 1.34 regression in CustomResourceDefinition handling that incorrectly warned about unrecognized formats on number and integer properties ([#133896](https://github.com/kubernetes/kubernetes/pull/133896), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cloud Provider, Contributor Experience, Network, Node and Scheduling] -- OpenAPI model packages of API types are generated into `zz_generated.model_name.go` files and are accessible using the `OpenAPIModelName()` function. This allows API authors to declare the desired OpenAPI model packages instead of using the go package path of API types. ([#131755](https://github.com/kubernetes/kubernetes/pull/131755), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling, Storage and Testing] -- Support for `kubectl get -o kyaml` is now on by default. To disable it, set `KUBECTL_KYAML=false`. ([#133327](https://github.com/kubernetes/kubernetes/pull/133327), [@thockin](https://github.com/thockin)) [SIG CLI] -- The storage version for MutatingAdmissionPolicy is updated to v1beta1. ([#133715](https://github.com/kubernetes/kubernetes/pull/133715), [@cici37](https://github.com/cici37)) [SIG API Machinery, Etcd and Testing] - -### Feature - -- Add paths section to kubelet statusz endpoint ([#133239](https://github.com/kubernetes/kubernetes/pull/133239), [@Peac36](https://github.com/Peac36)) [SIG Node] -- Add paths section to scheduler statusz endpoint ([#132606](https://github.com/kubernetes/kubernetes/pull/132606), [@Peac36](https://github.com/Peac36)) [SIG API Machinery, Architecture, Instrumentation, Network, Node, Scheduling and Testing] -- Added kubectl config set-context -n flag as a shorthand for --namespace ([#134384](https://github.com/kubernetes/kubernetes/pull/134384), [@tchap](https://github.com/tchap)) [SIG CLI and Testing] -- Added remote runtime and image `Close()` method to be able to close the connection. ([#133211](https://github.com/kubernetes/kubernetes/pull/133211), [@saschagrunert](https://github.com/saschagrunert)) [SIG Node] -- Adds metric for Maxunavailable feature ([#130951](https://github.com/kubernetes/kubernetes/pull/130951), [@Edwinhr716](https://github.com/Edwinhr716)) [SIG Apps and Instrumentation] -- Applyconfiguration-gen now generates extract functions for all subresources ([#132665](https://github.com/kubernetes/kubernetes/pull/132665), [@mrIncompetent](https://github.com/mrIncompetent)) [SIG API Machinery] -- Applyconfiguration-gen now preserves struct and field comments from source types in generated code ([#132663](https://github.com/kubernetes/kubernetes/pull/132663), [@mrIncompetent](https://github.com/mrIncompetent)) [SIG API Machinery] -- DRA: the resource.k8s.io API now uses the v1 API version (introduced in 1.34) as default storage version. Downgrading to 1.33 is not supported. ([#133876](https://github.com/kubernetes/kubernetes/pull/133876), [@kei01234kei](https://github.com/kei01234kei)) [SIG API Machinery, Etcd and Testing] -- Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Warning Failed 7m11s (x2 over 7m33s) kubelet spec.containers{nginx}: Failed to pull image "nginx": failed to pull and unpack image... ([#133627](https://github.com/kubernetes/kubernetes/pull/133627), [@itzPranshul](https://github.com/itzPranshul)) [SIG CLI] -- Introduces e2e tests that check component invariant metrics across the entire suite run. ([#133394](https://github.com/kubernetes/kubernetes/pull/133394), [@BenTheElder](https://github.com/BenTheElder)) [SIG Testing] -- K8s.io/apimachinery: Introduce a helper function to compare resourceVersion strings from two objects of the same resource ([#134330](https://github.com/kubernetes/kubernetes/pull/134330), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Auth, Instrumentation, Network, Node, Scheduling, Storage and Testing] -- Kubeadm: graduate the kubeadm specific feature gate ControlPlaneKubeletLocalMode to GA and lock it to enabled by default. To opt-out manually from this desired default behavior you must patch the "server" field in the /etc/kubernetes/kubelet.conf file. The subphase of "kubeadm join phase control-plane-join" called "etcd" is now deprecated, hidden and replaced by the subphase with identical functionality "etcd-join". "etcd" will be removed in a follow-up release. The subphase "kubelet-wait-bootstrap" of "kubeadm join" is no longer experimental and will always run. ([#134106](https://github.com/kubernetes/kubernetes/pull/134106), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubernetes is now built using Go 1.25.1 ([#134095](https://github.com/kubernetes/kubernetes/pull/134095), [@dims](https://github.com/dims)) [SIG Release and Testing] -- Kubernetes now uses Go Language Version 1.25, including https://go.dev/blog/container-aware-gomaxprocs ([#134120](https://github.com/kubernetes/kubernetes/pull/134120), [@BenTheElder](https://github.com/BenTheElder)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Release, Scheduling and Storage] -- Lock down the `AllowOverwriteTerminationGracePeriodSeconds` feature gate. ([#133792](https://github.com/kubernetes/kubernetes/pull/133792), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] -- Metrics: exclude dryRun requests from apiserver_request_sli_duration_seconds ([#131092](https://github.com/kubernetes/kubernetes/pull/131092), [@aldudko](https://github.com/aldudko)) [SIG API Machinery and Instrumentation] -- The validation in the resouce.k8s.io has been migrated to declarative validation. - If the `DeclarativeValidation` feature gate is enabled, mismatches with existing validation are reported via metrics. - If the `DeclarativeValidationTakeover` feature gate is enabled, declarative validation is the primary source of errors for migrated fields. ([#134072](https://github.com/kubernetes/kubernetes/pull/134072), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Apps and Auth] - -### Bug or Regression - -- Added the correct error when eviction is blocked due to the failSafe mechanism of the DisruptionController. ([#133097](https://github.com/kubernetes/kubernetes/pull/133097), [@kei01234kei](https://github.com/kei01234kei)) [SIG Apps and Node] -- Bugfix: the default serviceCIDR controller was not logging events because the event broadcaster was shutdown during its initialization. ([#133338](https://github.com/kubernetes/kubernetes/pull/133338), [@aojea](https://github.com/aojea)) [SIG Network] -- Deprecated metrics will be hidden as per the metrics deprecation policy https://kubernetes.io/docs/reference/using-api/deprecation-policy/#deprecating-a-metric ([#133436](https://github.com/kubernetes/kubernetes/pull/133436), [@richabanker](https://github.com/richabanker)) [SIG Architecture, Instrumentation and Network] -- Fix incorrect behavior of preemptor pod when preemption of the victim takes long to complete. The preemptor pod should not be circling in scheduling cycles until preemption is finished. ([#134294](https://github.com/kubernetes/kubernetes/pull/134294), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Scheduling and Testing] -- Fix missing kubelet_volume_stats_* metrics ([#133890](https://github.com/kubernetes/kubernetes/pull/133890), [@huww98](https://github.com/huww98)) [SIG Instrumentation and Node] -- Fix occasional schedule delay when the static PV is created ([#133929](https://github.com/kubernetes/kubernetes/pull/133929), [@huww98](https://github.com/huww98)) [SIG Scheduling and Storage] -- Fix resource claims deallocation for extended resource when pod is completed ([#134312](https://github.com/kubernetes/kubernetes/pull/134312), [@alaypatel07](https://github.com/alaypatel07)) [SIG Apps, Node and Testing] -- Fixed SELinux warning controller not emitting events on some SELinux label conflicts. ([#133425](https://github.com/kubernetes/kubernetes/pull/133425), [@jsafrane](https://github.com/jsafrane)) [SIG Apps, Storage and Testing] -- Fixed a bug in kube-proxy nftables mode (GA as of 1.33) that fails to determine if traffic originates from a local source on the node. The issue was caused by using the wrong meta `iif` instead of `iifname` for name based matches. ([#134024](https://github.com/kubernetes/kubernetes/pull/134024), [@jack4it](https://github.com/jack4it)) [SIG Network] -- Fixed a bug in kube-scheduler where pending pod preemption caused preemptor pods to be retried more frequently. ([#134245](https://github.com/kubernetes/kubernetes/pull/134245), [@macsko](https://github.com/macsko)) [SIG Scheduling and Testing] -- Fixed a bug that caused apiservers to send an inappropriate Content-Type request header to authorization, token authentication, imagepolicy admission, and audit webhooks when the alpha client-go feature gate "ClientsPreferCBOR" is enabled. ([#132960](https://github.com/kubernetes/kubernetes/pull/132960), [@benluddy](https://github.com/benluddy)) [SIG API Machinery and Node] -- Fixed a bug that caused duplicate validation when updating PersistentVolumeClaims, VolumeAttachments and VolumeAttributesClasses. ([#132549](https://github.com/kubernetes/kubernetes/pull/132549), [@gavinkflam](https://github.com/gavinkflam)) [SIG Storage] -- Fixed a bug that caused duplicate validation when updating role and role binding resources. ([#132550](https://github.com/kubernetes/kubernetes/pull/132550), [@gavinkflam](https://github.com/gavinkflam)) [SIG Auth] -- Fixed a bug where high latency kube-apiserver caused scheduling throughput degradation. ([#134154](https://github.com/kubernetes/kubernetes/pull/134154), [@macsko](https://github.com/macsko)) [SIG Scheduling] -- Fixed broken shell completion for api resources. ([#133771](https://github.com/kubernetes/kubernetes/pull/133771), [@marckhouzam](https://github.com/marckhouzam)) [SIG CLI] -- Fixed validation error when ConfigFlags has CertFile and (or) KeyFile and original config also contains CertFileData and (or) KeyFileData. ([#133917](https://github.com/kubernetes/kubernetes/pull/133917), [@n2h9](https://github.com/n2h9)) [SIG API Machinery and CLI] -- Fixes a possible data race during metrics registration ([#134390](https://github.com/kubernetes/kubernetes/pull/134390), [@liggitt](https://github.com/liggitt)) [SIG Architecture and Instrumentation] -- Implicit extended resource name derived from device class (deviceclass.resource.kubernetes.io/) can be used to request DRA devices matching the device class. ([#133363](https://github.com/kubernetes/kubernetes/pull/133363), [@yliaog](https://github.com/yliaog)) [SIG Node, Scheduling and Testing] -- Kube-apiserver: Fixes a 1.34 regression with spurious "Error getting keys" log messages ([#133817](https://github.com/kubernetes/kubernetes/pull/133817), [@serathius](https://github.com/serathius)) [SIG API Machinery and Etcd] -- Kube-apiserver: Fixes a possible 1.34 performance regression calculating object size statistics for resources not served from the watch cache, typically only Events ([#133873](https://github.com/kubernetes/kubernetes/pull/133873), [@serathius](https://github.com/serathius)) [SIG API Machinery and Etcd] -- Kube-apiserver: improve the validation error message shown for custom resources with CEL validation rules to include the value that failed validation ([#132798](https://github.com/kubernetes/kubernetes/pull/132798), [@cbandy](https://github.com/cbandy)) [SIG API Machinery] -- Kube-controller-manager: Fixes a possible data race in the garbage collection controller ([#134379](https://github.com/kubernetes/kubernetes/pull/134379), [@liggitt](https://github.com/liggitt)) [SIG API Machinery and Apps] -- Kubeadm: ensured waiting for apiserver uses a local client that doesn't reach to the control plane endpoint and instead reaches directly to the local API server endpoint. ([#134265](https://github.com/kubernetes/kubernetes/pull/134265), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubeadm: fix KUBEADM_UPGRADE_DRYRUN_DIR not honored in upgrade phase when writing kubelet config files ([#134007](https://github.com/kubernetes/kubernetes/pull/134007), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] -- Kubeadm: fixed a bug where the node registration information for a given node was not fetched correctly during "kubeadm upgrade node" and the node name can end up being incorrect in cases where the node name is not the same as the host name. ([#134319](https://github.com/kubernetes/kubernetes/pull/134319), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] -- Kubeadm: fixed bug where v1beta3's ClusterConfiguration.APIServer.TimeoutForControlPlane is not respected in newer versions of kubeadm where v1beta4 is the default. ([#133513](https://github.com/kubernetes/kubernetes/pull/133513), [@tom1299](https://github.com/tom1299)) [SIG Cluster Lifecycle] -- Kubelet: the connection to a DRA driver became unusable because of an internal deadlock when a connection was idle for 30 minutes. ([#133926](https://github.com/kubernetes/kubernetes/pull/133926), [@pohly](https://github.com/pohly)) [SIG Node] -- Pod can have multiple volumes reference the same PVC ([#122140](https://github.com/kubernetes/kubernetes/pull/122140), [@huww98](https://github.com/huww98)) [SIG Node, Storage and Testing] -- Previously, `kubectl scale` returned the error message `error: no objects passed to scale "" not found` when the specified resource did not exist. - For consistency with other commands(e.g. `kubectl get`), it has been changed to just return `Error from server (NotFound): "" not found`. ([#134017](https://github.com/kubernetes/kubernetes/pull/134017), [@mochizuki875](https://github.com/mochizuki875)) [SIG CLI] -- Promote VAC API test to conformance ([#133615](https://github.com/kubernetes/kubernetes/pull/133615), [@carlory](https://github.com/carlory)) [SIG Architecture, Storage and Testing] -- Remove incorrectly printed warning for SessionAffinity whenever a headless service is creater or updated ([#134054](https://github.com/kubernetes/kubernetes/pull/134054), [@Peac36](https://github.com/Peac36)) [SIG Network] -- The SchedulerAsyncAPICalls feature gate has been disabled to mitigate a bug where its interaction with asynchronous preemption in could degrade kube-scheduler performance, particularly under high kube-apiserver load. ([#134400](https://github.com/kubernetes/kubernetes/pull/134400), [@macsko](https://github.com/macsko)) [SIG Scheduling] -- When image garbage collection is unable to free enough disk space, the FreeDiskSpaceFailed warning event is now more actionable. Example: `Insufficient free disk space on the node's image filesystem (95.0% of 10.0 GiB used). Failed to free sufficient space by deleting unused images. Consider resizing the disk or deleting unused files.` ([#132578](https://github.com/kubernetes/kubernetes/pull/132578), [@drigz](https://github.com/drigz)) [SIG Node] - -### Other (Cleanup or Flake) - -- Bump addon manager to use kubectl v1.32.2 ([#130548](https://github.com/kubernetes/kubernetes/pull/130548), [@Jefftree](https://github.com/Jefftree)) [SIG Cloud Provider, Scalability and Testing] -- Dropping the experimental prefix from kubectl wait command's short description, since kubectl wait command has been stable for a long time. ([#133907](https://github.com/kubernetes/kubernetes/pull/133907), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI] -- Fix formatting of assorted go API deprecations for godoc / pkgsite and enable a linter to help catch mis-formatted deprecations ([#133571](https://github.com/kubernetes/kubernetes/pull/133571), [@BenTheElder](https://github.com/BenTheElder)) [SIG API Machinery, Architecture, CLI, Instrumentation and Testing] -- Improved HPA performance when using container-specific resource metrics by optimizing container lookup logic to exit early once the target container is found, reducing unnecessary iterations through all containers in a pod. ([#133415](https://github.com/kubernetes/kubernetes/pull/133415), [@AadiDev005](https://github.com/AadiDev005)) [SIG Apps and Autoscaling] -- Kube-apiserver: Fixes an issue where passing invalid DeleteOptions incorrectly returned status 500 rather than 400. ([#133358](https://github.com/kubernetes/kubernetes/pull/133358), [@ostrain](https://github.com/ostrain)) [SIG API Machinery] -- Kubeadm: removed the `RootlessControlPlane` feature gate. User Namespaces will serve as its replacement. ([#134178](https://github.com/kubernetes/kubernetes/pull/134178), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Cluster Lifecycle] -- Remove container name from messages for container created and started events. ([#134043](https://github.com/kubernetes/kubernetes/pull/134043), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] -- Removed deprecated gogo protocol definitions from `k8s.io/kubelet/pkg/apis/dra` in favor of `google.golang.org/protobuf`. ([#133026](https://github.com/kubernetes/kubernetes/pull/133026), [@saschagrunert](https://github.com/saschagrunert)) [SIG API Machinery and Node] -- Removed general available feature-gate SizeMemoryBackedVolumes ([#133720](https://github.com/kubernetes/kubernetes/pull/133720), [@carlory](https://github.com/carlory)) [SIG Node, Storage and Testing] -- Removed the `ComponentSLIs` feature gate, which had been promoted to stable as part of the Kubernetes 1.32 release. ([#133742](https://github.com/kubernetes/kubernetes/pull/133742), [@carlory](https://github.com/carlory)) [SIG Architecture and Instrumentation] -- Removing Experimental prefix from the description of kubectl wait to emphasize that it is stable. ([#133731](https://github.com/kubernetes/kubernetes/pull/133731), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI] -- Removing the KUBECTL_OPENAPIV3_PATCH environment variable entirely, since aggregated discovery has been stable from 1.30. ([#134130](https://github.com/kubernetes/kubernetes/pull/134130), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI] -- Specifies the deprecated version of apiserver_storage_objects metric in metrics docs ([#134028](https://github.com/kubernetes/kubernetes/pull/134028), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Etcd and Instrumentation] -- Tests: switch to https://go.dev/doc/go1.25#container-aware-gomaxprocs from go.uber.org/automaxprocs ([#133492](https://github.com/kubernetes/kubernetes/pull/133492), [@BenTheElder](https://github.com/BenTheElder)) [SIG Testing] -- The `/statusz` page for `kube-proxy` now includes a list of exposed endpoints, making it easier to debug and introspect. ([#133190](https://github.com/kubernetes/kubernetes/pull/133190), [@aman4433](https://github.com/aman4433)) [SIG Network and Node] -- Types in k/k/pkg/scheduler/framework: - Handle, - Plugin, - PreEnqueuePlugin, QueueSortPlugin, EnqueueExtensions, PreFilterExtensions, PreFilterPlugin, FilterPlugin, PostFilterPlugin, PreScorePlugin, ScorePlugin, ReservePlugin, PreBindPlugin, PostBindPlugin, PermitPlugin, BindPlugin, - PodActivator, PodNominator, PluginsRunner, - LessFunc, ScoreExtensions, NodeToStatusReader, NodeScoreList, NodeScore, NodePluginScores, PluginScore, NominatingMode, NominatingInfo, WaitingPod, PreFilterResult, PostFilterResult, - Extender, - NodeInfoLister, StorageInfoLister, SharedLister, ResourceSliceLister, DeviceClassLister, ResourceClaimTracker, SharedDRAManager - - are moved to package k8s.io/kube-scheduler/framework . Users should update import paths. The interfaces don't change. - - Type Parallelizer in k/k/pkg/scheduler/framework/parallelism is split into interface Parallelizer (in k8s.io/kube-scheduler/framework) and struct Parallelizer (location unchanged in k/k). Plugin developers should update the import path to staging repo. ([#133172](https://github.com/kubernetes/kubernetes/pull/133172), [@ania-borowiec](https://github.com/ania-borowiec)) [SIG Node, Release, Scheduling, Storage and Testing] -- Updated CNI plugins to v1.8.0. ([#133837](https://github.com/kubernetes/kubernetes/pull/133837), [@saschagrunert](https://github.com/saschagrunert)) [SIG Cloud Provider, Node and Testing] -- Updated cri-tools to v1.34.0. ([#133636](https://github.com/kubernetes/kubernetes/pull/133636), [@saschagrunert](https://github.com/saschagrunert)) [SIG Cloud Provider] -- Updated etcd to v3.6.5. ([#134251](https://github.com/kubernetes/kubernetes/pull/134251), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] -- Upgrade CoreDNS to v1.12.3 ([#132288](https://github.com/kubernetes/kubernetes/pull/132288), [@thevilledev](https://github.com/thevilledev)) [SIG Cloud Provider and Cluster Lifecycle] -- `kubectl auth reconcile` now re-attempts reconciliation if it encounters a conflict error ([#133323](https://github.com/kubernetes/kubernetes/pull/133323), [@liggitt](https://github.com/liggitt)) [SIG Auth and CLI] -- `kubectl get` and `kubectl describe` human-readable output no longer includes counts for referenced tokens and secrets ([#117160](https://github.com/kubernetes/kubernetes/pull/117160), [@liggitt](https://github.com/liggitt)) [SIG CLI and Testing] - -## Dependencies - -### Added -- github.com/moby/sys/atomicwriter: [v0.1.0](https://github.com/moby/sys/tree/atomicwriter/v0.1.0) -- golang.org/x/tools/go/expect: v0.1.1-deprecated -- golang.org/x/tools/go/packages/packagestest: v0.1.1-deprecated - -### Changed -- cloud.google.com/go/compute/metadata: v0.6.0 → v0.7.0 -- github.com/aws/aws-sdk-go-v2/config: [v1.27.24 → v1.29.14](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.24...config/v1.29.14) -- github.com/aws/aws-sdk-go-v2/credentials: [v1.17.24 → v1.17.67](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.24...credentials/v1.17.67) -- github.com/aws/aws-sdk-go-v2/feature/ec2/imds: [v1.16.9 → v1.16.30](https://github.com/aws/aws-sdk-go-v2/compare/feature/ec2/imds/v1.16.9...feature/ec2/imds/v1.16.30) -- github.com/aws/aws-sdk-go-v2/internal/configsources: [v1.3.13 → v1.3.34](https://github.com/aws/aws-sdk-go-v2/compare/internal/configsources/v1.3.13...internal/configsources/v1.3.34) -- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2: [v2.6.13 → v2.6.34](https://github.com/aws/aws-sdk-go-v2/compare/internal/endpoints/v2/v2.6.13...internal/endpoints/v2/v2.6.34) -- github.com/aws/aws-sdk-go-v2/internal/ini: [v1.8.0 → v1.8.3](https://github.com/aws/aws-sdk-go-v2/compare/internal/ini/v1.8.0...internal/ini/v1.8.3) -- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding: [v1.11.3 → v1.12.3](https://github.com/aws/aws-sdk-go-v2/compare/service/internal/accept-encoding/v1.11.3...service/internal/accept-encoding/v1.12.3) -- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url: [v1.11.15 → v1.12.15](https://github.com/aws/aws-sdk-go-v2/compare/service/internal/presigned-url/v1.11.15...service/internal/presigned-url/v1.12.15) -- github.com/aws/aws-sdk-go-v2/service/sso: [v1.22.1 → v1.25.3](https://github.com/aws/aws-sdk-go-v2/compare/service/sso/v1.22.1...service/sso/v1.25.3) -- github.com/aws/aws-sdk-go-v2/service/ssooidc: [v1.26.2 → v1.30.1](https://github.com/aws/aws-sdk-go-v2/compare/service/ssooidc/v1.26.2...service/ssooidc/v1.30.1) -- github.com/aws/aws-sdk-go-v2/service/sts: [v1.30.1 → v1.33.19](https://github.com/aws/aws-sdk-go-v2/compare/service/sts/v1.30.1...service/sts/v1.33.19) -- github.com/aws/aws-sdk-go-v2: [v1.30.1 → v1.36.3](https://github.com/aws/aws-sdk-go-v2/compare/v1.30.1...v1.36.3) -- github.com/aws/smithy-go: [v1.20.3 → v1.22.3](https://github.com/aws/smithy-go/compare/v1.20.3...v1.22.3) -- github.com/containerd/containerd/api: [v1.8.0 → v1.9.0](https://github.com/containerd/containerd/compare/api/v1.8.0...api/v1.9.0) -- github.com/containerd/ttrpc: [v1.2.6 → v1.2.7](https://github.com/containerd/ttrpc/compare/v1.2.6...v1.2.7) -- github.com/containerd/typeurl/v2: [v2.2.2 → v2.2.3](https://github.com/containerd/typeurl/compare/v2.2.2...v2.2.3) -- github.com/coredns/corefile-migration: [v1.0.26 → v1.0.27](https://github.com/coredns/corefile-migration/compare/v1.0.26...v1.0.27) -- github.com/docker/docker: [v26.1.4+incompatible → v28.2.2+incompatible](https://github.com/docker/docker/compare/v26.1.4...v28.2.2) -- github.com/go-logr/logr: [v1.4.2 → v1.4.3](https://github.com/go-logr/logr/compare/v1.4.2...v1.4.3) -- github.com/google/cadvisor: [v0.52.1 → v0.53.0](https://github.com/google/cadvisor/compare/v0.52.1...v0.53.0) -- github.com/opencontainers/cgroups: [v0.0.1 → v0.0.3](https://github.com/opencontainers/cgroups/compare/v0.0.1...v0.0.3) -- github.com/opencontainers/runc: [v1.2.5 → v1.3.0](https://github.com/opencontainers/runc/compare/v1.2.5...v1.3.0) -- github.com/opencontainers/runtime-spec: [v1.2.0 → v1.2.1](https://github.com/opencontainers/runtime-spec/compare/v1.2.0...v1.2.1) -- github.com/prometheus/client_golang: [v1.22.0 → v1.23.2](https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.2) -- github.com/prometheus/client_model: [v0.6.1 → v0.6.2](https://github.com/prometheus/client_model/compare/v0.6.1...v0.6.2) -- github.com/prometheus/common: [v0.62.0 → v0.66.1](https://github.com/prometheus/common/compare/v0.62.0...v0.66.1) -- github.com/prometheus/procfs: [v0.15.1 → v0.16.1](https://github.com/prometheus/procfs/compare/v0.15.1...v0.16.1) -- github.com/spf13/cobra: [v1.9.1 → v1.10.0](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.0) -- github.com/spf13/pflag: [v1.0.6 → v1.0.9](https://github.com/spf13/pflag/compare/v1.0.6...v1.0.9) -- github.com/stretchr/testify: [v1.10.0 → v1.11.1](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) -- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp: v0.58.0 → v0.61.0 -- go.opentelemetry.io/otel/metric: v1.35.0 → v1.36.0 -- go.opentelemetry.io/otel/sdk/metric: v1.34.0 → v1.36.0 -- go.opentelemetry.io/otel/sdk: v1.34.0 → v1.36.0 -- go.opentelemetry.io/otel/trace: v1.35.0 → v1.36.0 -- go.opentelemetry.io/otel: v1.35.0 → v1.36.0 -- golang.org/x/crypto: v0.36.0 → v0.41.0 -- golang.org/x/mod: v0.21.0 → v0.27.0 -- golang.org/x/net: v0.38.0 → v0.43.0 -- golang.org/x/oauth2: v0.27.0 → v0.30.0 -- golang.org/x/sync: v0.12.0 → v0.16.0 -- golang.org/x/sys: v0.31.0 → v0.35.0 -- golang.org/x/telemetry: bda5523 → 1a19826 -- golang.org/x/term: v0.30.0 → v0.34.0 -- golang.org/x/text: v0.23.0 → v0.28.0 -- golang.org/x/tools: v0.26.0 → v0.36.0 -- google.golang.org/genproto/googleapis/rpc: a0af3ef → 200df99 -- google.golang.org/grpc: v1.72.1 → v1.72.2 -- google.golang.org/protobuf: v1.36.5 → v1.36.8 -- gopkg.in/evanphx/json-patch.v4: v4.12.0 → v4.13.0 -- k8s.io/gengo/v2: 85fd79d → ec3ebc5 -- k8s.io/kube-openapi: f3f2b99 → 589584f -- k8s.io/system-validators: v1.10.1 → v1.11.1 -- sigs.k8s.io/json: cfa47c3 → 2d32026 - -### Removed -- gopkg.in/yaml.v2: v2.4.0 \ No newline at end of file diff --git a/CHANGELOG/CHANGELOG-1.36.md b/CHANGELOG/CHANGELOG-1.36.md new file mode 100644 index 0000000000000..dd1dc99b77eef --- /dev/null +++ b/CHANGELOG/CHANGELOG-1.36.md @@ -0,0 +1,1868 @@ + + +- [v1.36.1](#v1361) + - [Downloads for v1.36.1](#downloads-for-v1361) + - [Source Code](#source-code) + - [Client Binaries](#client-binaries) + - [Server Binaries](#server-binaries) + - [Node Binaries](#node-binaries) + - [Container Images](#container-images) + - [Changelog since v1.36.0](#changelog-since-v1360) + - [Changes by Kind](#changes-by-kind) + - [Bug or Regression](#bug-or-regression) + - [Dependencies](#dependencies) + - [Added](#added) + - [Changed](#changed) + - [Removed](#removed) +- [v1.36.0](#v1360) + - [Downloads for v1.36.0](#downloads-for-v1360) + - [Source Code](#source-code-1) + - [Client Binaries](#client-binaries-1) + - [Server Binaries](#server-binaries-1) + - [Node Binaries](#node-binaries-1) + - [Container Images](#container-images-1) + - [Changelog since v1.35.0](#changelog-since-v1350) + - [Urgent Upgrade Notes](#urgent-upgrade-notes) + - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade) + - [Changes by Kind](#changes-by-kind-1) + - [Dependency](#dependency) + - [Deprecation](#deprecation) + - [API Change](#api-change) + - [Feature](#feature) + - [Documentation](#documentation) + - [Failing Test](#failing-test) + - [Bug or Regression](#bug-or-regression-1) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake) + - [Dependencies](#dependencies-1) + - [Added](#added-1) + - [Changed](#changed-1) + - [Removed](#removed-1) +- [v1.36.0-rc.1](#v1360-rc1) + - [Downloads for v1.36.0-rc.1](#downloads-for-v1360-rc1) + - [Source Code](#source-code-2) + - [Client Binaries](#client-binaries-2) + - [Server Binaries](#server-binaries-2) + - [Node Binaries](#node-binaries-2) + - [Container Images](#container-images-2) + - [Changelog since v1.36.0-rc.0](#changelog-since-v1360-rc0) + - [Dependencies](#dependencies-2) + - [Added](#added-2) + - [Changed](#changed-2) + - [Removed](#removed-2) +- [v1.36.0-rc.0](#v1360-rc0) + - [Downloads for v1.36.0-rc.0](#downloads-for-v1360-rc0) + - [Source Code](#source-code-3) + - [Client Binaries](#client-binaries-3) + - [Server Binaries](#server-binaries-3) + - [Node Binaries](#node-binaries-3) + - [Container Images](#container-images-3) + - [Changelog since v1.36.0-beta.0](#changelog-since-v1360-beta0) + - [Changes by Kind](#changes-by-kind-2) + - [API Change](#api-change-1) + - [Feature](#feature-1) + - [Bug or Regression](#bug-or-regression-2) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-1) + - [Dependencies](#dependencies-3) + - [Added](#added-3) + - [Changed](#changed-3) + - [Removed](#removed-3) +- [v1.36.0-beta.0](#v1360-beta0) + - [Downloads for v1.36.0-beta.0](#downloads-for-v1360-beta0) + - [Source Code](#source-code-4) + - [Client Binaries](#client-binaries-4) + - [Server Binaries](#server-binaries-4) + - [Node Binaries](#node-binaries-4) + - [Container Images](#container-images-4) + - [Changelog since v1.36.0-alpha.2](#changelog-since-v1360-alpha2) + - [Urgent Upgrade Notes](#urgent-upgrade-notes-1) + - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade-1) + - [Changes by Kind](#changes-by-kind-3) + - [Deprecation](#deprecation-1) + - [API Change](#api-change-2) + - [Feature](#feature-2) + - [Documentation](#documentation-1) + - [Failing Test](#failing-test-1) + - [Bug or Regression](#bug-or-regression-3) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-2) + - [Dependencies](#dependencies-4) + - [Added](#added-4) + - [Changed](#changed-4) + - [Removed](#removed-4) +- [v1.36.0-alpha.2](#v1360-alpha2) + - [Downloads for v1.36.0-alpha.2](#downloads-for-v1360-alpha2) + - [Source Code](#source-code-5) + - [Client Binaries](#client-binaries-5) + - [Server Binaries](#server-binaries-5) + - [Node Binaries](#node-binaries-5) + - [Container Images](#container-images-5) + - [Changelog since v1.36.0-alpha.1](#changelog-since-v1360-alpha1) + - [Urgent Upgrade Notes](#urgent-upgrade-notes-2) + - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade-2) + - [Changes by Kind](#changes-by-kind-4) + - [Dependency](#dependency-1) + - [Deprecation](#deprecation-2) + - [API Change](#api-change-3) + - [Feature](#feature-3) + - [Failing Test](#failing-test-2) + - [Bug or Regression](#bug-or-regression-4) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-3) + - [Dependencies](#dependencies-5) + - [Added](#added-5) + - [Changed](#changed-5) + - [Removed](#removed-5) +- [v1.36.0-alpha.1](#v1360-alpha1) + - [Downloads for v1.36.0-alpha.1](#downloads-for-v1360-alpha1) + - [Source Code](#source-code-6) + - [Client Binaries](#client-binaries-6) + - [Server Binaries](#server-binaries-6) + - [Node Binaries](#node-binaries-6) + - [Container Images](#container-images-6) + - [Changelog since v1.35.0](#changelog-since-v1350-1) + - [Urgent Upgrade Notes](#urgent-upgrade-notes-3) + - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade-3) + - [Changes by Kind](#changes-by-kind-5) + - [Dependency](#dependency-2) + - [API Change](#api-change-4) + - [Feature](#feature-4) + - [Failing Test](#failing-test-3) + - [Bug or Regression](#bug-or-regression-5) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-4) + - [Dependencies](#dependencies-6) + - [Added](#added-6) + - [Changed](#changed-6) + - [Removed](#removed-6) + + + +# v1.36.1 + + +## Downloads for v1.36.1 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes.tar.gz) | f48211b142241d7c8e7a44f1883bb5b233592178ddb92964a6453318b27d4405c6b3843f0b0ae7c64d7a7fb244ca01391f355c858ec1b04bb545157245fba0f2 +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-src.tar.gz) | 5f2c24638d56895bb3575aff0e25641fd9c25c192e27c6cd5255b888efc041221975cff9df99c1c842b876d3092cb9eaec0fd5b410bd0ec9ea74f3bc437dfaff + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-darwin-amd64.tar.gz) | 18daddc3d49ebb60d4c8b80748ef656dc5c19b3be8257a49c9b2ba1035710e38f96059ab140db833d41ebaf117ffee7b4c4a07447a2925cd199cb31d0eada21b +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-darwin-arm64.tar.gz) | 2338e4e7a36e6bdbfbb2956d5210cf1a79c0e688cb687c60ccd3796b187a438ace7948a23ae292e4b698c25fb6340ab9b2933288188ca1af379f879334c6c882 +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-linux-386.tar.gz) | e6a6b3d9640e0dca69dc745205225945e010cf8ae455eb0fa552433f3dd52ce89444fd9831fb63c8cf5b764ffb564a1c34363eca07c5a7676ee48e859e9517c5 +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-linux-amd64.tar.gz) | 0e3293367ed63e7f0792a32d3f8b96c586fc7920a6e078da99065dd425cf8123d62383680809157417fc183c566323a3e2d81098340d07d87ccd90750acc64c5 +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-linux-arm.tar.gz) | 10ee2bebeaf467ba5c28890468341655118422096238b1e41ffd9e850e11cc8d7fde8a3121ae0e7610fd213aab660f44d92a8041e4ba9ffd10cb5dd93593bb91 +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-linux-arm64.tar.gz) | b5391a5de550ad7ef0e53e0a955414a3bd6ad9c18a7063dcf36bdc80cae193119a2d0da1cca0d49dd83af271c71633d98e63894223fd691d70d239e01d85286f +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-linux-ppc64le.tar.gz) | b2deb8c1fb8c62b8177c5539c79f7799e1e538d48eae60384d58223e8ceef20ddc86dd298be82c65cb4b542fe6c9361d55a77a084ae58bbcd071fc0c7d6eeac2 +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-linux-s390x.tar.gz) | f1c6529829f83c187d740d5f22aaa7032558534ffff140458f96317697eb34c1206a08e049b55527f10e23ed0017a38ca3cf3833d3016b24953b56ba6f762729 +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-windows-386.tar.gz) | 34d420f96bc74eaa78420be178a08f4f5065c91270aa8f236353b4502a9ae8cad20974c6688b28b9f9324fcf06002d7310ed548e5cb9035f9801a0e132d8aa17 +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-windows-amd64.tar.gz) | 8c112272e4bc48e3f9048cb6e7f845c0bb200d12239d254f2f49fc97980093844d47ecf0f5e64f045b69ea2bec6b0c255af513376861d2e10241f8685c30520b +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-client-windows-arm64.tar.gz) | dbf04d5abdc2650b91b1908a5a61ddeeec341defc291736454b38d8bc8c3c6bf9d6eae7c49362a899b97f3f18f1a775653f6f1e602b19604bf3dd3a9237d8c5a + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-server-linux-amd64.tar.gz) | 1e88dcb75f4b6fde297a3305e62c3b17dd479baec86b0bd6f7cd3f3ba4421b3ecf7f6ff5885ab75d3d611b68bc5844ad8af6416375f1a800c5e8e73f35aff19c +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-server-linux-arm64.tar.gz) | d2eda4870d45b60408f7c1cad19a7772a85e8341b3cda9c132e4d9c44575541681834671a8cffc76c18ded98a14795088a93dc007de2efd77d9e0ca859ea5d19 +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-server-linux-ppc64le.tar.gz) | 901603e0861a5055779221ff1cdc583ef5c9c803373bc063701d31ae9537864ab270c1302d6a28a0b6f7225d0317ce906599933e2906fa025eac9b60b734cce6 +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-server-linux-s390x.tar.gz) | 623f4277300b0bf71384994e30ddaf575fe8302636be5e425dd848b20b65cc2f1dc52ad044414e830de9dfb06202be7658223b41157ac633af1372cb219bfc6c + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-node-linux-amd64.tar.gz) | 4241d7cf7340a11cf5379599fff922378b50f0fad186a4648c31baad7b82ef601e5ae4a8fbe6d0e7ca0b6fe5e6f890196d9481a8a3b1f94937708815a35f608d +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-node-linux-arm64.tar.gz) | 887da59dea3760074659419763ec362770d8ad77c3e52d4f3b212f337c191b1c90168bf194deaabc24016458829240be7f2078de6d9b4cb89bba68dab3754216 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-node-linux-ppc64le.tar.gz) | 69c9c3cbc502e7060fed0e5f72e6a1abf524f38511ec3b0fb7b11fa8ecb9b0bb49f962f728b50eaa4480f3b7f790bdd9f1a86359b7b0c18acf8316e53e9bb5ad +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-node-linux-s390x.tar.gz) | 48c122f4c13e6df35c2d21614c3d8c43115a023b521bb04830bb9c363649a71159d6debcbb521ca43c3c521e2a62f4149e0a4bd34d7760eb492ce5f15a337b96 +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.1/kubernetes-node-windows-amd64.tar.gz) | 977fdb163d37f63b90cc737f2bb4ab7e68dac06febd6b85670ce62a8db3f61094a618289b01e2651a87f1becb50efdf860cdfe0e620511d81f73164ce98af518 + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.36.0 + +## Changes by Kind + +### Bug or Regression + +- DRA metadata read helper (KEP-5304): on decode skip only if version is unknown, return error if object/file is malformed ([#138859](https://github.com/kubernetes/kubernetes/pull/138859), [@alaypatel07](https://github.com/alaypatel07)) [SIG Node] +- Fixed kubelet failure starting on ZFS due to missing cadvisor plugin. ([#138590](https://github.com/kubernetes/kubernetes/pull/138590), [@BenTheElder](https://github.com/BenTheElder)) [SIG Node] +- Fixed stale remote HNS endpoint cleanup on Windows when a pod IP is reused across nodes in L2Bridge networks, preventing DNS timeouts caused by traffic being routed to the wrong node. ([#138603](https://github.com/kubernetes/kubernetes/pull/138603), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] +- Kube-proxy does not perform full-sync operations when operation in large cluster mode (more than 1000 endpoints) ([#138635](https://github.com/kubernetes/kubernetes/pull/138635), [@aojea](https://github.com/aojea)) [SIG Network] +- Kubeadm: during 'kubeadm init', if the default 'admin.conf' and 'super-admin.conf' paths are used, load the files, but construct in memory kubeconfigs that point to the InitConfiguration.localAPIEndpoint instead of the ClusterConfiguration.controlPlaneEndpoint. This would resolve issues with delayed load balancers which are provisioned only after the first kube-apiserver instance starts. ([#138682](https://github.com/kubernetes/kubernetes/pull/138682), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: skip LocalAPIEndpoint defaulting on 'kubeadm join' for worker nodes. ([#138802](https://github.com/kubernetes/kubernetes/pull/138802), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: use a dedicated ClusterRole 'system:kubelet-api-admin' for the kube-apiserver kubelet client. ([#138958](https://github.com/kubernetes/kubernetes/pull/138958), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: when checking the etcd cluster status use a quorum approach, instead of considering the health of all members. This would allow the check to not fail if there are sufficient healthy voting members. ([#138538](https://github.com/kubernetes/kubernetes/pull/138538), [@ahrtr](https://github.com/ahrtr)) [SIG Cluster Lifecycle] + +## Dependencies + +### Added +_Nothing has changed._ + +### Changed +_Nothing has changed._ + +### Removed +_Nothing has changed._ + + + +# v1.36.0 + +[Documentation](https://docs.k8s.io) + +## Downloads for v1.36.0 + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes.tar.gz) | `3c9b9225c75080950fdb53fdeb326606133eeb5efbc8ecdd7514c290f1aaf8fa247a6f6f5b34beb87658a0ba5533c1f3cc7a8c680fc30785775ad73702374834` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-src.tar.gz) | `0b2c28c5b9f58c3ac6286e892f0bfeb1dbf8bcb9a76dc6128c080ab4f39d861af9b0ae50bf4a819d6ec376b4e8d2a55122d51d5fb26a69dc8af29bcd2406fb48` + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-darwin-amd64.tar.gz) | `4be41ab0e38d809fad076b2c811124d93a6b48d696843f5bd85e5c77e27a112ee526f95de82c57d9f59060b27a7d2e036d6989246510f9772862b9f2ca87c5ed` +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-darwin-arm64.tar.gz) | `57b90606b066b6073362ee06ff8b00198abe2590ae89216f001a7d3bcd339dc8ede6616ad498eeaa65328a39834bc71e8c18944df0e90fc89139ac8d7290cfe9` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-linux-386.tar.gz) | `d1964452b0276b83933c1642743bb0058212bb88b41a5601e446ea49bb06fa1bb682d2cba4afd72896faf860a61e68494cb159db51c4c2ec5928de533f6cc9b9` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-linux-amd64.tar.gz) | `cac4ee270f7a5ca8e96f2b86f1b822bdc66168253b253f4838caf5bd16b8e314ae307c7ba718f32e9543d502f5d0c703bd3358449718c6956436969e125011cf` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-linux-arm.tar.gz) | `76117398c77401cb62303f765c6e42f93bff42d3f04d6501b282e14013fdce1ed57c743ed049995575c1326817c57db57523fdcca3ea3a7b7d58fe586d491bf1` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-linux-arm64.tar.gz) | `d669cc342059d88cf93db37d2bd41b444e352a9af64cc14767d77321e8e9bcdbdce886d605bf09b5dad09500a9d7b10023e3f07539915df4f175b56e0de8f5bb` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-linux-ppc64le.tar.gz) | `4245f7ec5bbc53b4b375c855110295d3e9640833e916ce83cdc9f610047442a705c17a641d8590c250d3c511771f478beab0f19d0258211d9fd1f97f7f00fcf3` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-linux-s390x.tar.gz) | `538b0b193767272ada79b832ec994d2172a7b88933c62711a117f6b0476902d7f4c771400e7468020dca8cb968d5ee220627a5249db2f16efb27bfa29e0570fc` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-windows-386.tar.gz) | `b97a5b7bcc96b42648fe9ea639742d05dc699a6394d3da246bfc72c810b650cb440f96319f0064e5479cd885aced8310640d5c8fcf6256fe5e88c0de93d27e9d` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-windows-amd64.tar.gz) | `d1d3ca9de4c5917538b0865aa28a1fa9b2c7cad46921ab85f661025f5ddc277755cda46d441359d2d5717b908d9bc3fd7fe2aae22d95deeb172b1fdd49b0c9b3` +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-client-windows-arm64.tar.gz) | `4cbe90820c58892bf4327634dcfdca64c7db36cf09e2beae2f417248abe05fa01f8ae7a2edec08fa2ea28a368bc6e3bba7431be30a138bfaf78ba4e433bfa463` + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-server-linux-amd64.tar.gz) | `1c64da92575451c2c7ff97c79b772e603995f8e76da1371a6b0746aaa27b65dce81c4d734cbb50f40e71486a8e08df36f14e12974f97d4d29e41f23a172a6a25` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-server-linux-arm64.tar.gz) | `7f95e451baedd9368a2fa637afa84c9ed1b958736540ca27a379e5e292e1a10c5d9a29539f833047574bf5d3b6f907f32dee5735acecc11aa4686f078108dcf8` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-server-linux-ppc64le.tar.gz) | `4d3f6b70ccb785d3264acf6b3cf0565560bb3c2ba9db85a06fa10c020ee248318ee009f9cc5b6d7171bda7ec2f96cf0f9a1a57ba857a4fa5331022c9122692d9` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-server-linux-s390x.tar.gz) | `7c12c4c89522c449fcd96837c27659edef53245b7a39e4802a7c00c6497624eff15b2d53ec7a70328176681967fa7e22bbf5c4b0f3fc32996daaf696ca54bc25` + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-node-linux-amd64.tar.gz) | `8bba5bd0cb77997ad965739caff3c8839fd16f284cf28a0e46c44538ce0b6fd83c5f3c608dd86c4831c3de9306bbfd02ce26bbe9a426754b6253efa1d5cc030f` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-node-linux-arm64.tar.gz) | `10a4c3660e6e19fae998b04345dbe42711e61745bf374405c33e8604335b12ff55ce7b68f8a0d67d1f7ce4519f706fbd8d80781b7128369fe047c38915e8d189` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-node-linux-ppc64le.tar.gz) | `9eb7a4df4b518df4b846bd12ec25b50908b610b823f4d781094e0667e47d607209acbace2747955a373366a68b77f32170e2de71f0b2ee8a87723bac30c41d95` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-node-linux-s390x.tar.gz) | `492a71b292953ee5ccb603bf132a99fdbf6ae6fa8ae6c02139887b77ae6e1d7d77e65429bf37bb46eee33bf32cee3fe8ead03f74cd7b0f1f5a7c7126ba839800` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0/kubernetes-node-windows-amd64.tar.gz) | `a4f2bdb613da646877aacae6a2c39cbb18d74f164973fa033b5f042f1ed8a4de0285e0d99f303d8fdd89cecaa341c6e29361d7677a6bde5bbfc956dd156ac55c` + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.35.0 + +## Urgent Upgrade Notes + +### (No, really, you MUST read this before you upgrade) + +- ACTION REQUIRED: + kube-controller-manager: Renamed metric `volume_operation_total_errors` to `volume_operation_errors_total`. If you are using custom monitoring dashboards or alerting rules based on the `volume_operation_total_errors` metric, update them to use the new `volume_operation_errors_total` metric. ([#136399](https://github.com/kubernetes/kubernetes/pull/136399), [@tico88612](https://github.com/tico88612)) [SIG Apps, Instrumentation, Storage and Testing] +- Added support for running PreBind plugins in parallel in the scheduler framework to improve binding latency. + ACTION REQUIRED: Plugins can opt-in to parallel execution by returning `AllowParallel: true` from the `PreBindPreFlight` method. PreBind plugin implementations need to be updated to return `PreBindPreFlightResult` from the `PreBindPreFlight` method; returning nil retains the existing sequential behavior. ([#135393](https://github.com/kubernetes/kubernetes/pull/135393), [@tosi3k](https://github.com/tosi3k)) [SIG Node, Scheduling, Storage and Testing] + +## Changes by Kind + +### Dependency + +- Fixed a bug where pod lifecycle hooks could run for their full duration when pods are terminated. ([#136598](https://github.com/kubernetes/kubernetes/pull/136598), [@dgrisonnet](https://github.com/dgrisonnet)) [SIG API Machinery, Auth, Cloud Provider, Node and Scheduling] +- Updated etcd client library to `v3.6.8`. ([#137225](https://github.com/kubernetes/kubernetes/pull/137225), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Auth, Cloud Provider, Cluster Lifecycle, Etcd, Node, Scheduling and Testing] + +### Deprecation + +- Added warnings and deprecation for Service `.spec.externalIPs`. ([#137293](https://github.com/kubernetes/kubernetes/pull/137293), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Apps, Network and Windows] +- Direct access to the `Raw` field of `metav1.FieldsV1` is deprecated. Code that constructs or reads `FieldsV1` should migrate to the new `NewFieldsV1(string)`, `GetRawBytes()`, `GetRawString()`, and `SetRawBytes()` accessor methods. ([#137304](https://github.com/kubernetes/kubernetes/pull/137304), [@aaron-prindle](https://github.com/aaron-prindle)) [SIG API Machinery, Apps and Testing] +- Disabled `git-repo` volume plugin by default, with no option to turn it back on. ([#136400](https://github.com/kubernetes/kubernetes/pull/136400), [@vinayakankugoyal](https://github.com/vinayakankugoyal)) [SIG Storage] +- Renamed `AllowlistEntry.Name` to `AllowlistEntry.Command` in the credential plugin allowlist. ([#137272](https://github.com/kubernetes/kubernetes/pull/137272), [@pmengelbert](https://github.com/pmengelbert)) [SIG API Machinery, Auth, CLI and Testing] + +### API Change + +- ACTION REQUIRED: DRA (Dynamic Resource Allocation) drivers and controllers now require granular RBAC permissions to update ResourceClaim statuses when the `DRAResourceClaimGranularStatusAuthorization` feature gate is enabled (beta in `v1.36`). Schedulers and controllers must be granted `update`/`patch` on `resourceclaims/binding`. DRA drivers must be granted `associated-node:update` or `arbitrary-node:update` (or patch equivalents) on `resourceclaims/driver`, restricted by their specific `resourceNames`. ([#134947](https://github.com/kubernetes/kubernetes/pull/134947), [@aojea](https://github.com/aojea)) [SIG API Machinery, Apps, Auth, Instrumentation, Node, Scheduling and Testing] +- ACTION REQUIRED: Removed the integrated support for flex-volumes in kubeadm. Users were advised to migrate away from flex-volumes as recommended by SIG Storage since `v1.22`. If `kubeadm` users wish to continue using the feature, they need a custom image for the KCM that is not based on distroless, pass the KCM flag `--flex-volume-plugin-dir`, and mount the directory `/usr/libexec/kubernetes/kubelet-plugins/volume/exec` in the KCM static pod using `kubeadm`'s `extraVolumes` mechanism before upgrading to `v1.36`. Previously, `kubeadm` automatically did the mounting if the user passed the flag. ([#136423](https://github.com/kubernetes/kubernetes/pull/136423), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- ACTION REQUIRED: Renamed metric `etcd_bookmark_counts` to `etcd_bookmark_total`. If you are using custom monitoring dashboards or alerting rules based on the `etcd_bookmark_counts` metric, update them to use the new `etcd_bookmark_total` metric. ([#136483](https://github.com/kubernetes/kubernetes/pull/136483), [@petern48](https://github.com/petern48)) [SIG API Machinery, Etcd, Instrumentation and Testing] +- Added SchedulingConstraints to express topology-aware scheduling (TAS) constraints for PodGroup scheduling behind the `TopologyAwareWorkloadScheduling` feature gate. Added the TopologyPlacement plugin implementing the PlacementGenerate extension point to take constraints into consideration during PodGroup scheduling. ([#137271](https://github.com/kubernetes/kubernetes/pull/137271), [@brejman](https://github.com/brejman)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Added `DisruptionMode`, `PriorityClassName`, and `Priority` fields to the Workload and PodGroup APIs to support workload-aware preemption when the `WorkloadAwarePreemption` feature gate is enabled. ([#136589](https://github.com/kubernetes/kubernetes/pull/136589), [@tosi3k](https://github.com/tosi3k)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Added `ImageVolumeWithDigest` which includes the digest of image volumes in the container status. ([#132807](https://github.com/kubernetes/kubernetes/pull/132807), [@iholder101](https://github.com/iholder101)) [SIG API Machinery, Apps, Node and Testing] +- Added `MemoryReservationPolicy` cgroup v2 MemoryQoS support to KubeletConfiguration for `memory.min` protection. ([#137584](https://github.com/kubernetes/kubernetes/pull/137584), [@QiWang19](https://github.com/QiWang19)) [SIG Node and Storage] +- Added `spec.stubPKCS10Request` to the Pod Certificates beta API to improve compatibility with existing certificate authority implementations that expect a PKCS#10 certificate signing request. `spec.pkixPublicKey` and `spec.proofOfPossession` were deprecated in favor of this field. ([#136729](https://github.com/kubernetes/kubernetes/pull/136729), [@ahmedtd](https://github.com/ahmedtd)) [SIG API Machinery, Auth, Node and Testing] +- Added a deletion protection mechanism for PodGroup objects. ([#137641](https://github.com/kubernetes/kubernetes/pull/137641), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Auth, Scheduling and Storage] +- Added alpha support (behind the `PersistentVolumeClaimUnusedSinceTime` feature gate) for tracking PersistentVolumeClaim unused status via a new `Unused` condition on PersistentVolumeClaimStatus. When enabled, the PVC protection controller sets `Unused=True` with a `lastTransitionTime` when no non-terminal Pods reference the PersistentVolumeClaim. ([#137862](https://github.com/kubernetes/kubernetes/pull/137862), [@gnufied](https://github.com/gnufied)) [SIG Apps, Auth, Storage and Testing] +- Added alpha support for manifest-based admission control configuration (KEP-5793). When the `ManifestBasedAdmissionControlConfig` feature gate is enabled, admission webhooks and CEL-based policies can be loaded from static manifest files on disk via the `staticManifestsDir` field in `AdmissionConfiguration`. These policies are active from API server startup, survive `etcd` unavailability, and can protect API-based admission resources from modification. ([#137346](https://github.com/kubernetes/kubernetes/pull/137346), [@aramase](https://github.com/aramase)) [SIG API Machinery, Apps, Architecture, Auth, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Release, Scheduling, Storage, Testing and Windows] +- Added an admission plugin that validates PodGroup resources reference an existing Workload and match the declared PodGroupTemplate spec. ([#137464](https://github.com/kubernetes/kubernetes/pull/137464), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Added list-type support for attributes in DRA (KEP-5491). The `DRAListTypeAttributes` feature gate (disabled by default) activates the following enhancements: + - DRA drivers can use list-type fields (`bools`/`ints`/`strings`/`versions`) for device attributes in ResourceSlice. The number of attribute values, including scalars and lists, per single device is limited to 48. + - The `matchAttribute`/`distinctAttribute` constraints in ResourceClaim now work on both scalar and list attributes. The `matchAttribute` constraint matches when the intersection of all list values among candidate devices is non-empty. The `distinctAttribute` constraint (behind the `ConsumableCapacity` feature gate) matches when all list values among candidate devices are pairwise disjoint. Scalar values are implicitly treated as a singleton set. + - Added a new CEL function `.includes` that works on both scalar and list attributes to test inclusion (e.g., `device.attributes["dra.example.com"].model.includes("model-a")`), supporting migration when a DRA driver changes an attribute value type from scalar to list or vice versa. ([#137190](https://github.com/kubernetes/kubernetes/pull/137190), [@everpeace](https://github.com/everpeace)) [SIG API Machinery, Node, Scheduling and Testing] +- Added new `concurrent-node-status-updates` flag that is split from the `concurrent-node-syncs` flag. ([#136716](https://github.com/kubernetes/kubernetes/pull/136716), [@yonizxz](https://github.com/yonizxz)) [SIG Cloud Provider] +- Added opt-in alpha support in the kubeletplugin framework for DRA drivers to publish DRA Device metadata in Pod CDI mounts. ([#137086](https://github.com/kubernetes/kubernetes/pull/137086), [@alaypatel07](https://github.com/alaypatel07)) [SIG Apps, Network, Node and Testing] +- Added opt-in scheduling behavior for CSI volumes. ([#137343](https://github.com/kubernetes/kubernetes/pull/137343), [@gnufied](https://github.com/gnufied)) [SIG API Machinery, Scheduling and Storage] +- Added placement-based PodGroup scheduling algorithm to the scheduler. Its use is guarded by the `TopologyAwareWorkloadScheduling` feature gate. ([#136944](https://github.com/kubernetes/kubernetes/pull/136944), [@brejman](https://github.com/brejman)) [SIG Scheduling and Testing] +- Added stability-based lifecycle for declarative validation (Alpha/Beta/Stable). Scheduling Workload `v1alpha1` now uses explicit declarative enforcement. ([#136793](https://github.com/kubernetes/kubernetes/pull/136793), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery and Scheduling] +- Added the PlacementGenerate extension point to the scheduler. It is used to generate placements for placement-based PodGroup scheduling. Its use is guarded by the `TopologyAwareWorkloadScheduling` feature gate. ([#137083](https://github.com/kubernetes/kubernetes/pull/137083), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Added the PlacementScore extension point to the scheduler for scoring placements in placement-based PodGroup scheduling, guarded by the `TopologyAwareWorkloadScheduling` feature gate. Deprecated `MinNodeScore` and `MaxNodeScore` in favor of `MinScore` and `MaxScore`. ([#137201](https://github.com/kubernetes/kubernetes/pull/137201), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Added the ResourcePoolStatusRequest API (`v1alpha1`) for querying DRA resource pool availability. External schedulers can discover available devices across pools before submitting workloads. Requires the `DRAResourcePoolStatus` feature gate (alpha). ([#137028](https://github.com/kubernetes/kubernetes/pull/137028), [@nmn3m](https://github.com/nmn3m)) [SIG API Machinery, Apps, Auth, Etcd, Instrumentation, Node, Scheduling, Storage and Testing] +- Added the `--concurrent-resourceclaim-syncs` flag to `kube-controller-manager` to configure `ResourceClaim` reconcile concurrency. ([#134701](https://github.com/kubernetes/kubernetes/pull/134701), [@anson627](https://github.com/anson627)) [SIG API Machinery, Apps, Node and Testing] +- Added the `--tls-curve-preferences` flag for configuring TLS key exchange mechanism. ([#137115](https://github.com/kubernetes/kubernetes/pull/137115), [@damdo](https://github.com/damdo)) [SIG API Machinery, Architecture, CLI, Cloud Provider, Node and Testing] +- Added the `PodGroupPodsCount` scheduler plugin to support workload-aware scheduling by prioritizing placements with higher Pod counts within a group. ([#137488](https://github.com/kubernetes/kubernetes/pull/137488), [@vshkrabkov](https://github.com/vshkrabkov)) [SIG Scheduling and Testing] +- Added the `tlsServerName` field to `EgressSelectorConfiguration` `TLSConfig` to allow overriding the server name used for TLS certificate verification. ([#136640](https://github.com/kubernetes/kubernetes/pull/136640), [@kennangaibel](https://github.com/kennangaibel)) [SIG API Machinery, Apps, Auth, Storage and Testing] +- Added the alpha `DRANodeAllocatableResources` feature, which introduces a new `ResourceSlice.Spec.Devices[*].NodeAllocatableResourceMappings` field for DRA drivers to declare how device resources map to node allocatable Kubernetes resources (e.g., cpu, memory).([#136725](https://github.com/kubernetes/kubernetes/pull/136725), [@pravk03](https://github.com/pravk03)) [SIG API Machinery, Apps, Node, Scheduling and Testing] +- Added topology-aware scheduling (TAS) logic to the PodGroup scheduling cycle behind the `TopologyAwareWorkloadScheduling` feature gate, supporting scheduling of PodGroups on nodes with matching topology domains. ([#137489](https://github.com/kubernetes/kubernetes/pull/137489), [@brejman](https://github.com/brejman)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Added validation to prevent negative duration values for `imageMinimumGCAge`. ([#135997](https://github.com/kubernetes/kubernetes/pull/135997), [@ngopalak-redhat](https://github.com/ngopalak-redhat)) [SIG API Machinery and Node] +- Changed deprecated `sets.String` with `sets.Set[string]` in apiserver admission subsystem. This is a **breaking change** for consumers of the `NewLifecycle` function. ([#134044](https://github.com/kubernetes/kubernetes/pull/134044), [@mcallzbl](https://github.com/mcallzbl)) [SIG API Machinery and Auth] +- Clarified documentation and comments to indicate that the `cpuCFSQuotaPeriod` kubelet config field requires the `CustomCPUCFSQuotaPeriod` feature gate when using non-default values. No functional changes introduced. ([#133845](https://github.com/kubernetes/kubernetes/pull/133845), [@rbiamru](https://github.com/rbiamru)) [SIG Node and Release] +- Corrected OpenAPI schema union validation for the `PodGroupPolicy` struct in `scheduling.k8s.io/v1alpha1`. ([#136424](https://github.com/kubernetes/kubernetes/pull/136424), [@JoelSpeed](https://github.com/JoelSpeed)) [SIG API Machinery and Scheduling] +- DRA `DeviceTaintRules`: the `TimeAdded` field of the taint is now automatically updated when changing the effect. ([#137167](https://github.com/kubernetes/kubernetes/pull/137167), [@pohly](https://github.com/pohly)) [SIG API Machinery, Node and Testing] +- DRA: Added a `spec.resourceClaims` field to PodGroup resources for referencing ResourceClaims and ResourceClaimTemplates. Claims made by a PodGroup are reserved for the entire PodGroup instead of individual Pods, supporting more than 256 Pods sharing a single ResourceClaim. ResourceClaimTemplates referenced by a PodGroup's claim replicate into a ResourceClaim specific to that PodGroup, shared by all of the group's Pods. ([#136989](https://github.com/kubernetes/kubernetes/pull/136989), [@nojnhuh](https://github.com/nojnhuh)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- DRA: Graduated Device Binding Conditions (KEP #5007) to beta, enabled by default in `v1.36`. ([#137795](https://github.com/kubernetes/kubernetes/pull/137795), [@ttsuuubasa](https://github.com/ttsuuubasa)) [SIG API Machinery, Node, Scheduling and Testing] +- DRA: Graduated device taints and tolerations (KEP #5055) to beta. Support for DeviceTaints in ResourceSlices is on by default. Support for `DeviceTaintRules` depends on enabling `resource.k8s.io/v1beta2` and the `DeviceTaintRules` feature gate. ([#137170](https://github.com/kubernetes/kubernetes/pull/137170), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Cluster Lifecycle, Etcd, Node, Scheduling and Testing] +- Extended `NodeResourcesFit` to implement the PlacementScore extension point. The usage of the PlacementScore extension point is guarded by the `TopologyAwareWorkloadScheduling` feature gate. ([#136652](https://github.com/kubernetes/kubernetes/pull/136652), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Fixed `fake.NewClientset()` to work properly with correct schema. ([#131068](https://github.com/kubernetes/kubernetes/pull/131068), [@soltysh](https://github.com/soltysh)) [SIG API Machinery] +- Fixed a few log calls that did not properly format their parameters. ([#137108](https://github.com/kubernetes/kubernetes/pull/137108), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Cluster Lifecycle, Network, Node, Scheduling and Testing] +- Fixed a potential nil pointer dereference in the scheduler's `NodeResourcesFitArgs` validation when using `RequestedToCapacityRatio` scoring strategy. ([#132120](https://github.com/kubernetes/kubernetes/pull/132120), [@flpanbin](https://github.com/flpanbin)) [SIG Scheduling] +- Fixed an issue in `kube-apiserver`, allowing it to recover from an established connection to an incorrect server that never returns the expected response during APIService availability checks. ([#137157](https://github.com/kubernetes/kubernetes/pull/137157), [@bsalamat](https://github.com/bsalamat)) [SIG API Machinery] +- For Pod resizes requested on nodes where the resize request exceeds the node's allocatable capacity or the node is running an OS that does not support resize, the request fails in admission rather than being marked as Infeasible in the Pod status later. ([#136043](https://github.com/kubernetes/kubernetes/pull/136043), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Node, Release, Scheduling, Storage and Testing] +- Generated `fake.NewClientset` which replaces the deprecated `NewSimpleClientset` for `kube-aggregator` and `sample-apiserver`. ([#136537](https://github.com/kubernetes/kubernetes/pull/136537), [@soltysh](https://github.com/soltysh)) [SIG API Machinery] +- Graduated metric `apiserver_storage_events_received_total` to beta. ([#136314](https://github.com/kubernetes/kubernetes/pull/136314), [@petern48](https://github.com/petern48)) [SIG API Machinery, Etcd, Instrumentation and Testing] +- Graduated the `ImageVolume` feature to stable. ([#136711](https://github.com/kubernetes/kubernetes/pull/136711), [@saschagrunert](https://github.com/saschagrunert)) [SIG Apps, Architecture, Node and Testing] +- Graduated the `InPlacePodLevelResourcesVerticalScaling` feature gate to beta, enabled by default. Pod-level CPU and memory resources can be resized in place for Pods with pod-level resources configured. ([#137684](https://github.com/kubernetes/kubernetes/pull/137684), [@ndixita](https://github.com/ndixita)) [SIG API Machinery, Apps, Autoscaling, Node, Release, Scheduling and Testing] +- Graduated the `UserNamespacesSupport` feature gate to GA. ([#136792](https://github.com/kubernetes/kubernetes/pull/136792), [@rata](https://github.com/rata)) [SIG API Machinery, Apps, CLI, Node, Storage and Testing] +- Graduated the `config.k8s.io/flagz` API to `v1beta1`. ([#137174](https://github.com/kubernetes/kubernetes/pull/137174), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation, Node, Scheduling and Testing] +- Graduated the `config.k8s.io/statusz` API to `v1beta1`. ([#137173](https://github.com/kubernetes/kubernetes/pull/137173), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation, Scheduling and Testing] +- HPA: Improved scaling to and from zero when the `HPAScaleToZero` feature gate is enabled. ([#135118](https://github.com/kubernetes/kubernetes/pull/135118), [@johanneswuerbach](https://github.com/johanneswuerbach)) [SIG Apps, Autoscaling and Testing] +- Integrated Workload and PodGroup APIs with the Job controllers to support gang-scheduling. ([#137032](https://github.com/kubernetes/kubernetes/pull/137032), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Instrumentation, Node, Scheduling and Testing] +- Introduced `scheduling.k8s.io/v1alpha2` Workload and PodGroup API to express workload-level scheduling requirements and let `kube-scheduler` act on those. Removed `scheduling.k8s.io/v1alpha1` Workload API. ([#136976](https://github.com/kubernetes/kubernetes/pull/136976), [@tosi3k](https://github.com/tosi3k)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling, Storage and Testing] +- Kube-apiserver: The `--audit-policy-file` config file now supports specifying `group: "*"` in resource rules to match all API groups. ([#135262](https://github.com/kubernetes/kubernetes/pull/135262), [@cmuuss](https://github.com/cmuuss)) [SIG API Machinery, Auth and Testing] +- Kube-controller-manager: Added ALPHA gauge metric `informer_queued_items` for informer queue length, published as `informer_queued_items{name=kube-controller-manager,group=,resource=,version=} `. ([#135782](https://github.com/kubernetes/kubernetes/pull/135782), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation and Testing] +- Kubelet: Added tiered cgroup v2 memory protection for MemoryQoS: `memory.min` for Guaranteed pods and `memory.low` for Burstable pods, with node-level metrics and rollback reconciliation (KEP-2570). ([#137719](https://github.com/kubernetes/kubernetes/pull/137719), [@sohankunkerkar](https://github.com/sohankunkerkar)) [SIG Node, Storage and Testing] +- Locked the `VolumeAttributesClass` feature gate to `true` and updated the preferred storage version to `storage.k8s.io/v1`. ([#134556](https://github.com/kubernetes/kubernetes/pull/134556), [@carlory](https://github.com/carlory)) [SIG API Machinery, Apps, Etcd, Network, Node, Scheduling, Storage and Testing] +- Marked the `endpoints` field as optional in the OpenAPI spec for `discovery.k8s.io/v1` EndpointSlice. This matches server behavior and resolves validation issues. ([#136111](https://github.com/kubernetes/kubernetes/pull/136111), [@aojea](https://github.com/aojea)) [SIG Network] +- Promoted `DRAPrioritizedList` to GA. ([#136924](https://github.com/kubernetes/kubernetes/pull/136924), [@troychiu](https://github.com/troychiu)) [SIG Apps, Architecture, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Network, Node, Release, Scheduling, Storage and Testing] +- Promoted `NodeDeclaredFeatures` to beta. ([#136042](https://github.com/kubernetes/kubernetes/pull/136042), [@pravk03](https://github.com/pravk03)) [SIG API Machinery, Apps, Cluster Lifecycle, Instrumentation, Node, Scheduling, Storage and Testing] +- Promoted `SnapshotMetadataService` to `v1beta1`. Removed support for the `v1alpha1` version. ([#137564](https://github.com/kubernetes/kubernetes/pull/137564), [@iPraveenParihar](https://github.com/iPraveenParihar)) [SIG Storage and Testing] +- Promoted mutable CSI node allocatable count to GA. The `MutableCSINodeAllocatableCount` feature gate is locked to enabled. ([#136230](https://github.com/kubernetes/kubernetes/pull/136230), [@torredil](https://github.com/torredil)) [SIG API Machinery and Storage] +- Promoted several EndpointSlice metrics from alpha to beta stability. ([#136368](https://github.com/kubernetes/kubernetes/pull/136368), [@bhope](https://github.com/bhope)) [SIG Instrumentation and Network] +- Promoted several component-base metrics (`kubernetes_build_info`, `rest_client_requests_total`, `rest_client_request_duration_seconds`, `running_managed_controllers`) from Alpha to Beta stability, providing stronger API and label stability guarantees for consumers. ([#136154](https://github.com/kubernetes/kubernetes/pull/136154), [@bhope](https://github.com/bhope)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Etcd, Instrumentation, Network, Node, Scalability, Scheduling, Storage and Testing] +- Promoted several scheduler metrics (`scheduler_goroutines`, `scheduler_permit_wait_duration_seconds`, `scheduler_plugin_evaluation_total`, `scheduler_plugin_execution_duration_seconds`, `scheduler_scheduling_algorithm_duration_seconds`, `scheduler_unschedulable_pods`) from alpha to beta stability, providing stronger API and label stability guarantees for metric consumers. ([#136155](https://github.com/kubernetes/kubernetes/pull/136155), [@bhope](https://github.com/bhope)) [SIG Instrumentation and Scheduling] +- Promoted the DRA extended resource feature to beta in `v1.36`. ([#135048](https://github.com/kubernetes/kubernetes/pull/135048), [@yliaog](https://github.com/yliaog)) [SIG API Machinery, Architecture, Auth, Network, Node, Scheduling and Testing] +- Promoted the `ConstrainedImpersonation` feature to beta, enabled by default. ([#137609](https://github.com/kubernetes/kubernetes/pull/137609), [@enj](https://github.com/enj)) [SIG API Machinery and Testing] +- Promoted the `DRAAdminAccess` feature gate to GA. ([#137373](https://github.com/kubernetes/kubernetes/pull/137373), [@ritazh](https://github.com/ritazh)) [SIG API Machinery, Auth, Node, Scheduling and Testing] +- Promoted the `MutatingAdmissionPolicy` to GA (v1) in Kubernetes `v1.36`. The feature is now enabled by default. ([#136039](https://github.com/kubernetes/kubernetes/pull/136039), [@lalitc375](https://github.com/lalitc375)) [SIG API Machinery, Architecture, Etcd and Testing] +- Promoted the `NodeLogQuery` feature gate to GA. ([#137544](https://github.com/kubernetes/kubernetes/pull/137544), [@jrvaldes](https://github.com/jrvaldes)) [SIG Node and Windows] +- Promoted the `ProcMountType` feature to GA. ([#137454](https://github.com/kubernetes/kubernetes/pull/137454), [@haircommander](https://github.com/haircommander)) [SIG API Machinery, Apps, Auth, CLI, Node, Storage and Testing] +- Promoted the `watch_list_duration_seconds` metric from ALPHA to BETA. ([#136086](https://github.com/kubernetes/kubernetes/pull/136086), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation, Node and Testing] +- Promoted two Job controller metrics from alpha to beta stability, providing stronger API and label stability guarantees for metric consumers. ([#136367](https://github.com/kubernetes/kubernetes/pull/136367), [@bhope](https://github.com/bhope)) [SIG Apps and Instrumentation] +- Promoted workqueue metrics from ALPHA to BETA. ([#135522](https://github.com/kubernetes/kubernetes/pull/135522), [@petern48](https://github.com/petern48)) [SIG Architecture, Instrumentation and Testing] +- Removed CustomResourceDefinition stored versions from status upon StorageVersionMigrator migration. ([#135297](https://github.com/kubernetes/kubernetes/pull/135297), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Auth and Testing] +- Removed the in-tree Portworx volume plugin, completing the migration to CSI. + Removed the GA `CSIMigrationPortworx` feature gate (locked since `v1.33`) and alpha `InTreePluginPortworxUnregister` feature gate, with all operations now redirected to CSI. ([#135322](https://github.com/kubernetes/kubernetes/pull/135322), [@carlory](https://github.com/carlory)) [SIG API Machinery, Apps, Auth, Node, Scalability, Scheduling, Storage and Testing] +- Removed the temporary build-tagged `ProtoMessage()` marker method implementations from Kubernetes REST API types in `k8s.io/api`, which had incorrectly identified them as standard `v1` proto messages. Protobuf serialization of Kubernetes API types should use [k8s.io/apimachinery/pkg/runtime/serializer/protobuf](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime/serializer/protobuf). ([#137084](https://github.com/kubernetes/kubernetes/pull/137084), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Apps, Architecture, Auth, Node, Scheduling and Storage] +- Slow requests that use impersonation can be tracked via the `apiserver.latency.k8s.io/impersonation` audit event annotation when the `ConstrainedImpersonation` feature is enabled. ([#137523](https://github.com/kubernetes/kubernetes/pull/137523), [@enj](https://github.com/enj)) [SIG API Machinery, Auth and Testing] +- The `DRAConsumableCapacity` feature gate is enabled by default. ([#136611](https://github.com/kubernetes/kubernetes/pull/136611), [@sunya-ch](https://github.com/sunya-ch)) [SIG API Machinery, Cluster Lifecycle, Node, Scheduling and Testing] +- The `StrictIPCIDRValidation` feature gate in `kube-apiserver` is enabled by default, meaning that API fields no longer allow IP or CIDR values with extraneous leading "0"s (e.g., `010.000.000.005` rather than `10.0.0.5`) or CIDR subnet/mask values with ambiguous semantics (e.g., `192.168.0.5/24` rather than `192.168.0.0/24` or `192.168.0.5/32`). ([#137053](https://github.com/kubernetes/kubernetes/pull/137053), [@danwinship](https://github.com/danwinship)) [SIG Network and Testing] +- The `kube-scheduler` now updates PodGroup status with a `PodGroupScheduled` condition reflecting whether the group was successfully scheduled or is unschedulable. ([#137611](https://github.com/kubernetes/kubernetes/pull/137611), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Scheduling and Testing] +- Updated API comments to reflect the stable state of Dynamic Resource Allocation (DRA). ([#136441](https://github.com/kubernetes/kubernetes/pull/136441), [@kannon92](https://github.com/kannon92)) [SIG API Machinery] +- Updated API server internal API group to improve openapi schema correctness for fields being optional or required. ([#134675](https://github.com/kubernetes/kubernetes/pull/134675), [@JoelSpeed](https://github.com/JoelSpeed)) [SIG API Machinery, Apps, Auth, Node and Storage] +- Updated the `/configz` endpoint of `kubelet`, `kube-scheduler`, cloud controller manager, and `kube-proxy` to serialize the `APIVersion` and `Kind` fields and use public types instead of internal. ([#136044](https://github.com/kubernetes/kubernetes/pull/136044), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Network, Node, Scheduling and Testing] + +### Feature + +- Added ALPHA counter metric `scheduler_pod_scheduled_after_flush_total` to track pods successfully scheduled after timeout flush from the `unschedulablePods` queue. ([#135126](https://github.com/kubernetes/kubernetes/pull/135126), [@mrvarmazyar](https://github.com/mrvarmazyar)) [SIG Scheduling] +- Added `ARCH` column in the `kubectl get node -o wide` output. ([#132402](https://github.com/kubernetes/kubernetes/pull/132402), [@astraw99](https://github.com/astraw99)) [SIG CLI] +- Added `apiserver_peer_proxy_errors_total` and `apiserver_peer_discovery_sync_errors_total` alpha metrics to apiserver to track errors encountered in peer proxying and peer discovery. ([#137065](https://github.com/kubernetes/kubernetes/pull/137065), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Added `kubectl explain -r` flag as a shorthand for `--recursive`. ([#135283](https://github.com/kubernetes/kubernetes/pull/135283), [@laervn](https://github.com/laervn)) [SIG CLI] +- Added `kubelet_metrics_provider` metric to help users identify where kubelet's metrics are coming from. ([#136952](https://github.com/kubernetes/kubernetes/pull/136952), [@dgrisonnet](https://github.com/dgrisonnet)) [SIG Node] +- Added a `PodGroup` scheduling cycle to `kube-scheduler`'s main scheduling loop, enabling all pods within a `PodGroup` to be scheduled within a single cycle. ([#136618](https://github.com/kubernetes/kubernetes/pull/136618), [@macsko](https://github.com/macsko)) [SIG Scheduling and Testing] +- Added a `show-secret` flag to the diff command to explicitly allow secret values to be displayed during the diff operation. ([#137019](https://github.com/kubernetes/kubernetes/pull/137019), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Added a new gRPC service to the `kubelet` that provides information about Pods running on the node. ([#134627](https://github.com/kubernetes/kubernetes/pull/134627), [@briansonnenberg](https://github.com/briansonnenberg)) [SIG Node and Testing] +- Added a warning when `kubectl rollout undo` is used on resources managed with `kubectl apply` to prevent unexpected behavior from annotation mismatch. ([#137064](https://github.com/kubernetes/kubernetes/pull/137064), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Added alpha counter metric `route_controller_route_sync_total` to Cloud Controller Manager to track route syncs with cloud providers. This metric is in alpha stage. ([#136539](https://github.com/kubernetes/kubernetes/pull/136539), [@lukasmetzner](https://github.com/lukasmetzner)) [SIG API Machinery, Cloud Provider and Instrumentation] +- Added alpha metrics tracking the resource version the cache layer of an informer is at. ([#137419](https://github.com/kubernetes/kubernetes/pull/137419), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Architecture, Instrumentation and Testing] +- Added an alpha `informer_processing_latency_seconds` histogram metric to measure event handler execution time in RealFIFO. ([#137101](https://github.com/kubernetes/kubernetes/pull/137101), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation and Testing] +- Added metrics for constrained impersonation: `apiserver_impersonation_attempts_total`, `apiserver_impersonation_attempts_duration_seconds`, `apiserver_impersonation_authorization_attempts_total`, and `apiserver_impersonation_authorization_attempts_duration_seconds` (labels: mode, decision). ([#137374](https://github.com/kubernetes/kubernetes/pull/137374), [@enj](https://github.com/enj)) [SIG API Machinery, Auth and Testing] +- Added missing flags to `webhook serving` options for `k8s.io/cloud-provider`. ([#136816](https://github.com/kubernetes/kubernetes/pull/136816), [@damdo](https://github.com/damdo)) [SIG Cloud Provider] +- Added multiple conditions support to the `kubectl wait` command. ([#136855](https://github.com/kubernetes/kubernetes/pull/136855), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] +- Added new RuntimeService streaming RPCs (`StreamPodSandboxes`, `StreamContainers`, `StreamContainerStats`, `StreamPodSandboxStats`, `StreamPodSandboxMetrics`) and new ImageService streaming RPC (`StreamImages`). ([#136987](https://github.com/kubernetes/kubernetes/pull/136987), [@bitoku](https://github.com/bitoku)) [SIG Cluster Lifecycle, Node and Testing] +- Added support for in-place Pod resize of running non-sidecar initContainers. ([#137352](https://github.com/kubernetes/kubernetes/pull/137352), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Apps, Autoscaling, Node, Scheduling, Storage and Testing] +- Added support for the CRI (and NRI) to block Pod-level resizes. ([#137555](https://github.com/kubernetes/kubernetes/pull/137555), [@natasha41575](https://github.com/natasha41575)) [SIG Node] +- Added support for unknown (non-pod) references in `ResourceClaim` `status.reservedFor`. The controller now gracefully skips these entries instead of halting sync, ensuring stale pod references can still be cleaned up. ([#136450](https://github.com/kubernetes/kubernetes/pull/136450), [@MohammedSaalif](https://github.com/MohammedSaalif)) [SIG Apps and Node] +- Added the `ControllerManagerReleaseLeaderElectionLockOnCancel` feature gate to gate leader election lock release on exit for `kube-controller-manager`. ([#136279](https://github.com/kubernetes/kubernetes/pull/136279), [@tchap](https://github.com/tchap)) [SIG API Machinery and Cloud Provider] +- Added the `ExtendWebSocketsToKubelet` feature gate (beta, default true in `v1.36`). When enabled, the API server proxies WebSocket `exec/attach/portforward` requests directly to the `kubelet` rather than translating or tunneling them at the API server. The `kubelet` handles WebSocket-to-SPDY stream translation (`exec/attach`) and WebSocket tunneling (portforward) using the same handlers previously used at the API server. The `kubelet` advertises support for this feature to the API server via the `NodeDeclaredFeatures` mechanism; the API server only proxies directly to a `kubelet` that has advertised support. Two new alpha metrics track routing decisions and WebSocket streaming volume: `apiserver_websocket_streaming_requests_total` (labels: subresource, proxy_type) and `kubelet_streaming_websocket_requests_total` (label: subresource). ([#136256](https://github.com/kubernetes/kubernetes/pull/136256), [@seans3](https://github.com/seans3)) [SIG API Machinery, Autoscaling, Node, Scheduling and Testing] +- Added the `UserNamespacesHostNetwork` runtime handler and integrated the `UserNamespacesHostNetworkSupport` feature gate with the `NodeDeclaredFeatures` feature gate. The `UserNamespacesHostNetworkSupport` feature gate only takes effect when the container runtime's `UserNamespacesHostNetwork` runtime handler returns true and the `NodeDeclaredFeatures` feature gate is enabled. ([#135828](https://github.com/kubernetes/kubernetes/pull/135828), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Autoscaling, Node, Scheduling and Testing] +- Added the `appProtocol` field to `kubectl describe service` output. ([#135744](https://github.com/kubernetes/kubernetes/pull/135744), [@ali-a-a](https://github.com/ali-a-a)) [SIG CLI] +- Added the `timezone` field to the `kubectl describe` CronJob output. ([#136663](https://github.com/kubernetes/kubernetes/pull/136663), [@kfess](https://github.com/kfess)) [SIG CLI] +- Added the ability for the StatefulSet controller to read its own Pod and PVC writes. ([#137254](https://github.com/kubernetes/kubernetes/pull/137254), [@michaelasp](https://github.com/michaelasp)) [SIG Apps] +- Added the ability for the `ReplicaSet` controller to read its own writes, preventing spurious reconciliation loops while the cache catches up to recent updates. ([#137212](https://github.com/kubernetes/kubernetes/pull/137212), [@michaelasp](https://github.com/michaelasp)) [SIG Apps] +- Added the metric `terminated_containers_total` to track the number of failed or succeeded containers, broken down by exit code. ([#137453](https://github.com/kubernetes/kubernetes/pull/137453), [@rawsocket](https://github.com/rawsocket)) [SIG Instrumentation, Node and Testing] +- Added tracing for WatchList requests. ([#137202](https://github.com/kubernetes/kubernetes/pull/137202), [@serathius](https://github.com/serathius)) [SIG API Machinery and Testing] +- Added two scheduler metrics for Device Binding Conditions, covering allocation attempts and PreBind duration with status and driver labels. ([#137284](https://github.com/kubernetes/kubernetes/pull/137284), [@ttsuuubasa](https://github.com/ttsuuubasa)) [SIG Node and Scheduling] +- Added write and read permissions for workloads to the `admin` cluster role, write permissions to the `edit` cluster role, and read permissions to the `view` cluster role. ([#135418](https://github.com/kubernetes/kubernetes/pull/135418), [@carlory](https://github.com/carlory)) [SIG Auth] +- Aligned the `scheduler_preemption_victims` metric definition between asynchronous and synchronous preemption modes. The metric now consistently reports the number of pods chosen as victims across both modes. ([#135955](https://github.com/kubernetes/kubernetes/pull/135955), [@utam0k](https://github.com/utam0k)) [SIG Scheduling] +- CRI API: Added the `image_id` field to the `PullImageResponse` message, serving as a unique identifier for the image on the node as returned by the container runtimes. ([#137217](https://github.com/kubernetes/kubernetes/pull/137217), [@stlaz](https://github.com/stlaz)) [SIG Node] +- Changed the default debug profile from `legacy` to `general`. The `legacy` profile is planned to be removed in `v1.39`. ([#135874](https://github.com/kubernetes/kubernetes/pull/135874), [@mochizuki875](https://github.com/mochizuki875)) [SIG CLI and Testing] +- Client-go: Default informer behavior now updates store state with all the objects in a list or relist before calling handler `OnDelete`, `OnAdd`, or `OnUpdate` methods for individual items which were deleted, added, or removed. This ensures that the store state which can be inspected by handlers corresponds to a set of objects that existed at a particular resource version on the server. This behavior is guarded by the `AtomicFIFO` feature gate, which is enabled by default in `v1.36` but can be disabled if needed to temporarily regain the previous behavior. ([#135462](https://github.com/kubernetes/kubernetes/pull/135462), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Client-go: Improved informer resync processing to reduce contention on store locks between incoming events and handler updates, which may result in observable timing differences of handler invocations. This behavior is guarded by the `AtomicFIFO` feature gate, which is enabled by default in `v1.36` but can be disabled if needed to temporarily regain the previous behavior. ([#136008](https://github.com/kubernetes/kubernetes/pull/136008), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Client-go: Informers can now enqueue new watch events while already-queued events are being processed. This avoids dropping watches during a burst of incoming events due to contention on slow processing. This behavior is controlled by the `UnlockWhileProcessing` client-go feature gate, which is enabled by default. ([#136264](https://github.com/kubernetes/kubernetes/pull/136264), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Scheduling] +- Client-go: informer stores now keep track the resourceVersion they are synced to (via add/update/delete events, or replace calls, or bookmark events), and provide a `LastStoreSyncResourceVersion` method to obtain this resource version. This method can return `""` if the store has not been synced to yet, and depends on the `AtomicFIFO` feature being enabled. ([#134827](https://github.com/kubernetes/kubernetes/pull/134827), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Testing] +- CustomResourceDefinition (CRD) validation now strictly enforces ranges for numeric formats (`int32`, `int64`, `float`, `double`) when specified in the schema. Existing objects with out-of-range values are preserved via validation ratcheting. ([#136582](https://github.com/kubernetes/kubernetes/pull/136582), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling and Storage] +- DRA ResourceSlice controller: Added optional `ReconcilePoolWithName` to allow per-pool reconciliation without setting `NodeName` on slices, so the scheduler can use `NodeSelector` or `allNodes` for node-owned, cluster-visible resources (e.g. network-shared devices). "All nodes" is no longer the default. When publishing devices for the entire cluster, it must be set explicitly. ([#137365](https://github.com/kubernetes/kubernetes/pull/137365), [@yaroslavborbat](https://github.com/yaroslavborbat)) [SIG Node and Testing] +- Enabled Prometheus native histogram support in `kube-apiserver` when the feature gate is enabled. Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160). ([#136763](https://github.com/kubernetes/kubernetes/pull/136763), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Cloud Provider, Instrumentation, Network, Node, Scheduling and Testing] +- Enabled Prometheus native histogram support in `kube-controller-manager` when the feature gate is enabled. Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160). ([#137779](https://github.com/kubernetes/kubernetes/pull/137779), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation and Testing] +- Enabled Prometheus native histogram support in `kube-proxy` when the feature gate is enabled. Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160). ([#137781](https://github.com/kubernetes/kubernetes/pull/137781), [@richabanker](https://github.com/richabanker)) [SIG Network] +- Enabled Prometheus native histogram support in `kube-scheduler` when the feature gate is enabled. Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160). ([#137466](https://github.com/kubernetes/kubernetes/pull/137466), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation, Scheduling and Testing] +- Enabled Prometheus native histogram support in `kubelet` when the feature gate is enabled. Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160). ([#137780](https://github.com/kubernetes/kubernetes/pull/137780), [@richabanker](https://github.com/richabanker)) [SIG Node] +- Enabled the Topology, CPU, and Memory managers to recognize and act upon `pod.spec.resources`, enabling two flexible resource management models. Both models support `guaranteed` Pods that contain a mix of containers that may be eligible to receive exclusive resource allocation or be part of the Pod-allocated shared resource pool. ([#134768](https://github.com/kubernetes/kubernetes/pull/134768), [@KevinTMtz](https://github.com/KevinTMtz)) [SIG Node and Testing] +- Enabled the `WatchCacheInitializationPostStartHook` feature gate by default. ([#135777](https://github.com/kubernetes/kubernetes/pull/135777), [@serathius](https://github.com/serathius)) [SIG API Machinery] +- Enabled workload-aware preemption for PodGroups when the `WorkloadAwarePreemption` feature gate is active. When PodGroup scheduling fails to find placement for a PodGroup, workload-aware preemption runs for the entire group instead of running default preemption for each individual Pod. ([#137606](https://github.com/kubernetes/kubernetes/pull/137606), [@Argh4k](https://github.com/Argh4k)) [SIG Apps, Node, Scheduling, Storage and Testing] +- Ensured single-container Pod can restart quickly with the `RestartAllContainers` action. ([#136966](https://github.com/kubernetes/kubernetes/pull/136966), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node and Testing] +- Fixed missing field conversions (`BindsToNode`, `BindingConditions`, `BindingFailureConditions`, `AllowMultipleAllocations`, `Capacity`) in DRA API `v1beta1` hand-written conversion code. ([#137240](https://github.com/kubernetes/kubernetes/pull/137240), [@yykkibbb](https://github.com/yykkibbb)) [SIG Node] +- Graduated `ComponentFlagz` to beta. ([#137386](https://github.com/kubernetes/kubernetes/pull/137386), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Auth, Instrumentation, Node and Testing] +- Graduated `ComponentStatusz` to beta. ([#137384](https://github.com/kubernetes/kubernetes/pull/137384), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Auth and Instrumentation] +- Graduated fine-grained kubelet API authorization to stable. ([#136116](https://github.com/kubernetes/kubernetes/pull/136116), [@vinayakankugoyal](https://github.com/vinayakankugoyal)) [SIG Node] +- Graduated the `KubeletPSI` feature to GA, enabled by default. The `kubelet` exposes Linux cgroup Pressure Stall Information (PSI) metrics, providing deeper visibility into system and Pod-level resource contention (CPU, Memory, and I/O) via the `kubelet` Summary API. ([#136548](https://github.com/kubernetes/kubernetes/pull/136548), [@mariafromano-25](https://github.com/mariafromano-25)) [SIG Node] +- Improved preemption behavior so that pods preempted during the `PreBind` phase are now re-queued into the backoff queue instead of being deleted via the API server, enabling more graceful handling of preemption during binding. ([#135502](https://github.com/kubernetes/kubernetes/pull/135502), [@Argh4k](https://github.com/Argh4k)) [SIG Scheduling and Testing] +- Instrumented `/flagz` and `/statusz` endpoints with apiserver request metrics (`apiserver_request_total`, `apiserver_request_duration_seconds`), with group and version labels reflecting the content-negotiated API version. ([#137021](https://github.com/kubernetes/kubernetes/pull/137021), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery and Instrumentation] +- Introduced index-based naming in the ResourceSlice controller and ensured ResourceSlices and pools are sorted lexicographically before allocation, allowing users to control allocation priority. ([#136641](https://github.com/kubernetes/kubernetes/pull/136641), [@troychiu](https://github.com/troychiu)) [SIG Node and Testing] +- Introduced new staging modules `k8s.io/streaming` and `k8s.io/cri-streaming` for Kubernetes streaming transport and CRI streaming server code. `k8s.io/apimachinery/pkg/util/httpstream` (including `spdy` and `wsstream`) remains available as a deprecated compatibility wrapper backed by `k8s.io/streaming`. The extracted SPDY roundtripper preserves CIDR matching in `NO_PROXY`/`no_proxy`. ([#137298](https://github.com/kubernetes/kubernetes/pull/137298), [@dims](https://github.com/dims)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Release, Scheduling, Storage and Testing] +- Kube-apiserver: Graduated the `UnknownVersionInteroperabilityProxy` feature gate to beta, enabled by default. The `--peer-ca-file` flag is required to turn on the proxy. ([#137172](https://github.com/kubernetes/kubernetes/pull/137172), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Kube-apiserver: Promoted the `ExternalServiceAccountTokenSigner` feature gate to GA. ([#136118](https://github.com/kubernetes/kubernetes/pull/136118), [@HarshalNeelkamal](https://github.com/HarshalNeelkamal)) [SIG API Machinery and Auth] +- Kube-controller-manager: The daemonset controller now defers syncing a DaemonSet object when the controller has not yet observed daemonset or pod writes from the last time the object was synced. This prevents spurious creation of duplicate pods for nodes when the controller's cache is stale. When a sync is deferred for this reason, a `daemonset_controller_stale_sync_skips_total` metric is incremented and a message is logged by the daemonset controller. This behavior can be temporarily disabled by setting the `StaleControllerConsistencyDaemonSet` feature gate to false. ([#134937](https://github.com/kubernetes/kubernetes/pull/134937), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Node, Scheduling and Testing] +- Kube-controller-manager: The job controller now defers syncing a Job object when the controller has not yet observed job or pod writes from the last time the object was synced. This prevents spurious creation of duplicate pods for jobs when the controller's cache is stale. When a sync is deferred for this reason, a `job_controller_stale_sync_skips_total` metric is incremented and a message is logged by the job controller. This behavior can be temporarily disabled by setting the `StaleControllerConsistencyJob` feature gate to false. ([#137210](https://github.com/kubernetes/kubernetes/pull/137210), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Apps] +- Kubeadm: Added the `--allow-deprecated-api` flag to `kubeadm config validate`. By default the command prints a warning for deprecated APIs unless the flag is passed. Additionally, added missing support for `v1beta4` `UpgradeConfiguration` to `kubeadm config migrate` and `kubeadm config validate` commands. ([#135148](https://github.com/kubernetes/kubernetes/pull/135148), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: Changed Node object patching behavior to retry on unknown (non-allowlisted) API errors within the polling duration instead of exiting early. ([#135776](https://github.com/kubernetes/kubernetes/pull/135776), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: Increased the timeout of the `kubeadm upgrade` `CreateJob` preflight check to 1 minute. This allows Windows worker nodes to have more time to run the preflight check. The check uses the `pause` image, so if you are experiencing slow pull times, you can either pre-pull the image on the worker using `kubeadm config images pull --kubernetes-version TARGET` or skip the preflight check with `--ignore-preflight-errors`. ([#136273](https://github.com/kubernetes/kubernetes/pull/136273), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: Promoted the `NodeLocalCRISocket` feature gate to GA and locked it to enabled. ([#135742](https://github.com/kubernetes/kubernetes/pull/135742), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Cluster Lifecycle] +- Kubeadm: Removed the `ControlPlaneKubeletLocalMode` feature gate, which graduated to GA in `v1.35` and was locked to enabled. ([#135773](https://github.com/kubernetes/kubernetes/pull/135773), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: The preflight check `ContainerRuntimeVersion` validates if the installed container runtime supports the `RuntimeConfig` gRPC method. For older kubelet versions than `v1.37`, it will return a preflight warning. ([#136898](https://github.com/kubernetes/kubernetes/pull/136898), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] +- Kubeadm: When using `--v=1` or higher log verbosity, prints information about the CA certificate used for discovery when using `kubeadm join`. ([#137102](https://github.com/kubernetes/kubernetes/pull/137102), [@sivchari](https://github.com/sivchari)) [SIG Cluster Lifecycle] +- Kubelet: Deferred the removal of deprecated kubelet configuration flags (and their related fallback behavior) from version 1.36 to 1.37, aligning with the end of containerd v1.7 support. ([#136846](https://github.com/kubernetes/kubernetes/pull/136846), [@carlory](https://github.com/carlory)) [SIG Node and Testing] +- Kubelet: If the `--client-ca-file` is updated while `kubelet` is running, the updated root certificates are correctly used to advertise accepted authorities to TLS clients connecting to the `kubelet` endpoints. This behavior is guarded by the `ReloadKubeletClientCAFile` feature gate, which is enabled by default. ([#136762](https://github.com/kubernetes/kubernetes/pull/136762), [@HarshalNeelkamal](https://github.com/HarshalNeelkamal)) [SIG API Machinery, Auth, Node and Testing] +- Kubernetes is now built using Go `v1.26.2`. ([#138299](https://github.com/kubernetes/kubernetes/pull/138299), [@xmudrii](https://github.com/xmudrii)) [SIG Release and Testing] +- Kubernetes is now built using Go `v1.26.0`. ([#137080](https://github.com/kubernetes/kubernetes/pull/137080), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] +- Kubernetes is now built with Go `v1.25.6`. ([#136257](https://github.com/kubernetes/kubernetes/pull/136257), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] +- Kubernetes is now built with Go `v1.25.6`. ([#136465](https://github.com/kubernetes/kubernetes/pull/136465), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] +- Kubernetes is now built with Go `v1.25.7`. ([#136750](https://github.com/kubernetes/kubernetes/pull/136750), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] +- Kubernetes is now built with Go `v1.25.7`. ([#136982](https://github.com/kubernetes/kubernetes/pull/136982), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] +- Preserved the logs of restarted containers for containers restarted by the `RestartAllContainers` feature. ([#136963](https://github.com/kubernetes/kubernetes/pull/136963), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node] +- Promoted `DRAPartitionableDevices` to beta. ([#137350](https://github.com/kubernetes/kubernetes/pull/137350), [@mortent](https://github.com/mortent)) [SIG Node, Scheduling and Testing] +- Promoted `kubectl kuberc` commands to beta. ([#136643](https://github.com/kubernetes/kubernetes/pull/136643), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] +- Promoted the `CSIServiceAccountTokenSecrets` feature gate to GA. ([#136596](https://github.com/kubernetes/kubernetes/pull/136596), [@aramase](https://github.com/aramase)) [SIG Auth and Storage] +- Promoted the `KubeletPodResourcesDynamicResources` and `KubeletPodResourcesGet` feature gates to GA. ([#136728](https://github.com/kubernetes/kubernetes/pull/136728), [@guptaNswati](https://github.com/guptaNswati)) [SIG Node and Testing] +- Promoted the `RelaxedServiceNameValidation` feature gate to beta and enabled it by default. + Service names are now validated with `NameIsDNSLabel()`, relaxing the pre-existing validation. ([#136389](https://github.com/kubernetes/kubernetes/pull/136389), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Network] +- Promoted the `RestartAllContainersOnContainerExits` feature gate to beta, enabled by default. ([#136681](https://github.com/kubernetes/kubernetes/pull/136681), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node and Testing] +- Reduced the needs of the setcap build image for `kube-apiserver` by no longer requiring that image to contain a shell (`sh` or `dash` or `bash`). ([#136633](https://github.com/kubernetes/kubernetes/pull/136633), [@addyess](https://github.com/addyess)) [SIG Release] +- Reverted the addition of the `image_id` field to the CRI API `PullImageResponse` message. ([#137574](https://github.com/kubernetes/kubernetes/pull/137574), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Node] +- Server images now use `staging/src/k8s.io/component-base/logs/kube-log-runner` instead of `go-runner`; full compatibility is maintained (including the same `/go-runner` executable path). In the future Kubernetes will use base-images without go-runner. ([#136954](https://github.com/kubernetes/kubernetes/pull/136954), [@BenTheElder](https://github.com/BenTheElder)) [SIG Instrumentation and Release] +- Updated CoreDNS to `v1.14.2`. ([#137605](https://github.com/kubernetes/kubernetes/pull/137605), [@pacoxu](https://github.com/pacoxu)) [SIG Cloud Provider and Cluster Lifecycle] +- Updated `kubectl describe node` to list aggregated ResourceSlices when the ResourceSlice API is present, detailing slice name, driver, and pool. ([#131744](https://github.com/kubernetes/kubernetes/pull/131744), [@ArangoGutierrez](https://github.com/ArangoGutierrez)) [SIG CLI] +- Updated `kubectl explain` to display an EXTERNAL DOCS section when a schema or field includes an `externalDocs` section. This appears after the DESCRIPTION block for top-level resources and after the field description for individual fields. The section is omitted in short mode and when `externalDocs` is absent. ([#136988](https://github.com/kubernetes/kubernetes/pull/136988), [@pedjak](https://github.com/pedjak)) [SIG CLI] +- Updated `kubectl get ingressclass` to display a `(default)` marker for the default IngressClass. ([#134422](https://github.com/kubernetes/kubernetes/pull/134422), [@jaehanbyun](https://github.com/jaehanbyun)) [SIG CLI and Network] +- Updated `kubectl kuberc set` with options for setting `credentialPluginPolicy` and `credentialPluginAllowlist`. ([#137300](https://github.com/kubernetes/kubernetes/pull/137300), [@pmengelbert](https://github.com/pmengelbert)) [SIG CLI] +- Updated cAdvisor to `v0.55.0` in vendor dependencies. ([#135829](https://github.com/kubernetes/kubernetes/pull/135829), [@dims](https://github.com/dims)) [SIG Node] +- Updated feature gate `MutablePodResourcesForSuspendedJobs` and `MutableSchedulingDirectivesForSuspendedJobs` to be enabled by default. ([#135965](https://github.com/kubernetes/kubernetes/pull/135965), [@kannon92](https://github.com/kannon92)) [SIG Apps and Testing] +- Updated node performance e2e tests to use PyTorch Wide-Deep workload instead of TensorFlow. ([#136397](https://github.com/kubernetes/kubernetes/pull/136397), [@dims](https://github.com/dims)) [SIG Testing] +- Updated node performance e2e tests to use PyTorch Wide-Deep workload instead of TensorFlow. ([#136398](https://github.com/kubernetes/kubernetes/pull/136398), [@dims](https://github.com/dims)) [SIG Node and Testing] +- Updated the `ImageLocality` scheduler plugin to consider `ImageVolume` images when scoring nodes for Pod scheduling. ([#130231](https://github.com/kubernetes/kubernetes/pull/130231), [@Barakmor1](https://github.com/Barakmor1)) [SIG Scheduling] +- When `kubectl exec` or `kubectl logs` are run with a specified container name, and no container with that name is found, `kubectl` lists the names of containers that would be valid to specify. ([#136973](https://github.com/kubernetes/kubernetes/pull/136973), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] + +### Documentation + +- Added metric component and endpoint to generated metric reference documentation. ([#136360](https://github.com/kubernetes/kubernetes/pull/136360), [@skl](https://github.com/skl)) [SIG Instrumentation and Testing] + +### Failing Test + +- Kubelet: Fixed device plugin test failures after kubelet restart. ([#135485](https://github.com/kubernetes/kubernetes/pull/135485), [@saschagrunert](https://github.com/saschagrunert)) [SIG Node and Testing] +- The `PLEGOnDemandRelist` feature flag is kept at beta level, but switched off by default. ([#137909](https://github.com/kubernetes/kubernetes/pull/137909), [@dims](https://github.com/dims)) [SIG Node] + +### Bug or Regression + +- Added the `--detach-keys` flag to `kubectl attach` and `kubectl run`, allowing detach without terminating the container. ([#134997](https://github.com/kubernetes/kubernetes/pull/134997), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG API Machinery and CLI] +- Capped `nf_conntrack_max` to 1,048,576 to prevent excessive memory consumption on high-core machines when using automatic calculation. ([#137002](https://github.com/kubernetes/kubernetes/pull/137002), [@kairosci](https://github.com/kairosci)) [SIG Apps and Network] +- Changed some error logs to info logs with verbosity level in `controller/resourcequota` and `controller/garbagecollector`. ([#136040](https://github.com/kubernetes/kubernetes/pull/136040), [@petern48](https://github.com/petern48)) [SIG API Machinery and Apps] +- Changed the `nodeGetCapabilities` method of `csiDriverClient` to return `NewUncertainProgressError` when receiving a non-final gRPC error. This resolves residual global mount paths during rapid pod creation-deletion cycles. ([#135930](https://github.com/kubernetes/kubernetes/pull/135930), [@249043822](https://github.com/249043822)) [SIG Node and Storage] +- Changed the behavior of default scheduler preemption plugin when preempting Pods that are in `WaitOnPermit` phase. They are now moved to the scheduler backoff queue instead of being marked as unschedulable. ([#135719](https://github.com/kubernetes/kubernetes/pull/135719), [@Argh4k](https://github.com/Argh4k)) [SIG Scheduling and Testing] +- Changed the runtime handlers list returned by the CRI runtime to be sorted, preventing unnecessary Node object updates when the order changes. ([#135358](https://github.com/kubernetes/kubernetes/pull/135358), [@harche](https://github.com/harche)) [SIG Node] +- Client-go: Fixed an unlikely deadlock during informer startup. ([#136509](https://github.com/kubernetes/kubernetes/pull/136509), [@pohly](https://github.com/pohly)) [SIG API Machinery] +- CustomResourceDefinitions: Fixed server-side apply field ownership tracking so that metadata ownership is correctly tracked for writes to the `/status` subresource. + Custom Resources: Fixed server-side apply field ownership to not update metadata from the `/status` subresource since these writes are wiped for custom resources. ([#137689](https://github.com/kubernetes/kubernetes/pull/137689), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Network and Testing] +- DRA BindingConditions: Fixed a panic in the scheduler when the `DRABindingConditions` feature was enabled and the same claim was reused among different Pods while deallocation happened in parallel. ([#137371](https://github.com/kubernetes/kubernetes/pull/137371), [@pohly](https://github.com/pohly)) [SIG Node, Scheduling and Testing] +- Disabled `SchedulerAsyncAPICalls` feature gate due to performance issues caused by API client throttling. ([#135903](https://github.com/kubernetes/kubernetes/pull/135903), [@macsko](https://github.com/macsko)) [SIG Scheduling] +- Disallowed setting a resize restart policy of `RestartContainer` on non-sidecar initContainers, as the resize of such containers has never been supported. ([#137458](https://github.com/kubernetes/kubernetes/pull/137458), [@natasha41575](https://github.com/natasha41575)) [SIG Apps, Node and Testing] +- Explicitly wrote `memory.min=0` for QoS cgroups when the calculated requests are zero. ([#137637](https://github.com/kubernetes/kubernetes/pull/137637), [@QiWang19](https://github.com/QiWang19)) [SIG Node] +- Fixed SELinux warning controller to not emit events for completed Pods (Succeeded and Failed states). ([#135629](https://github.com/kubernetes/kubernetes/pull/135629), [@jsafrane](https://github.com/jsafrane)) [SIG Apps, Storage and Testing] +- Fixed StatefulSets to always count `.status.availableReplicas` at the correct time without delay, resulting in faster StatefulSet rollout progress. ([#135428](https://github.com/kubernetes/kubernetes/pull/135428), [@atiratree](https://github.com/atiratree)) [SIG Apps] +- Fixed `DRA manager` not initializing sharedID from cache when `DRAConsumableCapacity` is enabled. ([#136734](https://github.com/kubernetes/kubernetes/pull/136734), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node and Scheduling] +- Fixed `PodCertificateRequest` OwnerReference using incorrect apiVersion "core/v1" instead of "v1", which prevented garbage collection of `PodCertificateRequests` when their owning Pod was deleted. ([#137008](https://github.com/kubernetes/kubernetes/pull/137008), [@srhppr](https://github.com/srhppr)) [SIG Auth and Node] +- Fixed `ReadWriteOncePod` preemption e2e test to run as serial, preventing it from causing other random e2e tests to flake. ([#135623](https://github.com/kubernetes/kubernetes/pull/135623), [@jsafrane](https://github.com/jsafrane)) [SIG Storage and Testing] +- Fixed `container_swap_usage_bytes` in the `/metrics/resource` endpoint to correctly report container-level swap usage instead of always reporting 0. The root cause was missing logic in `addCadvisorContainerCPUAndMemoryStats` to propagate swap stats from cadvisor to the container stats object. ([#137098](https://github.com/kubernetes/kubernetes/pull/137098), [@yuanwang04](https://github.com/yuanwang04)) [SIG Apps, Node and Testing] +- Fixed `event_handling_duration_seconds`, `preemption_goroutines_duration_seconds`, `run_podsandbox_duration_seconds`, and `store_schedule_results_duration_seconds` metrics incorrectly recording near-zero latency values instead of actual durations, caused by premature evaluation of `SinceInSeconds(startTime)` in a deferred call. ([#135749](https://github.com/kubernetes/kubernetes/pull/135749), [@novahe](https://github.com/novahe)) [SIG Architecture, Instrumentation, Node and Scheduling] +- Fixed `kube-apiserver` startup failure during upgrade when `MultiCIDRServiceAllocator` is enabled and the cluster has a large number of namespaces. The IP address repair controller retries on Forbidden errors from admission plugins that are not yet ready. ([#137147](https://github.com/kubernetes/kubernetes/pull/137147), [@haojiwu](https://github.com/haojiwu)) [SIG Testing] +- Fixed `kube-proxy` log spam when all of a Service's endpoints were unready. ([#136743](https://github.com/kubernetes/kubernetes/pull/136743), [@ansilh](https://github.com/ansilh)) [SIG Network] +- Fixed `kubectl delete` to properly handle deletion of multiple StatefulSet pods and exit normally. ([#135563](https://github.com/kubernetes/kubernetes/pull/135563), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG CLI, Network and Node] +- Fixed `kubectl describe node` to correctly display resource requests and limits for Pods using Pod-level resources. ([#137394](https://github.com/kubernetes/kubernetes/pull/137394), [@Nikateen](https://github.com/Nikateen)) [SIG CLI] +- Fixed `kubectl describe` to correctly recognize uppercase acronyms as a single element when displaying Custom Resource field names. ([#135683](https://github.com/kubernetes/kubernetes/pull/135683), [@uozalp](https://github.com/uozalp)) [SIG CLI] +- Fixed `kubectl label` output message to display `modified` when labels are both added and removed. ([#134849](https://github.com/kubernetes/kubernetes/pull/134849), [@tchap](https://github.com/tchap)) [SIG CLI] +- Fixed `kubectl logs -f` to wait for containers to start instead of failing immediately when pods are in ContainerCreating or PodInitializing states. ([#136411](https://github.com/kubernetes/kubernetes/pull/136411), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Fixed a `v1.29` regression in the `apiserver_watch_events_sizes` metric to report total outgoing watch traffic again. ([#135367](https://github.com/kubernetes/kubernetes/pull/135367), [@mborsz](https://github.com/mborsz)) [SIG API Machinery] +- Fixed a `v1.34` regression in `ipvs` and `winkernel` `kube-proxy` backends. These backends now revert to their `pre-v1.34` behavior of regularly rechecking all rules even when no Services or EndpointSlices change. ([#135631](https://github.com/kubernetes/kubernetes/pull/135631), [@danwinship](https://github.com/danwinship)) [SIG Network and Windows] +- Fixed a `v1.34` regression when starting pods with environment variables containing a value with `$` followed by a multi-byte character. ([#136325](https://github.com/kubernetes/kubernetes/pull/136325), [@AutuSnow](https://github.com/AutuSnow)) [SIG Architecture] +- Fixed a `v1.35` regression in StatefulSet parallel Pod management by disabling the `MaxUnavailableStatefulSet` feature by default. ([#137904](https://github.com/kubernetes/kubernetes/pull/137904), [@soltysh](https://github.com/soltysh)) [SIG Apps] +- Fixed a bug causing clients to error out when decoding large CBOR encoded lists. ([#135340](https://github.com/kubernetes/kubernetes/pull/135340), [@ricardomaraschini](https://github.com/ricardomaraschini)) [SIG API Machinery] +- Fixed a bug in `DeepEqualWithNilDifferentFromEmpty` where empty slices and maps were incorrectly considered equal to non-empty ones due to using OR (`||`) instead of AND (`&&`) logic. This could cause managed fields timestamps to not update when the only change was adding or removing all elements from a list or map. ([#135636](https://github.com/kubernetes/kubernetes/pull/135636), [@mikecook](https://github.com/mikecook)) [SIG API Machinery] +- Fixed a bug in the `dra_operations_duration_seconds` metric where the `is_error` label was recording inverted values. Error operations now correctly report `is_error=true`, and successful operations report `is_error=false`. ([#135227](https://github.com/kubernetes/kubernetes/pull/135227), [@hime](https://github.com/hime)) [SIG Node] +- Fixed a bug preventing Pods sharing ResourceClaims from being scheduled with GangScheduling. ([#137647](https://github.com/kubernetes/kubernetes/pull/137647), [@nojnhuh](https://github.com/nojnhuh)) [SIG Node, Scheduling and Testing] +- Fixed a bug that caused `EndpointSlice` churn for headless services with no ports defined. ([#136502](https://github.com/kubernetes/kubernetes/pull/136502), [@tzneal](https://github.com/tzneal)) [SIG Network] +- Fixed a bug where `kubectl apply --dry-run=client` would only output server state instead of merged manifest values when the resource already exists. ([#135513](https://github.com/kubernetes/kubernetes/pull/135513), [@grandeit](https://github.com/grandeit)) [SIG CLI] +- Fixed a bug where `kubectl plugin list` failed to detect overshadowed plugins on Windows. ([#136689](https://github.com/kubernetes/kubernetes/pull/136689), [@kfess](https://github.com/kfess)) [SIG CLI] +- Fixed a bug where the Gated pods metric was not updated when a Pod transitioned from Unschedulable to Gated during an update. ([#135368](https://github.com/kubernetes/kubernetes/pull/135368), [@vshkrabkov](https://github.com/vshkrabkov)) [SIG Scheduling] +- Fixed a bug where the `scheduler_unschedulable_pods` metric could be artificially inflated (leak) when a pod fails `PreEnqueue` plugins after being previously marked unschedulable. ([#135981](https://github.com/kubernetes/kubernetes/pull/135981), [@vshkrabkov](https://github.com/vshkrabkov)) [SIG Scheduling] +- Fixed a bug where users could not update HPAv2 resources that use object metrics with `averageValue` via the v1 HPA API. ([#137856](https://github.com/kubernetes/kubernetes/pull/137856), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Autoscaling] +- Fixed a bug where, after a `kubelet` restart, regular containers in a Pod with a sidecar (initContainer with `restartPolicy`: Always) and a `startupProbe` failed to restart after crashing. Affected Pods remained stuck with `RestartCount: 0` indefinitely. ([#137146](https://github.com/kubernetes/kubernetes/pull/137146), [@george-angel](https://github.com/george-angel)) [SIG Node and Testing] +- Fixed a data race in the `PopulateRefs` function in `k8s.io/apiserver/pkg/cel/openapi/resolver` where concurrent goroutines could simultaneously modify shared pointer fields from a shallow-copied schema struct. ([#136802](https://github.com/kubernetes/kubernetes/pull/136802), [@pohly](https://github.com/pohly)) [SIG API Machinery, Node and Testing] +- Fixed a kubelet device manager bug where topology hint computation enumerated O(2^n) NUMA node combinations using all machine NUMA nodes. On systems with many NUMA nodes that carry no devices (e.g. NVIDIA GB200 with 36 NUMA nodes), this caused kubelet to stall indefinitely during pod admission. The device manager now restricts iteration to NUMA nodes that actually host devices for the requested resource, reducing the search space to O(2^k) where k is typically 1–2. ([#138244](https://github.com/kubernetes/kubernetes/pull/138244), [@fanzhangio](https://github.com/fanzhangio)) [SIG Node] +- Fixed a loophole that allowed users to work around DRA extended resource quota set by system administrators. ([#135434](https://github.com/kubernetes/kubernetes/pull/135434), [@yliaog](https://github.com/yliaog)) [SIG API Machinery, Apps, Node, Scheduling and Testing] +- Fixed a race condition in CEL admission policy compilation that could cause `kube-apiserver` to crash with a `concurrent map read and map write` error under high load. ([#135759](https://github.com/kubernetes/kubernetes/pull/135759), [@Abhigyan-Shekhar](https://github.com/Abhigyan-Shekhar)) [SIG API Machinery and CLI] +- Fixed a race condition in Dynamic Resource Allocation (DRA) where the same device could be allocated twice for different `ResourceClaims` when scheduling many pods very rapidly. Depending on whether DRA drivers check for this during `NodePrepareResources` (they should, but not all may implement this properly), the second pod using the same device could fail to start until the first one is done or (worse) run in parallel. ([#136269](https://github.com/kubernetes/kubernetes/pull/136269), [@pohly](https://github.com/pohly)) [SIG Node, Scheduling and Testing] +- Fixed an issue in the Windows `kube-proxy` (winkernel) where IPv4 and IPv6 Service load balancers could be incorrectly shared, causing broken dual-stack Service behavior. The `kube-proxy` now tracks load balancers per IP family, enabling correct support for `PreferDualStack` and `RequireDualStack` Services on Windows nodes. ([#136241](https://github.com/kubernetes/kubernetes/pull/136241), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] +- Fixed an issue where zero-valued PSI (Pressure Stall Information) metrics were emitted by the `kubelet` when the OS does not support PSI, even if the `KubeletPSI` feature gate was enabled. ([#137326](https://github.com/kubernetes/kubernetes/pull/137326), [@amritansh1502](https://github.com/amritansh1502)) [SIG Node] +- Fixed container restart policy validation error message to correctly show available actions when the `RestartAllContainersOnContainerExits` feature gate is enabled. ([#137369](https://github.com/kubernetes/kubernetes/pull/137369), [@kfess](https://github.com/kfess)) [SIG Apps] +- Fixed erroneously reporting a pod-level resize in progress on Pod creation when the `InPlacePodLevelResourcesVerticalScaling` feature gate is enabled. ([#138049](https://github.com/kubernetes/kubernetes/pull/138049), [@ndixita](https://github.com/ndixita)) [SIG Node and Testing] +- Fixed feature gates `ChangeContainerStatusOnKubeletRestart` and `StatefulSetSemanticRevisionComparison` to be visible in `--help` output across different components. ([#135515](https://github.com/kubernetes/kubernetes/pull/135515), [@dims](https://github.com/dims)) [SIG Architecture] +- Fixed goroutine hot-loop in client-go `StartEventWatcher` when the event broadcaster shuts down before the cancellation context fires. ([#137398](https://github.com/kubernetes/kubernetes/pull/137398), [@Rajneesh180](https://github.com/Rajneesh180)) [SIG API Machinery] +- Fixed how image names are compared to the values from `preloadedImagesVerificationAllowlist` in the `kubelet`'s configuration. Previously, the use of "familiar" image names (e.g. "alpine") from a Pod did not properly match the same name in `preloadedImagesVerificationAllowlist` in the `kubelet`'s configuration. ([#137629](https://github.com/kubernetes/kubernetes/pull/137629), [@stlaz](https://github.com/stlaz)) [SIG Auth, Node and Testing] +- Fixed incorrect behavior when using AllocationModeAll with DRA PrioritizedList that prevented the allocator from successfully allocating a claim even when devices were available. ([#137347](https://github.com/kubernetes/kubernetes/pull/137347), [@mortent](https://github.com/mortent)) [SIG Node] +- Fixed informer-gen to generate SetTransform calls that correctly override per-informer transforms. ([#137473](https://github.com/kubernetes/kubernetes/pull/137473), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery and Scheduling] +- Fixed issues in server side apply and client-go's `Extract{TypeName}()` and `Extract{TypeName}From()` functions where empty arrays and maps were incorrectly treated as absent, and atomic elements from associative lists were incorrectly duplicated. ([#135391](https://github.com/kubernetes/kubernetes/pull/135391), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Network, Node, Scheduling and Storage] +- Fixed kubeadm to skip appending the client URL of etcd learner members to `c.Endpoints`, since learners do not serve client traffic. ([#137251](https://github.com/kubernetes/kubernetes/pull/137251), [@pacoxu](https://github.com/pacoxu)) [SIG Cluster Lifecycle] +- Fixed link file ownership of projected serviceAccountToken. ([#137332](https://github.com/kubernetes/kubernetes/pull/137332), [@gavinkflam](https://github.com/gavinkflam)) [SIG Storage] +- Fixed log verbosity for non-error messages in the SELinux warning controller so they are no longer logged at error level. ([#136050](https://github.com/kubernetes/kubernetes/pull/136050), [@ShaanveerS](https://github.com/ShaanveerS)) [SIG Apps and Storage] +- Fixed log verbosity for non-error messages in the storage version migrator so they are no longer logged at error level. ([#136046](https://github.com/kubernetes/kubernetes/pull/136046), [@Tanner-Gladson](https://github.com/Tanner-Gladson)) [SIG API Machinery and Apps] +- Fixed queue hint for certain plugins on change to pods with nominated nodes. ([#135392](https://github.com/kubernetes/kubernetes/pull/135392), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Fixed queue hint for inter-pod anti-affinity in case deleted pod's anti-affinity matched the pending pod, which might have caused delays in scheduling. ([#135325](https://github.com/kubernetes/kubernetes/pull/135325), [@brejman](https://github.com/brejman)) [SIG Scheduling and Testing] +- Fixed queue hint for the `interpodaffinity` plugin in case target pod labels change. ([#135394](https://github.com/kubernetes/kubernetes/pull/135394), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Fixed redundant SSH command executions in the `etcd` failure e2e test. ([#137001](https://github.com/kubernetes/kubernetes/pull/137001), [@kairosci](https://github.com/kairosci)) [SIG API Machinery and Testing] +- Fixed running of DRA e2e tests in air-gapped clusters or with test images in private registries. ([#138318](https://github.com/kubernetes/kubernetes/pull/138318), [@jsafrane](https://github.com/jsafrane)) [SIG Node and Testing] +- Fixed static pod status displaying `Init:0/1` when unable to retrieve init container status from container runtime. ([#131317](https://github.com/kubernetes/kubernetes/pull/131317), [@bitoku](https://github.com/bitoku)) [SIG Node and Testing] +- Fixed the `lastTerminationStatus` to match the `RestartAllContainers` action if the container was restarted this way. ([#136964](https://github.com/kubernetes/kubernetes/pull/136964), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node] +- Fixed the total Pod resources computation. ([#137683](https://github.com/kubernetes/kubernetes/pull/137683), [@ndixita](https://github.com/ndixita)) [SIG CLI and Node] +- Fixed unsupported `Table` object detection to cover all List and Watch operations, preventing the reflector from incorrectly processing resources returned in `Table` format. ([#136937](https://github.com/kubernetes/kubernetes/pull/136937), [@p0lyn0mial](https://github.com/p0lyn0mial)) [SIG API Machinery and Testing] +- Fixed validation error messages for `restartPolicyRules` and `exitCodes.values` to report "items" instead of "bytes". ([#137136](https://github.com/kubernetes/kubernetes/pull/137136), [@kfess](https://github.com/kfess)) [SIG Apps] +- Improved CPU usage in the `nftables` mode of `kube-proxy` when loading very large rulesets. ([#135800](https://github.com/kubernetes/kubernetes/pull/135800), [@danwinship](https://github.com/danwinship)) [SIG Network] +- Improved `DRA scheduling` performance by splitting ResourceSlice entries into shared and onNode categories, reducing Filter stage latency by ~50% in large clusters. ([#136588](https://github.com/kubernetes/kubernetes/pull/136588), [@abel-von](https://github.com/abel-von)) [SIG API Machinery, Apps, Auth, Node and Scheduling] +- Improved a misleading error message when updating `batch.Job`'s `status.startTime`. The error for unsuspended Jobs correctly indicates the field is immutable once set, instead of incorrectly referring to the action as a "removal". ([#136585](https://github.com/kubernetes/kubernetes/pull/136585), [@zhzhuang-zju](https://github.com/zhzhuang-zju)) [SIG Apps] +- Kube-apiserver: Fixed request latency annotation `apiserver.latency.k8s.io/total` in the audit log when request took more than `500ms`. ([#135685](https://github.com/kubernetes/kubernetes/pull/135685), [@chaochn47](https://github.com/chaochn47)) [SIG API Machinery] +- Kube-apiserver: Fixed the log verbosity level in the unsafe delete authorization check that was incorrectly using Error level instead of Info level. ([#136229](https://github.com/kubernetes/kubernetes/pull/136229), [@thc1006](https://github.com/thc1006)) [SIG API Machinery] +- Kube-apiserver: Liveness probes will now fail when the loopback client certificate expires. ([#136477](https://github.com/kubernetes/kubernetes/pull/136477), [@everettraven](https://github.com/everettraven)) [SIG API Machinery and Testing] +- Kube-apiserver: Setting `--audit-log-maxsize=0` now disables audit log rotation (the default remains `100 MB`). To avoid outages due to filling disks with ever-growing audit logs, `--audit-log-maxage` now defaults to `366 (1 year)` and `--audit-log-maxbackup` now defaults to `100`. If retention of all rotated logs is desired, age and count-based pruning can be disabled by explicitly specifying `--audit-log-maxage=0` and `--audit-log-maxbackup=0`. ([#136478](https://github.com/kubernetes/kubernetes/pull/136478), [@kairosci](https://github.com/kairosci)) [SIG API Machinery] +- Kube-controller-manager: Fixed `VolumeAttachment` cleanup when CSI's `attachRequired` switches from true to false. ([#129664](https://github.com/kubernetes/kubernetes/pull/129664), [@hkttty2009](https://github.com/hkttty2009)) [SIG Storage and Testing] +- Kube-proxy now correctly handles the case where a pod IP gets assigned to + a newly-created pod when the pod that previously had that IP has been + terminated but is not yet fully deleted. ([#135593](https://github.com/kubernetes/kubernetes/pull/135593), [@danwinship](https://github.com/danwinship)) [SIG Network] +- Kube-proxy: Fixed nftables mode to work on systems with `nft` `v1.1.3`. ([#137501](https://github.com/kubernetes/kubernetes/pull/137501), [@danwinship](https://github.com/danwinship)) [SIG Network] +- Kubeadm: Changed `kubeadm join` to wait for the etcd learner member to start before promoting it. ([#136014](https://github.com/kubernetes/kubernetes/pull/136014), [@SataQiu](https://github.com/SataQiu)) [SIG Cluster Lifecycle] +- Kubeadm: Fixed a bug where `kubeadm upgrade` failed if the content of the `/var/lib/kubelet/kubeadm-flags.env` file was `KUBELET_KUBEADM_ARGS=""`. ([#136127](https://github.com/kubernetes/kubernetes/pull/136127), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] +- Kubeadm: Ignored EINVAL when unmounting `/var/lib/kubelet` peer mounts during reset. ([#137494](https://github.com/kubernetes/kubernetes/pull/137494), [@fuweid](https://github.com/fuweid)) [SIG Cluster Lifecycle] +- Kubeadm: When applying user-provided overrides using `extraArgs`, the resulting list of arguments is no longer sorted alphanumerically. Only default arguments are sorted, while overrides preserve their order. This allows finer control for flags where order matters, such as `--service-account-issuer` for `kube-apiserver`. ([#135400](https://github.com/kubernetes/kubernetes/pull/135400), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubectl: Fixed `kyaml` output of `kubectl get ... --output-watch-events -o kyaml`. ([#136110](https://github.com/kubernetes/kubernetes/pull/136110), [@liggitt](https://github.com/liggitt)) [SIG CLI] +- Kubectl: Fixed a panic in `kubectl exec` when the terminal size queue delegate is uninitialized. ([#135918](https://github.com/kubernetes/kubernetes/pull/135918), [@MarcosDaNight](https://github.com/MarcosDaNight)) [SIG CLI] +- Kubectl: Fixed a panic when processing pods with nil resource requests but populated container status resources. ([#136534](https://github.com/kubernetes/kubernetes/pull/136534), [@dmaizel](https://github.com/dmaizel)) [SIG CLI] +- Kubectl: Fixed an issue where `kubectl run -i/-it` would miss container output written before the attach connection was established. ([#136010](https://github.com/kubernetes/kubernetes/pull/136010), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Kubelet: Fixed Dynamic Resource Allocation (DRA) to correctly handle multiple `ResourceClaims` even if one is already prepared. ([#135919](https://github.com/kubernetes/kubernetes/pull/135919), [@rogowski-piotr](https://github.com/rogowski-piotr)) [SIG Node and Testing] +- Kubelet: Fixed a data race in pod allocated resources. ([#136226](https://github.com/kubernetes/kubernetes/pull/136226), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] +- Kubelet: Fixed a data race in the container manager. ([#136206](https://github.com/kubernetes/kubernetes/pull/136206), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] +- Kubelet: Fixed a data race in the status manager. ([#136205](https://github.com/kubernetes/kubernetes/pull/136205), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] +- Kubelet: Fixed a data race in the volume manager's `WaitForAllPodsUnmount` that could cause errors to be lost during concurrent pod unmount operations. ([#135794](https://github.com/kubernetes/kubernetes/pull/135794), [@AutuSnow](https://github.com/AutuSnow)) [SIG Node and Storage] +- Kubelet: Fixed a nil pointer dereference when handling pod updates of mirror pods with the `NodeDeclaredFeatures` feature gate enabled. ([#136037](https://github.com/kubernetes/kubernetes/pull/136037), [@pravk03](https://github.com/pravk03)) [SIG Node] +- Kubelet: Fixed logging to properly respect verbosity levels. Previously, some debug/info messages using `V().Error()` would always be printed regardless of the configured log verbosity. ([#136028](https://github.com/kubernetes/kubernetes/pull/136028), [@thc1006](https://github.com/thc1006)) [SIG Node] +- Kubelet: Fixed preservation of DRA `NodeAllocatableResourceClaimStatuses` in PodStatus. ([#138030](https://github.com/kubernetes/kubernetes/pull/138030), [@askervin](https://github.com/askervin)) [SIG Node] +- Kubelet: Fixed reloading of server certificate files when they are changed on disk and kubelet is dialed by IP address instead of DNS/hostname. ([#133654](https://github.com/kubernetes/kubernetes/pull/133654), [@kwohlfahrt](https://github.com/kwohlfahrt)) [SIG API Machinery, Auth, Node and Testing] +- Kubelet: Relisted Pods on-demand for lower latency operations. Guarded by the beta feature gate `PLEGOnDemandRelist`. ([#137362](https://github.com/kubernetes/kubernetes/pull/137362), [@tallclair](https://github.com/tallclair)) [SIG Node] +- Kubelet: The plugin manager now properly handles plugin registration failures by removing failed plugins from the actual state and retrying with exponential backoff (initial delay `500ms`, doubling each failure up to `~2 minutes` maximum) to protect against broken plugins causing denial of service while still allowing recovery from transient failures. ([#133335](https://github.com/kubernetes/kubernetes/pull/133335), [@bart0sh](https://github.com/bart0sh)) [SIG Node, Storage and Testing] +- Kubernetes is now built using Go `v1.26.1`. ([#137474](https://github.com/kubernetes/kubernetes/pull/137474), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release and Testing] +- Optimized kube-proxy conntrack cleanup logic, reducing the time complexity of deleting stale UDP entries. This significantly improves performance when there are many stale connections to clean up. ([#135511](https://github.com/kubernetes/kubernetes/pull/135511), [@aojea](https://github.com/aojea)) [SIG Network] +- Previously, when trying to allocate devices through DRA for a node timed out, scheduling would proceed with another node if any had the necessary resources. This potentially hid that a node was ignored. Worse, if scheduling was slow overall, the Pod was incorrectly moved to "unschedulable" and only retried after a periodic sweep. Timeouts are now errors that are always visible as Pod scheduling failures and get retried with per-Pod exponential backoff. ([#137607](https://github.com/kubernetes/kubernetes/pull/137607), [@0xMH](https://github.com/0xMH)) [SIG Node, Scheduling and Testing] +- Reflected the expected replica count in the output of the `kubectl scale` command. ([#136945](https://github.com/kubernetes/kubernetes/pull/136945), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] +- Removed `GuaranteedQoSPodCPUResize` from node declared features. ([#136759](https://github.com/kubernetes/kubernetes/pull/136759), [@pravk03](https://github.com/pravk03)) [SIG Node and Testing] +- Removed `container_cpu_load_average_10s`, `container_cpu_load_d_average_10s`, and `cpu_tasks_state` metrics from being reported by cadvisor. This is done because the values were always 0, because a flag was not enabled in the kubelet. ([#134981](https://github.com/kubernetes/kubernetes/pull/134981), [@haircommander](https://github.com/haircommander)) [SIG Node and Testing] +- The `k8s.io/client-go/transport` package automatically reloads certificate authority roots from disk when they are supplied via a file path. This functionality is enabled by default and can be disabled via the `ClientsAllowCARotation` feature gate. ([#132922](https://github.com/kubernetes/kubernetes/pull/132922), [@yt2985](https://github.com/yt2985)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Instrumentation, Network, Node, Release, Scheduling and Testing] +- The `k8s.io/client-go/transport` package garbage collects TLS cache entries and client certificate rotation goroutines when a transport is no longer used. This functionality is enabled by default and can be controlled via the `ClientsAllowTLSCacheGC` feature gate. Binaries embedding `k8s.io/client-go` but not wiring the feature gates can disable it by setting the `KUBE_FEATURE_ClientsAllowTLSCacheGC=false` environment variable. When the feature is disabled, the TLS cache can grow indefinitely and client certificate rotation goroutines are leaked. The new `rest_client_transport_cert_rotation_gc_calls_total{}` and `rest_client_transport_cache_gc_calls_total{result: deleted/skipped}` counter metrics can be used with the preexisting `rest_client_transport_*` metrics to help with debugging. ([#136355](https://github.com/kubernetes/kubernetes/pull/136355), [@enj](https://github.com/enj)) [SIG API Machinery, Architecture, Auth, Instrumentation, Node and Testing] +- The `kubelet_pod_start_sli_duration_seconds_bucket` metric matches Pod startup latency SLI/SLO documentation. ([#131950](https://github.com/kubernetes/kubernetes/pull/131950), [@alimaazamat](https://github.com/alimaazamat)) [SIG Node] +- The `kubelet` sets the `PodReadyToStartContainers` condition immediately after sandbox creation rather than after image pull, reducing the time to condition True. ([#134660](https://github.com/kubernetes/kubernetes/pull/134660), [@Priyankasaggu11929](https://github.com/Priyankasaggu11929)) [SIG Apps, Node and Testing] +- The garbage collector correctly handles objects deleted externally, preventing spurious error logs. ([#136817](https://github.com/kubernetes/kubernetes/pull/136817), [@kairosci](https://github.com/kairosci)) [SIG API Machinery, Apps and Testing] +- Updated `NodeResourcesBalancedAllocation` scoring algorithm to align with the documentation. The score will now take into consideration both balance with and without the requested pod. Previous algorithm only considered balance with the requested pod. This can change the scheduling decisions in some cases. ([#135573](https://github.com/kubernetes/kubernetes/pull/135573), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Updated the CDI spec for discoverable metadata to `v0.5.0`. ([#138035](https://github.com/kubernetes/kubernetes/pull/138035), [@alaypatel07](https://github.com/alaypatel07)) [SIG Node] +- Updated the pause image to `v3.10.2`. ([#138199](https://github.com/kubernetes/kubernetes/pull/138199), [@neolit123](https://github.com/neolit123)) [SIG CLI, Cloud Provider, Cluster Lifecycle, Scheduling and Testing] +- Validation messages for a Pod's `status.resourceClaimStatuses[].resourceClaimName` refer correctly to the `resourceClaimName` field instead of the `name` field. ([#137321](https://github.com/kubernetes/kubernetes/pull/137321), [@nojnhuh](https://github.com/nojnhuh)) [SIG Apps] +- Writes to the ServiceCIDR main resource ignore status field changes in the request, consistent with all other Kubernetes APIs. The `ServiceCIDRStatusFieldWiping` feature gate can be disabled to restore the previous behavior; it will be locked to enabled in a future release. ([#137715](https://github.com/kubernetes/kubernetes/pull/137715), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Network and Testing] + +### Other (Cleanup or Flake) + +- Added `audit-id` to the "Starting watch" log line. ([#136084](https://github.com/kubernetes/kubernetes/pull/136084), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Added explicit logging when `WatchList` requests complete their initial listing phase. ([#136085](https://github.com/kubernetes/kubernetes/pull/136085), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Added group, version, and resource labels to the existing alpha metric `apiserver_rerouted_request_total`. ([#137063](https://github.com/kubernetes/kubernetes/pull/137063), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Added missing tests for client-go metrics. ([#136052](https://github.com/kubernetes/kubernetes/pull/136052), [@sreeram-venkitesh](https://github.com/sreeram-venkitesh)) [SIG Architecture and Instrumentation] +- Client-go: Fake client-go (i.e., anything using `k8s.io/client-go/testing`) now supports separate List+Watch calls with checking of `ResourceVersion` in the Watch call. This closes a race condition where creating an object directly after an informer cache has synced (List call completed) and before the Watch call completed would cause that object to not be sent to the informer. A visible side-effect of adding that support is that List metadata contains a `ResourceVersion` (starting at 1 for the empty set, incremented by one for each add/update) and that Watch may return objects where it previously did not. + Note that this List+Watch is not to be confused with the `ListWatch` feature, which uses a single call. That feature is still not supported by fake client-go. ([#136143](https://github.com/kubernetes/kubernetes/pull/136143), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth and CLI] +- Client-go: Fixed an issue where Reflector could get confused about the resource version it should use to restart a watch while receiving synthetic ADDED events at the beginning of a watch from `resourceVersion` 0 or empty string (`""`). ([#136583](https://github.com/kubernetes/kubernetes/pull/136583), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Deprecated the `SeparateCacheWatchRPC` feature gate. It is now locked to its default value (false) and can no longer be overridden. The feature gate will be removed in a future release. ([#135808](https://github.com/kubernetes/kubernetes/pull/135808), [@tico88612](https://github.com/tico88612)) [SIG API Machinery] +- Enabled YAML support for `/statusz` and `/flagz` endpoints. ([#135309](https://github.com/kubernetes/kubernetes/pull/135309), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation and Testing] +- Fixed DRA device taint eviction controller to avoid confusing intermediate status messages by delaying status updates after pod eviction until the informer cache is updated. ([#135611](https://github.com/kubernetes/kubernetes/pull/135611), [@Karthik-K-N](https://github.com/Karthik-K-N)) [SIG Apps and Scheduling] +- For performance reasons, `kubectl describe` defaults to showing related events only when describing a single object. Passing `--show-events` explicitly when describing multiple objects or fuzzy matching on prefix still shows related events if desired. ([#137145](https://github.com/kubernetes/kubernetes/pull/137145), [@mark-liu](https://github.com/mark-liu)) [SIG CLI] +- Improved stability by sorting containers by create time and ID in `kubeGenericRuntimeManager.GetPods()` and `GetPod()`. ([#137566](https://github.com/kubernetes/kubernetes/pull/137566), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG Node] +- Kubeadm: Removed the cleanup of the `--pod-infra-container-image` kubelet flag from `/var/lib/kubelet/kubeadm-flags.env` on upgrade. This cleanup was necessary when upgrading to `v1.35`. ([#135807](https://github.com/kubernetes/kubernetes/pull/135807), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] +- Kubeadm: Removed usage of the deprecated `etcd` flags `--experimental-initial-corrupt-check` and `--experimental-watch-progress-notify-interval` if the `etcd` version is < `v3.6.0`. In this version of kubeadm, `etcd` < `v3.6.0` is no longer supported in terms of the Kubernetes / `etcd` version mapping. These deprecated flags have been replaced by `--feature-gates=InitialCorruptCheck=true` and `--watch-progress-notify-interval`. ([#135701](https://github.com/kubernetes/kubernetes/pull/135701), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubelet: Fixed admission to correctly handle DRA-backed extended resources, allowing Pods to be admitted even when these resources are not present in the node's allocatable capacity. ([#135725](https://github.com/kubernetes/kubernetes/pull/135725), [@bart0sh](https://github.com/bart0sh)) [SIG Node, Scheduling and Testing] +- Kubernetes is now built using Go `v1.26.2`. ([#138261](https://github.com/kubernetes/kubernetes/pull/138261), [@dims](https://github.com/dims)) [SIG Architecture and Testing] +- Locked the `DisableNodeKubeProxyVersion` feature gate to enabled by default. ([#136673](https://github.com/kubernetes/kubernetes/pull/136673), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG CLI and Network] +- Promoted HPA metrics `reconciliations_total`, `reconciliation_duration_seconds`, `metric_computation_total`, and `metric_computation_duration_seconds` to beta. ([#136178](https://github.com/kubernetes/kubernetes/pull/136178), [@omerap12](https://github.com/omerap12)) [SIG Apps, Autoscaling and Instrumentation] +- Promoted `InOrderInformers` to GA via the usage of `RealFIFO`. This means that `DeltaFIFO` will gradually be deprecated in favor of `RealFIFO` in internal implementations. ([#136601](https://github.com/kubernetes/kubernetes/pull/136601), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Promoted `SELinuxChangePolicy` and `SELinuxMountReadWriteOncePod` to GA; they are enabled unconditionally. ([#136912](https://github.com/kubernetes/kubernetes/pull/136912), [@dfajmon](https://github.com/dfajmon)) [SIG Apps, Storage and Testing] +- Reduced get PV request from KCM pv-controller for CSI volumes. ([#134290](https://github.com/kubernetes/kubernetes/pull/134290), [@huww98](https://github.com/huww98)) [SIG Apps and Storage] +- Removed `v1alpha1` `WebhookAdmissionConfiguration`. It was deprecated in `v1.17` in favor of `apiserver.config.k8s.io/v1`. ([#137379](https://github.com/kubernetes/kubernetes/pull/137379), [@aramase](https://github.com/aramase)) [SIG API Machinery and Testing] +- Removed event listing behavior when describing a deleted Pod from file using `kubectl describe -f`, ensuring consistent NotFound error handling across all resource types. ([#135281](https://github.com/kubernetes/kubernetes/pull/135281), [@scaliby](https://github.com/scaliby)) [SIG CLI] +- Removed misleading `SuggestFor` entries from `kubectl wait` so that it is no longer suggested when users type `kubectl list` or `kubectl ps`. ([#137266](https://github.com/kubernetes/kubernetes/pull/137266), [@kfess](https://github.com/kfess)) [SIG CLI and Testing] +- Removed the `WatchFromStorageWithoutResourceVersion` feature gate in `v1.36`. ([#136066](https://github.com/kubernetes/kubernetes/pull/136066), [@serathius](https://github.com/serathius)) [SIG API Machinery] +- Removed the cri-client helper method `NewLogOptions`; `LogOptions` must be constructed directly. This eliminates the unwanted dependency from cri-client to apimachinery. ([#137827](https://github.com/kubernetes/kubernetes/pull/137827), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Node and Release] +- Removed the dead `--bounding-dirs` flag and `BoundingDirs` field from deepcopy-gen. ([#137348](https://github.com/kubernetes/kubernetes/pull/137348), [@Jefftree](https://github.com/Jefftree)) [SIG API Machinery] +- Removed the generally available feature gate `HonorPVReclaimPolicy`, which was locked and enabled since `v1.33`. ([#135335](https://github.com/kubernetes/kubernetes/pull/135335), [@carlory](https://github.com/carlory)) [SIG Apps and Storage] +- Renamed `PodGroupInfo` to `PodGroupState`, which may break custom scheduler plugins that use `Handle.WorkloadManager`. ([#136344](https://github.com/kubernetes/kubernetes/pull/136344), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Reverted graduation of `maxLength` property. ([#137274](https://github.com/kubernetes/kubernetes/pull/137274), [@lalitc375](https://github.com/lalitc375)) [SIG API Machinery] +- The "Failed to update lease optimistically" log message may not be shown to users anymore, depending on the log level they have set. ([#137753](https://github.com/kubernetes/kubernetes/pull/137753), [@adamkasztenny](https://github.com/adamkasztenny)) [SIG API Machinery] +- The `GetPCIeRootAttributeByPCIBusID` helper accepts a `fs.ReadLinkFS` optional argument to be filesystem-independent. ([#137220](https://github.com/kubernetes/kubernetes/pull/137220), [@ffromani](https://github.com/ffromani)) [SIG Node] +- The cri-api client accepts a context instead of a logger on initialization. ([#137248](https://github.com/kubernetes/kubernetes/pull/137248), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Cluster Lifecycle, Node and Testing] +- Truncated the watch cache RV metric to 15 digits to ensure precision. ([#137615](https://github.com/kubernetes/kubernetes/pull/137615), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Instrumentation] +- Updated `cri-tools` to `v1.35.0`. ([#135694](https://github.com/kubernetes/kubernetes/pull/135694), [@saschagrunert](https://github.com/saschagrunert)) [SIG Cloud Provider and Node] +- Updated etcd client library to `v3.6.6`. ([#135331](https://github.com/kubernetes/kubernetes/pull/135331), [@yashsingh74](https://github.com/yashsingh74)) [SIG API Machinery, Auth, Cloud Provider, Etcd, Node and Scheduling] +- Updated etcd client library to `v3.6.7`. ([#136407](https://github.com/kubernetes/kubernetes/pull/136407), [@ivanvc](https://github.com/ivanvc)) [SIG API Machinery, Auth, Cloud Provider, Node and Scheduling] +- Updated etcd images to `v3.6.8`. ([#137107](https://github.com/kubernetes/kubernetes/pull/137107), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] +- Updated kube-dns to `v1.26.7`. ([#134394](https://github.com/kubernetes/kubernetes/pull/134394), [@toredash](https://github.com/toredash)) [SIG Cloud Provider] +- Updated kustomize dependency to `v5.8.1`. ([#136892](https://github.com/kubernetes/kubernetes/pull/136892), [@koba1t](https://github.com/koba1t)) [SIG Architecture and CLI] + +## Dependencies + +### Added +- buf.build/go/protovalidate: [v0.12.0](https://github.com/bufbuild/protovalidate-go/commit/8fa707802a9ac7e6fa9860b23c0b09b76344cf29) +- github.com/cenkalti/backoff/v5: [v5.0.3](https://github.com/cenkalti/backoff/commit/7cad66a637c4ffff09d0795608116ddcc7eb1769) +- github.com/moby/moby/api: [v1.52.0](https://github.com/moby/moby/commit/1408c9ca4f4f0717b2d885fc87ae0ff000a91c40) +- github.com/moby/moby/client: [v0.2.1](https://github.com/moby/moby/commit/b6f067c0cfe38401c7fa7678efbbe383ebe134fb) +- go.opentelemetry.io/otel/exporters/stdout/stdouttrace: [v1.40.0](https://github.com/open-telemetry/opentelemetry-go/commit/a3a5317c5caed1656fb5b301b66dfeb3c4c944e0) +- gonum.org/v1/gonum: [v0.16.0](https://github.com/gonum/gonum/commit/7826ba4358a76646a8bd1f852201c4fed2c81c38) + +### Changed +- buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go: 63bb56e → 8976f5b +- cel.dev/expr: [v0.24.0 → v0.25.1](https://github.com/google/cel-spec/compare/v0.24.0...v0.25.1) +- cloud.google.com/go/compute/metadata: [v0.7.0 → v0.9.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.7.0...compute/metadata/v0.9.0) +- cyphar.com/go-pathrs: [v0.2.1 → v0.2.2](https://github.com/cyphar/libpathrs.git/compare/go-pathrs/v0.2.1...go-pathrs/v0.2.2) +- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp: [v1.26.0 → v1.30.0](https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/compare/v1.26.0...v1.30.0) +- github.com/Microsoft/hnslib: [v0.1.1 → v0.1.2](https://github.com/Microsoft/hnslib/compare/v0.1.1...v0.1.2) +- github.com/alecthomas/units: [b94a6e3 → 0f3dac3](https://github.com/alecthomas/units/compare/b94a6e3...0f3dac3) +- github.com/cncf/xds/go: [2f00578 → ee656c7](https://github.com/cncf/xds/compare/2f005788dc42b92dee41c8ad934450dc4746f027...ee656c7534f5d7dc23d44dd611689568f72017a6) +- github.com/containerd/containerd/api: [v1.9.0 → v1.10.0](https://github.com/containerd/containerd/compare/api/v1.9.0...api/v1.10.0) +- github.com/coredns/corefile-migration: [v1.0.29 → v1.0.31](https://github.com/coredns/corefile-migration/compare/v1.0.29...v1.0.31) +- github.com/coreos/go-oidc: [v2.3.0 → v2.5.0](https://github.com/coreos/go-oidc/compare/v2.3.0...v2.5.0) +- github.com/coreos/go-systemd/v22: [v22.5.0 → v22.7.0](https://github.com/coreos/go-systemd/compare/v22.5.0...v22.7.0) +- github.com/cyphar/filepath-securejoin: [v0.6.0 → v0.6.1](https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...v0.6.1) +- github.com/davecgh/go-spew: [v1.1.1 → d8f796a](https://github.com/davecgh/go-spew/compare/v1.1.1...d8f796a) +- github.com/docker/go-connections: [v0.5.0 → v0.6.0](https://github.com/docker/go-connections/compare/v0.5.0...v0.6.0) +- github.com/emicklei/go-restful/v3: [v3.12.2 → v3.13.0](https://github.com/emicklei/go-restful/compare/v3.12.2...v3.13.0) +- github.com/envoyproxy/go-control-plane: [v0.13.4 → v0.14.0](https://github.com/envoyproxy/go-control-plane/compare/v0.13.4...v0.14.0) +- github.com/envoyproxy/go-control-plane/envoy: [v1.32.4 → v1.36.0](https://github.com/envoyproxy/go-control-plane/compare/envoy/v1.32.4...envoy/v1.36.0) +- github.com/envoyproxy/protoc-gen-validate: [v1.2.1 → v1.3.0](https://github.com/envoyproxy/protoc-gen-validate/compare/v1.2.1...v1.3.0) +- github.com/go-jose/go-jose/v4: [v4.0.4 → v4.1.3](https://github.com/go-jose/go-jose/compare/v4.0.4...v4.1.3) +- github.com/godbus/dbus/v5: [v5.1.0 → v5.2.2](https://github.com/godbus/dbus/compare/v5.1.0...v5.2.2) +- github.com/golang-jwt/jwt/v5: [v5.2.2 → v5.3.0](https://github.com/golang-jwt/jwt/compare/v5.2.2...v5.3.0) +- github.com/golang/glog: [v1.2.4 → v1.2.5](https://github.com/golang/glog/compare/v1.2.4...v1.2.5) +- github.com/google/cadvisor: [v0.53.0 → v0.56.2](https://github.com/google/cadvisor/compare/v0.53.0...v0.56.2) +- github.com/google/pprof: [27863c8 → 294ebfa](https://github.com/google/pprof/compare/27863c87afa6df68fb88a574f81b47d6fb7fbf29...294ebfa9ad836ed3d00d43d54ea599339e403110) +- github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus: [v1.0.1 → v1.1.0](https://github.com/grpc-ecosystem/go-grpc-middleware/compare/providers/prometheus/v1.0.1...providers/prometheus/v1.1.0) +- github.com/grpc-ecosystem/go-grpc-middleware/v2: [v2.3.0 → v2.3.3](https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v2.3.0...v2.3.3) +- github.com/grpc-ecosystem/grpc-gateway/v2: [v2.26.3 → v2.27.7](https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.26.3...v2.27.7) +- github.com/ianlancetaylor/demangle: [bd984b5 → f615e6b](https://github.com/ianlancetaylor/demangle/compare/bd984b5ce465cae93d894caa85838021209b9a57...f615e6bd150ba7c2231ba4be6104d64396a3ea50) +- github.com/moby/spdystream: [v0.5.0 → v0.5.1](https://github.com/moby/spdystream/compare/v0.5.0...v0.5.1) +- github.com/onsi/ginkgo/v2: [v2.27.2 → v2.28.1](https://github.com/onsi/ginkgo/compare/v2.27.2...v2.28.1) +- github.com/onsi/gomega: [v1.38.2 → v1.39.1](https://github.com/onsi/gomega/compare/v1.38.2...v1.39.1) +- github.com/opencontainers/cgroups: [v0.0.3 → v0.0.6](https://github.com/opencontainers/cgroups/compare/v0.0.3...v0.0.6) +- github.com/opencontainers/runc: [v1.3.0 → v1.4.0](https://github.com/opencontainers/runc/compare/v1.3.0...v1.4.0) +- github.com/opencontainers/runtime-spec: [v1.2.1 → v1.3.0](https://github.com/opencontainers/runtime-spec/compare/v1.2.1...v1.3.0) +- github.com/opencontainers/selinux: [v1.13.0 → v1.13.1](https://github.com/opencontainers/selinux/compare/v1.13.0...v1.13.1) +- github.com/pmezard/go-difflib: [v1.0.0 → 5d4384e](https://github.com/pmezard/go-difflib/compare/v1.0.0...5d4384e) +- github.com/prometheus/common: [v0.66.1 → v0.67.5](https://github.com/prometheus/common/compare/v0.66.1...v0.67.5) +- github.com/prometheus/procfs: [v0.16.1 → v0.19.2](https://github.com/prometheus/procfs/compare/v0.16.1...v0.19.2) +- github.com/sergi/go-diff: [v1.2.0 → v1.4.0](https://github.com/sergi/go-diff/compare/v1.2.0...v1.4.0) +- github.com/spf13/cobra: [v1.10.0 → v1.10.2](https://github.com/spf13/cobra/compare/v1.10.0...v1.10.2) +- github.com/spiffe/go-spiffe/v2: [v2.5.0 → v2.6.0](https://github.com/spiffe/go-spiffe/compare/v2.5.0...v2.6.0) +- go.etcd.io/etcd/api/v3: [v3.6.5 → v3.6.8](https://github.com/etcd-io/etcd/compare/api/v3.6.5...api/v3.6.8) +- go.etcd.io/etcd/client/pkg/v3: [v3.6.5 → v3.6.8](https://github.com/etcd-io/etcd/compare/client/pkg/v3.6.5...client/pkg/v3.6.8) +- go.etcd.io/etcd/client/v3: [v3.6.5 → v3.6.8](https://github.com/etcd-io/etcd/compare/client/v3.6.5...client/v3.6.8) +- go.etcd.io/etcd/pkg/v3: [v3.6.5 → v3.6.8](https://github.com/etcd-io/etcd/compare/pkg/v3.6.5...pkg/v3.6.8) +- go.etcd.io/etcd/server/v3: [v3.6.5 → v3.6.8](https://github.com/etcd-io/etcd/compare/server/v3.6.5...server/v3.6.8) +- go.opentelemetry.io/auto/sdk: [v1.1.0 → v1.2.1](https://github.com/open-telemetry/opentelemetry-go-instrumentation/compare/sdk/v1.1.0...sdk/v1.2.1) +- go.opentelemetry.io/contrib/detectors/gcp: [v1.34.0 → v1.39.0](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/detectors/gcp/v1.34.0...detectors/gcp/v1.39.0) +- go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful: [v0.44.0 → v0.65.0](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/instrumentation/github.com/emicklei/go-restful/otelrestful/v0.44.0...instrumentation/github.com/emicklei/go-restful/otelrestful/v0.65.0) +- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc: [v0.60.0 → v0.65.0](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/instrumentation/google.golang.org/grpc/otelgrpc/v0.60.0...instrumentation/google.golang.org/grpc/otelgrpc/v0.65.0) +- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp: [v0.61.0 → v0.65.0](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/instrumentation/net/http/otelhttp/v0.61.0...instrumentation/net/http/otelhttp/v0.65.0) +- go.opentelemetry.io/contrib/propagators/b3: [v1.19.0 → v1.40.0](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/propagators/b3/v1.19.0...propagators/b3/v1.40.0) +- go.opentelemetry.io/otel: [v1.36.0 → v1.41.0](https://github.com/open-telemetry/opentelemetry-go/compare/a85ae98dcedc0761078518a715dea53e519b4846...v1.41.0) +- go.opentelemetry.io/otel/exporters/otlp/otlptrace: [v1.34.0 → v1.40.0](https://github.com/open-telemetry/opentelemetry-go/compare/exporters/otlp/otlptrace/v1.34.0...exporters/otlp/otlptrace/v1.40.0) +- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc: [v1.34.0 → v1.40.0](https://github.com/open-telemetry/opentelemetry-go/compare/exporters/otlp/otlptrace/otlptracegrpc/v1.34.0...exporters/otlp/otlptrace/otlptracegrpc/v1.40.0) +- go.opentelemetry.io/otel/metric: [v1.36.0 → v1.41.0](https://github.com/open-telemetry/opentelemetry-go/compare/metric/v1.36.0...metric/v1.41.0) +- go.opentelemetry.io/otel/sdk: [v1.36.0 → v1.40.0](https://github.com/open-telemetry/opentelemetry-go/compare/sdk/v1.36.0...sdk/v1.40.0) +- go.opentelemetry.io/otel/sdk/metric: [v1.36.0 → v1.40.0](https://github.com/open-telemetry/opentelemetry-go/compare/sdk/metric/v1.36.0...sdk/metric/v1.40.0) +- go.opentelemetry.io/otel/trace: [v1.36.0 → v1.41.0](https://github.com/open-telemetry/opentelemetry-go/compare/trace/v1.36.0...trace/v1.41.0) +- go.opentelemetry.io/proto/otlp: [v1.5.0 → v1.9.0](https://github.com/open-telemetry/opentelemetry-proto-go/compare/otlp/v1.5.0...otlp/v1.9.0) +- go.uber.org/zap: [v1.27.0 → v1.27.1](https://github.com/uber-go/zap/compare/v1.27.0...v1.27.1) +- golang.org/x/crypto: [v0.45.0 → v0.47.0](https://go.googlesource.com/crypto/+/4e0068c0098be10d7025c99ab7c50ce454c1f0f9^1..506e022208b864bc3c9c4a416fe56be75d10ad24/) +- golang.org/x/exp: [8a7402a → 944ab1f](https://go.googlesource.com/exp/+/8a7402abbf56ed11a2540c1d8beb569bd29e22d1^1..944ab1f22d936eefb8f6260ecd2053101d8d7b2a/) +- golang.org/x/mod: [v0.29.0 → v0.32.0](https://go.googlesource.com/mod/+/bba3e065a67271df90253c78c98f2cea7f572948^1..4c04067938546e62fc0572259a68a6912726bcdd/) +- golang.org/x/net: [v0.47.0 → v0.49.0](https://go.googlesource.com/net/+/9a296438e54dff851a45667aa645a97003b44db5^1..d977772e17ccaa1903b2af736f6405ab3a9f05cc/) +- golang.org/x/oauth2: [v0.30.0 → v0.34.0](https://go.googlesource.com/oauth2/+/cf1431934151b3a93e0b3286eb6798ca08ea3770^1..acc38155b7f6f36aefcb58faff6f36d314dd915c/) +- golang.org/x/sync: [v0.18.0 → v0.19.0](https://go.googlesource.com/sync/+/1966f539bbd7664efd5bb7462ae94d9db67f4502^1..2a180e22fddcc336475e72aa950be958c1b68d33/) +- golang.org/x/sys: [v0.38.0 → v0.40.0](https://go.googlesource.com/sys/+/15129aafc3056028aa2694528ac20373f8cd34e4^1..2f442297556c884f9b52fc6ef7280083f4d65023/) +- golang.org/x/telemetry: [078029d → bd525da](https://go.googlesource.com/telemetry/+/078029d740a8681fc932f4e25e60ff1a037f5eca^1..bd525da824e2505db9e8ac44025316bf6f43a6f6/) +- golang.org/x/term: [v0.37.0 → v0.39.0](https://go.googlesource.com/term/+/1231d5465be98a7c5f01140358c142d365d4fbb6^1..a7e5b0437ffa3159709172efbe396bc546550e23/) +- golang.org/x/text: [v0.31.0 → v0.33.0](https://go.googlesource.com/text/+/e7ff6b3572e1a83c072ef150c985f86603986e1b^1..536231a9abc69feaab8d726b5ec75ee8d3620829/) +- golang.org/x/time: [v0.9.0 → v0.14.0](https://go.googlesource.com/time/+/1ce61fe87e0e5dd90752d2b6c5972f9b6918e77c^1..2b4e43900c03fd6b77109b7b2b6d77583f48bc1c/) +- golang.org/x/tools: [v0.38.0 → v0.41.0](https://go.googlesource.com/tools/+/a22b5e8a9b8d2234e1e960ec2473e4011f012a6b^1..2ad2b30edf98d0e3b67a7b3e8f6d1d6e41c963c3/) +- google.golang.org/genproto/googleapis/api: [a0af3ef → 8636f87](https://github.com/googleapis/go-genproto/compare/a0af3efb3deb0aa5253d43c55b96e303c64cc06b...8636f8732409467ddc8453f81f4429397739bb17) +- google.golang.org/genproto/googleapis/rpc: [200df99 → 8636f87](https://github.com/googleapis/go-genproto/compare/200df99c418ae1eac9aa6d0268db9c22c1715c0c...8636f8732409467ddc8453f81f4429397739bb17) +- google.golang.org/grpc: [v1.72.2 → v1.79.3](https://github.com/grpc/grpc-go/compare/v1.72.2...v1.79.3) +- google.golang.org/protobuf: [v1.36.8 → f2248ac](https://go.googlesource.com/protobuf/+/0833cf304e6344e895e819f769afa28107fe8892^1..f2248ac996afc39b3df0777cdcc269f6ade50b07/) +- k8s.io/klog/v2: [v2.130.1 → v2.140.0](https://github.com/kubernetes/klog/compare/v2.130.1...main) +- k8s.io/kube-openapi: [589584f → 43fb72c](https://github.com/kubernetes/kube-openapi/compare/589584f1c912f4367fe8954f649a59a98b912da5...43fb72c5454a03ed83388cf20c070499ee359af8) +- k8s.io/utils: [bc988d5 → b8788ab](https://github.com/kubernetes/utils/compare/bc988d571ff40eb17793769e9c1b71ecf8ee9c0f...b8788abfbbc27cab6c8732274b5c2ae213868854) +- sigs.k8s.io/apiserver-network-proxy/konnectivity-client: [v0.31.2 → v0.34.0](https://github.com/kubernetes-sigs/apiserver-network-proxy/compare/konnectivity-client/v0.31.2...konnectivity-client/v0.34.0) +- sigs.k8s.io/knftables: [v0.0.17 → v0.0.21](https://github.com/kubernetes-sigs/knftables/compare/v0.0.17...v0.0.21) +- sigs.k8s.io/kustomize/api: [v0.20.1 → v0.21.1](https://github.com/kubernetes-sigs/kustomize/compare/api/v0.20.1...api/v0.21.1) +- sigs.k8s.io/kustomize/cmd/config: [v0.20.1 → v0.21.1](https://github.com/kubernetes-sigs/kustomize/compare/cmd/config/v0.20.1...cmd/config/v0.21.1) +- sigs.k8s.io/kustomize/kustomize/v5: [v5.7.1 → v5.8.1](https://github.com/kubernetes-sigs/kustomize/compare/kustomize/v5.7.1...kustomize/v5.8.1) +- sigs.k8s.io/kustomize/kyaml: [v0.20.1 → v0.21.1](https://github.com/kubernetes-sigs/kustomize/compare/kyaml/v0.20.1...kyaml/v0.21.1) +- sigs.k8s.io/structured-merge-diff/v6: [v6.3.0 → v6.3.2](https://github.com/kubernetes-sigs/structured-merge-diff/compare/v6.3.0...v6.3.2) + +### Removed +- github.com/armon/circbuf: [5111143](https://github.com/armon/circbuf/tree/5111143) +- github.com/bufbuild/protovalidate-go: [v0.9.1](https://github.com/bufbuild/protovalidate-go/commit/107b51bb93ef18ea769ece164b12e844315ae6b1) +- github.com/docker/docker: [v28.2.2](https://github.com/docker/docker/commit/45873be4ae3f5488c9498b3d9f17deaddaf609f4) +- github.com/gregjones/httpcache: [901d907](https://github.com/gregjones/httpcache/tree/901d907) +- github.com/grpc-ecosystem/go-grpc-prometheus: [v1.2.0](https://github.com/grpc-ecosystem/go-grpc-prometheus/tree/v1.2.0) +- github.com/karrick/godirwalk: [v1.17.0](https://github.com/karrick/godirwalk/tree/v1.17.0) +- github.com/libopenstorage/openstorage: [v1.0.0](https://github.com/libopenstorage/openstorage/tree/v1.0.0) +- github.com/moby/sys/atomicwriter: [v0.1.0](https://github.com/moby/sys/commit/4a75548218baa36bdbaaed1371a3e8a9cdfcffa0) +- github.com/mohae/deepcopy: [c48cc78](https://github.com/mohae/deepcopy/tree/c48cc78) +- github.com/morikuni/aec: [v1.0.0](https://github.com/morikuni/aec/tree/v1.0.0) +- github.com/mrunalp/fileutils: [v0.5.1](https://github.com/mrunalp/fileutils/commit/7363e975f9cfb558be601bece0df81714c3c9084) +- github.com/pkg/errors: [v0.9.1](https://github.com/pkg/errors/tree/v0.9.1) +- github.com/zeebo/errs: [v1.4.0](https://github.com/zeebo/errs/commit/13450ab50383c5857164d564544fa0bef3ff689d) +- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp: [v1.27.0](https://github.com/open-telemetry/opentelemetry-go/commit/5661ff0ded32cf1b83f1147dae96ca403c198504) +- go.uber.org/automaxprocs: [v1.6.0](https://github.com/uber-go/automaxprocs/commit/1ea14c35ce47a73089b824e504d1c92eeb61a5a6) +- gotest.tools/v3: v3.0.2 + + + +# v1.36.0-rc.1 + + +## Downloads for v1.36.0-rc.1 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes.tar.gz) | c134ae3f6d7b8276d2f2020ea0890a70f1ce136ee9460f4b12fa6719f6f4e8909fe9fbf85a52b4b87f6d2032d7044d57f1928f48060f251dbba644deb6649a1f +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-src.tar.gz) | 627e4211a1578935ad9635c2400f302ac2a08e56d8cdb0427dd2fa016f4a45f2d58aeaa0c1b089deb9abb2c3036d29b6ee919ada57a1606fabef8c513c618717 + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-darwin-amd64.tar.gz) | 876b2a5db62a21aa525e3b9766c9ad5a89b0b718fc36f35a0aa976de8d0890e482bf1cc9e07b805544d8d20aff6ef3869b5492afb877cac87f35cea169cf817f +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-darwin-arm64.tar.gz) | bd9f7e60992fd4508ae74722cfe7a7aa8e9805c46ed0dec6b4347c0c278eb97e672c4fb894007af9c59673514a9b0645e17196c492edaafb072381697ea45ac9 +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-linux-386.tar.gz) | cef8f4aa41dd953b9a6065b1e761c59b20d35cdf13a95a2bcffc6ae9fac77c14b833ee054429ade778c8032dce372ff4234c69cb463b072a4494b628e25400a1 +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-linux-amd64.tar.gz) | 0c23410d581b626d4f36dd291101ce81b64d15da95692693b16b05ffffb42b0f77c8e85b32631fa19e3c9d2109e267d775740f0e50dc29694093ef32be3bada1 +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-linux-arm.tar.gz) | 02b27b095c060baa4034588353c4f777eb4d0defc6a536ebe62e9a72a93987ecb5cbbe9181dc00a5c6fbeac7232e7770cf04655a46376b9b63bb5229eeeb63fd +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-linux-arm64.tar.gz) | 6777fc534be81b04deaf37051ff82359e894e71c2ec97656850ac1973375f1ec2d61e538e574b9d95d1548fa4c2e9e584fd0225f79930caba9f7d981ae0f8026 +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-linux-ppc64le.tar.gz) | 661dc1a997a9eae839b9f7835d062115b327590dd241ceb72bf32fe56e6bbc82614ff7b100f72056a694e2f4fd6a86d4396fc65a43858aafabbea825f925f274 +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-linux-s390x.tar.gz) | b9b1c9e6cc9b6d1778ab5ac290c5541dfd57877d8fed80f6a7a3b01108cb8296e038908fb881801d6cd5204906ee68672cf6ea2d4766e64034f5615dc3fd116b +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-windows-386.tar.gz) | 9f0c3d7f7057b0c61075db496041b92808e9039f9fbbbf4e8acb90c2e535700d1bdc11bdd78effd3a27b9b2593998a40016e7bd806311ff72256cc3d5e4c759c +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-windows-amd64.tar.gz) | 9d62b1d9e3c0c869907fb10cb850cd4ef855570f3991c08e779369fe4c732e06c36dafa2bfea0589bac4852308a74f9f4f4de495d30ccf80ea4b886538a4541f +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-client-windows-arm64.tar.gz) | e4f8a2dc09c59132cca3ffe96c9da2d2e862343382fbcb64cbb77b61e0ba02898b1f78b546a833c081307d0959190b39991b8b898313bb7ee3a7bcce65aea8de + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-server-linux-amd64.tar.gz) | 2a746d8485428451ea98b4a6bc87caff3d5a16380081b5dca8e4a945b398c415770b2ade78e9c356d4b1d8289b8eb04d155fd75c8a22bfd8e4a8eb8b68243174 +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-server-linux-arm64.tar.gz) | 0f8e304d016ec0b976a05df060ea6e6c750b2c0d0990f5572a2e2b285015a08aa4892ba1046822d34d5620f84b73d655ca32e054eb05d78a47c13f567d7a643f +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-server-linux-ppc64le.tar.gz) | d0b58696236107a80cd2d907316ce771d626dc7c3427b8d7f61191d506f68aa7bd0cd47aa2708f6e229bf8722a834bf4af9b1ed886e972e466bd306dba3f259f +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-server-linux-s390x.tar.gz) | 4bced69b2ff0b8231b9085729d8da1f59e3311c0013eee67ae742da79c9d8cff0921fca1f4fb08d9ed2d2d78b059b026ebddac7f36471b244d9dfdd952018c72 + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-node-linux-amd64.tar.gz) | 5adae9badcb1bc7dda5bc75a7fc41ead727e62a9744c5545a4c43a8e2e77127720f83212ad0b7a244c7c7f9b82abb2e2e3c2c8e18a4739320591ec1c388eb7d4 +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-node-linux-arm64.tar.gz) | cba5683285a0e74830d4334e35b1fa6495b553a03917af40c795157b57a96a7165e502b3dfac37d8cd619619c871eac1055c501615c2e762fbb7c1d9a824dae0 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-node-linux-ppc64le.tar.gz) | 1352a58e7e1aff9809cb1f875058ed9a1f5b80a8aeaa4c5b38cc91316c1fe0c3846c3868d3b8c70912a3a736d433197ca76a6a656f4b58ecc34ef8a773843042 +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-node-linux-s390x.tar.gz) | be83680c9c73bf2c9c2f90d27fe7efd9eb26074ebf4004391bb0650125c354bcc71c42b5b964ed1d725d34a7be60d3a676d9fac5efedd6fc157653a2c6cd32b5 +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.1/kubernetes-node-windows-amd64.tar.gz) | 45fdaa8d3020589e197ea37532c2428fecb71b0c4a7661d627b24ee52cacf9323b51387188e848f85f25f630b97ed86f0318b5708adcddfd5a3cdaa9e46e0908 + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.0-rc.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.36.0-rc.0 + +## Dependencies + +### Added +_Nothing has changed._ + +### Changed +- github.com/moby/spdystream: [v0.5.0 → v0.5.1](https://github.com/moby/spdystream/compare/v0.5.0...v0.5.1) +- go.opentelemetry.io/otel: [v1.40.0 → v1.41.0](https://github.com/open-telemetry/opentelemetry-go/compare/v1.40.0...v1.41.0) +- go.opentelemetry.io/otel/metric: [v1.40.0 → v1.41.0](https://github.com/open-telemetry/opentelemetry-go/compare/metric/v1.40.0...metric/v1.41.0) +- go.opentelemetry.io/otel/trace: [v1.40.0 → v1.41.0](https://github.com/open-telemetry/opentelemetry-go/compare/trace/v1.40.0...trace/v1.41.0) + +### Removed +_Nothing has changed._ + + + +# v1.36.0-rc.0 + + +## Downloads for v1.36.0-rc.0 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes.tar.gz) | 5b28aae440bdb013eb1497bc357a4b27eb51d275b6da72a9f8aec845169ecbc47ed8c82f091b3b62ede51765e587e652040e39f78da4dd5f560be88f3c3cc52b +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-src.tar.gz) | 84e1f0f1e3e57e4b19cb0971d0d980b47cbd2b512e0a0a1477bf88f9d0ff4b786a714e391276fc7b6c4ec119fb8311272b4838644be0bb54c898d31c6640e8b5 + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-darwin-amd64.tar.gz) | d7f5c47f327bee440c506a37876538eb232900b1d01c922ca3e3f3ee5a7cf73a35e1c9bcf0c045f9341ee41c04bdac1728f691b470c0ce6302704000347bbad1 +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-darwin-arm64.tar.gz) | fac20e24b8f9f4fba2c22a6acc193d2fc561542233c7707328a87a1ddbf0d2a476503487c055a21b3661df4cda7e96e271734192c65d1d8badd275cfabbb6ec8 +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-linux-386.tar.gz) | 64edce3331e69b6abb4cb6b0668125c511ee82e2e98978af44e541a3218d09d832f44cec05ea22fc8fd141587b15915153cd92f4664b6da6b2f56891f6b9af3e +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-linux-amd64.tar.gz) | e15cea3fe11262d903b549c26c36936232b08cfa231573a1c7aeaa64553ff2099534bae8e92c99a304ab7edc945e692f95da77ed68b4e4896a99377d00bcde66 +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-linux-arm.tar.gz) | eb93e29a907f19f4fabd77f0268c2ebb278a332aa182b9e8e3d56b9fff2a28442dee888689f87ca25b3111ee7dc87d9961d5bd0d1867d812edbeff867380996d +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-linux-arm64.tar.gz) | 3d184176e56da806368a36b2c8ee33e7f695970498132ed39ddf62d0733440bf1198833441ec05923b1b7d0d50da01896a5253e6be38f950775eb6e82022302f +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-linux-ppc64le.tar.gz) | e985ea5dd3a5d61bcb60e209d7e520c6102a0c7117e23c17ad5dc1a34538a5c73cd5e1abe335aba122e3e6abebaa98b4a1d98912f0f9e63efa9f824594ba54db +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-linux-s390x.tar.gz) | a44617572f6099efab0133cc2c0b4e9dcfedca6e9d4381036ab2b2a58909250b2062d14f2ef1c06fd814ecf1f6979784f9f5303983865ce490817d8f88abe021 +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-windows-386.tar.gz) | da1df34ff592947383f02c34df15ad2d68c09eb43de670eb6c59525ce2e35dd24578645e8c114314c19fc3c3f1fe6dc05d141db513b3326a3d76d08b23f92e74 +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-windows-amd64.tar.gz) | 37e27266d6a671c0d5dd2add6e3e441715f627419f4765a055020aa19d303997d5ae05da56b88a3a7de5b2d85be4b5b49ee776ad16349f458a94eabe3d4dca38 +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-client-windows-arm64.tar.gz) | 40f0e5a3fce2e5650d7a00ca1e55f49f1ac00c0a1fd91f79719102f0eac4bfb164f3deb94d3b6ac39c7f2596bdab8c95d43b76a68b81b07d9e000e6a3bf9532e + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-server-linux-amd64.tar.gz) | 134342bbe78abb9b02ae3ebe10b9e5de72d2ae9084f33c767d0045c04e740f5c50c40fb9bba00ac2afcb96afcd507b8745c26ec02a40ab0b6a2ca938b9797577 +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-server-linux-arm64.tar.gz) | 51a034a8e544db735f803237d6e363f156b36793fea613029c6db005d9fede5147f623f03732b3bf55d8674cd5faf2f6c675f2f0998c1fbb836548fb581402a5 +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-server-linux-ppc64le.tar.gz) | 5e9a38e2fd29e23240dd4f82f94504258310c5262b59d2b7dd24ddde6e61282042ac7727fdde7fd7c137ca3a63a7a6b8ae38278a839dc825129dc53069d82ceb +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-server-linux-s390x.tar.gz) | e70e9b0cf26a81c5d8030609f8d3aa91e569353d688fc74f6f63244453651f2f38139af92880b68393cc110c70d25fd0f686e06d55c6873bbb4e0f572dd964c2 + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-node-linux-amd64.tar.gz) | a716ef2572eedde30999ec7ab1a720dc749afa7c4440457b846f868634f1faa6d36b88d80838575cf41bd5eac0b229c2606f32898347bb56342a7ff854dd6852 +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-node-linux-arm64.tar.gz) | 69ae5167206cc88b125a8f53e618e4f452130a940ea5fbd1e79bf0c6ce82a624a468529cb4ac4d56cc644826fea3bf13a42c99cc43d54fd42acfa98eb04e0897 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-node-linux-ppc64le.tar.gz) | d9f292776a53c2db0d7a518eb6b3844d2eca971528e04ee30ac17340d7748eca9a0aa57edceef15267d254dd8a04fcc2a63485f8fda8610cb90a6c53b88779bd +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-node-linux-s390x.tar.gz) | 3f564f9340a52714669e73cff2c5b7a86971bd252ee095e0739e71792d42577d76a4df2fe669f18377cc5ed3a7dc577439af54e4d0033d2d5539978730a85ded +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-rc.0/kubernetes-node-windows-amd64.tar.gz) | b00af83413f84e2f585a74aa217af4deadf39b792479cc6e47b9b4e7ae1e2d4975c094d385d405a6a47de602153a7df7be9fa663d4734f3fdb6fb513b76a5c26 + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.0-rc.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.36.0-beta.0 + +## Changes by Kind + +### API Change + +- Add ResourcePoolStatusRequest API (v1alpha1) for querying DRA resource pool availability. This enables users and external schedulers to discover available devices across pools before submitting workloads. Requires the DRAResourcePoolStatus feature gate (alpha). ([#137028](https://github.com/kubernetes/kubernetes/pull/137028), [@nmn3m](https://github.com/nmn3m)) [SIG API Machinery, Apps, Auth, Etcd, Instrumentation, Node, Scheduling, Storage and Testing] +- Added `DisruptionMode`, `PriorityClassName` and `Priority` fields to Workload and PodGroup APIs to support workload-aware preemption when `WorkloadAwarePreemption` feature gate is enabled. ([#136589](https://github.com/kubernetes/kubernetes/pull/136589), [@tosi3k](https://github.com/tosi3k)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- DRA (Dynamic Resource Allocation) drivers and controllers now require granular RBAC permissions to update ResourceClaim statuses when the `DRAResourceClaimGranularStatusAuthorization` feature gate is enabled (Beta in 1.36). Schedulers and controllers must be granted `update`/`patch` on `resourceclaims/binding`. DRA drivers must be granted `associated-node:update` or `arbitrary-node:update` (or patch equivalents) on `resourceclaims/driver`, restricted by their specific `resourceNames`. ([#134947](https://github.com/kubernetes/kubernetes/pull/134947), [@aojea](https://github.com/aojea)) [SIG API Machinery, Apps, Auth, Instrumentation, Node, Scheduling and Testing] +- DRA: PodGroup resources can now make requests with ResourceClaims through a `spec.resourceClaims` field which can refer to ResourceClaims and ResourceClaimTemplates. Claims made by a PodGroup are reserved for the entire PodGroup instead of individual Pods, allowing more than 256 Pods to share a single ResourceClaim. ResourceClaimTemplates referenced by a PodGroup's claim will replicate into a ResourceClaim specific to that PodGroup able to be shared by all of the group's Pods. ([#136989](https://github.com/kubernetes/kubernetes/pull/136989), [@nojnhuh](https://github.com/nojnhuh)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Graduate InPlacePodLevelResourcesVerticalScaling feature to beta and have it on by default. This feature allows resizing the CPU and memory resources at pod-level for pods with pod-level resources set and enabled. ([#137684](https://github.com/kubernetes/kubernetes/pull/137684), [@ndixita](https://github.com/ndixita)) [SIG API Machinery, Apps, Autoscaling, Node, Release, Scheduling and Testing] +- Promote NodeLogQuery to GA. ([#137544](https://github.com/kubernetes/kubernetes/pull/137544), [@jrvaldes](https://github.com/jrvaldes)) [SIG Node and Windows] +- [Alpha] Introduce List Types for Attributes in DRA (KEP-5491). + + The `DRAListTypeAttributes` feature gate(false by default) can activate below enhancements. + + For DRA drivers, it can enable list-type fields(`bools/ints/strings/versions`) for device attributes in `ResourceSlice`. Please remember that the number of attribute values, including scalars and lists, per single device is limited to 48. + + For DRA users, this feature enhances the semantics of `matchAttribute`/`distinctAttribute` constraint in `ResourceClaim` to work on both scalar and list attributes. The `matchAttribute` constraint now matches when the intersection (as a set) of all the list values among candidate devices is non-empty. The `distinctAttribute` constraint, which is behind the `ConsumableCapacity` feature gate, matches when all the list values (as a set) among candidate devices are pairwise disjoint. In both constraints, scalar values are implicitly treated as a singleton set. + + And, a new CEL function `.includes` is introduced. The function can work on both scalar and list attributes to test inclusion(e.g., `device.attributes["dra.example.com"].model.includes("model-a")`). This can support smooth migration for CEL expression in DRA resources when a DRA driver changes the attribute value type from scalar to list, or vice versa. ([#137190](https://github.com/kubernetes/kubernetes/pull/137190), [@everpeace](https://github.com/everpeace)) [SIG API Machinery, Node, Scheduling and Testing] + +### Feature + +- Adds the `UserNamespacesHostNetwork` runtime handler and integrates the `UserNamespacesHostNetworkSupport` feature gate with the `NodeDeclaredFeatures` feature gate. The `UserNamespacesHostNetworkSupport` feature gate only takes effect when the container runtime's `UserNamespacesHostNetwork` runtime handler returns true and the `NodeDeclaredFeatures` feature gate is enabled. ([#135828](https://github.com/kubernetes/kubernetes/pull/135828), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Autoscaling, Node, Scheduling and Testing] +- When WorkloadAwarePreemption Feature Gate is enabled, and the Pod Group scheduling fails to find a place for the Pod Group, instead of running default preemption for each pod from the pod group, the workload aware preemption will be run for the whole group. ([#137606](https://github.com/kubernetes/kubernetes/pull/137606), [@Argh4k](https://github.com/Argh4k)) [SIG Apps, Node, Scheduling, Storage and Testing] + +### Bug or Regression + +- Fix erroneously reporting a pod-level resize in progress on pod creation when InPlacePodLevelResourcesVerticalScaling is enabled. ([#138049](https://github.com/kubernetes/kubernetes/pull/138049), [@ndixita](https://github.com/ndixita)) [SIG Node and Testing] +- Fixed kubelet to preserve DRA NodeAllocatableResourceClaimStatuses in Pod.Status. ([#138030](https://github.com/kubernetes/kubernetes/pull/138030), [@askervin](https://github.com/askervin)) [SIG Node] +- Fixes a 1.35 regression in StatefulSet Parallel pod management by disabling the MaxUnavailableStatefulSet feature by default. ([#137904](https://github.com/kubernetes/kubernetes/pull/137904), [@soltysh](https://github.com/soltysh)) [SIG Apps] +- Kep-5304: bump cdi spec for discoverable metadata to 0.5.0 ([#138035](https://github.com/kubernetes/kubernetes/pull/138035), [@alaypatel07](https://github.com/alaypatel07)) [SIG Node] +- Update the version of the pause image to 3.10.2. Track all pause image version dependencies under the 'registry.k8s.io/pause' item in build/dependencies.yaml. ([#138199](https://github.com/kubernetes/kubernetes/pull/138199), [@neolit123](https://github.com/neolit123)) [SIG CLI, Cloud Provider, Cluster Lifecycle, Scheduling and Testing] + +### Other (Cleanup or Flake) + +- Golang version has been updated to 1.26.2 ([#138261](https://github.com/kubernetes/kubernetes/pull/138261), [@dims](https://github.com/dims)) [SIG Architecture and Testing] + +## Dependencies + +### Added +_Nothing has changed._ + +### Changed +_Nothing has changed._ + +### Removed +_Nothing has changed._ + + + +# v1.36.0-beta.0 + + +## Downloads for v1.36.0-beta.0 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes.tar.gz) | 7e4c5eb75fdcbddb19c94139ead1b7d16bbc9332f319006312441db15cf2d5562625a42cc1a14aad93338a1e48e6c00e2abfe23071626d2a80959ce71c82fc07 +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-src.tar.gz) | a97a579bf0b56b408908d1ab58bb75c821e05bb26b301afb223302314a569ebed865b019118ece4a94e598564d21d36ad1f452ea5f64837e3ebbca67b11c1201 + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-darwin-amd64.tar.gz) | 942e91cc2e59e2b3c6d14d0e3547c349b5fdf573f76861baa78a650871bbddf80c5503b8ab42a7c34481fd5750781722274aae1210cddbb82cccc5ebb38c96f1 +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-darwin-arm64.tar.gz) | 3d985d2d528017b806cd6073ff4dee53faf3ee8b6a25c994287b82a4b09be6ed0e0ab122c4659b08d61fb0a0603bad00dd6c34f5d08f7d3a9fa7ed810619efd0 +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-linux-386.tar.gz) | 4631d3edc7dc0fe4dc2cfa6b0b520d167d372b60ea386dcea8c2b59b2e4c37bcf8fff93ef77813a5842dac7d45926efdc78a69fcaafe8aa488dab2d36eb36f37 +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-linux-amd64.tar.gz) | bc41228fd7e01c94c88b6f6798ae4a37834098880bc00109a5e54bd1138a9b827804426003dfedf92d418c8a1b79bc39443518b1cefbcac616c211528287e26b +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-linux-arm.tar.gz) | b89b8174eba4fc43560c35f37abb599b326a38963bdf1a4925c1eacbf1090c859ce6fb664475ab953859ea3c9f7e1d7bceaeadb2a9f42d2772efba7dc521917c +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-linux-arm64.tar.gz) | 4693d9992d1b2148e6cd93eafba1ef03a73a72f1b81c610be6f6597225bc31a7fe60775f04c1b89b7dc669eba16087762b14c21ae7d8fd405e1a9a24bd2b838b +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-linux-ppc64le.tar.gz) | 166ab0173f7e80f4d584fe2cf61693b3bb0896840d587e7c2b5fd670db5498f8facfefd2ea49c91c50ab53304c9bc430fa29e87420abb552355e1ed9068c7e95 +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-linux-s390x.tar.gz) | 0b3c66b59cd4935ff56b493a3aae0ddf441f89ab4e52a45edbfbd0cd882af317ddf85e40cb914681fbb89a55ad3707884d0c3046f8df2a9045702ff62df06bf2 +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-windows-386.tar.gz) | cb4083fed4d5e3140f7dd5730e7dabf681ea11999f0b6e946a6f840380355257f849b35600f29e2d87fc056036e41827fe5596991882eedc4fd9a5c3addf8d97 +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-windows-amd64.tar.gz) | 9b858b63cea22adfe924f5a583ed398c0c613a436c4f9db625d13ca52f1dd79c49daf7b6d981c2be1d90218385194822908dfdf70e41bc55b58c5ff8c6a32e00 +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-client-windows-arm64.tar.gz) | a7982d04ee2c2a804e575c20121036ac5fbfcb82f1c473525635ad7bb816055ec6f223b4c7612b7b87d185816368d2e690a1c3ed47336cc2eebbf6372f0576de + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-server-linux-amd64.tar.gz) | 826ce2b809ae92eddacdb7c1dc75108024112457a0c1d48a23809d282707d13292d5fa40502ef7b6f44a9d68821eb556f59af05498a47427fd337634018ed576 +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-server-linux-arm64.tar.gz) | 3f70adbb9b1389670b0d77c1caab3b2b83253caa90e4641fc4604a0d559f286a1b083d0111db7c0bc057e25f6184f2b1fed9a206ad21c29b7c0a03869c8cbaf2 +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-server-linux-ppc64le.tar.gz) | a092665ee24cdda2800f31be942704340bdd541590d7d752e0ba847fadbcf6eb047954f890476f7b926c49984b3d9cb1ef675eedb76163d3b155ab4f5430fc3d +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-server-linux-s390x.tar.gz) | acfb69d1a089bd2bd9250d37ebc705eaf80a3b831b80756cabe90f9a8d92816344a12859ec49e6f6a74e6ec0b2a39d382be7f3061f0ffad3f9d1fdb82e25d5f0 + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-node-linux-amd64.tar.gz) | cb8e8ec77f7e4ea9e8db50cb41339b1c1026b3565b0a47a6873156cec58e495897b0a767885838d9f085c1f6b69d40eb2e3b1babc5f9c85c235a13ddd9399670 +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-node-linux-arm64.tar.gz) | cea68a83e7b202e273721be4fd708fcd45d4d3e06f1d8b6da5d6282672e11282d0cb162a76595ae4b95381a27afd3eebfe15595b18539dac720658f67e9ab549 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-node-linux-ppc64le.tar.gz) | f1e73ab63bef0a79f8c04a4e123f99d6fcc5efd576766bb84dde9988979a87a7d4aba18e29e3696396cd1fca2281345b353fcd470cfbdfae27a122bdb9270ecd +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-node-linux-s390x.tar.gz) | 61f255a0e29fb87b10bf3aa0cd0d48921cf3491662acf39ed455e8322d76034129808a6855a95e451a69960991ee3641d11a1a487bc897b0f0097c4d5eb9bce6 +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-beta.0/kubernetes-node-windows-amd64.tar.gz) | 8e30f607ba4c196ae4fb422fa632d497d940bb82c34f3aef4ee13477070cf932119eb2968065d571bbd1a714f77ca6a6a238013fabfb6f4d580921553fd8abab + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.0-beta.0](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.36.0-alpha.2 + +## Urgent Upgrade Notes + +### (No, really, you MUST read this before you upgrade) + + - Kube-controller-manager: renamed metric 'volume_operation_total_errors' to 'volume_operation_errors_total'. If you are using custom monitoring dashboards or alerting rules based on the 'volume_operation_total_errors' metric, please update them to use the new 'volume_operation_errors_total' metric. ([#136399](https://github.com/kubernetes/kubernetes/pull/136399), [@tico88612](https://github.com/tico88612)) [SIG Apps, Instrumentation, Storage and Testing] + +## Changes by Kind + +### Deprecation + +- Add warnings and deprecation for Service.spec.externalIPs ([#137293](https://github.com/kubernetes/kubernetes/pull/137293), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Apps, Network and Windows] +- Direct access to the `Raw` field of `metav1.FieldsV1` is deprecated. Code that constructs or reads `FieldsV1` should migrate to the new `NewFieldsV1(string)`, `GetRawBytes()`, `GetRawString()`, and `SetRawBytes()` accessor methods. ([#137304](https://github.com/kubernetes/kubernetes/pull/137304), [@aaron-prindle](https://github.com/aaron-prindle)) [SIG API Machinery, Apps and Testing] +- Rename `AllowlistEntry.Name` to `AllowlistEntry.Command` in the credential plugin allowlist ([#137272](https://github.com/kubernetes/kubernetes/pull/137272), [@pmengelbert](https://github.com/pmengelbert)) [SIG API Machinery, Auth, CLI and Testing] + +### API Change + +- A few log calls which did not properly format their parameters were fixed. ([#137108](https://github.com/kubernetes/kubernetes/pull/137108), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Cluster Lifecycle, Network, Node, Scheduling and Testing] +- Add --tls-curve-preferences flag for configuring TLS key exchange mechanism ([#137115](https://github.com/kubernetes/kubernetes/pull/137115), [@damdo](https://github.com/damdo)) [SIG API Machinery, Architecture, CLI, Cloud Provider, Node and Testing] +- Add a deletion protection mechanism for PodGroup objects. ([#137641](https://github.com/kubernetes/kubernetes/pull/137641), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Auth, Scheduling and Storage] +- Add admission plugin that validates PodGroup resources reference an existing Workload and match the declared PodGroupTemplate spec. ([#137464](https://github.com/kubernetes/kubernetes/pull/137464), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Add alpha support for manifest-based admission control configuration (KEP-5793). When the `ManifestBasedAdmissionControlConfig` feature gate is enabled, admission webhooks and CEL-based policies can be loaded from static manifest files on disk via the `staticManifestsDir` field in `AdmissionConfiguration`. These policies are active from API server startup, survive etcd unavailability, and can protect API-based admission resources from modification. ([#137346](https://github.com/kubernetes/kubernetes/pull/137346), [@aramase](https://github.com/aramase)) [SIG API Machinery, Apps, Architecture, Auth, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Release, Scheduling, Storage, Testing and Windows] +- Add opt-in alpha support in kubeletplugin framework for DRA drivers to publish DRA Device metadata in pod CDI mounts ([#137086](https://github.com/kubernetes/kubernetes/pull/137086), [@alaypatel07](https://github.com/alaypatel07)) [SIG Apps, Network, Node and Testing] +- Add tlsServerName field to EgressSelectorConfiguration TLSConfig to allow overriding the server name used for TLS certificate verification ([#136640](https://github.com/kubernetes/kubernetes/pull/136640), [@kennangaibel](https://github.com/kennangaibel)) [SIG API Machinery, Apps, Auth, Storage and Testing] +- Added MemoryReservationPolicy cgroup v2 MemoryQoS support to KubeletConfiguration for memory.min protection. ([#137584](https://github.com/kubernetes/kubernetes/pull/137584), [@QiWang19](https://github.com/QiWang19)) [SIG Node and Storage] +- Added PlacementGenerate extension point to the scheduler. It's used to generate placements for placement-based pod group scheduling. Its use is guarded by the TopologyAwareWorkloadScheduling feature gate. ([#137083](https://github.com/kubernetes/kubernetes/pull/137083), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Added PlacementScore extension point to the scheduler. It's used to score placements in order to choose the best one for placement-based pod group scheduling. Its use is guarded by the TopologyAwareWorkloadScheduling feature gate. + + Deprecated MinNodeScore and MaxNodeScore in favor of MinScore and MaxScore. ([#137201](https://github.com/kubernetes/kubernetes/pull/137201), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Added SchedulingConstraints to express TAS constraints for pod group scheduling behind TopologyAwareWorkloadScheduling feature gate. + + Added TopologyPlacement plugin implementing PlacementGenerate extension point to take the constraints into consideration during pod group scheduling. The usage of this plugin is guarded by the TopologyAwareWorkloadScheduling feature gate. ([#137271](https://github.com/kubernetes/kubernetes/pull/137271), [@brejman](https://github.com/brejman)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Added TAS logic to the pod group scheduling cycle behind TopologyAwareWorkloadScheduling feature gate. This feature supports scheduling pod groups on nodes with matching topology domains. ([#137489](https://github.com/kubernetes/kubernetes/pull/137489), [@brejman](https://github.com/brejman)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling and Testing] +- Added `PodGroupPodsCount` scheduler plugin to support workload-aware scheduling by prioritizing placements with higher pod counts within a group. ([#137488](https://github.com/kubernetes/kubernetes/pull/137488), [@vshkrabkov](https://github.com/vshkrabkov)) [SIG Scheduling and Testing] +- Added alpha support (behind `PersistentVolumeClaimUnusedSinceTime` feature gate) for tracking PVC unused status via a new `Unused` condition on PersistentVolumeClaimStatus. When enabled, the PVC protection controller sets `Unused=True` with a `lastTransitionTime` when no non-terminal Pods reference the PVC, enabling external automation to identify and manage unused storage. ([#137862](https://github.com/kubernetes/kubernetes/pull/137862), [@gnufied](https://github.com/gnufied)) [SIG Apps, Auth, Storage and Testing] +- Added placement-based pod group scheduling algorithm to scheduler. Its use is guarded by the TopologyAwareWorkloadScheduling feature gate. ([#136944](https://github.com/kubernetes/kubernetes/pull/136944), [@brejman](https://github.com/brejman)) [SIG Scheduling and Testing] +- Allow users to opt-in to scheduling behaviour for CSI volume ([#137343](https://github.com/kubernetes/kubernetes/pull/137343), [@gnufied](https://github.com/gnufied)) [SIG API Machinery, Scheduling and Storage] +- Config.k8s.io.flagz API is graduated to v1beta1 ([#137174](https://github.com/kubernetes/kubernetes/pull/137174), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation, Node, Scheduling and Testing] +- Config.k8s.io.statusz API is graduated to v1beta1 ([#137173](https://github.com/kubernetes/kubernetes/pull/137173), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation, Scheduling and Testing] +- DRA DeviceTaintRules: the TimeAdded of the taint is not only added automatically, it now also gets updated automatically when changing the effect. ([#137167](https://github.com/kubernetes/kubernetes/pull/137167), [@pohly](https://github.com/pohly)) [SIG API Machinery, Node and Testing] +- DRA extended resource feature is promoted to beta in 1.36 ([#135048](https://github.com/kubernetes/kubernetes/pull/135048), [@yliaog](https://github.com/yliaog)) [SIG API Machinery, Architecture, Auth, Network, Node, Scheduling and Testing] +- DRA: graduate Device Binding Conditions (KEP #5007) to beta. The feature is now enabled by default in v1.36. ([#137795](https://github.com/kubernetes/kubernetes/pull/137795), [@ttsuuubasa](https://github.com/ttsuuubasa)) [SIG API Machinery, Node, Scheduling and Testing] +- DRA: graduated device taints and tolerations (KEP #5055) to beta. Support for DeviceTaints in ResourceSlices is on by default. Support for DeviceTaintRules depends on enabling resource.k8s.io/v1beta2 and the DeviceTaintRules feature gate. ([#137170](https://github.com/kubernetes/kubernetes/pull/137170), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth, Cluster Lifecycle, Etcd, Node, Scheduling and Testing] +- DRAConsumableCapacity is enabled by default. ([#136611](https://github.com/kubernetes/kubernetes/pull/136611), [@sunya-ch](https://github.com/sunya-ch)) [SIG API Machinery, Cluster Lifecycle, Node, Scheduling and Testing] +- Extended NodeResourcesFit to implement the PlacementScore extension point. The usage of the PlacementScore extension point is guarded by the TopologyAwareWorkloadScheduling feature gate. ([#136652](https://github.com/kubernetes/kubernetes/pull/136652), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Feature gate UserNamespacesSupport is now GA. ([#136792](https://github.com/kubernetes/kubernetes/pull/136792), [@rata](https://github.com/rata)) [SIG API Machinery, Apps, CLI, Node, Storage and Testing] +- For pod resizes requested on nodes where the resize request exceeds the node's allocatable capacity or the node is running an OS that does not support resize, the request will now fail in admission rather than be marked as Infeasible in the pod status later. ([#136043](https://github.com/kubernetes/kubernetes/pull/136043), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Node, Release, Scheduling, Storage and Testing] +- Graduate metric 'apiserver_storage_events_received_total' to BETA ([#136314](https://github.com/kubernetes/kubernetes/pull/136314), [@petern48](https://github.com/petern48)) [SIG API Machinery, Etcd, Instrumentation and Testing] +- Graduated `ImageVolume` feature to stable. ([#136711](https://github.com/kubernetes/kubernetes/pull/136711), [@saschagrunert](https://github.com/saschagrunert)) [SIG Apps, Architecture, Node and Testing] +- HPA: Improved scaling to and from zero with enabled HPAScaleToZero feature gate. ([#135118](https://github.com/kubernetes/kubernetes/pull/135118), [@johanneswuerbach](https://github.com/johanneswuerbach)) [SIG Apps, Autoscaling and Testing] +- Integrate Workload and PodGroup APIs with the Job controllers to support gang-scheduling. ([#137032](https://github.com/kubernetes/kubernetes/pull/137032), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Instrumentation, Node, Scheduling and Testing] +- Introduced scheduling.k8s.io/v1alpha2 Workload and PodGroup API to allow for expressing workload-level scheduling requirements and let kube-scheduler act on those. Removed scheduling.k8s.io/v1alpha1 Workload API. ([#136976](https://github.com/kubernetes/kubernetes/pull/136976), [@tosi3k](https://github.com/tosi3k)) [SIG API Machinery, Apps, Auth, CLI, Cloud Provider, Etcd, Node, Scheduling, Storage and Testing] +- Kube-scheduler now updates PodGroup status with a `PodGroupScheduled` condition reflecting whether the group was successfully scheduled or is unschedulable. ([#137611](https://github.com/kubernetes/kubernetes/pull/137611), [@helayoty](https://github.com/helayoty)) [SIG API Machinery, Apps, Scheduling and Testing] +- Promote DRAPrioritizedList to GA ([#136924](https://github.com/kubernetes/kubernetes/pull/136924), [@troychiu](https://github.com/troychiu)) [SIG Apps, Architecture, Autoscaling, CLI, Cloud Provider, Cluster Lifecycle, Network, Node, Release, Scheduling, Storage and Testing] +- Promote ProcMountType feature to GA ([#137454](https://github.com/kubernetes/kubernetes/pull/137454), [@haircommander](https://github.com/haircommander)) [SIG API Machinery, Apps, Auth, CLI, Node, Storage and Testing] +- Promote `NodeDeclaredFeatures` to beta. ([#136042](https://github.com/kubernetes/kubernetes/pull/136042), [@pravk03](https://github.com/pravk03)) [SIG API Machinery, Apps, Cluster Lifecycle, Instrumentation, Node, Scheduling, Storage and Testing] +- Promoted mutable CSI node allocatable count to GA. The `MutableCSINodeAllocatableCount` feature gate is now locked to enabled. ([#136230](https://github.com/kubernetes/kubernetes/pull/136230), [@torredil](https://github.com/torredil)) [SIG API Machinery and Storage] +- Promoted several endpointslice metrics from Alpha to Beta stability. ([#136368](https://github.com/kubernetes/kubernetes/pull/136368), [@bhope](https://github.com/bhope)) [SIG Instrumentation and Network] +- Promoted several scheduler metrics (`scheduler_goroutines`, `scheduler_permit_wait_duration_seconds`, `scheduler_plugin_evaluation_total`, `scheduler_plugin_execution_duration_seconds`, `scheduler_scheduling_algorithm_duration_seconds`, `scheduler_unschedulable_pods`) from Alpha to Beta stability, providing stronger API and label stability guarantees for metric consumers. ([#136155](https://github.com/kubernetes/kubernetes/pull/136155), [@bhope](https://github.com/bhope)) [SIG Instrumentation and Scheduling] +- Promoted the `DRAAdminAccess` feature gate to GA. ([#137373](https://github.com/kubernetes/kubernetes/pull/137373), [@ritazh](https://github.com/ritazh)) [SIG API Machinery, Auth, Node, Scheduling and Testing] +- Promoted two Job controller metrics from Alpha to Beta stability, providing stronger API and label stability guarantees for metric consumers. ([#136367](https://github.com/kubernetes/kubernetes/pull/136367), [@bhope](https://github.com/bhope)) [SIG Apps and Instrumentation] +- Remove CRD stored versions from status upon SVM migration ([#135297](https://github.com/kubernetes/kubernetes/pull/135297), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Auth and Testing] +- Renamed metric 'etcd_bookmark_counts' to 'etcd_bookmark_total'. If you are using custom monitoring dashboards or alerting rules based on the 'etcd_bookmark_counts' metric, please update them to use the new 'etcd_bookmark_total' metric. ([#136483](https://github.com/kubernetes/kubernetes/pull/136483), [@petern48](https://github.com/petern48)) [SIG API Machinery, Etcd, Instrumentation and Testing] +- Slow requests that use impersonation can now be tracked via the `apiserver.latency.k8s.io/impersonation` audit event annotation when the ConstrainedImpersonation feature is enabled. ([#137523](https://github.com/kubernetes/kubernetes/pull/137523), [@enj](https://github.com/enj)) [SIG API Machinery, Auth and Testing] +- The /configz endpoint of kubelet, scheduler, cloud controller manager, and kube-proxy serializes the APIVersion and Kind fields as well as using public types instead of internal. ([#136044](https://github.com/kubernetes/kubernetes/pull/136044), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Network, Node, Scheduling and Testing] +- The ConstrainedImpersonation feature is now beta and enabled by default. ([#137609](https://github.com/kubernetes/kubernetes/pull/137609), [@enj](https://github.com/enj)) [SIG API Machinery and Testing] +- The `StrictIPCIDRValidation` feature gate to kube-apiserver is now + enabled by default, meaning that API fields no longer allow IP or CIDR + values with extraneous leading "0"s (e.g., `010.000.000.005` rather than + `10.0.0.5`) or CIDR subnet/mask values with ambiguous semantics (e.g., + `192.168.0.5/24` rather than `192.168.0.0/24` or `192.168.0.5/32`). ([#137053](https://github.com/kubernetes/kubernetes/pull/137053), [@danwinship](https://github.com/danwinship)) [SIG Network and Testing] +- This change adds a new alpha feature DRANodeAllocatableResources, which includes: + - A new ResourceSlice.Spec.Devices[*].NodeAllocatableResourceMappings field for DRA drivers to declare how device resources map to node allocatable Kubernetes resources (e.g., cpu, memory). + - Changes in the DynamicResources plugin and the scheduler framework to correctly account for node allocatable resources requested through resource claims. + - Kubelet's admission handler validates if the node can fulfill node allocatable DRA requests along with standard requests in the pod spec. + ``` + + #### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: + + ([#136725](https://github.com/kubernetes/kubernetes/pull/136725), [@pravk03](https://github.com/pravk03)) [SIG API Machinery, Apps, Node, Scheduling and Testing] +- `SnapshotMetadataService` is now available in v1beta1 version. The support for the v1alpha1 version have been removed. ([#137564](https://github.com/kubernetes/kubernetes/pull/137564), [@iPraveenParihar](https://github.com/iPraveenParihar)) [SIG Storage and Testing] + +### Feature + +- A new gRPC service is added to the Kubelet that provides information about pods running on the node. ([#134627](https://github.com/kubernetes/kubernetes/pull/134627), [@briansonnenberg](https://github.com/briansonnenberg)) [SIG Node and Testing] +- Add alpha metrics tracking the resource version the cache layer of an informer is at. ([#137419](https://github.com/kubernetes/kubernetes/pull/137419), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Architecture, Instrumentation and Testing] +- Add the `timezone` field to the cronjob describe output. ([#136663](https://github.com/kubernetes/kubernetes/pull/136663), [@kfess](https://github.com/kfess)) [SIG CLI] +- Add the ability for statefulset controller to read its own pod and pvc writes ([#137254](https://github.com/kubernetes/kubernetes/pull/137254), [@michaelasp](https://github.com/michaelasp)) [SIG Apps] +- Add tracing for WatchList requests ([#137202](https://github.com/kubernetes/kubernetes/pull/137202), [@serathius](https://github.com/serathius)) [SIG API Machinery and Testing] +- Added ControllerManagerReleaseLeaderElectionLockOnCancel feature gate to gate leader election lock release on exit for kube-controller-manager ([#136279](https://github.com/kubernetes/kubernetes/pull/136279), [@tchap](https://github.com/tchap)) [SIG API Machinery and Cloud Provider] +- Added New RuntimeService streaming RPCs (`StreamPodSandboxes`, `StreamContainers`, `StreamContainerStats`, `StreamPodSandboxStats`, `StreamPodSandboxMetrics`) and New ImageService streaming RPC (`StreamImages`). ([#136987](https://github.com/kubernetes/kubernetes/pull/136987), [@bitoku](https://github.com/bitoku)) [SIG Cluster Lifecycle, Node and Testing] +- Added the metric terminated_containers_total to track the number of containers failed or succeeded broken down by exit code ([#137453](https://github.com/kubernetes/kubernetes/pull/137453), [@rawsocket](https://github.com/rawsocket)) [SIG Instrumentation, Node and Testing] +- Added two scheduler metrics for Device Binding Conditions, covering allocation attempts and PreBind duration with status and driver labels. ([#137284](https://github.com/kubernetes/kubernetes/pull/137284), [@ttsuuubasa](https://github.com/ttsuuubasa)) [SIG Node and Scheduling] +- Added warning when kubectl rollout undo is used on resources managed with kubectl apply to prevent unexpected behavior from annotation mismatch ([#137064](https://github.com/kubernetes/kubernetes/pull/137064), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Adding multiple conditions support to kubectl wait command. ([#136855](https://github.com/kubernetes/kubernetes/pull/136855), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] +- Adds metrics for constrained impersonation as described in https://kep.k8s.io/5284 + + apiserver_impersonation_attempts_total{mode, decision} + apiserver_impersonation_attempts_duration_seconds{mode, decision} + apiserver_impersonation_authorization_attempts_total{mode, decision} + apiserver_impersonation_authorization_attempts_duration_seconds{mode, decision} ([#137374](https://github.com/kubernetes/kubernetes/pull/137374), [@enj](https://github.com/enj)) [SIG API Machinery, Auth and Testing] +- Adds the ExtendWebSocketsToKubelet feature gate (Beta, default true in v1.36). When enabled, the API server proxies WebSocket exec/attach/portforward requests directly to the kubelet rather than translating or tunneling them at the API server. The kubelet now handles WebSocket-to-SPDY stream translation (exec/attach) and WebSocket tunneling (portforward) using the same handlers previously used at the API server. The kubelet advertises support for this feature to the API server via the NodeDeclaredFeatures mechanism; the API server only proxies directly to a kubelet that has advertised support. Two new ALPHA metrics are added to track routing decisions and WebSocket streaming volume: apiserver_websocket_streaming_requests_total (labels: subresource, proxy_type) and kubelet_streaming_websocket_requests_total (label: subresource). ([#136256](https://github.com/kubernetes/kubernetes/pull/136256), [@seans3](https://github.com/seans3)) [SIG API Machinery, Autoscaling, Node, Scheduling and Testing] +- Allow the CRI (and NRI) to block pod-level resizes. ([#137555](https://github.com/kubernetes/kubernetes/pull/137555), [@natasha41575](https://github.com/natasha41575)) [SIG Node] +- Bump coredns to 1.14.2 ([#137605](https://github.com/kubernetes/kubernetes/pull/137605), [@pacoxu](https://github.com/pacoxu)) [SIG Cloud Provider and Cluster Lifecycle] +- CRI API: A new field is added to the PullImageResponse message - `image_id`. This field serves as a unique identifier for the image on the node as returned by the container runtimes. ([#137217](https://github.com/kubernetes/kubernetes/pull/137217), [@stlaz](https://github.com/stlaz)) [SIG Node] +- DRA ResourceSlice controller: new optional `ReconcilePoolWithName` allows per-pool reconciliation without setting NodeName on slices, so the scheduler can use NodeSelector or allNodes for node-owned, cluster-visible resources (e.g. network-shared devices). "All nodes" is no longer the default. When publishing devices for the entire cluster, it *must* be set explicitly. ([#137365](https://github.com/kubernetes/kubernetes/pull/137365), [@yaroslavborbat](https://github.com/yaroslavborbat)) [SIG Node and Testing] +- Enable the feature gate `RestartAllContainersOnContainerExits` by default. The RestartAllContainersOnContainerExits feature is promoted to beta. ([#136681](https://github.com/kubernetes/kubernetes/pull/136681), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node and Testing] +- Enables Prometheus native histogram support in apiserver when feature gate is enabled. + Histograms are exposed in both classic and native formats using + exponential bucket configuration (factor=1.1, max buckets=160) ([#136763](https://github.com/kubernetes/kubernetes/pull/136763), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Cloud Provider, Instrumentation, Network, Node, Scheduling and Testing] +- Enables Prometheus native histogram support in kube-controller-manager when feature gate is enabled. + Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160) ([#137779](https://github.com/kubernetes/kubernetes/pull/137779), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation and Testing] +- Enables Prometheus native histogram support in kube-proxy when feature gate is enabled. + Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160) ([#137781](https://github.com/kubernetes/kubernetes/pull/137781), [@richabanker](https://github.com/richabanker)) [SIG Network] +- Enables Prometheus native histogram support in kubelet when feature gate is enabled. + Histograms are exposed in both classic and native formats using exponential bucket configuration (factor=1.1, max buckets=160) ([#137780](https://github.com/kubernetes/kubernetes/pull/137780), [@richabanker](https://github.com/richabanker)) [SIG Node] +- Enables Prometheus native histogram support in scheduler when feature gate is enabled. + Histograms are exposed in both classic and native formats using + exponential bucket configuration (factor=1.1, max buckets=160) ([#137466](https://github.com/kubernetes/kubernetes/pull/137466), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation, Scheduling and Testing] +- Ensures single-container pod can restart quickly with RestartAllContainers action. ([#136966](https://github.com/kubernetes/kubernetes/pull/136966), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node and Testing] +- Fix missing field conversions (BindsToNode, BindingConditions, BindingFailureConditions, AllowMultipleAllocations, Capacity) in DRA API v1beta1 hand-written conversion code ([#137240](https://github.com/kubernetes/kubernetes/pull/137240), [@yykkibbb](https://github.com/yykkibbb)) [SIG Node] +- Graduate ComponentFlagz to beta ([#137386](https://github.com/kubernetes/kubernetes/pull/137386), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Auth, Instrumentation, Node and Testing] +- Graduate ComponentStatusz to beta ([#137384](https://github.com/kubernetes/kubernetes/pull/137384), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Auth and Instrumentation] +- Instrument /flagz and /statusz endpoints with apiserver request metrics (apiserver_request_total, apiserver_request_duration_seconds), with group and version labels reflecting the content-negotiated API version. ([#137021](https://github.com/kubernetes/kubernetes/pull/137021), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery and Instrumentation] +- Introduce index-based naming in ResourceSlice controller and ensure ResourceSlices and pools are sorted lexicographically before allocation, allowing users to control allocation priority. ([#136641](https://github.com/kubernetes/kubernetes/pull/136641), [@troychiu](https://github.com/troychiu)) [SIG Node and Testing] +- Introduces new staging modules `k8s.io/streaming` and `k8s.io/cri-streaming` for Kubernetes streaming transport and CRI streaming server code. + + `k8s.io/apimachinery/pkg/util/httpstream` (including `spdy` and `wsstream`) remains available as a deprecated compatibility wrapper backed by `k8s.io/streaming`. + + The extracted SPDY roundtripper preserves CIDR matching in `NO_PROXY`/`no_proxy`. ([#137298](https://github.com/kubernetes/kubernetes/pull/137298), [@dims](https://github.com/dims)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Release, Scheduling, Storage and Testing] +- Kube-apiserver: The UnknownVersionInteroperabilityProxy feature gate graduates to beta and enabled by default. The `--peer-ca-file` flag is required to turn on the proxy. ([#137172](https://github.com/kubernetes/kubernetes/pull/137172), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Kubeadm: when using '--v=1' or higher log verbosity, print information of the CA certificate used for discovery when using 'kubeadm join'. ([#137102](https://github.com/kubernetes/kubernetes/pull/137102), [@sivchari](https://github.com/sivchari)) [SIG Cluster Lifecycle] +- Kubectl explain: when a schema or field includes an externalDocs section, it is now displayed as: + + + EXTERNAL DOCS: + + URL: + + + This appears after the DESCRIPTION block for top-level resources and + after the field description for individual fields. The section is + omitted in short mode and when `externalDocs` is absent. ([#136988](https://github.com/kubernetes/kubernetes/pull/136988), [@pedjak](https://github.com/pedjak)) [SIG CLI] +- Kubectl: `kubectl describe node` now lists aggregated **ResourceSlices** when the `ResourceSlice` API is present, detailing slice name, driver, and pool. ([#131744](https://github.com/kubernetes/kubernetes/pull/131744), [@ArangoGutierrez](https://github.com/ArangoGutierrez)) [SIG CLI] +- Kubelet: if the `--client-ca-file` is updated while kubelet is running, the updated root certificates are now correctly used to advertise accepted authorities to TLS clients connecting to the kubelet endpoints. This behavior is guarded by the `ReloadKubeletClientCAFile` feature gate, which is enabled by default. ([#136762](https://github.com/kubernetes/kubernetes/pull/136762), [@HarshalNeelkamal](https://github.com/HarshalNeelkamal)) [SIG API Machinery, Auth, Node and Testing] +- Kubernetes is now built using Go 1.26.0 ([#137080](https://github.com/kubernetes/kubernetes/pull/137080), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] +- Preserve the logs of restarted containers for containers restarted by feature RestartAllContainers. ([#136963](https://github.com/kubernetes/kubernetes/pull/136963), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node] +- Promote DRAPartitionableDevices to beta ([#137350](https://github.com/kubernetes/kubernetes/pull/137350), [@mortent](https://github.com/mortent)) [SIG Node, Scheduling and Testing] +- Promoted the `KubeletPodResourcesDynamicResources` and `KubeletPodResourcesGet` feature gates to GA. ([#136728](https://github.com/kubernetes/kubernetes/pull/136728), [@guptaNswati](https://github.com/guptaNswati)) [SIG Node and Testing] +- REVERT: CRI API: A new field is added to the PullImageResponse message - `image_id`. This field serves as a unique identifier for the image on the node as returned by the container runtimes. ([#137574](https://github.com/kubernetes/kubernetes/pull/137574), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Node] +- Reduces the needs of the setcap build image for kube-apiserver by no longer requiring that image to contain a shell (`sh` or `dash` or `bash`). ([#136633](https://github.com/kubernetes/kubernetes/pull/136633), [@addyess](https://github.com/addyess)) [SIG Release] +- Server images now use `staging/src/k8s.io/component-base/logs/kube-log-runner` instead of `go-runner`, full compatability is maintained (including the same `/go-runner` executable path). + + In the future Kubernetes will use base-images without go-runner. ([#136954](https://github.com/kubernetes/kubernetes/pull/136954), [@BenTheElder](https://github.com/BenTheElder)) [SIG Instrumentation and Release] +- Support in-place pod resize of running non-sidecar initContainers. ([#137352](https://github.com/kubernetes/kubernetes/pull/137352), [@natasha41575](https://github.com/natasha41575)) [SIG API Machinery, Apps, Autoscaling, Node, Scheduling, Storage and Testing] +- The KubeletPSI feature has graduated to General Availability (GA) and continues to be enabled by default. This feature allows the Kubelet to expose Linux cgroup Pressure Stall Information (PSI) metrics, providing deeper visibility into system and pod-level resource contention (CPU, Memory, and I/O) via the Kubelet Summary API. ([#136548](https://github.com/kubernetes/kubernetes/pull/136548), [@mariafromano-25](https://github.com/mariafromano-25)) [SIG Node] +- This change allows the Topology, CPU, and Memory managers to recognize and act upon + `pod.spec.resources`, enabling two flexible resource management models. Both models + support `guaranteed` pods that contain a mix of containers that may be eligible to receive + exclusive resource allocation or be part of the pod-allocated shared resource pool. ([#134768](https://github.com/kubernetes/kubernetes/pull/134768), [@KevinTMtz](https://github.com/KevinTMtz)) [SIG Node and Testing] +- Update `kubectl kuberc set` with options for setting `credentialPluginPolicy` and `credentialPluginAllowlist` ([#137300](https://github.com/kubernetes/kubernetes/pull/137300), [@pmengelbert](https://github.com/pmengelbert)) [SIG CLI] +- When `kubectl exec` or `kubectl logs` are run with a specified container name, and no container with that name is found, `kubectl` now lists the names of containers that would be valid to specify. ([#136973](https://github.com/kubernetes/kubernetes/pull/136973), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] + +### Documentation + +- Add metric component and endpoint to generated metric reference documentation. ([#136360](https://github.com/kubernetes/kubernetes/pull/136360), [@skl](https://github.com/skl)) [SIG Instrumentation and Testing] + +### Failing Test + +- PLEGOnDemandRelist feature flag is kept a Beta level, but switched off by default. ([#137909](https://github.com/kubernetes/kubernetes/pull/137909), [@dims](https://github.com/dims)) [SIG Node] + +### Bug or Regression + +- Add `--detach-keys` flag to `kubectl attach` and `kubectl run`, allowing detach without terminating the container ([#134997](https://github.com/kubernetes/kubernetes/pull/134997), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG API Machinery and CLI] +- Capped nf_conntrack_max to 1,048,576 to prevent excessive memory consumption on high-core machines when using automatic calculation. ([#137002](https://github.com/kubernetes/kubernetes/pull/137002), [@kairosci](https://github.com/kairosci)) [SIG Apps and Network] +- CustomResourceDefinitions: Fixed server-side apply field ownership tracking so that metadata ownership is correctly tracked for writes to the /status subresource. + Custom Resources: Fixed server-side apply field ownership to NOT be updates to metadata from the /status subresource since these writes are wiped for custom resources. ([#137689](https://github.com/kubernetes/kubernetes/pull/137689), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Network and Testing] +- DRA BindingConditions: when the DRABindingConditions feature was enabled, reusing the same claim among different pods may have caused a panic in the scheduler when deallocation happens in parallel (a rare race condition). ([#137371](https://github.com/kubernetes/kubernetes/pull/137371), [@pohly](https://github.com/pohly)) [SIG Node, Scheduling and Testing] +- Disallow setting a resize restart policy of `RestartContainer` on non-sidecar initContainers, as the resize of such containers has never been supported. ([#137458](https://github.com/kubernetes/kubernetes/pull/137458), [@natasha41575](https://github.com/natasha41575)) [SIG Apps, Node and Testing] +- Explicitly writes memory.min=0 for QoS cgroups when the calculated requests are zero ([#137637](https://github.com/kubernetes/kubernetes/pull/137637), [@QiWang19](https://github.com/QiWang19)) [SIG Node] +- Fix apiserver startup failure during upgrade when MultiCIDRServiceAllocator is enabled and the cluster has a large number of namespaces. The IP address repair controller now retries on Forbidden errors from admission plugins that are not yet ready. ([#137147](https://github.com/kubernetes/kubernetes/pull/137147), [@haojiwu](https://github.com/haojiwu)) [SIG Testing] +- Fix bug where users can't update HPAv2 resources that use object metrics with averageValue via the v1 HPA API ([#137856](https://github.com/kubernetes/kubernetes/pull/137856), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Autoscaling] +- Fix container restart policy validation error message to correctly show available actions when RestartAllContainersOnContainerExits feature gate is enabled ([#137369](https://github.com/kubernetes/kubernetes/pull/137369), [@kfess](https://github.com/kfess)) [SIG Apps] +- Fix goroutine hot-loop in client-go StartEventWatcher when the event broadcaster shuts down before the cancellation context fires. ([#137398](https://github.com/kubernetes/kubernetes/pull/137398), [@Rajneesh180](https://github.com/Rajneesh180)) [SIG API Machinery] +- Fix informer-gen to generate SetTransform calls that correctly override per-informer transforms. ([#137473](https://github.com/kubernetes/kubernetes/pull/137473), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery and Scheduling] +- Fix link file ownership of projected serviceAccountToken. ([#137332](https://github.com/kubernetes/kubernetes/pull/137332), [@gavinkflam](https://github.com/gavinkflam)) [SIG Storage] +- Fixed a bug preventing Pods sharing ResourceClaims from being scheduled with GangScheduling. ([#137647](https://github.com/kubernetes/kubernetes/pull/137647), [@nojnhuh](https://github.com/nojnhuh)) [SIG Node, Scheduling and Testing] +- Fixed a bug where, after a kubelet restart, regular containers in a pod with a + sidecar (initContainer with restartPolicy: Always) and a startupProbe failed + to restart after crashing. Affected pods remained stuck with RestartCount: 0 + indefinitely. ([#137146](https://github.com/kubernetes/kubernetes/pull/137146), [@george-angel](https://github.com/george-angel)) [SIG Node and Testing] +- Fixed an issue where zero-valued PSI (Pressure Stall Information) metrics were emitted by the kubelet when the OS does not support PSI, even if the KubeletPSI feature gate was enabled. ([#137326](https://github.com/kubernetes/kubernetes/pull/137326), [@amritansh1502](https://github.com/amritansh1502)) [SIG Node] +- Fixed how image names are compared to the values from `preloadedImagesVerificationAllowlist` in Kubelet's configuration. Previously, the use of "familiar" image names (e.g. "alpine") from a Pod wouldn't properly match the same name in `preloadedImagesVerificationAllowlist` in Kubelet's configuration. ([#137629](https://github.com/kubernetes/kubernetes/pull/137629), [@stlaz](https://github.com/stlaz)) [SIG Auth, Node and Testing] +- Fixed kubectl describe node to correctly display resource requests and limits for pods using pod-level resources. ([#137394](https://github.com/kubernetes/kubernetes/pull/137394), [@Nikateen](https://github.com/Nikateen)) [SIG CLI] +- Fixed redundant SSH command executions in the etcd failure e2e test. ([#137001](https://github.com/kubernetes/kubernetes/pull/137001), [@kairosci](https://github.com/kairosci)) [SIG API Machinery and Testing] +- Fixed the lastTerminationStatus to match RestartAllContainers action if the container was restarted this way. ([#136964](https://github.com/kubernetes/kubernetes/pull/136964), [@yuanwang04](https://github.com/yuanwang04)) [SIG Node] +- Fixed validation error messages for restartPolicyRules and exitCodes.values to report "items" instead of "bytes" ([#137136](https://github.com/kubernetes/kubernetes/pull/137136), [@kfess](https://github.com/kfess)) [SIG Apps] +- Fixes incorrect behavior when using AllocationModeAll with DRA PrioritizedList that prevented the allocator from successfully allocating a claim even when devices were available. ([#137347](https://github.com/kubernetes/kubernetes/pull/137347), [@mortent](https://github.com/mortent)) [SIG Node] +- Fixes kube-proxy's nftables mode to work on systems with nft 1.1.3. ([#137501](https://github.com/kubernetes/kubernetes/pull/137501), [@danwinship](https://github.com/danwinship)) [SIG Network] +- Fixes the total pod resources computation ([#137683](https://github.com/kubernetes/kubernetes/pull/137683), [@ndixita](https://github.com/ndixita)) [SIG CLI and Node] +- Garbage collector now correctly handles objects deleted externally, preventing spurious error logs. ([#136817](https://github.com/kubernetes/kubernetes/pull/136817), [@kairosci](https://github.com/kairosci)) [SIG API Machinery, Apps and Testing] +- Improved a misleading error message when updating `batch.Job`'s `status.startTime`. The error for unsuspended jobs now correctly indicates the field is immutable once set, instead of incorrectly referring to the action as a "removal". ([#136585](https://github.com/kubernetes/kubernetes/pull/136585), [@zhzhuang-zju](https://github.com/zhzhuang-zju)) [SIG Apps] +- K8s.io/client-go/transport now automatically reloads certificate authority roots from disk when they are supplied via a file path. This functionality is enabled by default and can be disabled via the ClientsAllowCARotation feature gate. ([#132922](https://github.com/kubernetes/kubernetes/pull/132922), [@yt2985](https://github.com/yt2985)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Instrumentation, Network, Node, Release, Scheduling and Testing] +- K8s.io/client-go/transport now garbage collects TLS cache entries and client certificate rotation go routines when a transport is no longer used. This functionality is enabled by default and can be controlled via the ClientsAllowTLSCacheGC feature gate. Binaries embedding k8s.io/client-go, but not wiring the feature gates can disable it by setting the KUBE_FEATURE_ClientsAllowTLSCacheGC=false environment variable. When the feature is disabled, the TLS cache can grow indefinitely and client certificate rotation go routines are leaked. The new rest_client_transport_cert_rotation_gc_calls_total{} and rest_client_transport_cache_gc_calls_total{result: deleted/skipped} counter metrics can be used with the preexisting rest_client_transport_* metrics to help with debugging. ([#136355](https://github.com/kubernetes/kubernetes/pull/136355), [@enj](https://github.com/enj)) [SIG API Machinery, Architecture, Auth, Instrumentation, Node and Testing] +- Kubeadm: ignore EINVAL when unmounting /var/lib/kubelet peer mounts during reset ([#137494](https://github.com/kubernetes/kubernetes/pull/137494), [@fuweid](https://github.com/fuweid)) [SIG Cluster Lifecycle] +- Kubelet now sets `PodReadyToStartContainers` condition immediately after sandbox creation rather than after image pull, reducing the time to condition True. ([#134660](https://github.com/kubernetes/kubernetes/pull/134660), [@Priyankasaggu11929](https://github.com/Priyankasaggu11929)) [SIG Apps, Node and Testing] +- Kubelet: relist pods on-demand for lower latency operations. Guarded by the new beta feature gate "PLEGOnDemandRelist". ([#137362](https://github.com/kubernetes/kubernetes/pull/137362), [@tallclair](https://github.com/tallclair)) [SIG Node] +- Kubelet_pod_start_sli_duration_seconds_bucket metric now matches pod startup latency SLI/SLO documentation. ([#131950](https://github.com/kubernetes/kubernetes/pull/131950), [@alimaazamat](https://github.com/alimaazamat)) [SIG Node] +- Kubernetes is now built using Go 1.26.1 ([#137474](https://github.com/kubernetes/kubernetes/pull/137474), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release and Testing] +- Previously, when trying to allocate devices through DRA for a node timed out, scheduling would proceed with another node if any had the necessary resources. This potentially hid that a node was ignored. Worse, if scheduling was slow overall, the pod was incorrectly moved to "unschedulable" and only retried after a periodic sweep. Now timeouts are errors that are always visible as pod scheduling failures and get retried with per-pod exponential backoff. ([#137607](https://github.com/kubernetes/kubernetes/pull/137607), [@0xMH](https://github.com/0xMH)) [SIG Node, Scheduling and Testing] +- Reflecting the expected replica count to the output of kubectl scale command ([#136945](https://github.com/kubernetes/kubernetes/pull/136945), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] +- Remove `GuaranteedQoSPodCPUResize` from node declared features. ([#136759](https://github.com/kubernetes/kubernetes/pull/136759), [@pravk03](https://github.com/pravk03)) [SIG Node and Testing] +- Validation messages for a Pod's `status.resourceClaimStatuses[].resourceClaimName` now refer correctly to the `resourceClaimName` field instead of the `name` field. ([#137321](https://github.com/kubernetes/kubernetes/pull/137321), [@nojnhuh](https://github.com/nojnhuh)) [SIG Apps] +- Writes to the ServiceCIDR main resource now ignore status field changes in the request, consistent with all other Kubernetes APIs. + The ServiceCIDRStatusFieldWiping feature gate can be disabled to restore the previous behavior; it will be locked to enabled in a future release. ([#137715](https://github.com/kubernetes/kubernetes/pull/137715), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Network and Testing] + +### Other (Cleanup or Flake) + +- Cri-client helper method NewLogOptions was removed and LogOptions must be constructed directly. This eliminates the unwanted depdendency from cri-client to apimachinery. ([#137827](https://github.com/kubernetes/kubernetes/pull/137827), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Node and Release] +- For performance reasons, `kubectl describe` now defaults to showing related events only when describing a single object. Passing `--show-events` explicitly when describing multiple objects or fuzzy matching on prefix will still show related events if desired. ([#137145](https://github.com/kubernetes/kubernetes/pull/137145), [@mark-liu](https://github.com/mark-liu)) [SIG CLI] +- Improve stability by sorting containers by create time and ID in kubeGenericRuntimeManager.GetPods() and GetPod() ([#137566](https://github.com/kubernetes/kubernetes/pull/137566), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG Node] +- Promote HPA metrics: reconciliations_total,reconciliation_duration_seconds,metric_computation_total,metric_computation_duration_seconds to beta ([#136178](https://github.com/kubernetes/kubernetes/pull/136178), [@omerap12](https://github.com/omerap12)) [SIG Apps, Autoscaling and Instrumentation] +- Promote `SELinuxChangePolicy` & `SELinuxMountReadWriteOncePod` to GA; it is now enabled unconditionally. ([#136912](https://github.com/kubernetes/kubernetes/pull/136912), [@dfajmon](https://github.com/dfajmon)) [SIG Apps, Storage and Testing] +- Reduced get PV request from KCM pv-controller for CSI volumes ([#134290](https://github.com/kubernetes/kubernetes/pull/134290), [@huww98](https://github.com/huww98)) [SIG Apps and Storage] +- Removed misleading `SuggestFor` entries from `kubectl wait` so that it is no longer suggested when users type `kubectl list` or `kubectl ps` ([#137266](https://github.com/kubernetes/kubernetes/pull/137266), [@kfess](https://github.com/kfess)) [SIG CLI and Testing] +- Removes the dead `--bounding-dirs` flag and `BoundingDirs` field from deepcopy-gen. ([#137348](https://github.com/kubernetes/kubernetes/pull/137348), [@Jefftree](https://github.com/Jefftree)) [SIG API Machinery] +- The "Failed to update lease optimistically" log message may not be shown to users anymore, depending on the log level they have set. ([#137753](https://github.com/kubernetes/kubernetes/pull/137753), [@adamkasztenny](https://github.com/adamkasztenny)) [SIG API Machinery] +- The GetPCIeRootAttributeByPCIBusID helper now accepts a `fs.ReadLinkFS` optional argument to be filesystem-independenent ([#137220](https://github.com/kubernetes/kubernetes/pull/137220), [@ffromani](https://github.com/ffromani)) [SIG Node] +- The cri-api client is now accepts a context and do not accept logger on iniitalization. ([#137248](https://github.com/kubernetes/kubernetes/pull/137248), [@SergeyKanzhelev](https://github.com/SergeyKanzhelev)) [SIG Cluster Lifecycle, Node and Testing] +- Truncates the watch cache RV metric to 15 digits to ensure precision ([#137615](https://github.com/kubernetes/kubernetes/pull/137615), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Instrumentation] +- `v1alpha1` WebhookAdmissionConfiguration has been removed. It was deprecated in v1.17 in favor of `apiserver.config.k8s.io/v1`. ([#137379](https://github.com/kubernetes/kubernetes/pull/137379), [@aramase](https://github.com/aramase)) [SIG API Machinery and Testing] + +## Dependencies + +### Added +_Nothing has changed._ + +### Changed +- cel.dev/expr: v0.24.0 → v0.25.1 +- github.com/cncf/xds/go: [0feb691 → ee656c7](https://github.com/cncf/xds/compare/0feb691...ee656c7) +- github.com/coredns/corefile-migration: [v1.0.30 → v1.0.31](https://github.com/coredns/corefile-migration/compare/v1.0.30...v1.0.31) +- github.com/envoyproxy/go-control-plane/envoy: [v1.35.0 → v1.36.0](https://github.com/envoyproxy/go-control-plane/compare/envoy/v1.35.0...envoy/v1.36.0) +- github.com/envoyproxy/go-control-plane: [75eaa19 → v0.14.0](https://github.com/envoyproxy/go-control-plane/compare/75eaa19...v0.14.0) +- github.com/envoyproxy/protoc-gen-validate: [v1.2.1 → v1.3.0](https://github.com/envoyproxy/protoc-gen-validate/compare/v1.2.1...v1.3.0) +- github.com/google/cadvisor: [v0.56.0 → v0.56.2](https://github.com/google/cadvisor/compare/v0.56.0...v0.56.2) +- github.com/google/pprof: [27863c8 → 294ebfa](https://github.com/google/pprof/compare/27863c8...294ebfa) +- github.com/ianlancetaylor/demangle: [bd984b5 → f615e6b](https://github.com/ianlancetaylor/demangle/compare/bd984b5...f615e6b) +- github.com/onsi/ginkgo/v2: [v2.27.4 → v2.28.1](https://github.com/onsi/ginkgo/compare/v2.27.4...v2.28.1) +- github.com/onsi/gomega: [v1.39.0 → v1.39.1](https://github.com/onsi/gomega/compare/v1.39.0...v1.39.1) +- github.com/spf13/cobra: [v1.10.0 → v1.10.2](https://github.com/spf13/cobra/compare/v1.10.0...v1.10.2) +- go.opentelemetry.io/contrib/detectors/gcp: v1.38.0 → v1.39.0 +- golang.org/x/telemetry: 8fff8a5 → bd525da +- golang.org/x/tools: v0.40.0 → v0.41.0 +- google.golang.org/grpc: v1.78.0 → v1.79.3 +- google.golang.org/protobuf: v1.36.11 → f2248ac +- k8s.io/klog/v2: v2.130.1 → v2.140.0 +- k8s.io/kube-openapi: a19766b → 43fb72c +- sigs.k8s.io/knftables: v0.0.17 → v0.0.21 + +### Removed +- go.uber.org/automaxprocs: v1.6.0 + + + +# v1.36.0-alpha.2 + + +## Downloads for v1.36.0-alpha.2 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes.tar.gz) | 7aa0ed3b03a1574ec0db00d47381e3c76610be89f5ceb52145add9f01533d72833b9594499e0a9af43c41df38d4c448906689cd5662532f32728e11f0f0b39d7 +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-src.tar.gz) | a6858468f30d207b375dd2278d8653c69211b364136b212e84ee563bba6ca2bfe89d5ecefda3bc88671ee0c901f02d0f50eefe1e12b7cc58a842fad90811bd23 + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-darwin-amd64.tar.gz) | 2dde9c67ba2d165e46d4e09c886b8b17563f3767f6fcb5ed98883dfe5bd2ecd7f70af94b0d71678a36916f8d6a6ba1cd3b2c9b4e8b7084e94dfef075291e32c5 +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-darwin-arm64.tar.gz) | 70178cdf6431041bcd6bcd55e67ba978ec5fe53a47ded53d6fa932e65af8742a47099de5c20fb44da279e957abf57f54281b4ce8f7f7e638810c89851751c7fd +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-linux-386.tar.gz) | 5ca76f2388552d2a4d601a19c0120d8db65f8f151ac2223a69cf426523de7d2d9532104d5464b15a588cb3c283203069f5347edd5a3ac912d459d7c703467b81 +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-linux-amd64.tar.gz) | f9901ed9e5a1d9b8638a2ac84e65e7986c81d2d72d086aa14cf298397d72a290e6b22b644cb74aa61ff0181df6e3ca38103863289679d4ae00195827a3333c93 +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-linux-arm.tar.gz) | a347d318571485aa21325ba4de7dd4afa29bffa4b8060ed04a9ef3afe7cea4f12c520e6eebc1b96e991147f0123096a1a745f905b687baa6981d1f8d16971452 +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-linux-arm64.tar.gz) | 2e493202a0023af3cff5f5e6d19307791a994eac931f18fe33fe5016861d5b17690b75aece91c02b5e022d4e00a824e85cb7fdc43ca45e238cea16d202ef7ec6 +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-linux-ppc64le.tar.gz) | 9feebb6cca23158f299085945e1ff6db5c1742a72d157f89d837811f84db5f9cb9b5db042f2898840a86839621b89066a1ccc925cd2a981af64bbf513d94e229 +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-linux-s390x.tar.gz) | c8a42085199cf548119fd2af703c84cc1d2fda61086b50e09d03ea18cd0aaf28063d21eb258b64c30884022c6ab81b78bfd6d848fc4bf04b410d2972eb23266f +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-windows-386.tar.gz) | ccb9320af60720149913f39a88d4abdf5c9d53d3ddd396a61504288576e6f2f27ef21cb26a23e27e34b4c56c95eb1daa54ed389460c51660074a92a3c34eee23 +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-windows-amd64.tar.gz) | c068c174870aca6710c688088b46bee4e852ea97be7e8c02aad10bef81d1a3e6485b50ff41bee11121cce357f74cf54d67f65df04087f590ff6259bd03eb2d11 +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-client-windows-arm64.tar.gz) | 9247e597dab4fb45facc3c32d4b2772e7bb5c9814a1f5ae64b5fdba5469784957df154a7b7aedc99e79e4fd81e07f7fe2b0eb492e17492fba497e94e5e501a8f + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-server-linux-amd64.tar.gz) | ef279ca406ed3e939972353287b9ad769df0915749c5ffe61f096b6f75ab7604ff03177affc8c66a2dd688fe8b3fb6c1a7e8deabb4985fa7f40517f483fc5983 +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-server-linux-arm64.tar.gz) | a3414c851a8c0cf42abfb1ba2e17fb40b31b4d2cd9a4224ed8ecfc951b127a829f4ab14fa2cf68dbabfbe19aa6212f09c207d25518832b13e9f81986415a1a88 +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-server-linux-ppc64le.tar.gz) | 841d2a9dd87ab1232d79611c855c1dd2d9aca11afdd4d790692e38894ef6fd37b382c68aacaea32daf71b39f196bea2448d860ce9ed70b03766d83895779a68b +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-server-linux-s390x.tar.gz) | ec89bea22f65a612eb0e52b03c6d277c35f5f2e2cbbc4cd50510f013b4d7ca6702f47017dcb417e1cc257017b1755ae02f6c21fac0d44881be2814aed0130127 + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-node-linux-amd64.tar.gz) | 37fca1f367e1b78035fd95f1bc547fca89018fc42f450e3660d6f108890e6f319a1e94bf52584178558101375846baf7721a59e88ef3a22f8a493381bbc2a6e1 +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-node-linux-arm64.tar.gz) | 6ddbfbe217414c874d2367cdba0943d9a8ead4c98bb4e2ccaec64cdb2a0218bae46f108b4fb3ba949f78707cc47477fd0b97dc2291a83d5c08832201a185f595 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-node-linux-ppc64le.tar.gz) | 6e1707b097daae5e2a68ffd068245034e056ee49d96a70bfce430de3b752ddc148027a9b94155d77da270df617d9450b59e02749fc06a41622e48c9505f00553 +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-node-linux-s390x.tar.gz) | 12674c621376b790dc2421a113ce3ca226af8d649a65d465ddf0ce29f21ecea39f74283b39ced2f8b894595b86f0c40bfcaea37e9d7b44e95d1f8cdb0f01e6b8 +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.2/kubernetes-node-windows-amd64.tar.gz) | 25e2cd21678e8051716172074a8cc9efeb53e8ff275a15f9d0e801616804b9cbb1a5b16378e057b905dad75ce0e94e9837934343374bef87d4bf9843c8b3ef76 + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.0-alpha.2](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.36.0-alpha.1 + +## Urgent Upgrade Notes + +### (No, really, you MUST read this before you upgrade) + + - Extended PostFilterResult with a list of Pods that the PostFilter plugin preempted. + PostFilter plugin implementations need to be updated to return the preempted Pods (if any) in the PostFilterResult from the PostFilter method; returning nil means the plugin did not preempt any Pods at all. ([#136254](https://github.com/kubernetes/kubernetes/pull/136254), [@tosi3k](https://github.com/tosi3k)) [SIG Scheduling] + - Kubeadm: removed the integrated support for flex-volumes in kubeadm. Users were advised to migrate away from flex-volumes as recommended by SIG Storage, since 1.22. If kubeadm users wish to continue using the feature, they would need a custom image for the KCM that is not based on distroless, pass the KCM flag `--flex-volume-plugin-dir` and mount the directory `/usr/libexec/kubernetes/kubelet-plugins/volume/exec` in the KCM static pod using kubeadm's `extraVolumes` mechanism before upgrading to 1.36. Up until now, kubeadm automatically did the mounting if the user passed the flag. ([#136423](https://github.com/kubernetes/kubernetes/pull/136423), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] + +## Changes by Kind + +### Dependency + +- Updates the etcd client library to v3.6.8 ([#137225](https://github.com/kubernetes/kubernetes/pull/137225), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Auth, Cloud Provider, Cluster Lifecycle, Etcd, Node, Scheduling and Testing] + +### Deprecation + +- Disabled git-repo volume plugin by default, with no option to turn it back on. ([#136400](https://github.com/kubernetes/kubernetes/pull/136400), [@vinayakankugoyal](https://github.com/vinayakankugoyal)) [SIG Storage] + +### API Change + +- Add a new concurrent-node-status-updates flag that is split from the concurrent-node-syncs flag ([#136716](https://github.com/kubernetes/kubernetes/pull/136716), [@yonizxz](https://github.com/yonizxz)) [SIG Cloud Provider] +- Fixed an issue in kube-apiserver, allowing it to recover from an established connection to an incorrect server that never returns the expected response during APIService availability checks. ([#137157](https://github.com/kubernetes/kubernetes/pull/137157), [@bsalamat](https://github.com/bsalamat)) [SIG API Machinery] +- Graduated MutatingAdmissionPolicy to GA (v1) in Kubernetes 1.36. The feature is now enabled by default. ([#136039](https://github.com/kubernetes/kubernetes/pull/136039), [@lalitc375](https://github.com/lalitc375)) [SIG API Machinery, Architecture, Etcd and Testing] +- Introduced stability-based lifecycle for declarative validation (Alpha/Beta/Stable). Scheduling Workload v1alpha1 now uses explicit declarative enforcement. ([#136793](https://github.com/kubernetes/kubernetes/pull/136793), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery and Scheduling] +- K8s.io/api: REST API types no longer implement marker `ProtoMessage()` methods, identifying them as standard v1 proto messages. Protobuf serialization of Kubernetes API types should use [k8s.io/apimachinery/pkg/runtime/serializer/protobuf](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime/serializer/protobuf). See [KEP-5589](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5589-gogo-dependency) for more details. ([#137084](https://github.com/kubernetes/kubernetes/pull/137084), [@liggitt](https://github.com/liggitt)) [SIG API Machinery, Apps, Architecture, Auth, Node, Scheduling and Storage] +- Pod Certificates (beta) now includes a PKCS#10 certificate signing request for wider compatibility with existing certificate authority software. ([#136729](https://github.com/kubernetes/kubernetes/pull/136729), [@ahmedtd](https://github.com/ahmedtd)) [SIG API Machinery, Auth, Node and Testing] +- Promoted several component-base metrics (`kubernetes_build_info`, `rest_client_requests_total`, `rest_client_request_duration_seconds`, `running_managed_controllers`) from Alpha to Beta stability, providing stronger API and label stability guarantees for consumers. ([#136154](https://github.com/kubernetes/kubernetes/pull/136154), [@bhope](https://github.com/bhope)) [SIG API Machinery, Apps, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Etcd, Instrumentation, Network, Node, Scalability, Scheduling, Storage and Testing] +- Replace deprecated sets.String with sets.Set[string] in apiserver admission subsystem. This is a breaking change for consumers of the NewLifecycle function. ([#134044](https://github.com/kubernetes/kubernetes/pull/134044), [@mcallzbl](https://github.com/mcallzbl)) [SIG API Machinery and Auth] +- Updates API server internal API group to improve openapi schema correctness for fields being optional or required ([#134675](https://github.com/kubernetes/kubernetes/pull/134675), [@JoelSpeed](https://github.com/JoelSpeed)) [SIG API Machinery, Apps, Auth, Node and Storage] + +### Feature + +- Add an alpha informer_processing_latency_seconds histogram metric to measure event handler execution time in RealFIFO. ([#137101](https://github.com/kubernetes/kubernetes/pull/137101), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation and Testing] +- Add the kubelet_metrics_provider metric to help users identify where kubelet's metrics are coming from. ([#136952](https://github.com/kubernetes/kubernetes/pull/136952), [@dgrisonnet](https://github.com/dgrisonnet)) [SIG Node] +- Added a show-secret flag to the diff command to explicitly allow secret values to be displayed during the diff operation. ([#137019](https://github.com/kubernetes/kubernetes/pull/137019), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Adds alpha metrics `apiserver_peer_proxy_errors_total` and `apiserver_peer_discovery_sync_errors_total` to apiserver to track errors encountered in peer proxying and peer discovery ([#137065](https://github.com/kubernetes/kubernetes/pull/137065), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Client-go: informer stores now keep track the resourceVersion they are synced to (via add/update/delete events, or replace calls, or bookmark events), and provide a `LastStoreSyncResourceVersion` method to obtain this resource version. This method can return `""` if the store has not been synced to yet, and depends on the AtomicFIFO feature being enabled. ([#134827](https://github.com/kubernetes/kubernetes/pull/134827), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Testing] +- Introduced PodGroup scheduling cycle in kube-scheduler to schedule entire PodGroup in one cycle. ([#136618](https://github.com/kubernetes/kubernetes/pull/136618), [@macsko](https://github.com/macsko)) [SIG Scheduling and Testing] +- K8s.io/cloud-provider: Adds missing TLS flags to webhook serving options ([#136816](https://github.com/kubernetes/kubernetes/pull/136816), [@damdo](https://github.com/damdo)) [SIG Cloud Provider] +- Kube-controller-manager: The daemonset controller now defers syncing a DaemonSet object when the controller has not yet observed daemonset or pod writes from the last time the object was synced. This prevents spurious creation of duplicate pods for nodes when the controller's cache is stale. When a sync is deferred for this reason, a `daemonset_controller_stale_sync_skips_total` metric is incremented and a message is logged by the daemonset controller. This behavior can be temporarily disabled by setting the `StaleControllerConsistencyDaemonSet` feature gate to false. ([#134937](https://github.com/kubernetes/kubernetes/pull/134937), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery, Apps, Node, Scheduling and Testing] +- Kube-controller-manager: The job controller now defers syncing a Job object when the controller has not yet observed job or pod writes from the last time the object was synced. This prevents spurious creation of duplicate pods for jobs when the controller's cache is stale. When a sync is deferred for this reason, a `job_controller_stale_sync_skips_total` metric is incremented and a message is logged by the job controller. This behavior can be temporarily disabled by setting the `StaleControllerConsistencyJob` feature gate to false. ([#137210](https://github.com/kubernetes/kubernetes/pull/137210), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Apps] +- Kubeadm: the preflight check `ContainerRuntimeVersion` validates if the installed container runtime supports the `RuntimeConfig` gRPC method. For older kubelet versions than 1.37, it will return a preflight warning. ([#136898](https://github.com/kubernetes/kubernetes/pull/136898), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] +- Kubelet: defer the configurations flags (and the related fallback behavior) deprecation removal timeline from 1.36 to 1.37 to align with containerd v1.7 support ([#136846](https://github.com/kubernetes/kubernetes/pull/136846), [@carlory](https://github.com/carlory)) [SIG Node and Testing] +- Kubernetes is now built using Go 1.25.7 ([#136982](https://github.com/kubernetes/kubernetes/pull/136982), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] +- Pods that are preempted when in PreBind phase will go back to backoff queue instead of being completely deleted from apisever. ([#135502](https://github.com/kubernetes/kubernetes/pull/135502), [@Argh4k](https://github.com/Argh4k)) [SIG Scheduling and Testing] +- Prevent the replicaset controller from spuriously reconciling while its own writes have not been read. ([#137212](https://github.com/kubernetes/kubernetes/pull/137212), [@michaelasp](https://github.com/michaelasp)) [SIG Apps] +- Updated feature gate `MutablePodResourcesForSuspendedJobs` and `MutableSchedulingDirectivesForSuspendedJobs` to be enabled by default. ([#135965](https://github.com/kubernetes/kubernetes/pull/135965), [@kannon92](https://github.com/kannon92)) [SIG Apps and Testing] + +### Failing Test + +- (reverts #136796, so ignore the release note from there...) ([#137169](https://github.com/kubernetes/kubernetes/pull/137169), [@danwinship](https://github.com/danwinship)) [SIG Network] + +### Bug or Regression + +- Extend unsupported Table object detection from watchlist only to all List and Watch operations. This prevents the reflector from processing resources returned in Table format which it cannot properly handle ([#136937](https://github.com/kubernetes/kubernetes/pull/136937), [@p0lyn0mial](https://github.com/p0lyn0mial)) [SIG API Machinery and Testing] +- Fix PodCertificateRequest OwnerReference using incorrect apiVersion "core/v1" instead of "v1", which prevented garbage collection of PodCertificateRequests when their owning Pod was deleted. ([#137008](https://github.com/kubernetes/kubernetes/pull/137008), [@srhppr](https://github.com/srhppr)) [SIG Auth and Node] +- Fix a bug where the DRA manager did not initialize sharedID from cache when DRAConsumableCapacity is enabled. ([#136734](https://github.com/kubernetes/kubernetes/pull/136734), [@sunya-ch](https://github.com/sunya-ch)) [SIG Node and Scheduling] +- Fixed /metrics/resource container_swap_usage_bytes to report the correct container swap usage ([#137098](https://github.com/kubernetes/kubernetes/pull/137098), [@yuanwang04](https://github.com/yuanwang04)) [SIG Apps, Node and Testing] +- Fixed a bug where `kubectl plugin list` failed to detect overshadowed plugins on Windows. ([#136689](https://github.com/kubernetes/kubernetes/pull/136689), [@kfess](https://github.com/kfess)) [SIG CLI] +- Fixed a bug where the event_handling_duration_seconds/preemption_goroutines_duration_seconds/run_podsandbox_duration_seconds/store_schedule_results_duration_seconds metric was recording + near-zero latency values instead of actual value. ([#135749](https://github.com/kubernetes/kubernetes/pull/135749), [@novahe](https://github.com/novahe)) [SIG Architecture, Instrumentation, Node and Scheduling] +- Fixed a data race in k8s.io/apiserver/pkg/cel/openapi/resolver with (probably) no real-world impact. ([#136802](https://github.com/kubernetes/kubernetes/pull/136802), [@pohly](https://github.com/pohly)) [SIG API Machinery, Node and Testing] +- Fixed kubectl logs -f to wait for containers to start instead of failing + immediately when pods are in ContainerCreating or PodInitializing states ([#136411](https://github.com/kubernetes/kubernetes/pull/136411), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Fixes kube-proxy log spam when all of a Service's endpoints were unready. ([#136743](https://github.com/kubernetes/kubernetes/pull/136743), [@ansilh](https://github.com/ansilh)) [SIG Network] +- Fixes kube-proxy's nftables mode to work on systems with nft 1.1.3. ([#136796](https://github.com/kubernetes/kubernetes/pull/136796), [@kairosci](https://github.com/kairosci)) [SIG API Machinery, Auth and Network] +- Kubeadm: do not add learner member to etcd client endpoints ([#137251](https://github.com/kubernetes/kubernetes/pull/137251), [@pacoxu](https://github.com/pacoxu)) [SIG Cluster Lifecycle] +- The metrics `container_cpu_load_average_10s`, `container_cpu_load_d_average_10s`, and `cpu_tasks_state` have been dropped from the reported metrics by cadvisor. This is done because the values were always 0, because a flag was not enabled in the kubelet. ([#134981](https://github.com/kubernetes/kubernetes/pull/134981), [@haircommander](https://github.com/haircommander)) [SIG Node and Testing] + +### Other (Cleanup or Flake) + +- Apiserver_rerouted_request_total metric will expose labels for group, version and resource. ([#137063](https://github.com/kubernetes/kubernetes/pull/137063), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Removed the generally available feature gate `HonorPVReclaimPolicy`, which was locked and enabled since 1.33. ([#135335](https://github.com/kubernetes/kubernetes/pull/135335), [@carlory](https://github.com/carlory)) [SIG Apps and Storage] +- The deprecated SeparateCacheWatchRPC feature gate is now locked to its default value (false) and can no longer be overridden. The feature gate will be removed in a future release. ([#135808](https://github.com/kubernetes/kubernetes/pull/135808), [@tico88612](https://github.com/tico88612)) [SIG API Machinery] +- Update etcd images to v3.6.8 ([#137107](https://github.com/kubernetes/kubernetes/pull/137107), [@joshjms](https://github.com/joshjms)) [SIG API Machinery, Cloud Provider, Cluster Lifecycle, Etcd and Testing] +- Upgrades functionality of `kubectl kustomize` as described at https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv5.8.1 ([#136892](https://github.com/kubernetes/kubernetes/pull/136892), [@koba1t](https://github.com/koba1t)) [SIG Architecture and CLI] + +## Dependencies + +### Added +_Nothing has changed._ + +### Changed +- github.com/grpc-ecosystem/grpc-gateway/v2: [v2.27.4 → v2.27.7](https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.27.4...v2.27.7) +- github.com/sergi/go-diff: [v1.2.0 → v1.4.0](https://github.com/sergi/go-diff/compare/v1.2.0...v1.4.0) +- go.etcd.io/etcd/api/v3: v3.6.7 → v3.6.8 +- go.etcd.io/etcd/client/pkg/v3: v3.6.7 → v3.6.8 +- go.etcd.io/etcd/client/v3: v3.6.7 → v3.6.8 +- go.etcd.io/etcd/pkg/v3: v3.6.7 → v3.6.8 +- go.etcd.io/etcd/server/v3: v3.6.7 → v3.6.8 +- go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful: v0.64.0 → v0.65.0 +- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc: v0.63.0 → v0.65.0 +- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp: v0.64.0 → v0.65.0 +- go.opentelemetry.io/contrib/propagators/b3: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/exporters/otlp/otlptrace: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/exporters/stdout/stdouttrace: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/metric: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/sdk/metric: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/sdk: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel/trace: v1.39.0 → v1.40.0 +- go.opentelemetry.io/otel: v1.39.0 → v1.40.0 +- google.golang.org/genproto/googleapis/api: 99fd39f → 8636f87 +- google.golang.org/genproto/googleapis/rpc: 99fd39f → 8636f87 +- k8s.io/utils: 914a6e7 → b8788ab +- sigs.k8s.io/kustomize/api: v0.20.1 → v0.21.1 +- sigs.k8s.io/kustomize/cmd/config: v0.20.1 → v0.21.1 +- sigs.k8s.io/kustomize/kustomize/v5: v5.7.1 → v5.8.1 +- sigs.k8s.io/kustomize/kyaml: v0.20.1 → v0.21.1 +- sigs.k8s.io/structured-merge-diff/v6: v6.3.1 → v6.3.2 + +### Removed +- github.com/pkg/errors: [v0.9.1](https://github.com/pkg/errors/tree/v0.9.1) + + + +# v1.36.0-alpha.1 + + +## Downloads for v1.36.0-alpha.1 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes.tar.gz) | 79ec354722859240b1d715c0da181e5a2a0fd354984ae9d511d58e7c09ec5cf7e54421db38a6d96b409b9a08b1c1b9dc13e7df20e7ab21299837d00cf72f162c +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-src.tar.gz) | f274bff791b16bb4de10730aabbfc027220f45c44d2dc8d1a8b575cc86421ec01fb106bcb2f3cb137145e64396ca37f2ec689932395162dcae5d3b6b65fc97ec + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-darwin-amd64.tar.gz) | 6fc2c7b184ee6435c0e7179dbe8ff63549631d9a5eb28262b10596a6f26e245ab2cf16402a6466e37b81a42760f811808796d1e83dd205125c0e64e1330772bd +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-darwin-arm64.tar.gz) | e56eb183ba431d530b6cbd83ee94c1c398f3f4969cdee247092738a5cbe2b567d705788f95adec2f13cf17ebd791165903a0f1fcac9fcbf36ed65b9a00f38ac3 +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-linux-386.tar.gz) | 1a65eb81a4cf1631fa6fb102f2dfcffd29732bb96479c0432c9780f2dcb4600f8c85991b0f68f038ae963348d39291c74a855a71705093ecc09218ee4ed5271c +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-linux-amd64.tar.gz) | a1824ed2091dac2289c99c67e504b01b0176657675496752136a630ecf57347e0a5578a21c3bd74d0baad995d1e99ba0c78b5dfc2a61316227171c25a216111b +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-linux-arm.tar.gz) | 9e368482df69b6990c917d6df0f3589851d72bcd7304226970eae32898baeb76e5955e93bda577d72231ca2342562ab91dfecb19dd5479f91352a4577f8f7d92 +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-linux-arm64.tar.gz) | 7894d5868aa7888a26648ea338fbe63031e2b3ce0919a337e69cba002c369a0bbe6971ed7add1fd5e2284ebc696f9bb9c0180c298fe349140482b2e93a51d72a +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-linux-ppc64le.tar.gz) | ae379c93762b86b8ddd1f1076b2e37c2866991e5a5700eb08ff5b65aaeca552764c6bb0506376b502e67f1734f5655b59ca4aa751b572667a9522065822874f1 +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-linux-s390x.tar.gz) | 316d1093cd109d91b55c5ca18a8aa2d0e04feb29bb688b3d52018818329519c8fa6b226de8505de3ced920c064b8f150dccb2829d2bb2a4bed594e4d663377cd +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-windows-386.tar.gz) | 48f7ca49e081393474c644c31a7bae8810dfc7673c2f1800207960ea14e73616e1b7717d312e4787e8b5179c5a46a256cdbaa80e01cdf37cf999f370a17060e1 +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-windows-amd64.tar.gz) | d70670136a91a5f81f7b030f258881917371102e958382d3dfef425d5d76718cc242d7ee86dbaaaa5705e019b4178e323a29cc0d4ace6d803550ea8d412189e2 +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-client-windows-arm64.tar.gz) | 99c08b44870989a9629573f110317215e4ec177a25298e28f497deb75867838085b987a100c8ee678ccf1345e4cef075d159c5cb9198a3a342a92733309820d5 + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-server-linux-amd64.tar.gz) | 9208265b86d2d7ebf8dd8f771a586de571aa07ab54c1d7428deb8803dbf63f2e396e30b103cac4da8ddf791be8b66dfce90e428906ec2d59e485953a6b6e1b7b +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-server-linux-arm64.tar.gz) | 704ce893ee7b239194aae37b608b175940efbf60072622a661d866221f6fdba9c6b6c4ca11008bf2be1cf2c70cb9f6e5aece9052efa2468c1e3b73510c35d2f0 +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-server-linux-ppc64le.tar.gz) | 99cc1cf4169b4c8a00b7e7e4f49c98703e0191dd0aa0e4f641a0b35a92e7d5df14587d5b56f571fa4d00290d806bbf916cb3d934537883f9364a04d66d0ba958 +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-server-linux-s390x.tar.gz) | ea9ac2489aa2b8d9a0bd8a8a12958b754edd0c9227aaee23dadf7fc7711cab5033edcb85dc545e6bd8cb78336e073b2cfb8ed8655932b91a4d8d22c3eb3dfb90 + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-node-linux-amd64.tar.gz) | 1cff3dd843e8ffbe2728967c3520bb000a551573fc9049fb4d3e6734d76a9ea72a2e54a0eca18bf68b0f023106714a877fb01b2e942720b707f738df1709361d +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-node-linux-arm64.tar.gz) | 55a65f51bc9d25ca2993bf2d277e1ceadc585d8af90e3f92b9b6fd682b7fa78f8fd0bb4bba127143825c70227d8815b037159feb8a319c0b4fb406e3b4dfa913 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-node-linux-ppc64le.tar.gz) | e67d17e2d38716e6d20a777d360454f0a0e04d8f5dc94f8888c823df55ddb96973e819229e043d0e9a7f25027f4f64583f13443d633f568b62237e1fcceae8b4 +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-node-linux-s390x.tar.gz) | 807376145bb55d7b534523c18fac4f61ddb8d20e0646c2152de28cd547f1ef82787f01e8b43a7ea1128a9c76c7ee14183e28629032396ff124629c1a6c6c9ab4 +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.36.0-alpha.1/kubernetes-node-windows-amd64.tar.gz) | 1f204db26af19933504f50292513ea3e238f57ac9bc8c4faad8a71271cd2c2920515ad0267ed87618ac4d6f510f111fe5a2feeebe084425b0f369905f577cac8 + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.36.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.36.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.36.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.36.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.36.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.36.0-alpha.1](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.35.0 + +## Urgent Upgrade Notes + +### (No, really, you MUST read this before you upgrade) + + - Added support for running PreBind plugins in parallel in the scheduler framework to improve the binding latency. + Plugins can now opt-in to parallel execution by returning `AllowParallel: true` from the PreBindPreFlight method. PreBind plugin implementations need to be updated to return the PreBindPreFlightResult from the PreBindPreFlight method; returning nil retains the existing sequential behavior. ([#135393](https://github.com/kubernetes/kubernetes/pull/135393), [@tosi3k](https://github.com/tosi3k)) [SIG Node, Scheduling, Storage and Testing] + +## Changes by Kind + +### Dependency + +- Fix a bug where pod lifecycle hooks could run for their full duration when pods are terminated. ([#136598](https://github.com/kubernetes/kubernetes/pull/136598), [@dgrisonnet](https://github.com/dgrisonnet)) [SIG API Machinery, Auth, Cloud Provider, Node and Scheduling] + +### API Change + +- Add --concurrent-resourceclaim-syncs to configure kube-controller-manager resource claim reconcile concurrency ([#134701](https://github.com/kubernetes/kubernetes/pull/134701), [@anson627](https://github.com/anson627)) [SIG API Machinery, Apps, Node and Testing] +- Added negative duration validation for imageMinimumGCAge ([#135997](https://github.com/kubernetes/kubernetes/pull/135997), [@ngopalak-redhat](https://github.com/ngopalak-redhat)) [SIG API Machinery and Node] +- Clarified documentation and comments to indicate that the `cpuCFSQuotaPeriod` kubelet config field requires the `CustomCPUCFSQuotaPeriod` feature gate when using non-default values. No functional changes introduced. ([#133845](https://github.com/kubernetes/kubernetes/pull/133845), [@rbiamru](https://github.com/rbiamru)) [SIG Node and Release] +- Correct openapi schema union validation for the PodGroupPolicy struct in scheduling v1alpha1 ([#136424](https://github.com/kubernetes/kubernetes/pull/136424), [@JoelSpeed](https://github.com/JoelSpeed)) [SIG API Machinery and Scheduling] +- Fixed a potential nil pointer dereference in the scheduler's NodeResourcesFitArgs validation when using RequestedToCapacityRatio scoring strategy ([#132120](https://github.com/kubernetes/kubernetes/pull/132120), [@flpanbin](https://github.com/flpanbin)) [SIG Scheduling] +- Fixes `fake.NewClientset()` to work properly with correct schema. ([#131068](https://github.com/kubernetes/kubernetes/pull/131068), [@soltysh](https://github.com/soltysh)) [SIG API Machinery] +- Generate fake.NewClientset which replace the deprecated NewSimpleClientset, for kube-aggregator and sample-apiserver ([#136537](https://github.com/kubernetes/kubernetes/pull/136537), [@soltysh](https://github.com/soltysh)) [SIG API Machinery] +- Graduate watch_list_duration_seconds from ALPHA to BETA ([#136086](https://github.com/kubernetes/kubernetes/pull/136086), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation, Node and Testing] +- Kube-apiserver: the `--audit-policy-file` config file now supports specifying `group: "*"` in resources rules to match all API groups ([#135262](https://github.com/kubernetes/kubernetes/pull/135262), [@cmuuss](https://github.com/cmuuss)) [SIG API Machinery, Auth and Testing] +- Kube-controller-manager: alpha gauge metrics for informer queue length are now published as `informer_queued_items{name=kube-controller-manager,group=,resource=,version=} ` ([#135782](https://github.com/kubernetes/kubernetes/pull/135782), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Architecture, Instrumentation and Testing] +- Locked the feature-gate VolumeAttributesClass to default (true) and bump VolumeAttributesClass preferred storage version to `storage.k8s.io/v1` ([#134556](https://github.com/kubernetes/kubernetes/pull/134556), [@carlory](https://github.com/carlory)) [SIG API Machinery, Apps, Etcd, Network, Node, Scheduling, Storage and Testing] +- Promote workqueue metrics from ALPHA to BETA ([#135522](https://github.com/kubernetes/kubernetes/pull/135522), [@petern48](https://github.com/petern48)) [SIG Architecture, Instrumentation and Testing] +- Removed the generally available feature gate `CSIMigrationPortworx`, which was locked and enabled since 1.33. + - Removed alpha feature gate `InTreePluginPortworxUnregister` + - Removed Portworx volume plugin from in-tree plugins because all operations are redirected to CSI. ([#135322](https://github.com/kubernetes/kubernetes/pull/135322), [@carlory](https://github.com/carlory)) [SIG API Machinery, Apps, Auth, Node, Scalability, Scheduling, Storage and Testing] +- The ImageVolumeWithDigest is added which adds the digest of image volumes to the container's status. ([#132807](https://github.com/kubernetes/kubernetes/pull/132807), [@iholder101](https://github.com/iholder101)) [SIG API Machinery, Apps, Node and Testing] +- The `endpoints` field in discovery.k8s.io/v1 EndpointSlice is now correctly defined as optional in the OpenAPI specification, matching the server's behavior. ([#136111](https://github.com/kubernetes/kubernetes/pull/136111), [@aojea](https://github.com/aojea)) [SIG Network] +- Update API comments to reflect that stable state of Dynamic Resource Allocation ([#136441](https://github.com/kubernetes/kubernetes/pull/136441), [@kannon92](https://github.com/kannon92)) [SIG API Machinery] + +### Feature + +- Add architecture to the kernel version column in the `kubectl get node -owide` output. ([#132402](https://github.com/kubernetes/kubernetes/pull/132402), [@astraw99](https://github.com/astraw99)) [SIG CLI] +- Add the `appProtocol` field to the service describe output. ([#135744](https://github.com/kubernetes/kubernetes/pull/135744), [@ali-a-a](https://github.com/ali-a-a)) [SIG CLI] +- Add write and read permissions for workloads to the admin cluster role. Add write permissions for workloads to the edit cluster role. Add read permissions for workloads to the view cluster role. ([#135418](https://github.com/kubernetes/kubernetes/pull/135418), [@carlory](https://github.com/carlory)) [SIG Auth] +- Added ALPHA metric `scheduler_pod_scheduled_after_flush_total` to count pods scheduled after being flushed from unschedulablePods due to timeout ([#135126](https://github.com/kubernetes/kubernetes/pull/135126), [@mrvarmazyar](https://github.com/mrvarmazyar)) [SIG Scheduling] +- Added kubectl explain -r flag as a shorthand for --recursive ([#135283](https://github.com/kubernetes/kubernetes/pull/135283), [@laervn](https://github.com/laervn)) [SIG CLI] +- Align the meaning of victim metrics between async preemption and sync preemption. The definition has been standardized to refer to the number of Pods chosen as victims. ([#135955](https://github.com/kubernetes/kubernetes/pull/135955), [@utam0k](https://github.com/utam0k)) [SIG Scheduling] +- CRD validation now strictly enforces ranges for numeric formats (int32, int64, float, double) when specified in the schema. Existing objects with out-of-range values are preserved via validation ratcheting ([#136582](https://github.com/kubernetes/kubernetes/pull/136582), [@yongruilin](https://github.com/yongruilin)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Instrumentation, Network, Node, Scheduling and Storage] +- Change the default debug profile from `legacy` to `general`. `legacy` profile is planned to be removed in v1.39. ([#135874](https://github.com/kubernetes/kubernetes/pull/135874), [@mochizuki875](https://github.com/mochizuki875)) [SIG CLI and Testing] +- Client-go informers can now enqueue new watch events while already-queued events are being processed. This avoids dropping watches during a burst of incoming events due to contention on slow processing. This behavior is controlled by the UnlockWhileProcessing client-go feature gate, which is enabled by default. ([#136264](https://github.com/kubernetes/kubernetes/pull/136264), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery and Scheduling] +- Client-go: Informer resync processing improved handling of Resync handling. This reduces contention on store locks between incoming events and handler updates, which may result in observable timing differences of handler invocations. This behavior is guarded by an AtomicFIFO feature gate. This gate is enabled by default in 1.36, but can be disabled if needed to temporarily regain the previous behavior. ([#136008](https://github.com/kubernetes/kubernetes/pull/136008), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Client-go: default informer behavior now updates store state with all the objects in a list or relist, before calling handler OnDelete/OnAdd/OnUpdate methods for individual items which were deleted/added/removed. This ensures that the store state which can be inspected by handlers actually corresponds to a set of objects that existed at a particular resource version on the server. This behavior is guarded by an AtomicFIFO feature gate. This gate is enabled by default in 1.36, but can be disabled if needed to temporarily regain the previous behavior. ([#135462](https://github.com/kubernetes/kubernetes/pull/135462), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Cloud Controller Manager now exports the counter metric `route_controller_route_sync_total`, which increments each time routes are synced with the cloud provider. This metric is in alpha stage. ([#136539](https://github.com/kubernetes/kubernetes/pull/136539), [@lukasmetzner](https://github.com/lukasmetzner)) [SIG API Machinery, Cloud Provider and Instrumentation] +- Enable WatchCacheInitializationPostStartHook by default ([#135777](https://github.com/kubernetes/kubernetes/pull/135777), [@serathius](https://github.com/serathius)) [SIG API Machinery] +- Graduated fine-grained kubelet API authorization to stable. ([#136116](https://github.com/kubernetes/kubernetes/pull/136116), [@vinayakankugoyal](https://github.com/vinayakankugoyal)) [SIG Node] +- ImageLocality plugin: consider ImageVolume images when scoring nodes for pod scheduling. ([#130231](https://github.com/kubernetes/kubernetes/pull/130231), [@Barakmor1](https://github.com/Barakmor1)) [SIG Scheduling] +- Kube-apiserver: Promoted `ExternalServiceAccountTokenSigner` feature to GA. ([#136118](https://github.com/kubernetes/kubernetes/pull/136118), [@HarshalNeelkamal](https://github.com/HarshalNeelkamal)) [SIG API Machinery and Auth] +- Kubeadm: Upgraded the `NodeLocalCRISocket` feature gating to GA and locked it to be enabled. ([#135742](https://github.com/kubernetes/kubernetes/pull/135742), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Cluster Lifecycle] +- Kubeadm: added the flag --allow-deprecated-api to 'kubeadm config validate'. By default the command will print a warning for a deprecated API unless the flag is passed. Additionally, added missing support for v1beta4 UpgradeConfiguration to 'kubeadm config migrate|validate' commands. ([#135148](https://github.com/kubernetes/kubernetes/pull/135148), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: bumped the timeout of the `kubeadm upgrade` `CreateJob` preflight check to 1 minute. This allows Windows worker nodes to have more time to run the preflight check. It uses the `pause` image, so if you are experiencing slow pull times, you can either pre-pull the new pause on the work using `kubeadm config images pull --kubernetes-version TARGET` or skip the preflight check with `--ignore-preflight-errors`. ([#136273](https://github.com/kubernetes/kubernetes/pull/136273), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: removed the kubeadm specific feature gate ControlPlaneKubeletLocalMode which became GA in 1.35 and was locked to enabled. ([#135773](https://github.com/kubernetes/kubernetes/pull/135773), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: when patching a Node object do not exit early on unknown (non-allowlisted) API errors. Instead, always retry within the duration of the polling for getting and patching a Node object. ([#135776](https://github.com/kubernetes/kubernetes/pull/135776), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubectl get ingressclass now displays (default) marker for default IngressClass ([#134422](https://github.com/kubernetes/kubernetes/pull/134422), [@jaehanbyun](https://github.com/jaehanbyun)) [SIG CLI and Network] +- Kubernetes is now built using Go 1.25.6 ([#136465](https://github.com/kubernetes/kubernetes/pull/136465), [@cpanato](https://github.com/cpanato)) [SIG Release and Testing] +- Kubernetes is now built with Go 1.25.6 ([#136257](https://github.com/kubernetes/kubernetes/pull/136257), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] +- Kubernetes is now built with Go 1.25.7 ([#136750](https://github.com/kubernetes/kubernetes/pull/136750), [@BenTheElder](https://github.com/BenTheElder)) [SIG Release] +- Promote Relaxed validation for Services names to beta (enabled by default) + + Promote `RelaxedServiceNameValidation` feature to beta (enabled by default) + The names of new Services names are validation with `NameIsDNSLabel()`, + relaxing the pre-existing validation. ([#136389](https://github.com/kubernetes/kubernetes/pull/136389), [@adrianmoisey](https://github.com/adrianmoisey)) [SIG Network] +- Promoted the `CSIServiceAccountTokenSecrets` feature gate to GA. ([#136596](https://github.com/kubernetes/kubernetes/pull/136596), [@aramase](https://github.com/aramase)) [SIG Auth and Storage] +- Promoting kubectl kuberc commands to beta ([#136643](https://github.com/kubernetes/kubernetes/pull/136643), [@ardaguclu](https://github.com/ardaguclu)) [SIG CLI and Testing] +- The ResourceClaim controller now correctly handles unknown (non-pod) references in the status.reservedFor field by skipping them instead of halting the sync process. ([#136450](https://github.com/kubernetes/kubernetes/pull/136450), [@MohammedSaalif](https://github.com/MohammedSaalif)) [SIG Apps and Node] +- Update to latest cAdvisor 0.55.0 in our vendor dependencies ([#135829](https://github.com/kubernetes/kubernetes/pull/135829), [@dims](https://github.com/dims)) [SIG Node] +- Using pytorch based e2e integration test instead of tensorflow in some node e2e CI tests. ([#136397](https://github.com/kubernetes/kubernetes/pull/136397), [@dims](https://github.com/dims)) [SIG Testing] +- Using pytorch based e2e integration test instead of tensorflow in some node e2e CI tests. ([#136398](https://github.com/kubernetes/kubernetes/pull/136398), [@dims](https://github.com/dims)) [SIG Node and Testing] + +### Failing Test + +- Fixed device plugin test failures after kubelet restart. ([#135485](https://github.com/kubernetes/kubernetes/pull/135485), [@saschagrunert](https://github.com/saschagrunert)) [SIG Node and Testing] + +### Bug or Regression + +- Added extra check to prevent users to work around DRA extended resource quota set by system admin ([#135434](https://github.com/kubernetes/kubernetes/pull/135434), [@yliaog](https://github.com/yliaog)) [SIG API Machinery, Apps, Node, Scheduling and Testing] +- Aligned `kubectl label` output message to include 'modified' when labels are both added and removed ([#134849](https://github.com/kubernetes/kubernetes/pull/134849), [@tchap](https://github.com/tchap)) [SIG CLI] +- Apiserver liveness probes will now fail when the loopback client certificate expires. ([#136477](https://github.com/kubernetes/kubernetes/pull/136477), [@everettraven](https://github.com/everettraven)) [SIG API Machinery and Testing] +- Changed the behavior of default scheduler preemption plugin when preempting pods that are in "WaitOnPermit" phase. They are now moved to the scheduler backoff queue instead of being marked as unschedulable. ([#135719](https://github.com/kubernetes/kubernetes/pull/135719), [@Argh4k](https://github.com/Argh4k)) [SIG Scheduling and Testing] +- Changes some instances of error logs to info logs with verbosity level inside of controller/resourcequota and controller/garbagecollector ([#136040](https://github.com/kubernetes/kubernetes/pull/136040), [@petern48](https://github.com/petern48)) [SIG API Machinery and Apps] +- Changes the nodeGetCapabilities method of csiDriverClient returning NewUncertainProgressError while received a non final GRPC error ([#135930](https://github.com/kubernetes/kubernetes/pull/135930), [@249043822](https://github.com/249043822)) [SIG Node and Storage] +- Client-go informers: fix an unlikely deadlock during informer startup. ([#136509](https://github.com/kubernetes/kubernetes/pull/136509), [@pohly](https://github.com/pohly)) [SIG API Machinery] +- DRA: when scheduling many pods very rapidly, sometimes the same device was allocated twice for different ResourceClaims due races between data processing in different goroutines. Depending on whether DRA drivers check for this during NodePrepareResources (they should, but maybe not all implement this properly), the second pod using the same device then failed to start until the first one is done or (worse) ran in parallel. ([#136269](https://github.com/kubernetes/kubernetes/pull/136269), [@pohly](https://github.com/pohly)) [SIG Node, Scheduling and Testing] +- Disabled `SchedulerAsyncAPICalls` feature gate due to performance issues caused by API client throttling. ([#135903](https://github.com/kubernetes/kubernetes/pull/135903), [@macsko](https://github.com/macsko)) [SIG Scheduling] +- Ensures a couple of feature gates - ChangeContainerStatusOnKubeletRestart and StatefulSetSemanticRevisionComparison are visible from the "--help" in different components ([#135515](https://github.com/kubernetes/kubernetes/pull/135515), [@dims](https://github.com/dims)) [SIG Architecture] +- Fix a nil pointer dereference in Kubelet when handling pod updates of mirror pods with the NodeDeclaredFeatures feature gate enabled. ([#136037](https://github.com/kubernetes/kubernetes/pull/136037), [@pravk03](https://github.com/pravk03)) [SIG Node] +- Fix apiserver request latency annotation in the audit log when request took more than 500ms ([#135685](https://github.com/kubernetes/kubernetes/pull/135685), [@chaochn47](https://github.com/chaochn47)) [SIG API Machinery] +- Fix data race in kubelet container manager. ([#136206](https://github.com/kubernetes/kubernetes/pull/136206), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] +- Fix data race in kubelet pod allocated resources. ([#136226](https://github.com/kubernetes/kubernetes/pull/136226), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] +- Fix data race in kubelet status manager. ([#136205](https://github.com/kubernetes/kubernetes/pull/136205), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG Node] +- Fix issues where server side apply patches operations incorrectly treat empty arrays and maps as absent. + Fix issue where client-go's `Extract{TypeName}()` and `Extract{TypeName}From() functions incorrectly treat empty arrays and maps as absent. + Fix issue where client-go's `Extract{TypeName}()` and `Extract{TypeName}From() functions would incorrectly duplicate atomic elements from associative lists. ([#135391](https://github.com/kubernetes/kubernetes/pull/135391), [@jpbetz](https://github.com/jpbetz)) [SIG API Machinery, Architecture, Auth, CLI, Cloud Provider, Cluster Lifecycle, Network, Node, Scheduling and Storage] +- Fix log verbosity level in apiserver's unsafe delete authorization check that was incorrectly using Error level instead of Info level ([#136229](https://github.com/kubernetes/kubernetes/pull/136229), [@thc1006](https://github.com/thc1006)) [SIG API Machinery] +- Fix queue hint for the interpodaffinity plugin in case target pod labels change ([#135394](https://github.com/kubernetes/kubernetes/pull/135394), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Fix static pod status is always Init:0/1 if unable to get init container status from container runtime. ([#131317](https://github.com/kubernetes/kubernetes/pull/131317), [@bitoku](https://github.com/bitoku)) [SIG Node and Testing] +- Fix the log verbosity level of some non-error logs that were incorrectly logged at error level ([#136046](https://github.com/kubernetes/kubernetes/pull/136046), [@Tanner-Gladson](https://github.com/Tanner-Gladson)) [SIG API Machinery and Apps] +- Fix the log verbosity level of some non-error logs that were incorrectly logged at error level ([#136050](https://github.com/kubernetes/kubernetes/pull/136050), [@ShaanveerS](https://github.com/ShaanveerS)) [SIG Apps and Storage] +- Fixed SELinux warning controller not to emit events for completed pods. ([#135629](https://github.com/kubernetes/kubernetes/pull/135629), [@jsafrane](https://github.com/jsafrane)) [SIG Apps, Storage and Testing] +- Fixed a bug causing clients to error out when decoding large CBOR encoded lists. ([#135340](https://github.com/kubernetes/kubernetes/pull/135340), [@ricardomaraschini](https://github.com/ricardomaraschini)) [SIG API Machinery] +- Fixed a bug in DeepEqualWithNilDifferentFromEmpty where empty slices/maps were incorrectly considered equal to non-empty ones due to using OR (||) instead of AND (&&) logic. This could cause managed fields timestamps to not update when the only change was adding or removing all elements from a list or map. ([#135636](https://github.com/kubernetes/kubernetes/pull/135636), [@mikecook](https://github.com/mikecook)) [SIG API Machinery] +- Fixed a bug in the `dra_operations_duration_seconds` metric where the `is_error` label was recording inverted values. Error operations now correctly report `is_error="true"`, and successful operations report `is_error="false"`. ([#135227](https://github.com/kubernetes/kubernetes/pull/135227), [@hime](https://github.com/hime)) [SIG Node] +- Fixed a bug that caused endpoint slice churn for headless services with no ports defined (#133474) ([#136502](https://github.com/kubernetes/kubernetes/pull/136502), [@tzneal](https://github.com/tzneal)) [SIG Network] +- Fixed a bug where `kubectl apply --dry-run=client` would only output server state instead of merged manifest values when the resource already exists. ([#135513](https://github.com/kubernetes/kubernetes/pull/135513), [@grandeit](https://github.com/grandeit)) [SIG CLI] +- Fixed a bug where the Gated pods metric was not updated when a Pod transitioned from Unschedulable to Gated during an update. ([#135368](https://github.com/kubernetes/kubernetes/pull/135368), [@vshkrabkov](https://github.com/vshkrabkov)) [SIG Scheduling] +- Fixed a bug where the `scheduler_unschedulable_pods` metric could be artificially inflated (leak) when a pod fails `PreEnqueue` plugins after being previously marked unschedulable. ([#135981](https://github.com/kubernetes/kubernetes/pull/135981), [@vshkrabkov](https://github.com/vshkrabkov)) [SIG Scheduling] +- Fixed a panic in `kubectl exec` when the terminal size queue delegate is uninitialized. ([#135918](https://github.com/kubernetes/kubernetes/pull/135918), [@MarcosDaNight](https://github.com/MarcosDaNight)) [SIG CLI] +- Fixed a panic in kubectl when processing pods with nil resource requests but populated container status resources. ([#136534](https://github.com/kubernetes/kubernetes/pull/136534), [@dmaizel](https://github.com/dmaizel)) [SIG CLI] +- Fixed a race condition in the CEL compiler that could occur when initializing composited policies concurrently. + + ### Description + Fixes a fatal crash (concurrent map read/write) in `NewCompositedCompilerFromTemplate`. + + The `NewCompositedCompilerFromTemplate` function previously performed a shallow copy of `CompositionEnv`, sharing the `MapType` pointer across all compilers. Under high concurrency, this caused a race condition when `FindStructFieldType` (reader) and `AddField` (writer) accessed `MapType.Fields` simultaneously, leading to an APIServer panic. + + This change implements a deep copy of the `Fields` map for each composition environment, ensuring thread safety. + + ### Issue + Fixes #135757 ([#135759](https://github.com/kubernetes/kubernetes/pull/135759), [@Abhigyan-Shekhar](https://github.com/Abhigyan-Shekhar)) [SIG API Machinery and CLI] +- Fixed an issue in the Windows kube-proxy (winkernel) where IPv4 and IPv6 Service load balancers could be incorrectly shared, causing broken dual-stack Service behavior. The kube-proxy now tracks load balancers per IP family, enabling correct support for PreferDualStack and RequireDualStack Services on Windows nodes. ([#136241](https://github.com/kubernetes/kubernetes/pull/136241), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] +- Fixed issue where `kubectl run -i/-it` would miss container output written before the attach connection was established. ([#136010](https://github.com/kubernetes/kubernetes/pull/136010), [@olamilekan000](https://github.com/olamilekan000)) [SIG CLI] +- Fixed kubelet logging to properly respect verbosity levels. Previously, some debug/info messages using V().Error() would always be printed regardless of the configured log verbosity. ([#136028](https://github.com/kubernetes/kubernetes/pull/136028), [@thc1006](https://github.com/thc1006)) [SIG Node] +- Fixed queue hint for certain plugins on change to pods with nominated nodes ([#135392](https://github.com/kubernetes/kubernetes/pull/135392), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Fixed queue hint for inter-pod anti-affinity in case deleted pod's anti-affinity matched the pending pod, which might have caused delays in scheduling. ([#135325](https://github.com/kubernetes/kubernetes/pull/135325), [@brejman](https://github.com/brejman)) [SIG Scheduling and Testing] +- Fixed volumeattachment cleanup in kube-controller-manager when CSI's attachRequired switches from true to false ([#129664](https://github.com/kubernetes/kubernetes/pull/129664), [@hkttty2009](https://github.com/hkttty2009)) [SIG Storage and Testing] +- Fixes a 1.29 regression in the apiserver_watch_events_sizes metric to report total outgoing watch traffic again ([#135367](https://github.com/kubernetes/kubernetes/pull/135367), [@mborsz](https://github.com/mborsz)) [SIG API Machinery] +- Fixes a 1.34 regression starting pods with environment variables with a value containing `$` followed by a multi-byte character ([#136325](https://github.com/kubernetes/kubernetes/pull/136325), [@AutuSnow](https://github.com/AutuSnow)) [SIG Architecture] +- Fixes a 1.34+ regression in ipvs and winkernel kube-proxy backends; these are now reverted back to their + pre-1.34 behavior of regularly rechecking all of their rules even when no + Services or EndpointSlices change. ([#135631](https://github.com/kubernetes/kubernetes/pull/135631), [@danwinship](https://github.com/danwinship)) [SIG Network and Windows] +- Fixes kube-proxy log spam when all of a Service's endpoints were unready. ([#136743](https://github.com/kubernetes/kubernetes/pull/136743), [@ansilh](https://github.com/ansilh)) [SIG Network] +- Kube-apiserver: setting `--audit-log-maxsize=0` now disables audit log rotation (the default remains `100` MB). In order to avoid outages due to filling disks with ever-growing audit logs, `--audit-log-maxage` now defaults to 366 (1 year) and `--audit-log-maxbackup` now defaults to 100. If retention of all rotated logs is desired, age and count-based pruning can be disabled by explicitly specifying `--audit-log-maxage=0` and `--audit-log-maxbackup=0`. ([#136478](https://github.com/kubernetes/kubernetes/pull/136478), [@kairosci](https://github.com/kairosci)) [SIG API Machinery] +- Kube-proxy now correctly handles the case where a pod IP gets assigned to + a newly-created pod when the pod that previously had that IP has been + terminated but is not yet fully deleted. ([#135593](https://github.com/kubernetes/kubernetes/pull/135593), [@danwinship](https://github.com/danwinship)) [SIG Network] +- Kubeadm: fix a bug where kubeadm upgrade is failed if the content of the `kubeadm-flags.env` file is `KUBELET_KUBEADM_ARGS=""` ([#136127](https://github.com/kubernetes/kubernetes/pull/136127), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] +- Kubeadm: waiting for etcd learner member to be started before promoting during 'kubeadm join' ([#136014](https://github.com/kubernetes/kubernetes/pull/136014), [@SataQiu](https://github.com/SataQiu)) [SIG Cluster Lifecycle] +- Kubeadm: when applying the overrides provided by the user using "extraArgs", do not sort the resulted list of arguments alpha-numerically. Instead, only sort the list of default arguments and keep the list of overrides unsorted. This allows finer control for flags which have an order that matters, such as, "--service-account-issuer" for kube-apiserver. ([#135400](https://github.com/kubernetes/kubernetes/pull/135400), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubectl: fixes kyaml output of `kubectl get ... --output-watch-events -o kyaml` ([#136110](https://github.com/kubernetes/kubernetes/pull/136110), [@liggitt](https://github.com/liggitt)) [SIG CLI] +- Kubelet(dra): correctly handles multiple ResourceClaims even if one is already prepared ([#135919](https://github.com/kubernetes/kubernetes/pull/135919), [@rogowski-piotr](https://github.com/rogowski-piotr)) [SIG Node and Testing] +- Kubelet: fix data race in volume manager's WaitForAllPodsUnmount that could cause errors to be lost during concurrent pod unmount operations. ([#135794](https://github.com/kubernetes/kubernetes/pull/135794), [@AutuSnow](https://github.com/AutuSnow)) [SIG Node and Storage] +- Kubelet: fixed reloading of kubelet server certificate files when they are changed on disk, and kubelet is dialed by IP address instead of DNS/hostname ([#133654](https://github.com/kubernetes/kubernetes/pull/133654), [@kwohlfahrt](https://github.com/kwohlfahrt)) [SIG API Machinery, Auth, Node and Testing] +- Optimized kube-proxy conntrack cleanup logic, reducing the time complexity of deleting stale UDP entries. This significantly improves performance when there are many stale connections to clean up. ([#135511](https://github.com/kubernetes/kubernetes/pull/135511), [@aojea](https://github.com/aojea)) [SIG Network] +- ReadWriteOncePod preemption e2e test no longer causes other random e2e tests to flake randomly. ([#135623](https://github.com/kubernetes/kubernetes/pull/135623), [@jsafrane](https://github.com/jsafrane)) [SIG Storage and Testing] +- Sort runtime handlers list coming from the CRI runtime ([#135358](https://github.com/kubernetes/kubernetes/pull/135358), [@harche](https://github.com/harche)) [SIG Node] +- StatefulSets should always count `.status.availableReplicas` at the correct time without a delay. This results in faster progress of StatefulSet rollout. ([#135428](https://github.com/kubernetes/kubernetes/pull/135428), [@atiratree](https://github.com/atiratree)) [SIG Apps] +- The kubelet plugin manager now properly handles plugin registration failures by removing failed plugins from the actual state and retrying with exponential backoff (initial delay 500ms, doubling each failure up to ~2 minutes maximum) to protect against broken plugins causing denial of service while still allowing recovery from transient failures. ([#133335](https://github.com/kubernetes/kubernetes/pull/133335), [@bart0sh](https://github.com/bart0sh)) [SIG Node, Storage and Testing] +- The nftables mode of kube-proxy now uses less CPU when loading + very large rulesets. ([#135800](https://github.com/kubernetes/kubernetes/pull/135800), [@danwinship](https://github.com/danwinship)) [SIG Network] +- Updated `NodeResourcesBalancedAllocation` scoring algorithm to align with the documentation. The score will now take into consideration both balance with and without the requested pod. Previous algorithm only considered balance with the requested pod. This can change the scheduling decisions in some cases. ([#135573](https://github.com/kubernetes/kubernetes/pull/135573), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- When use kubectl command to delete multiple sts pods, the kubectl command deletes pods and exits normally. ([#135563](https://github.com/kubernetes/kubernetes/pull/135563), [@yangjunmyfm192085](https://github.com/yangjunmyfm192085)) [SIG CLI, Network and Node] + +### Other (Cleanup or Flake) + +- Added missing tests for client-go metrics ([#136052](https://github.com/kubernetes/kubernetes/pull/136052), [@sreeram-venkitesh](https://github.com/sreeram-venkitesh)) [SIG Architecture and Instrumentation] +- Adds audit-id to 'Starting watch' log line ([#136084](https://github.com/kubernetes/kubernetes/pull/136084), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Adds explicit logging when WatchList requests complete their initial listing phase. ([#136085](https://github.com/kubernetes/kubernetes/pull/136085), [@richabanker](https://github.com/richabanker)) [SIG API Machinery] +- Client-go: Reflector no longer gets confused about the resource version it should use to restart a watch while receiving synthetic ADDED events at the beginning of a watch from resourceVersion "0" or "". ([#136583](https://github.com/kubernetes/kubernetes/pull/136583), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Client-go: fake client-go (i.e. anything using k8s.io/client-go/testing) now supports separate List+Watch calls with checking of ResourceVersion in the Watch call. This closes a race condition where creating an object directly after an informer cache has synced (= List call completed) and before the Watch call completed would cause that object to not be sent to the informer. A visible side-effect of adding that support is that List meta data contains a ResourceVersion (starting at "1" for the empty set, incremented by one for each add/update) and that Watch may return objects where it previously didn't. + + Note that this List+Watch is not to be confused with the ListWatch feature, which uses a single call. That feature is still not supported by fake client-go. ([#136143](https://github.com/kubernetes/kubernetes/pull/136143), [@pohly](https://github.com/pohly)) [SIG API Machinery, Apps, Auth and CLI] +- DRA device taint eviction: the controller might have reported "1 pod needs to be evicted in 1 namespace. 1 pod evicted since starting the controller." when only a single pod is involved, depending on timing (pod evicted, informer cache not updated yet). It would eventually arrive at the correct "1 pod evicted since starting the controller.", but now it tries harder to avoid the confusing intermediate state by delaying the status update after eviction. ([#135611](https://github.com/kubernetes/kubernetes/pull/135611), [@Karthik-K-N](https://github.com/Karthik-K-N)) [SIG Apps and Scheduling] +- DRA: Fixed Kubelet admission to correctly handle DRA-backed extended resources, allowing pods to be admitted even when these resources are not present in the node's allocatable capacity. ([#135725](https://github.com/kubernetes/kubernetes/pull/135725), [@bart0sh](https://github.com/bart0sh)) [SIG Node, Scheduling and Testing] +- Enables YAML support for statusz and flagz. ([#135309](https://github.com/kubernetes/kubernetes/pull/135309), [@richabanker](https://github.com/richabanker)) [SIG API Machinery, Instrumentation and Testing] +- Kubeadm: removed the cleanup of the "--pod-infra-container-image" kubelet flag from the "/var/lib/kubelet/kubeadm-flags.env" on upgrade. This cleanup was necessary when upgrading to 1.35. ([#135807](https://github.com/kubernetes/kubernetes/pull/135807), [@carlory](https://github.com/carlory)) [SIG Cluster Lifecycle] +- Kubeadm: removed usage of the deprecated flags '--experimental-initial-corrupt-check' and '--experimental-watch-progress-notify-interval' if the etcd version is < 3.6.0. In this version of kubeadm, etcd < 3.6.0 is no longer supported in terms of the k8s / etcd version mapping. These deprecated flags have been replaced by '--feature-gates=InitialCorruptCheck=true' and '--watch-progress-notify-interval'. ([#135701](https://github.com/kubernetes/kubernetes/pull/135701), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Lock the `DisableNodeKubeProxyVersion` feature gate to be enabled by default. ([#136673](https://github.com/kubernetes/kubernetes/pull/136673), [@HirazawaUi](https://github.com/HirazawaUi)) [SIG CLI and Network] +- Remove WatchFromStorageWithoutResourceVersion feature gate ([#136066](https://github.com/kubernetes/kubernetes/pull/136066), [@serathius](https://github.com/serathius)) [SIG API Machinery] +- Remove event listing behavior when describing a removed pod from file. ([#135281](https://github.com/kubernetes/kubernetes/pull/135281), [@scaliby](https://github.com/scaliby)) [SIG CLI] +- Renamed PodGroupInfo to PodGroupState, which can break custom scheduler plugins that use Handle.WorkloadManager ([#136344](https://github.com/kubernetes/kubernetes/pull/136344), [@brejman](https://github.com/brejman)) [SIG Scheduling] +- Set InOrderInformers to GA via the usage of RealFIFO, this means that DeltaFIFO will gradually be deprecated in favor of RealFIFO in internal implementations. ([#136601](https://github.com/kubernetes/kubernetes/pull/136601), [@michaelasp](https://github.com/michaelasp)) [SIG API Machinery] +- Updated cri-tools to v1.35.0. ([#135694](https://github.com/kubernetes/kubernetes/pull/135694), [@saschagrunert](https://github.com/saschagrunert)) [SIG Cloud Provider and Node] +- Updates the etcd client library to v3.6.6 ([#135331](https://github.com/kubernetes/kubernetes/pull/135331), [@yashsingh74](https://github.com/yashsingh74)) [SIG API Machinery, Auth, Cloud Provider, Etcd, Node and Scheduling] +- Updates the etcd client library to v3.6.7 ([#136407](https://github.com/kubernetes/kubernetes/pull/136407), [@ivanvc](https://github.com/ivanvc)) [SIG API Machinery, Auth, Cloud Provider, Node and Scheduling] + +## Dependencies + +### Added +- buf.build/go/protovalidate: v0.12.0 +- github.com/cenkalti/backoff/v5: [v5.0.3](https://github.com/cenkalti/backoff/tree/v5.0.3) +- github.com/moby/moby/api: [v1.52.0](https://github.com/moby/moby/tree/api/v1.52.0) +- github.com/moby/moby/client: [v0.2.1](https://github.com/moby/moby/tree/client/v0.2.1) +- go.opentelemetry.io/otel/exporters/stdout/stdouttrace: v1.39.0 +- gonum.org/v1/gonum: v0.16.0 + +### Changed +- buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go: 63bb56e → 8976f5b +- cloud.google.com/go/compute/metadata: v0.7.0 → v0.9.0 +- cyphar.com/go-pathrs: v0.2.1 → v0.2.2 +- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp: [v1.26.0 → v1.30.0](https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/compare/detectors/gcp/v1.26.0...detectors/gcp/v1.30.0) +- github.com/Microsoft/hnslib: [v0.1.1 → v0.1.2](https://github.com/Microsoft/hnslib/compare/v0.1.1...v0.1.2) +- github.com/alecthomas/units: [b94a6e3 → 0f3dac3](https://github.com/alecthomas/units/compare/b94a6e3...0f3dac3) +- github.com/cncf/xds/go: [2f00578 → 0feb691](https://github.com/cncf/xds/compare/2f00578...0feb691) +- github.com/containerd/containerd/api: [v1.9.0 → v1.10.0](https://github.com/containerd/containerd/compare/api/v1.9.0...api/v1.10.0) +- github.com/coredns/corefile-migration: [v1.0.29 → v1.0.30](https://github.com/coredns/corefile-migration/compare/v1.0.29...v1.0.30) +- github.com/coreos/go-oidc: [v2.3.0+incompatible → v2.5.0+incompatible](https://github.com/coreos/go-oidc/compare/v2.3.0...v2.5.0) +- github.com/coreos/go-systemd/v22: [v22.5.0 → v22.7.0](https://github.com/coreos/go-systemd/compare/v22.5.0...v22.7.0) +- github.com/cyphar/filepath-securejoin: [v0.6.0 → v0.6.1](https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...v0.6.1) +- github.com/davecgh/go-spew: [v1.1.1 → d8f796a](https://github.com/davecgh/go-spew/compare/v1.1.1...d8f796a) +- github.com/docker/go-connections: [v0.5.0 → v0.6.0](https://github.com/docker/go-connections/compare/v0.5.0...v0.6.0) +- github.com/emicklei/go-restful/v3: [v3.12.2 → v3.13.0](https://github.com/emicklei/go-restful/compare/v3.12.2...v3.13.0) +- github.com/envoyproxy/go-control-plane/envoy: [v1.32.4 → v1.35.0](https://github.com/envoyproxy/go-control-plane/compare/envoy/v1.32.4...envoy/v1.35.0) +- github.com/envoyproxy/go-control-plane: [v0.13.4 → 75eaa19](https://github.com/envoyproxy/go-control-plane/compare/v0.13.4...75eaa19) +- github.com/go-jose/go-jose/v4: [v4.0.4 → v4.1.3](https://github.com/go-jose/go-jose/compare/v4.0.4...v4.1.3) +- github.com/godbus/dbus/v5: [v5.1.0 → v5.2.2](https://github.com/godbus/dbus/compare/v5.1.0...v5.2.2) +- github.com/golang-jwt/jwt/v5: [v5.2.2 → v5.3.0](https://github.com/golang-jwt/jwt/compare/v5.2.2...v5.3.0) +- github.com/golang/glog: [v1.2.4 → v1.2.5](https://github.com/golang/glog/compare/v1.2.4...v1.2.5) +- github.com/google/cadvisor: [v0.53.0 → v0.56.0](https://github.com/google/cadvisor/compare/v0.53.0...v0.56.0) +- github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus: [v1.0.1 → v1.1.0](https://github.com/grpc-ecosystem/go-grpc-middleware/compare/providers/prometheus/v1.0.1...providers/prometheus/v1.1.0) +- github.com/grpc-ecosystem/go-grpc-middleware/v2: [v2.3.0 → v2.3.3](https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v2.3.0...v2.3.3) +- github.com/grpc-ecosystem/grpc-gateway/v2: [v2.26.3 → v2.27.4](https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.26.3...v2.27.4) +- github.com/onsi/ginkgo/v2: [v2.27.2 → v2.27.4](https://github.com/onsi/ginkgo/compare/v2.27.2...v2.27.4) +- github.com/onsi/gomega: [v1.38.2 → v1.39.0](https://github.com/onsi/gomega/compare/v1.38.2...v1.39.0) +- github.com/opencontainers/cgroups: [v0.0.3 → v0.0.6](https://github.com/opencontainers/cgroups/compare/v0.0.3...v0.0.6) +- github.com/opencontainers/runc: [v1.3.0 → v1.4.0](https://github.com/opencontainers/runc/compare/v1.3.0...v1.4.0) +- github.com/opencontainers/runtime-spec: [v1.2.1 → v1.3.0](https://github.com/opencontainers/runtime-spec/compare/v1.2.1...v1.3.0) +- github.com/opencontainers/selinux: [v1.13.0 → v1.13.1](https://github.com/opencontainers/selinux/compare/v1.13.0...v1.13.1) +- github.com/pmezard/go-difflib: [v1.0.0 → 5d4384e](https://github.com/pmezard/go-difflib/compare/v1.0.0...5d4384e) +- github.com/prometheus/common: [v0.66.1 → v0.67.5](https://github.com/prometheus/common/compare/v0.66.1...v0.67.5) +- github.com/prometheus/procfs: [v0.16.1 → v0.19.2](https://github.com/prometheus/procfs/compare/v0.16.1...v0.19.2) +- github.com/spiffe/go-spiffe/v2: [v2.5.0 → v2.6.0](https://github.com/spiffe/go-spiffe/compare/v2.5.0...v2.6.0) +- go.etcd.io/etcd/api/v3: v3.6.5 → v3.6.7 +- go.etcd.io/etcd/client/pkg/v3: v3.6.5 → v3.6.7 +- go.etcd.io/etcd/client/v3: v3.6.5 → v3.6.7 +- go.etcd.io/etcd/pkg/v3: v3.6.5 → v3.6.7 +- go.etcd.io/etcd/server/v3: v3.6.5 → v3.6.7 +- go.opentelemetry.io/auto/sdk: v1.1.0 → v1.2.1 +- go.opentelemetry.io/contrib/detectors/gcp: v1.34.0 → v1.38.0 +- go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful: v0.44.0 → v0.64.0 +- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc: v0.60.0 → v0.63.0 +- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp: v0.61.0 → v0.64.0 +- go.opentelemetry.io/contrib/propagators/b3: v1.19.0 → v1.39.0 +- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc: v1.34.0 → v1.39.0 +- go.opentelemetry.io/otel/exporters/otlp/otlptrace: v1.34.0 → v1.39.0 +- go.opentelemetry.io/otel/metric: v1.36.0 → v1.39.0 +- go.opentelemetry.io/otel/sdk/metric: v1.36.0 → v1.39.0 +- go.opentelemetry.io/otel/sdk: v1.36.0 → v1.39.0 +- go.opentelemetry.io/otel/trace: v1.36.0 → v1.39.0 +- go.opentelemetry.io/otel: v1.36.0 → v1.39.0 +- go.opentelemetry.io/proto/otlp: v1.5.0 → v1.9.0 +- go.uber.org/zap: v1.27.0 → v1.27.1 +- golang.org/x/crypto: v0.45.0 → v0.47.0 +- golang.org/x/exp: 8a7402a → 944ab1f +- golang.org/x/mod: v0.29.0 → v0.32.0 +- golang.org/x/net: v0.47.0 → v0.49.0 +- golang.org/x/oauth2: v0.30.0 → v0.34.0 +- golang.org/x/sync: v0.18.0 → v0.19.0 +- golang.org/x/sys: v0.38.0 → v0.40.0 +- golang.org/x/telemetry: 078029d → 8fff8a5 +- golang.org/x/term: v0.37.0 → v0.39.0 +- golang.org/x/text: v0.31.0 → v0.33.0 +- golang.org/x/time: v0.9.0 → v0.14.0 +- golang.org/x/tools: v0.38.0 → v0.40.0 +- google.golang.org/genproto/googleapis/api: a0af3ef → 99fd39f +- google.golang.org/genproto/googleapis/rpc: 200df99 → 99fd39f +- google.golang.org/grpc: v1.72.2 → v1.78.0 +- google.golang.org/protobuf: v1.36.8 → v1.36.11 +- k8s.io/kube-openapi: 589584f → a19766b +- k8s.io/utils: bc988d5 → 914a6e7 +- sigs.k8s.io/apiserver-network-proxy/konnectivity-client: v0.31.2 → v0.34.0 +- sigs.k8s.io/structured-merge-diff/v6: v6.3.0 → v6.3.1 + +### Removed +- github.com/armon/circbuf: [5111143](https://github.com/armon/circbuf/tree/5111143) +- github.com/bufbuild/protovalidate-go: [v0.9.1](https://github.com/bufbuild/protovalidate-go/tree/v0.9.1) +- github.com/docker/docker: [v28.2.2+incompatible](https://github.com/docker/docker/tree/v28.2.2) +- github.com/gregjones/httpcache: [901d907](https://github.com/gregjones/httpcache/tree/901d907) +- github.com/grpc-ecosystem/go-grpc-prometheus: [v1.2.0](https://github.com/grpc-ecosystem/go-grpc-prometheus/tree/v1.2.0) +- github.com/karrick/godirwalk: [v1.17.0](https://github.com/karrick/godirwalk/tree/v1.17.0) +- github.com/libopenstorage/openstorage: [v1.0.0](https://github.com/libopenstorage/openstorage/tree/v1.0.0) +- github.com/moby/sys/atomicwriter: [v0.1.0](https://github.com/moby/sys/tree/atomicwriter/v0.1.0) +- github.com/mohae/deepcopy: [c48cc78](https://github.com/mohae/deepcopy/tree/c48cc78) +- github.com/morikuni/aec: [v1.0.0](https://github.com/morikuni/aec/tree/v1.0.0) +- github.com/mrunalp/fileutils: [v0.5.1](https://github.com/mrunalp/fileutils/tree/v0.5.1) +- github.com/zeebo/errs: [v1.4.0](https://github.com/zeebo/errs/tree/v1.4.0) +- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp: v1.27.0 +- gotest.tools/v3: v3.0.2 \ No newline at end of file diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md index c587a652174a0..310b0f03cb405 100644 --- a/CHANGELOG/README.md +++ b/CHANGELOG/README.md @@ -1,5 +1,6 @@ # CHANGELOGs +- [CHANGELOG-1.36.md](./CHANGELOG-1.36.md) - [CHANGELOG-1.35.md](./CHANGELOG-1.35.md) - [CHANGELOG-1.34.md](./CHANGELOG-1.34.md) - [CHANGELOG-1.33.md](./CHANGELOG-1.33.md) diff --git a/LICENSES/vendor/github.com/armon/circbuf/LICENSE b/LICENSES/vendor/github.com/armon/circbuf/LICENSE deleted file mode 100644 index 38e849479cadf..0000000000000 --- a/LICENSES/vendor/github.com/armon/circbuf/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -= vendor/github.com/armon/circbuf licensed under: = - -The MIT License (MIT) - -Copyright (c) 2013 Armon Dadgar - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -= vendor/github.com/armon/circbuf/LICENSE d2d77030c0183e3d1e66d26dc1f243be diff --git a/LICENSES/vendor/github.com/cenkalti/backoff/v4/LICENSE b/LICENSES/vendor/github.com/cenkalti/backoff/v4/LICENSE deleted file mode 100644 index 3bbc6a0bc68d8..0000000000000 --- a/LICENSES/vendor/github.com/cenkalti/backoff/v4/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -= vendor/github.com/cenkalti/backoff/v4 licensed under: = - -The MIT License (MIT) - -Copyright (c) 2014 Cenk Altı - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -= vendor/github.com/cenkalti/backoff/v4/LICENSE 1571d94433e3f3aa05267efd4dbea68b diff --git a/LICENSES/vendor/github.com/cenkalti/backoff/v5/LICENSE b/LICENSES/vendor/github.com/cenkalti/backoff/v5/LICENSE new file mode 100644 index 0000000000000..57ccea1e438a9 --- /dev/null +++ b/LICENSES/vendor/github.com/cenkalti/backoff/v5/LICENSE @@ -0,0 +1,24 @@ += vendor/github.com/cenkalti/backoff/v5 licensed under: = + +The MIT License (MIT) + +Copyright (c) 2014 Cenk Altı + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + += vendor/github.com/cenkalti/backoff/v5/LICENSE 1571d94433e3f3aa05267efd4dbea68b diff --git a/LICENSES/vendor/github.com/gregjones/httpcache/LICENSE b/LICENSES/vendor/github.com/gregjones/httpcache/LICENSE deleted file mode 100644 index 36f2c40b3aa6f..0000000000000 --- a/LICENSES/vendor/github.com/gregjones/httpcache/LICENSE +++ /dev/null @@ -1,10 +0,0 @@ -= vendor/github.com/gregjones/httpcache licensed under: = - -Copyright © 2012 Greg Jones (greg.jones@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -= vendor/github.com/gregjones/httpcache/LICENSE.txt 3cfef421226b2dacde78a4871380ac24 diff --git a/LICENSES/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE b/LICENSES/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE deleted file mode 100644 index 5192fe1a5032e..0000000000000 --- a/LICENSES/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE +++ /dev/null @@ -1,204 +0,0 @@ -= vendor/github.com/grpc-ecosystem/go-grpc-prometheus licensed under: = - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. -= vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE 7ab5c73bb7e4679b16dd7c11b3559acf diff --git a/LICENSES/vendor/github.com/karrick/godirwalk/LICENSE b/LICENSES/vendor/github.com/karrick/godirwalk/LICENSE deleted file mode 100644 index 029f2ea14aa6a..0000000000000 --- a/LICENSES/vendor/github.com/karrick/godirwalk/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -= vendor/github.com/karrick/godirwalk licensed under: = - -BSD 2-Clause License - -Copyright (c) 2017, Karrick McDermott -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -= vendor/github.com/karrick/godirwalk/LICENSE 7bea66fc0a31c6329f9392034bee75d2 diff --git a/LICENSES/vendor/github.com/libopenstorage/openstorage/LICENSE b/LICENSES/vendor/github.com/libopenstorage/openstorage/LICENSE deleted file mode 100644 index db64e97266510..0000000000000 --- a/LICENSES/vendor/github.com/libopenstorage/openstorage/LICENSE +++ /dev/null @@ -1,195 +0,0 @@ -= vendor/github.com/libopenstorage/openstorage licensed under: = - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2015 Openstorage.org. - - 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. - -= vendor/github.com/libopenstorage/openstorage/LICENSE 40c3e1c9eacda859a17048003909a2f8 diff --git a/LICENSES/vendor/github.com/mohae/deepcopy/LICENSE b/LICENSES/vendor/github.com/mohae/deepcopy/LICENSE deleted file mode 100644 index da493e3a8f057..0000000000000 --- a/LICENSES/vendor/github.com/mohae/deepcopy/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -= vendor/github.com/mohae/deepcopy licensed under: = - -The MIT License (MIT) - -Copyright (c) 2014 Joel - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -= vendor/github.com/mohae/deepcopy/LICENSE 268dc9c546e3de67a93c1d12a039d702 diff --git a/LICENSES/vendor/github.com/mrunalp/fileutils/LICENSE b/LICENSES/vendor/github.com/mrunalp/fileutils/LICENSE deleted file mode 100644 index b82a46222b41b..0000000000000 --- a/LICENSES/vendor/github.com/mrunalp/fileutils/LICENSE +++ /dev/null @@ -1,195 +0,0 @@ -= vendor/github.com/mrunalp/fileutils licensed under: = - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2014 Docker, Inc. - - 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. - -= vendor/github.com/mrunalp/fileutils/LICENSE 435b266b3899aa8a959f17d41c56def8 diff --git a/LICENSES/vendor/github.com/pkg/errors/LICENSE b/LICENSES/vendor/github.com/pkg/errors/LICENSE deleted file mode 100644 index 6fe7f22313849..0000000000000 --- a/LICENSES/vendor/github.com/pkg/errors/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -= vendor/github.com/pkg/errors licensed under: = - -Copyright (c) 2015, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -= vendor/github.com/pkg/errors/LICENSE 6fe682a02df52c6653f33bd0f7126b5a diff --git a/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/LICENSE b/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/LICENSE index 27c6673590ad2..6add3ea40be93 100644 --- a/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE b/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE index 4494811b66852..0ae1dfee9ae4a 100644 --- a/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE b/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE index a1f5afb24ff32..46b3903d1ba38 100644 --- a/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/otel/LICENSE b/LICENSES/vendor/go.opentelemetry.io/otel/LICENSE index 5e0275325c717..a9ee9a3d063ea 100644 --- a/LICENSES/vendor/go.opentelemetry.io/otel/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/otel/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/otel/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/otel/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/LICENSE b/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/LICENSE index b71e8f49ca1da..3492ac860e448 100644 --- a/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/otel/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/otel/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/LICENSE b/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/LICENSE index fecd59a772ada..aa05abda88543 100644 --- a/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/otel/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/otel/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/otel/metric/LICENSE b/LICENSES/vendor/go.opentelemetry.io/otel/metric/LICENSE index 8f24a83f50721..bfe71f2b6887c 100644 --- a/LICENSES/vendor/go.opentelemetry.io/otel/metric/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/otel/metric/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/otel/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/otel/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/otel/sdk/LICENSE b/LICENSES/vendor/go.opentelemetry.io/otel/sdk/LICENSE index 9412c345a9209..126945516c84a 100644 --- a/LICENSES/vendor/go.opentelemetry.io/otel/sdk/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/otel/sdk/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/otel/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/otel/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.opentelemetry.io/otel/trace/LICENSE b/LICENSES/vendor/go.opentelemetry.io/otel/trace/LICENSE index 54b749c6a72d2..a8be8a1a81c01 100644 --- a/LICENSES/vendor/go.opentelemetry.io/otel/trace/LICENSE +++ b/LICENSES/vendor/go.opentelemetry.io/otel/trace/LICENSE @@ -202,4 +202,33 @@ See the License for the specific language governing permissions and limitations under the License. -= vendor/go.opentelemetry.io/otel/LICENSE 86d3f3a95c324c9479bd8986968f4327 +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. += vendor/go.opentelemetry.io/otel/LICENSE 24dad3abbe7a8f390afc8ab967bfefa7 diff --git a/LICENSES/vendor/go.uber.org/zap/LICENSE b/LICENSES/vendor/go.uber.org/zap/LICENSE index 9324cd1464a1e..60200bf8ba86a 100644 --- a/LICENSES/vendor/go.uber.org/zap/LICENSE +++ b/LICENSES/vendor/go.uber.org/zap/LICENSE @@ -1,6 +1,6 @@ = vendor/go.uber.org/zap licensed under: = -Copyright (c) 2016-2017 Uber Technologies, Inc. +Copyright (c) 2016-2024 Uber Technologies, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -20,4 +20,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -= vendor/go.uber.org/zap/LICENSE 5e8153e456a82529ea845e0d511abb69 += vendor/go.uber.org/zap/LICENSE a48e22dd4170c1eb095a423a50302852 diff --git a/LICENSES/vendor/k8s.io/utils/third_party/forked/golang/LICENSE b/LICENSES/vendor/k8s.io/utils/third_party/forked/golang/LICENSE new file mode 100644 index 0000000000000..74487567632c8 --- /dev/null +++ b/LICENSES/vendor/k8s.io/utils/third_party/forked/golang/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/vendor/k8s.io/utils/third_party/forked/golang/btree/LICENSE b/LICENSES/vendor/k8s.io/utils/third_party/forked/golang/btree/LICENSE new file mode 100644 index 0000000000000..7a4a3ea2424c0 --- /dev/null +++ b/LICENSES/vendor/k8s.io/utils/third_party/forked/golang/btree/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index b4832c3fcb67f..63ad1265681d3 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -181,12 +181,14 @@ aliases: # - ravisantoshgudimetla # - wojtek-t sig-scheduling: + - ania-borowiec - AxeZhan - damemi - denkensk - dom4ha - macsko - sanposhiho + - tosi3k - utam0k - kerthcet # emeritus: @@ -330,30 +332,30 @@ aliases: # - mwielgus # - raywainman sig-instrumentation-approvers: - - logicalhan - dashpole - RainbowMango - serathius - dgrisonnet - rexagod - richabanker + - pohly + # sig-instrumentation-emeritus + # - brancz + # - DirectXMan12 + # - ehashman + # - logicalhan sig-instrumentation-reviewers: - dashpole - s-urbaniak - coffeepac - - logicalhan - RainbowMango - serathius - dgrisonnet - pohly - mengjiao-liu + - nmn3m - rexagod - richabanker - # sig-instrumentation-emeritus - # - brancz - # - DirectXMan12 - # - ehashman - api-approvers: - deads2k - msau42 @@ -536,12 +538,18 @@ aliases: - soltysh - liggitt dep-reviewers: - - logicalhan + - BenTheElder + - cblecker + - dims + - thockin + - sttts + - soltysh + - liggitt feature-approvers: - adrianmoisey # Autoscaling - - andrewsykim # Cloud Provider - ardaguclu # CLI - aojea # Network, Testing + - cheftako # Cloud Provider - danwinship # Network - dashpole # Instrumentation - dchen1107 # Node @@ -551,24 +559,26 @@ aliases: - dims # Architecture - dom4ha # Scheduling - eddiezane # CLI + - elmiko # Cloud Provider - enj # Auth - gjtempleton # Autoscaling - jackfrancis # Autoscaling - janetkuo # Apps - jayunit100 # Windows + - joelspeed # Cloud Provider - jpbetz # API Machinery - jsafrane # Storage - jsturtevant # Windows - justinsb # Cluster Lifecycle - kow3ns # Apps - liggitt # Auth - - logicalhan # Instrumentation - luxas # Cluster Lifecycle - macsko # Scheduling - marosset # Windows - mikezappa87 # Network - mrunalp # Node - msau42 # Storage + - pohly # Instrumentation - rexagod # Instrumentation - richabanker # Instrumentation - saad-ali # Storage @@ -582,6 +592,8 @@ aliases: - towca # Autoscaling - xing-yang # Storage - wojtek-t # Scalability + # feature-approvers-emeritus: + # - logicalhan # Instrumentation # conformance aliases https://git.k8s.io/enhancements/keps/sig-architecture/20190412-conformance-behaviors.md conformance-behavior-approvers: - smarterclayton diff --git a/api/api-rules/sample_controller_violation_exceptions.list b/api/api-rules/sample_controller_violation_exceptions.list new file mode 100644 index 0000000000000..403bff328f00c --- /dev/null +++ b/api/api-rules/sample_controller_violation_exceptions.list @@ -0,0 +1,15 @@ +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,Format +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,d +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,i +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,s +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,int64Amount,scale +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,int64Amount,value +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,APIResourceList,APIResources +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Duration,Duration +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Object +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Type +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,MicroTime,Time +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,StatusCause,Type +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Time,Time +API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,ContentEncoding +API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,ContentType diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index 6d16fc9fda746..5b97e5001399d 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -23,6 +23,7 @@ API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfi API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,ReservedMemory API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,ShutdownGracePeriodByPodPriority API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,TLSCipherSuites +API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,TLSCurvePreferences API rule violation: names_match,k8s.io/api/authorization/v1beta1,SubjectAccessReviewSpec,Groups API rule violation: names_match,k8s.io/api/core/v1,AzureDiskVolumeSource,DataDiskURI API rule violation: names_match,k8s.io/api/core/v1,ContainerStatus,LastTerminationState @@ -52,19 +53,31 @@ API rule violation: names_match,k8s.io/api/core/v1,VolumeSource,StorageOS API rule violation: names_match,k8s.io/api/networking/v1,ServiceCIDRSpec,CIDRs API rule violation: names_match,k8s.io/api/networking/v1beta1,ServiceCIDRSpec,CIDRs API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,BoolValue +API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,BoolValues API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,IntValue +API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,IntValues API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,StringValue +API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,StringValues API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,VersionValue +API rule violation: names_match,k8s.io/api/resource/v1,DeviceAttribute,VersionValues API rule violation: names_match,k8s.io/api/resource/v1,NetworkDeviceData,IPs API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,BoolValue +API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,BoolValues API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,IntValue +API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,IntValues API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,StringValue +API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,StringValues API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,VersionValue +API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,VersionValues API rule violation: names_match,k8s.io/api/resource/v1beta1,NetworkDeviceData,IPs API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,BoolValue +API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,BoolValues API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,IntValue +API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,IntValues API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,StringValue +API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,StringValues API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,VersionValue +API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,VersionValues API rule violation: names_match,k8s.io/api/resource/v1beta2,NetworkDeviceData,IPs API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema @@ -146,6 +159,7 @@ API rule violation: names_match,k8s.io/cloud-provider/config/v1alpha1,KubeCloudS API rule violation: names_match,k8s.io/cloud-provider/config/v1alpha1,KubeCloudSharedConfiguration,RouteReconciliationPeriod API rule violation: names_match,k8s.io/cloud-provider/config/v1alpha1,KubeCloudSharedConfiguration,UseServiceAccountCredentials API rule violation: names_match,k8s.io/cloud-provider/config/v1alpha1,WebhookConfiguration,Webhooks +API rule violation: names_match,k8s.io/cloud-provider/controllers/node/config/v1alpha1,NodeControllerConfiguration,ConcurrentNodeStatusUpdates API rule violation: names_match,k8s.io/cloud-provider/controllers/node/config/v1alpha1,NodeControllerConfiguration,ConcurrentNodeSyncs API rule violation: names_match,k8s.io/cloud-provider/controllers/service/config/v1alpha1,ServiceControllerConfiguration,ConcurrentServiceSyncs API rule violation: names_match,k8s.io/controller-manager/config/v1alpha1,GenericControllerManagerConfiguration,Address @@ -218,6 +232,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,K API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,PodGCController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ReplicaSetController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ReplicationController +API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ResourceClaimController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ResourceQuotaController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,SAController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ServiceController @@ -251,6 +266,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,P API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,PodGCControllerConfiguration,TerminatedPodGCThreshold API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ReplicaSetControllerConfiguration,ConcurrentRSSyncs API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ReplicationControllerConfiguration,ConcurrentRCSyncs +API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ResourceClaimControllerConfiguration,ConcurrentSyncs API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ResourceQuotaControllerConfiguration,ConcurrentResourceQuotaSyncs API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ResourceQuotaControllerConfiguration,ResourceQuotaSyncPeriod API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,ConcurrentSATokenSyncs diff --git a/api/discovery/aggregated_v2.json b/api/discovery/aggregated_v2.json index 7a53c2e2a74e8..ad5e43e72a37e 100644 --- a/api/discovery/aggregated_v2.json +++ b/api/discovery/aggregated_v2.json @@ -1323,6 +1323,52 @@ { "freshness": "Current", "resources": [ + { + "categories": [ + "api-extensions" + ], + "resource": "mutatingadmissionpolicies", + "responseKind": { + "group": "", + "kind": "MutatingAdmissionPolicy", + "version": "" + }, + "scope": "Cluster", + "singularResource": "mutatingadmissionpolicy", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "categories": [ + "api-extensions" + ], + "resource": "mutatingadmissionpolicybindings", + "responseKind": { + "group": "", + "kind": "MutatingAdmissionPolicyBinding", + "version": "" + }, + "scope": "Cluster", + "singularResource": "mutatingadmissionpolicybinding", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, { "categories": [ "api-extensions" @@ -1631,6 +1677,41 @@ { "freshness": "Current", "resources": [ + { + "resource": "podgroups", + "responseKind": { + "group": "", + "kind": "PodGroup", + "version": "" + }, + "scope": "Namespaced", + "singularResource": "podgroup", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "PodGroup", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, { "resource": "workloads", "responseKind": { @@ -1652,7 +1733,7 @@ ] } ], - "version": "v1alpha1" + "version": "v1alpha2" } ] }, @@ -1936,6 +2017,41 @@ "watch" ] }, + { + "resource": "devicetaintrules", + "responseKind": { + "group": "", + "kind": "DeviceTaintRule", + "version": "" + }, + "scope": "Cluster", + "singularResource": "devicetaintrule", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "DeviceTaintRule", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, { "resource": "resourceclaims", "responseKind": { @@ -2152,6 +2268,41 @@ "update", "watch" ] + }, + { + "resource": "resourcepoolstatusrequests", + "responseKind": { + "group": "", + "kind": "ResourcePoolStatusRequest", + "version": "" + }, + "scope": "Cluster", + "singularResource": "resourcepoolstatusrequest", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "ResourcePoolStatusRequest", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] } ], "version": "v1alpha3" diff --git a/api/discovery/apis.json b/api/discovery/apis.json index ae395b2e4a089..741da70757d0a 100644 --- a/api/discovery/apis.json +++ b/api/discovery/apis.json @@ -223,8 +223,8 @@ "version": "v1" }, { - "groupVersion": "scheduling.k8s.io/v1alpha1", - "version": "v1alpha1" + "groupVersion": "scheduling.k8s.io/v1alpha2", + "version": "v1alpha2" } ] }, diff --git a/api/discovery/apis__admissionregistration.k8s.io__v1.json b/api/discovery/apis__admissionregistration.k8s.io__v1.json index 9334cc7948b07..ebddc9d4a3418 100644 --- a/api/discovery/apis__admissionregistration.k8s.io__v1.json +++ b/api/discovery/apis__admissionregistration.k8s.io__v1.json @@ -3,6 +3,46 @@ "groupVersion": "admissionregistration.k8s.io/v1", "kind": "APIResourceList", "resources": [ + { + "categories": [ + "api-extensions" + ], + "kind": "MutatingAdmissionPolicy", + "name": "mutatingadmissionpolicies", + "namespaced": false, + "singularName": "mutatingadmissionpolicy", + "storageVersionHash": "LYmCf+UMVdg=", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "categories": [ + "api-extensions" + ], + "kind": "MutatingAdmissionPolicyBinding", + "name": "mutatingadmissionpolicybindings", + "namespaced": false, + "singularName": "mutatingadmissionpolicybinding", + "storageVersionHash": "90V5FRZZ3Zg=", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, { "categories": [ "api-extensions" diff --git a/api/discovery/apis__resource.k8s.io__v1alpha3.json b/api/discovery/apis__resource.k8s.io__v1alpha3.json index 9e91b4e383be8..3033e11801a50 100644 --- a/api/discovery/apis__resource.k8s.io__v1alpha3.json +++ b/api/discovery/apis__resource.k8s.io__v1alpha3.json @@ -30,6 +30,34 @@ "patch", "update" ] + }, + { + "kind": "ResourcePoolStatusRequest", + "name": "resourcepoolstatusrequests", + "namespaced": false, + "singularName": "resourcepoolstatusrequest", + "storageVersionHash": "iy583CtMwdY=", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "kind": "ResourcePoolStatusRequest", + "name": "resourcepoolstatusrequests/status", + "namespaced": false, + "singularName": "", + "verbs": [ + "get", + "patch", + "update" + ] } ] } diff --git a/api/discovery/apis__resource.k8s.io__v1beta2.json b/api/discovery/apis__resource.k8s.io__v1beta2.json index 02e161222486b..ce6bc9c9ff266 100644 --- a/api/discovery/apis__resource.k8s.io__v1beta2.json +++ b/api/discovery/apis__resource.k8s.io__v1beta2.json @@ -20,6 +20,34 @@ "watch" ] }, + { + "kind": "DeviceTaintRule", + "name": "devicetaintrules", + "namespaced": false, + "singularName": "devicetaintrule", + "storageVersionHash": "DJ3UJ0fj8MI=", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "kind": "DeviceTaintRule", + "name": "devicetaintrules/status", + "namespaced": false, + "singularName": "", + "verbs": [ + "get", + "patch", + "update" + ] + }, { "kind": "ResourceClaim", "name": "resourceclaims", diff --git a/api/discovery/apis__scheduling.k8s.io.json b/api/discovery/apis__scheduling.k8s.io.json index a8f4e435bfb3b..fb9cfac6558d0 100644 --- a/api/discovery/apis__scheduling.k8s.io.json +++ b/api/discovery/apis__scheduling.k8s.io.json @@ -12,8 +12,8 @@ "version": "v1" }, { - "groupVersion": "scheduling.k8s.io/v1alpha1", - "version": "v1alpha1" + "groupVersion": "scheduling.k8s.io/v1alpha2", + "version": "v1alpha2" } ] } diff --git a/api/discovery/apis__scheduling.k8s.io__v1alpha1.json b/api/discovery/apis__scheduling.k8s.io__v1alpha1.json deleted file mode 100644 index 8bc6b402acd3a..0000000000000 --- a/api/discovery/apis__scheduling.k8s.io__v1alpha1.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "apiVersion": "v1", - "groupVersion": "scheduling.k8s.io/v1alpha1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "Workload", - "name": "workloads", - "namespaced": true, - "singularName": "workload", - "storageVersionHash": "7X3nQnlLxCA=", - "verbs": [ - "create", - "delete", - "deletecollection", - "get", - "list", - "patch", - "update", - "watch" - ] - } - ] -} diff --git a/api/discovery/apis__scheduling.k8s.io__v1alpha2.json b/api/discovery/apis__scheduling.k8s.io__v1alpha2.json new file mode 100644 index 0000000000000..28f21d9f2cfcc --- /dev/null +++ b/api/discovery/apis__scheduling.k8s.io__v1alpha2.json @@ -0,0 +1,52 @@ +{ + "apiVersion": "v1", + "groupVersion": "scheduling.k8s.io/v1alpha2", + "kind": "APIResourceList", + "resources": [ + { + "kind": "PodGroup", + "name": "podgroups", + "namespaced": true, + "singularName": "podgroup", + "storageVersionHash": "YYgX3v5V204=", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "kind": "PodGroup", + "name": "podgroups/status", + "namespaced": true, + "singularName": "", + "verbs": [ + "get", + "patch", + "update" + ] + }, + { + "kind": "Workload", + "name": "workloads", + "namespaced": true, + "singularName": "workload", + "storageVersionHash": "vSX7gJe5Mx8=", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + } + ] +} diff --git a/api/discovery/apis__storage.k8s.io__v1.json b/api/discovery/apis__storage.k8s.io__v1.json index a10713b88cf3c..005468f74ed37 100644 --- a/api/discovery/apis__storage.k8s.io__v1.json +++ b/api/discovery/apis__storage.k8s.io__v1.json @@ -110,7 +110,7 @@ "vac" ], "singularName": "volumeattributesclass", - "storageVersionHash": "Bl3MtjZ/n/s=", + "storageVersionHash": "tIjydgKBC5w=", "verbs": [ "create", "delete", diff --git a/api/discovery/apis__storage.k8s.io__v1beta1.json b/api/discovery/apis__storage.k8s.io__v1beta1.json index b74991544c7a3..276ef4259ffcc 100644 --- a/api/discovery/apis__storage.k8s.io__v1beta1.json +++ b/api/discovery/apis__storage.k8s.io__v1beta1.json @@ -11,7 +11,7 @@ "vac" ], "singularName": "volumeattributesclass", - "storageVersionHash": "Bl3MtjZ/n/s=", + "storageVersionHash": "tIjydgKBC5w=", "verbs": [ "create", "delete", diff --git a/api/openapi-spec/README.md b/api/openapi-spec/README.md index 3f7a1cdb9c239..fbba6ff0fa756 100644 --- a/api/openapi-spec/README.md +++ b/api/openapi-spec/README.md @@ -54,6 +54,34 @@ For example: } ``` +### `x-kubernetes-list-map-keys` + +Operations and Definitions may have `x-kubernetes-list-maps-keys` if they +are associated with a [kubernetes resource](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources). `x-kubernetes-list-type` = `map` specifies field names inside each list element to serve as unique keys for the list-as-map. + +**For example:** + +```json +{ + "type": "object", + "properties": { + "servers": { + "type": "array", + "x-kubernetes-list-type": "map", + "x-kubernetes-list-map-keys": ["name"], + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "address": { "type": "string" } + }, + "required": ["name"] + } + } + } +} +``` + ### `x-kubernetes-patch-strategy` and `x-kubernetes-patch-merge-key` Some of the definitions may have these extensions. For more information about PatchStrategy and PatchMergeKey see diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 2e0f73febff22..fe0a7b9b1da4e 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -1,5 +1,15 @@ { "definitions": { + "io.k8s.api.admissionregistration.v1.ApplyConfiguration": { + "description": "ApplyConfiguration defines the desired configuration values of an object.", + "properties": { + "expression": { + "description": "expression will be evaluated by CEL to create an apply configuration. ref: https://github.com/google/cel-spec\n\nApply configurations are declared in CEL using object initialization. For example, this CEL expression returns an apply configuration to set a single field:\n\n\tObject{\n\t spec: Object.spec{\n\t serviceAccountName: \"example\"\n\t }\n\t}\n\nApply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of values not included in the apply configuration.\n\nCEL expressions have access to the object types needed to create apply configurations:\n\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.admissionregistration.v1.AuditAnnotation": { "description": "AuditAnnotation describes how to produce an audit annotation for an API request.", "properties": { @@ -22,11 +32,11 @@ "description": "ExpressionWarning is a warning information that targets a specific expression.", "properties": { "fieldRef": { - "description": "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + "description": "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", "type": "string" }, "warning": { - "description": "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + "description": "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", "type": "string" } }, @@ -36,15 +46,25 @@ ], "type": "object" }, + "io.k8s.api.admissionregistration.v1.JSONPatch": { + "description": "JSONPatch defines a JSON Patch.", + "properties": { + "expression": { + "description": "expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). ref: https://github.com/google/cel-spec\n\nexpression must return an array of JSONPatch values.\n\nFor example, this CEL expression returns a JSON patch to conditionally modify a value:\n\n\t [\n\t JSONPatch{op: \"test\", path: \"/spec/example\", value: \"Red\"},\n\t JSONPatch{op: \"replace\", path: \"/spec/example\", value: \"Green\"}\n\t ]\n\nTo define an object for the patch value, use Object types. For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/spec/selector\",\n\t value: Object.spec.selector{matchLabels: {\"environment\": \"test\"}}\n\t }\n\t ]\n\nTo use strings containing '/' and '~' as JSONPatch path keys, use \"jsonpatch.escapeKey\". For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/metadata/labels/\" + jsonpatch.escapeKey(\"example.com/environment\"),\n\t value: \"test\"\n\t },\n\t ]\n\nCEL expressions have access to the types needed to create JSON patches and objects:\n\n- 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'.\n See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string,\n integer, array, map or object. If set, the 'path' and 'from' fields must be set to a\n [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL\n function may be used to escape path keys containing '/' and '~'.\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nCEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) as well as:\n\n- 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively).\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.admissionregistration.v1.MatchCondition": { "description": "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.", "properties": { "expression": { - "description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", "type": "string" }, "name": { - "description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "description": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", "type": "string" } }, @@ -58,7 +78,7 @@ "description": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "properties": { "excludeResourceRules": { - "description": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "description": "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.NamedRuleWithOperations" }, @@ -71,14 +91,14 @@ }, "namespaceSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "resourceRules": { - "description": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + "description": "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.NamedRuleWithOperations" }, @@ -89,11 +109,208 @@ "type": "object", "x-kubernetes-map-type": "atomic" }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy": { + "description": "MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec", + "description": "spec defines the desired behavior of the MutatingAdmissionPolicy." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding": { + "description": "MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding. Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget).\n\nAdding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec", + "description": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList": { + "description": "MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of PolicyBinding.", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBindingList", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec": { + "description": "MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding.", + "properties": { + "matchResources": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchResources", + "description": "matchResources limits what resources match this binding and may be mutated by it. Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and matchConditions before the resource may be mutated. When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints and matchConditions must match for the resource to be mutated. Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT." + }, + "paramRef": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ParamRef", + "description": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param." + }, + "policyName": { + "description": "policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList": { + "description": "MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of ValidatingAdmissionPolicy.", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyList", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec": { + "description": "MutatingAdmissionPolicySpec defines the desired behavior of the admission policy.", + "properties": { + "failurePolicy": { + "description": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if paramKind refers to a non-existent Kind. A binding is invalid if paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", + "type": "string" + }, + "matchConditions": { + "description": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the matchConstraints. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchCondition" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "matchConstraints": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchResources", + "description": "matchConstraints specifies what resources this policy is designed to validate. The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT. Required." + }, + "mutations": { + "description": "mutations contain operations to perform on matching objects. mutations may not be empty; a minimum of one mutation is required. mutations are evaluated in order, and are reinvoked according to the reinvocationPolicy. The mutations of a policy are invoked for each binding of this policy and reinvocation of mutations occurs on a per binding basis.", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.Mutation" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "paramKind": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ParamKind", + "description": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null." + }, + "reinvocationPolicy": { + "description": "reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: These mutations will not be called more than once per binding in a single admission evaluation.\n\nIfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only reinvoked when mutations change the object after this mutation is invoked. Required.", + "type": "string" + }, + "variables": { + "description": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except matchConditions because matchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, variables must be sorted by the order of first appearance and acyclic.", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.Variable" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, "io.k8s.api.admissionregistration.v1.MutatingWebhook": { "description": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.", "properties": { "admissionReviewVersions": { - "description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + "description": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", "items": { "type": "string" }, @@ -102,14 +319,14 @@ }, "clientConfig": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.WebhookClientConfig", - "description": "ClientConfig defines how to communicate with the hook. Required" + "description": "clientConfig defines how to communicate with the hook. Required" }, "failurePolicy": { - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", + "description": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", "type": "string" }, "matchConditions": { - "description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "description": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchCondition" }, @@ -126,23 +343,23 @@ "type": "string" }, "name": { - "description": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "description": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", "type": "string" }, "namespaceSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "reinvocationPolicy": { "description": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: the webhook will not be called more than once in a single admission evaluation.\n\nIfNeeded: the webhook will be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call. Webhooks that specify this option *must* be idempotent, able to process objects they previously admitted. Note: * the number of additional invocations is not guaranteed to be exactly one. * if additional invocations result in further modifications to the object, webhooks are not guaranteed to be invoked again. * webhooks that use this option may be reordered to minimize the number of additional invocations. * to validate an object after all mutations are guaranteed complete, use a validating admission webhook instead.\n\nDefaults to \"Never\".", "type": "string" }, "rules": { - "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "description": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.RuleWithOperations" }, @@ -150,11 +367,11 @@ "x-kubernetes-list-type": "atomic" }, "sideEffects": { - "description": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", + "description": "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", "type": "string" }, "timeoutSeconds": { - "description": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + "description": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", "format": "int32", "type": "integer" } @@ -180,10 +397,10 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "webhooks": { - "description": "Webhooks is a list of webhooks and the affected resources and operations.", + "description": "webhooks is a list of webhooks and the affected resources and operations.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhook" }, @@ -225,7 +442,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -240,11 +457,32 @@ } ] }, + "io.k8s.api.admissionregistration.v1.Mutation": { + "description": "Mutation specifies the CEL expression which is used to apply the Mutation.", + "properties": { + "applyConfiguration": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ApplyConfiguration", + "description": "applyConfiguration defines the desired configuration values of an object. The configuration is applied to the admission object using [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). A CEL expression is used to create apply configuration." + }, + "jsonPatch": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.JSONPatch", + "description": "jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. A CEL expression is used to create the JSON patch." + }, + "patchType": { + "description": "patchType indicates the patch strategy used. Allowed values are \"ApplyConfiguration\" and \"JSONPatch\". Required.", + "type": "string" + } + }, + "required": [ + "patchType" + ], + "type": "object" + }, "io.k8s.api.admissionregistration.v1.NamedRuleWithOperations": { "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -252,7 +490,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -260,7 +498,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -268,7 +506,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", "items": { "type": "string" }, @@ -276,7 +514,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "type": "string" }, @@ -295,11 +533,11 @@ "description": "ParamKind is a tuple of Group Kind and Version.", "properties": { "apiVersion": { - "description": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "description": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", "type": "string" }, "kind": { - "description": "Kind is the API kind the resources belong to. Required.", + "description": "kind is the API kind the resources belong to. Required.", "type": "string" } }, @@ -318,7 +556,7 @@ "type": "string" }, "parameterNotFoundAction": { - "description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + "description": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", "type": "string" }, "selector": { @@ -333,7 +571,7 @@ "description": "RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -341,7 +579,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -349,7 +587,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -357,7 +595,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "type": "string" }, @@ -375,19 +613,19 @@ "description": "ServiceReference holds a reference to Service.legacy.k8s.io", "properties": { "name": { - "description": "`name` is the name of the service. Required", + "description": "name is the name of the service. Required", "type": "string" }, "namespace": { - "description": "`namespace` is the namespace of the service. Required", + "description": "namespace is the namespace of the service. Required", "type": "string" }, "path": { - "description": "`path` is an optional URL path which will be sent in any request to this service.", + "description": "path is an optional URL path which will be sent in any request to this service.", "type": "string" }, "port": { - "description": "If specified, the port on the service that hosting webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", + "description": "port is the port on the service that hosts the webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", "format": "int32", "type": "integer" } @@ -402,7 +640,7 @@ "description": "TypeChecking contains results of type checking the expressions in the ValidatingAdmissionPolicy", "properties": { "expressionWarnings": { - "description": "The type checking warnings for each expression.", + "description": "expressionWarnings contains the type checking warnings for each expression.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ExpressionWarning" }, @@ -425,15 +663,15 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicySpec", - "description": "Specification of the desired behavior of the ValidatingAdmissionPolicy." + "description": "spec defines the desired behavior of the ValidatingAdmissionPolicy." }, "status": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyStatus", - "description": "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only." + "description": "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only." } }, "type": "object", @@ -458,13 +696,16 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingSpec", - "description": "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding." + "description": "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding." } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -494,7 +735,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -514,14 +755,14 @@ "properties": { "matchResources": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchResources", - "description": "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required." + "description": "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required." }, "paramRef": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ParamRef", "description": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param." }, "policyName": { - "description": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "description": "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", "type": "string" }, "validationActions": { @@ -533,6 +774,10 @@ "x-kubernetes-list-type": "set" } }, + "required": [ + "policyName", + "validationActions" + ], "type": "object" }, "io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList": { @@ -555,7 +800,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -586,7 +831,7 @@ "type": "string" }, "matchConditions": { - "description": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "description": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchCondition" }, @@ -600,14 +845,14 @@ }, "matchConstraints": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchResources", - "description": "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required." + "description": "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required." }, "paramKind": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ParamKind", - "description": "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null." + "description": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null." }, "validations": { - "description": "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + "description": "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.Validation" }, @@ -615,7 +860,7 @@ "x-kubernetes-list-type": "atomic" }, "variables": { - "description": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + "description": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.Variable" }, @@ -634,7 +879,7 @@ "description": "ValidatingAdmissionPolicyStatus represents the status of an admission validation policy.", "properties": { "conditions": { - "description": "The conditions represent the latest available observations of a policy's current state.", + "description": "conditions represent the latest available observations of a policy's current state.", "items": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" }, @@ -645,13 +890,13 @@ "x-kubernetes-list-type": "map" }, "observedGeneration": { - "description": "The generation observed by the controller.", + "description": "observedGeneration is the generation observed by the controller.", "format": "int64", "type": "integer" }, "typeChecking": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.TypeChecking", - "description": "The results of type checking for each expression. Presence of this field indicates the completion of the type checking." + "description": "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking." } }, "type": "object" @@ -660,7 +905,7 @@ "description": "ValidatingWebhook describes an admission webhook and the resources and operations it applies to.", "properties": { "admissionReviewVersions": { - "description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + "description": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", "items": { "type": "string" }, @@ -669,14 +914,14 @@ }, "clientConfig": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.WebhookClientConfig", - "description": "ClientConfig defines how to communicate with the hook. Required" + "description": "clientConfig defines how to communicate with the hook. Required" }, "failurePolicy": { - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", + "description": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", "type": "string" }, "matchConditions": { - "description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "description": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchCondition" }, @@ -693,19 +938,19 @@ "type": "string" }, "name": { - "description": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "description": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", "type": "string" }, "namespaceSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "rules": { - "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "description": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.RuleWithOperations" }, @@ -713,11 +958,11 @@ "x-kubernetes-list-type": "atomic" }, "sideEffects": { - "description": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", + "description": "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", "type": "string" }, "timeoutSeconds": { - "description": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + "description": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", "format": "int32", "type": "integer" } @@ -743,10 +988,10 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "webhooks": { - "description": "Webhooks is a list of webhooks and the affected resources and operations.", + "description": "webhooks is a list of webhooks and the affected resources and operations.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhook" }, @@ -788,7 +1033,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -807,11 +1052,11 @@ "description": "Validation specifies the CEL expression which is used to apply the validation.", "properties": { "expression": { - "description": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", "type": "string" }, "message": { - "description": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + "description": "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", "type": "string" }, "messageExpression": { @@ -819,7 +1064,7 @@ "type": "string" }, "reason": { - "description": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + "description": "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", "type": "string" } }, @@ -832,11 +1077,11 @@ "description": "Variable is the definition of a variable that is used for composition. A variable is defined as a named expression.", "properties": { "expression": { - "description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "description": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", "type": "string" }, "name": { - "description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "description": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", "type": "string" } }, @@ -851,16 +1096,16 @@ "description": "WebhookClientConfig contains the information to make a TLS connection with the webhook", "properties": { "caBundle": { - "description": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + "description": "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", "format": "byte", "type": "string" }, "service": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ServiceReference", - "description": "`service` is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`." + "description": "service is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`." }, "url": { - "description": "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + "description": "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", "type": "string" } }, @@ -889,11 +1134,11 @@ "io.k8s.api.admissionregistration.v1alpha1.MatchCondition": { "properties": { "expression": { - "description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", "type": "string" }, "name": { - "description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "description": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", "type": "string" } }, @@ -907,7 +1152,7 @@ "description": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "properties": { "excludeResourceRules": { - "description": "ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "description": "excludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.NamedRuleWithOperations" }, @@ -920,14 +1165,14 @@ }, "namespaceSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "ObjectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "resourceRules": { - "description": "ResourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", + "description": "resourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.NamedRuleWithOperations" }, @@ -951,11 +1196,11 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicySpec", - "description": "Specification of the desired behavior of the MutatingAdmissionPolicy." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicy." } }, "type": "object", @@ -980,11 +1225,11 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBindingSpec", - "description": "Specification of the desired behavior of the MutatingAdmissionPolicyBinding." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding." } }, "type": "object", @@ -1016,7 +1261,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -1069,7 +1314,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -1160,7 +1405,7 @@ "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -1168,7 +1413,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -1176,7 +1421,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -1184,7 +1429,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", "items": { "type": "string" }, @@ -1192,7 +1437,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "type": "string" }, @@ -1211,11 +1456,11 @@ "description": "ParamKind is a tuple of Group Kind and Version.", "properties": { "apiVersion": { - "description": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "description": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", "type": "string" }, "kind": { - "description": "Kind is the API kind the resources belong to. Required.", + "description": "kind is the API kind the resources belong to. Required.", "type": "string" } }, @@ -1226,7 +1471,7 @@ "description": "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.", "properties": { "name": { - "description": "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", + "description": "name is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", "type": "string" }, "namespace": { @@ -1234,7 +1479,7 @@ "type": "string" }, "parameterNotFoundAction": { - "description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`", + "description": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`", "type": "string" }, "selector": { @@ -1249,11 +1494,11 @@ "description": "Variable is the definition of a variable that is used for composition.", "properties": { "expression": { - "description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "description": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", "type": "string" }, "name": { - "description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "description": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", "type": "string" } }, @@ -1287,11 +1532,11 @@ "description": "MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook.", "properties": { "expression": { - "description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", "type": "string" }, "name": { - "description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "description": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", "type": "string" } }, @@ -1305,7 +1550,7 @@ "description": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "properties": { "excludeResourceRules": { - "description": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "description": "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.NamedRuleWithOperations" }, @@ -1318,14 +1563,14 @@ }, "namespaceSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "resourceRules": { - "description": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + "description": "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", "items": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.NamedRuleWithOperations" }, @@ -1349,11 +1594,11 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicySpec", - "description": "Specification of the desired behavior of the MutatingAdmissionPolicy." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicy." } }, "type": "object", @@ -1378,11 +1623,11 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBindingSpec", - "description": "Specification of the desired behavior of the MutatingAdmissionPolicyBinding." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding." } }, "type": "object", @@ -1414,7 +1659,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -1467,7 +1712,7 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -1558,7 +1803,7 @@ "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -1566,7 +1811,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -1574,7 +1819,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "type": "string" }, @@ -1582,7 +1827,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", "items": { "type": "string" }, @@ -1590,7 +1835,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "type": "string" }, @@ -1609,11 +1854,11 @@ "description": "ParamKind is a tuple of Group Kind and Version.", "properties": { "apiVersion": { - "description": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "description": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", "type": "string" }, "kind": { - "description": "Kind is the API kind the resources belong to. Required.", + "description": "kind is the API kind the resources belong to. Required.", "type": "string" } }, @@ -1632,7 +1877,7 @@ "type": "string" }, "parameterNotFoundAction": { - "description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + "description": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", "type": "string" }, "selector": { @@ -1647,11 +1892,11 @@ "description": "Variable is the definition of a variable that is used for composition. A variable is defined as a named expression.", "properties": { "expression": { - "description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "description": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", "type": "string" }, "name": { - "description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "description": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", "type": "string" } }, @@ -1666,11 +1911,11 @@ "description": "An API server instance reports the version it can decode and the version it encodes objects to when persisting objects in the backend.", "properties": { "apiServerID": { - "description": "The ID of the reporting API server.", + "description": "apiServerID is the ID of the reporting API server.", "type": "string" }, "decodableVersions": { - "description": "The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", + "description": "decodableVersions are the encoding versions the API server can handle to decode. The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", "items": { "type": "string" }, @@ -1678,11 +1923,11 @@ "x-kubernetes-list-type": "set" }, "encodingVersion": { - "description": "The API server encodes the object to this version when persisting it in the backend (e.g., etcd).", + "description": "encodingVersion the API server encodes the object to when persisting it in the backend (e.g., etcd).", "type": "string" }, "servedVersions": { - "description": "The API server can serve these versions. DecodableVersions must include all ServedVersions.", + "description": "servedVersions lists all versions the API server can serve. DecodableVersions must include all ServedVersions.", "items": { "type": "string" }, @@ -1690,6 +1935,11 @@ "x-kubernetes-list-type": "set" } }, + "required": [ + "apiServerID", + "encodingVersion", + "decodableVersions" + ], "type": "object" }, "io.k8s.api.apiserverinternal.v1alpha1.StorageVersion": { @@ -1705,20 +1955,19 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "The name is .." + "description": "metadata is the standard object metadata. The name is .." }, "spec": { "$ref": "#/definitions/io.k8s.api.apiserverinternal.v1alpha1.StorageVersionSpec", - "description": "Spec is an empty spec. It is here to comply with Kubernetes API style." + "description": "spec is an empty spec. It is here to comply with Kubernetes API style." }, "status": { "$ref": "#/definitions/io.k8s.api.apiserverinternal.v1alpha1.StorageVersionStatus", - "description": "API server instances report the version they can decode and the version they encode objects to when persisting objects in the backend." + "description": "status on the version the API server instance can decode from and encode objects to when persisting objects in the backend." } }, "required": [ - "spec", - "status" + "metadata" ], "type": "object", "x-kubernetes-group-version-kind": [ @@ -1734,27 +1983,27 @@ "properties": { "lastTransitionTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "Last time the condition transitioned from one status to another." + "description": "lastTransitionTime is the last time the condition transitioned from one status to another." }, "message": { - "description": "A human readable message indicating details about the transition.", + "description": "message is a human readable string indicating details about the transition.", "type": "string" }, "observedGeneration": { - "description": "If set, this represents the .metadata.generation that the condition was set based upon.", + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set.", "format": "int64", "type": "integer" }, "reason": { - "description": "The reason for the condition's last transition.", + "description": "reason for the condition's last transition.", "type": "string" }, "status": { - "description": "Status of the condition, one of True, False, Unknown.", + "description": "status of the condition, one of True, False, Unknown.", "type": "string" }, "type": { - "description": "Type of the condition.", + "description": "type of the condition.", "type": "string" } }, @@ -1809,11 +2058,11 @@ "description": "API server instances report the versions they can decode and the version they encode objects to when persisting objects in the backend.", "properties": { "commonEncodingVersion": { - "description": "If all API server instances agree on the same encoding storage version, then this field is set to that version. Otherwise this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", + "description": "commonEncodingVersion is set to an encoding storage version if all API server instances share that same version. If they don't share one storage version, this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", "type": "string" }, "conditions": { - "description": "The latest available observations of the storageVersion's state.", + "description": "conditions lists the latest available observations of the storageVersion's state.", "items": { "$ref": "#/definitions/io.k8s.api.apiserverinternal.v1alpha1.StorageVersionCondition" }, @@ -1824,7 +2073,7 @@ "x-kubernetes-list-type": "map" }, "storageVersions": { - "description": "The reported versions per API server instance.", + "description": "storageVersions lists the reported versions per API server instance.", "items": { "$ref": "#/definitions/io.k8s.api.apiserverinternal.v1alpha1.ServerStorageVersion" }, @@ -2832,19 +3081,19 @@ "description": "BoundObjectReference is a reference to an object that a token is bound to.", "properties": { "apiVersion": { - "description": "API version of the referent.", + "description": "apiVersion is API version of the referent.", "type": "string" }, "kind": { - "description": "Kind of the referent. Valid kinds are 'Pod' and 'Secret'.", + "description": "kind of the referent. Valid kinds are 'Pod' and 'Secret'.", "type": "string" }, "name": { - "description": "Name of the referent.", + "description": "name of the referent.", "type": "string" }, "uid": { - "description": "UID of the referent.", + "description": "uid of the referent.", "type": "string" } }, @@ -2863,11 +3112,11 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "status": { "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReviewStatus", - "description": "Status is filled in by the server with the user attributes." + "description": "status is filled in by the server with the user attributes." } }, "type": "object", @@ -2884,7 +3133,7 @@ "properties": { "userInfo": { "$ref": "#/definitions/io.k8s.api.authentication.v1.UserInfo", - "description": "User attributes of the user making this request." + "description": "userInfo is a set of attributes belonging to the user making this request." } }, "type": "object" @@ -2902,20 +3151,17 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenRequestSpec", - "description": "Spec holds information about the request being evaluated" + "description": "spec holds information about the request being evaluated" }, "status": { "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenRequestStatus", - "description": "Status is filled in by the server and indicates whether the token can be authenticated." + "description": "status is filled in by the server and indicates whether the token can be authenticated." } }, - "required": [ - "spec" - ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -2929,7 +3175,7 @@ "description": "TokenRequestSpec contains client provided parameters of a token request.", "properties": { "audiences": { - "description": "Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", + "description": "audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", "items": { "type": "string" }, @@ -2938,17 +3184,14 @@ }, "boundObjectRef": { "$ref": "#/definitions/io.k8s.api.authentication.v1.BoundObjectReference", - "description": "BoundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation." + "description": "boundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation." }, "expirationSeconds": { - "description": "ExpirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", + "description": "expirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", "format": "int64", "type": "integer" } }, - "required": [ - "audiences" - ], "type": "object" }, "io.k8s.api.authentication.v1.TokenRequestStatus": { @@ -2956,17 +3199,13 @@ "properties": { "expirationTimestamp": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "ExpirationTimestamp is the time of expiration of the returned token." + "description": "expirationTimestamp is the time of expiration of the returned token." }, "token": { - "description": "Token is the opaque bearer token.", + "description": "token is the opaque bearer token.", "type": "string" } }, - "required": [ - "token", - "expirationTimestamp" - ], "type": "object" }, "io.k8s.api.authentication.v1.TokenReview": { @@ -2982,15 +3221,15 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReviewSpec", - "description": "Spec holds information about the request being evaluated" + "description": "spec holds information about the request being evaluated" }, "status": { "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReviewStatus", - "description": "Status is filled in by the server and indicates whether the request can be authenticated." + "description": "status is filled in by the server and indicates whether the request can be authenticated." } }, "required": [ @@ -3009,7 +3248,7 @@ "description": "TokenReviewSpec is a description of the token authentication request.", "properties": { "audiences": { - "description": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", + "description": "audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", "items": { "type": "string" }, @@ -3017,17 +3256,20 @@ "x-kubernetes-list-type": "atomic" }, "token": { - "description": "Token is the opaque bearer token.", + "description": "token is the opaque bearer token.", "type": "string" } }, + "required": [ + "token" + ], "type": "object" }, "io.k8s.api.authentication.v1.TokenReviewStatus": { "description": "TokenReviewStatus is the result of the token authentication request.", "properties": { "audiences": { - "description": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", + "description": "audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", "items": { "type": "string" }, @@ -3035,16 +3277,16 @@ "x-kubernetes-list-type": "atomic" }, "authenticated": { - "description": "Authenticated indicates that the token was associated with a known user.", + "description": "authenticated indicates that the token was associated with a known user.", "type": "boolean" }, "error": { - "description": "Error indicates that the token couldn't be checked", + "description": "error indicates that the token couldn't be checked", "type": "string" }, "user": { "$ref": "#/definitions/io.k8s.api.authentication.v1.UserInfo", - "description": "User is the UserInfo associated with the provided token." + "description": "user is the UserInfo associated with the provided token." } }, "type": "object" @@ -3059,11 +3301,11 @@ }, "type": "array" }, - "description": "Any additional information provided by the authenticator.", + "description": "extra is any additional information provided by the authenticator.", "type": "object" }, "groups": { - "description": "The names of groups this user is a part of.", + "description": "groups is the names of groups this user is a part of.", "items": { "type": "string" }, @@ -3071,11 +3313,11 @@ "x-kubernetes-list-type": "atomic" }, "uid": { - "description": "A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", + "description": "uid is a unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", "type": "string" }, "username": { - "description": "The name that uniquely identifies this user among all active users.", + "description": "username is the name that uniquely identifies this user among all active users.", "type": "string" } }, @@ -3130,15 +3372,15 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SubjectAccessReviewSpec", - "description": "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted." + "description": "spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted." }, "status": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SubjectAccessReviewStatus", - "description": "Status is filled in by the server and indicates whether the request is allowed or not" + "description": "status is filled in by the server and indicates whether the request is allowed or not" } }, "required": [ @@ -3157,11 +3399,11 @@ "description": "NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface", "properties": { "path": { - "description": "Path is the URL path of the request", + "description": "path is the URL path of the request", "type": "string" }, "verb": { - "description": "Verb is the standard HTTP verb", + "description": "verb is the standard HTTP verb", "type": "string" } }, @@ -3171,7 +3413,7 @@ "description": "NonResourceRule holds information that describes a rule for the non-resource", "properties": { "nonResourceURLs": { - "description": "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", + "description": "nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", "items": { "type": "string" }, @@ -3179,7 +3421,7 @@ "x-kubernetes-list-type": "atomic" }, "verbs": { - "description": "Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", + "description": "verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", "items": { "type": "string" }, @@ -3200,7 +3442,7 @@ "description": "fieldSelector describes the limitation on access based on field. It can only limit access, not broaden it." }, "group": { - "description": "Group is the API Group of the Resource. \"*\" means all.", + "description": "group is the API Group of the Resource. \"*\" means all.", "type": "string" }, "labelSelector": { @@ -3208,27 +3450,27 @@ "description": "labelSelector describes the limitation on access based on labels. It can only limit access, not broaden it." }, "name": { - "description": "Name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", + "description": "name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", "type": "string" }, "namespace": { - "description": "Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", + "description": "namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", "type": "string" }, "resource": { - "description": "Resource is one of the existing resource types. \"*\" means all.", + "description": "resource is one of the existing resource types. \"*\" means all.", "type": "string" }, "subresource": { - "description": "Subresource is one of the existing resource types. \"\" means none.", + "description": "subresource is one of the existing resource types. \"\" means none.", "type": "string" }, "verb": { - "description": "Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "description": "verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", "type": "string" }, "version": { - "description": "Version is the API Version of the Resource. \"*\" means all.", + "description": "version is the API Version of the Resource. \"*\" means all.", "type": "string" } }, @@ -3238,7 +3480,7 @@ "description": "ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", "properties": { "apiGroups": { - "description": "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", + "description": "apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", "items": { "type": "string" }, @@ -3246,7 +3488,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", "items": { "type": "string" }, @@ -3254,7 +3496,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", + "description": "resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", "items": { "type": "string" }, @@ -3262,7 +3504,7 @@ "x-kubernetes-list-type": "atomic" }, "verbs": { - "description": "Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "description": "verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", "items": { "type": "string" }, @@ -3288,15 +3530,15 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SelfSubjectAccessReviewSpec", - "description": "Spec holds information about the request being evaluated. user and groups must be empty" + "description": "spec holds information about the request being evaluated. user and groups must be empty" }, "status": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SubjectAccessReviewStatus", - "description": "Status is filled in by the server and indicates whether the request is allowed or not" + "description": "status is filled in by the server and indicates whether the request is allowed or not" } }, "required": [ @@ -3312,15 +3554,15 @@ ] }, "io.k8s.api.authorization.v1.SelfSubjectAccessReviewSpec": { - "description": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + "description": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", "properties": { "nonResourceAttributes": { "$ref": "#/definitions/io.k8s.api.authorization.v1.NonResourceAttributes", - "description": "NonResourceAttributes describes information for a non-resource access request" + "description": "nonResourceAttributes describes information for a non-resource access request" }, "resourceAttributes": { "$ref": "#/definitions/io.k8s.api.authorization.v1.ResourceAttributes", - "description": "ResourceAuthorizationAttributes describes information for a resource access request" + "description": "resourceAttributes describes information for a resource access request" } }, "type": "object" @@ -3338,15 +3580,15 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SelfSubjectRulesReviewSpec", - "description": "Spec holds information about the request being evaluated." + "description": "spec holds information about the request being evaluated." }, "status": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SubjectRulesReviewStatus", - "description": "Status is filled in by the server and indicates the set of actions a user can perform." + "description": "status is filled in by the server and indicates the set of actions a user can perform." } }, "required": [ @@ -3365,7 +3607,7 @@ "description": "SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview.", "properties": { "namespace": { - "description": "Namespace to evaluate rules for. Required.", + "description": "namespace to evaluate rules for. Required.", "type": "string" } }, @@ -3384,15 +3626,15 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SubjectAccessReviewSpec", - "description": "Spec holds information about the request being evaluated" + "description": "spec holds information about the request being evaluated" }, "status": { "$ref": "#/definitions/io.k8s.api.authorization.v1.SubjectAccessReviewStatus", - "description": "Status is filled in by the server and indicates whether the request is allowed or not" + "description": "status is filled in by the server and indicates whether the request is allowed or not" } }, "required": [ @@ -3408,7 +3650,7 @@ ] }, "io.k8s.api.authorization.v1.SubjectAccessReviewSpec": { - "description": "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + "description": "SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", "properties": { "extra": { "additionalProperties": { @@ -3417,11 +3659,11 @@ }, "type": "array" }, - "description": "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", + "description": "extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", "type": "object" }, "groups": { - "description": "Groups is the groups you're testing for.", + "description": "groups is the groups you're testing for.", "items": { "type": "string" }, @@ -3430,18 +3672,18 @@ }, "nonResourceAttributes": { "$ref": "#/definitions/io.k8s.api.authorization.v1.NonResourceAttributes", - "description": "NonResourceAttributes describes information for a non-resource access request" + "description": "nonResourceAttributes describes information for a non-resource access request" }, "resourceAttributes": { "$ref": "#/definitions/io.k8s.api.authorization.v1.ResourceAttributes", - "description": "ResourceAuthorizationAttributes describes information for a resource access request" + "description": "resourceAttributes describes information for a resource access request" }, "uid": { - "description": "UID information about the requesting user.", + "description": "uid information about the requesting user.", "type": "string" }, "user": { - "description": "User is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", + "description": "user is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", "type": "string" } }, @@ -3451,19 +3693,19 @@ "description": "SubjectAccessReviewStatus", "properties": { "allowed": { - "description": "Allowed is required. True if the action would be allowed, false otherwise.", + "description": "allowed is required. True if the action would be allowed, false otherwise.", "type": "boolean" }, "denied": { - "description": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", + "description": "denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", "type": "boolean" }, "evaluationError": { - "description": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", + "description": "evaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", "type": "string" }, "reason": { - "description": "Reason is optional. It indicates why a request was allowed or denied.", + "description": "reason is optional. It indicates why a request was allowed or denied.", "type": "string" } }, @@ -3476,15 +3718,15 @@ "description": "SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on the set of authorizers the server is configured with and any errors experienced during evaluation. Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, even if that list is incomplete.", "properties": { "evaluationError": { - "description": "EvaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", + "description": "evaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", "type": "string" }, "incomplete": { - "description": "Incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", + "description": "incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", "type": "boolean" }, "nonResourceRules": { - "description": "NonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "description": "nonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", "items": { "$ref": "#/definitions/io.k8s.api.authorization.v1.NonResourceRule" }, @@ -3492,7 +3734,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceRules": { - "description": "ResourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "description": "resourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", "items": { "$ref": "#/definitions/io.k8s.api.authorization.v1.ResourceRule" }, @@ -3554,6 +3796,9 @@ "description": "status is the current information about the autoscaler." } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -3903,6 +4148,9 @@ "description": "status is the current information about the autoscaler." } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -4346,6 +4594,9 @@ "description": "Current status of a cron job. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -5293,7 +5544,7 @@ "type": "string" }, "pkixPublicKey": { - "description": "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", + "description": "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty. Signer implementations should extract the public key from the StubPKCS10Request field.", "format": "byte", "type": "string" }, @@ -5306,7 +5557,7 @@ "type": "string" }, "proofOfPossession": { - "description": "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).", + "description": "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.", "format": "byte", "type": "string" }, @@ -5322,6 +5573,11 @@ "description": "signerName indicates the requested signer.\n\nAll signer names beginning with `kubernetes.io` are reserved for use by the Kubernetes project. There is currently one well-known signer documented by the Kubernetes project, `kubernetes.io/kube-apiserver-client-pod`, which will issue client certificates understood by kube-apiserver. It is currently unimplemented.", "type": "string" }, + "stubPKCS10Request": { + "description": "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost signer implementations will ignore the contents of the CSR except to extract the subject public key. The API server automatically verifies the CSR signature during admission, so the signer does not need to repeat the verification. CSRs generated by kubelet are completely empty.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", + "format": "byte", + "type": "string" + }, "unverifiedUserAnnotations": { "additionalProperties": { "type": "string" @@ -5338,8 +5594,7 @@ "serviceAccountUID", "nodeName", "nodeUID", - "pkixPublicKey", - "proofOfPossession" + "stubPKCS10Request" ], "type": "object" }, @@ -5498,6 +5753,9 @@ "description": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -5597,6 +5855,9 @@ "description": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -8061,6 +8322,19 @@ }, "type": "object" }, + "io.k8s.api.core.v1.ImageVolumeStatus": { + "description": "ImageVolumeStatus represents the image-based volume status.", + "properties": { + "imageRef": { + "description": "ImageRef is the digest of the image used for this volume. It should have a value that's similar to the pod's status.containerStatuses[i].imageID. The ImageRef length should not exceed 256 characters.", + "type": "string" + } + }, + "required": [ + "imageRef" + ], + "type": "object" + }, "io.k8s.api.core.v1.KeyToPath": { "description": "Maps a string key to a path within a volume.", "properties": { @@ -8593,6 +8867,35 @@ }, "type": "object" }, + "io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus": { + "description": "NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA.", + "properties": { + "containers": { + "description": "Containers lists the names of all containers in this pod that reference the claim.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "resourceClaimName": { + "description": "ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation.", + "type": "string" + }, + "resources": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" + }, + "description": "Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim.", + "type": "object" + } + }, + "required": [ + "resourceClaimName", + "resources" + ], + "type": "object" + }, "io.k8s.api.core.v1.NodeCondition": { "description": "NodeCondition contains condition information for a node.", "properties": { @@ -9500,7 +9803,7 @@ }, "portworxVolume": { "$ref": "#/definitions/io.k8s.api.core.v1.PortworxVolumeSource", - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "quobyte": { "$ref": "#/definitions/io.k8s.api.core.v1.QuobyteVolumeSource", @@ -9756,7 +10059,7 @@ "type": "string" }, "observedGeneration": { - "description": "If set, this represents the .metadata.generation that the pod condition was set based upon. The PodObservedGenerationTracking feature gate must be enabled to use this field.", + "description": "If set, this represents the .metadata.generation that the pod condition was set based upon.", "format": "int64", "type": "integer" }, @@ -9920,7 +10223,7 @@ "type": "object" }, "io.k8s.api.core.v1.PodResourceClaim": { - "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and this Pod belongs to a PodGroup, a PodResourceClaim is matched to a PodGroupResourceClaim if all of their fields are equal (Name, ResourceClaimName, and ResourceClaimTemplateName). A matched claim references a single ResourceClaim shared across all Pods in the PodGroup, reserved for the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual Pods.", "properties": { "name": { "description": "Name uniquely identifies this resource claim inside the pod. This must be a DNS_LABEL.", @@ -9931,7 +10234,7 @@ "type": "string" }, "resourceClaimTemplateName": { - "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and the pod belongs to a PodGroup that defines a PodGroupResourceClaim with the same Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the ResourceClaim generated for the PodGroup. All pods in the group that define an equivalent PodResourceClaim matching the PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same generated ResourceClaim. ResourceClaims generated for a PodGroup are owned by the PodGroup and their lifecycles are tied to the PodGroup instead of any individual pod.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", "type": "string" } }, @@ -9948,7 +10251,7 @@ "type": "string" }, "resourceClaimName": { - "description": "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod. If this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", + "description": "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod.\n\nWhen the DRAWorkloadResourceClaims feature is enabled and the corresponding PodResourceClaim matches a PodGroupResourceClaim made by the Pod's PodGroup, then this is the name of the ResourceClaim generated and reserved for the PodGroup.\n\nIf this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", "type": "string" } }, @@ -9970,6 +10273,23 @@ ], "type": "object" }, + "io.k8s.api.core.v1.PodSchedulingGroup": { + "description": "PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics. Exactly one field must be specified.", + "properties": { + "podGroupName": { + "description": "PodGroupName specifies the name of the standalone PodGroup object that represents the runtime instance of this group. Must be a DNS subdomain.", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "podGroupName": "PodGroupName" + } + } + ] + }, "io.k8s.api.core.v1.PodSecurityContext": { "description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", "properties": { @@ -10120,7 +10440,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { @@ -10243,6 +10563,10 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "schedulingGroup": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodSchedulingGroup", + "description": "SchedulingGroup provides a reference to the immediate scheduling runtime grouping object that this Pod belongs to. This field is used by the scheduler to identify the group and apply the correct group scheduling policies. The association with a group also impacts other lifecycle aspects of a Pod that are relevant in a wider context of scheduling like preemption, resource attachment, etc. If not specified, the Pod is treated as a single unit in all of these aspects. The group object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a group object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." + }, "securityContext": { "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext", "description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field." @@ -10306,10 +10630,6 @@ "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" - }, - "workloadRef": { - "$ref": "#/definitions/io.k8s.api.core.v1.WorkloadReference", - "description": "WorkloadRef provides a reference to the Workload object that this Pod belongs to. This field is used by the scheduler to identify the PodGroup and apply the correct group scheduling policies. The Workload object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a Workload object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." } }, "required": [ @@ -10386,6 +10706,14 @@ "description": "A human readable message indicating details about why the pod is in this condition.", "type": "string" }, + "nodeAllocatableResourceClaimStatuses": { + "description": "NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources that were allocated for this pod through DRA claims. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "nominatedNodeName": { "description": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", "type": "string" @@ -11009,6 +11337,10 @@ "description": "Health of the resource. can be one of:\n - Healthy: operates as normal\n - Unhealthy: reported unhealthy. We consider this a temporary health issue\n since we do not have a mechanism today to distinguish\n temporary and permanent issues.\n - Unknown: The status cannot be determined.\n For example, Device Plugin got unregistered and hasn't been re-registered since.\n\nIn future we may want to introduce the PermanentlyUnhealthy Status.", "type": "string" }, + "message": { + "description": "Message provides human-readable context for Health (e.g. \"ECC error count exceeded threshold\"). This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise.", + "type": "string" + }, "resourceID": { "description": "ResourceID is the unique identifier of the resource. See the ResourceID type for more information.", "type": "string" @@ -11580,7 +11912,7 @@ "type": "boolean" }, "procMount": { - "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. Note that this field cannot be set when spec.os.name is windows.", "type": "string" }, "readOnlyRootFilesystem": { @@ -12338,7 +12670,7 @@ }, "image": { "$ref": "#/definitions/io.k8s.api.core.v1.ImageVolumeSource", - "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." + "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." }, "iscsi": { "$ref": "#/definitions/io.k8s.api.core.v1.ISCSIVolumeSource", @@ -12362,7 +12694,7 @@ }, "portworxVolume": { "$ref": "#/definitions/io.k8s.api.core.v1.PortworxVolumeSource", - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "projected": { "$ref": "#/definitions/io.k8s.api.core.v1.ProjectedVolumeSource", @@ -12472,6 +12804,10 @@ "recursiveReadOnly": { "description": "RecursiveReadOnly must be set to Disabled, Enabled, or unspecified (for non-readonly mounts). An IfPossible value in the original VolumeMount must be translated to Disabled or Enabled, depending on the mount result.", "type": "string" + }, + "volumeStatus": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeStatus", + "description": "volumeStatus represents volume-type-specific status about the mounted volume." } }, "required": [ @@ -12540,6 +12876,16 @@ }, "type": "object" }, + "io.k8s.api.core.v1.VolumeStatus": { + "description": "VolumeStatus represents the status of a mounted volume. At most one of its members must be specified.", + "properties": { + "image": { + "$ref": "#/definitions/io.k8s.api.core.v1.ImageVolumeStatus", + "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine." + } + }, + "type": "object" + }, "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource": { "description": "Represents a vSphere volume resource.", "properties": { @@ -12606,28 +12952,6 @@ }, "type": "object" }, - "io.k8s.api.core.v1.WorkloadReference": { - "description": "WorkloadReference identifies the Workload object and PodGroup membership that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics.", - "properties": { - "name": { - "description": "Name defines the name of the Workload object this Pod belongs to. Workload must be in the same namespace as the Pod. If it doesn't match any existing Workload, the Pod will remain unschedulable until a Workload object is created and observed by the kube-scheduler. It must be a DNS subdomain.", - "type": "string" - }, - "podGroup": { - "description": "PodGroup is the name of the PodGroup within the Workload that this Pod belongs to. If it doesn't match any existing PodGroup within the Workload, the Pod will remain unschedulable until the Workload object is recreated and observed by the kube-scheduler. It must be a DNS label.", - "type": "string" - }, - "podGroupReplicaKey": { - "description": "PodGroupReplicaKey specifies the replica key of the PodGroup to which this Pod belongs. It is used to distinguish pods belonging to different replicas of the same pod group. The pod group policy is applied separately to each replica. When set, it must be a DNS label.", - "type": "string" - } - }, - "required": [ - "name", - "podGroup" - ], - "type": "object" - }, "io.k8s.api.discovery.v1.Endpoint": { "description": "Endpoint represents a single logical \"backend\" implementing a service.", "properties": { @@ -12777,8 +13101,7 @@ } }, "required": [ - "addressType", - "endpoints" + "addressType" ], "type": "object", "x-kubernetes-group-version-kind": [ @@ -13632,6 +13955,9 @@ "description": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -14389,6 +14715,9 @@ "description": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -14852,12 +15181,6 @@ "type": "integer" } }, - "required": [ - "disruptionsAllowed", - "currentHealthy", - "desiredHealthy", - "expectedPods" - ], "type": "object" }, "io.k8s.api.rbac.v1.AggregationRule": { @@ -15230,7 +15553,6 @@ } }, "required": [ - "apiGroup", "kind", "name" ], @@ -15315,7 +15637,7 @@ "properties": { "allocationTimestamp": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." + "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." }, "devices": { "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceAllocationResult", @@ -15332,7 +15654,7 @@ "description": "CELDeviceSelector contains a CEL expression for selecting a device.", "properties": { "expression": { - "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", "type": "string" } }, @@ -15450,7 +15772,7 @@ "type": "object" }, "bindingConditions": { - "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -15458,7 +15780,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -15466,7 +15788,7 @@ "x-kubernetes-list-type": "atomic" }, "bindsToNode": { - "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "type": "boolean" }, "capacity": { @@ -15488,6 +15810,13 @@ "description": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", "type": "string" }, + "nodeAllocatableResourceMappings": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.api.resource.v1.NodeAllocatableResourceMapping" + }, + "description": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + "type": "object" + }, "nodeName": { "description": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", "type": "string" @@ -15497,7 +15826,7 @@ "description": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set." }, "taints": { - "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceTaint" }, @@ -15564,18 +15893,51 @@ "description": "BoolValue is a true/false value.", "type": "boolean" }, + "bools": { + "description": "BoolValues is a non-empty list of true/false values.", + "items": { + "type": "boolean" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "int": { "description": "IntValue is a number.", "format": "int64", "type": "integer" }, + "ints": { + "description": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "string": { "description": "StringValue is a string. Must not be longer than 64 characters.", "type": "string" }, + "strings": { + "description": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "version": { "description": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", "type": "string" + }, + "versions": { + "description": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" } }, "type": "object" @@ -15646,7 +16008,7 @@ "type": "object" }, "io.k8s.api.resource.v1.DeviceClass": { - "description": "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -15734,7 +16096,7 @@ "x-kubernetes-list-type": "atomic" }, "extendedResourceName": { - "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", "type": "string" }, "selectors": { @@ -15752,11 +16114,11 @@ "description": "DeviceConstraint must have exactly one field set besides Requests.", "properties": { "distinctAttribute": { - "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", "type": "string" }, "matchAttribute": { - "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as single-element lists for backward compatibility.\n\nMust include the domain qualifier.", "type": "string" }, "requests": { @@ -15820,11 +16182,11 @@ "description": "DeviceRequestAllocationResult contains the allocation result for one request.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "bindingConditions": { - "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -15832,7 +16194,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -15867,7 +16229,7 @@ "type": "string" }, "tolerations": { - "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceToleration" }, @@ -15926,7 +16288,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceToleration" }, @@ -15953,7 +16315,7 @@ }, "timeAdded": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -15997,7 +16359,7 @@ "description": "ExactDeviceRequest is a request for one or more identical devices.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "allocationMode": { @@ -16026,7 +16388,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceToleration" }, @@ -16043,11 +16405,11 @@ "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { "hardwareAddress": { - "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", "type": "string" }, "interfaceName": { - "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "type": "string" }, "ips": { @@ -16061,6 +16423,20 @@ }, "type": "object" }, + "io.k8s.api.resource.v1.NodeAllocatableResourceMapping": { + "description": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "properties": { + "allocationMultiplier": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", + "description": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16." + }, + "capacityKey": { + "description": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1.OpaqueDeviceConfiguration": { "description": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "properties": { @@ -16080,7 +16456,7 @@ "type": "object" }, "io.k8s.api.resource.v1.ResourceClaim": { - "description": "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -16225,7 +16601,7 @@ "type": "object" }, "io.k8s.api.resource.v1.ResourceClaimTemplate": { - "description": "ResourceClaimTemplate is used to produce ResourceClaim objects.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "ResourceClaimTemplate is used to produce ResourceClaim objects.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -16334,7 +16710,7 @@ "type": "object" }, "io.k8s.api.resource.v1.ResourceSlice": { - "description": "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -16463,7 +16839,7 @@ }, "timeAdded": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -16601,6 +16977,192 @@ }, "type": "object" }, + "io.k8s.api.resource.v1alpha3.PoolStatus": { + "description": "PoolStatus contains status information for a single resource pool.", + "properties": { + "allocatedDevices": { + "description": "AllocatedDevices is the number of devices currently allocated to claims. A value of 0 means no devices are allocated. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "availableDevices": { + "description": "AvailableDevices is the number of devices available for allocation. This equals TotalDevices - AllocatedDevices - UnavailableDevices. A value of 0 means no devices are currently available. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "driver": { + "description": "Driver is the DRA driver name for this pool. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + "type": "string" + }, + "generation": { + "description": "Generation is the pool generation observed across all ResourceSlices in this pool. Only the latest generation is reported. During a generation rollout, if not all slices at the latest generation have been published, the pool is included with a validationError and device counts unset.", + "format": "int64", + "type": "integer" + }, + "nodeName": { + "description": "NodeName is the node this pool is associated with. When omitted, the pool is not associated with a specific node. Must be a valid DNS subdomain name (RFC1123).", + "type": "string" + }, + "poolName": { + "description": "PoolName is the name of the pool. Must be a valid resource pool name (DNS subdomains separated by \"/\").", + "type": "string" + }, + "resourceSliceCount": { + "description": "ResourceSliceCount is the number of ResourceSlices that make up this pool. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "totalDevices": { + "description": "TotalDevices is the total number of devices in the pool across all slices. A value of 0 means the pool has no devices. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "unavailableDevices": { + "description": "UnavailableDevices is the number of devices that are not available due to taints or other conditions, but are not allocated. A value of 0 means all unallocated devices are available. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "validationError": { + "description": "ValidationError is set when the pool's data could not be fully validated (e.g., incomplete slice publication). When set, device count fields and ResourceSliceCount may be unset.", + "type": "string" + } + }, + "required": [ + "driver", + "poolName", + "generation" + ], + "type": "object" + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest": { + "description": "ResourcePoolStatusRequest triggers a one-time calculation of resource pool status based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. Users should delete and recreate requests to get updated information.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object metadata" + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec", + "description": "Spec defines the filters for which pools to include in the status. The spec is immutable once created." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus", + "description": "Status is populated by the controller with the calculated pool status. When status is non-nil, the request is considered complete and the entire object becomes immutable." + } + }, + "required": [ + "metadata", + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" + } + ] + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList": { + "description": "ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of ResourcePoolStatusRequests.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", + "description": "Standard list metadata" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequestList", + "version": "v1alpha3" + } + ] + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec": { + "description": "ResourcePoolStatusRequestSpec defines the filters for the pool status request.", + "properties": { + "driver": { + "description": "Driver specifies the DRA driver name to filter pools. Only pools from ResourceSlices with this driver will be included. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + "type": "string" + }, + "limit": { + "description": "Limit optionally specifies the maximum number of pools to return in the status. If more pools match the filter criteria, the response will be truncated (i.e., len(status.pools) < status.poolCount).\n\nDefault: 100 Minimum: 1 Maximum: 1000", + "format": "int32", + "type": "integer" + }, + "poolName": { + "description": "PoolName optionally filters to a specific pool name. If not specified, all pools from the specified driver are included. When specified, must be a non-empty valid resource pool name (DNS subdomains separated by \"/\").", + "type": "string" + } + }, + "required": [ + "driver" + ], + "type": "object" + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus": { + "description": "ResourcePoolStatusRequestStatus contains the calculated pool status information.", + "properties": { + "conditions": { + "description": "Conditions provide information about the state of the request. A condition with type=Complete or type=Failed will always be set when the status is populated.\n\nKnown condition types: - \"Complete\": True when the request has been processed successfully - \"Failed\": True when the request could not be processed", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "poolCount": { + "description": "PoolCount is the total number of pools that matched the filter criteria, regardless of truncation. This helps users understand how many pools exist even when the response is truncated. A value of 0 means no pools matched the filter criteria.", + "format": "int32", + "type": "integer" + }, + "pools": { + "description": "Pools contains the first `spec.limit` matching pools, sorted by driver then pool name. If `len(pools) < poolCount`, the list was truncated. When omitted, no pools matched the request filters.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.PoolStatus" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "poolCount" + ], + "type": "object" + }, "io.k8s.api.resource.v1beta1.AllocatedDeviceStatus": { "description": "AllocatedDeviceStatus contains the status of an allocated device, if the driver chooses to report it. This may include driver-specific information.\n\nThe combination of Driver, Pool, Device, and ShareID must match the corresponding key in Status.Allocation.Devices.", "properties": { @@ -16652,7 +17214,7 @@ "properties": { "allocationTimestamp": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." + "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." }, "devices": { "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceAllocationResult", @@ -16684,7 +17246,7 @@ "type": "object" }, "bindingConditions": { - "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -16692,7 +17254,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -16700,7 +17262,7 @@ "x-kubernetes-list-type": "atomic" }, "bindsToNode": { - "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "type": "boolean" }, "capacity": { @@ -16718,6 +17280,13 @@ "type": "array", "x-kubernetes-list-type": "atomic" }, + "nodeAllocatableResourceMappings": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping" + }, + "description": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + "type": "object" + }, "nodeName": { "description": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", "type": "string" @@ -16727,7 +17296,7 @@ "description": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set." }, "taints": { - "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceTaint" }, @@ -16741,7 +17310,7 @@ "description": "CELDeviceSelector contains a CEL expression for selecting a device.", "properties": { "expression": { - "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", "type": "string" } }, @@ -16911,18 +17480,51 @@ "description": "BoolValue is a true/false value.", "type": "boolean" }, + "bools": { + "description": "BoolValues is a non-empty list of true/false values.", + "items": { + "type": "boolean" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "int": { "description": "IntValue is a number.", "format": "int64", "type": "integer" }, + "ints": { + "description": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "string": { "description": "StringValue is a string. Must not be longer than 64 characters.", "type": "string" }, + "strings": { + "description": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "version": { "description": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", "type": "string" + }, + "versions": { + "description": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" } }, "type": "object" @@ -17081,7 +17683,7 @@ "x-kubernetes-list-type": "atomic" }, "extendedResourceName": { - "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", "type": "string" }, "selectors": { @@ -17099,11 +17701,11 @@ "description": "DeviceConstraint must have exactly one field set besides Requests.", "properties": { "distinctAttribute": { - "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", "type": "string" }, "matchAttribute": { - "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", "type": "string" }, "requests": { @@ -17183,7 +17785,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceToleration" }, @@ -17204,7 +17806,7 @@ "type": "boolean" }, "bindingConditions": { - "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -17212,7 +17814,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -17247,7 +17849,7 @@ "type": "string" }, "tolerations": { - "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceToleration" }, @@ -17306,7 +17908,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceToleration" }, @@ -17333,7 +17935,7 @@ }, "timeAdded": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -17377,11 +17979,11 @@ "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { "hardwareAddress": { - "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", "type": "string" }, "interfaceName": { - "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "type": "string" }, "ips": { @@ -17395,6 +17997,20 @@ }, "type": "object" }, + "io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping": { + "description": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "properties": { + "allocationMultiplier": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", + "description": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16." + }, + "capacityKey": { + "description": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1beta1.OpaqueDeviceConfiguration": { "description": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "properties": { @@ -17835,7 +18451,7 @@ "properties": { "allocationTimestamp": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." + "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." }, "devices": { "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceAllocationResult", @@ -17852,7 +18468,7 @@ "description": "CELDeviceSelector contains a CEL expression for selecting a device.", "properties": { "expression": { - "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", "type": "string" } }, @@ -17970,7 +18586,7 @@ "type": "object" }, "bindingConditions": { - "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -17978,7 +18594,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -17986,7 +18602,7 @@ "x-kubernetes-list-type": "atomic" }, "bindsToNode": { - "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "type": "boolean" }, "capacity": { @@ -18008,6 +18624,13 @@ "description": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", "type": "string" }, + "nodeAllocatableResourceMappings": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping" + }, + "description": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + "type": "object" + }, "nodeName": { "description": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", "type": "string" @@ -18017,7 +18640,7 @@ "description": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set." }, "taints": { - "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaint" }, @@ -18084,18 +18707,51 @@ "description": "BoolValue is a true/false value.", "type": "boolean" }, + "bools": { + "description": "BoolValues is a non-empty list of true/false values.", + "items": { + "type": "boolean" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "int": { "description": "IntValue is a number.", "format": "int64", "type": "integer" }, + "ints": { + "description": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "string": { "description": "StringValue is a string. Must not be longer than 64 characters.", "type": "string" }, + "strings": { + "description": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "version": { "description": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", "type": "string" + }, + "versions": { + "description": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" } }, "type": "object" @@ -18254,7 +18910,7 @@ "x-kubernetes-list-type": "atomic" }, "extendedResourceName": { - "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", "type": "string" }, "selectors": { @@ -18272,11 +18928,11 @@ "description": "DeviceConstraint must have exactly one field set besides Requests.", "properties": { "distinctAttribute": { - "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", "type": "string" }, "matchAttribute": { - "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", "type": "string" }, "requests": { @@ -18344,7 +19000,7 @@ "type": "boolean" }, "bindingConditions": { - "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -18352,7 +19008,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "type": "string" }, @@ -18387,7 +19043,7 @@ "type": "string" }, "tolerations": { - "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceToleration" }, @@ -18446,7 +19102,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceToleration" }, @@ -18473,7 +19129,7 @@ }, "timeAdded": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -18486,6 +19142,131 @@ ], "type": "object" }, + "io.k8s.api.resource.v1beta2.DeviceTaintRule": { + "description": "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object metadata" + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec", + "description": "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus", + "description": "Status provides information about what was requested in the spec." + } + }, + "required": [ + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + ] + }, + "io.k8s.api.resource.v1beta2.DeviceTaintRuleList": { + "description": "DeviceTaintRuleList is a collection of DeviceTaintRules.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of DeviceTaintRules.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", + "description": "Standard list metadata" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "DeviceTaintRuleList", + "version": "v1beta2" + } + ] + }, + "io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec": { + "description": "DeviceTaintRuleSpec specifies the selector and one taint.", + "properties": { + "deviceSelector": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintSelector", + "description": "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satisfied for a device to match. The empty selector matches all devices. Without a selector, no devices are matches." + }, + "taint": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaint", + "description": "The taint that gets applied to matching devices." + } + }, + "required": [ + "taint" + ], + "type": "object" + }, + "io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus": { + "description": "DeviceTaintRuleStatus provides information about an on-going pod eviction.", + "properties": { + "conditions": { + "description": "Conditions provide information about the state of the DeviceTaintRule and the cluster at some point in time, in a machine-readable and human-readable format.\n\nThe following condition is currently defined as part of this API, more may get added: - Type: EvictionInProgress - Status: True if there are currently pods which need to be evicted, False otherwise\n (includes the effects which don't cause eviction).\n- Reason: not specified, may change - Message: includes information about number of pending pods and already evicted pods\n in a human-readable format, updated periodically, may change\n\nFor `effect: None`, the condition above gets set once for each change to the spec, with the message containing information about what would happen if the effect was `NoExecute`. This feedback can be used to decide whether changing the effect to `NoExecute` will work as intended. It only gets set once to avoid having to constantly update the status.\n\nMust have 8 or fewer entries.", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + } + }, + "type": "object" + }, + "io.k8s.api.resource.v1beta2.DeviceTaintSelector": { + "description": "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.", + "properties": { + "device": { + "description": "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.", + "type": "string" + }, + "driver": { + "description": "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.", + "type": "string" + }, + "pool": { + "description": "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1beta2.DeviceToleration": { "description": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple using the matching operator .", "properties": { @@ -18546,7 +19327,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceToleration" }, @@ -18563,11 +19344,11 @@ "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { "hardwareAddress": { - "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", "type": "string" }, "interfaceName": { - "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "type": "string" }, "ips": { @@ -18581,6 +19362,20 @@ }, "type": "object" }, + "io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping": { + "description": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "properties": { + "allocationMultiplier": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", + "description": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16." + }, + "capacityKey": { + "description": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1beta2.OpaqueDeviceConfiguration": { "description": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "properties": { @@ -19003,9 +19798,6 @@ "type": "integer" } }, - "required": [ - "value" - ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -19050,11 +19842,11 @@ } ] }, - "io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy": { + "io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy": { "description": "BasicSchedulingPolicy indicates that standard Kubernetes scheduling behavior should be used.", "type": "object" }, - "io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy": { + "io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy": { "description": "GangSchedulingPolicy defines the parameters for gang scheduling.", "properties": { "minCount": { @@ -19068,39 +19860,309 @@ ], "type": "object" }, - "io.k8s.api.scheduling.v1alpha1.PodGroup": { - "description": "PodGroup represents a set of pods with a common scheduling policy.", + "io.k8s.api.scheduling.v1alpha2.PodGroup": { + "description": "PodGroup represents a runtime instance of pods grouped together. PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from Workload.podGroupTemplates. PodGroup API enablement is toggled by the GenericWorkload feature gate.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupSpec", + "description": "Spec defines the desired state of the PodGroup." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupStatus", + "description": "Status represents the current observed state of the PodGroup." + } + }, + "required": [ + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupList": { + "description": "PodGroupList contains a list of PodGroup resources.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of PodGroups.", + "items": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", + "description": "Standard list metadata." + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "scheduling.k8s.io", + "kind": "PodGroupList", + "version": "v1alpha2" + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim": { + "description": "PodGroupResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the PodGroup.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. Pods that need access to the ResourceClaim define a matching reference in its own Spec.ResourceClaims. The Pod's claim must match all fields of the PodGroup's claim exactly.", + "properties": { + "name": { + "description": "Name uniquely identifies this resource claim inside the PodGroup. This must be a DNS_LABEL.", + "type": "string" + }, + "resourceClaimName": { + "description": "ResourceClaimName is the name of a ResourceClaim object in the same namespace as this PodGroup. The ResourceClaim will be reserved for the PodGroup instead of its individual pods.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "type": "string" + }, + "resourceClaimTemplateName": { + "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this PodGroup.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim will also be deleted. The PodGroup name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus": { + "description": "PodGroupResourceClaimStatus is stored in the PodGroupStatus for each PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the generated name for the corresponding ResourceClaim.", "properties": { "name": { - "description": "Name is a unique identifier for the PodGroup within the Workload. It must be a DNS label. This field is immutable.", + "description": "Name uniquely identifies this resource claim inside the PodGroup. This must match the name of an entry in podgroup.spec.resourceClaims, which implies that the string must be a DNS_LABEL.", "type": "string" }, - "policy": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.PodGroupPolicy", - "description": "Policy defines the scheduling policy for this PodGroup." + "resourceClaimName": { + "description": "ResourceClaimName is the name of the ResourceClaim that was generated for the PodGroup in the namespace of the PodGroup. If this is unset, then generating a ResourceClaim was not necessary. The podgroup.spec.resourceClaims entry can be ignored in this case.", + "type": "string" } }, "required": [ - "name", - "policy" + "name" ], "type": "object" }, - "io.k8s.api.scheduling.v1alpha1.PodGroupPolicy": { - "description": "PodGroupPolicy defines the scheduling configuration for a PodGroup.", + "io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints": { + "description": "PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup.", + "properties": { + "topology": { + "description": "Topology defines the topology constraints for the pod group. Currently only a single topology constraint can be specified. This may change in the future.", + "items": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.TopologyConstraint" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy": { + "description": "PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. Exactly one policy must be set.", "properties": { "basic": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy", + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy", "description": "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior." }, "gang": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy", + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy", "description": "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics." } }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "basic": "Basic", + "gang": "Gang" + } + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupSpec": { + "description": "PodGroupSpec defines the desired state of a PodGroup.", + "properties": { + "disruptionMode": { + "description": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. Controllers are expected to fill this field by copying it from a PodGroupTemplate. One of Pod, PodGroup. Defaults to Pod if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "podGroupTemplateRef": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference", + "description": "PodGroupTemplateRef references an optional PodGroup template within other object (e.g. Workload) that was used to create the PodGroup. This field is immutable." + }, + "priority": { + "description": "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "description": "PriorityClassName defines the priority that should be considered when scheduling this pod group. Controllers are expected to fill this field by copying it from a PodGroupTemplate. Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate (i.e. if no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, the pod group's priority will be zero). This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "resourceClaims": { + "description": "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + "items": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + }, + "schedulingConstraints": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints", + "description": "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled." + }, + "schedulingPolicy": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy", + "description": "SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable." + } + }, + "required": [ + "schedulingPolicy" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupStatus": { + "description": "PodGroupStatus represents information about the status of a pod group.", + "properties": { + "conditions": { + "description": "Conditions represent the latest observations of the PodGroup's state.\n\nKnown condition types: - \"PodGroupScheduled\": Indicates whether the scheduling requirement has been satisfied. - \"DisruptionTarget\": Indicates whether the PodGroup is about to be terminated\n due to disruption such as preemption.\n\nKnown reasons for the PodGroupScheduled condition: - \"Unschedulable\": The PodGroup cannot be scheduled due to resource constraints,\n affinity/anti-affinity rules, or insufficient capacity for the gang.\n- \"SchedulerError\": The PodGroup cannot be scheduled due to some internal error\n that happened during scheduling, for example due to nodeAffinity parsing errors.\n\nKnown reasons for the DisruptionTarget condition: - \"PreemptionByScheduler\": The PodGroup was preempted by the scheduler to make room for\n higher-priority PodGroups or Pods.", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "resourceClaimStatuses": { + "description": "Status of resource claims.", + "items": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + } + }, + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupTemplate": { + "description": "PodGroupTemplate represents a template for a set of pods with a scheduling policy.", + "properties": { + "disruptionMode": { + "description": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. One of Pod, PodGroup. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "name": { + "description": "Name is a unique identifier for the PodGroupTemplate within the Workload. It must be a DNS label. This field is immutable.", + "type": "string" + }, + "priority": { + "description": "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "description": "PriorityClassName indicates the priority that should be considered when scheduling a pod group created from this template. If no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, pod groups created from this template will have the priority set to zero. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "resourceClaims": { + "description": "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + "items": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + }, + "schedulingConstraints": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints", + "description": "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled." + }, + "schedulingPolicy": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy", + "description": "SchedulingPolicy defines the scheduling policy for this PodGroupTemplate." + } + }, + "required": [ + "name", + "schedulingPolicy" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference": { + "description": "PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). Exactly one reference must be set.", + "properties": { + "workload": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference", + "description": "Workload references the PodGroupTemplate within the Workload object that was used to create the PodGroup." + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "workload": "Workload" + } + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.TopologyConstraint": { + "description": "TopologyConstraint defines a topology constraint for a PodGroup.", + "properties": { + "key": { + "description": "Key specifies the key of the node label representing the topology domain. All pods within the PodGroup must be colocated within the same domain instance. Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. Examples: \"topology.kubernetes.io/rack\"", + "type": "string" + } + }, + "required": [ + "key" + ], "type": "object" }, - "io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference": { + "io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference": { "description": "TypedLocalObjectReference allows to reference typed object inside the same namespace.", "properties": { "apiGroup": { @@ -19122,8 +20184,8 @@ ], "type": "object" }, - "io.k8s.api.scheduling.v1alpha1.Workload": { - "description": "Workload allows for expressing scheduling constraints that should be used when managing lifecycle of workloads from scheduling perspective, including scheduling, preemption, eviction and other phases.", + "io.k8s.api.scheduling.v1alpha2.Workload": { + "description": "Workload allows for expressing scheduling constraints that should be used when managing the lifecycle of workloads from the scheduling perspective, including scheduling, preemption, eviction and other phases. Workload API enablement is toggled by the GenericWorkload feature gate.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -19135,10 +20197,10 @@ }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "description": "Standard object's metadata. Name must be a DNS subdomain." + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.WorkloadSpec", + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.WorkloadSpec", "description": "Spec defines the desired behavior of a Workload." } }, @@ -19150,11 +20212,11 @@ { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } ] }, - "io.k8s.api.scheduling.v1alpha1.WorkloadList": { + "io.k8s.api.scheduling.v1alpha2.WorkloadList": { "description": "WorkloadList contains a list of Workload resources.", "properties": { "apiVersion": { @@ -19164,7 +20226,7 @@ "items": { "description": "Items is the list of Workloads.", "items": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" }, "type": "array" }, @@ -19185,21 +20247,39 @@ { "group": "scheduling.k8s.io", "kind": "WorkloadList", - "version": "v1alpha1" + "version": "v1alpha2" } ] }, - "io.k8s.api.scheduling.v1alpha1.WorkloadSpec": { + "io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference": { + "description": "WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object.", + "properties": { + "podGroupTemplateName": { + "description": "PodGroupTemplateName defines the PodGroupTemplate name within the Workload object.", + "type": "string" + }, + "workloadName": { + "description": "WorkloadName defines the name of the Workload object.", + "type": "string" + } + }, + "required": [ + "workloadName", + "podGroupTemplateName" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.WorkloadSpec": { "description": "WorkloadSpec defines the desired state of a Workload.", "properties": { "controllerRef": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference", - "description": "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. When set, it cannot be changed." + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference", + "description": "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. This field is immutable." }, - "podGroups": { - "description": "PodGroups is the list of pod groups that make up the Workload. The maximum number of pod groups is 8. This field is immutable.", + "podGroupTemplates": { + "description": "PodGroupTemplates is the list of templates that make up the Workload. The maximum number of templates is 8. This field is immutable.", "items": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.PodGroup" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupTemplate" }, "type": "array", "x-kubernetes-list-map-keys": [ @@ -19209,7 +20289,7 @@ } }, "required": [ - "podGroups" + "podGroupTemplates" ], "type": "object" }, @@ -19292,7 +20372,7 @@ "type": "string" }, "nodeAllocatableUpdatePeriodSeconds": { - "description": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", + "description": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis feature requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", "format": "int64", "type": "integer" }, @@ -19300,6 +20380,10 @@ "description": "podInfoOnMount indicates this CSI volume driver requires additional pod information (like podName, podUID, etc.) during mount operations, if set to true. If set to false, pod information will not be passed on mount. Default is false.\n\nThe CSI driver specifies podInfoOnMount as part of driver deployment. If true, Kubelet will pass pod information as VolumeContext in the CSI NodePublishVolume() calls. The CSI driver is responsible for parsing and validating the information passed in as VolumeContext.\n\nThe following VolumeContext will be passed if podInfoOnMount is set to true. This list might grow, but the prefix will be used. \"csi.storage.k8s.io/pod.name\": pod.Name \"csi.storage.k8s.io/pod.namespace\": pod.Namespace \"csi.storage.k8s.io/pod.uid\": string(pod.UID) \"csi.storage.k8s.io/ephemeral\": \"true\" if the volume is an ephemeral inline volume\n defined by a CSIVolumeSource, otherwise \"false\"\n\n\"csi.storage.k8s.io/ephemeral\" is a new feature in Kubernetes 1.16. It is only required for drivers which support both the \"Persistent\" and \"Ephemeral\" VolumeLifecycleMode. Other drivers can leave pod info disabled and/or ignore this field. As Kubernetes 1.15 doesn't support this field, drivers can only support one mode when deployed on such a cluster and the deployment determines which mode that is, for example via a command line parameter of the driver.\n\nThis field was immutable in Kubernetes < 1.29 and now is mutable.", "type": "boolean" }, + "preventPodSchedulingIfMissing": { + "description": "PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod scheduling if the CSI driver on the node is missing.\n\nEnabling this option will prevent the scheduler (or any other component which embeds default scheduler such as cluster-autoscaler) from scheduling pods to nodes where CSI driver is not installed.\n\nFor components(such as cluster-autoscaler) that embed the scheduler and run pod placement simulations using scheduler plugins, they MUST be aware of CSI driver registration information via CSINode object. They must create simulated CSINode objects in addition to Node objects during scheduling simulation, otherwise if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any newly created node may be rejected by the scheduler because of missing CSI driver information from the node.\n\nThis is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. Default is \"false\".", + "type": "boolean" + }, "requiresRepublish": { "description": "requiresRepublish indicates the CSI driver wants `NodePublishVolume` being periodically called to reflect any possible change in the mounted volume. This field defaults to false.\n\nNote: After a successful initial NodePublishVolume call, subsequent calls to NodePublishVolume should only update the contents of the volume. New mount points will not be seen by a running container.", "type": "boolean" @@ -19867,7 +20951,7 @@ "description": "VolumeError captures an error encountered during a volume operation.", "properties": { "errorCode": { - "description": "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", + "description": "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", "format": "int32", "type": "integer" }, @@ -21197,16 +22281,6 @@ "kind": "DeleteOptions", "version": "v2" }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, { "group": "batch", "kind": "DeleteOptions", @@ -21380,7 +22454,7 @@ { "group": "scheduling.k8s.io", "kind": "DeleteOptions", - "version": "v1alpha1" + "version": "v1alpha2" }, { "group": "scheduling.k8s.io", @@ -21540,6 +22614,10 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo", + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -21748,6 +22826,19 @@ ], "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -21969,16 +23060,6 @@ "kind": "WatchEvent", "version": "v2" }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, { "group": "batch", "kind": "WatchEvent", @@ -22152,7 +23233,7 @@ { "group": "scheduling.k8s.io", "kind": "WatchEvent", - "version": "v1alpha1" + "version": "v1alpha2" }, { "group": "scheduling.k8s.io", @@ -22671,6 +23752,13 @@ "type": "boolean", "uniqueItems": true }, + "shardSelector-Kgyki_3_": { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "type": "string", + "uniqueItems": true + }, "sinceSeconds-vE2NLdnP": { "description": "A relative time in seconds before the current time from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.", "in": "query", @@ -22933,6 +24021,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -23060,6 +24151,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -23136,6 +24230,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -23212,6 +24309,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -23288,6 +24388,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -23328,6 +24431,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -23585,6 +24691,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -23650,6 +24759,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -24085,6 +25197,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -24150,6 +25265,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -24585,6 +25703,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -24650,6 +25771,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -25085,6 +26209,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -25150,6 +26277,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -25585,6 +26715,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -25650,6 +26783,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -26279,6 +27415,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -26344,6 +27483,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -28409,6 +29551,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -28474,6 +29619,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -28909,6 +30057,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -28974,6 +30125,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -29797,6 +30951,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -29862,6 +31019,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -30491,6 +31651,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -30556,6 +31719,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -30991,6 +32157,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -31056,6 +32225,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -31584,6 +32756,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -31649,6 +32824,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -33311,6 +34489,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -33376,6 +34557,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -34508,6 +35692,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -34567,6 +35754,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -34632,6 +35822,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35269,6 +36462,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35345,6 +36541,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35421,6 +36620,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35497,6 +36699,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35573,6 +36778,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35649,6 +36857,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35725,6 +36936,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35801,6 +37015,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35877,6 +37094,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -35953,6 +37173,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36029,6 +37252,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36105,6 +37331,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36184,6 +37413,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36271,6 +37503,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36350,6 +37585,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36437,6 +37675,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36516,6 +37757,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36603,6 +37847,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36682,6 +37929,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36769,6 +38019,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36848,6 +38101,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -36935,6 +38191,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37014,6 +38273,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37101,6 +38363,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37180,6 +38445,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37267,6 +38535,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37346,6 +38617,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37433,6 +38707,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37512,6 +38789,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37599,6 +38879,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37678,6 +38961,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37765,6 +39051,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37844,6 +39133,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -37931,6 +39223,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38010,6 +39305,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38097,6 +39395,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38181,6 +39482,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38257,6 +39561,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38341,6 +39648,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38417,6 +39727,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38493,6 +39806,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38577,6 +39893,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38653,6 +39972,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38729,6 +40051,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38805,6 +40130,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38881,6 +40209,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -38957,6 +40288,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -39033,6 +40367,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -39109,6 +40446,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -39218,13 +40558,13 @@ ] } }, - "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicies": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of MutatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1CollectionMutatingWebhookConfiguration", + "description": "delete collection of MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1CollectionMutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -39269,6 +40609,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -39299,7 +40642,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, @@ -39307,8 +40650,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind MutatingWebhookConfiguration", - "operationId": "listAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "list or watch objects of kind MutatingAdmissionPolicy", + "operationId": "listAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -39334,6 +40677,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -39354,7 +40700,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "401": { @@ -39370,7 +40716,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, @@ -39383,15 +40729,15 @@ "consumes": [ "*/*" ], - "description": "create a MutatingWebhookConfiguration", - "operationId": "createAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "create a MutatingAdmissionPolicy", + "operationId": "createAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, { @@ -39422,19 +40768,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "401": { @@ -39450,18 +40796,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/{name}": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicies/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a MutatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "delete a MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -39518,7 +40864,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, @@ -39526,8 +40872,8 @@ "consumes": [ "*/*" ], - "description": "read the specified MutatingWebhookConfiguration", - "operationId": "readAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "read the specified MutatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1MutatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -39538,7 +40884,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "401": { @@ -39554,13 +40900,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, "parameters": [ { - "description": "name of the MutatingWebhookConfiguration", + "description": "name of the MutatingAdmissionPolicy", "in": "path", "name": "name", "required": true, @@ -39579,8 +40925,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified MutatingWebhookConfiguration", - "operationId": "patchAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "partially update the specified MutatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -39616,13 +40962,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "401": { @@ -39638,7 +40984,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, @@ -39646,15 +40992,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified MutatingWebhookConfiguration", - "operationId": "replaceAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "replace the specified MutatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, { @@ -39685,13 +41031,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "401": { @@ -39707,18 +41053,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicybindings": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ValidatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicy", + "description": "delete collection of MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1CollectionMutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -39763,6 +41109,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -39793,7 +41142,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, @@ -39801,8 +41150,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ValidatingAdmissionPolicy", - "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "list or watch objects of kind MutatingAdmissionPolicyBinding", + "operationId": "listAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -39828,6 +41177,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -39848,7 +41200,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "401": { @@ -39864,7 +41216,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, @@ -39877,15 +41229,15 @@ "consumes": [ "*/*" ], - "description": "create a ValidatingAdmissionPolicy", - "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "create a MutatingAdmissionPolicyBinding", + "operationId": "createAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, { @@ -39916,19 +41268,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -39944,18 +41296,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicybindings/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ValidatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "delete a MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -40012,207 +41364,16 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", - "version": "v1" - } - }, - "get": { - "consumes": [ - "*/*" - ], - "description": "read the specified ValidatingAdmissionPolicy", - "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicy", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1" - ], - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", - "version": "v1" - } - }, - "parameters": [ - { - "description": "name of the ValidatingAdmissionPolicy", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - } - ], - "patch": { - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update the specified ValidatingAdmissionPolicy", - "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicy", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1" - ], - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, - "put": { - "consumes": [ - "*/*" - ], - "description": "replace the specified ValidatingAdmissionPolicy", - "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicy", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", - "version": "v1" - } - } - }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified ValidatingAdmissionPolicy", - "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "description": "read the specified MutatingAdmissionPolicyBinding", + "operationId": "readAdmissionregistrationV1MutatingAdmissionPolicyBinding", "produces": [ "application/json", "application/yaml", @@ -40223,7 +41384,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -40239,13 +41400,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, "parameters": [ { - "description": "name of the ValidatingAdmissionPolicy", + "description": "name of the MutatingAdmissionPolicyBinding", "in": "path", "name": "name", "required": true, @@ -40264,8 +41425,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified ValidatingAdmissionPolicy", - "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "description": "partially update the specified MutatingAdmissionPolicyBinding", + "operationId": "patchAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -40301,13 +41462,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -40323,7 +41484,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, @@ -40331,15 +41492,15 @@ "consumes": [ "*/*" ], - "description": "replace status of the specified ValidatingAdmissionPolicy", - "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "description": "replace the specified MutatingAdmissionPolicyBinding", + "operationId": "replaceAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, { @@ -40370,13 +41531,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -40392,18 +41553,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings": { + "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ValidatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicyBinding", + "description": "delete collection of MutatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1CollectionMutatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -40448,6 +41609,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -40478,7 +41642,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, @@ -40486,8 +41650,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ValidatingAdmissionPolicyBinding", - "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "list or watch objects of kind MutatingWebhookConfiguration", + "operationId": "listAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -40513,6 +41677,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -40533,7 +41700,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "401": { @@ -40549,7 +41716,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, @@ -40562,15 +41729,15 @@ "consumes": [ "*/*" ], - "description": "create a ValidatingAdmissionPolicyBinding", - "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "create a MutatingWebhookConfiguration", + "operationId": "createAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, { @@ -40601,19 +41768,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "401": { @@ -40629,18 +41796,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings/{name}": { + "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ValidatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "delete a MutatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -40697,7 +41864,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, @@ -40705,8 +41872,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ValidatingAdmissionPolicyBinding", - "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "read the specified MutatingWebhookConfiguration", + "operationId": "readAdmissionregistrationV1MutatingWebhookConfiguration", "produces": [ "application/json", "application/yaml", @@ -40717,7 +41884,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "401": { @@ -40733,13 +41900,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, "parameters": [ { - "description": "name of the ValidatingAdmissionPolicyBinding", + "description": "name of the MutatingWebhookConfiguration", "in": "path", "name": "name", "required": true, @@ -40758,8 +41925,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ValidatingAdmissionPolicyBinding", - "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "partially update the specified MutatingWebhookConfiguration", + "operationId": "patchAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -40795,13 +41962,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "401": { @@ -40817,7 +41984,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, @@ -40825,15 +41992,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ValidatingAdmissionPolicyBinding", - "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "replace the specified MutatingWebhookConfiguration", + "operationId": "replaceAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, { @@ -40864,13 +42031,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "401": { @@ -40886,18 +42053,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ValidatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1CollectionValidatingWebhookConfiguration", + "description": "delete collection of ValidatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -40942,6 +42109,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -40972,7 +42142,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, @@ -40980,8 +42150,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ValidatingWebhookConfiguration", - "operationId": "listAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "list or watch objects of kind ValidatingAdmissionPolicy", + "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -41007,6 +42177,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -41027,7 +42200,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "401": { @@ -41043,7 +42216,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, @@ -41056,15 +42229,15 @@ "consumes": [ "*/*" ], - "description": "create a ValidatingWebhookConfiguration", - "operationId": "createAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "create a ValidatingAdmissionPolicy", + "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, { @@ -41095,19 +42268,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "401": { @@ -41123,18 +42296,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations/{name}": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ValidatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "delete a ValidatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -41191,7 +42364,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, @@ -41199,8 +42372,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ValidatingWebhookConfiguration", - "operationId": "readAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "read the specified ValidatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -41211,7 +42384,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "401": { @@ -41227,13 +42400,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, "parameters": [ { - "description": "name of the ValidatingWebhookConfiguration", + "description": "name of the ValidatingAdmissionPolicy", "in": "path", "name": "name", "required": true, @@ -41252,8 +42425,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ValidatingWebhookConfiguration", - "operationId": "patchAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "partially update the specified ValidatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -41289,13 +42462,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "401": { @@ -41311,7 +42484,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, @@ -41319,15 +42492,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ValidatingWebhookConfiguration", - "operationId": "replaceAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "replace the specified ValidatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, { @@ -41358,13 +42531,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "401": { @@ -41380,32 +42553,29 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/watch/mutatingwebhookconfigurations": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of MutatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1MutatingWebhookConfigurationList", + "description": "read status of the specified ValidatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyStatus", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "401": { @@ -41418,70 +42588,78 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" + "description": "name of the ValidatingAdmissionPolicy", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/admissionregistration.k8s.io/v1/watch/mutatingwebhookconfigurations/{name}": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified ValidatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch changes to an object of kind MutatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1MutatingWebhookConfiguration", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "401": { @@ -41494,78 +42672,152 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the MutatingWebhookConfiguration", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + "put": { + "consumes": [ + "*/*" + ], + "description": "replace status of the specified ValidatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "401": { + "description": "Unauthorized" + } }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicy", + "version": "v1" } - ] + } }, - "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicies": { - "get": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings": { + "delete": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ValidatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicyList", + "description": "delete collection of ValidatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -41578,56 +42830,54 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "ValidatingAdmissionPolicyBinding", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicies/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ValidatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "list or watch objects of kind ValidatingAdmissionPolicyBinding", + "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], "produces": [ "application/json", "application/yaml", @@ -41641,7 +42891,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" } }, "401": { @@ -41654,78 +42904,74 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "ValidatingAdmissionPolicyBinding", "version": "v1" } }, "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ValidatingAdmissionPolicy", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicybindings": { - "get": { + ], + "post": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ValidatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicyBindingList", + "description": "create a ValidatingAdmissionPolicyBinding", + "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" } }, "401": { @@ -41738,70 +42984,62 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "ValidatingAdmissionPolicyBinding", "version": "v1" } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] + } }, - "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicybindings/{name}": { - "get": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings/{name}": { + "delete": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ValidatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "delete a ValidatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -41814,78 +43052,30 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "ValidatingAdmissionPolicyBinding", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ValidatingAdmissionPolicyBinding", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/admissionregistration.k8s.io/v1/watch/validatingwebhookconfigurations": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ValidatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1ValidatingWebhookConfigurationList", + "description": "read the specified ValidatingAdmissionPolicyBinding", + "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyBinding", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" } }, "401": { @@ -41898,70 +43088,78 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicyBinding", "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" + "description": "name of the ValidatingAdmissionPolicyBinding", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/admissionregistration.k8s.io/v1/watch/validatingwebhookconfigurations/{name}": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified ValidatingAdmissionPolicyBinding", + "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch changes to an object of kind ValidatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1ValidatingWebhookConfiguration", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" } }, "401": { @@ -41974,67 +43172,46 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicyBinding", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ValidatingWebhookConfiguration", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/admissionregistration.k8s.io/v1alpha1/": { - "get": { + "put": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "*/*" + ], + "description": "replace the specified ValidatingAdmissionPolicyBinding", + "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } ], - "description": "get available resources", - "operationId": "getAdmissionregistrationV1alpha1APIResources", "produces": [ "application/json", "application/yaml", @@ -42045,7 +43222,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" } }, "401": { @@ -42056,17 +43239,23 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" - ] + "admissionregistration_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } } }, - "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicies": { + "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of MutatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1alpha1CollectionMutatingAdmissionPolicy", + "description": "delete collection of ValidatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1CollectionValidatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -42111,6 +43300,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -42136,21 +43328,21 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind MutatingAdmissionPolicy", - "operationId": "listAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "list or watch objects of kind ValidatingWebhookConfiguration", + "operationId": "listAdmissionregistrationV1ValidatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -42176,6 +43368,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -42196,7 +43391,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" } }, "401": { @@ -42207,13 +43402,13 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "parameters": [ @@ -42225,15 +43420,15 @@ "consumes": [ "*/*" ], - "description": "create a MutatingAdmissionPolicy", - "operationId": "createAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "create a ValidatingWebhookConfiguration", + "operationId": "createAdmissionregistrationV1ValidatingWebhookConfiguration", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, { @@ -42264,19 +43459,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "401": { @@ -42287,23 +43482,23 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicies/{name}": { + "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a MutatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "delete a ValidatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1ValidatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -42355,21 +43550,21 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified MutatingAdmissionPolicy", - "operationId": "readAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "read the specified ValidatingWebhookConfiguration", + "operationId": "readAdmissionregistrationV1ValidatingWebhookConfiguration", "produces": [ "application/json", "application/yaml", @@ -42380,7 +43575,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "401": { @@ -42391,18 +43586,18 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "parameters": [ { - "description": "name of the MutatingAdmissionPolicy", + "description": "name of the ValidatingWebhookConfiguration", "in": "path", "name": "name", "required": true, @@ -42421,8 +43616,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified MutatingAdmissionPolicy", - "operationId": "patchAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "partially update the specified ValidatingWebhookConfiguration", + "operationId": "patchAdmissionregistrationV1ValidatingWebhookConfiguration", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -42458,13 +43653,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "401": { @@ -42475,28 +43670,28 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified MutatingAdmissionPolicy", - "operationId": "replaceAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "replace the specified ValidatingWebhookConfiguration", + "operationId": "replaceAdmissionregistrationV1ValidatingWebhookConfiguration", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, { @@ -42527,13 +43722,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" } }, "401": { @@ -42544,82 +43739,37 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicybindings": { - "delete": { + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicies": { + "get": { "consumes": [ "*/*" ], - "description": "delete collection of MutatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1alpha1CollectionMutatingAdmissionPolicyBinding", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - } - ], + "description": "watch individual changes to a list of MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicyList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -42630,53 +43780,61 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "deletecollection", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "MutatingAdmissionPolicy", + "version": "v1" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicies/{name}": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind MutatingAdmissionPolicyBinding", - "operationId": "listAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ], + "description": "watch changes to an object of kind MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -42690,7 +43848,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBindingList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -42701,76 +43859,83 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "MutatingAdmissionPolicy", + "version": "v1" } }, "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the MutatingAdmissionPolicy", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "post": { + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicybindings": { + "get": { "consumes": [ "*/*" ], - "description": "create a MutatingAdmissionPolicyBinding", - "operationId": "createAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], + "description": "watch individual changes to a list of MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicyBindingList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -42781,64 +43946,75 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "version": "v1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicybindings/{name}": { - "delete": { + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicybindings/{name}": { + "get": { "consumes": [ "*/*" ], - "description": "delete a MutatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - } - ], + "description": "watch changes to an object of kind MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicyBinding", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -42849,32 +44025,83 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "version": "v1" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the MutatingAdmissionPolicyBinding", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingwebhookconfigurations": { "get": { "consumes": [ "*/*" ], - "description": "read the specified MutatingAdmissionPolicyBinding", - "operationId": "readAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", + "description": "watch individual changes to a list of MutatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1MutatingWebhookConfigurationList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -42885,80 +44112,75 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "MutatingWebhookConfiguration", + "version": "v1" } }, "parameters": [ { - "description": "name of the MutatingAdmissionPolicyBinding", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingwebhookconfigurations/{name}": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update the specified MutatingAdmissionPolicyBinding", - "operationId": "patchAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "*/*" ], + "description": "watch changes to an object of kind MutatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1MutatingWebhookConfiguration", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -42969,65 +44191,162 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "MutatingWebhookConfiguration", + "version": "v1" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the MutatingWebhookConfiguration", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicies": { + "get": { "consumes": [ "*/*" ], - "description": "replace the specified MutatingAdmissionPolicyBinding", - "operationId": "replaceAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, + "description": "watch individual changes to a list of ValidatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicyList", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true + "401": { + "description": "Unauthorized" } + }, + "schemes": [ + "https" ], + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicy", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicies/{name}": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch changes to an object of kind ValidatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -43038,23 +44357,69 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "ValidatingAdmissionPolicy", + "version": "v1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ValidatingAdmissionPolicy", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicies": { + "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicybindings": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicyList", + "description": "watch individual changes to a list of ValidatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicyBindingList", "produces": [ "application/json", "application/yaml", @@ -43079,13 +44444,13 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" } }, "parameters": [ @@ -43116,6 +44481,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -43124,13 +44492,13 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicies/{name}": { + "/apis/admissionregistration.k8s.io/v1/watch/validatingadmissionpolicybindings/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicy", + "description": "watch changes to an object of kind ValidatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1ValidatingAdmissionPolicyBinding", "produces": [ "application/json", "application/yaml", @@ -43155,13 +44523,13 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicy", - "version": "v1alpha1" + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" } }, "parameters": [ @@ -43181,7 +44549,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the MutatingAdmissionPolicy", + "description": "name of the ValidatingAdmissionPolicyBinding", "in": "path", "name": "name", "required": true, @@ -43200,6 +44568,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -43208,13 +44579,13 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicybindings": { + "/apis/admissionregistration.k8s.io/v1/watch/validatingwebhookconfigurations": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicyBindingList", + "description": "watch individual changes to a list of ValidatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1ValidatingWebhookConfigurationList", "produces": [ "application/json", "application/yaml", @@ -43239,13 +44610,13 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "parameters": [ @@ -43276,6 +44647,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -43284,13 +44658,13 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicybindings/{name}": { + "/apis/admissionregistration.k8s.io/v1/watch/validatingwebhookconfigurations/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", + "description": "watch changes to an object of kind ValidatingWebhookConfiguration. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1ValidatingWebhookConfiguration", "produces": [ "application/json", "application/yaml", @@ -43315,13 +44689,13 @@ "https" ], "tags": [ - "admissionregistration_v1alpha1" + "admissionregistration_v1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingAdmissionPolicyBinding", - "version": "v1alpha1" + "kind": "ValidatingWebhookConfiguration", + "version": "v1" } }, "parameters": [ @@ -43341,7 +44715,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the MutatingAdmissionPolicyBinding", + "description": "name of the ValidatingWebhookConfiguration", "in": "path", "name": "name", "required": true, @@ -43360,6 +44734,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -43368,7 +44745,7 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1beta1/": { + "/apis/admissionregistration.k8s.io/v1alpha1/": { "get": { "consumes": [ "application/json", @@ -43377,7 +44754,7 @@ "application/cbor" ], "description": "get available resources", - "operationId": "getAdmissionregistrationV1beta1APIResources", + "operationId": "getAdmissionregistrationV1alpha1APIResources", "produces": [ "application/json", "application/yaml", @@ -43399,17 +44776,17 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ] } }, - "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicies": { + "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicies": { "delete": { "consumes": [ "*/*" ], "description": "delete collection of MutatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1beta1CollectionMutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1alpha1CollectionMutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -43454,6 +44831,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -43479,13 +44859,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "get": { @@ -43493,7 +44873,7 @@ "*/*" ], "description": "list or watch objects of kind MutatingAdmissionPolicy", - "operationId": "listAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "listAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -43519,6 +44899,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -43539,7 +44922,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyList" } }, "401": { @@ -43550,13 +44933,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -43569,14 +44952,14 @@ "*/*" ], "description": "create a MutatingAdmissionPolicy", - "operationId": "createAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "createAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, { @@ -43607,19 +44990,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "401": { @@ -43630,23 +45013,23 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } } }, - "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicies/{name}": { + "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicies/{name}": { "delete": { "consumes": [ "*/*" ], "description": "delete a MutatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -43698,13 +45081,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "get": { @@ -43712,7 +45095,7 @@ "*/*" ], "description": "read the specified MutatingAdmissionPolicy", - "operationId": "readAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -43723,7 +45106,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "401": { @@ -43734,13 +45117,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -43765,7 +45148,7 @@ "application/apply-patch+cbor" ], "description": "partially update the specified MutatingAdmissionPolicy", - "operationId": "patchAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -43801,13 +45184,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "401": { @@ -43818,13 +45201,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "put": { @@ -43832,14 +45215,14 @@ "*/*" ], "description": "replace the specified MutatingAdmissionPolicy", - "operationId": "replaceAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, { @@ -43870,13 +45253,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicy" } }, "401": { @@ -43887,23 +45270,23 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } } }, - "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicybindings": { + "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicybindings": { "delete": { "consumes": [ "*/*" ], "description": "delete collection of MutatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1beta1CollectionMutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1alpha1CollectionMutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -43948,6 +45331,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -43973,13 +45359,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "get": { @@ -43987,7 +45373,7 @@ "*/*" ], "description": "list or watch objects of kind MutatingAdmissionPolicyBinding", - "operationId": "listAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "listAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -44013,6 +45399,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -44033,7 +45422,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBindingList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBindingList" } }, "401": { @@ -44044,13 +45433,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -44063,14 +45452,14 @@ "*/*" ], "description": "create a MutatingAdmissionPolicyBinding", - "operationId": "createAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "createAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, { @@ -44101,19 +45490,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -44124,23 +45513,23 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } } }, - "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicybindings/{name}": { + "/apis/admissionregistration.k8s.io/v1alpha1/mutatingadmissionpolicybindings/{name}": { "delete": { "consumes": [ "*/*" ], "description": "delete a MutatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -44192,13 +45581,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "get": { @@ -44206,7 +45595,7 @@ "*/*" ], "description": "read the specified MutatingAdmissionPolicyBinding", - "operationId": "readAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "readAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "produces": [ "application/json", "application/yaml", @@ -44217,7 +45606,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -44228,13 +45617,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -44259,7 +45648,7 @@ "application/apply-patch+cbor" ], "description": "partially update the specified MutatingAdmissionPolicyBinding", - "operationId": "patchAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "patchAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -44295,13 +45684,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -44312,13 +45701,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "put": { @@ -44326,14 +45715,14 @@ "*/*" ], "description": "replace the specified MutatingAdmissionPolicyBinding", - "operationId": "replaceAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "replaceAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, { @@ -44364,13 +45753,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -44381,23 +45770,23 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } } }, - "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicies": { + "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicies": { "get": { "consumes": [ "*/*" ], "description": "watch individual changes to a list of MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicyList", + "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicyList", "produces": [ "application/json", "application/yaml", @@ -44422,13 +45811,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -44459,6 +45848,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -44467,13 +45859,13 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicies/{name}": { + "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicies/{name}": { "get": { "consumes": [ "*/*" ], "description": "watch changes to an object of kind MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicy", + "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -44498,13 +45890,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicy", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -44543,6 +45935,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -44551,13 +45946,13 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicybindings": { + "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicybindings": { "get": { "consumes": [ "*/*" ], "description": "watch individual changes to a list of MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicyBindingList", + "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicyBindingList", "produces": [ "application/json", "application/yaml", @@ -44582,13 +45977,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -44619,6 +46014,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -44627,13 +46025,13 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicybindings/{name}": { + "/apis/admissionregistration.k8s.io/v1alpha1/watch/mutatingadmissionpolicybindings/{name}": { "get": { "consumes": [ "*/*" ], "description": "watch changes to an object of kind MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "operationId": "watchAdmissionregistrationV1alpha1MutatingAdmissionPolicyBinding", "produces": [ "application/json", "application/yaml", @@ -44658,13 +46056,13 @@ "https" ], "tags": [ - "admissionregistration_v1beta1" + "admissionregistration_v1alpha1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", "kind": "MutatingAdmissionPolicyBinding", - "version": "v1beta1" + "version": "v1alpha1" } }, "parameters": [ @@ -44703,6 +46101,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -44711,40 +46112,7 @@ } ] }, - "/apis/apiextensions.k8s.io/": { - "get": { - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "description": "get information of a group", - "operationId": "getApiextensionsAPIGroup", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "apiextensions" - ] - } - }, - "/apis/apiextensions.k8s.io/v1/": { + "/apis/admissionregistration.k8s.io/v1beta1/": { "get": { "consumes": [ "application/json", @@ -44753,7 +46121,7 @@ "application/cbor" ], "description": "get available resources", - "operationId": "getApiextensionsV1APIResources", + "operationId": "getAdmissionregistrationV1beta1APIResources", "produces": [ "application/json", "application/yaml", @@ -44775,17 +46143,17 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ] } }, - "/apis/apiextensions.k8s.io/v1/customresourcedefinitions": { + "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicies": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of CustomResourceDefinition", - "operationId": "deleteApiextensionsV1CollectionCustomResourceDefinition", + "description": "delete collection of MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1beta1CollectionMutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -44830,6 +46198,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -44855,21 +46226,21 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind CustomResourceDefinition", - "operationId": "listApiextensionsV1CustomResourceDefinition", + "description": "list or watch objects of kind MutatingAdmissionPolicy", + "operationId": "listAdmissionregistrationV1beta1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -44895,6 +46266,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -44915,7 +46289,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinitionList" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyList" } }, "401": { @@ -44926,13 +46300,13 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "parameters": [ @@ -44944,15 +46318,15 @@ "consumes": [ "*/*" ], - "description": "create a CustomResourceDefinition", - "operationId": "createApiextensionsV1CustomResourceDefinition", + "description": "create a MutatingAdmissionPolicy", + "operationId": "createAdmissionregistrationV1beta1MutatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, { @@ -44983,19 +46357,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "401": { @@ -45006,23 +46380,23 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } } }, - "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/{name}": { + "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicies/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a CustomResourceDefinition", - "operationId": "deleteApiextensionsV1CustomResourceDefinition", + "description": "delete a MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1beta1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -45074,21 +46448,21 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified CustomResourceDefinition", - "operationId": "readApiextensionsV1CustomResourceDefinition", + "description": "read the specified MutatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1beta1MutatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -45099,7 +46473,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "401": { @@ -45110,18 +46484,18 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "parameters": [ { - "description": "name of the CustomResourceDefinition", + "description": "name of the MutatingAdmissionPolicy", "in": "path", "name": "name", "required": true, @@ -45140,8 +46514,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified CustomResourceDefinition", - "operationId": "patchApiextensionsV1CustomResourceDefinition", + "description": "partially update the specified MutatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1beta1MutatingAdmissionPolicy", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -45177,13 +46551,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "401": { @@ -45194,28 +46568,28 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified CustomResourceDefinition", - "operationId": "replaceApiextensionsV1CustomResourceDefinition", + "description": "replace the specified MutatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1beta1MutatingAdmissionPolicy", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, { @@ -45246,13 +46620,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicy" } }, "401": { @@ -45263,23 +46637,216 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } } }, - "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/{name}/status": { + "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicybindings": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1beta1CollectionMutatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" + } + }, "get": { "consumes": [ "*/*" ], - "description": "read status of the specified CustomResourceDefinition", - "operationId": "readApiextensionsV1CustomResourceDefinitionStatus", + "description": "list or watch objects of kind MutatingAdmissionPolicyBinding", + "operationId": "listAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBindingList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a MutatingAdmissionPolicyBinding", + "operationId": "createAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", @@ -45290,7 +46857,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -45301,18 +46880,122 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1beta1/mutatingadmissionpolicybindings/{name}": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete a MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "read the specified MutatingAdmissionPolicyBinding", + "operationId": "readAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" } }, "parameters": [ { - "description": "name of the CustomResourceDefinition", + "description": "name of the MutatingAdmissionPolicyBinding", "in": "path", "name": "name", "required": true, @@ -45331,8 +47014,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified CustomResourceDefinition", - "operationId": "patchApiextensionsV1CustomResourceDefinitionStatus", + "description": "partially update the specified MutatingAdmissionPolicyBinding", + "operationId": "patchAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -45368,13 +47051,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -45385,28 +47068,28 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" } }, "put": { "consumes": [ "*/*" ], - "description": "replace status of the specified CustomResourceDefinition", - "operationId": "replaceApiextensionsV1CustomResourceDefinitionStatus", + "description": "replace the specified MutatingAdmissionPolicyBinding", + "operationId": "replaceAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" } }, { @@ -45437,13 +47120,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingAdmissionPolicyBinding" } }, "401": { @@ -45454,23 +47137,23 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" } } }, - "/apis/apiextensions.k8s.io/v1/watch/customresourcedefinitions": { + "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicies": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of CustomResourceDefinition. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchApiextensionsV1CustomResourceDefinitionList", + "description": "watch individual changes to a list of MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicyList", "produces": [ "application/json", "application/yaml", @@ -45495,13 +47178,13 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "parameters": [ @@ -45532,6 +47215,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -45540,13 +47226,13 @@ } ] }, - "/apis/apiextensions.k8s.io/v1/watch/customresourcedefinitions/{name}": { + "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicies/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind CustomResourceDefinition. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchApiextensionsV1CustomResourceDefinition", + "description": "watch changes to an object of kind MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicy", "produces": [ "application/json", "application/yaml", @@ -45571,13 +47257,13 @@ "https" ], "tags": [ - "apiextensions_v1" + "admissionregistration_v1beta1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { - "group": "apiextensions.k8s.io", - "kind": "CustomResourceDefinition", - "version": "v1" + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1beta1" } }, "parameters": [ @@ -45597,7 +47283,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the CustomResourceDefinition", + "description": "name of the MutatingAdmissionPolicy", "in": "path", "name": "name", "required": true, @@ -45616,6 +47302,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -45624,60 +47313,27 @@ } ] }, - "/apis/apiregistration.k8s.io/": { - "get": { - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "description": "get information of a group", - "operationId": "getApiregistrationAPIGroup", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "apiregistration" - ] - } - }, - "/apis/apiregistration.k8s.io/v1/": { + "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicybindings": { "get": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "*/*" ], - "description": "get available resources", - "operationId": "getApiregistrationV1APIResources", + "description": "watch individual changes to a list of MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicyBindingList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -45688,17 +47344,216 @@ "https" ], "tags": [ - "apiregistration_v1" - ] - } - }, - "/apis/apiregistration.k8s.io/v1/apiservices": { - "delete": { - "consumes": [ - "*/*" + "admissionregistration_v1beta1" ], - "description": "delete collection of APIService", - "operationId": "deleteApiregistrationV1CollectionAPIService", + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingadmissionpolicybindings/{name}": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch changes to an object of kind MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1beta1MutatingAdmissionPolicyBinding", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1beta1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the MutatingAdmissionPolicyBinding", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/apiextensions.k8s.io/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "description": "get information of a group", + "operationId": "getApiextensionsAPIGroup", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apiextensions" + ] + } + }, + "/apis/apiextensions.k8s.io/v1/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "description": "get available resources", + "operationId": "getApiextensionsV1APIResources", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apiextensions_v1" + ] + } + }, + "/apis/apiextensions.k8s.io/v1/customresourcedefinitions": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of CustomResourceDefinition", + "operationId": "deleteApiextensionsV1CollectionCustomResourceDefinition", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -45743,6 +47598,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -45768,12 +47626,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -45781,8 +47639,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind APIService", - "operationId": "listApiregistrationV1APIService", + "description": "list or watch objects of kind CustomResourceDefinition", + "operationId": "listApiextensionsV1CustomResourceDefinition", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -45808,6 +47666,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -45828,7 +47689,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIServiceList" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinitionList" } }, "401": { @@ -45839,12 +47700,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -45857,15 +47718,15 @@ "consumes": [ "*/*" ], - "description": "create an APIService", - "operationId": "createApiregistrationV1APIService", + "description": "create a CustomResourceDefinition", + "operationId": "createApiextensionsV1CustomResourceDefinition", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, { @@ -45896,19 +47757,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -45919,23 +47780,23 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } } }, - "/apis/apiregistration.k8s.io/v1/apiservices/{name}": { + "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete an APIService", - "operationId": "deleteApiregistrationV1APIService", + "description": "delete a CustomResourceDefinition", + "operationId": "deleteApiextensionsV1CustomResourceDefinition", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -45987,12 +47848,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -46000,8 +47861,8 @@ "consumes": [ "*/*" ], - "description": "read the specified APIService", - "operationId": "readApiregistrationV1APIService", + "description": "read the specified CustomResourceDefinition", + "operationId": "readApiextensionsV1CustomResourceDefinition", "produces": [ "application/json", "application/yaml", @@ -46012,7 +47873,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -46023,18 +47884,18 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, "parameters": [ { - "description": "name of the APIService", + "description": "name of the CustomResourceDefinition", "in": "path", "name": "name", "required": true, @@ -46053,8 +47914,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified APIService", - "operationId": "patchApiregistrationV1APIService", + "description": "partially update the specified CustomResourceDefinition", + "operationId": "patchApiextensionsV1CustomResourceDefinition", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -46090,13 +47951,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -46107,12 +47968,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -46120,15 +47981,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified APIService", - "operationId": "replaceApiregistrationV1APIService", + "description": "replace the specified CustomResourceDefinition", + "operationId": "replaceApiextensionsV1CustomResourceDefinition", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, { @@ -46159,13 +48020,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -46176,23 +48037,23 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } } }, - "/apis/apiregistration.k8s.io/v1/apiservices/{name}/status": { + "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified APIService", - "operationId": "readApiregistrationV1APIServiceStatus", + "description": "read status of the specified CustomResourceDefinition", + "operationId": "readApiextensionsV1CustomResourceDefinitionStatus", "produces": [ "application/json", "application/yaml", @@ -46203,7 +48064,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -46214,18 +48075,18 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, "parameters": [ { - "description": "name of the APIService", + "description": "name of the CustomResourceDefinition", "in": "path", "name": "name", "required": true, @@ -46244,8 +48105,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified APIService", - "operationId": "patchApiregistrationV1APIServiceStatus", + "description": "partially update status of the specified CustomResourceDefinition", + "operationId": "patchApiextensionsV1CustomResourceDefinitionStatus", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -46281,13 +48142,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -46298,12 +48159,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -46311,15 +48172,15 @@ "consumes": [ "*/*" ], - "description": "replace status of the specified APIService", - "operationId": "replaceApiregistrationV1APIServiceStatus", + "description": "replace status of the specified CustomResourceDefinition", + "operationId": "replaceApiextensionsV1CustomResourceDefinitionStatus", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, { @@ -46350,13 +48211,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinition" } }, "401": { @@ -46367,23 +48228,23 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } } }, - "/apis/apiregistration.k8s.io/v1/watch/apiservices": { + "/apis/apiextensions.k8s.io/v1/watch/customresourcedefinitions": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of APIService. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchApiregistrationV1APIServiceList", + "description": "watch individual changes to a list of CustomResourceDefinition. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchApiextensionsV1CustomResourceDefinitionList", "produces": [ "application/json", "application/yaml", @@ -46408,12 +48269,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -46445,6 +48306,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -46453,13 +48317,13 @@ } ] }, - "/apis/apiregistration.k8s.io/v1/watch/apiservices/{name}": { + "/apis/apiextensions.k8s.io/v1/watch/customresourcedefinitions/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind APIService. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchApiregistrationV1APIService", + "description": "watch changes to an object of kind CustomResourceDefinition. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchApiextensionsV1CustomResourceDefinition", "produces": [ "application/json", "application/yaml", @@ -46484,12 +48348,12 @@ "https" ], "tags": [ - "apiregistration_v1" + "apiextensions_v1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { - "group": "apiregistration.k8s.io", - "kind": "APIService", + "group": "apiextensions.k8s.io", + "kind": "CustomResourceDefinition", "version": "v1" } }, @@ -46510,7 +48374,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the APIService", + "description": "name of the CustomResourceDefinition", "in": "path", "name": "name", "required": true, @@ -46529,6 +48393,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -46537,7 +48404,7 @@ } ] }, - "/apis/apps/": { + "/apis/apiregistration.k8s.io/": { "get": { "consumes": [ "application/json", @@ -46545,7 +48412,7 @@ "application/vnd.kubernetes.protobuf" ], "description": "get information of a group", - "operationId": "getAppsAPIGroup", + "operationId": "getApiregistrationAPIGroup", "produces": [ "application/json", "application/yaml", @@ -46566,11 +48433,11 @@ "https" ], "tags": [ - "apps" + "apiregistration" ] } }, - "/apis/apps/v1/": { + "/apis/apiregistration.k8s.io/v1/": { "get": { "consumes": [ "application/json", @@ -46579,7 +48446,7 @@ "application/cbor" ], "description": "get available resources", - "operationId": "getAppsV1APIResources", + "operationId": "getApiregistrationV1APIResources", "produces": [ "application/json", "application/yaml", @@ -46601,245 +48468,17 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ] } }, - "/apis/apps/v1/controllerrevisions": { - "get": { - "consumes": [ - "*/*" - ], - "description": "list or watch objects of kind ControllerRevision", - "operationId": "listAppsV1ControllerRevisionForAllNamespaces", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevisionList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "apps_v1" - ], - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/apps/v1/daemonsets": { - "get": { - "consumes": [ - "*/*" - ], - "description": "list or watch objects of kind DaemonSet", - "operationId": "listAppsV1DaemonSetForAllNamespaces", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSetList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "apps_v1" - ], - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/apps/v1/deployments": { - "get": { - "consumes": [ - "*/*" - ], - "description": "list or watch objects of kind Deployment", - "operationId": "listAppsV1DeploymentForAllNamespaces", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "apps_v1" - ], - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "Deployment", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/apps/v1/namespaces/{namespace}/controllerrevisions": { + "/apis/apiregistration.k8s.io/v1/apiservices": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ControllerRevision", - "operationId": "deleteAppsV1CollectionNamespacedControllerRevision", + "description": "delete collection of APIService", + "operationId": "deleteApiregistrationV1CollectionAPIService", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -46884,6 +48523,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -46909,12 +48551,12 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, @@ -46922,8 +48564,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ControllerRevision", - "operationId": "listAppsV1NamespacedControllerRevision", + "description": "list or watch objects of kind APIService", + "operationId": "listApiregistrationV1APIService", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -46949,6 +48591,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -46969,7 +48614,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevisionList" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIServiceList" } }, "401": { @@ -46980,19 +48625,16 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, "parameters": [ - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -47001,15 +48643,15 @@ "consumes": [ "*/*" ], - "description": "create a ControllerRevision", - "operationId": "createAppsV1NamespacedControllerRevision", + "description": "create an APIService", + "operationId": "createApiregistrationV1APIService", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, { @@ -47040,19 +48682,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47063,23 +48705,23 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}": { + "/apis/apiregistration.k8s.io/v1/apiservices/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ControllerRevision", - "operationId": "deleteAppsV1NamespacedControllerRevision", + "description": "delete an APIService", + "operationId": "deleteApiregistrationV1APIService", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -47131,12 +48773,12 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, @@ -47144,8 +48786,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ControllerRevision", - "operationId": "readAppsV1NamespacedControllerRevision", + "description": "read the specified APIService", + "operationId": "readApiregistrationV1APIService", "produces": [ "application/json", "application/yaml", @@ -47156,7 +48798,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47167,27 +48809,24 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, "parameters": [ { - "description": "name of the ControllerRevision", + "description": "name of the APIService", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -47200,8 +48839,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ControllerRevision", - "operationId": "patchAppsV1NamespacedControllerRevision", + "description": "partially update the specified APIService", + "operationId": "patchApiregistrationV1APIService", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -47237,13 +48876,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47254,12 +48893,12 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, @@ -47267,15 +48906,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ControllerRevision", - "operationId": "replaceAppsV1NamespacedControllerRevision", + "description": "replace the specified APIService", + "operationId": "replaceApiregistrationV1APIService", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, { @@ -47306,13 +48945,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47323,71 +48962,23 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "ControllerRevision", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/daemonsets": { - "delete": { + "/apis/apiregistration.k8s.io/v1/apiservices/{name}/status": { + "get": { "consumes": [ "*/*" ], - "description": "delete collection of DaemonSet", - "operationId": "deleteAppsV1CollectionNamespacedDaemonSet", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - } - ], + "description": "read status of the specified APIService", + "operationId": "readApiregistrationV1APIServiceStatus", "produces": [ "application/json", "application/yaml", @@ -47398,7 +48989,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47409,67 +49000,80 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], - "x-kubernetes-action": "deletecollection", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, - "get": { + "parameters": [ + { + "description": "name of the APIService", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" ], - "description": "list or watch objects of kind DaemonSet", - "operationId": "listAppsV1NamespacedDaemonSet", + "description": "partially update status of the specified APIService", + "operationId": "patchApiregistrationV1APIServiceStatus", "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" + "$ref": "#/parameters/body-78PwaGsr" }, { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true }, { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + "$ref": "#/parameters/fieldManager-7c6nTn1T" }, { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true }, { - "$ref": "#/parameters/watch-XNNPZGbK" + "$ref": "#/parameters/force-tOGGb0Yi" } ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSetList" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47480,36 +49084,28 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - } - ], - "post": { + "put": { "consumes": [ "*/*" ], - "description": "create a DaemonSet", - "operationId": "createAppsV1NamespacedDaemonSet", + "description": "replace status of the specified APIService", + "operationId": "replaceApiregistrationV1APIServiceStatus", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, { @@ -47540,19 +49136,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService" } }, "401": { @@ -47563,64 +49153,37 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/daemonsets/{name}": { - "delete": { + "/apis/apiregistration.k8s.io/v1/watch/apiservices": { + "get": { "consumes": [ "*/*" ], - "description": "delete a DaemonSet", - "operationId": "deleteAppsV1NamespacedDaemonSet", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - } - ], + "description": "watch individual changes to a list of APIService. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchApiregistrationV1APIServiceList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -47631,32 +49194,75 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/apiregistration.k8s.io/v1/watch/apiservices/{name}": { "get": { "consumes": [ "*/*" ], - "description": "read the specified DaemonSet", - "operationId": "readAppsV1NamespacedDaemonSet", + "description": "watch changes to an object of kind APIService. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchApiregistrationV1APIService", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -47667,18 +49273,33 @@ "https" ], "tags": [ - "apps_v1" + "apiregistration_v1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", + "group": "apiregistration.k8s.io", + "kind": "APIService", "version": "v1" } }, "parameters": [ { - "description": "name of the DaemonSet", + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the APIService", "in": "path", "name": "name", "required": true, @@ -47686,64 +49307,47 @@ "uniqueItems": true }, { - "$ref": "#/parameters/namespace-vgWSWtn3" + "$ref": "#/parameters/pretty-tJGM1-ng" }, { - "$ref": "#/parameters/pretty-tJGM1-ng" + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/apps/": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update the specified DaemonSet", - "operationId": "patchAppsV1NamespacedDaemonSet", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" ], + "description": "get information of a group", + "operationId": "getAppsAPIGroup", "produces": [ "application/json", "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/vnd.kubernetes.protobuf" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" } }, "401": { @@ -47754,48 +49358,20 @@ "https" ], "tags": [ - "apps_v1" - ], - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", - "version": "v1" - } - }, - "put": { + "apps" + ] + } + }, + "/apis/apps/v1/": { + "get": { "consumes": [ - "*/*" - ], - "description": "replace the specified DaemonSet", - "operationId": "replaceAppsV1NamespacedDaemonSet", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" ], + "description": "get available resources", + "operationId": "getAppsV1APIResources", "produces": [ "application/json", "application/yaml", @@ -47806,13 +49382,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" } }, "401": { @@ -47824,33 +49394,30 @@ ], "tags": [ "apps_v1" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", - "version": "v1" - } + ] } }, - "/apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status": { + "/apis/apps/v1/controllerrevisions": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified DaemonSet", - "operationId": "readAppsV1NamespacedDaemonSetStatus", + "description": "list or watch objects of kind ControllerRevision", + "operationId": "listAppsV1ControllerRevisionForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevisionList" } }, "401": { @@ -47863,81 +49430,73 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "DaemonSet", + "kind": "ControllerRevision", "version": "v1" } }, "parameters": [ { - "description": "name of the DaemonSet", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/namespace-vgWSWtn3" + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/apps/v1/daemonsets": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update status of the specified DaemonSet", - "operationId": "patchAppsV1NamespacedDaemonSetStatus", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "*/*" ], + "description": "list or watch objects of kind DaemonSet", + "operationId": "listAppsV1DaemonSetForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSetList" } }, "401": { @@ -47950,63 +49509,73 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", "kind": "DaemonSet", "version": "v1" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/apps/v1/deployments": { + "get": { "consumes": [ "*/*" ], - "description": "replace status of the specified DaemonSet", - "operationId": "replaceAppsV1NamespacedDaemonSetStatus", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], + "description": "list or watch objects of kind Deployment", + "operationId": "listAppsV1DeploymentForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentList" } }, "401": { @@ -48019,26 +49588,64 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "DaemonSet", + "kind": "Deployment", "version": "v1" } - } - }, - "/apis/apps/v1/namespaces/{namespace}/deployments": { - "delete": { - "consumes": [ - "*/*" - ], - "description": "delete collection of Deployment", - "operationId": "deleteAppsV1CollectionNamespacedDeployment", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/controllerrevisions": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of ControllerRevision", + "operationId": "deleteAppsV1CollectionNamespacedControllerRevision", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { "$ref": "#/parameters/continue-QfD61s0i" }, { @@ -48078,6 +49685,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -48108,7 +49718,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } }, @@ -48116,8 +49726,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind Deployment", - "operationId": "listAppsV1NamespacedDeployment", + "description": "list or watch objects of kind ControllerRevision", + "operationId": "listAppsV1NamespacedControllerRevision", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -48143,6 +49753,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -48163,7 +49776,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentList" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevisionList" } }, "401": { @@ -48179,7 +49792,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } }, @@ -48195,15 +49808,15 @@ "consumes": [ "*/*" ], - "description": "create a Deployment", - "operationId": "createAppsV1NamespacedDeployment", + "description": "create a ControllerRevision", + "operationId": "createAppsV1NamespacedControllerRevision", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, { @@ -48234,19 +49847,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "401": { @@ -48262,18 +49875,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/deployments/{name}": { + "/apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a Deployment", - "operationId": "deleteAppsV1NamespacedDeployment", + "description": "delete a ControllerRevision", + "operationId": "deleteAppsV1NamespacedControllerRevision", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -48330,7 +49943,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } }, @@ -48338,8 +49951,8 @@ "consumes": [ "*/*" ], - "description": "read the specified Deployment", - "operationId": "readAppsV1NamespacedDeployment", + "description": "read the specified ControllerRevision", + "operationId": "readAppsV1NamespacedControllerRevision", "produces": [ "application/json", "application/yaml", @@ -48350,7 +49963,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "401": { @@ -48366,13 +49979,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } }, "parameters": [ { - "description": "name of the Deployment", + "description": "name of the ControllerRevision", "in": "path", "name": "name", "required": true, @@ -48394,8 +50007,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified Deployment", - "operationId": "patchAppsV1NamespacedDeployment", + "description": "partially update the specified ControllerRevision", + "operationId": "patchAppsV1NamespacedControllerRevision", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -48431,13 +50044,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "401": { @@ -48453,7 +50066,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } }, @@ -48461,15 +50074,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified Deployment", - "operationId": "replaceAppsV1NamespacedDeployment", + "description": "replace the specified ControllerRevision", + "operationId": "replaceAppsV1NamespacedControllerRevision", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, { @@ -48500,13 +50113,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" } }, "401": { @@ -48522,18 +50135,69 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale": { - "get": { + "/apis/apps/v1/namespaces/{namespace}/daemonsets": { + "delete": { "consumes": [ "*/*" ], - "description": "read scale of the specified Deployment", - "operationId": "readAppsV1NamespacedDeploymentScale", + "description": "delete collection of DaemonSet", + "operationId": "deleteAppsV1CollectionNamespacedDaemonSet", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], "produces": [ "application/json", "application/yaml", @@ -48544,7 +50208,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -48557,81 +50221,68 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { - "group": "autoscaling", - "kind": "Scale", + "group": "apps", + "kind": "DaemonSet", "version": "v1" } }, - "parameters": [ - { - "description": "name of the Scale", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - } - ], - "patch": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" + "*/*" ], - "description": "partially update scale of the specified Deployment", - "operationId": "patchAppsV1NamespacedDeploymentScale", + "description": "list or watch objects of kind DaemonSet", + "operationId": "listAppsV1NamespacedDaemonSet", "parameters": [ { - "$ref": "#/parameters/body-78PwaGsr" + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/continue-QfD61s0i" }, { - "$ref": "#/parameters/fieldManager-7c6nTn1T" + "$ref": "#/parameters/fieldSelector-xIcQKXFG" }, { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/labelSelector-5Zw57w4C" }, { - "$ref": "#/parameters/force-tOGGb0Yi" + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSetList" } }, "401": { @@ -48644,26 +50295,34 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { - "group": "autoscaling", - "kind": "Scale", + "group": "apps", + "kind": "DaemonSet", "version": "v1" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { "consumes": [ "*/*" ], - "description": "replace scale of the specified Deployment", - "operationId": "replaceAppsV1NamespacedDeploymentScale", + "description": "create a DaemonSet", + "operationId": "createAppsV1NamespacedDaemonSet", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, { @@ -48694,13 +50353,279 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "DaemonSet", + "version": "v1" + } + } + }, + "/apis/apps/v1/namespaces/{namespace}/daemonsets/{name}": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete a DaemonSet", + "operationId": "deleteAppsV1NamespacedDaemonSet", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "DaemonSet", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "read the specified DaemonSet", + "operationId": "readAppsV1NamespacedDaemonSet", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "DaemonSet", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the DaemonSet", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified DaemonSet", + "operationId": "patchAppsV1NamespacedDaemonSet", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "DaemonSet", + "version": "v1" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace the specified DaemonSet", + "operationId": "replaceAppsV1NamespacedDaemonSet", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "401": { @@ -48715,19 +50640,19 @@ ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "autoscaling", - "kind": "Scale", + "group": "apps", + "kind": "DaemonSet", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/status": { + "/apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified Deployment", - "operationId": "readAppsV1NamespacedDeploymentStatus", + "description": "read status of the specified DaemonSet", + "operationId": "readAppsV1NamespacedDaemonSetStatus", "produces": [ "application/json", "application/yaml", @@ -48738,7 +50663,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "401": { @@ -48754,13 +50679,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "DaemonSet", "version": "v1" } }, "parameters": [ { - "description": "name of the Deployment", + "description": "name of the DaemonSet", "in": "path", "name": "name", "required": true, @@ -48782,8 +50707,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified Deployment", - "operationId": "patchAppsV1NamespacedDeploymentStatus", + "description": "partially update status of the specified DaemonSet", + "operationId": "patchAppsV1NamespacedDaemonSetStatus", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -48819,13 +50744,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "401": { @@ -48841,7 +50766,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "DaemonSet", "version": "v1" } }, @@ -48849,15 +50774,15 @@ "consumes": [ "*/*" ], - "description": "replace status of the specified Deployment", - "operationId": "replaceAppsV1NamespacedDeploymentStatus", + "description": "replace status of the specified DaemonSet", + "operationId": "replaceAppsV1NamespacedDaemonSetStatus", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, { @@ -48888,13 +50813,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet" } }, "401": { @@ -48910,18 +50835,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "DaemonSet", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/replicasets": { + "/apis/apps/v1/namespaces/{namespace}/deployments": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ReplicaSet", - "operationId": "deleteAppsV1CollectionNamespacedReplicaSet", + "description": "delete collection of Deployment", + "operationId": "deleteAppsV1CollectionNamespacedDeployment", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -48966,6 +50891,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -48996,7 +50924,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, @@ -49004,8 +50932,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ReplicaSet", - "operationId": "listAppsV1NamespacedReplicaSet", + "description": "list or watch objects of kind Deployment", + "operationId": "listAppsV1NamespacedDeployment", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -49031,6 +50959,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -49051,7 +50982,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetList" + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentList" } }, "401": { @@ -49067,7 +50998,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, @@ -49083,15 +51014,15 @@ "consumes": [ "*/*" ], - "description": "create a ReplicaSet", - "operationId": "createAppsV1NamespacedReplicaSet", + "description": "create a Deployment", + "operationId": "createAppsV1NamespacedDeployment", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, { @@ -49122,19 +51053,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49150,18 +51081,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}": { + "/apis/apps/v1/namespaces/{namespace}/deployments/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ReplicaSet", - "operationId": "deleteAppsV1NamespacedReplicaSet", + "description": "delete a Deployment", + "operationId": "deleteAppsV1NamespacedDeployment", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -49218,7 +51149,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, @@ -49226,8 +51157,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ReplicaSet", - "operationId": "readAppsV1NamespacedReplicaSet", + "description": "read the specified Deployment", + "operationId": "readAppsV1NamespacedDeployment", "produces": [ "application/json", "application/yaml", @@ -49238,7 +51169,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49254,13 +51185,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, "parameters": [ { - "description": "name of the ReplicaSet", + "description": "name of the Deployment", "in": "path", "name": "name", "required": true, @@ -49282,8 +51213,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ReplicaSet", - "operationId": "patchAppsV1NamespacedReplicaSet", + "description": "partially update the specified Deployment", + "operationId": "patchAppsV1NamespacedDeployment", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -49319,13 +51250,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49341,7 +51272,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, @@ -49349,15 +51280,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ReplicaSet", - "operationId": "replaceAppsV1NamespacedReplicaSet", + "description": "replace the specified Deployment", + "operationId": "replaceAppsV1NamespacedDeployment", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, { @@ -49388,13 +51319,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49410,18 +51341,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale": { + "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale": { "get": { "consumes": [ "*/*" ], - "description": "read scale of the specified ReplicaSet", - "operationId": "readAppsV1NamespacedReplicaSetScale", + "description": "read scale of the specified Deployment", + "operationId": "readAppsV1NamespacedDeploymentScale", "produces": [ "application/json", "application/yaml", @@ -49476,8 +51407,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update scale of the specified ReplicaSet", - "operationId": "patchAppsV1NamespacedReplicaSetScale", + "description": "partially update scale of the specified Deployment", + "operationId": "patchAppsV1NamespacedDeploymentScale", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -49543,8 +51474,8 @@ "consumes": [ "*/*" ], - "description": "replace scale of the specified ReplicaSet", - "operationId": "replaceAppsV1NamespacedReplicaSetScale", + "description": "replace scale of the specified Deployment", + "operationId": "replaceAppsV1NamespacedDeploymentScale", "parameters": [ { "in": "body", @@ -49609,13 +51540,13 @@ } } }, - "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status": { + "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified ReplicaSet", - "operationId": "readAppsV1NamespacedReplicaSetStatus", + "description": "read status of the specified Deployment", + "operationId": "readAppsV1NamespacedDeploymentStatus", "produces": [ "application/json", "application/yaml", @@ -49626,7 +51557,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49642,13 +51573,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, "parameters": [ { - "description": "name of the ReplicaSet", + "description": "name of the Deployment", "in": "path", "name": "name", "required": true, @@ -49670,8 +51601,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified ReplicaSet", - "operationId": "patchAppsV1NamespacedReplicaSetStatus", + "description": "partially update status of the specified Deployment", + "operationId": "patchAppsV1NamespacedDeploymentStatus", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -49707,13 +51638,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49729,7 +51660,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, @@ -49737,15 +51668,15 @@ "consumes": [ "*/*" ], - "description": "replace status of the specified ReplicaSet", - "operationId": "replaceAppsV1NamespacedReplicaSetStatus", + "description": "replace status of the specified Deployment", + "operationId": "replaceAppsV1NamespacedDeploymentStatus", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, { @@ -49776,13 +51707,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" } }, "401": { @@ -49798,18 +51729,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/statefulsets": { + "/apis/apps/v1/namespaces/{namespace}/replicasets": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of StatefulSet", - "operationId": "deleteAppsV1CollectionNamespacedStatefulSet", + "description": "delete collection of ReplicaSet", + "operationId": "deleteAppsV1CollectionNamespacedReplicaSet", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -49854,6 +51785,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -49884,7 +51818,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, @@ -49892,8 +51826,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind StatefulSet", - "operationId": "listAppsV1NamespacedStatefulSet", + "description": "list or watch objects of kind ReplicaSet", + "operationId": "listAppsV1NamespacedReplicaSet", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -49919,6 +51853,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -49939,7 +51876,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetList" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetList" } }, "401": { @@ -49955,7 +51892,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, @@ -49971,15 +51908,15 @@ "consumes": [ "*/*" ], - "description": "create a StatefulSet", - "operationId": "createAppsV1NamespacedStatefulSet", + "description": "create a ReplicaSet", + "operationId": "createAppsV1NamespacedReplicaSet", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, { @@ -50010,19 +51947,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50038,18 +51975,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}": { + "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a StatefulSet", - "operationId": "deleteAppsV1NamespacedStatefulSet", + "description": "delete a ReplicaSet", + "operationId": "deleteAppsV1NamespacedReplicaSet", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -50106,7 +52043,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, @@ -50114,8 +52051,8 @@ "consumes": [ "*/*" ], - "description": "read the specified StatefulSet", - "operationId": "readAppsV1NamespacedStatefulSet", + "description": "read the specified ReplicaSet", + "operationId": "readAppsV1NamespacedReplicaSet", "produces": [ "application/json", "application/yaml", @@ -50126,7 +52063,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50142,13 +52079,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, "parameters": [ { - "description": "name of the StatefulSet", + "description": "name of the ReplicaSet", "in": "path", "name": "name", "required": true, @@ -50170,8 +52107,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified StatefulSet", - "operationId": "patchAppsV1NamespacedStatefulSet", + "description": "partially update the specified ReplicaSet", + "operationId": "patchAppsV1NamespacedReplicaSet", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -50207,13 +52144,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50229,7 +52166,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, @@ -50237,15 +52174,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified StatefulSet", - "operationId": "replaceAppsV1NamespacedStatefulSet", + "description": "replace the specified ReplicaSet", + "operationId": "replaceAppsV1NamespacedReplicaSet", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, { @@ -50276,13 +52213,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50298,18 +52235,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } } }, - "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale": { + "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale": { "get": { "consumes": [ "*/*" ], - "description": "read scale of the specified StatefulSet", - "operationId": "readAppsV1NamespacedStatefulSetScale", + "description": "read scale of the specified ReplicaSet", + "operationId": "readAppsV1NamespacedReplicaSetScale", "produces": [ "application/json", "application/yaml", @@ -50364,8 +52301,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update scale of the specified StatefulSet", - "operationId": "patchAppsV1NamespacedStatefulSetScale", + "description": "partially update scale of the specified ReplicaSet", + "operationId": "patchAppsV1NamespacedReplicaSetScale", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -50431,8 +52368,8 @@ "consumes": [ "*/*" ], - "description": "replace scale of the specified StatefulSet", - "operationId": "replaceAppsV1NamespacedStatefulSetScale", + "description": "replace scale of the specified ReplicaSet", + "operationId": "replaceAppsV1NamespacedReplicaSetScale", "parameters": [ { "in": "body", @@ -50497,13 +52434,13 @@ } } }, - "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status": { + "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified StatefulSet", - "operationId": "readAppsV1NamespacedStatefulSetStatus", + "description": "read status of the specified ReplicaSet", + "operationId": "readAppsV1NamespacedReplicaSetStatus", "produces": [ "application/json", "application/yaml", @@ -50514,7 +52451,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50530,13 +52467,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, "parameters": [ { - "description": "name of the StatefulSet", + "description": "name of the ReplicaSet", "in": "path", "name": "name", "required": true, @@ -50558,8 +52495,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified StatefulSet", - "operationId": "patchAppsV1NamespacedStatefulSetStatus", + "description": "partially update status of the specified ReplicaSet", + "operationId": "patchAppsV1NamespacedReplicaSetStatus", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -50595,13 +52532,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50617,7 +52554,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } }, @@ -50625,15 +52562,15 @@ "consumes": [ "*/*" ], - "description": "replace status of the specified StatefulSet", - "operationId": "replaceAppsV1NamespacedStatefulSetStatus", + "description": "replace status of the specified ReplicaSet", + "operationId": "replaceAppsV1NamespacedReplicaSetStatus", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, { @@ -50664,13 +52601,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" } }, "401": { @@ -50686,32 +52623,80 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ReplicaSet", "version": "v1" } } }, - "/apis/apps/v1/replicasets": { - "get": { + "/apis/apps/v1/namespaces/{namespace}/statefulsets": { + "delete": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ReplicaSet", - "operationId": "listAppsV1ReplicaSetForAllNamespaces", + "description": "delete collection of StatefulSet", + "operationId": "deleteAppsV1CollectionNamespacedStatefulSet", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -50724,56 +52709,54 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "StatefulSet", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/apps/v1/statefulsets": { "get": { "consumes": [ "*/*" ], "description": "list or watch objects of kind StatefulSet", - "operationId": "listAppsV1StatefulSetForAllNamespaces", + "operationId": "listAppsV1NamespacedStatefulSet", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], "produces": [ "application/json", "application/yaml", @@ -50809,61 +52792,68 @@ }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" + "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/apps/v1/watch/controllerrevisions": { - "get": { + ], + "post": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ControllerRevision. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1ControllerRevisionListForAllNamespaces", + "description": "create a StatefulSet", + "operationId": "createAppsV1NamespacedStatefulSet", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" } }, "401": { @@ -50876,70 +52866,62 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ControllerRevision", + "kind": "StatefulSet", "version": "v1" } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] + } }, - "/apis/apps/v1/watch/daemonsets": { - "get": { + "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}": { + "delete": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of DaemonSet. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1DaemonSetListForAllNamespaces", + "description": "delete a StatefulSet", + "operationId": "deleteAppsV1NamespacedStatefulSet", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -50952,70 +52934,30 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "DaemonSet", + "kind": "StatefulSet", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/apps/v1/watch/deployments": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of Deployment. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1DeploymentListForAllNamespaces", + "description": "read the specified StatefulSet", + "operationId": "readAppsV1NamespacedStatefulSet", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" } }, "401": { @@ -51028,70 +52970,81 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "StatefulSet", "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" + "description": "name of the StatefulSet", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { - "$ref": "#/parameters/limit-1NfNmdNH" + "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified StatefulSet", + "operationId": "patchAppsV1NamespacedStatefulSet", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch individual changes to a list of ControllerRevision. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1NamespacedControllerRevisionList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" } }, "401": { @@ -51104,73 +53057,63 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ControllerRevision", + "kind": "StatefulSet", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions/{name}": { - "get": { + "put": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ControllerRevision. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAppsV1NamespacedControllerRevision", + "description": "replace the specified StatefulSet", + "operationId": "replaceAppsV1NamespacedStatefulSet", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" } }, "401": { @@ -51183,81 +53126,32 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ControllerRevision", + "kind": "StatefulSet", "version": "v1" } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ControllerRevision", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] + } }, - "/apis/apps/v1/watch/namespaces/{namespace}/daemonsets": { + "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of DaemonSet. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1NamespacedDaemonSetList", + "description": "read scale of the specified StatefulSet", + "operationId": "readAppsV1NamespacedStatefulSetScale", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" } }, "401": { @@ -51270,59 +53164,371 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "apps", - "kind": "DaemonSet", + "group": "autoscaling", + "kind": "Scale", "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" + "description": "name of the Scale", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/apps/v1/watch/namespaces/{namespace}/daemonsets/{name}": { + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update scale of the specified StatefulSet", + "operationId": "patchAppsV1NamespacedStatefulSetScale", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace scale of the specified StatefulSet", + "operationId": "replaceAppsV1NamespacedStatefulSetScale", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + } + }, + "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind DaemonSet. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAppsV1NamespacedDaemonSet", + "description": "read status of the specified StatefulSet", + "operationId": "readAppsV1NamespacedStatefulSetStatus", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the StatefulSet", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified StatefulSet", + "operationId": "patchAppsV1NamespacedStatefulSetStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace status of the specified StatefulSet", + "operationId": "replaceAppsV1NamespacedStatefulSetStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + } + }, + "/apis/apps/v1/replicasets": { + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind ReplicaSet", + "operationId": "listAppsV1ReplicaSetForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -51336,7 +53542,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetList" } }, "401": { @@ -51349,10 +53555,10 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "DaemonSet", + "kind": "ReplicaSet", "version": "v1" } }, @@ -51372,17 +53578,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "description": "name of the DaemonSet", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -51395,6 +53590,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51403,13 +53601,13 @@ } ] }, - "/apis/apps/v1/watch/namespaces/{namespace}/deployments": { + "/apis/apps/v1/statefulsets": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of Deployment. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1NamespacedDeploymentList", + "description": "list or watch objects of kind StatefulSet", + "operationId": "listAppsV1StatefulSetForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -51423,7 +53621,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetList" } }, "401": { @@ -51436,10 +53634,10 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "StatefulSet", "version": "v1" } }, @@ -51459,9 +53657,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -51474,6 +53669,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51482,13 +53680,13 @@ } ] }, - "/apis/apps/v1/watch/namespaces/{namespace}/deployments/{name}": { + "/apis/apps/v1/watch/controllerrevisions": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind Deployment. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAppsV1NamespacedDeployment", + "description": "watch individual changes to a list of ControllerRevision. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1ControllerRevisionListForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -51515,10 +53713,10 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "Deployment", + "kind": "ControllerRevision", "version": "v1" } }, @@ -51538,17 +53736,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "description": "name of the Deployment", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -51561,6 +53748,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51569,13 +53759,13 @@ } ] }, - "/apis/apps/v1/watch/namespaces/{namespace}/replicasets": { + "/apis/apps/v1/watch/daemonsets": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ReplicaSet. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1NamespacedReplicaSetList", + "description": "watch individual changes to a list of DaemonSet. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1DaemonSetListForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -51605,7 +53795,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "DaemonSet", "version": "v1" } }, @@ -51625,9 +53815,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -51640,6 +53827,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51648,13 +53838,13 @@ } ] }, - "/apis/apps/v1/watch/namespaces/{namespace}/replicasets/{name}": { + "/apis/apps/v1/watch/deployments": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ReplicaSet. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAppsV1NamespacedReplicaSet", + "description": "watch individual changes to a list of Deployment. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1DeploymentListForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -51681,10 +53871,10 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "Deployment", "version": "v1" } }, @@ -51704,17 +53894,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "description": "name of the ReplicaSet", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -51727,6 +53906,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51735,13 +53917,13 @@ } ] }, - "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets": { + "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of StatefulSet. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1NamespacedStatefulSetList", + "description": "watch individual changes to a list of ControllerRevision. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1NamespacedControllerRevisionList", "produces": [ "application/json", "application/yaml", @@ -51771,7 +53953,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ControllerRevision", "version": "v1" } }, @@ -51806,6 +53988,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51814,13 +53999,13 @@ } ] }, - "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets/{name}": { + "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind StatefulSet. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchAppsV1NamespacedStatefulSet", + "description": "watch changes to an object of kind ControllerRevision. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAppsV1NamespacedControllerRevision", "produces": [ "application/json", "application/yaml", @@ -51850,7 +54035,7 @@ "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "ControllerRevision", "version": "v1" } }, @@ -51871,7 +54056,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the StatefulSet", + "description": "name of the ControllerRevision", "in": "path", "name": "name", "required": true, @@ -51893,6 +54078,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51901,13 +54089,13 @@ } ] }, - "/apis/apps/v1/watch/replicasets": { + "/apis/apps/v1/watch/namespaces/{namespace}/daemonsets": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ReplicaSet. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1ReplicaSetListForAllNamespaces", + "description": "watch individual changes to a list of DaemonSet. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1NamespacedDaemonSetList", "produces": [ "application/json", "application/yaml", @@ -51937,7 +54125,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "ReplicaSet", + "kind": "DaemonSet", "version": "v1" } }, @@ -51957,6 +54145,9 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -51969,6 +54160,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -51977,13 +54171,13 @@ } ] }, - "/apis/apps/v1/watch/statefulsets": { + "/apis/apps/v1/watch/namespaces/{namespace}/daemonsets/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of StatefulSet. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchAppsV1StatefulSetListForAllNamespaces", + "description": "watch changes to an object of kind DaemonSet. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAppsV1NamespacedDaemonSet", "produces": [ "application/json", "application/yaml", @@ -52010,10 +54204,10 @@ "tags": [ "apps_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "apps", - "kind": "StatefulSet", + "kind": "DaemonSet", "version": "v1" } }, @@ -52033,6 +54227,17 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, + { + "description": "name of the DaemonSet", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -52045,6 +54250,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -52053,25 +54261,27 @@ } ] }, - "/apis/authentication.k8s.io/": { + "/apis/apps/v1/watch/namespaces/{namespace}/deployments": { "get": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" + "*/*" ], - "description": "get information of a group", - "operationId": "getAuthenticationAPIGroup", + "description": "watch individual changes to a list of Deployment. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1NamespacedDeploymentList", "produces": [ "application/json", "application/yaml", - "application/vnd.kubernetes.protobuf" + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52082,31 +54292,78 @@ "https" ], "tags": [ - "authentication" - ] - } + "apps_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/authentication.k8s.io/v1/": { + "/apis/apps/v1/watch/namespaces/{namespace}/deployments/{name}": { "get": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "*/*" ], - "description": "get available resources", - "operationId": "getAuthenticationV1APIResources", + "description": "watch changes to an object of kind Deployment. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAppsV1NamespacedDeployment", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52117,72 +54374,86 @@ "https" ], "tags": [ - "authentication_v1" - ] - } - }, - "/apis/authentication.k8s.io/v1/selfsubjectreviews": { + "apps_v1" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, "parameters": [ { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" + "$ref": "#/parameters/continue-QfD61s0i" }, { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the Deployment", + "in": "path", + "name": "name", + "required": true, "type": "string", "uniqueItems": true }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "post": { + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/replicasets": { + "get": { "consumes": [ "*/*" ], - "description": "create a SelfSubjectReview", - "operationId": "createAuthenticationV1SelfSubjectReview", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" - } - } - ], + "description": "watch individual changes to a list of ReplicaSet. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1NamespacedReplicaSetList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52193,78 +54464,78 @@ "https" ], "tags": [ - "authentication_v1" + "apps_v1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { - "group": "authentication.k8s.io", - "kind": "SelfSubjectReview", + "group": "apps", + "kind": "ReplicaSet", "version": "v1" } - } - }, - "/apis/authentication.k8s.io/v1/tokenreviews": { + }, "parameters": [ { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" + "$ref": "#/parameters/continue-QfD61s0i" }, { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "post": { + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/replicasets/{name}": { + "get": { "consumes": [ "*/*" ], - "description": "create a TokenReview", - "operationId": "createAuthenticationV1TokenReview", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" - } - } - ], + "description": "watch changes to an object of kind ReplicaSet. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAppsV1NamespacedReplicaSet", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52275,35 +54546,86 @@ "https" ], "tags": [ - "authentication_v1" + "apps_v1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { - "group": "authentication.k8s.io", - "kind": "TokenReview", + "group": "apps", + "kind": "ReplicaSet", "version": "v1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ReplicaSet", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/authorization.k8s.io/": { + "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets": { "get": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" + "*/*" ], - "description": "get information of a group", - "operationId": "getAuthorizationAPIGroup", + "description": "watch individual changes to a list of StatefulSet. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1NamespacedStatefulSetList", "produces": [ "application/json", "application/yaml", - "application/vnd.kubernetes.protobuf" + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52314,31 +54636,78 @@ "https" ], "tags": [ - "authorization" - ] - } + "apps_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/authorization.k8s.io/v1/": { + "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets/{name}": { "get": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "*/*" ], - "description": "get available resources", - "operationId": "getAuthorizationV1APIResources", + "description": "watch changes to an object of kind StatefulSet. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAppsV1NamespacedStatefulSet", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52349,26 +54718,36 @@ "https" ], "tags": [ - "authorization_v1" - ] - } - }, - "/apis/authorization.k8s.io/v1/namespaces/{namespace}/localsubjectaccessreviews": { + "apps_v1" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, "parameters": [ { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" + "$ref": "#/parameters/continue-QfD61s0i" }, { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the StatefulSet", + "in": "path", + "name": "name", + "required": true, "type": "string", "uniqueItems": true }, @@ -52377,47 +54756,48 @@ }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "post": { + ] + }, + "/apis/apps/v1/watch/replicasets": { + "get": { "consumes": [ "*/*" ], - "description": "create a LocalSubjectAccessReview", - "operationId": "createAuthorizationV1NamespacedLocalSubjectAccessReview", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" - } - } - ], + "description": "watch individual changes to a list of ReplicaSet. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1ReplicaSetListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -52428,21 +54808,523 @@ "https" ], "tags": [ - "authorization_v1" + "apps_v1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { - "group": "authorization.k8s.io", - "kind": "LocalSubjectAccessReview", + "group": "apps", + "kind": "ReplicaSet", "version": "v1" } - } - }, - "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews": { + }, "parameters": [ { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/apps/v1/watch/statefulsets": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch individual changes to a list of StatefulSet. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAppsV1StatefulSetListForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/authentication.k8s.io/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "description": "get information of a group", + "operationId": "getAuthenticationAPIGroup", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authentication" + ] + } + }, + "/apis/authentication.k8s.io/v1/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "description": "get available resources", + "operationId": "getAuthenticationV1APIResources", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authentication_v1" + ] + } + }, + "/apis/authentication.k8s.io/v1/selfsubjectreviews": { + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a SelfSubjectReview", + "operationId": "createAuthenticationV1SelfSubjectReview", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" + } + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.SelfSubjectReview" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authentication_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "authentication.k8s.io", + "kind": "SelfSubjectReview", + "version": "v1" + } + } + }, + "/apis/authentication.k8s.io/v1/tokenreviews": { + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a TokenReview", + "operationId": "createAuthenticationV1TokenReview", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" + } + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.authentication.v1.TokenReview" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authentication_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "authentication.k8s.io", + "kind": "TokenReview", + "version": "v1" + } + } + }, + "/apis/authorization.k8s.io/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "description": "get information of a group", + "operationId": "getAuthorizationAPIGroup", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authorization" + ] + } + }, + "/apis/authorization.k8s.io/v1/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "description": "get available resources", + "operationId": "getAuthorizationV1APIResources", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authorization_v1" + ] + } + }, + "/apis/authorization.k8s.io/v1/namespaces/{namespace}/localsubjectaccessreviews": { + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a LocalSubjectAccessReview", + "operationId": "createAuthorizationV1NamespacedLocalSubjectAccessReview", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" + } + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.authorization.v1.LocalSubjectAccessReview" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "authorization_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "authorization.k8s.io", + "kind": "LocalSubjectAccessReview", + "version": "v1" + } + } + }, + "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews": { + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", "name": "dryRun", "type": "string", "uniqueItems": true @@ -52820,6 +55702,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -52879,6 +55764,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -52944,6 +55832,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -53590,6 +56481,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -53669,6 +56563,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -53756,6 +56653,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -53867,6 +56767,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -53926,6 +56829,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -53991,6 +56897,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -54637,6 +57546,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -54716,6 +57628,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -54803,6 +57718,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -54947,6 +57865,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -55023,6 +57944,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -55082,6 +58006,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -55147,6 +58074,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -55776,6 +58706,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -55841,6 +58774,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -56487,6 +59423,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -56563,6 +59502,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -56642,6 +59584,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -56729,6 +59674,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -56808,6 +59756,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -56895,6 +59846,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -57022,6 +59976,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -57087,6 +60044,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -57915,6 +60875,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -57999,6 +60962,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -58093,6 +61059,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -58158,6 +61127,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -58604,6 +61576,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -58688,6 +61663,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -58782,6 +61760,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -58847,6 +61828,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -59276,6 +62260,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -59341,6 +62328,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -59987,6 +62977,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60063,6 +63056,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60147,6 +63143,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60226,6 +63225,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60313,6 +63315,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60389,6 +63394,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60533,6 +63541,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -60592,6 +63603,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -60657,6 +63671,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -61109,6 +64126,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -61188,6 +64208,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -61275,6 +64298,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -61386,6 +64412,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -61445,6 +64474,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -61510,6 +64542,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -61962,6 +64997,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62041,6 +65079,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62128,6 +65169,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62239,6 +65283,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62298,6 +65345,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -62363,6 +65413,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62815,6 +65868,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62894,6 +65950,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -62981,6 +66040,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -63125,6 +66187,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -63184,6 +66249,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -63249,6 +66317,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -63701,6 +66772,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -63780,6 +66854,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -63867,6 +66944,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -64011,6 +67091,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -64070,6 +67153,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -64135,6 +67221,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -64587,6 +67676,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -64666,6 +67758,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -64753,6 +67848,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -64880,6 +67978,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -64945,6 +68046,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -65565,6 +68669,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -65630,6 +68737,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -66267,6 +69377,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -66351,6 +69464,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -66427,6 +69543,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -66511,6 +69630,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -66638,6 +69760,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -66703,6 +69828,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -67340,6 +70468,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -67424,6 +70555,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -67551,6 +70685,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -67616,6 +70753,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -68062,6 +71202,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -68121,6 +71264,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -68186,6 +71332,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -68615,6 +71764,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -68680,6 +71832,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -69309,6 +72464,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -69374,6 +72532,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -69826,6 +72987,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -69885,6 +73049,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -69950,6 +73117,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -70587,6 +73757,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -70671,6 +73844,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -70747,6 +73923,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -70823,6 +74002,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -70907,6 +74089,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -70986,6 +74171,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71073,6 +74261,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71152,6 +74343,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71239,6 +74433,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71315,6 +74512,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71391,6 +74591,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71475,6 +74678,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -71569,6 +74775,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -71634,6 +74843,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -72063,6 +75275,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -72128,6 +75343,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -72765,6 +75983,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -72849,6 +76070,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -72925,6 +76149,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -73009,6 +76236,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -73136,6 +76366,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -73201,6 +76434,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -73647,6 +76883,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -73731,6 +76970,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -73858,6 +77100,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -73923,6 +77168,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -74569,6 +77817,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -74648,6 +77899,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -74735,6 +77989,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -74811,6 +78068,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -74938,6 +78198,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -75003,6 +78266,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -75432,6 +78698,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -75497,6 +78766,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -75926,6 +79198,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -75991,6 +79266,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -76426,6 +79704,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -76491,6 +79772,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -76943,6 +80227,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77019,6 +80306,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77095,6 +80385,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77179,6 +80472,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77255,6 +80551,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77339,35 +80638,2081 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/rolebindings": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch individual changes to a list of RoleBinding. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchRbacAuthorizationV1NamespacedRoleBindingList", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "rbacAuthorization_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "rbac.authorization.k8s.io", + "kind": "RoleBinding", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/rolebindings/{name}": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch changes to an object of kind RoleBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchRbacAuthorizationV1NamespacedRoleBinding", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "rbacAuthorization_v1" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "rbac.authorization.k8s.io", + "kind": "RoleBinding", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the RoleBinding", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/roles": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch individual changes to a list of Role. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchRbacAuthorizationV1NamespacedRoleList", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "rbacAuthorization_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "rbac.authorization.k8s.io", + "kind": "Role", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/roles/{name}": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch changes to an object of kind Role. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchRbacAuthorizationV1NamespacedRole", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "rbacAuthorization_v1" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "rbac.authorization.k8s.io", + "kind": "Role", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the Role", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/rbac.authorization.k8s.io/v1/watch/rolebindings": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch individual changes to a list of RoleBinding. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchRbacAuthorizationV1RoleBindingListForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "rbacAuthorization_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "rbac.authorization.k8s.io", + "kind": "RoleBinding", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/rbac.authorization.k8s.io/v1/watch/roles": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch individual changes to a list of Role. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchRbacAuthorizationV1RoleListForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "rbacAuthorization_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "rbac.authorization.k8s.io", + "kind": "Role", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "description": "get information of a group", + "operationId": "getResourceAPIGroup", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource" + ] + } + }, + "/apis/resource.k8s.io/v1/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "description": "get available resources", + "operationId": "getResourceV1APIResources", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ] + } + }, + "/apis/resource.k8s.io/v1/deviceclasses": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of DeviceClass", + "operationId": "deleteResourceV1CollectionDeviceClass", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind DeviceClass", + "operationId": "listResourceV1DeviceClass", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClassList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a DeviceClass", + "operationId": "createResourceV1DeviceClass", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + } + }, + "/apis/resource.k8s.io/v1/deviceclasses/{name}": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete a DeviceClass", + "operationId": "deleteResourceV1DeviceClass", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "read the specified DeviceClass", + "operationId": "readResourceV1DeviceClass", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the DeviceClass", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified DeviceClass", + "operationId": "patchResourceV1DeviceClass", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace the specified DeviceClass", + "operationId": "replaceResourceV1DeviceClass", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1" + } + } + }, + "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaims": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of ResourceClaim", + "operationId": "deleteResourceV1CollectionNamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind ResourceClaim", + "operationId": "listResourceV1NamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a ResourceClaim", + "operationId": "createResourceV1NamespacedResourceClaim", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + } + }, + "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaims/{name}": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete a ResourceClaim", + "operationId": "deleteResourceV1NamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "read the specified ResourceClaim", + "operationId": "readResourceV1NamespacedResourceClaim", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the ResourceClaim", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified ResourceClaim", + "operationId": "patchResourceV1NamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace the specified ResourceClaim", + "operationId": "replaceResourceV1NamespacedResourceClaim", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + } + }, + "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaims/{name}/status": { + "get": { + "consumes": [ + "*/*" + ], + "description": "read status of the specified ResourceClaim", + "operationId": "readResourceV1NamespacedResourceClaimStatus", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the ResourceClaim", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified ResourceClaim", + "operationId": "patchResourceV1NamespacedResourceClaimStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace status of the specified ResourceClaim", + "operationId": "replaceResourceV1NamespacedResourceClaimStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + } + }, + "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaimtemplates": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of ResourceClaimTemplate", + "operationId": "deleteResourceV1CollectionNamespacedResourceClaimTemplate", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind ResourceClaimTemplate", + "operationId": "listResourceV1NamespacedResourceClaimTemplate", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplateList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a ResourceClaimTemplate", + "operationId": "createResourceV1NamespacedResourceClaimTemplate", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", + "version": "v1" } - ] + } }, - "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/rolebindings": { - "get": { + "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaimtemplates/{name}": { + "delete": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of RoleBinding. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchRbacAuthorizationV1NamespacedRoleBindingList", + "description": "delete a ResourceClaimTemplate", + "operationId": "deleteResourceV1NamespacedResourceClaimTemplate", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" } }, "401": { @@ -77378,75 +82723,32 @@ "https" ], "tags": [ - "rbacAuthorization_v1" + "resource_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { - "group": "rbac.authorization.k8s.io", - "kind": "RoleBinding", + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/rolebindings/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind RoleBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchRbacAuthorizationV1NamespacedRoleBinding", + "description": "read the specified ResourceClaimTemplate", + "operationId": "readResourceV1NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" } }, "401": { @@ -77457,33 +82759,18 @@ "https" ], "tags": [ - "rbacAuthorization_v1" + "resource_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "rbac.authorization.k8s.io", - "kind": "RoleBinding", + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the RoleBinding", + "description": "name of the ResourceClaimTemplate", "in": "path", "name": "name", "required": true, @@ -77495,45 +82782,60 @@ }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/roles": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified ResourceClaimTemplate", + "operationId": "patchResourceV1NamespacedResourceClaimTemplate", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch individual changes to a list of Role. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchRbacAuthorizationV1NamespacedRoleList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" } }, "401": { @@ -77544,75 +82846,65 @@ "https" ], "tags": [ - "rbacAuthorization_v1" + "resource_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "rbac.authorization.k8s.io", - "kind": "Role", + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/rbac.authorization.k8s.io/v1/watch/namespaces/{namespace}/roles/{name}": { - "get": { + "put": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind Role. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchRbacAuthorizationV1NamespacedRole", + "description": "replace the specified ResourceClaimTemplate", + "operationId": "replaceResourceV1NamespacedResourceClaimTemplate", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" } }, "401": { @@ -77623,69 +82915,23 @@ "https" ], "tags": [ - "rbacAuthorization_v1" + "resource_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "rbac.authorization.k8s.io", - "kind": "Role", + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", "version": "v1" } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the Role", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] + } }, - "/apis/rbac.authorization.k8s.io/v1/watch/rolebindings": { + "/apis/resource.k8s.io/v1/resourceclaims": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of RoleBinding. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchRbacAuthorizationV1RoleBindingListForAllNamespaces", + "description": "list or watch objects of kind ResourceClaim", + "operationId": "listResourceV1ResourceClaimForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -77699,7 +82945,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimList" } }, "401": { @@ -77710,12 +82956,12 @@ "https" ], "tags": [ - "rbacAuthorization_v1" + "resource_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { - "group": "rbac.authorization.k8s.io", - "kind": "RoleBinding", + "group": "resource.k8s.io", + "kind": "ResourceClaim", "version": "v1" } }, @@ -77747,6 +82993,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77755,13 +83004,13 @@ } ] }, - "/apis/rbac.authorization.k8s.io/v1/watch/roles": { + "/apis/resource.k8s.io/v1/resourceclaimtemplates": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of Role. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchRbacAuthorizationV1RoleListForAllNamespaces", + "description": "list or watch objects of kind ResourceClaimTemplate", + "operationId": "listResourceV1ResourceClaimTemplateForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -77775,7 +83024,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplateList" } }, "401": { @@ -77786,12 +83035,12 @@ "https" ], "tags": [ - "rbacAuthorization_v1" + "resource_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { - "group": "rbac.authorization.k8s.io", - "kind": "Role", + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", "version": "v1" } }, @@ -77823,6 +83072,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -77831,81 +83083,13 @@ } ] }, - "/apis/resource.k8s.io/": { - "get": { - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "description": "get information of a group", - "operationId": "getResourceAPIGroup", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource" - ] - } - }, - "/apis/resource.k8s.io/v1/": { - "get": { - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "description": "get available resources", - "operationId": "getResourceV1APIResources", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1" - ] - } - }, - "/apis/resource.k8s.io/v1/deviceclasses": { + "/apis/resource.k8s.io/v1/resourceslices": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of DeviceClass", - "operationId": "deleteResourceV1CollectionDeviceClass", + "description": "delete collection of ResourceSlice", + "operationId": "deleteResourceV1CollectionResourceSlice", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -77950,6 +83134,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -77980,7 +83167,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } }, @@ -77988,8 +83175,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind DeviceClass", - "operationId": "listResourceV1DeviceClass", + "description": "list or watch objects of kind ResourceSlice", + "operationId": "listResourceV1ResourceSlice", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -78015,6 +83202,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -78035,7 +83225,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClassList" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSliceList" } }, "401": { @@ -78051,7 +83241,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } }, @@ -78064,15 +83254,15 @@ "consumes": [ "*/*" ], - "description": "create a DeviceClass", - "operationId": "createResourceV1DeviceClass", + "description": "create a ResourceSlice", + "operationId": "createResourceV1ResourceSlice", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, { @@ -78103,19 +83293,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "401": { @@ -78131,18 +83321,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } } }, - "/apis/resource.k8s.io/v1/deviceclasses/{name}": { + "/apis/resource.k8s.io/v1/resourceslices/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a DeviceClass", - "operationId": "deleteResourceV1DeviceClass", + "description": "delete a ResourceSlice", + "operationId": "deleteResourceV1ResourceSlice", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -78177,13 +83367,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "401": { @@ -78199,7 +83389,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } }, @@ -78207,8 +83397,8 @@ "consumes": [ "*/*" ], - "description": "read the specified DeviceClass", - "operationId": "readResourceV1DeviceClass", + "description": "read the specified ResourceSlice", + "operationId": "readResourceV1ResourceSlice", "produces": [ "application/json", "application/yaml", @@ -78219,7 +83409,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "401": { @@ -78235,13 +83425,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } }, "parameters": [ { - "description": "name of the DeviceClass", + "description": "name of the ResourceSlice", "in": "path", "name": "name", "required": true, @@ -78260,8 +83450,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified DeviceClass", - "operationId": "patchResourceV1DeviceClass", + "description": "partially update the specified ResourceSlice", + "operationId": "patchResourceV1ResourceSlice", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -78297,13 +83487,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "401": { @@ -78319,7 +83509,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } }, @@ -78327,15 +83517,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified DeviceClass", - "operationId": "replaceResourceV1DeviceClass", + "description": "replace the specified ResourceSlice", + "operationId": "replaceResourceV1ResourceSlice", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, { @@ -78366,13 +83556,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" } }, "401": { @@ -78388,77 +83578,32 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceSlice", "version": "v1" } } }, - "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaims": { - "delete": { + "/apis/resource.k8s.io/v1/watch/deviceclasses": { + "get": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceClaim", - "operationId": "deleteResourceV1CollectionNamespacedResourceClaim", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - } - ], + "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1DeviceClassList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78471,51 +83616,59 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "deletecollection", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "DeviceClass", "version": "v1" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1/watch/deviceclasses/{name}": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaim", - "operationId": "listResourceV1NamespacedResourceClaim", - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ], + "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1DeviceClass", "produces": [ "application/json", "application/yaml", @@ -78529,7 +83682,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78542,77 +83695,163 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "DeviceClass", "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/namespace-vgWSWtn3" + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the DeviceClass", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "post": { + ] + }, + "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaims": { + "get": { "consumes": [ "*/*" ], - "description": "create a ResourceClaim", - "operationId": "createResourceV1NamespacedResourceClaim", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, + "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1NamespacedResourceClaimList", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true + "401": { + "description": "Unauthorized" } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaims/{name}": { + "get": { + "consumes": [ + "*/*" ], + "description": "watch changes to an object of kind ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1NamespacedResourceClaim", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78625,62 +83864,84 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "ResourceClaim", "version": "v1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ResourceClaim", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaims/{name}": { - "delete": { + "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaimtemplates": { + "get": { "consumes": [ "*/*" ], - "description": "delete a ResourceClaim", - "operationId": "deleteResourceV1NamespacedResourceClaim", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - } - ], + "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1NamespacedResourceClaimTemplateList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78693,30 +83954,76 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1" } - }, + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaimtemplates/{name}": { "get": { "consumes": [ "*/*" ], - "description": "read the specified ResourceClaim", - "operationId": "readResourceV1NamespacedResourceClaim", + "description": "watch changes to an object of kind ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78729,16 +84036,31 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1" } }, "parameters": [ { - "description": "name of the ResourceClaim", + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ResourceClaimTemplate", "in": "path", "name": "name", "required": true, @@ -78750,60 +84072,48 @@ }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/resource.k8s.io/v1/watch/resourceclaims": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update the specified ResourceClaim", - "operationId": "patchResourceV1NamespacedResourceClaim", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "*/*" ], + "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1ResourceClaimListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78816,63 +84126,73 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "ResourceClaim", "version": "v1" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1/watch/resourceclaimtemplates": { + "get": { "consumes": [ "*/*" ], - "description": "replace the specified ResourceClaim", - "operationId": "replaceResourceV1NamespacedResourceClaim", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], + "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1ResourceClaimTemplateListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78885,32 +84205,73 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaims/{name}/status": { + "/apis/resource.k8s.io/v1/watch/resourceslices": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified ResourceClaim", - "operationId": "readResourceV1NamespacedResourceClaimStatus", + "description": "watch individual changes to a list of ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1ResourceSliceList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -78923,81 +84284,73 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1" } }, "parameters": [ { - "description": "name of the ResourceClaim", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/namespace-vgWSWtn3" + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/resource.k8s.io/v1/watch/resourceslices/{name}": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update status of the specified ResourceClaim", - "operationId": "patchResourceV1NamespacedResourceClaimStatus", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "*/*" ], + "description": "watch changes to an object of kind ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1ResourceSlice", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -79010,46 +84363,70 @@ "tags": [ "resource_v1" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ResourceSlice", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1alpha3/": { + "get": { "consumes": [ - "*/*" - ], - "description": "replace status of the specified ResourceClaim", - "operationId": "replaceResourceV1NamespacedResourceClaimStatus", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" ], + "description": "get available resources", + "operationId": "getResourceV1alpha3APIResources", "produces": [ "application/json", "application/yaml", @@ -79060,13 +84437,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" } }, "401": { @@ -79077,23 +84448,17 @@ "https" ], "tags": [ - "resource_v1" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1" - } + "resource_v1alpha3" + ] } }, - "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaimtemplates": { + "/apis/resource.k8s.io/v1alpha3/devicetaintrules": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceClaimTemplate", - "operationId": "deleteResourceV1CollectionNamespacedResourceClaimTemplate", + "description": "delete collection of DeviceTaintRule", + "operationId": "deleteResourceV1alpha3CollectionDeviceTaintRule", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -79138,6 +84503,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -79163,21 +84531,21 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaimTemplate", - "operationId": "listResourceV1NamespacedResourceClaimTemplate", + "description": "list or watch objects of kind DeviceTaintRule", + "operationId": "listResourceV1alpha3DeviceTaintRule", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -79203,6 +84571,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -79223,7 +84594,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplateList" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" } }, "401": { @@ -79234,19 +84605,16 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "parameters": [ - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -79255,15 +84623,15 @@ "consumes": [ "*/*" ], - "description": "create a ResourceClaimTemplate", - "operationId": "createResourceV1NamespacedResourceClaimTemplate", + "description": "create a DeviceTaintRule", + "operationId": "createResourceV1alpha3DeviceTaintRule", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, { @@ -79294,19 +84662,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79317,23 +84685,23 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } } }, - "/apis/resource.k8s.io/v1/namespaces/{namespace}/resourceclaimtemplates/{name}": { + "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ResourceClaimTemplate", - "operationId": "deleteResourceV1NamespacedResourceClaimTemplate", + "description": "delete a DeviceTaintRule", + "operationId": "deleteResourceV1alpha3DeviceTaintRule", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -79368,13 +84736,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79385,21 +84753,21 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified ResourceClaimTemplate", - "operationId": "readResourceV1NamespacedResourceClaimTemplate", + "description": "read the specified DeviceTaintRule", + "operationId": "readResourceV1alpha3DeviceTaintRule", "produces": [ "application/json", "application/yaml", @@ -79410,7 +84778,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79421,27 +84789,24 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "parameters": [ { - "description": "name of the ResourceClaimTemplate", + "description": "name of the DeviceTaintRule", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -79454,8 +84819,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ResourceClaimTemplate", - "operationId": "patchResourceV1NamespacedResourceClaimTemplate", + "description": "partially update the specified DeviceTaintRule", + "operationId": "patchResourceV1alpha3DeviceTaintRule", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -79491,13 +84856,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79508,28 +84873,28 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified ResourceClaimTemplate", - "operationId": "replaceResourceV1NamespacedResourceClaimTemplate", + "description": "replace the specified DeviceTaintRule", + "operationId": "replaceResourceV1alpha3DeviceTaintRule", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, { @@ -79560,13 +84925,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79577,37 +84942,34 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } } }, - "/apis/resource.k8s.io/v1/resourceclaims": { + "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaim", - "operationId": "listResourceV1ResourceClaimForAllNamespaces", + "description": "read status of the specified DeviceTaintRule", + "operationId": "readResourceV1alpha3DeviceTaintRuleStatus", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimList" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79618,72 +84980,80 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1/resourceclaimtemplates": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified DeviceTaintRule", + "operationId": "patchResourceV1alpha3DeviceTaintRuleStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "list or watch objects of kind ResourceClaimTemplate", - "operationId": "listResourceV1ResourceClaimTemplateForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceClaimTemplateList" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "401": { @@ -79694,58 +85064,92 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + "put": { + "consumes": [ + "*/*" + ], + "description": "replace status of the specified DeviceTaintRule", + "operationId": "replaceResourceV1alpha3DeviceTaintRuleStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "401": { + "description": "Unauthorized" + } }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + "schemes": [ + "https" + ], + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" } - ] + } }, - "/apis/resource.k8s.io/v1/resourceslices": { + "/apis/resource.k8s.io/v1alpha3/resourcepoolstatusrequests": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceSlice", - "operationId": "deleteResourceV1CollectionResourceSlice", + "description": "delete collection of ResourcePoolStatusRequest", + "operationId": "deleteResourceV1alpha3CollectionResourcePoolStatusRequest", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -79790,6 +85194,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -79815,21 +85222,21 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceSlice", - "operationId": "listResourceV1ResourceSlice", + "description": "list or watch objects of kind ResourcePoolStatusRequest", + "operationId": "listResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -79855,6 +85262,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -79875,7 +85285,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSliceList" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" } }, "401": { @@ -79886,13 +85296,13 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "parameters": [ @@ -79904,15 +85314,15 @@ "consumes": [ "*/*" ], - "description": "create a ResourceSlice", - "operationId": "createResourceV1ResourceSlice", + "description": "create a ResourcePoolStatusRequest", + "operationId": "createResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, { @@ -79943,19 +85353,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -79966,23 +85376,23 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } } }, - "/apis/resource.k8s.io/v1/resourceslices/{name}": { + "/apis/resource.k8s.io/v1alpha3/resourcepoolstatusrequests/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ResourceSlice", - "operationId": "deleteResourceV1ResourceSlice", + "description": "delete a ResourcePoolStatusRequest", + "operationId": "deleteResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -80017,13 +85427,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -80034,21 +85444,21 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified ResourceSlice", - "operationId": "readResourceV1ResourceSlice", + "description": "read the specified ResourcePoolStatusRequest", + "operationId": "readResourceV1alpha3ResourcePoolStatusRequest", "produces": [ "application/json", "application/yaml", @@ -80059,7 +85469,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -80070,18 +85480,18 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "parameters": [ { - "description": "name of the ResourceSlice", + "description": "name of the ResourcePoolStatusRequest", "in": "path", "name": "name", "required": true, @@ -80100,8 +85510,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ResourceSlice", - "operationId": "patchResourceV1ResourceSlice", + "description": "partially update the specified ResourcePoolStatusRequest", + "operationId": "patchResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -80137,13 +85547,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -80154,28 +85564,28 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified ResourceSlice", - "operationId": "replaceResourceV1ResourceSlice", + "description": "replace the specified ResourcePoolStatusRequest", + "operationId": "replaceResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, { @@ -80206,13 +85616,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -80223,113 +85633,34 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } } }, - "/apis/resource.k8s.io/v1/watch/deviceclasses": { - "get": { - "consumes": [ - "*/*" - ], - "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1DeviceClassList", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1" - ], - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "DeviceClass", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1/watch/deviceclasses/{name}": { + "/apis/resource.k8s.io/v1alpha3/resourcepoolstatusrequests/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1DeviceClass", + "description": "read status of the specified ResourcePoolStatusRequest", + "operationId": "readResourceV1alpha3ResourcePoolStatusRequestStatus", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -80340,33 +85671,18 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the DeviceClass", + "description": "name of the ResourcePoolStatusRequest", "in": "path", "name": "name", "required": true, @@ -80375,124 +85691,60 @@ }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaims": { - "get": { + ], + "patch": { "consumes": [ - "*/*" - ], - "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1NamespacedResourceClaimList", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } + "description": "partially update status of the specified ResourcePoolStatusRequest", + "operationId": "patchResourceV1alpha3ResourcePoolStatusRequestStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" }, - "401": { - "description": "Unauthorized" + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1" - ], - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaims/{name}": { - "get": { - "consumes": [ - "*/*" ], - "description": "watch changes to an object of kind ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1NamespacedResourceClaim", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "401": { @@ -80503,235 +85755,92 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ResourceClaim", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaimtemplates": { - "get": { + "put": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1NamespacedResourceClaimTemplateList", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", + "description": "replace status of the specified ResourcePoolStatusRequest", + "operationId": "replaceResourceV1alpha3ResourcePoolStatusRequestStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, - "401": { - "description": "Unauthorized" + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1" - ], - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1/watch/namespaces/{namespace}/resourceclaimtemplates/{name}": { - "get": { - "consumes": [ - "*/*" ], - "description": "watch changes to an object of kind ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1" - ], - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ResourceClaimTemplate", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "401": { + "description": "Unauthorized" + } }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + "schemes": [ + "https" + ], + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } - ] + } }, - "/apis/resource.k8s.io/v1/watch/resourceclaims": { + "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1ResourceClaimListForAllNamespaces", + "description": "watch individual changes to a list of DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1alpha3DeviceTaintRuleList", "produces": [ "application/json", "application/yaml", @@ -80756,13 +85865,13 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "parameters": [ @@ -80793,6 +85902,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -80801,13 +85913,13 @@ } ] }, - "/apis/resource.k8s.io/v1/watch/resourceclaimtemplates": { + "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1ResourceClaimTemplateListForAllNamespaces", + "description": "watch changes to an object of kind DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1alpha3DeviceTaintRule", "produces": [ "application/json", "application/yaml", @@ -80832,13 +85944,13 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1" + "kind": "DeviceTaintRule", + "version": "v1alpha3" } }, "parameters": [ @@ -80857,6 +85969,14 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, + { + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -80869,6 +85989,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -80877,13 +86000,13 @@ } ] }, - "/apis/resource.k8s.io/v1/watch/resourceslices": { + "/apis/resource.k8s.io/v1alpha3/watch/resourcepoolstatusrequests": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1ResourceSliceList", + "description": "watch individual changes to a list of ResourcePoolStatusRequest. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1alpha3ResourcePoolStatusRequestList", "produces": [ "application/json", "application/yaml", @@ -80908,13 +86031,13 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "parameters": [ @@ -80945,6 +86068,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -80953,13 +86079,13 @@ } ] }, - "/apis/resource.k8s.io/v1/watch/resourceslices/{name}": { + "/apis/resource.k8s.io/v1alpha3/watch/resourcepoolstatusrequests/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1ResourceSlice", + "description": "watch changes to an object of kind ResourcePoolStatusRequest. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1alpha3ResourcePoolStatusRequest", "produces": [ "application/json", "application/yaml", @@ -80984,13 +86110,13 @@ "https" ], "tags": [ - "resource_v1" + "resource_v1alpha3" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1" + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" } }, "parameters": [ @@ -81010,7 +86136,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the ResourceSlice", + "description": "name of the ResourcePoolStatusRequest", "in": "path", "name": "name", "required": true, @@ -81029,6 +86155,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -81037,7 +86166,7 @@ } ] }, - "/apis/resource.k8s.io/v1alpha3/": { + "/apis/resource.k8s.io/v1beta1/": { "get": { "consumes": [ "application/json", @@ -81046,7 +86175,7 @@ "application/cbor" ], "description": "get available resources", - "operationId": "getResourceV1alpha3APIResources", + "operationId": "getResourceV1beta1APIResources", "produces": [ "application/json", "application/yaml", @@ -81068,17 +86197,17 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ] } }, - "/apis/resource.k8s.io/v1alpha3/devicetaintrules": { + "/apis/resource.k8s.io/v1beta1/deviceclasses": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of DeviceTaintRule", - "operationId": "deleteResourceV1alpha3CollectionDeviceTaintRule", + "description": "delete collection of DeviceClass", + "operationId": "deleteResourceV1beta1CollectionDeviceClass", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -81123,6 +86252,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -81148,21 +86280,21 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind DeviceTaintRule", - "operationId": "listResourceV1alpha3DeviceTaintRule", + "description": "list or watch objects of kind DeviceClass", + "operationId": "listResourceV1beta1DeviceClass", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -81188,6 +86320,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -81208,7 +86343,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClassList" } }, "401": { @@ -81219,13 +86354,13 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" } }, "parameters": [ @@ -81237,15 +86372,15 @@ "consumes": [ "*/*" ], - "description": "create a DeviceTaintRule", - "operationId": "createResourceV1alpha3DeviceTaintRule", + "description": "create a DeviceClass", + "operationId": "createResourceV1beta1DeviceClass", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, { @@ -81276,19 +86411,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "401": { @@ -81299,23 +86434,23 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" } } }, - "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}": { + "/apis/resource.k8s.io/v1beta1/deviceclasses/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a DeviceTaintRule", - "operationId": "deleteResourceV1alpha3DeviceTaintRule", + "description": "delete a DeviceClass", + "operationId": "deleteResourceV1beta1DeviceClass", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -81350,13 +86485,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "401": { @@ -81367,21 +86502,21 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified DeviceTaintRule", - "operationId": "readResourceV1alpha3DeviceTaintRule", + "description": "read the specified DeviceClass", + "operationId": "readResourceV1beta1DeviceClass", "produces": [ "application/json", "application/yaml", @@ -81392,7 +86527,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "401": { @@ -81403,18 +86538,18 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" } }, "parameters": [ { - "description": "name of the DeviceTaintRule", + "description": "name of the DeviceClass", "in": "path", "name": "name", "required": true, @@ -81433,8 +86568,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified DeviceTaintRule", - "operationId": "patchResourceV1alpha3DeviceTaintRule", + "description": "partially update the specified DeviceClass", + "operationId": "patchResourceV1beta1DeviceClass", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -81470,13 +86605,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "401": { @@ -81487,28 +86622,28 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified DeviceTaintRule", - "operationId": "replaceResourceV1alpha3DeviceTaintRule", + "description": "replace the specified DeviceClass", + "operationId": "replaceResourceV1beta1DeviceClass", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, { @@ -81539,13 +86674,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" } }, "401": { @@ -81556,23 +86691,335 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "DeviceClass", + "version": "v1beta1" + } + } + }, + "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaims": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of ResourceClaim", + "operationId": "deleteResourceV1beta1CollectionNamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind ResourceClaim", + "operationId": "listResourceV1beta1NamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a ResourceClaim", + "operationId": "createResourceV1beta1NamespacedResourceClaim", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta1" + } + } + }, + "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaims/{name}": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete a ResourceClaim", + "operationId": "deleteResourceV1beta1NamespacedResourceClaim", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta1" } - } - }, - "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}/status": { + }, "get": { "consumes": [ "*/*" ], - "description": "read status of the specified DeviceTaintRule", - "operationId": "readResourceV1alpha3DeviceTaintRuleStatus", + "description": "read the specified ResourceClaim", + "operationId": "readResourceV1beta1NamespacedResourceClaim", "produces": [ "application/json", "application/yaml", @@ -81583,7 +87030,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "401": { @@ -81594,24 +87041,27 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "ResourceClaim", + "version": "v1beta1" } }, "parameters": [ { - "description": "name of the DeviceTaintRule", + "description": "name of the ResourceClaim", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -81624,8 +87074,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update status of the specified DeviceTaintRule", - "operationId": "patchResourceV1alpha3DeviceTaintRuleStatus", + "description": "partially update the specified ResourceClaim", + "operationId": "patchResourceV1beta1NamespacedResourceClaim", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -81661,13 +87111,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "401": { @@ -81678,28 +87128,28 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "ResourceClaim", + "version": "v1beta1" } }, "put": { "consumes": [ "*/*" ], - "description": "replace status of the specified DeviceTaintRule", - "operationId": "replaceResourceV1alpha3DeviceTaintRuleStatus", + "description": "replace the specified ResourceClaim", + "operationId": "replaceResourceV1beta1NamespacedResourceClaim", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, { @@ -81730,13 +87180,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "401": { @@ -81747,37 +87197,34 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "ResourceClaim", + "version": "v1beta1" } } }, - "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules": { + "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaims/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1alpha3DeviceTaintRuleList", + "description": "read status of the specified ResourceClaim", + "operationId": "readResourceV1beta1NamespacedResourceClaimStatus", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "401": { @@ -81788,72 +87235,83 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "ResourceClaim", + "version": "v1beta1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" + "description": "name of the ResourceClaim", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { - "$ref": "#/parameters/limit-1NfNmdNH" + "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules/{name}": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified ResourceClaim", + "operationId": "patchResourceV1beta1NamespacedResourceClaimStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch changes to an object of kind DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1alpha3DeviceTaintRule", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "401": { @@ -81864,69 +87322,48 @@ "https" ], "tags": [ - "resource_v1alpha3" + "resource_v1beta1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" + "kind": "ResourceClaim", + "version": "v1beta1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the DeviceTaintRule", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta1/": { - "get": { + "put": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "*/*" + ], + "description": "replace status of the specified ResourceClaim", + "operationId": "replaceResourceV1beta1NamespacedResourceClaimStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } ], - "description": "get available resources", - "operationId": "getResourceV1beta1APIResources", "produces": [ "application/json", "application/yaml", @@ -81937,7 +87374,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" } }, "401": { @@ -81949,16 +87392,22 @@ ], "tags": [ "resource_v1beta1" - ] + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta1" + } } }, - "/apis/resource.k8s.io/v1beta1/deviceclasses": { + "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaimtemplates": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of DeviceClass", - "operationId": "deleteResourceV1beta1CollectionDeviceClass", + "description": "delete collection of ResourceClaimTemplate", + "operationId": "deleteResourceV1beta1CollectionNamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -82003,6 +87452,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -82033,7 +87485,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } }, @@ -82041,8 +87493,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind DeviceClass", - "operationId": "listResourceV1beta1DeviceClass", + "description": "list or watch objects of kind ResourceClaimTemplate", + "operationId": "listResourceV1beta1NamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -82068,6 +87520,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -82088,7 +87543,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClassList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplateList" } }, "401": { @@ -82104,11 +87559,14 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } }, "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -82117,15 +87575,15 @@ "consumes": [ "*/*" ], - "description": "create a DeviceClass", - "operationId": "createResourceV1beta1DeviceClass", + "description": "create a ResourceClaimTemplate", + "operationId": "createResourceV1beta1NamespacedResourceClaimTemplate", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, { @@ -82156,19 +87614,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "401": { @@ -82184,18 +87642,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } } }, - "/apis/resource.k8s.io/v1beta1/deviceclasses/{name}": { + "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaimtemplates/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a DeviceClass", - "operationId": "deleteResourceV1beta1DeviceClass", + "description": "delete a ResourceClaimTemplate", + "operationId": "deleteResourceV1beta1NamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -82230,13 +87688,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "401": { @@ -82252,7 +87710,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } }, @@ -82260,8 +87718,8 @@ "consumes": [ "*/*" ], - "description": "read the specified DeviceClass", - "operationId": "readResourceV1beta1DeviceClass", + "description": "read the specified ResourceClaimTemplate", + "operationId": "readResourceV1beta1NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", @@ -82272,7 +87730,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "401": { @@ -82288,19 +87746,22 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } }, "parameters": [ { - "description": "name of the DeviceClass", + "description": "name of the ResourceClaimTemplate", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -82313,8 +87774,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified DeviceClass", - "operationId": "patchResourceV1beta1DeviceClass", + "description": "partially update the specified ResourceClaimTemplate", + "operationId": "patchResourceV1beta1NamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -82350,13 +87811,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "401": { @@ -82372,7 +87833,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } }, @@ -82380,15 +87841,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified DeviceClass", - "operationId": "replaceResourceV1beta1DeviceClass", + "description": "replace the specified ResourceClaimTemplate", + "operationId": "replaceResourceV1beta1NamespacedResourceClaimTemplate", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, { @@ -82419,13 +87880,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" } }, "401": { @@ -82441,18 +87902,176 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta1" } } }, - "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaims": { + "/apis/resource.k8s.io/v1beta1/resourceclaims": { + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind ResourceClaim", + "operationId": "listResourceV1beta1ResourceClaimForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta1/resourceclaimtemplates": { + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind ResourceClaimTemplate", + "operationId": "listResourceV1beta1ResourceClaimTemplateForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplateList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaimTemplate", + "version": "v1beta1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta1/resourceslices": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceClaim", - "operationId": "deleteResourceV1beta1CollectionNamespacedResourceClaim", + "description": "delete collection of ResourceSlice", + "operationId": "deleteResourceV1beta1CollectionResourceSlice", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -82497,6 +88116,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -82527,7 +88149,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } }, @@ -82535,8 +88157,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaim", - "operationId": "listResourceV1beta1NamespacedResourceClaim", + "description": "list or watch objects of kind ResourceSlice", + "operationId": "listResourceV1beta1ResourceSlice", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -82562,6 +88184,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -82582,7 +88207,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSliceList" } }, "401": { @@ -82598,14 +88223,11 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } }, "parameters": [ - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -82614,15 +88236,15 @@ "consumes": [ "*/*" ], - "description": "create a ResourceClaim", - "operationId": "createResourceV1beta1NamespacedResourceClaim", + "description": "create a ResourceSlice", + "operationId": "createResourceV1beta1ResourceSlice", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, { @@ -82653,19 +88275,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "401": { @@ -82681,18 +88303,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } } }, - "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaims/{name}": { + "/apis/resource.k8s.io/v1beta1/resourceslices/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ResourceClaim", - "operationId": "deleteResourceV1beta1NamespacedResourceClaim", + "description": "delete a ResourceSlice", + "operationId": "deleteResourceV1beta1ResourceSlice", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -82727,13 +88349,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "401": { @@ -82749,7 +88371,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } }, @@ -82757,8 +88379,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ResourceClaim", - "operationId": "readResourceV1beta1NamespacedResourceClaim", + "description": "read the specified ResourceSlice", + "operationId": "readResourceV1beta1ResourceSlice", "produces": [ "application/json", "application/yaml", @@ -82769,7 +88391,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "401": { @@ -82785,22 +88407,19 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } }, "parameters": [ { - "description": "name of the ResourceClaim", + "description": "name of the ResourceSlice", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -82813,8 +88432,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ResourceClaim", - "operationId": "patchResourceV1beta1NamespacedResourceClaim", + "description": "partially update the specified ResourceSlice", + "operationId": "patchResourceV1beta1ResourceSlice", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -82850,13 +88469,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "401": { @@ -82872,7 +88491,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } }, @@ -82880,15 +88499,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ResourceClaim", - "operationId": "replaceResourceV1beta1NamespacedResourceClaim", + "description": "replace the specified ResourceSlice", + "operationId": "replaceResourceV1beta1ResourceSlice", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, { @@ -82919,13 +88538,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" } }, "401": { @@ -82941,29 +88560,32 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } } }, - "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaims/{name}/status": { + "/apis/resource.k8s.io/v1beta1/watch/deviceclasses": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified ResourceClaim", - "operationId": "readResourceV1beta1NamespacedResourceClaimStatus", + "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta1DeviceClassList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -82976,236 +88598,73 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "DeviceClass", "version": "v1beta1" } }, "parameters": [ { - "description": "name of the ResourceClaim", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/namespace-vgWSWtn3" + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" }, { "$ref": "#/parameters/pretty-tJGM1-ng" - } - ], - "patch": { - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update status of the specified ResourceClaim", - "operationId": "patchResourceV1beta1NamespacedResourceClaimStatus", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" - } - }, - "401": { - "description": "Unauthorized" - } }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1beta1" - ], - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta1" - } - }, - "put": { - "consumes": [ - "*/*" - ], - "description": "replace status of the specified ResourceClaim", - "operationId": "replaceResourceV1beta1NamespacedResourceClaimStatus", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaim" - } - }, - "401": { - "description": "Unauthorized" - } + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1beta1" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta1" + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - } + ] }, - "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaimtemplates": { - "delete": { + "/apis/resource.k8s.io/v1beta1/watch/deviceclasses/{name}": { + "get": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceClaimTemplate", - "operationId": "deleteResourceV1beta1CollectionNamespacedResourceClaimTemplate", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - } - ], + "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta1DeviceClass", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83218,51 +88677,67 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "deletecollection", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "DeviceClass", "version": "v1beta1" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the DeviceClass", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaims": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaimTemplate", - "operationId": "listResourceV1beta1NamespacedResourceClaimTemplate", - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ], + "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta1NamespacedResourceClaimList", "produces": [ "application/json", "application/yaml", @@ -83276,7 +88751,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplateList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83289,77 +88764,76 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceClaim", "version": "v1beta1" } }, "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, { "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "post": { + ] + }, + "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaims/{name}": { + "get": { "consumes": [ "*/*" ], - "description": "create a ResourceClaimTemplate", - "operationId": "createResourceV1beta1NamespacedResourceClaimTemplate", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], + "description": "watch changes to an object of kind ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta1NamespacedResourceClaim", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - "202": { - "description": "Accepted", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83372,62 +88846,84 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceClaim", "version": "v1beta1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ResourceClaim", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/resource.k8s.io/v1beta1/namespaces/{namespace}/resourceclaimtemplates/{name}": { - "delete": { + "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaimtemplates": { + "get": { "consumes": [ "*/*" ], - "description": "delete a ResourceClaimTemplate", - "operationId": "deleteResourceV1beta1NamespacedResourceClaimTemplate", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - } - ], + "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta1NamespacedResourceClaimTemplateList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83440,30 +88936,76 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "ResourceClaimTemplate", "version": "v1beta1" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaimtemplates/{name}": { "get": { "consumes": [ "*/*" ], - "description": "read the specified ResourceClaimTemplate", - "operationId": "readResourceV1beta1NamespacedResourceClaimTemplate", + "description": "watch changes to an object of kind ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta1NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83476,7 +89018,7 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "ResourceClaimTemplate", @@ -83484,6 +89026,21 @@ } }, "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, { "description": "name of the ResourceClaimTemplate", "in": "path", @@ -83497,60 +89054,48 @@ }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/resource.k8s.io/v1beta1/watch/resourceclaims": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update the specified ResourceClaimTemplate", - "operationId": "patchResourceV1beta1NamespacedResourceClaimTemplate", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "*/*" ], + "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta1ResourceClaimListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83563,63 +89108,73 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceClaim", "version": "v1beta1" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta1/watch/resourceclaimtemplates": { + "get": { "consumes": [ "*/*" ], - "description": "replace the specified ResourceClaimTemplate", - "operationId": "replaceResourceV1beta1NamespacedResourceClaimTemplate", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], + "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta1ResourceClaimTemplateListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83632,21 +89187,59 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "ResourceClaimTemplate", "version": "v1beta1" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/resource.k8s.io/v1beta1/resourceclaims": { + "/apis/resource.k8s.io/v1beta1/watch/resourceslices": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaim", - "operationId": "listResourceV1beta1ResourceClaimForAllNamespaces", + "description": "watch individual changes to a list of ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta1ResourceSliceList", "produces": [ "application/json", "application/yaml", @@ -83660,7 +89253,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83673,10 +89266,10 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta1" } }, @@ -83708,6 +89301,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -83716,13 +89312,13 @@ } ] }, - "/apis/resource.k8s.io/v1beta1/resourceclaimtemplates": { + "/apis/resource.k8s.io/v1beta1/watch/resourceslices/{name}": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaimTemplate", - "operationId": "listResourceV1beta1ResourceClaimTemplateForAllNamespaces", + "description": "watch changes to an object of kind ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta1ResourceSlice", "produces": [ "application/json", "application/yaml", @@ -83736,7 +89332,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceClaimTemplateList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -83749,10 +89345,10 @@ "tags": [ "resource_v1beta1" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta1" } }, @@ -83772,6 +89368,14 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, + { + "description": "name of the ResourceSlice", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -83784,6 +89388,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -83792,13 +89399,48 @@ } ] }, - "/apis/resource.k8s.io/v1beta1/resourceslices": { + "/apis/resource.k8s.io/v1beta2/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "description": "get available resources", + "operationId": "getResourceV1beta2APIResources", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta2" + ] + } + }, + "/apis/resource.k8s.io/v1beta2/deviceclasses": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceSlice", - "operationId": "deleteResourceV1beta1CollectionResourceSlice", + "description": "delete collection of DeviceClass", + "operationId": "deleteResourceV1beta2CollectionDeviceClass", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -83843,6 +89485,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -83868,21 +89513,21 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceSlice", - "operationId": "listResourceV1beta1ResourceSlice", + "description": "list or watch objects of kind DeviceClass", + "operationId": "listResourceV1beta2DeviceClass", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -83908,6 +89553,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -83928,7 +89576,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSliceList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClassList" } }, "401": { @@ -83939,13 +89587,13 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } }, "parameters": [ @@ -83957,15 +89605,15 @@ "consumes": [ "*/*" ], - "description": "create a ResourceSlice", - "operationId": "createResourceV1beta1ResourceSlice", + "description": "create a DeviceClass", + "operationId": "createResourceV1beta2DeviceClass", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, { @@ -83996,19 +89644,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "401": { @@ -84019,23 +89667,23 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta1/resourceslices/{name}": { + "/apis/resource.k8s.io/v1beta2/deviceclasses/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ResourceSlice", - "operationId": "deleteResourceV1beta1ResourceSlice", + "description": "delete a DeviceClass", + "operationId": "deleteResourceV1beta2DeviceClass", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -84070,13 +89718,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "401": { @@ -84087,21 +89735,21 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified ResourceSlice", - "operationId": "readResourceV1beta1ResourceSlice", + "description": "read the specified DeviceClass", + "operationId": "readResourceV1beta2DeviceClass", "produces": [ "application/json", "application/yaml", @@ -84112,7 +89760,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "401": { @@ -84123,18 +89771,18 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } }, "parameters": [ { - "description": "name of the ResourceSlice", + "description": "name of the DeviceClass", "in": "path", "name": "name", "required": true, @@ -84153,8 +89801,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ResourceSlice", - "operationId": "patchResourceV1beta1ResourceSlice", + "description": "partially update the specified DeviceClass", + "operationId": "patchResourceV1beta2DeviceClass", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -84190,13 +89838,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "401": { @@ -84207,28 +89855,28 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified ResourceSlice", - "operationId": "replaceResourceV1beta1ResourceSlice", + "description": "replace the specified DeviceClass", + "operationId": "replaceResourceV1beta2DeviceClass", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, { @@ -84259,13 +89907,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta1.ResourceSlice" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" } }, "401": { @@ -84276,113 +89924,85 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceClass", + "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta1/watch/deviceclasses": { - "get": { + "/apis/resource.k8s.io/v1beta2/devicetaintrules": { + "delete": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta1DeviceClassList", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } + "description": "delete collection of DeviceTaintRule", + "operationId": "deleteResourceV1beta2CollectionDeviceTaintRule", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" }, - "401": { - "description": "Unauthorized" + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1beta1" - ], - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "DeviceClass", - "version": "v1beta1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/deviceclasses/{name}": { - "get": { - "consumes": [ - "*/*" ], - "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta1DeviceClass", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -84393,66 +90013,56 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", - "version": "v1beta1" + "kind": "DeviceTaintRule", + "version": "v1beta2" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the DeviceClass", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaims": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta1NamespacedResourceClaimList", + "description": "list or watch objects of kind DeviceTaintRule", + "operationId": "listResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], "produces": [ "application/json", "application/yaml", @@ -84466,7 +90076,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" } }, "401": { @@ -84477,162 +90087,76 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta1" + "kind": "DeviceTaintRule", + "version": "v1beta2" } }, "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaims/{name}": { - "get": { + ], + "post": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta1NamespacedResourceClaim", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" - ], - "responses": { - "200": { - "description": "OK", + "description": "create a DeviceTaintRule", + "operationId": "createResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, - "401": { - "description": "Unauthorized" + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true } - }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1beta1" - ], - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ResourceClaim", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaimtemplates": { - "get": { - "consumes": [ - "*/*" ], - "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta1NamespacedResourceClaimTemplateList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -84643,75 +90167,64 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1beta1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + "kind": "DeviceTaintRule", + "version": "v1beta2" } - ] + } }, - "/apis/resource.k8s.io/v1beta1/watch/namespaces/{namespace}/resourceclaimtemplates/{name}": { - "get": { + "/apis/resource.k8s.io/v1beta2/devicetaintrules/{name}": { + "delete": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta1NamespacedResourceClaimTemplate", + "description": "delete a DeviceTaintRule", + "operationId": "deleteResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -84722,83 +90235,32 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1beta1" + "kind": "DeviceTaintRule", + "version": "v1beta2" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ResourceClaimTemplate", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/resourceclaims": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta1ResourceClaimListForAllNamespaces", + "description": "read the specified DeviceTaintRule", + "operationId": "readResourceV1beta2DeviceTaintRule", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -84809,72 +90271,80 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta1" + "kind": "DeviceTaintRule", + "version": "v1beta2" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/resourceclaimtemplates": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified DeviceTaintRule", + "operationId": "patchResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta1ResourceClaimTemplateListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -84885,72 +90355,65 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1beta1" + "kind": "DeviceTaintRule", + "version": "v1beta2" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta1/watch/resourceslices": { - "get": { + "put": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta1ResourceSliceList", + "description": "replace the specified DeviceTaintRule", + "operationId": "replaceResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -84961,72 +90424,34 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + "kind": "DeviceTaintRule", + "version": "v1beta2" } - ] + } }, - "/apis/resource.k8s.io/v1beta1/watch/resourceslices/{name}": { + "/apis/resource.k8s.io/v1beta2/devicetaintrules/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta1ResourceSlice", + "description": "read status of the specified DeviceTaintRule", + "operationId": "readResourceV1beta2DeviceTaintRuleStatus", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -85037,33 +90462,18 @@ "https" ], "tags": [ - "resource_v1beta1" + "resource_v1beta2" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta1" + "kind": "DeviceTaintRule", + "version": "v1beta2" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ResourceSlice", + "description": "name of the DeviceTaintRule", "in": "path", "name": "name", "required": true, @@ -85072,34 +90482,112 @@ }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1beta2/": { - "get": { + ], + "patch": { "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified DeviceTaintRule", + "operationId": "patchResourceV1beta2DeviceTaintRuleStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", "application/cbor" ], - "description": "get available resources", - "operationId": "getResourceV1beta2APIResources", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace status of the specified DeviceTaintRule", + "operationId": "replaceResourceV1beta2DeviceTaintRuleStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", @@ -85110,7 +90598,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "401": { @@ -85122,16 +90616,22 @@ ], "tags": [ "resource_v1beta2" - ] + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } } }, - "/apis/resource.k8s.io/v1beta2/deviceclasses": { + "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaims": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of DeviceClass", - "operationId": "deleteResourceV1beta2CollectionDeviceClass", + "description": "delete collection of ResourceClaim", + "operationId": "deleteResourceV1beta2CollectionNamespacedResourceClaim", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -85176,6 +90676,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -85206,7 +90709,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } }, @@ -85214,8 +90717,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind DeviceClass", - "operationId": "listResourceV1beta2DeviceClass", + "description": "list or watch objects of kind ResourceClaim", + "operationId": "listResourceV1beta2NamespacedResourceClaim", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -85241,6 +90744,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -85261,7 +90767,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimList" } }, "401": { @@ -85277,11 +90783,14 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } }, "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -85290,15 +90799,15 @@ "consumes": [ "*/*" ], - "description": "create a DeviceClass", - "operationId": "createResourceV1beta2DeviceClass", + "description": "create a ResourceClaim", + "operationId": "createResourceV1beta2NamespacedResourceClaim", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, { @@ -85329,19 +90838,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "401": { @@ -85357,18 +90866,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta2/deviceclasses/{name}": { + "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaims/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a DeviceClass", - "operationId": "deleteResourceV1beta2DeviceClass", + "description": "delete a ResourceClaim", + "operationId": "deleteResourceV1beta2NamespacedResourceClaim", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -85403,13 +90912,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "401": { @@ -85425,7 +90934,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } }, @@ -85433,8 +90942,8 @@ "consumes": [ "*/*" ], - "description": "read the specified DeviceClass", - "operationId": "readResourceV1beta2DeviceClass", + "description": "read the specified ResourceClaim", + "operationId": "readResourceV1beta2NamespacedResourceClaim", "produces": [ "application/json", "application/yaml", @@ -85445,7 +90954,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "401": { @@ -85461,19 +90970,22 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } }, "parameters": [ { - "description": "name of the DeviceClass", + "description": "name of the ResourceClaim", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -85486,8 +90998,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified DeviceClass", - "operationId": "patchResourceV1beta2DeviceClass", + "description": "partially update the specified ResourceClaim", + "operationId": "patchResourceV1beta2NamespacedResourceClaim", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -85523,13 +91035,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "401": { @@ -85545,7 +91057,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } }, @@ -85553,15 +91065,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified DeviceClass", - "operationId": "replaceResourceV1beta2DeviceClass", + "description": "replace the specified ResourceClaim", + "operationId": "replaceResourceV1beta2NamespacedResourceClaim", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, { @@ -85592,13 +91104,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" } }, "401": { @@ -85614,18 +91126,212 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaims": { + "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaims/{name}/status": { + "get": { + "consumes": [ + "*/*" + ], + "description": "read status of the specified ResourceClaim", + "operationId": "readResourceV1beta2NamespacedResourceClaimStatus", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta2" + } + }, + "parameters": [ + { + "description": "name of the ResourceClaim", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "patch": { + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified ResourceClaim", + "operationId": "patchResourceV1beta2NamespacedResourceClaimStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta2" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace status of the specified ResourceClaim", + "operationId": "replaceResourceV1beta2NamespacedResourceClaimStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceClaim", + "version": "v1beta2" + } + } + }, + "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaimtemplates": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceClaim", - "operationId": "deleteResourceV1beta2CollectionNamespacedResourceClaim", + "description": "delete collection of ResourceClaimTemplate", + "operationId": "deleteResourceV1beta2CollectionNamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -85670,6 +91376,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -85700,7 +91409,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, @@ -85708,8 +91417,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaim", - "operationId": "listResourceV1beta2NamespacedResourceClaim", + "description": "list or watch objects of kind ResourceClaimTemplate", + "operationId": "listResourceV1beta2NamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -85735,6 +91444,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -85755,7 +91467,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplateList" } }, "401": { @@ -85771,7 +91483,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, @@ -85787,15 +91499,15 @@ "consumes": [ "*/*" ], - "description": "create a ResourceClaim", - "operationId": "createResourceV1beta2NamespacedResourceClaim", + "description": "create a ResourceClaimTemplate", + "operationId": "createResourceV1beta2NamespacedResourceClaimTemplate", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, { @@ -85826,19 +91538,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "401": { @@ -85854,18 +91566,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaims/{name}": { + "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaimtemplates/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ResourceClaim", - "operationId": "deleteResourceV1beta2NamespacedResourceClaim", + "description": "delete a ResourceClaimTemplate", + "operationId": "deleteResourceV1beta2NamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -85900,13 +91612,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "401": { @@ -85922,7 +91634,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, @@ -85930,8 +91642,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ResourceClaim", - "operationId": "readResourceV1beta2NamespacedResourceClaim", + "description": "read the specified ResourceClaimTemplate", + "operationId": "readResourceV1beta2NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", @@ -85942,7 +91654,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "401": { @@ -85958,13 +91670,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, "parameters": [ { - "description": "name of the ResourceClaim", + "description": "name of the ResourceClaimTemplate", "in": "path", "name": "name", "required": true, @@ -85986,8 +91698,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ResourceClaim", - "operationId": "patchResourceV1beta2NamespacedResourceClaim", + "description": "partially update the specified ResourceClaimTemplate", + "operationId": "patchResourceV1beta2NamespacedResourceClaimTemplate", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -86023,13 +91735,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "401": { @@ -86045,7 +91757,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, @@ -86053,15 +91765,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ResourceClaim", - "operationId": "replaceResourceV1beta2NamespacedResourceClaim", + "description": "replace the specified ResourceClaimTemplate", + "operationId": "replaceResourceV1beta2NamespacedResourceClaimTemplate", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, { @@ -86092,13 +91804,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" } }, "401": { @@ -86114,29 +91826,32 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaims/{name}/status": { + "/apis/resource.k8s.io/v1beta2/resourceclaims": { "get": { "consumes": [ "*/*" ], - "description": "read status of the specified ResourceClaim", - "operationId": "readResourceV1beta2NamespacedResourceClaimStatus", + "description": "list or watch objects of kind ResourceClaim", + "operationId": "listResourceV1beta2ResourceClaimForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimList" } }, "401": { @@ -86149,7 +91864,7 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "ResourceClaim", @@ -86158,72 +91873,64 @@ }, "parameters": [ { - "description": "name of the ResourceClaim", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, { - "$ref": "#/parameters/namespace-vgWSWtn3" + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" }, { "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - ], - "patch": { + ] + }, + "/apis/resource.k8s.io/v1beta2/resourceclaimtemplates": { + "get": { "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update status of the specified ResourceClaim", - "operationId": "patchResourceV1beta2NamespacedResourceClaimStatus", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + "*/*" ], + "description": "list or watch objects of kind ResourceClaimTemplate", + "operationId": "listResourceV1beta2ResourceClaimTemplateForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplateList" } }, "401": { @@ -86236,90 +91943,59 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, - "put": { - "consumes": [ - "*/*" - ], - "description": "replace status of the specified ResourceClaim", - "operationId": "replaceResourceV1beta2NamespacedResourceClaimStatus", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaim" - } - }, - "401": { - "description": "Unauthorized" - } + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1beta2" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta2" + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - } + ] }, - "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaimtemplates": { + "/apis/resource.k8s.io/v1beta2/resourceslices": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceClaimTemplate", - "operationId": "deleteResourceV1beta2CollectionNamespacedResourceClaimTemplate", + "description": "delete collection of ResourceSlice", + "operationId": "deleteResourceV1beta2CollectionResourceSlice", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -86364,6 +92040,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -86394,7 +92073,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } }, @@ -86402,8 +92081,8 @@ "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaimTemplate", - "operationId": "listResourceV1beta2NamespacedResourceClaimTemplate", + "description": "list or watch objects of kind ResourceSlice", + "operationId": "listResourceV1beta2ResourceSlice", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -86429,6 +92108,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -86449,7 +92131,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplateList" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSliceList" } }, "401": { @@ -86465,14 +92147,11 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } }, "parameters": [ - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -86481,15 +92160,15 @@ "consumes": [ "*/*" ], - "description": "create a ResourceClaimTemplate", - "operationId": "createResourceV1beta2NamespacedResourceClaimTemplate", + "description": "create a ResourceSlice", + "operationId": "createResourceV1beta2ResourceSlice", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, { @@ -86520,19 +92199,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "401": { @@ -86548,18 +92227,18 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta2/namespaces/{namespace}/resourceclaimtemplates/{name}": { + "/apis/resource.k8s.io/v1beta2/resourceslices/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a ResourceClaimTemplate", - "operationId": "deleteResourceV1beta2NamespacedResourceClaimTemplate", + "description": "delete a ResourceSlice", + "operationId": "deleteResourceV1beta2ResourceSlice", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -86594,13 +92273,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "401": { @@ -86616,7 +92295,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } }, @@ -86624,8 +92303,8 @@ "consumes": [ "*/*" ], - "description": "read the specified ResourceClaimTemplate", - "operationId": "readResourceV1beta2NamespacedResourceClaimTemplate", + "description": "read the specified ResourceSlice", + "operationId": "readResourceV1beta2ResourceSlice", "produces": [ "application/json", "application/yaml", @@ -86636,7 +92315,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "401": { @@ -86652,22 +92331,19 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } }, "parameters": [ { - "description": "name of the ResourceClaimTemplate", + "description": "name of the ResourceSlice", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -86680,8 +92356,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified ResourceClaimTemplate", - "operationId": "patchResourceV1beta2NamespacedResourceClaimTemplate", + "description": "partially update the specified ResourceSlice", + "operationId": "patchResourceV1beta2ResourceSlice", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -86717,13 +92393,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "401": { @@ -86739,7 +92415,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } }, @@ -86747,15 +92423,15 @@ "consumes": [ "*/*" ], - "description": "replace the specified ResourceClaimTemplate", - "operationId": "replaceResourceV1beta2NamespacedResourceClaimTemplate", + "description": "replace the specified ResourceSlice", + "operationId": "replaceResourceV1beta2ResourceSlice", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, { @@ -86786,13 +92462,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplate" + "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" } }, "401": { @@ -86808,18 +92484,18 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "ResourceSlice", "version": "v1beta2" } } }, - "/apis/resource.k8s.io/v1beta2/resourceclaims": { + "/apis/resource.k8s.io/v1beta2/watch/deviceclasses": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaim", - "operationId": "listResourceV1beta2ResourceClaimForAllNamespaces", + "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2DeviceClassList", "produces": [ "application/json", "application/yaml", @@ -86833,7 +92509,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -86846,10 +92522,10 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "DeviceClass", "version": "v1beta2" } }, @@ -86881,6 +92557,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -86889,13 +92568,13 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/resourceclaimtemplates": { + "/apis/resource.k8s.io/v1beta2/watch/deviceclasses/{name}": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceClaimTemplate", - "operationId": "listResourceV1beta2ResourceClaimTemplateForAllNamespaces", + "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2DeviceClass", "produces": [ "application/json", "application/yaml", @@ -86909,7 +92588,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceClaimTemplateList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -86922,10 +92601,10 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", + "kind": "DeviceClass", "version": "v1beta2" } }, @@ -86945,6 +92624,14 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, + { + "description": "name of the DeviceClass", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -86957,6 +92644,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -86965,72 +92655,27 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/resourceslices": { - "delete": { + "/apis/resource.k8s.io/v1beta2/watch/devicetaintrules": { + "get": { "consumes": [ "*/*" ], - "description": "delete collection of ResourceSlice", - "operationId": "deleteResourceV1beta2CollectionResourceSlice", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - } - ], + "description": "watch individual changes to a list of DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2DeviceTaintRuleList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -87043,51 +92688,59 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "deletecollection", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta2/watch/devicetaintrules/{name}": { "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind ResourceSlice", - "operationId": "listResourceV1beta2ResourceSlice", - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ], + "description": "watch changes to an object of kind DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2DeviceTaintRule", "produces": [ "application/json", "application/yaml", @@ -87101,7 +92754,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSliceList" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -87114,142 +92767,81 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, { "$ref": "#/parameters/pretty-tJGM1-ng" - } - ], - "post": { - "consumes": [ - "*/*" - ], - "description": "create a ResourceSlice", - "operationId": "createResourceV1beta2ResourceSlice", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - "401": { - "description": "Unauthorized" - } }, - "schemes": [ - "https" - ], - "tags": [ - "resource_v1beta2" - ], - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta2" + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" } - } + ] }, - "/apis/resource.k8s.io/v1beta2/resourceslices/{name}": { - "delete": { + "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaims": { + "get": { "consumes": [ "*/*" ], - "description": "delete a ResourceSlice", - "operationId": "deleteResourceV1beta2ResourceSlice", - "parameters": [ - { - "$ref": "#/parameters/body-2Y1dVQaQ" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" - }, - { - "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" - }, - { - "$ref": "#/parameters/orphanDependents-uRB25kX5" - }, - { - "$ref": "#/parameters/propagationPolicy-6jk3prlO" - } - ], + "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2NamespacedResourceClaimList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -87262,30 +92854,76 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "ResourceClaim", "version": "v1beta2" } }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaims/{name}": { "get": { "consumes": [ "*/*" ], - "description": "read the specified ResourceSlice", - "operationId": "readResourceV1beta2ResourceSlice", + "description": "watch changes to an object of kind ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2NamespacedResourceClaim", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -87298,78 +92936,84 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "ResourceClaim", "version": "v1beta2" } }, "parameters": [ { - "description": "name of the ResourceSlice", + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ResourceClaim", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" - } - ], - "patch": { - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml", - "application/apply-patch+cbor" - ], - "description": "partially update the specified ResourceSlice", - "operationId": "patchResourceV1beta2ResourceSlice", - "parameters": [ - { - "$ref": "#/parameters/body-78PwaGsr" - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-7c6nTn1T" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/force-tOGGb0Yi" - } + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaimtemplates": { + "get": { + "consumes": [ + "*/*" ], + "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2NamespacedResourceClaimTemplateList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -87382,63 +93026,76 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, - "put": { + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaimtemplates/{name}": { + "get": { "consumes": [ "*/*" ], - "description": "replace the specified ResourceSlice", - "operationId": "replaceResourceV1beta2ResourceSlice", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true - } - ], + "description": "watch changes to an object of kind ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2NamespacedResourceClaimTemplate", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -87451,21 +93108,70 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the ResourceClaimTemplate", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/resource.k8s.io/v1beta2/watch/deviceclasses": { + "/apis/resource.k8s.io/v1beta2/watch/resourceclaims": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2DeviceClassList", + "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2ResourceClaimListForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -87495,7 +93201,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaim", "version": "v1beta2" } }, @@ -87527,6 +93233,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -87535,13 +93244,13 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/watch/deviceclasses/{name}": { + "/apis/resource.k8s.io/v1beta2/watch/resourceclaimtemplates": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta2DeviceClass", + "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2ResourceClaimTemplateListForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -87568,10 +93277,10 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "ResourceClaimTemplate", "version": "v1beta2" } }, @@ -87591,14 +93300,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "description": "name of the DeviceClass", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -87611,6 +93312,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -87619,13 +93323,13 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaims": { + "/apis/resource.k8s.io/v1beta2/watch/resourceslices": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2NamespacedResourceClaimList", + "description": "watch individual changes to a list of ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2ResourceSliceList", "produces": [ "application/json", "application/yaml", @@ -87655,7 +93359,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta2" } }, @@ -87675,9 +93379,6 @@ { "$ref": "#/parameters/limit-1NfNmdNH" }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -87690,6 +93391,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -87698,13 +93402,13 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaims/{name}": { + "/apis/resource.k8s.io/v1beta2/watch/resourceslices/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta2NamespacedResourceClaim", + "description": "watch changes to an object of kind ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2ResourceSlice", "produces": [ "application/json", "application/yaml", @@ -87734,7 +93438,7 @@ "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceClaim", + "kind": "ResourceSlice", "version": "v1beta2" } }, @@ -87755,16 +93459,13 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the ResourceClaim", + "description": "name of the ResourceSlice", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" }, @@ -87777,35 +93478,376 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/scheduling.k8s.io/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "description": "get information of a group", + "operationId": "getSchedulingAPIGroup", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling" + ] + } + }, + "/apis/scheduling.k8s.io/v1/": { + "get": { + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "description": "get available resources", + "operationId": "getSchedulingV1APIResources", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1" + ] + } + }, + "/apis/scheduling.k8s.io/v1/priorityclasses": { + "delete": { + "consumes": [ + "*/*" + ], + "description": "delete collection of PriorityClass", + "operationId": "deleteSchedulingV1CollectionPriorityClass", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" + } + }, + "get": { + "consumes": [ + "*/*" + ], + "description": "list or watch objects of kind PriorityClass", + "operationId": "listSchedulingV1PriorityClass", + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClassList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" + } + }, + "parameters": [ + { + "$ref": "#/parameters/pretty-tJGM1-ng" + } + ], + "post": { + "consumes": [ + "*/*" + ], + "description": "create a PriorityClass", + "operationId": "createSchedulingV1PriorityClass", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } - ] + } }, - "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaimtemplates": { - "get": { + "/apis/scheduling.k8s.io/v1/priorityclasses/{name}": { + "delete": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2NamespacedResourceClaimTemplateList", + "description": "delete a PriorityClass", + "operationId": "deleteSchedulingV1PriorityClass", + "parameters": [ + { + "$ref": "#/parameters/body-2Y1dVQaQ" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/gracePeriodSeconds--K5HaBOS" + }, + { + "$ref": "#/parameters/ignoreStoreReadErrorWithClusterBreakingPotential-QbNkfIqj" + }, + { + "$ref": "#/parameters/orphanDependents-uRB25kX5" + }, + { + "$ref": "#/parameters/propagationPolicy-6jk3prlO" + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" } }, "401": { @@ -87816,75 +93858,32 @@ "https" ], "tags": [ - "resource_v1beta2" + "scheduling_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1beta2" + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta2/watch/namespaces/{namespace}/resourceclaimtemplates/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta2NamespacedResourceClaimTemplate", + "description": "read the specified PriorityClass", + "operationId": "readSchedulingV1PriorityClass", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" } }, "401": { @@ -87895,83 +93894,80 @@ "https" ], "tags": [ - "resource_v1beta2" + "scheduling_v1" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1beta2" + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the ResourceClaimTemplate", + "description": "name of the PriorityClass", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, - { - "$ref": "#/parameters/namespace-vgWSWtn3" - }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/resource.k8s.io/v1beta2/watch/resourceclaims": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update the specified PriorityClass", + "operationId": "patchSchedulingV1PriorityClass", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch individual changes to a list of ResourceClaim. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2ResourceClaimListForAllNamespaces", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" } }, "401": { @@ -87982,72 +93978,65 @@ "https" ], "tags": [ - "resource_v1beta2" + "scheduling_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaim", - "version": "v1beta2" + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/resource.k8s.io/v1beta2/watch/resourceclaimtemplates": { - "get": { + "put": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceClaimTemplate. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2ResourceClaimTemplateListForAllNamespaces", + "description": "replace the specified PriorityClass", + "operationId": "replaceSchedulingV1PriorityClass", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" } }, "401": { @@ -88058,58 +94047,23 @@ "https" ], "tags": [ - "resource_v1beta2" + "scheduling_v1" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceClaimTemplate", - "version": "v1beta2" - } - }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } - ] + } }, - "/apis/resource.k8s.io/v1beta2/watch/resourceslices": { + "/apis/scheduling.k8s.io/v1/watch/priorityclasses": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2ResourceSliceList", + "description": "watch individual changes to a list of PriorityClass. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1PriorityClassList", "produces": [ "application/json", "application/yaml", @@ -88134,13 +94088,13 @@ "https" ], "tags": [ - "resource_v1beta2" + "scheduling_v1" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta2" + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } }, "parameters": [ @@ -88171,6 +94125,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -88179,13 +94136,13 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/watch/resourceslices/{name}": { + "/apis/scheduling.k8s.io/v1/watch/priorityclasses/{name}": { "get": { "consumes": [ "*/*" ], - "description": "watch changes to an object of kind ResourceSlice. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta2ResourceSlice", + "description": "watch changes to an object of kind PriorityClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchSchedulingV1PriorityClass", "produces": [ "application/json", "application/yaml", @@ -88210,13 +94167,13 @@ "https" ], "tags": [ - "resource_v1beta2" + "scheduling_v1" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "ResourceSlice", - "version": "v1beta2" + "group": "scheduling.k8s.io", + "kind": "PriorityClass", + "version": "v1" } }, "parameters": [ @@ -88236,7 +94193,7 @@ "$ref": "#/parameters/limit-1NfNmdNH" }, { - "description": "name of the ResourceSlice", + "description": "name of the PriorityClass", "in": "path", "name": "name", "required": true, @@ -88255,6 +94212,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -88263,40 +94223,7 @@ } ] }, - "/apis/scheduling.k8s.io/": { - "get": { - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "description": "get information of a group", - "operationId": "getSchedulingAPIGroup", - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "schemes": [ - "https" - ], - "tags": [ - "scheduling" - ] - } - }, - "/apis/scheduling.k8s.io/v1/": { + "/apis/scheduling.k8s.io/v1alpha2/": { "get": { "consumes": [ "application/json", @@ -88305,7 +94232,7 @@ "application/cbor" ], "description": "get available resources", - "operationId": "getSchedulingV1APIResources", + "operationId": "getSchedulingV1alpha2APIResources", "produces": [ "application/json", "application/yaml", @@ -88327,17 +94254,17 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ] } }, - "/apis/scheduling.k8s.io/v1/priorityclasses": { + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/podgroups": { "delete": { "consumes": [ "*/*" ], - "description": "delete collection of PriorityClass", - "operationId": "deleteSchedulingV1CollectionPriorityClass", + "description": "delete collection of PodGroup", + "operationId": "deleteSchedulingV1alpha2CollectionNamespacedPodGroup", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -88382,6 +94309,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -88407,21 +94337,21 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, "get": { "consumes": [ "*/*" ], - "description": "list or watch objects of kind PriorityClass", - "operationId": "listSchedulingV1PriorityClass", + "description": "list or watch objects of kind PodGroup", + "operationId": "listSchedulingV1alpha2NamespacedPodGroup", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -88447,6 +94377,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -88467,7 +94400,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClassList" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupList" } }, "401": { @@ -88478,16 +94411,19 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, "parameters": [ + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -88496,15 +94432,15 @@ "consumes": [ "*/*" ], - "description": "create a PriorityClass", - "operationId": "createSchedulingV1PriorityClass", + "description": "create a PodGroup", + "operationId": "createSchedulingV1alpha2NamespacedPodGroup", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, { @@ -88535,19 +94471,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -88558,23 +94494,23 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } } }, - "/apis/scheduling.k8s.io/v1/priorityclasses/{name}": { + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/podgroups/{name}": { "delete": { "consumes": [ "*/*" ], - "description": "delete a PriorityClass", - "operationId": "deleteSchedulingV1PriorityClass", + "description": "delete a PodGroup", + "operationId": "deleteSchedulingV1alpha2NamespacedPodGroup", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -88626,21 +94562,21 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, "get": { "consumes": [ "*/*" ], - "description": "read the specified PriorityClass", - "operationId": "readSchedulingV1PriorityClass", + "description": "read the specified PodGroup", + "operationId": "readSchedulingV1alpha2NamespacedPodGroup", "produces": [ "application/json", "application/yaml", @@ -88651,7 +94587,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -88662,24 +94598,27 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, "parameters": [ { - "description": "name of the PriorityClass", + "description": "name of the PodGroup", "in": "path", "name": "name", "required": true, "type": "string", "uniqueItems": true }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, { "$ref": "#/parameters/pretty-tJGM1-ng" } @@ -88692,8 +94631,8 @@ "application/apply-patch+yaml", "application/apply-patch+cbor" ], - "description": "partially update the specified PriorityClass", - "operationId": "patchSchedulingV1PriorityClass", + "description": "partially update the specified PodGroup", + "operationId": "patchSchedulingV1alpha2NamespacedPodGroup", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -88729,13 +94668,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -88746,28 +94685,28 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, "put": { "consumes": [ "*/*" ], - "description": "replace the specified PriorityClass", - "operationId": "replaceSchedulingV1PriorityClass", + "description": "replace the specified PodGroup", + "operationId": "replaceSchedulingV1alpha2NamespacedPodGroup", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, { @@ -88798,13 +94737,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1.PriorityClass" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -88815,37 +94754,34 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } } }, - "/apis/scheduling.k8s.io/v1/watch/priorityclasses": { + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/podgroups/{name}/status": { "get": { "consumes": [ "*/*" ], - "description": "watch individual changes to a list of PriorityClass. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchSchedulingV1PriorityClassList", + "description": "read status of the specified PodGroup", + "operationId": "readSchedulingV1alpha2NamespacedPodGroupStatus", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -88856,72 +94792,83 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], - "x-kubernetes-action": "watchlist", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, "parameters": [ { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" + "description": "name of the PodGroup", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true }, { - "$ref": "#/parameters/limit-1NfNmdNH" + "$ref": "#/parameters/namespace-vgWSWtn3" }, { "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" } - ] - }, - "/apis/scheduling.k8s.io/v1/watch/priorityclasses/{name}": { - "get": { + ], + "patch": { "consumes": [ - "*/*" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml", + "application/apply-patch+cbor" + ], + "description": "partially update status of the specified PodGroup", + "operationId": "patchSchedulingV1alpha2NamespacedPodGroupStatus", + "parameters": [ + { + "$ref": "#/parameters/body-78PwaGsr" + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-7c6nTn1T" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/force-tOGGb0Yi" + } ], - "description": "watch changes to an object of kind PriorityClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchSchedulingV1PriorityClass", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch", - "application/cbor-seq" + "application/cbor" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -88932,69 +94879,48 @@ "https" ], "tags": [ - "scheduling_v1" + "scheduling_v1alpha2" ], - "x-kubernetes-action": "watch", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "PriorityClass", - "version": "v1" + "kind": "PodGroup", + "version": "v1alpha2" } }, - "parameters": [ - { - "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" - }, - { - "$ref": "#/parameters/continue-QfD61s0i" - }, - { - "$ref": "#/parameters/fieldSelector-xIcQKXFG" - }, - { - "$ref": "#/parameters/labelSelector-5Zw57w4C" - }, - { - "$ref": "#/parameters/limit-1NfNmdNH" - }, - { - "description": "name of the PriorityClass", - "in": "path", - "name": "name", - "required": true, - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/pretty-tJGM1-ng" - }, - { - "$ref": "#/parameters/resourceVersion-5WAnf1kx" - }, - { - "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" - }, - { - "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" - }, - { - "$ref": "#/parameters/timeoutSeconds-yvYezaOC" - }, - { - "$ref": "#/parameters/watch-XNNPZGbK" - } - ] - }, - "/apis/scheduling.k8s.io/v1alpha1/": { - "get": { + "put": { "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/cbor" + "*/*" + ], + "description": "replace status of the specified PodGroup", + "operationId": "replaceSchedulingV1alpha2NamespacedPodGroupStatus", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } ], - "description": "get available resources", - "operationId": "getSchedulingV1alpha1APIResources", "produces": [ "application/json", "application/yaml", @@ -89005,7 +94931,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroup" } }, "401": { @@ -89016,17 +94948,23 @@ "https" ], "tags": [ - "scheduling_v1alpha1" - ] + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } } }, - "/apis/scheduling.k8s.io/v1alpha1/namespaces/{namespace}/workloads": { + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/workloads": { "delete": { "consumes": [ "*/*" ], "description": "delete collection of Workload", - "operationId": "deleteSchedulingV1alpha1CollectionNamespacedWorkload", + "operationId": "deleteSchedulingV1alpha2CollectionNamespacedWorkload", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -89071,6 +95009,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -89096,13 +95037,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "get": { @@ -89110,7 +95051,7 @@ "*/*" ], "description": "list or watch objects of kind Workload", - "operationId": "listSchedulingV1alpha1NamespacedWorkload", + "operationId": "listSchedulingV1alpha2NamespacedWorkload", "parameters": [ { "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" @@ -89136,6 +95077,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -89156,7 +95100,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.WorkloadList" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.WorkloadList" } }, "401": { @@ -89167,13 +95111,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "parameters": [ @@ -89189,14 +95133,14 @@ "*/*" ], "description": "create a Workload", - "operationId": "createSchedulingV1alpha1NamespacedWorkload", + "operationId": "createSchedulingV1alpha2NamespacedWorkload", "parameters": [ { "in": "body", "name": "body", "required": true, "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, { @@ -89227,19 +95171,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, "401": { @@ -89250,23 +95194,23 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } } }, - "/apis/scheduling.k8s.io/v1alpha1/namespaces/{namespace}/workloads/{name}": { + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/workloads/{name}": { "delete": { "consumes": [ "*/*" ], "description": "delete a Workload", - "operationId": "deleteSchedulingV1alpha1NamespacedWorkload", + "operationId": "deleteSchedulingV1alpha2NamespacedWorkload", "parameters": [ { "$ref": "#/parameters/body-2Y1dVQaQ" @@ -89318,13 +95262,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "get": { @@ -89332,7 +95276,7 @@ "*/*" ], "description": "read the specified Workload", - "operationId": "readSchedulingV1alpha1NamespacedWorkload", + "operationId": "readSchedulingV1alpha2NamespacedWorkload", "produces": [ "application/json", "application/yaml", @@ -89343,7 +95287,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, "401": { @@ -89354,13 +95298,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "parameters": [ @@ -89388,7 +95332,7 @@ "application/apply-patch+cbor" ], "description": "partially update the specified Workload", - "operationId": "patchSchedulingV1alpha1NamespacedWorkload", + "operationId": "patchSchedulingV1alpha2NamespacedWorkload", "parameters": [ { "$ref": "#/parameters/body-78PwaGsr" @@ -89424,13 +95368,82 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "put": { + "consumes": [ + "*/*" + ], + "description": "replace the specified Workload", + "operationId": "replaceSchedulingV1alpha2NamespacedWorkload", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/fieldManager-Qy4HdaTW" + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "type": "string", + "uniqueItems": true + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.Workload" } }, "401": { @@ -89441,65 +95454,198 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], - "x-kubernetes-action": "patch", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } - }, - "put": { + } + }, + "/apis/scheduling.k8s.io/v1alpha2/podgroups": { + "get": { "consumes": [ "*/*" ], - "description": "replace the specified Workload", - "operationId": "replaceSchedulingV1alpha1NamespacedWorkload", - "parameters": [ - { - "in": "body", - "name": "body", - "required": true, + "description": "list or watch objects of kind PodGroup", + "operationId": "listSchedulingV1alpha2PodGroupForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupList" } }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "type": "string", - "uniqueItems": true - }, - { - "$ref": "#/parameters/fieldManager-Qy4HdaTW" - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "type": "string", - "uniqueItems": true + "401": { + "description": "Unauthorized" } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/podgroups": { + "get": { + "consumes": [ + "*/*" ], + "description": "watch individual changes to a list of PodGroup. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1alpha2NamespacedPodGroupList", "produces": [ "application/json", "application/yaml", "application/vnd.kubernetes.protobuf", - "application/cbor" + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "201": { - "description": "Created", + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/podgroups/{name}": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch changes to an object of kind PodGroup. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchSchedulingV1alpha2NamespacedPodGroup", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.Workload" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "401": { @@ -89510,23 +95656,72 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" + "kind": "PodGroup", + "version": "v1alpha2" } - } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "description": "name of the PodGroup", + "in": "path", + "name": "name", + "required": true, + "type": "string", + "uniqueItems": true + }, + { + "$ref": "#/parameters/namespace-vgWSWtn3" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] }, - "/apis/scheduling.k8s.io/v1alpha1/watch/namespaces/{namespace}/workloads": { + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/workloads": { "get": { "consumes": [ "*/*" ], "description": "watch individual changes to a list of Workload. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchSchedulingV1alpha1NamespacedWorkloadList", + "operationId": "watchSchedulingV1alpha2NamespacedWorkloadList", "produces": [ "application/json", "application/yaml", @@ -89551,13 +95746,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "parameters": [ @@ -89591,6 +95786,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -89599,13 +95797,13 @@ } ] }, - "/apis/scheduling.k8s.io/v1alpha1/watch/namespaces/{namespace}/workloads/{name}": { + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/workloads/{name}": { "get": { "consumes": [ "*/*" ], "description": "watch changes to an object of kind Workload. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchSchedulingV1alpha1NamespacedWorkload", + "operationId": "watchSchedulingV1alpha2NamespacedWorkload", "produces": [ "application/json", "application/yaml", @@ -89630,13 +95828,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "parameters": [ @@ -89678,6 +95876,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -89686,13 +95887,92 @@ } ] }, - "/apis/scheduling.k8s.io/v1alpha1/watch/workloads": { + "/apis/scheduling.k8s.io/v1alpha2/watch/podgroups": { + "get": { + "consumes": [ + "*/*" + ], + "description": "watch individual changes to a list of PodGroup. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1alpha2PodGroupListForAllNamespaces", + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/cbor", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch", + "application/cbor-seq" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J" + }, + { + "$ref": "#/parameters/continue-QfD61s0i" + }, + { + "$ref": "#/parameters/fieldSelector-xIcQKXFG" + }, + { + "$ref": "#/parameters/labelSelector-5Zw57w4C" + }, + { + "$ref": "#/parameters/limit-1NfNmdNH" + }, + { + "$ref": "#/parameters/pretty-tJGM1-ng" + }, + { + "$ref": "#/parameters/resourceVersion-5WAnf1kx" + }, + { + "$ref": "#/parameters/resourceVersionMatch-t8XhRHeC" + }, + { + "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" + }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, + { + "$ref": "#/parameters/timeoutSeconds-yvYezaOC" + }, + { + "$ref": "#/parameters/watch-XNNPZGbK" + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/workloads": { "get": { "consumes": [ "*/*" ], "description": "watch individual changes to a list of Workload. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchSchedulingV1alpha1WorkloadListForAllNamespaces", + "operationId": "watchSchedulingV1alpha2WorkloadListForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -89717,13 +95997,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "parameters": [ @@ -89754,6 +96034,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -89762,13 +96045,13 @@ } ] }, - "/apis/scheduling.k8s.io/v1alpha1/workloads": { + "/apis/scheduling.k8s.io/v1alpha2/workloads": { "get": { "consumes": [ "*/*" ], "description": "list or watch objects of kind Workload", - "operationId": "listSchedulingV1alpha1WorkloadForAllNamespaces", + "operationId": "listSchedulingV1alpha2WorkloadForAllNamespaces", "produces": [ "application/json", "application/yaml", @@ -89782,7 +96065,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha1.WorkloadList" + "$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.WorkloadList" } }, "401": { @@ -89793,13 +96076,13 @@ "https" ], "tags": [ - "scheduling_v1alpha1" + "scheduling_v1alpha2" ], "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "scheduling.k8s.io", "kind": "Workload", - "version": "v1alpha1" + "version": "v1alpha2" } }, "parameters": [ @@ -89830,6 +96113,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -89957,6 +96243,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -90022,6 +96311,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -90451,6 +96743,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -90516,6 +96811,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -90962,6 +97260,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -91021,6 +97322,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -91086,6 +97390,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -91521,6 +97828,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -91586,6 +97896,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -92015,6 +98328,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -92080,6 +98396,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -92700,6 +99019,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -92765,6 +99087,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93211,6 +99536,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93295,6 +99623,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93371,6 +99702,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93455,6 +99789,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93531,6 +99868,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93610,6 +99950,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93697,6 +100040,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93773,6 +100119,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93857,6 +100206,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -93933,6 +100285,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94017,6 +100372,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94093,6 +100451,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94177,6 +100538,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94271,6 +100635,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -94336,6 +100703,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94782,6 +101152,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94866,6 +101239,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -94993,6 +101369,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" } @@ -95058,6 +101437,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -95695,6 +102077,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, @@ -95779,6 +102164,9 @@ { "$ref": "#/parameters/sendInitialEvents-rLXlEK_k" }, + { + "$ref": "#/parameters/shardSelector-Kgyki_3_" + }, { "$ref": "#/parameters/timeoutSeconds-yvYezaOC" }, diff --git a/api/openapi-spec/v3/api__v1_openapi.json b/api/openapi-spec/v3/api__v1_openapi.json index 8125c8ce3fc03..fc9fab5f1bc45 100644 --- a/api/openapi-spec/v3/api__v1_openapi.json +++ b/api/openapi-spec/v3/api__v1_openapi.json @@ -5,19 +5,19 @@ "description": "BoundObjectReference is a reference to an object that a token is bound to.", "properties": { "apiVersion": { - "description": "API version of the referent.", + "description": "apiVersion is API version of the referent.", "type": "string" }, "kind": { - "description": "Kind of the referent. Valid kinds are 'Pod' and 'Secret'.", + "description": "kind of the referent. Valid kinds are 'Pod' and 'Secret'.", "type": "string" }, "name": { - "description": "Name of the referent.", + "description": "name of the referent.", "type": "string" }, "uid": { - "description": "UID of the referent.", + "description": "uid of the referent.", "type": "string" } }, @@ -41,7 +41,7 @@ } ], "default": {}, - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "allOf": [ @@ -50,7 +50,7 @@ } ], "default": {}, - "description": "Spec holds information about the request being evaluated" + "description": "spec holds information about the request being evaluated" }, "status": { "allOf": [ @@ -59,12 +59,9 @@ } ], "default": {}, - "description": "Status is filled in by the server and indicates whether the token can be authenticated." + "description": "status is filled in by the server and indicates whether the token can be authenticated." } }, - "required": [ - "spec" - ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -78,7 +75,7 @@ "description": "TokenRequestSpec contains client provided parameters of a token request.", "properties": { "audiences": { - "description": "Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", + "description": "audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", "items": { "default": "", "type": "string" @@ -92,17 +89,14 @@ "$ref": "#/components/schemas/io.k8s.api.authentication.v1.BoundObjectReference" } ], - "description": "BoundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation." + "description": "boundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation." }, "expirationSeconds": { - "description": "ExpirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", + "description": "expirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", "format": "int64", "type": "integer" } }, - "required": [ - "audiences" - ], "type": "object" }, "io.k8s.api.authentication.v1.TokenRequestStatus": { @@ -114,18 +108,14 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "ExpirationTimestamp is the time of expiration of the returned token." + "description": "expirationTimestamp is the time of expiration of the returned token." }, "token": { "default": "", - "description": "Token is the opaque bearer token.", + "description": "token is the opaque bearer token.", "type": "string" } }, - "required": [ - "token", - "expirationTimestamp" - ], "type": "object" }, "io.k8s.api.autoscaling.v1.Scale": { @@ -3154,6 +3144,19 @@ }, "type": "object" }, + "io.k8s.api.core.v1.ImageVolumeStatus": { + "description": "ImageVolumeStatus represents the image-based volume status.", + "properties": { + "imageRef": { + "description": "ImageRef is the digest of the image used for this volume. It should have a value that's similar to the pod's status.containerStatuses[i].imageID. The ImageRef length should not exceed 256 characters.", + "type": "string" + } + }, + "required": [ + "imageRef" + ], + "type": "object" + }, "io.k8s.api.core.v1.KeyToPath": { "description": "Maps a string key to a path within a volume.", "properties": { @@ -3819,6 +3822,37 @@ }, "type": "object" }, + "io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus": { + "description": "NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA.", + "properties": { + "containers": { + "description": "Containers lists the names of all containers in this pod that reference the claim.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "resourceClaimName": { + "default": "", + "description": "ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation.", + "type": "string" + }, + "resources": { + "additionalProperties": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity" + }, + "description": "Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim.", + "type": "object" + } + }, + "required": [ + "resourceClaimName", + "resources" + ], + "type": "object" + }, "io.k8s.api.core.v1.NodeCondition": { "description": "NodeCondition contains condition information for a node.", "properties": { @@ -5038,7 +5072,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.PortworxVolumeSource" } ], - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "quobyte": { "allOf": [ @@ -5375,7 +5409,7 @@ "type": "string" }, "observedGeneration": { - "description": "If set, this represents the .metadata.generation that the pod condition was set based upon. The PodObservedGenerationTracking feature gate must be enabled to use this field.", + "description": "If set, this represents the .metadata.generation that the pod condition was set based upon.", "format": "int64", "type": "integer" }, @@ -5567,7 +5601,7 @@ "type": "object" }, "io.k8s.api.core.v1.PodResourceClaim": { - "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and this Pod belongs to a PodGroup, a PodResourceClaim is matched to a PodGroupResourceClaim if all of their fields are equal (Name, ResourceClaimName, and ResourceClaimTemplateName). A matched claim references a single ResourceClaim shared across all Pods in the PodGroup, reserved for the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual Pods.", "properties": { "name": { "default": "", @@ -5579,7 +5613,7 @@ "type": "string" }, "resourceClaimTemplateName": { - "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and the pod belongs to a PodGroup that defines a PodGroupResourceClaim with the same Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the ResourceClaim generated for the PodGroup. All pods in the group that define an equivalent PodResourceClaim matching the PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same generated ResourceClaim. ResourceClaims generated for a PodGroup are owned by the PodGroup and their lifecycles are tied to the PodGroup instead of any individual pod.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", "type": "string" } }, @@ -5597,7 +5631,7 @@ "type": "string" }, "resourceClaimName": { - "description": "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod. If this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", + "description": "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod.\n\nWhen the DRAWorkloadResourceClaims feature is enabled and the corresponding PodResourceClaim matches a PodGroupResourceClaim made by the Pod's PodGroup, then this is the name of the ResourceClaim generated and reserved for the PodGroup.\n\nIf this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", "type": "string" } }, @@ -5620,6 +5654,23 @@ ], "type": "object" }, + "io.k8s.api.core.v1.PodSchedulingGroup": { + "description": "PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics. Exactly one field must be specified.", + "properties": { + "podGroupName": { + "description": "PodGroupName specifies the name of the standalone PodGroup object that represents the runtime instance of this group. Must be a DNS subdomain.", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "podGroupName": "PodGroupName" + } + } + ] + }, "io.k8s.api.core.v1.PodSecurityContext": { "description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", "properties": { @@ -5815,7 +5866,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { @@ -5972,6 +6023,14 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "schedulingGroup": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGroup" + } + ], + "description": "SchedulingGroup provides a reference to the immediate scheduling runtime grouping object that this Pod belongs to. This field is used by the scheduler to identify the group and apply the correct group scheduling policies. The association with a group also impacts other lifecycle aspects of a Pod that are relevant in a wider context of scheduling like preemption, resource attachment, etc. If not specified, the Pod is treated as a single unit in all of these aspects. The group object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a group object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." + }, "securityContext": { "allOf": [ { @@ -6054,14 +6113,6 @@ "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" - }, - "workloadRef": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.core.v1.WorkloadReference" - } - ], - "description": "WorkloadRef provides a reference to the Workload object that this Pod belongs to. This field is used by the scheduler to identify the PodGroup and apply the correct group scheduling policies. The Workload object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a Workload object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." } }, "required": [ @@ -6167,6 +6218,19 @@ "description": "A human readable message indicating details about why the pod is in this condition.", "type": "string" }, + "nodeAllocatableResourceClaimStatuses": { + "description": "NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources that were allocated for this pod through DRA claims. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "nominatedNodeName": { "description": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", "type": "string" @@ -6938,6 +7002,10 @@ "description": "Health of the resource. can be one of:\n - Healthy: operates as normal\n - Unhealthy: reported unhealthy. We consider this a temporary health issue\n since we do not have a mechanism today to distinguish\n temporary and permanent issues.\n - Unknown: The status cannot be determined.\n For example, Device Plugin got unregistered and hasn't been re-registered since.\n\nIn future we may want to introduce the PermanentlyUnhealthy Status.", "type": "string" }, + "message": { + "description": "Message provides human-readable context for Health (e.g. \"ECC error count exceeded threshold\"). This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise.", + "type": "string" + }, "resourceID": { "default": "", "description": "ResourceID is the unique identifier of the resource. See the ResourceID type for more information.", @@ -7614,7 +7682,7 @@ "type": "boolean" }, "procMount": { - "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. Note that this field cannot be set when spec.os.name is windows.", "type": "string" }, "readOnlyRootFilesystem": { @@ -8537,7 +8605,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeSource" } ], - "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." + "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." }, "iscsi": { "allOf": [ @@ -8582,7 +8650,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.PortworxVolumeSource" } ], - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "projected": { "allOf": [ @@ -8726,6 +8794,14 @@ "recursiveReadOnly": { "description": "RecursiveReadOnly must be set to Disabled, Enabled, or unspecified (for non-readonly mounts). An IfPossible value in the original VolumeMount must be translated to Disabled or Enabled, depending on the mount result.", "type": "string" + }, + "volumeStatus": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.core.v1.VolumeStatus" + } + ], + "description": "volumeStatus represents volume-type-specific status about the mounted volume." } }, "required": [ @@ -8822,6 +8898,20 @@ }, "type": "object" }, + "io.k8s.api.core.v1.VolumeStatus": { + "description": "VolumeStatus represents the status of a mounted volume. At most one of its members must be specified.", + "properties": { + "image": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeStatus" + } + ], + "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine." + } + }, + "type": "object" + }, "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource": { "description": "Represents a vSphere volume resource.", "properties": { @@ -8895,30 +8985,6 @@ }, "type": "object" }, - "io.k8s.api.core.v1.WorkloadReference": { - "description": "WorkloadReference identifies the Workload object and PodGroup membership that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics.", - "properties": { - "name": { - "default": "", - "description": "Name defines the name of the Workload object this Pod belongs to. Workload must be in the same namespace as the Pod. If it doesn't match any existing Workload, the Pod will remain unschedulable until a Workload object is created and observed by the kube-scheduler. It must be a DNS subdomain.", - "type": "string" - }, - "podGroup": { - "default": "", - "description": "PodGroup is the name of the PodGroup within the Workload that this Pod belongs to. If it doesn't match any existing PodGroup within the Workload, the Pod will remain unschedulable until the Workload object is recreated and observed by the kube-scheduler. It must be a DNS label.", - "type": "string" - }, - "podGroupReplicaKey": { - "description": "PodGroupReplicaKey specifies the replica key of the PodGroup to which this Pod belongs. It is used to distinguish pods belonging to different replicas of the same pod group. The pod group policy is applied separately to each replica. When set, it must be a DNS label.", - "type": "string" - } - }, - "required": [ - "name", - "podGroup" - ], - "type": "object" - }, "io.k8s.api.policy.v1.Eviction": { "description": "Eviction evicts a pod from its node subject to certain policies and safety constraints. This is a subresource of Pod. A request to cause such an eviction is created by POSTing to .../pods//evictions.", "properties": { @@ -9180,311 +9246,6 @@ "group": "", "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -9568,6 +9329,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -9791,6 +9560,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -9935,311 +9718,6 @@ "group": "", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -10456,232 +9934,250 @@ } }, { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ] - }, - "/api/v1/componentstatuses/{name}": { - "get": { - "description": "read the specified ComponentStatus", - "operationId": "readCoreV1ComponentStatus", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "core_v1" - ], - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "kind": "ComponentStatus", - "version": "v1" - } - }, - "parameters": [ - { - "description": "name of the ComponentStatus", - "in": "path", - "name": "name", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ] - }, - "/api/v1/configmaps": { - "get": { - "description": "list or watch objects of kind ConfigMap", - "operationId": "listCoreV1ConfigMapForAllNamespaces", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - }, - "application/cbor-seq": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "core_v1" - ], - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "kind": "ConfigMap", - "version": "v1" - } - }, - "parameters": [ - { - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "in": "query", - "name": "allowWatchBookmarks", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/api/v1/componentstatuses/{name}": { + "get": { + "description": "read the specified ComponentStatus", + "operationId": "readCoreV1ComponentStatus", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ComponentStatus" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "core_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "", + "kind": "ComponentStatus", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the ComponentStatus", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/api/v1/configmaps": { + "get": { + "description": "list or watch objects of kind ConfigMap", + "operationId": "listCoreV1ConfigMapForAllNamespaces", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.core.v1.ConfigMapList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "core_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "", + "kind": "ConfigMap", + "version": "v1" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", "in": "query", - "name": "resourceVersionMatch", + "name": "shardSelector", "schema": { "type": "string", "uniqueItems": true } }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10843,6 +10339,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11004,6 +10509,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11165,6 +10679,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11262,6 +10785,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11746,6 +11278,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11881,6 +11422,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -12665,6 +12215,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -12800,6 +12359,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -13584,6 +13152,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -13719,6 +13296,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14503,6 +14089,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14638,6 +14233,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15422,6 +15026,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15557,6 +15170,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16660,6 +16282,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16795,6 +16426,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -19840,6 +19480,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -19975,6 +19624,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -20759,6 +20417,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -20894,6 +20561,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -22316,6 +21992,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -22451,6 +22136,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -23554,6 +23248,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -23689,6 +23392,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -24473,6 +24185,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -24608,6 +24329,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -25555,6 +25285,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -25690,6 +25429,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -28131,6 +27879,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -28266,6 +28023,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -29815,6 +29581,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -29948,6 +29723,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -30083,6 +29867,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -31184,6 +30977,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -31345,6 +31147,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -31506,6 +31317,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -31667,6 +31487,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -31828,6 +31657,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -31989,6 +31827,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -32150,6 +31997,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -32311,6 +32167,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -32472,6 +32337,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -32633,6 +32507,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -32794,6 +32677,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -32955,6 +32847,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -33126,6 +33027,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -33307,6 +33217,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -33478,6 +33397,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -33659,6 +33587,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -33830,6 +33767,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -34011,6 +33957,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -34182,6 +34137,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -34363,6 +34327,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -34534,6 +34507,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -34715,6 +34697,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -34886,6 +34877,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -35067,6 +35067,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -35238,6 +35247,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -35419,6 +35437,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -35590,6 +35617,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -35771,6 +35807,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -35942,6 +35987,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -36123,6 +36177,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -36294,6 +36357,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -36475,6 +36547,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -36646,6 +36727,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -36827,6 +36917,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -36998,6 +37097,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -37179,6 +37287,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -37350,6 +37467,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -37511,6 +37637,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -37682,6 +37817,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -37843,6 +37987,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38004,6 +38157,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38175,6 +38337,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38336,6 +38507,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38497,6 +38677,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38658,6 +38847,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38819,6 +39017,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -38980,6 +39187,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -39141,6 +39357,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -39302,6 +39527,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1_openapi.json index cb41191363958..1f5018c8fcd31 100644 --- a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1_openapi.json @@ -1,6 +1,16 @@ { "components": { "schemas": { + "io.k8s.api.admissionregistration.v1.ApplyConfiguration": { + "description": "ApplyConfiguration defines the desired configuration values of an object.", + "properties": { + "expression": { + "description": "expression will be evaluated by CEL to create an apply configuration. ref: https://github.com/google/cel-spec\n\nApply configurations are declared in CEL using object initialization. For example, this CEL expression returns an apply configuration to set a single field:\n\n\tObject{\n\t spec: Object.spec{\n\t serviceAccountName: \"example\"\n\t }\n\t}\n\nApply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of values not included in the apply configuration.\n\nCEL expressions have access to the object types needed to create apply configurations:\n\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.admissionregistration.v1.AuditAnnotation": { "description": "AuditAnnotation describes how to produce an audit annotation for an API request.", "properties": { @@ -26,12 +36,12 @@ "properties": { "fieldRef": { "default": "", - "description": "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + "description": "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", "type": "string" }, "warning": { "default": "", - "description": "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + "description": "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", "type": "string" } }, @@ -41,17 +51,27 @@ ], "type": "object" }, + "io.k8s.api.admissionregistration.v1.JSONPatch": { + "description": "JSONPatch defines a JSON Patch.", + "properties": { + "expression": { + "description": "expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). ref: https://github.com/google/cel-spec\n\nexpression must return an array of JSONPatch values.\n\nFor example, this CEL expression returns a JSON patch to conditionally modify a value:\n\n\t [\n\t JSONPatch{op: \"test\", path: \"/spec/example\", value: \"Red\"},\n\t JSONPatch{op: \"replace\", path: \"/spec/example\", value: \"Green\"}\n\t ]\n\nTo define an object for the patch value, use Object types. For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/spec/selector\",\n\t value: Object.spec.selector{matchLabels: {\"environment\": \"test\"}}\n\t }\n\t ]\n\nTo use strings containing '/' and '~' as JSONPatch path keys, use \"jsonpatch.escapeKey\". For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/metadata/labels/\" + jsonpatch.escapeKey(\"example.com/environment\"),\n\t value: \"test\"\n\t },\n\t ]\n\nCEL expressions have access to the types needed to create JSON patches and objects:\n\n- 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'.\n See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string,\n integer, array, map or object. If set, the 'path' and 'from' fields must be set to a\n [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL\n function may be used to escape path keys containing '/' and '~'.\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nCEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) as well as:\n\n- 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively).\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.admissionregistration.v1.MatchCondition": { "description": "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.", "properties": { "expression": { "default": "", - "description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", "type": "string" }, "name": { "default": "", - "description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "description": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", "type": "string" } }, @@ -65,7 +85,7 @@ "description": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "properties": { "excludeResourceRules": { - "description": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "description": "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "items": { "allOf": [ { @@ -87,7 +107,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "allOf": [ @@ -95,10 +115,10 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "resourceRules": { - "description": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + "description": "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", "items": { "allOf": [ { @@ -114,11 +134,279 @@ "type": "object", "x-kubernetes-map-type": "atomic" }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy": { + "description": "MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ], + "default": {}, + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec" + } + ], + "default": {}, + "description": "spec defines the desired behavior of the MutatingAdmissionPolicy." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding": { + "description": "MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding. Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget).\n\nAdding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ], + "default": {}, + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec" + } + ], + "default": {}, + "description": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList": { + "description": "MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of PolicyBinding.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" + } + ], + "default": {} + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBindingList", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec": { + "description": "MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding.", + "properties": { + "matchResources": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchResources" + } + ], + "description": "matchResources limits what resources match this binding and may be mutated by it. Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and matchConditions before the resource may be mutated. When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints and matchConditions must match for the resource to be mutated. Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT." + }, + "paramRef": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ParamRef" + } + ], + "description": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param." + }, + "policyName": { + "description": "policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList": { + "description": "MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of ValidatingAdmissionPolicy.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" + } + ], + "default": {} + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyList", + "version": "v1" + } + ] + }, + "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec": { + "description": "MutatingAdmissionPolicySpec defines the desired behavior of the admission policy.", + "properties": { + "failurePolicy": { + "description": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if paramKind refers to a non-existent Kind. A binding is invalid if paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", + "type": "string" + }, + "matchConditions": { + "description": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the matchConstraints. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchCondition" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "matchConstraints": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchResources" + } + ], + "description": "matchConstraints specifies what resources this policy is designed to validate. The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT. Required." + }, + "mutations": { + "description": "mutations contain operations to perform on matching objects. mutations may not be empty; a minimum of one mutation is required. mutations are evaluated in order, and are reinvoked according to the reinvocationPolicy. The mutations of a policy are invoked for each binding of this policy and reinvocation of mutations occurs on a per binding basis.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.Mutation" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "paramKind": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ParamKind" + } + ], + "description": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null." + }, + "reinvocationPolicy": { + "description": "reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: These mutations will not be called more than once per binding in a single admission evaluation.\n\nIfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only reinvoked when mutations change the object after this mutation is invoked. Required.", + "type": "string" + }, + "variables": { + "description": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except matchConditions because matchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, variables must be sorted by the order of first appearance and acyclic.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.Variable" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, "io.k8s.api.admissionregistration.v1.MutatingWebhook": { "description": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.", "properties": { "admissionReviewVersions": { - "description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + "description": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", "items": { "default": "", "type": "string" @@ -133,14 +421,14 @@ } ], "default": {}, - "description": "ClientConfig defines how to communicate with the hook. Required" + "description": "clientConfig defines how to communicate with the hook. Required" }, "failurePolicy": { - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", + "description": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", "type": "string" }, "matchConditions": { - "description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "description": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", "items": { "allOf": [ { @@ -163,7 +451,7 @@ }, "name": { "default": "", - "description": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "description": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", "type": "string" }, "namespaceSelector": { @@ -172,7 +460,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "allOf": [ @@ -180,14 +468,14 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "reinvocationPolicy": { "description": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: the webhook will not be called more than once in a single admission evaluation.\n\nIfNeeded: the webhook will be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call. Webhooks that specify this option *must* be idempotent, able to process objects they previously admitted. Note: * the number of additional invocations is not guaranteed to be exactly one. * if additional invocations result in further modifications to the object, webhooks are not guaranteed to be invoked again. * webhooks that use this option may be reordered to minimize the number of additional invocations. * to validate an object after all mutations are guaranteed complete, use a validating admission webhook instead.\n\nDefaults to \"Never\".", "type": "string" }, "rules": { - "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "description": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", "items": { "allOf": [ { @@ -200,11 +488,11 @@ "x-kubernetes-list-type": "atomic" }, "sideEffects": { - "description": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", + "description": "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", "type": "string" }, "timeoutSeconds": { - "description": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + "description": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", "format": "int32", "type": "integer" } @@ -235,10 +523,10 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "webhooks": { - "description": "Webhooks is a list of webhooks and the affected resources and operations.", + "description": "webhooks is a list of webhooks and the affected resources and operations.", "items": { "allOf": [ { @@ -295,7 +583,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -310,20 +598,50 @@ } ] }, - "io.k8s.api.admissionregistration.v1.NamedRuleWithOperations": { - "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", + "io.k8s.api.admissionregistration.v1.Mutation": { + "description": "Mutation specifies the CEL expression which is used to apply the Mutation.", "properties": { - "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", - "items": { - "default": "", - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "applyConfiguration": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ApplyConfiguration" + } + ], + "description": "applyConfiguration defines the desired configuration values of an object. The configuration is applied to the admission object using [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). A CEL expression is used to create apply configuration." + }, + "jsonPatch": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.JSONPatch" + } + ], + "description": "jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. A CEL expression is used to create the JSON patch." + }, + "patchType": { + "default": "", + "description": "patchType indicates the patch strategy used. Allowed values are \"ApplyConfiguration\" and \"JSONPatch\". Required.", + "type": "string" + } + }, + "required": [ + "patchType" + ], + "type": "object" + }, + "io.k8s.api.admissionregistration.v1.NamedRuleWithOperations": { + "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", + "properties": { + "apiGroups": { + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "apiVersions": { + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -332,7 +650,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -341,7 +659,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", "items": { "default": "", "type": "string" @@ -350,7 +668,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "default": "", "type": "string" @@ -370,11 +688,11 @@ "description": "ParamKind is a tuple of Group Kind and Version.", "properties": { "apiVersion": { - "description": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "description": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", "type": "string" }, "kind": { - "description": "Kind is the API kind the resources belong to. Required.", + "description": "kind is the API kind the resources belong to. Required.", "type": "string" } }, @@ -393,7 +711,7 @@ "type": "string" }, "parameterNotFoundAction": { - "description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + "description": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", "type": "string" }, "selector": { @@ -412,7 +730,7 @@ "description": "RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -421,7 +739,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -430,7 +748,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -439,7 +757,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "default": "", "type": "string" @@ -459,20 +777,20 @@ "properties": { "name": { "default": "", - "description": "`name` is the name of the service. Required", + "description": "name is the name of the service. Required", "type": "string" }, "namespace": { "default": "", - "description": "`namespace` is the namespace of the service. Required", + "description": "namespace is the namespace of the service. Required", "type": "string" }, "path": { - "description": "`path` is an optional URL path which will be sent in any request to this service.", + "description": "path is an optional URL path which will be sent in any request to this service.", "type": "string" }, "port": { - "description": "If specified, the port on the service that hosting webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", + "description": "port is the port on the service that hosts the webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", "format": "int32", "type": "integer" } @@ -487,7 +805,7 @@ "description": "TypeChecking contains results of type checking the expressions in the ValidatingAdmissionPolicy", "properties": { "expressionWarnings": { - "description": "The type checking warnings for each expression.", + "description": "expressionWarnings contains the type checking warnings for each expression.", "items": { "allOf": [ { @@ -520,7 +838,7 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "allOf": [ @@ -529,7 +847,7 @@ } ], "default": {}, - "description": "Specification of the desired behavior of the ValidatingAdmissionPolicy." + "description": "spec defines the desired behavior of the ValidatingAdmissionPolicy." }, "status": { "allOf": [ @@ -538,7 +856,7 @@ } ], "default": {}, - "description": "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only." + "description": "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only." } }, "type": "object", @@ -568,7 +886,7 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "allOf": [ @@ -577,9 +895,12 @@ } ], "default": {}, - "description": "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding." + "description": "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding." } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -619,7 +940,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -643,7 +964,7 @@ "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchResources" } ], - "description": "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required." + "description": "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required." }, "paramRef": { "allOf": [ @@ -654,7 +975,7 @@ "description": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param." }, "policyName": { - "description": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "description": "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", "type": "string" }, "validationActions": { @@ -667,6 +988,10 @@ "x-kubernetes-list-type": "set" } }, + "required": [ + "policyName", + "validationActions" + ], "type": "object" }, "io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList": { @@ -699,7 +1024,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -735,7 +1060,7 @@ "type": "string" }, "matchConditions": { - "description": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "description": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", "items": { "allOf": [ { @@ -758,7 +1083,7 @@ "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchResources" } ], - "description": "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required." + "description": "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required." }, "paramKind": { "allOf": [ @@ -766,10 +1091,10 @@ "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ParamKind" } ], - "description": "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null." + "description": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null." }, "validations": { - "description": "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + "description": "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", "items": { "allOf": [ { @@ -782,7 +1107,7 @@ "x-kubernetes-list-type": "atomic" }, "variables": { - "description": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + "description": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", "items": { "allOf": [ { @@ -806,7 +1131,7 @@ "description": "ValidatingAdmissionPolicyStatus represents the status of an admission validation policy.", "properties": { "conditions": { - "description": "The conditions represent the latest available observations of a policy's current state.", + "description": "conditions represent the latest available observations of a policy's current state.", "items": { "allOf": [ { @@ -822,7 +1147,7 @@ "x-kubernetes-list-type": "map" }, "observedGeneration": { - "description": "The generation observed by the controller.", + "description": "observedGeneration is the generation observed by the controller.", "format": "int64", "type": "integer" }, @@ -832,7 +1157,7 @@ "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.TypeChecking" } ], - "description": "The results of type checking for each expression. Presence of this field indicates the completion of the type checking." + "description": "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking." } }, "type": "object" @@ -841,7 +1166,7 @@ "description": "ValidatingWebhook describes an admission webhook and the resources and operations it applies to.", "properties": { "admissionReviewVersions": { - "description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + "description": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", "items": { "default": "", "type": "string" @@ -856,14 +1181,14 @@ } ], "default": {}, - "description": "ClientConfig defines how to communicate with the hook. Required" + "description": "clientConfig defines how to communicate with the hook. Required" }, "failurePolicy": { - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", + "description": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", "type": "string" }, "matchConditions": { - "description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "description": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", "items": { "allOf": [ { @@ -886,7 +1211,7 @@ }, "name": { "default": "", - "description": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "description": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", "type": "string" }, "namespaceSelector": { @@ -895,7 +1220,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "allOf": [ @@ -903,10 +1228,10 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "rules": { - "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "description": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", "items": { "allOf": [ { @@ -919,11 +1244,11 @@ "x-kubernetes-list-type": "atomic" }, "sideEffects": { - "description": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", + "description": "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", "type": "string" }, "timeoutSeconds": { - "description": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + "description": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", "format": "int32", "type": "integer" } @@ -954,10 +1279,10 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "webhooks": { - "description": "Webhooks is a list of webhooks and the affected resources and operations.", + "description": "webhooks is a list of webhooks and the affected resources and operations.", "items": { "allOf": [ { @@ -1014,7 +1339,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -1034,11 +1359,11 @@ "properties": { "expression": { "default": "", - "description": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", "type": "string" }, "message": { - "description": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + "description": "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", "type": "string" }, "messageExpression": { @@ -1046,7 +1371,7 @@ "type": "string" }, "reason": { - "description": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + "description": "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", "type": "string" } }, @@ -1060,12 +1385,12 @@ "properties": { "expression": { "default": "", - "description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "description": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", "type": "string" }, "name": { "default": "", - "description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "description": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", "type": "string" } }, @@ -1080,7 +1405,7 @@ "description": "WebhookClientConfig contains the information to make a TLS connection with the webhook", "properties": { "caBundle": { - "description": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + "description": "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", "format": "byte", "type": "string" }, @@ -1090,10 +1415,10 @@ "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ServiceReference" } ], - "description": "`service` is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`." + "description": "service is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`." }, "url": { - "description": "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + "description": "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", "type": "string" } }, @@ -1312,310 +1637,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "admissionregistration.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1699,6 +1724,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1917,6 +1950,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -2063,309 +2110,9 @@ "version": "v1" }, { - "group": "admission.k8s.io", + "group": "admissionregistration.k8s.io", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -2428,10 +2175,10 @@ ] } }, - "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicies": { "delete": { - "description": "delete collection of MutatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1CollectionMutatingWebhookConfiguration", + "description": "delete collection of MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1CollectionMutatingAdmissionPolicy", "parameters": [ { "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", @@ -2541,6 +2288,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2596,13 +2352,13 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, "get": { - "description": "list or watch objects of kind MutatingWebhookConfiguration", - "operationId": "listAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "list or watch objects of kind MutatingAdmissionPolicy", + "operationId": "listAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", @@ -2676,6 +2432,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2700,37 +2465,37 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" } } }, @@ -2746,7 +2511,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, @@ -2762,8 +2527,8 @@ } ], "post": { - "description": "create a MutatingWebhookConfiguration", - "operationId": "createAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "create a MutatingAdmissionPolicy", + "operationId": "createAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -2797,7 +2562,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -2808,22 +2573,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -2833,22 +2598,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -2858,22 +2623,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -2889,15 +2654,15 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/{name}": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicies/{name}": { "delete": { - "description": "delete a MutatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "delete a MutatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3015,34 +2780,34 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, "get": { - "description": "read the specified MutatingWebhookConfiguration", - "operationId": "readAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "read the specified MutatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1MutatingAdmissionPolicy", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -3058,13 +2823,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, "parameters": [ { - "description": "name of the MutatingWebhookConfiguration", + "description": "name of the MutatingAdmissionPolicy", "in": "path", "name": "name", "required": true, @@ -3084,8 +2849,8 @@ } ], "patch": { - "description": "partially update the specified MutatingWebhookConfiguration", - "operationId": "patchAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "partially update the specified MutatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3159,22 +2924,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -3184,22 +2949,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -3215,13 +2980,13 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } }, "put": { - "description": "replace the specified MutatingWebhookConfiguration", - "operationId": "replaceAdmissionregistrationV1MutatingWebhookConfiguration", + "description": "replace the specified MutatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1MutatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3255,7 +3020,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -3266,22 +3031,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -3291,22 +3056,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" } } }, @@ -3322,15 +3087,15 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "MutatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicybindings": { "delete": { - "description": "delete collection of ValidatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicy", + "description": "delete collection of MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1CollectionMutatingAdmissionPolicyBinding", "parameters": [ { "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", @@ -3440,6 +3205,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3495,13 +3269,13 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, "get": { - "description": "list or watch objects of kind ValidatingAdmissionPolicy", - "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "list or watch objects of kind MutatingAdmissionPolicyBinding", + "operationId": "listAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", @@ -3575,6 +3349,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3599,37 +3382,37 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" } } }, @@ -3645,7 +3428,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, @@ -3661,8 +3444,8 @@ } ], "post": { - "description": "create a ValidatingAdmissionPolicy", - "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "create a MutatingAdmissionPolicyBinding", + "operationId": "createAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3696,7 +3479,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -3707,22 +3490,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -3732,22 +3515,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -3757,22 +3540,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -3788,15 +3571,15 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}": { + "/apis/admissionregistration.k8s.io/v1/mutatingadmissionpolicybindings/{name}": { "delete": { - "description": "delete a ValidatingAdmissionPolicy", - "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "delete a MutatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3914,34 +3697,34 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, "get": { - "description": "read the specified ValidatingAdmissionPolicy", - "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "read the specified MutatingAdmissionPolicyBinding", + "operationId": "readAdmissionregistrationV1MutatingAdmissionPolicyBinding", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -3957,13 +3740,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, "parameters": [ { - "description": "name of the ValidatingAdmissionPolicy", + "description": "name of the MutatingAdmissionPolicyBinding", "in": "path", "name": "name", "required": true, @@ -3983,8 +3766,8 @@ } ], "patch": { - "description": "partially update the specified ValidatingAdmissionPolicy", - "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "partially update the specified MutatingAdmissionPolicyBinding", + "operationId": "patchAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -4058,22 +3841,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -4083,22 +3866,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -4114,13 +3897,13 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } }, "put": { - "description": "replace the specified ValidatingAdmissionPolicy", - "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicy", + "description": "replace the specified MutatingAdmissionPolicyBinding", + "operationId": "replaceAdmissionregistrationV1MutatingAdmissionPolicyBinding", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -4154,7 +3937,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -4165,22 +3948,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -4190,22 +3973,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" } } }, @@ -4221,80 +4004,25 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}/status": { - "get": { - "description": "read status of the specified ValidatingAdmissionPolicy", - "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyStatus", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "admissionregistration_v1" - ], - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", - "version": "v1" - } - }, - "parameters": [ - { - "description": "name of the ValidatingAdmissionPolicy", - "in": "path", - "name": "name", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "patch": { - "description": "partially update status of the specified ValidatingAdmissionPolicy", - "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations": { + "delete": { + "description": "delete collection of MutatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1CollectionMutatingWebhookConfiguration", "parameters": [ + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "in": "query", @@ -4305,293 +4033,39 @@ } }, { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", "in": "query", - "name": "fieldManager", + "name": "fieldSelector", "schema": { "type": "string", "uniqueItems": true } }, { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", "in": "query", - "name": "fieldValidation", + "name": "gracePeriodSeconds", "schema": { - "type": "string", + "type": "integer", "uniqueItems": true } }, { - "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", "in": "query", - "name": "force", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", "schema": { "type": "boolean", "uniqueItems": true } - } - ], - "requestBody": { - "content": { - "application/apply-patch+cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - } - }, - "description": "Created" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "admissionregistration_v1" - ], - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", - "version": "v1" - } - }, - "put": { - "description": "replace status of the specified ValidatingAdmissionPolicy", - "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyStatus", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", - "in": "query", - "name": "fieldManager", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" - } - } - }, - "description": "Created" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "admissionregistration_v1" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicy", - "version": "v1" - } - } - }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings": { - "delete": { - "description": "delete collection of ValidatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicyBinding", - "parameters": [ - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", - "in": "query", - "name": "gracePeriodSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", - "in": "query", - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true } }, { @@ -4648,6 +4122,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4703,13 +4186,13 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, "get": { - "description": "list or watch objects of kind ValidatingAdmissionPolicyBinding", - "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "list or watch objects of kind MutatingWebhookConfiguration", + "operationId": "listAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", @@ -4783,6 +4266,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4807,37 +4299,37 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } } }, @@ -4853,7 +4345,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, @@ -4869,8 +4361,8 @@ } ], "post": { - "description": "create a ValidatingAdmissionPolicyBinding", - "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "create a MutatingWebhookConfiguration", + "operationId": "createAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -4904,7 +4396,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -4915,22 +4407,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -4940,22 +4432,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -4965,22 +4457,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -4996,15 +4488,15 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings/{name}": { + "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/{name}": { "delete": { - "description": "delete a ValidatingAdmissionPolicyBinding", - "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "delete a MutatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -5122,34 +4614,34 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, "get": { - "description": "read the specified ValidatingAdmissionPolicyBinding", - "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "read the specified MutatingWebhookConfiguration", + "operationId": "readAdmissionregistrationV1MutatingWebhookConfiguration", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -5165,13 +4657,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, "parameters": [ { - "description": "name of the ValidatingAdmissionPolicyBinding", + "description": "name of the MutatingWebhookConfiguration", "in": "path", "name": "name", "required": true, @@ -5191,8 +4683,8 @@ } ], "patch": { - "description": "partially update the specified ValidatingAdmissionPolicyBinding", - "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "partially update the specified MutatingWebhookConfiguration", + "operationId": "patchAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -5266,22 +4758,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -5291,22 +4783,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -5322,13 +4814,13 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } }, "put": { - "description": "replace the specified ValidatingAdmissionPolicyBinding", - "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "description": "replace the specified MutatingWebhookConfiguration", + "operationId": "replaceAdmissionregistrationV1MutatingWebhookConfiguration", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -5362,7 +4854,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -5373,22 +4865,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -5398,22 +4890,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MutatingWebhookConfiguration" } } }, @@ -5429,15 +4921,15 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingAdmissionPolicyBinding", + "kind": "MutatingWebhookConfiguration", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies": { "delete": { - "description": "delete collection of ValidatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1CollectionValidatingWebhookConfiguration", + "description": "delete collection of ValidatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicy", "parameters": [ { "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", @@ -5547,6 +5039,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5602,13 +5103,13 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, "get": { - "description": "list or watch objects of kind ValidatingWebhookConfiguration", - "operationId": "listAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "list or watch objects of kind ValidatingAdmissionPolicy", + "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", @@ -5682,6 +5183,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5706,37 +5216,37 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyList" } } }, @@ -5752,7 +5262,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, @@ -5768,8 +5278,8 @@ } ], "post": { - "description": "create a ValidatingWebhookConfiguration", - "operationId": "createAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "create a ValidatingAdmissionPolicy", + "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -5803,7 +5313,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -5814,22 +5324,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -5839,22 +5349,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -5864,22 +5374,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -5895,15 +5405,15 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } } }, - "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations/{name}": { + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}": { "delete": { - "description": "delete a ValidatingWebhookConfiguration", - "operationId": "deleteAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "delete a ValidatingAdmissionPolicy", + "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -6021,34 +5531,34 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, "get": { - "description": "read the specified ValidatingWebhookConfiguration", - "operationId": "readAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "read the specified ValidatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicy", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -6064,13 +5574,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, "parameters": [ { - "description": "name of the ValidatingWebhookConfiguration", + "description": "name of the ValidatingAdmissionPolicy", "in": "path", "name": "name", "required": true, @@ -6090,8 +5600,8 @@ } ], "patch": { - "description": "partially update the specified ValidatingWebhookConfiguration", - "operationId": "patchAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "partially update the specified ValidatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -6165,22 +5675,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -6190,22 +5700,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" } } }, @@ -6221,13 +5731,13 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "ValidatingAdmissionPolicy", "version": "v1" } }, "put": { - "description": "replace the specified ValidatingWebhookConfiguration", - "operationId": "replaceAdmissionregistrationV1ValidatingWebhookConfiguration", + "description": "replace the specified ValidatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicy", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -6256,67 +5766,2790 @@ "uniqueItems": true } } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" - } - } - }, - "required": true + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicy", + "version": "v1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicies/{name}/status": { + "get": { + "description": "read status of the specified ValidatingAdmissionPolicy", + "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicy", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the ValidatingAdmissionPolicy", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update status of the specified ValidatingAdmissionPolicy", + "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicy", + "version": "v1" + } + }, + "put": { + "description": "replace status of the specified ValidatingAdmissionPolicy", + "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyStatus", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicy" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicy", + "version": "v1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings": { + "delete": { + "description": "delete collection of ValidatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1CollectionValidatingAdmissionPolicyBinding", + "parameters": [ + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + }, + "get": { + "description": "list or watch objects of kind ValidatingAdmissionPolicyBinding", + "operationId": "listAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBindingList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + }, + "parameters": [ + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a ValidatingAdmissionPolicyBinding", + "operationId": "createAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1/validatingadmissionpolicybindings/{name}": { + "delete": { + "description": "delete a ValidatingAdmissionPolicyBinding", + "operationId": "deleteAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + }, + "get": { + "description": "read the specified ValidatingAdmissionPolicyBinding", + "operationId": "readAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the ValidatingAdmissionPolicyBinding", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update the specified ValidatingAdmissionPolicyBinding", + "operationId": "patchAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + }, + "put": { + "description": "replace the specified ValidatingAdmissionPolicyBinding", + "operationId": "replaceAdmissionregistrationV1ValidatingAdmissionPolicyBinding", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingAdmissionPolicyBinding" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingAdmissionPolicyBinding", + "version": "v1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations": { + "delete": { + "description": "delete collection of ValidatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1CollectionValidatingWebhookConfiguration", + "parameters": [ + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + }, + "get": { + "description": "list or watch objects of kind ValidatingWebhookConfiguration", + "operationId": "listAdmissionregistrationV1ValidatingWebhookConfiguration", + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfigurationList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + }, + "parameters": [ + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a ValidatingWebhookConfiguration", + "operationId": "createAdmissionregistrationV1ValidatingWebhookConfiguration", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations/{name}": { + "delete": { + "description": "delete a ValidatingWebhookConfiguration", + "operationId": "deleteAdmissionregistrationV1ValidatingWebhookConfiguration", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + }, + "get": { + "description": "read the specified ValidatingWebhookConfiguration", + "operationId": "readAdmissionregistrationV1ValidatingWebhookConfiguration", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + }, + "parameters": [ + { + "description": "name of the ValidatingWebhookConfiguration", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update the specified ValidatingWebhookConfiguration", + "operationId": "patchAdmissionregistrationV1ValidatingWebhookConfiguration", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + }, + "put": { + "description": "replace the specified ValidatingWebhookConfiguration", + "operationId": "replaceAdmissionregistrationV1ValidatingWebhookConfiguration", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1" + } + } + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicies": { + "get": { + "description": "watch individual changes to a list of MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicyList", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicies/{name}": { + "get": { + "description": "watch changes to an object of kind MutatingAdmissionPolicy. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicy", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicy", + "version": "v1" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "name of the MutatingAdmissionPolicy", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicybindings": { + "get": { + "description": "watch individual changes to a list of MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicyBindingList", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "admissionregistration_v1" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingAdmissionPolicyBinding", + "version": "v1" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/admissionregistration.k8s.io/v1/watch/mutatingadmissionpolicybindings/{name}": { + "get": { + "description": "watch changes to an object of kind MutatingAdmissionPolicyBinding. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchAdmissionregistrationV1MutatingAdmissionPolicyBinding", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/json": { + "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/vnd.kubernetes.protobuf": { + "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { + "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/json": { + "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/vnd.kubernetes.protobuf": { + "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } } }, - "description": "Created" + "description": "OK" }, "401": { "description": "Unauthorized" @@ -6325,13 +8558,133 @@ "tags": [ "admissionregistration_v1" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "admissionregistration.k8s.io", - "kind": "ValidatingWebhookConfiguration", + "kind": "MutatingAdmissionPolicyBinding", "version": "v1" } - } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "name of the MutatingAdmissionPolicyBinding", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] }, "/apis/admissionregistration.k8s.io/v1/watch/mutatingwebhookconfigurations": { "get": { @@ -6474,6 +8827,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6645,6 +9007,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6806,6 +9177,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6977,6 +9357,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7138,6 +9527,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7309,6 +9707,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7470,6 +9877,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7641,6 +10057,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json index c5ececfdf252e..cf31d011e095e 100644 --- a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json +++ b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1alpha1_openapi.json @@ -25,12 +25,12 @@ "properties": { "expression": { "default": "", - "description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", "type": "string" }, "name": { "default": "", - "description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "description": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", "type": "string" } }, @@ -44,7 +44,7 @@ "description": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "properties": { "excludeResourceRules": { - "description": "ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "description": "excludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "items": { "allOf": [ { @@ -66,7 +66,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "allOf": [ @@ -74,10 +74,10 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "ObjectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "resourceRules": { - "description": "ResourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", + "description": "resourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", "items": { "allOf": [ { @@ -111,7 +111,7 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "allOf": [ @@ -120,7 +120,7 @@ } ], "default": {}, - "description": "Specification of the desired behavior of the MutatingAdmissionPolicy." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicy." } }, "type": "object", @@ -150,7 +150,7 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "allOf": [ @@ -159,7 +159,7 @@ } ], "default": {}, - "description": "Specification of the desired behavior of the MutatingAdmissionPolicyBinding." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding." } }, "type": "object", @@ -201,7 +201,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -272,7 +272,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -395,7 +395,7 @@ "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -404,7 +404,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -413,7 +413,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -422,7 +422,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", "items": { "default": "", "type": "string" @@ -431,7 +431,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "default": "", "type": "string" @@ -451,11 +451,11 @@ "description": "ParamKind is a tuple of Group Kind and Version.", "properties": { "apiVersion": { - "description": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "description": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", "type": "string" }, "kind": { - "description": "Kind is the API kind the resources belong to. Required.", + "description": "kind is the API kind the resources belong to. Required.", "type": "string" } }, @@ -466,7 +466,7 @@ "description": "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.", "properties": { "name": { - "description": "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", + "description": "name is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", "type": "string" }, "namespace": { @@ -474,7 +474,7 @@ "type": "string" }, "parameterNotFoundAction": { - "description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`", + "description": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`", "type": "string" }, "selector": { @@ -494,12 +494,12 @@ "properties": { "expression": { "default": "", - "description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "description": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", "type": "string" }, "name": { "default": "", - "description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "description": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", "type": "string" } }, @@ -676,310 +676,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "admissionregistration.k8s.io", "kind": "DeleteOptions", "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1063,6 +763,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1281,6 +989,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1426,310 +1148,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, { "group": "admissionregistration.k8s.io", "kind": "WatchEvent", "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1905,6 +1327,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2040,6 +1471,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2804,6 +2244,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2939,6 +2388,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3731,6 +3189,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3902,6 +3369,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4063,6 +3539,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4234,6 +3719,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1beta1_openapi.json index 9f7492da45b6b..56c3bc4ed6f17 100644 --- a/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__admissionregistration.k8s.io__v1beta1_openapi.json @@ -26,12 +26,12 @@ "properties": { "expression": { "default": "", - "description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "description": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", "type": "string" }, "name": { "default": "", - "description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "description": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", "type": "string" } }, @@ -45,7 +45,7 @@ "description": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "properties": { "excludeResourceRules": { - "description": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "description": "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "items": { "allOf": [ { @@ -67,7 +67,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + "description": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." }, "objectSelector": { "allOf": [ @@ -75,10 +75,10 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" } ], - "description": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + "description": "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." }, "resourceRules": { - "description": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + "description": "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", "items": { "allOf": [ { @@ -112,7 +112,7 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "allOf": [ @@ -121,7 +121,7 @@ } ], "default": {}, - "description": "Specification of the desired behavior of the MutatingAdmissionPolicy." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicy." } }, "type": "object", @@ -151,7 +151,7 @@ } ], "default": {}, - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + "description": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." }, "spec": { "allOf": [ @@ -160,7 +160,7 @@ } ], "default": {}, - "description": "Specification of the desired behavior of the MutatingAdmissionPolicyBinding." + "description": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding." } }, "type": "object", @@ -202,7 +202,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -273,7 +273,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" } }, "required": [ @@ -396,7 +396,7 @@ "description": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", "properties": { "apiGroups": { - "description": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "description": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -405,7 +405,7 @@ "x-kubernetes-list-type": "atomic" }, "apiVersions": { - "description": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "description": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -414,7 +414,7 @@ "x-kubernetes-list-type": "atomic" }, "operations": { - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "description": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", "items": { "default": "", "type": "string" @@ -423,7 +423,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", "items": { "default": "", "type": "string" @@ -432,7 +432,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "description": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "items": { "default": "", "type": "string" @@ -452,11 +452,11 @@ "description": "ParamKind is a tuple of Group Kind and Version.", "properties": { "apiVersion": { - "description": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "description": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", "type": "string" }, "kind": { - "description": "Kind is the API kind the resources belong to. Required.", + "description": "kind is the API kind the resources belong to. Required.", "type": "string" } }, @@ -475,7 +475,7 @@ "type": "string" }, "parameterNotFoundAction": { - "description": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + "description": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", "type": "string" }, "selector": { @@ -495,12 +495,12 @@ "properties": { "expression": { "default": "", - "description": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "description": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", "type": "string" }, "name": { "default": "", - "description": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "description": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", "type": "string" } }, @@ -678,310 +678,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "admissionregistration.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1065,6 +765,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1270,466 +978,180 @@ "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "resourceVersion": { - "description": "Specifies the target ResourceVersion", - "type": "string" - }, - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "format": "int32", - "type": "integer" - }, - "details": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - } - ], - "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ], - "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" - }, - "reason": { - "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", - "type": "string" - }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "resourceVersion": { + "description": "Specifies the target ResourceVersion", + "type": "string" }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "uid": { + "description": "Specifies the target UID.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", + "format": "int32", + "type": "integer" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "details": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" + } + ], + "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "message": { + "description": "A human-readable description of the status of this operation.", + "type": "string" }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" + "reason": { + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", + "group": "", + "kind": "Status", "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", "version": "v1" }, { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", + "group": "admissionregistration.k8s.io", "kind": "WatchEvent", "version": "v1beta1" } @@ -1907,6 +1329,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2042,6 +1473,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2806,6 +2246,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2941,6 +2390,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3733,6 +3191,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3904,6 +3371,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4065,6 +3541,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4236,6 +3721,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__apiextensions.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__apiextensions.k8s.io__v1_openapi.json index 8f7af3a53f336..7bfb0f2a7d064 100644 --- a/api/openapi-spec/v3/apis__apiextensions.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__apiextensions.k8s.io__v1_openapi.json @@ -1045,310 +1045,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "apiextensions.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1375,6 +1075,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1593,6 +1301,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1738,310 +1460,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, { "group": "apiextensions.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -2217,6 +1639,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2352,6 +1783,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3453,6 +2893,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3624,6 +3073,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__apiregistration.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__apiregistration.k8s.io__v1_openapi.json index 2eb04c986d1ee..237a6060e214e 100644 --- a/api/openapi-spec/v3/apis__apiregistration.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__apiregistration.k8s.io__v1_openapi.json @@ -168,310 +168,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "apiregistration.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -498,6 +198,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -716,6 +424,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -861,310 +583,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, { "group": "apiregistration.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1560,6 +982,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1695,6 +1126,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2796,6 +2236,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2967,6 +2416,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__apps__v1_openapi.json b/api/openapi-spec/v3/apis__apps__v1_openapi.json index 15df0b3735ed8..2ca1c0f9529f0 100644 --- a/api/openapi-spec/v3/apis__apps__v1_openapi.json +++ b/api/openapi-spec/v3/apis__apps__v1_openapi.json @@ -3750,7 +3750,7 @@ "type": "object" }, "io.k8s.api.core.v1.PodResourceClaim": { - "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and this Pod belongs to a PodGroup, a PodResourceClaim is matched to a PodGroupResourceClaim if all of their fields are equal (Name, ResourceClaimName, and ResourceClaimTemplateName). A matched claim references a single ResourceClaim shared across all Pods in the PodGroup, reserved for the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual Pods.", "properties": { "name": { "default": "", @@ -3762,7 +3762,7 @@ "type": "string" }, "resourceClaimTemplateName": { - "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and the pod belongs to a PodGroup that defines a PodGroupResourceClaim with the same Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the ResourceClaim generated for the PodGroup. All pods in the group that define an equivalent PodResourceClaim matching the PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same generated ResourceClaim. ResourceClaims generated for a PodGroup are owned by the PodGroup and their lifecycles are tied to the PodGroup instead of any individual pod.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", "type": "string" } }, @@ -3785,6 +3785,23 @@ ], "type": "object" }, + "io.k8s.api.core.v1.PodSchedulingGroup": { + "description": "PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics. Exactly one field must be specified.", + "properties": { + "podGroupName": { + "description": "PodGroupName specifies the name of the standalone PodGroup object that represents the runtime instance of this group. Must be a DNS subdomain.", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "podGroupName": "PodGroupName" + } + } + ] + }, "io.k8s.api.core.v1.PodSecurityContext": { "description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", "properties": { @@ -3980,7 +3997,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { @@ -4137,6 +4154,14 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "schedulingGroup": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGroup" + } + ], + "description": "SchedulingGroup provides a reference to the immediate scheduling runtime grouping object that this Pod belongs to. This field is used by the scheduler to identify the group and apply the correct group scheduling policies. The association with a group also impacts other lifecycle aspects of a Pod that are relevant in a wider context of scheduling like preemption, resource attachment, etc. If not specified, the Pod is treated as a single unit in all of these aspects. The group object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a group object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." + }, "securityContext": { "allOf": [ { @@ -4219,14 +4244,6 @@ "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" - }, - "workloadRef": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.core.v1.WorkloadReference" - } - ], - "description": "WorkloadRef provides a reference to the Workload object that this Pod belongs to. This field is used by the scheduler to identify the PodGroup and apply the correct group scheduling policies. The Workload object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a Workload object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." } }, "required": [ @@ -4803,7 +4820,7 @@ "type": "boolean" }, "procMount": { - "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. Note that this field cannot be set when spec.os.name is windows.", "type": "string" }, "readOnlyRootFilesystem": { @@ -5242,7 +5259,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeSource" } ], - "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." + "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." }, "iscsi": { "allOf": [ @@ -5287,7 +5304,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.PortworxVolumeSource" } ], - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "projected": { "allOf": [ @@ -5558,30 +5575,6 @@ }, "type": "object" }, - "io.k8s.api.core.v1.WorkloadReference": { - "description": "WorkloadReference identifies the Workload object and PodGroup membership that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics.", - "properties": { - "name": { - "default": "", - "description": "Name defines the name of the Workload object this Pod belongs to. Workload must be in the same namespace as the Pod. If it doesn't match any existing Workload, the Pod will remain unschedulable until a Workload object is created and observed by the kube-scheduler. It must be a DNS subdomain.", - "type": "string" - }, - "podGroup": { - "default": "", - "description": "PodGroup is the name of the PodGroup within the Workload that this Pod belongs to. If it doesn't match any existing PodGroup within the Workload, the Pod will remain unschedulable until the Workload object is recreated and observed by the kube-scheduler. It must be a DNS label.", - "type": "string" - }, - "podGroupReplicaKey": { - "description": "PodGroupReplicaKey specifies the replica key of the PodGroup to which this Pod belongs. It is used to distinguish pods belonging to different replicas of the same pod group. The pod group policy is applied separately to each replica. When set, it must be a DNS label.", - "type": "string" - } - }, - "required": [ - "name", - "podGroup" - ], - "type": "object" - }, "io.k8s.apimachinery.pkg.api.resource.Quantity": { "description": "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", "oneOf": [ @@ -5760,310 +5753,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "apps", "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -6147,6 +5840,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -6365,6 +6066,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -6466,354 +6181,54 @@ "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", "type": "string" }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" }, { - "group": "storagemigration.k8s.io", + "group": "apps", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" } ] }, @@ -7029,6 +6444,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7190,6 +6614,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7351,6 +6784,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7484,6 +6926,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7619,6 +7070,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8403,6 +7863,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8538,6 +8007,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9641,6 +9119,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9776,6 +9263,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11198,6 +10694,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11333,6 +10838,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -12755,6 +12269,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -12890,6 +12413,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14340,6 +13872,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14501,6 +14042,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14662,6 +14212,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14823,6 +14382,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -14984,6 +14552,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15155,6 +14732,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15336,6 +14922,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15507,6 +15102,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15688,6 +15292,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -15859,6 +15472,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16040,6 +15662,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16211,6 +15842,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16392,6 +16032,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16563,6 +16212,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16744,6 +16402,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -16905,6 +16572,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -17066,6 +16742,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__authentication.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__authentication.k8s.io__v1_openapi.json index 1d55c229c905f..24e7fe92fdc6f 100644 --- a/api/openapi-spec/v3/apis__authentication.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__authentication.k8s.io__v1_openapi.json @@ -19,7 +19,7 @@ } ], "default": {}, - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "status": { "allOf": [ @@ -28,7 +28,7 @@ } ], "default": {}, - "description": "Status is filled in by the server with the user attributes." + "description": "status is filled in by the server with the user attributes." } }, "type": "object", @@ -50,7 +50,7 @@ } ], "default": {}, - "description": "User attributes of the user making this request." + "description": "userInfo is a set of attributes belonging to the user making this request." } }, "type": "object" @@ -73,7 +73,7 @@ } ], "default": {}, - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "allOf": [ @@ -82,7 +82,7 @@ } ], "default": {}, - "description": "Spec holds information about the request being evaluated" + "description": "spec holds information about the request being evaluated" }, "status": { "allOf": [ @@ -91,7 +91,7 @@ } ], "default": {}, - "description": "Status is filled in by the server and indicates whether the request can be authenticated." + "description": "status is filled in by the server and indicates whether the request can be authenticated." } }, "required": [ @@ -110,7 +110,7 @@ "description": "TokenReviewSpec is a description of the token authentication request.", "properties": { "audiences": { - "description": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", + "description": "audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", "items": { "default": "", "type": "string" @@ -119,17 +119,20 @@ "x-kubernetes-list-type": "atomic" }, "token": { - "description": "Token is the opaque bearer token.", + "description": "token is the opaque bearer token.", "type": "string" } }, + "required": [ + "token" + ], "type": "object" }, "io.k8s.api.authentication.v1.TokenReviewStatus": { "description": "TokenReviewStatus is the result of the token authentication request.", "properties": { "audiences": { - "description": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", + "description": "audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", "items": { "default": "", "type": "string" @@ -138,11 +141,11 @@ "x-kubernetes-list-type": "atomic" }, "authenticated": { - "description": "Authenticated indicates that the token was associated with a known user.", + "description": "authenticated indicates that the token was associated with a known user.", "type": "boolean" }, "error": { - "description": "Error indicates that the token couldn't be checked", + "description": "error indicates that the token couldn't be checked", "type": "string" }, "user": { @@ -152,7 +155,7 @@ } ], "default": {}, - "description": "User is the UserInfo associated with the provided token." + "description": "user is the UserInfo associated with the provided token." } }, "type": "object" @@ -168,11 +171,11 @@ }, "type": "array" }, - "description": "Any additional information provided by the authenticator.", + "description": "extra is any additional information provided by the authenticator.", "type": "object" }, "groups": { - "description": "The names of groups this user is a part of.", + "description": "groups is the names of groups this user is a part of.", "items": { "default": "", "type": "string" @@ -181,11 +184,11 @@ "x-kubernetes-list-type": "atomic" }, "uid": { - "description": "A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", + "description": "uid is a unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", "type": "string" }, "username": { - "description": "The name that uniquely identifies this user among all active users.", + "description": "username is the name that uniquely identifies this user among all active users.", "type": "string" } }, diff --git a/api/openapi-spec/v3/apis__authorization.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__authorization.k8s.io__v1_openapi.json index a751b49235e4b..d78e3bbd99cc5 100644 --- a/api/openapi-spec/v3/apis__authorization.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__authorization.k8s.io__v1_openapi.json @@ -65,7 +65,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "allOf": [ @@ -74,7 +74,7 @@ } ], "default": {}, - "description": "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted." + "description": "spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted." }, "status": { "allOf": [ @@ -83,7 +83,7 @@ } ], "default": {}, - "description": "Status is filled in by the server and indicates whether the request is allowed or not" + "description": "status is filled in by the server and indicates whether the request is allowed or not" } }, "required": [ @@ -102,11 +102,11 @@ "description": "NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface", "properties": { "path": { - "description": "Path is the URL path of the request", + "description": "path is the URL path of the request", "type": "string" }, "verb": { - "description": "Verb is the standard HTTP verb", + "description": "verb is the standard HTTP verb", "type": "string" } }, @@ -116,7 +116,7 @@ "description": "NonResourceRule holds information that describes a rule for the non-resource", "properties": { "nonResourceURLs": { - "description": "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", + "description": "nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", "items": { "default": "", "type": "string" @@ -125,7 +125,7 @@ "x-kubernetes-list-type": "atomic" }, "verbs": { - "description": "Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", + "description": "verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", "items": { "default": "", "type": "string" @@ -151,7 +151,7 @@ "description": "fieldSelector describes the limitation on access based on field. It can only limit access, not broaden it." }, "group": { - "description": "Group is the API Group of the Resource. \"*\" means all.", + "description": "group is the API Group of the Resource. \"*\" means all.", "type": "string" }, "labelSelector": { @@ -163,27 +163,27 @@ "description": "labelSelector describes the limitation on access based on labels. It can only limit access, not broaden it." }, "name": { - "description": "Name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", + "description": "name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", "type": "string" }, "namespace": { - "description": "Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", + "description": "namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", "type": "string" }, "resource": { - "description": "Resource is one of the existing resource types. \"*\" means all.", + "description": "resource is one of the existing resource types. \"*\" means all.", "type": "string" }, "subresource": { - "description": "Subresource is one of the existing resource types. \"\" means none.", + "description": "subresource is one of the existing resource types. \"\" means none.", "type": "string" }, "verb": { - "description": "Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "description": "verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", "type": "string" }, "version": { - "description": "Version is the API Version of the Resource. \"*\" means all.", + "description": "version is the API Version of the Resource. \"*\" means all.", "type": "string" } }, @@ -193,7 +193,7 @@ "description": "ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", "properties": { "apiGroups": { - "description": "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", + "description": "apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", "items": { "default": "", "type": "string" @@ -202,7 +202,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceNames": { - "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", + "description": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", "items": { "default": "", "type": "string" @@ -211,7 +211,7 @@ "x-kubernetes-list-type": "atomic" }, "resources": { - "description": "Resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", + "description": "resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", "items": { "default": "", "type": "string" @@ -220,7 +220,7 @@ "x-kubernetes-list-type": "atomic" }, "verbs": { - "description": "Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "description": "verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", "items": { "default": "", "type": "string" @@ -252,7 +252,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "allOf": [ @@ -261,7 +261,7 @@ } ], "default": {}, - "description": "Spec holds information about the request being evaluated. user and groups must be empty" + "description": "spec holds information about the request being evaluated. user and groups must be empty" }, "status": { "allOf": [ @@ -270,7 +270,7 @@ } ], "default": {}, - "description": "Status is filled in by the server and indicates whether the request is allowed or not" + "description": "status is filled in by the server and indicates whether the request is allowed or not" } }, "required": [ @@ -286,7 +286,7 @@ ] }, "io.k8s.api.authorization.v1.SelfSubjectAccessReviewSpec": { - "description": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + "description": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", "properties": { "nonResourceAttributes": { "allOf": [ @@ -294,7 +294,7 @@ "$ref": "#/components/schemas/io.k8s.api.authorization.v1.NonResourceAttributes" } ], - "description": "NonResourceAttributes describes information for a non-resource access request" + "description": "nonResourceAttributes describes information for a non-resource access request" }, "resourceAttributes": { "allOf": [ @@ -302,7 +302,7 @@ "$ref": "#/components/schemas/io.k8s.api.authorization.v1.ResourceAttributes" } ], - "description": "ResourceAuthorizationAttributes describes information for a resource access request" + "description": "resourceAttributes describes information for a resource access request" } }, "type": "object" @@ -325,7 +325,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "allOf": [ @@ -334,7 +334,7 @@ } ], "default": {}, - "description": "Spec holds information about the request being evaluated." + "description": "spec holds information about the request being evaluated." }, "status": { "allOf": [ @@ -343,7 +343,7 @@ } ], "default": {}, - "description": "Status is filled in by the server and indicates the set of actions a user can perform." + "description": "status is filled in by the server and indicates the set of actions a user can perform." } }, "required": [ @@ -362,7 +362,7 @@ "description": "SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview.", "properties": { "namespace": { - "description": "Namespace to evaluate rules for. Required.", + "description": "namespace to evaluate rules for. Required.", "type": "string" } }, @@ -386,7 +386,7 @@ } ], "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + "description": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "allOf": [ @@ -395,7 +395,7 @@ } ], "default": {}, - "description": "Spec holds information about the request being evaluated" + "description": "spec holds information about the request being evaluated" }, "status": { "allOf": [ @@ -404,7 +404,7 @@ } ], "default": {}, - "description": "Status is filled in by the server and indicates whether the request is allowed or not" + "description": "status is filled in by the server and indicates whether the request is allowed or not" } }, "required": [ @@ -420,7 +420,7 @@ ] }, "io.k8s.api.authorization.v1.SubjectAccessReviewSpec": { - "description": "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + "description": "SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", "properties": { "extra": { "additionalProperties": { @@ -430,11 +430,11 @@ }, "type": "array" }, - "description": "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", + "description": "extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", "type": "object" }, "groups": { - "description": "Groups is the groups you're testing for.", + "description": "groups is the groups you're testing for.", "items": { "default": "", "type": "string" @@ -448,7 +448,7 @@ "$ref": "#/components/schemas/io.k8s.api.authorization.v1.NonResourceAttributes" } ], - "description": "NonResourceAttributes describes information for a non-resource access request" + "description": "nonResourceAttributes describes information for a non-resource access request" }, "resourceAttributes": { "allOf": [ @@ -456,14 +456,14 @@ "$ref": "#/components/schemas/io.k8s.api.authorization.v1.ResourceAttributes" } ], - "description": "ResourceAuthorizationAttributes describes information for a resource access request" + "description": "resourceAttributes describes information for a resource access request" }, "uid": { - "description": "UID information about the requesting user.", + "description": "uid information about the requesting user.", "type": "string" }, "user": { - "description": "User is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", + "description": "user is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", "type": "string" } }, @@ -474,19 +474,19 @@ "properties": { "allowed": { "default": false, - "description": "Allowed is required. True if the action would be allowed, false otherwise.", + "description": "allowed is required. True if the action would be allowed, false otherwise.", "type": "boolean" }, "denied": { - "description": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", + "description": "denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", "type": "boolean" }, "evaluationError": { - "description": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", + "description": "evaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", "type": "string" }, "reason": { - "description": "Reason is optional. It indicates why a request was allowed or denied.", + "description": "reason is optional. It indicates why a request was allowed or denied.", "type": "string" } }, @@ -499,16 +499,16 @@ "description": "SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on the set of authorizers the server is configured with and any errors experienced during evaluation. Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, even if that list is incomplete.", "properties": { "evaluationError": { - "description": "EvaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", + "description": "evaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", "type": "string" }, "incomplete": { "default": false, - "description": "Incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", + "description": "incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", "type": "boolean" }, "nonResourceRules": { - "description": "NonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "description": "nonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", "items": { "allOf": [ { @@ -521,7 +521,7 @@ "x-kubernetes-list-type": "atomic" }, "resourceRules": { - "description": "ResourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "description": "resourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", "items": { "allOf": [ { diff --git a/api/openapi-spec/v3/apis__autoscaling__v1_openapi.json b/api/openapi-spec/v3/apis__autoscaling__v1_openapi.json index a6a895b4bd9f2..4ebc6d74646a8 100644 --- a/api/openapi-spec/v3/apis__autoscaling__v1_openapi.json +++ b/api/openapi-spec/v3/apis__autoscaling__v1_openapi.json @@ -65,6 +65,9 @@ "description": "status is the current information about the autoscaler." } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -361,310 +364,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, { "group": "autoscaling", "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -691,6 +394,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -909,6 +620,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1054,310 +779,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, { "group": "autoscaling", "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1561,6 +986,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1694,6 +1128,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1829,6 +1272,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2960,6 +2412,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3131,6 +2592,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3312,6 +2782,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__autoscaling__v2_openapi.json b/api/openapi-spec/v3/apis__autoscaling__v2_openapi.json index da9e7ef04f965..f1966181196bc 100644 --- a/api/openapi-spec/v3/apis__autoscaling__v2_openapi.json +++ b/api/openapi-spec/v3/apis__autoscaling__v2_openapi.json @@ -244,6 +244,9 @@ "description": "status is the current information about the autoscaler." } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -1021,310 +1024,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "autoscaling", "kind": "DeleteOptions", "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1408,6 +1111,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1626,6 +1337,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1771,310 +1496,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, { "group": "autoscaling", "kind": "WatchEvent", "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -2278,6 +1703,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2411,6 +1845,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2546,6 +1989,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3677,6 +3129,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3848,6 +3309,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4029,6 +3499,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__batch__v1_openapi.json b/api/openapi-spec/v3/apis__batch__v1_openapi.json index 2df1e47230b57..23f94f2751cb9 100644 --- a/api/openapi-spec/v3/apis__batch__v1_openapi.json +++ b/api/openapi-spec/v3/apis__batch__v1_openapi.json @@ -40,6 +40,9 @@ "description": "Current status of a cron job. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -2941,7 +2944,7 @@ "type": "object" }, "io.k8s.api.core.v1.PodResourceClaim": { - "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + "description": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and this Pod belongs to a PodGroup, a PodResourceClaim is matched to a PodGroupResourceClaim if all of their fields are equal (Name, ResourceClaimName, and ResourceClaimTemplateName). A matched claim references a single ResourceClaim shared across all Pods in the PodGroup, reserved for the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual Pods.", "properties": { "name": { "default": "", @@ -2953,7 +2956,7 @@ "type": "string" }, "resourceClaimTemplateName": { - "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and the pod belongs to a PodGroup that defines a PodGroupResourceClaim with the same Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the ResourceClaim generated for the PodGroup. All pods in the group that define an equivalent PodResourceClaim matching the PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same generated ResourceClaim. ResourceClaims generated for a PodGroup are owned by the PodGroup and their lifecycles are tied to the PodGroup instead of any individual pod.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", "type": "string" } }, @@ -2976,6 +2979,23 @@ ], "type": "object" }, + "io.k8s.api.core.v1.PodSchedulingGroup": { + "description": "PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics. Exactly one field must be specified.", + "properties": { + "podGroupName": { + "description": "PodGroupName specifies the name of the standalone PodGroup object that represents the runtime instance of this group. Must be a DNS subdomain.", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "podGroupName": "PodGroupName" + } + } + ] + }, "io.k8s.api.core.v1.PodSecurityContext": { "description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", "properties": { @@ -3171,7 +3191,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { @@ -3328,6 +3348,14 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "schedulingGroup": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGroup" + } + ], + "description": "SchedulingGroup provides a reference to the immediate scheduling runtime grouping object that this Pod belongs to. This field is used by the scheduler to identify the group and apply the correct group scheduling policies. The association with a group also impacts other lifecycle aspects of a Pod that are relevant in a wider context of scheduling like preemption, resource attachment, etc. If not specified, the Pod is treated as a single unit in all of these aspects. The group object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a group object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." + }, "securityContext": { "allOf": [ { @@ -3410,14 +3438,6 @@ "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" - }, - "workloadRef": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.core.v1.WorkloadReference" - } - ], - "description": "WorkloadRef provides a reference to the Workload object that this Pod belongs to. This field is used by the scheduler to identify the PodGroup and apply the correct group scheduling policies. The Workload object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a Workload object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies." } }, "required": [ @@ -3994,7 +4014,7 @@ "type": "boolean" }, "procMount": { - "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. Note that this field cannot be set when spec.os.name is windows.", "type": "string" }, "readOnlyRootFilesystem": { @@ -4433,7 +4453,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeSource" } ], - "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." + "description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type." }, "iscsi": { "allOf": [ @@ -4478,7 +4498,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.PortworxVolumeSource" } ], - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "projected": { "allOf": [ @@ -4749,30 +4769,6 @@ }, "type": "object" }, - "io.k8s.api.core.v1.WorkloadReference": { - "description": "WorkloadReference identifies the Workload object and PodGroup membership that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics.", - "properties": { - "name": { - "default": "", - "description": "Name defines the name of the Workload object this Pod belongs to. Workload must be in the same namespace as the Pod. If it doesn't match any existing Workload, the Pod will remain unschedulable until a Workload object is created and observed by the kube-scheduler. It must be a DNS subdomain.", - "type": "string" - }, - "podGroup": { - "default": "", - "description": "PodGroup is the name of the PodGroup within the Workload that this Pod belongs to. If it doesn't match any existing PodGroup within the Workload, the Pod will remain unschedulable until the Workload object is recreated and observed by the kube-scheduler. It must be a DNS label.", - "type": "string" - }, - "podGroupReplicaKey": { - "description": "PodGroupReplicaKey specifies the replica key of the PodGroup to which this Pod belongs. It is used to distinguish pods belonging to different replicas of the same pod group. The pod group policy is applied separately to each replica. When set, it must be a DNS label.", - "type": "string" - } - }, - "required": [ - "name", - "podGroup" - ], - "type": "object" - }, "io.k8s.apimachinery.pkg.api.resource.Quantity": { "description": "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", "oneOf": [ @@ -4951,310 +4947,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, { "group": "batch", "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -5338,6 +5034,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -5556,15 +5260,29 @@ }, "type": "object" }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", "format": "int32", "type": "integer" }, @@ -5701,310 +5419,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, { "group": "batch", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -6220,6 +5638,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6381,6 +5808,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6514,6 +5950,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6649,6 +6094,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7752,6 +7206,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7887,6 +7350,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9018,6 +8490,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9179,6 +8660,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9350,6 +8840,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9531,6 +9030,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9702,6 +9210,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9883,6 +9400,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__certificates.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__certificates.k8s.io__v1_openapi.json index 980da428dc7a3..c477a95e82c2e 100644 --- a/api/openapi-spec/v3/apis__certificates.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__certificates.k8s.io__v1_openapi.json @@ -395,310 +395,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "certificates.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -725,6 +425,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -943,6 +651,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1088,310 +810,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, { "group": "certificates.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1567,6 +989,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1702,6 +1133,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3112,6 +2552,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3283,6 +2732,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__certificates.k8s.io__v1alpha1_openapi.json b/api/openapi-spec/v3/apis__certificates.k8s.io__v1alpha1_openapi.json index 19759ae02dd9e..3c6fca8ef98c0 100644 --- a/api/openapi-spec/v3/apis__certificates.k8s.io__v1alpha1_openapi.json +++ b/api/openapi-spec/v3/apis__certificates.k8s.io__v1alpha1_openapi.json @@ -273,310 +273,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "certificates.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -603,6 +303,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -821,6 +529,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -966,310 +688,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, { "group": "certificates.k8s.io", "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1445,6 +867,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1580,6 +1011,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2372,6 +1812,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2543,6 +1992,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__certificates.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__certificates.k8s.io__v1beta1_openapi.json index 4f39f990d9341..b56cd865e28d5 100644 --- a/api/openapi-spec/v3/apis__certificates.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__certificates.k8s.io__v1beta1_openapi.json @@ -222,7 +222,7 @@ "type": "string" }, "pkixPublicKey": { - "description": "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", + "description": "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty. Signer implementations should extract the public key from the StubPKCS10Request field.", "format": "byte", "type": "string" }, @@ -237,7 +237,7 @@ "type": "string" }, "proofOfPossession": { - "description": "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).", + "description": "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.", "format": "byte", "type": "string" }, @@ -256,6 +256,11 @@ "description": "signerName indicates the requested signer.\n\nAll signer names beginning with `kubernetes.io` are reserved for use by the Kubernetes project. There is currently one well-known signer documented by the Kubernetes project, `kubernetes.io/kube-apiserver-client-pod`, which will issue client certificates understood by kube-apiserver. It is currently unimplemented.", "type": "string" }, + "stubPKCS10Request": { + "description": "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost signer implementations will ignore the contents of the CSR except to extract the subject public key. The API server automatically verifies the CSR signature during admission, so the signer does not need to repeat the verification. CSRs generated by kubelet are completely empty.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", + "format": "byte", + "type": "string" + }, "unverifiedUserAnnotations": { "additionalProperties": { "default": "", @@ -273,8 +278,7 @@ "serviceAccountUID", "nodeName", "nodeUID", - "pkixPublicKey", - "proofOfPossession" + "stubPKCS10Request" ], "type": "object" }, @@ -543,310 +547,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "certificates.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -873,6 +577,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1081,463 +793,177 @@ "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", "properties": { "resourceVersion": { - "description": "Specifies the target ResourceVersion", - "type": "string" - }, - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "format": "int32", - "type": "integer" - }, - "details": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - } - ], - "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ], - "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" - }, - "reason": { - "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", - "type": "string" - }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "Specifies the target ResourceVersion", + "type": "string" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "uid": { + "description": "Specifies the target UID.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", + "format": "int32", + "type": "integer" }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" + "details": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" + } + ], + "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "message": { + "description": "A human-readable description of the status of this operation.", + "type": "string" }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "reason": { + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "resource.k8s.io", - "kind": "WatchEvent", + "group": "", + "kind": "Status", "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" }, { - "group": "storagemigration.k8s.io", + "group": "certificates.k8s.io", "kind": "WatchEvent", "version": "v1beta1" } @@ -1715,6 +1141,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1850,6 +1285,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2614,6 +2058,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2749,6 +2202,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3880,6 +3342,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4041,6 +3512,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4212,6 +3692,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4383,6 +3872,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4564,6 +4062,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4725,6 +4232,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__coordination.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__coordination.k8s.io__v1_openapi.json index 1bcf968e72352..9d07df499730d 100644 --- a/api/openapi-spec/v3/apis__coordination.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__coordination.k8s.io__v1_openapi.json @@ -296,310 +296,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, { "group": "coordination.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -626,6 +326,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -849,6 +557,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -994,310 +716,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, { "group": "coordination.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1501,6 +923,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1634,6 +1065,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1769,6 +1209,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2581,6 +2030,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2752,6 +2210,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2933,6 +2400,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__coordination.k8s.io__v1alpha2_openapi.json b/api/openapi-spec/v3/apis__coordination.k8s.io__v1alpha2_openapi.json index 913b8d3715294..b1d8d8e72bbf8 100644 --- a/api/openapi-spec/v3/apis__coordination.k8s.io__v1alpha2_openapi.json +++ b/api/openapi-spec/v3/apis__coordination.k8s.io__v1alpha2_openapi.json @@ -31,6 +31,9 @@ "description": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -297,310 +300,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "coordination.k8s.io", "kind": "DeleteOptions", "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -627,6 +330,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -850,6 +561,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -995,310 +720,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, { "group": "coordination.k8s.io", "kind": "WatchEvent", "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1502,6 +927,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1635,6 +1069,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1770,6 +1213,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2582,6 +2034,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2753,6 +2214,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2934,6 +2404,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__coordination.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__coordination.k8s.io__v1beta1_openapi.json index d062175244bb1..89a7b9bf7f0d9 100644 --- a/api/openapi-spec/v3/apis__coordination.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__coordination.k8s.io__v1beta1_openapi.json @@ -31,6 +31,9 @@ "description": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -297,310 +300,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, { "group": "coordination.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -627,6 +330,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -850,6 +561,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -995,310 +720,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, { "group": "coordination.k8s.io", "kind": "WatchEvent", "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1502,6 +927,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1635,6 +1069,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1770,6 +1213,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2582,6 +2034,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2753,6 +2214,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2934,6 +2404,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__discovery.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__discovery.k8s.io__v1_openapi.json index 64d668dd8031d..7dbd793eac1cf 100644 --- a/api/openapi-spec/v3/apis__discovery.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__discovery.k8s.io__v1_openapi.json @@ -226,8 +226,7 @@ } }, "required": [ - "addressType", - "endpoints" + "addressType" ], "type": "object", "x-kubernetes-group-version-kind": [ @@ -478,310 +477,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "discovery.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -808,6 +507,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1026,6 +733,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1171,310 +892,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, { "group": "discovery.k8s.io", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1678,6 +1099,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1811,6 +1241,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1946,6 +1385,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2758,6 +2206,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2929,6 +2386,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3110,6 +2576,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__events.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__events.k8s.io__v1_openapi.json index 66270d22eccee..8b3975e1dfb71 100644 --- a/api/openapi-spec/v3/apis__events.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__events.k8s.io__v1_openapi.json @@ -406,310 +406,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "events.k8s.io", "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -736,6 +436,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -959,6 +667,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1104,310 +826,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, { "group": "events.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1611,6 +1033,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1744,6 +1175,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1879,6 +1319,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2691,6 +2140,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2862,6 +2320,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3043,6 +2510,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__flowcontrol.apiserver.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__flowcontrol.apiserver.k8s.io__v1_openapi.json index fb04fcac3dfa4..103592ef39c56 100644 --- a/api/openapi-spec/v3/apis__flowcontrol.apiserver.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__flowcontrol.apiserver.k8s.io__v1_openapi.json @@ -897,310 +897,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "flowcontrol.apiserver.k8s.io", "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1227,6 +927,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1445,6 +1153,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1590,310 +1312,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, { "group": "flowcontrol.apiserver.k8s.io", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -2069,6 +1491,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2204,6 +1635,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3277,6 +2717,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3412,6 +2861,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4513,6 +3971,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4684,6 +4151,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4845,6 +4321,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5016,6 +4501,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__internal.apiserver.k8s.io__v1alpha1_openapi.json b/api/openapi-spec/v3/apis__internal.apiserver.k8s.io__v1alpha1_openapi.json index 29a9331d6bda5..59b96d7de7884 100644 --- a/api/openapi-spec/v3/apis__internal.apiserver.k8s.io__v1alpha1_openapi.json +++ b/api/openapi-spec/v3/apis__internal.apiserver.k8s.io__v1alpha1_openapi.json @@ -5,11 +5,11 @@ "description": "An API server instance reports the version it can decode and the version it encodes objects to when persisting objects in the backend.", "properties": { "apiServerID": { - "description": "The ID of the reporting API server.", + "description": "apiServerID is the ID of the reporting API server.", "type": "string" }, "decodableVersions": { - "description": "The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", + "description": "decodableVersions are the encoding versions the API server can handle to decode. The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", "items": { "default": "", "type": "string" @@ -18,11 +18,11 @@ "x-kubernetes-list-type": "set" }, "encodingVersion": { - "description": "The API server encodes the object to this version when persisting it in the backend (e.g., etcd).", + "description": "encodingVersion the API server encodes the object to when persisting it in the backend (e.g., etcd).", "type": "string" }, "servedVersions": { - "description": "The API server can serve these versions. DecodableVersions must include all ServedVersions.", + "description": "servedVersions lists all versions the API server can serve. DecodableVersions must include all ServedVersions.", "items": { "default": "", "type": "string" @@ -31,6 +31,11 @@ "x-kubernetes-list-type": "set" } }, + "required": [ + "apiServerID", + "encodingVersion", + "decodableVersions" + ], "type": "object" }, "io.k8s.api.apiserverinternal.v1alpha1.StorageVersion": { @@ -51,7 +56,7 @@ } ], "default": {}, - "description": "The name is .." + "description": "metadata is the standard object metadata. The name is .." }, "spec": { "allOf": [ @@ -60,7 +65,7 @@ } ], "default": {}, - "description": "Spec is an empty spec. It is here to comply with Kubernetes API style." + "description": "spec is an empty spec. It is here to comply with Kubernetes API style." }, "status": { "allOf": [ @@ -69,12 +74,11 @@ } ], "default": {}, - "description": "API server instances report the version they can decode and the version they encode objects to when persisting objects in the backend." + "description": "status on the version the API server instance can decode from and encode objects to when persisting objects in the backend." } }, "required": [ - "spec", - "status" + "metadata" ], "type": "object", "x-kubernetes-group-version-kind": [ @@ -94,30 +98,30 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "Last time the condition transitioned from one status to another." + "description": "lastTransitionTime is the last time the condition transitioned from one status to another." }, "message": { - "description": "A human readable message indicating details about the transition.", + "description": "message is a human readable string indicating details about the transition.", "type": "string" }, "observedGeneration": { - "description": "If set, this represents the .metadata.generation that the condition was set based upon.", + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set.", "format": "int64", "type": "integer" }, "reason": { "default": "", - "description": "The reason for the condition's last transition.", + "description": "reason for the condition's last transition.", "type": "string" }, "status": { "default": "", - "description": "Status of the condition, one of True, False, Unknown.", + "description": "status of the condition, one of True, False, Unknown.", "type": "string" }, "type": { "default": "", - "description": "Type of the condition.", + "description": "type of the condition.", "type": "string" } }, @@ -182,11 +186,11 @@ "description": "API server instances report the versions they can decode and the version they encode objects to when persisting objects in the backend.", "properties": { "commonEncodingVersion": { - "description": "If all API server instances agree on the same encoding storage version, then this field is set to that version. Otherwise this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", + "description": "commonEncodingVersion is set to an encoding storage version if all API server instances share that same version. If they don't share one storage version, this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", "type": "string" }, "conditions": { - "description": "The latest available observations of the storageVersion's state.", + "description": "conditions lists the latest available observations of the storageVersion's state.", "items": { "allOf": [ { @@ -202,7 +206,7 @@ "x-kubernetes-list-type": "map" }, "storageVersions": { - "description": "The reported versions per API server instance.", + "description": "storageVersions lists the reported versions per API server instance.", "items": { "allOf": [ { @@ -387,310 +391,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "internal.apiserver.k8s.io", "kind": "DeleteOptions", "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -717,6 +421,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -935,6 +647,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1080,310 +806,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, { "group": "internal.apiserver.k8s.io", "kind": "WatchEvent", "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1559,6 +985,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1694,6 +1129,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2795,6 +2239,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2966,6 +2419,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__networking.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__networking.k8s.io__v1_openapi.json index cc4bdb4a2fb5e..2fdae7a49c298 100644 --- a/api/openapi-spec/v3/apis__networking.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__networking.k8s.io__v1_openapi.json @@ -105,6 +105,9 @@ "description": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -1271,310 +1274,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "networking.k8s.io", "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1658,6 +1361,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1876,6 +1587,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1950,381 +1675,81 @@ "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "scheduling.k8s.io", + "group": "", "kind": "WatchEvent", "version": "v1" }, { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", + "group": "networking.k8s.io", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -2512,6 +1937,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2647,6 +2081,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3439,6 +2882,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3572,6 +3024,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3707,6 +3168,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4471,6 +3941,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4606,6 +4085,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5709,6 +5197,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5844,6 +5341,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6656,6 +6162,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6789,6 +6304,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6924,6 +6448,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8025,6 +7558,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8196,6 +7738,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8357,6 +7908,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8518,6 +8078,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8689,6 +8258,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8860,6 +8438,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9041,6 +8628,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9212,6 +8808,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9393,6 +8998,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9554,6 +9168,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9715,6 +9338,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9886,6 +9518,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__networking.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__networking.k8s.io__v1beta1_openapi.json index 5b05e1142bcf3..54ad9e088abd7 100644 --- a/api/openapi-spec/v3/apis__networking.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__networking.k8s.io__v1beta1_openapi.json @@ -31,6 +31,9 @@ "description": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, + "required": [ + "spec" + ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -473,310 +476,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "networking.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -803,6 +506,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1009,465 +720,179 @@ }, "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "resourceVersion": { - "description": "Specifies the target ResourceVersion", - "type": "string" - }, - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "format": "int32", - "type": "integer" - }, - "details": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - } - ], - "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ], - "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" - }, - "reason": { - "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", - "type": "string" - }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "properties": { + "resourceVersion": { + "description": "Specifies the target ResourceVersion", + "type": "string" }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "uid": { + "description": "Specifies the target UID.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", + "format": "int32", + "type": "integer" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "details": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" + } + ], + "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "message": { + "description": "A human-readable description of the status of this operation.", + "type": "string" }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" + "reason": { + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", + "group": "", + "kind": "Status", "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", "version": "v1" }, { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", + "group": "networking.k8s.io", "kind": "WatchEvent", "version": "v1beta1" } @@ -1645,6 +1070,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1780,6 +1214,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2544,6 +1987,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2679,6 +2131,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3780,6 +3241,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3951,6 +3421,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4112,6 +3591,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4283,6 +3771,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__node.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__node.k8s.io__v1_openapi.json index 08fd541701893..89931b5000a89 100644 --- a/api/openapi-spec/v3/apis__node.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__node.k8s.io__v1_openapi.json @@ -346,310 +346,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "node.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -676,6 +376,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -894,6 +602,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1039,310 +761,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, { "group": "node.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1518,6 +940,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1653,6 +1084,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2445,6 +1885,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2616,6 +2065,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__policy__v1_openapi.json b/api/openapi-spec/v3/apis__policy__v1_openapi.json index bd1134c93f57a..83d090288b015 100644 --- a/api/openapi-spec/v3/apis__policy__v1_openapi.json +++ b/api/openapi-spec/v3/apis__policy__v1_openapi.json @@ -187,12 +187,6 @@ "type": "integer" } }, - "required": [ - "disruptionsAllowed", - "currentHealthy", - "desiredHealthy", - "expectedPods" - ], "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { @@ -408,310 +402,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "policy", "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -795,6 +489,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1013,6 +715,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1158,310 +874,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, { "group": "policy", "kind": "WatchEvent", "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1649,6 +1065,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1784,6 +1209,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2915,6 +2349,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3086,6 +2529,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3267,6 +2719,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3428,6 +2889,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__rbac.authorization.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__rbac.authorization.k8s.io__v1_openapi.json index 525506b471ed4..0aa983a56c9da 100644 --- a/api/openapi-spec/v3/apis__rbac.authorization.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__rbac.authorization.k8s.io__v1_openapi.json @@ -478,7 +478,6 @@ } }, "required": [ - "apiGroup", "kind", "name" ], @@ -681,310 +680,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, { "group": "rbac.authorization.k8s.io", "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -1068,6 +767,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -1286,6 +993,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1323,418 +1044,118 @@ "default": {}, "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" }, - "reason": { - "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", - "type": "string" - }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "reason": { + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "resource.k8s.io", - "kind": "WatchEvent", + "group": "", + "kind": "Status", "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" }, { - "group": "storagemigration.k8s.io", + "group": "rbac.authorization.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" } ] }, @@ -1910,6 +1331,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2045,6 +1475,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2809,6 +2248,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2944,6 +2392,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3708,6 +3165,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3843,6 +3309,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4627,6 +4102,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4762,6 +4246,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5574,6 +5067,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5735,6 +5237,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5896,6 +5407,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6067,6 +5587,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6228,6 +5757,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6399,6 +5937,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6570,6 +6117,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6751,6 +6307,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6922,6 +6487,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7103,6 +6677,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7264,6 +6847,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7425,6 +7017,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1_openapi.json index 03eadd66258fc..52cd0ca2a5329 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1_openapi.json @@ -157,7 +157,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." + "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." }, "devices": { "allOf": [ @@ -184,7 +184,7 @@ "properties": { "expression": { "default": "", - "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", "type": "string" } }, @@ -337,7 +337,7 @@ "type": "object" }, "bindingConditions": { - "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -346,7 +346,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -355,7 +355,7 @@ "x-kubernetes-list-type": "atomic" }, "bindsToNode": { - "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "type": "boolean" }, "capacity": { @@ -388,6 +388,18 @@ "description": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", "type": "string" }, + "nodeAllocatableResourceMappings": { + "additionalProperties": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1.NodeAllocatableResourceMapping" + } + ], + "default": {} + }, + "description": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + "type": "object" + }, "nodeName": { "description": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", "type": "string" @@ -401,7 +413,7 @@ "description": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set." }, "taints": { - "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -489,18 +501,55 @@ "description": "BoolValue is a true/false value.", "type": "boolean" }, + "bools": { + "description": "BoolValues is a non-empty list of true/false values.", + "items": { + "default": false, + "type": "boolean" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "int": { "description": "IntValue is a number.", "format": "int64", "type": "integer" }, + "ints": { + "description": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "string": { "description": "StringValue is a string. Must not be longer than 64 characters.", "type": "string" }, + "strings": { + "description": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "version": { "description": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", "type": "string" + }, + "versions": { + "description": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" } }, "type": "object" @@ -599,7 +648,7 @@ "type": "object" }, "io.k8s.api.resource.v1.DeviceClass": { - "description": "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -716,7 +765,7 @@ "x-kubernetes-list-type": "atomic" }, "extendedResourceName": { - "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", "type": "string" }, "selectors": { @@ -739,11 +788,11 @@ "description": "DeviceConstraint must have exactly one field set besides Requests.", "properties": { "distinctAttribute": { - "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", "type": "string" }, "matchAttribute": { - "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as single-element lists for backward compatibility.\n\nMust include the domain qualifier.", "type": "string" }, "requests": { @@ -824,11 +873,11 @@ "description": "DeviceRequestAllocationResult contains the allocation result for one request.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "bindingConditions": { - "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -837,7 +886,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -877,7 +926,7 @@ "type": "string" }, "tolerations": { - "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -956,7 +1005,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -994,7 +1043,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -1039,7 +1088,7 @@ "description": "ExactDeviceRequest is a request for one or more identical devices.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "allocationMode": { @@ -1078,7 +1127,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -1100,11 +1149,11 @@ "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { "hardwareAddress": { - "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", "type": "string" }, "interfaceName": { - "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "type": "string" }, "ips": { @@ -1119,6 +1168,24 @@ }, "type": "object" }, + "io.k8s.api.resource.v1.NodeAllocatableResourceMapping": { + "description": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "properties": { + "allocationMultiplier": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity" + } + ], + "description": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16." + }, + "capacityKey": { + "description": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1.OpaqueDeviceConfiguration": { "description": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "properties": { @@ -1143,7 +1210,7 @@ "type": "object" }, "io.k8s.api.resource.v1.ResourceClaim": { - "description": "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -1335,7 +1402,7 @@ "type": "object" }, "io.k8s.api.resource.v1.ResourceClaimTemplate": { - "description": "ResourceClaimTemplate is used to produce ResourceClaim objects.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "ResourceClaimTemplate is used to produce ResourceClaim objects.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -1477,7 +1544,7 @@ "type": "object" }, "io.k8s.api.resource.v1.ResourceSlice": { - "description": "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "description": "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -1857,310 +1924,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "resource.k8s.io", "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -2187,6 +1954,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -2405,10 +2180,24 @@ }, "type": "object" }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", "properties": { - "apiVersion": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, @@ -2443,417 +2232,117 @@ "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" }, "reason": { - "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", - "type": "string" - }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "group": "", + "kind": "Status", + "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" }, { - "group": "storagemigration.k8s.io", + "group": "resource.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" } ] }, @@ -3029,6 +2518,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3164,6 +2662,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3928,6 +3435,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4063,6 +3579,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5166,6 +4691,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5301,6 +4835,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6113,6 +5656,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6274,6 +5826,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6407,6 +5968,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6542,6 +6112,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7334,6 +6913,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7505,6 +7093,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7676,6 +7273,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7857,6 +7463,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8028,6 +7643,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8209,6 +7833,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8370,6 +8003,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8531,6 +8173,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8692,6 +8343,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8863,6 +8523,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json index 1f20120ccae4e..9f592627cd56e 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json @@ -20,7 +20,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -197,6 +197,229 @@ }, "type": "object" }, + "io.k8s.api.resource.v1alpha3.PoolStatus": { + "description": "PoolStatus contains status information for a single resource pool.", + "properties": { + "allocatedDevices": { + "description": "AllocatedDevices is the number of devices currently allocated to claims. A value of 0 means no devices are allocated. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "availableDevices": { + "description": "AvailableDevices is the number of devices available for allocation. This equals TotalDevices - AllocatedDevices - UnavailableDevices. A value of 0 means no devices are currently available. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "driver": { + "description": "Driver is the DRA driver name for this pool. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + "type": "string" + }, + "generation": { + "default": 0, + "description": "Generation is the pool generation observed across all ResourceSlices in this pool. Only the latest generation is reported. During a generation rollout, if not all slices at the latest generation have been published, the pool is included with a validationError and device counts unset.", + "format": "int64", + "type": "integer" + }, + "nodeName": { + "description": "NodeName is the node this pool is associated with. When omitted, the pool is not associated with a specific node. Must be a valid DNS subdomain name (RFC1123).", + "type": "string" + }, + "poolName": { + "description": "PoolName is the name of the pool. Must be a valid resource pool name (DNS subdomains separated by \"/\").", + "type": "string" + }, + "resourceSliceCount": { + "description": "ResourceSliceCount is the number of ResourceSlices that make up this pool. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "totalDevices": { + "description": "TotalDevices is the total number of devices in the pool across all slices. A value of 0 means the pool has no devices. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "unavailableDevices": { + "description": "UnavailableDevices is the number of devices that are not available due to taints or other conditions, but are not allocated. A value of 0 means all unallocated devices are available. May be unset when validationError is set.", + "format": "int32", + "type": "integer" + }, + "validationError": { + "description": "ValidationError is set when the pool's data could not be fully validated (e.g., incomplete slice publication). When set, device count fields and ResourceSliceCount may be unset.", + "type": "string" + } + }, + "required": [ + "driver", + "poolName", + "generation" + ], + "type": "object" + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest": { + "description": "ResourcePoolStatusRequest triggers a one-time calculation of resource pool status based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. Users should delete and recreate requests to get updated information.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ], + "default": {}, + "description": "Standard object metadata" + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec" + } + ], + "default": {}, + "description": "Spec defines the filters for which pools to include in the status. The spec is immutable once created." + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus" + } + ], + "description": "Status is populated by the controller with the calculated pool status. When status is non-nil, the request is considered complete and the entire object becomes immutable." + } + }, + "required": [ + "metadata", + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" + } + ] + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList": { + "description": "ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of ResourcePoolStatusRequests.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + ], + "default": {} + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequestList", + "version": "v1alpha3" + } + ] + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec": { + "description": "ResourcePoolStatusRequestSpec defines the filters for the pool status request.", + "properties": { + "driver": { + "default": "", + "description": "Driver specifies the DRA driver name to filter pools. Only pools from ResourceSlices with this driver will be included. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + "type": "string" + }, + "limit": { + "default": 100, + "description": "Limit optionally specifies the maximum number of pools to return in the status. If more pools match the filter criteria, the response will be truncated (i.e., len(status.pools) < status.poolCount).\n\nDefault: 100 Minimum: 1 Maximum: 1000", + "format": "int32", + "type": "integer" + }, + "poolName": { + "description": "PoolName optionally filters to a specific pool name. If not specified, all pools from the specified driver are included. When specified, must be a non-empty valid resource pool name (DNS subdomains separated by \"/\").", + "type": "string" + } + }, + "required": [ + "driver" + ], + "type": "object" + }, + "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus": { + "description": "ResourcePoolStatusRequestStatus contains the calculated pool status information.", + "properties": { + "conditions": { + "description": "Conditions provide information about the state of the request. A condition with type=Complete or type=Failed will always be set when the status is populated.\n\nKnown condition types: - \"Complete\": True when the request has been processed successfully - \"Failed\": True when the request could not be processed", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "poolCount": { + "description": "PoolCount is the total number of pools that matched the filter criteria, regardless of truncation. This helps users understand how many pools exist even when the response is truncated. A value of 0 means no pools matched the filter criteria.", + "format": "int32", + "type": "integer" + }, + "pools": { + "description": "Pools contains the first `spec.limit` matching pools, sorted by driver then pool name. If `len(pools) < poolCount`, the list was truncated. When omitted, no pools matched the request filters.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.PoolStatus" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "poolCount" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { "description": "APIResource specifies the name of a resource and whether it is namespaced.", "properties": { @@ -411,369 +634,77 @@ "version": "v1" }, { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", + "group": "resource.k8s.io", "kind": "DeleteOptions", - "version": "v1" + "version": "v1alpha3" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { + "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "properties": { + "continue": { + "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + "type": "string" }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" + "remainingItemCount": { + "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "format": "int64", + "type": "integer" }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "resourceVersion": { + "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "fieldsV1": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" + } + ], + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { - "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { - "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", - "properties": { - "continue": { - "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", - "type": "string" - }, - "remainingItemCount": { - "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", - "format": "int64", - "type": "integer" - }, - "resourceVersion": { - "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { - "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", - "type": "string" - }, - "fieldsType": { - "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", - "type": "string" - }, - "fieldsV1": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" - } - ], - "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." - }, - "manager": { - "description": "Manager is an identifier of the workflow managing these fields.", - "type": "string" - }, - "operation": { - "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", - "type": "string" - }, - "subresource": { - "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", - "type": "string" + "subresource": { + "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + "type": "string" }, "time": { "allOf": [ @@ -958,6 +889,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1104,361 +1049,1281 @@ "version": "v1" }, { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", + "group": "resource.k8s.io", "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1alpha3" + } + ] + }, + "io.k8s.apimachinery.pkg.runtime.RawExtension": { + "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", + "type": "object" + } + }, + "securitySchemes": { + "BearerToken": { + "description": "Bearer Token authentication", + "in": "header", + "name": "authorization", + "type": "apiKey" + } + } + }, + "info": { + "title": "Kubernetes", + "version": "unversioned" + }, + "openapi": "3.0.0", + "paths": { + "/apis/resource.k8s.io/v1alpha3/": { + "get": { + "description": "get available resources", + "operationId": "getResourceV1alpha3APIResources", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + } + }, + "description": "OK" }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ] + } + }, + "/apis/resource.k8s.io/v1alpha3/devicetaintrules": { + "delete": { + "description": "delete collection of DeviceTaintRule", + "operationId": "deleteResourceV1alpha3CollectionDeviceTaintRule", + "parameters": [ { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } }, { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } }, { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "get": { + "description": "list or watch objects of kind DeviceTaintRule", + "operationId": "listResourceV1alpha3DeviceTaintRule", + "parameters": [ { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } }, { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } }, { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + } + } + }, + "description": "OK" }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "parameters": [ + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a DeviceTaintRule", + "operationId": "createResourceV1alpha3DeviceTaintRule", + "parameters": [ { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "Created" }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + } + }, + "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}": { + "delete": { + "description": "delete a DeviceTaintRule", + "operationId": "deleteResourceV1alpha3DeviceTaintRule", + "parameters": [ { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } }, { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } }, { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "Accepted" }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "get": { + "description": "read the specified DeviceTaintRule", + "operationId": "readResourceV1alpha3DeviceTaintRule", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "parameters": [ + { + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update the specified DeviceTaintRule", + "operationId": "patchResourceV1alpha3DeviceTaintRule", + "parameters": [ { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "put": { + "description": "replace the specified DeviceTaintRule", + "operationId": "replaceResourceV1alpha3DeviceTaintRule", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + } + }, + "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}/status": { + "get": { + "description": "read status of the specified DeviceTaintRule", + "operationId": "readResourceV1alpha3DeviceTaintRuleStatus", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "parameters": [ + { + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update status of the specified DeviceTaintRule", + "operationId": "patchResourceV1alpha3DeviceTaintRuleStatus", + "parameters": [ { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "Created" }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } + }, + "put": { + "description": "replace status of the specified DeviceTaintRule", + "operationId": "replaceResourceV1alpha3DeviceTaintRuleStatus", + "parameters": [ { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } }, { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } } - ] - }, - "io.k8s.apimachinery.pkg.runtime.RawExtension": { - "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", - "type": "object" - } - }, - "securitySchemes": { - "BearerToken": { - "description": "Bearer Token authentication", - "in": "header", - "name": "authorization", - "type": "apiKey" - } - } - }, - "info": { - "title": "Kubernetes", - "version": "unversioned" - }, - "openapi": "3.0.0", - "paths": { - "/apis/resource.k8s.io/v1alpha3/": { - "get": { - "description": "get available resources", - "operationId": "getResourceV1alpha3APIResources", + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" } } }, - "description": "OK" + "description": "Created" }, "401": { "description": "Unauthorized" @@ -1466,13 +2331,19 @@ }, "tags": [ "resource_v1alpha3" - ] + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1alpha3" + } } }, - "/apis/resource.k8s.io/v1alpha3/devicetaintrules": { + "/apis/resource.k8s.io/v1alpha3/resourcepoolstatusrequests": { "delete": { - "description": "delete collection of DeviceTaintRule", - "operationId": "deleteResourceV1alpha3CollectionDeviceTaintRule", + "description": "delete collection of ResourcePoolStatusRequest", + "operationId": "deleteResourceV1alpha3CollectionResourcePoolStatusRequest", "parameters": [ { "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", @@ -1582,6 +2453,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1637,13 +2517,13 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } }, "get": { - "description": "list or watch objects of kind DeviceTaintRule", - "operationId": "listResourceV1alpha3DeviceTaintRule", + "description": "list or watch objects of kind ResourcePoolStatusRequest", + "operationId": "listResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", @@ -1691,87 +2571,408 @@ } }, { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" + } + }, + "parameters": [ + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a ResourcePoolStatusRequest", + "operationId": "createResourceV1alpha3ResourcePoolStatusRequest", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" + } + } + }, + "/apis/resource.k8s.io/v1alpha3/resourcepoolstatusrequests/{name}": { + "delete": { + "description": "delete a ResourcePoolStatusRequest", + "operationId": "deleteResourceV1alpha3ResourcePoolStatusRequest", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "in": "query", - "name": "resourceVersion", + "name": "dryRun", "schema": { "type": "string", "uniqueItems": true } }, { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", "in": "query", - "name": "resourceVersionMatch", + "name": "gracePeriodSeconds", "schema": { - "type": "string", + "type": "integer", "uniqueItems": true } }, { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", "in": "query", - "name": "sendInitialEvents", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", "schema": { "type": "boolean", "uniqueItems": true } }, { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", "in": "query", - "name": "timeoutSeconds", + "name": "orphanDependents", "schema": { - "type": "integer", + "type": "boolean", "uniqueItems": true } }, { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", "in": "query", - "name": "watch", + "name": "propagationPolicy", "schema": { - "type": "boolean", + "type": "string", "uniqueItems": true } } ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, - "application/cbor-seq": { + "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, - "application/json": { + "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, - "application/json;stream=watch": { + "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, - "application/vnd.kubernetes.protobuf;stream=watch": { + "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1alpha3" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourcePoolStatusRequest", + "version": "v1alpha3" + } + }, + "get": { + "description": "read the specified ResourcePoolStatusRequest", + "operationId": "readResourceV1alpha3ResourcePoolStatusRequest", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRuleList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -1784,14 +2985,24 @@ "tags": [ "resource_v1alpha3" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } }, "parameters": [ + { + "description": "name of the ResourcePoolStatusRequest", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", "in": "query", @@ -1802,9 +3013,9 @@ } } ], - "post": { - "description": "create a DeviceTaintRule", - "operationId": "createResourceV1alpha3DeviceTaintRule", + "patch": { + "description": "partially update the specified ResourcePoolStatusRequest", + "operationId": "patchResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -1816,7 +3027,7 @@ } }, { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", "in": "query", "name": "fieldManager", "schema": { @@ -1832,13 +3043,42 @@ "type": "string", "uniqueItems": true } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } } ], "requestBody": { "content": { - "*/*": { + "application/apply-patch+cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" } } }, @@ -1849,22 +3089,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -1874,52 +3114,27 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, "description": "Created" }, - "202": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - } - }, - "description": "Accepted" - }, "401": { "description": "Unauthorized" } @@ -1927,18 +3142,16 @@ "tags": [ "resource_v1alpha3" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } - } - }, - "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}": { - "delete": { - "description": "delete a DeviceTaintRule", - "operationId": "deleteResourceV1alpha3DeviceTaintRule", + }, + "put": { + "description": "replace the specified ResourcePoolStatusRequest", + "operationId": "replaceResourceV1alpha3ResourcePoolStatusRequest", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -1950,36 +3163,18 @@ } }, { - "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", - "in": "query", - "name": "gracePeriodSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", - "in": "query", - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", "in": "query", - "name": "orphanDependents", + "name": "fieldManager", "schema": { - "type": "boolean", + "type": "string", "uniqueItems": true } }, { - "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", "in": "query", - "name": "propagationPolicy", + "name": "fieldValidation", "schema": { "type": "string", "uniqueItems": true @@ -1990,61 +3185,62 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } - } + }, + "required": true }, "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, "description": "OK" }, - "202": { + "201": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, - "description": "Accepted" + "description": "Created" }, "401": { "description": "Unauthorized" @@ -2053,37 +3249,39 @@ "tags": [ "resource_v1alpha3" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } - }, - "get": { - "description": "read the specified DeviceTaintRule", - "operationId": "readResourceV1alpha3DeviceTaintRule", + } + }, + "/apis/resource.k8s.io/v1alpha3/resourcepoolstatusrequests/{name}/status": { + "get": { + "description": "read status of the specified ResourcePoolStatusRequest", + "operationId": "readResourceV1alpha3ResourcePoolStatusRequestStatus", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -2099,13 +3297,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } }, "parameters": [ { - "description": "name of the DeviceTaintRule", + "description": "name of the ResourcePoolStatusRequest", "in": "path", "name": "name", "required": true, @@ -2125,8 +3323,8 @@ } ], "patch": { - "description": "partially update the specified DeviceTaintRule", - "operationId": "patchResourceV1alpha3DeviceTaintRule", + "description": "partially update status of the specified ResourcePoolStatusRequest", + "operationId": "patchResourceV1alpha3ResourcePoolStatusRequestStatus", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -2200,22 +3398,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -2225,22 +3423,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -2256,13 +3454,13 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } }, "put": { - "description": "replace the specified DeviceTaintRule", - "operationId": "replaceResourceV1alpha3DeviceTaintRule", + "description": "replace status of the specified ResourcePoolStatusRequest", + "operationId": "replaceResourceV1alpha3ResourcePoolStatusRequestStatus", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -2296,7 +3494,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -2307,22 +3505,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -2332,22 +3530,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" } } }, @@ -2363,36 +3561,51 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } } }, - "/apis/resource.k8s.io/v1alpha3/devicetaintrules/{name}/status": { + "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules": { "get": { - "description": "read status of the specified DeviceTaintRule", - "operationId": "readResourceV1alpha3DeviceTaintRuleStatus", + "description": "watch individual changes to a list of DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1alpha3DeviceTaintRuleList", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } } }, @@ -2405,7 +3618,7 @@ "tags": [ "resource_v1alpha3" ], - "x-kubernetes-action": "get", + "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "DeviceTaintRule", @@ -2414,15 +3627,50 @@ }, "parameters": [ { - "description": "name of the DeviceTaintRule", - "in": "path", - "name": "name", - "required": true, + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", "schema": { "type": "string", "uniqueItems": true } }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, { "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", "in": "query", @@ -2431,236 +3679,107 @@ "type": "string", "uniqueItems": true } - } - ], - "patch": { - "description": "partially update status of the specified DeviceTaintRule", - "operationId": "patchResourceV1alpha3DeviceTaintRuleStatus", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", - "in": "query", - "name": "fieldManager", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", - "in": "query", - "name": "force", - "schema": { - "type": "boolean", - "uniqueItems": true - } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true } - ], - "requestBody": { - "content": { - "application/apply-patch+cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - } - }, - "description": "Created" - }, - "401": { - "description": "Unauthorized" + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true } }, - "tags": [ - "resource_v1alpha3" - ], - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "resource.k8s.io", - "kind": "DeviceTaintRule", - "version": "v1alpha3" - } - }, - "put": { - "description": "replace status of the specified DeviceTaintRule", - "operationId": "replaceResourceV1alpha3DeviceTaintRuleStatus", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", - "in": "query", - "name": "fieldManager", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "schema": { - "type": "string", - "uniqueItems": true - } + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - } - }, - "required": true }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules/{name}": { + "get": { + "description": "watch changes to an object of kind DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1alpha3DeviceTaintRule", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/json": { + "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/vnd.kubernetes.protobuf": { + "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { + "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/json": { + "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/vnd.kubernetes.protobuf": { + "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceTaintRule" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } } }, - "description": "Created" + "description": "OK" }, "401": { "description": "Unauthorized" @@ -2669,18 +3788,138 @@ "tags": [ "resource_v1alpha3" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", "kind": "DeviceTaintRule", "version": "v1alpha3" } - } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] }, - "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules": { + "/apis/resource.k8s.io/v1alpha3/watch/resourcepoolstatusrequests": { "get": { - "description": "watch individual changes to a list of DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1alpha3DeviceTaintRuleList", + "description": "watch individual changes to a list of ResourcePoolStatusRequest. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1alpha3ResourcePoolStatusRequestList", "responses": { "200": { "content": { @@ -2732,7 +3971,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } }, @@ -2818,6 +4057,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2838,10 +4086,10 @@ } ] }, - "/apis/resource.k8s.io/v1alpha3/watch/devicetaintrules/{name}": { + "/apis/resource.k8s.io/v1alpha3/watch/resourcepoolstatusrequests/{name}": { "get": { - "description": "watch changes to an object of kind DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1alpha3DeviceTaintRule", + "description": "watch changes to an object of kind ResourcePoolStatusRequest. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1alpha3ResourcePoolStatusRequest", "responses": { "200": { "content": { @@ -2893,7 +4141,7 @@ "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceTaintRule", + "kind": "ResourcePoolStatusRequest", "version": "v1alpha3" } }, @@ -2944,7 +4192,7 @@ } }, { - "description": "name of the DeviceTaintRule", + "description": "name of the ResourcePoolStatusRequest", "in": "path", "name": "name", "required": true, @@ -2989,6 +4237,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json index d3cf12a1f92a1..2ea742721f097 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json @@ -157,7 +157,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." + "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." }, "devices": { "allOf": [ @@ -203,7 +203,7 @@ "type": "object" }, "bindingConditions": { - "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -212,7 +212,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -221,7 +221,7 @@ "x-kubernetes-list-type": "atomic" }, "bindsToNode": { - "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "type": "boolean" }, "capacity": { @@ -249,6 +249,18 @@ "type": "array", "x-kubernetes-list-type": "atomic" }, + "nodeAllocatableResourceMappings": { + "additionalProperties": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping" + } + ], + "default": {} + }, + "description": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + "type": "object" + }, "nodeName": { "description": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", "type": "string" @@ -262,7 +274,7 @@ "description": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set." }, "taints": { - "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -282,7 +294,7 @@ "properties": { "expression": { "default": "", - "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", "type": "string" } }, @@ -503,18 +515,55 @@ "description": "BoolValue is a true/false value.", "type": "boolean" }, + "bools": { + "description": "BoolValues is a non-empty list of true/false values.", + "items": { + "default": false, + "type": "boolean" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "int": { "description": "IntValue is a number.", "format": "int64", "type": "integer" }, + "ints": { + "description": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "string": { "description": "StringValue is a string. Must not be longer than 64 characters.", "type": "string" }, + "strings": { + "description": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "version": { "description": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", "type": "string" + }, + "versions": { + "description": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" } }, "type": "object" @@ -730,7 +779,7 @@ "x-kubernetes-list-type": "atomic" }, "extendedResourceName": { - "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", "type": "string" }, "selectors": { @@ -753,11 +802,11 @@ "description": "DeviceConstraint must have exactly one field set besides Requests.", "properties": { "distinctAttribute": { - "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", "type": "string" }, "matchAttribute": { - "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", "type": "string" }, "requests": { @@ -860,7 +909,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -886,7 +935,7 @@ "type": "boolean" }, "bindingConditions": { - "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -895,7 +944,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -935,7 +984,7 @@ "type": "string" }, "tolerations": { - "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -1014,7 +1063,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -1052,7 +1101,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -1097,11 +1146,11 @@ "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { "hardwareAddress": { - "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", "type": "string" }, "interfaceName": { - "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "type": "string" }, "ips": { @@ -1116,6 +1165,24 @@ }, "type": "object" }, + "io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping": { + "description": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "properties": { + "allocationMultiplier": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity" + } + ], + "description": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16." + }, + "capacityKey": { + "description": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1beta1.OpaqueDeviceConfiguration": { "description": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "properties": { @@ -1855,585 +1922,307 @@ "version": "v1" }, { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", + "group": "resource.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { + "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "properties": { + "continue": { + "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + "type": "string" }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "remainingItemCount": { + "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "format": "int64", + "type": "integer" }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "resourceVersion": { + "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" + "fieldsV1": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" + } + ], + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "subresource": { + "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + "type": "string" }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "time": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "properties": { + "annotations": { + "additionalProperties": { + "default": "", + "type": "string" + }, + "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", + "type": "object" }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "creationTimestamp": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" + "deletionGracePeriodSeconds": { + "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + "format": "int64", + "type": "integer" }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" + "deletionTimestamp": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" + "finalizers": { + "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set", + "x-kubernetes-patch-strategy": "merge" }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" + "generateName": { + "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + "type": "string" }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" + "generation": { + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + "format": "int64", + "type": "integer" }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" + "labels": { + "additionalProperties": { + "default": "", + "type": "string" + }, + "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", + "type": "object" }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "managedFields": { + "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" + "name": { + "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "namespace": { + "description": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", + "type": "string" }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "ownerReferences": { + "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "uid" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "uid", + "x-kubernetes-patch-strategy": "merge" }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" + "resourceVersion": { + "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" + "uid": { + "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { + "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + "properties": { + "apiVersion": { + "default": "", + "description": "API version of the referent.", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "blockOwnerDeletion": { + "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + "type": "boolean" }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "controller": { + "description": "If true, this reference points to the managing controller.", + "type": "boolean" }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" + "kind": { + "default": "", + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "name": { + "default": "", + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "uid": { + "default": "", + "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" } - ] + }, + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" }, - "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { - "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "io.k8s.apimachinery.pkg.apis.meta.v1.Patch": { + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", "type": "object" }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { - "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", "properties": { - "continue": { - "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", - "type": "string" - }, - "remainingItemCount": { - "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", - "format": "int64", - "type": "integer" - }, "resourceVersion": { - "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "description": "Specifies the target ResourceVersion", "type": "string" }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "uid": { + "description": "Specifies the target UID.", "type": "string" } }, "type": "object" }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { - "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", "properties": { "apiVersion": { - "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, - "fieldsType": { - "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", - "type": "string" + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", + "format": "int32", + "type": "integer" }, - "fieldsV1": { + "details": { "allOf": [ { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" } ], - "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." - }, - "manager": { - "description": "Manager is an identifier of the workflow managing these fields.", - "type": "string" + "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." }, - "operation": { - "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, - "subresource": { - "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + "message": { + "description": "A human-readable description of the status of this operation.", "type": "string" }, - "time": { + "metadata": { "allOf": [ { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ], - "description": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over." - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { - "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", - "properties": { - "annotations": { - "additionalProperties": { - "default": "", - "type": "string" - }, - "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", - "type": "object" - }, - "creationTimestamp": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ], - "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" - }, - "deletionGracePeriodSeconds": { - "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", - "format": "int64", - "type": "integer" - }, - "deletionTimestamp": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ], - "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" - }, - "finalizers": { - "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", - "items": { - "default": "", - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "set", - "x-kubernetes-patch-strategy": "merge" - }, - "generateName": { - "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", - "type": "string" - }, - "generation": { - "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", - "format": "int64", - "type": "integer" - }, - "labels": { - "additionalProperties": { - "default": "", - "type": "string" - }, - "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", - "type": "object" - }, - "managedFields": { - "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "name": { - "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", - "type": "string" - }, - "namespace": { - "description": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", - "type": "string" - }, - "ownerReferences": { - "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-map-keys": [ - "uid" - ], - "x-kubernetes-list-type": "map", - "x-kubernetes-patch-merge-key": "uid", - "x-kubernetes-patch-strategy": "merge" - }, - "resourceVersion": { - "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - }, - "uid": { - "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { - "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", - "properties": { - "apiVersion": { - "default": "", - "description": "API version of the referent.", - "type": "string" - }, - "blockOwnerDeletion": { - "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", - "type": "boolean" - }, - "controller": { - "description": "If true, this reference points to the managing controller.", - "type": "boolean" - }, - "kind": { - "default": "", - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "default": "", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", - "type": "string" - }, - "uid": { - "default": "", - "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "type": "object", - "x-kubernetes-map-type": "atomic" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Patch": { - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "resourceVersion": { - "description": "Specifies the target ResourceVersion", - "type": "string" - }, - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "format": "int32", - "type": "integer" - }, - "details": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - } - ], - "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" } ], "default": {}, @@ -2443,412 +2232,112 @@ "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", "type": "string" }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "group": "", + "kind": "Status", + "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" }, { - "group": "storagemigration.k8s.io", + "group": "resource.k8s.io", "kind": "WatchEvent", "version": "v1beta1" } @@ -3026,6 +2515,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3161,6 +2659,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3925,6 +3432,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4060,6 +3576,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5163,6 +4688,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5298,6 +4832,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6110,6 +5653,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6271,6 +5823,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6404,6 +5965,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6539,6 +6109,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7331,6 +6910,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7502,6 +7090,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7673,6 +7270,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7854,6 +7460,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8025,6 +7640,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8206,6 +7830,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8367,6 +8000,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8528,6 +8170,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8689,6 +8340,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8860,6 +8520,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1beta2_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1beta2_openapi.json index ba43ec7c65ffb..8598cff9b93f6 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1beta2_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1beta2_openapi.json @@ -157,7 +157,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." + "description": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate." }, "devices": { "allOf": [ @@ -184,7 +184,7 @@ "properties": { "expression": { "default": "", - "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "description": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", "type": "string" } }, @@ -337,7 +337,7 @@ "type": "object" }, "bindingConditions": { - "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -346,7 +346,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -355,7 +355,7 @@ "x-kubernetes-list-type": "atomic" }, "bindsToNode": { - "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "type": "boolean" }, "capacity": { @@ -388,6 +388,18 @@ "description": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", "type": "string" }, + "nodeAllocatableResourceMappings": { + "additionalProperties": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping" + } + ], + "default": {} + }, + "description": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + "type": "object" + }, "nodeName": { "description": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", "type": "string" @@ -401,7 +413,7 @@ "description": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set." }, "taints": { - "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -489,18 +501,55 @@ "description": "BoolValue is a true/false value.", "type": "boolean" }, + "bools": { + "description": "BoolValues is a non-empty list of true/false values.", + "items": { + "default": false, + "type": "boolean" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "int": { "description": "IntValue is a number.", "format": "int64", "type": "integer" }, + "ints": { + "description": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "string": { "description": "StringValue is a string. Must not be longer than 64 characters.", "type": "string" }, + "strings": { + "description": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "version": { "description": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", "type": "string" + }, + "versions": { + "description": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" } }, "type": "object" @@ -716,7 +765,7 @@ "x-kubernetes-list-type": "atomic" }, "extendedResourceName": { - "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "description": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", "type": "string" }, "selectors": { @@ -739,11 +788,11 @@ "description": "DeviceConstraint must have exactly one field set besides Requests.", "properties": { "distinctAttribute": { - "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "description": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", "type": "string" }, "matchAttribute": { - "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + "description": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", "type": "string" }, "requests": { @@ -828,7 +877,7 @@ "type": "boolean" }, "bindingConditions": { - "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -837,7 +886,7 @@ "x-kubernetes-list-type": "atomic" }, "bindingFailureConditions": { - "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "description": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "items": { "default": "", "type": "string" @@ -877,7 +926,7 @@ "type": "string" }, "tolerations": { - "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -956,7 +1005,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -994,7 +1043,7 @@ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" } ], - "description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set." + "description": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule)." }, "value": { "description": "The taint value corresponding to the taint key. Must be a label value.", @@ -1007,6 +1056,170 @@ ], "type": "object" }, + "io.k8s.api.resource.v1beta2.DeviceTaintRule": { + "description": "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ], + "default": {}, + "description": "Standard object metadata" + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec" + } + ], + "default": {}, + "description": "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number." + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus" + } + ], + "default": {}, + "description": "Status provides information about what was requested in the spec." + } + }, + "required": [ + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + ] + }, + "io.k8s.api.resource.v1beta2.DeviceTaintRuleList": { + "description": "DeviceTaintRuleList is a collection of DeviceTaintRules.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of DeviceTaintRules.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + ], + "default": {} + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata" + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "resource.k8s.io", + "kind": "DeviceTaintRuleList", + "version": "v1beta2" + } + ] + }, + "io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec": { + "description": "DeviceTaintRuleSpec specifies the selector and one taint.", + "properties": { + "deviceSelector": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintSelector" + } + ], + "description": "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satisfied for a device to match. The empty selector matches all devices. Without a selector, no devices are matches." + }, + "taint": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaint" + } + ], + "default": {}, + "description": "The taint that gets applied to matching devices." + } + }, + "required": [ + "taint" + ], + "type": "object" + }, + "io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus": { + "description": "DeviceTaintRuleStatus provides information about an on-going pod eviction.", + "properties": { + "conditions": { + "description": "Conditions provide information about the state of the DeviceTaintRule and the cluster at some point in time, in a machine-readable and human-readable format.\n\nThe following condition is currently defined as part of this API, more may get added: - Type: EvictionInProgress - Status: True if there are currently pods which need to be evicted, False otherwise\n (includes the effects which don't cause eviction).\n- Reason: not specified, may change - Message: includes information about number of pending pods and already evicted pods\n in a human-readable format, updated periodically, may change\n\nFor `effect: None`, the condition above gets set once for each change to the spec, with the message containing information about what would happen if the effect was `NoExecute`. This feedback can be used to decide whether changing the effect to `NoExecute` will work as intended. It only gets set once to avoid having to constantly update the status.\n\nMust have 8 or fewer entries.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + } + }, + "type": "object" + }, + "io.k8s.api.resource.v1beta2.DeviceTaintSelector": { + "description": "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.", + "properties": { + "device": { + "description": "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.", + "type": "string" + }, + "driver": { + "description": "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.", + "type": "string" + }, + "pool": { + "description": "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1beta2.DeviceToleration": { "description": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple using the matching operator .", "properties": { @@ -1078,7 +1291,7 @@ "x-kubernetes-list-type": "atomic" }, "tolerations": { - "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "items": { "allOf": [ { @@ -1100,11 +1313,11 @@ "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { "hardwareAddress": { - "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "description": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", "type": "string" }, "interfaceName": { - "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "description": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "type": "string" }, "ips": { @@ -1119,6 +1332,24 @@ }, "type": "object" }, + "io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping": { + "description": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "properties": { + "allocationMultiplier": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity" + } + ], + "description": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16." + }, + "capacityKey": { + "description": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "type": "string" + } + }, + "type": "object" + }, "io.k8s.api.resource.v1beta2.OpaqueDeviceConfiguration": { "description": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "properties": { @@ -1858,365 +2089,73 @@ "version": "v1" }, { - "group": "admission.k8s.io", + "group": "resource.k8s.io", "kind": "DeleteOptions", - "version": "v1" + "version": "v1beta2" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { + "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "properties": { + "continue": { + "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + "type": "string" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "remainingItemCount": { + "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "format": "int64", + "type": "integer" }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "resourceVersion": { + "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" + "fieldsV1": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" + } + ], + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { - "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { - "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", - "properties": { - "continue": { - "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", - "type": "string" - }, - "remainingItemCount": { - "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", - "format": "int64", - "type": "integer" - }, - "resourceVersion": { - "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { - "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", - "type": "string" - }, - "fieldsType": { - "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", - "type": "string" - }, - "fieldsV1": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" - } - ], - "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." - }, - "manager": { - "description": "Manager is an identifier of the workflow managing these fields.", - "type": "string" - }, - "operation": { - "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", - "type": "string" + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" }, "subresource": { "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", @@ -2405,6 +2344,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -2551,309 +2504,9 @@ "version": "v1" }, { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", + "group": "resource.k8s.io", "kind": "WatchEvent", "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -3029,6 +2682,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3164,6 +2826,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3173,52 +2844,1281 @@ "uniqueItems": true } }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "responses": { - "200": { + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + }, + "parameters": [ + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a DeviceClass", + "operationId": "createResourceV1beta2DeviceClass", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + } + }, + "/apis/resource.k8s.io/v1beta2/deviceclasses/{name}": { + "delete": { + "description": "delete a DeviceClass", + "operationId": "deleteResourceV1beta2DeviceClass", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + }, + "get": { + "description": "read the specified DeviceClass", + "operationId": "readResourceV1beta2DeviceClass", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + }, + "parameters": [ + { + "description": "name of the DeviceClass", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update the specified DeviceClass", + "operationId": "patchResourceV1beta2DeviceClass", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + }, + "put": { + "description": "replace the specified DeviceClass", + "operationId": "replaceResourceV1beta2DeviceClass", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + } + }, + "/apis/resource.k8s.io/v1beta2/devicetaintrules": { + "delete": { + "description": "delete collection of DeviceTaintRule", + "operationId": "deleteResourceV1beta2CollectionDeviceTaintRule", + "parameters": [ + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + }, + "get": { + "description": "list or watch objects of kind DeviceTaintRule", + "operationId": "listResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRuleList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + }, + "parameters": [ + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a DeviceTaintRule", + "operationId": "createResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + } + }, + "/apis/resource.k8s.io/v1beta2/devicetaintrules/{name}": { + "delete": { + "description": "delete a DeviceTaintRule", + "operationId": "deleteResourceV1beta2DeviceTaintRule", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + } + }, + "description": "OK" + }, + "202": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, - "application/cbor-seq": { + "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, - "application/json": { + "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, - "application/json;stream=watch": { + "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceTaintRule", + "version": "v1beta2" + } + }, + "get": { + "description": "read the specified DeviceTaintRule", + "operationId": "readResourceV1beta2DeviceTaintRule", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, - "application/vnd.kubernetes.protobuf": { + "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, - "application/vnd.kubernetes.protobuf;stream=watch": { + "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClassList" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3231,14 +4131,24 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "list", + "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, "parameters": [ + { + "description": "name of the DeviceTaintRule", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", "in": "query", @@ -3249,9 +4159,9 @@ } } ], - "post": { - "description": "create a DeviceClass", - "operationId": "createResourceV1beta2DeviceClass", + "patch": { + "description": "partially update the specified DeviceTaintRule", + "operationId": "patchResourceV1beta2DeviceTaintRule", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3263,7 +4173,7 @@ } }, { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", "in": "query", "name": "fieldManager", "schema": { @@ -3279,13 +4189,42 @@ "type": "string", "uniqueItems": true } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } } ], "requestBody": { "content": { - "*/*": { + "application/apply-patch+cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" } } }, @@ -3296,22 +4235,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3321,52 +4260,27 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, "description": "Created" }, - "202": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" - } - } - }, - "description": "Accepted" - }, "401": { "description": "Unauthorized" } @@ -3374,18 +4288,16 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "post", + "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } - } - }, - "/apis/resource.k8s.io/v1beta2/deviceclasses/{name}": { - "delete": { - "description": "delete a DeviceClass", - "operationId": "deleteResourceV1beta2DeviceClass", + }, + "put": { + "description": "replace the specified DeviceTaintRule", + "operationId": "replaceResourceV1beta2DeviceTaintRule", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3397,36 +4309,18 @@ } }, { - "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", - "in": "query", - "name": "gracePeriodSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", - "in": "query", - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", "in": "query", - "name": "orphanDependents", + "name": "fieldManager", "schema": { - "type": "boolean", + "type": "string", "uniqueItems": true } }, { - "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", "in": "query", - "name": "propagationPolicy", + "name": "fieldValidation", "schema": { "type": "string", "uniqueItems": true @@ -3437,61 +4331,62 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } - } + }, + "required": true }, "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, "description": "OK" }, - "202": { + "201": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, - "description": "Accepted" + "description": "Created" }, "401": { "description": "Unauthorized" @@ -3500,37 +4395,39 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "delete", + "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } - }, + } + }, + "/apis/resource.k8s.io/v1beta2/devicetaintrules/{name}/status": { "get": { - "description": "read the specified DeviceClass", - "operationId": "readResourceV1beta2DeviceClass", + "description": "read status of the specified DeviceTaintRule", + "operationId": "readResourceV1beta2DeviceTaintRuleStatus", "responses": { "200": { "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3546,13 +4443,13 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, "parameters": [ { - "description": "name of the DeviceClass", + "description": "name of the DeviceTaintRule", "in": "path", "name": "name", "required": true, @@ -3572,8 +4469,8 @@ } ], "patch": { - "description": "partially update the specified DeviceClass", - "operationId": "patchResourceV1beta2DeviceClass", + "description": "partially update status of the specified DeviceTaintRule", + "operationId": "patchResourceV1beta2DeviceTaintRuleStatus", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3647,22 +4544,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3672,22 +4569,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3703,13 +4600,13 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, "put": { - "description": "replace the specified DeviceClass", - "operationId": "replaceResourceV1beta2DeviceClass", + "description": "replace status of the specified DeviceTaintRule", + "operationId": "replaceResourceV1beta2DeviceTaintRuleStatus", "parameters": [ { "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", @@ -3743,7 +4640,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3754,22 +4651,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3779,22 +4676,22 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceClass" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.DeviceTaintRule" } } }, @@ -3810,7 +4707,7 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } } @@ -3928,6 +4825,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4063,6 +4969,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5166,6 +6081,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5301,6 +6225,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6113,6 +7046,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6274,6 +7216,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6407,6 +7358,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6542,6 +7502,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7132,51 +8101,281 @@ "content": { "application/cbor": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "ResourceSlice", + "version": "v1beta2" + } + } + }, + "/apis/resource.k8s.io/v1beta2/watch/deviceclasses": { + "get": { + "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2DeviceClassList", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "resource_v1beta2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "resource.k8s.io", + "kind": "DeviceClass", + "version": "v1beta2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/resource.k8s.io/v1beta2/watch/deviceclasses/{name}": { + "get": { + "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2DeviceClass", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/json": { + "application/cbor-seq": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/vnd.kubernetes.protobuf": { + "application/json": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { + "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/json": { + "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, - "application/vnd.kubernetes.protobuf": { + "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/io.k8s.api.resource.v1beta2.ResourceSlice" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } } }, - "description": "Created" + "description": "OK" }, "401": { "description": "Unauthorized" @@ -7185,18 +8384,138 @@ "tags": [ "resource_v1beta2" ], - "x-kubernetes-action": "put", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "ResourceSlice", + "kind": "DeviceClass", "version": "v1beta2" } - } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "name of the DeviceClass", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] }, - "/apis/resource.k8s.io/v1beta2/watch/deviceclasses": { + "/apis/resource.k8s.io/v1beta2/watch/devicetaintrules": { "get": { - "description": "watch individual changes to a list of DeviceClass. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchResourceV1beta2DeviceClassList", + "description": "watch individual changes to a list of DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchResourceV1beta2DeviceTaintRuleList", "responses": { "200": { "content": { @@ -7248,7 +8567,7 @@ "x-kubernetes-action": "watchlist", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, @@ -7334,6 +8653,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7354,10 +8682,10 @@ } ] }, - "/apis/resource.k8s.io/v1beta2/watch/deviceclasses/{name}": { + "/apis/resource.k8s.io/v1beta2/watch/devicetaintrules/{name}": { "get": { - "description": "watch changes to an object of kind DeviceClass. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchResourceV1beta2DeviceClass", + "description": "watch changes to an object of kind DeviceTaintRule. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchResourceV1beta2DeviceTaintRule", "responses": { "200": { "content": { @@ -7409,7 +8737,7 @@ "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "resource.k8s.io", - "kind": "DeviceClass", + "kind": "DeviceTaintRule", "version": "v1beta2" } }, @@ -7460,7 +8788,7 @@ } }, { - "description": "name of the DeviceClass", + "description": "name of the DeviceTaintRule", "in": "path", "name": "name", "required": true, @@ -7505,6 +8833,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7676,6 +9013,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7857,6 +9203,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8028,6 +9383,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8209,6 +9573,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8370,6 +9743,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8531,6 +9913,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8692,6 +10083,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8863,6 +10263,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__scheduling.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__scheduling.k8s.io__v1_openapi.json index 207485422ff51..cabc28676dd18 100644 --- a/api/openapi-spec/v3/apis__scheduling.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__scheduling.k8s.io__v1_openapi.json @@ -40,9 +40,6 @@ "type": "integer" } }, - "required": [ - "value" - ], "type": "object", "x-kubernetes-group-version-kind": [ { @@ -264,310 +261,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "scheduling.k8s.io", "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -594,6 +291,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -812,6 +517,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -957,310 +676,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, { "group": "scheduling.k8s.io", "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1436,6 +855,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1571,6 +999,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2363,6 +1800,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2534,6 +1980,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__scheduling.k8s.io__v1alpha1_openapi.json b/api/openapi-spec/v3/apis__scheduling.k8s.io__v1alpha1_openapi.json deleted file mode 100644 index 75e6562b8caa4..0000000000000 --- a/api/openapi-spec/v3/apis__scheduling.k8s.io__v1alpha1_openapi.json +++ /dev/null @@ -1,3033 +0,0 @@ -{ - "components": { - "schemas": { - "io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy": { - "description": "BasicSchedulingPolicy indicates that standard Kubernetes scheduling behavior should be used.", - "type": "object" - }, - "io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy": { - "description": "GangSchedulingPolicy defines the parameters for gang scheduling.", - "properties": { - "minCount": { - "default": 0, - "description": "MinCount is the minimum number of pods that must be schedulable or scheduled at the same time for the scheduler to admit the entire group. It must be a positive integer.", - "format": "int32", - "type": "integer" - } - }, - "required": [ - "minCount" - ], - "type": "object" - }, - "io.k8s.api.scheduling.v1alpha1.PodGroup": { - "description": "PodGroup represents a set of pods with a common scheduling policy.", - "properties": { - "name": { - "default": "", - "description": "Name is a unique identifier for the PodGroup within the Workload. It must be a DNS label. This field is immutable.", - "type": "string" - }, - "policy": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.PodGroupPolicy" - } - ], - "default": {}, - "description": "Policy defines the scheduling policy for this PodGroup." - } - }, - "required": [ - "name", - "policy" - ], - "type": "object" - }, - "io.k8s.api.scheduling.v1alpha1.PodGroupPolicy": { - "description": "PodGroupPolicy defines the scheduling configuration for a PodGroup.", - "properties": { - "basic": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy" - } - ], - "description": "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior." - }, - "gang": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy" - } - ], - "description": "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics." - } - }, - "type": "object" - }, - "io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference": { - "description": "TypedLocalObjectReference allows to reference typed object inside the same namespace.", - "properties": { - "apiGroup": { - "description": "APIGroup is the group for the resource being referenced. If APIGroup is empty, the specified Kind must be in the core API group. For any other third-party types, setting APIGroup is required. It must be a DNS subdomain.", - "type": "string" - }, - "kind": { - "default": "", - "description": "Kind is the type of resource being referenced. It must be a path segment name.", - "type": "string" - }, - "name": { - "default": "", - "description": "Name is the name of resource being referenced. It must be a path segment name.", - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object" - }, - "io.k8s.api.scheduling.v1alpha1.Workload": { - "description": "Workload allows for expressing scheduling constraints that should be used when managing lifecycle of workloads from scheduling perspective, including scheduling, preemption, eviction and other phases.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - ], - "default": {}, - "description": "Standard object's metadata. Name must be a DNS subdomain." - }, - "spec": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadSpec" - } - ], - "default": {}, - "description": "Spec defines the desired behavior of a Workload." - } - }, - "required": [ - "spec" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - ] - }, - "io.k8s.api.scheduling.v1alpha1.WorkloadList": { - "description": "WorkloadList contains a list of Workload resources.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "Items is the list of Workloads.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - ], - "default": {} - }, - "type": "array" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ], - "default": {}, - "description": "Standard list metadata." - } - }, - "required": [ - "items" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "scheduling.k8s.io", - "kind": "WorkloadList", - "version": "v1alpha1" - } - ] - }, - "io.k8s.api.scheduling.v1alpha1.WorkloadSpec": { - "description": "WorkloadSpec defines the desired state of a Workload.", - "properties": { - "controllerRef": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference" - } - ], - "description": "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. When set, it cannot be changed." - }, - "podGroups": { - "description": "PodGroups is the list of pod groups that make up the Workload. The maximum number of pod groups is 8. This field is immutable.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.PodGroup" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-map-keys": [ - "name" - ], - "x-kubernetes-list-type": "map" - } - }, - "required": [ - "podGroups" - ], - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { - "description": "APIResource specifies the name of a resource and whether it is namespaced.", - "properties": { - "categories": { - "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", - "items": { - "default": "", - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale\".", - "type": "string" - }, - "kind": { - "default": "", - "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", - "type": "string" - }, - "name": { - "default": "", - "description": "name is the plural name of the resource.", - "type": "string" - }, - "namespaced": { - "default": false, - "description": "namespaced indicates if a resource is namespaced or not.", - "type": "boolean" - }, - "shortNames": { - "description": "shortNames is a list of suggested short names of the resource.", - "items": { - "default": "", - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "singularName": { - "default": "", - "description": "singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface.", - "type": "string" - }, - "storageVersionHash": { - "description": "The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates.", - "type": "string" - }, - "verbs": { - "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", - "items": { - "default": "", - "type": "string" - }, - "type": "array" - }, - "version": { - "description": "version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".", - "type": "string" - } - }, - "required": [ - "name", - "singularName", - "namespaced", - "kind", - "verbs" - ], - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList": { - "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "groupVersion": { - "default": "", - "description": "groupVersion is the group and version this APIResourceList is for.", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "resources": { - "description": "resources contains the name of the resources and if they are namespaced.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - } - }, - "required": [ - "groupVersion", - "resources" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "APIResourceList", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions": { - "description": "DeleteOptions may be provided when deleting an API object.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "dryRun": { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "items": { - "default": "", - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "gracePeriodSeconds": { - "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", - "format": "int64", - "type": "integer" - }, - "ignoreStoreReadErrorWithClusterBreakingPotential": { - "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", - "type": "boolean" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "orphanDependents": { - "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", - "type": "boolean" - }, - "preconditions": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" - } - ], - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." - }, - "propagationPolicy": { - "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { - "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { - "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", - "properties": { - "continue": { - "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", - "type": "string" - }, - "remainingItemCount": { - "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", - "format": "int64", - "type": "integer" - }, - "resourceVersion": { - "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { - "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", - "type": "string" - }, - "fieldsType": { - "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", - "type": "string" - }, - "fieldsV1": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" - } - ], - "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." - }, - "manager": { - "description": "Manager is an identifier of the workflow managing these fields.", - "type": "string" - }, - "operation": { - "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", - "type": "string" - }, - "subresource": { - "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", - "type": "string" - }, - "time": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ], - "description": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over." - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { - "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", - "properties": { - "annotations": { - "additionalProperties": { - "default": "", - "type": "string" - }, - "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", - "type": "object" - }, - "creationTimestamp": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ], - "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" - }, - "deletionGracePeriodSeconds": { - "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", - "format": "int64", - "type": "integer" - }, - "deletionTimestamp": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - } - ], - "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" - }, - "finalizers": { - "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", - "items": { - "default": "", - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "set", - "x-kubernetes-patch-strategy": "merge" - }, - "generateName": { - "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", - "type": "string" - }, - "generation": { - "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", - "format": "int64", - "type": "integer" - }, - "labels": { - "additionalProperties": { - "default": "", - "type": "string" - }, - "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", - "type": "object" - }, - "managedFields": { - "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "name": { - "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", - "type": "string" - }, - "namespace": { - "description": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", - "type": "string" - }, - "ownerReferences": { - "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-map-keys": [ - "uid" - ], - "x-kubernetes-list-type": "map", - "x-kubernetes-patch-merge-key": "uid", - "x-kubernetes-patch-strategy": "merge" - }, - "resourceVersion": { - "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", - "type": "string" - }, - "uid": { - "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { - "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", - "properties": { - "apiVersion": { - "default": "", - "description": "API version of the referent.", - "type": "string" - }, - "blockOwnerDeletion": { - "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", - "type": "boolean" - }, - "controller": { - "description": "If true, this reference points to the managing controller.", - "type": "boolean" - }, - "kind": { - "default": "", - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "default": "", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", - "type": "string" - }, - "uid": { - "default": "", - "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "type": "object", - "x-kubernetes-map-type": "atomic" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Patch": { - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "resourceVersion": { - "description": "Specifies the target ResourceVersion", - "type": "string" - }, - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "format": "int32", - "type": "integer" - }, - "details": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - } - ], - "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ], - "default": {}, - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" - }, - "reason": { - "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", - "type": "string" - }, - "status": { - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "Status", - "version": "v1" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "field": { - "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", - "type": "string" - }, - "message": { - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", - "type": "string" - }, - "reason": { - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { - "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", - "properties": { - "causes": { - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - } - ] - }, - "io.k8s.apimachinery.pkg.runtime.RawExtension": { - "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", - "type": "object" - } - }, - "securitySchemes": { - "BearerToken": { - "description": "Bearer Token authentication", - "in": "header", - "name": "authorization", - "type": "apiKey" - } - } - }, - "info": { - "title": "Kubernetes", - "version": "unversioned" - }, - "openapi": "3.0.0", - "paths": { - "/apis/scheduling.k8s.io/v1alpha1/": { - "get": { - "description": "get available resources", - "operationId": "getSchedulingV1alpha1APIResources", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ] - } - }, - "/apis/scheduling.k8s.io/v1alpha1/namespaces/{namespace}/workloads": { - "delete": { - "description": "delete collection of Workload", - "operationId": "deleteSchedulingV1alpha1CollectionNamespacedWorkload", - "parameters": [ - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", - "in": "query", - "name": "gracePeriodSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", - "in": "query", - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", - "in": "query", - "name": "orphanDependents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", - "in": "query", - "name": "propagationPolicy", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersionMatch", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "get": { - "description": "list or watch objects of kind Workload", - "operationId": "listSchedulingV1alpha1NamespacedWorkload", - "parameters": [ - { - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "in": "query", - "name": "allowWatchBookmarks", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersionMatch", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/cbor-seq": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "description": "object name and auth scope, such as for teams and projects", - "in": "path", - "name": "namespace", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "post": { - "description": "create a Workload", - "operationId": "createSchedulingV1alpha1NamespacedWorkload", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", - "in": "query", - "name": "fieldManager", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "Created" - }, - "202": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "Accepted" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - } - }, - "/apis/scheduling.k8s.io/v1alpha1/namespaces/{namespace}/workloads/{name}": { - "delete": { - "description": "delete a Workload", - "operationId": "deleteSchedulingV1alpha1NamespacedWorkload", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", - "in": "query", - "name": "gracePeriodSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", - "in": "query", - "name": "ignoreStoreReadErrorWithClusterBreakingPotential", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", - "in": "query", - "name": "orphanDependents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", - "in": "query", - "name": "propagationPolicy", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - }, - "description": "OK" - }, - "202": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - }, - "description": "Accepted" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "get": { - "description": "read the specified Workload", - "operationId": "readSchedulingV1alpha1NamespacedWorkload", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "description": "name of the Workload", - "in": "path", - "name": "name", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "object name and auth scope, such as for teams and projects", - "in": "path", - "name": "namespace", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "patch": { - "description": "partially update the specified Workload", - "operationId": "patchSchedulingV1alpha1NamespacedWorkload", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", - "in": "query", - "name": "fieldManager", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", - "in": "query", - "name": "force", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/apply-patch+cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "Created" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "put": { - "description": "replace the specified Workload", - "operationId": "replaceSchedulingV1alpha1NamespacedWorkload", - "parameters": [ - { - "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", - "in": "query", - "name": "dryRun", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", - "in": "query", - "name": "fieldManager", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - "in": "query", - "name": "fieldValidation", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "OK" - }, - "201": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.Workload" - } - } - }, - "description": "Created" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - } - }, - "/apis/scheduling.k8s.io/v1alpha1/watch/namespaces/{namespace}/workloads": { - "get": { - "description": "watch individual changes to a list of Workload. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchSchedulingV1alpha1NamespacedWorkloadList", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/cbor-seq": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "in": "query", - "name": "allowWatchBookmarks", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "object name and auth scope, such as for teams and projects", - "in": "path", - "name": "namespace", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersionMatch", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ] - }, - "/apis/scheduling.k8s.io/v1alpha1/watch/namespaces/{namespace}/workloads/{name}": { - "get": { - "description": "watch changes to an object of kind Workload. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", - "operationId": "watchSchedulingV1alpha1NamespacedWorkload", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/cbor-seq": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "in": "query", - "name": "allowWatchBookmarks", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "name of the Workload", - "in": "path", - "name": "name", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "object name and auth scope, such as for teams and projects", - "in": "path", - "name": "namespace", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersionMatch", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ] - }, - "/apis/scheduling.k8s.io/v1alpha1/watch/workloads": { - "get": { - "description": "watch individual changes to a list of Workload. deprecated: use the 'watch' parameter with a list operation instead.", - "operationId": "watchSchedulingV1alpha1WorkloadListForAllNamespaces", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/cbor-seq": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "in": "query", - "name": "allowWatchBookmarks", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersionMatch", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ] - }, - "/apis/scheduling.k8s.io/v1alpha1/workloads": { - "get": { - "description": "list or watch objects of kind Workload", - "operationId": "listSchedulingV1alpha1WorkloadForAllNamespaces", - "responses": { - "200": { - "content": { - "application/cbor": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/cbor-seq": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/json;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/vnd.kubernetes.protobuf;stream=watch": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha1.WorkloadList" - } - } - }, - "description": "OK" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "scheduling_v1alpha1" - ], - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "scheduling.k8s.io", - "kind": "Workload", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "in": "query", - "name": "allowWatchBookmarks", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "in": "query", - "name": "continue", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "in": "query", - "name": "fieldSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "in": "query", - "name": "labelSelector", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", - "in": "query", - "name": "pretty", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersion", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "in": "query", - "name": "resourceVersionMatch", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "in": "query", - "name": "sendInitialEvents", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "in": "query", - "name": "timeoutSeconds", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "in": "query", - "name": "watch", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ] - } - } -} diff --git a/api/openapi-spec/v3/apis__scheduling.k8s.io__v1alpha2_openapi.json b/api/openapi-spec/v3/apis__scheduling.k8s.io__v1alpha2_openapi.json new file mode 100644 index 0000000000000..842d458042ff2 --- /dev/null +++ b/api/openapi-spec/v3/apis__scheduling.k8s.io__v1alpha2_openapi.json @@ -0,0 +1,4886 @@ +{ + "components": { + "schemas": { + "io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy": { + "description": "BasicSchedulingPolicy indicates that standard Kubernetes scheduling behavior should be used.", + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy": { + "description": "GangSchedulingPolicy defines the parameters for gang scheduling.", + "properties": { + "minCount": { + "default": 0, + "description": "MinCount is the minimum number of pods that must be schedulable or scheduled at the same time for the scheduler to admit the entire group. It must be a positive integer.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "minCount" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroup": { + "description": "PodGroup represents a runtime instance of pods grouped together. PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from Workload.podGroupTemplates. PodGroup API enablement is toggled by the GenericWorkload feature gate.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ], + "default": {}, + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupSpec" + } + ], + "default": {}, + "description": "Spec defines the desired state of the PodGroup." + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupStatus" + } + ], + "default": {}, + "description": "Status represents the current observed state of the PodGroup." + } + }, + "required": [ + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupList": { + "description": "PodGroupList contains a list of PodGroup resources.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of PodGroups.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + ], + "default": {} + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata." + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "scheduling.k8s.io", + "kind": "PodGroupList", + "version": "v1alpha2" + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim": { + "description": "PodGroupResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the PodGroup.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. Pods that need access to the ResourceClaim define a matching reference in its own Spec.ResourceClaims. The Pod's claim must match all fields of the PodGroup's claim exactly.", + "properties": { + "name": { + "default": "", + "description": "Name uniquely identifies this resource claim inside the PodGroup. This must be a DNS_LABEL.", + "type": "string" + }, + "resourceClaimName": { + "description": "ResourceClaimName is the name of a ResourceClaim object in the same namespace as this PodGroup. The ResourceClaim will be reserved for the PodGroup instead of its individual pods.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "type": "string" + }, + "resourceClaimTemplateName": { + "description": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this PodGroup.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim will also be deleted. The PodGroup name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus": { + "description": "PodGroupResourceClaimStatus is stored in the PodGroupStatus for each PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the generated name for the corresponding ResourceClaim.", + "properties": { + "name": { + "default": "", + "description": "Name uniquely identifies this resource claim inside the PodGroup. This must match the name of an entry in podgroup.spec.resourceClaims, which implies that the string must be a DNS_LABEL.", + "type": "string" + }, + "resourceClaimName": { + "description": "ResourceClaimName is the name of the ResourceClaim that was generated for the PodGroup in the namespace of the PodGroup. If this is unset, then generating a ResourceClaim was not necessary. The podgroup.spec.resourceClaims entry can be ignored in this case.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints": { + "description": "PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup.", + "properties": { + "topology": { + "description": "Topology defines the topology constraints for the pod group. Currently only a single topology constraint can be specified. This may change in the future.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.TopologyConstraint" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy": { + "description": "PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. Exactly one policy must be set.", + "properties": { + "basic": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy" + } + ], + "description": "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior." + }, + "gang": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy" + } + ], + "description": "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics." + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "basic": "Basic", + "gang": "Gang" + } + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupSpec": { + "description": "PodGroupSpec defines the desired state of a PodGroup.", + "properties": { + "disruptionMode": { + "default": "Pod", + "description": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. Controllers are expected to fill this field by copying it from a PodGroupTemplate. One of Pod, PodGroup. Defaults to Pod if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "podGroupTemplateRef": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference" + } + ], + "description": "PodGroupTemplateRef references an optional PodGroup template within other object (e.g. Workload) that was used to create the PodGroup. This field is immutable." + }, + "priority": { + "description": "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "description": "PriorityClassName defines the priority that should be considered when scheduling this pod group. Controllers are expected to fill this field by copying it from a PodGroupTemplate. Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate (i.e. if no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, the pod group's priority will be zero). This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "resourceClaims": { + "description": "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + }, + "schedulingConstraints": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints" + } + ], + "description": "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled." + }, + "schedulingPolicy": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy" + } + ], + "default": {}, + "description": "SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable." + } + }, + "required": [ + "schedulingPolicy" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupStatus": { + "description": "PodGroupStatus represents information about the status of a pod group.", + "properties": { + "conditions": { + "description": "Conditions represent the latest observations of the PodGroup's state.\n\nKnown condition types: - \"PodGroupScheduled\": Indicates whether the scheduling requirement has been satisfied. - \"DisruptionTarget\": Indicates whether the PodGroup is about to be terminated\n due to disruption such as preemption.\n\nKnown reasons for the PodGroupScheduled condition: - \"Unschedulable\": The PodGroup cannot be scheduled due to resource constraints,\n affinity/anti-affinity rules, or insufficient capacity for the gang.\n- \"SchedulerError\": The PodGroup cannot be scheduled due to some internal error\n that happened during scheduling, for example due to nodeAffinity parsing errors.\n\nKnown reasons for the DisruptionTarget condition: - \"PreemptionByScheduler\": The PodGroup was preempted by the scheduler to make room for\n higher-priority PodGroups or Pods.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Condition" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "resourceClaimStatuses": { + "description": "Status of resource claims.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + } + }, + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupTemplate": { + "description": "PodGroupTemplate represents a template for a set of pods with a scheduling policy.", + "properties": { + "disruptionMode": { + "description": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. One of Pod, PodGroup. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name is a unique identifier for the PodGroupTemplate within the Workload. It must be a DNS label. This field is immutable.", + "type": "string" + }, + "priority": { + "description": "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "description": "PriorityClassName indicates the priority that should be considered when scheduling a pod group created from this template. If no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, pod groups created from this template will have the priority set to zero. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "type": "string" + }, + "resourceClaims": { + "description": "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + }, + "schedulingConstraints": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints" + } + ], + "description": "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled." + }, + "schedulingPolicy": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy" + } + ], + "default": {}, + "description": "SchedulingPolicy defines the scheduling policy for this PodGroupTemplate." + } + }, + "required": [ + "name", + "schedulingPolicy" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference": { + "description": "PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). Exactly one reference must be set.", + "properties": { + "workload": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference" + } + ], + "description": "Workload references the PodGroupTemplate within the Workload object that was used to create the PodGroup." + } + }, + "type": "object", + "x-kubernetes-unions": [ + { + "fields-to-discriminateBy": { + "workload": "Workload" + } + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.TopologyConstraint": { + "description": "TopologyConstraint defines a topology constraint for a PodGroup.", + "properties": { + "key": { + "default": "", + "description": "Key specifies the key of the node label representing the topology domain. All pods within the PodGroup must be colocated within the same domain instance. Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. Examples: \"topology.kubernetes.io/rack\"", + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference": { + "description": "TypedLocalObjectReference allows to reference typed object inside the same namespace.", + "properties": { + "apiGroup": { + "description": "APIGroup is the group for the resource being referenced. If APIGroup is empty, the specified Kind must be in the core API group. For any other third-party types, setting APIGroup is required. It must be a DNS subdomain.", + "type": "string" + }, + "kind": { + "default": "", + "description": "Kind is the type of resource being referenced. It must be a path segment name.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name is the name of resource being referenced. It must be a path segment name.", + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.Workload": { + "description": "Workload allows for expressing scheduling constraints that should be used when managing the lifecycle of workloads from the scheduling perspective, including scheduling, preemption, eviction and other phases. Workload API enablement is toggled by the GenericWorkload feature gate.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ], + "default": {}, + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadSpec" + } + ], + "default": {}, + "description": "Spec defines the desired behavior of a Workload." + } + }, + "required": [ + "spec" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.WorkloadList": { + "description": "WorkloadList contains a list of Workload resources.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of Workloads.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + ], + "default": {} + }, + "type": "array" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata." + } + }, + "required": [ + "items" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "scheduling.k8s.io", + "kind": "WorkloadList", + "version": "v1alpha2" + } + ] + }, + "io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference": { + "description": "WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object.", + "properties": { + "podGroupTemplateName": { + "default": "", + "description": "PodGroupTemplateName defines the PodGroupTemplate name within the Workload object.", + "type": "string" + }, + "workloadName": { + "default": "", + "description": "WorkloadName defines the name of the Workload object.", + "type": "string" + } + }, + "required": [ + "workloadName", + "podGroupTemplateName" + ], + "type": "object" + }, + "io.k8s.api.scheduling.v1alpha2.WorkloadSpec": { + "description": "WorkloadSpec defines the desired state of a Workload.", + "properties": { + "controllerRef": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference" + } + ], + "description": "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. This field is immutable." + }, + "podGroupTemplates": { + "description": "PodGroupTemplates is the list of templates that make up the Workload. The maximum number of templates is 8. This field is immutable.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupTemplate" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "podGroupTemplates" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { + "description": "APIResource specifies the name of a resource and whether it is namespaced.", + "properties": { + "categories": { + "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "group": { + "description": "group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale\".", + "type": "string" + }, + "kind": { + "default": "", + "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", + "type": "string" + }, + "name": { + "default": "", + "description": "name is the plural name of the resource.", + "type": "string" + }, + "namespaced": { + "default": false, + "description": "namespaced indicates if a resource is namespaced or not.", + "type": "boolean" + }, + "shortNames": { + "description": "shortNames is a list of suggested short names of the resource.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "singularName": { + "default": "", + "description": "singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface.", + "type": "string" + }, + "storageVersionHash": { + "description": "The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates.", + "type": "string" + }, + "verbs": { + "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", + "items": { + "default": "", + "type": "string" + }, + "type": "array" + }, + "version": { + "description": "version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".", + "type": "string" + } + }, + "required": [ + "name", + "singularName", + "namespaced", + "kind", + "verbs" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList": { + "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "groupVersion": { + "default": "", + "description": "groupVersion is the group and version this APIResourceList is for.", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "resources": { + "description": "resources contains the name of the resources and if they are namespaced.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "groupVersion", + "resources" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "APIResourceList", + "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Condition": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable." + }, + "message": { + "default": "", + "description": "message is a human readable message indicating details about the transition. This may be an empty string.", + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.", + "format": "int64", + "type": "integer" + }, + "reason": { + "default": "", + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.", + "type": "string" + }, + "status": { + "default": "", + "description": "status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "default": "", + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions": { + "description": "DeleteOptions may be provided when deleting an API object.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "dryRun": { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "gracePeriodSeconds": { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "format": "int64", + "type": "integer" + }, + "ignoreStoreReadErrorWithClusterBreakingPotential": { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "type": "boolean" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "orphanDependents": { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "type": "boolean" + }, + "preconditions": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" + } + ], + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." + }, + "propagationPolicy": { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "DeleteOptions", + "version": "v1" + }, + { + "group": "scheduling.k8s.io", + "kind": "DeleteOptions", + "version": "v1alpha2" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { + "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "properties": { + "continue": { + "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + "type": "string" + }, + "remainingItemCount": { + "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "format": "int64", + "type": "integer" + }, + "resourceVersion": { + "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" + }, + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" + }, + "fieldsV1": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" + } + ], + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." + }, + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" + }, + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" + }, + "subresource": { + "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + "type": "string" + }, + "time": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "properties": { + "annotations": { + "additionalProperties": { + "default": "", + "type": "string" + }, + "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", + "type": "object" + }, + "creationTimestamp": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "deletionGracePeriodSeconds": { + "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + "format": "int64", + "type": "integer" + }, + "deletionTimestamp": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ], + "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "finalizers": { + "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", + "items": { + "default": "", + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set", + "x-kubernetes-patch-strategy": "merge" + }, + "generateName": { + "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + "type": "string" + }, + "generation": { + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + "format": "int64", + "type": "integer" + }, + "labels": { + "additionalProperties": { + "default": "", + "type": "string" + }, + "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", + "type": "object" + }, + "managedFields": { + "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "name": { + "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" + }, + "namespace": { + "description": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", + "type": "string" + }, + "ownerReferences": { + "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "uid" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "uid", + "x-kubernetes-patch-strategy": "merge" + }, + "resourceVersion": { + "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" + }, + "uid": { + "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { + "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + "properties": { + "apiVersion": { + "default": "", + "description": "API version of the referent.", + "type": "string" + }, + "blockOwnerDeletion": { + "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + "type": "boolean" + }, + "controller": { + "description": "If true, this reference points to the managing controller.", + "type": "boolean" + }, + "kind": { + "default": "", + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" + }, + "uid": { + "default": "", + "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Patch": { + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "resourceVersion": { + "description": "Specifies the target ResourceVersion", + "type": "string" + }, + "uid": { + "description": "Specifies the target UID.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", + "format": "int32", + "type": "integer" + }, + "details": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" + } + ], + "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type." + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "message": { + "description": "A human-readable description of the status of this operation.", + "type": "string" + }, + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ], + "default": {}, + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + }, + "reason": { + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" + }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "Status", + "version": "v1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" + }, + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" + }, + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" + }, + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" + }, + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" + }, + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." + }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "WatchEvent", + "version": "v1" + }, + { + "group": "scheduling.k8s.io", + "kind": "WatchEvent", + "version": "v1alpha2" + } + ] + }, + "io.k8s.apimachinery.pkg.runtime.RawExtension": { + "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", + "type": "object" + } + }, + "securitySchemes": { + "BearerToken": { + "description": "Bearer Token authentication", + "in": "header", + "name": "authorization", + "type": "apiKey" + } + } + }, + "info": { + "title": "Kubernetes", + "version": "unversioned" + }, + "openapi": "3.0.0", + "paths": { + "/apis/scheduling.k8s.io/v1alpha2/": { + "get": { + "description": "get available resources", + "operationId": "getSchedulingV1alpha2APIResources", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ] + } + }, + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/podgroups": { + "delete": { + "description": "delete collection of PodGroup", + "operationId": "deleteSchedulingV1alpha2CollectionNamespacedPodGroup", + "parameters": [ + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "get": { + "description": "list or watch objects of kind PodGroup", + "operationId": "listSchedulingV1alpha2NamespacedPodGroup", + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a PodGroup", + "operationId": "createSchedulingV1alpha2NamespacedPodGroup", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + } + }, + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/podgroups/{name}": { + "delete": { + "description": "delete a PodGroup", + "operationId": "deleteSchedulingV1alpha2NamespacedPodGroup", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "get": { + "description": "read the specified PodGroup", + "operationId": "readSchedulingV1alpha2NamespacedPodGroup", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "name of the PodGroup", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update the specified PodGroup", + "operationId": "patchSchedulingV1alpha2NamespacedPodGroup", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "put": { + "description": "replace the specified PodGroup", + "operationId": "replaceSchedulingV1alpha2NamespacedPodGroup", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + } + }, + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/podgroups/{name}/status": { + "get": { + "description": "read status of the specified PodGroup", + "operationId": "readSchedulingV1alpha2NamespacedPodGroupStatus", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "name of the PodGroup", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update status of the specified PodGroup", + "operationId": "patchSchedulingV1alpha2NamespacedPodGroupStatus", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "put": { + "description": "replace status of the specified PodGroup", + "operationId": "replaceSchedulingV1alpha2NamespacedPodGroupStatus", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroup" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + } + }, + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/workloads": { + "delete": { + "description": "delete collection of Workload", + "operationId": "deleteSchedulingV1alpha2CollectionNamespacedWorkload", + "parameters": [ + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "get": { + "description": "list or watch objects of kind Workload", + "operationId": "listSchedulingV1alpha2NamespacedWorkload", + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "post": { + "description": "create a Workload", + "operationId": "createSchedulingV1alpha2NamespacedWorkload", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "Created" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + } + }, + "/apis/scheduling.k8s.io/v1alpha2/namespaces/{namespace}/workloads/{name}": { + "delete": { + "description": "delete a Workload", + "operationId": "deleteSchedulingV1alpha2NamespacedWorkload", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "in": "query", + "name": "gracePeriodSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "in": "query", + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "in": "query", + "name": "orphanDependents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "in": "query", + "name": "propagationPolicy", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "OK" + }, + "202": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "description": "Accepted" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "get": { + "description": "read the specified Workload", + "operationId": "readSchedulingV1alpha2NamespacedWorkload", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "name of the Workload", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "patch": { + "description": "partially update the specified Workload", + "operationId": "patchSchedulingV1alpha2NamespacedWorkload", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "in": "query", + "name": "force", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "put": { + "description": "replace the specified Workload", + "operationId": "replaceSchedulingV1alpha2NamespacedWorkload", + "parameters": [ + { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "in": "query", + "name": "dryRun", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "in": "query", + "name": "fieldManager", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "in": "query", + "name": "fieldValidation", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "OK" + }, + "201": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.Workload" + } + } + }, + "description": "Created" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + } + }, + "/apis/scheduling.k8s.io/v1alpha2/podgroups": { + "get": { + "description": "list or watch objects of kind PodGroup", + "operationId": "listSchedulingV1alpha2PodGroupForAllNamespaces", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.PodGroupList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/podgroups": { + "get": { + "description": "watch individual changes to a list of PodGroup. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1alpha2NamespacedPodGroupList", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/podgroups/{name}": { + "get": { + "description": "watch changes to an object of kind PodGroup. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchSchedulingV1alpha2NamespacedPodGroup", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "name of the PodGroup", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/workloads": { + "get": { + "description": "watch individual changes to a list of Workload. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1alpha2NamespacedWorkloadList", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/namespaces/{namespace}/workloads/{name}": { + "get": { + "description": "watch changes to an object of kind Workload. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "operationId": "watchSchedulingV1alpha2NamespacedWorkload", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "name of the Workload", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "object name and auth scope, such as for teams and projects", + "in": "path", + "name": "namespace", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/podgroups": { + "get": { + "description": "watch individual changes to a list of PodGroup. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1alpha2PodGroupListForAllNamespaces", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "PodGroup", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/watch/workloads": { + "get": { + "description": "watch individual changes to a list of Workload. deprecated: use the 'watch' parameter with a list operation instead.", + "operationId": "watchSchedulingV1alpha2WorkloadListForAllNamespaces", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + }, + "/apis/scheduling.k8s.io/v1alpha2/workloads": { + "get": { + "description": "list or watch objects of kind Workload", + "operationId": "listSchedulingV1alpha2WorkloadForAllNamespaces", + "responses": { + "200": { + "content": { + "application/cbor": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/cbor-seq": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.api.scheduling.v1alpha2.WorkloadList" + } + } + }, + "description": "OK" + }, + "401": { + "description": "Unauthorized" + } + }, + "tags": [ + "scheduling_v1alpha2" + ], + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "scheduling.k8s.io", + "kind": "Workload", + "version": "v1alpha2" + } + }, + "parameters": [ + { + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "in": "query", + "name": "allowWatchBookmarks", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "in": "query", + "name": "continue", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "in": "query", + "name": "fieldSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "in": "query", + "name": "labelSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "in": "query", + "name": "pretty", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersion", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "in": "query", + "name": "resourceVersionMatch", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "in": "query", + "name": "sendInitialEvents", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "in": "query", + "name": "timeoutSeconds", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "in": "query", + "name": "watch", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ] + } + } +} diff --git a/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json index 6212f1d5363b1..990fa640f58d2 100644 --- a/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json @@ -820,7 +820,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.PortworxVolumeSource" } ], - "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on." + "description": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver." }, "quobyte": { "allOf": [ @@ -1295,7 +1295,7 @@ "type": "string" }, "nodeAllocatableUpdatePeriodSeconds": { - "description": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", + "description": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis feature requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", "format": "int64", "type": "integer" }, @@ -1303,6 +1303,10 @@ "description": "podInfoOnMount indicates this CSI volume driver requires additional pod information (like podName, podUID, etc.) during mount operations, if set to true. If set to false, pod information will not be passed on mount. Default is false.\n\nThe CSI driver specifies podInfoOnMount as part of driver deployment. If true, Kubelet will pass pod information as VolumeContext in the CSI NodePublishVolume() calls. The CSI driver is responsible for parsing and validating the information passed in as VolumeContext.\n\nThe following VolumeContext will be passed if podInfoOnMount is set to true. This list might grow, but the prefix will be used. \"csi.storage.k8s.io/pod.name\": pod.Name \"csi.storage.k8s.io/pod.namespace\": pod.Namespace \"csi.storage.k8s.io/pod.uid\": string(pod.UID) \"csi.storage.k8s.io/ephemeral\": \"true\" if the volume is an ephemeral inline volume\n defined by a CSIVolumeSource, otherwise \"false\"\n\n\"csi.storage.k8s.io/ephemeral\" is a new feature in Kubernetes 1.16. It is only required for drivers which support both the \"Persistent\" and \"Ephemeral\" VolumeLifecycleMode. Other drivers can leave pod info disabled and/or ignore this field. As Kubernetes 1.15 doesn't support this field, drivers can only support one mode when deployed on such a cluster and the deployment determines which mode that is, for example via a command line parameter of the driver.\n\nThis field was immutable in Kubernetes < 1.29 and now is mutable.", "type": "boolean" }, + "preventPodSchedulingIfMissing": { + "description": "PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod scheduling if the CSI driver on the node is missing.\n\nEnabling this option will prevent the scheduler (or any other component which embeds default scheduler such as cluster-autoscaler) from scheduling pods to nodes where CSI driver is not installed.\n\nFor components(such as cluster-autoscaler) that embed the scheduler and run pod placement simulations using scheduler plugins, they MUST be aware of CSI driver registration information via CSINode object. They must create simulated CSINode objects in addition to Node objects during scheduling simulation, otherwise if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any newly created node may be rejected by the scheduler because of missing CSI driver information from the node.\n\nThis is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. Default is \"false\".", + "type": "boolean" + }, "requiresRepublish": { "description": "requiresRepublish indicates the CSI driver wants `NodePublishVolume` being periodically called to reflect any possible change in the mounted volume. This field defaults to false.\n\nNote: After a successful initial NodePublishVolume call, subsequent calls to NodePublishVolume should only update the contents of the volume. New mount points will not be seen by a running container.", "type": "boolean" @@ -2023,7 +2027,7 @@ "description": "VolumeError captures an error encountered during a volume operation.", "properties": { "errorCode": { - "description": "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", + "description": "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", "format": "int32", "type": "integer" }, @@ -2231,310 +2235,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "storage.k8s.io", "kind": "DeleteOptions", "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -2618,6 +2322,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -2836,6 +2548,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -2917,374 +2643,74 @@ "items": { "allOf": [ { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - ], - "default": {} - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "group": { - "description": "The group attribute of the resource associated with the status StatusReason.", - "type": "string" - }, - "kind": { - "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", - "type": "string" - }, - "retryAfterSeconds": { - "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", - "format": "int32", - "type": "integer" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", - "type": "string" - } - }, - "type": "object" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", - "format": "date-time", - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "properties": { - "object": { - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" - } - ], - "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." - }, - "type": { - "default": "", - "type": "string" - } - }, - "required": [ - "type", - "object" - ], - "type": "object", - "x-kubernetes-group-version-kind": [ - { - "group": "", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "format": "int32", + "type": "integer" }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "properties": { + "object": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ], + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context." }, + "type": { + "default": "", + "type": "string" + } + }, + "required": [ + "type", + "object" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ { - "group": "storage.k8s.io", + "group": "", "kind": "WatchEvent", "version": "v1" }, { "group": "storage.k8s.io", "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" + "version": "v1" } ] }, @@ -3460,6 +2886,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -3595,6 +3030,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4359,6 +3803,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -4494,6 +3947,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5286,6 +4748,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5419,6 +4890,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -5554,6 +5034,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6338,6 +5827,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -6473,6 +5971,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7237,6 +6744,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -7372,6 +6888,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8445,6 +7970,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -8580,6 +8114,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9372,6 +8915,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9543,6 +9095,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9704,6 +9265,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -9875,6 +9445,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10036,6 +9615,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10207,6 +9795,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10388,6 +9985,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10549,6 +10155,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10720,6 +10335,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -10881,6 +10505,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11052,6 +10685,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11213,6 +10855,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -11384,6 +11035,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__storage.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__storage.k8s.io__v1beta1_openapi.json index 0843bd9e9e178..b3f898f043329 100644 --- a/api/openapi-spec/v3/apis__storage.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__storage.k8s.io__v1beta1_openapi.json @@ -259,310 +259,10 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, { "group": "storage.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" } ] }, @@ -589,6 +289,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -807,6 +515,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -952,310 +674,10 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, { "group": "storage.k8s.io", "kind": "WatchEvent", "version": "v1beta1" - }, - { - "group": "storagemigration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" } ] }, @@ -1431,6 +853,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1566,6 +997,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2358,6 +1798,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2529,6 +1978,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/api/openapi-spec/v3/apis__storagemigration.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__storagemigration.k8s.io__v1beta1_openapi.json index 537f1baccb5be..248d84b56c838 100644 --- a/api/openapi-spec/v3/apis__storagemigration.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__storagemigration.k8s.io__v1beta1_openapi.json @@ -353,306 +353,6 @@ "kind": "DeleteOptions", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "DeleteOptions", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "batch", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "policy", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "DeleteOptions", - "version": "v1beta1" - }, { "group": "storagemigration.k8s.io", "kind": "DeleteOptions", @@ -701,6 +401,14 @@ "selfLink": { "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "type": "string" + }, + "shardInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" + } + ], + "description": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate." } }, "type": "object" @@ -919,6 +627,20 @@ }, "type": "object" }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo": { + "description": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "properties": { + "selector": { + "default": "", + "description": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + "type": "string" + } + }, + "required": [ + "selector" + ], + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { "description": "Status is a return value for calls that don't return other objects.", "properties": { @@ -1064,306 +786,6 @@ "kind": "WatchEvent", "version": "v1" }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admission.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "admissionregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiextensions.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apiregistration.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "apps", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "authentication.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta1" - }, - { - "group": "autoscaling", - "kind": "WatchEvent", - "version": "v2beta2" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "batch", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "certificates.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha2" - }, - { - "group": "coordination.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "discovery.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "events.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "extensions", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "flowcontrol.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1beta3" - }, - { - "group": "imagepolicy.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "internal.apiserver.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "networking.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "node.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "policy", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "rbac.authorization.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha3" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "resource.k8s.io", - "kind": "WatchEvent", - "version": "v1beta2" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "scheduling.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1alpha1" - }, - { - "group": "storage.k8s.io", - "kind": "WatchEvent", - "version": "v1beta1" - }, { "group": "storagemigration.k8s.io", "kind": "WatchEvent", @@ -1543,6 +965,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -1678,6 +1109,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2779,6 +2219,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", @@ -2950,6 +2399,15 @@ "uniqueItems": true } }, + { + "description": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + "in": "query", + "name": "shardSelector", + "schema": { + "type": "string", + "uniqueItems": true + } + }, { "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "in": "query", diff --git a/build/build-image/cross/VERSION b/build/build-image/cross/VERSION index 0b69532f521be..eaff4eee6313c 100644 --- a/build/build-image/cross/VERSION +++ b/build/build-image/cross/VERSION @@ -1 +1 @@ -v1.35.0-go1.25.7-bullseye.0 \ No newline at end of file +v1.36.0-go1.26.4-bullseye.0 \ No newline at end of file diff --git a/build/common.sh b/build/common.sh index fca3077811b07..4e6a5307b630a 100755 --- a/build/common.sh +++ b/build/common.sh @@ -24,9 +24,6 @@ set -o pipefail # Unset CDPATH, having it set messes up with script import paths unset CDPATH -USER_ID=$(id -u) -GROUP_ID=$(id -g) - DOCKER_OPTS=${DOCKER_OPTS:-""} IFS=" " read -r -a DOCKER <<< "docker ${DOCKER_OPTS}" DOCKER_HOST=${DOCKER_HOST:-""} @@ -80,8 +77,8 @@ readonly REMOTE_OUTPUT_BINPATH="${REMOTE_OUTPUT_SUBPATH}/bin" readonly REMOTE_OUTPUT_GOPATH="${REMOTE_OUTPUT_SUBPATH}/go" # These are the default versions (image tags) for their respective base images. -readonly __default_distroless_iptables_version=v0.8.8 -readonly __default_go_runner_version=v2.4.0-go1.25.7-bookworm.0 +readonly __default_distroless_iptables_version=v0.9.3 +readonly __default_go_runner_version=v2.4.0-go1.26.4-bookworm.0 readonly __default_setcap_version=bookworm-v1.0.6 # The default image for all binaries which are dynamically linked. @@ -94,6 +91,7 @@ readonly __default_dynamic_base_image="$KUBE_BASE_IMAGE_REGISTRY/distroless-ipta # It can be overridden to change the image for all such commands. # When the per-command env variable is set, that env variable is # used without considering KUBE_GORUNNER_IMAGE. +# TODO: this should be replaced with a plain distroless base image readonly KUBE_GORUNNER_IMAGE="${KUBE_GORUNNER_IMAGE:-$KUBE_BASE_IMAGE_REGISTRY/go-runner:$__default_go_runner_version}" # __default_base_image takes the canonical build target for a Kubernetes command (e.g. k8s.io/kubernetes/cmd/kube-scheduler) diff --git a/build/dependencies.yaml b/build/dependencies.yaml index 5db5b3c4dabc2..ba2532f5de25f 100644 --- a/build/dependencies.yaml +++ b/build/dependencies.yaml @@ -18,7 +18,7 @@ dependencies: # CNI plugins - name: "cni" - version: 1.8.0 + version: 1.9.1 refPaths: - path: cluster/gce/config-common.sh match: WINDOWS_CNI_VERSION= @@ -31,7 +31,7 @@ dependencies: # CoreDNS - name: "coredns-kube-up" - version: 1.13.1 + version: 1.14.2 refPaths: - path: cluster/addons/dns/coredns/coredns.yaml.base match: registry.k8s.io/coredns @@ -41,14 +41,14 @@ dependencies: match: registry.k8s.io/coredns - name: "coredns-kubeadm" - version: 1.13.1 + version: 1.14.2 refPaths: - path: cmd/kubeadm/app/constants/constants.go match: CoreDNSVersion = # CRI Tools - name: "crictl" - version: 1.34.0 + version: 1.35.0 refPaths: - path: cluster/gce/windows/k8s-node-setup.psm1 match: CRICTL_VERSION = @@ -64,7 +64,7 @@ dependencies: # etcd - name: "etcd" - version: 3.6.6 + version: 3.6.8 refPaths: - path: cluster/gce/manifests/etcd.manifest match: etcd_docker_tag|etcd_version @@ -78,13 +78,35 @@ dependencies: match: registry.k8s.io/etcd - path: test/utils/image/manifest.go match: configs\[Etcd\] = Config{list\.GcEtcdRegistry, "etcd", "\d+\.\d+.\d+(-(alpha|beta|rc).\d+)?(-\d+)?"} + - path: test/e2e/testing-manifests/statefulset/etcd/statefulset.yaml + match: registry.k8s.io/etcd:\d+\.\d+\.\d+(-\d+)? - - name: "etcd-image" - version: 3.6.4 + # agnhost - E2E test image + - name: "agnhost" + version: 2.63.0 refPaths: - - path: cluster/images/etcd/Makefile - match: BUNDLED_ETCD_VERSIONS\?|LATEST_ETCD_VERSION\? - - path: cluster/images/etcd/migrate/options.go + - path: test/utils/image/manifest.go + match: configs\[Agnhost\] = Config{list\.PromoterE2eRegistry, "agnhost", "\d+\.\d+\.\d+"} + - path: test/images/kitten/BASEIMAGE + match: agnhost:\d+\.\d+\.\d+- + - path: test/images/nautilus/BASEIMAGE + match: agnhost:\d+\.\d+\.\d+- + - path: test/e2e/testing-manifests/kubectl/agnhost-primary-pod.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: test/e2e/testing-manifests/serviceloadbalancer/netexecrc.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: test/e2e/dra/test-driver/deploy/example/pod-inline.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: test/e2e/dra/test-driver/deploy/example/pod-external.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: test/e2e/dra/test-driver/deploy/example/pod-external-toleration.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: test/fixtures/pkg/kubectl/cmd/auth/rbac-resource-plus.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: test/fixtures/doc-yaml/user-guide/multi-pod.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ + - path: hack/testdata/pod-with-large-name.yaml + match: registry.k8s.io/e2e-test-images/agnhost:\d+\.\d+\.\d+ - name: "node-problem-detector" version: 1.34.0 @@ -107,14 +129,7 @@ dependencies: #- path: cluster/gce/windows/k8s-node-setup.psm1 # match: DEFAULT_NPD_VERSION - # From https://github.com/etcd-io/etcd/blob/main/Makefile - - name: "golang: etcd release version" - version: 1.23.11 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md - refPaths: - - path: cluster/images/etcd/Makefile - match: 'GOLANG_VERSION := \d+.\d+(alpha|beta|rc)?\.?(\d+)?' - - # This should ideally be updated to match the golang version + # This should ideally be updated to match the golang version in .go-version # but we can dynamically fetch go if the base image is out of date. # This allows us to ship go updates more quickly. # @@ -122,7 +137,7 @@ dependencies: # should also be updated, but go-runner is much harder to exploit and has # far less relevancy to go updates for Kubernetes more generally. - name: "registry.k8s.io/kube-cross: dependents" - version: v1.35.0-go1.25.7-bullseye.0 + version: v1.36.0-go1.26.4-bullseye.0 refPaths: - path: build/build-image/cross/VERSION @@ -132,7 +147,7 @@ dependencies: # This entry is a stub of the major version to allow dependency checks to # pass when building Kubernetes using a pre-release of Golang. - name: "golang: 1." - version: 1.25 + version: 1.26 refPaths: - path: build/build-image/cross/VERSION - path: hack/lib/golang.sh @@ -142,16 +157,6 @@ dependencies: - name: "registry.k8s.io/debian-base: dependents" version: bookworm-v1.0.6 refPaths: - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-arm:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-arm64:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-ppc64le:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-s390x:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - path: test/conformance/image/Makefile match: BASE_IMAGE_VERSION\?= - path: test/images/pets/peer-finder/BASEIMAGE @@ -170,7 +175,7 @@ dependencies: match: registry\.k8s\.io\/build-image\/debian-base:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - name: "registry.k8s.io/distroless-iptables: dependents" - version: v0.8.8 + version: v0.9.3 refPaths: - path: build/common.sh match: __default_distroless_iptables_version= @@ -178,62 +183,62 @@ dependencies: match: configs\[DistrolessIptables\] = Config{list\.BuildImageRegistry, "distroless-iptables", "v([0-9]+)\.([0-9]+)\.([0-9]+)"} - name: "registry.k8s.io/go-runner: dependents" - version: v2.4.0-go1.25.7-bookworm.0 + version: v2.4.0-go1.26.4-bookworm.0 refPaths: - path: build/common.sh match: __default_go_runner_version= - name: "registry.k8s.io/pause" - version: 3.10.1 + version: 3.10.2 refPaths: - path: build/pause/Makefile match: TAG\s*\?= - name: "registry.k8s.io/pause: dependents" - version: 3.10 + version: 3.10.2 refPaths: - path: cluster/gce/config-common.sh - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: cluster/gce/gci/configure-helper.sh - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: cluster/gce/windows/smoke-test.sh - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: cmd/kubeadm/app/constants/constants.go match: PauseVersion\s+= - path: hack/testdata/pod-with-precision.json - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: staging/src/k8s.io/kubectl/testdata/set/multi-resource-yaml.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: staging/src/k8s.io/kubectl/testdata/set/namespaced-resource.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/cmd/core.sh - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/fixtures/pkg/kubectl/cmd/set/namespaced-resource.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/benchmark-controller.json - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-default.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-node-affinity.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-pod-affinity.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-pod-anti-affinity.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-preferred-pod-affinity.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-preferred-pod-anti-affinity.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-preferred-topology-spreading.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-secret-volume.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/integration/scheduler_perf/templates/pod-with-topology-spreading.yaml - match: registry.k8s.io\/pause:\d+\.\d+ + match: registry.k8s.io\/pause:\d+\.\d+\.\d+ - path: test/utils/image/manifest.go - match: configs\[Pause\] = Config{list\.GcRegistry, "pause", "\d+\.\d+(.\d+)?"} + match: configs\[Pause\] = Config{list\.GcRegistry, "pause", "\d+\.\d+\.\d+"} - path: test/images/agnhost/fakeregistryserver/images.txt match: pause\s @@ -258,7 +263,5 @@ dependencies: refPaths: - path: build/pause/cloudbuild.yaml match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud - - path: cluster/images/etcd/cloudbuild.yaml - match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud - path: test/images/cloudbuild.yaml match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud diff --git a/build/lib/release.sh b/build/lib/release.sh index 4a086bdb39d94..135c1059a1c98 100644 --- a/build/lib/release.sh +++ b/build/lib/release.sh @@ -175,6 +175,10 @@ function kube::release::package_node_tarballs() { function kube::release::build_server_images() { kube::util::ensure-docker-buildx + # exclude kube-log-runner + local wrapped_binaries + wrapped_binaries=("${KUBE_SERVER_IMAGE_BINARIES[@]:1}") + # Clean out any old images rm -rf "${RELEASE_IMAGES}" local platform @@ -192,8 +196,8 @@ function kube::release::build_server_images() { # This fancy expression will expand to prepend a path # (${LOCAL_OUTPUT_BINPATH}/${platform}/) to every item in the - # KUBE_SERVER_IMAGE_BINARIES array. - cp "${KUBE_SERVER_IMAGE_BINARIES[@]/#/${LOCAL_OUTPUT_BINPATH}/${platform}/}" \ + # wrapped_binaries array. + cp "${wrapped_binaries[@]/#/${LOCAL_OUTPUT_BINPATH}/${platform}/}" \ "${release_stage}/server/bin/" kube::release::create_docker_images_for_server "${release_stage}/server/bin" "${arch}" @@ -318,6 +322,15 @@ function kube::release::create_docker_images_for_server() { docker_build_opts='--pull' fi + # kube-log-runner binary assumed to be staged here by build_server_images + # TODO: Windows (NOTE: We don't build Windows images currently) + local kube_log_runner_path="${LOCAL_OUTPUT_BINPATH}/linux/${arch}/kube-log-runner" + # warn if someone attempts to call this shell utility directly without building kube-log-runner ... + if [[ ! -f "${kube_log_runner_path}" ]]; then + kube::log::error "kube-log-runner missing, add staging/src/k8s.io/component-base/logs/kube-log-runner to server build targets if calling kube::build::images functions directly" + return 1 + fi + for wrappable in $binaries; do local binary_name=${wrappable%%,*} @@ -336,6 +349,7 @@ function kube::release::create_docker_images_for_server() { ( rm -rf "${docker_build_path}" mkdir -p "${docker_build_path}" + ln "${kube_log_runner_path}" "${docker_build_path}/kube-log-runner" ln "${binary_file_path}" "${docker_build_path}/${binary_name}" local build_log="${docker_build_path}/build.log" diff --git a/build/nsswitch.conf b/build/nsswitch.conf deleted file mode 100644 index 997b2ed005f2b..0000000000000 --- a/build/nsswitch.conf +++ /dev/null @@ -1,2 +0,0 @@ -# ensure go's non-cgo resolver respects /etc/hosts -hosts: files dns diff --git a/build/pause/CHANGELOG.md b/build/pause/CHANGELOG.md index d60ed694bf9b7..d05dc7990a507 100644 --- a/build/pause/CHANGELOG.md +++ b/build/pause/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.10.2 + +* Updating Windows pause image base to `mcr.microsoft.com/oss/v2/kubernetes/windows-pause-image-base:v0.4.1` to add Windows Server 2025 (ltsc2025) support. + # 3.10.1 * Updating base image for Windows pause container to `mcr.microsoft.com/oss/kubernetes/windows-pause-image-base:v0.4.1` to pick up security fixes in the nanoserver base image. ([#130102](https://github.com/kubernetes/kubernetes/pull/130102), [@marosset](https://github.com/marosset)) diff --git a/build/pause/Dockerfile.Rhel b/build/pause/Dockerfile.Rhel index 5297c15861dda..5751671a3d890 100644 --- a/build/pause/Dockerfile.Rhel +++ b/build/pause/Dockerfile.Rhel @@ -1,10 +1,10 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS builder WORKDIR /go/src/github.com/openshift/kubernetes/build/pause COPY . . RUN mkdir -p bin && \ gcc -Os -Wall -Werror -o bin/pause ./linux/pause.c -FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 +FROM registry.ci.openshift.org/ocp/5.0:base-rhel9 COPY --from=builder /go/src/github.com/openshift/kubernetes/build/pause/bin/pause /usr/bin/pod LABEL io.k8s.display-name="OpenShift Pod" \ io.k8s.description="This is a component of OpenShift and contains the binary that holds the pod namespaces." \ diff --git a/build/pause/Makefile b/build/pause/Makefile index 53fb498ab2551..c2446d8cd6bd7 100644 --- a/build/pause/Makefile +++ b/build/pause/Makefile @@ -17,7 +17,7 @@ REGISTRY ?= staging-k8s.gcr.io IMAGE = $(REGISTRY)/pause -TAG ?= 3.10.1 +TAG ?= 3.10.2 REV = $(shell git describe --contains --always --match='v*') # Architectures supported: amd64, arm, arm64, ppc64le and s390x @@ -32,7 +32,7 @@ OUTPUT_TYPE ?= docker BASE.linux := scratch # Source for windows pause image base is located at https://github.com/microsoft/windows-pause-image-base # BASE.windows includes separate manifests per os.version. -BASE.windows := mcr.microsoft.com/oss/kubernetes/windows-pause-image-base:v0.4.1@sha256:37cc10768383b55611d724a05eb18564cb5184c89b0c2faa7d4eff63475092df +BASE.windows := mcr.microsoft.com/oss/v2/kubernetes/windows-pause-image-base:v0.4.1@sha256:ba474944e82cac8913e0a402c7727c35d469977026b636bf460f0d52112c4e4f BASE := ${BASE.${OS}} JQ_IMAGE := ghcr.io/jqlang/jq@sha256:a186dcd84a1e28bb48cdf3d7768b890d08621a87bb651fadb7db6815a6bf5ad5 diff --git a/build/server-image/Dockerfile b/build/server-image/Dockerfile index 09d2cd1e6916e..727273082a679 100644 --- a/build/server-image/Dockerfile +++ b/build/server-image/Dockerfile @@ -20,3 +20,6 @@ ARG BINARY FROM "${BASEIMAGE}" COPY --chmod=755 ${BINARY} /usr/local/bin/${BINARY} +# NOTE: /go-runner is used here for compatibility +COPY --chmod=755 kube-log-runner /go-runner +ENTRYPOINT ["/go-runner"] diff --git a/build/server-image/kube-apiserver/Dockerfile b/build/server-image/kube-apiserver/Dockerfile index 27129d29bd9d6..ff815a0afee2b 100644 --- a/build/server-image/kube-apiserver/Dockerfile +++ b/build/server-image/kube-apiserver/Dockerfile @@ -20,11 +20,15 @@ ARG SETCAP_IMAGE # to setup qemu for the builder. FROM --platform=linux/$BUILDARCH ${SETCAP_IMAGE} ARG BINARY -COPY --chmod=755 ${BINARY} /${BINARY} +COPY --chmod=755 ${BINARY} /binary_to_set # We apply cap_net_bind_service so that kube-apiserver can be run as # non-root and still listen on port less than 1024 -RUN setcap cap_net_bind_service=+ep /${BINARY} +RUN ["setcap", "cap_net_bind_service=+ep", "/binary_to_set"] FROM --platform=linux/$TARGETARCH ${BASEIMAGE} ARG BINARY -COPY --from=0 /${BINARY} /usr/local/bin/${BINARY} + +COPY --from=0 /binary_to_set /usr/local/bin/${BINARY} +# NOTE: /go-runner is used here for compatibility +COPY --chmod=755 kube-log-runner /go-runner +ENTRYPOINT ["/go-runner"] diff --git a/build/tools.go b/build/tools.go index ad60ce3a675e0..0d3bab144030a 100644 --- a/build/tools.go +++ b/build/tools.go @@ -1,5 +1,4 @@ //go:build tools -// +build tools /* Copyright 2019 The Kubernetes Authors. diff --git a/cluster/addons/dns/coredns/coredns.yaml.base b/cluster/addons/dns/coredns/coredns.yaml.base index 9cce5c34537c0..4ede638fd6424 100644 --- a/cluster/addons/dns/coredns/coredns.yaml.base +++ b/cluster/addons/dns/coredns/coredns.yaml.base @@ -133,7 +133,7 @@ spec: kubernetes.io/os: linux containers: - name: coredns - image: registry.k8s.io/coredns/coredns:v1.13.1 + image: registry.k8s.io/coredns/coredns:v1.14.2 imagePullPolicy: IfNotPresent resources: limits: diff --git a/cluster/addons/dns/coredns/coredns.yaml.in b/cluster/addons/dns/coredns/coredns.yaml.in index ee07de6c328ba..acde4929a53a8 100644 --- a/cluster/addons/dns/coredns/coredns.yaml.in +++ b/cluster/addons/dns/coredns/coredns.yaml.in @@ -133,7 +133,7 @@ spec: kubernetes.io/os: linux containers: - name: coredns - image: registry.k8s.io/coredns/coredns:v1.13.1 + image: registry.k8s.io/coredns/coredns:v1.14.2 imagePullPolicy: IfNotPresent resources: limits: diff --git a/cluster/addons/dns/coredns/coredns.yaml.sed b/cluster/addons/dns/coredns/coredns.yaml.sed index 5df370800c978..5d0ea6703f3ba 100644 --- a/cluster/addons/dns/coredns/coredns.yaml.sed +++ b/cluster/addons/dns/coredns/coredns.yaml.sed @@ -133,7 +133,7 @@ spec: kubernetes.io/os: linux containers: - name: coredns - image: registry.k8s.io/coredns/coredns:v1.13.1 + image: registry.k8s.io/coredns/coredns:v1.14.2 imagePullPolicy: IfNotPresent resources: limits: diff --git a/cluster/addons/dns/kube-dns/kube-dns.yaml.base b/cluster/addons/dns/kube-dns/kube-dns.yaml.base index 2b97de8a6cff0..8441fdeaf9ec6 100644 --- a/cluster/addons/dns/kube-dns/kube-dns.yaml.base +++ b/cluster/addons/dns/kube-dns/kube-dns.yaml.base @@ -114,7 +114,7 @@ spec: kubernetes.io/os: linux containers: - name: kubedns - image: registry.k8s.io/dns/k8s-dns-kube-dns:1.26.4 + image: registry.k8s.io/dns/k8s-dns-kube-dns:1.26.7 resources: # TODO: Set memory limits when we've profiled the container for large # clusters, then set request = limit to keep this container in @@ -170,7 +170,7 @@ spec: runAsUser: 1001 runAsGroup: 1001 - name: dnsmasq - image: registry.k8s.io/dns/k8s-dns-dnsmasq-nanny:1.26.4 + image: registry.k8s.io/dns/k8s-dns-dnsmasq-nanny:1.26.7 livenessProbe: httpGet: path: /healthcheck/dnsmasq @@ -216,7 +216,7 @@ spec: - NET_BIND_SERVICE - SETGID - name: sidecar - image: registry.k8s.io/dns/k8s-dns-sidecar:1.26.4 + image: registry.k8s.io/dns/k8s-dns-sidecar:1.26.7 livenessProbe: httpGet: path: /metrics diff --git a/cluster/addons/dns/kube-dns/kube-dns.yaml.in b/cluster/addons/dns/kube-dns/kube-dns.yaml.in index bf80cb76680d9..3290bcb2c23a2 100644 --- a/cluster/addons/dns/kube-dns/kube-dns.yaml.in +++ b/cluster/addons/dns/kube-dns/kube-dns.yaml.in @@ -114,7 +114,7 @@ spec: kubernetes.io/os: linux containers: - name: kubedns - image: registry.k8s.io/dns/k8s-dns-kube-dns:1.26.4 + image: registry.k8s.io/dns/k8s-dns-kube-dns:1.26.7 resources: # TODO: Set memory limits when we've profiled the container for large # clusters, then set request = limit to keep this container in @@ -170,7 +170,7 @@ spec: runAsUser: 1001 runAsGroup: 1001 - name: dnsmasq - image: registry.k8s.io/dns/k8s-dns-dnsmasq-nanny:1.26.4 + image: registry.k8s.io/dns/k8s-dns-dnsmasq-nanny:1.26.7 livenessProbe: httpGet: path: /healthcheck/dnsmasq @@ -216,7 +216,7 @@ spec: - NET_BIND_SERVICE - SETGID - name: sidecar - image: registry.k8s.io/dns/k8s-dns-sidecar:1.26.4 + image: registry.k8s.io/dns/k8s-dns-sidecar:1.26.7 livenessProbe: httpGet: path: /metrics diff --git a/cluster/addons/dns/kube-dns/kube-dns.yaml.sed b/cluster/addons/dns/kube-dns/kube-dns.yaml.sed index 71e2f04ed56d9..5a4c957ed3abe 100644 --- a/cluster/addons/dns/kube-dns/kube-dns.yaml.sed +++ b/cluster/addons/dns/kube-dns/kube-dns.yaml.sed @@ -114,7 +114,7 @@ spec: kubernetes.io/os: linux containers: - name: kubedns - image: registry.k8s.io/dns/k8s-dns-kube-dns:1.26.4 + image: registry.k8s.io/dns/k8s-dns-kube-dns:1.26.7 resources: # TODO: Set memory limits when we've profiled the container for large # clusters, then set request = limit to keep this container in @@ -170,7 +170,7 @@ spec: runAsUser: 1001 runAsGroup: 1001 - name: dnsmasq - image: registry.k8s.io/dns/k8s-dns-dnsmasq-nanny:1.26.4 + image: registry.k8s.io/dns/k8s-dns-dnsmasq-nanny:1.26.7 livenessProbe: httpGet: path: /healthcheck/dnsmasq @@ -216,7 +216,7 @@ spec: - NET_BIND_SERVICE - SETGID - name: sidecar - image: registry.k8s.io/dns/k8s-dns-sidecar:1.26.4 + image: registry.k8s.io/dns/k8s-dns-sidecar:1.26.7 livenessProbe: httpGet: path: /metrics diff --git a/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml b/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml index 30a594227d747..19debbc317543 100644 --- a/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml +++ b/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml @@ -138,7 +138,7 @@ spec: operator: "Exists" containers: - name: node-cache - image: registry.k8s.io/dns/k8s-dns-node-cache:1.26.4 + image: registry.k8s.io/dns/k8s-dns-node-cache:1.26.7 resources: requests: cpu: 25m diff --git a/cluster/addons/kube-proxy/OWNERS b/cluster/addons/kube-proxy/OWNERS deleted file mode 100644 index 92644a5a03add..0000000000000 --- a/cluster/addons/kube-proxy/OWNERS +++ /dev/null @@ -1,12 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - bowei - - freehan - - mrhohn -reviewers: - - bowei - - freehan - - mrhohn -emeritus_approvers: - - jingax10 diff --git a/cluster/addons/kube-proxy/kube-proxy-ds.yaml b/cluster/addons/kube-proxy/kube-proxy-ds.yaml deleted file mode 100644 index 75ea9043c48cf..0000000000000 --- a/cluster/addons/kube-proxy/kube-proxy-ds.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# Please keep kube-proxy configuration in-sync with: -# cluster/saltbase/salt/kube-proxy/kube-proxy.manifest - -apiVersion: apps/v1 -kind: DaemonSet -metadata: - labels: - k8s-app: kube-proxy - addonmanager.kubernetes.io/mode: Reconcile - name: kube-proxy - namespace: kube-system -spec: - selector: - matchLabels: - k8s-app: kube-proxy - updateStrategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 10% - template: - metadata: - labels: - k8s-app: kube-proxy - spec: - priorityClassName: system-node-critical - hostNetwork: true - nodeSelector: - kubernetes.io/os: linux - node.kubernetes.io/kube-proxy-ds-ready: "true" - tolerations: - - operator: "Exists" - effect: "NoExecute" - - operator: "Exists" - effect: "NoSchedule" - containers: - - name: kube-proxy - image: {{pillar['kube_docker_registry']}}/kube-proxy-{{pillar['host_arch']}}:{{pillar['kube-proxy_docker_tag']}} - resources: - requests: - cpu: {{ cpurequest }} - memory: {{ memoryrequest }} - command: - - /bin/sh - - -c - - kube-proxy {{cluster_cidr}} --oom-score-adj=-998 {{params}} 1>>/var/log/kube-proxy.log 2>&1 - env: - - name: KUBERNETES_SERVICE_HOST - value: {{kubernetes_service_host_env_value}} - {{kube_cache_mutation_detector_env_name}} - {{kube_cache_mutation_detector_env_value}} - securityContext: - privileged: true - volumeMounts: - - mountPath: /var/log - name: varlog - readOnly: false - - mountPath: /run/xtables.lock - name: xtables-lock - readOnly: false - - mountPath: /lib/modules - name: lib-modules - readOnly: true - volumes: - - name: varlog - hostPath: - path: /var/log - - name: xtables-lock - hostPath: - path: /run/xtables.lock - type: FileOrCreate - - name: lib-modules - hostPath: - path: /lib/modules - serviceAccountName: kube-proxy diff --git a/cluster/addons/kube-proxy/kube-proxy-rbac.yaml b/cluster/addons/kube-proxy/kube-proxy-rbac.yaml deleted file mode 100644 index 0758fd025f0df..0000000000000 --- a/cluster/addons/kube-proxy/kube-proxy-rbac.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kube-proxy - namespace: kube-system - labels: - addonmanager.kubernetes.io/mode: Reconcile ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: system:kube-proxy - labels: - addonmanager.kubernetes.io/mode: Reconcile -subjects: - - kind: ServiceAccount - name: kube-proxy - namespace: kube-system -roleRef: - kind: ClusterRole - name: system:node-proxier - apiGroup: rbac.authorization.k8s.io diff --git a/cluster/addons/volumesnapshots/volume-snapshot-controller/volume-snapshot-controller-deployment.yaml b/cluster/addons/volumesnapshots/volume-snapshot-controller/volume-snapshot-controller-deployment.yaml index a0498468f0b36..db8c11cd7c5aa 100644 --- a/cluster/addons/volumesnapshots/volume-snapshot-controller/volume-snapshot-controller-deployment.yaml +++ b/cluster/addons/volumesnapshots/volume-snapshot-controller/volume-snapshot-controller-deployment.yaml @@ -22,7 +22,7 @@ spec: serviceAccount: volume-snapshot-controller containers: - name: volume-snapshot-controller - image: registry.k8s.io/sig-storage/snapshot-controller:v8.1.0 + image: registry.k8s.io/sig-storage/snapshot-controller:v8.5.0 args: - "--v=5" - "--metrics-path=/metrics" diff --git a/cluster/gce/addons/konnectivity-agent/konnectivity-agent-ds.yaml b/cluster/gce/addons/konnectivity-agent/konnectivity-agent-ds.yaml index 68d52a3ad7c37..df2720d843076 100644 --- a/cluster/gce/addons/konnectivity-agent/konnectivity-agent-ds.yaml +++ b/cluster/gce/addons/konnectivity-agent/konnectivity-agent-ds.yaml @@ -27,7 +27,7 @@ spec: nodeSelector: kubernetes.io/os: linux containers: - - image: registry.k8s.io/kas-network-proxy/proxy-agent:v0.31.2 + - image: registry.k8s.io/kas-network-proxy/proxy-agent:v0.34.0 name: konnectivity-agent command: ["/proxy-agent"] args: [ diff --git a/cluster/gce/config-common.sh b/cluster/gce/config-common.sh index e50985fc54bcd..b1d715f3c267c 100644 --- a/cluster/gce/config-common.sh +++ b/cluster/gce/config-common.sh @@ -136,7 +136,7 @@ export WINDOWS_CNI_CONFIG_DIR="${WINDOWS_K8S_DIR}\cni\config" # CNI storage path for Windows nodes export WINDOWS_CNI_STORAGE_PATH="https://github.com/containernetworking/plugins/releases/download" # CNI version for Windows nodes -export WINDOWS_CNI_VERSION="v1.8.0" +export WINDOWS_CNI_VERSION="v1.9.1" # Pod manifests directory for Windows nodes on Windows nodes. export WINDOWS_MANIFESTS_DIR="${WINDOWS_K8S_DIR}\manifests" # Directory where cert/key files will be stores on Windows nodes. @@ -154,7 +154,7 @@ export WINDOWS_KUBEPROXY_KUBECONFIG_FILE="${WINDOWS_K8S_DIR}\kubeproxy.kubeconfi # Path for kube-proxy kubeconfig file on Windows nodes. export WINDOWS_NODEPROBLEMDETECTOR_KUBECONFIG_FILE="${WINDOWS_K8S_DIR}\node-problem-detector.kubeconfig" # Pause container image for Windows container. -export WINDOWS_INFRA_CONTAINER="registry.k8s.io/pause:3.10" +export WINDOWS_INFRA_CONTAINER="registry.k8s.io/pause:3.10.2" # Storage Path for csi-proxy. csi-proxy only needs to be installed for Windows. export CSI_PROXY_STORAGE_PATH="https://storage.googleapis.com/gke-release/csi-proxy" # Version for csi-proxy @@ -162,11 +162,11 @@ export CSI_PROXY_VERSION="${CSI_PROXY_VERSION:-v1.2.1-gke.2}" # csi-proxy additional flags, there are additional flags that cannot be unset in k8s-node-setup.psm1 export CSI_PROXY_FLAGS="${CSI_PROXY_FLAGS:-}" # Storage path for auth-provider-gcp binaries -export AUTH_PROVIDER_GCP_STORAGE_PATH="${AUTH_PROVIDER_GCP_STORAGE_PATH:-https://storage.googleapis.com/gke-release/auth-provider-gcp}" +export AUTH_PROVIDER_GCP_STORAGE_PATH="${AUTH_PROVIDER_GCP_STORAGE_PATH:-https://artifacts.k8s.io/binaries/cloud-provider-gcp}" # auth-provider-gcp version -export AUTH_PROVIDER_GCP_VERSION="${AUTH_PROVIDER_GCP_VERSION:-v0.0.2-gke.4}" +export AUTH_PROVIDER_GCP_VERSION="${AUTH_PROVIDER_GCP_VERSION:-v35.0.0}" # Hash of auth-provider-gcp.exe binary -export AUTH_PROVIDER_GCP_HASH_WINDOWS_AMD64="${AUTH_PROVIDER_GCP_HASH_WINDOWS_AMD64:-348af2c189d938e1a4fa5ac5c640d21e003da1f000abcd6fd7eef2acd0678638286e40703618758d4fdfe2cc4b90e920f0422128ec777c74054af9dd4405de12}" +export AUTH_PROVIDER_GCP_HASH_WINDOWS_AMD64="${AUTH_PROVIDER_GCP_HASH_WINDOWS_AMD64:-a5e8c41369d7c49215dc2118185c5dcacc6e2f600dc1b8e28f3837a4191a18fdf0c8aed97b852bc8b273dd6c85dffba59471d60a86d126133789f83af79109b9}" # Directory of kubelet image credential provider binary files on windows export AUTH_PROVIDER_GCP_LINUX_BIN_DIR="${AUTH_PROVIDER_GCP_LINUX_BIN_DIR:-/home/kubernetes/bin}" # Location of kubelet image credential provider config file on windows diff --git a/cluster/gce/config-default.sh b/cluster/gce/config-default.sh index a104d3c1f8083..c742e9b16714f 100755 --- a/cluster/gce/config-default.sh +++ b/cluster/gce/config-default.sh @@ -88,7 +88,7 @@ fi # By default, the latest image from the image family will be used unless an # explicit image will be set. GCI_VERSION=${KUBE_GCI_VERSION:-} -IMAGE_FAMILY=${KUBE_GCE_IMAGE_FAMILY:-cos-121-lts} +IMAGE_FAMILY=${KUBE_GCE_IMAGE_FAMILY:-cos-125-lts} IMAGE_PROJECT=${KUBE_GCE_IMAGE_PROJECT:-cos-cloud} export MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-} export MASTER_IMAGE_FAMILY=${KUBE_GCE_MASTER_IMAGE_FAMILY:-${IMAGE_FAMILY}} @@ -469,17 +469,6 @@ PROMETHEUS_TO_SD_ENDPOINT="${PROMETHEUS_TO_SD_ENDPOINT:-https://monitoring.googl PROMETHEUS_TO_SD_PREFIX="${PROMETHEUS_TO_SD_PREFIX:-custom.googleapis.com}" ENABLE_PROMETHEUS_TO_SD="${ENABLE_PROMETHEUS_TO_SD:-false}" -# TODO(#51292): Make kube-proxy Daemonset default and remove the configuration here. -# Optional: [Experiment Only] Run kube-proxy as a DaemonSet if set to true, run as static pods otherwise. -KUBE_PROXY_DAEMONSET="${KUBE_PROXY_DAEMONSET:-false}" # true, false - -# Control whether the startup scripts manage the lifecycle of kube-proxy -# When true, the startup scripts do not enable kube-proxy either as a daemonset addon or as a static pod -# regardless of the value of KUBE_PROXY_DAEMONSET. -# When false, the value of KUBE_PROXY_DAEMONSET controls whether kube-proxy comes up as a static pod or -# as an addon daemonset. -KUBE_PROXY_DISABLE="${KUBE_PROXY_DISABLE:-false}" # true, false - # Will be passed into the kube-proxy via `--detect-local-mode` DETECT_LOCAL_MODE="${DETECT_LOCAL_MODE:-}" diff --git a/cluster/gce/config-test.sh b/cluster/gce/config-test.sh index 36bb207d6a47a..f774f26a8fd35 100755 --- a/cluster/gce/config-test.sh +++ b/cluster/gce/config-test.sh @@ -101,7 +101,7 @@ ALLOWED_NOTREADY_NODES=${ALLOWED_NOTREADY_NODES:-$(($(get-num-nodes) / 100))} # By default, the latest image from the image family will be used unless an # explicit image will be set. GCI_VERSION=${KUBE_GCI_VERSION:-} -IMAGE_FAMILY=${KUBE_GCE_IMAGE_FAMILY:-cos-121-lts} +IMAGE_FAMILY=${KUBE_GCE_IMAGE_FAMILY:-cos-125-lts} IMAGE_PROJECT=${KUBE_GCE_IMAGE_PROJECT:-cos-cloud} export MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-} export MASTER_IMAGE_FAMILY=${KUBE_GCE_MASTER_IMAGE_FAMILY:-${IMAGE_FAMILY}} @@ -246,7 +246,7 @@ fi if [[ "${MASTER_OS_DISTRIBUTION}" = 'gci' ]] || [[ "${MASTER_OS_DISTRIBUTION}" = 'ubuntu' ]]; then MASTER_KUBELET_TEST_ARGS="${MASTER_KUBELET_TEST_ARGS:-} --kernel-memcg-notification=true" fi -APISERVER_TEST_ARGS="${APISERVER_TEST_ARGS:-} --runtime-config=extensions/v1beta1,scheduling.k8s.io/v1alpha1 ${TEST_CLUSTER_DELETE_COLLECTION_WORKERS} ${TEST_CLUSTER_MAX_REQUESTS_INFLIGHT}" +APISERVER_TEST_ARGS="${APISERVER_TEST_ARGS:-} --runtime-config=extensions/v1beta1 ${TEST_CLUSTER_DELETE_COLLECTION_WORKERS} ${TEST_CLUSTER_MAX_REQUESTS_INFLIGHT}" CONTROLLER_MANAGER_TEST_ARGS="${CONTROLLER_MANAGER_TEST_ARGS:-} ${TEST_CLUSTER_RESYNC_PERIOD} ${TEST_CLUSTER_API_CONTENT_TYPE}" SCHEDULER_TEST_ARGS="${SCHEDULER_TEST_ARGS:-} ${TEST_CLUSTER_API_CONTENT_TYPE}" KUBEPROXY_TEST_ARGS="${KUBEPROXY_TEST_ARGS:-} ${TEST_CLUSTER_API_CONTENT_TYPE}" @@ -522,17 +522,6 @@ PROMETHEUS_TO_SD_ENDPOINT=${PROMETHEUS_TO_SD_ENDPOINT:-https://monitoring.google PROMETHEUS_TO_SD_PREFIX=${PROMETHEUS_TO_SD_PREFIX:-custom.googleapis.com} ENABLE_PROMETHEUS_TO_SD=${ENABLE_PROMETHEUS_TO_SD:-true} -# TODO(#51292): Make kube-proxy Daemonset default and remove the configuration here. -# Optional: [Experiment Only] Run kube-proxy as a DaemonSet if set to true, run as static pods otherwise. -KUBE_PROXY_DAEMONSET=${KUBE_PROXY_DAEMONSET:-false} # true, false - -# Control whether the startup scripts manage the lifecycle of kube-proxy -# When true, the startup scripts do not enable kube-proxy either as a daemonset addon or as a static pod -# regardless of the value of KUBE_PROXY_DAEMONSET. -# When false, the value of KUBE_PROXY_DAEMONSET controls whether kube-proxy comes up as a static pod or -# as an addon daemonset. -KUBE_PROXY_DISABLE="${KUBE_PROXY_DISABLE:-false}" # true, false - # Optional: Change the kube-proxy implementation. Choices are [iptables, ipvs, nftables]. KUBE_PROXY_MODE=${KUBE_PROXY_MODE:-iptables} diff --git a/cluster/gce/gci/configure-helper.sh b/cluster/gce/gci/configure-helper.sh index 67df1482d9f2b..95c481290683a 100755 --- a/cluster/gce/gci/configure-helper.sh +++ b/cluster/gce/gci/configure-helper.sh @@ -2170,7 +2170,6 @@ function update-legacy-addon-node-labels() { sleep 5 done update-node-label "beta.kubernetes.io/metadata-proxy-ready=true,cloud.google.com/metadata-proxy-ready!=true" "cloud.google.com/metadata-proxy-ready=true" - update-node-label "beta.kubernetes.io/kube-proxy-ds-ready=true,node.kubernetes.io/kube-proxy-ds-ready!=true" "node.kubernetes.io/kube-proxy-ds-ready=true" update-node-label "beta.kubernetes.io/masq-agent-ds-ready=true,node.kubernetes.io/masq-agent-ds-ready!=true" "node.kubernetes.io/masq-agent-ds-ready=true" } @@ -2893,17 +2892,6 @@ function start-kube-addons { fi # Set up manifests of other addons. - if [[ "${KUBE_PROXY_DAEMONSET:-}" == "true" ]] && [[ "${KUBE_PROXY_DISABLE:-}" != "true" ]]; then - if [ -n "${CUSTOM_KUBE_PROXY_YAML:-}" ]; then - # Replace with custom GKE kube proxy. - cat > "$src_dir/kube-proxy/kube-proxy-ds.yaml" < "${svc}" <<'EOF' +[Unit] +Description=containerd container runtime +Documentation=https://containerd.io +After=network.target local-fs.target +[Service] +ExecStartPre=-/sbin/modprobe overlay +ExecStart=/usr/bin/containerd +Type=notify +Delegate=yes +KillMode=process +Restart=always +RestartSec=5 +LimitNPROC=infinity +LimitCORE=infinity +LimitNOFILE=infinity +TasksMax=infinity +OOMScoreAdjust=-999 +[Install] +WantedBy=multi-user.target +EOF + systemctl daemon-reload && systemctl enable containerd +} + +# Download and install containerd binary from GitHub +function install-containerd-binary { + local -r version="$1" temp_dir="$(mktemp -d)" + local -r url="https://github.com/containerd/containerd/releases/download/${version}/containerd-${version#v}-${HOST_PLATFORM}-${HOST_ARCH}.tar.gz" + if download-robust "containerd ${version}" "${temp_dir}" "${url}"; then + tar --overwrite -xzf "${temp_dir}"/containerd-*.tar.gz -C /usr/ + rm -rf "${temp_dir}"; return 0 + fi + rm -rf "${temp_dir}"; return 1 +} + +# Download and install runc binary from GitHub +function install-runc-binary { + local -r version="$1" temp_dir="$(mktemp -d)" + local -r url="https://github.com/opencontainers/runc/releases/download/${version}/runc.${HOST_ARCH}" + if download-robust "runc ${version}" "${temp_dir}" "${url}"; then + cp "${temp_dir}/runc.${HOST_ARCH}" /usr/sbin/runc && chmod 755 /usr/sbin/runc + rm -rf "${temp_dir}"; return 0 + fi + rm -rf "${temp_dir}"; return 1 +} + +# Install containerd on Ubuntu. When both UBUNTU_INSTALL_CONTAINERD_VERSION and +# UBUNTU_INSTALL_RUNC_VERSION are set, skips apt and downloads binaries directly. function install-containerd-ubuntu { - # bailout if we are not on ubuntu if [[ -z "$(command -v lsb_release)" || $(lsb_release -si) != "Ubuntu" ]]; then - echo "Unable to automatically install containerd in non-ubuntu image. Bailing out..." - exit 2 + echo "Unable to automatically install containerd in non-ubuntu image. Bailing out..."; exit 2 + fi + + local -r custom_containerd="${UBUNTU_INSTALL_CONTAINERD_VERSION:-}" + local -r custom_runc="${UBUNTU_INSTALL_RUNC_VERSION:-}" + + # Both versions specified: skip apt, install binaries directly + if [[ -n "${custom_containerd}" && -n "${custom_runc}" ]]; then + echo "Installing containerd ${custom_containerd} and runc ${custom_runc} (skipping apt)" + ensure-containerd-systemd-service + install-containerd-binary "${custom_containerd}" || { echo "ERROR: containerd download failed"; exit 1; } + install-runc-binary "${custom_runc}" || { echo "ERROR: runc download failed"; exit 1; } + systemctl start containerd + return fi - # Install dependencies, some of these are already installed in the image but - # that's fine since they won't re-install and we can reuse the code below - # for another image someday. - apt-get update - apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - socat \ - curl \ - gnupg2 \ - nfs-common \ - software-properties-common \ - lsb-release - - release=$(lsb_release -cs) - - # Add the Docker apt-repository (as we install containerd from there) + # Install from Docker apt repo, optionally override binaries + local -r release="$(lsb_release -cs)" + local -r keyring="/etc/apt/keyrings/docker.gpg" + mkdir -p "$(dirname "${keyring}")" # shellcheck disable=SC2086 - curl ${CURL_FLAGS} \ - --location \ - "https://download.docker.com/${HOST_PLATFORM}/$(. /etc/os-release; echo "$ID")/gpg" \ - | apt-key add - - add-apt-repository \ - "deb [arch=${HOST_ARCH}] https://download.docker.com/${HOST_PLATFORM}/$(. /etc/os-release; echo "$ID") \ - $release stable" - - # Install containerd from Docker repo - apt-get update && \ - apt-get install -y --no-install-recommends containerd + curl ${CURL_FLAGS} -fsSL "https://download.docker.com/${HOST_PLATFORM}/$(. /etc/os-release; echo "$ID")/gpg" \ + | gpg --batch --dearmor -o "${keyring}" + chmod a+r "${keyring}" + echo "deb [arch=${HOST_ARCH} signed-by=${keyring}] https://download.docker.com/${HOST_PLATFORM}/$(. /etc/os-release; echo "$ID") ${release} stable" \ + > /etc/apt/sources.list.d/docker.list + + apt-get update && apt-get install -y --no-install-recommends containerd.io rm -rf /var/lib/apt/lists/* - # Override to latest versions of containerd and runc systemctl stop containerd - if [[ -n "${UBUNTU_INSTALL_CONTAINERD_VERSION:-}" ]]; then - local temp_dir - temp_dir=$(mktemp -d) - - # Download containerd - if download-robust "containerd ${UBUNTU_INSTALL_CONTAINERD_VERSION}" "${temp_dir}" \ - "https://github.com/containerd/containerd/releases/download/${UBUNTU_INSTALL_CONTAINERD_VERSION}/containerd-${UBUNTU_INSTALL_CONTAINERD_VERSION:1}-${HOST_PLATFORM}-${HOST_ARCH}.tar.gz"; then - tar --overwrite -xzv -C /usr/ -f "${temp_dir}"/containerd-*.tar.gz - fi - rm -rf "${temp_dir}" - fi - if [[ -n "${UBUNTU_INSTALL_RUNC_VERSION:-}" ]]; then - local temp_dir - temp_dir=$(mktemp -d) - - # Download and install runc - if download-robust "runc ${UBUNTU_INSTALL_RUNC_VERSION}" "${temp_dir}" \ - "https://github.com/opencontainers/runc/releases/download/${UBUNTU_INSTALL_RUNC_VERSION}/runc.${HOST_ARCH}"; then - cp "${temp_dir}/runc.${HOST_ARCH}" /usr/sbin/runc && chmod 755 /usr/sbin/runc - fi - rm -rf "${temp_dir}" - fi - sudo systemctl start containerd + [[ -n "${custom_containerd}" ]] && install-containerd-binary "${custom_containerd}" + [[ -n "${custom_runc}" ]] && install-runc-binary "${custom_runc}" + systemctl start containerd } # If we are on cos we can try to install containerd @@ -621,7 +645,7 @@ function install-containerd-cos { function install-auth-provider-gcp { local -r filename="auth-provider-gcp" - local -r auth_provider_storage_full_path="${AUTH_PROVIDER_GCP_STORAGE_PATH}/${AUTH_PROVIDER_GCP_VERSION}/${HOST_PLATFORM}_${HOST_ARCH}/${filename}" + local -r auth_provider_storage_full_path="${AUTH_PROVIDER_GCP_STORAGE_PATH}/${AUTH_PROVIDER_GCP_VERSION}/auth-provider-gcp/${HOST_PLATFORM}/${HOST_ARCH}/${filename}" echo "Downloading auth-provider-gcp ${auth_provider_storage_full_path}" . case "${HOST_ARCH}" in diff --git a/cluster/gce/gci/master.yaml b/cluster/gce/gci/master.yaml index 75cf541c98fac..adde05a357002 100644 --- a/cluster/gce/gci/master.yaml +++ b/cluster/gce/gci/master.yaml @@ -129,6 +129,8 @@ write_files: WantedBy=multi-user.target runcmd: + - systemctl mask apt-news.service apt-news.timer esm-cache.service snapd.service snapd.socket lxd-installer.socket ubuntu-advantage.service unattended-upgrades.service motd-news.timer update-notifier-motd.timer update-notifier-download.timer || true + - systemctl stop unattended-upgrades.service || true - systemctl daemon-reload - systemctl enable kube-bootstrap-logs-forwarder.service - systemctl enable kube-master-installation.service diff --git a/cluster/gce/gci/mounter/mounter.go b/cluster/gce/gci/mounter/mounter.go index 88dc459ea8148..7827556c4053c 100644 --- a/cluster/gce/gci/mounter/mounter.go +++ b/cluster/gce/gci/mounter/mounter.go @@ -28,6 +28,7 @@ const ( // Location of the mount file to use chrootCmd = "chroot" mountCmd = "mount" + mountBin = "/bin/mount" rootfs = "rootfs" nfsRPCBindErrMsg = "mount.nfs: rpc.statd is not running but is required for remote locking.\nmount.nfs: Either use '-o nolock' to keep locks local, or start statd.\nmount.nfs: an incorrect mount option was specified\n" rpcBindCmd = "/sbin/rpcbind" @@ -60,12 +61,12 @@ func main() { } } -// MountInChroot is to run mount within chroot with the passing root directory +// mountInChroot runs mount within chroot with the passing root directory func mountInChroot(rootfsPath string, args []string) error { if _, err := os.Stat(rootfsPath); os.IsNotExist(err) { return fmt.Errorf("path <%s> does not exist", rootfsPath) } - args = append([]string{rootfsPath, mountCmd}, args...) + args = append([]string{rootfsPath, mountBin}, args...) output, err := exec.Command(chrootCmd, args...).CombinedOutput() if err == nil { return nil diff --git a/cluster/gce/gci/node.yaml b/cluster/gce/gci/node.yaml index 3d03df211b7e4..dabaf9145bf92 100644 --- a/cluster/gce/gci/node.yaml +++ b/cluster/gce/gci/node.yaml @@ -87,6 +87,8 @@ write_files: options sunrpc max_resvport=986 runcmd: + - systemctl mask apt-news.service apt-news.timer esm-cache.service snapd.service snapd.socket lxd-installer.socket ubuntu-advantage.service unattended-upgrades.service motd-news.timer update-notifier-motd.timer update-notifier-download.timer || true + - systemctl stop unattended-upgrades.service || true - systemctl daemon-reload - systemctl enable kube-node-installation.service - systemctl enable kube-node-configuration.service diff --git a/cluster/gce/manifests/cloud-controller-manager.manifest b/cluster/gce/manifests/cloud-controller-manager.manifest index 6e0bd67e8954f..831c05d4a595f 100644 --- a/cluster/gce/manifests/cloud-controller-manager.manifest +++ b/cluster/gce/manifests/cloud-controller-manager.manifest @@ -23,7 +23,7 @@ "containers":[ { "name": "cloud-controller-manager", - "image": "registry.k8s.io/cloud-provider-gcp/cloud-controller-manager:v33.1.1", + "image": "registry.k8s.io/cloud-provider-gcp/cloud-controller-manager:v35.0.2", "command": ["/go-runner"], "resources": { "requests": { diff --git a/cluster/gce/manifests/etcd.manifest b/cluster/gce/manifests/etcd.manifest index b73ee24a91fd4..aae56ec2b6cbc 100644 --- a/cluster/gce/manifests/etcd.manifest +++ b/cluster/gce/manifests/etcd.manifest @@ -18,7 +18,7 @@ { "name": "etcd-container", {{security_context}} - "image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.6.6-0') }}", + "image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.6.8-0') }}", "resources": { "requests": { "cpu": {{ cpulimit }} @@ -43,7 +43,7 @@ "value": "{{ pillar.get('storage_backend', 'etcd3') }}" }, { "name": "TARGET_VERSION", - "value": "{{ pillar.get('etcd_version', '3.6.6') }}" + "value": "{{ pillar.get('etcd_version', '3.6.8') }}" }, { "name": "DO_NOT_MOVE_BINARIES", diff --git a/cluster/gce/manifests/konnectivity-server.yaml b/cluster/gce/manifests/konnectivity-server.yaml index ad0b0db26bfd6..289f129a7ce9e 100644 --- a/cluster/gce/manifests/konnectivity-server.yaml +++ b/cluster/gce/manifests/konnectivity-server.yaml @@ -20,7 +20,7 @@ spec: {{ disallow_privilege_escalation}} {{ capabilities }} {{ drop_capabilities }} - image: registry.k8s.io/kas-network-proxy/proxy-server:v0.31.2 + image: registry.k8s.io/kas-network-proxy/proxy-server:v0.34.0 resources: requests: cpu: 25m diff --git a/cluster/gce/manifests/kube-proxy.manifest b/cluster/gce/manifests/kube-proxy.manifest index ede540b5a795a..82e4310cdde8b 100644 --- a/cluster/gce/manifests/kube-proxy.manifest +++ b/cluster/gce/manifests/kube-proxy.manifest @@ -1,5 +1,3 @@ -# Please keep kube-proxy configuration in-sync with: -# cluster/addons/kube-proxy/kube-proxy-ds.yaml apiVersion: v1 kind: Pod metadata: diff --git a/cluster/gce/upgrade-aliases.sh b/cluster/gce/upgrade-aliases.sh index b8f2f4648c7d3..a6ffe1bc25beb 100755 --- a/cluster/gce/upgrade-aliases.sh +++ b/cluster/gce/upgrade-aliases.sh @@ -170,8 +170,8 @@ export KUBE_GCE_ENABLE_IP_ALIASES=true export SECONDARY_RANGE_NAME="pods-default" export STORAGE_BACKEND="etcd3" export STORAGE_MEDIA_TYPE="application/vnd.kubernetes.protobuf" -export ETCD_IMAGE=3.6.6-0 -export ETCD_VERSION=3.6.6 +export ETCD_IMAGE=3.6.8-0 +export ETCD_VERSION=3.6.8 # Upgrade master with updated kube envs "${KUBE_ROOT}/cluster/gce/upgrade.sh" -M -l diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index cbc361b0c7fcd..5ae35b3009d25 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -627,11 +627,6 @@ function write-windows-node-env { function build-linux-node-labels { local node_type=$1 local node_labels="" - if [[ "${KUBE_PROXY_DAEMONSET:-}" == "true" && "${node_type}" != "master" ]]; then - # Add kube-proxy daemonset label to node to avoid situation during cluster - # upgrade/downgrade when there are two instances of kube-proxy running on a node. - node_labels="node.kubernetes.io/kube-proxy-ds-ready=true" - fi if [[ -n "${NODE_LABELS:-}" ]]; then node_labels="${node_labels:+${node_labels},}${NODE_LABELS}" fi @@ -1153,7 +1148,6 @@ LOCAL_DNS_IP: $(yaml-quote "${LOCAL_DNS_IP:-}") DNS_DOMAIN: $(yaml-quote "${DNS_DOMAIN:-}") DNS_MEMORY_LIMIT: $(yaml-quote "${DNS_MEMORY_LIMIT:-}") ENABLE_DNS_HORIZONTAL_AUTOSCALER: $(yaml-quote "${ENABLE_DNS_HORIZONTAL_AUTOSCALER:-false}") -KUBE_PROXY_DAEMONSET: $(yaml-quote "${KUBE_PROXY_DAEMONSET:-false}") KUBE_PROXY_TOKEN: $(yaml-quote "${KUBE_PROXY_TOKEN:-}") KUBE_PROXY_MODE: $(yaml-quote "${KUBE_PROXY_MODE:-iptables}") DETECT_LOCAL_MODE: $(yaml-quote "${DETECT_LOCAL_MODE:-}") @@ -2144,18 +2138,14 @@ function update-or-verify-gcloud() { version=$(gcloud version --format=json) python3 -c" import json,sys -from distutils import version -minVersion = version.LooseVersion('1.3.0') required = [ 'alpha', 'beta', 'core' ] data = json.loads(sys.argv[1]) rel = data.get('Google Cloud SDK') if 'CL @' in rel: print('Using dev version of gcloud: %s' %rel) exit(0) -if rel != 'HEAD' and version.LooseVersion(rel) < minVersion: - print('gcloud version out of date ( < %s )' % minVersion) - exit(1) + missing = [] for c in required: if not data.get(c): @@ -3701,28 +3691,6 @@ function kube-down() { } & done - # Wait for last batch of jobs - kube::util::wait-for-jobs || { - echo -e "Failed to delete instance group(s)." >&2 - } - - # Deliberately do not quote, do not change unless a bug is found - # shellcheck disable=SC2068 - for template in ${templates[@]:-}; do - { - if gcloud compute instance-templates describe --project "${PROJECT}" "${template}" &>/dev/null; then - gcloud compute instance-templates delete \ - --project "${PROJECT}" \ - --quiet \ - "${template}" - fi - } & - done - - # Wait for last batch of jobs - kube::util::wait-for-jobs || { - echo -e "Failed to delete instance template(s)." >&2 - } # Delete the special heapster node (if it exists). if [[ -n "${HEAPSTER_MACHINE_TYPE:-}" ]]; then @@ -3735,6 +3703,10 @@ function kube-down() { --delete-disks all \ --zone "${ZONE}" \ "${heapster_machine_name}" + # ^^^ The invocation above continues to be blocking. + # It may be worth it for some scalability jobs to + # also make it non-blocking, but that is left to + # owners of those jobs. fi fi fi @@ -3766,7 +3738,27 @@ function kube-down() { --quiet \ --delete-disks all \ --zone "${ZONE}" \ - "${REPLICA_NAME}" + "${REPLICA_NAME}" & + fi + + # Wait for last batch of jobs + kube::util::wait-for-jobs || { + echo -e "Failed to delete instance group(s) and replica(s)." >&2 + } + + if [[ "${KUBE_DELETE_NODES:-}" != "false" ]]; then + # Deliberately do not quote, do not change unless a bug is found + # shellcheck disable=SC2068 + for template in ${templates[@]:-}; do + { + if gcloud compute instance-templates describe --project "${PROJECT}" "${template}" &>/dev/null; then + gcloud compute instance-templates delete \ + --project "${PROJECT}" \ + --quiet \ + "${template}" & + fi + } & + done fi # Delete the master replica pd (possibly leaked by kube-up if master create failed). @@ -3777,9 +3769,15 @@ function kube-down() { --project "${PROJECT}" \ --quiet \ --zone "${ZONE}" \ - "${replica_pd}" + "${replica_pd}" && + echo "Deleted ${replica_pd}" & # "gcloud compute disks delete --quiet" does not print anything, in contrast to other sub-commands. fi + # Wait for last batch of jobs + kube::util::wait-for-jobs || { + echo -e "Failed to delete instance templates and/or replica pd." >&2 + } + # Check if this are any remaining master replicas. local REMAINING_MASTER_COUNT REMAINING_MASTER_COUNT=$(gcloud compute instances list \ @@ -3886,7 +3884,7 @@ function kube-down() { --project "${PROJECT}" \ --quiet \ --zone "${ZONE}" \ - "${INSTANCE_PREFIX}"-influxdb-pd + "${INSTANCE_PREFIX}"-influxdb-pd && echo "Deleted ${INSTANCE_PREFIX}-influxdb-pd." fi # Delete all remaining firewall rules and network. diff --git a/cluster/gce/windows/k8s-node-setup.psm1 b/cluster/gce/windows/k8s-node-setup.psm1 index af23449eaadf3..1948c933c8e01 100644 --- a/cluster/gce/windows/k8s-node-setup.psm1 +++ b/cluster/gce/windows/k8s-node-setup.psm1 @@ -57,8 +57,8 @@ $GCE_METADATA_SERVER = "169.254.169.254" # exist until an initial HNS network has been created on the Windows node - see # Add_InitialHnsNetwork(). $MGMT_ADAPTER_NAME = "vEthernet (Ethernet*" -$CRICTL_VERSION = 'v1.34.0' -$CRICTL_SHA512 = 'b062756922dc5c5f41c5b13922dfea379cb9f4ce2431d1676c7dbace4f45c28a99ee71fd6cdd6c6f3d7679d33ac0ff052208207fc3f341b4ad047512dad756cb' +$CRICTL_VERSION = 'v1.35.0' +$CRICTL_SHA512 = '73572c89c62d882c31f5f3d2efef0f20c849a80bb868cbf41679fedd5f8cafa9b9d8d4a50cf78cdb1c739a935fa87e8d6bbb00d5fed04907037ecaf29410f8c3' Import-Module -Force C:\common.psm1 @@ -2308,7 +2308,7 @@ function DownloadAndInstall-AuthProviderGcpBinary { Log-Output "Installing auth provider gcp binaries" $tmp_dir = 'C:\k8s_tmp' New-Item -Force -ItemType 'directory' $tmp_dir | Out-Null - $url = "${env:AUTH_PROVIDER_GCP_STORAGE_PATH}/${env:AUTH_PROVIDER_GCP_VERSION}/windows_amd64/$filename" + $url = "${env:AUTH_PROVIDER_GCP_STORAGE_PATH}/${env:AUTH_PROVIDER_GCP_VERSION}/auth-provider-gcp/windows/amd64/$filename" MustDownload-File -Hash $AUTH_PROVIDER_GCP_HASH_WINDOWS_AMD64 -Algorithm SHA512 -OutFile $tmp_dir\$filename -URLs $url Move-Item -Force $tmp_dir\$filename ${env:AUTH_PROVIDER_GCP_WINDOWS_BIN_DIR} Remove-Item -Force -Recurse $tmp_dir diff --git a/cluster/gce/windows/smoke-test.sh b/cluster/gce/windows/smoke-test.sh index cef074f74da4f..712e6bedf651b 100755 --- a/cluster/gce/windows/smoke-test.sh +++ b/cluster/gce/windows/smoke-test.sh @@ -341,7 +341,7 @@ spec: spec: containers: - name: pause-win - image: registry.k8s.io/pause:3.10 + image: registry.k8s.io/pause:3.10.2 nodeSelector: kubernetes.io/os: windows tolerations: diff --git a/cluster/images/etcd/Dockerfile b/cluster/images/etcd/Dockerfile deleted file mode 100644 index 36dc86256d455..0000000000000 --- a/cluster/images/etcd/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2016 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. - -ARG BASEIMAGE -ARG RUNNERIMAGE - -FROM ${BASEIMAGE} as builder - -# This image needs bash for running "migrate-if-needed.sh". Instead of a full debian image -# we use just the bash-static and we wrap bash-static into a distroless image instead of -# a full debian image -RUN apt-get update -y \ - && apt-get -yy -q install --no-install-recommends --no-install-suggests --fix-missing \ - bash-static - -RUN cp /bin/bash-static /sh - -FROM ${RUNNERIMAGE} -WORKDIR / - -COPY --from=builder /sh /bin/ - -EXPOSE 2379 2380 4001 7001 -# etcdctl is used by etcd.manifest for livenessProbe. -COPY etcd* etcdctl* /usr/local/bin/ -COPY migrate-if-needed.sh migrate /usr/local/bin/ diff --git a/cluster/images/etcd/Dockerfile.windows b/cluster/images/etcd/Dockerfile.windows deleted file mode 100644 index c88131f81303c..0000000000000 --- a/cluster/images/etcd/Dockerfile.windows +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2021 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. - -ARG RUNNERIMAGE -FROM ${RUNNERIMAGE} - -EXPOSE 2379 2380 4001 7001 - -WORKDIR C:/usr/local/bin -COPY etcd* etcdctl* /usr/local/bin/ -COPY migrate-if-needed.bat /usr/local/bin/ -COPY migrate /usr/local/bin/migrate.exe - -# NOTE(claudiub): docker buildx sets the PATH env variable to a Linux-like PATH, -# # which is not desirable. See: https://github.com/moby/buildkit/issues/1560 -# # TODO(claudiub): remove this once the issue has been resolved. -ENV PATH="C:\usr\local\bin;C:\Windows\system32;C:\Windows;" diff --git a/cluster/images/etcd/Makefile b/cluster/images/etcd/Makefile deleted file mode 100644 index 7178ef7333ef0..0000000000000 --- a/cluster/images/etcd/Makefile +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright 2016 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. - -# Build the etcd image -# -# Usage: -# [BUNDLED_ETCD_VERSIONS=3.4.18 3.5.21 3.6.4] [REGISTRY=registry.k8s.io] [ARCH=amd64] [BASEIMAGE=busybox] make (build|push) -# -# The image contains different etcd versions to simplify -# upgrades. Thus be careful when removing any versions from here. -# -# NOTE: The etcd upgrade rules are that you can upgrade only 1 minor -# version at a time, and patch release don't matter. -# -# Except from etcd-$(version) and etcdctl-$(version) binaries, we also -# need etcd and etcdctl binaries for backward compatibility reasons. -# That binary will be set to the last version from $(BUNDLED_ETCD_VERSIONS). -BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.21 3.6.4 - -# LATEST_ETCD_VERSION identifies the most recent etcd version available. -LATEST_ETCD_VERSION?=3.6.4 - -# REVISION provides a version number for this image and all it's bundled -# artifacts. It should start at zero for each LATEST_ETCD_VERSION and increment -# for each revision of this image at that etcd version. -REVISION?=0 - -# IMAGE_TAG Uniquely identifies registry.k8s.io/etcd docker image with a tag of the form "-". -IMAGE_TAG=$(LATEST_ETCD_VERSION)-$(REVISION) - -ARCH?=amd64 - -# Operating systems supported: linux, windows -OS ?= linux -# OS Version for the Windows images: 1809, ltsc2022 -OSVERSION ?= 1809 - -# The output type could either be docker (local), or registry. -# If it is registry, it will also allow us to push the Windows images. -OUTPUT_TYPE ?= docker - -ALL_OS = linux windows -ALL_ARCH.linux = amd64 arm arm64 ppc64le s390x -ALL_OS_ARCH.linux = $(foreach arch, ${ALL_ARCH.linux}, linux-$(arch)) -ALL_ARCH.windows = amd64 -ALL_OSVERSIONS.windows := 1809 ltsc2022 -ALL_OS_ARCH.windows = $(foreach arch, $(ALL_ARCH.windows), $(foreach osversion, ${ALL_OSVERSIONS.windows}, windows-$(arch)-${osversion})) -ALL_OS_ARCH = $(foreach os, $(ALL_OS), ${ALL_OS_ARCH.${os}}) - -IMAGE_SUFFIX.linux = $(OS)-$(ARCH) -IMAGE_SUFFIX.windows = $(OS)-$(ARCH)-$(OSVERSION) -IMAGE_SUFFIX := ${IMAGE_SUFFIX.${OS}} - -# Image should be pulled from registry.k8s.io, which will auto-detect -# region (us, eu, asia, ...) and pull from the closest. -REGISTRY?=registry.k8s.io -# Images should be pushed to staging-k8s.gcr.io. -PUSH_REGISTRY?=staging-k8s.gcr.io - -MANIFEST_IMAGE := $(PUSH_REGISTRY)/etcd - -# Install binaries matching base distro permissions -BIN_INSTALL := install -m 0555 - -# Hosts running SELinux need :z added to volume mounts -SELINUX_ENABLED := $(shell cat /sys/fs/selinux/enforce 2> /dev/null || echo 0) - -ifeq ($(SELINUX_ENABLED),1) - DOCKER_VOL_OPTS?=:z -endif - -# This option is for running docker manifest command -export DOCKER_CLI_EXPERIMENTAL := enabled -# golang version should match the golang version of the official build from https://github.com/etcd-io/etcd/releases. -GOLANG_VERSION := 1.23.11 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md -GOARM?=7 -TEMP_DIR:=$(shell mktemp -d) - -DOCKERFILE.linux = Dockerfile -DOCKERFILE.windows = Dockerfile.windows -DOCKERFILE := ${DOCKERFILE.${OS}} - -ifeq ($(ARCH),amd64) - BASEIMAGE?=registry.k8s.io/build-image/debian-base:bookworm-v1.0.6 -endif -ifeq ($(ARCH),arm) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-arm:bookworm-v1.0.6 -endif -ifeq ($(ARCH),arm64) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-arm64:bookworm-v1.0.6 -endif -ifeq ($(ARCH),ppc64le) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-ppc64le:bookworm-v1.0.6 -endif -ifeq ($(ARCH),s390x) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-s390x:bookworm-v1.0.6 -endif - -BASE.windows = mcr.microsoft.com/windows/nanoserver - -RUNNERIMAGE.windows?=$(BASE.windows):$(OSVERSION) -RUNNERIMAGE.linux?=gcr.io/distroless/static:latest -RUNNERIMAGE := ${RUNNERIMAGE.${OS}} - -QEMUVERSION?=5.2.0-2 - -build: - # Explicitly copy files to the temp directory - $(BIN_INSTALL) migrate-if-needed.sh $(TEMP_DIR) - $(BIN_INSTALL) migrate-if-needed.bat $(TEMP_DIR) - install $(DOCKERFILE) $(TEMP_DIR) - - # Compile migrate - migrate_tmp_dir=$(shell mktemp -d); \ - docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -v $${migrate_tmp_dir}:/build$(DOCKER_VOL_OPTS) -e GOOS=$(OS) -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \ - /bin/bash -c "CGO_ENABLED=0 GO111MODULE=off go build -o /build/migrate k8s.io/kubernetes/cluster/images/etcd/migrate"; \ - $(BIN_INSTALL) $${migrate_tmp_dir}/migrate $(TEMP_DIR); - -ifeq ($(ARCH),amd64) - - # Do not compile if we should make an image for amd64, use the official etcd binaries instead - # For each release create a tmp dir 'etcd_release_tmp_dir' and unpack the release tar there. -ifeq ($(OS),windows) - for version in $(BUNDLED_ETCD_VERSIONS); do \ - etcd_release_tmp_dir=$(shell mktemp -d); \ - curl -sSL --retry 5 https://github.com/etcd-io/etcd/releases/download/v$$version/etcd-v$$version-windows-amd64.zip -o etcd-v$$version-windows-amd64.zip; \ - unzip -q -d $$etcd_release_tmp_dir etcd-v$$version-windows-amd64.zip; \ - rm etcd-v$$version-windows-amd64.zip; \ - $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd-v$$version-windows-amd64/etcd.exe $$etcd_release_tmp_dir/etcd-v$$version-windows-amd64/etcdctl.exe $(TEMP_DIR)/; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcd.exe $(TEMP_DIR)/etcd-$$version.exe; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcdctl.exe $(TEMP_DIR)/etcdctl-$$version.exe; \ - done -else - for version in $(BUNDLED_ETCD_VERSIONS); do \ - etcd_release_tmp_dir=$(shell mktemp -d); \ - curl -sSL --retry 5 https://github.com/etcd-io/etcd/releases/download/v$$version/etcd-v$$version-linux-amd64.tar.gz | tar -xz -C $$etcd_release_tmp_dir --strip-components=1; \ - $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \ - done -endif - -else - - # Download etcd in a golang container and cross-compile it statically - # For each release create a tmp dir 'etcd_release_tmp_dir' and unpack the release tar there. - arch_prefix="" - ifeq ($(ARCH),arm) - arch_prefix="GOARM=$(GOARM)" - endif - - # use '/go/src/go.etcd.io/etcd' to build etcd 3.4 and later. - for version in $(BUNDLED_ETCD_VERSIONS); do \ - etcd_release_tmp_dir=$(shell mktemp -d); \ - etcd_build_dir="/go/src/github.com/coreos/etcd"; \ - etcd_build_script="./build.sh"; \ - if [ $$(echo $$version | cut -d. -f2) -gt 3 ]; then \ - etcd_build_dir="/go/src/go.etcd.io/etcd"; \ - fi; \ - if [ $$(echo $$version | cut -d. -f2) -gt 5 ]; then \ - etcd_build_script="./scripts/build.sh"; \ - fi; \ - docker run --rm --interactive -v $${etcd_release_tmp_dir}:/etcdbin golang:$(GOLANG_VERSION)$(DOCKER_VOL_OPTS) /bin/bash -c \ - "git clone https://github.com/etcd-io/etcd $$etcd_build_dir \ - && cd $$etcd_build_dir \ - && git checkout v$${version} \ - && $(arch_prefix) GOARCH=$(ARCH) $$etcd_build_script \ - && cp -f bin/$(ARCH)/etcd* bin/etcd* /etcdbin; echo 'done'"; \ - $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \ - done - - # Add this ENV variable in order to workaround an unsupported arch blocker - # On arm (which is 32-bit), it can't handle >1GB data in-memory - ifeq ($(ARCH),arm) - cd $(TEMP_DIR) && echo "ENV ETCD_UNSUPPORTED_ARCH=$(ARCH)" >> $(DOCKERFILE) - endif -endif - - docker run --rm --privileged multiarch/qemu-user-static:$(QEMUVERSION) --reset -p yes - docker buildx version - BUILDER=$(shell docker buildx create --use) - - # And build the image - docker buildx build \ - --pull \ - --provenance=false \ - --sbom=false \ - --output=type=$(OUTPUT_TYPE) \ - --platform "$(OS)/$(ARCH)" \ - -t $(REGISTRY)/etcd:$(IMAGE_TAG)-$(IMAGE_SUFFIX) \ - --build-arg BASEIMAGE=$(BASEIMAGE) \ - --build-arg RUNNERIMAGE=$(RUNNERIMAGE) \ - -f $(TEMP_DIR)/$(DOCKERFILE) \ - $(TEMP_DIR) - docker buildx rm $$BUILDER - -push: build - -# split words on hyphen, access by 1-index -word-hyphen = $(word $2,$(subst -, ,$1)) - -sub-build-%: - $(MAKE) OUTPUT_TYPE=docker OS=$(call word-hyphen,$*,1) ARCH=$(call word-hyphen,$*,2) build - -all-build: $(addprefix sub-build-,$(ALL_OS_ARCH)) - -sub-push-image-%: - $(MAKE) OUTPUT_TYPE=registry OS=$(call word-hyphen,$*,1) ARCH=$(call word-hyphen,$*,2) OSVERSION=$(call word-hyphen,$*,3) REGISTRY=$(PUSH_REGISTRY) push - -all-push-images: $(addprefix sub-push-image-,$(ALL_OS_ARCH)) - -# NOTE(claudiub): A non-default builder instance is needed in order to build Windows images. -all-push: all-push-images push-manifest - -push-manifest: - docker manifest create --amend $(MANIFEST_IMAGE):$(IMAGE_TAG) $(shell echo $(ALL_OS_ARCH) | sed -e "s~[^ ]*~$(MANIFEST_IMAGE):$(IMAGE_TAG)\-&~g") - set -x; for arch in $(ALL_ARCH.linux); do docker manifest annotate --os linux --arch $${arch} ${MANIFEST_IMAGE}:${IMAGE_TAG} ${MANIFEST_IMAGE}:${IMAGE_TAG}-linux-$${arch}; done - # For Windows images, we also need to include the "os.version" in the manifest list, so the Windows node can pull the proper image it needs. - # we use awk to also trim the quotes around the OS version string. - set -x; \ - for arch in $(ALL_ARCH.windows); do \ - for osversion in ${ALL_OSVERSIONS.windows}; do \ - full_version=`docker manifest inspect ${BASE.windows}:$${osversion} | grep "os.version" | head -n 1 | awk -F\" '{print $$4}'` || true; \ - docker manifest annotate --os windows --arch $${arch} --os-version $${full_version} ${MANIFEST_IMAGE}:${IMAGE_TAG} ${MANIFEST_IMAGE}:${IMAGE_TAG}-windows-$${arch}-$${osversion}; \ - done; \ - done - docker manifest push --purge ${MANIFEST_IMAGE}:${IMAGE_TAG} - -unit-test: - docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \ - /bin/bash -c "CGO_ENABLED=0 go test -v k8s.io/kubernetes/cluster/images/etcd/migrate" - -# Integration tests require both a golang build environment and all the etcd binaries from a `registry.k8s.io/etcd` image (`/usr/local/bin/etcd-`, ...). -# Since the `registry.k8s.io/etcd` image is for runtime only and does not have a build golang environment, we create a new docker image to run integration tests -# with. -build-integration-test-image: build - cp -r $(TEMP_DIR) $(TEMP_DIR)_integration_test - cp Dockerfile $(TEMP_DIR)_integration_test/Dockerfile - docker build \ - --pull \ - -t etcd-integration-test \ - --build-arg BASEIMAGE=golang:$(GOLANG_VERSION) \ - --build-arg RUNNERIMAGE=$(RUNNERIMAGE) \ - $(TEMP_DIR)_integration_test - -integration-test: - docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -e GOARCH=$(ARCH) etcd-integration-test \ - /bin/bash -c "CGO_ENABLED=0 go test -tags=integration k8s.io/kubernetes/cluster/images/etcd/migrate -args -v 10 -logtostderr true" - -integration-build-test: build-integration-test-image integration-test -test: unit-test integration-build-test -all: all-build test -.PHONY: build push push-manifest all-push all-push-images all-build unit-test build-integration-test-image integration-test integration-build-test test diff --git a/cluster/images/etcd/OWNERS b/cluster/images/etcd/OWNERS deleted file mode 100644 index 30affa99d3856..0000000000000 --- a/cluster/images/etcd/OWNERS +++ /dev/null @@ -1,10 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -reviewers: - - jpbetz - - serathius -approvers: - - jpbetz - - serathius -labels: - - sig/etcd diff --git a/cluster/images/etcd/README.md b/cluster/images/etcd/README.md deleted file mode 100644 index 4e26caa06a67f..0000000000000 --- a/cluster/images/etcd/README.md +++ /dev/null @@ -1,79 +0,0 @@ -### registry.k8s.io/etcd docker image - -Provides docker images containing etcd and etcdctl binaries for multiple etcd -version as well as a migration operator utility for upgrading and downgrading -etcd--it's data directory in particular--to a target version. - -#### Versioning - -Each `registry.k8s.io/etcd` docker image is tagged with an version string of the form -`-`, e.g. `3.0.17-0`. The etcd version is the -SemVer of latest etcd version available in the image. The image revision -distinguishes between docker images with the same lastest etcd version but -changes (bug fixes and backward compatible improvements) to the migration -utility bundled with the image. - -In addition to the latest etcd version, each `registry.k8s.io/etcd` image contains -etcd and etcdctl binaries for older versions of etcd. These are used by the -migration operator utility when performing downgrades and multi-step upgrades, -but can also be used as the etcd target version. - -#### Usage - -Always run `/usr/local/bin/migrate` (or the -`/usr/local/bin/migrate-if-needed.sh` wrapper script) before starting the etcd -server. On Windows, run `C:\bin\migrate.exe` (or the `C:\bin\migrate-if-needed.bat -wrapper script`). - -`migrate` writes a `version.txt` file to track the "current" version -of etcd that was used to persist data to disk. A "target" version may also be provided -by the `TARGET_STORAGE` (e.g. "etcd3") and `TARGET_VERSION` (e.g. "3.4.13" ) -environment variables. If the persisted version differs from the target version, -`migrate-if-needed.sh` will migrate the data from the current to the target -version. - -Upgrades to any target version are supported. The data will be automatically upgraded -in steps to each minor version until the target version is reached. - -Downgrades to the previous minor version of the 3.x series is supported. - -#### Permissions - -By default, `migrate` will write data directory files with default permissions -according to the umask it is run with. When run in the published -`registry.k8s.io/etcd` images the default umask is 0022 which will result in 0755 -directory permissions and 0644 file permissions. - -#### Cross building - -For `amd64`, official `etcd` and `etcdctl` binaries are downloaded from Github -to maintain official support. For other architectures, `etcd` is cross-compiled -from source. Arch-specific `debian` images serve as base images. - -Windows images can be built on Linux nodes due to `docker buildx`, but they will -only be created and pushed when using the `all-push` make target. - -#### How to release - -First, update `ETCD_VERSION` and `REVSION` in the `Makefile`. - -Next, build and test the image: - -```console -$ make build test -``` - -Last, build and push the docker images for all supported architectures. - -```console -# Build images for all the architecture and push the manifest image as well -$ make all-push - -# Build images for all the architecture -$ make all-build - -# Build image for target architecture(default=amd64) -$ make build ARCH=ppc64le -``` - -If you don't want to push the images, run `make` or `make build` instead diff --git a/cluster/images/etcd/cloudbuild.yaml b/cluster/images/etcd/cloudbuild.yaml deleted file mode 100644 index 520fb8e74df67..0000000000000 --- a/cluster/images/etcd/cloudbuild.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# See https://cloud.google.com/cloud-build/docs/build-config -timeout: 1200s -options: - substitution_option: ALLOW_LOOSE - machineType: 'N1_HIGHCPU_8' -steps: - - name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20240523-a15ad90fc9@sha256:bb04162508c2c61637eae700a0d8e8c8be8f2d4c831d2b75e59db2d4dd6cf75d' - entrypoint: 'bash' - dir: ./cluster/images/etcd - env: - - DOCKER_CLI_EXPERIMENTAL=enabled - - REGISTRY=gcr.io/$PROJECT_ID - - PUSH_REGISTRY=gcr.io/$PROJECT_ID - - IMAGE=gcr.io/$PROJECT_ID/etcd - - BUILD_IMAGE=debian-build - - TMPDIR=/workspace - - HOME=/root # for docker buildx - args: - - '-c' - - | - gcloud auth configure-docker \ - && docker buildx create --name img-builder --use \ - && docker buildx inspect --bootstrap \ - && docker run --rm --privileged linuxkit/binfmt:4ea3b9b0938cbd19834c096aa31ff475cc75d281 \ - && make all-push diff --git a/cluster/images/etcd/migrate-if-needed.bat b/cluster/images/etcd/migrate-if-needed.bat deleted file mode 100755 index a264f82a883d3..0000000000000 --- a/cluster/images/etcd/migrate-if-needed.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -REM Copyright 2021 The Kubernetes Authors. -REM -REM Licensed under the Apache License, Version 2.0 (the "License"); -REM you may not use this file except in compliance with the License. -REM You may obtain a copy of the License at -REM -REM http://www.apache.org/licenses/LICENSE-2.0 -REM -REM Unless required by applicable law or agreed to in writing, software -REM distributed under the License is distributed on an "AS IS" BASIS, -REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -REM See the License for the specific language governing permissions and -REM limitations under the License. - -REM DEPRECATED: -REM The functionality has been moved to migrate binary and this script -REM if left for backward compatibility with previous manifests. It will be -REM removed in the future. - -C:\bin\migrate.exe diff --git a/cluster/images/etcd/migrate-if-needed.sh b/cluster/images/etcd/migrate-if-needed.sh deleted file mode 100755 index 75002700c31aa..0000000000000 --- a/cluster/images/etcd/migrate-if-needed.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Copyright 2016 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. - - -# DEPRECATED: -# The functionality has been moved to migrate binary and this script -# if left for backward compatibility with previous manifests. It will be -# removed in the future. - -set -o errexit -set -o nounset - -/usr/local/bin/migrate diff --git a/cluster/images/etcd/migrate/copy_file.go b/cluster/images/etcd/migrate/copy_file.go deleted file mode 100644 index 7a8421237b437..0000000000000 --- a/cluster/images/etcd/migrate/copy_file.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2020 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 main - -import ( - "fmt" - "io" - "os" - "path/filepath" -) - -func copyFile(source, dest string) error { - sf, err := os.Open(source) - if err != nil { - return fmt.Errorf("unable to open source file [%s]: %q", source, err) - } - defer sf.Close() - fi, err := sf.Stat() - if err != nil { - return fmt.Errorf("unable to stat source file [%s]: %q", source, err) - } - - dir := filepath.Dir(dest) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("unable to create directory [%s]: %q", dir, err) - } - df, err := os.Create(dest) - if err != nil { - return fmt.Errorf("unable to create destination file [%s]: %q", dest, err) - } - defer df.Close() - - _, err = io.Copy(df, sf) - if err != nil { - return fmt.Errorf("unable to copy [%s] to [%s]: %q", source, dest, err) - } - - if err := os.Chmod(dest, fi.Mode()); err != nil { - return fmt.Errorf("unable to close destination file: %q", err) - } - return nil -} diff --git a/cluster/images/etcd/migrate/data_dir.go b/cluster/images/etcd/migrate/data_dir.go deleted file mode 100644 index f13c555ec6182..0000000000000 --- a/cluster/images/etcd/migrate/data_dir.go +++ /dev/null @@ -1,189 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "k8s.io/klog/v2" -) - -// DataDirectory provides utilities for initializing and backing up an -// etcd "data-dir" as well as managing a version.txt file to track the -// etcd server version and storage version of the etcd data in the -// directory. -type DataDirectory struct { - path string - versionFile *VersionFile -} - -// OpenOrCreateDataDirectory opens a data directory, creating the directory -// if it doesn't not already exist. -func OpenOrCreateDataDirectory(path string) (*DataDirectory, error) { - exists, err := exists(path) - if err != nil { - return nil, err - } - if !exists { - klog.Infof("data directory '%s' does not exist, creating it", path) - err := os.MkdirAll(path, 0777) - if err != nil { - return nil, fmt.Errorf("failed to create data directory %s: %v", path, err) - } - } - versionFile := &VersionFile{ - path: filepath.Join(path, versionFilename), - } - return &DataDirectory{path, versionFile}, nil -} - -// Initialize set the version.txt to the target version if the data -// directory is empty. If the data directory is non-empty, no -// version.txt file will be written since the actual version of etcd -// used to create the data is unknown. -func (d *DataDirectory) Initialize(target *EtcdVersionPair) error { - isEmpty, err := d.IsEmpty() - if err != nil { - return err - } - if isEmpty { - klog.Infof("data directory '%s' is empty, writing target version '%s' to version.txt", d.path, target) - err = d.versionFile.Write(target) - if err != nil { - return fmt.Errorf("failed to write version.txt to '%s': %v", d.path, err) - } - return nil - } - return nil -} - -// Backup creates a backup copy of data directory. -func (d *DataDirectory) Backup() error { - backupDir := fmt.Sprintf("%s.bak", d.path) - err := os.RemoveAll(backupDir) - if err != nil { - return err - } - err = os.MkdirAll(backupDir, 0777) - if err != nil { - return err - } - err = copyDirectory(d.path, backupDir) - if err != nil { - return err - } - - return nil -} - -// IsEmpty returns true if the data directory is entirely empty. -func (d *DataDirectory) IsEmpty() (bool, error) { - dir, err := os.Open(d.path) - if err != nil { - return false, fmt.Errorf("failed to open data directory %s: %v", d.path, err) - } - defer dir.Close() - _, err = dir.Readdirnames(1) - if err == io.EOF { - return true, nil - } - return false, err -} - -// String returns the data directory path. -func (d *DataDirectory) String() string { - return d.path -} - -// VersionFile provides utilities for reading and writing version.txt files -// to etcd "data-dir" for tracking the etcd server and storage versions -// of the data in the directory. -type VersionFile struct { - path string -} - -func (v *VersionFile) nextPath() string { - return fmt.Sprintf("%s-next", v.path) -} - -// Exists returns true if a version.txt file exists on the file system. -func (v *VersionFile) Exists() (bool, error) { - return exists(v.path) -} - -// Read parses the version.txt file and returns it's contents. -func (v *VersionFile) Read() (*EtcdVersionPair, error) { - data, err := os.ReadFile(v.path) - if err != nil { - return nil, fmt.Errorf("failed to read version file %s: %v", v.path, err) - } - txt := strings.TrimSpace(string(data)) - vp, err := ParseEtcdVersionPair(txt) - if err != nil { - return nil, fmt.Errorf("failed to parse etcd '/' string from version.txt file contents '%s': %v", txt, err) - } - return vp, nil -} - -// equals returns true iff VersionFile exists and contains given EtcdVersionPair. -func (v *VersionFile) equals(vp *EtcdVersionPair) (bool, error) { - exists, err := v.Exists() - if err != nil { - return false, err - } - if !exists { - return false, nil - } - cvp, err := v.Read() - if err != nil { - return false, err - } - return vp.Equals(cvp), nil -} - -// Write creates or overwrites the contents of the version.txt file with the given EtcdVersionPair. -func (v *VersionFile) Write(vp *EtcdVersionPair) error { - // We do write only if file content differs from given EtcdVersionPair. - isUpToDate, err := v.equals(vp) - if err != nil { - return fmt.Errorf("failed to to check if version file %s should be changed: %v", v.path, err) - } - if isUpToDate { - return nil - } - // We do write + rename instead of just write to protect from version.txt - // corruption under full disk condition. - // See https://github.com/kubernetes/kubernetes/issues/98989. - err = os.WriteFile(v.nextPath(), []byte(vp.String()), 0666) - if err != nil { - return fmt.Errorf("failed to write new version file %s: %v", v.nextPath(), err) - } - return os.Rename(v.nextPath(), v.path) -} - -func exists(path string) (bool, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false, nil - } else if err != nil { - return false, err - } - return true, nil -} diff --git a/cluster/images/etcd/migrate/data_dir_test.go b/cluster/images/etcd/migrate/data_dir_test.go deleted file mode 100644 index 41b7279cad67b..0000000000000 --- a/cluster/images/etcd/migrate/data_dir_test.go +++ /dev/null @@ -1,167 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "os" - "path/filepath" - "testing" - - "github.com/blang/semver/v4" -) - -var ( - latestVersion = semver.MustParse("3.1.12") -) - -func TestExistingDataDirWithVersionFile(t *testing.T) { - d, err := OpenOrCreateDataDirectory("testdata/datadir_with_version") - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - isEmpty, err := d.IsEmpty() - if err != nil { - t.Fatalf("Failed to check if data dir is empty: %v", err) - } - if isEmpty { - t.Errorf("Expected non-empty data directory to exist") - } - exists, err := d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if !exists { - t.Fatalf("Expected version file %s to exist", d.versionFile.path) - } - vp, err := d.versionFile.Read() - if err != nil { - t.Fatalf("Failed to read version file %s: %v", d.versionFile.path, err) - } - expectedVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3} - if !vp.Equals(expectedVersion) { - t.Errorf("Expected version file to contain %s, but got %s", expectedVersion, vp) - } -} - -func TestExistingDataDirWithoutVersionFile(t *testing.T) { - targetVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3} - - d, err := OpenOrCreateDataDirectory("testdata/datadir_without_version") - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - exists, err := d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if exists { - t.Errorf("Expected version file %s not to exist", d.versionFile.path) - } - err = d.Initialize(targetVersion) - if err != nil { - t.Fatalf("Failed initialize data directory %s: %v", d.path, err) - } - exists, err = d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if exists { - t.Fatalf("Expected version file %s not to exist after initializing non-empty data-dir", d.versionFile.path) - } -} - -func TestNonexistingDataDir(t *testing.T) { - targetVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3} - path := newTestPath(t) - defer os.RemoveAll(path) - d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir")) - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - isEmpty, err := d.IsEmpty() - if err != nil { - t.Fatalf("Failed to check if data dir is empty: %v", err) - } - if !isEmpty { - t.Errorf("Expected empty data directory to exist") - } - err = d.Initialize(targetVersion) - if err != nil { - t.Fatalf("Failed initialize data directory %s: %v", d.path, err) - } - exists, err := d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if !exists { - t.Fatalf("Expected version file %s to exist", d.versionFile.path) - } - isEmpty, err = d.IsEmpty() - if err != nil { - t.Fatalf("Failed to check if data dir is empty: %v", err) - } - if isEmpty { - t.Errorf("Expected non-empty data directory to exist after Initialize()") - } - vp, err := d.versionFile.Read() - if err != nil { - t.Fatalf("Failed to read version file %s: %v", d.versionFile.path, err) - } - if !vp.Equals(targetVersion) { - t.Errorf("Expected version file to contain %s, but got %s", targetVersion, vp) - } -} - -func TestBackup(t *testing.T) { - path := newTestPath(t) - defer os.RemoveAll(path) - d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir")) - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - _, err = os.Create(filepath.Join(path, "data-dir", "empty.txt")) - if err != nil { - t.Fatal(err) - } - err = d.Backup() - if err != nil { - t.Fatalf("Failed to backup data directory %s: %v", d.path, err) - } - bak, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir.bak")) - if err != nil { - t.Fatalf("Failed to open backup data dir: %v", err) - } - isEmpty, err := bak.IsEmpty() - if err != nil { - t.Fatal(err) - } - if isEmpty { - t.Errorf("Expected non-empty backup directory to exist after Backup()") - } -} - -func newTestPath(t *testing.T) string { - path, err := os.MkdirTemp("", "etcd-migrate-test-") - if err != nil { - t.Fatalf("Failed to create tmp dir for test: %v", err) - } - err = os.Chmod(path, 0777) - if err != nil { - t.Fatalf("Failed to granting permission to tmp dir for test: %v", err) - } - return path -} diff --git a/cluster/images/etcd/migrate/integration_test.go b/cluster/images/etcd/migrate/integration_test.go deleted file mode 100644 index 968feba37976b..0000000000000 --- a/cluster/images/etcd/migrate/integration_test.go +++ /dev/null @@ -1,385 +0,0 @@ -//go:build integration -// +build integration - -/* -Copyright 2018 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 main - -import ( - "bytes" - cryptorand "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "flag" - "fmt" - "math/big" - "net" - "os" - "path/filepath" - "strings" - "sync" - "testing" - "time" - - "github.com/blang/semver/v4" - "k8s.io/klog/v2" - netutils "k8s.io/utils/net" -) - -var ( - testSupportedVersions = mustParseSupportedVersions([]string{"3.0.17", "3.1.12"}) - testVersionPrevious = &EtcdVersion{semver.MustParse("3.0.17")} - testVersionLatest = &EtcdVersion{semver.MustParse("3.1.12")} -) - -func init() { - // Enable klog which is used in dependencies - klog.InitFlags(nil) - flag.Set("logtostderr", "true") - flag.Set("v", "9") -} - -func TestMigrate(t *testing.T) { - migrations := []struct { - title string - memberCount int - startVersion string - endVersion string - protocol string - clientListenUrls string - }{ - // upgrades - {"v3-v3-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""}, - {"oldest-newest-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""}, - {"v3-v3-up-with-additional-client-url", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", "http://127.0.0.1:2379,http://10.128.0.1:2379"}, - - // warning: v2->v3 ha upgrades not currently supported. - {"ha-v3-v3-up", 3, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""}, - - // downgrades - {"v3-v3-down", 1, "3.1.12/etcd3", "3.0.17/etcd3", "https", ""}, - - // warning: ha downgrades not yet supported. - } - - for _, m := range migrations { - t.Run(m.title, func(t *testing.T) { - start := mustParseEtcdVersionPair(m.startVersion) - end := mustParseEtcdVersionPair(m.endVersion) - - testCfgs := clusterConfig(t, m.title, m.memberCount, m.protocol, m.clientListenUrls) - - servers := []*EtcdMigrateServer{} - for _, cfg := range testCfgs { - client, err := NewEtcdMigrateClient(cfg) - if err != nil { - t.Fatalf("Failed to create client: %v", err) - } - server := NewEtcdMigrateServer(cfg, client) - servers = append(servers, server) - } - - // Start the servers. - parallel(servers, func(server *EtcdMigrateServer) { - dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory) - if err != nil { - t.Fatalf("Error opening or creating data directory %s: %v", server.cfg.dataDirectory, err) - } - migrator := &Migrator{server.cfg, dataDir, server.client} - err = migrator.MigrateIfNeeded(start) - if err != nil { - t.Fatalf("Migration failed: %v", err) - } - err = server.Start(start.version) - if err != nil { - t.Fatalf("Failed to start server: %v", err) - } - }) - - // Write a value to each server, read it back. - parallel(servers, func(server *EtcdMigrateServer) { - key := fmt.Sprintf("/registry/%s", server.cfg.name) - value := fmt.Sprintf("value-%s", server.cfg.name) - err := server.client.Put(start.version, key, value) - if err != nil { - t.Fatalf("failed to write text value: %v", err) - } - - checkVal, err := server.client.Get(start.version, key) - if err != nil { - t.Errorf("Error getting %s for validation: %v", key, err) - } - if checkVal != value { - t.Errorf("Expected %s from %s but got %s", value, key, checkVal) - } - }) - - // Migrate the servers in series. - serial(servers, func(server *EtcdMigrateServer) { - err := server.Stop() - if err != nil { - t.Fatalf("Stop server failed: %v", err) - } - dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory) - if err != nil { - t.Fatalf("Error opening or creating data directory %s: %v", server.cfg.dataDirectory, err) - } - migrator := &Migrator{server.cfg, dataDir, server.client} - err = migrator.MigrateIfNeeded(end) - if err != nil { - t.Fatalf("Migration failed: %v", err) - } - err = server.Start(end.version) - if err != nil { - t.Fatalf("Start server failed: %v", err) - } - }) - - // Check that all test values can be read back from all the servers. - parallel(servers, func(server *EtcdMigrateServer) { - for _, s := range servers { - key := fmt.Sprintf("/registry/%s", s.cfg.name) - value := fmt.Sprintf("value-%s", s.cfg.name) - checkVal, err := server.client.Get(end.version, key) - if err != nil { - t.Errorf("Error getting %s from etcd 2.x after rollback from 3.x: %v", key, err) - } - if checkVal != value { - t.Errorf("Expected %s from %s but got %s when reading after rollback from %s to %s", value, key, checkVal, start, end) - } - } - }) - - // Stop the servers. - parallel(servers, func(server *EtcdMigrateServer) { - err := server.Stop() - if err != nil { - t.Fatalf("Failed to stop server: %v", err) - } - }) - - // Check that version.txt contains the correct end version. - parallel(servers, func(server *EtcdMigrateServer) { - dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory) - v, err := dataDir.versionFile.Read() - if err != nil { - t.Fatalf("Failed to read version.txt file: %v", err) - } - if !v.Equals(end) { - t.Errorf("Expected version.txt to contain %s but got %s", end, v) - } - // Integration tests are run in a docker container with umask of 0022. - checkPermissions(t, server.cfg.dataDirectory, 0755|os.ModeDir) - checkPermissions(t, dataDir.versionFile.path, 0644) - }) - }) - } -} - -func parallel(servers []*EtcdMigrateServer, fn func(server *EtcdMigrateServer)) { - var wg sync.WaitGroup - wg.Add(len(servers)) - for _, server := range servers { - go func(s *EtcdMigrateServer) { - defer wg.Done() - fn(s) - }(server) - } - wg.Wait() -} - -func serial(servers []*EtcdMigrateServer, fn func(server *EtcdMigrateServer)) { - for _, server := range servers { - fn(server) - } -} - -func checkPermissions(t *testing.T, path string, expected os.FileMode) { - info, err := os.Stat(path) - if err != nil { - t.Fatalf("Failed to stat file %s: %v", path, err) - } - if info.Mode() != expected { - t.Errorf("Expected permissions for file %s of %s, but got %s", path, expected, info.Mode()) - } -} - -func clusterConfig(t *testing.T, name string, memberCount int, protocol string, clientListenUrls string) []*EtcdMigrateCfg { - peers := []string{} - for i := 0; i < memberCount; i++ { - memberName := fmt.Sprintf("%s-%d", name, i) - peerPort := uint64(2380 + i*10000) - peer := fmt.Sprintf("%s=%s://127.0.0.1:%d", memberName, protocol, peerPort) - peers = append(peers, peer) - } - initialCluster := strings.Join(peers, ",") - - extraArgs := "" - if protocol == "https" { - extraArgs = getOrCreateTLSPeerCertArgs(t) - } - - cfgs := []*EtcdMigrateCfg{} - for i := 0; i < memberCount; i++ { - memberName := fmt.Sprintf("%s-%d", name, i) - peerURL := fmt.Sprintf("%s://127.0.0.1:%d", protocol, uint64(2380+i*10000)) - cfg := &EtcdMigrateCfg{ - binPath: "/usr/local/bin", - name: memberName, - initialCluster: initialCluster, - port: uint64(2379 + i*10000), - peerListenUrls: peerURL, - peerAdvertiseUrls: peerURL, - clientListenUrls: clientListenUrls, - etcdDataPrefix: "/registry", - ttlKeysDirectory: "/registry/events", - supportedVersions: testSupportedVersions, - dataDirectory: fmt.Sprintf("/tmp/etcd-data-dir-%s", memberName), - etcdServerArgs: extraArgs, - } - cfgs = append(cfgs, cfg) - } - return cfgs -} - -func getOrCreateTLSPeerCertArgs(t *testing.T) string { - spec := TestCertSpec{ - host: "localhost", - ips: []string{"127.0.0.1"}, - } - certDir := "/tmp/certs" - certFile := filepath.Join(certDir, "test.crt") - keyFile := filepath.Join(certDir, "test.key") - err := getOrCreateTestCertFiles(certFile, keyFile, spec) - if err != nil { - t.Fatalf("failed to create server cert: %v", err) - } - return fmt.Sprintf("--peer-client-cert-auth --peer-trusted-ca-file=%s --peer-cert-file=%s --peer-key-file=%s", certFile, certFile, keyFile) -} - -type TestCertSpec struct { - host string - names, ips []string // in certificate -} - -func getOrCreateTestCertFiles(certFileName, keyFileName string, spec TestCertSpec) (err error) { - if _, err := os.Stat(certFileName); err == nil { - if _, err := os.Stat(keyFileName); err == nil { - return nil - } - } - - certPem, keyPem, err := generateSelfSignedCertKey(spec.host, parseIPList(spec.ips), spec.names) - if err != nil { - return err - } - - os.MkdirAll(filepath.Dir(certFileName), os.FileMode(0777)) - err = os.WriteFile(certFileName, certPem, os.FileMode(0777)) - if err != nil { - return err - } - - os.MkdirAll(filepath.Dir(keyFileName), os.FileMode(0777)) - err = os.WriteFile(keyFileName, keyPem, os.FileMode(0777)) - if err != nil { - return err - } - - return nil -} - -func parseIPList(ips []string) []net.IP { - var netIPs []net.IP - for _, ip := range ips { - netIPs = append(netIPs, netutils.ParseIPSloppy(ip)) - } - return netIPs -} - -// generateSelfSignedCertKey creates a self-signed certificate and key for the given host. -// Host may be an IP or a DNS name -// You may also specify additional subject alt names (either ip or dns names) for the certificate -func generateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) { - priv, err := rsa.GenerateKey(cryptorand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()), - }, - NotBefore: time.Unix(0, 0), - NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - IsCA: true, - } - - if ip := netutils.ParseIPSloppy(host); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, host) - } - - template.IPAddresses = append(template.IPAddresses, alternateIPs...) - template.DNSNames = append(template.DNSNames, alternateDNS...) - - derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return nil, nil, err - } - - // Generate cert - certBuffer := bytes.Buffer{} - if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return nil, nil, err - } - - // Generate key - keyBuffer := bytes.Buffer{} - if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { - return nil, nil, err - } - - return certBuffer.Bytes(), keyBuffer.Bytes(), nil -} - -// mustParseEtcdVersionPair parses a "/" string to an EtcdVersionPair -// or panics if the parse fails. -func mustParseEtcdVersionPair(s string) *EtcdVersionPair { - pair, err := ParseEtcdVersionPair(s) - if err != nil { - panic(err) - } - return pair -} - -// mustParseSupportedVersions parses a comma separated list of etcd versions or panics if the parse fails. -func mustParseSupportedVersions(list []string) SupportedVersions { - versions, err := ParseSupportedVersions(list) - if err != nil { - panic(err) - } - return versions -} diff --git a/cluster/images/etcd/migrate/migrate.go b/cluster/images/etcd/migrate/migrate.go deleted file mode 100644 index e089786faf584..0000000000000 --- a/cluster/images/etcd/migrate/migrate.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "fmt" - "path/filepath" - - "github.com/spf13/cobra" - "k8s.io/klog/v2" -) - -const ( - versionFilename = "version.txt" -) - -var ( - migrateCmd = &cobra.Command{ - Short: "Upgrade/downgrade etcd data across multiple versions", - Long: `Upgrade or downgrade etcd data across multiple versions to the target version - -Given a 'bin-dir' directory of etcd and etcdctl binaries, an etcd 'data-dir' with a 'version.txt' file and -a target etcd version, this tool will upgrade or downgrade the etcd data from the version specified in -'version.txt' to the target version. -`, - Run: func(cmd *cobra.Command, args []string) { - runMigrate() - }, - } - opts = migrateOpts{} -) - -func main() { - registerFlags(migrateCmd.Flags(), &opts) - err := migrateCmd.Execute() - if err != nil { - klog.Errorf("Failed to execute migratecmd: %s", err) - } -} - -// runMigrate starts the migration. -func runMigrate() { - if err := opts.validateAndDefault(); err != nil { - klog.Fatalf("%v", err) - } - copyBinaries() - - target := &EtcdVersionPair{ - version: MustParseEtcdVersion(opts.targetVersion), - storageVersion: MustParseEtcdStorageVersion(opts.targetStorage), - } - - migrate( - opts.name, opts.port, opts.peerListenUrls, opts.peerAdvertiseUrls, opts.clientListenUrls, - opts.binDir, opts.dataDir, opts.etcdDataPrefix, opts.ttlKeysDirectory, opts.initialCluster, - target, opts.supportedVersions, opts.etcdServerArgs) -} - -func copyBinaries() { - if val, err := lookupEnv("DO_NOT_MOVE_BINARIES"); err != nil || val != "true" { - etcdVersioned := fmt.Sprintf("etcd-%s", opts.targetVersion) - etcdctlVersioned := fmt.Sprintf("etcdctl-%s", opts.targetVersion) - if err := copyFile(filepath.Join(opts.binDir, etcdVersioned), filepath.Join(opts.binDir, "etcd")); err != nil { - klog.Fatalf("Failed to copy %s: %v", etcdVersioned, err) - } - if err := copyFile(filepath.Join(opts.binDir, etcdctlVersioned), filepath.Join(opts.binDir, "etcdctl")); err != nil { - klog.Fatalf("Failed to copy %s: %v", etcdctlVersioned, err) - } - } -} - -// migrate opens or initializes the etcd data directory, configures the migrator, and starts the migration. -func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls string, clientListenUrls string, - binPath string, dataDirPath string, etcdDataPrefix string, ttlKeysDirectory string, - initialCluster string, target *EtcdVersionPair, bundledVersions SupportedVersions, etcdServerArgs string) { - - dataDir, err := OpenOrCreateDataDirectory(dataDirPath) - if err != nil { - klog.Fatalf("Error opening or creating data directory %s: %v", dataDirPath, err) - } - - cfg := &EtcdMigrateCfg{ - binPath: binPath, - name: name, - port: port, - peerListenUrls: peerListenUrls, - peerAdvertiseUrls: peerAdvertiseUrls, - clientListenUrls: clientListenUrls, - etcdDataPrefix: etcdDataPrefix, - ttlKeysDirectory: ttlKeysDirectory, - initialCluster: initialCluster, - supportedVersions: bundledVersions, - dataDirectory: dataDirPath, - etcdServerArgs: etcdServerArgs, - } - client, err := NewEtcdMigrateClient(cfg) - if err != nil { - klog.Fatalf("Migration failed: %v", err) - } - defer client.Close() - - migrator := &Migrator{cfg, dataDir, client} - - err = migrator.MigrateIfNeeded(target) - if err != nil { - klog.Fatalf("Migration failed: %v", err) - } -} diff --git a/cluster/images/etcd/migrate/migrate_client.go b/cluster/images/etcd/migrate/migrate_client.go deleted file mode 100644 index 229356b2715da..0000000000000 --- a/cluster/images/etcd/migrate/migrate_client.go +++ /dev/null @@ -1,201 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "bytes" - "context" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - clientv3 "go.etcd.io/etcd/client/v3" - "google.golang.org/grpc" - "k8s.io/klog/v2" -) - -// CombinedEtcdClient provides an implementation of EtcdMigrateClient using a combination of the etcd v2 client, v3 client -// and etcdctl commands called via the shell. -type CombinedEtcdClient struct { - cfg *EtcdMigrateCfg -} - -// NewEtcdMigrateClient creates a new EtcdMigrateClient from the given EtcdMigrateCfg. -func NewEtcdMigrateClient(cfg *EtcdMigrateCfg) (EtcdMigrateClient, error) { - return &CombinedEtcdClient{cfg}, nil -} - -// Close closes the client and releases any resources it holds. -func (e *CombinedEtcdClient) Close() error { - return nil -} - -// SetEtcdVersionKeyValue writes the given version to the etcd 'etcd_version' key. -// If no error is returned, the write was successful, indicating the etcd server is available -// and able to perform consensus writes. -func (e *CombinedEtcdClient) SetEtcdVersionKeyValue(version *EtcdVersion) error { - return e.Put(version, "etcd_version", version.String()) -} - -// Put write a single key value pair to etcd. -func (e *CombinedEtcdClient) Put(version *EtcdVersion, key, value string) error { - v3client, err := e.clientV3() - if err != nil { - return err - } - defer v3client.Close() - _, err = v3client.KV.Put(context.Background(), key, value) - return err -} - -// Get reads a single value for a given key. -func (e *CombinedEtcdClient) Get(version *EtcdVersion, key string) (string, error) { - v3client, err := e.clientV3() - if err != nil { - return "", err - } - defer v3client.Close() - resp, err := v3client.KV.Get(context.Background(), key) - if err != nil { - return "", err - } - kvs := resp.Kvs - if len(kvs) != 1 { - return "", fmt.Errorf("expected exactly one value for key %s but got %d", key, len(kvs)) - } - - return string(kvs[0].Value), nil -} - -func (e *CombinedEtcdClient) clientV3() (*clientv3.Client, error) { - return clientv3.New(clientv3.Config{ - Endpoints: []string{e.endpoint()}, - DialTimeout: 20 * time.Second, - DialOptions: []grpc.DialOption{ - grpc.WithBlock(), // block until the underlying connection is up - }, - }) -} - -// Backup creates a backup of an etcd2 data directory at the given backupDir. -func (e *CombinedEtcdClient) Backup(version *EtcdVersion, backupDir string) error { - // We cannot use etcd/client (v2) to make this call. It is implemented in the etcdctl client code. - if version.Major != 2 { - return fmt.Errorf("etcd 2.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "--debug", - "backup", - "--data-dir", e.cfg.dataDirectory, - "--backup-dir", backupDir, - ) -} - -// Snapshot captures a snapshot from a running etcd3 server and saves it to the given snapshotFile. -// We cannot use etcd/clientv3 to make this call. It is implemented in the etcdctl client code. -func (e *CombinedEtcdClient) Snapshot(version *EtcdVersion, snapshotFile string) error { - if version.Major != 3 { - return fmt.Errorf("etcd 3.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "--endpoints", e.endpoint(), - "snapshot", "save", snapshotFile, - ) -} - -// Restore restores a given snapshotFile into the data directory specified this clients config. -func (e *CombinedEtcdClient) Restore(version *EtcdVersion, snapshotFile string) error { - // We cannot use etcd/clientv3 to make this call. It is implemented in the etcdctl client code. - if version.Major != 3 { - return fmt.Errorf("etcd 3.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "snapshot", "restore", snapshotFile, - "--data-dir", e.cfg.dataDirectory, - "--name", e.cfg.name, - "--initial-advertise-peer-urls", e.cfg.peerAdvertiseUrls, - "--initial-cluster", e.cfg.initialCluster, - ) -} - -// Migrate upgrades a 'etcd2' storage version data directory to a 'etcd3' storage version -// data directory. -func (e *CombinedEtcdClient) Migrate(version *EtcdVersion) error { - // We cannot use etcd/clientv3 to make this call as it is implemented in etcd/etcdctl. - if version.Major != 3 { - return fmt.Errorf("etcd 3.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "migrate", - "--data-dir", e.cfg.dataDirectory, - ) -} - -func (e *CombinedEtcdClient) runEtcdctlCommand(version *EtcdVersion, args ...string) error { - etcdctlCmd := exec.Command(filepath.Join(e.cfg.binPath, fmt.Sprintf("etcdctl-%s", version)), args...) - etcdctlCmd.Env = []string{fmt.Sprintf("ETCDCTL_API=%d", version.Major)} - etcdctlCmd.Stdout = os.Stdout - etcdctlCmd.Stderr = os.Stderr - return etcdctlCmd.Run() -} - -// AttachLease attaches leases of the given leaseDuration to all the etcd objects under -// ttlKeysDirectory specified in this client's config. -func (e *CombinedEtcdClient) AttachLease(leaseDuration time.Duration) error { - ttlKeysPrefix := e.cfg.ttlKeysDirectory - // Make sure that ttlKeysPrefix is ended with "/" so that we only get children "directories". - if !strings.HasSuffix(ttlKeysPrefix, "/") { - ttlKeysPrefix += "/" - } - ctx := context.Background() - - v3client, err := e.clientV3() - if err != nil { - return err - } - defer v3client.Close() - objectsResp, err := v3client.KV.Get(ctx, ttlKeysPrefix, clientv3.WithPrefix()) - if err != nil { - return fmt.Errorf("error while getting objects to attach to the lease") - } - - lease, err := v3client.Lease.Grant(ctx, int64(leaseDuration/time.Second)) - if err != nil { - return fmt.Errorf("error while creating lease: %v", err) - } - klog.Infof("Lease with TTL: %v created", lease.TTL) - - klog.Infof("Attaching lease to %d entries", len(objectsResp.Kvs)) - for _, kv := range objectsResp.Kvs { - putResp, err := v3client.KV.Put(ctx, string(kv.Key), string(kv.Value), clientv3.WithLease(lease.ID), clientv3.WithPrevKV()) - if err != nil { - klog.Errorf("Error while attaching lease to: %s", string(kv.Key)) - } - if !bytes.Equal(putResp.PrevKv.Value, kv.Value) { - return fmt.Errorf("concurrent access to key detected when setting lease on %s, expected previous value of %s but got %s", - kv.Key, kv.Value, putResp.PrevKv.Value) - } - } - return nil -} - -func (e *CombinedEtcdClient) endpoint() string { - return fmt.Sprintf("http://127.0.0.1:%d", e.cfg.port) -} diff --git a/cluster/images/etcd/migrate/migrate_server.go b/cluster/images/etcd/migrate/migrate_server.go deleted file mode 100644 index da9c659cec3a4..0000000000000 --- a/cluster/images/etcd/migrate/migrate_server.go +++ /dev/null @@ -1,133 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "fmt" - "os" - "os/exec" - "strings" - "time" - - "k8s.io/klog/v2" -) - -// EtcdMigrateServer manages starting and stopping a versioned etcd server binary. -type EtcdMigrateServer struct { - cfg *EtcdMigrateCfg - client EtcdMigrateClient - cmd *exec.Cmd -} - -// NewEtcdMigrateServer creates a EtcdMigrateServer for starting and stopping a etcd server at the given version. -func NewEtcdMigrateServer(cfg *EtcdMigrateCfg, client EtcdMigrateClient) *EtcdMigrateServer { - return &EtcdMigrateServer{cfg: cfg, client: client} -} - -// Start starts an etcd server as a separate process, waits until it has started, and returns a exec.Cmd. -// TODO: Add support for listening to client via TLS. -func (r *EtcdMigrateServer) Start(version *EtcdVersion) error { - etcdCmd := exec.Command( - fmt.Sprintf("%s/etcd-%s", r.cfg.binPath, version), - "--name", r.cfg.name, - "--initial-cluster", r.cfg.initialCluster, - "--debug", - "--data-dir", r.cfg.dataDirectory, - "--listen-client-urls", r.cfg.clientListenUrls, - "--advertise-client-urls", fmt.Sprintf("http://127.0.0.1:%d", r.cfg.port), - "--listen-peer-urls", r.cfg.peerListenUrls, - "--initial-advertise-peer-urls", r.cfg.peerAdvertiseUrls, - ) - if r.cfg.etcdServerArgs != "" { - extraArgs := strings.Fields(r.cfg.etcdServerArgs) - etcdCmd.Args = append(etcdCmd.Args, extraArgs...) - } - fmt.Printf("Starting server %s: %+v\n", r.cfg.name, etcdCmd.Args) - - etcdCmd.Stdout = os.Stdout - etcdCmd.Stderr = os.Stderr - err := etcdCmd.Start() - if err != nil { - return err - } - interval := time.NewTicker(time.Millisecond * 500) - defer interval.Stop() - done := make(chan bool) - go func() { - time.Sleep(time.Minute * 2) - close(done) - }() - for { - select { - case <-interval.C: - err := r.client.SetEtcdVersionKeyValue(version) - if err != nil { - klog.Infof("Still waiting for etcd to start, current error: %v", err) - // keep waiting - } else { - klog.Infof("Etcd on port %d is up.", r.cfg.port) - r.cmd = etcdCmd - return nil - } - case <-done: - err = etcdCmd.Process.Kill() - if err != nil { - return fmt.Errorf("error killing etcd: %v", err) - } - return fmt.Errorf("timed out waiting for etcd on port %d", r.cfg.port) - } - } -} - -// Stop terminates the etcd server process. If the etcd server process has not been started -// or is not still running, this returns an error. -func (r *EtcdMigrateServer) Stop() error { - if r.cmd == nil { - return fmt.Errorf("cannot stop EtcdMigrateServer that has not been started") - } - err := r.cmd.Process.Signal(os.Interrupt) - if err != nil { - return fmt.Errorf("error sending SIGINT to etcd for graceful shutdown: %v", err) - } - gracefulWait := time.Minute * 2 - stopped := make(chan bool) - timedout := make(chan bool) - go func() { - time.Sleep(gracefulWait) - close(timedout) - }() - go func() { - select { - case <-stopped: - return - case <-timedout: - klog.Infof("etcd server has not terminated gracefully after %s, killing it.", gracefulWait) - r.cmd.Process.Kill() - return - } - }() - err = r.cmd.Wait() - close(stopped) - if exiterr, ok := err.(*exec.ExitError); ok { - klog.Infof("etcd server stopped (signal: %s)", exiterr.Error()) - // stopped - } else if err != nil { - return fmt.Errorf("error waiting for etcd to stop: %v", err) - } - klog.Infof("Stopped etcd server %s", r.cfg.name) - return nil -} diff --git a/cluster/images/etcd/migrate/migrator.go b/cluster/images/etcd/migrate/migrator.go deleted file mode 100644 index 2ecc87c29fea2..0000000000000 --- a/cluster/images/etcd/migrate/migrator.go +++ /dev/null @@ -1,194 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "fmt" - "os" - "time" - - "github.com/blang/semver/v4" - "k8s.io/klog/v2" -) - -// EtcdMigrateCfg provides all configuration required to perform etcd data upgrade/downgrade migrations. -type EtcdMigrateCfg struct { - binPath string - name string - initialCluster string - port uint64 - peerListenUrls string - peerAdvertiseUrls string - clientListenUrls string - etcdDataPrefix string - ttlKeysDirectory string - supportedVersions SupportedVersions - dataDirectory string - etcdServerArgs string -} - -// EtcdMigrateClient defines the etcd client operations required to perform migrations. -type EtcdMigrateClient interface { - SetEtcdVersionKeyValue(version *EtcdVersion) error - Get(version *EtcdVersion, key string) (string, error) - Put(version *EtcdVersion, key, value string) error - Backup(version *EtcdVersion, backupDir string) error - Snapshot(version *EtcdVersion, snapshotFile string) error - Restore(version *EtcdVersion, snapshotFile string) error - Migrate(version *EtcdVersion) error - AttachLease(leaseDuration time.Duration) error - Close() error -} - -// Migrator manages etcd data migrations. -type Migrator struct { - cfg *EtcdMigrateCfg // TODO: don't wire this directly in - dataDirectory *DataDirectory - client EtcdMigrateClient -} - -// MigrateIfNeeded upgrades or downgrades the etcd data directory to the given target version. -func (m *Migrator) MigrateIfNeeded(target *EtcdVersionPair) error { - klog.Infof("Starting migration to %s", target) - err := m.dataDirectory.Initialize(target) - if err != nil { - return fmt.Errorf("failed to initialize data directory %s: %v", m.dataDirectory.path, err) - } - - var current *EtcdVersionPair - vfExists, err := m.dataDirectory.versionFile.Exists() - if err != nil { - return err - } - if vfExists { - current, err = m.dataDirectory.versionFile.Read() - if err != nil { - return err - } - } else { - return fmt.Errorf("existing data directory '%s' is missing version.txt file, unable to migrate", m.dataDirectory.path) - } - - for { - klog.Infof("Converging current version '%s' to target version '%s'", current, target) - currentNextMinorVersion := &EtcdVersion{Version: semver.Version{Major: current.version.Major, Minor: current.version.Minor + 1}} - switch { - case current.version.MajorMinorEquals(target.version) || currentNextMinorVersion.MajorMinorEquals(target.version): - klog.Infof("current version '%s' equals or is one minor version previous of target version '%s' - migration complete", current, target) - err = m.dataDirectory.versionFile.Write(target) - if err != nil { - return fmt.Errorf("failed to write version.txt to '%s': %v", m.dataDirectory.path, err) - } - return nil - case current.storageVersion == storageEtcd2 && target.storageVersion == storageEtcd3: - return fmt.Errorf("upgrading from etcd2 storage to etcd3 storage is not supported") - case current.version.Major == 3 && target.version.Major == 2: - return fmt.Errorf("downgrading from etcd 3.x to 2.x is not supported") - case current.version.Major == target.version.Major && current.version.Minor < target.version.Minor: - stepVersion := m.cfg.supportedVersions.NextVersionPair(current) - klog.Infof("upgrading etcd from %s to %s", current, stepVersion) - current, err = m.minorVersionUpgrade(current, stepVersion) - case current.version.Major == 3 && target.version.Major == 3 && current.version.Minor > target.version.Minor: - klog.Infof("rolling etcd back from %s to %s", current, target) - current, err = m.rollbackEtcd3MinorVersion(current, target) - } - if err != nil { - return err - } - } -} - -func (m *Migrator) rollbackEtcd3MinorVersion(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) { - if target.version.Minor != current.version.Minor-1 { - return nil, fmt.Errorf("rollback from %s to %s not supported, only rollbacks to the previous minor version are supported", current.version, target.version) - } - - klog.Infof("Performing etcd %s -> %s rollback", current.version, target.version) - err := m.dataDirectory.Backup() - if err != nil { - return nil, err - } - - snapshotFilename := fmt.Sprintf("%s.snapshot.db", m.dataDirectory.path) - err = os.Remove(snapshotFilename) - if err != nil && !os.IsNotExist(err) { - return nil, fmt.Errorf("failed to clean snapshot file before rollback: %v", err) - } - - // Start current version of etcd. - runner := m.newServer() - klog.Infof("Starting etcd version %s to capture rollback snapshot.", current.version) - err = runner.Start(current.version) - if err != nil { - klog.Fatalf("Unable to automatically downgrade etcd: starting etcd version %s to capture rollback snapshot failed: %v", current.version, err) - return nil, err - } - - klog.Infof("Snapshotting etcd %s to %s", current.version, snapshotFilename) - err = m.client.Snapshot(current.version, snapshotFilename) - if err != nil { - return nil, err - } - - err = runner.Stop() - if err != nil { - return nil, err - } - - klog.Info("Backing up data before rolling back") - backupDir := fmt.Sprintf("%s.bak", m.dataDirectory) - err = os.RemoveAll(backupDir) - if err != nil { - return nil, err - } - origInfo, err := os.Stat(m.dataDirectory.path) - if err != nil { - return nil, err - } - err = os.Rename(m.dataDirectory.path, backupDir) - if err != nil { - return nil, err - } - - klog.Infof("Restoring etcd %s from %s", target.version, snapshotFilename) - err = m.client.Restore(target.version, snapshotFilename) - if err != nil { - return nil, err - } - err = os.Chmod(m.dataDirectory.path, origInfo.Mode()) - if err != nil { - return nil, err - } - - return target, nil -} - -func (m *Migrator) minorVersionUpgrade(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) { - runner := m.newServer() - - // Do the migration step, by just starting etcd in the target version. - err := runner.Start(target.version) - if err != nil { - return nil, err - } - err = runner.Stop() - return target, err -} - -func (m *Migrator) newServer() *EtcdMigrateServer { - return NewEtcdMigrateServer(m.cfg, m.client) -} diff --git a/cluster/images/etcd/migrate/options.go b/cluster/images/etcd/migrate/options.go deleted file mode 100644 index 42fbb3758981c..0000000000000 --- a/cluster/images/etcd/migrate/options.go +++ /dev/null @@ -1,249 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - flag "github.com/spf13/pflag" - "k8s.io/klog/v2" -) - -var ( - supportedEtcdVersions = []string{"3.4.18", "3.5.21", "3.6.4"} -) - -const ( - etcdNameEnv = "ETCD_NAME" - etcdHostnameEnv = "ETCD_HOSTNAME" - hostnameEnv = "HOSTNAME" - dataDirEnv = "DATA_DIRECTORY" - initialClusterEnv = "INITIAL_CLUSTER" - initialClusterFmt = "%s=http://localhost:%d" - peerListenUrlsEnv = "LISTEN_PEER_URLS" - peerListenUrlsFmt = "http://localhost:%d" - peerAdvertiseUrlsEnv = "INITIAL_ADVERTISE_PEER_URLS" - peerAdvertiseUrlsFmt = "http://localhost:%d" - clientListenURLsEnv = "LISTEN_CLIENT_URLS" - clientListenURLFmt = "http://127.0.0.1:%d" - targetVersionEnv = "TARGET_VERSION" - targetStorageEnv = "TARGET_STORAGE" - etcdDataPrefixEnv = "ETCD_DATA_PREFIX" - etcdDataPrefixDefault = "/registry" - ttlKeysDirectoryFmt = "%s/events" - etcdServerArgsEnv = "ETCD_CREDS" -) - -type migrateOpts struct { - name string - port uint64 - peerPort uint64 - peerListenUrls string - peerAdvertiseUrls string - binDir string - dataDir string - bundledVersions []string - supportedVersions SupportedVersions - etcdDataPrefix string - ttlKeysDirectory string - initialCluster string - targetVersion string - targetStorage string - etcdServerArgs string - clientListenUrls string -} - -func registerFlags(flags *flag.FlagSet, opt *migrateOpts) { - flags.StringVar(&opts.name, "name", "", - "etcd cluster member name. If unset fallbacks to defaults to ETCD_NAME env, if unset defaults to etcd- env, if unset defaults to etcd- env.") - flags.Uint64Var(&opts.port, "port", 0, - "etcd client port to use during migration operations. "+ - "This should be a different port than typically used by etcd to avoid clients accidentally connecting during upgrade/downgrade operations. "+ - "If unset default to 18629 or 18631 depending on .") - flags.Uint64Var(&opts.peerPort, "peer-port", 0, - "etcd peer port to use during migration operations. If unset defaults to 2380 or 2381 depending on .") - flags.StringVar(&opts.peerListenUrls, "listen-peer-urls", "", - "etcd --listen-peer-urls flag. If unset, fallbacks to LISTEN_PEER_URLS env and if unset defaults to http://localhost:.") - flags.StringVar(&opts.peerAdvertiseUrls, "initial-advertise-peer-urls", "", - "etcd --initial-advertise-peer-urls flag. If unset fallbacks to INITIAL_ADVERTISE_PEER_URLS env and if unset defaults to http://localhost:.") - flags.StringVar(&opts.clientListenUrls, "listen-client-urls", "", - "etcd --listen-client-urls flag. If unset, fallbacks to LISTEN_CLIENT_URLS env, and if unset defaults to http://127.0.0.1:.") - flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin", - "directory of etcd and etcdctl binaries, must contain etcd- and etcdctl- for each version listed in .") - flags.StringVar(&opts.dataDir, "data-dir", "", - "etcd data directory of etcd server to migrate. If unset fallbacks to DATA_DIRECTORY env.") - flags.StringSliceVar(&opts.bundledVersions, "bundled-versions", supportedEtcdVersions, - "comma separated list of etcd binary versions present under the bin-dir.") - flags.StringVar(&opts.etcdDataPrefix, "etcd-data-prefix", "", - "etcd key prefix under which all objects are kept. If unset fallbacks to ETCD_DATA_PREFIX env and if unset defaults to /registry.") - flags.StringVar(&opts.ttlKeysDirectory, "ttl-keys-directory", "", - "etcd key prefix under which all keys with TTLs are kept. Defaults to /events") - flags.StringVar(&opts.initialCluster, "initial-cluster", "", - "comma separated list of name=endpoint pairs. If unset fallbacks to INITIAL_CLUSTER and if unset defaults to =https://localhost:.") - flags.StringVar(&opts.targetVersion, "target-version", "", - "version of etcd to migrate to. Format must be ... If unset fallbacks to TARGET_VERSION env.") - flags.StringVar(&opts.targetStorage, "target-storage", "", - "storage version of etcd to migrate to, one of: etcd2, etcd3. If unset fallbacks to TARGET_STORAGE env.") - flags.StringVar(&opts.etcdServerArgs, "etcd-server-extra-args", "", - "additional etcd server args for starting etcd servers during migration steps, need to set TLS certs flags for multi-member clusters using mTLS for communication. "+ - "If unset fallbacks to ETCD_CREDS env.") -} - -func lookupEnv(env string) (string, error) { - result, ok := os.LookupEnv(env) - if !ok || len(result) == 0 { - return result, fmt.Errorf("%s variable unset - expected failure", env) - } - return result, nil -} - -func fallbackToEnv(flag, env string) (string, error) { - klog.Infof("--%s unset - falling back to %s variable", flag, env) - return lookupEnv(env) -} - -func fallbackToEnvWithDefault(flag, env, def string) string { - if value, err := lookupEnv(env); err == nil { - return value - } - klog.Warningf("%s variable for %s flag unset - defaulting to %s", env, flag, def) - return def -} - -func defaultName() (string, error) { - if etcdName, err := lookupEnv(etcdNameEnv); err == nil { - return etcdName, nil - } - klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdNameEnv, etcdHostnameEnv) - if etcdHostname, err := lookupEnv(etcdHostnameEnv); err == nil { - return fmt.Sprintf("etcd-%s", etcdHostname), nil - } - klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdHostnameEnv, hostnameEnv) - if hostname, err := lookupEnv(hostnameEnv); err == nil { - return fmt.Sprintf("etcd-%s", hostname), nil - } - return "", fmt.Errorf("defaulting --name failed due to all ETCD_NAME, ETCD_HOSTNAME and HOSTNAME unset") -} - -func (opts *migrateOpts) validateAndDefault() error { - var err error - - if opts.name == "" { - klog.Infof("--name unset - falling back to %s variable", etcdNameEnv) - if opts.name, err = defaultName(); err != nil { - return err - } - } - - if opts.dataDir == "" { - if opts.dataDir, err = fallbackToEnv("data-dir", dataDirEnv); err != nil { - return err - } - } - - etcdEventsRE := regexp.MustCompile("event") - if opts.port == 0 { - if etcdEventsRE.MatchString(opts.dataDir) { - opts.port = 18631 - } else { - opts.port = 18629 - } - klog.Infof("--port unset - defaulting to %d", opts.port) - } - if opts.peerPort == 0 { - if etcdEventsRE.MatchString(opts.dataDir) { - opts.peerPort = 2381 - } else { - opts.peerPort = 2380 - } - klog.Infof("--peer-port unset - defaulting to %d", opts.peerPort) - } - - if opts.initialCluster == "" { - def := fmt.Sprintf(initialClusterFmt, opts.name, opts.peerPort) - opts.initialCluster = fallbackToEnvWithDefault("initial-cluster", initialClusterEnv, def) - } - - if opts.peerListenUrls == "" { - def := fmt.Sprintf(peerListenUrlsFmt, opts.peerPort) - opts.peerListenUrls = fallbackToEnvWithDefault("listen-peer-urls", peerListenUrlsEnv, def) - } - - if opts.peerAdvertiseUrls == "" { - def := fmt.Sprintf(peerAdvertiseUrlsFmt, opts.peerPort) - opts.peerAdvertiseUrls = fallbackToEnvWithDefault("initial-advertise-peer-urls", peerAdvertiseUrlsEnv, def) - } - - if opts.clientListenUrls == "" { - def := fmt.Sprintf(clientListenURLFmt, opts.port) - opts.clientListenUrls = fallbackToEnvWithDefault("listen-client-urls", clientListenURLsEnv, def) - } - - if opts.targetVersion == "" { - if opts.targetVersion, err = fallbackToEnv("target-version", targetVersionEnv); err != nil { - return err - } - } - - if opts.targetStorage == "" { - if opts.targetStorage, err = fallbackToEnv("target-storage", targetStorageEnv); err != nil { - return err - } - } - - if opts.etcdDataPrefix == "" { - opts.etcdDataPrefix = fallbackToEnvWithDefault("etcd-data-prefix", etcdDataPrefixEnv, etcdDataPrefixDefault) - } - - if opts.ttlKeysDirectory == "" { - opts.ttlKeysDirectory = fmt.Sprintf(ttlKeysDirectoryFmt, opts.etcdDataPrefix) - klog.Infof("--ttl-keys-directory unset - defaulting to %s", opts.ttlKeysDirectory) - } - - if opts.etcdServerArgs == "" { - opts.etcdServerArgs = fallbackToEnvWithDefault("etcd-server-extra-args", etcdServerArgsEnv, "") - } - - if opts.supportedVersions, err = ParseSupportedVersions(opts.bundledVersions); err != nil { - return fmt.Errorf("failed to parse --bundled-versions: %v", err) - } - - if err := validateBundledVersions(opts.supportedVersions, opts.binDir); err != nil { - return fmt.Errorf("failed to validate that 'etcd-' and 'etcdctl-' binaries exist in --bin-dir '%s' for all --bundled-versions '%s': %v", - opts.binDir, strings.Join(opts.bundledVersions, ","), err) - } - return nil -} - -// validateBundledVersions checks that 'etcd-' and 'etcdctl-' binaries exist in the binDir -// for each version in the bundledVersions list. -func validateBundledVersions(bundledVersions SupportedVersions, binDir string) error { - for _, v := range bundledVersions { - for _, binaryName := range []string{"etcd", "etcdctl"} { - fn := filepath.Join(binDir, fmt.Sprintf("%s-%s", binaryName, v)) - if _, err := os.Stat(fn); err != nil { - return fmt.Errorf("failed to validate '%s' binary exists for bundled-version '%s': %v", fn, v, err) - } - - } - } - return nil -} diff --git a/cluster/images/etcd/migrate/options_test.go b/cluster/images/etcd/migrate/options_test.go deleted file mode 100644 index 6b1f3e95245d1..0000000000000 --- a/cluster/images/etcd/migrate/options_test.go +++ /dev/null @@ -1,134 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "os" - "testing" -) - -func setEnvVar(t *testing.T, env, val string, exists bool) { - if exists { - t.Setenv(env, val) - } else if prev, ok := os.LookupEnv(env); ok { - t.Cleanup(func() { os.Setenv(env, prev) }) - - if err := os.Unsetenv(env); err != nil { - t.Errorf("couldn't unset env %s: %v", env, err) - } - } -} - -func TestFallbackToEnv(t *testing.T) { - testCases := []struct { - desc string - env string - value string - valueSet bool - expectedValue string - expectedError bool - }{ - { - desc: "value unset", - env: "FOO", - valueSet: false, - expectedValue: "", - expectedError: true, - }, - { - desc: "value set empty", - env: "FOO", - value: "", - valueSet: true, - expectedValue: "", - expectedError: true, - }, - { - desc: "value set", - env: "FOO", - value: "foo", - valueSet: true, - expectedValue: "foo", - expectedError: false, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - setEnvVar(t, test.env, test.value, test.valueSet) - value, err := fallbackToEnv("some-flag", test.env) - if test.expectedError { - if err == nil { - t.Errorf("expected error, got: %v", err) - } - } else { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if value != test.expectedValue { - t.Errorf("unexpected result: %s, expected: %s", value, test.expectedValue) - } - } - }) - } -} - -func TestFallbackToEnvWithDefault(t *testing.T) { - testCases := []struct { - desc string - env string - value string - valueSet bool - defaultValue string - expectedValue string - expectedError bool - }{ - { - desc: "value unset", - env: "FOO", - valueSet: false, - defaultValue: "default", - expectedValue: "default", - }, - { - desc: "value set empty", - env: "FOO", - value: "", - valueSet: true, - defaultValue: "default", - expectedValue: "default", - }, - { - desc: "value set", - env: "FOO", - value: "foo", - valueSet: true, - defaultValue: "default", - expectedValue: "foo", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - setEnvVar(t, test.env, test.value, test.valueSet) - value := fallbackToEnvWithDefault("some-flag", test.env, test.defaultValue) - if value != test.expectedValue { - t.Errorf("unexpected result: %s, expected: %s", value, test.expectedValue) - } - }) - } -} diff --git a/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt b/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt deleted file mode 100644 index d557c0269dc57..0000000000000 --- a/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt +++ /dev/null @@ -1 +0,0 @@ -3.1.12/etcd3 diff --git a/cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder b/cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/cluster/images/etcd/migrate/util_others.go b/cluster/images/etcd/migrate/util_others.go deleted file mode 100644 index 79c826712a27e..0000000000000 --- a/cluster/images/etcd/migrate/util_others.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build !windows -// +build !windows - -/* -Copyright 2020 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 main - -import ( - "github.com/mrunalp/fileutils" -) - -func copyDirectory(source string, dest string) error { - return fileutils.CopyDirectory(source, dest) -} diff --git a/cluster/images/etcd/migrate/utils_windows.go b/cluster/images/etcd/migrate/utils_windows.go deleted file mode 100644 index be7cce349b826..0000000000000 --- a/cluster/images/etcd/migrate/utils_windows.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build windows -// +build windows - -/* -Copyright 2020 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 main - -import "fmt" - -func copyDirectory(source string, dest string) error { - return fmt.Errorf("no support for windows") -} diff --git a/cluster/images/etcd/migrate/versions.go b/cluster/images/etcd/migrate/versions.go deleted file mode 100644 index 4982ea88f5321..0000000000000 --- a/cluster/images/etcd/migrate/versions.go +++ /dev/null @@ -1,178 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "fmt" - "strings" - - "github.com/blang/semver/v4" -) - -// EtcdVersion specifies an etcd server binaries SemVer. -type EtcdVersion struct { - semver.Version -} - -// ParseEtcdVersion parses a SemVer string to an EtcdVersion. -func ParseEtcdVersion(s string) (*EtcdVersion, error) { - v, err := semver.Make(s) - if err != nil { - return nil, err - } - return &EtcdVersion{v}, nil -} - -// MustParseEtcdVersion parses a SemVer string to an EtcdVersion and panics if the parse fails. -func MustParseEtcdVersion(s string) *EtcdVersion { - return &EtcdVersion{semver.MustParse(s)} -} - -// String returns the version in SemVer string format. -func (v *EtcdVersion) String() string { - return v.Version.String() -} - -// Equals returns true if the versions are exactly equal. -func (v *EtcdVersion) Equals(o *EtcdVersion) bool { - return v.Version.Equals(o.Version) -} - -// MajorMinorEquals returns true if the major and minor parts of the versions are equal; -// if only patch versions differ, this returns true. -func (v *EtcdVersion) MajorMinorEquals(o *EtcdVersion) bool { - return v.Major == o.Major && v.Minor == o.Minor -} - -// EtcdStorageVersion identifies the storage version of an etcd data directory. -type EtcdStorageVersion int - -const ( - storageUnknown EtcdStorageVersion = iota - storageEtcd2 - storageEtcd3 -) - -// ParseEtcdStorageVersion parses an etcd storage version string to an EtcdStorageVersion. -func ParseEtcdStorageVersion(s string) (EtcdStorageVersion, error) { - switch s { - case "etcd2": - return storageEtcd2, nil - case "etcd3": - return storageEtcd3, nil - default: - return storageUnknown, fmt.Errorf("unrecognized storage version: %s", s) - } -} - -// MustParseEtcdStorageVersion parses an etcd storage version string to an EtcdStorageVersion and -// panics if the parse fails. -func MustParseEtcdStorageVersion(s string) EtcdStorageVersion { - version, err := ParseEtcdStorageVersion(s) - if err != nil { - panic(err) - } - return version -} - -// String returns the text representation of the EtcdStorageVersion, 'etcd2' or 'etcd3'. -func (v EtcdStorageVersion) String() string { - switch v { - case storageEtcd2: - return "etcd2" - case storageEtcd3: - return "etcd3" - default: - panic(fmt.Sprintf("enum value %d missing from EtcdStorageVersion String() function", v)) - } -} - -// EtcdVersionPair is composed of an etcd version and storage version. -type EtcdVersionPair struct { - version *EtcdVersion - storageVersion EtcdStorageVersion -} - -// ParseEtcdVersionPair parses a "/" string to an EtcdVersionPair. -func ParseEtcdVersionPair(s string) (*EtcdVersionPair, error) { - parts := strings.Split(s, "/") - if len(parts) != 2 { - return nil, fmt.Errorf("malformed version file, expected ../ but got %s", s) - } - version, err := ParseEtcdVersion(parts[0]) - if err != nil { - return nil, err - } - storageVersion, err := ParseEtcdStorageVersion(parts[1]) - if err != nil { - return nil, err - } - return &EtcdVersionPair{version, storageVersion}, nil -} - -// String returns "/" string of the EtcdVersionPair. -func (vp *EtcdVersionPair) String() string { - return fmt.Sprintf("%s/%s", vp.version, vp.storageVersion) -} - -// Equals returns true if both the versions and storage versions are exactly equal. -func (vp *EtcdVersionPair) Equals(o *EtcdVersionPair) bool { - return vp.version.Equals(o.version) && vp.storageVersion == o.storageVersion -} - -// SupportedVersions provides a list of etcd versions that are "supported" for some purpose. -// The list must be sorted from lowest semantic version to high. -type SupportedVersions []*EtcdVersion - -// NextVersion returns the next supported version after the given current version, or nil if no -// next version exists. -func (sv SupportedVersions) NextVersion(current *EtcdVersion) *EtcdVersion { - var nextVersion *EtcdVersion - for i, supportedVersion := range sv { - if current.MajorMinorEquals(supportedVersion) && len(sv) > i+1 { - nextVersion = sv[i+1] - } - } - return nextVersion -} - -// NextVersionPair returns the next supported version after the given current version and infers -// the storage version from the major version part of the next version. -func (sv SupportedVersions) NextVersionPair(current *EtcdVersionPair) *EtcdVersionPair { - nextVersion := sv.NextVersion(current.version) - if nextVersion == nil { - return nil - } - storageVersion := storageEtcd3 - if nextVersion.Major == 2 { - storageVersion = storageEtcd2 - } - return &EtcdVersionPair{version: nextVersion, storageVersion: storageVersion} -} - -// ParseSupportedVersions parses a list of etcd versions. -func ParseSupportedVersions(list []string) (SupportedVersions, error) { - var err error - versions := make(SupportedVersions, len(list)) - for i, v := range list { - versions[i], err = ParseEtcdVersion(strings.TrimSpace(v)) - if err != nil { - return nil, err - } - } - return versions, nil -} diff --git a/cluster/images/etcd/migrate/versions_test.go b/cluster/images/etcd/migrate/versions_test.go deleted file mode 100644 index 3e4114fd41807..0000000000000 --- a/cluster/images/etcd/migrate/versions_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2018 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 main - -import ( - "testing" - - "github.com/blang/semver/v4" -) - -func TestSerializeEtcdVersionPair(t *testing.T) { - cases := []struct { - versionTxt string - version *EtcdVersionPair - match bool - }{ - {"3.1.2/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("3.1.2")}, storageEtcd3}, true}, - {"1.1.1-rc.0/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("1.1.1-rc.0")}, storageEtcd3}, true}, - {"10.100.1000/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("10.100.1000")}, storageEtcd3}, true}, - } - - for _, c := range cases { - vp, err := ParseEtcdVersionPair(c.versionTxt) - if err != nil { - t.Errorf("Failed to parse '%s': %v", c.versionTxt, err) - } - if vp.Equals(c.version) != c.match { - t.Errorf("Expected '%s' to be parsed as '%+v', got '%+v'", c.versionTxt, c.version, vp) - } - if vp.String() != c.versionTxt { - t.Errorf("Expected round trip serialization back to '%s', got '%s'", c.versionTxt, vp.String()) - } - } - - unparsables := []string{ - "1.1/etcd3", - "1.1.1.1/etcd3", - "1.1.1/etcd4", - } - for _, unparsable := range unparsables { - vp, err := ParseEtcdVersionPair(unparsable) - if err == nil { - t.Errorf("Should have failed to parse '%s' but got '%s'", unparsable, vp) - } - } -} - -func TestMajorMinorEquals(t *testing.T) { - cases := []struct { - first *EtcdVersion - second *EtcdVersion - match bool - }{ - {&EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 0}}, true}, - {&EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, true}, - - {&EtcdVersion{semver.Version{Major: 3, Minor: 0, Patch: 0}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 0}}, false}, - {&EtcdVersion{semver.Version{Major: 2, Minor: 0, Patch: 0}}, &EtcdVersion{semver.Version{Major: 3, Minor: 0, Patch: 0}}, false}, - } - - for _, c := range cases { - if c.first.MajorMinorEquals(c.second) != c.match { - t.Errorf("Expected (%+v == %+v) == %t, got %t", c.first, c.second, c.match, !c.match) - } - } -} diff --git a/cmd/cloud-controller-manager/.import-restrictions b/cmd/cloud-controller-manager/.import-restrictions index 5b8a0d3582f53..117a31f7e17c0 100644 --- a/cmd/cloud-controller-manager/.import-restrictions +++ b/cmd/cloud-controller-manager/.import-restrictions @@ -41,6 +41,5 @@ rules: - k8s.io/kubernetes/pkg/util/taints - k8s.io/kubernetes/pkg/proxy/util - k8s.io/kubernetes/pkg/proxy/util/testing - - k8s.io/kubernetes/pkg/util/slice - k8s.io/kubernetes/pkg/util/sysctl - k8s.io/kubernetes/test/utils/ktesting diff --git a/cmd/genfeaturegates/genfeaturegates.go b/cmd/genfeaturegates/genfeaturegates.go new file mode 100644 index 0000000000000..f2690a7d3b23b --- /dev/null +++ b/cmd/genfeaturegates/genfeaturegates.go @@ -0,0 +1,598 @@ +/* +Copyright 2025 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 main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/component-base/featuregate" + + _ "k8s.io/apiextensions-apiserver/pkg/features" + _ "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + _ "k8s.io/kubernetes/pkg/features" +) + +var ( + sortBy = flag.String("sort", "name", "Sort by: name, stage, alpha, beta, ga, deprecated") + reverse = flag.Bool("reverse", false, "Reverse sort order") + output = flag.String("output", "", "Output file path (stdout if empty)") + filterStage = flag.String("stage", "", "Filter by stage: alpha, beta, ga, deprecated") + format = flag.String("format", "markdown", "Output format: markdown, json") +) + +// FeatureGateJSON represents a feature gate in JSON output format +type FeatureGateJSON struct { + Name string `json:"name"` + Stages []StageJSON `json:"stages"` + Dependencies []string `json:"dependencies,omitempty"` +} + +// StageJSON represents a stage in the feature gate lifecycle +type StageJSON struct { + Stage string `json:"stage"` + FromVersion string `json:"fromVersion"` + ToVersion string `json:"toVersion,omitempty"` + Default bool `json:"defaultValue"` + Locked bool `json:"locked,omitempty"` +} + +// stageInfo holds version range info for a single stage entry +type stageInfo struct { + fromVersion string + toVersion string +} + +// featureInfo holds processed information about a feature gate (for markdown) +type featureInfo struct { + name string + stage string + stageOrder int // for sorting: 1=Alpha, 2=Beta, 3=GA, 4=Deprecated + enabledFromVersion string + lockedFromVersion string + alphaStages []stageInfo + alphaVersion *version.Version + betaStages []stageInfo + betaVersion *version.Version + gaStages []stageInfo + gaVersion *version.Version + depStages []stageInfo + depVersion *version.Version + deps string + linkCode string + linkKEPs string +} + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [flags]\n\nFlags:\n", os.Args[0]) + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "\nExamples:\n") + fmt.Fprintf(os.Stderr, " %s # Markdown output (default)\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -format=json # JSON output\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -sort=stage # Sort by current stage\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -sort=alpha # Sort by alpha version\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -sort=ga -reverse # Sort by GA version, newest first\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -stage=alpha # Show only alpha features\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -output=features.json # Write to file\n", os.Args[0]) + } + flag.Parse() + + var result string + if *format == "json" { + result = generateJSON(*filterStage) + } else { + result = generateMarkdown(*sortBy, *reverse, *filterStage) + } + + if *output == "" { + fmt.Print(result) + } else { + dir := filepath.Dir(*output) + if dir != "" && dir != "." { + if err := os.MkdirAll(dir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "failed to create output directory: %v\n", err) + os.Exit(1) + } + } + if err := os.WriteFile(*output, []byte(result), 0644); err != nil { + fmt.Fprintf(os.Stderr, "failed to write output file: %v\n", err) + os.Exit(1) + } + } +} + +// skipFeatures are meta-features that should not appear in the feature list +var skipFeatures = map[string]bool{ + "AllAlpha": true, + "AllBeta": true, +} + +func generateJSON(filterStage string) string { + // Get all versioned feature specs and dependencies using public methods + allFeatures := utilfeature.DefaultMutableFeatureGate.GetAllVersioned() + allDependencies := utilfeature.DefaultMutableFeatureGate.Dependencies() + + // Build feature list + features := make([]FeatureGateJSON, 0, len(allFeatures)) + + for featureName, specs := range allFeatures { + feature := string(featureName) + + // Skip meta-features + if skipFeatures[feature] { + continue + } + + // Sort specs by version to process in order + sort.Sort(featuregate.VersionedSpecs(specs)) + + fg := FeatureGateJSON{ + Name: feature, + Stages: make([]StageJSON, 0), + } + + // Track versions for each stage to compute toVersion + type stageEntry struct { + stage string + fromVersion string + defaultVal bool + locked bool + } + var entries []stageEntry + + for _, spec := range specs { + stageName := "" + switch spec.PreRelease { + case featuregate.Alpha: + stageName = "alpha" + case featuregate.Beta: + stageName = "beta" + case featuregate.GA: + stageName = "stable" + case featuregate.Deprecated: + stageName = "deprecated" + } + + entries = append(entries, stageEntry{ + stage: stageName, + fromVersion: spec.Version.String(), + defaultVal: spec.Default, + locked: spec.LockToDefault, + }) + } + + // Convert entries to stages with toVersion + for i, entry := range entries { + stage := StageJSON{ + Stage: entry.stage, + FromVersion: entry.fromVersion, + Default: entry.defaultVal, + Locked: entry.locked, + } + + // Find toVersion: look for next entry that represents a change + // (different stage, different default, or different locked status) + for j := i + 1; j < len(entries); j++ { + if entries[j].stage != entry.stage || + entries[j].defaultVal != entry.defaultVal || + entries[j].locked != entry.locked { + // toVersion is one minor version before the next entry's fromVersion + stage.ToVersion = getPreviousMinorVersion(entries[j].fromVersion) + break + } + } + + fg.Stages = append(fg.Stages, stage) + } + + // Deduplicate stages - keep first occurrence of each stage with merged version range + fg.Stages = deduplicateStages(fg.Stages) + + // Apply stage filter + if filterStage != "" { + hasStage := false + for _, s := range fg.Stages { + if strings.EqualFold(s.Stage, filterStage) || (filterStage == "ga" && s.Stage == "stable") { + hasStage = true + break + } + } + if !hasStage { + continue + } + } + + // Get dependencies + deps := allDependencies[featuregate.Feature(feature)] + if len(deps) > 0 { + fg.Dependencies = make([]string, len(deps)) + for i, d := range deps { + fg.Dependencies[i] = string(d) + } + } + + features = append(features, fg) + } + + // Sort by name + sort.Slice(features, func(i, j int) bool { + return features[i].Name < features[j].Name + }) + + jsonBytes, err := json.MarshalIndent(features, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to marshal JSON: %v\n", err) + os.Exit(1) + } + + return string(jsonBytes) + "\n" +} + +// getPreviousMinorVersion returns the previous minor version (e.g., "1.30" -> "1.29") +func getPreviousMinorVersion(ver string) string { + v, err := version.Parse(ver) + if err != nil { + return "" + } + minor := v.Minor() + if minor > 0 { + return fmt.Sprintf("%d.%d", v.Major(), minor-1) + } + return "" +} + +// deduplicateStages merges consecutive entries with the same stage, default, and locked values. +// If defaultValue or locked changes within the same stage, we keep separate entries to preserve +// the full lifecycle history (e.g., beta disabled in 1.23, beta enabled in 1.24). +func deduplicateStages(stages []StageJSON) []StageJSON { + if len(stages) == 0 { + return stages + } + + result := make([]StageJSON, 0) + current := stages[0] + + for i := 1; i < len(stages); i++ { + // Only merge if stage, default, AND locked are all the same + if stages[i].Stage == current.Stage && + stages[i].Default == current.Default && + stages[i].Locked == current.Locked { + // Same stage with same settings, extend the version range + current.ToVersion = stages[i].ToVersion + } else { + // Different stage or settings changed, save current and start new + result = append(result, current) + current = stages[i] + } + } + result = append(result, current) + + return result +} + +func generateMarkdown(sortBy string, reverseSort bool, filterStage string) string { + // Get all versioned feature specs and dependencies using public methods + allFeatures := utilfeature.DefaultMutableFeatureGate.GetAllVersioned() + allDependencies := utilfeature.DefaultMutableFeatureGate.Dependencies() + + // Build feature info list + features := make([]featureInfo, 0, len(allFeatures)) + + for featureName, specs := range allFeatures { + feature := string(featureName) + + // Skip meta-features + if skipFeatures[feature] { + continue + } + + info := featureInfo{ + name: feature, + } + + // Sort specs by version to process in order + sort.Sort(featuregate.VersionedSpecs(specs)) + + // First pass: collect all entries with their stage info + type rawEntry struct { + version *version.Version + verStr string + isAlpha bool + isBeta bool + isGA bool + isDeprecated bool + defaultOn bool + locked bool + } + var entries []rawEntry + + for _, spec := range specs { + entry := rawEntry{ + version: spec.Version, + verStr: spec.Version.String(), + defaultOn: spec.Default, + locked: spec.LockToDefault, + } + switch spec.PreRelease { + case featuregate.Alpha: + entry.isAlpha = true + case featuregate.Beta: + entry.isBeta = true + case featuregate.GA: + entry.isGA = true + case featuregate.Deprecated: + entry.isDeprecated = true + } + entries = append(entries, entry) + + // Track first version for each stage and first enabled/locked versions + switch spec.PreRelease { + case featuregate.Alpha: + if info.alphaVersion == nil { + info.alphaVersion = spec.Version + } + info.stage = "Alpha" + info.stageOrder = 1 + case featuregate.Beta: + if info.betaVersion == nil { + info.betaVersion = spec.Version + } + info.stage = "Beta" + info.stageOrder = 2 + case featuregate.GA: + if info.gaVersion == nil { + info.gaVersion = spec.Version + } + info.stage = "GA" + info.stageOrder = 3 + case featuregate.Deprecated: + if info.depVersion == nil { + info.depVersion = spec.Version + } + info.stage = "Deprecated" + info.stageOrder = 4 + } + + // Track when feature was first enabled by default + if spec.Default && info.enabledFromVersion == "" { + info.enabledFromVersion = spec.Version.String() + } + // Track when feature was first locked + if spec.LockToDefault && info.lockedFromVersion == "" { + info.lockedFromVersion = spec.Version.String() + } + } + + // Second pass: build stage ranges with toVersion + // We only track stage transitions (alpha->beta->ga->deprecated), not enabled/locked changes. + // The Enabled and Locked columns show when those settings changed. + for i := 0; i < len(entries); i++ { + entry := entries[i] + si := stageInfo{ + fromVersion: entry.verStr, + } + + // Find toVersion: look for the next entry with a different stage + for j := i + 1; j < len(entries); j++ { + sameStage := (entry.isAlpha == entries[j].isAlpha && + entry.isBeta == entries[j].isBeta && + entry.isGA == entries[j].isGA && + entry.isDeprecated == entries[j].isDeprecated) + if !sameStage { + si.toVersion = getPreviousMinorVersion(entries[j].verStr) + break + } + } + + // Merge with previous entry if same stage, otherwise add new entry + if entry.isAlpha { + if len(info.alphaStages) > 0 { + last := &info.alphaStages[len(info.alphaStages)-1] + last.toVersion = si.toVersion + continue + } + info.alphaStages = append(info.alphaStages, si) + } else if entry.isBeta { + if len(info.betaStages) > 0 { + last := &info.betaStages[len(info.betaStages)-1] + last.toVersion = si.toVersion + continue + } + info.betaStages = append(info.betaStages, si) + } else if entry.isGA { + if len(info.gaStages) > 0 { + last := &info.gaStages[len(info.gaStages)-1] + last.toVersion = si.toVersion + continue + } + info.gaStages = append(info.gaStages, si) + } else if entry.isDeprecated { + if len(info.depStages) > 0 { + last := &info.depStages[len(info.depStages)-1] + last.toVersion = si.toVersion + continue + } + info.depStages = append(info.depStages, si) + } + } + + // Apply stage filter + if filterStage != "" && !strings.EqualFold(info.stage, filterStage) { + continue + } + + // Get dependencies for this feature as bullet list (one per line) + deps := allDependencies[featuregate.Feature(feature)] + if len(deps) > 0 { + depItems := make([]string, len(deps)) + for i, d := range deps { + depItems[i] = string(d) + } + info.deps = strings.Join(depItems, "
") + } + + info.linkCode = fmt.Sprintf("[code](https://cs.k8s.io/?q=%%5Cb%s%%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes)", feature) + info.linkKEPs = fmt.Sprintf("[KEPs](https://cs.k8s.io/?q=%%5Cb%s%%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements)", feature) + + features = append(features, info) + } + + // Sort features based on sortBy parameter + sortFeatures(features, sortBy, reverseSort) + + // Build markdown output + var sb strings.Builder + sb.WriteString("# Kubernetes Feature Gates\n\n") + sb.WriteString("\n") + sb.WriteString("\n\n") + + if filterStage != "" { + sb.WriteString(fmt.Sprintf("*Showing only %s features (%d)*\n\n", strings.ToUpper(filterStage), len(features))) + } + + sb.WriteString("| Feature | Enabled | Locked | Alpha | Beta | GA | Deprecated | Dependencies | Links |\n") + sb.WriteString("|---------|---------|--------|-------|------|----|------------|--------------|-------|\n") + + for _, info := range features { + sb.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s | %s | %s | %s %s |\n", + info.name, + formatEnabledColumn(info.enabledFromVersion), + formatLockedColumn(info.lockedFromVersion), + formatStageRanges(info.alphaStages), + formatStageRanges(info.betaStages), + formatStageRanges(info.gaStages), + formatStageRanges(info.depStages), + info.deps, info.linkCode, info.linkKEPs)) + } + + sb.WriteString("\n## Legend\n\n") + sb.WriteString("| Symbol | Meaning |\n") + sb.WriteString("|--------|--------|\n") + sb.WriteString("| :ballot_box_with_check: | Enabled by default (in Enabled column: version when first enabled) |\n") + sb.WriteString("| :closed_lock_with_key: | Locked to default, cannot be changed (in Locked column: version when first locked) |\n") + sb.WriteString("| 1.X– | Feature is in this state from version 1.X (open-ended range) |\n") + + return sb.String() +} + +// formatEnabledColumn formats the Enabled column showing when a feature was first enabled by default +func formatEnabledColumn(enabledFromVersion string) string { + if enabledFromVersion == "" { + return "" + } + return ":ballot_box_with_check: " + enabledFromVersion + "+" +} + +// formatLockedColumn formats the Locked column showing when a feature was first locked +func formatLockedColumn(lockedFromVersion string) string { + if lockedFromVersion == "" { + return "" + } + return ":closed_lock_with_key: " + lockedFromVersion + "+" +} + +// formatStageRanges formats a list of stage ranges for markdown display +// Each range shows "fromVersion–toVersion" (with en-dash) or "fromVersion–" for open-ended ranges +func formatStageRanges(stages []stageInfo) string { + if len(stages) == 0 { + return "" + } + + var parts []string + for i, s := range stages { + var versionRange string + if s.toVersion != "" && s.toVersion != s.fromVersion { + versionRange = s.fromVersion + "–" + s.toVersion // en-dash for range + } else if s.toVersion == "" && i == len(stages)-1 { + // Last stage with no end version - trailing en-dash indicates open range + versionRange = s.fromVersion + "–" + } else { + versionRange = s.fromVersion + } + + parts = append(parts, versionRange) + } + + return strings.Join(parts, ", ") +} + +func sortFeatures(features []featureInfo, sortBy string, reverseSort bool) { + var less func(i, j int) bool + + switch sortBy { + case "stage": + less = func(i, j int) bool { + if features[i].stageOrder != features[j].stageOrder { + return features[i].stageOrder < features[j].stageOrder + } + return features[i].name < features[j].name + } + case "alpha": + less = func(i, j int) bool { + return compareVersions(features[i].alphaVersion, features[j].alphaVersion, features[i].name, features[j].name) + } + case "beta": + less = func(i, j int) bool { + return compareVersions(features[i].betaVersion, features[j].betaVersion, features[i].name, features[j].name) + } + case "ga": + less = func(i, j int) bool { + return compareVersions(features[i].gaVersion, features[j].gaVersion, features[i].name, features[j].name) + } + case "deprecated": + less = func(i, j int) bool { + return compareVersions(features[i].depVersion, features[j].depVersion, features[i].name, features[j].name) + } + default: // "name" + less = func(i, j int) bool { + return features[i].name < features[j].name + } + } + + if reverseSort { + sort.Slice(features, func(i, j int) bool { + return less(j, i) + }) + } else { + sort.Slice(features, less) + } +} + +// compareVersions compares two versions, putting nil versions last +func compareVersions(v1, v2 *version.Version, name1, name2 string) bool { + if v1 == nil && v2 == nil { + return name1 < name2 + } + if v1 == nil { + return false // nil goes last + } + if v2 == nil { + return true // nil goes last + } + if v1.EqualTo(v2) { + return name1 < name2 + } + return v1.LessThan(v2) +} diff --git a/cmd/kube-apiserver/OWNERS b/cmd/kube-apiserver/OWNERS index 062e77cc1111f..4ed74bc0d0844 100644 --- a/cmd/kube-apiserver/OWNERS +++ b/cmd/kube-apiserver/OWNERS @@ -18,7 +18,6 @@ reviewers: - liggitt - sttts - hzxuzhonghu - - logicalhan - yue9944882 - jpbetz labels: diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index 00f302e8e9b36..a70fb3c61cfca 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -47,7 +47,7 @@ var apiVersionPriorities = merge(controlplaneapiserver.DefaultGenericAPIServiceP {Group: "storage.k8s.io", Version: "v1beta1"}: {Group: 16800, Version: 9}, {Group: "storage.k8s.io", Version: "v1alpha1"}: {Group: 16800, Version: 1}, {Group: "scheduling.k8s.io", Version: "v1"}: {Group: 16600, Version: 15}, - {Group: "scheduling.k8s.io", Version: "v1alpha1"}: {Group: 16600, Version: 1}, + {Group: "scheduling.k8s.io", Version: "v1alpha2"}: {Group: 16600, Version: 2}, {Group: "node.k8s.io", Version: "v1"}: {Group: 16300, Version: 15}, {Group: "node.k8s.io", Version: "v1alpha1"}: {Group: 16300, Version: 1}, {Group: "node.k8s.io", Version: "v1beta1"}: {Group: 16300, Version: 9}, diff --git a/cmd/kube-apiserver/app/testing/testserver.go b/cmd/kube-apiserver/app/testing/testserver.go index fd55cb1c6028e..f6451bfe3743e 100644 --- a/cmd/kube-apiserver/app/testing/testserver.go +++ b/cmd/kube-apiserver/app/testing/testserver.go @@ -54,7 +54,6 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - clientgotransport "k8s.io/client-go/transport" "k8s.io/client-go/util/cert" "k8s.io/client-go/util/keyutil" basecompatibility "k8s.io/component-base/compatibility" @@ -156,7 +155,11 @@ func NewDefaultTestServerOptions() *TestServerInstanceOptions { func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions, customFlags []string, storageConfig *storagebackend.Config) (result TestServer, err error) { // Some callers may have initialize ktesting already. tCtx, ok := t.(ktesting.TContext) - if !ok { + if ok { + // tCtx.Cancel below must not cancel whatever else might be using + // this existing TContext. + tCtx = tCtx.WithCancel() + } else { tCtx = ktesting.Init(t) } @@ -395,12 +398,6 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions, if instanceOptions.EnableCertAuth { if featureGate.Enabled(genericfeatures.UnknownVersionInteroperabilityProxy) { - // TODO: set up a general clean up for testserver - if clientgotransport.DialerStopCh == wait.NeverStop { - ctx, cancel := context.WithTimeout(context.Background(), time.Hour) - t.Cleanup(cancel) - clientgotransport.DialerStopCh = ctx.Done() - } s.PeerCAFile = filepath.Join(s.SecureServing.ServerCert.CertDirectory, s.SecureServing.ServerCert.PairName+".crt") } } diff --git a/cmd/kube-controller-manager/app/batch.go b/cmd/kube-controller-manager/app/batch.go index a1ae796755c0c..9669873fcc58a 100644 --- a/cmd/kube-controller-manager/app/batch.go +++ b/cmd/kube-controller-manager/app/batch.go @@ -23,9 +23,13 @@ import ( "context" "fmt" + schedulinginformers "k8s.io/client-go/informers/scheduling/v1alpha2" + + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/cmd/kube-controller-manager/names" "k8s.io/kubernetes/pkg/controller/cronjob" "k8s.io/kubernetes/pkg/controller/job" + "k8s.io/kubernetes/pkg/features" ) func newJobControllerDescriptor() *ControllerDescriptor { @@ -42,11 +46,20 @@ func newJobController(ctx context.Context, controllerContext ControllerContext, return nil, err } + var workloadInformer schedulinginformers.WorkloadInformer + var podGroupInformer schedulinginformers.PodGroupInformer + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadWithJob) { + workloadInformer = controllerContext.InformerFactory.Scheduling().V1alpha2().Workloads() + podGroupInformer = controllerContext.InformerFactory.Scheduling().V1alpha2().PodGroups() + } + jc, err := job.NewController( ctx, + client, controllerContext.InformerFactory.Core().V1().Pods(), controllerContext.InformerFactory.Batch().V1().Jobs(), - client, + workloadInformer, + podGroupInformer, ) if err != nil { return nil, fmt.Errorf("creating Job controller: %w", err) diff --git a/cmd/kube-controller-manager/app/controller_descriptor.go b/cmd/kube-controller-manager/app/controller_descriptor.go index 30df1ee8aa65c..10f35f287561d 100644 --- a/cmd/kube-controller-manager/app/controller_descriptor.go +++ b/cmd/kube-controller-manager/app/controller_descriptor.go @@ -194,6 +194,7 @@ func NewControllerDescriptors() map[string]*ControllerDescriptor { register(newGarbageCollectorControllerDescriptor()) register(newDaemonSetControllerDescriptor()) register(newJobControllerDescriptor()) + register(newPodGroupProtectionControllerDescriptor()) register(newDeploymentControllerDescriptor()) register(newReplicaSetControllerDescriptor()) register(newHorizontalPodAutoscalerControllerDescriptor()) @@ -230,6 +231,7 @@ func NewControllerDescriptors() map[string]*ControllerDescriptor { // feature gated register(newStorageVersionGarbageCollectorControllerDescriptor()) register(newResourceClaimControllerDescriptor()) + register(newResourcePoolStatusRequestControllerDescriptor()) register(newDeviceTaintEvictionControllerDescriptor()) register(newLegacyServiceAccountTokenCleanerControllerDescriptor()) register(newValidatingAdmissionPolicyStatusControllerDescriptor()) diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 182e41b852c27..6176be2b3c69a 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -21,6 +21,7 @@ package app import ( "context" + "errors" "fmt" "math/rand" "net/http" @@ -30,10 +31,12 @@ import ( "github.com/blang/semver/v4" "github.com/spf13/cobra" + coordinationv1 "k8s.io/api/coordination/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/uuid" @@ -52,6 +55,7 @@ import ( "k8s.io/client-go/metadata/metadatainformer" restclient "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" certutil "k8s.io/client-go/util/cert" @@ -72,14 +76,17 @@ import ( genericcontrollermanager "k8s.io/controller-manager/app" "k8s.io/controller-manager/controller" "k8s.io/controller-manager/pkg/clientbuilder" + cmfeatures "k8s.io/controller-manager/pkg/features" controllerhealthz "k8s.io/controller-manager/pkg/healthz" "k8s.io/controller-manager/pkg/informerfactory" "k8s.io/controller-manager/pkg/leadermigration" "k8s.io/klog/v2" + configv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1" "k8s.io/kubernetes/cmd/kube-controller-manager/app/config" "k8s.io/kubernetes/cmd/kube-controller-manager/app/options" "k8s.io/kubernetes/cmd/kube-controller-manager/names" kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config" + configv1alpha1conversion "k8s.io/kubernetes/pkg/controller/apis/config/v1alpha1" garbagecollector "k8s.io/kubernetes/pkg/controller/garbagecollector" kubefeatures "k8s.io/kubernetes/pkg/features" @@ -141,6 +148,9 @@ controller, and serviceaccounts controller.`, os.Exit(1) } + // We use context.Background() here still because using server.SetupSignalContext() would cause + // components like the event broadcaster to terminate on signal immediately, which is not what we want. + // Termination for that case is being handled explicitly in Run() later on. ctx := context.Background() c, err := s.Config(ctx, KnownControllers(), ControllersDisabledByDefault(), ControllerAliases()) if err != nil { @@ -157,8 +167,12 @@ controller, and serviceaccounts controller.`, fg.(featuregate.MutableFeatureGate).AddMetrics() // add component version metrics s.ComponentGlobalsRegistry.AddMetrics() + + if utilfeature.DefaultFeatureGate.Enabled(cmfeatures.ControllerManagerReleaseLeaderElectionLockOnExit) { + ctx = server.SetupSignalContext() + } stopCh := server.SetupSignalHandler() - return Run(context.Background(), c.Complete(), stopCh) + return Run(ctx, c.Complete(), stopCh) }, Args: func(cmd *cobra.Command, args []string) error { for _, arg := range args { @@ -209,10 +223,16 @@ func Run(ctx context.Context, c *config.CompletedConfig, stopCh2 <-chan struct{} c.EventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: c.Client.CoreV1().Events("")}) defer c.EventBroadcaster.Shutdown() - if cfgz, err := configz.New(ConfigzName); err == nil { - cfgz.Set(c.ComponentConfig) - } else { - logger.Error(err, "Unable to register configz") + externalConfig := &configv1alpha1.KubeControllerManagerConfiguration{} + if err := configv1alpha1conversion.Convert_config_KubeControllerManagerConfiguration_To_v1alpha1_KubeControllerManagerConfiguration(&c.ComponentConfig, externalConfig, nil); err != nil { + return fmt.Errorf("unable to convert configz: %w", err) + } + externalConfig.SetGroupVersionKind(configv1alpha1.SchemeGroupVersion.WithKind("KubeControllerManagerConfiguration")) + + if cfgz, err := configz.New(ConfigzName); err != nil { + return fmt.Errorf("unable to register configz: %w", err) + } else if err := cfgz.Set(externalConfig); err != nil { + return fmt.Errorf("unable to set configz: %w", err) } // start the localhost health monitor early so that it can be used by the LE client @@ -269,42 +289,43 @@ func Run(ctx context.Context, c *config.CompletedConfig, stopCh2 <-chan struct{} saTokenControllerDescriptor := newServiceAccountTokenControllerDescriptor(rootClientBuilder) - run := func(ctx context.Context, controllerDescriptors map[string]*ControllerDescriptor) { + run := func(ctx context.Context, controllerDescriptors map[string]*ControllerDescriptor) error { controllerContext, err := CreateControllerContext(ctx, c, rootClientBuilder, clientBuilder) if err != nil { logger.Error(err, "Error building controller context") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) + return err } // Prepare all controllers in advance. controllers, err := BuildControllers(ctx, controllerContext, controllerDescriptors, unsecuredMux, healthzHandler) if err != nil { logger.Error(err, "Error building controllers") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) + return err } // Start the informers. stopCh := ctx.Done() controllerContext.InformerFactory.Start(stopCh) + defer controllerContext.InformerFactory.Shutdown() controllerContext.ObjectOrMetadataInformerFactory.Start(stopCh) close(controllerContext.InformersStarted) // Actually start the controllers. if len(controllers) > 0 { if !RunControllers(ctx, controllerContext, controllers, ControllerStartJitter, c.ControllerShutdownTimeout) { - klog.FlushAndExit(klog.ExitFlushTimeout, 1) + return errors.New("controller shutdown timeout reached") } } else { <-ctx.Done() } + return nil } // No leader election, run directly if !c.ComponentConfig.Generic.LeaderElection.LeaderElect { controllerDescriptors := NewControllerDescriptors() controllerDescriptors[names.ServiceAccountTokenController] = saTokenControllerDescriptor - run(ctx, controllerDescriptors) - return nil + return run(ctx, controllerDescriptors) } id, err := os.Hostname() @@ -373,74 +394,113 @@ func Run(ctx context.Context, c *config.CompletedConfig, stopCh2 <-chan struct{} go leaseCandidate.Run(ctx) } - // Start the main lock - go leaderElectAndRun(ctx, c, id, electionChecker, - c.ComponentConfig.Generic.LeaderElection.ResourceLock, - c.ComponentConfig.Generic.LeaderElection.ResourceName, - leaderelection.LeaderCallbacks{ - OnStartedLeading: func(ctx context.Context) { - controllerDescriptors := NewControllerDescriptors() - if leaderMigrator != nil { - // If leader migration is enabled, we should start only non-migrated controllers - // for the main lock. - controllerDescriptors = filteredControllerDescriptors(controllerDescriptors, leaderMigrator.FilterFunc, leadermigration.ControllerNonMigrated) - logger.Info("leader migration: starting main controllers.") - } - controllerDescriptors[names.ServiceAccountTokenController] = saTokenControllerDescriptor - run(ctx, controllerDescriptors) - }, - OnStoppedLeading: func() { - select { - case <-stopCh: - // We were asked to terminate. Exit 0. - klog.Info("Requested to terminate. Exiting.") - os.Exit(0) - default: - // We lost the lock. - logger.Error(nil, "leaderelection lost") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - }, - }, stopCh) + // Start the main lock. + ctx, cancel := context.WithCancel(ctx) + defer cancel() - // If Leader Migration is enabled, proceed to attempt the migration lock. - if leaderMigrator != nil { - // Wait for Service Account Token Controller to start before acquiring the migration lock. - // At this point, the main lock must have already been acquired, or the KCM process already exited. - // We wait for the main lock before acquiring the migration lock to prevent the situation - // where KCM instance A holds the main lock while KCM instance B holds the migration lock. - <-leaderMigrator.MigrationReady + // startedLeading must be used to wrap any OnStartedLeading leader election callback. + var ( + errs []error + errsLock sync.Mutex + ) + startedLeading := func(next func(context.Context) error) func(context.Context) { + return func(ctx context.Context) { + // It's more efficient to cancel the context at the end of OnStartedLeading to signal termination, + // because OnStoppedLeading is only called once the LE lock is released. + defer cancel() + if err := next(ctx); err != nil && !errors.Is(err, context.Canceled) { + errsLock.Lock() + errs = append(errs, err) + errsLock.Unlock() + } + } + } - // Start the migration lock. - go leaderElectAndRun(ctx, c, id, electionChecker, - c.ComponentConfig.Generic.LeaderMigration.ResourceLock, - c.ComponentConfig.Generic.LeaderMigration.LeaderName, + var wg sync.WaitGroup + wg.Go(func() { + leaderElectAndRun(ctx, c, id, electionChecker, + c.ComponentConfig.Generic.LeaderElection.ResourceLock, + c.ComponentConfig.Generic.LeaderElection.ResourceName, leaderelection.LeaderCallbacks{ - OnStartedLeading: func(ctx context.Context) { - logger.Info("leader migration: starting migrated controllers.") + OnStartedLeading: startedLeading(func(ctx context.Context) error { controllerDescriptors := NewControllerDescriptors() - controllerDescriptors = filteredControllerDescriptors(controllerDescriptors, leaderMigrator.FilterFunc, leadermigration.ControllerMigrated) - // DO NOT start saTokenController under migration lock - delete(controllerDescriptors, names.ServiceAccountTokenController) - run(ctx, controllerDescriptors) - }, + if leaderMigrator != nil { + // If leader migration is enabled, we should start only non-migrated controllers + // for the main lock. + controllerDescriptors = filteredControllerDescriptors(controllerDescriptors, leaderMigrator.FilterFunc, leadermigration.ControllerNonMigrated) + logger.Info("leader migration: starting main controllers.") + } + controllerDescriptors[names.ServiceAccountTokenController] = saTokenControllerDescriptor + return run(ctx, controllerDescriptors) + }), OnStoppedLeading: func() { select { case <-stopCh: // We were asked to terminate. Exit 0. klog.Info("Requested to terminate. Exiting.") - os.Exit(0) + if !utilfeature.DefaultFeatureGate.Enabled(cmfeatures.ControllerManagerReleaseLeaderElectionLockOnExit) { + os.Exit(0) + } default: // We lost the lock. - logger.Error(nil, "migration leaderelection lost") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) + logger.Error(nil, "leaderelection lost/stopped") + if !utilfeature.DefaultFeatureGate.Enabled(cmfeatures.ControllerManagerReleaseLeaderElectionLockOnExit) { + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } } }, }, stopCh) + }) + + // If Leader Migration is enabled, proceed to attempt the migration lock. + if leaderMigrator != nil { + // Wait for Service Account Token Controller to start before acquiring the migration lock. + // At this point, the main lock must have already been acquired, or the KCM process already exited. + // We wait for the main lock before acquiring the migration lock to prevent the situation + // where KCM instance A holds the main lock while KCM instance B holds the migration lock. + select { + case <-leaderMigrator.MigrationReady: + // Start the migration lock. + wg.Go(func() { + leaderElectAndRun(ctx, c, id, electionChecker, + c.ComponentConfig.Generic.LeaderMigration.ResourceLock, + c.ComponentConfig.Generic.LeaderMigration.LeaderName, + leaderelection.LeaderCallbacks{ + OnStartedLeading: startedLeading(func(ctx context.Context) error { + logger.Info("leader migration: starting migrated controllers.") + controllerDescriptors := NewControllerDescriptors() + controllerDescriptors = filteredControllerDescriptors(controllerDescriptors, leaderMigrator.FilterFunc, leadermigration.ControllerMigrated) + // DO NOT start saTokenController under migration lock + delete(controllerDescriptors, names.ServiceAccountTokenController) + return run(ctx, controllerDescriptors) + }), + OnStoppedLeading: func() { + select { + case <-stopCh: + // We were asked to terminate. Exit 0. + klog.Info("Requested to terminate. Exiting.") + if !utilfeature.DefaultFeatureGate.Enabled(cmfeatures.ControllerManagerReleaseLeaderElectionLockOnExit) { + os.Exit(0) + } + default: + // We lost the lock. + logger.Error(nil, "migration leaderelection lost/stopped") + if !utilfeature.DefaultFeatureGate.Enabled(cmfeatures.ControllerManagerReleaseLeaderElectionLockOnExit) { + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + } + }, + }, stopCh) + }) + + case <-ctx.Done(): + } } - <-stopCh - return nil + // Block until all leader elections are stopped. + wg.Wait() + // There is no need to hold errsLock since by this time all goroutines have terminated. + return utilerrors.NewAggregate(errs) } // ControllerContext defines the context object for controller @@ -529,9 +589,14 @@ func CreateControllerContext(ctx context.Context, s *config.CompletedConfig, roo return ControllerContext{}, fmt.Errorf("failed to create Kubernetes client for %q: %w", "shared-informers", err) } + informerName, err := cache.NewInformerName("kube-controller-manager") + if err != nil { + return ControllerContext{}, fmt.Errorf("failed to create informer name: %w", err) + } + var sharedInformers informers.SharedInformerFactory if InformerFactoryOverride == nil { - sharedInformers = informers.NewSharedInformerFactoryWithOptions(versionedClient, ResyncPeriod(s)(), informers.WithTransform(trim)) + sharedInformers = informers.NewSharedInformerFactoryWithOptions(versionedClient, ResyncPeriod(s)(), informers.WithTransform(trim), informers.WithInformerName(informerName)) } else { sharedInformers = InformerFactoryOverride } @@ -847,17 +912,16 @@ func leaderElectAndRun(ctx context.Context, c *config.CompletedConfig, lockIdent cancel() }() leaderelection.RunOrDie(leCtx, leaderelection.LeaderElectionConfig{ - Lock: rl, - LeaseDuration: c.ComponentConfig.Generic.LeaderElection.LeaseDuration.Duration, - RenewDeadline: c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration, - RetryPeriod: c.ComponentConfig.Generic.LeaderElection.RetryPeriod.Duration, - Callbacks: callbacks, - WatchDog: electionChecker, - Name: leaseName, - Coordinated: utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection), + Lock: rl, + LeaseDuration: c.ComponentConfig.Generic.LeaderElection.LeaseDuration.Duration, + RenewDeadline: c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration, + RetryPeriod: c.ComponentConfig.Generic.LeaderElection.RetryPeriod.Duration, + Callbacks: callbacks, + WatchDog: electionChecker, + ReleaseOnCancel: utilfeature.DefaultFeatureGate.Enabled(cmfeatures.ControllerManagerReleaseLeaderElectionLockOnExit), + Name: leaseName, + Coordinated: utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection), }) - - panic("unreachable") } // filteredControllerDescriptors returns all controllerDescriptors after filtering through filterFunc. diff --git a/cmd/kube-controller-manager/app/controllermanager_test.go b/cmd/kube-controller-manager/app/controllermanager_test.go index bfb1b34f33180..3683f93288e18 100644 --- a/cmd/kube-controller-manager/app/controllermanager_test.go +++ b/cmd/kube-controller-manager/app/controllermanager_test.go @@ -64,6 +64,7 @@ func TestControllerNamesDeclaration(t *testing.T) { names.GarbageCollectorController, names.DaemonSetController, names.JobController, + names.PodGroupProtectionController, names.DeploymentController, names.ReplicaSetController, names.HorizontalPodAutoscalerController, @@ -98,6 +99,7 @@ func TestControllerNamesDeclaration(t *testing.T) { names.StorageVersionGarbageCollectorController, names.ResourceClaimController, names.DeviceTaintEvictionController, + names.ResourcePoolStatusRequestController, names.LegacyServiceAccountTokenCleanerController, names.ValidatingAdmissionPolicyStatusController, names.ServiceCIDRController, diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index 309286be8bbee..96e2abfec2b5d 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -30,7 +30,6 @@ import ( v1 "k8s.io/api/core/v1" genericfeatures "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/quota/v1/generic" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/discovery" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/metadata" @@ -42,7 +41,6 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/cmd/kube-controller-manager/names" pkgcontroller "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/controller/devicetainteviction" endpointcontroller "k8s.io/kubernetes/pkg/controller/endpoint" "k8s.io/kubernetes/pkg/controller/garbagecollector" namespacecontroller "k8s.io/kubernetes/pkg/controller/namespace" @@ -52,7 +50,6 @@ import ( lifecyclecontroller "k8s.io/kubernetes/pkg/controller/nodelifecycle" "k8s.io/kubernetes/pkg/controller/podgc" replicationcontroller "k8s.io/kubernetes/pkg/controller/replication" - "k8s.io/kubernetes/pkg/controller/resourceclaim" resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" "k8s.io/kubernetes/pkg/controller/storageversiongc" @@ -248,41 +245,6 @@ func newTaintEvictionController(ctx context.Context, controllerContext Controlle return newControllerLoop(tec.Run, controllerName), nil } -func newDeviceTaintEvictionControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.DeviceTaintEvictionController, - constructor: newDeviceTaintEvictionController, - requiredFeatureGates: []featuregate.Feature{ - // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing these feature gates. - features.DynamicResourceAllocation, - features.DRADeviceTaints, - }, - } -} - -func newDeviceTaintEvictionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { - client, err := controllerContext.NewClient(names.DeviceTaintEvictionController) - if err != nil { - return nil, err - } - - deviceTaintEvictionController := devicetainteviction.New( - client, - controllerContext.InformerFactory.Core().V1().Pods(), - controllerContext.InformerFactory.Resource().V1().ResourceClaims(), - controllerContext.InformerFactory.Resource().V1().ResourceSlices(), - controllerContext.InformerFactory.Resource().V1alpha3().DeviceTaintRules(), - controllerContext.InformerFactory.Resource().V1().DeviceClasses(), - controllerName, - ) - return newControllerLoop(func(ctx context.Context) { - if err := deviceTaintEvictionController.Run(ctx, int(controllerContext.ComponentConfig.DeviceTaintEvictionController.ConcurrentSyncs)); err != nil { - klog.FromContext(ctx).Error(err, "Device taint processing leading to Pod eviction failed and is now paused") - } - <-ctx.Done() - }, controllerName), nil -} - func newCloudNodeLifecycleControllerDescriptor() *ControllerDescriptor { return &ControllerDescriptor{ name: cpnames.CloudNodeLifecycleController, @@ -383,7 +345,7 @@ func newPersistentVolumeAttachDetachController(ctx context.Context, controllerCo csiDriverInformer, controllerContext.InformerFactory.Storage().V1().VolumeAttachments(), plugins, - GetDynamicPluginProber(controllerContext.ComponentConfig.PersistentVolumeBinderController.VolumeConfiguration), + GetDynamicPluginProber(ctx, controllerContext.ComponentConfig.PersistentVolumeBinderController.VolumeConfiguration), controllerContext.ComponentConfig.AttachDetachController.DisableAttachDetachReconcilerSync, controllerContext.ComponentConfig.AttachDetachController.ReconcilerSyncLoopPeriod.Duration, controllerContext.ComponentConfig.AttachDetachController.DisableForceDetachOnTimeout, @@ -423,7 +385,7 @@ func newPersistentVolumeExpanderController(ctx context.Context, controllerContex controllerContext.InformerFactory.Core().V1().PersistentVolumeClaims(), plugins, csiTranslator, - csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate), + csimigration.NewPluginManager(csiTranslator), ) if err != nil { return nil, fmt.Errorf("failed to init volume expand controller: %w", err) @@ -460,44 +422,6 @@ func newEphemeralVolumeController(ctx context.Context, controllerContext Control }, controllerName), nil } -const defaultResourceClaimControllerWorkers = 50 - -func newResourceClaimControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.ResourceClaimController, - aliases: []string{"resource-claim-controller"}, - constructor: newResourceClaimController, - requiredFeatureGates: []featuregate.Feature{ - features.DynamicResourceAllocation, // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing this feature - }, - } -} - -func newResourceClaimController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { - client, err := controllerContext.NewClient("resource-claim-controller") - if err != nil { - return nil, err - } - - ephemeralController, err := resourceclaim.NewController( - klog.FromContext(ctx), - resourceclaim.Features{ - AdminAccess: utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), - PrioritizedList: utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), - }, - client, - controllerContext.InformerFactory.Core().V1().Pods(), - controllerContext.InformerFactory.Resource().V1().ResourceClaims(), - controllerContext.InformerFactory.Resource().V1().ResourceClaimTemplates()) - if err != nil { - return nil, fmt.Errorf("failed to init resource claim controller: %w", err) - } - - return newControllerLoop(func(ctx context.Context) { - ephemeralController.Run(ctx, defaultResourceClaimControllerWorkers) - }, controllerName), nil -} - func newEndpointsControllerDescriptor() *ControllerDescriptor { return &ControllerDescriptor{ name: names.EndpointsController, @@ -597,7 +521,10 @@ func newResourceQuotaController(ctx context.Context, controllerContext Controlle discoveryFunc := resourceQuotaControllerDiscoveryClient.ServerPreferredNamespacedResources listerFuncForResource := generic.ListerFuncForResourceFunc(controllerContext.InformerFactory.ForResource) - quotaConfiguration := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource, controllerContext.InformerFactory) + quotaConfiguration, err := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource, controllerContext.InformerFactory) + if err != nil { + return nil, err + } resourceQuotaControllerOptions := &resourcequotacontroller.ControllerOptions{ QuotaClient: resourceQuotaControllerClient.CoreV1(), @@ -1136,7 +1063,7 @@ func newSELinuxWarningController(ctx context.Context, controllerContext Controll controllerContext.InformerFactory.Core().V1().PersistentVolumes(), csiDriverInformer, plugins, - GetDynamicPluginProber(controllerContext.ComponentConfig.PersistentVolumeBinderController.VolumeConfiguration), + GetDynamicPluginProber(ctx, controllerContext.ComponentConfig.PersistentVolumeBinderController.VolumeConfiguration), ) if err != nil { return nil, fmt.Errorf("failed to start SELinux warning controller: %w", err) diff --git a/cmd/kube-controller-manager/app/options/options.go b/cmd/kube-controller-manager/app/options/options.go index 2a1e8000b12a9..5f6d31203adb4 100644 --- a/cmd/kube-controller-manager/app/options/options.go +++ b/cmd/kube-controller-manager/app/options/options.go @@ -26,12 +26,10 @@ import ( v1 "k8s.io/api/core/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/version" "k8s.io/apiserver/pkg/server/flagz" apiserveroptions "k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" - clientgofeaturegate "k8s.io/client-go/features" clientset "k8s.io/client-go/kubernetes" clientgokubescheme "k8s.io/client-go/kubernetes/scheme" restclient "k8s.io/client-go/rest" @@ -42,10 +40,10 @@ import ( cpoptions "k8s.io/cloud-provider/options" cliflag "k8s.io/component-base/cli/flag" basecompatibility "k8s.io/component-base/compatibility" - "k8s.io/component-base/featuregate" "k8s.io/component-base/logs" logsapi "k8s.io/component-base/logs/api/v1" "k8s.io/component-base/metrics" + metricsfeatures "k8s.io/component-base/metrics/features" cmoptions "k8s.io/controller-manager/options" kubectrlmgrconfigv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1" kubecontrollerconfig "k8s.io/kubernetes/cmd/kube-controller-manager/app/config" @@ -80,6 +78,7 @@ type KubeControllerManagerOptions struct { DaemonSetController *DaemonSetControllerOptions DeploymentController *DeploymentControllerOptions DeviceTaintEvictionController *DeviceTaintEvictionControllerOptions + ResourceClaimController *ResourceClaimControllerOptions StatefulSetController *StatefulSetControllerOptions DeprecatedFlags *DeprecatedControllerOptions EndpointController *EndpointControllerOptions @@ -159,6 +158,9 @@ func NewKubeControllerManagerOptions() (*KubeControllerManagerOptions, error) { DeviceTaintEvictionController: &DeviceTaintEvictionControllerOptions{ &componentConfig.DeviceTaintEvictionController, }, + ResourceClaimController: &ResourceClaimControllerOptions{ + &componentConfig.ResourceClaimController, + }, StatefulSetController: &StatefulSetControllerOptions{ &componentConfig.StatefulSetController, }, @@ -281,6 +283,7 @@ func (s *KubeControllerManagerOptions) Flags(allControllers []string, disabledBy s.CSRSigningController.AddFlags(fss.FlagSet(names.CertificateSigningRequestSigningController)) s.DeploymentController.AddFlags(fss.FlagSet(names.DeploymentController)) s.DeviceTaintEvictionController.AddFlags(fss.FlagSet(names.DeviceTaintEvictionController)) + s.ResourceClaimController.AddFlags(fss.FlagSet(names.ResourceClaimController)) s.StatefulSetController.AddFlags(fss.FlagSet(names.StatefulSetController)) s.DaemonSetController.AddFlags(fss.FlagSet(names.DaemonSetController)) s.DeprecatedFlags.AddFlags(fss.FlagSet("deprecated")) @@ -315,13 +318,6 @@ func (s *KubeControllerManagerOptions) Flags(allControllers []string, disabledBy fss.FlagSet("generic").DurationVar(&s.ControllerShutdownTimeout, "controller-shutdown-timeout", s.ControllerShutdownTimeout, "Time to wait for the controllers to shut down before terminating the executable") - if !utilfeature.DefaultFeatureGate.Enabled(featuregate.Feature(clientgofeaturegate.WatchListClient)) { - ver := version.MustParse("1.34") - if err := utilfeature.DefaultMutableFeatureGate.OverrideDefaultAtVersion(featuregate.Feature(clientgofeaturegate.WatchListClient), true, ver); err != nil { - panic(fmt.Sprintf("unable to set %s feature gate, err: %v", clientgofeaturegate.WatchListClient, err)) - } - } - s.ComponentGlobalsRegistry.AddFlags(fss.FlagSet("generic")) fs.StringVar(&s.OpenShiftContext.OpenShiftConfig, "openshift-config", s.OpenShiftContext.OpenShiftConfig, "indicates that this process should be compatible with openshift start master") fs.MarkHidden("openshift-config") @@ -356,6 +352,9 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config, a if err := s.DeviceTaintEvictionController.ApplyTo(&c.ComponentConfig.DeviceTaintEvictionController); err != nil { return err } + if err := s.ResourceClaimController.ApplyTo(&c.ComponentConfig.ResourceClaimController); err != nil { + return err + } if err := s.StatefulSetController.ApplyTo(&c.ComponentConfig.StatefulSetController); err != nil { return err } @@ -459,6 +458,7 @@ func (s *KubeControllerManagerOptions) Validate(allControllers []string, disable errs = append(errs, s.DaemonSetController.Validate()...) errs = append(errs, s.DeploymentController.Validate()...) errs = append(errs, s.DeviceTaintEvictionController.Validate()...) + errs = append(errs, s.ResourceClaimController.Validate()...) errs = append(errs, s.StatefulSetController.Validate()...) errs = append(errs, s.DeprecatedFlags.Validate()...) errs = append(errs, s.EndpointController.Validate()...) @@ -548,6 +548,7 @@ func (s KubeControllerManagerOptions) Config(ctx context.Context, allControllers if err := s.ApplyTo(c, allControllers, disabledByDefaultControllers, controllerAliases); err != nil { return nil, err } + metricsfeatures.ApplyFeatureGates(utilfeature.DefaultMutableFeatureGate) s.Metrics.Apply() if s.ParsedFlags != nil { diff --git a/cmd/kube-controller-manager/app/options/options_test.go b/cmd/kube-controller-manager/app/options/options_test.go index d931170083250..ca159bfae548c 100644 --- a/cmd/kube-controller-manager/app/options/options_test.go +++ b/cmd/kube-controller-manager/app/options/options_test.go @@ -69,6 +69,7 @@ import ( podgcconfig "k8s.io/kubernetes/pkg/controller/podgc/config" replicasetconfig "k8s.io/kubernetes/pkg/controller/replicaset/config" replicationconfig "k8s.io/kubernetes/pkg/controller/replication/config" + resourceclaimconfig "k8s.io/kubernetes/pkg/controller/resourceclaim/config" resourcequotaconfig "k8s.io/kubernetes/pkg/controller/resourcequota/config" serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config" statefulsetconfig "k8s.io/kubernetes/pkg/controller/statefulset/config" @@ -100,6 +101,7 @@ var args = []string{ "--cluster-signing-legacy-unknown-key-file=/cluster-signing-legacy-unknown/key-file", "--concurrent-deployment-syncs=10", "--concurrent-device-taint-eviction-syncs=10", + "--concurrent-resourceclaim-syncs=10", "--concurrent-daemonset-syncs=10", "--concurrent-horizontal-pod-autoscaler-syncs=10", "--concurrent-statefulset-syncs=15", @@ -280,6 +282,11 @@ func TestAddFlags(t *testing.T) { ConcurrentSyncs: 10, }, }, + ResourceClaimController: &ResourceClaimControllerOptions{ + &resourceclaimconfig.ResourceClaimControllerConfiguration{ + ConcurrentSyncs: 10, + }, + }, StatefulSetController: &StatefulSetControllerOptions{ &statefulsetconfig.StatefulSetControllerConfiguration{ ConcurrentStatefulSetSyncs: 15, @@ -634,6 +641,9 @@ func TestApplyTo(t *testing.T) { DeviceTaintEvictionController: devicetaintevictionconfig.DeviceTaintEvictionControllerConfiguration{ ConcurrentSyncs: 10, }, + ResourceClaimController: resourceclaimconfig.ResourceClaimControllerConfiguration{ + ConcurrentSyncs: 10, + }, StatefulSetController: statefulsetconfig.StatefulSetControllerConfiguration{ ConcurrentStatefulSetSyncs: 15, }, @@ -1281,6 +1291,15 @@ func TestValidateControllersOptions(t *testing.T) { }, }, }, + { + name: "ResourceClaimControllerOptions", + expectErrors: false, + options: &ResourceClaimControllerOptions{ + &resourceclaimconfig.ResourceClaimControllerConfiguration{ + ConcurrentSyncs: 10, + }, + }, + }, { name: "DeprecatedControllerOptions", expectErrors: false, diff --git a/cmd/kube-controller-manager/app/options/resourceclaimcontroller.go b/cmd/kube-controller-manager/app/options/resourceclaimcontroller.go new file mode 100644 index 0000000000000..7b52a7fcccc2f --- /dev/null +++ b/cmd/kube-controller-manager/app/options/resourceclaimcontroller.go @@ -0,0 +1,63 @@ +/* +Copyright 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 options + +import ( + "fmt" + + "github.com/spf13/pflag" + + resourceclaimconfig "k8s.io/kubernetes/pkg/controller/resourceclaim/config" +) + +// ResourceClaimControllerOptions holds the ResourceClaimController options. +type ResourceClaimControllerOptions struct { + *resourceclaimconfig.ResourceClaimControllerConfiguration +} + +// AddFlags adds flags related to ResourceClaimController for controller manager to the specified FlagSet. +func (o *ResourceClaimControllerOptions) AddFlags(fs *pflag.FlagSet) { + if o == nil { + return + } + + fs.Int32Var(&o.ConcurrentSyncs, "concurrent-resourceclaim-syncs", o.ConcurrentSyncs, "The number of operations (creating or deleting ResourceClaims) allowed to run concurrently. Larger number = more responsive, but more CPU (and network) load") +} + +// ApplyTo fills up ResourceClaimController config with options. +func (o *ResourceClaimControllerOptions) ApplyTo(cfg *resourceclaimconfig.ResourceClaimControllerConfiguration) error { + if o == nil { + return nil + } + + cfg.ConcurrentSyncs = o.ConcurrentSyncs + + return nil +} + +// Validate checks validation of ResourceClaimControllerOptions. +func (o *ResourceClaimControllerOptions) Validate() []error { + if o == nil { + return nil + } + + var errs []error + if o.ConcurrentSyncs <= 0 { + errs = append(errs, fmt.Errorf("concurrent-resourceclaim-syncs must be larger than 0, got %d", o.ConcurrentSyncs)) + } + return errs +} diff --git a/cmd/kube-controller-manager/app/plugins.go b/cmd/kube-controller-manager/app/plugins.go index 3ed1e35120bef..80b8063c2fba5 100644 --- a/cmd/kube-controller-manager/app/plugins.go +++ b/cmd/kube-controller-manager/app/plugins.go @@ -21,6 +21,7 @@ import ( // This should probably be part of some configuration fed into the build for a // given binary target. + "context" "fmt" "k8s.io/klog/v2" @@ -35,7 +36,6 @@ import ( "k8s.io/kubernetes/pkg/volume/nfs" volumeutil "k8s.io/kubernetes/pkg/volume/util" - utilfeature "k8s.io/apiserver/pkg/util/feature" persistentvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config" "k8s.io/utils/exec" ) @@ -52,8 +52,8 @@ func ProbeAttachableVolumePlugins(logger klog.Logger, config persistentvolumecon // GetDynamicPluginProber gets the probers of dynamically discoverable plugins // for the attach/detach controller. // Currently only Flexvolume plugins are dynamically discoverable. -func GetDynamicPluginProber(config persistentvolumeconfig.VolumeConfiguration) volume.DynamicPluginProber { - return flexvolume.GetDynamicPluginProber(config.FlexVolumePluginDir, exec.New() /*exec.Interface*/) +func GetDynamicPluginProber(ctx context.Context, config persistentvolumeconfig.VolumeConfiguration) volume.DynamicPluginProber { + return flexvolume.GetDynamicPluginProber(ctx, config.FlexVolumePluginDir, exec.New() /*exec.Interface*/) } // ProbeExpandableVolumePlugins returns volume plugins which are expandable @@ -124,12 +124,6 @@ func probeControllerVolumePlugins(logger klog.Logger, config persistentvolumecon allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...) allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) - var err error - allPlugins, err = appendLegacyControllerProviders(logger, allPlugins, utilfeature.DefaultFeatureGate) - if err != nil { - return allPlugins, err - } - var filteredPlugins []volume.VolumePlugin if filter == nil { filteredPlugins = allPlugins diff --git a/cmd/kube-controller-manager/app/plugins_providers.go b/cmd/kube-controller-manager/app/plugins_providers.go deleted file mode 100644 index e76167242d36c..0000000000000 --- a/cmd/kube-controller-manager/app/plugins_providers.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2019 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 ( - "k8s.io/component-base/featuregate" - "k8s.io/csi-translation-lib/plugins" - "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/pkg/volume/csimigration" - "k8s.io/kubernetes/pkg/volume/portworx" -) - -type probeFn func() []volume.VolumePlugin - -func appendPluginBasedOnFeatureFlags(logger klog.Logger, plugins []volume.VolumePlugin, inTreePluginName string, featureGate featuregate.FeatureGate, pluginInfo pluginInfo) ([]volume.VolumePlugin, error) { - _, err := csimigration.CheckMigrationFeatureFlags(featureGate, pluginInfo.pluginMigrationFeature, pluginInfo.pluginUnregisterFeature) - if err != nil { - logger.Error(err, "Unexpected CSI Migration Feature Flags combination detected. CSI Migration may not take effect") - return nil, err - } - - // Skip appending the in-tree plugin to the list of plugins to be probed/initialized - // if the plugin unregister feature flag is set - if featureGate.Enabled(pluginInfo.pluginUnregisterFeature) { - logger.Info("Skip registration of plugin since feature flag is enabled", "plugin", inTreePluginName, "feature", pluginInfo.pluginUnregisterFeature) - return plugins, nil - } - plugins = append(plugins, pluginInfo.pluginProbeFunction()...) - return plugins, nil -} - -type pluginInfo struct { - pluginMigrationFeature featuregate.Feature - pluginUnregisterFeature featuregate.Feature - pluginProbeFunction probeFn -} - -func appendLegacyControllerProviders(logger klog.Logger, allPlugins []volume.VolumePlugin, featureGate featuregate.FeatureGate) ([]volume.VolumePlugin, error) { - pluginMigrationStatus := make(map[string]pluginInfo) - pluginMigrationStatus[plugins.PortworxVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationPortworx, pluginUnregisterFeature: features.InTreePluginPortworxUnregister, pluginProbeFunction: portworx.ProbeVolumePlugins} - var err error - for pluginName, pluginInfo := range pluginMigrationStatus { - allPlugins, err = appendPluginBasedOnFeatureFlags(logger, allPlugins, pluginName, featureGate, pluginInfo) - if err != nil { - return allPlugins, err - } - } - return allPlugins, nil -} diff --git a/cmd/kube-controller-manager/app/plugins_test.go b/cmd/kube-controller-manager/app/plugins_test.go index 219a96d8d143a..e42611dc40aad 100644 --- a/cmd/kube-controller-manager/app/plugins_test.go +++ b/cmd/kube-controller-manager/app/plugins_test.go @@ -53,7 +53,7 @@ func TestProbeExpandableVolumePlugins(t *testing.T) { if err != nil { t.Fatalf("TestProbeExpandableVolumePlugins failed: %s", err) } - checkPlugins(t, plugins, []string{"kubernetes.io/portworx-volume"}) + checkPlugins(t, plugins, []string{}) } func TestProbeControllerVolumePlugins(t *testing.T) { @@ -62,7 +62,7 @@ func TestProbeControllerVolumePlugins(t *testing.T) { if err != nil { t.Fatalf("ProbeControllerVolumePlugins failed: %s", err) } - checkPlugins(t, plugins, []string{"kubernetes.io/host-path", "kubernetes.io/nfs", "kubernetes.io/portworx-volume"}) + checkPlugins(t, plugins, []string{"kubernetes.io/host-path", "kubernetes.io/nfs"}) } func getConfig() persistentvolumeconfig.VolumeConfiguration { diff --git a/cmd/kube-controller-manager/app/resource.go b/cmd/kube-controller-manager/app/resource.go new file mode 100644 index 0000000000000..0d75dbe69eddf --- /dev/null +++ b/cmd/kube-controller-manager/app/resource.go @@ -0,0 +1,137 @@ +/* +Copyright 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 ( + "context" + "fmt" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" + "k8s.io/klog/v2" + "k8s.io/kubernetes/cmd/kube-controller-manager/names" + "k8s.io/kubernetes/pkg/controller/devicetainteviction" + "k8s.io/kubernetes/pkg/controller/resourceclaim" + "k8s.io/kubernetes/pkg/controller/resourcepoolstatusrequest" + "k8s.io/kubernetes/pkg/features" +) + +func newDeviceTaintEvictionControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.DeviceTaintEvictionController, + constructor: newDeviceTaintEvictionController, + requiredFeatureGates: []featuregate.Feature{ + // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing these feature gates. + features.DynamicResourceAllocation, + features.DRADeviceTaints, + }, + } +} + +func newDeviceTaintEvictionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + client, err := controllerContext.NewClient(names.DeviceTaintEvictionController) + if err != nil { + return nil, err + } + + deviceTaintEvictionController := devicetainteviction.New( + client, + controllerContext.InformerFactory.Core().V1().Pods(), + controllerContext.InformerFactory.Resource().V1().ResourceClaims(), + controllerContext.InformerFactory.Resource().V1().ResourceSlices(), + controllerContext.InformerFactory.Resource().V1beta2().DeviceTaintRules(), + controllerContext.InformerFactory.Resource().V1().DeviceClasses(), + controllerName, + utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims), + ) + return newControllerLoop(func(ctx context.Context) { + if err := deviceTaintEvictionController.Run(ctx, int(controllerContext.ComponentConfig.DeviceTaintEvictionController.ConcurrentSyncs)); err != nil { + klog.FromContext(ctx).Error(err, "Device taint processing leading to Pod eviction failed and is now paused") + } + <-ctx.Done() + }, controllerName), nil +} + +func newResourceClaimControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.ResourceClaimController, + aliases: []string{"resource-claim-controller"}, + constructor: newResourceClaimController, + requiredFeatureGates: []featuregate.Feature{ + features.DynamicResourceAllocation, // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing this feature + }, + } +} + +func newResourceClaimController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + client, err := controllerContext.NewClient("resource-claim-controller") + if err != nil { + return nil, err + } + + ephemeralController, err := resourceclaim.NewController( + klog.FromContext(ctx), + resourceclaim.Features{ + AdminAccess: utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), + PrioritizedList: utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), + WorkloadResourceClaims: utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims), + }, + client, + controllerContext.InformerFactory.Core().V1().Pods(), + controllerContext.InformerFactory.Scheduling().V1alpha2().PodGroups(), + controllerContext.InformerFactory.Resource().V1().ResourceClaims(), + controllerContext.InformerFactory.Resource().V1().ResourceClaimTemplates()) + if err != nil { + return nil, fmt.Errorf("failed to init resource claim controller: %w", err) + } + + return newControllerLoop(func(ctx context.Context) { + ephemeralController.Run(ctx, int(controllerContext.ComponentConfig.ResourceClaimController.ConcurrentSyncs)) + }, controllerName), nil +} + +func newResourcePoolStatusRequestControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.ResourcePoolStatusRequestController, + constructor: newResourcePoolStatusRequestController, + requiredFeatureGates: []featuregate.Feature{ + features.DRAResourcePoolStatus, + }, + } +} + +func newResourcePoolStatusRequestController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + client, err := controllerContext.NewClient("resourcepoolstatusrequest-controller") + if err != nil { + return nil, err + } + + controller, err := resourcepoolstatusrequest.NewController( + ctx, + client, + controllerContext.InformerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + controllerContext.InformerFactory.Resource().V1().ResourceSlices(), + controllerContext.InformerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + return nil, fmt.Errorf("failed to init resourcepoolstatusrequest controller: %w", err) + } + + return newControllerLoop(func(ctx context.Context) { + controller.Run(ctx, 1) // Single worker is sufficient for this controller + }, controllerName), nil +} diff --git a/cmd/kube-controller-manager/app/scheduling.go b/cmd/kube-controller-manager/app/scheduling.go new file mode 100644 index 0000000000000..919e7301da075 --- /dev/null +++ b/cmd/kube-controller-manager/app/scheduling.go @@ -0,0 +1,64 @@ +/* +Copyright 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 ( + "context" + "fmt" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" + "k8s.io/klog/v2" + + "k8s.io/kubernetes/cmd/kube-controller-manager/names" + "k8s.io/kubernetes/pkg/controller/scheduling/podgroupprotection" + "k8s.io/kubernetes/pkg/features" +) + +func newPodGroupProtectionControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.PodGroupProtectionController, + constructor: newPodGroupProtectionController, + requiredFeatureGates: []featuregate.Feature{ + features.GenericWorkload, + }, + } +} + +func newPodGroupProtectionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + return nil, nil + } + client, err := controllerContext.NewClient(controllerName) + if err != nil { + return nil, err + } + + pgProtectionController, err := podgroupprotection.NewPodGroupProtectionController( + klog.FromContext(ctx), + controllerContext.InformerFactory.Scheduling().V1alpha2().PodGroups(), + controllerContext.InformerFactory.Core().V1().Pods(), + client, + ) + if err != nil { + return nil, fmt.Errorf("failed to init %s: %w", controllerName, err) + } + + return newControllerLoop(func(ctx context.Context) { + pgProtectionController.Run(ctx, 1) + }, controllerName), nil +} diff --git a/cmd/kube-controller-manager/app/scheduling_test.go b/cmd/kube-controller-manager/app/scheduling_test.go new file mode 100644 index 0000000000000..44be0a4641979 --- /dev/null +++ b/cmd/kube-controller-manager/app/scheduling_test.go @@ -0,0 +1,100 @@ +/* +Copyright 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 ( + "context" + "testing" + "time" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + fakeclientset "k8s.io/client-go/kubernetes/fake" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/klog/v2/ktesting" + + "k8s.io/kubernetes/cmd/kube-controller-manager/names" + "k8s.io/kubernetes/pkg/features" +) + +func TestPodGroupProtectionController(t *testing.T) { + tests := []struct { + name string + enableFeatureGate bool + expectController bool + }{ + { + name: "controller runs when GenericWorkload is enabled", + enableFeatureGate: true, + expectController: true, + }, + { + name: "controller is disabled when GenericWorkload is disabled", + enableFeatureGate: false, + expectController: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, test.enableFeatureGate) + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + clientset := fakeclientset.NewClientset() + controllerCtx := ControllerContext{ + ClientBuilder: TestClientBuilder{clientset: clientset}, + InformerFactory: informers.NewSharedInformerFactoryWithOptions(clientset, time.Duration(1)), + } + controllerCtx.ComponentConfig.Generic.Controllers = []string{names.PodGroupProtectionController} + + // Verify the constructor produces the expected result. + ctrl, err := newPodGroupProtectionController(ctx, controllerCtx, names.PodGroupProtectionController) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if test.expectController && ctrl == nil { + t.Errorf("expected a controller, got nil") + } else if !test.expectController && ctrl != nil { + t.Errorf("expected no controller, got %v", ctrl) + } + + // Verify the descriptor gates the controller correctly. + initFuncCalled := false + descriptor := NewControllerDescriptors()[names.PodGroupProtectionController] + descriptor.constructor = func(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + initFuncCalled = true + return newControllerLoop(func(ctx context.Context) {}, controllerName), nil + } + + var healthChecks mockHealthCheckAdder + if err := runControllers(ctx, controllerCtx, map[string]*ControllerDescriptor{ + names.PodGroupProtectionController: descriptor, + }, &healthChecks); err != nil { + t.Fatalf("unexpected error starting controller: %v", err) + } + if test.expectController != initFuncCalled { + t.Errorf("constructor call: expected=%v, got=%v", test.expectController, initFuncCalled) + } + hasHealthCheck := len(healthChecks.Checks) > 0 + if test.expectController != hasHealthCheck { + t.Errorf("health check: expected=%v, got=%v", test.expectController, hasHealthCheck) + } + }) + } +} diff --git a/cmd/kube-controller-manager/app/storageversionmigrator.go b/cmd/kube-controller-manager/app/storageversionmigrator.go index 42c3ea1bef166..35eeb8eea168e 100644 --- a/cmd/kube-controller-manager/app/storageversionmigrator.go +++ b/cmd/kube-controller-manager/app/storageversionmigrator.go @@ -20,6 +20,7 @@ import ( "context" "fmt" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" @@ -69,6 +70,11 @@ func newSVMController(ctx context.Context, controllerContext ControllerContext, informer := controllerContext.InformerFactory.Storagemigration().V1beta1().StorageVersionMigrations() + crdClientset, err := apiextensionsclientset.NewForConfig(config) + if err != nil { + return nil, err + } + dynamicClient, err := dynamic.NewForConfig(config) if err != nil { return nil, err @@ -102,5 +108,11 @@ func newSVMController(ctx context.Context, controllerContext ControllerContext, informer, controllerContext.RESTMapper, ).Run, + svm.NewCustomResourceController( + ctx, + client, + informer, + crdClientset.ApiextensionsV1().CustomResourceDefinitions(), + ).Run, ), controllerName), nil } diff --git a/cmd/kube-controller-manager/app/testing/testserver.go b/cmd/kube-controller-manager/app/testing/testserver.go index b8d636b88b370..83701c42ed6e3 100644 --- a/cmd/kube-controller-manager/app/testing/testserver.go +++ b/cmd/kube-controller-manager/app/testing/testserver.go @@ -30,6 +30,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes" "k8s.io/component-base/compatibility" + "k8s.io/component-base/configz" "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" logsapi "k8s.io/component-base/logs/api/v1" @@ -78,6 +79,7 @@ func StartTestServer(t *testing.T, ctx context.Context, customFlags []string) (r logger.Error(err, "Failed to shutdown test server cleanly") } } + configz.Delete(app.ConfigzName) } defer func() { if result.TearDownFn == nil { diff --git a/cmd/kube-controller-manager/names/controller_names.go b/cmd/kube-controller-manager/names/controller_names.go index 216a35050a2e9..347a1ca79b85d 100644 --- a/cmd/kube-controller-manager/names/controller_names.go +++ b/cmd/kube-controller-manager/names/controller_names.go @@ -76,6 +76,7 @@ const ( ClusterRoleAggregationController = "clusterrole-aggregation-controller" PersistentVolumeClaimProtectionController = "persistentvolumeclaim-protection-controller" PersistentVolumeProtectionController = "persistentvolume-protection-controller" + PodGroupProtectionController = "podgroup-protection-controller" TTLAfterFinishedController = "ttl-after-finished-controller" RootCACertificatePublisherController = "root-ca-certificate-publisher-controller" ServiceCACertificatePublisherController = "service-ca-certificate-publisher-controller" @@ -83,6 +84,7 @@ const ( EphemeralVolumeController = "ephemeral-volume-controller" StorageVersionGarbageCollectorController = "storageversion-garbage-collector-controller" ResourceClaimController = "resourceclaim-controller" + ResourcePoolStatusRequestController = "resourcepoolstatusrequest-controller" LegacyServiceAccountTokenCleanerController = "legacy-serviceaccount-token-cleaner-controller" ValidatingAdmissionPolicyStatusController = "validatingadmissionpolicy-status-controller" VolumeAttributesClassProtectionController = "volumeattributesclass-protection-controller" diff --git a/cmd/kube-proxy/app/conntrack.go b/cmd/kube-proxy/app/conntrack.go deleted file mode 100644 index b8a9eda6011dd..0000000000000 --- a/cmd/kube-proxy/app/conntrack.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright 2015 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 ( - "context" - "errors" - "os" - "strconv" - "strings" - - "k8s.io/component-helpers/node/util/sysctl" - "k8s.io/klog/v2" - "k8s.io/mount-utils" -) - -// Conntracker is an interface to the global sysctl. Descriptions of the various -// sysctl fields can be found here: -// -// https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt -type Conntracker interface { - // SetMax adjusts nf_conntrack_max. - SetMax(ctx context.Context, max int) error - // SetTCPEstablishedTimeout adjusts nf_conntrack_tcp_timeout_established. - SetTCPEstablishedTimeout(ctx context.Context, seconds int) error - // SetTCPCloseWaitTimeout adjusts nf_conntrack_tcp_timeout_close_wait. - SetTCPCloseWaitTimeout(ctx context.Context, seconds int) error - // SetTCPBeLiberal adjusts nf_conntrack_tcp_be_liberal. - SetTCPBeLiberal(ctx context.Context, value int) error - // SetUDPTimeout adjusts nf_conntrack_udp_timeout. - SetUDPTimeout(ctx context.Context, seconds int) error - // SetUDPStreamTimeout adjusts nf_conntrack_udp_timeout_stream. - SetUDPStreamTimeout(ctx context.Context, seconds int) error -} - -type realConntracker struct { -} - -var errReadOnlySysFS = errors.New("readOnlySysFS") - -func (rct realConntracker) SetMax(ctx context.Context, max int) error { - logger := klog.FromContext(ctx) - logger.Info("Setting nf_conntrack_max", "nfConntrackMax", max) - if err := rct.setIntSysCtl(ctx, "nf_conntrack_max", max); err != nil { - return err - } - - // Check if hashsize is large enough for the nf_conntrack_max value. - hashsize, err := readIntStringFile("/sys/module/nf_conntrack/parameters/hashsize") - if err != nil { - return err - } - if hashsize >= (max / 4) { - return nil - } - - // sysfs is expected to be mounted as 'rw'. However, it may be unexpectedly - // mounted as 'ro' by docker because of known bugs (https://issues.k8s.io/134108). - // In that case we return a special error errReadOnlySysFS here, which the caller - // should deal with specially. - // - // TODO: this workaround can go away once we no longer support containerd 1.7. - writable, err := rct.isSysFSWritable(ctx) - if err != nil { - return err - } - if !writable { - return errReadOnlySysFS - } - - logger.Info("Setting conntrack hashsize", "conntrackHashsize", max/4) - return writeIntStringFile("/sys/module/nf_conntrack/parameters/hashsize", max/4) -} - -func (rct realConntracker) SetTCPEstablishedTimeout(ctx context.Context, seconds int) error { - return rct.setIntSysCtl(ctx, "nf_conntrack_tcp_timeout_established", seconds) -} - -func (rct realConntracker) SetTCPCloseWaitTimeout(ctx context.Context, seconds int) error { - return rct.setIntSysCtl(ctx, "nf_conntrack_tcp_timeout_close_wait", seconds) -} - -func (rct realConntracker) SetTCPBeLiberal(ctx context.Context, value int) error { - return rct.setIntSysCtl(ctx, "nf_conntrack_tcp_be_liberal", value) -} - -func (rct realConntracker) SetUDPTimeout(ctx context.Context, seconds int) error { - return rct.setIntSysCtl(ctx, "nf_conntrack_udp_timeout", seconds) -} - -func (rct realConntracker) SetUDPStreamTimeout(ctx context.Context, seconds int) error { - return rct.setIntSysCtl(ctx, "nf_conntrack_udp_timeout_stream", seconds) -} - -func (rct realConntracker) setIntSysCtl(ctx context.Context, name string, value int) error { - logger := klog.FromContext(ctx) - entry := "net/netfilter/" + name - - sys := sysctl.New() - if val, _ := sys.GetSysctl(entry); val != value { - logger.Info("Set sysctl", "entry", entry, "value", value) - if err := sys.SetSysctl(entry, value); err != nil { - return err - } - } - return nil -} - -// isSysFSWritable checks /proc/mounts to see whether sysfs is 'rw' or not. -func (rct realConntracker) isSysFSWritable(ctx context.Context) (bool, error) { - logger := klog.FromContext(ctx) - const permWritable = "rw" - const sysfsDevice = "sysfs" - m := mount.New("" /* default mount path */) - mountPoints, err := m.List() - if err != nil { - logger.Error(err, "Failed to list mount points") - return false, err - } - - for _, mountPoint := range mountPoints { - if mountPoint.Type != sysfsDevice { - continue - } - // Check whether sysfs is 'rw' - if len(mountPoint.Opts) > 0 && mountPoint.Opts[0] == permWritable { - return true, nil - } - logger.Error(nil, "Sysfs is not writable", "mountPoint", mountPoint, "mountOptions", mountPoint.Opts) - return false, errReadOnlySysFS - } - - return false, errors.New("no sysfs mounted") -} - -func readIntStringFile(filename string) (int, error) { - b, err := os.ReadFile(filename) - if err != nil { - return -1, err - } - return strconv.Atoi(strings.TrimSpace(string(b))) -} - -func writeIntStringFile(filename string, value int) error { - return os.WriteFile(filename, []byte(strconv.Itoa(value)), 0640) -} diff --git a/cmd/kube-proxy/app/init_linux.go b/cmd/kube-proxy/app/init_linux.go index 7e0ff787fe1d0..fb422d067c14c 100644 --- a/cmd/kube-proxy/app/init_linux.go +++ b/cmd/kube-proxy/app/init_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kube-proxy/app/init_other.go b/cmd/kube-proxy/app/init_other.go index a9fa789926c99..6282e00ea08f2 100644 --- a/cmd/kube-proxy/app/init_other.go +++ b/cmd/kube-proxy/app/init_other.go @@ -1,5 +1,4 @@ //go:build !windows && !linux -// +build !windows,!linux /* Copyright 2024 The Kubernetes Authors. diff --git a/cmd/kube-proxy/app/init_windows.go b/cmd/kube-proxy/app/init_windows.go index 8b450cd2ff598..2a0c28b22978a 100644 --- a/cmd/kube-proxy/app/init_windows.go +++ b/cmd/kube-proxy/app/init_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kube-proxy/app/options.go b/cmd/kube-proxy/app/options.go index ff7d349d2e9f5..64c143c06af10 100644 --- a/cmd/kube-proxy/app/options.go +++ b/cmd/kube-proxy/app/options.go @@ -32,6 +32,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" cliflag "k8s.io/component-base/cli/flag" logsapi "k8s.io/component-base/logs/api/v1" + metricsfeatures "k8s.io/component-base/metrics/features" zpagesfeatures "k8s.io/component-base/zpages/features" "k8s.io/klog/v2" "k8s.io/kube-proxy/config/v1alpha1" @@ -240,6 +241,9 @@ func (o *Options) Complete(fs *pflag.FlagSet) error { if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(o.config.FeatureGates); err != nil { return err } + // Propagate feature gate state to the metrics subsystem. This must be called + // after feature gates are set and before any histogram metrics are registered. + metricsfeatures.ApplyFeatureGates(utilfeature.DefaultFeatureGate) if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentFlagz) { nfs := cliflag.NamedFlagSets{ @@ -387,7 +391,7 @@ func (o *Options) Run(ctx context.Context) error { // Return an error when updated func (o *Options) runLoop(ctx context.Context) error { if o.watcher != nil { - o.watcher.Run() + o.watcher.Run(ctx) } // run the proxy in goroutine diff --git a/cmd/kube-proxy/app/server.go b/cmd/kube-proxy/app/server.go index 308e286e8fad2..2deae1f889392 100644 --- a/cmd/kube-proxy/app/server.go +++ b/cmd/kube-proxy/app/server.go @@ -65,12 +65,14 @@ import ( zpagesfeatures "k8s.io/component-base/zpages/features" nodeutil "k8s.io/component-helpers/node/util" "k8s.io/klog/v2" + configv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy/apis" kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" proxyconfigscheme "k8s.io/kubernetes/pkg/proxy/apis/config/scheme" + v1alpha1conversion "k8s.io/kubernetes/pkg/proxy/apis/config/v1alpha1" "k8s.io/kubernetes/pkg/proxy/config" "k8s.io/kubernetes/pkg/proxy/healthcheck" proxymetrics "k8s.io/kubernetes/pkg/proxy/metrics" @@ -186,11 +188,19 @@ func newProxyServer(ctx context.Context, config *kubeproxyconfig.KubeProxyConfig flagz: flagzReader, } + externalConfig := &configv1alpha1.KubeProxyConfiguration{} + if err := v1alpha1conversion.Convert_config_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(config, externalConfig, nil); err != nil { + return nil, fmt.Errorf("unable to convert configz: %w", err) + } + externalConfig.SetGroupVersionKind(configv1alpha1.SchemeGroupVersion.WithKind("KubeProxyConfiguration")) + cz, err := configz.New(kubeproxyconfig.GroupName) if err != nil { - return nil, fmt.Errorf("unable to register configz: %s", err) + return nil, fmt.Errorf("unable to register configz: %w", err) + } + if err := cz.Set(externalConfig); err != nil { + return nil, fmt.Errorf("unable to set configz: %w", err) } - cz.Set(config) if len(config.ShowHiddenMetricsForVersion) > 0 { metrics.SetShowHidden() @@ -570,13 +580,16 @@ func (s *ProxyServer) Run(ctx context.Context) error { return err } - labelSelector := labels.NewSelector() - labelSelector = labelSelector.Add(*noProxyName, *noHeadlessEndpoints) + labelSelectorNoProxyName := labels.NewSelector().Add(*noProxyName) + labelSelectorNoHeadlessEndpoints := labels.NewSelector().Add(*noHeadlessEndpoints) + + // Make informer that contains no filters + informerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client, s.Config.ConfigSyncPeriod.Duration) - // Make informers that filter out objects that want a non-default service proxy. - informerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client, s.Config.ConfigSyncPeriod.Duration, + // Make informers that filter out objects that do not contain a service.kubernetes.io/headless label + endpointSliceInformerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client, s.Config.ConfigSyncPeriod.Duration, informers.WithTweakListOptions(func(options *metav1.ListOptions) { - options.LabelSelector = labelSelector.String() + options.LabelSelector = labelSelectorNoHeadlessEndpoints.String() })) // Create configs (i.e. Watches for Services, EndpointSlices and ServiceCIDRs) @@ -586,14 +599,14 @@ func (s *ProxyServer) Run(ctx context.Context) error { // don't watch headless services for kube-proxy, they are proxied by DNS. serviceInformerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client, s.Config.ConfigSyncPeriod.Duration, informers.WithTweakListOptions(func(options *metav1.ListOptions) { - options.LabelSelector = labelSelector.String() + options.LabelSelector = labelSelectorNoProxyName.String() options.FieldSelector = fields.OneTermNotEqualSelector("spec.clusterIP", v1.ClusterIPNone).String() })) serviceConfig := config.NewServiceConfig(ctx, serviceInformerFactory.Core().V1().Services(), s.Config.ConfigSyncPeriod.Duration) serviceConfig.RegisterEventHandler(s.Proxier) go serviceConfig.Run(ctx.Done()) - endpointSliceConfig := config.NewEndpointSliceConfig(ctx, informerFactory.Discovery().V1().EndpointSlices(), s.Config.ConfigSyncPeriod.Duration) + endpointSliceConfig := config.NewEndpointSliceConfig(ctx, endpointSliceInformerFactory.Discovery().V1().EndpointSlices(), s.Config.ConfigSyncPeriod.Duration) endpointSliceConfig.RegisterEventHandler(s.Proxier) go endpointSliceConfig.Run(ctx.Done()) @@ -605,6 +618,7 @@ func (s *ProxyServer) Run(ctx context.Context) error { // This has to start after the calls to NewServiceConfig because that // function must configure its shared informer event handlers first. informerFactory.Start(wait.NeverStop) + endpointSliceInformerFactory.Start(wait.NeverStop) serviceInformerFactory.Start(wait.NeverStop) // hollow-proxy doesn't need node config, and we don't create nodeManager for hollow-proxy. diff --git a/cmd/kube-proxy/app/server_linux.go b/cmd/kube-proxy/app/server_linux.go index d75d597dda1cf..2cde573d5f24d 100644 --- a/cmd/kube-proxy/app/server_linux.go +++ b/cmd/kube-proxy/app/server_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. @@ -26,17 +25,14 @@ import ( "errors" "fmt" "os" - goruntime "runtime" - "time" - - "github.com/google/cadvisor/machine" - "github.com/google/cadvisor/utils/sysfs" + "strings" v1 "k8s.io/api/core/v1" utilsysctl "k8s.io/component-helpers/node/util/sysctl" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/proxy" proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config" + "k8s.io/kubernetes/pkg/proxy/conntrack" "k8s.io/kubernetes/pkg/proxy/iptables" "k8s.io/kubernetes/pkg/proxy/ipvs" utilipset "k8s.io/kubernetes/pkg/proxy/ipvs/ipset" @@ -69,10 +65,8 @@ func (o *Options) platformApplyDefaults(config *proxyconfigapi.KubeProxyConfigur // Proxier. It should fill in any platform-specific fields and perform other // platform-specific setup. func (s *ProxyServer) platformSetup(ctx context.Context) error { - ct := &realConntracker{} - err := s.setupConntrack(ctx, ct) - if err != nil { - return err + if err := conntrack.SetSysctls(ctx, &s.Config.Linux.Conntrack); err != nil { + return fmt.Errorf("could not set conntrack parameters from kube-proxy configuration: %w", err) } return nil @@ -113,7 +107,14 @@ func (s *ProxyServer) platformCheckSupported(ctx context.Context) (ipv4Supported // Check if the OS has IPv6 enabled, by verifying if the IPv6 interfaces are available _, errIPv6 := os.Stat("/proc/net/if_inet6") - if errIPv6 != nil { + if errIPv6 == nil { + // Also check that IPv6 hasn't been disabled + content, err := os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6") + if err == nil && strings.TrimSpace(string(content)) == "1" { + logger.Info("Kernel support disabled for family", "ipFamily", v1.IPv6Protocol) + ipv6Supported = false + } + } else { logger.Info("No kernel support for family", "ipFamily", v1.IPv6Protocol) ipv6Supported = false } @@ -296,90 +297,6 @@ func (s *ProxyServer) createProxier(ctx context.Context, config *proxyconfigapi. return proxier, nil } -func (s *ProxyServer) setupConntrack(ctx context.Context, ct Conntracker) error { - max, err := getConntrackMax(ctx, s.Config.Linux.Conntrack) - if err != nil { - return err - } - if max > 0 { - err := ct.SetMax(ctx, max) - if err != nil { - if err != errReadOnlySysFS { - return err - } - // errReadOnlySysFS means we ran into a known container runtim bug - // (https://issues.k8s.io/134108). For historical reasons we ignore - // this problem and just alert the admin that it occurred. - const message = "CRI error: /sys is read-only: " + - "cannot modify conntrack limits, problems may arise later (If running Docker, see docker issue #24000)" - s.Recorder.Eventf(s.NodeRef, nil, v1.EventTypeWarning, err.Error(), "StartKubeProxy", message) - } - } - - if s.Config.Linux.Conntrack.TCPEstablishedTimeout != nil && s.Config.Linux.Conntrack.TCPEstablishedTimeout.Duration > 0 { - timeout := int(s.Config.Linux.Conntrack.TCPEstablishedTimeout.Duration / time.Second) - if err := ct.SetTCPEstablishedTimeout(ctx, timeout); err != nil { - return err - } - } - - if s.Config.Linux.Conntrack.TCPCloseWaitTimeout != nil && s.Config.Linux.Conntrack.TCPCloseWaitTimeout.Duration > 0 { - timeout := int(s.Config.Linux.Conntrack.TCPCloseWaitTimeout.Duration / time.Second) - if err := ct.SetTCPCloseWaitTimeout(ctx, timeout); err != nil { - return err - } - } - - if s.Config.Linux.Conntrack.TCPBeLiberal { - if err := ct.SetTCPBeLiberal(ctx, 1); err != nil { - return err - } - } - - if s.Config.Linux.Conntrack.UDPTimeout.Duration > 0 { - timeout := int(s.Config.Linux.Conntrack.UDPTimeout.Duration / time.Second) - if err := ct.SetUDPTimeout(ctx, timeout); err != nil { - return err - } - } - - if s.Config.Linux.Conntrack.UDPStreamTimeout.Duration > 0 { - timeout := int(s.Config.Linux.Conntrack.UDPStreamTimeout.Duration / time.Second) - if err := ct.SetUDPStreamTimeout(ctx, timeout); err != nil { - return err - } - } - - return nil -} - -func getConntrackMax(ctx context.Context, config proxyconfigapi.KubeProxyConntrackConfiguration) (int, error) { - logger := klog.FromContext(ctx) - if config.MaxPerCore != nil && *config.MaxPerCore > 0 { - floor := 0 - if config.Min != nil { - floor = int(*config.Min) - } - scaled := int(*config.MaxPerCore) * detectNumCPU() - if scaled > floor { - logger.V(3).Info("GetConntrackMax: using scaled conntrack-max-per-core") - return scaled, nil - } - logger.V(3).Info("GetConntrackMax: using conntrack-min") - return floor, nil - } - return 0, nil -} - -func detectNumCPU() int { - // try get numCPU from /sys firstly due to a known issue (https://github.com/kubernetes/kubernetes/issues/99225) - _, numCPU, err := machine.GetTopology(sysfs.NewRealSysFs()) - if err != nil || numCPU < 1 { - return goruntime.NumCPU() - } - return numCPU -} - func getLocalDetectors(logger klog.Logger, primaryIPFamily v1.IPFamily, config *proxyconfigapi.KubeProxyConfiguration, nodePodCIDRs []string) map[v1.IPFamily]proxyutil.LocalTrafficDetector { localDetectors := map[v1.IPFamily]proxyutil.LocalTrafficDetector{ v1.IPv4Protocol: proxyutil.NewNoOpLocalDetector(), diff --git a/cmd/kube-proxy/app/server_linux_test.go b/cmd/kube-proxy/app/server_linux_test.go index 04d33569ecacc..3f0c2bd736610 100644 --- a/cmd/kube-proxy/app/server_linux_test.go +++ b/cmd/kube-proxy/app/server_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -20,25 +19,19 @@ limitations under the License. package app import ( - "context" - "errors" "fmt" "os" "path/filepath" "reflect" - goruntime "runtime" "strings" "testing" "time" - "github.com/google/go-cmp/cmp" "github.com/spf13/pflag" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config" proxyutil "k8s.io/kubernetes/pkg/proxy/util" "k8s.io/kubernetes/test/utils/ktesting" - "k8s.io/utils/ptr" ) func Test_platformApplyDefaults(t *testing.T) { @@ -555,214 +548,3 @@ detectLocalMode: "BridgeInterface"`) tearDown(file, tempDir) } } - -func TestGetConntrackMax(t *testing.T) { - ncores := goruntime.NumCPU() - testCases := []struct { - min int32 - maxPerCore int32 - expected int - err string - }{ - { - expected: 0, - }, - { - maxPerCore: 67890, // use this if Max is 0 - min: 1, // avoid 0 default - expected: 67890 * ncores, - }, - { - maxPerCore: 1, // ensure that Min is considered - min: 123456, - expected: 123456, - }, - { - maxPerCore: 0, // leave system setting - min: 123456, - expected: 0, - }, - } - - for i, tc := range testCases { - cfg := proxyconfigapi.KubeProxyConntrackConfiguration{ - Min: ptr.To(tc.min), - MaxPerCore: ptr.To(tc.maxPerCore), - } - _, ctx := ktesting.NewTestContext(t) - x, e := getConntrackMax(ctx, cfg) - if e != nil { - if tc.err == "" { - t.Errorf("[%d] unexpected error: %v", i, e) - } else if !strings.Contains(e.Error(), tc.err) { - t.Errorf("[%d] expected an error containing %q: %v", i, tc.err, e) - } - } else if x != tc.expected { - t.Errorf("[%d] expected %d, got %d", i, tc.expected, x) - } - } -} - -type fakeConntracker struct { - called []string - err error -} - -// SetMax value is calculated based on the number of CPUs by getConntrackMax() -func (fc *fakeConntracker) SetMax(ctx context.Context, max int) error { - fc.called = append(fc.called, "SetMax") - return fc.err -} -func (fc *fakeConntracker) SetTCPEstablishedTimeout(ctx context.Context, seconds int) error { - fc.called = append(fc.called, fmt.Sprintf("SetTCPEstablishedTimeout(%d)", seconds)) - return fc.err -} -func (fc *fakeConntracker) SetTCPCloseWaitTimeout(ctx context.Context, seconds int) error { - fc.called = append(fc.called, fmt.Sprintf("SetTCPCloseWaitTimeout(%d)", seconds)) - return fc.err -} -func (fc *fakeConntracker) SetTCPBeLiberal(ctx context.Context, value int) error { - fc.called = append(fc.called, fmt.Sprintf("SetTCPBeLiberal(%d)", value)) - return fc.err -} -func (fc *fakeConntracker) SetUDPTimeout(ctx context.Context, seconds int) error { - fc.called = append(fc.called, fmt.Sprintf("SetUDPTimeout(%d)", seconds)) - return fc.err -} -func (fc *fakeConntracker) SetUDPStreamTimeout(ctx context.Context, seconds int) error { - fc.called = append(fc.called, fmt.Sprintf("SetUDPStreamTimeout(%d)", seconds)) - return fc.err -} - -func TestSetupConntrack(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - tests := []struct { - name string - config proxyconfigapi.KubeProxyConntrackConfiguration - expect []string - conntrackErr error - wantErr bool - }{ - { - name: "do nothing if conntrack config is empty", - config: proxyconfigapi.KubeProxyConntrackConfiguration{}, - expect: nil, - }, - { - name: "SetMax is called if conntrack.maxPerCore is specified", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - MaxPerCore: ptr.To(int32(12)), - }, - expect: []string{"SetMax"}, - }, - { - name: "SetMax is not called if conntrack.maxPerCore is 0", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - MaxPerCore: ptr.To(int32(0)), - }, - expect: nil, - }, - { - name: "SetTCPEstablishedTimeout is called if conntrack.tcpEstablishedTimeout is specified", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - expect: []string{"SetTCPEstablishedTimeout(5)"}, - }, - { - name: "SetTCPEstablishedTimeout is not called if conntrack.tcpEstablishedTimeout is 0", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second}, - }, - expect: nil, - }, - { - name: "SetTCPCloseWaitTimeout is called if conntrack.tcpCloseWaitTimeout is specified", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - expect: []string{"SetTCPCloseWaitTimeout(5)"}, - }, - { - name: "SetTCPCloseWaitTimeout is not called if conntrack.tcpCloseWaitTimeout is 0", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second}, - }, - expect: nil, - }, - { - name: "SetTCPBeLiberal is called if conntrack.tcpBeLiberal is true", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPBeLiberal: true, - }, - expect: []string{"SetTCPBeLiberal(1)"}, - }, - { - name: "SetTCPBeLiberal is not called if conntrack.tcpBeLiberal is false", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPBeLiberal: false, - }, - expect: nil, - }, - { - name: "SetUDPTimeout is called if conntrack.udpTimeout is specified", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - expect: []string{"SetUDPTimeout(5)"}, - }, - { - name: "SetUDPTimeout is called if conntrack.udpTimeout is zero", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, - }, - expect: nil, - }, - { - name: "SetUDPStreamTimeout is called if conntrack.udpStreamTimeout is specified", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - expect: []string{"SetUDPStreamTimeout(5)"}, - }, - { - name: "SetUDPStreamTimeout is called if conntrack.udpStreamTimeout is zero", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - UDPStreamTimeout: metav1.Duration{Duration: 0 * time.Second}, - }, - expect: nil, - }, - { - name: "an error is returned if conntrack.SetTCPEstablishedTimeout fails", - config: proxyconfigapi.KubeProxyConntrackConfiguration{ - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - expect: []string{"SetTCPEstablishedTimeout(5)"}, - conntrackErr: errors.New("random error"), - wantErr: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - fc := &fakeConntracker{err: test.conntrackErr} - s := &ProxyServer{ - Config: &proxyconfigapi.KubeProxyConfiguration{ - Linux: proxyconfigapi.KubeProxyLinuxConfiguration{ - Conntrack: test.config, - }, - }, - } - err := s.setupConntrack(ctx, fc) - if test.wantErr && err == nil { - t.Errorf("Test %q: Expected error, got nil", test.name) - } - if !test.wantErr && err != nil { - t.Errorf("Test %q: Expected no error, got %v", test.name, err) - } - if !cmp.Equal(fc.called, test.expect) { - t.Errorf("Test %q: Expected conntrack calls: %v, got: %v", test.name, test.expect, fc.called) - } - }) - } -} diff --git a/cmd/kube-proxy/app/server_other.go b/cmd/kube-proxy/app/server_other.go index 331552f048bf6..f2a89d576c388 100644 --- a/cmd/kube-proxy/app/server_other.go +++ b/cmd/kube-proxy/app/server_other.go @@ -1,5 +1,4 @@ //go:build !windows && !linux -// +build !windows,!linux /* Copyright 2024 The Kubernetes Authors. diff --git a/cmd/kube-proxy/app/server_test.go b/cmd/kube-proxy/app/server_test.go index 4c85915d91330..7708893bf0e54 100644 --- a/cmd/kube-proxy/app/server_test.go +++ b/cmd/kube-proxy/app/server_test.go @@ -18,8 +18,10 @@ package app import ( "context" + "encoding/json" "errors" "fmt" + "io" "net" "net/http" "net/http/httptest" @@ -27,12 +29,21 @@ import ( "testing" "time" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/server/statusz" "k8s.io/apiserver/pkg/util/compatibility" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/configz" + featuregatetesting "k8s.io/component-base/featuregate/testing" + metricsfeatures "k8s.io/component-base/metrics/features" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" + kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" v1 "k8s.io/api/core/v1" kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" + proxymetrics "k8s.io/kubernetes/pkg/proxy/metrics" "k8s.io/kubernetes/test/utils/ktesting" netutils "k8s.io/utils/net" ) @@ -644,3 +655,84 @@ func TestStatuszRegistryReceivesListedPaths(t *testing.T) { wantSet.UnsortedList(), gotSet.UnsortedList(), body) } } + +func TestConfigz(t *testing.T) { + configz.Delete(kubeproxyconfig.GroupName) + cz, err := configz.New(kubeproxyconfig.GroupName) + if err != nil { + t.Fatalf("unable to register configz: %v", err) + } + defer configz.Delete(kubeproxyconfig.GroupName) + + externalConfig := &kubeproxyconfigv1alpha1.KubeProxyConfiguration{} + externalConfig.SetGroupVersionKind(kubeproxyconfigv1alpha1.SchemeGroupVersion.WithKind("KubeProxyConfiguration")) + if err := cz.Set(externalConfig); err != nil { + t.Fatalf("unable to set configz: %v", err) + } + + mux := http.NewServeMux() + configz.InstallHandler(mux) + s := httptest.NewServer(mux) + defer s.Close() + + resp, err := http.Get(s.URL + "/configz") + if err != nil { + t.Fatalf("unable to GET /configz: %v", err) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unable to read response body: %v", err) + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("want status 200, got %d", resp.StatusCode) + } + + var configzResp map[string]unstructured.Unstructured + if err := json.Unmarshal(body, &configzResp); err != nil { + t.Fatalf("failed to unmarshal configz: %v", err) + } + cfg, ok := configzResp[kubeproxyconfig.GroupName] + if !ok { + t.Fatalf("configz missing %q key", kubeproxyconfig.GroupName) + } + if cfg.GetAPIVersion() != "kubeproxy.config.k8s.io/v1alpha1" { + t.Errorf("unexpected APIVersion: %s", cfg.GetAPIVersion()) + } + if cfg.GetKind() != "KubeProxyConfiguration" { + t.Errorf("unexpected Kind: %s", cfg.GetKind()) + } + + // confirm that they expose public config type + var proxyConfig kubeproxyconfigv1alpha1.KubeProxyConfiguration + err = json.Unmarshal(body, &struct { + ComponentConfig *kubeproxyconfigv1alpha1.KubeProxyConfiguration `json:"kubeproxy.config.k8s.io"` + }{ComponentConfig: &proxyConfig}) + if err != nil { + t.Errorf("failed to deserialize into public config type: %v", err) + } +} + +func TestKubeProxyNativeHistogramMetrics(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, metricsfeatures.NativeHistograms, true) + metricsfeatures.ApplyFeatureGates(utilfeature.DefaultFeatureGate) + proxymetrics.RegisterMetrics(kubeproxyconfig.ProxyModeIPTables) + + proxymetrics.SyncProxyRulesLatency.WithLabelValues("4").Observe(0.001) + ts := httptest.NewServer(legacyregistry.Handler()) + defer ts.Close() + + histogramMetric := "kubeproxy_sync_proxy_rules_duration_seconds" + metrics, err := testutil.ScrapeMetricsProto(ts.URL+"/metrics", ts.Client()) + if err != nil { + t.Fatalf("failed to scrape metrics: %v", err) + } + mf, ok := metrics[histogramMetric] + if !ok { + t.Fatalf("metric %q not found in kube-proxy metrics endpoint", histogramMetric) + } + testutil.AssertHasNativeHistogram(t, mf, map[string]string{"ip_family": "4"}) +} diff --git a/cmd/kube-proxy/app/server_windows.go b/cmd/kube-proxy/app/server_windows.go index 05d1ddafd3d86..ab08abf23b0c0 100644 --- a/cmd/kube-proxy/app/server_windows.go +++ b/cmd/kube-proxy/app/server_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2014 The Kubernetes Authors. diff --git a/cmd/kube-scheduler/app/options/options.go b/cmd/kube-scheduler/app/options/options.go index 67bbd791c9d29..06ea926dd287c 100644 --- a/cmd/kube-scheduler/app/options/options.go +++ b/cmd/kube-scheduler/app/options/options.go @@ -47,6 +47,7 @@ import ( "k8s.io/component-base/logs" logsapi "k8s.io/component-base/logs/api/v1" "k8s.io/component-base/metrics" + metricsfeatures "k8s.io/component-base/metrics/features" zpagesfeatures "k8s.io/component-base/zpages/features" "k8s.io/klog/v2" schedulerappconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config" @@ -271,6 +272,7 @@ func (o *Options) ApplyTo(logger klog.Logger, c *schedulerappconfig.Config) erro return err } } + metricsfeatures.ApplyFeatureGates(utilfeature.DefaultFeatureGate) o.Metrics.Apply() // Apply value independently instead of using ApplyDeprecated() because it can't be configured via ComponentConfig. diff --git a/cmd/kube-scheduler/app/options/options_test.go b/cmd/kube-scheduler/app/options/options_test.go index 2e35f373f3271..e63ea35235c17 100644 --- a/cmd/kube-scheduler/app/options/options_test.go +++ b/cmd/kube-scheduler/app/options/options_test.go @@ -633,7 +633,8 @@ profiles: { Name: "DynamicResources", Args: &kubeschedulerconfig.DynamicResourcesArgs{ - FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, { @@ -749,7 +750,8 @@ profiles: { Name: "DynamicResources", Args: &kubeschedulerconfig.DynamicResourcesArgs{ - FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, { @@ -895,7 +897,8 @@ profiles: { Name: "DynamicResources", Args: &kubeschedulerconfig.DynamicResourcesArgs{ - FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, { diff --git a/cmd/kube-scheduler/app/server.go b/cmd/kube-scheduler/app/server.go index 0468a6921cd69..0aa6889103492 100644 --- a/cmd/kube-scheduler/app/server.go +++ b/cmd/kube-scheduler/app/server.go @@ -27,6 +27,7 @@ import ( "github.com/blang/semver/v4" "github.com/spf13/cobra" + coordinationv1 "k8s.io/api/coordination/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -63,12 +64,14 @@ import ( "k8s.io/component-base/version/verflag" zpagesfeatures "k8s.io/component-base/zpages/features" "k8s.io/klog/v2" + configv1 "k8s.io/kube-scheduler/config/v1" schedulerserverconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config" "k8s.io/kubernetes/cmd/kube-scheduler/app/options" kubefeatures "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler" kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config/latest" + conversionv1 "k8s.io/kubernetes/pkg/scheduler/apis/config/v1" "k8s.io/kubernetes/pkg/scheduler/framework/runtime" "k8s.io/kubernetes/pkg/scheduler/metrics/resources" "k8s.io/kubernetes/pkg/scheduler/profile" @@ -186,11 +189,17 @@ func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched * go cc.OpenShiftContext.PreferredHostHealthMonitor.Run(ctx) } + externalConfig := &configv1.KubeSchedulerConfiguration{} + if err := conversionv1.Convert_config_KubeSchedulerConfiguration_To_v1_KubeSchedulerConfiguration(&cc.ComponentConfig, externalConfig, nil); err != nil { + return fmt.Errorf("unable to convert configz: %w", err) + } + externalConfig.SetGroupVersionKind(configv1.SchemeGroupVersion.WithKind("KubeSchedulerConfiguration")) + // Configz registration. if cz, err := configz.New("componentconfig"); err != nil { - return fmt.Errorf("unable to register configz: %s", err) - } else { - cz.Set(cc.ComponentConfig) + return fmt.Errorf("unable to register configz: %w", err) + } else if err := cz.Set(externalConfig); err != nil { + return fmt.Errorf("unable to set configz: %w", err) } // Start events processing pipeline. @@ -409,7 +418,7 @@ func newEndpointsHandler(config *kubeschedulerconfig.KubeSchedulerConfiguration, } func getRecorderFactory(cc *schedulerserverconfig.CompletedConfig) profile.RecorderFactory { - return func(name string) events.EventRecorder { + return func(name string) events.EventRecorderLogger { return cc.EventBroadcaster.NewRecorder(name) } } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_unix.go b/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_unix.go index 7779e64021fea..f220ec70989f1 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_unix.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_windows.go b/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_windows.go index c2f424efc627a..045bf0e7dc3fd 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_windows.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta3/defaults_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_unix.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_unix.go index cb79b8d2e1869..5393eef110760 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_unix.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_windows.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_windows.go index 6926d7d8370c7..dd4ed1e725012 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_windows.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/util_unix.go b/cmd/kubeadm/app/apis/kubeadm/validation/util_unix.go index fbaac20ab33fe..4882315e29d1b 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/util_unix.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/util_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/util_windows.go b/cmd/kubeadm/app/apis/kubeadm/validation/util_windows.go index 86abd92642c42..1d6b5481b08f4 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/util_windows.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/util_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2022 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/cmd/certs_test.go b/cmd/kubeadm/app/cmd/certs_test.go index 7a556cd45e553..356b785bafa65 100644 --- a/cmd/kubeadm/app/cmd/certs_test.go +++ b/cmd/kubeadm/app/cmd/certs_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2018 The Kubernetes Authors. @@ -691,7 +690,6 @@ kubernetesVersion: %s`, } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { if tc.brokenCertName != "" { // remove the file to simulate a missing certificate diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index be8d486b531a6..95c9fd5314d3a 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" + "context" "fmt" "io" "os" @@ -311,7 +312,7 @@ func newCmdConfigMigrate(out io.Writer) *cobra.Command { // newCmdConfigValidate returns cobra.Command for the "kubeadm config validate" command func newCmdConfigValidate(out io.Writer) *cobra.Command { var cfgPath string - var allowExperimental bool + var allowDeprecated, allowExperimental bool cmd := &cobra.Command{ Use: "validate", @@ -336,7 +337,7 @@ func newCmdConfigValidate(out io.Writer) *cobra.Command { return err } - if err := configutil.ValidateConfig(cfgBytes, allowExperimental); err != nil { + if err := configutil.ValidateConfig(cfgBytes, allowDeprecated, allowExperimental); err != nil { return err } fmt.Fprintln(out, "ok") @@ -346,6 +347,7 @@ func newCmdConfigValidate(out io.Writer) *cobra.Command { Args: cobra.NoArgs, } options.AddConfigFlag(cmd.Flags(), &cfgPath) + cmd.Flags().BoolVar(&allowDeprecated, options.AllowDeprecatedAPI, false, "Allow validation of deprecated APIs.") cmd.Flags().BoolVar(&allowExperimental, options.AllowExperimentalAPI, false, "Allow validation of experimental, unreleased APIs.") return cmd } @@ -387,7 +389,7 @@ func newCmdConfigImagesPull() *cobra.Command { if err := containerRuntime.Connect(); err != nil { return err } - defer containerRuntime.Close() + defer containerRuntime.Close(context.Background()) return PullControlPlaneImages(containerRuntime, &internalcfg.ClusterConfiguration) }, Args: cobra.NoArgs, diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index a52b2952cf0ed..049c9b9cd2ca7 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -19,11 +19,9 @@ package cmd import ( "fmt" "io" - "net" "os" "path/filepath" "slices" - "strconv" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -93,6 +91,7 @@ type initData struct { skipTokenPrint bool dryRun bool kubeconfig *clientcmdapi.Config + kubeconfigOriginal *clientcmdapi.Config kubeconfigDir string kubeconfigPath string ignorePreflightErrors sets.Set[string] @@ -463,6 +462,9 @@ func (d *initData) CertificateDir() string { } // KubeConfig returns a kubeconfig after loading it from KubeConfigPath(). +// If the default kubeconfig path is used (admin.conf), instead of constructing +// a kubeconfig that points to the control plane endpoint, make it point to the localAPIEndpoint. +// This would allow 'kubeadm init' to only talk to the local kube-apiserver instance. func (d *initData) KubeConfig() (*clientcmdapi.Config, error) { if d.kubeconfig != nil { return d.kubeconfig, nil @@ -473,10 +475,26 @@ func (d *initData) KubeConfig() (*clientcmdapi.Config, error) { if err != nil { return nil, err } + d.kubeconfigOriginal = d.kubeconfig.DeepCopy() + + if d.kubeconfigPath == kubeadmconstants.GetAdminKubeConfigPath() { + kubeconfigutil.PointKubeConfigToLocalAPIEndpoint(d.kubeconfig, &d.Cfg().LocalAPIEndpoint) + } return d.kubeconfig, nil } +// KubeConfigOriginal returns the original kubeconfig loaded from file, without any modifications. +func (d *initData) KubeConfigOriginal() (*clientcmdapi.Config, error) { + if d.kubeconfigOriginal == nil { + if _, err := d.KubeConfig(); err != nil { + return nil, err + } + } + + return d.kubeconfigOriginal, nil +} + // KubeConfigDir returns the Kubernetes configuration directory or the temporary directory if DryRun is true. func (d *initData) KubeConfigDir() string { if d.dryRun { @@ -521,8 +539,12 @@ func (d *initData) OutputWriter() io.Writer { // getDryRunClient creates a fake client that answers some GET calls in order to be able to do the full init flow in dry-run mode. func getDryRunClient(d *initData) (clientset.Interface, error) { + kubeconfig, err := d.KubeConfig() + if err != nil { + return nil, err + } dryRun := apiclient.NewDryRun() - if err := dryRun.WithKubeConfigFile(d.KubeConfigPath()); err != nil { + if err := dryRun.WithKubeConfig(kubeconfig); err != nil { return nil, err } dryRun.WithDefaultMarshalFunction(). @@ -550,7 +572,11 @@ func (d *initData) Client() (clientset.Interface, error) { // and if the bootstrapping was not already done if !d.adminKubeConfigBootstrapped && isDefaultKubeConfigPath { // Call EnsureAdminClusterRoleBinding() to obtain a working client from admin.conf. - d.client, err = kubeconfigphase.EnsureAdminClusterRoleBinding(kubeadmconstants.KubernetesDir, nil) + d.client, err = kubeconfigphase.EnsureAdminClusterRoleBinding( + kubeadmconstants.KubernetesDir, + &d.Cfg().LocalAPIEndpoint, + nil, + ) if err != nil { return nil, errors.Wrapf(err, "could not bootstrap the admin user in file %s", kubeadmconstants.AdminKubeConfigFileName) } @@ -571,33 +597,9 @@ func (d *initData) Client() (clientset.Interface, error) { return d.client, nil } -// WaitControlPlaneClient returns a basic client used for the purpose of waiting -// for control plane components to report 'ok' on their respective health check endpoints. -// It uses the admin.conf as the base, but modifies it to point at the local API server instead -// of the control plane endpoint. -func (d *initData) WaitControlPlaneClient() (clientset.Interface, error) { - config, err := clientcmd.LoadFromFile(d.KubeConfigPath()) - if err != nil { - return nil, err - } - for _, v := range config.Clusters { - v.Server = fmt.Sprintf("https://%s", - net.JoinHostPort( - d.Cfg().LocalAPIEndpoint.AdvertiseAddress, - strconv.Itoa(int(d.Cfg().LocalAPIEndpoint.BindPort)), - ), - ) - } - client, err := kubeconfigutil.ToClientSet(config) - if err != nil { - return nil, err - } - return client, nil -} - // Tokens returns an array of token strings. func (d *initData) Tokens() []string { - tokens := []string{} + tokens := make([]string, 0, len(d.cfg.BootstrapTokens)) for _, bt := range d.cfg.BootstrapTokens { tokens = append(tokens, bt.Token.String()) } diff --git a/cmd/kubeadm/app/cmd/options/constant.go b/cmd/kubeadm/app/cmd/options/constant.go index 34334458617f2..44c5fb669d902 100644 --- a/cmd/kubeadm/app/cmd/options/constant.go +++ b/cmd/kubeadm/app/cmd/options/constant.go @@ -143,6 +143,9 @@ const ( // CleanupTmpDir flag indicates whether reset will cleanup the tmp dir CleanupTmpDir = "cleanup-tmp-dir" + // AllowDeprecatedAPI flag can be used to allow deprecated APIs + AllowDeprecatedAPI = "allow-deprecated-api" + // AllowExperimentalAPI flag can be used to allow experimental / work in progress APIs AllowExperimentalAPI = "allow-experimental-api" diff --git a/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go index 5fbef8bd5f343..2a103b9aeb30d 100644 --- a/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go +++ b/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go @@ -71,7 +71,7 @@ func runBootstrapToken(c workflow.RunData) error { if err != nil { return err } - kubeconfig, err := data.KubeConfig() + kubeconfig, err := data.KubeConfigOriginal() if err != nil { return err } @@ -108,6 +108,11 @@ func runBootstrapToken(c workflow.RunData) error { return err } + // Create RBAC rules that allow the API server kubelet client to access the kubelet API + if err := nodebootstraptokenphase.AllowAPIServerToAccessKubeletAPI(client); err != nil { + return errors.Wrap(err, "error allowing API server to access kubelet API") + } + // Create the cluster-info ConfigMap with the associated RBAC rules if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, kubeconfig); err != nil { return errors.Wrap(err, "error creating bootstrap ConfigMap") diff --git a/cmd/kubeadm/app/cmd/phases/init/certs.go b/cmd/kubeadm/app/cmd/phases/init/certs.go index 5d1df7453f09d..84280e52d9e83 100644 --- a/cmd/kubeadm/app/cmd/phases/init/certs.go +++ b/cmd/kubeadm/app/cmd/phases/init/certs.go @@ -18,6 +18,7 @@ package phases import ( "fmt" + "os" "path/filepath" "strings" @@ -216,20 +217,25 @@ func runCAPhase(ca *certsphase.KubeadmCert) func(c workflow.RunData) error { if cert, err := pkiutil.TryLoadCertFromDisk(data.CertificateDir(), ca.BaseName); err == nil { certsphase.CheckCertificatePeriodValidity(ca.BaseName, cert) + srcCertPath, srcKeyPath := pkiutil.PathsForCertAndKey(data.CertificateDir(), ca.BaseName) + dryRunCertPath, dryRunKeyPath := pkiutil.PathsForCertAndKey(data.CertificateWriteDir(), ca.BaseName) // If CA Cert existed while dryrun, copy CA Cert to dryrun dir for later use if data.DryRun() { - err := kubeadmutil.CopyFile(filepath.Join(data.CertificateDir(), kubeadmconstants.CACertName), filepath.Join(data.CertificateWriteDir(), kubeadmconstants.CACertName)) + if err := os.MkdirAll(filepath.Dir(dryRunCertPath), os.FileMode(0700)); err != nil { + return errors.Wrapf(err, "failed to create directory %s", filepath.Dir(dryRunCertPath)) + } + err := kubeadmutil.CopyFile(srcCertPath, dryRunCertPath) if err != nil { - return errors.Wrapf(err, "could not copy %s to dry run directory %s", kubeadmconstants.CACertName, data.CertificateWriteDir()) + return errors.Wrapf(err, "could not copy %s to dry run directory %s", fmt.Sprintf("%s.crt", ca.BaseName), data.CertificateWriteDir()) } } if _, err := pkiutil.TryLoadKeyFromDisk(data.CertificateDir(), ca.BaseName); err == nil { // If CA Key existed while dryrun, copy CA Key to dryrun dir for later use if data.DryRun() { - err := kubeadmutil.CopyFile(filepath.Join(data.CertificateDir(), kubeadmconstants.CAKeyName), filepath.Join(data.CertificateWriteDir(), kubeadmconstants.CAKeyName)) + err := kubeadmutil.CopyFile(srcKeyPath, dryRunKeyPath) if err != nil { - return errors.Wrapf(err, "could not copy %s to dry run directory %s", kubeadmconstants.CAKeyName, data.CertificateWriteDir()) + return errors.Wrapf(err, "could not copy %s to dry run directory %s", fmt.Sprintf("%s.key", ca.BaseName), data.CertificateWriteDir()) } } fmt.Printf("[certs] Using existing %s certificate authority\n", ca.BaseName) diff --git a/cmd/kubeadm/app/cmd/phases/init/certs_test.go b/cmd/kubeadm/app/cmd/phases/init/certs_test.go index 7ee76b6459c1a..fb8d3a97dc7ed 100644 --- a/cmd/kubeadm/app/cmd/phases/init/certs_test.go +++ b/cmd/kubeadm/app/cmd/phases/init/certs_test.go @@ -17,13 +17,17 @@ limitations under the License. package phases import ( + "os" + "path/filepath" "testing" "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" + certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" certstestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" pkiutiltesting "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil/testing" testutil "k8s.io/kubernetes/cmd/kubeadm/test" ) @@ -33,10 +37,19 @@ type testCertsData struct { cfg *kubeadmapi.InitConfiguration } +type testDryRunCertsData struct { + testCertsData + certificateDir string + certificateWriteDir string +} + func (t *testCertsData) Cfg() *kubeadmapi.InitConfiguration { return t.cfg } func (t *testCertsData) ExternalCA() bool { return false } func (t *testCertsData) CertificateDir() string { return t.cfg.CertificatesDir } func (t *testCertsData) CertificateWriteDir() string { return t.cfg.CertificatesDir } +func (t *testDryRunCertsData) DryRun() bool { return true } +func (t *testDryRunCertsData) CertificateDir() string { return t.certificateDir } +func (t *testDryRunCertsData) CertificateWriteDir() string { return t.certificateWriteDir } func TestCreateSparseCerts(t *testing.T) { for _, test := range certstestutil.GetSparseCertTestCases(t) { @@ -63,3 +76,45 @@ func TestCreateSparseCerts(t *testing.T) { }) } } + +func TestRunCAPhaseCopiesExistingCAFilesToDryRunDir(t *testing.T) { + for _, ca := range []*certsphase.KubeadmCert{ + certsphase.KubeadmCertRootCA(), + certsphase.KubeadmCertFrontProxyCA(), + certsphase.KubeadmCertEtcdCA(), + } { + t.Run(ca.Name, func(t *testing.T) { + pkiutiltesting.Reset() + + sourceDir := t.TempDir() + writeDir := t.TempDir() + caCert, caKey := certstestutil.SetupCertificateAuthority(t) + certPath, _ := pkiutil.PathsForCertAndKey(sourceDir, ca.BaseName) + if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0700)); err != nil { + t.Fatalf("failed to create source directory for %s: %v", ca.BaseName, err) + } + if err := pkiutil.WriteCertAndKey(sourceDir, ca.BaseName, caCert, caKey); err != nil { + t.Fatalf("failed to write source CA files for %s: %v", ca.BaseName, err) + } + + cfg := testutil.GetDefaultInternalConfig(t) + cfg.CertificatesDir = sourceDir + data := &testDryRunCertsData{ + testCertsData: testCertsData{cfg: cfg}, + certificateDir: sourceDir, + certificateWriteDir: writeDir, + } + + if err := runCAPhase(ca)(data); err != nil { + t.Fatalf("runCAPhase(%s) returned error: %v", ca.Name, err) + } + + if _, err := pkiutil.TryLoadCertFromDisk(writeDir, ca.BaseName); err != nil { + t.Fatalf("expected copied cert for %s in dry-run dir: %v", ca.BaseName, err) + } + if _, err := pkiutil.TryLoadKeyFromDisk(writeDir, ca.BaseName); err != nil { + t.Fatalf("expected copied key for %s in dry-run dir: %v", ca.BaseName, err) + } + }) + } +} diff --git a/cmd/kubeadm/app/cmd/phases/init/data.go b/cmd/kubeadm/app/cmd/phases/init/data.go index 6e7b8ba3a6b48..8e0a9d3c27b9d 100644 --- a/cmd/kubeadm/app/cmd/phases/init/data.go +++ b/cmd/kubeadm/app/cmd/phases/init/data.go @@ -40,6 +40,7 @@ type InitData interface { CertificateWriteDir() string CertificateDir() string KubeConfig() (*clientcmdapi.Config, error) + KubeConfigOriginal() (*clientcmdapi.Config, error) KubeConfigDir() string KubeConfigPath() string ManifestDir() string @@ -47,7 +48,34 @@ type InitData interface { ExternalCA() bool OutputWriter() io.Writer Client() (clientset.Interface, error) - WaitControlPlaneClient() (clientset.Interface, error) Tokens() []string PatchesDir() string } + +// a package local type for testing purposes. +type testInitData struct{} + +// testInitData must satisfy InitData. +var _ InitData = &testInitData{} + +func (t *testInitData) UploadCerts() bool { return false } +func (t *testInitData) CertificateKey() string { return "" } +func (t *testInitData) SetCertificateKey(key string) {} +func (t *testInitData) SkipCertificateKeyPrint() bool { return false } +func (t *testInitData) Cfg() *kubeadmapi.InitConfiguration { return nil } +func (t *testInitData) DryRun() bool { return false } +func (t *testInitData) SkipTokenPrint() bool { return false } +func (t *testInitData) IgnorePreflightErrors() sets.Set[string] { return nil } +func (t *testInitData) CertificateWriteDir() string { return "" } +func (t *testInitData) CertificateDir() string { return "" } +func (t *testInitData) KubeConfig() (*clientcmdapi.Config, error) { return nil, nil } +func (t *testInitData) KubeConfigOriginal() (*clientcmdapi.Config, error) { return nil, nil } +func (t *testInitData) KubeConfigDir() string { return "" } +func (t *testInitData) KubeConfigPath() string { return "" } +func (t *testInitData) ManifestDir() string { return "" } +func (t *testInitData) KubeletDir() string { return "" } +func (t *testInitData) ExternalCA() bool { return false } +func (t *testInitData) OutputWriter() io.Writer { return nil } +func (t *testInitData) Client() (clientset.Interface, error) { return nil, nil } +func (t *testInitData) Tokens() []string { return nil } +func (t *testInitData) PatchesDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/init/data_test.go b/cmd/kubeadm/app/cmd/phases/init/data_test.go deleted file mode 100644 index 8465021446ada..0000000000000 --- a/cmd/kubeadm/app/cmd/phases/init/data_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2019 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 phases - -import ( - "io" - - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -// a package local type for testing purposes. -type testInitData struct{} - -// testInitData must satisfy InitData. -var _ InitData = &testInitData{} - -func (t *testInitData) UploadCerts() bool { return false } -func (t *testInitData) CertificateKey() string { return "" } -func (t *testInitData) SetCertificateKey(key string) {} -func (t *testInitData) SkipCertificateKeyPrint() bool { return false } -func (t *testInitData) Cfg() *kubeadmapi.InitConfiguration { return nil } -func (t *testInitData) DryRun() bool { return false } -func (t *testInitData) SkipTokenPrint() bool { return false } -func (t *testInitData) IgnorePreflightErrors() sets.Set[string] { return nil } -func (t *testInitData) CertificateWriteDir() string { return "" } -func (t *testInitData) CertificateDir() string { return "" } -func (t *testInitData) KubeConfig() (*clientcmdapi.Config, error) { return nil, nil } -func (t *testInitData) KubeConfigDir() string { return "" } -func (t *testInitData) KubeConfigPath() string { return "" } -func (t *testInitData) ManifestDir() string { return "" } -func (t *testInitData) KubeletDir() string { return "" } -func (t *testInitData) ExternalCA() bool { return false } -func (t *testInitData) OutputWriter() io.Writer { return nil } -func (t *testInitData) Client() (clientset.Interface, error) { return nil, nil } -func (t *testInitData) WaitControlPlaneClient() (clientset.Interface, error) { return nil, nil } -func (t *testInitData) Tokens() []string { return nil } -func (t *testInitData) PatchesDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go b/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go index b3e514cfdc566..90f47b7178b56 100644 --- a/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go +++ b/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go @@ -35,7 +35,7 @@ import ( var ( kubeletFinalizePhaseExample = cmdutil.Examples(` - # Updates settings relevant to the kubelet after TLS bootstrap" + # Updates settings relevant to the kubelet after TLS bootstrap kubeadm init phase kubelet-finalize all --config `) ) diff --git a/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go b/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go index 5d68151926085..33317a5e284fc 100644 --- a/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go +++ b/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go @@ -28,9 +28,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" - patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" ) @@ -127,12 +125,6 @@ func runUploadKubeletConfig(c workflow.RunData) error { return errors.Wrap(err, "error creating kubelet configuration ConfigMap") } - if !features.Enabled(cfg.FeatureGates, features.NodeLocalCRISocket) { - klog.V(1).Infoln("[upload-config] Preserving the CRISocket information for the control-plane node") - if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil { - return errors.Wrap(err, "error writing CRISocket for this node") - } - } return nil } diff --git a/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go b/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go index c93d02aef1c56..db68251a2dfa1 100644 --- a/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go +++ b/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go @@ -58,7 +58,7 @@ func runWaitControlPlanePhase(c workflow.RunData) error { } } - client, err := data.WaitControlPlaneClient() + client, err := data.Client() if err != nil { return errors.Wrap(err, "cannot obtain client without bootstrap") } diff --git a/cmd/kubeadm/app/cmd/phases/join/controlplanejoin.go b/cmd/kubeadm/app/cmd/phases/join/controlplanejoin.go index c30df5f65779d..242d289402a2f 100644 --- a/cmd/kubeadm/app/cmd/phases/join/controlplanejoin.go +++ b/cmd/kubeadm/app/cmd/phases/join/controlplanejoin.go @@ -72,7 +72,6 @@ func NewControlPlaneJoinPhase() workflow.Phase { RunAllSiblings: true, ArgsValidator: cobra.NoArgs, }, - newEtcdLocalSubphase(), newMarkControlPlaneSubphase(), }, } @@ -90,22 +89,6 @@ func NewEtcdJoinPhase() workflow.Phase { } } -// TODO: Deprecated. Remove once ControlPlaneKubeletLocalMode is removed in 1.36. -// https://github.com/kubernetes/kubeadm/issues/2271 -func newEtcdLocalSubphase() workflow.Phase { - return workflow.Phase{ - Name: "etcd", - Short: "[DEPRECATED] Add a new local etcd member. Deprecated in favor of 'etcd-join' and will be removed in 1.36", - Run: runEtcdPhase, - InheritFlags: getControlPlaneJoinPhaseFlags("etcd"), - ArgsValidator: cobra.NoArgs, - Hidden: true, - RunIf: func(c workflow.RunData) (bool, error) { - return false, nil - }, - } -} - func newMarkControlPlaneSubphase() workflow.Phase { return workflow.Phase{ Name: "mark-control-plane", diff --git a/cmd/kubeadm/app/cmd/phases/join/data.go b/cmd/kubeadm/app/cmd/phases/join/data.go index 9e04d9032f9fe..197d018b75d8c 100644 --- a/cmd/kubeadm/app/cmd/phases/join/data.go +++ b/cmd/kubeadm/app/cmd/phases/join/data.go @@ -45,3 +45,24 @@ type JoinData interface { ManifestDir() string CertificateWriteDir() string } + +// a package local type for testing purposes. +type testJoinData struct{} + +// testJoinData must satisfy JoinData. +var _ JoinData = &testJoinData{} + +func (j *testJoinData) CertificateKey() string { return "" } +func (j *testJoinData) Cfg() *kubeadmapi.JoinConfiguration { return nil } +func (j *testJoinData) TLSBootstrapCfg() (*clientcmdapi.Config, error) { return nil, nil } +func (j *testJoinData) InitCfg() (*kubeadmapi.InitConfiguration, error) { return nil, nil } +func (j *testJoinData) Client() (clientset.Interface, error) { return nil, nil } +func (j *testJoinData) WaitControlPlaneClient() (clientset.Interface, error) { return nil, nil } +func (j *testJoinData) IgnorePreflightErrors() sets.Set[string] { return nil } +func (j *testJoinData) OutputWriter() io.Writer { return nil } +func (j *testJoinData) PatchesDir() string { return "" } +func (j *testJoinData) DryRun() bool { return false } +func (j *testJoinData) KubeConfigDir() string { return "" } +func (j *testJoinData) KubeletDir() string { return "" } +func (j *testJoinData) ManifestDir() string { return "" } +func (j *testJoinData) CertificateWriteDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/join/data_test.go b/cmd/kubeadm/app/cmd/phases/join/data_test.go deleted file mode 100644 index 5995acf7d7356..0000000000000 --- a/cmd/kubeadm/app/cmd/phases/join/data_test.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2019 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 phases - -import ( - "io" - - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -// a package local type for testing purposes. -type testJoinData struct{} - -// testJoinData must satisfy JoinData. -var _ JoinData = &testJoinData{} - -func (j *testJoinData) CertificateKey() string { return "" } -func (j *testJoinData) Cfg() *kubeadmapi.JoinConfiguration { return nil } -func (j *testJoinData) TLSBootstrapCfg() (*clientcmdapi.Config, error) { return nil, nil } -func (j *testJoinData) InitCfg() (*kubeadmapi.InitConfiguration, error) { return nil, nil } -func (j *testJoinData) Client() (clientset.Interface, error) { return nil, nil } -func (j *testJoinData) WaitControlPlaneClient() (clientset.Interface, error) { return nil, nil } -func (j *testJoinData) IgnorePreflightErrors() sets.Set[string] { return nil } -func (j *testJoinData) OutputWriter() io.Writer { return nil } -func (j *testJoinData) PatchesDir() string { return "" } -func (j *testJoinData) DryRun() bool { return false } -func (j *testJoinData) KubeConfigDir() string { return "" } -func (j *testJoinData) KubeletDir() string { return "" } -func (j *testJoinData) ManifestDir() string { return "" } -func (j *testJoinData) CertificateWriteDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/join/kubelet.go b/cmd/kubeadm/app/cmd/phases/join/kubelet.go index 822bf7a509ff9..7cb22a83abe88 100644 --- a/cmd/kubeadm/app/cmd/phases/join/kubelet.go +++ b/cmd/kubeadm/app/cmd/phases/join/kubelet.go @@ -40,7 +40,6 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" - patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" @@ -244,68 +243,50 @@ func runKubeletWaitBootstrapPhase(c workflow.RunData) (returnErr error) { if !ok { return errors.New("kubelet-start phase invoked with an invalid data struct") } + + if data.DryRun() { + fmt.Println("[kubelet-wait] Would wait for the kubelet to be bootstrapped") + return nil + } + cfg := data.Cfg() initCfg, err := data.InitCfg() if err != nil { return err } - var client clientset.Interface - - if data.DryRun() { - fmt.Println("[kubelet-wait] Would wait for the kubelet to be bootstrapped") - - // Use the dry-run client. - if client, err = data.Client(); err != nil { - return errors.Wrap(err, "could not get client for dry-run") - } - } else { - bootstrapKubeConfigFile := filepath.Join(data.KubeConfigDir(), kubeadmconstants.KubeletBootstrapKubeConfigFileName) - // Deletes the bootstrapKubeConfigFile, so the credential used for TLS bootstrap is removed from disk - defer func() { - _ = os.Remove(bootstrapKubeConfigFile) - }() - - // Apply patches to the in-memory kubelet configuration so that any configuration changes like kubelet healthz - // address and port options are respected during the wait below. WriteConfigToDisk already applied patches to - // the kubelet.yaml written to disk. This should be done after WriteConfigToDisk because both use the same config - // in memory and we don't want patches to be applied two times to the config that is written to disk. - if err := kubeletphase.ApplyPatchesToConfig(&initCfg.ClusterConfiguration, data.PatchesDir()); err != nil { - return errors.Wrap(err, "could not apply patches to the in-memory kubelet configuration") - } - - // Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf - // Wait for the kubelet to create the /etc/kubernetes/kubelet.conf kubeconfig file. If this process - // times out, display a somewhat user-friendly message. - waiter := apiclient.NewKubeWaiter(nil, 0, os.Stdout) - waiter.SetTimeout(cfg.Timeouts.KubeletHealthCheck.Duration) - kubeletConfig := initCfg.ClusterConfiguration.ComponentConfigs[componentconfigs.KubeletGroup].Get() - kubeletConfigTyped, ok := kubeletConfig.(*kubeletconfig.KubeletConfiguration) - if !ok { - return errors.New("could not convert the KubeletConfiguration to a typed object") - } - if err := waiter.WaitForKubelet(kubeletConfigTyped.HealthzBindAddress, *kubeletConfigTyped.HealthzPort); err != nil { - apiclient.PrintKubeletErrorHelpScreen(data.OutputWriter()) - return errors.Wrap(err, "failed while waiting for the kubelet to start") - } - - if err := waitForTLSBootstrappedClient(cfg.Timeouts.TLSBootstrap.Duration); err != nil { - apiclient.PrintKubeletErrorHelpScreen(data.OutputWriter()) - return errors.Wrap(err, "failed while waiting for TLS bootstrap") - } + bootstrapKubeConfigFile := filepath.Join(data.KubeConfigDir(), kubeadmconstants.KubeletBootstrapKubeConfigFileName) + // Deletes the bootstrapKubeConfigFile, so the credential used for TLS bootstrap is removed from disk + defer func() { + _ = os.Remove(bootstrapKubeConfigFile) + }() + + // Apply patches to the in-memory kubelet configuration so that any configuration changes like kubelet healthz + // address and port options are respected during the wait below. WriteConfigToDisk already applied patches to + // the kubelet.yaml written to disk. This should be done after WriteConfigToDisk because both use the same config + // in memory and we don't want patches to be applied two times to the config that is written to disk. + if err := kubeletphase.ApplyPatchesToConfig(&initCfg.ClusterConfiguration, data.PatchesDir()); err != nil { + return errors.Wrap(err, "could not apply patches to the in-memory kubelet configuration") + } - // When we know the /etc/kubernetes/kubelet.conf file is available, get the client - client, err = kubeconfigutil.ClientSetFromFile(kubeadmconstants.GetKubeletKubeConfigPath()) - if err != nil { - return err - } + // Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf + // Wait for the kubelet to create the /etc/kubernetes/kubelet.conf kubeconfig file. If this process + // times out, display a somewhat user-friendly message. + waiter := apiclient.NewKubeWaiter(nil, 0, os.Stdout) + waiter.SetTimeout(cfg.Timeouts.KubeletHealthCheck.Duration) + kubeletConfig := initCfg.ClusterConfiguration.ComponentConfigs[componentconfigs.KubeletGroup].Get() + kubeletConfigTyped, ok := kubeletConfig.(*kubeletconfig.KubeletConfiguration) + if !ok { + return errors.New("could not convert the KubeletConfiguration to a typed object") + } + if err := waiter.WaitForKubelet(kubeletConfigTyped.HealthzBindAddress, *kubeletConfigTyped.HealthzPort); err != nil { + apiclient.PrintKubeletErrorHelpScreen(data.OutputWriter()) + return errors.Wrap(err, "failed while waiting for the kubelet to start") } - if !features.Enabled(initCfg.ClusterConfiguration.FeatureGates, features.NodeLocalCRISocket) { - klog.V(1).Infoln("[kubelet-start] preserving the crisocket information for the node") - if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil { - return errors.Wrap(err, "error writing CRISocket for this node") - } + if err := waitForTLSBootstrappedClient(cfg.Timeouts.TLSBootstrap.Duration); err != nil { + apiclient.PrintKubeletErrorHelpScreen(data.OutputWriter()) + return errors.Wrap(err, "failed while waiting for TLS bootstrap") } return nil diff --git a/cmd/kubeadm/app/cmd/phases/reset/cleanupnode.go b/cmd/kubeadm/app/cmd/phases/reset/cleanupnode.go index 8a163a1686e55..31ff487a666af 100644 --- a/cmd/kubeadm/app/cmd/phases/reset/cleanupnode.go +++ b/cmd/kubeadm/app/cmd/phases/reset/cleanupnode.go @@ -17,6 +17,7 @@ limitations under the License. package phases import ( + "context" "fmt" "io" "os" @@ -138,7 +139,7 @@ func removeContainers(criSocketPath string) error { if err := containerRuntime.Connect(); err != nil { return err } - defer containerRuntime.Close() + defer containerRuntime.Close(context.Background()) containers, err := containerRuntime.ListKubeContainers() if err != nil { diff --git a/cmd/kubeadm/app/cmd/phases/reset/data.go b/cmd/kubeadm/app/cmd/phases/reset/data.go index 260a899ba4a2e..97bc15bb31ab7 100644 --- a/cmd/kubeadm/app/cmd/phases/reset/data.go +++ b/cmd/kubeadm/app/cmd/phases/reset/data.go @@ -39,3 +39,20 @@ type resetData interface { CRISocketPath() string CleanupTmpDir() bool } + +// a package local type for testing purposes. +type testData struct{} + +// testData must satisfy resetData. +var _ resetData = &testData{} + +func (t *testData) ForceReset() bool { return false } +func (t *testData) InputReader() io.Reader { return nil } +func (t *testData) IgnorePreflightErrors() sets.Set[string] { return nil } +func (t *testData) Cfg() *kubeadmapi.InitConfiguration { return nil } +func (t *testData) DryRun() bool { return false } +func (t *testData) Client() clientset.Interface { return nil } +func (t *testData) CertificatesDir() string { return "" } +func (t *testData) CRISocketPath() string { return "" } +func (t *testData) CleanupTmpDir() bool { return false } +func (t *testData) ResetCfg() *kubeadmapi.ResetConfiguration { return nil } diff --git a/cmd/kubeadm/app/cmd/phases/reset/data_test.go b/cmd/kubeadm/app/cmd/phases/reset/data_test.go deleted file mode 100644 index 470af26113817..0000000000000 --- a/cmd/kubeadm/app/cmd/phases/reset/data_test.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2022 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 phases - -import ( - "io" - - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -// a package local type for testing purposes. -type testData struct{} - -// testData must satisfy resetData. -var _ resetData = &testData{} - -func (t *testData) ForceReset() bool { return false } -func (t *testData) InputReader() io.Reader { return nil } -func (t *testData) IgnorePreflightErrors() sets.Set[string] { return nil } -func (t *testData) Cfg() *kubeadmapi.InitConfiguration { return nil } -func (t *testData) DryRun() bool { return false } -func (t *testData) Client() clientset.Interface { return nil } -func (t *testData) CertificatesDir() string { return "" } -func (t *testData) CRISocketPath() string { return "" } -func (t *testData) CleanupTmpDir() bool { return false } -func (t *testData) ResetCfg() *kubeadmapi.ResetConfiguration { return nil } diff --git a/cmd/kubeadm/app/cmd/phases/reset/removeetcdmember_test.go b/cmd/kubeadm/app/cmd/phases/reset/removeetcdmember_test.go index 49202f12b7e63..de6efdd8f0c27 100644 --- a/cmd/kubeadm/app/cmd/phases/reset/removeetcdmember_test.go +++ b/cmd/kubeadm/app/cmd/phases/reset/removeetcdmember_test.go @@ -17,6 +17,7 @@ limitations under the License. package phases import ( + _ "embed" "os" "path/filepath" "testing" @@ -26,34 +27,16 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) -const ( - etcdPod = `apiVersion: v1 -kind: Pod -metadata: -spec: - volumes: - - hostPath: - path: /path/to/etcd - type: DirectoryOrCreate - name: etcd-data - - hostPath: - path: /etc/kubernetes/pki/etcd - type: DirectoryOrCreate - name: etcd-certs` - - etcdPodWithoutDataVolume = `apiVersion: v1 -kind: Pod -metadata: -spec: - volumes: - - hostPath: - path: /etc/kubernetes/pki/etcd - type: DirectoryOrCreate - name: etcd-certs` - - etcdPodInvalid = `invalid pod` +var ( + //go:embed testdata/etcd-pod.yaml + etcdPod string + + //go:embed testdata/etcd-pod-without-data-volume.yaml + etcdPodWithoutDataVolume string ) +const etcdPodInvalid = `invalid pod` + func TestGetEtcdDataDir(t *testing.T) { tests := map[string]struct { dataDir string diff --git a/cmd/kubeadm/app/cmd/phases/reset/testdata/etcd-pod-without-data-volume.yaml b/cmd/kubeadm/app/cmd/phases/reset/testdata/etcd-pod-without-data-volume.yaml new file mode 100644 index 0000000000000..fe6d8d721b2d6 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/reset/testdata/etcd-pod-without-data-volume.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: +spec: + volumes: + - hostPath: + path: /etc/kubernetes/pki/etcd + type: DirectoryOrCreate + name: etcd-certs diff --git a/cmd/kubeadm/app/cmd/phases/reset/testdata/etcd-pod.yaml b/cmd/kubeadm/app/cmd/phases/reset/testdata/etcd-pod.yaml new file mode 100644 index 0000000000000..f3d13aea88d14 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/reset/testdata/etcd-pod.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: +spec: + volumes: + - hostPath: + path: /path/to/etcd + type: DirectoryOrCreate + name: etcd-data + - hostPath: + path: /etc/kubernetes/pki/etcd + type: DirectoryOrCreate + name: etcd-certs diff --git a/cmd/kubeadm/app/cmd/phases/reset/unmount.go b/cmd/kubeadm/app/cmd/phases/reset/unmount.go index 42e8c54376226..d9e2fd8a41fcf 100644 --- a/cmd/kubeadm/app/cmd/phases/reset/unmount.go +++ b/cmd/kubeadm/app/cmd/phases/reset/unmount.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/cmd/phases/reset/unmount_linux.go b/cmd/kubeadm/app/cmd/phases/reset/unmount_linux.go index 7cab7a643113a..fed46c36ac657 100644 --- a/cmd/kubeadm/app/cmd/phases/reset/unmount_linux.go +++ b/cmd/kubeadm/app/cmd/phases/reset/unmount_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/cmd/phases/reset/unmount_linux_test.go b/cmd/kubeadm/app/cmd/phases/reset/unmount_linux_test.go index f42dff5118b1e..b56d46b044643 100644 --- a/cmd/kubeadm/app/cmd/phases/reset/unmount_linux_test.go +++ b/cmd/kubeadm/app/cmd/phases/reset/unmount_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2023 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/apply/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/upgrade/apply/bootstraptoken.go index e0c40c09e4adc..58aec1ddc2e43 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/apply/bootstraptoken.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/apply/bootstraptoken.go @@ -79,6 +79,11 @@ func runBootstrapToken(c workflow.RunData) error { errs = append(errs, err) } + // Create/update RBAC rules that allow the API server kubelet client to access the kubelet API + if err := nodebootstraptoken.AllowAPIServerToAccessKubeletAPI(client); err != nil { + errs = append(errs, err) + } + // Create/update RBAC rules that makes the cluster-info ConfigMap reachable if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil { errs = append(errs, err) diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/apply/uploadconfig.go b/cmd/kubeadm/app/cmd/phases/upgrade/apply/uploadconfig.go index 52a56c1181225..da036d765421f 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/apply/uploadconfig.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/apply/uploadconfig.go @@ -26,7 +26,6 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" - "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" @@ -107,15 +106,9 @@ func runUploadKubeletConfig(c workflow.RunData) error { return errors.Wrap(err, "error creating kubelet configuration ConfigMap") } - if !features.Enabled(cfg.ClusterConfiguration.FeatureGates, features.NodeLocalCRISocket) { - klog.V(1).Infoln("[upgrade/upload-config] Preserving the CRISocket information for this control-plane node") - if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil { - return errors.Wrap(err, "error writing CRISocket for this node") - } - } else { - if err := patchnodephase.RemoveCRISocketAnnotation(client, cfg.NodeRegistration.Name); err != nil { - return err - } + // TODO Remove once NodeLocalCRISocket is removed in 1.37. + if err := patchnodephase.RemoveCRISocketAnnotation(client, cfg.NodeRegistration.Name); err != nil { + return err } return nil diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/data.go b/cmd/kubeadm/app/cmd/phases/upgrade/data.go index 016199c90f67d..3c06df2fb4e4e 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/data.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/data.go @@ -41,3 +41,22 @@ type Data interface { KubeConfigDir() string KubeletDir() string } + +// a package local type for testing purposes. +type testData struct{} + +// testData must satisfy Data. +var _ Data = &testData{} + +func (t *testData) EtcdUpgrade() bool { return false } +func (t *testData) RenewCerts() bool { return false } +func (t *testData) DryRun() bool { return false } +func (t *testData) Cfg() *kubeadmapi.UpgradeConfiguration { return nil } +func (t *testData) InitCfg() *kubeadmapi.InitConfiguration { return nil } +func (t *testData) IsControlPlaneNode() bool { return false } +func (t *testData) Client() clientset.Interface { return nil } +func (t *testData) IgnorePreflightErrors() sets.Set[string] { return nil } +func (t *testData) PatchesDir() string { return "" } +func (t *testData) OutputWriter() io.Writer { return nil } +func (t *testData) KubeConfigDir() string { return "" } +func (t *testData) KubeletDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/data_test.go b/cmd/kubeadm/app/cmd/phases/upgrade/data_test.go deleted file mode 100644 index 1684b20679472..0000000000000 --- a/cmd/kubeadm/app/cmd/phases/upgrade/data_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2024 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 upgrade - -import ( - "io" - - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -// a package local type for testing purposes. -type testData struct{} - -// testData must satisfy Data. -var _ Data = &testData{} - -func (t *testData) EtcdUpgrade() bool { return false } -func (t *testData) RenewCerts() bool { return false } -func (t *testData) DryRun() bool { return false } -func (t *testData) Cfg() *kubeadmapi.UpgradeConfiguration { return nil } -func (t *testData) InitCfg() *kubeadmapi.InitConfiguration { return nil } -func (t *testData) IsControlPlaneNode() bool { return false } -func (t *testData) Client() clientset.Interface { return nil } -func (t *testData) IgnorePreflightErrors() sets.Set[string] { return nil } -func (t *testData) PatchesDir() string { return "" } -func (t *testData) OutputWriter() io.Writer { return nil } -func (t *testData) KubeConfigDir() string { return "" } -func (t *testData) KubeletDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/postupgrade.go b/cmd/kubeadm/app/cmd/phases/upgrade/postupgrade.go index cdb05a42c0868..5cb63af22808b 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/postupgrade.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/postupgrade.go @@ -48,11 +48,7 @@ func runPostUpgrade(c workflow.RunData) error { // Rewrite the kubelet env file without unwanted flags to disk and print the remaining flags instead of dry-running. // If not dry-running, the kubelet env file will be backed up to the /etc/kubernetes/tmp/ dir, so that it could be // recovered if anything goes wrong. - unwantedFlags := []string{ - // The flag has been deprecated and no longer served a purpose in the kubelet as the logic was migrated to CRI. - // TODO: Remove it from this list in 1.36: https://github.com/kubernetes/kubeadm/issues/3108 - "pod-infra-container-image", - } + unwantedFlags := []string{} err := upgrade.RemoveKubeletArgsFromFile(data.KubeletDir(), data.KubeConfigDir(), unwantedFlags, data.DryRun(), data.OutputWriter()) if err != nil { return err diff --git a/cmd/kubeadm/app/cmd/reset.go b/cmd/kubeadm/app/cmd/reset.go index 23f2bf62a1c56..66a64e2cdf830 100644 --- a/cmd/kubeadm/app/cmd/reset.go +++ b/cmd/kubeadm/app/cmd/reset.go @@ -167,7 +167,7 @@ func newResetData(cmd *cobra.Command, opts *resetOptions, in io.Reader, out io.W certificatesDir = opts.externalcfg.CertificatesDir } else if len(resetCfg.CertificatesDir) > 0 { // configured in the ResetConfiguration certificatesDir = resetCfg.CertificatesDir - } else if len(initCfg.ClusterConfiguration.CertificatesDir) > 0 { // fetch from cluster + } else if initCfg != nil && len(initCfg.ClusterConfiguration.CertificatesDir) > 0 { // fetch from cluster certificatesDir = initCfg.ClusterConfiguration.CertificatesDir } diff --git a/cmd/kubeadm/app/cmd/testdata/token-config.yaml b/cmd/kubeadm/app/cmd/testdata/token-config.yaml new file mode 100644 index 0000000000000..b5abda9b83609 --- /dev/null +++ b/cmd/kubeadm/app/cmd/testdata/token-config.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: + server: localhost:8000 + name: prod +contexts: +- context: + cluster: prod + namespace: default + user: default-service-account + name: default +current-context: default +kind: Config +users: +- name: kubernetes-admin + user: + client-certificate-data: + client-key-data: diff --git a/cmd/kubeadm/app/cmd/token_test.go b/cmd/kubeadm/app/cmd/token_test.go index b3f0a12fc0922..4218648adc35e 100644 --- a/cmd/kubeadm/app/cmd/token_test.go +++ b/cmd/kubeadm/app/cmd/token_test.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" + _ "embed" "os" "path/filepath" "regexp" @@ -38,29 +39,10 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/output" ) -const ( - tokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$" - testConfigToken = `apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: - server: localhost:8000 - name: prod -contexts: -- context: - cluster: prod - namespace: default - user: default-service-account - name: default -current-context: default -kind: Config -users: -- name: kubernetes-admin - user: - client-certificate-data: - client-key-data: -` -) +//go:embed testdata/token-config.yaml +var testConfigToken string + +const tokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$" func TestRunGenerateToken(t *testing.T) { var buf bytes.Buffer diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index ae9c7a2dc7fce..24f3047e6adc5 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -18,6 +18,7 @@ package upgrade import ( "bytes" + _ "embed" "fmt" "os" "path/filepath" @@ -32,25 +33,8 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/output" ) -const testConfigToken = `apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: - server: localhost:8000 - name: prod -contexts: -- context: - cluster: prod - namespace: default - user: default-service-account - name: default -current-context: default -kind: Config -users: -- name: kubernetes-admin - user: - client-certificate-data: -` +//go:embed testdata/config-token.yaml +var testConfigToken string func TestEnforceRequirements(t *testing.T) { tmpDir := t.TempDir() diff --git a/cmd/kubeadm/app/cmd/upgrade/plan.go b/cmd/kubeadm/app/cmd/upgrade/plan.go index af73aec8e3b77..4c7283a9a0a23 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan.go @@ -234,7 +234,7 @@ func genAvailableUpgrade(up *upgrade.Upgrade, etcdUpgrade bool) outputapiv1alpha // sortedSliceFromStringStringArrayMap returns a slice of the keys in the map sorted alphabetically func sortedSliceFromStringStringArrayMap(strMap map[string][]string) []string { - strSlice := []string{} + strSlice := make([]string, 0, len(strMap)) for k := range strMap { strSlice = append(strSlice, k) } diff --git a/cmd/kubeadm/app/cmd/upgrade/testdata/config-token.yaml b/cmd/kubeadm/app/cmd/upgrade/testdata/config-token.yaml new file mode 100644 index 0000000000000..74a571a335d09 --- /dev/null +++ b/cmd/kubeadm/app/cmd/upgrade/testdata/config-token.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: + server: localhost:8000 + name: prod +contexts: +- context: + cluster: prod + namespace: default + user: default-service-account + name: default +current-context: default +kind: Config +preferences: {} +users: +- name: kubernetes-admin + user: + client-certificate-data: diff --git a/cmd/kubeadm/app/cmd/util_other_test.go b/cmd/kubeadm/app/cmd/util_other_test.go index d2a2ab4183221..121e0412afe99 100644 --- a/cmd/kubeadm/app/cmd/util_other_test.go +++ b/cmd/kubeadm/app/cmd/util_other_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/cmd/util_windows_test.go b/cmd/kubeadm/app/cmd/util_windows_test.go index 1eaa8e4bf5f01..6e7e174ffc3cf 100644 --- a/cmd/kubeadm/app/cmd/util_windows_test.go +++ b/cmd/kubeadm/app/cmd/util_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/componentconfigs/kubelet_unix.go b/cmd/kubeadm/app/componentconfigs/kubelet_unix.go index 932deb3d32abb..05849cbf8a10c 100644 --- a/cmd/kubeadm/app/componentconfigs/kubelet_unix.go +++ b/cmd/kubeadm/app/componentconfigs/kubelet_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/componentconfigs/kubelet_unix_test.go b/cmd/kubeadm/app/componentconfigs/kubelet_unix_test.go index c706bb8c534d8..aad3a69d60854 100644 --- a/cmd/kubeadm/app/componentconfigs/kubelet_unix_test.go +++ b/cmd/kubeadm/app/componentconfigs/kubelet_unix_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2024 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/componentconfigs/kubelet_windows.go b/cmd/kubeadm/app/componentconfigs/kubelet_windows.go index 6ef641082d020..87b877708b2da 100644 --- a/cmd/kubeadm/app/componentconfigs/kubelet_windows.go +++ b/cmd/kubeadm/app/componentconfigs/kubelet_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/componentconfigs/kubelet_windows_test.go b/cmd/kubeadm/app/componentconfigs/kubelet_windows_test.go index ea6c45c464110..8023ada185df8 100644 --- a/cmd/kubeadm/app/componentconfigs/kubelet_windows_test.go +++ b/cmd/kubeadm/app/componentconfigs/kubelet_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index fee98ece17c72..26389dfb7c0df 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -210,6 +210,11 @@ const ( // built-in ClusterRole. ClusterAdminsGroupAndClusterRoleBinding = "kubeadm:cluster-admins" + // KubeletAPIAdminClusterRoleBindingName is the name of the ClusterRoleBinding for the apiserver kubelet client + KubeletAPIAdminClusterRoleBindingName = "kubeadm:apiserver-kubelet-client" + // KubeletAPIAdminClusterRoleName is the name of the built-in ClusterRole for kubelet API access + KubeletAPIAdminClusterRoleName = "system:kubelet-api-admin" + // KubernetesAPICallTimeout specifies how long kubeadm should wait for API calls KubernetesAPICallTimeout = 1 * time.Minute // KubernetesAPICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation @@ -326,7 +331,7 @@ const ( MinExternalEtcdVersion = "3.5.24-0" // DefaultEtcdVersion indicates the default etcd version that kubeadm uses - DefaultEtcdVersion = "3.6.6-0" + DefaultEtcdVersion = "3.6.8-0" // Etcd defines variable used internally when referring to etcd component Etcd = "etcd" @@ -364,7 +369,7 @@ const ( CoreDNSImageName = "coredns" // CoreDNSVersion is the version of CoreDNS to be deployed if it is used - CoreDNSVersion = "v1.13.1" + CoreDNSVersion = "v1.14.2" // ClusterConfigurationKind is the string kind value for the ClusterConfiguration struct ClusterConfigurationKind = "ClusterConfiguration" @@ -442,7 +447,7 @@ const ( ModeNode string = "Node" // PauseVersion indicates the default pause image version for kubeadm - PauseVersion = "3.10.1" + PauseVersion = "3.10.2" // CgroupDriverSystemd holds the systemd driver type CgroupDriverSystemd = "systemd" @@ -478,12 +483,6 @@ var ( Effect: v1.TaintEffectNoSchedule, } - // ControlPlaneToleration is the toleration to apply on the PodSpec for being able to run that Pod on the control-plane - ControlPlaneToleration = v1.Toleration{ - Key: LabelNodeRoleControlPlane, - Effect: v1.TaintEffectNoSchedule, - } - // ControlPlaneComponents defines the control-plane component names ControlPlaneComponents = []string{KubeAPIServer, KubeControllerManager, KubeScheduler} @@ -496,16 +495,27 @@ var ( // CurrentKubernetesVersion specifies current Kubernetes version supported by kubeadm CurrentKubernetesVersion = getSkewedKubernetesVersion(0) - // SupportedEtcdVersion lists officially supported etcd versions with corresponding Kubernetes releases + // SupportedEtcdVersion lists officially supported etcd versions with corresponding + // Kubernetes releases. + // // If you are updating the versions in this map, make sure to also update: // - MinExternalEtcdVersion: with the minimum etcd version from this map. - // - DefaultEtcdVersion: with etcd version used for the current k8s release (0). - // The maximum length of the map should be 2, as kubeadm supports a maximum skew of -1 - // with the control plane version. + // - DefaultEtcdVersion: with etcd version used for the current k8s release. + // + // The maximum length of the map should be 3. kubeadm supports a maximum skew of -1 + // with the control plane version, but before a release the oldest k8s version in this + // map will be the only stable version, thus one additional version is required + // to be unit tested. + // + // This map should not be used directly in kubeadm code. Instead, it should be used with + // EtcdSupportedVersion(), as it allows for returning a valid version even if the input + // k8s version key is not defined (out of bounds). This allows for kubeadm to assume + // an etcd version even if the map is not yet updated before a release. The user will + // get a warning in that case, so ideally the map should be updated for each release. SupportedEtcdVersion = map[uint8]string{ - uint8(getSkewedKubernetesVersion(-2).Minor()): "3.5.24-0", - uint8(getSkewedKubernetesVersion(-1).Minor()): "3.6.6-0", - uint8(getSkewedKubernetesVersion(0).Minor()): "3.6.6-0", + 34: "3.6.8-0", + 35: "3.6.8-0", + 36: "3.6.8-0", } // KubeadmCertsClusterRoleName sets the name for the ClusterRole that allows diff --git a/cmd/kubeadm/app/constants/constants_test.go b/cmd/kubeadm/app/constants/constants_test.go index dea843890e169..ebd95631cffce 100644 --- a/cmd/kubeadm/app/constants/constants_test.go +++ b/cmd/kubeadm/app/constants/constants_test.go @@ -98,10 +98,18 @@ func TestGetStaticPodFilepath(t *testing.T) { } } +func TestEtcdSupportedVersionLength(t *testing.T) { + const max = 3 + if len(SupportedEtcdVersion) != max { + t.Fatalf("SupportedEtcdVersion must include exactly %d versions", max) + } +} + func TestEtcdSupportedVersion(t *testing.T) { var supportedEtcdVersion = map[uint8]string{ 17: "3.3.17-0", - 18: "3.4.3-0", + 18: "3.4.2-0", + 19: "3.4.3-0", } var tests = []struct { kubernetesVersion string @@ -135,7 +143,7 @@ func TestEtcdSupportedVersion(t *testing.T) { }, { kubernetesVersion: "1.18.1", - expectedVersion: version.MustParseSemantic("3.4.3-0"), + expectedVersion: version.MustParseSemantic("3.4.2-0"), expectedWarning: false, expectedError: false, }, diff --git a/cmd/kubeadm/app/constants/constants_unix.go b/cmd/kubeadm/app/constants/constants_unix.go index 0943d0610c0ab..0a843e46689e3 100644 --- a/cmd/kubeadm/app/constants/constants_unix.go +++ b/cmd/kubeadm/app/constants/constants_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/constants/constants_windows.go b/cmd/kubeadm/app/constants/constants_windows.go index 48e870023bc0f..5d89692b619ca 100644 --- a/cmd/kubeadm/app/constants/constants_windows.go +++ b/cmd/kubeadm/app/constants/constants_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/discovery/discovery.go b/cmd/kubeadm/app/discovery/discovery.go index ae51a902dc0d8..8bc79ab160319 100644 --- a/cmd/kubeadm/app/discovery/discovery.go +++ b/cmd/kubeadm/app/discovery/discovery.go @@ -17,10 +17,13 @@ limitations under the License. package discovery import ( + "crypto/x509" + "fmt" "net/url" clientset "k8s.io/client-go/kubernetes" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + clientcertutil "k8s.io/client-go/util/cert" bootstrapapi "k8s.io/cluster-bootstrap/token/api" "k8s.io/klog/v2" @@ -31,6 +34,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/discovery/token" "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" ) // TokenUser defines token user @@ -38,9 +42,8 @@ const TokenUser = "tls-bootstrap-token-user" // For returns a kubeconfig object that can be used for doing the TLS Bootstrap with the right credentials // Also, before returning anything, it makes sure it can trust the API Server +// It also prints the CA certificate information for user verification at verbosity level 1 func For(client clientset.Interface, cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { - // TODO: Print summary info about the CA certificate, along with the checksum signature - // we also need an ability for the user to configure the client to validate received CA cert against a checksum config, err := DiscoverValidatedKubeConfig(client, cfg) if err != nil { return nil, errors.Wrap(err, "couldn't validate the identity of the API Server") @@ -49,6 +52,7 @@ func For(client clientset.Interface, cfg *kubeadmapi.JoinConfiguration) (*client // If the users has provided a TLSBootstrapToken use it for the join process. // This is usually the case of Token discovery, but it can also be used with a discovery file // without embedded authentication credentials. + var skipAuthCheck bool if len(cfg.Discovery.TLSBootstrapToken) != 0 { klog.V(1).Info("[discovery] Using provided TLSBootstrapToken as authentication credentials for the join process") @@ -56,22 +60,31 @@ func For(client clientset.Interface, cfg *kubeadmapi.JoinConfiguration) (*client if err != nil { return nil, errors.Wrapf(err, "malformed kubeconfig in the %s ConfigMap", bootstrapapi.ConfigMapClusterInfo) } - return kubeconfigutil.CreateWithToken( + config = kubeconfigutil.CreateWithToken( clusterinfo.Server, kubeadmapiv1.DefaultClusterName, TokenUser, clusterinfo.CertificateAuthorityData, cfg.Discovery.TLSBootstrapToken, - ), nil + ) + skipAuthCheck = true } // if the config returned from discovery has authentication credentials, proceed with the TLS bootstrap process - if kubeconfigutil.HasAuthenticationCredentials(config) { - return config, nil + if !skipAuthCheck && !kubeconfigutil.HasAuthenticationCredentials(config) { + return nil, errors.New("could not find authentication credentials for the TLS bootstrap process. " + + "Please use Token discovery, a discovery file with embedded authentication credentials or a discovery " + + "file without authentication credentials but with the TLSBootstrapToken flag") } - // if there are no authentication credentials (nor in the config returned from discovery, nor in the TLSBootstrapToken), fail - return nil, errors.New("couldn't find authentication credentials for the TLS bootstrap process. Please use Token discovery, a discovery file with embedded authentication credentials or a discovery file without authentication credentials but with the TLSBootstrapToken flag") + // If the CA cert is available in the kubeconfig, print it for information purposes + var cert *x509.Certificate + if cert, err = getCACertFromKubeconfig(config); err != nil { + return nil, errors.Wrap(err, "could not get CA certificate from discovery kubeconfig") + } + klog.V(1).Infof("[discovery] Using CA certificate from discovery kubeconfig:\n%s", formatCACertInfo(cert)) + + return config, nil } // DiscoverValidatedKubeConfig returns a validated Config object that specifies where the cluster is and the CA cert to trust @@ -96,3 +109,36 @@ func isHTTPSURL(s string) bool { u, err := url.Parse(s) return err == nil && u.Scheme == "https" } + +// getCACertFromKubeconfig returns a single CA certificate from a kubeconfig, if available. +func getCACertFromKubeconfig(config *clientcmdapi.Config) (*x509.Certificate, error) { + _, clusterInfo, err := kubeconfigutil.GetClusterFromKubeConfig(config) + if err != nil { + return nil, err + } + certs, err := clientcertutil.ParseCertsPEM(clusterInfo.CertificateAuthorityData) + if err != nil { + return nil, err + } + if len(certs) == 0 { + return nil, errors.New("no CA certificate found in CertificateAuthorityData") + } + return certs[0], nil +} + +// formatCACertInfo formats summary information about the CA certificate. +func formatCACertInfo(cert *x509.Certificate) string { + return fmt.Sprintf("CA Certificate:\n"+ + "\tSubject: %v\n"+ + "\tIssuer: %v\n"+ + "\tSerialNumber: %v\n"+ + "\tNotBefore: %v\n"+ + "\tNotAfter: %v\n"+ + "\tSignatureAlgorithm: %v\n"+ + "\tPublicKeyAlgorithm: %v\n"+ + "\tHash: %v", + cert.Subject, cert.Issuer, cert.SerialNumber, cert.NotBefore, + cert.NotAfter, cert.SignatureAlgorithm, cert.PublicKeyAlgorithm, + pubkeypin.Hash(cert), + ) +} diff --git a/cmd/kubeadm/app/discovery/discovery_test.go b/cmd/kubeadm/app/discovery/discovery_test.go index bb14a7a7a1298..ea00089ed3d76 100644 --- a/cmd/kubeadm/app/discovery/discovery_test.go +++ b/cmd/kubeadm/app/discovery/discovery_test.go @@ -17,15 +17,23 @@ limitations under the License. package discovery import ( + "crypto/x509" + "crypto/x509/pkix" + _ "embed" + "math/big" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" fakeclient "k8s.io/client-go/kubernetes/fake" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) +//go:embed testdata/ca.crt +var testCACert []byte + func TestFor(t *testing.T) { tests := []struct { name string @@ -89,3 +97,83 @@ func TestFor(t *testing.T) { }) } } + +func TestGetCACertFromKubeconfig(t *testing.T) { + tests := []struct { + name string + config *clientcmdapi.Config + expectedError bool + }{ + { + name: "empty kubeconfig", + config: &clientcmdapi.Config{}, + expectedError: true, + }, + { + name: "kubeconfig with invalid cert", + config: &clientcmdapi.Config{ + CurrentContext: "cluster", + Contexts: map[string]*clientcmdapi.Context{ + "cluster": { + Cluster: "cluster", + }, + }, + Clusters: map[string]*clientcmdapi.Cluster{ + "cluster": { + CertificateAuthorityData: []byte("foo"), + }, + }, + }, + expectedError: true, + }, + { + name: "kubeconfig with CA cert", + config: &clientcmdapi.Config{ + CurrentContext: "cluster", + Contexts: map[string]*clientcmdapi.Context{ + "cluster": { + Cluster: "cluster", + }, + }, + Clusters: map[string]*clientcmdapi.Cluster{ + "cluster": { + CertificateAuthorityData: testCACert, + }, + }, + }, + expectedError: false, + }, + } + for _, rt := range tests { + t.Run(rt.name, func(t *testing.T) { + _, err := getCACertFromKubeconfig(rt.config) + if (err != nil) != rt.expectedError { + t.Errorf("Expected error: %v, got: %v, error: %v", rt.expectedError, err != nil, err) + } + }) + } +} + +func TestFormatCACertInfo(t *testing.T) { + certInfo := formatCACertInfo(&x509.Certificate{ + Subject: pkix.Name{CommonName: "test-subject"}, + Issuer: pkix.Name{CommonName: "test-issuer"}, + SerialNumber: big.NewInt(12345), + NotBefore: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2030, time.January, 1, 0, 0, 0, 0, time.UTC), + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + }) + expected := "CA Certificate:\n" + + "\tSubject: CN=test-subject\n" + + "\tIssuer: CN=test-issuer\n" + + "\tSerialNumber: 12345\n" + + "\tNotBefore: 2020-01-01 00:00:00 +0000 UTC\n" + + "\tNotAfter: 2030-01-01 00:00:00 +0000 UTC\n" + + "\tSignatureAlgorithm: SHA256-RSA\n" + + "\tPublicKeyAlgorithm: RSA\n" + + "\tHash: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + if certInfo != expected { + t.Errorf("expected:\n%s\ngot:\n%s\n", expected, certInfo) + } +} diff --git a/cmd/kubeadm/app/discovery/testdata/ca.crt b/cmd/kubeadm/app/discovery/testdata/ca.crt new file mode 100644 index 0000000000000..967a3f040d316 --- /dev/null +++ b/cmd/kubeadm/app/discovery/testdata/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIIB7Gep38zgZYwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE +AxMKa3ViZXJuZXRlczAeFw0yNjAyMjAxODEzMjlaFw0zNjAyMTgxODE4MjlaMBUx +EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCpzpdKhbm9HAyUxNov4Z8yI7bWiI7VHFX2sIMKF/kpJhuUW+crwj9nirYf +S9mlVSIneHrrtMn0q7/KlcO2CLSEbiFLHEOIE7wT+eMtobtX5AsHuYFMTiQxxGoZ +fuo1z8LH0cry3AUPy/c/nC5mCljWk+wNgAEDfZp47hGxc9SN17jAhlaMLadb9Apv +HipeHa3vGfTxAKFRz1g/b5844ZpVPX6mBl9/rJ3pPxtjJul3gR0PJtK/RdVc1F3R +B/AH59Zb0rd+OYSNevQ3uyle49jLpP2JX0qM8hwl2dc/Qpfc3u9MeaK9oz6LfPhM +3C+g8mmSWiw5vq2/XNMNoPN6NdmZAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS41d3PmSLk57SUVvGQr9Txhuxy3DAV +BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQBd4pE86l3Z +lKu4xvYyrR8A4EwSlwVyFtT0L1jK3RFRnsVhClxF4JhuQJO0TYcOl68dKUO+9r3X +RNNeSR5dDw9rP777uOop5KdFQl0mS3MDUEpX+Hsx3Bs5Z3mLf5/dpHN4CEsP6AUD +UmGzcchideM0C+SGl0t8KyDWyAzIkAAUeyN4vcfsEMoNDrZIC653bvyIlhQKgGJy +CDzRLpZl4iC7ltEMOeQw0GATFab1w62D13GiZJCAXhBrC+2lYiLQ1isKCck7HgSG +kJX7KaOlbvhxTWNp5lvC9XTUCNE2QTuN+1to41Vix9RQQFaR3hmrHgyAyAqXcNDi +g6WoC1hbyA5f +-----END CERTIFICATE----- diff --git a/cmd/kubeadm/app/discovery/token/testdata/ca-cert.pem b/cmd/kubeadm/app/discovery/token/testdata/ca-cert.pem new file mode 100644 index 0000000000000..edf1c46be7170 --- /dev/null +++ b/cmd/kubeadm/app/discovery/token/testdata/ca-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTE5MTEyMDAwNDk0MloXDTI5MTExNzAwNDk0MlowFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqQ +ctECzA8yFSuVYupOUYgrTmfQeKe/9BaDWagaq7ow9+I2IvsfWFvlrD8QQr8sea6q +xjq7TV67Vb4RxBaoYDA+yI5vIcujWUxULun64lu3Q6iC1sj2UnmUpIdgazRXXEkZ +vxA6EbAnoxA0+lBOn1CZWl23IQ4s70o2hZ7wIp/vevB88RRRjqtvgc5elsjsbmDF +LS7L1Zuye8c6gS93bR+VjVmSIfr1IEq0748tIIyXjAVCWPVCvuP41MlfPc/JVpZD +uD2+pO6ZYREcdAnOf2eD4/eLOMKko4L1dSFy9JKM5PLnOC0Zk0AYOd1vS8DTAfxj +XPEIY8OBYFhlsxf4TE8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH/OYq8zyl1+zSTmuow3yI/15PL1 +dl8hB7IKnZNWmC/LTdm/+noh3Sb1IdRv6HkKg/GUn0UMuRUngLhju3EO4ozJPQcX +quaxzgmTKNWJ6ErDvRvWhGX0ZcbdBfZv+dowyRqzd5nlJ49hC+NrtFFQq6P05BYn +7SemguqeXmXwIj2Sa+1DeR6lRm9o8shAYjnyThUFqaMn18kI3SANJ5vk/3DFrPEO +CKC9EzFku2kuxg2dM12PbRGZQ2o0K6HEZgrrIKTPOy3ocb8r9M0aSFhjOV/NqGA4 +SaupXSW6XfvIi/UHoIbU3pNcsnUJGnQfQvip95XKk/gqcUr+m50vxgumxtA= +-----END CERTIFICATE----- diff --git a/cmd/kubeadm/app/discovery/token/testdata/expected-kubeconfig.yaml b/cmd/kubeadm/app/discovery/token/testdata/expected-kubeconfig.yaml new file mode 100644 index 0000000000000..5edfbe8dd7676 --- /dev/null +++ b/cmd/kubeadm/app/discovery/token/testdata/expected-kubeconfig.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1URXlNREF3TkRrME1sb1hEVEk1TVRFeE56QXdORGswTWxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTXFRCmN0RUN6QTh5RlN1Vll1cE9VWWdyVG1mUWVLZS85QmFEV2FnYXE3b3c5K0kySXZzZldGdmxyRDhRUXI4c2VhNnEKeGpxN1RWNjdWYjRSeEJhb1lEQSt5STV2SWN1aldVeFVMdW42NGx1M1E2aUMxc2oyVW5tVXBJZGdhelJYWEVrWgp2eEE2RWJBbm94QTArbEJPbjFDWldsMjNJUTRzNzBvMmhaN3dJcC92ZXZCODhSUlJqcXR2Z2M1ZWxzanNibURGCkxTN0wxWnV5ZThjNmdTOTNiUitWalZtU0lmcjFJRXEwNzQ4dElJeVhqQVZDV1BWQ3Z1UDQxTWxmUGMvSlZwWkQKdUQyK3BPNlpZUkVjZEFuT2YyZUQ0L2VMT01La280TDFkU0Z5OUpLTTVQTG5PQzBaazBBWU9kMXZTOERUQWZ4agpYUEVJWThPQllGaGxzeGY0VEU4Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFIL09ZcTh6eWwxK3pTVG11b3czeUkvMTVQTDEKZGw4aEI3SUtuWk5XbUMvTFRkbS8rbm9oM1NiMUlkUnY2SGtLZy9HVW4wVU11UlVuZ0xoanUzRU80b3pKUFFjWApxdWF4emdtVEtOV0o2RXJEdlJ2V2hHWDBaY2JkQmZaditkb3d5UnF6ZDVubEo0OWhDK05ydEZGUXE2UDA1QlluCjdTZW1ndXFlWG1Yd0lqMlNhKzFEZVI2bFJtOW84c2hBWWpueVRoVUZxYU1uMThrSTNTQU5KNXZrLzNERnJQRU8KQ0tDOUV6Rmt1Mmt1eGcyZE0xMlBiUkdaUTJvMEs2SEVaZ3JySUtUUE95M29jYjhyOU0wYVNGaGpPVi9OcUdBNApTYXVwWFNXNlhmdklpL1VIb0liVTNwTmNzblVKR25RZlF2aXA5NVhLay9ncWNVcittNTB2eGd1bXh0QT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + server: https://127.0.0.1 + name: somecluster +contexts: +- context: + cluster: somecluster + user: token-bootstrap-client + name: token-bootstrap-client@somecluster +current-context: token-bootstrap-client@somecluster +kind: Config +users: null diff --git a/cmd/kubeadm/app/discovery/token/token_test.go b/cmd/kubeadm/app/discovery/token/token_test.go index 2614662dcead4..8ed447919d00b 100644 --- a/cmd/kubeadm/app/discovery/token/token_test.go +++ b/cmd/kubeadm/app/discovery/token/token_test.go @@ -17,6 +17,7 @@ limitations under the License. package token import ( + _ "embed" "testing" "time" @@ -34,44 +35,16 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" ) -func TestRetrieveValidatedConfigInfo(t *testing.T) { - const ( - caCert = `-----BEGIN CERTIFICATE----- -MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl -cm5ldGVzMB4XDTE5MTEyMDAwNDk0MloXDTI5MTExNzAwNDk0MlowFTETMBEGA1UE -AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqQ -ctECzA8yFSuVYupOUYgrTmfQeKe/9BaDWagaq7ow9+I2IvsfWFvlrD8QQr8sea6q -xjq7TV67Vb4RxBaoYDA+yI5vIcujWUxULun64lu3Q6iC1sj2UnmUpIdgazRXXEkZ -vxA6EbAnoxA0+lBOn1CZWl23IQ4s70o2hZ7wIp/vevB88RRRjqtvgc5elsjsbmDF -LS7L1Zuye8c6gS93bR+VjVmSIfr1IEq0748tIIyXjAVCWPVCvuP41MlfPc/JVpZD -uD2+pO6ZYREcdAnOf2eD4/eLOMKko4L1dSFy9JKM5PLnOC0Zk0AYOd1vS8DTAfxj -XPEIY8OBYFhlsxf4TE8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH/OYq8zyl1+zSTmuow3yI/15PL1 -dl8hB7IKnZNWmC/LTdm/+noh3Sb1IdRv6HkKg/GUn0UMuRUngLhju3EO4ozJPQcX -quaxzgmTKNWJ6ErDvRvWhGX0ZcbdBfZv+dowyRqzd5nlJ49hC+NrtFFQq6P05BYn -7SemguqeXmXwIj2Sa+1DeR6lRm9o8shAYjnyThUFqaMn18kI3SANJ5vk/3DFrPEO -CKC9EzFku2kuxg2dM12PbRGZQ2o0K6HEZgrrIKTPOy3ocb8r9M0aSFhjOV/NqGA4 -SaupXSW6XfvIi/UHoIbU3pNcsnUJGnQfQvip95XKk/gqcUr+m50vxgumxtA= ------END CERTIFICATE-----` +var ( + //go:embed testdata/ca-cert.pem + caCert string - caCertHash = "sha256:98be2e6d4d8a89aa308fb15de0c07e2531ce549c68dec1687cdd5c06f0826658" + //go:embed testdata/expected-kubeconfig.yaml + expectedKubeconfig string +) - expectedKubeconfig = `apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1URXlNREF3TkRrME1sb1hEVEk1TVRFeE56QXdORGswTWxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTXFRCmN0RUN6QTh5RlN1Vll1cE9VWWdyVG1mUWVLZS85QmFEV2FnYXE3b3c5K0kySXZzZldGdmxyRDhRUXI4c2VhNnEKeGpxN1RWNjdWYjRSeEJhb1lEQSt5STV2SWN1aldVeFVMdW42NGx1M1E2aUMxc2oyVW5tVXBJZGdhelJYWEVrWgp2eEE2RWJBbm94QTArbEJPbjFDWldsMjNJUTRzNzBvMmhaN3dJcC92ZXZCODhSUlJqcXR2Z2M1ZWxzanNibURGCkxTN0wxWnV5ZThjNmdTOTNiUitWalZtU0lmcjFJRXEwNzQ4dElJeVhqQVZDV1BWQ3Z1UDQxTWxmUGMvSlZwWkQKdUQyK3BPNlpZUkVjZEFuT2YyZUQ0L2VMT01La280TDFkU0Z5OUpLTTVQTG5PQzBaazBBWU9kMXZTOERUQWZ4agpYUEVJWThPQllGaGxzeGY0VEU4Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFIL09ZcTh6eWwxK3pTVG11b3czeUkvMTVQTDEKZGw4aEI3SUtuWk5XbUMvTFRkbS8rbm9oM1NiMUlkUnY2SGtLZy9HVW4wVU11UlVuZ0xoanUzRU80b3pKUFFjWApxdWF4emdtVEtOV0o2RXJEdlJ2V2hHWDBaY2JkQmZaditkb3d5UnF6ZDVubEo0OWhDK05ydEZGUXE2UDA1QlluCjdTZW1ndXFlWG1Yd0lqMlNhKzFEZVI2bFJtOW84c2hBWWpueVRoVUZxYU1uMThrSTNTQU5KNXZrLzNERnJQRU8KQ0tDOUV6Rmt1Mmt1eGcyZE0xMlBiUkdaUTJvMEs2SEVaZ3JySUtUUE95M29jYjhyOU0wYVNGaGpPVi9OcUdBNApTYXVwWFNXNlhmdklpL1VIb0liVTNwTmNzblVKR25RZlF2aXA5NVhLay9ncWNVcittNTB2eGd1bXh0QT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== - server: https://127.0.0.1 - name: somecluster -contexts: -- context: - cluster: somecluster - user: token-bootstrap-client - name: token-bootstrap-client@somecluster -current-context: token-bootstrap-client@somecluster -kind: Config -users: null -` - ) +func TestRetrieveValidatedConfigInfo(t *testing.T) { + const caCertHash = "sha256:98be2e6d4d8a89aa308fb15de0c07e2531ce549c68dec1687cdd5c06f0826658" tests := []struct { name string diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index e3fbe4bb6c998..85fc067b0722e 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -34,9 +34,6 @@ import ( // of code conflicts because changes are more likely to be scattered // across the file. const ( - // ControlPlaneKubeletLocalMode is expected to be in alpha in v1.31, beta in v1.33 - ControlPlaneKubeletLocalMode = "ControlPlaneKubeletLocalMode" - // NodeLocalCRISocket is expected to be in alpha in v1.32, beta in v1.34, ga in v1.36 NodeLocalCRISocket = "NodeLocalCRISocket" @@ -58,8 +55,7 @@ var InitFeatureGates = FeatureList{ DeprecationMessage: "Deprecated in favor of the core kubelet feature UserNamespacesSupport which is beta since 1.30." + " Once UserNamespacesSupport graduates to GA, kubeadm will start using it and RootlessControlPlane will be removed.", }, - ControlPlaneKubeletLocalMode: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.GA, LockToDefault: true}}, - NodeLocalCRISocket: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}}, + NodeLocalCRISocket: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.GA, LockToDefault: true}}, } // Feature represents a feature being gated diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index f1706973166c4..fe5e46f71b671 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -1679,7 +1679,7 @@ func newMockClientForTest(t *testing.T, replicas int32, deploymentSize int, imag image = "registry.k8s.io/coredns/coredns:" + kubeadmconstants.CoreDNSVersion } client := clientsetfake.NewSimpleClientset() - for i := 0; i < deploymentSize; i++ { + for i := range deploymentSize { _, err := client.AppsV1().Deployments(metav1.NamespaceSystem).Create(context.TODO(), &apps.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go index 72154c9ecde91..8485e3779a7b5 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go @@ -130,3 +130,25 @@ func AutoApproveNodeCertificateRotation(client clientset.Interface) error { }, }) } + +// AllowAPIServerToAccessKubeletAPI creates RBAC rules that allow the API server kubelet client to access the kubelet API +func AllowAPIServerToAccessKubeletAPI(client clientset.Interface) error { + fmt.Println("[bootstrap-token] Configured RBAC rules to allow the API server kubelet client certificate to access the kubelet API") + + return apiclient.CreateOrUpdate(client.RbacV1().ClusterRoleBindings(), &rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.KubeletAPIAdminClusterRoleBindingName, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: constants.KubeletAPIAdminClusterRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: rbac.UserKind, + Name: constants.APIServerKubeletClientCertCommonName, + }, + }, + }) +} diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap_test.go b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap_test.go index 1611410569293..7a75fdeb1c6b5 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap_test.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap_test.go @@ -278,6 +278,63 @@ func TestAllowBootstrapTokensToGetNodes(t *testing.T) { } } +func TestAllowAPIServerToAccessKubeletAPI(t *testing.T) { + tests := []struct { + name string + client clientset.Interface + }{ + { + name: "ClusterRoleBindings is empty", + client: clientsetfake.NewSimpleClientset(), + }, + { + name: "ClusterRoleBindings already exists", + client: newMockClusterRoleBinddingClientForTest(t, &rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.KubeletAPIAdminClusterRoleBindingName, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: constants.KubeletAPIAdminClusterRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: rbac.UserKind, + Name: constants.APIServerKubeletClientCertCommonName, + }, + }, + }), + }, + { + name: "Create new ClusterRoleBindings", + client: newMockClusterRoleBinddingClientForTest(t, &rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.KubeletAPIAdminClusterRoleBindingName, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: constants.KubeletAPIAdminClusterRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: rbac.GroupKind, + Name: constants.NodesGroup, + }, + }, + }), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := AllowAPIServerToAccessKubeletAPI(tt.client); err != nil { + t.Errorf("AllowAPIServerToAccessKubeletAPI() return error = %v", err) + } + }) + } +} + func newMockClusterRoleBinddingClientForTest(t *testing.T, clusterRoleBinding *rbac.ClusterRoleBinding) *clientsetfake.Clientset { client := clientsetfake.NewSimpleClientset() _, err := client.RbacV1().ClusterRoleBindings().Create(context.TODO(), clusterRoleBinding, metav1.CreateOptions{}) diff --git a/cmd/kubeadm/app/phases/certs/certlist.go b/cmd/kubeadm/app/phases/certs/certlist.go index 36caff1d9203e..11f773e359738 100644 --- a/cmd/kubeadm/app/phases/certs/certlist.go +++ b/cmd/kubeadm/app/phases/certs/certlist.go @@ -317,9 +317,8 @@ func KubeadmCertKubeletClient() *KubeadmCert { CAName: "ca", config: pkiutil.CertConfig{ Config: certutil.Config{ - CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName, - Organization: []string{kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, }, } diff --git a/cmd/kubeadm/app/phases/controlplane/manifests_test.go b/cmd/kubeadm/app/phases/controlplane/manifests_test.go index e5d9846690a86..a1d5a332cfec5 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests_test.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2016 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/phases/controlplane/volumes.go b/cmd/kubeadm/app/phases/controlplane/volumes.go index 8e537d85a481c..bd976cbcb4c1a 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes.go @@ -31,10 +31,8 @@ import ( ) const ( - caCertsVolumeName = "ca-certs" - caCertsVolumePath = "/etc/ssl/certs" - flexvolumeDirVolumeName = "flexvolume-dir" - defaultFlexvolumeDirVolumePath = "/usr/libexec/kubernetes/kubelet-plugins/volume/exec" + caCertsVolumeName = "ca-certs" + caCertsVolumePath = "/etc/ssl/certs" ) // caCertsExtraVolumePaths specifies the paths that can be conditionally mounted into the apiserver and controller-manager containers @@ -70,13 +68,6 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.ClusterConfiguration) // Read-only mount for the controller manager kubeconfig file controllerManagerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName) mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeConfigVolumeName, controllerManagerKubeConfigFile, controllerManagerKubeConfigFile, true, &hostPathFileOrCreate) - // Mount for the flexvolume directory (/usr/libexec/kubernetes/kubelet-plugins/volume/exec by default) - // Flexvolume dir must NOT be readonly as it is used for third-party plugins to integrate with their storage backends via unix domain socket. - flexvolumeDirVolumePath, idx := kubeadmapi.GetArgValue(cfg.ControllerManager.ExtraArgs, "flex-volume-plugin-dir", -1) - if idx == -1 { - flexvolumeDirVolumePath = defaultFlexvolumeDirVolumePath - } - mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate) // HostPath volumes for the scheduler // Read-only mount for the scheduler kubeconfig file diff --git a/cmd/kubeadm/app/phases/controlplane/volumes_test.go b/cmd/kubeadm/app/phases/controlplane/volumes_test.go index bcbdcb79f5fa9..6a72b5c0e98ed 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes_test.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. @@ -26,7 +25,7 @@ import ( "reflect" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -527,9 +526,6 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { t.Run(rt.name, func(t *testing.T) { mounts := getHostPathVolumesForTheControlPlane(rt.cfg) - // Avoid unit test errors when the flexvolume is mounted - delete(mounts.volumes[kubeadmconstants.KubeControllerManager], flexvolumeDirVolumeName) - delete(mounts.volumeMounts[kubeadmconstants.KubeControllerManager], flexvolumeDirVolumeName) if !reflect.DeepEqual(mounts.volumes, rt.vol) { t.Errorf( "failed getHostPathVolumesForTheControlPlane:\n\texpected: %v\n\t actual: %v", diff --git a/cmd/kubeadm/app/phases/copycerts/testutil_umask.go b/cmd/kubeadm/app/phases/copycerts/testutil_umask.go index d8ece9b22ba55..b405a43387295 100644 --- a/cmd/kubeadm/app/phases/copycerts/testutil_umask.go +++ b/cmd/kubeadm/app/phases/copycerts/testutil_umask.go @@ -1,5 +1,4 @@ //go:build darwin || linux -// +build darwin linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/phases/copycerts/testutil_umask_noop.go b/cmd/kubeadm/app/phases/copycerts/testutil_umask_noop.go index 22efc3b4ae574..ecd039c101181 100644 --- a/cmd/kubeadm/app/phases/copycerts/testutil_umask_noop.go +++ b/cmd/kubeadm/app/phases/copycerts/testutil_umask_noop.go @@ -1,5 +1,4 @@ //go:build !darwin && !linux -// +build !darwin,!linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/phases/etcd/local.go b/cmd/kubeadm/app/phases/etcd/local.go index e10aa9a76c877..29c93a661d9c3 100644 --- a/cmd/kubeadm/app/phases/etcd/local.go +++ b/cmd/kubeadm/app/phases/etcd/local.go @@ -28,7 +28,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/version" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" utilsnet "k8s.io/utils/net" @@ -118,7 +117,7 @@ func RemoveStackedEtcdMemberFromCluster(client clientset.Interface, cfg *kubeadm klog.V(2).Infof("[etcd] get the member id from peer: %s", etcdPeerAddress) id, err := etcdClient.GetMemberID(etcdPeerAddress) if err != nil { - if errors.Is(etcdutil.ErrNoMemberIDForPeerURL, err) { + if errors.Is(err, etcdutil.ErrNoMemberIDForPeerURL) { klog.V(5).Infof("[etcd] member was already removed, because no member id exists for peer %s", etcdPeerAddress) return nil } @@ -263,21 +262,8 @@ func getEtcdCommand(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.A {Name: "peer-client-cert-auth", Value: "true"}, {Name: "snapshot-count", Value: "10000"}, {Name: "listen-metrics-urls", Value: fmt.Sprintf("http://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdMetricsPort)))}, - } - - etcdImageTag := images.GetEtcdImageTag(cfg, supportedEtcdVersion) - if etcdVersion, err := version.ParseSemantic(etcdImageTag); err == nil && etcdVersion.AtLeast(version.MustParseSemantic("3.6.0")) { - // Arguments used by Etcd 3.6.0+. - // TODO: Start always using these once kubeadm only supports etcd >= 3.6.0 for all its supported k8s versions. - defaultArguments = append(defaultArguments, []kubeadmapi.Arg{ - {Name: "feature-gates", Value: "InitialCorruptCheck=true"}, - {Name: "watch-progress-notify-interval", Value: "5s"}, - }...) - } else { - defaultArguments = append(defaultArguments, []kubeadmapi.Arg{ - {Name: "experimental-initial-corrupt-check", Value: "true"}, - {Name: "experimental-watch-progress-notify-interval", Value: "5s"}, - }...) + {Name: "feature-gates", Value: "InitialCorruptCheck=true"}, + {Name: "watch-progress-notify-interval", Value: "5s"}, } if len(initialCluster) == 0 { diff --git a/cmd/kubeadm/app/phases/etcd/local_test.go b/cmd/kubeadm/app/phases/etcd/local_test.go index aa0bc2d56c19e..b6f62ef88eed8 100644 --- a/cmd/kubeadm/app/phases/etcd/local_test.go +++ b/cmd/kubeadm/app/phases/etcd/local_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. @@ -412,94 +411,6 @@ func TestGetEtcdCommand(t *testing.T) { fmt.Sprintf("--initial-cluster=foo=https://[2001:db8::3]:%d", kubeadmconstants.EtcdListenPeerPort), }, }, - { - name: "Default args - with k8s 1.33.0", - advertiseAddress: "1.2.3.4", - k8sVersion: "1.33.0", - nodeName: "bar", - supportedEtcdVersion: map[uint8]string{ - 33: "3.5.24", - 34: "3.6.5", - }, - expected: []string{ - "etcd", - "--name=bar", - "--experimental-initial-corrupt-check=true", - "--experimental-watch-progress-notify-interval=5s", - fmt.Sprintf("--listen-client-urls=https://127.0.0.1:%d,https://1.2.3.4:%d", kubeadmconstants.EtcdListenClientPort, kubeadmconstants.EtcdListenClientPort), - fmt.Sprintf("--listen-metrics-urls=http://127.0.0.1:%d", kubeadmconstants.EtcdMetricsPort), - fmt.Sprintf("--advertise-client-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenClientPort), - fmt.Sprintf("--listen-peer-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - fmt.Sprintf("--initial-advertise-peer-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - "--data-dir=/var/lib/etcd", - "--cert-file=" + filepath.FromSlash(kubeadmconstants.EtcdServerCertName), - "--key-file=" + filepath.FromSlash(kubeadmconstants.EtcdServerKeyName), - "--trusted-ca-file=" + filepath.FromSlash(kubeadmconstants.EtcdCACertName), - "--client-cert-auth=true", - "--peer-cert-file=" + filepath.FromSlash(kubeadmconstants.EtcdPeerCertName), - "--peer-key-file=" + filepath.FromSlash(kubeadmconstants.EtcdPeerKeyName), - "--peer-trusted-ca-file=" + filepath.FromSlash(kubeadmconstants.EtcdCACertName), - "--snapshot-count=10000", - "--peer-client-cert-auth=true", - fmt.Sprintf("--initial-cluster=bar=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - }, - }, - { - name: "Default args - with semver etcd image (3.5.21-0)", - advertiseAddress: "1.2.3.4", - etcdImageTag: "3.5.21-0", - nodeName: "bar", - expected: []string{ - "etcd", - "--name=bar", - "--experimental-initial-corrupt-check=true", - "--experimental-watch-progress-notify-interval=5s", - fmt.Sprintf("--listen-client-urls=https://127.0.0.1:%d,https://1.2.3.4:%d", kubeadmconstants.EtcdListenClientPort, kubeadmconstants.EtcdListenClientPort), - fmt.Sprintf("--listen-metrics-urls=http://127.0.0.1:%d", kubeadmconstants.EtcdMetricsPort), - fmt.Sprintf("--advertise-client-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenClientPort), - fmt.Sprintf("--listen-peer-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - fmt.Sprintf("--initial-advertise-peer-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - "--data-dir=/var/lib/etcd", - "--cert-file=" + filepath.FromSlash(kubeadmconstants.EtcdServerCertName), - "--key-file=" + filepath.FromSlash(kubeadmconstants.EtcdServerKeyName), - "--trusted-ca-file=" + filepath.FromSlash(kubeadmconstants.EtcdCACertName), - "--client-cert-auth=true", - "--peer-cert-file=" + filepath.FromSlash(kubeadmconstants.EtcdPeerCertName), - "--peer-key-file=" + filepath.FromSlash(kubeadmconstants.EtcdPeerKeyName), - "--peer-trusted-ca-file=" + filepath.FromSlash(kubeadmconstants.EtcdCACertName), - "--snapshot-count=10000", - "--peer-client-cert-auth=true", - fmt.Sprintf("--initial-cluster=bar=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - }, - }, - { - name: "Default args - with non-semver etcd image", - advertiseAddress: "1.2.3.4", - etcdImageTag: "non-semver", - nodeName: "bar", - expected: []string{ - "etcd", - "--name=bar", - "--experimental-initial-corrupt-check=true", - "--experimental-watch-progress-notify-interval=5s", - fmt.Sprintf("--listen-client-urls=https://127.0.0.1:%d,https://1.2.3.4:%d", kubeadmconstants.EtcdListenClientPort, kubeadmconstants.EtcdListenClientPort), - fmt.Sprintf("--listen-metrics-urls=http://127.0.0.1:%d", kubeadmconstants.EtcdMetricsPort), - fmt.Sprintf("--advertise-client-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenClientPort), - fmt.Sprintf("--listen-peer-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - fmt.Sprintf("--initial-advertise-peer-urls=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - "--data-dir=/var/lib/etcd", - "--cert-file=" + filepath.FromSlash(kubeadmconstants.EtcdServerCertName), - "--key-file=" + filepath.FromSlash(kubeadmconstants.EtcdServerKeyName), - "--trusted-ca-file=" + filepath.FromSlash(kubeadmconstants.EtcdCACertName), - "--client-cert-auth=true", - "--peer-cert-file=" + filepath.FromSlash(kubeadmconstants.EtcdPeerCertName), - "--peer-key-file=" + filepath.FromSlash(kubeadmconstants.EtcdPeerKeyName), - "--peer-trusted-ca-file=" + filepath.FromSlash(kubeadmconstants.EtcdCACertName), - "--snapshot-count=10000", - "--peer-client-cert-auth=true", - fmt.Sprintf("--initial-cluster=bar=https://1.2.3.4:%d", kubeadmconstants.EtcdListenPeerPort), - }, - }, } for _, rt := range tests { diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 09ef132cf0251..1aa01bfd582de 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -615,14 +615,19 @@ type EnsureRBACFunc func(context.Context, clientset.Interface, clientset.Interfa // constructs a client from super-admin.conf if the file exists. It then proceeds // to pass the clients to EnsureAdminClusterRoleBindingImpl. The function returns a // usable client from admin.conf with RBAC properly constructed or an error. -func EnsureAdminClusterRoleBinding(outDir string, ensureRBACFunc EnsureRBACFunc) (clientset.Interface, error) { +func EnsureAdminClusterRoleBinding(outDir string, lae *kubeadmapi.APIEndpoint, ensureRBACFunc EnsureRBACFunc) (clientset.Interface, error) { var ( err error adminClient, superAdminClient clientset.Interface ) // Create a client from admin.conf. - adminClient, err = kubeconfigutil.ClientSetFromFile(filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName)) + kubeconfig, err := clientcmd.LoadFromFile(filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName)) + if err != nil { + return nil, err + } + kubeconfigutil.PointKubeConfigToLocalAPIEndpoint(kubeconfig, lae) + adminClient, err = kubeconfigutil.ToClientSet(kubeconfig) if err != nil { return nil, err } @@ -630,7 +635,12 @@ func EnsureAdminClusterRoleBinding(outDir string, ensureRBACFunc EnsureRBACFunc) // Create a client from super-admin.conf. superAdminPath := filepath.Join(outDir, kubeadmconstants.SuperAdminKubeConfigFileName) if _, err := os.Stat(superAdminPath); err == nil { - superAdminClient, err = kubeconfigutil.ClientSetFromFile(superAdminPath) + kubeconfig, err := clientcmd.LoadFromFile(superAdminPath) + if err != nil { + return nil, err + } + kubeconfigutil.PointKubeConfigToLocalAPIEndpoint(kubeconfig, lae) + superAdminClient, err = kubeconfigutil.ToClientSet(kubeconfig) if err != nil { return nil, err } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index b0722ec77c327..7fad218363d09 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -971,7 +971,7 @@ func TestEnsureAdminClusterRoleBinding(t *testing.T) { } } - client, err := EnsureAdminClusterRoleBinding(dir, ensureRBACFunc) + client, err := EnsureAdminClusterRoleBinding(dir, &kubeadmapi.APIEndpoint{}, ensureRBACFunc) if (err != nil) != tc.expectedError { t.Fatalf("expected error: %v, got: %v, error: %v", err != nil, tc.expectedError, err) } diff --git a/cmd/kubeadm/app/phases/upgrade/health.go b/cmd/kubeadm/app/phases/upgrade/health.go index 30826fac46269..5a47de8e5afd1 100644 --- a/cmd/kubeadm/app/phases/upgrade/health.go +++ b/cmd/kubeadm/app/phases/upgrade/health.go @@ -106,7 +106,7 @@ func createJob(client clientset.Interface, cfg *kubeadmapi.ClusterConfiguration, const ( fieldSelector = "spec.unschedulable=false" ns = metav1.NamespaceSystem - timeout = 15 * time.Second + timeout = 60 * time.Second timeoutMargin = 5 * time.Second ) var ( diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 562c9cc87f23d..c071293f86257 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -19,6 +19,7 @@ package preflight import ( "bufio" "bytes" + "context" "crypto/tls" "crypto/x509" "encoding/json" @@ -94,7 +95,7 @@ func (crc ContainerRuntimeCheck) Check() (warnings, errorList []error) { if err := containerRuntime.Connect(); err != nil { return nil, []error{errors.Wrap(err, "could not connect to the container runtime")} } - defer containerRuntime.Close() + defer containerRuntime.Close(context.Background()) if err := containerRuntime.IsRunning(); err != nil { errorList = append(errorList, err) @@ -125,17 +126,17 @@ func (crvc ContainerRuntimeVersionCheck) Check() (warnings, errorList []error) { if err := containerRuntime.Connect(); err != nil { return nil, []error{errors.Wrap(err, "could not connect to the container runtime")} } - defer containerRuntime.Close() + defer containerRuntime.Close(context.Background()) ok, err := containerRuntime.IsRuntimeConfigImplemented() if err != nil { return nil, []error{errors.Wrap(err, "could not check if the runtime config is available")} } if !ok { - // TODO: return an error once the kubelet version is 1.36 or higher. + // TODO: return an error once the kubelet version is 1.37 or higher. // https://github.com/kubernetes/kubeadm/issues/3229 err := errors.New("You must update your container runtime to a version that supports the CRI method RuntimeConfig. " + - "Falling back to using cgroupDriver from kubelet config will be removed in 1.36. " + + "Falling back to using cgroupDriver from kubelet config will be removed in 1.37. " + "For more information, see https://git.k8s.io/enhancements/keps/sig-node/4033-group-driver-detection-over-cri") warnings = append(warnings, err) } @@ -883,7 +884,7 @@ func (ipc ImagePullCheck) Check() (warnings, errorList []error) { if err := containerRuntime.Connect(); err != nil { return nil, []error{errors.Wrap(err, "could not connect to the container runtime")} } - defer containerRuntime.Close() + defer containerRuntime.Close(context.Background()) // Handle unsupported image pull policy and policy Never. policy := ipc.imagePullPolicy diff --git a/cmd/kubeadm/app/preflight/checks_darwin.go b/cmd/kubeadm/app/preflight/checks_darwin.go index bf06ae46a727a..606faa58247f7 100644 --- a/cmd/kubeadm/app/preflight/checks_darwin.go +++ b/cmd/kubeadm/app/preflight/checks_darwin.go @@ -1,5 +1,4 @@ //go:build darwin -// +build darwin /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/preflight/checks_linux.go b/cmd/kubeadm/app/preflight/checks_linux.go index e5aa7a907796b..765dfc8219726 100644 --- a/cmd/kubeadm/app/preflight/checks_linux.go +++ b/cmd/kubeadm/app/preflight/checks_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/preflight/checks_other.go b/cmd/kubeadm/app/preflight/checks_other.go index 9756849ac8c82..26f91e4355dd6 100644 --- a/cmd/kubeadm/app/preflight/checks_other.go +++ b/cmd/kubeadm/app/preflight/checks_other.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2022 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/preflight/checks_unix.go b/cmd/kubeadm/app/preflight/checks_unix.go index ef1119ea943f4..649abbd4f0d65 100644 --- a/cmd/kubeadm/app/preflight/checks_unix.go +++ b/cmd/kubeadm/app/preflight/checks_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/preflight/checks_windows.go b/cmd/kubeadm/app/preflight/checks_windows.go index 562c6952a4f03..717146901e17f 100644 --- a/cmd/kubeadm/app/preflight/checks_windows.go +++ b/cmd/kubeadm/app/preflight/checks_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/apiclient/wait.go b/cmd/kubeadm/app/util/apiclient/wait.go index e55e432f1accb..68a8036041add 100644 --- a/cmd/kubeadm/app/util/apiclient/wait.go +++ b/cmd/kubeadm/app/util/apiclient/wait.go @@ -315,7 +315,7 @@ func (w *KubeWaiter) WaitForControlPlaneComponents(podMap map[string]*v1.Pod, ap }(comp) } - for i := 0; i < len(components); i++ { + for range components { if err := <-errChan; err != nil { errs = append(errs, err) } diff --git a/cmd/kubeadm/app/util/chroot_unix.go b/cmd/kubeadm/app/util/chroot_unix.go index bc303b89c18d6..06a84e09656ad 100644 --- a/cmd/kubeadm/app/util/chroot_unix.go +++ b/cmd/kubeadm/app/util/chroot_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/chroot_windows.go b/cmd/kubeadm/app/util/chroot_windows.go index 871db8b4dbd4a..41b496e96869a 100644 --- a/cmd/kubeadm/app/util/chroot_windows.go +++ b/cmd/kubeadm/app/util/chroot_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/config/cluster.go b/cmd/kubeadm/app/util/config/cluster.go index 3297c3447e55d..ebdb51c2abb1f 100644 --- a/cmd/kubeadm/app/util/config/cluster.go +++ b/cmd/kubeadm/app/util/config/cluster.go @@ -66,9 +66,11 @@ func FetchInitConfigurationFromCluster(client clientset.Interface, printer outpu return nil, err } - // Apply dynamic defaults - // NB. skip CRI detection here because it won't be used at all and will be overridden later - if err := SetInitDynamicDefaults(cfg, true); err != nil { + // Apply dynamic defaults. + // NB. skip CRI detection here because it won't be used at all and will be overridden later. + // NB. skip LocalAPIEndpoint defaulting when the caller did not request the endpoint (e.g. a + // worker join). + if err := SetInitDynamicDefaults(cfg, true, !getAPIEndpoint); err != nil { return nil, err } diff --git a/cmd/kubeadm/app/util/config/cluster_test.go b/cmd/kubeadm/app/util/config/cluster_test.go index f57770c56699c..adaf85dbb2616 100644 --- a/cmd/kubeadm/app/util/config/cluster_test.go +++ b/cmd/kubeadm/app/util/config/cluster_test.go @@ -18,6 +18,7 @@ package config import ( "context" + _ "embed" "fmt" "os" "path/filepath" @@ -75,120 +76,25 @@ kind: KubeletConfiguration `), } -var kubeletConfFiles = map[string][]byte{ - "withoutX509Cert": []byte(` -apiVersion: v1 -clusters: -- cluster: - server: https://10.0.2.15:6443 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: system:node:mynode - name: system:node:mynode@kubernetes -current-context: system:node:mynode@kubernetes -kind: Config -users: -- name: system:node:mynode - user: -`), - "configWithEmbeddedCert": []byte(` -apiVersion: v1 -clusters: -- cluster: - server: https://10.0.2.15:6443 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: system:node:mynode - name: system:node:mynode@kubernetes -current-context: system:node:mynode@kubernetes -kind: Config -users: -- name: system:node:mynode - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQWl3VURhYk5vZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ERXhOVE14TWpaYUZ3MHhPVEE1TURFeE5qQXhOVGxhTURReApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFYk1Ca0dBMVVFQXhNU2MzbHpkR1Z0T201dlpHVTZiWGx1CmIyUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWs2UXUzeStyNEZYUzZ4VkoKWU1vNE9kSkt3R1d1MHY4TEJIUnhhOUhvVHo1RXZLQnB1OVJoemt5dStUaFczb0xta2ZTRmNJcitHa0M5MW0zOApFelRmVE5JY2dsL0V5YkpmR1QvdGdUazZYd1kxY1UrUUdmSEFNNTBCVzFXTFVHc25CSllJZjA5eENnZTVoTkxLCnREeUJOWWNQZzg1bUJpOU9CNFJ2UlgyQVFRMjJwZ0xrQUpJWklOU0FEdUFrODN2b051SXM2YVY2bHBkR2Vva3YKdDlpTFdNR3p3a3ZTZUZQTlNGeWZ3Q055eENjb1FDQUNtSnJRS3NoeUE2bWNzdVhORWVXdlRQdVVFSWZPVFl4dwpxdkszRVBOK0xUYlA2anhUMWtTcFJUOSt4Z29uSlFhU0RsbUNBd20zRGJkSVppWUt3R2ppMkxKL0kvYWc0cTlzCjNLb0J2UUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcVVrU21jdW85OG5EK015b005VFdEV0pyTndySXpQTUNqRQpCSkdyREhVaHIwcEZlRjc0RHViODNzRXlaNjFxNUVQd2Y0enNLSzdzdDRUTzZhcE9pZWJYVmN3dnZoa09HQ2dFCmFVdGNOMjFhUGxtU0tOd0c4ai8yK3ZhbU80bGplK1NnZzRUUVB0eDZWejh5VXN2RFhxSUZycjNNd1gzSDA1RW4KWXAzN05JYkhKbGxHUW5LVHA5aTg5aXF4WXVhSERqZldiVHlEY3B5NldNVjdVaFYvY1plc3lGL0NBamNHd1V6YgowRlo5bW5tMnFONlBGWHZ4RmdMSGFWZzN2SVVCbkNmVVVyY1BDNE94VFNPK21aUmUxazh3eUFpVWovSk0rZllvCkcrMi9sbThUYVZqb1U3Rmk1S2E1RzVIWTJHTGFSN1ArSXhZY3JNSENsNjJZN1JxY3JuYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= -`), - "configWithLinkedCert": []byte(` -apiVersion: v1 -clusters: -- cluster: - server: https://10.0.2.15:6443 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: system:node:mynode - name: system:node:mynode@kubernetes -current-context: system:node:mynode@kubernetes -kind: Config -users: -- name: system:node:mynode - user: - client-certificate: kubelet.pem -`), - "configWithInvalidContext": []byte(` -apiVersion: v1 -clusters: -- cluster: - server: https://10.0.2.15:6443 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: system:node:mynode - name: system:node:mynode@kubernetes -current-context: invalidContext -kind: Config -users: -- name: system:node:mynode - user: - client-certificate: kubelet.pem -`), - "configWithInvalidUser": []byte(` -apiVersion: v1 -clusters: -- cluster: - server: https://10.0.2.15:6443 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: invalidUser - name: system:node:mynode@kubernetes -current-context: system:node:mynode@kubernetes -kind: Config -users: -- name: system:node:mynode - user: - client-certificate: kubelet.pem -`), -} +var ( + //go:embed testdata/kubelet-without-cert.yaml + configWithoutCert []byte -var pemFiles = map[string][]byte{ - "mynode.pem": []byte(` ------BEGIN CERTIFICATE----- -MIIC8jCCAdqgAwIBAgIIAiwUDabNogQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE -AxMKa3ViZXJuZXRlczAeFw0xODA5MDExNTMxMjZaFw0xOTA5MDExNjAxNTlaMDQx -FTATBgNVBAoTDHN5c3RlbTpub2RlczEbMBkGA1UEAxMSc3lzdGVtOm5vZGU6bXlu -b2RlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6Qu3y+r4FXS6xVJ -YMo4OdJKwGWu0v8LBHRxa9HoTz5EvKBpu9Rhzkyu+ThW3oLmkfSFcIr+GkC91m38 -EzTfTNIcgl/EybJfGT/tgTk6XwY1cU+QGfHAM50BW1WLUGsnBJYIf09xCge5hNLK -tDyBNYcPg85mBi9OB4RvRX2AQQ22pgLkAJIZINSADuAk83voNuIs6aV6lpdGeokv -t9iLWMGzwkvSeFPNSFyfwCNyxCcoQCACmJrQKshyA6mcsuXNEeWvTPuUEIfOTYxw -qvK3EPN+LTbP6jxT1kSpRT9+xgonJQaSDlmCAwm3DbdIZiYKwGji2LJ/I/ag4q9s -3KoBvQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH -AwIwDQYJKoZIhvcNAQELBQADggEBAKqUkSmcuo98nD+MyoM9TWDWJrNwrIzPMCjE -BJGrDHUhr0pFeF74Dub83sEyZ61q5EPwf4zsKK7st4TO6apOiebXVcwvvhkOGCgE -aUtcN21aPlmSKNwG8j/2+vamO4lje+Sgg4TQPtx6Vz8yUsvDXqIFrr3MwX3H05En -Yp37NIbHJllGQnKTp9i89iqxYuaHDjfWbTyDcpy6WMV7UhV/cZesyF/CAjcGwUzb -0FZ9mnm2qN6PFXvxFgLHaVg3vIUBnCfUUrcPC4OxTSO+mZRe1k8wyAiUj/JM+fYo -G+2/lm8TaVjoU7Fi5Ka5G5HY2GLaR7P+IxYcrMHCl62Y7Rqcrnc= ------END CERTIFICATE----- -`), -} + //go:embed testdata/kubelet-with-embedded-cert.yaml + configWithEmbeddedCert []byte + + //go:embed testdata/kubelet-with-linked-cert.yaml + configWithLinkedCert []byte + + //go:embed testdata/kubelet-with-invalid-context.yaml + configWithInvalidContext []byte + + //go:embed testdata/kubelet-with-invalid-user.yaml + configWithInvalidUser []byte +) + +//go:embed testdata/mynode.pem +var mynodePem []byte func TestGetNodeNameFromKubeletConfig(t *testing.T) { tmpdir, err := os.MkdirTemp("", "") @@ -205,31 +111,31 @@ func TestGetNodeNameFromKubeletConfig(t *testing.T) { }{ { name: "valid - with embedded cert", - kubeconfigContent: kubeletConfFiles["configWithEmbeddedCert"], + kubeconfigContent: configWithEmbeddedCert, }, { name: "invalid - linked cert missing", - kubeconfigContent: kubeletConfFiles["configWithLinkedCert"], + kubeconfigContent: configWithLinkedCert, expectedError: true, }, { name: "valid - with linked cert", - kubeconfigContent: kubeletConfFiles["configWithLinkedCert"], - pemContent: pemFiles["mynode.pem"], + kubeconfigContent: configWithLinkedCert, + pemContent: mynodePem, }, { name: "invalid - without embedded or linked X509Cert", - kubeconfigContent: kubeletConfFiles["withoutX509Cert"], + kubeconfigContent: configWithoutCert, expectedError: true, }, { name: "invalid - the current context is invalid", - kubeconfigContent: kubeletConfFiles["configWithInvalidContext"], + kubeconfigContent: configWithInvalidContext, expectedError: true, }, { name: "invalid - the user of the current context is invalid", - kubeconfigContent: kubeletConfFiles["configWithInvalidUser"], + kubeconfigContent: configWithInvalidUser, expectedError: true, }, } @@ -288,7 +194,7 @@ func TestGetNodeRegistration(t *testing.T) { }, { name: "valid", - fileContents: kubeletConfFiles["configWithEmbeddedCert"], + fileContents: configWithEmbeddedCert, node: &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, @@ -303,7 +209,7 @@ func TestGetNodeRegistration(t *testing.T) { }, { name: "invalid - no node", - fileContents: kubeletConfFiles["configWithEmbeddedCert"], + fileContents: configWithEmbeddedCert, expectedError: true, }, } @@ -545,7 +451,7 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { }, }, }, - fileContents: kubeletConfFiles["configWithEmbeddedCert"], + fileContents: configWithEmbeddedCert, node: &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, diff --git a/cmd/kubeadm/app/util/config/common.go b/cmd/kubeadm/app/util/config/common.go index 3fda0e89dd991..f8666754c8555 100644 --- a/cmd/kubeadm/app/util/config/common.go +++ b/cmd/kubeadm/app/util/config/common.go @@ -322,12 +322,28 @@ func MigrateOldConfig(oldConfig []byte, allowExperimental bool, mutators migrate newConfig = append(newConfig, b) } + // Migrate UpgradeConfiguration if there is any + if kubeadmutil.GroupVersionKindsHasUpgradeConfiguration(gvks...) { + o, err := documentMapToUpgradeConfiguration(gvkmap, true, allowExperimental, true) + if err != nil { + return []byte{}, err + } + if err := mutators.mutate([]any{o}); err != nil { + return []byte{}, err + } + b, err := MarshalKubeadmConfigObject(o, gv) + if err != nil { + return []byte{}, err + } + newConfig = append(newConfig, b) + } + return bytes.Join(newConfig, []byte(constants.YAMLDocumentSeparator)), nil } // ValidateConfig takes a byte slice containing a kubeadm configuration and performs conversion // to internal types and validation. -func ValidateConfig(config []byte, allowExperimental bool) error { +func ValidateConfig(config []byte, allowDeprecated, allowExperimental bool) error { gvkmap, err := kubeadmutil.SplitConfigDocuments(config) if err != nil { return err @@ -344,21 +360,28 @@ func ValidateConfig(config []byte, allowExperimental bool) error { // Validate InitConfiguration and ClusterConfiguration if there are any in the config if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvks...) { - if _, err := documentMapToInitConfiguration(gvkmap, true, allowExperimental, true, true); err != nil { + if _, err := documentMapToInitConfiguration(gvkmap, allowDeprecated, allowExperimental, true, true); err != nil { return err } } // Validate JoinConfiguration if there is any if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) { - if _, err := documentMapToJoinConfiguration(gvkmap, true, allowExperimental, true, true); err != nil { + if _, err := documentMapToJoinConfiguration(gvkmap, allowDeprecated, allowExperimental, true, true); err != nil { return err } } // Validate ResetConfiguration if there is any if kubeadmutil.GroupVersionKindsHasResetConfiguration(gvks...) { - if _, err := documentMapToResetConfiguration(gvkmap, true, allowExperimental, true, true); err != nil { + if _, err := documentMapToResetConfiguration(gvkmap, allowDeprecated, allowExperimental, true, true); err != nil { + return err + } + } + + // Validate UpgradeConfiguration if there is any + if kubeadmutil.GroupVersionKindsHasUpgradeConfiguration(gvks...) { + if _, err := documentMapToUpgradeConfiguration(gvkmap, allowDeprecated, allowExperimental, true); err != nil { return err } } diff --git a/cmd/kubeadm/app/util/config/common_test.go b/cmd/kubeadm/app/util/config/common_test.go index 8f8692cd7dda9..cf3b9907c456a 100644 --- a/cmd/kubeadm/app/util/config/common_test.go +++ b/cmd/kubeadm/app/util/config/common_test.go @@ -726,7 +726,7 @@ func TestValidateConfig(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := ValidateConfig([]byte(test.cfg), test.allowExperimental) + err := ValidateConfig([]byte(test.cfg), true, test.allowExperimental) if (err != nil) != test.expectedError { t.Fatalf("expected error: %v, got: %v, error: %v", test.expectedError, (err != nil), err) } diff --git a/cmd/kubeadm/app/util/config/initconfiguration.go b/cmd/kubeadm/app/util/config/initconfiguration.go index 94bb650c4dcbf..da52e8214993f 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration.go +++ b/cmd/kubeadm/app/util/config/initconfiguration.go @@ -56,16 +56,18 @@ var ( } ) -// SetInitDynamicDefaults checks and sets configuration values for the InitConfiguration object -func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration, skipCRIDetect bool) error { +// SetInitDynamicDefaults checks and sets configuration values for the InitConfiguration object. +func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration, skipCRIDetect, skipAPIEndpoint bool) error { if err := SetBootstrapTokensDynamicDefaults(&cfg.BootstrapTokens); err != nil { return err } if err := SetNodeRegistrationDynamicDefaults(&cfg.NodeRegistration, true, skipCRIDetect); err != nil { return err } - if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil { - return err + if !skipAPIEndpoint { + if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil { + return err + } } return SetClusterDynamicDefaults(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, &cfg.NodeRegistration) } @@ -245,7 +247,7 @@ func DefaultedInitConfiguration(versionedInitCfg *kubeadmapiv1.InitConfiguration } // Applies dynamic defaults to settings not provided with flags - if err := SetInitDynamicDefaults(internalcfg, opts.SkipCRIDetect); err != nil { + if err := SetInitDynamicDefaults(internalcfg, opts.SkipCRIDetect, false); err != nil { return nil, err } // Validates cfg (flags/configs + defaults + dynamic defaults) @@ -400,7 +402,7 @@ func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecat } // Applies dynamic defaults to settings not provided with flags - if err := SetInitDynamicDefaults(initcfg, skipCRIDetect); err != nil { + if err := SetInitDynamicDefaults(initcfg, skipCRIDetect, false); err != nil { return nil, err } diff --git a/cmd/kubeadm/app/util/config/initconfiguration_test.go b/cmd/kubeadm/app/util/config/initconfiguration_test.go index 8b95c4260dfa6..c2322bd461f38 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration_test.go +++ b/cmd/kubeadm/app/util/config/initconfiguration_test.go @@ -314,3 +314,56 @@ func TestBytesToInitConfiguration(t *testing.T) { } } } + +func TestSetInitDynamicDefaultsSkipAPIEndpoint(t *testing.T) { + // "not-an-ip" is a sentinel that would cause SetAPIEndpointDynamicDefaults to return + // an error if invoked. With skipAPIEndpoint=true, the value must be left untouched. + const sentinel = "not-an-ip" + + tests := []struct { + name string + skipAPIEndpoint bool + expectErr bool + expectAdvertise string + }{ + { + name: "skip leaves AdvertiseAddress untouched", + skipAPIEndpoint: true, + expectErr: false, + expectAdvertise: sentinel, + }, + { + name: "no skip surfaces invalid AdvertiseAddress error", + skipAPIEndpoint: false, + expectErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := &kubeadmapi.InitConfiguration{ + ClusterConfiguration: kubeadmapi.ClusterConfiguration{ + KubernetesVersion: constants.CurrentKubernetesVersion.String(), + }, + LocalAPIEndpoint: kubeadmapi.APIEndpoint{ + AdvertiseAddress: sentinel, + }, + } + + err := SetInitDynamicDefaults(cfg, true /* skipCRIDetect */, tc.skipAPIEndpoint) + if tc.expectErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if cfg.LocalAPIEndpoint.AdvertiseAddress != tc.expectAdvertise { + t.Errorf("AdvertiseAddress = %q, want %q", + cfg.LocalAPIEndpoint.AdvertiseAddress, tc.expectAdvertise) + } + }) + } +} diff --git a/cmd/kubeadm/app/util/config/testdata/kubelet-with-embedded-cert.yaml b/cmd/kubeadm/app/util/config/testdata/kubelet-with-embedded-cert.yaml new file mode 100644 index 0000000000000..9d18497a889c9 --- /dev/null +++ b/cmd/kubeadm/app/util/config/testdata/kubelet-with-embedded-cert.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +clusters: +- cluster: + server: https://10.0.2.15:6443 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: system:node:mynode + name: system:node:mynode@kubernetes +current-context: system:node:mynode@kubernetes +kind: Config +preferences: {} +users: +- name: system:node:mynode + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQWl3VURhYk5vZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ERXhOVE14TWpaYUZ3MHhPVEE1TURFeE5qQXhOVGxhTURReApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFYk1Ca0dBMVVFQXhNU2MzbHpkR1Z0T201dlpHVTZiWGx1CmIyUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWs2UXUzeStyNEZYUzZ4VkoKWU1vNE9kSkt3R1d1MHY4TEJIUnhhOUhvVHo1RXZLQnB1OVJoemt5dStUaFczb0xta2ZTRmNJcitHa0M5MW0zOApFelRmVE5JY2dsL0V5YkpmR1QvdGdUazZYd1kxY1UrUUdmSEFNNTBCVzFXTFVHc25CSllJZjA5eENnZTVoTkxLCnREeUJOWWNQZzg1bUJpOU9CNFJ2UlgyQVFRMjJwZ0xrQUpJWklOU0FEdUFrODN2b051SXM2YVY2bHBkR2Vva3YKdDlpTFdNR3p3a3ZTZUZQTlNGeWZ3Q055eENjb1FDQUNtSnJRS3NoeUE2bWNzdVhORWVXdlRQdVVFSWZPVFl4dwpxdkszRVBOK0xUYlA2anhUMWtTcFJUOSt4Z29uSlFhU0RsbUNBd20zRGJkSVppWUt3R2ppMkxKL0kvYWc0cTlzCjNLb0J2UUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcVVrU21jdW85OG5EK015b005VFdEV0pyTndySXpQTUNqRQpCSkdyREhVaHIwcEZlRjc0RHViODNzRXlaNjFxNUVQd2Y0enNLSzdzdDRUTzZhcE9pZWJYVmN3dnZoa09HQ2dFCmFVdGNOMjFhUGxtU0tOd0c4ai8yK3ZhbU80bGplK1NnZzRUUVB0eDZWejh5VXN2RFhxSUZycjNNd1gzSDA1RW4KWXAzN05JYkhKbGxHUW5LVHA5aTg5aXF4WXVhSERqZldiVHlEY3B5NldNVjdVaFYvY1plc3lGL0NBamNHd1V6YgowRlo5bW5tMnFONlBGWHZ4RmdMSGFWZzN2SVVCbkNmVVVyY1BDNE94VFNPK21aUmUxazh3eUFpVWovSk0rZllvCkcrMi9sbThUYVZqb1U3Rmk1S2E1RzVIWTJHTGFSN1ArSXhZY3JNSENsNjJZN1JxY3JuYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= diff --git a/cmd/kubeadm/app/util/config/testdata/kubelet-with-invalid-context.yaml b/cmd/kubeadm/app/util/config/testdata/kubelet-with-invalid-context.yaml new file mode 100644 index 0000000000000..29fb2c8e25b5c --- /dev/null +++ b/cmd/kubeadm/app/util/config/testdata/kubelet-with-invalid-context.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +clusters: +- cluster: + server: https://10.0.2.15:6443 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: system:node:mynode + name: system:node:mynode@kubernetes +current-context: invalidContext +kind: Config +preferences: {} +users: +- name: system:node:mynode + user: + client-certificate: kubelet.pem diff --git a/cmd/kubeadm/app/util/config/testdata/kubelet-with-invalid-user.yaml b/cmd/kubeadm/app/util/config/testdata/kubelet-with-invalid-user.yaml new file mode 100644 index 0000000000000..5ee4186433da0 --- /dev/null +++ b/cmd/kubeadm/app/util/config/testdata/kubelet-with-invalid-user.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +clusters: +- cluster: + server: https://10.0.2.15:6443 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: invalidUser + name: system:node:mynode@kubernetes +current-context: system:node:mynode@kubernetes +kind: Config +preferences: {} +users: +- name: system:node:mynode + user: + client-certificate: kubelet.pem diff --git a/cmd/kubeadm/app/util/config/testdata/kubelet-with-linked-cert.yaml b/cmd/kubeadm/app/util/config/testdata/kubelet-with-linked-cert.yaml new file mode 100644 index 0000000000000..40b67368fdb62 --- /dev/null +++ b/cmd/kubeadm/app/util/config/testdata/kubelet-with-linked-cert.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +clusters: +- cluster: + server: https://10.0.2.15:6443 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: system:node:mynode + name: system:node:mynode@kubernetes +current-context: system:node:mynode@kubernetes +kind: Config +preferences: {} +users: +- name: system:node:mynode + user: + client-certificate: kubelet.pem diff --git a/cmd/kubeadm/app/util/config/testdata/kubelet-without-cert.yaml b/cmd/kubeadm/app/util/config/testdata/kubelet-without-cert.yaml new file mode 100644 index 0000000000000..30cddb9999b5e --- /dev/null +++ b/cmd/kubeadm/app/util/config/testdata/kubelet-without-cert.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +clusters: +- cluster: + server: https://10.0.2.15:6443 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: system:node:mynode + name: system:node:mynode@kubernetes +current-context: system:node:mynode@kubernetes +kind: Config +preferences: {} +users: +- name: system:node:mynode + user: diff --git a/cmd/kubeadm/app/util/config/testdata/mynode.pem b/cmd/kubeadm/app/util/config/testdata/mynode.pem new file mode 100644 index 0000000000000..cca527ef3c460 --- /dev/null +++ b/cmd/kubeadm/app/util/config/testdata/mynode.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC8jCCAdqgAwIBAgIIAiwUDabNogQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE +AxMKa3ViZXJuZXRlczAeFw0xODA5MDExNTMxMjZaFw0xOTA5MDExNjAxNTlaMDQx +FTATBgNVBAoTDHN5c3RlbTpub2RlczEbMBkGA1UEAxMSc3lzdGVtOm5vZGU6bXlu +b2RlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6Qu3y+r4FXS6xVJ +YMo4OdJKwGWu0v8LBHRxa9HoTz5EvKBpu9Rhzkyu+ThW3oLmkfSFcIr+GkC91m38 +EzTfTNIcgl/EybJfGT/tgTk6XwY1cU+QGfHAM50BW1WLUGsnBJYIf09xCge5hNLK +tDyBNYcPg85mBi9OB4RvRX2AQQ22pgLkAJIZINSADuAk83voNuIs6aV6lpdGeokv +t9iLWMGzwkvSeFPNSFyfwCNyxCcoQCACmJrQKshyA6mcsuXNEeWvTPuUEIfOTYxw +qvK3EPN+LTbP6jxT1kSpRT9+xgonJQaSDlmCAwm3DbdIZiYKwGji2LJ/I/ag4q9s +3KoBvQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH +AwIwDQYJKoZIhvcNAQELBQADggEBAKqUkSmcuo98nD+MyoM9TWDWJrNwrIzPMCjE +BJGrDHUhr0pFeF74Dub83sEyZ61q5EPwf4zsKK7st4TO6apOiebXVcwvvhkOGCgE +aUtcN21aPlmSKNwG8j/2+vamO4lje+Sgg4TQPtx6Vz8yUsvDXqIFrr3MwX3H05En +Yp37NIbHJllGQnKTp9i89iqxYuaHDjfWbTyDcpy6WMV7UhV/cZesyF/CAjcGwUzb +0FZ9mnm2qN6PFXvxFgLHaVg3vIUBnCfUUrcPC4OxTSO+mZRe1k8wyAiUj/JM+fYo +G+2/lm8TaVjoU7Fi5Ka5G5HY2GLaR7P+IxYcrMHCl62Y7Rqcrnc= +-----END CERTIFICATE----- diff --git a/cmd/kubeadm/app/util/config/upgradeconfiguration.go b/cmd/kubeadm/app/util/config/upgradeconfiguration.go index 3b4b56909f686..43f5fb004f5e5 100644 --- a/cmd/kubeadm/app/util/config/upgradeconfiguration.go +++ b/cmd/kubeadm/app/util/config/upgradeconfiguration.go @@ -34,7 +34,7 @@ import ( // documentMapToUpgradeConfiguration takes a map between GVKs and YAML/JSON documents (as returned by SplitYAMLDocuments), // finds a UpgradeConfiguration, decodes it, dynamically defaults it and then validates it prior to return. -func documentMapToUpgradeConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated bool) (*kubeadmapi.UpgradeConfiguration, error) { +func documentMapToUpgradeConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated, allowExperimental, strictErrors bool) (*kubeadmapi.UpgradeConfiguration, error) { upgradeBytes := []byte{} for gvk, bytes := range gvkmap { @@ -44,13 +44,17 @@ func documentMapToUpgradeConfiguration(gvkmap kubeadmapi.DocumentMap, allowDepre } // check if this version is supported and possibly not deprecated - if err := validateSupportedVersion(gvk, allowDeprecated, true); err != nil { + if err := validateSupportedVersion(gvk, allowDeprecated, allowExperimental); err != nil { return nil, err } // verify the validity of the YAML/JSON if err := strict.VerifyUnmarshalStrict([]*runtime.Scheme{kubeadmscheme.Scheme}, gvk, bytes); err != nil { - klog.Warning(err.Error()) + if !strictErrors { + klog.Warning(err.Error()) + } else { + return nil, err + } } upgradeBytes = bytes @@ -107,7 +111,7 @@ func BytesToUpgradeConfiguration(b []byte) (*kubeadmapi.UpgradeConfiguration, er return nil, err } - return documentMapToUpgradeConfiguration(gvkmap, false) + return documentMapToUpgradeConfiguration(gvkmap, false, false, false) } // LoadOrDefaultUpgradeConfiguration takes a path to a config file and a versioned configuration that can serve as the default config diff --git a/cmd/kubeadm/app/util/copy_unix.go b/cmd/kubeadm/app/util/copy_unix.go index 0d75b09dd0eb6..c015a289fd7dc 100644 --- a/cmd/kubeadm/app/util/copy_unix.go +++ b/cmd/kubeadm/app/util/copy_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/copy_windows.go b/cmd/kubeadm/app/util/copy_windows.go index e20f4f7b93651..d0d9df99fc4b5 100644 --- a/cmd/kubeadm/app/util/copy_windows.go +++ b/cmd/kubeadm/app/util/copy_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2022 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/etcd/etcd.go b/cmd/kubeadm/app/util/etcd/etcd.go index 3ac966c70bf6f..e3da49c0c6d65 100644 --- a/cmd/kubeadm/app/util/etcd/etcd.go +++ b/cmd/kubeadm/app/util/etcd/etcd.go @@ -23,6 +23,7 @@ import ( "net" "net/url" "path/filepath" + "slices" "strconv" "strings" "time" @@ -35,6 +36,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" @@ -101,6 +103,13 @@ type Client struct { listMembersFunc func(timeout time.Duration) (*clientv3.MemberListResponse, error) } +type etcdMemberStatus struct { + ep string + status *clientv3.StatusResponse + // err is any error encountered while communicating with the etcd server. + err error +} + // New creates a new EtcdCluster client func New(endpoints []string, ca, cert, key string) (*Client, error) { client := Client{Endpoints: endpoints} @@ -399,7 +408,7 @@ func (c *Client) RemoveMember(id uint64) ([]Member, error) { respMembers = resp.Members return true, nil } - if errors.Is(rpctypes.ErrMemberNotFound, err) { + if errors.Is(err, rpctypes.ErrMemberNotFound) { klog.V(5).Infof("Member was already removed, because member %s was not found", strconv.FormatUint(id, 16)) listResp, err = cli.MemberList(ctx) if err == nil { @@ -535,7 +544,7 @@ func (c *Client) addMember(name string, peerAddrs string, isLearner bool) ([]Mem if !isLearner { // Add the new member client address to the list of endpoints - c.Endpoints = append(c.Endpoints, GetClientURLByIP(parsedPeerAddrs.Hostname())) + c.addEndpoint(GetClientURLByIP(parsedPeerAddrs.Hostname())) } return ret, nil @@ -617,11 +626,12 @@ func (c *Client) MemberPromote(learnerID uint64) error { // 2. context deadline exceeded // 3. peer URLs already exists // Once the client provides a way to check if the etcd learner is ready to promote, the retry logic can be revisited. + var promoteResp *clientv3.MemberPromoteResponse err = wait.PollUntilContextTimeout(context.Background(), constants.EtcdAPICallRetryInterval, kubeadmapi.GetActiveTimeouts().EtcdAPICall.Duration, true, func(_ context.Context) (bool, error) { ctx, cancel := context.WithTimeout(context.Background(), etcdTimeout) defer cancel() - _, err = cli.MemberPromote(ctx, learnerID) + promoteResp, err = cli.MemberPromote(ctx, learnerID) if err == nil { klog.V(1).Infof("[etcd] The learner was promoted as a voting member: %s", learnerIDUint) return true, nil @@ -633,18 +643,52 @@ func (c *Client) MemberPromote(learnerID uint64) error { if err != nil { return lastError } + + for _, m := range promoteResp.Members { + if m.ID == learnerID { + parsedPeerAddrs, err := url.Parse(m.PeerURLs[0]) + if err != nil { + return errors.Wrapf(err, "error parsing peer address %s", m.PeerURLs[0]) + } + c.addEndpoint(GetClientURLByIP(parsedPeerAddrs.Hostname())) + break + } + } + return nil } +func (c *Client) addEndpoint(ep string) { + if slices.Contains(c.Endpoints, ep) { + return + } + c.Endpoints = append(c.Endpoints, ep) +} + // CheckClusterHealth returns nil for status Up or error for status Down func (c *Client) CheckClusterHealth() error { - _, err := c.getClusterStatus() + _, ok, err := c.getClusterStatus() + if err != nil { + klog.V(1).Infof("[etcd] cluster has quorum: %t; some members are not healthy: %v\n", ok, err) + } + if ok { + return nil + } return err } -// getClusterStatus returns nil for status Up (along with endpoint status response map) or error for status Down -func (c *Client) getClusterStatus() (map[string]*clientv3.StatusResponse, error) { - clusterStatus := make(map[string]*clientv3.StatusResponse) +// getClusterStatus checks the health of the cluster members and returns +// their individual status map, whether cluster quorum is satisfied, and any +// aggregated member errors. +// +// The boolean result is true when a majority of members are healthy +// (healthyCount > totalCount/2). +// +// A member is considered unhealthy if its status request failed or if the +// reported status contains health errors. +func (c *Client) getClusterStatus() (map[string]*etcdMemberStatus, bool, error) { + // Step 1: get the cluster status first + clusterStatus := make(map[string]*etcdMemberStatus) for _, ep := range c.Endpoints { // Gets the member status var lastError error @@ -653,6 +697,7 @@ func (c *Client) getClusterStatus() (map[string]*clientv3.StatusResponse, error) true, func(_ context.Context) (bool, error) { cli, err := c.newEtcdClient(c.Endpoints) if err != nil { + klog.V(5).Infof("Failed to create etcd client with %v: %v", c.Endpoints, err) lastError = err return false, nil } @@ -669,33 +714,47 @@ func (c *Client) getClusterStatus() (map[string]*clientv3.StatusResponse, error) return false, nil }) if err != nil { - return nil, lastError + clusterStatus[ep] = &etcdMemberStatus{ep: ep, err: lastError} + } else { + clusterStatus[ep] = &etcdMemberStatus{ep: ep, status: resp} } + } + + // Step 2: evaluate the cluster status + totalCount, healthyCount := len(clusterStatus), 0 + var memberErrs []error - clusterStatus[ep] = resp + for ep, epStatus := range clusterStatus { + if epStatus.err != nil { + memberErrs = append(memberErrs, errors.Wrapf(epStatus.err, "the status of member %s is not available", ep)) + continue + } + if len(epStatus.status.Errors) > 0 { + memberErrs = append(memberErrs, errors.Errorf("member %s is not healthy: %s", ep, strings.Join(epStatus.status.Errors, ","))) + continue + } + healthyCount++ } - return clusterStatus, nil + + err := utilerrors.NewAggregate(memberErrs) + return clusterStatus, healthyCount > totalCount/2, err } -// WaitForClusterAvailable returns true if all endpoints in the cluster are available after retry attempts, an error is returned otherwise +// WaitForClusterAvailable returns true if the etcd cluster is healthy after retry attempts, otherwise returns an error. func (c *Client) WaitForClusterAvailable(retries int, retryInterval time.Duration) (bool, error) { - for i := 0; i < retries; i++ { + for i := range retries { if i > 0 { klog.V(1).Infof("[etcd] Waiting %v until next retry\n", retryInterval) time.Sleep(retryInterval) } klog.V(2).Infof("[etcd] attempting to see if all cluster endpoints (%s) are available %d/%d", c.Endpoints, i+1, retries) - _, err := c.getClusterStatus() + _, ok, err := c.getClusterStatus() if err != nil { - switch err { - case context.DeadlineExceeded: - klog.V(1).Infof("[etcd] Attempt timed out") - default: - klog.V(1).Infof("[etcd] Attempt failed with error: %v\n", err) - } - continue + klog.V(1).Infof("[etcd] cluster has quorum: %t; some members are not healthy: %v\n", ok, err) + } + if ok { + return true, nil } - return true, nil } return false, errors.New("timeout waiting for etcd cluster to be available") } diff --git a/cmd/kubeadm/app/util/etcd/etcd_test.go b/cmd/kubeadm/app/util/etcd/etcd_test.go index 67671a994d29c..cbf1a97463a5f 100644 --- a/cmd/kubeadm/app/util/etcd/etcd_test.go +++ b/cmd/kubeadm/app/util/etcd/etcd_test.go @@ -28,6 +28,7 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" clientsetfake "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" @@ -823,3 +824,144 @@ func TestGetMemberStatus(t *testing.T) { }) } } + +type fakeEtcdClientWithStatusResponse struct { + fakeEtcdClient + statusResponses map[string]*clientv3.StatusResponse + statusRequestErrors map[string]error +} + +// Status gets the status of the endpoint. +func (f *fakeEtcdClientWithStatusResponse) Status(_ context.Context, ep string) (*clientv3.StatusResponse, error) { + if f.statusRequestErrors != nil { + if _, ok := f.statusRequestErrors[ep]; ok { + return nil, f.statusRequestErrors[ep] + } + } + return f.statusResponses[ep], nil +} + +func TestEvaluateClusterStatus(t *testing.T) { + testCases := []struct { + name string + Endpoints []string + newEtcdClient func(endpoints []string) (etcdClient, error) + wantClusterHealthy bool + wantMemberErrors bool + }{ + { + name: "all the three members are healthy", + Endpoints: []string{"https://192.168.10.100:2379", "https://192.168.10.200:2379", "https://192.168.10.300:2379"}, + newEtcdClient: func(endpoints []string) (etcdClient, error) { + f := &fakeEtcdClientWithStatusResponse{ + statusResponses: map[string]*clientv3.StatusResponse{ + "https://192.168.10.100:2379": {}, + "https://192.168.10.200:2379": {}, + "https://192.168.10.300:2379": {}, + }, + } + return f, nil + }, + wantClusterHealthy: true, + wantMemberErrors: false, + }, + { + name: "one out of three members has errors", + Endpoints: []string{"https://192.168.10.100:2379", "https://192.168.10.200:2379", "https://192.168.10.300:2379"}, + newEtcdClient: func(endpoints []string) (etcdClient, error) { + f := &fakeEtcdClientWithStatusResponse{ + statusResponses: map[string]*clientv3.StatusResponse{ + "https://192.168.10.100:2379": {}, + "https://192.168.10.200:2379": {Errors: []string{"etcdserver: mvcc: database space exceeded"}}, + "https://192.168.10.300:2379": {}, + }, + } + return f, nil + }, + wantClusterHealthy: true, + wantMemberErrors: true, + }, + { + name: "one out of three members is unreachable", + Endpoints: []string{"https://192.168.10.100:2379", "https://192.168.10.200:2379", "https://192.168.10.300:2379"}, + newEtcdClient: func(endpoints []string) (etcdClient, error) { + f := &fakeEtcdClientWithStatusResponse{ + statusResponses: map[string]*clientv3.StatusResponse{ + "https://192.168.10.100:2379": {}, + "https://192.168.10.200:2379": {}, + "https://192.168.10.300:2379": {}, + }, + statusRequestErrors: map[string]error{ + "https://192.168.10.200:2379": errors.New("context deadline exceeded"), + }, + } + return f, nil + }, + wantClusterHealthy: true, + wantMemberErrors: true, + }, + { + name: "two out of three members has errors", + Endpoints: []string{"https://192.168.10.100:2379", "https://192.168.10.200:2379", "https://192.168.10.300:2379"}, + newEtcdClient: func(endpoints []string) (etcdClient, error) { + f := &fakeEtcdClientWithStatusResponse{ + statusResponses: map[string]*clientv3.StatusResponse{ + "https://192.168.10.100:2379": {}, + "https://192.168.10.200:2379": {Errors: []string{"etcdserver: mvcc: database space exceeded"}}, + "https://192.168.10.300:2379": {Errors: []string{"etcdserver: mvcc: data corrupted"}}, + }, + } + return f, nil + }, + wantClusterHealthy: false, + wantMemberErrors: true, + }, + { + name: "two out of three members are unreachable", + Endpoints: []string{"https://192.168.10.100:2379", "https://192.168.10.200:2379", "https://192.168.10.300:2379"}, + newEtcdClient: func(endpoints []string) (etcdClient, error) { + f := &fakeEtcdClientWithStatusResponse{ + statusResponses: map[string]*clientv3.StatusResponse{ + "https://192.168.10.100:2379": {}, + "https://192.168.10.200:2379": {}, + "https://192.168.10.300:2379": {}, + }, + statusRequestErrors: map[string]error{ + "https://192.168.10.200:2379": errors.New("context deadline exceeded"), + "https://192.168.10.300:2379": errors.New("context deadline exceeded"), + }, + } + return f, nil + }, + wantClusterHealthy: false, + wantMemberErrors: true, + }, + } + + // Temporarily reduce the etcd API call timeout from 2 minutes to 1 second. + oldActiveTimeout := kubeadmapi.GetActiveTimeouts() + newActiveTimeout := oldActiveTimeout.DeepCopy() + newActiveTimeout.EtcdAPICall = &metav1.Duration{Duration: 1 * time.Second} + kubeadmapi.SetActiveTimeouts(newActiveTimeout) + defer func() { + kubeadmapi.SetActiveTimeouts(oldActiveTimeout) + }() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := &Client{ + Endpoints: tc.Endpoints, + newEtcdClient: tc.newEtcdClient, + } + _, gotClusterHealthy, err := c.getClusterStatus() + + if gotClusterHealthy != tc.wantClusterHealthy { + t.Errorf("gotClusterHealthy = %t, want = %t", gotClusterHealthy, tc.wantClusterHealthy) + } + + if tc.wantMemberErrors != (err != nil) { + t.Errorf("gotMemberErrors = %v, wantMemberErrors = %t", err, tc.wantMemberErrors) + } + }) + } +} diff --git a/cmd/kubeadm/app/util/initsystem/initsystem_unix.go b/cmd/kubeadm/app/util/initsystem/initsystem_unix.go index 2ad4fdad66378..355adfd83c71c 100644 --- a/cmd/kubeadm/app/util/initsystem/initsystem_unix.go +++ b/cmd/kubeadm/app/util/initsystem/initsystem_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/initsystem/initsystem_windows.go b/cmd/kubeadm/app/util/initsystem/initsystem_windows.go index cefa462b01e03..0c8d4241efbdb 100644 --- a/cmd/kubeadm/app/util/initsystem/initsystem_windows.go +++ b/cmd/kubeadm/app/util/initsystem/initsystem_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go index 1b943645720d3..3ab63468b38e1 100644 --- a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go @@ -18,12 +18,15 @@ package kubeconfig import ( "fmt" + "net" "os" + "strconv" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" ) @@ -121,6 +124,18 @@ func GetClusterFromKubeConfig(config *clientcmdapi.Config) (string, *clientcmdap return "", nil, errors.Errorf("the current context is invalid: %s", config.CurrentContext) } +// PointKubeConfigToLocalAPIEndpoint modifies the provided kubeconfig to point to the given APIEndpoint. +func PointKubeConfigToLocalAPIEndpoint(config *clientcmdapi.Config, lae *kubeadmapi.APIEndpoint) { + for _, v := range config.Clusters { + v.Server = fmt.Sprintf("https://%s", + net.JoinHostPort( + lae.AdvertiseAddress, + strconv.Itoa(int(lae.BindPort)), + ), + ) + } +} + // HasAuthenticationCredentials returns true if the current user has valid authentication credentials for // token authentication, basic authentication or X509 authentication func HasAuthenticationCredentials(config *clientcmdapi.Config) bool { diff --git a/cmd/kubeadm/app/util/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/util/kubeconfig/kubeconfig_test.go index d108d1acc292b..74d429e9ba78a 100644 --- a/cmd/kubeadm/app/util/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/util/kubeconfig/kubeconfig_test.go @@ -18,6 +18,7 @@ package kubeconfig import ( "bytes" + _ "embed" "fmt" "os" "reflect" @@ -26,41 +27,11 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -const ( - configOut1 = `apiVersion: v1 -clusters: -- cluster: - server: "" - name: k8s -contexts: -- context: - cluster: k8s - user: user1 - name: user1@k8s -current-context: user1@k8s -kind: Config -users: -- name: user1 - user: - token: abc -` - configOut2 = `apiVersion: v1 -clusters: -- cluster: - server: localhost:8080 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: user2 - name: user2@kubernetes -current-context: user2@kubernetes -kind: Config -users: -- name: user2 - user: - token: cba -` +var ( + //go:embed testdata/user1.yaml + configOut1 string + //go:embed testdata/user2.yaml + configOut2 string ) type configClient struct { diff --git a/cmd/kubeadm/app/util/kubeconfig/testdata/user1.yaml b/cmd/kubeadm/app/util/kubeconfig/testdata/user1.yaml new file mode 100644 index 0000000000000..f0b1ca0da4463 --- /dev/null +++ b/cmd/kubeadm/app/util/kubeconfig/testdata/user1.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +clusters: +- cluster: + server: "" + name: k8s +contexts: +- context: + cluster: k8s + user: user1 + name: user1@k8s +current-context: user1@k8s +kind: Config +users: +- name: user1 + user: + token: abc diff --git a/cmd/kubeadm/app/util/kubeconfig/testdata/user2.yaml b/cmd/kubeadm/app/util/kubeconfig/testdata/user2.yaml new file mode 100644 index 0000000000000..d4086e8ea4dcf --- /dev/null +++ b/cmd/kubeadm/app/util/kubeconfig/testdata/user2.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +clusters: +- cluster: + server: localhost:8080 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: user2 + name: user2@kubernetes +current-context: user2@kubernetes +kind: Config +users: +- name: user2 + user: + token: cba diff --git a/cmd/kubeadm/app/util/marshal_test.go b/cmd/kubeadm/app/util/marshal_test.go index 246d22ba48208..44b67188c4fac 100644 --- a/cmd/kubeadm/app/util/marshal_test.go +++ b/cmd/kubeadm/app/util/marshal_test.go @@ -18,6 +18,7 @@ package util import ( "bytes" + _ "embed" "reflect" "sort" "testing" @@ -30,33 +31,25 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) +var ( + //go:embed testdata/foo.yaml + fooYAML []byte + //go:embed testdata/bar.yaml + barYAML []byte + //go:embed testdata/baz.yaml + bazYAML []byte + //go:embed testdata/nokind.yaml + nokindYAML []byte + //go:embed testdata/noapiversion.yaml + noapiversionYAML []byte +) + var files = map[string][]byte{ - "foo": []byte(` -kind: Foo -apiVersion: foo.k8s.io/v1 -fooField: foo -`), - "bar": []byte(` -apiVersion: bar.k8s.io/v2 -barField: bar -kind: Bar -`), - "baz": []byte(` -apiVersion: baz.k8s.io/v1 -kind: Baz -baz: - foo: bar -`), - "nokind": []byte(` -apiVersion: baz.k8s.io/v1 -foo: foo -bar: bar -`), - "noapiversion": []byte(` -kind: Bar -foo: foo -bar: bar -`), + "foo": fooYAML, + "bar": barYAML, + "baz": bazYAML, + "nokind": nokindYAML, + "noapiversion": noapiversionYAML, } func TestMarshalUnmarshalYaml(t *testing.T) { diff --git a/cmd/kubeadm/app/util/pubkeypin/pubkeypin_test.go b/cmd/kubeadm/app/util/pubkeypin/pubkeypin_test.go index af726ea9bc569..4ee464f1a08aa 100644 --- a/cmd/kubeadm/app/util/pubkeypin/pubkeypin_test.go +++ b/cmd/kubeadm/app/util/pubkeypin/pubkeypin_test.go @@ -18,6 +18,7 @@ package pubkeypin import ( "crypto/x509" + _ "embed" "encoding/pem" "strings" "testing" @@ -25,58 +26,17 @@ import ( // testCertPEM is a simple self-signed test certificate issued with the openssl CLI: // openssl req -new -newkey rsa:2048 -days 36500 -nodes -x509 -keyout /dev/null -out test.crt -const testCertPEM = ` ------BEGIN CERTIFICATE----- -MIIDRDCCAiygAwIBAgIJAJgVaCXvC6HkMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNV -BAMTFGt1YmVhZG0ta2V5cGlucy10ZXN0MCAXDTE3MDcwNTE3NDMxMFoYDzIxMTcw -NjExMTc0MzEwWjAfMR0wGwYDVQQDExRrdWJlYWRtLWtleXBpbnMtdGVzdDCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0ba8mHU9UtYlzM1Own2Fk/XGjR -J4uJQvSeGLtz1hID1IA0dLwruvgLCPadXEOw/f/IWIWcmT+ZmvIHZKa/woq2iHi5 -+HLhXs7aG4tjKGLYhag1hLjBI7icqV7ovkjdGAt9pWkxEzhIYClFMXDjKpMSynu+ -YX6nZ9tic1cOkHmx2yiZdMkuriRQnpTOa7bb03OC1VfGl7gHlOAIYaj4539WCOr8 -+ACTUMJUFEHcRZ2o8a/v6F9GMK+7SC8SJUI+GuroXqlMAdhEv4lX5Co52enYaClN -+D9FJLRpBv2YfiCQdJRaiTvCBSxEFz6BN+PtP5l2Hs703ZWEkOqCByM6HV8CAwEA -AaOBgDB+MB0GA1UdDgQWBBRQgUX8MhK2rWBWQiPHWcKzoWDH5DBPBgNVHSMESDBG -gBRQgUX8MhK2rWBWQiPHWcKzoWDH5KEjpCEwHzEdMBsGA1UEAxMUa3ViZWFkbS1r -ZXlwaW5zLXRlc3SCCQCYFWgl7wuh5DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB -BQUAA4IBAQCaAUif7Pfx3X0F08cxhx8/Hdx4jcJw6MCq6iq6rsXM32ge43t8OHKC -pJW08dk58a3O1YQSMMvD6GJDAiAfXzfwcwY6j258b1ZlI9Ag0VokvhMl/XfdCsdh -AWImnL1t4hvU5jLaImUUMlYxMcSfHBGAm7WJIZ2LdEfg6YWfZh+WGbg1W7uxLxk6 -y4h5rWdNnzBHWAGf7zJ0oEDV6W6RSwNXtC0JNnLaeIUm/6xdSddJlQPwUv8YH4jX -c1vuFqTnJBPcb7W//R/GI2Paicm1cmns9NLnPR35exHxFTy+D1yxmGokpoPMdife -aH+sfuxT8xeTPb3kjzF9eJTlnEquUDLM ------END CERTIFICATE-----` +// +//go:embed testdata/test-cert.pem +var testCertPEM string // expectedHash can be verified using the openssl CLI. const expectedHash = `sha256:345959acb2c3b2feb87d281961c893f62a314207ef02599f1cc4a5fb255480b3` // testCert2PEM is a second test cert generated the same way as testCertPEM -const testCert2PEM = ` ------BEGIN CERTIFICATE----- -MIID9jCCAt6gAwIBAgIJAN5MXZDic7qYMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCXRlc3RDZXJ0MjAgFw0xNzA3MjQxNjA0 -MDFaGA8yMTE3MDYzMDE2MDQwMVowWTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNv -bWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAG -A1UEAxMJdGVzdENlcnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -0brwpJYN2ytPWzRBtZSVc3dhkQlA59AzxzqeLLkano0Pxo9NIc3T/y58nnRI8uaS -I1P7BzUfJTiUEvmAtX8NggqKK4ld/gPrU+IRww1CUYS4KCkA/0d0ctPy0JwBCjD+ -b57G3rmNE8c+0jns6J96ZzNtqmv6N+ZlFBAXm1p4S+k0kGi5+hoQ6H7SYXjk2lG+ -r/8jPQEjy/NSdw1dcCA0Nc6o+hPr32927dS6J9KOhBeXNYUNdbuDDmroM9/gN2e/ -YMSA1olLeDPQ7Xvhk0PIyEDnHh83AffPCx5yM3htVRGddjIsPAVUJEL3z5leJtxe -fzyPghOhHJY0PXqznDQTcwIDAQABo4G+MIG7MB0GA1UdDgQWBBRP0IJqv/5rQ4Uf -SByl77dJeEapRDCBiwYDVR0jBIGDMIGAgBRP0IJqv/5rQ4UfSByl77dJeEapRKFd -pFswWTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAxMJdGVzdENlcnQyggkA -3kxdkOJzupgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA0RIMHc10 -wHHPMh9UflqBgDMF7gfbOL0juJfGloAOcohWWfMZBBJ0CQKMy3xRyoK3HmbW1eeb -iATjesw7t4VEAwf7mgKAd+eTfWYB952uq5qYJ2TI28mSofEq1Wz3RmrNkC1KCBs1 -u+YMFGwyl6necV9zKCeiju4jeovI1GA38TvH7MgYln6vMJ+FbgOXj7XCpek7dQiY -KGaeSSH218mGNQaWRQw2Sm3W6cFdANoCJUph4w18s7gjtFpfV63s80hXRps+vEyv -jEQMEQpG8Ss7HGJLGLBw/xAmG0e//XS/o2dDonbGbvzToFByz8OGxjMhk6yV6hdd -+iyvsLAw/MYMSA== ------END CERTIFICATE----- -` +// +//go:embed testdata/test-cert2.pem +var testCert2PEM string // testCert is a small helper to get a test x509.Certificate from the PEM constants func testCert(t *testing.T, pemString string) *x509.Certificate { diff --git a/cmd/kubeadm/app/util/pubkeypin/testdata/test-cert.pem b/cmd/kubeadm/app/util/pubkeypin/testdata/test-cert.pem new file mode 100644 index 0000000000000..b6067ca8fa3dc --- /dev/null +++ b/cmd/kubeadm/app/util/pubkeypin/testdata/test-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRDCCAiygAwIBAgIJAJgVaCXvC6HkMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNV +BAMTFGt1YmVhZG0ta2V5cGlucy10ZXN0MCAXDTE3MDcwNTE3NDMxMFoYDzIxMTcw +NjExMTc0MzEwWjAfMR0wGwYDVQQDExRrdWJlYWRtLWtleXBpbnMtdGVzdDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0ba8mHU9UtYlzM1Own2Fk/XGjR +J4uJQvSeGLtz1hID1IA0dLwruvgLCPadXEOw/f/IWIWcmT+ZmvIHZKa/woq2iHi5 ++HLhXs7aG4tjKGLYhag1hLjBI7icqV7ovkjdGAt9pWkxEzhIYClFMXDjKpMSynu+ +YX6nZ9tic1cOkHmx2yiZdMkuriRQnpTOa7bb03OC1VfGl7gHlOAIYaj4539WCOr8 ++ACTUMJUFEHcRZ2o8a/v6F9GMK+7SC8SJUI+GuroXqlMAdhEv4lX5Co52enYaClN ++D9FJLRpBv2YfiCQdJRaiTvCBSxEFz6BN+PtP5l2Hs703ZWEkOqCByM6HV8CAwEA +AaOBgDB+MB0GA1UdDgQWBBRQgUX8MhK2rWBWQiPHWcKzoWDH5DBPBgNVHSMESDBG +gBRQgUX8MhK2rWBWQiPHWcKzoWDH5KEjpCEwHzEdMBsGA1UEAxMUa3ViZWFkbS1r +ZXlwaW5zLXRlc3SCCQCYFWgl7wuh5DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4IBAQCaAUif7Pfx3X0F08cxhx8/Hdx4jcJw6MCq6iq6rsXM32ge43t8OHKC +pJW08dk58a3O1YQSMMvD6GJDAiAfXzfwcwY6j258b1ZlI9Ag0VokvhMl/XfdCsdh +AWImnL1t4hvU5jLaImUUMlYxMcSfHBGAm7WJIZ2LdEfg6YWfZh+WGbg1W7uxLxk6 +y4h5rWdNnzBHWAGf7zJ0oEDV6W6RSwNXtC0JNnLaeIUm/6xdSddJlQPwUv8YH4jX +c1vuFqTnJBPcb7W//R/GI2Paicm1cmns9NLnPR35exHxFTy+D1yxmGokpoPMdife +aH+sfuxT8xeTPb3kjzF9eJTlnEquUDLM +-----END CERTIFICATE----- diff --git a/cmd/kubeadm/app/util/pubkeypin/testdata/test-cert2.pem b/cmd/kubeadm/app/util/pubkeypin/testdata/test-cert2.pem new file mode 100644 index 0000000000000..67c01eaaa867b --- /dev/null +++ b/cmd/kubeadm/app/util/pubkeypin/testdata/test-cert2.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID9jCCAt6gAwIBAgIJAN5MXZDic7qYMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCXRlc3RDZXJ0MjAgFw0xNzA3MjQxNjA0 +MDFaGA8yMTE3MDYzMDE2MDQwMVowWTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNv +bWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAG +A1UEAxMJdGVzdENlcnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +0brwpJYN2ytPWzRBtZSVc3dhkQlA59AzxzqeLLkano0Pxo9NIc3T/y58nnRI8uaS +I1P7BzUfJTiUEvmAtX8NggqKK4ld/gPrU+IRww1CUYS4KCkA/0d0ctPy0JwBCjD+ +b57G3rmNE8c+0jns6J96ZzNtqmv6N+ZlFBAXm1p4S+k0kGi5+hoQ6H7SYXjk2lG+ +r/8jPQEjy/NSdw1dcCA0Nc6o+hPr32927dS6J9KOhBeXNYUNdbuDDmroM9/gN2e/ +YMSA1olLeDPQ7Xvhk0PIyEDnHh83AffPCx5yM3htVRGddjIsPAVUJEL3z5leJtxe +fzyPghOhHJY0PXqznDQTcwIDAQABo4G+MIG7MB0GA1UdDgQWBBRP0IJqv/5rQ4Uf +SByl77dJeEapRDCBiwYDVR0jBIGDMIGAgBRP0IJqv/5rQ4UfSByl77dJeEapRKFd +pFswWTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAxMJdGVzdENlcnQyggkA +3kxdkOJzupgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA0RIMHc10 +wHHPMh9UflqBgDMF7gfbOL0juJfGloAOcohWWfMZBBJ0CQKMy3xRyoK3HmbW1eeb +iATjesw7t4VEAwf7mgKAd+eTfWYB952uq5qYJ2TI28mSofEq1Wz3RmrNkC1KCBs1 +u+YMFGwyl6necV9zKCeiju4jeovI1GA38TvH7MgYln6vMJ+FbgOXj7XCpek7dQiY +KGaeSSH218mGNQaWRQw2Sm3W6cFdANoCJUph4w18s7gjtFpfV63s80hXRps+vEyv +jEQMEQpG8Ss7HGJLGLBw/xAmG0e//XS/o2dDonbGbvzToFByz8OGxjMhk6yV6hdd ++iyvsLAw/MYMSA== +-----END CERTIFICATE----- diff --git a/cmd/kubeadm/app/util/runtime/fake_impl.go b/cmd/kubeadm/app/util/runtime/fake_impl.go index b3bde5a3510b2..78c579831a87e 100644 --- a/cmd/kubeadm/app/util/runtime/fake_impl.go +++ b/cmd/kubeadm/app/util/runtime/fake_impl.go @@ -91,7 +91,7 @@ func (fake *FakeImpl) ListPodSandboxReturns(res []*v1.PodSandbox, err error) { } // NewRemoteImageService returns the new remote image service. -func (fake *FakeImpl) NewRemoteImageService(string, time.Duration) (cri.ImageManagerService, error) { +func (fake *FakeImpl) NewRemoteImageService(context.Context, string, time.Duration) (cri.ImageManagerService, error) { fakeReturns := fake.newRemoteImageServiceReturns return fakeReturns.res, fakeReturns.err } @@ -105,7 +105,7 @@ func (fake *FakeImpl) NewRemoteImageServiceReturns(res cri.ImageManagerService, } // NewRemoteRuntimeService returns the new remote runtime service. -func (fake *FakeImpl) NewRemoteRuntimeService(string, time.Duration) (cri.RuntimeService, error) { +func (fake *FakeImpl) NewRemoteRuntimeService(context.Context, string, time.Duration) (cri.RuntimeService, error) { fakeReturns := fake.newRemoteRuntimeServiceReturns return fakeReturns.res, fakeReturns.err } diff --git a/cmd/kubeadm/app/util/runtime/impl.go b/cmd/kubeadm/app/util/runtime/impl.go index e44f56b382416..5dadbdc3578dc 100644 --- a/cmd/kubeadm/app/util/runtime/impl.go +++ b/cmd/kubeadm/app/util/runtime/impl.go @@ -29,8 +29,8 @@ type defaultImpl struct{} // Impl is an interface for the container runtime implementation. type Impl interface { - NewRemoteRuntimeService(endpoint string, connectionTimeout time.Duration) (criapi.RuntimeService, error) - NewRemoteImageService(endpoint string, connectionTimeout time.Duration) (criapi.ImageManagerService, error) + NewRemoteRuntimeService(ctx context.Context, endpoint string, connectionTimeout time.Duration) (criapi.RuntimeService, error) + NewRemoteImageService(ctx context.Context, endpoint string, connectionTimeout time.Duration) (criapi.ImageManagerService, error) RuntimeConfig(ctx context.Context, runtimeService criapi.RuntimeService) (*runtimeapi.RuntimeConfigResponse, error) Status(ctx context.Context, runtimeService criapi.RuntimeService, verbose bool) (*runtimeapi.StatusResponse, error) ListPodSandbox(ctx context.Context, runtimeService criapi.RuntimeService, filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error) @@ -40,12 +40,12 @@ type Impl interface { ImageStatus(ctx context.Context, imageService criapi.ImageManagerService, image *runtimeapi.ImageSpec, verbose bool) (*runtimeapi.ImageStatusResponse, error) } -func (*defaultImpl) NewRemoteRuntimeService(endpoint string, connectionTimeout time.Duration) (criapi.RuntimeService, error) { - return criclient.NewRemoteRuntimeService(endpoint, defaultTimeout, nil, nil) +func (*defaultImpl) NewRemoteRuntimeService(ctx context.Context, endpoint string, connectionTimeout time.Duration) (criapi.RuntimeService, error) { + return criclient.NewRemoteRuntimeService(ctx, endpoint, connectionTimeout, nil, false) } -func (*defaultImpl) NewRemoteImageService(endpoint string, connectionTimeout time.Duration) (criapi.ImageManagerService, error) { - return criclient.NewRemoteImageService(endpoint, connectionTimeout, nil, nil) +func (*defaultImpl) NewRemoteImageService(ctx context.Context, endpoint string, connectionTimeout time.Duration) (criapi.ImageManagerService, error) { + return criclient.NewRemoteImageService(ctx, endpoint, connectionTimeout, nil, false) } func (*defaultImpl) RuntimeConfig(ctx context.Context, runtimeService criapi.RuntimeService) (*runtimeapi.RuntimeConfigResponse, error) { diff --git a/cmd/kubeadm/app/util/runtime/runtime.go b/cmd/kubeadm/app/util/runtime/runtime.go index 831d1478e8cd7..c5e658ac22540 100644 --- a/cmd/kubeadm/app/util/runtime/runtime.go +++ b/cmd/kubeadm/app/util/runtime/runtime.go @@ -45,7 +45,7 @@ var defaultKnownCRISockets = []string{ // ContainerRuntime is an interface for working with container runtimes type ContainerRuntime interface { Connect() error - Close() + Close(ctx context.Context) SetImpl(Impl) IsRunning() error ListKubeContainers() ([]string, error) @@ -83,13 +83,13 @@ func (runtime *CRIRuntime) SetImpl(impl Impl) { // Connect establishes a connection with the CRI runtime. func (runtime *CRIRuntime) Connect() error { - runtimeService, err := runtime.impl.NewRemoteRuntimeService(runtime.criSocket, defaultTimeout) + runtimeService, err := runtime.impl.NewRemoteRuntimeService(context.Background(), runtime.criSocket, defaultTimeout) if err != nil { return errors.Wrap(err, "failed to create new CRI runtime service") } runtime.runtimeService = runtimeService - imageService, err := runtime.impl.NewRemoteImageService(runtime.criSocket, defaultTimeout) + imageService, err := runtime.impl.NewRemoteImageService(context.Background(), runtime.criSocket, defaultTimeout) if err != nil { return errors.Wrap(err, "failed to create new CRI image service") } @@ -99,14 +99,14 @@ func (runtime *CRIRuntime) Connect() error { } // Close closes the connections to the runtime and image services. -func (runtime *CRIRuntime) Close() { +func (runtime *CRIRuntime) Close(ctx context.Context) { if runtime.runtimeService != nil { - if err := runtime.runtimeService.Close(); err != nil { + if err := runtime.runtimeService.Close(ctx); err != nil { klog.Warningf("failed to close runtime service: %v", err) } } if runtime.imageService != nil { - if err := runtime.imageService.Close(); err != nil { + if err := runtime.imageService.Close(ctx); err != nil { klog.Warningf("failed to close image service: %v", err) } } @@ -157,7 +157,7 @@ func (runtime *CRIRuntime) RemoveContainers(containers []string) error { errs := []error{} for _, container := range containers { var lastErr error - for i := 0; i < constants.RemoveContainerRetry; i++ { + for range constants.RemoveContainerRetry { klog.V(5).Infof("Attempting to remove container %v", container) ctx, cancel := defaultContext() @@ -189,7 +189,7 @@ func (runtime *CRIRuntime) RemoveContainers(containers []string) error { // PullImage pulls the image func (runtime *CRIRuntime) PullImage(image string) (err error) { - for i := 0; i < constants.PullImageRetry; i++ { + for range constants.PullImageRetry { if _, err = runtime.impl.PullImage(context.Background(), runtime.imageService, &runtimeapi.ImageSpec{Image: image}, nil, nil); err == nil { return nil } @@ -235,7 +235,7 @@ func pullImagesInParallelImpl(images []string, ifNotPresent bool, }() } - for i := 0; i < len(images); i++ { + for range images { if err := <-errChan; err != nil { errs = append(errs, err) } diff --git a/cmd/kubeadm/app/util/runtime/runtime_unix.go b/cmd/kubeadm/app/util/runtime/runtime_unix.go index 5989c14290fa6..41f7f3b6e5cd2 100644 --- a/cmd/kubeadm/app/util/runtime/runtime_unix.go +++ b/cmd/kubeadm/app/util/runtime/runtime_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/runtime/runtime_windows.go b/cmd/kubeadm/app/util/runtime/runtime_windows.go index 460bfcd03885a..754591b925a21 100644 --- a/cmd/kubeadm/app/util/runtime/runtime_windows.go +++ b/cmd/kubeadm/app/util/runtime/runtime_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2019 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/staticpod/testdata/invalid-with-default-fields.yaml b/cmd/kubeadm/app/util/staticpod/testdata/invalid-with-default-fields.yaml new file mode 100644 index 0000000000000..85b3e7600cc6e --- /dev/null +++ b/cmd/kubeadm/app/util/staticpod/testdata/invalid-with-default-fields.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + tier: control-plane + component: etcd + name: etcd + namespace: kube-system +spec: + containers: + - image: gcr.io/google_containers/etcd-amd64:3.1.11 + restartPolicy: "Always" +status: {} diff --git a/cmd/kubeadm/app/util/staticpod/testdata/valid-pod-different-order.yaml b/cmd/kubeadm/app/util/staticpod/testdata/valid-pod-different-order.yaml new file mode 100644 index 0000000000000..17ac23653b0d2 --- /dev/null +++ b/cmd/kubeadm/app/util/staticpod/testdata/valid-pod-different-order.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + tier: control-plane + component: etcd + name: etcd + namespace: kube-system +spec: + containers: + - image: gcr.io/google_containers/etcd-amd64:3.1.11 +status: {} diff --git a/cmd/kubeadm/app/util/staticpod/testdata/valid-pod.yaml b/cmd/kubeadm/app/util/staticpod/testdata/valid-pod.yaml new file mode 100644 index 0000000000000..8f995cdc7ccc9 --- /dev/null +++ b/cmd/kubeadm/app/util/staticpod/testdata/valid-pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + component: etcd + tier: control-plane + name: etcd + namespace: kube-system +spec: + containers: + - image: gcr.io/google_containers/etcd-amd64:3.1.11 +status: {} diff --git a/cmd/kubeadm/app/util/staticpod/testdata/valid-pod2.yaml b/cmd/kubeadm/app/util/staticpod/testdata/valid-pod2.yaml new file mode 100644 index 0000000000000..8a4b7e9e39565 --- /dev/null +++ b/cmd/kubeadm/app/util/staticpod/testdata/valid-pod2.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + component: etcd + tier: control-plane + name: etcd + namespace: kube-system +spec: + containers: + - image: gcr.io/google_containers/etcd-amd64:3.1.12 +status: {} diff --git a/cmd/kubeadm/app/util/staticpod/utils.go b/cmd/kubeadm/app/util/staticpod/utils.go index c5110eb94c6ba..a58f91e1c2458 100644 --- a/cmd/kubeadm/app/util/staticpod/utils.go +++ b/cmd/kubeadm/app/util/staticpod/utils.go @@ -34,9 +34,9 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/dump" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/dump" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" diff --git a/cmd/kubeadm/app/util/staticpod/utils_linux.go b/cmd/kubeadm/app/util/staticpod/utils_linux.go index 27003c46f37b4..50c0e56e6e593 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_linux.go +++ b/cmd/kubeadm/app/util/staticpod/utils_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/staticpod/utils_linux_test.go b/cmd/kubeadm/app/util/staticpod/utils_linux_test.go index 29a3e675e1ea9..ea6648303fa79 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_linux_test.go +++ b/cmd/kubeadm/app/util/staticpod/utils_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/staticpod/utils_others.go b/cmd/kubeadm/app/util/staticpod/utils_others.go index 59e520d385679..5e8f900452150 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_others.go +++ b/cmd/kubeadm/app/util/staticpod/utils_others.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/staticpod/utils_test.go b/cmd/kubeadm/app/util/staticpod/utils_test.go index 65809fcd55fc8..fe8c0227ac684 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_test.go +++ b/cmd/kubeadm/app/util/staticpod/utils_test.go @@ -17,6 +17,7 @@ limitations under the License. package staticpod import ( + _ "embed" "io" "os" "path/filepath" @@ -578,69 +579,22 @@ func TestVolumeMountMapToSlice(t *testing.T) { } } -const ( - validPod = ` -apiVersion: v1 -kind: Pod -metadata: - labels: - component: etcd - tier: control-plane - name: etcd - namespace: kube-system -spec: - containers: - - image: gcr.io/google_containers/etcd-amd64:3.1.11 -status: {} -` - validPodWithDifferentFieldsOrder = ` -apiVersion: v1 -kind: Pod -metadata: - labels: - tier: control-plane - component: etcd - name: etcd - namespace: kube-system -spec: - containers: - - image: gcr.io/google_containers/etcd-amd64:3.1.11 -status: {} -` - invalidWithDefaultFields = ` -apiVersion: v1 -kind: Pod -metadata: - labels: - tier: control-plane - component: etcd - name: etcd - namespace: kube-system -spec: - containers: - - image: gcr.io/google_containers/etcd-amd64:3.1.11 - restartPolicy: "Always" -status: {} -` - - validPod2 = ` -apiVersion: v1 -kind: Pod -metadata: - labels: - component: etcd - tier: control-plane - name: etcd - namespace: kube-system -spec: - containers: - - image: gcr.io/google_containers/etcd-amd64:3.1.12 -status: {} -` - - invalidPod = `---{ broken yaml @@@` +var ( + //go:embed testdata/valid-pod.yaml + validPod string + + //go:embed testdata/valid-pod-different-order.yaml + validPodWithDifferentFieldsOrder string + + //go:embed testdata/invalid-with-default-fields.yaml + invalidWithDefaultFields string + + //go:embed testdata/valid-pod2.yaml + validPod2 string ) +const invalidPod = `---{ broken yaml @@@` + func TestReadStaticPodFromDisk(t *testing.T) { tests := []struct { description string @@ -845,7 +799,7 @@ func TestManifestFilesAreEqual(t *testing.T) { tmpdir := t.TempDir() // write 2 manifests - for i := 0; i < 2; i++ { + for i := range 2 { if rt.podYamls[i] != "" { manifestPath := filepath.Join(tmpdir, strconv.Itoa(i)+".yaml") err := os.WriteFile(manifestPath, []byte(rt.podYamls[i]), 0644) diff --git a/cmd/kubeadm/app/util/testdata/bar.yaml b/cmd/kubeadm/app/util/testdata/bar.yaml new file mode 100644 index 0000000000000..33dd8ac972fb2 --- /dev/null +++ b/cmd/kubeadm/app/util/testdata/bar.yaml @@ -0,0 +1,3 @@ +apiVersion: bar.k8s.io/v2 +barField: bar +kind: Bar diff --git a/cmd/kubeadm/app/util/testdata/baz.yaml b/cmd/kubeadm/app/util/testdata/baz.yaml new file mode 100644 index 0000000000000..7d2e71fe82b4c --- /dev/null +++ b/cmd/kubeadm/app/util/testdata/baz.yaml @@ -0,0 +1,4 @@ +apiVersion: baz.k8s.io/v1 +kind: Baz +baz: + foo: bar diff --git a/cmd/kubeadm/app/util/testdata/foo.yaml b/cmd/kubeadm/app/util/testdata/foo.yaml new file mode 100644 index 0000000000000..5f496a60ce83b --- /dev/null +++ b/cmd/kubeadm/app/util/testdata/foo.yaml @@ -0,0 +1,3 @@ +kind: Foo +apiVersion: foo.k8s.io/v1 +fooField: foo diff --git a/cmd/kubeadm/app/util/testdata/noapiversion.yaml b/cmd/kubeadm/app/util/testdata/noapiversion.yaml new file mode 100644 index 0000000000000..d8af993fd4276 --- /dev/null +++ b/cmd/kubeadm/app/util/testdata/noapiversion.yaml @@ -0,0 +1,3 @@ +kind: Bar +foo: foo +bar: bar diff --git a/cmd/kubeadm/app/util/testdata/nokind.yaml b/cmd/kubeadm/app/util/testdata/nokind.yaml new file mode 100644 index 0000000000000..9ae2426bfb108 --- /dev/null +++ b/cmd/kubeadm/app/util/testdata/nokind.yaml @@ -0,0 +1,3 @@ +apiVersion: baz.k8s.io/v1 +foo: foo +bar: bar diff --git a/cmd/kubeadm/app/util/users/users_linux.go b/cmd/kubeadm/app/util/users/users_linux.go index 8c70bffbd9b2e..8105b902ded33 100644 --- a/cmd/kubeadm/app/util/users/users_linux.go +++ b/cmd/kubeadm/app/util/users/users_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/users/users_linux_test.go b/cmd/kubeadm/app/util/users/users_linux_test.go index 1baee04ceb990..9aff60ecbd58d 100644 --- a/cmd/kubeadm/app/util/users/users_linux_test.go +++ b/cmd/kubeadm/app/util/users/users_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubeadm/app/util/users/users_other.go b/cmd/kubeadm/app/util/users/users_other.go index 6c2b51bda1f16..5f3d5adf87909 100644 --- a/cmd/kubeadm/app/util/users/users_other.go +++ b/cmd/kubeadm/app/util/users/users_other.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubelet/app/auth.go b/cmd/kubelet/app/auth.go index 1000f319ae89e..aefcd45e38c8f 100644 --- a/cmd/kubelet/app/auth.go +++ b/cmd/kubelet/app/auth.go @@ -51,7 +51,7 @@ func BuildAuth(nodeName types.NodeName, client clientset.Interface, config kubel sarClient = client.AuthorizationV1() } - authenticator, runAuthenticatorCAReload, err := BuildAuthn(tokenClient, config.Authentication) + authenticator, runAuthenticatorCAReload, clientCAProvider, err := BuildAuthn(tokenClient, config.Authentication) if err != nil { return nil, nil, err } @@ -64,18 +64,23 @@ func BuildAuth(nodeName types.NodeName, client clientset.Interface, config kubel } authorizer = wrapAuthorizerWithMetricsScraper(authorizer) - return server.NewKubeletAuth(authenticator, attributes, authorizer), runAuthenticatorCAReload, nil + return server.NewKubeletAuth(authenticator, attributes, authorizer, clientCAProvider), runAuthenticatorCAReload, nil } // BuildAuthn creates an authenticator compatible with the kubelet's needs -func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kubeletconfig.KubeletAuthentication) (authenticator.Request, func(<-chan struct{}), error) { +func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kubeletconfig.KubeletAuthentication) (authenticator.Request, func(<-chan struct{}), dynamiccertificates.CAContentProvider, error) { + var caContentProvider dynamiccertificates.CAContentProvider var dynamicCAContentFromFile *dynamiccertificates.DynamicFileCAContent var err error if len(authn.X509.ClientCAFile) > 0 { dynamicCAContentFromFile, err = dynamiccertificates.NewDynamicCAContentFromFile("client-ca-bundle", authn.X509.ClientCAFile) if err != nil { - return nil, nil, err + return nil, nil, nil, err } + caContentProvider = dynamicCAContentFromFile + } else { + // no client CA configured, make a non-nil no-op CA provider + caContentProvider = dynamiccertificates.NewUnionCAContentProvider() } authenticatorConfig := authenticatorfactory.DelegatingAuthenticatorConfig{ @@ -86,7 +91,7 @@ func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kub if authn.Webhook.Enabled { if client == nil { - return nil, nil, errors.New("no client provided, cannot use webhook authentication") + return nil, nil, nil, errors.New("no client provided, cannot use webhook authentication") } authenticatorConfig.WebhookRetryBackoff = genericoptions.DefaultAuthWebhookRetryBackoff() authenticatorConfig.TokenAccessReviewClient = client @@ -94,7 +99,7 @@ func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kub authenticator, _, err := authenticatorConfig.New() if err != nil { - return nil, nil, err + return nil, nil, nil, err } return authenticator, func(stopCh <-chan struct{}) { @@ -111,7 +116,7 @@ func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kub if dynamicCAContentFromFile != nil { go dynamicCAContentFromFile.Run(ctx, 1) } - }, err + }, caContentProvider, err } // BuildAuthz creates an authorizer compatible with the kubelet's needs diff --git a/cmd/kubelet/app/auth_test.go b/cmd/kubelet/app/auth_test.go index 06498486b9c38..7bf374c2b206a 100644 --- a/cmd/kubelet/app/auth_test.go +++ b/cmd/kubelet/app/auth_test.go @@ -179,7 +179,7 @@ func TestAuthnWebhookRequestEncoding(t *testing.T) { t.Fatalf("failed to create authentication client: %v", err) } - authn, _, err := BuildAuthn(authnClient, kubeletconfig.KubeletAuthentication{ + authn, _, _, err := BuildAuthn(authnClient, kubeletconfig.KubeletAuthentication{ Webhook: kubeletconfig.KubeletWebhookAuthentication{ Enabled: true, }, diff --git a/cmd/kubelet/app/init_others.go b/cmd/kubelet/app/init_others.go index 1d81b9e4af8eb..7993a7fb7270f 100644 --- a/cmd/kubelet/app/init_others.go +++ b/cmd/kubelet/app/init_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubelet/app/init_windows.go b/cmd/kubelet/app/init_windows.go index 651bb484f460b..5488969237c00 100644 --- a/cmd/kubelet/app/init_windows.go +++ b/cmd/kubelet/app/init_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubelet/app/options/globalflags_linux.go b/cmd/kubelet/app/options/globalflags_linux.go index c2ba0d1bce39d..295cc10d0a652 100644 --- a/cmd/kubelet/app/options/globalflags_linux.go +++ b/cmd/kubelet/app/options/globalflags_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubelet/app/options/globalflags_other.go b/cmd/kubelet/app/options/globalflags_other.go index f630d1024b079..77aa2c8244c7b 100644 --- a/cmd/kubelet/app/options/globalflags_other.go +++ b/cmd/kubelet/app/options/globalflags_other.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubelet/app/options/options.go b/cmd/kubelet/app/options/options.go index 7b6d6f7d2c047..582186e3480b0 100644 --- a/cmd/kubelet/app/options/options.go +++ b/cmd/kubelet/app/options/options.go @@ -86,8 +86,10 @@ type KubeletFlags struct { // Omit this flag to use the combination of built-in default configuration values and flags. KubeletConfigFile string - // kubeletDropinConfigDirectory is a path to a directory to specify dropins allows the user to optionally specify - // additional configs to overwrite what is provided by default and in the KubeletConfigFile flag + // KubeletDropinConfigDirectory is the path to a directory containing drop-in configuration files that override settings from defaults and the --config file. + // The path may be absolute or relative; relative paths are under the Kubelet's current working directory. + // Drop-in files must have a '.conf' suffix and are processed in lexical order. Only '.conf' files are loaded; all other files are ignored. + // All .conf files in the directory and its subdirectories are processed in lexical order. KubeletDropinConfigDirectory string // WindowsService should be set to true if kubelet is running as a service on Windows. @@ -280,7 +282,7 @@ func (f *KubeletFlags) AddFlags(mainfs *pflag.FlagSet) { f.addOSFlags(fs) fs.StringVar(&f.KubeletConfigFile, "config", f.KubeletConfigFile, "The Kubelet will load its initial configuration from this file. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Omit this flag to use the built-in default configuration values. Command-line flags override configuration from this file.") - fs.StringVar(&f.KubeletDropinConfigDirectory, "config-dir", "", "Path to a directory to specify drop-ins, allows the user to optionally specify additional configs to overwrite what is provided by default and in the KubeletConfigFile flag. [default='']") + fs.StringVar(&f.KubeletDropinConfigDirectory, "config-dir", "", "Path to a directory containing drop-in configuration files that override settings from defaults and the --config file. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Drop-in files must have a '.conf' suffix (e.g., '99-kubelet-address.conf') and are processed in lexical order. Only '.conf' files are loaded; all other files are ignored. All .conf files in the directory and its subdirectories are processed in lexical order. [default='']") fs.StringVar(&f.KubeConfig, "kubeconfig", f.KubeConfig, "Path to a kubeconfig file, specifying how to connect to the API server. Providing --kubeconfig enables API server mode, omitting --kubeconfig enables standalone mode.") fs.StringVar(&f.BootstrapKubeconfig, "bootstrap-kubeconfig", f.BootstrapKubeconfig, "Path to a kubeconfig file that will be used to get client certificate for kubelet. "+ @@ -469,7 +471,8 @@ func AddKubeletConfigFlags(mainfs *pflag.FlagSet, c *kubeletconfigapi.KubeletCon fs.BoolVar(&c.RunOnce, "runonce", c.RunOnce, "If true, exit after spawning pods from static pod files or remote urls. Exclusive with --enable-server") fs.BoolVar(&c.CPUCFSQuota, "cpu-cfs-quota", c.CPUCFSQuota, "Enable CPU CFS quota enforcement for containers that specify CPU limits") - fs.DurationVar(&c.CPUCFSQuotaPeriod.Duration, "cpu-cfs-quota-period", c.CPUCFSQuotaPeriod.Duration, "Sets CPU CFS quota period value, cpu.cfs_period_us, defaults to Linux Kernel default") + fs.DurationVar(&c.CPUCFSQuotaPeriod.Duration, "cpu-cfs-quota-period", c.CPUCFSQuotaPeriod.Duration, "Sets CPU CFS quota period value (cpu.cfs_period_us). Defaults to Linux Kernel default. "+"Requires the CustomCPUCFSQuotaPeriod feature gate to set non-default values.") + fs.BoolVar(&c.EnableControllerAttachDetach, "enable-controller-attach-detach", c.EnableControllerAttachDetach, "Enables the Attach/Detach controller to manage attachment/detachment of volumes scheduled to this node, and disables kubelet from executing any attach/detach operations") fs.BoolVar(&c.MakeIPTablesUtilChains, "make-iptables-util-chains", c.MakeIPTablesUtilChains, "If true, kubelet will ensure iptables utility rules are present on host.") fs.StringVar(&c.ContainerLogMaxSize, "container-log-max-size", c.ContainerLogMaxSize, " Set the maximum size (e.g. 10Mi) of container log file before it is rotated.") diff --git a/cmd/kubelet/app/options/osflags_others.go b/cmd/kubelet/app/options/osflags_others.go index 083231a8d6e8a..3f03bb788ed17 100644 --- a/cmd/kubelet/app/options/osflags_others.go +++ b/cmd/kubelet/app/options/osflags_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubelet/app/options/osflags_windows.go b/cmd/kubelet/app/options/osflags_windows.go index 1805b33d82eae..e6cd2b430ecc5 100644 --- a/cmd/kubelet/app/options/osflags_windows.go +++ b/cmd/kubelet/app/options/osflags_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/cmd/kubelet/app/plugins.go b/cmd/kubelet/app/plugins.go index ba40088dfe023..95ce1ff5a92ce 100644 --- a/cmd/kubelet/app/plugins.go +++ b/cmd/kubelet/app/plugins.go @@ -52,11 +52,6 @@ func ProbeVolumePlugins(ctx context.Context, featureGate featuregate.FeatureGate // // Kubelet does not currently need to configure volume plugins. // If/when it does, see kube-controller-manager/app/plugins.go for example of using volume.VolumeConfig - var err error - allPlugins, err = appendLegacyProviderVolumes(ctx, allPlugins, featureGate) - if err != nil { - return allPlugins, err - } allPlugins = append(allPlugins, emptydir.ProbeVolumePlugins()...) allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...) allPlugins = append(allPlugins, hostpath.ProbeVolumePlugins(volume.VolumeConfig{})...) @@ -78,6 +73,6 @@ func ProbeVolumePlugins(ctx context.Context, featureGate featuregate.FeatureGate // GetDynamicPluginProber gets the probers of dynamically discoverable plugins // for kubelet. // Currently only Flexvolume plugins are dynamically discoverable. -func GetDynamicPluginProber(pluginDir string, runner exec.Interface) volume.DynamicPluginProber { - return flexvolume.GetDynamicPluginProber(pluginDir, runner) +func GetDynamicPluginProber(ctx context.Context, pluginDir string, runner exec.Interface) volume.DynamicPluginProber { + return flexvolume.GetDynamicPluginProber(ctx, pluginDir, runner) } diff --git a/cmd/kubelet/app/plugins_providers.go b/cmd/kubelet/app/plugins_providers.go deleted file mode 100644 index 8fdde49a8481d..0000000000000 --- a/cmd/kubelet/app/plugins_providers.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2019 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 ( - "context" - - "k8s.io/component-base/featuregate" - "k8s.io/csi-translation-lib/plugins" - "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/pkg/volume/csimigration" - "k8s.io/kubernetes/pkg/volume/portworx" -) - -type probeFn func() []volume.VolumePlugin - -func appendPluginBasedOnFeatureFlags(ctx context.Context, plugins []volume.VolumePlugin, inTreePluginName string, - featureGate featuregate.FeatureGate, pluginInfo pluginInfo) ([]volume.VolumePlugin, error) { - logger := klog.FromContext(ctx) - _, err := csimigration.CheckMigrationFeatureFlags(featureGate, pluginInfo.pluginMigrationFeature, pluginInfo.pluginUnregisterFeature) - if err != nil { - logger.Info("Unexpected CSI Migration Feature Flags combination detected, CSI Migration may not take effect", "err", err) - // TODO: fail and return here once alpha only tests can set the feature flags for a plugin correctly - } - - // Skip appending the in-tree plugin to the list of plugins to be probed/initialized - // if the plugin unregister feature flag is set - if featureGate.Enabled(pluginInfo.pluginUnregisterFeature) { - logger.Info("Skipped registration of plugin since feature flag is enabled", "pluginName", inTreePluginName, "featureFlag", pluginInfo.pluginUnregisterFeature) - return plugins, nil - } - - plugins = append(plugins, pluginInfo.pluginProbeFunction()...) - return plugins, nil -} - -type pluginInfo struct { - pluginMigrationFeature featuregate.Feature - pluginUnregisterFeature featuregate.Feature - pluginProbeFunction probeFn -} - -func appendLegacyProviderVolumes(ctx context.Context, allPlugins []volume.VolumePlugin, featureGate featuregate.FeatureGate) ([]volume.VolumePlugin, error) { - pluginMigrationStatus := make(map[string]pluginInfo) - pluginMigrationStatus[plugins.PortworxVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationPortworx, pluginUnregisterFeature: features.InTreePluginPortworxUnregister, pluginProbeFunction: portworx.ProbeVolumePlugins} - var err error - for pluginName, pluginInfo := range pluginMigrationStatus { - allPlugins, err = appendPluginBasedOnFeatureFlags(ctx, allPlugins, pluginName, featureGate, pluginInfo) - if err != nil { - return allPlugins, err - } - } - return allPlugins, nil -} diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 65174afad7c3b..17f8fd3da58db 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -63,6 +63,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" + utilversion "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/flagz" @@ -83,6 +84,7 @@ import ( "k8s.io/component-base/logs" logsapi "k8s.io/component-base/logs/api/v1" "k8s.io/component-base/metrics" + metricsfeatures "k8s.io/component-base/metrics/features" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/tracing" "k8s.io/component-base/version" @@ -119,6 +121,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/watchdog" utilfs "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/kubernetes/pkg/util/flock" + utilkernel "k8s.io/kubernetes/pkg/util/kernel" "k8s.io/kubernetes/pkg/util/oom" "k8s.io/kubernetes/pkg/util/rlimit" "k8s.io/kubernetes/pkg/volume/util/hostutil" @@ -218,8 +221,10 @@ is checked every 20 seconds (also configurable with a flag).`, } } // Merge the kubelet configurations if --config-dir is set + var skippedDropinFiles []string if len(kubeletFlags.KubeletDropinConfigDirectory) > 0 { - if err := mergeKubeletConfigurations(kubeletConfig, kubeletFlags.KubeletDropinConfigDirectory); err != nil { + skippedDropinFiles, err = mergeKubeletConfigurations(kubeletConfig, kubeletFlags.KubeletDropinConfigDirectory) + if err != nil { return fmt.Errorf("failed to merge kubelet configs: %w", err) } } @@ -284,6 +289,14 @@ is checked every 20 seconds (also configurable with a flag).`, for k := range config.StaticPodURLHeader { config.StaticPodURLHeader[k] = []string{""} } + + // Log skipped drop-in files if any were encountered during configuration merge + if len(skippedDropinFiles) > 0 { + for _, skippedFile := range skippedDropinFiles { + logger.V(4).Info("Skipped file in drop-in directory (does not have .conf extension)", "file", skippedFile) + } + } + // log the kubelet's config for inspection logger.V(5).Info("KubeletConfiguration", "configuration", klog.Format(config)) @@ -320,63 +333,70 @@ is checked every 20 seconds (also configurable with a flag).`, // configurations in files with lower numeric prefixes are applied first, followed by higher numeric prefixes. // For example, if the drop-in directory contains files named "10-config.conf" and "20-config.conf", // the configurations in "10-config.conf" will be applied first, and then the configurations in "20-config.conf" will be applied, -// potentially overriding the previous values. -func mergeKubeletConfigurations(kubeletConfig *kubeletconfiginternal.KubeletConfiguration, kubeletDropInConfigDir string) error { +// potentially overriding the previous values. Files in subdirectories are also processed in lexical order. +// Returns a list of skipped files and an error. +func mergeKubeletConfigurations(kubeletConfig *kubeletconfiginternal.KubeletConfiguration, kubeletDropInConfigDir string) ([]string, error) { const dropinFileExtension = ".conf" + var skippedFiles []string // TODO: move the "internal --> versioned --> merge --> default --> internal" operation inside the loop, // and use the version of each drop-in file as the versioned target once config files can be in versions other than just v1beta1 versionedConfig := &kubeletconfigv1beta1.KubeletConfiguration{} if err := kubeletconfigv1beta1conversion.Convert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(kubeletConfig, versionedConfig, nil); err != nil { - return err + return nil, err } baseKubeletConfigJSON, err := json.Marshal(versionedConfig) if err != nil { - return fmt.Errorf("failed to marshal base config: %w", err) + return nil, fmt.Errorf("failed to marshal base config: %w", err) } // Walk through the drop-in directory and update the configuration for each file if err := filepath.WalkDir(kubeletDropInConfigDir, func(path string, info fs.DirEntry, err error) error { if err != nil { return err } - if !info.IsDir() && filepath.Ext(info.Name()) == dropinFileExtension { - dropinConfigJSON, gvk, err := loadDropinConfigFileIntoJSON(path) - if err != nil { - return fmt.Errorf("failed to load kubelet dropin file, path: %s, error: %w", path, err) - } - if gvk == nil || gvk.Empty() { - return fmt.Errorf("failed to load kubelet dropin file, path: %s, no apiVersion/kind", path) - } - // TODO: expand this once more than a single kubelet config version exists - if *gvk != kubeletconfigv1beta1.SchemeGroupVersion.WithKind("KubeletConfiguration") { - return fmt.Errorf("failed to load kubelet dropin file, path: %s, unknown apiVersion/kind: %v", path, gvk.String()) - } - mergedConfigJSON, err := jsonpatch.MergePatch(baseKubeletConfigJSON, dropinConfigJSON) - if err != nil { - return fmt.Errorf("failed to merge drop-in and current config: %w", err) - } - baseKubeletConfigJSON = mergedConfigJSON + if info.IsDir() { + return nil + } + if filepath.Ext(info.Name()) != dropinFileExtension { + skippedFiles = append(skippedFiles, path) + return nil + } + dropinConfigJSON, gvk, err := loadDropinConfigFileIntoJSON(path) + if err != nil { + return fmt.Errorf("failed to load kubelet dropin file, path: %s, error: %w", path, err) } + if gvk == nil || gvk.Empty() { + return fmt.Errorf("failed to load kubelet dropin file, path: %s, no apiVersion/kind", path) + } + // TODO: expand this once more than a single kubelet config version exists + if *gvk != kubeletconfigv1beta1.SchemeGroupVersion.WithKind("KubeletConfiguration") { + return fmt.Errorf("failed to load kubelet dropin file, path: %s, unknown apiVersion/kind: %v", path, gvk.String()) + } + mergedConfigJSON, err := jsonpatch.MergePatch(baseKubeletConfigJSON, dropinConfigJSON) + if err != nil { + return fmt.Errorf("failed to merge drop-in and current config: %w", err) + } + baseKubeletConfigJSON = mergedConfigJSON return nil }); err != nil { - return fmt.Errorf("failed to walk through kubelet dropin directory %q: %w", kubeletDropInConfigDir, err) + return nil, fmt.Errorf("failed to walk through kubelet dropin directory %q: %w", kubeletDropInConfigDir, err) } // reset versioned config and decode versionedConfig = &kubeletconfigv1beta1.KubeletConfiguration{} if err := json.Unmarshal(baseKubeletConfigJSON, versionedConfig); err != nil { - return fmt.Errorf("failed to unmarshal merged JSON into kubelet configuration: %w", err) + return nil, fmt.Errorf("failed to unmarshal merged JSON into kubelet configuration: %w", err) } // apply defaulting after decoding kubeletconfigv1beta1conversion.SetDefaults_KubeletConfiguration(versionedConfig) // convert back to internal config if err := kubeletconfigv1beta1conversion.Convert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(versionedConfig, kubeletConfig, nil); err != nil { - return fmt.Errorf("failed to convert merged config to internal kubelet configuration: %w", err) + return nil, fmt.Errorf("failed to convert merged config to internal kubelet configuration: %w", err) } - return nil + return skippedFiles, nil } // newFlagSetWithGlobals constructs a new pflag.FlagSet with global flags registered @@ -511,7 +531,7 @@ func UnsecuredDependencies(ctx context.Context, s *options.KubeletServer, featur OOMAdjuster: oom.NewOOMAdjuster(), OSInterface: kubecontainer.RealOS{}, VolumePlugins: plugins, - DynamicPluginProber: GetDynamicPluginProber(s.VolumePluginDir, pluginRunner), + DynamicPluginProber: GetDynamicPluginProber(ctx, s.VolumePluginDir, pluginRunner), TLSOptions: tlsOptions}, nil } @@ -540,12 +560,12 @@ func setConfigz(cz *configz.Config, kc *kubeletconfiginternal.KubeletConfigurati if err != nil { return err } - versioned := kubeletconfigv1beta1.KubeletConfiguration{} - if err := scheme.Convert(kc, &versioned, nil); err != nil { + versioned := &kubeletconfigv1beta1.KubeletConfiguration{} + if err := scheme.Convert(kc, versioned, nil); err != nil { return err } - cz.Set(versioned) - return nil + versioned.GetObjectKind().SetGroupVersionKind(kubeletconfigv1beta1.SchemeGroupVersion.WithKind("KubeletConfiguration")) + return cz.Set(versioned) } func initConfigz(ctx context.Context, kc *kubeletconfiginternal.KubeletConfiguration) error { @@ -616,15 +636,28 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend if err != nil { return err } + // Propagate feature gate state to the metrics subsystem. This must be called + // after feature gates are set and before any histogram metrics are registered. + metricsfeatures.ApplyFeatureGates(featureGate) // validate the initial KubeletServer (we set feature gates first, because this validation depends on feature gates) if err := options.ValidateKubeletServer(s); err != nil { return err } - // Warn if MemoryQoS enabled with cgroups v1 - if utilfeature.DefaultFeatureGate.Enabled(features.MemoryQoS) && - !kubeletutil.IsCgroup2UnifiedMode() { - logger.Info("Warning: MemoryQoS feature only works with cgroups v2 on Linux, but enabled with cgroups v1") + // Warn about MemoryQoS compatibility issues + if utilfeature.DefaultFeatureGate.Enabled(features.MemoryQoS) { + if !kubeletutil.IsCgroup2UnifiedMode() { + logger.Info("Warning: MemoryQoS feature only works with cgroups v2 on Linux, but enabled with cgroups v1") + } else { + kernelVersion, err := utilkernel.GetVersion() + if err != nil { + logger.Error(err, "Failed to detect kernel version for MemoryQoS compatibility check") + } else if kernelVersion.LessThan(utilversion.MustParseGeneric(utilkernel.MemoryQoSMinKernelVersion)) { + logger.Info("Warning: MemoryQoS memory.high throttling may cause process livelock on older kernels", + "currentKernel", kernelVersion, + "minimumKernel", utilkernel.MemoryQoSMinKernelVersion) + } + } } // Obtain Kubelet Lock File if s.ExitOnLockContention && s.LockFilePath == "" { @@ -724,7 +757,7 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend runAuthenticatorCAReload(ctx.Done()) } - if err := kubelet.PreInitRuntimeService(&s.KubeletConfiguration, kubeDeps); err != nil { + if err := kubelet.PreInitRuntimeService(ctx, &s.KubeletConfiguration, kubeDeps); err != nil { return err } @@ -767,7 +800,7 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend if kubeDeps.CAdvisorInterface == nil { imageFsInfoProvider := cadvisor.NewImageFsInfoProvider(s.ContainerRuntimeEndpoint) - kubeDeps.CAdvisorInterface, err = cadvisor.New(imageFsInfoProvider, s.RootDirectory, cgroupRoots, cadvisor.UsingLegacyCadvisorStats(s.ContainerRuntimeEndpoint), s.LocalStorageCapacityIsolation) + kubeDeps.CAdvisorInterface, err = cadvisor.New(klog.FromContext(ctx), imageFsInfoProvider, s.RootDirectory, cgroupRoots, cadvisor.UsingLegacyCadvisorStats(s.ContainerRuntimeEndpoint), s.LocalStorageCapacityIsolation) if err != nil { return err } @@ -873,6 +906,7 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend CPUManagerReconcilePeriod: s.CPUManagerReconcilePeriod.Duration, MemoryManagerPolicy: s.MemoryManagerPolicy, MemoryManagerReservedMemory: s.ReservedMemory, + MemoryReservationPolicy: s.MemoryReservationPolicy, PodPidsLimit: s.PodPidsLimit, EnforceCPULimits: s.CPUCFSQuota, CPUCFSQuotaPeriod: s.CPUCFSQuotaPeriod.Duration, @@ -911,12 +945,12 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend if s.HealthzPort > 0 { mux := http.NewServeMux() healthz.InstallHandler(mux) - go wait.Until(func() { + go wait.UntilWithContext(ctx, func(ctx context.Context) { err := http.ListenAndServe(net.JoinHostPort(s.HealthzBindAddress, strconv.Itoa(int(s.HealthzPort))), mux) if err != nil { logger.Error(err, "Failed to start healthz server") } - }, 5*time.Second, wait.NeverStop) + }, 5*time.Second) } // If systemd is used, notify it that we have started @@ -1144,7 +1178,7 @@ func InitializeTLS(ctx context.Context, kf *options.KubeletFlags, kc *kubeletcon if len(tlsCipherSuites) > 0 { insecureCiphers := cliflag.InsecureTLSCiphers() - for i := 0; i < len(tlsCipherSuites); i++ { + for i := range tlsCipherSuites { for cipherName, cipherID := range insecureCiphers { if tlsCipherSuites[i] == cipherID { logger.Info("Use of insecure cipher detected.", "cipher", cipherName) @@ -1164,24 +1198,18 @@ func InitializeTLS(ctx context.Context, kf *options.KubeletFlags, kc *kubeletcon } } - tlsOptions := &server.TLSOptions{ - Config: &tls.Config{ - MinVersion: minTLSVersion, - CipherSuites: tlsCipherSuites, - }, - CertFile: kc.TLSCertFile, - KeyFile: kc.TLSPrivateKeyFile, + curvePreferences, err := cliflag.TLSCurvePreferences(kc.TLSCurvePreferences) + if err != nil { + return nil, err } - if len(kc.Authentication.X509.ClientCAFile) > 0 { - clientCAs, err := certutil.NewPool(kc.Authentication.X509.ClientCAFile) - if err != nil { - return nil, fmt.Errorf("unable to load client CA file %s: %w", kc.Authentication.X509.ClientCAFile, err) - } - // Specify allowed CAs for client certificates - tlsOptions.Config.ClientCAs = clientCAs - // Populate PeerCertificates in requests, but don't reject connections without verified certificates - tlsOptions.Config.ClientAuth = tls.RequestClientCert + tlsOptions := &server.TLSOptions{ + MinVersion: minTLSVersion, + CipherSuites: tlsCipherSuites, + CurvePreferences: curvePreferences, + CertFile: kc.TLSCertFile, + KeyFile: kc.TLSPrivateKeyFile, + ClientCAFile: kc.Authentication.X509.ClientCAFile, } return tlsOptions, nil @@ -1267,16 +1295,18 @@ func RunKubelet(ctx context.Context, kubeServer *options.KubeletServer, kubeDeps func startKubelet(ctx context.Context, k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, enableServer bool) { // start the kubelet - go k.Run(podCfg.Updates()) + go k.Run(ctx, podCfg.Updates()) // start the kubelet server if enableServer { - go k.ListenAndServe(ctx, kubeCfg, kubeDeps.TLSOptions, kubeDeps.Auth, kubeDeps.TracerProvider) + go k.ListenAndServe(ctx, kubeCfg, kubeDeps.TLSConfig, kubeDeps.Auth, kubeDeps.TracerProvider) } if kubeCfg.ReadOnlyPort > 0 { go k.ListenAndServeReadOnly(ctx, netutils.ParseIPSloppy(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort), kubeDeps.TracerProvider) } + go k.ListenAndServePodResources(ctx) + go k.ListenAndServePods(ctx) } func createAndInitKubelet( @@ -1322,7 +1352,7 @@ func createAndInitKubelet( k.BirthCry() - k.StartGarbageCollection() + k.StartGarbageCollection(ctx) return k, nil } @@ -1384,7 +1414,7 @@ func getCgroupDriverFromCRI(ctx context.Context, s *options.KubeletServer, kubeD ) // Retry a couple of times, hoping that any errors are transient. // Fail quickly on known, non transient errors. - for i := 0; i < 3; i++ { + for range 3 { runtimeConfig, err = kubeDeps.RemoteRuntimeService.RuntimeConfig(ctx) if err != nil { s, ok := status.FromError(err) @@ -1397,7 +1427,7 @@ func getCgroupDriverFromCRI(ctx context.Context, s *options.KubeletServer, kubeD } // CRI implementation doesn't support RuntimeConfig, fallback legacyregistry.MustRegister(kubeletmetrics.CRILosingSupport) - kubeletmetrics.CRILosingSupport.WithLabelValues("1.36.0").Inc() + kubeletmetrics.CRILosingSupport.WithLabelValues("1.37.0").Inc() logger.Info("CRI implementation should be updated to support RuntimeConfig. Falling back to using cgroupDriver from kubelet config.") return nil } diff --git a/cmd/kubelet/app/server_others.go b/cmd/kubelet/app/server_others.go index 1f96ad7e52b10..4faf436d3e65e 100644 --- a/cmd/kubelet/app/server_others.go +++ b/cmd/kubelet/app/server_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubelet/app/server_test.go b/cmd/kubelet/app/server_test.go index d552ae9d13ad4..2e1eaf7f4a4f2 100644 --- a/cmd/kubelet/app/server_test.go +++ b/cmd/kubelet/app/server_test.go @@ -78,10 +78,12 @@ func TestMergeKubeletConfigurations(t *testing.T) { kubeletConfig *kubeletconfiginternal.KubeletConfiguration dropin1 string dropin2 string + customDropins map[string]string overwrittenConfigFields map[string]interface{} cliArgs []string name string expectMergeError string + expectedSkippedFiles []string }{ { kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{ @@ -291,6 +293,54 @@ readOnlyPort: 10255 `, expectMergeError: "", }, + { + name: "yaml files are ignored, only .conf files are loaded", + kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeletConfiguration", + APIVersion: "kubelet.config.k8s.io/v1beta1", + }, + Port: int32(9090), + }, + customDropins: map[string]string{ + "10-should-be-ignored.yaml": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: 7777 +`, + "20-should-be-ignored.txt": "readme text", + "30-valid.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +readOnlyPort: 10255 +`, + }, + overwrittenConfigFields: map[string]interface{}{ + "Port": int32(9090), // Port should remain unchanged as .yaml file should be ignored + "ReadOnlyPort": int32(10255), // ReadOnlyPort should be set from .conf file + }, + expectedSkippedFiles: []string{"10-should-be-ignored.yaml", "20-should-be-ignored.txt"}, + }, + { + name: "invalid YAML syntax in drop-in file causes merge to fail", + kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeletConfiguration", + APIVersion: "kubelet.config.k8s.io/v1beta1", + }, + Port: int32(9090), + }, + customDropins: map[string]string{ + "10-malformed.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: [this is not valid + indentation is wrong + more indentation +`, + }, + expectMergeError: "10-malformed.conf", + }, } for _, test := range testCases { @@ -311,7 +361,7 @@ readOnlyPort: 10255 kubeletFlags.KubeletConfigFile = kubeletConfFile kubeletConfig = test.kubeletConfig } - if len(test.dropin1) > 0 || len(test.dropin2) > 0 { + if len(test.dropin1) > 0 || len(test.dropin2) > 0 || len(test.customDropins) > 0 { // Create kubelet.conf.d directory and drop-in configuration files kubeletConfDir := filepath.Join(tempDir, "kubelet.conf.d") err := os.Mkdir(kubeletConfDir, 0755) @@ -327,14 +377,30 @@ readOnlyPort: 10255 require.NoError(t, err, "failed to create config from a yaml file") } + for filename, content := range test.customDropins { + err = os.WriteFile(filepath.Join(kubeletConfDir, filename), []byte(content), 0644) + require.NoError(t, err, "failed to create custom dropin file") + } + // Merge the kubelet configurations - err = mergeKubeletConfigurations(kubeletConfig, kubeletConfDir) + skippedFiles, err := mergeKubeletConfigurations(kubeletConfig, kubeletConfDir) if test.expectMergeError == "" { require.NoError(t, err, "failed to merge kubelet drop-in configs") } else { require.Error(t, err) require.ErrorContains(t, err, test.expectMergeError) } + + // Verify skipped files if expected + if len(test.expectedSkippedFiles) > 0 { + // Extract just the filenames from the full paths + var skippedFilenames []string + for _, path := range skippedFiles { + skippedFilenames = append(skippedFilenames, filepath.Base(path)) + } + require.ElementsMatch(t, test.expectedSkippedFiles, skippedFilenames, + "skipped files do not match expected") + } } // Use kubelet config flag precedence @@ -350,3 +416,108 @@ readOnlyPort: 10255 }) } } + +func TestMergeKubeletConfigsWithSubdirs(t *testing.T) { + testCases := []struct { + name string + kubeletConfig *kubeletconfiginternal.KubeletConfiguration + dropins map[string]string // map[relativePath]content + overwrittenConfigFields map[string]interface{} + }{ + { + name: "subdirectories are processed in lexical order", + kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeletConfiguration", + APIVersion: "kubelet.config.k8s.io/v1beta1", + }, + Port: int32(9090), + ReadOnlyPort: int32(10257), + }, + dropins: map[string]string{ + "10-kubelet.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: 8080 +`, + "subdir/20-kubelet.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: 7777 +readOnlyPort: 9999 +`, + }, + overwrittenConfigFields: map[string]interface{}{ + "Port": int32(7777), + "ReadOnlyPort": int32(9999), + }, + }, + { + name: "same filename in multiple directories - lexical order applies", + kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeletConfiguration", + APIVersion: "kubelet.config.k8s.io/v1beta1", + }, + Port: int32(9090), + ReadOnlyPort: int32(10255), + }, + dropins: map[string]string{ + "10-kubelet.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: 8080 +`, + "sub-dir1/10-kubelet.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: 7070 +readOnlyPort: 8888 +`, + "sub-dir2/10-kubelet.conf": ` +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +port: 6060 +readOnlyPort: 9999 +`, + }, + overwrittenConfigFields: map[string]interface{}{ + "Port": int32(6060), + "ReadOnlyPort": int32(9999), + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + tempDir := t.TempDir() + kubeletConfDir := filepath.Join(tempDir, "kubelet.conf.d") + err := os.Mkdir(kubeletConfDir, 0755) + require.NoError(t, err, "Failed to create kubelet.conf.d directory") + + // Create drop-in files including those in subdirectories + for relPath, content := range test.dropins { + fullPath := filepath.Join(kubeletConfDir, relPath) + // Create subdirectory if needed + dir := filepath.Dir(fullPath) + if dir != kubeletConfDir { + err = os.MkdirAll(dir, 0755) + require.NoError(t, err, "Failed to create subdirectory: "+dir) + } + err = os.WriteFile(fullPath, []byte(content), 0644) + require.NoError(t, err, "Failed to create drop-in file: "+relPath) + } + + // Merge the kubelet configurations + _, err = mergeKubeletConfigurations(test.kubeletConfig, kubeletConfDir) + require.NoError(t, err, "failed to merge kubelet drop-in configs") + + // Verify the merged configuration fields + for fieldName, expectedValue := range test.overwrittenConfigFields { + value := reflect.ValueOf(test.kubeletConfig).Elem() + field := value.FieldByName(fieldName) + require.Equal(t, expectedValue, field.Interface(), "Field mismatch: "+fieldName) + } + }) + } +} diff --git a/cmd/kubelet/app/server_unsupported.go b/cmd/kubelet/app/server_unsupported.go index c4e74c8cb9770..b3f6722859cae 100644 --- a/cmd/kubelet/app/server_unsupported.go +++ b/cmd/kubelet/app/server_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2015 The Kubernetes Authors. diff --git a/cmd/kubelet/app/server_windows.go b/cmd/kubelet/app/server_windows.go index 781cd95cca9bf..79261a9aa3b19 100644 --- a/cmd/kubelet/app/server_windows.go +++ b/cmd/kubelet/app/server_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2021 The Kubernetes Authors. diff --git a/cmd/kubemark/app/hollow_node.go b/cmd/kubemark/app/hollow_node.go index fe7888489f66e..a0ab8eaadeda4 100644 --- a/cmd/kubemark/app/hollow_node.go +++ b/cmd/kubemark/app/hollow_node.go @@ -247,15 +247,14 @@ func run(ctx context.Context, config *hollowNodeConfig) error { return fmt.Errorf("Failed to start fake runtime, error: %w", err) } defer fakeRemoteRuntime.Stop() - logger := klog.Background() - runtimeService, err := remote.NewRemoteRuntimeService(endpoint, 15*time.Second, noop.NewTracerProvider(), &logger) + runtimeService, err := remote.NewRemoteRuntimeService(ctx, endpoint, 15*time.Second, noop.NewTracerProvider(), false) if err != nil { return fmt.Errorf("Failed to init runtime service, error: %w", err) } var imageService internalapi.ImageManagerService = fakeRemoteRuntime.ImageService if config.UseHostImageService { - imageService, err = remote.NewRemoteImageService(c.ImageServiceEndpoint, 15*time.Second, noop.NewTracerProvider(), &logger) + imageService, err = remote.NewRemoteImageService(ctx, c.ImageServiceEndpoint, 15*time.Second, noop.NewTracerProvider(), false) if err != nil { return fmt.Errorf("Failed to init image service, error: %w", err) } diff --git a/cmd/kubemark/app/hollow_node_test.go b/cmd/kubemark/app/hollow_node_test.go index c399433c4abbd..000fde253682a 100644 --- a/cmd/kubemark/app/hollow_node_test.go +++ b/cmd/kubemark/app/hollow_node_test.go @@ -72,7 +72,6 @@ func TestHollowNode(t *testing.T) { } for morph := range knownMorphs { - morph := morph t.Run(morph, func(t *testing.T) { s := &hollowNodeConfig{ KubeconfigPath: kubeconfigPath, diff --git a/cmd/preferredimports/OWNERS b/cmd/preferredimports/OWNERS index 768d003b9262f..dee6fd1367705 100644 --- a/cmd/preferredimports/OWNERS +++ b/cmd/preferredimports/OWNERS @@ -6,5 +6,5 @@ reviewers: approvers: - dims - cblecker -emeritus_aprovers: +emeritus_approvers: - spiffxp diff --git a/go.mod b/go.mod index 96752e9f02925..e9c32a2109b03 100644 --- a/go.mod +++ b/go.mod @@ -6,114 +6,112 @@ module k8s.io/kubernetes -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( bitbucket.org/bertimus9/systemstat v0.5.0 github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab github.com/Microsoft/go-winio v0.6.2 github.com/Microsoft/hnslib v0.1.2 - github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/blang/semver/v4 v4.0.0 github.com/container-storage-interface/spec v1.9.0 - github.com/coredns/corefile-migration v1.0.29 - github.com/coreos/go-oidc v2.3.0+incompatible - github.com/coreos/go-systemd/v22 v22.5.0 + github.com/coredns/corefile-migration v1.0.31 + github.com/coreos/go-oidc v2.5.0+incompatible + github.com/coreos/go-systemd/v22 v22.7.0 github.com/cpuguy83/go-md2man/v2 v2.0.6 - github.com/cyphar/filepath-securejoin v0.6.0 + github.com/cyphar/filepath-securejoin v0.6.1 github.com/distribution/reference v0.6.0 github.com/docker/go-units v0.5.0 - github.com/emicklei/go-restful/v3 v3.12.2 + github.com/emicklei/go-restful/v3 v3.13.0 github.com/fsnotify/fsnotify v1.9.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonreference v0.20.2 - github.com/godbus/dbus/v5 v5.1.0 - github.com/google/cadvisor v0.53.0 + github.com/godbus/dbus/v5 v5.2.2 + github.com/google/cadvisor v0.56.2 github.com/google/cel-go v0.26.0 github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/ishidawataru/sctp v0.0.0-20250521072954-ae8eb7fa7995 - github.com/libopenstorage/openstorage v1.0.0 github.com/lithammer/dedent v1.1.0 github.com/moby/ipvs v1.1.0 github.com/moby/sys/userns v0.1.0 - github.com/mrunalp/fileutils v0.5.1 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 - github.com/onsi/ginkgo/v2 v2.25.1 - github.com/onsi/gomega v1.38.2 - github.com/opencontainers/cgroups v0.0.3 - github.com/opencontainers/selinux v1.13.0 - github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818 - github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9 - github.com/openshift/apiserver-library-go v0.0.0-20260303173613-cd3676268d31 - github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a - github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 + github.com/onsi/ginkgo/v2 v2.28.0 + github.com/onsi/gomega v1.39.1 + github.com/opencontainers/cgroups v0.0.6 + github.com/opencontainers/selinux v1.13.1 + github.com/openshift-eng/openshift-tests-extension v0.0.0-20260612102633-2fd5b2fa4221 + github.com/openshift/api v0.0.0-20260612223652-30cac1b00eaf + github.com/openshift/apiserver-library-go v0.0.0-20260612231116-aa5ab1cf5d9d + github.com/openshift/client-go v0.0.0-20260612225005-10851b7bbd27 + github.com/openshift/library-go v0.0.0-20260612230159-06432ea1b022 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.66.1 + github.com/prometheus/common v0.67.5 github.com/robfig/cron/v3 v3.0.1 - github.com/spf13/cobra v1.10.0 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 - go.etcd.io/etcd/api/v3 v3.6.5 - go.etcd.io/etcd/client/pkg/v3 v3.6.5 - go.etcd.io/etcd/client/v3 v3.6.5 - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 - go.opentelemetry.io/otel/metric v1.36.0 - go.opentelemetry.io/otel/sdk v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 - go.opentelemetry.io/proto/otlp v1.5.0 + go.etcd.io/etcd/api/v3 v3.6.8 + go.etcd.io/etcd/client/pkg/v3 v3.6.8 + go.etcd.io/etcd/client/v3 v3.6.8 + go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.65.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 + go.opentelemetry.io/otel v1.41.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 + go.opentelemetry.io/otel/metric v1.41.0 + go.opentelemetry.io/otel/sdk v1.40.0 + go.opentelemetry.io/otel/trace v1.41.0 + go.opentelemetry.io/proto/otlp v1.9.0 go.uber.org/goleak v1.3.0 - go.uber.org/zap v1.27.0 + go.uber.org/zap v1.27.1 go.yaml.in/yaml/v2 v2.4.3 - golang.org/x/crypto v0.45.0 - golang.org/x/net v0.47.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.18.0 - golang.org/x/sys v0.38.0 - golang.org/x/term v0.37.0 - golang.org/x/text v0.31.0 - golang.org/x/time v0.9.0 - golang.org/x/tools v0.38.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a - google.golang.org/grpc v1.72.2 - google.golang.org/protobuf v1.36.8 + golang.org/x/crypto v0.47.0 + golang.org/x/net v0.49.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/sync v0.19.0 + golang.org/x/sys v0.40.0 + golang.org/x/term v0.39.0 + golang.org/x/text v0.33.0 + golang.org/x/time v0.14.0 + golang.org/x/tools v0.41.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/evanphx/json-patch.v4 v4.13.0 gopkg.in/go-jose/go-jose.v2 v2.6.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.35.1 - k8s.io/apiextensions-apiserver v0.35.1 - k8s.io/apimachinery v0.35.1 - k8s.io/apiserver v0.35.1 + k8s.io/api v0.36.2 + k8s.io/apiextensions-apiserver v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/apiserver v0.36.2 k8s.io/cli-runtime v0.0.0 - k8s.io/client-go v0.35.1 + k8s.io/client-go v0.36.2 k8s.io/cloud-provider v0.0.0 k8s.io/cluster-bootstrap v0.0.0 - k8s.io/code-generator v0.35.1 - k8s.io/component-base v0.35.1 - k8s.io/component-helpers v0.35.1 + k8s.io/code-generator v0.36.2 + k8s.io/component-base v0.36.2 + k8s.io/component-helpers v0.36.2 k8s.io/controller-manager v0.32.1 k8s.io/cri-api v0.0.0 k8s.io/cri-client v0.0.0 + k8s.io/cri-streaming v0.0.0 k8s.io/csi-translation-lib v0.0.0 - k8s.io/dynamic-resource-allocation v0.35.1 + k8s.io/dynamic-resource-allocation v0.36.2 k8s.io/endpointslice v0.0.0 k8s.io/externaljwt v0.0.0 - k8s.io/klog/v2 v2.130.1 - k8s.io/kms v0.35.1 - k8s.io/kube-aggregator v0.35.1 + k8s.io/klog/v2 v2.140.0 + k8s.io/kms v0.36.2 + k8s.io/kube-aggregator v0.36.2 k8s.io/kube-controller-manager v0.0.0 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a k8s.io/kube-proxy v0.0.0 k8s.io/kube-scheduler v0.0.0 k8s.io/kubectl v0.0.0 @@ -122,18 +120,19 @@ require ( k8s.io/mount-utils v0.0.0 k8s.io/pod-security-admission v0.0.0 k8s.io/sample-apiserver v0.0.0 + k8s.io/streaming v0.36.2 k8s.io/system-validators v1.12.1 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 - sigs.k8s.io/knftables v0.0.17 + sigs.k8s.io/knftables v0.0.21 sigs.k8s.io/randfill v1.0.0 - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 sigs.k8s.io/yaml v1.6.0 ) require ( - cel.dev/expr v0.24.0 // indirect - cyphar.com/go-pathrs v0.2.1 // indirect + cel.dev/expr v0.25.1 // indirect + cyphar.com/go-pathrs v0.2.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -141,10 +140,10 @@ require ( github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/containerd/containerd/api v1.9.0 // indirect + github.com/containerd/containerd/api v1.10.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -168,41 +167,36 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // 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.27.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/karrick/godirwalk v1.17.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/term v0.5.0 // 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/opencontainers/runtime-spec v1.3.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect @@ -213,29 +207,33 @@ require ( github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.5 // indirect - go.etcd.io/etcd/server/v3 v3.6.5 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.8 // indirect + go.etcd.io/etcd/server/v3 v3.6.8 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.29.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/mod v0.32.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect - sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/kustomize/api v0.21.1 // indirect + sigs.k8s.io/kustomize/kustomize/v5 v5.8.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 + github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20260424201627-4d4cc33d669d + github.com/openshift/api => github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf + github.com/openshift/apiserver-library-go => github.com/jubittajohn/apiserver-library-go v0.0.0-20260612231116-aa5ab1cf5d9d + github.com/openshift/client-go => github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27 + github.com/openshift/library-go => github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 k8s.io/api => ./staging/src/k8s.io/api k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery @@ -250,6 +248,7 @@ replace ( k8s.io/controller-manager => ./staging/src/k8s.io/controller-manager k8s.io/cri-api => ./staging/src/k8s.io/cri-api k8s.io/cri-client => ./staging/src/k8s.io/cri-client + k8s.io/cri-streaming => ./staging/src/k8s.io/cri-streaming k8s.io/csi-translation-lib => ./staging/src/k8s.io/csi-translation-lib k8s.io/dynamic-resource-allocation => ./staging/src/k8s.io/dynamic-resource-allocation k8s.io/endpointslice => ./staging/src/k8s.io/endpointslice @@ -267,4 +266,5 @@ replace ( k8s.io/sample-apiserver => ./staging/src/k8s.io/sample-apiserver k8s.io/sample-cli-plugin => ./staging/src/k8s.io/sample-cli-plugin k8s.io/sample-controller => ./staging/src/k8s.io/sample-controller + k8s.io/streaming => ./staging/src/k8s.io/streaming ) diff --git a/go.sum b/go.sum index faca449100b62..2403d647c1c85 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,17 @@ bitbucket.org/bertimus9/systemstat v0.5.0 h1:n0aLnh2Jo4nBUBym9cE5PJDG8GT6g+4VuS2Ya2jYYpA= bitbucket.org/bertimus9/systemstat v0.5.0/go.mod h1:EkUWPp8lKFPMXP8vnbpT5JDI0W/sTiLZAvN8ONWErHY= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1/go.mod h1:novQBstnxcGpfKf8qGRATqn1anQKwMJIbH5Q581jibU= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= -cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= -cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cyphar.com/go-pathrs v0.2.2 h1:y9w7hxbkr3zEL78Fjzeg4HEhs2xNy+fbwHiHGJJY2Xo= +cyphar.com/go-pathrs v0.2.2/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -25,15 +26,13 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2/go.mod h1:XyjUkMA8GN+tOOPXvnbi3XuRxWFvTJntqvTFnjmhzbk= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= 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/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= -github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= @@ -53,22 +52,22 @@ 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= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= -github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= -github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= +github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -81,21 +80,21 @@ github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++ github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0= github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= -github.com/coredns/corefile-migration v1.0.29 h1:g4cPYMXXDDs9uLE2gFYrJaPBuUAR07eEMGyh9JBE13w= -github.com/coredns/corefile-migration v1.0.29/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= -github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= -github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coredns/corefile-migration v1.0.31 h1:f7WGhY8M2Jn8P2dVO0p7wSQ1QKsMARl6WEyUjCb/V38= +github.com/coredns/corefile-migration v1.0.31/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= +github.com/coreos/go-oidc v2.5.0+incompatible h1:6W0vGJR3Tu0r0PwfmjOrRZSlfxeEln8dsejt3ZWIvwo= +github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 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= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= -github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -103,22 +102,20 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/distribution/distribution/v3 v3.0.0-20230511163743-f7717b7855ca/go.mod h1:t1IxPNGdTGez+YGKyJyQrtSSqisfMIm1hnFhvMPlxtE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= -github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +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.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/euank/go-kmsg-parser v2.0.0+incompatible h1:cHD53+PLQuuQyLZeriD1V/esuG4MuU0Pjs5y6iknohY= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= @@ -144,7 +141,7 @@ github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -166,14 +163,13 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= 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.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +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 v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -185,8 +181,8 @@ github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= 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/cadvisor v0.53.0 h1:pmveUw2VBlr/T2SBE9Fsp8gdLhKWyOBkECGbaas9mcI= -github.com/google/cadvisor v0.53.0/go.mod h1:Tz3zf/exzFfdWd1T/U/9eNst0ZR2C6CIV62LJATj5tg= +github.com/google/cadvisor v0.56.2 h1:ra6p4Nxc4zT8VLbZscWUxhvjsqy+1AzMvuSdEM90o1w= +github.com/google/cadvisor v0.56.2/go.mod h1:CWidr4DqGbkN4aKuOEjLB7Bab3gl01Xxm3co38C3xRU= 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/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= @@ -194,8 +190,8 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.1.1/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= @@ -203,21 +199,17 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS 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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +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.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -244,9 +236,15 @@ github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf h1:ZNPdLNPryuQVSUzna4We8hoCmzEduyxIbPa2tQgk0a0= +github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf/go.mod h1:FS918VnrERibSebUVMka1NP+eyrKGi5ry4nl/BXAKbs= +github.com/jubittajohn/apiserver-library-go v0.0.0-20260612231116-aa5ab1cf5d9d h1:eJ8cQOYL+k66D68i7AxgeXZxu3/YnUz+LLhzTYK+kZk= +github.com/jubittajohn/apiserver-library-go v0.0.0-20260612231116-aa5ab1cf5d9d/go.mod h1:amlK2WJGsmX88XpSbCSc6JrmWfHpMd65bQMtDyoBrik= +github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27 h1:zAmEOrYRpwjoS5Ob4V8PGWwbEts3CGCsZhzz9qrwaiE= +github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27/go.mod h1:bOkl2x9t+UlXxD8riuhEi0MbjjYnXuYCSzuDXTIA4wI= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 h1:Pk6nmvZxrSQ9IlpsOC9FQiV45idIDLTN3hft7dzxdSA= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022/go.mod h1:iGCkgqyOiqzvkTHEyxVf+RmxzvNNfEFR/AJLoFSxeG0= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= -github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -260,8 +258,6 @@ 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/libopenstorage/openstorage v1.0.0 h1:GLPam7/0mpdP8ZZtKjbfcXJBTIA/T1O6CBErVEFEyIM= -github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= @@ -281,9 +277,12 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/ipvs v1.1.0 h1:ONN4pGaZQgAx+1Scz5RvWV4Q7Gb+mvfRh3NsPS+1XQQ= github.com/moby/ipvs v1.1.0/go.mod h1:4VJMWuf098bsUMmZEiD4Tjk/O7mOn3l1PTD3s4OoYAs= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg= +github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= +github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k= +github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= @@ -297,47 +296,33 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q= -github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/opencontainers/cgroups v0.0.3 h1:Jc9dWh/0YLGjdy6J/9Ln8NM5BfTA4W2BY0GMozy3aDU= -github.com/opencontainers/cgroups v0.0.3/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/opencontainers/cgroups v0.0.6 h1:tfZFWTIIGaUUFImTyuTg+Mr5x8XRiSdZESgEBW7UxuI= +github.com/opencontainers/cgroups v0.0.6/go.mod h1:oWVzJsKK0gG9SCRBfTpnn16WcGEqDI8PAcpMGbqWxcs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runc v1.3.0/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs= -github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= -github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= -github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= -github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818 h1:jJLE/aCAqDf8U4wc3bE1IEKgIxbb0ICjCNVFA49x/8s= -github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M= -github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9 h1:yb8ul1HPFYhO04yp0D8T/qSySZnKv210f4nE//i/Bdg= -github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= -github.com/openshift/apiserver-library-go v0.0.0-20260303173613-cd3676268d31 h1:oYPQMrkzyk002L5aN8I2tkUHTEu9lsVrc1qiJmHJdXU= -github.com/openshift/apiserver-library-go v0.0.0-20260303173613-cd3676268d31/go.mod h1:mnTsMMTtXSPBQzqBp5HXBjLvliveKenRADFQy9m5jc0= +github.com/opencontainers/runc v1.4.0/go.mod h1:sch3Bh3c1NlyAkALoAUz5Br9ubMLZzFcxuovZbnkErk= +github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= +github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= +github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= +github.com/openshift-eng/openshift-tests-extension v0.0.0-20260612102633-2fd5b2fa4221 h1:k6W1oylHruGVhmnnW4t1IN/EPCIenUpnDKEpp+4bEZs= +github.com/openshift-eng/openshift-tests-extension v0.0.0-20260612102633-2fd5b2fa4221/go.mod h1:pHOS9c6BjZv91OkkHyIHAOWnYhxwcxWQkyYGEvPyUCE= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= -github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a h1:EKx2XhOKehd1C5ptY7IrLl4WV35E8kP0pRPnG5BUZXk= -github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a/go.mod h1:V933kvY/cb/Un7UCEOhXHUySNX327u7Epe8g9KNqg2Q= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 h1:AHjJETxL4n2ocMHBY7Epqex574puUSJjKIUkbasYAKI= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3/go.mod h1:gKG9lctU0yEftSoT3DUyeIWz1oAgF0EHUpwI4pnCo4o= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20260424201627-4d4cc33d669d h1:t+XyaZL4LpQx/AY2SETlMCQPLc9vd05ZZ7WXvD9doME= +github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20260424201627-4d4cc33d669d/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= @@ -351,10 +336,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -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/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.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= @@ -365,19 +350,18 @@ github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNl github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= -github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= -github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= 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= @@ -418,59 +402,58 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0 h1:KemlMZlVwBSEGaO91WKgp41BBFsnWqqj9sKRwmOqC40= -go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0/go.mod h1:uq8DrRaen3suIWTpdR/JNHCGpurSvMv9D5Nr5CU5TXc= -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/contrib/propagators/b3 v1.19.0 h1:ulz44cpm6V5oAeg5Aw9HyqGFMS6XM7untlMEhD7YzzA= -go.opentelemetry.io/contrib/propagators/b3 v1.19.0/go.mod h1:OzCmE2IVS+asTI+odXQstRGVfXQ4bXv9nMBRK0nNyqQ= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +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/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.65.0 h1:kCBKHmfe+cFC7nrgJQdxpjveMD5hXqHToW7xuIMsTqU= +go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.65.0/go.mod h1:JLdfEzERFdnjMGZPV3ceg4C+0s6uQalGoNWchryKO5I= +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/contrib/propagators/b3 v1.40.0 h1:xariChe8OOVF3rNlfzGFgQc61npQmXhzZj/i82mxMfg= +go.opentelemetry.io/contrib/propagators/b3 v1.40.0/go.mod h1:72WvbdxbOfXaELEQfonFfOL6osvcVjI7uJEE8C2nkrs= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +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.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 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.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -478,29 +461,29 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -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/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +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/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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -513,25 +496,25 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= 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= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -540,14 +523,16 @@ 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= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +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/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -566,34 +551,33 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= 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.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/system-validators v1.12.1 h1:AY1+COTLJN/Sj0w9QzH1H0yvyF3Kl6CguMnh32WlcUU= k8s.io/system-validators v1.12.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -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 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +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-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/knftables v0.0.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic= -sigs.k8s.io/knftables v0.0.17/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= +sigs.k8s.io/knftables v0.0.21 h1:mby+JtzhJH1Ucnq++ZJaM+ViQBY5JzIyX4bSCP8K5YQ= +sigs.k8s.io/knftables v0.0.21/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/cmd/config v0.20.1/go.mod h1:R7rQ8kxknVlXWVUIbxWtMgu8DCCNVtl8V0KrmeVd/KE= -sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw= -sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU= -sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= +sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= +sigs.k8s.io/kustomize/cmd/config v0.21.1/go.mod h1:7yEFYBJyBJlpZQ50VaRGQRtFMn3Vzn9Fb2wts4TCok4= +sigs.k8s.io/kustomize/kustomize/v5 v5.8.1 h1:Pgsg5psubpVEy7Nf6S89PARg5VmmWUC1l9dC6Dl4PG0= +sigs.k8s.io/kustomize/kustomize/v5 v5.8.1/go.mod h1:0vFa5pQ/elNEQMyiAJuGku9rhAMzz7u9+61hRqFKiwY= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/go.work b/go.work index bd119eefdea91..965e38ff8a102 100644 --- a/go.work +++ b/go.work @@ -1,8 +1,8 @@ // This is a generated file. Do not edit directly. -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 use ( . @@ -20,6 +20,7 @@ use ( ./staging/src/k8s.io/controller-manager ./staging/src/k8s.io/cri-api ./staging/src/k8s.io/cri-client + ./staging/src/k8s.io/cri-streaming ./staging/src/k8s.io/csi-translation-lib ./staging/src/k8s.io/dynamic-resource-allocation ./staging/src/k8s.io/endpointslice @@ -37,4 +38,5 @@ use ( ./staging/src/k8s.io/sample-apiserver ./staging/src/k8s.io/sample-cli-plugin ./staging/src/k8s.io/sample-controller + ./staging/src/k8s.io/streaming ) diff --git a/go.work.sum b/go.work.sum index eb9299c468b21..a31e87ace75b6 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,9 +1,10 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1 h1:4erM3WLgEG/HIBrpBDmRbs1puhd7p0z7kNXDuhHthwM= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M= +buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2 h1:x8Brv0YNEe6jY3V/hQglIG2nd8g5E2Zj5ubGKkPQctQ= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= @@ -19,22 +20,22 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= -github.com/bufbuild/protovalidate-go v0.9.1 h1:cdrIA33994yCcJyEIZRL36ZGTe9UDM/WHs5MBHEimiE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= github.com/distribution/distribution/v3 v3.0.0-20230511163743-f7717b7855ca h1:yGaIDzPWkgU+yRvI2x/rGdOU1hl6bLZzm0mETEUSHwk= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV38221VAK7qc2zhaO17bKys/18= @@ -46,7 +47,7 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE= +github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= @@ -54,13 +55,12 @@ github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHz github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= -github.com/opencontainers/runc v1.3.0 h1:cvP7xbEvD0QQAs0nZKLzkVog2OPZhI/V2w3WmTmUSXI= +github.com/opencontainers/runc v1.4.0 h1:FG1Hw0GBYPsNki+mBz1QOrSzbwbAcerhrAD2r097QCc= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee h1:+Sp5GGnjHDhT/a/nQ1xdp43UscBMr7G5wxsYotyhzJ4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0 h1:AHzMWDxNiAVscJL6+4wkvFRTpMnJqiaZFEKA/osaBXE= @@ -68,18 +68,14 @@ github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= -github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 h1:PFWFSkpArPNJxFX4ZKWAk9NSeRoZaXschn+ULa4xVek= -sigs.k8s.io/kustomize/cmd/config v0.20.1 h1:4APUORmZe2BYrsqgGfEKdd/r7gM6i43egLrUzilpiFo= +sigs.k8s.io/kustomize/cmd/config v0.21.1 h1:/gxf3J1rQD9nfuL8fHlrTLeUL+JHWbK44eOnXJDYx0M= diff --git a/hack/.import-aliases b/hack/.import-aliases index 274ca540e4778..b925a0cb41cab 100644 --- a/hack/.import-aliases +++ b/hack/.import-aliases @@ -36,7 +36,7 @@ "k8s.io/api/rbac/v1alpha1": "rbacv1alpha1", "k8s.io/api/rbac/v1beta1": "rbacv1beta1", "k8s.io/api/scheduling/v1": "schedulingv1", - "k8s.io/api/scheduling/v1alpha1": "schedulingv1alpha1", + "k8s.io/api/scheduling/v1alpha2": "schedulingv1alpha2", "k8s.io/api/scheduling/v1beta1": "schedulingv1beta1", "k8s.io/api/storage/v1": "storagev1", "k8s.io/api/storage/v1alpha1": "storagev1alpha1", diff --git a/hack/apidiff.sh b/hack/apidiff.sh index 57b4761459093..89f6f287dcda6 100755 --- a/hack/apidiff.sh +++ b/hack/apidiff.sh @@ -98,7 +98,16 @@ if [[ -z "${base}" && -n "${PULL_BASE_SHA:-}" && -n "${PULL_PULL_SHA:-}" ]]; the exit 1 fi elif [[ -z "${base}" ]]; then - if ! base="$(git merge-base origin/master "${target:-HEAD}")"; then + # origin is the default remote, but we encourage our contributors + # to have both origin (their fork) and upstream, if upstream is present + # then prefer upstream + # if they have called it something else, there's no good way to be sure ... + remote='origin' + if git remote | grep -q 'upstream'; then + remote='upstream' + fi + default_branch="$(git rev-parse --abbrev-ref "${remote}"/HEAD | cut -d/ -f2)" + if ! base="$(git merge-base "${remote}/${default_branch}" "${target:-HEAD}")"; then echo >&2 "Could not determine default base revision. -r must be used explicitly." exit 1 fi diff --git a/hack/cherry_pick_pull.sh b/hack/cherry_pick_pull.sh index a6f3529dd3423..f13a170550160 100755 --- a/hack/cherry_pick_pull.sh +++ b/hack/cherry_pick_pull.sh @@ -134,6 +134,7 @@ trap return_to_kansas EXIT SUBJECTS=() RELEASE_NOTES=() +KIND_LABELS=() function make-a-pr() { local rel rel="$(basename "${BRANCH}")" @@ -142,6 +143,16 @@ function make-a-pr() { local numandtitle numandtitle=$(printf '%s\n' "${SUBJECTS[@]}") + + local kind_commands="" + if [[ ${#KIND_LABELS[@]} -gt 0 ]]; then + while IFS= read -r label; do + if [[ -n "${label}" ]]; then + kind_commands+="/kind ${label#kind/}"$'\n' + fi + done < <(printf '%s\n' "${KIND_LABELS[@]}" | sort -u) + fi + prtext=$(cat < \"${ETCD_LOGFILE}\" >/dev/null" - etcd --advertise-client-urls "${KUBE_INTEGRATION_ETCD_URL}" --data-dir "${ETCD_DIR}" --listen-client-urls "${KUBE_INTEGRATION_ETCD_URL}" --log-level="${ETCD_LOGLEVEL}" 2> "${ETCD_LOGFILE}" >/dev/null & + kube::log::info "etcd --advertise-client-urls ${KUBE_INTEGRATION_ETCD_URL} --data-dir ${ETCD_DIR} --listen-client-urls http://${ETCD_HOST}:${ETCD_PORT} --listen-peer-urls http://localhost:0 --log-level=${ETCD_LOGLEVEL} 2> \"${ETCD_LOGFILE}\" >/dev/null" + etcd --advertise-client-urls "${KUBE_INTEGRATION_ETCD_URL}" --data-dir "${ETCD_DIR}" --listen-client-urls "${KUBE_INTEGRATION_ETCD_URL}" --listen-peer-urls "http://localhost:0" --log-level="${ETCD_LOGLEVEL}" 2> "${ETCD_LOGFILE}" >/dev/null & ETCD_PID=$! echo "Waiting for etcd to come up." diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index c401de05800c2..c6d29db1ba8b1 100755 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -91,7 +91,11 @@ readonly KUBE_SERVER_BINARIES=("${KUBE_SERVER_TARGETS[@]##*/}") # The set of server targets we build docker images for kube::golang::server_image_targets() { # NOTE: this contains cmd targets for kube::build::get_docker_wrapped_binaries + # NOTE: kube-log-runner should NOT be in kube::build::get_docker_wrapped_binaries + # This binary is used across all images + # kube-log-runner should be the first target so it can be skipped local targets=( + staging/src/k8s.io/component-base/logs/kube-log-runner cmd/kube-apiserver cmd/kube-controller-manager cmd/kube-scheduler @@ -519,7 +523,7 @@ EOF local go_version IFS=" " read -ra go_version <<< "$(GOFLAGS='' go version)" local minimum_go_version - minimum_go_version=go1.25 + minimum_go_version=go1.26 if [[ "${minimum_go_version}" != $(echo -e "${minimum_go_version}\n${go_version[2]}" | sort -s -t. -k 1,1 -k 2,2n -k 3,3n | head -n1) && "${go_version[2]}" != "devel" ]]; then kube::log::usage_from_stdin </dev/null || sudo mkdir -p "${CERT_DIR}" -CONTROLPLANE_SUDO=$(test -w "${CERT_DIR}" || echo "sudo -E") + +# CONTROLPLANE_SUDO is used for control plane components. If the CERT_DIR is not writable, +# "sudo -E" is used to gain the necessary write privileges. +# Can be set to something else or explicitly to empty to override the default. +CONTROLPLANE_SUDO=${CONTROLPLANE_SUDO-$(test -w "${CERT_DIR}" || echo "sudo -E")} + +# KUBELET_SUDO is used for starting the kubelet. +# Can be set to something else or explicitly to empty to override the default. +KUBELET_SUDO=${KUBELET_SUDO-sudo -E} + +# PROXY_SUDO is used for starting kube-proxy. +# Can be set to something else or explicitly to empty to override the default. +PROXY_SUDO=${PROXY_SUDO-sudo -E} + +# Note that "sudo" is still used in various other places and must work. if (( KUBE_VERBOSE <= 4 )); then set +x @@ -424,6 +448,13 @@ function detect_binary { cleanup() { echo "Cleaning up..." + + # Capture CoreDNS logs before shutting down (useful for debugging DNS issues) + if [[ "${ENABLE_CLUSTER_DNS}" == true ]]; then + echo "Capturing CoreDNS logs..." + ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" logs -n kube-system -l k8s-app=kube-dns --all-containers > "${LOG_DIR}/coredns.log" 2>&1 || true + fi + # delete running images # if [[ "${ENABLE_CLUSTER_DNS}" == true ]]; then # Still need to figure why this commands throw an error: Error from server: client: etcd cluster is unavailable or misconfigured @@ -841,27 +872,6 @@ function wait_coredns_available(){ return fi - local interval_time=2 - local coredns_wait_time=300 - - # kick the coredns pods to be recreated - ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" -n kube-system delete pods -l k8s-app=kube-dns - sleep 30 - - local coredns_pods_ready="${KUBECTL} --kubeconfig '${CERT_DIR}/admin.kubeconfig' wait --for=condition=Ready --timeout=60s pods -l k8s-app=kube-dns -n kube-system" - kube::util::wait_for_success "$coredns_wait_time" "$interval_time" "$coredns_pods_ready" - if [ $? == "1" ]; then - echo "time out on waiting for coredns pods" - exit 1 - fi - - local coredns_available="${KUBECTL} --kubeconfig '${CERT_DIR}/admin.kubeconfig' wait --for=condition=Available --timeout=60s deployments coredns -n kube-system" - kube::util::wait_for_success "$coredns_wait_time" "$interval_time" "$coredns_available" - if [ $? == "1" ]; then - echo "time out on waiting for coredns deployment" - exit 1 - fi - if [[ "${ENABLE_DAEMON}" = false ]]; then # bump log level echo "6" | sudo tee /proc/sys/kernel/printk @@ -1016,6 +1026,11 @@ EOF echo "topologyManagerPolicy: \"${TOPOLOGY_MANAGER_POLICY}\"" fi + # memorymanager policy + if [[ -n ${MEMORY_MANAGER_POLICY} ]]; then + echo "memoryManagerPolicy: \"${MEMORY_MANAGER_POLICY}\"" + fi + # cpumanager policy if [[ -n ${CPUMANAGER_POLICY} ]]; then echo "cpuManagerPolicy: \"${CPUMANAGER_POLICY}\"" @@ -1034,7 +1049,8 @@ EOF } >>"${TMP_DIR}"/kubelet.yaml # shellcheck disable=SC2024 - run kubelet "${KUBELET_LOG}" sudo -E "${GO_OUT}/kubelet" "${all_kubelet_flags[@]}" \ + # shellcheck disable=SC2086 # Word-splitting of KUBELET_SUDO is intentional. + run kubelet "${KUBELET_LOG}" ${KUBELET_SUDO} "${GO_OUT}/kubelet" "${all_kubelet_flags[@]}" \ --config="${TMP_DIR}"/kubelet.yaml & KUBELET_PID=$! @@ -1081,7 +1097,8 @@ EOF # Probably not necessary... # # shellcheck disable=SC2024 - run kube-proxy "${PROXY_LOG}" sudo "${GO_OUT}/kube-proxy" \ + # shellcheck disable=SC2086 # Word-splitting of KUBELET_SUDO is intentional. + run kube-proxy "${PROXY_LOG}" ${PROXY_SUDO} "${GO_OUT}/kube-proxy" \ --v="${LOG_LEVEL}" \ --config="${TMP_DIR}"/kube-proxy.yaml \ --healthz-port="${PROXY_HEALTHZ_PORT}" \ @@ -1312,14 +1329,19 @@ function install_cni { rm -rf "${cni_plugin_tarball}" && sudo find /opt/cni/bin -type f -not \( \ -iname host-local \ - -o -iname bridge \ + -o -iname ptp \ -o -iname portmap \ -o -iname loopback \ \) \ -delete - # containerd in kubekins supports CNI version 0.4.0 - echo "Configuring cni" + # Configure CNI using ptp (point-to-point) plugin instead of bridge. + # ptp creates direct veth pairs between pods and host namespace, which + # avoids the need for br_netfilter and bridge-nf-call-iptables settings + # that are unreliable in docker-in-docker environments. + # This approach is proven to work reliably by KIND (Kubernetes IN Docker): + # https://github.com/kubernetes-sigs/kind/blob/main/images/kindnetd/cmd/kindnetd/cni.go#L83-L121 + echo "Configuring cni (ptp mode)" sudo mkdir -p "$CNI_CONFIG_DIR" cat << EOF | sudo tee "$CNI_CONFIG_DIR"/10-containerd-net.conflist { @@ -1327,11 +1349,8 @@ function install_cni { "name": "containerd-net", "plugins": [ { - "type": "bridge", - "bridge": "cni0", - "isGateway": true, + "type": "ptp", "ipMasq": true, - "promiscMode": true, "ipam": { "type": "host-local", "ranges": [ @@ -1387,6 +1406,26 @@ if [[ "${KUBETEST_IN_DOCKER:-}" == "true" ]]; then # configure shared mounts to prevent failure in DIND scenarios mount --make-rshared / + # Configure kernel network parameters for container networking. + # These settings are required for ptp CNI and iptables-based kube-proxy + # to work correctly in docker-in-docker environments. + # See KIND's network configuration for reference. + echo "Configuring kernel network parameters for DIND..." + + # Enable route_localnet - allows routing to localhost addresses after NAT. + # Required for proper DNS resolution in containers. + echo 1 > /proc/sys/net/ipv4/conf/all/route_localnet + + # Set arp_ignore=0 - required for ptp CNI which uses /32 addresses. + # Ensures ARP replies are sent for all local addresses. + echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore + + # Ensure IP forwarding is enabled for pod-to-pod traffic + echo 1 > /proc/sys/net/ipv4/ip_forward + echo 1 > /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true + + echo "Kernel network parameters configured" + # to use containerd as kubelet container runtime we need to install cni install_cni @@ -1467,7 +1506,7 @@ if [[ "${START_MODE}" != *"nokubelet"* ]]; then # Detect the OS name/arch and display appropriate error. case "$(uname -s)" in Darwin) - print_color "kubelet is not currently supported in darwin, kubelet aborted." + print_color "kubelet is not supported on macOS. Please use https://sigs.k8s.io/kind" KUBELET_LOG="" ;; Linux) @@ -1487,7 +1526,7 @@ if [[ "${START_MODE}" != "kubeletonly" ]]; then # Detect the OS name/arch and display appropriate error. case "$(uname -s)" in Darwin) - print_color "kubelet is not currently supported in darwin, kube-proxy aborted." + print_color "kube-proxy is not supported on macOS. Please use https://sigs.k8s.io/kind." ;; Linux) start_kubeproxy diff --git a/hack/logcheck.conf b/hack/logcheck.conf index e8e3a11c4bae2..3a7e7f2903022 100644 --- a/hack/logcheck.conf +++ b/hack/logcheck.conf @@ -31,6 +31,7 @@ contextual k8s.io/client-go/rest/.* contextual k8s.io/client-go/tools/cache/.* contextual k8s.io/client-go/tools/events/.* contextual k8s.io/client-go/tools/record/.* +contextual k8s.io/component-base/featuregate/* contextual k8s.io/component-helpers/.* contextual k8s.io/cri-api/.* contextual k8s.io/cri-client/.* @@ -62,36 +63,11 @@ contextual k8s.io/kubernetes/pkg/scheduler/.* contextual k8s.io/kubernetes/pkg/security/.* contextual k8s.io/kubernetes/pkg/securitycontext/.* contextual k8s.io/kubernetes/test/e2e/dra/.* -contextual k8s.io/kubernetes/pkg/kubelet/certificate/.* -contextual k8s.io/kubernetes/pkg/kubelet/cm/devicemanager/.* -contextual k8s.io/kubernetes/pkg/kubelet/cm/dra/.* -contextual k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/.* -contextual k8s.io/kubernetes/pkg/kubelet/cm/memorymanager/.* -contextual k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/.* -contextual k8s.io/kubernetes/pkg/kubelet/lifecycle/.* -contextual k8s.io/kubernetes/pkg/kubelet/pleg/.* -contextual k8s.io/kubernetes/pkg/kubelet/clustertrustbundle/.* -contextual k8s.io/kubernetes/pkg/kubelet/token/.* -contextual k8s.io/kubernetes/pkg/kubelet/cadvisor/.* -contextual k8s.io/kubernetes/pkg/kubelet/config/.* -contextual k8s.io/kubernetes/pkg/kubelet/oom/.* -contextual k8s.io/kubernetes/pkg/kubelet/server/.* -contextual k8s.io/kubernetes/pkg/kubelet/status/.* -contextual k8s.io/kubernetes/pkg/kubelet/sysctl/.* -contextual k8s.io/kubernetes/pkg/kubelet/winstats/.* -contextual k8s.io/kubernetes/pkg/kubelet/apis/.* -contextual k8s.io/kubernetes/pkg/kubelet/kubeletconfig/.* -contextual k8s.io/kubernetes/pkg/kubelet/kuberuntime/.* -contextual k8s.io/kubernetes/pkg/kubelet/nodeshutdown/.* -contextual k8s.io/kubernetes/pkg/kubelet/pod/.* -contextual k8s.io/kubernetes/pkg/kubelet/preemption/.* -contextual k8s.io/kubernetes/pkg/kubelet/volumemanager/.* -contextual k8s.io/kubernetes/pkg/kubelet/util/.* -contextual k8s.io/kubernetes/pkg/kubelet/logs/.* contextual k8s.io/kubernetes/test/images/sample-device-plugin/.* - -# As long as contextual logging is alpha or beta, all WithName, WithValues, -# NewContext calls have to go through klog. Once it is GA, we can lift -# this restriction. Whether we then do a global search/replace remains -# to be decided. -with-helpers .* +contextual k8s.io/kubernetes/pkg/kubelet/.* +# Temporary carve-outs for PR1 split safety: these files still have legacy global klog calls. +# TODO: remove each exclusion as follow-up PRs migrate the file to contextual logging. +-contextual k8s.io/kubernetes/pkg/kubelet/kubelet_resources.go +-contextual k8s.io/kubernetes/pkg/kubelet/kubelet_volumes.go +-contextual k8s.io/kubernetes/pkg/kubelet/pod_container_deletor.go +-contextual k8s.io/kubernetes/pkg/kubelet/volume_host.go diff --git a/hack/make-rules/test.sh b/hack/make-rules/test.sh index db8d6df6c0ccb..442ab10b90933 100755 --- a/hack/make-rules/test.sh +++ b/hack/make-rules/test.sh @@ -102,7 +102,7 @@ isnum() { [[ "$1" =~ ^[0-9]+$ ]] } -PARALLEL="${PARALLEL:-1}" +PARALLEL="${PARALLEL:--1}" while getopts "hp:i:" opt ; do case ${opt} in h) @@ -176,6 +176,10 @@ if [[ -n "${KUBE_RACE}" ]] ; then goflags+=("${KUBE_RACE}") fi +if [[ "${PARALLEL}" -gt 0 ]]; then + goflags+=(-p "${PARALLEL}") +fi + junitFilenamePrefix() { if [[ -z "${KUBE_JUNIT_REPORT_DIR}" ]]; then echo "" @@ -269,7 +273,7 @@ checkFDs() { # due to the low default files limit on OS X. Warn about low limit. local fileslimit fileslimit="$(ulimit -n)" - if [[ ${fileslimit} -lt 1000 ]]; then + if [[ "${fileslimit}" != "unlimited" && "${fileslimit}" -lt 1000 ]]; then echo "WARNING: ulimit -n (files) should be at least 1000, is ${fileslimit}, may cause test failure"; fi } diff --git a/hack/testdata/pod-with-large-name.yaml b/hack/testdata/pod-with-large-name.yaml index 5c98ab8197038..bf79d916fd025 100644 --- a/hack/testdata/pod-with-large-name.yaml +++ b/hack/testdata/pod-with-large-name.yaml @@ -8,5 +8,5 @@ metadata: spec: containers: - name: kubernetes-serve-hostname - image: registry.k8s.io/e2e-test-images/agnhost:2.32 + image: registry.k8s.io/e2e-test-images/agnhost:2.63.0 command: ["/agnhost", "serve-hostname"] diff --git a/hack/testdata/pod-with-precision.json b/hack/testdata/pod-with-precision.json index 2312145d60fd7..ade9b8b1f7b20 100644 --- a/hack/testdata/pod-with-precision.json +++ b/hack/testdata/pod-with-precision.json @@ -9,7 +9,7 @@ "containers": [ { "name": "kubernetes-pause", - "image": "registry.k8s.io/pause:3.10.1" + "image": "registry.k8s.io/pause:3.10.2" } ], "restartPolicy": "Never", diff --git a/hack/tools/go.mod b/hack/tools/go.mod index 682ad6b005cdf..8e4359fb2ce0b 100644 --- a/hack/tools/go.mod +++ b/hack/tools/go.mod @@ -1,8 +1,8 @@ module k8s.io/kubernetes/hack/tools -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( golang.org/x/mod v0.27.0 diff --git a/hack/tools/go.work b/hack/tools/go.work index ebdf3df29addb..8db90b28242c0 100644 --- a/hack/tools/go.work +++ b/hack/tools/go.work @@ -1,8 +1,8 @@ // This is a hack, but it prevents go from climbing further and trying to // reconcile the various deps across the "real" modules and this one. -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 use . diff --git a/hack/tools/golangci-lint/go.mod b/hack/tools/golangci-lint/go.mod index fd5df6f6a91e7..ea77b822eacdd 100644 --- a/hack/tools/golangci-lint/go.mod +++ b/hack/tools/golangci-lint/go.mod @@ -1,6 +1,6 @@ module k8s.io/kubernetes/hack/tools/golangci-lint -go 1.25.0 +go 1.26.0 tool ( github.com/golangci/golangci-lint/v2/cmd/golangci-lint @@ -10,58 +10,61 @@ tool ( require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - golang.org/x/tools v0.37.0 + golang.org/x/tools v0.42.0 ) require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - codeberg.org/chavacava/garif v0.2.0 // indirect + codeberg.org/chavacava/garif v0.2.1 // indirect + codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect - dev.gaijin.team/go/golib v0.6.0 // indirect + dev.gaijin.team/go/golib v0.8.1 // indirect github.com/4meepo/tagalign v1.4.3 // indirect - github.com/Abirdcfly/dupword v0.1.6 // indirect + github.com/Abirdcfly/dupword v0.1.7 // indirect github.com/AdminBenni/iota-mixing v1.0.0 // indirect github.com/AlwxSin/noinlineerr v1.0.5 // indirect github.com/Antonboom/errname v1.1.1 // indirect github.com/Antonboom/nilnil v1.1.1 // indirect github.com/Antonboom/testifylint v1.6.4 // indirect - github.com/BurntSushi/toml v1.5.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect - github.com/MirrexOne/unqueryvet v1.2.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/MirrexOne/unqueryvet v1.5.4 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/alecthomas/chroma/v2 v2.20.0 // indirect + github.com/alecthomas/chroma/v2 v2.23.1 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect - github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alexkohler/prealloc v1.1.0 // indirect github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect - github.com/ashanbrown/forbidigo/v2 v2.1.0 // indirect - github.com/ashanbrown/makezero/v2 v2.0.1 // indirect + github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect + github.com/ashanbrown/makezero/v2 v2.1.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect - github.com/bombsimon/wsl/v5 v5.2.0 // indirect + github.com/bombsimon/wsl/v5 v5.6.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect github.com/butuzov/ireturn v0.4.0 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.9.1 // indirect + github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charithe/durationcheck v0.0.10 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charithe/durationcheck v0.0.11 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/x/ansi v0.8.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/daixiang0/gci v0.13.7 // indirect + github.com/daixiang0/gci v0.14.0 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect @@ -70,10 +73,10 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.6 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.16 // indirect - github.com/go-critic/go-critic v0.13.0 // indirect + github.com/ghostiam/protogetter v0.3.20 // indirect + github.com/go-critic/go-critic v0.14.3 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -81,20 +84,18 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/godoc-lint/godoc-lint v0.10.0 // indirect - github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/godoc-lint/godoc-lint v0.11.2 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint/v2 v2.5.0 // indirect - github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect - github.com/golangci/misspell v0.7.0 // indirect - github.com/golangci/nilerr v0.0.0-20250918000102-015671e622fe // indirect + github.com/golangci/golangci-lint/v2 v2.11.2 // indirect + github.com/golangci/golines v0.15.0 // indirect + github.com/golangci/misspell v0.8.0 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect @@ -104,58 +105,55 @@ require ( github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.8.2 // indirect + github.com/jgautheron/goconst v1.9.0 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect github.com/julz/importas v0.2.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect - github.com/kisielk/errcheck v1.9.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect + github.com/kisielk/errcheck v1.10.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/kulti/thelper v0.7.1 // indirect - github.com/kunwardeep/paralleltest v1.0.14 // indirect + github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.4.4 // indirect - github.com/ldez/gomoddirectives v0.7.0 // indirect + github.com/ldez/exptostd v0.4.5 // indirect + github.com/ldez/gomoddirectives v0.8.0 // indirect github.com/ldez/grignotin v0.10.1 // indirect + github.com/ldez/structtags v0.6.1 // indirect github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect github.com/manuelarte/funcorder v0.5.0 // indirect - github.com/maratori/testableexamples v1.0.0 // indirect - github.com/maratori/testpackage v1.1.1 // indirect + github.com/maratori/testableexamples v1.0.1 // indirect + github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.12.0 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect + github.com/mgechev/revive v1.15.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/termenv v0.16.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.21.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect + github.com/nunnatsa/ginkgolinter v0.23.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/polyfloyd/go-errorlint v1.8.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.4.4 // indirect - github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/quasilyte/go-ruleguard v0.4.5 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect @@ -164,34 +162,34 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.22.8 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/securego/gosec/v2 v2.24.7 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sonatard/noctx v0.4.0 // indirect + github.com/sonatard/noctx v0.5.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.12.0 // indirect + github.com/spf13/viper v1.21.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect + github.com/stretchr/objx v0.5.3 // indirect github.com/stretchr/testify v1.11.1 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tetafro/godot v1.5.4 // indirect - github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect + github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect - github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect - github.com/uudashr/gocognit v1.2.0 // indirect + github.com/uudashr/gocognit v1.2.1 // indirect github.com/uudashr/iface v1.4.1 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -201,27 +199,27 @@ require ( gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.14.0 // indirect go-simpler.org/sloglint v0.11.1 // indirect - go.augendre.info/arangolint v0.2.0 // indirect - go.augendre.info/fatcontext v0.8.1 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + go.augendre.info/arangolint v0.4.0 // indirect + go.augendre.info/fatcontext v0.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/exp/typeparams v0.0.0-20260218203240-3dfff04db8fa // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.6.1 // indirect + honnef.co/go/tools v0.7.0 // indirect k8s.io/apimachinery v0.32.3 // indirect + k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - mvdan.cc/gofumpt v0.9.1 // indirect - mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect - sigs.k8s.io/kube-api-linter v0.0.0-20251029172002-9992248f8813 // indirect - sigs.k8s.io/logtools v0.9.0 // indirect + mvdan.cc/gofumpt v0.9.2 // indirect + mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect + sigs.k8s.io/kube-api-linter v0.0.0-20260114104534-18147eee9c49 // indirect + sigs.k8s.io/logtools v0.10.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/hack/tools/golangci-lint/go.sum b/hack/tools/golangci-lint/go.sum index 76b82053aa859..d942a6b7f3290 100644 --- a/hack/tools/golangci-lint/go.sum +++ b/hack/tools/golangci-lint/go.sum @@ -2,49 +2,18 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= -codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= +codeberg.org/chavacava/garif v0.2.1 h1:K9oYxSlvlXrHXyW26Z4q61bTpJDo1wbvXcYKar/F/LM= +codeberg.org/chavacava/garif v0.2.1/go.mod h1:oHnDSmc0f9K1MeE+MQD/yjkiIB5Xsn5y3S9Dg96Xk84= +codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= +codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= -dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= -dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dev.gaijin.team/go/golib v0.8.1 h1:JYju4x9BSo+QD/AYeHULVDcvEhiFg8wOi6pT0IaZF5E= +dev.gaijin.team/go/golib v0.8.1/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= -github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c= -github.com/Abirdcfly/dupword v0.1.6/go.mod h1:s+BFMuL/I4YSiFv29snqyjwzDp4b65W2Kvy+PKzZ6cw= +github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= +github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= @@ -55,49 +24,40 @@ github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksuf github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/MirrexOne/unqueryvet v1.2.1 h1:M+zdXMq84g+E1YOLa7g7ExN3dWfZQrdDSTCM7gC+m/A= -github.com/MirrexOne/unqueryvet v1.2.1/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg= +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/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ= +github.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= -github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= +github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= +github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= -github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= -github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M= +github.com/alexkohler/prealloc v1.1.0/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig= github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= -github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs= -github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA= -github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY= -github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw= +github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo= +github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c= +github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE= +github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= @@ -106,8 +66,8 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.2.0 h1:PyCCwd3Q7abGs3e34IW4jLYlBS+FbsU6iK+Tb3NnDp4= -github.com/bombsimon/wsl/v5 v5.2.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I= +github.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8= +github.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= @@ -116,39 +76,35 @@ github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0= -github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= +github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= +github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= -github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= +github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= -github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= -github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= +github.com/daixiang0/gci v0.14.0 h1:h6AcLqmjIOBgojhtzY2CvBnA6RawPTkBHgtMvYD5YZ8= +github.com/daixiang0/gci v0.14.0/go.mod h1:w9E+SWQ4aPQ+xYPUdqitGDoXpT4mayqOfk7Szz2U6wQ= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= @@ -161,10 +117,6 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -173,30 +125,20 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.16 h1:UkrisuJBYLnZW6FcYUNBDJOqY3X22RtoYMlCsiNlFFA= -github.com/ghostiam/protogetter v0.3.16/go.mod h1:4SRRIv6PcjkIMpUkRUsP4TsUTqO/N3Fmvwivuc/sCHA= -github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY= -github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= +github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= +github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= +github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -218,46 +160,16 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godoc-lint/godoc-lint v0.10.0 h1:OcyrziBi18sQSEpib6NesVHEJ/Xcng97NunePBA48g4= -github.com/godoc-lint/godoc-lint v0.10.0/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7pDQg4NxMYLcBBsw= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= +github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= @@ -266,14 +178,12 @@ github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarog github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.5.0 h1:BDRg4ASm4J1y/DSRY6zwJ5tr5Yy8ZqbZ79XrCeFxaQo= -github.com/golangci/golangci-lint/v2 v2.5.0/go.mod h1:IJtWJBZkLbx7AVrIUzLd8Oi3ADtwaNpWbR3wthVWHcc= -github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8= -github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= -github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c= -github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= -github.com/golangci/nilerr v0.0.0-20250918000102-015671e622fe h1:F1pK9tBy41i7eesBFkSNMldwtiAaWiU+3fT/24sTnNI= -github.com/golangci/nilerr v0.0.0-20250918000102-015671e622fe/go.mod h1:CtTxAluxD2ng9aIT9bPrVoMuISFWCD+SaxtvYtdWA2k= +github.com/golangci/golangci-lint/v2 v2.11.2 h1:4Icd3mEqthcFcFww8L67OBtfKB/obXxko8aFUMqP/5w= +github.com/golangci/golangci-lint/v2 v2.11.2/go.mod h1:wexdFBIQNhHNhDe1oqzlGFE5dYUqlfccWJKWjoWF1GI= +github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= +github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= +github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= +github.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= @@ -282,37 +192,14 @@ github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2b github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= -github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= @@ -322,6 +209,8 @@ github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytm github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= +github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= +github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= @@ -330,85 +219,64 @@ github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1T github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= -github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= +github.com/jgautheron/goconst v1.9.0 h1:lCZu0hmff6FtZQ65KCcK2G/f49AbSJIMyugfhvR5Id8= +github.com/jgautheron/goconst v1.9.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= -github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= -github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= -github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= -github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= +github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= +github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= +github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= -github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8= -github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= +github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= +github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.4.4 h1:58AtQjnLcT/tI5W/1KU7xE/O7zW9RAWB6c/ScQAnfus= -github.com/ldez/exptostd v0.4.4/go.mod h1:QfdzPw6oHjFVdNV7ILoPu5sw3OZ3OG1JS0I5JN3J4Js= -github.com/ldez/gomoddirectives v0.7.0 h1:EOx8Dd56BZYSez11LVgdj025lKwlP0/E5OLSl9HDwsY= -github.com/ldez/gomoddirectives v0.7.0/go.mod h1:wR4v8MN9J8kcwvrkzrx6sC9xe9Cp68gWYCsda5xvyGc= +github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= +github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= +github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= +github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= +github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= +github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY= github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= -github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= +github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= +github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= +github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= @@ -417,39 +285,30 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.12.0 h1:Q+/kkbbwerrVYPv9d9efaPGmAO/NsxwW/nE6ahpQaCU= -github.com/mgechev/revive v1.12.0/go.mod h1:VXsY2LsTigk8XU9BpZauVLjVrhICMOV3k1lpB3CXrp8= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= +github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.21.0 h1:IYwuX+ajy3G1MezlMLB1BENRtFj16+Evyi4uki1NOOQ= -github.com/nunnatsa/ginkgolinter v0.21.0/go.mod h1:QlzY9UP9zaqu58FjYxhp9bnjuwXwG1bfW5rid9ChNMw= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= -github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= +github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -457,46 +316,23 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= -github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ= -github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= -github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= -github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +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.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA= +github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= +github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= +github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -505,10 +341,8 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -516,6 +350,8 @@ github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= @@ -524,73 +360,65 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.22.8 h1:3NMpmfXO8wAVFZPNsd3EscOTa32Jyo6FLLlW53bexMI= -github.com/securego/gosec/v2 v2.22.8/go.mod h1:ZAw8K2ikuH9qDlfdV87JmNghnVfKB1XC7+TVzk6Utto= +github.com/securego/gosec/v2 v2.24.7 h1:3k5yJnrhT1TTdsG0ZsnenlfCcT+7Y/+zeCPHbL7QAn8= +github.com/securego/gosec/v2 v2.24.7/go.mod h1:AdDJbjcG/XxFgVv7pW19vMNYlFM6+Q6Qy3t6lWAUcEY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= -github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= +github.com/sonatard/noctx v0.5.0 h1:e/jdaqAsuWVOKQ0P6NWiIdDNHmHT5SwuuSfojFjzwrw= +github.com/sonatard/noctx v0.5.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= -github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= +github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= +github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU= -github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU= +github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= +github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= -github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= +github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= @@ -604,7 +432,6 @@ github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+ github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -618,66 +445,30 @@ go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= -go.augendre.info/arangolint v0.2.0 h1:2NP/XudpPmfBhQKX4rMk+zDYIj//qbt4hfZmSSTcpj8= -go.augendre.info/arangolint v0.2.0/go.mod h1:Vx4KSJwu48tkE+8uxuf0cbBnAPgnt8O1KWiT7bljq7w= -go.augendre.info/fatcontext v0.8.1 h1:/T4+cCjpL9g71gJpcFAgVo/K5VFpqlN+NPU7QXxD5+A= -go.augendre.info/fatcontext v0.8.1/go.mod h1:r3Qz4ZOzex66wfyyj5VZ1xUcl81vzvHQ6/GWzzlMEwA= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= +go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= +go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= +go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= 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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.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= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 h1:Yl4H5w2RV7L/dvSHp2GerziT5K2CORgFINPaMFxWGWw= -golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/exp/typeparams v0.0.0-20260218203240-3dfff04db8fa h1:6Wi43P0isP8Nl8D4qJmA3VC4FswVH0RJkr5cauo67SQ= +golang.org/x/exp/typeparams v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo= 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.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -685,199 +476,70 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= -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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= 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= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= 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= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -886,125 +548,29 @@ 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= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -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= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -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.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/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= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= -honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= +honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +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/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -mvdan.cc/gofumpt v0.9.1 h1:p5YT2NfFWsYyTieYgwcQ8aKV3xRvFH4uuN/zB2gBbMQ= -mvdan.cc/gofumpt v0.9.1/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw= -mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= -mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/kube-api-linter v0.0.0-20251029172002-9992248f8813 h1:bn2rchbo/TdTln/RcJU3B49BjPBZTl7/+pj8RSVxLbY= -sigs.k8s.io/kube-api-linter v0.0.0-20251029172002-9992248f8813/go.mod h1:TwxOEmBIjl8POuwDF2VfmrHZ5a4o2SIPjDyqtTX7T3Q= -sigs.k8s.io/logtools v0.9.0 h1:IMP/HTDkfM6rg6os/tcEjmQeIHyOyu/enduM/cOPGNQ= -sigs.k8s.io/logtools v0.9.0/go.mod h1:74Z5BP7ehrMHi/Q31W1gSf8YgwT/4GPjVH5xPSPeZA0= +mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= +mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= +mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= +mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= +sigs.k8s.io/kube-api-linter v0.0.0-20260114104534-18147eee9c49 h1:Dlr79S/bnHg+g86cBxul/lbctdVfMTLN1c5XeN6/0JM= +sigs.k8s.io/kube-api-linter v0.0.0-20260114104534-18147eee9c49/go.mod h1:5mP60UakkCye+eOcZ5p98VnV2O49qreW1gq9TdsUf7Q= +sigs.k8s.io/logtools v0.10.1 h1:uerGjWqOnlFXjNARkbrWjSYsjC6RSKDvhwcN2rEMdaM= +sigs.k8s.io/logtools v0.10.1/go.mod h1:nkFoZEUa/k2zgFzO0CNNgl86ufV69OXqLbuWHpv0ZnA= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/hack/tools/golangci-lint/go.work b/hack/tools/golangci-lint/go.work index ebdf3df29addb..8db90b28242c0 100644 --- a/hack/tools/golangci-lint/go.work +++ b/hack/tools/golangci-lint/go.work @@ -1,8 +1,8 @@ // This is a hack, but it prevents go from climbing further and trying to // reconcile the various deps across the "real" modules and this one. -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 use . diff --git a/hack/unwanted-dependencies.json b/hack/unwanted-dependencies.json index cb651e3a1868b..48602e1795d5b 100644 --- a/hack/unwanted-dependencies.json +++ b/hack/unwanted-dependencies.json @@ -1,11 +1,6 @@ { "spec": { - "pinnedModules": { - "github.com/libopenstorage/openstorage": { - "Version": "v1.0.0", - "Reason": "portworx csi driver has been deprecated and will be removed - https://github.com/kubernetes/enhancements/issues/2589" - } - }, + "pinnedModules": {}, "unwantedModules": { "cloud.google.com/go": "cloud dependency", "cloud.google.com/go/bigquery": "cloud dependency", @@ -21,6 +16,7 @@ "github.com/containerd/cgroups": "standardize on single cgroups library from runc, refer #128157", "github.com/davecgh/go-spew": "refer to #103942", "github.com/evanphx/json-patch/v5": "refer to https://github.com/kubernetes/kubernetes/pull/91622#issuecomment-637087847", + "github.com/flynn/go-shlex": "unmaintained, archive mode", "github.com/form3tech-oss/jwt-go": "unmaintained, archive mode", "github.com/getsentry/raven-go": "unmaintained, archive mode", "github.com/go-bindata/go-bindata": "refer to #99829", @@ -34,6 +30,7 @@ "github.com/golang/mock": "unmaintained, archive mode", "github.com/golang/protobuf": "replace with google.golang.org/protobuf", "github.com/golang/groupcache": "unmaintained", + "github.com/google/btree": "unmaintained, archive mode", "github.com/google/gofuzz": "unmaintained, use sigs.k8s.io/randfill", "github.com/google/s2a-go": "cloud dependency, unstable", "github.com/google/shlex": "unmaintained, archive mode", @@ -44,7 +41,6 @@ "github.com/gorilla/rpc": "unmaintained, archive mode", "github.com/gorilla/schema": "unmaintained, archive mode", "github.com/gregjones/httpcache": "unmaintained, archive mode", - "github.com/grpc-ecosystem/go-grpc-prometheus": "unmaintained, archive mode", "github.com/grpc-ecosystem/grpc-gateway": "use github.com/grpc-ecosystem/grpc-gateway/v2", "github.com/hashicorp/consul": "MPL license not in CNCF allowlist", "github.com/hashicorp/errwrap": "MPL license not in CNCF allowlist", @@ -63,6 +59,7 @@ "github.com/influxdata/influxdb1-client": "db/datastore clients should not be required", "github.com/json-iterator/go": "refer to #105030", "github.com/klauspost/compress": "unreviewable assembly code, `prometheus/client_golang` should use stdlib instead", + "github.com/kr/pty": "unmaintained, archive mode", "github.com/mailru/easyjson": "unmaintained", "github.com/miekg/dns": "no dns client/server should be required", "github.com/mindprince/gonvml": "depends on nvml.h that does not appear to permit modification, redistribution", @@ -130,13 +127,15 @@ "go.uber.org/goleak", "go.uber.org/multierr", "go.uber.org/zap", - "k8s.io/apimachinery", "k8s.io/kube-openapi", "k8s.io/utils", "sigs.k8s.io/kustomize/api", "sigs.k8s.io/kustomize/kustomize/v5", "sigs.k8s.io/kustomize/kyaml" ], + "github.com/flynn/go-shlex": [ + "github.com/coredns/caddy" + ], "github.com/gogo/protobuf": [ "github.com/containerd/containerd/api", "github.com/containerd/errdefs/pkg", @@ -165,18 +164,14 @@ "google.golang.org/protobuf", "sigs.k8s.io/apiserver-network-proxy/konnectivity-client" ], + "github.com/google/btree": [ + "go.etcd.io/etcd/server/v3" + ], "github.com/google/gofuzz": [ "github.com/json-iterator/go" ], - "github.com/gregjones/httpcache": [ - "k8s.io/client-go" - ], - "github.com/grpc-ecosystem/go-grpc-prometheus": [ - "k8s.io/apiserver" - ], "github.com/json-iterator/go": [ "github.com/prometheus/client_golang", - "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful", "k8s.io/kube-openapi", "sigs.k8s.io/structured-merge-diff/v6" ], @@ -196,22 +191,15 @@ "github.com/modern-go/concurrent": [ "github.com/json-iterator/go", "github.com/prometheus/client_golang", - "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful", "k8s.io/kube-openapi", "sigs.k8s.io/structured-merge-diff/v6" ], "github.com/modern-go/reflect2": [ "github.com/json-iterator/go", "github.com/prometheus/client_golang", - "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful", "k8s.io/kube-openapi", "sigs.k8s.io/structured-merge-diff/v6" ], - "github.com/pkg/errors": [ - "github.com/google/cadvisor", - "sigs.k8s.io/kustomize/api", - "sigs.k8s.io/kustomize/kustomize/v5" - ], "github.com/planetscale/vtprotobuf": [ "google.golang.org/grpc" ], @@ -232,9 +220,7 @@ "github.com/google/cel-go", "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus", "github.com/grpc-ecosystem/go-grpc-middleware/v2", - "github.com/grpc-ecosystem/grpc-gateway/v2", "github.com/prometheus/common", - "github.com/spf13/cobra", "github.com/stretchr/objx", "github.com/stretchr/testify", "go.etcd.io/bbolt", @@ -267,8 +253,7 @@ "github.com/davecgh/go-spew", "github.com/gogo/protobuf", "github.com/golang/protobuf", - "github.com/gregjones/httpcache", - "github.com/grpc-ecosystem/go-grpc-prometheus", + "github.com/google/btree", "github.com/json-iterator/go", "github.com/mailru/easyjson", "github.com/modern-go/concurrent", diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 2d13d305a8256..62c6642fafc09 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -113,7 +113,7 @@ fi # Some of the later codegens depend on the results of this, so it needs to come # first in the case of regenerating everything. function codegen::protobuf() { - if [[ -n "${LINT:-}" ]]; then + if [[ -n "${LINT:-}" ]]; then if [[ "${KUBE_VERBOSE}" -gt 2 ]]; then kube::log::status "No linter for protobuf codegen" fi @@ -202,7 +202,6 @@ function codegen::deepcopy() { -v "${KUBE_VERBOSE}" \ --go-header-file "${BOILERPLATE_FILENAME}" \ --output-file "${output_file}" \ - --bounding-dirs "k8s.io/kubernetes,k8s.io/api" \ "${tag_pkgs[@]}" \ "$@" @@ -657,7 +656,8 @@ function codegen::openapi() { kube::util::read-array tag_files < <( k8s_tag_files_except \ staging/src/k8s.io/code-generator \ - staging/src/k8s.io/sample-apiserver + staging/src/k8s.io/sample-apiserver \ + staging/src/k8s.io/sample-controller ) local tag_dirs=() @@ -928,6 +928,7 @@ function codegen::protobindings() { "staging/src/k8s.io/kubelet/pkg/apis/dra" "staging/src/k8s.io/kubelet/pkg/apis/deviceplugin" "staging/src/k8s.io/kubelet/pkg/apis/podresources" + "staging/src/k8s.io/kubelet/pkg/apis/pods" "staging/src/k8s.io/kms/apis" "staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2" "staging/src/k8s.io/kubelet/pkg/apis/pluginregistration" diff --git a/hack/update-featuregates.sh b/hack/update-featuregates.sh index 792c6e05bc287..274e8f0fb5653 100755 --- a/hack/update-featuregates.sh +++ b/hack/update-featuregates.sh @@ -14,8 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This script updates test/compatibility_lifecycle/reference/versioned_feature_list.yaml -# with all the feature gate features. +# This script updates test/compatibility_lifecycle/reference/versioned_feature_list.yaml +# and test/compatibility_lifecycle/reference/feature_list.md with all the feature gate features. # Usage: `hack/update-featuregates.sh`. set -o errexit @@ -28,3 +28,4 @@ source "${KUBE_ROOT}/hack/lib/init.sh" cd "${KUBE_ROOT}" go run test/compatibility_lifecycle/main.go feature-gates update +go run cmd/genfeaturegates/genfeaturegates.go -output=test/compatibility_lifecycle/reference/feature_list.md diff --git a/hack/verify-e2e-images.sh b/hack/verify-e2e-images.sh index 7c99a73d727b6..db684e60d5a7b 100755 --- a/hack/verify-e2e-images.sh +++ b/hack/verify-e2e-images.sh @@ -24,6 +24,10 @@ cd "${KUBE_ROOT}" source hack/lib/init.sh ret=0 +# We need deterministic sort order and want to avoid this extra log output: +# i18n.go:119] Couldn't find the LC_ALL, LC_MESSAGES or LANG environment variables, defaulting to en_US +export LC_ALL=C + # NOTE: Please do NOT add any to this list!! # # We are aiming to consolidate on: registry.k8s.io/e2e-test-images/agnhost @@ -39,7 +43,10 @@ e2e_test="$(kube::util::find-binary e2e.test)" # validate "e2e.test --list-images": # - no unexpected output (whether it's on stderr or stdout) # - zero exit code (indirectly ensures that tests are set up properly) -output=$("${e2e_test}" --list-images 2>&1) || ret=$? +# +# For now (https://github.com/google/cadvisor/issues/3707, https://github.com/google/cadvisor/pull/3778) +# we have to ignore additional output caused by cadvisor. +output=$("${e2e_test}" --list-images 2>&1 | grep -v 'factory.go.*Registered Plugin' ) || ret=$? if [[ $ret -ne 0 ]]; then >&2 echo "FAIL: '${e2e_test} --list-images' failed:" >&2 echo "${output}" @@ -54,7 +61,7 @@ if [[ -n "${unexpected_output}" ]]; then fi # extract image names without the version -kube::util::read-array IMAGES < <(echo "${output}" | sed -E 's/^([[:alnum:]/.-]+):[^:]+$/\1/' | LC_ALL=C sort -u) +kube::util::read-array IMAGES < <(echo "${output}" | sed -E 's/^([[:alnum:]/.-]+):[^:]+$/\1/' | sort -u) # diff versus known permitted images >&2 echo "Diffing e2e image list ..." diff --git a/hack/verify-featuregates.sh b/hack/verify-featuregates.sh index 1122d62d79586..5b68b9174f817 100755 --- a/hack/verify-featuregates.sh +++ b/hack/verify-featuregates.sh @@ -15,7 +15,8 @@ # limitations under the License. # This script checks test/compatibility_lifecycle/reference/versioned_feature_list.yaml -# are up to date with all the feature gate features, and verifies no feature is removed before 3 versions post `lockedToDefault:true`. +# and test/compatibility_lifecycle/reference/feature_list.md are up to date with all the +# feature gate features, and verifies no feature is removed before 3 versions post `lockedToDefault:true`. # We should run `hack/update-featuregates.sh` if the list is out of date. # Usage: `hack/verify-featuregates.sh`. @@ -31,6 +32,19 @@ kube::golang::setup_env cd "${KUBE_ROOT}" if ! go run test/compatibility_lifecycle/main.go feature-gates verify; then - echo "Please run 'hack/update-featuregates.sh' to update the feature list." + echo >&2 "Please run 'hack/update-featuregates.sh' to update the feature list." + exit 1 +fi + +# Verify feature_list.md is up to date +FEATURE_LIST_MD="test/compatibility_lifecycle/reference/feature_list.md" +TMPFILE=$(mktemp) +trap 'rm -f "${TMPFILE}"' EXIT + +go run cmd/genfeaturegates/genfeaturegates.go -output="${TMPFILE}" + +if ! diff -q "${FEATURE_LIST_MD}" "${TMPFILE}" > /dev/null 2>&1; then + echo >&2 "${FEATURE_LIST_MD} is out of date." + echo >&2 "Please run 'hack/update-featuregates.sh' to update the feature list." exit 1 fi diff --git a/hack/verify-licenses.sh b/hack/verify-licenses.sh index 2c29cba6bd760..998acb5e607e2 100755 --- a/hack/verify-licenses.sh +++ b/hack/verify-licenses.sh @@ -33,7 +33,7 @@ kube::util::ensure-temp-dir ARTIFACTS="${ARTIFACTS:-${PWD}/_artifacts}" mkdir -p "$ARTIFACTS/logs/" -# Creating a new repository tree +# Creating a new repository tree # Deleting vendor directory to make go-licenses fetch license URLs from go-packages source repository git worktree add -f "${KUBE_TEMP}"/tmp_test_licenses/kubernetes HEAD >/dev/null 2>&1 || true cd "${KUBE_TEMP}"/tmp_test_licenses/kubernetes && rm -rf vendor @@ -55,7 +55,7 @@ echo '[INFO] Installing go-licenses...' go install github.com/google/go-licenses@latest # Fetching CNCF Approved List Of Licenses -# Refer: https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md +# Refer: https://github.com/cncf/foundation/blob/main/policies-guidance/allowed-third-party-license-policy.md curl -s 'https://spdx.org/licenses/licenses.json' -o "${ARTIFACTS}"/licenses.json echo '[INFO] Fetching current list of CNCF approved licenses...' @@ -122,11 +122,11 @@ fi if [[ ${#packages_flagged[@]} -gt 0 ]]; then - echo -e "\n[ERROR] The following go-packages in the project are using non-CNCF approved licenses. Please refer to the CNCF's approved licence list for further information: https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md" + echo -e "\n[ERROR] The following go-packages in the project are using non-CNCF approved licenses. Please refer to the CNCF's approved licence list for further information: https://github.com/cncf/foundation/blob/main/policies-guidance/allowed-third-party-license-policy.md" awk '{ printf "%-100s : %-20s : %s\n", $1, $2, $3 }' "${ARTIFACTS}"/notapproved_licenses.dump exit_code=1 elif [[ "${exit_code}" -eq 1 ]]; then - echo -e "\n[ERROR] Project is using go-packages with unknown or unreachable license URLs. Please refer to the CNCF's approved licence list for further information: https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md" + echo -e "\n[ERROR] Project is using go-packages with unknown or unreachable license URLs. Please refer to the CNCF's approved licence list for further information: https://github.com/cncf/foundation/blob/main/policies-guidance/allowed-third-party-license-policy.md" else echo -e "\n[SUCCESS] Scan complete! All go-packages under the project are using current CNCF approved licenses!" fi diff --git a/hack/verify-prometheus-imports.sh b/hack/verify-prometheus-imports.sh index ba1321e1e418c..69008202cd118 100755 --- a/hack/verify-prometheus-imports.sh +++ b/hack/verify-prometheus-imports.sh @@ -58,6 +58,7 @@ allowed_prometheus_importers=( ./staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go ./staging/src/k8s.io/component-base/metrics/metric.go ./staging/src/k8s.io/component-base/metrics/opts.go + ./staging/src/k8s.io/component-base/metrics/options_test.go ./staging/src/k8s.io/component-base/metrics/processstarttime_others.go ./staging/src/k8s.io/component-base/metrics/registry.go ./staging/src/k8s.io/component-base/metrics/registry_test.go @@ -70,9 +71,6 @@ allowed_prometheus_importers=( ./staging/src/k8s.io/component-base/metrics/value.go ./staging/src/k8s.io/component-base/metrics/wrappers.go ./test/e2e/apimachinery/flowcontrol.go - ./test/e2e_node/mirror_pod_grace_period_test.go - ./test/e2e/node/pods.go - ./test/e2e_node/resource_metrics_test.go ./test/instrumentation/main_test.go ./test/integration/apiserver/flowcontrol/concurrency_test.go ./test/integration/apiserver/flowcontrol/concurrency_util_test.go diff --git a/hack/verify-typecheck.sh b/hack/verify-typecheck.sh index e3f064cc8f1d3..1e83493a7c85c 100755 --- a/hack/verify-typecheck.sh +++ b/hack/verify-typecheck.sh @@ -39,8 +39,8 @@ if [[ $# == 0 ]]; then fi ret=0 -TYPECHECK_SERIAL="${TYPECHECK_SERIAL:-false}" -go run ./test/typecheck "$@" "--serial=$TYPECHECK_SERIAL" || ret=$? +TYPECHECK_SERIAL="${TYPECHECK_SERIAL:-true}" +go run ./test/typecheck "--serial=$TYPECHECK_SERIAL" "$@" || ret=$? if [[ $ret -ne 0 ]]; then echo "!!! Typecheck has failed. This may cause cross platform build failures." >&2 diff --git a/openshift-hack/cmd/k8s-tests-ext/disabled_tests.go b/openshift-hack/cmd/k8s-tests-ext/disabled_tests.go index d840da8ce667b..313b7f88ecf4f 100644 --- a/openshift-hack/cmd/k8s-tests-ext/disabled_tests.go +++ b/openshift-hack/cmd/k8s-tests-ext/disabled_tests.go @@ -162,9 +162,6 @@ func filterOutDisabledSpecs(specs et.ExtensionTestSpecs) et.ExtensionTestSpecs { // https://issues.redhat.com/browse/OCPBUGS-45275 "[sig-network] Connectivity Pod Lifecycle should be able to connect to other Pod from a terminating Pod", - - // https://issues.redhat.com/browse/OCPBUGS-63132 - "[sig-node] [Serial] Pod InPlace Resize Container (deferred-resizes) [FeatureGate:InPlacePodVerticalScaling] pod-resize-retry-deferred-test-3", }, // tests that need to be temporarily disabled while the rebase is in progress. "RebaseInProgress": { @@ -173,6 +170,12 @@ func filterOutDisabledSpecs(specs et.ExtensionTestSpecs) et.ExtensionTestSpecs { // https://issues.redhat.com/browse/OCPBUGS-61378 "[sig-network] Conntrack should be able to cleanup conntrack entries when UDP service target port changes for a NodePort service", + + // https://redhat.atlassian.net/browse/OCPBUGS-85262 + "[sig-cli] kubectl kuberc commands", + + // https://redhat.atlassian.net/browse/OCPBUGS-86789 + "[sig-api-machinery][Feature:APIServer][Late] API LBs follow /readyz of kube-apiserver and don't send request early", }, // tests that may work, but we don't support them "Unsupported": { diff --git a/openshift-hack/images/hyperkube/Dockerfile.rhel b/openshift-hack/images/hyperkube/Dockerfile.rhel index eb3aba07df961..ac000558bde21 100644 --- a/openshift-hack/images/hyperkube/Dockerfile.rhel +++ b/openshift-hack/images/hyperkube/Dockerfile.rhel @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS builder ARG TAGS="" WORKDIR /go/src/k8s.io/kubernetes COPY . . @@ -9,10 +9,10 @@ RUN make TAGS="${TAGS}" WHAT='cmd/kube-apiserver cmd/kube-controller-manager cmd /tmp/build && \ gzip /tmp/build/k8s-tests-ext -FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 +FROM registry.ci.openshift.org/ocp/5.0:base-rhel9 RUN yum install -y --setopt=tsflags=nodocs --setopt=skip_missing_names_on_install=False iproute && yum clean all COPY --from=builder /tmp/build/* /usr/bin/ LABEL io.k8s.display-name="OpenShift Kubernetes Server Commands" \ io.k8s.description="OpenShift is a platform for developing, building, and deploying containerized applications." \ io.openshift.tags="openshift,hyperkube" \ - io.openshift.build.versions="kubernetes=1.35.3" \ No newline at end of file + io.openshift.build.versions="kubernetes=1.36.2" diff --git a/openshift-hack/images/installer-kube-apiserver-artifacts/Dockerfile.rhel b/openshift-hack/images/installer-kube-apiserver-artifacts/Dockerfile.rhel index 985ed96da373e..8316c83c58adc 100644 --- a/openshift-hack/images/installer-kube-apiserver-artifacts/Dockerfile.rhel +++ b/openshift-hack/images/installer-kube-apiserver-artifacts/Dockerfile.rhel @@ -2,7 +2,7 @@ # the kube-apiserver layered on top of the cluster-native Linux installer image. # The resulting image is used to build the openshift-install binary. -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS macbuilder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS macbuilder ARG TAGS="" WORKDIR /go/src/k8s.io/kubernetes COPY . . @@ -10,7 +10,7 @@ ENV KUBE_BUILD_PLATFORMS=darwin/amd64 ENV KUBE_STATIC_OVERRIDES=kube-apiserver RUN make WHAT='cmd/kube-apiserver' -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS macarmbuilder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS macarmbuilder ARG TAGS="" WORKDIR /go/src/k8s.io/kubernetes COPY . . @@ -18,7 +18,7 @@ ENV KUBE_BUILD_PLATFORMS=darwin/arm64 ENV KUBE_STATIC_OVERRIDES=kube-apiserver RUN make WHAT='cmd/kube-apiserver' -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS linuxbuilder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS linuxbuilder ARG TAGS="" WORKDIR /go/src/k8s.io/kubernetes COPY . . @@ -27,7 +27,7 @@ ENV KUBE_BUILD_PLATFORMS=linux/amd64 ENV KUBE_STATIC_OVERRIDES=kube-apiserver RUN make WHAT='cmd/kube-apiserver' -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS linuxarmbuilder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS linuxarmbuilder ARG TAGS="" WORKDIR /go/src/k8s.io/kubernetes COPY . . @@ -36,7 +36,7 @@ ENV KUBE_BUILD_PLATFORMS=linux/arm64 ENV KUBE_STATIC_OVERRIDES=kube-apiserver RUN make WHAT='cmd/kube-apiserver' -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS builder ARG TAGS="" WORKDIR /go/src/k8s.io/kubernetes COPY . . @@ -44,7 +44,7 @@ ENV GO_COMPLIANCE_EXCLUDE=".*" ENV KUBE_STATIC_OVERRIDES=kube-apiserver RUN make WHAT='cmd/kube-apiserver' -FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 +FROM registry.ci.openshift.org/ocp/5.0:base-rhel9 COPY --from=macbuilder /go/src/k8s.io/kubernetes/_output/local/bin/darwin/amd64/kube-apiserver /usr/share/openshift/darwin/amd64/kube-apiserver COPY --from=macarmbuilder /go/src/k8s.io/kubernetes/_output/local/bin/darwin/arm64/kube-apiserver /usr/share/openshift/darwin/arm64/kube-apiserver COPY --from=linuxbuilder /go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver /usr/share/openshift/linux/amd64/kube-apiserver diff --git a/openshift-hack/images/kube-proxy/Dockerfile.rhel b/openshift-hack/images/kube-proxy/Dockerfile.rhel index 29af8385c08a2..7010f36a8c380 100644 --- a/openshift-hack/images/kube-proxy/Dockerfile.rhel +++ b/openshift-hack/images/kube-proxy/Dockerfile.rhel @@ -1,11 +1,11 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS builder WORKDIR /go/src/k8s.io/kubernetes COPY . . RUN make WHAT='cmd/kube-proxy' && \ mkdir -p /tmp/build && \ cp /go/src/k8s.io/kubernetes/_output/local/bin/linux/$(go env GOARCH)/kube-proxy /tmp/build -FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 +FROM registry.ci.openshift.org/ocp/5.0:base-rhel9 RUN INSTALL_PKGS="conntrack-tools iptables nftables" && \ yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ yum clean all && rm -rf /var/cache/* diff --git a/openshift-hack/images/tests/Dockerfile.rhel b/openshift-hack/images/tests/Dockerfile.rhel index 095050c337133..06652839fc952 100644 --- a/openshift-hack/images/tests/Dockerfile.rhel +++ b/openshift-hack/images/tests/Dockerfile.rhel @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0 AS builder WORKDIR /go/src/k8s.io/kubernetes COPY . . RUN make WHAT=openshift-hack/e2e/k8s-e2e.test; \ @@ -11,7 +11,7 @@ RUN make WHAT=openshift-hack/e2e/k8s-e2e.test; \ cp /go/src/k8s.io/kubernetes/openshift-hack/test-kubernetes-e2e.sh /tmp/build/; \ cp /go/src/k8s.io/kubernetes/openshift-hack/images/kube-proxy/test-kube-proxy.sh /tmp/build/ -FROM registry.ci.openshift.org/ocp/4.22:tools +FROM registry.ci.openshift.org/ocp/5.0:tools COPY --from=builder /tmp/build/k8s-e2e.test /usr/bin/ COPY --from=builder /tmp/build/ginkgo /usr/bin/ COPY --from=builder /tmp/build/k8s-tests-ext /usr/bin/ diff --git a/openshift-kube-apiserver/openshiftkubeapiserver/patch.go b/openshift-kube-apiserver/openshiftkubeapiserver/patch.go index 7fd68e6468244..32a2e3ce458da 100644 --- a/openshift-kube-apiserver/openshiftkubeapiserver/patch.go +++ b/openshift-kube-apiserver/openshiftkubeapiserver/patch.go @@ -66,6 +66,12 @@ func OpenShiftKubeAPIServerConfigPatch(genericConfig *genericapiserver.Config, k return nil }) + // Handle the new quota configuration API + quotaConfig, err := install.NewQuotaConfigurationForAdmission(kubeInformers, genericConfig.MergedResourceConfig) + if err != nil { + return err + } + *pluginInitializers = append(*pluginInitializers, imagepolicy.NewInitializer(imagereferencemutators.KubeImageMutators{}, enablement.OpenshiftConfig().ImagePolicyConfig.InternalRegistryHostname), restrictusers.NewInitializer(openshiftInformers.getOpenshiftUserInformers()), @@ -73,7 +79,7 @@ func OpenShiftKubeAPIServerConfigPatch(genericConfig *genericapiserver.Config, k clusterresourcequota.NewInitializer( openshiftInformers.getOpenshiftQuotaInformers().Quota().V1().ClusterResourceQuotas(), clusterQuotaMappingController.GetClusterQuotaMapper(), - generic.NewRegistry(install.NewQuotaConfigurationForAdmission(kubeInformers).Evaluators()), + generic.NewRegistry(quotaConfig.Evaluators()), ), nodeenv.NewInitializer(enablement.OpenshiftConfig().ProjectConfig.DefaultNodeSelector), admissionrestconfig.NewInitializer(*rest.CopyConfig(genericConfig.LoopbackClientConfig)), diff --git a/pkg/admission/plugin/policy/manifest/loader/mutating_loader.go b/pkg/admission/plugin/policy/manifest/loader/mutating_loader.go new file mode 100644 index 0000000000000..abd882bcf59cf --- /dev/null +++ b/pkg/admission/plugin/policy/manifest/loader/mutating_loader.go @@ -0,0 +1,146 @@ +/* +Copyright 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 loader provides functionality to load policy configurations from manifest files. +// This file handles MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding resources. +// It uses the package-level scheme and codecs variables defined in validating_loader.go for +// decoding, defaulting, and validation. +package loader + +import ( + "fmt" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime" + policyloader "k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" +) + +// LoadedMutatingPolicyManifests holds the MAP configurations loaded from manifest files. +type LoadedMutatingPolicyManifests struct { + // Policies is the list of loaded MutatingAdmissionPolicy resources. + Policies []*admissionregistrationv1.MutatingAdmissionPolicy + // Bindings is the list of loaded MutatingAdmissionPolicyBinding resources. + Bindings []*admissionregistrationv1.MutatingAdmissionPolicyBinding + // Hash is the sha256 hash of all loaded files, used for change detection. + Hash string +} + +// LoadMutatingManifestsFromDirectory reads all YAML and JSON files from the specified directory +// and parses them as MutatingAdmissionPolicy or MutatingAdmissionPolicyBinding resources. +// Files are processed in alphabetical order for deterministic behavior. +func LoadMutatingManifestsFromDirectory(dir string) (*LoadedMutatingPolicyManifests, error) { + policies, bindings, hash, err := policyloader.LoadPolicyManifests( + dir, + codecs.UniversalDeserializer(), + acceptMAP, + acceptMAPB, + func(b *admissionregistrationv1.MutatingAdmissionPolicyBinding) string { + return b.Spec.PolicyName + }, + ) + if err != nil { + return nil, err + } + return &LoadedMutatingPolicyManifests{ + Policies: policies, + Bindings: bindings, + Hash: hash, + }, nil +} + +// acceptMAP extracts, defaults, and validates MutatingAdmissionPolicy items. +func acceptMAP(obj runtime.Object) ([]*admissionregistrationv1.MutatingAdmissionPolicy, error) { + switch config := obj.(type) { + case *admissionregistrationv1.MutatingAdmissionPolicy: + if err := defaultAndValidateMAP(config); err != nil { + return nil, err + } + return []*admissionregistrationv1.MutatingAdmissionPolicy{config}, nil + case *admissionregistrationv1.MutatingAdmissionPolicyList: + items := make([]*admissionregistrationv1.MutatingAdmissionPolicy, len(config.Items)) + for i := range config.Items { + items[i] = &config.Items[i] + if err := defaultAndValidateMAP(items[i]); err != nil { + return nil, err + } + } + return items, nil + default: + return nil, policyloader.ErrUnrecognizedType + } +} + +// acceptMAPB extracts, defaults, and validates MutatingAdmissionPolicyBinding items. +func acceptMAPB(obj runtime.Object) ([]*admissionregistrationv1.MutatingAdmissionPolicyBinding, error) { + switch config := obj.(type) { + case *admissionregistrationv1.MutatingAdmissionPolicyBinding: + if err := defaultAndValidateMAPB(config); err != nil { + return nil, err + } + return []*admissionregistrationv1.MutatingAdmissionPolicyBinding{config}, nil + case *admissionregistrationv1.MutatingAdmissionPolicyBindingList: + items := make([]*admissionregistrationv1.MutatingAdmissionPolicyBinding, len(config.Items)) + for i := range config.Items { + items[i] = &config.Items[i] + if err := defaultAndValidateMAPB(items[i]); err != nil { + return nil, err + } + } + return items, nil + default: + return nil, policyloader.ErrUnrecognizedType + } +} + +// defaultAndValidateMAP applies scheme defaults and standard validation. +func defaultAndValidateMAP(policy *admissionregistrationv1.MutatingAdmissionPolicy) error { + scheme.Default(policy) + + internalObj := &admissionregistration.MutatingAdmissionPolicy{} + if err := scheme.Convert(policy, internalObj, nil); err != nil { + return fmt.Errorf("MutatingAdmissionPolicy %q: conversion error: %w", policy.Name, err) + } + if errs := validation.ValidateMutatingAdmissionPolicy(internalObj); len(errs) > 0 { + return fmt.Errorf("MutatingAdmissionPolicy %q: %w", policy.Name, errs.ToAggregate()) + } + resultObj := &admissionregistrationv1.MutatingAdmissionPolicy{} + if err := scheme.Convert(internalObj, resultObj, nil); err != nil { + return fmt.Errorf("MutatingAdmissionPolicy %q: back-conversion error: %w", policy.Name, err) + } + *policy = *resultObj + return nil +} + +// defaultAndValidateMAPB applies scheme defaults and standard validation. +func defaultAndValidateMAPB(binding *admissionregistrationv1.MutatingAdmissionPolicyBinding) error { + scheme.Default(binding) + + internalObj := &admissionregistration.MutatingAdmissionPolicyBinding{} + if err := scheme.Convert(binding, internalObj, nil); err != nil { + return fmt.Errorf("MutatingAdmissionPolicyBinding %q: conversion error: %w", binding.Name, err) + } + if errs := validation.ValidateMutatingAdmissionPolicyBinding(internalObj); len(errs) > 0 { + return fmt.Errorf("MutatingAdmissionPolicyBinding %q: %w", binding.Name, errs.ToAggregate()) + } + resultObj := &admissionregistrationv1.MutatingAdmissionPolicyBinding{} + if err := scheme.Convert(internalObj, resultObj, nil); err != nil { + return fmt.Errorf("MutatingAdmissionPolicyBinding %q: back-conversion error: %w", binding.Name, err) + } + *binding = *resultObj + return nil +} diff --git a/pkg/admission/plugin/policy/manifest/loader/mutating_loader_test.go b/pkg/admission/plugin/policy/manifest/loader/mutating_loader_test.go new file mode 100644 index 0000000000000..38f1487c80902 --- /dev/null +++ b/pkg/admission/plugin/policy/manifest/loader/mutating_loader_test.go @@ -0,0 +1,422 @@ +/* +Copyright 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 loader + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLoadMutatingManifestsFromDirectory(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantPolicyCount int + wantBindingCount int + }{ + { + name: "empty directory", + files: map[string]string{}, + }, + { + name: "load policy and binding", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: test-mutate.static.k8s.io +spec: + reinvocationPolicy: Never + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{spec: Object.spec{serviceAccountName: \"default\"}}" +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: test-mutate-binding.static.k8s.io +spec: + policyName: test-mutate.static.k8s.io +`, + }, + wantPolicyCount: 1, + wantBindingCount: 1, + }, + { + name: "binding references non-existent policy", + files: map[string]string{ + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: orphan-binding.static.k8s.io +spec: + policyName: does-not-exist.static.k8s.io +`, + }, + wantErr: true, + errContains: "does not exist", + }, + { + name: "policy without name", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: "" +spec: + reinvocationPolicy: Never + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +`, + }, + wantErr: true, + errContains: "name", + }, + { + name: "policy name missing static suffix", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: no-suffix +spec: + reinvocationPolicy: Never + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +`, + }, + wantErr: true, + errContains: "must have a name ending with", + }, + { + name: "duplicate policy names", + files: map[string]string{ + "01-policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: dup.static.k8s.io +spec: + reinvocationPolicy: Never + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +`, + "02-policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: dup.static.k8s.io +spec: + reinvocationPolicy: Never + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +`, + }, + wantErr: true, + errContains: "duplicate", + }, + { + name: "policy with paramKind", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: with-param.static.k8s.io +spec: + reinvocationPolicy: Never + paramKind: + apiVersion: v1 + kind: ConfigMap + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +`, + }, + wantErr: true, + errContains: "paramKind is not supported", + }, + { + name: "binding with paramRef", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + name: ref-policy.static.k8s.io +spec: + reinvocationPolicy: Never + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: with-param.static.k8s.io +spec: + policyName: ref-policy.static.k8s.io + paramRef: + name: some-configmap +`, + }, + wantErr: true, + errContains: "paramRef", + }, + { + name: "binding policyName missing static suffix", + files: map[string]string{ + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: bad-ref.static.k8s.io +spec: + policyName: some-policy-without-suffix +`, + }, + wantErr: true, + errContains: "must end with", + }, + { + name: "unsupported type in directory", + files: map[string]string{ + "wrong.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: wrong-type.static.k8s.io +spec: + validations: + - expression: "true" +`, + }, + wantErr: true, + errContains: "unsupported resource type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + } + result, err := LoadMutatingManifestsFromDirectory(dir) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error containing %q, got: %v", tt.errContains, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := len(result.Policies); got != tt.wantPolicyCount { + t.Errorf("Policies count = %d, want %d", got, tt.wantPolicyCount) + } + if got := len(result.Bindings); got != tt.wantBindingCount { + t.Errorf("Bindings count = %d, want %d", got, tt.wantBindingCount) + } + }) + } +} + +func TestLoadMutatingManifestsFromDirectory_ListTypes(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantPolicyCount int + wantBindingCount int + }{ + { + name: "MutatingAdmissionPolicyList", + files: map[string]string{ + "list.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicyList +items: +- metadata: + name: list-policy-1.static.k8s.io + spec: + reinvocationPolicy: Never + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +- metadata: + name: list-policy-2.static.k8s.io + spec: + reinvocationPolicy: Never + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +`, + }, + wantPolicyCount: 2, + }, + { + name: "v1.List with policy and binding", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: MutatingAdmissionPolicy + metadata: + name: v1list-policy.static.k8s.io + spec: + reinvocationPolicy: Never + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + mutations: + - patchType: ApplyConfiguration + applyConfiguration: + expression: "Object{}" +- apiVersion: admissionregistration.k8s.io/v1 + kind: MutatingAdmissionPolicyBinding + metadata: + name: v1list-binding.static.k8s.io + spec: + policyName: v1list-policy.static.k8s.io +`, + }, + wantPolicyCount: 1, + wantBindingCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + } + result, err := LoadMutatingManifestsFromDirectory(dir) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error containing %q, got: %v", tt.errContains, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := len(result.Policies); got != tt.wantPolicyCount { + t.Errorf("Policies count = %d, want %d", got, tt.wantPolicyCount) + } + if got := len(result.Bindings); got != tt.wantBindingCount { + t.Errorf("Bindings count = %d, want %d", got, tt.wantBindingCount) + } + }) + } +} diff --git a/pkg/admission/plugin/policy/manifest/loader/validating_loader.go b/pkg/admission/plugin/policy/manifest/loader/validating_loader.go new file mode 100644 index 0000000000000..5b2712dd208c1 --- /dev/null +++ b/pkg/admission/plugin/policy/manifest/loader/validating_loader.go @@ -0,0 +1,157 @@ +/* +Copyright 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 loader provides functionality to load VAP/MAP configurations from +// manifest files with scheme-based defaulting and validation. +package loader + +import ( + "fmt" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + policyloader "k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + admissionregistrationinstall "k8s.io/kubernetes/pkg/apis/admissionregistration/install" + "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict) +) + +func init() { + admissionregistrationinstall.Install(scheme) + scheme.AddUnversionedTypes(metav1.SchemeGroupVersion, &metav1.List{}, &metav1.Status{}) +} + +// LoadedValidatingPolicyManifests holds the VAP configurations loaded from manifest files. +type LoadedValidatingPolicyManifests struct { + // Policies is the list of loaded ValidatingAdmissionPolicy resources. + Policies []*admissionregistrationv1.ValidatingAdmissionPolicy + // Bindings is the list of loaded ValidatingAdmissionPolicyBinding resources. + Bindings []*admissionregistrationv1.ValidatingAdmissionPolicyBinding + // Hash is the sha256 hash of all loaded files, used for change detection. + Hash string +} + +// LoadManifestsFromDirectory reads all YAML and JSON files from the specified directory +// and parses them as ValidatingAdmissionPolicy or ValidatingAdmissionPolicyBinding resources. +// Files are processed in alphabetical order for deterministic behavior. +func LoadValidatingManifestsFromDirectory(dir string) (*LoadedValidatingPolicyManifests, error) { + policies, bindings, hash, err := policyloader.LoadPolicyManifests( + dir, + codecs.UniversalDeserializer(), + acceptVAP, + acceptVAPB, + func(b *admissionregistrationv1.ValidatingAdmissionPolicyBinding) string { + return b.Spec.PolicyName + }, + ) + if err != nil { + return nil, err + } + return &LoadedValidatingPolicyManifests{ + Policies: policies, + Bindings: bindings, + Hash: hash, + }, nil +} + +// acceptVAP extracts, defaults, and validates ValidatingAdmissionPolicy items. +func acceptVAP(obj runtime.Object) ([]*admissionregistrationv1.ValidatingAdmissionPolicy, error) { + switch config := obj.(type) { + case *admissionregistrationv1.ValidatingAdmissionPolicy: + if err := defaultAndValidateVAP(config); err != nil { + return nil, err + } + return []*admissionregistrationv1.ValidatingAdmissionPolicy{config}, nil + case *admissionregistrationv1.ValidatingAdmissionPolicyList: + items := make([]*admissionregistrationv1.ValidatingAdmissionPolicy, len(config.Items)) + for i := range config.Items { + items[i] = &config.Items[i] + if err := defaultAndValidateVAP(items[i]); err != nil { + return nil, err + } + } + return items, nil + default: + return nil, policyloader.ErrUnrecognizedType + } +} + +// acceptVAPB extracts, defaults, and validates ValidatingAdmissionPolicyBinding items. +func acceptVAPB(obj runtime.Object) ([]*admissionregistrationv1.ValidatingAdmissionPolicyBinding, error) { + switch config := obj.(type) { + case *admissionregistrationv1.ValidatingAdmissionPolicyBinding: + if err := defaultAndValidateVAPB(config); err != nil { + return nil, err + } + return []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{config}, nil + case *admissionregistrationv1.ValidatingAdmissionPolicyBindingList: + items := make([]*admissionregistrationv1.ValidatingAdmissionPolicyBinding, len(config.Items)) + for i := range config.Items { + items[i] = &config.Items[i] + if err := defaultAndValidateVAPB(items[i]); err != nil { + return nil, err + } + } + return items, nil + default: + return nil, policyloader.ErrUnrecognizedType + } +} + +// defaultAndValidateVAP applies scheme defaults and standard validation. +func defaultAndValidateVAP(policy *admissionregistrationv1.ValidatingAdmissionPolicy) error { + scheme.Default(policy) + + internalObj := &admissionregistration.ValidatingAdmissionPolicy{} + if err := scheme.Convert(policy, internalObj, nil); err != nil { + return fmt.Errorf("ValidatingAdmissionPolicy %q: conversion error: %w", policy.Name, err) + } + if errs := validation.ValidateValidatingAdmissionPolicy(internalObj); len(errs) > 0 { + return fmt.Errorf("ValidatingAdmissionPolicy %q: %w", policy.Name, errs.ToAggregate()) + } + resultObj := &admissionregistrationv1.ValidatingAdmissionPolicy{} + if err := scheme.Convert(internalObj, resultObj, nil); err != nil { + return fmt.Errorf("ValidatingAdmissionPolicy %q: back-conversion error: %w", policy.Name, err) + } + *policy = *resultObj + return nil +} + +// defaultAndValidateVAPB applies scheme defaults and standard validation. +func defaultAndValidateVAPB(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) error { + scheme.Default(binding) + + internalObj := &admissionregistration.ValidatingAdmissionPolicyBinding{} + if err := scheme.Convert(binding, internalObj, nil); err != nil { + return fmt.Errorf("ValidatingAdmissionPolicyBinding %q: conversion error: %w", binding.Name, err) + } + if errs := validation.ValidateValidatingAdmissionPolicyBinding(internalObj); len(errs) > 0 { + return fmt.Errorf("ValidatingAdmissionPolicyBinding %q: %w", binding.Name, errs.ToAggregate()) + } + resultObj := &admissionregistrationv1.ValidatingAdmissionPolicyBinding{} + if err := scheme.Convert(internalObj, resultObj, nil); err != nil { + return fmt.Errorf("ValidatingAdmissionPolicyBinding %q: back-conversion error: %w", binding.Name, err) + } + *binding = *resultObj + return nil +} diff --git a/pkg/admission/plugin/policy/manifest/loader/validating_loader_test.go b/pkg/admission/plugin/policy/manifest/loader/validating_loader_test.go new file mode 100644 index 0000000000000..8f09bcdc2fbda --- /dev/null +++ b/pkg/admission/plugin/policy/manifest/loader/validating_loader_test.go @@ -0,0 +1,495 @@ +/* +Copyright 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 loader + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLoadValidatingManifestsFromDirectory(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantPolicies int + wantBindings int + wantErr bool + wantErrContain string + }{ + { + name: "load policy and binding", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: test-policy.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "object.spec.containers.all(c, c.image.startsWith('allowed/'))" +`, + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: test-binding.static.k8s.io +spec: + policyName: test-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantPolicies: 1, + wantBindings: 1, + wantErr: false, + }, + { + name: "binding references non-existent policy", + files: map[string]string{ + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: test-binding.static.k8s.io +spec: + policyName: non-existent-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantErr: true, + wantErrContain: "does not exist in the manifest directory", + }, + { + name: "empty directory", + files: map[string]string{}, + wantPolicies: 0, + wantBindings: 0, + wantErr: false, + }, + { + name: "policy without name", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + wantErrContain: "name", + }, + { + name: "multi-document yaml with policy and binding", + files: map[string]string{ + "multi-doc.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: multidoc-policy.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: multidoc-binding.static.k8s.io +spec: + policyName: multidoc-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantPolicies: 1, + wantBindings: 1, + wantErr: false, + }, + { + name: "policy name missing static suffix", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: no-suffix +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + }, + wantErr: true, + wantErrContain: "must have a name ending with", + }, + { + name: "duplicate policy names", + files: map[string]string{ + "01-policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: dup.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + "02-policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: dup.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + }, + wantErr: true, + wantErrContain: "duplicate", + }, + { + name: "policy with paramKind", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: param-policy.static.k8s.io +spec: + paramKind: + apiVersion: v1 + kind: ConfigMap + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + }, + wantErr: true, + wantErrContain: "spec.paramKind is not supported", + }, + { + name: "binding with paramRef", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: ref-policy.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: ref-binding.static.k8s.io +spec: + policyName: ref-policy.static.k8s.io + paramRef: + name: my-config + namespace: default + validationActions: + - Deny +`, + }, + wantErr: true, + wantErrContain: "paramRef", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create temp directory + dir := t.TempDir() + + // Create files + for name, content := range tt.files { + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatalf("failed to write file %s: %v", name, err) + } + } + + // Load manifests + result, err := LoadValidatingManifestsFromDirectory(dir) + + if tt.wantErr { + if err == nil { + t.Error("expected error but got none") + } else if tt.wantErrContain != "" { + if !strings.Contains(err.Error(), tt.wantErrContain) { + t.Errorf("expected error containing %q, got %q", tt.wantErrContain, err.Error()) + } + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(result.Policies) != tt.wantPolicies { + t.Errorf("expected %d policies, got %d", tt.wantPolicies, len(result.Policies)) + } + + if len(result.Bindings) != tt.wantBindings { + t.Errorf("expected %d bindings, got %d", tt.wantBindings, len(result.Bindings)) + } + }) + } +} + +func TestLoadValidatingManifestsFromDirectory_ListTypes(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantPolicyCount int + wantBindingCount int + }{ + { + name: "ValidatingAdmissionPolicyList", + files: map[string]string{ + "list.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyList +items: +- metadata: + name: list-policy-1.static.k8s.io + spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +- metadata: + name: list-policy-2.static.k8s.io + spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + }, + wantPolicyCount: 2, + }, + { + name: "ValidatingAdmissionPolicyBindingList", + files: map[string]string{ + "list.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyList +items: +- metadata: + name: ref-policy.static.k8s.io + spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBindingList +items: +- metadata: + name: list-binding.static.k8s.io + spec: + policyName: ref-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantPolicyCount: 1, + wantBindingCount: 1, + }, + { + name: "v1.List with policy and binding", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingAdmissionPolicy + metadata: + name: v1list-policy.static.k8s.io + spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingAdmissionPolicyBinding + metadata: + name: v1list-binding.static.k8s.io + spec: + policyName: v1list-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantPolicyCount: 1, + wantBindingCount: 1, + }, + { + name: "v1.List with unsupported type", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingAdmissionPolicy + metadata: + name: valid.static.k8s.io + spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +- apiVersion: v1 + kind: ConfigMap + metadata: + name: not-a-policy +`, + }, + wantErr: true, + errContains: "failed to decode list item", + }, + { + name: "binding policyName missing static suffix", + files: map[string]string{ + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: bad-ref.static.k8s.io +spec: + policyName: some-policy-without-suffix + validationActions: + - Deny +`, + }, + wantErr: true, + errContains: "must end with", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + } + result, err := LoadValidatingManifestsFromDirectory(dir) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error containing %q, got: %v", tt.errContains, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := len(result.Policies); got != tt.wantPolicyCount { + t.Errorf("Policies count = %d, want %d", got, tt.wantPolicyCount) + } + if got := len(result.Bindings); got != tt.wantBindingCount { + t.Errorf("Bindings count = %d, want %d", got, tt.wantBindingCount) + } + }) + } +} diff --git a/pkg/admission/plugin/webhook/manifest/loader/loader.go b/pkg/admission/plugin/webhook/manifest/loader/loader.go new file mode 100644 index 0000000000000..7ca26dd06c24e --- /dev/null +++ b/pkg/admission/plugin/webhook/manifest/loader/loader.go @@ -0,0 +1,166 @@ +/* +Copyright 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 loader provides kube-apiserver-specific webhook manifest loading with +// scheme-based defaulting and validation. The generic load loop lives in the +// staging loader package; this package injects the kube-apiserver scheme, internal +// type conversion, and API validation. +package loader + +import ( + "fmt" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + stagingloader "k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + admissionregistrationinstall "k8s.io/kubernetes/pkg/apis/admissionregistration/install" + "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" +) + +// Re-export staging types so existing callers keep working. +type ValidatingLoadResult = stagingloader.ValidatingLoadResult +type MutatingLoadResult = stagingloader.MutatingLoadResult + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict) +) + +func init() { + admissionregistrationinstall.Install(scheme) + scheme.AddUnversionedTypes(metav1.SchemeGroupVersion, &metav1.List{}, &metav1.Status{}) +} + +// LoadValidatingManifests reads all YAML and JSON files from the specified directory +// and parses them as ValidatingWebhookConfiguration resources. +// Files containing MutatingWebhookConfiguration cause an error. +// Files are processed in alphabetical order for deterministic behavior. +func LoadValidatingManifests(dir string) (*ValidatingLoadResult, error) { + configs, hash, err := stagingloader.LoadManifests(dir, codecs.UniversalDeserializer(), func(obj runtime.Object) ([]*admissionregistrationv1.ValidatingWebhookConfiguration, error) { + return acceptObject(obj, extractValidating, defaultAndValidateVWC) + }) + if err != nil { + return nil, err + } + return &ValidatingLoadResult{Configurations: configs, Hash: hash}, nil +} + +// LoadMutatingManifests reads all YAML and JSON files from the specified directory +// and parses them as MutatingWebhookConfiguration resources. +// Files containing ValidatingWebhookConfiguration cause an error. +// Files are processed in alphabetical order for deterministic behavior. +func LoadMutatingManifests(dir string) (*MutatingLoadResult, error) { + configs, hash, err := stagingloader.LoadManifests(dir, codecs.UniversalDeserializer(), func(obj runtime.Object) ([]*admissionregistrationv1.MutatingWebhookConfiguration, error) { + return acceptObject(obj, extractMutating, defaultAndValidateMWC) + }) + if err != nil { + return nil, err + } + return &MutatingLoadResult{Configurations: configs, Hash: hash}, nil +} + +// acceptObject is a generic helper that handles type dispatch for decoded objects. +// extract attempts to get typed items from a single object or typed List. +// process applies defaulting and API validation to each individual item. +// Returns (items, nil) when the type is recognized, (nil, nil) when not +// recognized, or (nil, err) when recognized but processing fails. +func acceptObject[T runtime.Object]( + obj runtime.Object, + extract func(runtime.Object) ([]T, bool), + process func(T) error, +) ([]T, error) { + items, ok := extract(obj) + if !ok { + return nil, fmt.Errorf("unsupported resource type %T", obj) + } + for _, item := range items { + if err := process(item); err != nil { + return nil, err + } + } + return items, nil +} + +func extractValidating(obj runtime.Object) ([]*admissionregistrationv1.ValidatingWebhookConfiguration, bool) { + if c, ok := obj.(*admissionregistrationv1.ValidatingWebhookConfiguration); ok { + return []*admissionregistrationv1.ValidatingWebhookConfiguration{c}, true + } + if l, ok := obj.(*admissionregistrationv1.ValidatingWebhookConfigurationList); ok { + items := make([]*admissionregistrationv1.ValidatingWebhookConfiguration, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items, true + } + return nil, false +} + +func extractMutating(obj runtime.Object) ([]*admissionregistrationv1.MutatingWebhookConfiguration, bool) { + if c, ok := obj.(*admissionregistrationv1.MutatingWebhookConfiguration); ok { + return []*admissionregistrationv1.MutatingWebhookConfiguration{c}, true + } + if l, ok := obj.(*admissionregistrationv1.MutatingWebhookConfigurationList); ok { + items := make([]*admissionregistrationv1.MutatingWebhookConfiguration, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items, true + } + return nil, false +} + +// defaultAndValidateVWC applies scheme defaults and runs standard API validation +// on a ValidatingWebhookConfiguration. +func defaultAndValidateVWC(config *admissionregistrationv1.ValidatingWebhookConfiguration) error { + scheme.Default(config) + + internalObj := &admissionregistration.ValidatingWebhookConfiguration{} + if err := scheme.Convert(config, internalObj, nil); err != nil { + return fmt.Errorf("ValidatingWebhookConfiguration %q: conversion error: %w", config.Name, err) + } + if errs := validation.ValidateValidatingWebhookConfiguration(internalObj); len(errs) > 0 { + return fmt.Errorf("ValidatingWebhookConfiguration %q: %w", config.Name, errs.ToAggregate()) + } + resultConfig := &admissionregistrationv1.ValidatingWebhookConfiguration{} + if err := scheme.Convert(internalObj, resultConfig, nil); err != nil { + return fmt.Errorf("ValidatingWebhookConfiguration %q: back-conversion error: %w", config.Name, err) + } + *config = *resultConfig + return nil +} + +// defaultAndValidateMWC applies scheme defaults and runs standard API validation +// on a MutatingWebhookConfiguration. +func defaultAndValidateMWC(config *admissionregistrationv1.MutatingWebhookConfiguration) error { + scheme.Default(config) + + internalObj := &admissionregistration.MutatingWebhookConfiguration{} + if err := scheme.Convert(config, internalObj, nil); err != nil { + return fmt.Errorf("MutatingWebhookConfiguration %q: conversion error: %w", config.Name, err) + } + if errs := validation.ValidateMutatingWebhookConfiguration(internalObj); len(errs) > 0 { + return fmt.Errorf("MutatingWebhookConfiguration %q: %w", config.Name, errs.ToAggregate()) + } + resultConfig := &admissionregistrationv1.MutatingWebhookConfiguration{} + if err := scheme.Convert(internalObj, resultConfig, nil); err != nil { + return fmt.Errorf("MutatingWebhookConfiguration %q: back-conversion error: %w", config.Name, err) + } + *config = *resultConfig + return nil +} diff --git a/pkg/admission/plugin/webhook/manifest/loader/loader_test.go b/pkg/admission/plugin/webhook/manifest/loader/loader_test.go new file mode 100644 index 0000000000000..e9394f8213033 --- /dev/null +++ b/pkg/admission/plugin/webhook/manifest/loader/loader_test.go @@ -0,0 +1,919 @@ +/* +Copyright 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 loader + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLoadValidatingManifests(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantConfigCount int + wantWebhookAccessors int + }{ + { + name: "empty directory", + files: map[string]string{}, + wantErr: false, + }, + { + name: "single validating webhook yaml", + files: map[string]string{ + "validating.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test-validating.static.k8s.io +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/validate" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 1, + wantWebhookAccessors: 1, + }, + { + name: "multiple webhooks in one config", + files: map[string]string{ + "multi.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test-multi.static.k8s.io +webhooks: +- name: first.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/first" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +- name: second.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/second" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["UPDATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 1, + wantWebhookAccessors: 2, + }, + { + name: "json format", + files: map[string]string{ + "webhook.json": `{ + "apiVersion": "admissionregistration.k8s.io/v1", + "kind": "ValidatingWebhookConfiguration", + "metadata": {"name": "test-json.static.k8s.io"}, + "webhooks": [{ + "name": "json.webhook.io", + "admissionReviewVersions": ["v1"], + "clientConfig": {"url": "https://example.com/json", "caBundle": "dGVzdA=="}, + "sideEffects": "None", + "rules": [{"apiGroups": [""], "apiVersions": ["v1"], "operations": ["CREATE"], "resources": ["pods"]}] + }] +}`, + }, + wantConfigCount: 1, + wantWebhookAccessors: 1, + }, + { + name: "mixed validating and mutating errors for validating plugin", + files: map[string]string{ + "01-validating.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test-validating.static.k8s.io +webhooks: +- name: validate.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/validate" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + "02-mutating.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: test-mutating.static.k8s.io +webhooks: +- name: mutate.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/mutate" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "unsupported resource type", + }, + { + name: "multi-document yaml", + files: map[string]string{ + "multi-doc.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-first.static.k8s.io +webhooks: +- name: validate1.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/validate1" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-second.static.k8s.io +webhooks: +- name: validate2.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/validate2" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 2, + wantWebhookAccessors: 2, + }, + { + name: "yml extension", + files: map[string]string{ + "webhook.yml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test-yml.static.k8s.io +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 1, + wantWebhookAccessors: 1, + }, + { + name: "skip non-yaml files", + files: map[string]string{ + "readme.txt": "This is not a webhook configuration", + "webhook.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test.static.k8s.io +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 1, + wantWebhookAccessors: 1, + }, + { + name: "invalid yaml", + files: map[string]string{ + "bad.yaml": "this is not valid: yaml: content", + }, + wantErr: true, + errContains: "error loading", + }, + { + name: "unsupported resource type", + files: map[string]string{ + "pod.yaml": ` +apiVersion: v1 +kind: Pod +metadata: + name: test-pod +spec: + containers: + - name: test + image: test +`, + }, + wantErr: true, + errContains: "no kind", + }, + { + name: "missing name", + files: map[string]string{ + "noname.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "name", + }, + { + name: "name missing static suffix", + files: map[string]string{ + "webhook.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: no-suffix +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "must have a name ending with", + }, + { + name: "duplicate names across files", + files: map[string]string{ + "01-first.yaml": createValidatingWebhookYAML("dup.static.k8s.io"), + "02-second.yaml": createValidatingWebhookYAML("dup.static.k8s.io"), + }, + wantErr: true, + errContains: "duplicate", + }, + { + name: "webhook with service reference", + files: map[string]string{ + "webhook.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: svc-webhook.static.k8s.io +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + service: + name: my-webhook + namespace: my-ns + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "clientConfig.service is not supported", + }, + { + name: "webhook with no url", + files: map[string]string{ + "webhook.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: no-url.static.k8s.io +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: {} + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "clientConfig", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("failed to write test file: %v", err) + } + } + + result, err := LoadValidatingManifests(dir) + if tt.wantErr { + if err == nil { + t.Errorf("LoadValidatingManifests() expected error containing %q, got nil", tt.errContains) + return + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("LoadValidatingManifests() error = %v, want error containing %q", err, tt.errContains) + } + return + } + + if err != nil { + t.Errorf("LoadValidatingManifests() unexpected error: %v", err) + return + } + + if got := len(result.Configurations); got != tt.wantConfigCount { + t.Errorf("Configurations count = %d, want %d", got, tt.wantConfigCount) + } + + if got := len(result.GetWebhookAccessors()); got != tt.wantWebhookAccessors { + t.Errorf("WebhookAccessors count = %d, want %d", got, tt.wantWebhookAccessors) + } + }) + } +} + +func TestLoadMutatingManifests(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantConfigCount int + wantWebhookAccessors int + }{ + { + name: "single mutating webhook yaml", + files: map[string]string{ + "mutating.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: test-mutating.static.k8s.io +webhooks: +- name: test.mutate.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/mutate" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 1, + wantWebhookAccessors: 1, + }, + { + name: "missing static suffix", + files: map[string]string{ + "mutating.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: no-suffix +webhooks: +- name: test.mutate.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/mutate" + sideEffects: None +`, + }, + wantErr: true, + errContains: "must have a name ending with", + }, + { + name: "service reference not allowed", + files: map[string]string{ + "mutating.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: svc-ref.static.k8s.io +webhooks: +- name: test.mutate.io + admissionReviewVersions: ["v1"] + clientConfig: + service: + name: my-svc + namespace: default + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "clientConfig.service is not supported", + }, + { + name: "mixed validating and mutating errors for mutating plugin", + files: map[string]string{ + "mixed.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test-validating.static.k8s.io +webhooks: +- name: validate.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + sideEffects: None +`, + }, + wantErr: true, + errContains: "unsupported resource type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("failed to write test file: %v", err) + } + } + + result, err := LoadMutatingManifests(dir) + if tt.wantErr { + if err == nil { + t.Errorf("LoadMutatingManifests() expected error containing %q, got nil", tt.errContains) + return + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("LoadMutatingManifests() error = %v, want error containing %q", err, tt.errContains) + } + return + } + + if err != nil { + t.Errorf("LoadMutatingManifests() unexpected error: %v", err) + return + } + + if got := len(result.Configurations); got != tt.wantConfigCount { + t.Errorf("Configurations count = %d, want %d", got, tt.wantConfigCount) + } + + if got := len(result.GetWebhookAccessors()); got != tt.wantWebhookAccessors { + t.Errorf("WebhookAccessors count = %d, want %d", got, tt.wantWebhookAccessors) + } + }) + } +} + +func TestLoadManifestsFromDirectory_EmptyPath(t *testing.T) { + _, err := LoadValidatingManifests("") + if err == nil { + t.Error("LoadValidatingManifests(\"\") expected error, got nil") + } +} + +func TestLoadManifestsFromDirectory_NonExistentDirectory(t *testing.T) { + _, err := LoadValidatingManifests("/nonexistent/directory/path") + if err == nil { + t.Error("LoadValidatingManifests() expected error for non-existent directory, got nil") + } +} + +func TestLoadManifestsFromDirectory_AlphabeticalOrder(t *testing.T) { + dir := t.TempDir() + + files := []string{"03-third.yaml", "01-first.yaml", "02-second.yaml"} + for i, name := range files { + content := createValidatingWebhookYAML(name[:2] + "-config.static.k8s.io") + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("failed to write test file %d: %v", i, err) + } + } + + result, err := LoadValidatingManifests(dir) + if err != nil { + t.Fatalf("LoadValidatingManifests() unexpected error: %v", err) + } + + if len(result.Configurations) != 3 { + t.Fatalf("expected 3 configurations, got %d", len(result.Configurations)) + } + + // Verify order is deterministic based on configuration names (which match file prefixes) + accessors := result.GetWebhookAccessors() + if len(accessors) != 3 { + t.Fatalf("expected 3 accessors, got %d", len(accessors)) + } + + // The configs should be sorted by name (01-config.static.k8s.io, 02-config.static.k8s.io, 03-config.static.k8s.io) + expected := []string{"01-config.static.k8s.io", "02-config.static.k8s.io", "03-config.static.k8s.io"} + for i, acc := range accessors { + if acc.GetConfigurationName() != expected[i] { + t.Errorf("accessor[%d].GetConfigurationName() = %s, want %s", i, acc.GetConfigurationName(), expected[i]) + } + } +} + +func createValidatingWebhookYAML(name string) string { + return `apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: ` + name + ` +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +` +} + +func TestLoadManifestsFromDirectory_ListTypes(t *testing.T) { + t.Run("validating lists", func(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantConfigCount int + }{ + { + name: "ValidatingWebhookConfigurationList", + files: map[string]string{ + "list.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfigurationList +items: +- metadata: + name: list-item-1.static.k8s.io + webhooks: + - name: w1.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/1" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +- metadata: + name: list-item-2.static.k8s.io + webhooks: + - name: w2.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/2" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 2, + }, + { + name: "v1.List with valid validating webhooks", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: v1list-first.static.k8s.io + webhooks: + - name: first.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/first" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: v1list-second.static.k8s.io + webhooks: + - name: second.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/second" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 2, + }, + { + name: "v1.List with mixed webhook types errors", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: v1list-validating.static.k8s.io + webhooks: + - name: v.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/v" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +- apiVersion: admissionregistration.k8s.io/v1 + kind: MutatingWebhookConfiguration + metadata: + name: v1list-mutating.static.k8s.io + webhooks: + - name: m.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/m" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "unsupported resource type", + }, + { + name: "v1.List with unsupported type", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: valid.static.k8s.io + webhooks: + - name: w.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +- apiVersion: v1 + kind: ConfigMap + metadata: + name: not-a-webhook +`, + }, + wantErr: true, + errContains: "failed to decode list item", + }, + { + name: "ValidatingWebhookConfigurationList with invalid item", + files: map[string]string{ + "list.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfigurationList +items: +- metadata: + name: missing-suffix + webhooks: + - name: w1.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantErr: true, + errContains: "must have a name ending with", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + } + result, err := LoadValidatingManifests(dir) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error containing %q, got: %v", tt.errContains, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := len(result.Configurations); got != tt.wantConfigCount { + t.Errorf("Configurations count = %d, want %d", got, tt.wantConfigCount) + } + }) + } + }) + + t.Run("mutating lists", func(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantErr bool + errContains string + wantConfigCount int + }{ + { + name: "MutatingWebhookConfigurationList", + files: map[string]string{ + "list.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfigurationList +items: +- metadata: + name: mut-list-1.static.k8s.io + webhooks: + - name: m1.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com/1" + caBundle: dGVzdA== + sideEffects: None + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] +`, + }, + wantConfigCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + } + result, err := LoadMutatingManifests(dir) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error containing %q, got: %v", tt.errContains, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := len(result.Configurations); got != tt.wantConfigCount { + t.Errorf("Configurations count = %d, want %d", got, tt.wantConfigCount) + } + }) + } + }) +} diff --git a/pkg/api/persistentvolume/util_test.go b/pkg/api/persistentvolume/util_test.go index c0e410cb0e308..122b7ec3213ff 100644 --- a/pkg/api/persistentvolume/util_test.go +++ b/pkg/api/persistentvolume/util_test.go @@ -24,6 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" api "k8s.io/kubernetes/pkg/apis/core" @@ -87,6 +88,9 @@ func TestDropDisabledFields(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { + if !tc.vacEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.vacEnabled) DropDisabledSpecFields(tc.newSpec, tc.oldSpec) diff --git a/pkg/api/persistentvolumeclaim/util_test.go b/pkg/api/persistentvolumeclaim/util_test.go index f28f63f3f9aa4..0559d84250b34 100644 --- a/pkg/api/persistentvolumeclaim/util_test.go +++ b/pkg/api/persistentvolumeclaim/util_test.go @@ -442,6 +442,9 @@ func TestDropDisabledVolumeAttributesClass(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { + if !test.vacEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, test.vacEnabled) DropDisabledFields(&test.spec, &test.oldSpec) if test.spec.VolumeAttributesClassName != test.wantVAC { diff --git a/pkg/api/pod/testing/make.go b/pkg/api/pod/testing/make.go index e018a0a6371bf..5facd89c4b705 100644 --- a/pkg/api/pod/testing/make.go +++ b/pkg/api/pod/testing/make.go @@ -250,9 +250,9 @@ func SetObjectMeta(objectMeta metav1.ObjectMeta) Tweak { } } -func SetWorkloadRef(workloadRef *api.WorkloadReference) Tweak { +func SetSchedulingGroup(schedulingGroup *api.PodSchedulingGroup) Tweak { return func(pod *api.Pod) { - pod.Spec.WorkloadRef = workloadRef + pod.Spec.SchedulingGroup = schedulingGroup } } @@ -323,6 +323,12 @@ func SetContainerRestartPolicy(policy api.ContainerRestartPolicy) TweakContainer } } +func SetContainerRestartPolicyRules(rules ...api.ContainerRestartRule) TweakContainer { + return func(cnr *api.Container) { + cnr.RestartPolicyRules = rules + } +} + func MakePodStatus(tweaks ...TweakPodStatus) api.PodStatus { ps := api.PodStatus{} diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 1d0ae2209b756..dbda812aad197 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -430,7 +430,9 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po AllowContainerRestartPolicyRules: utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules), AllowUserNamespacesWithVolumeDevices: false, // This also allows restart rules on sidecar containers. - AllowRestartAllContainers: utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits), + AllowRestartAllContainers: utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits), + AllowImageVolumeWithDigest: utilfeature.DefaultFeatureGate.Enabled(features.ImageVolumeWithDigest), + AllowExistingRestartContainerForNonSidecarInitContainer: hasRestartContainerForNonSidecarInitContainer(oldPodSpec), } // If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate, @@ -754,7 +756,7 @@ func dropDisabledFields( dropDisabledDynamicResourceAllocationFields(podSpec, oldPodSpec) dropDisabledClusterTrustBundleProjection(podSpec, oldPodSpec) dropDisabledPodCertificateProjection(podSpec, oldPodSpec) - dropDisabledWorkloadRef(podSpec, oldPodSpec) + dropDisabledSchedulingGroup(podSpec, oldPodSpec) if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) && !inPlacePodVerticalScalingInUse(oldPodSpec) { // Drop ResizePolicy fields. Don't drop updates to Resources field as template.spec.resources @@ -1055,7 +1057,7 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec } } - if !utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatus) { + if !utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatus) && !resourceHealthStatusInUse(oldPodStatus) { setAllocatedResourcesStatusToNil := func(csl []api.ContainerStatus) { for i := range csl { csl[i].AllocatedResourcesStatus = nil @@ -1066,6 +1068,21 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec setAllocatedResourcesStatusToNil(podStatus.EphemeralContainerStatuses) } + if !utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatusMessage) && !resourceHealthStatusMessageInUse(oldPodStatus) { + dropMessageField := func(csl []api.ContainerStatus) { + for i := range csl { + for j := range csl[i].AllocatedResourcesStatus { + for k := range csl[i].AllocatedResourcesStatus[j].Resources { + csl[i].AllocatedResourcesStatus[j].Resources[k].Message = nil + } + } + } + } + dropMessageField(podStatus.ContainerStatuses) + dropMessageField(podStatus.InitContainerStatuses) + dropMessageField(podStatus.EphemeralContainerStatuses) + } + // drop ContainerStatus.User field to empty (disable SupplementalGroupsPolicy) if !utilfeature.DefaultFeatureGate.Enabled(features.SupplementalGroupsPolicy) && !supplementalGroupsPolicyInUse(oldPodSpec) { dropUserField := func(csl []api.ContainerStatus) { @@ -1084,6 +1101,13 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec podStatus.Conditions[i].ObservedGeneration = 0 } } + + if !utilfeature.DefaultFeatureGate.Enabled(features.ImageVolumeWithDigest) && !imageVolumeWithDigestInUse(oldPodStatus) { + dropImageVolumeWithDigest(podStatus) + } + + dropPodNodeAllocatableResourceStatus(podStatus, oldPodStatus) + } // dropDisabledDynamicResourceAllocationFields removes pod claim references from @@ -1105,6 +1129,78 @@ func draExendedResourceInUse(podStatus *api.PodStatus) bool { return false } +func dropPodNodeAllocatableResourceStatus(podStatus, oldPodStatus *api.PodStatus) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources) || draNodeAllocatableResourceStatusInUse(oldPodStatus) { + return + } + podStatus.NodeAllocatableResourceClaimStatuses = nil +} + +func draNodeAllocatableResourceStatusInUse(podStatus *api.PodStatus) bool { + if podStatus == nil { + return false + } + return len(podStatus.NodeAllocatableResourceClaimStatuses) > 0 +} + +func resourceHealthStatusInUse(podStatus *api.PodStatus) bool { + if podStatus == nil { + return false + } + + checkContainerStatuses := func(csl []api.ContainerStatus) bool { + for _, cs := range csl { + if len(cs.AllocatedResourcesStatus) > 0 { + return true + } + } + return false + } + + if checkContainerStatuses(podStatus.ContainerStatuses) { + return true + } + if checkContainerStatuses(podStatus.InitContainerStatuses) { + return true + } + if checkContainerStatuses(podStatus.EphemeralContainerStatuses) { + return true + } + + return false +} + +func resourceHealthStatusMessageInUse(podStatus *api.PodStatus) bool { + if podStatus == nil { + return false + } + + checkContainerStatuses := func(csl []api.ContainerStatus) bool { + for _, cs := range csl { + for _, rs := range cs.AllocatedResourcesStatus { + for _, rh := range rs.Resources { + if rh.Message != nil { + return true + } + } + } + } + return false + } + + if checkContainerStatuses(podStatus.ContainerStatuses) { + return true + } + if checkContainerStatuses(podStatus.InitContainerStatuses) { + return true + } + if checkContainerStatuses(podStatus.EphemeralContainerStatuses) { + return true + } + + return false +} + func dynamicResourceAllocationInUse(podSpec *api.PodSpec) bool { // We only need to check this field because the containers cannot have // resource requirements entries for claims without a corresponding @@ -1831,20 +1927,20 @@ func containerRestartRulesInUse(oldPodSpec *api.PodSpec) bool { return false } -// dropDisabledWorkloadRef removes pod workload reference from its spec +// dropDisabledSchedulingGroup removes pod scheduling group from its spec // unless it is already used by the old pod spec. -func dropDisabledWorkloadRef(podSpec, oldPodSpec *api.PodSpec) { - if !utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) && !workloadRefInUse(oldPodSpec) { - podSpec.WorkloadRef = nil +func dropDisabledSchedulingGroup(podSpec, oldPodSpec *api.PodSpec) { + if !utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) && !schedulingGroupInUse(oldPodSpec) { + podSpec.SchedulingGroup = nil } } -func workloadRefInUse(podSpec *api.PodSpec) bool { +func schedulingGroupInUse(podSpec *api.PodSpec) bool { if podSpec == nil { return false } - return podSpec.WorkloadRef != nil + return podSpec.SchedulingGroup != nil } func restartAllContainersActionInUse(oldPodSpec *api.PodSpec) bool { @@ -1871,3 +1967,77 @@ func restartAllContainersActionInUse(oldPodSpec *api.PodSpec) bool { } return false } + +func imageVolumeWithDigestInUse(oldPodStatus *api.PodStatus) bool { + if oldPodStatus == nil { + return false + } + + for _, containerStatus := range oldPodStatus.ContainerStatuses { + for _, volumeMount := range containerStatus.VolumeMounts { + if volumeMount.VolumeStatus != nil { + return true + } + } + } + + for _, containerStatus := range oldPodStatus.InitContainerStatuses { + for _, volumeMount := range containerStatus.VolumeMounts { + if volumeMount.VolumeStatus != nil { + return true + } + } + } + + for _, containerStatus := range oldPodStatus.EphemeralContainerStatuses { + for _, volumeMount := range containerStatus.VolumeMounts { + if volumeMount.VolumeStatus != nil { + return true + } + } + } + + return false +} + +func dropImageVolumeWithDigest(podStatus *api.PodStatus) { + if podStatus == nil { + return + } + + for i := range podStatus.ContainerStatuses { + for j := range podStatus.ContainerStatuses[i].VolumeMounts { + podStatus.ContainerStatuses[i].VolumeMounts[j].VolumeStatus = nil + } + } + + for i := range podStatus.InitContainerStatuses { + for j := range podStatus.InitContainerStatuses[i].VolumeMounts { + podStatus.InitContainerStatuses[i].VolumeMounts[j].VolumeStatus = nil + } + } + + for i := range podStatus.EphemeralContainerStatuses { + for j := range podStatus.EphemeralContainerStatuses[i].VolumeMounts { + podStatus.EphemeralContainerStatuses[i].VolumeMounts[j].VolumeStatus = nil + } + } +} + +// hasRestartContainerForNonSidecarInitContainer returns true if any non-sidecar init container +// has a RestartContainer resize policy. +func hasRestartContainerForNonSidecarInitContainer(spec *api.PodSpec) bool { + if spec == nil { + return false + } + for _, c := range spec.InitContainers { + if !IsRestartableInitContainer(&c) { + for _, p := range c.ResizePolicy { + if p.RestartPolicy == api.RestartContainer { + return true + } + } + } + } + return false +} diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index 3701535c04952..510b59c3a23d5 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -807,7 +807,10 @@ func TestDropProcMount(t *testing.T) { } t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ProcMountType, enabled) + if !enabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ProcMountType, enabled) + } var oldPodSpec *api.PodSpec if oldPod != nil { @@ -914,16 +917,66 @@ func TestDropDynamicResourceAllocation(t *testing.T) { }, }, } + podWithDRANodeAllocatableResourceStatus := &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Resources: api.ResourceRequirements{ + Claims: []api.ResourceClaim{{Name: "my-claim"}}, + }, + }, + }, + InitContainers: []api.Container{{}}, + EphemeralContainers: []api.EphemeralContainer{{}}, + ResourceClaims: []api.PodResourceClaim{ + { + Name: "my-claim", + ResourceClaimName: &resourceClaimName, + }, + }, + }, + Status: api.PodStatus{ + NodeAllocatableResourceClaimStatuses: []api.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Resources: map[api.ResourceName]resource.Quantity{ + api.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + } + + podWithoutDRANodeAllocatableResourceStatus := &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Resources: api.ResourceRequirements{ + Claims: []api.ResourceClaim{{Name: "my-claim"}}, + }, + }, + }, + InitContainers: []api.Container{{}}, + EphemeralContainers: []api.EphemeralContainer{{}}, + ResourceClaims: []api.PodResourceClaim{ + { + Name: "my-claim", + ResourceClaimName: &resourceClaimName, + }, + }, + }, + } var noPod *api.Pod testcases := []struct { - description string - enabled bool - extendedEnabled bool - oldPod *api.Pod - newPod *api.Pod - wantPod *api.Pod + description string + enabled bool + extendedEnabled bool + enableDRANodeAllocatableResouces bool + oldPod *api.Pod + newPod *api.Pod + wantPod *api.Pod }{ { description: "old with claims / new with claims / disabled", @@ -1054,6 +1107,38 @@ func TestDropDynamicResourceAllocation(t *testing.T) { newPod: podWithExtendedResource, wantPod: podWithExtendedResource, }, + { + description: "DRA node allocatable resources / no old pod / new with DRA node allocatable resource / disabled", + enabled: true, + enableDRANodeAllocatableResouces: false, + oldPod: noPod, + newPod: podWithDRANodeAllocatableResourceStatus, + wantPod: podWithoutDRANodeAllocatableResourceStatus, + }, + { + description: "DRA node allocatable resources / no old pod / new with DRA node allocatable resource / enabled", + enabled: true, + enableDRANodeAllocatableResouces: true, + oldPod: noPod, + newPod: podWithDRANodeAllocatableResourceStatus, + wantPod: podWithDRANodeAllocatableResourceStatus, + }, + { + description: "DRA node allocatable resources / old without node allocatable resource status / new with node allocatable resource status / disabled", + enabled: true, + enableDRANodeAllocatableResouces: false, + oldPod: podWithoutDRANodeAllocatableResourceStatus, + newPod: podWithDRANodeAllocatableResourceStatus, + wantPod: podWithoutDRANodeAllocatableResourceStatus, + }, + { + description: "DRA node allocatable resources / old without node allocatable resource status / new with node allocatable resource status / enabled", + enabled: true, + enableDRANodeAllocatableResouces: true, + oldPod: podWithoutDRANodeAllocatableResourceStatus, + newPod: podWithDRANodeAllocatableResourceStatus, + wantPod: podWithDRANodeAllocatableResourceStatus, + }, } for _, tc := range testcases { @@ -1061,10 +1146,14 @@ func TestDropDynamicResourceAllocation(t *testing.T) { if !tc.enabled { featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.34")) } - featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + overrides := featuregatetesting.FeatureOverrides{ features.DynamicResourceAllocation: tc.enabled, features.DRAExtendedResource: tc.extendedEnabled, - }) + } + if tc.enableDRANodeAllocatableResouces { + overrides[features.DRANodeAllocatableResources] = true + } + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, overrides) oldPod := tc.oldPod.DeepCopy() newPod := tc.newPod.DeepCopy() @@ -2584,7 +2673,11 @@ func TestDropHostUsers(t *testing.T) { } t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.UserNamespacesSupport, enabled) + // Set emulation version so that the feature gate can be disabled in the test + if !enabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.UserNamespacesSupport, enabled) + } DropDisabledPodFields(newPod, oldPod) @@ -4243,6 +4336,10 @@ func TestDropImageVolumes(t *testing.T) { for _, tc := range testcases { t.Run(tc.description, func(t *testing.T) { + if !tc.enabled { + // Set emulation version to v1.35 (last version before GA) to allow disabling the feature gate + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, tc.enabled) oldPod := tc.oldPod.DeepCopy() @@ -4380,7 +4477,10 @@ func TestDropSELinuxChangePolicy(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - + // un-lock locked feature gates, if necessary + if !sets.New(tc.gates...).Has(features.SELinuxChangePolicy) { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } // Set feature gates for the test. *Disable* those that are not in tc.gates. allGates := []featuregate.Feature{features.SELinuxChangePolicy, features.SELinuxMount} enabledGates := sets.New(tc.gates...) @@ -6417,16 +6517,15 @@ func TestAllowTaintTolerationComparisonOperators(t *testing.T) { } } -func TestDisabledWorkload(t *testing.T) { - podWithWorkload := &api.Pod{ +func TestDisabledSchedulingGroup(t *testing.T) { + podWithSchedulingGroup := &api.Pod{ Spec: api.PodSpec{ - WorkloadRef: &api.WorkloadReference{ - Name: "w", - PodGroup: "pg", + SchedulingGroup: &api.PodSchedulingGroup{ + PodGroupName: new("pg"), }, }, } - podWithoutWorkload := &api.Pod{ + podWithoutSchedulingGroup := &api.Pod{ Spec: api.PodSpec{}, } @@ -6438,56 +6537,56 @@ func TestDisabledWorkload(t *testing.T) { wantPod *api.Pod }{ { - name: "old with workload / new with workload / disabled", - oldPod: podWithWorkload, - newPod: podWithWorkload, - wantPod: podWithWorkload, + name: "old with scheduling group / new with scheduling group / disabled", + oldPod: podWithSchedulingGroup, + newPod: podWithSchedulingGroup, + wantPod: podWithSchedulingGroup, }, { - name: "old without workload / new with workload / disabled", - oldPod: podWithoutWorkload, - newPod: podWithWorkload, - wantPod: podWithoutWorkload, + name: "old without scheduling group / new with scheduling group / disabled", + oldPod: podWithoutSchedulingGroup, + newPod: podWithSchedulingGroup, + wantPod: podWithoutSchedulingGroup, }, { - name: "old with workload / new without workload / disabled", - oldPod: podWithWorkload, - newPod: podWithoutWorkload, - wantPod: podWithoutWorkload, + name: "old with scheduling group / new without scheduling group / disabled", + oldPod: podWithSchedulingGroup, + newPod: podWithoutSchedulingGroup, + wantPod: podWithoutSchedulingGroup, }, { - name: "old without workload / new without workload / disabled", - oldPod: podWithoutWorkload, - newPod: podWithoutWorkload, - wantPod: podWithoutWorkload, + name: "old without scheduling group / new without scheduling group / disabled", + oldPod: podWithoutSchedulingGroup, + newPod: podWithoutSchedulingGroup, + wantPod: podWithoutSchedulingGroup, }, { - name: "old with workload / new with workload / enabled", + name: "old with scheduling group / new with scheduling group / enabled", enabled: true, - oldPod: podWithWorkload, - newPod: podWithWorkload, - wantPod: podWithWorkload, + oldPod: podWithSchedulingGroup, + newPod: podWithSchedulingGroup, + wantPod: podWithSchedulingGroup, }, { - name: "old without workload / new with workload / enabled", + name: "old without scheduling group / new with scheduling group / enabled", enabled: true, - oldPod: podWithoutWorkload, - newPod: podWithWorkload, - wantPod: podWithWorkload, + oldPod: podWithoutSchedulingGroup, + newPod: podWithSchedulingGroup, + wantPod: podWithSchedulingGroup, }, { - name: "old with workload / new without workload / enabled", + name: "old with scheduling group / new without scheduling group / enabled", enabled: true, - oldPod: podWithWorkload, - newPod: podWithoutWorkload, - wantPod: podWithoutWorkload, + oldPod: podWithSchedulingGroup, + newPod: podWithoutSchedulingGroup, + wantPod: podWithoutSchedulingGroup, }, { - name: "old without workload / new without workload / enabled", + name: "old without scheduling group / new without scheduling group / enabled", enabled: true, - oldPod: podWithoutWorkload, - newPod: podWithoutWorkload, - wantPod: podWithoutWorkload, + oldPod: podWithoutSchedulingGroup, + newPod: podWithoutSchedulingGroup, + wantPod: podWithoutSchedulingGroup, }, } @@ -6734,3 +6833,734 @@ func TestDropDisabledPodStatusFields_InPlacePodLevelResourcesVerticalScaling(t * }) } } + +func TestResourceHealthStatusInUse(t *testing.T) { + testCases := []struct { + name string + podStatus *api.PodStatus + expected bool + }{ + { + name: "nil pod status", + podStatus: nil, + expected: false, + }, + { + name: "empty pod status", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{}, + }, + expected: false, + }, + { + name: "pod status with AllocatedResourcesStatus in container", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "test-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "pod status with AllocatedResourcesStatus in init container", + podStatus: &api.PodStatus{ + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "pod status with AllocatedResourcesStatus in ephemeral container", + podStatus: &api.PodStatus{ + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "pod status without AllocatedResourcesStatus", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "test-container", + }, + }, + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + }, + }, + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + }, + }, + }, + expected: false, + }, + { + name: "pod status with empty AllocatedResourcesStatus array", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "test-container", + AllocatedResourcesStatus: []api.ResourceStatus{}, + }, + }, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := resourceHealthStatusInUse(tc.podStatus) + if result != tc.expected { + t.Errorf("resourceHealthStatusInUse() = %v, want %v", result, tc.expected) + } + }) + } +} + +func TestDropDisabledPodStatusFields_ResourceHealthStatus(t *testing.T) { + podStatusWithResourceHealth := func() *api.PodStatus { + return &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container1", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + }, + }, + }, + }, + }, + }, + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/gpu", + Resources: []api.ResourceHealth{ + { + ResourceID: "gpu-1", + Health: api.ResourceHealthStatusUnhealthy, + }, + }, + }, + }, + }, + }, + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/nic", + Resources: []api.ResourceHealth{ + { + ResourceID: "nic-1", + Health: api.ResourceHealthStatusHealthy, + }, + }, + }, + }, + }, + }, + } + } + + podStatusWithoutResourceHealth := func() *api.PodStatus { + return &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container1", + }, + }, + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + }, + }, + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + }, + }, + } + } + + podStatusNilResourceHealth := func() *api.PodStatus { + return &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container1", + AllocatedResourcesStatus: nil, + }, + }, + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + AllocatedResourcesStatus: nil, + }, + }, + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + AllocatedResourcesStatus: nil, + }, + }, + } + } + + tests := []struct { + name string + enabled bool + podStatus *api.PodStatus + oldPodStatus *api.PodStatus + wantPodStatus *api.PodStatus + }{ + { + name: "feature enabled, old=without, new=without", + enabled: true, + oldPodStatus: podStatusWithoutResourceHealth(), + podStatus: podStatusWithoutResourceHealth(), + wantPodStatus: podStatusWithoutResourceHealth(), + }, + { + name: "feature enabled, old=with, new=with", + enabled: true, + oldPodStatus: podStatusWithResourceHealth(), + podStatus: podStatusWithResourceHealth(), + wantPodStatus: podStatusWithResourceHealth(), + }, + { + name: "feature enabled, old=without, new=with", + enabled: true, + oldPodStatus: podStatusWithoutResourceHealth(), + podStatus: podStatusWithResourceHealth(), + wantPodStatus: podStatusWithResourceHealth(), + }, + { + name: "feature disabled, old=without, new=without", + enabled: false, + oldPodStatus: podStatusWithoutResourceHealth(), + podStatus: podStatusWithoutResourceHealth(), + wantPodStatus: podStatusNilResourceHealth(), + }, + { + name: "feature disabled, old=without, new=with (should drop)", + enabled: false, + oldPodStatus: podStatusWithoutResourceHealth(), + podStatus: podStatusWithResourceHealth(), + wantPodStatus: podStatusNilResourceHealth(), + }, + { + name: "feature disabled, old=with, new=with (should preserve - bug fix)", + enabled: false, + oldPodStatus: podStatusWithResourceHealth(), + podStatus: podStatusWithResourceHealth(), + wantPodStatus: podStatusWithResourceHealth(), + }, + { + name: "feature disabled, old=with, new=without (should preserve nil)", + enabled: false, + oldPodStatus: podStatusWithResourceHealth(), + podStatus: podStatusWithoutResourceHealth(), + wantPodStatus: podStatusWithoutResourceHealth(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ResourceHealthStatus, tt.enabled) + dropDisabledPodStatusFields(tt.podStatus, tt.oldPodStatus, &api.PodSpec{}, &api.PodSpec{}) + if !reflect.DeepEqual(tt.podStatus, tt.wantPodStatus) { + t.Errorf("dropDisabledPodStatusFields() = %v, want %v\ndiff: %v", + tt.podStatus, tt.wantPodStatus, cmp.Diff(tt.wantPodStatus, tt.podStatus)) + } + }) + } +} + +func TestResourceHealthStatusMessageInUse(t *testing.T) { + message := "test message" + testCases := []struct { + name string + podStatus *api.PodStatus + expected bool + }{ + { + name: "nil pod status", + podStatus: nil, + expected: false, + }, + { + name: "empty pod status", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{}, + }, + expected: false, + }, + { + name: "pod status with message in container", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "test-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + Message: &message, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "pod status with message in init container", + podStatus: &api.PodStatus{ + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + Message: &message, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "pod status with message in ephemeral container", + podStatus: &api.PodStatus{ + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + Message: &message, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "pod status without message (nil pointer)", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "test-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + Message: nil, + }, + }, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "pod status with AllocatedResourcesStatus but no message", + podStatus: &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "test-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + }, + }, + }, + }, + }, + }, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := resourceHealthStatusMessageInUse(tc.podStatus) + if result != tc.expected { + t.Errorf("resourceHealthStatusMessageInUse() = %v, want %v", result, tc.expected) + } + }) + } +} + +func TestDropDisabledPodStatusFields_ResourceHealthStatusMessage(t *testing.T) { + message1 := "ECC error detected" + message2 := "GPU temperature high" + message3 := "NIC link down" + + podStatusWithMessage := func() *api.PodStatus { + return &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container1", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + Message: &message1, + }, + }, + }, + }, + }, + }, + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/gpu", + Resources: []api.ResourceHealth{ + { + ResourceID: "gpu-1", + Health: api.ResourceHealthStatusUnhealthy, + Message: &message2, + }, + }, + }, + }, + }, + }, + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/nic", + Resources: []api.ResourceHealth{ + { + ResourceID: "nic-1", + Health: api.ResourceHealthStatusHealthy, + Message: &message3, + }, + }, + }, + }, + }, + }, + } + } + + podStatusWithoutMessage := func() *api.PodStatus { + return &api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container1", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/device", + Resources: []api.ResourceHealth{ + { + ResourceID: "device-1", + Health: api.ResourceHealthStatusHealthy, + Message: nil, + }, + }, + }, + }, + }, + }, + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "init-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/gpu", + Resources: []api.ResourceHealth{ + { + ResourceID: "gpu-1", + Health: api.ResourceHealthStatusUnhealthy, + Message: nil, + }, + }, + }, + }, + }, + }, + EphemeralContainerStatuses: []api.ContainerStatus{ + { + Name: "ephemeral-container", + AllocatedResourcesStatus: []api.ResourceStatus{ + { + Name: "example.com/nic", + Resources: []api.ResourceHealth{ + { + ResourceID: "nic-1", + Health: api.ResourceHealthStatusHealthy, + Message: nil, + }, + }, + }, + }, + }, + }, + } + } + + tests := []struct { + name string + enabled bool + podStatus *api.PodStatus + oldPodStatus *api.PodStatus + wantPodStatus *api.PodStatus + }{ + { + name: "feature enabled, old=without, new=without", + enabled: true, + oldPodStatus: podStatusWithoutMessage(), + podStatus: podStatusWithoutMessage(), + wantPodStatus: podStatusWithoutMessage(), + }, + { + name: "feature enabled, old=with, new=with", + enabled: true, + oldPodStatus: podStatusWithMessage(), + podStatus: podStatusWithMessage(), + wantPodStatus: podStatusWithMessage(), + }, + { + name: "feature enabled, old=without, new=with", + enabled: true, + oldPodStatus: podStatusWithoutMessage(), + podStatus: podStatusWithMessage(), + wantPodStatus: podStatusWithMessage(), + }, + { + name: "feature disabled, old=without, new=without", + enabled: false, + oldPodStatus: podStatusWithoutMessage(), + podStatus: podStatusWithoutMessage(), + wantPodStatus: podStatusWithoutMessage(), + }, + { + name: "feature disabled, old=without, new=with (should drop)", + enabled: false, + oldPodStatus: podStatusWithoutMessage(), + podStatus: podStatusWithMessage(), + wantPodStatus: podStatusWithoutMessage(), + }, + { + name: "feature disabled, old=with, new=with (should preserve - bug fix)", + enabled: false, + oldPodStatus: podStatusWithMessage(), + podStatus: podStatusWithMessage(), + wantPodStatus: podStatusWithMessage(), + }, + { + name: "feature disabled, old=with, new=without (should preserve nil)", + enabled: false, + oldPodStatus: podStatusWithMessage(), + podStatus: podStatusWithoutMessage(), + wantPodStatus: podStatusWithoutMessage(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Enable ResourceHealthStatus as well since ResourceHealthStatusMessage depends on it + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ResourceHealthStatus, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ResourceHealthStatusMessage, tt.enabled) + dropDisabledPodStatusFields(tt.podStatus, tt.oldPodStatus, &api.PodSpec{}, &api.PodSpec{}) + if !reflect.DeepEqual(tt.podStatus, tt.wantPodStatus) { + t.Errorf("dropDisabledPodStatusFields() = %v, want %v\ndiff: %v", + tt.podStatus, tt.wantPodStatus, cmp.Diff(tt.wantPodStatus, tt.podStatus)) + } + }) + } +} + +func TestHasRestartContainerForNonSidecarInitContainer(t *testing.T) { + tests := []struct { + name string + podSpec *api.PodSpec + expected bool + }{ + { + name: "nil pod spec", + podSpec: nil, + expected: false, + }, + { + name: "no init containers", + podSpec: &api.PodSpec{InitContainers: []api.Container{}}, + expected: false, + }, + { + name: "regular init container without resize policy", + podSpec: &api.PodSpec{ + InitContainers: []api.Container{ + {Name: "init-1"}, + }, + }, + expected: false, + }, + { + name: "sidecar (restartable) init container with RestartContainer policy", + podSpec: &api.PodSpec{ + InitContainers: []api.Container{ + { + Name: "sidecar", + RestartPolicy: ptr.To(api.ContainerRestartPolicyAlways), + ResizePolicy: []api.ContainerResizePolicy{ + {ResourceName: api.ResourceCPU, RestartPolicy: api.RestartContainer}, + }, + }, + }, + }, + expected: false, // Should be false because it's a sidecar + }, + { + name: "non-sidecar init container with NotRequired policy", + podSpec: &api.PodSpec{ + InitContainers: []api.Container{ + { + Name: "init-1", + ResizePolicy: []api.ContainerResizePolicy{ + {ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired}, + }, + }, + }, + }, + expected: false, + }, + { + name: "non-sidecar init container with RestartContainer policy", + podSpec: &api.PodSpec{ + InitContainers: []api.Container{ + { + Name: "init-1", + ResizePolicy: []api.ContainerResizePolicy{ + {ResourceName: api.ResourceMemory, RestartPolicy: api.RestartContainer}, + }, + }, + }, + }, + expected: true, + }, + { + name: "mix of sidecar and non-sidecar with RestartContainer policy", + podSpec: &api.PodSpec{ + InitContainers: []api.Container{ + { + Name: "sidecar", + RestartPolicy: ptr.To(api.ContainerRestartPolicyAlways), + ResizePolicy: []api.ContainerResizePolicy{ + {ResourceName: api.ResourceCPU, RestartPolicy: api.RestartContainer}, + }, + }, + { + Name: "init-2", + ResizePolicy: []api.ContainerResizePolicy{ + {ResourceName: api.ResourceCPU, RestartPolicy: api.RestartContainer}, + }, + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := hasRestartContainerForNonSidecarInitContainer(tt.podSpec) + if result != tt.expected { + t.Errorf("expected %v, got %v", tt.expected, result) + } + }) + } +} diff --git a/pkg/api/pod/warnings_test.go b/pkg/api/pod/warnings_test.go index 20e0e41346bac..a1ef7ddd119f3 100644 --- a/pkg/api/pod/warnings_test.go +++ b/pkg/api/pod/warnings_test.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" api "k8s.io/kubernetes/pkg/apis/core" @@ -1827,13 +1828,18 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - `spec.dnsConfig.nameservers[1]: non-standard IP address "05.06.07.08" will be considered invalid in a future Kubernetes release: use "5.6.7.8"`, - `spec.hostAliases[0].ip: non-standard IP address "::ffff:1.2.3.4" will be considered invalid in a future Kubernetes release: use "1.2.3.4"`, + `spec.dnsConfig.nameservers[1]: non-standard IP address "05.06.07.08" is invalid: use "5.6.7.8"`, + `spec.hostAliases[0].ip: non-standard IP address "::ffff:1.2.3.4" is invalid: use "1.2.3.4"`, }, }, } for _, tc := range testcases { + + if !tc.gitRepoPluginDisabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.33")) + } + t.Run("podspec_"+tc.name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GitRepoVolumeDriver, !tc.gitRepoPluginDisabled) var oldTemplate *api.PodTemplateSpec diff --git a/pkg/api/service/warnings.go b/pkg/api/service/warnings.go index a9f0cdad25f0b..58903e2dba3eb 100644 --- a/pkg/api/service/warnings.go +++ b/pkg/api/service/warnings.go @@ -57,6 +57,11 @@ func GetWarningsForService(service, oldService *api.Service) []string { warnings = append(warnings, utilvalidation.GetWarningsForIP(field.NewPath("spec").Child("externalIPs").Index(i), externalIP)...) } + // Warn about using externalIPs + if len(service.Spec.ExternalIPs) > 0 && !isHeadlessService(service) && service.Spec.Type != api.ServiceTypeExternalName { //nolint:staticcheck // SA1019 testing deprecated field + warnings = append(warnings, "spec.externalIPs is deprecated and may no longer be implemented in some clusters") + } + if len(service.Spec.LoadBalancerIP) > 0 { warnings = append(warnings, utilvalidation.GetWarningsForIP(field.NewPath("spec").Child("loadBalancerIP"), service.Spec.LoadBalancerIP)...) } diff --git a/pkg/api/service/warnings_test.go b/pkg/api/service/warnings_test.go index 3fe97f7d7cb1b..424a47b390b9e 100644 --- a/pkg/api/service/warnings_test.go +++ b/pkg/api/service/warnings_test.go @@ -47,7 +47,7 @@ func TestGetWarningsForService(t *testing.T) { name: "externalIPs set when type is ExternalName", tweakSvc: func(s *api.Service) { s.Spec.Type = api.ServiceTypeExternalName - s.Spec.ExternalIPs = []string{"1.2.3.4"} + s.Spec.ExternalIPs = []string{"1.2.3.4"} //nolint:staticcheck // SA1019 testing deprecated field }, numWarnings: 1, }, { @@ -115,6 +115,12 @@ func TestGetWarningsForService(t *testing.T) { s.Spec.TrafficDistribution = ptr.To(api.ServiceTrafficDistributionPreferClose) }, numWarnings: 1, + }, { + name: "ExternalIPs set on ClusterIP service", + tweakSvc: func(s *api.Service) { + s.Spec.ExternalIPs = []string{"1.2.3.4"} //nolint:staticcheck // SA1019 testing deprecated field + }, + numWarnings: 1, }} for _, tc := range testCases { @@ -166,21 +172,21 @@ func TestGetWarningsForServiceClusterIPs(t *testing.T) { name: "IPv4 with leading zeros", service: service([]string{"192.012.2.2"}), want: []string{ - `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, }, }, { name: "Dual Stack IPv4-IPv6 and IPv4 with leading zeros", service: service([]string{"192.012.2.2", "2001:db8::2"}), want: []string{ - `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, }, }, { name: "Dual Stack IPv6-IPv4 and IPv4 with leading zeros", service: service([]string{"2001:db8::2", "192.012.2.2"}), want: []string{ - `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, }, }, { @@ -208,7 +214,7 @@ func TestGetWarningsForServiceClusterIPs(t *testing.T) { name: "Dual Stack IPv4-IPv6 and IPv4 with leading zeros and IPv6 non-canonical format", service: service([]string{"192.012.2.2", "2001:db8:0:0::2"}), want: []string{ - `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, `spec.clusterIPs[1]: IPv6 address "2001:db8:0:0::2" should be in RFC 5952 canonical format ("2001:db8::2")`, }, }, @@ -217,7 +223,7 @@ func TestGetWarningsForServiceClusterIPs(t *testing.T) { service: service([]string{"2001:db8:0:0::2", "192.012.2.2"}), want: []string{ `spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" should be in RFC 5952 canonical format ("2001:db8::2")`, - `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, }, }, { @@ -238,12 +244,13 @@ func TestGetWarningsForServiceClusterIPs(t *testing.T) { }, want: []string{ `spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" should be in RFC 5952 canonical format ("2001:db8::2")`, - `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, `spec.externalIPs[0]: IPv6 address "2001:db8:1:0::2" should be in RFC 5952 canonical format ("2001:db8:1::2")`, - `spec.externalIPs[1]: non-standard IP address "10.012.2.2" will be considered invalid in a future Kubernetes release: use "10.12.2.2"`, - `spec.loadBalancerIP: non-standard IP address "10.001.1.1" will be considered invalid in a future Kubernetes release: use "10.1.1.1"`, + `spec.externalIPs[1]: non-standard IP address "10.012.2.2" is invalid: use "10.12.2.2"`, + `spec.externalIPs is deprecated and may no longer be implemented in some clusters`, + `spec.loadBalancerIP: non-standard IP address "10.001.1.1" is invalid: use "10.1.1.1"`, `spec.loadBalancerSourceRanges[0]: IPv6 CIDR value "2001:db8:1:0::/64" should be in RFC 5952 canonical format ("2001:db8:1::/64")`, - `spec.loadBalancerSourceRanges[1]: non-standard CIDR value "10.012.2.0/24" will be considered invalid in a future Kubernetes release: use "10.12.2.0/24"`, + `spec.loadBalancerSourceRanges[1]: non-standard CIDR value "10.012.2.0/24" is invalid: use "10.12.2.0/24"`, }, }, } diff --git a/pkg/api/testing/defaulting_test.go b/pkg/api/testing/defaulting_test.go index 6805a5c76b139..f3f7cdce7eedf 100644 --- a/pkg/api/testing/defaulting_test.go +++ b/pkg/api/testing/defaulting_test.go @@ -25,6 +25,7 @@ import ( "github.com/google/go-cmp/cmp" "sigs.k8s.io/randfill" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" apiv1 "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" @@ -151,6 +152,8 @@ func TestDefaulting(t *testing.T) { {Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceClaimTemplateList"}: {}, {Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceSlice"}: {}, {Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceSliceList"}: {}, + {Group: "resource.k8s.io", Version: "v1beta2", Kind: "DeviceTaintRule"}: {}, + {Group: "resource.k8s.io", Version: "v1beta2", Kind: "DeviceTaintRuleList"}: {}, {Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaim"}: {}, {Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaimList"}: {}, {Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaimTemplate"}: {}, @@ -163,6 +166,8 @@ func TestDefaulting(t *testing.T) { {Group: "resource.k8s.io", Version: "v1", Kind: "ResourceClaimTemplateList"}: {}, {Group: "resource.k8s.io", Version: "v1", Kind: "ResourceSlice"}: {}, {Group: "resource.k8s.io", Version: "v1", Kind: "ResourceSliceList"}: {}, + {Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourcePoolStatusRequest"}: {}, + {Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourcePoolStatusRequestList"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicy"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyList"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyBinding"}: {}, @@ -191,6 +196,10 @@ func TestDefaulting(t *testing.T) { {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "ValidatingWebhookConfigurationList"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingWebhookConfiguration"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingWebhookConfigurationList"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicy"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicyBinding"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicyBindingList"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicyList"}: {}, {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {}, {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {}, {Group: "networking.k8s.io", Version: "v1beta1", Kind: "Ingress"}: {}, @@ -210,10 +219,10 @@ func TestDefaulting(t *testing.T) { {Group: "storage.k8s.io", Version: "v1beta1", Kind: "VolumeAttachment"}: {}, {Group: "storage.k8s.io", Version: "v1beta1", Kind: "VolumeAttachmentList"}: {}, {Group: "authentication.k8s.io", Version: "v1", Kind: "TokenRequest"}: {}, - {Group: "scheduling.k8s.io", Version: "v1alpha1", Kind: "PriorityClass"}: {}, + {Group: "scheduling.k8s.io", Version: "v1alpha2", Kind: "PodGroup"}: {}, + {Group: "scheduling.k8s.io", Version: "v1alpha2", Kind: "PodGroupList"}: {}, {Group: "scheduling.k8s.io", Version: "v1beta1", Kind: "PriorityClass"}: {}, {Group: "scheduling.k8s.io", Version: "v1", Kind: "PriorityClass"}: {}, - {Group: "scheduling.k8s.io", Version: "v1alpha1", Kind: "PriorityClassList"}: {}, {Group: "scheduling.k8s.io", Version: "v1beta1", Kind: "PriorityClassList"}: {}, {Group: "scheduling.k8s.io", Version: "v1", Kind: "PriorityClassList"}: {}, {Group: "flowcontrol.apiserver.k8s.io", Version: "v1alpha1", Kind: "PriorityLevelConfiguration"}: {}, @@ -239,7 +248,6 @@ func TestDefaulting(t *testing.T) { sort.Sort(testTypes) for _, gvk := range testTypes { - gvk := gvk t.Run(gvk.String(), func(t *testing.T) { // Each sub-tests gets its own fuzzer instance to make running it independent // from what other tests ran before. @@ -259,6 +267,26 @@ func TestDefaulting(t *testing.T) { c.FillNoCustom(s) s.TargetSelector = "" // need to fuzz requirement strings specially }, + // Custom fuzzer functions for admissionregistration.k8s.io/v1 types + func(s *admissionregistrationv1.MutatingAdmissionPolicySpec, c randfill.Continue) { + c.FillNoCustom(s) + s.FailurePolicy = nil // Ensure FailurePolicy is nil to trigger defaulting + s.ReinvocationPolicy = "" // Ensure ReinvocationPolicy is empty to trigger defaulting + }, + func(s *admissionregistrationv1.ValidatingAdmissionPolicySpec, c randfill.Continue) { + c.FillNoCustom(s) + s.FailurePolicy = nil // Ensure FailurePolicy is nil to trigger defaulting + }, + func(s *admissionregistrationv1.MatchResources, c randfill.Continue) { + c.FillNoCustom(s) + s.MatchPolicy = nil // Ensure MatchPolicy is nil to trigger defaulting + s.NamespaceSelector = nil // Ensure NamespaceSelector is nil to trigger defaulting + s.ObjectSelector = nil // Ensure ObjectSelector is nil to trigger defaulting + }, + func(s *admissionregistrationv1.Rule, c randfill.Continue) { + c.FillNoCustom(s) + s.Scope = nil // Ensure Scope is nil to trigger defaulting + }, ) _, expectedChanged := typesWithDefaulting[gvk] diff --git a/pkg/api/testing/validation.go b/pkg/api/testing/validation.go index e11ca144808c6..69ad77caec4ee 100644 --- a/pkg/api/testing/validation.go +++ b/pkg/api/testing/validation.go @@ -27,10 +27,16 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" runtimetest "k8s.io/apimachinery/pkg/runtime/testing" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/registry/rest" utilfeature "k8s.io/apiserver/pkg/util/feature" + validationmetrics "k8s.io/apiserver/pkg/validation" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" "k8s.io/kubernetes/pkg/api/legacyscheme" + "sigs.k8s.io/randfill" ) // ValidateFunc is a function that runs validation. @@ -88,8 +94,15 @@ func VerifyVersionedValidationEquivalence(t *testing.T, obj, old runtime.Object, if internalObj == nil { return } + // We do fuzzing on the internal version of the object. + // This is because custom fuzzing function are only + // supported for internal objects. + // Fuzz the internal object if a fuzzer is provided. + if opts.Fuzzer != nil { + opts.Fuzzer.Fill(internalObj) + } if old == nil { - runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, accumulate, opts.SubResources...) + runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, accumulate, opts.IgnoreObjectConversionErrors, opts.SubResources...) } else { // Convert old versioned object to internal format before validation. // runtimetest.RunUpdateValidationForEachVersion requires unversioned (internal) objects as input. @@ -100,7 +113,11 @@ func VerifyVersionedValidationEquivalence(t *testing.T, obj, old runtime.Object, if internalOld == nil { return } - runtimetest.RunUpdateValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, internalOld, accumulate, opts.SubResources...) + // Fuzz the internal old object if a fuzzer is provided. + if opts.Fuzzer != nil { + opts.Fuzzer.Fill(internalOld) + } + runtimetest.RunUpdateValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, internalOld, accumulate, opts.IgnoreObjectConversionErrors, opts.SubResources...) } // Make a copy so we can modify it. @@ -201,6 +218,20 @@ type validationOption struct { SubResources []string // NormalizationRules are the rules to apply to field paths before comparison. NormalizationRules []field.NormalizationRule + + // IgnoreObjectConversions skips the tests if the conversion from the internal object + // to the versioned object fails. + IgnoreObjectConversionErrors bool + + // MinEmulationVersion is the minimum emulation version that can be used in the + // "hand written validation" sub-test. When the test has already enabled a feature + // gate introduced after 1.35 (e.g. in 1.36+), setting the emulation version to + // 1.35 would fail because the feature gate did not exist at that version. + // If set to a version greater than 1.35, the sub-test is skipped. + MinEmulationVersion *version.Version + + // Fuzzer is the fuzzer to use for generating test objects. + Fuzzer *randfill.Filler } func WithSubResources(subResources ...string) ValidationTestConfig { @@ -215,16 +246,43 @@ func WithNormalizationRules(rules ...field.NormalizationRule) ValidationTestConf } } +func WithIgnoreObjectConversionErrors() ValidationTestConfig { + return func(o *validationOption) { + o.IgnoreObjectConversionErrors = true + } +} + +func WithFuzzer(fuzzer *randfill.Filler) ValidationTestConfig { + return func(o *validationOption) { + o.Fuzzer = fuzzer + } +} + +// WithMinEmulationVersion sets the minimum emulation version that can be used in the +// "hand written validation" sub-test. Use this when the test has already enabled a +// feature gate that was introduced after 1.35, which would cause the emulation version +// downgrade to 1.35 to fail. +func WithMinEmulationVersion(v *version.Version) ValidationTestConfig { + return func(o *validationOption) { + o.MinEmulationVersion = v + } +} + // VerifyValidationEquivalence provides a helper for testing the migration from // hand-written imperative validation to declarative validation. It ensures that // the validation logic remains consistent before and after the feature is enabled. // -// The function operates by running the provided validation function under two scenarios: -// 1. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled, +// The function operates by running the provided validation function under four scenarios: +// 1. With DeclarativeValidation and DeclarativeValidationBeta feature gates enabled, +// using the new declarative validation rules (Beta stage). +// 2. With DeclarativeValidation enabled and DeclarativeValidationBeta disabled, +// using the new declarative validation rules (Standard stage). +// 3. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled, // simulating the legacy hand-written validation. -// 2. With both feature gates enabled, using the new declarative validation rules. +// 4. With all declarative rules enforced (including Alpha), ensuring that the full set of +// declarative validations is correctly implemented (testing only). // -// It then asserts that the validation errors produced in both scenarios are equivalent, +// It then asserts that the validation errors produced in all scenarios are equivalent, // guaranteeing a safe migration. It also checks the errors against an expected set. // It compares errors by field, origin and type; all three should match to be called equivalent. // It also make sure all versions of the given API returns equivalent errors. @@ -234,9 +292,10 @@ func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime. for _, testcfg := range testConfigs { testcfg(opts) } - verifyValidationEquivalence(t, expectedErrs, func() field.ErrorList { - return validateFn(ctx, obj) - }, opts) + + verifyValidationEquivalence(t, expectedErrs, func(c context.Context) field.ErrorList { + return validateFn(c, obj) + }, ctx, opts) VerifyVersionedValidationEquivalence(t, obj, nil, testConfigs...) } @@ -244,12 +303,17 @@ func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime. // hand-written imperative validation to declarative validation for update operations. // It ensures that the validation logic remains consistent before and after the feature is enabled. // -// The function operates by running the provided validation function under two scenarios: -// 1. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled, +// The function operates by running the provided validation function under four scenarios: +// 1. With DeclarativeValidation and DeclarativeValidationBeta feature gates enabled, +// using the new declarative validation rules (Beta stage). +// 2. With DeclarativeValidation enabled and DeclarativeValidationBeta disabled, +// using the new declarative validation rules (Standard stage). +// 3. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled, // simulating the legacy hand-written validation. -// 2. With both feature gates enabled, using the new declarative validation rules. +// 4. With all declarative rules enforced (including Alpha), ensuring that the full set of +// declarative validations is correctly implemented (testing only). // -// It then asserts that the validation errors produced in both scenarios are equivalent, +// It then asserts that the validation errors produced in all scenarios are equivalent, // guaranteeing a safe migration. It also checks the errors against an expected set. // It compares errors by field, origin and type; all three should match to be called equivalent. // It also make sure all versions of the given API returns equivalent errors. @@ -259,44 +323,81 @@ func VerifyUpdateValidationEquivalence(t *testing.T, ctx context.Context, obj, o for _, testcfg := range testConfigs { testcfg(opts) } - verifyValidationEquivalence(t, expectedErrs, func() field.ErrorList { - return validateUpdateFn(ctx, obj, old) - }, opts) + + verifyValidationEquivalence(t, expectedErrs, func(c context.Context) field.ErrorList { + return validateUpdateFn(c, obj, old) + }, ctx, opts) VerifyVersionedValidationEquivalence(t, obj, old, testConfigs...) } // verifyValidationEquivalence is a generic helper that verifies validation equivalence with and without declarative validation. -func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, runValidations func() field.ErrorList, opt *validationOption) { +func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, runValidations func(context.Context) field.ErrorList, ctx context.Context, opt *validationOption) { t.Helper() - var declarativeTakeoverErrs field.ErrorList + var declarativeBetaEnabledErrs field.ErrorList + var declarativeBetaDisabledErrs field.ErrorList var imperativeErrs field.ErrorList + // Reset metrics to ensure a clean state for mismatch checking + legacyregistry.Reset() + defer legacyregistry.Reset() + // The errOutputMatcher is used to verify the output matches the expected errors in test cases. errOutputMatcher := field.ErrorMatcher{}.ByType().ByOrigin().ByFieldNormalized(opt.NormalizationRules) - // We only need to test both gate enabled and disabled together, because - // 1) the DeclarativeValidationTakeover won't take effect if DeclarativeValidation is disabled. - // 2) the validation output, when only DeclarativeValidation is enabled, is the same as when both gates are disabled. - t.Run("with declarative validation", func(t *testing.T) { + // 1. Declarative Validation with Beta Gate Enabled + t.Run("with declarative validation (Beta enabled)", func(t *testing.T) { + validationmetrics.ResetValidationMetricsInstance() featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ - features.DeclarativeValidation: true, - features.DeclarativeValidationTakeover: true, + features.DeclarativeValidation: true, + features.DeclarativeValidationBeta: true, }) - declarativeTakeoverErrs = runValidations() + declarativeBetaEnabledErrs = runValidations(ctx) if len(expectedErrs) > 0 { - errOutputMatcher.Test(t, expectedErrs, declarativeTakeoverErrs) - } else if len(declarativeTakeoverErrs) != 0 { - t.Errorf("expected no errors, but got: %v", declarativeTakeoverErrs) + errOutputMatcher.Test(t, expectedErrs, declarativeBetaEnabledErrs) + } else if len(declarativeBetaEnabledErrs) != 0 { + t.Errorf("expected no errors, but got: %v", declarativeBetaEnabledErrs) } + + // Ensure no mismatches were logged/metrics incremented + testutil.AssertVectorCount(t, "apiserver_validation_declarative_validation_mismatch_total", nil, 0) }) + // 2. Declarative Validation with Beta Gate Disabled + t.Run("with declarative validation (Beta disabled)", func(t *testing.T) { + validationmetrics.ResetValidationMetricsInstance() + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DeclarativeValidation: true, + features.DeclarativeValidationBeta: false, + }) + declarativeBetaDisabledErrs = runValidations(ctx) + + if len(expectedErrs) > 0 { + errOutputMatcher.Test(t, expectedErrs, declarativeBetaDisabledErrs) + } else if len(declarativeBetaDisabledErrs) != 0 { + t.Errorf("expected no errors, but got: %v", declarativeBetaDisabledErrs) + } + + // Ensure no mismatches were logged/metrics incremented + testutil.AssertVectorCount(t, "apiserver_validation_declarative_validation_mismatch_total", nil, 0) + }) + // 3. Legacy Hand Written Validation + // TODO: Remove this test case in 1.39 when emulation for 1.35 is no longer needed. + emulationVersion := version.MustParse("1.35") + skipHandWritten := opt.MinEmulationVersion != nil && opt.MinEmulationVersion.GreaterThan(emulationVersion) t.Run("hand written validation", func(t *testing.T) { + if skipHandWritten { + t.Skipf("skipping: minimum emulation version %s is greater than %s", opt.MinEmulationVersion, emulationVersion) + } + // Even when DeclarativeValidation gate is disabled, if the object's strategy has explicit + // declarative enforcement enabled, Standard declarative validations still run and are enforced. + // Emulating 1.35 ensures the DeclarativeValidationBeta gate is also effectively disabled (evaluated as false) + // because the feature gate was not introduced until 1.36. + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, emulationVersion) featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ - features.DeclarativeValidationTakeover: false, - features.DeclarativeValidation: false, + features.DeclarativeValidation: false, }) - imperativeErrs = runValidations() + imperativeErrs = runValidations(ctx) if len(expectedErrs) > 0 { errOutputMatcher.Test(t, expectedErrs, imperativeErrs) @@ -305,13 +406,43 @@ func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, run } }) + // 4. Declarative Validation with All Rules Enforced (Testing Only) + // This sub-test ensures that all declarative validation rules (including those marked as Alpha) + // are correctly implemented and match the expected errors. It uses a special context + // to force enforcement of all declarative rules and filter out their handwritten counterparts. + t.Run("with declarative validation (All Rules Enforced)", func(t *testing.T) { + validationmetrics.ResetValidationMetricsInstance() + // We don't strictly need to set feature gates here as the context override should force enforcement, + // but setting them ensures a consistent environment. + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DeclarativeValidation: true, + features.DeclarativeValidationBeta: true, + }) + testCtx := rest.WithAllDeclarativeEnforcedForTest(ctx) + allDeclarativeErrs := runValidations(testCtx) + + // The matcher here is more specific to ensure that errors from Alpha rules + // are included and matched correctly. + // This also ensure that errors are coming from the declarative validations only. + dvErrorMatcher := errOutputMatcher.ByValidationStabilityLevel().BySource() + if len(expectedErrs) > 0 { + dvErrorMatcher.Test(t, expectedErrs, allDeclarativeErrs) + } else if len(allDeclarativeErrs) != 0 { + t.Errorf("expected no errors, but got: %v", allDeclarativeErrs) + } + + // Ensure no mismatches were logged/metrics incremented + testutil.AssertVectorCount(t, "apiserver_validation_declarative_validation_mismatch_total", nil, 0) + }) + if t.Failed() { // There is no point in moving forward, if any of above tests failed for any reason. Running follow up tests will return noise. t.SkipNow() } - // The equivalenceMatcher is used to verify the output errors from hand-written imperative validation - // are equivalent to the output errors when DeclarativeValidationTakeover is enabled. + // The equivalenceMatcher is used to verify that the output errors from hand-written imperative validation + // are equivalent to the output errors in all declarative validation scenarios (Beta enabled/disabled). + // This ensures that enabling the feature gates does not change the validation outcome. equivalenceMatcher := field.ErrorMatcher{}.ByType().ByOrigin() if len(opt.NormalizationRules) > 0 { equivalenceMatcher = equivalenceMatcher.ByFieldNormalized(opt.NormalizationRules) @@ -319,11 +450,15 @@ func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, run equivalenceMatcher = equivalenceMatcher.ByField() } - // The imperative validation may produce duplicate errors, which is not supported by the ErrorMatcher. - // TODO: remove this once ErrorMatcher has been extended to handle this form of deduplication. - imperativeErrs = deDuplicateErrors(imperativeErrs, equivalenceMatcher) + if !skipHandWritten { + // The imperative validation may produce duplicate errors, which is not supported by the ErrorMatcher. + // TODO: remove this once ErrorMatcher has been extended to handle this form of deduplication. + imperativeErrs = deDuplicateErrors(imperativeErrs, equivalenceMatcher) - equivalenceMatcher.Test(t, imperativeErrs, declarativeTakeoverErrs) + // Verify equivalence across all scenarios + equivalenceMatcher.Test(t, imperativeErrs, declarativeBetaEnabledErrs) + equivalenceMatcher.Test(t, imperativeErrs, declarativeBetaDisabledErrs) + } } // deDuplicateErrors removes duplicate errors from an ErrorList based on the provided matcher. diff --git a/pkg/api/testing/validation_test.go b/pkg/api/testing/validation_test.go index 41ec078559180..23ffa1d99f2d1 100644 --- a/pkg/api/testing/validation_test.go +++ b/pkg/api/testing/validation_test.go @@ -23,7 +23,9 @@ import ( "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kubernetes/pkg/api/legacyscheme" + nodevalidation "k8s.io/kubernetes/pkg/apis/node/validation" resourcevalidation "k8s.io/kubernetes/pkg/apis/resource/validation" ) @@ -33,15 +35,47 @@ func TestVersionedValidationByFuzzing(t *testing.T) { typesWithDeclarativeValidation := []schema.GroupVersion{ // Registered group versions for versioned validation fuzz testing: {Group: "", Version: "v1"}, + {Group: "batch", Version: "v1"}, + {Group: "batch", Version: "v1beta1"}, {Group: "certificates.k8s.io", Version: "v1"}, {Group: "certificates.k8s.io", Version: "v1alpha1"}, {Group: "certificates.k8s.io", Version: "v1beta1"}, + {Group: "resource.k8s.io", Version: "v1"}, + {Group: "resource.k8s.io", Version: "v1alpha3"}, {Group: "resource.k8s.io", Version: "v1beta1"}, {Group: "resource.k8s.io", Version: "v1beta2"}, - {Group: "resource.k8s.io", Version: "v1"}, {Group: "storage.k8s.io", Version: "v1"}, - {Group: "storage.k8s.io", Version: "v1beta1"}, {Group: "storage.k8s.io", Version: "v1alpha1"}, + {Group: "storage.k8s.io", Version: "v1beta1"}, + {Group: "node.k8s.io", Version: "v1"}, + {Group: "node.k8s.io", Version: "v1alpha1"}, + {Group: "node.k8s.io", Version: "v1beta1"}, + {Group: "network.k8s.io", Version: "v1"}, + {Group: "network.k8s.io", Version: "v1beta1"}, + {Group: "autoscaling", Version: "v1"}, + {Group: "autoscaling", Version: "v1beta1"}, + {Group: "autoscaling", Version: "v1beta2"}, + {Group: "autoscaling", Version: "v2"}, + {Group: "admissionregistration.k8s.io", Version: "v1"}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1"}, + {Group: "admissionregistration.k8s.io", Version: "v1alpha1"}, + {Group: "discovery.k8s.io", Version: "v1"}, + {Group: "discovery.k8s.io", Version: "v1beta1"}, + {Group: "admissionregistration.k8s.io", Version: "v1"}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1"}, + {Group: "admissionregistration.k8s.io", Version: "v1alpha1"}, + } + + // subresourceOnly specifies the subresource path for types that can only be validated + // as subresources (e.g. autoscaling/Scale) and do not support root-level validation. + // For GVKs not in this map, the test defaults to fuzzing the root resource (""). + // Other resources with subresources (e.g. Pod status, exec) share validation logic with + // the root resource, so fuzzing the root is sufficient to verify validation equivalence. + subresourceOnly := map[schema.GroupVersionKind]string{ + {Group: "autoscaling", Version: "v1", Kind: "Scale"}: "scale", + {Group: "autoscaling", Version: "v1beta1", Kind: "Scale"}: "scale", + {Group: "autoscaling", Version: "v1beta2", Kind: "Scale"}: "scale", + {Group: "autoscaling", Version: "v2", Kind: "Scale"}: "scale", } fuzzIters := *roundtrip.FuzzIters / 10 // TODO: Find a better way to manage test running time @@ -56,10 +90,22 @@ func TestVersionedValidationByFuzzing(t *testing.T) { if err != nil { t.Fatalf("could not create a %v: %s", kind, err) } - f.Fill(obj) + + subresource := "" + if specific, ok := subresourceOnly[gvk]; ok { + subresource = specific + } var opts []ValidationTestConfig - opts = append(opts, WithNormalizationRules(resourcevalidation.ResourceNormalizationRules...)) + // TODO(API group level configuration): Consider configuring normalization rules at the + // API group level to avoid potential collisions when multiple rule sets are combined. + // This would allow each API group to register its own normalization rules independently. + allRules := append([]field.NormalizationRule{}, resourcevalidation.ResourceNormalizationRules...) + allRules = append(allRules, nodevalidation.NodeNormalizationRules...) + opts = append(opts, WithNormalizationRules(allRules...), WithFuzzer(f)) + if subresource != "" { + opts = append(opts, WithSubResources(subresource)) + } VerifyVersionedValidationEquivalence(t, obj, nil, opts...) @@ -67,7 +113,7 @@ func TestVersionedValidationByFuzzing(t *testing.T) { if err != nil { t.Fatalf("could not create a %v: %s", kind, err) } - f.Fill(old) + VerifyVersionedValidationEquivalence(t, obj, old, opts...) } }) diff --git a/pkg/api/v1/endpoints/util_test.go b/pkg/api/v1/endpoints/util_test.go index 897c0b76c5611..3046dfc63d335 100644 --- a/pkg/api/v1/endpoints/util_test.go +++ b/pkg/api/v1/endpoints/util_test.go @@ -22,7 +22,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) func podRef(uid string) *v1.ObjectReference { diff --git a/pkg/apis/admissionregistration/v1/defaults.go b/pkg/apis/admissionregistration/v1/defaults.go index a09ce6a2b8454..654c2d38dc45b 100644 --- a/pkg/apis/admissionregistration/v1/defaults.go +++ b/pkg/apis/admissionregistration/v1/defaults.go @@ -117,3 +117,11 @@ func SetDefaults_MatchResources(obj *admissionregistrationv1.MatchResources) { obj.ObjectSelector = &selector } } + +// SetDefaults_MutatingAdmissionPolicySpec sets defaults for MutatingAdmissionPolicySpec +func SetDefaults_MutatingAdmissionPolicySpec(obj *admissionregistrationv1.MutatingAdmissionPolicySpec) { + if obj.FailurePolicy == nil { + policy := admissionregistrationv1.Fail + obj.FailurePolicy = &policy + } +} diff --git a/pkg/apis/admissionregistration/v1/doc.go b/pkg/apis/admissionregistration/v1/doc.go index e789114906bd2..aaa00bebef0f4 100644 --- a/pkg/apis/admissionregistration/v1/doc.go +++ b/pkg/apis/admissionregistration/v1/doc.go @@ -19,6 +19,8 @@ limitations under the License. // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/admissionregistration/v1 // +groupName=admissionregistration.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/admissionregistration/v1 // Package v1 is the v1 version of the API. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration diff --git a/pkg/apis/admissionregistration/v1/zz_generated.conversion.go b/pkg/apis/admissionregistration/v1/zz_generated.conversion.go index 19da52d94f8e5..d10465a3a1d63 100644 --- a/pkg/apis/admissionregistration/v1/zz_generated.conversion.go +++ b/pkg/apis/admissionregistration/v1/zz_generated.conversion.go @@ -38,6 +38,16 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.ApplyConfiguration)(nil), (*admissionregistration.ApplyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ApplyConfiguration_To_admissionregistration_ApplyConfiguration(a.(*admissionregistrationv1.ApplyConfiguration), b.(*admissionregistration.ApplyConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.ApplyConfiguration)(nil), (*admissionregistrationv1.ApplyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_ApplyConfiguration_To_v1_ApplyConfiguration(a.(*admissionregistration.ApplyConfiguration), b.(*admissionregistrationv1.ApplyConfiguration), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.AuditAnnotation)(nil), (*admissionregistration.AuditAnnotation)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_AuditAnnotation_To_admissionregistration_AuditAnnotation(a.(*admissionregistrationv1.AuditAnnotation), b.(*admissionregistration.AuditAnnotation), scope) }); err != nil { @@ -58,6 +68,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.JSONPatch)(nil), (*admissionregistration.JSONPatch)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_JSONPatch_To_admissionregistration_JSONPatch(a.(*admissionregistrationv1.JSONPatch), b.(*admissionregistration.JSONPatch), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.JSONPatch)(nil), (*admissionregistrationv1.JSONPatch)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_JSONPatch_To_v1_JSONPatch(a.(*admissionregistration.JSONPatch), b.(*admissionregistrationv1.JSONPatch), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MatchCondition)(nil), (*admissionregistration.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_MatchCondition_To_admissionregistration_MatchCondition(a.(*admissionregistrationv1.MatchCondition), b.(*admissionregistration.MatchCondition), scope) }); err != nil { @@ -78,6 +98,66 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingAdmissionPolicy)(nil), (*admissionregistration.MutatingAdmissionPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicy_To_admissionregistration_MutatingAdmissionPolicy(a.(*admissionregistrationv1.MutatingAdmissionPolicy), b.(*admissionregistration.MutatingAdmissionPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingAdmissionPolicy)(nil), (*admissionregistrationv1.MutatingAdmissionPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingAdmissionPolicy_To_v1_MutatingAdmissionPolicy(a.(*admissionregistration.MutatingAdmissionPolicy), b.(*admissionregistrationv1.MutatingAdmissionPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingAdmissionPolicyBinding)(nil), (*admissionregistration.MutatingAdmissionPolicyBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicyBinding_To_admissionregistration_MutatingAdmissionPolicyBinding(a.(*admissionregistrationv1.MutatingAdmissionPolicyBinding), b.(*admissionregistration.MutatingAdmissionPolicyBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingAdmissionPolicyBinding)(nil), (*admissionregistrationv1.MutatingAdmissionPolicyBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingAdmissionPolicyBinding_To_v1_MutatingAdmissionPolicyBinding(a.(*admissionregistration.MutatingAdmissionPolicyBinding), b.(*admissionregistrationv1.MutatingAdmissionPolicyBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingAdmissionPolicyBindingList)(nil), (*admissionregistration.MutatingAdmissionPolicyBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicyBindingList_To_admissionregistration_MutatingAdmissionPolicyBindingList(a.(*admissionregistrationv1.MutatingAdmissionPolicyBindingList), b.(*admissionregistration.MutatingAdmissionPolicyBindingList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingAdmissionPolicyBindingList)(nil), (*admissionregistrationv1.MutatingAdmissionPolicyBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingAdmissionPolicyBindingList_To_v1_MutatingAdmissionPolicyBindingList(a.(*admissionregistration.MutatingAdmissionPolicyBindingList), b.(*admissionregistrationv1.MutatingAdmissionPolicyBindingList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingAdmissionPolicyBindingSpec)(nil), (*admissionregistration.MutatingAdmissionPolicyBindingSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicyBindingSpec_To_admissionregistration_MutatingAdmissionPolicyBindingSpec(a.(*admissionregistrationv1.MutatingAdmissionPolicyBindingSpec), b.(*admissionregistration.MutatingAdmissionPolicyBindingSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingAdmissionPolicyBindingSpec)(nil), (*admissionregistrationv1.MutatingAdmissionPolicyBindingSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingAdmissionPolicyBindingSpec_To_v1_MutatingAdmissionPolicyBindingSpec(a.(*admissionregistration.MutatingAdmissionPolicyBindingSpec), b.(*admissionregistrationv1.MutatingAdmissionPolicyBindingSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingAdmissionPolicyList)(nil), (*admissionregistration.MutatingAdmissionPolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicyList_To_admissionregistration_MutatingAdmissionPolicyList(a.(*admissionregistrationv1.MutatingAdmissionPolicyList), b.(*admissionregistration.MutatingAdmissionPolicyList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingAdmissionPolicyList)(nil), (*admissionregistrationv1.MutatingAdmissionPolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingAdmissionPolicyList_To_v1_MutatingAdmissionPolicyList(a.(*admissionregistration.MutatingAdmissionPolicyList), b.(*admissionregistrationv1.MutatingAdmissionPolicyList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingAdmissionPolicySpec)(nil), (*admissionregistration.MutatingAdmissionPolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicySpec_To_admissionregistration_MutatingAdmissionPolicySpec(a.(*admissionregistrationv1.MutatingAdmissionPolicySpec), b.(*admissionregistration.MutatingAdmissionPolicySpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingAdmissionPolicySpec)(nil), (*admissionregistrationv1.MutatingAdmissionPolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingAdmissionPolicySpec_To_v1_MutatingAdmissionPolicySpec(a.(*admissionregistration.MutatingAdmissionPolicySpec), b.(*admissionregistrationv1.MutatingAdmissionPolicySpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.MutatingWebhook)(nil), (*admissionregistration.MutatingWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_MutatingWebhook_To_admissionregistration_MutatingWebhook(a.(*admissionregistrationv1.MutatingWebhook), b.(*admissionregistration.MutatingWebhook), scope) }); err != nil { @@ -108,6 +188,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.Mutation)(nil), (*admissionregistration.Mutation)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Mutation_To_admissionregistration_Mutation(a.(*admissionregistrationv1.Mutation), b.(*admissionregistration.Mutation), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.Mutation)(nil), (*admissionregistrationv1.Mutation)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_Mutation_To_v1_Mutation(a.(*admissionregistration.Mutation), b.(*admissionregistrationv1.Mutation), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*admissionregistrationv1.NamedRuleWithOperations)(nil), (*admissionregistration.NamedRuleWithOperations)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_NamedRuleWithOperations_To_admissionregistration_NamedRuleWithOperations(a.(*admissionregistrationv1.NamedRuleWithOperations), b.(*admissionregistration.NamedRuleWithOperations), scope) }); err != nil { @@ -311,6 +401,26 @@ func RegisterConversions(s *runtime.Scheme) error { return nil } +func autoConvert_v1_ApplyConfiguration_To_admissionregistration_ApplyConfiguration(in *admissionregistrationv1.ApplyConfiguration, out *admissionregistration.ApplyConfiguration, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_v1_ApplyConfiguration_To_admissionregistration_ApplyConfiguration is an autogenerated conversion function. +func Convert_v1_ApplyConfiguration_To_admissionregistration_ApplyConfiguration(in *admissionregistrationv1.ApplyConfiguration, out *admissionregistration.ApplyConfiguration, s conversion.Scope) error { + return autoConvert_v1_ApplyConfiguration_To_admissionregistration_ApplyConfiguration(in, out, s) +} + +func autoConvert_admissionregistration_ApplyConfiguration_To_v1_ApplyConfiguration(in *admissionregistration.ApplyConfiguration, out *admissionregistrationv1.ApplyConfiguration, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_admissionregistration_ApplyConfiguration_To_v1_ApplyConfiguration is an autogenerated conversion function. +func Convert_admissionregistration_ApplyConfiguration_To_v1_ApplyConfiguration(in *admissionregistration.ApplyConfiguration, out *admissionregistrationv1.ApplyConfiguration, s conversion.Scope) error { + return autoConvert_admissionregistration_ApplyConfiguration_To_v1_ApplyConfiguration(in, out, s) +} + func autoConvert_v1_AuditAnnotation_To_admissionregistration_AuditAnnotation(in *admissionregistrationv1.AuditAnnotation, out *admissionregistration.AuditAnnotation, s conversion.Scope) error { out.Key = in.Key out.ValueExpression = in.ValueExpression @@ -355,6 +465,26 @@ func Convert_admissionregistration_ExpressionWarning_To_v1_ExpressionWarning(in return autoConvert_admissionregistration_ExpressionWarning_To_v1_ExpressionWarning(in, out, s) } +func autoConvert_v1_JSONPatch_To_admissionregistration_JSONPatch(in *admissionregistrationv1.JSONPatch, out *admissionregistration.JSONPatch, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_v1_JSONPatch_To_admissionregistration_JSONPatch is an autogenerated conversion function. +func Convert_v1_JSONPatch_To_admissionregistration_JSONPatch(in *admissionregistrationv1.JSONPatch, out *admissionregistration.JSONPatch, s conversion.Scope) error { + return autoConvert_v1_JSONPatch_To_admissionregistration_JSONPatch(in, out, s) +} + +func autoConvert_admissionregistration_JSONPatch_To_v1_JSONPatch(in *admissionregistration.JSONPatch, out *admissionregistrationv1.JSONPatch, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_admissionregistration_JSONPatch_To_v1_JSONPatch is an autogenerated conversion function. +func Convert_admissionregistration_JSONPatch_To_v1_JSONPatch(in *admissionregistration.JSONPatch, out *admissionregistrationv1.JSONPatch, s conversion.Scope) error { + return autoConvert_admissionregistration_JSONPatch_To_v1_JSONPatch(in, out, s) +} + func autoConvert_v1_MatchCondition_To_admissionregistration_MatchCondition(in *admissionregistrationv1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error { out.Name = in.Name out.Expression = in.Expression @@ -445,6 +575,230 @@ func Convert_admissionregistration_MatchResources_To_v1_MatchResources(in *admis return autoConvert_admissionregistration_MatchResources_To_v1_MatchResources(in, out, s) } +func autoConvert_v1_MutatingAdmissionPolicy_To_admissionregistration_MutatingAdmissionPolicy(in *admissionregistrationv1.MutatingAdmissionPolicy, out *admissionregistration.MutatingAdmissionPolicy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_MutatingAdmissionPolicySpec_To_admissionregistration_MutatingAdmissionPolicySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_v1_MutatingAdmissionPolicy_To_admissionregistration_MutatingAdmissionPolicy is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicy_To_admissionregistration_MutatingAdmissionPolicy(in *admissionregistrationv1.MutatingAdmissionPolicy, out *admissionregistration.MutatingAdmissionPolicy, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicy_To_admissionregistration_MutatingAdmissionPolicy(in, out, s) +} + +func autoConvert_admissionregistration_MutatingAdmissionPolicy_To_v1_MutatingAdmissionPolicy(in *admissionregistration.MutatingAdmissionPolicy, out *admissionregistrationv1.MutatingAdmissionPolicy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_admissionregistration_MutatingAdmissionPolicySpec_To_v1_MutatingAdmissionPolicySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_admissionregistration_MutatingAdmissionPolicy_To_v1_MutatingAdmissionPolicy is an autogenerated conversion function. +func Convert_admissionregistration_MutatingAdmissionPolicy_To_v1_MutatingAdmissionPolicy(in *admissionregistration.MutatingAdmissionPolicy, out *admissionregistrationv1.MutatingAdmissionPolicy, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingAdmissionPolicy_To_v1_MutatingAdmissionPolicy(in, out, s) +} + +func autoConvert_v1_MutatingAdmissionPolicyBinding_To_admissionregistration_MutatingAdmissionPolicyBinding(in *admissionregistrationv1.MutatingAdmissionPolicyBinding, out *admissionregistration.MutatingAdmissionPolicyBinding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_MutatingAdmissionPolicyBindingSpec_To_admissionregistration_MutatingAdmissionPolicyBindingSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_v1_MutatingAdmissionPolicyBinding_To_admissionregistration_MutatingAdmissionPolicyBinding is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicyBinding_To_admissionregistration_MutatingAdmissionPolicyBinding(in *admissionregistrationv1.MutatingAdmissionPolicyBinding, out *admissionregistration.MutatingAdmissionPolicyBinding, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicyBinding_To_admissionregistration_MutatingAdmissionPolicyBinding(in, out, s) +} + +func autoConvert_admissionregistration_MutatingAdmissionPolicyBinding_To_v1_MutatingAdmissionPolicyBinding(in *admissionregistration.MutatingAdmissionPolicyBinding, out *admissionregistrationv1.MutatingAdmissionPolicyBinding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_admissionregistration_MutatingAdmissionPolicyBindingSpec_To_v1_MutatingAdmissionPolicyBindingSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_admissionregistration_MutatingAdmissionPolicyBinding_To_v1_MutatingAdmissionPolicyBinding is an autogenerated conversion function. +func Convert_admissionregistration_MutatingAdmissionPolicyBinding_To_v1_MutatingAdmissionPolicyBinding(in *admissionregistration.MutatingAdmissionPolicyBinding, out *admissionregistrationv1.MutatingAdmissionPolicyBinding, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingAdmissionPolicyBinding_To_v1_MutatingAdmissionPolicyBinding(in, out, s) +} + +func autoConvert_v1_MutatingAdmissionPolicyBindingList_To_admissionregistration_MutatingAdmissionPolicyBindingList(in *admissionregistrationv1.MutatingAdmissionPolicyBindingList, out *admissionregistration.MutatingAdmissionPolicyBindingList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]admissionregistration.MutatingAdmissionPolicyBinding, len(*in)) + for i := range *in { + if err := Convert_v1_MutatingAdmissionPolicyBinding_To_admissionregistration_MutatingAdmissionPolicyBinding(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_MutatingAdmissionPolicyBindingList_To_admissionregistration_MutatingAdmissionPolicyBindingList is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicyBindingList_To_admissionregistration_MutatingAdmissionPolicyBindingList(in *admissionregistrationv1.MutatingAdmissionPolicyBindingList, out *admissionregistration.MutatingAdmissionPolicyBindingList, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicyBindingList_To_admissionregistration_MutatingAdmissionPolicyBindingList(in, out, s) +} + +func autoConvert_admissionregistration_MutatingAdmissionPolicyBindingList_To_v1_MutatingAdmissionPolicyBindingList(in *admissionregistration.MutatingAdmissionPolicyBindingList, out *admissionregistrationv1.MutatingAdmissionPolicyBindingList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]admissionregistrationv1.MutatingAdmissionPolicyBinding, len(*in)) + for i := range *in { + if err := Convert_admissionregistration_MutatingAdmissionPolicyBinding_To_v1_MutatingAdmissionPolicyBinding(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_admissionregistration_MutatingAdmissionPolicyBindingList_To_v1_MutatingAdmissionPolicyBindingList is an autogenerated conversion function. +func Convert_admissionregistration_MutatingAdmissionPolicyBindingList_To_v1_MutatingAdmissionPolicyBindingList(in *admissionregistration.MutatingAdmissionPolicyBindingList, out *admissionregistrationv1.MutatingAdmissionPolicyBindingList, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingAdmissionPolicyBindingList_To_v1_MutatingAdmissionPolicyBindingList(in, out, s) +} + +func autoConvert_v1_MutatingAdmissionPolicyBindingSpec_To_admissionregistration_MutatingAdmissionPolicyBindingSpec(in *admissionregistrationv1.MutatingAdmissionPolicyBindingSpec, out *admissionregistration.MutatingAdmissionPolicyBindingSpec, s conversion.Scope) error { + out.PolicyName = in.PolicyName + out.ParamRef = (*admissionregistration.ParamRef)(unsafe.Pointer(in.ParamRef)) + if in.MatchResources != nil { + in, out := &in.MatchResources, &out.MatchResources + *out = new(admissionregistration.MatchResources) + if err := Convert_v1_MatchResources_To_admissionregistration_MatchResources(*in, *out, s); err != nil { + return err + } + } else { + out.MatchResources = nil + } + return nil +} + +// Convert_v1_MutatingAdmissionPolicyBindingSpec_To_admissionregistration_MutatingAdmissionPolicyBindingSpec is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicyBindingSpec_To_admissionregistration_MutatingAdmissionPolicyBindingSpec(in *admissionregistrationv1.MutatingAdmissionPolicyBindingSpec, out *admissionregistration.MutatingAdmissionPolicyBindingSpec, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicyBindingSpec_To_admissionregistration_MutatingAdmissionPolicyBindingSpec(in, out, s) +} + +func autoConvert_admissionregistration_MutatingAdmissionPolicyBindingSpec_To_v1_MutatingAdmissionPolicyBindingSpec(in *admissionregistration.MutatingAdmissionPolicyBindingSpec, out *admissionregistrationv1.MutatingAdmissionPolicyBindingSpec, s conversion.Scope) error { + out.PolicyName = in.PolicyName + out.ParamRef = (*admissionregistrationv1.ParamRef)(unsafe.Pointer(in.ParamRef)) + if in.MatchResources != nil { + in, out := &in.MatchResources, &out.MatchResources + *out = new(admissionregistrationv1.MatchResources) + if err := Convert_admissionregistration_MatchResources_To_v1_MatchResources(*in, *out, s); err != nil { + return err + } + } else { + out.MatchResources = nil + } + return nil +} + +// Convert_admissionregistration_MutatingAdmissionPolicyBindingSpec_To_v1_MutatingAdmissionPolicyBindingSpec is an autogenerated conversion function. +func Convert_admissionregistration_MutatingAdmissionPolicyBindingSpec_To_v1_MutatingAdmissionPolicyBindingSpec(in *admissionregistration.MutatingAdmissionPolicyBindingSpec, out *admissionregistrationv1.MutatingAdmissionPolicyBindingSpec, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingAdmissionPolicyBindingSpec_To_v1_MutatingAdmissionPolicyBindingSpec(in, out, s) +} + +func autoConvert_v1_MutatingAdmissionPolicyList_To_admissionregistration_MutatingAdmissionPolicyList(in *admissionregistrationv1.MutatingAdmissionPolicyList, out *admissionregistration.MutatingAdmissionPolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]admissionregistration.MutatingAdmissionPolicy, len(*in)) + for i := range *in { + if err := Convert_v1_MutatingAdmissionPolicy_To_admissionregistration_MutatingAdmissionPolicy(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_MutatingAdmissionPolicyList_To_admissionregistration_MutatingAdmissionPolicyList is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicyList_To_admissionregistration_MutatingAdmissionPolicyList(in *admissionregistrationv1.MutatingAdmissionPolicyList, out *admissionregistration.MutatingAdmissionPolicyList, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicyList_To_admissionregistration_MutatingAdmissionPolicyList(in, out, s) +} + +func autoConvert_admissionregistration_MutatingAdmissionPolicyList_To_v1_MutatingAdmissionPolicyList(in *admissionregistration.MutatingAdmissionPolicyList, out *admissionregistrationv1.MutatingAdmissionPolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]admissionregistrationv1.MutatingAdmissionPolicy, len(*in)) + for i := range *in { + if err := Convert_admissionregistration_MutatingAdmissionPolicy_To_v1_MutatingAdmissionPolicy(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_admissionregistration_MutatingAdmissionPolicyList_To_v1_MutatingAdmissionPolicyList is an autogenerated conversion function. +func Convert_admissionregistration_MutatingAdmissionPolicyList_To_v1_MutatingAdmissionPolicyList(in *admissionregistration.MutatingAdmissionPolicyList, out *admissionregistrationv1.MutatingAdmissionPolicyList, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingAdmissionPolicyList_To_v1_MutatingAdmissionPolicyList(in, out, s) +} + +func autoConvert_v1_MutatingAdmissionPolicySpec_To_admissionregistration_MutatingAdmissionPolicySpec(in *admissionregistrationv1.MutatingAdmissionPolicySpec, out *admissionregistration.MutatingAdmissionPolicySpec, s conversion.Scope) error { + out.ParamKind = (*admissionregistration.ParamKind)(unsafe.Pointer(in.ParamKind)) + if in.MatchConstraints != nil { + in, out := &in.MatchConstraints, &out.MatchConstraints + *out = new(admissionregistration.MatchResources) + if err := Convert_v1_MatchResources_To_admissionregistration_MatchResources(*in, *out, s); err != nil { + return err + } + } else { + out.MatchConstraints = nil + } + out.Variables = *(*[]admissionregistration.Variable)(unsafe.Pointer(&in.Variables)) + out.Mutations = *(*[]admissionregistration.Mutation)(unsafe.Pointer(&in.Mutations)) + out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions)) + out.ReinvocationPolicy = admissionregistration.ReinvocationPolicyType(in.ReinvocationPolicy) + return nil +} + +// Convert_v1_MutatingAdmissionPolicySpec_To_admissionregistration_MutatingAdmissionPolicySpec is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicySpec_To_admissionregistration_MutatingAdmissionPolicySpec(in *admissionregistrationv1.MutatingAdmissionPolicySpec, out *admissionregistration.MutatingAdmissionPolicySpec, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicySpec_To_admissionregistration_MutatingAdmissionPolicySpec(in, out, s) +} + +func autoConvert_admissionregistration_MutatingAdmissionPolicySpec_To_v1_MutatingAdmissionPolicySpec(in *admissionregistration.MutatingAdmissionPolicySpec, out *admissionregistrationv1.MutatingAdmissionPolicySpec, s conversion.Scope) error { + out.ParamKind = (*admissionregistrationv1.ParamKind)(unsafe.Pointer(in.ParamKind)) + if in.MatchConstraints != nil { + in, out := &in.MatchConstraints, &out.MatchConstraints + *out = new(admissionregistrationv1.MatchResources) + if err := Convert_admissionregistration_MatchResources_To_v1_MatchResources(*in, *out, s); err != nil { + return err + } + } else { + out.MatchConstraints = nil + } + out.Variables = *(*[]admissionregistrationv1.Variable)(unsafe.Pointer(&in.Variables)) + out.Mutations = *(*[]admissionregistrationv1.Mutation)(unsafe.Pointer(&in.Mutations)) + out.FailurePolicy = (*admissionregistrationv1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.MatchConditions = *(*[]admissionregistrationv1.MatchCondition)(unsafe.Pointer(&in.MatchConditions)) + out.ReinvocationPolicy = admissionregistrationv1.ReinvocationPolicyType(in.ReinvocationPolicy) + return nil +} + +// Convert_admissionregistration_MutatingAdmissionPolicySpec_To_v1_MutatingAdmissionPolicySpec is an autogenerated conversion function. +func Convert_admissionregistration_MutatingAdmissionPolicySpec_To_v1_MutatingAdmissionPolicySpec(in *admissionregistration.MutatingAdmissionPolicySpec, out *admissionregistrationv1.MutatingAdmissionPolicySpec, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingAdmissionPolicySpec_To_v1_MutatingAdmissionPolicySpec(in, out, s) +} + func autoConvert_v1_MutatingWebhook_To_admissionregistration_MutatingWebhook(in *admissionregistrationv1.MutatingWebhook, out *admissionregistration.MutatingWebhook, s conversion.Scope) error { out.Name = in.Name if err := Convert_v1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { @@ -595,6 +949,30 @@ func Convert_admissionregistration_MutatingWebhookConfigurationList_To_v1_Mutati return autoConvert_admissionregistration_MutatingWebhookConfigurationList_To_v1_MutatingWebhookConfigurationList(in, out, s) } +func autoConvert_v1_Mutation_To_admissionregistration_Mutation(in *admissionregistrationv1.Mutation, out *admissionregistration.Mutation, s conversion.Scope) error { + out.PatchType = admissionregistration.PatchType(in.PatchType) + out.ApplyConfiguration = (*admissionregistration.ApplyConfiguration)(unsafe.Pointer(in.ApplyConfiguration)) + out.JSONPatch = (*admissionregistration.JSONPatch)(unsafe.Pointer(in.JSONPatch)) + return nil +} + +// Convert_v1_Mutation_To_admissionregistration_Mutation is an autogenerated conversion function. +func Convert_v1_Mutation_To_admissionregistration_Mutation(in *admissionregistrationv1.Mutation, out *admissionregistration.Mutation, s conversion.Scope) error { + return autoConvert_v1_Mutation_To_admissionregistration_Mutation(in, out, s) +} + +func autoConvert_admissionregistration_Mutation_To_v1_Mutation(in *admissionregistration.Mutation, out *admissionregistrationv1.Mutation, s conversion.Scope) error { + out.PatchType = admissionregistrationv1.PatchType(in.PatchType) + out.ApplyConfiguration = (*admissionregistrationv1.ApplyConfiguration)(unsafe.Pointer(in.ApplyConfiguration)) + out.JSONPatch = (*admissionregistrationv1.JSONPatch)(unsafe.Pointer(in.JSONPatch)) + return nil +} + +// Convert_admissionregistration_Mutation_To_v1_Mutation is an autogenerated conversion function. +func Convert_admissionregistration_Mutation_To_v1_Mutation(in *admissionregistration.Mutation, out *admissionregistrationv1.Mutation, s conversion.Scope) error { + return autoConvert_admissionregistration_Mutation_To_v1_Mutation(in, out, s) +} + func autoConvert_v1_NamedRuleWithOperations_To_admissionregistration_NamedRuleWithOperations(in *admissionregistrationv1.NamedRuleWithOperations, out *admissionregistration.NamedRuleWithOperations, s conversion.Scope) error { out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames)) if err := Convert_v1_RuleWithOperations_To_admissionregistration_RuleWithOperations(&in.RuleWithOperations, &out.RuleWithOperations, s); err != nil { diff --git a/pkg/apis/admissionregistration/v1/zz_generated.defaults.go b/pkg/apis/admissionregistration/v1/zz_generated.defaults.go index e27900b38fe37..a2856fad64e84 100644 --- a/pkg/apis/admissionregistration/v1/zz_generated.defaults.go +++ b/pkg/apis/admissionregistration/v1/zz_generated.defaults.go @@ -30,6 +30,18 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&admissionregistrationv1.MutatingAdmissionPolicy{}, func(obj interface{}) { + SetObjectDefaults_MutatingAdmissionPolicy(obj.(*admissionregistrationv1.MutatingAdmissionPolicy)) + }) + scheme.AddTypeDefaultingFunc(&admissionregistrationv1.MutatingAdmissionPolicyBinding{}, func(obj interface{}) { + SetObjectDefaults_MutatingAdmissionPolicyBinding(obj.(*admissionregistrationv1.MutatingAdmissionPolicyBinding)) + }) + scheme.AddTypeDefaultingFunc(&admissionregistrationv1.MutatingAdmissionPolicyBindingList{}, func(obj interface{}) { + SetObjectDefaults_MutatingAdmissionPolicyBindingList(obj.(*admissionregistrationv1.MutatingAdmissionPolicyBindingList)) + }) + scheme.AddTypeDefaultingFunc(&admissionregistrationv1.MutatingAdmissionPolicyList{}, func(obj interface{}) { + SetObjectDefaults_MutatingAdmissionPolicyList(obj.(*admissionregistrationv1.MutatingAdmissionPolicyList)) + }) scheme.AddTypeDefaultingFunc(&admissionregistrationv1.MutatingWebhookConfiguration{}, func(obj interface{}) { SetObjectDefaults_MutatingWebhookConfiguration(obj.(*admissionregistrationv1.MutatingWebhookConfiguration)) }) @@ -57,6 +69,49 @@ func RegisterDefaults(scheme *runtime.Scheme) error { return nil } +func SetObjectDefaults_MutatingAdmissionPolicy(in *admissionregistrationv1.MutatingAdmissionPolicy) { + SetDefaults_MutatingAdmissionPolicySpec(&in.Spec) + if in.Spec.MatchConstraints != nil { + SetDefaults_MatchResources(in.Spec.MatchConstraints) + for i := range in.Spec.MatchConstraints.ResourceRules { + a := &in.Spec.MatchConstraints.ResourceRules[i] + SetDefaults_Rule(&a.RuleWithOperations.Rule) + } + for i := range in.Spec.MatchConstraints.ExcludeResourceRules { + a := &in.Spec.MatchConstraints.ExcludeResourceRules[i] + SetDefaults_Rule(&a.RuleWithOperations.Rule) + } + } +} + +func SetObjectDefaults_MutatingAdmissionPolicyBinding(in *admissionregistrationv1.MutatingAdmissionPolicyBinding) { + if in.Spec.MatchResources != nil { + SetDefaults_MatchResources(in.Spec.MatchResources) + for i := range in.Spec.MatchResources.ResourceRules { + a := &in.Spec.MatchResources.ResourceRules[i] + SetDefaults_Rule(&a.RuleWithOperations.Rule) + } + for i := range in.Spec.MatchResources.ExcludeResourceRules { + a := &in.Spec.MatchResources.ExcludeResourceRules[i] + SetDefaults_Rule(&a.RuleWithOperations.Rule) + } + } +} + +func SetObjectDefaults_MutatingAdmissionPolicyBindingList(in *admissionregistrationv1.MutatingAdmissionPolicyBindingList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_MutatingAdmissionPolicyBinding(a) + } +} + +func SetObjectDefaults_MutatingAdmissionPolicyList(in *admissionregistrationv1.MutatingAdmissionPolicyList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_MutatingAdmissionPolicy(a) + } +} + func SetObjectDefaults_MutatingWebhookConfiguration(in *admissionregistrationv1.MutatingWebhookConfiguration) { for i := range in.Webhooks { a := &in.Webhooks[i] diff --git a/pkg/apis/admissionregistration/v1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1/zz_generated.validations.go new file mode 100644 index 0000000000000..fd858f722c7c7 --- /dev/null +++ b/pkg/apis/admissionregistration/v1/zz_generated.validations.go @@ -0,0 +1,125 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type ValidatingAdmissionPolicyBinding + scheme.AddValidationFunc((*admissionregistrationv1.ValidatingAdmissionPolicyBinding)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ValidatingAdmissionPolicyBinding(ctx, op, nil /* fldPath */, obj.(*admissionregistrationv1.ValidatingAdmissionPolicyBinding), safe.Cast[*admissionregistrationv1.ValidatingAdmissionPolicyBinding](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ValidatingAdmissionPolicyBinding validates an instance of ValidatingAdmissionPolicyBinding according +// to declarative validation rules in the API schema. +func Validate_ValidatingAdmissionPolicyBinding(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBinding) (errs field.ErrorList) { + // field admissionregistrationv1.ValidatingAdmissionPolicyBinding.TypeMeta has no validation + // field admissionregistrationv1.ValidatingAdmissionPolicyBinding.ObjectMeta has no validation + + // field admissionregistrationv1.ValidatingAdmissionPolicyBinding.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_ValidatingAdmissionPolicyBindingSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + return errs +} + +// Validate_ValidatingAdmissionPolicyBindingSpec validates an instance of ValidatingAdmissionPolicyBindingSpec according +// to declarative validation rules in the API schema. +func Validate_ValidatingAdmissionPolicyBindingSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec) (errs field.ErrorList) { + // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.PolicyName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("policyName"), &obj.PolicyName, safe.Field(oldObj, func(oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec) *string { + return &oldObj.PolicyName + }), oldObj != nil)...) + + // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.ParamRef has no validation + // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.MatchResources has no validation + + // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.ValidationActions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []admissionregistrationv1.ValidationAction, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("validationActions"), obj.ValidationActions, safe.Field(oldObj, func(oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec) []admissionregistrationv1.ValidationAction { + return oldObj.ValidationActions + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/admissionregistration/v1alpha1/doc.go b/pkg/apis/admissionregistration/v1alpha1/doc.go index 73f0360c0beb1..e017527e47424 100644 --- a/pkg/apis/admissionregistration/v1alpha1/doc.go +++ b/pkg/apis/admissionregistration/v1alpha1/doc.go @@ -19,6 +19,8 @@ limitations under the License. // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/admissionregistration/v1alpha1 // +groupName=admissionregistration.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/admissionregistration/v1alpha1 // Package v1alpha1 is the v1alpha1 version of the API. package v1alpha1 diff --git a/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go new file mode 100644 index 0000000000000..f5d734f52ec8a --- /dev/null +++ b/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go @@ -0,0 +1,125 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + fmt "fmt" + + admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type ValidatingAdmissionPolicyBinding + scheme.AddValidationFunc((*admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ValidatingAdmissionPolicyBinding(ctx, op, nil /* fldPath */, obj.(*admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding), safe.Cast[*admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ValidatingAdmissionPolicyBinding validates an instance of ValidatingAdmissionPolicyBinding according +// to declarative validation rules in the API schema. +func Validate_ValidatingAdmissionPolicyBinding(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) (errs field.ErrorList) { + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding.TypeMeta has no validation + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding.ObjectMeta has no validation + + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_ValidatingAdmissionPolicyBindingSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + return errs +} + +// Validate_ValidatingAdmissionPolicyBindingSpec validates an instance of ValidatingAdmissionPolicyBindingSpec according +// to declarative validation rules in the API schema. +func Validate_ValidatingAdmissionPolicyBindingSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec) (errs field.ErrorList) { + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.PolicyName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("policyName"), &obj.PolicyName, safe.Field(oldObj, func(oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec) *string { + return &oldObj.PolicyName + }), oldObj != nil)...) + + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.ParamRef has no validation + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.MatchResources has no validation + + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.ValidationActions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []admissionregistrationv1alpha1.ValidationAction, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("validationActions"), obj.ValidationActions, safe.Field(oldObj, func(oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec) []admissionregistrationv1alpha1.ValidationAction { + return oldObj.ValidationActions + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/admissionregistration/v1beta1/doc.go b/pkg/apis/admissionregistration/v1beta1/doc.go index 0afc7b4ae7e6c..400a34f65851c 100644 --- a/pkg/apis/admissionregistration/v1beta1/doc.go +++ b/pkg/apis/admissionregistration/v1beta1/doc.go @@ -19,6 +19,8 @@ limitations under the License. // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/admissionregistration/v1beta1 // +groupName=admissionregistration.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/admissionregistration/v1beta1 // Package v1beta1 is the v1beta1 version of the API. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration diff --git a/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go new file mode 100644 index 0000000000000..570613e351daa --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go @@ -0,0 +1,125 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type ValidatingAdmissionPolicyBinding + scheme.AddValidationFunc((*admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ValidatingAdmissionPolicyBinding(ctx, op, nil /* fldPath */, obj.(*admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding), safe.Cast[*admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ValidatingAdmissionPolicyBinding validates an instance of ValidatingAdmissionPolicyBinding according +// to declarative validation rules in the API schema. +func Validate_ValidatingAdmissionPolicyBinding(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding) (errs field.ErrorList) { + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding.TypeMeta has no validation + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding.ObjectMeta has no validation + + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_ValidatingAdmissionPolicyBindingSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + return errs +} + +// Validate_ValidatingAdmissionPolicyBindingSpec validates an instance of ValidatingAdmissionPolicyBindingSpec according +// to declarative validation rules in the API schema. +func Validate_ValidatingAdmissionPolicyBindingSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec) (errs field.ErrorList) { + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.PolicyName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("policyName"), &obj.PolicyName, safe.Field(oldObj, func(oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec) *string { + return &oldObj.PolicyName + }), oldObj != nil)...) + + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.ParamRef has no validation + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.MatchResources has no validation + + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.ValidationActions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []admissionregistrationv1beta1.ValidationAction, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("validationActions"), obj.ValidationActions, safe.Field(oldObj, func(oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec) []admissionregistrationv1beta1.ValidationAction { + return oldObj.ValidationActions + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/admissionregistration/validation/static_suffix.go b/pkg/apis/admissionregistration/validation/static_suffix.go new file mode 100644 index 0000000000000..7c147adbc29fe --- /dev/null +++ b/pkg/apis/admissionregistration/validation/static_suffix.go @@ -0,0 +1,53 @@ +/* +Copyright 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 validation + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// StaticConfigSuffix is the reserved suffix for manifest-based admission configurations. +// Resources with names ending in this suffix can only be created via static manifest +// files loaded at API server startup, not through the REST API. +// NOTE: This constant is duplicated in staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation.go +// because this package cannot import from staging. Keep both in sync. +const StaticConfigSuffix = ".static.k8s.io" + +// ValidateStaticSuffix validates that the name does not end with the reserved static suffix. +// When the ManifestBasedAdmissionControlConfig feature gate is enabled, returns an error +// if the name uses the reserved suffix. +func ValidateStaticSuffix(name string, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if strings.HasSuffix(name, StaticConfigSuffix) { + allErrors = append(allErrors, field.Invalid(fldPath, name, + fmt.Sprintf("names ending with %q are reserved for static manifest-based configurations and cannot be managed via the API", StaticConfigSuffix))) + } + return allErrors +} + +// WarningsForStaticSuffix returns warnings if the name uses the reserved static suffix. +func WarningsForStaticSuffix(name string) []string { + if strings.HasSuffix(name, StaticConfigSuffix) { + return []string{ + fmt.Sprintf("metadata.name: names ending with %q are reserved for static manifest-based configurations and cannot be managed via the API", StaticConfigSuffix), + } + } + return nil +} diff --git a/pkg/apis/admissionregistration/validation/static_suffix_test.go b/pkg/apis/admissionregistration/validation/static_suffix_test.go new file mode 100644 index 0000000000000..10a97a18209e9 --- /dev/null +++ b/pkg/apis/admissionregistration/validation/static_suffix_test.go @@ -0,0 +1,113 @@ +/* +Copyright 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 validation + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission/plugin/manifest" +) + +func TestValidateStaticSuffix(t *testing.T) { + tests := []struct { + name string + inputName string + wantError bool + }{ + { + name: "valid name without suffix", + inputName: "my-webhook", + wantError: false, + }, + { + name: "valid name with similar suffix", + inputName: "my-webhook.k8s.io", + wantError: false, + }, + { + name: "valid name with partial match", + inputName: "static.k8s.io.my-webhook", + wantError: false, + }, + { + name: "invalid name with static suffix", + inputName: "my-webhook.static.k8s.io", + wantError: true, + }, + { + name: "invalid name with only static suffix", + inputName: ".static.k8s.io", + wantError: true, + }, + { + name: "valid empty name", + inputName: "", + wantError: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + errs := ValidateStaticSuffix(tc.inputName, field.NewPath("metadata", "name")) + gotError := len(errs) > 0 + if gotError != tc.wantError { + t.Errorf("ValidateStaticSuffix(%q) = %v errors, want error: %v", tc.inputName, errs, tc.wantError) + } + }) + } +} + +func TestWarningsForStaticSuffix(t *testing.T) { + tests := []struct { + name string + inputName string + wantWarning bool + }{ + { + name: "valid name without suffix", + inputName: "my-webhook", + wantWarning: false, + }, + { + name: "name with static suffix", + inputName: "my-webhook.static.k8s.io", + wantWarning: true, + }, + { + name: "valid name with similar suffix", + inputName: "my-webhook.k8s.io", + wantWarning: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + warnings := WarningsForStaticSuffix(tc.inputName) + gotWarning := len(warnings) > 0 + if gotWarning != tc.wantWarning { + t.Errorf("WarningsForStaticSuffix(%q) = %v, want warning: %v", tc.inputName, warnings, tc.wantWarning) + } + }) + } +} + +func TestStaticConfigSuffixInSync(t *testing.T) { + if StaticConfigSuffix != manifest.StaticConfigSuffix { + t.Errorf("StaticConfigSuffix constants are out of sync: pkg=%q, staging=%q", StaticConfigSuffix, manifest.StaticConfigSuffix) + } +} diff --git a/pkg/apis/admissionregistration/validation/validation.go b/pkg/apis/admissionregistration/validation/validation.go index 815f51a497aea..d7197e5495368 100644 --- a/pkg/apis/admissionregistration/validation/validation.go +++ b/pkg/apis/admissionregistration/validation/validation.go @@ -24,8 +24,8 @@ import ( "sync" "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/validate/content" genericvalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/api/validation/path" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -328,12 +328,12 @@ func findMutatingPolicyPreexistingExpressions(mutatingPolicy *admissionregistrat for _, mc := range mutatingPolicy.Spec.MatchConditions { preexisting.matchConditionExpressions.Insert(mc.Expression) } - for _, v := range mutatingPolicy.Spec.Mutations { - if v.ApplyConfiguration != nil { - preexisting.applyConfigurationExpressions.Insert(v.ApplyConfiguration.Expression) + for _, m := range mutatingPolicy.Spec.Mutations { + if m.ApplyConfiguration != nil { + preexisting.applyConfigurationExpressions.Insert(m.ApplyConfiguration.Expression) } - if v.JSONPatch != nil { - preexisting.jsonPatchExpressions.Insert(v.JSONPatch.Expression) + if m.JSONPatch != nil { + preexisting.jsonPatchExpressions.Insert(m.JSONPatch.Expression) } } return preexisting @@ -939,7 +939,7 @@ func validateValidationActions(va []admissionregistration.ValidationAction, fldP allErrors = append(allErrors, field.Invalid(fldPath, va, "must not contain both Deny and Warn (repeating the same validation failure information in the API response and headers serves no purpose)")) } if len(actions) == 0 { - allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required")) + allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required").MarkCoveredByDeclarative()) } return allErrors } @@ -948,7 +948,7 @@ func validateNamedRuleWithOperations(n *admissionregistration.NamedRuleWithOpera var allErrors field.ErrorList resourceNames := sets.NewString() for i, rName := range n.ResourceNames { - for _, msg := range path.ValidatePathSegmentName(rName, false) { + for _, msg := range content.IsPathSegmentName(rName) { allErrors = append(allErrors, field.Invalid(fldPath.Child("resourceNames").Index(i), rName, msg)) } if resourceNames.Has(rName) { @@ -1182,7 +1182,7 @@ func validateValidatingAdmissionPolicyBindingSpec(spec *admissionregistration.Va var allErrors field.ErrorList if len(spec.PolicyName) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("policyName"), "")) + allErrors = append(allErrors, field.Required(fldPath.Child("policyName"), "").MarkCoveredByDeclarative()) } else { for _, msg := range genericvalidation.NameIsDNSSubdomain(spec.PolicyName, false) { allErrors = append(allErrors, field.Invalid(fldPath.Child("policyName"), spec.PolicyName, msg)) @@ -1202,7 +1202,7 @@ func validateParamRef(pr *admissionregistration.ParamRef, fldPath *field.Path) f } if len(pr.Name) > 0 { - for _, msg := range path.ValidatePathSegmentName(pr.Name, false) { + for _, msg := range content.IsPathSegmentName(pr.Name) { allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), pr.Name, msg)) } diff --git a/pkg/apis/apps/v1/defaults_test.go b/pkg/apis/apps/v1/defaults_test.go index 73935213e6141..880347879c701 100644 --- a/pkg/apis/apps/v1/defaults_test.go +++ b/pkg/apis/apps/v1/defaults_test.go @@ -595,7 +595,6 @@ func TestSetDefaultStatefulSet(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, test.enableMaxUnavailablePolicy) diff --git a/pkg/apis/apps/v1beta1/defaults_test.go b/pkg/apis/apps/v1beta1/defaults_test.go index 245d81aa2a827..347f871fa0b56 100644 --- a/pkg/apis/apps/v1beta1/defaults_test.go +++ b/pkg/apis/apps/v1beta1/defaults_test.go @@ -492,7 +492,6 @@ func TestSetDefaultStatefulSet(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, test.enableMaxUnavailablePolicy) obj2 := roundTrip(t, runtime.Object(test.original)) diff --git a/pkg/apis/apps/v1beta1/zz_generated.validations.go b/pkg/apis/apps/v1beta1/zz_generated.validations.go index cc6f631c8e2a8..581690d4f196c 100644 --- a/pkg/apis/apps/v1beta1/zz_generated.validations.go +++ b/pkg/apis/apps/v1beta1/zz_generated.validations.go @@ -77,13 +77,12 @@ func Validate_ScaleSpec(ctx context.Context, op operation.Operation, fldPath *fi // field appsv1beta1.ScaleSpec.Replicas errs = append(errs, func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { - // optional value-type fields with zero-value defaults are purely documentation // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } // call field-attached validations - errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha()...) return }(fldPath.Child("replicas"), &obj.Replicas, safe.Field(oldObj, func(oldObj *appsv1beta1.ScaleSpec) *int32 { return &oldObj.Replicas }), oldObj != nil)...) diff --git a/pkg/apis/apps/v1beta2/defaults_test.go b/pkg/apis/apps/v1beta2/defaults_test.go index 6cfc54c8e9ba5..58aac3cf940d9 100644 --- a/pkg/apis/apps/v1beta2/defaults_test.go +++ b/pkg/apis/apps/v1beta2/defaults_test.go @@ -467,7 +467,6 @@ func TestSetDefaultStatefulSet(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, test.enableMaxUnavailablePolicy) obj2 := roundTrip(t, runtime.Object(test.original)) diff --git a/pkg/apis/apps/v1beta2/zz_generated.validations.go b/pkg/apis/apps/v1beta2/zz_generated.validations.go index 0499add423c40..c87c8c3ad0a8f 100644 --- a/pkg/apis/apps/v1beta2/zz_generated.validations.go +++ b/pkg/apis/apps/v1beta2/zz_generated.validations.go @@ -77,13 +77,12 @@ func Validate_ScaleSpec(ctx context.Context, op operation.Operation, fldPath *fi // field appsv1beta2.ScaleSpec.Replicas errs = append(errs, func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { - // optional value-type fields with zero-value defaults are purely documentation // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } // call field-attached validations - errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha()...) return }(fldPath.Child("replicas"), &obj.Replicas, safe.Field(oldObj, func(oldObj *appsv1beta2.ScaleSpec) *int32 { return &oldObj.Replicas }), oldObj != nil)...) diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index c1dc227a736a6..3303575955852 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -28,7 +28,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kubernetes/pkg/api/pod" @@ -36,6 +35,7 @@ import ( "k8s.io/kubernetes/pkg/apis/apps" api "k8s.io/kubernetes/pkg/apis/core" corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/utils/dump" "k8s.io/utils/ptr" ) diff --git a/pkg/apis/autoscaling/annotations.go b/pkg/apis/autoscaling/annotations.go index d793cd4aa3831..0df01c14386e7 100644 --- a/pkg/apis/autoscaling/annotations.go +++ b/pkg/apis/autoscaling/annotations.go @@ -17,19 +17,19 @@ limitations under the License. package autoscaling // MetricSpecsAnnotation is the annotation which holds non-CPU-utilization HPA metric -// specs when converting the `Metrics` field from autoscaling/v2beta1 +// specs when converting the `Metrics` field from autoscaling/v2 const MetricSpecsAnnotation = "autoscaling.alpha.kubernetes.io/metrics" // MetricStatusesAnnotation is the annotation which holds non-CPU-utilization HPA metric -// statuses when converting the `CurrentMetrics` field from autoscaling/v2beta1 +// statuses when converting the `CurrentMetrics` field from autoscaling/v2 const MetricStatusesAnnotation = "autoscaling.alpha.kubernetes.io/current-metrics" // HorizontalPodAutoscalerConditionsAnnotation is the annotation which holds the conditions -// of an HPA when converting the `Conditions` field from autoscaling/v2beta1 +// of an HPA when converting the `Conditions` field from autoscaling/v2 const HorizontalPodAutoscalerConditionsAnnotation = "autoscaling.alpha.kubernetes.io/conditions" // DefaultCPUUtilization is the default value for CPU utilization, provided no other -// metrics are present. This is here because it's used by both the v2beta1 defaulting +// metrics are present. This is here because it's used by both the v2 defaulting // logic, and the pseudo-defaulting done in v1 conversion. const DefaultCPUUtilization = 80 diff --git a/pkg/apis/autoscaling/install/install.go b/pkg/apis/autoscaling/install/install.go index 424fc5ce85d37..144f50eb44696 100644 --- a/pkg/apis/autoscaling/install/install.go +++ b/pkg/apis/autoscaling/install/install.go @@ -25,8 +25,6 @@ import ( "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/autoscaling/v1" "k8s.io/kubernetes/pkg/apis/autoscaling/v2" - "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1" - "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2" ) func init() { @@ -36,9 +34,7 @@ func init() { // Install registers the API group and adds types to a scheme func Install(scheme *runtime.Scheme) { utilruntime.Must(autoscaling.AddToScheme(scheme)) - utilruntime.Must(v2beta2.AddToScheme(scheme)) utilruntime.Must(v2.AddToScheme(scheme)) - utilruntime.Must(v2beta1.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) - utilruntime.Must(scheme.SetVersionPriority(v2.SchemeGroupVersion, v1.SchemeGroupVersion, v2beta1.SchemeGroupVersion, v2beta2.SchemeGroupVersion)) + utilruntime.Must(scheme.SetVersionPriority(v2.SchemeGroupVersion, v1.SchemeGroupVersion)) } diff --git a/pkg/apis/autoscaling/types.go b/pkg/apis/autoscaling/types.go index b8802a992b2af..dfbc0474d9982 100644 --- a/pkg/apis/autoscaling/types.go +++ b/pkg/apis/autoscaling/types.go @@ -433,6 +433,8 @@ const ( // ScalingLimited indicates that the calculated scale based on metrics would be above or // below the range for the HPA, and has thus been capped. ScalingLimited HorizontalPodAutoscalerConditionType = "ScalingLimited" + // ScaledToZero indicates that the HPA controller scaled the workload to zero. + ScaledToZero HorizontalPodAutoscalerConditionType = "ScaledToZero" ) // HorizontalPodAutoscalerCondition describes the state of diff --git a/pkg/apis/autoscaling/v1/conversion.go b/pkg/apis/autoscaling/v1/conversion.go index ded80ce8e3621..5c91ab3668fc3 100644 --- a/pkg/apis/autoscaling/v1/conversion.go +++ b/pkg/apis/autoscaling/v1/conversion.go @@ -22,6 +22,7 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/conversion" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/core" @@ -82,15 +83,22 @@ func Convert_autoscaling_ObjectMetricSource_To_v1_ObjectMetricSource(in *autosca func Convert_v1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in *autoscalingv1.ObjectMetricSource, out *autoscaling.ObjectMetricSource, s conversion.Scope) error { var metricType autoscaling.MetricTargetType + var targetValue *resource.Quantity if in.AverageValue == nil { metricType = autoscaling.ValueMetricType + targetValue = &in.TargetValue } else { metricType = autoscaling.AverageValueMetricType + // Only preserve non-zero targetValue for averageValue metrics. + // The v1 type cannot omit the value field, and serializes value:"0", which fails validation on round-trip. + if !in.TargetValue.IsZero() { + targetValue = &in.TargetValue + } } out.Target = autoscaling.MetricTarget{ Type: metricType, - Value: &in.TargetValue, + Value: targetValue, AverageValue: in.AverageValue, } out.DescribedObject = autoscaling.CrossVersionObjectReference{ diff --git a/pkg/apis/autoscaling/v1/zz_generated.validations.go b/pkg/apis/autoscaling/v1/zz_generated.validations.go index cfb0a3cd4b3c3..1c3a27da4a354 100644 --- a/pkg/apis/autoscaling/v1/zz_generated.validations.go +++ b/pkg/apis/autoscaling/v1/zz_generated.validations.go @@ -26,6 +26,7 @@ import ( fmt "fmt" autoscalingv1 "k8s.io/api/autoscaling/v1" + equality "k8s.io/apimachinery/pkg/api/equality" operation "k8s.io/apimachinery/pkg/api/operation" safe "k8s.io/apimachinery/pkg/api/safe" validate "k8s.io/apimachinery/pkg/api/validate" @@ -38,6 +39,14 @@ func init() { localSchemeBuilder.Register(RegisterValidations) } // RegisterValidations adds validation functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterValidations(scheme *runtime.Scheme) error { + // type HorizontalPodAutoscaler + scheme.AddValidationFunc((*autoscalingv1.HorizontalPodAutoscaler)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_HorizontalPodAutoscaler(ctx, op, nil /* fldPath */, obj.(*autoscalingv1.HorizontalPodAutoscaler), safe.Cast[*autoscalingv1.HorizontalPodAutoscaler](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type Scale scheme.AddValidationFunc((*autoscalingv1.Scale)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -49,6 +58,83 @@ func RegisterValidations(scheme *runtime.Scheme) error { return nil } +// Validate_HorizontalPodAutoscaler validates an instance of HorizontalPodAutoscaler according +// to declarative validation rules in the API schema. +func Validate_HorizontalPodAutoscaler(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *autoscalingv1.HorizontalPodAutoscaler) (errs field.ErrorList) { + // field autoscalingv1.HorizontalPodAutoscaler.TypeMeta has no validation + // field autoscalingv1.HorizontalPodAutoscaler.ObjectMeta has no validation + + // field autoscalingv1.HorizontalPodAutoscaler.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *autoscalingv1.HorizontalPodAutoscalerSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_HorizontalPodAutoscalerSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *autoscalingv1.HorizontalPodAutoscaler) *autoscalingv1.HorizontalPodAutoscalerSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field autoscalingv1.HorizontalPodAutoscaler.Status has no validation + return errs +} + +// Validate_HorizontalPodAutoscalerSpec validates an instance of HorizontalPodAutoscalerSpec according +// to declarative validation rules in the API schema. +func Validate_HorizontalPodAutoscalerSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *autoscalingv1.HorizontalPodAutoscalerSpec) (errs field.ErrorList) { + // field autoscalingv1.HorizontalPodAutoscalerSpec.ScaleTargetRef has no validation + + // field autoscalingv1.HorizontalPodAutoscalerSpec.MinReplicas + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 0) + }).MarkAlpha()...) + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", false, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 1) + }).MarkAlpha()...) + return + }(fldPath.Child("minReplicas"), obj.MinReplicas, safe.Field(oldObj, func(oldObj *autoscalingv1.HorizontalPodAutoscalerSpec) *int32 { return oldObj.MinReplicas }), oldObj != nil)...) + + // field autoscalingv1.HorizontalPodAutoscalerSpec.MaxReplicas + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 1).MarkAlpha()...) + return + }(fldPath.Child("maxReplicas"), &obj.MaxReplicas, safe.Field(oldObj, func(oldObj *autoscalingv1.HorizontalPodAutoscalerSpec) *int32 { return &oldObj.MaxReplicas }), oldObj != nil)...) + + // field autoscalingv1.HorizontalPodAutoscalerSpec.TargetCPUUtilizationPercentage has no validation + return errs +} + // Validate_Scale validates an instance of Scale according // to declarative validation rules in the API schema. func Validate_Scale(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *autoscalingv1.Scale) (errs field.ErrorList) { @@ -77,13 +163,12 @@ func Validate_ScaleSpec(ctx context.Context, op operation.Operation, fldPath *fi // field autoscalingv1.ScaleSpec.Replicas errs = append(errs, func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { - // optional value-type fields with zero-value defaults are purely documentation // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } // call field-attached validations - errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha()...) return }(fldPath.Child("replicas"), &obj.Replicas, safe.Field(oldObj, func(oldObj *autoscalingv1.ScaleSpec) *int32 { return &oldObj.Replicas }), oldObj != nil)...) diff --git a/pkg/apis/autoscaling/v2/conversion_test.go b/pkg/apis/autoscaling/v2/conversion_test.go new file mode 100644 index 0000000000000..35ba9952498c2 --- /dev/null +++ b/pkg/apis/autoscaling/v2/conversion_test.go @@ -0,0 +1,107 @@ +/* +Copyright 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 v2 + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + autoscalingv1 "k8s.io/api/autoscaling/v1" + autoscalingv2 "k8s.io/api/autoscaling/v2" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/autoscaling" + v1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + "k8s.io/kubernetes/pkg/apis/autoscaling/validation" + "k8s.io/utils/ptr" +) + +// This test ensures that when converting an HPA with an object metric with averageValue +// from v2 to v1 and back to v2, the value is not defaulted back to zero, causing validation errors. +func TestObjectMetricAverageValue_RoundTripV2V1(t *testing.T) { + var err error + + // Construct a v2 HPA object metric, and set to averageValue with a non-zero value. + hpaV2 := &autoscalingv2.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-hpa", + Namespace: "default", + }, + Spec: autoscalingv2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "my-deployment", + APIVersion: "apps/v1", + }, + MinReplicas: ptr.To[int32](1), + MaxReplicas: 3, + Metrics: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ObjectMetricSourceType, + Object: &autoscalingv2.ObjectMetricSource{ + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.AverageValueMetricType, + AverageValue: ptr.To(resource.MustParse("100")), + }, + Metric: autoscalingv2.MetricIdentifier{ + Name: "requests-per-second", + }, + DescribedObject: autoscalingv2.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "my-deployment", + APIVersion: "apps/v1"}, + }, + }, + }, + }, + } + + // Convert from v2 to internal HPA and ensure that no validation errors are produced + hpaInternal := &autoscaling.HorizontalPodAutoscaler{} + err = Convert_v2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(hpaV2, hpaInternal, nil) + assert.NoError(t, err, "Conversion to internal should not fail") + + validationErrors := validation.ValidateHorizontalPodAutoscaler(hpaInternal, validation.HorizontalPodAutoscalerSpecValidationOptions{}) + assert.Zero(t, len(validationErrors), "Validation should not produce errors") + + // Convert internal HPA to v1 HPA + hpaV1 := &autoscalingv1.HorizontalPodAutoscaler{} + err = v1.Convert_autoscaling_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(hpaInternal, hpaV1, nil) + assert.NoError(t, err, "Conversion to v1 should not fail") + + // Serialise hpaV1 to JSON + jsonData, err := json.Marshal(hpaV1) + assert.NoError(t, err, "JSON marshalling should not fail") + + // Unmarshal json data to a new v1.HorizontalPodAutoscaler object + var hpaV1FromJSON autoscalingv1.HorizontalPodAutoscaler + err = json.Unmarshal(jsonData, &hpaV1FromJSON) + assert.NoError(t, err, "JSON unmarshalling should not fail") + + // Convert to internal from JSON-unmarshalled v1 HPA, and ensure that no validation errors are produced + hpaInternalFromV1 := &autoscaling.HorizontalPodAutoscaler{} + err = v1.Convert_v1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(&hpaV1FromJSON, hpaInternalFromV1, nil) + assert.NoError(t, err, "Conversion from v1 should not fail") + + validationErrors = validation.ValidateHorizontalPodAutoscaler(hpaInternalFromV1, validation.HorizontalPodAutoscalerSpecValidationOptions{}) + assert.Zero(t, len(validationErrors), "Validation should not produce errors") +} diff --git a/pkg/apis/autoscaling/v2/doc.go b/pkg/apis/autoscaling/v2/doc.go index c21980b3307a3..a23251b47595d 100644 --- a/pkg/apis/autoscaling/v2/doc.go +++ b/pkg/apis/autoscaling/v2/doc.go @@ -18,5 +18,7 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/autoscaling/v2 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/autoscaling/v2 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/autoscaling/v2 package v2 diff --git a/pkg/apis/autoscaling/v2/zz_generated.validations.go b/pkg/apis/autoscaling/v2/zz_generated.validations.go new file mode 100644 index 0000000000000..7b9aed557a8d7 --- /dev/null +++ b/pkg/apis/autoscaling/v2/zz_generated.validations.go @@ -0,0 +1,129 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v2 + +import ( + context "context" + fmt "fmt" + + autoscalingv2 "k8s.io/api/autoscaling/v2" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type HorizontalPodAutoscaler + scheme.AddValidationFunc((*autoscalingv2.HorizontalPodAutoscaler)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_HorizontalPodAutoscaler(ctx, op, nil /* fldPath */, obj.(*autoscalingv2.HorizontalPodAutoscaler), safe.Cast[*autoscalingv2.HorizontalPodAutoscaler](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_HorizontalPodAutoscaler validates an instance of HorizontalPodAutoscaler according +// to declarative validation rules in the API schema. +func Validate_HorizontalPodAutoscaler(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *autoscalingv2.HorizontalPodAutoscaler) (errs field.ErrorList) { + // field autoscalingv2.HorizontalPodAutoscaler.TypeMeta has no validation + // field autoscalingv2.HorizontalPodAutoscaler.ObjectMeta has no validation + + // field autoscalingv2.HorizontalPodAutoscaler.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *autoscalingv2.HorizontalPodAutoscalerSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_HorizontalPodAutoscalerSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *autoscalingv2.HorizontalPodAutoscaler) *autoscalingv2.HorizontalPodAutoscalerSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field autoscalingv2.HorizontalPodAutoscaler.Status has no validation + return errs +} + +// Validate_HorizontalPodAutoscalerSpec validates an instance of HorizontalPodAutoscalerSpec according +// to declarative validation rules in the API schema. +func Validate_HorizontalPodAutoscalerSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *autoscalingv2.HorizontalPodAutoscalerSpec) (errs field.ErrorList) { + // field autoscalingv2.HorizontalPodAutoscalerSpec.ScaleTargetRef has no validation + + // field autoscalingv2.HorizontalPodAutoscalerSpec.MinReplicas + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 0) + }).MarkAlpha()...) + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", false, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 1) + }).MarkAlpha()...) + return + }(fldPath.Child("minReplicas"), obj.MinReplicas, safe.Field(oldObj, func(oldObj *autoscalingv2.HorizontalPodAutoscalerSpec) *int32 { return oldObj.MinReplicas }), oldObj != nil)...) + + // field autoscalingv2.HorizontalPodAutoscalerSpec.MaxReplicas + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 1).MarkAlpha()...) + return + }(fldPath.Child("maxReplicas"), &obj.MaxReplicas, safe.Field(oldObj, func(oldObj *autoscalingv2.HorizontalPodAutoscalerSpec) *int32 { return &oldObj.MaxReplicas }), oldObj != nil)...) + + // field autoscalingv2.HorizontalPodAutoscalerSpec.Metrics has no validation + // field autoscalingv2.HorizontalPodAutoscalerSpec.Behavior has no validation + return errs +} diff --git a/pkg/apis/autoscaling/v2beta1/conversion.go b/pkg/apis/autoscaling/v2beta1/conversion.go deleted file mode 100644 index 513b41a2a3ee8..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/conversion.go +++ /dev/null @@ -1,360 +0,0 @@ -/* -Copyright 2018 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 v2beta1 - -import ( - "encoding/json" - - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/kubernetes/pkg/apis/autoscaling" - core "k8s.io/kubernetes/pkg/apis/core" -) - -func Convert_autoscaling_MetricTarget_To_v2beta1_CrossVersionObjectReference(in *autoscaling.MetricTarget, out *autoscalingv2beta1.CrossVersionObjectReference, s conversion.Scope) error { - return nil -} - -func Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_MetricTarget(in *autoscalingv2beta1.CrossVersionObjectReference, out *autoscaling.MetricTarget, s conversion.Scope) error { - return nil -} - -func Convert_v2beta1_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(in *autoscalingv2beta1.ContainerResourceMetricStatus, out *autoscaling.ContainerResourceMetricStatus, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - out.Container = in.Container - utilization := in.CurrentAverageUtilization - averageValue := in.CurrentAverageValue - out.Current = autoscaling.MetricValueStatus{ - AverageValue: &averageValue, - AverageUtilization: utilization, - } - return nil -} - -func Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta1_ContainerResourceMetricStatus(in *autoscaling.ContainerResourceMetricStatus, out *autoscalingv2beta1.ContainerResourceMetricStatus, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.Container = in.Container - out.CurrentAverageUtilization = in.Current.AverageUtilization - if in.Current.AverageValue != nil { - out.CurrentAverageValue = *in.Current.AverageValue - } - return nil -} - -func Convert_v2beta1_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in *autoscalingv2beta1.ResourceMetricStatus, out *autoscaling.ResourceMetricStatus, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - utilization := in.CurrentAverageUtilization - averageValue := in.CurrentAverageValue - out.Current = autoscaling.MetricValueStatus{ - AverageValue: &averageValue, - AverageUtilization: utilization, - } - return nil -} - -func Convert_autoscaling_ResourceMetricStatus_To_v2beta1_ResourceMetricStatus(in *autoscaling.ResourceMetricStatus, out *autoscalingv2beta1.ResourceMetricStatus, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.CurrentAverageUtilization = in.Current.AverageUtilization - if in.Current.AverageValue != nil { - out.CurrentAverageValue = *in.Current.AverageValue - } - return nil -} - -func Convert_v2beta1_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in *autoscalingv2beta1.ResourceMetricSource, out *autoscaling.ResourceMetricSource, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - utilization := in.TargetAverageUtilization - averageValue := in.TargetAverageValue - - var metricType autoscaling.MetricTargetType - if utilization == nil { - metricType = autoscaling.AverageValueMetricType - } else { - metricType = autoscaling.UtilizationMetricType - } - out.Target = autoscaling.MetricTarget{ - Type: metricType, - AverageValue: averageValue, - AverageUtilization: utilization, - } - return nil -} - -func Convert_autoscaling_ResourceMetricSource_To_v2beta1_ResourceMetricSource(in *autoscaling.ResourceMetricSource, out *autoscalingv2beta1.ResourceMetricSource, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.TargetAverageUtilization = in.Target.AverageUtilization - out.TargetAverageValue = in.Target.AverageValue - return nil -} - -func Convert_autoscaling_ExternalMetricSource_To_v2beta1_ExternalMetricSource(in *autoscaling.ExternalMetricSource, out *autoscalingv2beta1.ExternalMetricSource, s conversion.Scope) error { - out.MetricName = in.Metric.Name - out.TargetValue = in.Target.Value - out.TargetAverageValue = in.Target.AverageValue - out.MetricSelector = in.Metric.Selector - return nil -} - -func Convert_v2beta1_ExternalMetricSource_To_autoscaling_ExternalMetricSource(in *autoscalingv2beta1.ExternalMetricSource, out *autoscaling.ExternalMetricSource, s conversion.Scope) error { - value := in.TargetValue - averageValue := in.TargetAverageValue - - var metricType autoscaling.MetricTargetType - if value == nil { - metricType = autoscaling.AverageValueMetricType - } else { - metricType = autoscaling.ValueMetricType - } - - out.Target = autoscaling.MetricTarget{ - Type: metricType, - Value: value, - AverageValue: averageValue, - } - - out.Metric = autoscaling.MetricIdentifier{ - Name: in.MetricName, - Selector: in.MetricSelector, - } - return nil -} - -func Convert_autoscaling_ObjectMetricSource_To_v2beta1_ObjectMetricSource(in *autoscaling.ObjectMetricSource, out *autoscalingv2beta1.ObjectMetricSource, s conversion.Scope) error { - if in.Target.Value != nil { - out.TargetValue = *in.Target.Value - } - out.AverageValue = in.Target.AverageValue - - out.Target = autoscalingv2beta1.CrossVersionObjectReference{ - Kind: in.DescribedObject.Kind, - Name: in.DescribedObject.Name, - APIVersion: in.DescribedObject.APIVersion, - } - out.MetricName = in.Metric.Name - out.Selector = in.Metric.Selector - - return nil -} - -func Convert_v2beta1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in *autoscalingv2beta1.ObjectMetricSource, out *autoscaling.ObjectMetricSource, s conversion.Scope) error { - var metricType autoscaling.MetricTargetType - if in.AverageValue == nil { - metricType = autoscaling.ValueMetricType - } else { - metricType = autoscaling.AverageValueMetricType - } - out.Target = autoscaling.MetricTarget{ - Type: metricType, - Value: &in.TargetValue, - AverageValue: in.AverageValue, - } - out.DescribedObject = autoscaling.CrossVersionObjectReference{ - Kind: in.Target.Kind, - Name: in.Target.Name, - APIVersion: in.Target.APIVersion, - } - out.Metric = autoscaling.MetricIdentifier{ - Name: in.MetricName, - Selector: in.Selector, - } - return nil -} - -func Convert_autoscaling_PodsMetricSource_To_v2beta1_PodsMetricSource(in *autoscaling.PodsMetricSource, out *autoscalingv2beta1.PodsMetricSource, s conversion.Scope) error { - if in.Target.AverageValue != nil { - targetAverageValue := *in.Target.AverageValue - out.TargetAverageValue = targetAverageValue - } - - out.MetricName = in.Metric.Name - out.Selector = in.Metric.Selector - - return nil -} - -func Convert_v2beta1_PodsMetricSource_To_autoscaling_PodsMetricSource(in *autoscalingv2beta1.PodsMetricSource, out *autoscaling.PodsMetricSource, s conversion.Scope) error { - targetAverageValue := &in.TargetAverageValue - metricType := autoscaling.AverageValueMetricType - - out.Target = autoscaling.MetricTarget{ - Type: metricType, - AverageValue: targetAverageValue, - } - out.Metric = autoscaling.MetricIdentifier{ - Name: in.MetricName, - Selector: in.Selector, - } - return nil -} - -func Convert_autoscaling_ExternalMetricStatus_To_v2beta1_ExternalMetricStatus(in *autoscaling.ExternalMetricStatus, out *autoscalingv2beta1.ExternalMetricStatus, s conversion.Scope) error { - out.CurrentAverageValue = in.Current.AverageValue - out.MetricName = in.Metric.Name - if in.Current.Value != nil { - out.CurrentValue = *in.Current.Value - } - out.MetricSelector = in.Metric.Selector - return nil -} - -func Convert_v2beta1_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(in *autoscalingv2beta1.ExternalMetricStatus, out *autoscaling.ExternalMetricStatus, s conversion.Scope) error { - value := in.CurrentValue - averageValue := in.CurrentAverageValue - out.Current = autoscaling.MetricValueStatus{ - Value: &value, - AverageValue: averageValue, - } - out.Metric = autoscaling.MetricIdentifier{ - Name: in.MetricName, - Selector: in.MetricSelector, - } - return nil -} - -func Convert_autoscaling_ObjectMetricStatus_To_v2beta1_ObjectMetricStatus(in *autoscaling.ObjectMetricStatus, out *autoscalingv2beta1.ObjectMetricStatus, s conversion.Scope) error { - if in.Current.Value != nil { - out.CurrentValue = *in.Current.Value - } - out.Target = autoscalingv2beta1.CrossVersionObjectReference{ - Kind: in.DescribedObject.Kind, - Name: in.DescribedObject.Name, - APIVersion: in.DescribedObject.APIVersion, - } - out.MetricName = in.Metric.Name - out.Selector = in.Metric.Selector - if in.Current.AverageValue != nil { - currentAverageValue := *in.Current.AverageValue - out.AverageValue = ¤tAverageValue - } - return nil -} - -func Convert_v2beta1_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(in *autoscalingv2beta1.ObjectMetricStatus, out *autoscaling.ObjectMetricStatus, s conversion.Scope) error { - out.Current = autoscaling.MetricValueStatus{ - Value: &in.CurrentValue, - AverageValue: in.AverageValue, - } - out.DescribedObject = autoscaling.CrossVersionObjectReference{ - Kind: in.Target.Kind, - Name: in.Target.Name, - APIVersion: in.Target.APIVersion, - } - out.Metric = autoscaling.MetricIdentifier{ - Name: in.MetricName, - Selector: in.Selector, - } - return nil -} - -func Convert_autoscaling_PodsMetricStatus_To_v2beta1_PodsMetricStatus(in *autoscaling.PodsMetricStatus, out *autoscalingv2beta1.PodsMetricStatus, s conversion.Scope) error { - if in.Current.AverageValue != nil { - out.CurrentAverageValue = *in.Current.AverageValue - } - out.MetricName = in.Metric.Name - out.Selector = in.Metric.Selector - return nil -} - -func Convert_v2beta1_PodsMetricStatus_To_autoscaling_PodsMetricStatus(in *autoscalingv2beta1.PodsMetricStatus, out *autoscaling.PodsMetricStatus, s conversion.Scope) error { - out.Current = autoscaling.MetricValueStatus{ - AverageValue: &in.CurrentAverageValue, - } - out.Metric = autoscaling.MetricIdentifier{ - Name: in.MetricName, - Selector: in.Selector, - } - return nil -} - -func Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *autoscalingv2beta1.HorizontalPodAutoscaler, s conversion.Scope) error { - if err := autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in, out, s); err != nil { - return err - } - - // clear any pre-existing round-trip annotations to make sure the only ones set are ones we produced during conversion - annotations, copiedAnnotations := autoscaling.DropRoundTripHorizontalPodAutoscalerAnnotations(out.Annotations) - out.Annotations = annotations - - if in.Spec.Behavior != nil { - // TODO: this is marshaling an internal type. Fix this without breaking backwards compatibility with n-1 API servers. - behaviorEnc, err := json.Marshal(in.Spec.Behavior) - if err != nil { - return err - } - // copy before mutating - if !copiedAnnotations { - //nolint:ineffassign - copiedAnnotations = true - out.Annotations = autoscaling.DeepCopyStringMap(out.Annotations) - } - out.Annotations[autoscaling.BehaviorSpecsAnnotation] = string(behaviorEnc) - } - - return nil -} - -func Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *autoscalingv2beta1.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error { - if err := autoConvert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in, out, s); err != nil { - return err - } - - if behaviorEnc, hasBehaviors := out.Annotations[autoscaling.BehaviorSpecsAnnotation]; hasBehaviors { - // TODO: this is unmarshaling an internal type. Fix this without breaking backwards compatibility with n-1 API servers. - var behavior autoscaling.HorizontalPodAutoscalerBehavior - if err := json.Unmarshal([]byte(behaviorEnc), &behavior); err == nil && behavior != (autoscaling.HorizontalPodAutoscalerBehavior{}) { - // only move well-formed data from annotations to fields - out.Spec.Behavior = &behavior - } - } - - // drop round-tripping annotations after converting to internal - out.Annotations, _ = autoscaling.DropRoundTripHorizontalPodAutoscalerAnnotations(out.Annotations) - - return nil -} - -func Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in *autoscaling.HorizontalPodAutoscalerSpec, out *autoscalingv2beta1.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in, out, s) -} - -func Convert_v2beta1_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(in *autoscalingv2beta1.ContainerResourceMetricSource, out *autoscaling.ContainerResourceMetricSource, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - utilization := in.TargetAverageUtilization - averageValue := in.TargetAverageValue - - var metricType autoscaling.MetricTargetType - if utilization == nil { - metricType = autoscaling.AverageValueMetricType - } else { - metricType = autoscaling.UtilizationMetricType - } - out.Target = autoscaling.MetricTarget{ - Type: metricType, - AverageValue: averageValue, - AverageUtilization: utilization, - } - return nil -} - -func Convert_autoscaling_ContainerResourceMetricSource_To_v2beta1_ContainerResourceMetricSource(in *autoscaling.ContainerResourceMetricSource, out *autoscalingv2beta1.ContainerResourceMetricSource, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.TargetAverageUtilization = in.Target.AverageUtilization - out.TargetAverageValue = in.Target.AverageValue - return nil -} diff --git a/pkg/apis/autoscaling/v2beta1/conversion_test.go b/pkg/apis/autoscaling/v2beta1/conversion_test.go deleted file mode 100644 index 52de8300ebbe0..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/conversion_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -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. -*/ - -package v2beta1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "k8s.io/api/autoscaling/v2beta1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/apis/autoscaling" -) - -// Testing nil pointer panic uncovered by #70806 -// TODO(yue9944882): Test nil/empty conversion across all resource types -func TestNilOrEmptyConversion(t *testing.T) { - scheme := runtime.NewScheme() - assert.NoError(t, RegisterConversions(scheme)) - - testCases := []struct { - obj1 interface{} - obj2 interface{} - }{ - { - obj1: &autoscaling.ExternalMetricSource{}, - obj2: &v2beta1.ExternalMetricSource{}, - }, - { - obj1: &autoscaling.ExternalMetricStatus{}, - obj2: &v2beta1.ExternalMetricStatus{}, - }, - { - obj1: &autoscaling.PodsMetricSource{}, - obj2: &v2beta1.PodsMetricSource{}, - }, - { - obj1: &autoscaling.PodsMetricStatus{}, - obj2: &v2beta1.PodsMetricStatus{}, - }, - { - obj1: &autoscaling.ObjectMetricSource{}, - obj2: &v2beta1.ObjectMetricSource{}, - }, - { - obj1: &autoscaling.ObjectMetricStatus{}, - obj2: &v2beta1.ObjectMetricStatus{}, - }, - { - obj1: &autoscaling.ResourceMetricSource{}, - obj2: &v2beta1.ResourceMetricSource{}, - }, - { - obj1: &autoscaling.ResourceMetricStatus{}, - obj2: &v2beta1.ResourceMetricStatus{}, - }, - { - obj1: &autoscaling.HorizontalPodAutoscaler{}, - obj2: &v2beta1.HorizontalPodAutoscaler{}, - }, - { - obj1: &autoscaling.MetricTarget{}, - obj2: &v2beta1.CrossVersionObjectReference{}, - }, - } - for _, testCase := range testCases { - assert.NoError(t, scheme.Convert(testCase.obj1, testCase.obj2, nil)) - assert.NoError(t, scheme.Convert(testCase.obj2, testCase.obj1, nil)) - } -} diff --git a/pkg/apis/autoscaling/v2beta1/defaults.go b/pkg/apis/autoscaling/v2beta1/defaults.go deleted file mode 100644 index fe532c6e2acb3..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/defaults.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2016 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 v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/apis/autoscaling" - "k8s.io/utils/ptr" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -func SetDefaults_HorizontalPodAutoscaler(obj *autoscalingv2beta1.HorizontalPodAutoscaler) { - if obj.Spec.MinReplicas == nil { - obj.Spec.MinReplicas = ptr.To[int32](1) - } - - if len(obj.Spec.Metrics) == 0 { - utilizationDefaultVal := int32(autoscaling.DefaultCPUUtilization) - obj.Spec.Metrics = []autoscalingv2beta1.MetricSpec{ - { - Type: autoscalingv2beta1.ResourceMetricSourceType, - Resource: &autoscalingv2beta1.ResourceMetricSource{ - Name: v1.ResourceCPU, - TargetAverageUtilization: &utilizationDefaultVal, - }, - }, - } - } -} diff --git a/pkg/apis/autoscaling/v2beta1/defaults_test.go b/pkg/apis/autoscaling/v2beta1/defaults_test.go deleted file mode 100644 index a9eef3f691692..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/defaults_test.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -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. -*/ - -package v2beta1_test - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v1 "k8s.io/api/core/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/apis/autoscaling" - _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" - . "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1" - _ "k8s.io/kubernetes/pkg/apis/core/install" - "k8s.io/utils/ptr" -) - -func TestSetDefaultHPA(t *testing.T) { - utilizationDefaultVal := int32(autoscaling.DefaultCPUUtilization) - defaultReplicas := ptr.To[int32](1) - defaultTemplate := []autoscalingv2beta1.MetricSpec{ - { - Type: autoscalingv2beta1.ResourceMetricSourceType, - Resource: &autoscalingv2beta1.ResourceMetricSource{ - Name: v1.ResourceCPU, - TargetAverageUtilization: &utilizationDefaultVal, - }, - }, - } - - tests := []struct { - original *autoscalingv2beta1.HorizontalPodAutoscaler - expected *autoscalingv2beta1.HorizontalPodAutoscaler - }{ - { // MinReplicas default value - original: &autoscalingv2beta1.HorizontalPodAutoscaler{ - Spec: autoscalingv2beta1.HorizontalPodAutoscalerSpec{ - Metrics: defaultTemplate, - }, - }, - expected: &autoscalingv2beta1.HorizontalPodAutoscaler{ - Spec: autoscalingv2beta1.HorizontalPodAutoscalerSpec{ - MinReplicas: defaultReplicas, - Metrics: defaultTemplate, - }, - }, - }, - { // MinReplicas update - original: &autoscalingv2beta1.HorizontalPodAutoscaler{ - Spec: autoscalingv2beta1.HorizontalPodAutoscalerSpec{ - MinReplicas: ptr.To[int32](3), - Metrics: defaultTemplate, - }, - }, - expected: &autoscalingv2beta1.HorizontalPodAutoscaler{ - Spec: autoscalingv2beta1.HorizontalPodAutoscalerSpec{ - MinReplicas: ptr.To[int32](3), - Metrics: defaultTemplate, - }, - }, - }, - { // Metrics default value - original: &autoscalingv2beta1.HorizontalPodAutoscaler{ - Spec: autoscalingv2beta1.HorizontalPodAutoscalerSpec{ - MinReplicas: defaultReplicas, - }, - }, - expected: &autoscalingv2beta1.HorizontalPodAutoscaler{ - Spec: autoscalingv2beta1.HorizontalPodAutoscalerSpec{ - MinReplicas: defaultReplicas, - Metrics: defaultTemplate, - }, - }, - }, - } - - for i, test := range tests { - original := test.original - expected := test.expected - obj2 := roundTrip(t, runtime.Object(original)) - got, ok := obj2.(*autoscalingv2beta1.HorizontalPodAutoscaler) - if !ok { - t.Fatalf("(%d) unexpected object: %v", i, obj2) - } - if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) { - t.Errorf("(%d) got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", i, got.Spec, expected.Spec) - } - } -} - -func TestHorizontalPodAutoscalerAnnotations(t *testing.T) { - tests := []struct { - hpa autoscalingv2beta1.HorizontalPodAutoscaler - test string - }{ - { - hpa: autoscalingv2beta1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - autoscaling.HorizontalPodAutoscalerConditionsAnnotation: "", - autoscaling.MetricSpecsAnnotation: "", - autoscaling.BehaviorSpecsAnnotation: "", - autoscaling.MetricStatusesAnnotation: "", - }, - }, - }, - test: "test empty value for Annotations", - }, - { - hpa: autoscalingv2beta1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - autoscaling.HorizontalPodAutoscalerConditionsAnnotation: "abc", - autoscaling.MetricSpecsAnnotation: "abc", - autoscaling.BehaviorSpecsAnnotation: "abc", - autoscaling.MetricStatusesAnnotation: "abc", - }, - }, - }, - test: "test random value for Annotations", - }, - { - hpa: autoscalingv2beta1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - autoscaling.HorizontalPodAutoscalerConditionsAnnotation: "[]", - autoscaling.MetricSpecsAnnotation: "[]", - autoscaling.BehaviorSpecsAnnotation: "[]", - autoscaling.MetricStatusesAnnotation: "[]", - }, - }, - }, - test: "test empty array value for Annotations", - }, - } - - for _, test := range tests { - hpa := &test.hpa - hpaBeforeMuatate := *hpa.DeepCopy() - obj := roundTrip(t, runtime.Object(hpa)) - final_obj, ok := obj.(*autoscalingv2beta1.HorizontalPodAutoscaler) - if !ok { - t.Fatalf("unexpected object: %v", obj) - } - if !reflect.DeepEqual(*hpa, hpaBeforeMuatate) { - t.Errorf("diff: %v", cmp.Diff(*hpa, hpaBeforeMuatate)) - t.Errorf("expected: %#v\n actual: %#v", *hpa, hpaBeforeMuatate) - } - - if len(final_obj.ObjectMeta.Annotations) != 0 { - t.Fatalf("unexpected annotations: %v", final_obj.ObjectMeta.Annotations) - } - } -} - -func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(SchemeGroupVersion), obj) - if err != nil { - t.Errorf("%v\n %#v", err, obj) - return nil - } - obj2, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) - if err != nil { - t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) - return nil - } - obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) - err = legacyscheme.Scheme.Convert(obj2, obj3, nil) - if err != nil { - t.Errorf("%v\nSource: %#v", err, obj2) - return nil - } - return obj3 -} diff --git a/pkg/apis/autoscaling/v2beta1/doc.go b/pkg/apis/autoscaling/v2beta1/doc.go deleted file mode 100644 index 29163d3d3edf5..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2016 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. -*/ - -// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling -// +k8s:conversion-gen-external-types=k8s.io/api/autoscaling/v2beta1 -// +k8s:defaulter-gen=TypeMeta -// +k8s:defaulter-gen-input=k8s.io/api/autoscaling/v2beta1 - -package v2beta1 diff --git a/pkg/apis/autoscaling/v2beta1/register.go b/pkg/apis/autoscaling/v2beta1/register.go deleted file mode 100644 index b2433d34cf8d9..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/register.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2016 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 v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "autoscaling" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v2beta1"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - localSchemeBuilder = &autoscalingv2beta1.SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) -} diff --git a/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go b/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go deleted file mode 100644 index 75dc270daf261..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go +++ /dev/null @@ -1,812 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v2beta1 - -import ( - unsafe "unsafe" - - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" - core "k8s.io/kubernetes/pkg/apis/core" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.CrossVersionObjectReference)(nil), (*autoscaling.CrossVersionObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(a.(*autoscalingv2beta1.CrossVersionObjectReference), b.(*autoscaling.CrossVersionObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.CrossVersionObjectReference)(nil), (*autoscalingv2beta1.CrossVersionObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_CrossVersionObjectReference_To_v2beta1_CrossVersionObjectReference(a.(*autoscaling.CrossVersionObjectReference), b.(*autoscalingv2beta1.CrossVersionObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.HorizontalPodAutoscalerCondition)(nil), (*autoscaling.HorizontalPodAutoscalerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(a.(*autoscalingv2beta1.HorizontalPodAutoscalerCondition), b.(*autoscaling.HorizontalPodAutoscalerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerCondition)(nil), (*autoscalingv2beta1.HorizontalPodAutoscalerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta1_HorizontalPodAutoscalerCondition(a.(*autoscaling.HorizontalPodAutoscalerCondition), b.(*autoscalingv2beta1.HorizontalPodAutoscalerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.HorizontalPodAutoscalerList)(nil), (*autoscaling.HorizontalPodAutoscalerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(a.(*autoscalingv2beta1.HorizontalPodAutoscalerList), b.(*autoscaling.HorizontalPodAutoscalerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerList)(nil), (*autoscalingv2beta1.HorizontalPodAutoscalerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerList_To_v2beta1_HorizontalPodAutoscalerList(a.(*autoscaling.HorizontalPodAutoscalerList), b.(*autoscalingv2beta1.HorizontalPodAutoscalerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.HorizontalPodAutoscalerSpec)(nil), (*autoscaling.HorizontalPodAutoscalerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(a.(*autoscalingv2beta1.HorizontalPodAutoscalerSpec), b.(*autoscaling.HorizontalPodAutoscalerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.HorizontalPodAutoscalerStatus)(nil), (*autoscaling.HorizontalPodAutoscalerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(a.(*autoscalingv2beta1.HorizontalPodAutoscalerStatus), b.(*autoscaling.HorizontalPodAutoscalerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerStatus)(nil), (*autoscalingv2beta1.HorizontalPodAutoscalerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta1_HorizontalPodAutoscalerStatus(a.(*autoscaling.HorizontalPodAutoscalerStatus), b.(*autoscalingv2beta1.HorizontalPodAutoscalerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.MetricSpec)(nil), (*autoscaling.MetricSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_MetricSpec_To_autoscaling_MetricSpec(a.(*autoscalingv2beta1.MetricSpec), b.(*autoscaling.MetricSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricSpec)(nil), (*autoscalingv2beta1.MetricSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricSpec_To_v2beta1_MetricSpec(a.(*autoscaling.MetricSpec), b.(*autoscalingv2beta1.MetricSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta1.MetricStatus)(nil), (*autoscaling.MetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_MetricStatus_To_autoscaling_MetricStatus(a.(*autoscalingv2beta1.MetricStatus), b.(*autoscaling.MetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricStatus)(nil), (*autoscalingv2beta1.MetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricStatus_To_v2beta1_MetricStatus(a.(*autoscaling.MetricStatus), b.(*autoscalingv2beta1.MetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ContainerResourceMetricSource)(nil), (*autoscalingv2beta1.ContainerResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ContainerResourceMetricSource_To_v2beta1_ContainerResourceMetricSource(a.(*autoscaling.ContainerResourceMetricSource), b.(*autoscalingv2beta1.ContainerResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ContainerResourceMetricStatus)(nil), (*autoscalingv2beta1.ContainerResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta1_ContainerResourceMetricStatus(a.(*autoscaling.ContainerResourceMetricStatus), b.(*autoscalingv2beta1.ContainerResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ExternalMetricSource)(nil), (*autoscalingv2beta1.ExternalMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ExternalMetricSource_To_v2beta1_ExternalMetricSource(a.(*autoscaling.ExternalMetricSource), b.(*autoscalingv2beta1.ExternalMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ExternalMetricStatus)(nil), (*autoscalingv2beta1.ExternalMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ExternalMetricStatus_To_v2beta1_ExternalMetricStatus(a.(*autoscaling.ExternalMetricStatus), b.(*autoscalingv2beta1.ExternalMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.HorizontalPodAutoscalerSpec)(nil), (*autoscalingv2beta1.HorizontalPodAutoscalerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(a.(*autoscaling.HorizontalPodAutoscalerSpec), b.(*autoscalingv2beta1.HorizontalPodAutoscalerSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.HorizontalPodAutoscaler)(nil), (*autoscalingv2beta1.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(a.(*autoscaling.HorizontalPodAutoscaler), b.(*autoscalingv2beta1.HorizontalPodAutoscaler), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.MetricTarget)(nil), (*autoscalingv2beta1.CrossVersionObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricTarget_To_v2beta1_CrossVersionObjectReference(a.(*autoscaling.MetricTarget), b.(*autoscalingv2beta1.CrossVersionObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ObjectMetricSource)(nil), (*autoscalingv2beta1.ObjectMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ObjectMetricSource_To_v2beta1_ObjectMetricSource(a.(*autoscaling.ObjectMetricSource), b.(*autoscalingv2beta1.ObjectMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ObjectMetricStatus)(nil), (*autoscalingv2beta1.ObjectMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ObjectMetricStatus_To_v2beta1_ObjectMetricStatus(a.(*autoscaling.ObjectMetricStatus), b.(*autoscalingv2beta1.ObjectMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.PodsMetricSource)(nil), (*autoscalingv2beta1.PodsMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_PodsMetricSource_To_v2beta1_PodsMetricSource(a.(*autoscaling.PodsMetricSource), b.(*autoscalingv2beta1.PodsMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.PodsMetricStatus)(nil), (*autoscalingv2beta1.PodsMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_PodsMetricStatus_To_v2beta1_PodsMetricStatus(a.(*autoscaling.PodsMetricStatus), b.(*autoscalingv2beta1.PodsMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ResourceMetricSource)(nil), (*autoscalingv2beta1.ResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ResourceMetricSource_To_v2beta1_ResourceMetricSource(a.(*autoscaling.ResourceMetricSource), b.(*autoscalingv2beta1.ResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.ResourceMetricStatus)(nil), (*autoscalingv2beta1.ResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ResourceMetricStatus_To_v2beta1_ResourceMetricStatus(a.(*autoscaling.ResourceMetricStatus), b.(*autoscalingv2beta1.ResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ContainerResourceMetricSource)(nil), (*autoscaling.ContainerResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(a.(*autoscalingv2beta1.ContainerResourceMetricSource), b.(*autoscaling.ContainerResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ContainerResourceMetricStatus)(nil), (*autoscaling.ContainerResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(a.(*autoscalingv2beta1.ContainerResourceMetricStatus), b.(*autoscaling.ContainerResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.CrossVersionObjectReference)(nil), (*autoscaling.MetricTarget)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_MetricTarget(a.(*autoscalingv2beta1.CrossVersionObjectReference), b.(*autoscaling.MetricTarget), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ExternalMetricSource)(nil), (*autoscaling.ExternalMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ExternalMetricSource_To_autoscaling_ExternalMetricSource(a.(*autoscalingv2beta1.ExternalMetricSource), b.(*autoscaling.ExternalMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ExternalMetricStatus)(nil), (*autoscaling.ExternalMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(a.(*autoscalingv2beta1.ExternalMetricStatus), b.(*autoscaling.ExternalMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.HorizontalPodAutoscaler)(nil), (*autoscaling.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(a.(*autoscalingv2beta1.HorizontalPodAutoscaler), b.(*autoscaling.HorizontalPodAutoscaler), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ObjectMetricSource)(nil), (*autoscaling.ObjectMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(a.(*autoscalingv2beta1.ObjectMetricSource), b.(*autoscaling.ObjectMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ObjectMetricStatus)(nil), (*autoscaling.ObjectMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(a.(*autoscalingv2beta1.ObjectMetricStatus), b.(*autoscaling.ObjectMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.PodsMetricSource)(nil), (*autoscaling.PodsMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_PodsMetricSource_To_autoscaling_PodsMetricSource(a.(*autoscalingv2beta1.PodsMetricSource), b.(*autoscaling.PodsMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.PodsMetricStatus)(nil), (*autoscaling.PodsMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_PodsMetricStatus_To_autoscaling_PodsMetricStatus(a.(*autoscalingv2beta1.PodsMetricStatus), b.(*autoscaling.PodsMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ResourceMetricSource)(nil), (*autoscaling.ResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ResourceMetricSource_To_autoscaling_ResourceMetricSource(a.(*autoscalingv2beta1.ResourceMetricSource), b.(*autoscaling.ResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta1.ResourceMetricStatus)(nil), (*autoscaling.ResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta1_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(a.(*autoscalingv2beta1.ResourceMetricStatus), b.(*autoscaling.ResourceMetricStatus), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v2beta1_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(in *autoscalingv2beta1.ContainerResourceMetricSource, out *autoscaling.ContainerResourceMetricSource, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - // WARNING: in.TargetAverageUtilization requires manual conversion: does not exist in peer-type - // WARNING: in.TargetAverageValue requires manual conversion: does not exist in peer-type - out.Container = in.Container - return nil -} - -func autoConvert_autoscaling_ContainerResourceMetricSource_To_v2beta1_ContainerResourceMetricSource(in *autoscaling.ContainerResourceMetricSource, out *autoscalingv2beta1.ContainerResourceMetricSource, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.Container = in.Container - // WARNING: in.Target requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(in *autoscalingv2beta1.ContainerResourceMetricStatus, out *autoscaling.ContainerResourceMetricStatus, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - // WARNING: in.CurrentAverageUtilization requires manual conversion: does not exist in peer-type - // WARNING: in.CurrentAverageValue requires manual conversion: does not exist in peer-type - out.Container = in.Container - return nil -} - -func autoConvert_autoscaling_ContainerResourceMetricStatus_To_v2beta1_ContainerResourceMetricStatus(in *autoscaling.ContainerResourceMetricStatus, out *autoscalingv2beta1.ContainerResourceMetricStatus, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.Container = in.Container - // WARNING: in.Current requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(in *autoscalingv2beta1.CrossVersionObjectReference, out *autoscaling.CrossVersionObjectReference, s conversion.Scope) error { - out.Kind = in.Kind - out.Name = in.Name - out.APIVersion = in.APIVersion - return nil -} - -// Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference is an autogenerated conversion function. -func Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(in *autoscalingv2beta1.CrossVersionObjectReference, out *autoscaling.CrossVersionObjectReference, s conversion.Scope) error { - return autoConvert_v2beta1_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(in, out, s) -} - -func autoConvert_autoscaling_CrossVersionObjectReference_To_v2beta1_CrossVersionObjectReference(in *autoscaling.CrossVersionObjectReference, out *autoscalingv2beta1.CrossVersionObjectReference, s conversion.Scope) error { - out.Kind = in.Kind - out.Name = in.Name - out.APIVersion = in.APIVersion - return nil -} - -// Convert_autoscaling_CrossVersionObjectReference_To_v2beta1_CrossVersionObjectReference is an autogenerated conversion function. -func Convert_autoscaling_CrossVersionObjectReference_To_v2beta1_CrossVersionObjectReference(in *autoscaling.CrossVersionObjectReference, out *autoscalingv2beta1.CrossVersionObjectReference, s conversion.Scope) error { - return autoConvert_autoscaling_CrossVersionObjectReference_To_v2beta1_CrossVersionObjectReference(in, out, s) -} - -func autoConvert_v2beta1_ExternalMetricSource_To_autoscaling_ExternalMetricSource(in *autoscalingv2beta1.ExternalMetricSource, out *autoscaling.ExternalMetricSource, s conversion.Scope) error { - // WARNING: in.MetricName requires manual conversion: does not exist in peer-type - // WARNING: in.MetricSelector requires manual conversion: does not exist in peer-type - // WARNING: in.TargetValue requires manual conversion: does not exist in peer-type - // WARNING: in.TargetAverageValue requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_ExternalMetricSource_To_v2beta1_ExternalMetricSource(in *autoscaling.ExternalMetricSource, out *autoscalingv2beta1.ExternalMetricSource, s conversion.Scope) error { - // WARNING: in.Metric requires manual conversion: does not exist in peer-type - // WARNING: in.Target requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(in *autoscalingv2beta1.ExternalMetricStatus, out *autoscaling.ExternalMetricStatus, s conversion.Scope) error { - // WARNING: in.MetricName requires manual conversion: does not exist in peer-type - // WARNING: in.MetricSelector requires manual conversion: does not exist in peer-type - // WARNING: in.CurrentValue requires manual conversion: does not exist in peer-type - // WARNING: in.CurrentAverageValue requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_ExternalMetricStatus_To_v2beta1_ExternalMetricStatus(in *autoscaling.ExternalMetricStatus, out *autoscalingv2beta1.ExternalMetricStatus, s conversion.Scope) error { - // WARNING: in.Metric requires manual conversion: does not exist in peer-type - // WARNING: in.Current requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *autoscalingv2beta1.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v2beta1_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *autoscalingv2beta1.HorizontalPodAutoscaler, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta1_HorizontalPodAutoscalerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func autoConvert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in *autoscalingv2beta1.HorizontalPodAutoscalerCondition, out *autoscaling.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - out.Type = autoscaling.HorizontalPodAutoscalerConditionType(in.Type) - out.Status = autoscaling.ConditionStatus(in.Status) - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition is an autogenerated conversion function. -func Convert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in *autoscalingv2beta1.HorizontalPodAutoscalerCondition, out *autoscaling.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - return autoConvert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta1_HorizontalPodAutoscalerCondition(in *autoscaling.HorizontalPodAutoscalerCondition, out *autoscalingv2beta1.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - out.Type = autoscalingv2beta1.HorizontalPodAutoscalerConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta1_HorizontalPodAutoscalerCondition is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta1_HorizontalPodAutoscalerCondition(in *autoscaling.HorizontalPodAutoscalerCondition, out *autoscalingv2beta1.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta1_HorizontalPodAutoscalerCondition(in, out, s) -} - -func autoConvert_v2beta1_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(in *autoscalingv2beta1.HorizontalPodAutoscalerList, out *autoscaling.HorizontalPodAutoscalerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]autoscaling.HorizontalPodAutoscaler, len(*in)) - for i := range *in { - if err := Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v2beta1_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList is an autogenerated conversion function. -func Convert_v2beta1_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(in *autoscalingv2beta1.HorizontalPodAutoscalerList, out *autoscaling.HorizontalPodAutoscalerList, s conversion.Scope) error { - return autoConvert_v2beta1_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerList_To_v2beta1_HorizontalPodAutoscalerList(in *autoscaling.HorizontalPodAutoscalerList, out *autoscalingv2beta1.HorizontalPodAutoscalerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]autoscalingv2beta1.HorizontalPodAutoscaler, len(*in)) - for i := range *in { - if err := Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerList_To_v2beta1_HorizontalPodAutoscalerList is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerList_To_v2beta1_HorizontalPodAutoscalerList(in *autoscaling.HorizontalPodAutoscalerList, out *autoscalingv2beta1.HorizontalPodAutoscalerList, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerList_To_v2beta1_HorizontalPodAutoscalerList(in, out, s) -} - -func autoConvert_v2beta1_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(in *autoscalingv2beta1.HorizontalPodAutoscalerSpec, out *autoscaling.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - if err := Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(&in.ScaleTargetRef, &out.ScaleTargetRef, s); err != nil { - return err - } - out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas)) - out.MaxReplicas = in.MaxReplicas - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics - *out = make([]autoscaling.MetricSpec, len(*in)) - for i := range *in { - if err := Convert_v2beta1_MetricSpec_To_autoscaling_MetricSpec(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Metrics = nil - } - return nil -} - -// Convert_v2beta1_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec is an autogenerated conversion function. -func Convert_v2beta1_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(in *autoscalingv2beta1.HorizontalPodAutoscalerSpec, out *autoscaling.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - return autoConvert_v2beta1_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in *autoscaling.HorizontalPodAutoscalerSpec, out *autoscalingv2beta1.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - if err := Convert_autoscaling_CrossVersionObjectReference_To_v2beta1_CrossVersionObjectReference(&in.ScaleTargetRef, &out.ScaleTargetRef, s); err != nil { - return err - } - out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas)) - out.MaxReplicas = in.MaxReplicas - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics - *out = make([]autoscalingv2beta1.MetricSpec, len(*in)) - for i := range *in { - if err := Convert_autoscaling_MetricSpec_To_v2beta1_MetricSpec(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Metrics = nil - } - // WARNING: in.Behavior requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in *autoscalingv2beta1.HorizontalPodAutoscalerStatus, out *autoscaling.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - out.ObservedGeneration = (*int64)(unsafe.Pointer(in.ObservedGeneration)) - out.LastScaleTime = (*metav1.Time)(unsafe.Pointer(in.LastScaleTime)) - out.CurrentReplicas = in.CurrentReplicas - out.DesiredReplicas = in.DesiredReplicas - if in.CurrentMetrics != nil { - in, out := &in.CurrentMetrics, &out.CurrentMetrics - *out = make([]autoscaling.MetricStatus, len(*in)) - for i := range *in { - if err := Convert_v2beta1_MetricStatus_To_autoscaling_MetricStatus(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.CurrentMetrics = nil - } - out.Conditions = *(*[]autoscaling.HorizontalPodAutoscalerCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus is an autogenerated conversion function. -func Convert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in *autoscalingv2beta1.HorizontalPodAutoscalerStatus, out *autoscaling.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - return autoConvert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta1_HorizontalPodAutoscalerStatus(in *autoscaling.HorizontalPodAutoscalerStatus, out *autoscalingv2beta1.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - out.ObservedGeneration = (*int64)(unsafe.Pointer(in.ObservedGeneration)) - out.LastScaleTime = (*metav1.Time)(unsafe.Pointer(in.LastScaleTime)) - out.CurrentReplicas = in.CurrentReplicas - out.DesiredReplicas = in.DesiredReplicas - if in.CurrentMetrics != nil { - in, out := &in.CurrentMetrics, &out.CurrentMetrics - *out = make([]autoscalingv2beta1.MetricStatus, len(*in)) - for i := range *in { - if err := Convert_autoscaling_MetricStatus_To_v2beta1_MetricStatus(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.CurrentMetrics = nil - } - out.Conditions = *(*[]autoscalingv2beta1.HorizontalPodAutoscalerCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta1_HorizontalPodAutoscalerStatus is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta1_HorizontalPodAutoscalerStatus(in *autoscaling.HorizontalPodAutoscalerStatus, out *autoscalingv2beta1.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta1_HorizontalPodAutoscalerStatus(in, out, s) -} - -func autoConvert_v2beta1_MetricSpec_To_autoscaling_MetricSpec(in *autoscalingv2beta1.MetricSpec, out *autoscaling.MetricSpec, s conversion.Scope) error { - out.Type = autoscaling.MetricSourceType(in.Type) - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(autoscaling.ObjectMetricSource) - if err := Convert_v2beta1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.Object = nil - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(autoscaling.PodsMetricSource) - if err := Convert_v2beta1_PodsMetricSource_To_autoscaling_PodsMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.Pods = nil - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(autoscaling.ResourceMetricSource) - if err := Convert_v2beta1_ResourceMetricSource_To_autoscaling_ResourceMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.Resource = nil - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscaling.ContainerResourceMetricSource) - if err := Convert_v2beta1_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(autoscaling.ExternalMetricSource) - if err := Convert_v2beta1_ExternalMetricSource_To_autoscaling_ExternalMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.External = nil - } - return nil -} - -// Convert_v2beta1_MetricSpec_To_autoscaling_MetricSpec is an autogenerated conversion function. -func Convert_v2beta1_MetricSpec_To_autoscaling_MetricSpec(in *autoscalingv2beta1.MetricSpec, out *autoscaling.MetricSpec, s conversion.Scope) error { - return autoConvert_v2beta1_MetricSpec_To_autoscaling_MetricSpec(in, out, s) -} - -func autoConvert_autoscaling_MetricSpec_To_v2beta1_MetricSpec(in *autoscaling.MetricSpec, out *autoscalingv2beta1.MetricSpec, s conversion.Scope) error { - out.Type = autoscalingv2beta1.MetricSourceType(in.Type) - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(autoscalingv2beta1.ObjectMetricSource) - if err := Convert_autoscaling_ObjectMetricSource_To_v2beta1_ObjectMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.Object = nil - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(autoscalingv2beta1.PodsMetricSource) - if err := Convert_autoscaling_PodsMetricSource_To_v2beta1_PodsMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.Pods = nil - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(autoscalingv2beta1.ResourceMetricSource) - if err := Convert_autoscaling_ResourceMetricSource_To_v2beta1_ResourceMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.Resource = nil - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscalingv2beta1.ContainerResourceMetricSource) - if err := Convert_autoscaling_ContainerResourceMetricSource_To_v2beta1_ContainerResourceMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(autoscalingv2beta1.ExternalMetricSource) - if err := Convert_autoscaling_ExternalMetricSource_To_v2beta1_ExternalMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.External = nil - } - return nil -} - -// Convert_autoscaling_MetricSpec_To_v2beta1_MetricSpec is an autogenerated conversion function. -func Convert_autoscaling_MetricSpec_To_v2beta1_MetricSpec(in *autoscaling.MetricSpec, out *autoscalingv2beta1.MetricSpec, s conversion.Scope) error { - return autoConvert_autoscaling_MetricSpec_To_v2beta1_MetricSpec(in, out, s) -} - -func autoConvert_v2beta1_MetricStatus_To_autoscaling_MetricStatus(in *autoscalingv2beta1.MetricStatus, out *autoscaling.MetricStatus, s conversion.Scope) error { - out.Type = autoscaling.MetricSourceType(in.Type) - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(autoscaling.ObjectMetricStatus) - if err := Convert_v2beta1_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.Object = nil - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(autoscaling.PodsMetricStatus) - if err := Convert_v2beta1_PodsMetricStatus_To_autoscaling_PodsMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.Pods = nil - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(autoscaling.ResourceMetricStatus) - if err := Convert_v2beta1_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.Resource = nil - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscaling.ContainerResourceMetricStatus) - if err := Convert_v2beta1_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(autoscaling.ExternalMetricStatus) - if err := Convert_v2beta1_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.External = nil - } - return nil -} - -// Convert_v2beta1_MetricStatus_To_autoscaling_MetricStatus is an autogenerated conversion function. -func Convert_v2beta1_MetricStatus_To_autoscaling_MetricStatus(in *autoscalingv2beta1.MetricStatus, out *autoscaling.MetricStatus, s conversion.Scope) error { - return autoConvert_v2beta1_MetricStatus_To_autoscaling_MetricStatus(in, out, s) -} - -func autoConvert_autoscaling_MetricStatus_To_v2beta1_MetricStatus(in *autoscaling.MetricStatus, out *autoscalingv2beta1.MetricStatus, s conversion.Scope) error { - out.Type = autoscalingv2beta1.MetricSourceType(in.Type) - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(autoscalingv2beta1.ObjectMetricStatus) - if err := Convert_autoscaling_ObjectMetricStatus_To_v2beta1_ObjectMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.Object = nil - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(autoscalingv2beta1.PodsMetricStatus) - if err := Convert_autoscaling_PodsMetricStatus_To_v2beta1_PodsMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.Pods = nil - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(autoscalingv2beta1.ResourceMetricStatus) - if err := Convert_autoscaling_ResourceMetricStatus_To_v2beta1_ResourceMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.Resource = nil - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscalingv2beta1.ContainerResourceMetricStatus) - if err := Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta1_ContainerResourceMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(autoscalingv2beta1.ExternalMetricStatus) - if err := Convert_autoscaling_ExternalMetricStatus_To_v2beta1_ExternalMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.External = nil - } - return nil -} - -// Convert_autoscaling_MetricStatus_To_v2beta1_MetricStatus is an autogenerated conversion function. -func Convert_autoscaling_MetricStatus_To_v2beta1_MetricStatus(in *autoscaling.MetricStatus, out *autoscalingv2beta1.MetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_MetricStatus_To_v2beta1_MetricStatus(in, out, s) -} - -func autoConvert_v2beta1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in *autoscalingv2beta1.ObjectMetricSource, out *autoscaling.ObjectMetricSource, s conversion.Scope) error { - if err := Convert_v2beta1_CrossVersionObjectReference_To_autoscaling_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - // WARNING: in.MetricName requires manual conversion: does not exist in peer-type - // WARNING: in.TargetValue requires manual conversion: does not exist in peer-type - // WARNING: in.Selector requires manual conversion: does not exist in peer-type - // WARNING: in.AverageValue requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_ObjectMetricSource_To_v2beta1_ObjectMetricSource(in *autoscaling.ObjectMetricSource, out *autoscalingv2beta1.ObjectMetricSource, s conversion.Scope) error { - // WARNING: in.DescribedObject requires manual conversion: does not exist in peer-type - if err := Convert_autoscaling_MetricTarget_To_v2beta1_CrossVersionObjectReference(&in.Target, &out.Target, s); err != nil { - return err - } - // WARNING: in.Metric requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(in *autoscalingv2beta1.ObjectMetricStatus, out *autoscaling.ObjectMetricStatus, s conversion.Scope) error { - // WARNING: in.Target requires manual conversion: does not exist in peer-type - // WARNING: in.MetricName requires manual conversion: does not exist in peer-type - // WARNING: in.CurrentValue requires manual conversion: does not exist in peer-type - // WARNING: in.Selector requires manual conversion: does not exist in peer-type - // WARNING: in.AverageValue requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_ObjectMetricStatus_To_v2beta1_ObjectMetricStatus(in *autoscaling.ObjectMetricStatus, out *autoscalingv2beta1.ObjectMetricStatus, s conversion.Scope) error { - // WARNING: in.Metric requires manual conversion: does not exist in peer-type - // WARNING: in.Current requires manual conversion: does not exist in peer-type - // WARNING: in.DescribedObject requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_PodsMetricSource_To_autoscaling_PodsMetricSource(in *autoscalingv2beta1.PodsMetricSource, out *autoscaling.PodsMetricSource, s conversion.Scope) error { - // WARNING: in.MetricName requires manual conversion: does not exist in peer-type - // WARNING: in.TargetAverageValue requires manual conversion: does not exist in peer-type - // WARNING: in.Selector requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_PodsMetricSource_To_v2beta1_PodsMetricSource(in *autoscaling.PodsMetricSource, out *autoscalingv2beta1.PodsMetricSource, s conversion.Scope) error { - // WARNING: in.Metric requires manual conversion: does not exist in peer-type - // WARNING: in.Target requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_PodsMetricStatus_To_autoscaling_PodsMetricStatus(in *autoscalingv2beta1.PodsMetricStatus, out *autoscaling.PodsMetricStatus, s conversion.Scope) error { - // WARNING: in.MetricName requires manual conversion: does not exist in peer-type - // WARNING: in.CurrentAverageValue requires manual conversion: does not exist in peer-type - // WARNING: in.Selector requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_PodsMetricStatus_To_v2beta1_PodsMetricStatus(in *autoscaling.PodsMetricStatus, out *autoscalingv2beta1.PodsMetricStatus, s conversion.Scope) error { - // WARNING: in.Metric requires manual conversion: does not exist in peer-type - // WARNING: in.Current requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in *autoscalingv2beta1.ResourceMetricSource, out *autoscaling.ResourceMetricSource, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - // WARNING: in.TargetAverageUtilization requires manual conversion: does not exist in peer-type - // WARNING: in.TargetAverageValue requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_ResourceMetricSource_To_v2beta1_ResourceMetricSource(in *autoscaling.ResourceMetricSource, out *autoscalingv2beta1.ResourceMetricSource, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - // WARNING: in.Target requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta1_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in *autoscalingv2beta1.ResourceMetricStatus, out *autoscaling.ResourceMetricStatus, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - // WARNING: in.CurrentAverageUtilization requires manual conversion: does not exist in peer-type - // WARNING: in.CurrentAverageValue requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_autoscaling_ResourceMetricStatus_To_v2beta1_ResourceMetricStatus(in *autoscaling.ResourceMetricStatus, out *autoscalingv2beta1.ResourceMetricStatus, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - // WARNING: in.Current requires manual conversion: does not exist in peer-type - return nil -} diff --git a/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go b/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go deleted file mode 100644 index bf2bc2221798a..0000000000000 --- a/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&autoscalingv2beta1.HorizontalPodAutoscaler{}, func(obj interface{}) { - SetObjectDefaults_HorizontalPodAutoscaler(obj.(*autoscalingv2beta1.HorizontalPodAutoscaler)) - }) - scheme.AddTypeDefaultingFunc(&autoscalingv2beta1.HorizontalPodAutoscalerList{}, func(obj interface{}) { - SetObjectDefaults_HorizontalPodAutoscalerList(obj.(*autoscalingv2beta1.HorizontalPodAutoscalerList)) - }) - return nil -} - -func SetObjectDefaults_HorizontalPodAutoscaler(in *autoscalingv2beta1.HorizontalPodAutoscaler) { - SetDefaults_HorizontalPodAutoscaler(in) -} - -func SetObjectDefaults_HorizontalPodAutoscalerList(in *autoscalingv2beta1.HorizontalPodAutoscalerList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_HorizontalPodAutoscaler(a) - } -} diff --git a/pkg/apis/autoscaling/v2beta2/conversion.go b/pkg/apis/autoscaling/v2beta2/conversion.go deleted file mode 100644 index 7afa30faeded5..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/conversion.go +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright 2020 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 v2beta2 - -import ( - "fmt" - - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/kubernetes/pkg/apis/autoscaling" -) - -func Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *autoscalingv2beta2.HorizontalPodAutoscaler, s conversion.Scope) error { - if err := autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(in, out, s); err != nil { - return err - } - // Ensure old round-trips annotations are discarded - annotations, copiedAnnotations := autoscaling.DropRoundTripHorizontalPodAutoscalerAnnotations(out.Annotations) - out.Annotations = annotations - - behavior := in.Spec.Behavior - if behavior == nil { - return nil - } - // Save the tolerance fields in annotations for round-trip - if behavior.ScaleDown != nil && behavior.ScaleDown.Tolerance != nil { - if !copiedAnnotations { - copiedAnnotations = true - out.Annotations = autoscaling.DeepCopyStringMap(out.Annotations) - } - out.Annotations[autoscaling.ToleranceScaleDownAnnotation] = behavior.ScaleDown.Tolerance.String() - } - if behavior.ScaleUp != nil && behavior.ScaleUp.Tolerance != nil { - if !copiedAnnotations { - copiedAnnotations = true //nolint:ineffassign // Intentionally setting the variable even though it's not read because this might change when adding another if clause below in the future. - out.Annotations = autoscaling.DeepCopyStringMap(out.Annotations) - } - out.Annotations[autoscaling.ToleranceScaleUpAnnotation] = behavior.ScaleUp.Tolerance.String() - } - return nil -} - -func Convert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *autoscalingv2beta2.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error { - if err := autoConvert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in, out, s); err != nil { - return err - } - // Restore the tolerance fields from annotations for round-trip - if tolerance, ok := out.Annotations[autoscaling.ToleranceScaleDownAnnotation]; ok { - if out.Spec.Behavior == nil { - out.Spec.Behavior = &autoscaling.HorizontalPodAutoscalerBehavior{} - } - if out.Spec.Behavior.ScaleDown == nil { - out.Spec.Behavior.ScaleDown = &autoscaling.HPAScalingRules{} - } - q, err := resource.ParseQuantity(tolerance) - if err != nil { - return fmt.Errorf("failed to parse annotation %q: %w", autoscaling.ToleranceScaleDownAnnotation, err) - } - out.Spec.Behavior.ScaleDown.Tolerance = &q - } - if tolerance, ok := out.Annotations[autoscaling.ToleranceScaleUpAnnotation]; ok { - if out.Spec.Behavior == nil { - out.Spec.Behavior = &autoscaling.HorizontalPodAutoscalerBehavior{} - } - if out.Spec.Behavior.ScaleUp == nil { - out.Spec.Behavior.ScaleUp = &autoscaling.HPAScalingRules{} - } - q, err := resource.ParseQuantity(tolerance) - if err != nil { - return fmt.Errorf("failed to parse annotation %q: %w", autoscaling.ToleranceScaleUpAnnotation, err) - } - out.Spec.Behavior.ScaleUp.Tolerance = &q - } - // Do not save round-trip annotations in internal resource - out.Annotations, _ = autoscaling.DropRoundTripHorizontalPodAutoscalerAnnotations(out.Annotations) - return nil -} - -func Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(in *autoscalingv2beta2.HPAScalingRules, out *autoscaling.HPAScalingRules, s conversion.Scope) error { - // Tolerance field is handled in the HorizontalPodAutoscaler conversion function. - return autoConvert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(in, out, s) -} - -func Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(in *autoscaling.HPAScalingRules, out *autoscalingv2beta2.HPAScalingRules, s conversion.Scope) error { - // Tolerance field is handled in the HorizontalPodAutoscaler conversion function. - return autoConvert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(in, out, s) -} diff --git a/pkg/apis/autoscaling/v2beta2/conversion_test.go b/pkg/apis/autoscaling/v2beta2/conversion_test.go deleted file mode 100644 index faacf410fdd22..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/conversion_test.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright 2025 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 v2beta2 - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/apis/autoscaling" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/utils/ptr" -) - -func TestConvertRoundTrip(t *testing.T) { - tolerance1 := resource.MustParse("0.1") - tolerance2 := resource.MustParse("0.2") - tests := []struct { - name string - internalHPA *autoscaling.HorizontalPodAutoscaler - }{ - { - "Complete HPA with scale-up tolerance", - &autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: v1.ObjectMeta{ - Name: "hpa", - Namespace: "hpa-ns", - Annotations: map[string]string{"key": "value"}, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - MinReplicas: ptr.To(int32(1)), - MaxReplicas: 3, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, - }, - }, - Behavior: &autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 1, - PeriodSeconds: 2, - }, - }, - Tolerance: &tolerance1, - }, - }, - }, - }, - }, - { - "Scale-down and scale-up tolerances", - &autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - MinReplicas: ptr.To(int32(1)), - MaxReplicas: 3, - Behavior: &autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Tolerance: &tolerance1, - }, - ScaleDown: &autoscaling.HPAScalingRules{ - Tolerance: &tolerance2, - }, - }, - }, - }, - }, - { - "Scale-down tolerance only", - &autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - MinReplicas: ptr.To(int32(1)), - MaxReplicas: 3, - Behavior: &autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Tolerance: &tolerance2, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - v2beta2HPA := &autoscalingv2beta2.HorizontalPodAutoscaler{} - if err := Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(tt.internalHPA, v2beta2HPA, nil); err != nil { - t.Errorf("Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler() error = %v", err) - } - roundtripHPA := &autoscaling.HorizontalPodAutoscaler{} - if err := Convert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(v2beta2HPA, roundtripHPA, nil); err != nil { - t.Errorf("Convert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler() error = %v", err) - } - if !apiequality.Semantic.DeepEqual(tt.internalHPA, roundtripHPA) { - t.Errorf("HPA is not equivalent after round-trip: mismatch (-want +got):\n%s", cmp.Diff(tt.internalHPA, roundtripHPA)) - } - }) - } -} diff --git a/pkg/apis/autoscaling/v2beta2/defaults.go b/pkg/apis/autoscaling/v2beta2/defaults.go deleted file mode 100644 index 3d788219ccf3e..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/defaults.go +++ /dev/null @@ -1,134 +0,0 @@ -/* -Copyright 2018 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 v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/apis/autoscaling" - "k8s.io/utils/ptr" -) - -var ( - // These constants repeats previous HPA behavior - scaleUpLimitPercent int32 = 100 - scaleUpLimitMinimumPods int32 = 4 - scaleUpPeriod int32 = 15 - scaleUpStabilizationSeconds int32 - maxPolicy = autoscalingv2beta2.MaxPolicySelect - defaultHPAScaleUpRules = autoscalingv2beta2.HPAScalingRules{ - StabilizationWindowSeconds: &scaleUpStabilizationSeconds, - SelectPolicy: &maxPolicy, - Policies: []autoscalingv2beta2.HPAScalingPolicy{ - { - Type: autoscalingv2beta2.PodsScalingPolicy, - Value: scaleUpLimitMinimumPods, - PeriodSeconds: scaleUpPeriod, - }, - { - Type: autoscalingv2beta2.PercentScalingPolicy, - Value: scaleUpLimitPercent, - PeriodSeconds: scaleUpPeriod, - }, - }, - } - scaleDownPeriod int32 = 15 - // Currently we can set the downscaleStabilizationWindow from the command line - // So we can not rewrite the command line option from here - scaleDownStabilizationSeconds *int32 = nil - scaleDownLimitPercent int32 = 100 - defaultHPAScaleDownRules = autoscalingv2beta2.HPAScalingRules{ - StabilizationWindowSeconds: scaleDownStabilizationSeconds, - SelectPolicy: &maxPolicy, - Policies: []autoscalingv2beta2.HPAScalingPolicy{ - { - Type: autoscalingv2beta2.PercentScalingPolicy, - Value: scaleDownLimitPercent, - PeriodSeconds: scaleDownPeriod, - }, - }, - } -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -func SetDefaults_HorizontalPodAutoscaler(obj *autoscalingv2beta2.HorizontalPodAutoscaler) { - if obj.Spec.MinReplicas == nil { - obj.Spec.MinReplicas = ptr.To[int32](1) - } - - if len(obj.Spec.Metrics) == 0 { - utilizationDefaultVal := int32(autoscaling.DefaultCPUUtilization) - obj.Spec.Metrics = []autoscalingv2beta2.MetricSpec{ - { - Type: autoscalingv2beta2.ResourceMetricSourceType, - Resource: &autoscalingv2beta2.ResourceMetricSource{ - Name: v1.ResourceCPU, - Target: autoscalingv2beta2.MetricTarget{ - Type: autoscalingv2beta2.UtilizationMetricType, - AverageUtilization: &utilizationDefaultVal, - }, - }, - }, - } - } - SetDefaults_HorizontalPodAutoscalerBehavior(obj) -} - -// SetDefaults_HorizontalPodAutoscalerBehavior fills the behavior if it is not null -func SetDefaults_HorizontalPodAutoscalerBehavior(obj *autoscalingv2beta2.HorizontalPodAutoscaler) { - // if behavior is specified, we should fill all the 'nil' values with the default ones - if obj.Spec.Behavior != nil { - obj.Spec.Behavior.ScaleUp = GenerateHPAScaleUpRules(obj.Spec.Behavior.ScaleUp) - obj.Spec.Behavior.ScaleDown = GenerateHPAScaleDownRules(obj.Spec.Behavior.ScaleDown) - } -} - -// GenerateHPAScaleUpRules returns a fully-initialized HPAScalingRules value -// We guarantee that no pointer in the structure will have the 'nil' value -func GenerateHPAScaleUpRules(scalingRules *autoscalingv2beta2.HPAScalingRules) *autoscalingv2beta2.HPAScalingRules { - defaultScalingRules := defaultHPAScaleUpRules.DeepCopy() - return copyHPAScalingRules(scalingRules, defaultScalingRules) -} - -// GenerateHPAScaleDownRules returns a fully-initialized HPAScalingRules value -// We guarantee that no pointer in the structure will have the 'nil' value -// EXCEPT StabilizationWindowSeconds, for reasoning check the comment for defaultHPAScaleDownRules -func GenerateHPAScaleDownRules(scalingRules *autoscalingv2beta2.HPAScalingRules) *autoscalingv2beta2.HPAScalingRules { - defaultScalingRules := defaultHPAScaleDownRules.DeepCopy() - return copyHPAScalingRules(scalingRules, defaultScalingRules) -} - -// copyHPAScalingRules copies all non-`nil` fields in HPA constraint structure -func copyHPAScalingRules(from, to *autoscalingv2beta2.HPAScalingRules) *autoscalingv2beta2.HPAScalingRules { - if from == nil { - return to - } - if from.SelectPolicy != nil { - to.SelectPolicy = from.SelectPolicy - } - if from.StabilizationWindowSeconds != nil { - to.StabilizationWindowSeconds = from.StabilizationWindowSeconds - } - if from.Policies != nil { - to.Policies = from.Policies - } - return to -} diff --git a/pkg/apis/autoscaling/v2beta2/defaults_test.go b/pkg/apis/autoscaling/v2beta2/defaults_test.go deleted file mode 100644 index 607788b137ccf..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/defaults_test.go +++ /dev/null @@ -1,324 +0,0 @@ -/* -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. -*/ - -package v2beta2_test - -import ( - "reflect" - "testing" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api/legacyscheme" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - autoscalingv2 "k8s.io/api/autoscaling/v2beta2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/apis/autoscaling" - _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" - . "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2" - _ "k8s.io/kubernetes/pkg/apis/core/install" - "k8s.io/utils/ptr" -) - -func TestGenerateScaleDownRules(t *testing.T) { - type TestCase struct { - rateDownPods int32 - rateDownPodsPeriodSeconds int32 - rateDownPercent int32 - rateDownPercentPeriodSeconds int32 - stabilizationSeconds *int32 - selectPolicy *autoscalingv2.ScalingPolicySelect - - expectedPolicies []autoscalingv2.HPAScalingPolicy - expectedStabilization *int32 - expectedSelectPolicy string - annotation string - } - maxPolicy := autoscalingv2.MaxPolicySelect - minPolicy := autoscalingv2.MinPolicySelect - tests := []TestCase{ - { - annotation: "Default values", - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PercentScalingPolicy, Value: 100, PeriodSeconds: 15}, - }, - expectedStabilization: nil, - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - { - annotation: "All parameters are specified", - rateDownPods: 1, - rateDownPodsPeriodSeconds: 2, - rateDownPercent: 3, - rateDownPercentPeriodSeconds: 4, - stabilizationSeconds: ptr.To[int32](25), - selectPolicy: &maxPolicy, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PodsScalingPolicy, Value: 1, PeriodSeconds: 2}, - {Type: autoscalingv2.PercentScalingPolicy, Value: 3, PeriodSeconds: 4}, - }, - expectedStabilization: ptr.To[int32](25), - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - { - annotation: "Percent policy is specified", - rateDownPercent: 1, - rateDownPercentPeriodSeconds: 2, - selectPolicy: &minPolicy, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PercentScalingPolicy, Value: 1, PeriodSeconds: 2}, - }, - expectedStabilization: nil, - expectedSelectPolicy: string(autoscalingv2.MinPolicySelect), - }, - { - annotation: "Pods policy is specified", - rateDownPods: 3, - rateDownPodsPeriodSeconds: 4, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PodsScalingPolicy, Value: 3, PeriodSeconds: 4}, - }, - expectedStabilization: nil, - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - } - for _, tc := range tests { - t.Run(tc.annotation, func(t *testing.T) { - scaleDownRules := &autoscalingv2.HPAScalingRules{ - StabilizationWindowSeconds: tc.stabilizationSeconds, - SelectPolicy: tc.selectPolicy, - } - if tc.rateDownPods != 0 || tc.rateDownPodsPeriodSeconds != 0 { - scaleDownRules.Policies = append(scaleDownRules.Policies, autoscalingv2.HPAScalingPolicy{ - Type: autoscalingv2.PodsScalingPolicy, Value: tc.rateDownPods, PeriodSeconds: tc.rateDownPodsPeriodSeconds, - }) - } - if tc.rateDownPercent != 0 || tc.rateDownPercentPeriodSeconds != 0 { - scaleDownRules.Policies = append(scaleDownRules.Policies, autoscalingv2.HPAScalingPolicy{ - Type: autoscalingv2.PercentScalingPolicy, Value: tc.rateDownPercent, PeriodSeconds: tc.rateDownPercentPeriodSeconds, - }) - } - down := GenerateHPAScaleDownRules(scaleDownRules) - assert.EqualValues(t, tc.expectedPolicies, down.Policies) - if tc.expectedStabilization != nil { - assert.Equal(t, *tc.expectedStabilization, *down.StabilizationWindowSeconds) - } else { - assert.Equal(t, tc.expectedStabilization, down.StabilizationWindowSeconds) - } - assert.Equal(t, autoscalingv2.ScalingPolicySelect(tc.expectedSelectPolicy), *down.SelectPolicy) - }) - } -} - -func TestGenerateScaleUpRules(t *testing.T) { - type TestCase struct { - rateUpPods int32 - rateUpPodsPeriodSeconds int32 - rateUpPercent int32 - rateUpPercentPeriodSeconds int32 - stabilizationSeconds *int32 - selectPolicy *autoscalingv2.ScalingPolicySelect - - expectedPolicies []autoscalingv2.HPAScalingPolicy - expectedStabilization *int32 - expectedSelectPolicy string - annotation string - } - maxPolicy := autoscalingv2.MaxPolicySelect - minPolicy := autoscalingv2.MinPolicySelect - tests := []TestCase{ - { - annotation: "Default values", - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PodsScalingPolicy, Value: 4, PeriodSeconds: 15}, - {Type: autoscalingv2.PercentScalingPolicy, Value: 100, PeriodSeconds: 15}, - }, - expectedStabilization: ptr.To[int32](0), - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - { - annotation: "All parameters are specified", - rateUpPods: 1, - rateUpPodsPeriodSeconds: 2, - rateUpPercent: 3, - rateUpPercentPeriodSeconds: 4, - stabilizationSeconds: ptr.To[int32](25), - selectPolicy: &maxPolicy, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PodsScalingPolicy, Value: 1, PeriodSeconds: 2}, - {Type: autoscalingv2.PercentScalingPolicy, Value: 3, PeriodSeconds: 4}, - }, - expectedStabilization: ptr.To[int32](25), - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - { - annotation: "Pod policy is specified", - rateUpPods: 1, - rateUpPodsPeriodSeconds: 2, - selectPolicy: &minPolicy, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PodsScalingPolicy, Value: 1, PeriodSeconds: 2}, - }, - expectedStabilization: ptr.To[int32](0), - expectedSelectPolicy: string(autoscalingv2.MinPolicySelect), - }, - { - annotation: "Percent policy is specified", - rateUpPercent: 7, - rateUpPercentPeriodSeconds: 10, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PercentScalingPolicy, Value: 7, PeriodSeconds: 10}, - }, - expectedStabilization: ptr.To[int32](0), - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - { - annotation: "Pod policy and stabilization window are specified", - rateUpPodsPeriodSeconds: 2, - stabilizationSeconds: ptr.To[int32](25), - rateUpPods: 4, - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PodsScalingPolicy, Value: 4, PeriodSeconds: 2}, - }, - expectedStabilization: ptr.To[int32](25), - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - { - annotation: "Percent policy and stabilization window are specified", - rateUpPercent: 7, - rateUpPercentPeriodSeconds: 60, - stabilizationSeconds: ptr.To[int32](25), - expectedPolicies: []autoscalingv2.HPAScalingPolicy{ - {Type: autoscalingv2.PercentScalingPolicy, Value: 7, PeriodSeconds: 60}, - }, - expectedStabilization: ptr.To[int32](25), - expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect), - }, - } - for _, tc := range tests { - t.Run(tc.annotation, func(t *testing.T) { - scaleUpRules := &autoscalingv2.HPAScalingRules{ - StabilizationWindowSeconds: tc.stabilizationSeconds, - SelectPolicy: tc.selectPolicy, - } - if tc.rateUpPods != 0 || tc.rateUpPodsPeriodSeconds != 0 { - scaleUpRules.Policies = append(scaleUpRules.Policies, autoscalingv2.HPAScalingPolicy{ - Type: autoscalingv2.PodsScalingPolicy, Value: tc.rateUpPods, PeriodSeconds: tc.rateUpPodsPeriodSeconds, - }) - } - if tc.rateUpPercent != 0 || tc.rateUpPercentPeriodSeconds != 0 { - scaleUpRules.Policies = append(scaleUpRules.Policies, autoscalingv2.HPAScalingPolicy{ - Type: autoscalingv2.PercentScalingPolicy, Value: tc.rateUpPercent, PeriodSeconds: tc.rateUpPercentPeriodSeconds, - }) - } - up := GenerateHPAScaleUpRules(scaleUpRules) - assert.Equal(t, tc.expectedPolicies, up.Policies) - if tc.expectedStabilization != nil { - assert.Equal(t, *tc.expectedStabilization, *up.StabilizationWindowSeconds) - } else { - assert.Equal(t, tc.expectedStabilization, up.StabilizationWindowSeconds) - } - - assert.Equal(t, autoscalingv2.ScalingPolicySelect(tc.expectedSelectPolicy), *up.SelectPolicy) - }) - } -} - -func TestHorizontalPodAutoscalerAnnotations(t *testing.T) { - tests := []struct { - hpa autoscalingv2.HorizontalPodAutoscaler - test string - }{ - { - hpa: autoscalingv2.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - autoscaling.HorizontalPodAutoscalerConditionsAnnotation: "", - autoscaling.MetricSpecsAnnotation: "", - autoscaling.BehaviorSpecsAnnotation: "", - autoscaling.MetricStatusesAnnotation: "", - }, - }, - }, - test: "test empty value for Annotations", - }, - { - hpa: autoscalingv2.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - autoscaling.HorizontalPodAutoscalerConditionsAnnotation: "abc", - autoscaling.MetricSpecsAnnotation: "abc", - autoscaling.BehaviorSpecsAnnotation: "abc", - autoscaling.MetricStatusesAnnotation: "abc", - }, - }, - }, - test: "test random value for Annotations", - }, - { - hpa: autoscalingv2.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - autoscaling.HorizontalPodAutoscalerConditionsAnnotation: "[]", - autoscaling.MetricSpecsAnnotation: "[]", - autoscaling.BehaviorSpecsAnnotation: "[]", - autoscaling.MetricStatusesAnnotation: "[]", - }, - }, - }, - test: "test empty array value for Annotations", - }, - } - - for _, test := range tests { - hpa := &test.hpa - hpaBeforeMuatate := *hpa.DeepCopy() - obj := roundTrip(t, runtime.Object(hpa)) - final_obj, ok := obj.(*autoscalingv2.HorizontalPodAutoscaler) - if !ok { - t.Fatalf("unexpected object: %v", obj) - } - if !reflect.DeepEqual(*hpa, hpaBeforeMuatate) { - t.Errorf("diff: %v", cmp.Diff(*hpa, hpaBeforeMuatate)) - t.Errorf("expected: %#v\n actual: %#v", *hpa, hpaBeforeMuatate) - } - - if len(final_obj.ObjectMeta.Annotations) != 0 { - t.Fatalf("unexpected annotations: %v", final_obj.ObjectMeta.Annotations) - } - } -} - -func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(SchemeGroupVersion), obj) - if err != nil { - t.Errorf("%v\n %#v", err, obj) - return nil - } - obj2, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) - if err != nil { - t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) - return nil - } - obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) - err = legacyscheme.Scheme.Convert(obj2, obj3, nil) - if err != nil { - t.Errorf("%v\nSource: %#v", err, obj2) - return nil - } - return obj3 -} diff --git a/pkg/apis/autoscaling/v2beta2/doc.go b/pkg/apis/autoscaling/v2beta2/doc.go deleted file mode 100644 index c5d25c96563ce..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2018 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. -*/ - -// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling -// +k8s:conversion-gen-external-types=k8s.io/api/autoscaling/v2beta2 -// +k8s:defaulter-gen=TypeMeta -// +k8s:defaulter-gen-input=k8s.io/api/autoscaling/v2beta2 - -package v2beta2 diff --git a/pkg/apis/autoscaling/v2beta2/register.go b/pkg/apis/autoscaling/v2beta2/register.go deleted file mode 100644 index fc86de007e995..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/register.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2018 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 v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "autoscaling" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v2beta2"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - localSchemeBuilder = &autoscalingv2beta2.SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) -} diff --git a/pkg/apis/autoscaling/v2beta2/zz_generated.conversion.go b/pkg/apis/autoscaling/v2beta2/zz_generated.conversion.go deleted file mode 100644 index 7f544a0864f8d..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/zz_generated.conversion.go +++ /dev/null @@ -1,1076 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v2beta2 - -import ( - unsafe "unsafe" - - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" - core "k8s.io/kubernetes/pkg/apis/core" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ContainerResourceMetricSource)(nil), (*autoscaling.ContainerResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(a.(*autoscalingv2beta2.ContainerResourceMetricSource), b.(*autoscaling.ContainerResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ContainerResourceMetricSource)(nil), (*autoscalingv2beta2.ContainerResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ContainerResourceMetricSource_To_v2beta2_ContainerResourceMetricSource(a.(*autoscaling.ContainerResourceMetricSource), b.(*autoscalingv2beta2.ContainerResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ContainerResourceMetricStatus)(nil), (*autoscaling.ContainerResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(a.(*autoscalingv2beta2.ContainerResourceMetricStatus), b.(*autoscaling.ContainerResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ContainerResourceMetricStatus)(nil), (*autoscalingv2beta2.ContainerResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta2_ContainerResourceMetricStatus(a.(*autoscaling.ContainerResourceMetricStatus), b.(*autoscalingv2beta2.ContainerResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.CrossVersionObjectReference)(nil), (*autoscaling.CrossVersionObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(a.(*autoscalingv2beta2.CrossVersionObjectReference), b.(*autoscaling.CrossVersionObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.CrossVersionObjectReference)(nil), (*autoscalingv2beta2.CrossVersionObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(a.(*autoscaling.CrossVersionObjectReference), b.(*autoscalingv2beta2.CrossVersionObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ExternalMetricSource)(nil), (*autoscaling.ExternalMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ExternalMetricSource_To_autoscaling_ExternalMetricSource(a.(*autoscalingv2beta2.ExternalMetricSource), b.(*autoscaling.ExternalMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ExternalMetricSource)(nil), (*autoscalingv2beta2.ExternalMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ExternalMetricSource_To_v2beta2_ExternalMetricSource(a.(*autoscaling.ExternalMetricSource), b.(*autoscalingv2beta2.ExternalMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ExternalMetricStatus)(nil), (*autoscaling.ExternalMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(a.(*autoscalingv2beta2.ExternalMetricStatus), b.(*autoscaling.ExternalMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ExternalMetricStatus)(nil), (*autoscalingv2beta2.ExternalMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus(a.(*autoscaling.ExternalMetricStatus), b.(*autoscalingv2beta2.ExternalMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.HPAScalingPolicy)(nil), (*autoscaling.HPAScalingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(a.(*autoscalingv2beta2.HPAScalingPolicy), b.(*autoscaling.HPAScalingPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HPAScalingPolicy)(nil), (*autoscalingv2beta2.HPAScalingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(a.(*autoscaling.HPAScalingPolicy), b.(*autoscalingv2beta2.HPAScalingPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.HorizontalPodAutoscalerBehavior)(nil), (*autoscaling.HorizontalPodAutoscalerBehavior)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(a.(*autoscalingv2beta2.HorizontalPodAutoscalerBehavior), b.(*autoscaling.HorizontalPodAutoscalerBehavior), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerBehavior)(nil), (*autoscalingv2beta2.HorizontalPodAutoscalerBehavior)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(a.(*autoscaling.HorizontalPodAutoscalerBehavior), b.(*autoscalingv2beta2.HorizontalPodAutoscalerBehavior), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.HorizontalPodAutoscalerCondition)(nil), (*autoscaling.HorizontalPodAutoscalerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(a.(*autoscalingv2beta2.HorizontalPodAutoscalerCondition), b.(*autoscaling.HorizontalPodAutoscalerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerCondition)(nil), (*autoscalingv2beta2.HorizontalPodAutoscalerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta2_HorizontalPodAutoscalerCondition(a.(*autoscaling.HorizontalPodAutoscalerCondition), b.(*autoscalingv2beta2.HorizontalPodAutoscalerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.HorizontalPodAutoscalerList)(nil), (*autoscaling.HorizontalPodAutoscalerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(a.(*autoscalingv2beta2.HorizontalPodAutoscalerList), b.(*autoscaling.HorizontalPodAutoscalerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerList)(nil), (*autoscalingv2beta2.HorizontalPodAutoscalerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerList_To_v2beta2_HorizontalPodAutoscalerList(a.(*autoscaling.HorizontalPodAutoscalerList), b.(*autoscalingv2beta2.HorizontalPodAutoscalerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.HorizontalPodAutoscalerSpec)(nil), (*autoscaling.HorizontalPodAutoscalerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(a.(*autoscalingv2beta2.HorizontalPodAutoscalerSpec), b.(*autoscaling.HorizontalPodAutoscalerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerSpec)(nil), (*autoscalingv2beta2.HorizontalPodAutoscalerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPodAutoscalerSpec(a.(*autoscaling.HorizontalPodAutoscalerSpec), b.(*autoscalingv2beta2.HorizontalPodAutoscalerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.HorizontalPodAutoscalerStatus)(nil), (*autoscaling.HorizontalPodAutoscalerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(a.(*autoscalingv2beta2.HorizontalPodAutoscalerStatus), b.(*autoscaling.HorizontalPodAutoscalerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerStatus)(nil), (*autoscalingv2beta2.HorizontalPodAutoscalerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta2_HorizontalPodAutoscalerStatus(a.(*autoscaling.HorizontalPodAutoscalerStatus), b.(*autoscalingv2beta2.HorizontalPodAutoscalerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.MetricIdentifier)(nil), (*autoscaling.MetricIdentifier)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(a.(*autoscalingv2beta2.MetricIdentifier), b.(*autoscaling.MetricIdentifier), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricIdentifier)(nil), (*autoscalingv2beta2.MetricIdentifier)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(a.(*autoscaling.MetricIdentifier), b.(*autoscalingv2beta2.MetricIdentifier), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.MetricSpec)(nil), (*autoscaling.MetricSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_MetricSpec_To_autoscaling_MetricSpec(a.(*autoscalingv2beta2.MetricSpec), b.(*autoscaling.MetricSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricSpec)(nil), (*autoscalingv2beta2.MetricSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricSpec_To_v2beta2_MetricSpec(a.(*autoscaling.MetricSpec), b.(*autoscalingv2beta2.MetricSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.MetricStatus)(nil), (*autoscaling.MetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_MetricStatus_To_autoscaling_MetricStatus(a.(*autoscalingv2beta2.MetricStatus), b.(*autoscaling.MetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricStatus)(nil), (*autoscalingv2beta2.MetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricStatus_To_v2beta2_MetricStatus(a.(*autoscaling.MetricStatus), b.(*autoscalingv2beta2.MetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.MetricTarget)(nil), (*autoscaling.MetricTarget)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(a.(*autoscalingv2beta2.MetricTarget), b.(*autoscaling.MetricTarget), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricTarget)(nil), (*autoscalingv2beta2.MetricTarget)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(a.(*autoscaling.MetricTarget), b.(*autoscalingv2beta2.MetricTarget), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.MetricValueStatus)(nil), (*autoscaling.MetricValueStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(a.(*autoscalingv2beta2.MetricValueStatus), b.(*autoscaling.MetricValueStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.MetricValueStatus)(nil), (*autoscalingv2beta2.MetricValueStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(a.(*autoscaling.MetricValueStatus), b.(*autoscalingv2beta2.MetricValueStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ObjectMetricSource)(nil), (*autoscaling.ObjectMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ObjectMetricSource_To_autoscaling_ObjectMetricSource(a.(*autoscalingv2beta2.ObjectMetricSource), b.(*autoscaling.ObjectMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ObjectMetricSource)(nil), (*autoscalingv2beta2.ObjectMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ObjectMetricSource_To_v2beta2_ObjectMetricSource(a.(*autoscaling.ObjectMetricSource), b.(*autoscalingv2beta2.ObjectMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ObjectMetricStatus)(nil), (*autoscaling.ObjectMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(a.(*autoscalingv2beta2.ObjectMetricStatus), b.(*autoscaling.ObjectMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ObjectMetricStatus)(nil), (*autoscalingv2beta2.ObjectMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ObjectMetricStatus_To_v2beta2_ObjectMetricStatus(a.(*autoscaling.ObjectMetricStatus), b.(*autoscalingv2beta2.ObjectMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.PodsMetricSource)(nil), (*autoscaling.PodsMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_PodsMetricSource_To_autoscaling_PodsMetricSource(a.(*autoscalingv2beta2.PodsMetricSource), b.(*autoscaling.PodsMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.PodsMetricSource)(nil), (*autoscalingv2beta2.PodsMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_PodsMetricSource_To_v2beta2_PodsMetricSource(a.(*autoscaling.PodsMetricSource), b.(*autoscalingv2beta2.PodsMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.PodsMetricStatus)(nil), (*autoscaling.PodsMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_PodsMetricStatus_To_autoscaling_PodsMetricStatus(a.(*autoscalingv2beta2.PodsMetricStatus), b.(*autoscaling.PodsMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.PodsMetricStatus)(nil), (*autoscalingv2beta2.PodsMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_PodsMetricStatus_To_v2beta2_PodsMetricStatus(a.(*autoscaling.PodsMetricStatus), b.(*autoscalingv2beta2.PodsMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ResourceMetricSource)(nil), (*autoscaling.ResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ResourceMetricSource_To_autoscaling_ResourceMetricSource(a.(*autoscalingv2beta2.ResourceMetricSource), b.(*autoscaling.ResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ResourceMetricSource)(nil), (*autoscalingv2beta2.ResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ResourceMetricSource_To_v2beta2_ResourceMetricSource(a.(*autoscaling.ResourceMetricSource), b.(*autoscalingv2beta2.ResourceMetricSource), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscalingv2beta2.ResourceMetricStatus)(nil), (*autoscaling.ResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(a.(*autoscalingv2beta2.ResourceMetricStatus), b.(*autoscaling.ResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*autoscaling.ResourceMetricStatus)(nil), (*autoscalingv2beta2.ResourceMetricStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_ResourceMetricStatus_To_v2beta2_ResourceMetricStatus(a.(*autoscaling.ResourceMetricStatus), b.(*autoscalingv2beta2.ResourceMetricStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.HPAScalingRules)(nil), (*autoscalingv2beta2.HPAScalingRules)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(a.(*autoscaling.HPAScalingRules), b.(*autoscalingv2beta2.HPAScalingRules), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscaling.HorizontalPodAutoscaler)(nil), (*autoscalingv2beta2.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(a.(*autoscaling.HorizontalPodAutoscaler), b.(*autoscalingv2beta2.HorizontalPodAutoscaler), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta2.HPAScalingRules)(nil), (*autoscaling.HPAScalingRules)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(a.(*autoscalingv2beta2.HPAScalingRules), b.(*autoscaling.HPAScalingRules), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*autoscalingv2beta2.HorizontalPodAutoscaler)(nil), (*autoscaling.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(a.(*autoscalingv2beta2.HorizontalPodAutoscaler), b.(*autoscaling.HorizontalPodAutoscaler), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(in *autoscalingv2beta2.ContainerResourceMetricSource, out *autoscaling.ContainerResourceMetricSource, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - if err := Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - out.Container = in.Container - return nil -} - -// Convert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource is an autogenerated conversion function. -func Convert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(in *autoscalingv2beta2.ContainerResourceMetricSource, out *autoscaling.ContainerResourceMetricSource, s conversion.Scope) error { - return autoConvert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(in, out, s) -} - -func autoConvert_autoscaling_ContainerResourceMetricSource_To_v2beta2_ContainerResourceMetricSource(in *autoscaling.ContainerResourceMetricSource, out *autoscalingv2beta2.ContainerResourceMetricSource, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.Container = in.Container - if err := Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ContainerResourceMetricSource_To_v2beta2_ContainerResourceMetricSource is an autogenerated conversion function. -func Convert_autoscaling_ContainerResourceMetricSource_To_v2beta2_ContainerResourceMetricSource(in *autoscaling.ContainerResourceMetricSource, out *autoscalingv2beta2.ContainerResourceMetricSource, s conversion.Scope) error { - return autoConvert_autoscaling_ContainerResourceMetricSource_To_v2beta2_ContainerResourceMetricSource(in, out, s) -} - -func autoConvert_v2beta2_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(in *autoscalingv2beta2.ContainerResourceMetricStatus, out *autoscaling.ContainerResourceMetricStatus, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - if err := Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - out.Container = in.Container - return nil -} - -// Convert_v2beta2_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus is an autogenerated conversion function. -func Convert_v2beta2_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(in *autoscalingv2beta2.ContainerResourceMetricStatus, out *autoscaling.ContainerResourceMetricStatus, s conversion.Scope) error { - return autoConvert_v2beta2_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(in, out, s) -} - -func autoConvert_autoscaling_ContainerResourceMetricStatus_To_v2beta2_ContainerResourceMetricStatus(in *autoscaling.ContainerResourceMetricStatus, out *autoscalingv2beta2.ContainerResourceMetricStatus, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - out.Container = in.Container - if err := Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta2_ContainerResourceMetricStatus is an autogenerated conversion function. -func Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta2_ContainerResourceMetricStatus(in *autoscaling.ContainerResourceMetricStatus, out *autoscalingv2beta2.ContainerResourceMetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_ContainerResourceMetricStatus_To_v2beta2_ContainerResourceMetricStatus(in, out, s) -} - -func autoConvert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(in *autoscalingv2beta2.CrossVersionObjectReference, out *autoscaling.CrossVersionObjectReference, s conversion.Scope) error { - out.Kind = in.Kind - out.Name = in.Name - out.APIVersion = in.APIVersion - return nil -} - -// Convert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference is an autogenerated conversion function. -func Convert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(in *autoscalingv2beta2.CrossVersionObjectReference, out *autoscaling.CrossVersionObjectReference, s conversion.Scope) error { - return autoConvert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(in, out, s) -} - -func autoConvert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(in *autoscaling.CrossVersionObjectReference, out *autoscalingv2beta2.CrossVersionObjectReference, s conversion.Scope) error { - out.Kind = in.Kind - out.Name = in.Name - out.APIVersion = in.APIVersion - return nil -} - -// Convert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference is an autogenerated conversion function. -func Convert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(in *autoscaling.CrossVersionObjectReference, out *autoscalingv2beta2.CrossVersionObjectReference, s conversion.Scope) error { - return autoConvert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(in, out, s) -} - -func autoConvert_v2beta2_ExternalMetricSource_To_autoscaling_ExternalMetricSource(in *autoscalingv2beta2.ExternalMetricSource, out *autoscaling.ExternalMetricSource, s conversion.Scope) error { - if err := Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_ExternalMetricSource_To_autoscaling_ExternalMetricSource is an autogenerated conversion function. -func Convert_v2beta2_ExternalMetricSource_To_autoscaling_ExternalMetricSource(in *autoscalingv2beta2.ExternalMetricSource, out *autoscaling.ExternalMetricSource, s conversion.Scope) error { - return autoConvert_v2beta2_ExternalMetricSource_To_autoscaling_ExternalMetricSource(in, out, s) -} - -func autoConvert_autoscaling_ExternalMetricSource_To_v2beta2_ExternalMetricSource(in *autoscaling.ExternalMetricSource, out *autoscalingv2beta2.ExternalMetricSource, s conversion.Scope) error { - if err := Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ExternalMetricSource_To_v2beta2_ExternalMetricSource is an autogenerated conversion function. -func Convert_autoscaling_ExternalMetricSource_To_v2beta2_ExternalMetricSource(in *autoscaling.ExternalMetricSource, out *autoscalingv2beta2.ExternalMetricSource, s conversion.Scope) error { - return autoConvert_autoscaling_ExternalMetricSource_To_v2beta2_ExternalMetricSource(in, out, s) -} - -func autoConvert_v2beta2_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(in *autoscalingv2beta2.ExternalMetricStatus, out *autoscaling.ExternalMetricStatus, s conversion.Scope) error { - if err := Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus is an autogenerated conversion function. -func Convert_v2beta2_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(in *autoscalingv2beta2.ExternalMetricStatus, out *autoscaling.ExternalMetricStatus, s conversion.Scope) error { - return autoConvert_v2beta2_ExternalMetricStatus_To_autoscaling_ExternalMetricStatus(in, out, s) -} - -func autoConvert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus(in *autoscaling.ExternalMetricStatus, out *autoscalingv2beta2.ExternalMetricStatus, s conversion.Scope) error { - if err := Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus is an autogenerated conversion function. -func Convert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus(in *autoscaling.ExternalMetricStatus, out *autoscalingv2beta2.ExternalMetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus(in, out, s) -} - -func autoConvert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(in *autoscalingv2beta2.HPAScalingPolicy, out *autoscaling.HPAScalingPolicy, s conversion.Scope) error { - out.Type = autoscaling.HPAScalingPolicyType(in.Type) - out.Value = in.Value - out.PeriodSeconds = in.PeriodSeconds - return nil -} - -// Convert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy is an autogenerated conversion function. -func Convert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(in *autoscalingv2beta2.HPAScalingPolicy, out *autoscaling.HPAScalingPolicy, s conversion.Scope) error { - return autoConvert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(in, out, s) -} - -func autoConvert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(in *autoscaling.HPAScalingPolicy, out *autoscalingv2beta2.HPAScalingPolicy, s conversion.Scope) error { - out.Type = autoscalingv2beta2.HPAScalingPolicyType(in.Type) - out.Value = in.Value - out.PeriodSeconds = in.PeriodSeconds - return nil -} - -// Convert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy is an autogenerated conversion function. -func Convert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(in *autoscaling.HPAScalingPolicy, out *autoscalingv2beta2.HPAScalingPolicy, s conversion.Scope) error { - return autoConvert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(in, out, s) -} - -func autoConvert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(in *autoscalingv2beta2.HPAScalingRules, out *autoscaling.HPAScalingRules, s conversion.Scope) error { - out.StabilizationWindowSeconds = (*int32)(unsafe.Pointer(in.StabilizationWindowSeconds)) - out.SelectPolicy = (*autoscaling.ScalingPolicySelect)(unsafe.Pointer(in.SelectPolicy)) - out.Policies = *(*[]autoscaling.HPAScalingPolicy)(unsafe.Pointer(&in.Policies)) - return nil -} - -func autoConvert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(in *autoscaling.HPAScalingRules, out *autoscalingv2beta2.HPAScalingRules, s conversion.Scope) error { - out.StabilizationWindowSeconds = (*int32)(unsafe.Pointer(in.StabilizationWindowSeconds)) - out.SelectPolicy = (*autoscalingv2beta2.ScalingPolicySelect)(unsafe.Pointer(in.SelectPolicy)) - out.Policies = *(*[]autoscalingv2beta2.HPAScalingPolicy)(unsafe.Pointer(&in.Policies)) - // WARNING: in.Tolerance requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *autoscalingv2beta2.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v2beta2_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *autoscalingv2beta2.HorizontalPodAutoscaler, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta2_HorizontalPodAutoscalerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func autoConvert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(in *autoscalingv2beta2.HorizontalPodAutoscalerBehavior, out *autoscaling.HorizontalPodAutoscalerBehavior, s conversion.Scope) error { - if in.ScaleUp != nil { - in, out := &in.ScaleUp, &out.ScaleUp - *out = new(autoscaling.HPAScalingRules) - if err := Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(*in, *out, s); err != nil { - return err - } - } else { - out.ScaleUp = nil - } - if in.ScaleDown != nil { - in, out := &in.ScaleDown, &out.ScaleDown - *out = new(autoscaling.HPAScalingRules) - if err := Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(*in, *out, s); err != nil { - return err - } - } else { - out.ScaleDown = nil - } - return nil -} - -// Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior is an autogenerated conversion function. -func Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(in *autoscalingv2beta2.HorizontalPodAutoscalerBehavior, out *autoscaling.HorizontalPodAutoscalerBehavior, s conversion.Scope) error { - return autoConvert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(in *autoscaling.HorizontalPodAutoscalerBehavior, out *autoscalingv2beta2.HorizontalPodAutoscalerBehavior, s conversion.Scope) error { - if in.ScaleUp != nil { - in, out := &in.ScaleUp, &out.ScaleUp - *out = new(autoscalingv2beta2.HPAScalingRules) - if err := Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(*in, *out, s); err != nil { - return err - } - } else { - out.ScaleUp = nil - } - if in.ScaleDown != nil { - in, out := &in.ScaleDown, &out.ScaleDown - *out = new(autoscalingv2beta2.HPAScalingRules) - if err := Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(*in, *out, s); err != nil { - return err - } - } else { - out.ScaleDown = nil - } - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(in *autoscaling.HorizontalPodAutoscalerBehavior, out *autoscalingv2beta2.HorizontalPodAutoscalerBehavior, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(in, out, s) -} - -func autoConvert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in *autoscalingv2beta2.HorizontalPodAutoscalerCondition, out *autoscaling.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - out.Type = autoscaling.HorizontalPodAutoscalerConditionType(in.Type) - out.Status = autoscaling.ConditionStatus(in.Status) - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition is an autogenerated conversion function. -func Convert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in *autoscalingv2beta2.HorizontalPodAutoscalerCondition, out *autoscaling.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - return autoConvert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta2_HorizontalPodAutoscalerCondition(in *autoscaling.HorizontalPodAutoscalerCondition, out *autoscalingv2beta2.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - out.Type = autoscalingv2beta2.HorizontalPodAutoscalerConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta2_HorizontalPodAutoscalerCondition is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta2_HorizontalPodAutoscalerCondition(in *autoscaling.HorizontalPodAutoscalerCondition, out *autoscalingv2beta2.HorizontalPodAutoscalerCondition, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerCondition_To_v2beta2_HorizontalPodAutoscalerCondition(in, out, s) -} - -func autoConvert_v2beta2_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(in *autoscalingv2beta2.HorizontalPodAutoscalerList, out *autoscaling.HorizontalPodAutoscalerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]autoscaling.HorizontalPodAutoscaler, len(*in)) - for i := range *in { - if err := Convert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v2beta2_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList is an autogenerated conversion function. -func Convert_v2beta2_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(in *autoscalingv2beta2.HorizontalPodAutoscalerList, out *autoscaling.HorizontalPodAutoscalerList, s conversion.Scope) error { - return autoConvert_v2beta2_HorizontalPodAutoscalerList_To_autoscaling_HorizontalPodAutoscalerList(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerList_To_v2beta2_HorizontalPodAutoscalerList(in *autoscaling.HorizontalPodAutoscalerList, out *autoscalingv2beta2.HorizontalPodAutoscalerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]autoscalingv2beta2.HorizontalPodAutoscaler, len(*in)) - for i := range *in { - if err := Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerList_To_v2beta2_HorizontalPodAutoscalerList is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerList_To_v2beta2_HorizontalPodAutoscalerList(in *autoscaling.HorizontalPodAutoscalerList, out *autoscalingv2beta2.HorizontalPodAutoscalerList, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerList_To_v2beta2_HorizontalPodAutoscalerList(in, out, s) -} - -func autoConvert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(in *autoscalingv2beta2.HorizontalPodAutoscalerSpec, out *autoscaling.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - if err := Convert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(&in.ScaleTargetRef, &out.ScaleTargetRef, s); err != nil { - return err - } - out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas)) - out.MaxReplicas = in.MaxReplicas - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics - *out = make([]autoscaling.MetricSpec, len(*in)) - for i := range *in { - if err := Convert_v2beta2_MetricSpec_To_autoscaling_MetricSpec(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Metrics = nil - } - if in.Behavior != nil { - in, out := &in.Behavior, &out.Behavior - *out = new(autoscaling.HorizontalPodAutoscalerBehavior) - if err := Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(*in, *out, s); err != nil { - return err - } - } else { - out.Behavior = nil - } - return nil -} - -// Convert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec is an autogenerated conversion function. -func Convert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(in *autoscalingv2beta2.HorizontalPodAutoscalerSpec, out *autoscaling.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - return autoConvert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPodAutoscalerSpec(in *autoscaling.HorizontalPodAutoscalerSpec, out *autoscalingv2beta2.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - if err := Convert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(&in.ScaleTargetRef, &out.ScaleTargetRef, s); err != nil { - return err - } - out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas)) - out.MaxReplicas = in.MaxReplicas - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics - *out = make([]autoscalingv2beta2.MetricSpec, len(*in)) - for i := range *in { - if err := Convert_autoscaling_MetricSpec_To_v2beta2_MetricSpec(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Metrics = nil - } - if in.Behavior != nil { - in, out := &in.Behavior, &out.Behavior - *out = new(autoscalingv2beta2.HorizontalPodAutoscalerBehavior) - if err := Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(*in, *out, s); err != nil { - return err - } - } else { - out.Behavior = nil - } - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPodAutoscalerSpec is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPodAutoscalerSpec(in *autoscaling.HorizontalPodAutoscalerSpec, out *autoscalingv2beta2.HorizontalPodAutoscalerSpec, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPodAutoscalerSpec(in, out, s) -} - -func autoConvert_v2beta2_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in *autoscalingv2beta2.HorizontalPodAutoscalerStatus, out *autoscaling.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - out.ObservedGeneration = (*int64)(unsafe.Pointer(in.ObservedGeneration)) - out.LastScaleTime = (*metav1.Time)(unsafe.Pointer(in.LastScaleTime)) - out.CurrentReplicas = in.CurrentReplicas - out.DesiredReplicas = in.DesiredReplicas - if in.CurrentMetrics != nil { - in, out := &in.CurrentMetrics, &out.CurrentMetrics - *out = make([]autoscaling.MetricStatus, len(*in)) - for i := range *in { - if err := Convert_v2beta2_MetricStatus_To_autoscaling_MetricStatus(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.CurrentMetrics = nil - } - out.Conditions = *(*[]autoscaling.HorizontalPodAutoscalerCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_v2beta2_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus is an autogenerated conversion function. -func Convert_v2beta2_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in *autoscalingv2beta2.HorizontalPodAutoscalerStatus, out *autoscaling.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - return autoConvert_v2beta2_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in, out, s) -} - -func autoConvert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta2_HorizontalPodAutoscalerStatus(in *autoscaling.HorizontalPodAutoscalerStatus, out *autoscalingv2beta2.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - out.ObservedGeneration = (*int64)(unsafe.Pointer(in.ObservedGeneration)) - out.LastScaleTime = (*metav1.Time)(unsafe.Pointer(in.LastScaleTime)) - out.CurrentReplicas = in.CurrentReplicas - out.DesiredReplicas = in.DesiredReplicas - if in.CurrentMetrics != nil { - in, out := &in.CurrentMetrics, &out.CurrentMetrics - *out = make([]autoscalingv2beta2.MetricStatus, len(*in)) - for i := range *in { - if err := Convert_autoscaling_MetricStatus_To_v2beta2_MetricStatus(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.CurrentMetrics = nil - } - out.Conditions = *(*[]autoscalingv2beta2.HorizontalPodAutoscalerCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta2_HorizontalPodAutoscalerStatus is an autogenerated conversion function. -func Convert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta2_HorizontalPodAutoscalerStatus(in *autoscaling.HorizontalPodAutoscalerStatus, out *autoscalingv2beta2.HorizontalPodAutoscalerStatus, s conversion.Scope) error { - return autoConvert_autoscaling_HorizontalPodAutoscalerStatus_To_v2beta2_HorizontalPodAutoscalerStatus(in, out, s) -} - -func autoConvert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(in *autoscalingv2beta2.MetricIdentifier, out *autoscaling.MetricIdentifier, s conversion.Scope) error { - out.Name = in.Name - out.Selector = (*metav1.LabelSelector)(unsafe.Pointer(in.Selector)) - return nil -} - -// Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier is an autogenerated conversion function. -func Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(in *autoscalingv2beta2.MetricIdentifier, out *autoscaling.MetricIdentifier, s conversion.Scope) error { - return autoConvert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(in, out, s) -} - -func autoConvert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(in *autoscaling.MetricIdentifier, out *autoscalingv2beta2.MetricIdentifier, s conversion.Scope) error { - out.Name = in.Name - out.Selector = (*metav1.LabelSelector)(unsafe.Pointer(in.Selector)) - return nil -} - -// Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier is an autogenerated conversion function. -func Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(in *autoscaling.MetricIdentifier, out *autoscalingv2beta2.MetricIdentifier, s conversion.Scope) error { - return autoConvert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(in, out, s) -} - -func autoConvert_v2beta2_MetricSpec_To_autoscaling_MetricSpec(in *autoscalingv2beta2.MetricSpec, out *autoscaling.MetricSpec, s conversion.Scope) error { - out.Type = autoscaling.MetricSourceType(in.Type) - out.Object = (*autoscaling.ObjectMetricSource)(unsafe.Pointer(in.Object)) - out.Pods = (*autoscaling.PodsMetricSource)(unsafe.Pointer(in.Pods)) - out.Resource = (*autoscaling.ResourceMetricSource)(unsafe.Pointer(in.Resource)) - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscaling.ContainerResourceMetricSource) - if err := Convert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - out.External = (*autoscaling.ExternalMetricSource)(unsafe.Pointer(in.External)) - return nil -} - -// Convert_v2beta2_MetricSpec_To_autoscaling_MetricSpec is an autogenerated conversion function. -func Convert_v2beta2_MetricSpec_To_autoscaling_MetricSpec(in *autoscalingv2beta2.MetricSpec, out *autoscaling.MetricSpec, s conversion.Scope) error { - return autoConvert_v2beta2_MetricSpec_To_autoscaling_MetricSpec(in, out, s) -} - -func autoConvert_autoscaling_MetricSpec_To_v2beta2_MetricSpec(in *autoscaling.MetricSpec, out *autoscalingv2beta2.MetricSpec, s conversion.Scope) error { - out.Type = autoscalingv2beta2.MetricSourceType(in.Type) - out.Object = (*autoscalingv2beta2.ObjectMetricSource)(unsafe.Pointer(in.Object)) - out.Pods = (*autoscalingv2beta2.PodsMetricSource)(unsafe.Pointer(in.Pods)) - out.Resource = (*autoscalingv2beta2.ResourceMetricSource)(unsafe.Pointer(in.Resource)) - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscalingv2beta2.ContainerResourceMetricSource) - if err := Convert_autoscaling_ContainerResourceMetricSource_To_v2beta2_ContainerResourceMetricSource(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - out.External = (*autoscalingv2beta2.ExternalMetricSource)(unsafe.Pointer(in.External)) - return nil -} - -// Convert_autoscaling_MetricSpec_To_v2beta2_MetricSpec is an autogenerated conversion function. -func Convert_autoscaling_MetricSpec_To_v2beta2_MetricSpec(in *autoscaling.MetricSpec, out *autoscalingv2beta2.MetricSpec, s conversion.Scope) error { - return autoConvert_autoscaling_MetricSpec_To_v2beta2_MetricSpec(in, out, s) -} - -func autoConvert_v2beta2_MetricStatus_To_autoscaling_MetricStatus(in *autoscalingv2beta2.MetricStatus, out *autoscaling.MetricStatus, s conversion.Scope) error { - out.Type = autoscaling.MetricSourceType(in.Type) - out.Object = (*autoscaling.ObjectMetricStatus)(unsafe.Pointer(in.Object)) - out.Pods = (*autoscaling.PodsMetricStatus)(unsafe.Pointer(in.Pods)) - out.Resource = (*autoscaling.ResourceMetricStatus)(unsafe.Pointer(in.Resource)) - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscaling.ContainerResourceMetricStatus) - if err := Convert_v2beta2_ContainerResourceMetricStatus_To_autoscaling_ContainerResourceMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - out.External = (*autoscaling.ExternalMetricStatus)(unsafe.Pointer(in.External)) - return nil -} - -// Convert_v2beta2_MetricStatus_To_autoscaling_MetricStatus is an autogenerated conversion function. -func Convert_v2beta2_MetricStatus_To_autoscaling_MetricStatus(in *autoscalingv2beta2.MetricStatus, out *autoscaling.MetricStatus, s conversion.Scope) error { - return autoConvert_v2beta2_MetricStatus_To_autoscaling_MetricStatus(in, out, s) -} - -func autoConvert_autoscaling_MetricStatus_To_v2beta2_MetricStatus(in *autoscaling.MetricStatus, out *autoscalingv2beta2.MetricStatus, s conversion.Scope) error { - out.Type = autoscalingv2beta2.MetricSourceType(in.Type) - out.Object = (*autoscalingv2beta2.ObjectMetricStatus)(unsafe.Pointer(in.Object)) - out.Pods = (*autoscalingv2beta2.PodsMetricStatus)(unsafe.Pointer(in.Pods)) - out.Resource = (*autoscalingv2beta2.ResourceMetricStatus)(unsafe.Pointer(in.Resource)) - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(autoscalingv2beta2.ContainerResourceMetricStatus) - if err := Convert_autoscaling_ContainerResourceMetricStatus_To_v2beta2_ContainerResourceMetricStatus(*in, *out, s); err != nil { - return err - } - } else { - out.ContainerResource = nil - } - out.External = (*autoscalingv2beta2.ExternalMetricStatus)(unsafe.Pointer(in.External)) - return nil -} - -// Convert_autoscaling_MetricStatus_To_v2beta2_MetricStatus is an autogenerated conversion function. -func Convert_autoscaling_MetricStatus_To_v2beta2_MetricStatus(in *autoscaling.MetricStatus, out *autoscalingv2beta2.MetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_MetricStatus_To_v2beta2_MetricStatus(in, out, s) -} - -func autoConvert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(in *autoscalingv2beta2.MetricTarget, out *autoscaling.MetricTarget, s conversion.Scope) error { - out.Type = autoscaling.MetricTargetType(in.Type) - out.Value = (*resource.Quantity)(unsafe.Pointer(in.Value)) - out.AverageValue = (*resource.Quantity)(unsafe.Pointer(in.AverageValue)) - out.AverageUtilization = (*int32)(unsafe.Pointer(in.AverageUtilization)) - return nil -} - -// Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget is an autogenerated conversion function. -func Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(in *autoscalingv2beta2.MetricTarget, out *autoscaling.MetricTarget, s conversion.Scope) error { - return autoConvert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(in, out, s) -} - -func autoConvert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(in *autoscaling.MetricTarget, out *autoscalingv2beta2.MetricTarget, s conversion.Scope) error { - out.Type = autoscalingv2beta2.MetricTargetType(in.Type) - out.Value = (*resource.Quantity)(unsafe.Pointer(in.Value)) - out.AverageValue = (*resource.Quantity)(unsafe.Pointer(in.AverageValue)) - out.AverageUtilization = (*int32)(unsafe.Pointer(in.AverageUtilization)) - return nil -} - -// Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget is an autogenerated conversion function. -func Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(in *autoscaling.MetricTarget, out *autoscalingv2beta2.MetricTarget, s conversion.Scope) error { - return autoConvert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(in, out, s) -} - -func autoConvert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(in *autoscalingv2beta2.MetricValueStatus, out *autoscaling.MetricValueStatus, s conversion.Scope) error { - out.Value = (*resource.Quantity)(unsafe.Pointer(in.Value)) - out.AverageValue = (*resource.Quantity)(unsafe.Pointer(in.AverageValue)) - out.AverageUtilization = (*int32)(unsafe.Pointer(in.AverageUtilization)) - return nil -} - -// Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus is an autogenerated conversion function. -func Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(in *autoscalingv2beta2.MetricValueStatus, out *autoscaling.MetricValueStatus, s conversion.Scope) error { - return autoConvert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(in, out, s) -} - -func autoConvert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(in *autoscaling.MetricValueStatus, out *autoscalingv2beta2.MetricValueStatus, s conversion.Scope) error { - out.Value = (*resource.Quantity)(unsafe.Pointer(in.Value)) - out.AverageValue = (*resource.Quantity)(unsafe.Pointer(in.AverageValue)) - out.AverageUtilization = (*int32)(unsafe.Pointer(in.AverageUtilization)) - return nil -} - -// Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus is an autogenerated conversion function. -func Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(in *autoscaling.MetricValueStatus, out *autoscalingv2beta2.MetricValueStatus, s conversion.Scope) error { - return autoConvert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(in, out, s) -} - -func autoConvert_v2beta2_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in *autoscalingv2beta2.ObjectMetricSource, out *autoscaling.ObjectMetricSource, s conversion.Scope) error { - if err := Convert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(&in.DescribedObject, &out.DescribedObject, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_ObjectMetricSource_To_autoscaling_ObjectMetricSource is an autogenerated conversion function. -func Convert_v2beta2_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in *autoscalingv2beta2.ObjectMetricSource, out *autoscaling.ObjectMetricSource, s conversion.Scope) error { - return autoConvert_v2beta2_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in, out, s) -} - -func autoConvert_autoscaling_ObjectMetricSource_To_v2beta2_ObjectMetricSource(in *autoscaling.ObjectMetricSource, out *autoscalingv2beta2.ObjectMetricSource, s conversion.Scope) error { - if err := Convert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(&in.DescribedObject, &out.DescribedObject, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ObjectMetricSource_To_v2beta2_ObjectMetricSource is an autogenerated conversion function. -func Convert_autoscaling_ObjectMetricSource_To_v2beta2_ObjectMetricSource(in *autoscaling.ObjectMetricSource, out *autoscalingv2beta2.ObjectMetricSource, s conversion.Scope) error { - return autoConvert_autoscaling_ObjectMetricSource_To_v2beta2_ObjectMetricSource(in, out, s) -} - -func autoConvert_v2beta2_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(in *autoscalingv2beta2.ObjectMetricStatus, out *autoscaling.ObjectMetricStatus, s conversion.Scope) error { - if err := Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - if err := Convert_v2beta2_CrossVersionObjectReference_To_autoscaling_CrossVersionObjectReference(&in.DescribedObject, &out.DescribedObject, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus is an autogenerated conversion function. -func Convert_v2beta2_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(in *autoscalingv2beta2.ObjectMetricStatus, out *autoscaling.ObjectMetricStatus, s conversion.Scope) error { - return autoConvert_v2beta2_ObjectMetricStatus_To_autoscaling_ObjectMetricStatus(in, out, s) -} - -func autoConvert_autoscaling_ObjectMetricStatus_To_v2beta2_ObjectMetricStatus(in *autoscaling.ObjectMetricStatus, out *autoscalingv2beta2.ObjectMetricStatus, s conversion.Scope) error { - if err := Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - if err := Convert_autoscaling_CrossVersionObjectReference_To_v2beta2_CrossVersionObjectReference(&in.DescribedObject, &out.DescribedObject, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ObjectMetricStatus_To_v2beta2_ObjectMetricStatus is an autogenerated conversion function. -func Convert_autoscaling_ObjectMetricStatus_To_v2beta2_ObjectMetricStatus(in *autoscaling.ObjectMetricStatus, out *autoscalingv2beta2.ObjectMetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_ObjectMetricStatus_To_v2beta2_ObjectMetricStatus(in, out, s) -} - -func autoConvert_v2beta2_PodsMetricSource_To_autoscaling_PodsMetricSource(in *autoscalingv2beta2.PodsMetricSource, out *autoscaling.PodsMetricSource, s conversion.Scope) error { - if err := Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_PodsMetricSource_To_autoscaling_PodsMetricSource is an autogenerated conversion function. -func Convert_v2beta2_PodsMetricSource_To_autoscaling_PodsMetricSource(in *autoscalingv2beta2.PodsMetricSource, out *autoscaling.PodsMetricSource, s conversion.Scope) error { - return autoConvert_v2beta2_PodsMetricSource_To_autoscaling_PodsMetricSource(in, out, s) -} - -func autoConvert_autoscaling_PodsMetricSource_To_v2beta2_PodsMetricSource(in *autoscaling.PodsMetricSource, out *autoscalingv2beta2.PodsMetricSource, s conversion.Scope) error { - if err := Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_PodsMetricSource_To_v2beta2_PodsMetricSource is an autogenerated conversion function. -func Convert_autoscaling_PodsMetricSource_To_v2beta2_PodsMetricSource(in *autoscaling.PodsMetricSource, out *autoscalingv2beta2.PodsMetricSource, s conversion.Scope) error { - return autoConvert_autoscaling_PodsMetricSource_To_v2beta2_PodsMetricSource(in, out, s) -} - -func autoConvert_v2beta2_PodsMetricStatus_To_autoscaling_PodsMetricStatus(in *autoscalingv2beta2.PodsMetricStatus, out *autoscaling.PodsMetricStatus, s conversion.Scope) error { - if err := Convert_v2beta2_MetricIdentifier_To_autoscaling_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_PodsMetricStatus_To_autoscaling_PodsMetricStatus is an autogenerated conversion function. -func Convert_v2beta2_PodsMetricStatus_To_autoscaling_PodsMetricStatus(in *autoscalingv2beta2.PodsMetricStatus, out *autoscaling.PodsMetricStatus, s conversion.Scope) error { - return autoConvert_v2beta2_PodsMetricStatus_To_autoscaling_PodsMetricStatus(in, out, s) -} - -func autoConvert_autoscaling_PodsMetricStatus_To_v2beta2_PodsMetricStatus(in *autoscaling.PodsMetricStatus, out *autoscalingv2beta2.PodsMetricStatus, s conversion.Scope) error { - if err := Convert_autoscaling_MetricIdentifier_To_v2beta2_MetricIdentifier(&in.Metric, &out.Metric, s); err != nil { - return err - } - if err := Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_PodsMetricStatus_To_v2beta2_PodsMetricStatus is an autogenerated conversion function. -func Convert_autoscaling_PodsMetricStatus_To_v2beta2_PodsMetricStatus(in *autoscaling.PodsMetricStatus, out *autoscalingv2beta2.PodsMetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_PodsMetricStatus_To_v2beta2_PodsMetricStatus(in, out, s) -} - -func autoConvert_v2beta2_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in *autoscalingv2beta2.ResourceMetricSource, out *autoscaling.ResourceMetricSource, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - if err := Convert_v2beta2_MetricTarget_To_autoscaling_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_ResourceMetricSource_To_autoscaling_ResourceMetricSource is an autogenerated conversion function. -func Convert_v2beta2_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in *autoscalingv2beta2.ResourceMetricSource, out *autoscaling.ResourceMetricSource, s conversion.Scope) error { - return autoConvert_v2beta2_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in, out, s) -} - -func autoConvert_autoscaling_ResourceMetricSource_To_v2beta2_ResourceMetricSource(in *autoscaling.ResourceMetricSource, out *autoscalingv2beta2.ResourceMetricSource, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - if err := Convert_autoscaling_MetricTarget_To_v2beta2_MetricTarget(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ResourceMetricSource_To_v2beta2_ResourceMetricSource is an autogenerated conversion function. -func Convert_autoscaling_ResourceMetricSource_To_v2beta2_ResourceMetricSource(in *autoscaling.ResourceMetricSource, out *autoscalingv2beta2.ResourceMetricSource, s conversion.Scope) error { - return autoConvert_autoscaling_ResourceMetricSource_To_v2beta2_ResourceMetricSource(in, out, s) -} - -func autoConvert_v2beta2_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in *autoscalingv2beta2.ResourceMetricStatus, out *autoscaling.ResourceMetricStatus, s conversion.Scope) error { - out.Name = core.ResourceName(in.Name) - if err := Convert_v2beta2_MetricValueStatus_To_autoscaling_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_v2beta2_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus is an autogenerated conversion function. -func Convert_v2beta2_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in *autoscalingv2beta2.ResourceMetricStatus, out *autoscaling.ResourceMetricStatus, s conversion.Scope) error { - return autoConvert_v2beta2_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in, out, s) -} - -func autoConvert_autoscaling_ResourceMetricStatus_To_v2beta2_ResourceMetricStatus(in *autoscaling.ResourceMetricStatus, out *autoscalingv2beta2.ResourceMetricStatus, s conversion.Scope) error { - out.Name = v1.ResourceName(in.Name) - if err := Convert_autoscaling_MetricValueStatus_To_v2beta2_MetricValueStatus(&in.Current, &out.Current, s); err != nil { - return err - } - return nil -} - -// Convert_autoscaling_ResourceMetricStatus_To_v2beta2_ResourceMetricStatus is an autogenerated conversion function. -func Convert_autoscaling_ResourceMetricStatus_To_v2beta2_ResourceMetricStatus(in *autoscaling.ResourceMetricStatus, out *autoscalingv2beta2.ResourceMetricStatus, s conversion.Scope) error { - return autoConvert_autoscaling_ResourceMetricStatus_To_v2beta2_ResourceMetricStatus(in, out, s) -} diff --git a/pkg/apis/autoscaling/v2beta2/zz_generated.defaults.go b/pkg/apis/autoscaling/v2beta2/zz_generated.defaults.go deleted file mode 100644 index 710cd34278b6c..0000000000000 --- a/pkg/apis/autoscaling/v2beta2/zz_generated.defaults.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&autoscalingv2beta2.HorizontalPodAutoscaler{}, func(obj interface{}) { - SetObjectDefaults_HorizontalPodAutoscaler(obj.(*autoscalingv2beta2.HorizontalPodAutoscaler)) - }) - scheme.AddTypeDefaultingFunc(&autoscalingv2beta2.HorizontalPodAutoscalerList{}, func(obj interface{}) { - SetObjectDefaults_HorizontalPodAutoscalerList(obj.(*autoscalingv2beta2.HorizontalPodAutoscalerList)) - }) - return nil -} - -func SetObjectDefaults_HorizontalPodAutoscaler(in *autoscalingv2beta2.HorizontalPodAutoscaler) { - SetDefaults_HorizontalPodAutoscaler(in) -} - -func SetObjectDefaults_HorizontalPodAutoscalerList(in *autoscalingv2beta2.HorizontalPodAutoscalerList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_HorizontalPodAutoscaler(a) - } -} diff --git a/pkg/apis/autoscaling/validation/declarative_validation_test.go b/pkg/apis/autoscaling/validation/declarative_validation_test.go index 514bb9c70397b..f0c4fad4cf8e4 100644 --- a/pkg/apis/autoscaling/validation/declarative_validation_test.go +++ b/pkg/apis/autoscaling/validation/declarative_validation_test.go @@ -52,7 +52,7 @@ func TestScaleDeclarativeValidation(t *testing.T) { "spec.replicas: negative replicas": { input: mkScale(setScaleSpecReplicas(-1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum").MarkAlpha(), }, }, } diff --git a/pkg/apis/autoscaling/validation/validation.go b/pkg/apis/autoscaling/validation/validation.go index 524212f53a105..34b7926147afc 100644 --- a/pkg/apis/autoscaling/validation/validation.go +++ b/pkg/apis/autoscaling/validation/validation.go @@ -19,8 +19,8 @@ package validation import ( "fmt" + "k8s.io/apimachinery/pkg/api/validate/content" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" - pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" @@ -57,10 +57,12 @@ func validateHorizontalPodAutoscalerSpec(autoscaler autoscaling.HorizontalPodAut if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < opts.MinReplicasLowerBound { allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), *autoscaler.MinReplicas, - fmt.Sprintf("must be greater than or equal to %d", opts.MinReplicasLowerBound))) + fmt.Sprintf("must be greater than or equal to %d", opts.MinReplicasLowerBound)).WithOrigin("minimum").MarkCoveredByDeclarative()) } - if autoscaler.MaxReplicas < 1 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than 0")) + if autoscaler.MaxReplicas == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("maxReplicas"), "must be set and greater than 0").MarkCoveredByDeclarative()) + } else if autoscaler.MaxReplicas < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than or equal to 1").WithOrigin("minimum").MarkCoveredByDeclarative()) } if autoscaler.MinReplicas != nil && autoscaler.MaxReplicas < *autoscaler.MinReplicas { allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than or equal to `minReplicas`")) @@ -84,7 +86,7 @@ func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectRefer if len(ref.Kind) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Kind) { + for _, msg := range content.IsPathSegmentName(ref.Kind) { allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, msg)) } } @@ -92,7 +94,7 @@ func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectRefer if len(ref.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Name) { + for _, msg := range content.IsPathSegmentName(ref.Name) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg)) } } @@ -472,7 +474,7 @@ func validateMetricIdentifier(id autoscaling.MetricIdentifier, fldPath *field.Pa if len(id.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a metric name")) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(id.Name) { + for _, msg := range content.IsPathSegmentName(id.Name) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), id.Name, msg)) } } diff --git a/pkg/apis/batch/v1/doc.go b/pkg/apis/batch/v1/doc.go index e9f50b40c19cd..94c31f1fedb03 100644 --- a/pkg/apis/batch/v1/doc.go +++ b/pkg/apis/batch/v1/doc.go @@ -18,5 +18,7 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/batch/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/batch/v1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/batch/v1 package v1 diff --git a/pkg/apis/batch/v1/zz_generated.validations.go b/pkg/apis/batch/v1/zz_generated.validations.go new file mode 100644 index 0000000000000..10d0dd65c7d99 --- /dev/null +++ b/pkg/apis/batch/v1/zz_generated.validations.go @@ -0,0 +1,105 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + batchv1 "k8s.io/api/batch/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type CronJob + scheme.AddValidationFunc((*batchv1.CronJob)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_CronJob(ctx, op, nil /* fldPath */, obj.(*batchv1.CronJob), safe.Cast[*batchv1.CronJob](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_CronJob validates an instance of CronJob according +// to declarative validation rules in the API schema. +func Validate_CronJob(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *batchv1.CronJob) (errs field.ErrorList) { + // field batchv1.CronJob.TypeMeta has no validation + // field batchv1.CronJob.ObjectMeta has no validation + + // field batchv1.CronJob.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *batchv1.CronJobSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_CronJobSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *batchv1.CronJob) *batchv1.CronJobSpec { return &oldObj.Spec }), oldObj != nil)...) + + // field batchv1.CronJob.Status has no validation + return errs +} + +// Validate_CronJobSpec validates an instance of CronJobSpec according +// to declarative validation rules in the API schema. +func Validate_CronJobSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *batchv1.CronJobSpec) (errs field.ErrorList) { + // field batchv1.CronJobSpec.Schedule + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("schedule"), &obj.Schedule, safe.Field(oldObj, func(oldObj *batchv1.CronJobSpec) *string { return &oldObj.Schedule }), oldObj != nil)...) + + // field batchv1.CronJobSpec.TimeZone has no validation + // field batchv1.CronJobSpec.StartingDeadlineSeconds has no validation + // field batchv1.CronJobSpec.ConcurrencyPolicy has no validation + // field batchv1.CronJobSpec.Suspend has no validation + // field batchv1.CronJobSpec.JobTemplate has no validation + // field batchv1.CronJobSpec.SuccessfulJobsHistoryLimit has no validation + // field batchv1.CronJobSpec.FailedJobsHistoryLimit has no validation + return errs +} diff --git a/pkg/apis/batch/v1beta1/doc.go b/pkg/apis/batch/v1beta1/doc.go index 93b344748df0d..c800a89fc31fc 100644 --- a/pkg/apis/batch/v1beta1/doc.go +++ b/pkg/apis/batch/v1beta1/doc.go @@ -19,5 +19,7 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/batch/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/batch/v1beta1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/batch/v1beta1 package v1beta1 diff --git a/pkg/apis/batch/v1beta1/zz_generated.validations.go b/pkg/apis/batch/v1beta1/zz_generated.validations.go new file mode 100644 index 0000000000000..f01d5073e30aa --- /dev/null +++ b/pkg/apis/batch/v1beta1/zz_generated.validations.go @@ -0,0 +1,105 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + batchv1beta1 "k8s.io/api/batch/v1beta1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type CronJob + scheme.AddValidationFunc((*batchv1beta1.CronJob)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_CronJob(ctx, op, nil /* fldPath */, obj.(*batchv1beta1.CronJob), safe.Cast[*batchv1beta1.CronJob](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_CronJob validates an instance of CronJob according +// to declarative validation rules in the API schema. +func Validate_CronJob(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *batchv1beta1.CronJob) (errs field.ErrorList) { + // field batchv1beta1.CronJob.TypeMeta has no validation + // field batchv1beta1.CronJob.ObjectMeta has no validation + + // field batchv1beta1.CronJob.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *batchv1beta1.CronJobSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_CronJobSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *batchv1beta1.CronJob) *batchv1beta1.CronJobSpec { return &oldObj.Spec }), oldObj != nil)...) + + // field batchv1beta1.CronJob.Status has no validation + return errs +} + +// Validate_CronJobSpec validates an instance of CronJobSpec according +// to declarative validation rules in the API schema. +func Validate_CronJobSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *batchv1beta1.CronJobSpec) (errs field.ErrorList) { + // field batchv1beta1.CronJobSpec.Schedule + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("schedule"), &obj.Schedule, safe.Field(oldObj, func(oldObj *batchv1beta1.CronJobSpec) *string { return &oldObj.Schedule }), oldObj != nil)...) + + // field batchv1beta1.CronJobSpec.TimeZone has no validation + // field batchv1beta1.CronJobSpec.StartingDeadlineSeconds has no validation + // field batchv1beta1.CronJobSpec.ConcurrencyPolicy has no validation + // field batchv1beta1.CronJobSpec.Suspend has no validation + // field batchv1beta1.CronJobSpec.JobTemplate has no validation + // field batchv1beta1.CronJobSpec.SuccessfulJobsHistoryLimit has no validation + // field batchv1beta1.CronJobSpec.FailedJobsHistoryLimit has no validation + return errs +} diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index 6e7e093145531..94d82b1c41a3e 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -741,7 +741,7 @@ func ValidateJobStatusUpdate(job, oldJob *batch.Job, opts JobStatusValidationOpt // Note that we check `oldJob.Status.StartTime != nil` to allow transitioning from // startTime = nil to startTime != nil for unsuspended jobs, which is a desired transition. if oldJob.Status.StartTime != nil && !ptr.Equal(oldJob.Status.StartTime, job.Status.StartTime) && !ptr.Deref(job.Spec.Suspend, false) { - allErrs = append(allErrs, field.Required(statusFld.Child("startTime"), "startTime cannot be removed for unsuspended job")) + allErrs = append(allErrs, field.Invalid(statusFld.Child("startTime"), job.Status.StartTime, "field is immutable for unsuspended job once set")) } } if isJobSuccessCriteriaMet(oldJob) && !isJobSuccessCriteriaMet(job) { @@ -783,7 +783,7 @@ func validateCronJobSpec(spec, oldSpec *batch.CronJobSpec, fldPath *field.Path, allErrs := field.ErrorList{} if len(spec.Schedule) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), "")) + allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), "").MarkCoveredByDeclarative()) } else { allowTZInSchedule := false if oldSpec != nil { diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index 3bf0791852db1..63b36dd86e386 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -21,6 +21,7 @@ import ( "fmt" "strings" "testing" + "time" _ "time/tzdata" "github.com/google/go-cmp/cmp" @@ -2583,12 +2584,16 @@ func TestValidateJobUpdate(t *testing.T) { } func TestValidateJobUpdateStatus(t *testing.T) { + now := time.Now() + cases := map[string]struct { opts JobStatusValidationOptions old batch.Job update batch.Job wantErrs field.ErrorList + + cmpopts cmp.Options }{ "valid": { old: batch.Job{ @@ -2680,6 +2685,7 @@ func TestValidateJobUpdateStatus(t *testing.T) { {Type: field.ErrorTypeInvalid, Field: "status.ready"}, {Type: field.ErrorTypeInvalid, Field: "status.terminating"}, }, + cmpopts: cmp.Options{ignoreErrValueDetail}, }, "empty and duplicated uncounted pods": { old: batch.Job{ @@ -2709,12 +2715,105 @@ func TestValidateJobUpdateStatus(t *testing.T) { {Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.failed[3]"}, {Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods.failed[4]"}, }, + cmpopts: cmp.Options{ignoreErrValueDetail}, + }, + "immutable startTime for unsuspended job: with non-nil startTime": { + opts: JobStatusValidationOptions{ + RejectStartTimeUpdateForUnsuspendedJob: true, + }, + old: batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Spec: batch.JobSpec{ + Suspend: ptr.To(false), + }, + Status: batch.JobStatus{ + StartTime: &metav1.Time{ + Time: now, + }, + Active: 1, + }, + }, + update: batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Spec: batch.JobSpec{ + Suspend: ptr.To(false), + }, + Status: batch.JobStatus{ + StartTime: &metav1.Time{ + Time: now.Add(time.Second), // Attempt to change startTime + }, + Active: 1, + }, + }, + wantErrs: field.ErrorList{ + { + Type: field.ErrorTypeInvalid, + Field: "status.startTime", + BadValue: &metav1.Time{ + Time: now.Add(time.Second), + }, + Detail: "field is immutable for unsuspended job once set", + }, + }, + cmpopts: cmp.Options{cmpopts.IgnoreFields(field.Error{}, "Origin")}, + }, + "immutable startTime for unsuspended job: with nil startTime": { + opts: JobStatusValidationOptions{ + RejectStartTimeUpdateForUnsuspendedJob: true, + }, + old: batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Spec: batch.JobSpec{ + Suspend: ptr.To(false), + }, + Status: batch.JobStatus{ + StartTime: &metav1.Time{ + Time: now, + }, + Active: 1, + }, + }, + update: batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Spec: batch.JobSpec{ + Suspend: ptr.To(false), + }, + Status: batch.JobStatus{ + StartTime: nil, + Active: 1, + }, + }, + wantErrs: field.ErrorList{ + { + Type: field.ErrorTypeInvalid, + Field: "status.startTime", + BadValue: (*metav1.Time)(nil), + Detail: "field is immutable for unsuspended job once set", + }, + }, + cmpopts: cmp.Options{cmpopts.IgnoreFields(field.Error{}, "Origin")}, }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { errs := ValidateJobUpdateStatus(&tc.update, &tc.old, tc.opts) - if diff := cmp.Diff(tc.wantErrs, errs, ignoreErrValueDetail); diff != "" { + if diff := cmp.Diff(tc.wantErrs, errs, tc.cmpopts); diff != "" { t.Errorf("Unexpected errors (-want,+got):\n%s", diff) } }) diff --git a/pkg/apis/certificates/types.go b/pkg/apis/certificates/types.go index 1f7e345406b64..78429bbdf0f4d 100644 --- a/pkg/apis/certificates/types.go +++ b/pkg/apis/certificates/types.go @@ -340,8 +340,7 @@ type PodCertificateRequestSpec struct { // seconds (1 hour). This constraint is enforced by kube-apiserver. MaxExpirationSeconds *int32 - // pkixPublicKey is the PKIX-serialized public key the signer will issue the - // certificate to. + // The PKIX-serialized public key the signer will issue the certificate to. // // The key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, // or ED25519. Note that this list may be expanded in the future. @@ -352,13 +351,20 @@ type PodCertificateRequestSpec struct { // setting a status.conditions entry with a type of "Denied" and a reason of // "UnsupportedKeyType". It may also suggest a key type that it does support // in the message field. + // + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. Signer + // implementations should extract the public key from the StubPKCS10Request + // field. + // + // +optional PKIXPublicKey []byte - // proofOfPossession proves that the requesting kubelet holds the private - // key corresponding to pkixPublicKey. + // A proof that the requesting kubelet holds the private key corresponding + // to pkixPublicKey. // // It is contructed by signing the ASCII bytes of the pod's UID using - // `PKIXPublicKey`. + // `pkixPublicKey`. // // kube-apiserver validates the proof of possession during creation of the // PodCertificateRequest. @@ -374,8 +380,33 @@ type PodCertificateRequestSpec struct { // If the key is an ED25519 key, the the signature is as described by the // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the // golang library crypto/ed25519.Sign). + // + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. + // + // +optional ProofOfPossession []byte + // A PKCS#10 certificate signing request (DER-serialized) generated by + // Kubelet using the subject private key. + // + // Most signer implementations will ignore the contents of the CSR except to + // extract the subject public key. The API server automatically verifies the + // CSR signature during admission, so the signer does not need to repeat the + // verification. CSRs generated by kubelet are completely empty. + // + // The subject public key must be one of RSA3072, RSA4096, ECDSAP256, + // ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in + // the future. + // + // Signer implementations do not need to support all key types supported by + // kube-apiserver and kubelet. If a signer does not support the key type + // used for a given PodCertificateRequest, it must deny the request by + // setting a status.conditions entry with a type of "Denied" and a reason of + // "UnsupportedKeyType". It may also suggest a key type that it does support + // in the message field. + StubPKCS10Request []byte + // unverifiedUserAnnotations allow pod authors to pass additional information to // the signer implementation. Kubernetes does not restrict or validate this // metadata in any way. @@ -485,6 +516,9 @@ const ( // MaxProofOfPossessionSize is the maximum size permitted for the // ProofOfPossession field. MaxProofOfPossessionSize = 10 * 1024 + // MaxStubPKCS10RequestSize is the maximum size permitted for the + // UnverifiedPKCS10Request field. + MaxStubPKCS10RequestSize = 10 * 1024 // MaxCertificateChainSize is the maximum size permitted for the // CertificateChain field. // diff --git a/pkg/apis/certificates/v1/zz_generated.validations.go b/pkg/apis/certificates/v1/zz_generated.validations.go index 0e462a1700dc0..691ec59b03720 100644 --- a/pkg/apis/certificates/v1/zz_generated.validations.go +++ b/pkg/apis/certificates/v1/zz_generated.validations.go @@ -47,14 +47,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type CertificateSigningRequestList - scheme.AddValidationFunc((*certificatesv1.CertificateSigningRequestList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_CertificateSigningRequestList(ctx, op, nil /* fldPath */, obj.(*certificatesv1.CertificateSigningRequestList), safe.Cast[*certificatesv1.CertificateSigningRequestList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } @@ -82,29 +74,6 @@ func Validate_CertificateSigningRequest(ctx context.Context, op operation.Operat return errs } -// Validate_CertificateSigningRequestList validates an instance of CertificateSigningRequestList according -// to declarative validation rules in the API schema. -func Validate_CertificateSigningRequestList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *certificatesv1.CertificateSigningRequestList) (errs field.ErrorList) { - // field certificatesv1.CertificateSigningRequestList.TypeMeta has no validation - // field certificatesv1.CertificateSigningRequestList.ListMeta has no validation - - // field certificatesv1.CertificateSigningRequestList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []certificatesv1.CertificateSigningRequest, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_CertificateSigningRequest)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *certificatesv1.CertificateSigningRequestList) []certificatesv1.CertificateSigningRequest { - return oldObj.Items - }), oldObj != nil)...) - - return errs -} - var zeroOrOneOfMembershipFor_k8s_io_api_certificates_v1_CertificateSigningRequestStatus_conditions_ = validate.NewUnionMembership(validate.NewUnionMember("conditions[{\"type\": \"Approved\"}]"), validate.NewUnionMember("conditions[{\"type\": \"Denied\"}]")) // Validate_CertificateSigningRequestStatus validates an instance of CertificateSigningRequestStatus according @@ -120,7 +89,7 @@ func Validate_CertificateSigningRequestStatus(ctx context.Context, op operation. } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -140,7 +109,7 @@ func Validate_CertificateSigningRequestStatus(ctx context.Context, op operation. } } return false - })...) + }).MarkAlpha()...) return }(fldPath.Child("conditions"), obj.Conditions, safe.Field(oldObj, func(oldObj *certificatesv1.CertificateSigningRequestStatus) []certificatesv1.CertificateSigningRequestCondition { return oldObj.Conditions diff --git a/pkg/apis/certificates/v1beta1/zz_generated.conversion.go b/pkg/apis/certificates/v1beta1/zz_generated.conversion.go index 32315f49b16ba..147f5d815d130 100644 --- a/pkg/apis/certificates/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/certificates/v1beta1/zz_generated.conversion.go @@ -463,6 +463,7 @@ func autoConvert_v1beta1_PodCertificateRequestSpec_To_certificates_PodCertificat out.MaxExpirationSeconds = (*int32)(unsafe.Pointer(in.MaxExpirationSeconds)) out.PKIXPublicKey = *(*[]byte)(unsafe.Pointer(&in.PKIXPublicKey)) out.ProofOfPossession = *(*[]byte)(unsafe.Pointer(&in.ProofOfPossession)) + out.StubPKCS10Request = *(*[]byte)(unsafe.Pointer(&in.StubPKCS10Request)) out.UnverifiedUserAnnotations = *(*map[string]string)(unsafe.Pointer(&in.UnverifiedUserAnnotations)) return nil } @@ -483,6 +484,7 @@ func autoConvert_certificates_PodCertificateRequestSpec_To_v1beta1_PodCertificat out.MaxExpirationSeconds = (*int32)(unsafe.Pointer(in.MaxExpirationSeconds)) out.PKIXPublicKey = *(*[]byte)(unsafe.Pointer(&in.PKIXPublicKey)) out.ProofOfPossession = *(*[]byte)(unsafe.Pointer(&in.ProofOfPossession)) + out.StubPKCS10Request = *(*[]byte)(unsafe.Pointer(&in.StubPKCS10Request)) out.UnverifiedUserAnnotations = *(*map[string]string)(unsafe.Pointer(&in.UnverifiedUserAnnotations)) return nil } diff --git a/pkg/apis/certificates/v1beta1/zz_generated.validations.go b/pkg/apis/certificates/v1beta1/zz_generated.validations.go index f103482ea7c3e..faad9d5a011e3 100644 --- a/pkg/apis/certificates/v1beta1/zz_generated.validations.go +++ b/pkg/apis/certificates/v1beta1/zz_generated.validations.go @@ -47,14 +47,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type CertificateSigningRequestList - scheme.AddValidationFunc((*certificatesv1beta1.CertificateSigningRequestList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_CertificateSigningRequestList(ctx, op, nil /* fldPath */, obj.(*certificatesv1beta1.CertificateSigningRequestList), safe.Cast[*certificatesv1beta1.CertificateSigningRequestList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } @@ -82,29 +74,6 @@ func Validate_CertificateSigningRequest(ctx context.Context, op operation.Operat return errs } -// Validate_CertificateSigningRequestList validates an instance of CertificateSigningRequestList according -// to declarative validation rules in the API schema. -func Validate_CertificateSigningRequestList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *certificatesv1beta1.CertificateSigningRequestList) (errs field.ErrorList) { - // field certificatesv1beta1.CertificateSigningRequestList.TypeMeta has no validation - // field certificatesv1beta1.CertificateSigningRequestList.ListMeta has no validation - - // field certificatesv1beta1.CertificateSigningRequestList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []certificatesv1beta1.CertificateSigningRequest, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_CertificateSigningRequest)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *certificatesv1beta1.CertificateSigningRequestList) []certificatesv1beta1.CertificateSigningRequest { - return oldObj.Items - }), oldObj != nil)...) - - return errs -} - var zeroOrOneOfMembershipFor_k8s_io_api_certificates_v1beta1_CertificateSigningRequestStatus_conditions_ = validate.NewUnionMembership(validate.NewUnionMember("conditions[{\"type\": \"Approved\"}]"), validate.NewUnionMember("conditions[{\"type\": \"Denied\"}]")) // Validate_CertificateSigningRequestStatus validates an instance of CertificateSigningRequestStatus according @@ -120,7 +89,7 @@ func Validate_CertificateSigningRequestStatus(ctx context.Context, op operation. } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -140,7 +109,7 @@ func Validate_CertificateSigningRequestStatus(ctx context.Context, op operation. } } return false - })...) + }).MarkAlpha()...) return }(fldPath.Child("conditions"), obj.Conditions, safe.Field(oldObj, func(oldObj *certificatesv1beta1.CertificateSigningRequestStatus) []certificatesv1beta1.CertificateSigningRequestCondition { return oldObj.Conditions diff --git a/pkg/apis/certificates/validation/validation.go b/pkg/apis/certificates/validation/validation.go index 0b2b6046bbdd6..d83b3ccc25d1c 100644 --- a/pkg/apis/certificates/validation/validation.go +++ b/pkg/apis/certificates/validation/validation.go @@ -646,16 +646,41 @@ func ValidatePodCertificateRequestCreate(req *certificates.PodCertificateRequest } } + // Either (PKIXPublicKey, ProofOfPossession) xor (StubPKCS10Request) + // must be set. + + //nolint:staticcheck // SA1019 this deprecated field still needs to be validated. + if len(req.Spec.StubPKCS10Request) != 0 && len(req.Spec.PKIXPublicKey) == 0 && len(req.Spec.ProofOfPossession) == 0 { + // Valid, using StubPKCS10Request + allErrors = append(allErrors, validateStubPKCS10Request(req)...) + return allErrors + } else if len(req.Spec.StubPKCS10Request) == 0 && len(req.Spec.PKIXPublicKey) != 0 && len(req.Spec.ProofOfPossession) != 0 { + // Valid, using PKIXPublicKey and ProofOfPossession + allErrors = append(allErrors, validateDeprecatedPKIXPublicKey(req)...) + return allErrors + } else { + // Invalid, any other combination. + allErrors = append(allErrors, field.Invalid(field.NewPath("spec"), field.OmitValueType{}, "exactly one of (stubPKCS10Request) or (pkixPublicKey, proofOfPossession) must be set")) + return allErrors + } +} + +func validateDeprecatedPKIXPublicKey(req *certificates.PodCertificateRequest) field.ErrorList { + var allErrors field.ErrorList + + //nolint:staticcheck // SA1019 this deprecated field still needs to be validated. if len(req.Spec.PKIXPublicKey) > certificates.MaxPKIXPublicKeySize { allErrors = append(allErrors, field.TooLong(field.NewPath("spec", "pkixPublicKey"), req.Spec.PKIXPublicKey, certificates.MaxPKIXPublicKeySize)) return allErrors } + //nolint:staticcheck // SA1019 this deprecated field still needs to be validated. if len(req.Spec.ProofOfPossession) > certificates.MaxProofOfPossessionSize { allErrors = append(allErrors, field.TooLong(field.NewPath("spec", "proofOfPossession"), req.Spec.ProofOfPossession, certificates.MaxProofOfPossessionSize)) return allErrors } + //nolint:staticcheck // SA1019 this deprecated field still needs to be validated. pubAny, err := x509.ParsePKIXPublicKey(req.Spec.PKIXPublicKey) if err != nil { allErrors = append(allErrors, field.Invalid(pkixPath, req.Spec.PKIXPublicKey, "must be a valid PKIX-serialized public key")) @@ -699,6 +724,54 @@ func ValidatePodCertificateRequestCreate(req *certificates.PodCertificateRequest return allErrors } +func validateStubPKCS10Request(req *certificates.PodCertificateRequest) field.ErrorList { + var allErrors field.ErrorList + + if len(req.Spec.StubPKCS10Request) > certificates.MaxStubPKCS10RequestSize { + allErrors = append(allErrors, field.TooLong(pkcs10ReqPath, req.Spec.StubPKCS10Request, certificates.MaxStubPKCS10RequestSize)) + return allErrors + } + + pkcs10Req, err := x509.ParseCertificateRequest(req.Spec.StubPKCS10Request) + if err != nil { + allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "must be a valid PKCS#10 CSR")) + return allErrors + } + + // Check key type and parameters + switch pkcs10Pub := pkcs10Req.PublicKey.(type) { + case ed25519.PublicKey: + // ed25519 has no key configuration to check + case *ecdsa.PublicKey: + if pkcs10Pub.Curve != elliptic.P256() && pkcs10Pub.Curve != elliptic.P384() && pkcs10Pub.Curve != elliptic.P521() { + allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, "curve "+pkcs10Pub.Curve.Params().Name, "elliptic public keys must use curve P256, P384, or P521")) + return allErrors + } + case *rsa.PublicKey: + if pkcs10Pub.Size()*8 != 3072 && pkcs10Pub.Size()*8 != 4096 { + allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, fmt.Sprintf("%d-bit modulus", pkcs10Pub.Size()*8), "RSA keys must have modulus size 3072 or 4096")) + return allErrors + } + default: + allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "unknown public key type; supported types are Ed25519, ECDSA, and RSA")) + return allErrors + } + + // We explicitly do not validate the contents of the stub CSR. Kubelet + // always generates empty CSRs. Having kube-apiserver not care about the + // contents eases any future evolution we want to do here. + // + // Signers shipped by the Kubernetes project should deny the request if the + // CSR is not empty. + + if err := pkcs10Req.CheckSignature(); err != nil { + allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "invalid signature")) + return allErrors + } + + return allErrors +} + func hashBytes(in []byte) []byte { out := sha256.Sum256(in) return out[:] @@ -707,6 +780,7 @@ func hashBytes(in []byte) []byte { var ( pkixPath = field.NewPath("spec", "pkixPublicKey") popPath = field.NewPath("spec", "proofOfPossession") + pkcs10ReqPath = field.NewPath("spec", "stubPKCS10Request") certChainPath = field.NewPath("status", "certificateChain") notBeforePath = field.NewPath("status", "notBefore") notAfterPath = field.NewPath("status", "notAfter") @@ -826,12 +900,25 @@ func ValidatePodCertificateRequestStatusUpdate(newReq, oldReq *certificates.PodC } } - // Was the certificate issued to the public key in the spec? - wantPKAny, err := x509.ParsePKIXPublicKey(oldReq.Spec.PKIXPublicKey) - if err != nil { - allErrors = append(allErrors, field.Invalid(pkixPath, oldReq.Spec.PKIXPublicKey, "must be a valid PKIX-serialized public key")) - return allErrors + // Get the public key from either StubPKCS10Request or PKIXPublicKey. + var wantPKAny crypto.PublicKey + if len(oldReq.Spec.StubPKCS10Request) != 0 { + pkcs10Req, err := x509.ParseCertificateRequest(oldReq.Spec.StubPKCS10Request) + if err != nil { + allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "must be a valid PKCS#10 CSR")) + return allErrors + } + wantPKAny = pkcs10Req.PublicKey + } else { + //nolint:staticcheck // SA1019 this deprecated field still needs to be validated. + wantPKAny, err = x509.ParsePKIXPublicKey(oldReq.Spec.PKIXPublicKey) + if err != nil { + allErrors = append(allErrors, field.Invalid(pkixPath, field.OmitValueType{}, "must be a valid PKIX-serialized public key")) + return allErrors + } } + + // Was the certificate issued to the public key in the spec? switch wantPK := wantPKAny.(type) { case ed25519.PublicKey: if !wantPK.Equal(leafCert.PublicKey) { diff --git a/pkg/apis/certificates/validation/validation_test.go b/pkg/apis/certificates/validation/validation_test.go index 717251d188f4e..f91cfc8308238 100644 --- a/pkg/apis/certificates/validation/validation_test.go +++ b/pkg/apis/certificates/validation/validation_test.go @@ -1610,9 +1610,9 @@ func TestValidateClusterTrustBundleUpdate(t *testing.T) { func TestValidatePodCertificateRequestCreate(t *testing.T) { podUID1 := "pod-uid-1" - _, _, ed25519PubPKIX1, ed25519Proof1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1)) - _, _, ed25519PubPKIX2, ed25519Proof2 := mustMakeEd25519KeyAndProof(t, []byte("other-value")) - _, _, _, ed25519Proof3 := mustMakeEd25519KeyAndProof(t, []byte(podUID1)) + _, _, ed25519PubPKIX1, ed25519Proof1, ed25519CSR1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{}) + _, _, ed25519PubPKIX2, ed25519Proof2, _ := mustMakeEd25519KeyAndProof(t, []byte("other-value"), []string{}) + _, _, _, ed25519Proof3, _ := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{}) _, _, ecdsaP224PubPKIX1, ecdsaP224Proof1 := mustMakeECDSAKeyAndProof(t, elliptic.P224(), []byte(podUID1)) _, _, ecdsaP256PubPKIX1, ecdsaP256Proof1 := mustMakeECDSAKeyAndProof(t, elliptic.P256(), []byte(podUID1)) _, _, ecdsaP384PubPKIX1, ecdsaP384Proof1 := mustMakeECDSAKeyAndProof(t, elliptic.P384(), []byte(podUID1)) @@ -1624,7 +1624,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) { _, _, rsaWrongProofPKIX, rsaWrongProof := mustMakeRSAKeyAndProof(t, 3072, []byte("other-value")) podUIDEmpty := "" - _, _, pubPKIXEmpty, proofEmpty := mustMakeEd25519KeyAndProof(t, []byte(podUIDEmpty)) + _, _, pubPKIXEmpty, proofEmpty, _ := mustMakeEd25519KeyAndProof(t, []byte(podUIDEmpty), []string{}) testCases := []struct { description string @@ -1653,6 +1653,52 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) { }, wantErrors: nil, }, + { + description: "valid Ed25519 PCR (using PKCS#10)", + pcr: &capi.PodCertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "bar", + }, + Spec: capi.PodCertificateRequestSpec{ + SignerName: "foo.com/abc", + PodName: "pod-1", + PodUID: types.UID(podUID1), + ServiceAccountName: "sa-1", + ServiceAccountUID: "sa-uid-1", + NodeName: "node-1", + NodeUID: "node-uid-1", + MaxExpirationSeconds: ptr.To[int32](86400), + StubPKCS10Request: ed25519CSR1, + }, + }, + wantErrors: nil, + }, + { + description: "invalid Ed25519 PCR (both StubPKCS10Request and PKIXPublicKey set)", + pcr: &capi.PodCertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "bar", + }, + Spec: capi.PodCertificateRequestSpec{ + SignerName: "foo.com/abc", + PodName: "pod-1", + PodUID: types.UID(podUID1), + ServiceAccountName: "sa-1", + ServiceAccountUID: "sa-uid-1", + NodeName: "node-1", + NodeUID: "node-uid-1", + MaxExpirationSeconds: ptr.To[int32](86400), + PKIXPublicKey: ed25519PubPKIX1, + ProofOfPossession: ed25519Proof1, + StubPKCS10Request: ed25519CSR1, + }, + }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec"), field.OmitValueType{}, "exactly one of (stubPKCS10Request) or (pkixPublicKey, proofOfPossession) must be set"), + }, + }, { description: "invalid Ed25519 proof of possession (correct key signed wrong message)", pcr: &capi.PodCertificateRequest{ @@ -2187,7 +2233,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) { NodeUID: "node-uid-1", MaxExpirationSeconds: ptr.To[int32](86400), PKIXPublicKey: make([]byte, capi.MaxPKIXPublicKeySize+1), - ProofOfPossession: []byte{}, + ProofOfPossession: []byte("abc"), }, }, wantErrors: field.ErrorList{ @@ -2210,7 +2256,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) { NodeName: "node-1", NodeUID: "node-uid-1", MaxExpirationSeconds: ptr.To[int32](86400), - PKIXPublicKey: []byte{}, + PKIXPublicKey: ed25519PubPKIX1, ProofOfPossession: make([]byte, capi.MaxProofOfPossessionSize+1), }, }, @@ -2308,7 +2354,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) { func TestValidatePodCertificateRequestUpdate(t *testing.T) { podUID1 := "pod-uid-1" - _, _, pubPKIX1, proof1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1)) + _, _, pubPKIX1, proof1, _ := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{}) testCases := []struct { description string @@ -2396,7 +2442,7 @@ func TestValidatePodCertificateRequestStatusUpdate(t *testing.T) { intermediateCACertDER, intermediateCAPrivKey := mustMakeIntermediateCA(t, caCertDER, caPrivKey) podUID1 := "pod-uid-1" - _, pub1, pubPKIX1, proof1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1)) + _, pub1, pubPKIX1, proof1, _ := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{}) pod1Cert1 := mustSignCertForPublicKey(t, 24*time.Hour, pub1, caCertDER, caPrivKey, false, "", "") pod1Cert2 := mustSignCertForPublicKey(t, 18*time.Hour, pub1, caCertDER, caPrivKey, false, "", "") @@ -4438,7 +4484,7 @@ func mustParseTime(t *testing.T, stamp string) time.Time { return got } -func mustMakeEd25519KeyAndProof(t *testing.T, toBeSigned []byte) (ed25519.PrivateKey, ed25519.PublicKey, []byte, []byte) { +func mustMakeEd25519KeyAndProof(t *testing.T, toBeSigned []byte, pkcs10DNSSANS []string) (ed25519.PrivateKey, ed25519.PublicKey, []byte, []byte, []byte) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("Error while generating ed25519 key: %v", err) @@ -4448,7 +4494,13 @@ func mustMakeEd25519KeyAndProof(t *testing.T, toBeSigned []byte) (ed25519.Privat t.Fatalf("Error while marshaling PKIX public key: %v", err) } sig := ed25519.Sign(priv, toBeSigned) - return priv, pub, pubPKIX, sig + + pkcs10DER, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{DNSNames: pkcs10DNSSANS}, priv) + if err != nil { + t.Fatalf("Error while creating PKCS#10 certificate signing request: %v", err) + } + + return priv, pub, pubPKIX, sig, pkcs10DER } func mustMakeECDSAKeyAndProof(t *testing.T, curve elliptic.Curve, toBeSigned []byte) (*ecdsa.PrivateKey, *ecdsa.PublicKey, []byte, []byte) { diff --git a/pkg/apis/certificates/zz_generated.deepcopy.go b/pkg/apis/certificates/zz_generated.deepcopy.go index 9a526c246c6bb..73c5601e4ac5b 100644 --- a/pkg/apis/certificates/zz_generated.deepcopy.go +++ b/pkg/apis/certificates/zz_generated.deepcopy.go @@ -359,6 +359,11 @@ func (in *PodCertificateRequestSpec) DeepCopyInto(out *PodCertificateRequestSpec *out = make([]byte, len(*in)) copy(*out, *in) } + if in.StubPKCS10Request != nil { + in, out := &in.StubPKCS10Request, &out.StubPKCS10Request + *out = make([]byte, len(*in)) + copy(*out, *in) + } if in.UnverifiedUserAnnotations != nil { in, out := &in.UnverifiedUserAnnotations, &out.UnverifiedUserAnnotations *out = make(map[string]string, len(*in)) diff --git a/pkg/apis/core/helper/helpers.go b/pkg/apis/core/helper/helpers.go index 43d482f52d124..00592c1e4b0dc 100644 --- a/pkg/apis/core/helper/helpers.go +++ b/pkg/apis/core/helper/helpers.go @@ -105,6 +105,9 @@ var Semantic = conversion.EqualitiesOrDie( func(a, b metav1.Time) bool { return a.UTC() == b.UTC() }, + func(a, b metav1.FieldsV1) bool { + return a.Equal(b) + }, func(a, b labels.Selector) bool { return a.String() == b.String() }, diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index 1de0cf4495ca4..ed0bb0ea8782b 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -166,8 +166,7 @@ type VolumeSource struct { Projected *ProjectedVolumeSource // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. // +optional PortworxVolume *PortworxVolumeSource // scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. @@ -219,10 +218,9 @@ type VolumeSource struct { // A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. // The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. // The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - // The volume will be mounted read-only (ro) and non-executable files (noexec). + // The volume will be mounted read-only (ro). // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. - // +featureGate=ImageVolume // +optional Image *ImageVolumeSource } @@ -309,8 +307,7 @@ type PersistentVolumeSource struct { PhotonPersistentDisk *PhotonPersistentDiskVolumeSource // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. // +optional PortworxVolume *PortworxVolumeSource // scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. @@ -619,6 +616,17 @@ const ( PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError" // Volume is being modified PersistentVolumeClaimVolumeModifyingVolume PersistentVolumeClaimConditionType = "ModifyingVolume" + + // PersistentVolumeClaimUnused indicates whether the PVC is currently not in use by any Pod. + // When status is True, the PVC is not referenced by any non-terminal Pod. + // The lastTransitionTime indicates when the PVC last transitioned to being unused. + // + // Both in-use time and unused time duration indicated by this condition may be shorter or + // slightly longer than actual in-use time or unused time because of processing delays or + // when this feature was enabled in the cluster. + // + // Requires PersistentVolumeClaimUnusedSinceTime alpha featuregate + PersistentVolumeClaimUnused PersistentVolumeClaimConditionType = "Unused" ) // +enum @@ -2352,7 +2360,8 @@ type SecretKeySelector struct { // EnvFromSource represents the source of a set of ConfigMaps or Secrets type EnvFromSource struct { - // Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + // Optional text to prepend to the name of each environment variable. + // May consist of any printable ASCII characters except '='. // +optional Prefix string // The ConfigMap to select from. @@ -2642,7 +2651,8 @@ type Container struct { // +optional Ports []ContainerPort // List of sources to populate environment variables in the container. - // The keys defined within a source must be a C_IDENTIFIER. All invalid keys + // The keys defined within a source may consist of any printable ASCII characters except '='. + // All invalid keys // will be reported as an event when the container is starting. When a key exists in multiple // sources, the value associated with the last source will take precedence. // Values defined by an Env with a duplicate key will take precedence. @@ -2996,7 +3006,6 @@ type ContainerStatus struct { // AllocatedResources represents the compute resources allocated for this container by the // node. Kubelet sets this value to Container.Resources.Requests upon successful pod admission // and after successfully admitting desired pod resize. - // +featureGate=InPlacePodVerticalScalingAllocatedStatus // +optional AllocatedResources ResourceList // Resources represents the compute resource requests and limits that have been successfully @@ -3078,6 +3087,9 @@ type ResourceHealth struct { // // In future we may want to introduce the PermanentlyUnhealthy Status. Health ResourceHealthStatus + // Message provides human-readable context for Health (e.g. "ECC error count exceeded threshold"). + // This field is populated by the kubelet when ResourceHealthStatusMessage is enabled. + Message *string } // ContainerUser represents user identity information @@ -3161,7 +3173,6 @@ const ( // PodCondition represents pod's condition type PodCondition struct { Type PodConditionType - // +featureGate=PodObservedGenerationTracking // +optional ObservedGeneration int64 Status ConditionStatus @@ -3201,6 +3212,28 @@ type VolumeMountStatus struct { // depending on the mount result. // +optional RecursiveReadOnly *RecursiveReadOnlyMode + // volumeStatus represents volume-type-specific status about the mounted + // volume. + // +optional + VolumeStatus *VolumeStatus +} + +// VolumeStatus represents the status of a mounted volume. +// At most one of its members must be specified. +type VolumeStatus struct { + // image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + // +featureGate=ImageVolumeWithDigest + // +optional + Image *ImageVolumeStatus +} + +type ImageVolumeStatus struct { + // ImageRef is the digest of the image used for this volume. + // It should have a value that's similar to the pod's status.containerStatuses[i].imageID. + // The ImageRef length should not exceed 256 characters. + // +kubebuilder:validation:MaxLength=256 + // +required + ImageRef string } // RestartPolicy describes how the container should be restarted. @@ -3899,22 +3932,35 @@ type PodSpec struct { // +featureGate=HostnameOverride // +optional HostnameOverride *string - // WorkloadRef provides a reference to the Workload object that this Pod belongs to. - // This field is used by the scheduler to identify the PodGroup and apply the - // correct group scheduling policies. The Workload object referenced - // by this field may not exist at the time the Pod is created. - // This field is immutable, but a Workload object with the same name - // may be recreated with different policies. Doing this during pod scheduling + // SchedulingGroup provides a reference to the immediate scheduling runtime + // grouping object that this Pod belongs to. + // This field is used by the scheduler to identify the group and apply the + // correct group scheduling policies. The association with a group also + // impacts other lifecycle aspects of a Pod that are relevant in a wider context + // of scheduling like preemption, resource attachment, etc. If not specified, + // the Pod is treated as a single unit in all of these aspects. + // The group object referenced by this field may not exist at the time the + // Pod is created. + // This field is immutable, but a group object with the same name may be + // recreated with different policies. Doing this during pod scheduling // may result in the placement not conforming to the expected policies. // // +featureGate=GenericWorkload // +optional - WorkloadRef *WorkloadReference + SchedulingGroup *PodSchedulingGroup } // PodResourceClaim references exactly one ResourceClaim through a ClaimSource. // It adds a name to it that uniquely identifies the ResourceClaim inside the Pod. // Containers that need access to the ResourceClaim reference it with this name. +// +// When the DRAWorkloadResourceClaims feature gate is enabled and this Pod +// belongs to a PodGroup, a PodResourceClaim is matched to a +// PodGroupResourceClaim if all of their fields are equal (Name, +// ResourceClaimName, and ResourceClaimTemplateName). A matched claim references +// a single ResourceClaim shared across all Pods in the PodGroup, reserved for +// the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual +// Pods. type PodResourceClaim struct { // Name uniquely identifies this resource claim inside the pod. // This must be a DNS_LABEL. @@ -3936,6 +3982,16 @@ type PodResourceClaim struct { // generated component, will be used to form a unique name for the // ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses. // + // When the DRAWorkloadResourceClaims feature gate is enabled and the pod + // belongs to a PodGroup that defines a PodGroupResourceClaim with the same + // Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the + // ResourceClaim generated for the PodGroup. All pods in the group that + // define an equivalent PodResourceClaim matching the + // PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same + // generated ResourceClaim. ResourceClaims generated for a PodGroup are + // owned by the PodGroup and their lifecycles are tied to the PodGroup + // instead of any individual pod. + // // This field is immutable and no changes will be made to the // corresponding ResourceClaim by the control plane after creating the // ResourceClaim. @@ -3955,9 +4011,16 @@ type PodResourceClaimStatus struct { Name string // ResourceClaimName is the name of the ResourceClaim that was - // generated for the Pod in the namespace of the Pod. If this is - // unset, then generating a ResourceClaim was not necessary. The - // pod.spec.resourceClaims entry can be ignored in this case. + // generated for the Pod in the namespace of the Pod. + // + // When the DRAWorkloadResourceClaims feature is enabled and the + // corresponding PodResourceClaim matches a PodGroupResourceClaim + // made by the Pod's PodGroup, then this is the name of the + // ResourceClaim generated and reserved for the PodGroup. + // + // If this is unset, then generating a ResourceClaim was not + // necessary. The pod.spec.resourceClaims entry can be ignored in + // this case. ResourceClaimName *string } @@ -4008,34 +4071,18 @@ type PodSchedulingGate struct { Name string } -// WorkloadReference identifies the Workload object and PodGroup membership -// that a Pod belongs to. The scheduler uses this information to apply -// workload-aware scheduling semantics. -type WorkloadReference struct { - // Name defines the name of the Workload object this Pod belongs to. - // Workload must be in the same namespace as the Pod. - // If it doesn't match any existing Workload, the Pod will remain unschedulable - // until a Workload object is created and observed by the kube-scheduler. - // It must be a DNS subdomain. - // - // +required - Name string - - // PodGroup is the name of the PodGroup within the Workload that this Pod - // belongs to. If it doesn't match any existing PodGroup within the Workload, - // the Pod will remain unschedulable until the Workload object is recreated - // and observed by the kube-scheduler. It must be a DNS label. - // - // +required - PodGroup string - - // PodGroupReplicaKey specifies the replica key of the PodGroup to which this - // Pod belongs. It is used to distinguish pods belonging to different replicas - // of the same pod group. The pod group policy is applied separately to each replica. - // When set, it must be a DNS label. +// PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. +// The scheduler uses this information to apply workload-aware scheduling semantics. +// Exactly one field must be specified. +// +union +type PodSchedulingGroup struct { + // PodGroupName specifies the name of the standalone PodGroup object + // that represents the runtime instance of this group. + // Must be a DNS subdomain. // // +optional - PodGroupReplicaKey string + // +oneOf=GroupSelection + PodGroupName *string } // HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the @@ -4409,7 +4456,8 @@ type EphemeralContainerCommon struct { // +optional Ports []ContainerPort // List of sources to populate environment variables in the container. - // The keys defined within a source must be a C_IDENTIFIER. All invalid keys + // The keys defined within a source may consist of any printable ASCII characters except '='. + // All invalid keys // will be reported as an event when the container is starting. When a key exists in multiple // sources, the value associated with the last source will take precedence. // Values defined by an Env with a duplicate key will take precedence. @@ -4517,8 +4565,6 @@ type EphemeralContainer struct { // state of a system. type PodStatus struct { // If set, this represents the .metadata.generation that the pod status was set based upon. - // The PodObservedGenerationTracking feature gate must be enabled to use this field. - // +featureGate=PodObservedGenerationTracking // +optional ObservedGeneration int64 // +optional @@ -4631,6 +4677,16 @@ type PodStatus struct { // +featureGate=InPlacePodLevelResourcesVerticalScaling // +optional Resources *ResourceRequirements + + // NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources + // that were allocated for this pod through DRA claims. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // +featureGate=DRANodeAllocatableResources + // +optional + // +listType=atomic + NodeAllocatableResourceClaimStatuses []NodeAllocatableResourceClaimStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -5110,6 +5166,8 @@ type ServiceSpec struct { // ExternalIPs are used by external load balancers, or can be set by // users to handle external traffic that arrives at a node. + // + // Deprecated: ExternalIPs is deprecated and may be removed in a future version. // +optional ExternalIPs []string @@ -5509,7 +5567,6 @@ type NodeRuntimeHandlerFeatures struct { // +optional RecursiveReadOnlyMounts *bool // UserNamespaces is set to true if the runtime handler supports UserNamespaces, including for volumes. - // +featureGate=UserNamespacesSupport // +optional UserNamespaces *bool } @@ -5654,7 +5711,6 @@ type NodeStatus struct { // +optional Config *NodeConfigStatus // The available runtime handlers. - // +featureGate=UserNamespacesSupport // +optional RuntimeHandlers []NodeRuntimeHandler // Features describes the set of features implemented by the CRI implementation. @@ -7169,3 +7225,17 @@ type ImageVolumeSource struct { // +optional PullPolicy PullPolicy } + +// NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA. +type NodeAllocatableResourceClaimStatus struct { + // ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation. + // +required + ResourceClaimName string + // Containers lists the names of all containers in this pod that reference the claim. + // +optional + // +listType=set + Containers []string + // Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim. + // +required + Resources map[ResourceName]resource.Quantity +} diff --git a/pkg/apis/core/v1/defaults_test.go b/pkg/apis/core/v1/defaults_test.go index dc0a0baccc515..57187106a4732 100644 --- a/pkg/apis/core/v1/defaults_test.go +++ b/pkg/apis/core/v1/defaults_test.go @@ -175,9 +175,6 @@ func testWorkloadDefaults(t *testing.T, featuresEnabled bool) { ".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`, ".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`, } - if !featuresEnabled { - delete(expectedDefaults, ".Spec.Volumes[0].VolumeSource.Image.PullPolicy") - } t.Run("empty PodTemplateSpec", func(t *testing.T) { rc := &v1.ReplicationController{Spec: v1.ReplicationControllerSpec{Template: &v1.PodTemplateSpec{}}} template := rc.Spec.Template @@ -365,9 +362,6 @@ func testPodDefaults(t *testing.T, featuresEnabled bool) { ".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`, ".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`, } - if !featuresEnabled { - delete(expectedDefaults, ".Spec.Volumes[0].VolumeSource.Image.PullPolicy") - } defaults := detectDefaults(t, pod, reflect.ValueOf(pod)) if !reflect.DeepEqual(expectedDefaults, defaults) { t.Errorf("Defaults for PodSpec changed. This can cause spurious restarts of containers on API server upgrade.") diff --git a/pkg/apis/core/v1/helper/helpers_test.go b/pkg/apis/core/v1/helper/helpers_test.go index af9a4771fa9c2..f80cf033d3bbf 100644 --- a/pkg/apis/core/v1/helper/helpers_test.go +++ b/pkg/apis/core/v1/helper/helpers_test.go @@ -54,7 +54,6 @@ func TestIsNativeResource(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { t.Parallel() v := IsNativeResource(tc.resourceName) @@ -95,8 +94,6 @@ func TestHugePageSizeFromResourceName(t *testing.T) { } for i, tc := range testCases { - i := i - tc := tc t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { t.Parallel() v, err := HugePageSizeFromResourceName(tc.resourceName) @@ -164,8 +161,6 @@ func TestHugePageSizeFromMedium(t *testing.T) { }, } for i, tc := range testCases { - i := i - tc := tc t.Run(tc.description, func(t *testing.T) { t.Parallel() v, err := HugePageSizeFromMedium(tc.medium) @@ -206,7 +201,6 @@ func TestIsOvercommitAllowed(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { t.Parallel() v := IsOvercommitAllowed(tc.resourceName) diff --git a/pkg/apis/core/v1/helper/qos/qos.go b/pkg/apis/core/v1/helper/qos/qos.go index b6ba7bf63ae37..d2cabfcd3ab28 100644 --- a/pkg/apis/core/v1/helper/qos/qos.go +++ b/pkg/apis/core/v1/helper/qos/qos.go @@ -18,7 +18,6 @@ package qos import ( v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/sets" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/apis/core" @@ -43,10 +42,6 @@ func GetPodQOS(pod *v1.Pod) v1.PodQOSClass { return ComputePodQOS(pod) } -// zeroQuantity represents a resource.Quantity with value "0", used as a baseline -// for resource comparisons. -var zeroQuantity = resource.MustParse("0") - // processResourceList adds non-zero quantities for supported QoS compute resources // quantities from newList to list. func processResourceList(list, newList v1.ResourceList) { @@ -54,7 +49,7 @@ func processResourceList(list, newList v1.ResourceList) { if !isSupportedQoSComputeResource(name) { continue } - if quantity.Cmp(zeroQuantity) == 1 { + if quantity.Sign() == 1 { delta := quantity.DeepCopy() if _, exists := list[name]; !exists { list[name] = delta @@ -75,7 +70,7 @@ func getQOSResources(list v1.ResourceList) sets.Set[string] { if !isSupportedQoSComputeResource(name) { continue } - if quantity.Cmp(zeroQuantity) == 1 { + if quantity.Sign() == 1 { qosResources.Insert(string(name)) } } @@ -120,7 +115,7 @@ func ComputePodQOS(pod *v1.Pod) v1.PodQOSClass { if !isSupportedQoSComputeResource(name) { continue } - if quantity.Cmp(zeroQuantity) == 1 { + if quantity.Sign() == 1 { delta := quantity.DeepCopy() if _, exists := requests[name]; !exists { requests[name] = delta @@ -136,7 +131,7 @@ func ComputePodQOS(pod *v1.Pod) v1.PodQOSClass { if !isSupportedQoSComputeResource(name) { continue } - if quantity.Cmp(zeroQuantity) == 1 { + if quantity.Sign() == 1 { qosLimitsFound.Insert(string(name)) delta := quantity.DeepCopy() if _, exists := limits[name]; !exists { diff --git a/pkg/apis/core/v1/zz_generated.conversion.go b/pkg/apis/core/v1/zz_generated.conversion.go index 3770ea1916b7b..459408c5265ea 100644 --- a/pkg/apis/core/v1/zz_generated.conversion.go +++ b/pkg/apis/core/v1/zz_generated.conversion.go @@ -842,6 +842,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*corev1.ImageVolumeStatus)(nil), (*core.ImageVolumeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ImageVolumeStatus_To_core_ImageVolumeStatus(a.(*corev1.ImageVolumeStatus), b.(*core.ImageVolumeStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*core.ImageVolumeStatus)(nil), (*corev1.ImageVolumeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_core_ImageVolumeStatus_To_v1_ImageVolumeStatus(a.(*core.ImageVolumeStatus), b.(*corev1.ImageVolumeStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*corev1.KeyToPath)(nil), (*core.KeyToPath)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_KeyToPath_To_core_KeyToPath(a.(*corev1.KeyToPath), b.(*core.KeyToPath), scope) }); err != nil { @@ -1072,6 +1082,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*corev1.NodeAllocatableResourceClaimStatus)(nil), (*core.NodeAllocatableResourceClaimStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_NodeAllocatableResourceClaimStatus_To_core_NodeAllocatableResourceClaimStatus(a.(*corev1.NodeAllocatableResourceClaimStatus), b.(*core.NodeAllocatableResourceClaimStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*core.NodeAllocatableResourceClaimStatus)(nil), (*corev1.NodeAllocatableResourceClaimStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_core_NodeAllocatableResourceClaimStatus_To_v1_NodeAllocatableResourceClaimStatus(a.(*core.NodeAllocatableResourceClaimStatus), b.(*corev1.NodeAllocatableResourceClaimStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*corev1.NodeCondition)(nil), (*core.NodeCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_NodeCondition_To_core_NodeCondition(a.(*corev1.NodeCondition), b.(*core.NodeCondition), scope) }); err != nil { @@ -1562,6 +1582,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*corev1.PodSchedulingGroup)(nil), (*core.PodSchedulingGroup)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_PodSchedulingGroup_To_core_PodSchedulingGroup(a.(*corev1.PodSchedulingGroup), b.(*core.PodSchedulingGroup), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*core.PodSchedulingGroup)(nil), (*corev1.PodSchedulingGroup)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_core_PodSchedulingGroup_To_v1_PodSchedulingGroup(a.(*core.PodSchedulingGroup), b.(*corev1.PodSchedulingGroup), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*corev1.PodSecurityContext)(nil), (*core.PodSecurityContext)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_PodSecurityContext_To_core_PodSecurityContext(a.(*corev1.PodSecurityContext), b.(*core.PodSecurityContext), scope) }); err != nil { @@ -2307,6 +2337,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*corev1.VolumeStatus)(nil), (*core.VolumeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VolumeStatus_To_core_VolumeStatus(a.(*corev1.VolumeStatus), b.(*core.VolumeStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*core.VolumeStatus)(nil), (*corev1.VolumeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_core_VolumeStatus_To_v1_VolumeStatus(a.(*core.VolumeStatus), b.(*corev1.VolumeStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*corev1.VsphereVirtualDiskVolumeSource)(nil), (*core.VsphereVirtualDiskVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource(a.(*corev1.VsphereVirtualDiskVolumeSource), b.(*core.VsphereVirtualDiskVolumeSource), scope) }); err != nil { @@ -2337,16 +2377,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*corev1.WorkloadReference)(nil), (*core.WorkloadReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_WorkloadReference_To_core_WorkloadReference(a.(*corev1.WorkloadReference), b.(*core.WorkloadReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*core.WorkloadReference)(nil), (*corev1.WorkloadReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_core_WorkloadReference_To_v1_WorkloadReference(a.(*core.WorkloadReference), b.(*corev1.WorkloadReference), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*corev1.NodeProxyOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_NodeProxyOptions(a.(*url.Values), b.(*corev1.NodeProxyOptions), scope) }); err != nil { @@ -4648,6 +4678,26 @@ func Convert_core_ImageVolumeSource_To_v1_ImageVolumeSource(in *core.ImageVolume return autoConvert_core_ImageVolumeSource_To_v1_ImageVolumeSource(in, out, s) } +func autoConvert_v1_ImageVolumeStatus_To_core_ImageVolumeStatus(in *corev1.ImageVolumeStatus, out *core.ImageVolumeStatus, s conversion.Scope) error { + out.ImageRef = in.ImageRef + return nil +} + +// Convert_v1_ImageVolumeStatus_To_core_ImageVolumeStatus is an autogenerated conversion function. +func Convert_v1_ImageVolumeStatus_To_core_ImageVolumeStatus(in *corev1.ImageVolumeStatus, out *core.ImageVolumeStatus, s conversion.Scope) error { + return autoConvert_v1_ImageVolumeStatus_To_core_ImageVolumeStatus(in, out, s) +} + +func autoConvert_core_ImageVolumeStatus_To_v1_ImageVolumeStatus(in *core.ImageVolumeStatus, out *corev1.ImageVolumeStatus, s conversion.Scope) error { + out.ImageRef = in.ImageRef + return nil +} + +// Convert_core_ImageVolumeStatus_To_v1_ImageVolumeStatus is an autogenerated conversion function. +func Convert_core_ImageVolumeStatus_To_v1_ImageVolumeStatus(in *core.ImageVolumeStatus, out *corev1.ImageVolumeStatus, s conversion.Scope) error { + return autoConvert_core_ImageVolumeStatus_To_v1_ImageVolumeStatus(in, out, s) +} + func autoConvert_v1_KeyToPath_To_core_KeyToPath(in *corev1.KeyToPath, out *core.KeyToPath, s conversion.Scope) error { out.Key = in.Key out.Path = in.Path @@ -5220,6 +5270,30 @@ func Convert_core_NodeAffinity_To_v1_NodeAffinity(in *core.NodeAffinity, out *co return autoConvert_core_NodeAffinity_To_v1_NodeAffinity(in, out, s) } +func autoConvert_v1_NodeAllocatableResourceClaimStatus_To_core_NodeAllocatableResourceClaimStatus(in *corev1.NodeAllocatableResourceClaimStatus, out *core.NodeAllocatableResourceClaimStatus, s conversion.Scope) error { + out.ResourceClaimName = in.ResourceClaimName + out.Containers = *(*[]string)(unsafe.Pointer(&in.Containers)) + out.Resources = *(*map[core.ResourceName]resource.Quantity)(unsafe.Pointer(&in.Resources)) + return nil +} + +// Convert_v1_NodeAllocatableResourceClaimStatus_To_core_NodeAllocatableResourceClaimStatus is an autogenerated conversion function. +func Convert_v1_NodeAllocatableResourceClaimStatus_To_core_NodeAllocatableResourceClaimStatus(in *corev1.NodeAllocatableResourceClaimStatus, out *core.NodeAllocatableResourceClaimStatus, s conversion.Scope) error { + return autoConvert_v1_NodeAllocatableResourceClaimStatus_To_core_NodeAllocatableResourceClaimStatus(in, out, s) +} + +func autoConvert_core_NodeAllocatableResourceClaimStatus_To_v1_NodeAllocatableResourceClaimStatus(in *core.NodeAllocatableResourceClaimStatus, out *corev1.NodeAllocatableResourceClaimStatus, s conversion.Scope) error { + out.ResourceClaimName = in.ResourceClaimName + out.Containers = *(*[]string)(unsafe.Pointer(&in.Containers)) + out.Resources = *(*map[corev1.ResourceName]resource.Quantity)(unsafe.Pointer(&in.Resources)) + return nil +} + +// Convert_core_NodeAllocatableResourceClaimStatus_To_v1_NodeAllocatableResourceClaimStatus is an autogenerated conversion function. +func Convert_core_NodeAllocatableResourceClaimStatus_To_v1_NodeAllocatableResourceClaimStatus(in *core.NodeAllocatableResourceClaimStatus, out *corev1.NodeAllocatableResourceClaimStatus, s conversion.Scope) error { + return autoConvert_core_NodeAllocatableResourceClaimStatus_To_v1_NodeAllocatableResourceClaimStatus(in, out, s) +} + func autoConvert_v1_NodeCondition_To_core_NodeCondition(in *corev1.NodeCondition, out *core.NodeCondition, s conversion.Scope) error { out.Type = core.NodeConditionType(in.Type) out.Status = core.ConditionStatus(in.Status) @@ -6887,6 +6961,26 @@ func Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in *core.PodScheduli return autoConvert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in, out, s) } +func autoConvert_v1_PodSchedulingGroup_To_core_PodSchedulingGroup(in *corev1.PodSchedulingGroup, out *core.PodSchedulingGroup, s conversion.Scope) error { + out.PodGroupName = (*string)(unsafe.Pointer(in.PodGroupName)) + return nil +} + +// Convert_v1_PodSchedulingGroup_To_core_PodSchedulingGroup is an autogenerated conversion function. +func Convert_v1_PodSchedulingGroup_To_core_PodSchedulingGroup(in *corev1.PodSchedulingGroup, out *core.PodSchedulingGroup, s conversion.Scope) error { + return autoConvert_v1_PodSchedulingGroup_To_core_PodSchedulingGroup(in, out, s) +} + +func autoConvert_core_PodSchedulingGroup_To_v1_PodSchedulingGroup(in *core.PodSchedulingGroup, out *corev1.PodSchedulingGroup, s conversion.Scope) error { + out.PodGroupName = (*string)(unsafe.Pointer(in.PodGroupName)) + return nil +} + +// Convert_core_PodSchedulingGroup_To_v1_PodSchedulingGroup is an autogenerated conversion function. +func Convert_core_PodSchedulingGroup_To_v1_PodSchedulingGroup(in *core.PodSchedulingGroup, out *corev1.PodSchedulingGroup, s conversion.Scope) error { + return autoConvert_core_PodSchedulingGroup_To_v1_PodSchedulingGroup(in, out, s) +} + func autoConvert_v1_PodSecurityContext_To_core_PodSecurityContext(in *corev1.PodSecurityContext, out *core.PodSecurityContext, s conversion.Scope) error { out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) out.WindowsOptions = (*core.WindowsSecurityContextOptions)(unsafe.Pointer(in.WindowsOptions)) @@ -7016,7 +7110,7 @@ func autoConvert_v1_PodSpec_To_core_PodSpec(in *corev1.PodSpec, out *core.PodSpe out.ResourceClaims = *(*[]core.PodResourceClaim)(unsafe.Pointer(&in.ResourceClaims)) out.Resources = (*core.ResourceRequirements)(unsafe.Pointer(in.Resources)) out.HostnameOverride = (*string)(unsafe.Pointer(in.HostnameOverride)) - out.WorkloadRef = (*core.WorkloadReference)(unsafe.Pointer(in.WorkloadRef)) + out.SchedulingGroup = (*core.PodSchedulingGroup)(unsafe.Pointer(in.SchedulingGroup)) return nil } @@ -7074,7 +7168,7 @@ func autoConvert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *corev1.PodSpe out.ResourceClaims = *(*[]corev1.PodResourceClaim)(unsafe.Pointer(&in.ResourceClaims)) out.Resources = (*corev1.ResourceRequirements)(unsafe.Pointer(in.Resources)) out.HostnameOverride = (*string)(unsafe.Pointer(in.HostnameOverride)) - out.WorkloadRef = (*corev1.WorkloadReference)(unsafe.Pointer(in.WorkloadRef)) + out.SchedulingGroup = (*corev1.PodSchedulingGroup)(unsafe.Pointer(in.SchedulingGroup)) return nil } @@ -7099,6 +7193,7 @@ func autoConvert_v1_PodStatus_To_core_PodStatus(in *corev1.PodStatus, out *core. out.ExtendedResourceClaimStatus = (*core.PodExtendedResourceClaimStatus)(unsafe.Pointer(in.ExtendedResourceClaimStatus)) out.AllocatedResources = *(*core.ResourceList)(unsafe.Pointer(&in.AllocatedResources)) out.Resources = (*core.ResourceRequirements)(unsafe.Pointer(in.Resources)) + out.NodeAllocatableResourceClaimStatuses = *(*[]core.NodeAllocatableResourceClaimStatus)(unsafe.Pointer(&in.NodeAllocatableResourceClaimStatuses)) return nil } @@ -7122,6 +7217,7 @@ func autoConvert_core_PodStatus_To_v1_PodStatus(in *core.PodStatus, out *corev1. out.ExtendedResourceClaimStatus = (*corev1.PodExtendedResourceClaimStatus)(unsafe.Pointer(in.ExtendedResourceClaimStatus)) out.AllocatedResources = *(*corev1.ResourceList)(unsafe.Pointer(&in.AllocatedResources)) out.Resources = (*corev1.ResourceRequirements)(unsafe.Pointer(in.Resources)) + out.NodeAllocatableResourceClaimStatuses = *(*[]corev1.NodeAllocatableResourceClaimStatus)(unsafe.Pointer(&in.NodeAllocatableResourceClaimStatuses)) return nil } @@ -7798,6 +7894,7 @@ func Convert_core_ResourceFieldSelector_To_v1_ResourceFieldSelector(in *core.Res func autoConvert_v1_ResourceHealth_To_core_ResourceHealth(in *corev1.ResourceHealth, out *core.ResourceHealth, s conversion.Scope) error { out.ResourceID = core.ResourceID(in.ResourceID) out.Health = core.ResourceHealthStatus(in.Health) + out.Message = (*string)(unsafe.Pointer(in.Message)) return nil } @@ -7809,6 +7906,7 @@ func Convert_v1_ResourceHealth_To_core_ResourceHealth(in *corev1.ResourceHealth, func autoConvert_core_ResourceHealth_To_v1_ResourceHealth(in *core.ResourceHealth, out *corev1.ResourceHealth, s conversion.Scope) error { out.ResourceID = corev1.ResourceID(in.ResourceID) out.Health = corev1.ResourceHealthStatus(in.Health) + out.Message = (*string)(unsafe.Pointer(in.Message)) return nil } @@ -9098,6 +9196,7 @@ func autoConvert_v1_VolumeMountStatus_To_core_VolumeMountStatus(in *corev1.Volum out.MountPath = in.MountPath out.ReadOnly = in.ReadOnly out.RecursiveReadOnly = (*core.RecursiveReadOnlyMode)(unsafe.Pointer(in.RecursiveReadOnly)) + out.VolumeStatus = (*core.VolumeStatus)(unsafe.Pointer(in.VolumeStatus)) return nil } @@ -9111,6 +9210,7 @@ func autoConvert_core_VolumeMountStatus_To_v1_VolumeMountStatus(in *core.VolumeM out.MountPath = in.MountPath out.ReadOnly = in.ReadOnly out.RecursiveReadOnly = (*corev1.RecursiveReadOnlyMode)(unsafe.Pointer(in.RecursiveReadOnly)) + out.VolumeStatus = (*corev1.VolumeStatus)(unsafe.Pointer(in.VolumeStatus)) return nil } @@ -9301,6 +9401,26 @@ func Convert_core_VolumeSource_To_v1_VolumeSource(in *core.VolumeSource, out *co return autoConvert_core_VolumeSource_To_v1_VolumeSource(in, out, s) } +func autoConvert_v1_VolumeStatus_To_core_VolumeStatus(in *corev1.VolumeStatus, out *core.VolumeStatus, s conversion.Scope) error { + out.Image = (*core.ImageVolumeStatus)(unsafe.Pointer(in.Image)) + return nil +} + +// Convert_v1_VolumeStatus_To_core_VolumeStatus is an autogenerated conversion function. +func Convert_v1_VolumeStatus_To_core_VolumeStatus(in *corev1.VolumeStatus, out *core.VolumeStatus, s conversion.Scope) error { + return autoConvert_v1_VolumeStatus_To_core_VolumeStatus(in, out, s) +} + +func autoConvert_core_VolumeStatus_To_v1_VolumeStatus(in *core.VolumeStatus, out *corev1.VolumeStatus, s conversion.Scope) error { + out.Image = (*corev1.ImageVolumeStatus)(unsafe.Pointer(in.Image)) + return nil +} + +// Convert_core_VolumeStatus_To_v1_VolumeStatus is an autogenerated conversion function. +func Convert_core_VolumeStatus_To_v1_VolumeStatus(in *core.VolumeStatus, out *corev1.VolumeStatus, s conversion.Scope) error { + return autoConvert_core_VolumeStatus_To_v1_VolumeStatus(in, out, s) +} + func autoConvert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource(in *corev1.VsphereVirtualDiskVolumeSource, out *core.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { out.VolumePath = in.VolumePath out.FSType = in.FSType @@ -9378,27 +9498,3 @@ func autoConvert_core_WindowsSecurityContextOptions_To_v1_WindowsSecurityContext func Convert_core_WindowsSecurityContextOptions_To_v1_WindowsSecurityContextOptions(in *core.WindowsSecurityContextOptions, out *corev1.WindowsSecurityContextOptions, s conversion.Scope) error { return autoConvert_core_WindowsSecurityContextOptions_To_v1_WindowsSecurityContextOptions(in, out, s) } - -func autoConvert_v1_WorkloadReference_To_core_WorkloadReference(in *corev1.WorkloadReference, out *core.WorkloadReference, s conversion.Scope) error { - out.Name = in.Name - out.PodGroup = in.PodGroup - out.PodGroupReplicaKey = in.PodGroupReplicaKey - return nil -} - -// Convert_v1_WorkloadReference_To_core_WorkloadReference is an autogenerated conversion function. -func Convert_v1_WorkloadReference_To_core_WorkloadReference(in *corev1.WorkloadReference, out *core.WorkloadReference, s conversion.Scope) error { - return autoConvert_v1_WorkloadReference_To_core_WorkloadReference(in, out, s) -} - -func autoConvert_core_WorkloadReference_To_v1_WorkloadReference(in *core.WorkloadReference, out *corev1.WorkloadReference, s conversion.Scope) error { - out.Name = in.Name - out.PodGroup = in.PodGroup - out.PodGroupReplicaKey = in.PodGroupReplicaKey - return nil -} - -// Convert_core_WorkloadReference_To_v1_WorkloadReference is an autogenerated conversion function. -func Convert_core_WorkloadReference_To_v1_WorkloadReference(in *core.WorkloadReference, out *corev1.WorkloadReference, s conversion.Scope) error { - return autoConvert_core_WorkloadReference_To_v1_WorkloadReference(in, out, s) -} diff --git a/pkg/apis/core/v1/zz_generated.validations.go b/pkg/apis/core/v1/zz_generated.validations.go index 7e39374b6b2c6..a8f5fba015765 100644 --- a/pkg/apis/core/v1/zz_generated.validations.go +++ b/pkg/apis/core/v1/zz_generated.validations.go @@ -48,14 +48,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ReplicationControllerList - scheme.AddValidationFunc((*corev1.ReplicationControllerList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ReplicationControllerList(ctx, op, nil /* fldPath */, obj.(*corev1.ReplicationControllerList), safe.Cast[*corev1.ReplicationControllerList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } @@ -74,13 +66,13 @@ func Validate_ReplicationController(ctx context.Context, op operation.Operation, // call field-attached validations func() { // cohort name earlyReturn := false - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue); len(e) != 0 { + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName)...) + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName).MarkAlpha()...) }() return }(fldPath.Child("metadata"), &obj.ObjectMeta, safe.Field(oldObj, func(oldObj *corev1.ReplicationController) *metav1.ObjectMeta { return &oldObj.ObjectMeta }), oldObj != nil)...) @@ -101,27 +93,6 @@ func Validate_ReplicationController(ctx context.Context, op operation.Operation, return errs } -// Validate_ReplicationControllerList validates an instance of ReplicationControllerList according -// to declarative validation rules in the API schema. -func Validate_ReplicationControllerList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *corev1.ReplicationControllerList) (errs field.ErrorList) { - // field corev1.ReplicationControllerList.TypeMeta has no validation - // field corev1.ReplicationControllerList.ListMeta has no validation - - // field corev1.ReplicationControllerList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []corev1.ReplicationController, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ReplicationController)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *corev1.ReplicationControllerList) []corev1.ReplicationController { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ReplicationControllerSpec validates an instance of ReplicationControllerSpec according // to declarative validation rules in the API schema. func Validate_ReplicationControllerSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *corev1.ReplicationControllerSpec) (errs field.ErrorList) { @@ -135,27 +106,26 @@ func Validate_ReplicationControllerSpec(ctx context.Context, op operation.Operat // call field-attached validations earlyReturn := false // optional fields with default values are effectively required - if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha()...) return }(fldPath.Child("replicas"), obj.Replicas, safe.Field(oldObj, func(oldObj *corev1.ReplicationControllerSpec) *int32 { return oldObj.Replicas }), oldObj != nil)...) // field corev1.ReplicationControllerSpec.MinReadySeconds errs = append(errs, func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { - // optional value-type fields with zero-value defaults are purely documentation // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } // call field-attached validations - errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha()...) return }(fldPath.Child("minReadySeconds"), &obj.MinReadySeconds, safe.Field(oldObj, func(oldObj *corev1.ReplicationControllerSpec) *int32 { return &oldObj.MinReadySeconds }), oldObj != nil)...) diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index fbb62132d630d..84f855295fb93 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -65,6 +65,7 @@ import ( "k8s.io/kubernetes/pkg/apis/core/helper/qos" podshelper "k8s.io/kubernetes/pkg/apis/core/pods" corev1 "k8s.io/kubernetes/pkg/apis/core/v1" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/fieldpath" @@ -176,7 +177,7 @@ func ValidateQualifiedName(value string, fldPath *field.Path) field.ErrorList { func ValidateDNS1123SubdomainWithUnderScore(value string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, msg := range validation.IsDNS1123SubdomainWithUnderscore(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)).WithOrigin("format=k8s-dns-subdomain-with-underscore") + allErrs = append(allErrs, field.Invalid(fldPath, value, msg).WithOrigin("format=k8s-dns-subdomain-with-underscore")) } return allErrs } @@ -185,7 +186,7 @@ func ValidateDNS1123SubdomainWithUnderScore(value string, fldPath *field.Path) f func ValidateDNS1123Subdomain(value string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, msg := range validation.IsDNS1123Subdomain(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)).WithOrigin("format=k8s-long-name") + allErrs = append(allErrs, field.Invalid(fldPath, value, msg).WithOrigin("format=k8s-long-name")) } return allErrs } @@ -342,17 +343,9 @@ var ValidateResourceClaimName = apimachineryvalidation.NameIsDNSSubdomain // name for a ResourceClaimTemplate is valid. var ValidateResourceClaimTemplateName = apimachineryvalidation.NameIsDNSSubdomain -// ValidateWorkloadName can be used to check whether the given -// name for a Workload is valid. -var ValidateWorkloadName = apimachineryvalidation.NameIsDNSSubdomain - // ValidatePodGroupName can be used to check whether the given // name for a PodGroup is valid. -var ValidatePodGroupName = apimachineryvalidation.NameIsDNSLabel - -// ValidatePodGroupReplicaKey can be used to check whether the given -// PodGroupReplicaKey is valid. -var ValidatePodGroupReplicaKey = apimachineryvalidation.NameIsDNSLabel +var ValidatePodGroupName = apimachineryvalidation.NameIsDNSSubdomain // ValidateRuntimeClassName can be used to check whether the given RuntimeClass name is valid. // Prefix indicates this name will be used as part of generation, in which case @@ -1829,7 +1822,7 @@ func ValidateCSIDriverName(driverName string, fldPath *field.Path, opts ...Valid if len(driverName) > 63 { err := field.TooLong(fldPath, "" /*unused*/, 63) if slices.Contains(opts, SizeCovered) { - err = err.MarkCoveredByDeclarative() + err = err.WithOrigin("maxLength").MarkCoveredByDeclarative() } allErrs = append(allErrs, err) } @@ -3653,9 +3646,10 @@ func validateResizePolicy(policyList []core.ContainerResizePolicy, fldPath *fiel } if *podRestartPolicy == core.RestartPolicyNever && p.RestartPolicy != core.NotRequired { - allErrors = append(allErrors, field.Invalid(fldPath, p.RestartPolicy, "must be 'NotRequired' when `restartPolicy` is 'Never'")) + allErrors = append(allErrors, field.Invalid(fldPath, p.RestartPolicy, "must be 'NotRequired' when pod `restartPolicy` is 'Never'")) } } + return allErrors } @@ -3694,7 +3688,7 @@ func validateContainerRestartPolicy(policy *core.ContainerRestartPolicy, rules [ } if len(rules) > 20 { - allErrs = append(allErrs, field.TooLong(fldPath.Child("restartPolicyRules"), rules, 20)) + allErrs = append(allErrs, field.TooMany(fldPath.Child("restartPolicyRules"), len(rules), 20)) } for i, rule := range rules { policyRulesFld := fldPath.Child("restartPolicyRules").Index(i) @@ -3703,7 +3697,7 @@ func validateContainerRestartPolicy(policy *core.ContainerRestartPolicy, rules [ allowedActions = supportedContainerRestartRuleActionsWithRestartAllContainers } if !allowedActions.Has(rule.Action) { - allErrs = append(allErrs, field.NotSupported(policyRulesFld.Child("action"), rule.Action, sets.List(supportedContainerRestartRuleActions))) + allErrs = append(allErrs, field.NotSupported(policyRulesFld.Child("action"), rule.Action, sets.List(allowedActions))) } if rule.ExitCodes != nil { @@ -3713,7 +3707,7 @@ func validateContainerRestartPolicy(policy *core.ContainerRestartPolicy, rules [ } if len(rule.ExitCodes.Values) > 255 { - allErrs = append(allErrs, field.TooLong(exitCodesFld.Child("values"), rule.ExitCodes.Values, 255)) + allErrs = append(allErrs, field.TooMany(exitCodesFld.Child("values"), len(rule.ExitCodes.Values), 255)) } } else { allErrs = append(allErrs, field.Required(policyRulesFld.Child("exitCodes"), "must be specified")) @@ -3866,6 +3860,14 @@ func validateInitContainers(containers []core.Container, os *core.PodOS, regular if !opts.AllowSidecarResizePolicy && len(ctr.ResizePolicy) > 0 { allErrs = append(allErrs, field.Invalid(idxPath.Child("resizePolicy"), ctr.ResizePolicy, "must not be set for init containers")) } + + if !restartAlways && !opts.AllowExistingRestartContainerForNonSidecarInitContainer { + for j, p := range ctr.ResizePolicy { + if p.RestartPolicy == core.RestartContainer { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("resizePolicy").Index(j).Child("restartPolicy"), p.RestartPolicy, "must not be set to 'RestartContainer' for non-sidecar initContainers")) + } + } + } } return allErrs @@ -4485,6 +4487,8 @@ type PodValidationOptions struct { AllowEnvFilesValidation bool // Allows containers have restart policy and restart policy rules. AllowContainerRestartPolicyRules bool + // Allow existing RestartContainer resize policy for non-sidecar init containers for backward compatibility + AllowExistingRestartContainerForNonSidecarInitContainer bool // Allow user namespaces with volume devices, even though they will not function properly (should only be tolerated in updates of objects which already have this invalid configuration). AllowUserNamespacesWithVolumeDevices bool // Allow taint toleration comparison operators (Lt, Gt) @@ -4493,6 +4497,8 @@ type PodValidationOptions struct { AllowUserNamespacesHostNetworkSupport bool // Allow containers to have restart policy rule with RestartAllContainers action, applicable for init, sidecar, and regular containers. AllowRestartAllContainers bool + // Allows container statuses to contain image volume digest + AllowImageVolumeWithDigest bool } // validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set, @@ -4725,8 +4731,8 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi } } - if spec.WorkloadRef != nil { - allErrs = append(allErrs, validateWorkloadReference(spec.WorkloadRef, fldPath.Child("workloadRef"))...) + if spec.SchedulingGroup != nil { + allErrs = append(allErrs, validateSchedulingGroup(spec.SchedulingGroup, fldPath.Child("schedulingGroup"))...) } allErrs = append(allErrs, validateFileKeyRefVolumes(spec, fldPath)...) @@ -4977,7 +4983,7 @@ func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, allowInval path := fldPath.Child("values") for valueIndex, value := range rq.Values { for _, msg := range validation.IsValidLabelValue(value) { - allErrs = append(allErrs, field.Invalid(path.Index(valueIndex), value, msg)).WithOrigin("format=k8s-label-value") + allErrs = append(allErrs, field.Invalid(path.Index(valueIndex), value, msg).WithOrigin("format=k8s-label-value")) } } } @@ -6045,6 +6051,7 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions allErrs = append(allErrs, ValidateEphemeralContainerStateTransition(newPod.Status.EphemeralContainerStatuses, oldPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"))...) allErrs = append(allErrs, validatePodResourceClaimStatuses(newPod.Status.ResourceClaimStatuses, newPod.Spec.ResourceClaims, fldPath.Child("resourceClaimStatuses"))...) allErrs = append(allErrs, validatePodExtendedResourceClaimStatus(newPod.Status.ExtendedResourceClaimStatus, &newPod.Spec, fldPath.Child("extendedResourceClaimStatus"))...) + allErrs = append(allErrs, validateNodeAllocatableResourceClaimStatus(newPod.Status, &newPod.Spec, fldPath.Child("nodeAllocatableResourceClaimStatuses"))...) if newIPErrs := validatePodIPs(newPod, oldPod); len(newIPErrs) > 0 { allErrs = append(allErrs, newIPErrs...) @@ -6063,6 +6070,27 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions // ephemeral containers are not allowed to have resources allocated allErrs = append(allErrs, validateContainerStatusNoAllocatedResourcesStatus(newPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"))...) + if opts.AllowImageVolumeWithDigest { + for i, containerStatus := range newPod.Status.ContainerStatuses { + for j, volumeMountStatus := range containerStatus.VolumeMounts { + validatedField := fldPath.Child("containerStatuses").Index(i).Child("volumeMounts").Index(j).Child("volumeStatus") + allErrs = append(allErrs, validateVolumeStatus(volumeMountStatus.VolumeStatus, validatedField)...) + } + } + for i, containerStatus := range newPod.Status.InitContainerStatuses { + for j, volumeMountStatus := range containerStatus.VolumeMounts { + validatedField := fldPath.Child("initContainerStatuses").Index(i).Child("volumeMounts").Index(j).Child("volumeStatus") + allErrs = append(allErrs, validateVolumeStatus(volumeMountStatus.VolumeStatus, validatedField)...) + } + } + for i, containerStatus := range newPod.Status.EphemeralContainerStatuses { + for j, volumeMountStatus := range containerStatus.VolumeMounts { + validatedField := fldPath.Child("ephemeralContainerStatuses").Index(i).Child("volumeMounts").Index(j).Child("volumeStatus") + allErrs = append(allErrs, validateVolumeStatus(volumeMountStatus.VolumeStatus, validatedField)...) + } + } + } + return allErrs } @@ -6105,7 +6133,68 @@ func validatePodResourceClaimStatuses(statuses []core.PodResourceClaimStatus, po } if status.ResourceClaimName != nil { for _, detail := range ValidateResourceClaimName(*status.ResourceClaimName, false) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), status.ResourceClaimName, detail)) + allErrs = append(allErrs, field.Invalid(idxPath.Child("resourceClaimName"), status.ResourceClaimName, detail)) + } + } + } + + return allErrs +} + +// validateNodeAllocatableResourceClaimStatus validates NodeAllocatableResourceClaimStatuses in a pod status +func validateNodeAllocatableResourceClaimStatus(podStatus core.PodStatus, podSpec *core.PodSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(podStatus.NodeAllocatableResourceClaimStatuses) == 0 { + return allErrs + } + + for i, nodeAllocatableStatus := range podStatus.NodeAllocatableResourceClaimStatuses { + statusFldPath := fldPath.Index(i) + if nodeAllocatableStatus.ResourceClaimName == "" { + allErrs = append(allErrs, field.Required(statusFldPath.Child("resourceClaimName"), "must not be empty")) + } + + // First check the podSpec to see if the ResourceClaim is directly referenced. + // If not, check the podStatus to see if the ResourceClaim was generated from a template. + found := false + for _, claimRef := range podSpec.ResourceClaims { + if (claimRef.ResourceClaimName != nil) && (*claimRef.ResourceClaimName == nodeAllocatableStatus.ResourceClaimName) { + found = true + break + } + } + if !found { + for _, claimRef := range podStatus.ResourceClaimStatuses { + if (claimRef.ResourceClaimName != nil) && (*claimRef.ResourceClaimName == nodeAllocatableStatus.ResourceClaimName) { + found = true + break + } + } + } + + if !found { + allErrs = append(allErrs, field.Invalid(statusFldPath.Child("resourceClaimName"), nodeAllocatableStatus.ResourceClaimName, "no mapping found in pod reference")) + } + + // TODO(KEP-5517): Evaluate if its ok to have no containers referencing a node allocatable resource claim. + // This is pending on defining kubelet cgroup enforcement. + if len(nodeAllocatableStatus.Containers) == 0 { + allErrs = append(allErrs, field.Required(statusFldPath.Child("containers"), "must not be empty")) + } + + resourcesFldPath := statusFldPath.Child("resources") + if len(nodeAllocatableStatus.Resources) == 0 { + allErrs = append(allErrs, field.Required(resourcesFldPath, "must not be empty")) + } + + for resourceName, quantity := range nodeAllocatableStatus.Resources { + keyPath := resourcesFldPath.Key(string(resourceName)) + if !v1helper.IsNativeResource(v1.ResourceName(resourceName)) { + allErrs = append(allErrs, field.Invalid(keyPath, resourceName, "must be a node allocatable resource name")) + } + if quantity.Cmp(resource.Quantity{}) < 0 { + allErrs = append(allErrs, field.Invalid(keyPath, quantity.String(), "must be non-negative")) } } } @@ -6263,7 +6352,15 @@ func ValidatePodResize(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel allErrs = append(allErrs, validatePodLevelResourcesResize(newPod, oldPod, &newPodSpecCopy, specPath, opts)...) } - // Part 3: Validate that the changes between oldPod.Spec.Containers[].Resources and + // Part 3: Disable InPlaceResize if a pod is using DRA resource claims for node-allocatable resources. + // TODO(KEP-5517) - Handle in place resize with node-allocatable resource claims. + // Currently, the presence of any node-allocatable resource claim blocks resizing for all resources, irrespective of whether + // ResourceClaim is used for the same resource. + if len(oldPod.Status.NodeAllocatableResourceClaimStatuses) > 0 { + allErrs = append(allErrs, field.Forbidden(specPath, "pods with node allocatable resource claims cannot be resized")) + } + + // Part 4: Validate that the changes between oldPod.Spec.Containers[].Resources and // newPod.Spec.Containers[].Resources are allowed. Also validate that the changes between oldPod.Spec.InitContainers[].Resources and // newPod.Spec.InitContainers[].Resources are allowed. @@ -6276,17 +6373,15 @@ func ValidatePodResize(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel } // Do not allow removing resource requests/limits on resize. - if utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) { - for ix, ctr := range oldPod.Spec.InitContainers { - if !isRestartableInitContainer(&ctr) { - continue - } - allErrs = append(allErrs, validateContainerResize( - &newPod.Spec.InitContainers[ix].Resources, - &oldPod.Spec.InitContainers[ix].Resources, - newPod.Spec.InitContainers[ix].ResizePolicy, - specPath.Child("initContainers").Index(ix).Child("resources"))...) + for ix, ctr := range oldPod.Spec.InitContainers { + if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingInitContainers) && !isRestartableInitContainer(&ctr) { + continue } + allErrs = append(allErrs, validateContainerResize( + &newPod.Spec.InitContainers[ix].Resources, + &oldPod.Spec.InitContainers[ix].Resources, + newPod.Spec.InitContainers[ix].ResizePolicy, + specPath.Child("initContainers").Index(ix).Child("resources"))...) } for ix := range oldPod.Spec.Containers { allErrs = append(allErrs, validateContainerResize( @@ -6310,18 +6405,32 @@ func ValidatePodResize(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel newPodSpecCopy.Containers = newContainers // Ensure that only CPU and memory resources are mutable for restartable init containers. - // Also ensure that resources are immutable for non-restartable init containers. var newInitContainers []core.Container if utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) { for ix, container := range newPodSpecCopy.InitContainers { - if isRestartableInitContainer(&container) { // restartable init container + isRestartable := isRestartableInitContainer(&container) + canResize := isRestartable || utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingInitContainers) + modifiedContainer := !apiequality.Semantic.DeepEqual(container, oldPod.Spec.InitContainers[ix]) + + if canResize { dropCPUMemoryResourcesFromContainer(&container, &oldPod.Spec.InitContainers[ix]) if !apiequality.Semantic.DeepEqual(container, oldPod.Spec.InitContainers[ix]) { // This likely means that the user has made changes to resources other than CPU and memory for sidecar container. - errs := field.Forbidden(specPath, "only cpu and memory resources for sidecar containers are mutable") + errs := field.Forbidden(specPath.Child("initContainers").Index(ix), "only cpu and memory resources for init or sidecar containers are mutable") allErrs = append(allErrs, errs) } - } else if !apiequality.Semantic.DeepEqual(container, oldPod.Spec.InitContainers[ix]) { // non-restartable init container + if modifiedContainer && !isRestartable { + for _, resizePolicy := range container.ResizePolicy { + if resizePolicy.RestartPolicy == core.RestartContainer { + // TODO: This validation check can eventually be removed in 1.40, + // as https://github.com/kubernetes/kubernetes/pull/137458 prohibits + // the ability to set RestartContainer resize policy for non-sidecar init containers. + errs := field.Forbidden(specPath.Child("initContainers").Index(ix), "non-sidecar init containers with a resize policy of RestartContainer cannot be resized") + allErrs = append(allErrs, errs) + } + } + } + } else if modifiedContainer { // modified non-resizable init container // This likely means that the user has modified resources of non-sidecar init container. errs := field.Forbidden(specPath, "resources for non-sidecar init containers are immutable") allErrs = append(allErrs, errs) @@ -9506,6 +9615,14 @@ func validateContainerStatusAllocatedResourcesStatus(containerStatuses []core.Co allErrors = append(allErrors, field.NotSupported(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("resources").Index(k).Child("health"), r.Health, sets.List(supportedResourceHealthValues))) } + if r.Message != nil { + if len(*r.Message) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("resources").Index(k).Child("message"), "must be non-empty if specified")) + } else if len(*r.Message) > v1.ResourceHealthMessageMaxLength { + allErrors = append(allErrors, field.TooLong(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("resources").Index(k).Child("message"), *r.Message, v1.ResourceHealthMessageMaxLength)) + } + } + if uniqueResources.Has(r.ResourceID) { allErrors = append(allErrors, field.Duplicate(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("resources").Index(k).Child("resourceID"), r.ResourceID)) } else { @@ -9588,18 +9705,57 @@ func validateNodeSwapStatus(nodeSwapStatus *core.NodeSwapStatus, fldPath *field. return allErrors } -func validateWorkloadReference(workloadRef *core.WorkloadReference, fldPath *field.Path) field.ErrorList { +func validateSchedulingGroup(schedulingGroup *core.PodSchedulingGroup, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList - for _, detail := range ValidateWorkloadName(workloadRef.Name, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), workloadRef.Name, detail)) - } - for _, detail := range ValidatePodGroupName(workloadRef.PodGroup, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("podGroup"), workloadRef.PodGroup, detail)) - } - if workloadRef.PodGroupReplicaKey != "" { - for _, detail := range ValidatePodGroupReplicaKey(workloadRef.PodGroupReplicaKey, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("podGroupReplicaKey"), workloadRef.PodGroupReplicaKey, detail)) + if schedulingGroup.PodGroupName == nil { + // This is a one-of set, but there's only one possible option present right now. + allErrs = append(allErrs, field.Invalid(fldPath.Child("podGroupName"), schedulingGroup.PodGroupName, "must specify one of: `podGroupName`")) + } else { + name := *schedulingGroup.PodGroupName + for _, detail := range ValidatePodGroupName(name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("podGroupName"), name, detail)) } } return allErrs } + +func validateImageVolumeStatus(imageVolumeStatus *core.ImageVolumeStatus, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + + if imageVolumeStatus == nil { + return allErrors + } + + if imageVolumeStatus.ImageRef == "" { + allErrors = append(allErrors, field.Required(fldPath.Child("imageRef"), "imageRef must not be empty")) + } + + const maxImageRefLen = 256 + if len(imageVolumeStatus.ImageRef) > maxImageRefLen { + allErrors = append(allErrors, field.TooLong(fldPath.Child("imageRef"), imageVolumeStatus.ImageRef, maxImageRefLen)) + } + + return allErrors +} + +// validateVolumeStatus returns an error if the given struct has more than 1 populated field. +func validateVolumeStatus(volumeStatus *core.VolumeStatus, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + + if volumeStatus == nil { + return allErrors + } + + numStatuses := 0 + + if volumeStatus.Image != nil { + numStatuses++ + allErrors = append(allErrors, validateImageVolumeStatus(volumeStatus.Image, fldPath.Child("imageVolumeStatus"))...) + } + + if numStatuses > 1 { + allErrors = append(allErrors, field.Forbidden(fldPath, "may not specify more than 1 volume type")) + } + + return allErrors +} diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 97073c06c629f..c7c075c58595c 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -561,6 +561,9 @@ func TestValidatePersistentVolumes(t *testing.T) { for name, scenario := range scenarios { t.Run(name, func(t *testing.T) { + if !scenario.enableVolumeAttributesClass { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass) opts := ValidationOptionsForPersistentVolume(scenario.volume, nil) @@ -1040,6 +1043,9 @@ func TestValidationOptionsForPersistentVolume(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { + if !tc.enableVolumeAttributesClass { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass) opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv) @@ -1626,6 +1632,9 @@ func TestValidatePeristentVolumeAttributesClassUpdate(t *testing.T) { } for name, scenario := range scenarios { + if !scenario.enableVolumeAttributesClass { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass) originalNewPV := scenario.newPV.DeepCopy() @@ -2227,6 +2236,9 @@ func testValidatePVC(t *testing.T, ephemeral bool) { for name, scenario := range scenarios { t.Run(name, func(t *testing.T) { + if !scenario.enableVolumeAttributesClass { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass) var errs field.ErrorList @@ -3183,6 +3195,9 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { + if !tc.enableVolumeAttributesClass { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass) opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc) @@ -3214,6 +3229,9 @@ func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { + if !tc.enableVolumeAttributesClass { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass) opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate) @@ -8017,7 +8035,7 @@ func TestValidateResizePolicy(t *testing.T) { {ResourceName: "memory", RestartPolicy: "RestartContainer"}, }, ExpectError: true, - Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")}, + Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'")}, PodRestartPolicy: "Never", }, "InvalidMemoryPolicyWithPodRestartPolicy": { @@ -8026,7 +8044,7 @@ func TestValidateResizePolicy(t *testing.T) { {ResourceName: "memory", RestartPolicy: "NotRequired"}, }, ExpectError: true, - Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")}, + Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'")}, PodRestartPolicy: "Never", }, "InvalidMemoryCPUPolicyWithPodRestartPolicy": { @@ -8035,7 +8053,7 @@ func TestValidateResizePolicy(t *testing.T) { {ResourceName: "memory", RestartPolicy: "RestartContainer"}, }, ExpectError: true, - Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'"), field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")}, + Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'"), field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when pod `restartPolicy` is 'Never'")}, PodRestartPolicy: "Never", }, "ValidMemoryCPUPolicyWithPodRestartPolicy": { @@ -8049,19 +8067,21 @@ func TestValidateResizePolicy(t *testing.T) { }, } for k, v := range testCases { - errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy) - if !v.ExpectError && len(errs) > 0 { - t.Errorf("Testcase %s - expected success, got error: %+v", k, errs) - } - if v.ExpectError { - if len(errs) == 0 { - t.Errorf("Testcase %s - expected error, got success", k) + t.Run(k, func(t *testing.T) { + errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy) + if !v.ExpectError && len(errs) > 0 { + t.Errorf("Testcase %s - expected success, got error: %+v", k, errs) } - delta := cmp.Diff(errs, v.Errors) - if delta != "" { - t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta) + if v.ExpectError { + if len(errs) == 0 { + t.Errorf("Testcase %s - expected error, got success", k) + } + delta := cmp.Diff(errs, v.Errors) + if delta != "" { + t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta) + } } - } + }) } } @@ -9449,8 +9469,7 @@ func TestValidateInitContainers(t *testing.T) { Image: "nginx", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - } + }} successCase := []core.Container{{ Name: "container-1-same-host-port-different-protocol", @@ -9519,10 +9538,27 @@ func TestValidateInitContainers(t *testing.T) { ResizePolicy: []core.ContainerResizePolicy{ {ResourceName: "cpu", RestartPolicy: "NotRequired"}, }, - }, - } - var PodRestartPolicy core.RestartPolicy = "Never" - if errs := validateInitContainers(successCase, podOS, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{AllowSidecarResizePolicy: true}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 { + }, { + Name: "restartcontainer-policy-allowed-if-already-exists", + Image: "nginx", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}, + }, + }, { + Name: "restartcontainer-policy-allowed-if-sidecar", + Image: "nginx", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}, + }, + RestartPolicy: ptr.To(core.ContainerRestartPolicyAlways), + }} + + var PodRestartPolicy core.RestartPolicy = "Always" + if errs := validateInitContainers(successCase, podOS, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: true}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 { t.Errorf("expected success: %v", errs) } @@ -9897,22 +9933,41 @@ func TestValidateInitContainers(t *testing.T) { }, }}, field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}}, - }, - { - "Not supported ResizePolicy: invalid", - line(), - []core.Container{{ - Name: "init", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - ResizePolicy: []core.ContainerResizePolicy{ - {ResourceName: "cpu", RestartPolicy: "NotRequired"}, - }, - }}, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].resizePolicy", BadValue: []core.ContainerResizePolicy{{ResourceName: "cpu", RestartPolicy: "NotRequired"}}}}, - }, - } + }, { + "Not supported ResizePolicy: invalid", + line(), + []core.Container{{ + Name: "init", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: "cpu", RestartPolicy: "NotRequired"}, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].resizePolicy", BadValue: []core.ContainerResizePolicy{{ResourceName: "cpu", RestartPolicy: "NotRequired"}}}}, + }, { + "RestartContainer policy forbidden for non-sidecar init container", + line(), + []core.Container{{ + Name: "standard-init-no-restart", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}, + }, + }}, + field.ErrorList{{ + Type: field.ErrorTypeInvalid, + Field: "initContainers[0].resizePolicy", + BadValue: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}}, + }, { + Type: field.ErrorTypeInvalid, + Field: "initContainers[0].resizePolicy[0].restartPolicy", + BadValue: core.RestartContainer, + }}, + }} for _, tc := range errorCases { t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) { @@ -13700,6 +13755,37 @@ func TestValidatePodUpdate(t *testing.T) { ), err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set", + }, { + new: *podtest.MakePod("foo", + podtest.SetInitContainers(core.Container{ + Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}}, + }), + ), + old: *podtest.MakePod("foo", + podtest.SetInitContainers(core.Container{ + Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + }), + ), + err: "spec.initContainers[0].resizePolicy[0].restartPolicy: Invalid value: \"RestartContainer\": must not be set to 'RestartContainer' for non-sidecar initContainers", + test: "adding RestartContainer resize policy to non-sidecar init container should fail", + opts: PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: false}, + }, { + new: *podtest.MakePod("foo", + podtest.SetInitContainers(core.Container{ + Name: "ictr", Image: "image:v2", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}}, + }), + ), + old: *podtest.MakePod("foo", + podtest.SetInitContainers(core.Container{ + Name: "ictr", Image: "image:v1", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}}, + }), + ), + err: "", + test: "existing RestartContainer resize policy on non-sidecar init container should be allowed on update", + opts: PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: true}, }, { new: *podtest.MakePod("foo", podtest.SetOS(core.Windows), @@ -14557,19 +14643,44 @@ func TestValidatePodUpdate(t *testing.T) { test: "memory limit change with pod-level resources", }, { new: *podtest.MakePod("pod", - podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "w", - PodGroup: "pg", + podtest.SetSchedulingGroup(&core.PodSchedulingGroup{ + PodGroupName: new("pg1"), }), ), old: *podtest.MakePod("pod", - podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "w2", - PodGroup: "pg", + podtest.SetSchedulingGroup(&core.PodSchedulingGroup{ + PodGroupName: new("pg2"), }), ), err: "pod updates may not change fields other than", - test: "updated workloadRef", + test: "updated schedulingGroup", + }, { + new: *podtest.MakePod("pod", + podtest.SetContainers(podtest.MakeContainer("ctr", + podtest.SetContainerRestartPolicy(core.ContainerRestartPolicyNever), + podtest.SetContainerRestartPolicyRules(core.ContainerRestartRule{ + Action: core.ContainerRestartRuleActionRestartAllContainers, + ExitCodes: &core.ContainerRestartRuleOnExitCodes{ + Operator: core.ContainerRestartRuleOnExitCodesOpIn, + Values: []int32{42}, + }, + }), + )), + ), + old: *podtest.MakePod("pod", + podtest.SetContainers(podtest.MakeContainer("ctr", + podtest.SetContainerRestartPolicy(core.ContainerRestartPolicyNever), + podtest.SetContainerRestartPolicyRules(core.ContainerRestartRule{ + Action: core.ContainerRestartRuleActionRestartAllContainers, + ExitCodes: &core.ContainerRestartRuleOnExitCodes{ + Operator: core.ContainerRestartRuleOnExitCodesOpIn, + Values: []int32{43}, + }, + }), + )), + ), + err: "pod updates may not change fields other than", + test: "updated restartPolicyRules", }, } @@ -14621,6 +14732,7 @@ func TestValidatePodStatusUpdate(t *testing.T) { old core.Pod err string enableClearingNominatedNodeNameAfterBinding bool + validationOptions PodValidationOptions }{{ new: *podtest.MakePod("foo", podtest.SetStatus(core.PodStatus{ @@ -15180,7 +15292,7 @@ func TestValidatePodStatusUpdate(t *testing.T) { old: *podtest.MakePod("foo", podtest.SetResourceClaims(core.PodResourceClaim{Name: "my-claim"}), ), - err: `status.resourceClaimStatuses[0].name: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`, + err: `status.resourceClaimStatuses[0].resourceClaimName: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`, test: "Invalid ResourceClaim name", }, { new: *podtest.MakePod("foo", @@ -16013,6 +16125,117 @@ func TestValidatePodStatusUpdate(t *testing.T) { }), ), old: *podtest.MakePod("foo"), + }, { + old: *podtest.MakePod("foo", + podtest.SetStatus( + podtest.MakePodStatus( + podtest.SetContainerStatuses(core.ContainerStatus{ + VolumeMounts: []core.VolumeMountStatus{ + { + VolumeStatus: &core.VolumeStatus{ + Image: &core.ImageVolumeStatus{ + ImageRef: "imageref", + }, + }, + }, + }, + }), + ), + ), + ), + new: *podtest.MakePod("foo", + podtest.SetStatus( + podtest.MakePodStatus( + podtest.SetContainerStatuses(core.ContainerStatus{ + VolumeMounts: []core.VolumeMountStatus{ + { + VolumeStatus: &core.VolumeStatus{ + Image: nil, + }, + }, + }, + }), + ), + ), + ), + test: "should wipe VolumeStatus if ImageVolumeWithDigest feature gate is disabled didn't exist on previous pod", + validationOptions: PodValidationOptions{AllowImageVolumeWithDigest: false}, + }, { + old: *podtest.MakePod("foo", + podtest.SetStatus( + podtest.MakePodStatus( + podtest.SetContainerStatuses(core.ContainerStatus{ + VolumeMounts: []core.VolumeMountStatus{ + { + VolumeStatus: &core.VolumeStatus{ + Image: &core.ImageVolumeStatus{ + ImageRef: "", + }, + }, + }, + }, + }), + ), + ), + ), + new: *podtest.MakePod("foo", + podtest.SetStatus( + podtest.MakePodStatus( + podtest.SetContainerStatuses(core.ContainerStatus{ + VolumeMounts: []core.VolumeMountStatus{ + { + VolumeStatus: &core.VolumeStatus{ + Image: &core.ImageVolumeStatus{ + ImageRef: "", + }, + }, + }, + }, + }), + ), + ), + ), + test: "should not be allowed to set an empty imageRef", + validationOptions: PodValidationOptions{AllowImageVolumeWithDigest: true}, + err: `status.containerStatuses[0].volumeMounts[0].volumeStatus.imageVolumeStatus.imageRef: Required value: imageRef must not be empty`, + }, { + old: *podtest.MakePod("foo", + podtest.SetStatus( + podtest.MakePodStatus( + podtest.SetContainerStatuses(core.ContainerStatus{ + VolumeMounts: []core.VolumeMountStatus{ + { + VolumeStatus: &core.VolumeStatus{ + Image: &core.ImageVolumeStatus{ + ImageRef: strings.Repeat("x", 257), + }, + }, + }, + }, + }), + ), + ), + ), + new: *podtest.MakePod("foo", + podtest.SetStatus( + podtest.MakePodStatus( + podtest.SetContainerStatuses(core.ContainerStatus{ + VolumeMounts: []core.VolumeMountStatus{ + { + VolumeStatus: &core.VolumeStatus{ + Image: &core.ImageVolumeStatus{ + ImageRef: strings.Repeat("x", 257), + }, + }, + }, + }, + }), + ), + ), + ), + test: "should not be allowed to set an imageRef of len larger than 256", + validationOptions: PodValidationOptions{AllowImageVolumeWithDigest: true}, + err: `status.containerStatuses[0].volumeMounts[0].volumeStatus.imageVolumeStatus.imageRef: Too long: may not be more than 256`, }, } @@ -16021,7 +16244,7 @@ func TestValidatePodStatusUpdate(t *testing.T) { test.new.ResourceVersion = "1" test.old.ResourceVersion = "1" - errs := ValidatePodStatusUpdate(&test.new, &test.old, PodValidationOptions{}) + errs := ValidatePodStatusUpdate(&test.new, &test.old, test.validationOptions) if test.err == "" { if len(errs) != 0 { t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old) @@ -16036,6 +16259,304 @@ func TestValidatePodStatusUpdate(t *testing.T) { } } +func TestValidateNodeAllocatableResourceClaimStatus(t *testing.T) { + validPodSpec1 := core.PodSpec{ + Containers: []core.Container{ + { + Name: "c1", + Image: "image", + Resources: core.ResourceRequirements{ + Claims: []core.ResourceClaim{ + {Name: "claim1"}, + }, + }, + }, + }, + ResourceClaims: []core.PodResourceClaim{ + {Name: "claim1", ResourceClaimName: ptr.To("my-claim1")}, + }, + } + validPodSpec2 := core.PodSpec{ + Containers: []core.Container{ + { + Name: "c1", + Image: "image", + Resources: core.ResourceRequirements{ + Claims: []core.ResourceClaim{ + {Name: "claim1"}, + {Name: "claim2"}, + }, + }, + }, + }, + ResourceClaims: []core.PodResourceClaim{ + {Name: "claim1", ResourceClaimName: ptr.To("my-claim1")}, + {Name: "claim2", ResourceClaimName: ptr.To("my-claim2")}, + }, + } + + testCases := []struct { + name string + podStatus core.PodStatus + spec core.PodSpec + expectError bool + errorType field.ErrorType + errorField string + errorMsg string + }{ + { + name: "Valid NodeAllocatableResourceClaimStatus", + spec: validPodSpec1, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + core.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + }, + expectError: false, + }, + { + name: "Valid Multiple NodeAllocatableResourceClaimStatus", + spec: validPodSpec2, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + }, + }, + { + ResourceClaimName: "my-claim2", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceMemory: resource.MustParse("2Gi"), + }, + }, + }, + }, + expectError: false, + }, + { + name: "Invalid Resource Name", + spec: validPodSpec1, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + "example.com/foo": resource.MustParse("1"), + }, + }, + }, + }, + expectError: true, + errorType: field.ErrorTypeInvalid, + errorField: "status.nodeAllocatableResourceClaimStatuses[0].resources[example.com/foo]", + errorMsg: "must be a node allocatable resource name", + }, + { + name: "Negative Quantity", + spec: validPodSpec1, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("-1"), + }, + }, + }, + }, + expectError: true, + errorType: field.ErrorTypeInvalid, + errorField: "status.nodeAllocatableResourceClaimStatuses[0].resources[cpu]", + errorMsg: "must be non-negative", + }, + { + name: "Empty containers list", + spec: validPodSpec1, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + expectError: true, + errorType: field.ErrorTypeRequired, + errorField: "status.nodeAllocatableResourceClaimStatuses[0].containers", + errorMsg: "must not be empty", + }, + { + name: "Missing ResourceClaimName", + spec: validPodSpec1, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + expectError: true, + errorType: field.ErrorTypeRequired, + errorField: "status.nodeAllocatableResourceClaimStatuses[0].resourceClaimName", + errorMsg: "must not be empty", + }, + { + name: "Empty Resources", + spec: validPodSpec1, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{}, + }, + }, + }, + expectError: true, + errorType: field.ErrorTypeRequired, + errorField: "status.nodeAllocatableResourceClaimStatuses[0].resources", + errorMsg: "must not be empty", + }, + { + name: "Valid ResourceClaimName from PodSpec", + spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "c1", + Image: "image", + }, + }, + ResourceClaims: []core.PodResourceClaim{ + {Name: "claim1", ResourceClaimName: ptr.To("my-claim1")}, + }, + }, + podStatus: core.PodStatus{ + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + expectError: false, + }, + { + name: "Valid ResourceClaimName from PodStatus", + spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "c1", + Image: "image", + }, + }, + }, + podStatus: core.PodStatus{ + ResourceClaimStatuses: []core.PodResourceClaimStatus{ + {Name: "claim1", ResourceClaimName: ptr.To("generated-claim1")}, + }, + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "generated-claim1", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + expectError: false, + }, + { + name: "Invalid ResourceClaimName not found", + spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "c1", + Image: "image", + }, + }, + ResourceClaims: []core.PodResourceClaim{ + {Name: "claim1", ResourceClaimName: ptr.To("my-claim1")}, + }, + }, + podStatus: core.PodStatus{ + ResourceClaimStatuses: []core.PodResourceClaimStatus{ + {Name: "claim2", ResourceClaimName: ptr.To("generated-claim2")}, + }, + NodeAllocatableResourceClaimStatuses: []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "non-existent-claim", + Containers: []string{"c1"}, + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + expectError: true, + errorType: field.ErrorTypeInvalid, + errorField: "status.nodeAllocatableResourceClaimStatuses[0].resourceClaimName", + errorMsg: "not found in PodSpec.ResourceClaims or PodStatus.ResourceClaimStatuses", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + errs := validateNodeAllocatableResourceClaimStatus(tc.podStatus, &tc.spec, field.NewPath("status", "nodeAllocatableResourceClaimStatuses")) + + if !tc.expectError { + if len(errs) != 0 { + t.Errorf("Unexpected errors: %v", errs) + } + return + } + + if len(errs) == 0 { + t.Errorf("Expected errors, but got none") + return + } + + found := false + for _, err := range errs { + if err.Type == tc.errorType && err.Field == tc.errorField { + found = true + break + } + } + if !found { + if tc.errorMsg != "" { + t.Errorf("Expected error [Type: %v, Field: %s, Message containing: %q], but got %v", + tc.errorType, tc.errorField, tc.errorMsg, errs) + } else { + t.Errorf("Expected error [Type: %v, Field: %s], but got %v", + tc.errorType, tc.errorField, errs) + } + } + }) + } +} + func makeValidService() core.Service { clusterInternalTrafficPolicy := core.ServiceInternalTrafficPolicyCluster return core.Service{ @@ -22989,7 +23510,7 @@ func TestValidateOSFields(t *testing.T) { "Overhead", "Tolerations", "TopologySpreadConstraints", - "WorkloadRef", + "SchedulingGroup", ) expect := sets.NewString().Union(osSpecificFields).Union(osNeutralFields) @@ -27846,6 +28367,103 @@ func TestValidateContainerStatusAllocatedResourcesStatus(t *testing.T) { field.NotSupported(fldPath.Index(0).Child("allocatedResourcesStatus").Index(0).Child("resources").Index(0).Child("health"), core.ResourceHealthStatus("invalid-health-value"), []string{"Healthy", "Unhealthy", "Unknown"}), }, }, + + "don't allow message longer than max length": { + containers: []core.Container{ + { + Name: "container-1", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + "test.device/test": resource.MustParse("1"), + }, + }, + }, + }, + containerStatuses: []core.ContainerStatus{ + { + Name: "container-1", + AllocatedResourcesStatus: []core.ResourceStatus{ + { + Name: "test.device/test", + Resources: []core.ResourceHealth{ + { + ResourceID: "resource-1", + Health: core.ResourceHealthStatusHealthy, + Message: ptr.To(string(make([]byte, v1.ResourceHealthMessageMaxLength+1))), + }, + }, + }, + }, + }, + }, + wantFieldErrors: field.ErrorList{ + field.TooLong(fldPath.Index(0).Child("allocatedResourcesStatus").Index(0).Child("resources").Index(0).Child("message"), string(make([]byte, v1.ResourceHealthMessageMaxLength+1)), v1.ResourceHealthMessageMaxLength), + }, + }, + + "allow message at exactly max length": { + containers: []core.Container{ + { + Name: "container-1", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + "test.device/test": resource.MustParse("1"), + }, + }, + }, + }, + containerStatuses: []core.ContainerStatus{ + { + Name: "container-1", + AllocatedResourcesStatus: []core.ResourceStatus{ + { + Name: "test.device/test", + Resources: []core.ResourceHealth{ + { + ResourceID: "resource-1", + Health: core.ResourceHealthStatusHealthy, + Message: ptr.To(string(make([]byte, v1.ResourceHealthMessageMaxLength))), + }, + }, + }, + }, + }, + }, + wantFieldErrors: field.ErrorList{}, + }, + + "don't allow empty message": { + containers: []core.Container{ + { + Name: "container-1", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + "test.device/test": resource.MustParse("1"), + }, + }, + }, + }, + containerStatuses: []core.ContainerStatus{ + { + Name: "container-1", + AllocatedResourcesStatus: []core.ResourceStatus{ + { + Name: "test.device/test", + Resources: []core.ResourceHealth{ + { + ResourceID: "resource-1", + Health: core.ResourceHealthStatusHealthy, + Message: ptr.To(""), + }, + }, + }, + }, + }, + }, + wantFieldErrors: field.ErrorList{ + field.Required(fldPath.Index(0).Child("allocatedResourcesStatus").Index(0).Child("resources").Index(0).Child("message"), "must be non-empty if specified"), + }, + }, } for name, tt := range testCases { t.Run(name, func(t *testing.T) { @@ -27981,10 +28599,11 @@ func TestValidatePodResize(t *testing.T) { } tests := []struct { - test string - old *core.Pod - new *core.Pod - err string + test string + old *core.Pod + new *core.Pod + disableInitCtrResize bool + err string }{ { test: "pod-level resources resize with nil resources in old pod", @@ -28357,16 +28976,96 @@ func TestValidatePodResize(t *testing.T) { new: mkPodWithInitContainers(getResources("100m", "", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways), err: "spec.initContainers[0].resources.limits: Forbidden: resource limits cannot be removed", }, { - test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests", + test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests from sidecar container", old: mkPodWithInitContainers(getResources("100m", "100Mi", "", ""), getResources("100m", "", "", ""), core.ContainerRestartPolicyAlways), new: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "", "", ""), core.ContainerRestartPolicyAlways), err: "spec.initContainers[0].resources.requests: Forbidden: resource requests cannot be removed", }, { - test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests", + test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests from non-sidecar initContainer", old: mkPodWithInitContainers(getResources("100m", "100Mi", "", ""), getResources("100m", "", "", ""), ""), new: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "", "", ""), ""), - err: "spec: Forbidden: resources for non-sidecar init containers are immutable", - }, { + err: "spec.initContainers[0].resources.requests: Forbidden: resource requests cannot be removed", + }, + { + test: "Pod QoS unchanged, burstable -> burstable, attempt to resize non-sidecar initContainer without resize policy", + old: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("100m", "100Mi", "", ""), + Limits: getResources("100m", "", "", ""), + }), + ))), + new: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("200m", "200Mi", "", ""), + Limits: getResources("200m", "", "", ""), + }), + ))), + }, + { + test: "Pod QoS unchanged, burstable -> burstable, attempt to resize non-sidecar initContainer with NotRequired resize policy", + old: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("100m", "100Mi", "", ""), + Limits: getResources("100m", "", "", ""), + }), + podtest.SetContainerResizePolicy( + core.ContainerResizePolicy{ResourceName: "cpu", RestartPolicy: "NotRequired"}), + ))), + new: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("200m", "200Mi", "", ""), + Limits: getResources("200m", "", "", ""), + }), + podtest.SetContainerResizePolicy( + core.ContainerResizePolicy{ResourceName: "cpu", RestartPolicy: "NotRequired"}), + ))), + }, + { + test: "Pod QoS unchanged, burstable -> burstable, attempt to resize non-sidecar initContainer with RestartContainer resize policy", + old: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("100m", "100Mi", "", ""), + Limits: getResources("100m", "", "", ""), + }), + podtest.SetContainerResizePolicy( + core.ContainerResizePolicy{ResourceName: "cpu", RestartPolicy: "RestartContainer"}), + ))), + new: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("200m", "200Mi", "", ""), + Limits: getResources("200m", "", "", ""), + }), + podtest.SetContainerResizePolicy( + core.ContainerResizePolicy{ResourceName: "cpu", RestartPolicy: "RestartContainer"}), + ))), + err: "spec.initContainers[0]: Forbidden: non-sidecar init containers with a resize policy of RestartContainer cannot be resized", + }, + { + test: "Pod QoS unchanged, burstable -> burstable, attempt to resize non-sidecar initContainer with FG disabled", + old: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("100m", "100Mi", "", ""), + Limits: getResources("100m", "", "", ""), + }), + ))), + new: mkPodWithInitContainers(nil, nil, "", podtest.SetInitContainers( + podtest.MakeContainer("ctr-1", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("200m", "200Mi", "", ""), + Limits: getResources("200m", "", "", ""), + }), + ))), + disableInitCtrResize: true, + err: "Forbidden: resources for non-sidecar init containers are immutable", + }, + { test: "Pod with nil Resource field in Status", old: mkPod(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), podtest.SetStatus(core.PodStatus{ ContainerStatuses: []core.ContainerStatus{{ @@ -28457,7 +29156,7 @@ func TestValidatePodResize(t *testing.T) { test: "storage limit change for sidecar containers", old: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "100Mi", "2Gi", ""), core.ContainerRestartPolicyAlways), new: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "100Mi", "1Gi", ""), core.ContainerRestartPolicyAlways), - err: "spec: Forbidden: only cpu and memory resources for sidecar containers are mutable", + err: "spec.initContainers[0]: Forbidden: only cpu and memory resources for init or sidecar containers are mutable", }, { test: "cpu request change for sidecar containers", old: mkPodWithInitContainers(getResources("200m", "0", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways), @@ -28472,7 +29171,7 @@ func TestValidatePodResize(t *testing.T) { test: "storage request change for sidecar containers", old: mkPodWithInitContainers(getResources("100m", "0", "1Gi", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways), new: mkPodWithInitContainers(getResources("100m", "0", "2Gi", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways), - err: "spec: Forbidden: only cpu and memory resources for sidecar containers are mutable", + err: "spec.initContainers[0]: Forbidden: only cpu and memory resources for init or sidecar containers are mutable", }, { test: "pod container addition", old: podtest.MakePod("pod", podtest.SetContainers( @@ -28576,10 +29275,78 @@ func TestValidatePodResize(t *testing.T) { )), err: "spec: Forbidden: only cpu and memory resources are mutable", }, + { + test: "Resize pod with NodeAllocatableResourceClaimStatuses", + old: func() *core.Pod { + p := podtest.MakePod("pod", + podtest.SetContainers(podtest.MakeContainer("container", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("100m", "200Mi", "", ""), + }))), + podtest.SetPodResources(&core.ResourceRequirements{Limits: getResources("100m", "200Mi", "", "")}), + ) + p.Status.NodeAllocatableResourceClaimStatuses = []core.NodeAllocatableResourceClaimStatus{{ResourceClaimName: "node-allocatable-claim"}} + return p + }(), + new: func() *core.Pod { + p := podtest.MakePod("pod", + podtest.SetContainers(podtest.MakeContainer("container", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("200m", "200Mi", "", ""), + }))), + podtest.SetPodResources(&core.ResourceRequirements{Limits: getResources("200m", "200Mi", "", "")}), + ) + p.Status.NodeAllocatableResourceClaimStatuses = []core.NodeAllocatableResourceClaimStatus{{ResourceClaimName: "node-allocatable-claim"}} + return p + }(), + err: "spec: Forbidden: pods with node allocatable resource claims cannot be resized", + }, + { + test: "Resize pod with NodeAllocatableResourceClaimStatuses", + old: func() *core.Pod { + p := podtest.MakePod("pod", + podtest.SetContainers(podtest.MakeContainer("container", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("100m", "200Mi", "", ""), + }))), + podtest.SetPodResources(&core.ResourceRequirements{Limits: getResources("100m", "200Mi", "", "")}), + ) + p.Status.NodeAllocatableResourceClaimStatuses = []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("100m"), + }, + }, + } + return p + }(), + new: func() *core.Pod { + p := podtest.MakePod("pod", + podtest.SetContainers(podtest.MakeContainer("container", + podtest.SetContainerResources(core.ResourceRequirements{ + Requests: getResources("200m", "200Mi", "", ""), + }))), + podtest.SetPodResources(&core.ResourceRequirements{Limits: getResources("200m", "200Mi", "", "")}), + ) + p.Status.NodeAllocatableResourceClaimStatuses = []core.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[core.ResourceName]resource.Quantity{ + core.ResourceCPU: resource.MustParse("100m"), + }, + }, + } + return p + }(), + err: "spec: Forbidden: pods with node allocatable resource claims cannot be resized", + }, } for _, test := range tests { t.Run(test.test, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingInitContainers, !test.disableInitCtrResize) + test.new.ObjectMeta.ResourceVersion = "1" test.old.ObjectMeta.ResourceVersion = "1" @@ -29476,6 +30243,44 @@ func TestValidateContainerRestartPolicy(t *testing.T) { BadValue: core.ContainerRestartRuleOnExitCodesOperator(""), }}, }, + { + Name: "restart-policy-rules with too many exit codes", + RestartPolicy: &containerRestartPolicyNever, + RestartPolicyRules: []core.ContainerRestartRule{{ + Action: "Restart", + ExitCodes: &core.ContainerRestartRuleOnExitCodes{ + Operator: "In", + Values: make([]int32, 256), + }, + }}, + ExpectedErrors: field.ErrorList{{ + Type: field.ErrorTypeTooMany, + Field: "containers[0].restartPolicyRules[0].exitCodes.values", + BadValue: 256, + }}, + }, + { + Name: "restart-policy-rules with too many rules", + RestartPolicy: &containerRestartPolicyNever, + RestartPolicyRules: func() []core.ContainerRestartRule { + rules := make([]core.ContainerRestartRule, 21) + for i := range rules { + rules[i] = core.ContainerRestartRule{ + Action: "Restart", + ExitCodes: &core.ContainerRestartRuleOnExitCodes{ + Operator: "In", + Values: []int32{42}, + }, + } + } + return rules + }(), + ExpectedErrors: field.ErrorList{{ + Type: field.ErrorTypeTooMany, + Field: "containers[0].restartPolicyRules", + BadValue: 21, + }}, + }, } for _, tc := range errorCases { @@ -30277,17 +31082,10 @@ func TestValidateNodeDeclaredFeatures(t *testing.T) { } } -func TestValidateWorkloadReference(t *testing.T) { +func TestValidatePodSchedulingGroup(t *testing.T) { successCases := map[string]*core.Pod{ - "correct": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: "group", - PodGroupReplicaKey: "replica", - })), - "no replica key": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: "group", - PodGroupReplicaKey: "", + "correct": podtest.MakePod("", podtest.SetSchedulingGroup(&core.PodSchedulingGroup{ + PodGroupName: new("group"), })), } for name, pod := range successCases { @@ -30298,45 +31096,15 @@ func TestValidateWorkloadReference(t *testing.T) { } failureCases := map[string]*core.Pod{ - "empty workload name": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "", - PodGroup: "group", - PodGroupReplicaKey: "replica", - })), - "incorrect workload name": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: ".workload", - PodGroup: "group", - PodGroupReplicaKey: "replica", - })), - "too long workload name": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: strings.Repeat("w", 254), - PodGroup: "group", - PodGroupReplicaKey: "replica", - })), - "empty pod group": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: "", - PodGroupReplicaKey: "replica", - })), - "incorrect pod group": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: ".group", - PodGroupReplicaKey: "replica", - })), - "too long pod group": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: strings.Repeat("g", 64), - PodGroupReplicaKey: "replica", + "nil pod group name": podtest.MakePod("", podtest.SetSchedulingGroup(&core.PodSchedulingGroup{})), + "empty pod group name": podtest.MakePod("", podtest.SetSchedulingGroup(&core.PodSchedulingGroup{ + PodGroupName: new(""), })), - "incorrect replica key": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: "group", - PodGroupReplicaKey: ".replica", + "incorrect pod group name": podtest.MakePod("", podtest.SetSchedulingGroup(&core.PodSchedulingGroup{ + PodGroupName: new(".podGroup"), })), - "too long replica key": podtest.MakePod("", podtest.SetWorkloadRef(&core.WorkloadReference{ - Name: "workload", - PodGroup: "group", - PodGroupReplicaKey: strings.Repeat("r", 64), + "too long pod group name": podtest.MakePod("", podtest.SetSchedulingGroup(&core.PodSchedulingGroup{ + PodGroupName: new(strings.Repeat("w", 254)), })), } for name, pod := range failureCases { diff --git a/pkg/apis/core/zz_generated.deepcopy.go b/pkg/apis/core/zz_generated.deepcopy.go index 017ac70cd420a..e2d01dcdd3106 100644 --- a/pkg/apis/core/zz_generated.deepcopy.go +++ b/pkg/apis/core/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package core import ( + resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" @@ -2170,6 +2171,22 @@ func (in *ImageVolumeSource) DeepCopy() *ImageVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageVolumeStatus) DeepCopyInto(out *ImageVolumeStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageVolumeStatus. +func (in *ImageVolumeStatus) DeepCopy() *ImageVolumeStatus { + if in == nil { + return nil + } + out := new(ImageVolumeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KeyToPath) DeepCopyInto(out *KeyToPath) { *out = *in @@ -2762,6 +2779,34 @@ func (in *NodeAffinity) DeepCopy() *NodeAffinity { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAllocatableResourceClaimStatus) DeepCopyInto(out *NodeAllocatableResourceClaimStatus) { + *out = *in + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(map[ResourceName]resource.Quantity, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAllocatableResourceClaimStatus. +func (in *NodeAllocatableResourceClaimStatus) DeepCopy() *NodeAllocatableResourceClaimStatus { + if in == nil { + return nil + } + out := new(NodeAllocatableResourceClaimStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeCondition) DeepCopyInto(out *NodeCondition) { *out = *in @@ -4301,6 +4346,27 @@ func (in *PodSchedulingGate) DeepCopy() *PodSchedulingGate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSchedulingGroup) DeepCopyInto(out *PodSchedulingGroup) { + *out = *in + if in.PodGroupName != nil { + in, out := &in.PodGroupName, &out.PodGroupName + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSchedulingGroup. +func (in *PodSchedulingGroup) DeepCopy() *PodSchedulingGroup { + if in == nil { + return nil + } + out := new(PodSchedulingGroup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) { *out = *in @@ -4571,10 +4637,10 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { *out = new(string) **out = **in } - if in.WorkloadRef != nil { - in, out := &in.WorkloadRef, &out.WorkloadRef - *out = new(WorkloadReference) - **out = **in + if in.SchedulingGroup != nil { + in, out := &in.SchedulingGroup, &out.SchedulingGroup + *out = new(PodSchedulingGroup) + (*in).DeepCopyInto(*out) } return } @@ -4658,6 +4724,13 @@ func (in *PodStatus) DeepCopyInto(out *PodStatus) { *out = new(ResourceRequirements) (*in).DeepCopyInto(*out) } + if in.NodeAllocatableResourceClaimStatuses != nil { + in, out := &in.NodeAllocatableResourceClaimStatuses, &out.NodeAllocatableResourceClaimStatuses + *out = make([]NodeAllocatableResourceClaimStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -5224,6 +5297,11 @@ func (in *ResourceFieldSelector) DeepCopy() *ResourceFieldSelector { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceHealth) DeepCopyInto(out *ResourceHealth) { *out = *in + if in.Message != nil { + in, out := &in.Message, &out.Message + *out = new(string) + **out = **in + } return } @@ -5424,7 +5502,9 @@ func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { if in.Resources != nil { in, out := &in.Resources, &out.Resources *out = make([]ResourceHealth, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } return } @@ -6519,6 +6599,11 @@ func (in *VolumeMountStatus) DeepCopyInto(out *VolumeMountStatus) { *out = new(RecursiveReadOnlyMode) **out = **in } + if in.VolumeStatus != nil { + in, out := &in.VolumeStatus, &out.VolumeStatus + *out = new(VolumeStatus) + (*in).DeepCopyInto(*out) + } return } @@ -6795,6 +6880,27 @@ func (in *VolumeSource) DeepCopy() *VolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeStatus) DeepCopyInto(out *VolumeStatus) { + *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ImageVolumeStatus) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeStatus. +func (in *VolumeStatus) DeepCopy() *VolumeStatus { + if in == nil { + return nil + } + out := new(VolumeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VsphereVirtualDiskVolumeSource) DeepCopyInto(out *VsphereVirtualDiskVolumeSource) { *out = *in @@ -6863,19 +6969,3 @@ func (in *WindowsSecurityContextOptions) DeepCopy() *WindowsSecurityContextOptio in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkloadReference) DeepCopyInto(out *WorkloadReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadReference. -func (in *WorkloadReference) DeepCopy() *WorkloadReference { - if in == nil { - return nil - } - out := new(WorkloadReference) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apis/discovery/v1/doc.go b/pkg/apis/discovery/v1/doc.go index 83d3f4ab281c2..45029f3368c77 100644 --- a/pkg/apis/discovery/v1/doc.go +++ b/pkg/apis/discovery/v1/doc.go @@ -18,5 +18,7 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/discovery/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/discovery/v1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/discovery/v1 package v1 diff --git a/pkg/apis/discovery/v1/zz_generated.validations.go b/pkg/apis/discovery/v1/zz_generated.validations.go new file mode 100644 index 0000000000000..ec77329c9d762 --- /dev/null +++ b/pkg/apis/discovery/v1/zz_generated.validations.go @@ -0,0 +1,153 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + discoveryv1 "k8s.io/api/discovery/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type EndpointSlice + scheme.AddValidationFunc((*discoveryv1.EndpointSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_EndpointSlice(ctx, op, nil /* fldPath */, obj.(*discoveryv1.EndpointSlice), safe.Cast[*discoveryv1.EndpointSlice](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +var symbolsForAddressType = sets.New(discoveryv1.AddressTypeFQDN, discoveryv1.AddressTypeIPv4, discoveryv1.AddressTypeIPv6) + +// Validate_AddressType validates an instance of AddressType according +// to declarative validation rules in the API schema. +func Validate_AddressType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.AddressType) (errs field.ErrorList) { + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAddressType, nil).MarkAlpha()...) + + return errs +} + +// Validate_Endpoint validates an instance of Endpoint according +// to declarative validation rules in the API schema. +func Validate_Endpoint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.Endpoint) (errs field.ErrorList) { + // field discoveryv1.Endpoint.Addresses + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 100).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("addresses"), obj.Addresses, safe.Field(oldObj, func(oldObj *discoveryv1.Endpoint) []string { return oldObj.Addresses }), oldObj != nil)...) + + // field discoveryv1.Endpoint.Conditions has no validation + // field discoveryv1.Endpoint.Hostname has no validation + // field discoveryv1.Endpoint.TargetRef has no validation + // field discoveryv1.Endpoint.DeprecatedTopology has no validation + // field discoveryv1.Endpoint.NodeName has no validation + // field discoveryv1.Endpoint.Zone has no validation + // field discoveryv1.Endpoint.Hints has no validation + return errs +} + +// Validate_EndpointSlice validates an instance of EndpointSlice according +// to declarative validation rules in the API schema. +func Validate_EndpointSlice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.EndpointSlice) (errs field.ErrorList) { + // field discoveryv1.EndpointSlice.TypeMeta has no validation + // field discoveryv1.EndpointSlice.ObjectMeta has no validation + + // field discoveryv1.EndpointSlice.AddressType + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *discoveryv1.AddressType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_AddressType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("addressType"), &obj.AddressType, safe.Field(oldObj, func(oldObj *discoveryv1.EndpointSlice) *discoveryv1.AddressType { return &oldObj.AddressType }), oldObj != nil)...) + + // field discoveryv1.EndpointSlice.Endpoints + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []discoveryv1.Endpoint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Endpoint)...) + return + }(fldPath.Child("endpoints"), obj.Endpoints, safe.Field(oldObj, func(oldObj *discoveryv1.EndpointSlice) []discoveryv1.Endpoint { return oldObj.Endpoints }), oldObj != nil)...) + + // field discoveryv1.EndpointSlice.Ports has no validation + return errs +} diff --git a/pkg/apis/discovery/v1beta1/doc.go b/pkg/apis/discovery/v1beta1/doc.go index fbd710a604b39..01ae0dfb3fcc3 100644 --- a/pkg/apis/discovery/v1beta1/doc.go +++ b/pkg/apis/discovery/v1beta1/doc.go @@ -18,5 +18,7 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/discovery/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/discovery/v1beta1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/discovery/v1beta1 package v1beta1 diff --git a/pkg/apis/discovery/v1beta1/zz_generated.validations.go b/pkg/apis/discovery/v1beta1/zz_generated.validations.go new file mode 100644 index 0000000000000..312ab51fba56b --- /dev/null +++ b/pkg/apis/discovery/v1beta1/zz_generated.validations.go @@ -0,0 +1,152 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + discoveryv1beta1 "k8s.io/api/discovery/v1beta1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type EndpointSlice + scheme.AddValidationFunc((*discoveryv1beta1.EndpointSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_EndpointSlice(ctx, op, nil /* fldPath */, obj.(*discoveryv1beta1.EndpointSlice), safe.Cast[*discoveryv1beta1.EndpointSlice](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +var symbolsForAddressType = sets.New(discoveryv1beta1.AddressTypeFQDN, discoveryv1beta1.AddressTypeIPv4, discoveryv1beta1.AddressTypeIPv6) + +// Validate_AddressType validates an instance of AddressType according +// to declarative validation rules in the API schema. +func Validate_AddressType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.AddressType) (errs field.ErrorList) { + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAddressType, nil).MarkAlpha()...) + + return errs +} + +// Validate_Endpoint validates an instance of Endpoint according +// to declarative validation rules in the API schema. +func Validate_Endpoint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.Endpoint) (errs field.ErrorList) { + // field discoveryv1beta1.Endpoint.Addresses + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 100).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("addresses"), obj.Addresses, safe.Field(oldObj, func(oldObj *discoveryv1beta1.Endpoint) []string { return oldObj.Addresses }), oldObj != nil)...) + + // field discoveryv1beta1.Endpoint.Conditions has no validation + // field discoveryv1beta1.Endpoint.Hostname has no validation + // field discoveryv1beta1.Endpoint.TargetRef has no validation + // field discoveryv1beta1.Endpoint.Topology has no validation + // field discoveryv1beta1.Endpoint.NodeName has no validation + // field discoveryv1beta1.Endpoint.Hints has no validation + return errs +} + +// Validate_EndpointSlice validates an instance of EndpointSlice according +// to declarative validation rules in the API schema. +func Validate_EndpointSlice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.EndpointSlice) (errs field.ErrorList) { + // field discoveryv1beta1.EndpointSlice.TypeMeta has no validation + // field discoveryv1beta1.EndpointSlice.ObjectMeta has no validation + + // field discoveryv1beta1.EndpointSlice.AddressType + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *discoveryv1beta1.AddressType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_AddressType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("addressType"), &obj.AddressType, safe.Field(oldObj, func(oldObj *discoveryv1beta1.EndpointSlice) *discoveryv1beta1.AddressType { return &oldObj.AddressType }), oldObj != nil)...) + + // field discoveryv1beta1.EndpointSlice.Endpoints + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []discoveryv1beta1.Endpoint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Endpoint)...) + return + }(fldPath.Child("endpoints"), obj.Endpoints, safe.Field(oldObj, func(oldObj *discoveryv1beta1.EndpointSlice) []discoveryv1beta1.Endpoint { return oldObj.Endpoints }), oldObj != nil)...) + + // field discoveryv1beta1.EndpointSlice.Ports has no validation + return errs +} diff --git a/pkg/apis/discovery/validation/validation.go b/pkg/apis/discovery/validation/validation.go index 98e9f9f03b119..10bdc69a1d442 100644 --- a/pkg/apis/discovery/validation/validation.go +++ b/pkg/apis/discovery/validation/validation.go @@ -86,7 +86,7 @@ func ValidateEndpointSliceCreate(endpointSlice *discovery.EndpointSlice) field.E // ValidateEndpointSliceUpdate validates an EndpointSlice when it is updated. func ValidateEndpointSliceUpdate(newEndpointSlice, oldEndpointSlice *discovery.EndpointSlice) field.ErrorList { allErrs := ValidateEndpointSlice(newEndpointSlice, oldEndpointSlice) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType"))...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType")).WithOrigin("immutable").MarkCoveredByDeclarative()...) return allErrs } @@ -104,11 +104,10 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres addressPath := idxPath.Child("addresses") if len(endpoint.Addresses) == 0 { - allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address")) + allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address").MarkCoveredByDeclarative()) } else if len(endpoint.Addresses) > maxAddresses { - allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses)) + allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses).WithOrigin("maxItems").MarkCoveredByDeclarative()) } - for i, address := range endpoint.Addresses { // This validates known address types, unknown types fall through // and do not get validated. @@ -208,9 +207,9 @@ func validateAddressType(addressType discovery.AddressType) field.ErrorList { allErrs := field.ErrorList{} if addressType == "" { - allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "")) + allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "").MarkCoveredByDeclarative()) } else if !supportedAddressTypes.Has(addressType) { - allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, sets.List(supportedAddressTypes))) + allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, sets.List(supportedAddressTypes)).MarkCoveredByDeclarative()) } return allErrs diff --git a/pkg/apis/flowcontrol/v1/doc.go b/pkg/apis/flowcontrol/v1/doc.go index cf32c99fc7f89..a0bcdc5970815 100644 --- a/pkg/apis/flowcontrol/v1/doc.go +++ b/pkg/apis/flowcontrol/v1/doc.go @@ -18,6 +18,8 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1 // +groupName=flowcontrol.apiserver.k8s.io diff --git a/pkg/apis/flowcontrol/v1/zz_generated.validations.go b/pkg/apis/flowcontrol/v1/zz_generated.validations.go new file mode 100644 index 0000000000000..04fb2a122aa93 --- /dev/null +++ b/pkg/apis/flowcontrol/v1/zz_generated.validations.go @@ -0,0 +1,325 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + flowcontrolv1 "k8s.io/api/flowcontrol/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type PriorityLevelConfiguration + scheme.AddValidationFunc((*flowcontrolv1.PriorityLevelConfiguration)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_PriorityLevelConfiguration(ctx, op, nil /* fldPath */, obj.(*flowcontrolv1.PriorityLevelConfiguration), safe.Cast[*flowcontrolv1.PriorityLevelConfiguration](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_LimitResponse validates an instance of LimitResponse according +// to declarative validation rules in the API schema. +func Validate_LimitResponse(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitResponse) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "queuing", func(obj *flowcontrolv1.LimitResponse) *flowcontrolv1.QueuingConfiguration { return obj.Queuing }, func(obj *flowcontrolv1.LimitResponse) flowcontrolv1.LimitResponseType { return obj.Type }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1.QueuingConfiguration, flowcontrolv1.LimitResponseType]{ + { + Value: "Queue", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Reject", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1.LimitResponse.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitResponseType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1.LimitResponse) *flowcontrolv1.LimitResponseType { return &oldObj.Type }), oldObj != nil)...) + + // field flowcontrolv1.LimitResponse.Queuing + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.QueuingConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("queuing"), obj.Queuing, safe.Field(oldObj, func(oldObj *flowcontrolv1.LimitResponse) *flowcontrolv1.QueuingConfiguration { return oldObj.Queuing }), oldObj != nil)...) + + return errs +} + +// Validate_LimitedPriorityLevelConfiguration validates an instance of LimitedPriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_LimitedPriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1.LimitedPriorityLevelConfiguration.NominalConcurrencyShares has no validation + + // field flowcontrolv1.LimitedPriorityLevelConfiguration.LimitResponse + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitResponse, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_LimitResponse(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limitResponse"), &obj.LimitResponse, safe.Field(oldObj, func(oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration) *flowcontrolv1.LimitResponse { + return &oldObj.LimitResponse + }), oldObj != nil)...) + + // field flowcontrolv1.LimitedPriorityLevelConfiguration.LendablePercent has no validation + // field flowcontrolv1.LimitedPriorityLevelConfiguration.BorrowingLimitPercent has no validation + return errs +} + +// Validate_PriorityLevelConfiguration validates an instance of PriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.PriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1.PriorityLevelConfiguration.TypeMeta has no validation + // field flowcontrolv1.PriorityLevelConfiguration.ObjectMeta has no validation + + // field flowcontrolv1.PriorityLevelConfiguration.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.PriorityLevelConfigurationSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PriorityLevelConfigurationSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *flowcontrolv1.PriorityLevelConfiguration) *flowcontrolv1.PriorityLevelConfigurationSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field flowcontrolv1.PriorityLevelConfiguration.Status has no validation + return errs +} + +// Validate_PriorityLevelConfigurationSpec validates an instance of PriorityLevelConfigurationSpec according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfigurationSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.PriorityLevelConfigurationSpec) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "exempt", func(obj *flowcontrolv1.PriorityLevelConfigurationSpec) *flowcontrolv1.ExemptPriorityLevelConfiguration { + return obj.Exempt + }, func(obj *flowcontrolv1.PriorityLevelConfigurationSpec) flowcontrolv1.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1.ExemptPriorityLevelConfiguration, flowcontrolv1.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "limited", func(obj *flowcontrolv1.PriorityLevelConfigurationSpec) *flowcontrolv1.LimitedPriorityLevelConfiguration { + return obj.Limited + }, func(obj *flowcontrolv1.PriorityLevelConfigurationSpec) flowcontrolv1.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1.LimitedPriorityLevelConfiguration, flowcontrolv1.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1.PriorityLevelConfigurationSpec.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.PriorityLevelEnablement, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1.PriorityLevelConfigurationSpec) *flowcontrolv1.PriorityLevelEnablement { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1.PriorityLevelConfigurationSpec.Limited + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_LimitedPriorityLevelConfiguration(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limited"), obj.Limited, safe.Field(oldObj, func(oldObj *flowcontrolv1.PriorityLevelConfigurationSpec) *flowcontrolv1.LimitedPriorityLevelConfiguration { + return oldObj.Limited + }), oldObj != nil)...) + + // field flowcontrolv1.PriorityLevelConfigurationSpec.Exempt + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1.ExemptPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("exempt"), obj.Exempt, safe.Field(oldObj, func(oldObj *flowcontrolv1.PriorityLevelConfigurationSpec) *flowcontrolv1.ExemptPriorityLevelConfiguration { + return oldObj.Exempt + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/flowcontrol/v1beta1/doc.go b/pkg/apis/flowcontrol/v1beta1/doc.go index e4d5cebebc20b..12c41c18d11c6 100644 --- a/pkg/apis/flowcontrol/v1beta1/doc.go +++ b/pkg/apis/flowcontrol/v1beta1/doc.go @@ -18,6 +18,8 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1beta1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1beta1 // +groupName=flowcontrol.apiserver.k8s.io diff --git a/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go new file mode 100644 index 0000000000000..185c148be5b45 --- /dev/null +++ b/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go @@ -0,0 +1,331 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + flowcontrolv1beta1 "k8s.io/api/flowcontrol/v1beta1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type PriorityLevelConfiguration + scheme.AddValidationFunc((*flowcontrolv1beta1.PriorityLevelConfiguration)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_PriorityLevelConfiguration(ctx, op, nil /* fldPath */, obj.(*flowcontrolv1beta1.PriorityLevelConfiguration), safe.Cast[*flowcontrolv1beta1.PriorityLevelConfiguration](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_LimitResponse validates an instance of LimitResponse according +// to declarative validation rules in the API schema. +func Validate_LimitResponse(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitResponse) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "queuing", func(obj *flowcontrolv1beta1.LimitResponse) *flowcontrolv1beta1.QueuingConfiguration { + return obj.Queuing + }, func(obj *flowcontrolv1beta1.LimitResponse) flowcontrolv1beta1.LimitResponseType { return obj.Type }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta1.QueuingConfiguration, flowcontrolv1beta1.LimitResponseType]{ + { + Value: "Queue", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Reject", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1beta1.LimitResponse.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitResponseType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.LimitResponse) *flowcontrolv1beta1.LimitResponseType { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1beta1.LimitResponse.Queuing + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.QueuingConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("queuing"), obj.Queuing, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.LimitResponse) *flowcontrolv1beta1.QueuingConfiguration { + return oldObj.Queuing + }), oldObj != nil)...) + + return errs +} + +// Validate_LimitedPriorityLevelConfiguration validates an instance of LimitedPriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_LimitedPriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1beta1.LimitedPriorityLevelConfiguration.AssuredConcurrencyShares has no validation + + // field flowcontrolv1beta1.LimitedPriorityLevelConfiguration.LimitResponse + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitResponse, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_LimitResponse(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limitResponse"), &obj.LimitResponse, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration) *flowcontrolv1beta1.LimitResponse { + return &oldObj.LimitResponse + }), oldObj != nil)...) + + // field flowcontrolv1beta1.LimitedPriorityLevelConfiguration.LendablePercent has no validation + // field flowcontrolv1beta1.LimitedPriorityLevelConfiguration.BorrowingLimitPercent has no validation + return errs +} + +// Validate_PriorityLevelConfiguration validates an instance of PriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.PriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1beta1.PriorityLevelConfiguration.TypeMeta has no validation + // field flowcontrolv1beta1.PriorityLevelConfiguration.ObjectMeta has no validation + + // field flowcontrolv1beta1.PriorityLevelConfiguration.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.PriorityLevelConfigurationSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PriorityLevelConfigurationSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.PriorityLevelConfiguration) *flowcontrolv1beta1.PriorityLevelConfigurationSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field flowcontrolv1beta1.PriorityLevelConfiguration.Status has no validation + return errs +} + +// Validate_PriorityLevelConfigurationSpec validates an instance of PriorityLevelConfigurationSpec according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfigurationSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "exempt", func(obj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) *flowcontrolv1beta1.ExemptPriorityLevelConfiguration { + return obj.Exempt + }, func(obj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) flowcontrolv1beta1.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta1.ExemptPriorityLevelConfiguration, flowcontrolv1beta1.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "limited", func(obj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) *flowcontrolv1beta1.LimitedPriorityLevelConfiguration { + return obj.Limited + }, func(obj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) flowcontrolv1beta1.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta1.LimitedPriorityLevelConfiguration, flowcontrolv1beta1.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1beta1.PriorityLevelConfigurationSpec.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.PriorityLevelEnablement, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) *flowcontrolv1beta1.PriorityLevelEnablement { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1beta1.PriorityLevelConfigurationSpec.Limited + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_LimitedPriorityLevelConfiguration(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limited"), obj.Limited, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) *flowcontrolv1beta1.LimitedPriorityLevelConfiguration { + return oldObj.Limited + }), oldObj != nil)...) + + // field flowcontrolv1beta1.PriorityLevelConfigurationSpec.Exempt + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.ExemptPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("exempt"), obj.Exempt, safe.Field(oldObj, func(oldObj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) *flowcontrolv1beta1.ExemptPriorityLevelConfiguration { + return oldObj.Exempt + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/flowcontrol/v1beta2/doc.go b/pkg/apis/flowcontrol/v1beta2/doc.go index 7f38044a8ccac..75bac74f3548a 100644 --- a/pkg/apis/flowcontrol/v1beta2/doc.go +++ b/pkg/apis/flowcontrol/v1beta2/doc.go @@ -18,6 +18,8 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1beta2 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1beta2 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1beta2 // +groupName=flowcontrol.apiserver.k8s.io diff --git a/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go new file mode 100644 index 0000000000000..e8aff18cfef04 --- /dev/null +++ b/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go @@ -0,0 +1,331 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + fmt "fmt" + + flowcontrolv1beta2 "k8s.io/api/flowcontrol/v1beta2" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type PriorityLevelConfiguration + scheme.AddValidationFunc((*flowcontrolv1beta2.PriorityLevelConfiguration)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_PriorityLevelConfiguration(ctx, op, nil /* fldPath */, obj.(*flowcontrolv1beta2.PriorityLevelConfiguration), safe.Cast[*flowcontrolv1beta2.PriorityLevelConfiguration](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_LimitResponse validates an instance of LimitResponse according +// to declarative validation rules in the API schema. +func Validate_LimitResponse(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitResponse) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "queuing", func(obj *flowcontrolv1beta2.LimitResponse) *flowcontrolv1beta2.QueuingConfiguration { + return obj.Queuing + }, func(obj *flowcontrolv1beta2.LimitResponse) flowcontrolv1beta2.LimitResponseType { return obj.Type }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta2.QueuingConfiguration, flowcontrolv1beta2.LimitResponseType]{ + { + Value: "Queue", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Reject", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1beta2.LimitResponse.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitResponseType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.LimitResponse) *flowcontrolv1beta2.LimitResponseType { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1beta2.LimitResponse.Queuing + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.QueuingConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("queuing"), obj.Queuing, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.LimitResponse) *flowcontrolv1beta2.QueuingConfiguration { + return oldObj.Queuing + }), oldObj != nil)...) + + return errs +} + +// Validate_LimitedPriorityLevelConfiguration validates an instance of LimitedPriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_LimitedPriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1beta2.LimitedPriorityLevelConfiguration.AssuredConcurrencyShares has no validation + + // field flowcontrolv1beta2.LimitedPriorityLevelConfiguration.LimitResponse + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitResponse, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_LimitResponse(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limitResponse"), &obj.LimitResponse, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration) *flowcontrolv1beta2.LimitResponse { + return &oldObj.LimitResponse + }), oldObj != nil)...) + + // field flowcontrolv1beta2.LimitedPriorityLevelConfiguration.LendablePercent has no validation + // field flowcontrolv1beta2.LimitedPriorityLevelConfiguration.BorrowingLimitPercent has no validation + return errs +} + +// Validate_PriorityLevelConfiguration validates an instance of PriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.PriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1beta2.PriorityLevelConfiguration.TypeMeta has no validation + // field flowcontrolv1beta2.PriorityLevelConfiguration.ObjectMeta has no validation + + // field flowcontrolv1beta2.PriorityLevelConfiguration.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.PriorityLevelConfigurationSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PriorityLevelConfigurationSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.PriorityLevelConfiguration) *flowcontrolv1beta2.PriorityLevelConfigurationSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field flowcontrolv1beta2.PriorityLevelConfiguration.Status has no validation + return errs +} + +// Validate_PriorityLevelConfigurationSpec validates an instance of PriorityLevelConfigurationSpec according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfigurationSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "exempt", func(obj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) *flowcontrolv1beta2.ExemptPriorityLevelConfiguration { + return obj.Exempt + }, func(obj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) flowcontrolv1beta2.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta2.ExemptPriorityLevelConfiguration, flowcontrolv1beta2.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "limited", func(obj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) *flowcontrolv1beta2.LimitedPriorityLevelConfiguration { + return obj.Limited + }, func(obj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) flowcontrolv1beta2.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta2.LimitedPriorityLevelConfiguration, flowcontrolv1beta2.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1beta2.PriorityLevelConfigurationSpec.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.PriorityLevelEnablement, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) *flowcontrolv1beta2.PriorityLevelEnablement { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1beta2.PriorityLevelConfigurationSpec.Limited + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_LimitedPriorityLevelConfiguration(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limited"), obj.Limited, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) *flowcontrolv1beta2.LimitedPriorityLevelConfiguration { + return oldObj.Limited + }), oldObj != nil)...) + + // field flowcontrolv1beta2.PriorityLevelConfigurationSpec.Exempt + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.ExemptPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("exempt"), obj.Exempt, safe.Field(oldObj, func(oldObj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) *flowcontrolv1beta2.ExemptPriorityLevelConfiguration { + return oldObj.Exempt + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/flowcontrol/v1beta3/doc.go b/pkg/apis/flowcontrol/v1beta3/doc.go index 24081d457afc6..67a2e4b596c62 100644 --- a/pkg/apis/flowcontrol/v1beta3/doc.go +++ b/pkg/apis/flowcontrol/v1beta3/doc.go @@ -18,6 +18,8 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1beta3 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1beta3 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1beta3 // +groupName=flowcontrol.apiserver.k8s.io diff --git a/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go new file mode 100644 index 0000000000000..1d8f3fbc70481 --- /dev/null +++ b/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go @@ -0,0 +1,331 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta3 + +import ( + context "context" + fmt "fmt" + + flowcontrolv1beta3 "k8s.io/api/flowcontrol/v1beta3" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type PriorityLevelConfiguration + scheme.AddValidationFunc((*flowcontrolv1beta3.PriorityLevelConfiguration)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_PriorityLevelConfiguration(ctx, op, nil /* fldPath */, obj.(*flowcontrolv1beta3.PriorityLevelConfiguration), safe.Cast[*flowcontrolv1beta3.PriorityLevelConfiguration](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_LimitResponse validates an instance of LimitResponse according +// to declarative validation rules in the API schema. +func Validate_LimitResponse(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitResponse) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "queuing", func(obj *flowcontrolv1beta3.LimitResponse) *flowcontrolv1beta3.QueuingConfiguration { + return obj.Queuing + }, func(obj *flowcontrolv1beta3.LimitResponse) flowcontrolv1beta3.LimitResponseType { return obj.Type }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta3.QueuingConfiguration, flowcontrolv1beta3.LimitResponseType]{ + { + Value: "Queue", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Reject", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.QueuingConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1beta3.LimitResponse.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitResponseType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.LimitResponse) *flowcontrolv1beta3.LimitResponseType { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1beta3.LimitResponse.Queuing + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.QueuingConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("queuing"), obj.Queuing, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.LimitResponse) *flowcontrolv1beta3.QueuingConfiguration { + return oldObj.Queuing + }), oldObj != nil)...) + + return errs +} + +// Validate_LimitedPriorityLevelConfiguration validates an instance of LimitedPriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_LimitedPriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1beta3.LimitedPriorityLevelConfiguration.NominalConcurrencyShares has no validation + + // field flowcontrolv1beta3.LimitedPriorityLevelConfiguration.LimitResponse + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitResponse, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_LimitResponse(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limitResponse"), &obj.LimitResponse, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration) *flowcontrolv1beta3.LimitResponse { + return &oldObj.LimitResponse + }), oldObj != nil)...) + + // field flowcontrolv1beta3.LimitedPriorityLevelConfiguration.LendablePercent has no validation + // field flowcontrolv1beta3.LimitedPriorityLevelConfiguration.BorrowingLimitPercent has no validation + return errs +} + +// Validate_PriorityLevelConfiguration validates an instance of PriorityLevelConfiguration according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.PriorityLevelConfiguration) (errs field.ErrorList) { + // field flowcontrolv1beta3.PriorityLevelConfiguration.TypeMeta has no validation + // field flowcontrolv1beta3.PriorityLevelConfiguration.ObjectMeta has no validation + + // field flowcontrolv1beta3.PriorityLevelConfiguration.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.PriorityLevelConfigurationSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PriorityLevelConfigurationSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.PriorityLevelConfiguration) *flowcontrolv1beta3.PriorityLevelConfigurationSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field flowcontrolv1beta3.PriorityLevelConfiguration.Status has no validation + return errs +} + +// Validate_PriorityLevelConfigurationSpec validates an instance of PriorityLevelConfigurationSpec according +// to declarative validation rules in the API schema. +func Validate_PriorityLevelConfigurationSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "exempt", func(obj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) *flowcontrolv1beta3.ExemptPriorityLevelConfiguration { + return obj.Exempt + }, func(obj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) flowcontrolv1beta3.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta3.ExemptPriorityLevelConfiguration, flowcontrolv1beta3.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.ExemptPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "limited", func(obj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) *flowcontrolv1beta3.LimitedPriorityLevelConfiguration { + return obj.Limited + }, func(obj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) flowcontrolv1beta3.PriorityLevelEnablement { + return obj.Type + }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*flowcontrolv1beta3.LimitedPriorityLevelConfiguration, flowcontrolv1beta3.PriorityLevelEnablement]{ + { + Value: "Exempt", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "Limited", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field flowcontrolv1beta3.PriorityLevelConfigurationSpec.Type + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.PriorityLevelEnablement, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("type"), &obj.Type, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) *flowcontrolv1beta3.PriorityLevelEnablement { + return &oldObj.Type + }), oldObj != nil)...) + + // field flowcontrolv1beta3.PriorityLevelConfigurationSpec.Limited + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_LimitedPriorityLevelConfiguration(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("limited"), obj.Limited, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) *flowcontrolv1beta3.LimitedPriorityLevelConfiguration { + return oldObj.Limited + }), oldObj != nil)...) + + // field flowcontrolv1beta3.PriorityLevelConfigurationSpec.Exempt + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.ExemptPriorityLevelConfiguration, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("exempt"), obj.Exempt, safe.Field(oldObj, func(oldObj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) *flowcontrolv1beta3.ExemptPriorityLevelConfiguration { + return oldObj.Exempt + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/flowcontrol/validation/validation.go b/pkg/apis/flowcontrol/validation/validation.go index c82f3177abec4..510b5e4a17114 100644 --- a/pkg/apis/flowcontrol/validation/validation.go +++ b/pkg/apis/flowcontrol/validation/validation.go @@ -400,18 +400,18 @@ func ValidatePriorityLevelConfigurationSpec(spec *flowcontrol.PriorityLevelConfi switch spec.Type { case flowcontrol.PriorityLevelEnablementExempt: if spec.Limited != nil { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("limited"), "must be nil if the type is not Limited")) + allErrs = append(allErrs, field.Forbidden(fldPath.Child("limited"), "must be nil if the type is not Limited").MarkCoveredByDeclarative()) } if spec.Exempt != nil { allErrs = append(allErrs, ValidateExemptPriorityLevelConfiguration(spec.Exempt, fldPath.Child("exempt"))...) } case flowcontrol.PriorityLevelEnablementLimited: if spec.Exempt != nil { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("exempt"), "must be nil if the type is Limited")) + allErrs = append(allErrs, field.Forbidden(fldPath.Child("exempt"), "must be nil if the type is Limited").MarkCoveredByDeclarative()) } if spec.Limited == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("limited"), "must not be empty when type is Limited")) + allErrs = append(allErrs, field.Required(fldPath.Child("limited"), "must not be empty when type is Limited").MarkCoveredByDeclarative()) } else { allErrs = append(allErrs, ValidateLimitedPriorityLevelConfiguration(spec.Limited, requestGV, fldPath.Child("limited"), opts)...) } @@ -466,11 +466,11 @@ func ValidateLimitResponse(lr flowcontrol.LimitResponse, fldPath *field.Path) fi switch lr.Type { case flowcontrol.LimitResponseTypeReject: if lr.Queuing != nil { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("queuing"), "must be nil if limited.limitResponse.type is not Limited")) + allErrs = append(allErrs, field.Forbidden(fldPath.Child("queuing"), "must be nil if limited.limitResponse.type is not Limited").MarkCoveredByDeclarative()) } case flowcontrol.LimitResponseTypeQueue: if lr.Queuing == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("queuing"), "must not be empty if limited.limitResponse.type is Limited")) + allErrs = append(allErrs, field.Required(fldPath.Child("queuing"), "must not be empty if limited.limitResponse.type is Limited").MarkCoveredByDeclarative()) } else { allErrs = append(allErrs, ValidatePriorityLevelQueuingConfiguration(lr.Queuing, fldPath.Child("queuing"))...) } diff --git a/pkg/apis/flowcontrol/validation/validation_test.go b/pkg/apis/flowcontrol/validation/validation_test.go index 1cb93eaed9dc9..01a21ec01c00a 100644 --- a/pkg/apis/flowcontrol/validation/validation_test.go +++ b/pkg/apis/flowcontrol/validation/validation_test.go @@ -856,7 +856,7 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { priorityLevelConfiguration: exemptTypeRepurposed, expectedErrors: field.ErrorList{ field.Invalid(field.NewPath("spec").Child("type"), flowcontrol.PriorityLevelEnablementLimited, "must be 'Exempt' if and only if `name` is 'exempt'"), - field.Forbidden(field.NewPath("spec").Child("exempt"), "must be nil if the type is Limited"), + field.Forbidden(field.NewPath("spec").Child("exempt"), "must be nil if the type is Limited").MarkCoveredByDeclarative(), field.Invalid(field.NewPath("spec"), exemptTypeRepurposed.Spec, "spec of 'exempt' except the 'spec.exempt' field must equal the fixed value"), }, }, { @@ -869,7 +869,7 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { }, expectedErrors: field.ErrorList{ field.Invalid(field.NewPath("spec"), badExemptSpec3, "spec of 'exempt' except the 'spec.exempt' field must equal the fixed value"), - field.Forbidden(field.NewPath("spec").Child("limited"), "must be nil if the type is not Limited"), + field.Forbidden(field.NewPath("spec").Child("limited"), "must be nil if the type is not Limited").MarkCoveredByDeclarative(), }, }, { name: "admins are allowed to change the Exempt field of the 'exempt' pl", @@ -892,8 +892,8 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { }, }, expectedErrors: field.ErrorList{ - field.Forbidden(field.NewPath("spec").Child("exempt"), "must be nil if the type is Limited"), - field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited"), + field.Forbidden(field.NewPath("spec").Child("exempt"), "must be nil if the type is Limited").MarkCoveredByDeclarative(), + field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited").MarkCoveredByDeclarative(), }, }, { name: "limited requires more details", @@ -905,7 +905,7 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { Type: flowcontrol.PriorityLevelEnablementLimited, }, }, - expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited")}, + expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited").MarkCoveredByDeclarative()}, }, { name: "max-in-flight should work", priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ @@ -940,7 +940,7 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { QueueLengthLimit: 100, }}}}, }, - expectedErrors: field.ErrorList{field.Forbidden(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must be nil if limited.limitResponse.type is not Limited")}, + expectedErrors: field.ErrorList{field.Forbidden(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must be nil if limited.limitResponse.type is not Limited").MarkCoveredByDeclarative()}, }, { name: "wrong backstop spec should fail", priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ @@ -980,7 +980,7 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { Type: flowcontrol.LimitResponseTypeQueue, }}}, }, - expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must not be empty if limited.limitResponse.type is Limited")}, + expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must not be empty if limited.limitResponse.type is Limited").MarkCoveredByDeclarative()}, }, { name: "normal customized priority level should work", priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ diff --git a/pkg/apis/networking/util/helpers.go b/pkg/apis/networking/util/helpers.go new file mode 100644 index 0000000000000..1e2864f2e4c50 --- /dev/null +++ b/pkg/apis/networking/util/helpers.go @@ -0,0 +1,27 @@ +/* +Copyright 2025 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 util + +import ( + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// HasDefaultAnnotation returns true if the object metadata has the default annotation set. +func HasDefaultAnnotation(obj metav1.ObjectMeta) bool { + return obj.Annotations[networkingv1.AnnotationIsDefaultIngressClass] == "true" +} diff --git a/pkg/apis/networking/v1/doc.go b/pkg/apis/networking/v1/doc.go index 36d390fe889f4..b64058b4a5f44 100644 --- a/pkg/apis/networking/v1/doc.go +++ b/pkg/apis/networking/v1/doc.go @@ -20,5 +20,7 @@ limitations under the License. // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/networking/v1 // +groupName=networking.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/networking/v1 package v1 diff --git a/pkg/apis/networking/v1/zz_generated.validations.go b/pkg/apis/networking/v1/zz_generated.validations.go new file mode 100644 index 0000000000000..b7f88e4f5fe05 --- /dev/null +++ b/pkg/apis/networking/v1/zz_generated.validations.go @@ -0,0 +1,453 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + networkingv1 "k8s.io/api/networking/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type IPAddress + scheme.AddValidationFunc((*networkingv1.IPAddress)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_IPAddress(ctx, op, nil /* fldPath */, obj.(*networkingv1.IPAddress), safe.Cast[*networkingv1.IPAddress](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type IngressClass + scheme.AddValidationFunc((*networkingv1.IngressClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_IngressClass(ctx, op, nil /* fldPath */, obj.(*networkingv1.IngressClass), safe.Cast[*networkingv1.IngressClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type NetworkPolicy + scheme.AddValidationFunc((*networkingv1.NetworkPolicy)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_NetworkPolicy(ctx, op, nil /* fldPath */, obj.(*networkingv1.NetworkPolicy), safe.Cast[*networkingv1.NetworkPolicy](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_IPAddress validates an instance of IPAddress according +// to declarative validation rules in the API schema. +func Validate_IPAddress(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.IPAddress) (errs field.ErrorList) { + // field networkingv1.IPAddress.TypeMeta has no validation + // field networkingv1.IPAddress.ObjectMeta has no validation + + // field networkingv1.IPAddress.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1.IPAddressSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_IPAddressSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *networkingv1.IPAddress) *networkingv1.IPAddressSpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_IPAddressSpec validates an instance of IPAddressSpec according +// to declarative validation rules in the API schema. +func Validate_IPAddressSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.IPAddressSpec) (errs field.ErrorList) { + // field networkingv1.IPAddressSpec.ParentRef + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1.ParentReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ParentReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("parentRef"), obj.ParentRef, safe.Field(oldObj, func(oldObj *networkingv1.IPAddressSpec) *networkingv1.ParentReference { return oldObj.ParentRef }), oldObj != nil)...) + + return errs +} + +// Validate_IPBlock validates an instance of IPBlock according +// to declarative validation rules in the API schema. +func Validate_IPBlock(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.IPBlock) (errs field.ErrorList) { + // field networkingv1.IPBlock.CIDR + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("cidr"), &obj.CIDR, safe.Field(oldObj, func(oldObj *networkingv1.IPBlock) *string { return &oldObj.CIDR }), oldObj != nil)...) + + // field networkingv1.IPBlock.Except has no validation + return errs +} + +// Validate_IngressClass validates an instance of IngressClass according +// to declarative validation rules in the API schema. +func Validate_IngressClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.IngressClass) (errs field.ErrorList) { + // field networkingv1.IngressClass.TypeMeta has no validation + // field networkingv1.IngressClass.ObjectMeta has no validation + + // field networkingv1.IngressClass.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1.IngressClassSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_IngressClassSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *networkingv1.IngressClass) *networkingv1.IngressClassSpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_IngressClassParametersReference validates an instance of IngressClassParametersReference according +// to declarative validation rules in the API schema. +func Validate_IngressClassParametersReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.IngressClassParametersReference) (errs field.ErrorList) { + // field networkingv1.IngressClassParametersReference.APIGroup has no validation + + // field networkingv1.IngressClassParametersReference.Kind + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("kind"), &obj.Kind, safe.Field(oldObj, func(oldObj *networkingv1.IngressClassParametersReference) *string { return &oldObj.Kind }), oldObj != nil)...) + + // field networkingv1.IngressClassParametersReference.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *networkingv1.IngressClassParametersReference) *string { return &oldObj.Name }), oldObj != nil)...) + + // field networkingv1.IngressClassParametersReference.Scope has no validation + // field networkingv1.IngressClassParametersReference.Namespace has no validation + return errs +} + +// Validate_IngressClassSpec validates an instance of IngressClassSpec according +// to declarative validation rules in the API schema. +func Validate_IngressClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.IngressClassSpec) (errs field.ErrorList) { + // field networkingv1.IngressClassSpec.Controller has no validation + + // field networkingv1.IngressClassSpec.Parameters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1.IngressClassParametersReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IngressClassParametersReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("parameters"), obj.Parameters, safe.Field(oldObj, func(oldObj *networkingv1.IngressClassSpec) *networkingv1.IngressClassParametersReference { + return oldObj.Parameters + }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicy validates an instance of NetworkPolicy according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicy(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.NetworkPolicy) (errs field.ErrorList) { + // field networkingv1.NetworkPolicy.TypeMeta has no validation + // field networkingv1.NetworkPolicy.ObjectMeta has no validation + + // field networkingv1.NetworkPolicy.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1.NetworkPolicySpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_NetworkPolicySpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *networkingv1.NetworkPolicy) *networkingv1.NetworkPolicySpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicyEgressRule validates an instance of NetworkPolicyEgressRule according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicyEgressRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.NetworkPolicyEgressRule) (errs field.ErrorList) { + // field networkingv1.NetworkPolicyEgressRule.Ports has no validation + + // field networkingv1.NetworkPolicyEgressRule.To + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []networkingv1.NetworkPolicyPeer, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyPeer)...) + return + }(fldPath.Child("to"), obj.To, safe.Field(oldObj, func(oldObj *networkingv1.NetworkPolicyEgressRule) []networkingv1.NetworkPolicyPeer { return oldObj.To }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicyIngressRule validates an instance of NetworkPolicyIngressRule according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicyIngressRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.NetworkPolicyIngressRule) (errs field.ErrorList) { + // field networkingv1.NetworkPolicyIngressRule.Ports has no validation + + // field networkingv1.NetworkPolicyIngressRule.From + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []networkingv1.NetworkPolicyPeer, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyPeer)...) + return + }(fldPath.Child("from"), obj.From, safe.Field(oldObj, func(oldObj *networkingv1.NetworkPolicyIngressRule) []networkingv1.NetworkPolicyPeer { + return oldObj.From + }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicyPeer validates an instance of NetworkPolicyPeer according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicyPeer(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.NetworkPolicyPeer) (errs field.ErrorList) { + // field networkingv1.NetworkPolicyPeer.PodSelector has no validation + // field networkingv1.NetworkPolicyPeer.NamespaceSelector has no validation + + // field networkingv1.NetworkPolicyPeer.IPBlock + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1.IPBlock, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IPBlock(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("ipBlock"), obj.IPBlock, safe.Field(oldObj, func(oldObj *networkingv1.NetworkPolicyPeer) *networkingv1.IPBlock { return oldObj.IPBlock }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicySpec validates an instance of NetworkPolicySpec according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicySpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.NetworkPolicySpec) (errs field.ErrorList) { + // field networkingv1.NetworkPolicySpec.PodSelector has no validation + + // field networkingv1.NetworkPolicySpec.Ingress + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []networkingv1.NetworkPolicyIngressRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyIngressRule)...) + return + }(fldPath.Child("ingress"), obj.Ingress, safe.Field(oldObj, func(oldObj *networkingv1.NetworkPolicySpec) []networkingv1.NetworkPolicyIngressRule { + return oldObj.Ingress + }), oldObj != nil)...) + + // field networkingv1.NetworkPolicySpec.Egress + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []networkingv1.NetworkPolicyEgressRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyEgressRule)...) + return + }(fldPath.Child("egress"), obj.Egress, safe.Field(oldObj, func(oldObj *networkingv1.NetworkPolicySpec) []networkingv1.NetworkPolicyEgressRule { + return oldObj.Egress + }), oldObj != nil)...) + + // field networkingv1.NetworkPolicySpec.PolicyTypes has no validation + return errs +} + +// Validate_ParentReference validates an instance of ParentReference according +// to declarative validation rules in the API schema. +func Validate_ParentReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1.ParentReference) (errs field.ErrorList) { + // field networkingv1.ParentReference.Group has no validation + + // field networkingv1.ParentReference.Resource + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("resource"), &obj.Resource, safe.Field(oldObj, func(oldObj *networkingv1.ParentReference) *string { return &oldObj.Resource }), oldObj != nil)...) + + // field networkingv1.ParentReference.Namespace has no validation + + // field networkingv1.ParentReference.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *networkingv1.ParentReference) *string { return &oldObj.Name }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/networking/v1beta1/doc.go b/pkg/apis/networking/v1beta1/doc.go index 914234d39e222..0cc2573f562b3 100644 --- a/pkg/apis/networking/v1beta1/doc.go +++ b/pkg/apis/networking/v1beta1/doc.go @@ -20,5 +20,7 @@ limitations under the License. // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/networking/v1beta1 // +groupName=networking.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/networking/v1beta1 package v1beta1 diff --git a/pkg/apis/networking/v1beta1/zz_generated.validations.go b/pkg/apis/networking/v1beta1/zz_generated.validations.go new file mode 100644 index 0000000000000..7703f8bf6f8e2 --- /dev/null +++ b/pkg/apis/networking/v1beta1/zz_generated.validations.go @@ -0,0 +1,260 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + networkingv1beta1 "k8s.io/api/networking/v1beta1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type IPAddress + scheme.AddValidationFunc((*networkingv1beta1.IPAddress)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_IPAddress(ctx, op, nil /* fldPath */, obj.(*networkingv1beta1.IPAddress), safe.Cast[*networkingv1beta1.IPAddress](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type IngressClass + scheme.AddValidationFunc((*networkingv1beta1.IngressClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_IngressClass(ctx, op, nil /* fldPath */, obj.(*networkingv1beta1.IngressClass), safe.Cast[*networkingv1beta1.IngressClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_IPAddress validates an instance of IPAddress according +// to declarative validation rules in the API schema. +func Validate_IPAddress(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1beta1.IPAddress) (errs field.ErrorList) { + // field networkingv1beta1.IPAddress.TypeMeta has no validation + // field networkingv1beta1.IPAddress.ObjectMeta has no validation + + // field networkingv1beta1.IPAddress.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1beta1.IPAddressSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_IPAddressSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *networkingv1beta1.IPAddress) *networkingv1beta1.IPAddressSpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_IPAddressSpec validates an instance of IPAddressSpec according +// to declarative validation rules in the API schema. +func Validate_IPAddressSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1beta1.IPAddressSpec) (errs field.ErrorList) { + // field networkingv1beta1.IPAddressSpec.ParentRef + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1beta1.ParentReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ParentReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("parentRef"), obj.ParentRef, safe.Field(oldObj, func(oldObj *networkingv1beta1.IPAddressSpec) *networkingv1beta1.ParentReference { + return oldObj.ParentRef + }), oldObj != nil)...) + + return errs +} + +// Validate_IngressClass validates an instance of IngressClass according +// to declarative validation rules in the API schema. +func Validate_IngressClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1beta1.IngressClass) (errs field.ErrorList) { + // field networkingv1beta1.IngressClass.TypeMeta has no validation + // field networkingv1beta1.IngressClass.ObjectMeta has no validation + + // field networkingv1beta1.IngressClass.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1beta1.IngressClassSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_IngressClassSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *networkingv1beta1.IngressClass) *networkingv1beta1.IngressClassSpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_IngressClassParametersReference validates an instance of IngressClassParametersReference according +// to declarative validation rules in the API schema. +func Validate_IngressClassParametersReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1beta1.IngressClassParametersReference) (errs field.ErrorList) { + // field networkingv1beta1.IngressClassParametersReference.APIGroup has no validation + + // field networkingv1beta1.IngressClassParametersReference.Kind + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("kind"), &obj.Kind, safe.Field(oldObj, func(oldObj *networkingv1beta1.IngressClassParametersReference) *string { return &oldObj.Kind }), oldObj != nil)...) + + // field networkingv1beta1.IngressClassParametersReference.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *networkingv1beta1.IngressClassParametersReference) *string { return &oldObj.Name }), oldObj != nil)...) + + // field networkingv1beta1.IngressClassParametersReference.Scope has no validation + // field networkingv1beta1.IngressClassParametersReference.Namespace has no validation + return errs +} + +// Validate_IngressClassSpec validates an instance of IngressClassSpec according +// to declarative validation rules in the API schema. +func Validate_IngressClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1beta1.IngressClassSpec) (errs field.ErrorList) { + // field networkingv1beta1.IngressClassSpec.Controller has no validation + + // field networkingv1beta1.IngressClassSpec.Parameters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *networkingv1beta1.IngressClassParametersReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IngressClassParametersReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("parameters"), obj.Parameters, safe.Field(oldObj, func(oldObj *networkingv1beta1.IngressClassSpec) *networkingv1beta1.IngressClassParametersReference { + return oldObj.Parameters + }), oldObj != nil)...) + + return errs +} + +// Validate_ParentReference validates an instance of ParentReference according +// to declarative validation rules in the API schema. +func Validate_ParentReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *networkingv1beta1.ParentReference) (errs field.ErrorList) { + // field networkingv1beta1.ParentReference.Group has no validation + + // field networkingv1beta1.ParentReference.Resource + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("resource"), &obj.Resource, safe.Field(oldObj, func(oldObj *networkingv1beta1.ParentReference) *string { return &oldObj.Resource }), oldObj != nil)...) + + // field networkingv1beta1.ParentReference.Namespace has no validation + + // field networkingv1beta1.ParentReference.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *networkingv1beta1.ParentReference) *string { return &oldObj.Name }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/networking/validation/validation.go b/pkg/apis/networking/validation/validation.go index 31e5b29a2f59b..0332e53cbd524 100644 --- a/pkg/apis/networking/validation/validation.go +++ b/pkg/apis/networking/validation/validation.go @@ -20,8 +20,8 @@ import ( "fmt" "strings" + "k8s.io/apimachinery/pkg/api/validate/content" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" - pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" @@ -246,7 +246,7 @@ func ValidateNetworkPolicyUpdate(update, old *networking.NetworkPolicy, opts Net func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path, opts NetworkPolicyValidationOptions) field.ErrorList { allErrs := field.ErrorList{} if ipb.CIDR == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), "")) + allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), "").MarkCoveredByDeclarative()) return allErrs } allErrs = append(allErrs, apivalidation.IsValidCIDRForLegacyField(fldPath.Child("cidr"), ipb.CIDR, opts.AllowCIDRsEvenIfInvalid)...) @@ -605,7 +605,7 @@ func validateIngressTypedLocalObjectReference(params *api.TypedLocalObjectRefere if params.Kind == "" { allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(params.Kind) { + for _, msg := range content.IsPathSegmentName(params.Kind) { allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), params.Kind, msg)) } } @@ -613,7 +613,7 @@ func validateIngressTypedLocalObjectReference(params *api.TypedLocalObjectRefere if params.Name == "" { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) { + for _, msg := range content.IsPathSegmentName(params.Name) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) } } @@ -632,11 +632,27 @@ func validateIngressClassParametersReference(params *networking.IngressClassPara return allErrs } - allErrs = append(allErrs, validateIngressTypedLocalObjectReference(&api.TypedLocalObjectReference{ - APIGroup: params.APIGroup, - Kind: params.Kind, - Name: params.Name, - }, fldPath)...) + if params.APIGroup != nil { + for _, msg := range validation.IsDNS1123Subdomain(*params.APIGroup) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), *params.APIGroup, msg)) + } + } + + if params.Kind == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "").MarkCoveredByDeclarative()) + } else { + for _, msg := range content.IsPathSegmentName(params.Kind) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), params.Kind, msg)) + } + } + + if params.Name == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "").MarkCoveredByDeclarative()) + } else { + for _, msg := range content.IsPathSegmentName(params.Name) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) + } + } if params.Scope == nil { allErrs = append(allErrs, field.Required(fldPath.Child("scope"), "")) @@ -769,7 +785,7 @@ func validateIPAddressParentReference(params *networking.ParentReference, fldPat allErrs := field.ErrorList{} if params == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("parentRef"), "")) + allErrs = append(allErrs, field.Required(fldPath.Child("parentRef"), "").MarkCoveredByDeclarative()) return allErrs } @@ -783,25 +799,25 @@ func validateIPAddressParentReference(params *networking.ParentReference, fldPat // resource is required if params.Resource == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "")) + allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "").MarkCoveredByDeclarative()) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(params.Resource) { + for _, msg := range content.IsPathSegmentName(params.Resource) { allErrs = append(allErrs, field.Invalid(fldPath.Child("resource"), params.Resource, msg)) } } // name is required if params.Name == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "").MarkCoveredByDeclarative()) } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) { + for _, msg := range content.IsPathSegmentName(params.Name) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) } } // namespace is optional if params.Namespace != "" { - for _, msg := range pathvalidation.IsValidPathSegmentName(params.Namespace) { + for _, msg := range content.IsPathSegmentName(params.Namespace) { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), params.Namespace, msg)) } } @@ -812,7 +828,7 @@ func validateIPAddressParentReference(params *networking.ParentReference, fldPat func ValidateIPAddressUpdate(update, old *networking.IPAddress) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.ParentRef, old.Spec.ParentRef, field.NewPath("spec").Child("parentRef"))...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.ParentRef, old.Spec.ParentRef, field.NewPath("spec").Child("parentRef")).MarkCoveredByDeclarative().WithOrigin("immutable")...) return allErrs } diff --git a/pkg/apis/node/v1/doc.go b/pkg/apis/node/v1/doc.go index 6ce4ae903b1b7..c0829b62289a3 100644 --- a/pkg/apis/node/v1/doc.go +++ b/pkg/apis/node/v1/doc.go @@ -19,4 +19,7 @@ limitations under the License. // +groupName=node.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/node/v1 + package v1 diff --git a/pkg/apis/node/v1/zz_generated.validations.go b/pkg/apis/node/v1/zz_generated.validations.go new file mode 100644 index 0000000000000..491517115b7ff --- /dev/null +++ b/pkg/apis/node/v1/zz_generated.validations.go @@ -0,0 +1,85 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + nodev1 "k8s.io/api/node/v1" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type RuntimeClass + scheme.AddValidationFunc((*nodev1.RuntimeClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_RuntimeClass(ctx, op, nil /* fldPath */, obj.(*nodev1.RuntimeClass), safe.Cast[*nodev1.RuntimeClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_RuntimeClass validates an instance of RuntimeClass according +// to declarative validation rules in the API schema. +func Validate_RuntimeClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1.RuntimeClass) (errs field.ErrorList) { + // field nodev1.RuntimeClass.TypeMeta has no validation + // field nodev1.RuntimeClass.ObjectMeta has no validation + + // field nodev1.RuntimeClass.Handler + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return + }(fldPath.Child("handler"), &obj.Handler, safe.Field(oldObj, func(oldObj *nodev1.RuntimeClass) *string { return &oldObj.Handler }), oldObj != nil)...) + + // field nodev1.RuntimeClass.Overhead has no validation + // field nodev1.RuntimeClass.Scheduling has no validation + return errs +} diff --git a/pkg/apis/node/v1alpha1/doc.go b/pkg/apis/node/v1alpha1/doc.go index 5bc302941a2d1..d29e06fff2ebc 100644 --- a/pkg/apis/node/v1alpha1/doc.go +++ b/pkg/apis/node/v1alpha1/doc.go @@ -19,4 +19,7 @@ limitations under the License. // +groupName=node.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/node/v1alpha1 + package v1alpha1 diff --git a/pkg/apis/node/v1alpha1/zz_generated.validations.go b/pkg/apis/node/v1alpha1/zz_generated.validations.go new file mode 100644 index 0000000000000..579234c688c91 --- /dev/null +++ b/pkg/apis/node/v1alpha1/zz_generated.validations.go @@ -0,0 +1,104 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + fmt "fmt" + + nodev1alpha1 "k8s.io/api/node/v1alpha1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type RuntimeClass + scheme.AddValidationFunc((*nodev1alpha1.RuntimeClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_RuntimeClass(ctx, op, nil /* fldPath */, obj.(*nodev1alpha1.RuntimeClass), safe.Cast[*nodev1alpha1.RuntimeClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_RuntimeClass validates an instance of RuntimeClass according +// to declarative validation rules in the API schema. +func Validate_RuntimeClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClass) (errs field.ErrorList) { + // field nodev1alpha1.RuntimeClass.TypeMeta has no validation + // field nodev1alpha1.RuntimeClass.ObjectMeta has no validation + + // field nodev1alpha1.RuntimeClass.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClassSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_RuntimeClassSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *nodev1alpha1.RuntimeClass) *nodev1alpha1.RuntimeClassSpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_RuntimeClassSpec validates an instance of RuntimeClassSpec according +// to declarative validation rules in the API schema. +func Validate_RuntimeClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClassSpec) (errs field.ErrorList) { + // field nodev1alpha1.RuntimeClassSpec.RuntimeHandler + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return + }(fldPath.Child("runtimeHandler"), &obj.RuntimeHandler, safe.Field(oldObj, func(oldObj *nodev1alpha1.RuntimeClassSpec) *string { return &oldObj.RuntimeHandler }), oldObj != nil)...) + + // field nodev1alpha1.RuntimeClassSpec.Overhead has no validation + // field nodev1alpha1.RuntimeClassSpec.Scheduling has no validation + return errs +} diff --git a/pkg/apis/node/v1beta1/doc.go b/pkg/apis/node/v1beta1/doc.go index 7f6a5923acfa1..7b5f4fc14c90c 100644 --- a/pkg/apis/node/v1beta1/doc.go +++ b/pkg/apis/node/v1beta1/doc.go @@ -19,4 +19,7 @@ limitations under the License. // +groupName=node.k8s.io +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/node/v1beta1 + package v1beta1 diff --git a/pkg/apis/node/v1beta1/zz_generated.validations.go b/pkg/apis/node/v1beta1/zz_generated.validations.go new file mode 100644 index 0000000000000..45bc38fc868f2 --- /dev/null +++ b/pkg/apis/node/v1beta1/zz_generated.validations.go @@ -0,0 +1,85 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + nodev1beta1 "k8s.io/api/node/v1beta1" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type RuntimeClass + scheme.AddValidationFunc((*nodev1beta1.RuntimeClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_RuntimeClass(ctx, op, nil /* fldPath */, obj.(*nodev1beta1.RuntimeClass), safe.Cast[*nodev1beta1.RuntimeClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_RuntimeClass validates an instance of RuntimeClass according +// to declarative validation rules in the API schema. +func Validate_RuntimeClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1beta1.RuntimeClass) (errs field.ErrorList) { + // field nodev1beta1.RuntimeClass.TypeMeta has no validation + // field nodev1beta1.RuntimeClass.ObjectMeta has no validation + + // field nodev1beta1.RuntimeClass.Handler + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return + }(fldPath.Child("handler"), &obj.Handler, safe.Field(oldObj, func(oldObj *nodev1beta1.RuntimeClass) *string { return &oldObj.Handler }), oldObj != nil)...) + + // field nodev1beta1.RuntimeClass.Overhead has no validation + // field nodev1beta1.RuntimeClass.Scheduling has no validation + return errs +} diff --git a/pkg/apis/node/validation/validation.go b/pkg/apis/node/validation/validation.go index 58b80b55f1cee..b2b682d16d66f 100644 --- a/pkg/apis/node/validation/validation.go +++ b/pkg/apis/node/validation/validation.go @@ -23,14 +23,27 @@ import ( "k8s.io/kubernetes/pkg/apis/core" corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/node" + "regexp" ) +// Define normalization rules to handle field name differences across API versions +// v1alpha1 uses "spec.runtimeHandler" while v1/v1beta1 use "handler" +var NodeNormalizationRules = []field.NormalizationRule{ + { + Regexp: regexp.MustCompile(`^spec\.runtimeHandler(.*)$`), + Replacement: "handler$1", + }, +} + // ValidateRuntimeClass validates the RuntimeClass func ValidateRuntimeClass(rc *node.RuntimeClass) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&rc.ObjectMeta, false, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata")) - - for _, msg := range apivalidation.NameIsDNSLabel(rc.Handler, false) { - allErrs = append(allErrs, field.Invalid(field.NewPath("handler"), rc.Handler, msg)) + if rc.Handler == "" { + allErrs = append(allErrs, field.Required(field.NewPath("handler"), "").MarkCoveredByDeclarative()) + } else { + for _, msg := range apivalidation.NameIsDNSLabel(rc.Handler, false) { + allErrs = append(allErrs, field.Invalid(field.NewPath("handler"), rc.Handler, msg).MarkCoveredByDeclarative().WithOrigin("format=k8s-short-name")) + } } if rc.Overhead != nil { @@ -47,7 +60,7 @@ func ValidateRuntimeClass(rc *node.RuntimeClass) field.ErrorList { func ValidateRuntimeClassUpdate(new, old *node.RuntimeClass) field.ErrorList { allErrs := apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(new.Handler, old.Handler, field.NewPath("handler"))...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(new.Handler, old.Handler, field.NewPath("handler")).MarkCoveredByDeclarative().WithOrigin("immutable")...) return allErrs } diff --git a/pkg/apis/rbac/v1/helpers.go b/pkg/apis/rbac/v1/helpers.go index 669e48c8a6f20..d30e33a65a5f3 100644 --- a/pkg/apis/rbac/v1/helpers.go +++ b/pkg/apis/rbac/v1/helpers.go @@ -24,6 +24,7 @@ import ( "sort" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ) // +k8s:deepcopy-gen=false @@ -37,30 +38,36 @@ type PolicyRuleBuilder struct { func NewRule(verbs ...string) *PolicyRuleBuilder { return &PolicyRuleBuilder{ - PolicyRule: rbacv1.PolicyRule{Verbs: verbs}, + PolicyRule: rbacv1.PolicyRule{Verbs: sets.NewString(verbs...).List()}, } } func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { - r.PolicyRule.APIGroups = append(r.PolicyRule.APIGroups, groups...) + r.PolicyRule.APIGroups = combine(r.PolicyRule.APIGroups, groups) return r } func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { - r.PolicyRule.Resources = append(r.PolicyRule.Resources, resources...) + r.PolicyRule.Resources = combine(r.PolicyRule.Resources, resources) return r } func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { - r.PolicyRule.ResourceNames = append(r.PolicyRule.ResourceNames, names...) + r.PolicyRule.ResourceNames = combine(r.PolicyRule.ResourceNames, names) return r } func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { - r.PolicyRule.NonResourceURLs = append(r.PolicyRule.NonResourceURLs, urls...) + r.PolicyRule.NonResourceURLs = combine(r.PolicyRule.NonResourceURLs, urls) return r } +func combine(s1, s2 []string) []string { + s := sets.NewString(s1...) + s.Insert(s2...) + return s.List() +} + func (r *PolicyRuleBuilder) RuleOrDie() rbacv1.PolicyRule { ret, err := r.Rule() if err != nil { diff --git a/pkg/apis/rbac/v1/zz_generated.validations.go b/pkg/apis/rbac/v1/zz_generated.validations.go index 0e53d6e0b7e57..9cb1971123de9 100644 --- a/pkg/apis/rbac/v1/zz_generated.validations.go +++ b/pkg/apis/rbac/v1/zz_generated.validations.go @@ -39,6 +39,14 @@ func init() { localSchemeBuilder.Register(RegisterValidations) } // RegisterValidations adds validation functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterValidations(scheme *runtime.Scheme) error { + // type ClusterRole + scheme.AddValidationFunc((*rbacv1.ClusterRole)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ClusterRole(ctx, op, nil /* fldPath */, obj.(*rbacv1.ClusterRole), safe.Cast[*rbacv1.ClusterRole](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type ClusterRoleBinding scheme.AddValidationFunc((*rbacv1.ClusterRoleBinding)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -47,11 +55,11 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ClusterRoleBindingList - scheme.AddValidationFunc((*rbacv1.ClusterRoleBindingList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + // type Role + scheme.AddValidationFunc((*rbacv1.Role)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { case "/": - return Validate_ClusterRoleBindingList(ctx, op, nil /* fldPath */, obj.(*rbacv1.ClusterRoleBindingList), safe.Cast[*rbacv1.ClusterRoleBindingList](oldObj)) + return Validate_Role(ctx, op, nil /* fldPath */, obj.(*rbacv1.Role), safe.Cast[*rbacv1.Role](oldObj)) } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) @@ -63,17 +71,39 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type RoleBindingList - scheme.AddValidationFunc((*rbacv1.RoleBindingList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_RoleBindingList(ctx, op, nil /* fldPath */, obj.(*rbacv1.RoleBindingList), safe.Cast[*rbacv1.RoleBindingList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } +// Validate_ClusterRole validates an instance of ClusterRole according +// to declarative validation rules in the API schema. +func Validate_ClusterRole(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.ClusterRole) (errs field.ErrorList) { + // field rbacv1.ClusterRole.TypeMeta has no validation + // field rbacv1.ClusterRole.ObjectMeta has no validation + + // field rbacv1.ClusterRole.Rules + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []rbacv1.PolicyRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...) + return + }(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1.ClusterRole) []rbacv1.PolicyRule { return oldObj.Rules }), oldObj != nil)...) + + // field rbacv1.ClusterRole.AggregationRule has no validation + return errs +} + // Validate_ClusterRoleBinding validates an instance of ClusterRoleBinding according // to declarative validation rules in the API schema. func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.ClusterRoleBinding) (errs field.ErrorList) { @@ -87,6 +117,14 @@ func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fl if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Subject)...) return @@ -107,23 +145,60 @@ func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fl return errs } -// Validate_ClusterRoleBindingList validates an instance of ClusterRoleBindingList according +// Validate_PolicyRule validates an instance of PolicyRule according // to declarative validation rules in the API schema. -func Validate_ClusterRoleBindingList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.ClusterRoleBindingList) (errs field.ErrorList) { - // field rbacv1.ClusterRoleBindingList.TypeMeta has no validation - // field rbacv1.ClusterRoleBindingList.ListMeta has no validation +func Validate_PolicyRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.PolicyRule) (errs field.ErrorList) { + // field rbacv1.PolicyRule.Verbs + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("verbs"), obj.Verbs, safe.Field(oldObj, func(oldObj *rbacv1.PolicyRule) []string { return oldObj.Verbs }), oldObj != nil)...) - // field rbacv1.ClusterRoleBindingList.Items + // field rbacv1.PolicyRule.APIGroups has no validation + // field rbacv1.PolicyRule.Resources has no validation + // field rbacv1.PolicyRule.ResourceNames has no validation + // field rbacv1.PolicyRule.NonResourceURLs has no validation + return errs +} + +// Validate_Role validates an instance of Role according +// to declarative validation rules in the API schema. +func Validate_Role(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.Role) (errs field.ErrorList) { + // field rbacv1.Role.TypeMeta has no validation + // field rbacv1.Role.ObjectMeta has no validation + + // field rbacv1.Role.Rules errs = append(errs, - func(fldPath *field.Path, obj, oldObj []rbacv1.ClusterRoleBinding, oldValueCorrelated bool) (errs field.ErrorList) { + func(fldPath *field.Path, obj, oldObj []rbacv1.PolicyRule, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ClusterRoleBinding)...) + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...) return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1.ClusterRoleBindingList) []rbacv1.ClusterRoleBinding { return oldObj.Items }), oldObj != nil)...) + }(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1.Role) []rbacv1.PolicyRule { return oldObj.Rules }), oldObj != nil)...) return errs } @@ -141,6 +216,14 @@ func Validate_RoleBinding(ctx context.Context, op operation.Operation, fldPath * if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Subject)...) return @@ -161,27 +244,6 @@ func Validate_RoleBinding(ctx context.Context, op operation.Operation, fldPath * return errs } -// Validate_RoleBindingList validates an instance of RoleBindingList according -// to declarative validation rules in the API schema. -func Validate_RoleBindingList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.RoleBindingList) (errs field.ErrorList) { - // field rbacv1.RoleBindingList.TypeMeta has no validation - // field rbacv1.RoleBindingList.ListMeta has no validation - - // field rbacv1.RoleBindingList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []rbacv1.RoleBinding, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_RoleBinding)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1.RoleBindingList) []rbacv1.RoleBinding { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_RoleRef validates an instance of RoleRef according // to declarative validation rules in the API schema. func Validate_RoleRef(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.RoleRef) (errs field.ErrorList) { @@ -197,7 +259,7 @@ func Validate_RoleRef(ctx context.Context, op operation.Operation, fldPath *fiel } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -225,7 +287,7 @@ func Validate_Subject(ctx context.Context, op operation.Operation, fldPath *fiel } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } diff --git a/pkg/apis/rbac/v1alpha1/zz_generated.validations.go b/pkg/apis/rbac/v1alpha1/zz_generated.validations.go index 0f517283ac189..d9d49b980bbf2 100644 --- a/pkg/apis/rbac/v1alpha1/zz_generated.validations.go +++ b/pkg/apis/rbac/v1alpha1/zz_generated.validations.go @@ -39,6 +39,14 @@ func init() { localSchemeBuilder.Register(RegisterValidations) } // RegisterValidations adds validation functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterValidations(scheme *runtime.Scheme) error { + // type ClusterRole + scheme.AddValidationFunc((*rbacv1alpha1.ClusterRole)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ClusterRole(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.ClusterRole), safe.Cast[*rbacv1alpha1.ClusterRole](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type ClusterRoleBinding scheme.AddValidationFunc((*rbacv1alpha1.ClusterRoleBinding)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -47,11 +55,11 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ClusterRoleBindingList - scheme.AddValidationFunc((*rbacv1alpha1.ClusterRoleBindingList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + // type Role + scheme.AddValidationFunc((*rbacv1alpha1.Role)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { case "/": - return Validate_ClusterRoleBindingList(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.ClusterRoleBindingList), safe.Cast[*rbacv1alpha1.ClusterRoleBindingList](oldObj)) + return Validate_Role(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.Role), safe.Cast[*rbacv1alpha1.Role](oldObj)) } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) @@ -63,17 +71,39 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type RoleBindingList - scheme.AddValidationFunc((*rbacv1alpha1.RoleBindingList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_RoleBindingList(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.RoleBindingList), safe.Cast[*rbacv1alpha1.RoleBindingList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } +// Validate_ClusterRole validates an instance of ClusterRole according +// to declarative validation rules in the API schema. +func Validate_ClusterRole(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.ClusterRole) (errs field.ErrorList) { + // field rbacv1alpha1.ClusterRole.TypeMeta has no validation + // field rbacv1alpha1.ClusterRole.ObjectMeta has no validation + + // field rbacv1alpha1.ClusterRole.Rules + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.PolicyRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...) + return + }(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1alpha1.ClusterRole) []rbacv1alpha1.PolicyRule { return oldObj.Rules }), oldObj != nil)...) + + // field rbacv1alpha1.ClusterRole.AggregationRule has no validation + return errs +} + // Validate_ClusterRoleBinding validates an instance of ClusterRoleBinding according // to declarative validation rules in the API schema. func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.ClusterRoleBinding) (errs field.ErrorList) { @@ -87,6 +117,14 @@ func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fl if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Subject)...) return @@ -107,25 +145,60 @@ func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fl return errs } -// Validate_ClusterRoleBindingList validates an instance of ClusterRoleBindingList according +// Validate_PolicyRule validates an instance of PolicyRule according // to declarative validation rules in the API schema. -func Validate_ClusterRoleBindingList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.ClusterRoleBindingList) (errs field.ErrorList) { - // field rbacv1alpha1.ClusterRoleBindingList.TypeMeta has no validation - // field rbacv1alpha1.ClusterRoleBindingList.ListMeta has no validation +func Validate_PolicyRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.PolicyRule) (errs field.ErrorList) { + // field rbacv1alpha1.PolicyRule.Verbs + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("verbs"), obj.Verbs, safe.Field(oldObj, func(oldObj *rbacv1alpha1.PolicyRule) []string { return oldObj.Verbs }), oldObj != nil)...) - // field rbacv1alpha1.ClusterRoleBindingList.Items + // field rbacv1alpha1.PolicyRule.APIGroups has no validation + // field rbacv1alpha1.PolicyRule.Resources has no validation + // field rbacv1alpha1.PolicyRule.ResourceNames has no validation + // field rbacv1alpha1.PolicyRule.NonResourceURLs has no validation + return errs +} + +// Validate_Role validates an instance of Role according +// to declarative validation rules in the API schema. +func Validate_Role(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.Role) (errs field.ErrorList) { + // field rbacv1alpha1.Role.TypeMeta has no validation + // field rbacv1alpha1.Role.ObjectMeta has no validation + + // field rbacv1alpha1.Role.Rules errs = append(errs, - func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.ClusterRoleBinding, oldValueCorrelated bool) (errs field.ErrorList) { + func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.PolicyRule, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ClusterRoleBinding)...) + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...) return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1alpha1.ClusterRoleBindingList) []rbacv1alpha1.ClusterRoleBinding { - return oldObj.Items - }), oldObj != nil)...) + }(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1alpha1.Role) []rbacv1alpha1.PolicyRule { return oldObj.Rules }), oldObj != nil)...) return errs } @@ -143,6 +216,14 @@ func Validate_RoleBinding(ctx context.Context, op operation.Operation, fldPath * if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Subject)...) return @@ -163,27 +244,6 @@ func Validate_RoleBinding(ctx context.Context, op operation.Operation, fldPath * return errs } -// Validate_RoleBindingList validates an instance of RoleBindingList according -// to declarative validation rules in the API schema. -func Validate_RoleBindingList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.RoleBindingList) (errs field.ErrorList) { - // field rbacv1alpha1.RoleBindingList.TypeMeta has no validation - // field rbacv1alpha1.RoleBindingList.ListMeta has no validation - - // field rbacv1alpha1.RoleBindingList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.RoleBinding, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_RoleBinding)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1alpha1.RoleBindingList) []rbacv1alpha1.RoleBinding { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_RoleRef validates an instance of RoleRef according // to declarative validation rules in the API schema. func Validate_RoleRef(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.RoleRef) (errs field.ErrorList) { @@ -199,7 +259,7 @@ func Validate_RoleRef(ctx context.Context, op operation.Operation, fldPath *fiel } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -227,7 +287,7 @@ func Validate_Subject(ctx context.Context, op operation.Operation, fldPath *fiel } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } diff --git a/pkg/apis/rbac/v1beta1/zz_generated.validations.go b/pkg/apis/rbac/v1beta1/zz_generated.validations.go index 82e44b20bc192..aad986260b862 100644 --- a/pkg/apis/rbac/v1beta1/zz_generated.validations.go +++ b/pkg/apis/rbac/v1beta1/zz_generated.validations.go @@ -39,6 +39,14 @@ func init() { localSchemeBuilder.Register(RegisterValidations) } // RegisterValidations adds validation functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterValidations(scheme *runtime.Scheme) error { + // type ClusterRole + scheme.AddValidationFunc((*rbacv1beta1.ClusterRole)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ClusterRole(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.ClusterRole), safe.Cast[*rbacv1beta1.ClusterRole](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type ClusterRoleBinding scheme.AddValidationFunc((*rbacv1beta1.ClusterRoleBinding)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -47,11 +55,11 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ClusterRoleBindingList - scheme.AddValidationFunc((*rbacv1beta1.ClusterRoleBindingList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + // type Role + scheme.AddValidationFunc((*rbacv1beta1.Role)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { case "/": - return Validate_ClusterRoleBindingList(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.ClusterRoleBindingList), safe.Cast[*rbacv1beta1.ClusterRoleBindingList](oldObj)) + return Validate_Role(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.Role), safe.Cast[*rbacv1beta1.Role](oldObj)) } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) @@ -63,17 +71,39 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type RoleBindingList - scheme.AddValidationFunc((*rbacv1beta1.RoleBindingList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_RoleBindingList(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.RoleBindingList), safe.Cast[*rbacv1beta1.RoleBindingList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } +// Validate_ClusterRole validates an instance of ClusterRole according +// to declarative validation rules in the API schema. +func Validate_ClusterRole(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.ClusterRole) (errs field.ErrorList) { + // field rbacv1beta1.ClusterRole.TypeMeta has no validation + // field rbacv1beta1.ClusterRole.ObjectMeta has no validation + + // field rbacv1beta1.ClusterRole.Rules + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []rbacv1beta1.PolicyRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...) + return + }(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1beta1.ClusterRole) []rbacv1beta1.PolicyRule { return oldObj.Rules }), oldObj != nil)...) + + // field rbacv1beta1.ClusterRole.AggregationRule has no validation + return errs +} + // Validate_ClusterRoleBinding validates an instance of ClusterRoleBinding according // to declarative validation rules in the API schema. func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.ClusterRoleBinding) (errs field.ErrorList) { @@ -87,6 +117,14 @@ func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fl if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Subject)...) return @@ -107,23 +145,60 @@ func Validate_ClusterRoleBinding(ctx context.Context, op operation.Operation, fl return errs } -// Validate_ClusterRoleBindingList validates an instance of ClusterRoleBindingList according +// Validate_PolicyRule validates an instance of PolicyRule according // to declarative validation rules in the API schema. -func Validate_ClusterRoleBindingList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.ClusterRoleBindingList) (errs field.ErrorList) { - // field rbacv1beta1.ClusterRoleBindingList.TypeMeta has no validation - // field rbacv1beta1.ClusterRoleBindingList.ListMeta has no validation +func Validate_PolicyRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.PolicyRule) (errs field.ErrorList) { + // field rbacv1beta1.PolicyRule.Verbs + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("verbs"), obj.Verbs, safe.Field(oldObj, func(oldObj *rbacv1beta1.PolicyRule) []string { return oldObj.Verbs }), oldObj != nil)...) - // field rbacv1beta1.ClusterRoleBindingList.Items + // field rbacv1beta1.PolicyRule.APIGroups has no validation + // field rbacv1beta1.PolicyRule.Resources has no validation + // field rbacv1beta1.PolicyRule.ResourceNames has no validation + // field rbacv1beta1.PolicyRule.NonResourceURLs has no validation + return errs +} + +// Validate_Role validates an instance of Role according +// to declarative validation rules in the API schema. +func Validate_Role(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.Role) (errs field.ErrorList) { + // field rbacv1beta1.Role.TypeMeta has no validation + // field rbacv1beta1.Role.ObjectMeta has no validation + + // field rbacv1beta1.Role.Rules errs = append(errs, - func(fldPath *field.Path, obj, oldObj []rbacv1beta1.ClusterRoleBinding, oldValueCorrelated bool) (errs field.ErrorList) { + func(fldPath *field.Path, obj, oldObj []rbacv1beta1.PolicyRule, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ClusterRoleBinding)...) + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...) return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1beta1.ClusterRoleBindingList) []rbacv1beta1.ClusterRoleBinding { return oldObj.Items }), oldObj != nil)...) + }(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1beta1.Role) []rbacv1beta1.PolicyRule { return oldObj.Rules }), oldObj != nil)...) return errs } @@ -141,6 +216,14 @@ func Validate_RoleBinding(ctx context.Context, op operation.Operation, fldPath * if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Subject)...) return @@ -161,27 +244,6 @@ func Validate_RoleBinding(ctx context.Context, op operation.Operation, fldPath * return errs } -// Validate_RoleBindingList validates an instance of RoleBindingList according -// to declarative validation rules in the API schema. -func Validate_RoleBindingList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.RoleBindingList) (errs field.ErrorList) { - // field rbacv1beta1.RoleBindingList.TypeMeta has no validation - // field rbacv1beta1.RoleBindingList.ListMeta has no validation - - // field rbacv1beta1.RoleBindingList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []rbacv1beta1.RoleBinding, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_RoleBinding)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1beta1.RoleBindingList) []rbacv1beta1.RoleBinding { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_RoleRef validates an instance of RoleRef according // to declarative validation rules in the API schema. func Validate_RoleRef(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.RoleRef) (errs field.ErrorList) { @@ -197,7 +259,7 @@ func Validate_RoleRef(ctx context.Context, op operation.Operation, fldPath *fiel } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -225,7 +287,7 @@ func Validate_Subject(ctx context.Context, op operation.Operation, fldPath *fiel } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } diff --git a/pkg/apis/rbac/validation/validation.go b/pkg/apis/rbac/validation/validation.go index fcc672dbd2a84..a117cc88dfcc3 100644 --- a/pkg/apis/rbac/validation/validation.go +++ b/pkg/apis/rbac/validation/validation.go @@ -17,7 +17,7 @@ limitations under the License. package validation import ( - "k8s.io/apimachinery/pkg/api/validation/path" + "k8s.io/apimachinery/pkg/api/validate/content" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -30,7 +30,7 @@ import ( // * https://github.com/kubernetes/kubernetes/blob/60db507b279ce45bd16ea3db49bf181f2aeb3c3d/pkg/api/validation/name.go // * https://github.com/openshift/origin/blob/388478c40e751c4295dcb9a44dd69e5ac65d0e3b/pkg/api/helpers.go func ValidateRBACName(name string, prefix bool) []string { - return path.IsValidPathSegmentName(name) + return content.IsPathSegmentName(name) } func ValidateRole(role *rbac.Role) field.ErrorList { @@ -103,7 +103,7 @@ func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole func ValidatePolicyRule(rule rbac.PolicyRule, isNamespaced bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(rule.Verbs) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value")) + allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value").MarkCoveredByDeclarative()) } if len(rule.NonResourceURLs) > 0 { diff --git a/pkg/apis/resource/fuzzer/fuzzer.go b/pkg/apis/resource/fuzzer/fuzzer.go index e07c71373e1c1..473a8596b2757 100644 --- a/pkg/apis/resource/fuzzer/fuzzer.go +++ b/pkg/apis/resource/fuzzer/fuzzer.go @@ -106,5 +106,25 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { r.NodeName = nil } }, + func(r *resource.ResourcePoolStatusRequestStatus, c randfill.Continue) { + c.FillNoCustom(r) + // Empty slices become nil after round-tripping through JSON. + // Ensure they are either nil or have at least one element. + if len(r.Pools) == 0 { + r.Pools = nil + } + if len(r.Conditions) == 0 { + r.Conditions = nil + } + }, + func(r *resource.ResourcePoolStatusRequestSpec, c randfill.Continue) { + c.FillNoCustom(r) + // Limit has a default value of 100, so ensure it's set + // to prevent defaulting during round-trip. + if r.Limit == nil { + limit := int32(c.Int31n(1000) + 1) + r.Limit = &limit + } + }, } } diff --git a/pkg/apis/resource/register.go b/pkg/apis/resource/register.go index 3bf3a33bf5ae6..67dd307fd83eb 100644 --- a/pkg/apis/resource/register.go +++ b/pkg/apis/resource/register.go @@ -62,6 +62,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ResourceClaimTemplateList{}, &ResourceSlice{}, &ResourceSliceList{}, + &ResourcePoolStatusRequest{}, + &ResourcePoolStatusRequestList{}, ) return nil diff --git a/pkg/apis/resource/types.go b/pkg/apis/resource/types.go index 35933110d67ec..4549b0a3fc9f3 100644 --- a/pkg/apis/resource/types.go +++ b/pkg/apis/resource/types.go @@ -17,6 +17,7 @@ limitations under the License. package resource import ( + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -35,6 +36,11 @@ const ( // special ResourceClaim. Its single valid value is "true". // This is used only inside the scheduler. ExtendedResourceClaimAnnotation = "resource.kubernetes.io/extended-resource-claim" + // PodResourceClaimAnnotation is the annotation set on template-generated + // ResourceClaims by the ResourceClaim controller. Its value is the + // spec.resourceClaims[].name of the Pod or PodGroup for which the claim was + // generated. The Pod and its PodGroup are guaranteed to use the same name. + PodResourceClaimAnnotation = "resource.kubernetes.io/pod-claim-name" // Resource device class prefix is for generating implicit extended resource // name for a device class when its ExtendedResourceName field is not // specified. The generated name is this prefix + the device class name. @@ -249,7 +255,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 @@ -348,7 +361,7 @@ type Device struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional @@ -361,7 +374,7 @@ type Device struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -377,7 +390,7 @@ type Device struct { // All entries are condition types, which means // they must be labels. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -393,7 +406,7 @@ type Device struct { // All entries are condition types, which means // they must be labels. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -409,6 +422,59 @@ type Device struct { // +optional // +featureGate=DRAConsumableCapacity AllowMultipleAllocations *bool + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + NodeAllocatableResourceMappings map[v1.ResourceName]NodeAllocatableResourceMapping +} + +// NodeAllocatableResourceMapping defines the translation of the requested DRA device/capacity +// units to the corresponding quantity of the node-allocatable resource. +type NodeAllocatableResourceMapping struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + CapacityKey *QualifiedName + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + AllocationMultiplier *resource.Quantity } // DeviceCounterConsumption defines a set of counters that @@ -548,6 +614,10 @@ type CapacityRequestPolicyRange struct { // Limit for the sum of the number of entries in both attributes and capacity. const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32 +// Limit per device for the total number of string, version, bool or int values +// in list and non-list attributes. +const ResourceSliceMaxAttributeValuesPerDevice = 48 + // QualifiedName is the name of a device attribute or capacity. // // Attributes and capacities are defined either by the owner of the specific @@ -605,6 +675,46 @@ type DeviceAttribute struct { // +optional // +oneOf=ValueType VersionValue *string + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since:"1.36")=+oneOf=ValueType + // +featureGate=DRAListTypeAttributes + IntValues []int64 + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:alpha(since:"1.36")=+oneOf=ValueType + // +featureGate=DRAListTypeAttributes + BoolValues []bool + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since:"1.36")=+oneOf=ValueType + // +featureGate=DRAListTypeAttributes + StringValues []string + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since:"1.36")=+oneOf=ValueType + // +featureGate=DRAListTypeAttributes + VersionValues []string } // DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value. @@ -652,8 +762,12 @@ type DeviceTaint struct { // which will enable adding new enums within a single release without // ratcheting. - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. // // +optional TimeAdded *metav1.Time @@ -888,8 +1002,7 @@ type ExactDeviceRequest struct { // all ordinary claims to the device with respect to access modes and // any resource allocations. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. // // +optional @@ -911,7 +1024,7 @@ type ExactDeviceRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional @@ -1022,7 +1135,7 @@ type DeviceSubRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional @@ -1151,6 +1264,14 @@ type CELDeviceSelector struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -1214,6 +1335,11 @@ type DeviceConstraint struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. // // +optional @@ -1233,6 +1359,11 @@ type DeviceConstraint struct { // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -1456,7 +1587,7 @@ type AllocationResult struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -1531,8 +1662,7 @@ type DeviceRequestAllocationResult struct { // administrative access. See the corresponding request field // for a definition of mode. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. // // +optional @@ -1544,7 +1674,7 @@ type DeviceRequestAllocationResult struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional @@ -1555,7 +1685,7 @@ type DeviceRequestAllocationResult struct { // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -1566,7 +1696,7 @@ type DeviceRequestAllocationResult struct { // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -1999,3 +2129,135 @@ type DeviceTaintRuleList struct { // Items is the list of DeviceTaintRules. Items []DeviceTaintRule } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ResourcePoolStatusRequest triggers a one-time calculation of resource pool status +// based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. +// Users should delete and recreate requests to get updated information. +type ResourcePoolStatusRequest struct { + metav1.TypeMeta + // Standard object metadata + // +required + metav1.ObjectMeta + + // Spec defines the filters for which pools to include in the status. + // +required + Spec ResourcePoolStatusRequestSpec + + // Status is populated by the controller with the calculated pool status. + // +optional + Status *ResourcePoolStatusRequestStatus +} + +// ResourcePoolStatusRequestSpec defines the filters for the pool status request. +type ResourcePoolStatusRequestSpec struct { + // Driver specifies the DRA driver name to filter pools. + // Only pools from ResourceSlices with this driver will be included. + // This field is required to bound the scope of the request. + Driver string + + // PoolName optionally filters to a specific pool name. + // If not specified, all pools from the specified driver are included. + // When specified, must be a valid resource pool name (DNS subdomains separated by "/"). + // +optional + PoolName *string + + // Limit optionally specifies the maximum number of pools to return in the status. + // If more pools match the filter criteria, the response will be truncated. + // + // Default: 100 + // Maximum: 1000 + // + // +optional + Limit *int32 +} + +// ResourcePoolStatusRequestLimitDefault is the default value for spec.limit. +const ResourcePoolStatusRequestLimitDefault int32 = 100 + +// ResourcePoolStatusRequestLimitMax is the maximum allowed value for spec.limit. +const ResourcePoolStatusRequestLimitMax int32 = 1000 + +// ResourcePoolStatusRequestStatus contains the calculated pool status information. +type ResourcePoolStatusRequestStatus struct { + // PoolCount is the total number of pools that matched the filter criteria, + // regardless of truncation. + // +optional + PoolCount *int32 + + // Pools contains the first `spec.limit` matching pools, sorted by driver + // then pool name. If len(pools) < poolCount, the list was truncated. + // +optional + Pools []PoolStatus + + // Conditions provide information about the state of the request. + // A condition with type=Complete or type=Failed will always be set + // when the status is populated. + // +optional + Conditions []metav1.Condition +} + +// PoolStatus contains status information for a single resource pool. +type PoolStatus struct { + // Driver is the DRA driver name for this pool. + Driver string + + // PoolName is the name of the pool. + PoolName string + + // Generation is the pool generation observed across all ResourceSlices + // in this pool. + Generation int64 + + // ResourceSliceCount is the number of ResourceSlices that make up this pool. + // +optional + ResourceSliceCount *int32 + + // TotalDevices is the total number of devices in the pool across all slices. + // +optional + TotalDevices *int32 + + // AllocatedDevices is the number of devices currently allocated to claims. + // +optional + AllocatedDevices *int32 + + // AvailableDevices is the number of devices available for allocation. + // This equals TotalDevices - AllocatedDevices - UnavailableDevices. + // +optional + AvailableDevices *int32 + + // UnavailableDevices is the number of devices that are not available + // due to taints or other conditions, but are not allocated. + // +optional + UnavailableDevices *int32 + + // NodeName is the node this pool is associated with. + // When omitted, the pool is not associated with a specific node. + // +optional + NodeName *string + + // ValidationError is set when the pool's data could not be fully validated. + // When set, device count fields and ResourceSliceCount may be unset. + // +optional + ValidationError *string +} + +// ResourcePoolStatusRequestConditionComplete is the condition type for completed requests. +const ResourcePoolStatusRequestConditionComplete = "Complete" + +// ResourcePoolStatusRequestConditionFailed is the condition type for failed requests. +const ResourcePoolStatusRequestConditionFailed = "Failed" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests. +type ResourcePoolStatusRequestList struct { + metav1.TypeMeta + // Standard list metadata + // +optional + metav1.ListMeta + + // Items is the list of ResourcePoolStatusRequests. + Items []ResourcePoolStatusRequest +} diff --git a/pkg/apis/resource/v1/defaults.go b/pkg/apis/resource/v1/defaults.go index 623d663c51e3a..c93a8c307e1d1 100644 --- a/pkg/apis/resource/v1/defaults.go +++ b/pkg/apis/resource/v1/defaults.go @@ -20,6 +20,7 @@ import ( "time" resourceapi "k8s.io/api/resource/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -53,3 +54,14 @@ func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) { obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)} } } + +func SetDefaults_Device(obj *resourceapi.Device) { + // TODO: fix defaulter-gen not finding SetDefaults func on map value + for k, m := range obj.NodeAllocatableResourceMappings { + if m.AllocationMultiplier == nil { + q := resource.MustParse("1") + m.AllocationMultiplier = &q + obj.NodeAllocatableResourceMappings[k] = m + } + } +} diff --git a/pkg/apis/resource/v1/zz_generated.conversion.go b/pkg/apis/resource/v1/zz_generated.conversion.go index 4991c2215464c..283f70b775711 100644 --- a/pkg/apis/resource/v1/zz_generated.conversion.go +++ b/pkg/apis/resource/v1/zz_generated.conversion.go @@ -342,6 +342,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1.NodeAllocatableResourceMapping)(nil), (*resource.NodeAllocatableResourceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(a.(*resourcev1.NodeAllocatableResourceMapping), b.(*resource.NodeAllocatableResourceMapping), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.NodeAllocatableResourceMapping)(nil), (*resourcev1.NodeAllocatableResourceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_NodeAllocatableResourceMapping_To_v1_NodeAllocatableResourceMapping(a.(*resource.NodeAllocatableResourceMapping), b.(*resourcev1.NodeAllocatableResourceMapping), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*resourcev1.OpaqueDeviceConfiguration)(nil), (*resource.OpaqueDeviceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_OpaqueDeviceConfiguration_To_resource_OpaqueDeviceConfiguration(a.(*resourcev1.OpaqueDeviceConfiguration), b.(*resource.OpaqueDeviceConfiguration), scope) }); err != nil { @@ -678,6 +688,7 @@ func autoConvert_v1_Device_To_resource_Device(in *resourcev1.Device, out *resour out.BindingConditions = *(*[]string)(unsafe.Pointer(&in.BindingConditions)) out.BindingFailureConditions = *(*[]string)(unsafe.Pointer(&in.BindingFailureConditions)) out.AllowMultipleAllocations = (*bool)(unsafe.Pointer(in.AllowMultipleAllocations)) + out.NodeAllocatableResourceMappings = *(*map[corev1.ResourceName]resource.NodeAllocatableResourceMapping)(unsafe.Pointer(&in.NodeAllocatableResourceMappings)) return nil } @@ -699,6 +710,7 @@ func autoConvert_resource_Device_To_v1_Device(in *resource.Device, out *resource out.BindingConditions = *(*[]string)(unsafe.Pointer(&in.BindingConditions)) out.BindingFailureConditions = *(*[]string)(unsafe.Pointer(&in.BindingFailureConditions)) out.AllowMultipleAllocations = (*bool)(unsafe.Pointer(in.AllowMultipleAllocations)) + out.NodeAllocatableResourceMappings = *(*map[corev1.ResourceName]resourcev1.NodeAllocatableResourceMapping)(unsafe.Pointer(&in.NodeAllocatableResourceMappings)) return nil } @@ -762,6 +774,10 @@ func autoConvert_v1_DeviceAttribute_To_resource_DeviceAttribute(in *resourcev1.D out.BoolValue = (*bool)(unsafe.Pointer(in.BoolValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) + out.IntValues = *(*[]int64)(unsafe.Pointer(&in.IntValues)) + out.BoolValues = *(*[]bool)(unsafe.Pointer(&in.BoolValues)) + out.StringValues = *(*[]string)(unsafe.Pointer(&in.StringValues)) + out.VersionValues = *(*[]string)(unsafe.Pointer(&in.VersionValues)) return nil } @@ -775,6 +791,10 @@ func autoConvert_resource_DeviceAttribute_To_v1_DeviceAttribute(in *resource.Dev out.BoolValue = (*bool)(unsafe.Pointer(in.BoolValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) + out.IntValues = *(*[]int64)(unsafe.Pointer(&in.IntValues)) + out.BoolValues = *(*[]bool)(unsafe.Pointer(&in.BoolValues)) + out.StringValues = *(*[]string)(unsafe.Pointer(&in.StringValues)) + out.VersionValues = *(*[]string)(unsafe.Pointer(&in.VersionValues)) return nil } @@ -1241,6 +1261,28 @@ func Convert_resource_NetworkDeviceData_To_v1_NetworkDeviceData(in *resource.Net return autoConvert_resource_NetworkDeviceData_To_v1_NetworkDeviceData(in, out, s) } +func autoConvert_v1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in *resourcev1.NodeAllocatableResourceMapping, out *resource.NodeAllocatableResourceMapping, s conversion.Scope) error { + out.CapacityKey = (*resource.QualifiedName)(unsafe.Pointer(in.CapacityKey)) + out.AllocationMultiplier = (*apiresource.Quantity)(unsafe.Pointer(in.AllocationMultiplier)) + return nil +} + +// Convert_v1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping is an autogenerated conversion function. +func Convert_v1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in *resourcev1.NodeAllocatableResourceMapping, out *resource.NodeAllocatableResourceMapping, s conversion.Scope) error { + return autoConvert_v1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in, out, s) +} + +func autoConvert_resource_NodeAllocatableResourceMapping_To_v1_NodeAllocatableResourceMapping(in *resource.NodeAllocatableResourceMapping, out *resourcev1.NodeAllocatableResourceMapping, s conversion.Scope) error { + out.CapacityKey = (*resourcev1.QualifiedName)(unsafe.Pointer(in.CapacityKey)) + out.AllocationMultiplier = (*apiresource.Quantity)(unsafe.Pointer(in.AllocationMultiplier)) + return nil +} + +// Convert_resource_NodeAllocatableResourceMapping_To_v1_NodeAllocatableResourceMapping is an autogenerated conversion function. +func Convert_resource_NodeAllocatableResourceMapping_To_v1_NodeAllocatableResourceMapping(in *resource.NodeAllocatableResourceMapping, out *resourcev1.NodeAllocatableResourceMapping, s conversion.Scope) error { + return autoConvert_resource_NodeAllocatableResourceMapping_To_v1_NodeAllocatableResourceMapping(in, out, s) +} + func autoConvert_v1_OpaqueDeviceConfiguration_To_resource_OpaqueDeviceConfiguration(in *resourcev1.OpaqueDeviceConfiguration, out *resource.OpaqueDeviceConfiguration, s conversion.Scope) error { out.Driver = in.Driver out.Parameters = in.Parameters diff --git a/pkg/apis/resource/v1/zz_generated.defaults.go b/pkg/apis/resource/v1/zz_generated.defaults.go index d2dc71a8da279..24c675d2c2835 100644 --- a/pkg/apis/resource/v1/zz_generated.defaults.go +++ b/pkg/apis/resource/v1/zz_generated.defaults.go @@ -121,6 +121,7 @@ func SetObjectDefaults_ResourceClaimTemplateList(in *resourcev1.ResourceClaimTem func SetObjectDefaults_ResourceSlice(in *resourcev1.ResourceSlice) { for i := range in.Spec.Devices { a := &in.Spec.Devices[i] + SetDefaults_Device(a) for j := range a.Taints { b := &a.Taints[j] SetDefaults_DeviceTaint(b) diff --git a/pkg/apis/resource/v1/zz_generated.validations.go b/pkg/apis/resource/v1/zz_generated.validations.go index a3e1655353dc7..e33cf7a547db3 100644 --- a/pkg/apis/resource/v1/zz_generated.validations.go +++ b/pkg/apis/resource/v1/zz_generated.validations.go @@ -50,14 +50,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type DeviceClassList - scheme.AddValidationFunc((*resourcev1.DeviceClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_DeviceClassList(ctx, op, nil /* fldPath */, obj.(*resourcev1.DeviceClassList), safe.Cast[*resourcev1.DeviceClassList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceClaim scheme.AddValidationFunc((*resourcev1.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -66,14 +58,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceClaimList - scheme.AddValidationFunc((*resourcev1.ResourceClaimList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceClaimList(ctx, op, nil /* fldPath */, obj.(*resourcev1.ResourceClaimList), safe.Cast[*resourcev1.ResourceClaimList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceClaimTemplate scheme.AddValidationFunc((*resourcev1.ResourceClaimTemplate)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -82,14 +66,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceClaimTemplateList - scheme.AddValidationFunc((*resourcev1.ResourceClaimTemplateList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceClaimTemplateList(ctx, op, nil /* fldPath */, obj.(*resourcev1.ResourceClaimTemplateList), safe.Cast[*resourcev1.ResourceClaimTemplateList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceSlice scheme.AddValidationFunc((*resourcev1.ResourceSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -98,14 +74,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceSliceList - scheme.AddValidationFunc((*resourcev1.ResourceSliceList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceSliceList(ctx, op, nil /* fldPath */, obj.(*resourcev1.ResourceSliceList), safe.Cast[*resourcev1.ResourceSliceList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } @@ -125,13 +93,13 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation, } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("shareID"), obj.ShareID, safe.Field(oldObj, func(oldObj *resourcev1.AllocatedDeviceStatus) *string { return oldObj.ShareID }), oldObj != nil)...) @@ -147,7 +115,7 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation, } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -168,7 +136,7 @@ var symbolsForAllocationConfigSource = sets.New(resourcev1.AllocationConfigSourc // Validate_AllocationConfigSource validates an instance of AllocationConfigSource according // to declarative validation rules in the API schema. func Validate_AllocationConfigSource(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.AllocationConfigSource) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil).MarkAlpha()...) return errs } @@ -205,18 +173,37 @@ func Validate_CounterSet(ctx context.Context, op operation.Operation, fldPath *f } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *resourcev1.CounterSet) *string { return &oldObj.Name }), oldObj != nil)...) - // field resourcev1.CounterSet.Counters has no validation + // field resourcev1.CounterSet.Counters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]resourcev1.Counter, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName).MarkAlpha()...) + return + }(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1.CounterSet) map[string]resourcev1.Counter { return oldObj.Counters }), oldObj != nil)...) + return errs } @@ -232,6 +219,14 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the map and call the value type's validation function errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) return @@ -250,11 +245,11 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -263,7 +258,7 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceCounterConsumption, b resourcev1.DeviceCounterConsumption) bool { return a.CounterSet == b.CounterSet - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceCounterConsumption, b resourcev1.DeviceCounterConsumption) bool { return a.CounterSet == b.CounterSet @@ -282,6 +277,14 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceTaint)...) return @@ -298,11 +301,11 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -320,11 +323,11 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -334,6 +337,7 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field }(fldPath.Child("bindingFailureConditions"), obj.BindingFailureConditions, safe.Field(oldObj, func(oldObj *resourcev1.Device) []string { return oldObj.BindingFailureConditions }), oldObj != nil)...) // field resourcev1.Device.AllowMultipleAllocations has no validation + // field resourcev1.Device.NodeAllocatableResourceMappings has no validation return errs } @@ -349,7 +353,7 @@ func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -372,18 +376,18 @@ func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAllocationConfiguration) []string { return oldObj.Requests }), oldObj != nil)...) @@ -409,7 +413,7 @@ var symbolsForDeviceAllocationMode = sets.New(resourcev1.DeviceAllocationModeAll // Validate_DeviceAllocationMode validates an instance of DeviceAllocationMode according // to declarative validation rules in the API schema. func Validate_DeviceAllocationMode(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceAllocationMode) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceAllocationMode, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceAllocationMode, nil).MarkAlpha()...) return errs } @@ -426,11 +430,11 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -452,11 +456,11 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 64); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 64).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -472,7 +476,7 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } -var unionMembershipFor_k8s_io_api_resource_v1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) +var unionMembershipFor_k8s_io_api_resource_v1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version"), validate.NewUnionMember("ints"), validate.NewUnionMember("bools"), validate.NewUnionMember("strings"), validate.NewUnionMember("versions")) // Validate_DeviceAttribute validates an instance of DeviceAttribute according // to declarative validation rules in the API schema. @@ -497,7 +501,27 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa return false } return obj.VersionValue != nil - })...) + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.IntValues) != 0 + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.BoolValues) != 0 + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.StringValues) != 0 + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.VersionValues) != 0 + }).MarkAlpha()...) // field resourcev1.DeviceAttribute.IntValue errs = append(errs, @@ -508,7 +532,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -526,7 +550,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -544,7 +568,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -562,7 +586,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -571,6 +595,78 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa return }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *string { return oldObj.VersionValue }), oldObj != nil)...) + // field resourcev1.DeviceAttribute.IntValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("ints"), obj.IntValues, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) []int64 { return oldObj.IntValues }), oldObj != nil)...) + + // field resourcev1.DeviceAttribute.BoolValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []bool, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bools"), obj.BoolValues, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) []bool { return oldObj.BoolValues }), oldObj != nil)...) + + // field resourcev1.DeviceAttribute.StringValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("strings"), obj.StringValues, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) []string { return oldObj.StringValues }), oldObj != nil)...) + + // field resourcev1.DeviceAttribute.VersionValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("versions"), obj.VersionValues, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) []string { return oldObj.VersionValues }), oldObj != nil)...) + return errs } @@ -586,18 +682,18 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceRequest, b resourcev1.DeviceRequest) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceRequest, b resourcev1.DeviceRequest) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceRequest, b resourcev1.DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest)...) return @@ -612,11 +708,11 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -636,11 +732,11 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -666,18 +762,18 @@ func Validate_DeviceClaimConfiguration(ctx context.Context, op operation.Operati } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClaimConfiguration) []string { return oldObj.Requests }), oldObj != nil)...) @@ -713,13 +809,13 @@ func Validate_DeviceClass(ctx context.Context, op operation.Operation, fldPath * // call field-attached validations func() { // cohort name earlyReturn := false - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue); len(e) != 0 { + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName)...) + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName).MarkAlpha()...) }() return }(fldPath.Child("metadata"), &obj.ObjectMeta, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClass) *metav1.ObjectMeta { return &oldObj.ObjectMeta }), oldObj != nil)...) @@ -759,27 +855,6 @@ func Validate_DeviceClassConfiguration(ctx context.Context, op operation.Operati return errs } -// Validate_DeviceClassList validates an instance of DeviceClassList according -// to declarative validation rules in the API schema. -func Validate_DeviceClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClassList) (errs field.ErrorList) { - // field resourcev1.DeviceClassList.TypeMeta has no validation - // field resourcev1.DeviceClassList.ListMeta has no validation - - // field resourcev1.DeviceClassList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceClass, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceClass)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClassList) []resourcev1.DeviceClass { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_DeviceClassSpec validates an instance of DeviceClassSpec according // to declarative validation rules in the API schema. func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClassSpec) (errs field.ErrorList) { @@ -792,11 +867,11 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -814,11 +889,11 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -838,13 +913,13 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("extendedResourceName"), obj.ExtendedResourceName, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClassSpec) *string { return oldObj.ExtendedResourceName }), oldObj != nil)...) @@ -863,7 +938,7 @@ func Validate_DeviceConfiguration(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -891,18 +966,18 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1.DeviceConstraint) []string { return oldObj.Requests }), oldObj != nil)...) @@ -915,13 +990,13 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("matchAttribute"), obj.MatchAttribute, safe.Field(oldObj, func(oldObj *resourcev1.DeviceConstraint) *resourcev1.FullyQualifiedName { return oldObj.MatchAttribute }), oldObj != nil)...) @@ -941,18 +1016,39 @@ func Validate_DeviceCounterConsumption(ctx context.Context, op operation.Operati } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("counterSet"), &obj.CounterSet, safe.Field(oldObj, func(oldObj *resourcev1.DeviceCounterConsumption) *string { return &oldObj.CounterSet }), oldObj != nil)...) - // field resourcev1.DeviceCounterConsumption.Counters has no validation + // field resourcev1.DeviceCounterConsumption.Counters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]resourcev1.Counter, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName).MarkAlpha()...) + return + }(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1.DeviceCounterConsumption) map[string]resourcev1.Counter { + return oldObj.Counters + }), oldObj != nil)...) + return errs } @@ -970,7 +1066,7 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -990,18 +1086,18 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceSubRequest, b resourcev1.DeviceSubRequest) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceSubRequest, b resourcev1.DeviceSubRequest) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1.DeviceSubRequest, b resourcev1.DeviceSubRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceSubRequest)...) return @@ -1024,14 +1120,15 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) return }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1.DeviceRequestAllocationResult) *string { return &oldObj.Driver }), oldObj != nil)...) @@ -1044,14 +1141,14 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("pool"), &obj.Pool, safe.Field(oldObj, func(oldObj *resourcev1.DeviceRequestAllocationResult) *string { return &oldObj.Pool }), oldObj != nil)...) @@ -1065,6 +1162,14 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1081,11 +1186,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1103,11 +1208,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1127,13 +1232,13 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("shareID"), obj.ShareID, safe.Field(oldObj, func(oldObj *resourcev1.DeviceRequestAllocationResult) *types.UID { return oldObj.ShareID }), oldObj != nil)...) @@ -1155,14 +1260,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("deviceClassName"), &obj.DeviceClassName, safe.Field(oldObj, func(oldObj *resourcev1.DeviceSubRequest) *string { return &oldObj.DeviceClassName }), oldObj != nil)...) @@ -1175,11 +1280,11 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1195,6 +1300,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceAllocationMode(ctx, op, fldPath, obj, oldObj)...) return @@ -1211,6 +1324,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1235,7 +1356,7 @@ func Validate_DeviceTaint(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1256,7 +1377,7 @@ var symbolsForDeviceTaintEffect = sets.New(resourcev1.DeviceTaintEffectNoExecute // Validate_DeviceTaintEffect validates an instance of DeviceTaintEffect according // to declarative validation rules in the API schema. func Validate_DeviceTaintEffect(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceTaintEffect) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil).MarkAlpha()...) return errs } @@ -1273,13 +1394,13 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("key"), &obj.Key, safe.Field(oldObj, func(oldObj *resourcev1.DeviceToleration) *string { return &oldObj.Key }), oldObj != nil)...) @@ -1290,6 +1411,16 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceTolerationOperator(ctx, op, fldPath, obj, oldObj)...) return @@ -1306,6 +1437,14 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceTaintEffect(ctx, op, fldPath, obj, oldObj)...) return @@ -1320,7 +1459,7 @@ var symbolsForDeviceTolerationOperator = sets.New(resourcev1.DeviceTolerationOpE // Validate_DeviceTolerationOperator validates an instance of DeviceTolerationOperator according // to declarative validation rules in the API schema. func Validate_DeviceTolerationOperator(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceTolerationOperator) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTolerationOperator, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTolerationOperator, nil).MarkAlpha()...) return errs } @@ -1339,11 +1478,11 @@ func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fl } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1361,7 +1500,7 @@ func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fl } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1384,6 +1523,14 @@ func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fl if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1405,13 +1552,13 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 256)...) + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 256).MarkAlpha()...) return }(fldPath.Child("interfaceName"), &obj.InterfaceName, safe.Field(oldObj, func(oldObj *resourcev1.NetworkDeviceData) *string { return &oldObj.InterfaceName }), oldObj != nil)...) @@ -1424,18 +1571,18 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("ips"), obj.IPs, safe.Field(oldObj, func(oldObj *resourcev1.NetworkDeviceData) []string { return oldObj.IPs }), oldObj != nil)...) @@ -1448,13 +1595,13 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 128)...) + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 128).MarkAlpha()...) return }(fldPath.Child("hardwareAddress"), &obj.HardwareAddress, safe.Field(oldObj, func(oldObj *resourcev1.NetworkDeviceData) *string { return &oldObj.HardwareAddress }), oldObj != nil)...) @@ -1473,14 +1620,15 @@ func Validate_OpaqueDeviceConfiguration(ctx context.Context, op operation.Operat } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) return }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1.OpaqueDeviceConfiguration) *string { return &oldObj.Driver }), oldObj != nil)...) @@ -1503,7 +1651,7 @@ func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1530,27 +1678,6 @@ func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath return errs } -// Validate_ResourceClaimList validates an instance of ResourceClaimList according -// to declarative validation rules in the API schema. -func Validate_ResourceClaimList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimList) (errs field.ErrorList) { - // field resourcev1.ResourceClaimList.TypeMeta has no validation - // field resourcev1.ResourceClaimList.ListMeta has no validation - - // field resourcev1.ResourceClaimList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1.ResourceClaim, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaim)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimList) []resourcev1.ResourceClaim { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ResourceClaimSpec validates an instance of ResourceClaimSpec according // to declarative validation rules in the API schema. func Validate_ResourceClaimSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimSpec) (errs field.ErrorList) { @@ -1581,10 +1708,10 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify); len(e) != 0 { + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1605,11 +1732,11 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 256); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 256).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1618,7 +1745,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.ResourceClaimConsumerReference, b resourcev1.ResourceClaimConsumerReference) bool { return a.UID == b.UID - })...) + }).MarkAlpha()...) return }(fldPath.Child("reservedFor"), obj.ReservedFor, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimStatus) []resourcev1.ResourceClaimConsumerReference { return oldObj.ReservedFor @@ -1633,7 +1760,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1642,7 +1769,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.AllocatedDeviceStatus, b resourcev1.AllocatedDeviceStatus) bool { return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID)) - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1.AllocatedDeviceStatus, b resourcev1.AllocatedDeviceStatus) bool { return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID)) @@ -1676,29 +1803,6 @@ func Validate_ResourceClaimTemplate(ctx context.Context, op operation.Operation, return errs } -// Validate_ResourceClaimTemplateList validates an instance of ResourceClaimTemplateList according -// to declarative validation rules in the API schema. -func Validate_ResourceClaimTemplateList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimTemplateList) (errs field.ErrorList) { - // field resourcev1.ResourceClaimTemplateList.TypeMeta has no validation - // field resourcev1.ResourceClaimTemplateList.ListMeta has no validation - - // field resourcev1.ResourceClaimTemplateList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1.ResourceClaimTemplate, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaimTemplate)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimTemplateList) []resourcev1.ResourceClaimTemplate { - return oldObj.Items - }), oldObj != nil)...) - - return errs -} - // Validate_ResourceClaimTemplateSpec validates an instance of ResourceClaimTemplateSpec according // to declarative validation rules in the API schema. func Validate_ResourceClaimTemplateSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimTemplateSpec) (errs field.ErrorList) { @@ -1740,27 +1844,6 @@ func Validate_ResourceSlice(ctx context.Context, op operation.Operation, fldPath return errs } -// Validate_ResourceSliceList validates an instance of ResourceSliceList according -// to declarative validation rules in the API schema. -func Validate_ResourceSliceList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceSliceList) (errs field.ErrorList) { - // field resourcev1.ResourceSliceList.TypeMeta has no validation - // field resourcev1.ResourceSliceList.ListMeta has no validation - - // field resourcev1.ResourceSliceList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1.ResourceSlice, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceSlice)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1.ResourceSliceList) []resourcev1.ResourceSlice { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ResourceSliceSpec validates an instance of ResourceSliceSpec according // to declarative validation rules in the API schema. func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceSliceSpec) (errs field.ErrorList) { @@ -1779,7 +1862,7 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1801,18 +1884,18 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.CounterSet, b resourcev1.CounterSet) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1.CounterSet, b resourcev1.CounterSet) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1.CounterSet, b resourcev1.CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet)...) return diff --git a/pkg/apis/resource/v1alpha3/defaults.go b/pkg/apis/resource/v1alpha3/defaults.go index 0e21ccb692d67..5a6647fe9d5d8 100644 --- a/pkg/apis/resource/v1alpha3/defaults.go +++ b/pkg/apis/resource/v1alpha3/defaults.go @@ -33,3 +33,11 @@ func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) { obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)} } } + +// SetDefaults_ResourcePoolStatusRequestSpec sets defaults for ResourcePoolStatusRequestSpec. +func SetDefaults_ResourcePoolStatusRequestSpec(obj *resourceapi.ResourcePoolStatusRequestSpec) { + if obj.Limit == nil { + defaultLimit := resourceapi.ResourcePoolStatusRequestLimitDefault + obj.Limit = &defaultLimit + } +} diff --git a/pkg/apis/resource/v1alpha3/doc.go b/pkg/apis/resource/v1alpha3/doc.go index 2193bfc0b88da..a839d4c6d714d 100644 --- a/pkg/apis/resource/v1alpha3/doc.go +++ b/pkg/apis/resource/v1alpha3/doc.go @@ -18,6 +18,8 @@ limitations under the License. // +k8s:conversion-gen-external-types=k8s.io/api/resource/v1alpha3 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/resource/v1alpha3 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/resource/v1alpha3 // Package v1alpha3 is the v1alpha3 version of the resource API. package v1alpha3 diff --git a/pkg/apis/resource/v1alpha3/zz_generated.conversion.go b/pkg/apis/resource/v1alpha3/zz_generated.conversion.go index e5c6ee21cf91f..d49da1930b1f0 100644 --- a/pkg/apis/resource/v1alpha3/zz_generated.conversion.go +++ b/pkg/apis/resource/v1alpha3/zz_generated.conversion.go @@ -118,6 +118,56 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.PoolStatus)(nil), (*resource.PoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_PoolStatus_To_resource_PoolStatus(a.(*resourcev1alpha3.PoolStatus), b.(*resource.PoolStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.PoolStatus)(nil), (*resourcev1alpha3.PoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_PoolStatus_To_v1alpha3_PoolStatus(a.(*resource.PoolStatus), b.(*resourcev1alpha3.PoolStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.ResourcePoolStatusRequest)(nil), (*resource.ResourcePoolStatusRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_ResourcePoolStatusRequest_To_resource_ResourcePoolStatusRequest(a.(*resourcev1alpha3.ResourcePoolStatusRequest), b.(*resource.ResourcePoolStatusRequest), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.ResourcePoolStatusRequest)(nil), (*resourcev1alpha3.ResourcePoolStatusRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_ResourcePoolStatusRequest_To_v1alpha3_ResourcePoolStatusRequest(a.(*resource.ResourcePoolStatusRequest), b.(*resourcev1alpha3.ResourcePoolStatusRequest), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.ResourcePoolStatusRequestList)(nil), (*resource.ResourcePoolStatusRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_ResourcePoolStatusRequestList_To_resource_ResourcePoolStatusRequestList(a.(*resourcev1alpha3.ResourcePoolStatusRequestList), b.(*resource.ResourcePoolStatusRequestList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.ResourcePoolStatusRequestList)(nil), (*resourcev1alpha3.ResourcePoolStatusRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_ResourcePoolStatusRequestList_To_v1alpha3_ResourcePoolStatusRequestList(a.(*resource.ResourcePoolStatusRequestList), b.(*resourcev1alpha3.ResourcePoolStatusRequestList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.ResourcePoolStatusRequestSpec)(nil), (*resource.ResourcePoolStatusRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_ResourcePoolStatusRequestSpec_To_resource_ResourcePoolStatusRequestSpec(a.(*resourcev1alpha3.ResourcePoolStatusRequestSpec), b.(*resource.ResourcePoolStatusRequestSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.ResourcePoolStatusRequestSpec)(nil), (*resourcev1alpha3.ResourcePoolStatusRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_ResourcePoolStatusRequestSpec_To_v1alpha3_ResourcePoolStatusRequestSpec(a.(*resource.ResourcePoolStatusRequestSpec), b.(*resourcev1alpha3.ResourcePoolStatusRequestSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.ResourcePoolStatusRequestStatus)(nil), (*resource.ResourcePoolStatusRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_ResourcePoolStatusRequestStatus_To_resource_ResourcePoolStatusRequestStatus(a.(*resourcev1alpha3.ResourcePoolStatusRequestStatus), b.(*resource.ResourcePoolStatusRequestStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.ResourcePoolStatusRequestStatus)(nil), (*resourcev1alpha3.ResourcePoolStatusRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_ResourcePoolStatusRequestStatus_To_v1alpha3_ResourcePoolStatusRequestStatus(a.(*resource.ResourcePoolStatusRequestStatus), b.(*resourcev1alpha3.ResourcePoolStatusRequestStatus), scope) + }); err != nil { + return err + } return nil } @@ -310,3 +360,139 @@ func autoConvert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(in func Convert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(in *resource.DeviceTaintSelector, out *resourcev1alpha3.DeviceTaintSelector, s conversion.Scope) error { return autoConvert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(in, out, s) } + +func autoConvert_v1alpha3_PoolStatus_To_resource_PoolStatus(in *resourcev1alpha3.PoolStatus, out *resource.PoolStatus, s conversion.Scope) error { + out.Driver = in.Driver + out.PoolName = in.PoolName + out.Generation = in.Generation + out.ResourceSliceCount = (*int32)(unsafe.Pointer(in.ResourceSliceCount)) + out.TotalDevices = (*int32)(unsafe.Pointer(in.TotalDevices)) + out.AllocatedDevices = (*int32)(unsafe.Pointer(in.AllocatedDevices)) + out.AvailableDevices = (*int32)(unsafe.Pointer(in.AvailableDevices)) + out.UnavailableDevices = (*int32)(unsafe.Pointer(in.UnavailableDevices)) + out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) + out.ValidationError = (*string)(unsafe.Pointer(in.ValidationError)) + return nil +} + +// Convert_v1alpha3_PoolStatus_To_resource_PoolStatus is an autogenerated conversion function. +func Convert_v1alpha3_PoolStatus_To_resource_PoolStatus(in *resourcev1alpha3.PoolStatus, out *resource.PoolStatus, s conversion.Scope) error { + return autoConvert_v1alpha3_PoolStatus_To_resource_PoolStatus(in, out, s) +} + +func autoConvert_resource_PoolStatus_To_v1alpha3_PoolStatus(in *resource.PoolStatus, out *resourcev1alpha3.PoolStatus, s conversion.Scope) error { + out.Driver = in.Driver + out.PoolName = in.PoolName + out.Generation = in.Generation + out.ResourceSliceCount = (*int32)(unsafe.Pointer(in.ResourceSliceCount)) + out.TotalDevices = (*int32)(unsafe.Pointer(in.TotalDevices)) + out.AllocatedDevices = (*int32)(unsafe.Pointer(in.AllocatedDevices)) + out.AvailableDevices = (*int32)(unsafe.Pointer(in.AvailableDevices)) + out.UnavailableDevices = (*int32)(unsafe.Pointer(in.UnavailableDevices)) + out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) + out.ValidationError = (*string)(unsafe.Pointer(in.ValidationError)) + return nil +} + +// Convert_resource_PoolStatus_To_v1alpha3_PoolStatus is an autogenerated conversion function. +func Convert_resource_PoolStatus_To_v1alpha3_PoolStatus(in *resource.PoolStatus, out *resourcev1alpha3.PoolStatus, s conversion.Scope) error { + return autoConvert_resource_PoolStatus_To_v1alpha3_PoolStatus(in, out, s) +} + +func autoConvert_v1alpha3_ResourcePoolStatusRequest_To_resource_ResourcePoolStatusRequest(in *resourcev1alpha3.ResourcePoolStatusRequest, out *resource.ResourcePoolStatusRequest, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha3_ResourcePoolStatusRequestSpec_To_resource_ResourcePoolStatusRequestSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + out.Status = (*resource.ResourcePoolStatusRequestStatus)(unsafe.Pointer(in.Status)) + return nil +} + +// Convert_v1alpha3_ResourcePoolStatusRequest_To_resource_ResourcePoolStatusRequest is an autogenerated conversion function. +func Convert_v1alpha3_ResourcePoolStatusRequest_To_resource_ResourcePoolStatusRequest(in *resourcev1alpha3.ResourcePoolStatusRequest, out *resource.ResourcePoolStatusRequest, s conversion.Scope) error { + return autoConvert_v1alpha3_ResourcePoolStatusRequest_To_resource_ResourcePoolStatusRequest(in, out, s) +} + +func autoConvert_resource_ResourcePoolStatusRequest_To_v1alpha3_ResourcePoolStatusRequest(in *resource.ResourcePoolStatusRequest, out *resourcev1alpha3.ResourcePoolStatusRequest, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_resource_ResourcePoolStatusRequestSpec_To_v1alpha3_ResourcePoolStatusRequestSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + out.Status = (*resourcev1alpha3.ResourcePoolStatusRequestStatus)(unsafe.Pointer(in.Status)) + return nil +} + +// Convert_resource_ResourcePoolStatusRequest_To_v1alpha3_ResourcePoolStatusRequest is an autogenerated conversion function. +func Convert_resource_ResourcePoolStatusRequest_To_v1alpha3_ResourcePoolStatusRequest(in *resource.ResourcePoolStatusRequest, out *resourcev1alpha3.ResourcePoolStatusRequest, s conversion.Scope) error { + return autoConvert_resource_ResourcePoolStatusRequest_To_v1alpha3_ResourcePoolStatusRequest(in, out, s) +} + +func autoConvert_v1alpha3_ResourcePoolStatusRequestList_To_resource_ResourcePoolStatusRequestList(in *resourcev1alpha3.ResourcePoolStatusRequestList, out *resource.ResourcePoolStatusRequestList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]resource.ResourcePoolStatusRequest)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha3_ResourcePoolStatusRequestList_To_resource_ResourcePoolStatusRequestList is an autogenerated conversion function. +func Convert_v1alpha3_ResourcePoolStatusRequestList_To_resource_ResourcePoolStatusRequestList(in *resourcev1alpha3.ResourcePoolStatusRequestList, out *resource.ResourcePoolStatusRequestList, s conversion.Scope) error { + return autoConvert_v1alpha3_ResourcePoolStatusRequestList_To_resource_ResourcePoolStatusRequestList(in, out, s) +} + +func autoConvert_resource_ResourcePoolStatusRequestList_To_v1alpha3_ResourcePoolStatusRequestList(in *resource.ResourcePoolStatusRequestList, out *resourcev1alpha3.ResourcePoolStatusRequestList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]resourcev1alpha3.ResourcePoolStatusRequest)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_resource_ResourcePoolStatusRequestList_To_v1alpha3_ResourcePoolStatusRequestList is an autogenerated conversion function. +func Convert_resource_ResourcePoolStatusRequestList_To_v1alpha3_ResourcePoolStatusRequestList(in *resource.ResourcePoolStatusRequestList, out *resourcev1alpha3.ResourcePoolStatusRequestList, s conversion.Scope) error { + return autoConvert_resource_ResourcePoolStatusRequestList_To_v1alpha3_ResourcePoolStatusRequestList(in, out, s) +} + +func autoConvert_v1alpha3_ResourcePoolStatusRequestSpec_To_resource_ResourcePoolStatusRequestSpec(in *resourcev1alpha3.ResourcePoolStatusRequestSpec, out *resource.ResourcePoolStatusRequestSpec, s conversion.Scope) error { + out.Driver = in.Driver + out.PoolName = (*string)(unsafe.Pointer(in.PoolName)) + out.Limit = (*int32)(unsafe.Pointer(in.Limit)) + return nil +} + +// Convert_v1alpha3_ResourcePoolStatusRequestSpec_To_resource_ResourcePoolStatusRequestSpec is an autogenerated conversion function. +func Convert_v1alpha3_ResourcePoolStatusRequestSpec_To_resource_ResourcePoolStatusRequestSpec(in *resourcev1alpha3.ResourcePoolStatusRequestSpec, out *resource.ResourcePoolStatusRequestSpec, s conversion.Scope) error { + return autoConvert_v1alpha3_ResourcePoolStatusRequestSpec_To_resource_ResourcePoolStatusRequestSpec(in, out, s) +} + +func autoConvert_resource_ResourcePoolStatusRequestSpec_To_v1alpha3_ResourcePoolStatusRequestSpec(in *resource.ResourcePoolStatusRequestSpec, out *resourcev1alpha3.ResourcePoolStatusRequestSpec, s conversion.Scope) error { + out.Driver = in.Driver + out.PoolName = (*string)(unsafe.Pointer(in.PoolName)) + out.Limit = (*int32)(unsafe.Pointer(in.Limit)) + return nil +} + +// Convert_resource_ResourcePoolStatusRequestSpec_To_v1alpha3_ResourcePoolStatusRequestSpec is an autogenerated conversion function. +func Convert_resource_ResourcePoolStatusRequestSpec_To_v1alpha3_ResourcePoolStatusRequestSpec(in *resource.ResourcePoolStatusRequestSpec, out *resourcev1alpha3.ResourcePoolStatusRequestSpec, s conversion.Scope) error { + return autoConvert_resource_ResourcePoolStatusRequestSpec_To_v1alpha3_ResourcePoolStatusRequestSpec(in, out, s) +} + +func autoConvert_v1alpha3_ResourcePoolStatusRequestStatus_To_resource_ResourcePoolStatusRequestStatus(in *resourcev1alpha3.ResourcePoolStatusRequestStatus, out *resource.ResourcePoolStatusRequestStatus, s conversion.Scope) error { + out.PoolCount = (*int32)(unsafe.Pointer(in.PoolCount)) + out.Pools = *(*[]resource.PoolStatus)(unsafe.Pointer(&in.Pools)) + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1alpha3_ResourcePoolStatusRequestStatus_To_resource_ResourcePoolStatusRequestStatus is an autogenerated conversion function. +func Convert_v1alpha3_ResourcePoolStatusRequestStatus_To_resource_ResourcePoolStatusRequestStatus(in *resourcev1alpha3.ResourcePoolStatusRequestStatus, out *resource.ResourcePoolStatusRequestStatus, s conversion.Scope) error { + return autoConvert_v1alpha3_ResourcePoolStatusRequestStatus_To_resource_ResourcePoolStatusRequestStatus(in, out, s) +} + +func autoConvert_resource_ResourcePoolStatusRequestStatus_To_v1alpha3_ResourcePoolStatusRequestStatus(in *resource.ResourcePoolStatusRequestStatus, out *resourcev1alpha3.ResourcePoolStatusRequestStatus, s conversion.Scope) error { + out.PoolCount = (*int32)(unsafe.Pointer(in.PoolCount)) + out.Pools = *(*[]resourcev1alpha3.PoolStatus)(unsafe.Pointer(&in.Pools)) + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_resource_ResourcePoolStatusRequestStatus_To_v1alpha3_ResourcePoolStatusRequestStatus is an autogenerated conversion function. +func Convert_resource_ResourcePoolStatusRequestStatus_To_v1alpha3_ResourcePoolStatusRequestStatus(in *resource.ResourcePoolStatusRequestStatus, out *resourcev1alpha3.ResourcePoolStatusRequestStatus, s conversion.Scope) error { + return autoConvert_resource_ResourcePoolStatusRequestStatus_To_v1alpha3_ResourcePoolStatusRequestStatus(in, out, s) +} diff --git a/pkg/apis/resource/v1alpha3/zz_generated.defaults.go b/pkg/apis/resource/v1alpha3/zz_generated.defaults.go index 8b5f98b027b86..99fdcfa58c2ab 100644 --- a/pkg/apis/resource/v1alpha3/zz_generated.defaults.go +++ b/pkg/apis/resource/v1alpha3/zz_generated.defaults.go @@ -34,6 +34,12 @@ func RegisterDefaults(scheme *runtime.Scheme) error { scheme.AddTypeDefaultingFunc(&resourcev1alpha3.DeviceTaintRuleList{}, func(obj interface{}) { SetObjectDefaults_DeviceTaintRuleList(obj.(*resourcev1alpha3.DeviceTaintRuleList)) }) + scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourcePoolStatusRequest{}, func(obj interface{}) { + SetObjectDefaults_ResourcePoolStatusRequest(obj.(*resourcev1alpha3.ResourcePoolStatusRequest)) + }) + scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourcePoolStatusRequestList{}, func(obj interface{}) { + SetObjectDefaults_ResourcePoolStatusRequestList(obj.(*resourcev1alpha3.ResourcePoolStatusRequestList)) + }) return nil } @@ -47,3 +53,18 @@ func SetObjectDefaults_DeviceTaintRuleList(in *resourcev1alpha3.DeviceTaintRuleL SetObjectDefaults_DeviceTaintRule(a) } } + +func SetObjectDefaults_ResourcePoolStatusRequest(in *resourcev1alpha3.ResourcePoolStatusRequest) { + SetDefaults_ResourcePoolStatusRequestSpec(&in.Spec) + if in.Spec.Limit == nil { + var ptrVar1 int32 = 100 + in.Spec.Limit = &ptrVar1 + } +} + +func SetObjectDefaults_ResourcePoolStatusRequestList(in *resourcev1alpha3.ResourcePoolStatusRequestList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ResourcePoolStatusRequest(a) + } +} diff --git a/pkg/apis/resource/v1alpha3/zz_generated.validations.go b/pkg/apis/resource/v1alpha3/zz_generated.validations.go new file mode 100644 index 0000000000000..11499dd7664b2 --- /dev/null +++ b/pkg/apis/resource/v1alpha3/zz_generated.validations.go @@ -0,0 +1,544 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + context "context" + fmt "fmt" + + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type DeviceTaintRule + scheme.AddValidationFunc((*resourcev1alpha3.DeviceTaintRule)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceTaintRule(ctx, op, nil /* fldPath */, obj.(*resourcev1alpha3.DeviceTaintRule), safe.Cast[*resourcev1alpha3.DeviceTaintRule](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type ResourcePoolStatusRequest + scheme.AddValidationFunc((*resourcev1alpha3.ResourcePoolStatusRequest)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/", "/status": + return Validate_ResourcePoolStatusRequest(ctx, op, nil /* fldPath */, obj.(*resourcev1alpha3.ResourcePoolStatusRequest), safe.Cast[*resourcev1alpha3.ResourcePoolStatusRequest](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_DeviceTaint validates an instance of DeviceTaint according +// to declarative validation rules in the API schema. +func Validate_DeviceTaint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaint) (errs field.ErrorList) { + // field resourcev1alpha3.DeviceTaint.Key has no validation + // field resourcev1alpha3.DeviceTaint.Value has no validation + + // field resourcev1alpha3.DeviceTaint.Effect + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaintEffect, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_DeviceTaintEffect(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("effect"), &obj.Effect, safe.Field(oldObj, func(oldObj *resourcev1alpha3.DeviceTaint) *resourcev1alpha3.DeviceTaintEffect { return &oldObj.Effect }), oldObj != nil)...) + + // field resourcev1alpha3.DeviceTaint.TimeAdded has no validation + return errs +} + +var symbolsForDeviceTaintEffect = sets.New(resourcev1alpha3.DeviceTaintEffectNoExecute, resourcev1alpha3.DeviceTaintEffectNoSchedule, resourcev1alpha3.DeviceTaintEffectNone) + +// Validate_DeviceTaintEffect validates an instance of DeviceTaintEffect according +// to declarative validation rules in the API schema. +func Validate_DeviceTaintEffect(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaintEffect) (errs field.ErrorList) { + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil)...) + + return errs +} + +// Validate_DeviceTaintRule validates an instance of DeviceTaintRule according +// to declarative validation rules in the API schema. +func Validate_DeviceTaintRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaintRule) (errs field.ErrorList) { + // field resourcev1alpha3.DeviceTaintRule.TypeMeta has no validation + // field resourcev1alpha3.DeviceTaintRule.ObjectMeta has no validation + + // field resourcev1alpha3.DeviceTaintRule.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaintRuleSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceTaintRuleSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *resourcev1alpha3.DeviceTaintRule) *resourcev1alpha3.DeviceTaintRuleSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field resourcev1alpha3.DeviceTaintRule.Status has no validation + return errs +} + +// Validate_DeviceTaintRuleSpec validates an instance of DeviceTaintRuleSpec according +// to declarative validation rules in the API schema. +func Validate_DeviceTaintRuleSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaintRuleSpec) (errs field.ErrorList) { + // field resourcev1alpha3.DeviceTaintRuleSpec.DeviceSelector has no validation + + // field resourcev1alpha3.DeviceTaintRuleSpec.Taint + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1alpha3.DeviceTaint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceTaint(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("taint"), &obj.Taint, safe.Field(oldObj, func(oldObj *resourcev1alpha3.DeviceTaintRuleSpec) *resourcev1alpha3.DeviceTaint { return &oldObj.Taint }), oldObj != nil)...) + + return errs +} + +// Validate_PoolStatus validates an instance of PoolStatus according +// to declarative validation rules in the API schema. +func Validate_PoolStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.PoolStatus) (errs field.ErrorList) { + // field resourcev1alpha3.PoolStatus.Driver + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *string { return &oldObj.Driver }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.PoolName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("poolName"), &obj.PoolName, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *string { return &oldObj.PoolName }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.Generation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("generation"), &obj.Generation, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *int64 { return &oldObj.Generation }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.ResourceSliceCount + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("resourceSliceCount"), obj.ResourceSliceCount, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *int32 { return oldObj.ResourceSliceCount }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.TotalDevices + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("totalDevices"), obj.TotalDevices, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *int32 { return oldObj.TotalDevices }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.AllocatedDevices + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("allocatedDevices"), obj.AllocatedDevices, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *int32 { return oldObj.AllocatedDevices }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.AvailableDevices + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("availableDevices"), obj.AvailableDevices, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *int32 { return oldObj.AvailableDevices }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.UnavailableDevices + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("unavailableDevices"), obj.UnavailableDevices, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *int32 { return oldObj.UnavailableDevices }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.NodeName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("nodeName"), obj.NodeName, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *string { return oldObj.NodeName }), oldObj != nil)...) + + // field resourcev1alpha3.PoolStatus.ValidationError + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 256)...) + return + }(fldPath.Child("validationError"), obj.ValidationError, safe.Field(oldObj, func(oldObj *resourcev1alpha3.PoolStatus) *string { return oldObj.ValidationError }), oldObj != nil)...) + + return errs +} + +// Validate_ResourcePoolStatusRequest validates an instance of ResourcePoolStatusRequest according +// to declarative validation rules in the API schema. +func Validate_ResourcePoolStatusRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.ResourcePoolStatusRequest) (errs field.ErrorList) { + // field resourcev1alpha3.ResourcePoolStatusRequest.TypeMeta has no validation + // field resourcev1alpha3.ResourcePoolStatusRequest.ObjectMeta has no validation + + // field resourcev1alpha3.ResourcePoolStatusRequest.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1alpha3.ResourcePoolStatusRequestSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ResourcePoolStatusRequestSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequest) *resourcev1alpha3.ResourcePoolStatusRequestSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field resourcev1alpha3.ResourcePoolStatusRequest.Status + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1alpha3.ResourcePoolStatusRequestStatus, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ResourcePoolStatusRequestStatus(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("status"), obj.Status, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequest) *resourcev1alpha3.ResourcePoolStatusRequestStatus { + return oldObj.Status + }), oldObj != nil)...) + + return errs +} + +// Validate_ResourcePoolStatusRequestSpec validates an instance of ResourcePoolStatusRequestSpec according +// to declarative validation rules in the API schema. +func Validate_ResourcePoolStatusRequestSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.ResourcePoolStatusRequestSpec) (errs field.ErrorList) { + // field resourcev1alpha3.ResourcePoolStatusRequestSpec.Driver + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequestSpec) *string { return &oldObj.Driver }), oldObj != nil)...) + + // field resourcev1alpha3.ResourcePoolStatusRequestSpec.PoolName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("poolName"), obj.PoolName, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequestSpec) *string { return oldObj.PoolName }), oldObj != nil)...) + + // field resourcev1alpha3.ResourcePoolStatusRequestSpec.Limit + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1000)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("limit"), obj.Limit, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequestSpec) *int32 { return oldObj.Limit }), oldObj != nil)...) + + return errs +} + +// Validate_ResourcePoolStatusRequestStatus validates an instance of ResourcePoolStatusRequestStatus according +// to declarative validation rules in the API schema. +func Validate_ResourcePoolStatusRequestStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1alpha3.ResourcePoolStatusRequestStatus) (errs field.ErrorList) { + // field resourcev1alpha3.ResourcePoolStatusRequestStatus.PoolCount + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("poolCount"), obj.PoolCount, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequestStatus) *int32 { return oldObj.PoolCount }), oldObj != nil)...) + + // field resourcev1alpha3.ResourcePoolStatusRequestStatus.Pools + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1alpha3.PoolStatus, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 1000); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PoolStatus)...) + return + }(fldPath.Child("pools"), obj.Pools, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequestStatus) []resourcev1alpha3.PoolStatus { + return oldObj.Pools + }), oldObj != nil)...) + + // field resourcev1alpha3.ResourcePoolStatusRequestStatus.Conditions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []v1.Condition, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a v1.Condition, b v1.Condition) bool { return a.Type == b.Type })...) + return + }(fldPath.Child("conditions"), obj.Conditions, safe.Field(oldObj, func(oldObj *resourcev1alpha3.ResourcePoolStatusRequestStatus) []v1.Condition { + return oldObj.Conditions + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/resource/v1beta1/conversion.go b/pkg/apis/resource/v1beta1/conversion.go index 73519860675a2..6d2c970d8c4b6 100644 --- a/pkg/apis/resource/v1beta1/conversion.go +++ b/pkg/apis/resource/v1beta1/conversion.go @@ -220,6 +220,19 @@ func Convert_v1beta1_Device_To_resource_Device(in *resourcev1beta1.Device, out * out.BindingConditions = basic.BindingConditions out.BindingFailureConditions = basic.BindingFailureConditions out.AllowMultipleAllocations = in.Basic.AllowMultipleAllocations + + if basic.NodeAllocatableResourceMappings != nil { + out.NodeAllocatableResourceMappings = make(map[corev1.ResourceName]resource.NodeAllocatableResourceMapping) + for key, value := range basic.NodeAllocatableResourceMappings { + var outVal resource.NodeAllocatableResourceMapping + if err := autoConvert_v1beta1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(&value, &outVal, s); err != nil { + return err + } + out.NodeAllocatableResourceMappings[key] = outVal + } + } else { + out.NodeAllocatableResourceMappings = nil + } } return nil } @@ -269,6 +282,18 @@ func Convert_resource_Device_To_v1beta1_Device(in *resource.Device, out *resourc out.Basic.BindingConditions = in.BindingConditions out.Basic.BindingFailureConditions = in.BindingFailureConditions out.Basic.AllowMultipleAllocations = in.AllowMultipleAllocations + if in.NodeAllocatableResourceMappings != nil { + out.Basic.NodeAllocatableResourceMappings = make(map[corev1.ResourceName]resourcev1beta1.NodeAllocatableResourceMapping) + for key, value := range in.NodeAllocatableResourceMappings { + var outVal resourcev1beta1.NodeAllocatableResourceMapping + if err := autoConvert_resource_NodeAllocatableResourceMapping_To_v1beta1_NodeAllocatableResourceMapping(&value, &outVal, s); err != nil { + return err + } + out.Basic.NodeAllocatableResourceMappings[key] = outVal + } + } else { + out.Basic.NodeAllocatableResourceMappings = nil + } return nil } diff --git a/pkg/apis/resource/v1beta1/conversion_test.go b/pkg/apis/resource/v1beta1/conversion_test.go index 092243756048b..c027b0bc754b6 100644 --- a/pkg/apis/resource/v1beta1/conversion_test.go +++ b/pkg/apis/resource/v1beta1/conversion_test.go @@ -22,13 +22,17 @@ import ( "testing" "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" resourcev1beta1 "k8s.io/api/resource/v1beta1" apiresource "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/apis/resource" + "k8s.io/utils/ptr" ) func TestConversion(t *testing.T) { + nodeName := "test-node" testcases := []struct { name string in runtime.Object @@ -317,6 +321,112 @@ func TestConversion(t *testing.T) { }, }, }, + { + name: "ResourceSlice v1beta1 to internal with node allocatable resource mappings", + in: &resourcev1beta1.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{Name: "test-slice"}, + Spec: resourcev1beta1.ResourceSliceSpec{ + NodeName: nodeName, + Driver: "test-driver", + Pool: resourcev1beta1.ResourcePool{ + Name: "test-pool", + ResourceSliceCount: 1, + }, + Devices: []resourcev1beta1.Device{ + { + Name: "test-device", + Basic: &resourcev1beta1.BasicDevice{ + Attributes: map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute{ + "cpu_per_instance": {IntValue: ptr.To[int64](2)}, + }, + NodeAllocatableResourceMappings: map[v1.ResourceName]resourcev1beta1.NodeAllocatableResourceMapping{ + "cpu": { + AllocationMultiplier: ptr.To(apiresource.MustParse("1")), + }, + }, + }, + }, + }, + }, + }, + out: &resource.ResourceSlice{}, + expectOut: &resource.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{Name: "test-slice"}, + Spec: resource.ResourceSliceSpec{ + NodeName: &nodeName, + Driver: "test-driver", + Pool: resource.ResourcePool{ + Name: "test-pool", + ResourceSliceCount: 1, + }, + Devices: []resource.Device{ + { + Name: "test-device", + Attributes: map[resource.QualifiedName]resource.DeviceAttribute{ + "cpu_per_instance": {IntValue: ptr.To[int64](2)}, + }, + NodeAllocatableResourceMappings: map[v1.ResourceName]resource.NodeAllocatableResourceMapping{ + "cpu": { + AllocationMultiplier: ptr.To(apiresource.MustParse("1")), + }, + }}, + }, + }, + }, + }, + { + name: "ResourceSlice internal to v1beta1 with node allocatable resource mappings", + in: &resource.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{Name: "test-slice"}, + Spec: resource.ResourceSliceSpec{ + NodeName: &nodeName, + Driver: "test-driver", + Pool: resource.ResourcePool{ + Name: "test-pool", + ResourceSliceCount: 1, + }, + Devices: []resource.Device{ + { + Name: "test-device", + Attributes: map[resource.QualifiedName]resource.DeviceAttribute{ + "cpu_per_instance": {IntValue: ptr.To[int64](2)}, + }, + NodeAllocatableResourceMappings: map[v1.ResourceName]resource.NodeAllocatableResourceMapping{ + "cpu": { + AllocationMultiplier: ptr.To(apiresource.MustParse("1")), + }, + }}, + }, + }, + }, + out: &resourcev1beta1.ResourceSlice{}, + expectOut: &resourcev1beta1.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{Name: "test-slice"}, + Spec: resourcev1beta1.ResourceSliceSpec{ + NodeName: nodeName, + Driver: "test-driver", + Pool: resourcev1beta1.ResourcePool{ + Name: "test-pool", + ResourceSliceCount: 1, + }, + Devices: []resourcev1beta1.Device{ + { + Name: "test-device", + Basic: &resourcev1beta1.BasicDevice{ + Attributes: map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute{ + "cpu_per_instance": {IntValue: ptr.To[int64](2)}, + }, + NodeAllocatableResourceMappings: map[v1.ResourceName]resourcev1beta1.NodeAllocatableResourceMapping{ + "cpu": { + AllocationMultiplier: ptr.To(apiresource.MustParse("1")), + }, + }, + }, + }, + }, + }, + }, + }, } scheme := runtime.NewScheme() @@ -350,5 +460,4 @@ func TestConversion(t *testing.T) { } }) } - } diff --git a/pkg/apis/resource/v1beta1/defaults.go b/pkg/apis/resource/v1beta1/defaults.go index 7d43132522d5e..9919f413364bc 100644 --- a/pkg/apis/resource/v1beta1/defaults.go +++ b/pkg/apis/resource/v1beta1/defaults.go @@ -20,6 +20,7 @@ import ( "time" resourceapi "k8s.io/api/resource/v1beta1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -63,3 +64,14 @@ func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) { obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)} } } + +func SetDefaults_BasicDevice(obj *resourceapi.BasicDevice) { + // TODO: fix defaulter-gen not finding SetDefaults func on map value + for k, m := range obj.NodeAllocatableResourceMappings { + if m.AllocationMultiplier == nil { + q := resource.MustParse("1") + m.AllocationMultiplier = &q + obj.NodeAllocatableResourceMappings[k] = m + } + } +} diff --git a/pkg/apis/resource/v1beta1/zz_generated.conversion.go b/pkg/apis/resource/v1beta1/zz_generated.conversion.go index 852282ac30372..a560b729762d8 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/resource/v1beta1/zz_generated.conversion.go @@ -312,6 +312,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1beta1.NodeAllocatableResourceMapping)(nil), (*resource.NodeAllocatableResourceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(a.(*resourcev1beta1.NodeAllocatableResourceMapping), b.(*resource.NodeAllocatableResourceMapping), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.NodeAllocatableResourceMapping)(nil), (*resourcev1beta1.NodeAllocatableResourceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_NodeAllocatableResourceMapping_To_v1beta1_NodeAllocatableResourceMapping(a.(*resource.NodeAllocatableResourceMapping), b.(*resourcev1beta1.NodeAllocatableResourceMapping), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*resourcev1beta1.OpaqueDeviceConfiguration)(nil), (*resource.OpaqueDeviceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_OpaqueDeviceConfiguration_To_resource_OpaqueDeviceConfiguration(a.(*resourcev1beta1.OpaqueDeviceConfiguration), b.(*resource.OpaqueDeviceConfiguration), scope) }); err != nil { @@ -674,6 +684,7 @@ func autoConvert_resource_Device_To_v1beta1_Device(in *resource.Device, out *res // WARNING: in.BindingConditions requires manual conversion: does not exist in peer-type // WARNING: in.BindingFailureConditions requires manual conversion: does not exist in peer-type // WARNING: in.AllowMultipleAllocations requires manual conversion: does not exist in peer-type + // WARNING: in.NodeAllocatableResourceMappings requires manual conversion: does not exist in peer-type return nil } @@ -732,6 +743,10 @@ func autoConvert_v1beta1_DeviceAttribute_To_resource_DeviceAttribute(in *resourc out.BoolValue = (*bool)(unsafe.Pointer(in.BoolValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) + out.IntValues = *(*[]int64)(unsafe.Pointer(&in.IntValues)) + out.BoolValues = *(*[]bool)(unsafe.Pointer(&in.BoolValues)) + out.StringValues = *(*[]string)(unsafe.Pointer(&in.StringValues)) + out.VersionValues = *(*[]string)(unsafe.Pointer(&in.VersionValues)) return nil } @@ -745,6 +760,10 @@ func autoConvert_resource_DeviceAttribute_To_v1beta1_DeviceAttribute(in *resourc out.BoolValue = (*bool)(unsafe.Pointer(in.BoolValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) + out.IntValues = *(*[]int64)(unsafe.Pointer(&in.IntValues)) + out.BoolValues = *(*[]bool)(unsafe.Pointer(&in.BoolValues)) + out.StringValues = *(*[]string)(unsafe.Pointer(&in.StringValues)) + out.VersionValues = *(*[]string)(unsafe.Pointer(&in.VersionValues)) return nil } @@ -1195,6 +1214,28 @@ func Convert_resource_NetworkDeviceData_To_v1beta1_NetworkDeviceData(in *resourc return autoConvert_resource_NetworkDeviceData_To_v1beta1_NetworkDeviceData(in, out, s) } +func autoConvert_v1beta1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in *resourcev1beta1.NodeAllocatableResourceMapping, out *resource.NodeAllocatableResourceMapping, s conversion.Scope) error { + out.CapacityKey = (*resource.QualifiedName)(unsafe.Pointer(in.CapacityKey)) + out.AllocationMultiplier = (*apiresource.Quantity)(unsafe.Pointer(in.AllocationMultiplier)) + return nil +} + +// Convert_v1beta1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping is an autogenerated conversion function. +func Convert_v1beta1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in *resourcev1beta1.NodeAllocatableResourceMapping, out *resource.NodeAllocatableResourceMapping, s conversion.Scope) error { + return autoConvert_v1beta1_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in, out, s) +} + +func autoConvert_resource_NodeAllocatableResourceMapping_To_v1beta1_NodeAllocatableResourceMapping(in *resource.NodeAllocatableResourceMapping, out *resourcev1beta1.NodeAllocatableResourceMapping, s conversion.Scope) error { + out.CapacityKey = (*resourcev1beta1.QualifiedName)(unsafe.Pointer(in.CapacityKey)) + out.AllocationMultiplier = (*apiresource.Quantity)(unsafe.Pointer(in.AllocationMultiplier)) + return nil +} + +// Convert_resource_NodeAllocatableResourceMapping_To_v1beta1_NodeAllocatableResourceMapping is an autogenerated conversion function. +func Convert_resource_NodeAllocatableResourceMapping_To_v1beta1_NodeAllocatableResourceMapping(in *resource.NodeAllocatableResourceMapping, out *resourcev1beta1.NodeAllocatableResourceMapping, s conversion.Scope) error { + return autoConvert_resource_NodeAllocatableResourceMapping_To_v1beta1_NodeAllocatableResourceMapping(in, out, s) +} + func autoConvert_v1beta1_OpaqueDeviceConfiguration_To_resource_OpaqueDeviceConfiguration(in *resourcev1beta1.OpaqueDeviceConfiguration, out *resource.OpaqueDeviceConfiguration, s conversion.Scope) error { out.Driver = in.Driver out.Parameters = in.Parameters diff --git a/pkg/apis/resource/v1beta1/zz_generated.defaults.go b/pkg/apis/resource/v1beta1/zz_generated.defaults.go index 3323ca47a2b86..bac45a259569d 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/resource/v1beta1/zz_generated.defaults.go @@ -118,6 +118,7 @@ func SetObjectDefaults_ResourceSlice(in *resourcev1beta1.ResourceSlice) { for i := range in.Spec.Devices { a := &in.Spec.Devices[i] if a.Basic != nil { + SetDefaults_BasicDevice(a.Basic) for j := range a.Basic.Taints { b := &a.Basic.Taints[j] SetDefaults_DeviceTaint(b) diff --git a/pkg/apis/resource/v1beta1/zz_generated.validations.go b/pkg/apis/resource/v1beta1/zz_generated.validations.go index f96f7aeaed4b4..56f0ebb9de03d 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta1/zz_generated.validations.go @@ -50,14 +50,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type DeviceClassList - scheme.AddValidationFunc((*resourcev1beta1.DeviceClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_DeviceClassList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.DeviceClassList), safe.Cast[*resourcev1beta1.DeviceClassList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceClaim scheme.AddValidationFunc((*resourcev1beta1.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -66,14 +58,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceClaimList - scheme.AddValidationFunc((*resourcev1beta1.ResourceClaimList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceClaimList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.ResourceClaimList), safe.Cast[*resourcev1beta1.ResourceClaimList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceClaimTemplate scheme.AddValidationFunc((*resourcev1beta1.ResourceClaimTemplate)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -82,14 +66,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceClaimTemplateList - scheme.AddValidationFunc((*resourcev1beta1.ResourceClaimTemplateList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceClaimTemplateList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.ResourceClaimTemplateList), safe.Cast[*resourcev1beta1.ResourceClaimTemplateList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceSlice scheme.AddValidationFunc((*resourcev1beta1.ResourceSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -98,14 +74,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceSliceList - scheme.AddValidationFunc((*resourcev1beta1.ResourceSliceList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceSliceList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.ResourceSliceList), safe.Cast[*resourcev1beta1.ResourceSliceList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } @@ -125,13 +93,13 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation, } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("shareID"), obj.ShareID, safe.Field(oldObj, func(oldObj *resourcev1beta1.AllocatedDeviceStatus) *string { return oldObj.ShareID }), oldObj != nil)...) @@ -147,7 +115,7 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation, } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -168,7 +136,7 @@ var symbolsForAllocationConfigSource = sets.New(resourcev1beta1.AllocationConfig // Validate_AllocationConfigSource validates an instance of AllocationConfigSource according // to declarative validation rules in the API schema. func Validate_AllocationConfigSource(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.AllocationConfigSource) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil).MarkAlpha()...) return errs } @@ -205,6 +173,14 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the map and call the value type's validation function errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) return @@ -223,11 +199,11 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -236,7 +212,7 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceCounterConsumption, b resourcev1beta1.DeviceCounterConsumption) bool { return a.CounterSet == b.CounterSet - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceCounterConsumption, b resourcev1beta1.DeviceCounterConsumption) bool { return a.CounterSet == b.CounterSet @@ -257,6 +233,14 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceTaint)...) return @@ -273,11 +257,11 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -295,11 +279,11 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -309,6 +293,7 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath * }(fldPath.Child("bindingFailureConditions"), obj.BindingFailureConditions, safe.Field(oldObj, func(oldObj *resourcev1beta1.BasicDevice) []string { return oldObj.BindingFailureConditions }), oldObj != nil)...) // field resourcev1beta1.BasicDevice.AllowMultipleAllocations has no validation + // field resourcev1beta1.BasicDevice.NodeAllocatableResourceMappings has no validation return errs } @@ -324,18 +309,37 @@ func Validate_CounterSet(ctx context.Context, op operation.Operation, fldPath *f } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *resourcev1beta1.CounterSet) *string { return &oldObj.Name }), oldObj != nil)...) - // field resourcev1beta1.CounterSet.Counters has no validation + // field resourcev1beta1.CounterSet.Counters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta1.Counter, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName).MarkAlpha()...) + return + }(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta1.CounterSet) map[string]resourcev1beta1.Counter { return oldObj.Counters }), oldObj != nil)...) + return errs } @@ -351,6 +355,14 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_BasicDevice(ctx, op, fldPath, obj, oldObj)...) return @@ -371,7 +383,7 @@ func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -394,18 +406,18 @@ func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAllocationConfiguration) []string { return oldObj.Requests }), oldObj != nil)...) @@ -431,7 +443,7 @@ var symbolsForDeviceAllocationMode = sets.New(resourcev1beta1.DeviceAllocationMo // Validate_DeviceAllocationMode validates an instance of DeviceAllocationMode according // to declarative validation rules in the API schema. func Validate_DeviceAllocationMode(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceAllocationMode) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceAllocationMode, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceAllocationMode, nil).MarkAlpha()...) return errs } @@ -448,11 +460,11 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -474,11 +486,11 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 64); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 64).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -494,7 +506,7 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } -var unionMembershipFor_k8s_io_api_resource_v1beta1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) +var unionMembershipFor_k8s_io_api_resource_v1beta1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version"), validate.NewUnionMember("ints"), validate.NewUnionMember("bools"), validate.NewUnionMember("strings"), validate.NewUnionMember("versions")) // Validate_DeviceAttribute validates an instance of DeviceAttribute according // to declarative validation rules in the API schema. @@ -519,7 +531,27 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa return false } return obj.VersionValue != nil - })...) + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.IntValues) != 0 + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.BoolValues) != 0 + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.StringValues) != 0 + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.VersionValues) != 0 + }).MarkAlpha()...) // field resourcev1beta1.DeviceAttribute.IntValue errs = append(errs, @@ -530,7 +562,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -548,7 +580,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -566,7 +598,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -584,7 +616,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -593,6 +625,78 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa return }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *string { return oldObj.VersionValue }), oldObj != nil)...) + // field resourcev1beta1.DeviceAttribute.IntValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("ints"), obj.IntValues, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) []int64 { return oldObj.IntValues }), oldObj != nil)...) + + // field resourcev1beta1.DeviceAttribute.BoolValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []bool, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bools"), obj.BoolValues, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) []bool { return oldObj.BoolValues }), oldObj != nil)...) + + // field resourcev1beta1.DeviceAttribute.StringValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("strings"), obj.StringValues, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) []string { return oldObj.StringValues }), oldObj != nil)...) + + // field resourcev1beta1.DeviceAttribute.VersionValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("versions"), obj.VersionValues, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) []string { return oldObj.VersionValues }), oldObj != nil)...) + return errs } @@ -608,18 +712,18 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceRequest, b resourcev1beta1.DeviceRequest) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceRequest, b resourcev1beta1.DeviceRequest) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceRequest, b resourcev1beta1.DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest)...) return @@ -634,11 +738,11 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -660,11 +764,11 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -692,18 +796,18 @@ func Validate_DeviceClaimConfiguration(ctx context.Context, op operation.Operati } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClaimConfiguration) []string { return oldObj.Requests }), oldObj != nil)...) @@ -739,13 +843,13 @@ func Validate_DeviceClass(ctx context.Context, op operation.Operation, fldPath * // call field-attached validations func() { // cohort name earlyReturn := false - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue); len(e) != 0 { + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName)...) + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName).MarkAlpha()...) }() return }(fldPath.Child("metadata"), &obj.ObjectMeta, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClass) *v1.ObjectMeta { return &oldObj.ObjectMeta }), oldObj != nil)...) @@ -785,27 +889,6 @@ func Validate_DeviceClassConfiguration(ctx context.Context, op operation.Operati return errs } -// Validate_DeviceClassList validates an instance of DeviceClassList according -// to declarative validation rules in the API schema. -func Validate_DeviceClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClassList) (errs field.ErrorList) { - // field resourcev1beta1.DeviceClassList.TypeMeta has no validation - // field resourcev1beta1.DeviceClassList.ListMeta has no validation - - // field resourcev1beta1.DeviceClassList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceClass, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceClass)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClassList) []resourcev1beta1.DeviceClass { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_DeviceClassSpec validates an instance of DeviceClassSpec according // to declarative validation rules in the API schema. func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClassSpec) (errs field.ErrorList) { @@ -818,11 +901,11 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -842,11 +925,11 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -868,13 +951,13 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("extendedResourceName"), obj.ExtendedResourceName, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClassSpec) *string { return oldObj.ExtendedResourceName }), oldObj != nil)...) @@ -893,7 +976,7 @@ func Validate_DeviceConfiguration(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -921,18 +1004,18 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceConstraint) []string { return oldObj.Requests }), oldObj != nil)...) @@ -945,13 +1028,13 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("matchAttribute"), obj.MatchAttribute, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceConstraint) *resourcev1beta1.FullyQualifiedName { return oldObj.MatchAttribute @@ -973,18 +1056,39 @@ func Validate_DeviceCounterConsumption(ctx context.Context, op operation.Operati } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("counterSet"), &obj.CounterSet, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceCounterConsumption) *string { return &oldObj.CounterSet }), oldObj != nil)...) - // field resourcev1beta1.DeviceCounterConsumption.Counters has no validation + // field resourcev1beta1.DeviceCounterConsumption.Counters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta1.Counter, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName).MarkAlpha()...) + return + }(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceCounterConsumption) map[string]resourcev1beta1.Counter { + return oldObj.Counters + }), oldObj != nil)...) + return errs } @@ -1003,11 +1107,11 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1025,7 +1129,7 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1050,11 +1154,11 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1063,7 +1167,7 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceSubRequest, b resourcev1beta1.DeviceSubRequest) bool { return a.Name == b.Name - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.DeviceSubRequest, b resourcev1beta1.DeviceSubRequest) bool { return a.Name == b.Name @@ -1080,6 +1184,14 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1105,14 +1217,15 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) return }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequestAllocationResult) *string { return &oldObj.Driver }), oldObj != nil)...) @@ -1125,14 +1238,14 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("pool"), &obj.Pool, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequestAllocationResult) *string { return &oldObj.Pool }), oldObj != nil)...) @@ -1148,11 +1261,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1174,11 +1287,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1196,11 +1309,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1220,13 +1333,13 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("shareID"), obj.ShareID, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequestAllocationResult) *types.UID { return oldObj.ShareID }), oldObj != nil)...) @@ -1248,14 +1361,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("deviceClassName"), &obj.DeviceClassName, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceSubRequest) *string { return &oldObj.DeviceClassName }), oldObj != nil)...) @@ -1268,10 +1381,13 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } if earlyReturn { return // do not proceed } @@ -1287,6 +1403,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceAllocationMode(ctx, op, fldPath, obj, oldObj)...) return @@ -1303,6 +1427,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1329,7 +1461,7 @@ func Validate_DeviceTaint(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1350,7 +1482,7 @@ var symbolsForDeviceTaintEffect = sets.New(resourcev1beta1.DeviceTaintEffectNoEx // Validate_DeviceTaintEffect validates an instance of DeviceTaintEffect according // to declarative validation rules in the API schema. func Validate_DeviceTaintEffect(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceTaintEffect) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil).MarkAlpha()...) return errs } @@ -1367,13 +1499,13 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("key"), &obj.Key, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceToleration) *string { return &oldObj.Key }), oldObj != nil)...) @@ -1384,6 +1516,16 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceTolerationOperator(ctx, op, fldPath, obj, oldObj)...) return @@ -1400,6 +1542,14 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceTaintEffect(ctx, op, fldPath, obj, oldObj)...) return @@ -1416,7 +1566,7 @@ var symbolsForDeviceTolerationOperator = sets.New(resourcev1beta1.DeviceTolerati // Validate_DeviceTolerationOperator validates an instance of DeviceTolerationOperator according // to declarative validation rules in the API schema. func Validate_DeviceTolerationOperator(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceTolerationOperator) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTolerationOperator, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTolerationOperator, nil).MarkAlpha()...) return errs } @@ -1433,13 +1583,13 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 256)...) + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 256).MarkAlpha()...) return }(fldPath.Child("interfaceName"), &obj.InterfaceName, safe.Field(oldObj, func(oldObj *resourcev1beta1.NetworkDeviceData) *string { return &oldObj.InterfaceName }), oldObj != nil)...) @@ -1452,18 +1602,18 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("ips"), obj.IPs, safe.Field(oldObj, func(oldObj *resourcev1beta1.NetworkDeviceData) []string { return oldObj.IPs }), oldObj != nil)...) @@ -1476,13 +1626,13 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 128)...) + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 128).MarkAlpha()...) return }(fldPath.Child("hardwareAddress"), &obj.HardwareAddress, safe.Field(oldObj, func(oldObj *resourcev1beta1.NetworkDeviceData) *string { return &oldObj.HardwareAddress }), oldObj != nil)...) @@ -1501,14 +1651,15 @@ func Validate_OpaqueDeviceConfiguration(ctx context.Context, op operation.Operat } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1beta1.OpaqueDeviceConfiguration) *string { return &oldObj.Driver }), oldObj != nil)...) @@ -1531,7 +1682,7 @@ func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1560,27 +1711,6 @@ func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath return errs } -// Validate_ResourceClaimList validates an instance of ResourceClaimList according -// to declarative validation rules in the API schema. -func Validate_ResourceClaimList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimList) (errs field.ErrorList) { - // field resourcev1beta1.ResourceClaimList.TypeMeta has no validation - // field resourcev1beta1.ResourceClaimList.ListMeta has no validation - - // field resourcev1beta1.ResourceClaimList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta1.ResourceClaim, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaim)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimList) []resourcev1beta1.ResourceClaim { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ResourceClaimSpec validates an instance of ResourceClaimSpec according // to declarative validation rules in the API schema. func Validate_ResourceClaimSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimSpec) (errs field.ErrorList) { @@ -1611,10 +1741,10 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify); len(e) != 0 { + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1637,11 +1767,11 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 256); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 256).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1650,7 +1780,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.ResourceClaimConsumerReference, b resourcev1beta1.ResourceClaimConsumerReference) bool { return a.UID == b.UID - })...) + }).MarkAlpha()...) return }(fldPath.Child("reservedFor"), obj.ReservedFor, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimStatus) []resourcev1beta1.ResourceClaimConsumerReference { return oldObj.ReservedFor @@ -1665,7 +1795,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1674,7 +1804,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.AllocatedDeviceStatus, b resourcev1beta1.AllocatedDeviceStatus) bool { return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID)) - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.AllocatedDeviceStatus, b resourcev1beta1.AllocatedDeviceStatus) bool { return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID)) @@ -1710,29 +1840,6 @@ func Validate_ResourceClaimTemplate(ctx context.Context, op operation.Operation, return errs } -// Validate_ResourceClaimTemplateList validates an instance of ResourceClaimTemplateList according -// to declarative validation rules in the API schema. -func Validate_ResourceClaimTemplateList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimTemplateList) (errs field.ErrorList) { - // field resourcev1beta1.ResourceClaimTemplateList.TypeMeta has no validation - // field resourcev1beta1.ResourceClaimTemplateList.ListMeta has no validation - - // field resourcev1beta1.ResourceClaimTemplateList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta1.ResourceClaimTemplate, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaimTemplate)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimTemplateList) []resourcev1beta1.ResourceClaimTemplate { - return oldObj.Items - }), oldObj != nil)...) - - return errs -} - // Validate_ResourceClaimTemplateSpec validates an instance of ResourceClaimTemplateSpec according // to declarative validation rules in the API schema. func Validate_ResourceClaimTemplateSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimTemplateSpec) (errs field.ErrorList) { @@ -1776,27 +1883,6 @@ func Validate_ResourceSlice(ctx context.Context, op operation.Operation, fldPath return errs } -// Validate_ResourceSliceList validates an instance of ResourceSliceList according -// to declarative validation rules in the API schema. -func Validate_ResourceSliceList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceSliceList) (errs field.ErrorList) { - // field resourcev1beta1.ResourceSliceList.TypeMeta has no validation - // field resourcev1beta1.ResourceSliceList.ListMeta has no validation - - // field resourcev1beta1.ResourceSliceList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta1.ResourceSlice, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceSlice)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceSliceList) []resourcev1beta1.ResourceSlice { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ResourceSliceSpec validates an instance of ResourceSliceSpec according // to declarative validation rules in the API schema. func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceSliceSpec) (errs field.ErrorList) { @@ -1815,7 +1901,7 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1837,18 +1923,18 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.CounterSet, b resourcev1beta1.CounterSet) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.CounterSet, b resourcev1beta1.CounterSet) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta1.CounterSet, b resourcev1beta1.CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet)...) return diff --git a/pkg/apis/resource/v1beta2/defaults.go b/pkg/apis/resource/v1beta2/defaults.go index 4fb093567aec3..bd036d96bcb9b 100644 --- a/pkg/apis/resource/v1beta2/defaults.go +++ b/pkg/apis/resource/v1beta2/defaults.go @@ -20,6 +20,7 @@ import ( "time" resourceapi "k8s.io/api/resource/v1beta2" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -53,3 +54,14 @@ func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) { obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)} } } + +func SetDefaults_Device(obj *resourceapi.Device) { + // TODO: fix defaulter-gen not finding SetDefaults func on map value + for k, m := range obj.NodeAllocatableResourceMappings { + if m.AllocationMultiplier == nil { + q := resource.MustParse("1") + m.AllocationMultiplier = &q + obj.NodeAllocatableResourceMappings[k] = m + } + } +} diff --git a/pkg/apis/resource/v1beta2/zz_generated.conversion.go b/pkg/apis/resource/v1beta2/zz_generated.conversion.go index bebeb1f618a01..272952a54e70b 100644 --- a/pkg/apis/resource/v1beta2/zz_generated.conversion.go +++ b/pkg/apis/resource/v1beta2/zz_generated.conversion.go @@ -312,6 +312,56 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1beta2.DeviceTaintRule)(nil), (*resource.DeviceTaintRule)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_DeviceTaintRule_To_resource_DeviceTaintRule(a.(*resourcev1beta2.DeviceTaintRule), b.(*resource.DeviceTaintRule), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRule)(nil), (*resourcev1beta2.DeviceTaintRule)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceTaintRule_To_v1beta2_DeviceTaintRule(a.(*resource.DeviceTaintRule), b.(*resourcev1beta2.DeviceTaintRule), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1beta2.DeviceTaintRuleList)(nil), (*resource.DeviceTaintRuleList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(a.(*resourcev1beta2.DeviceTaintRuleList), b.(*resource.DeviceTaintRuleList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRuleList)(nil), (*resourcev1beta2.DeviceTaintRuleList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceTaintRuleList_To_v1beta2_DeviceTaintRuleList(a.(*resource.DeviceTaintRuleList), b.(*resourcev1beta2.DeviceTaintRuleList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1beta2.DeviceTaintRuleSpec)(nil), (*resource.DeviceTaintRuleSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(a.(*resourcev1beta2.DeviceTaintRuleSpec), b.(*resource.DeviceTaintRuleSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRuleSpec)(nil), (*resourcev1beta2.DeviceTaintRuleSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceTaintRuleSpec_To_v1beta2_DeviceTaintRuleSpec(a.(*resource.DeviceTaintRuleSpec), b.(*resourcev1beta2.DeviceTaintRuleSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1beta2.DeviceTaintRuleStatus)(nil), (*resource.DeviceTaintRuleStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_DeviceTaintRuleStatus_To_resource_DeviceTaintRuleStatus(a.(*resourcev1beta2.DeviceTaintRuleStatus), b.(*resource.DeviceTaintRuleStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRuleStatus)(nil), (*resourcev1beta2.DeviceTaintRuleStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceTaintRuleStatus_To_v1beta2_DeviceTaintRuleStatus(a.(*resource.DeviceTaintRuleStatus), b.(*resourcev1beta2.DeviceTaintRuleStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resourcev1beta2.DeviceTaintSelector)(nil), (*resource.DeviceTaintSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_DeviceTaintSelector_To_resource_DeviceTaintSelector(a.(*resourcev1beta2.DeviceTaintSelector), b.(*resource.DeviceTaintSelector), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintSelector)(nil), (*resourcev1beta2.DeviceTaintSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceTaintSelector_To_v1beta2_DeviceTaintSelector(a.(*resource.DeviceTaintSelector), b.(*resourcev1beta2.DeviceTaintSelector), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*resourcev1beta2.DeviceToleration)(nil), (*resource.DeviceToleration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_DeviceToleration_To_resource_DeviceToleration(a.(*resourcev1beta2.DeviceToleration), b.(*resource.DeviceToleration), scope) }); err != nil { @@ -342,6 +392,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1beta2.NodeAllocatableResourceMapping)(nil), (*resource.NodeAllocatableResourceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(a.(*resourcev1beta2.NodeAllocatableResourceMapping), b.(*resource.NodeAllocatableResourceMapping), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.NodeAllocatableResourceMapping)(nil), (*resourcev1beta2.NodeAllocatableResourceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_NodeAllocatableResourceMapping_To_v1beta2_NodeAllocatableResourceMapping(a.(*resource.NodeAllocatableResourceMapping), b.(*resourcev1beta2.NodeAllocatableResourceMapping), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*resourcev1beta2.OpaqueDeviceConfiguration)(nil), (*resource.OpaqueDeviceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_OpaqueDeviceConfiguration_To_resource_OpaqueDeviceConfiguration(a.(*resourcev1beta2.OpaqueDeviceConfiguration), b.(*resource.OpaqueDeviceConfiguration), scope) }); err != nil { @@ -678,6 +738,7 @@ func autoConvert_v1beta2_Device_To_resource_Device(in *resourcev1beta2.Device, o out.BindingConditions = *(*[]string)(unsafe.Pointer(&in.BindingConditions)) out.BindingFailureConditions = *(*[]string)(unsafe.Pointer(&in.BindingFailureConditions)) out.AllowMultipleAllocations = (*bool)(unsafe.Pointer(in.AllowMultipleAllocations)) + out.NodeAllocatableResourceMappings = *(*map[corev1.ResourceName]resource.NodeAllocatableResourceMapping)(unsafe.Pointer(&in.NodeAllocatableResourceMappings)) return nil } @@ -699,6 +760,7 @@ func autoConvert_resource_Device_To_v1beta2_Device(in *resource.Device, out *res out.BindingConditions = *(*[]string)(unsafe.Pointer(&in.BindingConditions)) out.BindingFailureConditions = *(*[]string)(unsafe.Pointer(&in.BindingFailureConditions)) out.AllowMultipleAllocations = (*bool)(unsafe.Pointer(in.AllowMultipleAllocations)) + out.NodeAllocatableResourceMappings = *(*map[corev1.ResourceName]resourcev1beta2.NodeAllocatableResourceMapping)(unsafe.Pointer(&in.NodeAllocatableResourceMappings)) return nil } @@ -762,6 +824,10 @@ func autoConvert_v1beta2_DeviceAttribute_To_resource_DeviceAttribute(in *resourc out.BoolValue = (*bool)(unsafe.Pointer(in.BoolValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) + out.IntValues = *(*[]int64)(unsafe.Pointer(&in.IntValues)) + out.BoolValues = *(*[]bool)(unsafe.Pointer(&in.BoolValues)) + out.StringValues = *(*[]string)(unsafe.Pointer(&in.StringValues)) + out.VersionValues = *(*[]string)(unsafe.Pointer(&in.VersionValues)) return nil } @@ -775,6 +841,10 @@ func autoConvert_resource_DeviceAttribute_To_v1beta2_DeviceAttribute(in *resourc out.BoolValue = (*bool)(unsafe.Pointer(in.BoolValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) + out.IntValues = *(*[]int64)(unsafe.Pointer(&in.IntValues)) + out.BoolValues = *(*[]bool)(unsafe.Pointer(&in.BoolValues)) + out.StringValues = *(*[]string)(unsafe.Pointer(&in.StringValues)) + out.VersionValues = *(*[]string)(unsafe.Pointer(&in.VersionValues)) return nil } @@ -1157,6 +1227,130 @@ func Convert_resource_DeviceTaint_To_v1beta2_DeviceTaint(in *resource.DeviceTain return autoConvert_resource_DeviceTaint_To_v1beta2_DeviceTaint(in, out, s) } +func autoConvert_v1beta2_DeviceTaintRule_To_resource_DeviceTaintRule(in *resourcev1beta2.DeviceTaintRule, out *resource.DeviceTaintRule, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta2_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta2_DeviceTaintRuleStatus_To_resource_DeviceTaintRuleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta2_DeviceTaintRule_To_resource_DeviceTaintRule is an autogenerated conversion function. +func Convert_v1beta2_DeviceTaintRule_To_resource_DeviceTaintRule(in *resourcev1beta2.DeviceTaintRule, out *resource.DeviceTaintRule, s conversion.Scope) error { + return autoConvert_v1beta2_DeviceTaintRule_To_resource_DeviceTaintRule(in, out, s) +} + +func autoConvert_resource_DeviceTaintRule_To_v1beta2_DeviceTaintRule(in *resource.DeviceTaintRule, out *resourcev1beta2.DeviceTaintRule, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_resource_DeviceTaintRuleSpec_To_v1beta2_DeviceTaintRuleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_resource_DeviceTaintRuleStatus_To_v1beta2_DeviceTaintRuleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_resource_DeviceTaintRule_To_v1beta2_DeviceTaintRule is an autogenerated conversion function. +func Convert_resource_DeviceTaintRule_To_v1beta2_DeviceTaintRule(in *resource.DeviceTaintRule, out *resourcev1beta2.DeviceTaintRule, s conversion.Scope) error { + return autoConvert_resource_DeviceTaintRule_To_v1beta2_DeviceTaintRule(in, out, s) +} + +func autoConvert_v1beta2_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(in *resourcev1beta2.DeviceTaintRuleList, out *resource.DeviceTaintRuleList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]resource.DeviceTaintRule)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta2_DeviceTaintRuleList_To_resource_DeviceTaintRuleList is an autogenerated conversion function. +func Convert_v1beta2_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(in *resourcev1beta2.DeviceTaintRuleList, out *resource.DeviceTaintRuleList, s conversion.Scope) error { + return autoConvert_v1beta2_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(in, out, s) +} + +func autoConvert_resource_DeviceTaintRuleList_To_v1beta2_DeviceTaintRuleList(in *resource.DeviceTaintRuleList, out *resourcev1beta2.DeviceTaintRuleList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]resourcev1beta2.DeviceTaintRule)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_resource_DeviceTaintRuleList_To_v1beta2_DeviceTaintRuleList is an autogenerated conversion function. +func Convert_resource_DeviceTaintRuleList_To_v1beta2_DeviceTaintRuleList(in *resource.DeviceTaintRuleList, out *resourcev1beta2.DeviceTaintRuleList, s conversion.Scope) error { + return autoConvert_resource_DeviceTaintRuleList_To_v1beta2_DeviceTaintRuleList(in, out, s) +} + +func autoConvert_v1beta2_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(in *resourcev1beta2.DeviceTaintRuleSpec, out *resource.DeviceTaintRuleSpec, s conversion.Scope) error { + out.DeviceSelector = (*resource.DeviceTaintSelector)(unsafe.Pointer(in.DeviceSelector)) + if err := Convert_v1beta2_DeviceTaint_To_resource_DeviceTaint(&in.Taint, &out.Taint, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta2_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec is an autogenerated conversion function. +func Convert_v1beta2_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(in *resourcev1beta2.DeviceTaintRuleSpec, out *resource.DeviceTaintRuleSpec, s conversion.Scope) error { + return autoConvert_v1beta2_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(in, out, s) +} + +func autoConvert_resource_DeviceTaintRuleSpec_To_v1beta2_DeviceTaintRuleSpec(in *resource.DeviceTaintRuleSpec, out *resourcev1beta2.DeviceTaintRuleSpec, s conversion.Scope) error { + out.DeviceSelector = (*resourcev1beta2.DeviceTaintSelector)(unsafe.Pointer(in.DeviceSelector)) + if err := Convert_resource_DeviceTaint_To_v1beta2_DeviceTaint(&in.Taint, &out.Taint, s); err != nil { + return err + } + return nil +} + +// Convert_resource_DeviceTaintRuleSpec_To_v1beta2_DeviceTaintRuleSpec is an autogenerated conversion function. +func Convert_resource_DeviceTaintRuleSpec_To_v1beta2_DeviceTaintRuleSpec(in *resource.DeviceTaintRuleSpec, out *resourcev1beta2.DeviceTaintRuleSpec, s conversion.Scope) error { + return autoConvert_resource_DeviceTaintRuleSpec_To_v1beta2_DeviceTaintRuleSpec(in, out, s) +} + +func autoConvert_v1beta2_DeviceTaintRuleStatus_To_resource_DeviceTaintRuleStatus(in *resourcev1beta2.DeviceTaintRuleStatus, out *resource.DeviceTaintRuleStatus, s conversion.Scope) error { + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1beta2_DeviceTaintRuleStatus_To_resource_DeviceTaintRuleStatus is an autogenerated conversion function. +func Convert_v1beta2_DeviceTaintRuleStatus_To_resource_DeviceTaintRuleStatus(in *resourcev1beta2.DeviceTaintRuleStatus, out *resource.DeviceTaintRuleStatus, s conversion.Scope) error { + return autoConvert_v1beta2_DeviceTaintRuleStatus_To_resource_DeviceTaintRuleStatus(in, out, s) +} + +func autoConvert_resource_DeviceTaintRuleStatus_To_v1beta2_DeviceTaintRuleStatus(in *resource.DeviceTaintRuleStatus, out *resourcev1beta2.DeviceTaintRuleStatus, s conversion.Scope) error { + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_resource_DeviceTaintRuleStatus_To_v1beta2_DeviceTaintRuleStatus is an autogenerated conversion function. +func Convert_resource_DeviceTaintRuleStatus_To_v1beta2_DeviceTaintRuleStatus(in *resource.DeviceTaintRuleStatus, out *resourcev1beta2.DeviceTaintRuleStatus, s conversion.Scope) error { + return autoConvert_resource_DeviceTaintRuleStatus_To_v1beta2_DeviceTaintRuleStatus(in, out, s) +} + +func autoConvert_v1beta2_DeviceTaintSelector_To_resource_DeviceTaintSelector(in *resourcev1beta2.DeviceTaintSelector, out *resource.DeviceTaintSelector, s conversion.Scope) error { + out.Driver = (*string)(unsafe.Pointer(in.Driver)) + out.Pool = (*string)(unsafe.Pointer(in.Pool)) + out.Device = (*string)(unsafe.Pointer(in.Device)) + return nil +} + +// Convert_v1beta2_DeviceTaintSelector_To_resource_DeviceTaintSelector is an autogenerated conversion function. +func Convert_v1beta2_DeviceTaintSelector_To_resource_DeviceTaintSelector(in *resourcev1beta2.DeviceTaintSelector, out *resource.DeviceTaintSelector, s conversion.Scope) error { + return autoConvert_v1beta2_DeviceTaintSelector_To_resource_DeviceTaintSelector(in, out, s) +} + +func autoConvert_resource_DeviceTaintSelector_To_v1beta2_DeviceTaintSelector(in *resource.DeviceTaintSelector, out *resourcev1beta2.DeviceTaintSelector, s conversion.Scope) error { + out.Driver = (*string)(unsafe.Pointer(in.Driver)) + out.Pool = (*string)(unsafe.Pointer(in.Pool)) + out.Device = (*string)(unsafe.Pointer(in.Device)) + return nil +} + +// Convert_resource_DeviceTaintSelector_To_v1beta2_DeviceTaintSelector is an autogenerated conversion function. +func Convert_resource_DeviceTaintSelector_To_v1beta2_DeviceTaintSelector(in *resource.DeviceTaintSelector, out *resourcev1beta2.DeviceTaintSelector, s conversion.Scope) error { + return autoConvert_resource_DeviceTaintSelector_To_v1beta2_DeviceTaintSelector(in, out, s) +} + func autoConvert_v1beta2_DeviceToleration_To_resource_DeviceToleration(in *resourcev1beta2.DeviceToleration, out *resource.DeviceToleration, s conversion.Scope) error { out.Key = in.Key out.Operator = resource.DeviceTolerationOperator(in.Operator) @@ -1241,6 +1435,28 @@ func Convert_resource_NetworkDeviceData_To_v1beta2_NetworkDeviceData(in *resourc return autoConvert_resource_NetworkDeviceData_To_v1beta2_NetworkDeviceData(in, out, s) } +func autoConvert_v1beta2_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in *resourcev1beta2.NodeAllocatableResourceMapping, out *resource.NodeAllocatableResourceMapping, s conversion.Scope) error { + out.CapacityKey = (*resource.QualifiedName)(unsafe.Pointer(in.CapacityKey)) + out.AllocationMultiplier = (*apiresource.Quantity)(unsafe.Pointer(in.AllocationMultiplier)) + return nil +} + +// Convert_v1beta2_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping is an autogenerated conversion function. +func Convert_v1beta2_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in *resourcev1beta2.NodeAllocatableResourceMapping, out *resource.NodeAllocatableResourceMapping, s conversion.Scope) error { + return autoConvert_v1beta2_NodeAllocatableResourceMapping_To_resource_NodeAllocatableResourceMapping(in, out, s) +} + +func autoConvert_resource_NodeAllocatableResourceMapping_To_v1beta2_NodeAllocatableResourceMapping(in *resource.NodeAllocatableResourceMapping, out *resourcev1beta2.NodeAllocatableResourceMapping, s conversion.Scope) error { + out.CapacityKey = (*resourcev1beta2.QualifiedName)(unsafe.Pointer(in.CapacityKey)) + out.AllocationMultiplier = (*apiresource.Quantity)(unsafe.Pointer(in.AllocationMultiplier)) + return nil +} + +// Convert_resource_NodeAllocatableResourceMapping_To_v1beta2_NodeAllocatableResourceMapping is an autogenerated conversion function. +func Convert_resource_NodeAllocatableResourceMapping_To_v1beta2_NodeAllocatableResourceMapping(in *resource.NodeAllocatableResourceMapping, out *resourcev1beta2.NodeAllocatableResourceMapping, s conversion.Scope) error { + return autoConvert_resource_NodeAllocatableResourceMapping_To_v1beta2_NodeAllocatableResourceMapping(in, out, s) +} + func autoConvert_v1beta2_OpaqueDeviceConfiguration_To_resource_OpaqueDeviceConfiguration(in *resourcev1beta2.OpaqueDeviceConfiguration, out *resource.OpaqueDeviceConfiguration, s conversion.Scope) error { out.Driver = in.Driver out.Parameters = in.Parameters diff --git a/pkg/apis/resource/v1beta2/zz_generated.defaults.go b/pkg/apis/resource/v1beta2/zz_generated.defaults.go index 97397d22cb16d..b6ad1797f2683 100644 --- a/pkg/apis/resource/v1beta2/zz_generated.defaults.go +++ b/pkg/apis/resource/v1beta2/zz_generated.defaults.go @@ -30,6 +30,10 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&resourcev1beta2.DeviceTaintRule{}, func(obj interface{}) { SetObjectDefaults_DeviceTaintRule(obj.(*resourcev1beta2.DeviceTaintRule)) }) + scheme.AddTypeDefaultingFunc(&resourcev1beta2.DeviceTaintRuleList{}, func(obj interface{}) { + SetObjectDefaults_DeviceTaintRuleList(obj.(*resourcev1beta2.DeviceTaintRuleList)) + }) scheme.AddTypeDefaultingFunc(&resourcev1beta2.ResourceClaim{}, func(obj interface{}) { SetObjectDefaults_ResourceClaim(obj.(*resourcev1beta2.ResourceClaim)) }) scheme.AddTypeDefaultingFunc(&resourcev1beta2.ResourceClaimList{}, func(obj interface{}) { SetObjectDefaults_ResourceClaimList(obj.(*resourcev1beta2.ResourceClaimList)) }) scheme.AddTypeDefaultingFunc(&resourcev1beta2.ResourceClaimTemplate{}, func(obj interface{}) { @@ -43,6 +47,17 @@ func RegisterDefaults(scheme *runtime.Scheme) error { return nil } +func SetObjectDefaults_DeviceTaintRule(in *resourcev1beta2.DeviceTaintRule) { + SetDefaults_DeviceTaint(&in.Spec.Taint) +} + +func SetObjectDefaults_DeviceTaintRuleList(in *resourcev1beta2.DeviceTaintRuleList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_DeviceTaintRule(a) + } +} + func SetObjectDefaults_ResourceClaim(in *resourcev1beta2.ResourceClaim) { for i := range in.Spec.Devices.Requests { a := &in.Spec.Devices.Requests[i] @@ -121,6 +136,7 @@ func SetObjectDefaults_ResourceClaimTemplateList(in *resourcev1beta2.ResourceCla func SetObjectDefaults_ResourceSlice(in *resourcev1beta2.ResourceSlice) { for i := range in.Spec.Devices { a := &in.Spec.Devices[i] + SetDefaults_Device(a) for j := range a.Taints { b := &a.Taints[j] SetDefaults_DeviceTaint(b) diff --git a/pkg/apis/resource/v1beta2/zz_generated.validations.go b/pkg/apis/resource/v1beta2/zz_generated.validations.go index 2960f5fa78b6d..49da3b5a4156f 100644 --- a/pkg/apis/resource/v1beta2/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta2/zz_generated.validations.go @@ -50,11 +50,11 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type DeviceClassList - scheme.AddValidationFunc((*resourcev1beta2.DeviceClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + // type DeviceTaintRule + scheme.AddValidationFunc((*resourcev1beta2.DeviceTaintRule)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { case "/": - return Validate_DeviceClassList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.DeviceClassList), safe.Cast[*resourcev1beta2.DeviceClassList](oldObj)) + return Validate_DeviceTaintRule(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.DeviceTaintRule), safe.Cast[*resourcev1beta2.DeviceTaintRule](oldObj)) } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) @@ -66,14 +66,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceClaimList - scheme.AddValidationFunc((*resourcev1beta2.ResourceClaimList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceClaimList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.ResourceClaimList), safe.Cast[*resourcev1beta2.ResourceClaimList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceClaimTemplate scheme.AddValidationFunc((*resourcev1beta2.ResourceClaimTemplate)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -82,14 +74,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceClaimTemplateList - scheme.AddValidationFunc((*resourcev1beta2.ResourceClaimTemplateList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceClaimTemplateList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.ResourceClaimTemplateList), safe.Cast[*resourcev1beta2.ResourceClaimTemplateList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) // type ResourceSlice scheme.AddValidationFunc((*resourcev1beta2.ResourceSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -98,14 +82,6 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type ResourceSliceList - scheme.AddValidationFunc((*resourcev1beta2.ResourceSliceList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { - switch op.Request.SubresourcePath() { - case "/": - return Validate_ResourceSliceList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.ResourceSliceList), safe.Cast[*resourcev1beta2.ResourceSliceList](oldObj)) - } - return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} - }) return nil } @@ -125,13 +101,13 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation, } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("shareID"), obj.ShareID, safe.Field(oldObj, func(oldObj *resourcev1beta2.AllocatedDeviceStatus) *string { return oldObj.ShareID }), oldObj != nil)...) @@ -147,7 +123,7 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation, } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -168,7 +144,7 @@ var symbolsForAllocationConfigSource = sets.New(resourcev1beta2.AllocationConfig // Validate_AllocationConfigSource validates an instance of AllocationConfigSource according // to declarative validation rules in the API schema. func Validate_AllocationConfigSource(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.AllocationConfigSource) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil).MarkAlpha()...) return errs } @@ -207,18 +183,37 @@ func Validate_CounterSet(ctx context.Context, op operation.Operation, fldPath *f } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *resourcev1beta2.CounterSet) *string { return &oldObj.Name }), oldObj != nil)...) - // field resourcev1beta2.CounterSet.Counters has no validation + // field resourcev1beta2.CounterSet.Counters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta2.Counter, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName).MarkAlpha()...) + return + }(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta2.CounterSet) map[string]resourcev1beta2.Counter { return oldObj.Counters }), oldObj != nil)...) + return errs } @@ -234,6 +229,14 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the map and call the value type's validation function errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) return @@ -252,11 +255,11 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -265,7 +268,7 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceCounterConsumption, b resourcev1beta2.DeviceCounterConsumption) bool { return a.CounterSet == b.CounterSet - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceCounterConsumption, b resourcev1beta2.DeviceCounterConsumption) bool { return a.CounterSet == b.CounterSet @@ -286,6 +289,14 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceTaint)...) return @@ -302,11 +313,11 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -324,11 +335,11 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -338,6 +349,7 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field }(fldPath.Child("bindingFailureConditions"), obj.BindingFailureConditions, safe.Field(oldObj, func(oldObj *resourcev1beta2.Device) []string { return oldObj.BindingFailureConditions }), oldObj != nil)...) // field resourcev1beta2.Device.AllowMultipleAllocations has no validation + // field resourcev1beta2.Device.NodeAllocatableResourceMappings has no validation return errs } @@ -353,7 +365,7 @@ func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -376,18 +388,18 @@ func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAllocationConfiguration) []string { return oldObj.Requests }), oldObj != nil)...) @@ -413,7 +425,7 @@ var symbolsForDeviceAllocationMode = sets.New(resourcev1beta2.DeviceAllocationMo // Validate_DeviceAllocationMode validates an instance of DeviceAllocationMode according // to declarative validation rules in the API schema. func Validate_DeviceAllocationMode(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceAllocationMode) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceAllocationMode, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceAllocationMode, nil).MarkAlpha()...) return errs } @@ -430,11 +442,11 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -456,11 +468,11 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 64); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 64).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -476,7 +488,7 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } -var unionMembershipFor_k8s_io_api_resource_v1beta2_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) +var unionMembershipFor_k8s_io_api_resource_v1beta2_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version"), validate.NewUnionMember("ints"), validate.NewUnionMember("bools"), validate.NewUnionMember("strings"), validate.NewUnionMember("versions")) // Validate_DeviceAttribute validates an instance of DeviceAttribute according // to declarative validation rules in the API schema. @@ -501,7 +513,27 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa return false } return obj.VersionValue != nil - })...) + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.IntValues) != 0 + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.BoolValues) != 0 + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.StringValues) != 0 + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return len(obj.VersionValues) != 0 + }).MarkAlpha()...) // field resourcev1beta2.DeviceAttribute.IntValue errs = append(errs, @@ -512,7 +544,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -530,7 +562,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -548,7 +580,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -566,7 +598,7 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -575,6 +607,78 @@ func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPa return }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *string { return oldObj.VersionValue }), oldObj != nil)...) + // field resourcev1beta2.DeviceAttribute.IntValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("ints"), obj.IntValues, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) []int64 { return oldObj.IntValues }), oldObj != nil)...) + + // field resourcev1beta2.DeviceAttribute.BoolValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []bool, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bools"), obj.BoolValues, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) []bool { return oldObj.BoolValues }), oldObj != nil)...) + + // field resourcev1beta2.DeviceAttribute.StringValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("strings"), obj.StringValues, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) []string { return oldObj.StringValues }), oldObj != nil)...) + + // field resourcev1beta2.DeviceAttribute.VersionValues + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("versions"), obj.VersionValues, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) []string { return oldObj.VersionValues }), oldObj != nil)...) + return errs } @@ -590,18 +694,18 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceRequest, b resourcev1beta2.DeviceRequest) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceRequest, b resourcev1beta2.DeviceRequest) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceRequest, b resourcev1beta2.DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest)...) return @@ -616,11 +720,11 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -642,11 +746,11 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -674,18 +778,18 @@ func Validate_DeviceClaimConfiguration(ctx context.Context, op operation.Operati } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClaimConfiguration) []string { return oldObj.Requests }), oldObj != nil)...) @@ -721,13 +825,13 @@ func Validate_DeviceClass(ctx context.Context, op operation.Operation, fldPath * // call field-attached validations func() { // cohort name earlyReturn := false - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue); len(e) != 0 { + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.OptionalValue).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName)...) + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *v1.ObjectMeta) *string { return &o.Name }, validate.DirectEqualPtr, validate.LongName).MarkAlpha()...) }() return }(fldPath.Child("metadata"), &obj.ObjectMeta, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClass) *v1.ObjectMeta { return &oldObj.ObjectMeta }), oldObj != nil)...) @@ -767,27 +871,6 @@ func Validate_DeviceClassConfiguration(ctx context.Context, op operation.Operati return errs } -// Validate_DeviceClassList validates an instance of DeviceClassList according -// to declarative validation rules in the API schema. -func Validate_DeviceClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClassList) (errs field.ErrorList) { - // field resourcev1beta2.DeviceClassList.TypeMeta has no validation - // field resourcev1beta2.DeviceClassList.ListMeta has no validation - - // field resourcev1beta2.DeviceClassList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceClass, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceClass)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClassList) []resourcev1beta2.DeviceClass { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_DeviceClassSpec validates an instance of DeviceClassSpec according // to declarative validation rules in the API schema. func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClassSpec) (errs field.ErrorList) { @@ -800,11 +883,11 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -824,11 +907,11 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -850,13 +933,13 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("extendedResourceName"), obj.ExtendedResourceName, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClassSpec) *string { return oldObj.ExtendedResourceName }), oldObj != nil)...) @@ -875,7 +958,7 @@ func Validate_DeviceConfiguration(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -903,18 +986,18 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceConstraint) []string { return oldObj.Requests }), oldObj != nil)...) @@ -927,13 +1010,13 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("matchAttribute"), obj.MatchAttribute, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceConstraint) *resourcev1beta2.FullyQualifiedName { return oldObj.MatchAttribute @@ -955,18 +1038,39 @@ func Validate_DeviceCounterConsumption(ctx context.Context, op operation.Operati } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("counterSet"), &obj.CounterSet, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceCounterConsumption) *string { return &oldObj.CounterSet }), oldObj != nil)...) - // field resourcev1beta2.DeviceCounterConsumption.Counters has no validation + // field resourcev1beta2.DeviceCounterConsumption.Counters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta2.Counter, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName).MarkAlpha()...) + return + }(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceCounterConsumption) map[string]resourcev1beta2.Counter { + return oldObj.Counters + }), oldObj != nil)...) + return errs } @@ -984,7 +1088,7 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1004,11 +1108,11 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1017,7 +1121,7 @@ func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceSubRequest, b resourcev1beta2.DeviceSubRequest) bool { return a.Name == b.Name - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.DeviceSubRequest, b resourcev1beta2.DeviceSubRequest) bool { return a.Name == b.Name @@ -1044,14 +1148,15 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) return }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceRequestAllocationResult) *string { return &oldObj.Driver }), oldObj != nil)...) @@ -1064,14 +1169,14 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("pool"), &obj.Pool, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceRequestAllocationResult) *string { return &oldObj.Pool }), oldObj != nil)...) @@ -1085,6 +1190,14 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1101,11 +1214,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1123,11 +1236,11 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1147,13 +1260,13 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("shareID"), obj.ShareID, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceRequestAllocationResult) *types.UID { return oldObj.ShareID }), oldObj != nil)...) @@ -1175,14 +1288,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("deviceClassName"), &obj.DeviceClassName, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceSubRequest) *string { return &oldObj.DeviceClassName }), oldObj != nil)...) @@ -1195,11 +1308,11 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1217,6 +1330,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceAllocationMode(ctx, op, fldPath, obj, oldObj)...) return @@ -1233,6 +1354,14 @@ func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1259,7 +1388,7 @@ func Validate_DeviceTaint(ctx context.Context, op operation.Operation, fldPath * } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1280,7 +1409,51 @@ var symbolsForDeviceTaintEffect = sets.New(resourcev1beta2.DeviceTaintEffectNoEx // Validate_DeviceTaintEffect validates an instance of DeviceTaintEffect according // to declarative validation rules in the API schema. func Validate_DeviceTaintEffect(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceTaintEffect) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTaintEffect, nil).MarkAlpha()...) + + return errs +} + +// Validate_DeviceTaintRule validates an instance of DeviceTaintRule according +// to declarative validation rules in the API schema. +func Validate_DeviceTaintRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceTaintRule) (errs field.ErrorList) { + // field resourcev1beta2.DeviceTaintRule.TypeMeta has no validation + // field resourcev1beta2.DeviceTaintRule.ObjectMeta has no validation + + // field resourcev1beta2.DeviceTaintRule.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceTaintRuleSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceTaintRuleSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceTaintRule) *resourcev1beta2.DeviceTaintRuleSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field resourcev1beta2.DeviceTaintRule.Status has no validation + return errs +} + +// Validate_DeviceTaintRuleSpec validates an instance of DeviceTaintRuleSpec according +// to declarative validation rules in the API schema. +func Validate_DeviceTaintRuleSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceTaintRuleSpec) (errs field.ErrorList) { + // field resourcev1beta2.DeviceTaintRuleSpec.DeviceSelector has no validation + + // field resourcev1beta2.DeviceTaintRuleSpec.Taint + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceTaint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceTaint(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("taint"), &obj.Taint, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceTaintRuleSpec) *resourcev1beta2.DeviceTaint { return &oldObj.Taint }), oldObj != nil)...) return errs } @@ -1297,13 +1470,13 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("key"), &obj.Key, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceToleration) *string { return &oldObj.Key }), oldObj != nil)...) @@ -1314,6 +1487,16 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceTolerationOperator(ctx, op, fldPath, obj, oldObj)...) return @@ -1330,6 +1513,14 @@ func Validate_DeviceToleration(ctx context.Context, op operation.Operation, fldP if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // call the type's validation function errs = append(errs, Validate_DeviceTaintEffect(ctx, op, fldPath, obj, oldObj)...) return @@ -1346,7 +1537,7 @@ var symbolsForDeviceTolerationOperator = sets.New(resourcev1beta2.DeviceTolerati // Validate_DeviceTolerationOperator validates an instance of DeviceTolerationOperator according // to declarative validation rules in the API schema. func Validate_DeviceTolerationOperator(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceTolerationOperator) (errs field.ErrorList) { - errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTolerationOperator, nil)...) + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDeviceTolerationOperator, nil).MarkAlpha()...) return errs } @@ -1365,11 +1556,11 @@ func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fl } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1389,7 +1580,7 @@ func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fl } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1412,6 +1603,14 @@ func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fl if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceToleration)...) return @@ -1435,13 +1634,13 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 256)...) + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 256).MarkAlpha()...) return }(fldPath.Child("interfaceName"), &obj.InterfaceName, safe.Field(oldObj, func(oldObj *resourcev1beta2.NetworkDeviceData) *string { return &oldObj.InterfaceName }), oldObj != nil)...) @@ -1454,18 +1653,18 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with set semantics require unique values - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) return }(fldPath.Child("ips"), obj.IPs, safe.Field(oldObj, func(oldObj *resourcev1beta2.NetworkDeviceData) []string { return oldObj.IPs }), oldObj != nil)...) @@ -1478,13 +1677,13 @@ func Validate_NetworkDeviceData(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 128)...) + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 128).MarkAlpha()...) return }(fldPath.Child("hardwareAddress"), &obj.HardwareAddress, safe.Field(oldObj, func(oldObj *resourcev1beta2.NetworkDeviceData) *string { return &oldObj.HardwareAddress }), oldObj != nil)...) @@ -1503,14 +1702,15 @@ func Validate_OpaqueDeviceConfiguration(ctx context.Context, op operation.Operat } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) return }(fldPath.Child("driver"), &obj.Driver, safe.Field(oldObj, func(oldObj *resourcev1beta2.OpaqueDeviceConfiguration) *string { return &oldObj.Driver }), oldObj != nil)...) @@ -1533,7 +1733,7 @@ func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1562,27 +1762,6 @@ func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath return errs } -// Validate_ResourceClaimList validates an instance of ResourceClaimList according -// to declarative validation rules in the API schema. -func Validate_ResourceClaimList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimList) (errs field.ErrorList) { - // field resourcev1beta2.ResourceClaimList.TypeMeta has no validation - // field resourcev1beta2.ResourceClaimList.ListMeta has no validation - - // field resourcev1beta2.ResourceClaimList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta2.ResourceClaim, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaim)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimList) []resourcev1beta2.ResourceClaim { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ResourceClaimSpec validates an instance of ResourceClaimSpec according // to declarative validation rules in the API schema. func Validate_ResourceClaimSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimSpec) (errs field.ErrorList) { @@ -1613,10 +1792,10 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify); len(e) != 0 { + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -1639,11 +1818,11 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 256); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 256).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -1652,7 +1831,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.ResourceClaimConsumerReference, b resourcev1beta2.ResourceClaimConsumerReference) bool { return a.UID == b.UID - })...) + }).MarkAlpha()...) return }(fldPath.Child("reservedFor"), obj.ReservedFor, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimStatus) []resourcev1beta2.ResourceClaimConsumerReference { return oldObj.ReservedFor @@ -1667,7 +1846,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1676,7 +1855,7 @@ func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, f // lists with map semantics require unique keys errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.AllocatedDeviceStatus, b resourcev1beta2.AllocatedDeviceStatus) bool { return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID)) - })...) + }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.AllocatedDeviceStatus, b resourcev1beta2.AllocatedDeviceStatus) bool { return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID)) @@ -1712,29 +1891,6 @@ func Validate_ResourceClaimTemplate(ctx context.Context, op operation.Operation, return errs } -// Validate_ResourceClaimTemplateList validates an instance of ResourceClaimTemplateList according -// to declarative validation rules in the API schema. -func Validate_ResourceClaimTemplateList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimTemplateList) (errs field.ErrorList) { - // field resourcev1beta2.ResourceClaimTemplateList.TypeMeta has no validation - // field resourcev1beta2.ResourceClaimTemplateList.ListMeta has no validation - - // field resourcev1beta2.ResourceClaimTemplateList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta2.ResourceClaimTemplate, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaimTemplate)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimTemplateList) []resourcev1beta2.ResourceClaimTemplate { - return oldObj.Items - }), oldObj != nil)...) - - return errs -} - // Validate_ResourceClaimTemplateSpec validates an instance of ResourceClaimTemplateSpec according // to declarative validation rules in the API schema. func Validate_ResourceClaimTemplateSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimTemplateSpec) (errs field.ErrorList) { @@ -1778,27 +1934,6 @@ func Validate_ResourceSlice(ctx context.Context, op operation.Operation, fldPath return errs } -// Validate_ResourceSliceList validates an instance of ResourceSliceList according -// to declarative validation rules in the API schema. -func Validate_ResourceSliceList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceSliceList) (errs field.ErrorList) { - // field resourcev1beta2.ResourceSliceList.TypeMeta has no validation - // field resourcev1beta2.ResourceSliceList.ListMeta has no validation - - // field resourcev1beta2.ResourceSliceList.Items - errs = append(errs, - func(fldPath *field.Path, obj, oldObj []resourcev1beta2.ResourceSlice, oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { - return nil - } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceSlice)...) - return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceSliceList) []resourcev1beta2.ResourceSlice { return oldObj.Items }), oldObj != nil)...) - - return errs -} - // Validate_ResourceSliceSpec validates an instance of ResourceSliceSpec according // to declarative validation rules in the API schema. func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceSliceSpec) (errs field.ErrorList) { @@ -1817,7 +1952,7 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } if earlyReturn { @@ -1839,18 +1974,18 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { earlyReturn = true } - if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } // lists with map semantics require unique keys - errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.CounterSet, b resourcev1beta2.CounterSet) bool { return a.Name == b.Name })...) + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.CounterSet, b resourcev1beta2.CounterSet) bool { return a.Name == b.Name }).MarkAlpha()...) // iterate the list and call the type's validation function errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a resourcev1beta2.CounterSet, b resourcev1beta2.CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet)...) return diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index ef2b4cfe82603..d98e6bc6245d8 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -325,7 +325,10 @@ func validateCELSelector(celSelector resource.CELDeviceSelector, fldPath *field. return allErrs } - result := dracel.GetCompiler(dracel.Features{EnableConsumableCapacity: utilfeature.DefaultFeatureGate.Enabled(features.DRAConsumableCapacity)}).CompileCELExpression(celSelector.Expression, dracel.Options{EnvType: &envType}) + result := dracel.GetCompiler(dracel.Features{ + EnableConsumableCapacity: utilfeature.DefaultFeatureGate.Enabled(features.DRAConsumableCapacity), + EnableListTypeAttributes: utilfeature.DefaultFeatureGate.Enabled(features.DRAListTypeAttributes), + }).CompileCELExpression(celSelector.Expression, dracel.Options{EnvType: &envType}) if result.Error != nil { allErrs = append(allErrs, convertCELErrorToValidationError(fldPath.Child("expression"), celSelector.Expression, result.Error)) } else if result.MaxCost > resource.CELSelectorExpressionMaxCost { @@ -409,7 +412,7 @@ func validateDeviceConfiguration(config resource.DeviceConfiguration, fldPath *f func validateOpaqueConfiguration(config resource.OpaqueDeviceConfiguration, fldPath *field.Path, stored bool) field.ErrorList { var allErrs field.ErrorList - allErrs = append(allErrs, validateDriverName(config.Driver, fldPath.Child("driver"), corevalidation.RequiredCovered, corevalidation.FormatCovered)...) + allErrs = append(allErrs, validateDriverName(config.Driver, fldPath.Child("driver"), corevalidation.RequiredCovered, corevalidation.FormatCovered, corevalidation.SizeCovered)...) allErrs = append(allErrs, validateRawExtension(config.Parameters, fldPath.Child("parameters"), stored, resource.OpaqueParametersMaxLength)...) return allErrs } @@ -509,7 +512,7 @@ func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames requestNames) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateRequestNameRef(result.Request, fldPath.Child("request"), requestNames)...) - allErrs = append(allErrs, validateDriverName(result.Driver, fldPath.Child("driver"), corevalidation.RequiredCovered, corevalidation.FormatCovered)...) + allErrs = append(allErrs, validateDriverName(result.Driver, fldPath.Child("driver"), corevalidation.RequiredCovered, corevalidation.FormatCovered, corevalidation.SizeCovered)...) allErrs = append(allErrs, validatePoolName(result.Pool, fldPath.Child("pool")).MarkCoveredByDeclarative()...) allErrs = append(allErrs, validateDeviceName(result.Device, fldPath.Child("device"))...) allErrs = append(allErrs, validateDeviceBindingParameters(result.BindingConditions, result.BindingFailureConditions, fldPath)...) @@ -708,8 +711,8 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat } maxDevices := resource.ResourceSliceMaxDevices - if haveDeviceTaints(spec) || haveConsumesCounters(spec) { - maxDevices = resource.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters + if haveDeviceTaints(spec) || haveConsumesCounters(spec) || haveListAttributes(spec) { + maxDevices = resource.ResourceSliceMaxDevicesWithAdvancedFeatures } allErrs = append(allErrs, validateSet(spec.Devices, maxDevices, func(device resource.Device, fldPath *field.Path) field.ErrorList { @@ -729,6 +732,24 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat return allErrs } +func haveListAttributes(spec *resource.ResourceSliceSpec) bool { + if spec == nil { + return false + } + + for _, device := range spec.Devices { + for _, attribute := range device.Attributes { + if attribute.BoolValues != nil || + attribute.IntValues != nil || + attribute.StringValues != nil || + attribute.VersionValues != nil { + return true + } + } + } + return false +} + func haveDeviceTaints(spec *resource.ResourceSliceSpec) bool { if spec == nil { return false @@ -773,14 +794,14 @@ func validateCounterSet(counterSet resource.CounterSet, fldPath *field.Path) fie if counterSet.Name == "" { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "").MarkCoveredByDeclarative()) } else { - allErrs = append(allErrs, validateCounterName(counterSet.Name, fldPath.Child("name"))...).MarkCoveredByDeclarative() + allErrs = append(allErrs, validateCounterName(counterSet.Name, fldPath.Child("name")).MarkCoveredByDeclarative()...) } if len(counterSet.Counters) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("counters"), "")) } else { // The size limit is enforced for across all sets by the caller. allErrs = append(allErrs, validateMap(counterSet.Counters, resource.ResourceSliceMaxCountersPerCounterSet, validation.DNS1123LabelMaxLength, - validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...) + validateCounterName, validateDeviceCounter, fldPath.Child("counters"), keysCovered)...) } return allErrs @@ -802,12 +823,18 @@ func validateDevice(device resource.Device, oldDevice *resource.Device, fldPath var allErrs field.ErrorList allowMultipleAllocations := device.AllowMultipleAllocations != nil && *device.AllowMultipleAllocations allErrs = append(allErrs, validateDeviceName(device.Name, fldPath.Child("name"))...) + // Warn about exceeding the maximum length only once. If any individual // field is too large, then so is the combination. + // we count the total number of attribute entries (scalars and list items) attributeAndCapacityLength := len(device.Attributes) + len(device.Capacity) if attributeAndCapacityLength > resource.ResourceSliceMaxAttributesAndCapacitiesPerDevice { allErrs = append(allErrs, field.Invalid(fldPath, attributeAndCapacityLength, fmt.Sprintf("the total number of attributes and capacities must not exceed %d", resource.ResourceSliceMaxAttributesAndCapacitiesPerDevice))) } + numAttributeValues := numEntriesDeviceAttribute(device) + if numAttributeValues > resource.ResourceSliceMaxAttributeValuesPerDevice { + allErrs = append(allErrs, field.Invalid(fldPath, numAttributeValues, fmt.Sprintf("the total number of attribute values must not exceed %d", resource.ResourceSliceMaxAttributeValuesPerDevice))) + } allErrs = append(allErrs, validateMap(device.Attributes, -1, attributeAndCapacityMaxKeyLength, validateQualifiedName, validateDeviceAttribute, fldPath.Child("attributes"))...) if allowMultipleAllocations { @@ -865,22 +892,72 @@ func validateDevice(device resource.Device, oldDevice *resource.Device, fldPath } allErrs = append(allErrs, validateDeviceBindingParameters(device.BindingConditions, device.BindingFailureConditions, fldPath)...) + allErrs = append(allErrs, validateNodeAllocatableResourceMappings(device.NodeAllocatableResourceMappings, device.Capacity, fldPath.Child("nodeAllocatableResourceMappings"))...) + + return allErrs +} + +func validateNodeAllocatableResourceMappings(mappings map[corev1.ResourceName]resource.NodeAllocatableResourceMapping, capacities map[resource.QualifiedName]resource.DeviceCapacity, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + for resourceName, mapping := range mappings { + keyPath := fldPath.Key(string(resourceName)) + if !v1helper.IsNativeResource(resourceName) { + allErrs = append(allErrs, field.Invalid(keyPath, resourceName, "must be a node allocatable resource name")) + } + + if mapping.AllocationMultiplier == nil && mapping.CapacityKey == nil { + allErrs = append(allErrs, field.Invalid(keyPath, "", "at least one of allocationMultiplier or capacityKey must be set")) + } else { + if mapping.AllocationMultiplier != nil { + if mapping.AllocationMultiplier.Sign() <= 0 { + allErrs = append(allErrs, field.Invalid(keyPath.Child("allocationMultiplier"), mapping.AllocationMultiplier.String(), "must be positive")) + } + } + if mapping.CapacityKey != nil { + if *mapping.CapacityKey == "" { + allErrs = append(allErrs, field.Invalid(keyPath.Child("capacityKey"), "", "capacityKey must not be an empty string")) + } else if capacities == nil { + allErrs = append(allErrs, field.NotFound(keyPath.Child("capacityKey"), *mapping.CapacityKey)) + } else if _, exists := capacities[*mapping.CapacityKey]; !exists { + allErrs = append(allErrs, field.NotFound(keyPath.Child("capacityKey"), *mapping.CapacityKey)) + } + } + } + } return allErrs } +// numEntriesDeviceAttribute counts the total number of entries in the device's attributes, +// counting each item in list values separately. +// This is used to enforce the maximum number of attributes and capacities +// per device when the DRAListTypeAttributes feature gate is enabled. +func numEntriesDeviceAttribute(device resource.Device) int { + numEntries := 0 + for _, attr := range device.Attributes { + if len(attr.BoolValues) == 0 && len(attr.IntValues) == 0 && len(attr.StringValues) == 0 && len(attr.VersionValues) == 0 { + numEntries++ + continue + } + // These fields are actually mutually exclusive, but we count the total number of entries across all of them. + // If setting multiple fields in the list value, the validation will fail later and report that exactly one value must be specified. + numEntries += len(attr.BoolValues) + len(attr.IntValues) + len(attr.StringValues) + len(attr.VersionValues) + } + return numEntries +} + func validateDeviceCounterConsumption(deviceCounterConsumption resource.DeviceCounterConsumption, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList if len(deviceCounterConsumption.CounterSet) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("counterSet"), "").MarkCoveredByDeclarative()) } else { - allErrs = append(allErrs, validateCounterName(deviceCounterConsumption.CounterSet, fldPath.Child("counterSet"))...).MarkCoveredByDeclarative() + allErrs = append(allErrs, validateCounterName(deviceCounterConsumption.CounterSet, fldPath.Child("counterSet")).MarkCoveredByDeclarative()...) } if len(deviceCounterConsumption.Counters) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("counters"), "")) } else { allErrs = append(allErrs, validateMap(deviceCounterConsumption.Counters, resource.ResourceSliceMaxCountersPerDeviceCounterConsumption, - validation.DNS1123LabelMaxLength, validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...) + validation.DNS1123LabelMaxLength, validateCounterName, validateDeviceCounter, fldPath.Child("counters"), keysCovered)...) } return allErrs } @@ -922,14 +999,44 @@ func validateDeviceAttribute(attribute resource.DeviceAttribute, fldPath *field. numFields++ allErrs = append(allErrs, validateDeviceAttributeVersionValue(attribute.VersionValue, fldPath.Child("version"))...) } + if attribute.BoolValues != nil { + numFields++ + if len(attribute.BoolValues) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("bools"), attribute.BoolValues, "must not be empty if specified")) + } + } + if attribute.IntValues != nil { + numFields++ + if len(attribute.IntValues) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("ints"), attribute.IntValues, "must not be empty if specified")) + } + } + if attribute.StringValues != nil { + numFields++ + if len(attribute.StringValues) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("strings"), attribute.StringValues, "must not be empty if specified")) + } + for i, item := range attribute.StringValues { + allErrs = append(allErrs, validateDeviceAttributeStringValue(&item, fldPath.Child("strings").Index(i))...) + } + } + if attribute.VersionValues != nil { + numFields++ + if len(attribute.VersionValues) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("versions"), attribute.VersionValues, "must not be empty if specified")) + } + for i, item := range attribute.VersionValues { + allErrs = append(allErrs, validateDeviceAttributeVersionValue(&item, fldPath.Child("versions").Index(i))...) + } + } switch numFields { case 0: - allErrs = append(allErrs, field.Invalid(fldPath, "", "exactly one value must be specified").MarkCoveredByDeclarative()) + allErrs = append(allErrs, field.Invalid(fldPath, "", "exactly one value must be specified").WithOrigin("union").MarkCoveredByDeclarative()) case 1: // Okay. default: - allErrs = append(allErrs, field.Invalid(fldPath, attribute, "exactly one value must be specified").MarkCoveredByDeclarative()) + allErrs = append(allErrs, field.Invalid(fldPath, attribute, "exactly one value must be specified").WithOrigin("union").MarkCoveredByDeclarative()) } return allErrs } @@ -1154,6 +1261,8 @@ const ( sizeCovered // The uniqueness check is covered by declarative validation. uniquenessCovered + // key validation is covered by declarative validation. + keysCovered ) // validateItems validates each item in a slice. @@ -1240,7 +1349,7 @@ func quantityKey(item apiresource.Quantity) string { // small limit gets increased because it is okay to include more details. // This is not used for validation of keys, which has to be done by // the callback function. -func validateMap[K ~string, T any](m map[K]T, maxSize, truncateKeyLen int, validateKey func(K, *field.Path) field.ErrorList, validateItem func(T, *field.Path) field.ErrorList, fldPath *field.Path) field.ErrorList { +func validateMap[K ~string, T any](m map[K]T, maxSize, truncateKeyLen int, validateKey func(K, *field.Path) field.ErrorList, validateItem func(T, *field.Path) field.ErrorList, fldPath *field.Path, opts ...validationOption) field.ErrorList { var allErrs field.ErrorList if maxSize >= 0 && len(m) > maxSize { allErrs = append(allErrs, field.TooMany(fldPath, len(m), maxSize)) @@ -1249,7 +1358,12 @@ func validateMap[K ~string, T any](m map[K]T, maxSize, truncateKeyLen int, valid } for key, item := range m { keyPath := fldPath.Key(truncateIfTooLong(string(key), truncateKeyLen)) - allErrs = append(allErrs, validateKey(key, keyPath)...) + + keyValidationErrors := validateKey(key, fldPath) + if slices.Contains(opts, keysCovered) { + keyValidationErrors = keyValidationErrors.MarkCoveredByDeclarative() + } + allErrs = append(allErrs, keyValidationErrors...) allErrs = append(allErrs, validateItem(item, keyPath)...) } return allErrs @@ -1322,11 +1436,11 @@ func validateNetworkDeviceData(networkDeviceData *resource.NetworkDeviceData, fl } if len(networkDeviceData.InterfaceName) > resource.NetworkDeviceDataInterfaceNameMaxLength { - allErrs = append(allErrs, field.TooLong(fldPath.Child("interfaceName"), "" /* unused */, resource.NetworkDeviceDataInterfaceNameMaxLength).WithOrigin("maxLength").MarkCoveredByDeclarative()) + allErrs = append(allErrs, field.TooLong(fldPath.Child("interfaceName"), "" /* unused */, resource.NetworkDeviceDataInterfaceNameMaxLength).WithOrigin("maxBytes").MarkCoveredByDeclarative()) } if len(networkDeviceData.HardwareAddress) > resource.NetworkDeviceDataHardwareAddressMaxLength { - allErrs = append(allErrs, field.TooLong(fldPath.Child("hardwareAddress"), "" /* unused */, resource.NetworkDeviceDataHardwareAddressMaxLength).WithOrigin("maxLength").MarkCoveredByDeclarative()) + allErrs = append(allErrs, field.TooLong(fldPath.Child("hardwareAddress"), "" /* unused */, resource.NetworkDeviceDataHardwareAddressMaxLength).WithOrigin("maxBytes").MarkCoveredByDeclarative()) } allErrs = append(allErrs, validateSet(networkDeviceData.IPs, resource.NetworkDeviceDataMaxIPs, @@ -1421,7 +1535,7 @@ func validateDeviceToleration(toleration resource.DeviceToleration, fldPath *fie case resource.DeviceTolerationOpEqual: allErrs = append(allErrs, validateLabelValue(toleration.Value, fldPath.Child("value"))...) case "": - allErrs = append(allErrs, field.Required(fldPath.Child("operator"), "")) + allErrs = append(allErrs, field.Required(fldPath.Child("operator"), "").MarkCoveredByDeclarative()) default: allErrs = append(allErrs, field.NotSupported(fldPath.Child("operator"), toleration.Operator, validDeviceTolerationOperators).MarkCoveredByDeclarative()) } diff --git a/pkg/apis/resource/validation/validation_resourceclaim_test.go b/pkg/apis/resource/validation/validation_resourceclaim_test.go index 9a6cffb82598c..fb90ca3a81f7f 100644 --- a/pkg/apis/resource/validation/validation_resourceclaim_test.go +++ b/pkg/apis/resource/validation/validation_resourceclaim_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/dynamic-resource-allocation/structured" @@ -814,11 +815,11 @@ func TestValidateClaim(t *testing.T) { var allErrs field.ErrorList fldPath := field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations") allErrs = append(allErrs, - field.Required(fldPath.Index(0).Child("operator"), ""), + field.Required(fldPath.Index(0).Child("operator"), "").MarkCoveredByDeclarative(), ) fldPath = field.NewPath("spec", "devices", "requests").Index(1).Child("exactly", "tolerations") allErrs = append(allErrs, - field.Required(fldPath.Index(3).Child("operator"), ""), + field.Required(fldPath.Index(3).Child("operator"), "").MarkCoveredByDeclarative(), field.NotSupported(fldPath.Index(4).Child("operator"), resource.DeviceTolerationOperator("some-other-op"), []resource.DeviceTolerationOperator{resource.DeviceTolerationOpEqual, resource.DeviceTolerationOpExists}).MarkCoveredByDeclarative(), @@ -1394,6 +1395,45 @@ func TestValidateClaimStatusUpdate(t *testing.T) { }, deviceStatusFeatureGate: true, }, + "valid-network-device-status-with-multi-byte-interface-name-and-hardware-address": { + oldClaim: func() *resource.ResourceClaim { return validAllocatedClaim }(), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Devices = []resource.AllocatedDeviceStatus{ + { + Driver: goodName, + Pool: goodName, + Device: goodName, + Conditions: []metav1.Condition{ + {Type: "test-0", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-1", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-2", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-3", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-4", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-5", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-6", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + {Type: "test-7", Status: metav1.ConditionTrue, Reason: "test_reason", LastTransitionTime: metav1.Now(), ObservedGeneration: 0}, + }, + Data: &runtime.RawExtension{ + Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`), + }, + NetworkData: &resource.NetworkDeviceData{ + InterfaceName: strings.Repeat("𝄞", 256/4), // the G clef unicode character is exactly 4 bytes so repeating this 256/4 times means that it is exactly 256 bytes worth of length and should be valid. + HardwareAddress: strings.Repeat("𝄞", 128/4), // the G clef unicode character is exactly 4 bytes so repeating this 128/4 times means that it is exactly 128 bytes worth of length and should be valid. + IPs: []string{ + "10.9.8.0/24", + "2001:db8::/64", + "10.9.8.1/24", + "2001:db8::1/64", + "10.9.8.2/24", "10.9.8.3/24", "10.9.8.4/24", "10.9.8.5/24", "10.9.8.6/24", "10.9.8.7/24", + "10.9.8.8/24", "10.9.8.9/24", "10.9.8.10/24", "10.9.8.11/24", "10.9.8.12/24", "10.9.8.13/24", + }, + }, + }, + } + return claim + }, + deviceStatusFeatureGate: true, + }, "invalid-device-status-duplicate": { wantFailures: field.ErrorList{ field.Duplicate(field.NewPath("status", "devices").Index(0).Child("networkData", "ips").Index(1), "2001:db8::1/64").MarkCoveredByDeclarative(), @@ -1483,6 +1523,36 @@ func TestValidateClaimStatusUpdate(t *testing.T) { }, deviceStatusFeatureGate: true, }, + "invalid-network-device-status-with-multi-byte-interface-name-and-hardware-address": { + wantFailures: field.ErrorList{ + field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "interfaceName"), "", resource.NetworkDeviceDataInterfaceNameMaxLength).MarkCoveredByDeclarative(), + field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "hardwareAddress"), "", resource.NetworkDeviceDataHardwareAddressMaxLength).MarkCoveredByDeclarative(), + field.Invalid(field.NewPath("status", "devices").Index(0).Child("networkData", "ips").Index(0), "300.9.8.0/24", "must be a valid address in CIDR form, (e.g. 10.9.8.7/24 or 2001:db8::1/64)"), + field.Invalid(field.NewPath("status", "devices").Index(0).Child("networkData", "ips").Index(1), "010.009.008.000/24", "must be in canonical form (\"10.9.8.0/24\")"), + field.Invalid(field.NewPath("status", "devices").Index(0).Child("networkData", "ips").Index(2), "2001:0db8::1/64", "must be in canonical form (\"2001:db8::1/64\")"), + }, + oldClaim: func() *resource.ResourceClaim { return validAllocatedClaim }(), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Devices = []resource.AllocatedDeviceStatus{ + { + Driver: goodName, + Pool: goodName, + Device: goodName, + NetworkData: &resource.NetworkDeviceData{ + InterfaceName: strings.Repeat("𝄞", resource.NetworkDeviceDataInterfaceNameMaxLength), // the G clef unicode character is exactly 4 bytes in length and should exceed the byte length limit for this field by a factor of 4 + HardwareAddress: strings.Repeat("𝄞", resource.NetworkDeviceDataHardwareAddressMaxLength), // the G clef unicode character is exactly 4 bytes in length and should exceed the byte length limit for this field by a factor of 4 + IPs: []string{ + "300.9.8.0/24", + "010.009.008.000/24", + "2001:0db8::1/64", + }, + }, + }, + } + return claim + }, + deviceStatusFeatureGate: true, + }, "invalid-data-device-status": { wantFailures: field.ErrorList{ field.Invalid(field.NewPath("status", "devices").Index(0).Child("data"), "", "error parsing data as JSON: invalid character 'o' in literal false (expecting 'a')"), @@ -2306,6 +2376,9 @@ func TestValidateClaimStatusUpdate(t *testing.T) { for name, scenario := range scenarios { t.Run(name, func(t *testing.T) { + if !scenario.adminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.DRAAdminAccess: scenario.adminAccess, features.DRAResourceClaimDeviceStatus: scenario.deviceStatusFeatureGate, diff --git a/pkg/apis/resource/validation/validation_resourcepoolstatusrequest.go b/pkg/apis/resource/validation/validation_resourcepoolstatusrequest.go new file mode 100644 index 0000000000000..c61eb399c7b73 --- /dev/null +++ b/pkg/apis/resource/validation/validation_resourcepoolstatusrequest.go @@ -0,0 +1,64 @@ +/* +Copyright 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 validation + +import ( + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/kubernetes/pkg/apis/resource" +) + +// ValidateResourcePoolStatusRequest validates a ResourcePoolStatusRequest. +func ValidateResourcePoolStatusRequest(request *resource.ResourcePoolStatusRequest) field.ErrorList { + allErrs := corevalidation.ValidateObjectMeta(&request.ObjectMeta, false, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + return allErrs +} + +// ValidateResourcePoolStatusRequestUpdate tests if an update to ResourcePoolStatusRequest is valid. +func ValidateResourcePoolStatusRequestUpdate(request, oldRequest *resource.ResourcePoolStatusRequest) field.ErrorList { + allErrs := corevalidation.ValidateObjectMetaUpdate(&request.ObjectMeta, &oldRequest.ObjectMeta, field.NewPath("metadata")) + return allErrs +} + +// ValidateResourcePoolStatusRequestStatusUpdate tests if a status update to ResourcePoolStatusRequest is valid. +func ValidateResourcePoolStatusRequestStatusUpdate(request, oldRequest *resource.ResourcePoolStatusRequest) field.ErrorList { + allErrs := corevalidation.ValidateObjectMetaUpdate(&request.ObjectMeta, &oldRequest.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, validateResourcePoolStatusRequestStatusUpdate(request.Status, oldRequest.Status, field.NewPath("status"))...) + return allErrs +} + +func validateResourcePoolStatusRequestStatusUpdate(status, oldStatus *resource.ResourcePoolStatusRequestStatus, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + // Once status is set, it becomes immutable (request is complete) — not DV covered + if oldStatus != nil { + allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(status, oldStatus, fldPath)...) + return allErrs + } + + // If new status is nil, nothing to validate + if status == nil { + return allErrs + } + + // Validate conditions — not DV covered + allErrs = append(allErrs, metav1validation.ValidateConditions(status.Conditions, fldPath.Child("conditions"))...) + + return allErrs +} diff --git a/pkg/apis/resource/validation/validation_resourcepoolstatusrequest_test.go b/pkg/apis/resource/validation/validation_resourcepoolstatusrequest_test.go new file mode 100644 index 0000000000000..aac85c2f76f50 --- /dev/null +++ b/pkg/apis/resource/validation/validation_resourcepoolstatusrequest_test.go @@ -0,0 +1,242 @@ +/* +Copyright 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 validation + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/resource" +) + +const ( + dns1123SubdomainErrorMsg = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')" +) + +func testResourcePoolStatusRequest(name string, spec resource.ResourcePoolStatusRequestSpec) *resource.ResourcePoolStatusRequest { + return &resource.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: *spec.DeepCopy(), + } +} + +func testResourcePoolStatusRequestForUpdate(name string, spec resource.ResourcePoolStatusRequestSpec) *resource.ResourcePoolStatusRequest { + return &resource.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: "1", + }, + Spec: *spec.DeepCopy(), + } +} + +var validResourcePoolStatusRequestSpec = resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + PoolName: new("pool-1"), + Limit: new(int32(100)), +} + +func TestValidateResourcePoolStatusRequest(t *testing.T) { + goodName := "my-request" + badName := "!@#$%^" + + scenarios := map[string]struct { + request *resource.ResourcePoolStatusRequest + wantFailures field.ErrorList + }{ + "valid-request": { + request: testResourcePoolStatusRequest(goodName, validResourcePoolStatusRequestSpec), + }, + "valid-driver-only": { + request: testResourcePoolStatusRequest(goodName, resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }), + }, + "valid-with-pool-name": { + request: testResourcePoolStatusRequest(goodName, resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + PoolName: new("node-1"), + }), + }, + "valid-with-limit": { + request: testResourcePoolStatusRequest(goodName, resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + Limit: new(int32(50)), + }), + }, + "valid-limit-min": { + request: testResourcePoolStatusRequest(goodName, resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + Limit: new(int32(1)), + }), + }, + "valid-limit-max": { + request: testResourcePoolStatusRequest(goodName, resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + Limit: new(int32(1000)), + }), + }, + "missing-name": { + request: testResourcePoolStatusRequest("", validResourcePoolStatusRequestSpec), + wantFailures: field.ErrorList{field.Required(field.NewPath("metadata", "name"), "name or generateName is required")}, + }, + "bad-name": { + request: testResourcePoolStatusRequest(badName, validResourcePoolStatusRequestSpec), + wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), badName, dns1123SubdomainErrorMsg)}, + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + errs := ValidateResourcePoolStatusRequest(scenario.request) + assertFailures(t, scenario.wantFailures, errs) + }) + } +} + +func TestValidateResourcePoolStatusRequestUpdate(t *testing.T) { + goodName := "my-request" + + scenarios := map[string]struct { + oldRequest *resource.ResourcePoolStatusRequest + newRequest *resource.ResourcePoolStatusRequest + wantFailures field.ErrorList + }{ + "no-change": { + oldRequest: testResourcePoolStatusRequestForUpdate(goodName, validResourcePoolStatusRequestSpec), + newRequest: testResourcePoolStatusRequestForUpdate(goodName, validResourcePoolStatusRequestSpec), + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + errs := ValidateResourcePoolStatusRequestUpdate(scenario.newRequest, scenario.oldRequest) + assertFailures(t, scenario.wantFailures, errs) + }) + } +} + +func TestValidateResourcePoolStatusRequestStatusUpdate(t *testing.T) { + goodName := "my-request" + now := metav1.Now() + + baseRequest := func() *resource.ResourcePoolStatusRequest { + r := testResourcePoolStatusRequestForUpdate(goodName, validResourcePoolStatusRequestSpec) + return r + } + + scenarios := map[string]struct { + oldRequest *resource.ResourcePoolStatusRequest + newRequest *resource.ResourcePoolStatusRequest + wantFailures field.ErrorList + }{ + "valid-initial-status-update": { + oldRequest: baseRequest(), + newRequest: func() *resource.ResourcePoolStatusRequest { + r := baseRequest() + r.Status = &resource.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(2)), + Pools: []resource.PoolStatus{ + { + Driver: "test.example.com", + PoolName: "pool-1", + NodeName: new("node-1"), + TotalDevices: new(int32(4)), + AllocatedDevices: new(int32(2)), + AvailableDevices: new(int32(2)), + UnavailableDevices: new(int32(0)), + ResourceSliceCount: new(int32(1)), + Generation: 1, + }, + }, + Conditions: []metav1.Condition{ + { + Type: "Complete", + Status: metav1.ConditionTrue, + LastTransitionTime: now, + Reason: "Calculated", + }, + }, + } + return r + }(), + }, + "valid-pool-with-validation-error": { + oldRequest: baseRequest(), + newRequest: func() *resource.ResourcePoolStatusRequest { + r := baseRequest() + validationErr := "pool data is inconsistent" + r.Status = &resource.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(1)), + Pools: []resource.PoolStatus{ + { + Driver: "test.example.com", + PoolName: "pool-1", + Generation: 1, + ValidationError: &validationErr, + }, + }, + Conditions: []metav1.Condition{ + { + Type: "Complete", + Status: metav1.ConditionTrue, + LastTransitionTime: now, + Reason: "Calculated", + }, + }, + } + return r + }(), + }, + "status-immutable-after-status-set": { + oldRequest: func() *resource.ResourcePoolStatusRequest { + r := baseRequest() + r.Status = &resource.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(2)), + } + return r + }(), + newRequest: func() *resource.ResourcePoolStatusRequest { + r := baseRequest() + r.Status = &resource.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(5)), // changed + } + return r + }(), + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("status"), + &resource.ResourcePoolStatusRequestStatus{PoolCount: new(int32(5))}, + "field is immutable"), + }, + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + errs := ValidateResourcePoolStatusRequestStatusUpdate(scenario.newRequest, scenario.oldRequest) + assertFailures(t, scenario.wantFailures, errs) + }) + } +} + +// new is a generic helper to create a pointer to a value. +func new[T any](v T) *T { + return &v +} diff --git a/pkg/apis/resource/validation/validation_resourceslice_test.go b/pkg/apis/resource/validation/validation_resourceslice_test.go index 7a867b6bdd538..e79e663fe67bf 100644 --- a/pkg/apis/resource/validation/validation_resourceslice_test.go +++ b/pkg/apis/resource/validation/validation_resourceslice_test.go @@ -18,9 +18,11 @@ package validation import ( "fmt" + "slices" "strings" "testing" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" @@ -77,9 +79,9 @@ func testResourceSlice(name, nodeName, driverName string, numDevices int) *resou }, }, } - for i := 0; i < numDevices; i++ { + for d := range numDevices { device := resourceapi.Device{ - Name: fmt.Sprintf("device-%d", i), + Name: fmt.Sprintf("device-%d", d), Attributes: testAttributes(), Capacity: testCapacity(), } @@ -110,6 +112,47 @@ func testResourceSliceWithBindingConditions(name, nodeName, driverName string, n return slice } +func testNodeAllocatableResourceCapacity() map[resourceapi.QualifiedName]resourceapi.DeviceCapacity { + return map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{ + "dra.example.com/cpu": {Value: resource.MustParse("100")}, + "dra.example.com/memory": {Value: resource.MustParse("100Gi")}, + } +} + +func testResourceSliceWithNodeAllocatableResources(name, nodeName, driverName string, numDevices int) *resourceapi.ResourceSlice { + slice := &resourceapi.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourceapi.ResourceSliceSpec{ + NodeName: &nodeName, + Driver: driverName, + Pool: resourceapi.ResourcePool{ + Name: nodeName, + ResourceSliceCount: 1, + }, + }, + } + for d := range numDevices { + device := resourceapi.Device{ + Name: fmt.Sprintf("device-%d", d), + Attributes: testAttributes(), + Capacity: testNodeAllocatableResourceCapacity(), + NodeAllocatableResourceMappings: map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + AllocationMultiplier: ptr.To(resource.MustParse("1")), + CapacityKey: ptr.To[resourceapi.QualifiedName]("dra.example.com/cpu"), + }, + "memory": { + CapacityKey: ptr.To[resourceapi.QualifiedName]("dra.example.com/cpu"), + }, + }, + } + slice.Spec.Devices = append(slice.Spec.Devices, device) + } + return slice +} + func TestValidateResourceSlice(t *testing.T) { goodName := "foo" badName := "!@#$%^" @@ -117,10 +160,33 @@ func TestValidateResourceSlice(t *testing.T) { now := metav1.Now() badValue := "spaces not allowed" + badMultipleListValue := resourceapi.DeviceAttribute{ + IntValues: []int64{1}, BoolValues: []bool{true}, + } + badMultipleListValueWithEmptyList := resourceapi.DeviceAttribute{ + IntValues: []int64{1}, BoolValues: []bool{true}, StringValues: []string{}, + } + badListStringValueTooLong := resourceapi.DeviceAttribute{ + StringValues: []string{strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1)}, + } + badListVersionValueTooLong := resourceapi.DeviceAttribute{ + VersionValues: []string{strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1)}, + } + badListAttributeTooManyValueWithMixedTypes := resourceapi.DeviceAttribute{ + // (ResourceSliceMaxAttributeValuesPerDevice+1) values in single attribute key with multiple types. + // note: this causes "exactly one value must be specified" error + // in addition to "the total number of attribute values must not exceed" error. + StringValues: slices.Repeat([]string{"x"}, resourceapi.ResourceSliceMaxAttributeValuesPerDevice-2), + IntValues: []int64{int64(1)}, + BoolValues: []bool{true}, + VersionValues: []string{"1.0.0"}, + } + scenarios := map[string]struct { - slice *resourceapi.ResourceSlice - wantFailures field.ErrorList - consumableCapacityFeatureGate bool + slice *resourceapi.ResourceSlice + wantFailures field.ErrorList + consumableCapacityFeatureGate bool + enableDRANodeAllocatableResourcesFeatureGate bool }{ "good": { slice: testResourceSlice(goodName, goodName, driverName, resourceapi.ResourceSliceMaxDevices), @@ -131,7 +197,7 @@ func TestValidateResourceSlice(t *testing.T) { }, "good-taints": { slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) for i := range slice.Spec.Devices { slice.Spec.Devices[i].Taints = []resourceapi.DeviceTaint{{Key: "example.com/taint", Effect: resourceapi.DeviceTaintEffectNoExecute}} } @@ -139,9 +205,9 @@ func TestValidateResourceSlice(t *testing.T) { }(), }, "too-large-taints": { - wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)}, + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) for i := range slice.Spec.Devices { slice.Spec.Devices[i].Taints = []resourceapi.DeviceTaint{{Key: "example.com/taint", Effect: resourceapi.DeviceTaintEffectNoExecute}} } @@ -390,7 +456,7 @@ func TestValidateResourceSlice(t *testing.T) { }, "bad-attribute": { wantFailures: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "", "exactly one value must be specified").MarkCoveredByDeclarative(), field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified").MarkCoveredByDeclarative(), field.Invalid(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), "must be a string compatible with semver.org spec 2.0.0"), @@ -414,6 +480,138 @@ func TestValidateResourceSlice(t *testing.T) { return slice }(), }, + "good-list-attribute": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, 4) + slice.Spec.Devices[0].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {BoolValues: []bool{true}}, + } + slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {IntValues: []int64{1}}, + } + slice.Spec.Devices[2].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {StringValues: []string{"x"}}, + } + slice.Spec.Devices[3].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {VersionValues: []string{"1.2.3"}}, + } + return slice + }(), + }, + "bad-list-attribute": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key(goodName), badMultipleListValue, "exactly one value must be specified").WithOrigin("union").MarkCoveredByDeclarative(), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(goodName).Child("strings"), []string{}, "must not be empty if specified"), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(goodName), badMultipleListValueWithEmptyList, "exactly one value must be specified").WithOrigin("union").MarkCoveredByDeclarative(), + field.TooLongMaxLength(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName).Child("strings").Index(0), badListStringValueTooLong.StringValues[0], resourceapi.DeviceAttributeMaxValueLength), + field.Invalid(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("versions").Index(0), badListVersionValueTooLong.VersionValues[0], "must be a string compatible with semver.org spec 2.0.0"), + field.TooLongMaxLength(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("versions").Index(0), badListVersionValueTooLong.VersionValues[0], resourceapi.DeviceAttributeMaxValueLength), + field.Invalid(field.NewPath("spec", "devices").Index(4).Child("attributes").Key(goodName).Child("bools"), []bool{}, "must not be empty if specified"), + field.Invalid(field.NewPath("spec", "devices").Index(5).Child("attributes").Key(goodName).Child("ints"), []int64{}, "must not be empty if specified"), + field.Invalid(field.NewPath("spec", "devices").Index(6).Child("attributes").Key(goodName).Child("strings"), []string{}, "must not be empty if specified"), + field.Invalid(field.NewPath("spec", "devices").Index(7).Child("attributes").Key(goodName).Child("versions"), []string{}, "must not be empty if specified"), + }, + + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, 8) + // multiple list values specified for single attribute key + slice.Spec.Devices[0].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): badMultipleListValue, + } + slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): badMultipleListValueWithEmptyList, + } + // max length violations for list values(string and version) + slice.Spec.Devices[2].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): badListStringValueTooLong, + } + slice.Spec.Devices[3].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): badListVersionValueTooLong, + } + // non-nil & empty list values + slice.Spec.Devices[4].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {BoolValues: []bool{}}, + } + slice.Spec.Devices[5].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {IntValues: []int64{}}, + } + slice.Spec.Devices[6].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {StringValues: []string{}}, + } + slice.Spec.Devices[7].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName(goodName): {VersionValues: []string{}}, + } + return slice + }(), + }, + "max-devices-with-list-of-int-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["ints"] = resourceapi.DeviceAttribute{IntValues: []int64{1, 2, 3}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-int-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["ints"] = resourceapi.DeviceAttribute{IntValues: []int64{1, 2, 3}} + return slice + }(), + }, + "max-devices-with-list-of-bool-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["bools"] = resourceapi.DeviceAttribute{BoolValues: []bool{true, false, true}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-bool-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["bools"] = resourceapi.DeviceAttribute{BoolValues: []bool{true, false, true}} + return slice + }(), + }, + "max-devices-with-list-of-string-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["strings"] = resourceapi.DeviceAttribute{StringValues: []string{"a", "b", "c"}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-string-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["strings"] = resourceapi.DeviceAttribute{StringValues: []string{"a", "b", "c"}} + return slice + }(), + }, + "max-devices-with-list-of-version-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["versions"] = resourceapi.DeviceAttribute{VersionValues: []string{"1.0.0", "2.0.0", "3.0.0"}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-version-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["versions"] = resourceapi.DeviceAttribute{VersionValues: []string{"1.0.0", "2.0.0", "3.0.0"}} + return slice + }(), + }, "good-attribute-names": { slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 2) @@ -426,8 +624,8 @@ func TestValidateResourceSlice(t *testing.T) { }, "bad-attribute-c-identifier": { wantFailures: field.ErrorList{ - field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength), - field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), + field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), }, slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 2) @@ -439,8 +637,8 @@ func TestValidateResourceSlice(t *testing.T) { }, "bad-attribute-domain": { wantFailures: field.ErrorList{ - field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength), - field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), }, slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 2) @@ -452,8 +650,8 @@ func TestValidateResourceSlice(t *testing.T) { }, "bad-key-too-long": { wantFailures: field.ErrorList{ - field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength), - field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("y", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength), + field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength), + field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("y", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength), }, slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 2) @@ -465,8 +663,8 @@ func TestValidateResourceSlice(t *testing.T) { }, "bad-attribute-empty-domain-and-c-identifier": { wantFailures: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("/"), "", "the domain must not be empty"), - field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("/"), "", "the name must not be empty"), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), "", "the domain must not be empty"), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), "", "the name must not be empty"), }, slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 2) @@ -483,7 +681,7 @@ func TestValidateResourceSlice(t *testing.T) { slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 5) slice.Spec.Devices[0].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{} - slice.Spec.Devices[0].Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{} + slice.Spec.Devices[0].Capacity = nil for i := 0; i < resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice; i++ { slice.Spec.Devices[0].Attributes[resourceapi.QualifiedName(fmt.Sprintf("attr_%d", i))] = resourceapi.DeviceAttribute{StringValue: ptr.To("x")} } @@ -503,6 +701,117 @@ func TestValidateResourceSlice(t *testing.T) { return slice }(), }, + "max-attribute-values-with-list": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(1), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(2), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(3), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(4), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(5), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(6), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(7), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(8), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(9), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(9).Child("attributes").Key("mixed_list_attrs"), badListAttributeTooManyValueWithMixedTypes, "exactly one value must be specified").MarkCoveredByDeclarative(), + field.Invalid(field.NewPath("spec", "devices").Index(10), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(11), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(12), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + field.Invalid(field.NewPath("spec", "devices").Index(13), resourceapi.ResourceSliceMaxAttributeValuesPerDevice+1, fmt.Sprintf("the total number of attribute values must not exceed %d", resourceapi.ResourceSliceMaxAttributeValuesPerDevice)), + }, + slice: func() *resourceapi.ResourceSlice { + maxStringAttributeValuesInList := func(maxAttributeValues int) map[resourceapi.QualifiedName]resourceapi.DeviceAttribute { + attributes := map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{} + // (ResourceSliceMaxAttributesAndCapacitiesPerDevice - 1) attribute keys with (ResourceSliceMaxAttributeValuesPerDevice) values + // to make room for one additional attribute key with one value so that not hitting the ResourceSliceMaxAttributesAndCapacitiesPerDevice limit, + // while hitting the ResourceSliceMaxAttributeValuesPerDevice limit. + for i := range resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice - 1 { + stringList := []string{"x"} + if i == 0 { + // The first list gets as many additional strings as allowed by the overall value limit. + stringList = slices.Repeat(stringList, maxAttributeValues-(resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice-2)) + } + attributes[resourceapi.QualifiedName(fmt.Sprintf("list_attr_%d", i))] = resourceapi.DeviceAttribute{ + StringValues: stringList, + } + } + return attributes + } + + slice := testResourceSlice(goodName, goodName, goodName, 14) + + // success: ResourceSliceMaxAttributeValues attributes with string list values + slice.Spec.Devices[0].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[0].Capacity = nil + + // error: extra int(scalar) attribute beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[1].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[1].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{IntValue: new(int64(1))} + slice.Spec.Devices[1].Capacity = nil + + // error: extra bool(scalar) attribute beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[2].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[2].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{BoolValue: new(true)} + slice.Spec.Devices[2].Capacity = nil + + // error: extra string(scalar) attribute beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[3].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[3].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{StringValue: ptr.To("x")} + slice.Spec.Devices[3].Capacity = nil + + // error: extra version(scalar) attribute beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[4].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[4].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{VersionValue: ptr.To("1.0.0")} + slice.Spec.Devices[4].Capacity = nil + + // error: extra int attribute(list) beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[5].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[5].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{IntValues: []int64{int64(1)}} + slice.Spec.Devices[5].Capacity = nil + + // error: extra bool attribute(list) beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[6].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[6].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{BoolValues: []bool{true}} + slice.Spec.Devices[6].Capacity = nil + + // error: extra string attribute(list) beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[7].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[7].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{StringValues: []string{"extra"}} + slice.Spec.Devices[7].Capacity = nil + + // error: extra version attribute(list) beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[8].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice) + slice.Spec.Devices[8].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{VersionValues: []string{"1.0.0"}} + slice.Spec.Devices[8].Capacity = nil + + // error: multiple types of list in single attribute beyond ResourceSliceMaxAttributeValues + slice.Spec.Devices[9].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + resourceapi.QualifiedName("mixed_list_attrs"): badListAttributeTooManyValueWithMixedTypes, + } + slice.Spec.Devices[9].Capacity = nil + + // error: extra bool values (list length 2) beyond ResourceSliceMaxAttributeValues. + // This verifies that BoolValues are counted by list length, not as a scalar fallback. + slice.Spec.Devices[10].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice - 1) + slice.Spec.Devices[10].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{BoolValues: []bool{true, false}} + slice.Spec.Devices[10].Capacity = nil + + // error: extra int values (list length 2) beyond ResourceSliceMaxAttributeValues. + slice.Spec.Devices[11].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice - 1) + slice.Spec.Devices[11].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{IntValues: []int64{1, 2}} + slice.Spec.Devices[11].Capacity = nil + + // error: extra string values (list length 2) beyond ResourceSliceMaxAttributeValues. + slice.Spec.Devices[12].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice - 1) + slice.Spec.Devices[12].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{StringValues: []string{"a", "b"}} + slice.Spec.Devices[12].Capacity = nil + + // error: extra version values (list length 2) beyond ResourceSliceMaxAttributeValues. + slice.Spec.Devices[13].Attributes = maxStringAttributeValuesInList(resourceapi.ResourceSliceMaxAttributeValuesPerDevice - 1) + slice.Spec.Devices[13].Attributes[resourceapi.QualifiedName("extra_attr")] = resourceapi.DeviceAttribute{VersionValues: []string{"1.0.0", "1.0.1"}} + slice.Spec.Devices[13].Capacity = nil + return slice + }(), + }, "forbidden-request-policy-on-single-allocatable-capacity": { wantFailures: field.ErrorList{ field.Forbidden(field.NewPath("spec", "devices").Index(1).Child("capacity").Key("cap").Child("requestPolicy"), "allowMultipleAllocations must be true"), @@ -746,7 +1055,7 @@ func TestValidateResourceSlice(t *testing.T) { }, "bad-countername-shared-counters": { wantFailures: field.ErrorList{ - field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters").Key(badName), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), + field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')").MarkCoveredByDeclarative(), }, slice: func() *resourceapi.ResourceSlice { slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1) @@ -929,7 +1238,7 @@ func TestValidateResourceSlice(t *testing.T) { }, "max-number-of-devices-with-consumes-counters": { slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) for i := range slice.Spec.Devices { slice.Spec.Devices[i].ConsumesCounters = []resourceapi.DeviceCounterConsumption{ { @@ -942,9 +1251,9 @@ func TestValidateResourceSlice(t *testing.T) { }(), }, "too-many-devices-with-consumes-counters": { - wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)}, + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{ { CounterSet: "counterset-0", @@ -993,11 +1302,136 @@ func TestValidateResourceSlice(t *testing.T) { wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions").Index(0), "condition1", "bindingFailureConditions must not overlap with bindingConditions")}, slice: testResourceSliceWithBindingConditions(goodName, goodName, driverName, 1, []string{"condition1", "condition2"}, []string{"condition1", "condition3"}), }, + "correct-node-allocatable-resource-mappings": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "node-allocatable-resource-mappings-both-quantityfrom": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: ptr.To[resourceapi.QualifiedName]("dra.example.com/cpu"), + AllocationMultiplier: ptr.To(resource.MustParse("1")), + }, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "bad-node-allocatable-resource-mappings-no-quantityfrom": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key(string(v1.ResourceCPU)), "", "at least one of allocationMultiplier or capacityKey must be set"), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: {}, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "bad-node-allocatable-resource-mappings-capacityKey-empty": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key(string(v1.ResourceCPU)).Child("capacityKey"), "", "capacityKey must not be an empty string"), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: ptr.To[resourceapi.QualifiedName](""), + }, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "bad-node-allocatable-resource-mappings-capacity-key-not-found": { + wantFailures: field.ErrorList{ + field.NotFound(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key(string(v1.ResourceCPU)).Child("capacityKey"), resourceapi.QualifiedName("nonexistent")), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: ptr.To[resourceapi.QualifiedName]("nonexistent"), + }, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "bad-node-allocatable-resource-mappings-invalid-allocation-multiplier-negative": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key(string(v1.ResourceCPU)).Child("allocationMultiplier"), "-1", "must be positive"), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: {AllocationMultiplier: ptr.To(resource.MustParse("-1"))}, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "bad-node-allocatable-resource-mappings-invalid-allocation-multiplier-zero": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key(string(v1.ResourceCPU)).Child("allocationMultiplier"), "0", "must be positive"), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: {AllocationMultiplier: ptr.To(resource.MustParse("0"))}, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + }, + "capacity key not in spec.devices[].capacity": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceMemory: { + CapacityKey: ptr.To[resourceapi.QualifiedName]("dra.example.com/hugepages"), + AllocationMultiplier: ptr.To(resource.MustParse("1")), + }, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + wantFailures: field.ErrorList{ + field.NotFound(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key(string(v1.ResourceMemory)).Child("capacityKey"), resourceapi.QualifiedName("dra.example.com/hugepages")), + }, + }, + "mapped resource is not a node allocatable resource": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSliceWithNodeAllocatableResources(goodName, goodName, driverName, 1) + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceName("dra.example.com/gpu"): { + CapacityKey: ptr.To[resourceapi.QualifiedName]("dra.example.com/cpu"), + AllocationMultiplier: ptr.To(resource.MustParse("1")), + }, + } + return slice + }(), + enableDRANodeAllocatableResourcesFeatureGate: true, + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("nodeAllocatableResourceMappings").Key("dra.example.com/gpu"), v1.ResourceName("dra.example.com/gpu"), "must be a node allocatable resource name"), + }, + }, } for name, scenario := range scenarios { t.Run(name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAConsumableCapacity, scenario.consumableCapacityFeatureGate) + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRANodeAllocatableResources: scenario.enableDRANodeAllocatableResourcesFeatureGate, + features.DRAConsumableCapacity: scenario.consumableCapacityFeatureGate, + }) errs := ValidateResourceSlice(scenario.slice) assertFailures(t, scenario.wantFailures, errs) }) diff --git a/pkg/apis/resource/zz_generated.deepcopy.go b/pkg/apis/resource/zz_generated.deepcopy.go index 1b869906e0556..33ec81b713add 100644 --- a/pkg/apis/resource/zz_generated.deepcopy.go +++ b/pkg/apis/resource/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package resource import ( + corev1 "k8s.io/api/core/v1" apiresource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -302,6 +303,13 @@ func (in *Device) DeepCopyInto(out *Device) { *out = new(bool) **out = **in } + if in.NodeAllocatableResourceMappings != nil { + in, out := &in.NodeAllocatableResourceMappings, &out.NodeAllocatableResourceMappings + *out = make(map[corev1.ResourceName]NodeAllocatableResourceMapping, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } return } @@ -390,6 +398,26 @@ func (in *DeviceAttribute) DeepCopyInto(out *DeviceAttribute) { *out = new(string) **out = **in } + if in.IntValues != nil { + in, out := &in.IntValues, &out.IntValues + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.BoolValues != nil { + in, out := &in.BoolValues, &out.BoolValues + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.StringValues != nil { + in, out := &in.StringValues, &out.StringValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.VersionValues != nil { + in, out := &in.VersionValues, &out.VersionValues + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1044,6 +1072,32 @@ func (in *NetworkDeviceData) DeepCopy() *NetworkDeviceData { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAllocatableResourceMapping) DeepCopyInto(out *NodeAllocatableResourceMapping) { + *out = *in + if in.CapacityKey != nil { + in, out := &in.CapacityKey, &out.CapacityKey + *out = new(QualifiedName) + **out = **in + } + if in.AllocationMultiplier != nil { + in, out := &in.AllocationMultiplier, &out.AllocationMultiplier + x := (*in).DeepCopy() + *out = &x + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAllocatableResourceMapping. +func (in *NodeAllocatableResourceMapping) DeepCopy() *NodeAllocatableResourceMapping { + if in == nil { + return nil + } + out := new(NodeAllocatableResourceMapping) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpaqueDeviceConfiguration) DeepCopyInto(out *OpaqueDeviceConfiguration) { *out = *in @@ -1061,6 +1115,57 @@ func (in *OpaqueDeviceConfiguration) DeepCopy() *OpaqueDeviceConfiguration { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PoolStatus) DeepCopyInto(out *PoolStatus) { + *out = *in + if in.ResourceSliceCount != nil { + in, out := &in.ResourceSliceCount, &out.ResourceSliceCount + *out = new(int32) + **out = **in + } + if in.TotalDevices != nil { + in, out := &in.TotalDevices, &out.TotalDevices + *out = new(int32) + **out = **in + } + if in.AllocatedDevices != nil { + in, out := &in.AllocatedDevices, &out.AllocatedDevices + *out = new(int32) + **out = **in + } + if in.AvailableDevices != nil { + in, out := &in.AvailableDevices, &out.AvailableDevices + *out = new(int32) + **out = **in + } + if in.UnavailableDevices != nil { + in, out := &in.UnavailableDevices, &out.UnavailableDevices + *out = new(int32) + **out = **in + } + if in.NodeName != nil { + in, out := &in.NodeName, &out.NodeName + *out = new(string) + **out = **in + } + if in.ValidationError != nil { + in, out := &in.ValidationError, &out.ValidationError + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PoolStatus. +func (in *PoolStatus) DeepCopy() *PoolStatus { + if in == nil { + return nil + } + out := new(PoolStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceClaim) DeepCopyInto(out *ResourceClaim) { *out = *in @@ -1282,6 +1387,132 @@ func (in *ResourcePool) DeepCopy() *ResourcePool { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequest) DeepCopyInto(out *ResourcePoolStatusRequest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(ResourcePoolStatusRequestStatus) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequest. +func (in *ResourcePoolStatusRequest) DeepCopy() *ResourcePoolStatusRequest { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourcePoolStatusRequest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequestList) DeepCopyInto(out *ResourcePoolStatusRequestList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ResourcePoolStatusRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequestList. +func (in *ResourcePoolStatusRequestList) DeepCopy() *ResourcePoolStatusRequestList { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequestList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourcePoolStatusRequestList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequestSpec) DeepCopyInto(out *ResourcePoolStatusRequestSpec) { + *out = *in + if in.PoolName != nil { + in, out := &in.PoolName, &out.PoolName + *out = new(string) + **out = **in + } + if in.Limit != nil { + in, out := &in.Limit, &out.Limit + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequestSpec. +func (in *ResourcePoolStatusRequestSpec) DeepCopy() *ResourcePoolStatusRequestSpec { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequestSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequestStatus) DeepCopyInto(out *ResourcePoolStatusRequestStatus) { + *out = *in + if in.PoolCount != nil { + in, out := &in.PoolCount, &out.PoolCount + *out = new(int32) + **out = **in + } + if in.Pools != nil { + in, out := &in.Pools, &out.Pools + *out = make([]PoolStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequestStatus. +func (in *ResourcePoolStatusRequestStatus) DeepCopy() *ResourcePoolStatusRequestStatus { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequestStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceSlice) DeepCopyInto(out *ResourceSlice) { *out = *in diff --git a/pkg/apis/scheduling/fuzzer/fuzzer.go b/pkg/apis/scheduling/fuzzer/fuzzer.go index 794723bc93c76..8142ef2e48caa 100644 --- a/pkg/apis/scheduling/fuzzer/fuzzer.go +++ b/pkg/apis/scheduling/fuzzer/fuzzer.go @@ -33,5 +33,11 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { s.PreemptionPolicy = &preemptLowerPriority } }, + func(s *scheduling.PodGroup, c randfill.Continue) { + c.FillNoCustom(s) + if s.Spec.DisruptionMode == nil { + s.Spec.DisruptionMode = new(scheduling.DisruptionModePod) + } + }, } } diff --git a/pkg/apis/scheduling/install/install.go b/pkg/apis/scheduling/install/install.go index e40810b3336df..a07c37e8adcfa 100644 --- a/pkg/apis/scheduling/install/install.go +++ b/pkg/apis/scheduling/install/install.go @@ -23,8 +23,8 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/scheduling" - "k8s.io/kubernetes/pkg/apis/scheduling/v1" - "k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1" + v1 "k8s.io/kubernetes/pkg/apis/scheduling/v1" + "k8s.io/kubernetes/pkg/apis/scheduling/v1alpha2" "k8s.io/kubernetes/pkg/apis/scheduling/v1beta1" ) @@ -37,6 +37,6 @@ func Install(scheme *runtime.Scheme) { utilruntime.Must(scheduling.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) utilruntime.Must(v1beta1.AddToScheme(scheme)) - utilruntime.Must(v1alpha1.AddToScheme(scheme)) - utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion, v1alpha1.SchemeGroupVersion)) + utilruntime.Must(v1alpha2.AddToScheme(scheme)) + utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion, v1alpha2.SchemeGroupVersion)) } diff --git a/pkg/apis/scheduling/register.go b/pkg/apis/scheduling/register.go index ae4c71a6ca473..f36459b279bd4 100644 --- a/pkg/apis/scheduling/register.go +++ b/pkg/apis/scheduling/register.go @@ -46,6 +46,8 @@ var ( func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, + &PodGroup{}, + &PodGroupList{}, &PriorityClass{}, &PriorityClassList{}, &Workload{}, diff --git a/pkg/apis/scheduling/types.go b/pkg/apis/scheduling/types.go index 12730404c5f76..dd7950f702b10 100644 --- a/pkg/apis/scheduling/types.go +++ b/pkg/apis/scheduling/types.go @@ -39,6 +39,10 @@ const ( SystemClusterCritical = SystemPriorityClassPrefix + "cluster-critical" // SystemNodeCritical is the system priority class name that represents node-critical. SystemNodeCritical = SystemPriorityClassPrefix + "node-critical" + + // PodGroupProtectionFinalizer is the finalizer added to PodGroups to prevent + // premature deletion while pods still reference them. + PodGroupProtectionFinalizer = GroupName + "/podgroup-protection" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -92,12 +96,13 @@ type PriorityClassList struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Workload allows for expressing scheduling constraints that should be used -// when managing lifecycle of workloads from scheduling perspective, +// when managing the lifecycle of workloads from the scheduling perspective, // including scheduling, preemption, eviction and other phases. +// Workload API enablement is toggled by the GenericWorkload feature gate. type Workload struct { metav1.TypeMeta // Standard object's metadata. - // Name must be a DNS subdomain. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // // +optional metav1.ObjectMeta @@ -122,26 +127,26 @@ type WorkloadList struct { Items []Workload } -// WorkloadMaxPodGroups is the maximum number of pod groups per Workload. -const WorkloadMaxPodGroups = 8 +// WorkloadMaxPodGroupTemplates is the maximum number of pod group templates per Workload. +const WorkloadMaxPodGroupTemplates = 8 // WorkloadSpec defines the desired state of a Workload. type WorkloadSpec struct { // ControllerRef is an optional reference to the controlling object, such as a // Deployment or Job. This field is intended for use by tools like CLIs // to provide a link back to the original workload definition. - // When set, it cannot be changed. + // This field is immutable. // // +optional ControllerRef *TypedLocalObjectReference - // PodGroups is the list of pod groups that make up the Workload. - // The maximum number of pod groups is 8. This field is immutable. + // PodGroupTemplates is the list of templates that make up the Workload. + // The maximum number of templates is 8. This field is immutable. // // +required // +listType=map // +listMapKey=name - PodGroups []PodGroup + PodGroupTemplates []PodGroupTemplate } // TypedLocalObjectReference allows to reference typed object inside the same namespace. @@ -165,34 +170,96 @@ type TypedLocalObjectReference struct { Name string } -// PodGroup represents a set of pods with a common scheduling policy. -type PodGroup struct { - // Name is a unique identifier for the PodGroup within the Workload. +// MaxPodGroupResourceClaims is the maximum number of resource claims for a +// PodGroup or a Workload's PodGroupTemplate. +const MaxPodGroupResourceClaims = 4 + +// PodGroupTemplate represents a template for a set of pods with a scheduling policy. +type PodGroupTemplate struct { + // Name is a unique identifier for the PodGroupTemplate within the Workload. // It must be a DNS label. This field is immutable. // // +required Name string - // Policy defines the scheduling policy for this PodGroup. + // SchedulingPolicy defines the scheduling policy for this PodGroupTemplate. // // +required - Policy PodGroupPolicy + SchedulingPolicy PodGroupSchedulingPolicy + + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + // + // +optional + // +featureGate=TopologyAwareWorkloadScheduling + SchedulingConstraints *PodGroupSchedulingConstraints + + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + // + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +featureGate=DRAWorkloadResourceClaims + ResourceClaims []PodGroupResourceClaim + + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // One of Pod, PodGroup. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + DisruptionMode *DisruptionMode + + // PriorityClassName indicates the priority that should be considered when scheduling + // a pod group created from this template. If no priority class is specified, admission + // control can set this to the global default priority class if it exists. Otherwise, + // pod groups created from this template will have the priority set to zero. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + PriorityClassName string + + // Priority is the value of priority of pod groups created from this template. Various + // system components use this field to find the priority of the pod group. When + // Priority Admission Controller is enabled, it prevents users from setting this field. + // The admission controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + Priority *int32 } -// PodGroupPolicy defines the scheduling configuration for a PodGroup. -type PodGroupPolicy struct { +// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. +// Exactly one policy must be set. +// +union +type PodGroupSchedulingPolicy struct { // Basic specifies that the pods in this group should be scheduled using // standard Kubernetes scheduling behavior. // // +optional - // +oneOf=PolicySelection Basic *BasicSchedulingPolicy // Gang specifies that the pods in this group should be scheduled using // all-or-nothing semantics. // // +optional - // +oneOf=PolicySelection Gang *GangSchedulingPolicy } @@ -214,3 +281,302 @@ type GangSchedulingPolicy struct { // +required MinCount int32 } + +// PodGroupResourceClaim references exactly one ResourceClaim, either directly +// or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim +// for the PodGroup. +// +// It adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. +// Pods that need access to the ResourceClaim define a matching reference in its +// own Spec.ResourceClaims. The Pod's claim must match all fields of the +// PodGroup's claim exactly. +type PodGroupResourceClaim struct { + // Name uniquely identifies this resource claim inside the PodGroup. + // This must be a DNS_LABEL. + // + // +required + Name string + + // ResourceClaimName is the name of a ResourceClaim object in the same + // namespace as this PodGroup. The ResourceClaim will be reserved for the + // PodGroup instead of its individual pods. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + // + // +optional + ResourceClaimName *string + + // ResourceClaimTemplateName is the name of a ResourceClaimTemplate + // object in the same namespace as this PodGroup. + // + // The template will be used to create a new ResourceClaim, which will + // be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim + // will also be deleted. The PodGroup name and resource name, along with a + // generated component, will be used to form a unique name for the + // ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses. + // + // This field is immutable and no changes will be made to the + // corresponding ResourceClaim by the control plane after creating the + // ResourceClaim. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + // + // +optional + ResourceClaimTemplateName *string +} + +// DisruptionMode describes the mode in which a PodGroup can be disrupted (e.g. preempted). +// +enum +type DisruptionMode string + +const ( + // DisruptionModePod means that individual pods can be disrupted or preempted independently. + // It doesn't depend on exact set of pods currently running in this PodGroup. + DisruptionModePod DisruptionMode = "Pod" + // DisruptionModePodGroup means that the whole PodGroup needs to be disrupted + // or preempted together. + DisruptionModePodGroup DisruptionMode = "PodGroup" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodGroup represents a runtime instance of pods grouped together. +// PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from +// Workload.podGroupTemplates. +// PodGroup API enablement is toggled by the GenericWorkload feature gate. +type PodGroup struct { + metav1.TypeMeta + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // + // +optional + metav1.ObjectMeta + + // Spec defines the desired state of the PodGroup. + // + // +required + Spec PodGroupSpec + + // Status represents the current observed state of the PodGroup. + // + // +optional + Status PodGroupStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodGroupList contains a list of PodGroup resources. +type PodGroupList struct { + metav1.TypeMeta + // Standard list metadata. + // + // +optional + metav1.ListMeta + + // Items is the list of PodGroups. + Items []PodGroup +} + +// PodGroupSpec defines the desired state of a PodGroup. +type PodGroupSpec struct { + // PodGroupTemplateRef references an optional PodGroup template within other object + // (e.g. Workload) that was used to create the PodGroup. This field is immutable. + // + // +optional + PodGroupTemplateRef *PodGroupTemplateReference + + // SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // + // +required + SchedulingPolicy PodGroupSchedulingPolicy + + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + // + // +optional + // +featureGate=TopologyAwareWorkloadScheduling + SchedulingConstraints *PodGroupSchedulingConstraints + + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods must reference these claims in order to consume the + // allocated devices. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + // + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +featureGate=DRAWorkloadResourceClaims + ResourceClaims []PodGroupResourceClaim + + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // One of Pod, PodGroup. Defaults to Pod if unset. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + DisruptionMode *DisruptionMode + + // PriorityClassName defines the priority that should be considered when scheduling this pod group. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate + // (i.e. if no priority class is specified, admission control can set this to the global default + // priority class if it exists. Otherwise, the pod group's priority will be zero). + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + PriorityClassName string + + // Priority is the value of priority of this pod group. Various system components + // use this field to find the priority of the pod group. When Priority Admission + // Controller is enabled, it prevents users from setting this field. The admission + // controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + Priority *int32 +} + +// PodGroupStatus represents information about the status of a pod group. +type PodGroupStatus struct { + // Conditions represent the latest observations of the PodGroup's state. + // + // Known condition types: + // - "PodGroupScheduled": Indicates whether the scheduling requirement has been satisfied. + // - "DisruptionTarget": Indicates whether the PodGroup is about to be terminated + // due to disruption such as preemption. + // + // Known reasons for the PodGroupScheduled condition: + // - "Unschedulable": The PodGroup cannot be scheduled due to resource constraints, + // affinity/anti-affinity rules, or insufficient capacity for the gang. + // - "SchedulerError": The PodGroup cannot be scheduled due to some internal error + // that happened during scheduling, for example due to nodeAffinity parsing errors. + // + // Known reasons for the DisruptionTarget condition: + // - "PreemptionByScheduler": The PodGroup was preempted by the scheduler to make room for + // higher-priority PodGroups or Pods. + // + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition + + // Status of resource claims. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +featureGate=DRAWorkloadResourceClaims + ResourceClaimStatuses []PodGroupResourceClaimStatus +} + +// Well-known condition types for PodGroups. +const ( + // PodGroupScheduled represents status of the scheduling process for this PodGroup. + PodGroupScheduled string = "PodGroupScheduled" + // DisruptionTarget indicates the PodGroup is about to be terminated due to disruption + // such as preemption. + DisruptionTarget string = "DisruptionTarget" +) + +// Well-known condition reasons for PodGroups. +const ( + // Unschedulable reason in the PodGroupScheduled condition indicates that the PodGroup cannot be scheduled + // due to resource constraints, affinity/anti-affinity rules, or insufficient capacity for the PodGroup. + PodGroupReasonUnschedulable string = "Unschedulable" + // SchedulerError reason in the PodGroupScheduled condition means that some internal error happens + // during scheduling, for example due to nodeAffinity parsing errors. + PodGroupReasonSchedulerError string = "SchedulerError" + // PreemptionByScheduler reason in the DisruptionTarget condition indicates the PodGroup was preempted + // to make room for higher-priority PodGroups or Pods. + PodGroupReasonPreemptionByScheduler string = "PreemptionByScheduler" +) + +// PodGroupResourceClaimStatus is stored in the PodGroupStatus for each +// PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the +// generated name for the corresponding ResourceClaim. +type PodGroupResourceClaimStatus struct { + // Name uniquely identifies this resource claim inside the PodGroup. This + // must match the name of an entry in podgroup.spec.resourceClaims, which + // implies that the string must be a DNS_LABEL. + // + // +required + Name string + + // ResourceClaimName is the name of the ResourceClaim that was generated for + // the PodGroup in the namespace of the PodGroup. If this is unset, then + // generating a ResourceClaim was not necessary. The + // podgroup.spec.resourceClaims entry can be ignored in this case. + // + // +optional + ResourceClaimName *string +} + +// PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). +// Exactly one reference must be set. +// +union +type PodGroupTemplateReference struct { + // Workload references the PodGroupTemplate within the Workload object that was used to create + // the PodGroup. + // + // +optional + Workload *WorkloadPodGroupTemplateReference +} + +// WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object. +type WorkloadPodGroupTemplateReference struct { + // WorkloadName defines the name of the Workload object. + // + // +required + WorkloadName string + + // PodGroupTemplateName defines the PodGroupTemplate name within the Workload object. + // + // +required + PodGroupTemplateName string +} + +// PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup. +type PodGroupSchedulingConstraints struct { + // Topology defines the topology constraints for the pod group. + // Currently only a single topology constraint can be specified. This may change in the future. + // + // +optional + // +listType=atomic + Topology []TopologyConstraint +} + +// TopologyConstraint defines a topology constraint for a PodGroup. +type TopologyConstraint struct { + // Key specifies the key of the node label representing the topology domain. + // All pods within the PodGroup must be colocated within the same domain instance. + // Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. + // Examples: "topology.kubernetes.io/rack" + // + // +required + Key string +} diff --git a/pkg/apis/scheduling/v1alpha1/defaults.go b/pkg/apis/scheduling/v1alpha1/defaults.go deleted file mode 100644 index 5340007a5efd4..0000000000000 --- a/pkg/apis/scheduling/v1alpha1/defaults.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2019 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 v1alpha1 - -import ( - apiv1 "k8s.io/api/core/v1" - "k8s.io/api/scheduling/v1alpha1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -// SetDefaults_PriorityClass sets additional defaults compared to its counterpart -// in extensions. -func SetDefaults_PriorityClass(obj *v1alpha1.PriorityClass) { - if obj.PreemptionPolicy == nil { - preemptLowerPriority := apiv1.PreemptLowerPriority - obj.PreemptionPolicy = &preemptLowerPriority - } -} diff --git a/pkg/apis/scheduling/v1alpha1/defaults_test.go b/pkg/apis/scheduling/v1alpha1/defaults_test.go deleted file mode 100644 index 95d0301a83b22..0000000000000 --- a/pkg/apis/scheduling/v1alpha1/defaults_test.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2019 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 v1alpha1_test - -import ( - "reflect" - "testing" - - apiv1 "k8s.io/api/core/v1" - "k8s.io/api/scheduling/v1alpha1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api/legacyscheme" - - // ensure types are installed - _ "k8s.io/kubernetes/pkg/apis/scheduling/install" -) - -func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - codec := legacyscheme.Codecs.LegacyCodec(v1alpha1.SchemeGroupVersion) - data, err := runtime.Encode(codec, obj) - if err != nil { - t.Errorf("%v\n %#v", err, obj) - return nil - } - obj2, err := runtime.Decode(codec, data) - if err != nil { - t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) - return nil - } - obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) - err = legacyscheme.Scheme.Convert(obj2, obj3, nil) - if err != nil { - t.Errorf("%v\nSource: %#v", err, obj2) - return nil - } - return obj3 -} - -func TestSetDefaultPreempting(t *testing.T) { - priorityClass := &v1alpha1.PriorityClass{} - - output := roundTrip(t, runtime.Object(priorityClass)).(*v1alpha1.PriorityClass) - if output.PreemptionPolicy == nil || *output.PreemptionPolicy != apiv1.PreemptLowerPriority { - t.Errorf("Expected PriorityClass.Preempting value: %+v\ngot: %+v\n", apiv1.PreemptLowerPriority, output.PreemptionPolicy) - } -} diff --git a/pkg/apis/scheduling/v1alpha1/doc.go b/pkg/apis/scheduling/v1alpha1/doc.go deleted file mode 100644 index db092377a2714..0000000000000 --- a/pkg/apis/scheduling/v1alpha1/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -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. -*/ - -// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/scheduling -// +k8s:conversion-gen-external-types=k8s.io/api/scheduling/v1alpha1 -// +groupName=scheduling.k8s.io -// +k8s:defaulter-gen=TypeMeta -// +k8s:defaulter-gen-input=k8s.io/api/scheduling/v1alpha1 - -package v1alpha1 diff --git a/pkg/apis/scheduling/v1alpha1/register.go b/pkg/apis/scheduling/v1alpha1/register.go deleted file mode 100644 index ed40537986dba..0000000000000 --- a/pkg/apis/scheduling/v1alpha1/register.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -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. -*/ - -package v1alpha1 - -import ( - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "scheduling.k8s.io" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - localSchemeBuilder = &schedulingv1alpha1.SchemeBuilder - // AddToScheme is a common registration function for mapping packaged scoped group & version keys to a scheme - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) -} diff --git a/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go b/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go deleted file mode 100644 index 994deb7e22add..0000000000000 --- a/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go +++ /dev/null @@ -1,373 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - unsafe "unsafe" - - v1 "k8s.io/api/core/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - core "k8s.io/kubernetes/pkg/apis/core" - scheduling "k8s.io/kubernetes/pkg/apis/scheduling" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.BasicSchedulingPolicy)(nil), (*scheduling.BasicSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(a.(*schedulingv1alpha1.BasicSchedulingPolicy), b.(*scheduling.BasicSchedulingPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.BasicSchedulingPolicy)(nil), (*schedulingv1alpha1.BasicSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_BasicSchedulingPolicy_To_v1alpha1_BasicSchedulingPolicy(a.(*scheduling.BasicSchedulingPolicy), b.(*schedulingv1alpha1.BasicSchedulingPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.GangSchedulingPolicy)(nil), (*scheduling.GangSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(a.(*schedulingv1alpha1.GangSchedulingPolicy), b.(*scheduling.GangSchedulingPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.GangSchedulingPolicy)(nil), (*schedulingv1alpha1.GangSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_GangSchedulingPolicy_To_v1alpha1_GangSchedulingPolicy(a.(*scheduling.GangSchedulingPolicy), b.(*schedulingv1alpha1.GangSchedulingPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.PodGroup)(nil), (*scheduling.PodGroup)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_PodGroup_To_scheduling_PodGroup(a.(*schedulingv1alpha1.PodGroup), b.(*scheduling.PodGroup), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.PodGroup)(nil), (*schedulingv1alpha1.PodGroup)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_PodGroup_To_v1alpha1_PodGroup(a.(*scheduling.PodGroup), b.(*schedulingv1alpha1.PodGroup), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.PodGroupPolicy)(nil), (*scheduling.PodGroupPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_PodGroupPolicy_To_scheduling_PodGroupPolicy(a.(*schedulingv1alpha1.PodGroupPolicy), b.(*scheduling.PodGroupPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupPolicy)(nil), (*schedulingv1alpha1.PodGroupPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_PodGroupPolicy_To_v1alpha1_PodGroupPolicy(a.(*scheduling.PodGroupPolicy), b.(*schedulingv1alpha1.PodGroupPolicy), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.PriorityClass)(nil), (*scheduling.PriorityClass)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_PriorityClass_To_scheduling_PriorityClass(a.(*schedulingv1alpha1.PriorityClass), b.(*scheduling.PriorityClass), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.PriorityClass)(nil), (*schedulingv1alpha1.PriorityClass)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_PriorityClass_To_v1alpha1_PriorityClass(a.(*scheduling.PriorityClass), b.(*schedulingv1alpha1.PriorityClass), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.PriorityClassList)(nil), (*scheduling.PriorityClassList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_PriorityClassList_To_scheduling_PriorityClassList(a.(*schedulingv1alpha1.PriorityClassList), b.(*scheduling.PriorityClassList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.PriorityClassList)(nil), (*schedulingv1alpha1.PriorityClassList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_PriorityClassList_To_v1alpha1_PriorityClassList(a.(*scheduling.PriorityClassList), b.(*schedulingv1alpha1.PriorityClassList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.TypedLocalObjectReference)(nil), (*scheduling.TypedLocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(a.(*schedulingv1alpha1.TypedLocalObjectReference), b.(*scheduling.TypedLocalObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.TypedLocalObjectReference)(nil), (*schedulingv1alpha1.TypedLocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_TypedLocalObjectReference_To_v1alpha1_TypedLocalObjectReference(a.(*scheduling.TypedLocalObjectReference), b.(*schedulingv1alpha1.TypedLocalObjectReference), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.Workload)(nil), (*scheduling.Workload)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_Workload_To_scheduling_Workload(a.(*schedulingv1alpha1.Workload), b.(*scheduling.Workload), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.Workload)(nil), (*schedulingv1alpha1.Workload)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_Workload_To_v1alpha1_Workload(a.(*scheduling.Workload), b.(*schedulingv1alpha1.Workload), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.WorkloadList)(nil), (*scheduling.WorkloadList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_WorkloadList_To_scheduling_WorkloadList(a.(*schedulingv1alpha1.WorkloadList), b.(*scheduling.WorkloadList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.WorkloadList)(nil), (*schedulingv1alpha1.WorkloadList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_WorkloadList_To_v1alpha1_WorkloadList(a.(*scheduling.WorkloadList), b.(*schedulingv1alpha1.WorkloadList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*schedulingv1alpha1.WorkloadSpec)(nil), (*scheduling.WorkloadSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_WorkloadSpec_To_scheduling_WorkloadSpec(a.(*schedulingv1alpha1.WorkloadSpec), b.(*scheduling.WorkloadSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*scheduling.WorkloadSpec)(nil), (*schedulingv1alpha1.WorkloadSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_scheduling_WorkloadSpec_To_v1alpha1_WorkloadSpec(a.(*scheduling.WorkloadSpec), b.(*schedulingv1alpha1.WorkloadSpec), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha1_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(in *schedulingv1alpha1.BasicSchedulingPolicy, out *scheduling.BasicSchedulingPolicy, s conversion.Scope) error { - return nil -} - -// Convert_v1alpha1_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy is an autogenerated conversion function. -func Convert_v1alpha1_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(in *schedulingv1alpha1.BasicSchedulingPolicy, out *scheduling.BasicSchedulingPolicy, s conversion.Scope) error { - return autoConvert_v1alpha1_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(in, out, s) -} - -func autoConvert_scheduling_BasicSchedulingPolicy_To_v1alpha1_BasicSchedulingPolicy(in *scheduling.BasicSchedulingPolicy, out *schedulingv1alpha1.BasicSchedulingPolicy, s conversion.Scope) error { - return nil -} - -// Convert_scheduling_BasicSchedulingPolicy_To_v1alpha1_BasicSchedulingPolicy is an autogenerated conversion function. -func Convert_scheduling_BasicSchedulingPolicy_To_v1alpha1_BasicSchedulingPolicy(in *scheduling.BasicSchedulingPolicy, out *schedulingv1alpha1.BasicSchedulingPolicy, s conversion.Scope) error { - return autoConvert_scheduling_BasicSchedulingPolicy_To_v1alpha1_BasicSchedulingPolicy(in, out, s) -} - -func autoConvert_v1alpha1_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(in *schedulingv1alpha1.GangSchedulingPolicy, out *scheduling.GangSchedulingPolicy, s conversion.Scope) error { - out.MinCount = in.MinCount - return nil -} - -// Convert_v1alpha1_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy is an autogenerated conversion function. -func Convert_v1alpha1_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(in *schedulingv1alpha1.GangSchedulingPolicy, out *scheduling.GangSchedulingPolicy, s conversion.Scope) error { - return autoConvert_v1alpha1_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(in, out, s) -} - -func autoConvert_scheduling_GangSchedulingPolicy_To_v1alpha1_GangSchedulingPolicy(in *scheduling.GangSchedulingPolicy, out *schedulingv1alpha1.GangSchedulingPolicy, s conversion.Scope) error { - out.MinCount = in.MinCount - return nil -} - -// Convert_scheduling_GangSchedulingPolicy_To_v1alpha1_GangSchedulingPolicy is an autogenerated conversion function. -func Convert_scheduling_GangSchedulingPolicy_To_v1alpha1_GangSchedulingPolicy(in *scheduling.GangSchedulingPolicy, out *schedulingv1alpha1.GangSchedulingPolicy, s conversion.Scope) error { - return autoConvert_scheduling_GangSchedulingPolicy_To_v1alpha1_GangSchedulingPolicy(in, out, s) -} - -func autoConvert_v1alpha1_PodGroup_To_scheduling_PodGroup(in *schedulingv1alpha1.PodGroup, out *scheduling.PodGroup, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_v1alpha1_PodGroupPolicy_To_scheduling_PodGroupPolicy(&in.Policy, &out.Policy, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha1_PodGroup_To_scheduling_PodGroup is an autogenerated conversion function. -func Convert_v1alpha1_PodGroup_To_scheduling_PodGroup(in *schedulingv1alpha1.PodGroup, out *scheduling.PodGroup, s conversion.Scope) error { - return autoConvert_v1alpha1_PodGroup_To_scheduling_PodGroup(in, out, s) -} - -func autoConvert_scheduling_PodGroup_To_v1alpha1_PodGroup(in *scheduling.PodGroup, out *schedulingv1alpha1.PodGroup, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_scheduling_PodGroupPolicy_To_v1alpha1_PodGroupPolicy(&in.Policy, &out.Policy, s); err != nil { - return err - } - return nil -} - -// Convert_scheduling_PodGroup_To_v1alpha1_PodGroup is an autogenerated conversion function. -func Convert_scheduling_PodGroup_To_v1alpha1_PodGroup(in *scheduling.PodGroup, out *schedulingv1alpha1.PodGroup, s conversion.Scope) error { - return autoConvert_scheduling_PodGroup_To_v1alpha1_PodGroup(in, out, s) -} - -func autoConvert_v1alpha1_PodGroupPolicy_To_scheduling_PodGroupPolicy(in *schedulingv1alpha1.PodGroupPolicy, out *scheduling.PodGroupPolicy, s conversion.Scope) error { - out.Basic = (*scheduling.BasicSchedulingPolicy)(unsafe.Pointer(in.Basic)) - out.Gang = (*scheduling.GangSchedulingPolicy)(unsafe.Pointer(in.Gang)) - return nil -} - -// Convert_v1alpha1_PodGroupPolicy_To_scheduling_PodGroupPolicy is an autogenerated conversion function. -func Convert_v1alpha1_PodGroupPolicy_To_scheduling_PodGroupPolicy(in *schedulingv1alpha1.PodGroupPolicy, out *scheduling.PodGroupPolicy, s conversion.Scope) error { - return autoConvert_v1alpha1_PodGroupPolicy_To_scheduling_PodGroupPolicy(in, out, s) -} - -func autoConvert_scheduling_PodGroupPolicy_To_v1alpha1_PodGroupPolicy(in *scheduling.PodGroupPolicy, out *schedulingv1alpha1.PodGroupPolicy, s conversion.Scope) error { - out.Basic = (*schedulingv1alpha1.BasicSchedulingPolicy)(unsafe.Pointer(in.Basic)) - out.Gang = (*schedulingv1alpha1.GangSchedulingPolicy)(unsafe.Pointer(in.Gang)) - return nil -} - -// Convert_scheduling_PodGroupPolicy_To_v1alpha1_PodGroupPolicy is an autogenerated conversion function. -func Convert_scheduling_PodGroupPolicy_To_v1alpha1_PodGroupPolicy(in *scheduling.PodGroupPolicy, out *schedulingv1alpha1.PodGroupPolicy, s conversion.Scope) error { - return autoConvert_scheduling_PodGroupPolicy_To_v1alpha1_PodGroupPolicy(in, out, s) -} - -func autoConvert_v1alpha1_PriorityClass_To_scheduling_PriorityClass(in *schedulingv1alpha1.PriorityClass, out *scheduling.PriorityClass, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Value = in.Value - out.GlobalDefault = in.GlobalDefault - out.Description = in.Description - out.PreemptionPolicy = (*core.PreemptionPolicy)(unsafe.Pointer(in.PreemptionPolicy)) - return nil -} - -// Convert_v1alpha1_PriorityClass_To_scheduling_PriorityClass is an autogenerated conversion function. -func Convert_v1alpha1_PriorityClass_To_scheduling_PriorityClass(in *schedulingv1alpha1.PriorityClass, out *scheduling.PriorityClass, s conversion.Scope) error { - return autoConvert_v1alpha1_PriorityClass_To_scheduling_PriorityClass(in, out, s) -} - -func autoConvert_scheduling_PriorityClass_To_v1alpha1_PriorityClass(in *scheduling.PriorityClass, out *schedulingv1alpha1.PriorityClass, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Value = in.Value - out.GlobalDefault = in.GlobalDefault - out.Description = in.Description - out.PreemptionPolicy = (*v1.PreemptionPolicy)(unsafe.Pointer(in.PreemptionPolicy)) - return nil -} - -// Convert_scheduling_PriorityClass_To_v1alpha1_PriorityClass is an autogenerated conversion function. -func Convert_scheduling_PriorityClass_To_v1alpha1_PriorityClass(in *scheduling.PriorityClass, out *schedulingv1alpha1.PriorityClass, s conversion.Scope) error { - return autoConvert_scheduling_PriorityClass_To_v1alpha1_PriorityClass(in, out, s) -} - -func autoConvert_v1alpha1_PriorityClassList_To_scheduling_PriorityClassList(in *schedulingv1alpha1.PriorityClassList, out *scheduling.PriorityClassList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]scheduling.PriorityClass)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1alpha1_PriorityClassList_To_scheduling_PriorityClassList is an autogenerated conversion function. -func Convert_v1alpha1_PriorityClassList_To_scheduling_PriorityClassList(in *schedulingv1alpha1.PriorityClassList, out *scheduling.PriorityClassList, s conversion.Scope) error { - return autoConvert_v1alpha1_PriorityClassList_To_scheduling_PriorityClassList(in, out, s) -} - -func autoConvert_scheduling_PriorityClassList_To_v1alpha1_PriorityClassList(in *scheduling.PriorityClassList, out *schedulingv1alpha1.PriorityClassList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]schedulingv1alpha1.PriorityClass)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_scheduling_PriorityClassList_To_v1alpha1_PriorityClassList is an autogenerated conversion function. -func Convert_scheduling_PriorityClassList_To_v1alpha1_PriorityClassList(in *scheduling.PriorityClassList, out *schedulingv1alpha1.PriorityClassList, s conversion.Scope) error { - return autoConvert_scheduling_PriorityClassList_To_v1alpha1_PriorityClassList(in, out, s) -} - -func autoConvert_v1alpha1_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(in *schedulingv1alpha1.TypedLocalObjectReference, out *scheduling.TypedLocalObjectReference, s conversion.Scope) error { - out.APIGroup = in.APIGroup - out.Kind = in.Kind - out.Name = in.Name - return nil -} - -// Convert_v1alpha1_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference is an autogenerated conversion function. -func Convert_v1alpha1_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(in *schedulingv1alpha1.TypedLocalObjectReference, out *scheduling.TypedLocalObjectReference, s conversion.Scope) error { - return autoConvert_v1alpha1_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(in, out, s) -} - -func autoConvert_scheduling_TypedLocalObjectReference_To_v1alpha1_TypedLocalObjectReference(in *scheduling.TypedLocalObjectReference, out *schedulingv1alpha1.TypedLocalObjectReference, s conversion.Scope) error { - out.APIGroup = in.APIGroup - out.Kind = in.Kind - out.Name = in.Name - return nil -} - -// Convert_scheduling_TypedLocalObjectReference_To_v1alpha1_TypedLocalObjectReference is an autogenerated conversion function. -func Convert_scheduling_TypedLocalObjectReference_To_v1alpha1_TypedLocalObjectReference(in *scheduling.TypedLocalObjectReference, out *schedulingv1alpha1.TypedLocalObjectReference, s conversion.Scope) error { - return autoConvert_scheduling_TypedLocalObjectReference_To_v1alpha1_TypedLocalObjectReference(in, out, s) -} - -func autoConvert_v1alpha1_Workload_To_scheduling_Workload(in *schedulingv1alpha1.Workload, out *scheduling.Workload, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha1_WorkloadSpec_To_scheduling_WorkloadSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha1_Workload_To_scheduling_Workload is an autogenerated conversion function. -func Convert_v1alpha1_Workload_To_scheduling_Workload(in *schedulingv1alpha1.Workload, out *scheduling.Workload, s conversion.Scope) error { - return autoConvert_v1alpha1_Workload_To_scheduling_Workload(in, out, s) -} - -func autoConvert_scheduling_Workload_To_v1alpha1_Workload(in *scheduling.Workload, out *schedulingv1alpha1.Workload, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_scheduling_WorkloadSpec_To_v1alpha1_WorkloadSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_scheduling_Workload_To_v1alpha1_Workload is an autogenerated conversion function. -func Convert_scheduling_Workload_To_v1alpha1_Workload(in *scheduling.Workload, out *schedulingv1alpha1.Workload, s conversion.Scope) error { - return autoConvert_scheduling_Workload_To_v1alpha1_Workload(in, out, s) -} - -func autoConvert_v1alpha1_WorkloadList_To_scheduling_WorkloadList(in *schedulingv1alpha1.WorkloadList, out *scheduling.WorkloadList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]scheduling.Workload)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1alpha1_WorkloadList_To_scheduling_WorkloadList is an autogenerated conversion function. -func Convert_v1alpha1_WorkloadList_To_scheduling_WorkloadList(in *schedulingv1alpha1.WorkloadList, out *scheduling.WorkloadList, s conversion.Scope) error { - return autoConvert_v1alpha1_WorkloadList_To_scheduling_WorkloadList(in, out, s) -} - -func autoConvert_scheduling_WorkloadList_To_v1alpha1_WorkloadList(in *scheduling.WorkloadList, out *schedulingv1alpha1.WorkloadList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]schedulingv1alpha1.Workload)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_scheduling_WorkloadList_To_v1alpha1_WorkloadList is an autogenerated conversion function. -func Convert_scheduling_WorkloadList_To_v1alpha1_WorkloadList(in *scheduling.WorkloadList, out *schedulingv1alpha1.WorkloadList, s conversion.Scope) error { - return autoConvert_scheduling_WorkloadList_To_v1alpha1_WorkloadList(in, out, s) -} - -func autoConvert_v1alpha1_WorkloadSpec_To_scheduling_WorkloadSpec(in *schedulingv1alpha1.WorkloadSpec, out *scheduling.WorkloadSpec, s conversion.Scope) error { - out.ControllerRef = (*scheduling.TypedLocalObjectReference)(unsafe.Pointer(in.ControllerRef)) - out.PodGroups = *(*[]scheduling.PodGroup)(unsafe.Pointer(&in.PodGroups)) - return nil -} - -// Convert_v1alpha1_WorkloadSpec_To_scheduling_WorkloadSpec is an autogenerated conversion function. -func Convert_v1alpha1_WorkloadSpec_To_scheduling_WorkloadSpec(in *schedulingv1alpha1.WorkloadSpec, out *scheduling.WorkloadSpec, s conversion.Scope) error { - return autoConvert_v1alpha1_WorkloadSpec_To_scheduling_WorkloadSpec(in, out, s) -} - -func autoConvert_scheduling_WorkloadSpec_To_v1alpha1_WorkloadSpec(in *scheduling.WorkloadSpec, out *schedulingv1alpha1.WorkloadSpec, s conversion.Scope) error { - out.ControllerRef = (*schedulingv1alpha1.TypedLocalObjectReference)(unsafe.Pointer(in.ControllerRef)) - out.PodGroups = *(*[]schedulingv1alpha1.PodGroup)(unsafe.Pointer(&in.PodGroups)) - return nil -} - -// Convert_scheduling_WorkloadSpec_To_v1alpha1_WorkloadSpec is an autogenerated conversion function. -func Convert_scheduling_WorkloadSpec_To_v1alpha1_WorkloadSpec(in *scheduling.WorkloadSpec, out *schedulingv1alpha1.WorkloadSpec, s conversion.Scope) error { - return autoConvert_scheduling_WorkloadSpec_To_v1alpha1_WorkloadSpec(in, out, s) -} diff --git a/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go b/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go deleted file mode 100644 index a9fa6fc69d922..0000000000000 --- a/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&schedulingv1alpha1.PriorityClass{}, func(obj interface{}) { SetObjectDefaults_PriorityClass(obj.(*schedulingv1alpha1.PriorityClass)) }) - scheme.AddTypeDefaultingFunc(&schedulingv1alpha1.PriorityClassList{}, func(obj interface{}) { - SetObjectDefaults_PriorityClassList(obj.(*schedulingv1alpha1.PriorityClassList)) - }) - return nil -} - -func SetObjectDefaults_PriorityClass(in *schedulingv1alpha1.PriorityClass) { - SetDefaults_PriorityClass(in) -} - -func SetObjectDefaults_PriorityClassList(in *schedulingv1alpha1.PriorityClassList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_PriorityClass(a) - } -} diff --git a/pkg/apis/scheduling/v1alpha2/defaults.go b/pkg/apis/scheduling/v1alpha2/defaults.go new file mode 100644 index 0000000000000..c590d7989ceae --- /dev/null +++ b/pkg/apis/scheduling/v1alpha2/defaults.go @@ -0,0 +1,23 @@ +/* +Copyright 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 v1alpha2 + +import runtime "k8s.io/apimachinery/pkg/runtime" + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} diff --git a/pkg/apis/scheduling/v1alpha2/doc.go b/pkg/apis/scheduling/v1alpha2/doc.go new file mode 100644 index 0000000000000..6187c6ca03662 --- /dev/null +++ b/pkg/apis/scheduling/v1alpha2/doc.go @@ -0,0 +1,25 @@ +/* +Copyright 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. +*/ + +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/scheduling +// +k8s:conversion-gen-external-types=k8s.io/api/scheduling/v1alpha2 +// +groupName=scheduling.k8s.io +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=k8s.io/api/scheduling/v1alpha2 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/scheduling/v1alpha2 + +package v1alpha2 diff --git a/pkg/apis/scheduling/v1alpha2/register.go b/pkg/apis/scheduling/v1alpha2/register.go new file mode 100644 index 0000000000000..78cdce5428a21 --- /dev/null +++ b/pkg/apis/scheduling/v1alpha2/register.go @@ -0,0 +1,46 @@ +/* +Copyright 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 v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package. +const GroupName = "scheduling.k8s.io" + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource. +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &schedulingv1alpha2.SchemeBuilder + // AddToScheme is a common registration function for mapping packaged scoped group & version keys to a scheme. + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addDefaultingFuncs) +} diff --git a/pkg/apis/scheduling/v1alpha2/zz_generated.conversion.go b/pkg/apis/scheduling/v1alpha2/zz_generated.conversion.go new file mode 100644 index 0000000000000..673ca5b222c9c --- /dev/null +++ b/pkg/apis/scheduling/v1alpha2/zz_generated.conversion.go @@ -0,0 +1,652 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + unsafe "unsafe" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + scheduling "k8s.io/kubernetes/pkg/apis/scheduling" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.BasicSchedulingPolicy)(nil), (*scheduling.BasicSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(a.(*schedulingv1alpha2.BasicSchedulingPolicy), b.(*scheduling.BasicSchedulingPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.BasicSchedulingPolicy)(nil), (*schedulingv1alpha2.BasicSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_BasicSchedulingPolicy_To_v1alpha2_BasicSchedulingPolicy(a.(*scheduling.BasicSchedulingPolicy), b.(*schedulingv1alpha2.BasicSchedulingPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.GangSchedulingPolicy)(nil), (*scheduling.GangSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(a.(*schedulingv1alpha2.GangSchedulingPolicy), b.(*scheduling.GangSchedulingPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.GangSchedulingPolicy)(nil), (*schedulingv1alpha2.GangSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_GangSchedulingPolicy_To_v1alpha2_GangSchedulingPolicy(a.(*scheduling.GangSchedulingPolicy), b.(*schedulingv1alpha2.GangSchedulingPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroup)(nil), (*scheduling.PodGroup)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroup_To_scheduling_PodGroup(a.(*schedulingv1alpha2.PodGroup), b.(*scheduling.PodGroup), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroup)(nil), (*schedulingv1alpha2.PodGroup)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroup_To_v1alpha2_PodGroup(a.(*scheduling.PodGroup), b.(*schedulingv1alpha2.PodGroup), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupList)(nil), (*scheduling.PodGroupList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupList_To_scheduling_PodGroupList(a.(*schedulingv1alpha2.PodGroupList), b.(*scheduling.PodGroupList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupList)(nil), (*schedulingv1alpha2.PodGroupList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupList_To_v1alpha2_PodGroupList(a.(*scheduling.PodGroupList), b.(*schedulingv1alpha2.PodGroupList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupResourceClaim)(nil), (*scheduling.PodGroupResourceClaim)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupResourceClaim_To_scheduling_PodGroupResourceClaim(a.(*schedulingv1alpha2.PodGroupResourceClaim), b.(*scheduling.PodGroupResourceClaim), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupResourceClaim)(nil), (*schedulingv1alpha2.PodGroupResourceClaim)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupResourceClaim_To_v1alpha2_PodGroupResourceClaim(a.(*scheduling.PodGroupResourceClaim), b.(*schedulingv1alpha2.PodGroupResourceClaim), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupResourceClaimStatus)(nil), (*scheduling.PodGroupResourceClaimStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupResourceClaimStatus_To_scheduling_PodGroupResourceClaimStatus(a.(*schedulingv1alpha2.PodGroupResourceClaimStatus), b.(*scheduling.PodGroupResourceClaimStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupResourceClaimStatus)(nil), (*schedulingv1alpha2.PodGroupResourceClaimStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupResourceClaimStatus_To_v1alpha2_PodGroupResourceClaimStatus(a.(*scheduling.PodGroupResourceClaimStatus), b.(*schedulingv1alpha2.PodGroupResourceClaimStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupSchedulingConstraints)(nil), (*scheduling.PodGroupSchedulingConstraints)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupSchedulingConstraints_To_scheduling_PodGroupSchedulingConstraints(a.(*schedulingv1alpha2.PodGroupSchedulingConstraints), b.(*scheduling.PodGroupSchedulingConstraints), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupSchedulingConstraints)(nil), (*schedulingv1alpha2.PodGroupSchedulingConstraints)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupSchedulingConstraints_To_v1alpha2_PodGroupSchedulingConstraints(a.(*scheduling.PodGroupSchedulingConstraints), b.(*schedulingv1alpha2.PodGroupSchedulingConstraints), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupSchedulingPolicy)(nil), (*scheduling.PodGroupSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy(a.(*schedulingv1alpha2.PodGroupSchedulingPolicy), b.(*scheduling.PodGroupSchedulingPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupSchedulingPolicy)(nil), (*schedulingv1alpha2.PodGroupSchedulingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy(a.(*scheduling.PodGroupSchedulingPolicy), b.(*schedulingv1alpha2.PodGroupSchedulingPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupSpec)(nil), (*scheduling.PodGroupSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec(a.(*schedulingv1alpha2.PodGroupSpec), b.(*scheduling.PodGroupSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupSpec)(nil), (*schedulingv1alpha2.PodGroupSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(a.(*scheduling.PodGroupSpec), b.(*schedulingv1alpha2.PodGroupSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupStatus)(nil), (*scheduling.PodGroupStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(a.(*schedulingv1alpha2.PodGroupStatus), b.(*scheduling.PodGroupStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupStatus)(nil), (*schedulingv1alpha2.PodGroupStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus(a.(*scheduling.PodGroupStatus), b.(*schedulingv1alpha2.PodGroupStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupTemplate)(nil), (*scheduling.PodGroupTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupTemplate_To_scheduling_PodGroupTemplate(a.(*schedulingv1alpha2.PodGroupTemplate), b.(*scheduling.PodGroupTemplate), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupTemplate)(nil), (*schedulingv1alpha2.PodGroupTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupTemplate_To_v1alpha2_PodGroupTemplate(a.(*scheduling.PodGroupTemplate), b.(*schedulingv1alpha2.PodGroupTemplate), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.PodGroupTemplateReference)(nil), (*scheduling.PodGroupTemplateReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_PodGroupTemplateReference_To_scheduling_PodGroupTemplateReference(a.(*schedulingv1alpha2.PodGroupTemplateReference), b.(*scheduling.PodGroupTemplateReference), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.PodGroupTemplateReference)(nil), (*schedulingv1alpha2.PodGroupTemplateReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_PodGroupTemplateReference_To_v1alpha2_PodGroupTemplateReference(a.(*scheduling.PodGroupTemplateReference), b.(*schedulingv1alpha2.PodGroupTemplateReference), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.TopologyConstraint)(nil), (*scheduling.TopologyConstraint)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_TopologyConstraint_To_scheduling_TopologyConstraint(a.(*schedulingv1alpha2.TopologyConstraint), b.(*scheduling.TopologyConstraint), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.TopologyConstraint)(nil), (*schedulingv1alpha2.TopologyConstraint)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_TopologyConstraint_To_v1alpha2_TopologyConstraint(a.(*scheduling.TopologyConstraint), b.(*schedulingv1alpha2.TopologyConstraint), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.TypedLocalObjectReference)(nil), (*scheduling.TypedLocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(a.(*schedulingv1alpha2.TypedLocalObjectReference), b.(*scheduling.TypedLocalObjectReference), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.TypedLocalObjectReference)(nil), (*schedulingv1alpha2.TypedLocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_TypedLocalObjectReference_To_v1alpha2_TypedLocalObjectReference(a.(*scheduling.TypedLocalObjectReference), b.(*schedulingv1alpha2.TypedLocalObjectReference), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.Workload)(nil), (*scheduling.Workload)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_Workload_To_scheduling_Workload(a.(*schedulingv1alpha2.Workload), b.(*scheduling.Workload), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.Workload)(nil), (*schedulingv1alpha2.Workload)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_Workload_To_v1alpha2_Workload(a.(*scheduling.Workload), b.(*schedulingv1alpha2.Workload), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.WorkloadList)(nil), (*scheduling.WorkloadList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_WorkloadList_To_scheduling_WorkloadList(a.(*schedulingv1alpha2.WorkloadList), b.(*scheduling.WorkloadList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.WorkloadList)(nil), (*schedulingv1alpha2.WorkloadList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_WorkloadList_To_v1alpha2_WorkloadList(a.(*scheduling.WorkloadList), b.(*schedulingv1alpha2.WorkloadList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.WorkloadPodGroupTemplateReference)(nil), (*scheduling.WorkloadPodGroupTemplateReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_WorkloadPodGroupTemplateReference_To_scheduling_WorkloadPodGroupTemplateReference(a.(*schedulingv1alpha2.WorkloadPodGroupTemplateReference), b.(*scheduling.WorkloadPodGroupTemplateReference), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.WorkloadPodGroupTemplateReference)(nil), (*schedulingv1alpha2.WorkloadPodGroupTemplateReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_WorkloadPodGroupTemplateReference_To_v1alpha2_WorkloadPodGroupTemplateReference(a.(*scheduling.WorkloadPodGroupTemplateReference), b.(*schedulingv1alpha2.WorkloadPodGroupTemplateReference), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*schedulingv1alpha2.WorkloadSpec)(nil), (*scheduling.WorkloadSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_WorkloadSpec_To_scheduling_WorkloadSpec(a.(*schedulingv1alpha2.WorkloadSpec), b.(*scheduling.WorkloadSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*scheduling.WorkloadSpec)(nil), (*schedulingv1alpha2.WorkloadSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_scheduling_WorkloadSpec_To_v1alpha2_WorkloadSpec(a.(*scheduling.WorkloadSpec), b.(*schedulingv1alpha2.WorkloadSpec), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha2_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(in *schedulingv1alpha2.BasicSchedulingPolicy, out *scheduling.BasicSchedulingPolicy, s conversion.Scope) error { + return nil +} + +// Convert_v1alpha2_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy is an autogenerated conversion function. +func Convert_v1alpha2_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(in *schedulingv1alpha2.BasicSchedulingPolicy, out *scheduling.BasicSchedulingPolicy, s conversion.Scope) error { + return autoConvert_v1alpha2_BasicSchedulingPolicy_To_scheduling_BasicSchedulingPolicy(in, out, s) +} + +func autoConvert_scheduling_BasicSchedulingPolicy_To_v1alpha2_BasicSchedulingPolicy(in *scheduling.BasicSchedulingPolicy, out *schedulingv1alpha2.BasicSchedulingPolicy, s conversion.Scope) error { + return nil +} + +// Convert_scheduling_BasicSchedulingPolicy_To_v1alpha2_BasicSchedulingPolicy is an autogenerated conversion function. +func Convert_scheduling_BasicSchedulingPolicy_To_v1alpha2_BasicSchedulingPolicy(in *scheduling.BasicSchedulingPolicy, out *schedulingv1alpha2.BasicSchedulingPolicy, s conversion.Scope) error { + return autoConvert_scheduling_BasicSchedulingPolicy_To_v1alpha2_BasicSchedulingPolicy(in, out, s) +} + +func autoConvert_v1alpha2_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(in *schedulingv1alpha2.GangSchedulingPolicy, out *scheduling.GangSchedulingPolicy, s conversion.Scope) error { + out.MinCount = in.MinCount + return nil +} + +// Convert_v1alpha2_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy is an autogenerated conversion function. +func Convert_v1alpha2_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(in *schedulingv1alpha2.GangSchedulingPolicy, out *scheduling.GangSchedulingPolicy, s conversion.Scope) error { + return autoConvert_v1alpha2_GangSchedulingPolicy_To_scheduling_GangSchedulingPolicy(in, out, s) +} + +func autoConvert_scheduling_GangSchedulingPolicy_To_v1alpha2_GangSchedulingPolicy(in *scheduling.GangSchedulingPolicy, out *schedulingv1alpha2.GangSchedulingPolicy, s conversion.Scope) error { + out.MinCount = in.MinCount + return nil +} + +// Convert_scheduling_GangSchedulingPolicy_To_v1alpha2_GangSchedulingPolicy is an autogenerated conversion function. +func Convert_scheduling_GangSchedulingPolicy_To_v1alpha2_GangSchedulingPolicy(in *scheduling.GangSchedulingPolicy, out *schedulingv1alpha2.GangSchedulingPolicy, s conversion.Scope) error { + return autoConvert_scheduling_GangSchedulingPolicy_To_v1alpha2_GangSchedulingPolicy(in, out, s) +} + +func autoConvert_v1alpha2_PodGroup_To_scheduling_PodGroup(in *schedulingv1alpha2.PodGroup, out *scheduling.PodGroup, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha2_PodGroup_To_scheduling_PodGroup is an autogenerated conversion function. +func Convert_v1alpha2_PodGroup_To_scheduling_PodGroup(in *schedulingv1alpha2.PodGroup, out *scheduling.PodGroup, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroup_To_scheduling_PodGroup(in, out, s) +} + +func autoConvert_scheduling_PodGroup_To_v1alpha2_PodGroup(in *scheduling.PodGroup, out *schedulingv1alpha2.PodGroup, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_scheduling_PodGroup_To_v1alpha2_PodGroup is an autogenerated conversion function. +func Convert_scheduling_PodGroup_To_v1alpha2_PodGroup(in *scheduling.PodGroup, out *schedulingv1alpha2.PodGroup, s conversion.Scope) error { + return autoConvert_scheduling_PodGroup_To_v1alpha2_PodGroup(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupList_To_scheduling_PodGroupList(in *schedulingv1alpha2.PodGroupList, out *scheduling.PodGroupList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]scheduling.PodGroup)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha2_PodGroupList_To_scheduling_PodGroupList is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupList_To_scheduling_PodGroupList(in *schedulingv1alpha2.PodGroupList, out *scheduling.PodGroupList, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupList_To_scheduling_PodGroupList(in, out, s) +} + +func autoConvert_scheduling_PodGroupList_To_v1alpha2_PodGroupList(in *scheduling.PodGroupList, out *schedulingv1alpha2.PodGroupList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]schedulingv1alpha2.PodGroup)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_scheduling_PodGroupList_To_v1alpha2_PodGroupList is an autogenerated conversion function. +func Convert_scheduling_PodGroupList_To_v1alpha2_PodGroupList(in *scheduling.PodGroupList, out *schedulingv1alpha2.PodGroupList, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupList_To_v1alpha2_PodGroupList(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupResourceClaim_To_scheduling_PodGroupResourceClaim(in *schedulingv1alpha2.PodGroupResourceClaim, out *scheduling.PodGroupResourceClaim, s conversion.Scope) error { + out.Name = in.Name + out.ResourceClaimName = (*string)(unsafe.Pointer(in.ResourceClaimName)) + out.ResourceClaimTemplateName = (*string)(unsafe.Pointer(in.ResourceClaimTemplateName)) + return nil +} + +// Convert_v1alpha2_PodGroupResourceClaim_To_scheduling_PodGroupResourceClaim is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupResourceClaim_To_scheduling_PodGroupResourceClaim(in *schedulingv1alpha2.PodGroupResourceClaim, out *scheduling.PodGroupResourceClaim, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupResourceClaim_To_scheduling_PodGroupResourceClaim(in, out, s) +} + +func autoConvert_scheduling_PodGroupResourceClaim_To_v1alpha2_PodGroupResourceClaim(in *scheduling.PodGroupResourceClaim, out *schedulingv1alpha2.PodGroupResourceClaim, s conversion.Scope) error { + out.Name = in.Name + out.ResourceClaimName = (*string)(unsafe.Pointer(in.ResourceClaimName)) + out.ResourceClaimTemplateName = (*string)(unsafe.Pointer(in.ResourceClaimTemplateName)) + return nil +} + +// Convert_scheduling_PodGroupResourceClaim_To_v1alpha2_PodGroupResourceClaim is an autogenerated conversion function. +func Convert_scheduling_PodGroupResourceClaim_To_v1alpha2_PodGroupResourceClaim(in *scheduling.PodGroupResourceClaim, out *schedulingv1alpha2.PodGroupResourceClaim, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupResourceClaim_To_v1alpha2_PodGroupResourceClaim(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupResourceClaimStatus_To_scheduling_PodGroupResourceClaimStatus(in *schedulingv1alpha2.PodGroupResourceClaimStatus, out *scheduling.PodGroupResourceClaimStatus, s conversion.Scope) error { + out.Name = in.Name + out.ResourceClaimName = (*string)(unsafe.Pointer(in.ResourceClaimName)) + return nil +} + +// Convert_v1alpha2_PodGroupResourceClaimStatus_To_scheduling_PodGroupResourceClaimStatus is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupResourceClaimStatus_To_scheduling_PodGroupResourceClaimStatus(in *schedulingv1alpha2.PodGroupResourceClaimStatus, out *scheduling.PodGroupResourceClaimStatus, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupResourceClaimStatus_To_scheduling_PodGroupResourceClaimStatus(in, out, s) +} + +func autoConvert_scheduling_PodGroupResourceClaimStatus_To_v1alpha2_PodGroupResourceClaimStatus(in *scheduling.PodGroupResourceClaimStatus, out *schedulingv1alpha2.PodGroupResourceClaimStatus, s conversion.Scope) error { + out.Name = in.Name + out.ResourceClaimName = (*string)(unsafe.Pointer(in.ResourceClaimName)) + return nil +} + +// Convert_scheduling_PodGroupResourceClaimStatus_To_v1alpha2_PodGroupResourceClaimStatus is an autogenerated conversion function. +func Convert_scheduling_PodGroupResourceClaimStatus_To_v1alpha2_PodGroupResourceClaimStatus(in *scheduling.PodGroupResourceClaimStatus, out *schedulingv1alpha2.PodGroupResourceClaimStatus, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupResourceClaimStatus_To_v1alpha2_PodGroupResourceClaimStatus(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupSchedulingConstraints_To_scheduling_PodGroupSchedulingConstraints(in *schedulingv1alpha2.PodGroupSchedulingConstraints, out *scheduling.PodGroupSchedulingConstraints, s conversion.Scope) error { + out.Topology = *(*[]scheduling.TopologyConstraint)(unsafe.Pointer(&in.Topology)) + return nil +} + +// Convert_v1alpha2_PodGroupSchedulingConstraints_To_scheduling_PodGroupSchedulingConstraints is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupSchedulingConstraints_To_scheduling_PodGroupSchedulingConstraints(in *schedulingv1alpha2.PodGroupSchedulingConstraints, out *scheduling.PodGroupSchedulingConstraints, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupSchedulingConstraints_To_scheduling_PodGroupSchedulingConstraints(in, out, s) +} + +func autoConvert_scheduling_PodGroupSchedulingConstraints_To_v1alpha2_PodGroupSchedulingConstraints(in *scheduling.PodGroupSchedulingConstraints, out *schedulingv1alpha2.PodGroupSchedulingConstraints, s conversion.Scope) error { + out.Topology = *(*[]schedulingv1alpha2.TopologyConstraint)(unsafe.Pointer(&in.Topology)) + return nil +} + +// Convert_scheduling_PodGroupSchedulingConstraints_To_v1alpha2_PodGroupSchedulingConstraints is an autogenerated conversion function. +func Convert_scheduling_PodGroupSchedulingConstraints_To_v1alpha2_PodGroupSchedulingConstraints(in *scheduling.PodGroupSchedulingConstraints, out *schedulingv1alpha2.PodGroupSchedulingConstraints, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupSchedulingConstraints_To_v1alpha2_PodGroupSchedulingConstraints(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy(in *schedulingv1alpha2.PodGroupSchedulingPolicy, out *scheduling.PodGroupSchedulingPolicy, s conversion.Scope) error { + out.Basic = (*scheduling.BasicSchedulingPolicy)(unsafe.Pointer(in.Basic)) + out.Gang = (*scheduling.GangSchedulingPolicy)(unsafe.Pointer(in.Gang)) + return nil +} + +// Convert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy(in *schedulingv1alpha2.PodGroupSchedulingPolicy, out *scheduling.PodGroupSchedulingPolicy, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy(in, out, s) +} + +func autoConvert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy(in *scheduling.PodGroupSchedulingPolicy, out *schedulingv1alpha2.PodGroupSchedulingPolicy, s conversion.Scope) error { + out.Basic = (*schedulingv1alpha2.BasicSchedulingPolicy)(unsafe.Pointer(in.Basic)) + out.Gang = (*schedulingv1alpha2.GangSchedulingPolicy)(unsafe.Pointer(in.Gang)) + return nil +} + +// Convert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy is an autogenerated conversion function. +func Convert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy(in *scheduling.PodGroupSchedulingPolicy, out *schedulingv1alpha2.PodGroupSchedulingPolicy, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec(in *schedulingv1alpha2.PodGroupSpec, out *scheduling.PodGroupSpec, s conversion.Scope) error { + out.PodGroupTemplateRef = (*scheduling.PodGroupTemplateReference)(unsafe.Pointer(in.PodGroupTemplateRef)) + if err := Convert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy(&in.SchedulingPolicy, &out.SchedulingPolicy, s); err != nil { + return err + } + out.SchedulingConstraints = (*scheduling.PodGroupSchedulingConstraints)(unsafe.Pointer(in.SchedulingConstraints)) + out.ResourceClaims = *(*[]scheduling.PodGroupResourceClaim)(unsafe.Pointer(&in.ResourceClaims)) + out.DisruptionMode = (*scheduling.DisruptionMode)(unsafe.Pointer(in.DisruptionMode)) + out.PriorityClassName = in.PriorityClassName + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) + return nil +} + +// Convert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec(in *schedulingv1alpha2.PodGroupSpec, out *scheduling.PodGroupSpec, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec(in, out, s) +} + +func autoConvert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(in *scheduling.PodGroupSpec, out *schedulingv1alpha2.PodGroupSpec, s conversion.Scope) error { + out.PodGroupTemplateRef = (*schedulingv1alpha2.PodGroupTemplateReference)(unsafe.Pointer(in.PodGroupTemplateRef)) + if err := Convert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy(&in.SchedulingPolicy, &out.SchedulingPolicy, s); err != nil { + return err + } + out.SchedulingConstraints = (*schedulingv1alpha2.PodGroupSchedulingConstraints)(unsafe.Pointer(in.SchedulingConstraints)) + out.ResourceClaims = *(*[]schedulingv1alpha2.PodGroupResourceClaim)(unsafe.Pointer(&in.ResourceClaims)) + out.DisruptionMode = (*schedulingv1alpha2.DisruptionMode)(unsafe.Pointer(in.DisruptionMode)) + out.PriorityClassName = in.PriorityClassName + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) + return nil +} + +// Convert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec is an autogenerated conversion function. +func Convert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(in *scheduling.PodGroupSpec, out *schedulingv1alpha2.PodGroupSpec, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(in *schedulingv1alpha2.PodGroupStatus, out *scheduling.PodGroupStatus, s conversion.Scope) error { + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + out.ResourceClaimStatuses = *(*[]scheduling.PodGroupResourceClaimStatus)(unsafe.Pointer(&in.ResourceClaimStatuses)) + return nil +} + +// Convert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(in *schedulingv1alpha2.PodGroupStatus, out *scheduling.PodGroupStatus, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(in, out, s) +} + +func autoConvert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus(in *scheduling.PodGroupStatus, out *schedulingv1alpha2.PodGroupStatus, s conversion.Scope) error { + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + out.ResourceClaimStatuses = *(*[]schedulingv1alpha2.PodGroupResourceClaimStatus)(unsafe.Pointer(&in.ResourceClaimStatuses)) + return nil +} + +// Convert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus is an autogenerated conversion function. +func Convert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus(in *scheduling.PodGroupStatus, out *schedulingv1alpha2.PodGroupStatus, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupTemplate_To_scheduling_PodGroupTemplate(in *schedulingv1alpha2.PodGroupTemplate, out *scheduling.PodGroupTemplate, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_v1alpha2_PodGroupSchedulingPolicy_To_scheduling_PodGroupSchedulingPolicy(&in.SchedulingPolicy, &out.SchedulingPolicy, s); err != nil { + return err + } + out.SchedulingConstraints = (*scheduling.PodGroupSchedulingConstraints)(unsafe.Pointer(in.SchedulingConstraints)) + out.ResourceClaims = *(*[]scheduling.PodGroupResourceClaim)(unsafe.Pointer(&in.ResourceClaims)) + out.DisruptionMode = (*scheduling.DisruptionMode)(unsafe.Pointer(in.DisruptionMode)) + out.PriorityClassName = in.PriorityClassName + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) + return nil +} + +// Convert_v1alpha2_PodGroupTemplate_To_scheduling_PodGroupTemplate is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupTemplate_To_scheduling_PodGroupTemplate(in *schedulingv1alpha2.PodGroupTemplate, out *scheduling.PodGroupTemplate, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupTemplate_To_scheduling_PodGroupTemplate(in, out, s) +} + +func autoConvert_scheduling_PodGroupTemplate_To_v1alpha2_PodGroupTemplate(in *scheduling.PodGroupTemplate, out *schedulingv1alpha2.PodGroupTemplate, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_scheduling_PodGroupSchedulingPolicy_To_v1alpha2_PodGroupSchedulingPolicy(&in.SchedulingPolicy, &out.SchedulingPolicy, s); err != nil { + return err + } + out.SchedulingConstraints = (*schedulingv1alpha2.PodGroupSchedulingConstraints)(unsafe.Pointer(in.SchedulingConstraints)) + out.ResourceClaims = *(*[]schedulingv1alpha2.PodGroupResourceClaim)(unsafe.Pointer(&in.ResourceClaims)) + out.DisruptionMode = (*schedulingv1alpha2.DisruptionMode)(unsafe.Pointer(in.DisruptionMode)) + out.PriorityClassName = in.PriorityClassName + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) + return nil +} + +// Convert_scheduling_PodGroupTemplate_To_v1alpha2_PodGroupTemplate is an autogenerated conversion function. +func Convert_scheduling_PodGroupTemplate_To_v1alpha2_PodGroupTemplate(in *scheduling.PodGroupTemplate, out *schedulingv1alpha2.PodGroupTemplate, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupTemplate_To_v1alpha2_PodGroupTemplate(in, out, s) +} + +func autoConvert_v1alpha2_PodGroupTemplateReference_To_scheduling_PodGroupTemplateReference(in *schedulingv1alpha2.PodGroupTemplateReference, out *scheduling.PodGroupTemplateReference, s conversion.Scope) error { + out.Workload = (*scheduling.WorkloadPodGroupTemplateReference)(unsafe.Pointer(in.Workload)) + return nil +} + +// Convert_v1alpha2_PodGroupTemplateReference_To_scheduling_PodGroupTemplateReference is an autogenerated conversion function. +func Convert_v1alpha2_PodGroupTemplateReference_To_scheduling_PodGroupTemplateReference(in *schedulingv1alpha2.PodGroupTemplateReference, out *scheduling.PodGroupTemplateReference, s conversion.Scope) error { + return autoConvert_v1alpha2_PodGroupTemplateReference_To_scheduling_PodGroupTemplateReference(in, out, s) +} + +func autoConvert_scheduling_PodGroupTemplateReference_To_v1alpha2_PodGroupTemplateReference(in *scheduling.PodGroupTemplateReference, out *schedulingv1alpha2.PodGroupTemplateReference, s conversion.Scope) error { + out.Workload = (*schedulingv1alpha2.WorkloadPodGroupTemplateReference)(unsafe.Pointer(in.Workload)) + return nil +} + +// Convert_scheduling_PodGroupTemplateReference_To_v1alpha2_PodGroupTemplateReference is an autogenerated conversion function. +func Convert_scheduling_PodGroupTemplateReference_To_v1alpha2_PodGroupTemplateReference(in *scheduling.PodGroupTemplateReference, out *schedulingv1alpha2.PodGroupTemplateReference, s conversion.Scope) error { + return autoConvert_scheduling_PodGroupTemplateReference_To_v1alpha2_PodGroupTemplateReference(in, out, s) +} + +func autoConvert_v1alpha2_TopologyConstraint_To_scheduling_TopologyConstraint(in *schedulingv1alpha2.TopologyConstraint, out *scheduling.TopologyConstraint, s conversion.Scope) error { + out.Key = in.Key + return nil +} + +// Convert_v1alpha2_TopologyConstraint_To_scheduling_TopologyConstraint is an autogenerated conversion function. +func Convert_v1alpha2_TopologyConstraint_To_scheduling_TopologyConstraint(in *schedulingv1alpha2.TopologyConstraint, out *scheduling.TopologyConstraint, s conversion.Scope) error { + return autoConvert_v1alpha2_TopologyConstraint_To_scheduling_TopologyConstraint(in, out, s) +} + +func autoConvert_scheduling_TopologyConstraint_To_v1alpha2_TopologyConstraint(in *scheduling.TopologyConstraint, out *schedulingv1alpha2.TopologyConstraint, s conversion.Scope) error { + out.Key = in.Key + return nil +} + +// Convert_scheduling_TopologyConstraint_To_v1alpha2_TopologyConstraint is an autogenerated conversion function. +func Convert_scheduling_TopologyConstraint_To_v1alpha2_TopologyConstraint(in *scheduling.TopologyConstraint, out *schedulingv1alpha2.TopologyConstraint, s conversion.Scope) error { + return autoConvert_scheduling_TopologyConstraint_To_v1alpha2_TopologyConstraint(in, out, s) +} + +func autoConvert_v1alpha2_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(in *schedulingv1alpha2.TypedLocalObjectReference, out *scheduling.TypedLocalObjectReference, s conversion.Scope) error { + out.APIGroup = in.APIGroup + out.Kind = in.Kind + out.Name = in.Name + return nil +} + +// Convert_v1alpha2_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference is an autogenerated conversion function. +func Convert_v1alpha2_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(in *schedulingv1alpha2.TypedLocalObjectReference, out *scheduling.TypedLocalObjectReference, s conversion.Scope) error { + return autoConvert_v1alpha2_TypedLocalObjectReference_To_scheduling_TypedLocalObjectReference(in, out, s) +} + +func autoConvert_scheduling_TypedLocalObjectReference_To_v1alpha2_TypedLocalObjectReference(in *scheduling.TypedLocalObjectReference, out *schedulingv1alpha2.TypedLocalObjectReference, s conversion.Scope) error { + out.APIGroup = in.APIGroup + out.Kind = in.Kind + out.Name = in.Name + return nil +} + +// Convert_scheduling_TypedLocalObjectReference_To_v1alpha2_TypedLocalObjectReference is an autogenerated conversion function. +func Convert_scheduling_TypedLocalObjectReference_To_v1alpha2_TypedLocalObjectReference(in *scheduling.TypedLocalObjectReference, out *schedulingv1alpha2.TypedLocalObjectReference, s conversion.Scope) error { + return autoConvert_scheduling_TypedLocalObjectReference_To_v1alpha2_TypedLocalObjectReference(in, out, s) +} + +func autoConvert_v1alpha2_Workload_To_scheduling_Workload(in *schedulingv1alpha2.Workload, out *scheduling.Workload, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha2_WorkloadSpec_To_scheduling_WorkloadSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha2_Workload_To_scheduling_Workload is an autogenerated conversion function. +func Convert_v1alpha2_Workload_To_scheduling_Workload(in *schedulingv1alpha2.Workload, out *scheduling.Workload, s conversion.Scope) error { + return autoConvert_v1alpha2_Workload_To_scheduling_Workload(in, out, s) +} + +func autoConvert_scheduling_Workload_To_v1alpha2_Workload(in *scheduling.Workload, out *schedulingv1alpha2.Workload, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_scheduling_WorkloadSpec_To_v1alpha2_WorkloadSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_scheduling_Workload_To_v1alpha2_Workload is an autogenerated conversion function. +func Convert_scheduling_Workload_To_v1alpha2_Workload(in *scheduling.Workload, out *schedulingv1alpha2.Workload, s conversion.Scope) error { + return autoConvert_scheduling_Workload_To_v1alpha2_Workload(in, out, s) +} + +func autoConvert_v1alpha2_WorkloadList_To_scheduling_WorkloadList(in *schedulingv1alpha2.WorkloadList, out *scheduling.WorkloadList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]scheduling.Workload)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha2_WorkloadList_To_scheduling_WorkloadList is an autogenerated conversion function. +func Convert_v1alpha2_WorkloadList_To_scheduling_WorkloadList(in *schedulingv1alpha2.WorkloadList, out *scheduling.WorkloadList, s conversion.Scope) error { + return autoConvert_v1alpha2_WorkloadList_To_scheduling_WorkloadList(in, out, s) +} + +func autoConvert_scheduling_WorkloadList_To_v1alpha2_WorkloadList(in *scheduling.WorkloadList, out *schedulingv1alpha2.WorkloadList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]schedulingv1alpha2.Workload)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_scheduling_WorkloadList_To_v1alpha2_WorkloadList is an autogenerated conversion function. +func Convert_scheduling_WorkloadList_To_v1alpha2_WorkloadList(in *scheduling.WorkloadList, out *schedulingv1alpha2.WorkloadList, s conversion.Scope) error { + return autoConvert_scheduling_WorkloadList_To_v1alpha2_WorkloadList(in, out, s) +} + +func autoConvert_v1alpha2_WorkloadPodGroupTemplateReference_To_scheduling_WorkloadPodGroupTemplateReference(in *schedulingv1alpha2.WorkloadPodGroupTemplateReference, out *scheduling.WorkloadPodGroupTemplateReference, s conversion.Scope) error { + out.WorkloadName = in.WorkloadName + out.PodGroupTemplateName = in.PodGroupTemplateName + return nil +} + +// Convert_v1alpha2_WorkloadPodGroupTemplateReference_To_scheduling_WorkloadPodGroupTemplateReference is an autogenerated conversion function. +func Convert_v1alpha2_WorkloadPodGroupTemplateReference_To_scheduling_WorkloadPodGroupTemplateReference(in *schedulingv1alpha2.WorkloadPodGroupTemplateReference, out *scheduling.WorkloadPodGroupTemplateReference, s conversion.Scope) error { + return autoConvert_v1alpha2_WorkloadPodGroupTemplateReference_To_scheduling_WorkloadPodGroupTemplateReference(in, out, s) +} + +func autoConvert_scheduling_WorkloadPodGroupTemplateReference_To_v1alpha2_WorkloadPodGroupTemplateReference(in *scheduling.WorkloadPodGroupTemplateReference, out *schedulingv1alpha2.WorkloadPodGroupTemplateReference, s conversion.Scope) error { + out.WorkloadName = in.WorkloadName + out.PodGroupTemplateName = in.PodGroupTemplateName + return nil +} + +// Convert_scheduling_WorkloadPodGroupTemplateReference_To_v1alpha2_WorkloadPodGroupTemplateReference is an autogenerated conversion function. +func Convert_scheduling_WorkloadPodGroupTemplateReference_To_v1alpha2_WorkloadPodGroupTemplateReference(in *scheduling.WorkloadPodGroupTemplateReference, out *schedulingv1alpha2.WorkloadPodGroupTemplateReference, s conversion.Scope) error { + return autoConvert_scheduling_WorkloadPodGroupTemplateReference_To_v1alpha2_WorkloadPodGroupTemplateReference(in, out, s) +} + +func autoConvert_v1alpha2_WorkloadSpec_To_scheduling_WorkloadSpec(in *schedulingv1alpha2.WorkloadSpec, out *scheduling.WorkloadSpec, s conversion.Scope) error { + out.ControllerRef = (*scheduling.TypedLocalObjectReference)(unsafe.Pointer(in.ControllerRef)) + out.PodGroupTemplates = *(*[]scheduling.PodGroupTemplate)(unsafe.Pointer(&in.PodGroupTemplates)) + return nil +} + +// Convert_v1alpha2_WorkloadSpec_To_scheduling_WorkloadSpec is an autogenerated conversion function. +func Convert_v1alpha2_WorkloadSpec_To_scheduling_WorkloadSpec(in *schedulingv1alpha2.WorkloadSpec, out *scheduling.WorkloadSpec, s conversion.Scope) error { + return autoConvert_v1alpha2_WorkloadSpec_To_scheduling_WorkloadSpec(in, out, s) +} + +func autoConvert_scheduling_WorkloadSpec_To_v1alpha2_WorkloadSpec(in *scheduling.WorkloadSpec, out *schedulingv1alpha2.WorkloadSpec, s conversion.Scope) error { + out.ControllerRef = (*schedulingv1alpha2.TypedLocalObjectReference)(unsafe.Pointer(in.ControllerRef)) + out.PodGroupTemplates = *(*[]schedulingv1alpha2.PodGroupTemplate)(unsafe.Pointer(&in.PodGroupTemplates)) + return nil +} + +// Convert_scheduling_WorkloadSpec_To_v1alpha2_WorkloadSpec is an autogenerated conversion function. +func Convert_scheduling_WorkloadSpec_To_v1alpha2_WorkloadSpec(in *scheduling.WorkloadSpec, out *schedulingv1alpha2.WorkloadSpec, s conversion.Scope) error { + return autoConvert_scheduling_WorkloadSpec_To_v1alpha2_WorkloadSpec(in, out, s) +} diff --git a/pkg/apis/scheduling/v1alpha2/zz_generated.defaults.go b/pkg/apis/scheduling/v1alpha2/zz_generated.defaults.go new file mode 100644 index 0000000000000..cd1725ad90f17 --- /dev/null +++ b/pkg/apis/scheduling/v1alpha2/zz_generated.defaults.go @@ -0,0 +1,50 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&schedulingv1alpha2.PodGroup{}, func(obj interface{}) { SetObjectDefaults_PodGroup(obj.(*schedulingv1alpha2.PodGroup)) }) + scheme.AddTypeDefaultingFunc(&schedulingv1alpha2.PodGroupList{}, func(obj interface{}) { SetObjectDefaults_PodGroupList(obj.(*schedulingv1alpha2.PodGroupList)) }) + return nil +} + +func SetObjectDefaults_PodGroup(in *schedulingv1alpha2.PodGroup) { + if in.Spec.DisruptionMode == nil { + var ptrVar1 schedulingv1alpha2.DisruptionMode = "Pod" + in.Spec.DisruptionMode = &ptrVar1 + } +} + +func SetObjectDefaults_PodGroupList(in *schedulingv1alpha2.PodGroupList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_PodGroup(a) + } +} diff --git a/pkg/apis/scheduling/v1alpha2/zz_generated.validations.go b/pkg/apis/scheduling/v1alpha2/zz_generated.validations.go new file mode 100644 index 0000000000000..e93231a5d4470 --- /dev/null +++ b/pkg/apis/scheduling/v1alpha2/zz_generated.validations.go @@ -0,0 +1,1053 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + fmt "fmt" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type PodGroup + scheme.AddValidationFunc((*schedulingv1alpha2.PodGroup)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/", "/status": + return Validate_PodGroup(ctx, op, nil /* fldPath */, obj.(*schedulingv1alpha2.PodGroup), safe.Cast[*schedulingv1alpha2.PodGroup](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type Workload + scheme.AddValidationFunc((*schedulingv1alpha2.Workload)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Workload(ctx, op, nil /* fldPath */, obj.(*schedulingv1alpha2.Workload), safe.Cast[*schedulingv1alpha2.Workload](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +var symbolsForDisruptionMode = sets.New(schedulingv1alpha2.DisruptionModePod, schedulingv1alpha2.DisruptionModePodGroup) + +// Validate_DisruptionMode validates an instance of DisruptionMode according +// to declarative validation rules in the API schema. +func Validate_DisruptionMode(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.DisruptionMode) (errs field.ErrorList) { + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForDisruptionMode, nil)...) + + return errs +} + +// Validate_GangSchedulingPolicy validates an instance of GangSchedulingPolicy according +// to declarative validation rules in the API schema. +func Validate_GangSchedulingPolicy(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.GangSchedulingPolicy) (errs field.ErrorList) { + // field schedulingv1alpha2.GangSchedulingPolicy.MinCount + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("minCount"), &obj.MinCount, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.GangSchedulingPolicy) *int32 { return &oldObj.MinCount }), oldObj != nil)...) + + return errs +} + +// Validate_PodGroup validates an instance of PodGroup according +// to declarative validation rules in the API schema. +func Validate_PodGroup(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroup) (errs field.ErrorList) { + // field schedulingv1alpha2.PodGroup.TypeMeta has no validation + // field schedulingv1alpha2.PodGroup.ObjectMeta has no validation + + // field schedulingv1alpha2.PodGroup.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PodGroupSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroup) *schedulingv1alpha2.PodGroupSpec { return &oldObj.Spec }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroup.Status + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupStatus, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PodGroupStatus(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("status"), &obj.Status, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroup) *schedulingv1alpha2.PodGroupStatus { return &oldObj.Status }), oldObj != nil)...) + + return errs +} + +var unionMembershipFor_k8s_io_api_scheduling_v1alpha2_PodGroupResourceClaim_ = validate.NewUnionMembership(validate.NewUnionMember("resourceClaimName"), validate.NewUnionMember("resourceClaimTemplateName")) + +// Validate_PodGroupResourceClaim validates an instance of PodGroupResourceClaim according +// to declarative validation rules in the API schema. +func Validate_PodGroupResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupResourceClaim) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_scheduling_v1alpha2_PodGroupResourceClaim_, func(obj *schedulingv1alpha2.PodGroupResourceClaim) bool { + if obj == nil { + return false + } + return obj.ResourceClaimName != nil + }, func(obj *schedulingv1alpha2.PodGroupResourceClaim) bool { + if obj == nil { + return false + } + return obj.ResourceClaimTemplateName != nil + })...) + + // field schedulingv1alpha2.PodGroupResourceClaim.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupResourceClaim) *string { return &oldObj.Name }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupResourceClaim.ResourceClaimName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("resourceClaimName"), obj.ResourceClaimName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupResourceClaim) *string { return oldObj.ResourceClaimName }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupResourceClaim.ResourceClaimTemplateName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("resourceClaimTemplateName"), obj.ResourceClaimTemplateName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupResourceClaim) *string { + return oldObj.ResourceClaimTemplateName + }), oldObj != nil)...) + + return errs +} + +// Validate_PodGroupResourceClaimStatus validates an instance of PodGroupResourceClaimStatus according +// to declarative validation rules in the API schema. +func Validate_PodGroupResourceClaimStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupResourceClaimStatus) (errs field.ErrorList) { + // field schedulingv1alpha2.PodGroupResourceClaimStatus.Name has no validation + + // field schedulingv1alpha2.PodGroupResourceClaimStatus.ResourceClaimName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("resourceClaimName"), obj.ResourceClaimName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupResourceClaimStatus) *string { return oldObj.ResourceClaimName }), oldObj != nil)...) + + return errs +} + +// Validate_PodGroupSchedulingConstraints validates an instance of PodGroupSchedulingConstraints according +// to declarative validation rules in the API schema. +func Validate_PodGroupSchedulingConstraints(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSchedulingConstraints) (errs field.ErrorList) { + // field schedulingv1alpha2.PodGroupSchedulingConstraints.Topology + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []schedulingv1alpha2.TopologyConstraint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_TopologyConstraint)...) + return + }(fldPath.Child("topology"), obj.Topology, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSchedulingConstraints) []schedulingv1alpha2.TopologyConstraint { + return oldObj.Topology + }), oldObj != nil)...) + + return errs +} + +var unionMembershipFor_k8s_io_api_scheduling_v1alpha2_PodGroupSchedulingPolicy_ = validate.NewUnionMembership(validate.NewUnionMember("basic"), validate.NewUnionMember("gang")) + +// Validate_PodGroupSchedulingPolicy validates an instance of PodGroupSchedulingPolicy according +// to declarative validation rules in the API schema. +func Validate_PodGroupSchedulingPolicy(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSchedulingPolicy) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_scheduling_v1alpha2_PodGroupSchedulingPolicy_, func(obj *schedulingv1alpha2.PodGroupSchedulingPolicy) bool { + if obj == nil { + return false + } + return obj.Basic != nil + }, func(obj *schedulingv1alpha2.PodGroupSchedulingPolicy) bool { + if obj == nil { + return false + } + return obj.Gang != nil + })...) + + // field schedulingv1alpha2.PodGroupSchedulingPolicy.Basic + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.BasicSchedulingPolicy, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("basic"), obj.Basic, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSchedulingPolicy) *schedulingv1alpha2.BasicSchedulingPolicy { + return oldObj.Basic + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSchedulingPolicy.Gang + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.GangSchedulingPolicy, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_GangSchedulingPolicy(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("gang"), obj.Gang, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSchedulingPolicy) *schedulingv1alpha2.GangSchedulingPolicy { + return oldObj.Gang + }), oldObj != nil)...) + + return errs +} + +// Validate_PodGroupSpec validates an instance of PodGroupSpec according +// to declarative validation rules in the API schema. +func Validate_PodGroupSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSpec) (errs field.ErrorList) { + // field schedulingv1alpha2.PodGroupSpec.PodGroupTemplateRef + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupTemplateReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_PodGroupTemplateReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("podGroupTemplateRef"), obj.PodGroupTemplateRef, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) *schedulingv1alpha2.PodGroupTemplateReference { + return oldObj.PodGroupTemplateRef + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSpec.SchedulingPolicy + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSchedulingPolicy, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_PodGroupSchedulingPolicy(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("schedulingPolicy"), &obj.SchedulingPolicy, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) *schedulingv1alpha2.PodGroupSchedulingPolicy { + return &oldObj.SchedulingPolicy + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSpec.SchedulingConstraints + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSchedulingConstraints, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", false, validate.ForbiddenPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", false, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", true, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", true, validate.Immutable); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_PodGroupSchedulingConstraints(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("schedulingConstraints"), obj.SchedulingConstraints, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) *schedulingv1alpha2.PodGroupSchedulingConstraints { + return oldObj.SchedulingConstraints + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSpec.ResourceClaims + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []schedulingv1alpha2.PodGroupResourceClaim, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupResourceClaim, b schedulingv1alpha2.PodGroupResourceClaim) bool { + return a.Name == b.Name + })...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupResourceClaim, b schedulingv1alpha2.PodGroupResourceClaim) bool { + return a.Name == b.Name + }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim)...) + return + }(fldPath.Child("resourceClaims"), obj.ResourceClaims, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) []schedulingv1alpha2.PodGroupResourceClaim { + return oldObj.ResourceClaims + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSpec.DisruptionMode + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.DisruptionMode, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, // optional fields with default values are effectively required + validate.RequiredPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.Immutable); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_DisruptionMode(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("disruptionMode"), obj.DisruptionMode, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) *schedulingv1alpha2.DisruptionMode { + return oldObj.DisruptionMode + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSpec.PriorityClassName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenValue); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalValue); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.OptionalValue); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.Immutable); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.LongName)...) + return + }(fldPath.Child("priorityClassName"), &obj.PriorityClassName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) *string { return &oldObj.PriorityClassName }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupSpec.Priority + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.Immutable); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Maximum(ctx, op, fldPath, obj, oldObj, 1000000000) + })...) + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, -2147483648) + })...) + return + }(fldPath.Child("priority"), obj.Priority, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupSpec) *int32 { return oldObj.Priority }), oldObj != nil)...) + + return errs +} + +// Validate_PodGroupStatus validates an instance of PodGroupStatus according +// to declarative validation rules in the API schema. +func Validate_PodGroupStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupStatus) (errs field.ErrorList) { + // field schedulingv1alpha2.PodGroupStatus.Conditions has no validation + + // field schedulingv1alpha2.PodGroupStatus.ResourceClaimStatuses + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []schedulingv1alpha2.PodGroupResourceClaimStatus, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupResourceClaimStatus, b schedulingv1alpha2.PodGroupResourceClaimStatus) bool { + return a.Name == b.Name + })...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupResourceClaimStatus, b schedulingv1alpha2.PodGroupResourceClaimStatus) bool { + return a.Name == b.Name + }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaimStatus)...) + return + }(fldPath.Child("resourceClaimStatuses"), obj.ResourceClaimStatuses, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupStatus) []schedulingv1alpha2.PodGroupResourceClaimStatus { + return oldObj.ResourceClaimStatuses + }), oldObj != nil)...) + + return errs +} + +// Validate_PodGroupTemplate validates an instance of PodGroupTemplate according +// to declarative validation rules in the API schema. +func Validate_PodGroupTemplate(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupTemplate) (errs field.ErrorList) { + // field schedulingv1alpha2.PodGroupTemplate.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) *string { return &oldObj.Name }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupTemplate.SchedulingPolicy + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSchedulingPolicy, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_PodGroupSchedulingPolicy(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("schedulingPolicy"), &obj.SchedulingPolicy, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) *schedulingv1alpha2.PodGroupSchedulingPolicy { + return &oldObj.SchedulingPolicy + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupTemplate.SchedulingConstraints + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupSchedulingConstraints, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", false, validate.ForbiddenPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", false, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "TopologyAwareWorkloadScheduling", true, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_PodGroupSchedulingConstraints(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("schedulingConstraints"), obj.SchedulingConstraints, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) *schedulingv1alpha2.PodGroupSchedulingConstraints { + return oldObj.SchedulingConstraints + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupTemplate.ResourceClaims + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []schedulingv1alpha2.PodGroupResourceClaim, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 4); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupResourceClaim, b schedulingv1alpha2.PodGroupResourceClaim) bool { + return a.Name == b.Name + })...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupResourceClaim, b schedulingv1alpha2.PodGroupResourceClaim) bool { + return a.Name == b.Name + }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim)...) + return + }(fldPath.Child("resourceClaims"), obj.ResourceClaims, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) []schedulingv1alpha2.PodGroupResourceClaim { + return oldObj.ResourceClaims + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupTemplate.DisruptionMode + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.DisruptionMode, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_DisruptionMode(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("disruptionMode"), obj.DisruptionMode, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) *schedulingv1alpha2.DisruptionMode { + return oldObj.DisruptionMode + }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupTemplate.PriorityClassName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenValue); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalValue); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.OptionalValue); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.LongName)...) + return + }(fldPath.Child("priorityClassName"), &obj.PriorityClassName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) *string { return &oldObj.PriorityClassName }), oldObj != nil)...) + + // field schedulingv1alpha2.PodGroupTemplate.Priority + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenPointer); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.OptionalPointer); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Maximum(ctx, op, fldPath, obj, oldObj, 1000000000) + })...) + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, -2147483648) + })...) + return + }(fldPath.Child("priority"), obj.Priority, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplate) *int32 { return oldObj.Priority }), oldObj != nil)...) + + return errs +} + +var unionMembershipFor_k8s_io_api_scheduling_v1alpha2_PodGroupTemplateReference_ = validate.NewUnionMembership(validate.NewUnionMember("workload")) + +// Validate_PodGroupTemplateReference validates an instance of PodGroupTemplateReference according +// to declarative validation rules in the API schema. +func Validate_PodGroupTemplateReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.PodGroupTemplateReference) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_scheduling_v1alpha2_PodGroupTemplateReference_, func(obj *schedulingv1alpha2.PodGroupTemplateReference) bool { + if obj == nil { + return false + } + return obj.Workload != nil + })...) + + // field schedulingv1alpha2.PodGroupTemplateReference.Workload + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.WorkloadPodGroupTemplateReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_WorkloadPodGroupTemplateReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("workload"), obj.Workload, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.PodGroupTemplateReference) *schedulingv1alpha2.WorkloadPodGroupTemplateReference { + return oldObj.Workload + }), oldObj != nil)...) + + return errs +} + +// Validate_TopologyConstraint validates an instance of TopologyConstraint according +// to declarative validation rules in the API schema. +func Validate_TopologyConstraint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.TopologyConstraint) (errs field.ErrorList) { + // field schedulingv1alpha2.TopologyConstraint.Key + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LabelKey(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("key"), &obj.Key, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.TopologyConstraint) *string { return &oldObj.Key }), oldObj != nil)...) + + return errs +} + +// Validate_TypedLocalObjectReference validates an instance of TypedLocalObjectReference according +// to declarative validation rules in the API schema. +func Validate_TypedLocalObjectReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.TypedLocalObjectReference) (errs field.ErrorList) { + // field schedulingv1alpha2.TypedLocalObjectReference.APIGroup + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("apiGroup"), &obj.APIGroup, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.TypedLocalObjectReference) *string { return &oldObj.APIGroup }), oldObj != nil)...) + + // field schedulingv1alpha2.TypedLocalObjectReference.Kind + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.PathSegmentName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("kind"), &obj.Kind, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.TypedLocalObjectReference) *string { return &oldObj.Kind }), oldObj != nil)...) + + // field schedulingv1alpha2.TypedLocalObjectReference.Name + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.PathSegmentName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("name"), &obj.Name, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.TypedLocalObjectReference) *string { return &oldObj.Name }), oldObj != nil)...) + + return errs +} + +// Validate_Workload validates an instance of Workload according +// to declarative validation rules in the API schema. +func Validate_Workload(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.Workload) (errs field.ErrorList) { + // field schedulingv1alpha2.Workload.TypeMeta has no validation + // field schedulingv1alpha2.Workload.ObjectMeta has no validation + + // field schedulingv1alpha2.Workload.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.WorkloadSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_WorkloadSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.Workload) *schedulingv1alpha2.WorkloadSpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_WorkloadPodGroupTemplateReference validates an instance of WorkloadPodGroupTemplateReference according +// to declarative validation rules in the API schema. +func Validate_WorkloadPodGroupTemplateReference(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.WorkloadPodGroupTemplateReference) (errs field.ErrorList) { + // field schedulingv1alpha2.WorkloadPodGroupTemplateReference.WorkloadName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("workloadName"), &obj.WorkloadName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.WorkloadPodGroupTemplateReference) *string { + return &oldObj.WorkloadName + }), oldObj != nil)...) + + // field schedulingv1alpha2.WorkloadPodGroupTemplateReference.PodGroupTemplateName + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("podGroupTemplateName"), &obj.PodGroupTemplateName, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.WorkloadPodGroupTemplateReference) *string { + return &oldObj.PodGroupTemplateName + }), oldObj != nil)...) + + return errs +} + +// Validate_WorkloadSpec validates an instance of WorkloadSpec according +// to declarative validation rules in the API schema. +func Validate_WorkloadSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *schedulingv1alpha2.WorkloadSpec) (errs field.ErrorList) { + // field schedulingv1alpha2.WorkloadSpec.ControllerRef + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *schedulingv1alpha2.TypedLocalObjectReference, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_TypedLocalObjectReference(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("controllerRef"), obj.ControllerRef, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.WorkloadSpec) *schedulingv1alpha2.TypedLocalObjectReference { + return oldObj.ControllerRef + }), oldObj != nil)...) + + // field schedulingv1alpha2.WorkloadSpec.PodGroupTemplates + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []schedulingv1alpha2.PodGroupTemplate, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupTemplate, b schedulingv1alpha2.PodGroupTemplate) bool { + return a.Name == b.Name + })...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a schedulingv1alpha2.PodGroupTemplate, b schedulingv1alpha2.PodGroupTemplate) bool { + return a.Name == b.Name + }, validate.SemanticDeepEqual, Validate_PodGroupTemplate)...) + return + }(fldPath.Child("podGroupTemplates"), obj.PodGroupTemplates, safe.Field(oldObj, func(oldObj *schedulingv1alpha2.WorkloadSpec) []schedulingv1alpha2.PodGroupTemplate { + return oldObj.PodGroupTemplates + }), oldObj != nil)...) + + return errs +} diff --git a/pkg/apis/scheduling/validation/validation.go b/pkg/apis/scheduling/validation/validation.go index 6ca22364544d3..16807cefefdf3 100644 --- a/pkg/apis/scheduling/validation/validation.go +++ b/pkg/apis/scheduling/validation/validation.go @@ -18,18 +18,21 @@ package validation import ( "fmt" + "slices" "strings" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" - pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation/field" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/scheduling" schedulingapiv1 "k8s.io/kubernetes/pkg/apis/scheduling/v1" ) +// validateWorkloadName can be used to check whether the given +// name for a Workload is valid. +var validateWorkloadName = apimachineryvalidation.NameIsDNSSubdomain + // ValidatePriorityClass tests whether required fields in the PriorityClass are // set correctly. func ValidatePriorityClass(pc *scheduling.PriorityClass) field.ErrorList { @@ -65,119 +68,52 @@ func ValidatePriorityClassUpdate(pc, oldPc *scheduling.PriorityClass) field.Erro return allErrs } -// ValidateWorkload tests if all fields in a Workload are set correctly. -func ValidateWorkload(workload *scheduling.Workload) field.ErrorList { - allErrs := apivalidation.ValidateObjectMeta(&workload.ObjectMeta, true, apivalidation.ValidateWorkloadName, field.NewPath("metadata")) - allErrs = append(allErrs, validateWorkloadSpec(&workload.Spec, field.NewPath("spec"))...) - return allErrs +// ValidatePodGroup tests if all fields in a PodGroup are set correctly. +func ValidatePodGroup(podGroup *scheduling.PodGroup) field.ErrorList { + return apivalidation.ValidateObjectMeta(&podGroup.ObjectMeta, true, apivalidation.ValidatePodGroupName, field.NewPath("metadata")) } -func validateWorkloadSpec(spec *scheduling.WorkloadSpec, fldPath *field.Path) field.ErrorList { - var allErrs field.ErrorList - if spec.ControllerRef != nil { - allErrs = append(allErrs, validateControllerRef(spec.ControllerRef, fldPath.Child("controllerRef"))...) - } - existingPodGroups := sets.New[string]() - podGroupsPath := fldPath.Child("podGroups") - if len(spec.PodGroups) == 0 { - allErrs = append(allErrs, field.Required(podGroupsPath, "must have at least one item")) - } else if len(spec.PodGroups) > scheduling.WorkloadMaxPodGroups { - allErrs = append(allErrs, field.TooMany(fldPath.Child("podGroups"), len(spec.PodGroups), scheduling.WorkloadMaxPodGroups)) - } else { - for i := range spec.PodGroups { - allErrs = append(allErrs, validatePodGroup(&spec.PodGroups[i], podGroupsPath.Index(i), existingPodGroups)...) - } - } - return allErrs +// ValidatePodGroupUpdate tests if an update to PodGroup is valid. +func ValidatePodGroupUpdate(podGroup, oldPodGroup *scheduling.PodGroup) field.ErrorList { + return apivalidation.ValidateObjectMetaUpdate(&podGroup.ObjectMeta, &oldPodGroup.ObjectMeta, field.NewPath("metadata")) } -func validateControllerRef(ref *scheduling.TypedLocalObjectReference, fldPath *field.Path) field.ErrorList { - var allErrs = field.ErrorList{} - if ref.APIGroup != "" { - for _, msg := range validation.IsDNS1123Subdomain(ref.APIGroup) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), ref.APIGroup, msg)) - } - } - if ref.Kind == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) - } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Kind) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, msg)) - } - } - if ref.Name == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } else { - for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Name) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg)) - } - } - return allErrs +// ValidateWorkload tests if all fields in a Workload are set correctly. +func ValidateWorkload(workload *scheduling.Workload) field.ErrorList { + return apivalidation.ValidateObjectMeta(&workload.ObjectMeta, true, validateWorkloadName, field.NewPath("metadata")) } -func validatePodGroup(podGroup *scheduling.PodGroup, fldPath *field.Path, existingPodGroups sets.Set[string]) field.ErrorList { - var allErrs field.ErrorList - for _, detail := range apivalidation.ValidatePodGroupName(podGroup.Name, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), podGroup.Name, detail)) - } - if existingPodGroups.Has(podGroup.Name) { - allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), podGroup.Name)) - } else { - existingPodGroups.Insert(podGroup.Name) - } - allErrs = append(allErrs, validatePodGroupPolicy(&podGroup.Policy, fldPath.Child("policy"))...) - return allErrs +// ValidateWorkloadUpdate tests if an update to Workload is valid. +func ValidateWorkloadUpdate(workload, oldWorkload *scheduling.Workload) field.ErrorList { + return apivalidation.ValidateObjectMetaUpdate(&workload.ObjectMeta, &oldWorkload.ObjectMeta, field.NewPath("metadata")) } -func validatePodGroupPolicy(policy *scheduling.PodGroupPolicy, fldPath *field.Path) field.ErrorList { - var allErrs field.ErrorList - var setFields []string - - if policy.Basic != nil { - setFields = append(setFields, "`basic`") - } - if policy.Gang != nil { - setFields = append(setFields, "`gang`") - } - - switch { - case len(setFields) == 0: - allErrs = append(allErrs, field.Required(fldPath, "exactly one of `basic`, `gang` is required")) - case len(setFields) > 1: - allErrs = append(allErrs, field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(setFields, ", ")), - "exactly one of `basic`, `gang` is required, but multiple fields are set")) - case policy.Basic != nil: - allErrs = append(allErrs, validatBasicSchedulingPolicy(policy.Basic, fldPath.Child("basic"))...) - case policy.Gang != nil: - allErrs = append(allErrs, validateGangSchedulingPolicy(policy.Gang, fldPath.Child("gang"))...) - } - +// ValidatePodGroupStatusUpdate tests if an update to the status of a PodGroup is valid. +func ValidatePodGroupStatusUpdate(podGroup, oldPodGroup *scheduling.PodGroup) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&podGroup.ObjectMeta, &oldPodGroup.ObjectMeta, field.NewPath("metadata")) + fldPath := field.NewPath("status") + allErrs = append(allErrs, metav1validation.ValidateConditions(podGroup.Status.Conditions, fldPath.Child("conditions"))...) + allErrs = append(allErrs, validatePodGroupResourceClaimStatuses(podGroup.Status.ResourceClaimStatuses, podGroup.Spec.ResourceClaims, fldPath.Child("resourceClaimStatuses"))...) return allErrs } -func validatBasicSchedulingPolicy(policy *scheduling.BasicSchedulingPolicy, fldPath *field.Path) field.ErrorList { - // BasicSchedulingPolicy has no fields. - return nil -} +func validatePodGroupResourceClaimStatuses(statuses []scheduling.PodGroupResourceClaimStatus, podGroupClaims []scheduling.PodGroupResourceClaim, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList -func validateGangSchedulingPolicy(policy *scheduling.GangSchedulingPolicy, fldPath *field.Path) field.ErrorList { - allErrs := apivalidation.ValidatePositiveField(int64(policy.MinCount), fldPath.Child("minCount")) - return allErrs -} + for i, status := range statuses { + idxPath := fldPath.Index(i) + // There's no need to check the content of the name. If it matches an entry, + // then it is valid, otherwise we reject it here. + if !havePodGroupClaim(podGroupClaims, status.Name) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), status.Name, "must match the name of an entry in `spec.resourceClaims`")) + } + } -// ValidateWorkloadUpdate tests if an update to Workload is valid. -func ValidateWorkloadUpdate(workload, oldWorkload *scheduling.Workload) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&workload.ObjectMeta, &oldWorkload.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, validateWorkloadSpec(&workload.Spec, field.NewPath("spec"))...) - allErrs = append(allErrs, validateWorkloadSpecUpdate(&workload.Spec, &oldWorkload.Spec, field.NewPath("spec"))...) return allErrs } -func validateWorkloadSpecUpdate(spec, oldSpec *scheduling.WorkloadSpec, fldPath *field.Path) field.ErrorList { - var allErrs field.ErrorList - if oldSpec.ControllerRef != nil { - allErrs = apimachineryvalidation.ValidateImmutableField(spec.ControllerRef, oldSpec.ControllerRef, field.NewPath("controllerRef")) - } - allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.PodGroups, oldSpec.PodGroups, field.NewPath("podGroups"))...) - return allErrs +func havePodGroupClaim(podGroupClaims []scheduling.PodGroupResourceClaim, name string) bool { + return slices.ContainsFunc(podGroupClaims, func(podGroupClaim scheduling.PodGroupResourceClaim) bool { + return podGroupClaim.Name == name + }) } diff --git a/pkg/apis/scheduling/validation/validation_test.go b/pkg/apis/scheduling/validation/validation_test.go index 8372169b5ae15..9060b4cbd234e 100644 --- a/pkg/apis/scheduling/validation/validation_test.go +++ b/pkg/apis/scheduling/validation/validation_test.go @@ -17,7 +17,6 @@ limitations under the License. package validation import ( - "fmt" "strings" "testing" @@ -189,8 +188,8 @@ func TestValidateWorkload(t *testing.T) { "no controllerRef": mkWorkload(func(w *scheduling.Workload) { w.Spec.ControllerRef = nil }), - "no controllerRef apiGroup": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.APIGroup = "" + "no scheduling constraints": mkWorkload(func(w *scheduling.Workload) { + w.Spec.PodGroupTemplates[1].SchedulingConstraints = nil }), } for name, workload := range successCases { @@ -200,89 +199,74 @@ func TestValidateWorkload(t *testing.T) { } } - failureCases := map[string]*scheduling.Workload{ - "no name": mkWorkload(func(w *scheduling.Workload) { - w.Name = "" - }), - "invalid name": mkWorkload(func(w *scheduling.Workload) { - w.Name = ".workload" - }), - "too long name": mkWorkload(func(w *scheduling.Workload) { - w.Name = strings.Repeat("w", 254) - }), - "no namespace": mkWorkload(func(w *scheduling.Workload) { - w.Namespace = "" - }), - "invalid namespace": mkWorkload(func(w *scheduling.Workload) { - w.Namespace = ".ns" - }), - "too long namespace": mkWorkload(func(w *scheduling.Workload) { - w.Namespace = strings.Repeat("n", 64) - }), - "invalid controllerRef apiGroup": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.APIGroup = ".group" - }), - "too long controllerRef apiGroup": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.APIGroup = strings.Repeat("g", 254) - }), - "no controllerRef kind": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.Kind = "" - }), - "invalid controllerRef kind": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.Kind = "/foo" - }), - "no controllerRef name": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.Name = "" - }), - "invalid controllerRef name": mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.Name = "/baz" - }), - "no pod groups": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups = nil - }), - "too many pod groups": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups = nil - for i := 0; i < scheduling.WorkloadMaxPodGroups+1; i++ { - w.Spec.PodGroups = append(w.Spec.PodGroups, scheduling.PodGroup{ - Name: fmt.Sprintf("group-%v", i), - Policy: scheduling.PodGroupPolicy{ - Basic: &scheduling.BasicSchedulingPolicy{}, - }, - }) + failureCases := map[string]struct { + workload *scheduling.Workload + expectedErrs field.ErrorList + }{ + "no name": { + workload: mkWorkload(func(w *scheduling.Workload) { + w.Name = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("metadata", "name"), "name or generateName is required"), + }, + }, + "invalid name": { + workload: mkWorkload(func(w *scheduling.Workload) { + w.Name = ".workload" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), ".workload", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + "too long name": { + workload: mkWorkload(func(w *scheduling.Workload) { + w.Name = strings.Repeat("w", 254) + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), ".name", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + "no namespace": { + workload: mkWorkload(func(w *scheduling.Workload) { + w.Namespace = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("metadata", "namespace"), "Required value"), + }, + }, + "invalid namespace": { + workload: mkWorkload(func(w *scheduling.Workload) { + w.Namespace = ".ns" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "namespace"), ".ns", "a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + "too long namespace": { + workload: mkWorkload(func(w *scheduling.Workload) { + w.Namespace = strings.Repeat("n", 64) + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "namespace"), strings.Repeat("n", 64), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + } + + for name, tc := range failureCases { + t.Run(name, func(t *testing.T) { + errs := ValidateWorkload(tc.workload) + if len(errs) == 0 { + t.Errorf("Expected failure") + return } - }), - "no pod group name": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[0].Name = "" - }), - "invalid pod group name": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[0].Name = ".group1" - }), - "too long pod group name": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[0].Name = strings.Repeat("g", 64) - }), - "no policy set": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[0].Policy = scheduling.PodGroupPolicy{} - }), - "two policies": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[0].Policy.Gang = &scheduling.GangSchedulingPolicy{ - MinCount: 2, + if len(errs) != len(tc.expectedErrs) { + t.Errorf("Expected %d errors, got %d: %v", len(tc.expectedErrs), len(errs), errs) + return } - }), - "zero min count in gang": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[1].Policy.Gang.MinCount = 0 - }), - "negative min count in gang": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[1].Policy.Gang.MinCount = -1 - }), - "two pod groups with the same name": mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[1].Name = w.Spec.PodGroups[0].Name - }), - } - for name, workload := range failureCases { - errs := ValidateWorkload(workload) - if len(errs) == 0 { - t.Errorf("Expected failure for %q", name) - } + matcher := field.ErrorMatcher{}.ByType().ByField() + matcher.Test(t, tc.expectedErrs, errs) + }) } } @@ -295,12 +279,6 @@ func TestValidateWorkloadUpdate(t *testing.T) { old: mkWorkload(), update: mkWorkload(), }, - "set controller ref": { - old: mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef = nil - }), - update: mkWorkload(), - }, } for name, tc := range successCases { tc.old.ResourceVersion = "0" @@ -327,47 +305,6 @@ func TestValidateWorkloadUpdate(t *testing.T) { w.Namespace += "bar" }), }, - "change pod group name": { - old: mkWorkload(), - update: mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[0].Name += "bar" - }), - }, - "add pod group": { - old: mkWorkload(), - update: mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups = append(w.Spec.PodGroups, scheduling.PodGroup{ - Name: "group3", - Policy: scheduling.PodGroupPolicy{ - Basic: &scheduling.BasicSchedulingPolicy{}, - }, - }) - }), - }, - "delete pod group": { - old: mkWorkload(), - update: mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups = w.Spec.PodGroups[:1] - }), - }, - "change gang min count": { - old: mkWorkload(), - update: mkWorkload(func(w *scheduling.Workload) { - w.Spec.PodGroups[1].Policy.Gang.MinCount = 5 - }), - }, - "change controllerRef": { - old: mkWorkload(), - update: mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef.Kind += "bar" - }), - }, - "delete controllerRef": { - old: mkWorkload(), - update: mkWorkload(func(w *scheduling.Workload) { - w.Spec.ControllerRef = nil - }), - }, } for name, tc := range failureCases { tc.old.ResourceVersion = "0" @@ -389,18 +326,28 @@ func mkWorkload(tweaks ...func(w *scheduling.Workload)) *scheduling.Workload { Kind: "foo", Name: "baz", }, - PodGroups: []scheduling.PodGroup{{ + PodGroupTemplates: []scheduling.PodGroupTemplate{{ Name: "group1", - Policy: scheduling.PodGroupPolicy{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ Basic: &scheduling.BasicSchedulingPolicy{}, }, + SchedulingConstraints: &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{ + {Key: "foo"}, + }, + }, }, { Name: "group2", - Policy: scheduling.PodGroupPolicy{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ Gang: &scheduling.GangSchedulingPolicy{ MinCount: 2, }, }, + SchedulingConstraints: &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{ + {Key: "foo"}, + }, + }, }}, }, } @@ -409,3 +356,347 @@ func mkWorkload(tweaks ...func(w *scheduling.Workload)) *scheduling.Workload { } return w } + +func TestValidatePodGroup(t *testing.T) { + successCases := map[string]*scheduling.PodGroup{ + "gang policy": mkPodGroup(), + "no scheduling constraints": mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Spec.SchedulingConstraints = nil + }), + } + for name, podGroup := range successCases { + errs := ValidatePodGroup(podGroup) + if len(errs) != 0 { + t.Errorf("Expected success for %q: %v", name, errs) + } + } + + failureCases := map[string]struct { + podGroup *scheduling.PodGroup + expectedErrs field.ErrorList + }{ + "no name": { + podGroup: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Name = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("metadata", "name"), "name or generateName is required"), + }, + }, + "invalid name": { + podGroup: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Name = ".podGroup" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), ".podGroup", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + "too long name": { + podGroup: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Name = strings.Repeat("w", 254) + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), ".name", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + "no namespace": { + podGroup: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Namespace = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("metadata", "namespace"), "Required value"), + }, + }, + "invalid namespace": { + podGroup: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Namespace = ".ns" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "namespace"), ".ns", "a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + "too long namespace": { + podGroup: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Namespace = strings.Repeat("n", 64) + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "namespace"), strings.Repeat("n", 64), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + }, + } + + for name, tc := range failureCases { + t.Run(name, func(t *testing.T) { + errs := ValidatePodGroup(tc.podGroup) + if len(errs) == 0 { + t.Errorf("Expected failure") + return + } + if len(errs) != len(tc.expectedErrs) { + t.Errorf("Expected %d errors, got %d: %v", len(tc.expectedErrs), len(errs), errs) + return + } + matcher := field.ErrorMatcher{}.ByType().ByField() + matcher.Test(t, tc.expectedErrs, errs) + }) + } +} + +func TestValidatePodGroupUpdate(t *testing.T) { + successCases := map[string]struct { + old *scheduling.PodGroup + update *scheduling.PodGroup + }{ + "no change": { + old: mkPodGroup(), + update: mkPodGroup(), + }, + "status update": { + old: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Status.Conditions = append(pg.Status.Conditions, metav1.Condition{ + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: metav1.Now(), + }) + }), + update: mkPodGroup(), + }, + } + for name, tc := range successCases { + tc.old.ResourceVersion = "0" + tc.update.ResourceVersion = "1" + errs := ValidatePodGroupUpdate(tc.update, tc.old) + if len(errs) != 0 { + t.Errorf("Expected success for %q: %v", name, errs) + } + } + + failureCases := map[string]struct { + old *scheduling.PodGroup + update *scheduling.PodGroup + }{ + "change name": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Name += "bar" + }), + }, + "change namespace": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Namespace += "bar" + }), + }, + } + for name, tc := range failureCases { + tc.old.ResourceVersion = "0" + tc.update.ResourceVersion = "1" + errs := ValidatePodGroupUpdate(tc.update, tc.old) + if len(errs) == 0 { + t.Errorf("Expected failure for %q", name) + } + } +} + +func TestValidatePodGroupStatusUpdate(t *testing.T) { + now := metav1.Now() + successCases := map[string]struct { + old *scheduling.PodGroup + update *scheduling.PodGroup + }{ + "no change": { + old: mkPodGroup(), + update: mkPodGroup(), + }, + "status update": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Status.Conditions = append(pg.Status.Conditions, metav1.Condition{ + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: now, + }) + }), + }, + "ok resource claim status": { + old: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Spec.ResourceClaims = []scheduling.PodGroupResourceClaim{ + {Name: "my-claim", ResourceClaimName: new("a-claim")}, + {Name: "my-other-claim", ResourceClaimName: new("a-claim")}, + } + }), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Spec.ResourceClaims = []scheduling.PodGroupResourceClaim{ + {Name: "my-claim", ResourceClaimName: new("a-claim")}, + {Name: "my-other-claim", ResourceClaimName: new("a-claim")}, + } + pg.Status.ResourceClaimStatuses = []scheduling.PodGroupResourceClaimStatus{ + {Name: "my-claim", ResourceClaimName: new("foo-my-claim-12345")}, + {Name: "my-other-claim", ResourceClaimName: nil}, + } + }), + }, + } + for name, tc := range successCases { + tc.old.ResourceVersion = "0" + tc.update.ResourceVersion = "1" + errs := ValidatePodGroupUpdate(tc.update, tc.old) + if len(errs) != 0 { + t.Errorf("Expected success for %q: %v", name, errs) + } + } + + failureCases := map[string]struct { + old *scheduling.PodGroup + update *scheduling.PodGroup + }{ + "change name": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Name += "bar" + }), + }, + "change namespace": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Namespace += "bar" + }), + }, + "two conditions with the same type": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + condition := metav1.Condition{ + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: now, + } + pg.Status.Conditions = append(pg.Status.Conditions, condition, condition) + }), + }, + "unrecognized condition status": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + conditions := []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionStatus("TrueOrFalse"), + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: now, + }, + } + pg.Status.Conditions = append(pg.Status.Conditions, conditions...) + }), + }, + "empty condition reason": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + conditions := []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Message: "Test status condition message", + LastTransitionTime: now, + }, + } + pg.Status.Conditions = append(pg.Status.Conditions, conditions...) + }), + }, + "improper condition reason format": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + conditions := []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: "Sche duled", + Message: "Test status condition message", + LastTransitionTime: now, + }, + } + pg.Status.Conditions = append(pg.Status.Conditions, conditions...) + }), + }, + "too long condition reason": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + conditions := []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: strings.Repeat("a", 1024+1), + Message: "Test status condition message", + LastTransitionTime: now, + }, + } + pg.Status.Conditions = append(pg.Status.Conditions, conditions...) + }), + }, + "too long condition message": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + conditions := []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: strings.Repeat("a", 32*1024+1), + LastTransitionTime: now, + }, + } + pg.Status.Conditions = append(pg.Status.Conditions, conditions...) + }), + }, + "non-existent resource claim in status": { + old: mkPodGroup(), + update: mkPodGroup(func(pg *scheduling.PodGroup) { + pg.Status.ResourceClaimStatuses = []scheduling.PodGroupResourceClaimStatus{ + {Name: "no-such-claim", ResourceClaimName: new("my-claim")}, + } + }), + }, + } + for name, tc := range failureCases { + tc.old.ResourceVersion = "0" + tc.update.ResourceVersion = "1" + errs := ValidatePodGroupStatusUpdate(tc.update, tc.old) + if len(errs) == 0 { + t.Errorf("Expected failure for %q", name) + } + } +} + +// mkPodGroup produces a PodGroup which passes validation with no tweaks. +func mkPodGroup(tweaks ...func(pg *scheduling.PodGroup)) *scheduling.PodGroup { + pg := &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "workload", Namespace: "ns"}, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w", + PodGroupTemplateName: "t1", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + SchedulingConstraints: &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{ + {Key: "foo"}, + }, + }, + }, + } + for _, tweak := range tweaks { + tweak(pg) + } + return pg +} diff --git a/pkg/apis/scheduling/zz_generated.deepcopy.go b/pkg/apis/scheduling/zz_generated.deepcopy.go index 853afd8c1c468..636a9590aec82 100644 --- a/pkg/apis/scheduling/zz_generated.deepcopy.go +++ b/pkg/apis/scheduling/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package scheduling import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" core "k8s.io/kubernetes/pkg/apis/core" ) @@ -61,7 +62,10 @@ func (in *GangSchedulingPolicy) DeepCopy() *GangSchedulingPolicy { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodGroup) DeepCopyInto(out *PodGroup) { *out = *in - in.Policy.DeepCopyInto(&out.Policy) + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -75,8 +79,117 @@ func (in *PodGroup) DeepCopy() *PodGroup { return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupList) DeepCopyInto(out *PodGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PodGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupList. +func (in *PodGroupList) DeepCopy() *PodGroupList { + if in == nil { + return nil + } + out := new(PodGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupResourceClaim) DeepCopyInto(out *PodGroupResourceClaim) { + *out = *in + if in.ResourceClaimName != nil { + in, out := &in.ResourceClaimName, &out.ResourceClaimName + *out = new(string) + **out = **in + } + if in.ResourceClaimTemplateName != nil { + in, out := &in.ResourceClaimTemplateName, &out.ResourceClaimTemplateName + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupResourceClaim. +func (in *PodGroupResourceClaim) DeepCopy() *PodGroupResourceClaim { + if in == nil { + return nil + } + out := new(PodGroupResourceClaim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupResourceClaimStatus) DeepCopyInto(out *PodGroupResourceClaimStatus) { + *out = *in + if in.ResourceClaimName != nil { + in, out := &in.ResourceClaimName, &out.ResourceClaimName + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupResourceClaimStatus. +func (in *PodGroupResourceClaimStatus) DeepCopy() *PodGroupResourceClaimStatus { + if in == nil { + return nil + } + out := new(PodGroupResourceClaimStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupSchedulingConstraints) DeepCopyInto(out *PodGroupSchedulingConstraints) { + *out = *in + if in.Topology != nil { + in, out := &in.Topology, &out.Topology + *out = make([]TopologyConstraint, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupSchedulingConstraints. +func (in *PodGroupSchedulingConstraints) DeepCopy() *PodGroupSchedulingConstraints { + if in == nil { + return nil + } + out := new(PodGroupSchedulingConstraints) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodGroupPolicy) DeepCopyInto(out *PodGroupPolicy) { +func (in *PodGroupSchedulingPolicy) DeepCopyInto(out *PodGroupSchedulingPolicy) { *out = *in if in.Basic != nil { in, out := &in.Basic, &out.Basic @@ -91,12 +204,146 @@ func (in *PodGroupPolicy) DeepCopyInto(out *PodGroupPolicy) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupPolicy. -func (in *PodGroupPolicy) DeepCopy() *PodGroupPolicy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupSchedulingPolicy. +func (in *PodGroupSchedulingPolicy) DeepCopy() *PodGroupSchedulingPolicy { if in == nil { return nil } - out := new(PodGroupPolicy) + out := new(PodGroupSchedulingPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupSpec) DeepCopyInto(out *PodGroupSpec) { + *out = *in + if in.PodGroupTemplateRef != nil { + in, out := &in.PodGroupTemplateRef, &out.PodGroupTemplateRef + *out = new(PodGroupTemplateReference) + (*in).DeepCopyInto(*out) + } + in.SchedulingPolicy.DeepCopyInto(&out.SchedulingPolicy) + if in.SchedulingConstraints != nil { + in, out := &in.SchedulingConstraints, &out.SchedulingConstraints + *out = new(PodGroupSchedulingConstraints) + (*in).DeepCopyInto(*out) + } + if in.ResourceClaims != nil { + in, out := &in.ResourceClaims, &out.ResourceClaims + *out = make([]PodGroupResourceClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DisruptionMode != nil { + in, out := &in.DisruptionMode, &out.DisruptionMode + *out = new(DisruptionMode) + **out = **in + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupSpec. +func (in *PodGroupSpec) DeepCopy() *PodGroupSpec { + if in == nil { + return nil + } + out := new(PodGroupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupStatus) DeepCopyInto(out *PodGroupStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ResourceClaimStatuses != nil { + in, out := &in.ResourceClaimStatuses, &out.ResourceClaimStatuses + *out = make([]PodGroupResourceClaimStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupStatus. +func (in *PodGroupStatus) DeepCopy() *PodGroupStatus { + if in == nil { + return nil + } + out := new(PodGroupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupTemplate) DeepCopyInto(out *PodGroupTemplate) { + *out = *in + in.SchedulingPolicy.DeepCopyInto(&out.SchedulingPolicy) + if in.SchedulingConstraints != nil { + in, out := &in.SchedulingConstraints, &out.SchedulingConstraints + *out = new(PodGroupSchedulingConstraints) + (*in).DeepCopyInto(*out) + } + if in.ResourceClaims != nil { + in, out := &in.ResourceClaims, &out.ResourceClaims + *out = make([]PodGroupResourceClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DisruptionMode != nil { + in, out := &in.DisruptionMode, &out.DisruptionMode + *out = new(DisruptionMode) + **out = **in + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupTemplate. +func (in *PodGroupTemplate) DeepCopy() *PodGroupTemplate { + if in == nil { + return nil + } + out := new(PodGroupTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupTemplateReference) DeepCopyInto(out *PodGroupTemplateReference) { + *out = *in + if in.Workload != nil { + in, out := &in.Workload, &out.Workload + *out = new(WorkloadPodGroupTemplateReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupTemplateReference. +func (in *PodGroupTemplateReference) DeepCopy() *PodGroupTemplateReference { + if in == nil { + return nil + } + out := new(PodGroupTemplateReference) in.DeepCopyInto(out) return out } @@ -165,6 +412,22 @@ func (in *PriorityClassList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TopologyConstraint) DeepCopyInto(out *TopologyConstraint) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TopologyConstraint. +func (in *TopologyConstraint) DeepCopy() *TopologyConstraint { + if in == nil { + return nil + } + out := new(TopologyConstraint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TypedLocalObjectReference) DeepCopyInto(out *TypedLocalObjectReference) { *out = *in @@ -241,6 +504,22 @@ func (in *WorkloadList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadPodGroupTemplateReference) DeepCopyInto(out *WorkloadPodGroupTemplateReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadPodGroupTemplateReference. +func (in *WorkloadPodGroupTemplateReference) DeepCopy() *WorkloadPodGroupTemplateReference { + if in == nil { + return nil + } + out := new(WorkloadPodGroupTemplateReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) { *out = *in @@ -249,9 +528,9 @@ func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) { *out = new(TypedLocalObjectReference) **out = **in } - if in.PodGroups != nil { - in, out := &in.PodGroups, &out.PodGroups - *out = make([]PodGroup, len(*in)) + if in.PodGroupTemplates != nil { + in, out := &in.PodGroupTemplates, &out.PodGroupTemplates + *out = make([]PodGroupTemplate, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/apis/storage/types.go b/pkg/apis/storage/types.go index 02aa225f34161..f32c5268ceebd 100644 --- a/pkg/apis/storage/types.go +++ b/pkg/apis/storage/types.go @@ -206,7 +206,7 @@ type VolumeError struct { // errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations. // - // This is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. + // This is an optional field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. // // +featureGate=MutableCSINodeAllocatableCount // +optional @@ -426,7 +426,7 @@ type CSIDriverSpec struct { // occur (neither periodic nor upon detecting capacity-related failures), and the // allocatable.count remains static. The minimum allowed value for this field is 10 seconds. // - // This is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled. + // This feature requires the MutableCSINodeAllocatableCount feature gate to be enabled. // // This field is mutable. // @@ -458,6 +458,27 @@ type CSIDriverSpec struct { // +featureGate=CSIServiceAccountTokenSecrets // +optional ServiceAccountTokenInSecrets *bool + + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + // +featureGate=VolumeLimitScaling + // +optional + PreventPodSchedulingIfMissing *bool } // FSGroupPolicy specifies if a CSI Driver supports modifying diff --git a/pkg/apis/storage/v1/defaults.go b/pkg/apis/storage/v1/defaults.go index 9377281d6cba9..26531ee1fe315 100644 --- a/pkg/apis/storage/v1/defaults.go +++ b/pkg/apis/storage/v1/defaults.go @@ -68,4 +68,9 @@ func SetDefaults_CSIDriver(obj *storagev1.CSIDriver) { obj.Spec.SELinuxMount = new(bool) *(obj.Spec.SELinuxMount) = false } + + if obj.Spec.PreventPodSchedulingIfMissing == nil && utilfeature.DefaultFeatureGate.Enabled(features.VolumeLimitScaling) { + obj.Spec.PreventPodSchedulingIfMissing = new(bool) + *(obj.Spec.PreventPodSchedulingIfMissing) = false + } } diff --git a/pkg/apis/storage/v1/defaults_test.go b/pkg/apis/storage/v1/defaults_test.go index ecb99686a12b5..662575c01f05f 100644 --- a/pkg/apis/storage/v1/defaults_test.go +++ b/pkg/apis/storage/v1/defaults_test.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-cmp/cmp" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -122,7 +123,6 @@ func TestSetDefaultCSIDriver(t *testing.T) { } func TestSetDefaultSELinuxMountReadWriteOncePodEnabled(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true) driver := &storagev1.CSIDriver{} // field should be defaulted @@ -137,6 +137,7 @@ func TestSetDefaultSELinuxMountReadWriteOncePodEnabled(t *testing.T) { } func TestSetDefaultSELinuxMountReadWriteOncePodDisabled(t *testing.T) { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, false) driver := &storagev1.CSIDriver{} @@ -147,3 +148,30 @@ func TestSetDefaultSELinuxMountReadWriteOncePodDisabled(t *testing.T) { t.Errorf("Expected SELinuxMount to remain nil, got: %+v", outSELinuxMount) } } + +func TestSetDefaultPreventPodSchedulingIfMissingVolumeLimitScalingEnabled(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeLimitScaling, true) + driver := &storagev1.CSIDriver{} + + // field should be defaulted + defaultPreventPodSchedulingIfMissing := false + output := roundTrip(t, runtime.Object(driver)).(*storagev1.CSIDriver) + outPreventPodSchedulingIfMissing := output.Spec.PreventPodSchedulingIfMissing + if outPreventPodSchedulingIfMissing == nil { + t.Errorf("Expected PreventPodSchedulingIfMissing to be defaulted to: %+v, got: nil", defaultPreventPodSchedulingIfMissing) + } else if *outPreventPodSchedulingIfMissing != defaultPreventPodSchedulingIfMissing { + t.Errorf("Expected PreventPodSchedulingIfMissing to be defaulted to: %+v, got: %+v", defaultPreventPodSchedulingIfMissing, *outPreventPodSchedulingIfMissing) + } +} + +func TestSetDefaultPreventPodSchedulingIfMissingVolumeLimitScalingDisabled(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeLimitScaling, false) + driver := &storagev1.CSIDriver{} + + // field should not be defaulted + output := roundTrip(t, runtime.Object(driver)).(*storagev1.CSIDriver) + outPreventPodSchedulingIfMissing := output.Spec.PreventPodSchedulingIfMissing + if outPreventPodSchedulingIfMissing != nil { + t.Errorf("Expected PreventPodSchedulingIfMissing to remain nil, got: %+v", *outPreventPodSchedulingIfMissing) + } +} diff --git a/pkg/apis/storage/v1/zz_generated.conversion.go b/pkg/apis/storage/v1/zz_generated.conversion.go index 98b90848128c3..4bc92bdd0916d 100644 --- a/pkg/apis/storage/v1/zz_generated.conversion.go +++ b/pkg/apis/storage/v1/zz_generated.conversion.go @@ -334,6 +334,7 @@ func autoConvert_v1_CSIDriverSpec_To_storage_CSIDriverSpec(in *storagev1.CSIDriv out.SELinuxMount = (*bool)(unsafe.Pointer(in.SELinuxMount)) out.NodeAllocatableUpdatePeriodSeconds = (*int64)(unsafe.Pointer(in.NodeAllocatableUpdatePeriodSeconds)) out.ServiceAccountTokenInSecrets = (*bool)(unsafe.Pointer(in.ServiceAccountTokenInSecrets)) + out.PreventPodSchedulingIfMissing = (*bool)(unsafe.Pointer(in.PreventPodSchedulingIfMissing)) return nil } @@ -353,6 +354,7 @@ func autoConvert_storage_CSIDriverSpec_To_v1_CSIDriverSpec(in *storage.CSIDriver out.SELinuxMount = (*bool)(unsafe.Pointer(in.SELinuxMount)) out.NodeAllocatableUpdatePeriodSeconds = (*int64)(unsafe.Pointer(in.NodeAllocatableUpdatePeriodSeconds)) out.ServiceAccountTokenInSecrets = (*bool)(unsafe.Pointer(in.ServiceAccountTokenInSecrets)) + out.PreventPodSchedulingIfMissing = (*bool)(unsafe.Pointer(in.PreventPodSchedulingIfMissing)) return nil } diff --git a/pkg/apis/storage/v1/zz_generated.validations.go b/pkg/apis/storage/v1/zz_generated.validations.go index 7122d64164dc8..55b3acf49423c 100644 --- a/pkg/apis/storage/v1/zz_generated.validations.go +++ b/pkg/apis/storage/v1/zz_generated.validations.go @@ -25,6 +25,7 @@ import ( context "context" fmt "fmt" + corev1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" equality "k8s.io/apimachinery/pkg/api/equality" operation "k8s.io/apimachinery/pkg/api/operation" @@ -47,11 +48,11 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type StorageClassList - scheme.AddValidationFunc((*storagev1.StorageClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + // type VolumeAttachment + scheme.AddValidationFunc((*storagev1.VolumeAttachment)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { - case "/": - return Validate_StorageClassList(ctx, op, nil /* fldPath */, obj.(*storagev1.StorageClassList), safe.Cast[*storagev1.StorageClassList](oldObj)) + case "/", "/status": + return Validate_VolumeAttachment(ctx, op, nil /* fldPath */, obj.(*storagev1.VolumeAttachment), safe.Cast[*storagev1.VolumeAttachment](oldObj)) } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) @@ -73,7 +74,11 @@ func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -83,32 +88,137 @@ func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath return }(fldPath.Child("provisioner"), &obj.Provisioner, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) *string { return &oldObj.Provisioner }), oldObj != nil)...) - // field storagev1.StorageClass.Parameters has no validation - // field storagev1.StorageClass.ReclaimPolicy has no validation + // field storagev1.StorageClass.Parameters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("parameters"), obj.Parameters, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) map[string]string { return oldObj.Parameters }), oldObj != nil)...) + + // field storagev1.StorageClass.ReclaimPolicy + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *corev1.PersistentVolumeReclaimPolicy, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("reclaimPolicy"), obj.ReclaimPolicy, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) *corev1.PersistentVolumeReclaimPolicy { + return oldObj.ReclaimPolicy + }), oldObj != nil)...) + // field storagev1.StorageClass.MountOptions has no validation // field storagev1.StorageClass.AllowVolumeExpansion has no validation - // field storagev1.StorageClass.VolumeBindingMode has no validation + + // field storagev1.StorageClass.VolumeBindingMode + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *storagev1.VolumeBindingMode, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("volumeBindingMode"), obj.VolumeBindingMode, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) *storagev1.VolumeBindingMode { return oldObj.VolumeBindingMode }), oldObj != nil)...) + // field storagev1.StorageClass.AllowedTopologies has no validation return errs } -// Validate_StorageClassList validates an instance of StorageClassList according +// Validate_VolumeAttachment validates an instance of VolumeAttachment according // to declarative validation rules in the API schema. -func Validate_StorageClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1.StorageClassList) (errs field.ErrorList) { - // field storagev1.StorageClassList.TypeMeta has no validation - // field storagev1.StorageClassList.ListMeta has no validation +func Validate_VolumeAttachment(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1.VolumeAttachment) (errs field.ErrorList) { + // field storagev1.VolumeAttachment.TypeMeta has no validation + // field storagev1.VolumeAttachment.ObjectMeta has no validation - // field storagev1.StorageClassList.Items + // field storagev1.VolumeAttachment.Spec errs = append(errs, - func(fldPath *field.Path, obj, oldObj []storagev1.StorageClass, oldValueCorrelated bool) (errs field.ErrorList) { + func(fldPath *field.Path, obj, oldObj *storagev1.VolumeAttachmentSpec, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StorageClass)...) + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_VolumeAttachmentSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *storagev1.VolumeAttachment) *storagev1.VolumeAttachmentSpec { return &oldObj.Spec }), oldObj != nil)...) + + // field storagev1.VolumeAttachment.Status has no validation + return errs +} + +// Validate_VolumeAttachmentSpec validates an instance of VolumeAttachmentSpec according +// to declarative validation rules in the API schema. +func Validate_VolumeAttachmentSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1.VolumeAttachmentSpec) (errs field.ErrorList) { + // field storagev1.VolumeAttachmentSpec.Attacher + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *storagev1.StorageClassList) []storagev1.StorageClass { return oldObj.Items }), oldObj != nil)...) + }(fldPath.Child("attacher"), &obj.Attacher, safe.Field(oldObj, func(oldObj *storagev1.VolumeAttachmentSpec) *string { return &oldObj.Attacher }), oldObj != nil)...) + // field storagev1.VolumeAttachmentSpec.Source has no validation + // field storagev1.VolumeAttachmentSpec.NodeName has no validation return errs } diff --git a/pkg/apis/storage/v1alpha1/zz_generated.validations.go b/pkg/apis/storage/v1alpha1/zz_generated.validations.go index 483365f8c94a1..3f8a2ee3f8290 100644 --- a/pkg/apis/storage/v1alpha1/zz_generated.validations.go +++ b/pkg/apis/storage/v1alpha1/zz_generated.validations.go @@ -22,7 +22,16 @@ limitations under the License. package v1alpha1 import ( + context "context" + fmt "fmt" + + storagev1alpha1 "k8s.io/api/storage/v1alpha1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" ) func init() { localSchemeBuilder.Register(RegisterValidations) } @@ -30,5 +39,75 @@ func init() { localSchemeBuilder.Register(RegisterValidations) } // RegisterValidations adds validation functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterValidations(scheme *runtime.Scheme) error { + // type VolumeAttachment + scheme.AddValidationFunc((*storagev1alpha1.VolumeAttachment)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/", "/status": + return Validate_VolumeAttachment(ctx, op, nil /* fldPath */, obj.(*storagev1alpha1.VolumeAttachment), safe.Cast[*storagev1alpha1.VolumeAttachment](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) return nil } + +// Validate_VolumeAttachment validates an instance of VolumeAttachment according +// to declarative validation rules in the API schema. +func Validate_VolumeAttachment(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1alpha1.VolumeAttachment) (errs field.ErrorList) { + // field storagev1alpha1.VolumeAttachment.TypeMeta has no validation + // field storagev1alpha1.VolumeAttachment.ObjectMeta has no validation + + // field storagev1alpha1.VolumeAttachment.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *storagev1alpha1.VolumeAttachmentSpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_VolumeAttachmentSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *storagev1alpha1.VolumeAttachment) *storagev1alpha1.VolumeAttachmentSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field storagev1alpha1.VolumeAttachment.Status has no validation + return errs +} + +// Validate_VolumeAttachmentSpec validates an instance of VolumeAttachmentSpec according +// to declarative validation rules in the API schema. +func Validate_VolumeAttachmentSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1alpha1.VolumeAttachmentSpec) (errs field.ErrorList) { + // field storagev1alpha1.VolumeAttachmentSpec.Attacher + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) + return + }(fldPath.Child("attacher"), &obj.Attacher, safe.Field(oldObj, func(oldObj *storagev1alpha1.VolumeAttachmentSpec) *string { return &oldObj.Attacher }), oldObj != nil)...) + + // field storagev1alpha1.VolumeAttachmentSpec.Source has no validation + // field storagev1alpha1.VolumeAttachmentSpec.NodeName has no validation + return errs +} diff --git a/pkg/apis/storage/v1beta1/defaults_test.go b/pkg/apis/storage/v1beta1/defaults_test.go index cf1ce6003b4bb..cd40fc1ee5ccc 100644 --- a/pkg/apis/storage/v1beta1/defaults_test.go +++ b/pkg/apis/storage/v1beta1/defaults_test.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-cmp/cmp" storagev1beta1 "k8s.io/api/storage/v1beta1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -152,7 +153,6 @@ func TestSetDefaultCSIDriver(t *testing.T) { } func TestSetDefaultSELinuxMountReadWriteOncePodEnabled(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true) driver := &storagev1beta1.CSIDriver{} // field should be defaulted @@ -167,6 +167,7 @@ func TestSetDefaultSELinuxMountReadWriteOncePodEnabled(t *testing.T) { } func TestSetDefaultSELinuxMountReadWriteOncePodDisabled(t *testing.T) { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, false) driver := &storagev1beta1.CSIDriver{} diff --git a/pkg/apis/storage/v1beta1/zz_generated.conversion.go b/pkg/apis/storage/v1beta1/zz_generated.conversion.go index e72efb43aaa0c..04dc7078d1756 100644 --- a/pkg/apis/storage/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/storage/v1beta1/zz_generated.conversion.go @@ -334,6 +334,7 @@ func autoConvert_v1beta1_CSIDriverSpec_To_storage_CSIDriverSpec(in *storagev1bet out.SELinuxMount = (*bool)(unsafe.Pointer(in.SELinuxMount)) out.NodeAllocatableUpdatePeriodSeconds = (*int64)(unsafe.Pointer(in.NodeAllocatableUpdatePeriodSeconds)) out.ServiceAccountTokenInSecrets = (*bool)(unsafe.Pointer(in.ServiceAccountTokenInSecrets)) + out.PreventPodSchedulingIfMissing = (*bool)(unsafe.Pointer(in.PreventPodSchedulingIfMissing)) return nil } @@ -353,6 +354,7 @@ func autoConvert_storage_CSIDriverSpec_To_v1beta1_CSIDriverSpec(in *storage.CSID out.SELinuxMount = (*bool)(unsafe.Pointer(in.SELinuxMount)) out.NodeAllocatableUpdatePeriodSeconds = (*int64)(unsafe.Pointer(in.NodeAllocatableUpdatePeriodSeconds)) out.ServiceAccountTokenInSecrets = (*bool)(unsafe.Pointer(in.ServiceAccountTokenInSecrets)) + out.PreventPodSchedulingIfMissing = (*bool)(unsafe.Pointer(in.PreventPodSchedulingIfMissing)) return nil } diff --git a/pkg/apis/storage/v1beta1/zz_generated.validations.go b/pkg/apis/storage/v1beta1/zz_generated.validations.go index fcb28065d2189..43aff91ac77ad 100644 --- a/pkg/apis/storage/v1beta1/zz_generated.validations.go +++ b/pkg/apis/storage/v1beta1/zz_generated.validations.go @@ -25,6 +25,7 @@ import ( context "context" fmt "fmt" + v1 "k8s.io/api/core/v1" storagev1beta1 "k8s.io/api/storage/v1beta1" equality "k8s.io/apimachinery/pkg/api/equality" operation "k8s.io/apimachinery/pkg/api/operation" @@ -47,11 +48,11 @@ func RegisterValidations(scheme *runtime.Scheme) error { } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) - // type StorageClassList - scheme.AddValidationFunc((*storagev1beta1.StorageClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + // type VolumeAttachment + scheme.AddValidationFunc((*storagev1beta1.VolumeAttachment)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { - case "/": - return Validate_StorageClassList(ctx, op, nil /* fldPath */, obj.(*storagev1beta1.StorageClassList), safe.Cast[*storagev1beta1.StorageClassList](oldObj)) + case "/", "/status": + return Validate_VolumeAttachment(ctx, op, nil /* fldPath */, obj.(*storagev1beta1.VolumeAttachment), safe.Cast[*storagev1beta1.VolumeAttachment](oldObj)) } return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} }) @@ -73,7 +74,11 @@ func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } @@ -83,32 +88,141 @@ func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath return }(fldPath.Child("provisioner"), &obj.Provisioner, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) *string { return &oldObj.Provisioner }), oldObj != nil)...) - // field storagev1beta1.StorageClass.Parameters has no validation - // field storagev1beta1.StorageClass.ReclaimPolicy has no validation + // field storagev1beta1.StorageClass.Parameters + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("parameters"), obj.Parameters, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) map[string]string { return oldObj.Parameters }), oldObj != nil)...) + + // field storagev1beta1.StorageClass.ReclaimPolicy + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *v1.PersistentVolumeReclaimPolicy, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("reclaimPolicy"), obj.ReclaimPolicy, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) *v1.PersistentVolumeReclaimPolicy { + return oldObj.ReclaimPolicy + }), oldObj != nil)...) + // field storagev1beta1.StorageClass.MountOptions has no validation // field storagev1beta1.StorageClass.AllowVolumeExpansion has no validation - // field storagev1beta1.StorageClass.VolumeBindingMode has no validation + + // field storagev1beta1.StorageClass.VolumeBindingMode + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *storagev1beta1.VolumeBindingMode, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("volumeBindingMode"), obj.VolumeBindingMode, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) *storagev1beta1.VolumeBindingMode { + return oldObj.VolumeBindingMode + }), oldObj != nil)...) + // field storagev1beta1.StorageClass.AllowedTopologies has no validation return errs } -// Validate_StorageClassList validates an instance of StorageClassList according +// Validate_VolumeAttachment validates an instance of VolumeAttachment according // to declarative validation rules in the API schema. -func Validate_StorageClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1beta1.StorageClassList) (errs field.ErrorList) { - // field storagev1beta1.StorageClassList.TypeMeta has no validation - // field storagev1beta1.StorageClassList.ListMeta has no validation +func Validate_VolumeAttachment(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1beta1.VolumeAttachment) (errs field.ErrorList) { + // field storagev1beta1.VolumeAttachment.TypeMeta has no validation + // field storagev1beta1.VolumeAttachment.ObjectMeta has no validation - // field storagev1beta1.StorageClassList.Items + // field storagev1beta1.VolumeAttachment.Spec errs = append(errs, - func(fldPath *field.Path, obj, oldObj []storagev1beta1.StorageClass, oldValueCorrelated bool) (errs field.ErrorList) { + func(fldPath *field.Path, obj, oldObj *storagev1beta1.VolumeAttachmentSpec, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { return nil } - // iterate the list and call the type's validation function - errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StorageClass)...) + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_VolumeAttachmentSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *storagev1beta1.VolumeAttachment) *storagev1beta1.VolumeAttachmentSpec { + return &oldObj.Spec + }), oldObj != nil)...) + + // field storagev1beta1.VolumeAttachment.Status has no validation + return errs +} + +// Validate_VolumeAttachmentSpec validates an instance of VolumeAttachmentSpec according +// to declarative validation rules in the API schema. +func Validate_VolumeAttachmentSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1beta1.VolumeAttachmentSpec) (errs field.ErrorList) { + // field storagev1beta1.VolumeAttachmentSpec.Attacher + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 63).MarkAlpha()...) return - }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClassList) []storagev1beta1.StorageClass { return oldObj.Items }), oldObj != nil)...) + }(fldPath.Child("attacher"), &obj.Attacher, safe.Field(oldObj, func(oldObj *storagev1beta1.VolumeAttachmentSpec) *string { return &oldObj.Attacher }), oldObj != nil)...) + // field storagev1beta1.VolumeAttachmentSpec.Source has no validation + // field storagev1beta1.VolumeAttachmentSpec.NodeName has no validation return errs } diff --git a/pkg/apis/storage/validation/validation.go b/pkg/apis/storage/validation/validation.go index e103b613aae58..e8267244a8f7e 100644 --- a/pkg/apis/storage/validation/validation.go +++ b/pkg/apis/storage/validation/validation.go @@ -44,15 +44,9 @@ const ( maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB maxVolumeErrorMessageSize = 1024 - csiNodeIDMaxLength = 192 - csiNodeIDLongerMaxLength = 256 + csiNodeIDMaxLength = 256 ) -// CSINodeValidationOptions contains the validation options for validating CSINode -type CSINodeValidationOptions struct { - AllowLongNodeID bool -} - // ValidateStorageClass validates a StorageClass. func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata")) @@ -68,19 +62,13 @@ func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList { // ValidateStorageClassUpdate tests if an update to StorageClass is valid. func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageClass) field.ErrorList { allErrs := apivalidation.ValidateObjectMetaUpdate(&storageClass.ObjectMeta, &oldStorageClass.ObjectMeta, field.NewPath("metadata")) - if !reflect.DeepEqual(oldStorageClass.Parameters, storageClass.Parameters) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("parameters"), "updates to parameters are forbidden.")) - } + allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(storageClass.Parameters, oldStorageClass.Parameters, field.NewPath("parameters")).WithOrigin("immutable").MarkCoveredByDeclarative()...) - if storageClass.Provisioner != oldStorageClass.Provisioner { - allErrs = append(allErrs, field.Forbidden(field.NewPath("provisioner"), "updates to provisioner are forbidden.")) - } + allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.Provisioner, oldStorageClass.Provisioner, field.NewPath("provisioner")).WithOrigin("immutable").MarkCoveredByDeclarative()...) - if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy { - allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden.")) - } + allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.ReclaimPolicy, oldStorageClass.ReclaimPolicy, field.NewPath("reclaimPolicy")).WithOrigin("immutable").MarkCoveredByDeclarative()...) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode")).WithOrigin("immutable").MarkCoveredByDeclarative()...) return allErrs } @@ -88,7 +76,7 @@ func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageCl func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(provisioner) == 0 { - allErrs = append(allErrs, field.Required(fldPath, provisioner)).MarkCoveredByDeclarative() + allErrs = append(allErrs, field.Required(fldPath, provisioner).MarkCoveredByDeclarative()) } if len(provisioner) > 0 { allErrs = append(allErrs, apivalidation.ValidateQualifiedName(strings.ToLower(provisioner), fldPath)...) @@ -129,6 +117,9 @@ var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDe // provisioning for storage classes with impossible reclaim policies, e.g. EBS is not Recyclable func validateReclaimPolicy(reclaimPolicy *api.PersistentVolumeReclaimPolicy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + if reclaimPolicy == nil { + return allErrs + } if len(string(*reclaimPolicy)) > 0 { if !supportedReclaimPolicy.Has(string(*reclaimPolicy)) { allErrs = append(allErrs, field.NotSupported(fldPath, reclaimPolicy, supportedReclaimPolicy.List())) @@ -142,14 +133,6 @@ func ValidateVolumeAttachment(volumeAttachment *storage.VolumeAttachment) field. allErrs := apivalidation.ValidateObjectMeta(&volumeAttachment.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata")) allErrs = append(allErrs, validateVolumeAttachmentSpec(&volumeAttachment.Spec, field.NewPath("spec"))...) allErrs = append(allErrs, validateVolumeAttachmentStatus(&volumeAttachment.Status, field.NewPath("status"))...) - return allErrs -} - -// ValidateVolumeAttachmentV1 validates a v1/VolumeAttachment. It contains only extra checks missing in -// ValidateVolumeAttachment. -func ValidateVolumeAttachmentV1(volumeAttachment *storage.VolumeAttachment) field.ErrorList { - allErrs := apivalidation.ValidateCSIDriverName(volumeAttachment.Spec.Attacher, field.NewPath("spec.attacher")) - if volumeAttachment.Spec.Source.PersistentVolumeName != nil { pvName := *volumeAttachment.Spec.Source.PersistentVolumeName for _, msg := range apivalidation.ValidatePersistentVolumeName(pvName, false) { @@ -163,22 +146,12 @@ func ValidateVolumeAttachmentV1(volumeAttachment *storage.VolumeAttachment) fiel // has valid data. func validateVolumeAttachmentSpec( spec *storage.VolumeAttachmentSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, validateAttacher(spec.Attacher, fldPath.Child("attacher"))...) + allErrs := apivalidation.ValidateCSIDriverName(spec.Attacher, fldPath.Child("attacher"), apivalidation.RequiredCovered, apivalidation.FormatCovered, apivalidation.SizeCovered) allErrs = append(allErrs, validateVolumeAttachmentSource(&spec.Source, fldPath.Child("source"))...) allErrs = append(allErrs, validateNodeName(spec.NodeName, fldPath.Child("nodeName"))...) return allErrs } -// validateAttacher tests if attacher is a valid qualified name. -func validateAttacher(attacher string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(attacher) == 0 { - allErrs = append(allErrs, field.Required(fldPath, attacher)) - } - return allErrs -} - // validateSource tests if the source is valid for VolumeAttachment. func validateVolumeAttachmentSource(source *storage.VolumeAttachmentSource, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -252,12 +225,7 @@ func validateVolumeError(e *storage.VolumeError, fldPath *field.Path) field.Erro // ValidateVolumeAttachmentUpdate validates a VolumeAttachment. func ValidateVolumeAttachmentUpdate(new, old *storage.VolumeAttachment) field.ErrorList { allErrs := ValidateVolumeAttachment(new) - - // Spec is read-only - // If this ever relaxes in the future, make sure to increment the Generation number in PrepareForUpdate - if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable")) - } + allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(new.Spec, old.Spec, field.NewPath("spec")).WithOrigin("immutable").MarkCoveredByDeclarative()...) return allErrs } @@ -303,15 +271,15 @@ func validateAllowedTopologies(topologies []api.TopologySelectorTerm, fldPath *f } // ValidateCSINode validates a CSINode. -func ValidateCSINode(csiNode *storage.CSINode, validationOpts CSINodeValidationOptions) field.ErrorList { +func ValidateCSINode(csiNode *storage.CSINode) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&csiNode.ObjectMeta, false, apivalidation.ValidateNodeName, field.NewPath("metadata")) - allErrs = append(allErrs, validateCSINodeSpec(&csiNode.Spec, field.NewPath("spec"), validationOpts)...) + allErrs = append(allErrs, validateCSINodeSpec(&csiNode.Spec, field.NewPath("spec"))...) return allErrs } // ValidateCSINodeUpdate validates a CSINode. -func ValidateCSINodeUpdate(new, old *storage.CSINode, validationOpts CSINodeValidationOptions) field.ErrorList { - allErrs := ValidateCSINode(new, validationOpts) +func ValidateCSINodeUpdate(new, old *storage.CSINode) field.ErrorList { + allErrs := ValidateCSINode(new) // Validate modifying fields inside an existing CSINodeDriver entry for _, oldDriver := range old.Spec.Drivers { @@ -339,39 +307,35 @@ func ValidateCSINodeUpdate(new, old *storage.CSINode, validationOpts CSINodeVali } // ValidateCSINodeSpec tests that the specified CSINodeSpec has valid data. -func validateCSINodeSpec( - spec *storage.CSINodeSpec, fldPath *field.Path, validationOpts CSINodeValidationOptions) field.ErrorList { +func validateCSINodeSpec(spec *storage.CSINodeSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - allErrs = append(allErrs, validateCSINodeDrivers(spec.Drivers, fldPath.Child("drivers"), validationOpts)...) + allErrs = append(allErrs, validateCSINodeDrivers(spec.Drivers, fldPath.Child("drivers"))...) return allErrs } // ValidateCSINodeDrivers tests that the specified CSINodeDrivers have valid data. -func validateCSINodeDrivers(drivers []storage.CSINodeDriver, fldPath *field.Path, validationOpts CSINodeValidationOptions) field.ErrorList { +func validateCSINodeDrivers(drivers []storage.CSINodeDriver, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} driverNamesInSpecs := sets.New[string]() for i, driver := range drivers { idxPath := fldPath.Index(i) - allErrs = append(allErrs, validateCSINodeDriver(driver, driverNamesInSpecs, idxPath, validationOpts)...) + allErrs = append(allErrs, validateCSINodeDriver(driver, driverNamesInSpecs, idxPath)...) } return allErrs } // validateCSINodeDriverNodeID tests if Name in CSINodeDriver is a valid node id. -func validateCSINodeDriverNodeID(nodeID string, fldPath *field.Path, validationOpts CSINodeValidationOptions) field.ErrorList { +func validateCSINodeDriverNodeID(nodeID string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // nodeID is always required if len(nodeID) == 0 { allErrs = append(allErrs, field.Required(fldPath, nodeID)) } - maxLength := csiNodeIDMaxLength - if validationOpts.AllowLongNodeID { - maxLength = csiNodeIDLongerMaxLength - } - if len(nodeID) > maxLength { - allErrs = append(allErrs, field.Invalid(fldPath, nodeID, fmt.Sprintf("must be %d characters or less", maxLength))) + + if len(nodeID) > csiNodeIDMaxLength { + allErrs = append(allErrs, field.Invalid(fldPath, nodeID, fmt.Sprintf("must be %d characters or less", csiNodeIDMaxLength))) } return allErrs } @@ -389,12 +353,11 @@ func validateCSINodeDriverAllocatable(a *storage.VolumeNodeResources, fldPath *f } // validateCSINodeDriver tests if CSINodeDriver has valid entries -func validateCSINodeDriver(driver storage.CSINodeDriver, driverNamesInSpecs sets.Set[string], fldPath *field.Path, - validationOpts CSINodeValidationOptions) field.ErrorList { +func validateCSINodeDriver(driver storage.CSINodeDriver, driverNamesInSpecs sets.Set[string], fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateCSIDriverName(driver.Name, fldPath.Child("name"))...) - allErrs = append(allErrs, validateCSINodeDriverNodeID(driver.NodeID, fldPath.Child("nodeID"), validationOpts)...) + allErrs = append(allErrs, validateCSINodeDriverNodeID(driver.NodeID, fldPath.Child("nodeID"))...) allErrs = append(allErrs, validateCSINodeDriverAllocatable(driver.Allocatable, fldPath.Child("allocatable"))...) // check for duplicate entries for the same driver in specs @@ -457,6 +420,7 @@ func validateCSIDriverSpec( allErrs = append(allErrs, validateSELinuxMount(spec.SELinuxMount, fldPath.Child("seLinuxMount"))...) allErrs = append(allErrs, validateNodeAllocatableUpdatePeriodSeconds(spec.NodeAllocatableUpdatePeriodSeconds, fldPath.Child("nodeAllocatableUpdatePeriodSeconds"))...) allErrs = append(allErrs, validateServiceAccountTokenInSecrets(spec.ServiceAccountTokenInSecrets, spec.TokenRequests, fldPath.Child("serviceAccountTokenInSecrets"))...) + allErrs = append(allErrs, validatePreventPodSchedulingIfMissing(spec.PreventPodSchedulingIfMissing, fldPath.Child("preventPodSchedulingIfMissing"))...) return allErrs } @@ -583,6 +547,16 @@ func validateServiceAccountTokenInSecrets(serviceAccountTokenInSecrets *bool, to return allErrs } +// validatePreventPodSchedulingIfMissing validates that preventPodSchedulingIfMissing is not set when the VolumeLimitScaling feature gate is disabled. +func validatePreventPodSchedulingIfMissing(preventPodSchedulingIfMissing *bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if preventPodSchedulingIfMissing == nil && utilfeature.DefaultFeatureGate.Enabled(features.VolumeLimitScaling) { + allErrs = append(allErrs, field.Required(fldPath, "")) + } + + return allErrs +} + // ValidateStorageCapacityName checks that a name is appropriate for a // CSIStorageCapacity object. var ValidateStorageCapacityName = apimachineryvalidation.NameIsDNSSubdomain diff --git a/pkg/apis/storage/validation/validation_test.go b/pkg/apis/storage/validation/validation_test.go index 61b0c99d7de60..58dc3c9ba5e1d 100644 --- a/pkg/apis/storage/validation/validation_test.go +++ b/pkg/apis/storage/validation/validation_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" api "k8s.io/kubernetes/pkg/apis/core" @@ -46,12 +47,6 @@ var ( }, }, } - longerIDValidateOption = CSINodeValidationOptions{ - AllowLongNodeID: true, - } - shorterIDValidationOption = CSINodeValidationOptions{ - AllowLongNodeID: false, - } ) func TestValidateStorageClass(t *testing.T) { @@ -575,7 +570,7 @@ func TestVolumeAttachmentValidationV1(t *testing.T) { }} for _, volumeAttachment := range successCases { - if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) != 0 { + if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) != 0 { t.Errorf("expected success: %+v", errs) } } @@ -603,7 +598,7 @@ func TestVolumeAttachmentValidationV1(t *testing.T) { }} for _, volumeAttachment := range errorCases { - if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) == 0 { + if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) == 0 { t.Errorf("Expected failure for test: %+v", volumeAttachment) } } @@ -936,9 +931,9 @@ func TestValidateAllowedTopologies(t *testing.T) { func TestCSINodeValidation(t *testing.T) { driverName := "driver-name" driverName2 := "1io.kubernetes-storage-2-csi-driver3" - longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" // 88 chars + longName := strings.Repeat("a", 88) nodeID := "nodeA" - longID := longName + longName + "abcdefghijklmnopqrstuvwxyz" // 202 chars + longID := strings.Repeat("a", 257) successCases := []storage.CSINode{{ // driver name: dot only ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, @@ -1085,27 +1080,11 @@ func TestCSINodeValidation(t *testing.T) { }} for _, csiNode := range successCases { - if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) != 0 { + if errs := ValidateCSINode(&csiNode); len(errs) != 0 { t.Errorf("expected success: %v", errs) } } - nodeIDCase := storage.CSINode{ - // node ID length > 128 but < 192 - ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{{ - Name: driverName, - NodeID: longID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }}, - }, - } - - if errs := ValidateCSINode(&nodeIDCase, longerIDValidateOption); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - errorCases := []storage.CSINode{{ // Empty driver name ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, @@ -1254,23 +1233,29 @@ func TestCSINodeValidation(t *testing.T) { TopologyKeys: []string{"Company.Com/zone1", "company.com/zone2"}, }}, }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "foo15"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: driverName, + NodeID: longID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, + }, }, - nodeIDCase, } for _, csiNode := range errorCases { - if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) == 0 { + if errs := ValidateCSINode(&csiNode); len(errs) == 0 { t.Errorf("Expected failure for test: %v", csiNode) } } } func TestCSINodeUpdateValidation(t *testing.T) { - //driverName := "driver-name" - //driverName2 := "1io.kubernetes-storage-2-csi-driver3" - //longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" nodeID := "nodeA" // Test with feature gate disabled + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MutableCSINodeAllocatableCount, false) old := storage.CSINode{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, @@ -1351,7 +1336,7 @@ func TestCSINodeUpdateValidation(t *testing.T) { }} for _, csiNode := range successCases { - if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) != 0 { + if errs := ValidateCSINodeUpdate(&csiNode, &old); len(errs) != 0 { t.Errorf("expected success: %+v", errs) } } @@ -1449,7 +1434,7 @@ func TestCSINodeUpdateValidation(t *testing.T) { }} for _, csiNode := range errorCases { - if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) == 0 { + if errs := ValidateCSINodeUpdate(&csiNode, &old); len(errs) == 0 { t.Errorf("Expected failure for test: %+v", csiNode) } } @@ -1506,24 +1491,23 @@ func TestCSINodeUpdateValidation(t *testing.T) { }} for _, csiNode := range errorCases { - if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) == 0 { + if errs := ValidateCSINodeUpdate(&csiNode, &old); len(errs) == 0 { t.Errorf("Expected failure for test: %+v", csiNode) } } for _, csiNode := range successCases { - if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) != 0 { + if errs := ValidateCSINodeUpdate(&csiNode, &old); len(errs) != 0 { t.Errorf("expected success with feature gate enabled: %+v", errs) } } } func TestCSIDriverValidation(t *testing.T) { - // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationSELinuxMountEnabledDisabled - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true) // assume this feature is on for this test, detailed enabled/disabled tests in TestMutableCSINodeAllocatableCountEnabledDisabled featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MutableCSINodeAllocatableCount, true) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIServiceAccountTokenSecrets, true) + // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationPreventPodSchedulingIfMissingEnabledDisabled + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeLimitScaling, true) driverName := "test-driver" longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" @@ -1539,6 +1523,7 @@ func TestCSIDriverValidation(t *testing.T) { notSELinuxMount := false serviceAccountTokenInSecrets := true notServiceAccountTokenInSecrets := false + preventPodSchedulingIfMissing := false supportedFSGroupPolicy := storage.FileFSGroupPolicy invalidFSGroupPolicy := storage.FSGroupPolicy("invalid-mode") validNodeAllocatableUpdatePeriodSeconds := int64(10) @@ -1547,78 +1532,86 @@ func TestCSIDriverValidation(t *testing.T) { successCases := []storage.CSIDriver{{ ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // driver name: dot only ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi.driver"}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: ¬StorageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: ¬StorageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // driver name: dash only ObjectMeta: metav1.ObjectMeta{Name: "io-kubernetes-storage-csi-driver"}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // driver name: numbers ObjectMeta: metav1.ObjectMeta{Name: "1csi2driver3"}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // driver name: dot and dash ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi-driver"}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, @@ -1630,7 +1623,8 @@ func TestCSIDriverValidation(t *testing.T) { VolumeLifecycleModes: []storage.VolumeLifecycleMode{ storage.VolumeLifecyclePersistent, }, - SELinuxMount: &seLinuxMount, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, @@ -1642,7 +1636,8 @@ func TestCSIDriverValidation(t *testing.T) { VolumeLifecycleModes: []storage.VolumeLifecycleMode{ storage.VolumeLifecycleEphemeral, }, - SELinuxMount: &seLinuxMount, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, @@ -1655,7 +1650,8 @@ func TestCSIDriverValidation(t *testing.T) { storage.VolumeLifecycleEphemeral, storage.VolumeLifecyclePersistent, }, - SELinuxMount: &seLinuxMount, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, @@ -1669,27 +1665,30 @@ func TestCSIDriverValidation(t *testing.T) { storage.VolumeLifecyclePersistent, storage.VolumeLifecycleEphemeral, }, - SELinuxMount: &seLinuxMount, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - FSGroupPolicy: &supportedFSGroupPolicy, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + FSGroupPolicy: &supportedFSGroupPolicy, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // SELinuxMount: false ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: ¬SELinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: ¬SELinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // With NodeAllocatableUpdatePeriodSeconds set to nil (valid) @@ -1700,6 +1699,7 @@ func TestCSIDriverValidation(t *testing.T) { RequiresRepublish: ¬RequiresRepublish, StorageCapacity: &storageCapacity, SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, NodeAllocatableUpdatePeriodSeconds: nil, }, }, { @@ -1711,42 +1711,46 @@ func TestCSIDriverValidation(t *testing.T) { RequiresRepublish: ¬RequiresRepublish, StorageCapacity: &storageCapacity, SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, NodeAllocatableUpdatePeriodSeconds: &validNodeAllocatableUpdatePeriodSeconds, }, }, { // With ServiceAccountTokenInSecrets set to true with TokenRequests ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - ServiceAccountTokenInSecrets: &serviceAccountTokenInSecrets, - TokenRequests: tokenRequests, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, + ServiceAccountTokenInSecrets: &serviceAccountTokenInSecrets, + TokenRequests: tokenRequests, }, }, { // With ServiceAccountTokenInSecrets set to false with TokenRequests ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - ServiceAccountTokenInSecrets: ¬ServiceAccountTokenInSecrets, - TokenRequests: tokenRequests, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, + ServiceAccountTokenInSecrets: ¬ServiceAccountTokenInSecrets, + TokenRequests: tokenRequests, }, }, { // With ServiceAccountTokenInSecrets set to nil (not set) ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - ServiceAccountTokenInSecrets: nil, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, + ServiceAccountTokenInSecrets: nil, }, }} @@ -1758,45 +1762,50 @@ func TestCSIDriverValidation(t *testing.T) { errorCases := []storage.CSIDriver{{ ObjectMeta: metav1.ObjectMeta{Name: invalidName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { ObjectMeta: metav1.ObjectMeta{Name: longName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // AttachRequired not set ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: nil, - PodInfoOnMount: &podInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: nil, + PodInfoOnMount: &podInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // PodInfoOnMount not set ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: nil, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: nil, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // StorageCapacity not set ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: &podInfoOnMount, - StorageCapacity: nil, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: &podInfoOnMount, + StorageCapacity: nil, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // invalid mode @@ -1808,17 +1817,19 @@ func TestCSIDriverValidation(t *testing.T) { VolumeLifecycleModes: []storage.VolumeLifecycleMode{ "no-such-mode", }, - SELinuxMount: &seLinuxMount, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // invalid fsGroupPolicy ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - FSGroupPolicy: &invalidFSGroupPolicy, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + FSGroupPolicy: &invalidFSGroupPolicy, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, }, { // no SELinuxMount @@ -1836,17 +1847,19 @@ func TestCSIDriverValidation(t *testing.T) { PodInfoOnMount: ¬PodInfoOnMount, StorageCapacity: &storageCapacity, SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, NodeAllocatableUpdatePeriodSeconds: &invalidNodeAllocatableUpdatePeriodSeconds, }, }, { // ServiceAccountTokenInSecrets set without TokenRequests (invalid) ObjectMeta: metav1.ObjectMeta{Name: driverName}, Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - ServiceAccountTokenInSecrets: &serviceAccountTokenInSecrets, + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, + ServiceAccountTokenInSecrets: &serviceAccountTokenInSecrets, }, }} @@ -1858,11 +1871,10 @@ func TestCSIDriverValidation(t *testing.T) { } func TestCSIDriverValidationUpdate(t *testing.T) { - // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationSELinuxMountEnabledDisabled - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true) // assume this feature is on for this test, detailed enabled/disabled tests in TestMutableCSINodeAllocatableCountEnabledDisabled featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MutableCSINodeAllocatableCount, true) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIServiceAccountTokenSecrets, true) + // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationPreventPodSchedulingIfMissingEnabledDisabled + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeLimitScaling, true) driverName := "test-driver" longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" @@ -1880,6 +1892,7 @@ func TestCSIDriverValidationUpdate(t *testing.T) { notSELinuxMount := false serviceAccountTokenInSecrets := true notServiceAccountTokenInSecrets := false + preventPodSchedulingIfMissing := false validNodeAllocatableUpdatePeriodSeconds := int64(10) invalidNodeAllocatableUpdatePeriodSeconds := int64(9) tokenRequests := []storage.TokenRequest{{Audience: "test-audience"}} @@ -1894,8 +1907,9 @@ func TestCSIDriverValidationUpdate(t *testing.T) { storage.VolumeLifecycleEphemeral, storage.VolumeLifecyclePersistent, }, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, + PreventPodSchedulingIfMissing: &preventPodSchedulingIfMissing, }, } @@ -2307,6 +2321,9 @@ func TestCSIDriverValidationSELinuxMountEnabledDisabled(t *testing.T) { }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if !test.featureEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.featureEnabled) csiDriver := &storage.CSIDriver{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, @@ -2385,6 +2402,9 @@ func TestCSIDriverValidationSELinuxMountEnabledDisabled(t *testing.T) { }} for _, test := range updateTests { t.Run(test.name, func(t *testing.T) { + if !test.featureEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.featureEnabled) oldCSIDriver := &storage.CSIDriver{ ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "1"}, @@ -2409,6 +2429,140 @@ func TestCSIDriverValidationSELinuxMountEnabledDisabled(t *testing.T) { } } +func TestCSIDriverValidationPreventPodSchedulingIfMissingEnabledDisabled(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true) + tests := []struct { + name string + featureEnabled bool + preventPodSchedulingIfMissingValue *bool + expectError bool + }{{ + name: "feature enabled, nil value", + featureEnabled: true, + preventPodSchedulingIfMissingValue: nil, + expectError: true, + }, { + name: "feature enabled, non-nil value", + featureEnabled: true, + preventPodSchedulingIfMissingValue: new(true), + expectError: false, + }, { + name: "feature disabled, nil value", + featureEnabled: false, + preventPodSchedulingIfMissingValue: nil, + expectError: false, + }, { + name: "feature disabled, non-nil value", + featureEnabled: false, + preventPodSchedulingIfMissingValue: new(true), + expectError: false, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeLimitScaling, test.featureEnabled) + csiDriver := &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.CSIDriverSpec{ + AttachRequired: new(true), + PodInfoOnMount: new(true), + RequiresRepublish: new(true), + StorageCapacity: new(true), + SELinuxMount: new(true), + PreventPodSchedulingIfMissing: test.preventPodSchedulingIfMissingValue, + }, + } + err := ValidateCSIDriver(csiDriver) + if test.expectError && err == nil { + t.Error("Expected validation error, got nil") + } + if !test.expectError && err != nil { + t.Errorf("Validation returned error: %s", err) + } + }) + } + + updateTests := []struct { + name string + featureEnabled bool + oldValue *bool + newValue *bool + expectError bool + }{{ + name: "feature enabled, nil->nil", + featureEnabled: true, + oldValue: nil, + newValue: nil, + expectError: true, // populated by defaulting and required when feature is enabled + }, { + name: "feature enabled, nil->set", + featureEnabled: true, + oldValue: nil, + newValue: new(true), + expectError: false, + }, { + name: "feature enabled, set->set", + featureEnabled: true, + oldValue: new(true), + newValue: new(true), + expectError: false, + }, { + name: "feature enabled, set->nil", + featureEnabled: true, + oldValue: new(true), + newValue: nil, + expectError: true, // populated by defaulting and required when feature is enabled + }, { + name: "feature disabled, nil->nil", + featureEnabled: false, + oldValue: nil, + newValue: nil, + expectError: false, + }, { + name: "feature disabled, nil->set", + featureEnabled: false, + oldValue: nil, + newValue: new(true), + expectError: false, + }, { + name: "feature disabled, set->set", + featureEnabled: false, + oldValue: new(true), + newValue: new(true), + expectError: false, + }, { + name: "feature disabled, set->nil", + featureEnabled: false, + oldValue: new(true), + newValue: nil, + expectError: false, + }} + for _, test := range updateTests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeLimitScaling, test.featureEnabled) + oldCSIDriver := &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: storage.CSIDriverSpec{ + AttachRequired: new(true), + PodInfoOnMount: new(true), + RequiresRepublish: new(true), + StorageCapacity: new(true), + SELinuxMount: new(true), + PreventPodSchedulingIfMissing: test.oldValue, + }, + } + newCSIDriver := oldCSIDriver.DeepCopy() + newCSIDriver.Spec.PreventPodSchedulingIfMissing = test.newValue + err := ValidateCSIDriverUpdate(newCSIDriver, oldCSIDriver) + if test.expectError && err == nil { + t.Error("Expected validation error, got nil") + } + if !test.expectError && err != nil { + t.Errorf("Validation returned error: %s", err) + } + }) + } +} + func TestValidateVolumeAttributesClass(t *testing.T) { successCases := []storage.VolumeAttributesClass{ { diff --git a/pkg/apis/storage/zz_generated.deepcopy.go b/pkg/apis/storage/zz_generated.deepcopy.go index f76e0695da70b..3433dbc05b0a0 100644 --- a/pkg/apis/storage/zz_generated.deepcopy.go +++ b/pkg/apis/storage/zz_generated.deepcopy.go @@ -142,6 +142,11 @@ func (in *CSIDriverSpec) DeepCopyInto(out *CSIDriverSpec) { *out = new(bool) **out = **in } + if in.PreventPodSchedulingIfMissing != nil { + in, out := &in.PreventPodSchedulingIfMissing, &out.PreventPodSchedulingIfMissing + *out = new(bool) + **out = **in + } return } diff --git a/pkg/apis/storagemigration/validation/validation.go b/pkg/apis/storagemigration/validation/validation.go index efcfb3618e755..fc8c3c78f0f97 100644 --- a/pkg/apis/storagemigration/validation/validation.go +++ b/pkg/apis/storagemigration/validation/validation.go @@ -70,12 +70,14 @@ func ValidateStorageVersionMigrationStatusUpdate(newSVMBundle, oldSVMBundle *sto fldPath := field.NewPath("status") // resource version should be a non-negative integer - cmp, err := resourceversion.CompareResourceVersion(newSVMBundle.Status.ResourceVersion, newSVMBundle.Status.ResourceVersion) - if err != nil || cmp != 0 { - if err == nil { - err = fmt.Errorf("unable to compare resource versions, %s is not equal to %s", newSVMBundle.Status.ResourceVersion, newSVMBundle.Status.ResourceVersion) + if len(newSVMBundle.Status.ResourceVersion) != 0 { + cmp, err := resourceversion.CompareResourceVersion(newSVMBundle.Status.ResourceVersion, newSVMBundle.Status.ResourceVersion) + if err != nil || cmp != 0 { + if err == nil { + err = fmt.Errorf("unable to compare resource versions, %s is not equal to %s", newSVMBundle.Status.ResourceVersion, newSVMBundle.Status.ResourceVersion) + } + allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newSVMBundle.Status.ResourceVersion, err.Error())) } - allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newSVMBundle.Status.ResourceVersion, err.Error())) } allErrs = append(allErrs, metav1validation.ValidateConditions(newSVMBundle.Status.Conditions, fldPath.Child("conditions"))...) @@ -90,14 +92,6 @@ func ValidateStorageVersionMigrationStatusUpdate(newSVMBundle, oldSVMBundle *sto allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Both success and failed conditions cannot be true at the same time")) } - // running must be false when success is true or failed is true - if isSuccessful(newSVMBundle) && isRunning(newSVMBundle) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Running condition cannot be true when success condition is true")) - } - if isFailed(newSVMBundle) && isRunning(newSVMBundle) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Running condition cannot be true when failed condition is true")) - } - // success cannot be set to false once it is true isOldSuccessful := isSuccessful(oldSVMBundle) if isOldSuccessful && !isSuccessful(newSVMBundle) { @@ -126,11 +120,3 @@ func isFailed(svm *storagemigration.StorageVersionMigration) bool { } return false } - -func isRunning(svm *storagemigration.StorageVersionMigration) bool { - runningCondition := metaconditions.FindStatusCondition(svm.Status.Conditions, string(storagemigration.MigrationRunning)) - if runningCondition != nil && runningCondition.Status == metav1.ConditionTrue { - return true - } - return false -} diff --git a/pkg/apis/storagemigration/validation/validation_test.go b/pkg/apis/storagemigration/validation/validation_test.go index e6dabecca30df..f21e4e804dc64 100644 --- a/pkg/apis/storagemigration/validation/validation_test.go +++ b/pkg/apis/storagemigration/validation/validation_test.go @@ -295,18 +295,6 @@ func TestValidateStorageVersionMigrationStatusUpdate(t *testing.T) { oldSVM: newTestSVM("123", runningCond), errorSubstring: "Both success and failed conditions cannot be true at the same time", }, - { - name: "invalid: both succeeded and running are true", - newSVM: newTestSVM("123", succeededCond, runningCond), - oldSVM: newTestSVM("123", runningCond), - errorSubstring: "Running condition cannot be true when success condition is true", - }, - { - name: "invalid: both failed and running are true", - newSVM: newTestSVM("123", failedCond, runningCond), - oldSVM: newTestSVM("123", runningCond), - errorSubstring: "Running condition cannot be true when failed condition is true", - }, { name: "invalid: succeeded changed from true to false", newSVM: newTestSVM("123", succeededFalseCond), diff --git a/pkg/client/tests/cri_streaming_spdy_compat_test.go b/pkg/client/tests/cri_streaming_spdy_compat_test.go new file mode 100644 index 0000000000000..1a3da5fb7d1b7 --- /dev/null +++ b/pkg/client/tests/cri_streaming_spdy_compat_test.go @@ -0,0 +1,286 @@ +/* +Copyright 2025 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 tests + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + restclient "k8s.io/client-go/rest" + remoteclient "k8s.io/client-go/tools/remotecommand" + clientspdy "k8s.io/client-go/transport/spdy" + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + streaming "k8s.io/cri-streaming/pkg/streaming" + remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand" + streamingspdy "k8s.io/streaming/pkg/httpstream/spdy" +) + +const ( + execContainerID = "cri-streaming-exec-container" + attachContainerID = "cri-streaming-attach-container" + + execInput = "exec-stdin" + execOutput = "exec-stdout" + execErr = "exec-stderr" + + attachInput = "attach-stdin" + attachOutput = "attach-stdout" + attachErr = "attach-stderr" +) + +type fakeStreamingRuntime struct{} + +func (*fakeStreamingRuntime) Exec(ctx context.Context, containerID string, cmd []string, in io.Reader, out, errStream io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { + if containerID != execContainerID { + return fmt.Errorf("unexpected exec container ID: %q", containerID) + } + + stdinData, err := io.ReadAll(in) + if err != nil { + return err + } + if string(stdinData) != execInput { + return fmt.Errorf("unexpected exec stdin: %q", string(stdinData)) + } + + if _, err := io.WriteString(out, execOutput); err != nil { + return err + } + if _, err := io.WriteString(errStream, execErr); err != nil { + return err + } + + return nil +} + +func (*fakeStreamingRuntime) Attach(ctx context.Context, containerID string, in io.Reader, out, errStream io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { + if containerID != attachContainerID { + return fmt.Errorf("unexpected attach container ID: %q", containerID) + } + + stdinData, err := io.ReadAll(in) + if err != nil { + return err + } + if string(stdinData) != attachInput { + return fmt.Errorf("unexpected attach stdin: %q", string(stdinData)) + } + + if _, err := io.WriteString(out, attachOutput); err != nil { + return err + } + if _, err := io.WriteString(errStream, attachErr); err != nil { + return err + } + + return nil +} + +func (*fakeStreamingRuntime) PortForward(ctx context.Context, podSandboxID string, port int32, stream io.ReadWriteCloser) error { + return errors.New("not implemented") +} + +func newCRIStreamingTestServer(t *testing.T) (streaming.Server, *httptest.Server) { + t.Helper() + + var server streaming.Server + httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + server.ServeHTTP(w, r) + })) + + baseURL, err := url.Parse(httpServer.URL) + if err != nil { + httpServer.Close() + t.Fatalf("failed to parse test server URL: %v", err) + } + + config := streaming.DefaultConfig + config.BaseURL = baseURL + + server, err = streaming.NewServer(config, &fakeStreamingRuntime{}) + if err != nil { + httpServer.Close() + t.Fatalf("failed to create cri streaming server: %v", err) + } + + return server, httpServer +} + +// Verifies that client-go's SPDY executor can successfully stream against +// the extracted cri-streaming server for both Exec and Attach endpoints. +func TestCRIStreamingSPDYExecAttachCompatibility(t *testing.T) { + server, httpServer := newCRIStreamingTestServer(t) + defer httpServer.Close() + + t.Run("exec", func(t *testing.T) { + response, err := server.GetExec(&runtimeapi.ExecRequest{ + ContainerId: execContainerID, + Cmd: []string{"echo", "exec"}, + Stdin: true, + Stdout: true, + Stderr: true, + }) + if err != nil { + t.Fatalf("failed to get exec URL: %v", err) + } + + requestURL, err := url.Parse(response.Url) + if err != nil { + t.Fatalf("failed to parse exec URL: %v", err) + } + + executor, err := remoteclient.NewSPDYExecutor(&restclient.Config{Host: requestURL.Host}, "POST", requestURL) + if err != nil { + t.Fatalf("failed to build exec executor: %v", err) + } + + var stdout bytes.Buffer + var stderr bytes.Buffer + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = executor.StreamWithContext(ctx, remoteclient.StreamOptions{ + Stdin: strings.NewReader(execInput), + Stdout: &stdout, + Stderr: &stderr, + }) + if err != nil { + t.Fatalf("unexpected exec stream error: %v", err) + } + + if got := stdout.String(); got != execOutput { + t.Fatalf("unexpected exec stdout: %q", got) + } + if got := stderr.String(); got != execErr { + t.Fatalf("unexpected exec stderr: %q", got) + } + }) + + t.Run("attach", func(t *testing.T) { + response, err := server.GetAttach(&runtimeapi.AttachRequest{ + ContainerId: attachContainerID, + Stdin: true, + Stdout: true, + Stderr: true, + }) + if err != nil { + t.Fatalf("failed to get attach URL: %v", err) + } + + requestURL, err := url.Parse(response.Url) + if err != nil { + t.Fatalf("failed to parse attach URL: %v", err) + } + + executor, err := remoteclient.NewSPDYExecutor(&restclient.Config{Host: requestURL.Host}, "POST", requestURL) + if err != nil { + t.Fatalf("failed to build attach executor: %v", err) + } + + var stdout bytes.Buffer + var stderr bytes.Buffer + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = executor.StreamWithContext(ctx, remoteclient.StreamOptions{ + Stdin: strings.NewReader(attachInput), + Stdout: &stdout, + Stderr: &stderr, + }) + if err != nil { + t.Fatalf("unexpected attach stream error: %v", err) + } + + if got := stdout.String(); got != attachOutput { + t.Fatalf("unexpected attach stdout: %q", got) + } + if got := stderr.String(); got != attachErr { + t.Fatalf("unexpected attach stderr: %q", got) + } + }) +} + +// Verifies client-go's compatibility upgrader adapter path by building a +// streaming SPDY roundtripper from k8s.io/streaming and adapting it into a +// client-go remotecommand executor. +func TestCRIStreamingSPDYExecCompatibilityWithStreamingUpgraderAdapter(t *testing.T) { + server, httpServer := newCRIStreamingTestServer(t) + defer httpServer.Close() + + response, err := server.GetExec(&runtimeapi.ExecRequest{ + ContainerId: execContainerID, + Cmd: []string{"echo", "exec"}, + Stdin: true, + Stdout: true, + Stderr: true, + }) + if err != nil { + t.Fatalf("failed to get exec URL: %v", err) + } + + requestURL, err := url.Parse(response.Url) + if err != nil { + t.Fatalf("failed to parse exec URL: %v", err) + } + + roundTripper, err := streamingspdy.NewRoundTripperWithConfig(streamingspdy.RoundTripperConfig{ + PingPeriod: 5 * time.Second, + }) + if err != nil { + t.Fatalf("failed to build streaming roundtripper: %v", err) + } + + executor, err := remoteclient.NewSPDYExecutorForTransports( + roundTripper, + clientspdy.NewUpgraderForStreaming(roundTripper), + "POST", + requestURL, + ) + if err != nil { + t.Fatalf("failed to build adapter-based executor: %v", err) + } + + var stdout bytes.Buffer + var stderr bytes.Buffer + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = executor.StreamWithContext(ctx, remoteclient.StreamOptions{ + Stdin: strings.NewReader(execInput), + Stdout: &stdout, + Stderr: &stderr, + }) + if err != nil { + t.Fatalf("unexpected exec stream error: %v", err) + } + + if got := stdout.String(); got != execOutput { + t.Fatalf("unexpected exec stdout: %q", got) + } + if got := stderr.String(); got != execErr { + t.Fatalf("unexpected exec stderr: %q", got) + } +} diff --git a/pkg/client/tests/portfoward_test.go b/pkg/client/tests/portfoward_test.go index dd0584738b9e4..17d9622490b8e 100644 --- a/pkg/client/tests/portfoward_test.go +++ b/pkg/client/tests/portfoward_test.go @@ -19,6 +19,7 @@ package tests import ( "bytes" "context" + "encoding/binary" "fmt" "io" "net" @@ -31,11 +32,12 @@ import ( "testing" "time" - "k8s.io/apimachinery/pkg/types" + "github.com/gorilla/websocket" restclient "k8s.io/client-go/rest" . "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" - "k8s.io/kubelet/pkg/cri/streaming/portforward" + "k8s.io/cri-streaming/pkg/streaming/portforward" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // fakePortForwarder simulates port forwarding for testing. It implements @@ -52,7 +54,7 @@ type fakePortForwarder struct { var _ portforward.PortForwarder = &fakePortForwarder{} -func (pf *fakePortForwarder) PortForward(_ context.Context, name string, uid types.UID, port int32, stream io.ReadWriteCloser) error { +func (pf *fakePortForwarder) PortForward(_ context.Context, name string, uid string, port int32, stream io.ReadWriteCloser) error { defer stream.Close() // read from the client @@ -249,3 +251,108 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) { t.Fatal("expected non-nil error for pf2.ForwardPorts") } } + +func TestForwardPortsWebSocketV4Framing(t *testing.T) { + const testPort int32 = 5001 + const clientPayload = "abcd" + const serverPayload = "1234" + + done := make(chan struct{}) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + defer close(done) + + opts, err := portforward.NewV4Options(req) + if err != nil { + t.Errorf("failed to build v4 options: %v", err) + return + } + + pf := &fakePortForwarder{ + expected: map[int32]string{testPort: clientPayload}, + received: make(map[int32]string), + send: map[int32]string{testPort: serverPayload}, + } + portforward.ServePortForward(w, req, pf, "pod", "uid", opts, 0, 10*time.Second, portforward.SupportedProtocols) + + got, ok := pf.received[testPort] + if !ok { + t.Errorf("server did not receive data for port %d", testPort) + return + } + if got != clientPayload { + t.Errorf("server expected %q, got %q for port %d", clientPayload, got, testPort) + } + })) + defer server.Close() + + wsURL := strings.Replace(server.URL, "http://", "ws://", 1) + fmt.Sprintf("?port=%d", testPort) + dialer := &websocket.Dialer{ + Subprotocols: []string{"v4." + wsstream.ChannelWebSocketProtocol}, + } + conn, _, err := dialer.Dial(wsURL, nil) + if err != nil { + t.Fatalf("websocket dial failed: %v", err) + } + defer conn.Close() + + expectedPortBytes := make([]byte, 2) + binary.LittleEndian.PutUint16(expectedPortBytes, uint16(testPort)) + + seenDataPreamble := false + seenErrorPreamble := false + for !(seenDataPreamble && seenErrorPreamble) { + _, frame, err := conn.ReadMessage() + if err != nil { + t.Fatalf("failed reading websocket preamble frame: %v", err) + } + if len(frame) == 0 { + continue + } + channel := frame[0] + payload := frame[1:] + switch channel { + case 0: + if !bytes.Equal(expectedPortBytes, payload) { + t.Fatalf("unexpected data-channel preamble payload: %q", payload) + } + seenDataPreamble = true + case 1: + if !bytes.Equal(expectedPortBytes, payload) { + t.Fatalf("unexpected error-channel preamble payload: %q", payload) + } + seenErrorPreamble = true + } + } + + frame := append([]byte{0}, []byte(clientPayload)...) + if err := conn.WriteMessage(websocket.BinaryMessage, frame); err != nil { + t.Fatalf("failed writing data frame: %v", err) + } + + gotServerPayload := false + for !gotServerPayload { + _, frame, err := conn.ReadMessage() + if err != nil { + t.Fatalf("failed reading websocket response frame: %v", err) + } + if len(frame) == 0 { + continue + } + if frame[0] != 0 { + continue + } + if bytes.Equal(frame[1:], []byte(serverPayload)) { + gotServerPayload = true + } + } + + if err := conn.Close(); err != nil { + t.Fatalf("failed closing websocket connection: %v", err) + } + + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for websocket portforward handler to complete") + } +} diff --git a/pkg/client/tests/remotecommand_test.go b/pkg/client/tests/remotecommand_test.go index 78b1cf387f5d0..4969a560a528b 100644 --- a/pkg/client/tests/remotecommand_test.go +++ b/pkg/client/tests/remotecommand_test.go @@ -31,15 +31,14 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/httpstream" remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand" restclient "k8s.io/client-go/rest" remoteclient "k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/transport/spdy" - "k8s.io/kubelet/pkg/cri/streaming/remotecommand" + "k8s.io/cri-streaming/pkg/streaming/remotecommand" "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/streaming/pkg/httpstream" ) type fakeExecutor struct { @@ -56,22 +55,22 @@ type fakeExecutor struct { exec bool } -func (ex *fakeExecutor) ExecInContainer(_ context.Context, name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize, timeout time.Duration) error { +func (ex *fakeExecutor) ExecInContainer(_ context.Context, name string, uid string, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error { return ex.run(name, uid, container, cmd, in, out, err, tty) } -func (ex *fakeExecutor) AttachContainer(_ context.Context, name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize) error { +func (ex *fakeExecutor) AttachContainer(_ context.Context, name string, uid string, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { return ex.run(name, uid, container, nil, in, out, err, tty) } -func (ex *fakeExecutor) run(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { +func (ex *fakeExecutor) run(name string, uid string, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { ex.command = cmd ex.tty = tty if e, a := "pod", name; e != a { ex.t.Errorf("%s: pod: expected %q, got %q", ex.testName, e, a) } - if e, a := "uid", uid; e != string(a) { + if e, a := "uid", uid; e != a { ex.t.Errorf("%s: uid: expected %q, got %q", ex.testName, e, a) } if ex.exec { @@ -339,7 +338,7 @@ func TestDial(t *testing.T) { Body: io.NopCloser(&bytes.Buffer{}), }, } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"}) + dialer := spdy.NewDialerForStreaming(spdy.NewUpgraderForStreaming(upgrader), &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"}) conn, protocol, err := dialer.Dial("protocol1") if err != nil { t.Fatal(err) diff --git a/pkg/controller/apis/config/types.go b/pkg/controller/apis/config/types.go index 3be7f98a5c4e6..fe47ad0f29f0c 100644 --- a/pkg/controller/apis/config/types.go +++ b/pkg/controller/apis/config/types.go @@ -38,6 +38,7 @@ import ( podgcconfig "k8s.io/kubernetes/pkg/controller/podgc/config" replicasetconfig "k8s.io/kubernetes/pkg/controller/replicaset/config" replicationconfig "k8s.io/kubernetes/pkg/controller/replication/config" + resourceclaimconfig "k8s.io/kubernetes/pkg/controller/resourceclaim/config" resourcequotaconfig "k8s.io/kubernetes/pkg/controller/resourcequota/config" serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config" statefulsetconfig "k8s.io/kubernetes/pkg/controller/statefulset/config" @@ -139,6 +140,8 @@ type KubeControllerManagerConfiguration struct { // ValidatingAdmissionPolicyStatusControllerConfiguration holds configuration for // ValidatingAdmissionPolicyStatusController related features. ValidatingAdmissionPolicyStatusController validatingadmissionpolicystatusconfig.ValidatingAdmissionPolicyStatusControllerConfiguration + // ResourceClaimControllerConfiguration contains elements configuring the resource claim controller. + ResourceClaimController resourceclaimconfig.ResourceClaimControllerConfiguration } // DeprecatedControllerConfiguration contains elements be deprecated. diff --git a/pkg/controller/apis/config/v1alpha1/defaults.go b/pkg/controller/apis/config/v1alpha1/defaults.go index d0d4fd160ae45..70a6c3d1dfd6c 100644 --- a/pkg/controller/apis/config/v1alpha1/defaults.go +++ b/pkg/controller/apis/config/v1alpha1/defaults.go @@ -38,6 +38,7 @@ import ( podgcconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/podgc/config/v1alpha1" replicasetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/replicaset/config/v1alpha1" replicationconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/replication/config/v1alpha1" + resourceclaimconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/resourceclaim/config/v1alpha1" resourcequotaconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/resourcequota/config/v1alpha1" serviceaccountconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/serviceaccount/config/v1alpha1" statefulsetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/statefulset/config/v1alpha1" @@ -74,6 +75,8 @@ func SetDefaults_KubeControllerManagerConfiguration(obj *kubectrlmgrconfigv1alph deploymentconfigv1alpha1.RecommendedDefaultDeploymentControllerConfiguration(&obj.DeploymentController) // Use the default RecommendedDefaultDeviceTaintEvictionControllerConfiguration options devicetaintevictionconfigv1alpha1.RecommendedDefaultDeviceTaintEvictionControllerConfiguration(&obj.DeviceTaintEvictionController) + // Use the default RecommendedDefaultResourceClaimControllerConfiguration options + resourceclaimconfigv1alpha1.RecommendedDefaultResourceClaimControllerConfiguration(&obj.ResourceClaimController) // Use the default RecommendedDefaultStatefulSetControllerConfiguration options statefulsetconfigv1alpha1.RecommendedDefaultStatefulSetControllerConfiguration(&obj.StatefulSetController) // Use the default RecommendedDefaultEndpointControllerConfiguration options diff --git a/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go index b9b276389aabd..0f13aea1de832 100644 --- a/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go +++ b/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go @@ -47,6 +47,7 @@ import ( podgcconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/podgc/config/v1alpha1" replicasetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/replicaset/config/v1alpha1" replicationconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/replication/config/v1alpha1" + resourceclaimconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/resourceclaim/config/v1alpha1" resourcequotaconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/resourcequota/config/v1alpha1" serviceaccountconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/serviceaccount/config/v1alpha1" statefulsetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/statefulset/config/v1alpha1" @@ -228,6 +229,9 @@ func autoConvert_v1alpha1_KubeControllerManagerConfiguration_To_config_KubeContr if err := devicetaintevictionconfigv1alpha1.Convert_v1alpha1_DeviceTaintEvictionControllerConfiguration_To_config_DeviceTaintEvictionControllerConfiguration(&in.DeviceTaintEvictionController, &out.DeviceTaintEvictionController, s); err != nil { return err } + if err := resourceclaimconfigv1alpha1.Convert_v1alpha1_ResourceClaimControllerConfiguration_To_config_ResourceClaimControllerConfiguration(&in.ResourceClaimController, &out.ResourceClaimController, s); err != nil { + return err + } return nil } @@ -327,6 +331,9 @@ func autoConvert_config_KubeControllerManagerConfiguration_To_v1alpha1_KubeContr if err := validatingadmissionpolicystatusconfigv1alpha1.Convert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(&in.ValidatingAdmissionPolicyStatusController, &out.ValidatingAdmissionPolicyStatusController, s); err != nil { return err } + if err := resourceclaimconfigv1alpha1.Convert_config_ResourceClaimControllerConfiguration_To_v1alpha1_ResourceClaimControllerConfiguration(&in.ResourceClaimController, &out.ResourceClaimController, s); err != nil { + return err + } return nil } diff --git a/pkg/controller/apis/config/zz_generated.deepcopy.go b/pkg/controller/apis/config/zz_generated.deepcopy.go index 82d905bbe210f..ff15a2b89757b 100644 --- a/pkg/controller/apis/config/zz_generated.deepcopy.go +++ b/pkg/controller/apis/config/zz_generated.deepcopy.go @@ -75,6 +75,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa out.StatefulSetController = in.StatefulSetController out.TTLAfterFinishedController = in.TTLAfterFinishedController out.ValidatingAdmissionPolicyStatusController = in.ValidatingAdmissionPolicyStatusController + out.ResourceClaimController = in.ResourceClaimController return } diff --git a/pkg/controller/bootstrap/common_test.go b/pkg/controller/bootstrap/common_test.go index 252ce1cea27c7..e50fc6efd5afc 100644 --- a/pkg/controller/bootstrap/common_test.go +++ b/pkg/controller/bootstrap/common_test.go @@ -22,10 +22,10 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/dump" core "k8s.io/client-go/testing" bootstrapapi "k8s.io/cluster-bootstrap/token/api" "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/utils/dump" ) func newTokenSecret(tokenID, tokenSecret string) *v1.Secret { diff --git a/pkg/controller/certificates/clustertrustbundlepublisher/publisher.go b/pkg/controller/certificates/clustertrustbundlepublisher/publisher.go index f39ac0fa41217..d932951339dc4 100644 --- a/pkg/controller/certificates/clustertrustbundlepublisher/publisher.go +++ b/pkg/controller/certificates/clustertrustbundlepublisher/publisher.go @@ -285,7 +285,7 @@ func (p *ClusterTrustBundlePublisher[T]) Run(ctx context.Context) { }() wg.Go(func() { - p.ctbInformer.Run(ctx.Done()) + p.ctbInformer.RunWithContext(ctx) }) if !cache.WaitForNamedCacheSyncWithContext(ctx, p.ctbListerSynced) { diff --git a/pkg/controller/certificates/clustertrustbundlepublisher/publisher_test.go b/pkg/controller/certificates/clustertrustbundlepublisher/publisher_test.go index ca34b5cea5cd2..b9bac8efaad5d 100644 --- a/pkg/controller/certificates/clustertrustbundlepublisher/publisher_test.go +++ b/pkg/controller/certificates/clustertrustbundlepublisher/publisher_test.go @@ -274,7 +274,7 @@ func TestCTBPublisherSync(t *testing.T) { t.Fatalf("failed to assert the controller for the beta API") } - go controller.ctbInformer.Run(testCtx.Done()) + go controller.ctbInformer.RunWithContext(testCtx) if !cache.WaitForCacheSync(testCtx.Done(), controller.ctbInformer.HasSynced) { t.Fatal("timed out waiting for informer to sync") } @@ -313,6 +313,10 @@ func fakeKubeClientSetWithCTBList(t *testing.T, signerName string, ctbs ...runti } } + // Ensure that Watch doesn't return any objects either by bumping up the resource version + // beyond what the fake client uses. + retList.ResourceVersion = "1000" + return true, retList, nil }) diff --git a/pkg/controller/certificates/signer/signer_test.go b/pkg/controller/certificates/signer/signer_test.go index 56ff04c9bbfec..e2bbcf00d3928 100644 --- a/pkg/controller/certificates/signer/signer_test.go +++ b/pkg/controller/certificates/signer/signer_test.go @@ -499,7 +499,6 @@ func Test_signer_duration(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go index 2b27994f8e7b7..47e428a2dfd3c 100644 --- a/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go +++ b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go @@ -129,15 +129,7 @@ func (c *ClusterRoleAggregationController) syncClusterRole(ctx context.Context, return nil } - err = c.applyClusterRoles(ctx, sharedClusterRole.Name, newPolicyRules) - if errors.IsUnsupportedMediaType(err) { // TODO: Remove this fallback at least one release after ServerSideApply GA - // When Server Side Apply is not enabled, fallback to Update. This is required when running - // 1.21 since api-server can be 1.20 during the upgrade/downgrade. - // Since Server Side Apply is enabled by default in Beta, this fallback only kicks in - // if the feature has been disabled using its feature flag. - err = c.updateClusterRoles(ctx, sharedClusterRole, newPolicyRules) - } - return err + return c.applyClusterRoles(ctx, sharedClusterRole.Name, newPolicyRules) } func (c *ClusterRoleAggregationController) applyClusterRoles(ctx context.Context, name string, newPolicyRules []rbacv1.PolicyRule) error { @@ -149,16 +141,6 @@ func (c *ClusterRoleAggregationController) applyClusterRoles(ctx context.Context return err } -func (c *ClusterRoleAggregationController) updateClusterRoles(ctx context.Context, sharedClusterRole *rbacv1.ClusterRole, newPolicyRules []rbacv1.PolicyRule) error { - clusterRole := sharedClusterRole.DeepCopy() - clusterRole.Rules = nil - for _, rule := range newPolicyRules { - clusterRole.Rules = append(clusterRole.Rules, *rule.DeepCopy()) - } - _, err := c.clusterRoleClient.ClusterRoles().Update(ctx, clusterRole, metav1.UpdateOptions{}) - return err -} - func toApplyPolicyRules(rules []rbacv1.PolicyRule) []*rbacv1ac.PolicyRuleApplyConfiguration { var result []*rbacv1ac.PolicyRuleApplyConfiguration for _, rule := range rules { diff --git a/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go index 5fb2f8901866f..c93c58a26bd05 100644 --- a/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go +++ b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go @@ -23,7 +23,6 @@ import ( "github.com/google/go-cmp/cmp" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/yaml" @@ -159,78 +158,58 @@ func TestSyncClusterRole(t *testing.T) { expectedClusterRoleApply: nil, }} - for _, serverSideApplyEnabled := range []bool{true, false} { - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - indexer := cache.NewIndexer(controller.KeyFunc, cache.Indexers{}) - objs := []runtime.Object{} - for _, obj := range test.startingClusterRoles { - objs = append(objs, obj) - indexer.Add(obj) - } - fakeClient := fakeclient.NewSimpleClientset(objs...) - - // The default reactor doesn't support apply, so we need our own (trivial) reactor - fakeClient.PrependReactor("patch", "clusterroles", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { - if serverSideApplyEnabled == false { - // UnsupportedMediaType - return true, nil, errors.NewGenericServerResponse(415, "get", action.GetResource().GroupResource(), "test", "Apply not supported", 0, true) - } - return true, nil, nil // clusterroleaggregator drops returned objects so no point in constructing them - }) - c := ClusterRoleAggregationController{ - clusterRoleClient: fakeClient.RbacV1(), - clusterRoleLister: rbaclisters.NewClusterRoleLister(indexer), - } - err := c.syncClusterRole(context.TODO(), test.clusterRoleToSync) - if err != nil { - t.Fatal(err) - } - - if test.expectedClusterRoleApply == nil { - if len(fakeClient.Actions()) != 0 { - t.Fatalf("unexpected actions %#v", fakeClient.Actions()) - } - return + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + indexer := cache.NewIndexer(controller.KeyFunc, cache.Indexers{}) + objs := []runtime.Object{} + for _, obj := range test.startingClusterRoles { + objs = append(objs, obj) + if err := indexer.Add(obj); err != nil { + t.Fatalf("failed to add object to indexer: %v", err) } + } + fakeClient := fakeclient.NewClientset(objs...) - expectedActions := 1 - if !serverSideApplyEnabled { - expectedActions = 2 - } - if len(fakeClient.Actions()) != expectedActions { + // The default reactor doesn't support apply, so we need our own (trivial) reactor + fakeClient.PrependReactor("patch", "clusterroles", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, nil // clusterroleaggregator drops returned objects so no point in constructing them + }) + c := ClusterRoleAggregationController{ + clusterRoleClient: fakeClient.RbacV1(), + clusterRoleLister: rbaclisters.NewClusterRoleLister(indexer), + } + err := c.syncClusterRole(context.TODO(), test.clusterRoleToSync) + if err != nil { + t.Fatal(err) + } + + if test.expectedClusterRoleApply == nil { + if len(fakeClient.Actions()) != 0 { t.Fatalf("unexpected actions %#v", fakeClient.Actions()) } - - action := fakeClient.Actions()[0] - if !action.Matches("patch", "clusterroles") { - t.Fatalf("unexpected action %#v", action) - } - applyAction, ok := action.(clienttesting.PatchAction) - if !ok { - t.Fatalf("unexpected action %#v", action) - } - ac := &rbacv1ac.ClusterRoleApplyConfiguration{} - if err := yaml.Unmarshal(applyAction.GetPatch(), ac); err != nil { - t.Fatalf("error unmarshalling apply request: %v", err) - } - if !equality.Semantic.DeepEqual(ac, test.expectedClusterRoleApply) { - t.Fatalf("%v", cmp.Diff(test.expectedClusterRoleApply, ac)) - } - if expectedActions == 2 { - action := fakeClient.Actions()[1] - if !action.Matches("update", "clusterroles") { - t.Fatalf("unexpected action %#v", action) - } - updateAction, ok := action.(clienttesting.UpdateAction) - if !ok { - t.Fatalf("unexpected action %#v", action) - } - if !equality.Semantic.DeepEqual(updateAction.GetObject().(*rbacv1.ClusterRole), test.expectedClusterRole) { - t.Fatalf("%v", cmp.Diff(test.expectedClusterRole, updateAction.GetObject().(*rbacv1.ClusterRole))) - } - } - }) - } + return + } + + expectedActions := 1 + if len(fakeClient.Actions()) != expectedActions { + t.Fatalf("unexpected actions %#v", fakeClient.Actions()) + } + + action := fakeClient.Actions()[0] + if !action.Matches("patch", "clusterroles") { + t.Fatalf("unexpected action %#v", action) + } + applyAction, ok := action.(clienttesting.PatchAction) + if !ok { + t.Fatalf("unexpected action %#v", action) + } + ac := &rbacv1ac.ClusterRoleApplyConfiguration{} + if err := yaml.Unmarshal(applyAction.GetPatch(), ac); err != nil { + t.Fatalf("error unmarshalling apply request: %v", err) + } + if !equality.Semantic.DeepEqual(ac, test.expectedClusterRoleApply) { + t.Fatalf("%v", cmp.Diff(test.expectedClusterRoleApply, ac)) + } + }) } } diff --git a/pkg/controller/controller_utils.go b/pkg/controller/controller_utils.go index c6c3b31f0d9ef..dd5268ae5a99d 100644 --- a/pkg/controller/controller_utils.go +++ b/pkg/controller/controller_utils.go @@ -482,6 +482,7 @@ type PodControlInterface interface { type RealPodControl struct { KubeClient clientset.Interface Recorder record.EventRecorder + OnWrite func(*v1.Pod, *metav1.OwnerReference) } var _ PodControlInterface = &RealPodControl{} @@ -551,11 +552,15 @@ func (r RealPodControl) CreatePodsWithGenerateName(ctx context.Context, namespac if len(generateName) > 0 { pod.ObjectMeta.GenerateName = generateName } - return r.createPods(ctx, namespace, pod, controllerObject) + return r.createPods(ctx, namespace, pod, controllerObject, controllerRef) } func (r RealPodControl) PatchPod(ctx context.Context, namespace, name string, data []byte) error { - _, err := r.KubeClient.CoreV1().Pods(namespace).Patch(ctx, name, types.StrategicMergePatchType, data, metav1.PatchOptions{}) + pod, err := r.KubeClient.CoreV1().Pods(namespace).Patch(ctx, name, types.StrategicMergePatchType, data, metav1.PatchOptions{}) + if err == nil && r.OnWrite != nil { + ownerRef := metav1.GetControllerOfNoCopy(pod) + r.OnWrite(pod, ownerRef) + } return err } @@ -584,7 +589,7 @@ func GetPodFromTemplate(template *v1.PodTemplateSpec, parentObject runtime.Objec return pod, nil } -func (r RealPodControl) createPods(ctx context.Context, namespace string, pod *v1.Pod, object runtime.Object) error { +func (r RealPodControl) createPods(ctx context.Context, namespace string, pod *v1.Pod, object runtime.Object, controllerRef *metav1.OwnerReference) error { if len(labels.Set(pod.Labels)) == 0 { return fmt.Errorf("unable to create pods, no labels") } @@ -597,6 +602,9 @@ func (r RealPodControl) createPods(ctx context.Context, namespace string, pod *v return err } logger := klog.FromContext(ctx) + if r.OnWrite != nil { + r.OnWrite(newPod, controllerRef) + } accessor, err := meta.Accessor(object) if err != nil { logger.Error(err, "parentObject does not have ObjectMeta") diff --git a/pkg/controller/controller_utils_test.go b/pkg/controller/controller_utils_test.go index 63a7e2768ae33..fb8b858388cd6 100644 --- a/pkg/controller/controller_utils_test.go +++ b/pkg/controller/controller_utils_test.go @@ -378,13 +378,18 @@ func TestCreatePodsWithGenerateName(t *testing.T) { defer testServer.Close() clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}, ContentType: runtime.ContentTypeJSON}}) + callbackCalled := false podControl := RealPodControl{ KubeClient: clientset, Recorder: &record.FakeRecorder{}, + OnWrite: func(*v1.Pod, *metav1.OwnerReference) { + callbackCalled = true + }, } err := test.podCreationFunc(podControl) require.NoError(t, err, "unexpected error: %v", err) + assert.True(t, callbackCalled, "OnWrite callback was not called") fakeHandler.ValidateRequest(t, "/api/v1/namespaces/default/pods", "POST", nil) var actualPod = &v1.Pod{} @@ -396,6 +401,32 @@ func TestCreatePodsWithGenerateName(t *testing.T) { } } +func TestPatchPodCallbacks(t *testing.T) { + fakeClient := fake.NewSimpleClientset( + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + }, + }, + ) + wroteCallbackCalled := false + podControl := RealPodControl{ + KubeClient: fakeClient, + Recorder: &record.FakeRecorder{}, + OnWrite: func(pod *v1.Pod, ownerRef *metav1.OwnerReference) { + wroteCallbackCalled = true + }, + } + + err := podControl.PatchPod(context.TODO(), "", "non-existing-pod", []byte("{}")) + assert.False(t, wroteCallbackCalled, "OnWrite callback was called when not expected") + assert.True(t, apierrors.IsNotFound(err), "Expected not found error") + + err = podControl.PatchPod(context.TODO(), "", "test-pod", []byte("{}")) + assert.True(t, wroteCallbackCalled, "OnWrite callback was not called") + assert.NoError(t, err, "Expected no error") +} + func TestDeletePodsAllowsMissing(t *testing.T) { fakeClient := fake.NewSimpleClientset() podControl := RealPodControl{ diff --git a/pkg/controller/cronjob/cronjob_controllerv2_test.go b/pkg/controller/cronjob/cronjob_controllerv2_test.go index fd3066f86f21e..c61b17b47f568 100644 --- a/pkg/controller/cronjob/cronjob_controllerv2_test.go +++ b/pkg/controller/cronjob/cronjob_controllerv2_test.go @@ -1247,8 +1247,6 @@ func TestControllerV2SyncCronJob(t *testing.T) { }, } for name, tc := range testCases { - name := name - tc := tc t.Run(name, func(t *testing.T) { cj := cronJob() diff --git a/pkg/controller/daemon/daemon_controller.go b/pkg/controller/daemon/daemon_controller.go index 5bcf013020741..eed0b5b16ae95 100644 --- a/pkg/controller/daemon/daemon_controller.go +++ b/pkg/controller/daemon/daemon_controller.go @@ -18,6 +18,7 @@ package daemon import ( "context" + "errors" "fmt" "reflect" "sort" @@ -30,6 +31,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -51,7 +54,9 @@ import ( "k8s.io/klog/v2" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/daemon/metrics" "k8s.io/kubernetes/pkg/controller/daemon/util" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" "k8s.io/kubernetes/pkg/features" ) @@ -79,6 +84,17 @@ const ( SucceededDaemonPodReason = "SucceededDaemonPod" ) +var ( + daemonsetGroupResource = schema.GroupResource{ + Group: "apps", + Resource: "daemonsets", + } + podGroupResource = schema.GroupResource{ + Group: "", + Resource: "pods", + } +) + // controllerKind contains the schema.GroupVersionKind for this controller type. var controllerKind = apps.SchemeGroupVersion.WithKind("DaemonSet") @@ -140,6 +156,8 @@ type DaemonSetsController struct { nodeUpdateQueue workqueue.TypedRateLimitingInterface[string] failedPodsBackoff *flowcontrol.Backoff + + consistencyStore consistencyutil.ConsistencyStore } // NewDaemonSetsController creates a new DaemonSetsController @@ -154,6 +172,31 @@ func NewDaemonSetsController( ) (*DaemonSetsController, error) { eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) logger := klog.FromContext(ctx) + var consistencyStore consistencyutil.ConsistencyStore + var podWriteCallback func(pod *v1.Pod, ds *metav1.OwnerReference) + if utilfeature.DefaultFeatureGate.Enabled(features.StaleControllerConsistencyDaemonSet) { + consistencyStore = consistencyutil.NewConsistencyStore(map[schema.GroupResource]consistencyutil.LastSyncRVGetter{ + podGroupResource: podInformer.Informer().GetStore(), + daemonsetGroupResource: daemonSetInformer.Informer().GetStore(), + }) + podWriteCallback = func(pod *v1.Pod, ds *metav1.OwnerReference) { + if ds == nil { + return + } + consistencyStore.WroteAt( + types.NamespacedName{ + Namespace: pod.Namespace, + Name: ds.Name, + }, + ds.UID, + podGroupResource, + pod.ResourceVersion, + ) + } + } else { + consistencyStore = consistencyutil.NewNoopConsistencyStore() + } + dsc := &DaemonSetsController{ kubeClient: kubeClient, eventBroadcaster: eventBroadcaster, @@ -161,7 +204,9 @@ func NewDaemonSetsController( podControl: controller.RealPodControl{ KubeClient: kubeClient, Recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "daemonset-controller"}), + OnWrite: podWriteCallback, }, + crControl: controller.RealControllerRevisionControl{ KubeClient: kubeClient, }, @@ -179,6 +224,7 @@ func NewDaemonSetsController( Name: "daemonset-node-updates", }, ), + consistencyStore: consistencyStore, } daemonSetInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -211,6 +257,7 @@ func NewDaemonSetsController( // Watch for creation/deletion of pods. The reason we watch is that we don't want a daemon set to create/delete // more pods until all the effects (expectations) of a daemon set's create/delete have been observed. + podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { dsc.addPod(logger, obj) @@ -245,6 +292,7 @@ func NewDaemonSetsController( dsc.failedPodsBackoff = failedPodsBackoff + metrics.Register() return dsc, nil } @@ -296,6 +344,13 @@ func (dsc *DaemonSetsController) deleteDaemonset(logger klog.Logger, obj interfa utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", ds, err)) return } + dsc.consistencyStore.Clear( + types.NamespacedName{ + Name: ds.Name, + Namespace: ds.Namespace, + }, + ds.UID, + ) // Delete expectations for the DaemonSet so if we create a new one with the same name it starts clean dsc.expectations.DeleteExpectations(logger, key) @@ -842,13 +897,13 @@ func (dsc *DaemonSetsController) podsShouldBeOnNode( msg := fmt.Sprintf("Found failed daemon pod %s/%s on node %s, will try to kill it", pod.Namespace, pod.Name, node.Name) logger.V(2).Info("Found failed daemon pod on node, will try to kill it", "pod", klog.KObj(pod), "node", klog.KObj(node)) // Emit an event so that it's discoverable to users. - dsc.eventRecorder.Eventf(ds, v1.EventTypeWarning, FailedDaemonPodReason, msg) + dsc.eventRecorder.Eventf(ds, v1.EventTypeWarning, FailedDaemonPodReason, "%s", msg) podsToDelete = append(podsToDelete, pod.Name) } else if pod.Status.Phase == v1.PodSucceeded { msg := fmt.Sprintf("Found succeeded daemon pod %s/%s on node %s, will try to delete it", pod.Namespace, pod.Name, node.Name) logger.V(2).Info("Found succeeded daemon pod on node, will try to delete it", "pod", klog.KObj(pod), "node", klog.KObj(node)) // Emit an event so that it's discoverable to users. - dsc.eventRecorder.Eventf(ds, v1.EventTypeNormal, SucceededDaemonPodReason, msg) + dsc.eventRecorder.Eventf(ds, v1.EventTypeNormal, SucceededDaemonPodReason, "%s", msg) podsToDelete = append(podsToDelete, pod.Name) } else { daemonPodsRunning = append(daemonPodsRunning, pod) @@ -1106,7 +1161,7 @@ func storeDaemonSetStatus( updatedNumberScheduled, numberAvailable, numberUnavailable int, - updateObservedGen bool) error { + updateObservedGen bool) (*apps.DaemonSet, error) { if int(ds.Status.DesiredNumberScheduled) == desiredNumberScheduled && int(ds.Status.CurrentNumberScheduled) == currentNumberScheduled && int(ds.Status.NumberMisscheduled) == numberMisscheduled && @@ -1115,7 +1170,7 @@ func storeDaemonSetStatus( int(ds.Status.NumberAvailable) == numberAvailable && int(ds.Status.NumberUnavailable) == numberUnavailable && ds.Status.ObservedGeneration >= ds.Generation { - return nil + return nil, nil } toUpdate := ds.DeepCopy() @@ -1133,8 +1188,9 @@ func storeDaemonSetStatus( toUpdate.Status.NumberAvailable = int32(numberAvailable) toUpdate.Status.NumberUnavailable = int32(numberUnavailable) - if _, updateErr = dsClient.UpdateStatus(ctx, toUpdate, metav1.UpdateOptions{}); updateErr == nil { - return nil + var result *apps.DaemonSet + if result, updateErr = dsClient.UpdateStatus(ctx, toUpdate, metav1.UpdateOptions{}); updateErr == nil { + return result, nil } // Stop retrying if we exceed statusUpdateRetries - the DaemonSet will be requeued with a rate limit. @@ -1145,10 +1201,10 @@ func storeDaemonSetStatus( if toUpdate, getErr = dsClient.Get(ctx, ds.Name, metav1.GetOptions{}); getErr != nil { // If the GET fails we can't trust status.Replicas anymore. This error // is bound to be more interesting than the update failure. - return getErr + return nil, getErr } } - return updateErr + return nil, updateErr } func (dsc *DaemonSetsController) updateDaemonSetStatus(ctx context.Context, ds *apps.DaemonSet, nodeList []*v1.Node, hash string, updateObservedGen bool) error { @@ -1199,10 +1255,18 @@ func (dsc *DaemonSetsController) updateDaemonSetStatus(ctx context.Context, ds * } numberUnavailable := desiredNumberScheduled - numberAvailable - err = storeDaemonSetStatus(ctx, dsc.kubeClient.AppsV1().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable, numberUnavailable, updateObservedGen) + updatedDS, err := storeDaemonSetStatus(ctx, dsc.kubeClient.AppsV1().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable, numberUnavailable, updateObservedGen) if err != nil { return fmt.Errorf("error storing status for daemon set %#v: %w", ds, err) } + if updatedDS != nil { + dsc.consistencyStore.WroteAt( + types.NamespacedName{Name: ds.Name, Namespace: ds.Namespace}, + updatedDS.UID, + daemonsetGroupResource, + updatedDS.ResourceVersion, + ) + } // Resync the DaemonSet after MinReadySeconds as a last line of defense to guard against clock-skew. if ds.Spec.MinReadySeconds > 0 && numberReady != numberAvailable { @@ -1223,10 +1287,31 @@ func (dsc *DaemonSetsController) syncDaemonSet(ctx context.Context, key string) if err != nil { return err } + + dsNamespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + + // If our writes have not yet been observed by our informers, we should not + // continue since our reads will not be in sync with our previously enacted + // state. + if err := dsc.consistencyStore.EnsureReady(dsNamespacedName); err != nil { + var consistencyErr *consistencyutil.ConsistencyError + if errors.As(err, &consistencyErr) { + metrics.DaemonsetRequeueSkips.WithLabelValues( + consistencyErr.GroupResource.Group, + consistencyErr.GroupResource.Resource, + ).Inc() + } + return err + } + ds, err := dsc.dsLister.DaemonSets(namespace).Get(name) if apierrors.IsNotFound(err) { logger.V(3).Info("Daemon set has been deleted", "daemonset", key) dsc.expectations.DeleteExpectations(logger, key) + dsc.consistencyStore.Clear(dsNamespacedName, "") return nil } if err != nil { diff --git a/pkg/controller/daemon/daemon_controller_test.go b/pkg/controller/daemon/daemon_controller_test.go index 221ee3b9acbb0..4e3dc28c39195 100644 --- a/pkg/controller/daemon/daemon_controller_test.go +++ b/pkg/controller/daemon/daemon_controller_test.go @@ -3621,7 +3621,7 @@ func TestStoreDaemonSetStatus(t *testing.T) { } return true, ds, nil }) - if err := storeDaemonSetStatus(context.TODO(), fakeClient.AppsV1().DaemonSets("default"), ds, 2, 2, 2, 2, 2, 2, 2, true); err != tt.expectedError { + if _, err := storeDaemonSetStatus(context.TODO(), fakeClient.AppsV1().DaemonSets("default"), ds, 2, 2, 2, 2, 2, 2, 2, true); !errors.Is(err, tt.expectedError) { t.Errorf("storeDaemonSetStatus() got %v, expected %v", err, tt.expectedError) } if getCalled != tt.expectedGetCalled { diff --git a/pkg/controller/daemon/metrics/metrics.go b/pkg/controller/daemon/metrics/metrics.go new file mode 100644 index 0000000000000..ba76c764436fa --- /dev/null +++ b/pkg/controller/daemon/metrics/metrics.go @@ -0,0 +1,48 @@ +/* +Copyright 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 metrics + +import ( + "sync" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const DaemonControllerSubsystem = "daemonset_controller" + +var ( + DaemonsetRequeueSkips = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: DaemonControllerSubsystem, + Name: "stale_sync_skips_total", + Help: "Total number of DaemonSet syncs skipped due to a stale watch cache.", + StabilityLevel: metrics.ALPHA, + }, + // These are the labels (dimensions) + []string{"group", "resource"}, + ) +) + +var registerMetrics sync.Once + +// Register registers DaemonSet Controller metrics. +func Register() { + registerMetrics.Do(func() { + legacyregistry.MustRegister(DaemonsetRequeueSkips) + }) +} diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index 8c5412b868c0d..b1dc644561bd4 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -83,6 +83,8 @@ type DeploymentController struct { rsLister appslisters.ReplicaSetLister // podLister can list/get pods from the shared informer's store podLister corelisters.PodLister + // podIndexer allows looking up pods by ControllerRef UID + podIndexer cache.Indexer // dListerSynced returns true if the Deployment store has been synced at least once. // Added as a member to the struct to allow injection for testing. @@ -156,6 +158,12 @@ func NewDeploymentController(ctx context.Context, dInformer appsinformers.Deploy dc.dListerSynced = dInformer.Informer().HasSynced dc.rsListerSynced = rsInformer.Informer().HasSynced dc.podListerSynced = podInformer.Informer().HasSynced + + if err := controller.AddPodControllerIndexer(podInformer.Informer()); err != nil { + return nil, fmt.Errorf("adding Pod controller UID indexer: %w", err) + } + dc.podIndexer = podInformer.Informer().GetIndexer() + return dc, nil } @@ -562,31 +570,16 @@ func (dc *DeploymentController) getReplicaSetsForDeployment(ctx context.Context, // NOTE: The pod pointers returned by this method point the pod objects in the cache and thus // shouldn't be modified in any way. func (dc *DeploymentController) getPodMapForDeployment(d *apps.Deployment, rsList []*apps.ReplicaSet) (map[types.UID][]*v1.Pod, error) { - // Get all Pods that potentially belong to this Deployment. - selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector) - if err != nil { - return nil, err - } - pods, err := dc.podLister.Pods(d.Namespace).List(selector) - if err != nil { - return nil, err - } // Group Pods by their controller (if it's in rsList). podMap := make(map[types.UID][]*v1.Pod, len(rsList)) + for _, rs := range rsList { - podMap[rs.UID] = []*v1.Pod{} - } - for _, pod := range pods { - // Do not ignore inactive Pods because Recreate Deployments need to verify that no - // Pods from older versions are running before spinning up new Pods. - controllerRef := metav1.GetControllerOf(pod) - if controllerRef == nil { - continue - } - // Only append if we care about this UID. - if _, ok := podMap[controllerRef.UID]; ok { - podMap[controllerRef.UID] = append(podMap[controllerRef.UID], pod) + // list all pods managed by this ReplicaSet using the pod indexer + pods, err := controller.FilterPodsByOwner(dc.podIndexer, &rs.ObjectMeta, "ReplicaSet", false) + if err != nil { + return nil, err } + podMap[rs.UID] = pods } return podMap, nil } diff --git a/pkg/controller/deployment/deployment_controller_test.go b/pkg/controller/deployment/deployment_controller_test.go index b4481d06a7e16..d8925fbf9e5e7 100644 --- a/pkg/controller/deployment/deployment_controller_test.go +++ b/pkg/controller/deployment/deployment_controller_test.go @@ -1005,49 +1005,74 @@ func TestDeleteReplicaSetOrphan(t *testing.T) { } func BenchmarkGetPodMapForDeployment(b *testing.B) { - _, ctx := ktesting.NewTestContext(b) - - f := newFixture(b) - - d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"}) - - rs1 := newReplicaSet(d, "rs1", 1) - rs2 := newReplicaSet(d, "rs2", 1) - - var pods []*v1.Pod - var objects []runtime.Object - for i := 0; i < 100; i++ { - p1, p2 := generatePodFromRS(rs1), generatePodFromRS(rs2) - p1.Name, p2.Name = p1.Name+fmt.Sprintf("-%d", i), p2.Name+fmt.Sprintf("-%d", i) - pods = append(pods, p1, p2) - objects = append(objects, p1, p2) - } - - f.dLister = append(f.dLister, d) - f.rsLister = append(f.rsLister, rs1, rs2) - f.podLister = append(f.podLister, pods...) - f.objects = append(f.objects, d, rs1, rs2) - f.objects = append(f.objects, objects...) - - // Start the fixture. - c, informers, err := f.newController(ctx) - if err != nil { - b.Fatalf("error creating Deployment controller: %v", err) - } - stopCh := make(chan struct{}) - defer close(stopCh) - informers.Start(stopCh) - - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - m, err := c.getPodMapForDeployment(d, f.rsLister) - if err != nil { - b.Fatalf("getPodMapForDeployment() error: %v", err) - } - if len(m) != 2 { - b.Errorf("Invalid map size, expected 2, got: %d", len(m)) - } + cases := []struct { + name string + numPods int + extraPods int // Pods in namespace not owned by this deployment + }{ + {name: "10-Pods-No-Noise", numPods: 10, extraPods: 0}, + {name: "10-Pods-With-Noise", numPods: 10, extraPods: 100}, + {name: "10000-Pods-No-Noise", numPods: 10000, extraPods: 0}, + {name: "10000-Pods-With-Noise", numPods: 10000, extraPods: 100000}, + } + + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + _, ctx := ktesting.NewTestContext(b) + + f := newFixture(b) + + d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"}) + + rs1 := newReplicaSet(d, "rs1", 1) + rs2 := newReplicaSet(d, "rs2", 1) + + var pods []*v1.Pod + var objects []runtime.Object + for i := 0; i < tc.numPods; i++ { + p1, p2 := generatePodFromRS(rs1), generatePodFromRS(rs2) + p1.Name, p2.Name = p1.Name+fmt.Sprintf("-%d", i), p2.Name+fmt.Sprintf("-%d", i) + pods = append(pods, p1, p2) + objects = append(objects, p1, p2) + } + + // Add extra pods that don't belong to the deployment + for i := 0; i < tc.extraPods; i++ { + p := generatePodFromRS(rs1) + p.Name = fmt.Sprintf("extra-pod-%d", i) + p.OwnerReferences = nil // Orphaned/unrelated + p.Labels = map[string]string{"foo": "notbar"} + pods = append(pods, p) + objects = append(objects, p) + } + + f.dLister = append(f.dLister, d) + f.rsLister = append(f.rsLister, rs1, rs2) + f.podLister = append(f.podLister, pods...) + f.objects = append(f.objects, d, rs1, rs2) + f.objects = append(f.objects, objects...) + + // Start the fixture. + c, informers, err := f.newController(ctx) + if err != nil { + b.Fatalf("error creating Deployment controller: %v", err) + } + stopCh := make(chan struct{}) + defer close(stopCh) + informers.Start(stopCh) + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + m, err := c.getPodMapForDeployment(d, f.rsLister) + if err != nil { + b.Fatalf("getPodMapForDeployment() error: %v", err) + } + if len(m) != 2 { + b.Errorf("Invalid map size, expected 2, got: %d", len(m)) + } + } + }) } } diff --git a/pkg/controller/deployment/recreate.go b/pkg/controller/deployment/recreate.go index 6da2704a466b0..8b8b80a0905cb 100644 --- a/pkg/controller/deployment/recreate.go +++ b/pkg/controller/deployment/recreate.go @@ -83,7 +83,7 @@ func (dc *DeploymentController) scaleDownOldReplicaSetsForRecreate(ctx context.C if *(rs.Spec.Replicas) == 0 { continue } - scaledRS, updatedRS, err := dc.scaleReplicaSetAndRecordEvent(ctx, rs, 0, deployment) + scaledRS, updatedRS, err := dc.scaleReplicaSet(ctx, rs, 0, deployment, false) if err != nil { return false, err } @@ -127,6 +127,6 @@ func oldPodsRunning(newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet, podMap ma // scaleUpNewReplicaSetForRecreate scales up new replica set when deployment strategy is "Recreate". func (dc *DeploymentController) scaleUpNewReplicaSetForRecreate(ctx context.Context, newRS *apps.ReplicaSet, deployment *apps.Deployment) (bool, error) { - scaled, _, err := dc.scaleReplicaSetAndRecordEvent(ctx, newRS, *(deployment.Spec.Replicas), deployment) + scaled, _, err := dc.scaleReplicaSet(ctx, newRS, *(deployment.Spec.Replicas), deployment, false) return scaled, err } diff --git a/pkg/controller/deployment/rollback.go b/pkg/controller/deployment/rollback.go index 11708ceb9b6d6..01993b85bf50b 100644 --- a/pkg/controller/deployment/rollback.go +++ b/pkg/controller/deployment/rollback.go @@ -103,11 +103,11 @@ func (dc *DeploymentController) rollbackToTemplate(ctx context.Context, d *apps. } func (dc *DeploymentController) emitRollbackWarningEvent(d *apps.Deployment, reason, message string) { - dc.eventRecorder.Eventf(d, v1.EventTypeWarning, reason, message) + dc.eventRecorder.Eventf(d, v1.EventTypeWarning, reason, "%s", message) } func (dc *DeploymentController) emitRollbackNormalEvent(d *apps.Deployment, message string) { - dc.eventRecorder.Eventf(d, v1.EventTypeNormal, deploymentutil.RollbackDone, message) + dc.eventRecorder.Eventf(d, v1.EventTypeNormal, deploymentutil.RollbackDone, "%s", message) } // updateDeploymentAndClearRollbackTo sets .spec.rollbackTo to nil and update the input deployment diff --git a/pkg/controller/deployment/rolling.go b/pkg/controller/deployment/rolling.go index 8da48469a23e7..d32584881b193 100644 --- a/pkg/controller/deployment/rolling.go +++ b/pkg/controller/deployment/rolling.go @@ -72,14 +72,14 @@ func (dc *DeploymentController) reconcileNewReplicaSet(ctx context.Context, allR } if *(newRS.Spec.Replicas) > *(deployment.Spec.Replicas) { // Scale down. - scaled, _, err := dc.scaleReplicaSetAndRecordEvent(ctx, newRS, *(deployment.Spec.Replicas), deployment) + scaled, _, err := dc.scaleReplicaSet(ctx, newRS, *(deployment.Spec.Replicas), deployment, false) return scaled, err } newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, newRS) if err != nil { return false, err } - scaled, _, err := dc.scaleReplicaSetAndRecordEvent(ctx, newRS, newReplicasCount, deployment) + scaled, _, err := dc.scaleReplicaSet(ctx, newRS, newReplicasCount, deployment, false) return scaled, err } @@ -178,7 +178,7 @@ func (dc *DeploymentController) cleanupUnhealthyReplicas(ctx context.Context, ol if newReplicasCount > *(targetRS.Spec.Replicas) { return nil, 0, fmt.Errorf("when cleaning up unhealthy replicas, got invalid request to scale down %s/%s %d -> %d", targetRS.Namespace, targetRS.Name, *(targetRS.Spec.Replicas), newReplicasCount) } - _, updatedOldRS, err := dc.scaleReplicaSetAndRecordEvent(ctx, targetRS, newReplicasCount, deployment) + _, updatedOldRS, err := dc.scaleReplicaSet(ctx, targetRS, newReplicasCount, deployment, false) if err != nil { return nil, totalScaledDown, err } @@ -223,7 +223,7 @@ func (dc *DeploymentController) scaleDownOldReplicaSetsForRollingUpdate(ctx cont if newReplicasCount > *(targetRS.Spec.Replicas) { return 0, fmt.Errorf("when scaling down old RS, got invalid request to scale down %s/%s %d -> %d", targetRS.Namespace, targetRS.Name, *(targetRS.Spec.Replicas), newReplicasCount) } - _, _, err := dc.scaleReplicaSetAndRecordEvent(ctx, targetRS, newReplicasCount, deployment) + _, _, err := dc.scaleReplicaSet(ctx, targetRS, newReplicasCount, deployment, false) if err != nil { return totalScaledDown, err } diff --git a/pkg/controller/deployment/sync.go b/pkg/controller/deployment/sync.go index 9f631dd02a7cd..76493294c9ba3 100644 --- a/pkg/controller/deployment/sync.go +++ b/pkg/controller/deployment/sync.go @@ -279,7 +279,7 @@ func (dc *DeploymentController) getNewReplicaSet(ctx context.Context, d *apps.De // these reasons as well. Related issue: https://github.com/kubernetes/kubernetes/issues/18568 _, _ = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(ctx, d, metav1.UpdateOptions{}) } - dc.eventRecorder.Eventf(d, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, msg) + dc.eventRecorder.Eventf(d, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, "%s", msg) return nil, err } if !alreadyExists && newReplicasCount > 0 { @@ -311,7 +311,7 @@ func (dc *DeploymentController) scale(ctx context.Context, deployment *apps.Depl if *(activeOrLatest.Spec.Replicas) == *(deployment.Spec.Replicas) { return nil } - _, _, err := dc.scaleReplicaSetAndRecordEvent(ctx, activeOrLatest, *(deployment.Spec.Replicas), deployment) + _, _, err := dc.scaleReplicaSet(ctx, activeOrLatest, *(deployment.Spec.Replicas), deployment, false) return err } @@ -319,7 +319,7 @@ func (dc *DeploymentController) scale(ctx context.Context, deployment *apps.Depl // This case handles replica set adoption during a saturated new replica set. if deploymentutil.IsSaturated(deployment, newRS) { for _, old := range controller.FilterActiveReplicaSets(oldRSs) { - if _, _, err := dc.scaleReplicaSetAndRecordEvent(ctx, old, 0, deployment); err != nil { + if _, _, err := dc.scaleReplicaSet(ctx, old, 0, deployment, false); err != nil { return err } } @@ -391,7 +391,7 @@ func (dc *DeploymentController) scale(ctx context.Context, deployment *apps.Depl } // TODO: Use transactions when we have them. - if _, _, err := dc.scaleReplicaSet(ctx, rs, nameToSize[rs.Name], deployment); err != nil { + if _, _, err := dc.scaleReplicaSet(ctx, rs, nameToSize[rs.Name], deployment, true); err != nil { // Return as soon as we fail, the deployment is requeued return err } @@ -400,19 +400,18 @@ func (dc *DeploymentController) scale(ctx context.Context, deployment *apps.Depl return nil } -func (dc *DeploymentController) scaleReplicaSetAndRecordEvent(ctx context.Context, rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment) (bool, *apps.ReplicaSet, error) { - // No need to scale - if *(rs.Spec.Replicas) == newScale { +// scaleReplicaSet acts in two modes: +// 1. lazy update - when forceUpdate is false, it will only act when the replicas +// where changed; +// 2. normal - either replicaset annotations (DesiredReplicasAnnotation and +// MaxReplicasAnnotation) require an update or the replicas have changed. +func (dc *DeploymentController) scaleReplicaSet(ctx context.Context, rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment, forceUpdate bool) (bool, *apps.ReplicaSet, error) { + // Don't scale, unless it's a forced update or the replicas actually differ + if !forceUpdate && *(rs.Spec.Replicas) == newScale { return false, rs, nil } - scaled, newRS, err := dc.scaleReplicaSet(ctx, rs, newScale, deployment) - return scaled, newRS, err -} - -func (dc *DeploymentController) scaleReplicaSet(ctx context.Context, rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment) (bool, *apps.ReplicaSet, error) { sizeNeedsUpdate := *(rs.Spec.Replicas) != newScale - annotationsNeedUpdate := deploymentutil.ReplicasAnnotationsNeedUpdate(rs, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment)) scaled := false diff --git a/pkg/controller/devicetainteviction/OWNERS b/pkg/controller/devicetainteviction/OWNERS index 19c02ee0b1063..42297f5d25e08 100644 --- a/pkg/controller/devicetainteviction/OWNERS +++ b/pkg/controller/devicetainteviction/OWNERS @@ -2,7 +2,9 @@ approvers: - sig-scheduling-maintainers + - pohly reviewers: - sig-scheduling + - pohly labels: - sig/scheduling diff --git a/pkg/controller/devicetainteviction/device_taint_eviction.go b/pkg/controller/devicetainteviction/device_taint_eviction.go index b80042080cfab..95ed7553dbc6b 100644 --- a/pkg/controller/devicetainteviction/device_taint_eviction.go +++ b/pkg/controller/devicetainteviction/device_taint_eviction.go @@ -30,7 +30,8 @@ import ( v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" - resourcealpha "k8s.io/api/resource/v1alpha3" + resourcbeta "k8s.io/api/resource/v1beta2" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -41,15 +42,15 @@ import ( "k8s.io/apimachinery/pkg/util/sets" utilfeature "k8s.io/apiserver/pkg/util/feature" metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" - resourceac "k8s.io/client-go/applyconfigurations/resource/v1alpha3" + resourceac "k8s.io/client-go/applyconfigurations/resource/v1beta2" coreinformers "k8s.io/client-go/informers/core/v1" resourceinformers "k8s.io/client-go/informers/resource/v1" - resourcealphainformers "k8s.io/client-go/informers/resource/v1alpha3" + resourcealphainformers "k8s.io/client-go/informers/resource/v1beta2" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" v1core "k8s.io/client-go/kubernetes/typed/core/v1" corelisters "k8s.io/client-go/listers/core/v1" - resourcealphalisters "k8s.io/client-go/listers/resource/v1alpha3" + resourcealphalisters "k8s.io/client-go/listers/resource/v1beta2" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -67,6 +68,10 @@ const ( // updates while eviction is in progress. Once it is done, it no longer gets // updated until in progress again. ruleStatusPeriod = 10 * time.Second + + maxUIDCacheEntries = 500 + + podGroupIndex = "podGroupPerPod" ) // Controller listens to Taint changes of DRA devices and Toleration changes of ResourceClaims, @@ -100,11 +105,17 @@ type Controller struct { ruleInformer resourcealphainformers.DeviceTaintRuleInformer classInformer resourceinformers.DeviceClassInformer ruleLister resourcealphalisters.DeviceTaintRuleLister - haveSynced []cache.InformerSynced + haveSynced []cache.DoneChecker hasSynced atomic.Int32 metrics metrics.Metrics workqueue workqueue.TypedRateLimitingInterface[workItem] + workloadResourceClaimsEnabled bool + + // The evictedPods cache keeps track of Pods for which we know that + // they have been evicted. + evictedPods *uidCache + evictPodHook func(pod tainteviction.NamespacedObject, eviction evictionAndReason) cancelEvictHook func(pod tainteviction.NamespacedObject) bool @@ -128,7 +139,7 @@ type Controller struct { pools map[poolID]pool // evictingRules tracks all DeviceTaintRules by name which cause pod eviction. - evictingRules map[string]*resourcealpha.DeviceTaintRule + evictingRules map[string]*resourcbeta.DeviceTaintRule // taintRuleStats tracks information about work that was done for a specific DeviceTaintRule instance. // @@ -283,7 +294,7 @@ func (er evictionReason) String() string { // trackedTaint augments a DeviceTaint with a pointer to its origin. // rule and slice are mutually exclusive. Exactly one of them is always set. type trackedTaint struct { - rule *resourcealpha.DeviceTaintRule + rule *resourcbeta.DeviceTaintRule slice sliceDeviceTaint } @@ -366,7 +377,7 @@ type workItem struct { ruleRef tainteviction.NamespacedObject } -func workItemForRule(rule *resourcealpha.DeviceTaintRule) workItem { +func workItemForRule(rule *resourcbeta.DeviceTaintRule) workItem { return workItem{ruleRef: tainteviction.NamespacedObject{NamespacedName: types.NamespacedName{Name: rule.Name}, UID: rule.UID}} } @@ -383,10 +394,11 @@ func (tc *Controller) maybeDeletePod(ctx context.Context, podRef tainteviction.N tc.mutex.Lock() tc.maybeDeletePodCount++ eviction, ok := tc.deletePodAt[podRef] + evicted := tc.evictedPods.has(podRef.UID) tc.mutex.Unlock() - logger.V(5).Info("Processing pod deletion work item", "active", ok, "eviction", eviction) + logger.V(5).Info("Processing pod deletion work item", "active", ok, "eviction", eviction, "evicted", evicted) - if !ok { + if !ok || evicted { logger.V(5).Info("Work item for pod deletion obsolete, nothing to do") return 0, nil } @@ -401,8 +413,12 @@ func (tc *Controller) maybeDeletePod(ctx context.Context, podRef tainteviction.N defer func() { if finalErr == nil { // Forget the deletion time, we are done. + // Also remember that we don't even need to + // check the pod again, should it have been + // added to the queue again in the meantime. tc.mutex.Lock() delete(tc.deletePodAt, podRef) + tc.evictedPods.add(podRef.UID) tc.mutex.Unlock() } }() @@ -509,7 +525,7 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev // Already set? index := slices.IndexFunc(rule.Status.Conditions, func(condition metav1.Condition) bool { - return condition.Type == resourcealpha.DeviceTaintConditionEvictionInProgress + return condition.Type == resourcbeta.DeviceTaintConditionEvictionInProgress }) // LastTransitionTime gets bumped each time we make any change to the condition, @@ -535,7 +551,7 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev // Checking all pods might be expensive. Only do it if really needed. var numTaintedSliceDevices, numTaintedAllocatedDevices, numPendingPods, numPendingNamespaces int64 switch rule.Spec.Taint.Effect { - case resourcealpha.DeviceTaintEffectNone: + case resourcbeta.DeviceTaintEffectNone: // Temporarily change the effect from None to NoExecute to simulate. // We pretend to do that through informer events. We hold the lock, // so there is no race with real informer events or other goroutines. @@ -543,23 +559,25 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev // To avoid having a lasting impact on the real controller instance // we make a temporary copy. ruleEvict := rule.DeepCopy() - ruleEvict.Spec.Taint.Effect = resourcealpha.DeviceTaintEffectNoExecute + ruleEvict.Spec.Taint.Effect = resourcbeta.DeviceTaintEffectNoExecute tc := &Controller{ - logger: klog.LoggerWithName(logger, "simulation"), - podLister: tc.podLister, - ruleLister: nil, // Replaced by simulateRule. - deletePodAt: make(map[tainteviction.NamespacedObject]evictionAndReason), - allocatedClaims: maps.Clone(tc.allocatedClaims), - pools: tc.pools, - evictingRules: make(map[string]*resourcealpha.DeviceTaintRule), - workqueue: &NOPQueue[workItem]{}, + logger: klog.LoggerWithName(logger, "simulation"), + podInformer: tc.podInformer, + podLister: tc.podLister, + ruleLister: nil, // Replaced by simulateRule. + deletePodAt: make(map[tainteviction.NamespacedObject]evictionAndReason), + allocatedClaims: maps.Clone(tc.allocatedClaims), + pools: tc.pools, + evictingRules: make(map[string]*resourcbeta.DeviceTaintRule), + workqueue: &NOPQueue[workItem]{}, + workloadResourceClaimsEnabled: tc.workloadResourceClaimsEnabled, } defer tc.workqueue.ShutDown() tc.handleRuleChange(rule, ruleEvict) numPendingPods, numPendingNamespaces, err = tc.countPendingPods(rule) numTaintedSliceDevices, numTaintedAllocatedDevices = tc.countTaintedDevices(rule) - case resourcealpha.DeviceTaintEffectNoExecute: + case resourcbeta.DeviceTaintEffectNoExecute: numPendingPods, numPendingNamespaces, err = tc.countPendingPods(rule) default: err = nil @@ -570,14 +588,14 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev // Some fields are tentative and get updated below. newCondition := metav1.Condition{ - Type: resourcealpha.DeviceTaintConditionEvictionInProgress, + Type: resourcbeta.DeviceTaintConditionEvictionInProgress, Status: metav1.ConditionFalse, Reason: string("Effect" + rule.Spec.Taint.Effect), ObservedGeneration: rule.Generation, LastTransitionTime: existingCondition.LastTransitionTime, // To avoid a false "is different" in the comparison, gets updated later. } switch rule.Spec.Taint.Effect { - case resourcealpha.DeviceTaintEffectNoExecute: + case resourcbeta.DeviceTaintEffectNoExecute: switch { case numPendingPods > 0: newCondition.Reason = "PodsPendingEviction" @@ -597,7 +615,7 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev default: newCondition.Reason = "NotStarted" } - case resourcealpha.DeviceTaintEffectNone: + case resourcbeta.DeviceTaintEffectNone: newCondition.Reason = "NoEffect" if numTaintedSliceDevices == 1 { newCondition.Message += "1 published device selected. " @@ -649,7 +667,7 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev ObservedGeneration: &newCondition.ObservedGeneration, LastTransitionTime: &newCondition.LastTransitionTime, })) - if _, err := tc.client.ResourceV1alpha3().DeviceTaintRules().ApplyStatus(ctx, ruleAC, metav1.ApplyOptions{FieldManager: tc.name, Force: true}); err != nil { + if _, err := tc.client.ResourceV1beta2().DeviceTaintRules().ApplyStatus(ctx, ruleAC, metav1.ApplyOptions{FieldManager: tc.name, Force: true}); err != nil { return 0, fmt.Errorf("add condition to DeviceTaintRule status: %w", err) } } @@ -658,7 +676,7 @@ func (tc *Controller) maybeUpdateRuleStatus(ctx context.Context, ruleRef taintev return 0, nil } -func (tc *Controller) countPendingPods(rule *resourcealpha.DeviceTaintRule) (int64, int64, error) { +func (tc *Controller) countPendingPods(rule *resourcbeta.DeviceTaintRule) (int64, int64, error) { pods, err := tc.podLister.List(labels.Everything()) if err != nil { return -1, -1, fmt.Errorf("list pod: %w", err) @@ -688,7 +706,7 @@ func (tc *Controller) countPendingPods(rule *resourcealpha.DeviceTaintRule) (int // countTaintedDevices determines the number of devices in slices matching the rule and // the number of allocated devices matching the rule. -func (tc *Controller) countTaintedDevices(rule *resourcealpha.DeviceTaintRule) (numTaintedSliceDevices int64, numTaintedAllocatedDevices int64) { +func (tc *Controller) countTaintedDevices(rule *resourcbeta.DeviceTaintRule) (numTaintedSliceDevices int64, numTaintedAllocatedDevices int64) { for poolID, pool := range tc.pools { for _, slice := range pool.slices { if slice.Spec.Pool.Generation != pool.maxGeneration { @@ -715,7 +733,7 @@ func (tc *Controller) countTaintedDevices(rule *resourcealpha.DeviceTaintRule) ( // New creates a new Controller that will use passed clientset to communicate with the API server. // Spawns no goroutines. That happens in Run. -func New(c clientset.Interface, podInformer coreinformers.PodInformer, claimInformer resourceinformers.ResourceClaimInformer, sliceInformer resourceinformers.ResourceSliceInformer, ruleInformer resourcealphainformers.DeviceTaintRuleInformer, classInformer resourceinformers.DeviceClassInformer, controllerName string) *Controller { +func New(c clientset.Interface, podInformer coreinformers.PodInformer, claimInformer resourceinformers.ResourceClaimInformer, sliceInformer resourceinformers.ResourceSliceInformer, ruleInformer resourcealphainformers.DeviceTaintRuleInformer, classInformer resourceinformers.DeviceClassInformer, controllerName string, workloadResourceClaimsEnabled bool) *Controller { metrics.Register() // It would be nicer to pass the controller name here, but that probably would break generating https://kubernetes.io/docs/reference/instrumentation/metrics. tc := &Controller{ @@ -730,16 +748,19 @@ func New(c clientset.Interface, podInformer coreinformers.PodInformer, claimInfo deletePodAt: make(map[tainteviction.NamespacedObject]evictionAndReason), allocatedClaims: make(map[types.NamespacedName]allocatedClaim), pools: make(map[poolID]pool), - evictingRules: make(map[string]*resourcealpha.DeviceTaintRule), + evictingRules: make(map[string]*resourcbeta.DeviceTaintRule), taintRuleStats: make(map[types.UID]taintRuleStats), // Instantiate all informers now to ensure that they get started. - haveSynced: []cache.InformerSynced{ - podInformer.Informer().HasSynced, - claimInformer.Informer().HasSynced, - sliceInformer.Informer().HasSynced, - classInformer.Informer().HasSynced, + haveSynced: []cache.DoneChecker{ + podInformer.Informer().HasSyncedChecker(), + claimInformer.Informer().HasSyncedChecker(), + sliceInformer.Informer().HasSyncedChecker(), + classInformer.Informer().HasSyncedChecker(), }, - metrics: metrics.Global, + metrics: metrics.Global, + evictedPods: newUIDCache(maxUIDCacheEntries), + + workloadResourceClaimsEnabled: workloadResourceClaimsEnabled, } // The informer for DeviceTaintRules only gets instantiated if the corresponding @@ -748,7 +769,7 @@ func New(c clientset.Interface, podInformer coreinformers.PodInformer, claimInfo if utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaintRules) { tc.ruleInformer = ruleInformer tc.ruleLister = ruleInformer.Lister() - tc.haveSynced = append(tc.haveSynced, ruleInformer.Informer().HasSynced) + tc.haveSynced = append(tc.haveSynced, ruleInformer.Informer().HasSyncedChecker()) } return tc @@ -807,6 +828,10 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { } defer eventBroadcaster.Shutdown() + if err := tc.addIndexers(); err != nil { + return err + } + claimHandler, err := tc.claimInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj any) { claim, ok := obj.(*resourceapi.ResourceClaim) @@ -852,7 +877,7 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { defer func() { _ = tc.claimInformer.Informer().RemoveEventHandler(claimHandler) }() - tc.haveSynced = append(tc.haveSynced, claimHandler.HasSynced) + tc.haveSynced = append(tc.haveSynced, claimHandler.HasSyncedChecker()) podHandler, err := tc.podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj any) { @@ -899,12 +924,12 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { defer func() { _ = tc.podInformer.Informer().RemoveEventHandler(podHandler) }() - tc.haveSynced = append(tc.haveSynced, podHandler.HasSynced) + tc.haveSynced = append(tc.haveSynced, podHandler.HasSyncedChecker()) if tc.ruleInformer != nil { ruleHandler, err := tc.ruleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj any) { - rule, ok := obj.(*resourcealpha.DeviceTaintRule) + rule, ok := obj.(*resourcbeta.DeviceTaintRule) if !ok { logger.Error(nil, "Expected DeviceTaintRule", "actual", fmt.Sprintf("%T", obj)) return @@ -914,12 +939,12 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { tc.handleRuleChange(nil, rule) }, UpdateFunc: func(oldObj, newObj any) { - oldRule, ok := oldObj.(*resourcealpha.DeviceTaintRule) + oldRule, ok := oldObj.(*resourcbeta.DeviceTaintRule) if !ok { logger.Error(nil, "Expected DeviceTaintRule", "actual", fmt.Sprintf("%T", oldObj)) return } - newRule, ok := newObj.(*resourcealpha.DeviceTaintRule) + newRule, ok := newObj.(*resourcbeta.DeviceTaintRule) if !ok { logger.Error(nil, "Expected DeviceTaintRule", "actual", fmt.Sprintf("%T", newObj)) } @@ -931,7 +956,7 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { obj = tombstone.Obj } - rule, ok := obj.(*resourcealpha.DeviceTaintRule) + rule, ok := obj.(*resourcbeta.DeviceTaintRule) if !ok { logger.Error(nil, "Expected DeviceTaintRule", "actual", fmt.Sprintf("%T", obj)) return @@ -947,7 +972,7 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { defer func() { _ = tc.ruleInformer.Informer().RemoveEventHandler(ruleHandler) }() - tc.haveSynced = append(tc.haveSynced, ruleHandler.HasSynced) + tc.haveSynced = append(tc.haveSynced, ruleHandler.HasSyncedChecker()) } sliceHandler, err := tc.sliceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -993,9 +1018,9 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { defer func() { _ = tc.sliceInformer.Informer().RemoveEventHandler(sliceHandler) }() - tc.haveSynced = append(tc.haveSynced, sliceHandler.HasSynced) + tc.haveSynced = append(tc.haveSynced, sliceHandler.HasSyncedChecker()) - if !cache.WaitForNamedCacheSyncWithContext(ctx, tc.haveSynced...) { + if !cache.WaitFor(ctx, "cache and event handler sync", tc.haveSynced...) { // If we get here, the caller canceled the context. This is not an error. return nil } @@ -1012,6 +1037,28 @@ func (tc *Controller) Run(ctx context.Context, numWorkers int) error { return nil } +func (tc *Controller) addIndexers() error { + if !tc.workloadResourceClaimsEnabled { + return nil + } + + err := tc.podInformer.Informer().AddIndexers(cache.Indexers{podGroupIndex: func(obj any) ([]string, error) { + pod, ok := obj.(*v1.Pod) + if !ok { + return nil, fmt.Errorf("unexpected type %T reached Pod informer indexer", obj) + } + if pod.Spec.SchedulingGroup == nil || pod.Spec.SchedulingGroup.PodGroupName == nil { + return nil, nil + } + return []string{pod.Namespace + "/" + *pod.Spec.SchedulingGroup.PodGroupName}, nil + }}) + if err != nil { + return fmt.Errorf("adding %s indexer for pods: %w", podGroupIndex, err) + } + + return nil +} + // evictPod ensures that the pod gets evicted at the specified time. // It doesn't block. func (tc *Controller) evictPod(podRef tainteviction.NamespacedObject, eviction evictionAndReason) { @@ -1258,7 +1305,7 @@ func (tc *Controller) allEvictingDeviceTaints(allocatedDevice resourceapi.Device } } -func ruleMatchesDevice(rule *resourcealpha.DeviceTaintRule, driverName, poolName, deviceName string) bool { +func ruleMatchesDevice(rule *resourcbeta.DeviceTaintRule, driverName, poolName, deviceName string) bool { selector := rule.Spec.DeviceSelector if selector == nil { return false @@ -1271,7 +1318,7 @@ func ruleMatchesDevice(rule *resourcealpha.DeviceTaintRule, driverName, poolName return true } -func (tc *Controller) handleRuleChange(oldRule, newRule *resourcealpha.DeviceTaintRule) { +func (tc *Controller) handleRuleChange(oldRule, newRule *resourcbeta.DeviceTaintRule) { rule := newRule if rule == nil { rule = oldRule @@ -1309,10 +1356,10 @@ func (tc *Controller) handleRuleChange(oldRule, newRule *resourcealpha.DeviceTai // Rule spec changes should be rare. Simply do a brute-force re-evaluation of all allocated claims. // Same with trying to avoid delete+add in evictingRules, the logic just becomes unnecessarily complex. - if oldRule != nil && oldRule.Spec.Taint.Effect == resourcealpha.DeviceTaintEffectNoExecute { + if oldRule != nil && oldRule.Spec.Taint.Effect == resourcbeta.DeviceTaintEffectNoExecute { delete(tc.evictingRules, oldRule.Name) } - if newRule != nil && newRule.Spec.Taint.Effect == resourcealpha.DeviceTaintEffectNoExecute { + if newRule != nil && newRule.Spec.Taint.Effect == resourcbeta.DeviceTaintEffectNoExecute { tc.evictingRules[newRule.Name] = newRule } for name, oldAllocatedClaim := range tc.allocatedClaims { @@ -1459,21 +1506,43 @@ func (tc *Controller) handlePodChange(oldPod, newPod *v1.Pod) { func (tc *Controller) handlePods(claim *resourceapi.ResourceClaim) { for _, consumer := range claim.Status.ReservedFor { - if consumer.APIGroup == "" && consumer.Resource == "pods" { + switch { + case consumer.APIGroup == "" && consumer.Resource == "pods": pod, err := tc.podLister.Pods(claim.Namespace).Get(consumer.Name) if err != nil { if apierrors.IsNotFound(err) { - return + continue } // Should not happen. utilruntime.HandleErrorWithLogger(tc.logger, err, "retrieve pod from cache") - return + continue } if pod.UID != consumer.UID { // Not the pod we were looking for. - return + continue } tc.handlePod(pod) + case consumer.APIGroup == schedulingapi.GroupName && consumer.Resource == "podgroups": + // PodGroups may persist in a ResourceClaim's status.reservedFor if + // the DRAWorkloadResourceClaims feature is disabled after having + // been enabled, so we need to check again here that the feature is + // enabled now. + if !tc.workloadResourceClaimsEnabled { + continue + } + podGroupKey := claim.Namespace + "/" + consumer.Name + pods, err := tc.podInformer.Informer().GetIndexer().ByIndex(podGroupIndex, podGroupKey) + if err != nil { + utilruntime.HandleErrorWithLogger(tc.logger, err, "retrieve Pods for PodGroup %s from cache", podGroupKey) + continue + } + for _, obj := range pods { + pod, ok := obj.(*v1.Pod) + if !ok { + continue + } + tc.handlePod(pod) + } } } } @@ -1524,11 +1593,11 @@ func (tc *Controller) podEvictionTime(pod *v1.Pod) *evictionAndReason { // Referenced, but not found or not allocated. Also not tainted. continue } - if mustCheckOwner && resourceclaim.IsForPod(pod, allocatedClaim.ResourceClaim) != nil { + if mustCheckOwner && resourceclaim.IsForPod(pod, allocatedClaim.ResourceClaim, tc.workloadResourceClaimsEnabled) != nil { // Claim and pod don't match. Ignore the claim. continue } - if !resourceclaim.IsReservedForPod(pod, allocatedClaim.ResourceClaim) { + if !resourceclaim.IsReservedForPod(pod, allocatedClaim.ResourceClaim, tc.workloadResourceClaimsEnabled) { // The pod isn't the one which is allowed and/or supposed to use the claim. // Perhaps that pod instance already got deleted and we are looking at its // replacement under the same name. Either way, ignore. diff --git a/pkg/controller/devicetainteviction/device_taint_eviction_test.go b/pkg/controller/devicetainteviction/device_taint_eviction_test.go index 3da7d197e88f7..c0a1922421085 100644 --- a/pkg/controller/devicetainteviction/device_taint_eviction_test.go +++ b/pkg/controller/devicetainteviction/device_taint_eviction_test.go @@ -17,6 +17,7 @@ limitations under the License. package devicetainteviction import ( + "context" "errors" "fmt" "maps" @@ -25,7 +26,6 @@ import ( "slices" "strings" "sync" - "sync/atomic" "testing" "time" @@ -34,22 +34,23 @@ import ( "github.com/onsi/gomega" "github.com/onsi/gomega/gstruct" gomegatypes "github.com/onsi/gomega/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" - resourcealpha "k8s.io/api/resource/v1alpha3" + resourcebeta "k8s.io/api/resource/v1beta2" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/watch" utilfeature "k8s.io/apiserver/pkg/util/feature" + clientfeatures "k8s.io/client-go/features" "k8s.io/client-go/informers" + clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" core "k8s.io/client-go/testing" - featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/featuregate" metricstestutil "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/controller/devicetainteviction/metrics" @@ -61,6 +62,31 @@ import ( "k8s.io/utils/ptr" ) +func init() { + // We must not use Default*Mutable*FeatureGate directly here, + // otherwise hack/verify-test-featuregates.sh complains. + if err := utilfeature.DefaultFeatureGate.(featuregate.MutableVersionedFeatureGate).SetFromMap(map[string]bool{ + string(features.DRADeviceTaints): true, + string(features.DRADeviceTaintRules): true, + string(features.DRAWorkloadResourceClaims): true, + string(features.GenericWorkload): true, // Dependency of DRAWorkloadResourceClaims + }); err != nil { + panic(err) + } + + // Disabled because we not only use the fake client, we also wrap it: + // that prevents auto-detection via IsWatchListSemanticsUnSupported. + // This here is simpler than implementing IsWatchListSemanticsUnSupported. + // + // We cannot use clientfeaturestesting.SetFeatureDuringTest (needs testing.TB), + // so here we just do it ourselves. + if err := clientfeatures.FeatureGates().(interface { + Set(clientfeatures.Feature, bool) error + }).Set(clientfeatures.WatchListClient, false); err != nil { + panic(err) + } +} + // Reduce typing with some constructors. func metav1Time(time time.Time) *metav1.Time { @@ -86,23 +112,17 @@ func l[T any](items ...T) []T { } // setup creates a controller which is ready to have its handle* methods called. -func setup(tCtx ktesting.TContext) *testContext { - featuregatetesting.SetFeatureGatesDuringTest(tCtx, utilfeature.DefaultFeatureGate, - featuregatetesting.FeatureOverrides{ - features.DRADeviceTaints: true, - features.DRADeviceTaintRules: true, - }, - ) - +func setup(tCtx ktesting.TContext, workloadResourceClaimsEnabled bool) *testContext { fakeClientset := fake.NewClientset() informerFactory := informers.NewSharedInformerFactory(fakeClientset, 0) controller := New(fakeClientset, informerFactory.Core().V1().Pods(), informerFactory.Resource().V1().ResourceClaims(), informerFactory.Resource().V1().ResourceSlices(), - informerFactory.Resource().V1alpha3().DeviceTaintRules(), + informerFactory.Resource().V1beta2().DeviceTaintRules(), informerFactory.Resource().V1().DeviceClasses(), "device-taint-eviction", + workloadResourceClaimsEnabled, ) tContext := &testContext{ TContext: tCtx, @@ -117,6 +137,8 @@ func setup(tCtx ktesting.TContext) *testContext { controller.eventLogger = &tContext.logger tContext.Controller.recorder = tContext.recorder + tCtx.ExpectNoError(controller.addIndexers()) + return tContext } @@ -133,7 +155,7 @@ type state struct { pods []*v1.Pod allocatedClaims []allocatedClaim slices []*resourceapi.ResourceSlice - rules []*resourcealpha.DeviceTaintRule + rules []*resourcebeta.DeviceTaintRule ruleStats map[types.UID]taintRuleStats // Pods might have been queued in the past and then not removed when removing from deletePodAt. @@ -146,7 +168,7 @@ type state struct { // step describes a state after handling ready work items and how much to move time forward. type step struct { pods []*v1.Pod - rules []*resourcealpha.DeviceTaintRule + rules []*resourcebeta.DeviceTaintRule ruleStats map[types.UID]taintRuleStats deletePodAt evictMap @@ -211,6 +233,8 @@ func assertEqual[T any](t interface { } type testCase struct { + workloadResourceClaimsEnabled bool + initialState state // events contains pairs of old and new objects which will @@ -249,11 +273,14 @@ func update[T any](oldObj, newObj *T) [2]*T { var ( podKind = v1.SchemeGroupVersion.WithKind("Pod") + podGroupKind = schedulingapi.SchemeGroupVersion.WithKind("PodGroup") nodeName = "worker" nodeName2 = "worker-2" driver = "some-driver" podName = "my-pod" podUID = "1234" + podGroupName = "my-podgroup" + podGroupUID = "5678" className = "my-resource-class" resourceName = "my-resource" claimName = podName + "-" + resourceName @@ -314,7 +341,7 @@ var ( slice := sliceTainted.DeepCopy() for i := range slice.Spec.Devices { for j := range slice.Spec.Devices[i].Taints { - slice.Spec.Devices[i].Taints[j].Effect = resourceapi.DeviceTaintEffect("unknown-effect") + slice.Spec.Devices[i].Taints[j].Effect = "unknown-effect" } } return slice @@ -355,95 +382,95 @@ var ( slice.Spec.Pool.Generation++ return slice }() - ruleEvict = &resourcealpha.DeviceTaintRule{ + ruleEvict = &resourcebeta.DeviceTaintRule{ ObjectMeta: metav1.ObjectMeta{ Name: "evict", UID: "1234", }, - Spec: resourcealpha.DeviceTaintRuleSpec{ - DeviceSelector: &resourcealpha.DeviceTaintSelector{ + Spec: resourcebeta.DeviceTaintRuleSpec{ + DeviceSelector: &resourcebeta.DeviceTaintSelector{ Driver: ptr.To(driver), }, - Taint: resourcealpha.DeviceTaint{ + Taint: resourcebeta.DeviceTaint{ Key: taint.Key, Value: taint.Value, - Effect: resourcealpha.DeviceTaintEffect(taint.Effect), + Effect: resourcebeta.DeviceTaintEffect(taint.Effect), TimeAdded: taint.TimeAdded, }, }, } - ruleEvictInstance1 = &resourcealpha.DeviceTaintRule{ + ruleEvictInstance1 = &resourcebeta.DeviceTaintRule{ ObjectMeta: metav1.ObjectMeta{ Name: "evict-instance", UID: "1234", }, - Spec: resourcealpha.DeviceTaintRuleSpec{ - DeviceSelector: &resourcealpha.DeviceTaintSelector{ + Spec: resourcebeta.DeviceTaintRuleSpec{ + DeviceSelector: &resourcebeta.DeviceTaintSelector{ Driver: ptr.To(driver), Device: ptr.To("instance"), }, - Taint: resourcealpha.DeviceTaint{ + Taint: resourcebeta.DeviceTaint{ Key: taint.Key, Value: taint.Value, - Effect: resourcealpha.DeviceTaintEffect(taint.Effect), + Effect: resourcebeta.DeviceTaintEffect(taint.Effect), TimeAdded: taintTime, }, }, } taintTimeLater = metav1Time(taintTime.Add(40 * time.Second)) - ruleEvictInstance2Later = &resourcealpha.DeviceTaintRule{ + ruleEvictInstance2Later = &resourcebeta.DeviceTaintRule{ ObjectMeta: metav1.ObjectMeta{ Name: "evict-instance-no-execute", UID: "5678", }, - Spec: resourcealpha.DeviceTaintRuleSpec{ - DeviceSelector: &resourcealpha.DeviceTaintSelector{ + Spec: resourcebeta.DeviceTaintRuleSpec{ + DeviceSelector: &resourcebeta.DeviceTaintSelector{ Driver: ptr.To(driver), Device: ptr.To("instance-no-execute"), }, - Taint: resourcealpha.DeviceTaint{ + Taint: resourcebeta.DeviceTaint{ Key: taint.Key, Value: taint.Value, - Effect: resourcealpha.DeviceTaintEffect(taint.Effect), + Effect: resourcebeta.DeviceTaintEffect(taint.Effect), TimeAdded: taintTimeLater, }, }, } - ruleNone = &resourcealpha.DeviceTaintRule{ + ruleNone = &resourcebeta.DeviceTaintRule{ ObjectMeta: metav1.ObjectMeta{ Name: "evict", UID: "1234", }, - Spec: resourcealpha.DeviceTaintRuleSpec{ - DeviceSelector: &resourcealpha.DeviceTaintSelector{ + Spec: resourcebeta.DeviceTaintRuleSpec{ + DeviceSelector: &resourcebeta.DeviceTaintSelector{ Driver: ptr.To(driver), }, - Taint: resourcealpha.DeviceTaint{ + Taint: resourcebeta.DeviceTaint{ Key: taint.Key, Value: taint.Value, - Effect: resourcealpha.DeviceTaintEffectNone, + Effect: resourcebeta.DeviceTaintEffectNone, TimeAdded: taint.TimeAdded, }, }, } - ruleEvictOther = &resourcealpha.DeviceTaintRule{ + ruleEvictOther = &resourcebeta.DeviceTaintRule{ ObjectMeta: metav1.ObjectMeta{ Name: "evict-other", UID: "1234-other", }, - Spec: resourcealpha.DeviceTaintRuleSpec{ - DeviceSelector: &resourcealpha.DeviceTaintSelector{ + Spec: resourcebeta.DeviceTaintRuleSpec{ + DeviceSelector: &resourcebeta.DeviceTaintSelector{ Device: ptr.To("instance"), }, - Taint: resourcealpha.DeviceTaint{ + Taint: resourcebeta.DeviceTaint{ Key: taint.Key, Value: taint.Value, - Effect: resourcealpha.DeviceTaintEffect(taint.Effect), + Effect: resourcebeta.DeviceTaintEffect(taint.Effect), TimeAdded: taint.TimeAdded, }, }, @@ -486,6 +513,11 @@ var ( Allocation(allocationResult). ReservedForPod(podName, types.UID(podUID)). Obj() + inUseClaimByPodGroup = st.FromResourceClaim(claim). + OwnerReference(podGroupName, podGroupUID, podGroupKind). + Allocation(allocationResult). + ReservedForPodGroup(podGroupName, types.UID(podGroupUID)). + Obj() inUseClaimOtherNamespace = st.FromResourceClaim(claim). Namespace(namespace+"-other"). OwnerReference(podName, podUID+"-2", podKind). // podWithClaimNameOtherNamespace below. @@ -530,6 +562,17 @@ var ( PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &claimName}). Node(nodeName). Obj() + podWithPodGroup = st.MakePod().Name(podName).Namespace(namespace). + UID(podUID). + Node(nodeName). + PodGroupName(podGroupName). + Obj() + podWithPodGroupClaim = st.MakePod().Name(podName).Namespace(namespace). + UID(podUID). + PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &claimName}). + Node(nodeName). + PodGroupName(podGroupName). + Obj() podWithTwoClaimNames = st.MakePod().Name(podName).Namespace(namespace). UID(podUID). PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &inUseClaim.Name}). @@ -656,7 +699,7 @@ func newEvictionTime(when *metav1.Time, args ...any) *evictionAndReason { case *resourceapi.ResourceSlice: reason = append(reason, trackedTaint{slice: sliceDeviceTaint{slice: obj, deviceName: args[i+1].(string), taintIndex: args[i+2].(int)}}) i += 3 - case *resourcealpha.DeviceTaintRule: + case *resourcebeta.DeviceTaintRule: reason = append(reason, trackedTaint{rule: obj}) i++ default: @@ -682,7 +725,7 @@ func newWorkItem(obj metav1.Object) workItem { ref := newObject(obj) var item workItem switch obj.(type) { - case *resourcealpha.DeviceTaintRule: + case *resourcebeta.DeviceTaintRule: item.ruleRef = ref case *v1.Pod: item.podRef = ref @@ -732,10 +775,10 @@ func listEvents(tCtx ktesting.TContext) []v1.Event { return events.Items } -func inProgress(rule *resourcealpha.DeviceTaintRule, status bool, reason, message string, when *metav1.Time) *resourcealpha.DeviceTaintRule { +func inProgress(rule *resourcebeta.DeviceTaintRule, status bool, reason, message string, when *metav1.Time) *resourcebeta.DeviceTaintRule { rule = rule.DeepCopy() condition := metav1.Condition{ - Type: resourcealpha.DeviceTaintConditionEvictionInProgress, + Type: resourcebeta.DeviceTaintConditionEvictionInProgress, Status: metav1.ConditionFalse, Reason: reason, Message: message, @@ -760,7 +803,10 @@ func inProgress(rule *resourcealpha.DeviceTaintRule, status bool, reason, messag // // Then pending work gets handled, potentially multiple times after // advancing time to reach "later" work items. -func TestController(t *testing.T) { +func TestController(t *testing.T) { testController(ktesting.Init(t)) } +func testController(tCtx ktesting.TContext) { + tCtx.Parallel() + for name, tc := range map[string]testCase{ "empty": {}, "populate-pools": { @@ -845,6 +891,35 @@ func TestController(t *testing.T) { }, wantEvents: l(deletePodEvent), }, + "evict-pod-resourceclaim-podgroup": { + workloadResourceClaimsEnabled: true, + events: []any{ + add(sliceTainted), + add(slice2), + add(inUseClaimByPodGroup), + add(podWithPodGroupClaim), + }, + finalState: state{ + slices: l(sliceTainted, slice2), + allocatedClaims: l(ac(inUseClaimByPodGroup, newEvictionTime(taintTime, sliceTainted, sliceTainted.Spec.Devices[0].Name, 0))), + deletePodAt: evictMap{newObject(podWithPodGroupClaim): *newEvictionTime(taintTime, sliceTainted, sliceTainted.Spec.Devices[0].Name, 0)}, + queued: MockState[workItem]{Ready: newWorkItems(podWithPodGroupClaim)}, + }, + wantEvents: l(deletePodEvent), + }, + "no-evict-pod-in-podgroup-without-claim": { + workloadResourceClaimsEnabled: true, + events: []any{ + add(sliceTainted), + add(slice2), + add(inUseClaimByPodGroup), + add(podWithPodGroup), + }, + finalState: state{ + slices: l(sliceTainted, slice2), + allocatedClaims: l(ac(inUseClaimByPodGroup, newEvictionTime(taintTime, sliceTainted, sliceTainted.Spec.Devices[0].Name, 0))), + }, + }, "evict-pod-rule": { events: []any{ add(inUseClaim), @@ -1036,6 +1111,26 @@ func TestController(t *testing.T) { }, }, }, + "none-podgroup-rule": { + workloadResourceClaimsEnabled: true, + events: []any{ + add(slice), + add(inUseClaimByPodGroup), + add(podWithPodGroupClaim), + add(ruleNone), + }, + finalState: state{ + slices: l(slice), + allocatedClaims: l(ac(inUseClaimByPodGroup)), + queued: MockState[workItem]{Ready: newWorkItems(ruleNone)}, + }, + process: []step{ + { + pods: l(podWithPodGroupClaim), + rules: l(inProgress(ruleNone, false, "NoEffect", "3 published devices selected. 1 allocated device selected. 1 pod would be evicted in 1 namespace if the effect was NoExecute. This information will not be updated again. Recreate the DeviceTaintRule to trigger an update.", taintTime)), + }, + }, + }, "multiple-claims-same-rule": { events: []any{ add(inUseClaim), @@ -1858,14 +1953,13 @@ func TestController(t *testing.T) { wantEvents: l(cancelPodEviction), }, } { - tCtx := ktesting.Init(t) - tCtx.Run(name, func(tCtx ktesting.TContext) { + tCtx.Parallel() numEvents := len(tc.events) if numEvents <= 1 { // No permutations. tCtx.SyncTest("", func(tCtx ktesting.TContext) { - tContext := setup(tCtx) + tContext := setup(tCtx, tc.workloadResourceClaimsEnabled) testHandlers(tContext, tc) }) return @@ -1883,9 +1977,12 @@ func TestController(t *testing.T) { tc := tc tc.events = events name := strings.Trim(fmt.Sprintf("%v", permutation), "[]") - tCtx.SyncTest(name, func(tCtx ktesting.TContext) { - tContext := setup(tCtx) - testHandlers(tContext, tc) + tCtx.Run(name, func(tCtx ktesting.TContext) { + tCtx.Parallel() + tCtx.SyncTest("", func(tCtx ktesting.TContext) { + tContext := setup(tCtx, tc.workloadResourceClaimsEnabled) + testHandlers(tContext, tc) + }) }) return } @@ -1934,7 +2031,7 @@ func testHandlers(tContext *testContext, tc testCase) { } return false, nil, nil }) - ruleStore := tContext.informerFactory.Resource().V1alpha3().DeviceTaintRules().Informer().GetStore() + ruleStore := tContext.informerFactory.Resource().V1beta2().DeviceTaintRules().Informer().GetStore() for _, rule := range tc.initialState.rules { tContext.ExpectNoError(ruleStore.Add(rule)) tContext.ExpectNoError(tContext.client.Tracker().Add(rule)) @@ -1961,12 +2058,12 @@ func testHandlers(tContext *testContext, tc testCase) { assertEqual(tContext, tc.finalState.allocatedClaimsAsMap(), tContext.allocatedClaims, "allocated claims") if !assertEqual(tContext, tc.finalState.slicesAsMap(), tContext.pools, "pools") { for key := range tContext.pools { - assert.Equal(tContext, tc.finalState.slicesAsMap()[key], tContext.pools[key], "pool") + tContext.Assert(tContext.pools[key]).Should(gomega.Equal(tc.finalState.slicesAsMap()[key]), "pool") } } assertEqual(tContext, tc.finalState.queued, tContext.mockQueue.State(), "work queue after event handlers", queueCmpOpts...) - assert.Empty(tContext, tc.finalState.pods, "pods not checked for final state") - assert.Empty(tContext, tc.finalState.rules, "rules not checked for final state") + tContext.Assert(tc.finalState.pods).Should(gomega.BeEmpty(), "pods not checked for final state") + tContext.Assert(tc.finalState.rules).Should(gomega.BeEmpty(), "rules not checked for final state") process := tc.process if process == nil && len(tc.finalState.queued.Ready) > 0 { @@ -1987,7 +2084,7 @@ func testHandlers(tContext *testContext, tc testCase) { pods, err := tContext.client.CoreV1().Pods("").List(tContext, metav1.ListOptions{}) tContext.ExpectNoError(err, prefix+"list pods") assertEqual(tContext, state.pods, trimPods(pods.Items), prefix+"pods after flushing work queue") - rules, err := tContext.client.ResourceV1alpha3().DeviceTaintRules().List(tContext, metav1.ListOptions{}) + rules, err := tContext.client.ResourceV1beta2().DeviceTaintRules().List(tContext, metav1.ListOptions{}) tContext.ExpectNoError(err, prefix+"list rules") actualRules := trimRules(rules.Items) assertEqual(tContext, state.rules, actualRules, prefix+"rules after flushing work queue") @@ -1996,7 +2093,6 @@ func testHandlers(tContext *testContext, tc testCase) { assertEqual(tContext, state.queuedProcessed, tContext.mockQueue.State(), prefix+"work queue after processing", queueCmpOpts...) time.Sleep(state.advance) for _, item := range tContext.mockQueue.State().Later { - fmt.Println(item.Item, item.Duration) tContext.mockQueue.CancelAfter(item.Item) tContext.mockQueue.AddAfter(item.Item, item.Duration-state.advance) } @@ -2045,28 +2141,32 @@ func applyEventPair(tContext *testContext, event any) { store := tContext.informerFactory.Core().V1().Pods().Informer().GetStore() switch { case pair[0] != nil && pair[1] != nil: - tContext.ExpectNoError(store.Update(pair[1])) - tContext.ExpectNoError(tContext.client.Tracker().Update(v1.SchemeGroupVersion.WithResource("pods"), pair[1], pair[1].Namespace)) + obj := pair[1].DeepCopy() + tContext.ExpectNoError(store.Update(obj)) + tContext.ExpectNoError(tContext.client.Tracker().Update(v1.SchemeGroupVersion.WithResource("pods"), obj, pair[1].Namespace)) case pair[0] != nil: tContext.ExpectNoError(store.Delete(pair[0])) tContext.ExpectNoError(tContext.client.Tracker().Delete(v1.SchemeGroupVersion.WithResource("pods"), pair[0].Namespace, pair[0].Name)) default: - tContext.ExpectNoError(store.Add(pair[1])) - tContext.ExpectNoError(tContext.client.Tracker().Add(pair[1])) + obj := pair[1].DeepCopy() + tContext.ExpectNoError(store.Add(obj)) + tContext.ExpectNoError(tContext.client.Tracker().Add(obj)) } tContext.handlePodChange(pair[0], pair[1]) - case [2]*resourcealpha.DeviceTaintRule: - store := tContext.informerFactory.Resource().V1alpha3().DeviceTaintRules().Informer().GetStore() + case [2]*resourcebeta.DeviceTaintRule: + store := tContext.informerFactory.Resource().V1beta2().DeviceTaintRules().Informer().GetStore() switch { case pair[0] != nil && pair[1] != nil: - tContext.ExpectNoError(store.Update(pair[1])) - tContext.ExpectNoError(tContext.client.Tracker().Update(resourcealpha.SchemeGroupVersion.WithResource("devicetaintrules"), pair[1], pair[1].Namespace)) + obj := pair[1].DeepCopy() + tContext.ExpectNoError(store.Update(obj)) + tContext.ExpectNoError(tContext.client.Tracker().Update(resourcebeta.SchemeGroupVersion.WithResource("devicetaintrules"), obj, pair[1].Namespace)) case pair[0] != nil: tContext.ExpectNoError(store.Delete(pair[0])) - tContext.ExpectNoError(tContext.client.Tracker().Delete(resourcealpha.SchemeGroupVersion.WithResource("devicetaintrules"), pair[0].Namespace, pair[0].Name)) + tContext.ExpectNoError(tContext.client.Tracker().Delete(resourcebeta.SchemeGroupVersion.WithResource("devicetaintrules"), pair[0].Namespace, pair[0].Name)) default: - tContext.ExpectNoError(store.Add(pair[1])) - tContext.ExpectNoError(tContext.client.Tracker().Add(pair[1])) + obj := pair[1].DeepCopy() + tContext.ExpectNoError(store.Add(obj)) + tContext.ExpectNoError(tContext.client.Tracker().Add(obj)) } tContext.handleRuleChange(pair[0], pair[1]) default: @@ -2083,7 +2183,7 @@ func trimPods(objs []v1.Pod) (trimmed []*v1.Pod) { return trimmed } -func trimRules(objs []resourcealpha.DeviceTaintRule) (trimmed []*resourcealpha.DeviceTaintRule) { +func trimRules(objs []resourcebeta.DeviceTaintRule) (trimmed []*resourcebeta.DeviceTaintRule) { for _, in := range objs { out := in.DeepCopy() out.ManagedFields = nil @@ -2094,105 +2194,66 @@ func trimRules(objs []resourcealpha.DeviceTaintRule) (trimmed []*resourcealpha.D return trimmed } -func newTestController(tCtx ktesting.TContext, clientSet *fake.Clientset) *Controller { - // fake.Clientset suffers from a race condition related to informers: - // it does not implement resource version support in its Watch - // implementation and instead assumes that watches are set up - // before further changes are made. - // - // If a test waits for caches to be synced and then immediately - // adds an object, that new object will never be seen by event handlers - // if the race goes wrong and the Watch call hadn't completed yet - // (can be triggered by adding a sleep before https://github.com/kubernetes/kubernetes/blob/b53b9fb5573323484af9a19cf3f5bfe80760abba/staging/src/k8s.io/client-go/tools/cache/reflector.go#L431). - // - // To work around this, we count all watches and only proceed when - // all of them are in place. This replaces the normal watch reactor - // (https://github.com/kubernetes/kubernetes/blob/b53b9fb5573323484af9a19cf3f5bfe80760abba/staging/src/k8s.io/client-go/kubernetes/fake/clientset_generated.go#L161-L173). - var numWatches atomic.Int32 - clientSet.PrependWatchReactor("*", func(action core.Action) (handled bool, ret watch.Interface, err error) { - var opts metav1.ListOptions - if watchActcion, ok := action.(core.WatchActionImpl); ok { - opts = watchActcion.ListOptions - } - gvr := action.GetResource() - ns := action.GetNamespace() - watch, err := clientSet.Tracker().Watch(gvr, ns, opts) - if err != nil { - return false, nil, err - } - numWatches.Add(1) - return true, watch, nil - }) - - informerFactory := informers.NewSharedInformerFactory(clientSet, 0) - - featuregatetesting.SetFeatureGatesDuringTest(tCtx, utilfeature.DefaultFeatureGate, - featuregatetesting.FeatureOverrides{ - features.DRADeviceTaints: true, - features.DRADeviceTaintRules: true, - }, - ) +func newTestController(tCtx ktesting.TContext) *Controller { + informerFactory := informers.NewSharedInformerFactory(tCtx.Client(), 0) controller := New(tCtx.Client(), informerFactory.Core().V1().Pods(), informerFactory.Resource().V1().ResourceClaims(), informerFactory.Resource().V1().ResourceSlices(), - informerFactory.Resource().V1alpha3().DeviceTaintRules(), + informerFactory.Resource().V1beta2().DeviceTaintRules(), informerFactory.Resource().V1().DeviceClasses(), "device-taint-eviction", + utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims), ) - controller.metrics = metrics.New(300 /* one large initial bucket for testing */) // TODO: inside a synctest bubble we should have deterministic delays and shouldn't need this trick. The remaining uncertainty comes from polling for informer cache sync. + controller.metrics = metrics.New() // Always log, not matter what the -v value is. logger := klog.FromContext(tCtx) controller.eventLogger = &logger - informerFactory.Start(tCtx.Done()) + informerFactory.StartWithContext(tCtx) tCtx.Cleanup(informerFactory.Shutdown) - tCtx.Log("starting to wait for watches") - if tCtx.IsSyncTest() { - tCtx.Wait() - require.Equal(tCtx, int32(5), numWatches.Load(), "All watches should be registered.") - } else { - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) int32 { - return numWatches.Load() - }).WithTimeout(5*time.Second).Should(gomega.Equal(int32(5)), "All watches should be registered.") - } - tCtx.Log("done waiting for watches") - return controller } -func testPodDeletionsMetrics(controller *Controller, total int) error { - // We cannot predict the sum of the latencies. Therefore we leave it at zero here - // and replace expected (>= 0) values when gathering. - expectedMetric := fmt.Sprintf(`# HELP device_taint_eviction_controller_pod_deletion_duration_seconds [ALPHA] Latency, in seconds, between the time when a device taint effect has been activated and a Pod's deletion via DeviceTaintEvictionController. +// testPodDeletionsMetrics verifies the metrics recorded by the controller based on +// the expected pod deletion latencies. Each entry in latencies represents one deleted pod. +func testPodDeletionsMetrics(controller *Controller, latencies ...time.Duration) error { + countLE := func(le float64) int { + total := 0 + for _, latency := range latencies { + if le == -1 || latency.Seconds() <= le { + total++ + } + } + return total + } + var totalDuration time.Duration + for _, latency := range latencies { + totalDuration += latency + } + + var expected strings.Builder + expected.WriteString(`# HELP device_taint_eviction_controller_pod_deletion_duration_seconds [ALPHA] Latency, in seconds, between the time when a device taint effect has been activated and a Pod's deletion via DeviceTaintEvictionController. # TYPE device_taint_eviction_controller_pod_deletion_duration_seconds histogram -device_taint_eviction_controller_pod_deletion_duration_seconds_bucket{le="300"} %[1]d -device_taint_eviction_controller_pod_deletion_duration_seconds_bucket{le="+Inf"} %[1]d -device_taint_eviction_controller_pod_deletion_duration_seconds_sum 0 -device_taint_eviction_controller_pod_deletion_duration_seconds_count %[1]d -# HELP device_taint_eviction_controller_pod_deletions_total [ALPHA] Total number of Pods deleted by DeviceTaintEvictionController since its start. +`) + for _, le := range []float64{0.005, 0.025, 0.1, 0.5, 1, 2.5, 10, 30, 60, 120, 180, 240} { + expected.WriteString(fmt.Sprintf("device_taint_eviction_controller_pod_deletion_duration_seconds_bucket{le=\"%f\"} %d\n", le, countLE(le))) + } + expected.WriteString(fmt.Sprintf("device_taint_eviction_controller_pod_deletion_duration_seconds_bucket{le=\"+Inf\"} %d\n", countLE(-1))) + expected.WriteString(fmt.Sprintf("device_taint_eviction_controller_pod_deletion_duration_seconds_sum %f\n", totalDuration.Seconds())) + expected.WriteString(fmt.Sprintf("device_taint_eviction_controller_pod_deletion_duration_seconds_count %d\n", len(latencies))) + expected.WriteString(fmt.Sprintf(`# HELP device_taint_eviction_controller_pod_deletions_total [ALPHA] Total number of Pods deleted by DeviceTaintEvictionController since its start. # TYPE device_taint_eviction_controller_pod_deletions_total counter -device_taint_eviction_controller_pod_deletions_total %[1]d -`, total) +device_taint_eviction_controller_pod_deletions_total %d +`, len(latencies))) + names := []string{ controller.metrics.PodDeletionsTotal.FQName(), controller.metrics.PodDeletionsLatency.FQName(), } - gather := func() ([]*metricstestutil.MetricFamily, error) { - got, err := controller.metrics.Gather() - for _, mf := range got { - for _, m := range mf.Metric { - if m.Histogram == nil || m.Histogram.SampleSum == nil || *m.Histogram.SampleSum < 0 { - continue - } - m.Histogram.SampleSum = ptr.To(float64(0)) - } - } - return got, err - } - return metricstestutil.GatherAndCompare(metricstestutil.GathererFunc(gather), strings.NewReader(expectedMetric), names...) + return metricstestutil.GatherAndCompare(controller.metrics, strings.NewReader(expected.String()), names...) } // TestEviction runs through the full flow of starting the controller and evicting one pod. @@ -2202,12 +2263,13 @@ device_taint_eviction_controller_pod_deletions_total %[1]d // // This runs in a bubble (https://pkg.go.dev/testing/synctest), so we can wait for goroutine // activity to settle down and then check the state. -func TestEviction(t *testing.T) { - tCtx := ktesting.Init(t) +func TestEviction(t *testing.T) { testEviction(ktesting.Init(t)) } +func testEviction(tCtx ktesting.TContext) { + tCtx.Parallel() + do := func(tCtx ktesting.TContext, what string, action func(tCtx ktesting.TContext) error) { tCtx.Log(what) - err := action(tCtx) - require.NoError(tCtx, err, what) + tCtx.ExpectNoError(action(tCtx)) } pod := podWithClaimName.DeepCopy() @@ -2295,7 +2357,7 @@ func TestEviction(t *testing.T) { tCtx.SyncTest(name, func(tCtx ktesting.TContext) { start := time.Now() fakeClientset := fake.NewClientset(tt.initialObjects...) - tCtx = ktesting.WithClients(tCtx, nil, nil, fakeClientset, nil, nil) + tCtx = tCtx.WithClients(nil, nil, fakeClientset, nil, nil) var podGets int var podUpdates int @@ -2305,25 +2367,25 @@ func TestEviction(t *testing.T) { fakeClientset.PrependReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { podGets++ podName := action.(core.GetAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of pod to patch") + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of pod to patch") return false, nil, nil }) fakeClientset.PrependReactor("patch", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { podUpdates++ podName := action.(core.PatchAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of pod to get") + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of pod to get") return false, nil, nil }) fakeClientset.PrependReactor("delete", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { podDeletions++ podName := action.(core.DeleteAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of pod to delete") + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of pod to delete") obj, err := fakeClientset.Tracker().Get(v1.SchemeGroupVersion.WithResource("pods"), pod.Namespace, pod.Name) - require.NoError(tCtx, err) + tCtx.ExpectNoError(err) updatedPod = obj.(*v1.Pod) return false, nil, nil }) - controller := newTestController(tCtx, fakeClientset) + controller := newTestController(tCtx) var wg sync.WaitGroup defer func() { @@ -2334,36 +2396,23 @@ func TestEviction(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - assert.NoError(tCtx, controller.Run(tCtx, 10 /* workers */), "eviction controller failed") + tCtx.AssertNoError(controller.Run(tCtx, 10 /* workers */), "eviction controller failed") }() // Eventually the controller should have synced it's informers. - if false { - // This feels like it should work (controller should run until it has started up, then block durably), but it doesn't. - // Time progresses while the controller is blocked in cache.WaitForNamedCacheSyncWithContext, so this is - // probably a good place to start looking. - // TODO: make "wait for cache sync" block on a channel. Alternatively, use a context and let `context.Cause` - // report success or failure (might be too hacky). + tCtx.Wait() + if controller.hasSynced.Load() <= 0 { + tCtx.Fatal("controller should have synced") + } + if tt.afterSync != nil { + tt.afterSync(tCtx) tCtx.Wait() - if controller.hasSynced.Load() <= 0 { - tCtx.Fatal("controller should have synced") - } - } else { - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) bool { - return controller.hasSynced.Load() > 0 - }).WithTimeout(30 * time.Second).Should(gomega.BeTrueBecause("controller synced")) - if tt.afterSync != nil { - tt.afterSync(tCtx) - } } - // We can wait for the controller to be idle. - tCtx.Wait() - // The number of API calls is deterministic. - assert.Equal(tCtx, 1, podGets, "get pod once") - assert.Equal(tCtx, 1, podUpdates, "update pod once") - assert.Equal(tCtx, 1, podDeletions, "delete pod once") + tCtx.Assert(podGets).Should(gomega.Equal(1), "get pod once") + tCtx.Assert(podUpdates).Should(gomega.Equal(1), "update pod once") + tCtx.Assert(podDeletions).Should(gomega.Equal(1), "delete pod once") _, err := fakeClientset.CoreV1().Pods(pod.Namespace).Get(tCtx, pod.Name, metav1.GetOptions{}) switch { @@ -2386,19 +2435,16 @@ func TestEviction(t *testing.T) { tCtx.Errorf("unexpected modified pod (-want, +got):\n%s", diff) } + // Timing is perfectly predictable. + tCtx.Expect(time.Since(start)).To(gomega.Equal(0*time.Second), "time to evict pod") + // Shortly after deletion we should also see updated metrics. // This is the last thing the controller does for a pod. - // Because of Wait we know that all goroutines are durably blocked and won't - // wake up again to change the metrics => no need for a "Consistently"! - gomega.NewWithT(tCtx).Expect(listEvents(tCtx)).Should(matchDeletionEvent()) - tCtx.ExpectNoError(testPodDeletionsMetrics(controller, 1), "pod eviction done") - - // Depending on timing, some of the "wait for cache synced" polling sleep a bit more or less, - // so there is a certain delta of uncertainty about the overall duration. Without that polling - // we probably could assert zero runtime here. - tCtx.Logf("eviction duration: %s", time.Since(start)) - delta := time.Second - require.WithinRange(tCtx, time.Now(), start, start.Add(delta), "time to evict pod") + // We know that all goroutines are durably blocked and + // queues are empty => no need for a "Consistently"! + tCtx.Assert(controller.workqueue.Len()).Should(gomega.Equal(0), "work queue empty") + tCtx.Assert(listEvents(tCtx)).Should(matchDeletionEvent()) + tCtx.ExpectNoError(testPodDeletionsMetrics(controller, 0*time.Second), "pod eviction done") }) } } @@ -2408,16 +2454,38 @@ func TestEviction(t *testing.T) { // // This runs in a bubble (https://pkg.go.dev/testing/synctest), so we can wait for goroutine // activity to settle down and then check the state. -func TestDeviceTaintRule(t *testing.T) { - tCtx := ktesting.Init(t) - tCtx.SyncTest("", testDeviceTaintRule) -} - +func TestDeviceTaintRule(t *testing.T) { testDeviceTaintRule(ktesting.Init(t)) } func testDeviceTaintRule(tCtx ktesting.TContext) { + tCtx.Parallel() + tCtx.SyncTest("immediate", func(tCtx ktesting.TContext) { synctestDeviceTaintRule(tCtx, false, false) }) + tCtx.SyncTest("delayed", func(tCtx ktesting.TContext) { synctestDeviceTaintRule(tCtx, true, false) }) + tCtx.SyncTest("slow", func(tCtx ktesting.TContext) { synctestDeviceTaintRule(tCtx, true, true) }) +} +func synctestDeviceTaintRule(tCtx ktesting.TContext, toleration, slowDelete bool) { rule := ruleNone.DeepCopy() - fakeClientset := fake.NewClientset(podWithClaimName, inUseClaim, rule) - tCtx = ktesting.WithClients(tCtx, nil, nil, fakeClientset, nil, nil) - controller := newTestController(tCtx, fakeClientset) + claim := inUseClaim.DeepCopy() + tolerationSeconds := int64(0) + if toleration { + tolerationSeconds = 30 + claim.Status.Allocation.Devices.Results[0].Tolerations = []resourceapi.DeviceToleration{{ + Effect: resourceapi.DeviceTaintEffectNoExecute, + Value: rule.Spec.Taint.Value, + TolerationSeconds: &tolerationSeconds, + }} + } + fakeClientset := fake.NewClientset(podWithClaimName, claim, rule) + blockDelete := make(chan struct{}) + client := clientset.Interface(fakeClientset) + if slowDelete { + client = &myFakeClient{client, func() { + // This coordinates with the main goroutine to move forward after a certain delay. + tCtx.Logf("Delaying pod deletion") + <-blockDelete + tCtx.Logf("Proceeding with pod deletion") + }} + } + tCtx = tCtx.WithClients(nil, nil, client, nil, nil) + controller := newTestController(tCtx) var wg sync.WaitGroup defer func() { @@ -2428,65 +2496,80 @@ func testDeviceTaintRule(tCtx ktesting.TContext) { wg.Go(func() { // Run with 1 worker to ensure sequential execution. Concurrent workers cause // non-deterministic ordering of status updates, leading to flakes in Status assertions. - assert.NoError(tCtx, controller.Run(tCtx, 1 /* workers */), "eviction controller failed") + tCtx.AssertNoError(controller.Run(tCtx, 1 /* workers */), "eviction controller failed") }) // Eventually the controller should have synced it's informers. - if false { - // This feels like it should work (controller should run until it has started up, then block durably), but it doesn't. - // Time progresses while the controller is blocked in cache.WaitForNamedCacheSyncWithContext, so this is - // probably a good place to start looking. - // TODO: make "wait for cache sync" block on a channel. Alternatively, use a context and let `context.Cause` - // report success or failure (might be too hacky). - tCtx.Wait() - if controller.hasSynced.Load() <= 0 { - tCtx.Fatal("controller should have synced") - } - } else { - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) bool { - return controller.hasSynced.Load() > 0 - }).WithTimeout(30 * time.Second).Should(gomega.BeTrueBecause("controller synced")) + tCtx.Wait() + if controller.hasSynced.Load() <= 0 { + tCtx.Fatal("controller should have synced") } - // We can wait for the controller to be idle. - tCtx.Wait() start := metav1.Now() - tCtx.Logf("TIME: start at %s", start) + tCtx.Logf("TIME: start") check(tCtx, "initial processing: ", l(inProgress(ruleNone, false, "NoEffect", "0 published devices selected. 1 allocated device selected. 1 pod would be evicted in 1 namespace if the effect was NoExecute. This information will not be updated again. Recreate the DeviceTaintRule to trigger an update.", &start /* processed before waiting for cache sync completion */)), l(podWithClaimName)) // Move time forward to ensure that we get different time stamps. time.Sleep(20 * time.Second) - rule.Spec.Taint.Effect = resourcealpha.DeviceTaintEffectNoExecute - rule, err := tCtx.Client().ResourceV1alpha3().DeviceTaintRules().Update(tCtx, rule, metav1.UpdateOptions{}) + updated := metav1.Now() + rule = rule.DeepCopy() // fake.NewClientset does not copy! Perhaps it should?! + rule.Spec.Taint.Effect = resourcebeta.DeviceTaintEffectNoExecute + // The real apiserver is going to bump this automatically in 1.36, + // but in a unit test we have to do it manually. + rule.Spec.Taint.TimeAdded = &updated + rule, err := tCtx.Client().ResourceV1beta2().DeviceTaintRules().Update(tCtx, rule, metav1.UpdateOptions{}) tCtx.ExpectNoError(err, "update rule") // Wait for eviction. tCtx.Wait() - evicted := metav1.Now() - tCtx.Logf("TIME: eviction done at %s", evicted) + tCtx.Logf("TIME: eviction %s since start", updated.Sub(start.Time)) // The rule status got updated once before evicting pods, but not yet after evicting it. - check(tCtx, "evict: ", l(inProgress(rule, true, "PodsPendingEviction", "1 pod needs to be evicted in 1 namespace.", &evicted)), nil) + var expectedPods []*v1.Pod + if tolerationSeconds > 0 { + // Eviction is delayed, pod not deleted yet. + expectedPods = append(expectedPods, podWithClaimName) + } + check(tCtx, "evict: ", l(inProgress(rule, true, "PodsPendingEviction", "1 pod needs to be evicted in 1 namespace.", &updated)), expectedPods) + + var slowDeleteDelay time.Duration + if tolerationSeconds > 0 { + // Need to move forward in time past the delay(s) + time.Sleep(time.Duration(tolerationSeconds) * time.Second) + if slowDelete { + // This is going to show up in the histogram. + slowDeleteDelay = 3 * time.Second + time.Sleep(slowDeleteDelay) + close(blockDelete) + } + tCtx.Wait() + // Now the pod is deleted. Status gets updated later. + check(tCtx, "evict: ", l(inProgress(rule, true, "PodsPendingEviction", "1 pod needs to be evicted in 1 namespace.", &updated)), nil) + } // AddAfter does not move time forward. Do it ourselves... time.Sleep(ruleStatusPeriod) slept := metav1.Now() - tCtx.Logf("TIME: slept till %s", slept) + tCtx.Logf("TIME: slept %s since start", slept.Sub(start.Time)) tCtx.Wait() done := metav1.Now() - tCtx.Logf("TIME: done at %s", done) + tCtx.Logf("TIME: done %s since start", done.Sub(start.Time)) check(tCtx, "done: ", l(inProgress(rule, false, "Completed", "1 pod evicted since starting the controller.", &done)), nil) assertEqual(tCtx, map[types.UID]taintRuleStats{rule.UID: {numEvictedPods: 1}}, controller.taintRuleStats, "taint rule statistics should have counted the pod") // Delete the rule and verify that we don't leak memory by still tracking it. - err = tCtx.Client().ResourceV1alpha3().DeviceTaintRules().Delete(tCtx, rule.Name, metav1.DeleteOptions{}) + err = tCtx.Client().ResourceV1beta2().DeviceTaintRules().Delete(tCtx, rule.Name, metav1.DeleteOptions{}) tCtx.ExpectNoError(err, "delete rule") tCtx.Wait() deleted := metav1.Now() - tCtx.Logf("TIME: deleted at %s", deleted) - assert.Empty(tCtx, controller.taintRuleStats, "taint rule statistics should have dropped the deleted rule") + tCtx.Logf("TIME: deleted %s since start", deleted.Sub(start.Time)) + tCtx.Assert(controller.taintRuleStats).Should(gomega.BeEmpty(), "taint rule statistics should have dropped the deleted rule") + + tCtx.Assert(controller.workqueue.Len()).Should(gomega.Equal(0), "work queue empty") + // There are no delays inside a synctest bubble. The pod always gets deleted "immediately". + tCtx.ExpectNoError(testPodDeletionsMetrics(controller, slowDeleteDelay)) } -func check(tCtx ktesting.TContext, prefix string, expectRules []*resourcealpha.DeviceTaintRule, expectPods []*v1.Pod) { +func check(tCtx ktesting.TContext, prefix string, expectRules []*resourcebeta.DeviceTaintRule, expectPods []*v1.Pod) { tCtx.Helper() opts := []cmp.Option{ @@ -2501,20 +2584,55 @@ func check(tCtx ktesting.TContext, prefix string, expectRules []*resourcealpha.D actualPods, err := tCtx.Client().CoreV1().Pods("").List(tCtx, metav1.ListOptions{}) tCtx.ExpectNoError(err, prefix+"list pods") assertEqual(tCtx, expectPods, trimPods(actualPods.Items), prefix+"pods", opts...) - rules, err := tCtx.Client().ResourceV1alpha3().DeviceTaintRules().List(tCtx, metav1.ListOptions{}) + rules, err := tCtx.Client().ResourceV1beta2().DeviceTaintRules().List(tCtx, metav1.ListOptions{}) tCtx.ExpectNoError(err, prefix+"list rules") assertEqual(tCtx, expectRules, trimRules(rules.Items), prefix+"rules", opts...) } +// myFakeClient and it's children have a single purpose: inject a callback into +// CoreV1().Pods().Delete. We cannot use fakeClientset.PrependReactor for that +// because it invokes the callback while holding the fake's mutex. If any other +// goroutine then also makes an API call, a synctest bubble deadlocks because +// holding the mutex is not considered "durably blocking" even though it is +// in our case. +type myFakeClient struct { + clientset.Interface + deletePodCallback func() +} + +func (mf *myFakeClient) CoreV1() corev1.CoreV1Interface { + return &myFakeCoreV1{mf.Interface.CoreV1(), mf.deletePodCallback} +} + +type myFakeCoreV1 struct { + corev1.CoreV1Interface + deletePodCallback func() +} + +func (mf *myFakeCoreV1) Pods(namespace string) corev1.PodInterface { + return &myFakePods{mf.CoreV1Interface.Pods(namespace), mf.deletePodCallback} +} + +type myFakePods struct { + corev1.PodInterface + deletePodCallback func() +} + +func (mf *myFakePods) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + mf.deletePodCallback() + return mf.PodInterface.Delete(ctx, name, opts) +} + // TestCancelEviction deletes the pod before the controller deletes it // or removes the slice. Either way, eviction gets cancelled. -func TestCancelEviction(t *testing.T) { - tCtx := ktesting.Init(t) - tCtx.SyncTest("pod-deleted", func(tCtx ktesting.TContext) { testCancelEviction(tCtx, true) }) - tCtx.SyncTest("slice-deleted", func(tCtx ktesting.TContext) { testCancelEviction(tCtx, false) }) +func TestCancelEviction(t *testing.T) { testCancelEviction(ktesting.Init(t)) } +func testCancelEviction(tCtx ktesting.TContext) { + tCtx.Parallel() + tCtx.SyncTest("pod-deleted", func(tCtx ktesting.TContext) { doCancelEviction(tCtx, true) }) + tCtx.SyncTest("slice-deleted", func(tCtx ktesting.TContext) { doCancelEviction(tCtx, false) }) } -func testCancelEviction(tCtx ktesting.TContext, deletePod bool) { +func doCancelEviction(tCtx ktesting.TContext, deletePod bool) { // The claim tolerates the taint long enough for us to // do something which cancels eviction. pod := podWithClaimName.DeepCopy() @@ -2533,8 +2651,8 @@ func testCancelEviction(tCtx ktesting.TContext, deletePod bool) { pod, ) pod, err := fakeClientset.CoreV1().Pods(pod.Namespace).Get(tCtx, pod.Name, metav1.GetOptions{}) - require.NoError(tCtx, err, "get pod before eviction") - assert.Equal(tCtx, podWithClaimName, pod, "test pod") + tCtx.ExpectNoError(err, "get pod before eviction") + tCtx.Assert(pod).Should(gomega.Equal(podWithClaimName), "test pod") var podGets int var podUpdates int @@ -2543,37 +2661,32 @@ func testCancelEviction(tCtx ktesting.TContext, deletePod bool) { fakeClientset.PrependReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { podGets++ podName := action.(core.GetAction).GetName() - assert.Equal(tCtx, podWithClaimName.Name, podName, "name of pod to patch") + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of pod to patch") return false, nil, nil }) fakeClientset.PrependReactor("patch", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { podUpdates++ podName := action.(core.PatchAction).GetName() - assert.Equal(tCtx, podWithClaimName.Name, podName, "name of pod to get") + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of pod to get") return false, nil, nil }) fakeClientset.PrependReactor("delete", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { podDeletions++ podName := action.(core.DeleteAction).GetName() - assert.Equal(tCtx, podWithClaimName.Name, podName, "name of pod to delete") + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of pod to delete") return false, nil, nil }) - tCtx = ktesting.WithClients(tCtx, nil, nil, fakeClientset, nil, nil) - controller := newTestController(tCtx, fakeClientset) + tCtx = tCtx.WithClients(nil, nil, fakeClientset, nil, nil) + controller := newTestController(tCtx) - var mutex sync.Mutex podEvicting := false controller.evictPodHook = func(podRef tainteviction.NamespacedObject, eviction evictionAndReason) { - assert.Equal(tCtx, newObject(pod), podRef) - mutex.Lock() - defer mutex.Unlock() + tCtx.Assert(podRef).Should(gomega.Equal(newObject(pod))) podEvicting = true } controller.cancelEvictHook = func(podRef tainteviction.NamespacedObject) bool { - assert.Equal(tCtx, newObject(pod), podRef) - mutex.Lock() - defer mutex.Unlock() + tCtx.Assert(podRef).Should(gomega.Equal(newObject(pod))) podEvicting = false return false } @@ -2587,15 +2700,12 @@ func testCancelEviction(tCtx ktesting.TContext, deletePod bool) { wg.Add(1) go func() { defer wg.Done() - assert.NoError(tCtx, controller.Run(tCtx, 10 /* workers */), "eviction controller failed") + tCtx.AssertNoError(controller.Run(tCtx, 10 /* workers */), "eviction controller failed") }() // Eventually the pod gets scheduled for eviction. - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) bool { - mutex.Lock() - defer mutex.Unlock() - return podEvicting - }).WithTimeout(30 * time.Second).Should(gomega.BeTrueBecause("pod pending eviction")) + tCtx.Wait() + tCtx.Expect(podEvicting).To(gomega.BeTrueBecause("pod pending eviction")) // Now we can delete the pod or slice. if deletePod { @@ -2605,191 +2715,177 @@ func testCancelEviction(tCtx ktesting.TContext, deletePod bool) { } // Shortly after deletion we should also see the cancellation. - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) bool { - mutex.Lock() - defer mutex.Unlock() - return podEvicting - }).WithTimeout(30 * time.Second).Should(gomega.BeFalseBecause("pod no longer pending eviction")) + tCtx.Wait() + tCtx.Expect(podEvicting).To(gomega.BeFalseBecause("pod no longer pending eviction")) // Whether we get an event depends on whether the pod still exists. - // If we expect an event, we need to wait for it. if !deletePod { - ktesting.Eventually(tCtx, listEvents).WithTimeout(30 * time.Second).Should(matchCancellationEvent()) + tCtx.Expect(listEvents(tCtx)).Should(matchCancellationEvent()) } - tCtx.Wait() matchEvents := matchCancellationEvent() if deletePod { matchEvents = gomega.BeEmpty() - assert.Equal(tCtx, 1, podDeletions, "Pod should have been deleted exactly once by test.") + tCtx.Assert(podDeletions).Should(gomega.Equal(1), "Pod should have been deleted exactly once by test.") } else { - assert.Equal(tCtx, 0, podDeletions, "Pod should not have been deleted.") + tCtx.Assert(podDeletions).Should(gomega.Equal(0), "Pod should not have been deleted.") } - // Naively (?) one could expect synctest.Wait to have blocked until the work item added via AddAfter - // got processed because before that the overall state isn't stable yet. But the workqueue package - // seems to implement AddAfter in a way which is not detected as "blocking on time to pass" by - // by synctest and therefore it returns without advancing time enough. - // - // Here we trigger that manually as a workaround (?). The factor doesn't really matter. - // Commenting this out causes the controller.maybeDeletePodCount check to fail. + // Time does not progress in synctest.Wait because it returns when all goroutines are blocked + // waiting for time to pass (for example, in a time.Sleep). + // To flush the workqueue, we have to move time forward by enough time to get past + // the delay of the AddAfter. time.Sleep(10 * time.Duration(tolerationSeconds) * time.Second) tCtx.Wait() - assert.Equal(tCtx, 0, podGets, "Worker should not have needed to get the pod.") - assert.Equal(tCtx, 0, podUpdates, "Worker should not have needed to update the pod.") - assert.Equal(tCtx, 0, controller.workqueue.Len(), "Work queue should be empty now.") - assert.Equal(tCtx, int64(1), controller.maybeDeletePodCount, "Work queue should have processed pod.") - gomega.NewWithT(tCtx).Expect(listEvents(tCtx)).Should(matchEvents) - tCtx.ExpectNoError(testPodDeletionsMetrics(controller, 0)) + tCtx.Assert(controller.workqueue.Len()).Should(gomega.Equal(0), "work queue empty") + gomega.NewGomegaWithT(tCtx).Expect(controller.workqueue.Len()).Should(gomega.Equal(0), "work queue empty") + tCtx.Assert(podGets).Should(gomega.Equal(0), "Worker should not have needed to get the pod.") + tCtx.Assert(podUpdates).Should(gomega.Equal(0), "Worker should not have needed to update the pod.") + tCtx.Assert(controller.maybeDeletePodCount).Should(gomega.Equal(int64(1)), "Work queue should have processed pod.") + tCtx.Assert(listEvents(tCtx)).Should(matchEvents) + tCtx.ExpectNoError(testPodDeletionsMetrics(controller)) } // TestParallelPodDeletion covers the scenario that a pod gets deleted right before // trying to evict it. -func TestParallelPodDeletion(t *testing.T) { - tCtx := ktesting.Init(t) +func TestParallelPodDeletion(t *testing.T) { testParallelPodDeletion(ktesting.Init(t)) } +func testParallelPodDeletion(tCtx ktesting.TContext) { tCtx.Parallel() + tCtx.SyncTest("", synctestParallelPodDeletion) +} +func synctestParallelPodDeletion(tCtx ktesting.TContext) { + // This scenario is the same as "evict-pod-resourceclaim" above. + pod := podWithClaimName.DeepCopy() + fakeClientset := fake.NewClientset( + sliceTainted, + slice2, + inUseClaim, + pod, + ) + tCtx = tCtx.WithClients(nil, nil, fakeClientset, nil, nil) - tCtx.SyncTest("", func(tCtx ktesting.TContext) { - // This scenario is the same as "evict-pod-resourceclaim" above. - pod := podWithClaimName.DeepCopy() - fakeClientset := fake.NewClientset( - sliceTainted, - slice2, - inUseClaim, - pod, - ) - tCtx = ktesting.WithClients(tCtx, nil, nil, fakeClientset, nil, nil) - - pod, err := fakeClientset.CoreV1().Pods(pod.Namespace).Get(tCtx, pod.Name, metav1.GetOptions{}) - require.NoError(tCtx, err, "get pod before eviction") - assert.Equal(tCtx, podWithClaimName, pod, "test pod") - - var mutex sync.Mutex - var podGets int - var podDeletions int - - fakeClientset.PrependReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { - mutex.Lock() - defer mutex.Unlock() - podGets++ - podName := action.(core.GetAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of patched pod") - - // This gets called directly before eviction. Pretend that it is deleted. - err = fakeClientset.Tracker().Delete(v1.SchemeGroupVersion.WithResource("pods"), pod.Namespace, pod.Name) - assert.NoError(tCtx, err, "delete pod") //nolint:testifylint // Here recording an unknown error and continuing is okay. - return true, nil, apierrors.NewNotFound(v1.SchemeGroupVersion.WithResource("pods").GroupResource(), pod.Name) - }) - fakeClientset.PrependReactor("delete", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { - mutex.Lock() - defer mutex.Unlock() - podDeletions++ - podName := action.(core.DeleteAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of deleted pod") - return false, nil, nil - }) - controller := newTestController(tCtx, fakeClientset) - - var wg sync.WaitGroup - defer func() { - tCtx.Log("Waiting for goroutine termination...") - tCtx.Cancel("time to stop") - wg.Wait() - }() - wg.Add(1) - go func() { - defer wg.Done() - assert.NoError(tCtx, controller.Run(tCtx, 10 /* workers */), "eviction controller failed") - }() - - // Eventually the pod gets deleted, in this test by us. - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) bool { - mutex.Lock() - defer mutex.Unlock() - return podGets >= 1 - }).WithTimeout(30 * time.Second).Should(gomega.BeTrueBecause("pod eviction started")) - - // We don't want any events. - tCtx.Wait() - assert.Equal(tCtx, 1, podGets, "number of pod get calls") - assert.Equal(tCtx, 0, podDeletions, "number of pod delete calls") - gomega.NewWithT(tCtx).Expect(listEvents(tCtx)).Should(gomega.BeEmpty()) - tCtx.ExpectNoError(testPodDeletionsMetrics(controller, 0)) + pod, err := fakeClientset.CoreV1().Pods(pod.Namespace).Get(tCtx, pod.Name, metav1.GetOptions{}) + tCtx.ExpectNoError(err, "get pod before eviction") + tCtx.Assert(pod).Should(gomega.Equal(podWithClaimName), "test pod") + + var podGets int + var podDeletions int + + fakeClientset.PrependReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + podGets++ + podName := action.(core.GetAction).GetName() + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of patched pod") + + // This gets called directly before eviction. Pretend that it is deleted. + err = fakeClientset.Tracker().Delete(v1.SchemeGroupVersion.WithResource("pods"), pod.Namespace, pod.Name) + tCtx.AssertNoError(err, "delete pod") + return true, nil, apierrors.NewNotFound(v1.SchemeGroupVersion.WithResource("pods").GroupResource(), pod.Name) }) + fakeClientset.PrependReactor("delete", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + podDeletions++ + podName := action.(core.DeleteAction).GetName() + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of deleted pod") + return false, nil, nil + }) + controller := newTestController(tCtx) + + var wg sync.WaitGroup + defer func() { + tCtx.Log("Waiting for goroutine termination...") + tCtx.Cancel("time to stop") + wg.Wait() + }() + wg.Add(1) + go func() { + defer wg.Done() + tCtx.AssertNoError(controller.Run(tCtx, 10 /* workers */), "eviction controller failed") + }() + + // We don't want any events. + tCtx.Wait() + tCtx.Assert(podGets).Should(gomega.Equal(1), "number of pod get calls") + tCtx.Assert(podDeletions).Should(gomega.Equal(0), "number of pod delete calls") + tCtx.Assert(controller.workqueue.Len()).Should(gomega.Equal(0), "Work queue should be empty now.") + tCtx.Assert(listEvents(tCtx)).Should(gomega.BeEmpty()) + tCtx.ExpectNoError(testPodDeletionsMetrics(controller)) } // TestRetry covers the scenario that an eviction attempt must be retried. -func TestRetry(t *testing.T) { - tCtx := ktesting.Init(t) - - tCtx.SyncTest("", func(tCtx ktesting.TContext) { - // This scenario is the same as "evict-pod-resourceclaim" above. - pod := podWithClaimName.DeepCopy() - fakeClientset := fake.NewClientset( - sliceTainted, - slice2, - inUseClaim, - pod, - ) - tCtx = ktesting.WithClients(tCtx, nil, nil, fakeClientset, nil, nil) - - pod, err := fakeClientset.CoreV1().Pods(pod.Namespace).Get(tCtx, pod.Name, metav1.GetOptions{}) - require.NoError(tCtx, err, "get pod before eviction") - assert.Equal(tCtx, podWithClaimName, pod, "test pod") - - var mutex sync.Mutex - var podGets int - var podDeletions int - - fakeClientset.PrependReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { - mutex.Lock() - defer mutex.Unlock() - podGets++ - podName := action.(core.GetAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of patched pod") - - // This gets called directly before eviction. Pretend that there is an intermittent error. - if podGets == 1 { - return true, nil, apierrors.NewInternalError(errors.New("fake error")) - } - return false, nil, nil - }) - fakeClientset.PrependReactor("delete", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { - mutex.Lock() - defer mutex.Unlock() - podDeletions++ - podName := action.(core.DeleteAction).GetName() - assert.Equal(t, podWithClaimName.Name, podName, "name of deleted pod") - return false, nil, nil - }) - controller := newTestController(tCtx, fakeClientset) - - var wg sync.WaitGroup - defer func() { - t.Log("Waiting for goroutine termination...") - tCtx.Cancel("time to stop") - wg.Wait() - }() - wg.Add(1) - go func() { - defer wg.Done() - assert.NoError(tCtx, controller.Run(tCtx, 10 /* workers */), "eviction controller failed") - }() - - // Eventually the pod gets deleted and the event is recorded. - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) error { - gomega.NewWithT(tCtx).Expect(listEvents(tCtx)).Should(matchDeletionEvent()) - return testPodDeletionsMetrics(controller, 1) - }).WithTimeout(30*time.Second).Should(gomega.Succeed(), "pod eviction done") - - // Now we can check the API calls. - tCtx.Wait() - assert.Equal(tCtx, 2, podGets, "number of pod get calls") - assert.Equal(tCtx, 1, podDeletions, "number of pod delete calls") - gomega.NewWithT(tCtx).Expect(listEvents(tCtx)).Should(matchDeletionEvent()) - tCtx.ExpectNoError(testPodDeletionsMetrics(controller, 1)) +func TestRetry(t *testing.T) { testRetry(ktesting.Init(t)) } +func testRetry(tCtx ktesting.TContext) { + tCtx.Parallel() + tCtx.SyncTest("", synctestRetry) +} +func synctestRetry(tCtx ktesting.TContext) { + // This scenario is the same as "evict-pod-resourceclaim" above. + pod := podWithClaimName.DeepCopy() + fakeClientset := fake.NewClientset( + sliceTainted, + slice2, + inUseClaim, + pod, + ) + tCtx = tCtx.WithClients(nil, nil, fakeClientset, nil, nil) + + pod, err := fakeClientset.CoreV1().Pods(pod.Namespace).Get(tCtx, pod.Name, metav1.GetOptions{}) + tCtx.ExpectNoError(err, "get pod before eviction") + tCtx.Assert(pod).Should(gomega.Equal(podWithClaimName), "test pod") + + var mutex sync.Mutex + var podGets int + var podDeletions int + + fakeClientset.PrependReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + mutex.Lock() + defer mutex.Unlock() + podGets++ + podName := action.(core.GetAction).GetName() + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of patched pod") + + // This gets called directly before eviction. Pretend that there is an intermittent error. + if podGets == 1 { + return true, nil, apierrors.NewInternalError(errors.New("fake error")) + } + return false, nil, nil }) + fakeClientset.PrependReactor("delete", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + mutex.Lock() + defer mutex.Unlock() + podDeletions++ + podName := action.(core.DeleteAction).GetName() + tCtx.Assert(podName).Should(gomega.Equal(podWithClaimName.Name), "name of deleted pod") + return false, nil, nil + }) + controller := newTestController(tCtx) + + var wg sync.WaitGroup + defer func() { + tCtx.Log("Waiting for goroutine termination...") + tCtx.Cancel("time to stop") + wg.Wait() + }() + wg.Add(1) + go func() { + defer wg.Done() + tCtx.AssertNoError(controller.Run(tCtx, 10 /* workers */), "eviction controller failed") + }() + + expectLatencies := []time.Duration{5 * time.Millisecond /* default exponential retry */} + + // Eventually the pod gets deleted and the event is recorded. + tCtx.Eventually(func(tCtx ktesting.TContext) error { + tCtx.Expect(listEvents(tCtx)).To(matchDeletionEvent()) + return testPodDeletionsMetrics(controller, expectLatencies...) + }).WithTimeout(30*time.Second).Should(gomega.Succeed(), "pod eviction done") + + // Now we can check the API calls. + tCtx.Wait() + tCtx.Assert(podGets).Should(gomega.Equal(2), "number of pod get calls") + tCtx.Assert(podDeletions).Should(gomega.Equal(1), "number of pod delete calls") + tCtx.Assert(listEvents(tCtx)).Should(matchDeletionEvent()) + tCtx.Assert(controller.workqueue.Len()).Should(gomega.Equal(0), "Work queue should be empty now.") + tCtx.ExpectNoError(testPodDeletionsMetrics(controller, expectLatencies...)) } // BenchTaintUntaint checks the full flow of detecting a claim as @@ -2797,32 +2893,33 @@ func TestRetry(t *testing.T) { // consumer, and then undoing that when the DeviceTaintRule is removed. func BenchmarkTaintUntaint(b *testing.B) { tCtx := ktesting.Init(b) - tContext := setup(tCtx) + tContext := setup(tCtx, false) // Disable DRAWorkloadResourceClaims because it's alpha podStore := tContext.informerFactory.Core().V1().Pods().Informer().GetStore() // No output, comment out if output is desired. tContext.Controller.eventLogger = nil - b.ResetTimer() - for i := 0; i < b.N; i++ { + // Condition must be exactly "b.Loop" to ensure that the special support + // in the compiler for benchmarks is active. + for b.Loop() { // Add objects... tContext.handleSliceChange(nil, slice) tContext.handleClaimChange(nil, inUseClaimWithToleration) - require.NoError(tContext, podStore.Add(podWithClaimName), "add pod") + tContext.ExpectNoError(podStore.Add(podWithClaimName), "add pod") tContext.handlePodChange(nil, podWithClaimName) - require.Empty(tContext, tContext.deletePodAt) + tContext.Expect(tContext.deletePodAt).To(gomega.BeEmpty()) // Now evict. tContext.handleSliceChange(slice, sliceTainted) // Because informer event handlers are synchronous, we get the expected result immediately. - require.NotEmpty(tContext, tContext.deletePodAt) + tContext.Expect(tContext.deletePodAt).NotTo(gomega.BeEmpty()) // ... and remove them again. tContext.handleSliceChange(sliceTainted, slice) - require.Empty(tContext, tContext.deletePodAt) + tContext.Expect(tContext.deletePodAt).To(gomega.BeEmpty()) tContext.handlePodChange(podWithClaimName, nil) - require.NoError(tContext, podStore.Delete(podWithClaimName), "remove pod") + tContext.ExpectNoError(podStore.Delete(podWithClaimName), "remove pod") tContext.handleClaimChange(inUseClaimWithToleration, nil) tContext.handleSliceChange(slice, nil) } diff --git a/pkg/controller/devicetainteviction/uid_cache.go b/pkg/controller/devicetainteviction/uid_cache.go new file mode 100644 index 0000000000000..9d33ef11680cc --- /dev/null +++ b/pkg/controller/devicetainteviction/uid_cache.go @@ -0,0 +1,46 @@ +/* +Copyright 2022 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 devicetainteviction + +import ( + "k8s.io/utils/lru" + + "k8s.io/apimachinery/pkg/types" +) + +// uidCache is an LRU cache for uid. +type uidCache struct { + cache *lru.Cache +} + +// newUIDCache returns a uidCache. +func newUIDCache(maxCacheEntries int) *uidCache { + return &uidCache{ + cache: lru.New(maxCacheEntries), + } +} + +// add adds a uid to the cache. +func (c *uidCache) add(uid types.UID) { + c.cache.Add(uid, nil) +} + +// has returns if a uid is in the cache. +func (c *uidCache) has(uid types.UID) bool { + _, found := c.cache.Get(uid) + return found +} diff --git a/pkg/controller/disruption/disruption.go b/pkg/controller/disruption/disruption.go index 4475f3cbf7a67..2d3c85e44ae57 100644 --- a/pkg/controller/disruption/disruption.go +++ b/pkg/controller/disruption/disruption.go @@ -19,6 +19,7 @@ package disruption import ( "context" "fmt" + "slices" "sync" "time" @@ -440,13 +441,7 @@ func verifyGroupKind(controllerRef *metav1.OwnerReference, expectedKind string, return false, nil } - for _, group := range expectedGroups { - if group == gv.Group { - return true, nil - } - } - - return false, nil + return slices.Contains(expectedGroups, gv.Group), nil } func (dc *DisruptionController) Run(ctx context.Context) { diff --git a/pkg/controller/endpoint/endpoints_controller.go b/pkg/controller/endpoint/endpoints_controller.go index fe1c5f40832c7..6b52f860a2f7a 100644 --- a/pkg/controller/endpoint/endpoints_controller.go +++ b/pkg/controller/endpoint/endpoints_controller.go @@ -220,8 +220,22 @@ func (e *Controller) Run(ctx context.Context, workers int) { func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointAddress, error) { var endpointIP string - - wantIPv6 := svc.Spec.IPFamilies[0] == v1.IPv6Protocol + ipFamily := v1.IPv4Protocol + + // IPFamilies is expected to be populated by apiserver defaulting, but + // some services may reach this controller with an empty IPFamilies via + // watch events. Infer the family from ClusterIP or + // pod IP so we never panic on IPFamilies[0]. + if len(svc.Spec.IPFamilies) > 0 { + ipFamily = svc.Spec.IPFamilies[0] + } else if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone { + if utilnet.IsIPv6String(svc.Spec.ClusterIP) { + ipFamily = v1.IPv6Protocol + } + } else if utilnet.IsIPv6String(pod.Status.PodIP) { + ipFamily = v1.IPv6Protocol + } + wantIPv6 := ipFamily == v1.IPv6Protocol // Find an IP that matches the family. We parse and restringify the IP in case the // value on the Pod is in an irregular format. diff --git a/pkg/controller/endpoint/endpoints_controller_test.go b/pkg/controller/endpoint/endpoints_controller_test.go index bead9c2f6494a..c12fb83a9fa10 100644 --- a/pkg/controller/endpoint/endpoints_controller_test.go +++ b/pkg/controller/endpoint/endpoints_controller_test.go @@ -3224,3 +3224,83 @@ func TestSyncEndpointsAddDeletePorts(t *testing.T) { t.Fatalf("incorrect endpoints after deleting first port:\n%s", diff) } } + +func TestPodToEndpointAddressForServiceEmptyIPFamilies(t *testing.T) { + testCases := []struct { + name string + clusterIP string + podIPs []v1.PodIP + podIP string + wantErr bool + wantFamily v1.IPFamily + }{ + { + name: "headful IPv4, IPv4 pod", + clusterIP: "10.0.0.1", + podIPs: []v1.PodIP{{IP: "10.244.0.1"}}, + wantFamily: v1.IPv4Protocol, + }, + { + name: "headful IPv6, IPv6 pod", + clusterIP: "fd00::1", + podIPs: []v1.PodIP{{IP: "fd00::10"}}, + wantFamily: v1.IPv6Protocol, + }, + { + name: "headful IPv4, no matching pod IP", + clusterIP: "10.0.0.1", + podIPs: []v1.PodIP{{IP: "fd00::10"}}, + wantErr: true, + }, + { + name: "headless, IPv4 pod", + clusterIP: v1.ClusterIPNone, + podIPs: []v1.PodIP{{IP: "10.244.0.1"}}, + podIP: "10.244.0.1", + wantFamily: v1.IPv4Protocol, + }, + { + name: "headless, IPv6 pod", + clusterIP: v1.ClusterIPNone, + podIPs: []v1.PodIP{{IP: "fd00::10"}}, + podIP: "fd00::10", + wantFamily: v1.IPv6Protocol, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: v1.ServiceSpec{ + // Intentionally leave IPFamilies empty. + ClusterIP: tc.clusterIP, + }, + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo-pod", Namespace: "bar", UID: "uid-1"}, + Spec: v1.PodSpec{NodeName: "node-1"}, + Status: v1.PodStatus{PodIP: tc.podIP, PodIPs: tc.podIPs}, + } + + addr, err := podToEndpointAddressForService(svc, pod) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error but got addr=%v", addr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if addr == nil { + t.Fatal("expected an address but got nil") + } + isV6 := utilnet.IsIPv6String(addr.IP) + wantV6 := tc.wantFamily == v1.IPv6Protocol + if isV6 != wantV6 { + t.Errorf("got IP %q (IPv6=%v), want family %v", addr.IP, isV6, tc.wantFamily) + } + }) + } +} diff --git a/pkg/controller/endpointslice/endpointslice_controller.go b/pkg/controller/endpointslice/endpointslice_controller.go index 2f424437b8327..3e39606a489af 100644 --- a/pkg/controller/endpointslice/endpointslice_controller.go +++ b/pkg/controller/endpointslice/endpointslice_controller.go @@ -162,21 +162,18 @@ func NewController(ctx context.Context, podInformer coreinformers.PodInformer, c.endpointUpdatesBatchPeriod = endpointUpdatesBatchPeriod - if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) { - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(_ interface{}) { - c.addNode() - }, - UpdateFunc: func(oldObj, newObj interface{}) { - c.updateNode(oldObj, newObj) - }, - DeleteFunc: func(_ interface{}) { - c.deleteNode() - }, - }) - - c.topologyCache = topologycache.NewTopologyCache() - } + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(_ interface{}) { + c.addNode() + }, + UpdateFunc: func(oldObj, newObj interface{}) { + c.updateNode(oldObj, newObj) + }, + DeleteFunc: func(_ interface{}) { + c.deleteNode() + }, + }) + c.topologyCache = topologycache.NewTopologyCache() c.reconciler = endpointslicerec.NewReconciler( c.client, @@ -186,7 +183,7 @@ func NewController(ctx context.Context, podInformer coreinformers.PodInformer, c.topologyCache, c.eventRecorder, ControllerName, - endpointslicerec.WithTrafficDistributionEnabled(utilfeature.DefaultFeatureGate.Enabled(features.ServiceTrafficDistribution), utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution)), + endpointslicerec.WithPreferSameTrafficDistributionEnabled(utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution)), ) return c diff --git a/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller.go b/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller.go index 9f943e6719f28..14ff2ecb55aab 100644 --- a/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller.go +++ b/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller.go @@ -240,7 +240,7 @@ func (c *Controller) Run(ctx context.Context, workers int) { for i := 0; i < workers; i++ { wg.Go(func() { - wait.Until(func() { c.worker(logger) }, c.workerLoopPeriod, ctx.Done()) + wait.UntilWithContext(ctx, c.worker, c.workerLoopPeriod) }) } <-ctx.Done() @@ -250,20 +250,20 @@ func (c *Controller) Run(ctx context.Context, workers int) { // marks them done. You may run as many of these in parallel as you wish; the // workqueue guarantees that they will not end up processing the same service // at the same time -func (c *Controller) worker(logger klog.Logger) { - for c.processNextWorkItem(logger) { +func (c *Controller) worker(ctx context.Context) { + for c.processNextWorkItem(ctx) { } } -func (c *Controller) processNextWorkItem(logger klog.Logger) bool { +func (c *Controller) processNextWorkItem(ctx context.Context) bool { cKey, quit := c.queue.Get() if quit { return false } defer c.queue.Done(cKey) - err := c.syncEndpoints(logger, cKey) - c.handleErr(logger, err, cKey) + err := c.syncEndpoints(ctx, cKey) + c.handleErr(klog.FromContext(ctx), err, cKey) return true } @@ -285,7 +285,8 @@ func (c *Controller) handleErr(logger klog.Logger, err error, key string) { utilruntime.HandleError(err) } -func (c *Controller) syncEndpoints(logger klog.Logger, key string) error { +func (c *Controller) syncEndpoints(ctx context.Context, key string) error { + logger := klog.FromContext(ctx) startTime := time.Now() defer func() { syncDuration := float64(time.Since(startTime).Milliseconds()) / 1000 @@ -305,7 +306,7 @@ func (c *Controller) syncEndpoints(logger klog.Logger, key string) error { if apierrors.IsNotFound(err) { logger.V(4).Info("Endpoints not found, cleaning up any mirrored EndpointSlices", "endpoints", klog.KRef(namespace, name)) c.endpointSliceTracker.DeleteService(namespace, name) - return c.deleteMirroredSlices(namespace, name) + return c.deleteMirroredSlices(ctx, namespace, name) } return err } @@ -313,7 +314,7 @@ func (c *Controller) syncEndpoints(logger klog.Logger, key string) error { if !c.shouldMirror(endpoints) { logger.V(4).Info("Endpoints should not be mirrored, cleaning up any mirrored EndpointSlices", "endpoints", klog.KRef(namespace, name)) c.endpointSliceTracker.DeleteService(namespace, name) - return c.deleteMirroredSlices(namespace, name) + return c.deleteMirroredSlices(ctx, namespace, name) } svc, err := c.serviceLister.Services(namespace).Get(name) @@ -321,7 +322,7 @@ func (c *Controller) syncEndpoints(logger klog.Logger, key string) error { if apierrors.IsNotFound(err) { logger.V(4).Info("Service not found, cleaning up any mirrored EndpointSlices", "service", klog.KRef(namespace, name)) c.endpointSliceTracker.DeleteService(namespace, name) - return c.deleteMirroredSlices(namespace, name) + return c.deleteMirroredSlices(ctx, namespace, name) } return err } @@ -330,7 +331,7 @@ func (c *Controller) syncEndpoints(logger klog.Logger, key string) error { if svc.Spec.Selector != nil { logger.V(4).Info("Service now has selector, cleaning up any mirrored EndpointSlices", "service", klog.KRef(namespace, name)) c.endpointSliceTracker.DeleteService(namespace, name) - return c.deleteMirroredSlices(namespace, name) + return c.deleteMirroredSlices(ctx, namespace, name) } endpointSlices, err := endpointSlicesMirroredForService(c.endpointSliceLister, namespace, name) @@ -342,7 +343,7 @@ func (c *Controller) syncEndpoints(logger klog.Logger, key string) error { return endpointslicepkg.NewStaleInformerCache("EndpointSlice informer cache is out of date") } - err = c.reconciler.reconcile(logger, endpoints, endpointSlices) + err = c.reconciler.reconcile(ctx, endpoints, endpointSlices) if err != nil { return err } @@ -529,14 +530,14 @@ func (c *Controller) queueEndpointsForEndpointSlice(endpointSlice *discovery.End // deleteMirroredSlices will delete and EndpointSlices that have been mirrored // for Endpoints with this namespace and name. -func (c *Controller) deleteMirroredSlices(namespace, name string) error { +func (c *Controller) deleteMirroredSlices(ctx context.Context, namespace, name string) error { endpointSlices, err := endpointSlicesMirroredForService(c.endpointSliceLister, namespace, name) if err != nil { return err } c.endpointSliceTracker.DeleteService(namespace, name) - return c.reconciler.deleteEndpoints(namespace, name, endpointSlices) + return c.reconciler.deleteEndpoints(ctx, namespace, name, endpointSlices) } // endpointSlicesMirroredForService returns the EndpointSlices that have been diff --git a/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller_test.go b/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller_test.go index 4846bcb76ced7..07d48f5ea5d23 100644 --- a/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller_test.go +++ b/pkg/controller/endpointslicemirroring/endpointslicemirroring_controller_test.go @@ -245,8 +245,8 @@ func TestSyncEndpoints(t *testing.T) { } } - logger, _ := ktesting.NewTestContext(t) - err := esController.syncEndpoints(logger, fmt.Sprintf("%s/%s", namespace, endpointsName)) + _, syncCtx := ktesting.NewTestContext(t) + err := esController.syncEndpoints(syncCtx, fmt.Sprintf("%s/%s", namespace, endpointsName)) if err != nil { t.Fatalf("Unexpected error from syncEndpoints: %v", err) } diff --git a/pkg/controller/endpointslicemirroring/reconciler.go b/pkg/controller/endpointslicemirroring/reconciler.go index 03044376e6146..6a2ffd087066f 100644 --- a/pkg/controller/endpointslicemirroring/reconciler.go +++ b/pkg/controller/endpointslicemirroring/reconciler.go @@ -61,7 +61,8 @@ type reconciler struct { // reconcile takes an Endpoints resource and ensures that corresponding // EndpointSlices exist. It creates, updates, or deletes EndpointSlices to // ensure the desired set of addresses are represented by EndpointSlices. -func (r *reconciler) reconcile(logger klog.Logger, endpoints *corev1.Endpoints, existingSlices []*discovery.EndpointSlice) error { +func (r *reconciler) reconcile(ctx context.Context, endpoints *corev1.Endpoints, existingSlices []*discovery.EndpointSlice) error { + logger := klog.FromContext(ctx) // Calculate desired state. d := newDesiredCalc() @@ -167,7 +168,7 @@ func (r *reconciler) reconcile(logger klog.Logger, endpoints *corev1.Endpoints, endpointsNN := types.NamespacedName{Name: endpoints.Name, Namespace: endpoints.Namespace} r.metricsCache.UpdateEndpointPortCache(endpointsNN, epMetrics) - return r.finalize(endpoints, slices) + return r.finalize(ctx, endpoints, slices) } // reconcileByPortMapping compares the endpoints found in existing slices with @@ -237,7 +238,7 @@ func (r *reconciler) reconcileByPortMapping( } // finalize creates, updates, and deletes slices as specified -func (r *reconciler) finalize(endpoints *corev1.Endpoints, slices slicesByAction) error { +func (r *reconciler) finalize(ctx context.Context, endpoints *corev1.Endpoints, slices slicesByAction) error { // If there are slices to create and delete, recycle the slices marked for // deletion by replacing creates with updates of slices that would otherwise // be deleted. @@ -249,7 +250,7 @@ func (r *reconciler) finalize(endpoints *corev1.Endpoints, slices slicesByAction // being deleted. if endpoints.DeletionTimestamp == nil { for _, endpointSlice := range slices.toCreate { - createdSlice, err := epsClient.Create(context.TODO(), endpointSlice, metav1.CreateOptions{}) + createdSlice, err := epsClient.Create(ctx, endpointSlice, metav1.CreateOptions{}) if err != nil { // If the namespace is terminating, creates will continue to fail. Simply drop the item. if errors.HasStatusCause(err, corev1.NamespaceTerminatingCause) { @@ -263,7 +264,7 @@ func (r *reconciler) finalize(endpoints *corev1.Endpoints, slices slicesByAction } for _, endpointSlice := range slices.toUpdate { - updatedSlice, err := epsClient.Update(context.TODO(), endpointSlice, metav1.UpdateOptions{}) + updatedSlice, err := epsClient.Update(ctx, endpointSlice, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("failed to update %s EndpointSlice for Endpoints %s/%s: %v", endpointSlice.Name, endpoints.Namespace, endpoints.Name, err) } @@ -272,7 +273,7 @@ func (r *reconciler) finalize(endpoints *corev1.Endpoints, slices slicesByAction } for _, endpointSlice := range slices.toDelete { - err := epsClient.Delete(context.TODO(), endpointSlice.Name, metav1.DeleteOptions{}) + err := epsClient.Delete(ctx, endpointSlice.Name, metav1.DeleteOptions{}) if err != nil { return fmt.Errorf("failed to delete %s EndpointSlice for Endpoints %s/%s: %v", endpointSlice.Name, endpoints.Namespace, endpoints.Name, err) } @@ -285,11 +286,11 @@ func (r *reconciler) finalize(endpoints *corev1.Endpoints, slices slicesByAction // deleteEndpoints deletes any associated EndpointSlices and cleans up any // Endpoints references from the metricsCache. -func (r *reconciler) deleteEndpoints(namespace, name string, endpointSlices []*discovery.EndpointSlice) error { +func (r *reconciler) deleteEndpoints(ctx context.Context, namespace, name string, endpointSlices []*discovery.EndpointSlice) error { r.metricsCache.DeleteEndpoints(types.NamespacedName{Namespace: namespace, Name: name}) var errs []error for _, endpointSlice := range endpointSlices { - err := r.client.DiscoveryV1().EndpointSlices(namespace).Delete(context.TODO(), endpointSlice.Name, metav1.DeleteOptions{}) + err := r.client.DiscoveryV1().EndpointSlices(namespace).Delete(ctx, endpointSlice.Name, metav1.DeleteOptions{}) if err != nil { errs = append(errs, err) } diff --git a/pkg/controller/endpointslicemirroring/reconciler_test.go b/pkg/controller/endpointslicemirroring/reconciler_test.go index e7fc8ffce54ad..e5c86b3d331bf 100644 --- a/pkg/controller/endpointslicemirroring/reconciler_test.go +++ b/pkg/controller/endpointslicemirroring/reconciler_test.go @@ -1316,8 +1316,8 @@ func fetchEndpointSlices(t *testing.T, client *fake.Clientset, namespace string) func reconcileHelper(t *testing.T, r *reconciler, endpoints *corev1.Endpoints, existingSlices []*discovery.EndpointSlice) { t.Helper() - logger, _ := ktesting.NewTestContext(t) - err := r.reconcile(logger, endpoints, existingSlices) + _, ctx := ktesting.NewTestContext(t) + err := r.reconcile(ctx, endpoints, existingSlices) if err != nil { t.Fatalf("Expected no error reconciling Endpoint Slices, got: %v", err) } diff --git a/pkg/controller/garbagecollector/dump.go b/pkg/controller/garbagecollector/dump.go index 1943a7f1fa3ae..884255cc4138b 100644 --- a/pkg/controller/garbagecollector/dump.go +++ b/pkg/controller/garbagecollector/dump.go @@ -148,9 +148,9 @@ func NewDOTVertex(node *node) *dotVertex { gvk: gv.WithKind(node.identity.Kind), namespace: node.identity.Namespace, name: node.identity.Name, - beingDeleted: node.beingDeleted, - deletingDependents: node.deletingDependents, - virtual: node.virtual, + beingDeleted: node.isBeingDeleted(), + deletingDependents: node.isDeletingDependents(), + virtual: !node.isObserved(), } } @@ -195,7 +195,7 @@ func toDOTNodesAndEdges(uidToNode map[types.UID]*node) ([]*dotVertex, []dotEdge) // add the vertices first, then edges. That avoids having to deal with missing refs. for _, node := range uidToNode { // skip adding objects that don't have owner references and aren't referred to. - if len(node.dependents) == 0 && len(node.owners) == 0 { + if node.dependentsLength() == 0 && len(node.getOwners()) == 0 { continue } vertex := NewDOTVertex(node) @@ -204,7 +204,7 @@ func toDOTNodesAndEdges(uidToNode map[types.UID]*node) ([]*dotVertex, []dotEdge) } for _, node := range uidToNode { currVertex := uidToVertex[node.identity.UID] - for _, ownerRef := range node.owners { + for _, ownerRef := range node.getOwners() { currOwnerVertex, ok := uidToVertex[ownerRef.UID] if !ok { currOwnerVertex = NewMissingdotVertex(ownerRef) @@ -253,14 +253,14 @@ func toDOTNodesAndEdgesForObj(uidToNode map[types.UID]*node, uids ...types.UID) interestingNodes[node.identity.UID] = node - for _, ownerRef := range node.owners { + for _, ownerRef := range node.getOwners() { // if we've already inspected this UID, don't add it to be inspected again if _, ok := interestingNodes[ownerRef.UID]; ok { continue } uidsToCheck = append(uidsToCheck, ownerRef.UID) } - for dependent := range node.dependents { + for _, dependent := range node.getDependents() { // if we've already inspected this UID, don't add it to be inspected again if _, ok := interestingNodes[dependent.identity.UID]; ok { continue diff --git a/pkg/controller/garbagecollector/dump_test.go b/pkg/controller/garbagecollector/dump_test.go index 45c2490684d63..7587f673c3bc6 100644 --- a/pkg/controller/garbagecollector/dump_test.go +++ b/pkg/controller/garbagecollector/dump_test.go @@ -26,7 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) var ( diff --git a/pkg/controller/garbagecollector/garbagecollector.go b/pkg/controller/garbagecollector/garbagecollector.go index bea3f2c66f5b0..f43578073e800 100644 --- a/pkg/controller/garbagecollector/garbagecollector.go +++ b/pkg/controller/garbagecollector/garbagecollector.go @@ -359,8 +359,8 @@ func (gc *GarbageCollector) attemptToDeleteWorker(ctx context.Context, item inte // 2. The reference is to an invalid group/version. We don't currently // have a way to distinguish this from a valid type we will recognize // after the next discovery sync. - // For now, record the error and retry. - logger.V(5).Error(err, "error syncing item", "item", n.identity) + // For now, log the error and retry. + logger.V(5).Info("error syncing item", "item", n.identity, "err", err) } else { utilruntime.HandleError(fmt.Errorf("error syncing item %s: %v", n, err)) } @@ -625,7 +625,12 @@ func (gc *GarbageCollector) attemptToDeleteItem(ctx context.Context, item *node) // FinalizerDeletingDependents from the item, resulting in the final // deletion of the item. policy := metav1.DeletePropagationForeground - return gc.deleteObject(item.identity, latest.ResourceVersion, latest.OwnerReferences, &policy) + err := gc.deleteObject(item.identity, latest.ResourceVersion, latest.OwnerReferences, &policy) + if errors.IsNotFound(err) { + gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity) + return enqueuedVirtualDeleteEventErr + } + return err default: // item doesn't have any solid owner, so it needs to be garbage // collected. Also, none of item's owners is waiting for the deletion of @@ -646,7 +651,12 @@ func (gc *GarbageCollector) attemptToDeleteItem(ctx context.Context, item *node) "item", item.identity, "propagationPolicy", policy, ) - return gc.deleteObject(item.identity, latest.ResourceVersion, latest.OwnerReferences, &policy) + err := gc.deleteObject(item.identity, latest.ResourceVersion, latest.OwnerReferences, &policy) + if errors.IsNotFound(err) { + gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity) + return enqueuedVirtualDeleteEventErr + } + return err } } diff --git a/pkg/controller/garbagecollector/garbagecollector_test.go b/pkg/controller/garbagecollector/garbagecollector_test.go index 8663e6c24a868..13b0a4aa12ccb 100644 --- a/pkg/controller/garbagecollector/garbagecollector_test.go +++ b/pkg/controller/garbagecollector/garbagecollector_test.go @@ -18,6 +18,7 @@ package garbagecollector import ( "context" + goerrors "errors" "fmt" "net/http" "net/http/httptest" @@ -255,6 +256,126 @@ func serilizeOrDie(t *testing.T, object interface{}) []byte { return data } +func TestAttemptToDeleteItemDeleteObjectNotFound(t *testing.T) { + pod := getPod("ExternallyDeletedPod", []metav1.OwnerReference{ + { + Kind: "ReplicationController", + Name: "owner1", + UID: "123", + APIVersion: "v1", + }, + }) + testHandler := &fakeActionHandler{ + response: map[string]FakeResponse{ + "GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1": { + 404, + []byte{}, + }, + "GET" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPod": { + 200, + serilizeOrDie(t, pod), + }, + "DELETE" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPod": { + 404, + []byte{}, + }, + }, + } + srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP) + defer srv.Close() + + gc := setupGC(t, clientConfig) + defer close(gc.stop) + + item := &node{ + identity: objectReference{ + OwnerReference: metav1.OwnerReference{ + Kind: pod.Kind, + APIVersion: pod.APIVersion, + Name: pod.Name, + UID: pod.UID, + }, + Namespace: pod.Namespace, + }, + owners: nil, + } + + err := gc.attemptToDeleteItem(context.TODO(), item) + if !goerrors.Is(err, enqueuedVirtualDeleteEventErr) { + t.Errorf("expected enqueuedVirtualDeleteEventErr, got: %v", err) + } + if gc.dependencyGraphBuilder.graphChanges.Len() == 0 { + t.Errorf("expected a virtual delete event to be enqueued in graphChanges, but the queue is empty") + } +} + +func TestAttemptToDeleteItemDeleteObjectNotFoundWaitingForDependents(t *testing.T) { + pod := getPod("ExternallyDeletedPodFG", []metav1.OwnerReference{ + { + Kind: "ReplicationController", + Name: "owner1", + UID: "123", + APIVersion: "v1", + BlockOwnerDeletion: func() *bool { b := true; return &b }(), + }, + }) + owner := &v1.ReplicationController{ + TypeMeta: metav1.TypeMeta{ + Kind: "ReplicationController", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "owner1", + Namespace: "ns1", + UID: "123", + DeletionTimestamp: func() *metav1.Time { t := metav1.Now(); return &t }(), + Finalizers: []string{metav1.FinalizerDeleteDependents}, + }, + } + testHandler := &fakeActionHandler{ + response: map[string]FakeResponse{ + "GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1": { + 200, + serilizeOrDie(t, owner), + }, + "GET" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPodFG": { + 200, + serilizeOrDie(t, pod), + }, + "DELETE" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPodFG": { + 404, + []byte{}, + }, + }, + } + srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP) + defer srv.Close() + + gc := setupGC(t, clientConfig) + defer close(gc.stop) + + item := &node{ + identity: objectReference{ + OwnerReference: metav1.OwnerReference{ + Kind: pod.Kind, + APIVersion: pod.APIVersion, + Name: pod.Name, + UID: pod.UID, + }, + Namespace: pod.Namespace, + }, + owners: nil, + } + + err := gc.attemptToDeleteItem(context.TODO(), item) + if !goerrors.Is(err, enqueuedVirtualDeleteEventErr) { + t.Errorf("expected enqueuedVirtualDeleteEventErr, got: %v", err) + } + if gc.dependencyGraphBuilder.graphChanges.Len() == 0 { + t.Errorf("expected a virtual delete event to be enqueued in graphChanges, but the queue is empty") + } +} + // test the attemptToDeleteItem function making the expected actions. func TestAttemptToDeleteItem(t *testing.T) { pod := getPod("ToBeDeletedPod", []metav1.OwnerReference{ @@ -919,8 +1040,9 @@ func TestGarbageCollectorSync(t *testing.T) { var wg sync.WaitGroup defer wg.Wait() - logger, tCtx := ktesting.NewTestContext(t) + tCtx := ktesting.Init(t) defer tCtx.Cancel("test has completed") + logger := tCtx.Logger() alwaysStarted := make(chan struct{}) close(alwaysStarted) diff --git a/pkg/controller/garbagecollector/graph.go b/pkg/controller/garbagecollector/graph.go index 77715309729e1..432e6537c6345 100644 --- a/pkg/controller/garbagecollector/graph.go +++ b/pkg/controller/garbagecollector/graph.go @@ -79,7 +79,8 @@ type node struct { virtualLock sync.RWMutex // when processing an Update event, we need to compare the updated // ownerReferences with the owners recorded in the graph. - owners []metav1.OwnerReference + owners []metav1.OwnerReference + ownersLock sync.RWMutex } // clone() must only be called from the single-threaded GraphBuilder.processGraphChanges() @@ -138,6 +139,18 @@ func (n *node) isDeletingDependents() bool { return n.deletingDependents } +func (n *node) setOwners(owners []metav1.OwnerReference) { + n.ownersLock.Lock() + defer n.ownersLock.Unlock() + n.owners = owners +} + +func (n *node) getOwners() []metav1.OwnerReference { + n.ownersLock.RLock() + defer n.ownersLock.RUnlock() + return n.owners +} + func (n *node) addDependent(dependent *node) { n.dependentsLock.Lock() defer n.dependentsLock.Unlock() @@ -179,7 +192,7 @@ func (n *node) blockingDependents() []*node { dependents := n.getDependents() var ret []*node for _, dep := range dependents { - for _, owner := range dep.owners { + for _, owner := range dep.getOwners() { if owner.UID == n.identity.UID && owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion { ret = append(ret, dep) } @@ -217,6 +230,9 @@ func (n *node) String() string { n.dependentsLock.RLock() defer n.dependentsLock.RUnlock() + n.ownersLock.RLock() + defer n.ownersLock.RUnlock() + return fmt.Sprintf("%#v", n) } diff --git a/pkg/controller/garbagecollector/graph_builder.go b/pkg/controller/garbagecollector/graph_builder.go index 9b3b7fc4844a3..1d59eeb059154 100644 --- a/pkg/controller/garbagecollector/graph_builder.go +++ b/pkg/controller/garbagecollector/graph_builder.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "reflect" + "slices" "sync" "time" @@ -223,7 +224,7 @@ func (gb *GraphBuilder) controllerFor(logger klog.Logger, resource schema.GroupV shared, err := gb.sharedInformers.ForResource(resource) if err != nil { - logger.V(4).Error(err, "unable to use a shared informer", "resource", resource, "kind", kind) + logger.V(4).Info("unable to use a shared informer", "resource", resource, "kind", kind, "err", err) return nil, nil, err } logger.V(4).Info("using a shared informer", "resource", resource, "kind", kind) @@ -621,12 +622,7 @@ func hasOrphanFinalizer(accessor metav1.Object) bool { func hasFinalizer(accessor metav1.Object, matchingFinalizer string) bool { finalizers := accessor.GetFinalizers() - for _, finalizer := range finalizers { - if finalizer == matchingFinalizer { - return true - } - } - return false + return slices.Contains(finalizers, matchingFinalizer) } // this function takes newAccessor directly because the caller already @@ -784,7 +780,7 @@ func (gb *GraphBuilder) processGraphChanges(logger klog.Logger) bool { // waiting for the deletion of their dependents. gb.addUnblockedOwnersToDeleteQueue(logger, removed, changed) // update the node itself - existingNode.owners = accessor.GetOwnerReferences() + existingNode.setOwners(accessor.GetOwnerReferences()) // Add the node to its new owners' dependent lists. gb.addDependentToOwners(logger, existingNode, added) // remove the node from the dependent list of node that are no longer in diff --git a/pkg/controller/garbagecollector/operations.go b/pkg/controller/garbagecollector/operations.go index a300025272807..40acfdef578d0 100644 --- a/pkg/controller/garbagecollector/operations.go +++ b/pkg/controller/garbagecollector/operations.go @@ -67,8 +67,8 @@ func (gc *GarbageCollector) deleteObject(item objectReference, resourceVersion s // check if the ownerReferences changed liveObject, liveErr := resourceClient.Get(context.TODO(), item.Name, metav1.GetOptions{}) if errors.IsNotFound(liveErr) { - // object we wanted to delete is gone, success! - return nil + // object we wanted to delete is gone, return NotFound so caller can handle it consistently + return liveErr } if liveErr == nil && liveObject.UID == item.UID && liveObject.ResourceVersion != resourceVersion && reflect.DeepEqual(liveObject.OwnerReferences, ownersAtResourceVersion) { // object changed, causing a conflict error, but ownerReferences did not change. diff --git a/pkg/controller/garbagecollector/patch.go b/pkg/controller/garbagecollector/patch.go index 25d8b2085132c..c447a65f5f6b4 100644 --- a/pkg/controller/garbagecollector/patch.go +++ b/pkg/controller/garbagecollector/patch.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -114,14 +115,7 @@ func (gc *GarbageCollector) deleteOwnerRefJSONMergePatch(item *node, ownerUIDs . expectedObjectMeta.ResourceVersion = accessor.GetResourceVersion() refs := accessor.GetOwnerReferences() for _, ref := range refs { - var skip bool - for _, ownerUID := range ownerUIDs { - if ref.UID == ownerUID { - skip = true - break - } - } - if !skip { + if !slices.Contains(ownerUIDs, ref.UID) { expectedObjectMeta.OwnerReferences = append(expectedObjectMeta.OwnerReferences, ref) } } @@ -134,7 +128,7 @@ func (n *node) unblockOwnerReferencesStrategicMergePatch() ([]byte, error) { var dummy metaonly.MetadataOnlyObject var blockingRefs []metav1.OwnerReference falseVar := false - for _, owner := range n.owners { + for _, owner := range n.getOwners() { if owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion { ref := owner ref.BlockOwnerDeletion = &falseVar @@ -157,7 +151,7 @@ func (gc *GarbageCollector) unblockOwnerReferencesJSONMergePatch(n *node) ([]byt expectedObjectMeta.ResourceVersion = accessor.GetResourceVersion() var expectedOwners []metav1.OwnerReference falseVar := false - for _, owner := range n.owners { + for _, owner := range n.getOwners() { owner.BlockOwnerDeletion = &falseVar expectedOwners = append(expectedOwners, owner) } diff --git a/pkg/controller/history/benchmark_test.go b/pkg/controller/history/benchmark_test.go new file mode 100644 index 0000000000000..d21d2ef0cc942 --- /dev/null +++ b/pkg/controller/history/benchmark_test.go @@ -0,0 +1,178 @@ +/* +Copyright 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 history + +import ( + "fmt" + "testing" + + apps "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/kubernetes/pkg/controller" +) + +func BenchmarkListControllerRevisions(b *testing.B) { + parent := &apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-parent", + Namespace: "default", + UID: types.UID("test-parent-uid"), + }, + } + parentKind := schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "StatefulSet", + } + + scenarios := []struct { + name string + totalRevisions int + ownedPercentage float64 // percentage of totalRevisions that are owned + orphanPercentage float64 // percentage of totalRevisions that are orphans + + }{ + { + name: "0.1pct_Owned_0pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 0.1, + orphanPercentage: 0, + }, + { + name: "0.1pct_Owned_0.1pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 0.1, + orphanPercentage: 0.1, + }, + { + name: "1pct_Owned_0pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 1, + orphanPercentage: 0, + }, + { + name: "1pct_Owned_1pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 1, + orphanPercentage: 1, + }, + { + name: "10pct_Owned_0pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 10, + orphanPercentage: 0, + }, + { + name: "10pct_Owned_10pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 10, + orphanPercentage: 10, + }, + { + name: "100pct_Owned_0pct_Orphan", + totalRevisions: 50000, + ownedPercentage: 100, + orphanPercentage: 0, + }, + } + + for _, s := range scenarios { + b.Run(s.name, func(b *testing.B) { + ownedRevisions := int(float64(s.totalRevisions) * s.ownedPercentage / 100) + orphans := int(float64(s.totalRevisions) * s.orphanPercentage / 100) + otherOwned := s.totalRevisions - ownedRevisions - orphans + + client := fake.NewClientset() + informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) + revisionInformer := informerFactory.Apps().V1().ControllerRevisions() + + if err := AddControllerRevisionControllerIndexer(revisionInformer.Informer()); err != nil { + b.Fatalf("failed to add indexer: %v", err) + } + + revisions := make([]runtime.Object, 0, s.totalRevisions) + for i := 0; i < ownedRevisions; i++ { + revisions = append(revisions, createRevision(s.name, i, parent, parentKind)) + } + + // Create orphaned revisions (nil controller ref) + for i := 0; i < orphans; i++ { + rev := createRevision(s.name, ownedRevisions+i, nil, schema.GroupVersionKind{}) + // Make sure it has no owner ref or at least no controller ref + rev.OwnerReferences = nil + revisions = append(revisions, rev) + } + + // Create revisions owned by other controllers + otherParent := &apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-parent", + Namespace: "default", + UID: types.UID("other-parent-uid"), + }, + } + for i := 0; i < otherOwned; i++ { + revisions = append(revisions, createRevision(s.name, ownedRevisions+orphans+i, otherParent, parentKind)) + } + + indexer := revisionInformer.Informer().GetIndexer() + for _, obj := range revisions { + if err := indexer.Add(obj); err != nil { + b.Fatalf("failed to add object to indexer: %v", err) + } + } + + history := NewHistory(client, revisionInformer.Lister(), indexer) + selector, _ := labels.Parse("name=" + s.name) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := history.ListControllerRevisions(parent, parentKind, selector) + if err != nil { + b.Fatalf("ListControllerRevisions failed: %v", err) + } + } + }) + } +} + +func createRevision(scenarioName string, index int, owner metav1.Object, ownerKind schema.GroupVersionKind) *apps.ControllerRevision { + rev := &apps.ControllerRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d", scenarioName, index), + Namespace: "default", + Labels: map[string]string{ + "name": scenarioName, + }, + UID: types.UID(fmt.Sprintf("%s-%d", scenarioName, index)), // Ensure unique UID + }, + Data: runtime.RawExtension{Raw: []byte("{}")}, + Revision: int64(index), + } + + if owner != nil { + rev.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(owner, ownerKind)} + } + + return rev +} diff --git a/pkg/controller/history/controller_history.go b/pkg/controller/history/controller_history.go index 9f4f82223a19a..834588fe5a9b6 100644 --- a/pkg/controller/history/controller_history.go +++ b/pkg/controller/history/controller_history.go @@ -48,6 +48,48 @@ import ( // ControllerRevisionHashLabel is the label used to indicate the hash value of a ControllerRevision's Data. const ControllerRevisionHashLabel = "controller.kubernetes.io/hash" +// ControllerRevisionControllerIndex is the index name for the ControllerRevision controller index. +const ControllerRevisionControllerIndex = "controllerRevisionController" + +// ControllerRevisionControllerIndexKey returns the index key to locate ControllerRevisions with the specified controller ownerReference. +// If the ownerReference is specified, the key is "namespace/kind/ownerReference.Name/ownerReference.UID". +// If the ownerReference is nil, the key is just "namespace" for orphan revisions. +func ControllerRevisionControllerIndexKey(namespace string, ownerReference *metav1.OwnerReference) string { + if ownerReference == nil { + return namespace + } + return namespace + "/" + ownerReference.Kind + "/" + ownerReference.Name + "/" + string(ownerReference.UID) +} + +// AddControllerRevisionControllerIndexer adds an indexer for ControllerRevision's controllerRef.UID to the given Informer. +// This indexer is used to efficiently look up ControllerRevisions by their ControllerRef.UID +func AddControllerRevisionControllerIndexer(informer cache.SharedIndexInformer) error { + if _, exists := informer.GetIndexer().GetIndexers()[ControllerRevisionControllerIndex]; exists { + // indexer already exists, do nothing + return nil + } + + err := informer.AddIndexers(cache.Indexers{ + ControllerRevisionControllerIndex: func(obj interface{}) ([]string, error) { + cr, ok := obj.(*apps.ControllerRevision) + if !ok { + return nil, nil + } + // Get the ControllerRef of the ControllerRevision to check if it's managed by a controller. + // Index with a non-nil controller (indicating an owned revision) or a nil controller (indicating an orphan revision). + return []string{ControllerRevisionControllerIndexKey(cr.Namespace, metav1.GetControllerOfNoCopy(cr))}, nil + }, + }) + if err != nil { + // It's possible that the indexer was added by another controller between the check and the AddIndexers call. + // Check again if the indexer was added by another controller, otherwise return the error. + if _, exists := informer.GetIndexer().GetIndexers()[ControllerRevisionControllerIndex]; exists { + return nil + } + } + return err +} + // ControllerRevisionName returns the Name for a ControllerRevision in the form prefix-hash. If the length // of prefix is greater than 223 bytes, it is truncated to allow for a name that is no larger than 253 bytes. func ControllerRevisionName(prefix string, hash string) string { @@ -158,7 +200,7 @@ type Interface interface { // ListControllerRevisions lists all ControllerRevisions matching selector and owned by parent or no other // controller. If the returned error is nil the returned slice of ControllerRevisions is valid. If the // returned error is not nil, the returned slice is not valid. - ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error) + ListControllerRevisions(parent metav1.Object, parentKind schema.GroupVersionKind, selector labels.Selector) ([]*apps.ControllerRevision, error) // CreateControllerRevision attempts to create the revision as owned by parent via a ControllerRef. If name // collision occurs, collisionCount (incremented each time collision occurs except for the first time) is // added to the hash of the revision and it is renamed using ControllerRevisionName. Implementations may @@ -186,8 +228,10 @@ type Interface interface { // NewHistory returns an instance of Interface that uses client to communicate with the API Server and lister to list // ControllerRevisions. This method should be used to create an Interface for all scenarios other than testing. -func NewHistory(client clientset.Interface, lister appslisters.ControllerRevisionLister) Interface { - return &realHistory{client, lister} +// If indexer is not nil, it will be used to optimize ListControllerRevisions and the indexer must have had +// AddControllerRevisionControllerIndexer called on it. +func NewHistory(client clientset.Interface, lister appslisters.ControllerRevisionLister, indexer cache.Indexer) Interface { + return &realHistory{client, lister, indexer} } // NewFakeHistory returns an instance of Interface that uses informer to create, update, list, and delete @@ -197,16 +241,44 @@ func NewFakeHistory(informer appsinformers.ControllerRevisionInformer) Interface } type realHistory struct { - client clientset.Interface - lister appslisters.ControllerRevisionLister + client clientset.Interface + lister appslisters.ControllerRevisionLister + indexer cache.Indexer } -func (rh *realHistory) ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error) { +func (rh *realHistory) ListControllerRevisions(parent metav1.Object, parentKind schema.GroupVersionKind, selector labels.Selector) ([]*apps.ControllerRevision, error) { // List all revisions in the namespace that match the selector - history, err := rh.lister.ControllerRevisions(parent.GetNamespace()).List(selector) - if err != nil { - return nil, err + var history []*apps.ControllerRevision + var err error + if rh.indexer != nil { + keys := []string{ + ControllerRevisionControllerIndexKey(parent.GetNamespace(), metav1.NewControllerRef(parent, parentKind)), + ControllerRevisionControllerIndexKey(parent.GetNamespace(), nil), // Include orphans + } + + for _, key := range keys { + objs, err := rh.indexer.ByIndex(ControllerRevisionControllerIndex, key) + if err != nil { + return nil, err + } + for _, obj := range objs { + cr, ok := obj.(*apps.ControllerRevision) + if !ok { + continue + } + if selector.Matches(labels.Set(cr.Labels)) { + history = append(history, cr) + } + } + } + } else { + // Fallback to slow list if no indexer + history, err = rh.lister.ControllerRevisions(parent.GetNamespace()).List(selector) + if err != nil { + return nil, err + } } + var owned []*apps.ControllerRevision for i := range history { ref := metav1.GetControllerOfNoCopy(history[i]) @@ -343,7 +415,7 @@ type fakeHistory struct { lister appslisters.ControllerRevisionLister } -func (fh *fakeHistory) ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error) { +func (fh *fakeHistory) ListControllerRevisions(parent metav1.Object, parentKind schema.GroupVersionKind, selector labels.Selector) ([]*apps.ControllerRevision, error) { history, err := fh.lister.ControllerRevisions(parent.GetNamespace()).List(selector) if err != nil { return nil, err diff --git a/pkg/controller/history/controller_history_test.go b/pkg/controller/history/controller_history_test.go index b60ab7db88f2a..3a1e031df0565 100644 --- a/pkg/controller/history/controller_history_test.go +++ b/pkg/controller/history/controller_history_test.go @@ -60,13 +60,16 @@ func TestRealHistory_ListControllerRevisions(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) for i := range test.revisions { informer.Informer().GetIndexer().Add(test.revisions[i]) } - history := NewHistory(client, informer.Lister()) - revisions, err := history.ListControllerRevisions(test.parent, test.selector) + history := NewHistory(client, informer.Lister(), informer.Informer().GetIndexer()) + revisions, err := history.ListControllerRevisions(test.parent, parentKind, test.selector) if err != nil { t.Errorf("%s: %s", test.name, err) } @@ -157,13 +160,16 @@ func TestFakeHistory_ListControllerRevisions(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) for i := range test.revisions { informer.Informer().GetIndexer().Add(test.revisions[i]) } history := NewFakeHistory(informer) - revisions, err := history.ListControllerRevisions(test.parent, test.selector) + revisions, err := history.ListControllerRevisions(test.parent, parentKind, test.selector) if err != nil { t.Errorf("%s: %s", test.name, err) } @@ -256,8 +262,11 @@ func TestRealHistory_CreateControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) - history := NewHistory(client, informer.Lister()) + history := NewHistory(client, informer.Lister(), informer.Informer().GetIndexer()) var collisionCount int32 for _, item := range test.existing { @@ -388,6 +397,9 @@ func TestFakeHistory_CreateControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) @@ -538,8 +550,11 @@ func TestRealHistory_UpdateControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) - history := NewHistory(client, informer.Lister()) + history := NewHistory(client, informer.Lister(), informer.Informer().GetIndexer()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) @@ -665,6 +680,9 @@ func TestFakeHistory_UpdateControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 @@ -753,8 +771,11 @@ func TestRealHistory_DeleteControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) - history := NewHistory(client, informer.Lister()) + history := NewHistory(client, informer.Lister(), informer.Informer().GetIndexer()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) @@ -856,6 +877,9 @@ func TestFakeHistory_DeleteControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 @@ -993,9 +1017,12 @@ func TestRealHistory_AdoptControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) - history := NewHistory(client, informer.Lister()) + history := NewHistory(client, informer.Lister(), informer.Informer().GetIndexer()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) @@ -1099,6 +1126,9 @@ func TestFakeHistory_AdoptControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) @@ -1244,9 +1274,12 @@ func TestRealHistory_ReleaseControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) - history := NewHistory(client, informer.Lister()) + history := NewHistory(client, informer.Lister(), informer.Informer().GetIndexer()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) @@ -1366,6 +1399,9 @@ func TestFakeHistory_ReleaseControllerRevision(t *testing.T) { defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() + if err := AddControllerRevisionControllerIndexer(informer.Informer()); err != nil { + t.Fatalf("failed to add indexer: %v", err) + } informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 diff --git a/pkg/controller/job/job_controller.go b/pkg/controller/job/job_controller.go index 1ace380fc1244..ccf9b222c7cae 100644 --- a/pkg/controller/job/job_controller.go +++ b/pkg/controller/job/job_controller.go @@ -18,8 +18,10 @@ package job import ( "context" + "errors" "fmt" "reflect" + "slices" "sort" "sync" "sync/atomic" @@ -27,9 +29,11 @@ import ( batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -38,11 +42,13 @@ import ( "k8s.io/apiserver/pkg/util/feature" batchinformers "k8s.io/client-go/informers/batch/v1" coreinformers "k8s.io/client-go/informers/core/v1" + schedulinginformers "k8s.io/client-go/informers/scheduling/v1alpha2" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" v1core "k8s.io/client-go/kubernetes/typed/core/v1" batchv1listers "k8s.io/client-go/listers/batch/v1" corelisters "k8s.io/client-go/listers/core/v1" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -51,6 +57,7 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/job/metrics" "k8s.io/kubernetes/pkg/controller/job/util" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" "k8s.io/kubernetes/pkg/features" "k8s.io/utils/clock" "k8s.io/utils/ptr" @@ -112,6 +119,19 @@ type Controller struct { // podIndexer allows looking up pods by ControllerRef UID podIndexer cache.Indexer + // workloadIndexer allows looking up workloads by Job name + workloadIndexer cache.Indexer + // podGroupIndexer allows looking up pod groups by Workload name + podGroupIndexer cache.Indexer + + // Workload/PodGroup listers and synced checks for workload APIs. + // These are nil when the feature gate is disabled. + workloadLister schedulinglisters.WorkloadLister + podGroupLister schedulinglisters.PodGroupLister + + workloadStoreSynced cache.InformerSynced + podGroupStoreSynced cache.InformerSynced + // Jobs that need to be updated queue workqueue.TypedRateLimitingInterface[string] @@ -130,6 +150,9 @@ type Controller struct { // finishedJobExpectations contains the job ids for which the job status is finished // but the corresponding event is not yet received. finishedJobExpectations sync.Map + + // consistencyStore stores information about the consistency of the job. + consistencyStore consistencyutil.ConsistencyStore } type syncJobCtx struct { @@ -148,6 +171,10 @@ type syncJobCtx struct { podsWithDelayedDeletionPerIndex map[int]*v1.Pod terminating *int32 ready int32 + + // Set only when the Job is eligible for workload integration, as + // determined by shouldManageWorkloadForJob function. + podGroup *schedulingv1alpha2.PodGroup } type orphanPodKeyKind int @@ -167,21 +194,69 @@ type orphanPodKey struct { value string } +var ( + jobGroupResource = schema.GroupResource{ + Group: "batch", + Resource: "jobs", + } + podGroupResource = schema.GroupResource{ + Group: "", + Resource: "pods", + } +) + // NewController creates a new Job controller that keeps the relevant pods // in sync with their corresponding Job objects. -func NewController(ctx context.Context, podInformer coreinformers.PodInformer, jobInformer batchinformers.JobInformer, kubeClient clientset.Interface) (*Controller, error) { - return newControllerWithClock(ctx, podInformer, jobInformer, kubeClient, &clock.RealClock{}) -} - -func newControllerWithClock(ctx context.Context, podInformer coreinformers.PodInformer, jobInformer batchinformers.JobInformer, kubeClient clientset.Interface, clock clock.WithTicker) (*Controller, error) { +// workloadInformer and podGroupInformer may be nil when the WorkloadWithJob +// feature gate is disabled; they are required when the gate is enabled. +func NewController(ctx context.Context, kubeClient clientset.Interface, + podInformer coreinformers.PodInformer, jobInformer batchinformers.JobInformer, + workloadInformer schedulinginformers.WorkloadInformer, + podGroupInformer schedulinginformers.PodGroupInformer) (*Controller, error) { + + return newControllerWithClock(ctx, kubeClient, &clock.RealClock{}, + podInformer, jobInformer, workloadInformer, podGroupInformer) +} + +func newControllerWithClock(ctx context.Context, kubeClient clientset.Interface, clock clock.WithTicker, + podInformer coreinformers.PodInformer, jobInformer batchinformers.JobInformer, + workloadInformer schedulinginformers.WorkloadInformer, + podGroupInformer schedulinginformers.PodGroupInformer, +) (*Controller, error) { eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) logger := klog.FromContext(ctx) + var consistencyStore consistencyutil.ConsistencyStore + var podWriteCallback func(pod *v1.Pod, ds *metav1.OwnerReference) + if feature.DefaultFeatureGate.Enabled(features.StaleControllerConsistencyJob) { + consistencyStore = consistencyutil.NewConsistencyStore(map[schema.GroupResource]consistencyutil.LastSyncRVGetter{ + podGroupResource: podInformer.Informer().GetStore(), + jobGroupResource: jobInformer.Informer().GetStore(), + }) + podWriteCallback = func(pod *v1.Pod, job *metav1.OwnerReference) { + if job == nil { + return + } + consistencyStore.WroteAt( + types.NamespacedName{ + Namespace: pod.Namespace, + Name: job.Name, + }, + job.UID, + podGroupResource, + pod.ResourceVersion, + ) + } + } else { + consistencyStore = consistencyutil.NewNoopConsistencyStore() + } + jm := &Controller{ kubeClient: kubeClient, podControl: controller.RealPodControl{ KubeClient: kubeClient, Recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "job-controller"}), + OnWrite: podWriteCallback, }, expectations: controller.NewControllerExpectations(), finalizerExpectations: newUIDTrackingExpectations(), @@ -192,6 +267,7 @@ func newControllerWithClock(ctx context.Context, podInformer coreinformers.PodIn clock: clock, podBackoffStore: newBackoffStore(), finishedJobExpectations: sync.Map{}, + consistencyStore: consistencyStore, } if _, err := jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -236,6 +312,12 @@ func newControllerWithClock(ctx context.Context, podInformer coreinformers.PodIn jm.patchJobHandler = jm.patchJob jm.syncHandler = jm.syncJob + if feature.DefaultFeatureGate.Enabled(features.WorkloadWithJob) { + if err := jm.addSchedulingInformers(logger, workloadInformer, podGroupInformer); err != nil { + return nil, err + } + } + metrics.Register() return jm, nil @@ -261,7 +343,11 @@ func (jm *Controller) Run(ctx context.Context, workers int) { wg.Wait() }() - if !cache.WaitForNamedCacheSyncWithContext(ctx, jm.podStoreSynced, jm.jobStoreSynced) { + syncFuncs := []cache.InformerSynced{jm.podStoreSynced, jm.jobStoreSynced} + if feature.DefaultFeatureGate.Enabled(features.WorkloadWithJob) { + syncFuncs = append(syncFuncs, jm.workloadStoreSynced, jm.podGroupStoreSynced) + } + if !cache.WaitForNamedCacheSyncWithContext(ctx, syncFuncs...) { return } @@ -557,6 +643,10 @@ func (jm *Controller) deleteJob(logger klog.Logger, obj interface{}) { return } } + jm.consistencyStore.Clear(types.NamespacedName{ + Namespace: jobObj.Namespace, + Name: jobObj.Name, + }, jobObj.UID) jm.finishedJobExpectations.Delete(jobObj.UID) jm.enqueueLabelSelector(jobObj) @@ -832,12 +922,28 @@ func (jm *Controller) syncJob(ctx context.Context, key string) (rErr error) { if len(ns) == 0 || len(name) == 0 { return fmt.Errorf("invalid job key %q: either namespace or name is missing", key) } + jobNamespacedName := types.NamespacedName{Namespace: ns, Name: name} + // If our writes have not yet been observed by our informers, we should not + // continue since our reads will not be in sync with our previously enacted + // state. + if err := jm.consistencyStore.EnsureReady(jobNamespacedName); err != nil { + var consistencyErr *consistencyutil.ConsistencyError + if errors.As(err, &consistencyErr) { + metrics.JobRequeueSkips.WithLabelValues( + consistencyErr.GroupResource.Group, + consistencyErr.GroupResource.Resource, + ).Inc() + } + return err + } + sharedJob, err := jm.jobLister.Jobs(ns).Get(name) if err != nil { if apierrors.IsNotFound(err) { logger.V(4).Info("Job has been deleted", "key", key) jm.expectations.DeleteExpectations(logger, key) jm.finalizerExpectations.deleteExpectations(logger, key) + jm.consistencyStore.Clear(jobNamespacedName, "") err := jm.podBackoffStore.removeBackoffRecord(key) if err != nil { @@ -869,6 +975,10 @@ func (jm *Controller) syncJob(ctx context.Context, key string) (rErr error) { // re-syncing here as the record has to be removed for finished/deleted jobs return fmt.Errorf("error removing backoff record %w", err) } + jm.consistencyStore.Clear(types.NamespacedName{ + Namespace: job.Namespace, + Name: job.Name, + }, job.UID) jm.finishedJobExpectations.Delete(job.UID) return nil } @@ -908,6 +1018,17 @@ func (jm *Controller) syncJob(ctx context.Context, key string) (rErr error) { if err != nil { return err } + + // ensure Workload and PodGroup exist for eligible Jobs. + // This must happen before pod management so that pods can reference the PodGroup. + var podGroup *schedulingv1alpha2.PodGroup + if shouldManageWorkloadForJob(&job) { + _, podGroup, err = jm.ensureWorkloadAndPodGroup(ctx, &job, pods) + if err != nil { + return fmt.Errorf("ensuring Workload and PodGroup for Job %s/%s: %w", job.Namespace, job.Name, err) + } + } + activePods := controller.FilterActivePods(logger, pods) jobCtx := &syncJobCtx{ job: &job, @@ -916,6 +1037,7 @@ func (jm *Controller) syncJob(ctx context.Context, key string) (rErr error) { ready: countReadyPods(activePods), uncounted: newUncountedTerminatedPods(*job.Status.UncountedTerminatedPods), expectedRmFinalizers: jm.finalizerExpectations.getExpectedUIDs(key), + podGroup: podGroup, } if trackTerminatingPods(&job) { jobCtx.terminating = ptr.To(controller.CountTerminatingPods(pods)) @@ -1766,6 +1888,25 @@ func (jm *Controller) manageJob(ctx context.Context, job *batch.Job, jobCtx *syn } podTemplate.Finalizers = appendJobCompletionFinalizerIfNotFound(podTemplate.Finalizers) + if jobCtx.podGroup != nil { + pg := jobCtx.podGroup + podTemplate.Spec.SchedulingGroup = &v1.PodSchedulingGroup{ + PodGroupName: &pg.Name, + } + podTemplate.OwnerReferences = append(podTemplate.OwnerReferences, + // Non-controller ownerRef to PodGroup (the controller ownerRef + // points to the Job and is set by CreatePodsWithGenerateName). + // BlockOwnerDeletion is intentionally left nil (false) so that + // PodGroup deletion is not blocked by surviving pods, because + // the Job's cascading GC already ensures proper cleanup ordering. + metav1.OwnerReference{ + APIVersion: schedulingv1alpha2.SchemeGroupVersion.String(), + Kind: "PodGroup", + Name: pg.Name, + UID: pg.UID, + }) + } + // Counters for pod creation status (used by the job_pods_creation_total metric) var creationsSucceeded, creationsFailed int32 = 0, 0 @@ -1889,12 +2030,31 @@ func activePodsForRemoval(job *batch.Job, pods []*v1.Pod, rmAtLeast int) []*v1.P // updateJobStatus calls the API to update the job status. func (jm *Controller) updateJobStatus(ctx context.Context, job *batch.Job) (*batch.Job, error) { - return jm.kubeClient.BatchV1().Jobs(job.Namespace).UpdateStatus(ctx, job, metav1.UpdateOptions{}) + job, err := jm.kubeClient.BatchV1().Jobs(job.Namespace).UpdateStatus(ctx, job, metav1.UpdateOptions{}) + if err != nil { + return nil, err + } + jm.consistencyStore.WroteAt( + types.NamespacedName{Name: job.Name, Namespace: job.Namespace}, + job.UID, + jobGroupResource, + job.ResourceVersion, + ) + return job, err } func (jm *Controller) patchJob(ctx context.Context, job *batch.Job, data []byte) error { - _, err := jm.kubeClient.BatchV1().Jobs(job.Namespace).Patch( + job, err := jm.kubeClient.BatchV1().Jobs(job.Namespace).Patch( ctx, job.Name, types.StrategicMergePatchType, data, metav1.PatchOptions{}) + if err != nil { + return err + } + jm.consistencyStore.WroteAt( + types.NamespacedName{Name: job.Name, Namespace: job.Namespace}, + job.UID, + jobGroupResource, + job.ResourceVersion, + ) return err } @@ -1931,10 +2091,8 @@ func getCompletionMode(job *batch.Job) string { } func appendJobCompletionFinalizerIfNotFound(finalizers []string) []string { - for _, fin := range finalizers { - if fin == batch.JobTrackingFinalizer { - return finalizers - } + if slices.Contains(finalizers, batch.JobTrackingFinalizer) { + return finalizers } return append(finalizers, batch.JobTrackingFinalizer) } diff --git a/pkg/controller/job/job_controller_test.go b/pkg/controller/job/job_controller_test.go index a580b80b76872..5c8797ea17611 100644 --- a/pkg/controller/job/job_controller_test.go +++ b/pkg/controller/job/job_controller_test.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "math" + "slices" "sort" "strconv" "testing" @@ -31,9 +32,11 @@ import ( batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" @@ -133,7 +136,12 @@ func newControllerFromClient(ctx context.Context, t *testing.T, kubeClient clien func newControllerFromClientWithClock(ctx context.Context, t *testing.T, kubeClient clientset.Interface, resyncPeriod controller.ResyncPeriodFunc, clock clock.WithTicker) (*Controller, informers.SharedInformerFactory) { t.Helper() sharedInformers := informers.NewSharedInformerFactory(kubeClient, resyncPeriod()) - jm, err := newControllerWithClock(ctx, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), kubeClient, clock) + jm, err := newControllerWithClock(ctx, kubeClient, clock, + sharedInformers.Core().V1().Pods(), + sharedInformers.Batch().V1().Jobs(), + sharedInformers.Scheduling().V1alpha2().Workloads(), + sharedInformers.Scheduling().V1alpha2().PodGroups(), + ) if err != nil { t.Fatalf("Error creating Job controller: %v", err) } @@ -4198,7 +4206,6 @@ func TestSyncJobWithJobPodFailurePolicy(t *testing.T) { } sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) for i, pod := range tc.pods { - pod := pod pb := podBuilder{Pod: &pod}.name(fmt.Sprintf("mypod-%d", i)).job(job) if job.Spec.CompletionMode != nil && *job.Spec.CompletionMode == batch.IndexedCompletion { pb.index(fmt.Sprintf("%v", i)) @@ -5904,7 +5911,6 @@ func TestSyncJobWithJobBackoffLimitPerIndex(t *testing.T) { } sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) for i, pod := range tc.pods { - pod := pod pb := podBuilder{Pod: &pod}.name(fmt.Sprintf("mypod-%d", i)).job(job) if job.Spec.CompletionMode != nil && *job.Spec.CompletionMode == batch.IndexedCompletion { pb.index(fmt.Sprintf("%v", getCompletionIndex(pod.Annotations))) @@ -6601,9 +6607,9 @@ func TestSyncJobExpectations(t *testing.T) { } func TestWatchJobs(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) + logger, ctx := ktesting.NewTestContext(t) clientset := fake.NewClientset() - fakeWatch := watch.NewFake() + fakeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) clientset.PrependWatchReactor("jobs", core.DefaultWatchReactor(fakeWatch, nil)) manager, sharedInformerFactory := newControllerFromClient(ctx, t, clientset, controller.NoResyncPeriodFunc) manager.podStoreSynced = alwaysReady @@ -6645,10 +6651,10 @@ func TestWatchJobs(t *testing.T) { } func TestWatchPods(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) + logger, ctx := ktesting.NewTestContext(t) testJob := newJob(2, 2, 6, batch.NonIndexedCompletion) clientset := fake.NewClientset(testJob) - fakeWatch := watch.NewFake() + fakeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) clientset.PrependWatchReactor("pods", core.DefaultWatchReactor(fakeWatch, nil)) manager, sharedInformerFactory := newControllerFromClient(ctx, t, clientset, controller.NoResyncPeriodFunc) manager.podStoreSynced = alwaysReady @@ -6696,7 +6702,7 @@ func TestWatchOrphanPods(t *testing.T) { t.Cleanup(cancel) clientset := fake.NewClientset() sharedInformers := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc()) - manager, err := NewController(ctx, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), clientset) + manager, err := NewController(ctx, clientset, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), sharedInformers.Scheduling().V1alpha2().Workloads(), sharedInformers.Scheduling().V1alpha2().PodGroups()) if err != nil { t.Fatalf("Error creating Job controller: %v", err) } @@ -6767,7 +6773,7 @@ func TestSyncOrphanPod(t *testing.T) { _, ctx := ktesting.NewTestContext(t) clientset := fake.NewClientset() sharedInformers := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc()) - manager, err := NewController(ctx, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), clientset) + manager, err := NewController(ctx, clientset, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), sharedInformers.Scheduling().V1alpha2().Workloads(), sharedInformers.Scheduling().V1alpha2().PodGroups()) if err != nil { t.Fatalf("Error creating Job controller: %v", err) } @@ -7689,7 +7695,7 @@ func TestFinalizersRemovedExpectations(t *testing.T) { _, ctx := ktesting.NewTestContext(t) clientset := fake.NewClientset() sharedInformers := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc()) - manager, err := NewController(ctx, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), clientset) + manager, err := NewController(ctx, clientset, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), sharedInformers.Scheduling().V1alpha2().Workloads(), sharedInformers.Scheduling().V1alpha2().PodGroups()) if err != nil { t.Fatalf("Error creating Job controller: %v", err) } @@ -7793,7 +7799,7 @@ func TestFinalizerCleanup(t *testing.T) { clientset := fake.NewClientset() sharedInformers := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc()) - manager, err := NewController(ctx, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), clientset) + manager, err := NewController(ctx, clientset, sharedInformers.Core().V1().Pods(), sharedInformers.Batch().V1().Jobs(), sharedInformers.Scheduling().V1alpha2().Workloads(), sharedInformers.Scheduling().V1alpha2().PodGroups()) if err != nil { t.Fatalf("Error creating Job controller: %v", err) } @@ -8026,10 +8032,8 @@ func (pb podBuilder) phase(p v1.PodPhase) podBuilder { } func (pb podBuilder) trackingFinalizer() podBuilder { - for _, f := range pb.Finalizers { - if f == batch.JobTrackingFinalizer { - return pb - } + if slices.Contains(pb.Finalizers, batch.JobTrackingFinalizer) { + return pb } pb.Finalizers = append(pb.Finalizers, batch.JobTrackingFinalizer) return pb @@ -8045,6 +8049,201 @@ func (pb podBuilder) customDeletionTimestamp(t time.Time) podBuilder { return pb } +func TestSyncJobPodSchedulingGroup(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + gangJob := newGangSchedulingJob("test-job", 4) + gangJob.Spec.BackoffLimit = ptr.To[int32](6) + templateName := fmt.Sprintf("%s-pgt-%d", gangJob.Name, 0) + + makeWorkload := func(job *batch.Job) *schedulingv1alpha2.Workload { + return &schedulingv1alpha2.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: computeWorkloadName(job), + Namespace: metav1.NamespaceDefault, + UID: types.UID("wl-uid"), + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(job, controllerKind)}, + }, + Spec: schedulingv1alpha2.WorkloadSpec{ + ControllerRef: &schedulingv1alpha2.TypedLocalObjectReference{ + APIGroup: "batch", + Kind: "Job", + Name: job.Name, + }, + PodGroupTemplates: []schedulingv1alpha2.PodGroupTemplate{ + {Name: templateName}, + }, + }, + } + } + + makePodGroup := func(job *batch.Job, wl *schedulingv1alpha2.Workload) *schedulingv1alpha2.PodGroup { + return &schedulingv1alpha2.PodGroup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "scheduling.k8s.io/v1alpha2", + Kind: "PodGroup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: computePodGroupName(wl.Name, templateName), + Namespace: metav1.NamespaceDefault, + UID: types.UID("pg-uid"), + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(job, controllerKind)}, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + PodGroupTemplateRef: &schedulingv1alpha2.PodGroupTemplateReference{ + Workload: &schedulingv1alpha2.WorkloadPodGroupTemplateReference{ + WorkloadName: wl.Name, + PodGroupTemplateName: templateName, + }, + }, + }, + } + } + + gangWorkload := makeWorkload(gangJob) + gangPodGroup := makePodGroup(gangJob, gangWorkload) + + testCases := map[string]struct { + job *batch.Job + existingWorkload *schedulingv1alpha2.Workload + existingPodGroup *schedulingv1alpha2.PodGroup + + workloadWithJob bool + + wantSchedulingGroupName string + wantPodGroupOwnerRef bool + wantCreations int32 + }{ + "gang-eligible job with PodGroup: pods get schedulingGroup and ownerRef": { + job: gangJob, + workloadWithJob: true, + existingWorkload: gangWorkload, + existingPodGroup: gangPodGroup, + + wantSchedulingGroupName: gangPodGroup.Name, + wantPodGroupOwnerRef: true, + wantCreations: 4, + }, + "non-eligible job: no schedulingGroup on pods": { + job: newJob(2, 5, 6, batch.NonIndexedCompletion), + workloadWithJob: true, + wantCreations: 2, + }, + "job with pre-existing schedulingGroup: preserved without PodGroup ownerRef": { + job: func() *batch.Job { + j := newGangSchedulingJob("opt-out-job", 4) + j.Spec.BackoffLimit = ptr.To[int32](6) + j.Spec.Template.Spec.SchedulingGroup = &v1.PodSchedulingGroup{ + PodGroupName: ptr.To("external-pg"), + } + return j + }(), + workloadWithJob: true, + wantSchedulingGroupName: "external-pg", + wantCreations: 4, + }, + "suspended gang-eligible job: discovers scheduling objects but creates no pods": { + job: func() *batch.Job { + j := newGangSchedulingJob("suspended-job", 4) + j.Spec.BackoffLimit = ptr.To[int32](6) + j.Spec.Suspend = ptr.To(true) + return j + }(), + workloadWithJob: true, + existingWorkload: func() *schedulingv1alpha2.Workload { + j := newGangSchedulingJob("suspended-job", 4) + return makeWorkload(j) + }(), + existingPodGroup: func() *schedulingv1alpha2.PodGroup { + j := newGangSchedulingJob("suspended-job", 4) + wl := makeWorkload(j) + return makePodGroup(j, wl) + }(), + wantCreations: 0, + }, + "feature gate disabled: gang-eligible job creates pods without scheduling objects": { + job: func() *batch.Job { + j := newGangSchedulingJob("gang-job", 4) + j.Spec.BackoffLimit = ptr.To[int32](6) + return j + }(), + wantCreations: 4, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + var objs []runtime.Object + if tc.existingWorkload != nil { + objs = append(objs, tc.existingWorkload) + } + if tc.existingPodGroup != nil { + objs = append(objs, tc.existingPodGroup) + } + clientSet := fake.NewClientset(objs...) + // TODO: this will be removed in 1.38. + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, utilversion.MustParse("1.36")) + featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: tc.workloadWithJob, + features.WorkloadWithJob: tc.workloadWithJob, + }) + + jm, sharedInformers := newControllerFromClient(ctx, t, clientSet, controller.NoResyncPeriodFunc) + + fakePodControl := controller.FakePodControl{} + jm.podControl = &fakePodControl + jm.updateStatusHandler = func(ctx context.Context, job *batch.Job) (*batch.Job, error) { + return job, nil + } + + sharedInformers.Start(ctx.Done()) + sharedInformers.WaitForCacheSync(ctx.Done()) + + if err := sharedInformers.Batch().V1().Jobs().Informer().GetIndexer().Add(tc.job); err != nil { + t.Fatalf("Failed to insert job in index: %v", err) + } + + err := jm.syncJob(ctx, testutil.GetKey(tc.job, t)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if int32(len(fakePodControl.Templates)) != tc.wantCreations { + t.Fatalf("expected %d pod creations, got %d", tc.wantCreations, len(fakePodControl.Templates)) + } + + for i, tmpl := range fakePodControl.Templates { + if tc.wantSchedulingGroupName != "" { + if tmpl.Spec.SchedulingGroup == nil { + t.Errorf("pod template [%d]: expected schedulingGroup, got nil", i) + continue + } + if tmpl.Spec.SchedulingGroup.PodGroupName == nil || *tmpl.Spec.SchedulingGroup.PodGroupName != tc.wantSchedulingGroupName { + got := "" + if tmpl.Spec.SchedulingGroup.PodGroupName != nil { + got = *tmpl.Spec.SchedulingGroup.PodGroupName + } + t.Errorf("pod template [%d]: schedulingGroup.podGroupName = %q, want %q", i, got, tc.wantSchedulingGroupName) + } + } else if tmpl.Spec.SchedulingGroup != nil { + t.Errorf("pod template [%d]: expected nil schedulingGroup, got %v", i, tmpl.Spec.SchedulingGroup) + } + + hasPGOwnerRef := false + for _, ref := range tmpl.OwnerReferences { + if ref.Kind == "PodGroup" { + hasPGOwnerRef = true + break + } + } + if tc.wantPodGroupOwnerRef != hasPGOwnerRef { + t.Errorf("pod template [%d]: PodGroup ownerRef present = %v, want %v", i, hasPGOwnerRef, tc.wantPodGroupOwnerRef) + } + } + }) + } +} + func setDurationDuringTest(val *time.Duration, newVal time.Duration) func() { origVal := *val *val = newVal diff --git a/pkg/controller/job/job_scheduling_manager.go b/pkg/controller/job/job_scheduling_manager.go new file mode 100644 index 0000000000000..f755c60723c67 --- /dev/null +++ b/pkg/controller/job/job_scheduling_manager.go @@ -0,0 +1,477 @@ +/* +Copyright 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 job + +import ( + "context" + "fmt" + "hash/fnv" + + batch "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apiserver/pkg/util/feature" + schedulinginformers "k8s.io/client-go/informers/scheduling/v1alpha2" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" + "k8s.io/klog/v2" + jobutil "k8s.io/kubernetes/pkg/controller/job/util" + "k8s.io/kubernetes/pkg/features" + hashutil "k8s.io/kubernetes/pkg/util/hash" +) + +const ( + // Workload.spec.controllerRef and PodGroup.spec.podGroupTemplateRef + // carry only names (no UIDs), so these indexes use namespace/name as + // the key. Callers must still verify ownership via ownerRef UID checks + // before acting on results. + workloadByJobNameIndexKey = "workloadByJobName" + podGroupByWorkloadNameIndexKey = "podGroupByWorkloadName" +) + +// shouldManageWorkloadForJob returns true if the Job should have Workload and PodGroup +// created. It checks: +// - parallelism > 1 +// - completionMode == Indexed +// - completions == parallelism +// - schedulingGroup is not already set on the pod template +// - WorkloadWithJob feature gate is enabled +func shouldManageWorkloadForJob(job *batch.Job) bool { + if !feature.DefaultFeatureGate.Enabled(features.WorkloadWithJob) { + return false + } + return jobutil.IsGangSchedulingCandidate( + job.Spec.Parallelism, + job.Spec.Completions, + isIndexedJob(job), + job.Spec.Template.Spec.SchedulingGroup != nil, + ) +} + +// ensureWorkloadAndPodGroup discovers or creates Workload and PodGroup for the given Job. +// Returns both objects when workload integration is active, or (nil, nil, nil) +// when the Job should fall back to default scheduling. +// +// The flow is: +// 1. Get or create a Workload owned by this controller. +// 2. Get or create a PodGroup owned by this controller. +func (jm *Controller) ensureWorkloadAndPodGroup(ctx context.Context, job *batch.Job, pods []*v1.Pod) (*schedulingv1alpha2.Workload, *schedulingv1alpha2.PodGroup, error) { + wl, err := jm.getOrCreateWorkload(ctx, job, pods) + if err != nil || wl == nil { + return nil, nil, err + } + pg, err := jm.getOrCreatePodGroup(ctx, job, wl) + return wl, pg, err +} + +// isNewJob returns true if the Job has never started. This prevents creating +// scheduling objects for Jobs that predate the feature gate being enabled. +// +// This heuristic is intentionally conservative for alpha and will be replaced +// by a more robust mechanism before beta. +// +// We check pods, StartTime, terminal counters, and the Suspended condition +// because each alone has gaps, pods may be GC'd, StartTime is reset on +// suspend, but Succeeded/Failed persist. A Job that was suspended early +// would pass the other checks, so the Suspended condition catches that case. +func isNewJob(job *batch.Job, pods []*v1.Pod) bool { + if len(pods) != 0 || + job.Status.StartTime != nil || + job.Status.Succeeded != 0 || + job.Status.Failed != 0 { + return false + } + return findConditionByType(job.Status.Conditions, batch.JobSuspended) == nil +} + +// getOrCreateWorkload returns the Workload for this Job, creating one if needed. +// Returns (nil, nil) when the Job should fall back to default scheduling +// (i.e., ambiguous matches, not owned by this controller, unsupported structure, or +// not a new Job). +func (jm *Controller) getOrCreateWorkload(ctx context.Context, job *batch.Job, pods []*v1.Pod) (*schedulingv1alpha2.Workload, error) { + logger := klog.FromContext(ctx) + + var result *schedulingv1alpha2.Workload + err := retry.OnError(retry.DefaultRetry, apierrors.IsAlreadyExists, func() error { + matched, err := jm.listWorkloadsForJob(job) + if err != nil { + logger.V(2).Info("Failed to list Workloads, falling back to default scheduling", + "job", klog.KObj(job), "err", err) + return nil + } + + switch len(matched) { + case 0: + if !isNewJob(job, pods) { + return nil + } + wl, err := jm.createWorkloadForJob(ctx, job) + if err != nil { + return err + } + result = wl + return nil + case 1: + wl := matched[0] + if !metav1.IsControlledBy(wl, job) { + logger.V(2).Info("Workload not owned by this Job, skipping workload integration", + "job", klog.KObj(job), "workload", klog.KObj(wl)) + return nil + } + result, err = jm.validateWorkloadStructure(logger, job, wl) + return err + default: + utilruntime.HandleError(fmt.Errorf("found %d Workloads referencing Job %s/%s, falling back to default scheduling", + len(matched), job.Namespace, job.Name)) + return nil + } + }) + return result, err +} + +// validateWorkloadStructure checks alpha constraints on the Workload. +// Returns (nil, nil) if the structure is unsupported. +// +// TODO(beta): This only validates the initial shape. A user (or another +// controller) could mutate the Workload after creation, breaking our +// assumptions (e.g., adding/removing PodGroupTemplates, changing the +// scheduling policy). Before beta we need to decide how to handle such +// mutations; overwrite them back to the expected state, fall back to +// default scheduling, or reject the update via admission. +func (jm *Controller) validateWorkloadStructure(logger klog.Logger, job *batch.Job, wl *schedulingv1alpha2.Workload) (*schedulingv1alpha2.Workload, error) { + if len(wl.Spec.PodGroupTemplates) != 1 { + logger.V(2).Info("Workload has unsupported number of PodGroupTemplates, falling back to default scheduling", + "job", klog.KObj(job), "workload", klog.KObj(wl), "count", len(wl.Spec.PodGroupTemplates)) + return nil, nil + } + return wl, nil +} + +// getOrCreatePodGroup returns the PodGroup for this Workload, creating one if needed. +// Returns (nil, nil) when the Job should fall back to default scheduling +// (ambiguous matches or not owned by this controller). +func (jm *Controller) getOrCreatePodGroup(ctx context.Context, job *batch.Job, workload *schedulingv1alpha2.Workload) (*schedulingv1alpha2.PodGroup, error) { + logger := klog.FromContext(ctx) + + var result *schedulingv1alpha2.PodGroup + err := retry.OnError(retry.DefaultRetry, apierrors.IsAlreadyExists, func() error { + matched, err := jm.listPodGroupsForWorkload(workload) + if err != nil { + logger.V(2).Info("Failed to list PodGroups, falling back to default scheduling", + "job", klog.KObj(job), "workload", klog.KObj(workload), "err", err) + return nil + } + + switch len(matched) { + case 0: + pg, err := jm.createPodGroupForWorkload(ctx, job, workload) + if err != nil { + return err + } + result = pg + return nil + case 1: + pg := matched[0] + if !metav1.IsControlledBy(pg, job) { + logger.V(2).Info("PodGroup not owned by this Job, skipping workload integration", + "job", klog.KObj(job), "workload", klog.KObj(workload), "podGroup", klog.KObj(pg)) + return nil + } + result = pg + return nil + default: + utilruntime.HandleError(fmt.Errorf("found %d PodGroups referencing Workload %s/%s, falling back to default scheduling", + len(matched), workload.Namespace, workload.Name)) + return nil + } + }) + return result, err +} + +// listWorkloadsForJob returns all Workloads in the Job's namespace whose +// spec.controllerRef points to the given Job. +func (jm *Controller) listWorkloadsForJob(job *batch.Job) ([]*schedulingv1alpha2.Workload, error) { + if jm.workloadIndexer == nil { + return nil, fmt.Errorf("workload indexer is not configured") + } + key := namespacedNameIndexKey(job.Namespace, job.Name) + all, err := jm.workloadIndexer.ByIndex(workloadByJobNameIndexKey, key) + if err != nil { + return nil, err + } + matched := make([]*schedulingv1alpha2.Workload, 0, len(all)) + for _, wl := range all { + workload, ok := wl.(*schedulingv1alpha2.Workload) + if !ok { + continue + } + matched = append(matched, workload) + } + return matched, nil +} + +// listPodGroupsForWorkload returns all PodGroups in the Workload's namespace +// whose spec.podGroupTemplateRef references the given Workload. +func (jm *Controller) listPodGroupsForWorkload(workload *schedulingv1alpha2.Workload) ([]*schedulingv1alpha2.PodGroup, error) { + if jm.podGroupIndexer == nil { + return nil, fmt.Errorf("podgroup indexer is not configured") + } + key := namespacedNameIndexKey(workload.Namespace, workload.Name) + all, err := jm.podGroupIndexer.ByIndex(podGroupByWorkloadNameIndexKey, key) + if err != nil { + return nil, err + } + matched := make([]*schedulingv1alpha2.PodGroup, 0, len(all)) + for _, pg := range all { + podGroup, ok := pg.(*schedulingv1alpha2.PodGroup) + if !ok { + continue + } + matched = append(matched, podGroup) + } + return matched, nil +} + +// buildPodGroupTemplates constructs the PodGroupTemplate slice for a Job's Workload. +// For alpha, a single template is created covering all of the Job's pods. +// This is the single place to extend when supporting multiple PodGroupTemplates per +// Workload in future. +func buildPodGroupTemplates(job *batch.Job) []schedulingv1alpha2.PodGroupTemplate { + return []schedulingv1alpha2.PodGroupTemplate{ + { + Name: fmt.Sprintf("%s-pgt-%d", job.Name, 0), + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{ + MinCount: *job.Spec.Parallelism, + }, + }, + }, + } +} + +// createWorkloadForJob creates a new Workload object for the given Job. +func (jm *Controller) createWorkloadForJob(ctx context.Context, job *batch.Job) (*schedulingv1alpha2.Workload, error) { + workloadName := computeWorkloadName(job) + templates := buildPodGroupTemplates(job) + + workload := &schedulingv1alpha2.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: workloadName, + Namespace: job.Namespace, + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(job, controllerKind), + }, + }, + Spec: schedulingv1alpha2.WorkloadSpec{ + ControllerRef: &schedulingv1alpha2.TypedLocalObjectReference{ + APIGroup: batch.SchemeGroupVersion.Group, + Kind: "Job", + Name: job.Name, + }, + PodGroupTemplates: templates, + }, + } + + created, err := jm.kubeClient.SchedulingV1alpha2().Workloads(job.Namespace).Create(ctx, workload, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + + jm.recorder.Eventf(job, v1.EventTypeNormal, "WorkloadCreated", + "Created Workload %s for Job %s", created.Name, job.Name) + return created, nil +} + +// createPodGroupForWorkload creates a new PodGroup for the given Workload. +func (jm *Controller) createPodGroupForWorkload(ctx context.Context, job *batch.Job, workload *schedulingv1alpha2.Workload) (*schedulingv1alpha2.PodGroup, error) { + if len(workload.Spec.PodGroupTemplates) == 0 { + return nil, fmt.Errorf("workload %s/%s has no PodGroupTemplates", workload.Namespace, workload.Name) + } + + template := workload.Spec.PodGroupTemplates[0] + podGroupName := computePodGroupName(workload.Name, template.Name) + + podGroup := &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: podGroupName, + Namespace: job.Namespace, + OwnerReferences: []metav1.OwnerReference{ + // Controller ownerRef to Job + *metav1.NewControllerRef(job, controllerKind), + // Non-controller ownerRef to Workload. + // BlockOwnerDeletion is intentionally left nil (false) so that + // Workload deletion is not blocked by surviving PodGroups. + { + APIVersion: schedulingv1alpha2.SchemeGroupVersion.String(), + Kind: "Workload", + Name: workload.Name, + UID: workload.UID, + }, + }, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + PodGroupTemplateRef: &schedulingv1alpha2.PodGroupTemplateReference{ + Workload: &schedulingv1alpha2.WorkloadPodGroupTemplateReference{ + WorkloadName: workload.Name, + PodGroupTemplateName: template.Name, + }, + }, + SchedulingPolicy: *template.SchedulingPolicy.DeepCopy(), + }, + } + + return jm.kubeClient.SchedulingV1alpha2().PodGroups(job.Namespace).Create(ctx, podGroup, metav1.CreateOptions{}) +} + +// computeWorkloadName generates a deterministic name for a Workload associated with a Job. +// The pattern is: - +// The hash is derived from the Job UID to ensure uniqueness and stability across restarts. +func computeWorkloadName(job *batch.Job) string { + hasher := fnv.New32a() + hashutil.DeepHashObject(hasher, job.UID) + hash := rand.SafeEncodeString(fmt.Sprint(hasher.Sum32())) + + // hash is ~10 chars; include separator "-" + maxPrefixLen := validation.DNS1123SubdomainMaxLength - len(hash) - 1 + prefix := job.Name + if len(prefix) > maxPrefixLen { + prefix = prefix[:maxPrefixLen] + } + return fmt.Sprintf("%s-%s", prefix, hash) +} + +// computePodGroupName generates a deterministic name for a PodGroup associated with a Workload. +// The pattern is: -- +// The hash is derived from the workload name and template name for uniqueness. +func computePodGroupName(workloadName, templateName string) string { + hasher := fnv.New32a() + // hash the full combination for uniqueness. + hasher.Write([]byte(workloadName)) + hasher.Write([]byte(templateName)) + hash := rand.SafeEncodeString(fmt.Sprint(hasher.Sum32())) + + // Truncate workloadName and templateName to fit within the DNS subdomain + // max length, reserving space for the hash and two "-" separators. + maxAvailable := validation.DNS1123SubdomainMaxLength - len(hash) - 2 + wl := workloadName + tpl := templateName + + if len(wl)+len(tpl) > maxAvailable { + // only truncate the part that exceeds its fair share. + // give each part up to half the space; if one is short, the other + // gets the remainder. + half := maxAvailable / 2 + switch { + case len(wl) <= half: + // workload name fits in its half, give template the rest. + tpl = tpl[:maxAvailable-len(wl)] + case len(tpl) <= half: + // template name fits in its half, give workload the rest. + wl = wl[:maxAvailable-len(tpl)] + default: + // both exceed half, split evenly (workload gets the extra char if odd). + wl = wl[:maxAvailable-half] + tpl = tpl[:half] + } + } + + return fmt.Sprintf("%s-%s-%s", wl, tpl, hash) +} + +// addSchedulingInformers wires up Workload and PodGroup informers +// and indexers so that changes to those objects re-enqueue the owning Job. +func (jm *Controller) addSchedulingInformers(logger klog.Logger, workloadInformer schedulinginformers.WorkloadInformer, podGroupInformer schedulinginformers.PodGroupInformer) error { + if workloadInformer == nil { + return fmt.Errorf("workload informer is required when the feature gate %q is enabled", features.WorkloadWithJob) + } + if podGroupInformer == nil { + return fmt.Errorf("pod group informer is required when the feature gate %q is enabled", features.WorkloadWithJob) + } + if err := workloadInformer.Informer().AddIndexers(cache.Indexers{ + workloadByJobNameIndexKey: workloadByJobNameIndexFunc, + }); err != nil { + return fmt.Errorf("adding Workload indexer: %w", err) + } + if err := podGroupInformer.Informer().AddIndexers(cache.Indexers{ + podGroupByWorkloadNameIndexKey: podGroupByWorkloadNameIndexFunc, + }); err != nil { + return fmt.Errorf("adding PodGroup indexer: %w", err) + } + jm.workloadLister = workloadInformer.Lister() + jm.workloadIndexer = workloadInformer.Informer().GetIndexer() + jm.workloadStoreSynced = workloadInformer.Informer().HasSynced + + jm.podGroupLister = podGroupInformer.Lister() + jm.podGroupIndexer = podGroupInformer.Informer().GetIndexer() + jm.podGroupStoreSynced = podGroupInformer.Informer().HasSynced + + return nil +} + +func namespacedNameIndexKey(namespace, name string) string { + return cache.NewObjectName(namespace, name).String() +} + +// workloadControllerRefJobName returns the Job name from the Workload's +// spec.controllerRef if it points to a batch Job, or "" otherwise. +func workloadControllerRefJobName(wl *schedulingv1alpha2.Workload) string { + if wl.Spec.ControllerRef == nil || + wl.Spec.ControllerRef.APIGroup != batch.SchemeGroupVersion.Group || + wl.Spec.ControllerRef.Kind != "Job" || + wl.Spec.ControllerRef.Name == "" { + return "" + } + return wl.Spec.ControllerRef.Name +} + +// podGroupWorkloadName returns the workload name from the PodGroup's +// spec.podGroupTemplateRef, or "" if the reference is not set. +func podGroupWorkloadName(pg *schedulingv1alpha2.PodGroup) string { + if pg.Spec.PodGroupTemplateRef == nil || + pg.Spec.PodGroupTemplateRef.Workload == nil || + pg.Spec.PodGroupTemplateRef.Workload.WorkloadName == "" { + return "" + } + return pg.Spec.PodGroupTemplateRef.Workload.WorkloadName +} + +func workloadByJobNameIndexFunc(obj interface{}) ([]string, error) { + wl, ok := obj.(*schedulingv1alpha2.Workload) + if !ok { + return nil, fmt.Errorf("unexpected object type %T", obj) + } + if name := workloadControllerRefJobName(wl); name != "" { + return []string{namespacedNameIndexKey(wl.Namespace, name)}, nil + } + return nil, nil +} + +func podGroupByWorkloadNameIndexFunc(obj interface{}) ([]string, error) { + pg, ok := obj.(*schedulingv1alpha2.PodGroup) + if !ok { + return nil, fmt.Errorf("unexpected object type %T", obj) + } + if name := podGroupWorkloadName(pg); name != "" { + return []string{namespacedNameIndexKey(pg.Namespace, name)}, nil + } + return nil, nil +} diff --git a/pkg/controller/job/job_scheduling_manager_test.go b/pkg/controller/job/job_scheduling_manager_test.go new file mode 100644 index 0000000000000..510c0921a7893 --- /dev/null +++ b/pkg/controller/job/job_scheduling_manager_test.go @@ -0,0 +1,783 @@ +/* +Copyright 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 job + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + + batch "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation" + utilversion "k8s.io/apimachinery/pkg/util/version" + "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + k8stesting "k8s.io/client-go/testing" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/klog/v2/ktesting" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/features" + "k8s.io/utils/ptr" +) + +// newGangSchedulingJob creates a Job that meets gang scheduling eligibility criteria. +func newGangSchedulingJob(name string, parallelism int32) *batch.Job { + indexed := batch.IndexedCompletion + return &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + UID: types.UID(name + "-uid"), + }, + Spec: batch.JobSpec{ + Parallelism: ptr.To(parallelism), + Completions: ptr.To(parallelism), + CompletionMode: &indexed, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"job-name": name}, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"job-name": name}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Image: "test"}}, + }, + }, + }, + } +} + +func newWorkloadJobControllerRef(jobName string) *schedulingv1alpha2.TypedLocalObjectReference { + return &schedulingv1alpha2.TypedLocalObjectReference{ + APIGroup: batch.SchemeGroupVersion.Group, + Kind: "Job", + Name: jobName, + } +} + +// newControllerWithSchedulingInformers creates a Job controller with Workload and PodGroup +// informers wired up, suitable for testing scheduling manager functionality. +func newControllerWithSchedulingInformers(ctx context.Context, t *testing.T, kubeClient *fake.Clientset) (*Controller, informers.SharedInformerFactory) { + t.Helper() + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, utilversion.MustParse("1.36")) + featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.WorkloadWithJob: true, + }) + sharedInformers := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) + jm, err := newControllerWithClock(ctx, + kubeClient, + realClock, + sharedInformers.Core().V1().Pods(), + sharedInformers.Batch().V1().Jobs(), + sharedInformers.Scheduling().V1alpha2().Workloads(), + sharedInformers.Scheduling().V1alpha2().PodGroups(), + ) + if err != nil { + t.Fatalf("Error creating Job controller: %v", err) + } + jm.podControl = &controller.FakePodControl{} + jm.podStoreSynced = alwaysReady + jm.jobStoreSynced = alwaysReady + jm.workloadStoreSynced = alwaysReady + jm.podGroupStoreSynced = alwaysReady + return jm, sharedInformers +} + +func TestShouldManageWorkloadForJob(t *testing.T) { + indexed := batch.IndexedCompletion + nonIndexed := batch.NonIndexedCompletion + + tests := map[string]struct { + workloadWithJob bool + job *batch.Job + want bool + }{ + "eligible: indexed, parallelism=completions>1, no schedulingGroup": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + Completions: ptr.To[int32](4), + CompletionMode: &indexed, + }, + }, + want: true, + }, + "feature gate disabled": { + workloadWithJob: false, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + Completions: ptr.To[int32](4), + CompletionMode: &indexed, + }, + }, + want: false, + }, + "parallelism is nil": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Completions: ptr.To[int32](4), + CompletionMode: &indexed, + }, + }, + want: false, + }, + "parallelism=1": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](1), + Completions: ptr.To[int32](1), + CompletionMode: &indexed, + }, + }, + want: false, + }, + "non-indexed completion mode": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + Completions: ptr.To[int32](4), + CompletionMode: &nonIndexed, + }, + }, + want: false, + }, + "completions is nil": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + CompletionMode: &indexed, + }, + }, + want: false, + }, + "completions != parallelism": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + Completions: ptr.To[int32](8), + CompletionMode: &indexed, + }, + }, + want: false, + }, + "schedulingGroup already set (opt-out)": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + Completions: ptr.To[int32](4), + CompletionMode: &indexed, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SchedulingGroup: &v1.PodSchedulingGroup{ + PodGroupName: ptr.To("existing-pg"), + }, + }, + }, + }, + }, + want: false, + }, + "completionMode is nil": { + workloadWithJob: true, + job: &batch.Job{ + Spec: batch.JobSpec{ + Parallelism: ptr.To[int32](4), + Completions: ptr.To[int32](4), + }, + }, + want: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, utilversion.MustParse("1.36")) + featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: tc.workloadWithJob, + features.WorkloadWithJob: tc.workloadWithJob, + }) + got := shouldManageWorkloadForJob(tc.job) + if got != tc.want { + t.Errorf("shouldManageWorkloadForJob() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestBuildPodGroupTemplates(t *testing.T) { + tests := map[string]struct { + jobName string + parallelism int32 + completions int32 + wantName string + wantMinCount int32 + }{ + "basic job": { + jobName: "my-training-job", + parallelism: 8, + completions: 8, + wantName: "my-training-job-pgt-0", + wantMinCount: 8, + }, + "single template with small parallelism": { + jobName: "small-job", + parallelism: 2, + completions: 2, + wantName: "small-job-pgt-0", + wantMinCount: 2, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + job := &batch.Job{ + ObjectMeta: metav1.ObjectMeta{Name: tc.jobName}, + Spec: batch.JobSpec{ + Parallelism: ptr.To(tc.parallelism), + Completions: ptr.To(tc.completions), + }, + } + templates := buildPodGroupTemplates(job) + + if len(templates) != 1 { + t.Fatalf("expected 1 template, got %d", len(templates)) + } + tpl := templates[0] + if tpl.Name != tc.wantName { + t.Errorf("template name = %q, want %q", tpl.Name, tc.wantName) + } + if tpl.SchedulingPolicy.Gang == nil { + t.Fatal("expected gang scheduling policy, got nil") + } + if tpl.SchedulingPolicy.Gang.MinCount != tc.wantMinCount { + t.Errorf("minCount = %d, want %d", tpl.SchedulingPolicy.Gang.MinCount, tc.wantMinCount) + } + }) + } +} + +func TestComputeWorkloadName(t *testing.T) { + tests := map[string]struct { + jobName string + jobUID types.UID + }{ + "short name": { + jobName: "my-job", + jobUID: "uid-123", + }, + "very long name exceeding DNS limit": { + jobName: strings.Repeat("a", 300), + jobUID: "uid-long", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + job := &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.jobName, + UID: tc.jobUID, + }, + } + result := computeWorkloadName(job) + + // Must fit within DNS subdomain length. + if len(result) > validation.DNS1123SubdomainMaxLength { + t.Errorf("name length %d exceeds DNS1123SubdomainMaxLength %d", len(result), validation.DNS1123SubdomainMaxLength) + } + + // Must contain a hash suffix (separated by "-"). + parts := strings.Split(result, "-") + if len(parts) < 2 { + t.Errorf("expected at least 2 parts (prefix-hash), got %q", result) + } + + // Must be deterministic. + result2 := computeWorkloadName(job) + if result != result2 { + t.Errorf("non-deterministic: %q != %q", result, result2) + } + + // Different UIDs must produce different names. + job2 := job.DeepCopy() + job2.UID = "different-uid" + result3 := computeWorkloadName(job2) + if result == result3 { + t.Error("different UIDs should produce different names") + } + }) + } +} + +func TestComputePodGroupName(t *testing.T) { + tests := map[string]struct { + workloadName string + templateName string + }{ + "short names": { + workloadName: "my-workload", + templateName: "my-template", + }, + "long workload name": { + workloadName: strings.Repeat("w", 250), + templateName: "tpl", + }, + "long template name": { + workloadName: "wl", + templateName: strings.Repeat("t", 250), + }, + "both long names": { + workloadName: strings.Repeat("w", 200), + templateName: strings.Repeat("t", 200), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := computePodGroupName(tc.workloadName, tc.templateName) + + if len(result) > validation.DNS1123SubdomainMaxLength { + t.Errorf("name length %d exceeds DNS1123SubdomainMaxLength %d", len(result), validation.DNS1123SubdomainMaxLength) + } + + parts := strings.Split(result, "-") + if len(parts) < 3 { + t.Errorf("expected at least 3 parts (wl-tpl-hash), got %q", result) + } + + result2 := computePodGroupName(tc.workloadName, tc.templateName) + if result != result2 { + t.Errorf("non-deterministic: %q != %q", result, result2) + } + + result3 := computePodGroupName(tc.workloadName, "other-template") + if result == result3 { + t.Error("different template names should produce different PodGroup names") + } + }) + } + + // Verify truncation preserves both parts. + t.Run("truncation preserves both parts", func(t *testing.T) { + result := computePodGroupName(strings.Repeat("w", 200), strings.Repeat("t", 200)) + lastDash := strings.LastIndex(result, "-") + if lastDash < 0 { + t.Fatalf("expected at least one dash in %q", result) + } + prefix := result[:lastDash] + if !strings.Contains(prefix, "www") { + t.Errorf("expected truncated workload name in prefix %q", prefix) + } + if !strings.Contains(prefix, "ttt") { + t.Errorf("expected truncated template name in prefix %q", prefix) + } + }) + + // Verify short name is not truncated when the other is long. + t.Run("short workload name is not truncated", func(t *testing.T) { + result := computePodGroupName("wl", strings.Repeat("t", 250)) + if !strings.HasPrefix(result, "wl-") { + t.Errorf("short workload name should not be truncated, got %q", result) + } + }) +} + +func TestEnsureWorkloadAndPodGroup(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + baseJob := newGangSchedulingJob("test-job", 4) + workloadName := computeWorkloadName(baseJob) + templateName := fmt.Sprintf("%s-pgt-%d", baseJob.Name, 0) + podGroupName := computePodGroupName(workloadName, templateName) + + makeWorkload := func(name, jobName string) *schedulingv1alpha2.Workload { + return &schedulingv1alpha2.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + UID: types.UID(name + "-uid"), + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(baseJob, controllerKind)}, + }, + Spec: schedulingv1alpha2.WorkloadSpec{ + ControllerRef: newWorkloadJobControllerRef(jobName), + PodGroupTemplates: []schedulingv1alpha2.PodGroupTemplate{ + { + Name: templateName, + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{MinCount: 4}, + }, + }, + }, + }, + } + } + + makePodGroup := func(name, wlName string) *schedulingv1alpha2.PodGroup { + return &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + UID: types.UID(name + "-uid"), + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(baseJob, controllerKind)}, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + PodGroupTemplateRef: &schedulingv1alpha2.PodGroupTemplateReference{ + Workload: &schedulingv1alpha2.WorkloadPodGroupTemplateReference{ + WorkloadName: wlName, + PodGroupTemplateName: templateName, + }, + }, + }, + } + } + + testCases := map[string]struct { + job *batch.Job + pods []*v1.Pod + existingWorkloads []*schedulingv1alpha2.Workload + existingPodGroups []*schedulingv1alpha2.PodGroup + + // When true, the Workload create reactor returns a generic error. + simulateCreateError bool + // When true, PodGroup create returns a generic error. + simulatePGCreateError bool + + wantPodGroup bool + wantPodGroupName string + wantErr bool + }{ + "creates both Workload and PodGroup when no pods exist": { + job: baseJob, + wantPodGroup: true, + wantPodGroupName: podGroupName, + }, + "skips creation when Job has pods but no Workload": { + job: baseJob, + pods: []*v1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: "pod-0", Namespace: metav1.NamespaceDefault}}}, + }, + "skips creation when Job has StartTime set": { + job: func() *batch.Job { + j := baseJob.DeepCopy() + now := metav1.Now() + j.Status.StartTime = &now + return j + }(), + }, + "skips creation when Job has succeeded pods": { + job: func() *batch.Job { + j := baseJob.DeepCopy() + j.Status.Succeeded = 1 + return j + }(), + }, + "skips creation when Job has failed pods": { + job: func() *batch.Job { + j := baseJob.DeepCopy() + j.Status.Failed = 1 + return j + }(), + }, + "skips creating Workload/PodGroup when Job was previously suspended": { + job: func() *batch.Job { + j := baseJob.DeepCopy() + j.Status.Conditions = []batch.JobCondition{ + { + Type: batch.JobSuspended, + Status: v1.ConditionTrue, + }, + } + return j + }(), + }, + "discovers existing Workload and PodGroup": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{makeWorkload(workloadName, baseJob.Name)}, + existingPodGroups: []*schedulingv1alpha2.PodGroup{makePodGroup(podGroupName, workloadName)}, + wantPodGroup: true, + wantPodGroupName: podGroupName, + }, + "discovers existing objects even when pods exist": { + job: baseJob, + pods: []*v1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: "pod-0", Namespace: metav1.NamespaceDefault}}}, + existingWorkloads: []*schedulingv1alpha2.Workload{makeWorkload(workloadName, baseJob.Name)}, + existingPodGroups: []*schedulingv1alpha2.PodGroup{makePodGroup(podGroupName, workloadName)}, + wantPodGroup: true, + wantPodGroupName: podGroupName, + }, + "workload found but no PodGroup creates one": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{makeWorkload("user-workload", baseJob.Name)}, + wantPodGroup: true, + }, + + "BYO Workload without controller ownerRef falls back": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "user-created-workload", + Namespace: metav1.NamespaceDefault, + UID: types.UID("user-wl-uid"), + }, + Spec: schedulingv1alpha2.WorkloadSpec{ + ControllerRef: newWorkloadJobControllerRef(baseJob.Name), + PodGroupTemplates: []schedulingv1alpha2.PodGroupTemplate{ + { + Name: templateName, + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{MinCount: 4}, + }, + }, + }, + }, + }, + }, + }, + "ambiguous Workloads fall back": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{ + makeWorkload("workload-1", baseJob.Name), + makeWorkload("workload-2", baseJob.Name), + }, + }, + "BYO PodGroup without controller ownerRef falls back": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{makeWorkload(workloadName, baseJob.Name)}, + existingPodGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "external-pg", + Namespace: metav1.NamespaceDefault, + UID: types.UID("ext-pg-uid"), + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + PodGroupTemplateRef: &schedulingv1alpha2.PodGroupTemplateReference{ + Workload: &schedulingv1alpha2.WorkloadPodGroupTemplateReference{ + WorkloadName: workloadName, + }, + }, + }, + }, + }, + }, + "ambiguous PodGroups fall back": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{makeWorkload(workloadName, baseJob.Name)}, + existingPodGroups: []*schedulingv1alpha2.PodGroup{ + makePodGroup("pg-1", workloadName), + makePodGroup("pg-2", workloadName), + }, + }, + "unsupported PodGroupTemplate count falls back": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: workloadName, + Namespace: metav1.NamespaceDefault, + UID: types.UID(workloadName + "-uid"), + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(baseJob, controllerKind)}, + }, + Spec: schedulingv1alpha2.WorkloadSpec{ + ControllerRef: newWorkloadJobControllerRef(baseJob.Name), + PodGroupTemplates: []schedulingv1alpha2.PodGroupTemplate{ + {Name: "group-a"}, + {Name: "group-b"}, + }, + }, + }, + }, + }, + + "Workload create error propagates": { + job: baseJob, + simulateCreateError: true, + wantErr: true, + }, + + "PodGroup create error propagates": { + job: baseJob, + existingWorkloads: []*schedulingv1alpha2.Workload{makeWorkload(workloadName, baseJob.Name)}, + simulatePGCreateError: true, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + var objs []runtime.Object + for _, wl := range tc.existingWorkloads { + objs = append(objs, wl) + } + for _, pg := range tc.existingPodGroups { + objs = append(objs, pg) + } + clientSet := fake.NewClientset(objs...) + jm, sharedInformers := newControllerWithSchedulingInformers(ctx, t, clientSet) + + if tc.simulateCreateError { + clientSet.PrependReactor("create", "workloads", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf("synthetic API error") + }) + } + + if tc.simulatePGCreateError { + clientSet.PrependReactor("create", "podgroups", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf("synthetic PG API error") + }) + } + + sharedInformers.Start(ctx.Done()) + sharedInformers.WaitForCacheSync(ctx.Done()) + + _, pg, err := jm.ensureWorkloadAndPodGroup(ctx, tc.job, tc.pods) + if tc.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tc.wantPodGroup { + if pg == nil { + t.Fatal("expected non-nil PodGroup") + } + if tc.wantPodGroupName != "" && pg.Name != tc.wantPodGroupName { + t.Errorf("PodGroup name = %q, want %q", pg.Name, tc.wantPodGroupName) + } + } else if pg != nil { + t.Errorf("expected nil PodGroup, got %v", pg) + } + }) + } +} + +func TestCreateWorkloadForJob(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + clientSet := fake.NewClientset() + jm, _ := newControllerWithSchedulingInformers(ctx, t, clientSet) + job := newGangSchedulingJob("my-job", 8) + + wl, err := jm.createWorkloadForJob(ctx, job) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedName := computeWorkloadName(job) + if wl.Name != expectedName { + t.Errorf("workload name = %q, want %q", wl.Name, expectedName) + } + if wl.Namespace != job.Namespace { + t.Errorf("workload namespace = %q, want %q", wl.Namespace, job.Namespace) + } + + if wl.Spec.ControllerRef == nil { + t.Fatal("expected controllerRef") + } + if diff := cmp.Diff(wl.Spec.ControllerRef, newWorkloadJobControllerRef(job.Name)); diff != "" { + t.Errorf("unexpected controllerRef (-got +want):\n%s", diff) + } + + if len(wl.Spec.PodGroupTemplates) != 1 { + t.Fatalf("expected 1 template, got %d", len(wl.Spec.PodGroupTemplates)) + } + tpl := wl.Spec.PodGroupTemplates[0] + if tpl.SchedulingPolicy.Gang == nil || tpl.SchedulingPolicy.Gang.MinCount != 8 { + t.Errorf("expected gang minCount=8, got %+v", tpl.SchedulingPolicy) + } + + if len(wl.OwnerReferences) != 1 || wl.OwnerReferences[0].Name != job.Name { + t.Errorf("unexpected ownerReferences: %v", wl.OwnerReferences) + } +} + +func TestCreatePodGroupForWorkload(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + clientSet := fake.NewClientset() + jm, _ := newControllerWithSchedulingInformers(ctx, t, clientSet) + job := newGangSchedulingJob("my-job", 4) + templateName := fmt.Sprintf("%s-pgt-%d", job.Name, 0) + + t.Run("creates PodGroup with correct name and ownerReferences", func(t *testing.T) { + workload := &schedulingv1alpha2.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-workload", + Namespace: job.Namespace, + UID: "wl-uid", + }, + Spec: schedulingv1alpha2.WorkloadSpec{ + PodGroupTemplates: []schedulingv1alpha2.PodGroupTemplate{ + { + Name: templateName, + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{MinCount: 4}, + }, + }, + }, + }, + } + + pg, err := jm.createPodGroupForWorkload(ctx, job, workload) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedName := computePodGroupName(workload.Name, templateName) + if pg.Name != expectedName { + t.Errorf("PodGroup name = %q, want %q", pg.Name, expectedName) + } + if len(pg.OwnerReferences) != 2 { + t.Fatalf("expected 2 ownerReferences, got %d", len(pg.OwnerReferences)) + } + }) + + t.Run("errors when Workload has no templates", func(t *testing.T) { + workload := &schedulingv1alpha2.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "empty-workload", + Namespace: job.Namespace, + }, + Spec: schedulingv1alpha2.WorkloadSpec{}, + } + + _, err := jm.createPodGroupForWorkload(ctx, job, workload) + if err == nil { + t.Fatal("expected error for Workload with no templates") + } + }) +} diff --git a/pkg/controller/job/metrics/metrics.go b/pkg/controller/job/metrics/metrics.go index a52067ada7fd4..bdb81926ca1d0 100644 --- a/pkg/controller/job/metrics/metrics.go +++ b/pkg/controller/job/metrics/metrics.go @@ -116,6 +116,7 @@ var ( rule. Possible values of the action label correspond to the possible values for the failure policy rule action, which are: "FailJob", "Ignore" and "Count".`, + StabilityLevel: metrics.BETA, }, []string{"action"}) @@ -129,6 +130,7 @@ var ( Help: `The number of terminated pods (phase=Failed|Succeeded) that have the finalizer batch.kubernetes.io/job-tracking The event label can be "add" or "delete".`, + StabilityLevel: metrics.BETA, }, []string{"event"}) // JobFinishedIndexesTotal records the number of finished indexes. @@ -159,6 +161,19 @@ Possible values of the "reason" label are: Possible values of the "status" label are: "succeeded", "failed".`, }, []string{"reason", "status"}) + + // JobRequeueSkips track the number of job syncs skipped due to a stale + // watch cache. + JobRequeueSkips = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: JobControllerSubsystem, + Name: "stale_sync_skips_total", + Help: "Total number of Job syncs skipped due to a stale watch cache.", + StabilityLevel: metrics.ALPHA, + }, + // These are the labels (dimensions) + []string{"group", "resource"}, + ) ) const ( @@ -212,5 +227,6 @@ func Register() { legacyregistry.MustRegister(JobFinishedIndexesTotal) legacyregistry.MustRegister(JobPodsCreationTotal) legacyregistry.MustRegister(JobByExternalControllerTotal) + legacyregistry.MustRegister(JobRequeueSkips) }) } diff --git a/pkg/controller/job/pod_failure_policy.go b/pkg/controller/job/pod_failure_policy.go index 98f7ed9624149..46f64aa0122b0 100644 --- a/pkg/controller/job/pod_failure_policy.go +++ b/pkg/controller/job/pod_failure_policy.go @@ -18,6 +18,7 @@ package job import ( "fmt" + "slices" batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" @@ -122,19 +123,9 @@ func getMatchingContainerFromList(containerStatuses []v1.ContainerStatus, requir func isOnExitCodesOperatorMatching(exitCode int32, requirement *batch.PodFailurePolicyOnExitCodesRequirement) bool { switch requirement.Operator { case batch.PodFailurePolicyOnExitCodesOpIn: - for _, value := range requirement.Values { - if value == exitCode { - return true - } - } - return false + return slices.Contains(requirement.Values, exitCode) case batch.PodFailurePolicyOnExitCodesOpNotIn: - for _, value := range requirement.Values { - if value == exitCode { - return false - } - } - return true + return !slices.Contains(requirement.Values, exitCode) default: return false } diff --git a/pkg/controller/job/tracking_utils.go b/pkg/controller/job/tracking_utils.go index 7e5147eb76f12..5df4037bb8d10 100644 --- a/pkg/controller/job/tracking_utils.go +++ b/pkg/controller/job/tracking_utils.go @@ -18,6 +18,7 @@ package job import ( "fmt" + "slices" "sync" batch "k8s.io/api/batch/v1" @@ -124,12 +125,7 @@ func newUIDTrackingExpectations() *uidTrackingExpectations { } func hasJobTrackingFinalizer(pod *v1.Pod) bool { - for _, fin := range pod.Finalizers { - if fin == batch.JobTrackingFinalizer { - return true - } - } - return false + return slices.Contains(pod.Finalizers, batch.JobTrackingFinalizer) } func recordFinishedPodWithTrackingFinalizer(oldPod, newPod *v1.Pod) { diff --git a/pkg/controller/job/tracking_utils_test.go b/pkg/controller/job/tracking_utils_test.go index dd83e3d146d55..d58258bc100b6 100644 --- a/pkg/controller/job/tracking_utils_test.go +++ b/pkg/controller/job/tracking_utils_test.go @@ -17,7 +17,7 @@ limitations under the License. package job import ( - "fmt" + "strings" "sync" "testing" @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/controller/job/metrics" @@ -91,7 +92,6 @@ func TestUIDTrackingExpectations(t *testing.T) { wg.Add(len(track.firstRound) + 1) track := track for _, uid := range track.firstRound { - uid := uid go func() { expectations.finalizerRemovalObserved(logger, track.job, uid) wg.Done() @@ -130,10 +130,9 @@ func TestUIDTrackingExpectations(t *testing.T) { func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { metrics.Register() cases := map[string]struct { - oldPod *v1.Pod - newPod *v1.Pod - wantAdd int - wantDelete int + oldPod *v1.Pod + newPod *v1.Pod + want string }{ "new non-finished Pod with finalizer": { newPod: &v1.Pod{ @@ -144,6 +143,7 @@ func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { Phase: v1.PodPending, }, }, + want: "", }, "pod with finalizer fails": { oldPod: &v1.Pod{ @@ -162,7 +162,10 @@ func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { Phase: v1.PodFailed, }, }, - wantAdd: 1, + want: `# HELP job_controller_terminated_pods_tracking_finalizer_total [BETA] The number of terminated pods (phase=Failed|Succeeded)\nthat have the finalizer batch.kubernetes.io/job-tracking\nThe event label can be "add" or "delete". +# TYPE job_controller_terminated_pods_tracking_finalizer_total counter +job_controller_terminated_pods_tracking_finalizer_total{event="add"} 1 +`, }, "pod with finalizer succeeds": { oldPod: &v1.Pod{ @@ -181,7 +184,10 @@ func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { Phase: v1.PodSucceeded, }, }, - wantAdd: 1, + want: `# HELP job_controller_terminated_pods_tracking_finalizer_total [BETA] The number of terminated pods (phase=Failed|Succeeded)\nthat have the finalizer batch.kubernetes.io/job-tracking\nThe event label can be "add" or "delete". +# TYPE job_controller_terminated_pods_tracking_finalizer_total counter +job_controller_terminated_pods_tracking_finalizer_total{event="add"} 1 +`, }, "succeeded pod loses finalizer": { oldPod: &v1.Pod{ @@ -197,7 +203,10 @@ func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { Phase: v1.PodSucceeded, }, }, - wantDelete: 1, + want: `# HELP job_controller_terminated_pods_tracking_finalizer_total [BETA] The number of terminated pods (phase=Failed|Succeeded)\nthat have the finalizer batch.kubernetes.io/job-tracking\nThe event label can be "add" or "delete". +# TYPE job_controller_terminated_pods_tracking_finalizer_total counter +job_controller_terminated_pods_tracking_finalizer_total{event="delete"} 1 +`, }, "pod without finalizer removed": { oldPod: &v1.Pod{ @@ -205,29 +214,62 @@ func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { Phase: v1.PodSucceeded, }, }, + want: "", }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { metrics.TerminatedPodsTrackingFinalizerTotal.Reset() recordFinishedPodWithTrackingFinalizer(tc.oldPod, tc.newPod) - if err := validateTerminatedPodsTrackingFinalizerTotal(metrics.Add, tc.wantAdd); err != nil { - t.Errorf("Failed validating terminated_pods_tracking_finalizer_total(add): %v", err) - } - if err := validateTerminatedPodsTrackingFinalizerTotal(metrics.Delete, tc.wantDelete); err != nil { - t.Errorf("Failed validating terminated_pods_tracking_finalizer_total(delete): %v", err) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.want), "job_controller_terminated_pods_tracking_finalizer_total"); err != nil { + t.Error(err) } }) } } -func validateTerminatedPodsTrackingFinalizerTotal(event string, want int) error { - got, err := testutil.GetCounterMetricValue(metrics.TerminatedPodsTrackingFinalizerTotal.WithLabelValues(event)) - if err != nil { - return err +func TestRecordJobPodFailurePolicyActions(t *testing.T) { + metrics.Register() + + cases := map[string]struct { + input map[string]int + want string + }{ + "no actions": { + input: nil, + want: "", + }, + "single action": { + input: map[string]int{ + "FailJob": 2, + }, + want: `# HELP job_controller_pod_failures_handled_by_failure_policy_total [BETA] The number of failed Pods handled by failure policy with\n respect to the failure policy action applied based on the matched\n rule. Possible values of the action label correspond to the\n possible values for the failure policy rule action, which are:\n "FailJob", "Ignore" and "Count". +# TYPE job_controller_pod_failures_handled_by_failure_policy_total counter +job_controller_pod_failures_handled_by_failure_policy_total{action="FailJob"} 2 +`, + }, + "multiple actions": { + input: map[string]int{ + "FailJob": 2, + "Ignore": 3, + "Count": 1, + }, + want: `# HELP job_controller_pod_failures_handled_by_failure_policy_total [BETA] The number of failed Pods handled by failure policy with\n respect to the failure policy action applied based on the matched\n rule. Possible values of the action label correspond to the\n possible values for the failure policy rule action, which are:\n "FailJob", "Ignore" and "Count". +# TYPE job_controller_pod_failures_handled_by_failure_policy_total counter +job_controller_pod_failures_handled_by_failure_policy_total{action="Count"} 1 +job_controller_pod_failures_handled_by_failure_policy_total{action="FailJob"} 2 +job_controller_pod_failures_handled_by_failure_policy_total{action="Ignore"} 3 +`, + }, } - if int(got) != want { - return fmt.Errorf("got value %d, want %d", int(got), want) + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + metrics.PodFailuresHandledByFailurePolicy.Reset() + recordJobPodFailurePolicyActions(tc.input) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.want), "job_controller_pod_failures_handled_by_failure_policy_total"); err != nil { + t.Error(err) + } + }) } - return nil } diff --git a/pkg/controller/job/util/utils.go b/pkg/controller/job/util/utils.go index 8c6986586b00c..7c8ea2be81711 100644 --- a/pkg/controller/job/util/utils.go +++ b/pkg/controller/job/util/utils.go @@ -48,3 +48,25 @@ func IsJobSucceeded(j *batch.Job) bool { } return false } + +// IsGangSchedulingCandidate returns true when a Job matches the gang scheduling +// shape used by Job workload integration: +// - parallelism > 1 +// - completionMode == Indexed +// - completions == parallelism +// - schedulingGroup is not pre-set +func IsGangSchedulingCandidate(parallelism, completions *int32, isIndexed bool, hasSchedulingGroup bool) bool { + if parallelism == nil || *parallelism <= 1 { + return false + } + if !isIndexed { + return false + } + if completions == nil || *completions != *parallelism { + return false + } + if hasSchedulingGroup { + return false + } + return true +} diff --git a/pkg/controller/job/util/utils_test.go b/pkg/controller/job/util/utils_test.go index 2e51b8531b2f0..607a3a15b2d03 100644 --- a/pkg/controller/job/util/utils_test.go +++ b/pkg/controller/job/util/utils_test.go @@ -21,6 +21,7 @@ import ( batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" ) func TestFinishedCondition(t *testing.T) { @@ -207,3 +208,76 @@ func TestIsJobSucceeded(t *testing.T) { }) } } + +func TestIsGangSchedulingCandidate(t *testing.T) { + tests := map[string]struct { + parallelism *int32 + completions *int32 + isIndexed bool + hasSchedulingGroup bool + want bool + }{ + "eligible: indexed, parallelism=completions>1": { + parallelism: ptr.To[int32](4), + completions: ptr.To[int32](4), + isIndexed: true, + want: true, + }, + "eligible: minimum parallelism of 2": { + parallelism: ptr.To[int32](2), + completions: ptr.To[int32](2), + isIndexed: true, + want: true, + }, + "not eligible: nil parallelism": { + parallelism: nil, + completions: ptr.To[int32](4), + isIndexed: true, + want: false, + }, + "not eligible: parallelism=1": { + parallelism: ptr.To[int32](1), + completions: ptr.To[int32](1), + isIndexed: true, + want: false, + }, + "not eligible: parallelism=0": { + parallelism: ptr.To[int32](0), + completions: ptr.To[int32](0), + isIndexed: true, + want: false, + }, + "not eligible: non-indexed": { + parallelism: ptr.To[int32](4), + completions: ptr.To[int32](4), + isIndexed: false, + want: false, + }, + "not eligible: nil completions": { + parallelism: ptr.To[int32](4), + completions: nil, + isIndexed: true, + want: false, + }, + "not eligible: completions != parallelism": { + parallelism: ptr.To[int32](4), + completions: ptr.To[int32](8), + isIndexed: true, + want: false, + }, + "not eligible: schedulingGroup already set": { + parallelism: ptr.To[int32](4), + completions: ptr.To[int32](4), + isIndexed: true, + hasSchedulingGroup: true, + want: false, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if got := IsGangSchedulingCandidate(tc.parallelism, tc.completions, tc.isIndexed, tc.hasSchedulingGroup); got != tc.want { + t.Fatalf("IsGangSchedulingCandidate() = %v, want %v", got, tc.want) + } + }) + } +} diff --git a/pkg/controller/nodeipam/ipam/sync/sync_test.go b/pkg/controller/nodeipam/ipam/sync/sync_test.go index 8afc22858cf62..1737d10e91782 100644 --- a/pkg/controller/nodeipam/ipam/sync/sync_test.go +++ b/pkg/controller/nodeipam/ipam/sync/sync_test.go @@ -231,19 +231,20 @@ func TestNodeSyncResync(t *testing.T) { fake := &fakeAPIs{ nodeRet: nodeWithCIDRRange, resyncTimeout: time.Millisecond, - reportChan: make(chan struct{}), - logger: logger, + // Allow one extra resync notification to land while the test is + // closing the loop down. + reportChan: make(chan struct{}, 1), + logger: logger, } cidr, _ := cidrset.NewCIDRSet(clusterCIDRRange, 24) sync := New(fake, fake, fake, SyncFromCluster, "node1", cidr) doneChan := make(chan struct{}) go sync.Loop(logger, doneChan) + // Wait for the first resync to complete <-fake.reportChan + // Close the operation channel to stop the loop close(sync.opChan) - // Unblock loop(). - go func() { - <-fake.reportChan - }() + // Wait for the loop to complete <-doneChan fake.dumpTrace() } diff --git a/pkg/controller/nodelifecycle/node_lifecycle_controller.go b/pkg/controller/nodelifecycle/node_lifecycle_controller.go index f92e7c0a0c941..51e6144b7f43c 100644 --- a/pkg/controller/nodelifecycle/node_lifecycle_controller.go +++ b/pkg/controller/nodelifecycle/node_lifecycle_controller.go @@ -368,23 +368,6 @@ func NewNodeLifecycleController( newPod := obj.(*v1.Pod) nc.podUpdated(prevPod, newPod) }, - DeleteFunc: func(obj interface{}) { - pod, isPod := obj.(*v1.Pod) - // We can get DeletedFinalStateUnknown instead of *v1.Pod here and we need to handle that correctly. - if !isPod { - deletedState, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - logger.Error(nil, "Received unexpected object", "object", obj) - return - } - pod, ok = deletedState.Obj.(*v1.Pod) - if !ok { - logger.Error(nil, "DeletedFinalStateUnknown contained non-Pod object", "object", deletedState.Obj) - return - } - } - nc.podUpdated(pod, nil) - }, }) nc.podInformerSynced = podInformer.Informer().HasSynced controller.AddPodNodeNameIndexer(podInformer.Informer()) diff --git a/pkg/controller/podautoscaler/horizontal.go b/pkg/controller/podautoscaler/horizontal.go index def3366018347..e676d5d48aeee 100644 --- a/pkg/controller/podautoscaler/horizontal.go +++ b/pkg/controller/podautoscaler/horizontal.go @@ -756,6 +756,20 @@ func (a *HorizontalController) recordInitialRecommendation(currentReplicas int32 } } +// shouldComputeMetricsForZeroReplicas determines the scaling behavior when current replicas is zero. +// Returns: +// - needsMetricComputation: true if metrics should be computed to determine desired replicas +// - shouldDisable: true if scaling should be disabled (workload was manually scaled to zero) +func (a *HorizontalController) shouldComputeMetricsForZeroReplicas( + minReplicas int32, + scaledToZeroCondition, canScaleFromZero bool, +) (needsMetricComputation bool, shouldDisable bool) { + if (minReplicas != 0 && scaledToZeroCondition) || canScaleFromZero { + return true, false + } + return false, true +} + func (a *HorizontalController) reconcileAutoscaler(ctx context.Context, hpaShared *autoscalingv2.HorizontalPodAutoscaler, key string) (retErr error) { // actionLabel is used to report which actions this reconciliation has taken. actionLabel := monitor.ActionLabelNone @@ -838,18 +852,37 @@ func (a *HorizontalController) reconcileAutoscaler(ctx context.Context, hpaShare rescale := true logger := klog.FromContext(ctx) - if currentReplicas == 0 && minReplicas != 0 { - // Autoscaling is disabled for this resource - desiredReplicas = 0 - rescale = false - setCondition(hpa, autoscalingv2.ScalingActive, v1.ConditionFalse, "ScalingDisabled", "scaling is disabled since the replica count of the target is zero") + // Pre-compute scale-to-zero related conditions + scaleToZeroFeatureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) + hasObjectOrExtMetrics := hasObjectOrExternalMetrics(hpa) + scaledToZeroCondition := scaleToZeroFeatureEnabled && getScaledToZeroConditionStatus(hpa) + canScaleFromZero := scaledToZeroCondition && hasObjectOrExtMetrics + + needsMetricComputation := true + + if currentReplicas == 0 { + var shouldDisable bool + needsMetricComputation, shouldDisable = a.shouldComputeMetricsForZeroReplicas(minReplicas, scaledToZeroCondition, canScaleFromZero) + if shouldDisable { + desiredReplicas = 0 + rescale = false + setCondition(hpa, autoscalingv2.ScalingActive, v1.ConditionFalse, "ScalingDisabled", "scaling is disabled since the replica count of the target is zero") + if !scaleToZeroFeatureEnabled { + removeCondition(hpa, autoscalingv2.ScaledToZero) + } + } } else if currentReplicas > hpa.Spec.MaxReplicas { rescaleReason = "Current number of replicas above Spec.MaxReplicas" desiredReplicas = hpa.Spec.MaxReplicas + needsMetricComputation = false } else if currentReplicas < minReplicas { rescaleReason = "Current number of replicas below Spec.MinReplicas" desiredReplicas = minReplicas - } else { + needsMetricComputation = false + } + + // Compute metrics and normalize desired replicas for cases that require metric-based scaling + if needsMetricComputation { var metricTimestamp time.Time metricDesiredReplicas, metricName, metricStatuses, metricTimestamp, err = a.computeReplicasForMetrics(ctx, hpa, scale, hpa.Spec.Metrics) // computeReplicasForMetrics may return both non-zero metricDesiredReplicas and an error. @@ -867,7 +900,11 @@ func (a *HorizontalController) reconcileAutoscaler(ctx context.Context, hpaShare retErr = err } - logger.V(4).Info("Proposing desired replicas", + logMessage := "Proposing desired replicas" + if currentReplicas == 0 { + logMessage = "Proposing desired replicas from zero" + } + logger.V(4).Info(logMessage, "desiredReplicas", metricDesiredReplicas, "metric", metricName, "tolerances", a.tolerancesForHpa(hpa), @@ -890,6 +927,13 @@ func (a *HorizontalController) reconcileAutoscaler(ctx context.Context, hpaShare } else { desiredReplicas = a.normalizeDesiredReplicasWithBehaviors(hpa, key, currentReplicas, desiredReplicas, minReplicas) } + // Ensure we scale to at least minReplicas when scaling from zero with increased minReplicas + if currentReplicas == 0 && minReplicas != 0 && scaledToZeroCondition && desiredReplicas < minReplicas { + desiredReplicas = minReplicas + if rescaleReason == "" { + rescaleReason = "Current number of replicas below Spec.MinReplicas" + } + } rescale = desiredReplicas != currentReplicas } if rescale { @@ -940,6 +984,17 @@ func (a *HorizontalController) reconcileAutoscaler(ctx context.Context, hpaShare "desiredReplicas", desiredReplicas, "reason", rescaleReason) + // Set ScaledToZero condition on every rescale so the condition is never stale. + if scaleToZeroFeatureEnabled { + if currentReplicas > 0 && desiredReplicas == 0 && minReplicas == 0 && hasObjectOrExtMetrics { + setCondition(hpa, autoscalingv2.ScaledToZero, v1.ConditionTrue, "ScaledToZero", "the HPA controller scaled the workload to zero") + } else { + setCondition(hpa, autoscalingv2.ScaledToZero, v1.ConditionFalse, "NotScaledToZero", "the HPA controller did not scale the workload to zero") + } + } else { + removeCondition(hpa, autoscalingv2.ScaledToZero) + } + if desiredReplicas > currentReplicas { actionLabel = monitor.ActionLabelScaleUp } else { @@ -1479,6 +1534,36 @@ func setCondition(hpa *autoscalingv2.HorizontalPodAutoscaler, conditionType auto hpa.Status.Conditions = setConditionInList(hpa.Status.Conditions, conditionType, status, reason, message, args...) } +func removeCondition(hpa *autoscalingv2.HorizontalPodAutoscaler, conditionType autoscalingv2.HorizontalPodAutoscalerConditionType) { + filtered := hpa.Status.Conditions[:0] + for _, c := range hpa.Status.Conditions { + if c.Type != conditionType { + filtered = append(filtered, c) + } + } + hpa.Status.Conditions = filtered +} + +// hasObjectOrExternalMetrics checks if the HPA has at least one object or external metric. +func hasObjectOrExternalMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler) bool { + for _, metric := range hpa.Spec.Metrics { + if metric.Type == autoscalingv2.ObjectMetricSourceType || metric.Type == autoscalingv2.ExternalMetricSourceType { + return true + } + } + return false +} + +// getScaledToZeroConditionStatus returns true if the ScaledToZero condition exists and is True. +func getScaledToZeroConditionStatus(hpa *autoscalingv2.HorizontalPodAutoscaler) bool { + for _, condition := range hpa.Status.Conditions { + if condition.Type == autoscalingv2.ScaledToZero { + return condition.Status == v1.ConditionTrue + } + } + return false +} + // setConditionInList sets the specific condition type on the given HPA to the specified value with the given // reason and message. The message and args are treated like a format string. The condition will be added if // it is not present. The new list will be returned. diff --git a/pkg/controller/podautoscaler/horizontal_test.go b/pkg/controller/podautoscaler/horizontal_test.go index 6e7c5ee6a42fb..a1fd608b4e14d 100644 --- a/pkg/controller/podautoscaler/horizontal_test.go +++ b/pkg/controller/podautoscaler/horizontal_test.go @@ -44,6 +44,7 @@ import ( scalefake "k8s.io/client-go/scale/fake" core "k8s.io/client-go/testing" featuregatetesting "k8s.io/component-base/featuregate/testing" + metricstestutil "k8s.io/component-base/metrics/testutil" "k8s.io/kubernetes/pkg/api/legacyscheme" autoscalingapiv2 "k8s.io/kubernetes/pkg/apis/autoscaling/v2" "k8s.io/kubernetes/pkg/controller" @@ -61,6 +62,7 @@ import ( "k8s.io/utils/ptr" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" _ "k8s.io/kubernetes/pkg/apis/apps/install" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" @@ -148,6 +150,15 @@ type testCase struct { expectedReportedMetricComputationErrorLabels map[autoscalingv2.MetricSourceType]monitor.ErrorLabel checkDesiredReplicaMetric bool + // expectedReconciliationCount specifies the minimum number of reconciliations to wait for. + // This verifies that reconciliationsTotal counter metric is incremented on each cycle. + // The actual count may be higher; we only verify it's at least this value. + expectedReconciliationCount int + // expectedMetricComputationCounts specifies the minimum computation count per metric type. + // This verifies that metricComputationTotal counter metric is incremented for each + // metric type on each reconciliation. The actual counts may be higher. + expectedMetricComputationCounts map[autoscalingv2.MetricSourceType]int + // Target resource information. resource *fakeResource @@ -161,8 +172,12 @@ type testCase struct { testEMClient *emfake.FakeExternalMetricsClient testScaleClient *scalefake.FakeScaleClient - recommendations []timestampedRecommendation - hpaSelectors *selectors.BiMultimap + recommendations []timestampedRecommendation + hpaSelectors *selectors.BiMultimap + initialConditions []autoscalingv2.HorizontalPodAutoscalerCondition + + verifyReconciliationDuration bool + verifyMetricComputationDurations bool } // Needs to be called under a lock. @@ -187,6 +202,7 @@ func init() { } func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfake.Clientset, *cmfake.FakeCustomMetricsClient, *emfake.FakeExternalMetricsClient, *scalefake.FakeScaleClient) { + logger, _ := ktesting.NewTestContext(t) namespace := "test-namespace" hpaName := "test-hpa" podNamePrefix := "test-pod" @@ -242,6 +258,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa CurrentReplicas: tc.specReplicas, DesiredReplicas: tc.specReplicas, LastScaleTime: tc.lastScaleTime, + Conditions: tc.initialConditions, }, } // Initialize default values @@ -511,7 +528,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa return true, obj, nil }) - fakeWatch := watch.NewFake() + fakeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) fakeMetricsClient := &metricsfake.Clientset{} @@ -687,55 +704,91 @@ func findCpuUtilization(metricStatus []autoscalingv2.MetricStatus) (utilization return nil } -func (tc *testCase) verifyResults(ctx context.Context, t *testing.T, m *mockMonitor) { +func (tc *testCase) verifyResults(ctx context.Context, t *testing.T) { tc.Lock() defer tc.Unlock() - assert.Equal(t, tc.specReplicas != tc.expectedDesiredReplicas, tc.scaleUpdated, "the scale should only be updated if we expected a change in replicas") assert.True(t, tc.statusUpdated, "the status should have been updated") if tc.verifyEvents { assert.Equal(t, tc.specReplicas != tc.expectedDesiredReplicas, tc.eventCreated, "an event should have been created only if we expected a change in replicas") } - tc.verifyRecordedMetric(ctx, t, m) + tc.verifyRecordedMetric(ctx, t) } -func (tc *testCase) verifyRecordedMetric(ctx context.Context, t *testing.T, m *mockMonitor) { - // First, wait for the reconciliation completed at least once. - m.waitUntilRecorded(ctx, t) - - assert.Equal(t, tc.expectedReportedReconciliationActionLabel, m.reconciliationActionLabels[0], "the reconciliation action should be recorded in monitor expectedly") - assert.Equal(t, tc.expectedReportedReconciliationErrorLabel, m.reconciliationErrorLabels[0], "the reconciliation error should be recorded in monitor expectedly") +func (tc *testCase) verifyRecordedMetric(ctx context.Context, t *testing.T) { + actionStr := string(tc.expectedReportedReconciliationActionLabel) + errorStr := string(tc.expectedReportedReconciliationErrorLabel) - if len(tc.expectedReportedMetricComputationActionLabels) != len(m.metricComputationActionLabels) { - t.Fatalf("the metric computation actions for %d types should be recorded, but actually only %d was recorded", len(tc.expectedReportedMetricComputationActionLabels), len(m.metricComputationActionLabels)) - } - if len(tc.expectedReportedMetricComputationErrorLabels) != len(m.metricComputationErrorLabels) { - t.Fatalf("the metric computation errors for %d types should be recorded, but actually only %d was recorded", len(tc.expectedReportedMetricComputationErrorLabels), len(m.metricComputationErrorLabels)) + if err := wait.PollUntilContextTimeout(ctx, 20*time.Millisecond, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) { + v, err := metricstestutil.GetCounterMetricValue(monitor.ReconciliationsTotal.WithLabelValues(actionStr, errorStr)) + if err != nil { + return false, nil + } + return v >= 1, nil + }); err != nil { + t.Fatalf("%s metric was not recorded for action=%s, error=%s", monitor.ReconciliationsTotal.Name, actionStr, errorStr) } - for metricType, l := range tc.expectedReportedMetricComputationActionLabels { - _, ok := m.metricComputationActionLabels[metricType] - if !ok { - t.Fatalf("the metric computation action should be recorded with metricType %s, but actually nothing was recorded", metricType) + if tc.verifyReconciliationDuration { + if err := wait.PollUntilContextTimeout(ctx, 20*time.Millisecond, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) { + count, err := metricstestutil.GetHistogramMetricCount(monitor.ReconciliationsDuration.WithLabelValues(actionStr, errorStr)) + if err != nil { + return false, nil + } + return count >= 1, nil + }); err != nil { + t.Fatalf("%s metric was not recorded for action=%s, error=%s", monitor.ReconciliationsDuration.Name, actionStr, errorStr) } - assert.Equal(t, l, m.metricComputationActionLabels[metricType][0], "the metric computation action should be recorded in monitor expectedly") } - for metricType, l := range tc.expectedReportedMetricComputationErrorLabels { - _, ok := m.metricComputationErrorLabels[metricType] - if !ok { - t.Fatalf("the metric computation error should be recorded with metricType %s, but actually nothing was recorded", metricType) + + if tc.expectedReconciliationCount > 0 { + v, err := metricstestutil.GetCounterMetricValue(monitor.ReconciliationsTotal.WithLabelValues(actionStr, errorStr)) + if err != nil { + t.Fatalf("error getting reconciliations total metric: %v", err) } - assert.Equal(t, l, m.metricComputationErrorLabels[metricType][0], "the metric computation error should be recorded in monitor expectedly") - } + assert.GreaterOrEqual(t, int(v), tc.expectedReconciliationCount, "reconciliation count should be at least %d", tc.expectedReconciliationCount) + + for metricType, expectedAction := range tc.expectedReportedMetricComputationActionLabels { + expectedError := tc.expectedReportedMetricComputationErrorLabels[metricType] + mcv, err := metricstestutil.GetCounterMetricValue( + monitor.MetricComputationTotal.WithLabelValues(string(expectedAction), string(expectedError), string(metricType))) + if err != nil { + t.Fatalf("metric computation total not found for type %s: %v", metricType, err) + } + assert.GreaterOrEqual(t, mcv, float64(1), "metric computation count for %s should be at least 1", metricType) - // TODO: Retrieve the namespace and HPA names from the test case (tc) to replace hardcoded values below (and check). - if tc.checkDesiredReplicaMetric { - currentValue := m.GetDesiredReplicasValue("test-namespace", "test-hpa") - assert.Equal(t, tc.expectedDesiredReplicas, currentValue, - "the desired replicas should be recorded in monitor expectedly") - } + if tc.verifyMetricComputationDurations { + count, err := metricstestutil.GetHistogramMetricCount( + monitor.MetricComputationDuration.WithLabelValues(string(expectedAction), string(expectedError), string(metricType))) + if err != nil { + t.Fatalf("error getting metric computation duration for type %s: %v", metricType, err) + } + assert.Positive(t, count, "metric computation duration for %s should be recorded", metricType) + } + } + + for metricType, expectedCount := range tc.expectedMetricComputationCounts { + expectedAction := tc.expectedReportedMetricComputationActionLabels[metricType] + expectedError := tc.expectedReportedMetricComputationErrorLabels[metricType] + mcv, err := metricstestutil.GetCounterMetricValue( + monitor.MetricComputationTotal.WithLabelValues(string(expectedAction), string(expectedError), string(metricType))) + if err != nil { + t.Fatalf("error getting metric computation count for type %s: %v", metricType, err) + } + assert.GreaterOrEqual(t, int(mcv), expectedCount, "metric computation count for %s should be at least %d", metricType, expectedCount) + } + // TODO: Retrieve the namespace and HPA names from the test case (tc) to replace hardcoded values below (and check). + if tc.checkDesiredReplicaMetric { + v, err := metricstestutil.GetGaugeMetricValue(monitor.DesiredReplicasCount.WithLabelValues("test-namespace", "test-hpa")) + if err != nil { + t.Fatalf("error getting desired replicas metric: %v", err) + } + assert.InEpsilon(t, float64(tc.expectedDesiredReplicas), v, 0.01, + "the desired replicas should be recorded in monitor expectedly") + } + } } func (tc *testCase) setupController(t *testing.T) (*HorizontalController, informers.SharedInformerFactory) { @@ -811,7 +864,15 @@ func (tc *testCase) setupController(t *testing.T) (*HorizontalController, inform hpaController.hpaSelectors = tc.hpaSelectors } - hpaController.monitor = newMockMonitor() + // reset all HPA prometheus metrics + monitor.Register() + monitor.ReconciliationsTotal.Reset() + monitor.ReconciliationsDuration.Reset() + monitor.MetricComputationTotal.Reset() + monitor.MetricComputationDuration.Reset() + monitor.NumHorizontalPodAutoscalers.Set(0) + monitor.DesiredReplicasCount.Reset() + hpaController.monitor = monitor.New() return hpaController, informerFactory } @@ -836,6 +897,7 @@ func (tc *testCase) runTestWithController(t *testing.T, hpaController *Horizonta tc.Lock() shouldWait := tc.verifyEvents + minReconciliations := tc.expectedReconciliationCount tc.Unlock() if shouldWait { @@ -852,13 +914,26 @@ func (tc *testCase) runTestWithController(t *testing.T, hpaController *Horizonta } } else { // Wait for HPA to be processed. - <-tc.processed - } - m, ok := hpaController.monitor.(*mockMonitor) - if !ok { - t.Fatalf("test HPA controller should have mockMonitor, but actually not") + if minReconciliations < 1 { + t.Logf("minReconciliations should be at least 1, got %d; adjusting to 1", minReconciliations) + minReconciliations = 1 + } + timeoutTime := time.Now().Add(5 * time.Second) + reconciliationsProcessed := 0 + for reconciliationsProcessed < minReconciliations && time.Now().Before(timeoutTime) { + select { + case <-tc.processed: + reconciliationsProcessed++ + case <-time.After(100 * time.Millisecond): + // continue waiting + } + } + if reconciliationsProcessed < minReconciliations { + t.Fatalf("expected at least %d reconciliations, but only got %d", minReconciliations, reconciliationsProcessed) + } } - tc.verifyResults(ctx, t, m) + + tc.verifyResults(ctx, t) } func (tc *testCase) runTest(t *testing.T) { @@ -866,89 +941,6 @@ func (tc *testCase) runTest(t *testing.T) { tc.runTestWithController(t, hpaController, informerFactory) } -// mockMonitor implements monitor.Monitor interface. -// It records which results are observed in slices. -type mockMonitor struct { - sync.RWMutex - reconciliationActionLabels []monitor.ActionLabel - reconciliationErrorLabels []monitor.ErrorLabel - - metricComputationActionLabels map[autoscalingv2.MetricSourceType][]monitor.ActionLabel - metricComputationErrorLabels map[autoscalingv2.MetricSourceType][]monitor.ErrorLabel - metricObjectsCount int - desiredReplicasValues map[string]int32 // key is "namespace/name" -} - -func newMockMonitor() *mockMonitor { - return &mockMonitor{ - metricComputationActionLabels: make(map[autoscalingv2.MetricSourceType][]monitor.ActionLabel), - metricComputationErrorLabels: make(map[autoscalingv2.MetricSourceType][]monitor.ErrorLabel), - desiredReplicasValues: make(map[string]int32), - } -} - -func (m *mockMonitor) ObserveReconciliationResult(action monitor.ActionLabel, err monitor.ErrorLabel, _ time.Duration) { - m.Lock() - defer m.Unlock() - m.reconciliationActionLabels = append(m.reconciliationActionLabels, action) - m.reconciliationErrorLabels = append(m.reconciliationErrorLabels, err) -} - -func (m *mockMonitor) ObserveMetricComputationResult(action monitor.ActionLabel, err monitor.ErrorLabel, duration time.Duration, metricType autoscalingv2.MetricSourceType) { - m.Lock() - defer m.Unlock() - - m.metricComputationActionLabels[metricType] = append(m.metricComputationActionLabels[metricType], action) - m.metricComputationErrorLabels[metricType] = append(m.metricComputationErrorLabels[metricType], err) -} - -// waitUntilRecorded waits for the HPA controller to reconcile at least once. -func (m *mockMonitor) waitUntilRecorded(ctx context.Context, t *testing.T) { - if err := wait.PollUntilContextTimeout(ctx, 20*time.Millisecond, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) { - m.RWMutex.RLock() - defer m.RWMutex.RUnlock() - if len(m.reconciliationActionLabels) == 0 || len(m.reconciliationErrorLabels) == 0 { - return false, nil - } - return true, nil - }); err != nil { - t.Fatalf("no reconciliation is recorded in the monitor, len(monitor.reconciliationActionLabels)=%v len(monitor.reconciliationErrorLabels)=%v ", len(m.reconciliationActionLabels), len(m.reconciliationErrorLabels)) - } -} - -func (m *mockMonitor) ObserveHPAAddition() { - m.Lock() - defer m.Unlock() - m.metricObjectsCount++ -} - -func (m *mockMonitor) ObserveHPADeletion() { - m.Lock() - defer m.Unlock() - m.metricObjectsCount-- -} - -func (m *mockMonitor) GetObjectsCount() int { - m.RLock() - defer m.RUnlock() - return m.metricObjectsCount -} - -func (m *mockMonitor) ObserveDesiredReplicas(namespace, hpaName string, desiredReplicas int32) { - fmt.Printf("putting %s/%s with %v", namespace, hpaName, desiredReplicas) - m.Lock() - defer m.Unlock() - key := fmt.Sprintf("%s/%s", namespace, hpaName) - m.desiredReplicasValues[key] = desiredReplicas -} - -func (m *mockMonitor) GetDesiredReplicasValue(namespace, hpaName string) int32 { - m.RLock() - defer m.RUnlock() - key := fmt.Sprintf("%s/%s", namespace, hpaName) - return m.desiredReplicasValues[key] -} - func TestScaleUp(t *testing.T) { tc := testCase{ minReplicas: 2, @@ -1448,85 +1440,147 @@ func TestScaleUpCMObject(t *testing.T) { } func TestScaleUpFromZeroCMObject(t *testing.T) { - targetValue := resource.MustParse("15.0") - tc := testCase{ - minReplicas: 0, - maxReplicas: 6, - specReplicas: 0, - statusReplicas: 0, - expectedDesiredReplicas: 2, - CPUTarget: 0, - metricsTarget: []autoscalingv2.MetricSpec{ - { - Type: autoscalingv2.ObjectMetricSourceType, - Object: &autoscalingv2.ObjectMetricSource{ - DescribedObject: autoscalingv2.CrossVersionObjectReference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "some-deployment", - }, - Metric: autoscalingv2.MetricIdentifier{ - Name: "qps", + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + targetValue := resource.MustParse("15.0") + tc := testCase{ + minReplicas: 0, + maxReplicas: 6, + specReplicas: 0, + statusReplicas: 0, + CPUTarget: 0, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ObjectMetricSourceType, + Object: &autoscalingv2.ObjectMetricSource{ + DescribedObject: autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "some-deployment", + }, + Metric: autoscalingv2.MetricIdentifier{ + Name: "qps", + }, + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.ValueMetricType, + Value: &targetValue, + }, + }, }, - Target: autoscalingv2.MetricTarget{ - Type: autoscalingv2.ValueMetricType, - Value: &targetValue, + }, + reportedLevels: []uint64{20000}, + initialConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + { + Type: autoscalingv2.ScaledToZero, + Status: v1.ConditionTrue, + Reason: "ScaledToZero", }, }, - }, - }, - reportedLevels: []uint64{20000}, - expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp, - expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, - expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ - autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp, - }, - expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ - autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, - }, + } + if fgEnabled { + tc.expectedDesiredReplicas = 2 + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.ScaledToZero, Status: v1.ConditionFalse, Reason: "NotScaledToZero"}, + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"}, + {Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"}, + } + tc.expectedReportedReconciliationActionLabel = monitor.ActionLabelScaleUp + tc.expectedReportedReconciliationErrorLabel = monitor.ErrorLabelNone + tc.expectedReportedMetricComputationActionLabels = map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp, + } + tc.expectedReportedMetricComputationErrorLabels = map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, + } + } else { + // FG off: scaledToZeroCondition=false, canScaleFromZero=false → scaling disabled + tc.expectedDesiredReplicas = 0 + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"}, + } + tc.expectedReportedReconciliationActionLabel = monitor.ActionLabelNone + tc.expectedReportedReconciliationErrorLabel = monitor.ErrorLabelNone + tc.expectedReportedMetricComputationActionLabels = map[autoscalingv2.MetricSourceType]monitor.ActionLabel{} + tc.expectedReportedMetricComputationErrorLabels = map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{} + } + tc.runTest(t) + }) } - tc.runTest(t) } func TestScaleUpFromZeroIgnoresToleranceCMObject(t *testing.T) { - targetValue := resource.MustParse("1.0") - tc := testCase{ - minReplicas: 0, - maxReplicas: 6, - specReplicas: 0, - statusReplicas: 0, - expectedDesiredReplicas: 1, - CPUTarget: 0, - metricsTarget: []autoscalingv2.MetricSpec{ - { - Type: autoscalingv2.ObjectMetricSourceType, - Object: &autoscalingv2.ObjectMetricSource{ - DescribedObject: autoscalingv2.CrossVersionObjectReference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "some-deployment", - }, - Metric: autoscalingv2.MetricIdentifier{ - Name: "qps", + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + targetValue := resource.MustParse("1.0") + tc := testCase{ + minReplicas: 0, + maxReplicas: 6, + specReplicas: 0, + statusReplicas: 0, + CPUTarget: 0, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ObjectMetricSourceType, + Object: &autoscalingv2.ObjectMetricSource{ + DescribedObject: autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "some-deployment", + }, + Metric: autoscalingv2.MetricIdentifier{ + Name: "qps", + }, + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.ValueMetricType, + Value: &targetValue, + }, + }, }, - Target: autoscalingv2.MetricTarget{ - Type: autoscalingv2.ValueMetricType, - Value: &targetValue, + }, + reportedLevels: []uint64{1000}, + initialConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + { + Type: autoscalingv2.ScaledToZero, + Status: v1.ConditionTrue, + Reason: "ScaledToZero", }, }, - }, - }, - reportedLevels: []uint64{1000}, - expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp, - expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, - expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ - autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp, - }, - expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ - autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, - }, + } + if fgEnabled { + tc.expectedDesiredReplicas = 1 + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.ScaledToZero, Status: v1.ConditionFalse, Reason: "NotScaledToZero"}, + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"}, + {Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"}, + } + tc.expectedReportedReconciliationActionLabel = monitor.ActionLabelScaleUp + tc.expectedReportedReconciliationErrorLabel = monitor.ErrorLabelNone + tc.expectedReportedMetricComputationActionLabels = map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp, + } + tc.expectedReportedMetricComputationErrorLabels = map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, + } + } else { + // FG off: scaledToZeroCondition=false, canScaleFromZero=false → scaling disabled + tc.expectedDesiredReplicas = 0 + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"}, + } + tc.expectedReportedReconciliationActionLabel = monitor.ActionLabelNone + tc.expectedReportedReconciliationErrorLabel = monitor.ErrorLabelNone + tc.expectedReportedMetricComputationActionLabels = map[autoscalingv2.MetricSourceType]monitor.ActionLabel{} + tc.expectedReportedMetricComputationErrorLabels = map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{} + } + tc.runTest(t) + }) } - tc.runTest(t) } func TestScaleUpPerPodCMObject(t *testing.T) { @@ -1758,36 +1812,39 @@ func TestScaleUpOneMetricInvalid(t *testing.T) { } func TestScaleUpFromZeroOneMetricInvalid(t *testing.T) { - tc := testCase{ - minReplicas: 0, - maxReplicas: 6, - specReplicas: 0, - statusReplicas: 0, - expectedDesiredReplicas: 4, - CPUTarget: 30, - verifyCPUCurrent: true, - metricsTarget: []autoscalingv2.MetricSpec{ - { - Type: "CheddarCheese", - }, - }, - reportedLevels: []uint64{300, 400, 500}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - recommendations: []timestampedRecommendation{}, - expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp, - expectedReportedReconciliationErrorLabel: monitor.ErrorLabelInternal, - expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ - autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp, - // Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded. - "CheddarCheese": monitor.ActionLabelNone, - }, - expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ - autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone, - // Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded. - "CheddarCheese": monitor.ErrorLabelSpec, - }, + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + // This test uses CPU metrics, not object/external, so it can't scale from zero + // regardless of feature gate state (only object/external metrics support scale-from-zero per KEP-2021) + tc := testCase{ + minReplicas: 0, + maxReplicas: 6, + specReplicas: 0, + statusReplicas: 0, + expectedDesiredReplicas: 0, + CPUTarget: 30, + verifyCPUCurrent: false, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: "CheddarCheese", + }, + }, + reportedLevels: []uint64{300, 400, 500}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + recommendations: []timestampedRecommendation{}, + expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"}, + }, + expectedReportedReconciliationActionLabel: monitor.ActionLabelNone, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{}, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{}, + } + tc.runTest(t) + }) } - tc.runTest(t) } func TestScaleUpBothMetricsEmpty(t *testing.T) { // Switch to missing @@ -1939,46 +1996,62 @@ func TestScaleDownCMObject(t *testing.T) { } func TestScaleDownToZeroCMObject(t *testing.T) { - targetValue := resource.MustParse("20.0") - tc := testCase{ - minReplicas: 0, - maxReplicas: 6, - specReplicas: 5, - statusReplicas: 5, - expectedDesiredReplicas: 0, - CPUTarget: 0, - metricsTarget: []autoscalingv2.MetricSpec{ - { - Type: autoscalingv2.ObjectMetricSourceType, - Object: &autoscalingv2.ObjectMetricSource{ - DescribedObject: autoscalingv2.CrossVersionObjectReference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "some-deployment", - }, - Metric: autoscalingv2.MetricIdentifier{ - Name: "qps", - }, - Target: autoscalingv2.MetricTarget{ - Type: autoscalingv2.ValueMetricType, - Value: &targetValue, + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + targetValue := resource.MustParse("20.0") + tc := testCase{ + minReplicas: 0, + maxReplicas: 6, + specReplicas: 5, + statusReplicas: 5, + expectedDesiredReplicas: 0, + CPUTarget: 0, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ObjectMetricSourceType, + Object: &autoscalingv2.ObjectMetricSource{ + DescribedObject: autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "some-deployment", + }, + Metric: autoscalingv2.MetricIdentifier{ + Name: "qps", + }, + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.ValueMetricType, + Value: &targetValue, + }, + }, }, }, - }, - }, - reportedLevels: []uint64{0}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - recommendations: []timestampedRecommendation{}, - expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown, - expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, - expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ - autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleDown, - }, - expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ - autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, - }, + reportedLevels: []uint64{0}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + recommendations: []timestampedRecommendation{}, + expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleDown, + }, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, + }, + } + if fgEnabled { + // FG on: ScaledToZero condition set on scale-down to zero + tc.expectedConditions = statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{ + Type: autoscalingv2.ScaledToZero, + Status: v1.ConditionTrue, + Reason: "ScaledToZero", + }) + } else { + // FG off: no ScaledToZero condition, but scale-down still happens + tc.expectedConditions = statusOkWithOverrides() + } + tc.runTest(t) + }) } - tc.runTest(t) } func TestScaleDownPerPodCMObject(t *testing.T) { @@ -2061,39 +2134,252 @@ func TestScaleDownCMExternal(t *testing.T) { } func TestScaleDownToZeroCMExternal(t *testing.T) { - tc := testCase{ - minReplicas: 0, - maxReplicas: 6, - specReplicas: 5, - statusReplicas: 5, - expectedDesiredReplicas: 0, - metricsTarget: []autoscalingv2.MetricSpec{ - { - Type: autoscalingv2.ExternalMetricSourceType, - External: &autoscalingv2.ExternalMetricSource{ - Metric: autoscalingv2.MetricIdentifier{ - Name: "qps", - Selector: &metav1.LabelSelector{}, + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + tc := testCase{ + minReplicas: 0, + maxReplicas: 6, + specReplicas: 5, + statusReplicas: 5, + expectedDesiredReplicas: 0, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ExternalMetricSourceType, + External: &autoscalingv2.ExternalMetricSource{ + Metric: autoscalingv2.MetricIdentifier{ + Name: "qps", + Selector: &metav1.LabelSelector{}, + }, + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.ValueMetricType, + Value: resource.NewMilliQuantity(14400, resource.DecimalSI), + }, + }, }, - Target: autoscalingv2.MetricTarget{ - Type: autoscalingv2.ValueMetricType, - Value: resource.NewMilliQuantity(14400, resource.DecimalSI), + }, + reportedLevels: []uint64{0}, + recommendations: []timestampedRecommendation{}, + expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleDown, + }, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone, + }, + } + if fgEnabled { + tc.expectedConditions = statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{ + Type: autoscalingv2.ScaledToZero, + Status: v1.ConditionTrue, + Reason: "ScaledToZero", + }) + } else { + tc.expectedConditions = statusOkWithOverrides() + } + tc.runTest(t) + }) + } +} + +func TestScaleUpFromZeroWithoutCondition(t *testing.T) { + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + // Without ScaledToZero condition, scaling from zero is disabled regardless of feature gate + targetValue := resource.MustParse("15.0") + tc := testCase{ + minReplicas: 0, + maxReplicas: 6, + specReplicas: 0, + statusReplicas: 0, + expectedDesiredReplicas: 0, + CPUTarget: 0, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ObjectMetricSourceType, + Object: &autoscalingv2.ObjectMetricSource{ + DescribedObject: autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "some-deployment", + }, + Metric: autoscalingv2.MetricIdentifier{ + Name: "qps", + }, + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.ValueMetricType, + Value: &targetValue, + }, + }, }, }, - }, - }, - reportedLevels: []uint64{0}, - recommendations: []timestampedRecommendation{}, - expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown, - expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, - expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ - autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleDown, - }, - expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ - autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone, - }, + reportedLevels: []uint64{20000}, + expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"}, + }, + expectedReportedReconciliationActionLabel: monitor.ActionLabelNone, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{}, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{}, + } + tc.runTest(t) + }) + } +} + +func TestScaleUpFromZeroWhenMinReplicasIncreased(t *testing.T) { + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + specReplicas: 0, + statusReplicas: 0, + CPUTarget: 0, + metricsTarget: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ObjectMetricSourceType, + Object: &autoscalingv2.ObjectMetricSource{ + DescribedObject: autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "some-deployment", + }, + Metric: autoscalingv2.MetricIdentifier{ + Name: "qps", + }, + Target: autoscalingv2.MetricTarget{ + Type: autoscalingv2.ValueMetricType, + Value: resource.NewMilliQuantity(15000, resource.DecimalSI), + }, + }, + }, + }, + reportedLevels: []uint64{10000}, + initialConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + { + Type: autoscalingv2.ScaledToZero, + Status: v1.ConditionTrue, + Reason: "ScaledToZero", + }, + }, + } + if fgEnabled { + // HPA previously scaled to zero, minReplicas increased to 2 → scale up to minReplicas + tc.expectedDesiredReplicas = 2 + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.ScaledToZero, Status: v1.ConditionFalse, Reason: "NotScaledToZero"}, + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"}, + {Type: autoscalingv2.ScalingLimited, Status: v1.ConditionTrue, Reason: "TooFewReplicas"}, + } + tc.expectedReportedReconciliationActionLabel = monitor.ActionLabelScaleUp + tc.expectedReportedReconciliationErrorLabel = monitor.ErrorLabelNone + tc.expectedReportedMetricComputationActionLabels = map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp, + } + tc.expectedReportedMetricComputationErrorLabels = map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone, + } + } else { + // FG off: scaledToZeroCondition=false → treated as manual scale-down, scaling disabled + tc.expectedDesiredReplicas = 0 + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"}, + } + tc.expectedReportedReconciliationActionLabel = monitor.ActionLabelNone + tc.expectedReportedReconciliationErrorLabel = monitor.ErrorLabelNone + tc.expectedReportedMetricComputationActionLabels = map[autoscalingv2.MetricSourceType]monitor.ActionLabel{} + tc.expectedReportedMetricComputationErrorLabels = map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{} + } + tc.runTest(t) + }) + } +} + +func TestScaledToZeroConditionHandledOnNormalRescale(t *testing.T) { + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + tc := testCase{ + minReplicas: 1, + maxReplicas: 6, + specReplicas: 3, + statusReplicas: 3, + expectedDesiredReplicas: 5, + CPUTarget: 30, + reportedLevels: []uint64{300, 500, 700}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + useMetricsAPI: true, + initialConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + { + Type: autoscalingv2.ScaledToZero, + Status: v1.ConditionTrue, + Reason: "ScaledToZero", + }, + }, + expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp, + }, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone, + }, + } + if fgEnabled { + // FG on: ScaledToZero condition set to False on rescale + tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.ScaledToZero, Status: v1.ConditionFalse, Reason: "NotScaledToZero"}, + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"}, + {Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"}, + } + } else { + // FG off: stale ScaledToZero condition removed on rescale + tc.expectedConditions = statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{ + Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, + Reason: "DesiredWithinRange", + }) + } + tc.runTest(t) + }) + } +} + +func TestManualScaleToZeroDisablesHPA(t *testing.T) { + for _, fgEnabled := range []bool{true, false} { + t.Run(fmt.Sprintf("HPAScaleToZero=%v", fgEnabled), func(t *testing.T) { + // With minReplicas >= 1, a workload manually scaled to zero (no ScaledToZero condition) + // should cause HPA to pause (ScalingDisabled), regardless of feature gate + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, fgEnabled) + tc := testCase{ + minReplicas: 1, + maxReplicas: 6, + specReplicas: 0, + statusReplicas: 0, + expectedDesiredReplicas: 0, + CPUTarget: 50, + reportedLevels: []uint64{}, + reportedCPURequests: []resource.Quantity{}, + useMetricsAPI: true, + expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"}, + }, + expectedReportedReconciliationActionLabel: monitor.ActionLabelNone, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{}, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{}, + } + tc.runTest(t) + }) } - tc.runTest(t) } func TestScaleDownPerPodCMExternal(t *testing.T) { @@ -5445,7 +5731,9 @@ func TestMultipleHPAs(t *testing.T) { ) hpaController.scaleUpEvents = scaleUpEventsMap hpaController.scaleDownEvents = scaleDownEventsMap - hpaController.monitor = newMockMonitor() + monitor.Register() + monitor.NumHorizontalPodAutoscalers.Set(0) + hpaController.monitor = monitor.New() informerFactory.Start(tCtx.Done()) go hpaController.Run(tCtx, 5) @@ -5463,14 +5751,17 @@ func TestMultipleHPAs(t *testing.T) { } assert.Len(t, processedHPA, hpaCount, "Expected to process all HPAs") - assert.Equal(t, hpaCount, hpaController.monitor.(*mockMonitor).GetObjectsCount(), "Expected objects count to match number of HPAs") + v, err := metricstestutil.GetGaugeMetricValue(monitor.NumHorizontalPodAutoscalers) + require.NoError(t, err) + assert.InEpsilon(t, float64(hpaCount), v, 0.01, "Expected objects count to match number of HPAs") // Simulate the watch event for deletion hpaWatcher.Delete(&hpaList[0]) // Give the controller time to process the deletion and update the monitor assert.Eventually(t, func() bool { - return hpaController.monitor.(*mockMonitor).GetObjectsCount() == hpaCount-1 + v, err := metricstestutil.GetGaugeMetricValue(monitor.NumHorizontalPodAutoscalers) + return err == nil && v == float64(hpaCount-1) }, 5*time.Second, 100*time.Millisecond, "Expected objects count to be hpaCount-1 after an HPA was deleted") } @@ -5518,6 +5809,97 @@ func TestHPARescaleWithSuccessfulConflictRetry(t *testing.T) { tc.runTest(t) } +func TestReconciliationDurationIsRecorded(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + specReplicas: 3, + statusReplicas: 3, + expectedDesiredReplicas: 5, + CPUTarget: 30, + verifyCPUCurrent: true, + reportedLevels: []uint64{300, 500, 700}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + useMetricsAPI: true, + expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp, + }, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone, + }, + verifyReconciliationDuration: true, + verifyMetricComputationDurations: true, + } + tc.runTest(t) +} + +func TestReconciliationDurationIsRecordedOnError(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + specReplicas: 4, + statusReplicas: 4, + expectedDesiredReplicas: 4, + CPUTarget: 100, + reportedLevels: []uint64{}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + useMetricsAPI: true, + expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{ + {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, + {Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"}, + }, + expectedReportedReconciliationActionLabel: monitor.ActionLabelNone, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelInternal, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone, + }, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelInternal, + }, + verifyReconciliationDuration: true, + verifyMetricComputationDurations: true, + } + tc.runTest(t) +} + +func TestReconciliationsTotalCountMultipleReconciliations(t *testing.T) { + tc := testCase{ + minReplicas: 1, + maxReplicas: 5, + specReplicas: 3, + statusReplicas: 3, + expectedDesiredReplicas: 3, + CPUTarget: 100, + reportedLevels: []uint64{1010, 1030, 1020}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, + useMetricsAPI: true, + expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{ + Type: autoscalingv2.AbleToScale, + Status: v1.ConditionTrue, + Reason: "ReadyForNewScale", + }), + expectedReportedReconciliationActionLabel: monitor.ActionLabelNone, + expectedReportedReconciliationErrorLabel: monitor.ErrorLabelNone, + expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone, + }, + expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{ + autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone, + }, + // The specific count value is not important. This test verifies that + // reconciliationsTotal counter is incremented on each reconciliation cycle. + expectedReconciliationCount: 3, + // The specific count values are not important. This test verifies that + // metricComputationTotal counter is incremented for each metric type on each reconciliation. + expectedMetricComputationCounts: map[autoscalingv2.MetricSourceType]int{ + autoscalingv2.ResourceMetricSourceType: 3, + }, + } + tc.runTest(t) +} + func TestBuildQuantity(t *testing.T) { tests := []struct { name string diff --git a/pkg/controller/podautoscaler/monitor/metrics.go b/pkg/controller/podautoscaler/monitor/metrics.go index d91b11f058de5..e3e56feb7ee37 100644 --- a/pkg/controller/podautoscaler/monitor/metrics.go +++ b/pkg/controller/podautoscaler/monitor/metrics.go @@ -30,37 +30,37 @@ const ( ) var ( - reconciliationsTotal = metrics.NewCounterVec( + ReconciliationsTotal = metrics.NewCounterVec( &metrics.CounterOpts{ Subsystem: hpaControllerSubsystem, Name: "reconciliations_total", Help: "Number of reconciliations of HPA controller. The label 'action' should be either 'scale_down', 'scale_up', or 'none'. Also, the label 'error' should be either 'spec', 'internal', or 'none'. Note that if both spec and internal errors happen during a reconciliation, the first one to occur is reported in `error` label.", - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"action", "error"}) - reconciliationsDuration = metrics.NewHistogramVec( + ReconciliationsDuration = metrics.NewHistogramVec( &metrics.HistogramOpts{ Subsystem: hpaControllerSubsystem, Name: "reconciliation_duration_seconds", Help: "The time(seconds) that the HPA controller takes to reconcile once. The label 'action' should be either 'scale_down', 'scale_up', or 'none'. Also, the label 'error' should be either 'spec', 'internal', or 'none'. Note that if both spec and internal errors happen during a reconciliation, the first one to occur is reported in `error` label.", Buckets: metrics.ExponentialBuckets(0.001, 2, 15), - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"action", "error"}) - metricComputationTotal = metrics.NewCounterVec( + MetricComputationTotal = metrics.NewCounterVec( &metrics.CounterOpts{ Subsystem: hpaControllerSubsystem, Name: "metric_computation_total", Help: "Number of metric computations. The label 'action' should be either 'scale_down', 'scale_up', or 'none'. Also, the label 'error' should be either 'spec', 'internal', or 'none'. The label 'metric_type' corresponds to HPA.spec.metrics[*].type", - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"action", "error", "metric_type"}) - metricComputationDuration = metrics.NewHistogramVec( + MetricComputationDuration = metrics.NewHistogramVec( &metrics.HistogramOpts{ Subsystem: hpaControllerSubsystem, Name: "metric_computation_duration_seconds", Help: "The time(seconds) that the HPA controller takes to calculate one metric. The label 'action' should be either 'scale_down', 'scale_up', or 'none'. The label 'error' should be either 'spec', 'internal', or 'none'. The label 'metric_type' corresponds to HPA.spec.metrics[*].type", Buckets: metrics.ExponentialBuckets(0.001, 2, 15), - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"action", "error", "metric_type"}) - numHorizontalPodAutoscalers = metrics.NewGauge( + NumHorizontalPodAutoscalers = metrics.NewGauge( &metrics.GaugeOpts{ Subsystem: hpaControllerSubsystem, Name: "num_horizontal_pod_autoscalers", @@ -68,7 +68,7 @@ var ( StabilityLevel: metrics.ALPHA, }, ) - desiredReplicasCount = metrics.NewGaugeVec( + DesiredReplicasCount = metrics.NewGaugeVec( &metrics.GaugeOpts{ Subsystem: hpaControllerSubsystem, Name: "desired_replicas", @@ -77,12 +77,12 @@ var ( }, []string{"namespace", "hpa_name"}) metricsList = []metrics.Registerable{ - reconciliationsTotal, - reconciliationsDuration, - metricComputationTotal, - metricComputationDuration, - numHorizontalPodAutoscalers, - desiredReplicasCount, + ReconciliationsTotal, + ReconciliationsDuration, + MetricComputationTotal, + MetricComputationDuration, + NumHorizontalPodAutoscalers, + DesiredReplicasCount, } ) diff --git a/pkg/controller/podautoscaler/monitor/monitor.go b/pkg/controller/podautoscaler/monitor/monitor.go index e5d5b75f5ace8..53653f7a2b970 100644 --- a/pkg/controller/podautoscaler/monitor/monitor.go +++ b/pkg/controller/podautoscaler/monitor/monitor.go @@ -54,27 +54,27 @@ func New() Monitor { // ObserveReconciliationResult observes some metrics from a reconciliation result. func (r *monitor) ObserveReconciliationResult(action ActionLabel, err ErrorLabel, duration time.Duration) { - reconciliationsTotal.WithLabelValues(string(action), string(err)).Inc() - reconciliationsDuration.WithLabelValues(string(action), string(err)).Observe(duration.Seconds()) + ReconciliationsTotal.WithLabelValues(string(action), string(err)).Inc() + ReconciliationsDuration.WithLabelValues(string(action), string(err)).Observe(duration.Seconds()) } // ObserveMetricComputationResult observes some metrics from a metric computation result. func (r *monitor) ObserveMetricComputationResult(action ActionLabel, err ErrorLabel, duration time.Duration, metricType v2.MetricSourceType) { - metricComputationTotal.WithLabelValues(string(action), string(err), string(metricType)).Inc() - metricComputationDuration.WithLabelValues(string(action), string(err), string(metricType)).Observe(duration.Seconds()) + MetricComputationTotal.WithLabelValues(string(action), string(err), string(metricType)).Inc() + MetricComputationDuration.WithLabelValues(string(action), string(err), string(metricType)).Observe(duration.Seconds()) } // ObserveHPAAddition observes the addition of an HPA object. func (r *monitor) ObserveHPAAddition() { - numHorizontalPodAutoscalers.Inc() + NumHorizontalPodAutoscalers.Inc() } // ObserveHPADeletion observes the deletion of an HPA object. func (r *monitor) ObserveHPADeletion() { - numHorizontalPodAutoscalers.Dec() + NumHorizontalPodAutoscalers.Dec() } // ObserveDesiredReplicas records the desired replica count for an HPA object. func (r *monitor) ObserveDesiredReplicas(namespace, hpaName string, desiredReplicas int32) { - desiredReplicasCount.WithLabelValues(namespace, hpaName).Set(float64(desiredReplicas)) + DesiredReplicasCount.WithLabelValues(namespace, hpaName).Set(float64(desiredReplicas)) } diff --git a/pkg/controller/replicaset/metrics/metrics.go b/pkg/controller/replicaset/metrics/metrics.go index 8fe43fce23c8e..e4e43340ee397 100644 --- a/pkg/controller/replicaset/metrics/metrics.go +++ b/pkg/controller/replicaset/metrics/metrics.go @@ -22,19 +22,34 @@ import ( const ReplicaSetControllerSubsystem = "replicaset_controller" -var SortingDeletionAgeRatio = metrics.NewHistogram( - &metrics.HistogramOpts{ - Subsystem: ReplicaSetControllerSubsystem, - Name: "sorting_deletion_age_ratio", - Help: "The ratio of chosen deleted pod's ages to the current youngest pod's age (at the time). Should be <2. " + - "The intent of this metric is to measure the rough efficacy of the LogarithmicScaleDown feature gate's effect on " + - "the sorting (and deletion) of pods when a replicaset scales down. This only considers Ready pods when calculating and reporting.", - Buckets: metrics.ExponentialBuckets(0.25, 2, 6), - StabilityLevel: metrics.ALPHA, - }, +var ( + SortingDeletionAgeRatio = metrics.NewHistogram( + &metrics.HistogramOpts{ + Subsystem: ReplicaSetControllerSubsystem, + Name: "sorting_deletion_age_ratio", + Help: "The ratio of chosen deleted pod's ages to the current youngest pod's age (at the time). Should be <2. " + + "The intent of this metric is to measure the rough efficacy of the LogarithmicScaleDown feature gate's effect on " + + "the sorting (and deletion) of pods when a replicaset scales down. This only considers Ready pods when calculating and reporting.", + Buckets: metrics.ExponentialBuckets(0.25, 2, 6), + StabilityLevel: metrics.ALPHA, + }, + ) + + ReplicaSetRequeueSkips = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: ReplicaSetControllerSubsystem, + Name: "stale_sync_skips_total", + Help: "Total number of ReplicaSet syncs skipped due to a stale watch cache.", + StabilityLevel: metrics.ALPHA, + }, + []string{"group", "resource"}, + ) ) // Register registers ReplicaSet controller metrics. func Register(registrationFunc func(metrics.Registerable) error) error { - return registrationFunc(SortingDeletionAgeRatio) + if err := registrationFunc(SortingDeletionAgeRatio); err != nil { + return err + } + return registrationFunc(ReplicaSetRequeueSkips) } diff --git a/pkg/controller/replicaset/replica_set.go b/pkg/controller/replicaset/replica_set.go index d29bdca2480ff..3828594963979 100644 --- a/pkg/controller/replicaset/replica_set.go +++ b/pkg/controller/replicaset/replica_set.go @@ -29,6 +29,7 @@ package replicaset import ( "context" + "errors" "fmt" "reflect" "sort" @@ -61,6 +62,7 @@ import ( podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/replicaset/metrics" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" "k8s.io/kubernetes/pkg/features" "k8s.io/utils/clock" "k8s.io/utils/ptr" @@ -79,6 +81,17 @@ const ( controllerUIDIndex = "controllerUID" ) +var ( + replicaSetGroupResource = schema.GroupResource{ + Group: "apps", + Resource: "replicasets", + } + podGroupResource = schema.GroupResource{ + Group: "", + Resource: "pods", + } +) + // ReplicaSetController is responsible for synchronizing ReplicaSet objects stored // in the system with actual running pods. type ReplicaSetController struct { @@ -120,6 +133,8 @@ type ReplicaSetController struct { clock clock.PassiveClock + consistencyStore consistencyutil.ConsistencyStore + // Controller specific features; see ReplicaSetControllerFeatures for details. controllerFeatures ReplicaSetControllerFeatures } @@ -143,6 +158,32 @@ func NewReplicaSetController(ctx context.Context, rsInformer appsinformers.Repli if err := metrics.Register(legacyregistry.Register); err != nil { logger.Error(err, "unable to register metrics") } + + var consistencyStore consistencyutil.ConsistencyStore + var podWriteCallback func(pod *v1.Pod, rs *metav1.OwnerReference) + if utilfeature.DefaultFeatureGate.Enabled(features.StaleControllerConsistencyReplicaSet) { + consistencyStore = consistencyutil.NewConsistencyStore(map[schema.GroupResource]consistencyutil.LastSyncRVGetter{ + podGroupResource: podInformer.Informer().GetStore(), + replicaSetGroupResource: rsInformer.Informer().GetStore(), + }) + podWriteCallback = func(pod *v1.Pod, rs *metav1.OwnerReference) { + if rs == nil { + return + } + consistencyStore.WroteAt( + types.NamespacedName{ + Namespace: pod.Namespace, + Name: rs.Name, + }, + rs.UID, + podGroupResource, + pod.ResourceVersion, + ) + } + } else { + consistencyStore = consistencyutil.NewNoopConsistencyStore() + } + return NewBaseController(logger, rsInformer, podInformer, kubeClient, burstReplicas, apps.SchemeGroupVersion.WithKind("ReplicaSet"), "replicaset_controller", @@ -150,16 +191,18 @@ func NewReplicaSetController(ctx context.Context, rsInformer appsinformers.Repli controller.RealPodControl{ KubeClient: kubeClient, Recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "replicaset-controller"}), + OnWrite: podWriteCallback, }, eventBroadcaster, DefaultReplicaSetControllerFeatures(), + consistencyStore, ) } // NewBaseController is the implementation of NewReplicaSetController with additional injected // parameters so that it can also serve as the implementation of NewReplicationController. func NewBaseController(logger klog.Logger, rsInformer appsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, kubeClient clientset.Interface, burstReplicas int, - gvk schema.GroupVersionKind, metricOwnerName, queueName string, podControl controller.PodControlInterface, eventBroadcaster record.EventBroadcaster, controllerFeatures ReplicaSetControllerFeatures) *ReplicaSetController { + gvk schema.GroupVersionKind, metricOwnerName, queueName string, podControl controller.PodControlInterface, eventBroadcaster record.EventBroadcaster, controllerFeatures ReplicaSetControllerFeatures, consistencyStore consistencyutil.ConsistencyStore) *ReplicaSetController { rsc := &ReplicaSetController{ GroupVersionKind: gvk, @@ -174,6 +217,7 @@ func NewBaseController(logger klog.Logger, rsInformer appsinformers.ReplicaSetIn ), clock: clock.RealClock{}, controllerFeatures: controllerFeatures, + consistencyStore: consistencyStore, } rsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -405,6 +449,10 @@ func (rsc *ReplicaSetController) deleteRS(logger klog.Logger, obj interface{}) { logger.V(4).Info("Deleting", "replicaSet", klog.KObj(rs)) + rsc.consistencyStore.Clear(types.NamespacedName{ + Namespace: rs.Namespace, + Name: rs.Name, + }, rs.UID) // Delete expectations for the ReplicaSet so if we create a new one with the same name it starts clean rsc.expectations.DeleteExpectations(logger, key) @@ -715,9 +763,22 @@ func (rsc *ReplicaSetController) syncReplicaSet(ctx context.Context, key string) if err != nil { return err } + rsNamespacedName := types.NamespacedName{Namespace: namespace, Name: name} + if err := rsc.consistencyStore.EnsureReady(rsNamespacedName); err != nil { + var consistencyErr *consistencyutil.ConsistencyError + if errors.As(err, &consistencyErr) { + metrics.ReplicaSetRequeueSkips.WithLabelValues( + consistencyErr.GroupResource.Group, + consistencyErr.GroupResource.Resource, + ).Inc() + } + return err + } + rs, err := rsc.rsLister.ReplicaSets(namespace).Get(name) if apierrors.IsNotFound(err) { logger.V(4).Info("deleted", "kind", rsc.Kind, "key", key) + rsc.consistencyStore.Clear(rsNamespacedName, "") rsc.expectations.DeleteExpectations(logger, key) return nil } @@ -769,6 +830,12 @@ func (rsc *ReplicaSetController) syncReplicaSet(ctx context.Context, key string) // Returning an error causes a requeue without forcing a hotloop return err } + rsc.consistencyStore.WroteAt( + types.NamespacedName{Name: rs.Name, Namespace: rs.Namespace}, + rs.UID, + replicaSetGroupResource, + updatedRS.ResourceVersion, + ) if manageReplicasErr != nil { return manageReplicasErr } diff --git a/pkg/controller/replicaset/replica_set_test.go b/pkg/controller/replicaset/replica_set_test.go index d221366c2934c..80ae82ecfe45e 100644 --- a/pkg/controller/replicaset/replica_set_test.go +++ b/pkg/controller/replicaset/replica_set_test.go @@ -625,8 +625,8 @@ func TestRelatedPodsLookup(t *testing.T) { } func TestWatchControllers(t *testing.T) { - - fakeWatch := watch.NewFake() + logger, _ := ktesting.NewTestContext(t) + fakeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) client := fake.NewSimpleClientset() client.PrependWatchReactor("replicasets", core.DefaultWatchReactor(fakeWatch, nil)) stopCh := make(chan struct{}) @@ -676,10 +676,10 @@ func TestWatchControllers(t *testing.T) { } func TestWatchPods(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) + logger, ctx := ktesting.NewTestContext(t) client := fake.NewSimpleClientset() - fakeWatch := watch.NewFake() + fakeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) client.PrependWatchReactor("pods", core.DefaultWatchReactor(fakeWatch, nil)) stopCh := make(chan struct{}) @@ -1631,7 +1631,7 @@ func TestDoNotAdoptOrCreateIfBeingDeletedRace(t *testing.T) { } func TestReplicaSetAvailabilityCheck(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) + tCtx := ktesting.Init(t) labelMap := map[string]string{"foo": "bar"} rs := newReplicaSet(4, labelMap) @@ -1665,7 +1665,7 @@ func TestReplicaSetAvailabilityCheck(t *testing.T) { fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl - err := manager.syncReplicaSet(ctx, GetKey(rs, t)) + err := manager.syncReplicaSet(tCtx, GetKey(rs, t)) if err != nil { t.Fatal(err) } @@ -1703,9 +1703,7 @@ func TestReplicaSetAvailabilityCheck(t *testing.T) { } // RS should be re-queued after 700ms to recompute .status.availableReplicas (200ms extra for the test). - ktesting.Eventually(ctx, func(tCtx ktesting.TContext) int { - return manager.queue.Len() - }).WithTimeout(900*time.Millisecond). + tCtx.Eventually(manager.queue.Len).WithTimeout(900*time.Millisecond). WithPolling(10*time.Millisecond). Should(gomega.Equal(1), " RS should be re-queued to recompute .status.availableReplicas") diff --git a/pkg/controller/replication/replication_controller.go b/pkg/controller/replication/replication_controller.go index aa6902f747ea0..beb52b405540b 100644 --- a/pkg/controller/replication/replication_controller.go +++ b/pkg/controller/replication/replication_controller.go @@ -36,6 +36,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/replicaset" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" ) const ( @@ -68,6 +69,10 @@ func NewReplicationManager(ctx context.Context, podInformer coreinformers.PodInf // ReplicaSets do support this field, which is then propagated to Deployments for higher-level features. EnableStatusTerminatingReplicas: false, }, + // TODO: Replication controller does not currently support stale controller consistency. + // In order to support stale controller consistency, we would need to parameterize the metrics + // and resource types passed to the consistency store. + consistencyutil.NewNoopConsistencyStore(), ), } } diff --git a/pkg/controller/resourceclaim/config/doc.go b/pkg/controller/resourceclaim/config/doc.go new file mode 100644 index 0000000000000..e605045a40158 --- /dev/null +++ b/pkg/controller/resourceclaim/config/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package + +package config diff --git a/pkg/controller/resourceclaim/config/types.go b/pkg/controller/resourceclaim/config/types.go new file mode 100644 index 0000000000000..ae8b7a0f909ed --- /dev/null +++ b/pkg/controller/resourceclaim/config/types.go @@ -0,0 +1,26 @@ +/* +Copyright 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 config + +// ResourceClaimControllerConfiguration contains elements configuring the resource claim controller. +type ResourceClaimControllerConfiguration struct { + // ConcurrentSyncs is the number of operations (deleting a pod, updating a ResourcClaim status, etc.) + // that will be done concurrently. Larger number = processing, but more CPU (and network) load. + // + // The default is 50. + ConcurrentSyncs int32 +} diff --git a/pkg/controller/resourceclaim/config/v1alpha1/conversion.go b/pkg/controller/resourceclaim/config/v1alpha1/conversion.go new file mode 100644 index 0000000000000..0b4bcfad9262e --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/conversion.go @@ -0,0 +1,40 @@ +/* +Copyright 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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/kube-controller-manager/config/v1alpha1" + "k8s.io/kubernetes/pkg/controller/resourceclaim/config" +) + +// Important! The public back-and-forth conversion functions for the types in this package +// with ResourceClaimControllerConfiguration types need to be manually exposed like this in order for +// other packages that reference this package to be able to call these conversion functions +// in an autogenerated manner. +// TODO: Fix the bug in conversion-gen so it automatically discovers these Convert_* functions +// in autogenerated code as well. + +// Convert_v1alpha1_ResourceClaimControllerConfiguration_To_config_ResourceClaimControllerConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_ResourceClaimControllerConfiguration_To_config_ResourceClaimControllerConfiguration(in *v1alpha1.ResourceClaimControllerConfiguration, out *config.ResourceClaimControllerConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_ResourceClaimControllerConfiguration_To_config_ResourceClaimControllerConfiguration(in, out, s) +} + +// Convert_config_ResourceClaimControllerConfiguration_To_v1alpha1_ResourceClaimControllerConfiguration is an autogenerated conversion function. +func Convert_config_ResourceClaimControllerConfiguration_To_v1alpha1_ResourceClaimControllerConfiguration(in *config.ResourceClaimControllerConfiguration, out *v1alpha1.ResourceClaimControllerConfiguration, s conversion.Scope) error { + return autoConvert_config_ResourceClaimControllerConfiguration_To_v1alpha1_ResourceClaimControllerConfiguration(in, out, s) +} diff --git a/pkg/controller/resourceclaim/config/v1alpha1/defaults.go b/pkg/controller/resourceclaim/config/v1alpha1/defaults.go new file mode 100644 index 0000000000000..645dc091811c6 --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/defaults.go @@ -0,0 +1,38 @@ +/* +Copyright 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 v1alpha1 + +import ( + kubectrlmgrconfigv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1" +) + +// RecommendedDefaultResourceClaimControllerConfiguration defaults a pointer to a +// ResourceClaimControllerConfiguration struct. This will set the recommended default +// values, but they may be subject to change between API versions. This function +// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo` +// function to allow consumers of this type to set whatever defaults for their +// embedded configs. Forcing consumers to use these defaults would be problematic +// as defaulting in the scheme is done as part of the conversion, and there would +// be no easy way to opt-out. Instead, if you want to use this defaulting method +// run it in your wrapper struct of this type in its `SetDefaults_` method. +func RecommendedDefaultResourceClaimControllerConfiguration(obj *kubectrlmgrconfigv1alpha1.ResourceClaimControllerConfiguration) { + if obj.ConcurrentSyncs == 0 { + // 50 workers were needed to pass the 1000 simulated node DRA test (#133113). + // The workers are mostly just idle unless some real work has to be done. + obj.ConcurrentSyncs = 50 + } +} diff --git a/pkg/controller/resourceclaim/config/v1alpha1/defaults_test.go b/pkg/controller/resourceclaim/config/v1alpha1/defaults_test.go new file mode 100644 index 0000000000000..4d53f043ac905 --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/defaults_test.go @@ -0,0 +1,31 @@ +/* +Copyright 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 v1alpha1 + +import ( + "testing" + + kubectrlmgrconfigv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1" +) + +func TestRecommendedDefaultResourceClaimControllerConfiguration(t *testing.T) { + config := new(kubectrlmgrconfigv1alpha1.ResourceClaimControllerConfiguration) + RecommendedDefaultResourceClaimControllerConfiguration(config) + if config.ConcurrentSyncs != 50 { + t.Errorf("incorrect default value, expected 50 but got %v", config.ConcurrentSyncs) + } +} diff --git a/pkg/controller/resourceclaim/config/v1alpha1/doc.go b/pkg/controller/resourceclaim/config/v1alpha1/doc.go new file mode 100644 index 0000000000000..b8d0df67ccd82 --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/controller/resourceclaim/config +// +k8s:conversion-gen-external-types=k8s.io/kube-controller-manager/config/v1alpha1 + +package v1alpha1 diff --git a/pkg/controller/resourceclaim/config/v1alpha1/register.go b/pkg/controller/resourceclaim/config/v1alpha1/register.go new file mode 100644 index 0000000000000..85bbf3effe499 --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/register.go @@ -0,0 +1,31 @@ +/* +Copyright 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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + // SchemeBuilder is the scheme builder with scheme init functions to run for this API package + SchemeBuilder runtime.SchemeBuilder + // localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package, + // defaulting and conversion init funcs are registered as well. + localSchemeBuilder = &SchemeBuilder + // AddToScheme is a global function that registers this API group & version to a scheme + AddToScheme = localSchemeBuilder.AddToScheme +) diff --git a/pkg/controller/resourceclaim/config/v1alpha1/zz_generated.conversion.go b/pkg/controller/resourceclaim/config/v1alpha1/zz_generated.conversion.go new file mode 100644 index 0000000000000..6dd0c16a6ccc3 --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,92 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + configv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1" + config "k8s.io/kubernetes/pkg/controller/resourceclaim/config" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*configv1alpha1.GroupResource)(nil), (*v1.GroupResource)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_GroupResource_To_v1_GroupResource(a.(*configv1alpha1.GroupResource), b.(*v1.GroupResource), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1.GroupResource)(nil), (*configv1alpha1.GroupResource)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_GroupResource_To_v1alpha1_GroupResource(a.(*v1.GroupResource), b.(*configv1alpha1.GroupResource), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*config.ResourceClaimControllerConfiguration)(nil), (*configv1alpha1.ResourceClaimControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_config_ResourceClaimControllerConfiguration_To_v1alpha1_ResourceClaimControllerConfiguration(a.(*config.ResourceClaimControllerConfiguration), b.(*configv1alpha1.ResourceClaimControllerConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*configv1alpha1.ResourceClaimControllerConfiguration)(nil), (*config.ResourceClaimControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ResourceClaimControllerConfiguration_To_config_ResourceClaimControllerConfiguration(a.(*configv1alpha1.ResourceClaimControllerConfiguration), b.(*config.ResourceClaimControllerConfiguration), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_GroupResource_To_v1_GroupResource(in *configv1alpha1.GroupResource, out *v1.GroupResource, s conversion.Scope) error { + out.Group = in.Group + out.Resource = in.Resource + return nil +} + +// Convert_v1alpha1_GroupResource_To_v1_GroupResource is an autogenerated conversion function. +func Convert_v1alpha1_GroupResource_To_v1_GroupResource(in *configv1alpha1.GroupResource, out *v1.GroupResource, s conversion.Scope) error { + return autoConvert_v1alpha1_GroupResource_To_v1_GroupResource(in, out, s) +} + +func autoConvert_v1_GroupResource_To_v1alpha1_GroupResource(in *v1.GroupResource, out *configv1alpha1.GroupResource, s conversion.Scope) error { + out.Group = in.Group + out.Resource = in.Resource + return nil +} + +// Convert_v1_GroupResource_To_v1alpha1_GroupResource is an autogenerated conversion function. +func Convert_v1_GroupResource_To_v1alpha1_GroupResource(in *v1.GroupResource, out *configv1alpha1.GroupResource, s conversion.Scope) error { + return autoConvert_v1_GroupResource_To_v1alpha1_GroupResource(in, out, s) +} + +func autoConvert_v1alpha1_ResourceClaimControllerConfiguration_To_config_ResourceClaimControllerConfiguration(in *configv1alpha1.ResourceClaimControllerConfiguration, out *config.ResourceClaimControllerConfiguration, s conversion.Scope) error { + out.ConcurrentSyncs = in.ConcurrentSyncs + return nil +} + +func autoConvert_config_ResourceClaimControllerConfiguration_To_v1alpha1_ResourceClaimControllerConfiguration(in *config.ResourceClaimControllerConfiguration, out *configv1alpha1.ResourceClaimControllerConfiguration, s conversion.Scope) error { + out.ConcurrentSyncs = in.ConcurrentSyncs + return nil +} diff --git a/pkg/controller/resourceclaim/config/v1alpha1/zz_generated.deepcopy.go b/pkg/controller/resourceclaim/config/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..61f6555edfc5a --- /dev/null +++ b/pkg/controller/resourceclaim/config/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 diff --git a/pkg/controller/resourceclaim/config/zz_generated.deepcopy.go b/pkg/controller/resourceclaim/config/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..9838c926ccce8 --- /dev/null +++ b/pkg/controller/resourceclaim/config/zz_generated.deepcopy.go @@ -0,0 +1,38 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package config + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceClaimControllerConfiguration) DeepCopyInto(out *ResourceClaimControllerConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceClaimControllerConfiguration. +func (in *ResourceClaimControllerConfiguration) DeepCopy() *ResourceClaimControllerConfiguration { + if in == nil { + return nil + } + out := new(ResourceClaimControllerConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/controller/resourceclaim/controller.go b/pkg/controller/resourceclaim/controller.go index 2d7d33e0c20d8..f7dadf8b0ef38 100644 --- a/pkg/controller/resourceclaim/controller.go +++ b/pkg/controller/resourceclaim/controller.go @@ -27,20 +27,25 @@ import ( v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" corev1apply "k8s.io/client-go/applyconfigurations/core/v1" + schedulingapply "k8s.io/client-go/applyconfigurations/scheduling/v1alpha2" v1informers "k8s.io/client-go/informers/core/v1" resourceinformers "k8s.io/client-go/informers/resource/v1" + schedulinginformers "k8s.io/client-go/informers/scheduling/v1alpha2" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1listers "k8s.io/client-go/listers/core/v1" resourcelisters "k8s.io/client-go/listers/resource/v1" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -53,23 +58,38 @@ import ( ) const ( - // podResourceClaimIndex is the lookup name for the index function which indexes by pod ResourceClaim templates. - podResourceClaimIndex = "pod-resource-claim-index" + // podResourceClaimIndex is the lookup name for the index function which indexes by pod ResourceClaim. + podResourceClaimIndex = "podResourceClaim" - // podResourceClaimAnnotation is the special annotation that generated - // ResourceClaims get. Its value is the pod.spec.resourceClaims[].name - // for which it was generated. This is used only inside the controller - // and not documented as part of the Kubernetes API. - podResourceClaimAnnotation = "resource.kubernetes.io/pod-claim-name" + // podResourceClaimTemplateIndex is the lookup name for the index function which indexes only by pod ResourceClaim templates. + podResourceClaimTemplateIndex = "podResourceClaimTemplate" + + // podGroupResourceClaimIndex is the lookup name for the index function + // which indexes by PodGroup ResourceClaim. + podGroupResourceClaimIndex = "podGroupResourceClaim" + + // podGroupResourceClaimTemplateIndex is the lookup name for the index + // function which indexes only by PodGroup ResourceClaim templates. + podGroupResourceClaimTemplateIndex = "podGroupResourceClaimTemplate" // claimPodOwnerIndex is used to find ResourceClaims which have // a specific pod as owner. Values for this index are the pod UID. - claimPodOwnerIndex = "claim-pod-owner-index" + claimPodOwnerIndex = "claimPodOwner" + + // claimPodGroupOwnerIndex is used to find ResourceClaims which have a + // specific PodGroup as owner. Values for this index are the PodGroup + // "namespace/UID". + claimPodGroupOwnerIndex = "claimPodGroupOwner" + + // podGroupMembersIndex is used to find Pods in a given PodGroup. + podGroupMembersIndex = "podGroupMembers" // Field manager used to update the pod status. fieldManager = "ResourceClaimController" maxUIDCacheEntries = 500 + + schedulingAPIGroupPrefix = schedulingapi.GroupName + "/" ) // Controller creates ResourceClaims for ResourceClaimTemplates in a pod spec. @@ -94,6 +114,12 @@ type Controller struct { podLister v1listers.PodLister podSynced cache.InformerSynced + // podGroupLister is the shared PodGroup lister used to fetch PodGroup + // objects from the API server. It is shared with other controllers and + // therefore the PodGroup objects in its store should be treated as immutable. + podGroupLister schedulinglisters.PodGroupLister + podGroupSynced cache.InformerSynced + // templateLister is the shared ResourceClaimTemplate lister used to // fetch template objects from the API server. It is shared with other // controllers and therefore the objects in its store should be treated @@ -105,6 +131,10 @@ type Controller struct { // limit iteration over pods to those of interest. podIndexer cache.Indexer + // podGroupIndexer has the common PodGroupResourceClaim indexer installed To + // limit iteration over PodGroups to those of interest. + podGroupIndexer cache.Indexer + // recorder is used to record events in the API server recorder record.EventRecorder @@ -117,14 +147,16 @@ type Controller struct { } const ( - claimKeyPrefix = "claim:" - podKeyPrefix = "pod:" + claimKeyPrefix = "claim:" + podKeyPrefix = "pod:" + podGroupKeyPrefix = "podgroup:" ) // Features defines which features should be enabled in the controller. type Features struct { - AdminAccess bool - PrioritizedList bool + AdminAccess bool + PrioritizedList bool + WorkloadResourceClaims bool } // NewController creates a ResourceClaim controller. @@ -133,6 +165,7 @@ func NewController( features Features, kubeClient clientset.Interface, podInformer v1informers.PodInformer, + podGroupInformer schedulinginformers.PodGroupInformer, claimInformer resourceinformers.ResourceClaimInformer, templateInformer resourceinformers.ResourceClaimTemplateInformer) (*Controller, error) { @@ -170,24 +203,82 @@ func NewController( } if _, err := claimInformer.Informer().AddEventHandlerWithOptions(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { - logger.V(6).Info("new claim", "claimDump", obj) + logger.V(6).Info("New claim", "claimDump", obj) ec.enqueueResourceClaim(logger, nil, obj) }, UpdateFunc: func(old, updated interface{}) { - logger.V(6).Info("updated claim", "claimDump", updated) + logger.V(6).Info("Updated claim", "claimDump", updated) ec.enqueueResourceClaim(logger, old, updated) }, DeleteFunc: func(obj interface{}) { - logger.V(6).Info("deleted claim", "claimDump", obj) + logger.V(6).Info("Deleted claim", "claimDump", obj) ec.enqueueResourceClaim(logger, obj, nil) }, }, cache.HandlerOptions{Logger: &logger}); err != nil { return nil, err } + if _, err := templateInformer.Informer().AddEventHandlerWithOptions(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + logger.V(6).Info("New claim template", "claimTemplateDump", obj) + ec.enqueueResourceClaimTemplate(logger, obj) + }, + UpdateFunc: func(old, updated interface{}) { + logger.V(6).Info("Updated claim template", "claimTemplateDump", updated) + ec.enqueueResourceClaimTemplate(logger, updated) + }, + DeleteFunc: func(obj interface{}) { + logger.V(6).Info("Deleted claim template", "claimTemplateDump", obj) + }, + }, cache.HandlerOptions{Logger: &logger}); err != nil { + return nil, err + } if err := ec.podIndexer.AddIndexers(cache.Indexers{podResourceClaimIndex: podResourceClaimIndexFunc}); err != nil { return nil, fmt.Errorf("could not initialize ResourceClaim controller: %w", err) } + if err := ec.podIndexer.AddIndexers(cache.Indexers{podResourceClaimTemplateIndex: podResourceClaimTemplateIndexFunc}); err != nil { + return nil, fmt.Errorf("could not initialize ResourceClaim controller: %w", err) + } + + ec.podGroupSynced = func() bool { return true } + if features.WorkloadResourceClaims { + ec.podGroupLister = podGroupInformer.Lister() + ec.podGroupSynced = podGroupInformer.Informer().HasSynced + ec.podGroupIndexer = podGroupInformer.Informer().GetIndexer() + + if _, err := podGroupInformer.Informer().AddEventHandlerWithOptions(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + logger.V(6).Info("New PodGroup", "podGroupDump", obj) + ec.enqueuePodGroup(logger, obj, false) + }, + UpdateFunc: func(old, updated interface{}) { + logger.V(6).Info("Updated PodGroup", "podGroupDump", updated) + ec.enqueuePodGroup(logger, updated, false) + }, + DeleteFunc: func(obj interface{}) { + logger.V(6).Info("Deleted PodGroup", "podGroupDump", obj) + ec.enqueuePodGroup(logger, obj, true) + }, + }, cache.HandlerOptions{Logger: &logger}); err != nil { + return nil, err + } + + if err := ec.podGroupIndexer.AddIndexers(cache.Indexers{podGroupResourceClaimIndex: podGroupResourceClaimIndexFunc}); err != nil { + return nil, fmt.Errorf("could not initialize ResourceClaim controller: %w", err) + } + if err := ec.podGroupIndexer.AddIndexers(cache.Indexers{podGroupResourceClaimTemplateIndex: podGroupResourceClaimTemplateIndexFunc}); err != nil { + return nil, fmt.Errorf("could not initialize ResourceClaim controller: %w", err) + } + + if err := ec.podIndexer.AddIndexers(cache.Indexers{podGroupMembersIndex: podGroupMemberIndexFunc}); err != nil { + return nil, fmt.Errorf("could not initialize ResourceClaim controller: %w", err) + } + + if err := claimInformer.Informer().GetIndexer().AddIndexers(cache.Indexers{claimPodGroupOwnerIndex: claimPodGroupOwnerIndexFunc}); err != nil { + return nil, fmt.Errorf("could not initialize ResourceClaim controller: %w", err) + } + } + // The mutation cache acts as an additional layer for the informer // cache and after a create made by the controller returns that // object until the informer catches up. That is necessary @@ -213,6 +304,71 @@ func NewController( return ec, nil } +func (ec *Controller) enqueueResourceClaimTemplate(logger klog.Logger, obj interface{}) { + if d, ok := obj.(cache.DeletedFinalStateUnknown); ok { + obj = d.Obj + } + template, ok := obj.(*resourceapi.ResourceClaimTemplate) + if !ok { + // Not a template,return + runtime.HandleErrorWithLogger(logger, nil, "EnqueueResourceClaimTemplate called for unexpected object", "type", fmt.Sprintf("%T", obj)) + return + } + + logger.V(6).Info("ResourceClaimTemplate added or updated", "resourceClaimTemplate", klog.KObj(template)) + + // Enqueue all pods with this template name + objects, err := ec.podIndexer.ByIndex(podResourceClaimTemplateIndex, fmt.Sprintf("%s/%s", template.Namespace, template.Name)) + if err != nil { + runtime.HandleErrorWithLogger(logger, err, "Unable to list pods for claim template", "resourceClaimTemplate", klog.KObj(template)) + return + } + if len(objects) == 0 { + logger.V(6).Info("ResourceClaimTemplate change unrelated to any known pod", "resourceClaimTemplate", klog.KObj(template)) + } + + for _, object := range objects { + pod, ok1 := object.(*v1.Pod) + if !ok1 { + // Not a pod?! + runtime.HandleErrorWithLogger(logger, nil, "unexpected type in Pod cache", "type", fmt.Sprintf("%T", object)) + return + } + logger.V(4).Info( + "Enqueuing pod due to ResourceClaimTemplate change", + "resourceClaimTemplate", klog.KObj(template), + "pod", klog.KObj(pod), + ) + ec.enqueuePod(logger, object, false) + } + + if ec.features.WorkloadResourceClaims { + // Enqueue all PodGroups with this template name + objects, err := ec.podGroupIndexer.ByIndex(podGroupResourceClaimTemplateIndex, fmt.Sprintf("%s/%s", template.Namespace, template.Name)) + if err != nil { + runtime.HandleErrorWithLogger(logger, err, "Unable to list PodGroups for claim template", "resourceClaimTemplate", klog.KObj(template)) + return + } + if len(objects) == 0 { + logger.V(6).Info("ResourceClaimTemplate change unrelated to any known PodGroup", "resourceClaimTemplate", klog.KObj(template)) + } + for _, object := range objects { + podGroup, ok := object.(*schedulingapi.PodGroup) + if !ok { + // Not a PodGroup?! + runtime.HandleErrorWithLogger(logger, nil, "unexpected type in PodGroup cache", "type", fmt.Sprintf("%T", object)) + return + } + logger.V(4).Info( + "Enqueuing PodGroup due to ResourceClaimTemplate change", + "resourceClaimTemplate", klog.KObj(template), + "podgroup", klog.KObj(podGroup), + ) + ec.enqueuePodGroup(logger, object, false) + } + } +} + func (ec *Controller) enqueuePod(logger klog.Logger, obj interface{}, deleted bool) { if d, ok := obj.(cache.DeletedFinalStateUnknown); ok { obj = d.Obj @@ -220,7 +376,7 @@ func (ec *Controller) enqueuePod(logger klog.Logger, obj interface{}, deleted bo pod, ok := obj.(*v1.Pod) if !ok { // Not a pod?! - logger.Error(nil, "enqueuePod called for unexpected object", "type", fmt.Sprintf("%T", obj)) + logger.Error(nil, "EnqueuePod called for unexpected object", "type", fmt.Sprintf("%T", obj)) return } @@ -238,11 +394,11 @@ func (ec *Controller) enqueuePod(logger klog.Logger, obj interface{}, deleted bo } if deleted { - logger.V(6).Info("pod got deleted", "pod", klog.KObj(pod)) + logger.V(6).Info("Pod got deleted", "pod", klog.KObj(pod)) ec.deletedObjects.Add(pod.UID) } - logger.V(6).Info("pod with resource claims changed", "pod", klog.KObj(pod), "deleted", deleted) + logger.V(6).Info("Pod with resource claims changed", "pod", klog.KObj(pod), "deleted", deleted) // Release reservations of a deleted or completed pod? needsClaims, reason := podNeedsClaims(pod, deleted) @@ -282,11 +438,11 @@ func (ec *Controller) enqueuePod(logger klog.Logger, obj interface{}, deleted bo needsWork, reason := ec.podNeedsWork(pod) if needsWork { - logger.V(6).Info("enqueing pod", "pod", klog.KObj(pod), "reason", reason) + logger.V(6).Info("Enqueueing pod", "pod", klog.KObj(pod), "reason", reason) ec.queue.Add(podKeyPrefix + pod.Namespace + "/" + pod.Name) return } - logger.V(6).Info("not enqueing pod", "pod", klog.KObj(pod), "reason", reason) + logger.V(6).Info("Not enqueueing pod", "pod", klog.KObj(pod), "reason", reason) } func podNeedsClaims(pod *v1.Pod, deleted bool) (bool, string) { @@ -312,6 +468,8 @@ func (ec *Controller) podNeedsWork(pod *v1.Pod) (bool, string) { return false, "pod is deleted" } + var doNothingReasons []string + for _, podClaim := range pod.Spec.ResourceClaims { claimName, checkOwner, err := resourceclaim.Name(pod, &podClaim) if err != nil { @@ -320,15 +478,30 @@ func (ec *Controller) podNeedsWork(pod *v1.Pod) (bool, string) { // If the claimName is nil, then it has been determined before // that the claim is not needed. if claimName == nil { - return false, "claim is not needed" + doNothingReasons = append(doNothingReasons, fmt.Sprintf("claim %s is not needed", podClaim.Name)) + continue } claim, err := ec.claimLister.ResourceClaims(pod.Namespace).Get(*claimName) if apierrors.IsNotFound(err) { if podClaim.ResourceClaimTemplateName != nil { + podGroup, err := ec.getPodGroup(pod) + if err != nil { + return true, "ResourceClaim might have to be created from template: " + err.Error() + } + bindTo, err := resourceclaim.BindTo(pod, podGroup, &podClaim) + if err != nil { + doNothingReasons = append(doNothingReasons, fmt.Sprintf("not sure what to do for ResourceClaim for claim %s: %v", podClaim.Name, err)) + continue + } + if bindTo.APIGroup == schedulingapi.GroupName && bindTo.Resource == "podgroups" { + doNothingReasons = append(doNothingReasons, fmt.Sprintf("ResourceClaim needs to be created for the PodGroup claim %s", podClaim.Name)) + continue + } return true, "must create ResourceClaim from template" } // User needs to create claim. - return false, "claim is missing and must be created by user" + doNothingReasons = append(doNothingReasons, "ResourceClaim for claim %s is missing and must be created by user", podClaim.Name) + continue } if err != nil { // Shouldn't happen. @@ -336,9 +509,10 @@ func (ec *Controller) podNeedsWork(pod *v1.Pod) (bool, string) { } if checkOwner { - if err := resourceclaim.IsForPod(pod, claim); err != nil { + if err := resourceclaim.IsForPod(pod, claim, ec.features.WorkloadResourceClaims); err != nil { // Cannot proceed with the pod unless that other claim gets deleted. - return false, err.Error() + doNothingReasons = append(doNothingReasons, fmt.Sprintf("claim %s: %v", podClaim.Name, err)) + continue } } @@ -350,14 +524,155 @@ func (ec *Controller) podNeedsWork(pod *v1.Pod) (bool, string) { } if claim.Status.Allocation != nil && - !resourceclaim.IsReservedForPod(pod, claim) && + !resourceclaim.IsReservedForPod(pod, claim, ec.features.WorkloadResourceClaims) && resourceclaim.CanBeReserved(claim) { // Need to reserve it. return true, fmt.Sprintf("need to reserve claim %s for pod", klog.KObj(claim)) } } - return false, "nothing to do" + return false, "nothing to do for any claim: " + strings.Join(doNothingReasons, ", ") +} + +func (ec *Controller) enqueuePodGroup(logger klog.Logger, obj interface{}, deleted bool) { + if d, ok := obj.(cache.DeletedFinalStateUnknown); ok { + obj = d.Obj + } + podGroup, ok := obj.(*schedulingapi.PodGroup) + if !ok { + // Not a PodGroup?! + logger.Error(nil, "enqueuePodGroup called for unexpected object", "type", fmt.Sprintf("%T", obj)) + return + } + + // Check if PodGroup has any resource claims to process. + hasResourceClaims := len(podGroup.Spec.ResourceClaims) > 0 + if !hasResourceClaims { + // Nothing to do for it at all. + return + } + + if deleted { + logger.V(6).Info("PodGroup got deleted", "podgroup", klog.KObj(podGroup)) + ec.deletedObjects.Add(podGroup.UID) + } + + logger.V(6).Info("PodGroup with resource claims changed", "podgroup", klog.KObj(podGroup), "deleted", deleted) + + // Release reservations of a deleted PodGroup? + needsClaims, reason := podGroupNeedsClaims(podGroup, deleted) + if needsClaims { + logger.V(6).Info("Not touching claims", "podgroup", klog.KObj(podGroup), "reason", reason) + } else { + for _, podGroupClaim := range podGroup.Spec.ResourceClaims { + claimName, _, err := resourceclaim.NameFromPodGroup(podGroup, &podGroupClaim) + switch { + case err != nil: + // Either the claim was not created (nothing to do here) or + // the API changed. The later will also get reported elsewhere, + // so here it's just a debug message. + logger.V(6).Info("Nothing to do for claim during PodGroup change", "podgroup", klog.KObj(podGroup), "podGroupClaim", podGroupClaim.Name, "err", err, "reason", reason) + case claimName != nil: + key := claimKeyPrefix + podGroup.Namespace + "/" + *claimName + logger.V(6).Info("Process claim", "podgroup", klog.KObj(podGroup), "claim", klog.KRef(podGroup.Namespace, *claimName), "key", key, "reason", reason) + ec.queue.Add(key) + default: + // Nothing to do, claim wasn't generated. + logger.V(6).Info("Nothing to do for skipped claim during PodGroup change", "podgroup", klog.KObj(podGroup), "podGroupClaim", podGroupClaim.Name, "reason", reason) + } + } + } + + needsWork, reason := ec.podGroupNeedsWork(podGroup) + if needsWork { + logger.V(6).Info("Enqueueing PodGroup", "podgroup", klog.KObj(podGroup), "reason", reason) + ec.queue.Add(podGroupKeyPrefix + podGroup.Namespace + "/" + podGroup.Name) + } else { + logger.V(6).Info("Not enqueueing PodGroup", "podgroup", klog.KObj(podGroup), "reason", reason) + } + + // A PodGroup status update may make ResourceClaims allocated for the + // PodGroup available to the group's Pods. Pods enqueued here will have + // their status updated to reflect the status of their claims which match + // the PodGroup's to bootstrap the ResourceClaim->Pod index. + objects, err := ec.podIndexer.ByIndex(podGroupMembersIndex, podGroup.Namespace+"/"+podGroup.Name) + if err != nil { + runtime.HandleErrorWithLogger(logger, err, "Unable to list Pods for PodGroup", "podgroup", klog.KObj(podGroup)) + return + } + if len(objects) == 0 { + logger.V(6).Info("PodGroup change unrelated to any known Pod", "podgroup", klog.KObj(podGroup)) + } + for _, object := range objects { + pod, ok := object.(*v1.Pod) + if !ok { + // Not a pod?! + runtime.HandleErrorWithLogger(logger, nil, "unexpected object in Pod indexer", "type", fmt.Sprintf("%T", object)) + return + } + logger.V(4).Info( + "Enqueuing Pod due to PodGroup change", + "podgroup", klog.KObj(podGroup), + "pod", klog.KObj(pod), + ) + ec.enqueuePod(logger, object, false) + } +} + +func podGroupNeedsClaims(_ *schedulingapi.PodGroup, deleted bool) (bool, string) { + if deleted { + return false, "PodGroup got removed" + } + // Still needs claims. + return true, "Pods in the PodGroup might run" +} + +// podGroupNeedsWork checks whether a new or modified PodGroup needs to be processed +// further by a worker. It returns a boolean with the result and an explanation +// for it. +func (ec *Controller) podGroupNeedsWork(podGroup *schedulingapi.PodGroup) (bool, string) { + if podGroup.DeletionTimestamp != nil { + // Nothing else to do for the PodGroup. + return false, "PodGroup is deleted" + } + + var doNothingReasons []string + + for _, podGroupClaim := range podGroup.Spec.ResourceClaims { + claimName, checkOwner, err := resourceclaim.NameFromPodGroup(podGroup, &podGroupClaim) + if err != nil { + return true, err.Error() + } + // If the claimName is nil, then it has been determined before + // that the claim is not needed. + if claimName == nil { + doNothingReasons = append(doNothingReasons, fmt.Sprintf("claim %s is not needed", podGroupClaim.Name)) + continue + } + claim, err := ec.claimLister.ResourceClaims(podGroup.Namespace).Get(*claimName) + if apierrors.IsNotFound(err) { + if podGroupClaim.ResourceClaimTemplateName != nil { + return true, "must create ResourceClaim from template" + } + // User needs to create claim. + doNothingReasons = append(doNothingReasons, "ResourceClaim claim %s is missing and must be created by user", *claimName, podGroupClaim.Name) + continue + } + if err != nil { + // Shouldn't happen. + return true, fmt.Sprintf("internal error while checking for claim: %v", err) + } + + if checkOwner { + if err := resourceclaim.IsForPodGroup(podGroup, claim); err != nil { + // Cannot proceed with the PodGroup unless that other claim gets deleted. + doNothingReasons = append(doNothingReasons, fmt.Sprintf("claim %s: %v", podGroupClaim.Name, err)) + continue + } + } + } + + return false, "nothing to do for any claim: " + strings.Join(doNothingReasons, ", ") } func (ec *Controller) enqueueResourceClaim(logger klog.Logger, oldObj, newObj interface{}) { @@ -388,26 +703,41 @@ func (ec *Controller) enqueueResourceClaim(logger klog.Logger, oldObj, newObj in // stale pods in ReservedFor. During an update, a pod might get added // that already no longer exists. key := claimKeyPrefix + claim.Namespace + "/" + claim.Name - logger.V(6).Info("enqueing new or updated claim", "claim", klog.KObj(claim), "key", key) + logger.V(6).Info("Enqueueing new or updated claim", "claim", klog.KObj(claim), "key", key) ec.queue.Add(key) } else { - logger.V(6).Info("not enqueing deleted claim", "claim", klog.KObj(claim)) + logger.V(6).Info("Not enqueueing deleted claim", "claim", klog.KObj(claim)) } // Also check whether this causes work for any of the currently // known pods which use the ResourceClaim. objs, err := ec.podIndexer.ByIndex(podResourceClaimIndex, fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)) if err != nil { - logger.Error(err, "listing pods from cache") + logger.Error(err, "Failed to list pods from cache") return } if len(objs) == 0 { - logger.V(6).Info("unrelated to any known pod", "claim", klog.KObj(claim)) - return + logger.V(6).Info("ResourceClaim change unrelated to any known pod", "claim", klog.KObj(claim)) } for _, obj := range objs { ec.enqueuePod(logger, obj, false) } + + if ec.features.WorkloadResourceClaims { + // Also check whether this causes work for any of the currently + // known PodGroups which use the ResourceClaim. + podGroups, err := ec.podGroupIndexer.ByIndex(podGroupResourceClaimIndex, fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)) + if err != nil { + logger.Error(err, "Failed to list PodGroups from cache") + return + } + if len(podGroups) == 0 { + logger.V(6).Info("ResourceClaim change unrelated to any known PodGroup", "claim", klog.KObj(claim)) + } + for _, podGroup := range podGroups { + ec.enqueuePodGroup(logger, podGroup, false) + } + } } func (ec *Controller) Run(ctx context.Context, workers int) { @@ -429,7 +759,7 @@ func (ec *Controller) Run(ctx context.Context, workers int) { wg.Wait() }() - if !cache.WaitForNamedCacheSyncWithContext(ctx, ec.podSynced, ec.claimsSynced, ec.templatesSynced) { + if !cache.WaitForNamedCacheSyncWithContext(ctx, ec.podSynced, ec.podGroupSynced, ec.claimsSynced, ec.templatesSynced) { return } @@ -481,6 +811,8 @@ func (ec *Controller) syncHandler(ctx context.Context, key string) error { switch prefix { case podKeyPrefix: return ec.syncPod(ctx, namespace, name) + case podGroupKeyPrefix: + return ec.syncPodGroup(ctx, namespace, name) case claimKeyPrefix: return ec.syncClaim(ctx, namespace, name) default: @@ -495,7 +827,7 @@ func (ec *Controller) syncPod(ctx context.Context, namespace, name string) error pod, err := ec.podLister.Pods(namespace).Get(name) if err != nil { if apierrors.IsNotFound(err) { - logger.V(5).Info("nothing to do for pod, it is gone") + logger.V(5).Info("Nothing to do for pod, it is gone") return nil } return err @@ -503,13 +835,22 @@ func (ec *Controller) syncPod(ctx context.Context, namespace, name string) error // Ignore pods which are already getting deleted. if pod.DeletionTimestamp != nil { - logger.V(5).Info("nothing to do for pod, it is marked for deletion") + logger.V(5).Info("Nothing to do for pod, it is marked for deletion") return nil } + podGroup, err := ec.getPodGroup(pod) + if err != nil { + return fmt.Errorf("get PodGroup for Pod %s/%s: %w", pod.Namespace, pod.Name, err) + } + if podGroup != nil { + logger = klog.LoggerWithValues(logger, "podgroup", klog.KObj(podGroup)) + ctx = klog.NewContext(ctx, logger) + } + var newPodClaims map[string]string for _, podClaim := range pod.Spec.ResourceClaims { - if err := ec.handleClaim(ctx, pod, podClaim, &newPodClaims); err != nil { + if err := ec.handleClaim(ctx, pod, podGroup, podClaim, &newPodClaims); err != nil { if ec.recorder != nil { ec.recorder.Event(pod, v1.EventTypeWarning, "FailedResourceClaimCreation", fmt.Sprintf("PodResourceClaim %s: %v", podClaim.Name, err)) } @@ -532,7 +873,7 @@ func (ec *Controller) syncPod(ctx context.Context, namespace, name string) error if pod.Spec.NodeName == "" { // Scheduler will handle reservations. - logger.V(5).Info("nothing to do for pod, scheduler will deal with it") + logger.V(5).Info("Nothing to do for pod, scheduler will deal with it") return nil } @@ -554,15 +895,19 @@ func (ec *Controller) syncPod(ctx context.Context, namespace, name string) error return fmt.Errorf("retrieve claim: %v", err) } if checkOwner { - if err := resourceclaim.IsForPod(pod, claim); err != nil { + if err := resourceclaim.IsForPod(pod, claim, ec.features.WorkloadResourceClaims); err != nil { return err } } if claim.Status.Allocation != nil && - !resourceclaim.IsReservedForPod(pod, claim) && + !resourceclaim.IsReservedForPod(pod, claim, ec.features.WorkloadResourceClaims) && resourceclaim.CanBeReserved(claim) { - logger.V(5).Info("reserve claim for pod", "resourceClaim", klog.KObj(claim)) - if err := ec.reserveForPod(ctx, pod, claim); err != nil { + bindTo, err := resourceclaim.BindTo(pod, podGroup, &podClaim) + if err != nil { + return err + } + logger.V(5).Info("Reserve claim", "resourceClaim", klog.KObj(claim), "reservedForResource", bindTo.Resource) + if err := ec.reserveFor(ctx, bindTo, claim); err != nil { return err } } @@ -572,10 +917,10 @@ func (ec *Controller) syncPod(ctx context.Context, namespace, name string) error } // handleClaim is invoked for each resource claim of a pod. -func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podClaim v1.PodResourceClaim, newPodClaims *map[string]string) error { +func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podGroup *schedulingapi.PodGroup, podClaim v1.PodResourceClaim, newPodClaims *map[string]string) error { logger := klog.LoggerWithValues(klog.FromContext(ctx), "podClaim", podClaim.Name) ctx = klog.NewContext(ctx, logger) - logger.V(5).Info("checking", "podClaim", podClaim.Name) + logger.V(5).Info("Checking", "podClaim", podClaim.Name) // resourceclaim.Name checks for the situation that the client doesn't // know some future addition to the API. Therefore it gets called here @@ -602,14 +947,14 @@ func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podClaim v1. if claim != nil { var err error if mustCheckOwner { - err = resourceclaim.IsForPod(pod, claim) + err = resourceclaim.IsForPod(pod, claim, ec.features.WorkloadResourceClaims) } if err == nil { // Already created, nothing more to do. - logger.V(5).Info("claim already created", "podClaim", podClaim.Name, "resourceClaim", claimName) + logger.V(5).Info("Claim already created", "podClaim", podClaim.Name, "resourceClaim", claimName) return nil } - logger.Error(err, "claim that was created for the pod is no longer owned by the pod, creating a new one", "podClaim", podClaim.Name, "resourceClaim", claimName) + logger.Error(err, "Claim that was created for the pod is no longer owned by the pod, creating a new one", "podClaim", podClaim.Name, "resourceClaim", claimName) } } @@ -619,18 +964,269 @@ func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podClaim v1. return nil } + bindTo, err := resourceclaim.BindTo(pod, podGroup, &podClaim) + if err != nil { + return err + } + isPodGroupClaim := bindTo.APIGroup == schedulingapi.GroupName && bindTo.Resource == "podgroups" + var claim *resourceapi.ResourceClaim + if isPodGroupClaim { + claim, err = ec.findPodGroupResourceClaim(podGroup, schedulingapi.PodGroupResourceClaim{ + Name: podClaim.Name, + }) + if err != nil { + return fmt.Errorf("finding ResourceClaim for claim %s in PodGroup %s/%s failed: %w", podClaim.Name, podGroup.Namespace, podGroup.Name, err) + } + } else { + // Before we create a new ResourceClaim, check if there is an orphaned one. + // This covers the case that the controller has created it, but then fails + // before it can update the pod status. + claim, err = ec.findPodResourceClaim(pod, podClaim) + if err != nil { + return fmt.Errorf("finding ResourceClaim for claim %s in pod %s/%s failed: %w", podClaim.Name, pod.Namespace, pod.Name, err) + } + + if claim == nil { + template, err := ec.templateLister.ResourceClaimTemplates(pod.Namespace).Get(*templateName) + if err != nil { + return fmt.Errorf("resource claim template %q: %w", *templateName, err) + } + + if !ec.features.AdminAccess && needsAdminAccess(template) { + return errors.New("admin access is requested, but the feature is disabled") + } + + if !ec.features.PrioritizedList && hasPrioritizedList(template) { + return errors.New("template includes a prioritized list of subrequests, but the feature is disabled") + } + + // Create the ResourceClaim with pod as owner, with a generated name that uses + // - as base. + isTrue := true + annotations := template.Spec.ObjectMeta.Annotations + if annotations == nil { + annotations = make(map[string]string) + } + annotations[resourceapi.PodResourceClaimAnnotation] = podClaim.Name + generateName := pod.Name + "-" + podClaim.Name + "-" + maxBaseLen := 57 // Leave space for hyphen and 5 random characters in a name with 63 characters. + if len(generateName) > maxBaseLen { + // We could leave truncation to the apiserver, but as + // it removes at the end, we would loose everything + // from the pod claim name when the pod name is long. + // We can do better and truncate both strings, + // proportional to their length. + generateName = pod.Name[0:len(pod.Name)*maxBaseLen/len(generateName)] + + "-" + + podClaim.Name[0:len(podClaim.Name)*maxBaseLen/len(generateName)] + } + claim = &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: generateName, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Pod", + Name: pod.Name, + UID: pod.UID, + Controller: &isTrue, + }, + }, + Annotations: annotations, + Labels: template.Spec.ObjectMeta.Labels, + }, + Spec: template.Spec.Spec, + } + metricLabel := getAdminAccessMetricLabel(claim) + claimName := claim.Name + claim, err = ec.kubeClient.ResourceV1().ResourceClaims(pod.Namespace).Create(ctx, claim, metav1.CreateOptions{}) + if err != nil { + resourceclaimmetrics.ResourceClaimCreate.WithLabelValues("failure", metricLabel).Inc() + return fmt.Errorf("create ResourceClaim %s: %w", claimName, err) + } + resourceclaimmetrics.ResourceClaimCreate.WithLabelValues("success", metricLabel).Inc() + logger.V(4).Info("Created ResourceClaim", "claim", klog.KObj(claim), "pod", klog.KObj(pod)) + ec.claimCache.Mutation(claim) + } + } + + // Remember the new ResourceClaim for a batch PodStatus update in our caller. + if claim != nil { + if *newPodClaims == nil { + *newPodClaims = make(map[string]string) + } + (*newPodClaims)[podClaim.Name] = claim.Name + } + + return nil +} + +func needsAdminAccess(claimTemplate *resourceapi.ResourceClaimTemplate) bool { + for _, request := range claimTemplate.Spec.Spec.Devices.Requests { + if request.Exactly != nil && ptr.Deref(request.Exactly.AdminAccess, false) { + return true + } + } + return false +} + +func hasPrioritizedList(claimTemplate *resourceapi.ResourceClaimTemplate) bool { + for _, request := range claimTemplate.Spec.Spec.Devices.Requests { + if len(request.FirstAvailable) > 0 { + return true + } + } + return false +} + +// findPodResourceClaim looks for an existing ResourceClaim with the right +// annotation (ties it to the pod claim) and the right ownership (ties it to +// the pod). +func (ec *Controller) findPodResourceClaim(pod *v1.Pod, podClaim v1.PodResourceClaim) (*resourceapi.ResourceClaim, error) { + // Only claims owned by the pod will get returned here. + claims, err := ec.claimCache.ByIndex(claimPodOwnerIndex, string(pod.UID)) + if err != nil { + return nil, err + } + for _, claimObj := range claims { + claim, ok := claimObj.(*resourceapi.ResourceClaim) + if !ok { + return nil, fmt.Errorf("unexpected object of type %T returned by claim cache", claimObj) + } + podClaimName, ok := claim.Annotations[resourceapi.PodResourceClaimAnnotation] + // No annotation? Then it cannot be an automatically generated claim + // and we need to ignore it. + if !ok { + continue + } + + // Not the claim for this particular pod claim? + if podClaimName != podClaim.Name { + continue + } + + // Pick the first one that matches. There shouldn't be more than one. If there is, + // then all others will be ignored until the pod gets deleted. Then they also get + // cleaned up. + return claim, nil + } + + return nil, nil +} + +func (ec *Controller) reserveFor(ctx context.Context, consumer resourceapi.ResourceClaimConsumerReference, claim *resourceapi.ResourceClaim) error { + claim = claim.DeepCopy() + claim.Status.ReservedFor = append(claim.Status.ReservedFor, consumer) + if _, err := ec.kubeClient.ResourceV1().ResourceClaims(claim.Namespace).UpdateStatus(ctx, claim, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("reserve claim %s: %w", klog.KObj(claim), err) + } + return nil +} + +func (ec *Controller) syncPodGroup(ctx context.Context, namespace, name string) error { + logger := klog.LoggerWithValues(klog.FromContext(ctx), "podgroup", klog.KRef(namespace, name)) + ctx = klog.NewContext(ctx, logger) + podGroup, err := ec.podGroupLister.PodGroups(namespace).Get(name) + if err != nil { + if apierrors.IsNotFound(err) { + logger.V(5).Info("Nothing to do for PodGroup, it is gone") + return nil + } + return err + } + + // Ignore PodGroups which are already getting deleted. + if podGroup.DeletionTimestamp != nil { + logger.V(5).Info("Nothing to do for PodGroup, it is marked for deletion") + return nil + } + + var newPodGroupClaims map[string]string + for _, podGroupClaim := range podGroup.Spec.ResourceClaims { + if err := ec.handlePodGroupClaim(ctx, podGroup, podGroupClaim, &newPodGroupClaims); err != nil { + if ec.recorder != nil { + ec.recorder.Event(podGroup, v1.EventTypeWarning, "FailedResourceClaimCreation", fmt.Sprintf("PodGroupResourceClaim %s: %v", podGroupClaim.Name, err)) + } + return fmt.Errorf("PodGroup %s/%s, PodGroupResourceClaim %s: %w", namespace, name, podGroupClaim.Name, err) + } + } + + if newPodGroupClaims != nil { + // Patch the PodGroup status with the new information about + // generated ResourceClaims. + statuses := make([]*schedulingapply.PodGroupResourceClaimStatusApplyConfiguration, 0, len(newPodGroupClaims)) + for podGroupClaimName, resourceClaimName := range newPodGroupClaims { + statuses = append(statuses, schedulingapply.PodGroupResourceClaimStatus().WithName(podGroupClaimName).WithResourceClaimName(resourceClaimName)) + } + podGroupApply := schedulingapply.PodGroup(name, namespace).WithStatus(schedulingapply.PodGroupStatus().WithResourceClaimStatuses(statuses...)) + if _, err := ec.kubeClient.SchedulingV1alpha2().PodGroups(namespace).ApplyStatus(ctx, podGroupApply, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}); err != nil { + return fmt.Errorf("update PodGroup %s/%s ResourceClaimStatuses: %w", namespace, name, err) + } + } + + return nil +} + +// handlePodGroupClaim is invoked for each resource claim of a PodGroup. +func (ec *Controller) handlePodGroupClaim(ctx context.Context, podGroup *schedulingapi.PodGroup, podGroupClaim schedulingapi.PodGroupResourceClaim, newPodGroupClaims *map[string]string) error { + logger := klog.LoggerWithValues(klog.FromContext(ctx), "podGroupClaim", podGroupClaim.Name) + ctx = klog.NewContext(ctx, logger) + logger.V(5).Info("Checking", "podGroupClaim", podGroupClaim.Name) + + // resourceclaim.Name checks for the situation that the client doesn't + // know some future addition to the API. Therefore it gets called here + // even if there is no template to work on, because if some new field + // gets added, the expectation might be that the controller does + // something for it. + claimName, mustCheckOwner, err := resourceclaim.NameFromPodGroup(podGroup, &podGroupClaim) + switch { + case errors.Is(err, resourceclaim.ErrClaimNotFound): + // Continue below. + case err != nil: + return fmt.Errorf("checking for claim before creating it: %w", err) + case claimName == nil: + // Nothing to do, no claim needed. + return nil + case *claimName != "": + claimName := *claimName + // The ResourceClaim should exist because it is recorded in the podgroup.status.resourceClaimStatuses, + // but perhaps it was deleted accidentally. In that case we re-create it. + claim, err := ec.claimLister.ResourceClaims(podGroup.Namespace).Get(claimName) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if claim != nil { + var err error + if mustCheckOwner { + err = resourceclaim.IsForPodGroup(podGroup, claim) + } + if err == nil { + // Already created, nothing more to do. + logger.V(5).Info("Claim already created", "podGroupClaim", podGroupClaim.Name, "resourceClaim", claimName) + return nil + } + logger.Error(err, "Claim that was created for the PodGroup is no longer owned by the PodGroup, creating a new one", "podGroupClaim", podGroupClaim.Name, "resourceClaim", claimName) + } + } + + templateName := podGroupClaim.ResourceClaimTemplateName + if templateName == nil { + // Nothing to do. + return nil + } + // Before we create a new ResourceClaim, check if there is an orphaned one. // This covers the case that the controller has created it, but then fails // before it can update the pod status. - claim, err := ec.findPodResourceClaim(pod, podClaim) + claim, err := ec.findPodGroupResourceClaim(podGroup, podGroupClaim) if err != nil { - return fmt.Errorf("finding ResourceClaim for claim %s in pod %s/%s failed: %v", podClaim.Name, pod.Namespace, pod.Name, err) + return fmt.Errorf("finding ResourceClaim for claim %s in PodGroup %s/%s failed: %w", podGroupClaim.Name, podGroup.Namespace, podGroup.Name, err) } if claim == nil { - template, err := ec.templateLister.ResourceClaimTemplates(pod.Namespace).Get(*templateName) + template, err := ec.templateLister.ResourceClaimTemplates(podGroup.Namespace).Get(*templateName) if err != nil { - return fmt.Errorf("resource claim template %q: %v", *templateName, err) + return fmt.Errorf("resource claim template %q: %w", *templateName, err) } if !ec.features.AdminAccess && needsAdminAccess(template) { @@ -641,35 +1237,35 @@ func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podClaim v1. return errors.New("template includes a prioritized list of subrequests, but the feature is disabled") } - // Create the ResourceClaim with pod as owner, with a generated name that uses - // - as base. + // Create the ResourceClaim with PodGroup as owner, with a generated name that uses + // - as base. isTrue := true annotations := template.Spec.ObjectMeta.Annotations if annotations == nil { annotations = make(map[string]string) } - annotations[podResourceClaimAnnotation] = podClaim.Name - generateName := pod.Name + "-" + podClaim.Name + "-" + annotations[resourceapi.PodResourceClaimAnnotation] = podGroupClaim.Name + generateName := podGroup.Name + "-" + podGroupClaim.Name + "-" maxBaseLen := 57 // Leave space for hyphen and 5 random characters in a name with 63 characters. if len(generateName) > maxBaseLen { // We could leave truncation to the apiserver, but as // it removes at the end, we would loose everything - // from the pod claim name when the pod name is long. + // from the pod group claim name when the pod group name is long. // We can do better and truncate both strings, // proportional to their length. - generateName = pod.Name[0:len(pod.Name)*maxBaseLen/len(generateName)] + + generateName = podGroup.Name[0:len(podGroup.Name)*maxBaseLen/len(generateName)] + "-" + - podClaim.Name[0:len(podClaim.Name)*maxBaseLen/len(generateName)] + podGroupClaim.Name[0:len(podGroupClaim.Name)*maxBaseLen/len(generateName)] } claim = &resourceapi.ResourceClaim{ ObjectMeta: metav1.ObjectMeta{ GenerateName: generateName, OwnerReferences: []metav1.OwnerReference{ { - APIVersion: "v1", - Kind: "Pod", - Name: pod.Name, - UID: pod.UID, + APIVersion: schedulingapi.SchemeGroupVersion.String(), + Kind: "PodGroup", + Name: podGroup.Name, + UID: podGroup.UID, Controller: &isTrue, }, }, @@ -680,49 +1276,31 @@ func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podClaim v1. } metricLabel := getAdminAccessMetricLabel(claim) claimName := claim.Name - claim, err = ec.kubeClient.ResourceV1().ResourceClaims(pod.Namespace).Create(ctx, claim, metav1.CreateOptions{}) + claim, err = ec.kubeClient.ResourceV1().ResourceClaims(podGroup.Namespace).Create(ctx, claim, metav1.CreateOptions{}) if err != nil { resourceclaimmetrics.ResourceClaimCreate.WithLabelValues("failure", metricLabel).Inc() - return fmt.Errorf("create ResourceClaim %s: %v", claimName, err) + return fmt.Errorf("create ResourceClaim %s: %w", claimName, err) } resourceclaimmetrics.ResourceClaimCreate.WithLabelValues("success", metricLabel).Inc() - logger.V(4).Info("Created ResourceClaim", "claim", klog.KObj(claim), "pod", klog.KObj(pod)) + logger.V(4).Info("Created ResourceClaim", "claim", klog.KObj(claim), "podgroup", klog.KObj(podGroup)) ec.claimCache.Mutation(claim) } // Remember the new ResourceClaim for a batch PodStatus update in our caller. - if *newPodClaims == nil { - *newPodClaims = make(map[string]string) + if *newPodGroupClaims == nil { + *newPodGroupClaims = make(map[string]string) } - (*newPodClaims)[podClaim.Name] = claim.Name + (*newPodGroupClaims)[podGroupClaim.Name] = claim.Name return nil } -func needsAdminAccess(claimTemplate *resourceapi.ResourceClaimTemplate) bool { - for _, request := range claimTemplate.Spec.Spec.Devices.Requests { - if request.Exactly != nil && ptr.Deref(request.Exactly.AdminAccess, false) { - return true - } - } - return false -} - -func hasPrioritizedList(claimTemplate *resourceapi.ResourceClaimTemplate) bool { - for _, request := range claimTemplate.Spec.Spec.Devices.Requests { - if len(request.FirstAvailable) > 0 { - return true - } - } - return false -} - -// findPodResourceClaim looks for an existing ResourceClaim with the right -// annotation (ties it to the pod claim) and the right ownership (ties it to -// the pod). -func (ec *Controller) findPodResourceClaim(pod *v1.Pod, podClaim v1.PodResourceClaim) (*resourceapi.ResourceClaim, error) { - // Only claims owned by the pod will get returned here. - claims, err := ec.claimCache.ByIndex(claimPodOwnerIndex, string(pod.UID)) +// findPodGroupResourceClaim looks for an existing ResourceClaim with the right +// annotation (ties it to the pod group claim) and the right ownership (ties it to +// the pod group). +func (ec *Controller) findPodGroupResourceClaim(podGroup *schedulingapi.PodGroup, podGroupClaim schedulingapi.PodGroupResourceClaim) (*resourceapi.ResourceClaim, error) { + // Only claims owned by the PodGroup will get returned here. + claims, err := ec.claimCache.ByIndex(claimPodGroupOwnerIndex, podGroup.Namespace+"/"+string(podGroup.UID)) if err != nil { return nil, err } @@ -731,54 +1309,42 @@ func (ec *Controller) findPodResourceClaim(pod *v1.Pod, podClaim v1.PodResourceC if !ok { return nil, fmt.Errorf("unexpected object of type %T returned by claim cache", claimObj) } - podClaimName, ok := claim.Annotations[podResourceClaimAnnotation] + podGroupClaimName, ok := claim.Annotations[resourceapi.PodResourceClaimAnnotation] // No annotation? Then it cannot be an automatically generated claim // and we need to ignore it. if !ok { continue } - // Not the claim for this particular pod claim? - if podClaimName != podClaim.Name { + // Not the claim for this particular PodGroup claim? + if podGroupClaimName != podGroupClaim.Name { continue } // Pick the first one that matches. There shouldn't be more than one. If there is, - // then all others will be ignored until the pod gets deleted. Then they also get + // then all others will be ignored until the PodGroup gets deleted. Then they also get // cleaned up. return claim, nil } return nil, nil } -func (ec *Controller) reserveForPod(ctx context.Context, pod *v1.Pod, claim *resourceapi.ResourceClaim) error { - claim = claim.DeepCopy() - claim.Status.ReservedFor = append(claim.Status.ReservedFor, - resourceapi.ResourceClaimConsumerReference{ - Resource: "pods", - Name: pod.Name, - UID: pod.UID, - }) - if _, err := ec.kubeClient.ResourceV1().ResourceClaims(claim.Namespace).UpdateStatus(ctx, claim, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("reserve claim %s for pod: %w", klog.KObj(claim), err) - } - return nil -} - func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) error { logger := klog.LoggerWithValues(klog.FromContext(ctx), "claim", klog.KRef(namespace, name)) ctx = klog.NewContext(ctx, logger) claim, err := ec.claimLister.ResourceClaims(namespace).Get(name) if err != nil { if apierrors.IsNotFound(err) { - logger.V(5).Info("nothing to do for claim, it is gone") + logger.V(5).Info("Nothing to do for claim, it is gone") return nil } return err } - // Check if the ReservedFor entries are all still valid. - valid := make([]resourceapi.ResourceClaimConsumerReference, 0, len(claim.Status.ReservedFor)) + // Check if the ReservedFor entries are all still valid. Essentially we are + // validating and potentially removing pod references, but just leaving any + // non-pod references in the list. + remaining := make([]resourceapi.ResourceClaimConsumerReference, 0, len(claim.Status.ReservedFor)) for _, reservedFor := range claim.Status.ReservedFor { if reservedFor.APIGroup == "" && reservedFor.Resource == "pods" { @@ -815,35 +1381,87 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err return err } if pod == nil || pod.UID != reservedFor.UID { - logger.V(6).Info("remove reservation because pod is gone or got replaced", "pod", klog.KObj(pod), "claim", klog.KRef(namespace, name)) + logger.V(6).Info("Remove reservation because pod is gone or got replaced", "pod", klog.KObj(pod), "claim", klog.KRef(namespace, name)) keepEntry = false } case pod.UID != reservedFor.UID: - logger.V(6).Info("remove reservation because pod got replaced with new instance", "pod", klog.KObj(pod), "claim", klog.KRef(namespace, name)) + logger.V(6).Info("Remove reservation because pod got replaced with new instance", "pod", klog.KObj(pod), "claim", klog.KRef(namespace, name)) keepEntry = false case isPodDone(pod): - logger.V(6).Info("remove reservation because pod will not run anymore", "pod", klog.KObj(pod), "claim", klog.KRef(namespace, name)) + logger.V(6).Info("Remove reservation because pod will not run anymore", "pod", klog.KObj(pod), "claim", klog.KRef(namespace, name)) + keepEntry = false + } + } + + if keepEntry { + remaining = append(remaining, reservedFor) + } + continue + } + if ec.features.WorkloadResourceClaims && + reservedFor.APIGroup == schedulingapi.GroupName && + reservedFor.Resource == "podgroups" { + // A podgroup falls into one of three categories: + // - we have it in our cache -> don't remove it until we are told that it got removed + // - we don't have it in our cache anymore, but we have seen it before -> it was deleted, remove it + // - not in our cache, not seen -> double-check with API server before removal + + keepEntry := true + + // Tracking deleted PodGroups in the LRU cache is an + // optimization. Without this cache, the code would + // have to do the API call below for every deleted PodGroup + // to ensure that the PodGroup really doesn't exist. With + // the cache, most of the time the PodGroup will be recorded + // as deleted and the API call can be avoided. + if ec.deletedObjects.Has(reservedFor.UID) { + // We know that the PodGroup was deleted. This is + // easy to check and thus is done first. + keepEntry = false + } else { + podGroup, err := ec.podGroupLister.PodGroups(claim.Namespace).Get(reservedFor.Name) + switch { + case err != nil && !apierrors.IsNotFound(err): + return err + case err != nil: + // We might not have it in our informer cache + // yet. Removing the PodGroup while the scheduler is + // scheduling its Pods would be bad. We have to be + // absolutely sure and thus have to check with + // the API server. + podGroup, err := ec.kubeClient.SchedulingV1alpha2().PodGroups(claim.Namespace).Get(ctx, reservedFor.Name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if podGroup == nil || podGroup.UID != reservedFor.UID { + logger.V(6).Info("Remove reservation because PodGroup is gone or got replaced", "podgroup", klog.KObj(podGroup), "claim", klog.KRef(namespace, name)) + keepEntry = false + } + case podGroup.UID != reservedFor.UID: + logger.V(6).Info("Remove reservation because PodGroup got replaced with new instance", "podgroup", klog.KObj(podGroup), "claim", klog.KRef(namespace, name)) keepEntry = false } } if keepEntry { - valid = append(valid, reservedFor) + remaining = append(remaining, reservedFor) } continue } - // TODO: support generic object lookup - return fmt.Errorf("unsupported ReservedFor entry: %v", reservedFor) + // We don't know how to check this entry, so we just keep it to avoid + // accidentally removing a reservation for an unknown consumer type that we + // don't support yet. + remaining = append(remaining, reservedFor) } builtinControllerFinalizer := slices.Index(claim.Finalizers, resourceapi.Finalizer) - logger.V(5).Info("claim reserved for counts", "currentCount", len(claim.Status.ReservedFor), "claim", klog.KRef(namespace, name), "updatedCount", len(valid), "builtinController", builtinControllerFinalizer >= 0) - if len(valid) < len(claim.Status.ReservedFor) { + logger.V(5).Info("Claim reserved for counts", "currentCount", len(claim.Status.ReservedFor), "claim", klog.KRef(namespace, name), "updatedCount", len(remaining), "builtinController", builtinControllerFinalizer >= 0) + if len(remaining) < len(claim.Status.ReservedFor) { // This is not using a patch because we want the update to fail if anything // changed in the meantime. claim := claim.DeepCopy() - claim.Status.ReservedFor = valid + claim.Status.ReservedFor = remaining // DRA always performs delayed allocations. Relatedly, it also // deallocates a claim as soon as the last consumer stops using @@ -861,7 +1479,7 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err // those, the resource claim controller will trigger deletion when the // pod is done. However, it doesn't hurt to also trigger deallocation // for such claims and not checking for them keeps this code simpler. - if len(valid) == 0 { + if len(remaining) == 0 { // This is a sanity check. There shouldn't be any claims without this // finalizer because there's no longer any other way of allocating claims. // Classic DRA was the alternative earlier. @@ -876,6 +1494,7 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err if err != nil { return err } + logger.V(5).Info("Removed consumers", "claim", klog.KRef(namespace, name), "currentCount", len(claim.Status.ReservedFor), "allocated", claim.Status.Allocation != nil) // Now also remove the finalizer if it is not needed anymore. // Note that the index may have changed as a result of the UpdateStatus call. @@ -885,8 +1504,9 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err if _, err := ec.kubeClient.ResourceV1().ResourceClaims(claim.Namespace).Update(ctx, claim, metav1.UpdateOptions{}); err != nil { return err } + logger.V(5).Info("Removed finalizer after removing consumers", "claim", klog.KRef(namespace, name)) } - } else if builtinControllerFinalizer >= 0 && claim.DeletionTimestamp != nil && len(valid) == 0 { + } else if builtinControllerFinalizer >= 0 && claim.DeletionTimestamp != nil && len(remaining) == 0 { claim := claim.DeepCopy() if claim.Status.Allocation != nil { // This can happen when a claim with immediate allocation @@ -898,6 +1518,7 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err if err != nil { return err } + logger.V(5).Info("Removed allocation because not needed", "claim", klog.KRef(namespace, name)) } // Whether it was allocated or not, remove the finalizer to unblock removal. claim.Finalizers = slices.Delete(claim.Finalizers, builtinControllerFinalizer, builtinControllerFinalizer+1) @@ -905,9 +1526,10 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err if err != nil { return err } + logger.V(5).Info("Removed finalizer because not needed", "claim", klog.KRef(namespace, name)) } - if len(valid) == 0 { + if len(remaining) == 0 { // Claim is not reserved. If it was generated for a pod and // that pod is not going to run, the claim can be // deleted. Normally the garbage collector does that, but the @@ -921,29 +1543,38 @@ func (ec *Controller) syncClaim(ctx context.Context, namespace, name string) err if pod.UID != podUID || isPodDone(pod) { // We are certain that the owning pod is not going to need // the claim and therefore remove the claim. - logger.V(5).Info("deleting unused generated claim", "claim", klog.KObj(claim), "pod", klog.KObj(pod)) err := ec.kubeClient.ResourceV1().ResourceClaims(claim.Namespace).Delete(ctx, claim.Name, metav1.DeleteOptions{}) if err != nil { - return fmt.Errorf("delete claim %s: %w", klog.KObj(claim), err) + return fmt.Errorf("delete unused generated claim %s: %w", klog.KObj(claim), err) } + logger.V(5).Info("Deleted unused generated claim", "claim", klog.KObj(claim), "pod", klog.KObj(pod)) } else { - logger.V(6).Info("wrong pod content, not deleting claim", "claim", klog.KObj(claim), "podUID", podUID, "podContent", pod) + logger.V(6).Info("Wrong pod content, not deleting claim", "claim", klog.KObj(claim), "podUID", podUID, "podContent", pod) } case apierrors.IsNotFound(err): // We might not know the pod *yet*. Instead of doing an expensive API call, // let the garbage collector handle the case that the pod is truly gone. - logger.V(5).Info("pod for claim not found", "claim", klog.KObj(claim), "pod", klog.KRef(claim.Namespace, podName)) + logger.V(5).Info("Pod for claim not found", "claim", klog.KObj(claim), "pod", klog.KRef(claim.Namespace, podName)) default: return fmt.Errorf("lookup pod: %v", err) } } else { - logger.V(5).Info("claim not generated for a pod", "claim", klog.KObj(claim)) + logger.V(5).Info("Claim not generated for a pod", "claim", klog.KObj(claim)) } } return nil } +func (ec *Controller) getPodGroup(pod *v1.Pod) (*schedulingapi.PodGroup, error) { + if !ec.features.WorkloadResourceClaims || + pod.Spec.SchedulingGroup == nil || + pod.Spec.SchedulingGroup.PodGroupName == nil { + return nil, nil + } + return ec.podGroupLister.PodGroups(pod.Namespace).Get(*pod.Spec.SchedulingGroup.PodGroupName) +} + func owningPod(claim *resourceapi.ResourceClaim) (string, types.UID) { for _, owner := range claim.OwnerReferences { if ptr.Deref(owner.Controller, false) && @@ -955,14 +1586,32 @@ func owningPod(claim *resourceapi.ResourceClaim) (string, types.UID) { return "", "" } +func podResourceClaimTemplateIndexFunc(obj interface{}) ([]string, error) { + pod, ok := obj.(*v1.Pod) + if !ok { + return nil, nil + } + + keySet := sets.New[string]() + + for _, podClaim := range pod.Spec.ResourceClaims { + if podClaim.ResourceClaimTemplateName != nil { + resourceTemplate := *podClaim.ResourceClaimTemplateName + keySet.Insert(fmt.Sprintf("%s/%s", pod.Namespace, resourceTemplate)) + } + } + + return sets.List(keySet), nil +} + // podResourceClaimIndexFunc is an index function that returns ResourceClaim keys (= // namespace/name) for ResourceClaim or ResourceClaimTemplates in a given pod. func podResourceClaimIndexFunc(obj interface{}) ([]string, error) { pod, ok := obj.(*v1.Pod) if !ok { - return []string{}, nil + return nil, nil } - keys := []string{} + keySet := sets.New[string]() for _, podClaim := range pod.Spec.ResourceClaims { claimName, _, err := resourceclaim.Name(pod, &podClaim) if err != nil || claimName == nil { @@ -971,11 +1620,67 @@ func podResourceClaimIndexFunc(obj interface{}) ([]string, error) { // we simply don't index. continue } - keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, *claimName)) + keySet.Insert(fmt.Sprintf("%s/%s", pod.Namespace, *claimName)) + } + return sets.List(keySet), nil +} + +// podGroupMemberIndexFunc is an index function that returns PodGroup keys (= +// namespace/name) for Pods in a given group. +func podGroupMemberIndexFunc(obj interface{}) ([]string, error) { + pod, ok := obj.(*v1.Pod) + if !ok { + return nil, nil + } + var keys []string + if pod.Spec.SchedulingGroup != nil && pod.Spec.SchedulingGroup.PodGroupName != nil { + keys = []string{pod.Namespace + "/" + *pod.Spec.SchedulingGroup.PodGroupName} } return keys, nil } +// podGroupResourceClaimTemplateIndexFunc is an index function that returns +// ResourceClaimTemplate keys (= namespace/name) for a given PodGroup. +func podGroupResourceClaimTemplateIndexFunc(obj interface{}) ([]string, error) { + podGroup, ok := obj.(*schedulingapi.PodGroup) + if !ok { + return nil, nil + } + + keySet := sets.New[string]() + + for _, podGroupClaim := range podGroup.Spec.ResourceClaims { + if podGroupClaim.ResourceClaimTemplateName != nil { + resourceTemplate := *podGroupClaim.ResourceClaimTemplateName + keySet.Insert(fmt.Sprintf("%s/%s", podGroup.Namespace, resourceTemplate)) + } + } + + return sets.List(keySet), nil +} + +// podGroupResourceClaimIndexFunc is an index function that returns +// ResourceClaim keys (= namespace/name) for ResourceClaim or +// ResourceClaimTemplates in a given PodGroup. +func podGroupResourceClaimIndexFunc(obj interface{}) ([]string, error) { + podGroup, ok := obj.(*schedulingapi.PodGroup) + if !ok { + return nil, nil + } + keySet := sets.New[string]() + for _, podGroupClaim := range podGroup.Spec.ResourceClaims { + claimName, _, err := resourceclaim.NameFromPodGroup(podGroup, &podGroupClaim) + if err != nil || claimName == nil { + // Index functions are not supposed to fail, the caller will panic. + // For both error reasons (claim not created yet, unknown API) + // we simply don't index. + continue + } + keySet.Insert(fmt.Sprintf("%s/%s", podGroup.Namespace, *claimName)) + } + return sets.List(keySet), nil +} + // isPodDone returns true if it is certain that none of the containers are running and never will run. func isPodDone(pod *v1.Pod) bool { return podutil.IsPodPhaseTerminal(pod.Status.Phase) || @@ -1001,6 +1706,21 @@ func claimPodOwnerIndexFunc(obj interface{}) ([]string, error) { } return keys, nil } + +// claimPodGroupOwnerIndexFunc is an index function that returns the PodGroup UIDs of +// all PodGroups which own the resource claim. Should only be one, though. +func claimPodGroupOwnerIndexFunc(obj interface{}) ([]string, error) { + claim, ok := obj.(*resourceapi.ResourceClaim) + if !ok { + return nil, nil + } + controller := metav1.GetControllerOfNoCopy(claim) + if controller == nil || controller.Kind != "PodGroup" || !strings.HasPrefix(controller.APIVersion, schedulingAPIGroupPrefix) { + return nil, nil + } + return []string{claim.Namespace + "/" + string(controller.UID)}, nil +} + func getAdminAccessMetricLabel(claim *resourceapi.ResourceClaim) string { if claim == nil { return "false" @@ -1052,7 +1772,7 @@ func (collector *customCollector) CollectWithStability(ch chan<- metrics.Metric) source := "" if val, ok := rc.Annotations[resourceapi.ExtendedResourceClaimAnnotation]; ok && val == "true" { source = "extended_resource" - } else if val, ok := rc.Annotations[podResourceClaimAnnotation]; ok && val != "" { + } else if val, ok := rc.Annotations[resourceapi.PodResourceClaimAnnotation]; ok && val != "" { source = "resource_claim_template" } rcMetrics[resourceclaimmetrics.NumResourceClaimLabels{Allocated: allocated, AdminAccess: adminAccess, Source: source}]++ diff --git a/pkg/controller/resourceclaim/controller_test.go b/pkg/controller/resourceclaim/controller_test.go index 33e207effd7c8..3badda6a751a0 100644 --- a/pkg/controller/resourceclaim/controller_test.go +++ b/pkg/controller/resourceclaim/controller_test.go @@ -19,6 +19,7 @@ package resourceclaim import ( "errors" "fmt" + "slices" "sort" "sync" "testing" @@ -29,9 +30,11 @@ import ( v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/diff" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -52,39 +55,59 @@ import ( var ( testPodName = "test-pod" + testPodGroupName = "test-podgroup" testNamespace = "my-namespace" testPodUID = types.UID("uidpod1") + testPodGroupUID = types.UID("uidpodgroup1") otherNamespace = "not-my-namespace" podResourceClaimName = "acme-resource" templateName = "my-template" className = "my-resource-class" nodeName = "worker" - testPod = makePod(testPodName, testNamespace, testPodUID) - testPodWithResource = makePod(testPodName, testNamespace, testPodUID, *makePodResourceClaim(podResourceClaimName, templateName)) + testPod = makePod(testPodName, testNamespace, testPodUID) + testPodWithResource = makePod(testPodName, testNamespace, testPodUID, *makePodResourceClaim(podResourceClaimName, templateName)) + testPodWithPodGroupResource = podInPodGroup(testPodWithResource, testPodName, testPodGroupName) otherTestPod = makePod(testPodName+"-II", testNamespace, testPodUID+"-II") + testPodGroupWithResource = makePodGroup(testPodGroupName, testNamespace, testPodGroupUID, *makePodGroupResourceClaim(podResourceClaimName, templateName)) + testPodGroupWithResourceInStatus = func() *schedulingapi.PodGroup { + podGroup := testPodGroupWithResource.DeepCopy() + podGroup.Status.ResourceClaimStatuses = []schedulingapi.PodGroupResourceClaimStatus{ + {Name: podResourceClaimName, ResourceClaimName: &testPodGroupClaim.Name}, + } + return podGroup + }() + testClaim = makeClaim(testPodName+"-"+podResourceClaimName, testNamespace, className, makeOwnerReference(testPodWithResource, true)) + testPodGroupClaim = makeClaim(testPodName+"-"+podResourceClaimName, testNamespace, className, makeOwnerReference(testPodGroupWithResource, true)) testClaimAllocated = allocateClaim(testClaim) testClaimReserved = reserveClaim(testClaimAllocated, testPodWithResource) testClaimReservedTwice = reserveClaim(testClaimReserved, otherTestPod) testClaimKey = claimKeyPrefix + testClaim.Namespace + "/" + testClaim.Name + testPodKey = podKeyPrefix + testNamespace + "/" + testPodName + testPodGroupKey = podGroupKeyPrefix + testNamespace + "/" + testPodGroupName + + testClaimReservedForPodGroup = reserveClaim(testClaimAllocated, testPodGroupWithResource) - templatedTestClaim = makeTemplatedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true), nil) - templatedTestClaimAllocated = allocateClaim(templatedTestClaim) - templatedTestClaimReserved = reserveClaim(templatedTestClaimAllocated, testPodWithResource) + templatedTestClaim = makeTemplatedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true), nil) + templatedTestClaimAllocated = allocateClaim(templatedTestClaim) + templatedTestClaimReserved = reserveClaim(templatedTestClaimAllocated, testPodWithResource) + templatedTestClaimReservedForPodGroup = reserveClaim(templatedTestClaimAllocated, testPodGroupWithResource) + templatedTestPodGroupClaim = makeTemplatedClaim(podResourceClaimName, testPodGroupName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodGroupWithResource, true), nil) - templatedTestClaimWithAdmin = makeTemplatedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true), ptr.To(true)) + templatedTestClaimWithAdmin = makeTemplatedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true), new(true)) templatedTestClaimWithAdminAllocated = allocateClaim(templatedTestClaimWithAdmin) extendedTestClaim = makeExtendedResourceClaim(testPodName, testNamespace, 1, makeOwnerReference(testPodWithResource, true)) extendedTestClaimAllocated = allocateClaim(extendedTestClaim) - conflictingClaim = makeClaim(testPodName+"-"+podResourceClaimName, testNamespace, className, nil) - otherNamespaceClaim = makeClaim(testPodName+"-"+podResourceClaimName, otherNamespace, className, nil) - template = makeTemplate(templateName, testNamespace, className, nil) - templateWithAdminAccess = makeTemplate(templateName, testNamespace, className, ptr.To(true)) + conflictingClaim = makeClaim(testPodName+"-"+podResourceClaimName, testNamespace, className, nil) + conflictingPodGroupClaim = makeClaim(testPodGroupName+"-"+podResourceClaimName, testNamespace, className, nil) + otherNamespaceClaim = makeClaim(testPodName+"-"+podResourceClaimName, otherNamespace, className, nil) + template = makeTemplate(templateName, testNamespace, className, nil) + templateWithAdminAccess = makeTemplate(templateName, testNamespace, className, new(true)) testPodWithNodeName = func() *v1.Pod { pod := testPodWithResource.DeepCopy() @@ -95,24 +118,28 @@ var ( }) return pod }() - adminAccessFeatureOffError = "admin access is requested, but the feature is disabled" + testPodWithPodGroupAndNodeName = podInPodGroup(testPodWithNodeName, testPodName, testPodGroupName) + adminAccessFeatureOffError = "admin access is requested, but the feature is disabled" ) func TestSyncHandler(t *testing.T) { tests := []struct { - name string - key string - adminAccessEnabled bool - prioritizedListEnabled bool - claims []*resourceapi.ResourceClaim - claimsInCache []*resourceapi.ResourceClaim - pods []*v1.Pod - podsLater []*v1.Pod - templates []*resourceapi.ResourceClaimTemplate - expectedClaims []resourceapi.ResourceClaim - expectedStatuses map[string][]v1.PodResourceClaimStatus - expectedError string - expectedMetrics expectedMetrics + name string + key string + adminAccessEnabled bool + prioritizedListEnabled bool + workloadResourceClaimsEnabled bool + claims []*resourceapi.ResourceClaim + claimsInCache []*resourceapi.ResourceClaim + pods []*v1.Pod + podsLater []*v1.Pod + podGroups []*schedulingapi.PodGroup + templates []*resourceapi.ResourceClaimTemplate + expectedClaims []resourceapi.ResourceClaim + expectedStatuses map[string][]v1.PodResourceClaimStatus + expectedPodGroupStatuses map[string][]schedulingapi.PodGroupResourceClaimStatus + expectedError string + expectedMetrics expectedMetrics }{ { name: "create", @@ -148,6 +175,72 @@ func TestSyncHandler(t *testing.T) { adminAccessEnabled: true, expectedMetrics: expectedMetrics{0, 1, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "create for PodGroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podGroupKey(testPodGroupWithResource), + expectedClaims: []resourceapi.ResourceClaim{*templatedTestPodGroupClaim}, + expectedPodGroupStatuses: map[string][]schedulingapi.PodGroupResourceClaimStatus{ + testPodGroupWithResource.Name: { + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{1, 0, 0, 0}, + }, + { + workloadResourceClaimsEnabled: true, + name: "skip create for Pod with PodGroup before template exists", + pods: []*v1.Pod{testPodWithPodGroupResource}, + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + templates: []*resourceapi.ResourceClaimTemplate{}, + key: podKey(testPodWithPodGroupResource), + expectedClaims: nil, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, + { + workloadResourceClaimsEnabled: true, + name: "skip create for Pod with PodGroup after template exists", + pods: []*v1.Pod{testPodWithPodGroupResource}, + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podKey(testPodWithPodGroupResource), + expectedClaims: nil, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, + { + workloadResourceClaimsEnabled: true, + name: "update Pod status with PodGroup claim", + pods: []*v1.Pod{testPodWithPodGroupResource}, + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + claims: []*resourceapi.ResourceClaim{templatedTestPodGroupClaim}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podKey(testPodWithPodGroupResource), + expectedClaims: []resourceapi.ResourceClaim{*templatedTestPodGroupClaim}, + expectedStatuses: map[string][]v1.PodResourceClaimStatus{ + testPodWithPodGroupResource.Name: { + {Name: testPodWithPodGroupResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, + { + workloadResourceClaimsEnabled: false, + name: "create ResourceClaim for Pod with PodGroup claim when feature is disabled", + pods: []*v1.Pod{testPodWithPodGroupResource}, + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + claims: []*resourceapi.ResourceClaim{templatedTestPodGroupClaim}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podKey(testPodWithPodGroupResource), + expectedClaims: []resourceapi.ResourceClaim{*templatedTestPodGroupClaim, *templatedTestClaim}, + expectedStatuses: map[string][]v1.PodResourceClaimStatus{ + testPodWithPodGroupResource.Name: { + {Name: testPodWithPodGroupResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{1, 0, 0, 0}, + }, { name: "nop", pods: []*v1.Pod{func() *v1.Pod { @@ -168,6 +261,27 @@ func TestSyncHandler(t *testing.T) { }, expectedMetrics: expectedMetrics{0, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "nop for PodGroup", + podGroups: []*schedulingapi.PodGroup{func() *schedulingapi.PodGroup { + podGroup := testPodGroupWithResource.DeepCopy() + podGroup.Status.ResourceClaimStatuses = []schedulingapi.PodGroupResourceClaimStatus{ + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + } + return podGroup + }()}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podGroupKey(testPodGroupWithResource), + claims: []*resourceapi.ResourceClaim{templatedTestPodGroupClaim}, + expectedClaims: []resourceapi.ResourceClaim{*templatedTestPodGroupClaim}, + expectedPodGroupStatuses: map[string][]schedulingapi.PodGroupResourceClaimStatus{ + testPodGroupWithResource.Name: { + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, { name: "recreate", pods: []*v1.Pod{func() *v1.Pod { @@ -187,6 +301,26 @@ func TestSyncHandler(t *testing.T) { }, expectedMetrics: expectedMetrics{1, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "recreate for PodGroup", + podGroups: []*schedulingapi.PodGroup{func() *schedulingapi.PodGroup { + pod := testPodGroupWithResource.DeepCopy() + pod.Status.ResourceClaimStatuses = []schedulingapi.PodGroupResourceClaimStatus{ + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + } + return pod + }()}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podGroupKey(testPodGroupWithResource), + expectedClaims: []resourceapi.ResourceClaim{*templatedTestPodGroupClaim}, + expectedPodGroupStatuses: map[string][]schedulingapi.PodGroupResourceClaimStatus{ + testPodGroupWithResource.Name: { + {Name: testPodWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{1, 0, 0, 0}, + }, { name: "missing-template", pods: []*v1.Pod{testPodWithResource}, @@ -194,6 +328,14 @@ func TestSyncHandler(t *testing.T) { key: podKey(testPodWithResource), expectedError: "resource claim template \"my-template\": resourceclaimtemplate.resource.k8s.io \"my-template\" not found", }, + { + workloadResourceClaimsEnabled: true, + name: "missing-template-podgroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + templates: nil, + key: podGroupKey(testPodGroupWithResource), + expectedError: "resource claim template \"my-template\": resourceclaimtemplate.resource.k8s.io \"my-template\" not found", + }, { name: "find-existing-claim-by-label", pods: []*v1.Pod{testPodWithResource}, @@ -207,6 +349,20 @@ func TestSyncHandler(t *testing.T) { }, expectedMetrics: expectedMetrics{0, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "find-existing-claim-by-label-podgroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + key: podGroupKey(testPodGroupWithResource), + claims: []*resourceapi.ResourceClaim{templatedTestPodGroupClaim}, + expectedClaims: []resourceapi.ResourceClaim{*templatedTestPodGroupClaim}, + expectedPodGroupStatuses: map[string][]schedulingapi.PodGroupResourceClaimStatus{ + testPodGroupWithResource.Name: { + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, { name: "find-created-claim-in-cache", pods: []*v1.Pod{testPodWithResource}, @@ -219,10 +375,28 @@ func TestSyncHandler(t *testing.T) { }, expectedMetrics: expectedMetrics{0, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "find-created-claim-in-cache-podgroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + key: podGroupKey(testPodGroupWithResource), + claimsInCache: []*resourceapi.ResourceClaim{templatedTestPodGroupClaim}, + expectedPodGroupStatuses: map[string][]schedulingapi.PodGroupResourceClaimStatus{ + testPodGroupWithResource.Name: { + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, { name: "no-such-pod", key: podKey(testPodWithResource), }, + { + workloadResourceClaimsEnabled: true, + name: "no-such-podgroup", + key: podGroupKey(testPodGroupWithResource), + }, { name: "pod-deleted", pods: func() []*v1.Pod { @@ -233,6 +407,17 @@ func TestSyncHandler(t *testing.T) { }(), key: podKey(testPodWithResource), }, + { + workloadResourceClaimsEnabled: true, + name: "podgroup-deleted", + podGroups: func() []*schedulingapi.PodGroup { + deleted := metav1.Now() + podGroups := []*schedulingapi.PodGroup{testPodGroupWithResource.DeepCopy()} + podGroups[0].DeletionTimestamp = &deleted + return podGroups + }(), + key: podGroupKey(testPodGroupWithResource), + }, { name: "no-volumes", pods: []*v1.Pod{testPod}, @@ -252,6 +437,21 @@ func TestSyncHandler(t *testing.T) { }, expectedMetrics: expectedMetrics{1, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "create-with-other-claim-podgroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podGroupKey(testPodGroupWithResource), + claims: []*resourceapi.ResourceClaim{otherNamespaceClaim}, + expectedClaims: []resourceapi.ResourceClaim{*otherNamespaceClaim, *templatedTestPodGroupClaim}, + expectedPodGroupStatuses: map[string][]schedulingapi.PodGroupResourceClaimStatus{ + testPodGroupWithResource.Name: { + {Name: testPodGroupWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestPodGroupClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{1, 0, 0, 0}, + }, { name: "wrong-claim-owner", pods: []*v1.Pod{testPodWithResource}, @@ -260,6 +460,15 @@ func TestSyncHandler(t *testing.T) { expectedClaims: []resourceapi.ResourceClaim{*conflictingClaim}, expectedError: "resource claim template \"my-template\": resourceclaimtemplate.resource.k8s.io \"my-template\" not found", }, + { + workloadResourceClaimsEnabled: true, + name: "wrong-claim-owner-podgroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + key: podGroupKey(testPodGroupWithResource), + claims: []*resourceapi.ResourceClaim{conflictingPodGroupClaim}, + expectedClaims: []resourceapi.ResourceClaim{*conflictingPodGroupClaim}, + expectedError: "resource claim template \"my-template\": resourceclaimtemplate.resource.k8s.io \"my-template\" not found", + }, { name: "create-conflict", pods: []*v1.Pod{testPodWithResource}, @@ -268,6 +477,15 @@ func TestSyncHandler(t *testing.T) { expectedMetrics: expectedMetrics{1, 0, 1, 0}, expectedError: "create ResourceClaim : Operation cannot be fulfilled on resourceclaims.resource.k8s.io \"fake name\": fake conflict", }, + { + workloadResourceClaimsEnabled: true, + name: "create-conflict-podgroup", + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + templates: []*resourceapi.ResourceClaimTemplate{template}, + key: podGroupKey(testPodGroupWithResource), + expectedMetrics: expectedMetrics{1, 0, 1, 0}, + expectedError: "create ResourceClaim : Operation cannot be fulfilled on resourceclaims.resource.k8s.io \"fake name\": fake conflict", + }, { name: "stay-reserved-seen", pods: []*v1.Pod{testPodWithResource}, @@ -297,6 +515,29 @@ func TestSyncHandler(t *testing.T) { }(), expectedMetrics: expectedMetrics{0, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "clear-reserved-podgroup", + podGroups: []*schedulingapi.PodGroup{}, + key: claimKey(testClaimReservedForPodGroup), + claims: []*resourceapi.ResourceClaim{structuredParameters(testClaimReservedForPodGroup)}, + expectedClaims: func() []resourceapi.ResourceClaim { + claim := testClaimAllocated.DeepCopy() + claim.Finalizers = []string{} + claim.Status.Allocation = nil + return []resourceapi.ResourceClaim{*claim} + }(), + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, + { + workloadResourceClaimsEnabled: false, + name: "dont-clear-reserved-podgroup-feature-disabled", + podGroups: []*schedulingapi.PodGroup{}, + key: claimKey(testClaimReservedForPodGroup), + claims: []*resourceapi.ResourceClaim{structuredParameters(testClaimReservedForPodGroup)}, + expectedClaims: []resourceapi.ResourceClaim{*structuredParameters(testClaimReservedForPodGroup)}, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, { name: "dont-clear-reserved-structured", pods: []*v1.Pod{testPodWithResource}, @@ -399,18 +640,84 @@ func TestSyncHandler(t *testing.T) { }, expectedMetrics: expectedMetrics{0, 0, 0, 0}, }, + { + workloadResourceClaimsEnabled: true, + name: "add-reserved-podgroup", + pods: []*v1.Pod{testPodWithPodGroupAndNodeName}, + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + key: podKey(testPodWithPodGroupAndNodeName), + templates: []*resourceapi.ResourceClaimTemplate{template}, + claims: []*resourceapi.ResourceClaim{templatedTestClaimAllocated}, + expectedClaims: []resourceapi.ResourceClaim{*templatedTestClaimReservedForPodGroup}, + expectedStatuses: map[string][]v1.PodResourceClaimStatus{ + testPodWithPodGroupAndNodeName.Name: { + {Name: testPodWithPodGroupAndNodeName.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, + { + workloadResourceClaimsEnabled: false, + name: "add-reserved-podgroup-feature-disabled", + pods: []*v1.Pod{testPodWithPodGroupAndNodeName}, + podGroups: []*schedulingapi.PodGroup{testPodGroupWithResource}, + key: podKey(testPodWithPodGroupAndNodeName), + templates: []*resourceapi.ResourceClaimTemplate{template}, + claims: []*resourceapi.ResourceClaim{templatedTestClaimAllocated}, + expectedClaims: []resourceapi.ResourceClaim{*templatedTestClaimReserved}, + expectedStatuses: map[string][]v1.PodResourceClaimStatus{ + testPodWithPodGroupAndNodeName.Name: { + {Name: testPodWithPodGroupAndNodeName.Spec.ResourceClaims[0].Name, ResourceClaimName: &templatedTestClaim.Name}, + }, + }, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, + { + name: "clean up pod reservation with non-pod reservation present", + pods: func() []*v1.Pod { + pod := testPodWithResource.DeepCopy() + pod.Status.Phase = v1.PodSucceeded + return []*v1.Pod{pod} + }(), + claims: func() []*resourceapi.ResourceClaim { + claim := testClaimReserved.DeepCopy() + nonPodRef := resourceapi.ResourceClaimConsumerReference{ + APIGroup: "foo.com", + Resource: "foo", + Name: "foo", + UID: "123", + } + claim.Status.ReservedFor = append(claim.Status.ReservedFor, nonPodRef) + return []*resourceapi.ResourceClaim{claim} + }(), + key: testClaimKey, + expectedClaims: []resourceapi.ResourceClaim{func() resourceapi.ResourceClaim { + claim := testClaimReserved.DeepCopy() + nonPodRef := resourceapi.ResourceClaimConsumerReference{ + APIGroup: "foo.com", + Resource: "foo", + Name: "foo", + UID: "123", + } + claim.Status.ReservedFor = []resourceapi.ResourceClaimConsumerReference{nonPodRef} + return *claim + }()}, + expectedMetrics: expectedMetrics{0, 0, 0, 0}, + }, } for _, tc := range tests { // Run sequentially because of global logging and global metrics. t.Run(tc.name, func(t *testing.T) { tCtx := ktesting.Init(t) - tCtx = ktesting.WithCancel(tCtx) var objects []runtime.Object for _, pod := range tc.pods { objects = append(objects, pod) } + for _, podGroup := range tc.podGroups { + objects = append(objects, podGroup) + } for _, claim := range tc.claims { objects = append(objects, claim) } @@ -426,15 +733,17 @@ func TestSyncHandler(t *testing.T) { } informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) podInformer := informerFactory.Core().V1().Pods() + podGroupInformer := informerFactory.Scheduling().V1alpha2().PodGroups() claimInformer := informerFactory.Resource().V1().ResourceClaims() templateInformer := informerFactory.Resource().V1().ResourceClaimTemplates() setupMetrics() features := Features{ - AdminAccess: tc.adminAccessEnabled, - PrioritizedList: tc.prioritizedListEnabled, + AdminAccess: tc.adminAccessEnabled, + PrioritizedList: tc.prioritizedListEnabled, + WorkloadResourceClaims: tc.workloadResourceClaimsEnabled, } - ec, err := NewController(tCtx.Logger(), features, fakeKubeClient, podInformer, claimInformer, templateInformer) + ec, err := NewController(tCtx.Logger(), features, fakeKubeClient, podInformer, podGroupInformer, claimInformer, templateInformer) if err != nil { t.Fatalf("error creating ephemeral controller : %v", err) } @@ -493,24 +802,173 @@ func TestSyncHandler(t *testing.T) { } assert.Equal(t, tc.expectedStatuses, actualStatuses, "pod resource claim statuses") + podGroups, err := fakeKubeClient.SchedulingV1alpha2().PodGroups("").List(tCtx, metav1.ListOptions{}) + if err != nil { + t.Fatalf("unexpected error while listing podgroups: %v", err) + } + var actualPodGroupStatuses map[string][]schedulingapi.PodGroupResourceClaimStatus + for _, podGroup := range podGroups.Items { + if len(podGroup.Status.ResourceClaimStatuses) == 0 { + continue + } + if actualPodGroupStatuses == nil { + actualPodGroupStatuses = make(map[string][]schedulingapi.PodGroupResourceClaimStatus) + } + actualPodGroupStatuses[podGroup.Name] = podGroup.Status.ResourceClaimStatuses + } + assert.Equal(t, tc.expectedPodGroupStatuses, actualPodGroupStatuses, "podgroup resource claim statuses") + expectMetrics(t, tc.expectedMetrics) }) } } +func TestResourceClaimTemplateEventHandler(t *testing.T) { + tCtx := ktesting.Init(t) + + fakeKubeClient := createTestClient() + informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) + podInformer := informerFactory.Core().V1().Pods() + podGroupInformer := informerFactory.Scheduling().V1alpha2().PodGroups() + claimInformer := informerFactory.Resource().V1().ResourceClaims() + templateInformer := informerFactory.Resource().V1().ResourceClaimTemplates() + claimTemplateClient := fakeKubeClient.ResourceV1().ResourceClaimTemplates(testNamespace) + claimTemplateTmpClient := fakeKubeClient.ResourceV1().ResourceClaimTemplates("tmp") + podClient := fakeKubeClient.CoreV1().Pods(testNamespace) + podTmpClient := fakeKubeClient.CoreV1().Pods("tmp") + + ec, err := NewController(tCtx.Logger(), Features{}, fakeKubeClient, podInformer, podGroupInformer, claimInformer, templateInformer) + tCtx.ExpectNoError(err, "creating ephemeral controller") + + informerFactory.Start(tCtx.Done()) + stopInformers := func() { + tCtx.Cancel("stopping informers") + informerFactory.Shutdown() + } + defer stopInformers() + + expectQueue := func(tCtx ktesting.TContext, expectedKeys []string, expectedIndexerKeys []string) { + g := gomega.NewWithT(tCtx) + tCtx.Helper() + + lenDiffMessage := func() string { + actualKeys := []string{} + for ec.queue.Len() > 0 { + actual, _ := ec.queue.Get() + actualKeys = append(actualKeys, actual) + ec.queue.Forget(actual) + ec.queue.Done(actual) + } + return "Workqueue does not contain expected number of elements\n" + + "Diff of elements (- expected, + actual):\n" + + diff.Diff(expectedKeys, actualKeys) + } + + g.Eventually(ec.queue.Len). + WithTimeout(5*time.Second). + Should(gomega.Equal(len(expectedKeys)), lenDiffMessage) + g.Consistently(ec.queue.Len). + WithTimeout(1*time.Second). + Should(gomega.Equal(len(expectedKeys)), lenDiffMessage) + + g.Eventually(func() int { return len(ec.podIndexer.ListIndexFuncValues(podResourceClaimTemplateIndex)) }). + WithTimeout(5 * time.Second). + Should(gomega.Equal(len(expectedIndexerKeys))) + g.Consistently(func() int { return len(ec.podIndexer.ListIndexFuncValues(podResourceClaimTemplateIndex)) }). + WithTimeout(1 * time.Second). + Should(gomega.Equal(len(expectedIndexerKeys))) + + for _, expected := range expectedKeys { + actual, shuttingDown := ec.queue.Get() + g.Expect(shuttingDown).To(gomega.BeFalseBecause("workqueue is unexpectedly shutting down")) + g.Expect(actual).To(gomega.Equal(expected)) + ec.queue.Forget(actual) + ec.queue.Done(actual) + } + + for _, src := range expectedIndexerKeys { + objects, err := ec.podIndexer.ByIndex(podResourceClaimTemplateIndex, src) + g.Expect(err).NotTo(gomega.HaveOccurred(), "should not error when getting objects by index for key %s", src) + g.Expect(objects).NotTo(gomega.BeEmpty(), "should have at least one object indexed for key %s", src) + + // Verify that the indexed objects are the expected pods + found := false + for _, obj := range objects { + pod, ok := obj.(*v1.Pod) + if !ok { + continue + } + // Check if this pod matches the expected template reference + for _, claim := range pod.Spec.ResourceClaims { + if claim.ResourceClaimTemplateName != nil { + // Build the expected index key for this pod + expectedKey := pod.Namespace + "/" + *claim.ResourceClaimTemplateName + if expectedKey == src { + found = true + break + } + } + } + } + g.Expect(found).To(gomega.BeTrueBecause("should find a pod with template %s in index for key %s", templateName, src)) + } + } + + tmpNamespace := "tmp" + + expectQueue(tCtx, []string{}, []string{}) + + // Create two pods: + // - testPodWithResource in the my-namespace namespace + // - fake-1 in the tmp namespace + _, err = podClient.Create(tCtx, testPodWithResource, metav1.CreateOptions{}) + _, err1 := podTmpClient.Create(tCtx, makePod("fake-1", tmpNamespace, "uidpod2", *makePodResourceClaim(podResourceClaimName, templateName)), metav1.CreateOptions{}) + tCtx.Step("create pod", func(tCtx ktesting.TContext) { + tCtx.ExpectNoError(err) + tCtx.ExpectNoError(err1) + expectQueue(tCtx, []string{testPodKey, podKeyPrefix + tmpNamespace + "/" + "fake-1"}, []string{testNamespace + "/" + templateName, tmpNamespace + "/" + templateName}) + }) + + // The item has been forgotten and marked as done in the workqueue,so queue is nil + tCtx.Step("expect queue is nil", func(tCtx ktesting.TContext) { + expectQueue(tCtx, []string{}, []string{testNamespace + "/" + templateName, tmpNamespace + "/" + templateName}) + }) + + // After create claim template,queue should have test pod key + _, err = claimTemplateClient.Create(tCtx, template, metav1.CreateOptions{}) + tCtx.Step("create claim template after pod backoff", func(tCtx ktesting.TContext) { + tCtx.ExpectNoError(err) + expectQueue(tCtx, []string{testPodKey}, []string{testNamespace + "/" + templateName, tmpNamespace + "/" + templateName}) + }) + + // The item has been forgotten and marked as done in the workqueue,so queue is nil + tCtx.Step("expect queue is nil", func(tCtx ktesting.TContext) { + expectQueue(tCtx, []string{}, []string{testNamespace + "/" + templateName, tmpNamespace + "/" + templateName}) + }) + + // After create tmp namespace claim template,queue should have fake pod key + TmpNamespaceTemplate := makeTemplate(templateName, "tmp", className, nil) + _, err = claimTemplateTmpClient.Create(tCtx, TmpNamespaceTemplate, metav1.CreateOptions{}) + tCtx.Step("create claim template in tmp namespace after pod backoff in test namespace", func(tCtx ktesting.TContext) { + tCtx.ExpectNoError(err) + expectQueue(tCtx, []string{podKeyPrefix + tmpNamespace + "/" + "fake-1"}, []string{testNamespace + "/" + templateName, tmpNamespace + "/" + templateName}) + }) + +} + func TestResourceClaimEventHandler(t *testing.T) { tCtx := ktesting.Init(t) - tCtx = ktesting.WithCancel(tCtx) fakeKubeClient := createTestClient() informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) podInformer := informerFactory.Core().V1().Pods() + podGroupInformer := informerFactory.Scheduling().V1alpha2().PodGroups() claimInformer := informerFactory.Resource().V1().ResourceClaims() templateInformer := informerFactory.Resource().V1().ResourceClaimTemplates() setupMetrics() claimClient := fakeKubeClient.ResourceV1().ResourceClaims(testNamespace) - ec, err := NewController(tCtx.Logger(), Features{}, fakeKubeClient, podInformer, claimInformer, templateInformer) + ec, err := NewController(tCtx.Logger(), Features{}, fakeKubeClient, podInformer, podGroupInformer, claimInformer, templateInformer) tCtx.ExpectNoError(err, "creating ephemeral controller") informerFactory.Start(tCtx.Done()) @@ -559,7 +1017,7 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Create(tCtx, testClaim, metav1.CreateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: ""}, 1) - ktesting.Step(tCtx, "create claim", func(tCtx ktesting.TContext) { + tCtx.Step("create claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) expectQueue(tCtx, []string{testClaimKey}) @@ -568,7 +1026,7 @@ func TestResourceClaimEventHandler(t *testing.T) { modifiedClaim := testClaim.DeepCopy() modifiedClaim.Labels = map[string]string{"foo": "bar"} _, err = claimClient.Update(tCtx, modifiedClaim, metav1.UpdateOptions{}) - ktesting.Step(tCtx, "modify claim", func(tCtx ktesting.TContext) { + tCtx.Step("modify claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Consistently(tCtx) expectQueue(tCtx, []string{testClaimKey}) @@ -577,7 +1035,7 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Update(tCtx, testClaimAllocated, metav1.UpdateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: ""}, -1) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "false", Source: ""}, 1) - ktesting.Step(tCtx, "allocate claim", func(tCtx ktesting.TContext) { + tCtx.Step("allocate claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) expectQueue(tCtx, []string{testClaimKey}) @@ -586,7 +1044,7 @@ func TestResourceClaimEventHandler(t *testing.T) { modifiedClaim = testClaimAllocated.DeepCopy() modifiedClaim.Labels = map[string]string{"foo": "bar2"} _, err = claimClient.Update(tCtx, modifiedClaim, metav1.UpdateOptions{}) - ktesting.Step(tCtx, "modify claim", func(tCtx ktesting.TContext) { + tCtx.Step("modify claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Consistently(tCtx) expectQueue(tCtx, []string{testClaimKey}) @@ -596,7 +1054,7 @@ func TestResourceClaimEventHandler(t *testing.T) { otherClaimAllocated.Name += "2" _, err = claimClient.Create(tCtx, otherClaimAllocated, metav1.CreateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "false", Source: ""}, 1) - ktesting.Step(tCtx, "create allocated claim", func(tCtx ktesting.TContext) { + tCtx.Step("create allocated claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) expectQueue(tCtx, []string{testClaimKey + "2"}) @@ -605,7 +1063,7 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Update(tCtx, testClaim, metav1.UpdateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: ""}, 1) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "false", Source: ""}, -1) - ktesting.Step(tCtx, "deallocate claim", func(tCtx ktesting.TContext) { + tCtx.Step("deallocate claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) expectQueue(tCtx, []string{testClaimKey}) @@ -613,7 +1071,7 @@ func TestResourceClaimEventHandler(t *testing.T) { err = claimClient.Delete(tCtx, testClaim.Name, metav1.DeleteOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: ""}, -1) - ktesting.Step(tCtx, "delete deallocated claim", func(tCtx ktesting.TContext) { + tCtx.Step("delete deallocated claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) expectQueue(tCtx, []string{}) @@ -621,7 +1079,7 @@ func TestResourceClaimEventHandler(t *testing.T) { err = claimClient.Delete(tCtx, otherClaimAllocated.Name, metav1.DeleteOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "false", Source: ""}, -1) - ktesting.Step(tCtx, "delete allocated claim", func(tCtx ktesting.TContext) { + tCtx.Step("delete allocated claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) expectQueue(tCtx, []string{}) @@ -629,7 +1087,7 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Create(tCtx, templatedTestClaimWithAdmin, metav1.CreateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "true", Source: "resource_claim_template"}, 1) - ktesting.Step(tCtx, "create claim with admin access", func(tCtx ktesting.TContext) { + tCtx.Step("create claim with admin access", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) @@ -637,7 +1095,7 @@ func TestResourceClaimEventHandler(t *testing.T) { modifiedClaim = templatedTestClaimWithAdmin.DeepCopy() modifiedClaim.Labels = map[string]string{"foo": "bar"} _, err = claimClient.Update(tCtx, modifiedClaim, metav1.UpdateOptions{}) - ktesting.Step(tCtx, "modify claim", func(tCtx ktesting.TContext) { + tCtx.Step("modify claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Consistently(tCtx) }) @@ -645,7 +1103,7 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Update(tCtx, templatedTestClaimWithAdminAllocated, metav1.UpdateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "true", Source: "resource_claim_template"}, -1) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "true", Source: "resource_claim_template"}, 1) - ktesting.Step(tCtx, "allocate claim with admin access", func(tCtx ktesting.TContext) { + tCtx.Step("allocate claim with admin access", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) @@ -653,7 +1111,7 @@ func TestResourceClaimEventHandler(t *testing.T) { modifiedClaim = templatedTestClaimWithAdminAllocated.DeepCopy() modifiedClaim.Labels = map[string]string{"foo": "bar2"} _, err = claimClient.Update(tCtx, modifiedClaim, metav1.UpdateOptions{}) - ktesting.Step(tCtx, "modify claim", func(tCtx ktesting.TContext) { + tCtx.Step("modify claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Consistently(tCtx) }) @@ -662,7 +1120,7 @@ func TestResourceClaimEventHandler(t *testing.T) { otherClaimAllocated.Name += "2" _, err = claimClient.Create(tCtx, otherClaimAllocated, metav1.CreateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "true", Source: "resource_claim_template"}, 1) - ktesting.Step(tCtx, "create allocated claim with admin access", func(tCtx ktesting.TContext) { + tCtx.Step("create allocated claim with admin access", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) @@ -670,28 +1128,28 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Update(tCtx, templatedTestClaimWithAdmin, metav1.UpdateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "true", Source: "resource_claim_template"}, 1) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "true", Source: "resource_claim_template"}, -1) - ktesting.Step(tCtx, "deallocate claim with admin access", func(tCtx ktesting.TContext) { + tCtx.Step("deallocate claim with admin access", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) err = claimClient.Delete(tCtx, templatedTestClaimWithAdmin.Name, metav1.DeleteOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "true", Source: "resource_claim_template"}, -1) - ktesting.Step(tCtx, "delete deallocated claim with admin access", func(tCtx ktesting.TContext) { + tCtx.Step("delete deallocated claim with admin access", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) err = claimClient.Delete(tCtx, otherClaimAllocated.Name, metav1.DeleteOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "true", Source: "resource_claim_template"}, -1) - ktesting.Step(tCtx, "delete allocated claim with admin access", func(tCtx ktesting.TContext) { + tCtx.Step("delete allocated claim with admin access", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) _, err = claimClient.Create(tCtx, extendedTestClaim, metav1.CreateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: "extended_resource"}, 1) - ktesting.Step(tCtx, "create extended resource claim", func(tCtx ktesting.TContext) { + tCtx.Step("create extended resource claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) @@ -699,7 +1157,7 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Update(tCtx, extendedTestClaimAllocated, metav1.UpdateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: "extended_resource"}, -1) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "false", Source: "extended_resource"}, 1) - ktesting.Step(tCtx, "allocate extended resource claim", func(tCtx ktesting.TContext) { + tCtx.Step("allocate extended resource claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) @@ -707,14 +1165,14 @@ func TestResourceClaimEventHandler(t *testing.T) { _, err = claimClient.Update(tCtx, extendedTestClaim, metav1.UpdateOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: "extended_resource"}, 1) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "true", AdminAccess: "false", Source: "extended_resource"}, -1) - ktesting.Step(tCtx, "deallocate extended resource claim", func(tCtx ktesting.TContext) { + tCtx.Step("deallocate extended resource claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) err = claimClient.Delete(tCtx, extendedTestClaim.Name, metav1.DeleteOptions{}) em = em.withUpdates(resourceclaimmetrics.NumResourceClaimLabels{Allocated: "false", AdminAccess: "false", Source: "extended_resource"}, -1) - ktesting.Step(tCtx, "delete extended resource claim", func(tCtx ktesting.TContext) { + tCtx.Step("delete extended resource claim", func(tCtx ktesting.TContext) { tCtx.ExpectNoError(err) em.Eventually(tCtx) }) @@ -722,6 +1180,163 @@ func TestResourceClaimEventHandler(t *testing.T) { em.Consistently(tCtx) } +func TestEventHandlers(t *testing.T) { testEventHandlers(ktesting.Init(t)) } +func testEventHandlers(tCtx ktesting.TContext) { + type object interface { + runtime.Object + metav1.Object + } + + tests := map[string]struct { + features Features + initialObjects []runtime.Object + createObjects []object + updateObjects []object + deleteObjects []object + expectedKeys []string + expectedMetrics map[resourceclaimmetrics.NumResourceClaimLabels]float64 + }{ + "nothing": {}, + "new-podgroup-feature-disabled": { + features: Features{WorkloadResourceClaims: false}, + createObjects: []object{testPodGroupWithResourceInStatus}, + expectedKeys: []string{}, + }, + "new-podgroup": { + features: Features{WorkloadResourceClaims: true}, + createObjects: []object{testPodGroupWithResourceInStatus}, + expectedKeys: []string{testPodGroupKey}, + }, + "new-podgroup-templated-claim-already-exists": { + features: Features{WorkloadResourceClaims: true}, + initialObjects: []runtime.Object{testPodGroupClaim}, + createObjects: []object{testPodGroupWithResourceInStatus}, + expectedKeys: []string{}, + expectedMetrics: map[resourceclaimmetrics.NumResourceClaimLabels]float64{ + {Allocated: "false", AdminAccess: "false"}: 1, + }, + }, + "new-templated-claim-for-podgroup": { + features: Features{WorkloadResourceClaims: true}, + initialObjects: []runtime.Object{testPodGroupWithResourceInStatus}, + createObjects: []object{testPodGroupClaim}, + expectedKeys: []string{testClaimKey}, + expectedMetrics: map[resourceclaimmetrics.NumResourceClaimLabels]float64{ + {Allocated: "false", AdminAccess: "false"}: 1, + }, + }, + "new-template-for-podgroup": { + features: Features{WorkloadResourceClaims: true}, + initialObjects: []runtime.Object{testPodGroupWithResource}, + createObjects: []object{template}, + expectedKeys: []string{testPodGroupKey}, + }, + "podgroup-claim-status-update": { + features: Features{WorkloadResourceClaims: true}, + initialObjects: []runtime.Object{ + testPodGroupWithResource, + podInPodGroup(testPodWithPodGroupResource, testPodName+"-1", testPodGroupName), + podInPodGroup(testPodWithPodGroupResource, testPodName+"-2", testPodGroupName), + podInPodGroup(testPodWithPodGroupResource, testPodName+"-3", testPodGroupName+"-2"), + podInPodGroup(testPod, testPodName+"-4", testPodGroupName), + }, + updateObjects: []object{testPodGroupWithResourceInStatus}, + expectedKeys: []string{ + testPodGroupKey, + testPodKey + "-1", + testPodKey + "-2", + }, + }, + "podgroup-claim-status-update-feature-disabled": { + features: Features{WorkloadResourceClaims: false}, + initialObjects: []runtime.Object{ + testPodGroupWithResource, + podInPodGroup(testPodWithPodGroupResource, testPodName+"-1", testPodGroupName), + podInPodGroup(testPodWithPodGroupResource, testPodName+"-2", testPodGroupName), + podInPodGroup(testPodWithPodGroupResource, testPodName+"-3", testPodGroupName+"-2"), + podInPodGroup(testPod, testPodName+"-4", testPodGroupName), + }, + updateObjects: []object{testPodGroupWithResourceInStatus}, + expectedKeys: []string{}, + }, + } + for name, test := range tests { + tCtx.SyncTest(name, func(tCtx ktesting.TContext) { + fakeKubeClient := createTestClient(test.initialObjects...) + informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) + podInformer := informerFactory.Core().V1().Pods() + podGroupInformer := informerFactory.Scheduling().V1alpha2().PodGroups() + claimInformer := informerFactory.Resource().V1().ResourceClaims() + templateInformer := informerFactory.Resource().V1().ResourceClaimTemplates() + setupMetrics() + + ec, err := NewController(tCtx.Logger(), test.features, fakeKubeClient, podInformer, podGroupInformer, claimInformer, templateInformer) + tCtx.ExpectNoError(err, "creating ephemeral controller") + tCtx.Cleanup(ec.queue.ShutDown) + + informerFactory.Start(tCtx.Done()) + stopInformers := func() { + tCtx.Cancel("stopping informers") + informerFactory.Shutdown() + } + tCtx.Cleanup(stopInformers) + + drainQueue := func() []string { + tCtx.Wait() + actualKeys := []string{} + for ec.queue.Len() > 0 { + actual, shuttingDown := ec.queue.Get() + tCtx.Expect(shuttingDown).To(gomega.BeFalseBecause("workqueue should not be shutting down")) + actualKeys = append(actualKeys, actual) + ec.queue.Forget(actual) + ec.queue.Done(actual) + } + return actualKeys + } + gvr := func(obj metav1.Object) schema.GroupVersionResource { + switch obj.(type) { + case *v1.Pod: + return v1.SchemeGroupVersion.WithResource("pods") + case *schedulingapi.PodGroup: + return schedulingapi.SchemeGroupVersion.WithResource("podgroups") + case *resourceapi.ResourceClaim: + return resourceapi.SchemeGroupVersion.WithResource("resourceclaims") + case *resourceapi.ResourceClaimTemplate: + return resourceapi.SchemeGroupVersion.WithResource("resourceclaimtemplates") + } + tCtx.Fatalf("invalid object type %T", obj) + return schema.GroupVersionResource{} + } + + // Not checking after initial objects added. Waiting for the + // interesting operations. + _ = drainQueue() + + for _, object := range test.createObjects { + err := fakeKubeClient.Tracker().Create(gvr(object), object, object.GetNamespace(), metav1.CreateOptions{}) + tCtx.ExpectNoError(err) + } + for _, object := range test.updateObjects { + err := fakeKubeClient.Tracker().Update(gvr(object), object, object.GetNamespace(), metav1.UpdateOptions{}) + tCtx.ExpectNoError(err) + } + for _, object := range test.deleteObjects { + err := fakeKubeClient.Tracker().Delete(gvr(object), object.GetName(), object.GetNamespace(), metav1.DeleteOptions{}) + tCtx.ExpectNoError(err) + } + + actualKeys := drainQueue() + tCtx.Expect(actualKeys).To(gomega.ConsistOf(test.expectedKeys), "Workqueue does not contain expected elements") + + em := newNumMetrics(claimInformer.Lister()) + for labels, val := range test.expectedMetrics { + em = em.withUpdates(labels, val) + } + em.verify(tCtx) + }) + } +} + func TestGetAdminAccessMetricLabel(t *testing.T) { tests := []struct { name string @@ -866,7 +1481,7 @@ func makeTemplatedClaim(podClaimName, generateName, namespace, classname string, Name: fmt.Sprintf("%s-%d", generateName, createCounter), GenerateName: generateName, Namespace: namespace, - Annotations: map[string]string{"resource.kubernetes.io/pod-claim-name": podClaimName}, + Annotations: map[string]string{resourceapi.PodResourceClaimAnnotation: podClaimName}, }, } if owner != nil { @@ -922,13 +1537,25 @@ func structuredParameters(claim *resourceapi.ResourceClaim) *resourceapi.Resourc return claim } -func reserveClaim(claim *resourceapi.ResourceClaim, pod *v1.Pod) *resourceapi.ResourceClaim { +func reserveClaim(claim *resourceapi.ResourceClaim, obj metav1.Object) *resourceapi.ResourceClaim { claim = claim.DeepCopy() + var apiGroup, resource string + switch obj.(type) { + case *v1.Pod: + apiGroup = v1.GroupName + resource = "pods" + case *schedulingapi.PodGroup: + apiGroup = schedulingapi.GroupName + resource = "podgroups" + default: + panic(fmt.Sprintf("invalid type: %T", obj)) + } claim.Status.ReservedFor = append(claim.Status.ReservedFor, resourceapi.ResourceClaimConsumerReference{ - Resource: "pods", - Name: pod.Name, - UID: pod.UID, + APIGroup: apiGroup, + Resource: resource, + Name: obj.GetName(), + UID: obj.GetUID(), }, ) return claim @@ -952,6 +1579,32 @@ func makePod(name, namespace string, uid types.UID, podClaims ...v1.PodResourceC return pod } +func makePodGroupResourceClaim(name, templateName string) *schedulingapi.PodGroupResourceClaim { + return &schedulingapi.PodGroupResourceClaim{ + Name: name, + ResourceClaimTemplateName: &templateName, + } +} + +func makePodGroup(name, namespace string, uid types.UID, podGroupClaims ...schedulingapi.PodGroupResourceClaim) *schedulingapi.PodGroup { + podGroup := &schedulingapi.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, UID: uid}, + Spec: schedulingapi.PodGroupSpec{ + ResourceClaims: podGroupClaims, + }, + } + + return podGroup +} +func podInPodGroup(pod *v1.Pod, podName, podGroupName string) *v1.Pod { + pod = pod.DeepCopy() + pod.Name = podName + pod.Spec.SchedulingGroup = &v1.PodSchedulingGroup{ + PodGroupName: &podGroupName, + } + return pod +} + func makeTemplate(name, namespace, classname string, adminAccess *bool) *resourceapi.ResourceClaimTemplate { template := &resourceapi.ResourceClaimTemplate{ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, @@ -980,16 +1633,31 @@ func podKey(pod *v1.Pod) string { return podKeyPrefix + pod.Namespace + "/" + pod.Name } +func podGroupKey(podGroup *schedulingapi.PodGroup) string { + return podGroupKeyPrefix + podGroup.Namespace + "/" + podGroup.Name +} + func claimKey(claim *resourceapi.ResourceClaim) string { return claimKeyPrefix + claim.Namespace + "/" + claim.Name } -func makeOwnerReference(pod *v1.Pod, isController bool) *metav1.OwnerReference { +func makeOwnerReference(obj metav1.Object, isController bool) *metav1.OwnerReference { + var apiVersion, kind string + switch obj.(type) { + case *v1.Pod: + apiVersion = v1.SchemeGroupVersion.String() + kind = "Pod" + case *schedulingapi.PodGroup: + apiVersion = schedulingapi.SchemeGroupVersion.String() + kind = "PodGroup" + default: + panic(fmt.Sprintf("invalid type %T", obj)) + } return &metav1.OwnerReference{ - APIVersion: "v1", - Kind: "Pod", - Name: pod.Name, - UID: pod.UID, + APIVersion: apiVersion, + Kind: kind, + Name: obj.GetName(), + UID: obj.GetUID(), Controller: &isController, } } @@ -1085,6 +1753,13 @@ func getNumMetric(lister resourcelisters.ResourceClaimLister, logger klog.Logger return em, nil } +func (em numMetrics) verify(tCtx ktesting.TContext) { + tCtx.Helper() + result, err := getNumMetric(em.lister, tCtx.Logger()) + tCtx.ExpectNoError(err) + tCtx.Expect(result.metrics).To(gomega.Equal(em.metrics)) +} + func (em numMetrics) Eventually(tCtx ktesting.TContext) { g := gomega.NewWithT(tCtx) tCtx.Helper() @@ -1324,17 +1999,17 @@ func TestEnqueuePodExtendedResourceClaims(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.featureGateEnabled) tCtx := ktesting.Init(t) - tCtx = ktesting.WithCancel(tCtx) fakeKubeClient := createTestClient() informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) podInformer := informerFactory.Core().V1().Pods() + podGroupInformer := informerFactory.Scheduling().V1alpha2().PodGroups() claimInformer := informerFactory.Resource().V1().ResourceClaims() templateInformer := informerFactory.Resource().V1().ResourceClaimTemplates() setupMetrics() - ec, err := NewController(tCtx.Logger(), Features{}, fakeKubeClient, podInformer, claimInformer, templateInformer) + ec, err := NewController(tCtx.Logger(), Features{}, fakeKubeClient, podInformer, podGroupInformer, claimInformer, templateInformer) if err != nil { t.Fatalf("error creating controller: %v", err) } @@ -1360,13 +2035,7 @@ func TestEnqueuePodExtendedResourceClaims(t *testing.T) { if test.pod.Status.ExtendedResourceClaimStatus != nil { expectedClaimKey = claimKeyPrefix + test.pod.Namespace + "/" + test.pod.Status.ExtendedResourceClaimStatus.ResourceClaimName } - found := false - for _, k := range keys { - if k == expectedClaimKey { - found = true - break - } - } + found := slices.Contains(keys, expectedClaimKey) if test.expectExtendedClaimEnqueued && !found { t.Errorf("expected extended claim key %q to be enqueued, got keys: %v", expectedClaimKey, keys) } diff --git a/pkg/controller/resourcepoolstatusrequest/controller.go b/pkg/controller/resourcepoolstatusrequest/controller.go new file mode 100644 index 0000000000000..752e991ccf230 --- /dev/null +++ b/pkg/controller/resourcepoolstatusrequest/controller.go @@ -0,0 +1,556 @@ +/* +Copyright 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 resourcepoolstatusrequest + +import ( + "context" + "fmt" + "sort" + "time" + + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + resourcev1informers "k8s.io/client-go/informers/resource/v1" + resourcev1alpha3informers "k8s.io/client-go/informers/resource/v1alpha3" + clientset "k8s.io/client-go/kubernetes" + resourcev1listers "k8s.io/client-go/listers/resource/v1" + resourcev1alpha3listers "k8s.io/client-go/listers/resource/v1alpha3" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/controller/resourcepoolstatusrequest/metrics" +) + +const ( + // ControllerName is the name of this controller. + ControllerName = "resourcepoolstatusrequest-controller" + + // maxRetries is the number of times a request will be retried before it is dropped. + maxRetries = 5 + + // cleanupPollingInterval is how often the controller checks for expired requests. + cleanupPollingInterval = 10 * time.Minute + + // completedRequestTTL is how long a completed request (with Complete or Failed condition) + // is retained before being automatically deleted. + completedRequestTTL = 1 * time.Hour + + // pendingRequestTTL is how long a pending request (without status) + // is retained before being automatically deleted. This handles stuck requests. + pendingRequestTTL = 24 * time.Hour +) + +// Controller manages ResourcePoolStatusRequest processing. +type Controller struct { + client clientset.Interface + + // requestLister can list/get ResourcePoolStatusRequests from the shared informer's store + requestLister resourcev1alpha3listers.ResourcePoolStatusRequestLister + + // sliceLister can list/get ResourceSlices from the shared informer's store + sliceLister resourcev1listers.ResourceSliceLister + + // claimLister can list/get ResourceClaims from the shared informer's store + claimLister resourcev1listers.ResourceClaimLister + + // requestSynced returns true if the ResourcePoolStatusRequest store has been synced + requestSynced cache.InformerSynced + + // sliceSynced returns true if the ResourceSlice store has been synced + sliceSynced cache.InformerSynced + + // claimSynced returns true if the ResourceClaim store has been synced + claimSynced cache.InformerSynced + + // workqueue is a rate limited work queue for processing ResourcePoolStatusRequests + workqueue workqueue.TypedRateLimitingInterface[string] +} + +// NewController creates a new ResourcePoolStatusRequest controller. +func NewController( + ctx context.Context, + client clientset.Interface, + requestInformer resourcev1alpha3informers.ResourcePoolStatusRequestInformer, + sliceInformer resourcev1informers.ResourceSliceInformer, + claimInformer resourcev1informers.ResourceClaimInformer, +) (*Controller, error) { + logger := klog.FromContext(ctx) + + c := &Controller{ + client: client, + requestLister: requestInformer.Lister(), + sliceLister: sliceInformer.Lister(), + claimLister: claimInformer.Lister(), + requestSynced: requestInformer.Informer().HasSynced, + sliceSynced: sliceInformer.Informer().HasSynced, + claimSynced: claimInformer.Informer().HasSynced, + workqueue: workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[string]()), + } + + // Register metrics + metrics.Register() + + // Set up event handlers for ResourcePoolStatusRequests + _, err := requestInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.enqueueRequest(logger, obj) + }, + UpdateFunc: func(old, new interface{}) { + c.enqueueRequest(logger, new) + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to add request event handler: %w", err) + } + + logger.Info("ResourcePoolStatusRequest controller initialized") + return c, nil +} + +// Run starts the controller workers. +func (c *Controller) Run(ctx context.Context, workers int) { + defer utilruntime.HandleCrash() + defer c.workqueue.ShutDown() + + logger := klog.FromContext(ctx) + logger.Info("Starting ResourcePoolStatusRequest controller") + + // Wait for the caches to be synced before starting workers + logger.Info("Waiting for informer caches to sync") + if !cache.WaitForCacheSync(ctx.Done(), c.requestSynced, c.sliceSynced, c.claimSynced) { + logger.Error(nil, "Failed to wait for caches to sync") + return + } + + logger.Info("Starting workers", "count", workers) + for range workers { + go wait.UntilWithContext(ctx, c.runWorker, time.Second) + } + + // Start the cleanup goroutine for TTL-based garbage collection + go wait.UntilWithContext(ctx, c.cleanupExpiredRequests, cleanupPollingInterval) + + <-ctx.Done() + logger.Info("Shutting down ResourcePoolStatusRequest controller") +} + +// runWorker is a long-running function that will continually call the +// processNextWorkItem function in order to read and process a message on the workqueue. +func (c *Controller) runWorker(ctx context.Context) { + for c.processNextWorkItem(ctx) { + } +} + +// processNextWorkItem will read a single work item off the workqueue and +// attempt to process it. +func (c *Controller) processNextWorkItem(ctx context.Context) bool { + key, shutdown := c.workqueue.Get() + if shutdown { + return false + } + + defer c.workqueue.Done(key) + + logger := klog.FromContext(ctx) + err := c.syncRequest(ctx, key) + if err == nil { + c.workqueue.Forget(key) + return true + } + + if c.workqueue.NumRequeues(key) < maxRetries { + logger.Error(err, "Error syncing request, requeuing", "request", key) + c.workqueue.AddRateLimited(key) + return true + } + + logger.Error(err, "Dropping request out of the queue after max retries", "request", key, "retries", maxRetries) + c.workqueue.Forget(key) + utilruntime.HandleError(err) + + return true +} + +// syncRequest processes a single ResourcePoolStatusRequest. +func (c *Controller) syncRequest(ctx context.Context, key string) error { + logger := klog.FromContext(ctx) + startTime := time.Now() + var driverName string + var recordDuration bool + + defer func() { + if driverName != "" && recordDuration { + metrics.RequestProcessingDuration.WithLabelValues(driverName).Observe(time.Since(startTime).Seconds()) + } + }() + + // Get the request + request, err := c.requestLister.Get(key) + if err != nil { + // Request was deleted, nothing to do + return nil + } + + driverName = request.Spec.Driver + + // Skip if already processed (status is set) + if request.Status != nil { + logger.V(4).Info("Request already processed, skipping", "request", key) + return nil + } + + logger.V(2).Info("Processing ResourcePoolStatusRequest", "request", key, "driver", request.Spec.Driver) + + // Calculate the pool status on-demand from listers + status := c.calculatePoolStatus(ctx, request) + + // Requeue if pools have validation errors and retries remain — gives drivers + // time to publish remaining slices before we finalize the response. + hasIncomplete := false + for _, p := range status.Pools { + if p.ValidationError != nil { + hasIncomplete = true + break + } + } + if hasIncomplete { + return fmt.Errorf("incomplete pools detected, requeueing") + } + + // Update the request status + requestCopy := request.DeepCopy() + requestCopy.Status = &status + + _, err = c.client.ResourceV1alpha3().ResourcePoolStatusRequests().UpdateStatus(ctx, requestCopy, metav1.UpdateOptions{}) + if err != nil { + recordDuration = true + metrics.RequestProcessingErrors.WithLabelValues(driverName).Inc() + metrics.RequestsProcessed.WithLabelValues(driverName).Inc() + return fmt.Errorf("failed to update status for request %s: %w", key, err) + } + + recordDuration = true + metrics.RequestsProcessed.WithLabelValues(driverName).Inc() + logger.V(2).Info("Successfully processed ResourcePoolStatusRequest", "request", key, "poolCount", len(status.Pools)) + return nil +} + +// calculatePoolStatus computes the pool status on-demand by reading directly +// from the shared informer listers. No caches are maintained between requests. +func (c *Controller) calculatePoolStatus(ctx context.Context, request *resourcev1alpha3.ResourcePoolStatusRequest) resourcev1alpha3.ResourcePoolStatusRequestStatus { + logger := klog.FromContext(ctx) + + driver := request.Spec.Driver + var poolNameFilter string + if request.Spec.PoolName != nil { + poolNameFilter = *request.Spec.PoolName + } + + // Step 1: Aggregate pool data from ResourceSlices. + // Uses two passes: first finds the max generation per pool, then only + // counts slices at that generation (older-generation slices are ignored + // per KEP-5677). + type poolInfo struct { + driver string + poolName string + nodeName string + nodeNameMixed bool // true when slices have different NodeNames + totalDevices int32 + sliceCount int32 + expectedSliceCount int64 + generation int64 + } + + slices, err := c.sliceLister.List(labels.Everything()) + if err != nil { + logger.Error(err, "Failed to list ResourceSlices") + return errorStatus("Failed to list ResourceSlices: " + err.Error()) + } + + // Pass 1: Find max generation per pool + maxGeneration := make(map[string]int64) + for _, slice := range slices { + if slice.Spec.Driver != driver { + continue + } + slicePoolName := slice.Spec.Pool.Name + if poolNameFilter != "" && slicePoolName != poolNameFilter { + continue + } + key := slice.Spec.Driver + "/" + slicePoolName + if gen, exists := maxGeneration[key]; !exists || slice.Spec.Pool.Generation > gen { + maxGeneration[key] = slice.Spec.Pool.Generation + } + } + + // Pass 2: Aggregate only slices at max generation + poolData := make(map[string]*poolInfo) + for _, slice := range slices { + if slice.Spec.Driver != driver { + continue + } + slicePoolName := slice.Spec.Pool.Name + if poolNameFilter != "" && slicePoolName != poolNameFilter { + continue + } + + key := slice.Spec.Driver + "/" + slicePoolName + + // Skip slices from older generations + if slice.Spec.Pool.Generation < maxGeneration[key] { + continue + } + + deviceCount := int32(len(slice.Spec.Devices)) + info, exists := poolData[key] + if !exists { + var nodeName string + if slice.Spec.NodeName != nil { + nodeName = *slice.Spec.NodeName + } + poolData[key] = &poolInfo{ + driver: slice.Spec.Driver, + poolName: slicePoolName, + nodeName: nodeName, + totalDevices: deviceCount, + sliceCount: 1, + expectedSliceCount: slice.Spec.Pool.ResourceSliceCount, + generation: maxGeneration[key], + } + } else { + info.totalDevices += deviceCount + info.sliceCount++ + // Check NodeName consistency across slices + sliceNodeName := "" + if slice.Spec.NodeName != nil { + sliceNodeName = *slice.Spec.NodeName + } + if sliceNodeName != info.nodeName { + info.nodeNameMixed = true + } + } + } + + // Step 2: Count allocations from ResourceClaims + allocationData := make(map[string]int32) + + claims, err := c.claimLister.List(labels.Everything()) + if err != nil { + logger.Error(err, "Failed to list ResourceClaims") + return errorStatus("Failed to list ResourceClaims: " + err.Error()) + } + + for _, claim := range claims { + if claim.Status.Allocation == nil { + continue + } + for _, result := range claim.Status.Allocation.Devices.Results { + key := result.Driver + "/" + result.Pool + allocationData[key]++ + } + } + + // Step 3: Build pool status list, marking incomplete pools with a validation error + var pools []resourcev1alpha3.PoolStatus + for key, info := range poolData { + pool := resourcev1alpha3.PoolStatus{ + Driver: info.driver, + PoolName: info.poolName, + Generation: info.generation, + } + if info.nodeName != "" && !info.nodeNameMixed { + nodeName := info.nodeName + pool.NodeName = &nodeName + } + + if int64(info.sliceCount) < info.expectedSliceCount { + // Incomplete pool: set validation error, leave device counts and slice count nil + errMsg := fmt.Sprintf("pool %s/%s is incomplete: observed %d/%d slices at generation %d", + info.driver, info.poolName, info.sliceCount, info.expectedSliceCount, info.generation) + // Truncate to 256 bytes to stay within the API field's +k8s:maxBytes=256 limit. + if len(errMsg) > 256 { + errMsg = errMsg[:256] + } + pool.ValidationError = &errMsg + } else { + // Complete pool: populate device counts and slice count + allocatedDevices := allocationData[key] + // UnavailableDevices is currently always 0 in Alpha because the controller + // does not yet inspect device conditions/taints. This will be computed from + // real data when device health tracking is wired in. + unavailDevices := int32(0) + availableDevices := max(0, info.totalDevices-allocatedDevices-unavailDevices) + + totalDevices := info.totalDevices + allocDevices := allocatedDevices + availDevices := availableDevices + sliceCount := info.sliceCount + pool.ResourceSliceCount = &sliceCount + pool.TotalDevices = &totalDevices + pool.AllocatedDevices = &allocDevices + pool.AvailableDevices = &availDevices + pool.UnavailableDevices = &unavailDevices + } + pools = append(pools, pool) + } + + // Sort pools by driver, then pool name + sort.Slice(pools, func(i, j int) bool { + if pools[i].Driver != pools[j].Driver { + return pools[i].Driver < pools[j].Driver + } + return pools[i].PoolName < pools[j].PoolName + }) + + poolCount := int32(len(pools)) + + // Apply limit if specified + limit := resourcev1alpha3.ResourcePoolStatusRequestLimitDefault + if request.Spec.Limit != nil { + limit = *request.Spec.Limit + } + if int32(len(pools)) > limit { + pools = pools[:limit] + } + + // Count incomplete pools for condition message + incompletePoolCount := 0 + for _, p := range pools { + if p.ValidationError != nil { + incompletePoolCount++ + } + } + + conditionMessage := fmt.Sprintf("Calculated status for %d pools", len(pools)) + if incompletePoolCount > 0 { + conditionMessage = fmt.Sprintf("Calculated status for %d pools (%d incomplete)", len(pools), incompletePoolCount) + } + + now := metav1.Now() + status := resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: &poolCount, + Pools: pools, + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: now, + Reason: "CalculationComplete", + Message: conditionMessage, + }, + }, + } + + logger.V(4).Info("Calculated pool status", + "request", request.Name, + "poolCount", poolCount, + "returned", len(pools)) + + return status +} + +// errorStatus returns a status indicating a processing failure. +func errorStatus(message string) resourcev1alpha3.ResourcePoolStatusRequestStatus { + now := metav1.Now() + zero := int32(0) + return resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: &zero, + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionFailed, + Status: metav1.ConditionTrue, + LastTransitionTime: now, + Reason: "CalculationFailed", + Message: message, + }, + }, + } +} + +// enqueueRequest adds a request to the workqueue. +func (c *Controller) enqueueRequest(logger klog.Logger, obj interface{}) { + request, ok := obj.(*resourcev1alpha3.ResourcePoolStatusRequest) + if !ok { + logger.Error(nil, "Failed to cast object to ResourcePoolStatusRequest") + return + } + + // Skip if already processed (status is set) + if request.Status != nil { + return + } + + c.workqueue.Add(request.Name) +} + +// cleanupExpiredRequests deletes ResourcePoolStatusRequests that have exceeded their TTL. +// Completed requests (with status set) are deleted after completedRequestTTL. +// Pending requests (without status) are deleted after pendingRequestTTL. +func (c *Controller) cleanupExpiredRequests(ctx context.Context) { + logger := klog.FromContext(ctx) + + requests, err := c.requestLister.List(labels.Everything()) + if err != nil { + logger.Error(err, "Failed to list ResourcePoolStatusRequests for cleanup") + return + } + + var deletedCount int + for _, request := range requests { + if c.shouldDeleteRequest(request) { + if err := c.client.ResourceV1alpha3().ResourcePoolStatusRequests().Delete(ctx, request.Name, metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &request.UID}}); err != nil { + // Ignore NotFound errors - the request may have been deleted by another process + if !apierrors.IsNotFound(err) { + logger.Error(err, "Failed to delete expired ResourcePoolStatusRequest", "request", request.Name) + } + continue + } + deletedCount++ + logger.V(2).Info("Deleted expired ResourcePoolStatusRequest", "request", request.Name) + } + } + + if deletedCount > 0 { + logger.Info("Cleanup completed", "deletedCount", deletedCount) + } +} + +// shouldDeleteRequest determines if a request should be deleted based on TTL. +func (c *Controller) shouldDeleteRequest(request *resourcev1alpha3.ResourcePoolStatusRequest) bool { + if request.Status != nil { + // Completed/failed request: check against completedRequestTTL using condition time + for _, cond := range request.Status.Conditions { + if cond.Type == resourcev1alpha3.ResourcePoolStatusRequestConditionComplete || + cond.Type == resourcev1alpha3.ResourcePoolStatusRequestConditionFailed { + return isOlderThan(cond.LastTransitionTime.Time, completedRequestTTL) + } + } + // Status set but no Complete/Failed condition (edge case); use creation time + return isOlderThan(request.CreationTimestamp.Time, completedRequestTTL) + } + // Pending request: check against pendingRequestTTL based on creation time + return isOlderThan(request.CreationTimestamp.Time, pendingRequestTTL) +} + +// isOlderThan checks if the given time is older than the specified duration. +func isOlderThan(t time.Time, d time.Duration) bool { + return !t.IsZero() && time.Since(t) > d +} diff --git a/pkg/controller/resourcepoolstatusrequest/controller_test.go b/pkg/controller/resourcepoolstatusrequest/controller_test.go new file mode 100644 index 0000000000000..4524b4c6403e2 --- /dev/null +++ b/pkg/controller/resourcepoolstatusrequest/controller_test.go @@ -0,0 +1,965 @@ +/* +Copyright 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 resourcepoolstatusrequest + +import ( + "fmt" + "testing" + "time" + + resourcev1 "k8s.io/api/resource/v1" + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/klog/v2/ktesting" +) + +func TestCalculatePoolStatus(t *testing.T) { + testCases := []struct { + name string + request *resourcev1alpha3.ResourcePoolStatusRequest + slices []*resourcev1.ResourceSlice + claims []*resourcev1.ResourceClaim + expectedPools int + expectedTotal int32 + expectedAlloc int32 + expectedPoolCount int32 // expected PoolCount value (total matching pools before limit) + expectedTruncated bool // true when len(Pools) < PoolCount + checkNodeName bool // when true, verify first pool's NodeName matches expectedNodeName + expectedNodeName *string // expected NodeName for the first pool (nil means no NodeName) + checkIncomplete bool // when true, verify incomplete pool behavior + expectedIncomplete int // number of pools expected to have ValidationError set + }{ + { + name: "single-pool-no-allocations", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 4, + expectedAlloc: 0, + }, + { + name: "single-pool-with-allocations", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + }, + claims: []*resourcev1.ResourceClaim{ + makeAllocatedClaim("claim-1", "default", "test.example.com", "pool-1", "device-0"), + makeAllocatedClaim("claim-2", "default", "test.example.com", "pool-1", "device-1"), + }, + expectedPools: 1, + expectedTotal: 4, + expectedAlloc: 2, + }, + { + name: "multiple-pools", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "test.example.com", "pool-2", "node-2", 8), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 2, + expectedTotal: 12, + expectedAlloc: 0, + }, + { + name: "filter-by-driver", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "other.example.com", "pool-2", "node-2", 8), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 4, + expectedAlloc: 0, + }, + { + name: "filter-by-pool-name", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + PoolName: new("pool-1"), + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "test.example.com", "pool-2", "node-2", 8), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 4, + expectedAlloc: 0, + }, + { + name: "multiple-slices-same-pool", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "test.example.com", "pool-1", "node-1", 4), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 8, + expectedAlloc: 0, + }, + { + name: "limit-pools", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + Limit: new(int32(1)), + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "test.example.com", "pool-2", "node-2", 8), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedPoolCount: 2, + expectedTruncated: true, + }, + { + name: "no-matching-pools", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "nonexistent.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 0, + expectedTotal: 0, + expectedAlloc: 0, + }, + { + name: "older-generation-slices-ignored", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + // Old generation slice (should be ignored) + makeSliceWithGeneration("slice-old", "test.example.com", "pool-1", "node-1", 8, 1), + // New generation slices (should be counted) + makeSliceWithGeneration("slice-new-1", "test.example.com", "pool-1", "node-1", 4, 2), + makeSliceWithGeneration("slice-new-2", "test.example.com", "pool-1", "node-1", 4, 2), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 8, // 4+4 from gen 2 only, not 8 from gen 1 + expectedAlloc: 0, + }, + { + name: "incomplete-pool-has-validation-error", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + // Pool expects 5 slices but only 3 are published + makeSliceWithExpectedCount("slice-1", "test.example.com", "pool-1", "node-1", 4, 1, 5), + makeSliceWithExpectedCount("slice-2", "test.example.com", "pool-1", "node-1", 4, 1, 5), + makeSliceWithExpectedCount("slice-3", "test.example.com", "pool-1", "node-1", 4, 1, 5), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + checkIncomplete: true, + expectedIncomplete: 1, + }, + { + name: "mix-of-complete-and-incomplete-pools", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + // pool-1: complete (1/1 slices) + makeSliceWithExpectedCount("slice-1", "test.example.com", "pool-1", "node-1", 4, 1, 1), + // pool-2: incomplete (2/3 slices) + makeSliceWithExpectedCount("slice-2a", "test.example.com", "pool-2", "node-2", 4, 1, 3), + makeSliceWithExpectedCount("slice-2b", "test.example.com", "pool-2", "node-2", 4, 1, 3), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 2, + expectedTotal: 4, // only pool-1 (complete) contributes to total + expectedAlloc: 0, + checkIncomplete: true, + expectedIncomplete: 1, + }, + { + name: "same-node-pool-sets-nodename", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "test.example.com", "pool-1", "node-1", 4), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 8, + expectedAlloc: 0, + checkNodeName: true, + expectedNodeName: new("node-1"), + }, + { + name: "mixed-node-pool-clears-nodename", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4), + makeSlice("slice-2", "test.example.com", "pool-1", "node-2", 4), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 8, + expectedAlloc: 0, + checkNodeName: true, + expectedNodeName: nil, + }, + { + name: "nil-nodename-slices-no-nodename", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + makeSliceWithoutNode("slice-1", "test.example.com", "pool-1", 4), + makeSliceWithoutNode("slice-2", "test.example.com", "pool-1", 4), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 8, + expectedAlloc: 0, + checkNodeName: true, + expectedNodeName: nil, + }, + { + name: "all-pools-complete-no-validation-errors", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + }, + slices: []*resourcev1.ResourceSlice{ + // pool-1: complete (2/2 slices) + makeSliceWithExpectedCount("slice-1a", "test.example.com", "pool-1", "node-1", 4, 1, 2), + makeSliceWithExpectedCount("slice-1b", "test.example.com", "pool-1", "node-1", 4, 1, 2), + }, + claims: []*resourcev1.ResourceClaim{}, + expectedPools: 1, + expectedTotal: 8, + expectedAlloc: 0, + checkIncomplete: true, + expectedIncomplete: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + // Create fake client and informers + fakeClient := fake.NewClientset() + informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + + // Create controller + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + t.Fatalf("Failed to create controller: %v", err) + } + + // Add slices to the informer store + for _, slice := range tc.slices { + err := informerFactory.Resource().V1().ResourceSlices().Informer().GetStore().Add(slice) + if err != nil { + t.Fatalf("Failed to add slice to informer: %v", err) + } + } + + // Add claims to the informer store + for _, claim := range tc.claims { + err := informerFactory.Resource().V1().ResourceClaims().Informer().GetStore().Add(claim) + if err != nil { + t.Fatalf("Failed to add claim to informer: %v", err) + } + } + + // Calculate pool status (reads directly from listers) + status := controller.calculatePoolStatus(ctx, tc.request) + + // Verify pool count + if len(status.Pools) != tc.expectedPools { + t.Errorf("Expected %d pools, got %d", tc.expectedPools, len(status.Pools)) + } + + // Verify PoolCount field + if tc.expectedPoolCount > 0 { + if status.PoolCount == nil { + t.Errorf("Expected PoolCount to be %d, got nil", tc.expectedPoolCount) + } else if *status.PoolCount != tc.expectedPoolCount { + t.Errorf("Expected PoolCount %d, got %d", tc.expectedPoolCount, *status.PoolCount) + } + } + + // Verify truncation: len(Pools) < PoolCount means truncated + if tc.expectedTruncated { + if status.PoolCount == nil || int32(len(status.Pools)) >= *status.PoolCount { + t.Errorf("Expected truncation (len(Pools)=%d < PoolCount), but PoolCount=%v", len(status.Pools), status.PoolCount) + } + } + + // Count device totals from complete pools only (those without ValidationError) + if tc.expectedPools > 0 && !tc.expectedTruncated { + var totalDevices, allocatedDevices int32 + for _, pool := range status.Pools { + if pool.TotalDevices != nil { + totalDevices += *pool.TotalDevices + } + if pool.AllocatedDevices != nil { + allocatedDevices += *pool.AllocatedDevices + } + } + if totalDevices != tc.expectedTotal { + t.Errorf("Expected total devices %d, got %d", tc.expectedTotal, totalDevices) + } + if allocatedDevices != tc.expectedAlloc { + t.Errorf("Expected allocated devices %d, got %d", tc.expectedAlloc, allocatedDevices) + } + } + + // Verify NodeName when test specifies expected value + if tc.checkNodeName && len(status.Pools) == 1 { + pool := status.Pools[0] + if tc.expectedNodeName != nil { + if pool.NodeName == nil { + t.Errorf("Expected NodeName %q, got nil", *tc.expectedNodeName) + } else if *pool.NodeName != *tc.expectedNodeName { + t.Errorf("Expected NodeName %q, got %q", *tc.expectedNodeName, *pool.NodeName) + } + } else if pool.NodeName != nil { + t.Errorf("Expected NodeName to be nil, got %q", *pool.NodeName) + } + } + + // Verify incomplete pool counts (pools with ValidationError set) + if tc.checkIncomplete { + incompleteCount := 0 + for _, pool := range status.Pools { + if pool.ValidationError != nil { + incompleteCount++ + // Incomplete pools should have nil device counts and nil ResourceSliceCount + if pool.TotalDevices != nil { + t.Errorf("Incomplete pool %s/%s should have nil TotalDevices, got %d", pool.Driver, pool.PoolName, *pool.TotalDevices) + } + if pool.AllocatedDevices != nil { + t.Errorf("Incomplete pool %s/%s should have nil AllocatedDevices, got %d", pool.Driver, pool.PoolName, *pool.AllocatedDevices) + } + if pool.ResourceSliceCount != nil { + t.Errorf("Incomplete pool %s/%s should have nil ResourceSliceCount, got %d", pool.Driver, pool.PoolName, *pool.ResourceSliceCount) + } + } + } + if incompleteCount != tc.expectedIncomplete { + t.Errorf("Expected %d incomplete pools (with ValidationError), got %d", tc.expectedIncomplete, incompleteCount) + } + } + + // Verify conditions are set + if len(status.Conditions) == 0 { + t.Error("Expected Conditions to be set") + } + }) + } +} + +func TestSyncRequest(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + // Create fake client and informers + request := &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + } + + fakeClient := fake.NewClientset(request) + informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + + // Create controller + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + t.Fatalf("Failed to create controller: %v", err) + } + + // Add request to informer + err = informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests().Informer().GetStore().Add(request) + if err != nil { + t.Fatalf("Failed to add request to informer: %v", err) + } + + // Add a slice + slice := makeSlice("slice-1", "test.example.com", "pool-1", "node-1", 4) + err = informerFactory.Resource().V1().ResourceSlices().Informer().GetStore().Add(slice) + if err != nil { + t.Fatalf("Failed to add slice to informer: %v", err) + } + + // Sync the request + err = controller.syncRequest(ctx, "test-request") + if err != nil { + t.Fatalf("syncRequest failed: %v", err) + } + + // Verify the status was updated (fake client should have received UpdateStatus call) + actions := fakeClient.Actions() + var foundUpdate bool + for _, action := range actions { + if action.GetVerb() == "update" && action.GetSubresource() == "status" { + foundUpdate = true + break + } + } + if !foundUpdate { + t.Error("Expected status update action, but none found") + } +} + +func TestSyncRequestRequeuesIncompletePool(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + request := &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + } + + fakeClient := fake.NewClientset(request) + informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + t.Fatalf("Failed to create controller: %v", err) + } + + // Add request to informer + err = informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests().Informer().GetStore().Add(request) + if err != nil { + t.Fatalf("Failed to add request to informer: %v", err) + } + + // Add an incomplete pool (expects 3 slices, only 1 published) + slice := makeSliceWithExpectedCount("slice-1", "test.example.com", "pool-1", "node-1", 4, 1, 3) + err = informerFactory.Resource().V1().ResourceSlices().Informer().GetStore().Add(slice) + if err != nil { + t.Fatalf("Failed to add slice to informer: %v", err) + } + + // syncRequest should always return an error for incomplete pools, + // letting processNextWorkItem handle retry counting and drop logic. + err = controller.syncRequest(ctx, "test-request") + if err == nil { + t.Fatal("Expected syncRequest to return error for incomplete pool requeue, got nil") + } + + // Verify no status update was made + for _, action := range fakeClient.Actions() { + if action.GetVerb() == "update" && action.GetSubresource() == "status" { + t.Error("Should not update status when requeueing for incomplete pools") + } + } + + // Even after retries are exhausted, syncRequest still returns an error; + // it is processNextWorkItem that decides to drop the key. + for range maxRetries { + controller.workqueue.AddRateLimited("test-request") + key, _ := controller.workqueue.Get() + controller.workqueue.Done(key) + } + + fakeClient.ClearActions() + err = controller.syncRequest(ctx, "test-request") + if err == nil { + t.Fatal("Expected syncRequest to still return error for incomplete pools after retries exhausted") + } +} + +func TestSkipProcessedRequest(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + now := metav1.Now() + // Create a request that's already been processed (Status is non-nil with conditions) + request := &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{Name: "test-request"}, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + Status: &resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(0)), + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: now, + Reason: "CalculationComplete", + Message: "Calculated status for 0 pools", + }, + }, + }, + } + + fakeClient := fake.NewClientset(request) + informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + t.Fatalf("Failed to create controller: %v", err) + } + + err = informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests().Informer().GetStore().Add(request) + if err != nil { + t.Fatalf("Failed to add request to informer: %v", err) + } + + // Sync should skip the already-processed request + err = controller.syncRequest(ctx, "test-request") + if err != nil { + t.Fatalf("syncRequest failed: %v", err) + } + + // Verify no update was made + actions := fakeClient.Actions() + for _, action := range actions { + if action.GetVerb() == "update" && action.GetSubresource() == "status" { + t.Error("Should not update status for already-processed request") + } + } +} + +// Helper functions + +func makeSlice(name, driver, pool, node string, deviceCount int) *resourcev1.ResourceSlice { + return makeSliceWithGenerationAndCount(name, driver, pool, node, deviceCount, 1, 1) +} + +func makeSliceWithExpectedCount(name, driver, pool, node string, deviceCount int, generation int64, resourceSliceCount int64) *resourcev1.ResourceSlice { + return makeSliceWithGenerationAndCount(name, driver, pool, node, deviceCount, generation, resourceSliceCount) +} + +func makeSliceWithGeneration(name, driver, pool, node string, deviceCount int, generation int64) *resourcev1.ResourceSlice { + return makeSliceWithGenerationAndCount(name, driver, pool, node, deviceCount, generation, 1) +} + +func makeSliceWithGenerationAndCount(name, driver, pool, node string, deviceCount int, generation int64, resourceSliceCount int64) *resourcev1.ResourceSlice { + devices := make([]resourcev1.Device, deviceCount) + for i := range deviceCount { + devices[i] = resourcev1.Device{ + Name: fmt.Sprintf("device-%d", i), + } + } + + return &resourcev1.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourcev1.ResourceSliceSpec{ + Driver: driver, + NodeName: &node, + Pool: resourcev1.ResourcePool{ + Name: pool, + Generation: generation, + ResourceSliceCount: resourceSliceCount, + }, + Devices: devices, + }, + } +} + +func makeAllocatedClaim(name, namespace, driver, pool, device string) *resourcev1.ResourceClaim { + return &resourcev1.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: resourcev1.ResourceClaimSpec{}, + Status: resourcev1.ResourceClaimStatus{ + Allocation: &resourcev1.AllocationResult{ + Devices: resourcev1.DeviceAllocationResult{ + Results: []resourcev1.DeviceRequestAllocationResult{ + { + Driver: driver, + Pool: pool, + Device: device, + }, + }, + }, + }, + }, + } +} + +func TestIsOlderThan(t *testing.T) { + testCases := []struct { + name string + time time.Time + duration time.Duration + expected bool + }{ + { + name: "zero-time-is-not-older", + time: time.Time{}, + duration: time.Hour, + expected: false, + }, + { + name: "recent-time-is-not-older", + time: time.Now().Add(-30 * time.Minute), + duration: time.Hour, + expected: false, + }, + { + name: "old-time-is-older", + time: time.Now().Add(-2 * time.Hour), + duration: time.Hour, + expected: true, + }, + { + name: "just-under-boundary-is-not-older", + time: time.Now().Add(-time.Hour + time.Minute), + duration: time.Hour, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := isOlderThan(tc.time, tc.duration) + if result != tc.expected { + t.Errorf("isOlderThan(%v, %v) = %v, expected %v", tc.time, tc.duration, result, tc.expected) + } + }) + } +} + +func TestShouldDeleteRequest(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + fakeClient := fake.NewClientset() + informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + t.Fatalf("Failed to create controller: %v", err) + } + + testCases := []struct { + name string + request *resourcev1alpha3.ResourcePoolStatusRequest + expectedDelete bool + }{ + { + name: "completed-request-not-expired", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Now(), + }, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + Status: &resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(0)), + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "CalculationComplete", + Message: "Calculated status for 0 pools", + }, + }, + }, + }, + expectedDelete: false, + }, + { + name: "completed-request-expired", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Hour)), + }, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + Status: &resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(0)), + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.NewTime(time.Now().Add(-2 * time.Hour)), + Reason: "CalculationComplete", + Message: "Calculated status for 0 pools", + }, + }, + }, + }, + expectedDelete: true, + }, + { + name: "pending-request-not-expired", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Now(), + }, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + // No status - request is pending + }, + expectedDelete: false, + }, + { + name: "pending-request-expired", + request: &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.NewTime(time.Now().Add(-25 * time.Hour)), + }, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + // No status - pending request expired + }, + expectedDelete: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := controller.shouldDeleteRequest(tc.request) + if result != tc.expectedDelete { + t.Errorf("shouldDeleteRequest() = %v, expected %v", result, tc.expectedDelete) + } + }) + } +} + +func TestCleanupExpiredRequests(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + // Create requests: one expired, one not expired + expiredRequest := &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "expired-request", + CreationTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Hour)), + }, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + Status: &resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(0)), + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.NewTime(time.Now().Add(-2 * time.Hour)), + Reason: "CalculationComplete", + Message: "Calculated status for 0 pools", + }, + }, + }, + } + + activeRequest := &resourcev1alpha3.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "active-request", + CreationTimestamp: metav1.Now(), + }, + Spec: resourcev1alpha3.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + }, + Status: &resourcev1alpha3.ResourcePoolStatusRequestStatus{ + PoolCount: new(int32(0)), + Conditions: []metav1.Condition{ + { + Type: resourcev1alpha3.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "CalculationComplete", + Message: "Calculated status for 0 pools", + }, + }, + }, + } + + fakeClient := fake.NewClientset(expiredRequest, activeRequest) + informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + t.Fatalf("Failed to create controller: %v", err) + } + + // Add requests to informer store + err = informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests().Informer().GetStore().Add(expiredRequest) + if err != nil { + t.Fatalf("Failed to add expired request to informer: %v", err) + } + err = informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests().Informer().GetStore().Add(activeRequest) + if err != nil { + t.Fatalf("Failed to add active request to informer: %v", err) + } + + // Run cleanup + controller.cleanupExpiredRequests(ctx) + + // Verify that delete was called for expired request + actions := fakeClient.Actions() + var deletedExpired, deletedActive bool + for _, action := range actions { + if action.GetVerb() == "delete" { + deleteAction, ok := action.(interface{ GetName() string }) + if ok { + if deleteAction.GetName() == "expired-request" { + deletedExpired = true + } + if deleteAction.GetName() == "active-request" { + deletedActive = true + } + } + } + } + + if !deletedExpired { + t.Error("Expected expired-request to be deleted") + } + if deletedActive { + t.Error("Did not expect active-request to be deleted") + } +} + +func makeSliceWithoutNode(name, driver, pool string, deviceCount int) *resourcev1.ResourceSlice { + devices := make([]resourcev1.Device, deviceCount) + for i := range deviceCount { + devices[i] = resourcev1.Device{ + Name: fmt.Sprintf("device-%d", i), + } + } + + return &resourcev1.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourcev1.ResourceSliceSpec{ + Driver: driver, + Pool: resourcev1.ResourcePool{ + Name: pool, + Generation: 1, + ResourceSliceCount: 1, + }, + Devices: devices, + }, + } +} + +// new is a generic helper to create a pointer to a value. +func new[T any](v T) *T { + return &v +} diff --git a/pkg/controller/resourcepoolstatusrequest/doc.go b/pkg/controller/resourcepoolstatusrequest/doc.go new file mode 100644 index 0000000000000..d9a6591e5192f --- /dev/null +++ b/pkg/controller/resourcepoolstatusrequest/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 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 resourcepoolstatusrequest contains the controller for processing +// ResourcePoolStatusRequest objects. The controller watches for new requests +// and calculates pool status information by aggregating data from ResourceSlices +// and tracking allocations from ResourceClaims. +package resourcepoolstatusrequest diff --git a/pkg/controller/resourcepoolstatusrequest/metrics/metrics.go b/pkg/controller/resourcepoolstatusrequest/metrics/metrics.go new file mode 100644 index 0000000000000..20022331df93d --- /dev/null +++ b/pkg/controller/resourcepoolstatusrequest/metrics/metrics.go @@ -0,0 +1,77 @@ +/* +Copyright 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 metrics + +import ( + "sync" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const ( + // Subsystem is the subsystem name used for metrics. + Subsystem = "resourcepoolstatusrequest_controller" +) + +var ( + // RequestsProcessed tracks the total number of ResourcePoolStatusRequests processed + // (both successes and failures). + RequestsProcessed = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: Subsystem, + Name: "requests_processed_total", + Help: "Total number of ResourcePoolStatusRequests processed", + StabilityLevel: metrics.ALPHA, + }, + []string{"driver_name"}, + ) + + // RequestProcessingErrors tracks the number of errors during request processing. + RequestProcessingErrors = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: Subsystem, + Name: "request_processing_errors_total", + Help: "Total number of errors encountered while processing ResourcePoolStatusRequests", + StabilityLevel: metrics.ALPHA, + }, + []string{"driver_name"}, + ) + + // RequestProcessingDuration tracks the time taken to process requests. + RequestProcessingDuration = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Subsystem: Subsystem, + Name: "request_processing_duration_seconds", + Help: "Time taken to process a ResourcePoolStatusRequest", + Buckets: metrics.ExponentialBuckets(0.001, 2, 15), + StabilityLevel: metrics.ALPHA, + }, + []string{"driver_name"}, + ) +) + +var registerOnce sync.Once + +// Register registers all metrics with the legacy registry. +func Register() { + registerOnce.Do(func() { + legacyregistry.MustRegister(RequestsProcessed) + legacyregistry.MustRegister(RequestProcessingErrors) + legacyregistry.MustRegister(RequestProcessingDuration) + }) +} diff --git a/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go b/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go new file mode 100644 index 0000000000000..5bba13ba2a129 --- /dev/null +++ b/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go @@ -0,0 +1,104 @@ +/* +Copyright 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 metrics + +import ( + "strings" + "testing" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/testutil" +) + +func TestRequestsProcessed(t *testing.T) { + registry := metrics.NewKubeRegistry() + defer registry.Reset() + registry.MustRegister(RequestsProcessed) + + RequestsProcessed.WithLabelValues("driver-a.example.com").Inc() + RequestsProcessed.WithLabelValues("driver-a.example.com").Inc() + RequestsProcessed.WithLabelValues("driver-b.example.com").Inc() + + want := `# HELP resourcepoolstatusrequest_controller_requests_processed_total [ALPHA] Total number of ResourcePoolStatusRequests processed +# TYPE resourcepoolstatusrequest_controller_requests_processed_total counter +resourcepoolstatusrequest_controller_requests_processed_total{driver_name="driver-a.example.com"} 2 +resourcepoolstatusrequest_controller_requests_processed_total{driver_name="driver-b.example.com"} 1 +` + if err := testutil.GatherAndCompare(registry, strings.NewReader(want), "resourcepoolstatusrequest_controller_requests_processed_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } +} + +func TestRequestProcessingErrors(t *testing.T) { + registry := metrics.NewKubeRegistry() + defer registry.Reset() + registry.MustRegister(RequestProcessingErrors) + + RequestProcessingErrors.WithLabelValues("driver-a.example.com").Inc() + + want := `# HELP resourcepoolstatusrequest_controller_request_processing_errors_total [ALPHA] Total number of errors encountered while processing ResourcePoolStatusRequests +# TYPE resourcepoolstatusrequest_controller_request_processing_errors_total counter +resourcepoolstatusrequest_controller_request_processing_errors_total{driver_name="driver-a.example.com"} 1 +` + if err := testutil.GatherAndCompare(registry, strings.NewReader(want), "resourcepoolstatusrequest_controller_request_processing_errors_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } +} + +func TestRequestProcessingDuration(t *testing.T) { + registry := metrics.NewKubeRegistry() + defer registry.Reset() + registry.MustRegister(RequestProcessingDuration) + + RequestProcessingDuration.WithLabelValues("driver-a.example.com").Observe(0.5) + RequestProcessingDuration.WithLabelValues("driver-b.example.com").Observe(1.0) + + // Use gatherWithoutDurations to strip timing-dependent histogram fields + // (SampleSum and Bucket), verifying only _count lines. + want := `# HELP resourcepoolstatusrequest_controller_request_processing_duration_seconds [ALPHA] Time taken to process a ResourcePoolStatusRequest +# TYPE resourcepoolstatusrequest_controller_request_processing_duration_seconds histogram +resourcepoolstatusrequest_controller_request_processing_duration_seconds_count{driver_name="driver-a.example.com"} 1 +resourcepoolstatusrequest_controller_request_processing_duration_seconds_count{driver_name="driver-b.example.com"} 1 +` + if err := testutil.GatherAndCompare(gatherWithoutDurations(registry), strings.NewReader(want), "resourcepoolstatusrequest_controller_request_processing_duration_seconds"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } +} + +// gatherWithoutDurations wraps a registry and strips timing-dependent fields +// (SampleSum and Bucket) from histograms so that tests only verify _count. +func gatherWithoutDurations(registry metrics.KubeRegistry) testutil.GathererFunc { + return func() ([]*testutil.MetricFamily, error) { + got, err := registry.Gather() + for _, mf := range got { + for _, m := range mf.Metric { + if m.Histogram == nil { + continue + } + m.Histogram.SampleSum = nil + m.Histogram.Bucket = nil + } + } + return got, err + } +} + +func TestRegister(t *testing.T) { + // Verify Register does not panic when called multiple times. + Register() + Register() +} diff --git a/pkg/controller/resourcequota/resource_quota_controller_test.go b/pkg/controller/resourcequota/resource_quota_controller_test.go index f58b7cdf27d02..9341ac4674de4 100644 --- a/pkg/controller/resourcequota/resource_quota_controller_test.go +++ b/pkg/controller/resourcequota/resource_quota_controller_test.go @@ -111,7 +111,10 @@ type quotaController struct { func setupQuotaController(t *testing.T, kubeClient kubernetes.Interface, lister quota.ListerForResourceFunc, discoveryFunc NamespacedResourcesFunc) quotaController { informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - quotaConfiguration := install.NewQuotaConfigurationForControllers(lister, informerFactory) + quotaConfiguration, err := install.NewQuotaConfigurationForControllers(lister, informerFactory) + if err != nil { + t.Fatal(err) + } alwaysStarted := make(chan struct{}) close(alwaysStarted) resourceQuotaControllerOptions := &ControllerOptions{ diff --git a/pkg/controller/resourcequota/resource_quota_monitor.go b/pkg/controller/resourcequota/resource_quota_monitor.go index 28aef8be53967..3dbffb3b38fdc 100644 --- a/pkg/controller/resourcequota/resource_quota_monitor.go +++ b/pkg/controller/resourcequota/resource_quota_monitor.go @@ -176,7 +176,7 @@ func (qm *QuotaMonitor) controllerFor(ctx context.Context, resource schema.Group shared.Informer().AddEventHandlerWithResyncPeriod(handlers, qm.resyncPeriod()) return shared.Informer().GetController(), nil } - logger.V(4).Error(err, "QuotaMonitor unable to use a shared informer", "resource", resource.String()) + logger.V(4).Info("QuotaMonitor unable to use a shared informer", "resource", resource.String(), "err", err) // TODO: if we can share storage with garbage collector, it may make sense to support other resources // until that time, aggregated api servers will have to run their own controller to reconcile their own quota. diff --git a/pkg/controller/scheduling/podgroupprotection/podgroup_protection_controller.go b/pkg/controller/scheduling/podgroupprotection/podgroup_protection_controller.go new file mode 100644 index 0000000000000..c7eadb7426f3b --- /dev/null +++ b/pkg/controller/scheduling/podgroupprotection/podgroup_protection_controller.go @@ -0,0 +1,339 @@ +/* +Copyright 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 podgroupprotection + +import ( + "context" + "fmt" + "sync" + "time" + + v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + schedulinginformers "k8s.io/client-go/informers/scheduling/v1alpha2" + clientset "k8s.io/client-go/kubernetes" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/controller/util/protectionutil" + "k8s.io/kubernetes/pkg/util/slice" +) + +const ( + // The index name for looking up active pods by their + // schedulingGroup.podGroupName field. + activePodSchedulingGroupIndex = "activePodSchedulingGroup" +) + +// Controller manages the PodGroupProtectionFinalizer on PodGroup objects. +// The finalizer is stamped at creation time by the PodGroupProtection admission +// plugin; this controller removes it when the PodGroup is being deleted and no +// active (non-terminated) pods still reference it. +type Controller struct { + kubeClient clientset.Interface + + podGroupLister schedulinglisters.PodGroupLister + podGroupSynced cache.InformerSynced + + podSynced cache.InformerSynced + + // podIndexer has the common Pod indexer installed to + // limit iteration over pods to those of interest. + podIndexer cache.Indexer + + queue workqueue.TypedRateLimitingInterface[string] +} + +// NewPodGroupProtectionController returns a new instance of the PodGroup protection controller. +func NewPodGroupProtectionController( + logger klog.Logger, + podGroupInformer schedulinginformers.PodGroupInformer, + podInformer coreinformers.PodInformer, + kubeClient clientset.Interface, +) (*Controller, error) { + c := &Controller{ + kubeClient: kubeClient, + podGroupLister: podGroupInformer.Lister(), + podGroupSynced: podGroupInformer.Informer().HasSynced, + podIndexer: podInformer.Informer().GetIndexer(), + podSynced: podInformer.Informer().HasSynced, + queue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[string](), + workqueue.TypedRateLimitingQueueConfig[string]{Name: "podgroupprotection"}, + ), + } + + if _, err := podGroupInformer.Informer().AddEventHandlerWithOptions(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.handlePodGroupUpdate(logger, obj) + }, + UpdateFunc: func(old, new interface{}) { + c.handlePodGroupUpdate(logger, new) + }, + }, cache.HandlerOptions{Logger: &logger}); err != nil { + return nil, err + } + + if err := addActivePodSchedulingGroupIndexer(c.podIndexer); err != nil { + return nil, fmt.Errorf("could not initialize PodGroup protection controller: %w", err) + } + + if _, err := podInformer.Informer().AddEventHandlerWithOptions(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.handlePodChange(logger, nil, obj) + }, + DeleteFunc: func(obj interface{}) { + c.handlePodChange(logger, obj, nil) + }, + UpdateFunc: func(old, new interface{}) { + c.handlePodChange(logger, old, new) + }, + }, cache.HandlerOptions{Logger: &logger}); err != nil { + return nil, err + } + + return c, nil +} + +// addActivePodSchedulingGroupIndexer adds an indexer to look up active +// pods by their schedulingGroup.podGroupName field so we can efficiently +// determine whether a PodGroup still has active pods. +func addActivePodSchedulingGroupIndexer(indexer cache.Indexer) error { + return indexer.AddIndexers(cache.Indexers{ + activePodSchedulingGroupIndex: func(obj interface{}) ([]string, error) { + pod, ok := obj.(*v1.Pod) + if !ok { + return nil, nil + } + if isPodTerminated(pod) { + return nil, nil + } + if pod.Spec.SchedulingGroup == nil || pod.Spec.SchedulingGroup.PodGroupName == nil { + return nil, nil + } + return []string{pod.Namespace + "/" + *pod.Spec.SchedulingGroup.PodGroupName}, nil + }, + }) +} + +// Run runs the controller goroutines. +func (c *Controller) Run(ctx context.Context, workers int) { + defer utilruntime.HandleCrash() + + logger := klog.FromContext(ctx) + logger.Info("Starting PodGroup protection controller") + + var wg sync.WaitGroup + defer func() { + logger.Info("Shutting down PodGroup protection controller") + c.queue.ShutDown() + wg.Wait() + }() + + if !cache.WaitForNamedCacheSyncWithContext(ctx, c.podGroupSynced, c.podSynced) { + return + } + + for range workers { + wg.Go(func() { + wait.UntilWithContext(ctx, c.runWorker, time.Second) + }) + } + <-ctx.Done() +} + +func (c *Controller) runWorker(ctx context.Context) { + for c.processNextWorkItem(ctx) { + } +} + +func (c *Controller) processNextWorkItem(ctx context.Context) bool { + pgKey, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(pgKey) + + err := c.processPodGroup(ctx, pgKey) + if err == nil { + c.queue.Forget(pgKey) + return true + } + + c.queue.AddRateLimited(pgKey) + utilruntime.HandleError(fmt.Errorf("PodGroup %v failed with: %w", pgKey, err)) + + return true +} + +func (c *Controller) processPodGroup(ctx context.Context, pgKey string) error { + logger := klog.FromContext(ctx) + logger.V(4).Info("Processing PodGroup", "podGroup", pgKey) + + pgNamespace, pgName, err := cache.SplitMetaNamespaceKey(pgKey) + if err != nil { + return fmt.Errorf("error parsing PodGroup key %q: %w", pgKey, err) + } + + pg, err := c.podGroupLister.PodGroups(pgNamespace).Get(pgName) + if apierrors.IsNotFound(err) { + logger.V(4).Info("PodGroup not found, ignoring", "podGroup", pgKey) + return nil + } + if err != nil { + return err + } + + if !protectionutil.IsDeletionCandidate(pg, scheduling.PodGroupProtectionFinalizer) { + return nil + } + + isUsed, err := c.hasActivePods(ctx, pg) + if err != nil { + return err + } + if !isUsed { + return c.removeFinalizer(ctx, pg) + } + logger.V(4).Info("Keeping PodGroup finalizer because it is still being used by pods", "podGroup", klog.KObj(pg)) + return nil +} + +func (c *Controller) removeFinalizer(ctx context.Context, pg *schedulingv1alpha2.PodGroup) error { + logger := klog.FromContext(ctx) + pgClone := pg.DeepCopy() + + pgClone.Finalizers = slice.RemoveString(pgClone.Finalizers, scheduling.PodGroupProtectionFinalizer, nil) + _, err := c.kubeClient.SchedulingV1alpha2().PodGroups(pgClone.Namespace).Update(ctx, pgClone, metav1.UpdateOptions{}) + if err != nil { + logger.Error(err, "Error removing protection finalizer from PodGroup", "podGroup", klog.KObj(pg)) + return err + } + + logger.V(3).Info("Removed protection finalizer from PodGroup", "podGroup", klog.KObj(pg)) + return nil +} + +// hasActivePods returns true if any active pods reference the PodGroup +// via spec.schedulingGroup.podGroupName. The index only contains +// non-terminated pods, so a non-empty result means the PodGroup is still in use. +func (c *Controller) hasActivePods(ctx context.Context, pg *schedulingv1alpha2.PodGroup) (bool, error) { + logger := klog.FromContext(ctx) + indexKey := pg.Namespace + "/" + pg.Name + + objs, err := c.podIndexer.ByIndex(activePodSchedulingGroupIndex, indexKey) + if err != nil { + return false, fmt.Errorf("index-based list of active pods failed for PodGroup %s: %w", indexKey, err) + } + + if len(objs) > 0 { + logger.V(4).Info("Pod is using PodGroup", "pod", klog.KObj(objs[0].(*v1.Pod)), "podGroup", klog.KObj(pg)) + return true, nil + } + + logger.V(4).Info("No active pods found using PodGroup", "podGroup", klog.KObj(pg)) + return false, nil +} + +// isPodTerminated returns true if the pod has completed (Succeeded or Failed). +func isPodTerminated(pod *v1.Pod) bool { + return pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed +} + +// handlePodGroupUpdate handles PodGroup add/update events. +// Only deletion candidates which are being deleted and have the finalizer need processing. +func (c *Controller) handlePodGroupUpdate(logger klog.Logger, obj interface{}) { + pg, ok := obj.(*schedulingv1alpha2.PodGroup) + if !ok { + utilruntime.HandleError(fmt.Errorf("PodGroup informer returned non-PodGroup object: %#v", obj)) + return + } + if !protectionutil.IsDeletionCandidate(pg, scheduling.PodGroupProtectionFinalizer) { + return + } + key, err := cache.MetaNamespaceKeyFunc(pg) + if err != nil { + utilruntime.HandleError(fmt.Errorf("couldn't get key for PodGroup %#v: %w", pg, err)) + return + } + logger.V(4).Info("Got event on PodGroup", "podGroup", klog.KObj(pg)) + c.queue.Add(key) +} + +// handlePodChange handles Pod add/delete/update events. +// It enqueues the referenced PodGroup only when the event could affect +// finalizer decisions where the pod is deleted or transitioned to a terminal phase. +func (c *Controller) handlePodChange(logger klog.Logger, old, new interface{}) { + newPod := getPod(new) + oldPod := getPod(old) + + if newPod != nil && isPodTerminated(newPod) { + c.enqueuePodGroupForPod(logger, newPod) + } + + // An update notification might mask the deletion of a pod X and the + // following creation of a pod Y with the same namespaced name as X. If + // that's the case, X needs to be processed as well to handle the case + // where it was the last active pod keeping the finalizer on a PodGroup. + if newPod != nil && oldPod != nil && oldPod.UID != newPod.UID { + c.enqueuePodGroupForPod(logger, oldPod) + } + + if newPod == nil && oldPod != nil { + c.enqueuePodGroupForPod(logger, oldPod) + } +} + +// enqueuePodGroupForPod enqueues the PodGroup referenced by the pod. +// Callers are responsible for only passing pods whose state change could allow +// finalizer removal (deleted or transitioned to a terminal phase). +func (c *Controller) enqueuePodGroupForPod(logger klog.Logger, pod *v1.Pod) { + if pod.Spec.SchedulingGroup == nil || pod.Spec.SchedulingGroup.PodGroupName == nil { + return + } + + pgKey := pod.Namespace + "/" + *pod.Spec.SchedulingGroup.PodGroupName + logger.V(4).Info("Enqueuing PodGroup for pod event", "pod", klog.KObj(pod), "podGroup", pgKey) + c.queue.Add(pgKey) +} + +func getPod(obj interface{}) *v1.Pod { + if obj == nil { + return nil + } + pod, ok := obj.(*v1.Pod) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj)) + return nil + } + pod, ok = tombstone.Obj.(*v1.Pod) + if !ok { + utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a Pod %#v", obj)) + return nil + } + } + return pod +} diff --git a/pkg/controller/scheduling/podgroupprotection/podgroup_protection_controller_test.go b/pkg/controller/scheduling/podgroupprotection/podgroup_protection_controller_test.go new file mode 100644 index 0000000000000..e727a986a9933 --- /dev/null +++ b/pkg/controller/scheduling/podgroupprotection/podgroup_protection_controller_test.go @@ -0,0 +1,551 @@ +/* +Copyright 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 podgroupprotection + +import ( + "context" + "slices" + "testing" + "time" + + v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/utils/ptr" +) + +const ( + defaultNS = "default" + defaultPGName = "my-podgroup" + defaultPGUID = "pg-uid-1" +) + +func podGroup() *schedulingv1alpha2.PodGroup { + return &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultPGName, + Namespace: defaultNS, + UID: defaultPGUID, + }, + } +} + +func withFinalizer(pg *schedulingv1alpha2.PodGroup) *schedulingv1alpha2.PodGroup { + pg.Finalizers = append(pg.Finalizers, scheduling.PodGroupProtectionFinalizer) + return pg +} + +func deletedPodGroup(pg *schedulingv1alpha2.PodGroup) *schedulingv1alpha2.PodGroup { + pg.DeletionTimestamp = &metav1.Time{} + return pg +} + +func podForPG(name string, pgName string) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: defaultNS, + UID: types.UID(name + "-uid"), + }, + Spec: v1.PodSpec{ + NodeName: "node-1", + SchedulingGroup: &v1.PodSchedulingGroup{ + PodGroupName: ptr.To(pgName), + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + } +} + +func terminatedPod(pod *v1.Pod, phase v1.PodPhase) *v1.Pod { + pod.Status.Phase = phase + return pod +} + +func unscheduledPod(pod *v1.Pod) *v1.Pod { + pod.Spec.NodeName = "" + return pod +} + +func podWithoutSchedulingGroup(name string) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: defaultNS, + UID: types.UID(name + "-uid"), + }, + Spec: v1.PodSpec{ + NodeName: "node-1", + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + } +} + +func TestIsPodTerminated(t *testing.T) { + tests := map[string]struct { + phase v1.PodPhase + want bool + }{ + "running": {phase: v1.PodRunning, want: false}, + "pending": {phase: v1.PodPending, want: false}, + "succeeded": {phase: v1.PodSucceeded, want: true}, + "failed": {phase: v1.PodFailed, want: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + pod := &v1.Pod{Status: v1.PodStatus{Phase: tc.phase}} + if got := isPodTerminated(pod); got != tc.want { + t.Errorf("isPodTerminated(%v) = %v, want %v", tc.phase, got, tc.want) + } + }) + } +} + +func TestGetPod(t *testing.T) { + tests := map[string]struct { + obj interface{} + want bool + }{ + "nil": { + obj: nil, + want: false, + }, + "pod": { + obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "p"}}, + want: true, + }, + "tombstone with pod": { + obj: cache.DeletedFinalStateUnknown{ + Key: "default/p", + Obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "p"}}, + }, + want: true, + }, + "tombstone with non-pod": { + obj: cache.DeletedFinalStateUnknown{ + Key: "default/p", + Obj: &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "p"}}, + }, + want: false, + }, + "non-pod object": { + obj: &v1.ConfigMap{}, + want: false, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := getPod(tc.obj) + if (got != nil) != tc.want { + t.Errorf("parsePod() returned pod=%v, want non-nil=%v", got, tc.want) + } + }) + } +} + +func TestHandlePodChange(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + + tests := map[string]struct { + old interface{} + new interface{} + wantSize int + }{ + "deleted pod with schedulingGroup enqueues": { + old: podForPG("pod-1", defaultPGName), + new: nil, + wantSize: 1, + }, + "terminated pod enqueues": { + old: podForPG("pod-1", defaultPGName), + new: terminatedPod(podForPG("pod-1", defaultPGName), v1.PodSucceeded), + wantSize: 1, + }, + "unscheduled new pod does not enqueue": { + old: nil, + new: unscheduledPod(podForPG("pod-1", defaultPGName)), + wantSize: 0, + }, + "scheduled running pod does not enqueue": { + old: nil, + new: podForPG("pod-1", defaultPGName), + wantSize: 0, + }, + "deleted pod without schedulingGroup does not enqueue": { + old: podWithoutSchedulingGroup("pod-1"), + new: nil, + wantSize: 0, + }, + "deleted pod with nil podGroupName does not enqueue": { + old: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: defaultNS}, + Spec: v1.PodSpec{ + SchedulingGroup: &v1.PodSchedulingGroup{PodGroupName: nil}, + }, + }, + new: nil, + wantSize: 0, + }, + "UID mismatch with terminated new pod, same PodGroup, deduplicates enqueue": { + old: func() *v1.Pod { + p := podForPG("pod-1", defaultPGName) + p.UID = "old-uid" + return p + }(), + new: func() interface{} { + p := terminatedPod(podForPG("pod-1", defaultPGName), v1.PodSucceeded) + p.UID = "new-uid" + return p + }(), + wantSize: 1, + }, + "UID mismatch with terminated new pod referencing different PodGroup enqueues both": { + old: func() *v1.Pod { + p := podForPG("pod-1", defaultPGName) + p.UID = "old-uid" + return p + }(), + new: func() interface{} { + p := terminatedPod(podForPG("pod-1", "other-pg"), v1.PodFailed) + p.UID = "new-uid" + return p + }(), + wantSize: 2, + }, + "UID mismatch on update with non-terminated new pod enqueues only old pod": { + old: func() *v1.Pod { + p := podForPG("pod-1", defaultPGName) + p.UID = "old-uid" + return p + }(), + new: func() interface{} { + p := unscheduledPod(podForPG("pod-1", defaultPGName)) + p.UID = "new-uid" + return p + }(), + wantSize: 1, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + c := &Controller{ + queue: workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[string]()), + } + defer c.queue.ShutDown() + c.handlePodChange(logger, tc.old, tc.new) + + if c.queue.Len() != tc.wantSize { + t.Errorf("queue size = %d, want %d", c.queue.Len(), tc.wantSize) + } + }) + } +} + +func TestPodGroupProtectionController(t *testing.T) { + tests := []struct { + name string + // Objects to seed into the fake client before the controller starts. + initialObjects []runtime.Object + // Pod to delete (by name in defaultNS) after the controller starts. + podToDelete string + // Whether the finalizer should be present on the PodGroup after the + // controller has finished processing. + expectFinalizer bool + }{ + { + name: "new PodGroup without finalizer, no action (admission plugin handles it)", + initialObjects: []runtime.Object{podGroup()}, + expectFinalizer: false, + }, + { + name: "new PodGroup without finalizer, active pod exists, then no action (admission plugin handles it)", + initialObjects: []runtime.Object{podGroup(), podForPG("pod-1", defaultPGName)}, + expectFinalizer: false, + }, + { + name: "PodGroup with finalizer, not being deleted, then no action", + initialObjects: []runtime.Object{withFinalizer(podGroup())}, + expectFinalizer: true, + }, + { + name: "deleted PodGroup with finalizer, no active pods, then finalizer is removed", + initialObjects: []runtime.Object{deletedPodGroup(withFinalizer(podGroup()))}, + expectFinalizer: false, + }, + { + name: "deleted PodGroup with finalizer, active pod exists, then finalizer is kept", + initialObjects: []runtime.Object{deletedPodGroup(withFinalizer(podGroup())), podForPG("pod-1", defaultPGName)}, + expectFinalizer: true, + }, + { + name: "deleted PodGroup with finalizer, only terminated pods, then finalizer is removed", + initialObjects: []runtime.Object{deletedPodGroup(withFinalizer(podGroup())), terminatedPod(podForPG("pod-1", defaultPGName), v1.PodSucceeded)}, + expectFinalizer: false, + }, + { + name: "deleted PodGroup with finalizer, mix of active and terminated pods, then finalizer is kept", + initialObjects: []runtime.Object{ + deletedPodGroup(withFinalizer(podGroup())), + podForPG("pod-active", defaultPGName), + terminatedPod(podForPG("pod-done", defaultPGName), v1.PodSucceeded), + }, + expectFinalizer: true, + }, + { + name: "PodGroup without finalizer, already deleted, then no action (not a deletion candidate)", + initialObjects: []runtime.Object{deletedPodGroup(podGroup())}, + expectFinalizer: false, + }, + { + name: "PodGroup without finalizer, already deleted, active pods exist, then no action (should not add finalizer to deleting object)", + initialObjects: []runtime.Object{deletedPodGroup(podGroup()), podForPG("pod-1", defaultPGName)}, + expectFinalizer: false, + }, + { + name: "pod deleted, PodGroup being deleted with finalizer, was last active pod, finalizer is removed", + initialObjects: []runtime.Object{deletedPodGroup(withFinalizer(podGroup())), podForPG("pod-1", defaultPGName)}, + podToDelete: "pod-1", + expectFinalizer: false, + }, + { + name: "pod terminated succeeded, PodGroup being deleted with finalizer, was last active pod, then finalizer is removed", + initialObjects: []runtime.Object{deletedPodGroup(withFinalizer(podGroup())), terminatedPod(podForPG("pod-1", defaultPGName), v1.PodSucceeded)}, + expectFinalizer: false, + }, + { + name: "pod terminated failed, PodGroup being deleted with finalizer, was last active pod, then finalizer is removed", + initialObjects: []runtime.Object{deletedPodGroup(withFinalizer(podGroup())), terminatedPod(podForPG("pod-1", defaultPGName), v1.PodFailed)}, + expectFinalizer: false, + }, + { + name: "new unscheduled pod, PodGroup without finalizer, then no action (admission plugin handles it)", + initialObjects: []runtime.Object{podGroup(), unscheduledPod(podForPG("pod-1", defaultPGName))}, + expectFinalizer: false, + }, + { + name: "pod without schedulingGroup -> no PodGroup action", + initialObjects: []runtime.Object{withFinalizer(podGroup()), podWithoutSchedulingGroup("pod-1")}, + expectFinalizer: true, + }, + { + name: "terminated pod references non-existent PodGroup, controller handles gracefully", + initialObjects: []runtime.Object{terminatedPod(podForPG("pod-1", defaultPGName), v1.PodSucceeded)}, + expectFinalizer: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + + client := fake.NewClientset(test.initialObjects...) + informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) + pgInformer := informerFactory.Scheduling().V1alpha2().PodGroups() + podInformer := informerFactory.Core().V1().Pods() + + ctrl, err := NewPodGroupProtectionController(klog.FromContext(ctx), pgInformer, podInformer, client) + if err != nil { + t.Fatalf("unexpected error creating controller: %v", err) + } + + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + go ctrl.Run(ctx, 1) + + if test.podToDelete != "" { + if err := client.CoreV1().Pods(defaultNS).Delete(ctx, test.podToDelete, metav1.DeleteOptions{}); err != nil { + t.Fatalf("deleting pod: %v", err) + } + } + + if err := wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) { + pg, err := client.SchedulingV1alpha2().PodGroups(defaultNS).Get(ctx, defaultPGName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return !test.expectFinalizer, nil + } + if err != nil { + return false, err + } + hasFinalizer := slices.Contains(pg.Finalizers, scheduling.PodGroupProtectionFinalizer) + return hasFinalizer == test.expectFinalizer, nil + }); err != nil { + t.Fatalf("timed out waiting for expected finalizer state (want present=%v): %v", test.expectFinalizer, err) + } + }) + } +} + +func TestActivePodSchedulingGroupIndexer(t *testing.T) { + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + if err := addActivePodSchedulingGroupIndexer(indexer); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pod1 := podForPG("pod-1", "pg-a") + _ = indexer.Add(pod1) + + pod2 := podWithoutSchedulingGroup("pod-2") + _ = indexer.Add(pod2) + + pod3 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-3", Namespace: defaultNS, UID: "pod-3-uid"}, + Spec: v1.PodSpec{ + SchedulingGroup: &v1.PodSchedulingGroup{PodGroupName: nil}, + }, + } + _ = indexer.Add(pod3) + + // Terminated pod should not appear in the index. + pod4 := terminatedPod(podForPG("pod-4", "pg-a"), v1.PodSucceeded) + _ = indexer.Add(pod4) + + objs, err := indexer.ByIndex(activePodSchedulingGroupIndex, defaultNS+"/pg-a") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(objs) != 1 { + t.Fatalf("expected 1 active pod for pg-a, got %d", len(objs)) + } + if objs[0].(*v1.Pod).Name != "pod-1" { + t.Errorf("expected pod-1, got %s", objs[0].(*v1.Pod).Name) + } + + // Nonexistent PodGroup should return empty. + objs, err = indexer.ByIndex(activePodSchedulingGroupIndex, defaultNS+"/nonexistent") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(objs) != 0 { + t.Errorf("expected 0 pods for nonexistent PodGroup, got %d", len(objs)) + } +} + +func TestHasActivePods(t *testing.T) { + tests := map[string]struct { + pods []runtime.Object + want bool + }{ + "no pods": { + pods: nil, + want: false, + }, + "active pod referencing PodGroup": { + pods: []runtime.Object{ + podForPG("pod-1", defaultPGName), + }, + want: true, + }, + "only terminated pods": { + pods: []runtime.Object{ + terminatedPod(podForPG("pod-1", defaultPGName), v1.PodSucceeded), + terminatedPod(podForPG("pod-2", defaultPGName), v1.PodFailed), + }, + want: false, + }, + "mix of active and terminated": { + pods: []runtime.Object{ + podForPG("pod-active", defaultPGName), + terminatedPod(podForPG("pod-done", defaultPGName), v1.PodSucceeded), + }, + want: true, + }, + "pods referencing different PodGroup": { + pods: []runtime.Object{ + podForPG("pod-1", "other-pg"), + }, + want: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + if err := addActivePodSchedulingGroupIndexer(indexer); err != nil { + t.Fatalf("unexpected error: %v", err) + } + for _, obj := range tc.pods { + _ = indexer.Add(obj) + } + + ctrl := &Controller{ + podIndexer: indexer, + } + pg := podGroup() + got, err := ctrl.hasActivePods(context.Background(), pg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tc.want { + t.Errorf("hasActivePods() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestHandlePodGroupUpdate(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + + tests := map[string]struct { + pg *schedulingv1alpha2.PodGroup + wantSize int + }{ + "PodGroup without finalizer, not deleting/not enqueued": { + pg: podGroup(), + wantSize: 0, + }, + "PodGroup is deletion candidate -> enqueued": { + pg: deletedPodGroup(withFinalizer(podGroup())), + wantSize: 1, + }, + "PodGroup has finalizer, not deleting -> not enqueued": { + pg: withFinalizer(podGroup()), + wantSize: 0, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + c := &Controller{ + queue: workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[string]()), + } + defer c.queue.ShutDown() + c.handlePodGroupUpdate(logger, tc.pg) + if c.queue.Len() != tc.wantSize { + t.Errorf("queue size = %d, want %d", c.queue.Len(), tc.wantSize) + } + }) + } +} diff --git a/pkg/controller/serviceaccount/legacy_serviceaccount_token_cleaner_test.go b/pkg/controller/serviceaccount/legacy_serviceaccount_token_cleaner_test.go index ccc9ea4b4e311..a7ce0eb99a9d0 100644 --- a/pkg/controller/serviceaccount/legacy_serviceaccount_token_cleaner_test.go +++ b/pkg/controller/serviceaccount/legacy_serviceaccount_token_cleaner_test.go @@ -17,7 +17,6 @@ limitations under the License. package serviceaccount import ( - "context" "encoding/json" "reflect" "testing" @@ -396,7 +395,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) { } secrets.Add(tc.ExistingSecret) - ctx := context.TODO() + ctx := t.Context() cleaner.evaluateSATokens(ctx) actions := client.Actions() diff --git a/pkg/controller/serviceaccount/serviceaccounts_controller_test.go b/pkg/controller/serviceaccount/serviceaccounts_controller_test.go index 3057418a5acc9..c6b19eb57364c 100644 --- a/pkg/controller/serviceaccount/serviceaccounts_controller_test.go +++ b/pkg/controller/serviceaccount/serviceaccounts_controller_test.go @@ -168,8 +168,9 @@ func TestServiceAccountCreation(t *testing.T) { var wg sync.WaitGroup defer wg.Wait() - logger, ctx := ktesting.NewTestContext(t) - defer ctx.Cancel("test case terminating") + tCtx := ktesting.Init(t) + logger := tCtx.Logger() + defer tCtx.Cancel("test case terminating") controller, err := NewServiceAccountsController( logger, @@ -200,7 +201,7 @@ func TestServiceAccountCreation(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) wg.Go(func() { - controller.Run(ctx, 1) + controller.Run(tCtx, 1) }) if tc.ExistingNamespace != nil { diff --git a/pkg/controller/serviceaccount/tokengetter.go b/pkg/controller/serviceaccount/tokengetter.go index 98afde0b727e4..1c17231f76dde 100644 --- a/pkg/controller/serviceaccount/tokengetter.go +++ b/pkg/controller/serviceaccount/tokengetter.go @@ -18,7 +18,8 @@ package serviceaccount import ( "context" - "k8s.io/api/core/v1" + + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" @@ -43,28 +44,28 @@ func NewGetterFromClient(c clientset.Interface, secretLister v1listers.SecretLis return clientGetter{c, secretLister, serviceAccountLister, podLister, nodeLister} } -func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) { +func (c clientGetter) GetServiceAccount(ctx context.Context, namespace, name string) (*v1.ServiceAccount, error) { if serviceAccount, err := c.serviceAccountLister.ServiceAccounts(namespace).Get(name); err == nil { return serviceAccount, nil } - return c.client.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + return c.client.CoreV1().ServiceAccounts(namespace).Get(ctx, name, metav1.GetOptions{}) } -func (c clientGetter) GetPod(namespace, name string) (*v1.Pod, error) { +func (c clientGetter) GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) { if pod, err := c.podLister.Pods(namespace).Get(name); err == nil { return pod, nil } - return c.client.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + return c.client.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) } -func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) { +func (c clientGetter) GetSecret(ctx context.Context, namespace, name string) (*v1.Secret, error) { if secret, err := c.secretLister.Secrets(namespace).Get(name); err == nil { return secret, nil } - return c.client.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + return c.client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) } -func (c clientGetter) GetNode(name string) (*v1.Node, error) { +func (c clientGetter) GetNode(ctx context.Context, name string) (*v1.Node, error) { // handle the case where the node lister isn't set due to feature being disabled if c.nodeLister == nil { return nil, apierrors.NewNotFound(v1.Resource("nodes"), name) @@ -72,5 +73,5 @@ func (c clientGetter) GetNode(name string) (*v1.Node, error) { if node, err := c.nodeLister.Get(name); err == nil { return node, nil } - return c.client.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{}) + return c.client.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{}) } diff --git a/pkg/controller/serviceaccount/tokens_controller.go b/pkg/controller/serviceaccount/tokens_controller.go index 60f69d1aa1e87..7ae288cca274f 100644 --- a/pkg/controller/serviceaccount/tokens_controller.go +++ b/pkg/controller/serviceaccount/tokens_controller.go @@ -199,9 +199,14 @@ func (e *TokensController) Run(ctx context.Context, workers int) { } func (e *TokensController) queueServiceAccountSync(obj interface{}) { + if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { + obj = tombstone.Obj + } if serviceAccount, ok := obj.(*v1.ServiceAccount); ok { e.syncServiceAccountQueue.Add(makeServiceAccountKey(serviceAccount)) + return } + utilruntime.HandleError(fmt.Errorf("error decoding object, invalid type: %T", obj)) } func (e *TokensController) queueServiceAccountUpdateSync(oldObj interface{}, newObj interface{}) { @@ -258,7 +263,7 @@ func (e *TokensController) syncServiceAccount(ctx context.Context) { return } - sa, err := e.getServiceAccount(saInfo.namespace, saInfo.name, saInfo.uid, false) + sa, err := e.getServiceAccount(ctx, saInfo.namespace, saInfo.name, saInfo.uid, false) switch { case err != nil: logger.Error(err, "Getting service account") @@ -267,7 +272,7 @@ func (e *TokensController) syncServiceAccount(ctx context.Context) { // service account no longer exists, so delete related tokens logger.V(4).Info("Service account deleted, removing tokens", "namespace", saInfo.namespace, "serviceaccount", saInfo.name) sa = &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: saInfo.namespace, Name: saInfo.name, UID: saInfo.uid}} - retry, err = e.deleteTokens(sa) + retry, err = e.deleteTokens(ctx, sa) if err != nil { logger.Error(err, "Error deleting serviceaccount tokens", "namespace", saInfo.namespace, "serviceaccount", saInfo.name) } @@ -294,24 +299,24 @@ func (e *TokensController) syncSecret(ctx context.Context) { return } - secret, err := e.getSecret(secretInfo.namespace, secretInfo.name, secretInfo.uid) + secret, err := e.getSecret(ctx, secretInfo.namespace, secretInfo.name, secretInfo.uid) switch { case err != nil: logger.Error(err, "Getting secret") retry = true case secret == nil: // If the service account exists - if sa, saErr := e.getServiceAccount(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, false); saErr == nil && sa != nil { + if sa, saErr := e.getServiceAccount(ctx, secretInfo.namespace, secretInfo.saName, secretInfo.saUID, false); saErr == nil && sa != nil { // secret no longer exists, so delete references to this secret from the service account if err := clientretry.RetryOnConflict(RemoveTokenBackoff, func() error { - return e.removeSecretReference(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, secretInfo.name) + return e.removeSecretReference(ctx, secretInfo.namespace, secretInfo.saName, secretInfo.saUID, secretInfo.name) }); err != nil { logger.Error(err, "Removing secret reference") } } default: // Ensure service account exists - sa, saErr := e.getServiceAccount(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, true) + sa, saErr := e.getServiceAccount(ctx, secretInfo.namespace, secretInfo.saName, secretInfo.saUID, true) switch { case saErr != nil: logger.Error(saErr, "Getting service account") @@ -319,13 +324,13 @@ func (e *TokensController) syncSecret(ctx context.Context) { case sa == nil: // Delete token logger.V(4).Info("Service account does not exist, deleting token", "secret", klog.KRef(secretInfo.namespace, secretInfo.name)) - if retriable, err := e.deleteToken(secretInfo.namespace, secretInfo.name, secretInfo.uid); err != nil { + if retriable, err := e.deleteToken(ctx, secretInfo.namespace, secretInfo.name, secretInfo.uid); err != nil { logger.Error(err, "Deleting serviceaccount token", "secret", klog.KRef(secretInfo.namespace, secretInfo.name), "serviceAccount", klog.KRef(secretInfo.namespace, secretInfo.saName)) retry = retriable } default: // Update token if needed - if retriable, err := e.generateTokenIfNeeded(logger, sa, secret); err != nil { + if retriable, err := e.generateTokenIfNeeded(ctx, sa, secret); err != nil { logger.Error(err, "Populating serviceaccount token", "secret", klog.KRef(secretInfo.namespace, secretInfo.name), "serviceAccount", klog.KRef(secretInfo.namespace, secretInfo.saName)) retry = retriable } @@ -333,7 +338,7 @@ func (e *TokensController) syncSecret(ctx context.Context) { } } -func (e *TokensController) deleteTokens(serviceAccount *v1.ServiceAccount) ( /*retry*/ bool, error) { +func (e *TokensController) deleteTokens(ctx context.Context, serviceAccount *v1.ServiceAccount) ( /*retry*/ bool, error) { tokens, err := e.listTokenSecrets(serviceAccount) if err != nil { // don't retry on cache lookup errors @@ -342,7 +347,7 @@ func (e *TokensController) deleteTokens(serviceAccount *v1.ServiceAccount) ( /*r retry := false errs := []error{} for _, token := range tokens { - r, err := e.deleteToken(token.Namespace, token.Name, token.UID) + r, err := e.deleteToken(ctx, token.Namespace, token.Name, token.UID) if err != nil { errs = append(errs, err) } @@ -353,12 +358,12 @@ func (e *TokensController) deleteTokens(serviceAccount *v1.ServiceAccount) ( /*r return retry, utilerrors.NewAggregate(errs) } -func (e *TokensController) deleteToken(ns, name string, uid types.UID) ( /*retry*/ bool, error) { +func (e *TokensController) deleteToken(ctx context.Context, ns, name string, uid types.UID) ( /*retry*/ bool, error) { var opts metav1.DeleteOptions if len(uid) > 0 { opts.Preconditions = &metav1.Preconditions{UID: &uid} } - err := e.client.CoreV1().Secrets(ns).Delete(context.TODO(), name, opts) + err := e.client.CoreV1().Secrets(ns).Delete(ctx, name, opts) // NotFound doesn't need a retry (it's already been deleted) // Conflict doesn't need a retry (the UID precondition failed) if err == nil || apierrors.IsNotFound(err) || apierrors.IsConflict(err) { @@ -382,7 +387,7 @@ func (e *TokensController) secretUpdateNeeded(secret *v1.Secret) (bool, bool, bo } // generateTokenIfNeeded populates the token data for the given Secret if not already set -func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAccount *v1.ServiceAccount, cachedSecret *v1.Secret) ( /* retry */ bool, error) { +func (e *TokensController) generateTokenIfNeeded(ctx context.Context, serviceAccount *v1.ServiceAccount, cachedSecret *v1.Secret) ( /* retry */ bool, error) { // Check the cached secret to see if changes are needed if needsCA, needsServiceServingCA, needsNamespace, needsToken := e.secretUpdateNeeded(cachedSecret); !needsCA && !needsServiceServingCA && !needsToken && !needsNamespace { return false, nil @@ -391,7 +396,7 @@ func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAcco // We don't want to update the cache's copy of the secret // so add the token to a freshly retrieved copy of the secret secrets := e.client.CoreV1().Secrets(cachedSecret.Namespace) - liveSecret, err := secrets.Get(context.TODO(), cachedSecret.Name, metav1.GetOptions{}) + liveSecret, err := secrets.Get(ctx, cachedSecret.Name, metav1.GetOptions{}) if err != nil { // Retry for any error other than a NotFound return !apierrors.IsNotFound(err), err @@ -399,7 +404,7 @@ func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAcco if liveSecret.ResourceVersion != cachedSecret.ResourceVersion { // our view of the secret is not up to date // we'll get notified of an update event later and get to try again - logger.V(2).Info("Secret is not up to date, skipping token population", "secret", klog.KRef(liveSecret.Namespace, liveSecret.Name)) + klog.FromContext(ctx).V(2).Info("Secret is not up to date, skipping token population", "secret", klog.KRef(liveSecret.Namespace, liveSecret.Name)) return false, nil } @@ -430,8 +435,7 @@ func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAcco // Generate the token if needsToken { c, pc := serviceaccount.LegacyClaims(*serviceAccount, *liveSecret) - // TODO: need to plumb context if using external signer ever becomes a posibility. - token, err := e.token.GenerateToken(context.TODO(), c, pc) + token, err := e.token.GenerateToken(ctx, c, pc) if err != nil { return false, err } @@ -443,7 +447,7 @@ func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAcco liveSecret.Annotations[v1.ServiceAccountUIDKey] = string(serviceAccount.UID) // Save the secret - _, err = secrets.Update(context.TODO(), liveSecret, metav1.UpdateOptions{}) + _, err = secrets.Update(ctx, liveSecret, metav1.UpdateOptions{}) if apierrors.IsConflict(err) || apierrors.IsNotFound(err) { // if we got a Conflict error, the secret was updated by someone else, and we'll get an update notification later // if we got a NotFound error, the secret no longer exists, and we don't need to populate a token @@ -456,11 +460,11 @@ func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAcco } // removeSecretReference updates the given ServiceAccount to remove a reference to the given secretName if needed. -func (e *TokensController) removeSecretReference(saNamespace string, saName string, saUID types.UID, secretName string) error { +func (e *TokensController) removeSecretReference(ctx context.Context, saNamespace string, saName string, saUID types.UID, secretName string) error { // We don't want to update the cache's copy of the service account // so remove the secret from a freshly retrieved copy of the service account serviceAccounts := e.client.CoreV1().ServiceAccounts(saNamespace) - serviceAccount, err := serviceAccounts.Get(context.TODO(), saName, metav1.GetOptions{}) + serviceAccount, err := serviceAccounts.Get(ctx, saName, metav1.GetOptions{}) // Ignore NotFound errors when attempting to remove a reference if apierrors.IsNotFound(err) { return nil @@ -487,7 +491,7 @@ func (e *TokensController) removeSecretReference(saNamespace string, saName stri } } serviceAccount.Secrets = secrets - _, err = serviceAccounts.Update(context.TODO(), serviceAccount, metav1.UpdateOptions{}) + _, err = serviceAccounts.Update(ctx, serviceAccount, metav1.UpdateOptions{}) // Ignore NotFound errors when attempting to remove a reference if apierrors.IsNotFound(err) { return nil @@ -495,7 +499,7 @@ func (e *TokensController) removeSecretReference(saNamespace string, saName stri return err } -func (e *TokensController) getServiceAccount(ns string, name string, uid types.UID, fetchOnCacheMiss bool) (*v1.ServiceAccount, error) { +func (e *TokensController) getServiceAccount(ctx context.Context, ns string, name string, uid types.UID, fetchOnCacheMiss bool) (*v1.ServiceAccount, error) { // Look up in cache sa, err := e.serviceAccounts.ServiceAccounts(ns).Get(name) if err != nil && !apierrors.IsNotFound(err) { @@ -513,7 +517,7 @@ func (e *TokensController) getServiceAccount(ns string, name string, uid types.U } // Live lookup - sa, err = e.client.CoreV1().ServiceAccounts(ns).Get(context.TODO(), name, metav1.GetOptions{}) + sa, err = e.client.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { return nil, nil } @@ -527,7 +531,7 @@ func (e *TokensController) getServiceAccount(ns string, name string, uid types.U return nil, nil } -func (e *TokensController) getSecret(ns string, name string, uid types.UID) (*v1.Secret, error) { +func (e *TokensController) getSecret(ctx context.Context, ns string, name string, uid types.UID) (*v1.Secret, error) { // Look up in cache secret, err := e.secrets.Secrets(ns).Get(name) if err != nil { diff --git a/pkg/controller/serviceaccount/tokens_controller_test.go b/pkg/controller/serviceaccount/tokens_controller_test.go index e1fef1128614c..6c0d0716ad72e 100644 --- a/pkg/controller/serviceaccount/tokens_controller_test.go +++ b/pkg/controller/serviceaccount/tokens_controller_test.go @@ -27,13 +27,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/utils/dump" ) type testGenerator struct { @@ -551,3 +552,28 @@ func TestTokenCreation(t *testing.T) { }) } } + +func TestQueueServiceAccountSync_Tombstone(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + sa := serviceAccount(emptySecretReferences()) + tombstone := cache.DeletedFinalStateUnknown{ + Key: "default/default", + Obj: sa, + } + + client := fake.NewClientset(sa) + informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) + tokenController, err := NewTokensController(logger, informerFactory.Core().V1().ServiceAccounts(), informerFactory.Core().V1().Secrets(), client, TokensControllerOptions{}) + if err != nil { + t.Fatalf("error creating Tokens controller: %v", err) + } + + tokenController.queueServiceAccountSync(tombstone) + if tokenController.syncServiceAccountQueue.Len() != 1 { + t.Errorf("expected 1 item in queue, got %d", tokenController.syncServiceAccountQueue.Len()) + } + key, _ := tokenController.syncServiceAccountQueue.Get() + if key.uid != sa.UID { + t.Errorf("expected UID %s, got %s", sa.UID, key.uid) + } +} diff --git a/pkg/controller/servicecidrs/servicecidrs_controller.go b/pkg/controller/servicecidrs/servicecidrs_controller.go index 8a563cb0a7946..9f9c26a4c0c62 100644 --- a/pkg/controller/servicecidrs/servicecidrs_controller.go +++ b/pkg/controller/servicecidrs/servicecidrs_controller.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "net/netip" + "slices" "sync" "time" @@ -416,10 +417,8 @@ func (c *Controller) canDeleteCIDR(ctx context.Context, serviceCIDR *networkinga } func (c *Controller) addServiceCIDRFinalizerIfNeeded(ctx context.Context, cidr *networkingapiv1.ServiceCIDR) error { - for _, f := range cidr.GetFinalizers() { - if f == ServiceCIDRProtectionFinalizer { - return nil - } + if slices.Contains(cidr.GetFinalizers(), ServiceCIDRProtectionFinalizer) { + return nil } patch := map[string]interface{}{ @@ -441,14 +440,7 @@ func (c *Controller) addServiceCIDRFinalizerIfNeeded(ctx context.Context, cidr * } func (c *Controller) removeServiceCIDRFinalizerIfNeeded(ctx context.Context, cidr *networkingapiv1.ServiceCIDR) error { - found := false - for _, f := range cidr.GetFinalizers() { - if f == ServiceCIDRProtectionFinalizer { - found = true - break - } - } - if !found { + if !slices.Contains(cidr.GetFinalizers(), ServiceCIDRProtectionFinalizer) { return nil } patch := map[string]interface{}{ diff --git a/pkg/controller/statefulset/metrics/metrics.go b/pkg/controller/statefulset/metrics/metrics.go index ff530a3426258..1c4bc167c549c 100644 --- a/pkg/controller/statefulset/metrics/metrics.go +++ b/pkg/controller/statefulset/metrics/metrics.go @@ -62,6 +62,17 @@ var ( StabilityLevel: metrics.ALPHA, }, []string{"statefulset_namespace", "statefulset_name", "pod_management_policy"}, ) + + // StatefulSetRequeueSkips tracks the number of StatefulSet syncs skipped due to a stale watch cache. + StatefulSetRequeueSkips = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: StatefulSetControllerSubsystem, + Name: "stale_sync_skips_total", + Help: "Total number of StatefulSet syncs skipped due to a stale watch cache.", + StabilityLevel: metrics.ALPHA, + }, + []string{"group", "resource"}, + ) ) var registerMetrics sync.Once diff --git a/pkg/controller/statefulset/stateful_pod_control.go b/pkg/controller/statefulset/stateful_pod_control.go index b272b66759fd7..9762203bc106f 100644 --- a/pkg/controller/statefulset/stateful_pod_control.go +++ b/pkg/controller/statefulset/stateful_pod_control.go @@ -26,6 +26,7 @@ import ( v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" errorutils "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientset "k8s.io/client-go/kubernetes" @@ -33,18 +34,19 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" ) // StatefulPodControlObjectManager abstracts the manipulation of Pods and PVCs. The real controller implements this // with a clientset for writes and listers for reads; for tests we provide stubs. type StatefulPodControlObjectManager interface { - CreatePod(ctx context.Context, pod *v1.Pod) error + CreatePod(ctx context.Context, pod *v1.Pod, ss *apps.StatefulSet) error GetPod(namespace, podName string) (*v1.Pod, error) - UpdatePod(pod *v1.Pod) error + UpdatePod(pod *v1.Pod, ss *apps.StatefulSet) error DeletePod(pod *v1.Pod) error - CreateClaim(claim *v1.PersistentVolumeClaim) error + CreateClaim(claim *v1.PersistentVolumeClaim, ss *apps.StatefulSet) error GetClaim(namespace, claimName string) (*v1.PersistentVolumeClaim, error) - UpdateClaim(claim *v1.PersistentVolumeClaim) error + UpdateClaim(claim *v1.PersistentVolumeClaim, ss *apps.StatefulSet) error } // StatefulPodControl defines the interface that StatefulSetController uses to create, update, and delete Pods, @@ -63,8 +65,9 @@ func NewStatefulPodControl( podLister corelisters.PodLister, claimLister corelisters.PersistentVolumeClaimLister, recorder record.EventRecorder, + consistencyStore consistencyutil.ConsistencyStore, ) *StatefulPodControl { - return &StatefulPodControl{&realStatefulPodControlObjectManager{client, podLister, claimLister}, recorder} + return &StatefulPodControl{&realStatefulPodControlObjectManager{client, podLister, claimLister, consistencyStore}, recorder} } // NewStatefulPodControlFromManager creates a StatefulPodControl using the given StatefulPodControlObjectManager and recorder. @@ -74,13 +77,23 @@ func NewStatefulPodControlFromManager(om StatefulPodControlObjectManager, record // realStatefulPodControlObjectManager uses a clientset.Interface and listers. type realStatefulPodControlObjectManager struct { - client clientset.Interface - podLister corelisters.PodLister - claimLister corelisters.PersistentVolumeClaimLister + client clientset.Interface + podLister corelisters.PodLister + claimLister corelisters.PersistentVolumeClaimLister + consistencyStore consistencyutil.ConsistencyStore } -func (om *realStatefulPodControlObjectManager) CreatePod(ctx context.Context, pod *v1.Pod) error { - _, err := om.client.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) +func (om *realStatefulPodControlObjectManager) CreatePod(ctx context.Context, pod *v1.Pod, ss *apps.StatefulSet) error { + pod, err := om.client.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) + if err == nil { + ssNamespacedName := types.NamespacedName{Namespace: ss.Namespace, Name: ss.Name} + om.consistencyStore.WroteAt( + ssNamespacedName, + ss.UID, + podGroupResource, + pod.ResourceVersion, + ) + } return err } @@ -88,8 +101,17 @@ func (om *realStatefulPodControlObjectManager) GetPod(namespace, podName string) return om.podLister.Pods(namespace).Get(podName) } -func (om *realStatefulPodControlObjectManager) UpdatePod(pod *v1.Pod) error { - _, err := om.client.CoreV1().Pods(pod.Namespace).Update(context.TODO(), pod, metav1.UpdateOptions{}) +func (om *realStatefulPodControlObjectManager) UpdatePod(pod *v1.Pod, ss *apps.StatefulSet) error { + pod, err := om.client.CoreV1().Pods(pod.Namespace).Update(context.TODO(), pod, metav1.UpdateOptions{}) + if err == nil { + ssNamespacedName := types.NamespacedName{Namespace: ss.Namespace, Name: ss.Name} + om.consistencyStore.WroteAt( + ssNamespacedName, + ss.UID, + podGroupResource, + pod.ResourceVersion, + ) + } return err } @@ -97,8 +119,17 @@ func (om *realStatefulPodControlObjectManager) DeletePod(pod *v1.Pod) error { return om.client.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{}) } -func (om *realStatefulPodControlObjectManager) CreateClaim(claim *v1.PersistentVolumeClaim) error { - _, err := om.client.CoreV1().PersistentVolumeClaims(claim.Namespace).Create(context.TODO(), claim, metav1.CreateOptions{}) +func (om *realStatefulPodControlObjectManager) CreateClaim(claim *v1.PersistentVolumeClaim, ss *apps.StatefulSet) error { + claim, err := om.client.CoreV1().PersistentVolumeClaims(claim.Namespace).Create(context.TODO(), claim, metav1.CreateOptions{}) + if err == nil { + ssNamespacedName := types.NamespacedName{Namespace: ss.Namespace, Name: ss.Name} + om.consistencyStore.WroteAt( + ssNamespacedName, + ss.UID, + persistentVolumeClaimGroupResource, + claim.ResourceVersion, + ) + } return err } @@ -106,8 +137,17 @@ func (om *realStatefulPodControlObjectManager) GetClaim(namespace, claimName str return om.claimLister.PersistentVolumeClaims(namespace).Get(claimName) } -func (om *realStatefulPodControlObjectManager) UpdateClaim(claim *v1.PersistentVolumeClaim) error { - _, err := om.client.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(context.TODO(), claim, metav1.UpdateOptions{}) +func (om *realStatefulPodControlObjectManager) UpdateClaim(claim *v1.PersistentVolumeClaim, ss *apps.StatefulSet) error { + claim, err := om.client.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(context.TODO(), claim, metav1.UpdateOptions{}) + if err == nil { + ssNamespacedName := types.NamespacedName{Namespace: ss.Namespace, Name: ss.Name} + om.consistencyStore.WroteAt( + ssNamespacedName, + ss.UID, + persistentVolumeClaimGroupResource, + claim.ResourceVersion, + ) + } return err } @@ -118,7 +158,7 @@ func (spc *StatefulPodControl) CreateStatefulPod(ctx context.Context, set *apps. return err } // If we created the PVCs attempt to create the Pod - err := spc.objectMgr.CreatePod(ctx, pod) + err := spc.objectMgr.CreatePod(ctx, pod, set) // sink already exists errors if apierrors.IsAlreadyExists(err) { return err @@ -173,7 +213,7 @@ func (spc *StatefulPodControl) UpdateStatefulPod(ctx context.Context, set *apps. attemptedUpdate = true // commit the update, retrying on conflicts - updateErr := spc.objectMgr.UpdatePod(pod) + updateErr := spc.objectMgr.UpdatePod(pod, set) if updateErr == nil { return nil } @@ -193,10 +233,17 @@ func (spc *StatefulPodControl) UpdateStatefulPod(ctx context.Context, set *apps. return err } +// DeleteStatefulPod deletes a Pod. A NotFound is considered a successful response, since the +// end result is the same: the pod is deleted. func (spc *StatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error { - err := spc.objectMgr.DeletePod(pod) - spc.recordPodEvent("delete", set, pod, err) - return err + if err := spc.objectMgr.DeletePod(pod); err != nil { + if !apierrors.IsNotFound(err) { + spc.recordPodEvent("delete", set, pod, err) + return err + } + } + spc.recordPodEvent("delete", set, pod, nil) + return nil } // ClaimsMatchRetentionPolicy returns false if the PVCs for pod are not consistent with set's PVC deletion policy. @@ -213,7 +260,8 @@ func (spc *StatefulPodControl) ClaimsMatchRetentionPolicy(ctx context.Context, s case apierrors.IsNotFound(err): klog.FromContext(ctx).V(4).Info("Expected claim missing, continuing to pick up in next iteration", "PVC", klog.KObj(claim)) case err != nil: - return false, fmt.Errorf("Could not retrieve claim %s for %s when checking PVC deletion policy", claimName, pod.Name) + return false, fmt.Errorf( + "Could not retrieve claim %s for %s when checking PVC deletion policy: %w", claimName, pod.Name, err) default: if !isClaimOwnerUpToDate(logger, claim, set, pod) { return false, nil @@ -245,7 +293,7 @@ func (spc *StatefulPodControl) UpdatePodClaimForRetentionPolicy(ctx context.Cont if !isClaimOwnerUpToDate(logger, claim, set, pod) { claim = claim.DeepCopy() // Make a copy so we don't mutate the shared cache. updateClaimOwnerRefForSetAndPod(logger, claim, set, pod) - if err := spc.objectMgr.UpdateClaim(claim); err != nil { + if err := spc.objectMgr.UpdateClaim(claim, set); err != nil { return fmt.Errorf("could not update claim %s for delete policy ownerRefs: %w", claimName, err) } } @@ -336,15 +384,15 @@ func (spc *StatefulPodControl) createPersistentVolumeClaims(set *apps.StatefulSe pvc, err := spc.objectMgr.GetClaim(claim.Namespace, claim.Name) switch { case apierrors.IsNotFound(err): - err := spc.objectMgr.CreateClaim(&claim) + err := spc.objectMgr.CreateClaim(&claim, set) if err != nil { - errs = append(errs, fmt.Errorf("failed to create PVC %s: %s", claim.Name, err)) + errs = append(errs, fmt.Errorf("failed to create PVC %s: %w", claim.Name, err)) } if err == nil || !apierrors.IsAlreadyExists(err) { spc.recordClaimEvent("create", set, pod, &claim, err) } case err != nil: - errs = append(errs, fmt.Errorf("failed to retrieve PVC %s: %s", claim.Name, err)) + errs = append(errs, fmt.Errorf("failed to retrieve PVC %s: %w", claim.Name, err)) spc.recordClaimEvent("create", set, pod, &claim, err) default: if pvc.DeletionTimestamp != nil { diff --git a/pkg/controller/statefulset/stateful_pod_control_test.go b/pkg/controller/statefulset/stateful_pod_control_test.go index 27528e77e0f51..8cac11b32aa9a 100644 --- a/pkg/controller/statefulset/stateful_pod_control_test.go +++ b/pkg/controller/statefulset/stateful_pod_control_test.go @@ -24,11 +24,13 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/fake" corelisters "k8s.io/client-go/listers/core/v1" @@ -38,6 +40,7 @@ import ( "k8s.io/klog/v2/ktesting" _ "k8s.io/kubernetes/pkg/apis/apps/install" _ "k8s.io/kubernetes/pkg/apis/core/install" + "k8s.io/kubernetes/pkg/controller/util/consistency" "k8s.io/utils/ptr" ) @@ -48,7 +51,7 @@ func TestStatefulPodControlCreatesPods(t *testing.T) { fakeClient := &fake.Clientset{} claimIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) claimLister := corelisters.NewPersistentVolumeClaimLister(claimIndexer) - control := NewStatefulPodControl(fakeClient, nil, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, claimLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("get", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), action.GetResource().Resource) }) @@ -87,7 +90,7 @@ func TestStatefulPodControlCreatePodExists(t *testing.T) { pvcIndexer.Add(&pvc) } pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { create := action.(core.CreateAction) return true, create.GetObject(), nil @@ -114,7 +117,7 @@ func TestStatefulPodControlCreatePodPvcCreateFailure(t *testing.T) { fakeClient := &fake.Clientset{} pvcIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewInternalError(errors.New("API server down")) }) @@ -149,7 +152,7 @@ func TestStatefulPodControlCreatePodPVCDeleting(t *testing.T) { pvcIndexer.Add(&pvc) } pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { create := action.(core.CreateAction) return true, create.GetObject(), nil @@ -188,7 +191,7 @@ func TestStatefulPodControlCreatePodPvcGetFailure(t *testing.T) { fakeClient := &fake.Clientset{} pvcIndexer := &fakeIndexer{getError: errors.New("API server down")} pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewInternalError(errors.New("API server down")) }) @@ -217,7 +220,7 @@ func TestStatefulPodControlCreatePodFailed(t *testing.T) { fakeClient := &fake.Clientset{} pvcIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { create := action.(core.CreateAction) return true, create.GetObject(), nil @@ -252,7 +255,7 @@ func TestStatefulPodControlNoOpUpdate(t *testing.T) { indexer.Add(&claim) } claimLister := corelisters.NewPersistentVolumeClaimLister(indexer) - control := NewStatefulPodControl(fakeClient, nil, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, claimLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { t.Error("no-op update should not make any client invocation") return true, nil, apierrors.NewInternalError(errors.New("if we are here we have a problem")) @@ -274,7 +277,7 @@ func TestStatefulPodControlUpdatesIdentity(t *testing.T) { fakeClient := fake.NewSimpleClientset(set, pod) indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) claimLister := corelisters.NewPersistentVolumeClaimLister(indexer) - control := NewStatefulPodControl(fakeClient, nil, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, claimLister, recorder, consistency.NewNoopConsistencyStore()) var updated *v1.Pod fakeClient.PrependReactor("update", "pods", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) @@ -309,7 +312,7 @@ func TestStatefulPodControlUpdateIdentityFailure(t *testing.T) { podLister := corelisters.NewPodLister(podIndexer) claimIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) claimLister := corelisters.NewPersistentVolumeClaimLister(claimIndexer) - control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "pods", func(action core.Action) (bool, runtime.Object, error) { pod.Name = "goo-0" return true, nil, apierrors.NewInternalError(errors.New("API server down")) @@ -337,7 +340,7 @@ func TestStatefulPodControlUpdatesPodStorage(t *testing.T) { fakeClient := &fake.Clientset{} pvcIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) pvcs := getPersistentVolumeClaims(set, pod) volumes := make([]v1.Volume, 0, len(pod.Spec.Volumes)) for i := range pod.Spec.Volumes { @@ -385,7 +388,7 @@ func TestStatefulPodControlUpdatePodStorageFailure(t *testing.T) { fakeClient := &fake.Clientset{} pvcIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) pvcLister := corelisters.NewPersistentVolumeClaimLister(pvcIndexer) - control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder) + control := NewStatefulPodControl(fakeClient, nil, pvcLister, recorder, consistency.NewNoopConsistencyStore()) pvcs := getPersistentVolumeClaims(set, pod) volumes := make([]v1.Volume, 0, len(pod.Spec.Volumes)) for i := range pod.Spec.Volumes { @@ -433,7 +436,7 @@ func TestStatefulPodControlUpdatePodConflictSuccess(t *testing.T) { claim := claims[k] claimIndexer.Add(&claim) } - control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder, consistency.NewNoopConsistencyStore()) conflict := false fakeClient.AddReactor("update", "pods", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) @@ -464,7 +467,7 @@ func TestStatefulPodControlDeletesStatefulPod(t *testing.T) { set := newStatefulSet(3) pod := newStatefulSetPod(set, 0) fakeClient := &fake.Clientset{} - control := NewStatefulPodControl(fakeClient, nil, nil, recorder) + control := NewStatefulPodControl(fakeClient, nil, nil, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("delete", "pods", func(action core.Action) (bool, runtime.Object, error) { return true, nil, nil }) @@ -479,12 +482,32 @@ func TestStatefulPodControlDeletesStatefulPod(t *testing.T) { } } +func TestStatefulPodControlDeleteStatefulPodNotFound(t *testing.T) { + recorder := record.NewFakeRecorder(10) + set := newStatefulSet(3) + pod := newStatefulSetPod(set, 0) + fakeClient := &fake.Clientset{} + control := NewStatefulPodControl(fakeClient, nil, nil, recorder, consistency.NewNoopConsistencyStore()) + fakeClient.AddReactor("delete", "pods", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), pod.Name) + }) + if err := control.DeleteStatefulPod(set, pod); err != nil { + t.Errorf("DeleteStatefulPod returned error for NotFound pod: %s", err) + } + events := collectEvents(recorder.Events) + if eventCount := len(events); eventCount != 1 { + t.Errorf("delete not-found: got %d events, but want 1", eventCount) + } else if !strings.Contains(events[0], v1.EventTypeNormal) { + t.Errorf("Found unexpected non-normal event %s", events[0]) + } +} + func TestStatefulPodControlDeleteFailure(t *testing.T) { recorder := record.NewFakeRecorder(10) set := newStatefulSet(3) pod := newStatefulSetPod(set, 0) fakeClient := &fake.Clientset{} - control := NewStatefulPodControl(fakeClient, nil, nil, recorder) + control := NewStatefulPodControl(fakeClient, nil, nil, recorder, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("delete", "pods", func(action core.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewInternalError(errors.New("API server down")) }) @@ -513,7 +536,7 @@ func TestStatefulPodControlClaimsMatchDeletionPolcy(t *testing.T) { claim := claims[k] indexer.Add(&claim) } - control := NewStatefulPodControl(fakeClient, nil, claimLister, &noopRecorder{}) + control := NewStatefulPodControl(fakeClient, nil, claimLister, &noopRecorder{}, consistency.NewNoopConsistencyStore()) set.Spec.PersistentVolumeClaimRetentionPolicy = &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, @@ -578,7 +601,7 @@ func TestStatefulPodControlUpdatePodClaimForRetentionPolicy(t *testing.T) { t.Errorf("Could not add claim %s: %v", k, err) } } - control := NewStatefulPodControl(fakeClient, nil, claimLister, &noopRecorder{}) + control := NewStatefulPodControl(fakeClient, nil, claimLister, &noopRecorder{}, consistency.NewNoopConsistencyStore()) set.Spec.PersistentVolumeClaimRetentionPolicy = &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ WhenDeleted: apps.DeletePersistentVolumeClaimRetentionPolicyType, WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, @@ -700,7 +723,7 @@ func TestPodClaimIsStale(t *testing.T) { pod.SetUID("123") } claimLister := corelisters.NewPersistentVolumeClaimLister(claimIndexer) - control := NewStatefulPodControl(&fake.Clientset{}, nil, claimLister, &noopRecorder{}) + control := NewStatefulPodControl(&fake.Clientset{}, nil, claimLister, &noopRecorder{}, consistency.NewNoopConsistencyStore()) expected := tc.expected // Note that the error isn't / can't be tested. if stale, _ := control.PodClaimIsStale(&set, &pod); stale != expected { @@ -738,7 +761,7 @@ func TestStatefulPodControlRetainDeletionPolicyUpdate(t *testing.T) { t.Errorf("couldn't add %s to index: %v", claim.GetName(), err) } } - control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder, consistency.NewNoopConsistencyStore()) if err := control.UpdateStatefulPod(ctx, set, pod); err != nil { t.Errorf("Successful update returned an error: %s", err) } @@ -784,7 +807,7 @@ func TestStatefulPodControlRetentionPolicyUpdate(t *testing.T) { }) podLister := corelisters.NewPodLister(podIndexer) claimLister := corelisters.NewPersistentVolumeClaimLister(claimIndexer) - control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder, consistency.NewNoopConsistencyStore()) if err := control.UpdateStatefulPod(ctx, set, pod); err != nil { t.Errorf("Successful update returned an error: %s", err) } @@ -822,7 +845,7 @@ func TestStatefulPodControlRetentionPolicyUpdateMissingClaims(t *testing.T) { claimIndexer.Update(update.GetObject()) return true, update.GetObject(), nil }) - control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder) + control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder, consistency.NewNoopConsistencyStore()) if err := control.UpdateStatefulPod(ctx, set, pod); err != nil { t.Error("Unexpected error on pod update when PVCs are missing") } @@ -868,3 +891,138 @@ func collectEvents(source <-chan string) []string { } return events } + +type fakeRVGetter struct { + rv string +} + +func (f *fakeRVGetter) LastStoreSyncResourceVersion() string { + return f.rv +} + +func TestStatefulPodControlConsistencyStore(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + testCases := []struct { + name string + action func(context.Context, *StatefulPodControl, *apps.StatefulSet, *v1.Pod) error + resource schema.GroupResource + setupMocks func(*fake.Clientset, cache.Indexer, *v1.Pod, *apps.StatefulSet) + }{ + { + name: "CreatePod", + action: func(ctx context.Context, control *StatefulPodControl, set *apps.StatefulSet, pod *v1.Pod) error { + return control.CreateStatefulPod(ctx, set, pod) + }, + resource: schema.GroupResource{Group: "", Resource: "pods"}, + setupMocks: func(fakeClient *fake.Clientset, claimIndexer cache.Indexer, pod *v1.Pod, set *apps.StatefulSet) { + fakeClient.AddReactor("get", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), action.GetResource().Resource) + }) + fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { + create := action.(core.CreateAction) + create.GetObject().(*v1.PersistentVolumeClaim).ResourceVersion = "2" + require.NoError(t, claimIndexer.Add(create.GetObject())) + return true, create.GetObject(), nil + }) + fakeClient.AddReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) { + create := action.(core.CreateAction) + create.GetObject().(*v1.Pod).ResourceVersion = "2" + return true, create.GetObject(), nil + }) + }, + }, + { + name: "UpdatePod", + action: func(ctx context.Context, control *StatefulPodControl, set *apps.StatefulSet, pod *v1.Pod) error { + pod.Name = "goo-0" // mismatch identity to trigger update + return control.UpdateStatefulPod(ctx, set, pod) + }, + resource: schema.GroupResource{Group: "", Resource: "pods"}, + setupMocks: func(fakeClient *fake.Clientset, claimIndexer cache.Indexer, pod *v1.Pod, set *apps.StatefulSet) { + fakeClient.PrependReactor("update", "pods", func(action core.Action) (bool, runtime.Object, error) { + update := action.(core.UpdateAction) + update.GetObject().(*v1.Pod).ResourceVersion = "2" + return true, update.GetObject(), nil + }) + }, + }, + { + name: "CreateClaim", + action: func(ctx context.Context, control *StatefulPodControl, set *apps.StatefulSet, pod *v1.Pod) error { + return control.createPersistentVolumeClaims(set, pod) + }, + resource: schema.GroupResource{Group: "", Resource: "persistentvolumeclaims"}, + setupMocks: func(fakeClient *fake.Clientset, claimIndexer cache.Indexer, pod *v1.Pod, set *apps.StatefulSet) { + fakeClient.AddReactor("get", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), action.GetResource().Resource) + }) + fakeClient.AddReactor("create", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { + create := action.(core.CreateAction) + create.GetObject().(*v1.PersistentVolumeClaim).ResourceVersion = "2" + require.NoError(t, claimIndexer.Add(create.GetObject())) + return true, create.GetObject(), nil + }) + }, + }, + { + name: "UpdateClaim", + action: func(ctx context.Context, control *StatefulPodControl, set *apps.StatefulSet, pod *v1.Pod) error { + return control.UpdatePodClaimForRetentionPolicy(ctx, set, pod) + }, + resource: schema.GroupResource{Group: "", Resource: "persistentvolumeclaims"}, + setupMocks: func(fakeClient *fake.Clientset, claimIndexer cache.Indexer, pod *v1.Pod, set *apps.StatefulSet) { + set.Spec.PersistentVolumeClaimRetentionPolicy = &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: apps.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, + } + claims := getPersistentVolumeClaims(set, pod) + for k := range claims { + claim := claims[k] + require.NoError(t, claimIndexer.Add(&claim)) + } + fakeClient.AddReactor("update", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { + update := action.(core.UpdateAction) + update.GetObject().(*v1.PersistentVolumeClaim).ResourceVersion = "2" + return true, update.GetObject(), nil + }) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + recorder := record.NewFakeRecorder(10) + set := newStatefulSet(3) + set.GetObjectMeta().SetUID("set-123") + pod := newStatefulSetPod(set, 0) + fakeClient := &fake.Clientset{} + podIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) + podLister := corelisters.NewPodLister(podIndexer) + claimIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) + claimLister := corelisters.NewPersistentVolumeClaimLister(claimIndexer) + + rvGetter := &fakeRVGetter{rv: "1"} + consistencyStore := consistency.NewConsistencyStore(map[schema.GroupResource]consistency.LastSyncRVGetter{ + tc.resource: rvGetter, + }) + + control := NewStatefulPodControl(fakeClient, podLister, claimLister, recorder, consistencyStore) + + tc.setupMocks(fakeClient, claimIndexer, pod, set) + + if err := tc.action(ctx, control, set, pod); err != nil { + t.Fatalf("action failed: %v", err) + } + + if err := consistencyStore.EnsureReady(types.NamespacedName{Namespace: set.Namespace, Name: set.Name}); err == nil { + t.Error("expected consistency store to return an error, got nil") + } + + rvGetter.rv = "2" + + if err := consistencyStore.EnsureReady(types.NamespacedName{Namespace: set.Namespace, Name: set.Name}); err != nil { + t.Errorf("expected consistency store to be ready, got an error: %v", err) + } + }) + } +} diff --git a/pkg/controller/statefulset/stateful_set.go b/pkg/controller/statefulset/stateful_set.go index 922df499c1cc8..44feceb4ba5f2 100644 --- a/pkg/controller/statefulset/stateful_set.go +++ b/pkg/controller/statefulset/stateful_set.go @@ -18,6 +18,7 @@ package statefulset import ( "context" + "errors" "fmt" "reflect" "sync" @@ -25,11 +26,14 @@ import ( apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" appsinformers "k8s.io/client-go/informers/apps/v1" coreinformers "k8s.io/client-go/informers/core/v1" clientset "k8s.io/client-go/kubernetes" @@ -44,6 +48,8 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/history" "k8s.io/kubernetes/pkg/controller/statefulset/metrics" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" + "k8s.io/kubernetes/pkg/features" "k8s.io/utils/clock" "k8s.io/utils/ptr" @@ -84,8 +90,25 @@ type StatefulSetController struct { // eventBroadcaster is the core of event processing pipeline. eventBroadcaster record.EventBroadcaster clock clock.PassiveClock + // consistencyStore is used to track the state of the controller's operations. + consistencyStore consistencyutil.ConsistencyStore } +var ( + statefulSetGroupResource = schema.GroupResource{ + Group: "apps", + Resource: "statefulsets", + } + podGroupResource = schema.GroupResource{ + Group: "", + Resource: "pods", + } + persistentVolumeClaimGroupResource = schema.GroupResource{ + Group: "", + Resource: "persistentvolumeclaims", + } +) + // NewStatefulSetController creates a new statefulset controller. func NewStatefulSetController( ctx context.Context, @@ -101,6 +124,35 @@ func NewStatefulSetController( // Register metrics metrics.Register() + if err := history.AddControllerRevisionControllerIndexer(revInformer.Informer()); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to add controller revision controller indexer: %w", err)) + } + + var consistencyStore consistencyutil.ConsistencyStore + var podWriteCallback func(pod *v1.Pod, rs *metav1.OwnerReference) + if utilfeature.DefaultFeatureGate.Enabled(features.StaleControllerConsistencyStatefulSet) { + consistencyStore = consistencyutil.NewConsistencyStore(map[schema.GroupResource]consistencyutil.LastSyncRVGetter{ + podGroupResource: podInformer.Informer().GetStore(), + persistentVolumeClaimGroupResource: pvcInformer.Informer().GetStore(), + statefulSetGroupResource: setInformer.Informer().GetStore(), + }) + podWriteCallback = func(pod *v1.Pod, ss *metav1.OwnerReference) { + if ss == nil { + return + } + consistencyStore.WroteAt( + types.NamespacedName{ + Namespace: pod.Namespace, + Name: ss.Name, + }, + ss.UID, + podGroupResource, + pod.ResourceVersion, + ) + } + } else { + consistencyStore = consistencyutil.NewNoopConsistencyStore() + } ssc := &StatefulSetController{ kubeClient: kubeClient, control: NewDefaultStatefulSetControl( @@ -108,9 +160,10 @@ func NewStatefulSetController( kubeClient, podInformer.Lister(), pvcInformer.Lister(), - recorder), - NewRealStatefulSetStatusUpdater(kubeClient, setInformer.Lister()), - history.NewHistory(kubeClient, revInformer.Lister()), + recorder, + consistencyStore), + NewRealStatefulSetStatusUpdater(kubeClient, setInformer.Lister(), consistencyStore), + history.NewHistory(kubeClient, revInformer.Lister(), revInformer.Informer().GetIndexer()), ), pvcListerSynced: pvcInformer.Informer().HasSynced, revListerSynced: revInformer.Informer().HasSynced, @@ -118,10 +171,11 @@ func NewStatefulSetController( workqueue.DefaultTypedControllerRateLimiter[string](), workqueue.TypedRateLimitingQueueConfig[string]{Name: "statefulset"}, ), - podControl: controller.RealPodControl{KubeClient: kubeClient, Recorder: recorder}, + podControl: controller.RealPodControl{KubeClient: kubeClient, Recorder: recorder, OnWrite: podWriteCallback}, eventBroadcaster: eventBroadcaster, clock: clock.RealClock{}, + consistencyStore: consistencyStore, } podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -478,8 +532,25 @@ func (ssc *StatefulSetController) sync(ctx context.Context, key string) error { if err != nil { return err } + + ssNamespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + if err := ssc.consistencyStore.EnsureReady(ssNamespacedName); err != nil { + var consistencyErr *consistencyutil.ConsistencyError + if errors.As(err, &consistencyErr) { + metrics.StatefulSetRequeueSkips.WithLabelValues( + consistencyErr.GroupResource.Group, + consistencyErr.GroupResource.Resource, + ).Inc() + } + return err + } + set, err := ssc.setLister.StatefulSets(namespace).Get(name) - if errors.IsNotFound(err) { + if apierrors.IsNotFound(err) { + ssc.consistencyStore.Clear(ssNamespacedName, "") logger.Info("StatefulSet has been deleted", "key", key) return nil } diff --git a/pkg/controller/statefulset/stateful_set_control.go b/pkg/controller/statefulset/stateful_set_control.go index dd339cc7814fc..b06f8d443ee96 100644 --- a/pkg/controller/statefulset/stateful_set_control.go +++ b/pkg/controller/statefulset/stateful_set_control.go @@ -27,7 +27,6 @@ import ( apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -155,7 +154,7 @@ func (ssc *defaultStatefulSetControl) ListRevisions(set *apps.StatefulSet) ([]*a if err != nil { return nil, err } - return ssc.controllerHistory.ListControllerRevisions(set, selector) + return ssc.controllerHistory.ListControllerRevisions(set, controllerKind, selector) } func (ssc *defaultStatefulSetControl) AdoptOrphanRevisions( @@ -719,9 +718,7 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(ctx context.Context, set logger.V(2).Info("Pod of StatefulSet is terminating for update", "statefulSet", klog.KObj(set), "pod", klog.KObj(replicas[target])) if err := ssc.podControl.DeleteStatefulPod(set, replicas[target]); err != nil { - if !errors.IsNotFound(err) { - return &status, err - } + return &status, err } status.CurrentReplicas-- return &status, err @@ -798,9 +795,7 @@ func updateStatefulSetAfterInvariantEstablished(ctx context.Context, ssc *defaul "statefulSet", klog.KObj(set), "pod", klog.KObj(replicas[target])) if err := ssc.podControl.DeleteStatefulPod(set, replicas[target]); err != nil { - if !errors.IsNotFound(err) { - return &status, err - } + return &status, err } deletedPods++ status.CurrentReplicas-- diff --git a/pkg/controller/statefulset/stateful_set_control_test.go b/pkg/controller/statefulset/stateful_set_control_test.go index 734572e86d4c3..1fbd8f3b0502f 100644 --- a/pkg/controller/statefulset/stateful_set_control_test.go +++ b/pkg/controller/statefulset/stateful_set_control_test.go @@ -24,6 +24,7 @@ import ( "math/rand" "reflect" "runtime" + "slices" "sort" "strconv" "strings" @@ -886,7 +887,7 @@ func TestStatefulSetControl_getSetRevisions(t *testing.T) { if len(revisions) != test.expectedCount { t.Errorf("%s: want %d revisions got %d", test.name, test.expectedCount, len(revisions)) } - if test.err && err == nil { + if test.err { t.Errorf("%s: expected error", test.name) } if !test.err && !history.EqualRevision(current, test.expectedCurrent) { @@ -2092,9 +2093,6 @@ func TestStatefulSetControlRollback(t *testing.T) { if err := updateStatefulSetControl(set, ssc, om, assertUpdateInvariants); err != nil { t.Fatalf("%s: %s", test.name, err) } - if err != nil { - t.Fatalf("%s: %s", test.name, err) - } pods, err = om.podsLister.Pods(set.Namespace).List(selector) if err != nil { t.Fatalf("%s: %s", test.name, err) @@ -2514,7 +2512,7 @@ func newFakeObjectManager(informerFactory informers.SharedInformerFactory) *fake } } -func (om *fakeObjectManager) CreatePod(ctx context.Context, pod *v1.Pod) error { +func (om *fakeObjectManager) CreatePod(ctx context.Context, pod *v1.Pod, ss *apps.StatefulSet) error { defer om.createPodTracker.trackParallelRequests() if err := om.createPodTracker.incWithOptionalError(); err != nil { return err @@ -2527,7 +2525,7 @@ func (om *fakeObjectManager) GetPod(namespace, podName string) (*v1.Pod, error) return om.podsLister.Pods(namespace).Get(podName) } -func (om *fakeObjectManager) UpdatePod(pod *v1.Pod) error { +func (om *fakeObjectManager) UpdatePod(pod *v1.Pod, ss *apps.StatefulSet) error { defer om.updatePodTracker.trackParallelRequests() if err := om.updatePodTracker.incWithOptionalError(); err != nil { return err @@ -2550,7 +2548,7 @@ func (om *fakeObjectManager) DeletePod(pod *v1.Pod) error { return nil // Not found, no error in deleting. } -func (om *fakeObjectManager) CreateClaim(claim *v1.PersistentVolumeClaim) error { +func (om *fakeObjectManager) CreateClaim(claim *v1.PersistentVolumeClaim, ss *apps.StatefulSet) error { om.claimsIndexer.Update(claim) return nil } @@ -2559,7 +2557,7 @@ func (om *fakeObjectManager) GetClaim(namespace, claimName string) (*v1.Persiste return om.claimsLister.PersistentVolumeClaims(namespace).Get(claimName) } -func (om *fakeObjectManager) UpdateClaim(claim *v1.PersistentVolumeClaim) error { +func (om *fakeObjectManager) UpdateClaim(claim *v1.PersistentVolumeClaim, ss *apps.StatefulSet) error { // Validate ownerRefs. refs := claim.GetOwnerReferences() for _, ref := range refs { @@ -3407,10 +3405,8 @@ func isOrHasInternalError(err error) bool { } var agg utilerrors.Aggregate if errors.As(err, &agg) { - for _, e := range agg.Errors() { - if apierrors.IsInternalError(e) { - return true - } + if slices.ContainsFunc(agg.Errors(), apierrors.IsInternalError) { + return true } } return apierrors.IsInternalError(err) diff --git a/pkg/controller/statefulset/stateful_set_status_updater.go b/pkg/controller/statefulset/stateful_set_status_updater.go index 409ad92e20fc3..5fc7577ed96e7 100644 --- a/pkg/controller/statefulset/stateful_set_status_updater.go +++ b/pkg/controller/statefulset/stateful_set_status_updater.go @@ -21,11 +21,13 @@ import ( apps "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientset "k8s.io/client-go/kubernetes" appslisters "k8s.io/client-go/listers/apps/v1" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" + consistencyutil "k8s.io/kubernetes/pkg/controller/util/consistency" ) // StatefulSetStatusUpdaterInterface is an interface used to update the StatefulSetStatus associated with a StatefulSet. @@ -40,13 +42,15 @@ type StatefulSetStatusUpdaterInterface interface { // using the supplied client and setLister. func NewRealStatefulSetStatusUpdater( client clientset.Interface, - setLister appslisters.StatefulSetLister) StatefulSetStatusUpdaterInterface { - return &realStatefulSetStatusUpdater{client, setLister} + setLister appslisters.StatefulSetLister, + consistencyStore consistencyutil.ConsistencyStore) StatefulSetStatusUpdaterInterface { + return &realStatefulSetStatusUpdater{client, setLister, consistencyStore} } type realStatefulSetStatusUpdater struct { - client clientset.Interface - setLister appslisters.StatefulSetLister + client clientset.Interface + setLister appslisters.StatefulSetLister + consistencyStore consistencyutil.ConsistencyStore } func (ssu *realStatefulSetStatusUpdater) UpdateStatefulSetStatus( @@ -58,10 +62,17 @@ func (ssu *realStatefulSetStatusUpdater) UpdateStatefulSetStatus( return retry.RetryOnConflict(retry.DefaultRetry, func() error { set.Status = *status // TODO: This context.TODO should use a real context once we have RetryOnConflictWithContext - _, updateErr := ssu.client.AppsV1().StatefulSets(set.Namespace).UpdateStatus(context.TODO(), set, metav1.UpdateOptions{}) + updatedSet, updateErr := ssu.client.AppsV1().StatefulSets(set.Namespace).UpdateStatus(context.TODO(), set, metav1.UpdateOptions{}) if updateErr == nil { + ssu.consistencyStore.WroteAt( + types.NamespacedName{Namespace: updatedSet.Namespace, Name: updatedSet.Name}, + updatedSet.UID, + statefulSetGroupResource, + updatedSet.ResourceVersion, + ) return nil } + if updated, err := ssu.setLister.StatefulSets(set.Namespace).Get(set.Name); err == nil { // make a copy so we don't mutate the shared cache set = updated.DeepCopy() diff --git a/pkg/controller/statefulset/stateful_set_status_updater_test.go b/pkg/controller/statefulset/stateful_set_status_updater_test.go index 83c51bd873a78..db1f5e7384fcf 100644 --- a/pkg/controller/statefulset/stateful_set_status_updater_test.go +++ b/pkg/controller/statefulset/stateful_set_status_updater_test.go @@ -24,17 +24,20 @@ import ( apps "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/fake" appslisters "k8s.io/client-go/listers/apps/v1" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" + "k8s.io/kubernetes/pkg/controller/util/consistency" ) func TestStatefulSetUpdaterUpdatesSetStatus(t *testing.T) { set := newStatefulSet(3) status := apps.StatefulSetStatus{ObservedGeneration: 1, Replicas: 2} fakeClient := &fake.Clientset{} - updater := NewRealStatefulSetStatusUpdater(fakeClient, nil) + updater := NewRealStatefulSetStatusUpdater(fakeClient, nil, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) return true, update.GetObject(), nil @@ -51,7 +54,7 @@ func TestStatefulSetStatusUpdaterUpdatesObservedGeneration(t *testing.T) { set := newStatefulSet(3) status := apps.StatefulSetStatus{ObservedGeneration: 3, Replicas: 2} fakeClient := &fake.Clientset{} - updater := NewRealStatefulSetStatusUpdater(fakeClient, nil) + updater := NewRealStatefulSetStatusUpdater(fakeClient, nil, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) sts := update.GetObject().(*apps.StatefulSet) @@ -72,7 +75,7 @@ func TestStatefulSetStatusUpdaterUpdateReplicasFailure(t *testing.T) { indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) indexer.Add(set) setLister := appslisters.NewStatefulSetLister(indexer) - updater := NewRealStatefulSetStatusUpdater(fakeClient, setLister) + updater := NewRealStatefulSetStatusUpdater(fakeClient, setLister, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewInternalError(errors.New("API server down")) }) @@ -89,7 +92,7 @@ func TestStatefulSetStatusUpdaterUpdateReplicasConflict(t *testing.T) { indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) indexer.Add(set) setLister := appslisters.NewStatefulSetLister(indexer) - updater := NewRealStatefulSetStatusUpdater(fakeClient, setLister) + updater := NewRealStatefulSetStatusUpdater(fakeClient, setLister, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) if !conflict { @@ -114,7 +117,7 @@ func TestStatefulSetStatusUpdaterUpdateReplicasConflictFailure(t *testing.T) { indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) indexer.Add(set) setLister := appslisters.NewStatefulSetLister(indexer) - updater := NewRealStatefulSetStatusUpdater(fakeClient, setLister) + updater := NewRealStatefulSetStatusUpdater(fakeClient, setLister, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) return true, update.GetObject(), apierrors.NewConflict(action.GetResource().GroupResource(), set.Name, errors.New("object already exists")) @@ -128,7 +131,7 @@ func TestStatefulSetStatusUpdaterGetAvailableReplicas(t *testing.T) { set := newStatefulSet(3) status := apps.StatefulSetStatus{ObservedGeneration: 1, Replicas: 2, AvailableReplicas: 3} fakeClient := &fake.Clientset{} - updater := NewRealStatefulSetStatusUpdater(fakeClient, nil) + updater := NewRealStatefulSetStatusUpdater(fakeClient, nil, consistency.NewNoopConsistencyStore()) fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { update := action.(core.UpdateAction) return true, update.GetObject(), nil @@ -140,3 +143,34 @@ func TestStatefulSetStatusUpdaterGetAvailableReplicas(t *testing.T) { t.Errorf("UpdateStatefulSetStatus mutated the sets replicas %d", set.Status.AvailableReplicas) } } + +func TestStatefulSetStatusUpdaterConsistencyStore(t *testing.T) { + set := newStatefulSet(3) + status := apps.StatefulSetStatus{ObservedGeneration: 1, Replicas: 2} + fakeClient := &fake.Clientset{} + + rvGetter := &fakeRVGetter{rv: "1"} + consistencyStore := consistency.NewConsistencyStore(map[schema.GroupResource]consistency.LastSyncRVGetter{ + {Group: "apps", Resource: "statefulsets"}: rvGetter, + }) + + updater := NewRealStatefulSetStatusUpdater(fakeClient, nil, consistencyStore) + fakeClient.AddReactor("update", "statefulsets", func(action core.Action) (bool, runtime.Object, error) { + update := action.(core.UpdateAction) + update.GetObject().(*apps.StatefulSet).ResourceVersion = "2" + return true, update.GetObject(), nil + }) + if err := updater.UpdateStatefulSetStatus(context.TODO(), set, &status); err != nil { + t.Fatalf("Error returned on successful status update: %s", err) + } + + if err := consistencyStore.EnsureReady(types.NamespacedName{Namespace: set.Namespace, Name: set.Name}); err == nil { + t.Error("expected consistency store to return an error, got nil") + } + + rvGetter.rv = "2" + + if err := consistencyStore.EnsureReady(types.NamespacedName{Namespace: set.Namespace, Name: set.Name}); err != nil { + t.Errorf("expected consistency store to be ready, got an error: %v", err) + } +} diff --git a/pkg/controller/statefulset/stateful_set_test.go b/pkg/controller/statefulset/stateful_set_test.go index cad55da0445ad..51340a05054a1 100644 --- a/pkg/controller/statefulset/stateful_set_test.go +++ b/pkg/controller/statefulset/stateful_set_test.go @@ -271,7 +271,7 @@ func TestStatefulSetControllerDeletionTimestampRace(t *testing.T) { } // It should not adopt revisions. - revisions, err := ssh.ListControllerRevisions(set, selector) + revisions, err := ssh.ListControllerRevisions(set, parentKind, selector) if err != nil { t.Fatal(err) } @@ -719,7 +719,9 @@ func TestOrphanedPodsWithPVCDeletePolicy(t *testing.T) { om.podsIndexer.Add(pod) claims := getPersistentVolumeClaims(set, pod) for _, claim := range claims { - om.CreateClaim(&claim) + if err := om.CreateClaim(&claim, set); err != nil { + t.Errorf("Failed to create claim %s: %v", claim.Name, err) + } } } @@ -911,11 +913,11 @@ func TestStaleOwnerRefOnScaleup(t *testing.T) { } func TestStatefulSetAvailabilityCheck(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) + tCtx := ktesting.Init(t) set := setMinReadySeconds(newStatefulSet(4), int32(5)) // 5 seconds set = setupPodManagementPolicy(apps.ParallelPodManagement, set) - ssc, _, om, _ := newFakeStatefulSetController(ctx, set) + ssc, _, om, _ := newFakeStatefulSetController(tCtx, set) if err := om.setsIndexer.Add(set); err != nil { t.Fatalf("could not add set to the cache: %v", err) } @@ -939,7 +941,7 @@ func TestStatefulSetAvailabilityCheck(t *testing.T) { t.Fatalf("%d: %v", i, err) } } - err := ssc.syncStatefulSet(ctx, set, pods) + err := ssc.syncStatefulSet(tCtx, set, pods) if err != nil { t.Fatal(err) } @@ -963,9 +965,7 @@ func TestStatefulSetAvailabilityCheck(t *testing.T) { } // RS should be re-queued after 700ms to recompute .status.availableReplicas (200ms extra for the test). - ktesting.Eventually(ctx, func(tCtx ktesting.TContext) int { - return ssc.queue.Len() - }).WithTimeout(900*time.Millisecond). + tCtx.Eventually(ssc.queue.Len).WithTimeout(900*time.Millisecond). WithPolling(10*time.Millisecond). Should(gomega.Equal(1), " StatefulSet should be re-queued to recompute .status.availableReplicas") } diff --git a/pkg/controller/storageversionmigrator/migrationrunner.go b/pkg/controller/storageversionmigrator/migrationrunner.go new file mode 100644 index 0000000000000..a1d617e7741d2 --- /dev/null +++ b/pkg/controller/storageversionmigrator/migrationrunner.go @@ -0,0 +1,424 @@ +/* +Copyright 2025 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 storageversionmigrator + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + applyconfigurationapiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/applyconfiguration/apiextensions/v1" + apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + + svmv1beta1 "k8s.io/api/storagemigration/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + svminformers "k8s.io/client-go/informers/storagemigration/v1beta1" + clientset "k8s.io/client-go/kubernetes" + svmlisters "k8s.io/client-go/listers/storagemigration/v1beta1" +) + +const MigrationRunnerControllerName string = "migration-runner-controller" + +// MigrationRunnerController is responsible for managing the lifecycle of +// StorageVersionMigration resources. It will ensure that only a single StorageVersionMigration +// will be active at a time for a given GroupResource, by adding the MigrationRunning condition +// to the SVMs that are active. Only these SVMs will be processed by the downstream migration controllers. +// +// It adds the StorageMigrating condition to the CRD and is in charge of removing old +// stored versions from the CRD status. +type MigrationRunnerController struct { + svmListers svmlisters.StorageVersionMigrationLister + svmSynced cache.InformerSynced + queue workqueue.TypedRateLimitingInterface[metav1.GroupResource] + kubeClient clientset.Interface + crdClient apiextensionsclient.CustomResourceDefinitionInterface +} + +func NewCustomResourceController( + ctx context.Context, + kubeClient clientset.Interface, + svmInformer svminformers.StorageVersionMigrationInformer, + crdClient apiextensionsclient.CustomResourceDefinitionInterface, +) *MigrationRunnerController { + logger := klog.FromContext(ctx) + + crController := &MigrationRunnerController{ + kubeClient: kubeClient, + svmListers: svmInformer.Lister(), + svmSynced: svmInformer.Informer().HasSynced, + crdClient: crdClient, + queue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[metav1.GroupResource](), + workqueue.TypedRateLimitingQueueConfig[metav1.GroupResource]{Name: ResourceVersionControllerName}, + ), + } + + _, _ = svmInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + crController.addSVM(logger, obj) + }, + UpdateFunc: func(oldObj, newObj interface{}) { + crController.updateSVM(logger, oldObj, newObj) + }, + DeleteFunc: func(obj interface{}) { + crController.deleteSVM(logger, obj) + }, + }) + + return crController +} + +func (crc *MigrationRunnerController) addSVM(logger klog.Logger, obj interface{}) { + svm := obj.(*svmv1beta1.StorageVersionMigration) + logger.V(4).Info("Adding", "svm", klog.KObj(svm)) + crc.enqueue(svm) +} + +func (crc *MigrationRunnerController) updateSVM(logger klog.Logger, oldObj, newObj interface{}) { + oldSVM := oldObj.(*svmv1beta1.StorageVersionMigration) + newSVM := newObj.(*svmv1beta1.StorageVersionMigration) + logger.V(4).Info("Updating", "svm", klog.KObj(oldSVM)) + crc.enqueue(newSVM) +} + +func (crc *MigrationRunnerController) deleteSVM(logger klog.Logger, obj interface{}) { + svm, ok := obj.(*svmv1beta1.StorageVersionMigration) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + logger.Info("could not cast obj to DeletedFinalStateUnknown", "object", obj) + return + } + svm, ok = tombstone.Obj.(*svmv1beta1.StorageVersionMigration) + if !ok { + logger.Info("could not cast tombstone to SVM", "object", obj) + return + } + } + logger.V(4).Info("Deleting", "svm", klog.KObj(svm)) + crc.enqueue(svm) +} + +func (crc *MigrationRunnerController) enqueue(svm *svmv1beta1.StorageVersionMigration) { + crc.queue.Add(svm.Spec.Resource) +} + +func (crc *MigrationRunnerController) Run(ctx context.Context) { + defer utilruntime.HandleCrash() + + logger := klog.FromContext(ctx) + logger.Info("Starting", "controller", MigrationRunnerControllerName) + + var wg sync.WaitGroup + defer func() { + logger.Info("Shutting down", "controller", MigrationRunnerControllerName) + crc.queue.ShutDown() + wg.Wait() + }() + + if !cache.WaitForNamedCacheSyncWithContext(ctx, crc.svmSynced) { + return + } + + wg.Go(func() { + wait.UntilWithContext(ctx, crc.worker, time.Second) + }) + <-ctx.Done() +} + +func (crc *MigrationRunnerController) worker(ctx context.Context) { + for crc.processNext(ctx) { + } +} + +func (crc *MigrationRunnerController) processNext(ctx context.Context) bool { + key, quit := crc.queue.Get() + if quit { + return false + } + defer crc.queue.Done(key) + + err := crc.sync(ctx, key) + if err == nil { + crc.queue.Forget(key) + return true + } + + utilruntime.HandleErrorWithContext(ctx, err, "Error syncing SVM resource, retrying", "svm", key) + crc.queue.AddRateLimited(key) + + return true +} + +func (crc *MigrationRunnerController) sync(ctx context.Context, resource metav1.GroupResource) error { + // Get all SVMs for this resource and trigger re-sync + svmsToCleanup, svmToPromote, err := crc.getSVMsForResource(resource) + if err != nil { + return err + } + + for _, svm := range svmsToCleanup { + if err := crc.cleanupAdmission(ctx, svm); err != nil { + return err + } + } + + if svmToPromote != nil { + if err := crc.markAsActive(ctx, svmToPromote); err != nil { + return err + } + } + + return nil +} + +func (crc *MigrationRunnerController) cleanupAdmission(ctx context.Context, svm *svmv1beta1.StorageVersionMigration) error { + needsUpdate := false + migratingCond := meta.FindStatusCondition(svm.Status.Conditions, string(svmv1beta1.MigrationRunning)) + if migratingCond != nil && migratingCond.Status == metav1.ConditionTrue { + newCond := &metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionFalse, + Reason: "MigrationCompleted", + Message: "Migration completed", + ObservedGeneration: svm.Generation, + } + + meta.SetStatusCondition(&svm.Status.Conditions, *newCond) + needsUpdate = true + } + + if needsUpdate { + if err := crc.cleanupCRD(ctx, svm); err != nil { + return err + } + + _, err := crc.kubeClient.StoragemigrationV1beta1(). + StorageVersionMigrations(). + UpdateStatus( + ctx, + svm, + metav1.UpdateOptions{}, + ) + if err != nil { + return err + } + } + + return nil +} + +func (crc *MigrationRunnerController) markAsActive(ctx context.Context, svm *svmv1beta1.StorageVersionMigration) error { + // Mark CRD as undergoing migration. + crd, exists, err := crc.crdForGroupResource(ctx, svm.Spec.Resource) + if err != nil { + return err + } + if exists { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + crd, err := crc.crdClient.Get(ctx, crd.Name, metav1.GetOptions{}) + if err != nil { + return err + } + applyConfig := applyconfigurationapiextensionsv1.CustomResourceDefinition(crd.Name). + // We add resource version to ensure there were no changes to the CRD + // between our get and apply, since if there was that may affect the + // generation we set in the condition. + WithResourceVersion(crd.ResourceVersion). + WithStatus(applyconfigurationapiextensionsv1.CustomResourceDefinitionStatus(). + WithConditions(applyconfigurationapiextensionsv1.CustomResourceDefinitionCondition(). + WithType(apiextensionsv1.StorageMigrating). + WithStatus(apiextensionsv1.ConditionTrue). + WithLastTransitionTime(metav1.Now()). + WithReason("MigrationRunning"). + WithMessage(fmt.Sprintf("Migration %s is running", svm.Name)). + WithObservedGeneration(crd.Generation))) + + // TODO: We should see if there's a way to surface non-conflict/not-found errors + // to the SVM Status to let the user know that the migration is stuck due to issues + // outside of the controller's control. + _, err = crc.crdClient.ApplyStatus(ctx, applyConfig, metav1.ApplyOptions{FieldManager: MigrationRunnerControllerName, Force: true}) + return err + }) + if err != nil { + return err + } + } + + svm = setStatusConditions(svm, svmv1beta1.MigrationRunning, "MigrationRunning", "The migration is running") + _, err = crc.kubeClient.StoragemigrationV1beta1(). + StorageVersionMigrations(). + UpdateStatus( + ctx, + svm, + metav1.UpdateOptions{}, + ) + if err != nil { + return err + } + + return nil +} + +func compareSVM(a, b *svmv1beta1.StorageVersionMigration) int { + if i := a.CreationTimestamp.Compare(b.CreationTimestamp.Time); i != 0 { + return i + } + return strings.Compare(a.Name, b.Name) +} + +func (crc *MigrationRunnerController) getSVMsForResource(resource metav1.GroupResource) (svmsToCleanup []*svmv1beta1.StorageVersionMigration, svmToPromote *svmv1beta1.StorageVersionMigration, err error) { + svms, err := crc.svmListers.List(labels.Everything()) + if err != nil { + return nil, nil, err + } + svmInProgress := false + for _, svm := range svms { + if svm.Spec.Resource != resource { + continue + } + svmRunning := isRunning(svm) + svmTerminal := isTerminal(svm) + promotable := !svmRunning && !svmTerminal && !svmInProgress + switch { + case svmRunning && svmTerminal: + svmsToCleanup = append(svmsToCleanup, svm.DeepCopy()) + + case svmRunning: + svmInProgress = true + svmToPromote = nil + + case promotable && (svmToPromote == nil || compareSVM(svm, svmToPromote) == -1): + svmToPromote = svm + } + } + + if svmToPromote != nil { + svmToPromote = svmToPromote.DeepCopy() + } + return svmsToCleanup, svmToPromote, nil +} + +func isTerminal(svm *svmv1beta1.StorageVersionMigration) bool { + return meta.IsStatusConditionTrue(svm.Status.Conditions, string(svmv1beta1.MigrationSucceeded)) || + meta.IsStatusConditionTrue(svm.Status.Conditions, string(svmv1beta1.MigrationFailed)) +} + +func isRunning(svm *svmv1beta1.StorageVersionMigration) bool { + return meta.IsStatusConditionTrue(svm.Status.Conditions, string(svmv1beta1.MigrationRunning)) +} + +func (crc *MigrationRunnerController) cleanupCRD(ctx context.Context, toBeProcessedSVM *svmv1beta1.StorageVersionMigration) error { + migratingSuccessfulCond := meta.FindStatusCondition(toBeProcessedSVM.Status.Conditions, string(svmv1beta1.MigrationSucceeded)) + success := migratingSuccessfulCond != nil && migratingSuccessfulCond.Status == metav1.ConditionTrue + crd, exists, err := crc.crdForGroupResource(ctx, toBeProcessedSVM.Spec.Resource) + if err != nil { + return err + } + if !exists { + return nil + } + if err := crc.finishCRDMigration(ctx, crd, success); err != nil { + return err + } + + return nil +} + +func (crc *MigrationRunnerController) finishCRDMigration(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition, success bool) error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + crd, err := crc.crdClient.Get(ctx, crd.Name, metav1.GetOptions{}) + if err != nil { + return err + } + migratingCond := apihelpers.FindCRDCondition(crd, apiextensionsv1.StorageMigrating) + var canUpdateStoredVersions bool + if migratingCond != nil && migratingCond.ObservedGeneration == crd.Generation { + canUpdateStoredVersions = true + } + + var reason, msg string + if success { + reason = "MigrationSucceeded" + msg = "The migration has succeeded" + if canUpdateStoredVersions { + msg += " and the stored versions have been updated" + } else { + msg += " but the stored versions have not been updated due to a generation mismatch" + } + } else { + reason = "MigrationFailed" + msg = "The migration has failed" + } + + applyConfig := applyconfigurationapiextensionsv1.CustomResourceDefinition(crd.Name). + // We add resource version to ensure there were no changes to the CRD + // between our get and apply, since if there was that may affect the validity + // of the stored versions we are about to set. + WithResourceVersion(crd.ResourceVersion). + WithStatus(applyconfigurationapiextensionsv1.CustomResourceDefinitionStatus(). + WithConditions(applyconfigurationapiextensionsv1.CustomResourceDefinitionCondition(). + WithType(apiextensionsv1.StorageMigrating). + WithStatus(apiextensionsv1.ConditionFalse). + WithLastTransitionTime(metav1.Now()). + WithReason(reason). + WithMessage(msg). + WithObservedGeneration(crd.Generation))) + + if canUpdateStoredVersions { + // Wipe out all stored versions that we have migrated off of if nothing else + // has transpired while the migration was in progress. + for _, version := range crd.Spec.Versions { + if version.Storage { + applyConfig.Status.WithStoredVersions(version.Name) + break + } + } + } + + // TODO: We should see if there's a way to surface non-conflict/not-found errors + // to the SVM Status to let the user know that the migration is stuck due to issues + // outside of the controller's control. + _, err = crc.crdClient.ApplyStatus(ctx, applyConfig, metav1.ApplyOptions{FieldManager: MigrationRunnerControllerName, Force: true}) + return err + }) +} + +func (crc *MigrationRunnerController) crdForGroupResource(ctx context.Context, gr metav1.GroupResource) (*apiextensionsv1.CustomResourceDefinition, bool, error) { + crdName := fmt.Sprintf("%s.%s", gr.Resource, gr.Group) + crd, err := crc.crdClient.Get(ctx, crdName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return nil, false, nil + } + if err != nil { + return nil, false, err + } + return crd, true, nil +} diff --git a/pkg/controller/storageversionmigrator/migrationrunner_test.go b/pkg/controller/storageversionmigrator/migrationrunner_test.go new file mode 100644 index 0000000000000..9320d665c1c22 --- /dev/null +++ b/pkg/controller/storageversionmigrator/migrationrunner_test.go @@ -0,0 +1,471 @@ +/* +Copyright 2025 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 storageversionmigrator + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" + svmv1beta1 "k8s.io/api/storagemigration/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" + apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + svminformers "k8s.io/client-go/informers" + kubefake "k8s.io/client-go/kubernetes/fake" + k8stesting "k8s.io/client-go/testing" +) + +func init() { + _ = apiextensionsv1.AddToScheme(apiextensionsscheme.Scheme) +} + +func TestCustomResourceController_Sync(t *testing.T) { + newSVM := func(name, group, resource string, conditions ...metav1.Condition) *svmv1beta1.StorageVersionMigration { + return &svmv1beta1.StorageVersionMigration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: svmv1beta1.StorageVersionMigrationSpec{ + Resource: metav1.GroupResource{Group: group, Resource: resource}, + }, + Status: svmv1beta1.StorageVersionMigrationStatus{ + Conditions: conditions, + }, + } + } + + newCRD := func(name string, generation int64, storageVersion string) *apiextensionsv1.CustomResourceDefinition { + return &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Generation: generation, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + {Name: "v1", Storage: storageVersion == "v1"}, + {Name: "v2", Storage: storageVersion == "v2"}, + }, + }, + } + } + + newCRDPatchAction := func(name string, conditions []apiextensionsv1.CustomResourceDefinitionCondition, storedVersions []string) k8stesting.PatchAction { + statusMap := map[string]interface{}{ + "conditions": conditions, + } + if storedVersions != nil { + statusMap["storedVersions"] = storedVersions + } + patchObj := map[string]interface{}{ + "status": statusMap, + } + patch, err := json.Marshal(patchObj) + if err != nil { + panic(err) + } + return k8stesting.NewPatchAction( + apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions"), + "", + name, + types.MergePatchType, + patch, + ) + } + + testCases := []struct { + name string + svms []*svmv1beta1.StorageVersionMigration + crd *apiextensionsv1.CustomResourceDefinition + expectErr bool + expectKubeActions []k8stesting.Action + expectCRDActions []k8stesting.Action + }{ + { + name: "Initial sync: CRD missing", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets")}, + expectErr: false, + expectKubeActions: []k8stesting.Action{ + k8stesting.NewUpdateAction( + svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), + "", + newSVM("test-svm", "example.com", "widgets", metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + Reason: "MigrationRunning", + Message: "The migration is running", + }), + ), + }, + }, + { + name: "Initial sync: CRD exists", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets")}, + crd: newCRD("widgets.example.com", 1, "v2"), + expectErr: false, + expectKubeActions: []k8stesting.Action{ + k8stesting.NewUpdateAction( + svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), + "", + newSVM("test-svm", "example.com", "widgets", metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + Reason: "MigrationRunning", + Message: "The migration is running", + }), + ), + }, + expectCRDActions: []k8stesting.Action{ + newCRDPatchAction( + "widgets.example.com", + []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.StorageMigrating, + Status: apiextensionsv1.ConditionTrue, + Reason: "MigrationRunning", + Message: "Migration test-svm is running", + }, + }, + nil, + ), + }, + }, + { + name: "Migration running (No-op)", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets", metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + })}, + crd: newCRD("widgets.example.com", 1, "v2"), + expectErr: false, + }, + { + name: "Migration succeeded: Update CRD stored versions", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets", + metav1.Condition{ + Type: string(svmv1beta1.MigrationSucceeded), + Status: metav1.ConditionTrue, + }, + metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + )}, + crd: func() *apiextensionsv1.CustomResourceDefinition { + c := newCRD("widgets.example.com", 1, "v2") + c.Status.Conditions = []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.StorageMigrating, + Status: apiextensionsv1.ConditionTrue, + ObservedGeneration: 1, + Reason: "MigrationRunning", + }, + } + return c + }(), + expectErr: false, + expectCRDActions: []k8stesting.Action{ + newCRDPatchAction( + "widgets.example.com", + []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.StorageMigrating, + Status: apiextensionsv1.ConditionFalse, + Reason: "MigrationSucceeded", + Message: "The migration has succeeded and the stored versions have been updated", + }, + }, + []string{"v2"}, + ), + }, + }, + { + name: "Migration succeeded: CRD generation mismatch (No update)", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets", + metav1.Condition{ + Type: string(svmv1beta1.MigrationSucceeded), + Status: metav1.ConditionTrue, + }, + metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + )}, + crd: newCRD("widgets.example.com", 2, "v2"), + expectErr: false, + expectCRDActions: []k8stesting.Action{ + newCRDPatchAction( + "widgets.example.com", + []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.StorageMigrating, + Status: apiextensionsv1.ConditionFalse, + Reason: "MigrationSucceeded", + Message: "The migration has succeeded but the stored versions have not been updated due to a generation mismatch", + }, + }, + nil, + ), + }, + }, + { + name: "Migration failed: CRD condition updated", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets", + metav1.Condition{ + Type: string(svmv1beta1.MigrationFailed), + Status: metav1.ConditionTrue, + }, + metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + )}, + crd: func() *apiextensionsv1.CustomResourceDefinition { + c := newCRD("widgets.example.com", 1, "v2") + c.Status.Conditions = []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.StorageMigrating, + Status: apiextensionsv1.ConditionTrue, + ObservedGeneration: 1, + Reason: "MigrationRunning", + }, + } + return c + }(), + expectErr: false, + expectCRDActions: []k8stesting.Action{ + newCRDPatchAction( + "widgets.example.com", + []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.StorageMigrating, + Status: apiextensionsv1.ConditionFalse, + Reason: "MigrationFailed", + Message: "The migration has failed", + }, + }, + []string{"v2"}, + ), + }, + }, + { + name: "Cleanup admission updates SVM condition", + svms: []*svmv1beta1.StorageVersionMigration{newSVM("test-svm", "example.com", "widgets", + metav1.Condition{ + Type: string(svmv1beta1.MigrationSucceeded), + Status: metav1.ConditionTrue, + }, + metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + Reason: "MigrationRunning", + ObservedGeneration: 1, + }, + )}, + expectErr: false, + expectKubeActions: []k8stesting.Action{ + k8stesting.NewUpdateSubresourceAction( + svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), + "status", + "", + newSVM("test-svm", "example.com", "widgets", + metav1.Condition{ + Type: string(svmv1beta1.MigrationSucceeded), + Status: metav1.ConditionTrue, + }, + metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionFalse, + Reason: "MigrationCompleted", + }, + ), + ), + }, + }, + { + name: "Promote older SVM first", + svms: []*svmv1beta1.StorageVersionMigration{ + func() *svmv1beta1.StorageVersionMigration { + svm := newSVM("test-svm-newer", "example.com", "widgets") + svm.CreationTimestamp = metav1.Time{Time: time.Now().Add(1 * time.Hour)} + return svm + }(), + func() *svmv1beta1.StorageVersionMigration { + svm := newSVM("test-svm-older", "example.com", "widgets") + svm.CreationTimestamp = metav1.Time{Time: time.Now().Add(-1 * time.Hour)} + return svm + }(), + }, + expectErr: false, + expectKubeActions: []k8stesting.Action{ + k8stesting.NewUpdateAction( + svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), + "", + newSVM("test-svm-older", "example.com", "widgets", metav1.Condition{ + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + Reason: "MigrationRunning", + Message: "The migration is running", + }), + ), + }, + }, + } + + filterActions := func(actions []k8stesting.Action) []k8stesting.Action { + var ret []k8stesting.Action + for _, action := range actions { + if action.GetVerb() == "list" || action.GetVerb() == "watch" || action.GetVerb() == "get" { + continue + } + ret = append(ret, action) + } + return ret + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + var initialSVMs []runtime.Object + for _, svm := range tc.svms { + initialSVMs = append(initialSVMs, svm) + } + kubeClient := kubefake.NewClientset(initialSVMs...) + + kubeInformerFactory := svminformers.NewSharedInformerFactory(kubeClient, 0) + svmInformer := kubeInformerFactory.Storagemigration().V1beta1().StorageVersionMigrations() + for _, svm := range tc.svms { + err := svmInformer.Informer().GetStore().Add(svm) + require.NoError(t, err) + } + + var initialCRDs []runtime.Object + if tc.crd != nil { + initialCRDs = append(initialCRDs, tc.crd) + } + crdClientSet := apiextensionsfake.NewClientset(initialCRDs...) + + crdScheme := runtime.NewScheme() + _ = apiextensionsv1.AddToScheme(crdScheme) + crdTracker := k8stesting.NewObjectTracker(crdScheme, serializer.NewCodecFactory(crdScheme).UniversalDecoder()) + + for _, obj := range initialCRDs { + _ = crdTracker.Add(obj) + } + + crdClientSet.PrependReactor("*", "*", k8stesting.ObjectReaction(crdTracker)) + + controller := NewCustomResourceController( + ctx, + kubeClient, + svmInformer, + crdClientSet.ApiextensionsV1().CustomResourceDefinitions(), + ) + + resource := metav1.GroupResource{Group: "example.com", Resource: "widgets"} + if len(tc.svms) > 0 { + resource = tc.svms[0].Spec.Resource + } + + err := controller.sync(ctx, resource) + + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + if tc.expectKubeActions != nil { + kubeActions := filterActions(kubeClient.Actions()) + require.Len(t, kubeActions, len(tc.expectKubeActions), "mismatched number of kube client actions") + + for i, expected := range tc.expectKubeActions { + actual := kubeActions[i] + require.Equal(t, expected.GetVerb(), actual.GetVerb(), "kube action %d: verb mismatch", i) + require.Equal(t, expected.GetResource(), actual.GetResource(), "kube action %d: resource mismatch", i) + + if updateAction, ok := actual.(k8stesting.UpdateAction); ok { + actualObj := updateAction.GetObject().(*svmv1beta1.StorageVersionMigration) + expectedObj := expected.(k8stesting.UpdateAction).GetObject().(*svmv1beta1.StorageVersionMigration) + + require.Equal(t, expectedObj.Name, actualObj.Name, "object name mismatch") + require.Len(t, actualObj.Status.Conditions, len(expectedObj.Status.Conditions), "condition count mismatch") + for j, expCond := range expectedObj.Status.Conditions { + actCond := actualObj.Status.Conditions[j] + require.Equal(t, expCond.Type, actCond.Type, "condition type mismatch") + require.Equal(t, expCond.Status, actCond.Status, "condition status mismatch") + if expCond.Reason != "" { + require.Equal(t, expCond.Reason, actCond.Reason, "condition reason mismatch") + } + } + } + } + } + + // Verify CRD status updates + if tc.expectCRDActions != nil { + crdActions := filterActions(crdClientSet.Actions()) + require.Len(t, crdActions, len(tc.expectCRDActions), "mismatched number of crd client actions") + + for i, expected := range tc.expectCRDActions { + actual := crdActions[i] + require.Equal(t, expected.GetVerb(), actual.GetVerb(), "crd action %d: verb mismatch", i) + + if patchAction, ok := actual.(k8stesting.PatchAction); ok { + actualPatch := patchAction.GetPatch() + expectedPatch := expected.(k8stesting.PatchAction).GetPatch() + + var actualMap, expectedMap map[string]interface{} + require.NoError(t, json.Unmarshal(actualPatch, &actualMap)) + require.NoError(t, json.Unmarshal(expectedPatch, &expectedMap)) + + // Helper to verify conditions ignoring LastTransitionTime + actualStatus := actualMap["status"].(map[string]interface{}) + expectedStatus := expectedMap["status"].(map[string]interface{}) + + if expectedConds, ok := expectedStatus["conditions"].([]interface{}); ok { + actualConds := actualStatus["conditions"].([]interface{}) + require.Len(t, actualConds, len(expectedConds)) + for k, expC := range expectedConds { + expCondMap := expC.(map[string]interface{}) + actCondMap := actualConds[k].(map[string]interface{}) + require.Equal(t, expCondMap["type"], actCondMap["type"]) + require.Equal(t, expCondMap["status"], actCondMap["status"]) + require.Equal(t, expCondMap["reason"], actCondMap["reason"]) + require.Equal(t, expCondMap["message"], actCondMap["message"]) + } + // Remove conditions for deep equal of the rest + delete(actualStatus, "conditions") + delete(expectedStatus, "conditions") + } + + require.Equal(t, expectedStatus, actualStatus) + } + } + } + }) + } +} diff --git a/pkg/controller/storageversionmigrator/resourceversion.go b/pkg/controller/storageversionmigrator/resourceversion.go index 2c708288933ee..fc6bc345bfa6d 100644 --- a/pkg/controller/storageversionmigrator/resourceversion.go +++ b/pkg/controller/storageversionmigrator/resourceversion.go @@ -189,6 +189,11 @@ func (rv *ResourceVersionController) sync(ctx context.Context, key string) error } // working with copy to avoid race condition between this and migration controller toBeProcessedSVM := svm.DeepCopy() + + if !meta.IsStatusConditionTrue(toBeProcessedSVM.Status.Conditions, string(svmv1beta1.MigrationRunning)) { + logger.V(4).Info("Migration is not running yet, skipping", "svm", name) + return nil + } gr := toBeProcessedSVM.Spec.Resource if meta.IsStatusConditionTrue(toBeProcessedSVM.Status.Conditions, string(svmv1beta1.MigrationSucceeded)) || diff --git a/pkg/controller/storageversionmigrator/resourceversion_test.go b/pkg/controller/storageversionmigrator/resourceversion_test.go index 60e4bd40cc1a8..b25737b0c78c8 100644 --- a/pkg/controller/storageversionmigrator/resourceversion_test.go +++ b/pkg/controller/storageversionmigrator/resourceversion_test.go @@ -206,10 +206,20 @@ func TestRVSync(t *testing.T) { expectErr string expectKubeActions []kubetesting.Action }{ + { + name: "Migration not active", + key: "inactive-svm", + svm: newSVMWithConditions("inactive-svm", "", []metav1.Condition{}), + }, { name: "Successful RV acquisition", key: "test-svm", - svm: newSVM("test-svm", ""), + svm: newSVMWithConditions("test-svm", "", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + }), discoveryResources: &metav1.APIResourceList{ GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ @@ -225,7 +235,12 @@ func TestRVSync(t *testing.T) { kubetesting.NewUpdateAction( svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", - newSVM("test-svm", "12345"), + newSVMWithConditions("test-svm", "12345", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + }), ), }, }, @@ -234,6 +249,11 @@ func TestRVSync(t *testing.T) { key: "non-existent-svm", svm: nil, }, + { + name: "SVM has no CRD condition", + key: "succeeded-svm", + svm: newSVM("succeeded-svm", ""), + }, { name: "SVM already succeeded", key: "succeeded-svm", @@ -257,12 +277,22 @@ func TestRVSync(t *testing.T) { { name: "RV already set", key: "rv-set-svm", - svm: newSVM("rv-set-svm", "123"), + svm: newSVMWithConditions("rv-set-svm", "123", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + }), }, { name: "Resource not migratable", key: "not-migratable-svm", - svm: newSVM("not-migratable-svm", ""), + svm: newSVMWithConditions("not-migratable-svm", "", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + }), discoveryResources: &metav1.APIResourceList{ GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ @@ -274,6 +304,10 @@ func TestRVSync(t *testing.T) { svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", newSVMWithConditions("not-migratable-svm", "", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, { Type: string(svmv1beta1.MigrationFailed), Status: metav1.ConditionTrue, @@ -285,7 +319,12 @@ func TestRVSync(t *testing.T) { { name: "Metadata list error", key: "metadata-error-svm", - svm: newSVM("metadata-error-svm", ""), + svm: newSVMWithConditions("metadata-error-svm", "", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + }), discoveryResources: &metav1.APIResourceList{ GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ @@ -299,7 +338,12 @@ func TestRVSync(t *testing.T) { { name: "Invalid RV returned", key: "invalid-rv-svm", - svm: newSVM("invalid-rv-svm", ""), + svm: newSVMWithConditions("invalid-rv-svm", "", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, + }), discoveryResources: &metav1.APIResourceList{ GroupVersion: "apps/v1", APIResources: []metav1.APIResource{ @@ -316,6 +360,10 @@ func TestRVSync(t *testing.T) { svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", newSVMWithConditions("invalid-rv-svm", "", []metav1.Condition{ + { + Type: string(svmv1beta1.MigrationRunning), + Status: metav1.ConditionTrue, + }, { Type: string(svmv1beta1.MigrationFailed), Status: metav1.ConditionTrue, diff --git a/pkg/controller/storageversionmigrator/storageversionmigrator.go b/pkg/controller/storageversionmigrator/storageversionmigrator.go index 8d88c2892681a..7efdb4c9d512a 100644 --- a/pkg/controller/storageversionmigrator/storageversionmigrator.go +++ b/pkg/controller/storageversionmigrator/storageversionmigrator.go @@ -224,7 +224,7 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error { return err } if !exists { - logger.V(4).Error(err, "resource does not exist in our rest mapper", "gvr", gvr.String()) + logger.V(4).Info("resource does not exist in our rest mapper", "gvr", gvr.String()) if toBeProcessedSVM.CreationTimestamp.Add(time.Minute).After(time.Now()) { return fmt.Errorf("resource does not exist in rest mapper, requeuing to attempt again: %w", err) } @@ -241,7 +241,7 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error { return errMonitor } if !hasSynced { - logger.V(4).Error(errMonitor, "resource does not exist in GC", "gvr", gvr.String()) + logger.V(4).Info("resource does not exist in GC", "gvr", gvr.String()) // our mapper could be missing a recently created custom resource, so give it some time to catch up // we resync discovery every 30 seconds so twice that should be sufficient @@ -262,18 +262,7 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error { return fmt.Errorf("GC cache is not up to date, requeuing to attempt again. gcListResourceVersion: %s, listResourceVersion: %s", gcListResourceVersion, listResourceVersion) } - toBeProcessedSVM, err = svmc.kubeClient.StoragemigrationV1beta1(). - StorageVersionMigrations(). - UpdateStatus( - ctx, - setStatusConditions(toBeProcessedSVM, svmv1beta1.MigrationRunning, migrationRunningStatusReason, ""), - metav1.UpdateOptions{}, - ) - if err != nil { - return err - } - - err, failedMigration := svmc.runMigration(ctx, logger, *gvr, resourceMonitor, toBeProcessedSVM, listResourceVersion) + err, failedMigration := svmc.runMigration(ctx, *gvr, resourceMonitor, toBeProcessedSVM, listResourceVersion) if err != nil { return err } @@ -296,11 +285,12 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error { return nil } -func (svmc *SVMController) runMigration(ctx context.Context, logger klog.Logger, gvr schema.GroupVersionResource, resourceMonitor *garbagecollector.Monitor, toBeProcessedSVM *svmv1beta1.StorageVersionMigration, listResourceVersion string) (err error, failed bool) { +func (svmc *SVMController) runMigration(ctx context.Context, gvr schema.GroupVersionResource, resourceMonitor *garbagecollector.Monitor, toBeProcessedSVM *svmv1beta1.StorageVersionMigration, listResourceVersion string) (err error, failed bool) { gvk, err := svmc.restMapper.KindFor(gvr) if err != nil { return svmc.failMigration(ctx, toBeProcessedSVM, err), true } + logger := klog.FromContext(ctx) for _, obj := range resourceMonitor.Store.List() { accessor, err := meta.Accessor(obj) if err != nil { @@ -308,7 +298,7 @@ func (svmc *SVMController) runMigration(ctx context.Context, logger klog.Logger, } rvCmp, err := resourceversion.CompareResourceVersion(accessor.GetResourceVersion(), listResourceVersion) if err != nil { - logger.V(4).Error(err, "Unable to compare the resource version of the resource", "namespace", accessor.GetNamespace(), "name", accessor.GetName(), "gvr", gvr.String(), "accessorRV", accessor.GetResourceVersion(), "listResourceVersion", listResourceVersion, "error", err.Error()) + logger.Error(err, "Unable to compare the resource version of the resource", "namespace", accessor.GetNamespace(), "name", accessor.GetName(), "gvr", gvr.String(), "accessorRV", accessor.GetResourceVersion(), "listResourceVersion", listResourceVersion, "error", err.Error()) return svmc.failMigration(ctx, toBeProcessedSVM, err), true } if rvCmp == 1 { @@ -358,7 +348,7 @@ func (svmc *SVMController) runMigration(ctx context.Context, logger klog.Logger, } if errPatch != nil { - logger.V(4).Error(errPatch, "Failed to migrate the resource", "namespace", accessor.GetNamespace(), "name", accessor.GetName(), "gvr", gvr.String(), "reason", apierrors.ReasonForError(errPatch)) + logger.Error(errPatch, "Failed to migrate the resource", "namespace", accessor.GetNamespace(), "name", accessor.GetName(), "gvr", gvr.String(), "reason", apierrors.ReasonForError(errPatch)) errStatus := svmc.failMigration(ctx, toBeProcessedSVM, errPatch) return errStatus, true } diff --git a/pkg/controller/storageversionmigrator/storageversionmigrator_test.go b/pkg/controller/storageversionmigrator/storageversionmigrator_test.go index 01abee132482d..9393e31739d6c 100644 --- a/pkg/controller/storageversionmigrator/storageversionmigrator_test.go +++ b/pkg/controller/storageversionmigrator/storageversionmigrator_test.go @@ -175,22 +175,10 @@ func TestSync(t *testing.T) { }, expectErr: false, expectKubeActions: []k8stesting.Action{ - k8stesting.NewUpdateAction( - svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), - "", - newSVMWithConditions("test-svm", "100", []metav1.Condition{{ - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionTrue, - }}), - ), k8stesting.NewUpdateAction( svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionFalse, - }, { Type: string(svmv1beta1.MigrationSucceeded), Status: metav1.ConditionTrue, @@ -268,22 +256,10 @@ func TestSync(t *testing.T) { }, expectErr: false, expectKubeActions: []k8stesting.Action{ - k8stesting.NewUpdateAction( - svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), - "", - newSVMWithConditions("test-svm", "100", []metav1.Condition{{ - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionTrue, - }}), - ), k8stesting.NewUpdateAction( svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionFalse, - }, { Type: string(svmv1beta1.MigrationFailed), Status: metav1.ConditionTrue, @@ -311,20 +287,6 @@ func TestSync(t *testing.T) { svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionTrue, - }, - }), - ), - k8stesting.NewUpdateAction( - svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), - "", - newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionFalse, - }, { Type: string(svmv1beta1.MigrationSucceeded), Status: metav1.ConditionTrue, @@ -359,18 +321,6 @@ func TestSync(t *testing.T) { "ns1/res1": apierrors.NewTooManyRequests("simulating throttling", 1), }, expectErr: true, - expectKubeActions: []k8stesting.Action{ - k8stesting.NewUpdateAction( - svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), - "", - newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionTrue, - }, - }), - ), - }, expectDynamicActions: []k8stesting.Action{ k8stesting.NewPatchAction(testGVR, "ns1", "res1", types.ApplyPatchType, mustMarshal(t, typeMetaUIDRV{ TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"}, @@ -422,20 +372,6 @@ func TestSync(t *testing.T) { svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), "", newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionTrue, - }, - }), - ), - k8stesting.NewUpdateAction( - svmv1beta1.SchemeGroupVersion.WithResource("storageversionmigrations"), - "", - newSVMWithConditions("test-svm", "100", []metav1.Condition{ - { - Type: string(svmv1beta1.MigrationRunning), - Status: metav1.ConditionFalse, - }, { Type: string(svmv1beta1.MigrationFailed), Status: metav1.ConditionTrue, @@ -484,7 +420,7 @@ func TestSync(t *testing.T) { if tc.expectKubeActions != nil { kubeActions := filterActions(kubeClient.Actions()) - require.Len(t, kubeActions, len(tc.expectKubeActions), "mismatched number of kube client actions") + require.Len(t, kubeActions, len(tc.expectKubeActions), "mismatched number of kube client actions, expected %d, got %d", len(tc.expectKubeActions), len(kubeActions)) for i, expected := range tc.expectKubeActions { actual := kubeActions[i] diff --git a/pkg/controller/storageversionmigrator/util.go b/pkg/controller/storageversionmigrator/util.go index 78f64e605e0ea..0c7d7b2b25380 100644 --- a/pkg/controller/storageversionmigrator/util.go +++ b/pkg/controller/storageversionmigrator/util.go @@ -34,14 +34,6 @@ func setStatusConditions( return toBeUpdatedSVM } - if conditionType == svmv1beta1.MigrationSucceeded || conditionType == svmv1beta1.MigrationFailed { - // set running condition to false if we're finished - runningCond := meta.FindStatusCondition(toBeUpdatedSVM.Status.Conditions, string(svmv1beta1.MigrationRunning)) - if runningCond != nil { - runningCond.Status = metav1.ConditionFalse - } - } - toBeUpdatedSVM.Status.Conditions = append(toBeUpdatedSVM.Status.Conditions, metav1.Condition{ Type: string(conditionType), Status: metav1.ConditionTrue, diff --git a/pkg/controller/testutil/test_utils.go b/pkg/controller/testutil/test_utils.go index 77a731fa6cc24..d29ab68be702c 100644 --- a/pkg/controller/testutil/test_utils.go +++ b/pkg/controller/testutil/test_utils.go @@ -280,8 +280,9 @@ func (m *FakeNodeHandler) PatchStatus(ctx context.Context, nodeName string, data } // Watch watches Nodes in a fake store. -func (m *FakeNodeHandler) Watch(_ context.Context, opts metav1.ListOptions) (watch.Interface, error) { - return watch.NewFake(), nil +func (m *FakeNodeHandler) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + logger := klog.FromContext(ctx) + return watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}), nil } // Patch patches a Node in the fake store. diff --git a/pkg/controller/util/consistency/consistency.go b/pkg/controller/util/consistency/consistency.go new file mode 100644 index 0000000000000..7c727fb21b244 --- /dev/null +++ b/pkg/controller/util/consistency/consistency.go @@ -0,0 +1,217 @@ +/* +Copyright 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 consistency + +import ( + "fmt" + "sync" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/resourceversion" +) + +type ConsistencyStore interface { + // WroteAt records a written RV for an owned resource. + WroteAt(owner types.NamespacedName, ownerUID types.UID, resource schema.GroupResource, rv string) + // Clear wipes the owner if the UID matches, if left empty it will wipe no + // matter what the UID is. + Clear(owner types.NamespacedName, ownerUID types.UID) + // EnsureReady queries the ConsistencyStore to check whether or not the + // stores records are up to date, returning an error if they are not. + EnsureReady(owner types.NamespacedName) error +} + +// ConsistencyError is an error type returned by EnsureReady with information +// about the resource versions and GroupKind that caused the error. +type ConsistencyError struct { + ReadRV string + WroteRV string + GroupResource schema.GroupResource +} + +func (c *ConsistencyError) Error() string { + return fmt.Sprintf("read version: %s is not as new as written version: %s for group resource %s", c.ReadRV, c.WroteRV, c.GroupResource.String()) +} + +var _ ConsistencyStore = &RealConsistencyStore{} + +type LastSyncRVGetter interface { + LastStoreSyncResourceVersion() string +} + +type RealConsistencyStore struct { + // writesLock guards reads/additions/deletions to the writes map. + // individual records are responsible for managing their own thread safety. + writesLock sync.RWMutex + // writes is a map of owner -> ownerRecord + writes map[types.NamespacedName]*ownerRecord + + stores map[schema.GroupResource]LastSyncRVGetter +} + +func NewConsistencyStore(stores map[schema.GroupResource]LastSyncRVGetter) *RealConsistencyStore { + return &RealConsistencyStore{ + writes: map[types.NamespacedName]*ownerRecord{}, + stores: stores, + } +} + +// getWrittenRecord returns the record for the given owner, or nil if no record exists. +func (c *RealConsistencyStore) getWrittenRecord(owner types.NamespacedName) *ownerRecord { + c.writesLock.RLock() + defer c.writesLock.RUnlock() + return c.writes[owner] +} + +// ensureWrittenRecord returns a ownerRecord for the given owner and ownerUID. +// If there is no current record, one is created. +// If there is a current record with a different ownerUID, it is replaced with an empty record for the specified ownerUID. +func (c *RealConsistencyStore) ensureWrittenRecord(owner types.NamespacedName, ownerUID types.UID) *ownerRecord { + // fast path, already exists + if record := c.getWrittenRecord(owner); record != nil && record.ownerUID == ownerUID { + return record + } + + // slow path, init + c.writesLock.Lock() + defer c.writesLock.Unlock() + // check again after write lock + if record := c.writes[owner]; record != nil && record.ownerUID == ownerUID { + return record + } + // initialize to the given uid + record := newOwnerRecord(ownerUID) + c.writes[owner] = record + return record +} + +// WroteAt writes the latest written RV if it is greater than the currently +// written RV for the owner. +func (c *RealConsistencyStore) WroteAt(owner types.NamespacedName, ownerUID types.UID, resource schema.GroupResource, rv string) { + c.ensureWrittenRecord(owner, ownerUID).WroteAt(resource, rv) +} + +// Clear deletes the record for owner if it exists and matches the specified +// ownerUID (or the specified ownerUID is empty) +func (c *RealConsistencyStore) Clear(owner types.NamespacedName, ownerUID types.UID) { + // deleted owners typically have an existing record, not worth checking the fast path for missing records + c.writesLock.Lock() + defer c.writesLock.Unlock() + if record := c.writes[owner]; record != nil && (len(ownerUID) == 0 || record.ownerUID == ownerUID) { + delete(c.writes, owner) + } +} + +// EnsureReady returns nil if observed resource versions are at least as new as +// any recorded versions for the given owner, otherwise returning the error of +// what happened. Must not be called concurrent with WroteAt for the same owner. +func (c *RealConsistencyStore) EnsureReady(owner types.NamespacedName) error { + record := c.getWrittenRecord(owner) + if record == nil { + return nil + } + err := record.EnsureReady(c) + if err == nil { + c.Clear(owner, record.ownerUID) + return nil + } + return err +} + +type ownerRecord struct { + // ownerUID must not be mutated after creation + ownerUID types.UID + + versionsLock sync.Mutex + versions map[schema.GroupResource]string +} + +func newOwnerRecord(ownerUID types.UID) *ownerRecord { + return &ownerRecord{ownerUID: ownerUID, versions: map[schema.GroupResource]string{}} +} + +// WroteAt increments the written resource version of an ownerRecord if it is +// the newest seen resource version for that resource. +func (w *ownerRecord) WroteAt(resource schema.GroupResource, rv string) { + w.versionsLock.Lock() + defer w.versionsLock.Unlock() + if _, ok := w.versions[resource]; !ok { + w.versions[resource] = rv + return + } + cmp, err := resourceversion.CompareResourceVersion(w.versions[resource], rv) + if err == nil && cmp >= 0 { + return + } + w.versions[resource] = rv +} + +// EnsureReady checks whether or not the ownerRecord is ready compared to the +// read resource versions in the consistency store. +func (w *ownerRecord) EnsureReady(c *RealConsistencyStore) error { + w.versionsLock.Lock() + defer w.versionsLock.Unlock() + for gr, wroteRV := range w.versions { + store, exists := c.stores[gr] + if !exists || store == nil { + continue + } + readRV := store.LastStoreSyncResourceVersion() + if readRV == "" { + // Since we wait for the store to be ready, the only time "" is if the + // LastStoreSyncResourceVersion() feature is not enabled. + continue + } + i, err := resourceversion.CompareResourceVersion(wroteRV, readRV) + if err != nil { + // comparison errors indicate there's a data problem with resource versions, continue so we don't block syncing + continue + } + if i > 0 { + // read version is not as new as owner version, not ready + return &ConsistencyError{ + WroteRV: wroteRV, + ReadRV: readRV, + GroupResource: gr, + } + } + } + return nil +} + +// NoopConsistencyStore is a consistency store that stores nothing and always +// returns IsReady as true. To be used when the associated feature gate is not +// enabled. +type NoopConsistencyStore struct{} + +var _ ConsistencyStore = &NoopConsistencyStore{} + +func (*NoopConsistencyStore) WroteAt(owner types.NamespacedName, ownerUID types.UID, resource schema.GroupResource, rv string) { +} + +func (*NoopConsistencyStore) ReadAt(resource schema.GroupResource, rv string) {} + +func (*NoopConsistencyStore) Clear(owner types.NamespacedName, ownerUID types.UID) {} + +func (*NoopConsistencyStore) EnsureReady(owner types.NamespacedName) error { + return nil +} + +func NewNoopConsistencyStore() *NoopConsistencyStore { + return &NoopConsistencyStore{} +} diff --git a/pkg/controller/util/consistency/consistency_test.go b/pkg/controller/util/consistency/consistency_test.go new file mode 100644 index 0000000000000..114152da375c7 --- /dev/null +++ b/pkg/controller/util/consistency/consistency_test.go @@ -0,0 +1,292 @@ +/* +Copyright 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 consistency + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" +) + +func TestOwnerRecord_WroteAt(t *testing.T) { + uid := types.UID("owner-uid-1") + or := newOwnerRecord(uid) + assert.Equal(t, uid, or.ownerUID) + require.NotNil(t, or.versions) + + grPod := schema.GroupResource{Group: "", Resource: "pods"} + grDs := schema.GroupResource{Group: "apps", Resource: "daemonsets"} + + // First write + or.WroteAt(grPod, "5") + assert.Equal(t, "5", or.versions[grPod]) + + // Second write (higher) + or.WroteAt(grPod, "10") + assert.Equal(t, "10", or.versions[grPod]) + + // Third write (lower) + or.WroteAt(grPod, "8") + assert.Equal(t, "10", or.versions[grPod]) + + // Write to different resource + or.WroteAt(grDs, "1") + assert.Equal(t, "1", or.versions[grDs]) + assert.Equal(t, "10", or.versions[grPod], "Pod version should be unchanged") +} + +func TestOwnerRecord_IsReady(t *testing.T) { + uid := types.UID("owner-uid-1") + or := newOwnerRecord(uid) + grPod := schema.GroupResource{Group: "", Resource: "pods"} + grDs := schema.GroupResource{Group: "apps", Resource: "daemonsets"} + podStore := cache.NewStore(cache.MetaNamespaceKeyFunc) + dsStore := cache.NewStore(cache.MetaNamespaceKeyFunc) + resourceStores := map[schema.GroupResource]LastSyncRVGetter{ + grPod: podStore, + grDs: dsStore, + } + + store := NewConsistencyStore(resourceStores) + + // Case 1: No writes. Should be ready. + require.NoError(t, or.EnsureReady(store), "Should be ready if no writes are recorded") + + // Add a write + or.WroteAt(grPod, "10") + + // Case 2: Write exists, but no reads. Should stay ready. + require.NoError(t, or.EnsureReady(store), "Should stay ready if write exists and no read") + + // Add a read, but it's lower + podStore.Bookmark("5") + + // Case 3: Write exists, read is lower. Not ready. + require.Error(t, or.EnsureReady(store), "Not ready if read < write") + + // Add a read, equal + podStore.Bookmark("10") + + // Case 4: Write exists, read is equal. Ready. + require.NoError(t, or.EnsureReady(store), "Ready if read == write") + + // Add a read, higher + podStore.Bookmark("15") + + // Case 5: Write exists, read is higher. Ready. + require.NoError(t, or.EnsureReady(store), "Ready if read > write") + + // Add a second write and read + or.WroteAt(grDs, "100") + dsStore.Bookmark("50") + + // Case 6: One resource ready, one not. Not ready. + require.Error(t, or.EnsureReady(store), "Not ready if one of multiple writes is not ready (no read)") + + // Make the second one ready + dsStore.Bookmark("100") + + // Case 7: All resources ready. Ready. + require.NoError(t, or.EnsureReady(store), "Ready if all writes are ready") +} + +func TestConsistencyStore_New(t *testing.T) { + store := NewConsistencyStore(nil) + require.NotNil(t, store) + require.NotNil(t, store.writes) + assert.Empty(t, store.writes) +} + +func TestConsistencyStore_EnsureWrittenRecord(t *testing.T) { + store := NewConsistencyStore(nil) + owner := types.NamespacedName{Name: "owner1"} + uid1 := types.UID("uid-1") + uid2 := types.UID("uid-2") + + // Create new + r1 := store.ensureWrittenRecord(owner, uid1) + require.NotNil(t, r1) + assert.Equal(t, uid1, r1.ownerUID) + assert.Same(t, r1, store.writes[owner]) + + // Get existing with same UID + r2 := store.ensureWrittenRecord(owner, uid1) + assert.Same(t, r1, r2, "Should return existing record for same UID") + + // Get existing with different UID (should replace) + r3 := store.ensureWrittenRecord(owner, uid2) + require.NotNil(t, r3) + assert.NotSame(t, r1, r3, "Should be a new record") + assert.Equal(t, uid2, r3.ownerUID) + assert.Same(t, r3, store.writes[owner], "New record should replace old one in map") + assert.Empty(t, r3.versions, "New record should be empty") + + // Check that old record is detached + grPod := schema.GroupResource{Group: "", Resource: "pods"} + r1.WroteAt(grPod, "10") // Write to old record + assert.Empty(t, r3.versions, "Write to old record should not affect new record") +} + +func TestConsistencyStore_EnsureWrittenRecord_Concurrent(t *testing.T) { + store := NewConsistencyStore(nil) + owner := types.NamespacedName{Name: "owner1"} + uid1 := types.UID("uid-1") + uid2 := types.UID("uid-2") + + wg := sync.WaitGroup{} + numGoroutines := 50 + + // Concurrent creation with same UID + var firstRecord *ownerRecord + var once sync.Once + for range numGoroutines { + wg.Add(1) + go func() { + defer wg.Done() + r := store.ensureWrittenRecord(owner, uid1) + assert.Equal(t, uid1, r.ownerUID) + once.Do(func() { + firstRecord = r + }) + assert.Same(t, firstRecord, r) + }() + } + wg.Wait() + require.NotNil(t, firstRecord) + assert.Len(t, store.writes, 1) + + // Concurrent replacement with new UID + var replacementRecord *ownerRecord + var replaceOnce sync.Once + for range numGoroutines { + wg.Add(1) + go func() { + defer wg.Done() + r := store.ensureWrittenRecord(owner, uid2) + assert.Equal(t, uid2, r.ownerUID) + replaceOnce.Do(func() { + replacementRecord = r + }) + assert.Same(t, replacementRecord, r) + }() + } + wg.Wait() + require.NotNil(t, replacementRecord) + assert.Len(t, store.writes, 1) + assert.Same(t, replacementRecord, store.writes[owner]) + assert.NotSame(t, firstRecord, replacementRecord) +} + +func TestConsistencyStore_WroteAt(t *testing.T) { + store := NewConsistencyStore(nil) + owner := types.NamespacedName{Name: "owner1"} + uid1 := types.UID("uid-1") + grPod := schema.GroupResource{Group: "", Resource: "pods"} + + store.WroteAt(owner, uid1, grPod, "10") + + record := store.getWrittenRecord(owner) + require.NotNil(t, record) + assert.Equal(t, uid1, record.ownerUID) + + assert.Equal(t, "10", record.versions[grPod]) + + // Write again + store.WroteAt(owner, uid1, grPod, "20") + assert.Equal(t, "20", record.versions[grPod]) +} + +func TestConsistencyStore_Clear(t *testing.T) { + store := NewConsistencyStore(nil) + owner1 := types.NamespacedName{Name: "owner1"} + owner2 := types.NamespacedName{Name: "owner2"} + uid1 := types.UID("uid-1") + uid2 := types.UID("uid-2") + + // Setup + r1 := store.ensureWrittenRecord(owner1, uid1) + r2 := store.ensureWrittenRecord(owner2, uid2) + require.Len(t, store.writes, 2) + + // Clear non-existent + store.Clear(types.NamespacedName{Name: "non-existent"}, uid1) + assert.Len(t, store.writes, 2) + + // Clear with wrong UID + store.Clear(owner1, uid2) + assert.Len(t, store.writes, 2, "Should not clear with wrong UID") + assert.Same(t, r1, store.writes[owner1]) + + // Clear with correct UID + store.Clear(owner1, uid1) + assert.Len(t, store.writes, 1, "Should clear with correct UID") + assert.Nil(t, store.writes[owner1]) + assert.Same(t, r2, store.writes[owner2], "Other record should remain") + + // Re-add r1 + store.ensureWrittenRecord(owner1, uid1) + require.Len(t, store.writes, 2) + + // Clear with empty UID + store.Clear(owner1, "") + assert.Len(t, store.writes, 1, "Should clear with empty UID") + assert.Nil(t, store.writes[owner1]) + assert.Same(t, r2, store.writes[owner2]) +} + +func TestConsistencyStore_IsReady(t *testing.T) { + owner1 := types.NamespacedName{Name: "owner1"} + uid1 := types.UID("uid-1") + grPod := schema.GroupResource{Group: "", Resource: "pods"} + podStore := cache.NewStore(cache.MetaNamespaceKeyFunc) + resourceStores := map[schema.GroupResource]LastSyncRVGetter{ + grPod: podStore, + } + + store := NewConsistencyStore(resourceStores) + + // Case 1: No record. Ready. + require.NoError(t, store.EnsureReady(owner1), "Ready if no record exists") + + // Add a write and initial read rv + podStore.Bookmark("5") + store.WroteAt(owner1, uid1, grPod, "10") + + // Case 2: Record exists, read < write. Not ready. + require.Error(t, store.EnsureReady(owner1), "Not ready if read < write") + + // Add read, equal + podStore.Bookmark("10") + + // Case 3: Record exists, read == write. Ready. + require.NoError(t, store.EnsureReady(owner1), "Ready if read == write") + + // Add read, higher + podStore.Bookmark("15") + + // Case 4: Record exists, read > write. Ready. + require.NoError(t, store.EnsureReady(owner1), "Ready if read > write") + + // Assert that the record no longer exists, we no longer need to track the + // reads as long as the read has been higher than the latest write. + assert.Nil(t, store.getWrittenRecord(owner1), "Written record should no longer exist") +} diff --git a/pkg/controller/util/protectionutil/utils.go b/pkg/controller/util/protectionutil/utils.go new file mode 100644 index 0000000000000..86685c642c7d4 --- /dev/null +++ b/pkg/controller/util/protectionutil/utils.go @@ -0,0 +1,33 @@ +/* +Copyright 2019 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 protectionutil + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// IsDeletionCandidate checks if object is candidate to be deleted +func IsDeletionCandidate(obj metav1.Object, finalizer string) bool { + return obj.GetDeletionTimestamp() != nil && slices.Contains(obj.GetFinalizers(), finalizer) +} + +// NeedToAddFinalizer checks if need to add finalizer to object +func NeedToAddFinalizer(obj metav1.Object, finalizer string) bool { + return obj.GetDeletionTimestamp() == nil && !slices.Contains(obj.GetFinalizers(), finalizer) +} diff --git a/pkg/controller/volume/protectionutil/utils_test.go b/pkg/controller/util/protectionutil/utils_test.go similarity index 100% rename from pkg/controller/volume/protectionutil/utils_test.go rename to pkg/controller/util/protectionutil/utils_test.go diff --git a/pkg/controller/volume/protectionutil/wrappers.go b/pkg/controller/util/protectionutil/wrappers.go similarity index 100% rename from pkg/controller/volume/protectionutil/wrappers.go rename to pkg/controller/util/protectionutil/wrappers.go diff --git a/pkg/controller/volume/attachdetach/attach_detach_controller.go b/pkg/controller/volume/attachdetach/attach_detach_controller.go index be2f737e153d0..63f381ba1098e 100644 --- a/pkg/controller/volume/attachdetach/attach_detach_controller.go +++ b/pkg/controller/volume/attachdetach/attach_detach_controller.go @@ -31,7 +31,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" - utilfeature "k8s.io/apiserver/pkg/util/feature" coreinformers "k8s.io/client-go/informers/core/v1" storageinformersv1 "k8s.io/client-go/informers/storage/v1" clientset "k8s.io/client-go/kubernetes" @@ -181,7 +180,7 @@ func NewAttachDetachController( csiTranslator := csitrans.New() adc.intreeToCSITranslator = csiTranslator - adc.csiMigratedPluginManager = csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) + adc.csiMigratedPluginManager = csimigration.NewPluginManager(csiTranslator) adc.desiredStateOfWorldPopulator = populator.NewDesiredStateOfWorldPopulator( timerConfig.DesiredStateOfWorldPopulatorLoopSleepPeriod, @@ -509,7 +508,6 @@ func (adc *attachDetachController) podAdd(logger klog.Logger, obj interface{}) { volumeActionFlag := util.DetermineVolumeAction( pod, - adc.desiredStateOfWorld, true /* default volume action */) util.ProcessPodVolumes(logger, pod, volumeActionFlag, /* addVolumes */ @@ -533,7 +531,6 @@ func (adc *attachDetachController) podUpdate(logger klog.Logger, oldObj, newObj volumeActionFlag := util.DetermineVolumeAction( pod, - adc.desiredStateOfWorld, true /* default volume action */) util.ProcessPodVolumes(logger, pod, volumeActionFlag, /* addVolumes */ @@ -541,8 +538,12 @@ func (adc *attachDetachController) podUpdate(logger klog.Logger, oldObj, newObj } func (adc *attachDetachController) podDelete(logger klog.Logger, obj interface{}) { + if tombstone, ok := obj.(kcache.DeletedFinalStateUnknown); ok { + obj = tombstone.Obj + } pod, ok := obj.(*v1.Pod) - if pod == nil || !ok { + if !ok { + runtime.HandleError(fmt.Errorf("unexpected object type: %T", obj)) return } @@ -665,7 +666,6 @@ func (adc *attachDetachController) syncPVCByKey(logger klog.Logger, key string) } volumeActionFlag := util.DetermineVolumeAction( pod, - adc.desiredStateOfWorld, true /* default volume action */) util.ProcessPodVolumes(logger, pod, volumeActionFlag, /* addVolumes */ @@ -715,9 +715,7 @@ func (adc *attachDetachController) processVolumeAttachments(logger klog.Logger) var plugin volume.AttachableVolumePlugin volumeSpec := volume.NewSpecFromPersistentVolume(pv, false) - // Consult csiMigratedPluginManager first before querying the plugins registered during runtime in volumePluginMgr. - // In-tree plugins that provisioned PVs will not be registered anymore after migration to CSI, once the respective - // feature gate is enabled. + // If the PV has been migrated, translate the in-tree volumeSpec to CSI volumeSpec. if inTreePluginName, err := adc.csiMigratedPluginManager.GetInTreePluginNameFromSpec(pv, nil); err == nil { if adc.csiMigratedPluginManager.IsMigrationEnabledForPlugin(inTreePluginName) { // PV is migrated and should be handled by the CSI plugin instead of the in-tree one @@ -822,10 +820,6 @@ func (adc *attachDetachController) GetMounter() mount.Interface { return nil } -func (adc *attachDetachController) GetHostName() string { - return "" -} - func (adc *attachDetachController) GetNodeAllocatable() (v1.ResourceList, error) { return v1.ResourceList{}, nil } diff --git a/pkg/controller/volume/attachdetach/attach_detach_controller_test.go b/pkg/controller/volume/attachdetach/attach_detach_controller_test.go index f2d19bd111f9b..af4f34707defd 100644 --- a/pkg/controller/volume/attachdetach/attach_detach_controller_test.go +++ b/pkg/controller/volume/attachdetach/attach_detach_controller_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "runtime" + "slices" "sync" "testing" "time" @@ -74,25 +75,28 @@ func createADC(t testing.TB, tCtx ktesting.TContext, fakeKubeClient *fake.Client } func Test_NewAttachDetachController_Positive(t *testing.T) { + tCtx := ktesting.Init(t) + // Arrange - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(tCtx.Logger()) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) - tCtx := ktesting.Init(t) // Act createADC(t, tCtx, fakeKubeClient, informerFactory, nil) } func Test_AttachDetachControllerStateOfWorldPopulators_Positive(t *testing.T) { + tCtx := ktesting.Init(t) + logger := tCtx.Logger() + // Arrange - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) var plugins []volume.VolumePlugin plugins = append(plugins, controllervolumetesting.CreateTestPlugin(false)...) plugins = append(plugins, csi.ProbeVolumePlugins()...) - logger, tCtx := ktesting.NewTestContext(t) adc := createADC(t, tCtx, fakeKubeClient, informerFactory, plugins) // Act @@ -201,7 +205,7 @@ func BenchmarkPopulateActualStateOfWorld(b *testing.B) { fakeKubeClient := largeClusterClient(b, 10000) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) - logger, tCtx := ktesting.NewTestContext(b) + tCtx := ktesting.Init(b) adc := createADC(b, tCtx, fakeKubeClient, informerFactory, nil) // Act @@ -210,7 +214,7 @@ func BenchmarkPopulateActualStateOfWorld(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - err := adc.populateActualStateOfWorld(logger) + err := adc.populateActualStateOfWorld(tCtx.Logger()) if err != nil { b.Fatalf("Run failed with error. Expected: Actual: <%v>", err) } @@ -221,7 +225,8 @@ func BenchmarkNodeUpdate(b *testing.B) { fakeKubeClient := largeClusterClient(b, 3000) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) - logger, tCtx := ktesting.NewTestContext(b) + tCtx := ktesting.Init(b) + logger := tCtx.Logger() adc := createADC(b, tCtx, fakeKubeClient, informerFactory, nil) informerFactory.Start(tCtx.Done()) @@ -279,7 +284,10 @@ func Test_AttachDetachControllerRecovery(t *testing.T) { } func attachDetachRecoveryTestCase(t *testing.T, extraPods1 []*v1.Pod, extraPods2 []*v1.Pod) { - fakeKubeClient := controllervolumetesting.CreateTestClient() + tCtx := ktesting.Init(t) + logger := tCtx.Logger() + + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, time.Second*1) plugins := controllervolumetesting.CreateTestPlugin(true) var prober volume.DynamicPluginProber = nil // TODO (#51147) inject mock @@ -291,7 +299,6 @@ func attachDetachRecoveryTestCase(t *testing.T, extraPods1 []*v1.Pod, extraPods2 // Create the controller var wg sync.WaitGroup defer wg.Wait() - logger, tCtx := ktesting.NewTestContext(t) defer tCtx.Cancel("test case terminating") adcObj, err := NewAttachDetachController( @@ -534,7 +541,10 @@ func Test_ADC_VolumeAttachmentRecovery(t *testing.T) { } func volumeAttachmentRecoveryTestCase(t *testing.T, tc vaTest) { - fakeKubeClient := controllervolumetesting.CreateTestClient() + tCtx := ktesting.Init(t) + logger := tCtx.Logger() + + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, time.Second*1) var plugins []volume.VolumePlugin @@ -549,7 +559,6 @@ func volumeAttachmentRecoveryTestCase(t *testing.T, tc vaTest) { // Create the controller var wg sync.WaitGroup defer wg.Wait() - logger, tCtx := ktesting.NewTestContext(t) defer tCtx.Cancel("test case terminating") adc := createADC(t, tCtx, fakeKubeClient, informerFactory, plugins) @@ -708,13 +717,7 @@ func verifyAttachDetachCalls(t *testing.T, testPlugin *controllervolumetesting.T expectedNode, verify_op, tries) } for _, expectedVolume := range expectedVolumeList { - volFound = false - for _, volume := range volumeList { - if expectedVolume == volume { - volFound = true - break - } - } + volFound = slices.Contains(volumeList, expectedVolume) if !volFound && tries == 10 { t.Fatalf("Expected %v operation not found, node:%v, volume: %v, tries: %d", verify_op, expectedNode, expectedVolume, tries) @@ -732,3 +735,31 @@ func verifyAttachDetachCalls(t *testing.T, testPlugin *controllervolumetesting.T t.Fatalf("Fatal error encountered in the testing volume plugin") } } + +func TestPodDelete_Tombstone(t *testing.T) { + tCtx := ktesting.Init(t) + logger := tCtx.Logger() + + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) + informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) + + adc := createADC(t, tCtx, fakeKubeClient, informerFactory, + append(controllervolumetesting.CreateTestPlugin(false), csi.ProbeVolumePlugins()...)) + + pod := controllervolumetesting.NewPodWithVolume("pod1", "vol1", "node1") + volumeName := v1.UniqueVolumeName(csiPDUniqueNamePrefix + "pdName") + + adc.desiredStateOfWorld.AddNode("node1") + adc.podAdd(logger, pod) + + if !adc.desiredStateOfWorld.VolumeExists(volumeName, "node1") { + t.Fatalf("expected volume %s to exist in dsw", volumeName) + } + + // Tombstone + adc.podDelete(logger, kcache.DeletedFinalStateUnknown{Key: "mynamespace/pod1", Obj: pod}) + + if adc.desiredStateOfWorld.VolumeExists(volumeName, "node1") { + t.Errorf("expected volume %s to be removed from dsw", volumeName) + } +} diff --git a/pkg/controller/volume/attachdetach/cache/actual_state_of_world_test.go b/pkg/controller/volume/attachdetach/cache/actual_state_of_world_test.go index 1e4afa312d668..5e80019093845 100644 --- a/pkg/controller/volume/attachdetach/cache/actual_state_of_world_test.go +++ b/pkg/controller/volume/attachdetach/cache/actual_state_of_world_test.go @@ -629,7 +629,7 @@ func Test_GetAttachedVolumes_Positive_OneVolumeTwoNodes(t *testing.T) { t.Fatalf("Failed to get volume plugin from spec %v, %v", volumeSpec, err) } uniqueVolumeName, err := volumeutil.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) - if err != nil || plugin == nil { + if err != nil || uniqueVolumeName == "" { t.Fatalf("Failed to get uniqueVolumeName from spec %v, %v", volumeSpec, err) } logger, _ := ktesting.NewTestContext(t) @@ -927,9 +927,6 @@ func Test_MarkDesireToDetach_Positive_MarkedAddVolumeNodeReset(t *testing.T) { if markDesireToDetachErr != nil { t.Fatalf("RemoveVolumeFromReportAsAttached failed. Expected: Actual: <%v>", markDesireToDetachErr) } - if addErr != nil { - t.Fatalf("AddVolumeNode failed. Expected: Actual: <%v>", addErr) - } // Assert attachedVolumes := asw.GetAttachedVolumes() @@ -1224,7 +1221,7 @@ func Test_GetAttachedVolumesForNode_Positive_OneVolumeTwoNodes(t *testing.T) { t.Fatalf("Failed to get volume plugin from spec %v, %v", volumeSpec, err) } uniqueVolumeName, err := volumeutil.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) - if err != nil || plugin == nil { + if err != nil || uniqueVolumeName == "" { t.Fatalf("Failed to get uniqueVolumeName from spec %v, %v", volumeSpec, err) } generatedVolumeName1, add1Err := asw.AddVolumeNode(logger, uniqueVolumeName, volumeSpec, node1Name, devicePath, true) @@ -1269,7 +1266,7 @@ func Test_OneVolumeTwoNodes_TwoDevicePaths(t *testing.T) { t.Fatalf("Failed to get volume plugin from spec %v, %v", volumeSpec, err) } uniqueVolumeName, err := volumeutil.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) - if err != nil || plugin == nil { + if err != nil || uniqueVolumeName == "" { t.Fatalf("Failed to get uniqueVolumeName from spec %v, %v", volumeSpec, err) } generatedVolumeName1, add1Err := asw.AddVolumeNode(logger, uniqueVolumeName, volumeSpec, node1Name, devicePath1, true) diff --git a/pkg/controller/volume/attachdetach/metrics/metrics_test.go b/pkg/controller/volume/attachdetach/metrics/metrics_test.go index a8c649f4e2e41..9ad31a3c86514 100644 --- a/pkg/controller/volume/attachdetach/metrics/metrics_test.go +++ b/pkg/controller/volume/attachdetach/metrics/metrics_test.go @@ -23,7 +23,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" csitrans "k8s.io/csi-translation-lib" @@ -120,7 +119,7 @@ func TestVolumesInUseMetricCollection(t *testing.T) { nil, nil, fakeVolumePluginMgr, - csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate), + csimigration.NewPluginManager(csiTranslator), csiTranslator) logger, _ := ktesting.NewTestContext(t) nodeUseMap := metricCollector.getVolumeInUseCount(logger) @@ -163,7 +162,7 @@ func TestTotalVolumesMetricCollection(t *testing.T) { asw, dsw, fakeVolumePluginMgr, - csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate), + csimigration.NewPluginManager(csiTranslator), csiTranslator) totalVolumesMap := metricCollector.getTotalVolumesCount() diff --git a/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator.go b/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator.go index 620abaa0a4b03..b74d58ec0321c 100644 --- a/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator.go +++ b/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator.go @@ -140,7 +140,6 @@ func (dswp *desiredStateOfWorldPopulator) findAndRemoveDeletedPods(logger klog.L default: volumeActionFlag := util.DetermineVolumeAction( informerPod, - dswp.desiredStateOfWorld, true /* default volume action */) if volumeActionFlag { diff --git a/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator_test.go b/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator_test.go index 788091681c1d3..00ae3253b9465 100644 --- a/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator_test.go +++ b/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator_test.go @@ -23,7 +23,6 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" csitrans "k8s.io/csi-translation-lib" @@ -86,7 +85,7 @@ func TestFindAndAddActivePods_FindAndRemoveDeletedPods(t *testing.T) { podLister: fakePodInformer.Lister(), pvcLister: pvcLister, pvLister: pvLister, - csiMigratedPluginManager: csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate), + csiMigratedPluginManager: csimigration.NewPluginManager(csiTranslator), intreeToCSITranslator: csiTranslator, } @@ -191,7 +190,7 @@ func TestFindAndRemoveNonattachableVolumes(t *testing.T) { podLister: fakePodInformer.Lister(), pvcLister: pvcLister, pvLister: pvLister, - csiMigratedPluginManager: csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate), + csiMigratedPluginManager: csimigration.NewPluginManager(csiTranslator), intreeToCSITranslator: csiTranslator, } diff --git a/pkg/controller/volume/attachdetach/reconciler/reconciler.go b/pkg/controller/volume/attachdetach/reconciler/reconciler.go index b79ad1c7f362e..6bbca35bb1c16 100644 --- a/pkg/controller/volume/attachdetach/reconciler/reconciler.go +++ b/pkg/controller/volume/attachdetach/reconciler/reconciler.go @@ -395,7 +395,7 @@ func (rc *reconciler) reportMultiAttachError(logger klog.Logger, volumeToAttach // We did not find any pods that requests the volume. The pod must have been deleted already. simpleMsg, _ := volumeToAttach.GenerateMsg("Multi-Attach error", "Volume is already exclusively attached to one node and can't be attached to another") for _, pod := range volumeToAttach.ScheduledPods { - rc.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, simpleMsg) + rc.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, "%s", simpleMsg) } // Log detailed message to system admin logger.Info("Multi-Attach error: volume is already exclusively attached and can't be attached to another node", "attachedTo", otherNodesStr, "volume", volumeToAttach) @@ -430,7 +430,7 @@ func (rc *reconciler) reportMultiAttachError(logger klog.Logger, volumeToAttach msg = fmt.Sprintf("Volume is already used by %d pod(s) in different namespaces", otherPods) } simpleMsg, _ := volumeToAttach.GenerateMsg("Multi-Attach error", msg) - rc.recorder.Eventf(scheduledPod, v1.EventTypeWarning, kevents.FailedAttachVolume, simpleMsg) + rc.recorder.Eventf(scheduledPod, v1.EventTypeWarning, kevents.FailedAttachVolume, "%s", simpleMsg) } // Log all pods for system admin diff --git a/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go b/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go index 86a7b32ffc501..ac2e88c761411 100644 --- a/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go +++ b/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go @@ -57,12 +57,16 @@ var registerMetrics sync.Once // Calls Run() // Verifies there are no calls to attach or detach. func Test_Run_Positive_DoNothing(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -78,9 +82,6 @@ func Test_Run_Positive_DoNothing(t *testing.T) { reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, false, dsw, asw, ad, nsu, nodeLister, fakeRecorder) // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -95,11 +96,15 @@ func Test_Run_Positive_DoNothing(t *testing.T) { // Calls Run() // Verifies there is one attach call and no detach calls. func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -131,9 +136,6 @@ func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) { } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -149,11 +151,15 @@ func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) { // Deletes the node/volume/pod tuple from desiredStateOfWorld cache. // Verifies there is one detach call and no (new) attach calls. func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -185,9 +191,6 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *te } // Act - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -224,6 +227,10 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *te // Deletes the node/volume/pod tuple from desiredStateOfWorld cache without first marking the node/volume as unmounted. // Verifies there is one detach call and no (new) attach calls. func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMountedVolume(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + registerMetrics.Do(func() { legacyregistry.MustRegister(metrics.ForceDetachMetricCounter) }) @@ -243,7 +250,7 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMountedVolume(t *test volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -276,9 +283,6 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMountedVolume(t *test } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -320,11 +324,15 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMountedVolume(t *test // Deletes the node/volume/pod tuple from desiredStateOfWorld cache. // Verifies there are NO detach call and no (new) attach calls. func Test_Run_Negative_OneDesiredVolumeAttachThenDetachWithUnmountedVolumeUpdateStatusFail(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -356,9 +364,6 @@ func Test_Run_Negative_OneDesiredVolumeAttachThenDetachWithUnmountedVolumeUpdate } // Act - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -398,11 +403,15 @@ func Test_Run_Negative_OneDesiredVolumeAttachThenDetachWithUnmountedVolumeUpdate // Deletes the second node/volume/pod tuple from desiredStateOfWorld cache without first marking the node/volume as unmounted. // Verifies there are two detach calls and no (new) attach calls. func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteMany(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -436,9 +445,6 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteMany(t *testing. } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -493,11 +499,15 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteMany(t *testing. // Deletes the node/volume/pod tuple from desiredStateOfWorld which succeeded in attaching // Verifies there are two attach call and one detach call. func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteOnce(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -531,9 +541,6 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteOnce(t *testing. } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -586,11 +593,15 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteOnce(t *testing. // Secondly, delete this pod. // Lastly, create a pod scheduled to a normal node which will trigger attach volume to the node. The attach should return successfully. func Test_Run_OneVolumeAttachAndDetachUncertainNodesWithReadWriteOnce(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -614,9 +625,6 @@ func Test_Run_OneVolumeAttachAndDetachUncertainNodesWithReadWriteOnce(t *testing dsw.AddNode(nodeName2) // Act - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Add the pod in which the volume is attached to the uncertain node @@ -648,11 +656,15 @@ func Test_Run_OneVolumeAttachAndDetachUncertainNodesWithReadWriteOnce(t *testing } func Test_Run_UpdateNodeStatusFailBeforeOneVolumeDetachNodeWithReadWriteOnce(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -663,9 +675,6 @@ func Test_Run_UpdateNodeStatusFailBeforeOneVolumeDetachNodeWithReadWriteOnce(t * informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) nodeLister := informerFactory.Core().V1().Nodes().Lister() nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() rc := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, false, dsw, asw, ad, nsu, nodeLister, fakeRecorder) reconciliationLoopFunc := rc.(*reconciler).reconciliationLoopFunc(ctx) @@ -711,11 +720,15 @@ func Test_Run_UpdateNodeStatusFailBeforeOneVolumeDetachNodeWithReadWriteOnce(t * } func Test_Run_OneVolumeDetachFailNodeWithReadWriteOnce(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -740,9 +753,6 @@ func Test_Run_OneVolumeDetachFailNodeWithReadWriteOnce(t *testing.T) { dsw.AddNode(nodeName2) // Act - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Add the pod in which the volume is attached to the FailDetachNode @@ -792,11 +802,15 @@ func Test_Run_OneVolumeDetachFailNodeWithReadWriteOnce(t *testing.T) { // Secondly, delete the this pod. // Lastly, create a pod scheduled to a normal node which will trigger attach volume to the node. The attach should return successfully. func Test_Run_OneVolumeAttachAndDetachTimeoutNodesWithReadWriteOnce(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -820,9 +834,6 @@ func Test_Run_OneVolumeAttachAndDetachTimeoutNodesWithReadWriteOnce(t *testing.T dsw.AddNode(nodeName2) // Act - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Add the pod in which the volume is attached to the timeout node @@ -863,6 +874,10 @@ func Test_Run_OneVolumeAttachAndDetachTimeoutNodesWithReadWriteOnce(t *testing.T // Deletes the pod from desiredStateOfWorld cache without first marking the node/volume as unmounted. // Verifies there is one detach call and no (new) attach calls. func Test_Run_OneVolumeDetachOnOutOfServiceTaintedNode(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + registerMetrics.Do(func() { legacyregistry.MustRegister(metrics.ForceDetachMetricCounter) }) @@ -882,7 +897,7 @@ func Test_Run_OneVolumeDetachOnOutOfServiceTaintedNode(t *testing.T) { volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -923,9 +938,6 @@ func Test_Run_OneVolumeDetachOnOutOfServiceTaintedNode(t *testing.T) { } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -964,11 +976,15 @@ func Test_Run_OneVolumeDetachOnOutOfServiceTaintedNode(t *testing.T) { // Deletes the pod from desiredStateOfWorld cache without first marking the node/volume as unmounted. // Verifies there is no detach call and no (new) attach calls. func Test_Run_OneVolumeDetachOnNoOutOfServiceTaintedNode(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -1006,9 +1022,6 @@ func Test_Run_OneVolumeDetachOnNoOutOfServiceTaintedNode(t *testing.T) { } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -1041,11 +1054,15 @@ func Test_Run_OneVolumeDetachOnNoOutOfServiceTaintedNode(t *testing.T) { // Marks the node as unhealthy. // Verifies that the volume is detached after maxWaitForUnmountDuration. func Test_Run_OneVolumeDetachOnUnhealthyNode(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Arrange volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -1091,9 +1108,6 @@ func Test_Run_OneVolumeDetachOnUnhealthyNode(t *testing.T) { } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -1143,6 +1157,10 @@ func Test_Run_OneVolumeDetachOnUnhealthyNode(t *testing.T) { // Verifies that there is still just one attach call. // Verifies there is now one detach call. func Test_Run_OneVolumeDetachOnUnhealthyNodeWithForceDetachOnUnmountDisabled(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + registerMetrics.Do(func() { legacyregistry.MustRegister(metrics.ForceDetachMetricCounter) }) @@ -1161,7 +1179,7 @@ func Test_Run_OneVolumeDetachOnUnhealthyNodeWithForceDetachOnUnmountDisabled(t * volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := &record.FakeRecorder{} fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -1210,9 +1228,6 @@ func Test_Run_OneVolumeDetachOnUnhealthyNodeWithForceDetachOnUnmountDisabled(t * } // Act - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() go reconciler.Run(ctx) // Assert @@ -1321,12 +1336,13 @@ func Test_ReportMultiAttachError(t *testing.T) { } for _, test := range tests { + logger, _ := ktesting.NewTestContext(t) // Arrange t.Logf("Test %q starting", test.name) volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) asw := cache.NewActualStateOfWorld(volumePluginMgr) - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(logger) fakeRecorder := record.NewFakeRecorder(100) fakeHandler := volumetesting.NewBlockVolumePathHandler() ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( @@ -1359,7 +1375,6 @@ func Test_ReportMultiAttachError(t *testing.T) { } } // Act - logger, _ := ktesting.NewTestContext(t) volumes := dsw.GetVolumesToAttach() for _, vol := range volumes { if vol.NodeName == "node1" { diff --git a/pkg/controller/volume/attachdetach/testing/testvolumespec.go b/pkg/controller/volume/attachdetach/testing/testvolumespec.go index 93809a236246b..1bc130f76c47b 100644 --- a/pkg/controller/volume/attachdetach/testing/testvolumespec.go +++ b/pkg/controller/volume/attachdetach/testing/testvolumespec.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" + "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" ) @@ -54,7 +55,7 @@ func GetTestVolumeSpec(volumeName string, diskName v1.UniqueVolumeName) *volume. } } -func CreateTestClient() *fake.Clientset { +func CreateTestClient(logger klog.Logger) *fake.Clientset { var extraPods *v1.PodList var volumeAttachments *storagev1.VolumeAttachmentList var pvs *v1.PersistentVolumeList @@ -193,7 +194,7 @@ func CreateTestClient() *fake.Clientset { return true, createAction.GetObject(), nil }) - fakeWatch := watch.NewFake() + fakeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) return fakeClient diff --git a/pkg/controller/volume/attachdetach/util/util.go b/pkg/controller/volume/attachdetach/util/util.go index b08f41135bd2b..5baddc8bfea17 100644 --- a/pkg/controller/volume/attachdetach/util/util.go +++ b/pkg/controller/volume/attachdetach/util/util.go @@ -178,7 +178,7 @@ func getPVSpecFromCache(name string, pvcReadOnly bool, expectedClaimUID types.UI // DetermineVolumeAction returns true if volume and pod needs to be added to dswp // and it returns false if volume and pod needs to be removed from dswp -func DetermineVolumeAction(pod *v1.Pod, desiredStateOfWorld cache.DesiredStateOfWorld, defaultAction bool) bool { +func DetermineVolumeAction(pod *v1.Pod, defaultAction bool) bool { if pod == nil || len(pod.Spec.Volumes) <= 0 { return defaultAction } diff --git a/pkg/controller/volume/attachdetach/util/util_test.go b/pkg/controller/volume/attachdetach/util/util_test.go index d14e2e3b89a91..ec2445fe667d7 100644 --- a/pkg/controller/volume/attachdetach/util/util_test.go +++ b/pkg/controller/volume/attachdetach/util/util_test.go @@ -35,7 +35,6 @@ import ( "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/fc" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" utiltesting "k8s.io/client-go/util/testing" "k8s.io/kubernetes/pkg/volume" @@ -291,7 +290,7 @@ func setup(nodeName string, t *testing.T) (*volume.VolumePluginMgr, csimigration *fsVolumeMode = v1.PersistentVolumeFilesystem csiTranslator := csitrans.New() - intreeToCSITranslator := csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) + intreeToCSITranslator := csimigration.NewPluginManager(csiTranslator) kubeClient := fake.NewSimpleClientset() factory := informers.NewSharedInformerFactory(kubeClient, time.Minute) diff --git a/pkg/controller/volume/expand/expand_controller.go b/pkg/controller/volume/expand/expand_controller.go index 3d8ca84a7a988..cf19e9e4f251a 100644 --- a/pkg/controller/volume/expand/expand_controller.go +++ b/pkg/controller/volume/expand/expand_controller.go @@ -417,10 +417,6 @@ func (expc *expandController) GetMounter() mount.Interface { return nil } -func (expc *expandController) GetHostName() string { - return "" -} - func (expc *expandController) GetNodeAllocatable() (v1.ResourceList, error) { return v1.ResourceList{}, nil } diff --git a/pkg/controller/volume/expand/expand_controller_test.go b/pkg/controller/volume/expand/expand_controller_test.go index 6710ad4267f89..bb59f0a4623f2 100644 --- a/pkg/controller/volume/expand/expand_controller_test.go +++ b/pkg/controller/volume/expand/expand_controller_test.go @@ -30,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" coretesting "k8s.io/client-go/testing" csitrans "k8s.io/csi-translation-lib" @@ -94,7 +93,7 @@ func TestSyncHandler(t *testing.T) { for _, tc := range tests { tCtx := ktesting.Init(t) test := tc - fakeKubeClient := controllervolumetesting.CreateTestClient() + fakeKubeClient := controllervolumetesting.CreateTestClient(tCtx.Logger()) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() @@ -108,7 +107,7 @@ func TestSyncHandler(t *testing.T) { } allPlugins := []volume.VolumePlugin{} translator := csitrans.New() - expc, err := NewExpandController(tCtx, fakeKubeClient, pvcInformer, allPlugins, translator, csimigration.NewPluginManager(translator, utilfeature.DefaultFeatureGate)) + expc, err := NewExpandController(tCtx, fakeKubeClient, pvcInformer, allPlugins, translator, csimigration.NewPluginManager(translator)) if err != nil { t.Fatalf("error creating expand controller : %v", err) } diff --git a/pkg/controller/volume/persistentvolume/binder_test.go b/pkg/controller/volume/persistentvolume/binder_test.go index 08b280f2190ef..57799bb51ec84 100644 --- a/pkg/controller/volume/persistentvolume/binder_test.go +++ b/pkg/controller/volume/persistentvolume/binder_test.go @@ -23,6 +23,7 @@ import ( v1 "k8s.io/api/core/v1" storage "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-helpers/storage/volume" @@ -800,6 +801,9 @@ func TestSync(t *testing.T) { } for _, isEnabled := range []bool{true, false} { + if !isEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, isEnabled) allTests := tests diff --git a/pkg/controller/volume/persistentvolume/delete_test.go b/pkg/controller/volume/persistentvolume/delete_test.go index 062c077931be4..63b678dea7033 100644 --- a/pkg/controller/volume/persistentvolume/delete_test.go +++ b/pkg/controller/volume/persistentvolume/delete_test.go @@ -22,13 +22,9 @@ import ( v1 "k8s.io/api/core/v1" storage "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/util/version" - utilfeature "k8s.io/apiserver/pkg/util/feature" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-helpers/storage/volume" "k8s.io/klog/v2/ktesting" pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing" - "k8s.io/kubernetes/pkg/features" ) // Test single call to syncVolume, expecting recycling to happen. @@ -37,10 +33,6 @@ import ( // 3. Compare resulting volumes with expected volumes. func TestDeleteSync(t *testing.T) { const gceDriver = "pd.csi.storage.gke.io" - // Default enable the HonorPVReclaimPolicy feature gate. - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HonorPVReclaimPolicy, true) _, ctx := ktesting.NewTestContext(t) tests := []controllerTest{ { diff --git a/pkg/controller/volume/persistentvolume/framework_test.go b/pkg/controller/volume/persistentvolume/framework_test.go index 92dcd255b5beb..c7dda1ea9cb71 100644 --- a/pkg/controller/volume/persistentvolume/framework_test.go +++ b/pkg/controller/volume/persistentvolume/framework_test.go @@ -809,7 +809,6 @@ func runSyncTests(t *testing.T, ctx context.Context, tests []controllerTest, sto } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { doit(t, test) }) @@ -944,7 +943,6 @@ func runMultisyncTests(t *testing.T, ctx context.Context, tests []controllerTest } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() run(t, test) diff --git a/pkg/controller/volume/persistentvolume/index_test.go b/pkg/controller/volume/persistentvolume/index_test.go index 25b50d9e06bd2..12854858a999e 100644 --- a/pkg/controller/volume/persistentvolume/index_test.go +++ b/pkg/controller/volume/persistentvolume/index_test.go @@ -23,12 +23,9 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/scheme" ref "k8s.io/client-go/tools/reference" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-helpers/storage/volume" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume/util" ) @@ -78,9 +75,6 @@ func makeVolumeModePVC(size string, mode *v1.PersistentVolumeMode, modfn func(*v } func TestMatchVolume(t *testing.T) { - // Default enable the VolumeAttributesClass feature gate. - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true) - volList := newPersistentVolumeOrderedIndex() for _, pv := range createTestVolumes() { volList.store.Add(pv) diff --git a/pkg/controller/volume/persistentvolume/metrics/metrics.go b/pkg/controller/volume/persistentvolume/metrics/metrics.go index 8d86414b5cb3e..97de147ad8d14 100644 --- a/pkg/controller/volume/persistentvolume/metrics/metrics.go +++ b/pkg/controller/volume/persistentvolume/metrics/metrics.go @@ -68,6 +68,7 @@ func Register(pvLister PVLister, pvcLister PVCLister, pluginMgr *volume.VolumePl registerMetrics.Do(func() { legacyregistry.CustomMustRegister(newPVAndPVCCountCollector(pvLister, pvcLister, pluginMgr)) legacyregistry.MustRegister(volumeOperationErrorsMetric) + legacyregistry.MustRegister(volumeOperationErrorMetric) legacyregistry.MustRegister(retroactiveStorageClassMetric) legacyregistry.MustRegister(retroactiveStorageClassErrorMetric) }) @@ -135,7 +136,16 @@ var ( volumeOperationErrorsMetric = metrics.NewCounterVec( &metrics.CounterOpts{ - Name: "volume_operation_total_errors", + Name: "volume_operation_total_errors", + Help: "Total volume operation errors", + StabilityLevel: metrics.ALPHA, + DeprecatedVersion: "1.36.0", + }, + []string{"plugin_name", "operation_name"}) + + volumeOperationErrorMetric = metrics.NewCounterVec( + &metrics.CounterOpts{ + Name: "volume_operation_errors_total", Help: "Total volume operation errors", StabilityLevel: metrics.ALPHA, }, @@ -282,6 +292,7 @@ func RecordVolumeOperationErrorMetric(pluginName, opName string) { pluginName = "N/A" } volumeOperationErrorsMetric.WithLabelValues(pluginName, opName).Inc() + volumeOperationErrorMetric.WithLabelValues(pluginName, opName).Inc() } // operationTimestamp stores the start time of an operation by a plugin diff --git a/pkg/controller/volume/persistentvolume/metrics/metrics_test.go b/pkg/controller/volume/persistentvolume/metrics/metrics_test.go new file mode 100644 index 0000000000000..3116cc6b6eb5a --- /dev/null +++ b/pkg/controller/volume/persistentvolume/metrics/metrics_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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 metrics + +import ( + "strings" + "testing" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/testutil" +) + +// TestVolumeOperationError tests recording metric when operation has error +func TestVolumeOperationError(t *testing.T) { + registry := metrics.NewKubeRegistry() + defer registry.Reset() + registry.MustRegister(volumeOperationErrorMetric) + + RecordVolumeOperationErrorMetric("kubernetes.io/fake-plugin", "deletion") + + want := `# HELP volume_operation_errors_total [ALPHA] Total volume operation errors +# TYPE volume_operation_errors_total counter +volume_operation_errors_total{operation_name="deletion",plugin_name="kubernetes.io/fake-plugin"} 1 +` + + if err := testutil.GatherAndCompare(registry, strings.NewReader(want), "volume_operation_errors_total"); err != nil { + t.Errorf("failed to gather metrics: %v", err) + } +} diff --git a/pkg/controller/volume/persistentvolume/provision_test.go b/pkg/controller/volume/persistentvolume/provision_test.go index 47281b7504535..d82991012837b 100644 --- a/pkg/controller/volume/persistentvolume/provision_test.go +++ b/pkg/controller/volume/persistentvolume/provision_test.go @@ -20,16 +20,12 @@ import ( "errors" "testing" - utilfeature "k8s.io/apiserver/pkg/util/feature" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2/ktesting" - "k8s.io/kubernetes/pkg/features" v1 "k8s.io/api/core/v1" storage "k8s.io/api/storage/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/version" corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/component-helpers/storage/volume" @@ -172,10 +168,6 @@ var provision2Success = provisionCall{ // 2. Call the syncVolume *once*. // 3. Compare resulting volumes with expected volumes. func TestProvisionSync(t *testing.T) { - // Default enable the HonorPVReclaimPolicy feature gate. - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HonorPVReclaimPolicy, true) _, ctx := ktesting.NewTestContext(t) tests := []controllerTest{ { @@ -601,11 +593,6 @@ func TestProvisionSync(t *testing.T) { // // Some limit of calls in enforced to prevent endless loops. func TestProvisionMultiSync(t *testing.T) { - // Default enable the HonorPVReclaimPolicy feature gate. - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HonorPVReclaimPolicy, true) - _, ctx := ktesting.NewTestContext(t) tests := []controllerTest{ { diff --git a/pkg/controller/volume/persistentvolume/pv_controller.go b/pkg/controller/volume/persistentvolume/pv_controller.go index 70b593ae955a9..575d1bfc51e5d 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller.go +++ b/pkg/controller/volume/persistentvolume/pv_controller.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "reflect" + "slices" "strings" "time" @@ -189,8 +190,8 @@ type PersistentVolumeController struct { // version errors in API server and other checks in this controller), // however overall speed of multi-worker controller would be lower than if // it runs single thread only. - claimQueue *workqueue.Typed[string] - volumeQueue *workqueue.Typed[string] + claimQueue workqueue.TypedRateLimitingInterface[string] + volumeQueue workqueue.TypedRateLimitingInterface[string] // Map of scheduled/running operations. runningOperations goroutinemap.GoRoutineMap @@ -1324,6 +1325,22 @@ func (ctrl *PersistentVolumeController) deleteVolumeOperation(ctx context.Contex logger := klog.FromContext(ctx) logger.V(4).Info("DeleteVolumeOperation started", "volumeName", volume.Name) + plugin, err := ctrl.findDeletablePlugin(volume) + if err != nil { + if _, err := ctrl.updateVolumePhaseWithEvent(ctx, volume, v1.VolumeFailed, v1.EventTypeWarning, events.VolumeFailedDelete, err.Error()); err != nil { + logger.V(4).Info("DeleteVolumeOperation: failed to mark volume as failed", "volumeName", volume.Name, "err", err) + // Save failed, retry on the next deletion attempt + return "", err + } + } + if plugin == nil { + // External deleter is requested, do nothing + logger.V(3).Info("External deleter for volume requested, ignoring", "volumeName", volume.Name) + return "", nil + } + pluginName := plugin.GetPluginName() + logger.V(5).Info("Found a deleter plugin for volume", "pluginName", pluginName, "volumeName", volume.Name) + // This method may have been waiting for a volume lock for some time. // Previous deleteVolumeOperation might just have saved an updated version, so // read current volume state now. @@ -1333,12 +1350,6 @@ func (ctrl *PersistentVolumeController) deleteVolumeOperation(ctx context.Contex return "", nil } - if !utilfeature.DefaultFeatureGate.Enabled(features.HonorPVReclaimPolicy) { - if newVolume.GetDeletionTimestamp() != nil { - logger.V(3).Info("Volume is already being deleted", "volumeName", volume.Name) - return "", nil - } - } needsReclaim, err := ctrl.isVolumeReleased(logger, newVolume) if err != nil { logger.V(3).Info("Error reading claim for volume", "volumeName", volume.Name, "err", err) @@ -1349,7 +1360,7 @@ func (ctrl *PersistentVolumeController) deleteVolumeOperation(ctx context.Contex return "", nil } - pluginName, deleted, err := ctrl.doDeleteVolume(ctx, volume) + err = ctrl.doDeleteVolume(ctx, plugin, volume) if err != nil { // Delete failed, update the volume and emit an event. logger.V(3).Info("Deletion of volume failed", "volumeName", volume.Name, "err", err) @@ -1371,10 +1382,6 @@ func (ctrl *PersistentVolumeController) deleteVolumeOperation(ctx context.Contex // the volume in every syncVolume() call. return pluginName, err } - if !deleted { - // The volume waits for deletion by an external plugin. Do nothing. - return pluginName, nil - } logger.V(4).Info("DeleteVolumeOperation: success", "volumeName", volume.Name) // Delete the volume @@ -1496,29 +1503,17 @@ func (ctrl *PersistentVolumeController) findNonScheduledPodsByPVC(pvc *v1.Persis // the volume plugin name. Also, it returns 'true', when the volume was deleted and // 'false' when the volume cannot be deleted because the deleter is external. No // error should be reported in this case. -func (ctrl *PersistentVolumeController) doDeleteVolume(ctx context.Context, volume *v1.PersistentVolume) (string, bool, error) { +func (ctrl *PersistentVolumeController) doDeleteVolume(ctx context.Context, plugin vol.DeletableVolumePlugin, volume *v1.PersistentVolume) error { logger := klog.FromContext(ctx) logger.V(4).Info("doDeleteVolume", "volumeName", volume.Name) var err error - plugin, err := ctrl.findDeletablePlugin(volume) - if err != nil { - return "", false, err - } - if plugin == nil { - // External deleter is requested, do nothing - logger.V(3).Info("External deleter for volume requested, ignoring", "volumeName", volume.Name) - return "", false, nil - } - - // Plugin found pluginName := plugin.GetPluginName() - logger.V(5).Info("Found a deleter plugin for volume", "pluginName", pluginName, "volumeName", volume.Name) spec := vol.NewSpecFromPersistentVolume(volume, false) deleter, err := plugin.NewDeleter(logger, spec) if err != nil { // Cannot create deleter - return pluginName, false, fmt.Errorf("failed to create deleter for volume %q: %w", volume.Name, err) + return fmt.Errorf("failed to create deleter for volume %q: %w", volume.Name, err) } opComplete := util.OperationCompleteHook(pluginName, "volume_delete") @@ -1526,22 +1521,16 @@ func (ctrl *PersistentVolumeController) doDeleteVolume(ctx context.Context, volu opComplete(volumetypes.CompleteFuncParam{Err: &err}) if err != nil { // Deleter failed - return pluginName, false, err + return err } logger.V(2).Info("Volume deleted", "volumeName", volume.Name) - // Remove in-tree delete finalizer on the PV as the volume has been deleted from the underlying storage - if utilfeature.DefaultFeatureGate.Enabled(features.HonorPVReclaimPolicy) { - err = ctrl.removeDeletionProtectionFinalizer(ctx, volume) - if err != nil { - return pluginName, true, err - } + if err := ctrl.removeDeletionProtectionFinalizer(ctx, volume); err != nil { + return err } - return pluginName, true, nil + return nil } func (ctrl *PersistentVolumeController) removeDeletionProtectionFinalizer(ctx context.Context, volume *v1.PersistentVolume) error { - var err error - pvUpdateNeeded := false // Retrieve latest version logger := klog.FromContext(ctx) newVolume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(ctx, volume.Name, metav1.GetOptions{}) @@ -1552,12 +1541,8 @@ func (ctrl *PersistentVolumeController) removeDeletionProtectionFinalizer(ctx co volume = newVolume volumeClone := volume.DeepCopy() pvFinalizers := volumeClone.Finalizers - if pvFinalizers != nil && slice.ContainsString(pvFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) { - pvUpdateNeeded = true - pvFinalizers = slice.RemoveString(pvFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) - } - if pvUpdateNeeded { - volumeClone.SetFinalizers(pvFinalizers) + if pvFinalizers != nil && slices.Contains(pvFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer) { + volumeClone.SetFinalizers(slice.RemoveString(pvFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil)) _, err = ctrl.kubeClient.CoreV1().PersistentVolumes().Update(ctx, volumeClone, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("persistent volume controller can't update finalizer: %v", err) @@ -1740,11 +1725,9 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation( metav1.SetMetaDataAnnotation(&volume.ObjectMeta, storagehelpers.AnnBoundByController, "yes") metav1.SetMetaDataAnnotation(&volume.ObjectMeta, storagehelpers.AnnDynamicallyProvisioned, plugin.GetPluginName()) - if utilfeature.DefaultFeatureGate.Enabled(features.HonorPVReclaimPolicy) { - if volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimDelete { - // Add In-Tree protection finalizer here only when the reclaim policy is `Delete` - volume.SetFinalizers([]string{storagehelpers.PVDeletionInTreeProtectionFinalizer}) - } + if volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimDelete { + // Add In-Tree protection finalizer here only when the reclaim policy is `Delete` + volume.SetFinalizers([]string{storagehelpers.PVDeletionInTreeProtectionFinalizer}) } // Try to create the PV object several times @@ -1781,25 +1764,27 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation( logger.V(3).Info(strerr) ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr) - var deleteErr error - var deleted bool - for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ { - _, deleted, deleteErr = ctrl.doDeleteVolume(ctx, volume) - if deleteErr == nil && deleted { - // Delete succeeded - logger.V(4).Info("provisionClaimOperation: cleaning volume succeeded", "PVC", klog.KObj(claim), "volumeName", volume.Name) - break - } - if !deleted { + delPlugin, deleteErr := ctrl.findDeletablePlugin(volume) + if deleteErr == nil { + if delPlugin == nil { // This is unreachable code, the volume was provisioned by an // internal plugin and therefore there MUST be an internal // plugin that deletes it. logger.Error(nil, "Error finding internal deleter for volume plugin", "plugin", plugin.GetPluginName()) - break + return pluginName, nil + } + + for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ { + deleteErr = ctrl.doDeleteVolume(ctx, delPlugin, volume) + if deleteErr == nil { + // Delete succeeded + logger.V(4).Info("provisionClaimOperation: cleaning volume succeeded", "PVC", klog.KObj(claim), "volumeName", volume.Name) + break + } + // Delete failed, try again after a while. + logger.V(3).Info("Failed to delete volume", "volumeName", volume.Name, "err", deleteErr) + time.Sleep(ctrl.createProvisionedPVInterval) } - // Delete failed, try again after a while. - logger.V(3).Info("Failed to delete volume", "volumeName", volume.Name, "err", deleteErr) - time.Sleep(ctrl.createProvisionedPVInterval) } if deleteErr != nil { diff --git a/pkg/controller/volume/persistentvolume/pv_controller_base.go b/pkg/controller/volume/persistentvolume/pv_controller_base.go index e8628a41be9c7..b1dbe5e5226dc 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller_base.go +++ b/pkg/controller/volume/persistentvolume/pv_controller_base.go @@ -19,6 +19,7 @@ package persistentvolume import ( "context" "fmt" + "slices" "strconv" "strings" "sync" @@ -31,7 +32,6 @@ import ( "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" - utilfeature "k8s.io/apiserver/pkg/util/feature" coreinformers "k8s.io/client-go/informers/core/v1" storageinformers "k8s.io/client-go/informers/storage/v1" clientset "k8s.io/client-go/kubernetes" @@ -46,7 +46,6 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/volume/common" "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/metrics" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/goroutinemap" "k8s.io/kubernetes/pkg/util/slice" vol "k8s.io/kubernetes/pkg/volume" @@ -88,10 +87,16 @@ func NewController(ctx context.Context, p ControllerParameters) (*PersistentVolu enableDynamicProvisioning: p.EnableDynamicProvisioning, createProvisionedPVRetryCount: createProvisionedPVRetryCount, createProvisionedPVInterval: createProvisionedPVInterval, - claimQueue: workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[string]{Name: "claims"}), - volumeQueue: workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[string]{Name: "volumes"}), - resyncPeriod: p.SyncPeriod, - operationTimestamps: metrics.NewOperationStartTimeCache(), + claimQueue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[string](), + workqueue.TypedRateLimitingQueueConfig[string]{Name: "claims"}, + ), + volumeQueue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[string](), + workqueue.TypedRateLimitingQueueConfig[string]{Name: "volumes"}, + ), + resyncPeriod: p.SyncPeriod, + operationTimestamps: metrics.NewOperationStartTimeCache(), } // Prober is nil because PV is not aware of Flexvolume. @@ -135,7 +140,7 @@ func NewController(ctx context.Context, p ControllerParameters) (*PersistentVolu csiTranslator := csitrans.New() controller.translator = csiTranslator - controller.csiMigratedPluginManager = csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) + controller.csiMigratedPluginManager = csimigration.NewPluginManager(csiTranslator) return controller, nil } @@ -195,16 +200,17 @@ func (ctrl *PersistentVolumeController) storeClaimUpdate(logger klog.Logger, cla // updateVolume runs in worker thread and handles "volume added", // "volume updated" and "periodic sync" events. -func (ctrl *PersistentVolumeController) updateVolume(ctx context.Context, volume *v1.PersistentVolume) { +func (ctrl *PersistentVolumeController) updateVolume(ctx context.Context, volume *v1.PersistentVolume) error { // Store the new volume version in the cache and do not process it if this // is an old version. logger := klog.FromContext(ctx) new, err := ctrl.storeVolumeUpdate(logger, volume) if err != nil { - logger.Error(err, "") + logger.Error(err, "Error storing volume update") + return err } if !new { - return + return nil } err = ctrl.syncVolume(ctx, volume) @@ -214,9 +220,11 @@ func (ctrl *PersistentVolumeController) updateVolume(ctx context.Context, volume // recovers from it easily. logger.V(3).Info("Could not sync volume", "volumeName", volume.Name, "err", err) } else { - logger.Error(err, "Could not sync volume", "volumeName", volume.Name, "err", err) + logger.Error(err, "Could not sync volume", "volumeName", volume.Name) } + return err } + return nil } // deleteVolume runs in worker thread and handles "volume deleted" event. @@ -245,16 +253,17 @@ func (ctrl *PersistentVolumeController) deleteVolume(ctx context.Context, volume // updateClaim runs in worker thread and handles "claim added", // "claim updated" and "periodic sync" events. -func (ctrl *PersistentVolumeController) updateClaim(ctx context.Context, claim *v1.PersistentVolumeClaim) { +func (ctrl *PersistentVolumeController) updateClaim(ctx context.Context, claim *v1.PersistentVolumeClaim) error { // Store the new claim version in the cache and do not process it if this is // an old version. logger := klog.FromContext(ctx) new, err := ctrl.storeClaimUpdate(logger, claim) if err != nil { - logger.Error(err, "") + logger.Error(err, "Error storing claim update") + return err } if !new { - return + return nil } err = ctrl.syncClaim(ctx, claim) if err != nil { @@ -263,9 +272,11 @@ func (ctrl *PersistentVolumeController) updateClaim(ctx context.Context, claim * // recovers from it easily. logger.V(3).Info("Could not sync claim", "PVC", klog.KObj(claim), "err", err) } else { - logger.Error(err, "Could not sync volume", "PVC", klog.KObj(claim)) + logger.Error(err, "Could not sync claim", "PVC", klog.KObj(claim)) } + return err } + return nil } // Unit test [5-5] [5-6] [5-7] @@ -390,9 +401,6 @@ func (ctrl *PersistentVolumeController) updateVolumeMigrationAnnotationsAndFinal func modifyDeletionFinalizers(logger klog.Logger, cmpm CSIMigratedPluginManager, volume *v1.PersistentVolume) ([]string, bool) { modified := false var outFinalizers []string - if !utilfeature.DefaultFeatureGate.Enabled(features.HonorPVReclaimPolicy) { - return volume.Finalizers, false - } if !metav1.HasAnnotation(volume.ObjectMeta, storagehelpers.AnnDynamicallyProvisioned) { // PV deletion protection finalizer is currently supported only for dynamically // provisioned volumes. @@ -404,7 +412,7 @@ func modifyDeletionFinalizers(logger klog.Logger, cmpm CSIMigratedPluginManager, provisioner := volume.Annotations[storagehelpers.AnnDynamicallyProvisioned] if cmpm.IsMigrationEnabledForPlugin(provisioner) { // Remove in-tree delete finalizer on the PV as migration is enabled. - if slice.ContainsString(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) { + if slices.Contains(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer) { outFinalizers = slice.RemoveString(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) modified = true } @@ -417,18 +425,18 @@ func modifyDeletionFinalizers(logger klog.Logger, cmpm CSIMigratedPluginManager, } reclaimPolicy := volume.Spec.PersistentVolumeReclaimPolicy // Add back the in-tree PV deletion protection finalizer if does not already exists - if reclaimPolicy == v1.PersistentVolumeReclaimDelete && !slice.ContainsString(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) { + if reclaimPolicy == v1.PersistentVolumeReclaimDelete && !slices.Contains(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer) { logger.V(4).Info("Adding in-tree pv deletion protection finalizer on volume", "volumeName", volume.Name) outFinalizers = append(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer) modified = true - } else if (reclaimPolicy == v1.PersistentVolumeReclaimRetain || reclaimPolicy == v1.PersistentVolumeReclaimRecycle) && slice.ContainsString(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) { + } else if (reclaimPolicy == v1.PersistentVolumeReclaimRetain || reclaimPolicy == v1.PersistentVolumeReclaimRecycle) && slices.Contains(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer) { // Remove the in-tree PV deletion protection finalizer if the reclaim policy is 'Retain' or 'Recycle' logger.V(4).Info("Removing in-tree pv deletion protection finalizer on volume", "volumeName", volume.Name) outFinalizers = slice.RemoveString(outFinalizers, storagehelpers.PVDeletionInTreeProtectionFinalizer, nil) modified = true } // Remove the external PV deletion protection finalizer - if slice.ContainsString(outFinalizers, storagehelpers.PVDeletionProtectionFinalizer, nil) { + if slices.Contains(outFinalizers, storagehelpers.PVDeletionProtectionFinalizer) { logger.V(4).Info("Removing external pv deletion protection finalizer on volume", "volumeName", volume.Name) outFinalizers = slice.RemoveString(outFinalizers, storagehelpers.PVDeletionProtectionFinalizer, nil) modified = true @@ -492,119 +500,144 @@ func updateMigrationAnnotations(logger klog.Logger, cmpm CSIMigratedPluginManage return false } -// volumeWorker processes items from volumeQueue. It must run only once, -// syncVolume is not assured to be reentrant. +// volumeWorker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. func (ctrl *PersistentVolumeController) volumeWorker(ctx context.Context) { + for ctrl.processNextVolumeWorkItem(ctx) { + } +} + +// processNextVolumeWorkItem deals with one key off the volumeQueue. It returns false when it's time to quit. +func (ctrl *PersistentVolumeController) processNextVolumeWorkItem(ctx context.Context) bool { + key, quit := ctrl.volumeQueue.Get() + if quit { + return false + } + defer ctrl.volumeQueue.Done(key) + + err := ctrl.syncVolumeByKey(ctx, key) + if err == nil { + ctrl.volumeQueue.Forget(key) + return true + } + + ctrl.volumeQueue.AddRateLimited(key) + + return true +} + +// syncVolumeByKey processes a single volume identified by key from the queue. +func (ctrl *PersistentVolumeController) syncVolumeByKey(ctx context.Context, key string) error { logger := klog.FromContext(ctx) - workFunc := func(ctx context.Context) bool { - key, quit := ctrl.volumeQueue.Get() - if quit { - return true - } - defer ctrl.volumeQueue.Done(key) - logger.V(5).Info("volumeWorker", "volumeKey", key) + logger.V(5).Info("syncVolumeByKey", "volumeKey", key) - _, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - logger.V(4).Info("Error getting name of volume to get volume from informer", "volumeKey", key, "err", err) - return false - } - volume, err := ctrl.volumeLister.Get(name) - if err == nil { - // The volume still exists in informer cache, the event must have - // been add/update/sync - ctrl.updateVolume(ctx, volume) - return false - } - if !errors.IsNotFound(err) { - logger.V(2).Info("Error getting volume from informer", "volumeKey", key, "err", err) - return false - } + _, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.V(4).Info("Error getting name of volume to get volume from informer", "volumeKey", key, "err", err) + return nil + } - // The volume is not in informer cache, the event must have been - // "delete" - volumeObj, found, err := ctrl.volumes.store.GetByKey(key) - if err != nil { - logger.V(2).Info("Error getting volume from cache", "volumeKey", key, "err", err) - return false - } - if !found { - // The controller has already processed the delete event and - // deleted the volume from its cache - logger.V(2).Info("Deletion of volume was already processed", "volumeKey", key) - return false - } - volume, ok := volumeObj.(*v1.PersistentVolume) - if !ok { - logger.Error(nil, "Expected volume, got", "obj", volumeObj) - return false - } - ctrl.deleteVolume(ctx, volume) - return false + volume, err := ctrl.volumeLister.Get(name) + if err == nil { + // The volume still exists in informer cache, the event must have + // been add/update/sync + return ctrl.updateVolume(ctx, volume) } - for { - if quit := workFunc(ctx); quit { - logger.Info("Volume worker queue shutting down") - return - } + if !errors.IsNotFound(err) { + logger.V(2).Info("Error getting volume from informer", "volumeKey", key, "err", err) + return err + } + + // The volume is not in informer cache, the event must have been "delete" + volumeObj, found, err := ctrl.volumes.store.GetByKey(key) + if err != nil { + logger.V(2).Info("Error getting volume from cache", "volumeKey", key, "err", err) + return err + } + if !found { + // The controller has already processed the delete event and + // deleted the volume from its cache + logger.V(2).Info("Deletion of volume was already processed", "volumeKey", key) + return nil } + volume, ok := volumeObj.(*v1.PersistentVolume) + if !ok { + logger.Error(nil, "Expected volume, got", "obj", volumeObj) + // Don't retry on type assertion failure + return nil + } + ctrl.deleteVolume(ctx, volume) + return nil } -// claimWorker processes items from claimQueue. It must run only once, -// syncClaim is not reentrant. +// claimWorker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. func (ctrl *PersistentVolumeController) claimWorker(ctx context.Context) { + for ctrl.processNextClaimWorkItem(ctx) { + } +} + +// processNextClaimWorkItem deals with one key off the claimQueue. It returns false when it's time to quit. +func (ctrl *PersistentVolumeController) processNextClaimWorkItem(ctx context.Context) bool { + key, quit := ctrl.claimQueue.Get() + if quit { + return false + } + defer ctrl.claimQueue.Done(key) + + err := ctrl.syncClaimByKey(ctx, key) + if err == nil { + ctrl.claimQueue.Forget(key) + return true + } + + ctrl.claimQueue.AddRateLimited(key) + + return true +} + +// syncClaimByKey processes a single claim identified by key from the queue. +func (ctrl *PersistentVolumeController) syncClaimByKey(ctx context.Context, key string) error { logger := klog.FromContext(ctx) - workFunc := func() bool { - key, quit := ctrl.claimQueue.Get() - if quit { - return true - } - defer ctrl.claimQueue.Done(key) - logger.V(5).Info("claimWorker", "claimKey", key) + logger.V(5).Info("syncClaimByKey", "claimKey", key) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - logger.V(4).Info("Error getting namespace & name of claim to get claim from informer", "claimKey", key, "err", err) - return false - } - claim, err := ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name) - if err == nil { - // The claim still exists in informer cache, the event must have - // been add/update/sync - ctrl.updateClaim(ctx, claim) - return false - } - if !errors.IsNotFound(err) { - logger.V(2).Info("Error getting claim from informer", "claimKey", key, "err", err) - return false - } + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.V(4).Info("Error getting namespace & name of claim to get claim from informer", "claimKey", key, "err", err) + return nil + } - // The claim is not in informer cache, the event must have been "delete" - claimObj, found, err := ctrl.claims.GetByKey(key) - if err != nil { - logger.V(2).Info("Error getting claim from cache", "claimKey", key, "err", err) - return false - } - if !found { - // The controller has already processed the delete event and - // deleted the claim from its cache - logger.V(2).Info("Deletion of claim was already processed", "claimKey", key) - return false - } - claim, ok := claimObj.(*v1.PersistentVolumeClaim) - if !ok { - logger.Error(nil, "Expected claim, got", "obj", claimObj) - return false - } - ctrl.deleteClaim(ctx, claim) - return false + claim, err := ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name) + if err == nil { + // The claim still exists in informer cache, the event must have + // been add/update/sync + return ctrl.updateClaim(ctx, claim) } - for { - if quit := workFunc(); quit { - logger.Info("Claim worker queue shutting down") - return - } + if !errors.IsNotFound(err) { + logger.V(2).Info("Error getting claim from informer", "claimKey", key, "err", err) + return err + } + + // The claim is not in informer cache, the event must have been "delete" + claimObj, found, err := ctrl.claims.GetByKey(key) + if err != nil { + logger.V(2).Info("Error getting claim from cache", "claimKey", key, "err", err) + return err + } + if !found { + // The controller has already processed the delete event and + // deleted the claim from its cache + logger.V(2).Info("Deletion of claim was already processed", "claimKey", key) + return nil + } + claim, ok := claimObj.(*v1.PersistentVolumeClaim) + if !ok { + logger.Error(nil, "Expected claim, got", "obj", claimObj) + // Don't retry on type assertion failure + return nil } + ctrl.deleteClaim(ctx, claim) + return nil } // resync supplements short resync period of shared informers - we don't want diff --git a/pkg/controller/volume/persistentvolume/pv_controller_test.go b/pkg/controller/volume/persistentvolume/pv_controller_test.go index 3618b1b11a025..28d02080ddae2 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller_test.go +++ b/pkg/controller/volume/persistentvolume/pv_controller_test.go @@ -27,22 +27,18 @@ import ( v1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" storagelisters "k8s.io/client-go/listers/storage/v1" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-helpers/storage/volume" csitrans "k8s.io/csi-translation-lib" "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/controller" pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/util" ) @@ -53,10 +49,6 @@ import ( // can't reliably simulate periodic sync of volumes/claims - it would be // either very timing-sensitive or slow to wait for real periodic sync. func TestControllerSync(t *testing.T) { - // Default enable the HonorPVReclaimPolicy feature gate. - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HonorPVReclaimPolicy, true) tests := []controllerTest{ // [Unit test set 5] - controller tests. // We test the controller as if @@ -319,13 +311,13 @@ func TestControllerSync(t *testing.T) { // Initialize the controller client := &fake.Clientset{} - fakeVolumeWatch := watch.NewFake() + fakeVolumeWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) client.PrependWatchReactor("persistentvolumes", core.DefaultWatchReactor(fakeVolumeWatch, nil)) - fakeClaimWatch := watch.NewFake() + fakeClaimWatch := watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}) client.PrependWatchReactor("persistentvolumeclaims", core.DefaultWatchReactor(fakeClaimWatch, nil)) - client.PrependWatchReactor("storageclasses", core.DefaultWatchReactor(watch.NewFake(), nil)) - client.PrependWatchReactor("nodes", core.DefaultWatchReactor(watch.NewFake(), nil)) - client.PrependWatchReactor("pods", core.DefaultWatchReactor(watch.NewFake(), nil)) + client.PrependWatchReactor("storageclasses", core.DefaultWatchReactor(watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}), nil)) + client.PrependWatchReactor("nodes", core.DefaultWatchReactor(watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}), nil)) + client.PrependWatchReactor("pods", core.DefaultWatchReactor(watch.NewFakeWithOptions(watch.FakeOptions{Logger: &logger}), nil)) informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) ctrl, err := newTestController(ctx, client, informers, true) @@ -402,7 +394,6 @@ func TestControllerSync(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { doit(test) }) @@ -583,7 +574,7 @@ func TestAnnealMigrationAnnotations(t *testing.T) { } translator := csitrans.New() - cmpm := csimigration.NewPluginManager(translator, utilfeature.DefaultFeatureGate) + cmpm := csimigration.NewPluginManager(translator) logger, _ := ktesting.NewTestContext(t) for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -612,9 +603,6 @@ func TestModifyDeletionFinalizers(t *testing.T) { // in-tree plugin is used as migration is disabled. When that plugin is migrated, a different // non-migrated one should be used. If all plugins are migrated this test can be removed. The // gce in-tree plugin is used for a migrated driver as it is feature-locked as of 1.25. - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HonorPVReclaimPolicy, true) const nonmigratedDriver = "rbd.csi.ceph.com" const migratedPlugin = "kubernetes.io/gce-pd" const migratedDriver = "pd.csi.storage.gke.io" @@ -745,7 +733,7 @@ func TestModifyDeletionFinalizers(t *testing.T) { } translator := csitrans.New() - cmpm := csimigration.NewPluginManager(translator, utilfeature.DefaultFeatureGate) + cmpm := csimigration.NewPluginManager(translator) logger, _ := ktesting.NewTestContext(t) for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/controller/volume/persistentvolume/volume_host.go b/pkg/controller/volume/persistentvolume/volume_host.go index 0f3c8bad0581d..dc937eec9a1f8 100644 --- a/pkg/controller/volume/persistentvolume/volume_host.go +++ b/pkg/controller/volume/persistentvolume/volume_host.go @@ -75,10 +75,6 @@ func (ctrl *PersistentVolumeController) GetMounter() mount.Interface { return nil } -func (ctrl *PersistentVolumeController) GetHostName() string { - return "" -} - func (ctrl *PersistentVolumeController) GetNodeAllocatable() (v1.ResourceList, error) { return v1.ResourceList{}, nil } diff --git a/pkg/controller/volume/protectionutil/utils.go b/pkg/controller/volume/protectionutil/utils.go deleted file mode 100644 index c1a8c89624819..0000000000000 --- a/pkg/controller/volume/protectionutil/utils.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2019 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 protectionutil - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/util/slice" -) - -// IsDeletionCandidate checks if object is candidate to be deleted -func IsDeletionCandidate(obj metav1.Object, finalizer string) bool { - return obj.GetDeletionTimestamp() != nil && slice.ContainsString(obj.GetFinalizers(), - finalizer, nil) -} - -// NeedToAddFinalizer checks if need to add finalizer to object -func NeedToAddFinalizer(obj metav1.Object, finalizer string) bool { - return obj.GetDeletionTimestamp() == nil && !slice.ContainsString(obj.GetFinalizers(), - finalizer, nil) -} diff --git a/pkg/controller/volume/pvcprotection/pvc_protection_controller.go b/pkg/controller/volume/pvcprotection/pvc_protection_controller.go index 526016d1dc024..fb490ee22bedd 100644 --- a/pkg/controller/volume/pvcprotection/pvc_protection_controller.go +++ b/pkg/controller/volume/pvcprotection/pvc_protection_controller.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" coreinformers "k8s.io/client-go/informers/core/v1" clientset "k8s.io/client-go/kubernetes" corelisters "k8s.io/client-go/listers/core/v1" @@ -34,8 +35,9 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/component-helpers/storage/ephemeral" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/controller/util/protectionutil" "k8s.io/kubernetes/pkg/controller/volume/common" - "k8s.io/kubernetes/pkg/controller/volume/protectionutil" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/slice" volumeutil "k8s.io/kubernetes/pkg/volume/util" ) @@ -115,6 +117,10 @@ type Controller struct { pvcProcessingStore *pvcProcessingStore } +var unusedSinceNowFunc = metav1.Now + +type podUsageCheckFunc func(logger klog.Logger, pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool + // NewPVCProtectionController returns a new instance of PVCProtectionController. func NewPVCProtectionController(logger klog.Logger, pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface) (*Controller, error) { e := &Controller{ @@ -254,10 +260,20 @@ func (c *Controller) processPVC(ctx context.Context, pvcNamespace, pvcName strin return err } + if utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeClaimUnusedSinceTime) && pvc.DeletionTimestamp == nil { + isUsed, err := c.isBeingUsedWith(ctx, pvc, lazyLivePodList, c.podUsesPVCForUnusedSince) + if err != nil { + return err + } + if err := c.updateUnusedCondition(ctx, pvc, isUsed); err != nil { + return err + } + } + if protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) { // PVC should be deleted. Check if it's used and remove finalizer if // it's not. - isUsed, err := c.isBeingUsed(ctx, pvc, lazyLivePodList) + isUsed, err := c.isBeingUsedWith(ctx, pvc, lazyLivePodList, c.podUsesPVCForDeletion) if err != nil { return err } @@ -303,12 +319,63 @@ func (c *Controller) removeFinalizer(ctx context.Context, pvc *v1.PersistentVolu return nil } -func (c *Controller) isBeingUsed(ctx context.Context, pvc *v1.PersistentVolumeClaim, lazyLivePodList *LazyLivePodList) (bool, error) { +func (c *Controller) updateUnusedCondition(ctx context.Context, pvc *v1.PersistentVolumeClaim, isUsed bool) error { + existingCondition := findCondition(pvc.Status.Conditions, v1.PersistentVolumeClaimUnused) + + switch { + case isUsed && (existingCondition == nil || existingCondition.Status == v1.ConditionTrue): + // PVC was unused but is now in use -> set Unused=False + return c.setUnusedCondition(ctx, pvc, v1.ConditionFalse, "PodUsingPVC", "A pod is currently referencing this PVC") + case !isUsed && (existingCondition == nil || existingCondition.Status != v1.ConditionTrue): + // PVC is not in use and condition doesn't reflect that -> set Unused=True + return c.setUnusedCondition(ctx, pvc, v1.ConditionTrue, "NoPodsUsingPVC", "No pods are currently referencing this PVC") + default: + return nil + } +} + +func (c *Controller) setUnusedCondition(ctx context.Context, pvc *v1.PersistentVolumeClaim, status v1.ConditionStatus, reason, message string) error { + claimClone := pvc.DeepCopy() + now := unusedSinceNowFunc() + newCondition := v1.PersistentVolumeClaimCondition{ + Type: v1.PersistentVolumeClaimUnused, + Status: status, + LastTransitionTime: now, + Reason: reason, + Message: message, + } + + if existing := findCondition(claimClone.Status.Conditions, v1.PersistentVolumeClaimUnused); existing != nil { + *existing = newCondition + } else { + claimClone.Status.Conditions = append(claimClone.Status.Conditions, newCondition) + } + + _, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(ctx, claimClone, metav1.UpdateOptions{}) + logger := klog.FromContext(ctx) + if err != nil { + logger.Error(err, "Error updating Unused condition in PVC status", "PVC", klog.KObj(pvc)) + return err + } + logger.V(3).Info("Updated Unused condition in PVC status", "PVC", klog.KObj(pvc), "status", status) + return nil +} + +func findCondition(conditions []v1.PersistentVolumeClaimCondition, condType v1.PersistentVolumeClaimConditionType) *v1.PersistentVolumeClaimCondition { + for i := range conditions { + if conditions[i].Type == condType { + return &conditions[i] + } + } + return nil +} + +func (c *Controller) isBeingUsedWith(ctx context.Context, pvc *v1.PersistentVolumeClaim, lazyLivePodList *LazyLivePodList, podUsage podUsageCheckFunc) (bool, error) { // Look for a Pod using pvc in the Informer's cache. If one is found the // correct decision to keep pvc is taken without doing an expensive live // list. logger := klog.FromContext(ctx) - if inUse, err := c.askInformer(logger, pvc); err != nil { + if inUse, err := c.askInformer(logger, pvc, podUsage); err != nil { // No need to return because a live list will follow. logger.Error(err, "") } else if inUse { @@ -323,10 +390,10 @@ func (c *Controller) isBeingUsed(ctx context.Context, pvc *v1.PersistentVolumeCl // Use a "lazy" live pod list: lazyLivePodList caches the first successful live pod list response, // so for a large number of PVC deletions in a short duration, subsequent requests can use the cached pod list // instead of issuing a lot of API requests. The cache is refreshed for each run of processNextWorkItem(). - return c.askAPIServer(ctx, pvc, lazyLivePodList) + return c.askAPIServer(ctx, pvc, lazyLivePodList, podUsage) } -func (c *Controller) askInformer(logger klog.Logger, pvc *v1.PersistentVolumeClaim) (bool, error) { +func (c *Controller) askInformer(logger klog.Logger, pvc *v1.PersistentVolumeClaim, podUsage podUsageCheckFunc) (bool, error) { logger.V(4).Info("Looking for Pods using PVC in the Informer's cache", "PVC", klog.KObj(pvc)) // The indexer is used to find pods which might use the PVC. @@ -343,7 +410,7 @@ func (c *Controller) askInformer(logger klog.Logger, pvc *v1.PersistentVolumeCla // We still need to look at each volume: that's redundant for volume.PersistentVolumeClaim, // but for volume.Ephemeral we need to be sure that this particular PVC is the one // created for the ephemeral volume. - if c.podUsesPVC(logger, pod, pvc) { + if podUsage(logger, pod, pvc) { return true, nil } } @@ -352,7 +419,7 @@ func (c *Controller) askInformer(logger klog.Logger, pvc *v1.PersistentVolumeCla return false, nil } -func (c *Controller) askAPIServer(ctx context.Context, pvc *v1.PersistentVolumeClaim, lazyLivePodList *LazyLivePodList) (bool, error) { +func (c *Controller) askAPIServer(ctx context.Context, pvc *v1.PersistentVolumeClaim, lazyLivePodList *LazyLivePodList, podUsage podUsageCheckFunc) (bool, error) { logger := klog.FromContext(ctx) logger.V(4).Info("Looking for Pods using PVC", "PVC", klog.KObj(pvc)) if lazyLivePodList.getCache() == nil { @@ -370,7 +437,7 @@ func (c *Controller) askAPIServer(ctx context.Context, pvc *v1.PersistentVolumeC } for _, pod := range lazyLivePodList.getCache() { - if c.podUsesPVC(logger, &pod, pvc) { + if podUsage(logger, &pod, pvc) { return true, nil } } @@ -379,7 +446,7 @@ func (c *Controller) askAPIServer(ctx context.Context, pvc *v1.PersistentVolumeC return false, nil } -func (c *Controller) podUsesPVC(logger klog.Logger, pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool { +func (c *Controller) podUsesPVCForDeletion(logger klog.Logger, pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool { // Check whether pvc is used by pod only if pod is scheduled, because // kubelet sees pods after they have been scheduled and it won't allow // starting a pod referencing a PVC with a non-nil deletionTimestamp. @@ -395,6 +462,21 @@ func (c *Controller) podUsesPVC(logger klog.Logger, pod *v1.Pod, pvc *v1.Persist return false } +func (c *Controller) podUsesPVCForUnusedSince(logger klog.Logger, pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool { + if volumeutil.IsPodTerminated(pod, pod.Status) { + return false + } + + for _, volume := range pod.Spec.Volumes { + if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc.Name || + volume.Ephemeral != nil && ephemeral.VolumeClaimName(pod, &volume) == pvc.Name && ephemeral.VolumeIsForPod(pod, pvc) == nil { + logger.V(4).Info("Pod references PVC for unused tracking", "pod", klog.KObj(pod), "PVC", klog.KObj(pvc)) + return true + } + } + return false +} + // podIsShutDown returns true if kubelet is done with the pod or // it was force-deleted. func podIsShutDown(pod *v1.Pod) bool { @@ -439,6 +521,10 @@ func (c *Controller) pvcAddedUpdated(logger klog.Logger, obj interface{}) { } logger.V(4).Info("Got event on PVC", "pvc", klog.KObj(pvc)) + if utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeClaimUnusedSinceTime) && findCondition(pvc.Status.Conditions, v1.PersistentVolumeClaimUnused) == nil { + c.queue.Add(key) + } + if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) || protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) { c.queue.Add(key) } @@ -482,7 +568,8 @@ func (*Controller) parsePod(obj interface{}) *v1.Pod { func (c *Controller) enqueuePVCs(logger klog.Logger, pod *v1.Pod, deleted bool) { // Filter out pods that can't help us to remove a finalizer on PVC - if !deleted && !volumeutil.IsPodTerminated(pod, pod.Status) && pod.Spec.NodeName != "" { + if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeClaimUnusedSinceTime) && + !deleted && !volumeutil.IsPodTerminated(pod, pod.Status) && pod.Spec.NodeName != "" { return } diff --git a/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go b/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go index 3884ac94e6a17..e0506e48970e4 100644 --- a/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go +++ b/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go @@ -30,13 +30,17 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/apimachinery/pkg/util/version" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/features" volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/utils/dump" ) type reaction struct { @@ -148,6 +152,21 @@ func withProtectionFinalizer(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolume return pvc } +func withUnusedCondition(status v1.ConditionStatus, ts metav1.Time, pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim { + pvc.Status.Conditions = append(pvc.Status.Conditions, v1.PersistentVolumeClaimCondition{ + Type: v1.PersistentVolumeClaimUnused, + Status: status, + LastTransitionTime: ts, + Reason: "NoPodsUsingPVC", + Message: "No pods are currently referencing this PVC", + }) + if status == v1.ConditionFalse { + pvc.Status.Conditions[len(pvc.Status.Conditions)-1].Reason = "PodUsingPVC" + pvc.Status.Conditions[len(pvc.Status.Conditions)-1].Message = "A pod is currently referencing this PVC" + } + return pvc +} + func deleted(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim { pvc.DeletionTimestamp = &metav1.Time{} return pvc @@ -207,7 +226,8 @@ func TestPVCProtectionController(t *testing.T) { } tests := []struct { - name string + name string + enablePVCUnusedSinceTime bool // Object to insert into fake kubeclient before the test starts. initialObjects []runtime.Object // Whether not to insert the content of initialObjects into the @@ -402,6 +422,188 @@ func TestPVCProtectionController(t *testing.T) { }, }, // + // PVC UnusedSince condition — when feature gate is enabled + // + { + name: "feature enabled: unused PVC with no pods -> Unused=True condition set", + enablePVCUnusedSinceTime: true, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: PVC already has Unused=True and is still unused -> no update", + enablePVCUnusedSinceTime: true, + updatedPVCs: []*v1.PersistentVolumeClaim{withUnusedCondition(v1.ConditionTrue, metav1.Unix(100, 0), withProtectionFinalizer(pvc()))}, + // PVC already has condition (index != -1), so pvcAddedUpdated does not enqueue it. + // No pod event to trigger re-evaluation. No actions expected. + expectedActions: []clienttesting.Action{}, + }, + { + name: "feature enabled: running pod references PVC with Unused=True -> condition set to False", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withUnusedCondition(v1.ConditionTrue, metav1.Unix(100, 0), withProtectionFinalizer(pvc())), + }, + updatedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionFalse, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: PVC in use by running pod without condition -> Unused=False set proactively", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())), + }, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionFalse, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: deleting PVC -> condition not updated, finalizer removed", + enablePVCUnusedSinceTime: true, + updatedPVCs: []*v1.PersistentVolumeClaim{deleted(withProtectionFinalizer(pvc()))}, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())), + }, + }, + { + name: "feature disabled: unused PVC with no pods -> no condition action", + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{}, + }, + { + name: "feature enabled: unscheduled pending pod references PVC -> Unused=False set proactively", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + unscheduled(withPVC(defaultPVCName, pod())), + }, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionFalse, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: terminated (Succeeded) pod does not count as using PVC -> Unused=True set", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withStatus(v1.PodSucceeded, withPVC(defaultPVCName, pod())), + }, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: terminated (Failed) pod does not count as using PVC -> Unused=True set", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withStatus(v1.PodFailed, withPVC(defaultPVCName, pod())), + }, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: status update fails -> controller retries", + enablePVCUnusedSinceTime: true, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + reactors: []reaction{ + { + verb: "update", + resource: "persistentvolumeclaims", + reactorfn: generateUpdateErrorFunc(t, 2), + }, + }, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: pod not in informer cache but exists in API -> Unused=False set proactively", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withPVC(defaultPVCName, pod()), + }, + informersAreLate: true, + updatedPVCs: []*v1.PersistentVolumeClaim{withProtectionFinalizer(pvc())}, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionFalse, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: one of two pods deleted, remaining pod keeps PVC in use -> Unused=False set proactively", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withPVC(defaultPVCName, pod()), + withProtectionFinalizer(pvc()), + }, + deletedPod: withPVC(defaultPVCName, withUID("uid2", podWithConfig("pod2", defaultNS))), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionFalse, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: deleting PVC that already has Unused=True condition -> condition preserved when removing finalizer", + enablePVCUnusedSinceTime: true, + updatedPVCs: []*v1.PersistentVolumeClaim{ + deleted(withUnusedCondition(v1.ConditionTrue, metav1.Unix(100, 0), withProtectionFinalizer(pvc()))), + }, + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(withUnusedCondition(v1.ConditionTrue, metav1.Unix(100, 0), pvc()))), + }, + }, + { + name: "feature enabled: pod deleted, PVC becomes unused -> Unused=True condition set", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withProtectionFinalizer(pvc()), + }, + deletedPod: withPVC(defaultPVCName, pod()), + expectedActions: []clienttesting.Action{ + clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}), + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionTrue, metav1.Unix(123, 0), withProtectionFinalizer(pvc()))), + }, + }, + { + name: "feature enabled: used PVC without finalizer -> Unused=False set then finalizer added", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withPVC(defaultPVCName, pod()), + }, + updatedPVCs: []*v1.PersistentVolumeClaim{pvc()}, + expectedActions: []clienttesting.Action{ + // First: condition set proactively (UpdateStatus on PVC without finalizer) + clienttesting.NewUpdateSubresourceAction(pvcGVR, "status", defaultNS, withUnusedCondition(v1.ConditionFalse, metav1.Unix(123, 0), pvc())), + // Then: finalizer added (Update on original PVC without condition) + clienttesting.NewUpdateAction(pvcGVR, defaultNS, withProtectionFinalizer(pvc())), + }, + }, + { + name: "feature enabled: deleted PVC with finalizer and pod using it -> finalizer kept (existing behavior)", + enablePVCUnusedSinceTime: true, + initialObjects: []runtime.Object{ + withPVC(defaultPVCName, pod()), + }, + updatedPVCs: []*v1.PersistentVolumeClaim{deleted(withProtectionFinalizer(pvc()))}, + expectedActions: []clienttesting.Action{}, + }, + // // Pod events // { @@ -484,6 +686,15 @@ func TestPVCProtectionController(t *testing.T) { } for _, test := range tests { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.36")) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PersistentVolumeClaimUnusedSinceTime, test.enablePVCUnusedSinceTime) + + origNowFunc := unusedSinceNowFunc + unusedSinceNowFunc = func() metav1.Time { return metav1.Unix(123, 0) } + t.Cleanup(func() { + unusedSinceNowFunc = origNowFunc + }) + // Create initial data for client and informers. var ( clientObjs []runtime.Object diff --git a/pkg/controller/volume/pvprotection/pv_protection_controller.go b/pkg/controller/volume/pvprotection/pv_protection_controller.go index 1b748226d04bd..161ef7a370b63 100644 --- a/pkg/controller/volume/pvprotection/pv_protection_controller.go +++ b/pkg/controller/volume/pvprotection/pv_protection_controller.go @@ -33,7 +33,7 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/controller/volume/protectionutil" + "k8s.io/kubernetes/pkg/controller/util/protectionutil" "k8s.io/kubernetes/pkg/util/slice" volumeutil "k8s.io/kubernetes/pkg/volume/util" ) diff --git a/pkg/controller/volume/pvprotection/pv_protection_controller_test.go b/pkg/controller/volume/pvprotection/pv_protection_controller_test.go index 310f5fecd704e..6649777af6977 100644 --- a/pkg/controller/volume/pvprotection/pv_protection_controller_test.go +++ b/pkg/controller/volume/pvprotection/pv_protection_controller_test.go @@ -29,13 +29,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/controller" volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/utils/dump" ) const defaultPVName = "default-pv" diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache.go b/pkg/controller/volume/selinuxwarning/cache/volumecache.go index ebd756584c0e6..32f7ff48651e9 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache.go @@ -266,9 +266,7 @@ func (c *volumeCache) dump(logger klog.Logger) { for volumeID := range c.volumes { volumeIDs = append(volumeIDs, volumeID) } - sort.Slice(volumeIDs, func(i, j int) bool { - return volumeIDs[i] < volumeIDs[j] - }) + slices.Sort(volumeIDs) for _, volumeID := range volumeIDs { volume := c.volumes[volumeID] logger.Info("Cached volume", "volume", volumeID, "csiDriver", volume.csiDriver) diff --git a/pkg/controller/volume/selinuxwarning/selinux_warning_controller.go b/pkg/controller/volume/selinuxwarning/selinux_warning_controller.go index 1281074dc06af..53c08d1f6ab5f 100644 --- a/pkg/controller/volume/selinuxwarning/selinux_warning_controller.go +++ b/pkg/controller/volume/selinuxwarning/selinux_warning_controller.go @@ -29,7 +29,6 @@ import ( errorutils "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" - utilfeature "k8s.io/apiserver/pkg/util/feature" coreinformers "k8s.io/client-go/informers/core/v1" storageinformersv1 "k8s.io/client-go/informers/storage/v1" clientset "k8s.io/client-go/kubernetes" @@ -130,7 +129,7 @@ func NewController( } csiTranslator := csitrans.New() c.csiTranslator = csiTranslator - c.cmpm = csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) + c.cmpm = csimigration.NewPluginManager(csiTranslator) // Index pods by its PVC keys. Then we don't need to iterate all pods every time to find // pods which reference given PVC. @@ -473,7 +472,7 @@ func (c *Controller) syncPod(ctx context.Context, pod *v1.Pod) error { if volumeutil.IsMultipleSELinuxLabelsError(err) { c.eventRecorder.Eventf(pod, v1.EventTypeWarning, "MultipleSELinuxLabels", "Volume %q is mounted twice with different SELinux labels inside this pod", mount) } - logger.V(4).Error(err, "failed to get SELinux label", "pod", klog.KObj(pod), "volume", mount) + logger.V(4).Info("failed to get SELinux label", "pod", klog.KObj(pod), "volume", mount, "err", err) errs = append(errs, err) continue } @@ -536,7 +535,7 @@ func (c *Controller) reportConflictEvents(logger klog.Logger, conflicts []volume for _, conflict := range conflicts { pod, err := c.podLister.Pods(conflict.Pod.Namespace).Get(conflict.Pod.Name) if err != nil { - logger.V(2).Error(err, "failed to get first pod for event", "pod", conflict.Pod) + logger.V(2).Info("failed to get first pod for event", "pod", conflict.Pod, "err", err) // It does not make sense to report a conflict that has been resolved by deleting one of the pods. return } diff --git a/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go b/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go index 1d2e66f4e40e9..48832947a3009 100644 --- a/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go +++ b/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go @@ -27,17 +27,14 @@ import ( storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/controller" volumecache "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/cache" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" volumetesting "k8s.io/kubernetes/pkg/volume/testing" "k8s.io/utils/ptr" @@ -497,8 +494,6 @@ func TestSELinuxWarningController_Sync(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxChangePolicy, true) - var wg sync.WaitGroup defer wg.Wait() _, ctx := ktesting.NewTestContext(t) diff --git a/pkg/controller/volume/selinuxwarning/volumehost.go b/pkg/controller/volume/selinuxwarning/volumehost.go index e301836c21794..71a9822e7f9d6 100644 --- a/pkg/controller/volume/selinuxwarning/volumehost.go +++ b/pkg/controller/volume/selinuxwarning/volumehost.go @@ -75,10 +75,6 @@ func (c *Controller) GetMounter() mount.Interface { return nil } -func (c *Controller) GetHostName() string { - return "" -} - func (c *Controller) GetNodeAllocatable() (v1.ResourceList, error) { return v1.ResourceList{}, nil } diff --git a/pkg/controller/volume/vacprotection/vac_protection_controller.go b/pkg/controller/volume/vacprotection/vac_protection_controller.go index a9ede7f5da862..10f22d9d9e27d 100644 --- a/pkg/controller/volume/vacprotection/vac_protection_controller.go +++ b/pkg/controller/volume/vacprotection/vac_protection_controller.go @@ -36,7 +36,7 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/controller/volume/protectionutil" + "k8s.io/kubernetes/pkg/controller/util/protectionutil" "k8s.io/kubernetes/pkg/util/slice" volumeutil "k8s.io/kubernetes/pkg/volume/util" "k8s.io/utils/ptr" diff --git a/pkg/controller/volume/vacprotection/vac_protection_controller_test.go b/pkg/controller/volume/vacprotection/vac_protection_controller_test.go index 8fa2cf0c2b732..93d7b8277837a 100644 --- a/pkg/controller/volume/vacprotection/vac_protection_controller_test.go +++ b/pkg/controller/volume/vacprotection/vac_protection_controller_test.go @@ -29,14 +29,14 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/controller/volume/protectionutil" + "k8s.io/kubernetes/pkg/controller/util/protectionutil" volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/utils/dump" ) var ( diff --git a/pkg/controlplane/apiserver/admission/config.go b/pkg/controlplane/apiserver/admission/config.go index 55269b12a9cfe..505f056d63875 100644 --- a/pkg/controlplane/apiserver/admission/config.go +++ b/pkg/controlplane/apiserver/admission/config.go @@ -24,6 +24,7 @@ import ( "k8s.io/apiserver/pkg/admission" webhookinit "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer" egressselector "k8s.io/apiserver/pkg/server/egressselector" + "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiserver/pkg/util/webhook" externalinformers "k8s.io/client-go/informers" "k8s.io/client-go/rest" @@ -35,6 +36,7 @@ import ( type Config struct { LoopbackClientConfig *rest.Config ExternalInformers externalinformers.SharedInformerFactory + APIResourceConfig storage.APIResourceConfigSource } // New sets up the plugins and admission start hooks needed for admission @@ -42,8 +44,12 @@ func (c *Config) New(proxyTransport *http.Transport, egressSelector *egressselec webhookAuthResolverWrapper := webhook.NewDefaultAuthenticationInfoResolverWrapper(proxyTransport, egressSelector, c.LoopbackClientConfig, tp) webhookPluginInitializer := webhookinit.NewPluginInitializer(webhookAuthResolverWrapper, serviceResolver) + quotaConfiguration, err := quotainstall.NewQuotaConfigurationForAdmission(c.ExternalInformers, c.APIResourceConfig) + if err != nil { + return nil, err + } kubePluginInitializer := NewPluginInitializer( - quotainstall.NewQuotaConfigurationForAdmission(c.ExternalInformers), + quotaConfiguration, exclusion.Excluded(), ) diff --git a/pkg/controlplane/apiserver/config.go b/pkg/controlplane/apiserver/config.go index f284b48c7ec81..8a19b25f4058b 100644 --- a/pkg/controlplane/apiserver/config.go +++ b/pkg/controlplane/apiserver/config.go @@ -54,6 +54,7 @@ import ( clientgoclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/keyutil" basecompatibility "k8s.io/component-base/compatibility" + metricsfeatures "k8s.io/component-base/metrics/features" aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" openapicommon "k8s.io/kube-openapi/pkg/common" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -136,7 +137,7 @@ func BuildGenericConfig( return } - if lastErr = s.SecureServing.ApplyTo(&genericConfig.SecureServing, &genericConfig.LoopbackClientConfig); lastErr != nil { + if lastErr = s.SecureServing.ApplyToConfig(genericConfig); lastErr != nil { return } @@ -295,6 +296,7 @@ func CreateConfig( ) { proxyTransport := CreateProxyTransport() + metricsfeatures.ApplyFeatureGates(utilfeature.DefaultFeatureGate) opts.Metrics.Apply() serviceaccount.RegisterMetrics() @@ -344,7 +346,9 @@ func CreateConfig( opts.PeerAdvertiseAddress, genericConfig.APIServerID, config.Extra.PeerEndpointLeaseReconciler, - config.Generic.Serializer) + config.Generic.Serializer, + config.Generic.EgressSelector, + ) if err != nil { return nil, nil, err } @@ -374,6 +378,7 @@ func CreateConfig( genericAdmissionConfig := controlplaneadmission.Config{ ExternalInformers: versionedInformers, LoopbackClientConfig: genericConfig.LoopbackClientConfig, + APIResourceConfig: storageFactory.APIResourceConfigSource, } genericInitializers, err := genericAdmissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver, genericConfig.TracerProvider) if err != nil { diff --git a/pkg/controlplane/apiserver/options/validation_test.go b/pkg/controlplane/apiserver/options/validation_test.go index 2799b79212212..ea74d862e5954 100644 --- a/pkg/controlplane/apiserver/options/validation_test.go +++ b/pkg/controlplane/apiserver/options/validation_test.go @@ -28,7 +28,6 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" basecompatibility "k8s.io/component-base/compatibility" basemetrics "k8s.io/component-base/metrics" - "k8s.io/kubernetes/pkg/features" peerreconcilers "k8s.io/apiserver/pkg/reconcilers" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -150,9 +149,7 @@ func TestValidateUnknownVersionInteroperabilityProxy(t *testing.T) { PeerCAFile: test.peerCAFile, PeerAdvertiseAddress: test.peerAdvertiseAddress, } - if test.featureEnabled { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.UnknownVersionInteroperabilityProxy, true) - } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.UnknownVersionInteroperabilityProxy, test.featureEnabled) var errMessageGot string if errs := validateUnknownVersionInteroperabilityProxyFlags(options); len(errs) > 0 { errMessageGot = errs[0].Error() @@ -241,13 +238,11 @@ func TestValidateOptions(t *testing.T) { func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { tests := []struct { name string - featureEnabled bool options *Options expectedErrors []error }{ { - name: "Signing keys file provided while external signer endpoint is provided", - featureEnabled: true, + name: "Signing keys file provided while external signer endpoint is provided", expectedErrors: []error{ fmt.Errorf("can't set `--service-account-signing-key-file` and/or `--service-account-key-file` with `--service-account-signing-endpoint` (They are mutually exclusive)"), }, @@ -257,8 +252,7 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, }, { - name: "Verification keys file provided while external signer endpoint is provided", - featureEnabled: true, + name: "Verification keys file provided while external signer endpoint is provided", expectedErrors: []error{ fmt.Errorf("can't set `--service-account-signing-key-file` and/or `--service-account-key-file` with `--service-account-signing-endpoint` (They are mutually exclusive)"), }, @@ -275,8 +269,7 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, }, { - name: "Verification key and signing key file provided while external signer endpoint is provided", - featureEnabled: true, + name: "Verification key and signing key file provided while external signer endpoint is provided", expectedErrors: []error{ fmt.Errorf("can't set `--service-account-signing-key-file` and/or `--service-account-key-file` with `--service-account-signing-endpoint` (They are mutually exclusive)"), }, @@ -293,27 +286,15 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, }, }, - { - name: "feature disabled and external signer endpoint is provided", - featureEnabled: false, - expectedErrors: []error{ - fmt.Errorf("setting `--service-account-signing-endpoint` requires enabling ExternalServiceAccountTokenSigner feature gate"), - }, - options: &Options{ - ServiceAccountSigningEndpoint: "@ebc.eng.hij", - }, - }, { name: "relative external signer endpoint provided", - featureEnabled: true, expectedErrors: []error{}, options: &Options{ ServiceAccountSigningEndpoint: "abc", }, }, { - name: "invalid external signer endpoint provided - 2", - featureEnabled: true, + name: "invalid external signer endpoint provided - 2", expectedErrors: []error{ fmt.Errorf("invalid value \"@abc@\" passed for `--service-account-signing-endpoint`, when prefixed with @ must be a valid abstract socket name"), }, @@ -322,8 +303,7 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, }, { - name: "invalid external signer endpoint provided - 3", - featureEnabled: true, + name: "invalid external signer endpoint provided - 3", expectedErrors: []error{ fmt.Errorf("invalid value \"@abc.abc .ae\" passed for `--service-account-signing-endpoint`, when prefixed with @ must be a valid abstract socket name"), }, @@ -333,7 +313,6 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, { name: "valid external signer endpoint provided - 1", - featureEnabled: true, expectedErrors: []error{}, options: &Options{ ServiceAccountSigningEndpoint: "/e/an_b-d/efg", @@ -341,7 +320,6 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, { name: "valid external signer endpoint provided - 2", - featureEnabled: true, expectedErrors: []error{}, options: &Options{ ServiceAccountSigningEndpoint: "@ebc.sock", @@ -349,18 +327,15 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { }, { name: "valid external signer endpoint provided - 3", - featureEnabled: true, expectedErrors: []error{}, options: &Options{ ServiceAccountSigningEndpoint: "@ebc.eng.hij", }, }, { - name: "All errors at once", - featureEnabled: false, + name: "All errors at once", expectedErrors: []error{ fmt.Errorf("can't set `--service-account-signing-key-file` and/or `--service-account-key-file` with `--service-account-signing-endpoint` (They are mutually exclusive)"), - fmt.Errorf("setting `--service-account-signing-endpoint` requires enabling ExternalServiceAccountTokenSigner feature gate"), fmt.Errorf("invalid value \"@a@\" passed for `--service-account-signing-endpoint`, when prefixed with @ must be a valid abstract socket name"), }, options: &Options{ @@ -388,7 +363,6 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { } } - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExternalServiceAccountTokenSigner, test.featureEnabled) errs := validateServiceAccountTokenSigningConfig(test.options) if !reflect.DeepEqual(errs, test.expectedErrors) { t.Errorf("Expected errors message: %v \n but got: %v", test.expectedErrors, errs) diff --git a/pkg/controlplane/apiserver/peer.go b/pkg/controlplane/apiserver/peer.go index 9806bf25701f3..3d1fa8a54189c 100644 --- a/pkg/controlplane/apiserver/peer.go +++ b/pkg/controlplane/apiserver/peer.go @@ -28,6 +28,7 @@ import ( "k8s.io/client-go/transport" genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/server/egressselector" serverstorage "k8s.io/apiserver/pkg/server/storage" utilpeerproxy "k8s.io/apiserver/pkg/util/peerproxy" coordinationv1informers "k8s.io/client-go/informers/coordination/v1" @@ -52,7 +53,9 @@ func BuildPeerProxy( peerAdvertiseAddress reconcilers.PeerAdvertiseAddress, apiServerID string, reconciler reconcilers.PeerEndpointLeaseReconciler, - serializer runtime.NegotiatedSerializer) (utilpeerproxy.Interface, error) { + serializer runtime.NegotiatedSerializer, + es *egressselector.EgressSelector, +) (utilpeerproxy.Interface, error) { if proxyClientCertFile == "" { return nil, fmt.Errorf("error building peer proxy handler, proxy-cert-file not specified") } @@ -69,6 +72,19 @@ func BuildPeerProxy( ServerName: "kubernetes.default.svc", }} + if es != nil { + egressDialer, err := es.Lookup(egressselector.ControlPlane.AsNetworkContext()) + if err != nil { + return nil, err + } + + // We only set the DialHolder if an egress dialer is explicitly configured for the + // ControlPlane network context, otherwise the transport will fall back to the default direct dialer. + if egressDialer != nil { + proxyClientConfig.DialHolder = &transport.DialHolder{Dial: egressDialer} + } + } + return utilpeerproxy.NewPeerProxyHandler( apiServerID, IdentityLeaseComponentLabelKey+"="+KubeAPIServer, diff --git a/pkg/controlplane/apiserver/samples/generic/server/admission_test.go b/pkg/controlplane/apiserver/samples/generic/server/admission_test.go index b9aab2fd49c2a..ad3054e76116b 100644 --- a/pkg/controlplane/apiserver/samples/generic/server/admission_test.go +++ b/pkg/controlplane/apiserver/samples/generic/server/admission_test.go @@ -21,13 +21,17 @@ import ( "k8s.io/apimachinery/pkg/util/sets" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" + "k8s.io/kubernetes/plugin/pkg/admission/job" "k8s.io/kubernetes/plugin/pkg/admission/limitranger" "k8s.io/kubernetes/plugin/pkg/admission/network/defaultingressclass" "k8s.io/kubernetes/plugin/pkg/admission/nodedeclaredfeatures" "k8s.io/kubernetes/plugin/pkg/admission/nodetaint" + "k8s.io/kubernetes/plugin/pkg/admission/podgroup" + "k8s.io/kubernetes/plugin/pkg/admission/podresize" "k8s.io/kubernetes/plugin/pkg/admission/podtopologylabels" podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority" "k8s.io/kubernetes/plugin/pkg/admission/runtimeclass" + "k8s.io/kubernetes/plugin/pkg/admission/scheduling/podgroupprotection" "k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize" "k8s.io/kubernetes/plugin/pkg/admission/storage/storageclass/setdefault" @@ -39,13 +43,17 @@ var intentionallyOffPlugins = sets.New[string]( setdefault.PluginName, // DefaultStorageClass resize.PluginName, // PersistentVolumeClaimResize storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection + podgroupprotection.PluginName, // PodGroupProtection podpriority.PluginName, // Priority nodetaint.PluginName, // TaintNodesByCondition runtimeclass.PluginName, // RuntimeClass defaultingressclass.PluginName, // DefaultIngressClass podsecurity.PluginName, // PodSecurity podtopologylabels.PluginName, // PodTopologyLabels + podgroup.PluginName, // PodGroupWorkloadExists nodedeclaredfeatures.PluginName, // NodeDeclaredFeatures + job.PluginName, // JobValidation + podresize.PluginName, // PodResize ) func TestDefaultOffAdmissionPlugins(t *testing.T) { diff --git a/pkg/controlplane/apiserver/samples/generic/server/config.go b/pkg/controlplane/apiserver/samples/generic/server/config.go index fe57b93fe8c20..879e2f601cb0a 100644 --- a/pkg/controlplane/apiserver/samples/generic/server/config.go +++ b/pkg/controlplane/apiserver/samples/generic/server/config.go @@ -80,7 +80,7 @@ func NewConfig(opts options.CompletedOptions) (*Config, error) { genericConfig, versionedInformers, storageFactory, err := controlplaneapiserver.BuildGenericConfig( opts, []*runtime.Scheme{legacyscheme.Scheme, apiextensionsapiserver.Scheme, aggregatorscheme.Scheme}, - controlplane.DefaultAPIResourceConfigSource(), + controlplane.DefaultGenericAPIResourceConfigSource(), generatedopenapi.GetOpenAPIDefinitions, ) if err != nil { diff --git a/pkg/controlplane/apiserver/samples/generic/server/serviceaccounts.go b/pkg/controlplane/apiserver/samples/generic/server/serviceaccounts.go index fd944e00e54b0..932c5ac277c6d 100644 --- a/pkg/controlplane/apiserver/samples/generic/server/serviceaccounts.go +++ b/pkg/controlplane/apiserver/samples/generic/server/serviceaccounts.go @@ -17,6 +17,8 @@ limitations under the License. package server import ( + "context" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/informers" @@ -36,18 +38,18 @@ func genericTokenGetter(factory informers.SharedInformerFactory) serviceaccount. return clientGetter{secretLister: factory.Core().V1().Secrets().Lister(), serviceAccountLister: factory.Core().V1().ServiceAccounts().Lister()} } -func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) { +func (c clientGetter) GetServiceAccount(ctx context.Context, namespace, name string) (*v1.ServiceAccount, error) { return c.serviceAccountLister.ServiceAccounts(namespace).Get(name) } -func (c clientGetter) GetPod(namespace, name string) (*v1.Pod, error) { +func (c clientGetter) GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) { return nil, apierrors.NewNotFound(v1.Resource("pods"), name) } -func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) { +func (c clientGetter) GetSecret(ctx context.Context, namespace, name string) (*v1.Secret, error) { return c.secretLister.Secrets(namespace).Get(name) } -func (c clientGetter) GetNode(name string) (*v1.Node, error) { +func (c clientGetter) GetNode(ctx context.Context, name string) (*v1.Node, error) { return nil, apierrors.NewNotFound(v1.Resource("nodes"), name) } diff --git a/pkg/controlplane/apiserver/server.go b/pkg/controlplane/apiserver/server.go index 614c0fe4559c4..1a3968867078d 100644 --- a/pkg/controlplane/apiserver/server.go +++ b/pkg/controlplane/apiserver/server.go @@ -34,12 +34,10 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/dynamiccertificates" - "k8s.io/apiserver/pkg/server/flagz" serverstorage "k8s.io/apiserver/pkg/server/storage" utilfeature "k8s.io/apiserver/pkg/util/feature" clientgoinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" - zpagesfeatures "k8s.io/component-base/zpages/features" "k8s.io/component-helpers/apimachinery/lease" "k8s.io/klog/v2" "k8s.io/utils/clock" @@ -141,6 +139,9 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele } kubernetesservice.KubeAPIServerEmitEventFn = s.GenericAPIServer.Eventf + s.GenericAPIServer.RegisterDestroyFunc(func() { + kubernetesservice.KubeAPIServerEmitEventFn = nil + }) client, err := kubernetes.NewForConfig(s.GenericAPIServer.LoopbackClientConfig) if err != nil { @@ -158,12 +159,6 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele return nil, fmt.Errorf("failed to get listener address: %w", err) } - if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentFlagz) { - if c.Generic.Flagz != nil { - flagz.Install(s.GenericAPIServer.Handler.NonGoRestfulMux, name, c.Generic.Flagz) - } - } - if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.CoordinatedLeaderElection) { leaseInformer := s.VersionedInformers.Coordination().V1().Leases() lcInformer := s.VersionedInformers.Coordination().V1beta1().LeaseCandidates() @@ -228,23 +223,11 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele return err }) - // Run peer-discovery sync loop - s.GenericAPIServer.AddPostStartHookOrDie("peer-discovery-cache-sync", func(context genericapiserver.PostStartHookContext) error { + // Run peer-discovery workers + s.GenericAPIServer.AddPostStartHookOrDie("peer-discovery-workers", func(context genericapiserver.PostStartHookContext) error { go c.Extra.PeerProxy.RunPeerDiscoveryCacheSync(context, 1) - return nil - }) - - // RunGVDeletionWorkers processes GVs from deleted CRDs/APIServices. If a GV is no longer in use, - // it is marked for removal from peer-discovery (with a deletion timestamp), triggering a grace period before cleanup. - s.GenericAPIServer.AddPostStartHookOrDie("gv-deletion-workers", func(context genericapiserver.PostStartHookContext) error { - go c.Extra.PeerProxy.RunGVDeletionWorkers(context, 1) - return nil - }) - - // RunExcludedGVsReaper removes GVs from the peer-discovery exclusion list after their grace period expires. - // This ensures we don't include stale CRDs/aggregated APIs from peer discovery in the aggregated discovery. - s.GenericAPIServer.AddPostStartHookOrDie("excluded-groups-reaper", func(context genericapiserver.PostStartHookContext) error { - go c.Extra.PeerProxy.RunExcludedGVsReaper(context.Done()) + go c.Extra.PeerProxy.RunPeerDiscoveryActiveGVTracker(context) + go c.Extra.PeerProxy.RunPeerDiscoveryRefilter(context) return nil }) } diff --git a/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go b/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go index 0dfe2cbca13c9..f1c5040961073 100644 --- a/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go +++ b/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go @@ -27,7 +27,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authentication/request/headerrequest" "k8s.io/apiserver/pkg/features" @@ -38,6 +37,7 @@ import ( clienttesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/utils/dump" ) var ( diff --git a/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller.go b/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller.go index 0cc5d71e098f8..840982ce0659c 100644 --- a/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller.go +++ b/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller.go @@ -99,63 +99,64 @@ type Controller struct { // Start will not return until the default ServiceCIDR exists or stopCh is closed. func (c *Controller) Start(ctx context.Context) { - defer utilruntime.HandleCrash() - stopCh := ctx.Done() + defer utilruntime.HandleCrashWithContext(ctx) + logger := klog.FromContext(ctx) c.eventBroadcaster = record.NewBroadcaster(record.WithContext(ctx)) c.eventRecorder = c.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: controllerName}) c.eventBroadcaster.StartStructuredLogging(0) c.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: c.client.CoreV1().Events("")}) - klog.Infof("Starting %s", controllerName) + logger.Info("Starting", "controller", controllerName) go func() { <-ctx.Done() - klog.Infof("Shutting down %s", controllerName) + logger.Info("Shutting down", "controller", controllerName) c.eventBroadcaster.Shutdown() }() - go c.serviceCIDRInformer.Run(stopCh) - if !cache.WaitForNamedCacheSync(controllerName, stopCh, c.serviceCIDRsSynced) { + go c.serviceCIDRInformer.RunWithContext(ctx) + if !cache.WaitForNamedCacheSyncWithContext(ctx, c.serviceCIDRsSynced) { return } // wait until first successfully sync // this blocks apiserver startup so poll with a short interval err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (bool, error) { - syncErr := c.sync() + syncErr := c.sync(ctx) return syncErr == nil, nil }) if err != nil { - klog.Infof("error initializing the default ServiceCIDR: %v", err) - + logger.Error(err, "error initializing the default ServiceCIDR") } // run the sync loop in the background with the defined interval - go wait.Until(func() { - err := c.sync() + go wait.UntilWithContext(ctx, func(ctx context.Context) { + err := c.sync(ctx) if err != nil { - klog.Infof("error trying to sync the default ServiceCIDR: %v", err) + logger.Error(err, "error trying to sync the default ServiceCIDR") } - }, c.interval, stopCh) + }, c.interval) } -func (c *Controller) sync() error { +func (c *Controller) sync(ctx context.Context) error { + logger := klog.FromContext(ctx) + // check if the default ServiceCIDR already exist serviceCIDR, err := c.serviceCIDRLister.Get(DefaultServiceCIDRName) // if exists if err == nil { // single to dual stack upgrade if len(c.cidrs) == 2 && len(serviceCIDR.Spec.CIDRs) == 1 && c.cidrs[0] == serviceCIDR.Spec.CIDRs[0] { - klog.Infof("Updating default ServiceCIDR from single-stack (%v) to dual-stack (%v)", serviceCIDR.Spec.CIDRs, c.cidrs) + logger.Info("Updating default ServiceCIDR from single-stack to dual-stack", "from", serviceCIDR.Spec.CIDRs, "to", c.cidrs) serviceCIDRcopy := serviceCIDR.DeepCopy() serviceCIDRcopy.Spec.CIDRs = c.cidrs - _, err := c.client.NetworkingV1().ServiceCIDRs().Update(context.Background(), serviceCIDRcopy, metav1.UpdateOptions{}) + _, err := c.client.NetworkingV1().ServiceCIDRs().Update(ctx, serviceCIDRcopy, metav1.UpdateOptions{}) if err != nil { - klog.Infof("The default ServiceCIDR can not be updated from %s to dual stack %v : %v", c.cidrs[0], c.cidrs, err) + logger.Error(err, "The default ServiceCIDR can not be updated to dual stack", "from", c.cidrs[0], "to", c.cidrs) c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, "KubernetesDefaultServiceCIDRError", "The default ServiceCIDR can not be upgraded from %s to dual stack %v : %v", c.cidrs[0], c.cidrs, err) } } else { - c.syncStatus(serviceCIDR) + c.syncStatus(ctx, serviceCIDR) } return nil } @@ -166,7 +167,7 @@ func (c *Controller) sync() error { } // default ServiceCIDR does not exist - klog.Infof("Creating default ServiceCIDR with CIDRs: %v", c.cidrs) + logger.Info("Creating default ServiceCIDR", "CIDRs", c.cidrs) serviceCIDR = &networkingapiv1.ServiceCIDR{ ObjectMeta: metav1.ObjectMeta{ Name: DefaultServiceCIDRName, @@ -175,20 +176,22 @@ func (c *Controller) sync() error { CIDRs: c.cidrs, }, } - serviceCIDR, err = c.client.NetworkingV1().ServiceCIDRs().Create(context.Background(), serviceCIDR, metav1.CreateOptions{}) + serviceCIDR, err = c.client.NetworkingV1().ServiceCIDRs().Create(ctx, serviceCIDR, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, "KubernetesDefaultServiceCIDRError", "The default ServiceCIDR can not be created") return err } - c.syncStatus(serviceCIDR) + c.syncStatus(ctx, serviceCIDR) return nil } -func (c *Controller) syncStatus(serviceCIDR *networkingapiv1.ServiceCIDR) { +func (c *Controller) syncStatus(ctx context.Context, serviceCIDR *networkingapiv1.ServiceCIDR) { + logger := klog.FromContext(ctx) + // don't sync the status of the ServiceCIDR if is being deleted, // deletion must be handled by the controller-manager if !serviceCIDR.GetDeletionTimestamp().IsZero() { - klog.V(6).Infof("ServiceCIDR %s is being deleted, skipping status sync", serviceCIDR.Name) + logger.V(6).Info("ServiceCIDR is being deleted, skipping status sync", "serviceCIDR", klog.KObj(serviceCIDR)) return } @@ -200,7 +203,7 @@ func (c *Controller) syncStatus(serviceCIDR *networkingapiv1.ServiceCIDR) { // Handle inconsistent configuration if !sameConfig { if !c.reportedMismatchedCIDRs { - klog.Infof("Inconsistent ServiceCIDR status for %s, controller configuration: %v, ServiceCIDR configuration: %v. Configure the flags to match current ServiceCIDR or manually delete it.", serviceCIDR.Name, c.cidrs, serviceCIDR.Spec.CIDRs) + logger.Info("Inconsistent ServiceCIDR status", "serviceCIDR", klog.KObj(serviceCIDR), "controllerCIDRs", c.cidrs, "serviceCIDRs", serviceCIDR.Spec.CIDRs) c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, "KubernetesDefaultServiceCIDRInconsistent", "The default ServiceCIDR %v does not match the controller flag configurations %s", serviceCIDR.Spec.CIDRs, c.cidrs) c.reportedMismatchedCIDRs = true } @@ -217,18 +220,18 @@ func (c *Controller) syncStatus(serviceCIDR *networkingapiv1.ServiceCIDR) { // is the case, then it will require an intervention by the cluster administrator. case currentReadyCondition != nil && currentReadyCondition.Status == metav1.ConditionFalse: if !c.reportedNotReadyCondition { - klog.InfoS("Default ServiceCIDR condition Ready is False, but controller configuration matches. Please validate your cluster's network configuration.", "serviceCIDR", klog.KObj(serviceCIDR), "status", currentReadyCondition.Status, "reason", currentReadyCondition.Reason, "message", currentReadyCondition.Message) - c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, currentReadyCondition.Reason, "Configuration matches, but "+currentReadyCondition.Message) + logger.Info("Default ServiceCIDR condition Ready is False, but controller configuration matches. Please validate your cluster's network configuration.", "serviceCIDR", klog.KObj(serviceCIDR), "status", currentReadyCondition.Status, "reason", currentReadyCondition.Reason, "message", currentReadyCondition.Message) + c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, currentReadyCondition.Reason, "Configuration matches, but %s", currentReadyCondition.Message) c.reportedNotReadyCondition = true } // Current Ready=True and config matches, nothing to do. case currentReadyCondition != nil && currentReadyCondition.Status == metav1.ConditionTrue: - klog.V(6).Infof("ServiceCIDR %s is Ready and configuration matches. No status update needed.", serviceCIDR.Name) + logger.V(6).Info("ServiceCIDR is Ready and configuration matches. No status update needed.", "serviceCIDR", klog.KObj(serviceCIDR)) // No condition set and ServiceCIDR matches this apiserver configuration, set condition to True case currentReadyCondition == nil || currentReadyCondition.Status == metav1.ConditionUnknown: - klog.Infof("Setting default ServiceCIDR condition Ready to True") + logger.Info("Setting default ServiceCIDR condition Ready to True") svcApplyStatus := networkingapiv1apply.ServiceCIDRStatus().WithConditions( metav1apply.Condition(). WithType(networkingapiv1.ServiceCIDRConditionReady). @@ -236,8 +239,8 @@ func (c *Controller) syncStatus(serviceCIDR *networkingapiv1.ServiceCIDR) { WithMessage("Kubernetes default Service CIDR is ready"). WithLastTransitionTime(metav1.Now())) svcApply := networkingapiv1apply.ServiceCIDR(DefaultServiceCIDRName).WithStatus(svcApplyStatus) - if _, errApply := c.client.NetworkingV1().ServiceCIDRs().ApplyStatus(context.Background(), svcApply, metav1.ApplyOptions{FieldManager: controllerName, Force: true}); errApply != nil { - klog.Infof("error updating default ServiceCIDR status: %v", errApply) + if _, errApply := c.client.NetworkingV1().ServiceCIDRs().ApplyStatus(ctx, svcApply, metav1.ApplyOptions{FieldManager: controllerName, Force: true}); errApply != nil { + logger.Error(errApply, "error updating default ServiceCIDR status") c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, "KubernetesDefaultServiceCIDRError", "The default ServiceCIDR Status can not be set to Ready=True") } default: diff --git a/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller_test.go b/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller_test.go index 73a369831d110..29ad8d7263967 100644 --- a/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller_test.go +++ b/pkg/controlplane/controller/defaultservicecidr/default_servicecidr_controller_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/client-go/kubernetes/fake" k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/record" + "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/ptr" ) @@ -156,8 +157,12 @@ func TestControllerSync(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) client, controller := newController(t, []string{defaultIPv4CIDR, defaultIPv6CIDR}, tc.cidrs...) - controller.sync() + err := controller.sync(ctx) + if err != nil { + t.Errorf("unexpected error: %v", err) + } expectAction(t, client.Actions(), tc.actions) }) } @@ -330,11 +335,13 @@ func TestControllerSyncConversions(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + // Initialize controller and client with the existing ServiceCIDR client, controller := newController(t, tc.controllerCIDRs, tc.existingCIDR) // Call the syncStatus method directly - err := controller.sync() + err := controller.sync(ctx) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/controlplane/controller/kubernetesservice/controller.go b/pkg/controlplane/controller/kubernetesservice/controller.go index e5d8c04206b72..06eefc045e92b 100644 --- a/pkg/controlplane/controller/kubernetesservice/controller.go +++ b/pkg/controlplane/controller/kubernetesservice/controller.go @@ -155,7 +155,9 @@ func (c *Controller) Run(ch <-chan struct{}) { return } - KubeAPIServerEmitEventFn(corev1.EventTypeWarning, "KubeAPIReadyz", "readyz=true") + if KubeAPIServerEmitEventFn != nil { + KubeAPIServerEmitEventFn(corev1.EventTypeWarning, "KubeAPIReadyz", "readyz=true") + } wait.NonSlidingUntil(func() { // Service definition is not reconciled after first diff --git a/pkg/controlplane/controller/leaderelection/leaderelection_controller.go b/pkg/controlplane/controller/leaderelection/leaderelection_controller.go index 3b44c30e452f9..1a4ab5a163c18 100644 --- a/pkg/controlplane/controller/leaderelection/leaderelection_controller.go +++ b/pkg/controlplane/controller/leaderelection/leaderelection_controller.go @@ -75,7 +75,7 @@ type Controller struct { } func (c *Controller) Run(ctx context.Context, workers int) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer c.queue.ShutDown() defer func() { err := c.leaseInformer.Informer().RemoveEventHandler(c.leaseRegistration) @@ -95,7 +95,7 @@ func (c *Controller) Run(ctx context.Context, workers int) { // This controller is leader elected and may start after informers have already started. List on startup. lcs, err := c.leaseCandidateInformer.Lister().List(labels.Everything()) if err != nil { - utilruntime.HandleError(err) + utilruntime.HandleErrorWithContext(ctx, err, "Failed to list lease candidates") return } for _, lc := range lcs { @@ -166,7 +166,9 @@ func (c *Controller) processNextElectionItem(ctx context.Context) bool { } intervalForRequeue, err := c.reconcileElectionStep(ctx, key) - utilruntime.HandleError(err) + if err != nil { // HandleErrorWithContext logs regardless of error value, filter out nil err. + utilruntime.HandleErrorWithContext(ctx, err, "Failed to reconcile election step", "key", key) + } if intervalForRequeue != noRequeue { defer c.queue.AddAfter(key, intervalForRequeue) } diff --git a/pkg/controlplane/controller/leaderelection/leasecandidategc_controller.go b/pkg/controlplane/controller/leaderelection/leasecandidategc_controller.go index bcc4f89fdca04..f3d5449e0618e 100644 --- a/pkg/controlplane/controller/leaderelection/leasecandidategc_controller.go +++ b/pkg/controlplane/controller/leaderelection/leasecandidategc_controller.go @@ -18,7 +18,6 @@ package leaderelection import ( "context" - "fmt" "time" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -61,13 +60,13 @@ func NewLeaseCandidateGC(clientset kubernetes.Interface, gcCheckPeriod time.Dura // Run starts one worker. func (c *LeaseCandidateGCController) Run(ctx context.Context) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer klog.Infof("Shutting down apiserver leasecandidate garbage collector") klog.Infof("Starting apiserver leasecandidate garbage collector") - if !cache.WaitForCacheSync(ctx.Done(), c.leaseCandidatesSynced) { - utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) + if !cache.WaitForNamedCacheSyncWithContext(ctx, c.leaseCandidatesSynced) { + utilruntime.HandleErrorWithContext(ctx, nil, "Timed out waiting for caches to sync") return } diff --git a/pkg/controlplane/controller/leaderelection/run_with_leaderelection.go b/pkg/controlplane/controller/leaderelection/run_with_leaderelection.go index 2593bc7642395..cca4a547a3d31 100644 --- a/pkg/controlplane/controller/leaderelection/run_with_leaderelection.go +++ b/pkg/controlplane/controller/leaderelection/run_with_leaderelection.go @@ -50,7 +50,7 @@ func RunWithLeaderElection(ctx context.Context, config *rest.Config, newRunnerFn } identity := hostname + "_" + string(uuid.NewUUID()) - wait.Until(func() { + wait.UntilWithContext(ctx, func(ctx context.Context) { callbacks := leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { var err error @@ -94,5 +94,5 @@ func RunWithLeaderElection(ctx context.Context, config *rest.Config, newRunnerFn return } le.Run(ctx) - }, timers.RetryPeriod, ctx.Done()) + }, timers.RetryPeriod) } diff --git a/pkg/controlplane/instance.go b/pkg/controlplane/instance.go index 49c3da79dcf7a..62b38b0370d07 100644 --- a/pkg/controlplane/instance.go +++ b/pkg/controlplane/instance.go @@ -54,7 +54,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingapiv1 "k8s.io/api/scheduling/v1" - schedulingapiv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingapiv1alpha2 "k8s.io/api/scheduling/v1alpha2" storageapiv1 "k8s.io/api/storage/v1" storageapiv1alpha1 "k8s.io/api/storage/v1alpha1" storageapiv1beta1 "k8s.io/api/storage/v1beta1" @@ -432,7 +432,10 @@ func (c CompletedConfig) StorageProviders(client *kubernetes.Clientset) ([]contr appsrest.StorageProvider{}, admissionregistrationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, DiscoveryClient: client.Discovery()}, eventsrest.RESTStorageProvider{TTL: c.ControlPlane.EventTTL}, - resourcerest.RESTStorageProvider{NamespaceClient: client.CoreV1().Namespaces()}, + resourcerest.RESTStorageProvider{ + NamespaceClient: client.CoreV1().Namespaces(), + Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, + }, } if AdditionalStorageProvidersForTests != nil { @@ -443,69 +446,88 @@ func (c CompletedConfig) StorageProviders(client *kubernetes.Clientset) ([]contr } var ( - // stableAPIGroupVersionsEnabledByDefault is a list of our stable versions. - stableAPIGroupVersionsEnabledByDefault = []schema.GroupVersion{ + // genericStableAPIGroupVersionsEnabledByDefault is a list of our stable versions for API groups provided by GenericStorageProviders. + genericStableAPIGroupVersionsEnabledByDefault = []schema.GroupVersion{ admissionregistrationv1.SchemeGroupVersion, apiv1.SchemeGroupVersion, - appsv1.SchemeGroupVersion, authenticationv1.SchemeGroupVersion, authorizationapiv1.SchemeGroupVersion, + certificatesapiv1.SchemeGroupVersion, + coordinationapiv1.SchemeGroupVersion, + eventsv1.SchemeGroupVersion, + rbacv1.SchemeGroupVersion, + flowcontrolv1.SchemeGroupVersion, + } + // stableAPIGroupVersionsEnabledByDefault is a list of our stable versions for additional API groups only provided in kube-apiserver. + stableAPIGroupVersionsEnabledByDefault = []schema.GroupVersion{ + appsv1.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion, autoscalingapiv2.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, - certificatesapiv1.SchemeGroupVersion, - coordinationapiv1.SchemeGroupVersion, discoveryv1.SchemeGroupVersion, - eventsv1.SchemeGroupVersion, networkingapiv1.SchemeGroupVersion, nodev1.SchemeGroupVersion, policyapiv1.SchemeGroupVersion, - rbacv1.SchemeGroupVersion, resourcev1.SchemeGroupVersion, storageapiv1.SchemeGroupVersion, schedulingapiv1.SchemeGroupVersion, - flowcontrolv1.SchemeGroupVersion, } - // betaAPIGroupVersionsDisabledByDefault is for all future beta groupVersions. - betaAPIGroupVersionsDisabledByDefault = []schema.GroupVersion{ + // genericBetaAPIGroupVersionsDisabledByDefault is for all future beta groupVersions for API groups provided by GenericStorageProviders. + genericBetaAPIGroupVersionsDisabledByDefault = []schema.GroupVersion{ admissionregistrationv1beta1.SchemeGroupVersion, authenticationv1beta1.SchemeGroupVersion, certificatesv1beta1.SchemeGroupVersion, coordinationv1beta1.SchemeGroupVersion, - storageapiv1beta1.SchemeGroupVersion, flowcontrolv1beta1.SchemeGroupVersion, flowcontrolv1beta2.SchemeGroupVersion, flowcontrolv1beta3.SchemeGroupVersion, + svmv1beta1.SchemeGroupVersion, + } + // betaAPIGroupVersionsDisabledByDefault is for all future beta groupVersions for additional API groups only provided in kube-apiserver. + betaAPIGroupVersionsDisabledByDefault = []schema.GroupVersion{ + storageapiv1beta1.SchemeGroupVersion, networkingapiv1beta1.SchemeGroupVersion, resourcev1beta1.SchemeGroupVersion, resourcev1beta2.SchemeGroupVersion, - svmv1beta1.SchemeGroupVersion, } - // alphaAPIGroupVersionsDisabledByDefault holds the alpha APIs we have. They are always disabled by default. - alphaAPIGroupVersionsDisabledByDefault = []schema.GroupVersion{ + // genericAlphaAPIGroupVersionsDisabledByDefault holds the alpha APIs we have for API groups provided by GenericStorageProviders. They are always disabled by default. + genericAlphaAPIGroupVersionsDisabledByDefault = []schema.GroupVersion{ admissionregistrationv1alpha1.SchemeGroupVersion, apiserverinternalv1alpha1.SchemeGroupVersion, authenticationv1alpha1.SchemeGroupVersion, apiserverinternalv1alpha1.SchemeGroupVersion, coordinationv1alpha2.SchemeGroupVersion, - resourcev1alpha3.SchemeGroupVersion, certificatesv1alpha1.SchemeGroupVersion, - schedulingapiv1alpha1.SchemeGroupVersion, + } + // alphaAPIGroupVersionsDisabledByDefault holds the alpha APIs we have for additional API groups only provided in kube-apiserver. They are always disabled by default. + alphaAPIGroupVersionsDisabledByDefault = []schema.GroupVersion{ + resourcev1alpha3.SchemeGroupVersion, + schedulingapiv1alpha2.SchemeGroupVersion, storageapiv1alpha1.SchemeGroupVersion, } ) -// DefaultAPIResourceConfigSource returns default configuration for an APIResource. -func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { +// DefaultGenericAPIResourceConfigSource returns default configuration for resources served by GenericStorageProviders. +func DefaultGenericAPIResourceConfigSource() *serverstorage.ResourceConfig { ret := serverstorage.NewResourceConfig() // NOTE: GroupVersions listed here will be enabled by default. Don't put alpha or beta versions in the list. - ret.EnableVersions(stableAPIGroupVersionsEnabledByDefault...) + ret.EnableVersions(genericStableAPIGroupVersionsEnabledByDefault...) + // disable alpha and beta versions explicitly so we have a full list of what's possible to serve + ret.DisableVersions(genericBetaAPIGroupVersionsDisabledByDefault...) + ret.DisableVersions(genericAlphaAPIGroupVersionsDisabledByDefault...) + return ret +} +// DefaultAPIResourceConfigSource returns default configuration for resources served by kube-apiserver. +func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { + // start with generic configuration + ret := DefaultGenericAPIResourceConfigSource() + // NOTE: GroupVersions listed here will be enabled by default. Don't put alpha or beta versions in the list. + ret.EnableVersions(stableAPIGroupVersionsEnabledByDefault...) // disable alpha and beta versions explicitly so we have a full list of what's possible to serve ret.DisableVersions(betaAPIGroupVersionsDisabledByDefault...) ret.DisableVersions(alphaAPIGroupVersionsDisabledByDefault...) - return ret } diff --git a/pkg/controlplane/instance_test.go b/pkg/controlplane/instance_test.go index 4d941e8e5f316..120642eeae24a 100644 --- a/pkg/controlplane/instance_test.go +++ b/pkg/controlplane/instance_test.go @@ -30,11 +30,9 @@ import ( "testing" "github.com/stretchr/testify/assert" - autoscalingrest "k8s.io/kubernetes/pkg/registry/autoscaling/rest" - resourcerest "k8s.io/kubernetes/pkg/registry/resource/rest" - autoscalingapiv2beta1 "k8s.io/api/autoscaling/v2beta1" - autoscalingapiv2beta2 "k8s.io/api/autoscaling/v2beta2" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + batchapiv1beta1 "k8s.io/api/batch/v1beta1" certificatesapiv1beta1 "k8s.io/api/certificates/v1beta1" discoveryv1beta1 "k8s.io/api/discovery/v1beta1" @@ -51,6 +49,7 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizerfactory" openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" + "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/server/resourceconfig" @@ -69,11 +68,11 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver" "k8s.io/kubernetes/pkg/controlplane/reconcilers" - "k8s.io/kubernetes/pkg/controlplane/storageversionhashdata" generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi" "k8s.io/kubernetes/pkg/kubeapiserver" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" appsrest "k8s.io/kubernetes/pkg/registry/apps/rest" + autoscalingrest "k8s.io/kubernetes/pkg/registry/autoscaling/rest" batchrest "k8s.io/kubernetes/pkg/registry/batch/rest" certificatesrest "k8s.io/kubernetes/pkg/registry/certificates/rest" corerest "k8s.io/kubernetes/pkg/registry/core/rest" @@ -82,6 +81,7 @@ import ( noderest "k8s.io/kubernetes/pkg/registry/node/rest" policyrest "k8s.io/kubernetes/pkg/registry/policy/rest" "k8s.io/kubernetes/pkg/registry/registrytest" + resourcerest "k8s.io/kubernetes/pkg/registry/resource/rest" schedulingrest "k8s.io/kubernetes/pkg/registry/scheduling/rest" storagerest "k8s.io/kubernetes/pkg/registry/storage/rest" ) @@ -366,7 +366,7 @@ func TestStorageVersionHashes(t *testing.T) { for _, r := range g.APIResources { apiResources.Insert(g.GroupVersion + "/" + r.Name) if strings.Contains(r.Name, "/") || - storageversionhashdata.NoStorageVersionHash.Has(g.GroupVersion+"/"+r.Name) { + noStorageVersionHash.Has(g.GroupVersion+"/"+r.Name) { if r.StorageVersionHash != "" { t.Errorf("expect resource %s/%s to have empty storageVersionHash, got hash %q", g.GroupVersion, r.Name, r.StorageVersionHash) } @@ -378,15 +378,15 @@ func TestStorageVersionHashes(t *testing.T) { } // Uncomment the following line if you want to update storageversionhash/data.go // fmt.Printf("\"%s/%s\": \"%s\",\n", g.GroupVersion, r.Name, r.StorageVersionHash) - expected := storageversionhashdata.GVRToStorageVersionHash[g.GroupVersion+"/"+r.Name] + expected := gvrToStorageVersionHash[g.GroupVersion+"/"+r.Name] if r.StorageVersionHash != expected { t.Errorf("expect the storageVersionHash of %s/%s to be %q, got %q", g.GroupVersion, r.Name, expected, r.StorageVersionHash) } count++ } } - if count != len(storageversionhashdata.GVRToStorageVersionHash) { - knownResources := sets.StringKeySet(storageversionhashdata.GVRToStorageVersionHash) + if count != len(gvrToStorageVersionHash) { + knownResources := sets.StringKeySet(gvrToStorageVersionHash) t.Errorf("please remove the redundant entries from GVRToStorageVersionHash: %v", knownResources.Difference(apiResources).List()) } } @@ -467,15 +467,13 @@ func TestNewBetaResourcesEnabledByDefault(t *testing.T) { // legacyEnabledBetaResources is nearly a duplication from elsewhere. This is intentional. These types already have // GA equivalents available and should therefore never have a beta enabled by default again. legacyEnabledBetaResources := map[schema.GroupVersionResource]bool{ - autoscalingapiv2beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): true, - autoscalingapiv2beta2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): true, - batchapiv1beta1.SchemeGroupVersion.WithResource("cronjobs"): true, - discoveryv1beta1.SchemeGroupVersion.WithResource("endpointslices"): true, - eventsv1beta1.SchemeGroupVersion.WithResource("events"): true, - nodev1beta1.SchemeGroupVersion.WithResource("runtimeclasses"): true, - policyapiv1beta1.SchemeGroupVersion.WithResource("poddisruptionbudgets"): true, - policyapiv1beta1.SchemeGroupVersion.WithResource("podsecuritypolicies"): true, - storageapiv1beta1.SchemeGroupVersion.WithResource("csinodes"): true, + batchapiv1beta1.SchemeGroupVersion.WithResource("cronjobs"): true, + discoveryv1beta1.SchemeGroupVersion.WithResource("endpointslices"): true, + eventsv1beta1.SchemeGroupVersion.WithResource("events"): true, + nodev1beta1.SchemeGroupVersion.WithResource("runtimeclasses"): true, + policyapiv1beta1.SchemeGroupVersion.WithResource("poddisruptionbudgets"): true, + policyapiv1beta1.SchemeGroupVersion.WithResource("podsecuritypolicies"): true, + storageapiv1beta1.SchemeGroupVersion.WithResource("csinodes"): true, } config := DefaultAPIResourceConfigSource() @@ -516,6 +514,23 @@ func TestGenericStorageProviders(t *testing.T) { t.Fatal(err) } + genericStorageGroups := sets.New[string]() + for _, provider := range generic { + genericStorageGroups.Insert(provider.GroupName()) + } + + genericConfigGroups := sets.New[string]() + for gv := range DefaultGenericAPIResourceConfigSource().GroupVersionConfigs { + genericConfigGroups.Insert(gv.Group) + } + + if extra := genericStorageGroups.Difference(genericConfigGroups); len(extra) > 0 { + t.Fatalf("GenericStorageProviders has providers for groups in DefaultGenericAPIResourceConfigSource: %v", sets.List(extra)) + } + if extra := genericConfigGroups.Difference(genericStorageGroups); len(extra) > 0 { + t.Fatalf("DefaultGenericAPIResourceConfigSource has config for groups not in GenericStorageProviders: %v", sets.List(extra)) + } + g := 0 // generic index for k := range kube { kt := reflect.TypeOf(kube[k]) @@ -556,3 +571,168 @@ func TestGenericStorageProviders(t *testing.T) { t.Errorf("Unexpected, generic APIs found: %#v", generic[g:]) } } + +// TestGetResetFieldsHasAllVersions verifies that every storage implementing +// ResetFieldsStrategy or ResetFieldsFilterStrategy returns entries for all +// served API versions of that resource. +func TestGetResetFieldsHasAllVersions(t *testing.T) { + _, config, _ := setUp(t) + + client, err := kubernetes.NewForConfig(config.ControlPlane.Generic.LoopbackClientConfig) + if err != nil { + t.Fatal(err) + } + + providers, err := config.Complete().StorageProviders(client) + if err != nil { + t.Fatal(err) + } + + // Enable all versions (including alpha and beta) to test comprehensively. + allVersionsConfig := DefaultAPIResourceConfigSource() + allVersionsConfig.EnableVersions(betaAPIGroupVersionsDisabledByDefault...) + allVersionsConfig.EnableVersions(alphaAPIGroupVersionsDisabledByDefault...) + + for _, provider := range providers { + groupName := provider.GroupName() + apiGroupInfo, err := provider.NewRESTStorage(allVersionsConfig, config.ControlPlane.Generic.RESTOptionsGetter) + if err != nil { + t.Errorf("error creating REST storage for %s: %v", groupName, err) + continue + } + + allServedVersionsByResource := map[string][]string{} + for v, resourcesInVersion := range apiGroupInfo.VersionedResourcesStorageMap { + for resource := range resourcesInVersion { + var apiVersion string + if len(groupName) == 0 { + apiVersion = v + } else { + apiVersion = groupName + "/" + v + } + allServedVersionsByResource[resource] = append(allServedVersionsByResource[resource], apiVersion) + } + } + + // Check every storage that implements GetResetFields or GetResetFieldsFilter. + for v, resourcesInVersion := range apiGroupInfo.VersionedResourcesStorageMap { + for resource, storage := range resourcesInVersion { + servedVersions := allServedVersionsByResource[resource] + if len(servedVersions) <= 1 { + continue // only one version, nothing to check + } + if rfs, ok := storage.(rest.ResetFieldsStrategy); ok { + resetFields := rfs.GetResetFields() + if resetFields == nil { + continue // no reset fields, nothing to check + } + for _, sv := range servedVersions { + if _, exists := resetFields[fieldpath.APIVersion(sv)]; !exists { + t.Errorf("%s: GetResetFields() is missing version %q, has %v; all served versions: %v", + gvr(groupName, v, resource), sv, resetFields, servedVersions) + } + } + } + + if rfs, ok := storage.(rest.ResetFieldsFilterStrategy); ok { + resetFieldsFilter := rfs.GetResetFieldsFilter() + if resetFieldsFilter == nil { + continue + } + for _, sv := range servedVersions { + if _, exists := resetFieldsFilter[fieldpath.APIVersion(sv)]; !exists { + t.Errorf("%s: GetResetFieldsFilter() is missing version %q, has %v; all served versions: %v", + gvr(groupName, v, resource), sv, resetFieldsFilter, servedVersions) + } + } + } + } + } + } +} + +func gvr(g string, v string, r string) string { + var gvr string + if len(g) == 0 { + gvr = fmt.Sprintf("%s/%s", v, r) + } else { + gvr = fmt.Sprintf("%s/%s/%s", g, v, r) + } + return gvr +} + +// noStorageVersionHash lists resources that legitimately with empty storage +// version hash. +var noStorageVersionHash = sets.NewString( + "v1/bindings", + "v1/componentstatuses", + "authentication.k8s.io/v1/selfsubjectreviews", + "authentication.k8s.io/v1/tokenreviews", + "authorization.k8s.io/v1/localsubjectaccessreviews", + "authorization.k8s.io/v1/selfsubjectaccessreviews", + "authorization.k8s.io/v1/selfsubjectrulesreviews", + "authorization.k8s.io/v1/subjectaccessreviews", +) + +// gvrToStorageVersionHash shouldn't change unless we intentionally change the +// storage version of a resource. +var gvrToStorageVersionHash = map[string]string{ + "v1/configmaps": "qFsyl6wFWjQ=", + "v1/endpoints": "fWeeMqaN/OA=", + "v1/events": "r2yiGXH7wu8=", + "v1/limitranges": "EBKMFVe6cwo=", + "v1/namespaces": "Q3oi5N2YM8M=", + "v1/nodes": "XwShjMxG9Fs=", + "v1/persistentvolumeclaims": "QWTyNDq0dC4=", + "v1/persistentvolumes": "HN/zwEC+JgM=", + "v1/pods": "xPOwRZ+Yhw8=", + "v1/podtemplates": "LIXB2x4IFpk=", + "v1/replicationcontrollers": "Jond2If31h0=", + "v1/resourcequotas": "8uhSgffRX6w=", + "v1/secrets": "S6u1pOWzb84=", + "v1/serviceaccounts": "pbx9ZvyFpBE=", + "v1/services": "0/CO1lhkEBI=", + "autoscaling/v1/horizontalpodautoscalers": "qwQve8ut294=", + "autoscaling/v2/horizontalpodautoscalers": "qwQve8ut294=", + "batch/v1/jobs": "mudhfqk/qZY=", + "batch/v1/cronjobs": "sd5LIXh4Fjs=", + "certificates.k8s.io/v1/certificatesigningrequests": "95fRKMXA+00=", + "coordination.k8s.io/v1/leases": "gqkMMb/YqFM=", + "discovery.k8s.io/v1/endpointslices": "qgS0xkrxYAI=", + "networking.k8s.io/v1/networkpolicies": "YpfwF18m1G8=", + "networking.k8s.io/v1/ingresses": "39NQlfNR+bo=", + "networking.k8s.io/v1/ingressclasses": "l/iqIbDgFyQ=", + "networking.k8s.io/v1/ipaddresses": "3f/plJFChNE=", + "networking.k8s.io/v1/servicecidrs": "gNLvruQhW2g=", + "node.k8s.io/v1/runtimeclasses": "WQTu1GL3T2Q=", + "policy/v1/poddisruptionbudgets": "EVWiDmWqyJw=", + "rbac.authorization.k8s.io/v1/clusterrolebindings": "48tpQ8gZHFc=", + "rbac.authorization.k8s.io/v1/clusterroles": "bYE5ZWDrJ44=", + "rbac.authorization.k8s.io/v1/rolebindings": "eGsCzGH6b1g=", + "rbac.authorization.k8s.io/v1/roles": "7FuwZcIIItM=", + "resource.k8s.io/v1/deviceclasses": "Yk2PTc1Ybxk=", + "resource.k8s.io/v1/resourceclaims": "wgAZaHcZxUg=", + "resource.k8s.io/v1/resourceclaimtemplates": "TuzjC49aUfM=", + "resource.k8s.io/v1/resourceslices": "KsC072WgaEY=", + "scheduling.k8s.io/v1/priorityclasses": "1QwjyaZjj3Y=", + "storage.k8s.io/v1/csidrivers": "hL6j/rwBV5w=", + "storage.k8s.io/v1/csinodes": "Pe62DkZtjuo=", + "storage.k8s.io/v1/storageclasses": "K+m6uJwbjGY=", + "storage.k8s.io/v1/csistoragecapacities": "xeVl+2Ly1kE=", + "storage.k8s.io/v1/volumeattachments": "tJx/ezt6UDU=", + "storage.k8s.io/v1/volumeattributesclasses": "tIjydgKBC5w=", + "apps/v1/controllerrevisions": "85nkx63pcBU=", + "apps/v1/daemonsets": "dd7pWHUlMKQ=", + "apps/v1/deployments": "8aSe+NMegvE=", + "apps/v1/replicasets": "P1RzHs8/mWQ=", + "apps/v1/statefulsets": "H+vl74LkKdo=", + "admissionregistration.k8s.io/v1/mutatingwebhookconfigurations": "Sqi0GUgDaX0=", + "admissionregistration.k8s.io/v1/validatingwebhookconfigurations": "B0wHjQmsGNk=", + "admissionregistration.k8s.io/v1/mutatingadmissionpolicies": "LYmCf+UMVdg=", + "admissionregistration.k8s.io/v1/mutatingadmissionpolicybindings": "90V5FRZZ3Zg=", + "admissionregistration.k8s.io/v1/validatingadmissionpolicies": "6OxvlMmQ6is=", + "admissionregistration.k8s.io/v1/validatingadmissionpolicybindings": "v9715VZqakg=", + "events.k8s.io/v1/events": "r2yiGXH7wu8=", + "flowcontrol.apiserver.k8s.io/v1/flowschemas": "GJVAJZSZBIw=", + "flowcontrol.apiserver.k8s.io/v1/prioritylevelconfigurations": "Kir5PVfvNeI=", +} diff --git a/pkg/controlplane/storageversionhashdata/OWNERS b/pkg/controlplane/storageversionhashdata/OWNERS deleted file mode 100644 index 82d3f9736f4c4..0000000000000 --- a/pkg/controlplane/storageversionhashdata/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -approvers: - - api-approvers -reviewers: - - api-reviewers diff --git a/pkg/controlplane/storageversionhashdata/data.go b/pkg/controlplane/storageversionhashdata/data.go deleted file mode 100644 index eef787a688c21..0000000000000 --- a/pkg/controlplane/storageversionhashdata/data.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2019 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 storageversionhashdata is for test only. -package storageversionhashdata - -import ( - "k8s.io/apimachinery/pkg/util/sets" -) - -// NoStorageVersionHash lists resources that legitimately with empty storage -// version hash. -var NoStorageVersionHash = sets.NewString( - "v1/bindings", - "v1/componentstatuses", - "authentication.k8s.io/v1/selfsubjectreviews", - "authentication.k8s.io/v1/tokenreviews", - "authorization.k8s.io/v1/localsubjectaccessreviews", - "authorization.k8s.io/v1/selfsubjectaccessreviews", - "authorization.k8s.io/v1/selfsubjectrulesreviews", - "authorization.k8s.io/v1/subjectaccessreviews", -) - -// GVRToStorageVersionHash shouldn't change unless we intentionally change the -// storage version of a resource. -var GVRToStorageVersionHash = map[string]string{ - "v1/configmaps": "qFsyl6wFWjQ=", - "v1/endpoints": "fWeeMqaN/OA=", - "v1/events": "r2yiGXH7wu8=", - "v1/limitranges": "EBKMFVe6cwo=", - "v1/namespaces": "Q3oi5N2YM8M=", - "v1/nodes": "XwShjMxG9Fs=", - "v1/persistentvolumeclaims": "QWTyNDq0dC4=", - "v1/persistentvolumes": "HN/zwEC+JgM=", - "v1/pods": "xPOwRZ+Yhw8=", - "v1/podtemplates": "LIXB2x4IFpk=", - "v1/replicationcontrollers": "Jond2If31h0=", - "v1/resourcequotas": "8uhSgffRX6w=", - "v1/secrets": "S6u1pOWzb84=", - "v1/serviceaccounts": "pbx9ZvyFpBE=", - "v1/services": "0/CO1lhkEBI=", - "autoscaling/v1/horizontalpodautoscalers": "qwQve8ut294=", - "autoscaling/v2/horizontalpodautoscalers": "qwQve8ut294=", - "batch/v1/jobs": "mudhfqk/qZY=", - "batch/v1/cronjobs": "sd5LIXh4Fjs=", - "certificates.k8s.io/v1/certificatesigningrequests": "95fRKMXA+00=", - "coordination.k8s.io/v1/leases": "gqkMMb/YqFM=", - "discovery.k8s.io/v1/endpointslices": "qgS0xkrxYAI=", - "networking.k8s.io/v1/networkpolicies": "YpfwF18m1G8=", - "networking.k8s.io/v1/ingresses": "39NQlfNR+bo=", - "networking.k8s.io/v1/ingressclasses": "l/iqIbDgFyQ=", - "networking.k8s.io/v1/ipaddresses": "3f/plJFChNE=", - "networking.k8s.io/v1/servicecidrs": "gNLvruQhW2g=", - "node.k8s.io/v1/runtimeclasses": "WQTu1GL3T2Q=", - "policy/v1/poddisruptionbudgets": "EVWiDmWqyJw=", - "rbac.authorization.k8s.io/v1/clusterrolebindings": "48tpQ8gZHFc=", - "rbac.authorization.k8s.io/v1/clusterroles": "bYE5ZWDrJ44=", - "rbac.authorization.k8s.io/v1/rolebindings": "eGsCzGH6b1g=", - "rbac.authorization.k8s.io/v1/roles": "7FuwZcIIItM=", - "resource.k8s.io/v1/deviceclasses": "Yk2PTc1Ybxk=", - "resource.k8s.io/v1/resourceclaims": "wgAZaHcZxUg=", - "resource.k8s.io/v1/resourceclaimtemplates": "TuzjC49aUfM=", - "resource.k8s.io/v1/resourceslices": "KsC072WgaEY=", - "scheduling.k8s.io/v1/priorityclasses": "1QwjyaZjj3Y=", - "storage.k8s.io/v1/csidrivers": "hL6j/rwBV5w=", - "storage.k8s.io/v1/csinodes": "Pe62DkZtjuo=", - "storage.k8s.io/v1/storageclasses": "K+m6uJwbjGY=", - "storage.k8s.io/v1/csistoragecapacities": "xeVl+2Ly1kE=", - "storage.k8s.io/v1/volumeattachments": "tJx/ezt6UDU=", - "storage.k8s.io/v1/volumeattributesclasses": "Bl3MtjZ/n/s=", - "apps/v1/controllerrevisions": "85nkx63pcBU=", - "apps/v1/daemonsets": "dd7pWHUlMKQ=", - "apps/v1/deployments": "8aSe+NMegvE=", - "apps/v1/replicasets": "P1RzHs8/mWQ=", - "apps/v1/statefulsets": "H+vl74LkKdo=", - "admissionregistration.k8s.io/v1/mutatingwebhookconfigurations": "Sqi0GUgDaX0=", - "admissionregistration.k8s.io/v1/validatingwebhookconfigurations": "B0wHjQmsGNk=", - "admissionregistration.k8s.io/v1/validatingadmissionpolicies": "6OxvlMmQ6is=", - "admissionregistration.k8s.io/v1/validatingadmissionpolicybindings": "v9715VZqakg=", - "events.k8s.io/v1/events": "r2yiGXH7wu8=", - "flowcontrol.apiserver.k8s.io/v1/flowschemas": "GJVAJZSZBIw=", - "flowcontrol.apiserver.k8s.io/v1/prioritylevelconfigurations": "Kir5PVfvNeI=", -} diff --git a/pkg/credentialprovider/plugin/plugin_test.go b/pkg/credentialprovider/plugin/plugin_test.go index 5f940964eb498..638e7c7caac18 100644 --- a/pkg/credentialprovider/plugin/plugin_test.go +++ b/pkg/credentialprovider/plugin/plugin_test.go @@ -571,7 +571,6 @@ func Test_ProvideWithCoordinates(t *testing.T) { } for _, testcase := range testcases { - testcase := testcase t.Run(testcase.name, func(t *testing.T) { var buf bytes.Buffer klog.SetOutput(&buf) @@ -657,7 +656,6 @@ func Test_ProvideParallel(t *testing.T) { } for _, testcase := range testcases { - testcase := testcase t.Run(testcase.name, func(t *testing.T) { t.Parallel() var wg sync.WaitGroup @@ -1292,9 +1290,7 @@ func TestGenerateServiceAccountCacheKey_Deterministic(t *testing.T) { } for _, tc := range testCases { - tc := tc for _, tc2 := range testCases { - tc2 := tc2 t.Run(fmt.Sprintf("%+v-%+v", tc, tc2), func(t *testing.T) { serviceAccountCacheKey1, err1 := generateServiceAccountCacheKey(cacheKeyParams{ namespace: tc.serviceAccountNamespace, diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index bff6faf524354..1a9de05efc3d1 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -56,6 +56,14 @@ const ( // Allow spec.terminationGracePeriodSeconds to be overridden by MaxPodGracePeriodSeconds in soft evictions. AllowOverwriteTerminationGracePeriodSeconds featuregate.Feature = "AllowOverwriteTerminationGracePeriodSeconds" + // owner: @adrianmoisey + // kep: https://kep.k8s.io/5707 + // + // When enabled, kube-proxy will program iptables/nftables/ipvs rules for Service.spec.externalIPs. + // When disabled, kube-proxy will not program rules for externalIPs, effectively disabling this + // deprecated feature. + AllowServiceExternalIPs featuregate.Feature = "AllowServiceExternalIPs" + // owner: @bswartz // // Enables usage of any object for volume data source in PVCs @@ -110,11 +118,11 @@ const ( // Allow the usage of options to fine-tune the cpumanager policies. CPUManagerPolicyOptions featuregate.Feature = "CPUManagerPolicyOptions" - // owner: @trierra - // kep: http://kep.k8s.io/2589 + // owner: @bitoku + // kep: https://kep.k8s.io/5825 // - // Enables the Portworx in-tree driver to Portworx migration feature. - CSIMigrationPortworx featuregate.Feature = "CSIMigrationPortworx" + // Enables using streaming RPCs for CRI list operations. + CRIListStreaming featuregate.Feature = "CRIListStreaming" // owner: @aramase // kep: http://kep.k8s.io/5538 @@ -131,7 +139,7 @@ const ( // owner: @HirazawaUi // // Enabling this feature gate will cause the pod's status to change due to a kubelet restart. - ChangeContainerStatusOnKubeletRestart = "ChangeContainerStatusOnKubeletRestart" + ChangeContainerStatusOnKubeletRestart featuregate.Feature = "ChangeContainerStatusOnKubeletRestart" // owner: @sanposhiho @wojtek-t // kep: https://kep.k8s.io/5278 @@ -188,6 +196,7 @@ const ( // depending on the DRA driver, may enable additional permissions // when a container uses the allocated device. DRAAdminAccess featuregate.Feature = "DRAAdminAccess" + // owner: @sunya-ch // kep: https://kep.k8s.io/5075 // @@ -224,6 +233,20 @@ const ( // Enables support for providing extended resource requests backed by DRA. DRAExtendedResource featuregate.Feature = "DRAExtendedResource" + // owner: @everpeace + // kep: http://kep.k8s.io/5491 + // + // Enable list type attributes for DRA devices in ResourceSlice + // and extends ResourceClaim's matchAttribute/distinctAttribute + // semantics so that they can work with list type attributes. + DRAListTypeAttributes featuregate.Feature = "DRAListTypeAttributes" + + // owner: @pravk03 + // kep: https://kep.k8s.io/5517 + // + // Enables support for node allocatable resources backed by DRA. + DRANodeAllocatableResources featuregate.Feature = "DRANodeAllocatableResources" + // owner: @mortent, @cici37 // kep: http://kep.k8s.io/4815 // @@ -247,6 +270,23 @@ const ( // status from DRA drivers. DRAResourceClaimDeviceStatus featuregate.Feature = "DRAResourceClaimDeviceStatus" + // owner: @aojea + // kep: http://kep.k8s.io/4817 + // + // Enables fine-grained authorization checks for ResourceClaim status updates. + // Requires separate permission on resourceclaims/binding to update + // status.allocation and status.reservedFor, and per-driver permission on + // resourceclaims/driver using associated-node / arbitrary-node verb prefixes + // to update status.devices. + DRAResourceClaimGranularStatusAuthorization featuregate.Feature = "DRAResourceClaimGranularStatusAuthorization" + + // owner: @nmn3m + // kep: http://kep.k8s.io/5677 + // + // Enables ResourcePoolStatusRequest API for querying DRA resource pool + // availability status. + DRAResourcePoolStatus featuregate.Feature = "DRAResourcePoolStatus" + // owner: @pohly // kep: http://kep.k8s.io/4381 // @@ -255,6 +295,12 @@ const ( // scheduler plugin configuration). DRASchedulerFilterTimeout featuregate.Feature = "DRASchedulerFilterTimeout" + // owner: @nojnhuh + // kep: https://kep.k8s.io/5729 + // + // Enables support for reserving and replicating templated ResourceClaims for an entire PodGroup. + DRAWorkloadResourceClaims featuregate.Feature = "DRAWorkloadResourceClaims" + // owner: @atiratree // kep: http://kep.k8s.io/3973 // @@ -321,6 +367,13 @@ const ( // Locked to default, will remove in v1.38. Progress is reflected in KEP #1972 update ExecProbeTimeout featuregate.Feature = "ExecProbeTimeout" + // owner: @seans3 + // kep: http://kep.k8s.io/4006 + // + // Enables the API server to proxy websockets to the kubelet for exec/attach + // if the kubelet has advertised that it supports them. + ExtendWebSocketsToKubelet featuregate.Feature = "ExtendWebSocketsToKubelet" + // owner: @HarshalNeelkamal // // Enables external service account JWT signing and key management. @@ -358,18 +411,12 @@ const ( // Enables support of configurable HPA scale-up and scale-down tolerances. HPAConfigurableTolerance featuregate.Feature = "HPAConfigurableTolerance" - // owner: @dxist + // owner: @johanneswuerbach + // kep: https://kep.k8s.io/2021 // // Enables support of HPA scaling to zero pods when an object or custom metric is configured. HPAScaleToZero featuregate.Feature = "HPAScaleToZero" - // owner: @deepakkinni @xing-yang - // kep: https://kep.k8s.io/2644 - // - // Honor Persistent Volume Reclaim Policy when it is "Delete" irrespective of PV-PVC - // deletion ordering. - HonorPVReclaimPolicy featuregate.Feature = "HonorPVReclaimPolicy" - // owner: @HirazawaUi // kep: https://kep.k8s.io/4762 // @@ -388,6 +435,12 @@ const ( // Enables the image volume source. ImageVolume featuregate.Feature = "ImageVolume" + // owner: @iholder101 + // kep: https://kep.k8s.io/4639 + // + // Enables adding the ImageVolume's digest to the pod's status. + ImageVolumeWithDigest featuregate.Feature = "ImageVolumeWithDigest" + // owner: @ndixita // kep: https://kep.k8s.io/5419 // @@ -400,14 +453,6 @@ const ( // Enables In-Place Pod Vertical Scaling InPlacePodVerticalScaling featuregate.Feature = "InPlacePodVerticalScaling" - // owner: @tallclair - // kep: http://kep.k8s.io/1287 - // - // Deprecated: This feature gate is no longer used. - // Was: Enables the AllocatedResources field in container status. This feature requires - // InPlacePodVerticalScaling also be enabled. - InPlacePodVerticalScalingAllocatedStatus featuregate.Feature = "InPlacePodVerticalScalingAllocatedStatus" - // owner: @tallclair @esotsal // // Allow resource resize for containers in Guaranteed pods with integer CPU requests ( default false ). @@ -421,10 +466,10 @@ const ( // Applies only in nodes with InPlacePodVerticalScaling and Memory Manager features enabled. InPlacePodVerticalScalingExclusiveMemory featuregate.Feature = "InPlacePodVerticalScalingExclusiveMemory" - // owner: @trierra + // owner: @natasha41575 // - // Disables the Portworx in-tree driver. - InTreePluginPortworxUnregister featuregate.Feature = "InTreePluginPortworxUnregister" + // Allow in-place pod resize of running non-sidecar init containers. + InPlacePodVerticalScalingInitContainers featuregate.Feature = "InPlacePodVerticalScalingInitContainers" // owner: @mimowo // kep: https://kep.k8s.io/3850 @@ -588,11 +633,6 @@ const ( // Enables maxUnavailable for StatefulSet MaxUnavailableStatefulSet featuregate.Feature = "MaxUnavailableStatefulSet" - // owner: @cynepco3hahue(alukiano) @cezaryzukowski @k-wiatrzyk, @Tal-or (only for GA graduation) - // - // Allows setting memory affinity for a container based on NUMA topology - MemoryManager featuregate.Feature = "MemoryManager" - // owner: @xiaoxubeii // kep: https://kep.k8s.io/2570 // @@ -685,6 +725,17 @@ const ( // Enables ordered namespace deletion. OrderedNamespaceDeletion featuregate.Feature = "OrderedNamespaceDeletion" + // owner: @tallclair + // + // Enables relisting individual pods on-demand. + PLEGOnDemandRelist featuregate.Feature = "PLEGOnDemandRelist" + + // owner: @ArvindParekh + // kep: https://kep.k8s.io/5541 + // + // Adds an Unused condition to PersistentVolumeClaim status that indicates when the PVC was last used by a pod. + PersistentVolumeClaimUnusedSinceTime featuregate.Feature = "PersistentVolumeClaimUnusedSinceTime" + // owner: @haircommander // kep: https://kep.k8s.io/2364 // @@ -703,6 +754,13 @@ const ( // Enables controlling pod ranking on replicaset scale-down. PodDeletionCost featuregate.Feature = "PodDeletionCost" + // owner: @KevinTMtz + // kep: https://kep.k8s.io/5526 + // + // PodLevelResourceManagers enables pod-level resource management in the Topology, CPU, and Memory managers. + // This feature depends on the PodLevelResources feature. + PodLevelResourceManagers featuregate.Feature = "PodLevelResourceManagers" + // owner: @ndixita // key: https://kep.k8s.io/2837 // @@ -760,6 +818,12 @@ const ( // similar to how `metadata.annotations` behaves. PodTopologyLabelsAdmission featuregate.Feature = "PodTopologyLabelsAdmission" + // owner: @briansonnenberg + // kep: https://kep.k8s.io/4188 + // + // Enables the PodsAPI feature to expose pod information via a gRPC API on the kubelet. + PodsAPI featuregate.Feature = "PodsAPI" + // owner: @seans3 // kep: http://kep.k8s.io/4006 // @@ -825,6 +889,11 @@ const ( // Relaxed DNS search string validation. RelaxedServiceNameValidation featuregate.Feature = "RelaxedServiceNameValidation" + // owner: @harshaln + // + // Enables running kubelet in a configuration that monitors and loads ClientCA from file paths + ReloadKubeletClientCAFile = "ReloadKubeletClientCAFile" + // owner: @zhangweikop // // Enable kubelet tls server to update certificate if the specified certificate files are changed. @@ -838,6 +907,12 @@ const ( // Adds the AllocatedResourcesStatus to the container status. ResourceHealthStatus featuregate.Feature = "ResourceHealthStatus" + // owner: @SergeyKanzhelev + // kep: https://kep.k8s.io/4680 + // + // Adds a message to the AllocatedResourcesStatus entries. + ResourceHealthStatusMessage featuregate.Feature = "ResourceHealthStatusMessage" + // owner: @yuanwang04 // kep: https://kep.k8s.io/5532 // @@ -944,11 +1019,10 @@ const ( // service account tokens bound to Pod objects. ServiceAccountTokenPodNodeInfo featuregate.Feature = "ServiceAccountTokenPodNodeInfo" - // owner: @gauravkghildiyal @robscott - // kep: https://kep.k8s.io/4444 + // owner: @jpbetz // - // Enables trafficDistribution field on Services. - ServiceTrafficDistribution featuregate.Feature = "ServiceTrafficDistribution" + // When enabled, the apiserver ignores changes to status fields in writes to the ServiceCIDR root resource. + ServiceCIDRStatusFieldWiping featuregate.Feature = "ServiceCIDRStatusFieldWiping" // owner: @gjkim42 @SergeyKanzhelev @matthyx @tzneal // kep: http://kep.k8s.io/753 @@ -958,11 +1032,39 @@ const ( // pod's lifecycle and will not block pod termination. SidecarContainers featuregate.Feature = "SidecarContainers" + // owner: @michaelasp + // kep: http://kep.k8s.io/5647 + // + // Introduces the ability for the DaemonSet controller to be able to read its writes + // prior to running a reconcile on the same object. + StaleControllerConsistencyDaemonSet featuregate.Feature = "StaleControllerConsistencyDaemonSet" + + // owner: @michaelasp + // kep: http://kep.k8s.io/5647 + // + // Introduces the ability for the Job controller to be able to read its writes + // prior to running a reconcile on the same object. + StaleControllerConsistencyJob featuregate.Feature = "StaleControllerConsistencyJob" + + // owner: @michaelasp + // kep: http://kep.k8s.io/5647 + // + // Introduces the ability for the ReplicaSet controller to be able to read its writes + // prior to running a reconcile on the same object. + StaleControllerConsistencyReplicaSet featuregate.Feature = "StaleControllerConsistencyReplicaSet" + + // owner: @michaelasp + // kep: http://kep.k8s.io/5647 + // + // Introduces the ability for the StatefulSet controller to be able to read its writes + // prior to running a reconcile on the same object. + StaleControllerConsistencyStatefulSet featuregate.Feature = "StaleControllerConsistencyStatefulSet" + // owner: @liggitt // // Mitigates spurious statefulset rollouts due to controller revision comparison mismatches // which are not semantically significant (e.g. serialization differences or missing defaulted fields). - StatefulSetSemanticRevisionComparison = "StatefulSetSemanticRevisionComparison" + StatefulSetSemanticRevisionComparison featuregate.Feature = "StatefulSetSemanticRevisionComparison" // owner: @cupnes // kep: https://kep.k8s.io/4049 @@ -1018,11 +1120,12 @@ const ( // Enables numeric comparison operators (Lt, Gt) for tolerations to match taints with threshold-based values. TaintTolerationComparisonOperators featuregate.Feature = "TaintTolerationComparisonOperators" - // owner: @robscott - // kep: https://kep.k8s.io/2433 + // owner: @44past4 + // kep: https://kep.k8s.io/5732 // - // Enables topology aware hints for EndpointSlices - TopologyAwareHints featuregate.Feature = "TopologyAwareHints" + // Enables topology-aware workload scheduling feature in kube-scheduler and related PodGroup API fields. + // When enabled, scheduler will try various placements for a pod group and pick the best one. + TopologyAwareWorkloadScheduling featuregate.Feature = "TopologyAwareWorkloadScheduling" // owner: @PiotrProkop // kep: https://kep.k8s.io/3545 @@ -1108,6 +1211,20 @@ const ( // // Enables support for joining Windows containers to a hosts' network namespace. WindowsHostNetwork featuregate.Feature = "WindowsHostNetwork" + + // owner: @wojtek-t + // kep: https://kep.k8s.io/5710 + // + // Enables support for workload-aware preemption in pod group scheduling cycle + // and related PodGroup and Workload API fields. + WorkloadAwarePreemption featuregate.Feature = "WorkloadAwarePreemption" + + // owner: @helayoty @mm4tt @wojtek-t + // kep: https://kep.k8s.io/5547 + // + // Enables the Job controller to automatically create Workload and PodGroup + // objects for Jobs that qualify for gang scheduling. + WorkloadWithJob featuregate.Feature = "WorkloadWithJob" ) // defaultVersionedKubernetesFeatureGates consists of all known Kubernetes-specific feature keys with VersionedSpecs. @@ -1137,6 +1254,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Deprecated, LockToDefault: true}, // remove in 1.38 }, + AllowServiceExternalIPs: { + {Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA}, + }, + AnyVolumeDataSource: { {Version: version.MustParse("1.18"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.24"), Default: true, PreRelease: featuregate.Beta}, @@ -1155,6 +1276,8 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate CPUCFSQuotaPeriod: { {Version: version.MustParse("1.12"), Default: false, PreRelease: featuregate.Alpha}, + // see https://github.com/kubernetes/kubernetes/pull/136339 for full context + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA}, // LockToDefault in 1.37, remove in 1.40 }, CPUManagerPolicyAlphaOptions: { @@ -1171,15 +1294,13 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.36 }, - CSIMigrationPortworx: { - {Version: version.MustParse("1.23"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.25"), Default: false, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta}, // On by default (requires Portworx CSI driver) - {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.36 + CRIListStreaming: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, }, CSIServiceAccountTokenSecrets: { {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.36; remove in 1.39 }, CSIVolumeHealth: { @@ -1227,35 +1348,51 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate DRAAdminAccess: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.36; remove in 1.39 }, DRAConsumableCapacity: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, DRADeviceBindingConditions: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, DRADeviceTaintRules: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Beta}, // Depends on an off-by-default beta API. }, DRADeviceTaints: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, DRAExtendedResource: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + DRAListTypeAttributes: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + + DRANodeAllocatableResources: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, }, DRAPartitionableDevices: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, DRAPrioritizedList: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA}, }, DRAResourceClaimDeviceStatus: { @@ -1263,10 +1400,22 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, }, + DRAResourceClaimGranularStatusAuthorization: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + DRAResourcePoolStatus: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + DRASchedulerFilterTimeout: { {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, + DRAWorkloadResourceClaims: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + DeploymentReplicaSetTerminatingReplicas: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, @@ -1281,12 +1430,14 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate DisableCPUQuotaWithExclusiveCPUs: { {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Deprecated}, // LockToDefault(true) in 1.37, remove in 1.38 }, DisableNodeKubeProxyVersion: { {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Deprecated}, - {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Deprecated}, // lock to default in 1.34 and remove in v1.37 + {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Deprecated}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Deprecated, LockToDefault: true}, // lock to default in 1.36 and remove in v1.39 }, DynamicResourceAllocation: { @@ -1296,6 +1447,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // TODO (https://github.com/kubernetes/kubernetes/issues/134459): remove completely in 1.38 }, + EnvFiles: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, @@ -1309,9 +1461,14 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in v1.38 }, + ExtendWebSocketsToKubelet: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + ExternalServiceAccountTokenSigner: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, GangScheduling: { @@ -1325,6 +1482,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate GitRepoVolumeDriver: { {Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA}, {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated}, + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Deprecated, LockToDefault: true}, }, GracefulNodeShutdown: { @@ -1346,12 +1504,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.16"), Default: false, PreRelease: featuregate.Alpha}, }, - HonorPVReclaimPolicy: { - {Version: version.MustParse("1.23"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.36 - }, - HostnameOverride: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, @@ -1367,10 +1519,16 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA and LockToDefault in 1.36, remove in 1.39 + }, + + ImageVolumeWithDigest: { + {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, }, InPlacePodLevelResourcesVerticalScaling: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, InPlacePodVerticalScaling: { @@ -1379,11 +1537,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.38 }, - InPlacePodVerticalScalingAllocatedStatus: { - {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated}, // remove in 1.36 - }, - InPlacePodVerticalScalingExclusiveCPUs: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, }, @@ -1392,8 +1545,8 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, }, - InTreePluginPortworxUnregister: { - {Version: version.MustParse("1.23"), Default: false, PreRelease: featuregate.Alpha}, // remove it along with CSIMigrationPortworx in 1.36 + InPlacePodVerticalScalingInitContainers: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, JobBackoffLimitPerIndex: { @@ -1439,6 +1592,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate KubeletFineGrainedAuthz: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, KubeletInUserNamespace: { @@ -1448,16 +1602,19 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate KubeletPSI: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, KubeletPodResourcesDynamicResources: { {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.37 }, KubeletPodResourcesGet: { {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.37 }, KubeletPodResourcesListUseActivePods: { @@ -1517,12 +1674,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Beta}, }, - MemoryManager: { - {Version: version.MustParse("1.21"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.22"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, - }, - MemoryQoS: { {Version: version.MustParse("1.22"), Default: false, PreRelease: featuregate.Alpha}, }, @@ -1538,6 +1689,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.38 }, MutablePVNodeAffinity: { @@ -1546,10 +1698,12 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate MutablePodResourcesForSuspendedJobs: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, MutableSchedulingDirectivesForSuspendedJobs: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, NFTablesProxyMode: { @@ -1560,6 +1714,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate NodeDeclaredFeatures: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, NodeInclusionPolicyInPodTopologySpread: { @@ -1571,6 +1726,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate NodeLogQuery: { {Version: version.MustParse("1.27"), Default: true, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.39, locked to default in 1.36 }, NodeSwap: { @@ -1595,6 +1751,14 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.37 }, + PLEGOnDemandRelist: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + PersistentVolumeClaimUnusedSinceTime: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + PodAndContainerStatsFromCRI: { {Version: version.MustParse("1.23"), Default: false, PreRelease: featuregate.Alpha}, }, @@ -1609,6 +1773,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.22"), Default: true, PreRelease: featuregate.Beta}, }, + PodLevelResourceManagers: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + PodLevelResources: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, @@ -1652,6 +1820,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, }, + PodsAPI: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + PortForwardWebsockets: { {Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta}, @@ -1671,6 +1843,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.12"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.39 }, QOSReserved: { @@ -1707,6 +1880,11 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate RelaxedServiceNameValidation: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + ReloadKubeletClientCAFile: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, ReloadKubeletServerCertificateFile: { @@ -1715,10 +1893,16 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate ResourceHealthStatus: { {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + ResourceHealthStatusMessage: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, RestartAllContainersOnContainerExits: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, RotateKubeletServerCertificate: { @@ -1733,6 +1917,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate SELinuxChangePolicy: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.39, locked to default in 1.36 }, SELinuxMount: { @@ -1744,6 +1929,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.25"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.39, locked to default in 1.36 }, SchedulerAsyncAPICalls: { @@ -1799,10 +1985,9 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, - ServiceTrafficDistribution: { - {Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA and LockToDefault in 1.33, remove in 1.36 + ServiceCIDRStatusFieldWiping: { + {Version: version.MustParse("1.0"), Default: false, PreRelease: featuregate.GA}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Deprecated}, }, SidecarContainers: { @@ -1811,6 +1996,22 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.33"), Default: true, LockToDefault: true, PreRelease: featuregate.GA}, // GA in 1.33 remove in 1.36 }, + StaleControllerConsistencyDaemonSet: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + StaleControllerConsistencyJob: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + StaleControllerConsistencyReplicaSet: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + + StaleControllerConsistencyStatefulSet: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, + }, + StatefulSetSemanticRevisionComparison: { // This is a mitigation for a 1.34 regression due to serialization differences that cannot be feature-gated, // so this mitigation should not auto-disable even if emulating versions prior to 1.34 with --emulation-version. @@ -1843,6 +2044,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate StrictIPCIDRValidation: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, SupplementalGroupsPolicy: { @@ -1860,11 +2062,8 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, }, - TopologyAwareHints: { - {Version: version.MustParse("1.21"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.23"), Default: false, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.24"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, + TopologyAwareWorkloadScheduling: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, }, TopologyManagerPolicyAlphaOptions: { @@ -1879,7 +2078,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate TopologyManagerPolicyOptions: { {Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA}, + {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.37 }, TranslateStreamCloseWebsocketRequests: { @@ -1895,12 +2094,14 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.25"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.39. }, VolumeAttributesClass: { {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.GA}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, VolumeLimitScaling: { @@ -1933,8 +2134,17 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated}, }, + WorkloadAwarePreemption: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + + WorkloadWithJob: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + apiextensionsfeatures.CRDObservedGenerationTracking: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, apiextensionsfeatures.CRDValidationRatcheting: { @@ -2016,6 +2226,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate genericfeatures.ConstrainedImpersonation: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, genericfeatures.CoordinatedLeaderElection: { @@ -2025,10 +2236,16 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate genericfeatures.DeclarativeValidation: { {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA and LockToDefault in 1.36, remove in 1.39 + }, + + genericfeatures.DeclarativeValidationBeta: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, genericfeatures.DeclarativeValidationTakeover: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Deprecated}, }, genericfeatures.DetectCacheInconsistency: { @@ -2046,9 +2263,14 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, + genericfeatures.ManifestBasedAdmissionControlConfig: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + genericfeatures.MutatingAdmissionPolicy: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA}, }, genericfeatures.OpenAPIEnums: { @@ -2075,6 +2297,11 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate genericfeatures.SeparateCacheWatchRPC: { {Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated}, + {Version: version.MustParse("1.36"), Default: false, LockToDefault: true, PreRelease: featuregate.Deprecated}, + }, + + genericfeatures.ShardedListAndWatch: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, }, genericfeatures.SizeBasedListCostEstimate: { @@ -2104,12 +2331,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, }, - genericfeatures.StructuredAuthorizationConfiguration: { - {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, - }, - genericfeatures.TokenRequestServiceAccountUIDValidation: { {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, @@ -2121,15 +2342,12 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate genericfeatures.UnknownVersionInteroperabilityProxy: { {Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, genericfeatures.WatchCacheInitializationPostStartHook: { {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Beta}, - }, - - genericfeatures.WatchFromStorageWithoutResourceVersion: { - {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated, LockToDefault: true}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, genericfeatures.WatchList: { @@ -2150,10 +2368,12 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate zpagesfeatures.ComponentFlagz: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, zpagesfeatures.ComponentStatusz: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, } @@ -2170,6 +2390,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature AllowOverwriteTerminationGracePeriodSeconds: {}, + AllowServiceExternalIPs: {}, + AnyVolumeDataSource: {}, AuthorizeNodeWithSelectors: {genericfeatures.AuthorizeWithSelectors}, @@ -2184,7 +2406,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature CPUManagerPolicyOptions: {}, - CSIMigrationPortworx: {}, + CRIListStreaming: {}, CSIServiceAccountTokenSecrets: {}, @@ -2220,14 +2442,24 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature DRAExtendedResource: {DynamicResourceAllocation}, + DRAListTypeAttributes: {DynamicResourceAllocation}, + + DRANodeAllocatableResources: {DynamicResourceAllocation}, + DRAPartitionableDevices: {DynamicResourceAllocation}, DRAPrioritizedList: {DynamicResourceAllocation}, DRAResourceClaimDeviceStatus: {}, // Soft dependency on DynamicResourceAllocation due to on/off-by-default conflict. + DRAResourceClaimGranularStatusAuthorization: {DynamicResourceAllocation, DRAResourceClaimDeviceStatus}, + + DRAResourcePoolStatus: {DynamicResourceAllocation}, + DRASchedulerFilterTimeout: {DynamicResourceAllocation}, + DRAWorkloadResourceClaims: {DynamicResourceAllocation, GenericWorkload}, + DeploymentReplicaSetTerminatingReplicas: {}, DisableAllocatorDualWrite: {MultiCIDRServiceAllocator}, @@ -2244,6 +2476,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature ExecProbeTimeout: {}, + ExtendWebSocketsToKubelet: {NodeDeclaredFeatures}, + ExternalServiceAccountTokenSigner: {}, GangScheduling: {GenericWorkload}, @@ -2260,25 +2494,23 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature HPAScaleToZero: {}, - HonorPVReclaimPolicy: {}, - HostnameOverride: {}, ImageMaximumGCAge: {}, ImageVolume: {}, + ImageVolumeWithDigest: {ImageVolume}, + InPlacePodLevelResourcesVerticalScaling: {InPlacePodVerticalScaling, PodLevelResources, NodeDeclaredFeatures}, InPlacePodVerticalScaling: {}, - InPlacePodVerticalScalingAllocatedStatus: {InPlacePodVerticalScaling}, - InPlacePodVerticalScalingExclusiveCPUs: {InPlacePodVerticalScaling}, - InPlacePodVerticalScalingExclusiveMemory: {InPlacePodVerticalScaling, MemoryManager}, + InPlacePodVerticalScalingExclusiveMemory: {InPlacePodVerticalScaling}, - InTreePluginPortworxUnregister: {}, + InPlacePodVerticalScalingInitContainers: {InPlacePodVerticalScaling, NodeDeclaredFeatures}, JobBackoffLimitPerIndex: {}, @@ -2326,8 +2558,6 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature MaxUnavailableStatefulSet: {}, - MemoryManager: {}, - MemoryQoS: {}, MultiCIDRServiceAllocator: {}, @@ -2356,12 +2586,18 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature OrderedNamespaceDeletion: {}, + PLEGOnDemandRelist: {}, + + PersistentVolumeClaimUnusedSinceTime: {}, + PodAndContainerStatsFromCRI: {}, PodCertificateRequest: {AuthorizeNodeWithSelectors}, PodDeletionCost: {}, + PodLevelResourceManagers: {PodLevelResources}, + PodLevelResources: {}, PodLifecycleSleepAction: {}, @@ -2378,6 +2614,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature PodTopologyLabelsAdmission: {}, + PodsAPI: {}, + PortForwardWebsockets: {}, PreferSameTrafficDistribution: {}, @@ -2400,10 +2638,14 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature RelaxedServiceNameValidation: {}, + ReloadKubeletClientCAFile: {}, + ReloadKubeletServerCertificateFile: {}, ResourceHealthStatus: {DynamicResourceAllocation}, + ResourceHealthStatusMessage: {ResourceHealthStatus}, + // RestartAllContainersOnContainerExits introduces a new container restart rule action. // All restart rules will be dropped by API if ContainerRestartRules feature is not enabled. RestartAllContainersOnContainerExits: {ContainerRestartRules, NodeDeclaredFeatures}, @@ -2438,10 +2680,18 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature ServiceAccountTokenPodNodeInfo: {}, - ServiceTrafficDistribution: {}, + ServiceCIDRStatusFieldWiping: {}, SidecarContainers: {}, + StaleControllerConsistencyDaemonSet: {featuregate.Feature(clientfeatures.AtomicFIFO)}, + + StaleControllerConsistencyJob: {featuregate.Feature(clientfeatures.AtomicFIFO)}, + + StaleControllerConsistencyReplicaSet: {featuregate.Feature(clientfeatures.AtomicFIFO)}, + + StaleControllerConsistencyStatefulSet: {featuregate.Feature(clientfeatures.AtomicFIFO)}, + StatefulSetSemanticRevisionComparison: {}, StorageCapacityScoring: {}, @@ -2462,7 +2712,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature TaintTolerationComparisonOperators: {}, - TopologyAwareHints: {}, + TopologyAwareWorkloadScheduling: {GenericWorkload}, TopologyManagerPolicyAlphaOptions: {}, @@ -2472,7 +2722,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature TranslateStreamCloseWebsocketRequests: {}, - UserNamespacesHostNetworkSupport: {UserNamespacesSupport}, + UserNamespacesHostNetworkSupport: {UserNamespacesSupport, NodeDeclaredFeatures}, UserNamespacesSupport: {}, @@ -2484,12 +2734,16 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature WinOverlay: {}, - WindowsCPUAndMemoryAffinity: {MemoryManager}, + WindowsCPUAndMemoryAffinity: {}, WindowsGracefulNodeShutdown: {GracefulNodeShutdown}, WindowsHostNetwork: {}, + WorkloadAwarePreemption: {GangScheduling}, + + WorkloadWithJob: {GenericWorkload}, + apiextensionsfeatures.CRDObservedGenerationTracking: {}, apiextensionsfeatures.CRDValidationRatcheting: {}, @@ -2526,6 +2780,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature genericfeatures.DeclarativeValidation: {}, + genericfeatures.DeclarativeValidationBeta: {genericfeatures.DeclarativeValidation}, genericfeatures.DeclarativeValidationTakeover: {genericfeatures.DeclarativeValidation}, genericfeatures.DetectCacheInconsistency: {}, @@ -2534,6 +2789,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature genericfeatures.ListFromCacheSnapshot: {}, + genericfeatures.ManifestBasedAdmissionControlConfig: {}, + genericfeatures.MutatingAdmissionPolicy: {}, genericfeatures.OpenAPIEnums: {}, @@ -2546,6 +2803,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature genericfeatures.SeparateCacheWatchRPC: {}, + genericfeatures.ShardedListAndWatch: {}, + genericfeatures.SizeBasedListCostEstimate: {}, genericfeatures.StorageVersionAPI: {genericfeatures.APIServerIdentity}, @@ -2558,8 +2817,6 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature genericfeatures.StructuredAuthenticationConfigurationJWKSMetrics: {genericfeatures.StructuredAuthenticationConfiguration}, - genericfeatures.StructuredAuthorizationConfiguration: {}, - genericfeatures.TokenRequestServiceAccountUIDValidation: {}, genericfeatures.UnauthenticatedHTTP2DOSMitigation: {}, @@ -2568,8 +2825,6 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature genericfeatures.WatchCacheInitializationPostStartHook: {}, - genericfeatures.WatchFromStorageWithoutResourceVersion: {}, - genericfeatures.WatchList: {}, kcmfeatures.CloudControllerManagerWatchBasedRoutesReconciliation: {}, @@ -2583,9 +2838,6 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature func init() { registerOpenshiftFeatures() - runtime.Must(utilfeature.DefaultMutableFeatureGate.AddVersioned(defaultVersionedKubernetesFeatureGates)) - runtime.Must(utilfeature.DefaultMutableFeatureGate.AddDependencies(defaultKubernetesFeatureGateDependencies)) - runtime.Must(zpagesfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) // Register all client-go features with kube's feature gate instance and make all client-go // feature checks use kube's instance. The effect is that for kube binaries, client-go @@ -2595,4 +2847,8 @@ func init() { ca := &clientAdapter{utilfeature.DefaultMutableFeatureGate} runtime.Must(clientfeatures.AddVersionedFeaturesToExistingFeatureGates(ca)) clientfeatures.ReplaceFeatureGates(ca) + + runtime.Must(utilfeature.DefaultMutableFeatureGate.AddVersioned(defaultVersionedKubernetesFeatureGates)) + runtime.Must(utilfeature.DefaultMutableFeatureGate.AddDependencies(defaultKubernetesFeatureGateDependencies)) + runtime.Must(zpagesfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) } diff --git a/pkg/generated/openapi/cmd/models-schema/main.go b/pkg/generated/openapi/cmd/models-schema/main.go index e3ca808c8e432..b20af94271d43 100644 --- a/pkg/generated/openapi/cmd/models-schema/main.go +++ b/pkg/generated/openapi/cmd/models-schema/main.go @@ -30,7 +30,7 @@ import ( func main() { err := output() if err != nil { - os.Stderr.WriteString(fmt.Sprintf("Failed: %v", err)) + os.Stderr.WriteString(fmt.Sprintf("Failed: %v", err)) // nolint:errcheck os.Exit(1) } } @@ -71,6 +71,6 @@ func output() error { if err != nil { return fmt.Errorf("error serializing api definitions: %w", err) } - os.Stdout.Write(data) + os.Stdout.Write(data) // nolint:errcheck return nil } diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 9b4f5c04533d9..1bd9d0c21432f 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -38,8 +38,6 @@ import ( authorizationv1beta1 "k8s.io/api/authorization/v1beta1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v2beta2 "k8s.io/api/autoscaling/v2beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1 "k8s.io/api/certificates/v1" @@ -74,7 +72,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingv1 "k8s.io/api/scheduling/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" storagev1 "k8s.io/api/storage/v1" storagev1alpha1 "k8s.io/api/storage/v1alpha1" @@ -91,7 +89,9 @@ import ( version "k8s.io/apimachinery/pkg/version" auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" apiv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" + apiv1beta1 "k8s.io/apiserver/pkg/server/flagz/api/v1beta1" statuszapiv1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1" + statuszapiv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1" clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" configv1alpha1 "k8s.io/cloud-provider/config/v1alpha1" @@ -124,13 +124,22 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ + v1.ApplyConfiguration{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_ApplyConfiguration(ref), v1.AuditAnnotation{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_AuditAnnotation(ref), v1.ExpressionWarning{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_ExpressionWarning(ref), + v1.JSONPatch{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_JSONPatch(ref), v1.MatchCondition{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MatchCondition(ref), v1.MatchResources{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MatchResources(ref), + v1.MutatingAdmissionPolicy{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicy(ref), + v1.MutatingAdmissionPolicyBinding{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyBinding(ref), + v1.MutatingAdmissionPolicyBindingList{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyBindingList(ref), + v1.MutatingAdmissionPolicyBindingSpec{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyBindingSpec(ref), + v1.MutatingAdmissionPolicyList{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyList(ref), + v1.MutatingAdmissionPolicySpec{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicySpec(ref), v1.MutatingWebhook{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref), v1.MutatingWebhookConfiguration{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfiguration(ref), v1.MutatingWebhookConfigurationList{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfigurationList(ref), + v1.Mutation{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_Mutation(ref), v1.NamedRuleWithOperations{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref), v1.ParamKind{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_ParamKind(ref), v1.ParamRef{}.OpenAPIModelName(): schema_k8sio_api_admissionregistration_v1_ParamRef(ref), @@ -404,48 +413,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA autoscalingv2.PodsMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2_PodsMetricStatus(ref), autoscalingv2.ResourceMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2_ResourceMetricSource(ref), autoscalingv2.ResourceMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2_ResourceMetricStatus(ref), - autoscalingv2beta1.ContainerResourceMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ContainerResourceMetricSource(ref), - autoscalingv2beta1.ContainerResourceMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ContainerResourceMetricStatus(ref), - autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_CrossVersionObjectReference(ref), - autoscalingv2beta1.ExternalMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ExternalMetricSource(ref), - autoscalingv2beta1.ExternalMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ExternalMetricStatus(ref), - autoscalingv2beta1.HorizontalPodAutoscaler{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscaler(ref), - autoscalingv2beta1.HorizontalPodAutoscalerCondition{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerCondition(ref), - autoscalingv2beta1.HorizontalPodAutoscalerList{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerList(ref), - autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerSpec(ref), - autoscalingv2beta1.HorizontalPodAutoscalerStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerStatus(ref), - autoscalingv2beta1.MetricSpec{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_MetricSpec(ref), - autoscalingv2beta1.MetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_MetricStatus(ref), - autoscalingv2beta1.ObjectMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ObjectMetricSource(ref), - autoscalingv2beta1.ObjectMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ObjectMetricStatus(ref), - autoscalingv2beta1.PodsMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_PodsMetricSource(ref), - autoscalingv2beta1.PodsMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_PodsMetricStatus(ref), - autoscalingv2beta1.ResourceMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ResourceMetricSource(ref), - autoscalingv2beta1.ResourceMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta1_ResourceMetricStatus(ref), - v2beta2.ContainerResourceMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricSource(ref), - v2beta2.ContainerResourceMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricStatus(ref), - v2beta2.CrossVersionObjectReference{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_CrossVersionObjectReference(ref), - v2beta2.ExternalMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ExternalMetricSource(ref), - v2beta2.ExternalMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ExternalMetricStatus(ref), - v2beta2.HPAScalingPolicy{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HPAScalingPolicy(ref), - v2beta2.HPAScalingRules{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HPAScalingRules(ref), - v2beta2.HorizontalPodAutoscaler{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscaler(ref), - v2beta2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerBehavior(ref), - v2beta2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerCondition(ref), - v2beta2.HorizontalPodAutoscalerList{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerList(ref), - v2beta2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerSpec(ref), - v2beta2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerStatus(ref), - v2beta2.MetricIdentifier{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_MetricIdentifier(ref), - v2beta2.MetricSpec{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_MetricSpec(ref), - v2beta2.MetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_MetricStatus(ref), - v2beta2.MetricTarget{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_MetricTarget(ref), - v2beta2.MetricValueStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_MetricValueStatus(ref), - v2beta2.ObjectMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ObjectMetricSource(ref), - v2beta2.ObjectMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ObjectMetricStatus(ref), - v2beta2.PodsMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_PodsMetricSource(ref), - v2beta2.PodsMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_PodsMetricStatus(ref), - v2beta2.ResourceMetricSource{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ResourceMetricSource(ref), - v2beta2.ResourceMetricStatus{}.OpenAPIModelName(): schema_k8sio_api_autoscaling_v2beta2_ResourceMetricStatus(ref), batchv1.CronJob{}.OpenAPIModelName(): schema_k8sio_api_batch_v1_CronJob(ref), batchv1.CronJobList{}.OpenAPIModelName(): schema_k8sio_api_batch_v1_CronJobList(ref), batchv1.CronJobSpec{}.OpenAPIModelName(): schema_k8sio_api_batch_v1_CronJobSpec(ref), @@ -580,6 +547,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA corev1.ISCSIPersistentVolumeSource{}.OpenAPIModelName(): schema_k8sio_api_core_v1_ISCSIPersistentVolumeSource(ref), corev1.ISCSIVolumeSource{}.OpenAPIModelName(): schema_k8sio_api_core_v1_ISCSIVolumeSource(ref), corev1.ImageVolumeSource{}.OpenAPIModelName(): schema_k8sio_api_core_v1_ImageVolumeSource(ref), + corev1.ImageVolumeStatus{}.OpenAPIModelName(): schema_k8sio_api_core_v1_ImageVolumeStatus(ref), corev1.KeyToPath{}.OpenAPIModelName(): schema_k8sio_api_core_v1_KeyToPath(ref), corev1.Lifecycle{}.OpenAPIModelName(): schema_k8sio_api_core_v1_Lifecycle(ref), corev1.LifecycleHandler{}.OpenAPIModelName(): schema_k8sio_api_core_v1_LifecycleHandler(ref), @@ -603,6 +571,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA corev1.Node{}.OpenAPIModelName(): schema_k8sio_api_core_v1_Node(ref), corev1.NodeAddress{}.OpenAPIModelName(): schema_k8sio_api_core_v1_NodeAddress(ref), corev1.NodeAffinity{}.OpenAPIModelName(): schema_k8sio_api_core_v1_NodeAffinity(ref), + corev1.NodeAllocatableResourceClaimStatus{}.OpenAPIModelName(): schema_k8sio_api_core_v1_NodeAllocatableResourceClaimStatus(ref), corev1.NodeCondition{}.OpenAPIModelName(): schema_k8sio_api_core_v1_NodeCondition(ref), corev1.NodeConfigSource{}.OpenAPIModelName(): schema_k8sio_api_core_v1_NodeConfigSource(ref), corev1.NodeConfigStatus{}.OpenAPIModelName(): schema_k8sio_api_core_v1_NodeConfigStatus(ref), @@ -655,6 +624,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA corev1.PodResourceClaim{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodResourceClaim(ref), corev1.PodResourceClaimStatus{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodResourceClaimStatus(ref), corev1.PodSchedulingGate{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodSchedulingGate(ref), + corev1.PodSchedulingGroup{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodSchedulingGroup(ref), corev1.PodSecurityContext{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodSecurityContext(ref), corev1.PodSignature{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodSignature(ref), corev1.PodSpec{}.OpenAPIModelName(): schema_k8sio_api_core_v1_PodSpec(ref), @@ -733,10 +703,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA corev1.VolumeProjection{}.OpenAPIModelName(): schema_k8sio_api_core_v1_VolumeProjection(ref), corev1.VolumeResourceRequirements{}.OpenAPIModelName(): schema_k8sio_api_core_v1_VolumeResourceRequirements(ref), corev1.VolumeSource{}.OpenAPIModelName(): schema_k8sio_api_core_v1_VolumeSource(ref), + corev1.VolumeStatus{}.OpenAPIModelName(): schema_k8sio_api_core_v1_VolumeStatus(ref), corev1.VsphereVirtualDiskVolumeSource{}.OpenAPIModelName(): schema_k8sio_api_core_v1_VsphereVirtualDiskVolumeSource(ref), corev1.WeightedPodAffinityTerm{}.OpenAPIModelName(): schema_k8sio_api_core_v1_WeightedPodAffinityTerm(ref), corev1.WindowsSecurityContextOptions{}.OpenAPIModelName(): schema_k8sio_api_core_v1_WindowsSecurityContextOptions(ref), - corev1.WorkloadReference{}.OpenAPIModelName(): schema_k8sio_api_core_v1_WorkloadReference(ref), discoveryv1.Endpoint{}.OpenAPIModelName(): schema_k8sio_api_discovery_v1_Endpoint(ref), discoveryv1.EndpointConditions{}.OpenAPIModelName(): schema_k8sio_api_discovery_v1_EndpointConditions(ref), discoveryv1.EndpointHints{}.OpenAPIModelName(): schema_k8sio_api_discovery_v1_EndpointHints(ref), @@ -1049,6 +1019,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA resourcev1.DeviceToleration{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_DeviceToleration(ref), resourcev1.ExactDeviceRequest{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_ExactDeviceRequest(ref), resourcev1.NetworkDeviceData{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_NetworkDeviceData(ref), + resourcev1.NodeAllocatableResourceMapping{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_NodeAllocatableResourceMapping(ref), resourcev1.OpaqueDeviceConfiguration{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_OpaqueDeviceConfiguration(ref), resourcev1.ResourceClaim{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_ResourceClaim(ref), resourcev1.ResourceClaimConsumerReference{}.OpenAPIModelName(): schema_k8sio_api_resource_v1_ResourceClaimConsumerReference(ref), @@ -1070,6 +1041,11 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA v1alpha3.DeviceTaintRuleSpec{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_DeviceTaintRuleSpec(ref), v1alpha3.DeviceTaintRuleStatus{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_DeviceTaintRuleStatus(ref), v1alpha3.DeviceTaintSelector{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_DeviceTaintSelector(ref), + v1alpha3.PoolStatus{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_PoolStatus(ref), + v1alpha3.ResourcePoolStatusRequest{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequest(ref), + v1alpha3.ResourcePoolStatusRequestList{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequestList(ref), + v1alpha3.ResourcePoolStatusRequestSpec{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequestSpec(ref), + v1alpha3.ResourcePoolStatusRequestStatus{}.OpenAPIModelName(): schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequestStatus(ref), resourcev1beta1.AllocatedDeviceStatus{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_AllocatedDeviceStatus(ref), resourcev1beta1.AllocationResult{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_AllocationResult(ref), resourcev1beta1.BasicDevice{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_BasicDevice(ref), @@ -1100,6 +1076,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA resourcev1beta1.DeviceTaint{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_DeviceTaint(ref), resourcev1beta1.DeviceToleration{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_DeviceToleration(ref), resourcev1beta1.NetworkDeviceData{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_NetworkDeviceData(ref), + resourcev1beta1.NodeAllocatableResourceMapping{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_NodeAllocatableResourceMapping(ref), resourcev1beta1.OpaqueDeviceConfiguration{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_OpaqueDeviceConfiguration(ref), resourcev1beta1.ResourceClaim{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_ResourceClaim(ref), resourcev1beta1.ResourceClaimConsumerReference{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta1_ResourceClaimConsumerReference(ref), @@ -1140,9 +1117,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA resourcev1beta2.DeviceSelector{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceSelector(ref), resourcev1beta2.DeviceSubRequest{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceSubRequest(ref), resourcev1beta2.DeviceTaint{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceTaint(ref), + resourcev1beta2.DeviceTaintRule{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceTaintRule(ref), + resourcev1beta2.DeviceTaintRuleList{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceTaintRuleList(ref), + resourcev1beta2.DeviceTaintRuleSpec{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceTaintRuleSpec(ref), + resourcev1beta2.DeviceTaintRuleStatus{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceTaintRuleStatus(ref), + resourcev1beta2.DeviceTaintSelector{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceTaintSelector(ref), resourcev1beta2.DeviceToleration{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_DeviceToleration(ref), resourcev1beta2.ExactDeviceRequest{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_ExactDeviceRequest(ref), resourcev1beta2.NetworkDeviceData{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_NetworkDeviceData(ref), + resourcev1beta2.NodeAllocatableResourceMapping{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_NodeAllocatableResourceMapping(ref), resourcev1beta2.OpaqueDeviceConfiguration{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_OpaqueDeviceConfiguration(ref), resourcev1beta2.ResourceClaim{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_ResourceClaim(ref), resourcev1beta2.ResourceClaimConsumerReference{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_ResourceClaimConsumerReference(ref), @@ -1158,16 +1141,24 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA resourcev1beta2.ResourceSliceSpec{}.OpenAPIModelName(): schema_k8sio_api_resource_v1beta2_ResourceSliceSpec(ref), schedulingv1.PriorityClass{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1_PriorityClass(ref), schedulingv1.PriorityClassList{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1_PriorityClassList(ref), - schedulingv1alpha1.BasicSchedulingPolicy{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_BasicSchedulingPolicy(ref), - schedulingv1alpha1.GangSchedulingPolicy{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_GangSchedulingPolicy(ref), - schedulingv1alpha1.PodGroup{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_PodGroup(ref), - schedulingv1alpha1.PodGroupPolicy{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_PodGroupPolicy(ref), - schedulingv1alpha1.PriorityClass{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_PriorityClass(ref), - schedulingv1alpha1.PriorityClassList{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_PriorityClassList(ref), - schedulingv1alpha1.TypedLocalObjectReference{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_TypedLocalObjectReference(ref), - schedulingv1alpha1.Workload{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_Workload(ref), - schedulingv1alpha1.WorkloadList{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_WorkloadList(ref), - schedulingv1alpha1.WorkloadSpec{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha1_WorkloadSpec(ref), + schedulingv1alpha2.BasicSchedulingPolicy{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_BasicSchedulingPolicy(ref), + schedulingv1alpha2.GangSchedulingPolicy{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_GangSchedulingPolicy(ref), + schedulingv1alpha2.PodGroup{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroup(ref), + schedulingv1alpha2.PodGroupList{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupList(ref), + schedulingv1alpha2.PodGroupResourceClaim{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupResourceClaim(ref), + schedulingv1alpha2.PodGroupResourceClaimStatus{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupResourceClaimStatus(ref), + schedulingv1alpha2.PodGroupSchedulingConstraints{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupSchedulingConstraints(ref), + schedulingv1alpha2.PodGroupSchedulingPolicy{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupSchedulingPolicy(ref), + schedulingv1alpha2.PodGroupSpec{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupSpec(ref), + schedulingv1alpha2.PodGroupStatus{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupStatus(ref), + schedulingv1alpha2.PodGroupTemplate{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupTemplate(ref), + schedulingv1alpha2.PodGroupTemplateReference{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_PodGroupTemplateReference(ref), + schedulingv1alpha2.TopologyConstraint{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_TopologyConstraint(ref), + schedulingv1alpha2.TypedLocalObjectReference{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_TypedLocalObjectReference(ref), + schedulingv1alpha2.Workload{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_Workload(ref), + schedulingv1alpha2.WorkloadList{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_WorkloadList(ref), + schedulingv1alpha2.WorkloadPodGroupTemplateReference{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_WorkloadPodGroupTemplateReference(ref), + schedulingv1alpha2.WorkloadSpec{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1alpha2_WorkloadSpec(ref), schedulingv1beta1.PriorityClass{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1beta1_PriorityClass(ref), schedulingv1beta1.PriorityClassList{}.OpenAPIModelName(): schema_k8sio_api_scheduling_v1beta1_PriorityClassList(ref), storagev1.CSIDriver{}.OpenAPIModelName(): schema_k8sio_api_storage_v1_CSIDriver(ref), @@ -1316,6 +1307,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA metav1.Preconditions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Preconditions(ref), metav1.RootPaths{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_RootPaths(ref), metav1.ServerAddressByClientCIDR{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref), + metav1.ShardInfo{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ShardInfo(ref), metav1.Status{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Status(ref), metav1.StatusCause{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_StatusCause(ref), metav1.StatusDetails{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_StatusDetails(ref), @@ -1350,7 +1342,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA auditv1.PolicyList{}.OpenAPIModelName(): schema_pkg_apis_audit_v1_PolicyList(ref), auditv1.PolicyRule{}.OpenAPIModelName(): schema_pkg_apis_audit_v1_PolicyRule(ref), apiv1alpha1.Flagz{}.OpenAPIModelName(): schema_server_flagz_api_v1alpha1_Flagz(ref), + apiv1beta1.Flagz{}.OpenAPIModelName(): schema_server_flagz_api_v1beta1_Flagz(ref), statuszapiv1alpha1.Statusz{}.OpenAPIModelName(): schema_server_statusz_api_v1alpha1_Statusz(ref), + statuszapiv1beta1.Statusz{}.OpenAPIModelName(): schema_server_statusz_api_v1beta1_Statusz(ref), clientauthenticationv1.Cluster{}.OpenAPIModelName(): schema_pkg_apis_clientauthentication_v1_Cluster(ref), clientauthenticationv1.ExecCredential{}.OpenAPIModelName(): schema_pkg_apis_clientauthentication_v1_ExecCredential(ref), clientauthenticationv1.ExecCredentialSpec{}.OpenAPIModelName(): schema_pkg_apis_clientauthentication_v1_ExecCredentialSpec(ref), @@ -1412,6 +1406,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA kubecontrollermanagerconfigv1alpha1.PodGCControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_PodGCControllerConfiguration(ref), kubecontrollermanagerconfigv1alpha1.ReplicaSetControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_ReplicaSetControllerConfiguration(ref), kubecontrollermanagerconfigv1alpha1.ReplicationControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_ReplicationControllerConfiguration(ref), + kubecontrollermanagerconfigv1alpha1.ResourceClaimControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_ResourceClaimControllerConfiguration(ref), kubecontrollermanagerconfigv1alpha1.ResourceQuotaControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_ResourceQuotaControllerConfiguration(ref), kubecontrollermanagerconfigv1alpha1.SAControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_SAControllerConfiguration(ref), kubecontrollermanagerconfigv1alpha1.StatefulSetControllerConfiguration{}.OpenAPIModelName(): schema_k8sio_kube_controller_manager_config_v1alpha1_StatefulSetControllerConfiguration(ref), @@ -1512,6 +1507,26 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA } } +func schema_k8sio_api_admissionregistration_v1_ApplyConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplyConfiguration defines the desired configuration values of an object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "expression": { + SchemaProps: spec.SchemaProps{ + Description: "expression will be evaluated by CEL to create an apply configuration. ref: https://github.com/google/cel-spec\n\nApply configurations are declared in CEL using object initialization. For example, this CEL expression returns an apply configuration to set a single field:\n\n\tObject{\n\t spec: Object.spec{\n\t serviceAccountName: \"example\"\n\t }\n\t}\n\nApply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of values not included in the apply configuration.\n\nCEL expressions have access to the object types needed to create apply configurations:\n\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_k8sio_api_admissionregistration_v1_AuditAnnotation(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1551,7 +1566,7 @@ func schema_k8sio_api_admissionregistration_v1_ExpressionWarning(ref common.Refe Properties: map[string]spec.Schema{ "fieldRef": { SchemaProps: spec.SchemaProps{ - Description: "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + Description: "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", Default: "", Type: []string{"string"}, Format: "", @@ -1559,7 +1574,7 @@ func schema_k8sio_api_admissionregistration_v1_ExpressionWarning(ref common.Refe }, "warning": { SchemaProps: spec.SchemaProps{ - Description: "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + Description: "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", Default: "", Type: []string{"string"}, Format: "", @@ -1572,6 +1587,26 @@ func schema_k8sio_api_admissionregistration_v1_ExpressionWarning(ref common.Refe } } +func schema_k8sio_api_admissionregistration_v1_JSONPatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONPatch defines a JSON Patch.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "expression": { + SchemaProps: spec.SchemaProps{ + Description: "expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). ref: https://github.com/google/cel-spec\n\nexpression must return an array of JSONPatch values.\n\nFor example, this CEL expression returns a JSON patch to conditionally modify a value:\n\n\t [\n\t JSONPatch{op: \"test\", path: \"/spec/example\", value: \"Red\"},\n\t JSONPatch{op: \"replace\", path: \"/spec/example\", value: \"Green\"}\n\t ]\n\nTo define an object for the patch value, use Object types. For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/spec/selector\",\n\t value: Object.spec.selector{matchLabels: {\"environment\": \"test\"}}\n\t }\n\t ]\n\nTo use strings containing '/' and '~' as JSONPatch path keys, use \"jsonpatch.escapeKey\". For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/metadata/labels/\" + jsonpatch.escapeKey(\"example.com/environment\"),\n\t value: \"test\"\n\t },\n\t ]\n\nCEL expressions have access to the types needed to create JSON patches and objects:\n\n- 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'.\n See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string,\n integer, array, map or object. If set, the 'path' and 'from' fields must be set to a\n [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL\n function may be used to escape path keys containing '/' and '~'.\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nCEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) as well as:\n\n- 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively).\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_k8sio_api_admissionregistration_v1_MatchCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1581,7 +1616,7 @@ func schema_k8sio_api_admissionregistration_v1_MatchCondition(ref common.Referen Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + Description: "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -1589,7 +1624,7 @@ func schema_k8sio_api_admissionregistration_v1_MatchCondition(ref common.Referen }, "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + Description: "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -1611,13 +1646,13 @@ func schema_k8sio_api_admissionregistration_v1_MatchResources(ref common.Referen Properties: map[string]spec.Schema{ "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, @@ -1628,7 +1663,7 @@ func schema_k8sio_api_admissionregistration_v1_MatchResources(ref common.Referen }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + Description: "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1647,7 +1682,7 @@ func schema_k8sio_api_admissionregistration_v1_MatchResources(ref common.Referen }, }, SchemaProps: spec.SchemaProps{ - Description: "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + Description: "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1680,6 +1715,333 @@ func schema_k8sio_api_admissionregistration_v1_MatchResources(ref common.Referen } } +func schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec defines the desired behavior of the MutatingAdmissionPolicy.", + Default: map[string]interface{}{}, + Ref: ref(v1.MutatingAdmissionPolicySpec{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + v1.MutatingAdmissionPolicySpec{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding. Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget).\n\nAdding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec defines the desired behavior of the MutatingAdmissionPolicyBinding.", + Default: map[string]interface{}{}, + Ref: ref(v1.MutatingAdmissionPolicyBindingSpec{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + v1.MutatingAdmissionPolicyBindingSpec{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyBindingList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of PolicyBinding.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.MutatingAdmissionPolicyBinding{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + v1.MutatingAdmissionPolicyBinding{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyBindingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "policyName": { + SchemaProps: spec.SchemaProps{ + Description: "policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + Type: []string{"string"}, + Format: "", + }, + }, + "paramRef": { + SchemaProps: spec.SchemaProps{ + Description: "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.", + Ref: ref(v1.ParamRef{}.OpenAPIModelName()), + }, + }, + "matchResources": { + SchemaProps: spec.SchemaProps{ + Description: "matchResources limits what resources match this binding and may be mutated by it. Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and matchConditions before the resource may be mutated. When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints and matchConditions must match for the resource to be mutated. Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT.", + Ref: ref(v1.MatchResources{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + v1.MatchResources{}.OpenAPIModelName(), v1.ParamRef{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicyList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of ValidatingAdmissionPolicy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.MutatingAdmissionPolicy{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + v1.MutatingAdmissionPolicy{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_admissionregistration_v1_MutatingAdmissionPolicySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MutatingAdmissionPolicySpec defines the desired behavior of the admission policy.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "paramKind": { + SchemaProps: spec.SchemaProps{ + Description: "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null.", + Ref: ref(v1.ParamKind{}.OpenAPIModelName()), + }, + }, + "matchConstraints": { + SchemaProps: spec.SchemaProps{ + Description: "matchConstraints specifies what resources this policy is designed to validate. The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT. Required.", + Ref: ref(v1.MatchResources{}.OpenAPIModelName()), + }, + }, + "variables": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except matchConditions because matchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, variables must be sorted by the order of first appearance and acyclic.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.Variable{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + "mutations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "mutations contain operations to perform on matching objects. mutations may not be empty; a minimum of one mutation is required. mutations are evaluated in order, and are reinvoked according to the reinvocationPolicy. The mutations of a policy are invoked for each binding of this policy and reinvocation of mutations occurs on a per binding basis.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.Mutation{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + "failurePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if paramKind refers to a non-existent Kind. A binding is invalid if paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nAllowed values are Ignore or Fail. Defaults to Fail.\n\nPossible enum values:\n - `\"Fail\"` means that an error calling the webhook causes the admission to fail.\n - `\"Ignore\"` means that an error calling the webhook is ignored.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Fail", "Ignore"}, + }, + }, + "matchConditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the matchConstraints. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.MatchCondition{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + "reinvocationPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: These mutations will not be called more than once per binding in a single admission evaluation.\n\nIfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only reinvoked when mutations change the object after this mutation is invoked. Required.\n\nPossible enum values:\n - `\"IfNeeded\"` indicates that the mutation may be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial mutation call.\n - `\"Never\"` indicates that the mutation must not be called more than once in a single admission evaluation.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"IfNeeded", "Never"}, + }, + }, + }, + }, + }, + Dependencies: []string{ + v1.MatchCondition{}.OpenAPIModelName(), v1.MatchResources{}.OpenAPIModelName(), v1.Mutation{}.OpenAPIModelName(), v1.ParamKind{}.OpenAPIModelName(), v1.Variable{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1689,7 +2051,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + Description: "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", Default: "", Type: []string{"string"}, Format: "", @@ -1697,7 +2059,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, "clientConfig": { SchemaProps: spec.SchemaProps{ - Description: "ClientConfig defines how to communicate with the hook. Required", + Description: "clientConfig defines how to communicate with the hook. Required", Default: map[string]interface{}{}, Ref: ref(v1.WebhookClientConfig{}.OpenAPIModelName()), }, @@ -1709,7 +2071,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, }, SchemaProps: spec.SchemaProps{ - Description: "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + Description: "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1723,7 +2085,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, "failurePolicy": { SchemaProps: spec.SchemaProps{ - Description: "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.\n\nPossible enum values:\n - `\"Fail\"` means that an error calling the webhook causes the admission to fail.\n - `\"Ignore\"` means that an error calling the webhook is ignored.", + Description: "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.\n\nPossible enum values:\n - `\"Fail\"` means that an error calling the webhook causes the admission to fail.\n - `\"Ignore\"` means that an error calling the webhook is ignored.", Type: []string{"string"}, Format: "", Enum: []interface{}{"Fail", "Ignore"}, @@ -1739,19 +2101,19 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "sideEffects": { SchemaProps: spec.SchemaProps{ - Description: "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.\n\nPossible enum values:\n - `\"None\"` means that calling the webhook will have no side effects.\n - `\"NoneOnDryRun\"` means that calling the webhook will possibly have side effects, but if the request being reviewed has the dry-run attribute, the side effects will be suppressed.\n - `\"Some\"` means that calling the webhook will possibly have side effects. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.\n - `\"Unknown\"` means that no information is known about the side effects of calling the webhook. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.", + Description: "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.\n\nPossible enum values:\n - `\"None\"` means that calling the webhook will have no side effects.\n - `\"NoneOnDryRun\"` means that calling the webhook will possibly have side effects, but if the request being reviewed has the dry-run attribute, the side effects will be suppressed.\n - `\"Some\"` means that calling the webhook will possibly have side effects. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.\n - `\"Unknown\"` means that no information is known about the side effects of calling the webhook. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.", Type: []string{"string"}, Format: "", Enum: []interface{}{"None", "NoneOnDryRun", "Some", "Unknown"}, @@ -1759,7 +2121,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, "timeoutSeconds": { SchemaProps: spec.SchemaProps{ - Description: "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + Description: "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", Type: []string{"integer"}, Format: "int32", }, @@ -1771,7 +2133,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, }, SchemaProps: spec.SchemaProps{ - Description: "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + Description: "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1804,7 +2166,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1848,7 +2210,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfiguration(ref }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -1865,7 +2227,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfiguration(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Webhooks is a list of webhooks and the affected resources and operations.", + Description: "webhooks is a list of webhooks and the affected resources and operations.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1908,7 +2270,7 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfigurationList( }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -1936,6 +2298,43 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfigurationList( } } +func schema_k8sio_api_admissionregistration_v1_Mutation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Mutation specifies the CEL expression which is used to apply the Mutation.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "patchType": { + SchemaProps: spec.SchemaProps{ + Description: "patchType indicates the patch strategy used. Allowed values are \"ApplyConfiguration\" and \"JSONPatch\". Required.\n\n\nPossible enum values:\n - `\"ApplyConfiguration\"` ApplyConfiguration indicates that the mutation is using apply configuration to mutate the object.\n - `\"JSONPatch\"` JSONPatch indicates that the object is mutated through JSON Patch.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ApplyConfiguration", "JSONPatch"}, + }, + }, + "applyConfiguration": { + SchemaProps: spec.SchemaProps{ + Description: "applyConfiguration defines the desired configuration values of an object. The configuration is applied to the admission object using [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). A CEL expression is used to create apply configuration.", + Ref: ref(v1.ApplyConfiguration{}.OpenAPIModelName()), + }, + }, + "jsonPatch": { + SchemaProps: spec.SchemaProps{ + Description: "jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. A CEL expression is used to create the JSON patch.", + Ref: ref(v1.JSONPatch{}.OpenAPIModelName()), + }, + }, + }, + Required: []string{"patchType"}, + }, + }, + Dependencies: []string{ + v1.ApplyConfiguration{}.OpenAPIModelName(), v1.JSONPatch{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1950,7 +2349,7 @@ func schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref commo }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + Description: "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1970,7 +2369,7 @@ func schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref commo }, }, SchemaProps: spec.SchemaProps{ - Description: "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + Description: "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1991,7 +2390,7 @@ func schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref commo }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + Description: "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2011,7 +2410,7 @@ func schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref commo }, }, SchemaProps: spec.SchemaProps{ - Description: "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + Description: "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2031,7 +2430,7 @@ func schema_k8sio_api_admissionregistration_v1_NamedRuleWithOperations(ref commo }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + Description: "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2072,14 +2471,14 @@ func schema_k8sio_api_admissionregistration_v1_ParamKind(ref common.ReferenceCal Properties: map[string]spec.Schema{ "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + Description: "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", Type: []string{"string"}, Format: "", }, }, "kind": { SchemaProps: spec.SchemaProps{ - Description: "Kind is the API kind the resources belong to. Required.", + Description: "kind is the API kind the resources belong to. Required.", Type: []string{"string"}, Format: "", }, @@ -2124,7 +2523,7 @@ func schema_k8sio_api_admissionregistration_v1_ParamRef(ref common.ReferenceCall }, "parameterNotFoundAction": { SchemaProps: spec.SchemaProps{ - Description: "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + Description: "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", Type: []string{"string"}, Format: "", }, @@ -2156,7 +2555,7 @@ func schema_k8sio_api_admissionregistration_v1_Rule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + Description: "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2176,7 +2575,7 @@ func schema_k8sio_api_admissionregistration_v1_Rule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + Description: "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2196,7 +2595,7 @@ func schema_k8sio_api_admissionregistration_v1_Rule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + Description: "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2237,7 +2636,7 @@ func schema_k8sio_api_admissionregistration_v1_RuleWithOperations(ref common.Ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + Description: "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2258,7 +2657,7 @@ func schema_k8sio_api_admissionregistration_v1_RuleWithOperations(ref common.Ref }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + Description: "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2278,7 +2677,7 @@ func schema_k8sio_api_admissionregistration_v1_RuleWithOperations(ref common.Ref }, }, SchemaProps: spec.SchemaProps{ - Description: "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + Description: "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2298,7 +2697,7 @@ func schema_k8sio_api_admissionregistration_v1_RuleWithOperations(ref common.Ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + Description: "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2334,7 +2733,7 @@ func schema_k8sio_api_admissionregistration_v1_ServiceReference(ref common.Refer Properties: map[string]spec.Schema{ "namespace": { SchemaProps: spec.SchemaProps{ - Description: "`namespace` is the namespace of the service. Required", + Description: "namespace is the namespace of the service. Required", Default: "", Type: []string{"string"}, Format: "", @@ -2342,7 +2741,7 @@ func schema_k8sio_api_admissionregistration_v1_ServiceReference(ref common.Refer }, "name": { SchemaProps: spec.SchemaProps{ - Description: "`name` is the name of the service. Required", + Description: "name is the name of the service. Required", Default: "", Type: []string{"string"}, Format: "", @@ -2350,14 +2749,14 @@ func schema_k8sio_api_admissionregistration_v1_ServiceReference(ref common.Refer }, "path": { SchemaProps: spec.SchemaProps{ - Description: "`path` is an optional URL path which will be sent in any request to this service.", + Description: "path is an optional URL path which will be sent in any request to this service.", Type: []string{"string"}, Format: "", }, }, "port": { SchemaProps: spec.SchemaProps{ - Description: "If specified, the port on the service that hosting webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", + Description: "port is the port on the service that hosts the webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", Type: []string{"integer"}, Format: "int32", }, @@ -2383,7 +2782,7 @@ func schema_k8sio_api_admissionregistration_v1_TypeChecking(ref common.Reference }, }, SchemaProps: spec.SchemaProps{ - Description: "The type checking warnings for each expression.", + Description: "expressionWarnings contains the type checking warnings for each expression.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2426,21 +2825,21 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicy(ref com }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the ValidatingAdmissionPolicy.", + Description: "spec defines the desired behavior of the ValidatingAdmissionPolicy.", Default: map[string]interface{}{}, Ref: ref(v1.ValidatingAdmissionPolicySpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", + Description: "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", Default: map[string]interface{}{}, Ref: ref(v1.ValidatingAdmissionPolicyStatus{}.OpenAPIModelName()), }, @@ -2476,19 +2875,20 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyBinding( }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.", + Description: "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding.", Default: map[string]interface{}{}, Ref: ref(v1.ValidatingAdmissionPolicyBindingSpec{}.OpenAPIModelName()), }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -2519,7 +2919,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyBindingL }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -2556,7 +2956,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyBindingS Properties: map[string]spec.Schema{ "policyName": { SchemaProps: spec.SchemaProps{ - Description: "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + Description: "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", Type: []string{"string"}, Format: "", }, @@ -2569,7 +2969,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyBindingS }, "matchResources": { SchemaProps: spec.SchemaProps{ - Description: "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", + Description: "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", Ref: ref(v1.MatchResources{}.OpenAPIModelName()), }, }, @@ -2595,6 +2995,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyBindingS }, }, }, + Required: []string{"policyName", "validationActions"}, }, }, Dependencies: []string{ @@ -2625,7 +3026,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyList(ref }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -2662,13 +3063,13 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicySpec(ref Properties: map[string]spec.Schema{ "paramKind": { SchemaProps: spec.SchemaProps{ - Description: "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", + Description: "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", Ref: ref(v1.ParamKind{}.OpenAPIModelName()), }, }, "matchConstraints": { SchemaProps: spec.SchemaProps{ - Description: "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", + Description: "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", Ref: ref(v1.MatchResources{}.OpenAPIModelName()), }, }, @@ -2679,7 +3080,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicySpec(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + Description: "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2730,7 +3131,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicySpec(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2754,7 +3155,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicySpec(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + Description: "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2783,14 +3184,14 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyStatus(r Properties: map[string]spec.Schema{ "observedGeneration": { SchemaProps: spec.SchemaProps{ - Description: "The generation observed by the controller.", + Description: "observedGeneration is the generation observed by the controller.", Type: []string{"integer"}, Format: "int64", }, }, "typeChecking": { SchemaProps: spec.SchemaProps{ - Description: "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.", + Description: "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking.", Ref: ref(v1.TypeChecking{}.OpenAPIModelName()), }, }, @@ -2804,7 +3205,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyStatus(r }, }, SchemaProps: spec.SchemaProps{ - Description: "The conditions represent the latest available observations of a policy's current state.", + Description: "conditions represent the latest available observations of a policy's current state.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2833,7 +3234,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + Description: "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", Default: "", Type: []string{"string"}, Format: "", @@ -2841,7 +3242,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, "clientConfig": { SchemaProps: spec.SchemaProps{ - Description: "ClientConfig defines how to communicate with the hook. Required", + Description: "clientConfig defines how to communicate with the hook. Required", Default: map[string]interface{}{}, Ref: ref(v1.WebhookClientConfig{}.OpenAPIModelName()), }, @@ -2853,7 +3254,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, }, SchemaProps: spec.SchemaProps{ - Description: "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + Description: "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2867,7 +3268,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, "failurePolicy": { SchemaProps: spec.SchemaProps{ - Description: "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.\n\nPossible enum values:\n - `\"Fail\"` means that an error calling the webhook causes the admission to fail.\n - `\"Ignore\"` means that an error calling the webhook is ignored.", + Description: "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.\n\nPossible enum values:\n - `\"Fail\"` means that an error calling the webhook causes the admission to fail.\n - `\"Ignore\"` means that an error calling the webhook is ignored.", Type: []string{"string"}, Format: "", Enum: []interface{}{"Fail", "Ignore"}, @@ -2883,19 +3284,19 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "sideEffects": { SchemaProps: spec.SchemaProps{ - Description: "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.\n\nPossible enum values:\n - `\"None\"` means that calling the webhook will have no side effects.\n - `\"NoneOnDryRun\"` means that calling the webhook will possibly have side effects, but if the request being reviewed has the dry-run attribute, the side effects will be suppressed.\n - `\"Some\"` means that calling the webhook will possibly have side effects. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.\n - `\"Unknown\"` means that no information is known about the side effects of calling the webhook. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.", + Description: "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.\n\nPossible enum values:\n - `\"None\"` means that calling the webhook will have no side effects.\n - `\"NoneOnDryRun\"` means that calling the webhook will possibly have side effects, but if the request being reviewed has the dry-run attribute, the side effects will be suppressed.\n - `\"Some\"` means that calling the webhook will possibly have side effects. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.\n - `\"Unknown\"` means that no information is known about the side effects of calling the webhook. If a request with the dry-run attribute would trigger a call to this webhook, the request will instead fail.", Type: []string{"string"}, Format: "", Enum: []interface{}{"None", "NoneOnDryRun", "Some", "Unknown"}, @@ -2903,7 +3304,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, "timeoutSeconds": { SchemaProps: spec.SchemaProps{ - Description: "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + Description: "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", Type: []string{"integer"}, Format: "int32", }, @@ -2915,7 +3316,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, }, SchemaProps: spec.SchemaProps{ - Description: "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + Description: "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2940,7 +3341,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2984,7 +3385,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhookConfiguration(re }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -3001,7 +3402,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhookConfiguration(re }, }, SchemaProps: spec.SchemaProps{ - Description: "Webhooks is a list of webhooks and the affected resources and operations.", + Description: "webhooks is a list of webhooks and the affected resources and operations.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3044,7 +3445,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhookConfigurationLis }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -3081,7 +3482,7 @@ func schema_k8sio_api_admissionregistration_v1_Validation(ref common.ReferenceCa Properties: map[string]spec.Schema{ "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + Description: "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -3089,14 +3490,14 @@ func schema_k8sio_api_admissionregistration_v1_Validation(ref common.ReferenceCa }, "message": { SchemaProps: spec.SchemaProps{ - Description: "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + Description: "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", Type: []string{"string"}, Format: "", }, }, "reason": { SchemaProps: spec.SchemaProps{ - Description: "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + Description: "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", Type: []string{"string"}, Format: "", }, @@ -3124,7 +3525,7 @@ func schema_k8sio_api_admissionregistration_v1_Variable(ref common.ReferenceCall Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + Description: "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", Default: "", Type: []string{"string"}, Format: "", @@ -3132,7 +3533,7 @@ func schema_k8sio_api_admissionregistration_v1_Variable(ref common.ReferenceCall }, "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + Description: "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", Default: "", Type: []string{"string"}, Format: "", @@ -3159,20 +3560,20 @@ func schema_k8sio_api_admissionregistration_v1_WebhookClientConfig(ref common.Re Properties: map[string]spec.Schema{ "url": { SchemaProps: spec.SchemaProps{ - Description: "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + Description: "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", Type: []string{"string"}, Format: "", }, }, "service": { SchemaProps: spec.SchemaProps{ - Description: "`service` is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", + Description: "service is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", Ref: ref(v1.ServiceReference{}.OpenAPIModelName()), }, }, "caBundle": { SchemaProps: spec.SchemaProps{ - Description: "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + Description: "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", Type: []string{"string"}, Format: "byte", }, @@ -3244,7 +3645,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ExpressionWarning(ref commo Properties: map[string]spec.Schema{ "fieldRef": { SchemaProps: spec.SchemaProps{ - Description: "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + Description: "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", Default: "", Type: []string{"string"}, Format: "", @@ -3252,7 +3653,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ExpressionWarning(ref commo }, "warning": { SchemaProps: spec.SchemaProps{ - Description: "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + Description: "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", Default: "", Type: []string{"string"}, Format: "", @@ -3293,7 +3694,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MatchCondition(ref common.R Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + Description: "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -3301,7 +3702,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MatchCondition(ref common.R }, "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + Description: "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -3323,13 +3724,13 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MatchResources(ref common.R Properties: map[string]spec.Schema{ "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, @@ -3340,7 +3741,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MatchResources(ref common.R }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", + Description: "resourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3359,7 +3760,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MatchResources(ref common.R }, }, SchemaProps: spec.SchemaProps{ - Description: "ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + Description: "excludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3415,14 +3816,14 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MutatingAdmissionPolicy(ref }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the MutatingAdmissionPolicy.", + Description: "spec defines the desired behavior of the MutatingAdmissionPolicy.", Default: map[string]interface{}{}, Ref: ref(v1alpha1.MutatingAdmissionPolicySpec{}.OpenAPIModelName()), }, @@ -3458,14 +3859,14 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MutatingAdmissionPolicyBind }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the MutatingAdmissionPolicyBinding.", + Description: "spec defines the desired behavior of the MutatingAdmissionPolicyBinding.", Default: map[string]interface{}{}, Ref: ref(v1alpha1.MutatingAdmissionPolicyBindingSpec{}.OpenAPIModelName()), }, @@ -3501,7 +3902,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MutatingAdmissionPolicyBind }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -3586,7 +3987,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_MutatingAdmissionPolicyList }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -3770,7 +4171,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + Description: "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3790,7 +4191,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + Description: "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3811,7 +4212,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + Description: "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3831,7 +4232,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + Description: "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3851,7 +4252,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + Description: "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3892,14 +4293,14 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ParamKind(ref common.Refere Properties: map[string]spec.Schema{ "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + Description: "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", Type: []string{"string"}, Format: "", }, }, "kind": { SchemaProps: spec.SchemaProps{ - Description: "Kind is the API kind the resources belong to. Required.", + Description: "kind is the API kind the resources belong to. Required.", Type: []string{"string"}, Format: "", }, @@ -3924,7 +4325,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ParamRef(ref common.Referen Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", + Description: "name is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", Type: []string{"string"}, Format: "", }, @@ -3944,7 +4345,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ParamRef(ref common.Referen }, "parameterNotFoundAction": { SchemaProps: spec.SchemaProps{ - Description: "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`\n\nPossible enum values:\n - `\"Allow\"` Ignore means that an error finding params for a binding is ignored\n - `\"Deny\"` Fail means that an error finding params for a binding is ignored", + Description: "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`\n\nPossible enum values:\n - `\"Allow\"` Ignore means that an error finding params for a binding is ignored\n - `\"Deny\"` Fail means that an error finding params for a binding is ignored", Type: []string{"string"}, Format: "", Enum: []interface{}{"Allow", "Deny"}, @@ -3977,7 +4378,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_TypeChecking(ref common.Ref }, }, SchemaProps: spec.SchemaProps{ - Description: "The type checking warnings for each expression.", + Description: "expressionWarnings contains the type checking warnings for each expression.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4020,21 +4421,21 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicy(r }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the ValidatingAdmissionPolicy.", + Description: "spec defines the desired behavior of the ValidatingAdmissionPolicy.", Default: map[string]interface{}{}, Ref: ref(v1alpha1.ValidatingAdmissionPolicySpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", + Description: "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", Default: map[string]interface{}{}, Ref: ref(v1alpha1.ValidatingAdmissionPolicyStatus{}.OpenAPIModelName()), }, @@ -4070,19 +4471,20 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.", + Description: "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding.", Default: map[string]interface{}{}, Ref: ref(v1alpha1.ValidatingAdmissionPolicyBindingSpec{}.OpenAPIModelName()), }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -4113,7 +4515,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -4150,7 +4552,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi Properties: map[string]spec.Schema{ "policyName": { SchemaProps: spec.SchemaProps{ - Description: "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + Description: "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", Type: []string{"string"}, Format: "", }, @@ -4163,7 +4565,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi }, "matchResources": { SchemaProps: spec.SchemaProps{ - Description: "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", + Description: "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", Ref: ref(v1alpha1.MatchResources{}.OpenAPIModelName()), }, }, @@ -4189,6 +4591,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi }, }, }, + Required: []string{"policyName", "validationActions"}, }, }, Dependencies: []string{ @@ -4219,7 +4622,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyLi }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -4256,13 +4659,13 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySp Properties: map[string]spec.Schema{ "paramKind": { SchemaProps: spec.SchemaProps{ - Description: "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", + Description: "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", Ref: ref(v1alpha1.ParamKind{}.OpenAPIModelName()), }, }, "matchConstraints": { SchemaProps: spec.SchemaProps{ - Description: "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", + Description: "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", Ref: ref(v1alpha1.MatchResources{}.OpenAPIModelName()), }, }, @@ -4273,7 +4676,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySp }, }, SchemaProps: spec.SchemaProps{ - Description: "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + Description: "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4324,7 +4727,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySp }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4348,7 +4751,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySp }, }, SchemaProps: spec.SchemaProps{ - Description: "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + Description: "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4377,14 +4780,14 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySt Properties: map[string]spec.Schema{ "observedGeneration": { SchemaProps: spec.SchemaProps{ - Description: "The generation observed by the controller.", + Description: "observedGeneration is the generation observed by the controller.", Type: []string{"integer"}, Format: "int64", }, }, "typeChecking": { SchemaProps: spec.SchemaProps{ - Description: "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.", + Description: "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking.", Ref: ref(v1alpha1.TypeChecking{}.OpenAPIModelName()), }, }, @@ -4398,7 +4801,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySt }, }, SchemaProps: spec.SchemaProps{ - Description: "The conditions represent the latest available observations of a policy's current state.", + Description: "conditions represent the latest available observations of a policy's current state.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4427,7 +4830,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref common.Refer Properties: map[string]spec.Schema{ "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + Description: "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -4435,14 +4838,14 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref common.Refer }, "message": { SchemaProps: spec.SchemaProps{ - Description: "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + Description: "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", Type: []string{"string"}, Format: "", }, }, "reason": { SchemaProps: spec.SchemaProps{ - Description: "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + Description: "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", Type: []string{"string"}, Format: "", }, @@ -4470,7 +4873,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Variable(ref common.Referen Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + Description: "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", Default: "", Type: []string{"string"}, Format: "", @@ -4478,7 +4881,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Variable(ref common.Referen }, "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + Description: "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", Default: "", Type: []string{"string"}, Format: "", @@ -4550,7 +4953,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ExpressionWarning(ref common Properties: map[string]spec.Schema{ "fieldRef": { SchemaProps: spec.SchemaProps{ - Description: "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + Description: "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", Default: "", Type: []string{"string"}, Format: "", @@ -4558,7 +4961,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ExpressionWarning(ref common }, "warning": { SchemaProps: spec.SchemaProps{ - Description: "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + Description: "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", Default: "", Type: []string{"string"}, Format: "", @@ -4600,7 +5003,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref common.Re Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + Description: "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -4608,7 +5011,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref common.Re }, "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + Description: "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -4630,13 +5033,13 @@ func schema_k8sio_api_admissionregistration_v1beta1_MatchResources(ref common.Re Properties: map[string]spec.Schema{ "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, @@ -4647,7 +5050,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MatchResources(ref common.Re }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + Description: "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4666,7 +5069,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MatchResources(ref common.Re }, }, SchemaProps: spec.SchemaProps{ - Description: "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + Description: "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4721,14 +5124,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingAdmissionPolicy(ref }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the MutatingAdmissionPolicy.", + Description: "spec defines the desired behavior of the MutatingAdmissionPolicy.", Default: map[string]interface{}{}, Ref: ref(v1beta1.MutatingAdmissionPolicySpec{}.OpenAPIModelName()), }, @@ -4764,14 +5167,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingAdmissionPolicyBindi }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the MutatingAdmissionPolicyBinding.", + Description: "spec defines the desired behavior of the MutatingAdmissionPolicyBinding.", Default: map[string]interface{}{}, Ref: ref(v1beta1.MutatingAdmissionPolicyBindingSpec{}.OpenAPIModelName()), }, @@ -4807,7 +5210,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingAdmissionPolicyBindi }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -4892,7 +5295,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingAdmissionPolicyList( }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -5033,7 +5436,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + Description: "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", Default: "", Type: []string{"string"}, Format: "", @@ -5041,7 +5444,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R }, "clientConfig": { SchemaProps: spec.SchemaProps{ - Description: "ClientConfig defines how to communicate with the hook. Required", + Description: "clientConfig defines how to communicate with the hook. Required", Default: map[string]interface{}{}, Ref: ref(v1beta1.WebhookClientConfig{}.OpenAPIModelName()), }, @@ -5053,7 +5456,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R }, }, SchemaProps: spec.SchemaProps{ - Description: "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + Description: "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5067,7 +5470,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R }, "failurePolicy": { SchemaProps: spec.SchemaProps{ - Description: "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + Description: "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", Type: []string{"string"}, Format: "", }, @@ -5081,26 +5484,26 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R }, "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "sideEffects": { SchemaProps: spec.SchemaProps{ - Description: "SideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", + Description: "sideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", Type: []string{"string"}, Format: "", }, }, "timeoutSeconds": { SchemaProps: spec.SchemaProps{ - Description: "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", + Description: "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", Type: []string{"integer"}, Format: "int32", }, @@ -5112,7 +5515,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R }, }, SchemaProps: spec.SchemaProps{ - Description: "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", + Description: "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5145,7 +5548,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5189,7 +5592,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfiguration }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -5206,7 +5609,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfiguration }, }, SchemaProps: spec.SchemaProps{ - Description: "Webhooks is a list of webhooks and the affected resources and operations.", + Description: "webhooks is a list of webhooks and the affected resources and operations.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5249,7 +5652,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfiguration }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -5328,7 +5731,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + Description: "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5348,7 +5751,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + Description: "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5369,7 +5772,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + Description: "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5389,7 +5792,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + Description: "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5409,7 +5812,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_NamedRuleWithOperations(ref }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + Description: "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5450,14 +5853,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_ParamKind(ref common.Referen Properties: map[string]spec.Schema{ "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + Description: "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", Type: []string{"string"}, Format: "", }, }, "kind": { SchemaProps: spec.SchemaProps{ - Description: "Kind is the API kind the resources belong to. Required.", + Description: "kind is the API kind the resources belong to. Required.", Type: []string{"string"}, Format: "", }, @@ -5502,7 +5905,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ParamRef(ref common.Referenc }, "parameterNotFoundAction": { SchemaProps: spec.SchemaProps{ - Description: "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + Description: "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", Type: []string{"string"}, Format: "", }, @@ -5529,7 +5932,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ServiceReference(ref common. Properties: map[string]spec.Schema{ "namespace": { SchemaProps: spec.SchemaProps{ - Description: "`namespace` is the namespace of the service. Required", + Description: "namespace is the namespace of the service. Required", Default: "", Type: []string{"string"}, Format: "", @@ -5537,7 +5940,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ServiceReference(ref common. }, "name": { SchemaProps: spec.SchemaProps{ - Description: "`name` is the name of the service. Required", + Description: "name is the name of the service. Required", Default: "", Type: []string{"string"}, Format: "", @@ -5545,14 +5948,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_ServiceReference(ref common. }, "path": { SchemaProps: spec.SchemaProps{ - Description: "`path` is an optional URL path which will be sent in any request to this service.", + Description: "path is an optional URL path which will be sent in any request to this service.", Type: []string{"string"}, Format: "", }, }, "port": { SchemaProps: spec.SchemaProps{ - Description: "If specified, the port on the service that hosting webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", + Description: "port is the port on the service that hosts the webhook. Default to 443 for backward compatibility. port should be a valid port number (1-65535, inclusive).", Type: []string{"integer"}, Format: "int32", }, @@ -5578,7 +5981,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_TypeChecking(ref common.Refe }, }, SchemaProps: spec.SchemaProps{ - Description: "The type checking warnings for each expression.", + Description: "expressionWarnings contains the type checking warnings for each expression.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5621,21 +6024,21 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicy(re }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the ValidatingAdmissionPolicy.", + Description: "spec defines the desired behavior of the ValidatingAdmissionPolicy.", Default: map[string]interface{}{}, Ref: ref(v1beta1.ValidatingAdmissionPolicySpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", + Description: "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", Default: map[string]interface{}{}, Ref: ref(v1beta1.ValidatingAdmissionPolicyStatus{}.OpenAPIModelName()), }, @@ -5671,19 +6074,20 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyBin }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.", + Description: "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding.", Default: map[string]interface{}{}, Ref: ref(v1beta1.ValidatingAdmissionPolicyBindingSpec{}.OpenAPIModelName()), }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -5714,7 +6118,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyBin }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -5751,7 +6155,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyBin Properties: map[string]spec.Schema{ "policyName": { SchemaProps: spec.SchemaProps{ - Description: "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + Description: "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", Type: []string{"string"}, Format: "", }, @@ -5764,7 +6168,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyBin }, "matchResources": { SchemaProps: spec.SchemaProps{ - Description: "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", + Description: "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", Ref: ref(v1beta1.MatchResources{}.OpenAPIModelName()), }, }, @@ -5790,6 +6194,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyBin }, }, }, + Required: []string{"policyName", "validationActions"}, }, }, Dependencies: []string{ @@ -5820,7 +6225,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyLis }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -5857,13 +6262,13 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicySpe Properties: map[string]spec.Schema{ "paramKind": { SchemaProps: spec.SchemaProps{ - Description: "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", + Description: "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", Ref: ref(v1beta1.ParamKind{}.OpenAPIModelName()), }, }, "matchConstraints": { SchemaProps: spec.SchemaProps{ - Description: "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", + Description: "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", Ref: ref(v1beta1.MatchResources{}.OpenAPIModelName()), }, }, @@ -5874,7 +6279,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicySpe }, }, SchemaProps: spec.SchemaProps{ - Description: "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + Description: "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5924,7 +6329,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicySpe }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5948,7 +6353,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicySpe }, }, SchemaProps: spec.SchemaProps{ - Description: "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + Description: "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -5977,14 +6382,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicySta Properties: map[string]spec.Schema{ "observedGeneration": { SchemaProps: spec.SchemaProps{ - Description: "The generation observed by the controller.", + Description: "observedGeneration is the generation observed by the controller.", Type: []string{"integer"}, Format: "int64", }, }, "typeChecking": { SchemaProps: spec.SchemaProps{ - Description: "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.", + Description: "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking.", Ref: ref(v1beta1.TypeChecking{}.OpenAPIModelName()), }, }, @@ -5998,7 +6403,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicySta }, }, SchemaProps: spec.SchemaProps{ - Description: "The conditions represent the latest available observations of a policy's current state.", + Description: "conditions represent the latest available observations of a policy's current state.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -6027,7 +6432,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + Description: "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", Default: "", Type: []string{"string"}, Format: "", @@ -6035,7 +6440,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, "clientConfig": { SchemaProps: spec.SchemaProps{ - Description: "ClientConfig defines how to communicate with the hook. Required", + Description: "clientConfig defines how to communicate with the hook. Required", Default: map[string]interface{}{}, Ref: ref(v1beta1.WebhookClientConfig{}.OpenAPIModelName()), }, @@ -6047,7 +6452,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + Description: "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -6061,7 +6466,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, "failurePolicy": { SchemaProps: spec.SchemaProps{ - Description: "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + Description: "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", Type: []string{"string"}, Format: "", }, @@ -6075,13 +6480,13 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, "namespaceSelector": { SchemaProps: spec.SchemaProps{ - Description: "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + Description: "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, "objectSelector": { SchemaProps: spec.SchemaProps{ - Description: "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + Description: "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, @@ -6092,14 +6497,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "SideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", + Description: "sideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", Type: []string{"string"}, Format: "", }, }, "timeoutSeconds": { SchemaProps: spec.SchemaProps{ - Description: "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", + Description: "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", Type: []string{"integer"}, Format: "int32", }, @@ -6111,7 +6516,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", + Description: "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -6136,7 +6541,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + Description: "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -6180,7 +6585,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhookConfigurati }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Description: "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -6197,7 +6602,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhookConfigurati }, }, SchemaProps: spec.SchemaProps{ - Description: "Webhooks is a list of webhooks and the affected resources and operations.", + Description: "webhooks is a list of webhooks and the affected resources and operations.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -6240,7 +6645,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhookConfigurati }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -6277,7 +6682,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_Validation(ref common.Refere Properties: map[string]spec.Schema{ "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + Description: "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", Default: "", Type: []string{"string"}, Format: "", @@ -6285,14 +6690,14 @@ func schema_k8sio_api_admissionregistration_v1beta1_Validation(ref common.Refere }, "message": { SchemaProps: spec.SchemaProps{ - Description: "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + Description: "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", Type: []string{"string"}, Format: "", }, }, "reason": { SchemaProps: spec.SchemaProps{ - Description: "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + Description: "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", Type: []string{"string"}, Format: "", }, @@ -6320,7 +6725,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_Variable(ref common.Referenc Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + Description: "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", Default: "", Type: []string{"string"}, Format: "", @@ -6328,7 +6733,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_Variable(ref common.Referenc }, "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + Description: "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", Default: "", Type: []string{"string"}, Format: "", @@ -6355,20 +6760,20 @@ func schema_k8sio_api_admissionregistration_v1beta1_WebhookClientConfig(ref comm Properties: map[string]spec.Schema{ "url": { SchemaProps: spec.SchemaProps{ - Description: "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + Description: "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", Type: []string{"string"}, Format: "", }, }, "service": { SchemaProps: spec.SchemaProps{ - Description: "`service` is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", + Description: "service is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", Ref: ref(v1beta1.ServiceReference{}.OpenAPIModelName()), }, }, "caBundle": { SchemaProps: spec.SchemaProps{ - Description: "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + Description: "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", Type: []string{"string"}, Format: "byte", }, @@ -6404,7 +6809,7 @@ func schema_k8sio_api_apidiscovery_v2_APIGroupDiscovery(ref common.ReferenceCall }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. The only field completed will be name. For instance, resourceVersion will be empty. name is the name of the API group whose discovery information is presented here. name is allowed to be \"\" to represent the legacy, ungroupified resources. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is standard object's metadata. The only field completed will be name. For instance, resourceVersion will be empty. name is the name of the API group whose discovery information is presented here. name is allowed to be \"\" to represent the legacy, ungroupified resources. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -6768,7 +7173,7 @@ func schema_k8sio_api_apidiscovery_v2beta1_APIGroupDiscovery(ref common.Referenc }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. The only field completed will be name. For instance, resourceVersion will be empty. name is the name of the API group whose discovery information is presented here. name is allowed to be \"\" to represent the legacy, ungroupified resources. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is standard object's metadata. The only field completed will be name. For instance, resourceVersion will be empty. name is the name of the API group whose discovery information is presented here. name is allowed to be \"\" to represent the legacy, ungroupified resources. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -7118,14 +7523,14 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_ServerStorageVersion(ref common Properties: map[string]spec.Schema{ "apiServerID": { SchemaProps: spec.SchemaProps{ - Description: "The ID of the reporting API server.", + Description: "apiServerID is the ID of the reporting API server.", Type: []string{"string"}, Format: "", }, }, "encodingVersion": { SchemaProps: spec.SchemaProps{ - Description: "The API server encodes the object to this version when persisting it in the backend (e.g., etcd).", + Description: "encodingVersion the API server encodes the object to when persisting it in the backend (e.g., etcd).", Type: []string{"string"}, Format: "", }, @@ -7137,7 +7542,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_ServerStorageVersion(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", + Description: "decodableVersions are the encoding versions the API server can handle to decode. The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -7157,7 +7562,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_ServerStorageVersion(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "The API server can serve these versions. DecodableVersions must include all ServedVersions.", + Description: "servedVersions lists all versions the API server can serve. DecodableVersions must include all ServedVersions.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -7171,6 +7576,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_ServerStorageVersion(ref common }, }, }, + Required: []string{"apiServerID", "encodingVersion", "decodableVersions"}, }, }, } @@ -7199,27 +7605,27 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersion(ref common.Refer }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "The name is ..", + Description: "metadata is the standard object metadata. The name is ..", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec is an empty spec. It is here to comply with Kubernetes API style.", + Description: "spec is an empty spec. It is here to comply with Kubernetes API style.", Default: map[string]interface{}{}, Ref: ref(apiserverinternalv1alpha1.StorageVersionSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "API server instances report the version they can decode and the version they encode objects to when persisting objects in the backend.", + Description: "status on the version the API server instance can decode from and encode objects to when persisting objects in the backend.", Default: map[string]interface{}{}, Ref: ref(apiserverinternalv1alpha1.StorageVersionStatus{}.OpenAPIModelName()), }, }, }, - Required: []string{"spec", "status"}, + Required: []string{"metadata"}, }, }, Dependencies: []string{ @@ -7236,7 +7642,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionCondition(ref com Properties: map[string]spec.Schema{ "type": { SchemaProps: spec.SchemaProps{ - Description: "Type of the condition.", + Description: "type of the condition.", Default: "", Type: []string{"string"}, Format: "", @@ -7244,7 +7650,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionCondition(ref com }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status of the condition, one of True, False, Unknown.", + Description: "status of the condition, one of True, False, Unknown.", Default: "", Type: []string{"string"}, Format: "", @@ -7252,20 +7658,20 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionCondition(ref com }, "observedGeneration": { SchemaProps: spec.SchemaProps{ - Description: "If set, this represents the .metadata.generation that the condition was set based upon.", + Description: "observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set.", Type: []string{"integer"}, Format: "int64", }, }, "lastTransitionTime": { SchemaProps: spec.SchemaProps{ - Description: "Last time the condition transitioned from one status to another.", + Description: "lastTransitionTime is the last time the condition transitioned from one status to another.", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, "reason": { SchemaProps: spec.SchemaProps{ - Description: "The reason for the condition's last transition.", + Description: "reason for the condition's last transition.", Default: "", Type: []string{"string"}, Format: "", @@ -7273,7 +7679,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionCondition(ref com }, "message": { SchemaProps: spec.SchemaProps{ - Description: "A human readable message indicating details about the transition.", + Description: "message is a human readable string indicating details about the transition.", Type: []string{"string"}, Format: "", }, @@ -7366,7 +7772,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionStatus(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "The reported versions per API server instance.", + Description: "storageVersions lists the reported versions per API server instance.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -7380,7 +7786,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionStatus(ref common }, "commonEncodingVersion": { SchemaProps: spec.SchemaProps{ - Description: "If all API server instances agree on the same encoding storage version, then this field is set to that version. Otherwise this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", + Description: "commonEncodingVersion is set to an encoding storage version if all API server instances share that same version. If they don't share one storage version, this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", Type: []string{"string"}, Format: "", }, @@ -7395,7 +7801,7 @@ func schema_k8sio_api_apiserverinternal_v1alpha1_StorageVersionStatus(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "The latest available observations of the storageVersion's state.", + Description: "conditions lists the latest available observations of the storageVersion's state.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -11890,28 +12296,28 @@ func schema_k8sio_api_authentication_v1_BoundObjectReference(ref common.Referenc Properties: map[string]spec.Schema{ "kind": { SchemaProps: spec.SchemaProps{ - Description: "Kind of the referent. Valid kinds are 'Pod' and 'Secret'.", + Description: "kind of the referent. Valid kinds are 'Pod' and 'Secret'.", Type: []string{"string"}, Format: "", }, }, "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "API version of the referent.", + Description: "apiVersion is API version of the referent.", Type: []string{"string"}, Format: "", }, }, "name": { SchemaProps: spec.SchemaProps{ - Description: "Name of the referent.", + Description: "name of the referent.", Type: []string{"string"}, Format: "", }, }, "uid": { SchemaProps: spec.SchemaProps{ - Description: "UID of the referent.", + Description: "uid of the referent.", Type: []string{"string"}, Format: "", }, @@ -11945,14 +12351,14 @@ func schema_k8sio_api_authentication_v1_SelfSubjectReview(ref common.ReferenceCa }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server with the user attributes.", + Description: "status is filled in by the server with the user attributes.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.SelfSubjectReviewStatus{}.OpenAPIModelName()), }, @@ -11974,7 +12380,7 @@ func schema_k8sio_api_authentication_v1_SelfSubjectReviewStatus(ref common.Refer Properties: map[string]spec.Schema{ "userInfo": { SchemaProps: spec.SchemaProps{ - Description: "User attributes of the user making this request.", + Description: "userInfo is a set of attributes belonging to the user making this request.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.UserInfo{}.OpenAPIModelName()), }, @@ -12010,27 +12416,26 @@ func schema_k8sio_api_authentication_v1_TokenRequest(ref common.ReferenceCallbac }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated", + Description: "spec holds information about the request being evaluated", Default: map[string]interface{}{}, Ref: ref(authenticationv1.TokenRequestSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the token can be authenticated.", + Description: "status is filled in by the server and indicates whether the token can be authenticated.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.TokenRequestStatus{}.OpenAPIModelName()), }, }, }, - Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -12052,7 +12457,7 @@ func schema_k8sio_api_authentication_v1_TokenRequestSpec(ref common.ReferenceCal }, }, SchemaProps: spec.SchemaProps{ - Description: "Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", + Description: "audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12067,19 +12472,18 @@ func schema_k8sio_api_authentication_v1_TokenRequestSpec(ref common.ReferenceCal }, "expirationSeconds": { SchemaProps: spec.SchemaProps{ - Description: "ExpirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", + Description: "expirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", Type: []string{"integer"}, Format: "int64", }, }, "boundObjectRef": { SchemaProps: spec.SchemaProps{ - Description: "BoundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation.", + Description: "boundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation.", Ref: ref(authenticationv1.BoundObjectReference{}.OpenAPIModelName()), }, }, }, - Required: []string{"audiences"}, }, }, Dependencies: []string{ @@ -12096,7 +12500,7 @@ func schema_k8sio_api_authentication_v1_TokenRequestStatus(ref common.ReferenceC Properties: map[string]spec.Schema{ "token": { SchemaProps: spec.SchemaProps{ - Description: "Token is the opaque bearer token.", + Description: "token is the opaque bearer token.", Default: "", Type: []string{"string"}, Format: "", @@ -12104,12 +12508,11 @@ func schema_k8sio_api_authentication_v1_TokenRequestStatus(ref common.ReferenceC }, "expirationTimestamp": { SchemaProps: spec.SchemaProps{ - Description: "ExpirationTimestamp is the time of expiration of the returned token.", + Description: "expirationTimestamp is the time of expiration of the returned token.", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, }, - Required: []string{"token", "expirationTimestamp"}, }, }, Dependencies: []string{ @@ -12140,21 +12543,21 @@ func schema_k8sio_api_authentication_v1_TokenReview(ref common.ReferenceCallback }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated", + Description: "spec holds information about the request being evaluated", Default: map[string]interface{}{}, Ref: ref(authenticationv1.TokenReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request can be authenticated.", + Description: "status is filled in by the server and indicates whether the request can be authenticated.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.TokenReviewStatus{}.OpenAPIModelName()), }, @@ -12177,7 +12580,7 @@ func schema_k8sio_api_authentication_v1_TokenReviewSpec(ref common.ReferenceCall Properties: map[string]spec.Schema{ "token": { SchemaProps: spec.SchemaProps{ - Description: "Token is the opaque bearer token.", + Description: "token is the opaque bearer token.", Type: []string{"string"}, Format: "", }, @@ -12189,7 +12592,7 @@ func schema_k8sio_api_authentication_v1_TokenReviewSpec(ref common.ReferenceCall }, }, SchemaProps: spec.SchemaProps{ - Description: "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", + Description: "audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12203,6 +12606,7 @@ func schema_k8sio_api_authentication_v1_TokenReviewSpec(ref common.ReferenceCall }, }, }, + Required: []string{"token"}, }, }, } @@ -12217,14 +12621,14 @@ func schema_k8sio_api_authentication_v1_TokenReviewStatus(ref common.ReferenceCa Properties: map[string]spec.Schema{ "authenticated": { SchemaProps: spec.SchemaProps{ - Description: "Authenticated indicates that the token was associated with a known user.", + Description: "authenticated indicates that the token was associated with a known user.", Type: []string{"boolean"}, Format: "", }, }, "user": { SchemaProps: spec.SchemaProps{ - Description: "User is the UserInfo associated with the provided token.", + Description: "user is the UserInfo associated with the provided token.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.UserInfo{}.OpenAPIModelName()), }, @@ -12236,7 +12640,7 @@ func schema_k8sio_api_authentication_v1_TokenReviewStatus(ref common.ReferenceCa }, }, SchemaProps: spec.SchemaProps{ - Description: "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", + Description: "audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12251,7 +12655,7 @@ func schema_k8sio_api_authentication_v1_TokenReviewStatus(ref common.ReferenceCa }, "error": { SchemaProps: spec.SchemaProps{ - Description: "Error indicates that the token couldn't be checked", + Description: "error indicates that the token couldn't be checked", Type: []string{"string"}, Format: "", }, @@ -12273,14 +12677,14 @@ func schema_k8sio_api_authentication_v1_UserInfo(ref common.ReferenceCallback) c Properties: map[string]spec.Schema{ "username": { SchemaProps: spec.SchemaProps{ - Description: "The name that uniquely identifies this user among all active users.", + Description: "username is the name that uniquely identifies this user among all active users.", Type: []string{"string"}, Format: "", }, }, "uid": { SchemaProps: spec.SchemaProps{ - Description: "A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", + Description: "uid is a unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", Type: []string{"string"}, Format: "", }, @@ -12292,7 +12696,7 @@ func schema_k8sio_api_authentication_v1_UserInfo(ref common.ReferenceCallback) c }, }, SchemaProps: spec.SchemaProps{ - Description: "The names of groups this user is a part of.", + Description: "groups is the names of groups this user is a part of.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12307,7 +12711,7 @@ func schema_k8sio_api_authentication_v1_UserInfo(ref common.ReferenceCallback) c }, "extra": { SchemaProps: spec.SchemaProps{ - Description: "Any additional information provided by the authenticator.", + Description: "extra is any additional information provided by the authenticator.", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, @@ -12357,14 +12761,14 @@ func schema_k8sio_api_authentication_v1alpha1_SelfSubjectReview(ref common.Refer }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server with the user attributes.", + Description: "status is filled in by the server with the user attributes.", Default: map[string]interface{}{}, Ref: ref(authenticationv1alpha1.SelfSubjectReviewStatus{}.OpenAPIModelName()), }, @@ -12386,7 +12790,7 @@ func schema_k8sio_api_authentication_v1alpha1_SelfSubjectReviewStatus(ref common Properties: map[string]spec.Schema{ "userInfo": { SchemaProps: spec.SchemaProps{ - Description: "User attributes of the user making this request.", + Description: "userInfo is a set of attributes belonging to the user making this request.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.UserInfo{}.OpenAPIModelName()), }, @@ -12422,14 +12826,14 @@ func schema_k8sio_api_authentication_v1beta1_SelfSubjectReview(ref common.Refere }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server with the user attributes.", + Description: "status is filled in by the server with the user attributes.", Default: map[string]interface{}{}, Ref: ref(authenticationv1beta1.SelfSubjectReviewStatus{}.OpenAPIModelName()), }, @@ -12451,7 +12855,7 @@ func schema_k8sio_api_authentication_v1beta1_SelfSubjectReviewStatus(ref common. Properties: map[string]spec.Schema{ "userInfo": { SchemaProps: spec.SchemaProps{ - Description: "User attributes of the user making this request.", + Description: "userInfo is a set of attributes belonging to the user making this request.", Default: map[string]interface{}{}, Ref: ref(authenticationv1.UserInfo{}.OpenAPIModelName()), }, @@ -12487,21 +12891,21 @@ func schema_k8sio_api_authentication_v1beta1_TokenReview(ref common.ReferenceCal }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated", + Description: "spec holds information about the request being evaluated", Default: map[string]interface{}{}, Ref: ref(authenticationv1beta1.TokenReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the token can be authenticated.", + Description: "status is filled in by the server and indicates whether the token can be authenticated.", Default: map[string]interface{}{}, Ref: ref(authenticationv1beta1.TokenReviewStatus{}.OpenAPIModelName()), }, @@ -12524,7 +12928,7 @@ func schema_k8sio_api_authentication_v1beta1_TokenReviewSpec(ref common.Referenc Properties: map[string]spec.Schema{ "token": { SchemaProps: spec.SchemaProps{ - Description: "Token is the opaque bearer token.", + Description: "token is the opaque bearer token.", Type: []string{"string"}, Format: "", }, @@ -12536,7 +12940,7 @@ func schema_k8sio_api_authentication_v1beta1_TokenReviewSpec(ref common.Referenc }, }, SchemaProps: spec.SchemaProps{ - Description: "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", + Description: "audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12550,6 +12954,7 @@ func schema_k8sio_api_authentication_v1beta1_TokenReviewSpec(ref common.Referenc }, }, }, + Required: []string{"token"}, }, }, } @@ -12564,14 +12969,14 @@ func schema_k8sio_api_authentication_v1beta1_TokenReviewStatus(ref common.Refere Properties: map[string]spec.Schema{ "authenticated": { SchemaProps: spec.SchemaProps{ - Description: "Authenticated indicates that the token was associated with a known user.", + Description: "authenticated indicates that the token was associated with a known user.", Type: []string{"boolean"}, Format: "", }, }, "user": { SchemaProps: spec.SchemaProps{ - Description: "User is the UserInfo associated with the provided token.", + Description: "user is the UserInfo associated with the provided token.", Default: map[string]interface{}{}, Ref: ref(authenticationv1beta1.UserInfo{}.OpenAPIModelName()), }, @@ -12583,7 +12988,7 @@ func schema_k8sio_api_authentication_v1beta1_TokenReviewStatus(ref common.Refere }, }, SchemaProps: spec.SchemaProps{ - Description: "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", + Description: "audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12598,7 +13003,7 @@ func schema_k8sio_api_authentication_v1beta1_TokenReviewStatus(ref common.Refere }, "error": { SchemaProps: spec.SchemaProps{ - Description: "Error indicates that the token couldn't be checked", + Description: "error indicates that the token couldn't be checked", Type: []string{"string"}, Format: "", }, @@ -12620,14 +13025,14 @@ func schema_k8sio_api_authentication_v1beta1_UserInfo(ref common.ReferenceCallba Properties: map[string]spec.Schema{ "username": { SchemaProps: spec.SchemaProps{ - Description: "The name that uniquely identifies this user among all active users.", + Description: "username is the name that uniquely identifies this user among all active users.", Type: []string{"string"}, Format: "", }, }, "uid": { SchemaProps: spec.SchemaProps{ - Description: "A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", + Description: "uid is a unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", Type: []string{"string"}, Format: "", }, @@ -12639,7 +13044,7 @@ func schema_k8sio_api_authentication_v1beta1_UserInfo(ref common.ReferenceCallba }, }, SchemaProps: spec.SchemaProps{ - Description: "The names of groups this user is a part of.", + Description: "groups is the names of groups this user is a part of.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12654,7 +13059,7 @@ func schema_k8sio_api_authentication_v1beta1_UserInfo(ref common.ReferenceCallba }, "extra": { SchemaProps: spec.SchemaProps{ - Description: "Any additional information provided by the authenticator.", + Description: "extra is any additional information provided by the authenticator.", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, @@ -12786,21 +13191,21 @@ func schema_k8sio_api_authorization_v1_LocalSubjectAccessReview(ref common.Refer }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", + Description: "spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SubjectAccessReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request is allowed or not", + Description: "status is filled in by the server and indicates whether the request is allowed or not", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SubjectAccessReviewStatus{}.OpenAPIModelName()), }, @@ -12823,14 +13228,14 @@ func schema_k8sio_api_authorization_v1_NonResourceAttributes(ref common.Referenc Properties: map[string]spec.Schema{ "path": { SchemaProps: spec.SchemaProps{ - Description: "Path is the URL path of the request", + Description: "path is the URL path of the request", Type: []string{"string"}, Format: "", }, }, "verb": { SchemaProps: spec.SchemaProps{ - Description: "Verb is the standard HTTP verb", + Description: "verb is the standard HTTP verb", Type: []string{"string"}, Format: "", }, @@ -12855,7 +13260,7 @@ func schema_k8sio_api_authorization_v1_NonResourceRule(ref common.ReferenceCallb }, }, SchemaProps: spec.SchemaProps{ - Description: "Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", + Description: "verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12875,7 +13280,7 @@ func schema_k8sio_api_authorization_v1_NonResourceRule(ref common.ReferenceCallb }, }, SchemaProps: spec.SchemaProps{ - Description: "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", + Description: "nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -12904,49 +13309,49 @@ func schema_k8sio_api_authorization_v1_ResourceAttributes(ref common.ReferenceCa Properties: map[string]spec.Schema{ "namespace": { SchemaProps: spec.SchemaProps{ - Description: "Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", + Description: "namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", Type: []string{"string"}, Format: "", }, }, "verb": { SchemaProps: spec.SchemaProps{ - Description: "Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + Description: "verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "group": { SchemaProps: spec.SchemaProps{ - Description: "Group is the API Group of the Resource. \"*\" means all.", + Description: "group is the API Group of the Resource. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "version": { SchemaProps: spec.SchemaProps{ - Description: "Version is the API Version of the Resource. \"*\" means all.", + Description: "version is the API Version of the Resource. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "resource": { SchemaProps: spec.SchemaProps{ - Description: "Resource is one of the existing resource types. \"*\" means all.", + Description: "resource is one of the existing resource types. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "subresource": { SchemaProps: spec.SchemaProps{ - Description: "Subresource is one of the existing resource types. \"\" means none.", + Description: "subresource is one of the existing resource types. \"\" means none.", Type: []string{"string"}, Format: "", }, }, "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", + Description: "name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", Type: []string{"string"}, Format: "", }, @@ -12985,7 +13390,7 @@ func schema_k8sio_api_authorization_v1_ResourceRule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + Description: "verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13005,7 +13410,7 @@ func schema_k8sio_api_authorization_v1_ResourceRule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", + Description: "apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13025,7 +13430,7 @@ func schema_k8sio_api_authorization_v1_ResourceRule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", + Description: "resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13045,7 +13450,7 @@ func schema_k8sio_api_authorization_v1_ResourceRule(ref common.ReferenceCallback }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", + Description: "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13088,21 +13493,21 @@ func schema_k8sio_api_authorization_v1_SelfSubjectAccessReview(ref common.Refere }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated. user and groups must be empty", + Description: "spec holds information about the request being evaluated. user and groups must be empty", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SelfSubjectAccessReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request is allowed or not", + Description: "status is filled in by the server and indicates whether the request is allowed or not", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SubjectAccessReviewStatus{}.OpenAPIModelName()), }, @@ -13120,18 +13525,18 @@ func schema_k8sio_api_authorization_v1_SelfSubjectAccessReviewSpec(ref common.Re return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + Description: "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", Type: []string{"object"}, Properties: map[string]spec.Schema{ "resourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "ResourceAuthorizationAttributes describes information for a resource access request", + Description: "resourceAttributes describes information for a resource access request", Ref: ref(authorizationv1.ResourceAttributes{}.OpenAPIModelName()), }, }, "nonResourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "NonResourceAttributes describes information for a non-resource access request", + Description: "nonResourceAttributes describes information for a non-resource access request", Ref: ref(authorizationv1.NonResourceAttributes{}.OpenAPIModelName()), }, }, @@ -13166,21 +13571,21 @@ func schema_k8sio_api_authorization_v1_SelfSubjectRulesReview(ref common.Referen }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated.", + Description: "spec holds information about the request being evaluated.", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SelfSubjectRulesReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates the set of actions a user can perform.", + Description: "status is filled in by the server and indicates the set of actions a user can perform.", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SubjectRulesReviewStatus{}.OpenAPIModelName()), }, @@ -13203,7 +13608,7 @@ func schema_k8sio_api_authorization_v1_SelfSubjectRulesReviewSpec(ref common.Ref Properties: map[string]spec.Schema{ "namespace": { SchemaProps: spec.SchemaProps{ - Description: "Namespace to evaluate rules for. Required.", + Description: "namespace to evaluate rules for. Required.", Type: []string{"string"}, Format: "", }, @@ -13237,21 +13642,21 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReview(ref common.ReferenceC }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated", + Description: "spec holds information about the request being evaluated", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SubjectAccessReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request is allowed or not", + Description: "status is filled in by the server and indicates whether the request is allowed or not", Default: map[string]interface{}{}, Ref: ref(authorizationv1.SubjectAccessReviewStatus{}.OpenAPIModelName()), }, @@ -13269,24 +13674,24 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReviewSpec(ref common.Refere return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + Description: "SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", Type: []string{"object"}, Properties: map[string]spec.Schema{ "resourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "ResourceAuthorizationAttributes describes information for a resource access request", + Description: "resourceAttributes describes information for a resource access request", Ref: ref(authorizationv1.ResourceAttributes{}.OpenAPIModelName()), }, }, "nonResourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "NonResourceAttributes describes information for a non-resource access request", + Description: "nonResourceAttributes describes information for a non-resource access request", Ref: ref(authorizationv1.NonResourceAttributes{}.OpenAPIModelName()), }, }, "user": { SchemaProps: spec.SchemaProps{ - Description: "User is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", + Description: "user is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", Type: []string{"string"}, Format: "", }, @@ -13298,7 +13703,7 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReviewSpec(ref common.Refere }, }, SchemaProps: spec.SchemaProps{ - Description: "Groups is the groups you're testing for.", + Description: "groups is the groups you're testing for.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13313,7 +13718,7 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReviewSpec(ref common.Refere }, "extra": { SchemaProps: spec.SchemaProps{ - Description: "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", + Description: "extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, @@ -13336,7 +13741,7 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReviewSpec(ref common.Refere }, "uid": { SchemaProps: spec.SchemaProps{ - Description: "UID information about the requesting user.", + Description: "uid information about the requesting user.", Type: []string{"string"}, Format: "", }, @@ -13358,7 +13763,7 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReviewStatus(ref common.Refe Properties: map[string]spec.Schema{ "allowed": { SchemaProps: spec.SchemaProps{ - Description: "Allowed is required. True if the action would be allowed, false otherwise.", + Description: "allowed is required. True if the action would be allowed, false otherwise.", Default: false, Type: []string{"boolean"}, Format: "", @@ -13366,21 +13771,21 @@ func schema_k8sio_api_authorization_v1_SubjectAccessReviewStatus(ref common.Refe }, "denied": { SchemaProps: spec.SchemaProps{ - Description: "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", + Description: "denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", Type: []string{"boolean"}, Format: "", }, }, "reason": { SchemaProps: spec.SchemaProps{ - Description: "Reason is optional. It indicates why a request was allowed or denied.", + Description: "reason is optional. It indicates why a request was allowed or denied.", Type: []string{"string"}, Format: "", }, }, "evaluationError": { SchemaProps: spec.SchemaProps{ - Description: "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", + Description: "evaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", Type: []string{"string"}, Format: "", }, @@ -13406,7 +13811,7 @@ func schema_k8sio_api_authorization_v1_SubjectRulesReviewStatus(ref common.Refer }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + Description: "resourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13425,7 +13830,7 @@ func schema_k8sio_api_authorization_v1_SubjectRulesReviewStatus(ref common.Refer }, }, SchemaProps: spec.SchemaProps{ - Description: "NonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + Description: "nonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13439,7 +13844,7 @@ func schema_k8sio_api_authorization_v1_SubjectRulesReviewStatus(ref common.Refer }, "incomplete": { SchemaProps: spec.SchemaProps{ - Description: "Incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", + Description: "incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", Default: false, Type: []string{"boolean"}, Format: "", @@ -13447,7 +13852,7 @@ func schema_k8sio_api_authorization_v1_SubjectRulesReviewStatus(ref common.Refer }, "evaluationError": { SchemaProps: spec.SchemaProps{ - Description: "EvaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", + Description: "evaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", Type: []string{"string"}, Format: "", }, @@ -13484,21 +13889,21 @@ func schema_k8sio_api_authorization_v1beta1_LocalSubjectAccessReview(ref common. }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", + Description: "spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SubjectAccessReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request is allowed or not", + Description: "status is filled in by the server and indicates whether the request is allowed or not", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SubjectAccessReviewStatus{}.OpenAPIModelName()), }, @@ -13521,14 +13926,14 @@ func schema_k8sio_api_authorization_v1beta1_NonResourceAttributes(ref common.Ref Properties: map[string]spec.Schema{ "path": { SchemaProps: spec.SchemaProps{ - Description: "Path is the URL path of the request", + Description: "path is the URL path of the request", Type: []string{"string"}, Format: "", }, }, "verb": { SchemaProps: spec.SchemaProps{ - Description: "Verb is the standard HTTP verb", + Description: "verb is the standard HTTP verb", Type: []string{"string"}, Format: "", }, @@ -13553,7 +13958,7 @@ func schema_k8sio_api_authorization_v1beta1_NonResourceRule(ref common.Reference }, }, SchemaProps: spec.SchemaProps{ - Description: "Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", + Description: "verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13573,7 +13978,7 @@ func schema_k8sio_api_authorization_v1beta1_NonResourceRule(ref common.Reference }, }, SchemaProps: spec.SchemaProps{ - Description: "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", + Description: "nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13602,49 +14007,49 @@ func schema_k8sio_api_authorization_v1beta1_ResourceAttributes(ref common.Refere Properties: map[string]spec.Schema{ "namespace": { SchemaProps: spec.SchemaProps{ - Description: "Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", + Description: "namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", Type: []string{"string"}, Format: "", }, }, "verb": { SchemaProps: spec.SchemaProps{ - Description: "Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + Description: "verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "group": { SchemaProps: spec.SchemaProps{ - Description: "Group is the API Group of the Resource. \"*\" means all.", + Description: "group is the API Group of the Resource. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "version": { SchemaProps: spec.SchemaProps{ - Description: "Version is the API Version of the Resource. \"*\" means all.", + Description: "version is the API Version of the Resource. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "resource": { SchemaProps: spec.SchemaProps{ - Description: "Resource is one of the existing resource types. \"*\" means all.", + Description: "resource is one of the existing resource types. \"*\" means all.", Type: []string{"string"}, Format: "", }, }, "subresource": { SchemaProps: spec.SchemaProps{ - Description: "Subresource is one of the existing resource types. \"\" means none.", + Description: "subresource is one of the existing resource types. \"\" means none.", Type: []string{"string"}, Format: "", }, }, "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", + Description: "name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", Type: []string{"string"}, Format: "", }, @@ -13683,7 +14088,7 @@ func schema_k8sio_api_authorization_v1beta1_ResourceRule(ref common.ReferenceCal }, }, SchemaProps: spec.SchemaProps{ - Description: "Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + Description: "verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13703,7 +14108,7 @@ func schema_k8sio_api_authorization_v1beta1_ResourceRule(ref common.ReferenceCal }, }, SchemaProps: spec.SchemaProps{ - Description: "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", + Description: "apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13723,7 +14128,7 @@ func schema_k8sio_api_authorization_v1beta1_ResourceRule(ref common.ReferenceCal }, }, SchemaProps: spec.SchemaProps{ - Description: "Resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", + Description: "resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13743,7 +14148,7 @@ func schema_k8sio_api_authorization_v1beta1_ResourceRule(ref common.ReferenceCal }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", + Description: "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -13786,21 +14191,21 @@ func schema_k8sio_api_authorization_v1beta1_SelfSubjectAccessReview(ref common.R }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated. user and groups must be empty", + Description: "spec holds information about the request being evaluated. user and groups must be empty", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SelfSubjectAccessReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request is allowed or not", + Description: "status is filled in by the server and indicates whether the request is allowed or not", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SubjectAccessReviewStatus{}.OpenAPIModelName()), }, @@ -13818,18 +14223,18 @@ func schema_k8sio_api_authorization_v1beta1_SelfSubjectAccessReviewSpec(ref comm return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + Description: "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", Type: []string{"object"}, Properties: map[string]spec.Schema{ "resourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "ResourceAuthorizationAttributes describes information for a resource access request", + Description: "resourceAttributes describes information for a resource access request", Ref: ref(authorizationv1beta1.ResourceAttributes{}.OpenAPIModelName()), }, }, "nonResourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "NonResourceAttributes describes information for a non-resource access request", + Description: "nonResourceAttributes describes information for a non-resource access request", Ref: ref(authorizationv1beta1.NonResourceAttributes{}.OpenAPIModelName()), }, }, @@ -13864,21 +14269,21 @@ func schema_k8sio_api_authorization_v1beta1_SelfSubjectRulesReview(ref common.Re }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated.", + Description: "spec holds information about the request being evaluated.", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SelfSubjectRulesReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates the set of actions a user can perform.", + Description: "status is filled in by the server and indicates the set of actions a user can perform.", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SubjectRulesReviewStatus{}.OpenAPIModelName()), }, @@ -13901,7 +14306,7 @@ func schema_k8sio_api_authorization_v1beta1_SelfSubjectRulesReviewSpec(ref commo Properties: map[string]spec.Schema{ "namespace": { SchemaProps: spec.SchemaProps{ - Description: "Namespace to evaluate rules for. Required.", + Description: "namespace to evaluate rules for. Required.", Type: []string{"string"}, Format: "", }, @@ -13935,21 +14340,21 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReview(ref common.Refer }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Spec holds information about the request being evaluated", + Description: "spec holds information about the request being evaluated", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SubjectAccessReviewSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status is filled in by the server and indicates whether the request is allowed or not", + Description: "status is filled in by the server and indicates whether the request is allowed or not", Default: map[string]interface{}{}, Ref: ref(authorizationv1beta1.SubjectAccessReviewStatus{}.OpenAPIModelName()), }, @@ -13967,24 +14372,24 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReviewSpec(ref common.R return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + Description: "SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", Type: []string{"object"}, Properties: map[string]spec.Schema{ "resourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "ResourceAuthorizationAttributes describes information for a resource access request", + Description: "resourceAttributes describes information for a resource access request", Ref: ref(authorizationv1beta1.ResourceAttributes{}.OpenAPIModelName()), }, }, "nonResourceAttributes": { SchemaProps: spec.SchemaProps{ - Description: "NonResourceAttributes describes information for a non-resource access request", + Description: "nonResourceAttributes describes information for a non-resource access request", Ref: ref(authorizationv1beta1.NonResourceAttributes{}.OpenAPIModelName()), }, }, "user": { SchemaProps: spec.SchemaProps{ - Description: "User is the user you're testing for. If you specify \"User\" but not \"Group\", then is it interpreted as \"What if User were not a member of any groups", + Description: "user is the user you're testing for. If you specify \"User\" but not \"Group\", then is it interpreted as \"What if User were not a member of any groups", Type: []string{"string"}, Format: "", }, @@ -13996,7 +14401,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReviewSpec(ref common.R }, }, SchemaProps: spec.SchemaProps{ - Description: "Groups is the groups you're testing for.", + Description: "group is the groups you're testing for.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -14011,7 +14416,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReviewSpec(ref common.R }, "extra": { SchemaProps: spec.SchemaProps{ - Description: "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", + Description: "extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, @@ -14034,7 +14439,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReviewSpec(ref common.R }, "uid": { SchemaProps: spec.SchemaProps{ - Description: "UID information about the requesting user.", + Description: "uid information about the requesting user.", Type: []string{"string"}, Format: "", }, @@ -14056,7 +14461,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReviewStatus(ref common Properties: map[string]spec.Schema{ "allowed": { SchemaProps: spec.SchemaProps{ - Description: "Allowed is required. True if the action would be allowed, false otherwise.", + Description: "allowed is required. True if the action would be allowed, false otherwise.", Default: false, Type: []string{"boolean"}, Format: "", @@ -14064,21 +14469,21 @@ func schema_k8sio_api_authorization_v1beta1_SubjectAccessReviewStatus(ref common }, "denied": { SchemaProps: spec.SchemaProps{ - Description: "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", + Description: "denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", Type: []string{"boolean"}, Format: "", }, }, "reason": { SchemaProps: spec.SchemaProps{ - Description: "Reason is optional. It indicates why a request was allowed or denied.", + Description: "reason is optional. It indicates why a request was allowed or denied.", Type: []string{"string"}, Format: "", }, }, "evaluationError": { SchemaProps: spec.SchemaProps{ - Description: "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", + Description: "evaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", Type: []string{"string"}, Format: "", }, @@ -14104,7 +14509,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectRulesReviewStatus(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "ResourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + Description: "resourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -14123,7 +14528,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectRulesReviewStatus(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "NonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + Description: "nonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -14137,7 +14542,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectRulesReviewStatus(ref common. }, "incomplete": { SchemaProps: spec.SchemaProps{ - Description: "Incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", + Description: "incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", Default: false, Type: []string{"boolean"}, Format: "", @@ -14145,7 +14550,7 @@ func schema_k8sio_api_authorization_v1beta1_SubjectRulesReviewStatus(ref common. }, "evaluationError": { SchemaProps: spec.SchemaProps{ - Description: "EvaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", + Description: "evaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", Type: []string{"string"}, Format: "", }, @@ -14418,6 +14823,7 @@ func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscaler(ref common.Referenc }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -14477,1934 +14883,11 @@ func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerCondition(ref common } } -func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "list of horizontal pod autoscaler objects.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata.", - Default: map[string]interface{}{}, - Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Description: "items is the list of horizontal pod autoscaler objects.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv1.HorizontalPodAutoscaler{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - autoscalingv1.HorizontalPodAutoscaler{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "specification of a horizontal pod autoscaler.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "scaleTargetRef": { - SchemaProps: spec.SchemaProps{ - Description: "reference to scaled resource; horizontal pod autoscaler will learn the current resource consumption and will set the desired number of pods by using its Scale subresource.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()), - }, - }, - "minReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "minReplicas is the lower limit for the number of replicas to which the autoscaler can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the alpha feature gate HPAScaleToZero is enabled and at least one Object or External metric is configured. Scaling is active as long as at least one metric value is available.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "maxReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "maxReplicas is the upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "targetCPUUtilizationPercentage": { - SchemaProps: spec.SchemaProps{ - Description: "targetCPUUtilizationPercentage is the target average CPU utilization (represented as a percentage of requested CPU) over all the pods; if not specified the default autoscaling policy will be used.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - Required: []string{"scaleTargetRef", "maxReplicas"}, - }, - }, - Dependencies: []string{ - autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "current status of a horizontal pod autoscaler", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "observedGeneration": { - SchemaProps: spec.SchemaProps{ - Description: "observedGeneration is the most recent generation observed by this autoscaler.", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "lastScaleTime": { - SchemaProps: spec.SchemaProps{ - Description: "lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods; used by the autoscaler to control how often the number of pods is changed.", - Ref: ref(metav1.Time{}.OpenAPIModelName()), - }, - }, - "currentReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "currentReplicas is the current number of replicas of pods managed by this autoscaler.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "desiredReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "desiredReplicas is the desired number of replicas of pods managed by this autoscaler.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "currentCPUUtilizationPercentage": { - SchemaProps: spec.SchemaProps{ - Description: "currentCPUUtilizationPercentage is the current average CPU utilization over all pods, represented as a percentage of requested CPU, e.g. 70 means that an average pod is using now 70% of its requested CPU.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - Required: []string{"currentReplicas", "desiredReplicas"}, - }, - }, - Dependencies: []string{ - metav1.Time{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_MetricSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricSpec specifies how to scale based on a single metric (only `type` and one other matching field should be set at once).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type is the type of metric source. It should be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.\n\nPossible enum values:\n - `\"ContainerResource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).\n - `\"External\"` is a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).\n - `\"Object\"` is a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).\n - `\"Pods\"` is a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.\n - `\"Resource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).", - Default: "", - Type: []string{"string"}, - Format: "", - Enum: []interface{}{"ContainerResource", "External", "Object", "Pods", "Resource"}, - }, - }, - "object": { - SchemaProps: spec.SchemaProps{ - Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(autoscalingv1.ObjectMetricSource{}.OpenAPIModelName()), - }, - }, - "pods": { - SchemaProps: spec.SchemaProps{ - Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(autoscalingv1.PodsMetricSource{}.OpenAPIModelName()), - }, - }, - "resource": { - SchemaProps: spec.SchemaProps{ - Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv1.ResourceMetricSource{}.OpenAPIModelName()), - }, - }, - "containerResource": { - SchemaProps: spec.SchemaProps{ - Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv1.ContainerResourceMetricSource{}.OpenAPIModelName()), - }, - }, - "external": { - SchemaProps: spec.SchemaProps{ - Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(autoscalingv1.ExternalMetricSource{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"type"}, - }, - }, - Dependencies: []string{ - autoscalingv1.ContainerResourceMetricSource{}.OpenAPIModelName(), autoscalingv1.ExternalMetricSource{}.OpenAPIModelName(), autoscalingv1.ObjectMetricSource{}.OpenAPIModelName(), autoscalingv1.PodsMetricSource{}.OpenAPIModelName(), autoscalingv1.ResourceMetricSource{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_MetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricStatus describes the last-read state of a single metric.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type is the type of metric source. It will be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.\n\nPossible enum values:\n - `\"ContainerResource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).\n - `\"External\"` is a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).\n - `\"Object\"` is a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).\n - `\"Pods\"` is a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.\n - `\"Resource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).", - Default: "", - Type: []string{"string"}, - Format: "", - Enum: []interface{}{"ContainerResource", "External", "Object", "Pods", "Resource"}, - }, - }, - "object": { - SchemaProps: spec.SchemaProps{ - Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(autoscalingv1.ObjectMetricStatus{}.OpenAPIModelName()), - }, - }, - "pods": { - SchemaProps: spec.SchemaProps{ - Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(autoscalingv1.PodsMetricStatus{}.OpenAPIModelName()), - }, - }, - "resource": { - SchemaProps: spec.SchemaProps{ - Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv1.ResourceMetricStatus{}.OpenAPIModelName()), - }, - }, - "containerResource": { - SchemaProps: spec.SchemaProps{ - Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv1.ContainerResourceMetricStatus{}.OpenAPIModelName()), - }, - }, - "external": { - SchemaProps: spec.SchemaProps{ - Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(autoscalingv1.ExternalMetricStatus{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"type"}, - }, - }, - Dependencies: []string{ - autoscalingv1.ContainerResourceMetricStatus{}.OpenAPIModelName(), autoscalingv1.ExternalMetricStatus{}.OpenAPIModelName(), autoscalingv1.ObjectMetricStatus{}.OpenAPIModelName(), autoscalingv1.PodsMetricStatus{}.OpenAPIModelName(), autoscalingv1.ResourceMetricStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_ObjectMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ObjectMetricSource indicates how to scale on a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target is the described Kubernetes object.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()), - }, - }, - "metricName": { - SchemaProps: spec.SchemaProps{ - Description: "metricName is the name of the metric in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "targetValue": { - SchemaProps: spec.SchemaProps{ - Description: "targetValue is the target value of the metric (as a quantity).", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric. When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping When unset, just the metricName will be used to gather metrics.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - "averageValue": { - SchemaProps: spec.SchemaProps{ - Description: "averageValue is the target value of the average of the metric across all relevant pods (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"target", "metricName", "targetValue"}, - }, - }, - Dependencies: []string{ - autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_ObjectMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ObjectMetricStatus indicates the current value of a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target is the described Kubernetes object.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()), - }, - }, - "metricName": { - SchemaProps: spec.SchemaProps{ - Description: "metricName is the name of the metric in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "currentValue": { - SchemaProps: spec.SchemaProps{ - Description: "currentValue is the current value of the metric (as a quantity).", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set in the ObjectMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. When unset, just the metricName will be used to gather metrics.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - "averageValue": { - SchemaProps: spec.SchemaProps{ - Description: "averageValue is the current value of the average of the metric across all relevant pods (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"target", "metricName", "currentValue"}, - }, - }, - Dependencies: []string{ - autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_PodsMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "PodsMetricSource indicates how to scale on a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metricName": { - SchemaProps: spec.SchemaProps{ - Description: "metricName is the name of the metric in question", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "targetAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "targetAverageValue is the target value of the average of the metric across all relevant pods (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping When unset, just the metricName will be used to gather metrics.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metricName", "targetAverageValue"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_PodsMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "PodsMetricStatus indicates the current value of a metric describing each pod in the current scale target (for example, transactions-processed-per-second).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metricName": { - SchemaProps: spec.SchemaProps{ - Description: "metricName is the name of the metric in question", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "currentAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageValue is the current value of the average of the metric across all relevant pods (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set in the PodsMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. When unset, just the metricName will be used to gather metrics.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metricName", "currentAverageValue"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_ResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "targetAverageUtilization": { - SchemaProps: spec.SchemaProps{ - Description: "targetAverageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "targetAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "targetAverageValue is the target value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"name"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_ResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "currentAverageUtilization": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. It will only be present if `targetAverageValue` was set in the corresponding metric specification.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "currentAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageValue is the current value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type. It will always be set, regardless of the corresponding metric specification.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"name", "currentAverageValue"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_Scale(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Scale represents a scaling request for a resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - Default: map[string]interface{}{}, - Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "spec defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv1.ScaleSpec{}.OpenAPIModelName()), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Description: "status is the current status of the scale. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. Read-only.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv1.ScaleStatus{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - Dependencies: []string{ - autoscalingv1.ScaleSpec{}.OpenAPIModelName(), autoscalingv1.ScaleStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v1_ScaleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ScaleSpec describes the attributes of a scale subresource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "replicas": { - SchemaProps: spec.SchemaProps{ - Description: "replicas is the desired number of instances for the scaled object.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - }, - }, - } -} - -func schema_k8sio_api_autoscaling_v1_ScaleStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ScaleStatus represents the current status of a scale subresource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "replicas": { - SchemaProps: spec.SchemaProps{ - Description: "replicas is the actual number of observed instances of the scaled object.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "selector is the label query over pods that should match the replicas count. This is same as the label selector but in the string format to avoid introspection by clients. The string will be in the same format as the query-param syntax. More info about label selectors: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"replicas"}, - }, - }, - } -} - -func schema_k8sio_api_autoscaling_v2_ContainerResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ContainerResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target specifies the target value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), - }, - }, - "container": { - SchemaProps: spec.SchemaProps{ - Description: "container is the name of the container in the pods of the scaling target", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"name", "target", "container"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricTarget{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_ContainerResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ContainerResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "current": { - SchemaProps: spec.SchemaProps{ - Description: "current contains the current value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), - }, - }, - "container": { - SchemaProps: spec.SchemaProps{ - Description: "container is the name of the container in the pods of the scaling target", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"name", "current", "container"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_CrossVersionObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "CrossVersionObjectReference contains enough information to let you identify the referred resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "apiVersion is the API version of the referent", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"kind", "name"}, - }, - }, - } -} - -func schema_k8sio_api_autoscaling_v2_ExternalMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ExternalMetricSource indicates how to scale on a metric not associated with any Kubernetes object (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metric": { - SchemaProps: spec.SchemaProps{ - Description: "metric identifies the target metric by name and selector", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), - }, - }, - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target specifies the target value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metric", "target"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricTarget{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_ExternalMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ExternalMetricStatus indicates the current value of a global metric not associated with any Kubernetes object.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metric": { - SchemaProps: spec.SchemaProps{ - Description: "metric identifies the target metric by name and selector", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), - }, - }, - "current": { - SchemaProps: spec.SchemaProps{ - Description: "current contains the current value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metric", "current"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HPAScalingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HPAScalingPolicy is a single policy which must hold true for a specified past interval.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type is used to specify the scaling policy.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "value": { - SchemaProps: spec.SchemaProps{ - Description: "value contains the amount of change which is permitted by the policy. It must be greater than zero", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "periodSeconds": { - SchemaProps: spec.SchemaProps{ - Description: "periodSeconds specifies the window of time for which the policy should hold true. PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - Required: []string{"type", "value", "periodSeconds"}, - }, - }, - } -} - -func schema_k8sio_api_autoscaling_v2_HPAScalingRules(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HPAScalingRules configures the scaling behavior for one direction via scaling Policy Rules and a configurable metric tolerance.\n\nScaling Policy Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.\n\nThe tolerance is applied to the metric values and prevents scaling too eagerly for small metric variations. (Note that setting a tolerance requires the beta HPAConfigurableTolerance feature gate to be enabled.)", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "stabilizationWindowSeconds": { - SchemaProps: spec.SchemaProps{ - Description: "stabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "selectPolicy": { - SchemaProps: spec.SchemaProps{ - Description: "selectPolicy is used to specify which policy should be used. If not set, the default value Max is used.", - Type: []string{"string"}, - Format: "", - }, - }, - "policies": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "policies is a list of potential scaling polices which can be used during scaling. If not set, use the default values: - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. - For scale down: allow all pods to be removed in a 15s window.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.HPAScalingPolicy{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - "tolerance": { - SchemaProps: spec.SchemaProps{ - Description: "tolerance is the tolerance on the ratio between the current and desired metric value under which no updates are made to the desired number of replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not set, the default cluster-wide tolerance is applied (by default 10%).\n\nFor example, if autoscaling is configured with a memory consumption target of 100Mi, and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be triggered when the actual consumption falls below 95Mi or exceeds 101Mi.\n\nThis is an beta field and requires the HPAConfigurableTolerance feature gate to be enabled.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - Dependencies: []string{ - autoscalingv2.HPAScalingPolicy{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscaler(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscaler is the configuration for a horizontal pod autoscaler, which automatically manages the replica count of any resource implementing the scale subresource based on the metrics specified.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Description: "metadata is the standard object metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - Default: map[string]interface{}{}, - Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "spec is the specification for the behaviour of the autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName()), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Description: "status is the current information about the autoscaler.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - Dependencies: []string{ - autoscalingv2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName(), autoscalingv2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerBehavior(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "scaleUp": { - SchemaProps: spec.SchemaProps{ - Description: "scaleUp is scaling policy for scaling Up. If not set, the default value is the higher of:\n * increase no more than 4 pods per 60 seconds\n * double the number of pods per 60 seconds\nNo stabilization is used.", - Ref: ref(autoscalingv2.HPAScalingRules{}.OpenAPIModelName()), - }, - }, - "scaleDown": { - SchemaProps: spec.SchemaProps{ - Description: "scaleDown is scaling policy for scaling Down. If not set, the default value is to allow to scale down to minReplicas pods, with a 300 second stabilization window (i.e., the highest recommendation for the last 300sec is used).", - Ref: ref(autoscalingv2.HPAScalingRules{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - Dependencies: []string{ - autoscalingv2.HPAScalingRules{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerCondition describes the state of a HorizontalPodAutoscaler at a certain point.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type describes the current condition", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Description: "status is the status of the condition (True, False, Unknown)", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "lastTransitionTime": { - SchemaProps: spec.SchemaProps{ - Description: "lastTransitionTime is the last time the condition transitioned from one status to another", - Ref: ref(metav1.Time{}.OpenAPIModelName()), - }, - }, - "reason": { - SchemaProps: spec.SchemaProps{ - Description: "reason is the reason for the condition's last transition.", - Type: []string{"string"}, - Format: "", - }, - }, - "message": { - SchemaProps: spec.SchemaProps{ - Description: "message is a human-readable explanation containing details about the transition", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"type", "status"}, - }, - }, - Dependencies: []string{ - metav1.Time{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerList is a list of horizontal pod autoscaler objects.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Description: "metadata is the standard list metadata.", - Default: map[string]interface{}{}, - Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Description: "items is the list of horizontal pod autoscaler objects.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.HorizontalPodAutoscaler{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - autoscalingv2.HorizontalPodAutoscaler{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "scaleTargetRef": { - SchemaProps: spec.SchemaProps{ - Description: "scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics should be collected, as well as to actually change the replica count.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName()), - }, - }, - "minReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "minReplicas is the lower limit for the number of replicas to which the autoscaler can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the alpha feature gate HPAScaleToZero is enabled and at least one Object or External metric is configured. Scaling is active as long as at least one metric value is available.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "maxReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. It cannot be less that minReplicas.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "metrics": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "metrics contains the specifications for which to use to calculate the desired replica count (the maximum replica count across all metrics will be used). The desired replica count is calculated multiplying the ratio between the target value and the current value by the current number of pods. Ergo, metrics used must decrease as the pod count is increased, and vice-versa. See the individual metric source types for more information about how each type of metric must respond. If not set, the default metric will be set to 80% average CPU utilization.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricSpec{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - "behavior": { - SchemaProps: spec.SchemaProps{ - Description: "behavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). If not set, the default HPAScalingRules for scale up and scale down are used.", - Ref: ref(autoscalingv2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"scaleTargetRef", "maxReplicas"}, - }, - }, - Dependencies: []string{ - autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName(), autoscalingv2.MetricSpec{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "observedGeneration": { - SchemaProps: spec.SchemaProps{ - Description: "observedGeneration is the most recent generation observed by this autoscaler.", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "lastScaleTime": { - SchemaProps: spec.SchemaProps{ - Description: "lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, used by the autoscaler to control how often the number of pods is changed.", - Ref: ref(metav1.Time{}.OpenAPIModelName()), - }, - }, - "currentReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "currentReplicas is current number of replicas of pods managed by this autoscaler, as last seen by the autoscaler.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "desiredReplicas": { - SchemaProps: spec.SchemaProps{ - Description: "desiredReplicas is the desired number of replicas of pods managed by this autoscaler, as last calculated by the autoscaler.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - "currentMetrics": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "currentMetrics is the last read state of the metrics used by this autoscaler.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricStatus{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - "conditions": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ - "type", - }, - "x-kubernetes-list-type": "map", - "x-kubernetes-patch-merge-key": "type", - "x-kubernetes-patch-strategy": "merge", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "conditions is the set of conditions required for this autoscaler to scale its target, and indicates whether or not those conditions are met.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - }, - Required: []string{"desiredReplicas"}, - }, - }, - Dependencies: []string{ - autoscalingv2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName(), autoscalingv2.MetricStatus{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_MetricIdentifier(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricIdentifier defines the name and optionally selector for a metric", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the given metric", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. When unset, just the metricName will be used to gather metrics.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"name"}, - }, - }, - Dependencies: []string{ - metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_MetricSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricSpec specifies how to scale based on a single metric (only `type` and one other matching field should be set at once).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type is the type of metric source. It should be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "object": { - SchemaProps: spec.SchemaProps{ - Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(autoscalingv2.ObjectMetricSource{}.OpenAPIModelName()), - }, - }, - "pods": { - SchemaProps: spec.SchemaProps{ - Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(autoscalingv2.PodsMetricSource{}.OpenAPIModelName()), - }, - }, - "resource": { - SchemaProps: spec.SchemaProps{ - Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2.ResourceMetricSource{}.OpenAPIModelName()), - }, - }, - "containerResource": { - SchemaProps: spec.SchemaProps{ - Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2.ContainerResourceMetricSource{}.OpenAPIModelName()), - }, - }, - "external": { - SchemaProps: spec.SchemaProps{ - Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(autoscalingv2.ExternalMetricSource{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"type"}, - }, - }, - Dependencies: []string{ - autoscalingv2.ContainerResourceMetricSource{}.OpenAPIModelName(), autoscalingv2.ExternalMetricSource{}.OpenAPIModelName(), autoscalingv2.ObjectMetricSource{}.OpenAPIModelName(), autoscalingv2.PodsMetricSource{}.OpenAPIModelName(), autoscalingv2.ResourceMetricSource{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_MetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricStatus describes the last-read state of a single metric.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type is the type of metric source. It will be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "object": { - SchemaProps: spec.SchemaProps{ - Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(autoscalingv2.ObjectMetricStatus{}.OpenAPIModelName()), - }, - }, - "pods": { - SchemaProps: spec.SchemaProps{ - Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(autoscalingv2.PodsMetricStatus{}.OpenAPIModelName()), - }, - }, - "resource": { - SchemaProps: spec.SchemaProps{ - Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2.ResourceMetricStatus{}.OpenAPIModelName()), - }, - }, - "containerResource": { - SchemaProps: spec.SchemaProps{ - Description: "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2.ContainerResourceMetricStatus{}.OpenAPIModelName()), - }, - }, - "external": { - SchemaProps: spec.SchemaProps{ - Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(autoscalingv2.ExternalMetricStatus{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"type"}, - }, - }, - Dependencies: []string{ - autoscalingv2.ContainerResourceMetricStatus{}.OpenAPIModelName(), autoscalingv2.ExternalMetricStatus{}.OpenAPIModelName(), autoscalingv2.ObjectMetricStatus{}.OpenAPIModelName(), autoscalingv2.PodsMetricStatus{}.OpenAPIModelName(), autoscalingv2.ResourceMetricStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_MetricTarget(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricTarget defines the target value, average value, or average utilization of a specific metric", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type represents whether the metric type is Utilization, Value, or AverageValue", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "value": { - SchemaProps: spec.SchemaProps{ - Description: "value is the target value of the metric (as a quantity).", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "averageValue": { - SchemaProps: spec.SchemaProps{ - Description: "averageValue is the target value of the average of the metric across all relevant pods (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "averageUtilization": { - SchemaProps: spec.SchemaProps{ - Description: "averageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. Currently only valid for Resource metric source type", - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - Required: []string{"type"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_MetricValueStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "MetricValueStatus holds the current value for a metric", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "value": { - SchemaProps: spec.SchemaProps{ - Description: "value is the current value of the metric (as a quantity).", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "averageValue": { - SchemaProps: spec.SchemaProps{ - Description: "averageValue is the current value of the average of the metric across all relevant pods (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "averageUtilization": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_ObjectMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ObjectMetricSource indicates how to scale on a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "describedObject": { - SchemaProps: spec.SchemaProps{ - Description: "describedObject specifies the descriptions of a object,such as kind,name apiVersion", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName()), - }, - }, - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target specifies the target value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), - }, - }, - "metric": { - SchemaProps: spec.SchemaProps{ - Description: "metric identifies the target metric by name and selector", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"describedObject", "target", "metric"}, - }, - }, - Dependencies: []string{ - autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricTarget{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_ObjectMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ObjectMetricStatus indicates the current value of a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metric": { - SchemaProps: spec.SchemaProps{ - Description: "metric identifies the target metric by name and selector", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), - }, - }, - "current": { - SchemaProps: spec.SchemaProps{ - Description: "current contains the current value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), - }, - }, - "describedObject": { - SchemaProps: spec.SchemaProps{ - Description: "DescribedObject specifies the descriptions of a object,such as kind,name apiVersion", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metric", "current", "describedObject"}, - }, - }, - Dependencies: []string{ - autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_PodsMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "PodsMetricSource indicates how to scale on a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metric": { - SchemaProps: spec.SchemaProps{ - Description: "metric identifies the target metric by name and selector", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), - }, - }, - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target specifies the target value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metric", "target"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricTarget{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_PodsMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "PodsMetricStatus indicates the current value of a metric describing each pod in the current scale target (for example, transactions-processed-per-second).", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metric": { - SchemaProps: spec.SchemaProps{ - Description: "metric identifies the target metric by name and selector", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), - }, - }, - "current": { - SchemaProps: spec.SchemaProps{ - Description: "current contains the current value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metric", "current"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_ResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "target": { - SchemaProps: spec.SchemaProps{ - Description: "target specifies the target value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"name", "target"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricTarget{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2_ResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "current": { - SchemaProps: spec.SchemaProps{ - Description: "current contains the current value for the given metric", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"name", "current"}, - }, - }, - Dependencies: []string{ - autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_ContainerResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ContainerResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "targetAverageUtilization": { - SchemaProps: spec.SchemaProps{ - Description: "targetAverageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "targetAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "targetAverageValue is the target value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "container": { - SchemaProps: spec.SchemaProps{ - Description: "container is the name of the container in the pods of the scaling target", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"name", "container"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_ContainerResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ContainerResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "name is the name of the resource in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "currentAverageUtilization": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. It will only be present if `targetAverageValue` was set in the corresponding metric specification.", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "currentAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageValue is the current value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type. It will always be set, regardless of the corresponding metric specification.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "container": { - SchemaProps: spec.SchemaProps{ - Description: "container is the name of the container in the pods of the scaling target", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"name", "currentAverageValue", "container"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_CrossVersionObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "CrossVersionObjectReference contains enough information to let you identify the referred resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "name": { - SchemaProps: spec.SchemaProps{ - Description: "Name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "API version of the referent", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"kind", "name"}, - }, - }, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_ExternalMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ExternalMetricSource indicates how to scale on a metric not associated with any Kubernetes object (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster). Exactly one \"target\" type should be set.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metricName": { - SchemaProps: spec.SchemaProps{ - Description: "metricName is the name of the metric in question.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "metricSelector": { - SchemaProps: spec.SchemaProps{ - Description: "metricSelector is used to identify a specific time series within a given metric.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - "targetValue": { - SchemaProps: spec.SchemaProps{ - Description: "targetValue is the target value of the metric (as a quantity). Mutually exclusive with TargetAverageValue.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "targetAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "targetAverageValue is the target per-pod value of global metric (as a quantity). Mutually exclusive with TargetValue.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metricName"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_ExternalMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ExternalMetricStatus indicates the current value of a global metric not associated with any Kubernetes object.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "metricName": { - SchemaProps: spec.SchemaProps{ - Description: "metricName is the name of a metric used for autoscaling in metric system.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "metricSelector": { - SchemaProps: spec.SchemaProps{ - Description: "metricSelector is used to identify a specific time series within a given metric.", - Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), - }, - }, - "currentValue": { - SchemaProps: spec.SchemaProps{ - Description: "currentValue is the current value of the metric (as a quantity)", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - "currentAverageValue": { - SchemaProps: spec.SchemaProps{ - Description: "currentAverageValue is the current value of metric averaged over autoscaled pods.", - Ref: ref(resource.Quantity{}.OpenAPIModelName()), - }, - }, - }, - Required: []string{"metricName", "currentValue"}, - }, - }, - Dependencies: []string{ - resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscaler(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscaler is the configuration for a horizontal pod autoscaler, which automatically manages the replica count of any resource implementing the scale subresource based on the metrics specified.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Description: "metadata is the standard object metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - Default: map[string]interface{}{}, - Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "spec is the specification for the behaviour of the autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.OpenAPIModelName()), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Description: "status is the current information about the autoscaler.", - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.HorizontalPodAutoscalerStatus{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - Dependencies: []string{ - autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.OpenAPIModelName(), autoscalingv2beta1.HorizontalPodAutoscalerStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerCondition describes the state of a HorizontalPodAutoscaler at a certain point.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type describes the current condition", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Description: "status is the status of the condition (True, False, Unknown)", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "lastTransitionTime": { - SchemaProps: spec.SchemaProps{ - Description: "lastTransitionTime is the last time the condition transitioned from one status to another", - Ref: ref(metav1.Time{}.OpenAPIModelName()), - }, - }, - "reason": { - SchemaProps: spec.SchemaProps{ - Description: "reason is the reason for the condition's last transition.", - Type: []string{"string"}, - Format: "", - }, - }, - "message": { - SchemaProps: spec.SchemaProps{ - Description: "message is a human-readable explanation containing details about the transition", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"type", "status"}, - }, - }, - Dependencies: []string{ - metav1.Time{}.OpenAPIModelName()}, - } -} - -func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscaler is a list of horizontal pod autoscaler objects.", + Description: "list of horizontal pod autoscaler objects.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -16423,7 +14906,7 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerList(ref common }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "metadata is the standard list metadata.", + Description: "Standard list metadata.", Default: map[string]interface{}{}, Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), }, @@ -16436,7 +14919,7 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerList(ref common Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.HorizontalPodAutoscaler{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.HorizontalPodAutoscaler{}.OpenAPIModelName()), }, }, }, @@ -16447,22 +14930,22 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerList(ref common }, }, Dependencies: []string{ - autoscalingv2beta1.HorizontalPodAutoscaler{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + autoscalingv1.HorizontalPodAutoscaler{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler.", + Description: "specification of a horizontal pod autoscaler.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "scaleTargetRef": { SchemaProps: spec.SchemaProps{ - Description: "scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics should be collected, as well as to actually change the replica count.", + Description: "reference to scaled resource; horizontal pod autoscaler will learn the current resource consumption and will set the desired number of pods by using its Scale subresource.", Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()), }, }, "minReplicas": { @@ -16474,29 +14957,17 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerSpec(ref common }, "maxReplicas": { SchemaProps: spec.SchemaProps{ - Description: "maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. It cannot be less that minReplicas.", + Description: "maxReplicas is the upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.", Default: 0, Type: []string{"integer"}, Format: "int32", }, }, - "metrics": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, + "targetCPUUtilizationPercentage": { SchemaProps: spec.SchemaProps{ - Description: "metrics contains the specifications for which to use to calculate the desired replica count (the maximum replica count across all metrics will be used). The desired replica count is calculated multiplying the ratio between the target value and the current value by the current number of pods. Ergo, metrics used must decrease as the pod count is increased, and vice-versa. See the individual metric source types for more information about how each type of metric must respond.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.MetricSpec{}.OpenAPIModelName()), - }, - }, - }, + Description: "targetCPUUtilizationPercentage is the target average CPU utilization (represented as a percentage of requested CPU) over all the pods; if not specified the default autoscaling policy will be used.", + Type: []string{"integer"}, + Format: "int32", }, }, }, @@ -16504,15 +14975,15 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerSpec(ref common }, }, Dependencies: []string{ - autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2beta1.MetricSpec{}.OpenAPIModelName()}, + autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscalerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler.", + Description: "current status of a horizontal pod autoscaler", Type: []string{"object"}, Properties: map[string]spec.Schema{ "observedGeneration": { @@ -16524,13 +14995,13 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerStatus(ref comm }, "lastScaleTime": { SchemaProps: spec.SchemaProps{ - Description: "lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, used by the autoscaler to control how often the number of pods is changed.", + Description: "lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods; used by the autoscaler to control how often the number of pods is changed.", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, "currentReplicas": { SchemaProps: spec.SchemaProps{ - Description: "currentReplicas is current number of replicas of pods managed by this autoscaler, as last seen by the autoscaler.", + Description: "currentReplicas is the current number of replicas of pods managed by this autoscaler.", Default: 0, Type: []string{"integer"}, Format: "int32", @@ -16538,48 +15009,17 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerStatus(ref comm }, "desiredReplicas": { SchemaProps: spec.SchemaProps{ - Description: "desiredReplicas is the desired number of replicas of pods managed by this autoscaler, as last calculated by the autoscaler.", + Description: "desiredReplicas is the desired number of replicas of pods managed by this autoscaler.", Default: 0, Type: []string{"integer"}, Format: "int32", }, }, - "currentMetrics": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "currentMetrics is the last read state of the metrics used by this autoscaler.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.MetricStatus{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - "conditions": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, + "currentCPUUtilizationPercentage": { SchemaProps: spec.SchemaProps{ - Description: "conditions is the set of conditions required for this autoscaler to scale its target, and indicates whether or not those conditions are met.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.HorizontalPodAutoscalerCondition{}.OpenAPIModelName()), - }, - }, - }, + Description: "currentCPUUtilizationPercentage is the current average CPU utilization over all pods, represented as a percentage of requested CPU, e.g. 70 means that an average pod is using now 70% of its requested CPU.", + Type: []string{"integer"}, + Format: "int32", }, }, }, @@ -16587,11 +15027,11 @@ func schema_k8sio_api_autoscaling_v2beta1_HorizontalPodAutoscalerStatus(ref comm }, }, Dependencies: []string{ - autoscalingv2beta1.HorizontalPodAutoscalerCondition{}.OpenAPIModelName(), autoscalingv2beta1.MetricStatus{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, + metav1.Time{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_MetricSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_MetricSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16600,40 +15040,41 @@ func schema_k8sio_api_autoscaling_v2beta1_MetricSpec(ref common.ReferenceCallbac Properties: map[string]spec.Schema{ "type": { SchemaProps: spec.SchemaProps{ - Description: "type is the type of metric source. It should be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.", + Description: "type is the type of metric source. It should be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.\n\nPossible enum values:\n - `\"ContainerResource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).\n - `\"External\"` is a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).\n - `\"Object\"` is a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).\n - `\"Pods\"` is a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.\n - `\"Resource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).", Default: "", Type: []string{"string"}, Format: "", + Enum: []interface{}{"ContainerResource", "External", "Object", "Pods", "Resource"}, }, }, "object": { SchemaProps: spec.SchemaProps{ Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(autoscalingv2beta1.ObjectMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.ObjectMetricSource{}.OpenAPIModelName()), }, }, "pods": { SchemaProps: spec.SchemaProps{ Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(autoscalingv2beta1.PodsMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.PodsMetricSource{}.OpenAPIModelName()), }, }, "resource": { SchemaProps: spec.SchemaProps{ Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2beta1.ResourceMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.ResourceMetricSource{}.OpenAPIModelName()), }, }, "containerResource": { SchemaProps: spec.SchemaProps{ - Description: "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2beta1.ContainerResourceMetricSource{}.OpenAPIModelName()), + Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", + Ref: ref(autoscalingv1.ContainerResourceMetricSource{}.OpenAPIModelName()), }, }, "external": { SchemaProps: spec.SchemaProps{ Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(autoscalingv2beta1.ExternalMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.ExternalMetricSource{}.OpenAPIModelName()), }, }, }, @@ -16641,11 +15082,11 @@ func schema_k8sio_api_autoscaling_v2beta1_MetricSpec(ref common.ReferenceCallbac }, }, Dependencies: []string{ - autoscalingv2beta1.ContainerResourceMetricSource{}.OpenAPIModelName(), autoscalingv2beta1.ExternalMetricSource{}.OpenAPIModelName(), autoscalingv2beta1.ObjectMetricSource{}.OpenAPIModelName(), autoscalingv2beta1.PodsMetricSource{}.OpenAPIModelName(), autoscalingv2beta1.ResourceMetricSource{}.OpenAPIModelName()}, + autoscalingv1.ContainerResourceMetricSource{}.OpenAPIModelName(), autoscalingv1.ExternalMetricSource{}.OpenAPIModelName(), autoscalingv1.ObjectMetricSource{}.OpenAPIModelName(), autoscalingv1.PodsMetricSource{}.OpenAPIModelName(), autoscalingv1.ResourceMetricSource{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_MetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_MetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16654,40 +15095,41 @@ func schema_k8sio_api_autoscaling_v2beta1_MetricStatus(ref common.ReferenceCallb Properties: map[string]spec.Schema{ "type": { SchemaProps: spec.SchemaProps{ - Description: "type is the type of metric source. It will be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.", + Description: "type is the type of metric source. It will be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.\n\nPossible enum values:\n - `\"ContainerResource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).\n - `\"External\"` is a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).\n - `\"Object\"` is a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).\n - `\"Pods\"` is a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.\n - `\"Resource\"` is a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics (the \"pods\" source).", Default: "", Type: []string{"string"}, Format: "", + Enum: []interface{}{"ContainerResource", "External", "Object", "Pods", "Resource"}, }, }, "object": { SchemaProps: spec.SchemaProps{ Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(autoscalingv2beta1.ObjectMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.ObjectMetricStatus{}.OpenAPIModelName()), }, }, "pods": { SchemaProps: spec.SchemaProps{ Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(autoscalingv2beta1.PodsMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.PodsMetricStatus{}.OpenAPIModelName()), }, }, "resource": { SchemaProps: spec.SchemaProps{ Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2beta1.ResourceMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.ResourceMetricStatus{}.OpenAPIModelName()), }, }, "containerResource": { SchemaProps: spec.SchemaProps{ - Description: "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(autoscalingv2beta1.ContainerResourceMetricStatus{}.OpenAPIModelName()), + Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", + Ref: ref(autoscalingv1.ContainerResourceMetricStatus{}.OpenAPIModelName()), }, }, "external": { SchemaProps: spec.SchemaProps{ Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(autoscalingv2beta1.ExternalMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.ExternalMetricStatus{}.OpenAPIModelName()), }, }, }, @@ -16695,11 +15137,11 @@ func schema_k8sio_api_autoscaling_v2beta1_MetricStatus(ref common.ReferenceCallb }, }, Dependencies: []string{ - autoscalingv2beta1.ContainerResourceMetricStatus{}.OpenAPIModelName(), autoscalingv2beta1.ExternalMetricStatus{}.OpenAPIModelName(), autoscalingv2beta1.ObjectMetricStatus{}.OpenAPIModelName(), autoscalingv2beta1.PodsMetricStatus{}.OpenAPIModelName(), autoscalingv2beta1.ResourceMetricStatus{}.OpenAPIModelName()}, + autoscalingv1.ContainerResourceMetricStatus{}.OpenAPIModelName(), autoscalingv1.ExternalMetricStatus{}.OpenAPIModelName(), autoscalingv1.ObjectMetricStatus{}.OpenAPIModelName(), autoscalingv1.PodsMetricStatus{}.OpenAPIModelName(), autoscalingv1.ResourceMetricStatus{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_ObjectMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16710,7 +15152,7 @@ func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricSource(ref common.Referenc SchemaProps: spec.SchemaProps{ Description: "target is the described Kubernetes object.", Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()), }, }, "metricName": { @@ -16729,7 +15171,7 @@ func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricSource(ref common.Referenc }, "selector": { SchemaProps: spec.SchemaProps{ - Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping When unset, just the metricName will be used to gather metrics.", + Description: "selector is the string-encoded form of a standard kubernetes label selector for the given metric. When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping When unset, just the metricName will be used to gather metrics.", Ref: ref(metav1.LabelSelector{}.OpenAPIModelName()), }, }, @@ -16744,11 +15186,11 @@ func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricSource(ref common.Referenc }, }, Dependencies: []string{ - autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, + autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_ObjectMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16759,7 +15201,7 @@ func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricStatus(ref common.Referenc SchemaProps: spec.SchemaProps{ Description: "target is the described Kubernetes object.", Default: map[string]interface{}{}, - Ref: ref(autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName()), + Ref: ref(autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName()), }, }, "metricName": { @@ -16793,11 +15235,11 @@ func schema_k8sio_api_autoscaling_v2beta1_ObjectMetricStatus(ref common.Referenc }, }, Dependencies: []string{ - autoscalingv2beta1.CrossVersionObjectReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, + autoscalingv1.CrossVersionObjectReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.LabelSelector{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta1_PodsMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_PodsMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16833,7 +15275,7 @@ func schema_k8sio_api_autoscaling_v2beta1_PodsMetricSource(ref common.ReferenceC } } -func schema_k8sio_api_autoscaling_v2beta1_PodsMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_PodsMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16869,7 +15311,7 @@ func schema_k8sio_api_autoscaling_v2beta1_PodsMetricStatus(ref common.ReferenceC } } -func schema_k8sio_api_autoscaling_v2beta1_ResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_ResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16906,7 +15348,7 @@ func schema_k8sio_api_autoscaling_v2beta1_ResourceMetricSource(ref common.Refere } } -func schema_k8sio_api_autoscaling_v2beta1_ResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_ResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16943,7 +15385,107 @@ func schema_k8sio_api_autoscaling_v2beta1_ResourceMetricStatus(ref common.Refere } } -func schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v1_Scale(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Scale represents a scaling request for a resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", + Default: map[string]interface{}{}, + Ref: ref(autoscalingv1.ScaleSpec{}.OpenAPIModelName()), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status is the current status of the scale. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. Read-only.", + Default: map[string]interface{}{}, + Ref: ref(autoscalingv1.ScaleStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + autoscalingv1.ScaleSpec{}.OpenAPIModelName(), autoscalingv1.ScaleStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_autoscaling_v1_ScaleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ScaleSpec describes the attributes of a scale subresource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "replicas": { + SchemaProps: spec.SchemaProps{ + Description: "replicas is the desired number of instances for the scaled object.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_autoscaling_v1_ScaleStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ScaleStatus represents the current status of a scale subresource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "replicas": { + SchemaProps: spec.SchemaProps{ + Description: "replicas is the actual number of observed instances of the scaled object.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "selector is the label query over pods that should match the replicas count. This is same as the label selector but in the string format to avoid introspection by clients. The string will be in the same format as the query-param syntax. More info about label selectors: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"replicas"}, + }, + }, + } +} + +func schema_k8sio_api_autoscaling_v2_ContainerResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -16962,7 +15504,7 @@ func schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricSource(ref comm SchemaProps: spec.SchemaProps{ Description: "target specifies the target value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricTarget{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), }, }, "container": { @@ -16978,11 +15520,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricSource(ref comm }, }, Dependencies: []string{ - v2beta2.MetricTarget{}.OpenAPIModelName()}, + autoscalingv2.MetricTarget{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ContainerResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17001,7 +15543,7 @@ func schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricStatus(ref comm SchemaProps: spec.SchemaProps{ Description: "current contains the current value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricValueStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), }, }, "container": { @@ -17017,11 +15559,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ContainerResourceMetricStatus(ref comm }, }, Dependencies: []string{ - v2beta2.MetricValueStatus{}.OpenAPIModelName()}, + autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_CrossVersionObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_CrossVersionObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17058,7 +15600,7 @@ func schema_k8sio_api_autoscaling_v2beta2_CrossVersionObjectReference(ref common } } -func schema_k8sio_api_autoscaling_v2beta2_ExternalMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ExternalMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17069,14 +15611,14 @@ func schema_k8sio_api_autoscaling_v2beta2_ExternalMetricSource(ref common.Refere SchemaProps: spec.SchemaProps{ Description: "metric identifies the target metric by name and selector", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricIdentifier{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), }, }, "target": { SchemaProps: spec.SchemaProps{ Description: "target specifies the target value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricTarget{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), }, }, }, @@ -17084,11 +15626,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ExternalMetricSource(ref common.Refere }, }, Dependencies: []string{ - v2beta2.MetricIdentifier{}.OpenAPIModelName(), v2beta2.MetricTarget{}.OpenAPIModelName()}, + autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricTarget{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_ExternalMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ExternalMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17099,14 +15641,14 @@ func schema_k8sio_api_autoscaling_v2beta2_ExternalMetricStatus(ref common.Refere SchemaProps: spec.SchemaProps{ Description: "metric identifies the target metric by name and selector", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricIdentifier{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), }, }, "current": { SchemaProps: spec.SchemaProps{ Description: "current contains the current value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricValueStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), }, }, }, @@ -17114,11 +15656,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ExternalMetricStatus(ref common.Refere }, }, Dependencies: []string{ - v2beta2.MetricIdentifier{}.OpenAPIModelName(), v2beta2.MetricValueStatus{}.OpenAPIModelName()}, + autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_HPAScalingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HPAScalingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17156,11 +15698,11 @@ func schema_k8sio_api_autoscaling_v2beta2_HPAScalingPolicy(ref common.ReferenceC } } -func schema_k8sio_api_autoscaling_v2beta2_HPAScalingRules(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HPAScalingRules(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HPAScalingRules configures the scaling behavior for one direction. These Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.", + Description: "HPAScalingRules configures the scaling behavior for one direction via scaling Policy Rules and a configurable metric tolerance.\n\nScaling Policy Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.\n\nThe tolerance is applied to the metric values and prevents scaling too eagerly for small metric variations. (Note that setting a tolerance requires the beta HPAConfigurableTolerance feature gate to be enabled.)", Type: []string{"object"}, Properties: map[string]spec.Schema{ "stabilizationWindowSeconds": { @@ -17172,7 +15714,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HPAScalingRules(ref common.ReferenceCa }, "selectPolicy": { SchemaProps: spec.SchemaProps{ - Description: "selectPolicy is used to specify which policy should be used. If not set, the default value MaxPolicySelect is used.", + Description: "selectPolicy is used to specify which policy should be used. If not set, the default value Max is used.", Type: []string{"string"}, Format: "", }, @@ -17184,27 +15726,33 @@ func schema_k8sio_api_autoscaling_v2beta2_HPAScalingRules(ref common.ReferenceCa }, }, SchemaProps: spec.SchemaProps{ - Description: "policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid", + Description: "policies is a list of potential scaling polices which can be used during scaling. If not set, use the default values: - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. - For scale down: allow all pods to be removed in a 15s window.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v2beta2.HPAScalingPolicy{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HPAScalingPolicy{}.OpenAPIModelName()), }, }, }, }, }, + "tolerance": { + SchemaProps: spec.SchemaProps{ + Description: "tolerance is the tolerance on the ratio between the current and desired metric value under which no updates are made to the desired number of replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not set, the default cluster-wide tolerance is applied (by default 10%).\n\nFor example, if autoscaling is configured with a memory consumption target of 100Mi, and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be triggered when the actual consumption falls below 95Mi or exceeds 101Mi.\n\nThis is an beta field and requires the HPAConfigurableTolerance feature gate to be enabled.", + Ref: ref(resource.Quantity{}.OpenAPIModelName()), + }, + }, }, }, }, Dependencies: []string{ - v2beta2.HPAScalingPolicy{}.OpenAPIModelName()}, + autoscalingv2.HPAScalingPolicy{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscaler(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscaler(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17236,25 +15784,26 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscaler(ref common.Ref SchemaProps: spec.SchemaProps{ Description: "spec is the specification for the behaviour of the autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", Default: map[string]interface{}{}, - Ref: ref(v2beta2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ Description: "status is the current information about the autoscaler.", Default: map[string]interface{}{}, - Ref: ref(v2beta2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName()), }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ - v2beta2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName(), v2beta2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + autoscalingv2.HorizontalPodAutoscalerSpec{}.OpenAPIModelName(), autoscalingv2.HorizontalPodAutoscalerStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerBehavior(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerBehavior(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17264,24 +15813,24 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerBehavior(ref co "scaleUp": { SchemaProps: spec.SchemaProps{ Description: "scaleUp is scaling policy for scaling Up. If not set, the default value is the higher of:\n * increase no more than 4 pods per 60 seconds\n * double the number of pods per 60 seconds\nNo stabilization is used.", - Ref: ref(v2beta2.HPAScalingRules{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HPAScalingRules{}.OpenAPIModelName()), }, }, "scaleDown": { SchemaProps: spec.SchemaProps{ Description: "scaleDown is scaling policy for scaling Down. If not set, the default value is to allow to scale down to minReplicas pods, with a 300 second stabilization window (i.e., the highest recommendation for the last 300sec is used).", - Ref: ref(v2beta2.HPAScalingRules{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HPAScalingRules{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - v2beta2.HPAScalingRules{}.OpenAPIModelName()}, + autoscalingv2.HPAScalingRules{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17333,7 +15882,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerCondition(ref c } } -func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17369,7 +15918,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerList(ref common Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v2beta2.HorizontalPodAutoscaler{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HorizontalPodAutoscaler{}.OpenAPIModelName()), }, }, }, @@ -17380,11 +15929,11 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerList(ref common }, }, Dependencies: []string{ - v2beta2.HorizontalPodAutoscaler{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + autoscalingv2.HorizontalPodAutoscaler{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17395,7 +15944,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerSpec(ref common SchemaProps: spec.SchemaProps{ Description: "scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics should be collected, as well as to actually change the replica count.", Default: map[string]interface{}{}, - Ref: ref(v2beta2.CrossVersionObjectReference{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName()), }, }, "minReplicas": { @@ -17426,7 +15975,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerSpec(ref common Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricSpec{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricSpec{}.OpenAPIModelName()), }, }, }, @@ -17435,7 +15984,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerSpec(ref common "behavior": { SchemaProps: spec.SchemaProps{ Description: "behavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). If not set, the default HPAScalingRules for scale up and scale down are used.", - Ref: ref(v2beta2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName()), }, }, }, @@ -17443,11 +15992,11 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerSpec(ref common }, }, Dependencies: []string{ - v2beta2.CrossVersionObjectReference{}.OpenAPIModelName(), v2beta2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName(), v2beta2.MetricSpec{}.OpenAPIModelName()}, + autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2.HorizontalPodAutoscalerBehavior{}.OpenAPIModelName(), autoscalingv2.MetricSpec{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_HorizontalPodAutoscalerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17470,7 +16019,6 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerStatus(ref comm "currentReplicas": { SchemaProps: spec.SchemaProps{ Description: "currentReplicas is current number of replicas of pods managed by this autoscaler, as last seen by the autoscaler.", - Default: 0, Type: []string{"integer"}, Format: "int32", }, @@ -17496,7 +16044,7 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerStatus(ref comm Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricStatus{}.OpenAPIModelName()), }, }, }, @@ -17505,7 +16053,12 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerStatus(ref comm "conditions": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", }, }, SchemaProps: spec.SchemaProps{ @@ -17515,22 +16068,22 @@ func schema_k8sio_api_autoscaling_v2beta2_HorizontalPodAutoscalerStatus(ref comm Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v2beta2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName()), }, }, }, }, }, }, - Required: []string{"currentReplicas", "desiredReplicas"}, + Required: []string{"desiredReplicas"}, }, }, Dependencies: []string{ - v2beta2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName(), v2beta2.MetricStatus{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, + autoscalingv2.HorizontalPodAutoscalerCondition{}.OpenAPIModelName(), autoscalingv2.MetricStatus{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_MetricIdentifier(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_MetricIdentifier(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17560,7 +16113,7 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricIdentifier(ref common.ReferenceC } } -func schema_k8sio_api_autoscaling_v2beta2_MetricSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_MetricSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17578,31 +16131,31 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricSpec(ref common.ReferenceCallbac "object": { SchemaProps: spec.SchemaProps{ Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(v2beta2.ObjectMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.ObjectMetricSource{}.OpenAPIModelName()), }, }, "pods": { SchemaProps: spec.SchemaProps{ Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(v2beta2.PodsMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.PodsMetricSource{}.OpenAPIModelName()), }, }, "resource": { SchemaProps: spec.SchemaProps{ Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(v2beta2.ResourceMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.ResourceMetricSource{}.OpenAPIModelName()), }, }, "containerResource": { SchemaProps: spec.SchemaProps{ - Description: "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(v2beta2.ContainerResourceMetricSource{}.OpenAPIModelName()), + Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", + Ref: ref(autoscalingv2.ContainerResourceMetricSource{}.OpenAPIModelName()), }, }, "external": { SchemaProps: spec.SchemaProps{ Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(v2beta2.ExternalMetricSource{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.ExternalMetricSource{}.OpenAPIModelName()), }, }, }, @@ -17610,11 +16163,11 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricSpec(ref common.ReferenceCallbac }, }, Dependencies: []string{ - v2beta2.ContainerResourceMetricSource{}.OpenAPIModelName(), v2beta2.ExternalMetricSource{}.OpenAPIModelName(), v2beta2.ObjectMetricSource{}.OpenAPIModelName(), v2beta2.PodsMetricSource{}.OpenAPIModelName(), v2beta2.ResourceMetricSource{}.OpenAPIModelName()}, + autoscalingv2.ContainerResourceMetricSource{}.OpenAPIModelName(), autoscalingv2.ExternalMetricSource{}.OpenAPIModelName(), autoscalingv2.ObjectMetricSource{}.OpenAPIModelName(), autoscalingv2.PodsMetricSource{}.OpenAPIModelName(), autoscalingv2.ResourceMetricSource{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_MetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_MetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17632,31 +16185,31 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricStatus(ref common.ReferenceCallb "object": { SchemaProps: spec.SchemaProps{ Description: "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - Ref: ref(v2beta2.ObjectMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.ObjectMetricStatus{}.OpenAPIModelName()), }, }, "pods": { SchemaProps: spec.SchemaProps{ Description: "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - Ref: ref(v2beta2.PodsMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.PodsMetricStatus{}.OpenAPIModelName()), }, }, "resource": { SchemaProps: spec.SchemaProps{ Description: "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(v2beta2.ResourceMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.ResourceMetricStatus{}.OpenAPIModelName()), }, }, "containerResource": { SchemaProps: spec.SchemaProps{ - Description: "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - Ref: ref(v2beta2.ContainerResourceMetricStatus{}.OpenAPIModelName()), + Description: "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", + Ref: ref(autoscalingv2.ContainerResourceMetricStatus{}.OpenAPIModelName()), }, }, "external": { SchemaProps: spec.SchemaProps{ Description: "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - Ref: ref(v2beta2.ExternalMetricStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.ExternalMetricStatus{}.OpenAPIModelName()), }, }, }, @@ -17664,11 +16217,11 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricStatus(ref common.ReferenceCallb }, }, Dependencies: []string{ - v2beta2.ContainerResourceMetricStatus{}.OpenAPIModelName(), v2beta2.ExternalMetricStatus{}.OpenAPIModelName(), v2beta2.ObjectMetricStatus{}.OpenAPIModelName(), v2beta2.PodsMetricStatus{}.OpenAPIModelName(), v2beta2.ResourceMetricStatus{}.OpenAPIModelName()}, + autoscalingv2.ContainerResourceMetricStatus{}.OpenAPIModelName(), autoscalingv2.ExternalMetricStatus{}.OpenAPIModelName(), autoscalingv2.ObjectMetricStatus{}.OpenAPIModelName(), autoscalingv2.PodsMetricStatus{}.OpenAPIModelName(), autoscalingv2.ResourceMetricStatus{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_MetricTarget(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_MetricTarget(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17711,7 +16264,7 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricTarget(ref common.ReferenceCallb } } -func schema_k8sio_api_autoscaling_v2beta2_MetricValueStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_MetricValueStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17732,7 +16285,7 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricValueStatus(ref common.Reference }, "averageUtilization": { SchemaProps: spec.SchemaProps{ - Description: "averageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", + Description: "currentAverageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", Type: []string{"integer"}, Format: "int32", }, @@ -17745,7 +16298,7 @@ func schema_k8sio_api_autoscaling_v2beta2_MetricValueStatus(ref common.Reference } } -func schema_k8sio_api_autoscaling_v2beta2_ObjectMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ObjectMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17754,22 +16307,23 @@ func schema_k8sio_api_autoscaling_v2beta2_ObjectMetricSource(ref common.Referenc Properties: map[string]spec.Schema{ "describedObject": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v2beta2.CrossVersionObjectReference{}.OpenAPIModelName()), + Description: "describedObject specifies the descriptions of a object,such as kind,name apiVersion", + Default: map[string]interface{}{}, + Ref: ref(autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName()), }, }, "target": { SchemaProps: spec.SchemaProps{ Description: "target specifies the target value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricTarget{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), }, }, "metric": { SchemaProps: spec.SchemaProps{ Description: "metric identifies the target metric by name and selector", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricIdentifier{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), }, }, }, @@ -17777,11 +16331,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ObjectMetricSource(ref common.Referenc }, }, Dependencies: []string{ - v2beta2.CrossVersionObjectReference{}.OpenAPIModelName(), v2beta2.MetricIdentifier{}.OpenAPIModelName(), v2beta2.MetricTarget{}.OpenAPIModelName()}, + autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricTarget{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_ObjectMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ObjectMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17792,20 +16346,21 @@ func schema_k8sio_api_autoscaling_v2beta2_ObjectMetricStatus(ref common.Referenc SchemaProps: spec.SchemaProps{ Description: "metric identifies the target metric by name and selector", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricIdentifier{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), }, }, "current": { SchemaProps: spec.SchemaProps{ Description: "current contains the current value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricValueStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), }, }, "describedObject": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v2beta2.CrossVersionObjectReference{}.OpenAPIModelName()), + Description: "DescribedObject specifies the descriptions of a object,such as kind,name apiVersion", + Default: map[string]interface{}{}, + Ref: ref(autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName()), }, }, }, @@ -17813,11 +16368,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ObjectMetricStatus(ref common.Referenc }, }, Dependencies: []string{ - v2beta2.CrossVersionObjectReference{}.OpenAPIModelName(), v2beta2.MetricIdentifier{}.OpenAPIModelName(), v2beta2.MetricValueStatus{}.OpenAPIModelName()}, + autoscalingv2.CrossVersionObjectReference{}.OpenAPIModelName(), autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_PodsMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_PodsMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17828,14 +16383,14 @@ func schema_k8sio_api_autoscaling_v2beta2_PodsMetricSource(ref common.ReferenceC SchemaProps: spec.SchemaProps{ Description: "metric identifies the target metric by name and selector", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricIdentifier{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), }, }, "target": { SchemaProps: spec.SchemaProps{ Description: "target specifies the target value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricTarget{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), }, }, }, @@ -17843,11 +16398,11 @@ func schema_k8sio_api_autoscaling_v2beta2_PodsMetricSource(ref common.ReferenceC }, }, Dependencies: []string{ - v2beta2.MetricIdentifier{}.OpenAPIModelName(), v2beta2.MetricTarget{}.OpenAPIModelName()}, + autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricTarget{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_PodsMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_PodsMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17858,14 +16413,14 @@ func schema_k8sio_api_autoscaling_v2beta2_PodsMetricStatus(ref common.ReferenceC SchemaProps: spec.SchemaProps{ Description: "metric identifies the target metric by name and selector", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricIdentifier{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricIdentifier{}.OpenAPIModelName()), }, }, "current": { SchemaProps: spec.SchemaProps{ Description: "current contains the current value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricValueStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), }, }, }, @@ -17873,11 +16428,11 @@ func schema_k8sio_api_autoscaling_v2beta2_PodsMetricStatus(ref common.ReferenceC }, }, Dependencies: []string{ - v2beta2.MetricIdentifier{}.OpenAPIModelName(), v2beta2.MetricValueStatus{}.OpenAPIModelName()}, + autoscalingv2.MetricIdentifier{}.OpenAPIModelName(), autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_ResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ResourceMetricSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17896,7 +16451,7 @@ func schema_k8sio_api_autoscaling_v2beta2_ResourceMetricSource(ref common.Refere SchemaProps: spec.SchemaProps{ Description: "target specifies the target value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricTarget{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricTarget{}.OpenAPIModelName()), }, }, }, @@ -17904,11 +16459,11 @@ func schema_k8sio_api_autoscaling_v2beta2_ResourceMetricSource(ref common.Refere }, }, Dependencies: []string{ - v2beta2.MetricTarget{}.OpenAPIModelName()}, + autoscalingv2.MetricTarget{}.OpenAPIModelName()}, } } -func schema_k8sio_api_autoscaling_v2beta2_ResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_autoscaling_v2_ResourceMetricStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -17927,7 +16482,7 @@ func schema_k8sio_api_autoscaling_v2beta2_ResourceMetricStatus(ref common.Refere SchemaProps: spec.SchemaProps{ Description: "current contains the current value for the given metric", Default: map[string]interface{}{}, - Ref: ref(v2beta2.MetricValueStatus{}.OpenAPIModelName()), + Ref: ref(autoscalingv2.MetricValueStatus{}.OpenAPIModelName()), }, }, }, @@ -17935,7 +16490,7 @@ func schema_k8sio_api_autoscaling_v2beta2_ResourceMetricStatus(ref common.Refere }, }, Dependencies: []string{ - v2beta2.MetricValueStatus{}.OpenAPIModelName()}, + autoscalingv2.MetricValueStatus{}.OpenAPIModelName()}, } } @@ -17982,6 +16537,7 @@ func schema_k8sio_api_batch_v1_CronJob(ref common.ReferenceCallback) common.Open }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -18901,6 +17457,7 @@ func schema_k8sio_api_batch_v1beta1_CronJob(ref common.ReferenceCallback) common }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -20156,14 +18713,21 @@ func schema_k8sio_api_certificates_v1beta1_PodCertificateRequestSpec(ref common. }, "pkixPublicKey": { SchemaProps: spec.SchemaProps{ - Description: "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", + Description: "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty. Signer implementations should extract the public key from the StubPKCS10Request field.", Type: []string{"string"}, Format: "byte", }, }, "proofOfPossession": { SchemaProps: spec.SchemaProps{ - Description: "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).", + Description: "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "stubPKCS10Request": { + SchemaProps: spec.SchemaProps{ + Description: "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost signer implementations will ignore the contents of the CSR except to extract the subject public key. The API server automatically verifies the CSR signature during admission, so the signer does not need to repeat the verification. CSRs generated by kubelet are completely empty.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", Type: []string{"string"}, Format: "byte", }, @@ -20185,7 +18749,7 @@ func schema_k8sio_api_certificates_v1beta1_PodCertificateRequestSpec(ref common. }, }, }, - Required: []string{"signerName", "podName", "podUID", "serviceAccountName", "serviceAccountUID", "nodeName", "nodeUID", "pkixPublicKey", "proofOfPossession"}, + Required: []string{"signerName", "podName", "podUID", "serviceAccountName", "serviceAccountUID", "nodeName", "nodeUID", "stubPKCS10Request"}, }, }, } @@ -20447,6 +19011,7 @@ func schema_k8sio_api_coordination_v1alpha2_LeaseCandidate(ref common.ReferenceC }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -20642,6 +19207,7 @@ func schema_k8sio_api_coordination_v1beta1_LeaseCandidate(ref common.ReferenceCa }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -25529,6 +24095,27 @@ func schema_k8sio_api_core_v1_ImageVolumeSource(ref common.ReferenceCallback) co } } +func schema_k8sio_api_core_v1_ImageVolumeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ImageVolumeStatus represents the image-based volume status.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "imageRef": { + SchemaProps: spec.SchemaProps{ + Description: "ImageRef is the digest of the image used for this volume. It should have a value that's similar to the pod's status.containerStatuses[i].imageID. The ImageRef length should not exceed 256 characters.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"imageRef"}, + }, + }, + } +} + func schema_k8sio_api_core_v1_KeyToPath(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -26527,6 +25114,64 @@ func schema_k8sio_api_core_v1_NodeAffinity(ref common.ReferenceCallback) common. } } +func schema_k8sio_api_core_v1_NodeAllocatableResourceClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "resourceClaimName": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "containers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Containers lists the names of all containers in this pod that reference the claim.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref(resource.Quantity{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"resourceClaimName", "resources"}, + }, + }, + Dependencies: []string{ + resource.Quantity{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_core_v1_NodeCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -28163,7 +26808,7 @@ func schema_k8sio_api_core_v1_PersistentVolumeSource(ref common.ReferenceCallbac }, "portworxVolume": { SchemaProps: spec.SchemaProps{ - Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver.", Ref: ref(corev1.PortworxVolumeSource{}.OpenAPIModelName()), }, }, @@ -28324,7 +26969,7 @@ func schema_k8sio_api_core_v1_PersistentVolumeSpec(ref common.ReferenceCallback) }, "portworxVolume": { SchemaProps: spec.SchemaProps{ - Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver.", Ref: ref(corev1.PortworxVolumeSource{}.OpenAPIModelName()), }, }, @@ -28923,7 +27568,7 @@ func schema_k8sio_api_core_v1_PodCondition(ref common.ReferenceCallback) common. }, "observedGeneration": { SchemaProps: spec.SchemaProps{ - Description: "If set, this represents the .metadata.generation that the pod condition was set based upon. The PodObservedGenerationTracking feature gate must be enabled to use this field.", + Description: "If set, this represents the .metadata.generation that the pod condition was set based upon.", Type: []string{"integer"}, Format: "int64", }, @@ -29498,7 +28143,7 @@ func schema_k8sio_api_core_v1_PodResourceClaim(ref common.ReferenceCallback) com return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + Description: "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and this Pod belongs to a PodGroup, a PodResourceClaim is matched to a PodGroupResourceClaim if all of their fields are equal (Name, ResourceClaimName, and ResourceClaimTemplateName). A matched claim references a single ResourceClaim shared across all Pods in the PodGroup, reserved for the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual Pods.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "name": { @@ -29518,7 +28163,7 @@ func schema_k8sio_api_core_v1_PodResourceClaim(ref common.ReferenceCallback) com }, "resourceClaimTemplateName": { SchemaProps: spec.SchemaProps{ - Description: "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + Description: "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and the pod belongs to a PodGroup that defines a PodGroupResourceClaim with the same Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the ResourceClaim generated for the PodGroup. All pods in the group that define an equivalent PodResourceClaim matching the PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same generated ResourceClaim. ResourceClaims generated for a PodGroup are owned by the PodGroup and their lifecycles are tied to the PodGroup instead of any individual pod.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", Type: []string{"string"}, Format: "", }, @@ -29547,7 +28192,7 @@ func schema_k8sio_api_core_v1_PodResourceClaimStatus(ref common.ReferenceCallbac }, "resourceClaimName": { SchemaProps: spec.SchemaProps{ - Description: "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod. If this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", + Description: "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod.\n\nWhen the DRAWorkloadResourceClaims feature is enabled and the corresponding PodResourceClaim matches a PodGroupResourceClaim made by the Pod's PodGroup, then this is the name of the ResourceClaim generated and reserved for the PodGroup.\n\nIf this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", Type: []string{"string"}, Format: "", }, @@ -29581,6 +28226,37 @@ func schema_k8sio_api_core_v1_PodSchedulingGate(ref common.ReferenceCallback) co } } +func schema_k8sio_api_core_v1_PodSchedulingGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics. Exactly one field must be specified.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "podGroupName": { + SchemaProps: spec.SchemaProps{ + Description: "PodGroupName specifies the name of the standalone PodGroup object that represents the runtime instance of this group. Must be a DNS subdomain.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-unions": []interface{}{ + map[string]interface{}{ + "fields-to-discriminateBy": map[string]interface{}{ + "podGroupName": "PodGroupName", + }, + }, + }, + }, + }, + }, + } +} + func schema_k8sio_api_core_v1_PodSecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -30156,7 +28832,7 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA }, "hostUsers": { SchemaProps: spec.SchemaProps{ - Description: "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + Description: "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", Type: []string{"boolean"}, Format: "", }, @@ -30222,10 +28898,10 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA Format: "", }, }, - "workloadRef": { + "schedulingGroup": { SchemaProps: spec.SchemaProps{ - Description: "WorkloadRef provides a reference to the Workload object that this Pod belongs to. This field is used by the scheduler to identify the PodGroup and apply the correct group scheduling policies. The Workload object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a Workload object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies.", - Ref: ref(corev1.WorkloadReference{}.OpenAPIModelName()), + Description: "SchedulingGroup provides a reference to the immediate scheduling runtime grouping object that this Pod belongs to. This field is used by the scheduler to identify the group and apply the correct group scheduling policies. The association with a group also impacts other lifecycle aspects of a Pod that are relevant in a wider context of scheduling like preemption, resource attachment, etc. If not specified, the Pod is treated as a single unit in all of these aspects. The group object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a group object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies.", + Ref: ref(corev1.PodSchedulingGroup{}.OpenAPIModelName()), }, }, }, @@ -30233,7 +28909,7 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA }, }, Dependencies: []string{ - corev1.Affinity{}.OpenAPIModelName(), corev1.Container{}.OpenAPIModelName(), corev1.EphemeralContainer{}.OpenAPIModelName(), corev1.HostAlias{}.OpenAPIModelName(), corev1.LocalObjectReference{}.OpenAPIModelName(), corev1.PodDNSConfig{}.OpenAPIModelName(), corev1.PodOS{}.OpenAPIModelName(), corev1.PodReadinessGate{}.OpenAPIModelName(), corev1.PodResourceClaim{}.OpenAPIModelName(), corev1.PodSchedulingGate{}.OpenAPIModelName(), corev1.PodSecurityContext{}.OpenAPIModelName(), corev1.ResourceRequirements{}.OpenAPIModelName(), corev1.Toleration{}.OpenAPIModelName(), corev1.TopologySpreadConstraint{}.OpenAPIModelName(), corev1.Volume{}.OpenAPIModelName(), corev1.WorkloadReference{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName()}, + corev1.Affinity{}.OpenAPIModelName(), corev1.Container{}.OpenAPIModelName(), corev1.EphemeralContainer{}.OpenAPIModelName(), corev1.HostAlias{}.OpenAPIModelName(), corev1.LocalObjectReference{}.OpenAPIModelName(), corev1.PodDNSConfig{}.OpenAPIModelName(), corev1.PodOS{}.OpenAPIModelName(), corev1.PodReadinessGate{}.OpenAPIModelName(), corev1.PodResourceClaim{}.OpenAPIModelName(), corev1.PodSchedulingGate{}.OpenAPIModelName(), corev1.PodSchedulingGroup{}.OpenAPIModelName(), corev1.PodSecurityContext{}.OpenAPIModelName(), corev1.ResourceRequirements{}.OpenAPIModelName(), corev1.Toleration{}.OpenAPIModelName(), corev1.TopologySpreadConstraint{}.OpenAPIModelName(), corev1.Volume{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName()}, } } @@ -30491,11 +29167,30 @@ func schema_k8sio_api_core_v1_PodStatus(ref common.ReferenceCallback) common.Ope Ref: ref(corev1.ResourceRequirements{}.OpenAPIModelName()), }, }, + "nodeAllocatableResourceClaimStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources that were allocated for this pod through DRA claims. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(corev1.NodeAllocatableResourceClaimStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - corev1.ContainerStatus{}.OpenAPIModelName(), corev1.HostIP{}.OpenAPIModelName(), corev1.PodCondition{}.OpenAPIModelName(), corev1.PodExtendedResourceClaimStatus{}.OpenAPIModelName(), corev1.PodIP{}.OpenAPIModelName(), corev1.PodResourceClaimStatus{}.OpenAPIModelName(), corev1.ResourceRequirements{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, + corev1.ContainerStatus{}.OpenAPIModelName(), corev1.HostIP{}.OpenAPIModelName(), corev1.NodeAllocatableResourceClaimStatus{}.OpenAPIModelName(), corev1.PodCondition{}.OpenAPIModelName(), corev1.PodExtendedResourceClaimStatus{}.OpenAPIModelName(), corev1.PodIP{}.OpenAPIModelName(), corev1.PodResourceClaimStatus{}.OpenAPIModelName(), corev1.ResourceRequirements{}.OpenAPIModelName(), resource.Quantity{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, } } @@ -31640,6 +30335,13 @@ func schema_k8sio_api_core_v1_ResourceHealth(ref common.ReferenceCallback) commo Format: "", }, }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message provides human-readable context for Health (e.g. \"ECC error count exceeded threshold\"). This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise.", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"resourceID"}, }, @@ -32717,7 +31419,7 @@ func schema_k8sio_api_core_v1_SecurityContext(ref common.ReferenceCallback) comm }, "procMount": { SchemaProps: spec.SchemaProps{ - Description: "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.\n\nPossible enum values:\n - `\"Default\"` uses the container runtime defaults for readonly and masked paths for /proc. Most container runtimes mask certain paths in /proc to avoid accidental security exposure of special devices or information.\n - `\"Unmasked\"` bypasses the default masking behavior of the container runtime and ensures the newly created /proc the container stays in tact with no modifications.", + Description: "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. Note that this field cannot be set when spec.os.name is windows.\n\nPossible enum values:\n - `\"Default\"` uses the container runtime defaults for readonly and masked paths for /proc. Most container runtimes mask certain paths in /proc to avoid accidental security exposure of special devices or information.\n - `\"Unmasked\"` bypasses the default masking behavior of the container runtime and ensures the newly created /proc the container stays in tact with no modifications.", Type: []string{"string"}, Format: "", Enum: []interface{}{"Default", "Unmasked"}, @@ -34151,7 +32853,7 @@ func schema_k8sio_api_core_v1_Volume(ref common.ReferenceCallback) common.OpenAP }, "portworxVolume": { SchemaProps: spec.SchemaProps{ - Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver.", Ref: ref(corev1.PortworxVolumeSource{}.OpenAPIModelName()), }, }, @@ -34181,7 +32883,7 @@ func schema_k8sio_api_core_v1_Volume(ref common.ReferenceCallback) common.OpenAP }, "image": { SchemaProps: spec.SchemaProps{ - Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", + Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", Ref: ref(corev1.ImageVolumeSource{}.OpenAPIModelName()), }, }, @@ -34327,10 +33029,18 @@ func schema_k8sio_api_core_v1_VolumeMountStatus(ref common.ReferenceCallback) co Format: "", }, }, + "volumeStatus": { + SchemaProps: spec.SchemaProps{ + Description: "volumeStatus represents volume-type-specific status about the mounted volume.", + Ref: ref(corev1.VolumeStatus{}.OpenAPIModelName()), + }, + }, }, Required: []string{"name", "mountPath"}, }, }, + Dependencies: []string{ + corev1.VolumeStatus{}.OpenAPIModelName()}, } } @@ -34602,7 +33312,7 @@ func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common. }, "portworxVolume": { SchemaProps: spec.SchemaProps{ - Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver.", Ref: ref(corev1.PortworxVolumeSource{}.OpenAPIModelName()), }, }, @@ -34632,7 +33342,7 @@ func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common. }, "image": { SchemaProps: spec.SchemaProps{ - Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", + Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", Ref: ref(corev1.ImageVolumeSource{}.OpenAPIModelName()), }, }, @@ -34644,6 +33354,27 @@ func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common. } } +func schema_k8sio_api_core_v1_VolumeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VolumeStatus represents the status of a mounted volume. At most one of its members must be specified.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "image": { + SchemaProps: spec.SchemaProps{ + Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine.", + Ref: ref(corev1.ImageVolumeStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + corev1.ImageVolumeStatus{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_core_v1_VsphereVirtualDiskVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -34759,43 +33490,6 @@ func schema_k8sio_api_core_v1_WindowsSecurityContextOptions(ref common.Reference } } -func schema_k8sio_api_core_v1_WorkloadReference(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "WorkloadReference identifies the Workload object and PodGroup membership that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Description: "Name defines the name of the Workload object this Pod belongs to. Workload must be in the same namespace as the Pod. If it doesn't match any existing Workload, the Pod will remain unschedulable until a Workload object is created and observed by the kube-scheduler. It must be a DNS subdomain.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "podGroup": { - SchemaProps: spec.SchemaProps{ - Description: "PodGroup is the name of the PodGroup within the Workload that this Pod belongs to. If it doesn't match any existing PodGroup within the Workload, the Pod will remain unschedulable until the Workload object is recreated and observed by the kube-scheduler. It must be a DNS label.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "podGroupReplicaKey": { - SchemaProps: spec.SchemaProps{ - Description: "PodGroupReplicaKey specifies the replica key of the PodGroup to which this Pod belongs. It is used to distinguish pods belonging to different replicas of the same pod group. The pod group policy is applied separately to each replica. When set, it must be a DNS label.", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"name", "podGroup"}, - }, - }, - } -} - func schema_k8sio_api_discovery_v1_Endpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -35098,7 +33792,7 @@ func schema_k8sio_api_discovery_v1_EndpointSlice(ref common.ReferenceCallback) c }, }, }, - Required: []string{"addressType", "endpoints"}, + Required: []string{"addressType"}, }, }, Dependencies: []string{ @@ -35445,10 +34139,11 @@ func schema_k8sio_api_discovery_v1beta1_EndpointSlice(ref common.ReferenceCallba }, "addressType": { SchemaProps: spec.SchemaProps{ - Description: "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. This field is immutable after creation. The following address types are currently supported: * IPv4: Represents an IPv4 Address. * IPv6: Represents an IPv6 Address. * FQDN: Represents a Fully Qualified Domain Name.", + Description: "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. This field is immutable after creation. The following address types are currently supported: * IPv4: Represents an IPv4 Address. * IPv6: Represents an IPv6 Address. * FQDN: Represents a Fully Qualified Domain Name.\n\nPossible enum values:\n - `\"FQDN\"` represents a FQDN.\n - `\"IPv4\"` represents an IPv4 Address.\n - `\"IPv6\"` represents an IPv6 Address.", Default: "", Type: []string{"string"}, Format: "", + Enum: []interface{}{"FQDN", "IPv4", "IPv6"}, }, }, "endpoints": { @@ -42328,7 +41023,6 @@ func schema_k8sio_api_imagepolicy_v1alpha1_ImageReview(ref common.ReferenceCallb }, }, }, - Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -42452,7 +41146,6 @@ func schema_k8sio_api_imagepolicy_v1alpha1_ImageReviewStatus(ref common.Referenc }, }, }, - Required: []string{"allowed"}, }, }, } @@ -42567,6 +41260,7 @@ func schema_k8sio_api_networking_v1_IPAddress(ref common.ReferenceCallback) comm }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -44021,6 +42715,7 @@ func schema_k8sio_api_networking_v1beta1_IPAddress(ref common.ReferenceCallback) }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -45792,7 +44487,6 @@ func schema_k8sio_api_policy_v1_PodDisruptionBudgetStatus(ref common.ReferenceCa }, }, }, - Required: []string{"disruptionsAllowed", "currentHealthy", "desiredHealthy", "expectedPods"}, }, }, Dependencies: []string{ @@ -46069,7 +44763,6 @@ func schema_k8sio_api_policy_v1beta1_PodDisruptionBudgetStatus(ref common.Refere }, }, }, - Required: []string{"disruptionsAllowed", "currentHealthy", "desiredHealthy", "expectedPods"}, }, }, Dependencies: []string{ @@ -46703,7 +45396,7 @@ func schema_k8sio_api_rbac_v1_RoleRef(ref common.ReferenceCallback) common.OpenA }, }, }, - Required: []string{"apiGroup", "kind", "name"}, + Required: []string{"kind", "name"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -47389,7 +46082,7 @@ func schema_k8sio_api_rbac_v1alpha1_RoleRef(ref common.ReferenceCallback) common }, }, }, - Required: []string{"apiGroup", "kind", "name"}, + Required: []string{"kind", "name"}, }, }, } @@ -48065,7 +46758,7 @@ func schema_k8sio_api_rbac_v1beta1_RoleRef(ref common.ReferenceCallback) common. }, }, }, - Required: []string{"apiGroup", "kind", "name"}, + Required: []string{"kind", "name"}, }, }, } @@ -48218,7 +46911,7 @@ func schema_k8sio_api_resource_v1_AllocationResult(ref common.ReferenceCallback) }, "allocationTimestamp": { SchemaProps: spec.SchemaProps{ - Description: "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", + Description: "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -48239,7 +46932,7 @@ func schema_k8sio_api_resource_v1_CELDeviceSelector(ref common.ReferenceCallback Properties: map[string]spec.Schema{ "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + Description: "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", Default: "", Type: []string{"string"}, Format: "", @@ -48512,7 +47205,7 @@ func schema_k8sio_api_resource_v1_Device(ref common.ReferenceCallback) common.Op }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -48526,7 +47219,7 @@ func schema_k8sio_api_resource_v1_Device(ref common.ReferenceCallback) common.Op }, "bindsToNode": { SchemaProps: spec.SchemaProps{ - Description: "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"boolean"}, Format: "", }, @@ -48538,7 +47231,7 @@ func schema_k8sio_api_resource_v1_Device(ref common.ReferenceCallback) common.Op }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -48558,7 +47251,7 @@ func schema_k8sio_api_resource_v1_Device(ref common.ReferenceCallback) common.Op }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -48578,12 +47271,27 @@ func schema_k8sio_api_resource_v1_Device(ref common.ReferenceCallback) common.Op Format: "", }, }, + "nodeAllocatableResourceMappings": { + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(resourcev1.NodeAllocatableResourceMapping{}.OpenAPIModelName()), + }, + }, + }, + }, + }, }, Required: []string{"name"}, }, }, Dependencies: []string{ - corev1.NodeSelector{}.OpenAPIModelName(), resourcev1.DeviceAttribute{}.OpenAPIModelName(), resourcev1.DeviceCapacity{}.OpenAPIModelName(), resourcev1.DeviceCounterConsumption{}.OpenAPIModelName(), resourcev1.DeviceTaint{}.OpenAPIModelName()}, + corev1.NodeSelector{}.OpenAPIModelName(), resourcev1.DeviceAttribute{}.OpenAPIModelName(), resourcev1.DeviceCapacity{}.OpenAPIModelName(), resourcev1.DeviceCounterConsumption{}.OpenAPIModelName(), resourcev1.DeviceTaint{}.OpenAPIModelName(), resourcev1.NodeAllocatableResourceMapping{}.OpenAPIModelName()}, } } @@ -48726,6 +47434,86 @@ func schema_k8sio_api_resource_v1_DeviceAttribute(ref common.ReferenceCallback) Format: "", }, }, + "ints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + "bools": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "BoolValues is a non-empty list of true/false values.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + "strings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, @@ -48877,7 +47665,7 @@ func schema_k8sio_api_resource_v1_DeviceClass(ref common.ReferenceCallback) comm return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + Description: "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -49036,7 +47824,7 @@ func schema_k8sio_api_resource_v1_DeviceClassSpec(ref common.ReferenceCallback) }, "extendedResourceName": { SchemaProps: spec.SchemaProps{ - Description: "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + Description: "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", Type: []string{"string"}, Format: "", }, @@ -49099,14 +47887,14 @@ func schema_k8sio_api_resource_v1_DeviceConstraint(ref common.ReferenceCallback) }, "matchAttribute": { SchemaProps: spec.SchemaProps{ - Description: "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + Description: "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as single-element lists for backward compatibility.\n\nMust include the domain qualifier.", Type: []string{"string"}, Format: "", }, }, "distinctAttribute": { SchemaProps: spec.SchemaProps{ - Description: "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + Description: "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", Type: []string{"string"}, Format: "", }, @@ -49246,7 +48034,7 @@ func schema_k8sio_api_resource_v1_DeviceRequestAllocationResult(ref common.Refer }, "adminAccess": { SchemaProps: spec.SchemaProps{ - Description: "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + Description: "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", Type: []string{"boolean"}, Format: "", }, @@ -49258,7 +48046,7 @@ func schema_k8sio_api_resource_v1_DeviceRequestAllocationResult(ref common.Refer }, }, SchemaProps: spec.SchemaProps{ - Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49277,7 +48065,7 @@ func schema_k8sio_api_resource_v1_DeviceRequestAllocationResult(ref common.Refer }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49297,7 +48085,7 @@ func schema_k8sio_api_resource_v1_DeviceRequestAllocationResult(ref common.Refer }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49425,7 +48213,7 @@ func schema_k8sio_api_resource_v1_DeviceSubRequest(ref common.ReferenceCallback) }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49485,7 +48273,7 @@ func schema_k8sio_api_resource_v1_DeviceTaint(ref common.ReferenceCallback) comm }, "timeAdded": { SchemaProps: spec.SchemaProps{ - Description: "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + Description: "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -49600,7 +48388,7 @@ func schema_k8sio_api_resource_v1_ExactDeviceRequest(ref common.ReferenceCallbac }, "adminAccess": { SchemaProps: spec.SchemaProps{ - Description: "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + Description: "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", Type: []string{"boolean"}, Format: "", }, @@ -49612,7 +48400,7 @@ func schema_k8sio_api_resource_v1_ExactDeviceRequest(ref common.ReferenceCallbac }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49648,7 +48436,7 @@ func schema_k8sio_api_resource_v1_NetworkDeviceData(ref common.ReferenceCallback Properties: map[string]spec.Schema{ "interfaceName": { SchemaProps: spec.SchemaProps{ - Description: "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + Description: "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", Type: []string{"string"}, Format: "", }, @@ -49675,7 +48463,7 @@ func schema_k8sio_api_resource_v1_NetworkDeviceData(ref common.ReferenceCallback }, "hardwareAddress": { SchemaProps: spec.SchemaProps{ - Description: "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + Description: "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", Type: []string{"string"}, Format: "", }, @@ -49686,6 +48474,34 @@ func schema_k8sio_api_resource_v1_NetworkDeviceData(ref common.ReferenceCallback } } +func schema_k8sio_api_resource_v1_NodeAllocatableResourceMapping(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capacityKey": { + SchemaProps: spec.SchemaProps{ + Description: "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + Type: []string{"string"}, + Format: "", + }, + }, + "allocationMultiplier": { + SchemaProps: spec.SchemaProps{ + Description: "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16.", + Ref: ref(resource.Quantity{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + resource.Quantity{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_resource_v1_OpaqueDeviceConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -49720,7 +48536,7 @@ func schema_k8sio_api_resource_v1_ResourceClaim(ref common.ReferenceCallback) co return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + Description: "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -49959,7 +48775,7 @@ func schema_k8sio_api_resource_v1_ResourceClaimTemplate(ref common.ReferenceCall return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ResourceClaimTemplate is used to produce ResourceClaim objects.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + Description: "ResourceClaimTemplate is used to produce ResourceClaim objects.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -50122,7 +48938,7 @@ func schema_k8sio_api_resource_v1_ResourceSlice(ref common.ReferenceCallback) co return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + Description: "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -50385,7 +49201,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceTaint(ref common.ReferenceCallback }, "timeAdded": { SchemaProps: spec.SchemaProps{ - Description: "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + Description: "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -50602,6 +49418,295 @@ func schema_k8sio_api_resource_v1alpha3_DeviceTaintSelector(ref common.Reference } } +func schema_k8sio_api_resource_v1alpha3_PoolStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PoolStatus contains status information for a single resource pool.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "Driver is the DRA driver name for this pool. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + Type: []string{"string"}, + Format: "", + }, + }, + "poolName": { + SchemaProps: spec.SchemaProps{ + Description: "PoolName is the name of the pool. Must be a valid resource pool name (DNS subdomains separated by \"/\").", + Type: []string{"string"}, + Format: "", + }, + }, + "generation": { + SchemaProps: spec.SchemaProps{ + Description: "Generation is the pool generation observed across all ResourceSlices in this pool. Only the latest generation is reported. During a generation rollout, if not all slices at the latest generation have been published, the pool is included with a validationError and device counts unset.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "resourceSliceCount": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceSliceCount is the number of ResourceSlices that make up this pool. May be unset when validationError is set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "totalDevices": { + SchemaProps: spec.SchemaProps{ + Description: "TotalDevices is the total number of devices in the pool across all slices. A value of 0 means the pool has no devices. May be unset when validationError is set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "allocatedDevices": { + SchemaProps: spec.SchemaProps{ + Description: "AllocatedDevices is the number of devices currently allocated to claims. A value of 0 means no devices are allocated. May be unset when validationError is set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "availableDevices": { + SchemaProps: spec.SchemaProps{ + Description: "AvailableDevices is the number of devices available for allocation. This equals TotalDevices - AllocatedDevices - UnavailableDevices. A value of 0 means no devices are currently available. May be unset when validationError is set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "unavailableDevices": { + SchemaProps: spec.SchemaProps{ + Description: "UnavailableDevices is the number of devices that are not available due to taints or other conditions, but are not allocated. A value of 0 means all unallocated devices are available. May be unset when validationError is set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "nodeName": { + SchemaProps: spec.SchemaProps{ + Description: "NodeName is the node this pool is associated with. When omitted, the pool is not associated with a specific node. Must be a valid DNS subdomain name (RFC1123).", + Type: []string{"string"}, + Format: "", + }, + }, + "validationError": { + SchemaProps: spec.SchemaProps{ + Description: "ValidationError is set when the pool's data could not be fully validated (e.g., incomplete slice publication). When set, device count fields and ResourceSliceCount may be unset.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"driver", "poolName", "generation"}, + }, + }, + } +} + +func schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourcePoolStatusRequest triggers a one-time calculation of resource pool status based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. Users should delete and recreate requests to get updated information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object metadata", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the filters for which pools to include in the status. The spec is immutable once created.", + Default: map[string]interface{}{}, + Ref: ref(v1alpha3.ResourcePoolStatusRequestSpec{}.OpenAPIModelName()), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status is populated by the controller with the calculated pool status. When status is non-nil, the request is considered complete and the entire object becomes immutable.", + Ref: ref(v1alpha3.ResourcePoolStatusRequestStatus{}.OpenAPIModelName()), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + v1alpha3.ResourcePoolStatusRequestSpec{}.OpenAPIModelName(), v1alpha3.ResourcePoolStatusRequestStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequestList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata", + Default: map[string]interface{}{}, + Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is the list of ResourcePoolStatusRequests.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1alpha3.ResourcePoolStatusRequest{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + v1alpha3.ResourcePoolStatusRequest{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequestSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourcePoolStatusRequestSpec defines the filters for the pool status request.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "Driver specifies the DRA driver name to filter pools. Only pools from ResourceSlices with this driver will be included. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "poolName": { + SchemaProps: spec.SchemaProps{ + Description: "PoolName optionally filters to a specific pool name. If not specified, all pools from the specified driver are included. When specified, must be a non-empty valid resource pool name (DNS subdomains separated by \"/\").", + Type: []string{"string"}, + Format: "", + }, + }, + "limit": { + SchemaProps: spec.SchemaProps{ + Description: "Limit optionally specifies the maximum number of pools to return in the status. If more pools match the filter criteria, the response will be truncated (i.e., len(status.pools) < status.poolCount).\n\nDefault: 100 Minimum: 1 Maximum: 1000", + Default: 100, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"driver"}, + }, + }, + } +} + +func schema_k8sio_api_resource_v1alpha3_ResourcePoolStatusRequestStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourcePoolStatusRequestStatus contains the calculated pool status information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "poolCount": { + SchemaProps: spec.SchemaProps{ + Description: "PoolCount is the total number of pools that matched the filter criteria, regardless of truncation. This helps users understand how many pools exist even when the response is truncated. A value of 0 means no pools matched the filter criteria.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "pools": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Pools contains the first `spec.limit` matching pools, sorted by driver then pool name. If `len(pools) < poolCount`, the list was truncated. When omitted, no pools matched the request filters.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1alpha3.PoolStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions provide information about the state of the request. A condition with type=Complete or type=Failed will always be set when the status is populated.\n\nKnown condition types: - \"Complete\": True when the request has been processed successfully - \"Failed\": True when the request could not be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(metav1.Condition{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"poolCount"}, + }, + }, + Dependencies: []string{ + v1alpha3.PoolStatus{}.OpenAPIModelName(), metav1.Condition{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_resource_v1beta1_AllocatedDeviceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -50705,7 +49810,7 @@ func schema_k8sio_api_resource_v1beta1_AllocationResult(ref common.ReferenceCall }, "allocationTimestamp": { SchemaProps: spec.SchemaProps{ - Description: "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", + Description: "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -50800,7 +49905,7 @@ func schema_k8sio_api_resource_v1beta1_BasicDevice(ref common.ReferenceCallback) }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -50814,7 +49919,7 @@ func schema_k8sio_api_resource_v1beta1_BasicDevice(ref common.ReferenceCallback) }, "bindsToNode": { SchemaProps: spec.SchemaProps{ - Description: "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"boolean"}, Format: "", }, @@ -50826,7 +49931,7 @@ func schema_k8sio_api_resource_v1beta1_BasicDevice(ref common.ReferenceCallback) }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -50846,7 +49951,7 @@ func schema_k8sio_api_resource_v1beta1_BasicDevice(ref common.ReferenceCallback) }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -50866,11 +49971,26 @@ func schema_k8sio_api_resource_v1beta1_BasicDevice(ref common.ReferenceCallback) Format: "", }, }, + "nodeAllocatableResourceMappings": { + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(resourcev1beta1.NodeAllocatableResourceMapping{}.OpenAPIModelName()), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - corev1.NodeSelector{}.OpenAPIModelName(), resourcev1beta1.DeviceAttribute{}.OpenAPIModelName(), resourcev1beta1.DeviceCapacity{}.OpenAPIModelName(), resourcev1beta1.DeviceCounterConsumption{}.OpenAPIModelName(), resourcev1beta1.DeviceTaint{}.OpenAPIModelName()}, + corev1.NodeSelector{}.OpenAPIModelName(), resourcev1beta1.DeviceAttribute{}.OpenAPIModelName(), resourcev1beta1.DeviceCapacity{}.OpenAPIModelName(), resourcev1beta1.DeviceCounterConsumption{}.OpenAPIModelName(), resourcev1beta1.DeviceTaint{}.OpenAPIModelName(), resourcev1beta1.NodeAllocatableResourceMapping{}.OpenAPIModelName()}, } } @@ -50883,7 +50003,7 @@ func schema_k8sio_api_resource_v1beta1_CELDeviceSelector(ref common.ReferenceCal Properties: map[string]spec.Schema{ "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + Description: "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", Default: "", Type: []string{"string"}, Format: "", @@ -51234,6 +50354,86 @@ func schema_k8sio_api_resource_v1beta1_DeviceAttribute(ref common.ReferenceCallb Format: "", }, }, + "ints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + "bools": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "BoolValues is a non-empty list of true/false values.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + "strings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, @@ -51544,7 +50744,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceClassSpec(ref common.ReferenceCallb }, "extendedResourceName": { SchemaProps: spec.SchemaProps{ - Description: "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + Description: "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", Type: []string{"string"}, Format: "", }, @@ -51607,14 +50807,14 @@ func schema_k8sio_api_resource_v1beta1_DeviceConstraint(ref common.ReferenceCall }, "matchAttribute": { SchemaProps: spec.SchemaProps{ - Description: "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + Description: "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", Type: []string{"string"}, Format: "", }, }, "distinctAttribute": { SchemaProps: spec.SchemaProps{ - Description: "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + Description: "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", Type: []string{"string"}, Format: "", }, @@ -51754,7 +50954,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequest(ref common.ReferenceCallbac }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -51834,7 +51034,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -51853,7 +51053,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -51873,7 +51073,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -52001,7 +51201,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceSubRequest(ref common.ReferenceCall }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -52061,7 +51261,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceTaint(ref common.ReferenceCallback) }, "timeAdded": { SchemaProps: spec.SchemaProps{ - Description: "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + Description: "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -52134,7 +51334,7 @@ func schema_k8sio_api_resource_v1beta1_NetworkDeviceData(ref common.ReferenceCal Properties: map[string]spec.Schema{ "interfaceName": { SchemaProps: spec.SchemaProps{ - Description: "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + Description: "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", Type: []string{"string"}, Format: "", }, @@ -52161,14 +51361,42 @@ func schema_k8sio_api_resource_v1beta1_NetworkDeviceData(ref common.ReferenceCal }, "hardwareAddress": { SchemaProps: spec.SchemaProps{ - Description: "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + Description: "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_resource_v1beta1_NodeAllocatableResourceMapping(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capacityKey": { + SchemaProps: spec.SchemaProps{ + Description: "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", Type: []string{"string"}, Format: "", }, }, + "allocationMultiplier": { + SchemaProps: spec.SchemaProps{ + Description: "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16.", + Ref: ref(resource.Quantity{}.OpenAPIModelName()), + }, + }, }, }, }, + Dependencies: []string{ + resource.Quantity{}.OpenAPIModelName()}, } } @@ -52898,7 +52126,7 @@ func schema_k8sio_api_resource_v1beta2_AllocationResult(ref common.ReferenceCall }, "allocationTimestamp": { SchemaProps: spec.SchemaProps{ - Description: "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", + Description: "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -52919,7 +52147,7 @@ func schema_k8sio_api_resource_v1beta2_CELDeviceSelector(ref common.ReferenceCal Properties: map[string]spec.Schema{ "expression": { SchemaProps: spec.SchemaProps{ - Description: "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + Description: "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", Default: "", Type: []string{"string"}, Format: "", @@ -53192,7 +52420,7 @@ func schema_k8sio_api_resource_v1beta2_Device(ref common.ReferenceCallback) comm }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -53206,7 +52434,7 @@ func schema_k8sio_api_resource_v1beta2_Device(ref common.ReferenceCallback) comm }, "bindsToNode": { SchemaProps: spec.SchemaProps{ - Description: "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"boolean"}, Format: "", }, @@ -53218,7 +52446,7 @@ func schema_k8sio_api_resource_v1beta2_Device(ref common.ReferenceCallback) comm }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -53238,7 +52466,7 @@ func schema_k8sio_api_resource_v1beta2_Device(ref common.ReferenceCallback) comm }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -53258,12 +52486,27 @@ func schema_k8sio_api_resource_v1beta2_Device(ref common.ReferenceCallback) comm Format: "", }, }, + "nodeAllocatableResourceMappings": { + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(resourcev1beta2.NodeAllocatableResourceMapping{}.OpenAPIModelName()), + }, + }, + }, + }, + }, }, Required: []string{"name"}, }, }, Dependencies: []string{ - corev1.NodeSelector{}.OpenAPIModelName(), resourcev1beta2.DeviceAttribute{}.OpenAPIModelName(), resourcev1beta2.DeviceCapacity{}.OpenAPIModelName(), resourcev1beta2.DeviceCounterConsumption{}.OpenAPIModelName(), resourcev1beta2.DeviceTaint{}.OpenAPIModelName()}, + corev1.NodeSelector{}.OpenAPIModelName(), resourcev1beta2.DeviceAttribute{}.OpenAPIModelName(), resourcev1beta2.DeviceCapacity{}.OpenAPIModelName(), resourcev1beta2.DeviceCounterConsumption{}.OpenAPIModelName(), resourcev1beta2.DeviceTaint{}.OpenAPIModelName(), resourcev1beta2.NodeAllocatableResourceMapping{}.OpenAPIModelName()}, } } @@ -53406,6 +52649,86 @@ func schema_k8sio_api_resource_v1beta2_DeviceAttribute(ref common.ReferenceCallb Format: "", }, }, + "ints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + "bools": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "BoolValues is a non-empty list of true/false values.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + "strings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, @@ -53716,7 +53039,7 @@ func schema_k8sio_api_resource_v1beta2_DeviceClassSpec(ref common.ReferenceCallb }, "extendedResourceName": { SchemaProps: spec.SchemaProps{ - Description: "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + Description: "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", Type: []string{"string"}, Format: "", }, @@ -53779,14 +53102,14 @@ func schema_k8sio_api_resource_v1beta2_DeviceConstraint(ref common.ReferenceCall }, "matchAttribute": { SchemaProps: spec.SchemaProps{ - Description: "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", + Description: "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", Type: []string{"string"}, Format: "", }, }, "distinctAttribute": { SchemaProps: spec.SchemaProps{ - Description: "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + Description: "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", Type: []string{"string"}, Format: "", }, @@ -53938,7 +53261,7 @@ func schema_k8sio_api_resource_v1beta2_DeviceRequestAllocationResult(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -53957,7 +53280,7 @@ func schema_k8sio_api_resource_v1beta2_DeviceRequestAllocationResult(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -53977,7 +53300,7 @@ func schema_k8sio_api_resource_v1beta2_DeviceRequestAllocationResult(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + Description: "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -54105,7 +53428,7 @@ func schema_k8sio_api_resource_v1beta2_DeviceSubRequest(ref common.ReferenceCall }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -54165,7 +53488,7 @@ func schema_k8sio_api_resource_v1beta2_DeviceTaint(ref common.ReferenceCallback) }, "timeAdded": { SchemaProps: spec.SchemaProps{ - Description: "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + Description: "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", Ref: ref(metav1.Time{}.OpenAPIModelName()), }, }, @@ -54178,6 +53501,210 @@ func schema_k8sio_api_resource_v1beta2_DeviceTaint(ref common.ReferenceCallback) } } +func schema_k8sio_api_resource_v1beta2_DeviceTaintRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object metadata", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number.", + Default: map[string]interface{}{}, + Ref: ref(resourcev1beta2.DeviceTaintRuleSpec{}.OpenAPIModelName()), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status provides information about what was requested in the spec.", + Default: map[string]interface{}{}, + Ref: ref(resourcev1beta2.DeviceTaintRuleStatus{}.OpenAPIModelName()), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + resourcev1beta2.DeviceTaintRuleSpec{}.OpenAPIModelName(), resourcev1beta2.DeviceTaintRuleStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_resource_v1beta2_DeviceTaintRuleList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceTaintRuleList is a collection of DeviceTaintRules.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata", + Default: map[string]interface{}{}, + Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is the list of DeviceTaintRules.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(resourcev1beta2.DeviceTaintRule{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + resourcev1beta2.DeviceTaintRule{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_resource_v1beta2_DeviceTaintRuleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceTaintRuleSpec specifies the selector and one taint.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "deviceSelector": { + SchemaProps: spec.SchemaProps{ + Description: "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satisfied for a device to match. The empty selector matches all devices. Without a selector, no devices are matches.", + Ref: ref(resourcev1beta2.DeviceTaintSelector{}.OpenAPIModelName()), + }, + }, + "taint": { + SchemaProps: spec.SchemaProps{ + Description: "The taint that gets applied to matching devices.", + Default: map[string]interface{}{}, + Ref: ref(resourcev1beta2.DeviceTaint{}.OpenAPIModelName()), + }, + }, + }, + Required: []string{"taint"}, + }, + }, + Dependencies: []string{ + resourcev1beta2.DeviceTaint{}.OpenAPIModelName(), resourcev1beta2.DeviceTaintSelector{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_resource_v1beta2_DeviceTaintRuleStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceTaintRuleStatus provides information about an on-going pod eviction.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions provide information about the state of the DeviceTaintRule and the cluster at some point in time, in a machine-readable and human-readable format.\n\nThe following condition is currently defined as part of this API, more may get added: - Type: EvictionInProgress - Status: True if there are currently pods which need to be evicted, False otherwise\n (includes the effects which don't cause eviction).\n- Reason: not specified, may change - Message: includes information about number of pending pods and already evicted pods\n in a human-readable format, updated periodically, may change\n\nFor `effect: None`, the condition above gets set once for each change to the spec, with the message containing information about what would happen if the effect was `NoExecute`. This feedback can be used to decide whether changing the effect to `NoExecute` will work as intended. It only gets set once to avoid having to constantly update the status.\n\nMust have 8 or fewer entries.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(metav1.Condition{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + metav1.Condition{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_resource_v1beta2_DeviceTaintSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.", + Type: []string{"string"}, + Format: "", + }, + }, + "pool": { + SchemaProps: spec.SchemaProps{ + Description: "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.", + Type: []string{"string"}, + Format: "", + }, + }, + "device": { + SchemaProps: spec.SchemaProps{ + Description: "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_k8sio_api_resource_v1beta2_DeviceToleration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -54292,7 +53819,7 @@ func schema_k8sio_api_resource_v1beta2_ExactDeviceRequest(ref common.ReferenceCa }, }, SchemaProps: spec.SchemaProps{ - Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -54328,7 +53855,7 @@ func schema_k8sio_api_resource_v1beta2_NetworkDeviceData(ref common.ReferenceCal Properties: map[string]spec.Schema{ "interfaceName": { SchemaProps: spec.SchemaProps{ - Description: "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + Description: "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", Type: []string{"string"}, Format: "", }, @@ -54355,7 +53882,7 @@ func schema_k8sio_api_resource_v1beta2_NetworkDeviceData(ref common.ReferenceCal }, "hardwareAddress": { SchemaProps: spec.SchemaProps{ - Description: "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + Description: "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", Type: []string{"string"}, Format: "", }, @@ -54366,6 +53893,34 @@ func schema_k8sio_api_resource_v1beta2_NetworkDeviceData(ref common.ReferenceCal } } +func schema_k8sio_api_resource_v1beta2_NodeAllocatableResourceMapping(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capacityKey": { + SchemaProps: spec.SchemaProps{ + Description: "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + Type: []string{"string"}, + Format: "", + }, + }, + "allocationMultiplier": { + SchemaProps: spec.SchemaProps{ + Description: "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16.", + Ref: ref(resource.Quantity{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + resource.Quantity{}.OpenAPIModelName()}, + } +} + func schema_k8sio_api_resource_v1beta2_OpaqueDeviceConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -55048,7 +54603,6 @@ func schema_k8sio_api_scheduling_v1_PriorityClass(ref common.ReferenceCallback) }, }, }, - Required: []string{"value"}, }, }, Dependencies: []string{ @@ -55107,7 +54661,7 @@ func schema_k8sio_api_scheduling_v1_PriorityClassList(ref common.ReferenceCallba } } -func schema_k8sio_api_scheduling_v1alpha1_BasicSchedulingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_BasicSchedulingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -55118,7 +54672,7 @@ func schema_k8sio_api_scheduling_v1alpha1_BasicSchedulingPolicy(ref common.Refer } } -func schema_k8sio_api_scheduling_v1alpha1_GangSchedulingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_GangSchedulingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -55140,183 +54694,529 @@ func schema_k8sio_api_scheduling_v1alpha1_GangSchedulingPolicy(ref common.Refere } } -func schema_k8sio_api_scheduling_v1alpha1_PodGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_PodGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "PodGroup represents a set of pods with a common scheduling policy.", + Description: "PodGroup represents a runtime instance of pods grouped together. PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from Workload.podGroupTemplates. PodGroup API enablement is toggled by the GenericWorkload feature gate.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "kind": { SchemaProps: spec.SchemaProps{ - Description: "Name is a unique identifier for the PodGroup within the Workload. It must be a DNS label. This field is immutable.", - Default: "", + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", Type: []string{"string"}, Format: "", }, }, - "policy": { + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired state of the PodGroup.", + Default: map[string]interface{}{}, + Ref: ref(schedulingv1alpha2.PodGroupSpec{}.OpenAPIModelName()), + }, + }, + "status": { SchemaProps: spec.SchemaProps{ - Description: "Policy defines the scheduling policy for this PodGroup.", + Description: "Status represents the current observed state of the PodGroup.", Default: map[string]interface{}{}, - Ref: ref(schedulingv1alpha1.PodGroupPolicy{}.OpenAPIModelName()), + Ref: ref(schedulingv1alpha2.PodGroupStatus{}.OpenAPIModelName()), }, }, }, - Required: []string{"name", "policy"}, + Required: []string{"spec"}, }, }, Dependencies: []string{ - schedulingv1alpha1.PodGroupPolicy{}.OpenAPIModelName()}, + schedulingv1alpha2.PodGroupSpec{}.OpenAPIModelName(), schedulingv1alpha2.PodGroupStatus{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_scheduling_v1alpha1_PodGroupPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_PodGroupList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "PodGroupPolicy defines the scheduling configuration for a PodGroup.", + Description: "PodGroupList contains a list of PodGroup resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "basic": { + "kind": { SchemaProps: spec.SchemaProps{ - Description: "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior.", - Ref: ref(schedulingv1alpha1.BasicSchedulingPolicy{}.OpenAPIModelName()), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", }, }, - "gang": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics.", - Ref: ref(schedulingv1alpha1.GangSchedulingPolicy{}.OpenAPIModelName()), + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata.", + Default: map[string]interface{}{}, + Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is the list of PodGroups.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(schedulingv1alpha2.PodGroup{}.OpenAPIModelName()), + }, + }, + }, }, }, }, + Required: []string{"items"}, }, }, Dependencies: []string{ - schedulingv1alpha1.BasicSchedulingPolicy{}.OpenAPIModelName(), schedulingv1alpha1.GangSchedulingPolicy{}.OpenAPIModelName()}, + schedulingv1alpha2.PodGroup{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_scheduling_v1alpha1_PriorityClass(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_PodGroupResourceClaim(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "DEPRECATED - This group version of PriorityClass is deprecated by scheduling.k8s.io/v1/PriorityClass. PriorityClass defines mapping from a priority class name to the priority integer value. The value can be any valid integer.", + Description: "PodGroupResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the PodGroup.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. Pods that need access to the ResourceClaim define a matching reference in its own Spec.ResourceClaims. The Pod's claim must match all fields of the PodGroup's claim exactly.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "name": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "Name uniquely identifies this resource claim inside the PodGroup. This must be a DNS_LABEL.", + Default: "", Type: []string{"string"}, Format: "", }, }, - "apiVersion": { + "resourceClaimName": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Description: "ResourceClaimName is the name of a ResourceClaim object in the same namespace as this PodGroup. The ResourceClaim will be reserved for the PodGroup instead of its individual pods.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", Type: []string{"string"}, Format: "", }, }, - "metadata": { + "resourceClaimTemplateName": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - Default: map[string]interface{}{}, - Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + Description: "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this PodGroup.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim will also be deleted. The PodGroup name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + Type: []string{"string"}, + Format: "", }, }, - "value": { + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_PodGroupResourceClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodGroupResourceClaimStatus is stored in the PodGroupStatus for each PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the generated name for the corresponding ResourceClaim.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { SchemaProps: spec.SchemaProps{ - Description: "value represents the integer value of this priority class. This is the actual priority that pods receive when they have the name of this class in their pod spec.", - Default: 0, - Type: []string{"integer"}, - Format: "int32", + Description: "Name uniquely identifies this resource claim inside the PodGroup. This must match the name of an entry in podgroup.spec.resourceClaims, which implies that the string must be a DNS_LABEL.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "globalDefault": { + "resourceClaimName": { SchemaProps: spec.SchemaProps{ - Description: "globalDefault specifies whether this PriorityClass should be considered as the default priority for pods that do not have any priority class. Only one PriorityClass can be marked as `globalDefault`. However, if more than one PriorityClasses exists with their `globalDefault` field set to true, the smallest value of such global default PriorityClasses will be used as the default priority.", - Type: []string{"boolean"}, + Description: "ResourceClaimName is the name of the ResourceClaim that was generated for the PodGroup in the namespace of the PodGroup. If this is unset, then generating a ResourceClaim was not necessary. The podgroup.spec.resourceClaims entry can be ignored in this case.", + Type: []string{"string"}, Format: "", }, }, - "description": { + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_PodGroupSchedulingConstraints(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "topology": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, SchemaProps: spec.SchemaProps{ - Description: "description is an arbitrary string that usually provides guidelines on when this priority class should be used.", + Description: "Topology defines the topology constraints for the pod group. Currently only a single topology constraint can be specified. This may change in the future.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(schedulingv1alpha2.TopologyConstraint{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + schedulingv1alpha2.TopologyConstraint{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_PodGroupSchedulingPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. Exactly one policy must be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "basic": { + SchemaProps: spec.SchemaProps{ + Description: "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior.", + Ref: ref(schedulingv1alpha2.BasicSchedulingPolicy{}.OpenAPIModelName()), + }, + }, + "gang": { + SchemaProps: spec.SchemaProps{ + Description: "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics.", + Ref: ref(schedulingv1alpha2.GangSchedulingPolicy{}.OpenAPIModelName()), + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-unions": []interface{}{ + map[string]interface{}{ + "fields-to-discriminateBy": map[string]interface{}{ + "basic": "Basic", + "gang": "Gang", + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + schedulingv1alpha2.BasicSchedulingPolicy{}.OpenAPIModelName(), schedulingv1alpha2.GangSchedulingPolicy{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_PodGroupSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodGroupSpec defines the desired state of a PodGroup.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "podGroupTemplateRef": { + SchemaProps: spec.SchemaProps{ + Description: "PodGroupTemplateRef references an optional PodGroup template within other object (e.g. Workload) that was used to create the PodGroup. This field is immutable.", + Ref: ref(schedulingv1alpha2.PodGroupTemplateReference{}.OpenAPIModelName()), + }, + }, + "schedulingPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable.", + Default: map[string]interface{}{}, + Ref: ref(schedulingv1alpha2.PodGroupSchedulingPolicy{}.OpenAPIModelName()), + }, + }, + "schedulingConstraints": { + SchemaProps: spec.SchemaProps{ + Description: "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled.", + Ref: ref(schedulingv1alpha2.PodGroupSchedulingConstraints{}.OpenAPIModelName()), + }, + }, + "resourceClaims": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(schedulingv1alpha2.PodGroupResourceClaim{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + "disruptionMode": { + SchemaProps: spec.SchemaProps{ + Description: "DisruptionMode defines the mode in which a given PodGroup can be disrupted. Controllers are expected to fill this field by copying it from a PodGroupTemplate. One of Pod, PodGroup. Defaults to Pod if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.\n\n\nPossible enum values:\n - `\"Pod\"` means that individual pods can be disrupted or preempted independently. It doesn't depend on exact set of pods currently running in this PodGroup.\n - `\"PodGroup\"` means that the whole PodGroup needs to be disrupted or preempted together.", + Default: "Pod", Type: []string{"string"}, Format: "", + Enum: []interface{}{"Pod", "PodGroup"}, }, }, - "preemptionPolicy": { + "priorityClassName": { SchemaProps: spec.SchemaProps{ - Description: "preemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset.\n\nPossible enum values:\n - `\"Never\"` means that pod never preempts other pods with lower priority.\n - `\"PreemptLowerPriority\"` means that pod can preempt other pods with lower priority.", + Description: "PriorityClassName defines the priority that should be considered when scheduling this pod group. Controllers are expected to fill this field by copying it from a PodGroupTemplate. Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate (i.e. if no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, the pod group's priority will be zero). This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", Type: []string{"string"}, Format: "", - Enum: []interface{}{"Never", "PreemptLowerPriority"}, + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + Type: []string{"integer"}, + Format: "int32", }, }, }, - Required: []string{"value"}, + Required: []string{"schedulingPolicy"}, }, }, Dependencies: []string{ - metav1.ObjectMeta{}.OpenAPIModelName()}, + schedulingv1alpha2.PodGroupResourceClaim{}.OpenAPIModelName(), schedulingv1alpha2.PodGroupSchedulingConstraints{}.OpenAPIModelName(), schedulingv1alpha2.PodGroupSchedulingPolicy{}.OpenAPIModelName(), schedulingv1alpha2.PodGroupTemplateReference{}.OpenAPIModelName()}, } } -func schema_k8sio_api_scheduling_v1alpha1_PriorityClassList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_PodGroupStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "PriorityClassList is a collection of priority classes.", + Description: "PodGroupStatus represents information about the status of a pod group.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", + Description: "Conditions represent the latest observations of the PodGroup's state.\n\nKnown condition types: - \"PodGroupScheduled\": Indicates whether the scheduling requirement has been satisfied. - \"DisruptionTarget\": Indicates whether the PodGroup is about to be terminated\n due to disruption such as preemption.\n\nKnown reasons for the PodGroupScheduled condition: - \"Unschedulable\": The PodGroup cannot be scheduled due to resource constraints,\n affinity/anti-affinity rules, or insufficient capacity for the gang.\n- \"SchedulerError\": The PodGroup cannot be scheduled due to some internal error\n that happened during scheduling, for example due to nodeAffinity parsing errors.\n\nKnown reasons for the DisruptionTarget condition: - \"PreemptionByScheduler\": The PodGroup was preempted by the scheduler to make room for\n higher-priority PodGroups or Pods.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(metav1.Condition{}.OpenAPIModelName()), + }, + }, + }, }, }, - "apiVersion": { + "resourceClaimStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys", + }, + }, SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Description: "Status of resource claims.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(schedulingv1alpha2.PodGroupResourceClaimStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + schedulingv1alpha2.PodGroupResourceClaimStatus{}.OpenAPIModelName(), metav1.Condition{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_PodGroupTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodGroupTemplate represents a template for a set of pods with a scheduling policy.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is a unique identifier for the PodGroupTemplate within the Workload. It must be a DNS label. This field is immutable.", + Default: "", Type: []string{"string"}, Format: "", }, }, - "metadata": { + "schedulingPolicy": { SchemaProps: spec.SchemaProps{ - Description: "Standard list metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Description: "SchedulingPolicy defines the scheduling policy for this PodGroupTemplate.", Default: map[string]interface{}{}, - Ref: ref(metav1.ListMeta{}.OpenAPIModelName()), + Ref: ref(schedulingv1alpha2.PodGroupSchedulingPolicy{}.OpenAPIModelName()), }, }, - "items": { + "schedulingConstraints": { SchemaProps: spec.SchemaProps{ - Description: "items is the list of PriorityClasses", + Description: "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled.", + Ref: ref(schedulingv1alpha2.PodGroupSchedulingConstraints{}.OpenAPIModelName()), + }, + }, + "resourceClaims": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(schedulingv1alpha1.PriorityClass{}.OpenAPIModelName()), + Ref: ref(schedulingv1alpha2.PodGroupResourceClaim{}.OpenAPIModelName()), }, }, }, }, }, + "disruptionMode": { + SchemaProps: spec.SchemaProps{ + Description: "DisruptionMode defines the mode in which a given PodGroup can be disrupted. One of Pod, PodGroup. This field is available only when the WorkloadAwarePreemption feature gate is enabled.\n\n\nPossible enum values:\n - `\"Pod\"` means that individual pods can be disrupted or preempted independently. It doesn't depend on exact set of pods currently running in this PodGroup.\n - `\"PodGroup\"` means that the whole PodGroup needs to be disrupted or preempted together.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Pod", "PodGroup"}, + }, + }, + "priorityClassName": { + SchemaProps: spec.SchemaProps{ + Description: "PriorityClassName indicates the priority that should be considered when scheduling a pod group created from this template. If no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, pod groups created from this template will have the priority set to zero. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + Type: []string{"string"}, + Format: "", + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + Type: []string{"integer"}, + Format: "int32", + }, + }, }, - Required: []string{"items"}, + Required: []string{"name", "schedulingPolicy"}, }, }, Dependencies: []string{ - schedulingv1alpha1.PriorityClass{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + schedulingv1alpha2.PodGroupResourceClaim{}.OpenAPIModelName(), schedulingv1alpha2.PodGroupSchedulingConstraints{}.OpenAPIModelName(), schedulingv1alpha2.PodGroupSchedulingPolicy{}.OpenAPIModelName()}, } } -func schema_k8sio_api_scheduling_v1alpha1_TypedLocalObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_PodGroupTemplateReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). Exactly one reference must be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "workload": { + SchemaProps: spec.SchemaProps{ + Description: "Workload references the PodGroupTemplate within the Workload object that was used to create the PodGroup.", + Ref: ref(schedulingv1alpha2.WorkloadPodGroupTemplateReference{}.OpenAPIModelName()), + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-unions": []interface{}{ + map[string]interface{}{ + "fields-to-discriminateBy": map[string]interface{}{ + "workload": "Workload", + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + schedulingv1alpha2.WorkloadPodGroupTemplateReference{}.OpenAPIModelName()}, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_TopologyConstraint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TopologyConstraint defines a topology constraint for a PodGroup.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "Key specifies the key of the node label representing the topology domain. All pods within the PodGroup must be colocated within the same domain instance. Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. Examples: \"topology.kubernetes.io/rack\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key"}, + }, + }, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_TypedLocalObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -55353,11 +55253,11 @@ func schema_k8sio_api_scheduling_v1alpha1_TypedLocalObjectReference(ref common.R } } -func schema_k8sio_api_scheduling_v1alpha1_Workload(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_Workload(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Workload allows for expressing scheduling constraints that should be used when managing lifecycle of workloads from scheduling perspective, including scheduling, preemption, eviction and other phases.", + Description: "Workload allows for expressing scheduling constraints that should be used when managing the lifecycle of workloads from the scheduling perspective, including scheduling, preemption, eviction and other phases. Workload API enablement is toggled by the GenericWorkload feature gate.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -55376,7 +55276,7 @@ func schema_k8sio_api_scheduling_v1alpha1_Workload(ref common.ReferenceCallback) }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata. Name must be a DNS subdomain.", + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", Default: map[string]interface{}{}, Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), }, @@ -55385,7 +55285,7 @@ func schema_k8sio_api_scheduling_v1alpha1_Workload(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Description: "Spec defines the desired behavior of a Workload.", Default: map[string]interface{}{}, - Ref: ref(schedulingv1alpha1.WorkloadSpec{}.OpenAPIModelName()), + Ref: ref(schedulingv1alpha2.WorkloadSpec{}.OpenAPIModelName()), }, }, }, @@ -55393,11 +55293,11 @@ func schema_k8sio_api_scheduling_v1alpha1_Workload(ref common.ReferenceCallback) }, }, Dependencies: []string{ - schedulingv1alpha1.WorkloadSpec{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, + schedulingv1alpha2.WorkloadSpec{}.OpenAPIModelName(), metav1.ObjectMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_scheduling_v1alpha1_WorkloadList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_WorkloadList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -55433,7 +55333,7 @@ func schema_k8sio_api_scheduling_v1alpha1_WorkloadList(ref common.ReferenceCallb Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(schedulingv1alpha1.Workload{}.OpenAPIModelName()), + Ref: ref(schedulingv1alpha2.Workload{}.OpenAPIModelName()), }, }, }, @@ -55444,11 +55344,41 @@ func schema_k8sio_api_scheduling_v1alpha1_WorkloadList(ref common.ReferenceCallb }, }, Dependencies: []string{ - schedulingv1alpha1.Workload{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, + schedulingv1alpha2.Workload{}.OpenAPIModelName(), metav1.ListMeta{}.OpenAPIModelName()}, } } -func schema_k8sio_api_scheduling_v1alpha1_WorkloadSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_k8sio_api_scheduling_v1alpha2_WorkloadPodGroupTemplateReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "workloadName": { + SchemaProps: spec.SchemaProps{ + Description: "WorkloadName defines the name of the Workload object.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "podGroupTemplateName": { + SchemaProps: spec.SchemaProps{ + Description: "PodGroupTemplateName defines the PodGroupTemplate name within the Workload object.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"workloadName", "podGroupTemplateName"}, + }, + }, + } +} + +func schema_k8sio_api_scheduling_v1alpha2_WorkloadSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -55457,11 +55387,11 @@ func schema_k8sio_api_scheduling_v1alpha1_WorkloadSpec(ref common.ReferenceCallb Properties: map[string]spec.Schema{ "controllerRef": { SchemaProps: spec.SchemaProps{ - Description: "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. When set, it cannot be changed.", - Ref: ref(schedulingv1alpha1.TypedLocalObjectReference{}.OpenAPIModelName()), + Description: "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. This field is immutable.", + Ref: ref(schedulingv1alpha2.TypedLocalObjectReference{}.OpenAPIModelName()), }, }, - "podGroups": { + "podGroupTemplates": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-map-keys": []interface{}{ @@ -55471,24 +55401,24 @@ func schema_k8sio_api_scheduling_v1alpha1_WorkloadSpec(ref common.ReferenceCallb }, }, SchemaProps: spec.SchemaProps{ - Description: "PodGroups is the list of pod groups that make up the Workload. The maximum number of pod groups is 8. This field is immutable.", + Description: "PodGroupTemplates is the list of templates that make up the Workload. The maximum number of templates is 8. This field is immutable.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(schedulingv1alpha1.PodGroup{}.OpenAPIModelName()), + Ref: ref(schedulingv1alpha2.PodGroupTemplate{}.OpenAPIModelName()), }, }, }, }, }, }, - Required: []string{"podGroups"}, + Required: []string{"podGroupTemplates"}, }, }, Dependencies: []string{ - schedulingv1alpha1.PodGroup{}.OpenAPIModelName(), schedulingv1alpha1.TypedLocalObjectReference{}.OpenAPIModelName()}, + schedulingv1alpha2.PodGroupTemplate{}.OpenAPIModelName(), schedulingv1alpha2.TypedLocalObjectReference{}.OpenAPIModelName()}, } } @@ -55551,7 +55481,6 @@ func schema_k8sio_api_scheduling_v1beta1_PriorityClass(ref common.ReferenceCallb }, }, }, - Required: []string{"value"}, }, }, Dependencies: []string{ @@ -55795,7 +55724,7 @@ func schema_k8sio_api_storage_v1_CSIDriverSpec(ref common.ReferenceCallback) com }, "nodeAllocatableUpdatePeriodSeconds": { SchemaProps: spec.SchemaProps{ - Description: "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", + Description: "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis feature requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", Type: []string{"integer"}, Format: "int64", }, @@ -55807,6 +55736,13 @@ func schema_k8sio_api_storage_v1_CSIDriverSpec(ref common.ReferenceCallback) com Format: "", }, }, + "preventPodSchedulingIfMissing": { + SchemaProps: spec.SchemaProps{ + Description: "PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod scheduling if the CSI driver on the node is missing.\n\nEnabling this option will prevent the scheduler (or any other component which embeds default scheduler such as cluster-autoscaler) from scheduling pods to nodes where CSI driver is not installed.\n\nFor components(such as cluster-autoscaler) that embed the scheduler and run pod placement simulations using scheduler plugins, they MUST be aware of CSI driver registration information via CSINode object. They must create simulated CSINode objects in addition to Node objects during scheduling simulation, otherwise if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any newly created node may be rejected by the scheduler because of missing CSI driver information from the node.\n\nThis is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. Default is \"false\".", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, @@ -56680,7 +56616,7 @@ func schema_k8sio_api_storage_v1_VolumeError(ref common.ReferenceCallback) commo }, "errorCode": { SchemaProps: spec.SchemaProps{ - Description: "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", + Description: "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", Type: []string{"integer"}, Format: "int32", }, @@ -57392,6 +57328,13 @@ func schema_k8sio_api_storage_v1beta1_CSIDriverSpec(ref common.ReferenceCallback Format: "", }, }, + "preventPodSchedulingIfMissing": { + SchemaProps: spec.SchemaProps{ + Description: "PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod scheduling if the CSI driver on the node is missing.\n\nEnabling this option will prevent the scheduler (or any other component which embeds default scheduler such as cluster-autoscaler) from scheduling pods to nodes where CSI driver is not installed.\n\nFor components(such as cluster-autoscaler) that embed the scheduler and run pod placement simulations using scheduler plugins, they MUST be aware of CSI driver registration information via CSINode object. They must create simulated CSINode objects in addition to Node objects during scheduling simulation, otherwise if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any newly created node may be rejected by the scheduler because of missing CSI driver information from the node.\n\nThis is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. Default is \"false\".", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, @@ -62690,9 +62633,17 @@ func schema_pkg_apis_meta_v1_ListMeta(ref common.ReferenceCallback) common.OpenA Format: "int64", }, }, + "shardInfo": { + SchemaProps: spec.SchemaProps{ + Description: "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + Ref: ref(metav1.ShardInfo{}.OpenAPIModelName()), + }, + }, }, }, }, + Dependencies: []string{ + metav1.ShardInfo{}.OpenAPIModelName()}, } } @@ -62787,6 +62738,13 @@ func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.Op Format: "", }, }, + "shardSelector": { + SchemaProps: spec.SchemaProps{ + Description: "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -63368,6 +63326,28 @@ func schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref common.ReferenceCallb } } +func schema_pkg_apis_meta_v1_ShardInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"selector"}, + }, + }, + } +} + func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -64887,7 +64867,7 @@ func schema_pkg_apis_audit_v1_GroupResources(ref common.ReferenceCallback) commo Properties: map[string]spec.Schema{ "group": { SchemaProps: spec.SchemaProps{ - Description: "Group is the name of the API group that contains the resources. The empty string represents the core API group.", + Description: "Group is the name of the API group that contains the resources. The empty string represents the core API group. `*` matches all groups", Type: []string{"string"}, Format: "", }, @@ -65355,6 +65335,58 @@ func schema_server_flagz_api_v1alpha1_Flagz(ref common.ReferenceCallback) common } } +func schema_server_flagz_api_v1beta1_Flagz(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Flagz is the structured response for the /flagz endpoint.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "flags": { + SchemaProps: spec.SchemaProps{ + Description: "Flags contains the command-line flags and their values. The keys are the flag names and the values are the flag values, possibly with confidential values redacted.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + metav1.ObjectMeta{}.OpenAPIModelName()}, + } +} + func schema_server_statusz_api_v1alpha1_Statusz(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -65449,6 +65481,100 @@ func schema_server_statusz_api_v1alpha1_Statusz(ref common.ReferenceCallback) co } } +func schema_server_statusz_api_v1beta1_Statusz(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Statusz is a struct used for versioned statusz endpoint.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "startTime": { + SchemaProps: spec.SchemaProps{ + Description: "StartTime is the time the component process was initiated.", + Ref: ref(metav1.Time{}.OpenAPIModelName()), + }, + }, + "uptimeSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "UptimeSeconds is the duration in seconds for which the component has been running continuously.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "goVersion": { + SchemaProps: spec.SchemaProps{ + Description: "GoVersion is the version of the Go programming language used to build the binary. The format is not guaranteed to be consistent across different Go builds.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "binaryVersion": { + SchemaProps: spec.SchemaProps{ + Description: "BinaryVersion is the version of the component's binary. The format is not guaranteed to be semantic versioning and may be an arbitrary string.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationVersion": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationVersion is the Kubernetes API version which this component is emulating. if present, formatted as \".\"", + Type: []string{"string"}, + Format: "", + }, + }, + "paths": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Paths contains relative URLs to other essential read-only endpoints for debugging and troubleshooting.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"startTime", "uptimeSeconds", "binaryVersion"}, + }, + }, + Dependencies: []string{ + metav1.ObjectMeta{}.OpenAPIModelName(), metav1.Time{}.OpenAPIModelName()}, + } +} + func schema_pkg_apis_clientauthentication_v1_Cluster(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -66066,8 +66192,16 @@ func schema_controllers_node_config_v1alpha1_NodeControllerConfiguration(ref com Format: "int32", }, }, + "ConcurrentNodeStatusUpdates": { + SchemaProps: spec.SchemaProps{ + Description: "ConcurrentNodeStatusUpdates is the number of workers concurrently updating node statuses. If unspecified or 0, ConcurrentNodeSyncs is used instead", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, }, - Required: []string{"ConcurrentNodeSyncs"}, + Required: []string{"ConcurrentNodeSyncs", "ConcurrentNodeStatusUpdates"}, }, }, } @@ -67887,12 +68021,19 @@ func schema_k8sio_kube_controller_manager_config_v1alpha1_KubeControllerManagerC Ref: ref(kubecontrollermanagerconfigv1alpha1.DeviceTaintEvictionControllerConfiguration{}.OpenAPIModelName()), }, }, + "ResourceClaimController": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimControllerConfiguration contains elements configuring the resource claim controller.", + Default: map[string]interface{}{}, + Ref: ref(kubecontrollermanagerconfigv1alpha1.ResourceClaimControllerConfiguration{}.OpenAPIModelName()), + }, + }, }, - Required: []string{"Generic", "KubeCloudShared", "AttachDetachController", "CSRSigningController", "DaemonSetController", "DeploymentController", "StatefulSetController", "DeprecatedController", "EndpointController", "EndpointSliceController", "EndpointSliceMirroringController", "EphemeralVolumeController", "GarbageCollectorController", "HPAController", "JobController", "CronJobController", "LegacySATokenCleaner", "NamespaceController", "NodeIPAMController", "NodeLifecycleController", "PersistentVolumeBinderController", "PodGCController", "ReplicaSetController", "ReplicationController", "ResourceQuotaController", "SAController", "ServiceController", "TTLAfterFinishedController", "ValidatingAdmissionPolicyStatusController", "DeviceTaintEvictionController"}, + Required: []string{"Generic", "KubeCloudShared", "AttachDetachController", "CSRSigningController", "DaemonSetController", "DeploymentController", "StatefulSetController", "DeprecatedController", "EndpointController", "EndpointSliceController", "EndpointSliceMirroringController", "EphemeralVolumeController", "GarbageCollectorController", "HPAController", "JobController", "CronJobController", "LegacySATokenCleaner", "NamespaceController", "NodeIPAMController", "NodeLifecycleController", "PersistentVolumeBinderController", "PodGCController", "ReplicaSetController", "ReplicationController", "ResourceQuotaController", "SAController", "ServiceController", "TTLAfterFinishedController", "ValidatingAdmissionPolicyStatusController", "DeviceTaintEvictionController", "ResourceClaimController"}, }, }, Dependencies: []string{ - configv1alpha1.KubeCloudSharedConfiguration{}.OpenAPIModelName(), serviceconfigv1alpha1.ServiceControllerConfiguration{}.OpenAPIModelName(), controllermanagerconfigv1alpha1.GenericControllerManagerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.AttachDetachControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.CSRSigningControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.CronJobControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DaemonSetControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DeploymentControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DeprecatedControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DeviceTaintEvictionControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EndpointControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EndpointSliceControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EndpointSliceMirroringControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EphemeralVolumeControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.GarbageCollectorControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.HPAControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.JobControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.LegacySATokenCleanerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.NamespaceControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.NodeIPAMControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.NodeLifecycleControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.PersistentVolumeBinderControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.PodGCControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ReplicaSetControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ReplicationControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ResourceQuotaControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.SAControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.StatefulSetControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.TTLAfterFinishedControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration{}.OpenAPIModelName()}, + configv1alpha1.KubeCloudSharedConfiguration{}.OpenAPIModelName(), serviceconfigv1alpha1.ServiceControllerConfiguration{}.OpenAPIModelName(), controllermanagerconfigv1alpha1.GenericControllerManagerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.AttachDetachControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.CSRSigningControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.CronJobControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DaemonSetControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DeploymentControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DeprecatedControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.DeviceTaintEvictionControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EndpointControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EndpointSliceControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EndpointSliceMirroringControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.EphemeralVolumeControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.GarbageCollectorControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.HPAControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.JobControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.LegacySATokenCleanerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.NamespaceControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.NodeIPAMControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.NodeLifecycleControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.PersistentVolumeBinderControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.PodGCControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ReplicaSetControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ReplicationControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ResourceClaimControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ResourceQuotaControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.SAControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.StatefulSetControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.TTLAfterFinishedControllerConfiguration{}.OpenAPIModelName(), kubecontrollermanagerconfigv1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration{}.OpenAPIModelName()}, } } @@ -68233,6 +68374,28 @@ func schema_k8sio_kube_controller_manager_config_v1alpha1_ReplicationControllerC } } +func schema_k8sio_kube_controller_manager_config_v1alpha1_ResourceClaimControllerConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimControllerConfiguration contains elements configuring the resource claim controller.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ConcurrentSyncs": { + SchemaProps: spec.SchemaProps{ + Description: "ConcurrentSyncs is the number of operations (deleting a pod, updating a ResourcClaim status, etc.) that will be done concurrently. Larger number = processing, but more CPU (and network) load.\n\nThe default is 50.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"ConcurrentSyncs"}, + }, + }, + } +} + func schema_k8sio_kube_controller_manager_config_v1alpha1_ResourceQuotaControllerConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -69659,7 +69822,7 @@ func schema_k8sio_kube_scheduler_config_v1_Plugin(ref common.ReferenceCallback) return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Plugin specifies a plugin name and its weight when applicable. Weight is used only for Score plugins.", + Description: "Plugin specifies a plugin name and its weight when applicable. Weight is used only for Score and PlacementScore plugins.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "name": { @@ -69672,7 +69835,7 @@ func schema_k8sio_kube_scheduler_config_v1_Plugin(ref common.ReferenceCallback) }, "weight": { SchemaProps: spec.SchemaProps{ - Description: "Weight defines the weight of plugin, only used for Score plugins.", + Description: "Weight defines the weight of plugin, only used for Score and PlacementScore plugins.", Type: []string{"integer"}, Format: "int32", }, @@ -69868,6 +70031,20 @@ func schema_k8sio_kube_scheduler_config_v1_Plugins(ref common.ReferenceCallback) Ref: ref(configv1.PluginSet{}.OpenAPIModelName()), }, }, + "placementGenerate": { + SchemaProps: spec.SchemaProps{ + Description: "PlacementGenerate is a list of plugins that should be invoked during pod group scheduling cycle when determining placements for a pod group.", + Default: map[string]interface{}{}, + Ref: ref(configv1.PluginSet{}.OpenAPIModelName()), + }, + }, + "placementScore": { + SchemaProps: spec.SchemaProps{ + Description: "PlacementScore is a list of plugins that should be invoked during workload scheduling cycle when ranking pod group assignments.", + Default: map[string]interface{}{}, + Ref: ref(configv1.PluginSet{}.OpenAPIModelName()), + }, + }, }, }, }, @@ -70461,14 +70638,19 @@ func schema_kubectl_pkg_config_v1beta1_AllowlistEntry(ref common.ReferenceCallba Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name matching is performed by first resolving the absolute path of both the plugin and the name in the allowlist entry using `exec.LookPath`. It will be called on both, and the resulting strings must be equal. If either call to `exec.LookPath` results in an error, the `Name` check will be considered a failure.", - Default: "", + Description: "Name matching is performed by first resolving the absolute path of both the plugin and the name in the allowlist entry using `exec.LookPath`. It will be called on both, and the resulting strings must be equal. If either call to `exec.LookPath` results in an error, the `Name` check will be considered a failure.\n\nDeprecated: use Command instead.", + Type: []string{"string"}, + Format: "", + }, + }, + "command": { + SchemaProps: spec.SchemaProps{ + Description: "Command matching is performed by first resolving the absolute path of both the plugin and the name in the allowlist entry using `exec.LookPath`. It will be called on both, and the resulting strings must be equal. If either call to `exec.LookPath` results in an error, the `Command` check will be considered a failure.", Type: []string{"string"}, Format: "", }, }, }, - Required: []string{"name"}, }, }, } @@ -71894,6 +72076,21 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen }, }, }, + "tlsCurvePreferences": { + SchemaProps: spec.SchemaProps{ + Description: "tlsCurvePreferences is the set of allowed key exchange mechanisms for the server, specified as numeric Go crypto/tls CurveID values. 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 empty, the default Go curves will be used. Default: nil", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, "tlsMinVersion": { SchemaProps: spec.SchemaProps{ Description: "tlsMinVersion is the minimum TLS version supported. Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). Default: \"\"", @@ -72068,7 +72265,7 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen }, "imageMinimumGCAge": { SchemaProps: spec.SchemaProps{ - Description: "imageMinimumGCAge is the minimum age for an unused image before it is garbage collected. Default: \"2m\"", + Description: "imageMinimumGCAge is the minimum age for an unused image before it is garbage collected. The field value must be greater than 0. If unset or 0, defaults to 2m. Default: \"2m\"", Ref: ref(metav1.Duration{}.OpenAPIModelName()), }, }, @@ -72584,7 +72781,7 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen }, "enforceNodeAllocatable": { SchemaProps: spec.SchemaProps{ - Description: "This flag specifies the various Node Allocatable enforcements that Kubelet needs to perform. This flag accepts a list of options. Acceptable options are `none`, `pods`, `system-reserved` and `kube-reserved`. If `none` is specified, no other options may be specified. When `system-reserved` is in the list, systemReservedCgroup must be specified. When `kube-reserved` is in the list, kubeReservedCgroup must be specified. This field is supported only when `cgroupsPerQOS` is set to true. Refer to [Node Allocatable](https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#node-allocatable) for more information. Default: [\"pods\"]", + Description: "This flag specifies the various Node Allocatable enforcements that Kubelet needs to perform. This flag accepts a list of options. Acceptable options are `none`, `pods`, `system-reserved`, `system-reserved-compressible`, `kube-reserved`, and `kube-reserved-compressible`. If `none` is specified, no other options may be specified. When a `system-reserved` option is in the list, systemReservedCgroup must be specified. When a `kube-reserved` option is in the list, kubeReservedCgroup must be specified. If a `compressible` option is specified, the corresponding non-compressible option may not be specified. This field is supported only when `cgroupsPerQOS` is set to true. Refer to [Node Allocatable](https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#node-allocatable) for more information. Default: [\"pods\"]", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -72729,6 +72926,13 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen Format: "double", }, }, + "memoryReservationPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "MemoryReservationPolicy controls how the kubelet applies cgroup v2 memory protection. \"None\" (default): The kubelet does not set memory.min for containers and pods, ensuring no hard memory is locked by the kernel. \"TieredReservation\": The kubelet sets cgroup v2 memory.min for Guaranteed pods and memory.low for Burstable pods based on memory requests. Guaranteed memory is never reclaimed by the kernel; Burstable memory is preferentially retained but may be reclaimed under extreme pressure. See https://kep.k8s.io/2570 for more details. Default: None", + Type: []string{"string"}, + Format: "", + }, + }, "registerWithTaints": { SchemaProps: spec.SchemaProps{ Description: "registerWithTaints are an array of taints to add to a node object when the kubelet registers itself. This only takes effect when registerNode is true and upon the initial registration of the node. Default: nil", diff --git a/pkg/kubeapiserver/OWNERS b/pkg/kubeapiserver/OWNERS index 845132ed12a8c..09b1ec47c6d8a 100644 --- a/pkg/kubeapiserver/OWNERS +++ b/pkg/kubeapiserver/OWNERS @@ -10,7 +10,6 @@ reviewers: - liggitt - sttts - cheftako - - logicalhan - jpbetz labels: - sig/api-machinery diff --git a/pkg/kubeapiserver/admission/initializer.go b/pkg/kubeapiserver/admission/initializer.go index c90a0dbfe3462..f5dd071c3d882 100644 --- a/pkg/kubeapiserver/admission/initializer.go +++ b/pkg/kubeapiserver/admission/initializer.go @@ -17,22 +17,71 @@ limitations under the License. package admission import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + policyloader "k8s.io/kubernetes/pkg/admission/plugin/policy/manifest/loader" + webhookloader "k8s.io/kubernetes/pkg/admission/plugin/webhook/manifest/loader" ) // TODO add a `WantsToRun` which takes a stopCh. Might make it generic. // PluginInitializer is used for initialization of the Kubernetes specific admission plugins. type PluginInitializer struct { + loaders *initializer.ManifestLoaders } var _ admission.PluginInitializer = &PluginInitializer{} // NewPluginInitializer constructs new instance of PluginInitializer func NewPluginInitializer() *PluginInitializer { - return &PluginInitializer{} + return &PluginInitializer{ + loaders: newManifestLoaders(), + } } // Initialize checks the initialization interfaces implemented by each plugin // and provide the appropriate initialization data -func (i *PluginInitializer) Initialize(plugin admission.Interface) {} +func (i *PluginInitializer) Initialize(plugin admission.Interface) { + if wants, ok := plugin.(initializer.WantsManifestLoaders); ok { + wants.SetManifestLoaders(i.loaders) + } +} + +func newManifestLoaders() *initializer.ManifestLoaders { + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return &initializer.ManifestLoaders{} + } + return &initializer.ManifestLoaders{ + LoadValidatingWebhookManifests: func(dir string) ([]*admissionregistrationv1.ValidatingWebhookConfiguration, string, error) { + result, err := webhookloader.LoadValidatingManifests(dir) + if err != nil { + return nil, "", err + } + return result.Configurations, result.Hash, nil + }, + LoadMutatingWebhookManifests: func(dir string) ([]*admissionregistrationv1.MutatingWebhookConfiguration, string, error) { + result, err := webhookloader.LoadMutatingManifests(dir) + if err != nil { + return nil, "", err + } + return result.Configurations, result.Hash, nil + }, + LoadValidatingPolicyManifests: func(dir string) ([]*admissionregistrationv1.ValidatingAdmissionPolicy, []*admissionregistrationv1.ValidatingAdmissionPolicyBinding, string, error) { + manifests, err := policyloader.LoadValidatingManifestsFromDirectory(dir) + if err != nil { + return nil, nil, "", err + } + return manifests.Policies, manifests.Bindings, manifests.Hash, nil + }, + LoadMutatingPolicyManifests: func(dir string) ([]*admissionregistrationv1.MutatingAdmissionPolicy, []*admissionregistrationv1.MutatingAdmissionPolicyBinding, string, error) { + manifests, err := policyloader.LoadMutatingManifestsFromDirectory(dir) + if err != nil { + return nil, nil, "", err + } + return manifests.Policies, manifests.Bindings, manifests.Hash, nil + }, + } +} diff --git a/pkg/kubeapiserver/authorizer/reload.go b/pkg/kubeapiserver/authorizer/reload.go index f37704287fd6f..050b08bd47415 100644 --- a/pkg/kubeapiserver/authorizer/reload.go +++ b/pkg/kubeapiserver/authorizer/reload.go @@ -39,6 +39,7 @@ import ( authorizationmetrics "k8s.io/apiserver/pkg/authorization/metrics" "k8s.io/apiserver/pkg/authorization/union" "k8s.io/apiserver/pkg/server/options/authorizationconfig/metrics" + "k8s.io/apiserver/pkg/util/filesystem" webhookutil "k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/plugin/pkg/authorizer/webhook" webhookmetrics "k8s.io/apiserver/plugin/pkg/authorizer/webhook/metrics" @@ -46,7 +47,6 @@ import ( "k8s.io/kubernetes/pkg/auth/authorizer/abac" "k8s.io/kubernetes/pkg/auth/nodeidentifier" "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" - "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" ) diff --git a/pkg/kubeapiserver/default_storage_factory_builder.go b/pkg/kubeapiserver/default_storage_factory_builder.go index 6cf4688bfc8b9..9c250886c7144 100644 --- a/pkg/kubeapiserver/default_storage_factory_builder.go +++ b/pkg/kubeapiserver/default_storage_factory_builder.go @@ -39,7 +39,6 @@ import ( "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/resource" "k8s.io/kubernetes/pkg/apis/scheduling" - "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/apis/storagemigration" ) @@ -82,16 +81,16 @@ func NewStorageFactoryConfigEffectiveVersion(effectiveVersion basecompatibility. // TODO (https://github.com/kubernetes/kubernetes/issues/108451): remove the override in 1.25. // apisstorage.Resource("csistoragecapacities").WithVersion("v1beta1"), coordination.Resource("leasecandidates").WithVersion("v1beta1"), - admissionregistration.Resource("mutatingadmissionpolicies").WithVersion("v1beta1"), - admissionregistration.Resource("mutatingadmissionpolicybindings").WithVersion("v1beta1"), + admissionregistration.Resource("mutatingadmissionpolicies").WithVersion("v1beta1"), // TODO: remove in 1.37. + admissionregistration.Resource("mutatingadmissionpolicybindings").WithVersion("v1beta1"), // TODO: remove in 1.37. certificates.Resource("clustertrustbundles").WithVersion("v1beta1"), certificates.Resource("podcertificaterequests").WithVersion("v1beta1"), - storage.Resource("volumeattributesclasses").WithVersion("v1beta1"), storagemigration.Resource("storagemigrations").WithVersion("v1beta1"), resource.Resource("devicetaintrules").WithVersion("v1alpha3"), - scheduling.Resource("workloads").WithVersion("v1alpha1"), + resource.Resource("resourcepoolstatusrequests").WithVersion("v1alpha3"), + scheduling.Resource("workloads").WithVersion("v1alpha2"), + scheduling.Resource("podgroups").WithVersion("v1alpha2"), } - return &StorageFactoryConfig{ Serializer: legacyscheme.Codecs, DefaultResourceEncoding: serverstorage.NewDefaultResourceEncodingConfigForEffectiveVersion(legacyscheme.Scheme, effectiveVersion), diff --git a/pkg/kubeapiserver/options/authentication.go b/pkg/kubeapiserver/options/authentication.go index 9b202d9d728ff..4e7b630d9130a 100644 --- a/pkg/kubeapiserver/options/authentication.go +++ b/pkg/kubeapiserver/options/authentication.go @@ -47,6 +47,7 @@ import ( genericoptions "k8s.io/apiserver/pkg/server/options" authenticationconfigmetrics "k8s.io/apiserver/pkg/server/options/authenticationconfig/metrics" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/apiserver/pkg/util/filesystem" "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -61,7 +62,6 @@ import ( kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" "k8s.io/kubernetes/pkg/serviceaccount" - "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap" "k8s.io/utils/ptr" ) diff --git a/pkg/kubeapiserver/options/authorization.go b/pkg/kubeapiserver/options/authorization.go index 2edfa11309dd6..b1e8272dd57be 100644 --- a/pkg/kubeapiserver/options/authorization.go +++ b/pkg/kubeapiserver/options/authorization.go @@ -22,8 +22,6 @@ import ( "time" authorizationcel "k8s.io/apiserver/pkg/authorization/cel" - genericfeatures "k8s.io/apiserver/pkg/features" - utilfeature "k8s.io/apiserver/pkg/util/feature" "github.com/spf13/pflag" @@ -108,10 +106,6 @@ func (o *BuiltInAuthorizationOptions) Validate() []error { // - the config file can be loaded // - the config file represents a valid configuration if o.AuthorizationConfigurationFile != "" { - if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) { - return append(allErrors, fmt.Errorf("--%s cannot be used without enabling StructuredAuthorizationConfiguration feature flag", authorizationConfigFlag)) - } - // error out if legacy flags are defined if o.AreLegacyFlagsSet != nil && o.AreLegacyFlagsSet() { return append(allErrors, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag)) @@ -193,7 +187,6 @@ func (o *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.AuthorizationConfigurationFile, authorizationConfigFlag, o.AuthorizationConfigurationFile, ""+ "File with Authorization Configuration to configure the authorizer chain. "+ - "Requires feature gate StructuredAuthorizationConfiguration. "+ "This flag is mutually exclusive with the other --authorization-mode and --authorization-webhook-* flags.") // preserves compatibility with any method set during initialization @@ -229,9 +222,6 @@ func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFac // else, // - build the AuthorizationConfig from the legacy flags if o.AuthorizationConfigurationFile != "" { - if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) { - return nil, fmt.Errorf("--%s cannot be used without enabling StructuredAuthorizationConfiguration feature flag", authorizationConfigFlag) - } // error out if legacy flags are defined if o.AreLegacyFlagsSet != nil && o.AreLegacyFlagsSet() { return nil, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag) diff --git a/pkg/kubeapiserver/options/plugins.go b/pkg/kubeapiserver/options/plugins.go index 4fb7d0b5b8238..1e9828bb6a2ae 100644 --- a/pkg/kubeapiserver/options/plugins.go +++ b/pkg/kubeapiserver/options/plugins.go @@ -39,6 +39,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration" "k8s.io/kubernetes/plugin/pkg/admission/gc" "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy" + jobadmission "k8s.io/kubernetes/plugin/pkg/admission/job" "k8s.io/kubernetes/plugin/pkg/admission/limitranger" "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision" "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists" @@ -47,11 +48,14 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/nodedeclaredfeatures" "k8s.io/kubernetes/plugin/pkg/admission/noderestriction" "k8s.io/kubernetes/plugin/pkg/admission/nodetaint" + "k8s.io/kubernetes/plugin/pkg/admission/podgroup" "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector" + "k8s.io/kubernetes/plugin/pkg/admission/podresize" "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction" "k8s.io/kubernetes/plugin/pkg/admission/podtopologylabels" podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority" "k8s.io/kubernetes/plugin/pkg/admission/runtimeclass" + "k8s.io/kubernetes/plugin/pkg/admission/scheduling/podgroupprotection" "k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize" @@ -88,6 +92,7 @@ var AllOrderedPlugins = []string{ extendedresourcetoleration.PluginName, // ExtendedResourceToleration setdefault.PluginName, // DefaultStorageClass storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection + podgroupprotection.PluginName, // PodGroupProtection gc.PluginName, // OwnerReferencesPermissionEnforcement resize.PluginName, // PersistentVolumeClaimResize runtimeclass.PluginName, // RuntimeClass @@ -98,7 +103,10 @@ var AllOrderedPlugins = []string{ defaultingressclass.PluginName, // DefaultIngressClass denyserviceexternalips.PluginName, // DenyServiceExternalIPs podtopologylabels.PluginName, // PodTopologyLabels + podgroup.PluginName, // PodGroupWorkloadExists nodedeclaredfeatures.PluginName, // NodeDeclaredFeatureValidator + jobadmission.PluginName, // JobValidation, only active when feature gate WorkloadWithJob is enabled. + podresize.PluginName, // PodResizeValidator // new admission plugins should generally be inserted above here // webhook, resourcequota, and deny plugins must go at the end @@ -146,12 +154,16 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { setdefault.Register(plugins) resize.Register(plugins) storageobjectinuseprotection.Register(plugins) + podgroupprotection.Register(plugins) certapproval.Register(plugins) certsigning.Register(plugins) ctbattest.Register(plugins) certsubjectrestriction.Register(plugins) podtopologylabels.Register(plugins) + podgroup.Register(plugins) nodedeclaredfeatures.Register(plugins) + jobadmission.Register(plugins) + podresize.Register(plugins) } // DefaultOffAdmissionPlugins get admission plugins off by default for kube-apiserver. @@ -167,6 +179,7 @@ func DefaultOffAdmissionPlugins() sets.Set[string] { validatingwebhook.PluginName, // ValidatingAdmissionWebhook resourcequota.PluginName, // ResourceQuota storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection + podgroupprotection.PluginName, // PodGroupProtection podpriority.PluginName, // Priority nodetaint.PluginName, // TaintNodesByCondition runtimeclass.PluginName, // RuntimeClass @@ -179,7 +192,10 @@ func DefaultOffAdmissionPlugins() sets.Set[string] { podtopologylabels.PluginName, // PodTopologyLabels, only active when feature gate PodTopologyLabelsAdmission is enabled. mutatingadmissionpolicy.PluginName, // Mutatingadmissionpolicy, only active when feature gate MutatingAdmissionpolicy is enabled validatingadmissionpolicy.PluginName, // ValidatingAdmissionPolicy, only active when feature gate ValidatingAdmissionPolicy is enabled + podgroup.PluginName, // PodGroupWorkloadExists, only active when feature gate GenericWorkload is enabled nodedeclaredfeatures.PluginName, // NodeDeclaredFeatureValidator, only active when feature gate NodeDeclaredFeatures is enabled + jobadmission.PluginName, // JobValidation, only active when feature gate WorkloadWithJob is enabled + podresize.PluginName, // PodResizeValidator, only active when feature gate InPlacePodVerticalScaling is enabled ) return sets.New(AllOrderedPlugins...).Difference(defaultOnPlugins) diff --git a/pkg/kubectl/.import-restrictions b/pkg/kubectl/.import-restrictions index 67a3ce0ef8135..2d5a211115ddd 100644 --- a/pkg/kubectl/.import-restrictions +++ b/pkg/kubectl/.import-restrictions @@ -83,7 +83,7 @@ rules: - k8s.io/kubernetes/pkg/apis/resource/v1alpha3 - k8s.io/kubernetes/pkg/apis/scheduling - k8s.io/kubernetes/pkg/apis/scheduling/install - - k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1 + - k8s.io/kubernetes/pkg/apis/scheduling/v1alpha2 - k8s.io/kubernetes/pkg/apis/settings - k8s.io/kubernetes/pkg/apis/settings/install - k8s.io/kubernetes/pkg/apis/settings/v1alpha1 diff --git a/pkg/kubectl/cmd/convert/convert_test.go b/pkg/kubectl/cmd/convert/convert_test.go index fc4f3002e7fb6..8b2c6f1883d8e 100644 --- a/pkg/kubectl/cmd/convert/convert_test.go +++ b/pkg/kubectl/cmd/convert/convert_test.go @@ -61,35 +61,6 @@ func TestConvertObject(t *testing.T) { }, }, }, - { - name: "v1 HPA to v2beta1 HPA", - file: "../../../../test/fixtures/pkg/kubectl/cmd/convert/v1HPA.yaml", - outputVersion: "autoscaling/v2beta1", - fields: []checkField{ - { - expected: "apiVersion: autoscaling/v2beta1", - }, - { - expected: "name: cpu", - }, - { - expected: "targetAverageUtilization: 50", - }, - }, - }, - { - name: "v2beta1 HPA to v1 HPA", - file: "../../../../test/fixtures/pkg/kubectl/cmd/convert/v2beta1HPA.yaml", - outputVersion: "autoscaling/v1", - fields: []checkField{ - { - expected: "apiVersion: autoscaling/v1", - }, - { - expected: "targetCPUUtilizationPercentage: 50", - }, - }, - }, { name: "v1beta1 Ingress to extensions Ingress", file: "../../../../test/fixtures/pkg/kubectl/cmd/convert/v1beta1ingress.yaml", diff --git a/pkg/kubelet/allocation/allocation_manager.go b/pkg/kubelet/allocation/allocation_manager.go index a6167f269466e..33857d112c823 100644 --- a/pkg/kubelet/allocation/allocation_manager.go +++ b/pkg/kubelet/allocation/allocation_manager.go @@ -18,7 +18,6 @@ package allocation import ( "context" - "fmt" "path/filepath" "slices" "sync" @@ -34,21 +33,15 @@ import ( resourcehelper "k8s.io/component-helpers/resource" "k8s.io/klog/v2" podutil "k8s.io/kubernetes/pkg/api/v1/pod" - "k8s.io/kubernetes/pkg/api/v1/resource" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/allocation/state" - "k8s.io/kubernetes/pkg/kubelet/cm" - "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" - "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager" "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/events" "k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/pkg/kubelet/status" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" - "k8s.io/kubernetes/pkg/kubelet/util/format" ) // podStatusManagerStateFile is the file name where status manager stores its state @@ -58,10 +51,11 @@ const ( initialRetryDelay = 30 * time.Second retryDelay = 3 * time.Minute - TriggerReasonPodResized = "pod_resized" - TriggerReasonPodUpdated = "pod_updated" - TriggerReasonPodsAdded = "pods_added" - TriggerReasonPodsRemoved = "pods_removed" + TriggerReasonPodResized = "pod_resized" + TriggerReasonPodUpdated = "pod_updated" + TriggerReasonPodsAdded = "pods_added" + TriggerReasonPodsRemoved = "pods_removed" + TriggerReasonPodTerminated = "pod_terminated" triggerReasonPeriodic = "periodic_retry" ) @@ -121,12 +115,10 @@ type Manager interface { type manager struct { allocated state.State - admitHandlers lifecycle.PodAdmitHandlers - containerRuntime kubecontainer.Runtime - statusManager status.Manager - sourcesReady config.SourcesReady - nodeConfig cm.NodeConfig - nodeAllocatableAbsolute v1.ResourceList + admitHandlers lifecycle.PodAdmitHandlers + containerRuntime kubecontainer.Runtime + statusManager status.Manager + sourcesReady config.SourcesReady ticker *time.Ticker triggerPodSync func(pod *v1.Pod) @@ -136,27 +128,26 @@ type manager struct { allocationMutex sync.Mutex podsWithPendingResizes []types.UID - recorder record.EventRecorder + recorder record.EventRecorderLogger } func NewManager(checkpointDirectory string, - nodeConfig cm.NodeConfig, - nodeAllocatableAbsolute v1.ResourceList, statusManager status.Manager, triggerPodSync func(pod *v1.Pod), getActivePods func() []*v1.Pod, getPodByUID func(types.UID) (*v1.Pod, bool), sourcesReady config.SourcesReady, - recorder record.EventRecorder, + recorder record.EventRecorderLogger, ) Manager { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() return &manager{ - allocated: newStateImpl(checkpointDirectory, allocatedPodsStateFile), + allocated: newStateImpl(logger, checkpointDirectory, allocatedPodsStateFile), - statusManager: statusManager, - admitHandlers: lifecycle.PodAdmitHandlers{}, - sourcesReady: sourcesReady, - nodeConfig: nodeConfig, - nodeAllocatableAbsolute: nodeAllocatableAbsolute, + statusManager: statusManager, + admitHandlers: lifecycle.PodAdmitHandlers{}, + sourcesReady: sourcesReady, ticker: time.NewTicker(initialRetryDelay), triggerPodSync: triggerPodSync, @@ -166,7 +157,7 @@ func NewManager(checkpointDirectory string, } } -func newStateImpl(checkpointDirectory, checkpointName string) state.State { +func newStateImpl(logger klog.Logger, checkpointDirectory, checkpointName string) state.State { if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { return state.NewNoopStateCheckpoint() } @@ -174,7 +165,7 @@ func newStateImpl(checkpointDirectory, checkpointName string) state.State { stateImpl, err := state.NewStateCheckpoint(checkpointDirectory, checkpointName) if err != nil { // This is a critical, non-recoverable failure. - klog.ErrorS(err, "Failed to initialize allocation checkpoint manager", + logger.Error(err, "Failed to initialize allocation checkpoint manager", "checkpointPath", filepath.Join(checkpointDirectory, checkpointName)) panic(err) } @@ -184,23 +175,20 @@ func newStateImpl(checkpointDirectory, checkpointName string) state.State { // NewInMemoryManager returns an allocation manager that doesn't persist state. // For testing purposes only! -func NewInMemoryManager(nodeConfig cm.NodeConfig, - nodeAllocatableAbsolute v1.ResourceList, +func NewInMemoryManager( statusManager status.Manager, triggerPodSync func(pod *v1.Pod), getActivePods func() []*v1.Pod, getPodByUID func(types.UID) (*v1.Pod, bool), sourcesReady config.SourcesReady, - recorder record.EventRecorder, + recorder record.EventRecorderLogger, ) Manager { return &manager{ allocated: state.NewStateMemory(nil), - statusManager: statusManager, - admitHandlers: lifecycle.PodAdmitHandlers{}, - sourcesReady: sourcesReady, - nodeConfig: nodeConfig, - nodeAllocatableAbsolute: nodeAllocatableAbsolute, + statusManager: statusManager, + admitHandlers: lifecycle.PodAdmitHandlers{}, + sourcesReady: sourcesReady, ticker: time.NewTicker(initialRetryDelay), triggerPodSync: triggerPodSync, @@ -213,12 +201,13 @@ func NewInMemoryManager(nodeConfig cm.NodeConfig, func (m *manager) Run(ctx context.Context) { // Start a goroutine to periodically check for pending resizes and process them if needed. go func() { + logger := klog.FromContext(ctx) for { select { case <-m.ticker.C: - successfulResizes := m.retryPendingResizes(triggerReasonPeriodic) + successfulResizes := m.retryPendingResizes(logger, triggerReasonPeriodic) for _, po := range successfulResizes { - klog.InfoS("Successfully retried resize after timeout", "pod", klog.KObj(po)) + logger.Info("Successfully retried resize after timeout", "pod", klog.KObj(po)) } case <-ctx.Done(): m.ticker.Stop() @@ -229,15 +218,18 @@ func (m *manager) Run(ctx context.Context) { } func (m *manager) RetryPendingResizes(trigger string) { - m.retryPendingResizes(trigger) + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + m.retryPendingResizes(logger, trigger) } -func (m *manager) retryPendingResizes(trigger string) []*v1.Pod { +func (m *manager) retryPendingResizes(logger klog.Logger, trigger string) []*v1.Pod { m.allocationMutex.Lock() defer m.allocationMutex.Unlock() if !m.sourcesReady.AllReady() { - klog.V(4).InfoS("Skipping evaluation of pending resizes; sources are not ready") + logger.V(4).Info("Skipping evaluation of pending resizes; sources are not ready") m.ticker.Reset(initialRetryDelay) return nil } @@ -251,25 +243,25 @@ func (m *manager) retryPendingResizes(trigger string) []*v1.Pod { for _, uid := range m.podsWithPendingResizes { pod, found := m.getPodByUID(uid) if !found { - klog.V(4).InfoS("Pod not found; removing from pending resizes", "podUID", uid) + logger.V(4).Info("Pod not found; removing from pending resizes", "podUID", uid) continue } oldResizeStatus := m.statusManager.GetPodResizeConditions(uid) isDeferred := m.statusManager.IsPodResizeDeferred(uid) - resizeAllocated, err := m.handlePodResourcesResize(pod) + resizeAllocated, err := m.handlePodResourcesResize(logger, pod) switch { case err != nil: - klog.ErrorS(err, "Failed to handle pod resources resize", "pod", klog.KObj(pod)) + logger.Error(err, "Failed to handle pod resources resize", "pod", klog.KObj(pod)) newPendingResizes = append(newPendingResizes, uid) case m.statusManager.IsPodResizeDeferred(uid): - klog.V(4).InfoS("Pod resize is deferred; will reevaluate later", "pod", klog.KObj(pod)) + logger.V(4).Info("Pod resize is deferred; will reevaluate later", "pod", klog.KObj(pod)) newPendingResizes = append(newPendingResizes, uid) case m.statusManager.IsPodResizeInfeasible(uid): - klog.V(4).InfoS("Pod resize is infeasible", "pod", klog.KObj(pod)) + logger.V(4).Info("Pod resize is infeasible", "pod", klog.KObj(pod)) default: - klog.V(4).InfoS("Pod resize successfully allocated", "pod", klog.KObj(pod)) + logger.V(4).Info("Pod resize successfully allocated", "pod", klog.KObj(pod)) successfulResizes = append(successfulResizes, pod) if isDeferred { metrics.PodDeferredAcceptedResizes.WithLabelValues(trigger).Inc() @@ -288,6 +280,9 @@ func (m *manager) retryPendingResizes(trigger string) []*v1.Pod { } func (m *manager) PushPendingResize(uid types.UID) { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() m.allocationMutex.Lock() defer m.allocationMutex.Unlock() @@ -300,7 +295,7 @@ func (m *manager) PushPendingResize(uid types.UID) { // Add the pod to the pending resizes list and sort by priority. m.podsWithPendingResizes = append(m.podsWithPendingResizes, uid) - m.sortPendingResizes() + m.sortPendingResizes(logger) } // sortPendingResizes sorts the list of pending resizes: @@ -308,12 +303,12 @@ func (m *manager) PushPendingResize(uid types.UID) { // - Second, based on the pod's PriorityClass. // - Third, based on the pod's QoS class. // - Last, prioritizing resizes that have been in the deferred state the longest. -func (m *manager) sortPendingResizes() { +func (m *manager) sortPendingResizes(logger klog.Logger) { var pendingPods []*v1.Pod for _, uid := range m.podsWithPendingResizes { pod, found := m.getPodByUID(uid) if !found { - klog.V(4).InfoS("Pod not found; removing from pending resizes", "podUID", uid) + logger.V(4).Info("Pod not found; removing from pending resizes", "podUID", uid) continue } pendingPods = append(pendingPods, pod) @@ -490,7 +485,16 @@ func updatePodFromAllocation(pod *v1.Pod, allocated state.PodResourceInfo) (*v1. // SetAllocatedResources checkpoints the resources allocated to a pod's containers func (m *manager) SetAllocatedResources(pod *v1.Pod) error { - return m.allocated.SetPodResourceInfo(pod.UID, allocationFromPod(pod)) + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + return m.allocated.SetPodResourceInfo(logger, pod.UID, allocationFromPod(pod)) +} + +// hasPodAllocatedResources returns whether a pod has been allocated. +func (m *manager) hasPodAllocatedResources(pod *v1.Pod) bool { + _, allocated := m.allocated.GetPodResourceInfo(pod.UID) + return allocated } func allocationFromPod(pod *v1.Pod) state.PodResourceInfo { @@ -499,18 +503,14 @@ func allocationFromPod(pod *v1.Pod) state.PodResourceInfo { podAlloc.PodLevelResources = pod.Spec.Resources.DeepCopy() } podAlloc.ContainerResources = make(map[string]v1.ResourceRequirements) - for _, container := range pod.Spec.Containers { + for container, containerType := range podutil.ContainerIter(&pod.Spec, podutil.InitContainers|podutil.Containers) { + if !IsResizableContainer(container, containerType) { + continue + } alloc := *container.Resources.DeepCopy() podAlloc.ContainerResources[container.Name] = alloc } - for _, container := range pod.Spec.InitContainers { - if podutil.IsRestartableInitContainer(&container) { - alloc := *container.Resources.DeepCopy() - podAlloc.ContainerResources[container.Name] = alloc - } - } - return podAlloc } @@ -525,6 +525,9 @@ func (m *manager) SetContainerRuntime(runtime kubecontainer.Runtime) { } func (m *manager) AddPod(activePods []*v1.Pod, pod *v1.Pod) (bool, string, string) { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() m.allocationMutex.Lock() defer m.allocationMutex.Unlock() @@ -536,13 +539,13 @@ func (m *manager) AddPod(activePods []*v1.Pod, pod *v1.Pod) (bool, string, strin // Check if we can admit the pod; if so, update the allocation. allocatedPods := m.getAllocatedPods(activePods) - ok, reason, message := m.canAdmitPod(allocatedPods, pod) + ok, reason, message := m.canAdmitPod(logger, allocatedPods, pod, lifecycle.AddOperation) if ok && utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { // Checkpoint the resource values at which the Pod has been admitted or resized. if err := m.SetAllocatedResources(pod); err != nil { // TODO(vinaykul,InPlacePodVerticalScaling): Can we recover from this in some way? Investigate - klog.ErrorS(err, "SetPodAllocation failed", "pod", klog.KObj(pod)) + logger.Error(err, "SetPodAllocation failed", "pod", klog.KObj(pod)) } } @@ -550,9 +553,12 @@ func (m *manager) AddPod(activePods []*v1.Pod, pod *v1.Pod) (bool, string, strin } func (m *manager) RemovePod(uid types.UID) { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() if err := m.allocated.RemovePod(uid); err != nil { // If the deletion fails, it will be retried by RemoveOrphanedPods, so we can safely ignore the error. - klog.V(3).ErrorS(err, "Failed to delete pod allocation", "podUID", uid) + logger.V(3).Info("Failed to delete pod allocation", "podUID", uid, "err", err) } } @@ -560,43 +566,21 @@ func (m *manager) RemoveOrphanedPods(remainingPods sets.Set[types.UID]) { m.allocated.RemoveOrphanedPods(remainingPods) } -func (m *manager) handlePodResourcesResize(pod *v1.Pod) (bool, error) { - allocatedPod, updated := m.UpdatePodFromAllocation(pod) +func (m *manager) handlePodResourcesResize(logger klog.Logger, pod *v1.Pod) (bool, error) { + _, updated := m.UpdatePodFromAllocation(pod) if !updated { // Desired resources == allocated resources. Pod allocation does not need to be updated. m.statusManager.ClearPodResizePendingCondition(pod.UID) return false, nil - - } else if resizable, msg, reason := IsInPlacePodVerticalScalingAllowed(pod); !resizable { - // If there is a pending resize but the resize is not allowed, always use the allocated resources. - metrics.PodInfeasibleResizes.WithLabelValues(reason).Inc() - m.statusManager.SetPodResizePendingCondition(pod.UID, v1.PodReasonInfeasible, msg, pod.Generation) - return false, nil - - } else if resizeNotAllowed, msg := disallowResizeForSwappableContainers(m.containerRuntime, pod, allocatedPod); resizeNotAllowed { - // If this resize involve swap recalculation, set as infeasible, as IPPR with swap is not supported for beta. - metrics.PodInfeasibleResizes.WithLabelValues("swap_limitation").Inc() - m.statusManager.SetPodResizePendingCondition(pod.UID, v1.PodReasonInfeasible, msg, pod.Generation) - return false, nil - } - - if !apiequality.Semantic.DeepEqual(pod.Spec.Resources, allocatedPod.Spec.Resources) { - if resizable, msg, reason := IsInPlacePodLevelResourcesVerticalScalingAllowed(pod); !resizable { - // If there is a pending pod-level resources resize but the resize is not allowed, always use the allocated resources. - metrics.PodInfeasibleResizes.WithLabelValues(reason).Inc() - m.statusManager.SetPodResizePendingCondition(pod.UID, v1.PodReasonInfeasible, msg, pod.Generation) - return false, nil - } } // Desired resources != allocated resources. Can we update the allocation to the desired resources? - fit, reason, message := m.canResizePod(m.getAllocatedPods(m.getActivePods()), pod) + fit, reason, message := m.canAdmitPod(logger, m.getAllocatedPods(m.getActivePods()), pod, lifecycle.ResizeOperation) if fit { // Update pod resource allocation checkpoint if err := m.SetAllocatedResources(pod); err != nil { return false, err } - allocatedPod = pod m.statusManager.ClearPodResizePendingCondition(pod.UID) // Clear any errors that may have been surfaced from a previous resize and update the @@ -604,9 +588,8 @@ func (m *manager) handlePodResourcesResize(pod *v1.Pod) (bool, error) { m.statusManager.ClearPodResizeInProgressCondition(pod.UID) m.statusManager.SetPodResizeInProgressCondition(pod.UID, "", "", pod.Generation) - msg := events.PodResizeStartedMsg(allocatedPod, pod.Generation) - m.recorder.Eventf(pod, v1.EventTypeNormal, events.ResizeStarted, msg) - + msg := events.PodResizeStartedMsg(logger, pod, pod.Generation) + m.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeNormal, events.ResizeStarted, "%s", msg) return true, nil } @@ -616,48 +599,14 @@ func (m *manager) handlePodResourcesResize(pod *v1.Pod) (bool, error) { if reason == v1.PodReasonInfeasible { eventType = events.ResizeInfeasible } - msg := events.PodResizePendingMsg(pod, reason, message, pod.Generation) - m.recorder.Eventf(pod, v1.EventTypeWarning, eventType, msg) + msg := events.PodResizePendingMsg(logger, pod, reason, message, pod.Generation) + m.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, eventType, "%s", msg) } } return false, nil } -func disallowResizeForSwappableContainers(runtime kubecontainer.Runtime, desiredPod, allocatedPod *v1.Pod) (bool, string) { - if desiredPod == nil || allocatedPod == nil { - return false, "" - } - restartableMemoryResizePolicy := func(resizePolicies []v1.ContainerResizePolicy) bool { - for _, policy := range resizePolicies { - if policy.ResourceName == v1.ResourceMemory { - return policy.RestartPolicy == v1.RestartContainer - } - } - return false - } - allocatedContainers := make(map[string]v1.Container) - for _, container := range append(allocatedPod.Spec.Containers, allocatedPod.Spec.InitContainers...) { - allocatedContainers[container.Name] = container - } - for _, desiredContainer := range append(desiredPod.Spec.Containers, desiredPod.Spec.InitContainers...) { - allocatedContainer, ok := allocatedContainers[desiredContainer.Name] - if !ok { - continue - } - origMemRequest := desiredContainer.Resources.Requests[v1.ResourceMemory] - newMemRequest := allocatedContainer.Resources.Requests[v1.ResourceMemory] - if !origMemRequest.Equal(newMemRequest) && !restartableMemoryResizePolicy(allocatedContainer.ResizePolicy) { - aSwapBehavior := runtime.GetContainerSwapBehavior(desiredPod, &desiredContainer) - bSwapBehavior := runtime.GetContainerSwapBehavior(allocatedPod, &allocatedContainer) - if aSwapBehavior != kubetypes.NoSwap || bSwapBehavior != kubetypes.NoSwap { - return true, "In-place resize of containers with swap is not supported." - } - } - } - return false, "" -} - // canAdmitPod determines if a pod can be admitted, and gives a reason if it // cannot. "pod" is new pod, while "pods" are all admitted pods // The function returns a boolean value indicating whether the pod @@ -665,15 +614,15 @@ func disallowResizeForSwappableContainers(runtime kubecontainer.Runtime, desired // the pod cannot be admitted. // allocatedPods should represent the pods that have already been admitted, along with their // admitted (allocated) resources. -func (m *manager) canAdmitPod(allocatedPods []*v1.Pod, pod *v1.Pod) (bool, string, string) { +func (m *manager) canAdmitPod(logger klog.Logger, allocatedPods []*v1.Pod, pod *v1.Pod, operation lifecycle.Operation) (bool, string, string) { // Filter out the pod being evaluated. allocatedPods = slices.DeleteFunc(allocatedPods, func(p *v1.Pod) bool { return p.UID == pod.UID }) // If any handler rejects, the pod is rejected. - attrs := &lifecycle.PodAdmitAttributes{Pod: pod, OtherPods: allocatedPods} + attrs := &lifecycle.PodAdmitAttributes{Pod: pod, OtherPods: allocatedPods, Operation: operation} for _, podAdmitHandler := range m.admitHandlers { if result := podAdmitHandler.Admit(attrs); !result.Admit { - klog.InfoS("Pod admission denied", "podUID", attrs.Pod.UID, "pod", klog.KObj(attrs.Pod), "reason", result.Reason, "message", result.Message) + logger.Info("Pod admission denied", "podUID", attrs.Pod.UID, "pod", klog.KObj(attrs.Pod), "reason", result.Reason, "message", result.Message, "operation", operation) return false, result.Reason, result.Message } } @@ -681,82 +630,19 @@ func (m *manager) canAdmitPod(allocatedPods []*v1.Pod, pod *v1.Pod) (bool, strin return true, "", "" } -// canResizePod determines if the requested resize is currently feasible. -// pod should hold the desired (pre-allocated) spec. -// Returns true if the resize can proceed; returns a reason and message -// otherwise. -func (m *manager) canResizePod(allocatedPods []*v1.Pod, pod *v1.Pod) (bool, string, string) { - // TODO: Move this logic into a PodAdmitHandler by introducing an operation field to - // lifecycle.PodAdmitAttributes, and combine canResizePod with canAdmitPod. - if v1qos.GetPodQOS(pod) == v1.PodQOSGuaranteed { - if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingExclusiveCPUs) && - m.nodeConfig.CPUManagerPolicy == string(cpumanager.PolicyStatic) && - m.guaranteedPodResourceResizeRequired(pod, v1.ResourceCPU) { - msg := fmt.Sprintf("Resize is infeasible for Guaranteed Pods alongside CPU Manager policy \"%s\"", string(cpumanager.PolicyStatic)) - klog.V(3).InfoS(msg, "pod", format.Pod(pod)) - metrics.PodInfeasibleResizes.WithLabelValues("guaranteed_pod_cpu_manager_static_policy").Inc() - return false, v1.PodReasonInfeasible, msg - } - if utilfeature.DefaultFeatureGate.Enabled(features.MemoryManager) && - !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingExclusiveMemory) && - m.nodeConfig.MemoryManagerPolicy == string(memorymanager.PolicyTypeStatic) && - m.guaranteedPodResourceResizeRequired(pod, v1.ResourceMemory) { - msg := fmt.Sprintf("Resize is infeasible for Guaranteed Pods alongside Memory Manager policy \"%s\"", string(memorymanager.PolicyTypeStatic)) - klog.V(3).InfoS(msg, "pod", format.Pod(pod)) - metrics.PodInfeasibleResizes.WithLabelValues("guaranteed_pod_memory_manager_static_policy").Inc() - return false, v1.PodReasonInfeasible, msg - } - } - - cpuAvailable := m.nodeAllocatableAbsolute.Cpu().MilliValue() - memAvailable := m.nodeAllocatableAbsolute.Memory().Value() - cpuRequests := resource.GetResourceRequest(pod, v1.ResourceCPU) - memRequests := resource.GetResourceRequest(pod, v1.ResourceMemory) - if cpuRequests > cpuAvailable || memRequests > memAvailable { - var msg string - if memRequests > memAvailable { - msg = fmt.Sprintf("memory, requested: %d, capacity: %d", memRequests, memAvailable) - } else { - msg = fmt.Sprintf("cpu, requested: %d, capacity: %d", cpuRequests, cpuAvailable) - } - msg = "Node didn't have enough capacity: " + msg - klog.V(3).InfoS(msg, "pod", klog.KObj(pod)) - metrics.PodInfeasibleResizes.WithLabelValues("insufficient_node_allocatable").Inc() - return false, v1.PodReasonInfeasible, msg - } - - if ok, failReason, failMessage := m.canAdmitPod(allocatedPods, pod); !ok { - // Log reason and return. - klog.V(3).InfoS("Resize cannot be accommodated", "pod", klog.KObj(pod), "reason", failReason, "message", failMessage) - return false, v1.PodReasonDeferred, failMessage - } - - return true, "", "" -} - -func (m *manager) guaranteedPodResourceResizeRequired(pod *v1.Pod, resourceName v1.ResourceName) bool { - for container, containerType := range podutil.ContainerIter(&pod.Spec, podutil.InitContainers|podutil.Containers) { - if !IsResizableContainer(container, containerType) { - continue - } - requestedResources := container.Resources - allocatedresources, _ := m.GetContainerResourceAllocation(pod.UID, container.Name) - // For Guaranteed pods, requests must equal limits, so checking requests is sufficient. - if !requestedResources.Requests[resourceName].Equal(allocatedresources.Requests[resourceName]) { - return true - } - } - return false -} - func (m *manager) getAllocatedPods(activePods []*v1.Pod) []*v1.Pod { if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { return activePods } - allocatedPods := make([]*v1.Pod, len(activePods)) - for i, pod := range activePods { - allocatedPods[i], _ = m.UpdatePodFromAllocation(pod) + allocatedPods := make([]*v1.Pod, 0, len(activePods)) + for _, pod := range activePods { + // Filter out pods that don't yet have an allocation, which will filter pods that + // are potentially going to be denied at admission. + if m.hasPodAllocatedResources(pod) { + allocatedPod, _ := m.UpdatePodFromAllocation(pod) + allocatedPods = append(allocatedPods, allocatedPod) + } } return allocatedPods } @@ -764,7 +650,7 @@ func (m *manager) getAllocatedPods(activePods []*v1.Pod) []*v1.Pod { func IsResizableContainer(container *v1.Container, containerType podutil.ContainerType) bool { switch containerType { case podutil.InitContainers: - return podutil.IsRestartableInitContainer(container) + return utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingInitContainers) || podutil.IsRestartableInitContainer(container) case podutil.Containers: return true default: diff --git a/pkg/kubelet/allocation/allocation_manager_test.go b/pkg/kubelet/allocation/allocation_manager_test.go index 2e33fb1f161f4..f971c2a3f530f 100644 --- a/pkg/kubelet/allocation/allocation_manager_test.go +++ b/pkg/kubelet/allocation/allocation_manager_test.go @@ -54,6 +54,7 @@ import ( kubetypes "k8s.io/kubernetes/pkg/kubelet/types" kubeletutil "k8s.io/kubernetes/pkg/kubelet/util" _ "k8s.io/kubernetes/pkg/volume/hostpath" + "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/ptr" ) @@ -145,6 +146,7 @@ func TestUpdatePodFromAllocation(t *testing.T) { resizedPod := pod.DeepCopy() resizedPod.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(200, resource.DecimalSI) resizedPod.Spec.InitContainers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(300, resource.DecimalSI) + resizedPod.Spec.InitContainers[1].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(300, resource.DecimalSI) resizedPodWithPodLevelResources := resizedPod.DeepCopy() resizedPodWithPodLevelResources.Spec.Resources = &v1.ResourceRequirements{ @@ -157,7 +159,15 @@ func TestUpdatePodFromAllocation(t *testing.T) { v1.ResourceMemory: *resource.NewQuantity(2500, resource.DecimalSI), }, } + + podWithPodLevelResourcesAndOverhead := podWithPodLevelResources.DeepCopy() + podWithPodLevelResourcesAndOverhead.Spec.Overhead = v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(100, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(200, resource.DecimalSI), + } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true) + tests := []struct { name string pod *v1.Pod @@ -249,6 +259,14 @@ func TestUpdatePodFromAllocation(t *testing.T) { expectUpdate: true, expectPod: resizedPodWithPodLevelResources, inPlacePodLevelResizeEnabled: true, + }, { + name: "pod-level resources with overhead, checkpoint matches spec (no overhead stored)", + pod: podWithPodLevelResourcesAndOverhead, + allocated: state.PodResourceInfo{ + PodLevelResources: podWithPodLevelResourcesAndOverhead.Spec.Resources.DeepCopy(), + }, + expectUpdate: false, + inPlacePodLevelResizeEnabled: true, }, { name: "resized pod-level resources with feature gate disabled", pod: podWithPodLevelResources, @@ -268,11 +286,12 @@ func TestUpdatePodFromAllocation(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if test.inPlacePodLevelResizeEnabled { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true) - } + logger, _ := ktesting.NewTestContext(t) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, test.inPlacePodLevelResizeEnabled) + allocationManager := makeAllocationManager(t, &containertest.FakeRuntime{}, nil, nil) pod := test.pod.DeepCopy() - allocatedPod, updated := updatePodFromAllocation(pod, test.allocated) + allocationManager.(*manager).allocated.SetPodResourceInfo(logger, pod.UID, test.allocated) + allocatedPod, updated := allocationManager.UpdatePodFromAllocation(pod) if test.expectUpdate { assert.True(t, updated, "updated") @@ -290,9 +309,6 @@ func TestRetryPendingResizes(t *testing.T) { if goruntime.GOOS == "windows" { t.Skip("InPlacePodVerticalScaling is not currently supported for Windows") } - metrics.Register() - metrics.PodInfeasibleResizes.Reset() - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) containerRestartPolicyAlways := v1.ContainerRestartPolicyAlways @@ -352,7 +368,6 @@ func TestRetryPendingResizes(t *testing.T) { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, }, - RestartPolicy: &containerRestartPolicyAlways, }, }, } @@ -471,7 +486,7 @@ func TestRetryPendingResizes(t *testing.T) { expectPodSyncTriggered: "true", }, { - name: "Request memory increase beyond node capacity - expect Infeasible", + name: "Request memory increase beyond node capacity - expect Deferred", originalRequests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, newRequests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem4500M}, expectedAllocatedReqs: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, @@ -480,14 +495,14 @@ func TestRetryPendingResizes(t *testing.T) { { Type: v1.PodResizePending, Status: "True", - Reason: "Infeasible", - Message: "Node didn't have enough capacity: memory, requested: 4718592000, capacity: 4294967296", + Reason: "Deferred", + Message: "Node didn't have enough resource: memory, requested: 4718592000, used: 2147483648, capacity: 4294967296", }, }, expectPodSyncTriggered: "true", }, { - name: "Request CPU increase beyond node capacity - expect Infeasible", + name: "Request CPU increase beyond node capacity - expect Deferred", originalRequests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, newRequests: v1.ResourceList{v1.ResourceCPU: cpu5000m, v1.ResourceMemory: mem1000M}, expectedAllocatedReqs: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, @@ -496,8 +511,8 @@ func TestRetryPendingResizes(t *testing.T) { { Type: v1.PodResizePending, Status: "True", - Reason: "Infeasible", - Message: "Node didn't have enough capacity: cpu, requested: 5000, capacity: 4000", + Reason: "Deferred", + Message: "Node didn't have enough resource: cpu, requested: 5000, used: 2000, capacity: 4000", }, }, expectPodSyncTriggered: "true", @@ -569,23 +584,6 @@ func TestRetryPendingResizes(t *testing.T) { expectedAllocatedReqs: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, expectedResize: nil, }, - { - name: "static pod, expect Infeasible", - originalRequests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, - newRequests: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem500M}, - expectedAllocatedReqs: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, - annotations: map[string]string{kubetypes.ConfigSourceAnnotationKey: kubetypes.FileSource}, - - expectedResize: []*v1.PodCondition{ - { - Type: v1.PodResizePending, - Status: "True", - Reason: "Infeasible", - Message: "In-place resize of static-pods is not supported", - }, - }, - expectPodSyncTriggered: "true", - }, { name: "Increase CPU from min shares", originalRequests: v1.ResourceList{v1.ResourceCPU: cpu2m}, @@ -703,7 +701,7 @@ func TestRetryPendingResizes(t *testing.T) { expectPodSyncTriggered: "true", }, { - name: "pod-level: Request memory increase beyond node capacity - expect Infeasible", + name: "pod-level: Request memory increase beyond node capacity - expect Deferred", inPlacePodLevelResizeEnabled: true, originalPodRequests: v1.ResourceList{v1.ResourceCPU: cpu1500m, v1.ResourceMemory: mem1500M}, newPodRequests: v1.ResourceList{v1.ResourceCPU: cpu1500m, v1.ResourceMemory: mem4500M}, @@ -715,8 +713,8 @@ func TestRetryPendingResizes(t *testing.T) { { Type: v1.PodResizePending, Status: "True", - Reason: "Infeasible", - Message: "Node didn't have enough capacity: memory, requested: 4718592000, capacity: 4294967296", + Reason: "Deferred", + Message: "Node didn't have enough resource: memory, requested: 4718592000, used: 2147483648, capacity: 4294967296", }, }, expectPodSyncTriggered: "true", @@ -724,19 +722,24 @@ func TestRetryPendingResizes(t *testing.T) { } for _, tt := range tests { - for _, isSidecarContainer := range []bool{false, true} { - if tt.inPlacePodLevelResizeEnabled && isSidecarContainer { + for _, containerType := range []string{"regular", "non-sidecar-init", "sidecar"} { + isInitContainer := containerType != "regular" + + if tt.inPlacePodLevelResizeEnabled && isInitContainer { continue // pod level resources makes the distinction between container types irrelevant } - t.Run(fmt.Sprintf("%s/sidecar=%t", tt.name, isSidecarContainer), func(t *testing.T) { + t.Run(fmt.Sprintf("%s/containerType=%s", tt.name, containerType), func(t *testing.T) { if tt.inPlacePodLevelResizeEnabled { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true) } var originalPod *v1.Pod var originalCtr *v1.Container - if isSidecarContainer { + if isInitContainer { originalPod = testPod2.DeepCopy() originalCtr = &originalPod.Spec.InitContainers[0] + if containerType == "sidecar" { + originalPod.Spec.InitContainers[0].RestartPolicy = &containerRestartPolicyAlways + } } else { originalPod = testPod1.DeepCopy() originalCtr = &originalPod.Spec.Containers[0] @@ -749,7 +752,7 @@ func TestRetryPendingResizes(t *testing.T) { } newPod := originalPod.DeepCopy() - if isSidecarContainer { + if isInitContainer { newPod.Spec.InitContainers[0].Resources.Requests = tt.newRequests newPod.Spec.InitContainers[0].Resources.Limits = tt.newLimits } else { @@ -778,6 +781,11 @@ func TestRetryPendingResizes(t *testing.T) { } allocationManager := makeAllocationManager(t, &containertest.FakeRuntime{PodStatus: *podStatus}, []*v1.Pod{testPod1, testPod2, testPod3}, nil) + // Set allocations for all test pods to ensure they're counted in capacity calculations + require.NoError(t, allocationManager.SetAllocatedResources(testPod1)) + require.NoError(t, allocationManager.SetAllocatedResources(testPod2)) + require.NoError(t, allocationManager.SetAllocatedResources(testPod3)) + if !tt.newResourcesAllocated { require.NoError(t, allocationManager.SetAllocatedResources(originalPod)) } else { @@ -806,7 +814,7 @@ func TestRetryPendingResizes(t *testing.T) { } var updatedPodCtr v1.Container - if isSidecarContainer { + if isInitContainer { updatedPodCtr = updatedPod.Spec.InitContainers[0] } else { updatedPodCtr = updatedPod.Spec.Containers[0] @@ -859,16 +867,6 @@ func TestRetryPendingResizes(t *testing.T) { }) } } - - expectedMetrics := ` - # HELP kubelet_pod_infeasible_resizes_total [ALPHA] Number of infeasible resizes for pods. - # TYPE kubelet_pod_infeasible_resizes_total counter - kubelet_pod_infeasible_resizes_total{reason_detail="insufficient_node_allocatable"} 5 - kubelet_pod_infeasible_resizes_total{reason_detail="static_pod"} 2 - ` - assert.NoError(t, testutil.GatherAndCompare( - legacyregistry.DefaultGatherer, strings.NewReader(expectedMetrics), "kubelet_pod_infeasible_resizes_total", - )) } func TestRetryPendingResizesGuanteedQOSPods(t *testing.T) { @@ -1385,8 +1383,8 @@ func TestRetryPendingResizesMultipleConditions(t *testing.T) { { Type: v1.PodResizePending, Status: "True", - Reason: v1.PodReasonInfeasible, - Message: "Node didn't have enough capacity: memory, requested: 4718592000, capacity: 4294967296", + Reason: v1.PodReasonDeferred, + Message: "Node didn't have enough resource: cpu, requested: 5000, used: 0, capacity: 4000", ObservedGeneration: 2, }, { @@ -1395,7 +1393,7 @@ func TestRetryPendingResizesMultipleConditions(t *testing.T) { ObservedGeneration: 1, }, }, - expectedEvent: `Warning ResizeInfeasible Pod resize Infeasible: {"containers":[{"name":"c1","resources":{"requests":{"cpu":"5","memory":"4500Mi"}}}],"generation":2,"error":"Node didn't have enough capacity: memory, requested: 4718592000, capacity: 4294967296"}`, + expectedEvent: `Warning ResizeDeferred Pod resize OutOfcpu: {"containers":[{"name":"c1","resources":{"requests":{"cpu":"5","memory":"4500Mi"}}}],"generation":2,"error":"Node didn't have enough resource: cpu, requested: 5000, used: 0, capacity: 4000"}`, }, { name: "same as previous case to ensure no new event is generated", @@ -1406,8 +1404,8 @@ func TestRetryPendingResizesMultipleConditions(t *testing.T) { { Type: v1.PodResizePending, Status: "True", - Reason: v1.PodReasonInfeasible, - Message: "Node didn't have enough capacity: memory, requested: 4718592000, capacity: 4294967296", + Reason: v1.PodReasonDeferred, + Message: "Node didn't have enough resource: cpu, requested: 5000, used: 0, capacity: 4000", ObservedGeneration: 2, }, { @@ -1538,6 +1536,11 @@ func TestAllocationManagerAddPodWithPLR(t *testing.T) { pod1SmallWithPLR := pod1Small.DeepCopy() pod1SmallWithPLR.Spec.Resources = &v1.ResourceRequirements{Requests: cpu1Mem1G} + pod1SmallWithPLRAndOverhead := pod1SmallWithPLR.DeepCopy() + pod1SmallWithPLRAndOverhead.Spec.Overhead = v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + } pod1LargeWithPLR := pod1Large.DeepCopy() pod1LargeWithPLR.Spec.Resources = &v1.ResourceRequirements{Requests: cpu2Mem2G} pod2SmallWithPLR := pod2Small.DeepCopy() @@ -1573,6 +1576,17 @@ func TestAllocationManagerAddPodWithPLR(t *testing.T) { expectedAllocatedResourcesState: map[types.UID]resourceState{pod1UID: {podResources: cpu1Mem1G, containerResources: cpu1Mem1G}}, ipprPLRFeatureGate: true, }, + { + name: "PLR IPPR Enabled - New pod with overhead admitted and allocated resources updated without overhead", + initialAllocatedResourcesState: map[types.UID]resourceState{}, + currentActivePods: []*v1.Pod{}, + podToAdd: pod1SmallWithPLRAndOverhead, + admitFunc: nil, + expectAdmit: true, + // allocated resources updated with pod1's resources, excluding overhead + expectedAllocatedResourcesState: map[types.UID]resourceState{pod1UID: {podResources: cpu1Mem1G, containerResources: cpu1Mem1G}}, + ipprPLRFeatureGate: true, + }, { name: "PLR IPPR Disabled - New pod admitted with PLR but allocated pod-level resources not updated", initialAllocatedResourcesState: map[types.UID]resourceState{}, @@ -2345,6 +2359,7 @@ func TestRecordPodDeferredAcceptedResizes(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) original := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ UID: "1111", @@ -2387,7 +2402,7 @@ func TestRecordPodDeferredAcceptedResizes(t *testing.T) { return resizedPod, true } am.PushPendingResize(original.UID) - resizedPods := am.(*manager).retryPendingResizes(tc.trigger) + resizedPods := am.(*manager).retryPendingResizes(logger, tc.trigger) require.Len(t, resizedPods, 1) require.Equal(t, original.UID, resizedPods[0].UID) @@ -2404,8 +2419,125 @@ func TestRecordPodDeferredAcceptedResizes(t *testing.T) { } } +// TestPendingNonAllocatedPodsExcludedFromCapacity verifies that pods that are +// not in Running phase and have no allocated resources are excluded from capacity +// calculations. This prevents a race condition where pods that will fail admission +// consume capacity and prevent legitimate pod operations. +func TestPendingNonAllocatedPodsExcludedFromCapacity(t *testing.T) { + if goruntime.GOOS == "windows" { + t.Skip("InPlacePodVerticalScaling is not currently supported for Windows") + } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true) + + cpu1000m := resource.MustParse("1") + cpu1500m := resource.MustParse("1500m") + cpu10000m := resource.MustParse("10") // Way beyond node capacity (4 CPUs allocatable) + mem1000M := resource.MustParse("1Gi") + mem10000M := resource.MustParse("10Gi") // Way beyond node capacity (4Gi allocatable) + + runningPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "running-pod", + Name: "running-pod", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "c1", + Image: "test-image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, + }, + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + AllocatedResources: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, + Resources: &v1.ResourceRequirements{}, + }, + }, + }, + } + + pendingPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "pending-pod", + Name: "pending-pod", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "c1", + Image: "test-image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: cpu10000m, v1.ResourceMemory: mem10000M}, + }, + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodPending, + }, + } + + podStatus := &kubecontainer.PodStatus{ + ID: runningPod.UID, + Name: runningPod.Name, + Namespace: runningPod.Namespace, + } + podStatus.ContainerStatuses = make([]*kubecontainer.Status, len(runningPod.Spec.Containers)) + for i, c := range runningPod.Spec.Containers { + setContainerStatus(podStatus, &c, i) + } + + allocationManager := makeAllocationManager(t, &containertest.FakeRuntime{PodStatus: *podStatus}, []*v1.Pod{runningPod, pendingPod}, nil) + require.NoError(t, allocationManager.SetAllocatedResources(runningPod)) + t.Cleanup(func() { + allocationManager.RemovePod(runningPod.UID) + allocationManager.RemovePod(pendingPod.UID) + }) + + resizedPod := runningPod.DeepCopy() + resizedPod.Spec.Containers[0].Resources.Requests = v1.ResourceList{v1.ResourceCPU: cpu1500m, v1.ResourceMemory: mem1000M} + + allocationManager.(*manager).getPodByUID = func(uid types.UID) (*v1.Pod, bool) { + if uid == runningPod.UID { + return resizedPod, true + } + if uid == pendingPod.UID { + return pendingPod, true + } + return nil, false + } + + allocationManager.PushPendingResize(runningPod.UID) + allocationManager.RetryPendingResizes(TriggerReasonPodUpdated) + + // Verify the resize succeeded. If pendingPod was incorrectly included in capacity + // calculations, this would be deferred (1500m + 10000m > 4000m allocatable). + resizeStatus := allocationManager.(*manager).statusManager.GetPodResizeConditions(runningPod.UID) + require.Len(t, resizeStatus, 1) + assert.Equal(t, v1.PodResizeInProgress, resizeStatus[0].Type) + assert.Equal(t, v1.ConditionTrue, resizeStatus[0].Status) + + alloc, found := allocationManager.GetContainerResourceAllocation(runningPod.UID, "c1") + require.True(t, found) + assert.Equal(t, cpu1500m, *alloc.Requests.Cpu()) + + _, foundPending := allocationManager.GetContainerResourceAllocation(pendingPod.UID, "c1") + assert.False(t, foundPending) +} + func makeAllocationManager(t *testing.T, runtime *containertest.FakeRuntime, allocatedPods []*v1.Pod, nodeConfig *cm.NodeConfig) Manager { t.Helper() + logger, _ := ktesting.NewTestContext(t) statusManager := status.NewManager(&fake.Clientset{}, kubepod.NewBasicPodManager(), &statustest.FakePodDeletionSafetyProvider{}, kubeletutil.NewPodStartupLatencyTracker()) var containerManager *cm.FakeContainerManager if nodeConfig == nil { @@ -2414,8 +2546,6 @@ func makeAllocationManager(t *testing.T, runtime *containertest.FakeRuntime, all containerManager = cm.NewFakeContainerManagerWithNodeConfig(*nodeConfig) } allocationManager := NewInMemoryManager( - containerManager.GetNodeConfig(), - containerManager.GetNodeAllocatableAbsolute(), statusManager, func(pod *v1.Pod) { /* For testing, just mark the pod as having a pod sync triggered in an annotation. */ @@ -2453,9 +2583,10 @@ func makeAllocationManager(t *testing.T, runtime *containertest.FakeRuntime, all }, }, nil } - handler := lifecycle.NewPredicateAdmitHandler(getNode, lifecycle.NewAdmissionFailureHandlerStub(), containerManager.UpdatePluginResources) - allocationManager.AddPodAdmitHandlers(lifecycle.PodAdmitHandlers{handler}) + predicateHandler := lifecycle.NewPredicateAdmitHandler(getNode, lifecycle.NewAdmissionFailureHandlerStub(), containerManager.UpdatePluginResources) + resizeHandler := NewPodResizesAdmitHandler(containerManager, runtime, allocationManager, logger) + allocationManager.AddPodAdmitHandlers(lifecycle.PodAdmitHandlers{resizeHandler, predicateHandler}) return allocationManager } diff --git a/pkg/kubelet/allocation/features_linux.go b/pkg/kubelet/allocation/features_linux.go index fad24ace7e3cc..142afc415c5b8 100644 --- a/pkg/kubelet/allocation/features_linux.go +++ b/pkg/kubelet/allocation/features_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2025 The Kubernetes Authors. @@ -23,16 +22,12 @@ import ( v1 "k8s.io/api/core/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/features" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) func IsInPlacePodVerticalScalingAllowed(pod *v1.Pod) (allowed bool, msg, reason string) { if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { return false, "InPlacePodVerticalScaling is disabled", "feature_gate_off" } - if kubetypes.IsStaticPod(pod) { - return false, "In-place resize of static-pods is not supported", "static_pod" - } return true, "", "" } diff --git a/pkg/kubelet/allocation/features_unsupported.go b/pkg/kubelet/allocation/features_unsupported.go index 127b062326f72..a81a51b4009c1 100644 --- a/pkg/kubelet/allocation/features_unsupported.go +++ b/pkg/kubelet/allocation/features_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2025 The Kubernetes Authors. diff --git a/pkg/kubelet/allocation/features_windows.go b/pkg/kubelet/allocation/features_windows.go index 15b6855f39a9c..eda519b97418e 100644 --- a/pkg/kubelet/allocation/features_windows.go +++ b/pkg/kubelet/allocation/features_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2025 The Kubernetes Authors. diff --git a/pkg/kubelet/allocation/handlers.go b/pkg/kubelet/allocation/handlers.go new file mode 100644 index 0000000000000..4bbe7bfcbb92d --- /dev/null +++ b/pkg/kubelet/allocation/handlers.go @@ -0,0 +1,151 @@ +/* +Copyright 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 allocation + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/klog/v2" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/cm" + "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" + "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" + "k8s.io/kubernetes/pkg/kubelet/metrics" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/format" +) + +// NewPodResizesAdmitHandler returns a PodAdmitHandler which is used to evaluate +// if a pod resize can be allocated by the kubelet. +func NewPodResizesAdmitHandler(containerManager cm.ContainerManager, containerRuntime kubecontainer.Runtime, allocationManager Manager, logger klog.Logger) lifecycle.PodAdmitHandler { + return &podResizesAdmitHandler{ + containerManager: containerManager, + containerRuntime: containerRuntime, + allocationManager: allocationManager, + logger: logger, + } +} + +type podResizesAdmitHandler struct { + containerManager cm.ContainerManager + containerRuntime kubecontainer.Runtime + allocationManager Manager + logger klog.Logger +} + +func (h *podResizesAdmitHandler) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult { + if attrs.Operation != lifecycle.ResizeOperation { + return lifecycle.PodAdmitResult{Admit: true} + } + + pod := attrs.Pod + allocatedPod, _ := h.allocationManager.UpdatePodFromAllocation(pod) + if resizable, msg, reason := IsInPlacePodVerticalScalingAllowed(pod); !resizable { + // If there is a pending resize but the resize is not allowed, always use the allocated resources. + metrics.PodInfeasibleResizes.WithLabelValues(reason).Inc() + return lifecycle.PodAdmitResult{Admit: false, Reason: v1.PodReasonInfeasible, Message: msg} + } + if resizeNotAllowed, msg := disallowResizeForSwappableContainers(h.containerRuntime, pod, allocatedPod); resizeNotAllowed { + // If this resize involve swap recalculation, set as infeasible, as IPPR with swap is not supported for beta. + metrics.PodInfeasibleResizes.WithLabelValues("swap_limitation").Inc() + return lifecycle.PodAdmitResult{Admit: false, Reason: v1.PodReasonInfeasible, Message: msg} + } + if !apiequality.Semantic.DeepEqual(pod.Spec.Resources, allocatedPod.Spec.Resources) { + if resizable, msg, reason := IsInPlacePodLevelResourcesVerticalScalingAllowed(pod); !resizable { + // If there is a pending pod-level resources resize but the resize is not allowed, always use the allocated resources. + metrics.PodInfeasibleResizes.WithLabelValues(reason).Inc() + return lifecycle.PodAdmitResult{Admit: false, Reason: v1.PodReasonInfeasible, Message: msg} + } + } + + if v1qos.GetPodQOS(pod) == v1.PodQOSGuaranteed { + if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingExclusiveCPUs) && + h.containerManager.GetNodeConfig().CPUManagerPolicy == string(cpumanager.PolicyStatic) && + h.guaranteedPodResourceResizeRequired(pod, v1.ResourceCPU) { + msg := fmt.Sprintf("Resize is infeasible for Guaranteed Pods alongside CPU Manager policy \"%s\"", string(cpumanager.PolicyStatic)) + h.logger.V(3).Info(msg, "pod", format.Pod(pod)) + metrics.PodInfeasibleResizes.WithLabelValues("guaranteed_pod_cpu_manager_static_policy").Inc() + return lifecycle.PodAdmitResult{Admit: false, Reason: v1.PodReasonInfeasible, Message: msg} + } + if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingExclusiveMemory) && + h.containerManager.GetNodeConfig().MemoryManagerPolicy == string(memorymanager.PolicyTypeStatic) && + h.guaranteedPodResourceResizeRequired(pod, v1.ResourceMemory) { + msg := fmt.Sprintf("Resize is infeasible for Guaranteed Pods alongside Memory Manager policy \"%s\"", string(memorymanager.PolicyTypeStatic)) + h.logger.V(3).Info(msg, "pod", format.Pod(pod)) + metrics.PodInfeasibleResizes.WithLabelValues("guaranteed_pod_memory_manager_static_policy").Inc() + return lifecycle.PodAdmitResult{Admit: false, Reason: v1.PodReasonInfeasible, Message: msg} + } + } + + return lifecycle.PodAdmitResult{Admit: true} +} + +func disallowResizeForSwappableContainers(runtime kubecontainer.Runtime, desiredPod, allocatedPod *v1.Pod) (bool, string) { + if desiredPod == nil || allocatedPod == nil { + return false, "" + } + restartableMemoryResizePolicy := func(resizePolicies []v1.ContainerResizePolicy) bool { + for _, policy := range resizePolicies { + if policy.ResourceName == v1.ResourceMemory { + return policy.RestartPolicy == v1.RestartContainer + } + } + return false + } + allocatedContainers := make(map[string]v1.Container) + for _, container := range append(allocatedPod.Spec.Containers, allocatedPod.Spec.InitContainers...) { + allocatedContainers[container.Name] = container + } + for _, desiredContainer := range append(desiredPod.Spec.Containers, desiredPod.Spec.InitContainers...) { + allocatedContainer, ok := allocatedContainers[desiredContainer.Name] + if !ok { + continue + } + origMemRequest := desiredContainer.Resources.Requests[v1.ResourceMemory] + newMemRequest := allocatedContainer.Resources.Requests[v1.ResourceMemory] + if !origMemRequest.Equal(newMemRequest) && !restartableMemoryResizePolicy(allocatedContainer.ResizePolicy) { + aSwapBehavior := runtime.GetContainerSwapBehavior(desiredPod, &desiredContainer) + bSwapBehavior := runtime.GetContainerSwapBehavior(allocatedPod, &allocatedContainer) + if aSwapBehavior != kubetypes.NoSwap || bSwapBehavior != kubetypes.NoSwap { + return true, "In-place resize of containers with swap is not supported." + } + } + } + return false, "" +} + +func (h *podResizesAdmitHandler) guaranteedPodResourceResizeRequired(pod *v1.Pod, resourceName v1.ResourceName) bool { + for container, containerType := range podutil.ContainerIter(&pod.Spec, podutil.InitContainers|podutil.Containers) { + if !IsResizableContainer(container, containerType) { + continue + } + requestedResources := container.Resources + allocatedresources, _ := h.allocationManager.GetContainerResourceAllocation(pod.UID, container.Name) + // For Guaranteed pods, requests must equal limits, so checking requests is sufficient. + if !requestedResources.Requests[resourceName].Equal(allocatedresources.Requests[resourceName]) { + return true + } + } + return false +} diff --git a/pkg/kubelet/allocation/state/state.go b/pkg/kubelet/allocation/state/state.go index d21b476595030..6c9588ebf685d 100644 --- a/pkg/kubelet/allocation/state/state.go +++ b/pkg/kubelet/allocation/state/state.go @@ -20,6 +20,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" ) // PodResourceInfo stores resource requirements for containers within a pod. @@ -58,7 +59,7 @@ type Reader interface { type writer interface { SetContainerResources(podUID types.UID, containerName string, resources v1.ResourceRequirements) error - SetPodResourceInfo(podUID types.UID, resourceInfo PodResourceInfo) error + SetPodResourceInfo(logger klog.Logger, podUID types.UID, resourceInfo PodResourceInfo) error SetPodLevelResources(podUID types.UID, alloc *v1.ResourceRequirements) error RemovePod(podUID types.UID) error // RemoveOrphanedPods removes the stored state for any pods not included in the set of remaining pods. diff --git a/pkg/kubelet/allocation/state/state_checkpoint.go b/pkg/kubelet/allocation/state/state_checkpoint.go index cdfff8b164852..5aea24d82d345 100644 --- a/pkg/kubelet/allocation/state/state_checkpoint.go +++ b/pkg/kubelet/allocation/state/state_checkpoint.go @@ -42,12 +42,15 @@ type stateCheckpoint struct { // NewStateCheckpoint creates new State for keeping track of pod resource information with checkpoint backend func NewStateCheckpoint(stateDir, checkpointName string) (State, error) { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() checkpointManager, err := checkpointmanager.NewCheckpointManager(stateDir) if err != nil { return nil, fmt.Errorf("failed to initialize checkpoint manager for pod resource information tracking: %w", err) } - pra, checksum, err := restoreState(checkpointManager, checkpointName) + pra, checksum, err := restoreState(logger, checkpointManager, checkpointName) if err != nil { //lint:ignore ST1005 user-facing error message return nil, fmt.Errorf("could not restore state from checkpoint: %w, please drain this node and delete pod resource information checkpoint file %q before restarting Kubelet", @@ -64,7 +67,7 @@ func NewStateCheckpoint(stateDir, checkpointName string) (State, error) { } // restores state from a checkpoint and creates it if it doesn't exist -func restoreState(checkpointManager checkpointmanager.CheckpointManager, checkpointName string) (PodResourceInfoMap, checksum.Checksum, error) { +func restoreState(logger klog.Logger, checkpointManager checkpointmanager.CheckpointManager, checkpointName string) (PodResourceInfoMap, checksum.Checksum, error) { checkpoint := &Checkpoint{} if err := checkpointManager.GetCheckpoint(checkpointName, checkpoint); err != nil { if err == errors.ErrCheckpointNotFound { @@ -78,12 +81,12 @@ func restoreState(checkpointManager checkpointmanager.CheckpointManager, checkpo return nil, 0, fmt.Errorf("failed to get pod resource information: %w", err) } - klog.V(2).InfoS("State checkpoint: restored pod resource state from checkpoint") + logger.V(2).Info("State checkpoint: restored pod resource state from checkpoint") return praInfo.Entries, checkpoint.Checksum, nil } // saves state to a checkpoint, caller is responsible for locking -func (sc *stateCheckpoint) storeState() error { +func (sc *stateCheckpoint) storeState(logger klog.Logger) error { resourceInfo := sc.cache.GetPodResourceInfoMap() checkpoint, err := NewCheckpoint(&PodResourceCheckpointInfo{ @@ -98,7 +101,7 @@ func (sc *stateCheckpoint) storeState() error { } err = sc.checkpointManager.CreateCheckpoint(sc.checkpointName, checkpoint) if err != nil { - klog.ErrorS(err, "Failed to save pod resource information checkpoint") + logger.Error(err, "Failed to save pod resource information checkpoint") return err } sc.lastChecksum = checkpoint.Checksum @@ -135,35 +138,41 @@ func (sc *stateCheckpoint) GetPodResourceInfo(podUID types.UID) (PodResourceInfo // SetContainerResoruces sets resources information for a pod's container func (sc *stateCheckpoint) SetContainerResources(podUID types.UID, containerName string, resources v1.ResourceRequirements) error { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() sc.mux.Lock() defer sc.mux.Unlock() err := sc.cache.SetContainerResources(podUID, containerName, resources) if err != nil { return err } - return sc.storeState() + return sc.storeState(logger) } // SetPodLevelResources sets resources information for a pod's resources at pod-level. func (sc *stateCheckpoint) SetPodLevelResources(podUID types.UID, resInfo *v1.ResourceRequirements) error { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() sc.mux.Lock() defer sc.mux.Unlock() err := sc.cache.SetPodLevelResources(podUID, resInfo) if err != nil { return err } - return sc.storeState() + return sc.storeState(logger) } // SetPodResourceInfo sets pod resource information -func (sc *stateCheckpoint) SetPodResourceInfo(podUID types.UID, resourceInfo PodResourceInfo) error { +func (sc *stateCheckpoint) SetPodResourceInfo(logger klog.Logger, podUID types.UID, resourceInfo PodResourceInfo) error { sc.mux.Lock() defer sc.mux.Unlock() - err := sc.cache.SetPodResourceInfo(podUID, resourceInfo) + err := sc.cache.SetPodResourceInfo(logger, podUID, resourceInfo) if err != nil { return err } - return sc.storeState() + return sc.storeState(logger) } // Delete deletes resource information for specified pod @@ -213,7 +222,7 @@ func (sc *noopStateCheckpoint) SetPodLevelResources(_ types.UID, _ *v1.ResourceR return nil } -func (sc *noopStateCheckpoint) SetPodResourceInfo(_ types.UID, _ PodResourceInfo) error { +func (sc *noopStateCheckpoint) SetPodResourceInfo(_ klog.Logger, _ types.UID, _ PodResourceInfo) error { return nil } diff --git a/pkg/kubelet/allocation/state/state_checkpoint_test.go b/pkg/kubelet/allocation/state/state_checkpoint_test.go index e306d7e32d5cc..9a42c8b0a8627 100644 --- a/pkg/kubelet/allocation/state/state_checkpoint_test.go +++ b/pkg/kubelet/allocation/state/state_checkpoint_test.go @@ -26,6 +26,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" ) @@ -115,12 +116,13 @@ func Test_stateCheckpoint_storeState(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testDir := getTestDir(t) originalSC, err := NewStateCheckpoint(testDir, testCheckpoint) require.NoError(t, err) for podUID, alloc := range tt.args.resInfoMap { - err = originalSC.SetPodResourceInfo(podUID, alloc) + err = originalSC.SetPodResourceInfo(logger, podUID, alloc) require.NoError(t, err) } @@ -139,12 +141,12 @@ func Test_stateCheckpoint_storeState(t *testing.T) { // Setting the pod allocations to the same values should not re-write the checkpoint. for podUID, alloc := range tt.args.resInfoMap { - require.NoError(t, originalSC.SetPodResourceInfo(podUID, alloc)) + require.NoError(t, originalSC.SetPodResourceInfo(logger, podUID, alloc)) require.NoFileExists(t, checkpointPath, "checkpoint should not be re-written") } // Setting a new value should update the checkpoint. - require.NoError(t, originalSC.SetPodResourceInfo("foo-bar", PodResourceInfo{ + require.NoError(t, originalSC.SetPodResourceInfo(logger, "foo-bar", PodResourceInfo{ ContainerResources: map[string]v1.ResourceRequirements{ "container1": {Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}}, }, @@ -156,6 +158,7 @@ func Test_stateCheckpoint_storeState(t *testing.T) { } func Test_stateCheckpoint_formatUpgraded(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) // Based on the PodResourceAllocationInfo struct, it's mostly possible that new field will be added // in struct PodResourceAllocationInfo, rather than in struct PodResourceAllocationInfo.AllocationEntries. // Emulate upgrade scenario by pretending that `ResizeStatusEntries` is a new field. @@ -184,7 +187,7 @@ func Test_stateCheckpoint_formatUpgraded(t *testing.T) { err = sc.checkpointManager.CreateCheckpoint(sc.checkpointName, checkpoint) require.NoError(t, err, "failed to create old checkpoint") - actualPodResourceAllocation, _, err := restoreState(sc.checkpointManager, sc.checkpointName) + actualPodResourceAllocation, _, err := restoreState(logger, sc.checkpointManager, sc.checkpointName) require.NoError(t, err, "failed to restore state") require.Equal(t, expectedPodResourceAllocation, actualPodResourceAllocation, "pod resource allocation info is not equal") diff --git a/pkg/kubelet/allocation/state/state_mem.go b/pkg/kubelet/allocation/state/state_mem.go index 3dfad44837ca1..89b7f280bc33f 100644 --- a/pkg/kubelet/allocation/state/state_mem.go +++ b/pkg/kubelet/allocation/state/state_mem.go @@ -34,10 +34,13 @@ var _ State = &stateMemory{} // NewStateMemory creates new State to track resources resourcesated to pods func NewStateMemory(resources PodResourceInfoMap) State { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() if resources == nil { resources = PodResourceInfoMap{} } - klog.V(2).InfoS("Initialized new in-memory state store for pod resource information tracking") + logger.V(2).Info("Initialized new in-memory state store for pod resource information tracking") return &stateMemory{ podResources: resources, } @@ -87,6 +90,10 @@ func (s *stateMemory) GetPodResourceInfo(podUID types.UID) (PodResourceInfo, boo } func (s *stateMemory) SetContainerResources(podUID types.UID, containerName string, resources v1.ResourceRequirements) error { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + s.Lock() defer s.Unlock() @@ -104,11 +111,14 @@ func (s *stateMemory) SetContainerResources(podUID types.UID, containerName stri podInfo.ContainerResources[containerName] = resources s.podResources[podUID] = podInfo - klog.V(3).InfoS("Updated container resource information", "podUID", podUID, "containerName", containerName, "resources", resources) + logger.V(3).Info("Updated container resource information", "podUID", podUID, "containerName", containerName, "resources", resources) return nil } func (s *stateMemory) SetPodLevelResources(podUID types.UID, resources *v1.ResourceRequirements) error { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() s.Lock() defer s.Unlock() @@ -121,24 +131,27 @@ func (s *stateMemory) SetPodLevelResources(podUID types.UID, resources *v1.Resou s.podResources[podUID] = podInfo - klog.V(3).InfoS("Updated pod-level resource info", "podUID", podUID, "resources", resources) + logger.V(3).Info("Updated pod-level resource info", "podUID", podUID, "resources", resources) return nil } -func (s *stateMemory) SetPodResourceInfo(podUID types.UID, resourceInfo PodResourceInfo) error { +func (s *stateMemory) SetPodResourceInfo(logger klog.Logger, podUID types.UID, resourceInfo PodResourceInfo) error { s.Lock() defer s.Unlock() s.podResources[podUID] = resourceInfo - klog.V(3).InfoS("Updated pod resource information", "podUID", podUID, "information", resourceInfo) + logger.V(3).Info("Updated pod resource information", "podUID", podUID, "information", resourceInfo) return nil } func (s *stateMemory) RemovePod(podUID types.UID) error { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() s.Lock() defer s.Unlock() delete(s.podResources, podUID) - klog.V(3).InfoS("Deleted pod resource information", "podUID", podUID) + logger.V(3).Info("Deleted pod resource information", "podUID", podUID) return nil } diff --git a/pkg/kubelet/apis/config/fuzzer/fuzzer.go b/pkg/kubelet/apis/config/fuzzer/fuzzer.go index bc2f9b0700724..beb8183f97502 100644 --- a/pkg/kubelet/apis/config/fuzzer/fuzzer.go +++ b/pkg/kubelet/apis/config/fuzzer/fuzzer.go @@ -122,6 +122,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { } obj.EnableSystemLogHandler = true obj.MemoryThrottlingFactor = ptr.To(rand.Float64()) + obj.MemoryReservationPolicy = kubeletconfig.NoneMemoryReservationPolicy obj.LocalStorageCapacityIsolation = true obj.FeatureGates = map[string]bool{ "AllAlpha": false, diff --git a/pkg/kubelet/apis/config/helpers_test.go b/pkg/kubelet/apis/config/helpers_test.go index 48ea219d273f2..7cda6b706fd54 100644 --- a/pkg/kubelet/apis/config/helpers_test.go +++ b/pkg/kubelet/apis/config/helpers_test.go @@ -236,6 +236,7 @@ var ( "SingleProcessOOMKill", "Logging.Verbosity", "TLSCipherSuites[*]", + "TLSCurvePreferences[*]", "TLSMinVersion", "IPTablesDropBit", "IPTablesMasqueradeBit", @@ -299,6 +300,7 @@ var ( "ShutdownGracePeriod.Duration", "ShutdownGracePeriodCriticalPods.Duration", "MemoryThrottlingFactor", + "MemoryReservationPolicy", "ContainerRuntimeEndpoint", "ImageServiceEndpoint", "Tracing.Endpoint", diff --git a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml index 3b0ea0fc8d9f3..7134ebcb64e2e 100644 --- a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml +++ b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/after/v1beta1.yaml @@ -70,6 +70,7 @@ makeIPTablesUtilChains: true maxOpenFiles: 1000000 maxPods: 110 memoryManagerPolicy: None +memoryReservationPolicy: None memorySwap: {} memoryThrottlingFactor: 0.9 mergeDefaultEvictionSettings: false diff --git a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml index 1ceb95dc24ec7..dc9d85d9a6476 100644 --- a/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml +++ b/pkg/kubelet/apis/config/scheme/testdata/KubeletConfiguration/roundtrip/default/v1beta1.yaml @@ -70,6 +70,7 @@ makeIPTablesUtilChains: true maxOpenFiles: 1000000 maxPods: 110 memoryManagerPolicy: None +memoryReservationPolicy: None memorySwap: {} memoryThrottlingFactor: 0.9 mergeDefaultEvictionSettings: false diff --git a/pkg/kubelet/apis/config/types.go b/pkg/kubelet/apis/config/types.go index 6ab19ac1c6614..c032cfe511e1c 100644 --- a/pkg/kubelet/apis/config/types.go +++ b/pkg/kubelet/apis/config/types.go @@ -131,6 +131,14 @@ type KubeletConfiguration struct { // Note that TLS 1.3 ciphersuites are not configurable. // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). TLSCipherSuites []string + // TLSCurvePreferences is the set of allowed key exchange mechanisms for the server, + // specified as numeric Go crypto/tls CurveID values. + // 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 empty, the default Go curves will be used. + TLSCurvePreferences []int32 // TLSMinVersion is the minimum TLS version supported. // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). TLSMinVersion string @@ -219,6 +227,8 @@ type KubeletConfiguration struct { NodeLeaseDurationSeconds int32 // ImageMinimumGCAge is the minimum age for an unused image before it is // garbage collected. + // The field value must be greater than 0. + // If unset or 0, defaults to 2m. ImageMinimumGCAge metav1.Duration // ImageMaximumGCAge is the maximum age an image can be unused before it is garbage collected. // The default of this field is "0s", which disables this field--meaning images won't be garbage @@ -308,6 +318,7 @@ type KubeletConfiguration struct { // specify CPU limits CPUCFSQuota bool // CPUCFSQuotaPeriod sets the CPU CFS quota period value, cpu.cfs_period_us, defaults to 100ms + // Default: "100ms". Changing this value requires enabling the CustomCPUCFSQuotaPeriod feature gate. CPUCFSQuotaPeriod metav1.Duration // maxOpenFiles is Number of files that can be opened by Kubelet process. MaxOpenFiles int64 @@ -418,7 +429,7 @@ type KubeletConfiguration struct { // Refer to [Node Allocatable](https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#node-allocatable) doc for more information. KubeReservedCgroup string // This flag specifies the various Node Allocatable enforcements that Kubelet needs to perform. - // This flag accepts a list of options. Acceptable options are `pods`, `system-reserved` & `kube-reserved`. + // This flag accepts a list of options. Acceptable options are `pods`, `system-reserved`, `system-reserved-compressible`, `kube-reserved`, and `kube-reserved-compressible`. // Refer to [Node Allocatable](https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#node-allocatable) doc for more information. EnforceNodeAllocatable []string // This option specifies the cpu list reserved for the host level system threads and kubernetes related threads. @@ -440,7 +451,6 @@ type KubeletConfiguration struct { // EnableSystemLogHandler has to be enabled in addition for this feature to work. // Enabling this feature has security implications. The recommendation is to enable it on a need basis for debugging // purposes and disabling otherwise. - // +featureGate=NodeLogQuery // +optional EnableSystemLogQuery bool // ShutdownGracePeriod specifies the total duration that the node should delay the shutdown and total grace period for pod termination during a node shutdown. @@ -492,6 +502,16 @@ type KubeletConfiguration struct { // +featureGate=MemoryQoS // +optional MemoryThrottlingFactor *float64 + // MemoryReservationPolicy controls how the kubelet applies cgroup v2 memory protection. + // "None" (default): The kubelet does not set memory.min for containers and pods, + // ensuring no hard memory is locked by the kernel. + // "TieredReservation": The kubelet sets cgroup v2 memory.min for Guaranteed pods and memory.low for Burstable pods based on memory requests. + // Guaranteed memory is never reclaimed by the kernel; Burstable memory is preferentially retained but may be reclaimed under extreme pressure. + // See https://kep.k8s.io/2570 for more details. + // Default: None + // +featureGate=MemoryQoS + // +optional + MemoryReservationPolicy MemoryReservationPolicy // registerWithTaints are an array of taints to add to a node object when // the kubelet registers itself. This only takes effect when registerNode // is true and upon the initial registration of the node. @@ -841,6 +861,18 @@ const ( AlwaysVerify ImagePullCredentialsVerificationPolicy = "AlwaysVerify" ) +// MemoryReservationPolicy defines how the kubelet applies cgroup v2 memory protection. +type MemoryReservationPolicy string + +const ( + // NoneMemoryReservationPolicy disables memory.min protection for containers and pods. + // This is the default to maintain node stability by preventing "locked" memory. + NoneMemoryReservationPolicy MemoryReservationPolicy = "None" + // TieredReservationMemoryReservationPolicy enables tiered memory protection: + // memory.min for Guaranteed pods, memory.low for Burstable pods. + TieredReservationMemoryReservationPolicy MemoryReservationPolicy = "TieredReservation" +) + // ImagePullIntent is a record of the kubelet attempting to pull an image. // // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/kubelet/apis/config/v1beta1/defaults.go b/pkg/kubelet/apis/config/v1beta1/defaults.go index ba5e7aeb6008e..2d30c50a38e5d 100644 --- a/pkg/kubelet/apis/config/v1beta1/defaults.go +++ b/pkg/kubelet/apis/config/v1beta1/defaults.go @@ -41,7 +41,8 @@ const ( DefaultVolumePluginDir = "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/" DefaultPodLogsDir = "/var/log/pods" // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2570-memory-qos - DefaultMemoryThrottlingFactor = 0.9 + DefaultMemoryThrottlingFactor = 0.9 + DefaultMemoryReservationPolicy = kubeletconfigv1beta1.NoneMemoryReservationPolicy // MaxContainerBackOff is the max backoff period for container restarts, exported for the e2e test MaxContainerBackOff = 300 * time.Second ) @@ -295,6 +296,9 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura if obj.MemoryThrottlingFactor == nil { obj.MemoryThrottlingFactor = ptr.To(DefaultMemoryThrottlingFactor) } + if obj.MemoryReservationPolicy == "" { + obj.MemoryReservationPolicy = DefaultMemoryReservationPolicy + } if obj.RegisterNode == nil { obj.RegisterNode = ptr.To(true) } diff --git a/pkg/kubelet/apis/config/v1beta1/defaults_test.go b/pkg/kubelet/apis/config/v1beta1/defaults_test.go index 471e74924db4a..abc6926f7b50e 100644 --- a/pkg/kubelet/apis/config/v1beta1/defaults_test.go +++ b/pkg/kubelet/apis/config/v1beta1/defaults_test.go @@ -128,6 +128,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To(DefaultMemoryThrottlingFactor), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: DefaultPodLogsDir, @@ -265,6 +266,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To[float64](0), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(false), LocalStorageCapacityIsolation: ptr.To(false), PodLogsDir: "", @@ -370,6 +372,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To[float64](0), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(false), LocalStorageCapacityIsolation: ptr.To(false), PodLogsDir: DefaultPodLogsDir, @@ -531,6 +534,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(true), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To[float64](1), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: "/custom/path", @@ -689,6 +693,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(true), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To[float64](1), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: "/custom/path", @@ -788,6 +793,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To(DefaultMemoryThrottlingFactor), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: DefaultPodLogsDir, @@ -887,6 +893,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To(DefaultMemoryThrottlingFactor), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: DefaultPodLogsDir, @@ -986,6 +993,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To(DefaultMemoryThrottlingFactor), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: DefaultPodLogsDir, @@ -1085,6 +1093,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) { SeccompDefault: ptr.To(false), FailCgroupV1: ptr.To(true), MemoryThrottlingFactor: ptr.To(DefaultMemoryThrottlingFactor), + MemoryReservationPolicy: v1beta1.NoneMemoryReservationPolicy, RegisterNode: ptr.To(true), LocalStorageCapacityIsolation: ptr.To(true), PodLogsDir: DefaultPodLogsDir, diff --git a/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go b/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go index 96c6d7df9c89e..05bf134e67dff 100644 --- a/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go +++ b/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go @@ -582,6 +582,7 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in out.TLSCertFile = in.TLSCertFile out.TLSPrivateKeyFile = in.TLSPrivateKeyFile out.TLSCipherSuites = *(*[]string)(unsafe.Pointer(&in.TLSCipherSuites)) + out.TLSCurvePreferences = *(*[]int32)(unsafe.Pointer(&in.TLSCurvePreferences)) out.TLSMinVersion = in.TLSMinVersion out.RotateCertificates = in.RotateCertificates out.ServerTLSBootstrap = in.ServerTLSBootstrap @@ -749,6 +750,7 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in return err } out.MemoryThrottlingFactor = (*float64)(unsafe.Pointer(in.MemoryThrottlingFactor)) + out.MemoryReservationPolicy = config.MemoryReservationPolicy(in.MemoryReservationPolicy) out.RegisterWithTaints = *(*[]corev1.Taint)(unsafe.Pointer(&in.RegisterWithTaints)) if err := v1.Convert_Pointer_bool_To_bool(&in.RegisterNode, &out.RegisterNode, s); err != nil { return err @@ -790,6 +792,7 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in out.TLSCertFile = in.TLSCertFile out.TLSPrivateKeyFile = in.TLSPrivateKeyFile out.TLSCipherSuites = *(*[]string)(unsafe.Pointer(&in.TLSCipherSuites)) + out.TLSCurvePreferences = *(*[]int32)(unsafe.Pointer(&in.TLSCurvePreferences)) out.TLSMinVersion = in.TLSMinVersion out.RotateCertificates = in.RotateCertificates out.ServerTLSBootstrap = in.ServerTLSBootstrap @@ -952,6 +955,7 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in return err } out.MemoryThrottlingFactor = (*float64)(unsafe.Pointer(in.MemoryThrottlingFactor)) + out.MemoryReservationPolicy = configv1beta1.MemoryReservationPolicy(in.MemoryReservationPolicy) out.RegisterWithTaints = *(*[]corev1.Taint)(unsafe.Pointer(&in.RegisterWithTaints)) if err := v1.Convert_bool_To_Pointer_bool(&in.RegisterNode, &out.RegisterNode, s); err != nil { return err diff --git a/pkg/kubelet/apis/config/validation/validation.go b/pkg/kubelet/apis/config/validation/validation.go index 5fc256fc79520..625f77ac8f6fa 100644 --- a/pkg/kubelet/apis/config/validation/validation.go +++ b/pkg/kubelet/apis/config/validation/validation.go @@ -96,6 +96,9 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur if kc.ImageMaximumGCAge.Duration != 0 && !localFeatureGate.Enabled(features.ImageMaximumGCAge) { allErrors = append(allErrors, fmt.Errorf("invalid configuration: ImageMaximumGCAge feature gate is required for Kubelet configuration option imageMaximumGCAge")) } + if kc.ImageMinimumGCAge.Duration < 0 { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageMinimumGCAge %v must not be negative", kc.ImageMinimumGCAge.Duration)) + } if kc.ImageMaximumGCAge.Duration < 0 { allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageMaximumGCAge %v must not be negative", kc.ImageMaximumGCAge.Duration)) } @@ -348,6 +351,17 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur allErrors = append(allErrors, fmt.Errorf("invalid configuration: memoryThrottlingFactor %v must be greater than 0 and less than or equal to 1.0", *kc.MemoryThrottlingFactor)) } + if !localFeatureGate.Enabled(features.MemoryQoS) && + kc.MemoryReservationPolicy == kubeletconfig.TieredReservationMemoryReservationPolicy { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: memoryReservationPolicy %q requires MemoryQoS feature gate to be enabled", + kc.MemoryReservationPolicy)) + } + switch kc.MemoryReservationPolicy { + case kubeletconfig.NoneMemoryReservationPolicy, kubeletconfig.TieredReservationMemoryReservationPolicy: + default: + allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for memoryReservationPolicy. Valid options are %q or %q", kc.MemoryReservationPolicy, kubeletconfig.NoneMemoryReservationPolicy, kubeletconfig.TieredReservationMemoryReservationPolicy)) + } + if kc.ContainerRuntimeEndpoint == "" { allErrors = append(allErrors, fmt.Errorf("invalid configuration: the containerRuntimeEndpoint was not specified or empty")) } diff --git a/pkg/kubelet/apis/config/validation/validation_linux.go b/pkg/kubelet/apis/config/validation/validation_linux.go index 76c976f6c00c1..e7a95526361f0 100644 --- a/pkg/kubelet/apis/config/validation/validation_linux.go +++ b/pkg/kubelet/apis/config/validation/validation_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/apis/config/validation/validation_others.go b/pkg/kubelet/apis/config/validation/validation_others.go index 34adffb844347..876c749f66081 100644 --- a/pkg/kubelet/apis/config/validation/validation_others.go +++ b/pkg/kubelet/apis/config/validation/validation_others.go @@ -1,5 +1,4 @@ //go:build !windows && !linux -// +build !windows,!linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/apis/config/validation/validation_others_test.go b/pkg/kubelet/apis/config/validation/validation_others_test.go index a99c404262592..c3eaebb2a9cc0 100644 --- a/pkg/kubelet/apis/config/validation/validation_others_test.go +++ b/pkg/kubelet/apis/config/validation/validation_others_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/kubelet/apis/config/validation/validation_test.go b/pkg/kubelet/apis/config/validation/validation_test.go index 01ec8e0412b51..655192b45fe62 100644 --- a/pkg/kubelet/apis/config/validation/validation_test.go +++ b/pkg/kubelet/apis/config/validation/validation_test.go @@ -48,6 +48,7 @@ var ( HealthzPort: 10248, ImageGCHighThresholdPercent: 85, ImageGCLowThresholdPercent: 80, + ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute}, ImagePullCredentialsVerificationPolicy: "NeverVerifyPreloadedImages", IPTablesDropBit: 15, IPTablesMasqueradeBit: 14, @@ -70,8 +71,8 @@ var ( ShutdownGracePeriod: metav1.Duration{Duration: 30 * time.Second}, ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 10 * time.Second}, MemoryThrottlingFactor: ptr.To(0.9), + MemoryReservationPolicy: kubeletconfig.NoneMemoryReservationPolicy, FeatureGates: map[string]bool{ - "CustomCPUCFSQuotaPeriod": true, "GracefulNodeShutdown": true, "MemoryQoS": true, "KubeletCrashLoopBackOffMax": true, @@ -164,7 +165,6 @@ func TestValidateKubeletConfiguration(t *testing.T) { }, { name: "invalid CPUCFSQuotaPeriod", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": true} conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 2 * time.Second} return conf }, @@ -417,7 +417,7 @@ func TestValidateKubeletConfiguration(t *testing.T) { }, { name: "CrashLoopBackOff.MaxContainerRestartPeriod just a little too high", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true, "CustomCPUCFSQuotaPeriod": true} + conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true} conf.CrashLoopBackOff = kubeletconfig.CrashLoopBackOffConfig{ // 300.9 seconds MaxContainerRestartPeriod: &metav1.Duration{Duration: 300900 * time.Millisecond}, @@ -429,7 +429,7 @@ func TestValidateKubeletConfiguration(t *testing.T) { { name: "CrashLoopBackOff.MaxContainerRestartPeriod just a little too low", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true, "CustomCPUCFSQuotaPeriod": true} + conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true} conf.CrashLoopBackOff = kubeletconfig.CrashLoopBackOffConfig{ // 300.9 seconds MaxContainerRestartPeriod: &metav1.Duration{Duration: 999 * time.Millisecond}, @@ -441,13 +441,13 @@ func TestValidateKubeletConfiguration(t *testing.T) { { name: "KubeletCrashLoopBackOffMax feature gate on, no crashLoopBackOff config, ok", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true, "CustomCPUCFSQuotaPeriod": true} + conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true} return conf }, }, { name: "KubeletCrashLoopBackOffMax feature gate on, but no crashLoopBackOff.MaxContainerRestartPeriod config", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true, "CustomCPUCFSQuotaPeriod": true} + conf.FeatureGates = map[string]bool{"KubeletCrashLoopBackOffMax": true} conf.CrashLoopBackOff = kubeletconfig.CrashLoopBackOffConfig{} return conf }, @@ -565,6 +565,21 @@ func TestValidateKubeletConfiguration(t *testing.T) { return conf }, errMsg: "invalid configuration: memoryThrottlingFactor 1.1 must be greater than 0 and less than or equal to 1.0", + }, { + name: "MemoryReservationPolicy requires MemoryQoS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"MemoryQoS": false} + conf.MemoryReservationPolicy = kubeletconfig.TieredReservationMemoryReservationPolicy + return conf + }, + errMsg: "invalid configuration: memoryReservationPolicy \"TieredReservation\" requires MemoryQoS feature gate to be enabled", + }, { + name: "invalid MemoryReservationPolicy", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MemoryReservationPolicy = "invalid" + return conf + }, + errMsg: "invalid configuration: option \"invalid\" specified for memoryReservationPolicy. Valid options are \"None\" or \"TieredReservation\"", }, { name: "invalid Taint.TimeAdded", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { @@ -611,6 +626,15 @@ func TestValidateKubeletConfiguration(t *testing.T) { return conf }, errMsg: "invalid configuration: Specifying shutdownGracePeriodByPodPriority requires feature gate GracefulNodeShutdownBasedOnPodPriority", + }, { + name: "enableSystemLogQuery is enabled with explicit default NodeLogQuery feature gate enabled", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"NodeLogQuery": true} + conf.EnableSystemLogHandler = true + conf.EnableSystemLogQuery = true + return conf + }, + errMsg: "", }, { name: "enableSystemLogQuery is enabled without NodeLogQuery feature gate", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { @@ -618,16 +642,23 @@ func TestValidateKubeletConfiguration(t *testing.T) { conf.EnableSystemLogQuery = true return conf }, - errMsg: "invalid configuration: NodeLogQuery feature gate is required for enableSystemLogQuery", + errMsg: "cannot set feature gate NodeLogQuery to false, feature is locked to true", }, { name: "enableSystemLogQuery is enabled without enableSystemLogHandler", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"NodeLogQuery": true} conf.EnableSystemLogHandler = false conf.EnableSystemLogQuery = true return conf }, errMsg: "invalid configuration: enableSystemLogHandler is required for enableSystemLogQuery", + }, { + name: "enableSystemLogQuery is enabled with enableSystemLogHandler", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnableSystemLogHandler = true + conf.EnableSystemLogQuery = true + return conf + }, + errMsg: "", }, { name: "imageMaximumGCAge should not be negative", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { @@ -720,7 +751,7 @@ func TestValidateKubeletConfiguration(t *testing.T) { conf.ImagePullCredentialsVerificationPolicy = "invalid" return conf }, - errMsg: `option "invalid" specified for imagePullCredentialsVerificationPolicy. Valid options are "NeverVerify", "NeverVerifyPreloadedImages", "NeverVerifyAllowlistedImages" or "AlwaysVerify"]`, + errMsg: `option "invalid" specified for imagePullCredentialsVerificationPolicy. Valid options are "NeverVerify", "NeverVerifyPreloadedImages", "NeverVerifyAllowlistedImages" or "AlwaysVerify"`, }, { name: "invalid PreloadedImagesVerificationAllowlist configuration - featuregate enabled", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { @@ -729,7 +760,7 @@ func TestValidateKubeletConfiguration(t *testing.T) { conf.PreloadedImagesVerificationAllowlist = []string{"test.test/repo"} return conf }, - errMsg: "can't set `preloadedImagesVerificationAllowlist` if `imagePullCredentialsVertificationPolicy` is not \"NeverVerifyAllowlistedImages\"]", + errMsg: "can't set `preloadedImagesVerificationAllowlist` if `imagePullCredentialsVertificationPolicy` is not \"NeverVerifyAllowlistedImages\"", }, { name: "invalid PreloadedImagesVerificationAllowlist configuration - featuregate disabled", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { @@ -755,6 +786,20 @@ func TestValidateKubeletConfiguration(t *testing.T) { // In OpenShift we need to tolerate unrecognized feature gates // errMsg: "unrecognized feature gate: invalid", errMsg: "", + }, { + name: "invalid configuration: invalid ImageMinimumGCAge", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageMinimumGCAge = metav1.Duration{Duration: -1} + return conf + }, + errMsg: "invalid configuration: imageMinimumGCAge -1ns must not be negative", + }, { + name: "valid ImageMinimumGCAge set to default 1ns", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + // Verify that values other than the default are accepted. + conf.ImageMinimumGCAge = metav1.Duration{Duration: 1 * time.Nanosecond} + return conf + }, }, } diff --git a/pkg/kubelet/apis/config/validation/validation_windows.go b/pkg/kubelet/apis/config/validation/validation_windows.go index 510d315da0ddb..3a5b85abe99d4 100644 --- a/pkg/kubelet/apis/config/validation/validation_windows.go +++ b/pkg/kubelet/apis/config/validation/validation_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/apis/config/validation/validation_windows_test.go b/pkg/kubelet/apis/config/validation/validation_windows_test.go index 73435915b3bf5..d697c642f8adc 100644 --- a/pkg/kubelet/apis/config/validation/validation_windows_test.go +++ b/pkg/kubelet/apis/config/validation/validation_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/kubelet/apis/config/zz_generated.deepcopy.go b/pkg/kubelet/apis/config/zz_generated.deepcopy.go index 8acf53327772d..26c3758636df5 100644 --- a/pkg/kubelet/apis/config/zz_generated.deepcopy.go +++ b/pkg/kubelet/apis/config/zz_generated.deepcopy.go @@ -333,6 +333,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.TLSCurvePreferences != nil { + in, out := &in.TLSCurvePreferences, &out.TLSCurvePreferences + *out = make([]int32, len(*in)) + copy(*out, *in) + } out.Authentication = in.Authentication out.Authorization = in.Authorization if in.PreloadedImagesVerificationAllowlist != nil { diff --git a/pkg/kubelet/apis/podresources/OWNERS b/pkg/kubelet/apis/podresources/OWNERS new file mode 100644 index 0000000000000..17d88950597d9 --- /dev/null +++ b/pkg/kubelet/apis/podresources/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - sig-node-approvers # https://github.com/kubernetes/kubernetes/blob/master/OWNERS_ALIASES#LC220:~:text=sig%2Dnode%2Dapprovers + - ffromani +reviewers: + - sig-node-reviewers # see https://github.com/kubernetes/kubernetes/blob/master/OWNERS_ALIASES#LC220:~:text=sig%2Dnode%2Dreviewers +labels: + - area/kubelet + - sig/node diff --git a/pkg/kubelet/apis/podresources/client_test.go b/pkg/kubelet/apis/podresources/client_test.go new file mode 100644 index 0000000000000..5ca0b0bd415c7 --- /dev/null +++ b/pkg/kubelet/apis/podresources/client_test.go @@ -0,0 +1,55 @@ +/* +Copyright 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 podresources + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + fakeremote "k8s.io/cri-client/pkg/fake" +) + +func TestGetClient(t *testing.T) { + testCases := map[string]func(string, time.Duration, int) (any, *grpc.ClientConn, error){ + "v1alpha1": func(socket string, timeout time.Duration, maxSize int) (any, *grpc.ClientConn, error) { + return GetV1alpha1Client(socket, timeout, maxSize) + }, + "v1": func(socket string, timeout time.Duration, maxSize int) (any, *grpc.ClientConn, error) { + return GetV1Client(socket, timeout, maxSize) + }, + } + + for version, getClientFn := range testCases { + t.Run(version, func(t *testing.T) { + socketPath, err := fakeremote.GenerateEndpoint() + require.NoError(t, err) + + client, conn, err := getClientFn(socketPath, 10*time.Second, 1024*1024) + require.NoError(t, err) + require.NotNil(t, client) + require.NoError(t, conn.Close()) + + client, conn, err = getClientFn("invalid\x00endpoint", 100*time.Millisecond, 1024*1024) + require.Error(t, err) + assert.Nil(t, client) + assert.Nil(t, conn) + }) + } +} diff --git a/pkg/kubelet/apis/podresources/server_v1.go b/pkg/kubelet/apis/podresources/server_v1.go index d5524e68bac8c..19ea25ce73c5d 100644 --- a/pkg/kubelet/apis/podresources/server_v1.go +++ b/pkg/kubelet/apis/podresources/server_v1.go @@ -64,6 +64,7 @@ func (p *v1PodResourcesServer) List(ctx context.Context, req *podresourcesv1.Lis var pods []*v1.Pod if p.useActivePods { + // GetActivePods already filters out terminal pods, so no need for additional filtering. pods = p.podsProvider.GetActivePods() } else { pods = p.podsProvider.GetPods() @@ -119,11 +120,6 @@ func (p *v1PodResourcesServer) Get(ctx context.Context, req *podresourcesv1.GetP metrics.PodResourcesEndpointRequestsTotalCount.WithLabelValues("v1").Inc() metrics.PodResourcesEndpointRequestsGetCount.WithLabelValues("v1").Inc() - if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.KubeletPodResourcesGet) { - metrics.PodResourcesEndpointErrorsGetCount.WithLabelValues("v1").Inc() - return nil, fmt.Errorf("PodResources API Get method disabled") - } - pod, exist := p.podsProvider.GetPodByName(req.PodNamespace, req.PodName) if !exist { metrics.PodResourcesEndpointErrorsGetCount.WithLabelValues("v1").Inc() @@ -157,14 +153,11 @@ func (p *v1PodResourcesServer) Get(ctx context.Context, req *podresourcesv1.GetP func (p *v1PodResourcesServer) getContainerResources(pod *v1.Pod, container *v1.Container) *podresourcesv1.ContainerResources { containerResources := &podresourcesv1.ContainerResources{ - Name: container.Name, - Devices: p.devicesProvider.GetDevices(string(pod.UID), container.Name), - CpuIds: p.cpusProvider.GetCPUs(string(pod.UID), container.Name), - Memory: p.memoryProvider.GetMemory(string(pod.UID), container.Name), + Name: container.Name, + Devices: p.devicesProvider.GetDevices(string(pod.UID), container.Name), + CpuIds: p.cpusProvider.GetCPUs(string(pod.UID), container.Name), + Memory: p.memoryProvider.GetMemory(string(pod.UID), container.Name), + DynamicResources: p.dynamicResourcesProvider.GetDynamicResources(pod, container), } - if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.KubeletPodResourcesDynamicResources) { - containerResources.DynamicResources = p.dynamicResourcesProvider.GetDynamicResources(pod, container) - } - return containerResources } diff --git a/pkg/kubelet/apis/podresources/server_v1_test.go b/pkg/kubelet/apis/podresources/server_v1_test.go index 0875366206dca..86bfd88e94dad 100644 --- a/pkg/kubelet/apis/podresources/server_v1_test.go +++ b/pkg/kubelet/apis/podresources/server_v1_test.go @@ -28,17 +28,12 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - utilfeature "k8s.io/apiserver/pkg/util/feature" - featuregatetesting "k8s.io/component-base/featuregate/testing" podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1" - pkgfeatures "k8s.io/kubernetes/pkg/features" podresourcetest "k8s.io/kubernetes/pkg/kubelet/apis/podresources/testing" "k8s.io/kubernetes/test/utils/ktesting" ) func TestListPodResourcesV1(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.KubeletPodResourcesDynamicResources, true) - tCtx := ktesting.Init(t) podName := "pod-name" podNamespace := "pod-namespace" @@ -294,8 +289,6 @@ func collectNamespacedNamesFromPodResources(prs []*podresourcesapi.PodResources) } func TestListPodResourcesUsesOnlyActivePodsV1(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.KubeletPodResourcesDynamicResources, true) - tCtx := ktesting.Init(t) numaID := int64(1) @@ -411,7 +404,6 @@ func TestListPodResourcesUsesOnlyActivePodsV1(t *testing.T) { } func TestListPodResourcesWithInitContainersV1(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.KubeletPodResourcesDynamicResources, true) tCtx := ktesting.Init(t) podName := "pod-name" podNamespace := "pod-namespace" @@ -899,11 +891,6 @@ func TestAllocatableResources(t *testing.T) { } func TestGetPodResourcesV1(t *testing.T) { - featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ - pkgfeatures.KubeletPodResourcesGet: true, - pkgfeatures.KubeletPodResourcesDynamicResources: true, - }) - tCtx := ktesting.Init(t) podName := "pod-name" podNamespace := "pod-namespace" @@ -952,11 +939,14 @@ func TestGetPodResourcesV1(t *testing.T) { } pluginCDIDevices := []*podresourcesapi.CDIDevice{{Name: "dra-dev0"}, {Name: "dra-dev1"}} + draDriverName := "dra.example.com" + poolName := "worker-1-pool" + deviceName := "gpu-1" draDevs := []*podresourcesapi.DynamicResource{ { ClaimName: "claim-name", ClaimNamespace: "default", - ClaimResources: []*podresourcesapi.ClaimResource{{CdiDevices: pluginCDIDevices}}, + ClaimResources: []*podresourcesapi.ClaimResource{{CdiDevices: pluginCDIDevices, DriverName: draDriverName, PoolName: poolName, DeviceName: deviceName}}, }, } @@ -1079,11 +1069,6 @@ func TestGetPodResourcesV1(t *testing.T) { } func TestGetPodResourcesWithInitContainersV1(t *testing.T) { - featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ - pkgfeatures.KubeletPodResourcesGet: true, - pkgfeatures.KubeletPodResourcesDynamicResources: true, - }) - tCtx := ktesting.Init(t) podName := "pod-name" podNamespace := "pod-namespace" diff --git a/pkg/kubelet/apis/pods/constants.go b/pkg/kubelet/apis/pods/constants.go new file mode 100644 index 0000000000000..43d5bcc38b254 --- /dev/null +++ b/pkg/kubelet/apis/pods/constants.go @@ -0,0 +1,26 @@ +/* +Copyright 2025 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 pods + +const ( + Resource = "/pods" + Socket = "pods-api" + + // Duplicated from PodResources API + DefaultQPS = 100 + DefaultBurstTokens = 10 +) diff --git a/pkg/kubelet/apis/pods/server.go b/pkg/kubelet/apis/pods/server.go new file mode 100644 index 0000000000000..5b9fff3e7f680 --- /dev/null +++ b/pkg/kubelet/apis/pods/server.go @@ -0,0 +1,348 @@ +/* +Copyright 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 pods + +import ( + "context" + "sort" + "sync" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/klog/v2" + podsv1alpha1 "k8s.io/kubelet/pkg/apis/pods/v1alpha1" + "k8s.io/kubernetes/pkg/kubelet/metrics" + "k8s.io/kubernetes/pkg/kubelet/pod" + podstatus "k8s.io/kubernetes/pkg/kubelet/status" +) + +type PodWatchEvent struct { + Type watch.EventType + UID types.UID + Pod *v1.Pod +} + +type broadcaster struct { + lock sync.RWMutex + clients map[chan PodWatchEvent]struct{} + incoming chan PodWatchEvent +} + +func NewBroadcaster() *broadcaster { + b := &broadcaster{ + clients: make(map[chan PodWatchEvent]struct{}), + incoming: make(chan PodWatchEvent, 1000), + } + go b.run() + return b +} + +func (b *broadcaster) run() { + logger := klog.FromContext(context.Background()) + for event := range b.incoming { + b.lock.RLock() + // We collect clients to drop to avoid modifying the map during iteration + // which would require a Write lock and block other RLockers. + var clientsToDrop []chan PodWatchEvent + + for client := range b.clients { + select { + case client <- event: + default: + logger.Info("Watch client channel is full, dropping client.") + metrics.PodWatchEventsDroppedTotal.Inc() + clientsToDrop = append(clientsToDrop, client) + } + } + b.lock.RUnlock() + + if len(clientsToDrop) > 0 { + b.lock.Lock() + for _, client := range clientsToDrop { + if _, ok := b.clients[client]; ok { + delete(b.clients, client) + close(client) + } + } + b.lock.Unlock() + } + } +} + +func (b *broadcaster) Register(client chan PodWatchEvent) { + b.lock.Lock() + defer b.lock.Unlock() + b.clients[client] = struct{}{} + logger := klog.FromContext(context.Background()) + logger.Info("Registered new watch client", "totalClients", len(b.clients)) +} + +func (b *broadcaster) Unregister(client chan PodWatchEvent) { + b.lock.Lock() + defer b.lock.Unlock() + if _, ok := b.clients[client]; ok { + delete(b.clients, client) + close(client) + } + logger := klog.FromContext(context.Background()) + logger.Info("Unregistered watch client", "totalClients", len(b.clients)) +} + +func (b *broadcaster) Broadcast(event PodWatchEvent) { + select { + case b.incoming <- event: + default: + // This should realistically never happen because run() purges slow clients. + logger := klog.FromContext(context.Background()) + logger.Info("Broadcaster internal buffer full, dropping event.") + metrics.PodWatchEventsDroppedTotal.Inc() + } +} + +// PodsServer is the gRPC server that provides pod information. +type PodsServer struct { + podsv1alpha1.UnimplementedPodsServer + podManager pod.Manager + statusProvider podstatus.PodStatusProvider + broadcaster *broadcaster +} + +// NewPodsServer creates a new PodServer for production use. +func NewPodsServer(broadcaster *broadcaster, podManager pod.Manager, statusProvider podstatus.PodStatusProvider) *PodsServer { + return &PodsServer{ + podManager: podManager, + statusProvider: statusProvider, + broadcaster: broadcaster, + } +} + +// NewPodsServerForTest creates a new PodServer with an injectable ticker for testing. +func NewPodsServerForTest(broadcaster *broadcaster, podManager pod.Manager, statusProvider podstatus.PodStatusProvider) *PodsServer { + return &PodsServer{ + podManager: podManager, + statusProvider: statusProvider, + broadcaster: broadcaster, + } +} + +// OnPodUpdated is called when a pod's spec and status are coherent. +func (s *PodsServer) OnPodUpdated(pod *v1.Pod, status v1.PodStatus, isAdded bool) { + eventType := watch.Modified + if isAdded { + eventType = watch.Added + } + podCopy := *pod + podCopy.Status = status + s.broadcaster.Broadcast(PodWatchEvent{Type: eventType, UID: pod.UID, Pod: &podCopy}) + logger := klog.FromContext(context.Background()) + logger.Info("Pod update broadcasted", "podUID", pod.UID, "type", eventType) +} + +// OnPodRemoved is called when a pod is removed. +func (s *PodsServer) OnPodRemoved(pod *v1.Pod) { + minimalPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Name, + Namespace: pod.Namespace, + UID: pod.UID, + }, + } + s.broadcaster.Broadcast(PodWatchEvent{Type: watch.Deleted, UID: pod.UID, Pod: minimalPod}) + logger := klog.FromContext(context.Background()) + logger.Info("Pod removed broadcasted", "podUID", pod.UID) +} + +// ListPods returns a list of pods. +func (s *PodsServer) ListPods(ctx context.Context, req *podsv1alpha1.ListPodsRequest) (*podsv1alpha1.ListPodsResponse, error) { + logger := klog.FromContext(ctx) + logger.Info("ListPods called") + + // TODO: Implement filtering based on req.Filter, pagination with req.PageToken and req.PageSize + podsToReturn := s.podManager.GetPods() + sort.Slice(podsToReturn, func(i, j int) bool { + return podsToReturn[i].UID < podsToReturn[j].UID + }) + + protoPods := make([][]byte, len(podsToReturn)) + for i, p := range podsToReturn { + podToMarshal := p + if podStatus, ok := s.statusProvider.GetPodStatus(p.UID); ok { + podCopy := *p + podCopy.Status = podStatus + podToMarshal = &podCopy + } + podBytes, err := podToMarshal.Marshal() + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to marshal pod: %v", err) + } + protoPods[i] = podBytes + } + + return &podsv1alpha1.ListPodsResponse{Pods: protoPods}, nil +} + +// GetPod returns a single pod by UID. +func (s *PodsServer) GetPod(ctx context.Context, req *podsv1alpha1.GetPodRequest) (*podsv1alpha1.GetPodResponse, error) { + logger := klog.FromContext(ctx) + logger.Info("GetPod called", "podUID", req.PodUID) + + podUID := types.UID(req.PodUID) + pod, ok := s.podManager.GetPodByUID(podUID) + if !ok { + return nil, status.Errorf(codes.NotFound, "pod with UID %s not found", req.PodUID) + } + + podToMarshal := pod + if podStatus, ok := s.statusProvider.GetPodStatus(podUID); ok { + podCopy := *pod + podCopy.Status = podStatus + podToMarshal = &podCopy + } + + podBytes, err := podToMarshal.Marshal() + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to marshal pod: %v", err) + } + + return &podsv1alpha1.GetPodResponse{Pod: podBytes}, nil +} + +// WatchPods streams pod events. +func (s *PodsServer) WatchPods(req *podsv1alpha1.WatchPodsRequest, stream podsv1alpha1.Pods_WatchPodsServer) error { + clientAddr := "unknown" + if p, ok := peer.FromContext(stream.Context()); ok { + clientAddr = p.Addr.String() + } + logger := klog.FromContext(stream.Context()) + logger.Info("WatchPods called", "client", clientAddr) + + clientChannel := make(chan PodWatchEvent, 100) + s.broadcaster.Register(clientChannel) + defer func() { + s.broadcaster.Unregister(clientChannel) + logger.Info("Watch client disconnected", "client", clientAddr) + }() + + // Send initial ADDED events + initialPods := s.podManager.GetPods() + sort.Slice(initialPods, func(i, j int) bool { + return initialPods[i].UID < initialPods[j].UID + }) + + for _, p := range initialPods { + podToMarshal := p + if podStatus, ok := s.statusProvider.GetPodStatus(p.UID); ok { + podCopy := *p + podCopy.Status = podStatus + podToMarshal = &podCopy + } + podBytes, err := podToMarshal.Marshal() + if err != nil { + logger.Error(err, "Error marshalling initial watch event pod") + metrics.PodWatchEventsDroppedTotal.Inc() + return status.Errorf(codes.Internal, "error marshalling initial watch event pod: %v", err) + } + if err := stream.Send(&podsv1alpha1.WatchPodsEvent{ + Type: podsv1alpha1.EventType_ADDED, + Pod: podBytes, + }); err != nil { + logger.Error(err, "Error sending initial watch event") + return err + } + } + + // Send INITIAL_SYNC_COMPLETE event to indicate that the initial list is complete + if err := stream.Send(&podsv1alpha1.WatchPodsEvent{ + Type: podsv1alpha1.EventType_INITIAL_SYNC_COMPLETE, + }); err != nil { + logger.Error(err, "Error sending initial sync watch event") + return err + } + + for { + select { + case <-stream.Context().Done(): + logger.Info("Watch context cancelled", "client", clientAddr) + return stream.Context().Err() + case event, ok := <-clientChannel: + if !ok { + logger.Info("Watch client channel closed", "client", clientAddr) + return status.Errorf(codes.ResourceExhausted, "watch client channel closed") + } + var podToMarshal *v1.Pod + if event.Pod != nil { + podToMarshal = event.Pod + } else if event.Type != watch.Bookmark { + // Fallback to looking up the pod if it is not provided in the event. + // This should not happen with the current implementation but is kept for safety. + p, ok := s.podManager.GetPodByUID(event.UID) + if ok { + podToMarshal = p + } else { + logger.Info("Pod not found in manager during watch event processing", "uid", event.UID, "type", event.Type) + continue + } + } + + var podBytes []byte + if podToMarshal != nil { + var err error + podBytes, err = podToMarshal.Marshal() + if err != nil { + logger.Error(err, "Error marshalling watch event pod") + metrics.PodWatchEventsDroppedTotal.Inc() + return status.Errorf(codes.Internal, "error marshalling watch event pod: %v", err) + } + } + + eventType, err := convertWatchEventType(event.Type) + if err != nil { + logger.Error(err, "Unknown watch event type") + continue + } + + if err := stream.Send(&podsv1alpha1.WatchPodsEvent{ + Type: eventType, + Pod: podBytes, + }); err != nil { + logger.Error(err, "Error sending watch event to client", "client", clientAddr) + return err + } + } + } +} + +func convertWatchEventType(watchType watch.EventType) (podsv1alpha1.EventType, error) { + switch watchType { + case watch.Added: + return podsv1alpha1.EventType_ADDED, nil + case watch.Modified: + return podsv1alpha1.EventType_MODIFIED, nil + case watch.Deleted: + return podsv1alpha1.EventType_DELETED, nil + case watch.Bookmark: + return podsv1alpha1.EventType_INITIAL_SYNC_COMPLETE, nil + default: + return podsv1alpha1.EventType_UNSPECIFIED, status.Errorf(codes.Internal, "unknown watch event type: %v", watchType) + } +} diff --git a/pkg/kubelet/apis/pods/server_unit_test.go b/pkg/kubelet/apis/pods/server_unit_test.go new file mode 100644 index 0000000000000..66059450a9e26 --- /dev/null +++ b/pkg/kubelet/apis/pods/server_unit_test.go @@ -0,0 +1,434 @@ +/* +Copyright 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 pods_test + +import ( + "context" + "fmt" + "math/rand" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/component-base/metrics/testutil" + podsv1alpha1 "k8s.io/kubelet/pkg/apis/pods/v1alpha1" + "k8s.io/kubernetes/pkg/api/legacyscheme" + corefuzzer "k8s.io/kubernetes/pkg/apis/core/fuzzer" + podsapi "k8s.io/kubernetes/pkg/kubelet/apis/pods" + "k8s.io/kubernetes/pkg/kubelet/metrics" + kubepodtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + statustest "k8s.io/kubernetes/pkg/kubelet/status/testing" +) + +func TestStartEventLoop(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Name: "pod1", Namespace: "ns1"}} + + clientChannel := make(chan podsapi.PodWatchEvent, 100) + broadcaster.Register(clientChannel) + defer broadcaster.Unregister(clientChannel) + + server.OnPodUpdated(pod1, v1.PodStatus{Phase: v1.PodPending}, true) + server.OnPodUpdated(pod1, v1.PodStatus{Phase: v1.PodSucceeded}, false) + server.OnPodRemoved(pod1) + + event := <-clientChannel + assert.Equal(t, watch.Added, event.Type) + assert.Equal(t, pod1.UID, event.UID) + assert.Equal(t, v1.PodPending, event.Pod.Status.Phase) + + event = <-clientChannel + assert.Equal(t, watch.Modified, event.Type) + assert.Equal(t, pod1.UID, event.UID) + assert.Equal(t, v1.PodSucceeded, event.Pod.Status.Phase) + + event = <-clientChannel + assert.Equal(t, watch.Deleted, event.Type) + assert.Equal(t, pod1.UID, event.UID) + require.NotNil(t, event.Pod) + assert.Equal(t, pod1.Name, event.Pod.Name) + assert.Equal(t, pod1.Namespace, event.Pod.Namespace) + assert.Equal(t, pod1.UID, event.Pod.UID) + assert.Empty(t, event.Pod.Spec.Containers) + + select { + case event := <-clientChannel: + t.Errorf("Unexpected event received: %v", event) + default: + } +} + +type MockWatchPodsServer struct { + grpc.ServerStream + Ctx context.Context + EventCh chan *podsv1alpha1.WatchPodsEvent +} + +func (m *MockWatchPodsServer) Send(event *podsv1alpha1.WatchPodsEvent) error { + select { + case m.EventCh <- event: + return nil + case <-m.Ctx.Done(): + return m.Ctx.Err() + } +} + +func (m *MockWatchPodsServer) Context() context.Context { + return m.Ctx +} + +func TestWatchPods(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Name: "pod1", Namespace: "ns1"}} + + mockManager.On("GetPods").Return([]*v1.Pod{pod1}) + mockManager.On("GetPodByUID", types.UID("pod1-uid")).Return(pod1, true) + mockStatus.On("GetPodStatus", mock.Anything).Return(v1.PodStatus{}, false) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + mockStream := &MockWatchPodsServer{ + Ctx: ctx, + EventCh: make(chan *podsv1alpha1.WatchPodsEvent, 10), + } + + go func() { + _ = server.WatchPods(&podsv1alpha1.WatchPodsRequest{}, mockStream) + }() + + // Verify initial ADDED event + event := <-mockStream.EventCh + assert.Equal(t, podsv1alpha1.EventType_ADDED, event.Type) + podOut := &v1.Pod{} + err := podOut.Unmarshal(event.Pod) + require.NoError(t, err) + assert.Equal(t, "pod1", podOut.Name) + + // Verify INITIAL_SYNC event + event = <-mockStream.EventCh + assert.Equal(t, podsv1alpha1.EventType_INITIAL_SYNC_COMPLETE, event.Type) + assert.Empty(t, event.Pod) + + // Trigger an update + server.OnPodUpdated(pod1, pod1.Status, false) + + // Verify MODIFIED event + event = <-mockStream.EventCh + assert.Equal(t, podsv1alpha1.EventType_MODIFIED, event.Type) + podOut = &v1.Pod{} + err = podOut.Unmarshal(event.Pod) + require.NoError(t, err) + assert.Equal(t, "pod1", podOut.Name) + + // Trigger a removal + server.OnPodRemoved(pod1) + + // Verify DELETED event + event = <-mockStream.EventCh + assert.Equal(t, podsv1alpha1.EventType_DELETED, event.Type) + podOut = &v1.Pod{} + err = podOut.Unmarshal(event.Pod) + require.NoError(t, err) + assert.Equal(t, "pod1", podOut.Name) + assert.Equal(t, "ns1", podOut.Namespace) + assert.Equal(t, pod1.UID, podOut.UID) + assert.Empty(t, podOut.Spec.Containers) +} + +func TestListPods(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Name: "pod1", Namespace: "ns1"}} + pod2 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod2-uid", Name: "pod2", Namespace: "ns2"}} + + mockManager.On("GetPods").Return([]*v1.Pod{pod1, pod2}) + mockStatus.On("GetPodStatus", mock.Anything).Return(v1.PodStatus{}, false) + + resp, err := server.ListPods(context.Background(), &podsv1alpha1.ListPodsRequest{}) + require.NoError(t, err) + pod1Out := &v1.Pod{} + err = pod1Out.Unmarshal(resp.Pods[0]) + require.NoError(t, err) + pod2Out := &v1.Pod{} + err = pod2Out.Unmarshal(resp.Pods[1]) + require.NoError(t, err) + require.Equal(t, "pod1", pod1Out.Name) + assert.Equal(t, "pod2", pod2Out.Name) +} + +func TestGetPod(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Name: "pod1", Namespace: "ns1"}} + pod1.Spec.EphemeralContainers = []v1.EphemeralContainer{ + { + EphemeralContainerCommon: v1.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + Command: []string{"sh"}, + Stdin: true, + TTY: true, + }, + }, + } + pod1.Status = v1.PodStatus{Phase: v1.PodRunning} + + mockManager.On("GetPodByUID", mock.Anything).Return(pod1, true) + mockStatus.On("GetPodStatus", mock.Anything).Return(v1.PodStatus{}, false) + + resp, err := server.GetPod(context.Background(), &podsv1alpha1.GetPodRequest{PodUID: "pod1-uid"}) + require.NoError(t, err) + podOut := &v1.Pod{} + err = podOut.Unmarshal(resp.Pod) + require.NoError(t, err) + require.Equal(t, "pod1", podOut.Name) + assert.Equal(t, v1.PodRunning, podOut.Status.Phase) + require.Len(t, podOut.Spec.EphemeralContainers, 1) + assert.Equal(t, "debugger", podOut.Spec.EphemeralContainers[0].Name) + assert.Equal(t, "busybox", podOut.Spec.EphemeralContainers[0].Image) + assert.Equal(t, []string{"sh"}, podOut.Spec.EphemeralContainers[0].Command) + assert.True(t, podOut.Spec.EphemeralContainers[0].Stdin) + assert.True(t, podOut.Spec.EphemeralContainers[0].TTY) +} + +func TestStaticPod(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + + staticPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "static-pod-uid", + Name: "static-pod", + Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/config.source": "file", + }, + }, + } + + mockManager.On("GetPodByUID", types.UID("static-pod-uid")).Return(staticPod, true) + mockStatus.On("GetPodStatus", mock.Anything).Return(v1.PodStatus{}, false) + + resp, err := server.GetPod(context.Background(), &podsv1alpha1.GetPodRequest{PodUID: "static-pod-uid"}) + require.NoError(t, err) + + podOut := &v1.Pod{} + err = podOut.Unmarshal(resp.Pod) + require.NoError(t, err) + assert.Equal(t, "static-pod", podOut.Name) + assert.Equal(t, "file", podOut.Annotations["kubernetes.io/config.source"]) +} + +func TestErrorsAndMetrics(t *testing.T) { + metrics.Register() + + t.Run("DroppedWatchEventIncrementsMetric", func(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Name: "pod1", Namespace: "ns1"}} + mockStatus.On("GetPodStatus", mock.Anything).Return(v1.PodStatus{}, false) + server.OnPodUpdated(pod1, pod1.Status, true) + + // Reset the metric before the test + metrics.PodWatchEventsDroppedTotal.Reset() + clientChannel := make(chan podsapi.PodWatchEvent, 1) // Buffered channel of size 1 + broadcaster.Register(clientChannel) + defer broadcaster.Unregister(clientChannel) + + // Send two events. The first one should fill the buffer. + broadcaster.Broadcast(podsapi.PodWatchEvent{UID: "1"}) + // The second one should be dropped and increment the metric. + broadcaster.Broadcast(podsapi.PodWatchEvent{UID: "2"}) + + // Wait for the background goroutine to process the events and update metrics + err := wait.PollUntilContextTimeout(context.Background(), 10*time.Millisecond, 2*time.Second, true, func(ctx context.Context) (bool, error) { + count, err := testutil.GetCounterMetricValue(metrics.PodWatchEventsDroppedTotal) + if err != nil { + return false, err + } + return count == 1, nil + }) + require.NoError(t, err, "Metric PodWatchEventsDroppedTotal should be 1") + + err = testutil.CollectAndCompare(metrics.PodWatchEventsDroppedTotal, strings.NewReader(` + # HELP kubelet_pod_watch_events_dropped_total [ALPHA] Cumulative number of pod watch events dropped. + # TYPE kubelet_pod_watch_events_dropped_total counter + kubelet_pod_watch_events_dropped_total 1 + `)) + require.NoError(t, err) + }) +} + +func TestSerialize(t *testing.T) { + apiObjectFuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, corefuzzer.Funcs), rand.NewSource(152), legacyscheme.Codecs) + for range 100 { + pod := &v1.Pod{} + apiObjectFuzzer.Fill(pod) + podBytes, err := pod.Marshal() + if err != nil { + t.Fatal(err) + } + + resp := &podsv1alpha1.ListPodsResponse{Pods: [][]byte{podBytes}} + data, err := proto.Marshal(resp) + if err != nil { + t.Fatal(err) + } + + resp2 := &podsv1alpha1.ListPodsResponse{} + if err := proto.Unmarshal(data, resp2); err != nil { + t.Fatal(err) + } + + if !proto.Equal(resp, resp2) { + t.Fatal("round-tripped objects were different") + } + pod2 := &v1.Pod{} + if err := pod2.Unmarshal(resp2.Pods[0]); err != nil { + t.Fatal(err) + } + if !apiequality.Semantic.DeepEqual(pod, pod2) { + t.Fatal("round-tripped objects were different") + } + } +} + +func TestBroadcaster_SlowClient(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + + numFastClients := 3 + numEvents := 10 + fastClients := make([]chan podsapi.PodWatchEvent, numFastClients) + for i := range numFastClients { + fastClients[i] = make(chan podsapi.PodWatchEvent, numEvents) + broadcaster.Register(fastClients[i]) + } + + slowClient := make(chan podsapi.PodWatchEvent) + broadcaster.Register(slowClient) + + for i := range numEvents { + broadcaster.Broadcast(podsapi.PodWatchEvent{UID: types.UID(fmt.Sprintf("event-%d", i))}) + } + + for i := range numFastClients { + for j := range numEvents { + select { + case event := <-fastClients[i]: + expectedUID := types.UID(fmt.Sprintf("event-%d", j)) + assert.Equal(t, expectedUID, event.UID, "Fast client %d missed or got wrong event", i) + case <-time.After(2 * time.Second): + t.Fatalf("Fast client %d timed out waiting for event %d", i, j) + } + } + } + + err := wait.PollUntilContextTimeout(context.Background(), 10*time.Millisecond, 2*time.Second, true, func(ctx context.Context) (bool, error) { + select { + case _, ok := <-slowClient: + if !ok { + return true, nil + } + return false, nil + default: + count, _ := testutil.GetCounterMetricValue(metrics.PodWatchEventsDroppedTotal) + return count >= 1, nil + } + }) + assert.NoError(t, err, "Slow client should have been dropped") +} + +func TestStatusOverlay(t *testing.T) { + broadcaster := podsapi.NewBroadcaster() + mockManager := new(kubepodtest.MockManager) + mockStatus := new(statustest.MockPodStatusProvider) + server := podsapi.NewPodsServerForTest(broadcaster, mockManager, mockStatus) + + pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Name: "pod1", Namespace: "ns1"}} + pod1.Status.Phase = v1.PodPending + + overlaidStatus := v1.PodStatus{Phase: v1.PodRunning} + + mockManager.On("GetPods").Return([]*v1.Pod{pod1}) + mockManager.On("GetPodByUID", pod1.UID).Return(pod1, true) + mockStatus.On("GetPodStatus", pod1.UID).Return(overlaidStatus, true) + + t.Run("GetPod overlays status", func(t *testing.T) { + resp, err := server.GetPod(context.Background(), &podsv1alpha1.GetPodRequest{PodUID: string(pod1.UID)}) + require.NoError(t, err) + podOut := &v1.Pod{} + err = podOut.Unmarshal(resp.Pod) + require.NoError(t, err) + assert.Equal(t, v1.PodRunning, podOut.Status.Phase) + }) + + t.Run("ListPods overlays status", func(t *testing.T) { + resp, err := server.ListPods(context.Background(), &podsv1alpha1.ListPodsRequest{}) + require.NoError(t, err) + podOut := &v1.Pod{} + err = podOut.Unmarshal(resp.Pods[0]) + require.NoError(t, err) + assert.Equal(t, v1.PodRunning, podOut.Status.Phase) + }) + + t.Run("WatchPods overlays status in initial sync", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockStream := &MockWatchPodsServer{ + Ctx: ctx, + EventCh: make(chan *podsv1alpha1.WatchPodsEvent, 10), + } + go func() { + _ = server.WatchPods(&podsv1alpha1.WatchPodsRequest{}, mockStream) + }() + + event := <-mockStream.EventCh + assert.Equal(t, podsv1alpha1.EventType_ADDED, event.Type) + podOut := &v1.Pod{} + err := podOut.Unmarshal(event.Pod) + require.NoError(t, err) + assert.Equal(t, v1.PodRunning, podOut.Status.Phase) + }) +} diff --git a/pkg/kubelet/cadvisor/cadvisor_linux.go b/pkg/kubelet/cadvisor/cadvisor_linux.go index 4b494131d47ed..f2fd621d39dea 100644 --- a/pkg/kubelet/cadvisor/cadvisor_linux.go +++ b/pkg/kubelet/cadvisor/cadvisor_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -33,12 +32,23 @@ import ( _ "github.com/google/cadvisor/container/crio/install" _ "github.com/google/cadvisor/container/systemd/install" + // Register filesystem plugins needed for container stats. + _ "github.com/google/cadvisor/fs/btrfs/install" + _ "github.com/google/cadvisor/fs/devicemapper/install" + _ "github.com/google/cadvisor/fs/nfs/install" + _ "github.com/google/cadvisor/fs/overlay/install" + _ "github.com/google/cadvisor/fs/tmpfs/install" + _ "github.com/google/cadvisor/fs/vfs/install" + _ "github.com/google/cadvisor/fs/zfs/install" + "github.com/google/cadvisor/cache/memory" cadvisormetrics "github.com/google/cadvisor/container" cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapiv2 "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/manager" "github.com/google/cadvisor/utils/sysfs" + "github.com/opencontainers/cgroups" + cgroupfs2 "github.com/opencontainers/cgroups/fs2" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/features" @@ -88,13 +98,12 @@ func init() { } // New creates a new cAdvisor Interface for linux systems. -func New(imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupRoots []string, usingLegacyStats, localStorageCapacityIsolation bool) (Interface, error) { +func New(logger klog.Logger, imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupRoots []string, usingLegacyStats, localStorageCapacityIsolation bool) (Interface, error) { sysFs := sysfs.NewRealSysFs() includedMetrics := cadvisormetrics.MetricSet{ cadvisormetrics.CpuUsageMetrics: struct{}{}, cadvisormetrics.MemoryUsageMetrics: struct{}{}, - cadvisormetrics.CpuLoadMetrics: struct{}{}, cadvisormetrics.DiskIOMetrics: struct{}{}, cadvisormetrics.NetworkUsageMetrics: struct{}{}, cadvisormetrics.AppMetrics: struct{}{}, @@ -103,7 +112,11 @@ func New(imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupRoots [ } if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPSI) { - includedMetrics[cadvisormetrics.PressureMetrics] = struct{}{} + if IsPsiEnabled(logger) { + includedMetrics[cadvisormetrics.PressureMetrics] = struct{}{} + } else { + logger.Info("PSI support not available") + } } if usingLegacyStats || localStorageCapacityIsolation { @@ -163,6 +176,26 @@ func (cc *cadvisorClient) ImagesFsInfo(ctx context.Context) (cadvisorapiv2.FsInf return cc.getFsInfo(ctx, label) } +// IsPsiEnabled checks whether PSI (Pressure Stall Information) is available on +// the host by opening the root cgroup's cpu.pressure file using the same +// opencontainers/cgroups library that cAdvisor uses to read actual PSI values. +// PSI is a single kernel feature (CONFIG_PSI / boot param "psi=") so checking +// cpu.pressure alone is sufficient to determine support for all three resources +// (cpu, memory, io). +func IsPsiEnabled(logger klog.Logger) bool { + return isPsiEnabled(logger, cgroupfs2.UnifiedMountpoint, "cpu.pressure") +} + +func isPsiEnabled(logger klog.Logger, cgroupDir, psiFile string) bool { + f, err := cgroups.OpenFile(cgroupDir, psiFile, os.O_RDONLY) + if err != nil { + logger.V(4).Info("PSI not available", "dir", cgroupDir, "file", psiFile, "err", err) + return false + } + _ = f.Close() + return true +} + func (cc *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) { return cc.GetDirFsInfo(cc.rootPath) } diff --git a/pkg/kubelet/cadvisor/cadvisor_linux_test.go b/pkg/kubelet/cadvisor/cadvisor_linux_test.go index c603e6d7f788c..2c48d84538423 100644 --- a/pkg/kubelet/cadvisor/cadvisor_linux_test.go +++ b/pkg/kubelet/cadvisor/cadvisor_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. @@ -21,14 +20,52 @@ package cadvisor import ( "fmt" + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/google/cadvisor/container/crio" cadvisorfs "github.com/google/cadvisor/fs" + "github.com/opencontainers/cgroups" + "k8s.io/klog/v2" ) +func TestIsPsiEnabled(t *testing.T) { + testcases := []struct { + description string + createPSIFile bool + expected bool + }{{ + description: "PSI enabled when cgroup pressure file exists", + createPSIFile: true, + expected: true, + }, { + description: "PSI disabled when cgroup pressure file does not exist", + createPSIFile: false, + expected: false, + }} + + cgroups.TestMode = true + defer func() { cgroups.TestMode = false }() + + for _, tc := range testcases { + t.Run(tc.description, func(t *testing.T) { + tmpDir := t.TempDir() + + if tc.createPSIFile { + err := os.WriteFile(filepath.Join(tmpDir, "cpu.pressure"), []byte("some avg10=0.00 avg60=0.00 avg300=0.00 total=0\n"), 0644) + require.NoError(t, err) + } + + result := isPsiEnabled(klog.Background(), tmpDir, "cpu.pressure") + assert.Equal(t, tc.expected, result) + }) + } +} + func TestImageFsInfoLabel(t *testing.T) { testcases := []struct { description string diff --git a/pkg/kubelet/cadvisor/cadvisor_unsupported.go b/pkg/kubelet/cadvisor/cadvisor_unsupported.go index e555f2e5794b0..5c7b63e04080e 100644 --- a/pkg/kubelet/cadvisor/cadvisor_unsupported.go +++ b/pkg/kubelet/cadvisor/cadvisor_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2015 The Kubernetes Authors. @@ -25,6 +24,7 @@ import ( cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "k8s.io/klog/v2" ) type cadvisorUnsupported struct { @@ -33,7 +33,7 @@ type cadvisorUnsupported struct { var _ Interface = new(cadvisorUnsupported) // New creates a new cAdvisor Interface for unsupported systems. -func New(imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupsRoots []string, usingLegacyStats, localStorageCapacityIsolation bool) (Interface, error) { +func New(_ klog.Logger, imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupsRoots []string, usingLegacyStats, localStorageCapacityIsolation bool) (Interface, error) { return &cadvisorUnsupported{}, nil } @@ -74,3 +74,7 @@ func (cu *cadvisorUnsupported) ContainerFsInfo(context.Context) (cadvisorapiv2.F func (cu *cadvisorUnsupported) GetDirFsInfo(path string) (cadvisorapiv2.FsInfo, error) { return cadvisorapiv2.FsInfo{}, nil } + +func IsPsiEnabled(_ klog.Logger) bool { + return false +} diff --git a/pkg/kubelet/cadvisor/cadvisor_windows.go b/pkg/kubelet/cadvisor/cadvisor_windows.go index 5e688cbe840a7..c9003b6963525 100644 --- a/pkg/kubelet/cadvisor/cadvisor_windows.go +++ b/pkg/kubelet/cadvisor/cadvisor_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2015 The Kubernetes Authors. @@ -24,7 +23,6 @@ import ( cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapiv2 "github.com/google/cadvisor/info/v2" - "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/kubelet/winstats" ) @@ -37,7 +35,7 @@ type cadvisorClient struct { var _ Interface = new(cadvisorClient) // New creates a cAdvisor and exports its API on the specified port if port > 0. -func New(imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupRoots []string, usingLegacyStats, localStorageCapacityIsolation bool) (Interface, error) { +func New(_ klog.Logger, imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupRoots []string, usingLegacyStats, localStorageCapacityIsolation bool) (Interface, error) { client, err := winstats.NewPerfCounterClient(klog.TODO()) return &cadvisorClient{ rootPath: rootPath, @@ -81,3 +79,7 @@ func (cu *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) { func (cu *cadvisorClient) GetDirFsInfo(path string) (cadvisorapiv2.FsInfo, error) { return cu.winStatsClient.GetDirFsInfo(path) } + +func IsPsiEnabled(_ klog.Logger) bool { + return false +} diff --git a/pkg/kubelet/cadvisor/helpers_linux.go b/pkg/kubelet/cadvisor/helpers_linux.go index 684274787e3ab..fb19f47ec442d 100644 --- a/pkg/kubelet/cadvisor/helpers_linux.go +++ b/pkg/kubelet/cadvisor/helpers_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/cadvisor/helpers_unsupported.go b/pkg/kubelet/cadvisor/helpers_unsupported.go index 7d4dd4f7c488e..a8e3dca97eb05 100644 --- a/pkg/kubelet/cadvisor/helpers_unsupported.go +++ b/pkg/kubelet/cadvisor/helpers_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/cadvisor/util_test.go b/pkg/kubelet/cadvisor/util_test.go index 375b1cf4fdbe8..49da037bd6285 100644 --- a/pkg/kubelet/cadvisor/util_test.go +++ b/pkg/kubelet/cadvisor/util_test.go @@ -1,5 +1,4 @@ //go:build cgo && linux -// +build cgo,linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/client/kubelet_client.go b/pkg/kubelet/client/kubelet_client.go index 00d08e6f8002d..b9d366ad462d2 100644 --- a/pkg/kubelet/client/kubelet_client.go +++ b/pkg/kubelet/client/kubelet_client.go @@ -68,6 +68,7 @@ type ConnectionInfo struct { Port string Transport http.RoundTripper InsecureSkipTLSVerifyTransport http.RoundTripper + NodeFeatures []string } // ConnectionInfoGetter provides ConnectionInfo for the kubelet running on a named node @@ -209,5 +210,6 @@ func (k *NodeConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeNa Port: strconv.Itoa(port), Transport: k.transport, InsecureSkipTLSVerifyTransport: k.insecureSkipTLSVerifyTransport, + NodeFeatures: node.Status.DeclaredFeatures, }, nil } diff --git a/pkg/kubelet/client/kubelet_client_test.go b/pkg/kubelet/client/kubelet_client_test.go index 0c9a07f3db38c..1cf8e5a3d94a6 100644 --- a/pkg/kubelet/client/kubelet_client_test.go +++ b/pkg/kubelet/client/kubelet_client_test.go @@ -24,6 +24,7 @@ import ( "net/http/httptest" "net/http/httputil" "net/url" + "slices" "strconv" "testing" @@ -281,12 +282,13 @@ func TestNewNodeConnectionInfoGetter(t *testing.T) { func TestGetConnectionInfo(t *testing.T) { tests := []struct { - name string - nodeGetter NodeGetter - nodeName types.NodeName - expectedHost string - expectedPort string - expectError bool + name string + nodeGetter NodeGetter + nodeName types.NodeName + expectedHost string + expectedPort string + expectedNodeFeatures []string + expectError bool }{ { name: "valid node with kubelet endpoint port", @@ -304,10 +306,11 @@ func TestGetConnectionInfo(t *testing.T) { }, }, }, - nodeName: "test-node", - expectedHost: "203.0.113.10", - expectedPort: "10250", - expectError: false, + nodeName: "test-node", + expectedHost: "203.0.113.10", + expectedPort: "10250", + expectedNodeFeatures: nil, + expectError: false, }, { name: "valid node without kubelet endpoint port (uses default)", @@ -324,10 +327,11 @@ func TestGetConnectionInfo(t *testing.T) { }, }, }, - nodeName: "test-node", - expectedHost: "192.168.1.10", - expectedPort: "10250", // default port - expectError: false, + nodeName: "test-node", + expectedHost: "192.168.1.10", + expectedPort: "10250", // default port + expectedNodeFeatures: nil, + expectError: false, }, { name: "node with external IP preferred", @@ -345,10 +349,11 @@ func TestGetConnectionInfo(t *testing.T) { }, }, }, - nodeName: "test-node", - expectedHost: "203.0.113.10", // External IP should be preferred when available - expectedPort: "10250", - expectError: false, + nodeName: "test-node", + expectedHost: "203.0.113.10", // External IP should be preferred when available + expectedPort: "10250", + expectedNodeFeatures: nil, + expectError: false, }, { name: "node not found", @@ -374,6 +379,72 @@ func TestGetConnectionInfo(t *testing.T) { nodeName: "test-node", expectError: true, }, + { + name: "node with one declared feature", + nodeGetter: &mockNodeGetter{ + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "test-node"}, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "192.168.1.10"}, + }, + DaemonEndpoints: v1.NodeDaemonEndpoints{ + KubeletEndpoint: v1.DaemonEndpoint{Port: 10250}, + }, + DeclaredFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, + }, + }, + nodeName: "test-node", + expectedHost: "192.168.1.10", + expectedPort: "10250", + expectedNodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + expectError: false, + }, + { + name: "node with multiple declared features", + nodeGetter: &mockNodeGetter{ + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "test-node"}, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "192.168.1.10"}, + }, + DaemonEndpoints: v1.NodeDaemonEndpoints{ + KubeletEndpoint: v1.DaemonEndpoint{Port: 10250}, + }, + DeclaredFeatures: []string{"ExtendWebSocketsToKubelet", "SomeOtherFeature"}, + }, + }, + }, + nodeName: "test-node", + expectedHost: "192.168.1.10", + expectedPort: "10250", + expectedNodeFeatures: []string{"ExtendWebSocketsToKubelet", "SomeOtherFeature"}, + expectError: false, + }, + { + name: "node with empty declared features", + nodeGetter: &mockNodeGetter{ + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "test-node"}, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "192.168.1.10"}, + }, + DaemonEndpoints: v1.NodeDaemonEndpoints{ + KubeletEndpoint: v1.DaemonEndpoint{Port: 10250}, + }, + DeclaredFeatures: []string{}, + }, + }, + }, + nodeName: "test-node", + expectedHost: "192.168.1.10", + expectedPort: "10250", + expectedNodeFeatures: []string{}, + expectError: false, + }, } for _, tt := range tests { @@ -426,6 +497,10 @@ func TestGetConnectionInfo(t *testing.T) { if connInfo.InsecureSkipTLSVerifyTransport == nil { t.Errorf("expected non-nil InsecureSkipTLSVerifyTransport") } + + if !slices.Equal(connInfo.NodeFeatures, tt.expectedNodeFeatures) { + t.Errorf("expected NodeFeatures %v, got %v", tt.expectedNodeFeatures, connInfo.NodeFeatures) + } }) } } diff --git a/pkg/kubelet/cm/admission/errors.go b/pkg/kubelet/cm/admission/errors.go index 5e549220394c5..1072e7938b7b8 100644 --- a/pkg/kubelet/cm/admission/errors.go +++ b/pkg/kubelet/cm/admission/errors.go @@ -24,7 +24,8 @@ import ( ) const ( - ErrorReasonUnexpected = "UnexpectedAdmissionError" + ErrorReasonUnexpected = "UnexpectedAdmissionError" + ErrorReasonEmptyPodSharedPool = "EmptyPodSharedPoolError" ) type Error interface { @@ -44,6 +45,26 @@ func (e *unexpectedAdmissionError) Type() string { return ErrorReasonUnexpected } +// EmptyPodSharedPoolError represents an error due to a pod spec being invalid +// when the Topology manager's scope is set to pod, because the pod spec +// produces an empty pod shared pool and there are containers that require it. +type EmptyPodSharedPoolError struct { + message string +} + +// NewEmptyPodSharedPoolError returns a new EmptyPodSharedPoolError. +func NewEmptyPodSharedPoolError(err error) *EmptyPodSharedPoolError { + return &EmptyPodSharedPoolError{message: err.Error()} +} + +func (e *EmptyPodSharedPoolError) Error() string { + return e.message +} + +func (e *EmptyPodSharedPoolError) Type() string { + return ErrorReasonEmptyPodSharedPool +} + func GetPodAdmitResult(err error) lifecycle.PodAdmitResult { if err == nil { return lifecycle.PodAdmitResult{Admit: true} diff --git a/pkg/kubelet/cm/cgroup_manager_linux.go b/pkg/kubelet/cm/cgroup_manager_linux.go index 45b63a88d4224..c698d3cb40779 100644 --- a/pkg/kubelet/cm/cgroup_manager_linux.go +++ b/pkg/kubelet/cm/cgroup_manager_linux.go @@ -42,6 +42,8 @@ const ( systemdSuffix string = ".slice" // Cgroup2MemoryMin is memory.min for cgroup v2 Cgroup2MemoryMin string = "memory.min" + // Cgroup2MemoryLow is memory.low for cgroup v2 + Cgroup2MemoryLow string = "memory.low" // Cgroup2MemoryHigh is memory.high for cgroup v2 Cgroup2MemoryHigh string = "memory.high" Cgroup2MaxCpuLimit string = "max" @@ -308,7 +310,7 @@ func (m *cgroupCommon) toResources(logger klog.Logger, resourceConfig *ResourceC resources.CpuPeriod = *resourceConfig.CPUPeriod } if resourceConfig.PidsLimit != nil { - resources.PidsLimit = *resourceConfig.PidsLimit + resources.PidsLimit = resourceConfig.PidsLimit } if !resourceConfig.CPUSet.IsEmpty() { resources.CpusetCpus = resourceConfig.CPUSet.String() diff --git a/pkg/kubelet/cm/cgroup_manager_linux_test.go b/pkg/kubelet/cm/cgroup_manager_linux_test.go index 2388d4da20c09..41f6367e79bb4 100644 --- a/pkg/kubelet/cm/cgroup_manager_linux_test.go +++ b/pkg/kubelet/cm/cgroup_manager_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/cgroup_manager_test.go b/pkg/kubelet/cm/cgroup_manager_test.go index e763bec6de566..6588aaf0b359c 100644 --- a/pkg/kubelet/cm/cgroup_manager_test.go +++ b/pkg/kubelet/cm/cgroup_manager_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/cgroup_manager_unsupported.go b/pkg/kubelet/cm/cgroup_manager_unsupported.go index 5088e7b132e80..dd8cab794229d 100644 --- a/pkg/kubelet/cm/cgroup_manager_unsupported.go +++ b/pkg/kubelet/cm/cgroup_manager_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/container_manager.go b/pkg/kubelet/cm/container_manager.go index b1719fe85a608..b761f6d952067 100644 --- a/pkg/kubelet/cm/container_manager.go +++ b/pkg/kubelet/cm/container_manager.go @@ -60,7 +60,7 @@ const ( type ActivePodsFunc func() []*v1.Pod -type GetNodeFunc func() (*v1.Node, error) +type GetNodeFunc func(context.Context) (*v1.Node, error) // Manages the containers running on a machine. type ContainerManager interface { @@ -193,6 +193,7 @@ type NodeConfig struct { CPUManagerReconcilePeriod time.Duration MemoryManagerPolicy string MemoryManagerReservedMemory []kubeletconfig.MemoryReservation + MemoryReservationPolicy kubeletconfig.MemoryReservationPolicy PodPidsLimit int64 EnforceCPULimits bool CPUCFSQuotaPeriod time.Duration @@ -224,25 +225,25 @@ func int64Slice(in []int) []int64 { return out } -func podHasExclusiveCPUs(cr cpuAllocationReader, pod *v1.Pod) bool { +func podHasExclusiveCPUs(logger klog.Logger, cr cpuAllocationReader, pod *v1.Pod) bool { for _, container := range pod.Spec.InitContainers { - if containerHasExclusiveCPUs(cr, pod, &container) { + if containerHasExclusiveCPUs(logger, cr, pod, &container) { return true } } for _, container := range pod.Spec.Containers { - if containerHasExclusiveCPUs(cr, pod, &container) { + if containerHasExclusiveCPUs(logger, cr, pod, &container) { return true } } - klog.V(4).InfoS("Pod contains no container with pinned cpus", "podName", pod.Name) + logger.V(4).Info("Pod contains no container with pinned cpus", "podName", pod.Name) return false } -func containerHasExclusiveCPUs(cr cpuAllocationReader, pod *v1.Pod, container *v1.Container) bool { +func containerHasExclusiveCPUs(logger klog.Logger, cr cpuAllocationReader, pod *v1.Pod, container *v1.Container) bool { exclusiveCPUs := cr.GetExclusiveCPUs(string(pod.UID), container.Name) if !exclusiveCPUs.IsEmpty() { - klog.V(4).InfoS("Container has pinned cpus", "podName", pod.Name, "containerName", container.Name) + logger.V(4).Info("Container has pinned cpus", "podName", pod.Name, "containerName", container.Name) return true } return false diff --git a/pkg/kubelet/cm/container_manager_linux.go b/pkg/kubelet/cm/container_manager_linux.go index 0b19cd7a7df99..161a973fe4d8d 100644 --- a/pkg/kubelet/cm/container_manager_linux.go +++ b/pkg/kubelet/cm/container_manager_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -45,6 +44,7 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/record" utilsysctl "k8s.io/component-helpers/node/util/sysctl" + resourcehelper "k8s.io/component-helpers/resource" internalapi "k8s.io/cri-api/pkg/apis" pluginwatcherapi "k8s.io/kubelet/pkg/apis/pluginregistration/v1" podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1" @@ -55,6 +55,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/cm/dra" "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager" memorymanagerstate "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager/state" + cmqos "k8s.io/kubernetes/pkg/kubelet/cm/qos" "k8s.io/kubernetes/pkg/kubelet/cm/resourceupdates" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" cmutil "k8s.io/kubernetes/pkg/kubelet/cm/util" @@ -149,7 +150,7 @@ var _ ContainerManager = &containerManagerImpl{} // checks if the required cgroups subsystems are mounted. // As of now, only 'cpu' and 'memory' are required. // cpu quota is a soft requirement. -func validateSystemRequirements(mountUtil mount.Interface) (features, error) { +func validateSystemRequirements(logger klog.Logger, mountUtil mount.Interface) (features, error) { const ( cgroupMountType = "cgroup" localErr = "system validation failed" @@ -190,11 +191,11 @@ func validateSystemRequirements(mountUtil mount.Interface) (features, error) { // CPU cgroup is required and so it expected to be mounted at this point. periodExists, err := utilpath.Exists(utilpath.CheckFollowSymlink, path.Join(cpuMountPoint, "cpu.cfs_period_us")) if err != nil { - klog.ErrorS(err, "Failed to detect if CPU cgroup cpu.cfs_period_us is available") + logger.Error(err, "Failed to detect if CPU cgroup cpu.cfs_period_us is available") } quotaExists, err := utilpath.Exists(utilpath.CheckFollowSymlink, path.Join(cpuMountPoint, "cpu.cfs_quota_us")) if err != nil { - klog.ErrorS(err, "Failed to detect if CPU cgroup cpu.cfs_quota_us is available") + logger.Error(err, "Failed to detect if CPU cgroup cpu.cfs_quota_us is available") } if quotaExists && periodExists { f.cpuHardcapping = true @@ -408,8 +409,9 @@ func (cm *containerManagerImpl) NewPodContainerManager() PodContainerManager { enforceCPULimits: cm.EnforceCPULimits, // cpuCFSQuotaPeriod is in microseconds. NodeConfig.CPUCFSQuotaPeriod is time.Duration (measured in nano seconds). // Convert (cm.CPUCFSQuotaPeriod) [nanoseconds] / time.Microsecond (1000) to get cpuCFSQuotaPeriod in microseconds. - cpuCFSQuotaPeriod: uint64(cm.CPUCFSQuotaPeriod / time.Microsecond), - podContainerManager: cm, + cpuCFSQuotaPeriod: uint64(cm.CPUCFSQuotaPeriod / time.Microsecond), + podContainerManager: cm, + memoryReservationPolicy: cm.MemoryReservationPolicy, } } return &podContainerManagerNoop{ @@ -418,11 +420,39 @@ func (cm *containerManagerImpl) NewPodContainerManager() PodContainerManager { } func (cm *containerManagerImpl) PodHasExclusiveCPUs(pod *v1.Pod) bool { - return podHasExclusiveCPUs(cm.cpuManager, pod) + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { + if cm.cpuManager.GetResourceIsolationLevel(pod, &container) != cmqos.ResourceIsolationContainer { + return false + } + } + + logger.V(4).Info("Pod has pinned cpus", "podName", pod.Name) + return true + } + + return podHasExclusiveCPUs(logger, cm.cpuManager, pod) } func (cm *containerManagerImpl) ContainerHasExclusiveCPUs(pod *v1.Pod, container *v1.Container) bool { - return containerHasExclusiveCPUs(cm.cpuManager, pod, container) + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodLevelResourceManagers) { + if cm.cpuManager.GetResourceIsolationLevel(pod, container) != cmqos.ResourceIsolationContainer { + return false + } + + logger.V(4).Info("Container has pinned cpus", "podName", pod.Name, "containerName", container.Name) + return true + } + + return containerHasExclusiveCPUs(logger, cm.cpuManager, pod, container) } func (cm *containerManagerImpl) InternalContainerLifecycle() InternalContainerLifecycle { @@ -453,7 +483,7 @@ const ( // setupKernelTunables validates kernel tunable flags are set as expected // depending upon the specified option, it will either warn, error, or modify the kernel tunable flags -func setupKernelTunables(option KernelTunableBehavior) error { +func setupKernelTunables(logger klog.Logger, option KernelTunableBehavior) error { desiredState := map[string]int{ utilsysctl.VMOvercommitMemory: utilsysctl.VMOvercommitMemoryAlways, utilsysctl.VMPanicOnOOM: utilsysctl.VMPanicOnOOMInvokeOOMKiller, @@ -480,17 +510,17 @@ func setupKernelTunables(option KernelTunableBehavior) error { case KernelTunableError: errList = append(errList, fmt.Errorf("invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val)) case KernelTunableWarn: - klog.V(2).InfoS("Invalid kernel flag", "flag", flag, "expectedValue", expectedValue, "actualValue", val) + logger.V(2).Info("Invalid kernel flag", "flag", flag, "expectedValue", expectedValue, "actualValue", val) case KernelTunableModify: - klog.V(2).InfoS("Updating kernel flag", "flag", flag, "expectedValue", expectedValue, "actualValue", val) + logger.V(2).Info("Updating kernel flag", "flag", flag, "expectedValue", expectedValue, "actualValue", val) err = sysctl.SetSysctl(flag, expectedValue) if err != nil { if inuserns.RunningInUserNS() { if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.KubeletInUserNamespace) { - klog.V(2).InfoS("Updating kernel flag failed (running in UserNS, ignoring)", "flag", flag, "err", err) + logger.V(2).Info("Updating kernel flag failed (running in UserNS, ignoring)", "flag", flag, "err", err) continue } - klog.ErrorS(err, "Updating kernel flag failed (Hint: enable KubeletInUserNamespace feature flag to ignore the error)", "flag", flag) + logger.Error(err, "Updating kernel flag failed (Hint: enable KubeletInUserNamespace feature flag to ignore the error)", "flag", flag) } errList = append(errList, err) } @@ -502,7 +532,7 @@ func setupKernelTunables(option KernelTunableBehavior) error { func (cm *containerManagerImpl) setupNode(ctx context.Context, activePods ActivePodsFunc) error { logger := klog.FromContext(ctx) - f, err := validateSystemRequirements(cm.mountUtil) + f, err := validateSystemRequirements(logger, cm.mountUtil) if err != nil { return err } @@ -513,7 +543,7 @@ func (cm *containerManagerImpl) setupNode(ctx context.Context, activePods Active if cm.GetNodeConfig().ProtectKernelDefaults { b = KernelTunableError } - if err := setupKernelTunables(b); err != nil { + if err := setupKernelTunables(logger, b); err != nil { return err } @@ -544,7 +574,7 @@ func (cm *containerManagerImpl) setupNode(ctx context.Context, activePods Active return err } cont.ensureStateFunc = func(manager cgroups.Manager) error { - return ensureSystemCgroups("/", manager) + return ensureSystemCgroups(logger, "/", manager) } systemContainers = append(systemContainers, cont) } @@ -650,9 +680,11 @@ func (cm *containerManagerImpl) Start(ctx context.Context, node *v1.Node, if err != nil { return fmt.Errorf("failed to get rootfs info: %v", err) } + cm.Lock() for rName, rCap := range cadvisor.EphemeralStorageCapacityFromFsInfo(rootfs) { cm.capacity[rName] = rCap } + cm.Unlock() } // Ensure that node allocatable configuration is valid. @@ -772,23 +804,23 @@ func (cm *containerManagerImpl) SystemCgroupsLimit() v1.ResourceList { } } -func isProcessRunningInHost(pid int) (bool, error) { +func isProcessRunningInHost(logger klog.Logger, pid int) (bool, error) { // Get init pid namespace. initPidNs, err := os.Readlink("/proc/1/ns/pid") if err != nil { return false, fmt.Errorf("failed to find pid namespace of init process") } - klog.V(10).InfoS("Found init PID namespace", "namespace", initPidNs) + logger.V(10).Info("Found init PID namespace", "namespace", initPidNs) processPidNs, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/pid", pid)) if err != nil { - return false, fmt.Errorf("failed to find pid namespace of process %q", pid) + return false, fmt.Errorf("failed to find pid namespace of process %d", pid) } - klog.V(10).InfoS("Process info", "pid", pid, "namespace", processPidNs) + logger.V(10).Info("Process info", "pid", pid, "namespace", processPidNs) return initPidNs == processPidNs, nil } func ensureProcessInContainerWithOOMScore(logger klog.Logger, pid int, oomScoreAdj int, manager cgroups.Manager) error { - if runningInHost, err := isProcessRunningInHost(pid); err != nil { + if runningInHost, err := isProcessRunningInHost(logger, pid); err != nil { // Err on the side of caution. Avoid moving the docker daemon unless we are able to identify its context. return err } else if !runningInHost { @@ -888,7 +920,7 @@ func getContainer(logger klog.Logger, pid int) (string, error) { // // The reason of leaving kernel threads at root cgroup is that we don't want to tie the // execution of these threads with to-be defined /system quota and create priority inversions. -func ensureSystemCgroups(rootCgroupPath string, manager cgroups.Manager) error { +func ensureSystemCgroups(logger klog.Logger, rootCgroupPath string, manager cgroups.Manager) error { // Move non-kernel PIDs to the system container. // Only keep errors on latest attempt. var finalErr error @@ -914,7 +946,7 @@ func ensureSystemCgroups(rootCgroupPath string, manager cgroups.Manager) error { return nil } - klog.V(3).InfoS("Moving non-kernel processes", "pids", pids) + logger.V(3).Info("Moving non-kernel processes", "pids", pids) for _, pid := range pids { err := manager.Apply(pid) if err != nil { @@ -943,6 +975,11 @@ func isKernelPid(pid int) bool { // GetCapacity returns node capacity data for "cpu", "memory", "ephemeral-storage", and "huge-pages*" // At present this method is only invoked when introspecting ephemeral storage func (cm *containerManagerImpl) GetCapacity(localStorageCapacityIsolation bool) v1.ResourceList { + cm.RLock() + defer cm.RUnlock() + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() if localStorageCapacityIsolation { // We store allocatable ephemeral-storage in the capacity property once we Start() the container manager if _, ok := cm.capacity[v1.ResourceEphemeralStorage]; !ok { @@ -950,7 +987,7 @@ func (cm *containerManagerImpl) GetCapacity(localStorageCapacityIsolation bool) if cm.cadvisorInterface != nil { rootfs, err := cm.cadvisorInterface.RootFsInfo() if err != nil { - klog.ErrorS(err, "Unable to get rootfs data from cAdvisor interface") + logger.Error(err, "Unable to get rootfs data from cAdvisor interface") // If the rootfsinfo retrieval from cAdvisor fails for any reason, fallback to returning the capacity property with no ephemeral storage data return cm.capacity } @@ -1015,6 +1052,9 @@ func (cm *containerManagerImpl) GetAllocatableMemory() []*podresourcesapi.Contai } func (cm *containerManagerImpl) GetDynamicResources(pod *v1.Pod, container *v1.Container) []*podresourcesapi.DynamicResource { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.DynamicResourceAllocation) { return []*podresourcesapi.DynamicResource{} } @@ -1022,7 +1062,7 @@ func (cm *containerManagerImpl) GetDynamicResources(pod *v1.Pod, container *v1.C var containerDynamicResources []*podresourcesapi.DynamicResource containerClaimInfos, err := cm.draManager.GetContainerClaimInfos(pod, container) if err != nil { - klog.ErrorS(err, "Unable to get container claim info state") + logger.Error(err, "Unable to get container claim info state") return []*podresourcesapi.DynamicResource{} } for _, containerClaimInfo := range containerClaimInfos { diff --git a/pkg/kubelet/cm/container_manager_linux_test.go b/pkg/kubelet/cm/container_manager_linux_test.go index 7e38e2e476ebe..8d3d7db4aa339 100644 --- a/pkg/kubelet/cm/container_manager_linux_test.go +++ b/pkg/kubelet/cm/container_manager_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -27,18 +26,51 @@ import ( "time" cadvisorapiv2 "github.com/google/cadvisor/info/v2" - "github.com/opencontainers/cgroups" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2/ktesting" + kubefeatures "k8s.io/kubernetes/pkg/features" cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" - + "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" + cmqos "k8s.io/kubernetes/pkg/kubelet/cm/qos" "k8s.io/mount-utils" + "k8s.io/utils/cpuset" ) +type mockCPUAllocationReader struct { + cpumanager.Manager + sets map[string]cpuset.CPUSet + isolationLevels map[string]cmqos.ResourceIsolationLevel +} + +func (m *mockCPUAllocationReader) GetExclusiveCPUs(podUID, containerName string) cpuset.CPUSet { + key := podUID + "/" + containerName + if cset, ok := m.sets[key]; ok { + return cset + } + return cpuset.New() +} + +func (m *mockCPUAllocationReader) GetResourceIsolationLevel(pod *v1.Pod, container *v1.Container) cmqos.ResourceIsolationLevel { + key := string(pod.UID) + "/" + container.Name + if level, ok := m.isolationLevels[key]; ok { + return level + } + // Fallback to behavior based on sets existence if isolation level not explicitly set, + // to maintain compatibility with other tests or provide a reasonable default. + // For TestContainerHasExclusiveCPUs, we set isolationLevels explicitly. + if _, ok := m.sets[key]; ok { + return cmqos.ResourceIsolationContainer + } + return cmqos.ResourceIsolationHost +} + func fakeContainerMgrMountInt() mount.Interface { return mount.NewFakeMounter( []mount.MountPoint{ @@ -66,7 +98,8 @@ func fakeContainerMgrMountInt() mount.Interface { } func TestCgroupMountValidationSuccess(t *testing.T) { - f, err := validateSystemRequirements(fakeContainerMgrMountInt()) + logger, _ := ktesting.NewTestContext(t) + f, err := validateSystemRequirements(logger, fakeContainerMgrMountInt()) assert.NoError(t, err) if cgroups.IsCgroup2UnifiedMode() { assert.True(t, f.cpuHardcapping, "cpu hardcapping is expected to be enabled") @@ -79,6 +112,7 @@ func TestCgroupMountValidationMemoryMissing(t *testing.T) { if cgroups.IsCgroup2UnifiedMode() { t.Skip("skipping cgroup v1 test on a cgroup v2 system") } + logger, _ := ktesting.NewTestContext(t) mountInt := mount.NewFakeMounter( []mount.MountPoint{ { @@ -97,7 +131,7 @@ func TestCgroupMountValidationMemoryMissing(t *testing.T) { Opts: []string{"rw", "relatime", "cpuacct"}, }, }) - _, err := validateSystemRequirements(mountInt) + _, err := validateSystemRequirements(logger, mountInt) assert.Error(t, err) } @@ -105,6 +139,7 @@ func TestCgroupMountValidationMultipleSubsystem(t *testing.T) { if cgroups.IsCgroup2UnifiedMode() { t.Skip("skipping cgroup v1 test on a cgroup v2 system") } + logger, _ := ktesting.NewTestContext(t) mountInt := mount.NewFakeMounter( []mount.MountPoint{ { @@ -123,7 +158,7 @@ func TestCgroupMountValidationMultipleSubsystem(t *testing.T) { Opts: []string{"rw", "relatime", "cpuacct"}, }, }) - _, err := validateSystemRequirements(mountInt) + _, err := validateSystemRequirements(logger, mountInt) assert.NoError(t, err) } @@ -144,6 +179,7 @@ func TestSoftRequirementsValidationSuccess(t *testing.T) { if cgroups.IsCgroup2UnifiedMode() { t.Skip("skipping cgroup v1 test on a cgroup v2 system") } + logger, _ := ktesting.NewTestContext(t) req := require.New(t) tempDir, err := os.MkdirTemp("", "") req.NoError(err) @@ -169,7 +205,7 @@ func TestSoftRequirementsValidationSuccess(t *testing.T) { Opts: []string{"rw", "relatime", "cpuacct", "memory"}, }, }) - f, err := validateSystemRequirements(mountInt) + f, err := validateSystemRequirements(logger, mountInt) assert.NoError(t, err) assert.True(t, f.cpuHardcapping, "cpu hardcapping is expected to be enabled") } @@ -322,7 +358,6 @@ func TestNewPodContainerManager(t *testing.T) { } for _, c := range cases { - c := c t.Run(c.name, func(t *testing.T) { t.Parallel() pcm := c.cm.NewPodContainerManager() @@ -343,3 +378,347 @@ func TestNewPodContainerManager(t *testing.T) { }) } } + +func TestContainerHasExclusiveCPUs(t *testing.T) { + guaranteedQOSResources := v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + } + burstableQOSResources := v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + } + guaranteedQOSResourcesNonIntegerCPUNotExclusive := v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("500m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("500m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + } + + testCases := []struct { + name string + pod *v1.Pod + containerName string + cpuSets map[string]cpuset.CPUSet + isolationLevels map[string]cmqos.ResourceIsolationLevel + expectExclusiveCPUs bool + podLevelResourceManagersEnabled bool + }{ + { + name: "No pod-level resources, Guaranteed container, has exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "c1", Resources: guaranteedQOSResources}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{"pod1/c1": cpuset.New(1)}, + expectExclusiveCPUs: true, + podLevelResourceManagersEnabled: true, + }, + { + name: "Pod-level resources, Guaranteed container, Integer CPUs, exclusive CPUs assigned", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1", Resources: guaranteedQOSResources}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{"pod1/c1": cpuset.New(1)}, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{"pod1/c1": cmqos.ResourceIsolationContainer}, + expectExclusiveCPUs: true, + podLevelResourceManagersEnabled: true, + }, + { + name: "Pod-level resources, Guaranteed container, Integer CPUs, no exclusive CPUs assigned", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1", Resources: guaranteedQOSResources}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: true, + }, + { + name: "Pod-level resources, Guaranteed container, non-integer CPU", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1", Resources: guaranteedQOSResourcesNonIntegerCPUNotExclusive}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{"pod1/c1": cpuset.New(1)}, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{"pod1/c1": cmqos.ResourceIsolationPod}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: true, + }, + { + name: "Pod-level resources, Burstable container, has exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1", Resources: burstableQOSResources}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{"pod1/c1": cpuset.New(1)}, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{"pod1/c1": cmqos.ResourceIsolationPod}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: true, + }, + { + name: "Pod-level resources, BestEffort container, has exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1"}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{"pod1/c1": cpuset.New(1)}, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{"pod1/c1": cmqos.ResourceIsolationPod}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: true, + }, + { + name: "Pod-level resources, Guaranteed container, Integer CPUs, no exclusive CPUs assigned", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1", Resources: guaranteedQOSResources}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: false, + }, + { + name: "Pod-level resources, BestEffort container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &guaranteedQOSResources, + Containers: []v1.Container{{Name: "c1"}}, + }, + }, + containerName: "c1", + cpuSets: map[string]cpuset.CPUSet{}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.PodLevelResources, tc.podLevelResourceManagersEnabled) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + + mockReader := &mockCPUAllocationReader{ + sets: tc.cpuSets, + isolationLevels: tc.isolationLevels, + } + cm := &containerManagerImpl{ + cpuManager: mockReader, + } + + var targetContainer *v1.Container + for i := range tc.pod.Spec.Containers { + if tc.pod.Spec.Containers[i].Name == tc.containerName { + targetContainer = &tc.pod.Spec.Containers[i] + break + } + } + require.NotNil(t, targetContainer, "container %s not found in pod spec", tc.containerName) + + result := cm.ContainerHasExclusiveCPUs(tc.pod, targetContainer) + assert.Equal(t, tc.expectExclusiveCPUs, result) + }) + } +} + +func TestPodHasExclusiveCPUs(t *testing.T) { + testCases := []struct { + name string + pod *v1.Pod + cpuSets map[string]cpuset.CPUSet + isolationLevels map[string]cmqos.ResourceIsolationLevel + expectExclusiveCPUs bool + podLevelResourceManagersEnabled bool + }{ + { + name: "No exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "c1"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{}, + expectExclusiveCPUs: false, + }, + { + name: "One container with exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "c1"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{ + "pod1/c1": cpuset.New(1), + }, + expectExclusiveCPUs: true, + }, + { + name: "Init container with exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + {Name: "init1"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{ + "pod1/init1": cpuset.New(1), + }, + expectExclusiveCPUs: true, + }, + { + name: "PodLevelResourceManagers enabled, One container with exclusive CPUs", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + }, + Containers: []v1.Container{ + {Name: "c1"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{ + "pod1/c1": cpuset.New(1), + }, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{"pod1/c1": cmqos.ResourceIsolationContainer}, + expectExclusiveCPUs: true, + podLevelResourceManagersEnabled: true, + }, + { + name: "PodLevelResourceManagers enabled, One container in pod shared pool (ResourceIsolationPod)", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + }, + Containers: []v1.Container{ + {Name: "c1"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{ + "pod1/c1": cpuset.New(1), + }, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{"pod1/c1": cmqos.ResourceIsolationPod}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: true, + }, + { + name: "PodLevelResourceManagers enabled, Mixed containers (one exclusive, one shared)", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + }, + Containers: []v1.Container{ + {Name: "c1"}, + {Name: "c2"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{ + "pod1/c1": cpuset.New(1), + "pod1/c2": cpuset.New(2), + }, + isolationLevels: map[string]cmqos.ResourceIsolationLevel{ + "pod1/c1": cmqos.ResourceIsolationContainer, + "pod1/c2": cmqos.ResourceIsolationPod, + }, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: true, + }, + { + name: "PodLevelResourceManagers disabled, Pod Level Resources Only (No Exclusive CPUs)", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + }, + Containers: []v1.Container{ + {Name: "c1"}, + }, + }, + }, + cpuSets: map[string]cpuset.CPUSet{}, + expectExclusiveCPUs: false, + podLevelResourceManagersEnabled: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.PodLevelResources, tc.podLevelResourceManagersEnabled) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + + mockReader := &mockCPUAllocationReader{ + sets: tc.cpuSets, + isolationLevels: tc.isolationLevels, + } + cm := &containerManagerImpl{ + cpuManager: mockReader, + } + + result := cm.PodHasExclusiveCPUs(tc.pod) + assert.Equal(t, tc.expectExclusiveCPUs, result) + }) + } +} diff --git a/pkg/kubelet/cm/container_manager_test.go b/pkg/kubelet/cm/container_manager_test.go new file mode 100644 index 0000000000000..0f673dedc49d4 --- /dev/null +++ b/pkg/kubelet/cm/container_manager_test.go @@ -0,0 +1,92 @@ +//go:build linux + +/* +Copyright 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 cm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParsePercentage(t *testing.T) { + testCases := []struct { + name string + input string + expectErr bool + expected int64 + }{ + { + name: "valid zero percentage", + input: "0%", + expected: 0, + expectErr: false, + }, + { + name: "valid mid percentage", + input: "50%", + expected: 50, + expectErr: false, + }, + { + name: "valid max percentage", + input: "100%", + expected: 100, + expectErr: false, + }, + { + name: "missing percentage sign", + input: "50", + expectErr: true, + }, + { + name: "negative percentage", + input: "-1%", + expectErr: true, + }, + { + name: "percentage greater than 100", + input: "101%", + expectErr: true, + }, + { + name: "non-numeric percentage", + input: "abc%", + expectErr: true, + }, + { + name: "empty string", + input: "", + expectErr: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result, err := parsePercentage(testCase.input) + + if testCase.expectErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, testCase.expected, result) + }) + } +} diff --git a/pkg/kubelet/cm/container_manager_unsupported.go b/pkg/kubelet/cm/container_manager_unsupported.go index a92f0ce89f0ac..8a5ff199be782 100644 --- a/pkg/kubelet/cm/container_manager_unsupported.go +++ b/pkg/kubelet/cm/container_manager_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/container_manager_windows.go b/pkg/kubelet/cm/container_manager_windows.go index 1f967620cdff8..9a7405d3e4d93 100644 --- a/pkg/kubelet/cm/container_manager_windows.go +++ b/pkg/kubelet/cm/container_manager_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2015 The Kubernetes Authors. @@ -376,9 +375,15 @@ func (cm *containerManagerImpl) PodMightNeedToUnprepareResources(UID types.UID) } func (cm *containerManagerImpl) PodHasExclusiveCPUs(pod *v1.Pod) bool { - return podHasExclusiveCPUs(cm.cpuManager, pod) + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + return podHasExclusiveCPUs(logger, cm.cpuManager, pod) } func (cm *containerManagerImpl) ContainerHasExclusiveCPUs(pod *v1.Pod, container *v1.Container) bool { - return containerHasExclusiveCPUs(cm.cpuManager, pod, container) + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() + return containerHasExclusiveCPUs(logger, cm.cpuManager, pod, container) } diff --git a/pkg/kubelet/cm/containermap/container_map_test.go b/pkg/kubelet/cm/containermap/container_map_test.go index 0e6d1211c8c11..94b011ea31450 100644 --- a/pkg/kubelet/cm/containermap/container_map_test.go +++ b/pkg/kubelet/cm/containermap/container_map_test.go @@ -52,7 +52,7 @@ func TestContainerMapCloneUnshared(t *testing.T) { // check the original copy didn't change // early sanity check, random ID, no special meaning podUIDRedo, containerNameRedo, err2 := cm.GetContainerRef("fakeContainerID-C") - if err != nil { + if err2 != nil { t.Fatalf("unexpected error: %v", err2) } if podUIDRedo != "fakePodUID-2" || containerNameRedo != "fakeContainerName-c2" { diff --git a/pkg/kubelet/cm/cpumanager/cpu_manager.go b/pkg/kubelet/cm/cpumanager/cpu_manager.go index f5a7e249dcfa8..3814300f04403 100644 --- a/pkg/kubelet/cm/cpumanager/cpu_manager.go +++ b/pkg/kubelet/cm/cpumanager/cpu_manager.go @@ -27,12 +27,16 @@ import ( cadvisorapi "github.com/google/cadvisor/info/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" + resourcehelper "k8s.io/component-helpers/resource" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/klog/v2" + kubefeatures "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" + cmqos "k8s.io/kubernetes/pkg/kubelet/cm/qos" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" @@ -91,6 +95,9 @@ type Manager interface { // among this and other resource controllers. GetPodTopologyHints(pod *v1.Pod) map[string][]topologymanager.TopologyHint + // AllocatePod is called to trigger the allocation of CPUs to a pod. + AllocatePod(pod *v1.Pod) error + // GetAllocatableCPUs returns the total set of CPUs available for allocation. GetAllocatableCPUs() cpuset.CPUSet @@ -101,6 +108,9 @@ type Manager interface { // GetAllCPUs returns all the CPUs known by cpumanager, as reported by the // hardware discovery. Maps to the CPU capacity. GetAllCPUs() cpuset.CPUSet + + // GetResourceIsolationLevel returns the isolation level of the container. + GetResourceIsolationLevel(pod *v1.Pod, container *v1.Container) cmqos.ResourceIsolationLevel } type manager struct { @@ -276,6 +286,23 @@ func (m *manager) Allocate(p *v1.Pod, c *v1.Container) error { return nil } +func (m *manager) AllocatePod(pod *v1.Pod) error { + logger := klog.TODO() // until we move topology manager to contextual logging + + // Garbage collect any stranded resources before allocating CPUs. + m.removeStaleState(logger) + + m.Lock() + defer m.Unlock() + + // Call down into the policy to assign this container CPUs if required. + if err := m.policy.AllocatePod(logger, m.state, pod); err != nil { + logger.Error(err, "AllocatePod error", "pod", klog.KObj(pod)) + return err + } + return nil +} + func (m *manager) AddContainer(logger logr.Logger, pod *v1.Pod, container *v1.Container, containerID string) { m.Lock() defer m.Unlock() @@ -542,10 +569,30 @@ func (m *manager) GetExclusiveCPUs(podUID, containerName string) cpuset.CPUSet { if result, ok := m.state.GetCPUSet(podUID, containerName); ok { return result } - - return cpuset.CPUSet{} + return cpuset.New() } func (m *manager) GetCPUAffinity(podUID, containerName string) cpuset.CPUSet { return m.state.GetCPUSetOrDefault(podUID, containerName) } + +func resourcesQualifyForExclusiveCPUs(container *v1.Container) bool { + if !cmqos.IsContainerEquivalentQOSGuaranteed(container) { + return false + } + + cpuLimit := container.Resources.Limits[v1.ResourceCPU] + return cpuLimit.Value()*1000 == cpuLimit.MilliValue() +} + +func (m *manager) GetResourceIsolationLevel(pod *v1.Pod, container *v1.Container) cmqos.ResourceIsolationLevel { + if _, ok := m.state.GetCPUSet(string(pod.UID), container.Name); !ok { + return cmqos.ResourceIsolationHost + } + + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) && !resourcesQualifyForExclusiveCPUs(container) { + return cmqos.ResourceIsolationPod + } + + return cmqos.ResourceIsolationContainer +} diff --git a/pkg/kubelet/cm/cpumanager/cpu_manager_others.go b/pkg/kubelet/cm/cpumanager/cpu_manager_others.go index 556583b1a7cc9..753cd683593a5 100644 --- a/pkg/kubelet/cm/cpumanager/cpu_manager_others.go +++ b/pkg/kubelet/cm/cpumanager/cpu_manager_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/cpumanager/cpu_manager_test.go b/pkg/kubelet/cm/cpumanager/cpu_manager_test.go index 2361595a4bf7e..e14258581865f 100644 --- a/pkg/kubelet/cm/cpumanager/cpu_manager_test.go +++ b/pkg/kubelet/cm/cpumanager/cpu_manager_test.go @@ -33,6 +33,7 @@ import ( "github.com/go-logr/logr" cadvisorapi "github.com/google/cadvisor/info/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,8 +49,9 @@ import ( ) type mockState struct { - assignments state.ContainerCPUAssignments - defaultCPUSet cpuset.CPUSet + assignments state.ContainerCPUAssignments + podAssignments state.PodCPUAssignments + defaultCPUSet cpuset.CPUSet } func (s *mockState) GetCPUSet(podUID string, containerName string) (cpuset.CPUSet, bool) { @@ -68,6 +70,11 @@ func (s *mockState) GetCPUSetOrDefault(podUID string, containerName string) cpus return s.GetDefaultCPUSet() } +func (s *mockState) GetPodCPUSet(podUID string) (cpuset.CPUSet, bool) { + res, ok := s.podAssignments[podUID] + return res.CPUSet.Clone(), ok +} + func (s *mockState) SetCPUSet(podUID string, containerName string, cset cpuset.CPUSet) { if _, exists := s.assignments[podUID]; !exists { s.assignments[podUID] = make(map[string]cpuset.CPUSet) @@ -75,6 +82,16 @@ func (s *mockState) SetCPUSet(podUID string, containerName string, cset cpuset.C s.assignments[podUID][containerName] = cset } +func (s *mockState) SetPodCPUSet(podUID string, cset cpuset.CPUSet) { + if s.podAssignments == nil { + s.podAssignments = make(state.PodCPUAssignments) + } + + podAssigments := s.podAssignments[podUID] + podAssigments.CPUSet = cset + s.podAssignments[podUID] = podAssigments +} + func (s *mockState) SetDefaultCPUSet(cset cpuset.CPUSet) { s.defaultCPUSet = cset } @@ -87,18 +104,31 @@ func (s *mockState) Delete(podUID string, containerName string) { } func (s *mockState) ClearState() { - s.defaultCPUSet = cpuset.CPUSet{} + s.defaultCPUSet = cpuset.New() s.assignments = make(state.ContainerCPUAssignments) + s.podAssignments = make(state.PodCPUAssignments) +} + +func (s *mockState) DeletePod(podUID string) { + delete(s.podAssignments, podUID) } func (s *mockState) SetCPUAssignments(a state.ContainerCPUAssignments) { s.assignments = a.Clone() } +func (s *mockState) SetPodCPUAssignments(a state.PodCPUAssignments) { + s.podAssignments = a.Clone() +} + func (s *mockState) GetCPUAssignments() state.ContainerCPUAssignments { return s.assignments.Clone() } +func (s *mockState) GetPodCPUAssignments() state.PodCPUAssignments { + return s.podAssignments.Clone() +} + type mockPolicy struct { err error } @@ -127,6 +157,10 @@ func (p *mockPolicy) GetPodTopologyHints(_ logr.Logger, s state.State, pod *v1.P return nil } +func (p *mockPolicy) AllocatePod(_ logr.Logger, s state.State, pod *v1.Pod) error { + return p.err +} + func (p *mockPolicy) GetAllocatableCPUs(m state.State) cpuset.CPUSet { return cpuset.New() } @@ -139,6 +173,10 @@ func (rt mockRuntimeService) UpdateContainerResources(_ context.Context, id stri return rt.err } +func (rt mockRuntimeService) Close(_ context.Context) error { + return rt.err +} + type mockPodStatusProvider struct { podStatus v1.PodStatus found bool @@ -249,37 +287,62 @@ func makeMultiContainerPodWithOptions(initCPUs, appCPUs []*containerOptions) *v1 }, } - for i, cpu := range initCPUs { - pod.Spec.InitContainers = append(pod.Spec.InitContainers, v1.Container{ + for i, opts := range initCPUs { + container := v1.Container{ Name: "initContainer-" + strconv.Itoa(i), - Resources: v1.ResourceRequirements{ + } + if opts.request != "" { + container.Resources = v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu.request), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), + v1.ResourceCPU: resource.MustParse(opts.request), + v1.ResourceMemory: resource.MustParse("1G"), }, Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu.limit), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), + v1.ResourceCPU: resource.MustParse(opts.limit), + v1.ResourceMemory: resource.MustParse("1G"), }, - }, - RestartPolicy: &cpu.restartPolicy, - }) + } + } + if opts.restartPolicy != "" { + container.RestartPolicy = &opts.restartPolicy + } + pod.Spec.InitContainers = append(pod.Spec.InitContainers, container) } - for i, cpu := range appCPUs { - pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{ + for i, opts := range appCPUs { + container := v1.Container{ Name: "appContainer-" + strconv.Itoa(i), - Resources: v1.ResourceRequirements{ + } + if opts.request != "" { + container.Resources = v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu.request), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), + v1.ResourceCPU: resource.MustParse(opts.request), + v1.ResourceMemory: resource.MustParse("1G"), }, Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu.limit), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), + v1.ResourceCPU: resource.MustParse(opts.limit), + v1.ResourceMemory: resource.MustParse("1G"), }, - }, - }) + } + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + + return pod +} + +func makeMultiContainerPodWithOptionsAndPodLevelResources(podLevelCPUs string, initCPUs, appCPUs []*containerOptions) *v1.Pod { + pod := makeMultiContainerPodWithOptions(initCPUs, appCPUs) + + pod.Spec.Resources = &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse(podLevelCPUs), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse(podLevelCPUs), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), + }, } return pod @@ -738,7 +801,7 @@ func TestCPUManagerGenerate(t *testing.T) { t.Errorf("Unexpected policy name. Have: %q wants %q", rawMgr.policy.Name(), testCase.expectedPolicy) } if rawMgr.topology == nil { - t.Errorf("Expected topology to be non-nil for policy '%v'. Have: %q", rawMgr.policy.Name(), rawMgr.topology) + t.Errorf("Expected topology to be non-nil for policy '%v'. Have: %v", rawMgr.policy.Name(), rawMgr.topology) } } }) diff --git a/pkg/kubelet/cm/cpumanager/cpu_manager_windows.go b/pkg/kubelet/cm/cpumanager/cpu_manager_windows.go index f9a239ce550e0..7b73ff30476f7 100644 --- a/pkg/kubelet/cm/cpumanager/cpu_manager_windows.go +++ b/pkg/kubelet/cm/cpumanager/cpu_manager_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/cpumanager/fake_cpu_manager.go b/pkg/kubelet/cm/cpumanager/fake_cpu_manager.go index 38ce4e655e725..a15dfce0651aa 100644 --- a/pkg/kubelet/cm/cpumanager/fake_cpu_manager.go +++ b/pkg/kubelet/cm/cpumanager/fake_cpu_manager.go @@ -24,6 +24,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" + cmqos "k8s.io/kubernetes/pkg/kubelet/cm/qos" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/status" @@ -74,28 +75,38 @@ func (m *fakeManager) GetPodTopologyHints(pod *v1.Pod) map[string][]topologymana return map[string][]topologymanager.TopologyHint{} } +func (m *fakeManager) AllocatePod(pod *v1.Pod) error { + logger := klog.TODO() + logger.Info("AllocatePod", "pod", klog.KObj(pod)) + return nil +} + func (m *fakeManager) State() state.Reader { return m.state } func (m *fakeManager) GetExclusiveCPUs(podUID, containerName string) cpuset.CPUSet { m.logger.Info("GetExclusiveCPUs", "podUID", podUID, "containerName", containerName) - return cpuset.CPUSet{} + return cpuset.New() } func (m *fakeManager) GetAllocatableCPUs() cpuset.CPUSet { m.logger.Info("Get Allocatable CPUs") - return cpuset.CPUSet{} + return cpuset.New() } func (m *fakeManager) GetCPUAffinity(podUID, containerName string) cpuset.CPUSet { m.logger.Info("GetCPUAffinity", "podUID", podUID, "containerName", containerName) - return cpuset.CPUSet{} + return cpuset.New() } func (m *fakeManager) GetAllCPUs() cpuset.CPUSet { m.logger.Info("GetAllCPUs") - return cpuset.CPUSet{} + return cpuset.New() +} + +func (m *fakeManager) GetResourceIsolationLevel(pod *v1.Pod, container *v1.Container) cmqos.ResourceIsolationLevel { + return cmqos.ResourceIsolationContainer } // NewFakeManager creates empty/fake cpu manager diff --git a/pkg/kubelet/cm/cpumanager/policy.go b/pkg/kubelet/cm/cpumanager/policy.go index 628b0254af16a..26afcb3945a6b 100644 --- a/pkg/kubelet/cm/cpumanager/policy.go +++ b/pkg/kubelet/cm/cpumanager/policy.go @@ -41,6 +41,8 @@ type Policy interface { // and is consulted to achieve NUMA aware resource alignment per Pod // among this and other resource controllers. GetPodTopologyHints(logger logr.Logger, s state.State, pod *v1.Pod) map[string][]topologymanager.TopologyHint + // AllocatePod is called to trigger the allocation of CPUs to a pod. + AllocatePod(logger logr.Logger, s state.State, pod *v1.Pod) error // GetAllocatableCPUs returns the total set of CPUs available for allocation. GetAllocatableCPUs(m state.State) cpuset.CPUSet } diff --git a/pkg/kubelet/cm/cpumanager/policy_none.go b/pkg/kubelet/cm/cpumanager/policy_none.go index e29d0598a36f0..8a43eb707af3f 100644 --- a/pkg/kubelet/cm/cpumanager/policy_none.go +++ b/pkg/kubelet/cm/cpumanager/policy_none.go @@ -67,6 +67,10 @@ func (p *nonePolicy) GetPodTopologyHints(_ logr.Logger, s state.State, pod *v1.P return nil } +func (p *nonePolicy) AllocatePod(_ logr.Logger, s state.State, pod *v1.Pod) error { + return nil +} + // Assignable CPUs are the ones that can be exclusively allocated to pods that meet the exclusivity requirement // (ie guaranteed QoS class and integral CPU request). // Assignability of CPUs as a concept is only applicable in case of static policy i.e. scenarios where workloads diff --git a/pkg/kubelet/cm/cpumanager/policy_options.go b/pkg/kubelet/cm/cpumanager/policy_options.go index 745c6bf7ca5e9..19da027a8b94b 100644 --- a/pkg/kubelet/cm/cpumanager/policy_options.go +++ b/pkg/kubelet/cm/cpumanager/policy_options.go @@ -44,11 +44,11 @@ var ( ) betaOptions = sets.New[string]( DistributeCPUsAcrossNUMAOption, - PreferAlignByUnCoreCacheOption, ) stableOptions = sets.New[string]( FullPCPUsOnlyOption, StrictCPUReservationOption, + PreferAlignByUnCoreCacheOption, ) ) diff --git a/pkg/kubelet/cm/cpumanager/policy_options_test.go b/pkg/kubelet/cm/cpumanager/policy_options_test.go index 5af8060cc23c5..3fc6793763f7e 100644 --- a/pkg/kubelet/cm/cpumanager/policy_options_test.go +++ b/pkg/kubelet/cm/cpumanager/policy_options_test.go @@ -106,18 +106,6 @@ func TestPolicyOptionsAvailable(t *testing.T) { featureGateEnable: true, expectedAvailable: false, }, - { - option: PreferAlignByUnCoreCacheOption, - featureGate: pkgfeatures.CPUManagerPolicyBetaOptions, - featureGateEnable: false, - expectedAvailable: false, - }, - { - option: PreferAlignByUnCoreCacheOption, - featureGate: pkgfeatures.CPUManagerPolicyBetaOptions, - featureGateEnable: true, - expectedAvailable: true, - }, } for _, testCase := range testCases { t.Run(testCase.option, func(t *testing.T) { @@ -135,6 +123,7 @@ func TestPolicyOptionsAlwaysAvailableOnceGA(t *testing.T) { options := []string{ FullPCPUsOnlyOption, StrictCPUReservationOption, + PreferAlignByUnCoreCacheOption, } for _, option := range options { t.Run(option, func(t *testing.T) { diff --git a/pkg/kubelet/cm/cpumanager/policy_static.go b/pkg/kubelet/cm/cpumanager/policy_static.go index 635570dbf492d..2b9daf460bc91 100644 --- a/pkg/kubelet/cm/cpumanager/policy_static.go +++ b/pkg/kubelet/cm/cpumanager/policy_static.go @@ -19,18 +19,22 @@ package cpumanager import ( "fmt" "strconv" + "strings" "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" utilfeature "k8s.io/apiserver/pkg/util/feature" resourcehelper "k8s.io/component-helpers/resource" "k8s.io/klog/v2" podutil "k8s.io/kubernetes/pkg/api/v1/pod" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" + cmqos "k8s.io/kubernetes/pkg/kubelet/cm/qos" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" "k8s.io/kubernetes/pkg/kubelet/managed" @@ -39,7 +43,6 @@ import ( ) const ( - // PolicyStatic is the name of the static policy. // Should options be given, these will be ignored and backward (up to 1.21 included) // compatible behaviour will be enforced @@ -269,6 +272,11 @@ func (p *staticPolicy) validateState(logger logr.Logger, s state.State) error { tmpCPUSets = append(tmpCPUSets, cset) } } + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + for _, podAssigments := range s.GetPodCPUAssignments() { + tmpCPUSets = append(tmpCPUSets, podAssigments.CPUSet) + } + } totalKnownCPUs = totalKnownCPUs.Union(tmpCPUSets...) availableCPUs := p.topology.CPUDetails.CPUs() @@ -330,6 +338,239 @@ func (p *staticPolicy) updateCPUsToReuse(pod *v1.Pod, container *v1.Container, c p.cpusToReuse[string(pod.UID)] = p.cpusToReuse[string(pod.UID)].Difference(cset) } +// validatePodScopeResources checks for the "empty shared pool" scenario. This occurs +// when the sum of exclusive container CPU requests consumes the entire pod-level +// budget, leaving no CPUs for containers that require a shared pool. Such a +// configuration is invalid because it would lead to containers in the shared +// pool having an empty cpuset, causing them to run on the node's shared pool +// and breaking NUMA affinity. +func (p *staticPolicy) validatePodScopeResources(logger logr.Logger, pod *v1.Pod) error { + podTotalCPUs := p.podGuaranteedCPUs(logger, pod) + + hasSharedLongRunningContainers := false + // Sum CPU requests for all containers that run for the full pod lifetime + // (main containers and restartable init containers) to determine the + // total exclusive CPU usage that might impact the shared pool. + sumOfLongRunningExclusiveCPUs := 0 + + // Check for empty shared pool for standard init containers. + for _, container := range pod.Spec.InitContainers { + guaranteedCPUs := p.guaranteedCPUs(logger, pod, &container) + + if !podutil.IsRestartableInitContainer(&container) { + if guaranteedCPUs == 0 && sumOfLongRunningExclusiveCPUs >= podTotalCPUs { + // This check ensures that if there are any standard init containers that need a + // shared pool, that pool is not empty. An empty pool would occur if the pod + // budget is fully consumed by sidecars. + return fmt.Errorf("pod rejected, pod has shared init containers but no cpus available for them") + } + } else { + // If there are restartable init containers, keep track for the empty shared pool check. + if guaranteedCPUs == 0 { + hasSharedLongRunningContainers = true + } else { + sumOfLongRunningExclusiveCPUs += guaranteedCPUs + } + } + } + + // Check for empty shared pool for concurrently running containers (app and restartable init containers). + for _, container := range pod.Spec.Containers { + guaranteedCPUs := p.guaranteedCPUs(logger, pod, &container) + + if guaranteedCPUs == 0 { + hasSharedLongRunningContainers = true + } else { + sumOfLongRunningExclusiveCPUs += guaranteedCPUs + } + } + + // This check ensures that if there are any standard containers that need a shared + // pool, that pool is not empty. An empty pool would occur if the pod budget is + // fully consumed by sidecars and main containers with exclusive resources. + if hasSharedLongRunningContainers && sumOfLongRunningExclusiveCPUs >= podTotalCPUs { + return fmt.Errorf("pod rejected, sum of exclusive container cpu requests equals pod budget, leaving no cpus for shared containers") + } + + return nil +} + +// This function is the entry point for pod-level resource allocation. +// It's called once per pod by the Topology Manager's pod-scope admit handler. +// The logic here allocates a single "bubble" of CPUs for the entire pod +// and then partitions that bubble among the containers. +func (p *staticPolicy) AllocatePod(logger logr.Logger, s state.State, pod *v1.Pod) (rerr error) { + podUID := string(pod.UID) + logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod)) + logger.V(4).Info("AllocatePod called for pod-level managed pod") + + // 1. Calculate the total number of CPUs required for the pod, considering init container reuse. + totalPodCPUs := p.podGuaranteedCPUs(logger, pod) + if totalPodCPUs == 0 { + // pod belongs in the shared pool (nothing to do; use default cpuset) + return nil + } + logger.V(4).Info("Calculated total pod CPUs", "totalCPUs", totalPodCPUs) + + defer func() { + if rerr != nil { + metrics.CPUManagerPinningErrorsTotal.Inc() + metrics.ResourceManagerAllocationErrorsTotal.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerPod).Inc() + } + }() + + // 2. Validate for the "empty shared pool" case. + // Even though this is checked during hint generation, we must re-evaluate it here. + // If the Topology Manager is running with the "best-effort" or "none" policies, + // it will still admit the pod even if GetPodTopologyHints returns an empty hint list. + // This ensures we explicitly reject the pod during allocation to prevent Kubelet + // from attempting to create invalid cgroups (e.g., an empty cpuset.cpus) for shared containers. + if err := p.validatePodScopeResources(logger, pod); err != nil { + return admission.NewEmptyPodSharedPoolError(err) + } + + // 3. Enforce SMT alignment policy if FullPhysicalCPUsOnly is enabled. + if err := p.enforceSMTAlignment(s, totalPodCPUs); err != nil { + return err + } + + // 4. Allocate the entire CPU "bubble" for the pod using the hint from the Topology Manager. + hint := p.affinity.GetAffinity(podUID, append(pod.Spec.InitContainers, pod.Spec.Containers...)[0].Name) + podAllocation, err := p.allocateCPUs(logger, s, totalPodCPUs, hint.NUMANodeAffinity, cpuset.New()) + if err != nil { + logger.Error(err, "Unable to allocate CPUs for pod", "totalPodCPUs", totalPodCPUs) + return err + } + p.updateMetricsOnAllocate(logger, s, podAllocation) + logger.V(4).Info("Allocated pod-level CPU bubble", "allocation", podAllocation.CPUs) + + // Store the pod-level allocation in the state. + s.SetPodCPUSet(podUID, podAllocation.CPUs) + + // 5. Partition the pod's allocation, handling init container CPU reuse correctly. + exclusiveCPUs := make(map[string]cpuset.CPUSet) + sidecarCPUs := cpuset.New() + + // First, iterate through all init containers and allocate their CPUs from the initial pod bubble. + for _, c := range pod.Spec.InitContainers { + if numCPUs := p.guaranteedCPUs(logger, pod, &c); numCPUs > 0 { + metrics.CPUManagerPinningRequestsTotal.Inc() + // The pool available for this init container is the entire pod allocation + // minus what's already taken by sidecars. + runnablePool := podAllocation.CPUs.Difference(sidecarCPUs) + cset, err := p.takeByTopology(logger, runnablePool, numCPUs) + if err != nil { + return err + } + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerExclusivePod).Inc() + exclusiveCPUs[c.Name] = cset + + // If it's a restartable sidecar, its CPUs are permanently consumed. + if podutil.IsRestartableInitContainer(&c) { + sidecarCPUs = sidecarCPUs.Union(cset) + } + } else { + // Restartable init containers will access the main pod shared pool. + if podutil.IsRestartableInitContainer(&c) { + continue + } + + // Non restartable init containers will have access to the whole pod pool + // amount minus the sidecar exclusive CPUs up to that point. + runnablePool := podAllocation.CPUs.Difference(sidecarCPUs) + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerSharedPod).Inc() + exclusiveCPUs[c.Name] = runnablePool + } + } + + // Explicitly record that the full CPU bubble is available again, minus what sidecars are using. + // This avoids carrying forward complex accounting from standard init containers. + podSharedPool := podAllocation.CPUs.Difference(sidecarCPUs) + + // Second, iterate through regular containers, allocating from the remaining pool. + for _, c := range pod.Spec.Containers { + if numCPUs := p.guaranteedCPUs(logger, pod, &c); numCPUs > 0 { + metrics.CPUManagerPinningRequestsTotal.Inc() + cset, err := p.takeByTopology(logger, podSharedPool, numCPUs) + if err != nil { + return err + } + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerExclusivePod).Inc() + exclusiveCPUs[c.Name] = cset + // Consume CPUs from the app container pool. + podSharedPool = podSharedPool.Difference(cset) + } + } + + logger.V(4).Info("Partitioned pod-level CPU allocation", "exclusiveCPUs", podCPUAllocationToString(exclusiveCPUs), "podSharedPool", podSharedPool) + + // 6. Save all container assignments to the state. + for _, c := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { + if cset, isExclusive := exclusiveCPUs[c.Name]; isExclusive { + s.SetCPUSet(podUID, c.Name, cset) + } else { + s.SetCPUSet(podUID, c.Name, podSharedPool) + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerSharedPod).Inc() + } + + metrics.ResourceManagerAllocationsTotal.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerPod).Inc() + } + + return nil +} + +func podCPUAllocationToString(alloc map[string]cpuset.CPUSet) string { + if len(alloc) == 0 { + return "" + } + var sb strings.Builder + for name, cset := range alloc { + fmt.Fprintf(&sb, ",%s={%s}", name, cset) + } + // if we gone so far, it means we have at least 1 alloc, so the cut is safe + repr := sb.String()[1:] + return "<" + repr + ">" +} + +func (p *staticPolicy) enforceSMTAlignment(s state.State, numCPUs int) error { + if !p.options.FullPhysicalCPUsOnly { + return nil + } + + if (numCPUs % p.cpuGroupSize) != 0 { + // Since CPU Manager has been enabled requesting strict SMT alignment, it means a guaranteed pod can only be admitted + // if the CPU requested is a multiple of the number of virtual cpus per physical cores. + // In case CPU request is not a multiple of the number of virtual cpus per physical cores the Pod will be put + // in Failed state, with SMTAlignmentError as reason. Since the allocation happens in terms of physical cores + // and the scheduler is responsible for ensuring that the workload goes to a node that has enough CPUs, + // the pod would be placed on a node where there are enough physical cores available to be allocated. + // Just like the behaviour in case of static policy, takeByTopology will try to first allocate CPUs from the same socket + // and only in case the request cannot be sattisfied on a single socket, CPU allocation is done for a workload to occupy all + // CPUs on a physical core. Allocation of individual threads would never have to occur. + return SMTAlignmentError{ + RequestedCPUs: numCPUs, + CpusPerCore: p.cpuGroupSize, + CausedByPhysicalCPUs: false, + } + } + + availablePhysicalCPUs := p.GetAvailablePhysicalCPUs(s).Size() + + // It's legal to reserve CPUs which are not core siblings. In this case the CPU allocator can descend to single cores + // when picking CPUs. This will void the guarantee of FullPhysicalCPUsOnly. To prevent this, we need to additionally consider + // all the core siblings of the reserved CPUs as unavailable when computing the free CPUs, before to start the actual allocation. + // This way, by construction all possible CPUs allocation whose number is multiple of the SMT level are now correct again. + if numCPUs > availablePhysicalCPUs { + return SMTAlignmentError{ + RequestedCPUs: numCPUs, + CpusPerCore: p.cpuGroupSize, + AvailablePhysicalCPUs: availablePhysicalCPUs, + CausedByPhysicalCPUs: true, + } + } + return nil +} + func (p *staticPolicy) Allocate(logger logr.Logger, s state.State, pod *v1.Pod, container *v1.Container) (rerr error) { logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", container.Name) logger.Info("Allocate start") // V=0 for backward compatibility @@ -341,18 +582,17 @@ func (p *staticPolicy) Allocate(logger logr.Logger, s state.State, pod *v1.Pod, return nil } - if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) { - logger.Info("CPU Manager allocation skipped, pod is using pod-level resources which are not supported by the static CPU manager policy") + if (!utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) || !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)) && resourcehelper.IsPodLevelResourcesSet(pod) { + logger.V(2).Info("CPU Manager allocation skipped, pod is using pod-level resources but the PodLevelResourceManagers feature gate is not enabled", "pod", klog.KObj(pod), "podUID", pod.UID) return nil } - logger.Info("Static policy: Allocate") - // container belongs in an exclusively allocated pool metrics.CPUManagerPinningRequestsTotal.Inc() defer func() { if rerr != nil { metrics.CPUManagerPinningErrorsTotal.Inc() + metrics.ResourceManagerAllocationErrorsTotal.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerNode).Inc() if p.options.FullPhysicalCPUsOnly { metrics.ContainerAlignedComputeResourcesFailure.WithLabelValues(metrics.AlignScopeContainer, metrics.AlignedPhysicalCPU).Inc() } @@ -365,39 +605,10 @@ func (p *staticPolicy) Allocate(logger logr.Logger, s state.State, pod *v1.Pod, } }() - if p.options.FullPhysicalCPUsOnly { - if (numCPUs % p.cpuGroupSize) != 0 { - // Since CPU Manager has been enabled requesting strict SMT alignment, it means a guaranteed pod can only be admitted - // if the CPU requested is a multiple of the number of virtual cpus per physical cores. - // In case CPU request is not a multiple of the number of virtual cpus per physical cores the Pod will be put - // in Failed state, with SMTAlignmentError as reason. Since the allocation happens in terms of physical cores - // and the scheduler is responsible for ensuring that the workload goes to a node that has enough CPUs, - // the pod would be placed on a node where there are enough physical cores available to be allocated. - // Just like the behaviour in case of static policy, takeByTopology will try to first allocate CPUs from the same socket - // and only in case the request cannot be sattisfied on a single socket, CPU allocation is done for a workload to occupy all - // CPUs on a physical core. Allocation of individual threads would never have to occur. - return SMTAlignmentError{ - RequestedCPUs: numCPUs, - CpusPerCore: p.cpuGroupSize, - CausedByPhysicalCPUs: false, - } - } - - availablePhysicalCPUs := p.GetAvailablePhysicalCPUs(s).Size() - - // It's legal to reserve CPUs which are not core siblings. In this case the CPU allocator can descend to single cores - // when picking CPUs. This will void the guarantee of FullPhysicalCPUsOnly. To prevent this, we need to additionally consider - // all the core siblings of the reserved CPUs as unavailable when computing the free CPUs, before to start the actual allocation. - // This way, by construction all possible CPUs allocation whose number is multiple of the SMT level are now correct again. - if numCPUs > availablePhysicalCPUs { - return SMTAlignmentError{ - RequestedCPUs: numCPUs, - CpusPerCore: p.cpuGroupSize, - AvailablePhysicalCPUs: availablePhysicalCPUs, - CausedByPhysicalCPUs: true, - } - } + if err := p.enforceSMTAlignment(s, numCPUs); err != nil { + return err } + if cset, ok := s.GetCPUSet(string(pod.UID), container.Name); ok { p.updateCPUsToReuse(pod, container, cset) logger.Info("Static policy: container already present in state, skipping") @@ -418,6 +629,8 @@ func (p *staticPolicy) Allocate(logger logr.Logger, s state.State, pod *v1.Pod, s.SetCPUSet(string(pod.UID), container.Name, cpuAllocation.CPUs) p.updateCPUsToReuse(pod, container, cpuAllocation.CPUs) p.updateMetricsOnAllocate(logger, s, cpuAllocation) + metrics.ResourceManagerAllocationsTotal.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerNode).Inc() + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerExclusiveNode).Inc() logger.V(4).Info("Allocated exclusive CPUs", "cpuset", cpuAllocation.CPUs.String()) return nil @@ -440,12 +653,30 @@ func (p *staticPolicy) RemoveContainer(logger logr.Logger, s state.State, podUID logger = klog.LoggerWithValues(logger, "podUID", podUID, "containerName", containerName) logger.Info("RemoveContainer start") // backward compatibility defer logger.V(4).Info("RemoveContainer start") - cpusInUse := getAssignedCPUsOfSiblings(s, podUID, containerName) toRelease, ok := s.GetCPUSet(podUID, containerName) if !ok { return nil } s.Delete(podUID, containerName) + + // Check if this pod is managed with pod-level CPUs. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + if podCPUSet, isPodLevel := s.GetPodCPUSet(podUID); isPodLevel { + // If this was the last container for the pod, then release the entire pod-level CPU set. + if len(s.GetCPUAssignments()[podUID]) == 0 { + updatedCPUSets := s.GetDefaultCPUSet().Union(podCPUSet) + s.SetDefaultCPUSet(updatedCPUSets) + s.DeletePod(podUID) // Clean up all state for the pod. + p.updateMetricsOnRelease(logger, s, podCPUSet) + logger.Info("Released pod-level CPUs", "defaultCPUSet", updatedCPUSets) + } + // If other containers still exist, do not release any CPUs yet. + // The pod-level CPUs will be released when the last container is removed. + return nil + } + } + + cpusInUse := getAssignedCPUsOfSiblings(s, podUID, containerName) // Mutate the shared pool, adding released cpus. toRelease = toRelease.Difference(cpusInUse) updatedCPUs := s.GetDefaultCPUSet().Union(toRelease) @@ -493,25 +724,57 @@ func (p *staticPolicy) allocateCPUs(logger logr.Logger, s state.State, numCPUs i return result, nil } +func isIntegralCPUAmount(cpuQuantity resource.Quantity) bool { + return cpuQuantity.Value()*1000 == cpuQuantity.MilliValue() +} + func (p *staticPolicy) guaranteedCPUs(logger logr.Logger, pod *v1.Pod, container *v1.Container) int { qos := v1qos.GetPodQOS(pod) if qos != v1.PodQOSGuaranteed { logger.V(5).Info("Exclusive CPU allocation skipped, pod QoS is not guaranteed", "qos", qos) return 0 } + cpuQuantity := container.Resources.Requests[v1.ResourceCPU] + + // For pod-level resource management, a container is only considered for exclusive + // CPUs if its request equals its limit for both the CPU and Memory. This + // aligns with the Guaranteed QoS requirement for container-level resources. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) && !cmqos.IsContainerEquivalentQOSGuaranteed(container) { + logger.V(5).Info("Exclusive CPU allocation skipped, container is not eligible, request and limit are not equal", "pod", klog.KObj(pod), "containerName", container.Name) + return 0 + } + cpuValue := cpuQuantity.Value() - if cpuValue*1000 != cpuQuantity.MilliValue() { - logger.V(5).Info("Exclusive CPU allocation skipped, pod requested non-integral CPUs", "cpu", cpuValue) + if !isIntegralCPUAmount(cpuQuantity) { + logger.V(5).Info("Exclusive CPU allocation skipped, container requested non-integral CPUs", "pod", klog.KObj(pod), "containerName", container.Name, "cpu", cpuValue) return 0 } // Safe downcast to do for all systems with < 2.1 billion CPUs. // Per the language spec, `int` is guaranteed to be at least 32 bits wide. // https://golang.org/ref/spec#Numeric_types - return int(cpuQuantity.Value()) + return int(cpuValue) } func (p *staticPolicy) podGuaranteedCPUs(logger logr.Logger, pod *v1.Pod) int { + // If pod-level resources are set, use them directly. + // This check is important because this function is called from GetPodTopologyHints, + // which runs before the main feature gate check in AllocatePod. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + qos := v1qos.GetPodQOS(pod) + if qos != v1.PodQOSGuaranteed { + return 0 + } + + if cpuQuantity, ok := pod.Spec.Resources.Requests[v1.ResourceCPU]; ok { + // We only consider integer CPU requests for guaranteed CPUs at the pod level. + if isIntegralCPUAmount(cpuQuantity) { + return int(cpuQuantity.Value()) + } + } + return 0 + } + // The maximum of requested CPUs by init containers. requestedByInitContainers := 0 requestedByRestartableInitContainers := 0 @@ -576,8 +839,10 @@ func (p *staticPolicy) GetTopologyHints(logger logr.Logger, s state.State, pod * return nil } - if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) { - logger.V(3).Info("CPU Manager hint generation skipped, pod is using pod-level resources which are not supported by the static CPU manager policy", "pod", klog.KObj(pod), "podUID", pod.UID) + // If the pod has pod-level resources but the feature gate is disabled, + // log it and return nil hints to admit the pod without alignment. + if (!utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) || !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)) && resourcehelper.IsPodLevelResourcesSet(pod) { + logger.V(3).Info("CPU Manager hint generation skipped, pod is using pod-level resources but the PodLevelResourceManagers feature gate is not enabled") return nil } @@ -596,7 +861,7 @@ func (p *staticPolicy) GetTopologyHints(logger logr.Logger, s state.State, pod * } logger.Info("Regenerating TopologyHints for CPUs already allocated") return map[string][]topologymanager.TopologyHint{ - string(v1.ResourceCPU): p.generateCPUTopologyHints(allocated, cpuset.CPUSet{}, requested), + string(v1.ResourceCPU): p.generateCPUTopologyHints(allocated, cpuset.New(), requested), } } @@ -630,11 +895,27 @@ func (p *staticPolicy) GetPodTopologyHints(logger logr.Logger, s state.State, po return nil } - if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) { - logger.V(3).Info("CPU Manager pod hint generation skipped, pod is using pod-level resources which are not supported by the static CPU manager policy") + // If the pod has pod-level resources but the feature gate is disabled, + // log it and return nil hints to admit the pod without alignment. + if (!utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) || !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)) && resourcehelper.IsPodLevelResourcesSet(pod) { + logger.V(3).Info("CPU Manager pod hint generation skipped, pod is using pod-level resources but the PodLevelResourceManagers feature gate is not enabled", "podUID", pod.UID) return nil } + // Validate that if a pod has containers that will be placed in a shared pool, + // there are actually CPUs left over for that pool after accounting for all + // exclusive allocations. If the sum of exclusive CPU requests consumes the + // entire pod-level CPU budget, no hints will be generated, causing the pod + // to be rejected by the Topology Manager. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + if err := p.validatePodScopeResources(logger, pod); err != nil { + logger.V(2).Info("Invalid pod spec. Sum of exclusive container requests equals pod budget, leaving no CPUs for shared containers") + return map[string][]topologymanager.TopologyHint{ + string(v1.ResourceCPU): {}, + } + } + } + assignedCPUs := cpuset.New() for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { logger_ := klog.LoggerWithValues(logger, "containerName", container.Name) @@ -660,7 +941,7 @@ func (p *staticPolicy) GetPodTopologyHints(logger logr.Logger, s state.State, po if assignedCPUs.Size() == requested { logger.Info("Regenerating TopologyHints for CPUs already allocated") return map[string][]topologymanager.TopologyHint{ - string(v1.ResourceCPU): p.generateCPUTopologyHints(assignedCPUs, cpuset.CPUSet{}, requested), + string(v1.ResourceCPU): p.generateCPUTopologyHints(assignedCPUs, cpuset.New(), requested), } } diff --git a/pkg/kubelet/cm/cpumanager/policy_static_restore_test.go b/pkg/kubelet/cm/cpumanager/policy_static_restore_test.go new file mode 100644 index 0000000000000..4759a6b6fbbb1 --- /dev/null +++ b/pkg/kubelet/cm/cpumanager/policy_static_restore_test.go @@ -0,0 +1,249 @@ +/* +Copyright 2025 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 cpumanager + +import ( + "context" + "runtime" + "testing" + "time" + + cadvisorapi "github.com/google/cadvisor/info/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + resourcehelper "k8s.io/component-helpers/resource" + pkgfeatures "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/cm/containermap" + "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" + "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/utils/cpuset" +) + +// For the scope of the test, any pod that has pod-level resources and the +// PodLevelResourceManagers feature is enabled, will be processed by AllocatePod +func TestCPUManagerRestoreState(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("CPU Manager static policy is not available on Windows") + } + + testCases := []struct { + description string + podLevelResourcesEnabled bool + podLevelResourceManagersEnabled bool + podCPURequest string + containers []*containerOptions + expectPodCPUSet bool + }{ + { + description: "Pod-level resources and managers enabled", + podCPURequest: "2", + containers: []*containerOptions{ + {name: "container1", request: "1", limit: "1"}, + }, + expectPodCPUSet: true, + podLevelResourceManagersEnabled: true, + podLevelResourcesEnabled: true, + }, + { + description: "Pod-level resources enabled, managers disabled", + podCPURequest: "2", + containers: []*containerOptions{ + {name: "container1", request: "1", limit: "1"}, + }, + expectPodCPUSet: false, + podLevelResourceManagersEnabled: false, + podLevelResourcesEnabled: true, + }, + { + description: "Container-level pod, features enabled", + podCPURequest: "", + containers: []*containerOptions{ + {name: "container1", request: "1", limit: "1"}, + {name: "container2", request: "1", limit: "1"}, + }, + expectPodCPUSet: false, + podLevelResourceManagersEnabled: true, + podLevelResourcesEnabled: true, + }, + { + description: "Container-level pod, features disabled", + podCPURequest: "", + containers: []*containerOptions{ + {name: "container1", request: "1", limit: "1"}, + {name: "container2", request: "1", limit: "1"}, + }, + expectPodCPUSet: false, + podLevelResourceManagersEnabled: false, + podLevelResourcesEnabled: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.PodLevelResources, tc.podLevelResourcesEnabled) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + + sDir := t.TempDir() + logger, _ := ktesting.NewTestContext(t) + mgr, err := NewManager( + logger, + "static", + nil, + 5*time.Second, + &cadvisorapi.MachineInfo{ + NumCores: 4, + Topology: []cadvisorapi.Node{ + { + Cores: []cadvisorapi.Core{ + {Id: 0, Threads: []int{0}}, + {Id: 1, Threads: []int{1}}, + {Id: 2, Threads: []int{2}}, + {Id: 3, Threads: []int{3}}, + }, + }, + }, + }, + cpuset.New(), + v1.ResourceList{v1.ResourceCPU: *resource.NewQuantity(1, resource.DecimalSI)}, + sDir, + topologymanager.NewFakeManager(), + ) + if err != nil { + t.Fatalf("could not create manager: %v", err) + } + + // Create a pod + var pod *v1.Pod + if tc.podCPURequest != "" { + pod = makeMultiContainerPodWithOptionsAndPodLevelResources(tc.podCPURequest, nil, tc.containers) + } else { + pod = makeMultiContainerPodWithOptions(nil, tc.containers) + } + pod.Name = "pod1" + pod.UID = types.UID("pod1") + + // Start manager to initialize state and activePods + err = mgr.Start(context.Background(), func() []*v1.Pod { return []*v1.Pod{pod} }, &sourcesReadyStub{}, mockPodStatusProvider{}, mockRuntimeService{}, containermap.NewContainerMap()) + if err != nil { + t.Fatalf("could not start manager: %v", err) + } + + // Allocate resources (Pod Scope) + if tc.podLevelResourceManagersEnabled && resourcehelper.IsPodLevelResourcesSet(pod) { + err = mgr.AllocatePod(pod) + if err != nil { + t.Fatalf("could not allocate pod: %v", err) + } + } else { + // Allocate resources (Container Scope / Legacy) + for i := range pod.Spec.Containers { + container := &pod.Spec.Containers[i] + err = mgr.Allocate(pod, container) + if err != nil { + t.Fatalf("could not allocate container %s: %v", container.Name, err) + } + } + } + + // Add containers (simulate running) + for i := range pod.Spec.Containers { + container := &pod.Spec.Containers[i] + mgr.AddContainer(logger, pod, container, container.Name) + } + + // Verify state before restart + podCPUSet, _ := mgr.State().GetPodCPUSet(string(pod.UID)) + if tc.expectPodCPUSet && podCPUSet.IsEmpty() { + t.Errorf("expected pod cpu set to be present") + } else if !tc.expectPodCPUSet && !podCPUSet.IsEmpty() { + t.Errorf("expected no pod cpu set, but got some") + } + + // Re-create manager to simulate restart + mgr2, err := NewManager( + logger, + "static", + nil, + 5*time.Second, + &cadvisorapi.MachineInfo{ + NumCores: 4, + Topology: []cadvisorapi.Node{ + { + Cores: []cadvisorapi.Core{ + {Id: 0, Threads: []int{0}}, + {Id: 1, Threads: []int{1}}, + {Id: 2, Threads: []int{2}}, + {Id: 3, Threads: []int{3}}, + }, + }, + }, + }, + cpuset.New(), + v1.ResourceList{v1.ResourceCPU: *resource.NewQuantity(1, resource.DecimalSI)}, + sDir, + topologymanager.NewFakeManager(), + ) + if err != nil { + t.Fatalf("could not create manager 2: %v", err) + } + + err = mgr2.Start(context.Background(), func() []*v1.Pod { return []*v1.Pod{pod} }, &sourcesReadyStub{}, mockPodStatusProvider{}, mockRuntimeService{}, containermap.NewContainerMap()) + if err != nil { + t.Fatalf("could not start manager 2: %v", err) + } + + // Verify state restored + podCPUSetRestored, _ := mgr2.State().GetPodCPUSet(string(pod.UID)) + if tc.expectPodCPUSet { + if podCPUSetRestored.IsEmpty() { + t.Errorf("expected pod cpu set to be present after restore") + } + if podCPUSetRestored.Size() != podCPUSet.Size() { + t.Errorf("expected pod cpu set size to be %d, got %d", podCPUSet.Size(), podCPUSetRestored.Size()) + } + } else if !podCPUSetRestored.IsEmpty() { + t.Errorf("expected no pod cpu set after restore, but got some") + } + + for i := range pod.Spec.Containers { + container := &pod.Spec.Containers[i] + containerCPUSet, ok := mgr2.State().GetCPUSet(string(pod.UID), container.Name) + // If allocation was skipped (PLR enabled but Manager disabled), no cpuset. + allocationSkipped := tc.podLevelResourcesEnabled && !tc.podLevelResourceManagersEnabled && resourcehelper.IsPodLevelResourcesSet(pod) + + if allocationSkipped { + if ok { + t.Errorf("expected no container cpu set for %s after restore (allocation skipped), but got some", container.Name) + } + } else { + if !ok { + t.Errorf("expected container cpu set to be present for %s after restore", container.Name) + } + // Only check size if we expect assignments (guaranteed) + if p := mgr2.(*manager).policy.(*staticPolicy); p.guaranteedCPUs(logger, pod, container) > 0 { + if containerCPUSet.IsEmpty() { + t.Errorf("expected container cpu set to be non-empty for guaranteed container %s", container.Name) + } + } + } + } + }) + } +} diff --git a/pkg/kubelet/cm/cpumanager/policy_static_test.go b/pkg/kubelet/cm/cpumanager/policy_static_test.go index 234654dead8e2..d9b8933404d6c 100644 --- a/pkg/kubelet/cm/cpumanager/policy_static_test.go +++ b/pkg/kubelet/cm/cpumanager/policy_static_test.go @@ -21,22 +21,41 @@ import ( "reflect" "testing" - "k8s.io/kubernetes/pkg/kubelet/managed" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2" pkgfeatures "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" + "k8s.io/kubernetes/pkg/kubelet/managed" + "k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/cpuset" ) +var ( + containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways +) + +func newNUMAAffinity(bits ...int) bitmask.BitMask { + affinity, err := bitmask.NewBitMask(bits...) + if err != nil { + panic(err) + } + return affinity +} + type staticPolicyTest struct { description string topo *topology.CPUTopology @@ -578,10 +597,6 @@ func TestStaticPolicyAdd(t *testing.T) { expCSet: cpuset.New(1, 2, 3, 4, 5, 7, 8, 9, 10, 11), }, } - newNUMAAffinity := func(bits ...int) bitmask.BitMask { - affinity, _ := bitmask.NewBitMask(bits...) - return affinity - } alignBySocketOptionTestCases := []staticPolicyTest{ { description: "Align by socket: true, cpu's within same socket of numa in hint are part of allocation", @@ -2064,6 +2079,431 @@ func TestStaticPolicyOptions(t *testing.T) { } } +type staticPolicyAllocatePodTest struct { + description string + topo *topology.CPUTopology + numReservedCPUs int + reservedCPUs cpuset.CPUSet + options map[string]string + stAssignments state.ContainerCPUAssignments + stDefaultCPUSet cpuset.CPUSet + pod *v1.Pod + topologyHint topologymanager.TopologyHint + expErr error + expPodAssignments state.ContainerCPUAssignments + expDefaultCPUSet cpuset.CPUSet + podLevelResourcesEnabled bool + podLevelResourceManagersEnabled bool + requiredMetrics requiredMetrics +} + +type requiredMetrics struct { + expTotalAllocs int + expTotalErrors int + expExclusiveAssignments int + expPodSharedPoolAssignments int +} + +type containerSpec struct { + name string + request string + limit string + restartPolicy *v1.ContainerRestartPolicy +} + +func makePodWithContainersAndPodLevelResources(podName, podRequest, podLimit string, initContainers, appContainers []containerSpec) *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + UID: types.UID(podName), + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{}, + Containers: []v1.Container{}, + RestartPolicy: v1.RestartPolicyNever, + }, + } + + for _, c := range initContainers { + container := v1.Container{ + Name: c.name, + Image: "image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + } + if c.request != "" { + container.Resources.Requests[v1.ResourceCPU] = resource.MustParse(c.request) + } + if c.limit != "" { + container.Resources.Limits[v1.ResourceCPU] = resource.MustParse(c.limit) + } + if c.restartPolicy != nil { + container.RestartPolicy = c.restartPolicy + } + pod.Spec.InitContainers = append(pod.Spec.InitContainers, container) + } + + for _, c := range appContainers { + container := v1.Container{ + Name: c.name, + Image: "image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + } + if c.request != "" { + container.Resources.Requests[v1.ResourceCPU] = resource.MustParse(c.request) + } + if c.limit != "" { + container.Resources.Limits[v1.ResourceCPU] = resource.MustParse(c.limit) + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + + if podRequest != "" || podLimit != "" { + pod.Spec.Resources = &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("300Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("300Mi"), + }, + } + if podRequest != "" { + pod.Spec.Resources.Requests[v1.ResourceCPU] = resource.MustParse(podRequest) + } + if podLimit != "" { + pod.Spec.Resources.Limits[v1.ResourceCPU] = resource.MustParse(podLimit) + } + } else { + pod.Spec.Resources = nil + } + return pod +} + +func TestStaticPolicyAllocatePod(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + + testCases := []staticPolicyAllocatePodTest{ + { + description: "should successfully allocate CPUs for a guaranteed pod with pod-level resources", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithPodLevelResources("pod1", "2", "2", "container1", "1", "1"), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "pod1": map[string]cpuset.CPUSet{ + "container1": cpuset.New(2), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 3, 4, 5, 6, 7, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 1, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 0, + }, + }, + { + description: "scope: pod, should allocate exclusive CPUs to a guaranteed pod with pod-level resources and guaranteed container, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithPodLevelResources("gu-pod-level-resources", "2", "2", "gu-container", "1", "1"), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "gu-pod-level-resources": map[string]cpuset.CPUSet{ + "gu-container": cpuset.New(2), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 3, 4, 5, 6, 7, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 1, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 0, + }, + }, + { + description: "scope: pod, should allocate exclusive CPUs to a guaranteed pod with pod-level resources and non-guaranteed container, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("gu-pod-level-resources", "1", "1", []containerSpec{}, []containerSpec{ + {name: "ngu-container"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "gu-pod-level-resources": map[string]cpuset.CPUSet{ + "ngu-container": cpuset.New(6), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 1, + expExclusiveAssignments: 0, + expPodSharedPoolAssignments: 1, + }, + }, + { + description: "scope: pod, should allocate exclusive CPUs to a guaranteed pod with pod-level resources and mix of guaranteed and non-guaranteed containers, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("gu-pod-level-mix-ctn", "3", "3", []containerSpec{}, []containerSpec{ + {name: "gu-container-1", request: "1", limit: "1"}, + {name: "gu-container-2", request: "1", limit: "1"}, + {name: "ngu-container"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "gu-pod-level-mix-ctn": { + "gu-container-1": cpuset.New(6), + "gu-container-2": cpuset.New(2), + "ngu-container": cpuset.New(8), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 3, 4, 5, 7, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 3, + expExclusiveAssignments: 2, + expPodSharedPoolAssignments: 1, + }, + }, + { + description: "scope: pod, should allocate exclusive CPUs to a guaranteed pod with pod-level resources and mix of guaranteed standard and init containers, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("gu-pod-level-gu-init-ctn", "2", "2", []containerSpec{ + {name: "gu-init-container-1", request: "1", limit: "1"}, + {name: "gu-init-container-2", request: "1", limit: "1"}, + }, []containerSpec{ + {name: "gu-container-1", request: "1", limit: "1"}, + {name: "gu-container-2", request: "1", limit: "1"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "gu-pod-level-gu-init-ctn": { + "gu-init-container-1": cpuset.New(2), + "gu-init-container-2": cpuset.New(2), + "gu-container-1": cpuset.New(2), + "gu-container-2": cpuset.New(8), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 3, 4, 5, 6, 7, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 4, + expExclusiveAssignments: 4, + expPodSharedPoolAssignments: 0, + }, + }, + { + description: "scope: pod, should allocate exclusive CPUs to a guaranteed pod with pod-level resources and mix of guaranteed standard and guaranteed restartable and non-guaranteed standard init containers, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("gu-pod-level-gu-init-ctn", "3", "3", []containerSpec{ + {name: "ngu-init-container-1"}, + {name: "gu-init-restartable-2", request: "1", limit: "1", restartPolicy: &containerRestartPolicyAlways}, + {name: "ngu-init-container-3"}, + }, []containerSpec{ + {name: "gu-container-1", request: "1", limit: "1"}, + {name: "ngu-container-2"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "gu-pod-level-gu-init-ctn": { + "ngu-init-container-1": cpuset.New(2, 6, 8), + "gu-init-restartable-2": cpuset.New(6), + "ngu-init-container-3": cpuset.New(2, 8), + "gu-container-1": cpuset.New(2), + "ngu-container-2": cpuset.New(8), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 3, 4, 5, 7, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 5, + expExclusiveAssignments: 2, + expPodSharedPoolAssignments: 3, + }, + }, + { + description: "scope: pod, should allocate exclusive CPUs to a guaranteed pod with pod-level resources and mix of guaranteed standard and non-guaranteed restartable init containers, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("gu-pod-level-gu-init-ctn", "3", "3", []containerSpec{ + {name: "ngu-init-container-1"}, + {name: "ngu-init-restartable-2", restartPolicy: &containerRestartPolicyAlways}, + {name: "ngu-init-container-3"}, + }, []containerSpec{ + {name: "gu-container-1", request: "1", limit: "1"}, + {name: "ngu-container-2"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{ + "gu-pod-level-gu-init-ctn": { + "ngu-init-container-1": cpuset.New(2, 6, 8), + "ngu-init-restartable-2": cpuset.New(2, 8), + "ngu-init-container-3": cpuset.New(2, 6, 8), + "gu-container-1": cpuset.New(6), + "ngu-container-2": cpuset.New(2, 8), + }, + }, + expDefaultCPUSet: cpuset.New(0, 1, 3, 4, 5, 7, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 5, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 4, + }, + }, + { + description: "scope: pod, should reject a pod that would result in an empty pod shared pool", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("gu-pod-level-empty-pod-shared-pool", "2", "2", []containerSpec{}, []containerSpec{ + {name: "gu-container-1", request: "1", limit: "1"}, + {name: "gu-container-2", request: "1", limit: "1"}, + {name: "ngu-container"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: admission.NewEmptyPodSharedPoolError(fmt.Errorf("pod rejected, sum of exclusive container cpu requests equals pod budget, leaving no cpus for shared containers")), + expPodAssignments: state.ContainerCPUAssignments{}, + expDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalErrors: 1, + }, + }, + { + description: "scope: pod, should not allocate exclusive CPUs to a non-guaranteed pod with pod-level resources and guaranteed containers, PodLevelResourceManagers enabled", + topo: topoDualSocketHT, + numReservedCPUs: 1, + reservedCPUs: cpuset.New(0), + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + pod: makePodWithContainersAndPodLevelResources("ngu-pod-level-empty-pod-shared-pool", "2", "1", []containerSpec{}, []containerSpec{ + {name: "gu-container-1", request: "1", limit: "1"}, + }), + topologyHint: topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expErr: nil, + expPodAssignments: state.ContainerCPUAssignments{}, + expDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalErrors: 0, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.PodLevelResources, testCase.podLevelResourcesEnabled) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.PodLevelResourceManagers, testCase.podLevelResourceManagersEnabled) + + metrics.Register() + metrics.ResourceManagerAllocationsTotal.Reset() + metrics.ResourceManagerAllocationErrorsTotal.Reset() + metrics.ResourceManagerContainerAssignments.Reset() + + policy, err := NewStaticPolicy(logger, testCase.topo, testCase.numReservedCPUs, testCase.reservedCPUs, topologymanager.NewFakeManager(), testCase.options) + if err != nil { + t.Fatalf("NewStaticPolicy() failed: %v", err) + } + + st := &mockState{ + assignments: testCase.stAssignments, + defaultCPUSet: testCase.stDefaultCPUSet, + } + + err = policy.AllocatePod(logger, st, testCase.pod) + if testCase.expErr != nil { + require.Error(t, err) + + errors, err := testutil.GetCounterMetricValue(metrics.ResourceManagerAllocationErrorsTotal.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerPod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expTotalErrors), errors, 0.001, "expected allocation errors to be incremented") + + return + } + require.NoError(t, err) + + if !reflect.DeepEqual(st.GetCPUAssignments(), testCase.expPodAssignments) { + t.Errorf("StaticPolicy AllocatePod() error (%v). expected assignments: %v but got: %v", + testCase.description, testCase.expPodAssignments, st.GetCPUAssignments()) + } + + if !st.GetDefaultCPUSet().Equals(testCase.expDefaultCPUSet) { + t.Errorf("StaticPolicy AllocatePod() error (%v). expected default cpuset: %v but got: %v", + testCase.description, testCase.expDefaultCPUSet, st.GetDefaultCPUSet()) + } + + allocations, err := testutil.GetCounterMetricValue(metrics.ResourceManagerAllocationsTotal.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerPod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expTotalAllocs), allocations, 0.001, "unexpected number of allocations") + + exclusiveAssignments, err := testutil.GetCounterMetricValue(metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerExclusivePod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expExclusiveAssignments), exclusiveAssignments, 0.001, "unexpected number of assignments") + + podSharedPoolAssignments, err := testutil.GetCounterMetricValue(metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerCPU, metrics.ResourceManagerSharedPod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expPodSharedPoolAssignments), podSharedPoolAssignments, 0.001, "unexpected number of assignments") + }) + } +} + func TestSMTAlignmentErrorText(t *testing.T) { type smtErrTestCase struct { name string @@ -2102,6 +2542,218 @@ func TestSMTAlignmentErrorText(t *testing.T) { } } +func TestValidatePodScopeResources(t *testing.T) { + testCases := []struct { + name string + pod *v1.Pod + scope string + features map[featuregate.Feature]bool + expectErr bool + topology *topology.CPUTopology + numReserved int + reservedCPUs cpuset.CPUSet + policyOptions map[string]string + }{ + { + name: "Valid: Pod-level != Guaranteed containers, has podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "4", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "2", limit: "2"}, + {name: "c2"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Only Guaranteed containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "4", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "2", limit: "2"}, + {name: "c2", request: "2", limit: "2"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Only podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "4", + []*containerOptions{}, + []*containerOptions{ + {name: "c1"}, + {name: "c2"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Pod-level == Guaranteed containers, has podSharedPool containers with request, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "6", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "3", limit: "3"}, + {name: "c2", request: "2", limit: "2"}, + {name: "c3", request: "500m", limit: "1000m"}, + {name: "c4"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Failure: Pod-level == Guaranteed containers, has podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "5", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "3", limit: "3"}, + {name: "c2", request: "2", limit: "2"}, + {name: "c3"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: true, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Pod-level resources with standard and restartable init containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2", + []*containerOptions{ + {name: "init-container1", request: "1", limit: "1"}, + {name: "restartable-init-container1", request: "1", limit: "1", restartPolicy: v1.ContainerRestartPolicyAlways}, + }, + []*containerOptions{ + {name: "container1"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Pod-level resources equal init and standard container resources, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2", + []*containerOptions{ + {name: "init-container1", request: "2", limit: "2"}, + }, + []*containerOptions{ + {name: "container1", request: "2", limit: "2"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Failure: Pod-level resources with shared standard init container and no available pool, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2", + []*containerOptions{ + {name: "restartable-init-container1", request: "2", limit: "2", restartPolicy: v1.ContainerRestartPolicyAlways}, + {name: "init-container1"}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: true, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Pod-level resources with shared standard init container and no available pool, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2", + []*containerOptions{ + {name: "init-container1"}, + {name: "restartable-init-container1", request: "2", limit: "2", restartPolicy: v1.ContainerRestartPolicyAlways}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + { + name: "Failure: Pod-level == Restartable guaranteed init containers, has podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2", + []*containerOptions{ + {name: "restartable-init-container1", request: "2", limit: "2", restartPolicy: v1.ContainerRestartPolicyAlways}, + {name: "restartable-init-container2", restartPolicy: v1.ContainerRestartPolicyAlways}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: true, + topology: topoSingleSocketHT, + }, + { + name: "Valid: Pod-level resources with only restartable one init container and one standard container, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2", + []*containerOptions{ + {name: "restartable-init-container1", request: "1", limit: "1", restartPolicy: v1.ContainerRestartPolicyAlways}, + }, + []*containerOptions{ + {name: "container1", request: "1", limit: "1"}, + }, + ), + scope: topologymanager.PodTopologyScope, + features: map[featuregate.Feature]bool{pkgfeatures.PodLevelResources: true, pkgfeatures.PodLevelResourceManagers: true}, + expectErr: false, + topology: topoSingleSocketHT, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + + for feature, enabled := range tc.features { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, enabled) + } + + fakeTopologyManager := topologymanager.NewFakeManagerWithScope(tc.scope) + policy, err := NewStaticPolicy(logger, tc.topology, tc.numReserved, tc.reservedCPUs, fakeTopologyManager, tc.policyOptions) + if err != nil { + t.Fatalf("NewStaticPolicy() failed: %v", err) + } + + err = policy.(*staticPolicy).validatePodScopeResources(logger, tc.pod) + + if tc.expectErr && err == nil { + t.Error("Expected an error, but got none") + } + if !tc.expectErr && err != nil { + t.Errorf("Unexpected error: %v", err) + } + }) + } +} + func newCPUSetPtr(cpus ...int) *cpuset.CPUSet { ret := cpuset.New(cpus...) return &ret diff --git a/pkg/kubelet/cm/cpumanager/state/checkpoint.go b/pkg/kubelet/cm/cpumanager/state/checkpoint.go index 564c3482c0441..6903ef3e9d922 100644 --- a/pkg/kubelet/cm/cpumanager/state/checkpoint.go +++ b/pkg/kubelet/cm/cpumanager/state/checkpoint.go @@ -22,18 +22,33 @@ import ( "hash/fnv" "strings" - "k8s.io/apimachinery/pkg/util/dump" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors" + "k8s.io/utils/dump" ) var _ checkpointmanager.Checkpoint = &CPUManagerCheckpointV1{} var _ checkpointmanager.Checkpoint = &CPUManagerCheckpointV2{} +var _ checkpointmanager.Checkpoint = &CPUManagerCheckpointV3{} var _ checkpointmanager.Checkpoint = &CPUManagerCheckpoint{} -// CPUManagerCheckpoint struct is used to store cpu/pod assignments in a checkpoint in v2 format +// CPUManagerCheckpoint struct is used to store cpu/pod assignments in a checkpoint in v3 format type CPUManagerCheckpoint struct { + PolicyName string `json:"policyName"` + DefaultCPUSet string `json:"defaultCpuSet"` + Entries map[string]map[string]string `json:"entries,omitempty"` + PodEntries PodCPUAssignments `json:"podEntries,omitempty"` + Checksum checksum.Checksum `json:"checksum"` +} + +// CPUManagerCheckpoint struct is used to store cpu/pod assignments in a checkpoint in v3 format +type CPUManagerCheckpointV3 = CPUManagerCheckpoint + +// CPUManagerCheckpointV2 struct is used to store cpu/pod assignments in a checkpoint in v2 format +type CPUManagerCheckpointV2 struct { PolicyName string `json:"policyName"` DefaultCPUSet string `json:"defaultCpuSet"` Entries map[string]map[string]string `json:"entries,omitempty"` @@ -48,13 +63,10 @@ type CPUManagerCheckpointV1 struct { Checksum checksum.Checksum `json:"checksum"` } -// CPUManagerCheckpointV2 struct is used to store cpu/pod assignments in a checkpoint in v2 format -type CPUManagerCheckpointV2 = CPUManagerCheckpoint - // NewCPUManagerCheckpoint returns an instance of Checkpoint -func NewCPUManagerCheckpoint() *CPUManagerCheckpoint { +func newCPUManagerCheckpoint() *CPUManagerCheckpoint { //nolint:staticcheck // unexported-type-in-api user-facing error message - return newCPUManagerCheckpointV2() + return newCPUManagerCheckpointV3() } func newCPUManagerCheckpointV1() *CPUManagerCheckpointV1 { @@ -69,6 +81,13 @@ func newCPUManagerCheckpointV2() *CPUManagerCheckpointV2 { } } +func newCPUManagerCheckpointV3() *CPUManagerCheckpoint { + return &CPUManagerCheckpoint{ + Entries: make(map[string]map[string]string), + PodEntries: make(PodCPUAssignments), + } +} + // MarshalCheckpoint returns marshalled checkpoint in v1 format func (cp *CPUManagerCheckpointV1) MarshalCheckpoint() ([]byte, error) { // make sure checksum wasn't set before so it doesn't affect output checksum @@ -81,6 +100,24 @@ func (cp *CPUManagerCheckpointV1) MarshalCheckpoint() ([]byte, error) { func (cp *CPUManagerCheckpointV2) MarshalCheckpoint() ([]byte, error) { // make sure checksum wasn't set before so it doesn't affect output checksum cp.Checksum = 0 + + // In order to preserve rollback compatibility when the feature gate is disabled, + // we must generate a checksum using the legacy struct name "CPUManagerCheckpoint" + // instead of "CPUManagerCheckpointV2". Older Kubelets do not have the string + // replacement logic and expect the original struct name. + object := dump.ForHash(cp) + object = strings.Replace(object, "CPUManagerCheckpointV2", "CPUManagerCheckpoint", 1) + hash := fnv.New32a() + _, _ = fmt.Fprintf(hash, "%v", object) + cp.Checksum = checksum.Checksum(hash.Sum32()) + + return json.Marshal(*cp) +} + +// MarshalCheckpoint returns marshalled checkpoint in v3 format +func (cp *CPUManagerCheckpoint) MarshalCheckpoint() ([]byte, error) { + // make sure checksum wasn't set before so it doesn't affect output checksum + cp.Checksum = 0 cp.Checksum = checksum.New(cp) return json.Marshal(*cp) } @@ -95,6 +132,11 @@ func (cp *CPUManagerCheckpointV2) UnmarshalCheckpoint(blob []byte) error { return json.Unmarshal(blob, cp) } +// UnmarshalCheckpoint tries to unmarshal passed bytes to checkpoint in v3 format +func (cp *CPUManagerCheckpoint) UnmarshalCheckpoint(blob []byte) error { + return json.Unmarshal(blob, cp) +} + // VerifyChecksum verifies that current checksum of checkpoint is valid in v1 format func (cp *CPUManagerCheckpointV1) VerifyChecksum() error { if cp.Checksum == 0 { @@ -109,7 +151,7 @@ func (cp *CPUManagerCheckpointV1) VerifyChecksum() error { cp.Checksum = ck hash := fnv.New32a() - fmt.Fprintf(hash, "%v", object) + _, _ = fmt.Fprintf(hash, "%v", object) actualCS := checksum.Checksum(hash.Sum32()) if cp.Checksum != actualCS { return &errors.CorruptCheckpointError{ @@ -127,6 +169,38 @@ func (cp *CPUManagerCheckpointV2) VerifyChecksum() error { // accept empty checksum for compatibility with old file backend return nil } + + ck := cp.Checksum + cp.Checksum = 0 + + if !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + err := ck.Verify(cp) + if err == nil { + cp.Checksum = ck + return nil + } + // If the standard verification failed, it could be a legacy checkpoint. + // Fallback to the legacy verification method, when V3 is latest. + } + + object := dump.ForHash(cp) + object = strings.Replace(object, "CPUManagerCheckpointV2", "CPUManagerCheckpoint", 1) + cp.Checksum = ck + + hash := fnv.New32a() + _, _ = fmt.Fprintf(hash, "%v", object) + actualCS := checksum.Checksum(hash.Sum32()) + if cp.Checksum != actualCS { + return &errors.CorruptCheckpointError{ + ActualCS: uint64(actualCS), + ExpectedCS: uint64(cp.Checksum), + } + } + return nil +} + +// VerifyChecksum verifies that current checksum of checkpoint is valid in v3 format +func (cp *CPUManagerCheckpoint) VerifyChecksum() error { ck := cp.Checksum cp.Checksum = 0 err := ck.Verify(cp) diff --git a/pkg/kubelet/cm/cpumanager/state/state.go b/pkg/kubelet/cm/cpumanager/state/state.go index 352fddfb9cdad..00e404c3de410 100644 --- a/pkg/kubelet/cm/cpumanager/state/state.go +++ b/pkg/kubelet/cm/cpumanager/state/state.go @@ -17,6 +17,10 @@ limitations under the License. package state import ( + "encoding/json" + "fmt" + "maps" + "k8s.io/utils/cpuset" ) @@ -28,19 +32,65 @@ func (as ContainerCPUAssignments) Clone() ContainerCPUAssignments { ret := make(ContainerCPUAssignments, len(as)) for pod := range as { ret[pod] = make(map[string]cpuset.CPUSet, len(as[pod])) - for container, cset := range as[pod] { - ret[pod][container] = cset - } + maps.Copy(ret[pod], as[pod]) } return ret } +// PodCPUAssignments contains pod-level CPU assignments. +type PodCPUAssignments map[string]PodEntry + +// PodEntry represents pod-level CPU assignments for a pod +type PodEntry struct { + CPUSet cpuset.CPUSet `json:"cpuSet,omitempty"` +} + +type podEntryJSON struct { + CPUSet string `json:"cpuSet,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +// It is required because CPUSet is stored as a string in the checkpoint JSON, +// but used as a struct in the internal state. +func (p PodEntry) MarshalJSON() ([]byte, error) { + return json.Marshal(podEntryJSON{ + CPUSet: p.CPUSet.String(), + }) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// It is required because CPUSet is stored as a string in the checkpoint JSON, +// but used as a struct in the internal state. +func (p *PodEntry) UnmarshalJSON(b []byte) error { + var entry podEntryJSON + if err := json.Unmarshal(b, &entry); err != nil { + return err + } + cset, err := cpuset.Parse(entry.CPUSet) + if err != nil { + return fmt.Errorf("failed to parse cpuset: %w", err) + } + p.CPUSet = cset + return nil +} + +// Clone returns a copy of PodCPUAssignments +func (a PodCPUAssignments) Clone() PodCPUAssignments { + clone := make(PodCPUAssignments, len(a)) + maps.Copy(clone, a) + return clone +} + // Reader interface used to read current cpu/pod assignment state type Reader interface { GetCPUSet(podUID string, containerName string) (cpuset.CPUSet, bool) GetDefaultCPUSet() cpuset.CPUSet GetCPUSetOrDefault(podUID string, containerName string) cpuset.CPUSet GetCPUAssignments() ContainerCPUAssignments + // GetPodCPUSet returns the pod-level CPU assignments of a pod + GetPodCPUSet(podUID string) (cpuset.CPUSet, bool) + // GetPodCPUAssignments returns all pod-level CPU assignments + GetPodCPUAssignments() PodCPUAssignments } type writer interface { @@ -49,6 +99,12 @@ type writer interface { SetCPUAssignments(ContainerCPUAssignments) Delete(podUID string, containerName string) ClearState() + // SetPodCPUSet stores pod-level CPU assignments of a pod + SetPodCPUSet(podUID string, cpuset cpuset.CPUSet) + // SetPodCPUAssignments sets PodCPUAssignments by using the passed parameter + SetPodCPUAssignments(PodCPUAssignments) + // DeletePod deletes pod-level CPU assignments for specified pod + DeletePod(podUID string) } // State interface provides methods for tracking and setting cpu/pod assignment diff --git a/pkg/kubelet/cm/cpumanager/state/state_checkpoint.go b/pkg/kubelet/cm/cpumanager/state/state_checkpoint.go index 9774bf3728717..1b03ee273c268 100644 --- a/pkg/kubelet/cm/cpumanager/state/state_checkpoint.go +++ b/pkg/kubelet/cm/cpumanager/state/state_checkpoint.go @@ -17,14 +17,18 @@ limitations under the License. package state import ( + "errors" "fmt" + "maps" "path/filepath" "sync" "github.com/go-logr/logr" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" - "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors" + cperrors "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" "k8s.io/utils/cpuset" ) @@ -92,48 +96,85 @@ func (sc *stateCheckpoint) migrateV1CheckpointToV2Checkpoint(src *CPUManagerChec return nil } +// migrateV2CheckpointToV3Checkpoint() converts checkpoints from the v1 format to the v2 format +func (sc *stateCheckpoint) migrateV2CheckpointToV3Checkpoint(src *CPUManagerCheckpointV2, dst *CPUManagerCheckpointV3) { + if src.PolicyName != "" { + dst.PolicyName = src.PolicyName + } + if src.DefaultCPUSet != "" { + dst.DefaultCPUSet = src.DefaultCPUSet + } + if len(src.Entries) > 0 { + dst.Entries = make(map[string]map[string]string, len(src.Entries)) + for podUID, containerEntries := range src.Entries { + dst.Entries[podUID] = make(map[string]string, len(containerEntries)) + maps.Copy(dst.Entries[podUID], containerEntries) + } + } +} + // restores state from a checkpoint and creates it if it doesn't exist func (sc *stateCheckpoint) restoreState() error { sc.mux.Lock() defer sc.mux.Unlock() - var err error - checkpointV1 := newCPUManagerCheckpointV1() - checkpointV2 := newCPUManagerCheckpointV2() - - if err = sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV1); err != nil { - checkpointV1 = &CPUManagerCheckpointV1{} // reset it back to 0 - if err = sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV2); err != nil { - if err == errors.ErrCheckpointNotFound { - return sc.storeState() - } - return err - } - } - - if err = sc.migrateV1CheckpointToV2Checkpoint(checkpointV1, checkpointV2); err != nil { - return fmt.Errorf("error migrating v1 checkpoint state to v2 checkpoint state: %s", err) + var checkpoint any + var err error + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + checkpoint, err = sc.loadAndMigrateCheckpointV3() + } else { + checkpoint, err = sc.loadAndMigrateCheckpointV2() } - if sc.policyName != checkpointV2.PolicyName { - return fmt.Errorf("configured policy %q differs from state checkpoint policy %q", sc.policyName, checkpointV2.PolicyName) + if err != nil { + if errors.Is(err, cperrors.ErrCheckpointNotFound) { + return sc.storeState() + } + return err } var tmpDefaultCPUSet cpuset.CPUSet - if tmpDefaultCPUSet, err = cpuset.Parse(checkpointV2.DefaultCPUSet); err != nil { - return fmt.Errorf("could not parse default cpu set %q: %v", checkpointV2.DefaultCPUSet, err) - } - var tmpContainerCPUSet cpuset.CPUSet tmpAssignments := ContainerCPUAssignments{} - for pod := range checkpointV2.Entries { - tmpAssignments[pod] = make(map[string]cpuset.CPUSet, len(checkpointV2.Entries[pod])) - for container, cpuString := range checkpointV2.Entries[pod] { - if tmpContainerCPUSet, err = cpuset.Parse(cpuString); err != nil { - return fmt.Errorf("could not parse cpuset %q for container %q in pod %q: %v", cpuString, container, pod, err) + tmpPodAssignments := PodCPUAssignments{} + + switch cp := checkpoint.(type) { + case *CPUManagerCheckpointV3: + if sc.policyName != cp.PolicyName { + return fmt.Errorf("configured policy %q differs from state checkpoint policy %q", sc.policyName, cp.PolicyName) + } + if tmpDefaultCPUSet, err = cpuset.Parse(cp.DefaultCPUSet); err != nil { + return fmt.Errorf("could not parse default cpu set %q: %w", cp.DefaultCPUSet, err) + } + for pod := range cp.Entries { + tmpAssignments[pod] = make(map[string]cpuset.CPUSet, len(cp.Entries[pod])) + for container, cpuString := range cp.Entries[pod] { + if tmpContainerCPUSet, err = cpuset.Parse(cpuString); err != nil { + return fmt.Errorf("could not parse cpuset %q for container %q in pod %q: %w", cpuString, container, pod, err) + } + tmpAssignments[pod][container] = tmpContainerCPUSet + } + } + maps.Copy(tmpPodAssignments, cp.PodEntries) + sc.cache.SetPodCPUAssignments(tmpPodAssignments) + case *CPUManagerCheckpointV2: + if sc.policyName != cp.PolicyName { + return fmt.Errorf("configured policy %q differs from state checkpoint policy %q", sc.policyName, cp.PolicyName) + } + if tmpDefaultCPUSet, err = cpuset.Parse(cp.DefaultCPUSet); err != nil { + return fmt.Errorf("could not parse default cpu set %q: %w", cp.DefaultCPUSet, err) + } + for pod := range cp.Entries { + tmpAssignments[pod] = make(map[string]cpuset.CPUSet, len(cp.Entries[pod])) + for container, cpuString := range cp.Entries[pod] { + if tmpContainerCPUSet, err = cpuset.Parse(cpuString); err != nil { + return fmt.Errorf("could not parse cpuset %q for container %q in pod %q: %w", cpuString, container, pod, err) + } + tmpAssignments[pod][container] = tmpContainerCPUSet } - tmpAssignments[pod][container] = tmpContainerCPUSet } + default: + return fmt.Errorf("unknown checkpoint version %T", cp) } sc.cache.SetDefaultCPUSet(tmpDefaultCPUSet) @@ -145,9 +186,117 @@ func (sc *stateCheckpoint) restoreState() error { return nil } +// loadAndMigrateCheckpoint loads the latest checkpoint and migrates it from older versions if needed. +func (sc *stateCheckpoint) loadAndMigrateCheckpointV3() (*CPUManagerCheckpointV3, error) { + // Try to load as V3. + checkpointV3 := newCPUManagerCheckpointV3() + err := sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV3) + if err == nil { + return checkpointV3, nil + } + if errors.Is(err, cperrors.ErrCheckpointNotFound) { + return nil, err + } + + // Log the V3 load error and fall back to V2. + if errors.Is(err, cperrors.CorruptCheckpointError{}) { + sc.logger.Error(err, "V3 checkpoint is corrupt, falling back to V2") + } else { + sc.logger.Info("could not load V3 checkpoint, falling back to V2", "err", err) + } + + // Try to load as V2. + checkpointV2, err := sc.loadAndMigrateCheckpointV2() + if err != nil { + return nil, err + } + + // Loaded V2, now migrate to V3. + sc.logger.Info("migrating cpu manager checkpoint from v2 to v3") + sc.migrateV2CheckpointToV3Checkpoint(checkpointV2, checkpointV3) + + return checkpointV3, nil +} + +func (sc *stateCheckpoint) loadAndMigrateCheckpointV2() (*CPUManagerCheckpointV2, error) { + // Try to load as V2. + checkpointV2 := newCPUManagerCheckpointV2() + err := sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV2) + if err == nil { + return checkpointV2, nil + } + if errors.Is(err, cperrors.ErrCheckpointNotFound) { + return nil, err + } + + // Log the V2 load error and fall back to V1. + if errors.Is(err, cperrors.CorruptCheckpointError{}) { + sc.logger.Error(err, "V2 checkpoint is corrupt, falling back to V1") + } else { + sc.logger.Info("could not load V2 checkpoint, falling back to V1", "err", err) + } + + // Try to load as V1. + checkpointV1, err := sc.loadCheckpointV1() + if err == nil { + // Loaded V1, now migrate V1 -> V2. + sc.logger.Info("migrating cpu manager checkpoint from v1 to v2") + tmpV2 := newCPUManagerCheckpointV2() + if migrationErr := sc.migrateV1CheckpointToV2Checkpoint(checkpointV1, tmpV2); migrationErr != nil { + return nil, fmt.Errorf("failed to migrate checkpoint from v1 to v2: %w", migrationErr) + } + return tmpV2, nil + } + + // All attempts failed. Return the last error we got (from the V1 read attempt). + return nil, err +} + +func (sc *stateCheckpoint) loadCheckpointV1() (*CPUManagerCheckpointV1, error) { + checkpointV1 := newCPUManagerCheckpointV1() + err := sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV1) + if err != nil { + return nil, err + } + return checkpointV1, nil +} + // saves state to a checkpoint, caller is responsible for locking func (sc *stateCheckpoint) storeState() error { - checkpoint := NewCPUManagerCheckpoint() + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + return sc.storeStateV3() + } + return sc.storeStateV2() +} + +// saves state to a checkpoint, caller is responsible for locking +func (sc *stateCheckpoint) storeStateV3() error { + checkpoint := newCPUManagerCheckpoint() + checkpoint.PolicyName = sc.policyName + checkpoint.DefaultCPUSet = sc.cache.GetDefaultCPUSet().String() + + assignments := sc.cache.GetCPUAssignments() + for pod := range assignments { + checkpoint.Entries[pod] = make(map[string]string, len(assignments[pod])) + for container, cset := range assignments[pod] { + checkpoint.Entries[pod][container] = cset.String() + } + } + + podAssignments := sc.cache.GetPodCPUAssignments() + maps.Copy(checkpoint.PodEntries, podAssignments) + + err := sc.checkpointManager.CreateCheckpoint(sc.checkpointName, checkpoint) + if err != nil { + sc.logger.Error(err, "Failed to save checkpoint") + return err + } + return nil +} + +// saves state to a checkpoint, caller is responsible for locking +func (sc *stateCheckpoint) storeStateV2() error { + checkpoint := newCPUManagerCheckpointV2() checkpoint.PolicyName = sc.policyName checkpoint.DefaultCPUSet = sc.cache.GetDefaultCPUSet().String() @@ -200,6 +349,32 @@ func (sc *stateCheckpoint) GetCPUAssignments() ContainerCPUAssignments { return sc.cache.GetCPUAssignments() } +// GetPodCPUAssignments returns pod-level CPU assignments +func (sc *stateCheckpoint) GetPodCPUAssignments() PodCPUAssignments { + sc.mux.RLock() + defer sc.mux.RUnlock() + + return sc.cache.GetPodCPUAssignments() +} + +func (sc *stateCheckpoint) SetPodCPUAssignments(assignments PodCPUAssignments) { + sc.mux.Lock() + defer sc.mux.Unlock() + + sc.cache.SetPodCPUAssignments(assignments) + err := sc.storeState() + if err != nil { + sc.logger.Error(err, "Failed to store state to checkpoint") + } +} + +// GetPodCPUSet returns pod-level CPU set +func (sc *stateCheckpoint) GetPodCPUSet(podUID string) (cpuset.CPUSet, bool) { + sc.mux.RLock() + defer sc.mux.RUnlock() + return sc.cache.GetPodCPUSet(podUID) +} + // SetCPUSet sets CPU set func (sc *stateCheckpoint) SetCPUSet(podUID string, containerName string, cset cpuset.CPUSet) { sc.mux.Lock() @@ -233,6 +408,17 @@ func (sc *stateCheckpoint) SetCPUAssignments(a ContainerCPUAssignments) { } } +// SetPodCPUSet sets pod-level CPU set +func (sc *stateCheckpoint) SetPodCPUSet(podUID string, cset cpuset.CPUSet) { + sc.mux.Lock() + defer sc.mux.Unlock() + sc.cache.SetPodCPUSet(podUID, cset) + err := sc.storeState() + if err != nil { + sc.logger.Error(err, "Failed to store state to checkpoint", "podUID", podUID) + } +} + // Delete deletes assignment for specified pod func (sc *stateCheckpoint) Delete(podUID string, containerName string) { sc.mux.Lock() @@ -244,6 +430,18 @@ func (sc *stateCheckpoint) Delete(podUID string, containerName string) { } } +// DeletePod deletes pod-level CPU assignments for specified pod. It does not +// affect container-level assignments. +func (sc *stateCheckpoint) DeletePod(podUID string) { + sc.mux.Lock() + defer sc.mux.Unlock() + sc.cache.DeletePod(podUID) + err := sc.storeState() + if err != nil { + sc.logger.Error(err, "Failed to store state to checkpoint", "podUID", podUID) + } +} + // ClearState clears the state and saves it in a checkpoint func (sc *stateCheckpoint) ClearState() { sc.mux.Lock() diff --git a/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go b/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go index 458419fe1e43f..75be69562f592 100644 --- a/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go +++ b/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go @@ -17,13 +17,18 @@ limitations under the License. package state import ( + "encoding/json" "os" "reflect" "strings" "testing" "github.com/stretchr/testify/require" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" + "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing" "k8s.io/kubernetes/test/utils/ktesting" @@ -34,12 +39,13 @@ const testingCheckpoint = "cpumanager_checkpoint_test" func TestCheckpointStateRestore(t *testing.T) { testCases := []struct { - description string - checkpointContent string - policyName string - initialContainers containermap.ContainerMap - expectedError string - expectedState *stateMemory + description string + checkpointContent string + policyName string + initialContainers containermap.ContainerMap + expectedError string + expectedState *stateMemory + podLevelResourceManagersEnabled bool }{ { "Restore non-existing checkpoint", @@ -48,6 +54,7 @@ func TestCheckpointStateRestore(t *testing.T) { containermap.ContainerMap{}, "", &stateMemory{}, + false, }, { "Restore default cpu set", @@ -63,6 +70,7 @@ func TestCheckpointStateRestore(t *testing.T) { &stateMemory{ defaultCPUSet: cpuset.New(4, 5, 6), }, + false, }, { "Restore valid checkpoint", @@ -89,6 +97,7 @@ func TestCheckpointStateRestore(t *testing.T) { }, defaultCPUSet: cpuset.New(1, 2, 3), }, + false, }, { "Restore checkpoint with invalid checksum", @@ -102,6 +111,7 @@ func TestCheckpointStateRestore(t *testing.T) { containermap.ContainerMap{}, "checkpoint is corrupted", &stateMemory{}, + false, }, { "Restore checkpoint with invalid JSON", @@ -110,6 +120,7 @@ func TestCheckpointStateRestore(t *testing.T) { containermap.ContainerMap{}, "unexpected end of JSON input", &stateMemory{}, + false, }, { "Restore checkpoint with invalid policy name", @@ -123,6 +134,7 @@ func TestCheckpointStateRestore(t *testing.T) { containermap.ContainerMap{}, `configured policy "none" differs from state checkpoint policy "other"`, &stateMemory{}, + false, }, { "Restore checkpoint with unparsable default cpu set", @@ -136,6 +148,7 @@ func TestCheckpointStateRestore(t *testing.T) { containermap.ContainerMap{}, `could not parse default cpu set "1.3": strconv.Atoi: parsing "1.3": invalid syntax`, &stateMemory{}, + false, }, { "Restore checkpoint with unparsable assignment entry", @@ -154,6 +167,7 @@ func TestCheckpointStateRestore(t *testing.T) { containermap.ContainerMap{}, `could not parse cpuset "asd" for container "container2" in pod "pod": strconv.Atoi: parsing "asd": invalid syntax`, &stateMemory{}, + false, }, { "Restore checkpoint from checkpoint with v1 checksum", @@ -168,6 +182,7 @@ func TestCheckpointStateRestore(t *testing.T) { &stateMemory{ defaultCPUSet: cpuset.New(1, 2, 3), }, + false, }, { "Restore checkpoint with migration", @@ -197,6 +212,189 @@ func TestCheckpointStateRestore(t *testing.T) { }, defaultCPUSet: cpuset.New(1, 2, 3), }, + false, + }, + { + "Restore checkpoint from v1 (migration) with PodLevelResourceManagers enabled", + `{ + "policyName": "none", + "defaultCPUSet": "1-3", + "entries": { + "containerID1": "4-6", + "containerID2": "1-3" + }, + "checksum": 3680390589 + }`, + "none", + func() containermap.ContainerMap { + cm := containermap.NewContainerMap() + cm.Add("pod", "container1", "containerID1") + cm.Add("pod", "container2", "containerID2") + return cm + }(), + "", + &stateMemory{ + assignments: ContainerCPUAssignments{ + "pod": map[string]cpuset.CPUSet{ + "container1": cpuset.New(4, 5, 6), + "container2": cpuset.New(1, 2, 3), + }, + }, + defaultCPUSet: cpuset.New(1, 2, 3), + }, + true, + }, + { + "Restore checkpoint from v2 (migration) with PodLevelResourceManagers enabled", + `{ + "policyName": "none", + "defaultCPUSet": "1-3", + "entries": { + "pod": { + "container1": "4-6", + "container2": "1-3" + } + }, + "checksum": 3610638499 + }`, + "none", + containermap.ContainerMap{}, + "", + &stateMemory{ + assignments: ContainerCPUAssignments{ + "pod": map[string]cpuset.CPUSet{ + "container1": cpuset.New(4, 5, 6), + "container2": cpuset.New(1, 2, 3), + }, + }, + defaultCPUSet: cpuset.New(1, 2, 3), + }, + true, + }, + { + "Restore checkpoint from v2 (migration) with PodLevelResourceManagers disabled", + `{ + "policyName": "none", + "defaultCPUSet": "1-3", + "entries": { + "pod": { + "container1": "4-6", + "container2": "1-3" + } + }, + "checksum": 3610638499 + }`, + "none", + containermap.ContainerMap{}, + "", + &stateMemory{ + assignments: ContainerCPUAssignments{ + "pod": map[string]cpuset.CPUSet{ + "container1": cpuset.New(4, 5, 6), + "container2": cpuset.New(1, 2, 3), + }, + }, + defaultCPUSet: cpuset.New(1, 2, 3), + }, + false, + }, + { + "Restore valid v3 checkpoint with PodLevelResourceManagers enabled", + `{ + "policyName": "none", + "defaultCPUSet": "1-3", + "entries": { + "pod": { + "container1": "4-6", + "container2": "1-3" + } + }, + "podEntries": { + "pod": { + "cpuSet": "4-6" + } + }, + "checksum": 2649431787 + }`, + "none", + containermap.ContainerMap{}, + "", + &stateMemory{ + assignments: ContainerCPUAssignments{ + "pod": map[string]cpuset.CPUSet{ + "container1": cpuset.New(4, 5, 6), + "container2": cpuset.New(1, 2, 3), + }, + }, + podAssignments: PodCPUAssignments{ + "pod": PodEntry{ + CPUSet: cpuset.New(4, 5, 6), + }, + }, + defaultCPUSet: cpuset.New(1, 2, 3), + }, + true, + }, + { + "Restore valid v3 checkpoint with PodLevelResourceManagers disabled", + `{ + "policyName": "none", + "defaultCPUSet": "1-3", + "entries": { + "pod": { + "container1": "4-6", + "container2": "1-3" + } + }, + "podEntries": { + "pod": { + "cpuSet": "4-6" + } + }, + "checksum": 2649431787 + }`, + "none", + containermap.ContainerMap{}, + "could not restore state from checkpoint", + &stateMemory{ + assignments: ContainerCPUAssignments{ + "pod": map[string]cpuset.CPUSet{ + "container1": cpuset.New(4, 5, 6), + "container2": cpuset.New(1, 2, 3), + }, + }, + podAssignments: PodCPUAssignments{ + "pod": PodEntry{ + CPUSet: cpuset.New(4, 5, 6), + }, + }, + defaultCPUSet: cpuset.New(1, 2, 3), + }, + false, + }, + { + "Restore non-existing checkpoint with PodLevelResourceManagers enabled", + "", + "none", + containermap.ContainerMap{}, + "", + &stateMemory{}, + true, + }, + { + "Restore corrupt checkpoint with PodLevelResourceManagers enabled", + `{ + "policyName": "none", + "defaultCPUSet": "1-3", + "entries": {}, + "podEntries": {}, + "checksum": 12345 + }`, + "none", + containermap.ContainerMap{}, + "checkpoint is corrupted", + &stateMemory{}, + true, }, } @@ -210,6 +408,11 @@ func TestCheckpointStateRestore(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { + if tc.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, true) + } + // ensure there is no previous checkpoint cpm.RemoveCheckpoint(testingCheckpoint) @@ -436,3 +639,53 @@ func AssertStateEqual(t *testing.T, sf State, sm State) { t.Errorf("State CPU assignments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm) } } + +func TestCPUManagerCheckpointV2_MarshalCheckpoint_ForwardCompatibility(t *testing.T) { + // 1. Create a V2 checkpoint using the struct defined in the current codebase (1.36+) + currentCheckpoint := &CPUManagerCheckpointV2{ + PolicyName: "none", + DefaultCPUSet: "1-3", + Entries: make(map[string]map[string]string), + } + + // Marshal it using the logic that forces the "CPUManagerCheckpoint" name + data, err := currentCheckpoint.MarshalCheckpoint() + if err != nil { + t.Fatalf("Failed to marshal checkpoint: %v", err) + } + + // 2. Unmarshal the raw JSON to extract the checksum that was actually written to the file + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + actualChecksumFloat, ok := result["checksum"].(float64) + if !ok { + t.Fatalf("Checksum field missing or invalid type") + } + writtenChecksum := checksum.Checksum(uint64(actualChecksumFloat)) + + // 3. Reconstruct how versions 1.35 and earlier would calculate the checksum + // by defining a struct with the exact legacy name and fields. + type CPUManagerCheckpoint struct { + PolicyName string `json:"policyName"` + DefaultCPUSet string `json:"defaultCpuSet"` + Entries map[string]map[string]string `json:"entries,omitempty"` + Checksum checksum.Checksum `json:"checksum"` + } + + legacyCheckpoint := &CPUManagerCheckpoint{ + PolicyName: currentCheckpoint.PolicyName, + DefaultCPUSet: currentCheckpoint.DefaultCPUSet, + Entries: currentCheckpoint.Entries, + } + + expectedLegacyChecksum := checksum.New(legacyCheckpoint) + + // 4. Assert that the checksum written by our 1.36+ code matches + // what a 1.35 Kubelet would expect to see. + if writtenChecksum != expectedLegacyChecksum { + t.Errorf("Written Checksum %d does not match legacy calculation %d. Forward compatibility broken.", writtenChecksum, expectedLegacyChecksum) + } +} diff --git a/pkg/kubelet/cm/cpumanager/state/state_mem.go b/pkg/kubelet/cm/cpumanager/state/state_mem.go index 3da3c1a8d311b..172bea6c67e62 100644 --- a/pkg/kubelet/cm/cpumanager/state/state_mem.go +++ b/pkg/kubelet/cm/cpumanager/state/state_mem.go @@ -17,18 +17,22 @@ limitations under the License. package state import ( + "maps" "sync" "github.com/go-logr/logr" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" "k8s.io/utils/cpuset" ) type stateMemory struct { sync.RWMutex - logger logr.Logger - assignments ContainerCPUAssignments - defaultCPUSet cpuset.CPUSet + logger logr.Logger + assignments ContainerCPUAssignments + podAssignments PodCPUAssignments + defaultCPUSet cpuset.CPUSet } var _ State = &stateMemory{} @@ -39,6 +43,16 @@ func NewMemoryState(logger logr.Logger) State { // since we store a checkpoint, we can use the relatively expensive "WithName". logger = klog.LoggerWithName(logger, "CPUManager state memory") logger.Info("Initialized") + + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + return &stateMemory{ + logger: logger, + assignments: ContainerCPUAssignments{}, + podAssignments: PodCPUAssignments{}, + defaultCPUSet: cpuset.New(), + } + } + return &stateMemory{ logger: logger, assignments: ContainerCPUAssignments{}, @@ -74,6 +88,21 @@ func (s *stateMemory) GetCPUAssignments() ContainerCPUAssignments { return s.assignments.Clone() } +func (s *stateMemory) GetPodCPUSet(podUID string) (cpuset.CPUSet, bool) { + s.RLock() + defer s.RUnlock() + res, ok := s.podAssignments[podUID] + return res.CPUSet.Clone(), ok +} + +func (s *stateMemory) GetPodCPUAssignments() PodCPUAssignments { + s.RLock() + defer s.RUnlock() + clone := make(PodCPUAssignments) + maps.Copy(clone, s.podAssignments) + return clone +} + func (s *stateMemory) SetCPUSet(podUID string, containerName string, cset cpuset.CPUSet) { s.Lock() defer s.Unlock() @@ -94,6 +123,16 @@ func (s *stateMemory) SetDefaultCPUSet(cset cpuset.CPUSet) { s.logger.Info("Updated default CPUSet", "cpuSet", cset) } +func (s *stateMemory) SetPodCPUSet(podUID string, cset cpuset.CPUSet) { + s.Lock() + defer s.Unlock() + + podEntry := s.podAssignments[podUID] + podEntry.CPUSet = cset + s.podAssignments[podUID] = podEntry + s.logger.Info("Updated pod CPUSet", "podUID", podUID, "cpuSet", cset) +} + func (s *stateMemory) SetCPUAssignments(a ContainerCPUAssignments) { s.Lock() defer s.Unlock() @@ -102,6 +141,15 @@ func (s *stateMemory) SetCPUAssignments(a ContainerCPUAssignments) { s.logger.Info("Updated CPUSet assignments", "assignments", a) } +func (s *stateMemory) SetPodCPUAssignments(a PodCPUAssignments) { + s.Lock() + defer s.Unlock() + + s.podAssignments = make(PodCPUAssignments) + maps.Copy(s.podAssignments, a) + s.logger.Info("Updated pod CPUSet assignments", "assignments", a) +} + func (s *stateMemory) Delete(podUID string, containerName string) { s.Lock() defer s.Unlock() @@ -113,11 +161,24 @@ func (s *stateMemory) Delete(podUID string, containerName string) { s.logger.V(2).Info("Deleted CPUSet assignment", "podUID", podUID, "containerName", containerName) } +// DeletePod deletes pod-level CPU assignments for specified pod. It does not +// affect container-level assignments. +func (s *stateMemory) DeletePod(podUID string) { + s.Lock() + defer s.Unlock() + + delete(s.podAssignments, podUID) + s.logger.V(2).Info("Deleted pod", "podUID", podUID) +} + func (s *stateMemory) ClearState() { s.Lock() defer s.Unlock() - s.defaultCPUSet = cpuset.CPUSet{} + s.defaultCPUSet = cpuset.New() s.assignments = make(ContainerCPUAssignments) + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + s.podAssignments = make(PodCPUAssignments) + } s.logger.V(2).Info("Cleared state") } diff --git a/pkg/kubelet/cm/cpumanager/topology_hints_test.go b/pkg/kubelet/cm/cpumanager/topology_hints_test.go index 166b86db0530d..f5357d819b5d0 100644 --- a/pkg/kubelet/cm/cpumanager/topology_hints_test.go +++ b/pkg/kubelet/cm/cpumanager/topology_hints_test.go @@ -36,13 +36,14 @@ import ( ) type testCase struct { - name string - pod v1.Pod - container v1.Container - assignments state.ContainerCPUAssignments - defaultCPUSet cpuset.CPUSet - expectedHints []topologymanager.TopologyHint - podLevelResourcesEnabled bool + name string + pod v1.Pod + container v1.Container + assignments state.ContainerCPUAssignments + defaultCPUSet cpuset.CPUSet + expectedHints []topologymanager.TopologyHint + podLevelResourcesEnabled bool + podLevelResourceManagersEnabled bool } func returnMachineInfo() cadvisorapi.MachineInfo { @@ -68,6 +69,7 @@ func returnMachineInfo() cadvisorapi.MachineInfo { } type containerOptions struct { + name string request string limit string restartPolicy v1.ContainerRestartPolicy @@ -132,12 +134,16 @@ func TestPodGuaranteedCPUs(t *testing.T) { {request: "100", limit: "100"}, }) + testPodWithPLR := makePodWithPodLevelResources("fakePodWithPLR", "3", "3", "fakeContainer", "1", "1") + p := staticPolicy{} tcases := []struct { - name string - pod *v1.Pod - expectedCPU int + name string + pod *v1.Pod + expectedCPU int + podLevelResourcesEnabled bool + podLevelResourceManagersEnabled bool }{ { name: "TestCase01: if requestedCPU == 0, Pod is not Guaranteed Qos", @@ -199,13 +205,26 @@ func TestPodGuaranteedCPUs(t *testing.T) { pod: testPod12, expectedCPU: 210, }, + { + name: "TestCase13: Pod with pod-level resources and PodLevelResourceManagers enabled", + pod: testPodWithPLR, + expectedCPU: 3, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + }, } for _, tc := range tcases { t.Run(tc.name, func(t *testing.T) { + if tc.podLevelResourcesEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + } + if tc.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + } requestedCPU := p.podGuaranteedCPUs(logger, tc.pod) if requestedCPU != tc.expectedCPU { - t.Errorf("Expected in result to be %v , got %v", tc.expectedCPU, requestedCPU) + t.Errorf("Test case %q: Expected in result to be %v , got %v", tc.name, tc.expectedCPU, requestedCPU) } }) } @@ -216,7 +235,12 @@ func TestGetTopologyHints(t *testing.T) { machineInfo := returnMachineInfo() for _, tc := range returnTestCases() { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + if tc.podLevelResourcesEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + } + if tc.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + } topology, _ := topology.Discover(logger, &machineInfo) @@ -257,7 +281,7 @@ func TestGetTopologyHints(t *testing.T) { return tc.expectedHints[i].LessThan(tc.expectedHints[j]) }) if !reflect.DeepEqual(tc.expectedHints, hints) { - t.Errorf("Expected in result to be %v , got %v", tc.expectedHints, hints) + t.Errorf("Test case %q: Expected in result to be %v , got %v", tc.name, tc.expectedHints, hints) } } } @@ -267,7 +291,12 @@ func TestGetPodTopologyHints(t *testing.T) { machineInfo := returnMachineInfo() for _, tc := range returnTestCases() { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + if tc.podLevelResourcesEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + } + if tc.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + } topology, _ := topology.Discover(logger, &machineInfo) @@ -308,7 +337,7 @@ func TestGetPodTopologyHints(t *testing.T) { return tc.expectedHints[i].LessThan(tc.expectedHints[j]) }) if !reflect.DeepEqual(tc.expectedHints, podHints) { - t.Errorf("Expected in result to be %v , got %v", tc.expectedHints, podHints) + t.Errorf("Test case %q: Expected in result to be %v , got %v", tc.name, tc.expectedHints, podHints) } } } @@ -493,6 +522,143 @@ func TestGetPodTopologyHintsWithPolicyOptions(t *testing.T) { } } +func TestTopologyHintsPodLevelResources(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + machineInfo := returnMachineInfo() + testPodGuaranteedOverridePLR := makePodWithPodLevelResources("guaranteedPod", "3", "3", "c1", "1", "2") + testContainerGuaranteedOverridePLR := &testPodGuaranteedOverridePLR.Spec.Containers[0] + + testPodGuaranteedPLR := makePodWithPodLevelResources("guaranteedPod", "3", "3", "c1", "2", "2") + testContainerGuaranteedPLR := &testPodGuaranteedPLR.Spec.Containers[0] + + testPodBurstablePLR := makePodWithPodLevelResources("burstablePod", "2", "4", "c1", "1", "2") + testContainerBurstablePLR := &testPodBurstablePLR.Spec.Containers[0] + + firstSocketMask, _ := bitmask.NewBitMask(0) + crossSocketMask, _ := bitmask.NewBitMask(0, 1) + + testCases := []struct { + name string + pod v1.Pod + container v1.Container + assignments state.ContainerCPUAssignments + defaultCPUSet cpuset.CPUSet + expectedPodLevelHints []topologymanager.TopologyHint + expectedContainerLevelHints []topologymanager.TopologyHint + }{ + { + name: "Guaranteed pod level resources overriding burstable container should only generate hints for pod scope", + pod: *testPodGuaranteedOverridePLR, + container: *testContainerGuaranteedOverridePLR, + defaultCPUSet: cpuset.New(0, 1, 2, 3), + expectedPodLevelHints: []topologymanager.TopologyHint{ + { + NUMANodeAffinity: firstSocketMask, + Preferred: true, + }, + { + NUMANodeAffinity: crossSocketMask, + Preferred: false, + }, + }, + expectedContainerLevelHints: nil, + }, + { + name: "Guaranteed pod level resources with guaranteed container should generate hints for both scopes", + pod: *testPodGuaranteedPLR, + container: *testContainerGuaranteedPLR, + defaultCPUSet: cpuset.New(0, 1, 2, 3), + expectedPodLevelHints: []topologymanager.TopologyHint{ + { + NUMANodeAffinity: firstSocketMask, + Preferred: true, + }, + { + NUMANodeAffinity: crossSocketMask, + Preferred: false, + }, + }, + expectedContainerLevelHints: []topologymanager.TopologyHint{ + { + NUMANodeAffinity: firstSocketMask, + Preferred: true, + }, + { + NUMANodeAffinity: crossSocketMask, + Preferred: false, + }, + }, + }, + { + name: "Burstable pod level resources with burstable container should not generate hints for both scopes", + pod: *testPodBurstablePLR, + container: *testContainerBurstablePLR, + defaultCPUSet: cpuset.New(0, 1, 2, 3), + expectedPodLevelHints: nil, + expectedContainerLevelHints: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, true) + + topology, _ := topology.Discover(logger, &machineInfo) + + var activePods []*v1.Pod + for p := range tc.assignments { + pod := v1.Pod{} + pod.UID = types.UID(p) + for c := range tc.assignments[p] { + container := v1.Container{} + container.Name = c + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + activePods = append(activePods, &pod) + } + + m := manager{ + policy: &staticPolicy{ + topology: topology, + }, + state: &mockState{ + assignments: tc.assignments, + defaultCPUSet: tc.defaultCPUSet, + }, + topology: topology, + activePods: func() []*v1.Pod { return activePods }, + podStatusProvider: mockPodStatusProvider{}, + sourcesReady: &sourcesReadyStub{}, + } + + // Test GetPodTopologyHints + podHints := m.GetPodTopologyHints(&tc.pod)[string(v1.ResourceCPU)] + sort.SliceStable(podHints, func(i, j int) bool { + return podHints[i].LessThan(podHints[j]) + }) + sort.SliceStable(tc.expectedPodLevelHints, func(i, j int) bool { + return tc.expectedPodLevelHints[i].LessThan(tc.expectedPodLevelHints[j]) + }) + if !reflect.DeepEqual(tc.expectedPodLevelHints, podHints) { + t.Errorf("Test case %q (GetPodTopologyHints): Expected in result to be %v , got %v", tc.name, tc.expectedPodLevelHints, podHints) + } + + // Test GetTopologyHints + containerHints := m.GetTopologyHints(&tc.pod, &tc.container)[string(v1.ResourceCPU)] + sort.SliceStable(containerHints, func(i, j int) bool { + return containerHints[i].LessThan(containerHints[j]) + }) + sort.SliceStable(tc.expectedContainerLevelHints, func(i, j int) bool { + return tc.expectedContainerLevelHints[i].LessThan(tc.expectedContainerLevelHints[j]) + }) + if !reflect.DeepEqual(tc.expectedContainerLevelHints, containerHints) { + t.Errorf("Test case %q (GetTopologyHints): Expected in result to be %v , got %v", tc.name, tc.expectedContainerLevelHints, containerHints) + } + }) + } +} + func returnTestCases() []testCase { testPod1 := makePod("fakePod", "fakeContainer", "2", "2") testContainer1 := &testPod1.Spec.Containers[0] @@ -503,9 +669,12 @@ func returnTestCases() []testCase { testPod4 := makePod("fakePod", "fakeContainer", "11", "11") testContainer4 := &testPod4.Spec.Containers[0] - testPod5 := makePodWithPodLevelResources("fakePod", "5", "5", "fakeContainer", "4", "4") + testPod5 := makePodWithPodLevelResources("fakePod", "3", "3", "fakeContainer", "2", "2") testContainer5 := &testPod5.Spec.Containers[0] + testPodBurstablePLR := makePodWithPodLevelResources("burstablePod", "2", "4", "c1", "1", "2") + testContainerBurstablePLR := &testPodBurstablePLR.Spec.Containers[0] + firstSocketMask, _ := bitmask.NewBitMask(0) secondSocketMask, _ := bitmask.NewBitMask(1) crossSocketMask, _ := bitmask.NewBitMask(0, 1) @@ -669,12 +838,40 @@ func returnTestCases() []testCase { expectedHints: []topologymanager.TopologyHint{}, }, { - name: "Pod has pod level resources, no hint generation", - pod: *testPod5, - container: *testContainer5, - defaultCPUSet: cpuset.New(0, 1, 2, 3), - expectedHints: nil, - podLevelResourcesEnabled: true, + name: "Pod has pod level resources but PodLevelResourceManagersEnabled is disabled, no hint generation", + pod: *testPod5, + container: *testContainer5, + defaultCPUSet: cpuset.New(0, 1, 2, 3), + expectedHints: nil, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: false, + }, + { + name: "Pod has pod level resources and PodLevelResourceManagersEnabled is enabled, hint generation", + pod: *testPod5, + container: *testContainer5, + defaultCPUSet: cpuset.New(0, 1, 2, 3), + expectedHints: []topologymanager.TopologyHint{ + { + NUMANodeAffinity: firstSocketMask, + Preferred: true, + }, + { + NUMANodeAffinity: crossSocketMask, + Preferred: false, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + }, + { + name: "Burstable pod with pod level resources should not generate hints", + pod: *testPodBurstablePLR, + container: *testContainerBurstablePLR, + defaultCPUSet: cpuset.New(0, 1, 2, 3), + expectedHints: nil, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, }, } } diff --git a/pkg/kubelet/cm/devicemanager/manager.go b/pkg/kubelet/cm/devicemanager/manager.go index 166de0ccf7f84..cd796773a2c4c 100644 --- a/pkg/kubelet/cm/devicemanager/manager.go +++ b/pkg/kubelet/cm/devicemanager/manager.go @@ -250,7 +250,7 @@ func (m *ManagerImpl) PluginDisconnected(logger klog.Logger, resourceName string if ep, exists := m.endpoints[resourceName]; exists { m.markResourceUnhealthy(logger, resourceName) - logger.V(2).Info("Endpoint became unhealthy", "resourceName", resourceName, "endpoint", ep) + logger.V(2).Info("Endpoint became unhealthy", "resourceName", resourceName) ep.e.setStopTime(time.Now()) } @@ -1122,6 +1122,12 @@ func (m *ManagerImpl) GetAllocatableDevices() ResourceDeviceInstances { return resp } +// AllocatePod is called to trigger the allocation of resources to a pod. +func (m *ManagerImpl) AllocatePod(pod *v1.Pod) error { + // Device Manager does not support pod level resource allocation. + return nil +} + // GetDevices returns the devices used by the specified container func (m *ManagerImpl) GetDevices(podUID, containerName string) ResourceDeviceInstances { return m.podDevices.getContainerDevices(podUID, containerName) @@ -1203,21 +1209,36 @@ func (m *ManagerImpl) ShouldResetExtendedResourceCapacity() bool { } func (m *ManagerImpl) isContainerAlreadyRunning(logger klog.Logger, podUID, cntName string) bool { - cntID, err := m.containerMap.GetContainerID(podUID, cntName) - if err != nil { - logger.Error(err, "Container not found in the initial map, assumed NOT running", "podUID", podUID, "containerName", cntName) + // Check if ANY container for this pod/container name is running. + // This handles the case where a container restarted before kubelet restart, + // so the containerMap might have multiple entries (old exited + new running). + // We need to check all of them to see if any are running. + // + // Note: if container runtime is down when kubelet restarts, containerRunningSet will be empty, + // so containers will fail admission, hitting https://github.com/kubernetes/kubernetes/issues/118559. + // This scenario should however be rare enough. + foundAnyContainer := false + foundRunningContainer := false + + m.containerMap.Visit(func(visitPodUID, visitContainerName, visitContainerID string) { + if visitPodUID == podUID && visitContainerName == cntName { + foundAnyContainer = true + if m.containerRunningSet.Has(visitContainerID) { + foundRunningContainer = true + logger.V(4).Info("Container found in the initial running set", "podUID", podUID, "containerName", cntName, "containerID", visitContainerID) + } + } + }) + + if !foundAnyContainer { + logger.V(4).Info("Container not found in the initial map, assumed NOT running", "podUID", podUID, "containerName", cntName) return false } - // note that if container runtime is down when kubelet restarts, this set will be empty, - // so on kubelet restart containers will again fail admission, hitting https://github.com/kubernetes/kubernetes/issues/118559 again. - // This scenario should however be rare enough. - if !m.containerRunningSet.Has(cntID) { - logger.V(4).Info("Container not present in the initial running set", "podUID", podUID, "containerName", cntName, "containerID", cntID) + if !foundRunningContainer { + logger.V(4).Info("Container found in map but not in running set", "podUID", podUID, "containerName", cntName) return false } - // Once we make it here we know we have a running container. - logger.V(4).Info("Container found in the initial set, assumed running", "podUID", podUID, "containerName", cntName, "containerID", cntID) return true } diff --git a/pkg/kubelet/cm/devicemanager/manager_test.go b/pkg/kubelet/cm/devicemanager/manager_test.go index 5178cb4c08f65..38add8678e7d0 100644 --- a/pkg/kubelet/cm/devicemanager/manager_test.go +++ b/pkg/kubelet/cm/devicemanager/manager_test.go @@ -1099,9 +1099,9 @@ func TestPodContainerDeviceAllocation(t *testing.T) { if testCase.expectedContainerOptsLen == nil { as.Nil(runContainerOpts) } else { - as.Equal(len(runContainerOpts.Devices), testCase.expectedContainerOptsLen[0]) - as.Equal(len(runContainerOpts.Mounts), testCase.expectedContainerOptsLen[1]) - as.Equal(len(runContainerOpts.Envs), testCase.expectedContainerOptsLen[2]) + as.Len(runContainerOpts.Devices, testCase.expectedContainerOptsLen[0]) + as.Len(runContainerOpts.Mounts, testCase.expectedContainerOptsLen[1]) + as.Len(runContainerOpts.Envs, testCase.expectedContainerOptsLen[2]) } as.Equal(testCase.expectedAllocatedResName1, testManager.allocatedDevices[res1.resourceName].Len()) as.Equal(testCase.expectedAllocatedResName2, testManager.allocatedDevices[res2.resourceName].Len()) @@ -1275,7 +1275,7 @@ func TestDevicesToAllocateConflictWithUpdateAllocatedDevices(t *testing.T) { set, err := testManager.devicesToAllocate(tCtx, podToAllocate, containerToAllocate, resourceName, 1, sets.New[string]()) assert.NoError(t, err) - assert.Equal(t, set, sets.New[string](deviceID)) + assert.Equal(t, sets.New[string](deviceID), set) } func TestGetDeviceRunContainerOptions(t *testing.T) { @@ -1675,15 +1675,15 @@ func TestDevicePreStartContainer(t *testing.T) { as.Contains(initializedDevs, "dev1") as.Contains(initializedDevs, "dev2") - as.Equal(len(initializedDevs), res1.devs.Devices().Len()) + as.Len(initializedDevs, res1.devs.Devices().Len()) expectedResps, err := allocateStubFunc()([]string{"dev1", "dev2"}) as.NoError(err) as.Len(expectedResps.ContainerResponses, 1) expectedResp := expectedResps.ContainerResponses[0] - as.Equal(len(runContainerOpts.Devices), len(expectedResp.Devices)) - as.Equal(len(runContainerOpts.Mounts), len(expectedResp.Mounts)) - as.Equal(len(runContainerOpts.Envs), len(expectedResp.Envs)) + as.Len(runContainerOpts.Devices, len(expectedResp.Devices)) + as.Len(runContainerOpts.Mounts, len(expectedResp.Mounts)) + as.Len(runContainerOpts.Envs, len(expectedResp.Envs)) pod2 := makePod(v1.ResourceList{ v1.ResourceName(res1.resourceName): *resource.NewQuantity(int64(0), resource.DecimalSI)}) diff --git a/pkg/kubelet/cm/devicemanager/plugin/v1beta1/client.go b/pkg/kubelet/cm/devicemanager/plugin/v1beta1/client.go index cdb6a50497101..a6e0e79c10f27 100644 --- a/pkg/kubelet/cm/devicemanager/plugin/v1beta1/client.go +++ b/pkg/kubelet/cm/devicemanager/plugin/v1beta1/client.go @@ -104,7 +104,7 @@ func (c *client) Disconnect(logger klog.Logger) error { c.mutex.Lock() if c.grpc != nil { if err := c.grpc.Close(); err != nil { - logger.V(2).Info("Failed to close gRPC connection", "resource", c.Resource(), "err", err) + logger.V(2).Info("Failed to close grpc connection", "resource", c.Resource(), "err", err) } c.grpc = nil } diff --git a/pkg/kubelet/cm/devicemanager/topology_hints_test.go b/pkg/kubelet/cm/devicemanager/topology_hints_test.go index 17e7751c0cc65..04d70a3e2d90a 100644 --- a/pkg/kubelet/cm/devicemanager/topology_hints_test.go +++ b/pkg/kubelet/cm/devicemanager/topology_hints_test.go @@ -44,6 +44,10 @@ func (m *mockAffinityStore) GetPolicy() topologymanager.Policy { return nil } +func (m *mockAffinityStore) Name() string { + return "container" +} + func makeNUMADevice(id string, numa int) *pluginapi.Device { return &pluginapi.Device{ ID: id, diff --git a/pkg/kubelet/cm/devicemanager/types.go b/pkg/kubelet/cm/devicemanager/types.go index b377443c434c2..7ba66c463ea44 100644 --- a/pkg/kubelet/cm/devicemanager/types.go +++ b/pkg/kubelet/cm/devicemanager/types.go @@ -89,6 +89,9 @@ type Manager interface { // and is consulted to make Topology aware resource alignments per Pod GetPodTopologyHints(pod *v1.Pod) map[string][]topologymanager.TopologyHint + // AllocatePod is called to trigger the allocation of resources to a pod. + AllocatePod(pod *v1.Pod) error + // UpdateAllocatedDevices frees any Devices that are bound to terminated pods. UpdateAllocatedDevices() diff --git a/pkg/kubelet/cm/dra/OWNERS b/pkg/kubelet/cm/dra/OWNERS index 74bf9b65e656f..d9e61e4cb9889 100644 --- a/pkg/kubelet/cm/dra/OWNERS +++ b/pkg/kubelet/cm/dra/OWNERS @@ -1,2 +1,5 @@ labels: - wg/device-management +approvers: + - pohly + - bart0sh diff --git a/pkg/kubelet/cm/dra/claiminfo.go b/pkg/kubelet/cm/dra/claiminfo.go index f743258a14fc3..c3dd650500ec0 100644 --- a/pkg/kubelet/cm/dra/claiminfo.go +++ b/pkg/kubelet/cm/dra/claiminfo.go @@ -45,8 +45,6 @@ type ClaimInfo struct { // claimInfoCache is a cache of processed resource claims keyed by namespace/claimname. type claimInfoCache struct { - logger klog.Logger - sync.RWMutex checkpointer state.Checkpointer claimInfo map[string]*ClaimInfo @@ -137,7 +135,7 @@ func (info *ClaimInfo) cdiDevicesAsList(requestName string) []kubecontainer.CDID } // newClaimInfoCache creates a new claim info cache object, pre-populated from a checkpoint (if present). -func newClaimInfoCache(logger klog.Logger, stateDir, checkpointName string) (*claimInfoCache, error) { +func newClaimInfoCache(stateDir, checkpointName string) (*claimInfoCache, error) { checkpointer, err := state.NewCheckpointer(stateDir, checkpointName) if err != nil { return nil, fmt.Errorf("could not initialize checkpoint manager, please drain node and remove DRA state file, err: %w", err) @@ -149,7 +147,6 @@ func newClaimInfoCache(logger klog.Logger, stateDir, checkpointName string) (*cl } cache := &claimInfoCache{ - logger: logger, checkpointer: checkpointer, claimInfo: make(map[string]*ClaimInfo), } @@ -169,11 +166,12 @@ func newClaimInfoCache(logger klog.Logger, stateDir, checkpointName string) (*cl // withLock runs a function while holding the claimInfoCache lock. // It logs changes. -func (cache *claimInfoCache) withLock(f func() error) error { +func (cache *claimInfoCache) withLock(logger klog.Logger, f func() error) error { + logger = logger.WithName("dra-claiminfo") cache.Lock() defer cache.Unlock() - if loggerV := cache.logger.V(5); loggerV.Enabled() { + if loggerV := logger.V(5); loggerV.Enabled() { claimsInUseBefore := cache.claimsInUse() defer func() { claimsInUseAfter := cache.claimsInUse() @@ -188,7 +186,7 @@ func (cache *claimInfoCache) withLock(f func() error) error { } if changed { - cache.logger.V(5).Info("ResourceClaim usage changed", "claimsInUse", delta) + logger.V(5).Info("ResourceClaim usage changed", "claimsInUse", delta) } }() } diff --git a/pkg/kubelet/cm/dra/claiminfo_test.go b/pkg/kubelet/cm/dra/claiminfo_test.go index 67f34b5ef62d2..21bd1b1221beb 100644 --- a/pkg/kubelet/cm/dra/claiminfo_test.go +++ b/pkg/kubelet/cm/dra/claiminfo_test.go @@ -34,6 +34,11 @@ import ( "k8s.io/kubernetes/test/utils/ktesting/initoption" ) +// Some of these tests capture log output. Don't reduce the verbosity or they will fail! +func init() { + ktesting.SetDefaultVerbosity(5) +} + // ClaimInfo test cases const ( @@ -45,6 +50,7 @@ const ( cdiID = "test-driver/test=cdi-test-device" // CDI device ID poolName = "test-pool" requestName = "test-request" + requestName2 = "test-request-2" claimName = "test-claim" claimUID = types.UID(claimName + "-uid") podUID = "test-pod-uid" @@ -440,8 +446,7 @@ func TestNewClaimInfoCache(t *testing.T) { }, } { t.Run(test.description, func(t *testing.T) { - tCtx := ktesting.Init(t) - result, err := newClaimInfoCache(tCtx.Logger(), test.stateDir, test.checkpointName) + result, err := newClaimInfoCache(test.stateDir, test.checkpointName) if test.wantErr { assert.Error(t, err) return @@ -500,10 +505,10 @@ func TestClaimInfoCacheWithLock(t *testing.T) { } { t.Run(test.description, func(t *testing.T) { tCtx := ktesting.Init(t) - cache, err := newClaimInfoCache(tCtx.Logger(), t.TempDir(), "test-checkpoint") + cache, err := newClaimInfoCache(t.TempDir(), "test-checkpoint") require.NoError(t, err) assert.NotNil(t, cache) - err = cache.withLock(test.funcGen(cache)) + err = cache.withLock(tCtx.Logger(), test.funcGen(cache)) if test.wantErr { assert.Error(t, err) return @@ -560,8 +565,7 @@ func TestClaimInfoCacheWithRLock(t *testing.T) { }, } { t.Run(test.description, func(t *testing.T) { - tCtx := ktesting.Init(t) - cache, err := newClaimInfoCache(tCtx.Logger(), t.TempDir(), "test-checkpoint") + cache, err := newClaimInfoCache(t.TempDir(), "test-checkpoint") require.NoError(t, err) assert.NotNil(t, cache) err = cache.withRLock(test.funcGen(cache)) @@ -614,7 +618,7 @@ dra_resource_claims_in_use{driver_name=""} 1 dra_resource_claims_in_use{driver_name="other-test-driver"} 1 dra_resource_claims_in_use{driver_name="test-driver"} 1 `, - expectLog: `INFO ResourceClaim usage changed claimsInUse=< + expectLog: `INFO dra-claiminfo: ResourceClaim usage changed claimsInUse=< : 1 (+1) other-test-driver: 1 (+1) test-driver: 1 (+1) @@ -650,7 +654,7 @@ dra_resource_claims_in_use{driver_name=""} 2 dra_resource_claims_in_use{driver_name="other-test-driver"} 1 dra_resource_claims_in_use{driver_name="test-driver"} 2 `, - expectLog: `INFO ResourceClaim usage changed claimsInUse=< + expectLog: `INFO dra-claiminfo: ResourceClaim usage changed claimsInUse=< : 2 (+1) other-test-driver: 1 (+1) test-driver: 2 (+1) @@ -660,13 +664,13 @@ dra_resource_claims_in_use{driver_name="test-driver"} 2 } { t.Run(test.description, func(t *testing.T) { tCtx := ktesting.Init(t, initoption.BufferLogs(true)) - cache, err := newClaimInfoCache(tCtx.Logger(), t.TempDir(), "test-checkpoint") + cache, err := newClaimInfoCache(t.TempDir(), "test-checkpoint") for _, claimInfo := range test.initialClaimInfo { cache.add(claimInfo) } require.NoError(t, err) assert.NotNil(t, cache) - _ = cache.withLock(func() error { + _ = cache.withLock(tCtx.Logger(), func() error { cache.add(test.claimInfo) return nil }) @@ -804,7 +808,7 @@ dra_resource_claims_in_use{driver_name=""} 1 dra_resource_claims_in_use{driver_name="test-driver"} 1 dra_resource_claims_in_use{driver_name="other-test-driver"} 1 `, - expectLog: `INFO ResourceClaim usage changed claimsInUse=< + expectLog: `INFO dra-claiminfo: ResourceClaim usage changed claimsInUse=< : 1 (-1) other-test-driver: 1 (+0) test-driver: 1 (-1) @@ -822,8 +826,7 @@ dra_resource_claims_in_use{driver_name=""} 0 } { t.Run(test.description, func(t *testing.T) { tCtx := ktesting.Init(t, initoption.BufferLogs(true)) - test.claimInfoCache.logger = tCtx.Logger() - _ = test.claimInfoCache.withLock(func() error { + _ = test.claimInfoCache.withLock(tCtx.Logger(), func() error { test.claimInfoCache.delete(claimName, namespace) return nil }) @@ -881,8 +884,7 @@ func TestSyncToCheckpoint(t *testing.T) { }, } { t.Run(test.description, func(t *testing.T) { - tCtx := ktesting.Init(t) - cache, err := newClaimInfoCache(tCtx.Logger(), test.stateDir, test.checkpointName) + cache, err := newClaimInfoCache(test.stateDir, test.checkpointName) require.NoError(t, err) err = cache.syncToCheckpoint() if test.wantErr { diff --git a/pkg/kubelet/cm/dra/healthinfo.go b/pkg/kubelet/cm/dra/healthinfo.go index 47f7ef12c7b9a..68f138e88edd8 100644 --- a/pkg/kubelet/cm/dra/healthinfo.go +++ b/pkg/kubelet/cm/dra/healthinfo.go @@ -41,13 +41,14 @@ type healthInfoCache struct { } // newHealthInfoCache creates a new cache, loading from a checkpoint if present. -func newHealthInfoCache(stateFile string) (*healthInfoCache, error) { +func newHealthInfoCache(logger klog.Logger, stateFile string) (*healthInfoCache, error) { + logger = logger.WithName("dra-healthinfo") cache := &healthInfoCache{ HealthInfo: &state.DevicesHealthMap{}, stateFile: stateFile, } if err := cache.loadFromCheckpoint(); err != nil { - klog.Background().Error(err, "Failed to load health checkpoint, proceeding with empty cache") + logger.Error(err, "Failed to load health checkpoint, proceeding with empty cache") } return cache, nil } @@ -84,7 +85,8 @@ func (cache *healthInfoCache) withRLock(f func() error) error { // saveToCheckpointInternal does the actual saving without locking. // Assumes the caller holds the necessary lock. -func (cache *healthInfoCache) saveToCheckpointInternal() error { +func (cache *healthInfoCache) saveToCheckpointInternal(logger klog.Logger) error { + logger = logger.WithName("dra-healthinfo") if cache.stateFile == "" { return nil } @@ -100,7 +102,7 @@ func (cache *healthInfoCache) saveToCheckpointInternal() error { defer func() { if err := os.Remove(tempFile.Name()); err != nil && !os.IsNotExist(err) { - klog.Background().Error(err, "Failed to remove temporary checkpoint file", "path", tempFile.Name()) + logger.Error(err, "Failed to remove temporary checkpoint file", "path", tempFile.Name()) } }() @@ -121,8 +123,13 @@ func (cache *healthInfoCache) saveToCheckpointInternal() error { } // getHealthInfo returns the current health info, adjusting for timeouts. -func (cache *healthInfoCache) getHealthInfo(driverName, poolName, deviceName string) state.DeviceHealthStatus { - res := state.DeviceHealthStatusUnknown +func (cache *healthInfoCache) getHealthInfo(driverName, poolName, deviceName string) state.DeviceHealth { + res := state.DeviceHealth{ + PoolName: poolName, + DeviceName: deviceName, + Health: state.DeviceHealthStatusUnknown, + Message: "", + } _ = cache.withRLock(func() error { now := time.Now() @@ -137,9 +144,11 @@ func (cache *healthInfoCache) getHealthInfo(driverName, poolName, deviceName str // Check if device health has timed out if now.Sub(device.LastUpdated) > timeout { - res = state.DeviceHealthStatusUnknown + // Keep default Unknown status, clear message for stale device + res.Health = state.DeviceHealthStatusUnknown + res.Message = "" } else { - res = device.Health + res = device } } } @@ -151,7 +160,8 @@ func (cache *healthInfoCache) getHealthInfo(driverName, poolName, deviceName str // updateHealthInfo reconciles the cache with a fresh list of device health states // from a plugin. It identifies which devices have changed state and handles devices // that are no longer being reported by the plugin. -func (cache *healthInfoCache) updateHealthInfo(driverName string, devices []state.DeviceHealth) ([]state.DeviceHealth, error) { +func (cache *healthInfoCache) updateHealthInfo(logger klog.Logger, driverName string, devices []state.DeviceHealth) ([]state.DeviceHealth, error) { + logger = logger.WithName("dra-healthinfo") changedDevices := []state.DeviceHealth{} err := cache.withLock(func() error { now := time.Now() @@ -173,7 +183,8 @@ func (cache *healthInfoCache) updateHealthInfo(driverName string, devices []stat existingDevice, ok := currentDriver.Devices[key] - if !ok || existingDevice.Health != reportedDevice.Health || existingDevice.HealthCheckTimeout != reportedDevice.HealthCheckTimeout { + // Consider health status, message, and timeout changes as updates + if !ok || existingDevice.Health != reportedDevice.Health || existingDevice.Message != reportedDevice.Message || existingDevice.HealthCheckTimeout != reportedDevice.HealthCheckTimeout { changedDevices = append(changedDevices, reportedDevice) } @@ -194,6 +205,7 @@ func (cache *healthInfoCache) updateHealthInfo(driverName string, devices []stat // Mark as unknown if the device health has timed out if existingDevice.Health != state.DeviceHealthStatusUnknown && now.Sub(existingDevice.LastUpdated) > timeout { existingDevice.Health = state.DeviceHealthStatusUnknown + existingDevice.Message = "" existingDevice.LastUpdated = now currentDriver.Devices[key] = existingDevice @@ -204,8 +216,8 @@ func (cache *healthInfoCache) updateHealthInfo(driverName string, devices []stat // Phase 3: Persist changes to the checkpoint file if any state changed. if len(changedDevices) > 0 { - if err := cache.saveToCheckpointInternal(); err != nil { - klog.Background().Error(err, "Failed to save health checkpoint after update. Kubelet restart may lose the device health information.") + if err := cache.saveToCheckpointInternal(logger); err != nil { + logger.Error(err, "Failed to save health checkpoint after update. Kubelet restart may lose the device health information.") } } return nil @@ -218,9 +230,9 @@ func (cache *healthInfoCache) updateHealthInfo(driverName string, devices []stat } // clearDriver clears all health data for a specific driver. -func (cache *healthInfoCache) clearDriver(driverName string) error { +func (cache *healthInfoCache) clearDriver(logger klog.Logger, driverName string) error { return cache.withLock(func() error { delete(*cache.HealthInfo, driverName) - return cache.saveToCheckpointInternal() + return cache.saveToCheckpointInternal(logger) }) } diff --git a/pkg/kubelet/cm/dra/healthinfo_test.go b/pkg/kubelet/cm/dra/healthinfo_test.go index 947ef515f686d..e10df9f5599a1 100644 --- a/pkg/kubelet/cm/dra/healthinfo_test.go +++ b/pkg/kubelet/cm/dra/healthinfo_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/kubernetes/pkg/kubelet/cm/dra/state" + "k8s.io/kubernetes/test/utils/ktesting" ) const ( @@ -47,6 +48,7 @@ var ( // `TestNewHealthInfoCache tests cache creation and checkpoint loading. func TestNewHealthInfoCache(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { description string stateFile string @@ -68,7 +70,7 @@ func TestNewHealthInfoCache(t *testing.T) { require.NoError(t, err) require.NoError(t, f.Close()) } - cache, err := newHealthInfoCache(test.stateFile) + cache, err := newHealthInfoCache(logger, test.stateFile) if test.wantErr { assert.Error(t, err) return @@ -108,7 +110,8 @@ func assertDeviceHealthElementsMatchIgnoreTime(t *testing.T, expected, actual [] // TestWithLock tests the withLock method’s behavior. func TestWithLock(t *testing.T) { - cache, err := newHealthInfoCache("") + logger, _ := ktesting.NewTestContext(t) + cache, err := newHealthInfoCache(logger, "") require.NoError(t, err) tests := []struct { description string @@ -147,7 +150,8 @@ func TestWithLock(t *testing.T) { // TestWithRLock tests the withRLock method’s behavior. func TestWithRLock(t *testing.T) { - cache, err := newHealthInfoCache("") + logger, _ := ktesting.NewTestContext(t) + cache, err := newHealthInfoCache(logger, "") require.NoError(t, err) tests := []struct { description string @@ -191,16 +195,17 @@ func TestWithRLock(t *testing.T) { // TestGetHealthInfo tests retrieving health status. func TestGetHealthInfo(t *testing.T) { - cache, err := newHealthInfoCache("") + logger, _ := ktesting.NewTestContext(t) + cache, err := newHealthInfoCache(logger, "") require.NoError(t, err) // Initial state - assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice).Health) // Add a device - _, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{testDeviceHealth}) + _, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{testDeviceHealth}) require.NoError(t, err) - assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, testDevice).Health) // Test timeout (simulated with old LastUpdated) err = cache.withLock(func() error { @@ -213,7 +218,7 @@ func TestGetHealthInfo(t *testing.T) { return nil }) require.NoError(t, err) - assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice).Health) } // TestGetHealthInfoRobust tests retrieving health status logic solely & against many cases. @@ -348,7 +353,7 @@ func TestGetHealthInfoRobust(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cache := &healthInfoCache{HealthInfo: tt.initialState} - health := cache.getHealthInfo(tt.driverName, tt.poolName, tt.deviceName) + health := cache.getHealthInfo(tt.driverName, tt.poolName, tt.deviceName).Health assert.Equal(t, tt.expectedHealth, health) }) } @@ -356,20 +361,21 @@ func TestGetHealthInfoRobust(t *testing.T) { // TestUpdateHealthInfo tests adding, updating, and reconciling device health. func TestUpdateHealthInfo(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tmpFile := path.Join(t.TempDir(), "health_checkpoint_test") - cache, err := newHealthInfoCache(tmpFile) + cache, err := newHealthInfoCache(logger, tmpFile) require.NoError(t, err) // 1 -- Add new device deviceToAdd := testDeviceHealth expectedChanged1 := []state.DeviceHealth{deviceToAdd} - changedDevices, err := cache.updateHealthInfo(testDriver, []state.DeviceHealth{testDeviceHealth}) + changedDevices, err := cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{testDeviceHealth}) require.NoError(t, err) assertDeviceHealthElementsMatchIgnoreTime(t, expectedChanged1, changedDevices) - assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, testDevice).Health) // 2 -- Update with no change - changedDevices, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{testDeviceHealth}) + changedDevices, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{testDeviceHealth}) require.NoError(t, err) assert.Empty(t, changedDevices, "Scenario 2: Changed devices list should be empty") @@ -377,10 +383,10 @@ func TestUpdateHealthInfo(t *testing.T) { newHealth := testDeviceHealth newHealth.Health = state.DeviceHealthStatusUnhealthy expectedChanged3 := []state.DeviceHealth{newHealth} - changedDevices, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{newHealth}) + changedDevices, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{newHealth}) require.NoError(t, err) assertDeviceHealthElementsMatchIgnoreTime(t, expectedChanged3, changedDevices) - assert.Equal(t, state.DeviceHealthStatusUnhealthy, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusUnhealthy, cache.getHealthInfo(testDriver, testPool, testDevice).Health) // 4 -- Add second device, omit first secondDevice := state.DeviceHealth{PoolName: testPool, DeviceName: "device2", Health: state.DeviceHealthStatusHealthy, HealthCheckTimeout: DefaultHealthTimeout} @@ -399,21 +405,21 @@ func TestUpdateHealthInfo(t *testing.T) { }) require.NoError(t, err) - changedDevices, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{secondDevice}) + changedDevices, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{secondDevice}) require.NoError(t, err) assertDeviceHealthElementsMatchIgnoreTime(t, expectedChanged4, changedDevices) - assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, "device2")) - assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, "device2").Health) + assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice).Health) // 5 -- Test persistence - cache2, err := newHealthInfoCache(tmpFile) + cache2, err := newHealthInfoCache(logger, tmpFile) require.NoError(t, err) - assert.Equal(t, state.DeviceHealthStatusHealthy, cache2.getHealthInfo(testDriver, testPool, "device2")) - assert.Equal(t, state.DeviceHealthStatusUnknown, cache2.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusHealthy, cache2.getHealthInfo(testDriver, testPool, "device2").Health) + assert.Equal(t, state.DeviceHealthStatusUnknown, cache2.getHealthInfo(testDriver, testPool, testDevice).Health) // 6 -- Test how updateHealthInfo handles device timeouts timeoutDevice := state.DeviceHealth{PoolName: testPool, DeviceName: "timeoutDevice", Health: "Unhealthy", HealthCheckTimeout: DefaultHealthTimeout} - _, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{timeoutDevice}) + _, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{timeoutDevice}) require.NoError(t, err) // Manually manipulate the last updated time of timeoutDevice to seem like it surpassed healthtimeout. @@ -430,7 +436,7 @@ func TestUpdateHealthInfo(t *testing.T) { expectedTimeoutDeviceUnknown := state.DeviceHealth{PoolName: testPool, DeviceName: "timeoutDevice", Health: state.DeviceHealthStatusUnknown, HealthCheckTimeout: DefaultHealthTimeout} expectedChanged6 := []state.DeviceHealth{expectedTimeoutDeviceUnknown} - changedDevices, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{}) + changedDevices, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{}) require.NoError(t, err) assertDeviceHealthElementsMatchIgnoreTime(t, expectedChanged6, changedDevices) @@ -441,14 +447,15 @@ func TestUpdateHealthInfo(t *testing.T) { // TestClearDriver tests clearing a driver’s health data. func TestClearDriver(t *testing.T) { - cache, err := newHealthInfoCache("") + logger, _ := ktesting.NewTestContext(t) + cache, err := newHealthInfoCache(logger, "") require.NoError(t, err) - _, err = cache.updateHealthInfo(testDriver, []state.DeviceHealth{testDeviceHealth}) + _, err = cache.updateHealthInfo(logger, testDriver, []state.DeviceHealth{testDeviceHealth}) require.NoError(t, err) - assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusHealthy, cache.getHealthInfo(testDriver, testPool, testDevice).Health) - err = cache.clearDriver(testDriver) + err = cache.clearDriver(logger, testDriver) require.NoError(t, err) - assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice)) + assert.Equal(t, state.DeviceHealthStatusUnknown, cache.getHealthInfo(testDriver, testPool, testDevice).Health) } diff --git a/pkg/kubelet/cm/dra/manager.go b/pkg/kubelet/cm/dra/manager.go index d37747ec18ac4..ea468f5787785 100644 --- a/pkg/kubelet/cm/dra/manager.go +++ b/pkg/kubelet/cm/dra/manager.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "path/filepath" + "slices" "strconv" "time" @@ -74,7 +75,7 @@ const defaultWipingDelay = 30 * time.Second type ActivePodsFunc func() []*v1.Pod // GetNodeFunc is a function that returns the node object using the kubelet's node lister. -type GetNodeFunc func() (*v1.Node, error) +type GetNodeFunc func(context.Context) (*v1.Node, error) // Manager is responsible for managing ResourceClaims. // It ensures that they are prepared before starting pods @@ -118,12 +119,12 @@ type Manager struct { // - Avoid repeated "failed to ...: failed to ..." when wrapping errors. // - Avoid wrapping when it does not provide relevant additional information to keep the user-visible error short. func NewManager(logger klog.Logger, kubeClient clientset.Interface, stateFileDirectory string) (*Manager, error) { - claimInfoCache, err := newClaimInfoCache(logger, stateFileDirectory, draManagerStateFileName) + claimInfoCache, err := newClaimInfoCache(stateFileDirectory, draManagerStateFileName) if err != nil { return nil, fmt.Errorf("create ResourceClaim cache: %w", err) } - healthInfoCache, err := newHealthInfoCache(filepath.Join(stateFileDirectory, "dra_health_state")) + healthInfoCache, err := newHealthInfoCache(logger, filepath.Join(stateFileDirectory, "dra_health_state")) if err != nil { return nil, fmt.Errorf("failed to create healthInfo cache: %w", err) } @@ -172,7 +173,7 @@ func (m *Manager) initDRAPluginManager(ctx context.Context, getNode GetNodeFunc, // reconcileLoop ensures that any stale state in the manager's claimInfoCache gets periodically reconciled. func (m *Manager) reconcileLoop(ctx context.Context) { - logger := klog.FromContext(ctx) + logger := klog.FromContext(ctx).WithName("dra-manager") // Only once all sources are ready do we attempt to reconcile. // This ensures that the call to m.activePods() below will succeed with // the actual active pods list. @@ -226,7 +227,8 @@ func (m *Manager) reconcileLoop(ctx context.Context) { func (m *Manager) PrepareResources(ctx context.Context, pod *v1.Pod) error { startTime := time.Now() err := m.prepareResources(ctx, pod) - kubeletmetrics.DRAOperationsDuration.WithLabelValues("PrepareResources", strconv.FormatBool(err == nil)).Observe(time.Since(startTime).Seconds()) + isError := (err != nil) + kubeletmetrics.DRAOperationsDuration.WithLabelValues("PrepareResources", strconv.FormatBool(isError)).Observe(time.Since(startTime).Seconds()) if err != nil { return fmt.Errorf("prepare dynamic resources: %w", err) } @@ -235,7 +237,8 @@ func (m *Manager) PrepareResources(ctx context.Context, pod *v1.Pod) error { func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { var err error - logger := klog.FromContext(ctx) + logger := klog.FromContext(ctx).WithName("dra-manager") + logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod)) batches := make(map[*draplugin.DRAPlugin][]*drapb.Claim) resourceClaims := make(map[types.UID]*resourceapi.ResourceClaim) @@ -268,7 +271,7 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { for i := range podResourceClaims { podClaim := &podResourceClaims[i] infos[i].podClaim = podClaim - logger.V(3).Info("Processing resource", "pod", klog.KObj(pod), "podClaim", podClaim.Name) + logger.V(3).Info("Processing resource", "podClaim", podClaim.Name) claimName, mustCheckOwner, err := resourceclaim.Name(pod, podClaim) if err != nil { return err @@ -288,14 +291,14 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { } if mustCheckOwner { - if err = resourceclaim.IsForPod(pod, resourceClaim); err != nil { + if err = resourceclaim.IsForPod(pod, resourceClaim, m.podGroupResourceClaimsEnabled()); err != nil { // No wrapping, error is already informative. return err } } // Check if pod is in the ReservedFor for the claim - if !resourceclaim.IsReservedForPod(pod, resourceClaim) { + if !resourceclaim.IsReservedForPod(pod, resourceClaim, m.podGroupResourceClaimsEnabled()) { return fmt.Errorf("pod %s (%s) is not allowed to use ResourceClaim %s (%s)", pod.Name, pod.UID, *claimName, resourceClaim.UID) } @@ -326,12 +329,12 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { // Now that we have everything that we need, we can update the claim info cache. // Almost nothing can go wrong anymore at this point. - err = m.cache.withLock(func() error { + err = m.cache.withLock(logger, func() error { for i := range podResourceClaims { resourceClaim := infos[i].resourceClaim podClaim := infos[i].podClaim if resourceClaim == nil { - logger.V(5).Info("No need to prepare resources, no claim generated", "pod", klog.KObj(pod), "podClaim", podClaim.Name) + logger.V(5).Info("No need to prepare resources, no claim generated", "podClaim", podClaim.Name) continue } // Get a reference to the claim info for this claim from the cache. @@ -340,12 +343,12 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { if !exists { claimInfo = infos[i].claimInfo m.cache.add(claimInfo) - logger.V(6).Info("Created new claim info cache entry", "pod", klog.KObj(pod), "podClaim", podClaim.Name, "claim", klog.KObj(resourceClaim), "claimInfoEntry", claimInfo) + logger.V(6).Info("Created new claim info cache entry", "podClaim", podClaim.Name, "claim", klog.KObj(resourceClaim), "claimInfoEntry", claimInfo) } else { if claimInfo.ClaimUID != resourceClaim.UID { return fmt.Errorf("old ResourceClaim with same name %s and different UID %s still exists (previous pod force-deleted?!)", resourceClaim.Name, claimInfo.ClaimUID) } - logger.V(6).Info("Found existing claim info cache entry", "pod", klog.KObj(pod), "podClaim", podClaim.Name, "claim", klog.KObj(resourceClaim), "claimInfoEntry", claimInfo) + logger.V(6).Info("Found existing claim info cache entry", "podClaim", podClaim.Name, "claim", klog.KObj(resourceClaim), "claimInfoEntry", claimInfo) } // Add a reference to the current pod in the claim info. @@ -361,7 +364,7 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { // If this claim is already prepared, continue preparing for any remaining claims. if claimInfo.isPrepared() { - logger.V(5).Info("Resources already prepared", "pod", klog.KObj(pod), "podClaim", podClaim.Name, "claim", klog.KObj(resourceClaim)) + logger.V(5).Info("Resources already prepared", "podClaim", podClaim.Name, "claim", klog.KObj(resourceClaim)) continue } @@ -411,10 +414,10 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { claim := resourceClaims[types.UID(claimUID)] // Add the prepared CDI devices to the claim info - err := m.cache.withLock(func() error { + err := m.cache.withLock(logger, func() error { info, exists := m.cache.get(claim.Name, claim.Namespace) if !exists { - return fmt.Errorf("internal error: unable to get claim info for ResourceClaim %s", claim.Name) + return fmt.Errorf("internal error: unable to get claim info for ResourceClaim %s in namespace %s", claim.Name, claim.Namespace) } for _, device := range result.GetDevices() { info.addDevice(plugin.DriverName(), state.Device{PoolName: device.PoolName, @@ -436,12 +439,12 @@ func (m *Manager) prepareResources(ctx context.Context, pod *v1.Pod) error { } // Atomically perform some operations on the claimInfo cache. - err = m.cache.withLock(func() error { + err = m.cache.withLock(logger, func() error { // Mark all pod claims as prepared. for _, claim := range resourceClaims { info, exists := m.cache.get(claim.Name, claim.Namespace) if !exists { - return fmt.Errorf("internal error: unable to get claim info for ResourceClaim %s", claim.Name) + return fmt.Errorf("internal error: unable to get claim info for ResourceClaim %s in namespace %s", claim.Name, claim.Namespace) } info.setPrepared() } @@ -471,13 +474,40 @@ func lookupClaimRequest(claims []*drapb.Claim, claimUID string) *drapb.Claim { return nil } +// truncateHealthMessage truncates a health message to the maximum allowed length. +// If the message exceeds v1.ResourceHealthMessageMaxLength, it is truncated to +// (maxLength - 3) characters and "..." is appended. +func truncateHealthMessage(message string) string { + if len(message) <= v1.ResourceHealthMessageMaxLength { + return message + } + return message[:v1.ResourceHealthMessageMaxLength-3] + "..." +} + // GetResources gets a ContainerInfo object from the claimInfo cache. // This information is used by the caller to update a container config. func (m *Manager) GetResources(pod *v1.Pod, container *v1.Container) (*ContainerInfo, error) { cdiDevices := []kubecontainer.CDIDevice{} + // claimRequests maps claimName -> []requestNames for this container + claimRequests := make(map[string][]string) + + // Collect regular ResourceClaims + containerClaimsMap := make(map[string][]string, len(container.Resources.Claims)) + for _, claim := range container.Resources.Claims { + if _, ok := containerClaimsMap[claim.Name]; !ok { + containerClaimsMap[claim.Name] = []string{} + } + containerClaimsMap[claim.Name] = append(containerClaimsMap[claim.Name], claim.Request) + } + for i := range pod.Spec.ResourceClaims { podClaim := &pod.Spec.ResourceClaims[i] + requests, ok := containerClaimsMap[podClaim.Name] + if !ok { + continue + } + claimName, _, err := resourceclaim.Name(pod, podClaim) if err != nil { // No wrapping, error is already informative. @@ -489,61 +519,54 @@ func (m *Manager) GetResources(pod *v1.Pod, container *v1.Container) (*Container if claimName == nil { continue } - for _, claim := range container.Resources.Claims { - if podClaim.Name != claim.Name { - continue - } - err := m.cache.withRLock(func() error { - claimInfo, exists := m.cache.get(*claimName, pod.Namespace) - if !exists { - return fmt.Errorf("internal error: unable to get claim info for ResourceClaim %s", *claimName) - } - - // As of Kubernetes 1.31, CDI device IDs are not passed via annotations anymore. - cdiDevices = append(cdiDevices, claimInfo.cdiDevicesAsList(claim.Request)...) - - return nil - }) - if err != nil { - // No wrapping, this is the error above. - return nil, err - } - } + claimRequests[*claimName] = append(claimRequests[*claimName], requests...) } + // Collect extended resource claims if feature is enabled if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.DRAExtendedResource) && pod.Status.ExtendedResourceClaimStatus != nil { claimName := pod.Status.ExtendedResourceClaimStatus.ResourceClaimName // if the container has requests for extended resources backed by DRA, // they must have been allocated via the extendedResourceClaim created // by the kube-scheduler. + + // Build map of this container's extended resource requests + extendedResourceRequests := make(map[string]bool) + for rName, rValue := range container.Resources.Requests { + if !rValue.IsZero() && schedutil.IsDRAExtendedResourceName(rName) { + extendedResourceRequests[rName.String()] = true + } + } + + // Collect request names matching this container's extended resources + for _, rm := range pod.Status.ExtendedResourceClaimStatus.RequestMappings { + // allow multiple device requests per container per resource. + if rm.ContainerName == container.Name && extendedResourceRequests[rm.ResourceName] { + claimRequests[claimName] = append(claimRequests[claimName], rm.RequestName) + } + } + } + + // Process all collected claims + for claimName, requestNames := range claimRequests { err := m.cache.withRLock(func() error { claimInfo, exists := m.cache.get(claimName, pod.Namespace) if !exists { - return fmt.Errorf("unable to get claim info for claim %s in namespace %s", claimName, pod.Namespace) + return fmt.Errorf("internal error: unable to get claim info for ResourceClaim %s in namespace %s", claimName, pod.Namespace) } - for rName, rValue := range container.Resources.Requests { - if rValue.IsZero() { - // We only care about the resources requested by the pod - continue - } - if schedutil.IsDRAExtendedResourceName(rName) { - for _, rm := range pod.Status.ExtendedResourceClaimStatus.RequestMappings { - // allow multiple device requests per container per resource. - if rm.ContainerName == container.Name && rm.ResourceName == rName.String() { - // As of Kubernetes 1.31, CDI device IDs are not passed via annotations anymore. - cdiDevices = append(cdiDevices, claimInfo.cdiDevicesAsList(rm.RequestName)...) - } - } - } + for _, requestName := range requestNames { + // As of Kubernetes 1.31, CDI device IDs are not passed via annotations anymore. + cdiDevices = append(cdiDevices, claimInfo.cdiDevicesAsList(requestName)...) } return nil }) if err != nil { + // No wrapping, this is the error above. return nil, err } } + return &ContainerInfo{CDIDevices: cdiDevices}, nil } @@ -554,7 +577,8 @@ func (m *Manager) GetResources(pod *v1.Pod, container *v1.Container) (*Container func (m *Manager) UnprepareResources(ctx context.Context, pod *v1.Pod) error { startTime := time.Now() err := m.unprepareResourcesForPod(ctx, pod) - kubeletmetrics.DRAOperationsDuration.WithLabelValues("UnprepareResources", strconv.FormatBool(err == nil)).Observe(time.Since(startTime).Seconds()) + isError := (err != nil) + kubeletmetrics.DRAOperationsDuration.WithLabelValues("UnprepareResources", strconv.FormatBool(isError)).Observe(time.Since(startTime).Seconds()) if err != nil { return fmt.Errorf("unprepare dynamic resources: %w", err) } @@ -587,12 +611,12 @@ func (m *Manager) unprepareResourcesForPod(ctx context.Context, pod *v1.Pod) err } func (m *Manager) unprepareResources(ctx context.Context, podUID types.UID, namespace string, claimNames []string) error { - logger := klog.FromContext(ctx) + logger := klog.FromContext(ctx).WithName("dra-manager") batches := make(map[string][]*drapb.Claim) claimNamesMap := make(map[types.UID]string) for _, claimName := range claimNames { // Atomically perform some operations on the claimInfo cache. - err := m.cache.withLock(func() error { + err := m.cache.withLock(logger, func() error { // Get the claim info from the cache claimInfo, exists := m.cache.get(claimName, namespace) @@ -668,7 +692,7 @@ func (m *Manager) unprepareResources(ctx context.Context, podUID types.UID, name } // Atomically perform some operations on the claimInfo cache. - err := m.cache.withLock(func() error { + err := m.cache.withLock(logger, func() error { // TODO(#132978): Re-evaluate this logic to support post-mortem health updates. // As of the initial implementation, we immediately delete the claim info upon // unprepare. This means a late-arriving health update for a terminated pod @@ -708,7 +732,18 @@ func (m *Manager) PodMightNeedToUnprepareResources(uid types.UID) bool { func (m *Manager) GetContainerClaimInfos(pod *v1.Pod, container *v1.Container) ([]*ClaimInfo, error) { claimInfos := make([]*ClaimInfo, 0, len(pod.Spec.ResourceClaims)) + // Build a map of container claims for O(1) lookup + containerClaimsMap := make(map[string]bool, len(container.Resources.Claims)) + for _, claim := range container.Resources.Claims { + containerClaimsMap[claim.Name] = true + } + for i, podResourceClaim := range pod.Spec.ResourceClaims { + // Only process claims that this container actually uses + if !containerClaimsMap[podResourceClaim.Name] { + continue + } + claimName, _, err := resourceclaim.Name(pod, &pod.Spec.ResourceClaims[i]) if err != nil { // No wrapping, the error is already informative. @@ -722,23 +757,17 @@ func (m *Manager) GetContainerClaimInfos(pod *v1.Pod, container *v1.Container) ( // Ownership doesn't get checked here, this should have been done before. - for _, claim := range container.Resources.Claims { - if podResourceClaim.Name != claim.Name { - continue - } - - err := m.cache.withRLock(func() error { - claimInfo, exists := m.cache.get(*claimName, pod.Namespace) - if !exists { - return fmt.Errorf("unable to get information for ResourceClaim %s", *claimName) - } - claimInfos = append(claimInfos, claimInfo.DeepCopy()) - return nil - }) - if err != nil { - // No wrapping, this is the error above. - return nil, err + err = m.cache.withRLock(func() error { + claimInfo, exists := m.cache.get(*claimName, pod.Namespace) + if !exists { + return fmt.Errorf("unable to get information for ResourceClaim %s", *claimName) } + claimInfos = append(claimInfos, claimInfo.DeepCopy()) + return nil + }) + if err != nil { + // No wrapping, this is the error above. + return nil, err } } @@ -775,7 +804,9 @@ func (m *Manager) GetContainerClaimInfos(pod *v1.Pod, container *v1.Container) ( // UpdateAllocatedResourcesStatus updates the health status of allocated DRA resources in the pod's container statuses. func (m *Manager) UpdateAllocatedResourcesStatus(pod *v1.Pod, status *v1.PodStatus) { - logger := klog.FromContext(context.Background()) + logger := klog.FromContext(context.Background()).WithName("dra-manager") + logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod)) + enableHealthMessage := utilfeature.DefaultFeatureGate.Enabled(kubefeatures.ResourceHealthStatusMessage) for i := range status.ContainerStatuses { containerStatus := &status.ContainerStatuses[i] @@ -800,21 +831,37 @@ func (m *Manager) UpdateAllocatedResourcesStatus(pod *v1.Pod, status *v1.PodStat } } + // Build a map of pod claim statuses for O(1) lookup + podClaimStatusMap := make(map[string]string, len(pod.Status.ResourceClaimStatuses)) + for _, podClaimStatus := range pod.Status.ResourceClaimStatuses { + if podClaimStatus.ResourceClaimName != nil { + podClaimStatusMap[podClaimStatus.Name] = *podClaimStatus.ResourceClaimName + } + } + // Iterate through the claims requested by this specific container. for _, claim := range containerSpec.Resources.Claims { // Find the actual name of the ResourceClaim object. - var actualClaimName string - for _, podClaimStatus := range pod.Status.ResourceClaimStatuses { - if podClaimStatus.Name == claim.Name { - if podClaimStatus.ResourceClaimName != nil { - actualClaimName = *podClaimStatus.ResourceClaimName + // Try the O(1) map lookup first, then fall back to resolving + // from the pod spec. The fallback is needed because + // ResourceClaimStatuses may not be populated yet when a + // health update triggers an early status sync. + actualClaimName := podClaimStatusMap[claim.Name] + + if actualClaimName == "" { + for i := range pod.Spec.ResourceClaims { + if pod.Spec.ResourceClaims[i].Name == claim.Name { + claimNamePtr, _, err := resourceclaim.Name(pod, &pod.Spec.ResourceClaims[i]) + if err == nil && claimNamePtr != nil { + actualClaimName = *claimNamePtr + } + break } - break } } if actualClaimName == "" { - logger.V(4).Info("Could not find generated name for resource claim in pod status", "pod", klog.KObj(pod), "container", containerSpec.Name, "claimName", claim.Name) + logger.V(4).Info("Could not find generated name for resource claim", "container", containerSpec.Name, "claimName", claim.Name) continue } @@ -825,7 +872,7 @@ func (m *Manager) UpdateAllocatedResourcesStatus(pod *v1.Pod, status *v1.PodStat // Use the actual claim name to look up claim info. claimInfo, exists := m.cache.get(actualClaimName, pod.Namespace) if !exists { - logger.V(4).Info("Could not find claim info for resource claim", "pod", klog.KObj(pod), "container", containerSpec.Name, "claimName", actualClaimName) + logger.V(4).Info("Could not find claim info for resource claim", "container", containerSpec.Name, "claimName", actualClaimName) return } @@ -852,26 +899,34 @@ func (m *Manager) UpdateAllocatedResourcesStatus(pod *v1.Pod, status *v1.PodStat // Clear previous health entries before adding current ones. resStatus.Resources = []v1.ResourceHealth{} + // Use a map to track resourceIDs we've already added to avoid duplicates. + // Multiple devices in claimInfo.DriverState may have the same resourceID + // (e.g., when using the same CDI device ID), so we need to deduplicate. + seenResourceIDs := make(map[v1.ResourceID]bool) + for driverName, driverState := range claimInfo.DriverState { for _, device := range driverState.Devices { - healthStr := m.healthInfoCache.getHealthInfo(driverName, device.PoolName, device.DeviceName) - - var health v1.ResourceHealthStatus - switch healthStr { - case "Healthy": - health = v1.ResourceHealthStatusHealthy - case "Unhealthy": - health = v1.ResourceHealthStatusUnhealthy - default: - health = v1.ResourceHealthStatusUnknown + // Skip devices that don't match the request we're reporting for. + // Use BaseRequestRef to handle subrequests (e.g., "request/subrequest" -> "request") + // since device.RequestNames are stored with subrequest suffixes stripped. + if claim.Request != "" && len(device.RequestNames) > 0 && !slices.Contains(device.RequestNames, resourceclaim.BaseRequestRef(claim.Request)) { + continue } - resourceHealth := v1.ResourceHealth{Health: health} - if len(device.CDIDeviceIDs) > 0 { - resourceHealth.ResourceID = v1.ResourceID(device.CDIDeviceIDs[0]) - } else { - resourceHealth.ResourceID = v1.ResourceID(fmt.Sprintf("%s/%s/%s", driverName, device.PoolName, device.DeviceName)) + healthInfo := m.healthInfoCache.getHealthInfo(driverName, device.PoolName, device.DeviceName) + resourceHealth := buildResourceHealth( + driverName, + device, + healthInfo, + enableHealthMessage, + ) + + // Skip if we've already added this resourceID + if seenResourceIDs[resourceHealth.ResourceID] { + continue } + seenResourceIDs[resourceHealth.ResourceID] = true + resStatus.Resources = append(resStatus.Resources, resourceHealth) } } @@ -889,15 +944,80 @@ func (m *Manager) UpdateAllocatedResourcesStatus(pod *v1.Pod, status *v1.PodStat } } +func toResourceHealthStatus(health state.DeviceHealthStatus) v1.ResourceHealthStatus { + switch health { + case state.DeviceHealthStatusHealthy: + return v1.ResourceHealthStatusHealthy + case state.DeviceHealthStatusUnhealthy: + return v1.ResourceHealthStatusUnhealthy + default: + return v1.ResourceHealthStatusUnknown + } +} + +func buildResourceHealth(driverName string, device state.Device, healthInfo state.DeviceHealth, enableMessage bool) v1.ResourceHealth { + // Create the ResourceHealth entry + resourceHealth := v1.ResourceHealth{ + Health: toResourceHealthStatus(healthInfo.Health), + } + if enableMessage && len(healthInfo.Message) > 0 { + resourceHealth.Message = &healthInfo.Message + } + // Use first CDI device ID as ResourceID, with fallback + if len(device.CDIDeviceIDs) > 0 { + resourceHealth.ResourceID = v1.ResourceID(device.CDIDeviceIDs[0]) + } else { + resourceHealth.ResourceID = v1.ResourceID(fmt.Sprintf("%s/%s/%s", driverName, device.PoolName, device.DeviceName)) + } + return resourceHealth +} + +func toDeviceHealthStatus(health drahealthv1alpha1.HealthStatus) state.DeviceHealthStatus { + switch health { + case drahealthv1alpha1.HealthStatus_HEALTHY: + return state.DeviceHealthStatusHealthy + case drahealthv1alpha1.HealthStatus_UNHEALTHY: + return state.DeviceHealthStatusUnhealthy + default: + return state.DeviceHealthStatusUnknown + } +} + +func buildDeviceHealth(logger klog.Logger, device *drahealthv1alpha1.DeviceHealth) state.DeviceHealth { + // Extract the health check timeout from the gRPC response + // If not specified, zero, or negative, use the default timeout + timeout := DefaultHealthTimeout + timeoutSeconds := device.GetHealthCheckTimeoutSeconds() + if timeoutSeconds > 0 { + timeout = time.Duration(timeoutSeconds) * time.Second + } else if timeoutSeconds < 0 { + // Log warning for negative timeout values and use default + logger.V(4).Info("Ignoring negative health check timeout, using default", + "poolName", device.GetDevice().GetPoolName(), + "deviceName", device.GetDevice().GetDeviceName(), + "providedTimeout", timeoutSeconds, + "defaultTimeout", DefaultHealthTimeout) + } + return state.DeviceHealth{ + PoolName: device.GetDevice().GetPoolName(), + DeviceName: device.GetDevice().GetDeviceName(), + Health: toDeviceHealthStatus(device.GetHealth()), + LastUpdated: time.Unix(device.GetLastUpdatedTime(), 0), + HealthCheckTimeout: timeout, + Message: truncateHealthMessage(device.GetMessage()), + } +} + // HandleWatchResourcesStream processes health updates from the DRA plugin. func (m *Manager) HandleWatchResourcesStream(ctx context.Context, stream drahealthv1alpha1.DRAResourceHealth_NodeWatchResourcesClient, pluginName string) error { - logger := klog.FromContext(ctx) + logger := klog.FromContext(ctx).WithName("dra-manager") + logger = klog.LoggerWithValues(logger, "pluginName", pluginName) defer func() { - logger.V(4).Info("Clearing health cache for driver upon stream exit", "pluginName", pluginName) + logger.V(4).Info("Clearing health cache for driver upon stream exit") // Use a separate context for clearDriver if needed, though background should be fine. - if err := m.healthInfoCache.clearDriver(pluginName); err != nil { - logger.Error(err, "Failed to clear health info cache for driver", "pluginName", pluginName) + if err := m.healthInfoCache.clearDriver(logger, pluginName); err != nil { + logger.Error(err, "Failed to clear health info cache for driver") } }() @@ -906,63 +1026,31 @@ func (m *Manager) HandleWatchResourcesStream(ctx context.Context, stream draheal if err != nil { // Context canceled, normal shutdown. if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - logger.V(4).Info("Stopping health monitoring due to context cancellation", "pluginName", pluginName, "reason", err) + logger.V(4).Info("Stopping health monitoring due to context cancellation", "reason", err) return err } // Stream closed cleanly by the server, get normal EOF. if errors.Is(err, io.EOF) { - logger.V(4).Info("Stream ended with EOF", "pluginName", pluginName) + logger.V(4).Info("Stream ended with EOF") return nil } // Other errors are unexpected, log & return. - logger.Error(err, "Error receiving from WatchResources stream", "pluginName", pluginName) + logger.Error(err, "Error receiving from WatchResources stream") return err } // Convert drahealthv1alpha1.DeviceHealth to state.DeviceHealth devices := make([]state.DeviceHealth, len(resp.GetDevices())) for i, d := range resp.GetDevices() { - var health state.DeviceHealthStatus - switch d.GetHealth() { - case drahealthv1alpha1.HealthStatus_HEALTHY: - health = state.DeviceHealthStatusHealthy - case drahealthv1alpha1.HealthStatus_UNHEALTHY: - health = state.DeviceHealthStatusUnhealthy - default: - health = state.DeviceHealthStatusUnknown - } - - // Extract the health check timeout from the gRPC response - // If not specified, zero, or negative, use the default timeout - timeout := DefaultHealthTimeout - timeoutSeconds := d.GetHealthCheckTimeoutSeconds() - if timeoutSeconds > 0 { - timeout = time.Duration(timeoutSeconds) * time.Second - } else if timeoutSeconds < 0 { - // Log warning for negative timeout values and use default - logger.V(4).Info("Ignoring negative health check timeout, using default", - "pluginName", pluginName, - "poolName", d.GetDevice().GetPoolName(), - "deviceName", d.GetDevice().GetDeviceName(), - "providedTimeout", timeoutSeconds, - "defaultTimeout", DefaultHealthTimeout) - } - - devices[i] = state.DeviceHealth{ - PoolName: d.GetDevice().GetPoolName(), - DeviceName: d.GetDevice().GetDeviceName(), - Health: health, - LastUpdated: time.Unix(d.GetLastUpdatedTime(), 0), - HealthCheckTimeout: timeout, - } - } - - changedDevices, updateErr := m.healthInfoCache.updateHealthInfo(pluginName, devices) + devices[i] = buildDeviceHealth(logger, d) + } + + changedDevices, updateErr := m.healthInfoCache.updateHealthInfo(logger, pluginName, devices) if updateErr != nil { - logger.Error(updateErr, "Failed to update health info cache", "pluginName", pluginName) + logger.Error(updateErr, "Failed to update health info cache") } if len(changedDevices) > 0 { - logger.V(4).Info("Health info changed, checking affected pods", "pluginName", pluginName, "changedDevicesCount", len(changedDevices)) + logger.V(4).Info("Health info changed, checking affected pods", "changedDevicesCount", len(changedDevices)) podsToUpdate := sets.New[string]() @@ -983,14 +1071,14 @@ func (m *Manager) HandleWatchResourcesStream(ctx context.Context, stream draheal if podsToUpdate.Len() > 0 { podUIDs := podsToUpdate.UnsortedList() - logger.Info("Sending health update notification for pods", "pluginName", pluginName, "pods", podUIDs) + logger.Info("Sending health update notification for pods", "pods", podUIDs) select { case m.update <- resourceupdates.Update{PodUIDs: podUIDs}: default: - logger.Error(nil, "DRA health update channel is full, discarding pod update notification", "pluginName", pluginName, "pods", podUIDs) + logger.Error(nil, "DRA health update channel is full, discarding pod update notification", "pods", podUIDs) } } else { - logger.V(4).Info("Health info changed, but no active pods found using the affected devices", "pluginName", pluginName) + logger.V(4).Info("Health info changed, but no active pods found using the affected devices") } } @@ -1002,3 +1090,7 @@ func (m *Manager) Updates() <-chan resourceupdates.Update { // Return the internal channel that HandleWatchResourcesStream writes to. return m.update } + +func (*Manager) podGroupResourceClaimsEnabled() bool { + return utilfeature.DefaultFeatureGate.Enabled(kubefeatures.DRAWorkloadResourceClaims) +} diff --git a/pkg/kubelet/cm/dra/manager_test.go b/pkg/kubelet/cm/dra/manager_test.go index 209a30becf245..4863f5ae548c0 100644 --- a/pkg/kubelet/cm/dra/manager_test.go +++ b/pkg/kubelet/cm/dra/manager_test.go @@ -24,6 +24,7 @@ import ( "net" "os" "path/filepath" + "strings" "sync" "sync/atomic" "testing" @@ -37,6 +38,7 @@ import ( v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -44,8 +46,12 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" featuregatetesting "k8s.io/component-base/featuregate/testing" + metricstestutil "k8s.io/component-base/metrics/testutil" "k8s.io/dynamic-resource-allocation/resourceclaim" "k8s.io/klog/v2" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics" + "k8s.io/utils/ptr" drahealthv1alpha1 "k8s.io/kubelet/pkg/apis/dra-health/v1alpha1" drapb "k8s.io/kubelet/pkg/apis/dra/v1" @@ -59,6 +65,7 @@ const ( driverClassName = "test" podName = "test-pod" containerName = "test-container" + podGroupName = "test-podgroup" ) var ( @@ -81,6 +88,22 @@ type fakeDRADriverGRPCServer struct { watchResourcesError error } +var gatherWithoutDurations = metricstestutil.GathererFunc(func() ([]*metricstestutil.MetricFamily, error) { + got, err := kubeletmetrics.GetGather().Gather() + for _, mf := range got { + + for _, m := range mf.Metric { + if m.Histogram == nil { + continue + } + // Remove everything from a histogram that depends on timing. + m.Histogram.SampleSum = nil + m.Histogram.Bucket = nil + } + } + return got, err +}) + func (s *fakeDRADriverGRPCServer) NodePrepareResources(ctx context.Context, req *drapb.NodePrepareResourcesRequest) (*drapb.NodePrepareResourcesResponse, error) { s.prepareResourceCalls.Add(1) @@ -436,6 +459,38 @@ func genTestPodWithExtendedResource() *v1.Pod { } } +func genTestPodWithPodGroup() *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: namespace, + UID: podUID, + }, + Spec: v1.PodSpec{ + SchedulingGroup: &v1.PodSchedulingGroup{ + PodGroupName: new(podGroupName), + }, + ResourceClaims: []v1.PodResourceClaim{ + { + Name: claimName, + ResourceClaimName: new(claimName), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Claims: []v1.ResourceClaim{ + { + Name: claimName, + }, + }, + }, + }, + }, + }, + } +} + // genTestClaim generates resource claim object func genTestClaim(name, driver, device, podUID string) *resourceapi.ResourceClaim { return &resourceapi.ResourceClaim{ @@ -516,8 +571,51 @@ func genTestClaimWithExtendedResource(name, driver, device, podUID string) *reso } } +func genTestPodGroupClaim(name, driver, device, podGroupName string) *resourceapi.ResourceClaim { + return &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: types.UID(fmt.Sprintf("%s-uid", name)), + }, + Spec: resourceapi.ResourceClaimSpec{ + Devices: resourceapi.DeviceClaim{ + Requests: []resourceapi.DeviceRequest{ + { + Name: requestName, + Exactly: &resourceapi.ExactDeviceRequest{ + DeviceClassName: className, + }, + }, + }, + }, + }, + Status: resourceapi.ResourceClaimStatus{ + Allocation: &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{ + { + Request: requestName, + Pool: poolName, + Device: device, + Driver: driver, + }, + }, + }, + }, + ReservedFor: []resourceapi.ResourceClaimConsumerReference{ + { + APIGroup: schedulingapi.GroupName, + Resource: "podgroups", + Name: podGroupName, + }, + }, + }, + } +} + // genTestClaimInfo generates claim info object -func genTestClaimInfo(claimUID types.UID, podUIDs []string, prepared bool) *ClaimInfo { +func genTestClaimInfo(claimUID types.UID, podUIDs []string, prepared bool, shareID *types.UID) *ClaimInfo { return &ClaimInfo{ ClaimInfoState: state.ClaimInfoState{ ClaimUID: claimUID, @@ -529,6 +627,7 @@ func genTestClaimInfo(claimUID types.UID, podUIDs []string, prepared bool) *Clai Devices: []state.Device{{ PoolName: poolName, DeviceName: deviceName, + ShareID: shareID, RequestNames: []string{requestName}, CDIDeviceIDs: []string{cdiID}, }}, @@ -617,11 +716,12 @@ func TestGetResources(t *testing.T) { kubeClient := fake.NewSimpleClientset() for _, test := range []struct { - description string - container *v1.Container - pod *v1.Pod - claimInfo *ClaimInfo - wantErr bool + description string + container *v1.Container + pod *v1.Pod + claimInfo *ClaimInfo + wantErr bool + wantCDIDevices []kubecontainer.CDIDevice }{ { description: "claim info with devices", @@ -635,8 +735,9 @@ func TestGetResources(t *testing.T) { }, }, }, - pod: genTestPod(), - claimInfo: genTestClaimInfo(claimUID, nil, false), + pod: genTestPod(), + claimInfo: genTestClaimInfo(claimUID, nil, false, nil), + wantCDIDevices: []kubecontainer.CDIDevice{{Name: cdiID}}, }, { description: "nil claiminfo", @@ -663,8 +764,47 @@ func TestGetResources(t *testing.T) { }, }, }, - pod: genTestPodWithExtendedResource(), - claimInfo: genTestClaimInfoWithExtendedResource(nil, false), + pod: genTestPodWithExtendedResource(), + claimInfo: genTestClaimInfoWithExtendedResource(nil, false), + wantCDIDevices: []kubecontainer.CDIDevice{{Name: cdiID}}, + }, + { + description: "same claim referenced by multiple requests", + container: &v1.Container{ + Name: containerName, + Resources: v1.ResourceRequirements{ + Claims: []v1.ResourceClaim{ + { + Name: claimName, + Request: requestName, + }, + { + Name: claimName, + Request: requestName2, + }, + }, + }, + }, + pod: genTestPod(), + claimInfo: &ClaimInfo{ + ClaimInfoState: state.ClaimInfoState{ + ClaimUID: claimUID, + ClaimName: claimName, + Namespace: namespace, + PodUIDs: sets.New[string](), + DriverState: map[string]state.DriverState{ + driverName: { + Devices: []state.Device{{ + PoolName: poolName, + DeviceName: deviceName, + RequestNames: []string{requestName, requestName2}, + CDIDeviceIDs: []string{cdiID}, + }}, + }, + }, + }, + }, + wantCDIDevices: []kubecontainer.CDIDevice{{Name: cdiID}, {Name: cdiID}}, }, } { t.Run(test.description, func(t *testing.T) { @@ -682,13 +822,13 @@ func TestGetResources(t *testing.T) { assert.Error(t, err) } else { require.NoError(t, err) - assert.Equal(t, test.claimInfo.DriverState[driverName].Devices[0].CDIDeviceIDs[0], containerInfo.CDIDevices[0].Name) + assert.Equal(t, test.wantCDIDevices, containerInfo.CDIDevices) } }) } } -func getFakeNode() (*v1.Node, error) { +func getFakeNode(context.Context) (*v1.Node, error) { return &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "worker"}}, nil } @@ -708,21 +848,36 @@ func TestPrepareResources(t *testing.T) { wantTimeout bool wantResourceSkipped bool + draWorkloadResourceClaimsEnabled bool + expectedErrMsg string expectedClaimInfoState state.ClaimInfoState expectedPrepareCalls uint32 + expectedMetric string }{ { description: "claim doesn't exist", driverName: driverName, pod: genTestPod(), expectedErrMsg: "fetch ResourceClaim ", + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "unknown driver", pod: genTestPod(), claim: genTestClaim(claimName, "unknown driver", deviceName, podUID), expectedErrMsg: "prepare dynamic resources: DRA driver unknown driver is not registered", + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "should prepare resources, driver returns nil value", @@ -732,6 +887,17 @@ func TestPrepareResources(t *testing.T) { resp: &drapb.NodePrepareResourcesResponse{Claims: map[string]*drapb.NodePrepareResourceResponse{string(claimUID): nil}}, expectedClaimInfoState: genClaimInfoState(""), expectedPrepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, }, { description: "driver returns empty result", @@ -741,6 +907,17 @@ func TestPrepareResources(t *testing.T) { resp: &drapb.NodePrepareResourcesResponse{Claims: map[string]*drapb.NodePrepareResourceResponse{}}, expectedPrepareCalls: 1, expectedErrMsg: "NodePrepareResources skipped 1 ResourceClaims", + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "pod is not allowed to use resource claim", @@ -748,6 +925,26 @@ func TestPrepareResources(t *testing.T) { pod: genTestPod(), claim: genTestClaim(claimName, driverName, deviceName, ""), expectedErrMsg: "is not allowed to use ResourceClaim ", + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, + }, + { + description: "pod is not allowed to use resource claim from podgroup", + draWorkloadResourceClaimsEnabled: true, + driverName: driverName, + pod: genTestPodWithPodGroup(), + claim: genTestPodGroupClaim(claimName, driverName, deviceName, ""), + expectedErrMsg: "is not allowed to use ResourceClaim ", + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "no container uses the claim", @@ -770,8 +967,19 @@ func TestPrepareResources(t *testing.T) { }, }, }, - claim: genTestClaim(claimName, driverName, deviceName, podUID), - expectedPrepareCalls: 1, + claim: genTestClaim(claimName, driverName, deviceName, podUID), + expectedPrepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, expectedClaimInfoState: genClaimInfoState(cdiID), resp: genPrepareResourcesResponse(claimUID), }, @@ -780,9 +988,15 @@ func TestPrepareResources(t *testing.T) { driverName: driverName, pod: genTestPod(), claim: genTestClaim(claimName, driverName, deviceName, podUID), - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true, nil), expectedClaimInfoState: genClaimInfoState(cdiID), resp: genPrepareResourcesResponse(claimUID), + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, }, { description: "should timeout", @@ -792,6 +1006,17 @@ func TestPrepareResources(t *testing.T) { wantTimeout: true, expectedPrepareCalls: 1, expectedErrMsg: "NodePrepareResources: rpc error: code = DeadlineExceeded", + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="DeadlineExceeded",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="DeadlineExceeded",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="DeadlineExceeded",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "should prepare resource, claim not in cache", @@ -801,6 +1026,17 @@ func TestPrepareResources(t *testing.T) { expectedClaimInfoState: genClaimInfoState(cdiID), resp: genPrepareResourcesResponse(claimUID), expectedPrepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, }, { description: "should prepare extended resource claim backed by DRA", @@ -821,14 +1057,69 @@ func TestPrepareResources(t *testing.T) { }, }}, expectedPrepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, + }, + { + description: "should prepare resource for podgroup, claim not in cache", + draWorkloadResourceClaimsEnabled: true, + driverName: driverName, + pod: genTestPodWithPodGroup(), + claim: genTestPodGroupClaim(claimName, driverName, deviceName, podGroupName), + expectedClaimInfoState: genClaimInfoState(cdiID), + resp: genPrepareResourcesResponse(claimUID), + expectedPrepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, + }, + { + description: "should fail to prepare resource for podgroup when the feature is disabled", + draWorkloadResourceClaimsEnabled: false, + driverName: driverName, + pod: genTestPodWithPodGroup(), + claim: genTestPodGroupClaim(claimName, driverName, deviceName, podGroupName), + expectedClaimInfoState: genClaimInfoState(cdiID), + resp: genPrepareResourcesResponse(claimUID), + expectedPrepareCalls: 0, + expectedErrMsg: "is not allowed to use ResourceClaim ", + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "claim UIDs mismatch", driverName: driverName, pod: genTestPod(), claim: genTestClaim(claimName, driverName, deviceName, podUID), - claimInfo: genTestClaimInfo(anotherClaimUID, []string{podUID}, false), + claimInfo: genTestClaimInfo(anotherClaimUID, []string{podUID}, false, nil), expectedErrMsg: fmt.Sprintf("old ResourceClaim with same name %s and different UID %s still exists", claimName, anotherClaimUID), + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareResources"} 1 +`, }, { description: "should prepare resources with share id", @@ -854,13 +1145,37 @@ func TestPrepareResources(t *testing.T) { }, }}, expectedPrepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 +`, }, } { t.Run(test.description, func(t *testing.T) { - backgroundCtx, cancel := context.WithCancel(context.Background()) + tCtx := ktesting.Init(t) + backgroundCtx, cancel := context.WithCancel(tCtx) defer cancel() - tCtx := ktesting.Init(t) + kubeletmetrics.Register() + kubeletmetrics.DRAOperationsDuration.Reset() + kubeletmetrics.DRAGRPCOperationsDuration.Reset() + defer func() { + require.NoError(t, + metricstestutil.GatherAndCompare(gatherWithoutDurations, + strings.NewReader(test.expectedMetric), + kubeletmetrics.DRAOperationsDuration.FQName(), + kubeletmetrics.DRAGRPCOperationsDuration.FQName(), + ), + ) + }() + backgroundCtx = klog.NewContext(backgroundCtx, tCtx.Logger()) manager, err := NewManager(tCtx.Logger(), fakeKubeClient, t.TempDir()) @@ -896,8 +1211,12 @@ func TestPrepareResources(t *testing.T) { manager.cache.add(test.claimInfo) } - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, true) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAConsumableCapacity, true) + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRAExtendedResource: true, + features.DRAConsumableCapacity: true, + features.DRAWorkloadResourceClaims: test.draWorkloadResourceClaimsEnabled, + features.GenericWorkload: test.draWorkloadResourceClaimsEnabled, // dependency of DRAWorkloadResourceClaims + }) err = manager.PrepareResources(backgroundCtx, test.pod) assert.Equal(t, test.expectedPrepareCalls, draServerInfo.server.prepareResourceCalls.Load()) @@ -1019,6 +1338,7 @@ func TestUnprepareResources(t *testing.T) { expectedUnprepareCalls uint32 expectedErrMsg string + expectedMetric string }{ { description: "unknown driver", @@ -1045,31 +1365,65 @@ func TestUnprepareResources(t *testing.T) { }, expectedErrMsg: "unprepare dynamic resources: DRA driver unknown-driver is not registered", expectedUnprepareCalls: 0, + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="UnprepareResources"} 1 +`, }, { description: "resource claim referenced by other pod(s)", driverName: driverName, pod: genTestPod(), - claimInfo: genTestClaimInfo(claimUID, []string{podUID, "another-pod-uid"}, true), + claimInfo: genTestClaimInfo(claimUID, []string{podUID, "another-pod-uid"}, true, nil), wantResourceSkipped: true, + expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="UnprepareResources"} 1 +`, }, { description: "should timeout", driverName: driverName, pod: genTestPod(), - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true, nil), wantTimeout: true, expectedUnprepareCalls: 1, expectedErrMsg: "NodeUnprepareResources: rpc error: code = DeadlineExceeded", + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="DeadlineExceeded",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="DeadlineExceeded",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="DeadlineExceeded",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="UnprepareResources"} 1 +`, }, { description: "should fail when driver returns empty response", driverName: driverName, pod: genTestPod(), - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true, nil), resp: &drapb.NodeUnprepareResourcesResponse{Claims: map[string]*drapb.NodeUnprepareResourceResponse{}}, expectedUnprepareCalls: 1, expectedErrMsg: "NodeUnprepareResources skipped 1 ResourceClaims", + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="true",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="true",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="true",operation_name="UnprepareResources"} 1 +`, }, { description: "should unprepare already prepared extended resource backed by DRA", @@ -1078,30 +1432,72 @@ func TestUnprepareResources(t *testing.T) { claim: genTestClaimWithExtendedResource(claimName, driverName, deviceName, podUID), claimInfo: genTestClaimInfoWithExtendedResource([]string{podUID}, true), expectedUnprepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="UnprepareResources"} 1 +`, }, { description: "should unprepare already prepared resource", driverName: driverName, pod: genTestPod(), claim: genTestClaim(claimName, driverName, deviceName, podUID), - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true, nil), expectedUnprepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="UnprepareResources"} 1 +`, }, { description: "should unprepare resource when driver returns nil value", driverName: driverName, pod: genTestPod(), - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true, nil), resp: &drapb.NodeUnprepareResourcesResponse{Claims: map[string]*drapb.NodeUnprepareResourceResponse{string(claimUID): nil}}, expectedUnprepareCalls: 1, + expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations +# TYPE dra_grpc_operations_duration_seconds histogram +dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources",le="+Inf"} 1 +dra_grpc_operations_duration_seconds_sum{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 0 +dra_grpc_operations_duration_seconds_count{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodeUnprepareResources"} 1 +# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. +# TYPE dra_operations_duration_seconds histogram +dra_operations_duration_seconds_bucket{is_error="false",operation_name="UnprepareResources",le="+Inf"} 1 +dra_operations_duration_seconds_sum{is_error="false",operation_name="UnprepareResources"} 0 +dra_operations_duration_seconds_count{is_error="false",operation_name="UnprepareResources"} 1 +`, }, } { t.Run(test.description, func(t *testing.T) { - backgroundCtx, cancel := context.WithCancel(context.Background()) - defer cancel() - tCtx := ktesting.Init(t) - backgroundCtx = klog.NewContext(backgroundCtx, tCtx.Logger()) + + kubeletmetrics.Register() + kubeletmetrics.DRAOperationsDuration.Reset() + kubeletmetrics.DRAGRPCOperationsDuration.Reset() + defer func() { + require.NoError(t, + metricstestutil.GatherAndCompare(gatherWithoutDurations, + strings.NewReader(test.expectedMetric), + kubeletmetrics.DRAOperationsDuration.FQName(), + kubeletmetrics.DRAGRPCOperationsDuration.FQName(), + ), + ) + }() var pluginClientTimeout *time.Duration if test.wantTimeout { @@ -1109,7 +1505,7 @@ func TestUnprepareResources(t *testing.T) { pluginClientTimeout = &timeout } - draServerInfo, err := setupFakeDRADriverGRPCServer(backgroundCtx, test.wantTimeout, pluginClientTimeout, nil, test.resp, nil) + draServerInfo, err := setupFakeDRADriverGRPCServer(tCtx, test.wantTimeout, pluginClientTimeout, nil, test.resp, nil) if err != nil { t.Fatal(err) } @@ -1117,7 +1513,7 @@ func TestUnprepareResources(t *testing.T) { manager, err := NewManager(tCtx.Logger(), fakeKubeClient, t.TempDir()) require.NoError(t, err, "create DRA manager") - manager.initDRAPluginManager(backgroundCtx, getFakeNode, time.Second /* very short wiping delay for testing */) + manager.initDRAPluginManager(tCtx, getFakeNode, time.Second /* very short wiping delay for testing */) plg := manager.GetWatcherHandler() if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{drapb.DRAPluginService}, pluginClientTimeout); err != nil { @@ -1129,7 +1525,7 @@ func TestUnprepareResources(t *testing.T) { } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, true) - err = manager.UnprepareResources(backgroundCtx, test.pod) + err = manager.UnprepareResources(tCtx, test.pod) assert.Equal(t, test.expectedUnprepareCalls, draServerInfo.server.unprepareResourceCalls.Load()) @@ -1198,7 +1594,7 @@ func TestGetContainerClaimInfos(t *testing.T) { description: "should get claim info", expectedClaimName: claimName, pod: genTestPod(), - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, false), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, false, nil), }, { description: "should get extended resource claim info", @@ -1240,7 +1636,7 @@ func TestGetContainerClaimInfos(t *testing.T) { }, }, }, - claimInfo: genTestClaimInfo(claimUID, []string{podUID}, false), + claimInfo: genTestClaimInfo(claimUID, []string{podUID}, false, nil), expectedErrMsg: "none of the supported fields are set", }, { @@ -1374,7 +1770,9 @@ func TestParallelPrepareUnprepareResources(t *testing.T) { // is updated correctly, if affected pods are identified, and if update notifications are sent // through the manager's update channel. It covers various scenarios including health changes, stream errors, and context cancellation. func TestHandleWatchResourcesStream(t *testing.T) { - overallTestCtx, overallTestCancel := context.WithCancel(ktesting.Init(t)) + tCtx := ktesting.Init(t) + logger := tCtx.Logger() + overallTestCtx, overallTestCancel := context.WithCancel(tCtx) defer overallTestCancel() // Helper to create and setup a new manager for each sub-test @@ -1445,7 +1843,7 @@ func TestHandleWatchResourcesStream(t *testing.T) { defer stCancel() // Setup: Create a manager with a relevant claim already in its cache. - initialClaim := genTestClaimInfo(claimUID, []string{string(podUID)}, true) + initialClaim := genTestClaimInfo(claimUID, []string{string(podUID)}, true, nil) manager, runStreamTest := setupNewManagerAndRunStreamTest(t, stCtx, initialClaim) t.Log("HealthChangeForAllocatedDevice: Test Case Started") @@ -1483,7 +1881,7 @@ func TestHandleWatchResourcesStream(t *testing.T) { } // Check cache state - cachedHealth := manager.healthInfoCache.getHealthInfo(driverName, poolName, deviceName) + cachedHealth := manager.healthInfoCache.getHealthInfo(driverName, poolName, deviceName).Health assert.Equal(t, state.DeviceHealthStatus("Unhealthy"), cachedHealth, "Cache update check failed") t.Log("HealthChangeForAllocatedDevice: Closing responses channel to signal EOF") @@ -1541,7 +1939,7 @@ func TestHandleWatchResourcesStream(t *testing.T) { } // Check health cache for the "other-device" - cachedHealthOther := manager.healthInfoCache.getHealthInfo(driverName, poolName, "other-device") + cachedHealthOther := manager.healthInfoCache.getHealthInfo(driverName, poolName, "other-device").Health assert.Equal(t, state.DeviceHealthStatus("Unhealthy"), cachedHealthOther, "Cache update for other-device failed") close(responses) @@ -1562,12 +1960,12 @@ func TestHandleWatchResourcesStream(t *testing.T) { defer stCancel() // Setup: Manager with a claim and the device already marked Unhealthy in health cache - initialClaim := genTestClaimInfo(claimUID, []string{string(podUID)}, true) + initialClaim := genTestClaimInfo(claimUID, []string{string(podUID)}, true, nil) manager, runStreamTest := setupNewManagerAndRunStreamTest(t, stCtx, initialClaim) // Pre-populate health cache initialHealth := state.DeviceHealth{PoolName: poolName, DeviceName: deviceName, Health: "Unhealthy", LastUpdated: time.Now().Add(-5 * time.Millisecond), HealthCheckTimeout: DefaultHealthTimeout} // Ensure LastUpdated is slightly in past - _, err := manager.healthInfoCache.updateHealthInfo(driverName, []state.DeviceHealth{initialHealth}) + _, err := manager.healthInfoCache.updateHealthInfo(logger, driverName, []state.DeviceHealth{initialHealth}) require.NoError(t, err, "Failed to pre-populate health cache") t.Log("NoActualStateChange: Test Case Started") @@ -1678,6 +2076,192 @@ func TestHandleWatchResourcesStream(t *testing.T) { require.Error(t, finalErr) assert.True(t, errors.Is(finalErr, context.Canceled) || errors.Is(finalErr, context.DeadlineExceeded)) }) + + // Test Case 6: Health change for a device allocated with ShareID + // This test validates that ShareID does not interfere with health status reporting. + t.Run("HealthChangeForDeviceWithShareID", func(t *testing.T) { + stCtx, stCancel := context.WithCancel(overallTestCtx) + defer stCancel() + + // Setup: Create a manager with a claim that has ShareID set on the device + testShareUID := types.UID("test-share-uid-for-health") + initialClaim := genTestClaimInfo(claimUID, []string{string(podUID)}, true, &testShareUID) + manager, runStreamTest := setupNewManagerAndRunStreamTest(t, stCtx, initialClaim) + + t.Log("HealthChangeForDeviceWithShareID: Test Case Started") + + responses := make(chan struct { + Resp *drahealthv1alpha1.NodeWatchResourcesResponse + Err error + }, 1) + updateChan, done, streamErrChan := runStreamTest(stCtx, responses) + + // Send health update for the device (same device that has ShareID in the claim) + unhealthyDeviceMsg := &drahealthv1alpha1.DeviceHealth{ + Device: &drahealthv1alpha1.DeviceIdentifier{ + PoolName: poolName, + DeviceName: deviceName, + }, + Health: drahealthv1alpha1.HealthStatus_UNHEALTHY, + LastUpdatedTime: time.Now().Unix(), + } + t.Logf("HealthChangeForDeviceWithShareID: Sending health update: %+v", unhealthyDeviceMsg) + responses <- struct { + Resp *drahealthv1alpha1.NodeWatchResourcesResponse + Err error + }{ + Resp: &drahealthv1alpha1.NodeWatchResourcesResponse{Devices: []*drahealthv1alpha1.DeviceHealth{unhealthyDeviceMsg}}, + } + + t.Log("HealthChangeForDeviceWithShareID: Waiting for update on manager channel") + select { + case upd := <-updateChan: + t.Logf("HealthChangeForDeviceWithShareID: Received update: %+v", upd) + assert.ElementsMatch(t, []string{string(podUID)}, upd.PodUIDs, "Expected pod UID in update for device with ShareID") + case <-time.After(2 * time.Second): + t.Fatal("HealthChangeForDeviceWithShareID: Timeout waiting for pod update - ShareID may be interfering with health reporting") + } + + // Verify health cache is updated correctly + cachedHealth := manager.healthInfoCache.getHealthInfo(driverName, poolName, deviceName) + assert.Equal(t, state.DeviceHealthStatusUnhealthy, cachedHealth.Health, "Health cache should be updated for device with ShareID") + + // Verify the claim still has the ShareID set (it shouldn't be lost during health updates) + claimFromCache, exists := manager.cache.get(claimName, namespace) + require.True(t, exists, "Claim should still exist in cache") + devices := claimFromCache.DriverState[driverName].Devices + require.Len(t, devices, 1, "Claim should have one device") + assert.NotNil(t, devices[0].ShareID, "ShareID should still be set on the device") + assert.Equal(t, testShareUID, *devices[0].ShareID, "ShareID value should be preserved") + + t.Log("HealthChangeForDeviceWithShareID: Closing responses channel to signal EOF") + close(responses) + + t.Log("HealthChangeForDeviceWithShareID: Waiting on done channel") + var finalErr error + select { + case <-done: + finalErr = <-streamErrChan + t.Log("HealthChangeForDeviceWithShareID: done channel closed, stream goroutine finished.") + case <-time.After(1 * time.Second): + t.Fatal("HealthChangeForDeviceWithShareID: Timed out waiting for HandleWatchResourcesStream to finish") + } + assert.True(t, finalErr == nil || errors.Is(finalErr, io.EOF), "Expected nil or io.EOF, got %v", finalErr) + }) + + // Test Case 7: Health change affects multiple pods sharing the same device via ShareID + // When two pods share the same physical device (identified by pool/device, but with different ShareIDs), + // a health change should notify both pods since they're using the same underlying hardware. + t.Run("HealthChangeForMultiplePodsWithSharedDevice", func(t *testing.T) { + stCtx, stCancel := context.WithCancel(overallTestCtx) + defer stCancel() + + // Setup: Two claims from different pods, both using the same device with different ShareIDs + pod1UID := types.UID("pod-1-sharing-device") + pod2UID := types.UID("pod-2-sharing-device") + claim1UID := types.UID("claim-1-shared") + claim2UID := types.UID("claim-2-shared") + shareUID1 := types.UID("share-uid-1") + shareUID2 := types.UID("share-uid-2") + + // Both claims reference the same physical device (same pool/device) but with different ShareIDs + claim1 := &ClaimInfo{ + ClaimInfoState: state.ClaimInfoState{ + ClaimUID: claim1UID, + ClaimName: "shared-claim-1", + Namespace: namespace, + PodUIDs: sets.New[string](string(pod1UID)), + DriverState: map[string]state.DriverState{ + driverName: { + Devices: []state.Device{{ + PoolName: poolName, + DeviceName: deviceName, // Same device as claim2 + ShareID: &shareUID1, + RequestNames: []string{requestName}, + CDIDeviceIDs: []string{cdiID}, + }}, + }, + }, + }, + prepared: true, + } + claim2 := &ClaimInfo{ + ClaimInfoState: state.ClaimInfoState{ + ClaimUID: claim2UID, + ClaimName: "shared-claim-2", + Namespace: namespace, + PodUIDs: sets.New[string](string(pod2UID)), + DriverState: map[string]state.DriverState{ + driverName: { + Devices: []state.Device{{ + PoolName: poolName, + DeviceName: deviceName, // Same device as claim1 + ShareID: &shareUID2, + RequestNames: []string{requestName}, + CDIDeviceIDs: []string{cdiID}, + }}, + }, + }, + }, + prepared: true, + } + + manager, runStreamTest := setupNewManagerAndRunStreamTest(t, stCtx, claim1, claim2) + + t.Log("HealthChangeForMultiplePodsWithSharedDevice: Test Case Started") + + responses := make(chan struct { + Resp *drahealthv1alpha1.NodeWatchResourcesResponse + Err error + }, 1) + updateChan, done, streamErrChan := runStreamTest(stCtx, responses) + + // Send health update for the shared device + unhealthyDeviceMsg := &drahealthv1alpha1.DeviceHealth{ + Device: &drahealthv1alpha1.DeviceIdentifier{ + PoolName: poolName, + DeviceName: deviceName, + }, + Health: drahealthv1alpha1.HealthStatus_UNHEALTHY, + LastUpdatedTime: time.Now().Unix(), + } + t.Logf("HealthChangeForMultiplePodsWithSharedDevice: Sending health update: %+v", unhealthyDeviceMsg) + responses <- struct { + Resp *drahealthv1alpha1.NodeWatchResourcesResponse + Err error + }{ + Resp: &drahealthv1alpha1.NodeWatchResourcesResponse{Devices: []*drahealthv1alpha1.DeviceHealth{unhealthyDeviceMsg}}, + } + + t.Log("HealthChangeForMultiplePodsWithSharedDevice: Waiting for update on manager channel") + select { + case upd := <-updateChan: + t.Logf("HealthChangeForMultiplePodsWithSharedDevice: Received update: %+v", upd) + // Both pods should be notified since they share the same device + assert.Len(t, upd.PodUIDs, 2, "Both pods sharing the device should be notified") + assert.Contains(t, upd.PodUIDs, string(pod1UID), "Pod 1 should be notified") + assert.Contains(t, upd.PodUIDs, string(pod2UID), "Pod 2 should be notified") + case <-time.After(2 * time.Second): + t.Fatal("HealthChangeForMultiplePodsWithSharedDevice: Timeout waiting for pod update") + } + + // Verify health cache is updated + cachedHealth := manager.healthInfoCache.getHealthInfo(driverName, poolName, deviceName) + assert.Equal(t, state.DeviceHealthStatusUnhealthy, cachedHealth.Health, "Health cache should show Unhealthy for shared device") + + t.Log("HealthChangeForMultiplePodsWithSharedDevice: Closing responses channel to signal EOF") + close(responses) + + var finalErr error + select { + case <-done: + finalErr = <-streamErrChan + t.Log("HealthChangeForMultiplePodsWithSharedDevice: done channel closed") + case <-time.After(1 * time.Second): + t.Fatal("HealthChangeForMultiplePodsWithSharedDevice: Timed out waiting for stream to finish") + } + assert.True(t, finalErr == nil || errors.Is(finalErr, io.EOF), "Expected nil or io.EOF, got %v", finalErr) + }) } // TestUpdateAllocatedResourcesStatus verifies that the manager can correctly @@ -1735,7 +2319,7 @@ func TestUpdateAllocatedResourcesStatus(t *testing.T) { { Name: "claim:claim1", Resources: []v1.ResourceHealth{ - {ResourceID: "test-driver/pool/dev-a", Health: v1.ResourceHealthStatusHealthy}, + {ResourceID: "test-driver/pool/dev-a", Health: v1.ResourceHealthStatusHealthy, Message: ptr.To("Device is operating normally")}, }, }, }, @@ -1770,7 +2354,7 @@ func TestUpdateAllocatedResourcesStatus(t *testing.T) { { Name: "claim:renamed-pod-claim", Resources: []v1.ResourceHealth{ - {ResourceID: "test-driver/pool/dev-b", Health: v1.ResourceHealthStatusHealthy}, + {ResourceID: "test-driver/pool/dev-b", Health: v1.ResourceHealthStatusHealthy, Message: ptr.To("Device is operating normally")}, }, }, }, @@ -1805,7 +2389,52 @@ func TestUpdateAllocatedResourcesStatus(t *testing.T) { { Name: "claim:templated-claim", Resources: []v1.ResourceHealth{ - {ResourceID: "test-driver/pool/dev-c", Health: v1.ResourceHealthStatusHealthy}, + {ResourceID: "test-driver/pool/dev-c", Health: v1.ResourceHealthStatusHealthy, Message: ptr.To("Device is operating normally")}, + }, + }, + }, + }, + // Test case for ShareID: Verifies that health status is correctly reported for devices with ShareID set. + // This validates that KEP-4860 (ShareID) does not interfere with KEP-4680 (Resource Health Status). + { + name: "Claim with ShareID", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-shareid", UID: "pod-shareid-uid"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "container1", Resources: v1.ResourceRequirements{Claims: []v1.ResourceClaim{{Name: "shareid-claim"}}}}, + }, + ResourceClaims: []v1.PodResourceClaim{ + {Name: "shareid-claim", ResourceClaimName: ptr.To("shareid-claim-object")}, + }, + }, + Status: v1.PodStatus{ + ResourceClaimStatuses: []v1.PodResourceClaimStatus{ + {Name: "shareid-claim", ResourceClaimName: ptr.To("shareid-claim-object")}, + }, + }, + }, + claimInfos: []*ClaimInfo{ + { + ClaimInfoState: state.ClaimInfoState{ + ClaimName: "shareid-claim-object", + PodUIDs: sets.New("pod-shareid-uid"), + DriverState: map[string]state.DriverState{ + "test-driver": {Devices: []state.Device{{ + PoolName: "pool", + DeviceName: "dev-shared", + ShareID: ptr.To(types.UID("test-share-id")), // Device has ShareID + }}}, + }, + }, + }, + }, + initialStatus: &v1.PodStatus{ContainerStatuses: []v1.ContainerStatus{{Name: "container1"}}}, + expectedAllocatedResourcesStatus: []v1.ResourceStatus{ + { + Name: "claim:shareid-claim", + Resources: []v1.ResourceHealth{ + {ResourceID: "test-driver/pool/dev-shared", Health: v1.ResourceHealthStatusHealthy, Message: ptr.To("Device is operating normally")}, }, }, }, @@ -1814,8 +2443,10 @@ func TestUpdateAllocatedResourcesStatus(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ResourceHealthStatusMessage, true) tCtx := ktesting.Init(t) - manager, err := NewManager(tCtx.Logger(), nil, t.TempDir()) + logger := tCtx.Logger() + manager, err := NewManager(logger, nil, t.TempDir()) require.NoError(t, err) for _, ci := range tc.claimInfos { @@ -1827,9 +2458,14 @@ func TestUpdateAllocatedResourcesStatus(t *testing.T) { for _, ci := range tc.claimInfos { for driverName, ds := range ci.DriverState { for _, dev := range ds.Devices { - devices = append(devices, state.DeviceHealth{PoolName: dev.PoolName, DeviceName: dev.DeviceName, Health: state.DeviceHealthStatusHealthy}) + devices = append(devices, state.DeviceHealth{ + PoolName: dev.PoolName, + DeviceName: dev.DeviceName, + Health: state.DeviceHealthStatusHealthy, + Message: "Device is operating normally", + }) } - _, err := manager.healthInfoCache.updateHealthInfo(driverName, devices) + _, err := manager.healthInfoCache.updateHealthInfo(logger, driverName, devices) require.NoError(t, err) } } @@ -1842,3 +2478,430 @@ func TestUpdateAllocatedResourcesStatus(t *testing.T) { }) } } + +func TestUpdateAllocatedResourcesStatus_Subrequest(t *testing.T) { + directClaimName := "test-claim" + + testCases := []struct { + name string + claimRequest string + deviceRequestNames []string + expectHealthReported bool + expectedHealthMessage string + expectedHealthStatus v1.ResourceHealthStatus + }{ + { + name: "Subrequest matches base name", + claimRequest: "req-base/sub", + deviceRequestNames: []string{"req-base"}, + expectHealthReported: true, + expectedHealthMessage: "Device healthy", + expectedHealthStatus: v1.ResourceHealthStatusHealthy, + }, + { + name: "Subrequest does not match different base name", + claimRequest: "req-other/sub", + deviceRequestNames: []string{"req-base"}, + expectHealthReported: false, + }, + { + name: "Base request matches base name", + claimRequest: "req-base", + deviceRequestNames: []string{"req-base"}, + expectHealthReported: true, + expectedHealthMessage: "Device healthy", + expectedHealthStatus: v1.ResourceHealthStatusHealthy, + }, + { + name: "Empty request matches any device", + claimRequest: "", + deviceRequestNames: []string{"req-base"}, + expectHealthReported: true, + expectedHealthMessage: "Device healthy", + expectedHealthStatus: v1.ResourceHealthStatusHealthy, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ResourceHealthStatusMessage, true) + tCtx := ktesting.Init(t) + manager, err := NewManager(tCtx.Logger(), nil, t.TempDir()) + require.NoError(t, err) + + // Setup claim info with device + claimInfo := &ClaimInfo{ + ClaimInfoState: state.ClaimInfoState{ + ClaimName: directClaimName, + PodUIDs: sets.New("test-pod-uid"), + DriverState: map[string]state.DriverState{ + "test-driver": { + Devices: []state.Device{ + { + PoolName: "test-pool", + DeviceName: "test-device", + RequestNames: tc.deviceRequestNames, + }, + }, + }, + }, + }, + } + manager.cache.add(claimInfo) + + // Setup health cache + devices := []state.DeviceHealth{ + { + PoolName: "test-pool", + DeviceName: "test-device", + Health: state.DeviceHealthStatusHealthy, + Message: "Device healthy", + }, + } + _, err = manager.healthInfoCache.updateHealthInfo(tCtx.Logger(), "test-driver", devices) + require.NoError(t, err) + + // Create pod and status + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pod", UID: "test-pod-uid"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container1", + Resources: v1.ResourceRequirements{ + Claims: []v1.ResourceClaim{ + {Name: "claim1", Request: tc.claimRequest}, + }, + }, + }, + }, + ResourceClaims: []v1.PodResourceClaim{ + {Name: "claim1", ResourceClaimName: &directClaimName}, + }, + }, + Status: v1.PodStatus{ + ResourceClaimStatuses: []v1.PodResourceClaimStatus{ + {Name: "claim1", ResourceClaimName: &directClaimName}, + }, + }, + } + + status := &v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + {Name: "container1"}, + }, + } + + // Call UpdateAllocatedResourcesStatus + manager.UpdateAllocatedResourcesStatus(pod, status) + + // Assert results + require.Len(t, status.ContainerStatuses, 1) + + if tc.expectHealthReported { + // Should have one ResourceStatus entry + require.Len(t, status.ContainerStatuses[0].AllocatedResourcesStatus, 1) + resStatus := status.ContainerStatuses[0].AllocatedResourcesStatus[0] + + // Should have one ResourceHealth entry + require.Len(t, resStatus.Resources, 1, "Expected exactly one ResourceHealth entry") + healthEntry := resStatus.Resources[0] + + // Verify health matches what we put in the cache + assert.Equal(t, tc.expectedHealthStatus, healthEntry.Health) + assert.Equal(t, tc.expectedHealthMessage, ptr.Deref(healthEntry.Message, "")) + assert.Equal(t, v1.ResourceID("test-driver/test-pool/test-device"), healthEntry.ResourceID) + } else { + // Should have no ResourceStatus entries at all (empty statuses are pruned) + assert.Empty(t, status.ContainerStatuses[0].AllocatedResourcesStatus, + "Expected AllocatedResourcesStatus to be empty when device is filtered out") + } + }) + } +} + +func TestTruncateHealthMessage(t *testing.T) { + testCases := map[string]struct { + input string + expected string + }{ + "empty message": { + input: "", + expected: "", + }, + "short message": { + input: "Device is healthy", + expected: "Device is healthy", + }, + "message at exactly max length": { + input: string(make([]byte, v1.ResourceHealthMessageMaxLength)), + expected: string(make([]byte, v1.ResourceHealthMessageMaxLength)), + }, + "message one character over max length": { + input: string(make([]byte, v1.ResourceHealthMessageMaxLength+1)), + expected: string(make([]byte, v1.ResourceHealthMessageMaxLength-3)) + "...", + }, + "very long message": { + input: string(make([]byte, v1.ResourceHealthMessageMaxLength*2)), + expected: string(make([]byte, v1.ResourceHealthMessageMaxLength-3)) + "...", + }, + "message with meaningful content": { + input: "ECC error count exceeded threshold on device GPU-0", + expected: "ECC error count exceeded threshold on device GPU-0", + }, + "long message with meaningful content": { + input: string(make([]byte, v1.ResourceHealthMessageMaxLength)) + " extra content", + expected: string(make([]byte, v1.ResourceHealthMessageMaxLength-3)) + "...", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result := truncateHealthMessage(tc.input) + assert.Equal(t, tc.expected, result) + // Verify the result is never longer than max length + assert.LessOrEqual(t, len(result), v1.ResourceHealthMessageMaxLength) + // Verify that if truncation occurred, it ends with "..." + if len(tc.input) > v1.ResourceHealthMessageMaxLength { + assert.Len(t, result, v1.ResourceHealthMessageMaxLength) + assert.Equal(t, "...", result[len(result)-3:]) + } + }) + } +} + +func TestBuildResourceHealth(t *testing.T) { + testCases := map[string]struct { + device state.Device + healthInfo state.DeviceHealth + enableMessage bool + wantHealth v1.ResourceHealthStatus + wantResource v1.ResourceID + wantMessage *string + }{ + "message enabled and CDI id": { + device: state.Device{ + PoolName: "pool", + DeviceName: "device", + CDIDeviceIDs: []string{"driver/pool=device"}, + }, + healthInfo: state.DeviceHealth{ + Health: state.DeviceHealthStatusHealthy, + Message: "device ok", + }, + enableMessage: true, + wantHealth: v1.ResourceHealthStatusHealthy, + wantResource: v1.ResourceID("driver/pool=device"), + wantMessage: ptr.To("device ok"), + }, + "message disabled": { + device: state.Device{ + PoolName: "pool", + DeviceName: "device", + CDIDeviceIDs: []string{"driver/pool=device"}, + }, + healthInfo: state.DeviceHealth{ + Health: state.DeviceHealthStatusHealthy, + Message: "device ok", + }, + enableMessage: false, + wantHealth: v1.ResourceHealthStatusHealthy, + wantResource: v1.ResourceID("driver/pool=device"), + wantMessage: nil, + }, + "empty message": { + device: state.Device{ + PoolName: "pool", + DeviceName: "device", + CDIDeviceIDs: []string{"driver/pool=device"}, + }, + healthInfo: state.DeviceHealth{ + Health: state.DeviceHealthStatusHealthy, + Message: "", + }, + enableMessage: true, + wantHealth: v1.ResourceHealthStatusHealthy, + wantResource: v1.ResourceID("driver/pool=device"), + wantMessage: nil, + }, + "unhealthy status": { + device: state.Device{ + PoolName: "pool", + DeviceName: "device", + CDIDeviceIDs: []string{"driver/pool=device"}, + }, + healthInfo: state.DeviceHealth{ + Health: state.DeviceHealthStatusUnhealthy, + Message: "device ok", + }, + enableMessage: true, + wantHealth: v1.ResourceHealthStatusUnhealthy, + wantResource: v1.ResourceID("driver/pool=device"), + wantMessage: ptr.To("device ok"), + }, + "unknown status": { + device: state.Device{ + PoolName: "pool", + DeviceName: "device", + CDIDeviceIDs: []string{"driver/pool=device"}, + }, + healthInfo: state.DeviceHealth{ + Health: state.DeviceHealthStatusUnknown, + Message: "device ok", + }, + enableMessage: true, + wantHealth: v1.ResourceHealthStatusUnknown, + wantResource: v1.ResourceID("driver/pool=device"), + wantMessage: ptr.To("device ok"), + }, + "fallback resource id": { + device: state.Device{ + PoolName: "pool", + DeviceName: "device", + }, + healthInfo: state.DeviceHealth{ + Health: state.DeviceHealthStatusUnhealthy, + Message: "device ok", + }, + enableMessage: true, + wantHealth: v1.ResourceHealthStatusUnhealthy, + wantResource: v1.ResourceID("driver/pool/device"), + wantMessage: ptr.To("device ok"), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := buildResourceHealth("driver", tc.device, tc.healthInfo, tc.enableMessage) + assert.Equal(t, tc.wantHealth, got.Health) + assert.Equal(t, tc.wantResource, got.ResourceID) + assert.Equal(t, tc.wantMessage, got.Message) + }) + } +} + +func TestToResourceHealthStatus(t *testing.T) { + testCases := map[string]struct { + input state.DeviceHealthStatus + expected v1.ResourceHealthStatus + }{ + "healthy": { + input: state.DeviceHealthStatusHealthy, + expected: v1.ResourceHealthStatusHealthy, + }, + "unhealthy": { + input: state.DeviceHealthStatusUnhealthy, + expected: v1.ResourceHealthStatusUnhealthy, + }, + "unknown": { + input: state.DeviceHealthStatusUnknown, + expected: v1.ResourceHealthStatusUnknown, + }, + "empty": { + input: state.DeviceHealthStatus(""), + expected: v1.ResourceHealthStatusUnknown, + }, + "unexpected": { + input: state.DeviceHealthStatus("Other"), + expected: v1.ResourceHealthStatusUnknown, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, toResourceHealthStatus(tc.input)) + }) + } +} + +func TestToDeviceHealthStatus(t *testing.T) { + testCases := map[string]struct { + input drahealthv1alpha1.HealthStatus + expected state.DeviceHealthStatus + }{ + "healthy": { + input: drahealthv1alpha1.HealthStatus_HEALTHY, + expected: state.DeviceHealthStatusHealthy, + }, + "unhealthy": { + input: drahealthv1alpha1.HealthStatus_UNHEALTHY, + expected: state.DeviceHealthStatusUnhealthy, + }, + "unknown": { + input: drahealthv1alpha1.HealthStatus_UNKNOWN, + expected: state.DeviceHealthStatusUnknown, + }, + "unexpected": { + input: drahealthv1alpha1.HealthStatus(99), + expected: state.DeviceHealthStatusUnknown, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, toDeviceHealthStatus(tc.input)) + }) + } +} + +func TestBuildDeviceHealth(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + longMessage := strings.Repeat("a", v1.ResourceHealthMessageMaxLength+5) + + testCases := map[string]struct { + health drahealthv1alpha1.HealthStatus + timeoutSeconds int64 + message string + wantHealth state.DeviceHealthStatus + wantTimeout time.Duration + wantMessage string + }{ + "healthy with positive timeout": { + health: drahealthv1alpha1.HealthStatus_HEALTHY, + timeoutSeconds: 12, + message: "ok", + wantHealth: state.DeviceHealthStatusHealthy, + wantTimeout: 12 * time.Second, + wantMessage: "ok", + }, + "unhealthy with zero timeout": { + health: drahealthv1alpha1.HealthStatus_UNHEALTHY, + timeoutSeconds: 0, + message: "fail", + wantHealth: state.DeviceHealthStatusUnhealthy, + wantTimeout: DefaultHealthTimeout, + wantMessage: "fail", + }, + "unknown with negative timeout": { + health: drahealthv1alpha1.HealthStatus_UNKNOWN, + timeoutSeconds: -1, + message: longMessage, + wantHealth: state.DeviceHealthStatusUnknown, + wantTimeout: DefaultHealthTimeout, + wantMessage: truncateHealthMessage(longMessage), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + grpcDevice := &drahealthv1alpha1.DeviceHealth{ + Device: &drahealthv1alpha1.DeviceIdentifier{ + PoolName: "pool", + DeviceName: "device", + }, + Health: tc.health, + LastUpdatedTime: 123, + HealthCheckTimeoutSeconds: tc.timeoutSeconds, + Message: tc.message, + } + + got := buildDeviceHealth(logger, grpcDevice) + assert.Equal(t, "pool", got.PoolName) + assert.Equal(t, "device", got.DeviceName) + assert.Equal(t, tc.wantHealth, got.Health) + assert.Equal(t, time.Unix(123, 0), got.LastUpdated) + assert.Equal(t, tc.wantTimeout, got.HealthCheckTimeout) + assert.Equal(t, tc.wantMessage, got.Message) + }) + } +} diff --git a/pkg/kubelet/cm/dra/plugin/dra_plugin.go b/pkg/kubelet/cm/dra/plugin/dra_plugin.go index 1529bdcc3afa7..a6598fec649ae 100644 --- a/pkg/kubelet/cm/dra/plugin/dra_plugin.go +++ b/pkg/kubelet/cm/dra/plugin/dra_plugin.go @@ -18,15 +18,11 @@ package plugin import ( "context" - "errors" "fmt" - "net" "sync" "time" "google.golang.org/grpc" - "google.golang.org/grpc/connectivity" - "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" "k8s.io/klog/v2" @@ -65,70 +61,10 @@ type DRAPlugin struct { mutex sync.Mutex backgroundCtx context.Context - healthClient drahealthv1alpha1.DRAResourceHealthClient healthStreamCtx context.Context healthStreamCancel context.CancelFunc } -func (p *DRAPlugin) getOrCreateGRPCConn() (*grpc.ClientConn, error) { - p.mutex.Lock() - defer p.mutex.Unlock() - - // If connection exists and is ready, return it. - if p.conn != nil && p.conn.GetState() != connectivity.Shutdown { - // Initialize health client if connection exists but client is nil - // This allows lazy init if connection was established before health was added. - if p.healthClient == nil { - p.healthClient = drahealthv1alpha1.NewDRAResourceHealthClient(p.conn) - klog.FromContext(p.backgroundCtx).V(4).Info("Initialized DRAResourceHealthClient lazily") - } - return p.conn, nil - } - - // If the connection is dead, clean it up before creating a new one. - if p.conn != nil { - if err := p.conn.Close(); err != nil { - return nil, fmt.Errorf("failed to close stale gRPC connection to %s: %w", p.endpoint, err) - } - p.conn = nil - p.healthClient = nil - } - - ctx := p.backgroundCtx - logger := klog.FromContext(ctx) - - network := "unix" - logger.V(4).Info("Creating new gRPC connection", "protocol", network, "endpoint", p.endpoint) - // grpc.Dial is deprecated. grpc.NewClient should be used instead. - // For now this gets ignored because this function is meant to establish - // the connection, with the one second timeout below. Perhaps that - // approach should be reconsidered? - //nolint:staticcheck - conn, err := grpc.Dial( - p.endpoint, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(ctx context.Context, target string) (net.Conn, error) { - return (&net.Dialer{}).DialContext(ctx, network, target) - }), - grpc.WithChainUnaryInterceptor(newMetricsInterceptor(p.driverName)), - ) - if err != nil { - return nil, err - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - if ok := conn.WaitForStateChange(ctx, connectivity.Connecting); !ok { - return nil, errors.New("timed out waiting for gRPC connection to be ready") - } - - p.conn = conn - p.healthClient = drahealthv1alpha1.NewDRAResourceHealthClient(p.conn) - - return p.conn, nil -} - func (p *DRAPlugin) DriverName() string { return p.driverName } @@ -138,7 +74,7 @@ func (p *DRAPlugin) NodePrepareResources( req *drapbv1.NodePrepareResourcesRequest, opts ...grpc.CallOption, ) (*drapbv1.NodePrepareResourcesResponse, error) { - logger := klog.FromContext(ctx) + logger := klog.FromContext(ctx).WithName("dra-plugin") logger = klog.LoggerWithValues(logger, "driverName", p.driverName, "endpoint", p.endpoint) ctx = klog.NewContext(ctx, logger) logger.V(4).Info("Calling NodePrepareResources rpc", "request", req) @@ -169,11 +105,12 @@ func (p *DRAPlugin) NodeUnprepareResources( req *drapbv1.NodeUnprepareResourcesRequest, opts ...grpc.CallOption, ) (*drapbv1.NodeUnprepareResourcesResponse, error) { - logger := klog.FromContext(ctx) - logger.V(4).Info("Calling NodeUnprepareResource rpc", "request", req) + logger := klog.FromContext(ctx).WithName("dra-plugin") logger = klog.LoggerWithValues(logger, "driverName", p.driverName, "endpoint", p.endpoint) ctx = klog.NewContext(ctx, logger) + logger.V(4).Info("Calling NodeUnprepareResource rpc", "request", req) + ctx, cancel := context.WithTimeout(ctx, p.clientCallTimeout) defer cancel() @@ -221,17 +158,12 @@ func (p *DRAPlugin) HealthStreamCancel() context.CancelFunc { // NodeWatchResources establishes a stream to receive health updates from the DRA plugin. func (p *DRAPlugin) NodeWatchResources(ctx context.Context) (drahealthv1alpha1.DRAResourceHealth_NodeWatchResourcesClient, error) { - // Ensure a connection and the health client exist before proceeding. - // This call is idempotent and will create them if they don't exist. - _, err := p.getOrCreateGRPCConn() - if err != nil { - klog.FromContext(p.backgroundCtx).Error(err, "Failed to get gRPC connection for health client") - return nil, err - } + logger := klog.FromContext(ctx).WithName("dra-plugin") + logger = klog.LoggerWithValues(logger, "driverName", p.driverName, "endpoint", p.endpoint) + healthClient := drahealthv1alpha1.NewDRAResourceHealthClient(p.conn) - logger := klog.FromContext(ctx).WithValues("pluginName", p.driverName) logger.V(4).Info("Starting WatchResources stream") - stream, err := p.healthClient.NodeWatchResources(ctx, &drahealthv1alpha1.NodeWatchResourcesRequest{}) + stream, err := healthClient.NodeWatchResources(ctx, &drahealthv1alpha1.NodeWatchResourcesRequest{}) if err != nil { logger.Error(err, "NodeWatchResources RPC call failed") return nil, err diff --git a/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go b/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go index fd08372c93b5d..367ad09751779 100644 --- a/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go +++ b/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go @@ -58,7 +58,7 @@ type DRAPluginManager struct { backgroundCtx context.Context cancel func(err error) kubeClient kubernetes.Interface - getNode func() (*v1.Node, error) + getNode func(context.Context) (*v1.Node, error) wipingDelay time.Duration streamHandler StreamHandler @@ -146,7 +146,7 @@ func (m *monitoredPlugin) HandleConn(_ context.Context, stats grpcstats.ConnStat // The context can be used to cancel all background activities. // If desired, Stop can be called in addition or instead of canceling // the context. It then also waits for background activities to stop. -func NewDRAPluginManager(ctx context.Context, kubeClient kubernetes.Interface, getNode func() (*v1.Node, error), streamHandler StreamHandler, wipingDelay time.Duration) *DRAPluginManager { +func NewDRAPluginManager(ctx context.Context, kubeClient kubernetes.Interface, getNode func(context.Context) (*v1.Node, error), streamHandler StreamHandler, wipingDelay time.Duration) *DRAPluginManager { ctx, cancel := context.WithCancelCause(ctx) pm := &DRAPluginManager{ backgroundCtx: klog.NewContext(ctx, klog.LoggerWithName(klog.FromContext(ctx), "DRA registration handler")), @@ -228,7 +228,7 @@ func (pm *DRAPluginManager) wipeResourceSlices(ctx context.Context, driver strin // Error logging is done inside the loop. Context cancellation doesn't get logged. _ = wait.ExponentialBackoffWithContext(ctx, backoff, func(ctx context.Context) (bool, error) { - node, err := pm.getNode() + node, err := pm.getNode(ctx) if apierrors.IsNotFound(err) { return false, nil } @@ -384,7 +384,7 @@ func (pm *DRAPluginManager) add(driverName string, endpoint string, chosenServic } p.conn = conn - if utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatus) { + if utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatus) && pm.streamHandler != nil { pm.wg.Add(1) go func() { defer pm.wg.Done() diff --git a/pkg/kubelet/cm/dra/plugin/dra_plugin_test.go b/pkg/kubelet/cm/dra/plugin/dra_plugin_test.go index e443e981d6aaa..e3b1c04adb442 100644 --- a/pkg/kubelet/cm/dra/plugin/dra_plugin_test.go +++ b/pkg/kubelet/cm/dra/plugin/dra_plugin_test.go @@ -93,8 +93,8 @@ func (f *fakeGRPCServer) NodeWatchResources(in *drahealthv1alpha1.NodeWatchResou // tearDown is an idempotent cleanup function. type tearDown func() -func setupGRPCServerWithFake(service, addr string, fakeGRPCServer fakeGRPCServerInterface) (tearDown, error) { - ctx, cancel := context.WithCancel(context.Background()) +func setupGRPCServerWithFake(ctx context.Context, service, addr string, fakeGRPCServer fakeGRPCServerInterface) (tearDown, error) { + ctx, cancel := context.WithCancel(ctx) listener, err := net.Listen("unix", addr) if err != nil { @@ -145,15 +145,14 @@ func setupGRPCServerWithFake(service, addr string, fakeGRPCServer fakeGRPCServer return teardown, nil } -func setupFakeGRPCServer(service, addr string) (tearDown, error) { - return setupGRPCServerWithFake(service, addr, &fakeGRPCServer{}) +func setupFakeGRPCServer(ctx context.Context, service, addr string) (tearDown, error) { + return setupGRPCServerWithFake(ctx, service, addr, &fakeGRPCServer{}) } func TestGRPCConnIsReused(t *testing.T) { tCtx := ktesting.Init(t) - service := drapbv1.DRAPluginService addr := path.Join(t.TempDir(), "dra.sock") - teardown, err := setupFakeGRPCServer(service, addr) + teardown, err := setupFakeGRPCServer(tCtx, "", addr) require.NoError(t, err) defer teardown() @@ -165,7 +164,7 @@ func TestGRPCConnIsReused(t *testing.T) { // ensure the plugin we are using is registered draPlugins := NewDRAPluginManager(tCtx, nil, nil, &mockStreamHandler{}, 0) - tCtx.ExpectNoError(draPlugins.add(driverName, addr, service, defaultClientCallTimeout), "add plugin") + tCtx.ExpectNoError(draPlugins.add(driverName, addr, drapbv1.DRAPluginService, defaultClientCallTimeout), "add plugin") plugin, err := draPlugins.GetPlugin(driverName) tCtx.ExpectNoError(err, "get plugin") conn := plugin.conn @@ -206,13 +205,69 @@ func TestGRPCConnIsReused(t *testing.T) { // We should have only one entry otherwise it means another gRPC connection has been created require.Len(t, reusedConns, 1, "expected length to be 1 but got %d", len(reusedConns)) require.Equal(t, 2, reusedConns[conn], "expected counter to be 2 but got %d", reusedConns[conn]) + + tCtx.Run("health_api_reuses_connection", func(tCtx ktesting.TContext) { + ctx, cancel := context.WithTimeout(tCtx, 5*time.Second) + defer cancel() + + originalConn := plugin.conn + + stream, err := plugin.NodeWatchResources(ctx) + require.NoError(tCtx, err, "Health stream should work") + require.NotNil(tCtx, stream) + + require.Equal(tCtx, originalConn, plugin.conn, "Health API should reuse the same connection") + + resp, err := stream.Recv() + require.NoError(tCtx, err, "Should receive health data") + require.NotNil(tCtx, resp) + require.Len(tCtx, resp.Devices, 1) + assert.Equal(tCtx, "pool1", resp.Devices[0].GetDevice().GetPoolName()) + assert.Equal(tCtx, "dev1", resp.Devices[0].GetDevice().GetDeviceName()) + assert.Equal(tCtx, drahealthv1alpha1.HealthStatus_HEALTHY, resp.Devices[0].GetHealth()) + + require.Equal(tCtx, originalConn, plugin.conn, "Connection should remain unchanged after health operations") + + prepareReq := &drapbv1.NodePrepareResourcesRequest{ + Claims: []*drapbv1.Claim{ + { + Namespace: "dummy-namespace", + Uid: "dummy-uid", + Name: "dummy-claim", + }, + }, + } + + prepareResp, err := plugin.NodePrepareResources(ctx, prepareReq) + require.NoError(tCtx, err, "NodePrepareResources should work") + require.NotNil(tCtx, prepareResp) + require.NotNil(tCtx, prepareResp.Claims["claim-uid"]) + + require.Equal(tCtx, originalConn, plugin.conn, "Connection should remain unchanged after NodePrepareResources") + + unprepareReq := &drapbv1.NodeUnprepareResourcesRequest{ + Claims: []*drapbv1.Claim{ + { + Namespace: "dummy-namespace", + Uid: "dummy-uid", + Name: "dummy-claim", + }, + }, + } + + unprepareResp, err := plugin.NodeUnprepareResources(ctx, unprepareReq) + require.NoError(tCtx, err, "NodeUnprepareResources should work") + require.NotNil(tCtx, unprepareResp) + + require.Equal(tCtx, originalConn, plugin.conn, "Connection should remain unchanged after all API calls") + }) } func TestGRPCConnUsableAfterIdle(t *testing.T) { tCtx := ktesting.Init(t) service := drapbv1.DRAPluginService addr := path.Join(t.TempDir(), "dra.sock") - teardown, err := setupFakeGRPCServer(service, addr) + teardown, err := setupFakeGRPCServer(tCtx, service, addr) require.NoError(t, err) defer teardown() @@ -241,7 +296,7 @@ func TestGRPCConnUsableAfterIdle(t *testing.T) { }, } - callCtx := ktesting.WithTimeout(tCtx, 10*time.Second, "call timed out") + callCtx := tCtx.WithTimeout(10*time.Second, "call timed out") _, err = plugin.NodePrepareResources(callCtx, req) tCtx.ExpectNoError(err, "NodePrepareResources") } @@ -323,7 +378,7 @@ func TestGRPCMethods(t *testing.T) { t.Run(test.description, func(t *testing.T) { tCtx := ktesting.Init(t) addr := path.Join(t.TempDir(), "dra.sock") - teardown, err := setupFakeGRPCServer(test.service, addr) + teardown, err := setupFakeGRPCServer(tCtx, test.service, addr) if err != nil { t.Fatal(err) } @@ -363,7 +418,7 @@ func TestGRPCWithTimeoutEnforced(t *testing.T) { blocked: blocked, done: make(chan struct{}), } - teardown, err := setupGRPCServerWithFake(service, addr, server) + teardown, err := setupGRPCServerWithFake(tCtx, service, addr, server) require.NoError(t, err, "failed to setup grpc server") defer teardown() @@ -477,7 +532,7 @@ func TestPlugin_WatchResources(t *testing.T) { driverName := "test-driver" addr := path.Join(t.TempDir(), "dra.sock") - teardown, err := setupFakeGRPCServer("", addr) + teardown, err := setupFakeGRPCServer(tCtx, "", addr) require.NoError(t, err) defer teardown() diff --git a/pkg/kubelet/cm/dra/plugin/registration_test.go b/pkg/kubelet/cm/dra/plugin/registration_test.go index acf674162ef24..443089653dd03 100644 --- a/pkg/kubelet/cm/dra/plugin/registration_test.go +++ b/pkg/kubelet/cm/dra/plugin/registration_test.go @@ -17,6 +17,7 @@ limitations under the License. package plugin import ( + "context" "path" "sort" "strings" @@ -47,7 +48,7 @@ const ( pluginB = "pluginB" ) -func getFakeNode() (*v1.Node, error) { +func getFakeNode(context.Context) (*v1.Node, error) { return &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}}, nil } @@ -104,7 +105,7 @@ func getFakeClient(t *testing.T, nodeName, driverName string, slice *resourceapi func requireNoSlices(tCtx ktesting.TContext) { tCtx.Helper() - ktesting.Eventually(tCtx, func(tCtx ktesting.TContext) error { + tCtx.Eventually(func(tCtx ktesting.TContext) error { slices, err := tCtx.Client().ResourceV1().ResourceSlices().List(tCtx, metav1.ListOptions{}) if err != nil { return err @@ -202,12 +203,12 @@ func TestRegistrationHandler(t *testing.T) { service := drapb.DRAPluginService tmp := t.TempDir() endpointA := path.Join(tmp, socketFileA) - teardownA, err := setupFakeGRPCServer(service, endpointA) + teardownA, err := setupFakeGRPCServer(tCtx, service, endpointA) require.NoError(t, err) tCtx.Cleanup(teardownA) endpoint := path.Join(tmp, test.socketFile) - teardown, err := setupFakeGRPCServer(service, endpoint) + teardown, err := setupFakeGRPCServer(tCtx, service, endpoint) require.NoError(t, err) tCtx.Cleanup(teardown) @@ -217,7 +218,7 @@ func TestRegistrationHandler(t *testing.T) { if test.withClient { fakeClient := getFakeClient(t, nodeName, test.driverName, getSlice("test-slice")) client = fakeClient - tCtx = ktesting.WithClients(tCtx, nil, nil, client, nil, nil) + tCtx = tCtx.WithClients(nil, nil, client, nil, nil) } // The DRAPluginManager wipes all slices at startup. @@ -306,7 +307,7 @@ func TestConnectionHandling(t *testing.T) { slice := getSlice(sliceName) client := getFakeClient(t, nodeName, driverName, slice) - tCtx = ktesting.WithClients(tCtx, nil, nil, client, nil, nil) + tCtx = tCtx.WithClients(nil, nil, client, nil, nil) // The handler wipes all slices at startup. draPlugins := NewDRAPluginManager(tCtx, client, getFakeNode, &mockStreamHandler{}, test.delay) @@ -315,7 +316,7 @@ func TestConnectionHandling(t *testing.T) { // Run GRPC service. endpoint := path.Join(t.TempDir(), "dra.sock") - teardown, err := setupFakeGRPCServer(service, endpoint) + teardown, err := setupFakeGRPCServer(tCtx, service, endpoint) require.NoError(t, err) defer teardown() @@ -344,7 +345,7 @@ func TestConnectionHandling(t *testing.T) { // Start up gRPC server again. tCtx.Log("Restarting plugin gRPC server") - teardown, err = setupFakeGRPCServer(service, endpoint) + teardown, err = setupFakeGRPCServer(tCtx, service, endpoint) require.NoError(t, err) defer teardown() diff --git a/pkg/kubelet/cm/dra/state/state.go b/pkg/kubelet/cm/dra/state/state.go index d61a5b65f615a..5861f6ff57313 100644 --- a/pkg/kubelet/cm/dra/state/state.go +++ b/pkg/kubelet/cm/dra/state/state.go @@ -101,4 +101,8 @@ type DeviceHealth struct { // Zero value means use the default timeout (DefaultHealthTimeout). // This ensures backward compatibility with existing data. HealthCheckTimeout time.Duration + + // Message provides additional details about the device's health status. + // This field is optional and may be empty. + Message string } diff --git a/pkg/kubelet/cm/fake_container_manager.go b/pkg/kubelet/cm/fake_container_manager.go index 728cdf8d1c298..96ca29bf3e19e 100644 --- a/pkg/kubelet/cm/fake_container_manager.go +++ b/pkg/kubelet/cm/fake_container_manager.go @@ -55,9 +55,10 @@ var _ ContainerManager = &FakeContainerManager{} func NewFakeContainerManager() *FakeContainerManager { return &FakeContainerManager{ PodContainerManager: NewFakePodContainerManager(), - // Using klog.Background() for fake/test implementations where no real context is available - cpuManager: cpumanager.NewFakeManager(klog.Background()), - memoryManager: memorymanager.NewFakeManager(klog.Background()), + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + cpuManager: cpumanager.NewFakeManager(klog.TODO()), + memoryManager: memorymanager.NewFakeManager(klog.TODO()), } } diff --git a/pkg/kubelet/cm/helpers.go b/pkg/kubelet/cm/helpers.go index 1e7a5ca0c6d29..9efdfe8287142 100644 --- a/pkg/kubelet/cm/helpers.go +++ b/pkg/kubelet/cm/helpers.go @@ -25,6 +25,7 @@ import ( internalapi "k8s.io/cri-api/pkg/apis" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/klog/v2" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" ) @@ -32,7 +33,7 @@ import ( // for typecheck across platforms var _ func(int64, int64) int64 = MilliCPUToQuota var _ func(int64) uint64 = MilliCPUToShares -var _ func(*v1.Pod, bool, uint64, bool) *ResourceConfig = ResourceConfigForPod +var _ func(*v1.Pod, bool, uint64, bool, kubeletconfig.MemoryReservationPolicy) *ResourceConfig = ResourceConfigForPod var _ func() (*CgroupSubsystems, error) = GetCgroupSubsystems var _ func(string) ([]int, error) = getCgroupProcs var _ func(types.UID) string = GetPodCgroupNameSuffix diff --git a/pkg/kubelet/cm/helpers_linux.go b/pkg/kubelet/cm/helpers_linux.go index b73ff774f8dc4..ffd5db669e88d 100644 --- a/pkg/kubelet/cm/helpers_linux.go +++ b/pkg/kubelet/cm/helpers_linux.go @@ -34,6 +34,7 @@ import ( v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/cm/util" ) @@ -123,7 +124,7 @@ func HugePageLimits(resourceList v1.ResourceList) map[int64]int64 { } // ResourceConfigForPod takes the input pod and outputs the cgroup resource config. -func ResourceConfigForPod(allocatedPod *v1.Pod, enforceCPULimits bool, cpuPeriod uint64, enforceMemoryQoS bool) *ResourceConfig { +func ResourceConfigForPod(allocatedPod *v1.Pod, enforceCPULimits bool, cpuPeriod uint64, enforceMemoryQoS bool, memoryReservationPolicy kubeletconfig.MemoryReservationPolicy) *ResourceConfig { podLevelResourcesEnabled := utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodLevelResources) // sum requests and limits. reqs := resourcehelper.PodRequests(allocatedPod, resourcehelper.PodResourcesOptions{ @@ -207,14 +208,20 @@ func ResourceConfigForPod(allocatedPod *v1.Pod, enforceCPULimits bool, cpuPeriod } result.HugePageLimit = hugePageLimits - if enforceMemoryQoS { - memoryMin := int64(0) + if enforceMemoryQoS && memoryReservationPolicy == kubeletconfig.TieredReservationMemoryReservationPolicy { + memoryRequest := int64(0) if request, found := reqs[v1.ResourceMemory]; found { - memoryMin = request.Value() + memoryRequest = request.Value() } - if memoryMin > 0 { + if memoryRequest > 0 { + // Guaranteed pods get memory.min (hard protection, kernel never reclaims). + // Burstable pods get memory.low (soft protection, kernel prefers not to reclaim). + cgroupKey := Cgroup2MemoryLow + if qosClass == v1.PodQOSGuaranteed { + cgroupKey = Cgroup2MemoryMin + } result.Unified = map[string]string{ - Cgroup2MemoryMin: strconv.FormatInt(memoryMin, 10), + cgroupKey: strconv.FormatInt(memoryRequest, 10), } } } @@ -347,7 +354,7 @@ func GetKubeletContainer(logger klog.Logger, kubeletCgroups string) (string, err func CPURequestsFromConfig(podConfig *ResourceConfig) *resource.Quantity { var cpuRequest *resource.Quantity if podConfig != nil && *podConfig.CPUShares > 0 { - milliCPU := sharesToMilliCPU(int64(*podConfig.CPUShares)) + milliCPU := SharesToMilliCPU(int64(*podConfig.CPUShares)) if milliCPU > 0 { cpuRequest = resource.NewMilliQuantity(milliCPU, resource.DecimalSI) } @@ -360,7 +367,7 @@ func CPULimitsFromConfig(podConfig *ResourceConfig) *resource.Quantity { var cpuLimit *resource.Quantity if podConfig != nil && *podConfig.CPUPeriod > 0 { - milliCPU := quotaToMilliCPU(*podConfig.CPUQuota, int64(*podConfig.CPUPeriod)) + milliCPU := QuotaToMilliCPU(*podConfig.CPUQuota, int64(*podConfig.CPUPeriod)) if milliCPU > 0 { cpuLimit = resource.NewMilliQuantity(milliCPU, resource.DecimalSI) } @@ -378,9 +385,8 @@ func MemoryLimitsFromConfig(podConfig *ResourceConfig) *resource.Quantity { return memLimit } -// sharesToMilliCPU converts CpuShares (cpu.shares) to milli-CPU value -// TODO - dedup sharesToMilliCPU with sharesToMilliCPU in pkg/kubelet/kuberuntime/helpers_linux.go -func sharesToMilliCPU(shares int64) int64 { +// SharesToMilliCPU converts CpuShares (cpu.shares) to milli-CPU value +func SharesToMilliCPU(shares int64) int64 { milliCPU := int64(0) if shares >= int64(MinShares) { milliCPU = int64(math.Ceil(float64(shares*MilliCPUToCPU) / float64(SharesPerCPU))) @@ -388,10 +394,8 @@ func sharesToMilliCPU(shares int64) int64 { return milliCPU } -// quotaToMilliCPU converts cpu.cfs_quota_us and cpu.cfs_period_us to milli-CPU -// value -// TODO - dedup quotaToMilliCPU with sharesToMilliCPU in pkg/kubelet/kuberuntime/helpers_linux.go -func quotaToMilliCPU(quota int64, period int64) int64 { +// QuotaToMilliCPU converts cpu.cfs_quota_us and cpu.cfs_period_us to milli-CPU value +func QuotaToMilliCPU(quota int64, period int64) int64 { if quota == -1 { return int64(0) } diff --git a/pkg/kubelet/cm/helpers_linux_test.go b/pkg/kubelet/cm/helpers_linux_test.go index 6bc95ff6432a7..fbebb4f4c041d 100644 --- a/pkg/kubelet/cm/helpers_linux_test.go +++ b/pkg/kubelet/cm/helpers_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -30,6 +29,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" pkgfeatures "k8s.io/kubernetes/pkg/features" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" ) // getResourceList returns a ResourceList with the @@ -56,6 +56,7 @@ func getResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequir func TestResourceConfigForPod(t *testing.T) { defaultQuotaPeriod := uint64(100 * time.Millisecond / time.Microsecond) // in microseconds tunedQuotaPeriod := uint64(5 * time.Millisecond / time.Microsecond) // in microseconds + tunedQuota := int64(1 * time.Millisecond / time.Microsecond) minShares := uint64(MinShares) burstableShares := MilliCPUToShares(100) @@ -69,14 +70,16 @@ func TestResourceConfigForPod(t *testing.T) { memoryQuantity = resource.MustParse("100Mi") cpuNoLimit := int64(-1) guaranteedMemory := memoryQuantity.Value() - testCases := map[string]struct { + testCases := []struct { + description string pod *v1.Pod expected *ResourceConfig enforceCPULimits bool quotaPeriod uint64 // in microseconds podLevelResourcesEnabled bool }{ - "besteffort": { + { + description: "besteffort", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -90,7 +93,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &minShares}, }, - "burstable-no-limits": { + { + description: "burstable-no-limits", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -104,7 +108,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares}, }, - "burstable-with-limits": { + { + description: "burstable-with-limits", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -118,7 +123,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, }, - "burstable-with-limits-no-cpu-enforcement": { + { + description: "burstable-with-limits-no-cpu-enforcement", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -132,7 +138,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, }, - "burstable-partial-limits": { + { + description: "burstable-partial-limits", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -149,7 +156,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstablePartialShares}, }, - "burstable-with-limits-with-tuned-quota": { + { + description: "burstable-with-limits-with-tuned-quota", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -161,9 +169,10 @@ func TestResourceConfigForPod(t *testing.T) { }, enforceCPULimits: true, quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory}, + expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &tunedQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory}, }, - "burstable-with-limits-no-cpu-enforcement-with-tuned-quota": { + { + description: "burstable-with-limits-no-cpu-enforcement-with-tuned-quota", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -177,7 +186,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: tunedQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory}, }, - "burstable-partial-limits-with-tuned-quota": { + { + description: "burstable-partial-limits-with-tuned-quota", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -194,7 +204,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: tunedQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstablePartialShares}, }, - "guaranteed": { + { + description: "guaranteed", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -208,7 +219,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &guaranteedQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &guaranteedMemory}, }, - "guaranteed-no-cpu-enforcement": { + { + description: "guaranteed-no-cpu-enforcement", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -222,7 +234,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &cpuNoLimit, CPUPeriod: &defaultQuotaPeriod, Memory: &guaranteedMemory}, }, - "guaranteed-with-tuned-quota": { + { + description: "guaranteed-with-tuned-quota", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -236,7 +249,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: tunedQuotaPeriod, expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &guaranteedTunedQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &guaranteedMemory}, }, - "guaranteed-no-cpu-enforcement-with-tuned-quota": { + { + description: "guaranteed-no-cpu-enforcement-with-tuned-quota", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -250,7 +264,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: tunedQuotaPeriod, expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &cpuNoLimit, CPUPeriod: &tunedQuotaPeriod, Memory: &guaranteedMemory}, }, - "burstable-partial-limits-with-init-containers": { + { + description: "burstable-partial-limits-with-init-containers", pod: &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -275,7 +290,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: tunedQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstablePartialShares}, }, - "besteffort-with-pod-level-resources-enabled": { + { + description: "besteffort-with-pod-level-resources-enabled", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -294,7 +310,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &minShares}, }, - "burstable-with-pod-level-requests": { + { + description: "burstable-with-pod-level-requests", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -312,7 +329,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares}, }, - "burstable-with-pod-and-container-level-requests": { + { + description: "burstable-with-pod-and-container-level-requests", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -331,7 +349,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares}, }, - "burstable-with-pod-level-resources": { + { + description: "burstable-with-pod-level-resources", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -350,7 +369,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, }, - "burstable-with-pod-and-container-level-resources": { + { + description: "burstable-with-pod-and-container-level-resources", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -370,7 +390,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, }, - "burstable-with-partial-pod-level-resources-limits": { + { + description: "burstable-with-partial-pod-level-resources-limits", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -389,7 +410,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &burstablePartialShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, }, - "guaranteed-with-pod-level-resources": { + { + description: "guaranteed-with-pod-level-resources", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -408,7 +430,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &guaranteedQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &guaranteedMemory}, }, - "guaranteed-with-pod-and-container-level-resources": { + { + description: "guaranteed-with-pod-and-container-level-resources", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -428,7 +451,8 @@ func TestResourceConfigForPod(t *testing.T) { quotaPeriod: defaultQuotaPeriod, expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &guaranteedQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &guaranteedMemory}, }, - "guaranteed-pod-level-resources-with-init-containers": { + { + description: "guaranteed-pod-level-resources-with-init-containers", pod: &v1.Pod{ Spec: v1.PodSpec{ Resources: &v1.ResourceRequirements{ @@ -456,241 +480,23 @@ func TestResourceConfigForPod(t *testing.T) { }, } - for testName, testCase := range testCases { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.PodLevelResources, testCase.podLevelResourcesEnabled) - actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, false) - if !reflect.DeepEqual(actual.CPUPeriod, testCase.expected.CPUPeriod) { - t.Errorf("unexpected result, test: %v, cpu period not as expected. Expected: %v, Actual:%v", testName, *testCase.expected.CPUPeriod, *actual.CPUPeriod) - } - if !reflect.DeepEqual(actual.CPUQuota, testCase.expected.CPUQuota) { - t.Errorf("unexpected result, test: %v, cpu quota not as expected. Expected: %v, Actual:%v", testName, *testCase.expected.CPUQuota, *actual.CPUQuota) - } - if !reflect.DeepEqual(actual.CPUShares, testCase.expected.CPUShares) { - t.Errorf("unexpected result, test: %v, cpu shares not as expected. Expected: %v, Actual:%v", testName, *testCase.expected.CPUShares, *actual.CPUShares) - } - if !reflect.DeepEqual(actual.Memory, testCase.expected.Memory) { - t.Errorf("unexpected result, test: %v, memory not as expected. Expected: %v, Actual:%v", testName, *testCase.expected.Memory, *actual.Memory) - } - } -} - -func TestResourceConfigForPodWithCustomCPUCFSQuotaPeriod(t *testing.T) { - defaultQuotaPeriod := uint64(100 * time.Millisecond / time.Microsecond) // in microseconds - tunedQuotaPeriod := uint64(5 * time.Millisecond / time.Microsecond) // in microseconds - tunedQuota := int64(1 * time.Millisecond / time.Microsecond) - - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.CPUCFSQuotaPeriod, true) - - minShares := uint64(MinShares) - burstableShares := MilliCPUToShares(100) - memoryQuantity := resource.MustParse("200Mi") - burstableMemory := memoryQuantity.Value() - burstablePartialShares := MilliCPUToShares(200) - burstableQuota := MilliCPUToQuota(200, int64(defaultQuotaPeriod)) - guaranteedShares := MilliCPUToShares(100) - guaranteedQuota := MilliCPUToQuota(100, int64(defaultQuotaPeriod)) - guaranteedTunedQuota := MilliCPUToQuota(100, int64(tunedQuotaPeriod)) - memoryQuantity = resource.MustParse("100Mi") - cpuNoLimit := int64(-1) - guaranteedMemory := memoryQuantity.Value() - testCases := map[string]struct { - pod *v1.Pod - expected *ResourceConfig - enforceCPULimits bool - quotaPeriod uint64 // in microseconds - }{ - "besteffort": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("", ""), getResourceList("", "")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &minShares}, - }, - "burstable-no-limits": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("", "")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares}, - }, - "burstable-with-limits": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, - }, - "burstable-with-limits-no-cpu-enforcement": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")), - }, - }, - }, - }, - enforceCPULimits: false, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory}, - }, - "burstable-partial-limits": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")), - }, - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("", "")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstablePartialShares}, - }, - "burstable-with-limits-with-tuned-quota": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &tunedQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory}, - }, - "burstable-with-limits-no-cpu-enforcement-with-tuned-quota": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")), - }, - }, - }, - }, - enforceCPULimits: false, - quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory}, - }, - "burstable-partial-limits-with-tuned-quota": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")), - }, - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("", "")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstablePartialShares}, - }, - "guaranteed": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &guaranteedQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &guaranteedMemory}, - }, - "guaranteed-no-cpu-enforcement": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")), - }, - }, - }, - }, - enforceCPULimits: false, - quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &cpuNoLimit, CPUPeriod: &defaultQuotaPeriod, Memory: &guaranteedMemory}, - }, - "guaranteed-with-tuned-quota": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")), - }, - }, - }, - }, - enforceCPULimits: true, - quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &guaranteedTunedQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &guaranteedMemory}, - }, - "guaranteed-no-cpu-enforcement-with-tuned-quota": { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")), - }, - }, - }, - }, - enforceCPULimits: false, - quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &guaranteedShares, CPUQuota: &cpuNoLimit, CPUPeriod: &tunedQuotaPeriod, Memory: &guaranteedMemory}, - }, - } - - for testName, testCase := range testCases { - - actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, false) - - if !reflect.DeepEqual(actual.CPUPeriod, testCase.expected.CPUPeriod) { - t.Errorf("unexpected result, test: %v, cpu period not as expected", testName) - } - if !reflect.DeepEqual(actual.CPUQuota, testCase.expected.CPUQuota) { - t.Errorf("unexpected result, test: %v, cpu quota not as expected", testName) - } - if !reflect.DeepEqual(actual.CPUShares, testCase.expected.CPUShares) { - t.Errorf("unexpected result, test: %v, cpu shares not as expected", testName) - } - if !reflect.DeepEqual(actual.Memory, testCase.expected.Memory) { - t.Errorf("unexpected result, test: %v, memory not as expected", testName) - } + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.PodLevelResources, testCase.podLevelResourcesEnabled) + actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, false, kubeletconfig.NoneMemoryReservationPolicy) + if !reflect.DeepEqual(actual.CPUPeriod, testCase.expected.CPUPeriod) { + t.Errorf("cpu period not as expected. Expected: %v, Actual:%v", *testCase.expected.CPUPeriod, *actual.CPUPeriod) + } + if !reflect.DeepEqual(actual.CPUQuota, testCase.expected.CPUQuota) { + t.Errorf("cpu quota not as expected. Expected: %v, Actual:%v", *testCase.expected.CPUQuota, *actual.CPUQuota) + } + if !reflect.DeepEqual(actual.CPUShares, testCase.expected.CPUShares) { + t.Errorf("cpu shares not as expected. Expected: %v, Actual:%v", *testCase.expected.CPUShares, *actual.CPUShares) + } + if !reflect.DeepEqual(actual.Memory, testCase.expected.Memory) { + t.Errorf("memory not as expected. Expected: %v, Actual:%v", *testCase.expected.Memory, *actual.Memory) + } + }) } } @@ -836,7 +642,7 @@ func TestHugePageLimits(t *testing.T) { }, }, } - resultValuePod := ResourceConfigForPod(&p, false, 0, false) + resultValuePod := ResourceConfigForPod(&p, false, 0, false, kubeletconfig.NoneMemoryReservationPolicy) if !reflect.DeepEqual(testcase.expected, resultValuePod.HugePageLimit) { t.Errorf("unexpected result for ResourceConfigForPod(), expected: %v, actual: %v", testcase.expected, resultValuePod) } @@ -892,7 +698,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: true, quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, Unified: map[string]string{"memory.min": "104857600"}}, + expected: &ResourceConfig{CPUShares: &burstableShares, Unified: map[string]string{"memory.low": "104857600"}}, }, "burstable-with-limits": { pod: &v1.Pod{ @@ -906,7 +712,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: true, quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.min": "104857600"}}, + expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.low": "104857600"}}, }, "burstable-with-limits-no-cpu-enforcement": { pod: &v1.Pod{ @@ -920,7 +726,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: false, quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.min": "104857600"}}, + expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &defaultQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.low": "104857600"}}, }, "burstable-partial-limits": { pod: &v1.Pod{ @@ -937,7 +743,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: true, quotaPeriod: defaultQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstablePartialShares, Unified: map[string]string{"memory.min": "209715200"}}, + expected: &ResourceConfig{CPUShares: &burstablePartialShares, Unified: map[string]string{"memory.low": "209715200"}}, }, "burstable-with-limits-with-tuned-quota": { pod: &v1.Pod{ @@ -951,7 +757,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: true, quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.min": "104857600"}}, + expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &burstableQuota, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.low": "104857600"}}, }, "burstable-with-limits-no-cpu-enforcement-with-tuned-quota": { pod: &v1.Pod{ @@ -965,7 +771,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: false, quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.min": "104857600"}}, + expected: &ResourceConfig{CPUShares: &burstableShares, CPUQuota: &cpuNoLimit, CPUPeriod: &tunedQuotaPeriod, Memory: &burstableMemory, Unified: map[string]string{"memory.low": "104857600"}}, }, "burstable-partial-limits-with-tuned-quota": { pod: &v1.Pod{ @@ -982,7 +788,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { }, enforceCPULimits: true, quotaPeriod: tunedQuotaPeriod, - expected: &ResourceConfig{CPUShares: &burstablePartialShares, Unified: map[string]string{"memory.min": "209715200"}}, + expected: &ResourceConfig{CPUShares: &burstablePartialShares, Unified: map[string]string{"memory.low": "209715200"}}, }, "guaranteed": { pod: &v1.Pod{ @@ -1044,7 +850,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) { for testName, testCase := range testCases { - actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, true) + actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, true, kubeletconfig.TieredReservationMemoryReservationPolicy) if !reflect.DeepEqual(actual.Unified, testCase.expected.Unified) { t.Errorf("unexpected result, test: %v, unified not as expected", testName) diff --git a/pkg/kubelet/cm/helpers_unsupported.go b/pkg/kubelet/cm/helpers_unsupported.go index 33df93ff8c4e5..148e45bc02b88 100644 --- a/pkg/kubelet/cm/helpers_unsupported.go +++ b/pkg/kubelet/cm/helpers_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. @@ -24,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" ) const ( @@ -49,7 +49,7 @@ func MilliCPUToShares(milliCPU int64) uint64 { } // ResourceConfigForPod takes the input pod and outputs the cgroup resource config. -func ResourceConfigForPod(pod *v1.Pod, enforceCPULimit bool, cpuPeriod uint64, enforceMemoryQoS bool) *ResourceConfig { +func ResourceConfigForPod(pod *v1.Pod, enforceCPULimit bool, cpuPeriod uint64, enforceMemoryQoS bool, memoryReservationPolicy kubeletconfig.MemoryReservationPolicy) *ResourceConfig { return nil } diff --git a/pkg/kubelet/cm/internal_container_lifecycle_linux.go b/pkg/kubelet/cm/internal_container_lifecycle_linux.go index 91561cf7131a7..39bb1e76d9337 100644 --- a/pkg/kubelet/cm/internal_container_lifecycle_linux.go +++ b/pkg/kubelet/cm/internal_container_lifecycle_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/internal_container_lifecycle_test.go b/pkg/kubelet/cm/internal_container_lifecycle_test.go index 3c5bcb2ce32dd..a12caa121aee4 100644 --- a/pkg/kubelet/cm/internal_container_lifecycle_test.go +++ b/pkg/kubelet/cm/internal_container_lifecycle_test.go @@ -22,7 +22,7 @@ import ( "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" - "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" @@ -89,7 +89,8 @@ func TestPreStartContainer(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _ = test.lifecycle.PreStartContainer(klog.Background(), pod, container, "42") + logger, _ := ktesting.NewTestContext(t) + _ = test.lifecycle.PreStartContainer(logger, pod, container, "42") }) cManager := test.lifecycle.cpuManager diff --git a/pkg/kubelet/cm/internal_container_lifecycle_unsupported.go b/pkg/kubelet/cm/internal_container_lifecycle_unsupported.go index 4f6aa89a8ecc4..c8cde975be5bc 100644 --- a/pkg/kubelet/cm/internal_container_lifecycle_unsupported.go +++ b/pkg/kubelet/cm/internal_container_lifecycle_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/internal_container_lifecycle_windows.go b/pkg/kubelet/cm/internal_container_lifecycle_windows.go index ec960f8ead0c7..ee638c68687c9 100644 --- a/pkg/kubelet/cm/internal_container_lifecycle_windows.go +++ b/pkg/kubelet/cm/internal_container_lifecycle_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/internal_container_lifecycle_windows_test.go b/pkg/kubelet/cm/internal_container_lifecycle_windows_test.go index 747250ead3df2..f31355a4d772c 100644 --- a/pkg/kubelet/cm/internal_container_lifecycle_windows_test.go +++ b/pkg/kubelet/cm/internal_container_lifecycle_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/memorymanager/fake_memory_manager.go b/pkg/kubelet/cm/memorymanager/fake_memory_manager.go index e589c78f573d3..bbe3c4a885f6a 100644 --- a/pkg/kubelet/cm/memorymanager/fake_memory_manager.go +++ b/pkg/kubelet/cm/memorymanager/fake_memory_manager.go @@ -76,6 +76,12 @@ func (m *fakeManager) GetPodTopologyHints(pod *v1.Pod) map[string][]topologymana return map[string][]topologymanager.TopologyHint{} } +func (m *fakeManager) AllocatePod(pod *v1.Pod) error { + logger := klog.TODO() + logger.Info("AllocatePod", "pod", klog.KObj(pod)) + return nil +} + func (m *fakeManager) State() state.Reader { return m.state } diff --git a/pkg/kubelet/cm/memorymanager/memory_manager.go b/pkg/kubelet/cm/memorymanager/memory_manager.go index 585ebff101390..9e11d24627fb3 100644 --- a/pkg/kubelet/cm/memorymanager/memory_manager.go +++ b/pkg/kubelet/cm/memorymanager/memory_manager.go @@ -84,6 +84,9 @@ type Manager interface { // and other resource controllers. GetPodTopologyHints(*v1.Pod) map[string][]topologymanager.TopologyHint + // AllocatePod is called to trigger the allocation of memory to a pod. + AllocatePod(pod *v1.Pod) error + // GetMemoryNUMANodes provides NUMA nodes that are used to allocate the container memory GetMemoryNUMANodes(logger klog.Logger, pod *v1.Pod, container *v1.Container) sets.Set[int] @@ -276,6 +279,22 @@ func (m *manager) Allocate(pod *v1.Pod, container *v1.Container) error { return nil } +func (m *manager) AllocatePod(pod *v1.Pod) error { + logger := klog.TODO() // until we move topology manager to contextual logging + + m.removeStaleState(logger) + + m.Lock() + defer m.Unlock() + + // Call down into the policy to assign this container memory if required. + if err := m.policy.AllocatePod(logger, m.state, pod); err != nil { + logger.Error(err, "AllocatePod error", "pod", klog.KObj(pod)) + return err + } + return nil +} + // RemoveContainer removes the container from the state func (m *manager) RemoveContainer(logger klog.Logger, containerID string) error { logger = klog.LoggerWithValues(logger, "containerID", containerID) diff --git a/pkg/kubelet/cm/memorymanager/memory_manager_test.go b/pkg/kubelet/cm/memorymanager/memory_manager_test.go index 6844710fecd98..59c624115031c 100644 --- a/pkg/kubelet/cm/memorymanager/memory_manager_test.go +++ b/pkg/kubelet/cm/memorymanager/memory_manager_test.go @@ -114,6 +114,10 @@ func (p *mockPolicy) GetPodTopologyHints(klog.Logger, state.State, *v1.Pod) map[ return nil } +func (p *mockPolicy) AllocatePod(logger klog.Logger, s state.State, pod *v1.Pod) error { + return p.err +} + // GetAllocatableMemory returns the amount of allocatable memory for each NUMA node func (p *mockPolicy) GetAllocatableMemory(state.State) []state.Block { return []state.Block{} @@ -127,6 +131,10 @@ func (rt mockRuntimeService) UpdateContainerResources(_ context.Context, id stri return rt.err } +func (rt mockRuntimeService) Close(_ context.Context) error { + return rt.err +} + type mockPodStatusProvider struct { podStatus v1.PodStatus found bool diff --git a/pkg/kubelet/cm/memorymanager/policy.go b/pkg/kubelet/cm/memorymanager/policy.go index 3c17ec93c21fa..c4dfe376b8330 100644 --- a/pkg/kubelet/cm/memorymanager/policy.go +++ b/pkg/kubelet/cm/memorymanager/policy.go @@ -42,6 +42,8 @@ type Policy interface { // and is consulted to achieve NUMA aware resource alignment among this // and other resource controllers. GetPodTopologyHints(logger klog.Logger, s state.State, pod *v1.Pod) map[string][]topologymanager.TopologyHint + // AllocatePod is called to trigger the allocation of memory to a pod-level resources pod. + AllocatePod(logger klog.Logger, s state.State, pod *v1.Pod) error // GetAllocatableMemory returns the amount of allocatable memory for each NUMA node GetAllocatableMemory(s state.State) []state.Block } diff --git a/pkg/kubelet/cm/memorymanager/policy_best_effort.go b/pkg/kubelet/cm/memorymanager/policy_best_effort.go index 53f5746d48d87..59fcdba7a962c 100644 --- a/pkg/kubelet/cm/memorymanager/policy_best_effort.go +++ b/pkg/kubelet/cm/memorymanager/policy_best_effort.go @@ -69,7 +69,8 @@ func (p *bestEffortPolicy) RemoveContainer(logger logr.Logger, s state.State, po } func (p *bestEffortPolicy) GetPodTopologyHints(logger logr.Logger, s state.State, pod *v1.Pod) map[string][]topologymanager.TopologyHint { - return p.static.GetPodTopologyHints(logger, s, pod) + // Pod-level resources are not supported on Windows. + return nil } func (p *bestEffortPolicy) GetTopologyHints(logger logr.Logger, s state.State, pod *v1.Pod, container *v1.Container) map[string][]topologymanager.TopologyHint { @@ -79,3 +80,8 @@ func (p *bestEffortPolicy) GetTopologyHints(logger logr.Logger, s state.State, p func (p *bestEffortPolicy) GetAllocatableMemory(s state.State) []state.Block { return p.static.GetAllocatableMemory(s) } + +func (p *bestEffortPolicy) AllocatePod(logger logr.Logger, s state.State, pod *v1.Pod) error { + // Pod-level resources are not supported on Windows. + return nil +} diff --git a/pkg/kubelet/cm/memorymanager/policy_none.go b/pkg/kubelet/cm/memorymanager/policy_none.go index ceb2d236d1faf..7499913cec226 100644 --- a/pkg/kubelet/cm/memorymanager/policy_none.go +++ b/pkg/kubelet/cm/memorymanager/policy_none.go @@ -68,6 +68,10 @@ func (p *none) GetPodTopologyHints(_ klog.Logger, s state.State, pod *v1.Pod) ma return nil } +func (p *none) AllocatePod(_ klog.Logger, s state.State, pod *v1.Pod) error { + return nil +} + // GetAllocatableMemory returns the amount of allocatable memory for each NUMA node func (p *none) GetAllocatableMemory(s state.State) []state.Block { return []state.Block{} diff --git a/pkg/kubelet/cm/memorymanager/policy_static.go b/pkg/kubelet/cm/memorymanager/policy_static.go index eb5c3b5f67c32..16b3b4a637b12 100644 --- a/pkg/kubelet/cm/memorymanager/policy_static.go +++ b/pkg/kubelet/cm/memorymanager/policy_static.go @@ -19,7 +19,9 @@ package memorymanager import ( "fmt" "sort" + "strings" + "github.com/go-logr/logr" cadvisorapi "github.com/google/cadvisor/info/v1" v1 "k8s.io/api/core/v1" @@ -31,7 +33,9 @@ import ( corehelper "k8s.io/kubernetes/pkg/apis/core/v1/helper" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager/state" + cmqos "k8s.io/kubernetes/pkg/kubelet/cm/qos" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" "k8s.io/kubernetes/pkg/kubelet/metrics" @@ -94,28 +98,353 @@ func (p *staticPolicy) Start(logger klog.Logger, s state.State) error { return nil } +// validatePodScopeResources checks for the "empty shared pool" scenario. This +// occurs when the sum of exclusive container memory requests consumes the +// entire pod-level budget, leaving no memory for containers that require a +// shared pool. Such a configuration is invalid because it would lead to +// containers in the shared pool having no memory allocated, causing them to +// fail. +func (p *staticPolicy) validatePodScopeResources(logger logr.Logger, pod *v1.Pod) error { + podTotalMemory, err := getPodRequestedResources(logger, pod) + if err != nil { + return err + } + + hasSharedLongRunningContainers := false + // Sum memory requests for all containers that run for the full pod lifetime + // (main containers and restartable init containers) to determine the + // total exclusive memory usage that might impact the shared pool. + sumOfLongRunningExclusiveMemory := make(map[v1.ResourceName]uint64) + + // Check for empty shared pool for standard init containers. + for _, container := range pod.Spec.InitContainers { + containerReqs, err := getContainerRequestedResources(logger, pod, &container) + if err != nil { + return err + } + + if !podutil.IsRestartableInitContainer(&container) { + if len(containerReqs) == 0 { + // This check ensures that if there are any standard init containers that need a + // shared pool, that pool is not empty. An empty pool would occur if the pod + // budget is fully consumed by sidecars. + for resourceName, poolSize := range podTotalMemory { + if sumOfLongRunningExclusiveMemory[resourceName] >= poolSize { + return fmt.Errorf("pod rejected, pod has shared init containers but no %s available for them", resourceName) + } + } + } + } else { + // If there are restartable init containers, keep track for the empty shared pool check. + if len(containerReqs) == 0 { + hasSharedLongRunningContainers = true + } else { + for resourceName, reqAmount := range containerReqs { + sumOfLongRunningExclusiveMemory[resourceName] += reqAmount + } + } + } + } + + // Check for empty shared pool for concurrently running containers (app and restartable init containers). + for _, container := range pod.Spec.Containers { + containerReqs, err := getContainerRequestedResources(logger, pod, &container) + if err != nil { + return err + } + + if len(containerReqs) == 0 { + hasSharedLongRunningContainers = true + } else { + for resourceName, val := range containerReqs { + sumOfLongRunningExclusiveMemory[resourceName] += val + } + } + } + + // This check ensures that if there are any standard containers that need a shared + // pool, that pool is not empty. An empty pool would occur if the pod budget is + // fully consumed by sidecars and main containers with exclusive resources. + if hasSharedLongRunningContainers { + for resourceName, totalPodResource := range podTotalMemory { + if sumOfLongRunningExclusiveMemory[resourceName] >= totalPodResource { + return fmt.Errorf("pod rejected, sum of exclusive container %s requests equals pod budget, leaving no memory for shared containers", resourceName) + } + } + } + + return nil +} + +// AllocatePod is the entry point for pod-level resource allocation. +// It's called once per pod by the Topology Manager's pod-scope admit handler. +// The logic here allocates a single NUMA-aligned "bubble" of memory for the +// entire pod. All containers within the pod will share this NUMA binding. +func (p *staticPolicy) AllocatePod(logger klog.Logger, s state.State, pod *v1.Pod) (rerr error) { + podUID := string(pod.UID) + logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod)) + logger.V(4).Info("AllocatePod called for pod-level managed pod") + + qos := v1qos.GetPodQOS(pod) + if qos != v1.PodQOSGuaranteed { + return nil + } + + // 1. Calculate memory resources. + podTotalMemory, err := getPodRequestedResources(logger, pod) + if err != nil { + logger.Error(err, "Failed to get pod requested resources", "podUID", podUID) + return nil + } + logger.V(4).Info("Calculated total pod memory", "podTotalMemory", podTotalMemory) + + metrics.MemoryManagerPinningRequestTotal.Inc() + defer func() { + if rerr != nil { + metrics.MemoryManagerPinningErrorsTotal.Inc() + metrics.ResourceManagerAllocationErrorsTotal.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerPod).Inc() + } + }() + + // 2. Validate for the "empty shared pool" case. + // Even though this is checked during hint generation, we must re-evaluate it here. + // If the Topology Manager is running with the "best-effort" or "none" policies, + // it will still admit the pod even if GetPodTopologyHints returns an empty hint list. + // This ensures we explicitly reject the pod during allocation to prevent Kubelet + // from attempting to create invalid cgroups (e.g., an empty cpuset.mems) for shared containers. + if err := p.validatePodScopeResources(logger, pod); err != nil { + return admission.NewEmptyPodSharedPoolError(err) + } + + // 3. Handle hints and NUMA alignment. + machineState := s.GetMachineState() + bestHint := p.affinity.GetAffinity(podUID, append(pod.Spec.InitContainers, pod.Spec.Containers...)[0].Name) + if bestHint.NUMANodeAffinity == nil { + defaultHint, err := p.getDefaultHint(machineState, pod, podTotalMemory) + if err != nil { + return err + } + if !defaultHint.Preferred && bestHint.Preferred { + return fmt.Errorf("[memorymanager] failed to find the default preferred hint") + } + bestHint = *defaultHint + } + + if !isAffinitySatisfyRequest(machineState, bestHint.NUMANodeAffinity, podTotalMemory) { + extendedHint, err := p.extendTopologyManagerHint(machineState, pod, podTotalMemory, bestHint.NUMANodeAffinity) + if err != nil { + return err + } + if !extendedHint.Preferred && bestHint.Preferred { + return fmt.Errorf("[memorymanager] failed to find the extended preferred hint") + } + bestHint = *extendedHint + } + + if isAffinityViolatingNUMAAllocations(machineState, bestHint.NUMANodeAffinity) { + return fmt.Errorf("[memorymanager] preferred hint violates NUMA node allocation") + } + + maskBits := bestHint.NUMANodeAffinity.GetBits() + + // 4. Partition the pod's memory allocation, handling init container reuse correctly. + exclusiveMemory := make(map[string][]state.Block) + sidecarMemory := make(map[v1.ResourceName]uint64) + + // First, iterate through all init containers and allocate their memory. + for _, c := range pod.Spec.InitContainers { + reqs, err := getContainerRequestedResources(logger, pod, &c) + if err != nil { + return err + } + + metrics.MemoryManagerPinningRequestTotal.Inc() + + // The pool available for this init container is the entire pod allocation + // minus what's already taken by sidecars. + runnablePool := make(map[v1.ResourceName]uint64) + for resourceName, total := range podTotalMemory { + runnablePool[resourceName] = total - sidecarMemory[resourceName] + } + + if len(reqs) == 0 { + // Restartable init containers will access the main pod shared pool. + if podutil.IsRestartableInitContainer(&c) { + continue + } + + // Non restartable init containers will have access to the whole pod pool + // amount minus the sidecar exclusive memory up to that point. + blocks := []state.Block{} + for resourceName, req := range runnablePool { + if req > 0 { + blocks = append(blocks, state.Block{ + NUMAAffinity: maskBits, + Size: req, + Type: resourceName, + }) + } + } + exclusiveMemory[c.Name] = blocks + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerSharedPod).Inc() + continue + } + + // Allocate blocks + blocks := []state.Block{} + for resourceName, req := range reqs { + if req > 0 { + blocks = append(blocks, state.Block{ + NUMAAffinity: maskBits, + Size: req, + Type: resourceName, + }) + } + } + exclusiveMemory[c.Name] = blocks + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerExclusivePod).Inc() + + // If it's a restartable sidecar, its memory is permanently consumed from the pod's budget. + if podutil.IsRestartableInitContainer(&c) { + for resourceName, req := range reqs { + sidecarMemory[resourceName] += req + } + } + } + + // Recalculate the pool available for app containers. + // This is the total pod budget minus what all sidecars are using. + appContainerPool := make(map[v1.ResourceName]uint64) + for resourceName, total := range podTotalMemory { + appContainerPool[resourceName] = total - sidecarMemory[resourceName] + } + + // Second, iterate through regular containers, allocating from the remaining pool. + for _, c := range pod.Spec.Containers { + reqs, err := getContainerRequestedResources(logger, pod, &c) + if err != nil { + // This should not happen, as we have already validated the pod's resources. + return err + } + + // A container with no memory requests will be part of the shared pool. + if len(reqs) == 0 { + continue + } + metrics.MemoryManagerPinningRequestTotal.Inc() + + // Allocate blocks and consume from the pool + blocks := []state.Block{} + for resourceName, req := range reqs { + if req > 0 { + blocks = append(blocks, state.Block{ + NUMAAffinity: maskBits, + Size: req, + Type: resourceName, + }) + appContainerPool[resourceName] -= req + } + } + exclusiveMemory[c.Name] = blocks + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerExclusivePod).Inc() + } + + // The final shared pool is what remains after all exclusive allocations. + podSharedPoolBlocks := []state.Block{} + for resourceName, val := range appContainerPool { + if val > 0 { + podSharedPoolBlocks = append(podSharedPoolBlocks, state.Block{ + NUMAAffinity: maskBits, + Size: val, + Type: resourceName, + }) + } + } + + podAllocation := []state.Block{} + for resourceName, requestedSize := range podTotalMemory { + podAllocation = append(podAllocation, state.Block{ + NUMAAffinity: maskBits, + Size: requestedSize, + Type: resourceName, + }) + } + s.SetPodMemoryBlocks(podUID, podAllocation) + + // 5. Now that partitioning is successful, update the global machine state. + for resourceName, requestedSize := range podTotalMemory { + p.updateMachineState(machineState, maskBits, resourceName, requestedSize) + } + logger.V(4).Info("Partitioned pod-level memory allocation", "exclusiveMemory", podMemoryAllocationToString(exclusiveMemory), "podSharedPool", memoryBlocksToString(podSharedPoolBlocks)) + + // 6. Save all container assignments to the state. + for _, c := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { + if blocks, isExclusive := exclusiveMemory[c.Name]; isExclusive { + s.SetMemoryBlocks(podUID, c.Name, blocks) + } else { + s.SetMemoryBlocks(podUID, c.Name, podSharedPoolBlocks) + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerSharedPod).Inc() + } + + metrics.ResourceManagerAllocationsTotal.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerPod).Inc() + } + + s.SetMachineState(machineState) + return nil +} + +func podMemoryAllocationToString(alloc map[string][]state.Block) string { + if len(alloc) == 0 { + return "" + } + var sb strings.Builder + for name, blocks := range alloc { + fmt.Fprintf(&sb, ",%s=%s", name, memoryBlocksToString(blocks)) + } + // if we have gone so far, it means we have at least 1 alloc, so the cut is safe + repr := sb.String()[1:] + return "<" + repr + ">" +} + +func memoryBlocksToString(blocks []state.Block) string { + if len(blocks) == 0 { + return "[]" + } + var sb strings.Builder + for _, block := range blocks { + // Format: {Type:SizeMi @ [NUMA_Nodes]} + fmt.Fprintf(&sb, ",{%s:%dMi @ %v}", block.Type, block.Size/(1024*1024), block.NUMAAffinity) + } + // Cut the leading comma + repr := sb.String()[1:] + return "[" + repr + "]" +} + // Allocate call is idempotent func (p *staticPolicy) Allocate(logger klog.Logger, s state.State, pod *v1.Pod, container *v1.Container) (rerr error) { // allocate the memory only for guaranteed pods logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod), "containerName", container.Name) + + podUID := string(pod.UID) + // Allocate the memory only for guaranteed pods qos := v1qos.GetPodQOS(pod) if qos != v1.PodQOSGuaranteed { logger.V(5).Info("Exclusive memory allocation skipped, pod QoS is not guaranteed", "qos", qos) return nil } - podUID := string(pod.UID) - if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) { + if (!utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) || !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)) && resourcehelper.IsPodLevelResourcesSet(pod) { logger.V(2).Info("Allocation skipped, pod is using pod-level resources which are not supported by the static Memory manager policy", "podUID", podUID) return nil } logger.Info("Allocate") - // container belongs in an exclusively allocated pool + // Container belongs in an exclusively allocated pool metrics.MemoryManagerPinningRequestTotal.Inc() defer func() { if rerr != nil { metrics.MemoryManagerPinningErrorsTotal.Inc() + metrics.ResourceManagerAllocationErrorsTotal.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerNode).Inc() } }() if blocks := s.GetMemoryBlocks(podUID, container.Name); blocks != nil { @@ -129,7 +458,7 @@ func (p *staticPolicy) Allocate(logger klog.Logger, s state.State, pod *v1.Pod, hint := p.affinity.GetAffinity(podUID, container.Name) logger.Info("Got topology affinity", "hint", hint) - requestedResources, err := getRequestedResources(pod, container) + requestedResources, err := getContainerRequestedResources(logger, pod, container) if err != nil { return err } @@ -190,6 +519,7 @@ func (p *staticPolicy) Allocate(logger klog.Logger, s state.State, pod *v1.Pod, // Update nodes memory state p.updateMachineState(machineState, maskBits, resourceName, requestedSize) + metrics.ResourceManagerAllocationsTotal.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerNode).Inc() } p.updatePodReusableMemory(pod, container, containerBlocks) @@ -204,6 +534,7 @@ func (p *staticPolicy) Allocate(logger klog.Logger, s state.State, pod *v1.Pod, // though the final state here doesn't accurately reflect what was (in reality) allocated to each container // TODO: we should refactor our state structs to reflect the amount of the re-used memory p.updateInitContainersMemoryBlocks(logger, s, pod, container, containerBlocks) + metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerExclusiveNode).Inc() logger.V(4).Info("Allocated exclusive memory") return nil @@ -257,15 +588,35 @@ func (p *staticPolicy) getPodReusableMemory(pod *v1.Pod, numaAffinity bitmask.Bi // RemoveContainer call is idempotent func (p *staticPolicy) RemoveContainer(logger klog.Logger, s state.State, podUID string, containerName string) { logger = klog.LoggerWithValues(logger, "podUID", podUID, "containerName", containerName) + logger.Info("RemoveContainer", "podUID", podUID, "containerName", containerName) + + // Check if this pod is managed with pod-level memory. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + if podMemory := s.GetPodMemoryBlocks(podUID); podMemory != nil { + s.Delete(podUID, containerName) + // If this was the last container for the pod, then release the entire pod-level memory. + if len(s.GetMemoryAssignments()[podUID]) == 0 { + p.releaseMemory(s, podMemory) + s.DeletePod(podUID) // Clean up all state for the pod. + logger.Info("Released pod-level memory") + } + // If other containers still exist, do not release any memory yet. + // The pod-level memory will be released when the last container is removed. + return + } + } blocks := s.GetMemoryBlocks(podUID, containerName) if blocks == nil { return } - - logger.Info("RemoveContainer", "podUID", podUID, "containerName", containerName) s.Delete(podUID, containerName) + p.releaseMemory(s, blocks) + logger.Info("Released container-level memory") +} + +func (p *staticPolicy) releaseMemory(s state.State, blocks []state.Block) { // Mutate machine memory state to update free and reserved memory machineState := s.GetMachineState() for _, b := range blocks { @@ -346,13 +697,32 @@ func regenerateHints(logger klog.Logger, pod *v1.Pod, ctn *v1.Container, ctnBloc return hints } -func getPodRequestedResources(pod *v1.Pod) (map[v1.ResourceName]uint64, error) { +func getPodRequestedResources(logger logr.Logger, pod *v1.Pod) (map[v1.ResourceName]uint64, error) { + // If pod-level resources are set, use them directly. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + requestedResources := make(map[v1.ResourceName]uint64) + for resourceName, quantity := range pod.Spec.Resources.Requests { + if resourceName != v1.ResourceMemory && !corehelper.IsHugePageResourceName(resourceName) { + continue + } + requestedSize, succeed := quantity.AsInt64() + if !succeed { + return nil, fmt.Errorf("[memorymanager] failed to represent quantity as int64") + } + if requestedSize <= 0 { + continue + } + requestedResources[resourceName] = uint64(requestedSize) + } + return requestedResources, nil + } + // Maximun resources requested by init containers at any given time. reqRsrcsByInitCtrs := make(map[v1.ResourceName]uint64) // Total resources requested by restartable init containers. reqRsrcsByRestartableInitCtrs := make(map[v1.ResourceName]uint64) for _, ctr := range pod.Spec.InitContainers { - reqRsrcs, err := getRequestedResources(pod, &ctr) + reqRsrcs, err := getContainerRequestedResources(logger, pod, &ctr) if err != nil { return nil, err @@ -374,7 +744,7 @@ func getPodRequestedResources(pod *v1.Pod) (map[v1.ResourceName]uint64, error) { reqRsrcsByAppCtrs := make(map[v1.ResourceName]uint64) for _, ctr := range pod.Spec.Containers { - reqRsrcs, err := getRequestedResources(pod, &ctr) + reqRsrcs, err := getContainerRequestedResources(logger, pod, &ctr) if err != nil { return nil, err @@ -408,14 +778,37 @@ func (p *staticPolicy) GetPodTopologyHints(logger klog.Logger, s state.State, po return nil } - reqRsrcs, err := getPodRequestedResources(pod) + // Get a count of how much memory has been requested by Pod. + reqRsrcs, err := getPodRequestedResources(logger, pod) if err != nil { logger.Error(err, "Failed to get pod requested resources", "podUID", pod.UID) return nil } - if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) { - logger.V(3).Info("Topology hints generation skipped, pod is using pod-level resources which are not supported by the static Memory manager policy", "podUID", pod.UID) + // If the pod has pod-level resources but the feature gate is disabled, + // log it and return nil hints to admit the pod without alignment. + if (!utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) || !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)) && resourcehelper.IsPodLevelResourcesSet(pod) { + logger.V(3).Info("Memory Manager pod hint generation skipped, pod is using pod-level resources but the PodLevelResourceManagers feature gate is not enabled", "podUID", pod.UID) + return nil + } + + // Validate that if a pod has containers that will be placed in a shared pool, + // there are actually memory left over for that pool after accounting for all + // exclusive allocations. If the sum of exclusive memory requests consumes the + // entire pod-level memory budget, no hints will be generated, causing the pod + // to be rejected by the Topology Manager. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + if err := p.validatePodScopeResources(logger, pod); err != nil { + logger.V(2).Info("Pod admission will fail", "error", err) + hints := make(map[string][]topologymanager.TopologyHint) + for resourceName := range reqRsrcs { + hints[string(resourceName)] = []topologymanager.TopologyHint{} + } + return hints + } + } + + if len(reqRsrcs) == 0 { return nil } @@ -443,14 +836,16 @@ func (p *staticPolicy) GetTopologyHints(logger klog.Logger, s state.State, pod * return nil } - requestedResources, err := getRequestedResources(pod, container) + requestedResources, err := getContainerRequestedResources(logger, pod, container) if err != nil { logger.Error(err, "Failed to get container requested resources", "podUID", pod.UID, "containerName", container.Name) return nil } - if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) { - logger.V(3).Info("Topology hints generation skipped, pod is using pod-level resources which are not supported by the static Memory manager policy", "podUID", pod.UID) + // If the pod has pod-level resources but the feature gate is disabled, + // log it and return nil hints to admit the pod without alignment. + if (!utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) || !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)) && resourcehelper.IsPodLevelResourcesSet(pod) { + logger.V(3).Info("Memory Manager pod hint generation skipped, pod is using pod-level resources but the PodLevelResourceManagers feature gate is not enabled", "podUID", pod.UID) return nil } @@ -465,12 +860,21 @@ func (p *staticPolicy) GetTopologyHints(logger klog.Logger, s state.State, pod * return p.calculateHints(s.GetMachineState(), pod, requestedResources) } -func getRequestedResources(pod *v1.Pod, container *v1.Container) (map[v1.ResourceName]uint64, error) { +func getContainerRequestedResources(logger logr.Logger, pod *v1.Pod, container *v1.Container) (map[v1.ResourceName]uint64, error) { requestedResources := map[v1.ResourceName]uint64{} + // For pod-level resource management, a container is only considered for exclusive + // memory if its request equals its limit for both the CPU and Memory. This + // aligns with the Guaranteed QoS requirement for container-level resources. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) && !cmqos.IsContainerEquivalentQOSGuaranteed(container) { + logger.V(5).Info("Exclusive Memory allocation skipped, container is not eligible, request and limit are not equal", "pod", klog.KObj(pod), "containerName", container.Name) + return requestedResources, nil + } + for resourceName, quantity := range container.Resources.Requests { if resourceName != v1.ResourceMemory && !corehelper.IsHugePageResourceName(resourceName) { continue } + requestedSize, succeed := quantity.AsInt64() if !succeed { return nil, fmt.Errorf("[memorymanager] failed to represent quantity as int64") @@ -614,45 +1018,18 @@ func (p *staticPolicy) validateState(logger klog.Logger, s state.State) error { // calculate all memory assigned to containers expectedMachineState := p.getDefaultMachineState() for pod, container := range memoryAssignments { - for containerName, blocks := range container { - for _, b := range blocks { - requestedSize := b.Size - for _, nodeID := range b.NUMAAffinity { - nodeState, ok := expectedMachineState[nodeID] - if !ok { - return fmt.Errorf("[memorymanager] (pod: %s, container: %s) the memory assignment uses the NUMA that does not exist", pod, containerName) - } - - nodeState.NumberOfAssignments++ - nodeState.Cells = b.NUMAAffinity - - memoryState, ok := nodeState.MemoryMap[b.Type] - if !ok { - return fmt.Errorf("[memorymanager] (pod: %s, container: %s) the memory assignment uses memory resource that does not exist", pod, containerName) - } - - if requestedSize == 0 { - continue - } - - // this node does not have enough memory continue to the next one - if memoryState.Free <= 0 { - continue - } - - // the node has enough memory to satisfy the request - if memoryState.Free >= requestedSize { - memoryState.Reserved += requestedSize - memoryState.Free -= requestedSize - requestedSize = 0 - continue - } - - // the node does not have enough memory, use the node remaining memory and move to the next node - requestedSize -= memoryState.Free - memoryState.Reserved += memoryState.Free - memoryState.Free = 0 + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + if podBlocks := s.GetPodMemoryBlocks(pod); len(podBlocks) > 0 { + if err := p.updateExpectedMachineState(expectedMachineState, podBlocks, pod, ""); err != nil { + return err } + continue + } + } + + for containerName, blocks := range container { + if err := p.updateExpectedMachineState(expectedMachineState, blocks, pod, containerName); err != nil { + return err } } } @@ -668,6 +1045,49 @@ func (p *staticPolicy) validateState(logger klog.Logger, s state.State) error { return nil } +func (p *staticPolicy) updateExpectedMachineState(expectedMachineState state.NUMANodeMap, blocks []state.Block, podUID, containerName string) error { + for _, b := range blocks { + requestedSize := b.Size + for _, nodeID := range b.NUMAAffinity { + nodeState, ok := expectedMachineState[nodeID] + if !ok { + return fmt.Errorf("[memorymanager] (pod: %s, container: %s) the memory assignment uses the NUMA that does not exist", podUID, containerName) + } + + nodeState.NumberOfAssignments++ + nodeState.Cells = b.NUMAAffinity + + memoryState, ok := nodeState.MemoryMap[b.Type] + if !ok { + return fmt.Errorf("[memorymanager] (pod: %s, container: %s) the memory assignment uses memory resource that does not exist", podUID, containerName) + } + + if requestedSize == 0 { + continue + } + + // this node does not have enough memory continue to the next one + if memoryState.Free <= 0 { + continue + } + + // the node has enough memory to satisfy the request + if memoryState.Free >= requestedSize { + memoryState.Reserved += requestedSize + memoryState.Free -= requestedSize + requestedSize = 0 + continue + } + + // the node does not have enough memory, use the node remaining memory and move to the next node + requestedSize -= memoryState.Free + memoryState.Reserved += memoryState.Free + memoryState.Free = 0 + } + } + return nil +} + func areMachineStatesEqual(logger klog.Logger, ms1, ms2 state.NUMANodeMap) bool { if len(ms1) != len(ms2) { logger.Info("Node states were different", "lengthNode1", len(ms1), "lengthNode2", len(ms2)) diff --git a/pkg/kubelet/cm/memorymanager/policy_static_restore_test.go b/pkg/kubelet/cm/memorymanager/policy_static_restore_test.go new file mode 100644 index 0000000000000..7bebe05e2359c --- /dev/null +++ b/pkg/kubelet/cm/memorymanager/policy_static_restore_test.go @@ -0,0 +1,203 @@ +/* +Copyright 2025 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 memorymanager + +import ( + "context" + "runtime" + "testing" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + resourcehelper "k8s.io/component-helpers/resource" + "k8s.io/kubernetes/pkg/features" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" + "k8s.io/kubernetes/pkg/kubelet/cm/containermap" + "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" + "k8s.io/kubernetes/test/utils/ktesting" +) + +// For the scope of the test, any pod that has pod-level resources and the +// PodLevelResourceManagers feature is enabled, will be processed by AllocatePod +func TestMemoryManagerRestoreState(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Memory Manager static policy is not available on Windows") + } + + testCases := []struct { + description string + podLevelResourcesEnabled bool + podLevelResourceManagersEnabled bool + podMemoryRequest string + containers []containerSpec + expectPodBlocks bool + }{ + { + description: "PodLevelResources and PodLevelResourceManagers enabled", + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + podMemoryRequest: "128Mi", + containers: []containerSpec{ + {name: "container1", memRequest: "100Mi", memLimit: "100Mi"}, + }, + expectPodBlocks: true, + }, + { + description: "PodLevelResources enabled, PodLevelResourceManagers disabled", + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: false, + podMemoryRequest: "128Mi", + containers: []containerSpec{ + {name: "container1", memRequest: "100Mi", memLimit: "100Mi"}, + }, + expectPodBlocks: false, + }, + { + description: "Container-level pod, features enabled", + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + podMemoryRequest: "", + containers: []containerSpec{ + {name: "container1", memRequest: "100Mi", memLimit: "100Mi"}, + {name: "container2", memRequest: "100Mi", memLimit: "100Mi"}, + }, + expectPodBlocks: false, + }, + { + description: "Container-level pod, features disabled", + podLevelResourcesEnabled: false, + podLevelResourceManagersEnabled: false, + podMemoryRequest: "", + containers: []containerSpec{ + {name: "container1", memRequest: "100Mi", memLimit: "100Mi"}, + {name: "container2", memRequest: "100Mi", memLimit: "100Mi"}, + }, + expectPodBlocks: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, tc.podLevelResourceManagersEnabled) + + logger, _ := ktesting.NewTestContext(t) + machineInfo := returnMachineInfo() + nodeAllocatableReservation := v1.ResourceList{ + v1.ResourceMemory: *resource.NewQuantity(2*gb, resource.BinarySI), + } + systemReservedMemory := []kubeletconfig.MemoryReservation{ + { + NumaNode: 0, + Limits: v1.ResourceList{ + v1.ResourceMemory: *resource.NewQuantity(gb, resource.BinarySI), + }, + }, + { + NumaNode: 1, + Limits: v1.ResourceList{ + v1.ResourceMemory: *resource.NewQuantity(gb, resource.BinarySI), + }, + }, + } + affinity := topologymanager.NewFakeManager() + + // Create new manager + sDir := t.TempDir() + mgr, err := NewManager(logger, string(PolicyTypeStatic), &machineInfo, nodeAllocatableReservation, systemReservedMemory, sDir, affinity) + if err != nil { + t.Fatalf("could not create manager: %v", err) + } + + // Create a pod with pod-level resources + pod := getPodWithContainersAndPodLevelResources("pod1", tc.podMemoryRequest, tc.podMemoryRequest, nil, tc.containers) + + // Start manager to initialize state + err = mgr.Start(context.Background(), func() []*v1.Pod { return []*v1.Pod{pod} }, &sourcesReadyStub{}, mockPodStatusProvider{}, mockRuntimeService{}, containermap.NewContainerMap()) + if err != nil { + t.Fatalf("could not start manager: %v", err) + } + + // Allocate resources + if tc.podLevelResourceManagersEnabled && resourcehelper.IsPodLevelResourcesSet(pod) { + err = mgr.AllocatePod(pod) + if err != nil { + t.Fatalf("could not allocate pod: %v", err) + } + } else { + // Add containers (allocates exclusive resources from the pod pool) + for i := range pod.Spec.Containers { + container := &pod.Spec.Containers[i] + err = mgr.Allocate(pod, container) + if err != nil { + t.Fatalf("could not allocate container %s: %v", container.Name, err) + } + mgr.AddContainer(logger, pod, container, container.Name) + } + } + + // Verify state before restart + podBlocks := mgr.State().GetPodMemoryBlocks(string(pod.UID)) + if tc.expectPodBlocks && len(podBlocks) == 0 { + t.Errorf("expected pod memory blocks to be present") + } else if !tc.expectPodBlocks && len(podBlocks) > 0 { + t.Errorf("expected no pod memory blocks, but got some") + } + + // Re-create manager to simulate restart + mgr2, err := NewManager(logger, string(PolicyTypeStatic), &machineInfo, nodeAllocatableReservation, systemReservedMemory, sDir, affinity) + if err != nil { + t.Fatalf("could not create manager 2: %v", err) + } + + err = mgr2.Start(context.Background(), func() []*v1.Pod { return []*v1.Pod{pod} }, &sourcesReadyStub{}, mockPodStatusProvider{}, mockRuntimeService{}, containermap.NewContainerMap()) + if err != nil { + t.Fatalf("could not start manager 2: %v", err) + } + + // Verify state restored + podBlocksRestored := mgr2.State().GetPodMemoryBlocks(string(pod.UID)) + if tc.expectPodBlocks { + if len(podBlocksRestored) == 0 { + t.Errorf("expected pod memory blocks to be present after restore") + } + if len(podBlocksRestored) != len(podBlocks) { + t.Errorf("expected pod memory blocks count to match, got %d want %d", len(podBlocksRestored), len(podBlocks)) + } + } else if len(podBlocksRestored) > 0 { + t.Errorf("expected no pod memory blocks after restore, but got some") + } + + // Verify containers restored + for _, container := range pod.Spec.Containers { + containerBlocksRestored := mgr2.State().GetMemoryBlocks(string(pod.UID), container.Name) + // If pod-level resources are enabled but managers are disabled, allocation is skipped, so no blocks. + if tc.podLevelResourcesEnabled && !tc.podLevelResourceManagersEnabled { + if len(containerBlocksRestored) > 0 { + t.Errorf("expected no container memory blocks after restore (allocation skipped) for %s, but got some", container.Name) + } + } else { + if len(containerBlocksRestored) == 0 { + t.Errorf("expected container memory blocks to be present after restore for %s", container.Name) + } + } + } + }) + } +} diff --git a/pkg/kubelet/cm/memorymanager/policy_static_test.go b/pkg/kubelet/cm/memorymanager/policy_static_test.go index 5f51082f87b3d..0464ce71c5cdd 100644 --- a/pkg/kubelet/cm/memorymanager/policy_static_test.go +++ b/pkg/kubelet/cm/memorymanager/policy_static_test.go @@ -19,19 +19,27 @@ package memorymanager import ( "fmt" "reflect" + "strconv" "testing" cadvisorapi "github.com/google/cadvisor/info/v1" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/metrics/testutil" "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/cm/memorymanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" + "k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/test/utils/ktesting" ) @@ -47,11 +55,22 @@ var ( podLevelRequirementsGuaranteed = &v1.ResourceRequirements{ Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000Mi"), + v1.ResourceCPU: resource.MustParse("1000m"), v1.ResourceMemory: resource.MustParse("1Gi"), }, Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000Mi"), + v1.ResourceCPU: resource.MustParse("1000m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + } + + podLevelRequirementsBurstable = &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1000m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("500m"), v1.ResourceMemory: resource.MustParse("1Gi"), }, } @@ -133,19 +152,28 @@ func areContainerMemoryAssignmentsEqual(t *testing.T, cma1, cma2 state.Container } type testStaticPolicy struct { - description string - assignments state.ContainerMemoryAssignments - expectedAssignments state.ContainerMemoryAssignments - machineState state.NUMANodeMap - expectedMachineState state.NUMANodeMap - systemReserved systemReservedMemory - expectedError error - machineInfo *cadvisorapi.MachineInfo - pod *v1.Pod - topologyHint *topologymanager.TopologyHint - expectedTopologyHints map[string][]topologymanager.TopologyHint - initContainersReusableMemory reusableMemory - podLevelResourcesEnabled bool + description string + assignments state.ContainerMemoryAssignments + expectedAssignments state.ContainerMemoryAssignments + machineState state.NUMANodeMap + expectedMachineState state.NUMANodeMap + systemReserved systemReservedMemory + expectedError error + machineInfo *cadvisorapi.MachineInfo + pod *v1.Pod + topologyHint *topologymanager.TopologyHint + expectedTopologyHints map[string][]topologymanager.TopologyHint + initContainersReusableMemory reusableMemory + podLevelResourcesEnabled bool + podLevelResourceManagersEnabled bool + requiredMetrics requiredMetrics +} + +type requiredMetrics struct { + expTotalAllocs int + expTotalErrors int + expExclusiveAssignments int + expPodSharedPoolAssignments int } func initTests(t *testing.T, testCase *testStaticPolicy, hint *topologymanager.TopologyHint, initContainersReusableMemory reusableMemory) (Policy, state.State, error) { @@ -3816,51 +3844,142 @@ func TestStaticPolicyGetTopologyHints(t *testing.T) { expectedTopologyHints: nil, podLevelResourcesEnabled: true, }, - } - - for _, testCase := range testCases { - t.Run(testCase.description, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, testCase.podLevelResourcesEnabled) - - p, s, err := initTests(t, &testCase, nil, nil) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - topologyHints := p.GetTopologyHints(logger, s, testCase.pod, &testCase.pod.Spec.Containers[0]) - if !reflect.DeepEqual(topologyHints, testCase.expectedTopologyHints) { - t.Fatalf("The actual topology hints: '%+v' are different from the expected one: '%+v'", topologyHints, testCase.expectedTopologyHints) - } - }) - } -} - -func TestStaticPolicyGetPodTopologyHints(t *testing.T) { - logger, _ := ktesting.NewTestContext(t) - testCases := []testStaticPolicy{ { - description: "should not provide pod topology hints for guaranteed pod with pod level resources", + description: "should provide pod topology hints for guaranteed pod with pod level resources when PodLevelResourceManagers is enabled", pod: getPodWithPodLevelResources("pod1", podLevelRequirementsGuaranteed, "container1", requirementsGuaranteed), + machineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, + }, + hugepages1Gi: { + Allocatable: gb, + Free: gb, + Reserved: 0, + SystemReserved: 0, + TotalMemSize: gb, + }, + }, + Cells: []int{0}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, + }, + hugepages1Gi: { + Allocatable: gb, + Free: gb, + Reserved: 0, + SystemReserved: 0, + TotalMemSize: gb, + }, + }, + Cells: []int{1}, + }, + }, systemReserved: systemReservedMemory{ 0: map[v1.ResourceName]uint64{ v1.ResourceMemory: 1024 * mb, }, + 1: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, }, - expectedTopologyHints: nil, - podLevelResourcesEnabled: true, + machineInfo: &cadvisorapi.MachineInfo{ + Topology: []cadvisorapi.Node{ + { + Id: 0, + Memory: 3 * gb, + HugePages: []cadvisorapi.HugePagesInfo{ + { + PageSize: pageSize1Gb, + NumPages: 1, + }, + }, + }, + { + Id: 1, + Memory: 3 * gb, + HugePages: []cadvisorapi.HugePagesInfo{ + { + PageSize: pageSize1Gb, + NumPages: 1, + }, + }, + }, + }, + }, + expectedTopologyHints: map[string][]topologymanager.TopologyHint{ + string(v1.ResourceMemory): { + { + NUMANodeAffinity: newNUMAAffinity(0), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(1), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(0, 1), + Preferred: false, + }, + }, + string(hugepages1Gi): { + { + NUMANodeAffinity: newNUMAAffinity(0), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(1), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(0, 1), + Preferred: false, + }, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + }, { + description: "should not provide pod topology hints for burstable pod with pod level resources when PodLevelResourceManagers is enabled", + pod: getPodWithPodLevelResources("pod1", podLevelRequirementsBurstable, "container1", requirementsGuaranteed), + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + expectedTopologyHints: nil, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, }, } for _, testCase := range testCases { t.Run(testCase.description, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, testCase.podLevelResourcesEnabled) + if testCase.podLevelResourcesEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, testCase.podLevelResourcesEnabled) + } + if testCase.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, testCase.podLevelResourceManagersEnabled) + } p, s, err := initTests(t, &testCase, nil, nil) if err != nil { t.Fatalf("Unexpected error: %v", err) } - topologyHints := p.GetPodTopologyHints(logger, s, testCase.pod) + topologyHints := p.GetTopologyHints(logger, s, testCase.pod, &testCase.pod.Spec.Containers[0]) if !reflect.DeepEqual(topologyHints, testCase.expectedTopologyHints) { t.Fatalf("The actual topology hints: '%+v' are different from the expected one: '%+v'", topologyHints, testCase.expectedTopologyHints) } @@ -3868,114 +3987,333 @@ func TestStaticPolicyGetPodTopologyHints(t *testing.T) { } } -func Test_getPodRequestedResources(t *testing.T) { - testCases := []struct { - description string - pod *v1.Pod - expected map[v1.ResourceName]uint64 - }{ +func TestStaticPolicyGetPodTopologyHints(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []testStaticPolicy{ { - description: "maximum resources of init containers > total resources of containers", - pod: getPodWithInitContainers( - "", - []v1.Container{ - { - Name: "container1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("1Gi"), - hugepages1Gi: resource.MustParse("1Gi"), - }, + description: "should provide pod topology hints for guaranteed pod without container level resources when PodLevelResources and PodLevelResourceManagers are disabled", + pod: getPod("pod1", "container1", requirementsGuaranteed), + machineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, }, - }, - { - Name: "container2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("2Gi"), - hugepages1Gi: resource.MustParse("2Gi"), - }, + hugepages1Gi: { + Allocatable: gb, + Free: gb, + Reserved: 0, + SystemReserved: 0, + TotalMemSize: gb, }, }, + Cells: []int{0}, }, - []v1.Container{ - { - Name: "initContainer1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("1Gi"), - hugepages1Gi: resource.MustParse("1Gi"), - }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, }, - }, - { - Name: "initContainer2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("4Gi"), - hugepages1Gi: resource.MustParse("4Gi"), - }, + hugepages1Gi: { + Allocatable: gb, + Free: gb, + Reserved: 0, + SystemReserved: 0, + TotalMemSize: gb, }, }, + Cells: []int{1}, }, - ), - expected: map[v1.ResourceName]uint64{ - v1.ResourceMemory: 4 * gb, - hugepages1Gi: 4 * gb, }, - }, - { - description: "maximum resources of init containers < total resources of containers", - pod: getPodWithInitContainers( - "", - []v1.Container{ + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + 1: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + machineInfo: &cadvisorapi.MachineInfo{ + Topology: []cadvisorapi.Node{ { - Name: "container1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("2Gi"), - hugepages1Gi: resource.MustParse("2Gi"), + Id: 0, + Memory: 3 * gb, + HugePages: []cadvisorapi.HugePagesInfo{ + { + PageSize: pageSize1Gb, + NumPages: 1, }, }, }, { - Name: "container2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("3Gi"), - hugepages1Gi: resource.MustParse("3Gi"), + Id: 1, + Memory: 3 * gb, + HugePages: []cadvisorapi.HugePagesInfo{ + { + PageSize: pageSize1Gb, + NumPages: 1, }, }, }, }, - []v1.Container{ + }, + expectedTopologyHints: map[string][]topologymanager.TopologyHint{ + string(v1.ResourceMemory): { { - Name: "initContainer1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("1Gi"), - hugepages1Gi: resource.MustParse("1Gi"), - }, + NUMANodeAffinity: newNUMAAffinity(0), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(1), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(0, 1), + Preferred: false, + }, + }, + string(hugepages1Gi): { + { + NUMANodeAffinity: newNUMAAffinity(0), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(1), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(0, 1), + Preferred: false, + }, + }, + }, + podLevelResourcesEnabled: false, + podLevelResourceManagersEnabled: false, + }, + { + description: "should not provide pod topology hints for burstable pod with container level resources when PodLevelResources and PodLevelResourceManagers are disabled", + pod: getPod("pod1", "container1", requirementsBurstable), + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + expectedTopologyHints: nil, + podLevelResourcesEnabled: false, + podLevelResourceManagersEnabled: false, + }, + { + description: "should not provide pod topology hints for guaranteed pod with pod level resources when PodLevelResourceManagers is disabled", + pod: getPodWithPodLevelResources("pod1", podLevelRequirementsGuaranteed, "container1", requirementsGuaranteed), + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + expectedTopologyHints: nil, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: false, + }, + { + description: "should provide pod topology hints for guaranteed pod with pod level resources when PodLevelResourceManagers is enabled", + pod: getPodWithPodLevelResources("pod1", podLevelRequirementsGuaranteed, "container1", requirementsGuaranteed), + machineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, + }, + }, + Cells: []int{0}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, }, }, + Cells: []int{1}, + }, + }, + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + 1: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + machineInfo: &cadvisorapi.MachineInfo{ + Topology: []cadvisorapi.Node{ { - Name: "initContainer2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("3Gi"), - hugepages1Gi: resource.MustParse("3Gi"), - }, + Id: 0, + Memory: 3 * gb, + }, + { + Id: 1, + Memory: 3 * gb, + }, + }, + }, + expectedTopologyHints: map[string][]topologymanager.TopologyHint{ + string(v1.ResourceMemory): { + { + NUMANodeAffinity: newNUMAAffinity(0), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(1), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(0, 1), + Preferred: false, + }, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + }, + { + description: "should provide pod topology hints for guaranteed pod with pod level resources that overrides container level resources", + pod: getPodWithPodLevelResources("pod1", podLevelRequirementsGuaranteed, "container1", requirementsBurstable), + machineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, }, }, + Cells: []int{0}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 2 * gb, + Free: 2 * gb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 3 * gb, + }, + }, + Cells: []int{1}, + }, + }, + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + 1: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + machineInfo: &cadvisorapi.MachineInfo{ + Topology: []cadvisorapi.Node{ + { + Id: 0, + Memory: 3 * gb, + }, + { + Id: 1, + Memory: 3 * gb, + }, + }, + }, + expectedTopologyHints: map[string][]topologymanager.TopologyHint{ + string(v1.ResourceMemory): { + { + NUMANodeAffinity: newNUMAAffinity(0), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(1), + Preferred: true, + }, + { + NUMANodeAffinity: newNUMAAffinity(0, 1), + Preferred: false, + }, }, - ), - expected: map[v1.ResourceName]uint64{ - v1.ResourceMemory: 5 * gb, - hugepages1Gi: 5 * gb, }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, }, { - description: "calculate different resources independently", + description: "should not provide pod topology hints for burstable pod with pod level resources when PodLevelResourceManagers is enabled", + pod: getPodWithPodLevelResources("pod1", podLevelRequirementsBurstable, "container1", requirementsGuaranteed), + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + expectedTopologyHints: nil, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + }, + { + description: "should not provide pod topology hints for burstable pod with pod level and container level burstable resources", + pod: getPodWithPodLevelResources("pod1", podLevelRequirementsBurstable, "container1", requirementsBurstable), + systemReserved: systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 1024 * mb, + }, + }, + expectedTopologyHints: nil, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + if testCase.podLevelResourcesEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, testCase.podLevelResourcesEnabled) + } + if testCase.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, testCase.podLevelResourceManagersEnabled) + } + + p, s, err := initTests(t, &testCase, nil, nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + topologyHints := p.GetPodTopologyHints(logger, s, testCase.pod) + if !reflect.DeepEqual(topologyHints, testCase.expectedTopologyHints) { + t.Fatalf("The actual topology hints: '%+v' are different from the expected one: '%+v'", topologyHints, testCase.expectedTopologyHints) + } + }) + } +} + +func Test_getPodRequestedResources(t *testing.T) { + testCases := []struct { + description string + pod *v1.Pod + expected map[v1.ResourceName]uint64 + }{ + { + description: "maximum resources of init containers > total resources of containers", pod: getPodWithInitContainers( "", []v1.Container{ @@ -3983,7 +4321,7 @@ func Test_getPodRequestedResources(t *testing.T) { Name: "container1", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("2Gi"), + v1.ResourceMemory: resource.MustParse("1Gi"), hugepages1Gi: resource.MustParse("1Gi"), }, }, @@ -3992,7 +4330,7 @@ func Test_getPodRequestedResources(t *testing.T) { Name: "container2", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("3Gi"), + v1.ResourceMemory: resource.MustParse("2Gi"), hugepages1Gi: resource.MustParse("2Gi"), }, }, @@ -4012,7 +4350,7 @@ func Test_getPodRequestedResources(t *testing.T) { Name: "initContainer2", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("3Gi"), + v1.ResourceMemory: resource.MustParse("4Gi"), hugepages1Gi: resource.MustParse("4Gi"), }, }, @@ -4020,12 +4358,12 @@ func Test_getPodRequestedResources(t *testing.T) { }, ), expected: map[v1.ResourceName]uint64{ - v1.ResourceMemory: 5 * gb, + v1.ResourceMemory: 4 * gb, hugepages1Gi: 4 * gb, }, }, { - description: "maximum resources of init containers > total resources of long running containers, including restartable init containers", + description: "maximum resources of init containers < total resources of containers", pod: getPodWithInitContainers( "", []v1.Container{ @@ -4033,8 +4371,8 @@ func Test_getPodRequestedResources(t *testing.T) { Name: "container1", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("1Gi"), - hugepages1Gi: resource.MustParse("1Gi"), + v1.ResourceMemory: resource.MustParse("2Gi"), + hugepages1Gi: resource.MustParse("2Gi"), }, }, }, @@ -4042,41 +4380,141 @@ func Test_getPodRequestedResources(t *testing.T) { Name: "container2", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("2Gi"), - hugepages1Gi: resource.MustParse("2Gi"), + v1.ResourceMemory: resource.MustParse("3Gi"), + hugepages1Gi: resource.MustParse("3Gi"), }, }, }, }, []v1.Container{ { - Name: "restartableInit1", + Name: "initContainer1", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("2Gi"), - hugepages1Gi: resource.MustParse("2Gi"), + v1.ResourceMemory: resource.MustParse("1Gi"), + hugepages1Gi: resource.MustParse("1Gi"), }, }, - RestartPolicy: &containerRestartPolicyAlways, }, { Name: "initContainer2", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("4Gi"), - hugepages1Gi: resource.MustParse("4Gi"), + v1.ResourceMemory: resource.MustParse("3Gi"), + hugepages1Gi: resource.MustParse("3Gi"), }, }, }, }, ), expected: map[v1.ResourceName]uint64{ - v1.ResourceMemory: 6 * gb, - hugepages1Gi: 6 * gb, + v1.ResourceMemory: 5 * gb, + hugepages1Gi: 5 * gb, }, }, { - description: "maximum resources of init containers < total resources of long running containers, including restartable init containers", + description: "calculate different resources independently", + pod: getPodWithInitContainers( + "", + []v1.Container{ + { + Name: "container1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("2Gi"), + hugepages1Gi: resource.MustParse("1Gi"), + }, + }, + }, + { + Name: "container2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("3Gi"), + hugepages1Gi: resource.MustParse("2Gi"), + }, + }, + }, + }, + []v1.Container{ + { + Name: "initContainer1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("1Gi"), + hugepages1Gi: resource.MustParse("1Gi"), + }, + }, + }, + { + Name: "initContainer2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("3Gi"), + hugepages1Gi: resource.MustParse("4Gi"), + }, + }, + }, + }, + ), + expected: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 5 * gb, + hugepages1Gi: 4 * gb, + }, + }, + { + description: "maximum resources of init containers > total resources of long running containers, including restartable init containers", + pod: getPodWithInitContainers( + "", + []v1.Container{ + { + Name: "container1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("1Gi"), + hugepages1Gi: resource.MustParse("1Gi"), + }, + }, + }, + { + Name: "container2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("2Gi"), + hugepages1Gi: resource.MustParse("2Gi"), + }, + }, + }, + }, + []v1.Container{ + { + Name: "restartableInit1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("2Gi"), + hugepages1Gi: resource.MustParse("2Gi"), + }, + }, + RestartPolicy: &containerRestartPolicyAlways, + }, + { + Name: "initContainer2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("4Gi"), + hugepages1Gi: resource.MustParse("4Gi"), + }, + }, + }, + }, + ), + expected: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 6 * gb, + hugepages1Gi: 6 * gb, + }, + }, + { + description: "maximum resources of init containers < total resources of long running containers, including restartable init containers", pod: getPodWithInitContainers( "", []v1.Container{ @@ -4130,7 +4568,9 @@ func Test_getPodRequestedResources(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { - actual, err := getPodRequestedResources(tc.pod) + logger, _ := ktesting.NewTestContext(t) + + actual, err := getPodRequestedResources(logger, tc.pod) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -4216,3 +4656,1053 @@ func Test_isAffinityViolatingNUMAAllocations(t *testing.T) { }) } } + +type containerOptions struct { + name string + request string + limit string + restartPolicy *v1.ContainerRestartPolicy +} + +type podOption func(*v1.Pod) + +func withPodResources(memRequest, memLimit string) podOption { + return func(pod *v1.Pod) { + pod.Spec.Resources = &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse(memRequest), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse(memLimit), + }, + } + } +} + +func makeMultiContainerPodWithOptions(initContainers, appContainers []*containerOptions, podOptions ...podOption) *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + UID: "podUID", + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{}, + Containers: []v1.Container{}, + }, + } + + for i, opts := range initContainers { + container := v1.Container{ + Name: "initContainer-" + strconv.Itoa(i), + } + if opts.request != "" { + container.Resources = v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse(opts.request), + v1.ResourceCPU: resource.MustParse("1"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse(opts.limit), + v1.ResourceCPU: resource.MustParse("1"), + }, + } + } + if opts.restartPolicy != nil { + container.RestartPolicy = opts.restartPolicy + } + pod.Spec.InitContainers = append(pod.Spec.InitContainers, container) + } + + for i, opts := range appContainers { + container := v1.Container{ + Name: "appContainer-" + strconv.Itoa(i), + } + if opts.request != "" { + container.Resources = v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse(opts.request), + v1.ResourceCPU: resource.MustParse("1"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse(opts.limit), + v1.ResourceCPU: resource.MustParse("1"), + }, + } + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + + for _, fn := range podOptions { + fn(pod) + } + + return pod +} + +func makeMultiContainerPodWithOptionsAndPodLevelResources(podLevelMemory string, initContainers, appContainers []*containerOptions) *v1.Pod { + return makeMultiContainerPodWithOptions(initContainers, appContainers, withPodResources(podLevelMemory, podLevelMemory)) +} + +func TestValidatePodScopeResources(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, true) + testCases := []struct { + name string + pod *v1.Pod + expectedErr error + scope string + features map[featuregate.Feature]bool + }{ + { + name: "Valid: Pod-level != Guaranteed containers, has podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "1Gi", limit: "1Gi"}, + {name: "c2"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Only Guaranteed containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "1Gi", limit: "1Gi"}, + {name: "c2", request: "1Gi", limit: "1Gi"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Only podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{}, + []*containerOptions{ + {name: "c1"}, + {name: "c2"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Pod-level == Guaranteed containers, has podSharedPool containers with request, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "6Gi", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "3Gi", limit: "3Gi"}, + {name: "c2", request: "2Gi", limit: "2Gi"}, + {name: "c3", request: "500Gi", limit: "1Gi"}, + {name: "c4"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Failure: Pod-level == Guaranteed containers, has podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "5Gi", + []*containerOptions{}, + []*containerOptions{ + {name: "c1", request: "2Gi", limit: "2Gi"}, + {name: "c2", request: "3Gi", limit: "3Gi"}, + {name: "c3"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: fmt.Errorf("pod rejected, sum of exclusive container memory requests equals pod budget, leaving no memory for shared containers"), + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Pod-level resources with standard and restartable init containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "init-c1", request: "1Gi", limit: "1Gi"}, + {name: "restartable-init-c1", request: "1Gi", limit: "1Gi", restartPolicy: &containerRestartPolicyAlways}, + }, + []*containerOptions{ + {name: "c1"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Pod-level resources equal init and standard container resources without non-guaranteed containers do not produce empty shared pool error, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "restartable-init-c1", request: "1Gi", limit: "1Gi", restartPolicy: &containerRestartPolicyAlways}, + }, + []*containerOptions{ + {name: "c1", request: "1Gi", limit: "1Gi"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Failure: Pod-level resources when guaranteed sidecar that equals pod resources and then non-guaranteed init container produce empty shared pool error, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "restartable-init-c1", request: "2Gi", limit: "2Gi", restartPolicy: &containerRestartPolicyAlways}, + {name: "init-c1"}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: fmt.Errorf("pod rejected, pod has shared init containers but no memory available for them"), + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Pod-level resources when non-guaranteed standard init container first and then guaranteed sidecar container do not produce empty shared pool error, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "init-c1"}, + {name: "restartable-init-c1", request: "2Gi", limit: "2Gi", restartPolicy: &containerRestartPolicyAlways}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Pod-level resources when guaranteed standard init container first and then guaranteed sidecar container do not produce empty shared pool error, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "init-c1", request: "2Gi", limit: "2Gi"}, + {name: "restartable-init-c1", request: "2Gi", limit: "2Gi", restartPolicy: &containerRestartPolicyAlways}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Failure: Pod-level == Restartable guaranteed init containers, has podSharedPool containers, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "restartable-init-c1", request: "2Gi", limit: "2Gi", restartPolicy: &containerRestartPolicyAlways}, + {name: "restartable-init-c2", restartPolicy: &containerRestartPolicyAlways}, + }, + []*containerOptions{}, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: fmt.Errorf("pod rejected, sum of exclusive container memory requests equals pod budget, leaving no memory for shared containers"), + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + { + name: "Valid: Pod-level resources with only restartable one init container and one standard container, PLR: Enabled, PLRM: Enabled", + pod: makeMultiContainerPodWithOptionsAndPodLevelResources( + "2Gi", + []*containerOptions{ + {name: "restartable-init-c", request: "1Gi", limit: "1Gi", restartPolicy: &containerRestartPolicyAlways}, + }, + []*containerOptions{ + {name: "c1", request: "1Gi", limit: "1Gi"}, + }, + ), + scope: topologymanager.PodTopologyScope, + expectedErr: nil, + features: map[featuregate.Feature]bool{features.PodLevelResources: true, features.PodLevelResourceManagers: true}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for feature, enabled := range tc.features { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, enabled) + } + + logger, _ := ktesting.NewTestContext(t) + machineInfo := &cadvisorapi.MachineInfo{ + Topology: []cadvisorapi.Node{ + { + Id: 0, + Memory: 8 * gb, + }, + }, + } + systemReserved := systemReservedMemory{ + 0: map[v1.ResourceName]uint64{ + v1.ResourceMemory: 256 * mb, + }, + } + policy, err := NewPolicyStatic(logger, machineInfo, systemReserved, topologymanager.NewFakeManagerWithScope(tc.scope)) + if err != nil { + t.Fatalf("NewPolicyStatic() failed: %v", err) + } + + err = policy.(*staticPolicy).validatePodScopeResources(logger, tc.pod) + if tc.expectedErr != nil { + if err == nil { + t.Errorf("Expected error %q, got nil", tc.expectedErr.Error()) + } else if err.Error() != tc.expectedErr.Error() { + t.Errorf("Expected error %q, got %q", tc.expectedErr.Error(), err.Error()) + } + } else if err != nil { + t.Errorf("Unexpected error: %v", err) + } + }) + } +} + +type containerSpec struct { + name string + memRequest string + memLimit string + restartPolicy *v1.ContainerRestartPolicy +} + +func getPodWithContainersAndPodLevelResources(podName, podMemRequest, podMemLimit string, initContainers, appContainers []containerSpec) *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + UID: "podUID", // Using a static UID for simplicity in tests, can be overridden. + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{}, + Containers: []v1.Container{}, + RestartPolicy: v1.RestartPolicyNever, + }, + } + pod.ObjectMeta.UID = types.UID(podName) + + for i, c := range initContainers { + container := v1.Container{ + Name: c.name, + Image: "image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + } + if c.name == "" { + container.Name = "init-container-" + strconv.Itoa(i) + } + if c.memRequest != "" { + container.Resources.Requests[v1.ResourceMemory] = resource.MustParse(c.memRequest) + } + if c.memLimit != "" { + container.Resources.Limits[v1.ResourceMemory] = resource.MustParse(c.memLimit) + } + if c.restartPolicy != nil { + container.RestartPolicy = c.restartPolicy + } + pod.Spec.InitContainers = append(pod.Spec.InitContainers, container) + } + + for i, c := range appContainers { + container := v1.Container{ + Name: c.name, + Image: "image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + } + if c.name == "" { + container.Name = "app-container-" + strconv.Itoa(i) + } + if c.memRequest != "" { + container.Resources.Requests[v1.ResourceMemory] = resource.MustParse(c.memRequest) + } + if c.memLimit != "" { + container.Resources.Limits[v1.ResourceMemory] = resource.MustParse(c.memLimit) + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + + if podMemRequest != "" || podMemLimit != "" { + pod.Spec.Resources = &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + } + if podMemRequest != "" { + pod.Spec.Resources.Requests[v1.ResourceMemory] = resource.MustParse(podMemRequest) + } + if podMemLimit != "" { + pod.Spec.Resources.Limits[v1.ResourceMemory] = resource.MustParse(podMemLimit) + } + } else { + pod.Spec.Resources = nil + } + return pod +} + +func TestStaticPolicyAllocatePod(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + + machineInfo := &cadvisorapi.MachineInfo{ + Topology: []cadvisorapi.Node{ + {Id: 0, Memory: 2 * gb}, + {Id: 1, Memory: 2 * gb}, + }, + } + + systemReserved := systemReservedMemory{ + 0: map[v1.ResourceName]uint64{v1.ResourceMemory: 512 * mb}, + 1: map[v1.ResourceName]uint64{v1.ResourceMemory: 512 * mb}, + } + + machineState := state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 1536 * mb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 0, + Cells: []int{0}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 1536 * mb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 0, + Cells: []int{1}, + }, + } + + testCases := []testStaticPolicy{ + { + description: "should successfully allocate memory for a guaranteed pod with pod-level resources", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources("pod1", "1Gi", "1Gi", nil, []containerSpec{ + {name: "container1", memRequest: "512Mi", memLimit: "512Mi"}, + }), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod1": { + "container1": []state.Block{ + {NUMAAffinity: []int{0}, Size: 512 * mb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 1536 * mb, + Reserved: 0, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 0, + Cells: []int{1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 1, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 0, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and guaranteed container, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources("pod2", "2Gi", "2Gi", nil, []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + }), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod2": { + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 1024 * mb, + Reserved: 512 * mb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 0, + Reserved: 1536 * mb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 1, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 0, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and non-guaranteed container, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources("pod3", "2Gi", "2Gi", nil, []containerSpec{ + {name: "container1"}, + {name: "container2"}, + }), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0, 1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod3": { + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + "container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 2, + expExclusiveAssignments: 0, + expPodSharedPoolAssignments: 2, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and mix of guaranteed and non-guaranteed containers, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources("pod3", "2Gi", "2Gi", nil, []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2"}, + {name: "container3"}, + }), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0, 1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod3": { + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "container3": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 3, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 2, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and mix of guaranteed standard and guaranteed init containers, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources( + "pod4", "2Gi", "2Gi", + []containerSpec{ + {name: "init-container1", memRequest: "2Gi", memLimit: "2Gi"}, + }, + []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2"}, + }, + ), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0, 1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod4": { + "init-container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 3, + expExclusiveAssignments: 2, + expPodSharedPoolAssignments: 1, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and mix of guaranteed standard and guaranteed restartable and non-guaranteed standard init containers, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources( + "pod4", "3Gi", "3Gi", + []containerSpec{ + {name: "init-container1"}, + {name: "init-restartable-container2", memRequest: "1Gi", memLimit: "1Gi", restartPolicy: &containerRestartPolicyAlways}, + {name: "init-container3"}, + }, + []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2"}, + }, + ), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0, 1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod4": { + "init-container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 3 * gb, Type: v1.ResourceMemory}, + }, + "init-restartable-container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "init-container3": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 0, + Reserved: 1536 * mb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 0, + Reserved: 1536 * mb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 5, + expExclusiveAssignments: 2, + expPodSharedPoolAssignments: 3, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and mix of guaranteed standard and non-guaranteed restartable and standard init containers, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources( + "pod4", "3Gi", "3Gi", + []containerSpec{ + {name: "init-container1"}, + {name: "init-restartable-container2", restartPolicy: &containerRestartPolicyAlways}, + {name: "init-container3"}, + }, + []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2"}, + }, + ), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0, 1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod4": { + "init-container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 3 * gb, Type: v1.ResourceMemory}, + }, + "init-restartable-container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + "init-container3": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 3 * gb, Type: v1.ResourceMemory}, + }, + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 0, + Reserved: 1536 * mb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 0, + Reserved: 1536 * mb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 5, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 4, + }, + }, + { + description: "scope: pod, should allocate exclusive memory to a guaranteed pod with pod-level resources and mix of guaranteed standard and non-guaranteed init containers, multi NUMA node, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources( + "pod4", "2Gi", "2Gi", + []containerSpec{ + {name: "init-container1"}, + }, + []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2"}, + }, + ), + topologyHint: &topologymanager.TopologyHint{NUMANodeAffinity: newNUMAAffinity(0, 1), Preferred: true}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{ + "pod4": { + "init-container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 2 * gb, Type: v1.ResourceMemory}, + }, + "container1": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + "container2": []state.Block{ + {NUMAAffinity: []int{0, 1}, Size: 1 * gb, Type: v1.ResourceMemory}, + }, + }, + }, + expectedMachineState: state.NUMANodeMap{ + 0: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + 1: &state.NUMANodeState{ + MemoryMap: map[v1.ResourceName]*state.MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536 * mb, + Free: 512 * mb, + Reserved: 1 * gb, + SystemReserved: 512 * mb, + TotalMemSize: 2 * gb, + }, + }, + NumberOfAssignments: 1, + Cells: []int{0, 1}, + }, + }, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalAllocs: 3, + expExclusiveAssignments: 1, + expPodSharedPoolAssignments: 2, + }, + }, + { + description: "scope: pod, should reject a pod that would result in an empty pod shared pool", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources("pod3", "2Gi", "2Gi", nil, []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container3"}, + }), + topologyHint: &topologymanager.TopologyHint{}, + expectedError: admission.NewEmptyPodSharedPoolError(fmt.Errorf("pod rejected, sum of exclusive container memory requests equals pod budget, leaving no memory for shared containers")), + expectedAssignments: state.ContainerMemoryAssignments{}, + expectedMachineState: machineState, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalErrors: 1, + }, + }, + { + description: "scope: pod, should not allocate exclusive memory to a non-guaranteed pod with pod-level resources and guaranteed containers, PodLevelResourceManagers enabled", + machineInfo: machineInfo, + systemReserved: systemReserved, + machineState: machineState, + pod: getPodWithContainersAndPodLevelResources("pod3", "1Gi", "2Gi", nil, []containerSpec{ + {name: "container1", memRequest: "1Gi", memLimit: "1Gi"}, + {name: "container2"}, + {name: "container3"}, + }), + topologyHint: &topologymanager.TopologyHint{}, + expectedError: nil, + expectedAssignments: state.ContainerMemoryAssignments{}, + expectedMachineState: machineState, + podLevelResourcesEnabled: true, + podLevelResourceManagersEnabled: true, + requiredMetrics: requiredMetrics{ + expTotalErrors: 0, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, testCase.podLevelResourcesEnabled) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, testCase.podLevelResourceManagersEnabled) + + metrics.Register() + metrics.ResourceManagerAllocationsTotal.Reset() + metrics.ResourceManagerAllocationErrorsTotal.Reset() + metrics.ResourceManagerContainerAssignments.Reset() + + p, s, err := initTests(t, &testCase, testCase.topologyHint, testCase.initContainersReusableMemory) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + err = p.AllocatePod(logger, s, testCase.pod) + if testCase.expectedError != nil { + require.Error(t, err) + require.Equal(t, testCase.expectedError.Error(), err.Error()) + + errors, err := testutil.GetCounterMetricValue(metrics.ResourceManagerAllocationErrorsTotal.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerPod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expTotalErrors), errors, 0.001, "expected allocation errors to be incremented") + + return + } + require.NoError(t, err) + + assignments := s.GetMemoryAssignments() + if !areContainerMemoryAssignmentsEqual(t, assignments, testCase.expectedAssignments) { + t.Fatalf("Actual assignments: %v is different from the expected one: %v", assignments, testCase.expectedAssignments) + } + + machineState := s.GetMachineState() + if !areMachineStatesEqual(logger, machineState, testCase.expectedMachineState) { + t.Fatalf("The actual machine state: %v is different from the expected one: %v", machineState, testCase.expectedMachineState) + } + + allocations, err := testutil.GetCounterMetricValue(metrics.ResourceManagerAllocationsTotal.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerPod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expTotalAllocs), allocations, 0.001, "unexpected number of allocations") + + exclusiveAssignments, err := testutil.GetCounterMetricValue(metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerExclusivePod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expExclusiveAssignments), exclusiveAssignments, 0.001, "unexpected number of assignments") + + podSharedPoolAssignments, err := testutil.GetCounterMetricValue(metrics.ResourceManagerContainerAssignments.WithLabelValues(metrics.ResourceManagerMemory, metrics.ResourceManagerSharedPod)) + require.NoError(t, err) + require.InDelta(t, float64(testCase.requiredMetrics.expPodSharedPoolAssignments), podSharedPoolAssignments, 0.001, "unexpected number of assignments") + }) + } +} diff --git a/pkg/kubelet/cm/memorymanager/state/checkpoint.go b/pkg/kubelet/cm/memorymanager/state/checkpoint.go index 93b1302baa131..445d0cf57b777 100644 --- a/pkg/kubelet/cm/memorymanager/state/checkpoint.go +++ b/pkg/kubelet/cm/memorymanager/state/checkpoint.go @@ -18,15 +18,36 @@ package state import ( "encoding/json" + "fmt" + "hash/fnv" + "strings" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" + "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors" + "k8s.io/utils/dump" ) +var _ checkpointmanager.Checkpoint = &MemoryManagerCheckpointV1{} +var _ checkpointmanager.Checkpoint = &MemoryManagerCheckpointV2{} var _ checkpointmanager.Checkpoint = &MemoryManagerCheckpoint{} -// MemoryManagerCheckpoint struct is used to store memory/pod assignments in a checkpoint +// MemoryManagerCheckpoint struct is used to store memory/pod assignments in a checkpoint in v2 format type MemoryManagerCheckpoint struct { + PolicyName string `json:"policyName"` + MachineState NUMANodeMap `json:"machineState"` + Entries ContainerMemoryAssignments `json:"entries,omitempty"` + PodEntries PodMemoryAssignments `json:"podEntries,omitempty"` + Checksum checksum.Checksum `json:"checksum"` +} + +// MemoryManagerCheckpointV2 struct is used to store memory/pod assignments in a checkpoint in v2 format +type MemoryManagerCheckpointV2 = MemoryManagerCheckpoint + +// MemoryManagerCheckpointV1 struct is used to store memory/pod assignments in a checkpoint in v1 format +type MemoryManagerCheckpointV1 struct { PolicyName string `json:"policyName"` MachineState NUMANodeMap `json:"machineState"` Entries ContainerMemoryAssignments `json:"entries,omitempty"` @@ -34,28 +55,100 @@ type MemoryManagerCheckpoint struct { } // NewMemoryManagerCheckpoint returns an instance of Checkpoint -func NewMemoryManagerCheckpoint() *MemoryManagerCheckpoint { +func newMemoryManagerCheckpoint() *MemoryManagerCheckpoint { //nolint:staticcheck // unexported-type-in-api user-facing error message - return &MemoryManagerCheckpoint{ + return newMemoryManagerCheckpointV2() +} + +func newMemoryManagerCheckpointV1() *MemoryManagerCheckpointV1 { + return &MemoryManagerCheckpointV1{ Entries: ContainerMemoryAssignments{}, MachineState: NUMANodeMap{}, } } -// MarshalCheckpoint returns marshalled checkpoint -func (mp *MemoryManagerCheckpoint) MarshalCheckpoint() ([]byte, error) { +func newMemoryManagerCheckpointV2() *MemoryManagerCheckpointV2 { + return &MemoryManagerCheckpointV2{ + Entries: ContainerMemoryAssignments{}, + PodEntries: PodMemoryAssignments{}, + MachineState: NUMANodeMap{}, + } +} + +// MarshalCheckpoint returns marshalled checkpoint in v1 format +func (mp *MemoryManagerCheckpointV1) MarshalCheckpoint() ([]byte, error) { + // make sure checksum wasn't set before so it doesn't affect output checksum + mp.Checksum = 0 + + // In order to preserve rollback compatibility when the feature gate is disabled, + // we must generate a checksum using the legacy struct name "MemoryManagerCheckpoint" + // instead of "MemoryManagerCheckpointV1". Older Kubelets do not have the string + // replacement logic and expect the original struct name. + object := dump.ForHash(mp) + object = strings.Replace(object, "MemoryManagerCheckpointV1", "MemoryManagerCheckpoint", 1) + hash := fnv.New32a() + _, _ = fmt.Fprintf(hash, "%v", object) + mp.Checksum = checksum.Checksum(hash.Sum32()) + + return json.Marshal(*mp) +} + +// MarshalCheckpoint returns marshalled checkpoint in v2 format +func (mp *MemoryManagerCheckpointV2) MarshalCheckpoint() ([]byte, error) { // make sure checksum wasn't set before so it doesn't affect output checksum mp.Checksum = 0 mp.Checksum = checksum.New(mp) return json.Marshal(*mp) } -// UnmarshalCheckpoint tries to unmarshal passed bytes to checkpoint -func (mp *MemoryManagerCheckpoint) UnmarshalCheckpoint(blob []byte) error { +// UnmarshalCheckpoint tries to unmarshal passed bytes to checkpoint in v1 format +func (mp *MemoryManagerCheckpointV1) UnmarshalCheckpoint(blob []byte) error { + return json.Unmarshal(blob, mp) +} + +// UnmarshalCheckpoint tries to unmarshal passed bytes to checkpoint in v2 format +func (mp *MemoryManagerCheckpointV2) UnmarshalCheckpoint(blob []byte) error { return json.Unmarshal(blob, mp) } -// VerifyChecksum verifies that current checksum of checkpoint is valid +// VerifyChecksum verifies that current checksum of checkpoint is valid in v1 format +func (mp *MemoryManagerCheckpointV1) VerifyChecksum() error { + if mp.Checksum == 0 { + // accept empty checksum for compatibility with old file backend + return nil + } + + ck := mp.Checksum + mp.Checksum = 0 + + if !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + err := ck.Verify(mp) + if err == nil { + mp.Checksum = ck + return nil + } + // If the standard verification failed, it could be a legacy checkpoint. + // Fallback to the legacy verification method, when V2 is latest. + } + + object := dump.ForHash(mp) + object = strings.Replace(object, "MemoryManagerCheckpointV1", "MemoryManagerCheckpoint", 1) + mp.Checksum = ck + + hash := fnv.New32a() + _, _ = fmt.Fprintf(hash, "%v", object) + actualCS := checksum.Checksum(hash.Sum32()) + if mp.Checksum != actualCS { + return &errors.CorruptCheckpointError{ + ActualCS: uint64(actualCS), + ExpectedCS: uint64(mp.Checksum), + } + } + + return nil +} + +// VerifyChecksum verifies that current checksum of checkpoint is valid in v2 format func (mp *MemoryManagerCheckpoint) VerifyChecksum() error { ck := mp.Checksum mp.Checksum = 0 diff --git a/pkg/kubelet/cm/memorymanager/state/state.go b/pkg/kubelet/cm/memorymanager/state/state.go index 322ca608e4ca0..03c68fa336b8d 100644 --- a/pkg/kubelet/cm/memorymanager/state/state.go +++ b/pkg/kubelet/cm/memorymanager/state/state.go @@ -100,14 +100,26 @@ func (as ContainerMemoryAssignments) Clone() ContainerMemoryAssignments { return clone } +// PodMemoryAssignments stores memory assignments of pods +type PodMemoryAssignments map[string]PodEntry + +// PodEntry represents pod-level memory assignments for a pod +type PodEntry struct { + MemoryBlocks []Block `json:"memoryBlocks,omitempty"` +} + // Reader interface used to read current memory/pod assignment state type Reader interface { // GetMachineState returns Memory Map stored in the State GetMachineState() NUMANodeMap // GetMemoryBlocks returns memory assignments of a container GetMemoryBlocks(podUID string, containerName string) []Block + // GetPodMemoryBlocks returns pod-level memory assignments of a pod + GetPodMemoryBlocks(podUID string) []Block // GetMemoryAssignments returns ContainerMemoryAssignments GetMemoryAssignments() ContainerMemoryAssignments + // GetPodMemoryAssignments returns all pod-level memory assignments + GetPodMemoryAssignments() PodMemoryAssignments } type writer interface { @@ -115,10 +127,16 @@ type writer interface { SetMachineState(memoryMap NUMANodeMap) // SetMemoryBlocks stores memory assignments of a container SetMemoryBlocks(podUID string, containerName string, blocks []Block) + // SetPodMemoryBlocks stores pod-level memory assignments of a pod + SetPodMemoryBlocks(podUID string, blocks []Block) // SetMemoryAssignments sets ContainerMemoryAssignments by using the passed parameter SetMemoryAssignments(assignments ContainerMemoryAssignments) + // SetPodMemoryAssignments sets PodMemoryAssignments by using the passed parameter + SetPodMemoryAssignments(assignments PodMemoryAssignments) // Delete deletes corresponding Blocks from ContainerMemoryAssignments Delete(podUID string, containerName string) + // DeletePod deletes all pod-level memory assignments for specified pod + DeletePod(podUID string) // ClearState clears machineState and ContainerMemoryAssignments ClearState() } diff --git a/pkg/kubelet/cm/memorymanager/state/state_checkpoint.go b/pkg/kubelet/cm/memorymanager/state/state_checkpoint.go index a23833f26c034..ae55415c774ba 100644 --- a/pkg/kubelet/cm/memorymanager/state/state_checkpoint.go +++ b/pkg/kubelet/cm/memorymanager/state/state_checkpoint.go @@ -17,13 +17,17 @@ limitations under the License. package state import ( + "errors" "fmt" + "maps" "path/filepath" "sync" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" - "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors" + cperrors "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors" ) var _ State = &stateCheckpoint{} @@ -61,35 +65,132 @@ func NewCheckpointState(logger klog.Logger, stateDir, checkpointName, policyName return stateCheckpoint, nil } +// migrateV1CheckpointToV2Checkpoint converts checkpoints from the v1 format to the v2 format +func (sc *stateCheckpoint) migrateV1CheckpointToV2Checkpoint(src *MemoryManagerCheckpointV1, dst *MemoryManagerCheckpointV2) { + if src.PolicyName != "" { + dst.PolicyName = src.PolicyName + } + if src.MachineState != nil { + dst.MachineState = src.MachineState.Clone() + } + if len(src.Entries) > 0 { + dst.Entries = src.Entries.Clone() + } +} + // restores state from a checkpoint and creates it if it doesn't exist func (sc *stateCheckpoint) restoreState() error { sc.Lock() defer sc.Unlock() + + var checkpoint any var err error - checkpoint := NewMemoryManagerCheckpoint() - if err = sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpoint); err != nil { - if err == errors.ErrCheckpointNotFound { + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + checkpoint, err = sc.loadAndMigrateCheckpointV2() + } else { + checkpoint, err = sc.loadCheckpointV1() + } + + if err != nil { + if errors.Is(err, cperrors.ErrCheckpointNotFound) { return sc.storeState() } return err } - if sc.policyName != checkpoint.PolicyName { - return fmt.Errorf("[memorymanager] configured policy %q differs from state checkpoint policy %q", sc.policyName, checkpoint.PolicyName) + switch cp := checkpoint.(type) { + case *MemoryManagerCheckpointV2: + if sc.policyName != cp.PolicyName { + return fmt.Errorf("[memorymanager] configured policy %q differs from state checkpoint policy %q", sc.policyName, cp.PolicyName) + } + sc.cache.SetMachineState(cp.MachineState) + sc.cache.SetMemoryAssignments(cp.Entries) + sc.cache.SetPodMemoryAssignments(cp.PodEntries) + case *MemoryManagerCheckpointV1: + if sc.policyName != cp.PolicyName { + return fmt.Errorf("[memorymanager] configured policy %q differs from state checkpoint policy %q", sc.policyName, cp.PolicyName) + } + sc.cache.SetMachineState(cp.MachineState) + sc.cache.SetMemoryAssignments(cp.Entries) + default: + return fmt.Errorf("unknown checkpoint type: %T", cp) } - sc.cache.SetMachineState(checkpoint.MachineState) - sc.cache.SetMemoryAssignments(checkpoint.Entries) - sc.logger.V(2).Info("State checkpoint: restored state from checkpoint") return nil } +// loadAndMigrateCheckpointV2 loads the latest checkpoint and migrates it from older versions if needed. +func (sc *stateCheckpoint) loadAndMigrateCheckpointV2() (*MemoryManagerCheckpointV2, error) { + // Try to load as V2. + checkpointV2 := newMemoryManagerCheckpointV2() + err := sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV2) + if err == nil { + return checkpointV2, nil + } + if errors.Is(err, cperrors.ErrCheckpointNotFound) { + return nil, err + } + + // Log the V2 load error and fall back to V1. + if errors.Is(err, cperrors.CorruptCheckpointError{}) { + sc.logger.Error(err, "V2 checkpoint for memory manager is corrupt, falling back to V1") + } else { + sc.logger.Info("could not load V2 checkpoint for memory manager, falling back to V1", "err", err) + } + + // Try to load as V1. + checkpointV1, err := sc.loadCheckpointV1() + if err != nil { + // This will return ErrCheckpointNotFound if it's not found, or a corruption error. + return nil, err + } + + // Loaded V1, now migrate to V2. + sc.logger.Info("migrating memory manager checkpoint from v1 to v2") + sc.migrateV1CheckpointToV2Checkpoint(checkpointV1, checkpointV2) + + return checkpointV2, nil +} + +func (sc *stateCheckpoint) loadCheckpointV1() (*MemoryManagerCheckpointV1, error) { + checkpointV1 := newMemoryManagerCheckpointV1() + err := sc.checkpointManager.GetCheckpoint(sc.checkpointName, checkpointV1) + if err != nil { + return nil, err + } + return checkpointV1, nil +} + // saves state to a checkpoint, caller is responsible for locking func (sc *stateCheckpoint) storeState() error { - checkpoint := NewMemoryManagerCheckpoint() + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + return sc.storeStateV2() + } + return sc.storeStateV1() +} + +func (sc *stateCheckpoint) storeStateV2() error { + checkpoint := newMemoryManagerCheckpoint() + checkpoint.PolicyName = sc.policyName + checkpoint.MachineState = sc.cache.GetMachineState() + checkpoint.Entries = sc.cache.GetMemoryAssignments() + + podAssignments := sc.cache.GetPodMemoryAssignments() + maps.Copy(checkpoint.PodEntries, podAssignments) + + err := sc.checkpointManager.CreateCheckpoint(sc.checkpointName, checkpoint) + if err != nil { + sc.logger.Error(err, "Could not save checkpoint") + return err + } + return nil +} + +func (sc *stateCheckpoint) storeStateV1() error { + checkpoint := newMemoryManagerCheckpointV1() checkpoint.PolicyName = sc.policyName checkpoint.MachineState = sc.cache.GetMachineState() checkpoint.Entries = sc.cache.GetMemoryAssignments() @@ -126,6 +227,22 @@ func (sc *stateCheckpoint) GetMemoryAssignments() ContainerMemoryAssignments { return sc.cache.GetMemoryAssignments() } +// GetPodMemoryBlocks returns memory assignments of a pod +func (sc *stateCheckpoint) GetPodMemoryBlocks(podUID string) []Block { + sc.RLock() + defer sc.RUnlock() + + return sc.cache.GetPodMemoryBlocks(podUID) +} + +// GetPodMemoryAssignments returns all pod-level memory assignments +func (sc *stateCheckpoint) GetPodMemoryAssignments() PodMemoryAssignments { + sc.RLock() + defer sc.RUnlock() + + return sc.cache.GetPodMemoryAssignments() +} + // SetMachineState stores NUMANodeMap in State func (sc *stateCheckpoint) SetMachineState(memoryMap NUMANodeMap) { sc.Lock() @@ -162,6 +279,29 @@ func (sc *stateCheckpoint) SetMemoryAssignments(assignments ContainerMemoryAssig } } +func (sc *stateCheckpoint) SetPodMemoryAssignments(assignments PodMemoryAssignments) { + sc.Lock() + defer sc.Unlock() + + sc.cache.SetPodMemoryAssignments(assignments) + err := sc.storeState() + if err != nil { + sc.logger.Error(err, "Failed to store state to checkpoint") + } +} + +// SetPodMemoryBlocks stores memory assignments of a pod +func (sc *stateCheckpoint) SetPodMemoryBlocks(podUID string, blocks []Block) { + sc.Lock() + defer sc.Unlock() + + sc.cache.SetPodMemoryBlocks(podUID, blocks) + err := sc.storeState() + if err != nil { + sc.logger.Error(err, "Failed to store state to checkpoint", "podUID", podUID) + } +} + // Delete deletes corresponding Blocks from ContainerMemoryAssignments func (sc *stateCheckpoint) Delete(podUID string, containerName string) { sc.Lock() @@ -174,6 +314,19 @@ func (sc *stateCheckpoint) Delete(podUID string, containerName string) { } } +// DeletePod deletes pod-level CPU assignments for specified pod. It does not +// affect container-level assignments. +func (sc *stateCheckpoint) DeletePod(podUID string) { + sc.Lock() + defer sc.Unlock() + + sc.cache.DeletePod(podUID) + err := sc.storeState() + if err != nil { + sc.logger.Error(err, "Failed to store state to checkpoint", "podUID", podUID) + } +} + // ClearState clears machineState and ContainerMemoryAssignments func (sc *stateCheckpoint) ClearState() { sc.Lock() diff --git a/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go b/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go index ae9be6f3ec026..fb4b247b88151 100644 --- a/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go +++ b/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go @@ -17,14 +17,20 @@ limitations under the License. package state import ( + "encoding/json" "os" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" + "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing" "k8s.io/kubernetes/test/utils/ktesting" ) @@ -45,16 +51,18 @@ func assertStateEqual(t *testing.T, restoredState, expectedState State) { func TestCheckpointStateRestore(t *testing.T) { logger, _ := ktesting.NewTestContext(t) testCases := []struct { - description string - checkpointContent string - expectedError string - expectedState *stateMemory + description string + checkpointContent string + expectedError string + expectedState *stateMemory + podLevelResourceManagersEnabled bool }{ { "Restore non-existing checkpoint", "", "", &stateMemory{}, + false, }, { "Restore valid checkpoint", @@ -91,6 +99,7 @@ func TestCheckpointStateRestore(t *testing.T) { }, }, }, + false, }, { "Restore checkpoint with invalid checksum", @@ -102,12 +111,206 @@ func TestCheckpointStateRestore(t *testing.T) { }`, "checkpoint is corrupted", &stateMemory{}, + false, }, { "Restore checkpoint with invalid JSON", `{`, "unexpected end of JSON input", &stateMemory{}, + false, + }, + { + "Restore checkpoint from v1 (migration) with PodLevelResourceManagers enabled", + `{ + "policyName":"static", + "machineState":{"0":{"numberOfAssignments":0,"memoryMap":{"memory":{"total":2048,"systemReserved":512,"allocatable":1536,"reserved":512,"free":1024}},"cells":[]}}, + "entries":{"pod":{"container1":[{"numaAffinity":[0],"type":"memory","size":512}]}}, + "checksum": 4215593881 + }`, + "", + &stateMemory{ + assignments: ContainerMemoryAssignments{ + "pod": map[string][]Block{ + "container1": { + { + NUMAAffinity: []int{0}, + Type: v1.ResourceMemory, + Size: 512, + }, + }, + }, + }, + machineState: NUMANodeMap{ + 0: &NUMANodeState{ + MemoryMap: map[v1.ResourceName]*MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536, + Free: 1024, + Reserved: 512, + SystemReserved: 512, + TotalMemSize: 2048, + }, + }, + }, + }, + }, + true, + }, + { + "Restore checkpoint from v1 (migration) with PodLevelResourceManagers disabled", + `{ + "policyName":"static", + "machineState":{"0":{"numberOfAssignments":0,"memoryMap":{"memory":{"total":2048,"systemReserved":512,"allocatable":1536,"reserved":512,"free":1024}},"cells":[]}}, + "entries":{"pod":{"container1":[{"numaAffinity":[0],"type":"memory","size":512}]}}, + "checksum": 4215593881 + }`, + "", + &stateMemory{ + assignments: ContainerMemoryAssignments{ + "pod": map[string][]Block{ + "container1": { + { + NUMAAffinity: []int{0}, + Type: v1.ResourceMemory, + Size: 512, + }, + }, + }, + }, + machineState: NUMANodeMap{ + 0: &NUMANodeState{ + MemoryMap: map[v1.ResourceName]*MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536, + Free: 1024, + Reserved: 512, + SystemReserved: 512, + TotalMemSize: 2048, + }, + }, + }, + }, + }, + false, + }, + { + "Restore checkpoint from v2 with PodLevelResourceManagers enabled", + `{ + "policyName":"static", + "machineState":{"0":{"numberOfAssignments":0,"memoryMap":{"memory":{"total":2048,"systemReserved":512,"allocatable":1536,"reserved":512,"free":1024}},"cells":[]}}, + "entries":{"pod":{"container1":[{"numaAffinity":[0],"type":"memory","size":512}]}}, + "podEntries":{"pod":{"memoryBlocks":[{"numaAffinity":[0],"type":"memory","size":512}]}}, + "checksum": 1278640530 + }`, + "", + &stateMemory{ + assignments: ContainerMemoryAssignments{ + "pod": map[string][]Block{ + "container1": { + { + NUMAAffinity: []int{0}, + Type: v1.ResourceMemory, + Size: 512, + }, + }, + }, + }, + podAssignments: PodMemoryAssignments{ + "pod": PodEntry{ + MemoryBlocks: []Block{ + { + NUMAAffinity: []int{0}, + Type: v1.ResourceMemory, + Size: 512, + }, + }, + }, + }, + machineState: NUMANodeMap{ + 0: &NUMANodeState{ + MemoryMap: map[v1.ResourceName]*MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536, + Free: 1024, + Reserved: 512, + SystemReserved: 512, + TotalMemSize: 2048, + }, + }, + }, + }, + }, + true, + }, + { + "Restore checkpoint from v2 with PodLevelResourceManagers disabled", + `{ + "policyName":"static", + "machineState":{"0":{"numberOfAssignments":0,"memoryMap":{"memory":{"total":2048,"systemReserved":512,"allocatable":1536,"reserved":512,"free":1024}},"cells":[]}}, + "entries":{"pod":{"container1":[{"numaAffinity":[0],"type":"memory","size":512}]}}, + "podEntries":{"pod":{"memoryBlocks":[{"numaAffinity":[0],"type":"memory","size":512}]}}, + "checksum": 1278640530 + }`, + "checkpoint is corrupted, please drain this node and delete the memory manager checkpoint file", + &stateMemory{ + assignments: ContainerMemoryAssignments{ + "pod": map[string][]Block{ + "container1": { + { + NUMAAffinity: []int{0}, + Type: v1.ResourceMemory, + Size: 512, + }, + }, + }, + }, + podAssignments: PodMemoryAssignments{ + "pod": PodEntry{ + MemoryBlocks: []Block{ + { + NUMAAffinity: []int{0}, + Type: v1.ResourceMemory, + Size: 512, + }, + }, + }, + }, + machineState: NUMANodeMap{ + 0: &NUMANodeState{ + MemoryMap: map[v1.ResourceName]*MemoryTable{ + v1.ResourceMemory: { + Allocatable: 1536, + Free: 1024, + Reserved: 512, + SystemReserved: 512, + TotalMemSize: 2048, + }, + }, + }, + }, + }, + false, + }, + { + "Restore non-existing checkpoint with PodLevelResourceManagers enabled", + "", + "", + &stateMemory{}, + true, + }, + { + "Restore corrupt checkpoint with PodLevelResourceManagers enabled", + `{ + "policyName":"static", + "machineState":{"0":{"numberOfAssignments":0,"memoryMap":{"memory":{"total":2048,"systemReserved":512,"allocatable":1536,"reserved":512,"free":1024}},"cells":[]}}, + "entries":{}, + "podEntries":{}, + "checksum": 12345 + }`, + "checkpoint is corrupted", + &stateMemory{}, + true, }, } @@ -124,6 +327,11 @@ func TestCheckpointStateRestore(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { + if tc.podLevelResourceManagersEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResourceManagers, true) + } + // ensure there is no previous checkpoint assert.NoError(t, cpm.RemoveCheckpoint(testingCheckpoint), "could not remove testing checkpoint") @@ -138,7 +346,7 @@ func TestCheckpointStateRestore(t *testing.T) { assert.Error(t, err) assert.ErrorContains(t, err, "could not restore state from checkpoint: "+tc.expectedError) } else { - assert.NoError(t, err, "unexpected error while creating checkpointState") + require.NoError(t, err, "unexpected error while creating checkpointState") // compare state after restoration with the one expected assertStateEqual(t, restoredState, tc.expectedState) } @@ -386,3 +594,53 @@ func TestCheckpointStateClear(t *testing.T) { }) } } + +func TestMemoryManagerCheckpointV1_MarshalCheckpoint_ForwardCompatibility(t *testing.T) { + // 1. Create a V1 checkpoint using the struct defined in the current codebase (1.36+) + currentCheckpoint := &MemoryManagerCheckpointV1{ + PolicyName: "none", + MachineState: NUMANodeMap{}, + Entries: ContainerMemoryAssignments{}, + } + + // Marshal it using the logic that forces the "MemoryManagerCheckpoint" name + data, err := currentCheckpoint.MarshalCheckpoint() + if err != nil { + t.Fatalf("Failed to marshal checkpoint: %v", err) + } + + // 2. Unmarshal the raw JSON to extract the checksum that was actually written to the file + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + actualChecksumFloat, ok := result["checksum"].(float64) + if !ok { + t.Fatalf("Checksum field missing or invalid type") + } + writtenChecksum := checksum.Checksum(uint64(actualChecksumFloat)) + + // 3. Reconstruct how versions 1.35 and earlier would calculate the checksum + // by defining a struct with the exact legacy name and fields. + type MemoryManagerCheckpoint struct { + PolicyName string `json:"policyName"` + MachineState NUMANodeMap `json:"machineState"` + Entries ContainerMemoryAssignments `json:"entries,omitempty"` + Checksum checksum.Checksum `json:"checksum"` + } + + legacyCheckpoint := &MemoryManagerCheckpoint{ + PolicyName: currentCheckpoint.PolicyName, + MachineState: currentCheckpoint.MachineState, + Entries: currentCheckpoint.Entries, + } + + expectedLegacyChecksum := checksum.New(legacyCheckpoint) + + // 4. Assert that the checksum written by our 1.36+ code matches + // what a 1.35 Kubelet would expect to see. + if writtenChecksum != expectedLegacyChecksum { + t.Errorf("Written Checksum %d does not match legacy calculation %d. Forward compatibility broken.", writtenChecksum, expectedLegacyChecksum) + } +} diff --git a/pkg/kubelet/cm/memorymanager/state/state_mem.go b/pkg/kubelet/cm/memorymanager/state/state_mem.go index 59aec9256087c..87b848517b8de 100644 --- a/pkg/kubelet/cm/memorymanager/state/state_mem.go +++ b/pkg/kubelet/cm/memorymanager/state/state_mem.go @@ -17,16 +17,20 @@ limitations under the License. package state import ( + "maps" "sync" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" ) type stateMemory struct { sync.RWMutex - logger klog.Logger - assignments ContainerMemoryAssignments - machineState NUMANodeMap + logger klog.Logger + assignments ContainerMemoryAssignments + podAssignments PodMemoryAssignments + machineState NUMANodeMap } var _ State = &stateMemory{} @@ -34,6 +38,16 @@ var _ State = &stateMemory{} // NewMemoryState creates new State for keeping track of cpu/pod assignment func NewMemoryState(logger klog.Logger) State { logger.Info("Initializing new in-memory state store") + + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + return &stateMemory{ + logger: logger, + assignments: ContainerMemoryAssignments{}, + podAssignments: PodMemoryAssignments{}, + machineState: NUMANodeMap{}, + } + } + return &stateMemory{ logger: logger, assignments: ContainerMemoryAssignments{}, @@ -68,6 +82,27 @@ func (s *stateMemory) GetMemoryAssignments() ContainerMemoryAssignments { return s.assignments.Clone() } +// GetPodMemoryBlocks returns memory assignments of a pod +func (s *stateMemory) GetPodMemoryBlocks(podUID string) []Block { + s.RLock() + defer s.RUnlock() + + if res, ok := s.podAssignments[podUID]; ok { + return append([]Block{}, res.MemoryBlocks...) + } + return nil +} + +// GetPodMemoryAssignments returns all pod-level memory assignments +func (s *stateMemory) GetPodMemoryAssignments() PodMemoryAssignments { + s.RLock() + defer s.RUnlock() + + clone := make(PodMemoryAssignments) + maps.Copy(clone, s.podAssignments) + return clone +} + // SetMachineState stores NUMANodeMap in State func (s *stateMemory) SetMachineState(nodeMap NUMANodeMap) { s.Lock() @@ -99,6 +134,27 @@ func (s *stateMemory) SetMemoryAssignments(assignments ContainerMemoryAssignment s.logger.V(5).Info("Updated Memory assignments", "assignments", assignments) } +// SetPodMemoryAssignments sets PodMemoryAssignments by using the passed parameter +func (s *stateMemory) SetPodMemoryAssignments(assignments PodMemoryAssignments) { + s.Lock() + defer s.Unlock() + + s.podAssignments = make(PodMemoryAssignments) + maps.Copy(s.podAssignments, assignments) + s.logger.V(5).Info("Updated Pod Memory assignments", "assignments", assignments) +} + +// SetPodMemoryBlocks stores memory assignments of a pod +func (s *stateMemory) SetPodMemoryBlocks(podUID string, blocks []Block) { + s.Lock() + defer s.Unlock() + + podAssigments := s.podAssignments[podUID] + podAssigments.MemoryBlocks = append([]Block{}, blocks...) + s.podAssignments[podUID] = podAssigments + s.logger.Info("Updated pod memory state", "podUID", podUID) +} + // Delete deletes corresponding Blocks from ContainerMemoryAssignments func (s *stateMemory) Delete(podUID string, containerName string) { s.Lock() @@ -115,6 +171,16 @@ func (s *stateMemory) Delete(podUID string, containerName string) { s.logger.V(2).Info("Deleted memory assignment", "podUID", podUID, "containerName", containerName) } +// DeletePod deletes pod-level CPU assignments for specified pod. It does not +// affect container-level assignments. +func (s *stateMemory) DeletePod(podUID string) { + s.Lock() + defer s.Unlock() + + delete(s.podAssignments, podUID) + s.logger.V(2).Info("Deleted pod memory assignment", "podUID", podUID) +} + // ClearState clears machineState and ContainerMemoryAssignments func (s *stateMemory) ClearState() { s.Lock() @@ -122,5 +188,8 @@ func (s *stateMemory) ClearState() { s.machineState = NUMANodeMap{} s.assignments = make(ContainerMemoryAssignments) + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + s.podAssignments = make(PodMemoryAssignments) + } s.logger.V(2).Info("Cleared state") } diff --git a/pkg/kubelet/cm/node_container_manager_linux.go b/pkg/kubelet/cm/node_container_manager_linux.go index 2981c4ebd4ec0..39a7a880c4e39 100644 --- a/pkg/kubelet/cm/node_container_manager_linux.go +++ b/pkg/kubelet/cm/node_container_manager_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. @@ -245,6 +244,8 @@ func getCgroupConfigInternal(rl v1.ResourceList, compressibleResourcesOnly bool) // Note that not all resources that are available on the node are included in the returned list of resources. // Returns a ResourceList. func (cm *containerManagerImpl) GetNodeAllocatableAbsolute() v1.ResourceList { + cm.RLock() + defer cm.RUnlock() return cm.getNodeAllocatableAbsoluteImpl(cm.capacity) } @@ -276,6 +277,8 @@ func (cm *containerManagerImpl) getNodeAllocatableInternalAbsolute() v1.Resource // GetNodeAllocatableReservation returns amount of compute or storage resource that have to be reserved on this node from scheduling. func (cm *containerManagerImpl) GetNodeAllocatableReservation() v1.ResourceList { + cm.RLock() + defer cm.RUnlock() evictionReservation := hardEvictionReservation(cm.HardEvictionThresholds, cm.capacity) result := make(v1.ResourceList) for k := range cm.capacity { @@ -299,6 +302,8 @@ func (cm *containerManagerImpl) GetNodeAllocatableReservation() v1.ResourceList // validateNodeAllocatable ensures that the user specified Node Allocatable Configuration doesn't reserve more than the node capacity. // Returns error if the configuration is invalid, nil otherwise. func (cm *containerManagerImpl) validateNodeAllocatable() error { + cm.RLock() + defer cm.RUnlock() var errors []string nar := cm.GetNodeAllocatableReservation() for k, v := range nar { diff --git a/pkg/kubelet/cm/node_container_manager_linux_test.go b/pkg/kubelet/cm/node_container_manager_linux_test.go index bd3b9d616a920..813c866c03a45 100644 --- a/pkg/kubelet/cm/node_container_manager_linux_test.go +++ b/pkg/kubelet/cm/node_container_manager_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/pod_container_manager_linux.go b/pkg/kubelet/cm/pod_container_manager_linux.go index 14a77a741b29d..327e612c4213e 100644 --- a/pkg/kubelet/cm/pod_container_manager_linux.go +++ b/pkg/kubelet/cm/pod_container_manager_linux.go @@ -31,6 +31,7 @@ import ( "k8s.io/klog/v2" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" ) const ( @@ -57,6 +58,8 @@ type podContainerManagerImpl struct { cpuCFSQuotaPeriod uint64 // podContainerManager is the ContainerManager running on the machine podContainerManager ContainerManager + // memoryReservationPolicy controls memory reservation protection behavior + memoryReservationPolicy kubeletconfig.MemoryReservationPolicy } // Make sure that podContainerManagerImpl implements the PodContainerManager interface @@ -77,7 +80,7 @@ func (m *podContainerManagerImpl) EnsureExists(logger klog.Logger, pod *v1.Pod) if !alreadyExists { enforceCPULimits := m.enforceCPULimits if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.DisableCPUQuotaWithExclusiveCPUs) && m.podContainerManager.PodHasExclusiveCPUs(pod) { - klog.V(2).InfoS("Disabled CFS quota", "pod", klog.KObj(pod)) + logger.V(2).Info("Disabled CFS quota", "pod", klog.KObj(pod)) enforceCPULimits = false } enforceMemoryQoS := false @@ -89,13 +92,13 @@ func (m *podContainerManagerImpl) EnsureExists(logger klog.Logger, pod *v1.Pod) podContainerName, _ := m.GetPodContainerName(pod) containerConfig := &CgroupConfig{ Name: podContainerName, - ResourceParameters: ResourceConfigForPod(pod, enforceCPULimits, m.cpuCFSQuotaPeriod, enforceMemoryQoS), + ResourceParameters: ResourceConfigForPod(pod, enforceCPULimits, m.cpuCFSQuotaPeriod, enforceMemoryQoS, m.memoryReservationPolicy), } if m.podPidsLimit > 0 { containerConfig.ResourceParameters.PidsLimit = &m.podPidsLimit } if enforceMemoryQoS { - klog.V(4).InfoS("MemoryQoS config for pod", "pod", klog.KObj(pod), "unified", containerConfig.ResourceParameters.Unified) + logger.V(4).Info("MemoryQoS config for pod", "pod", klog.KObj(pod), "unified", containerConfig.ResourceParameters.Unified) } if err := m.cgroupManager.Create(logger, containerConfig); err != nil { return fmt.Errorf("failed to create container for %v : %v", podContainerName, err) @@ -148,14 +151,14 @@ func (m *podContainerManagerImpl) SetPodCgroupConfig(logger klog.Logger, pod *v1 } // Kill one process ID -func (m *podContainerManagerImpl) killOnePid(pid int) error { +func (m *podContainerManagerImpl) killOnePid(logger klog.Logger, pid int) error { // os.FindProcess never returns an error on POSIX // https://go-review.googlesource.com/c/go/+/19093 p, _ := os.FindProcess(pid) if err := p.Kill(); err != nil { // If the process already exited, that's fine. if errors.Is(err, os.ErrProcessDone) { - klog.V(3).InfoS("Process no longer exists", "pid", pid) + logger.V(3).Info("Process no longer exists", "pid", pid) return nil } return err @@ -178,23 +181,23 @@ func (m *podContainerManagerImpl) tryKillingCgroupProcesses(logger klog.Logger, removed := map[int]bool{} for i := 0; i < 5; i++ { if i != 0 { - klog.V(3).InfoS("Attempt failed to kill all unwanted process from cgroup, retrying", "attempt", i, "cgroupName", podCgroup) + logger.V(3).Info("Attempt failed to kill all unwanted process from cgroup, retrying", "attempt", i, "cgroupName", podCgroup) } errlist = []error{} for _, pid := range pidsToKill { if _, ok := removed[pid]; ok { continue } - klog.V(3).InfoS("Attempting to kill process from cgroup", "pid", pid, "cgroupName", podCgroup) - if err := m.killOnePid(pid); err != nil { - klog.V(3).InfoS("Failed to kill process from cgroup", "pid", pid, "cgroupName", podCgroup, "err", err) + logger.V(3).Info("Attempting to kill process from cgroup", "pid", pid, "cgroupName", podCgroup) + if err := m.killOnePid(logger, pid); err != nil { + logger.V(3).Info("Failed to kill process from cgroup", "pid", pid, "cgroupName", podCgroup, "err", err) errlist = append(errlist, err) } else { removed[pid] = true } } if len(errlist) == 0 { - klog.V(3).InfoS("Successfully killed all unwanted processes from cgroup", "cgroupName", podCgroup) + logger.V(3).Info("Successfully killed all unwanted processes from cgroup", "cgroupName", podCgroup) return nil } } @@ -254,6 +257,9 @@ func (m *podContainerManagerImpl) IsPodCgroup(cgroupfs string) (bool, types.UID) // GetAllPodsFromCgroups scans through all the subsystems of pod cgroups // Get list of pods whose cgroup still exist on the cgroup mounts func (m *podContainerManagerImpl) GetAllPodsFromCgroups() (map[types.UID]CgroupName, error) { + // Use klog.TODO() because we currently do not have a proper logger to pass in. + // Replace this with an appropriate logger when refactoring this function to accept a logger parameter. + logger := klog.TODO() // Map for storing all the found pods on the disk foundPods := make(map[types.UID]CgroupName) qosContainersList := [3]CgroupName{m.qosContainersInfo.BestEffort, m.qosContainersInfo.Burstable, m.qosContainersInfo.Guaranteed} @@ -294,7 +300,7 @@ func (m *podContainerManagerImpl) GetAllPodsFromCgroups() (map[types.UID]CgroupN parts := strings.Split(basePath, podCgroupNamePrefix) // the uid is missing, so we log the unexpected cgroup not of form pod if len(parts) != 2 { - klog.InfoS("Pod cgroup manager ignored unexpected cgroup because it is not a pod", "path", cgroupfsPath) + logger.Info("Pod cgroup manager ignored unexpected cgroup because it is not a pod", "path", cgroupfsPath) continue } podUID := parts[1] diff --git a/pkg/kubelet/cm/pod_container_manager_linux_test.go b/pkg/kubelet/cm/pod_container_manager_linux_test.go index 971cecf19eb5b..8fe23e8429948 100644 --- a/pkg/kubelet/cm/pod_container_manager_linux_test.go +++ b/pkg/kubelet/cm/pod_container_manager_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/qos/doc.go b/pkg/kubelet/cm/qos/doc.go new file mode 100644 index 0000000000000..e5fd17f7fc901 --- /dev/null +++ b/pkg/kubelet/cm/qos/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2025 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 cm/qos includes the internal helper the Container Manager (cm) uses to perform +// resource allocation based on Quality of Service. +// The recommended import alias is `cmqos`. + +package qos diff --git a/pkg/kubelet/cm/qos/helpers.go b/pkg/kubelet/cm/qos/helpers.go new file mode 100644 index 0000000000000..30b14cc6aa323 --- /dev/null +++ b/pkg/kubelet/cm/qos/helpers.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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 qos + +import ( + v1 "k8s.io/api/core/v1" +) + +// IsContainerEquivalentQOSGuaranteed checks if a container is considered Guaranteed +// from the perspective of resource managers that allocate exclusive resources. +// This requires the container to have both requests and limits set and equal +// for both CPU and Memory. +func IsContainerEquivalentQOSGuaranteed(container *v1.Container) bool { + // Check container-level CPU requests and limits are equal. + cpuRequest := container.Resources.Requests[v1.ResourceCPU] + cpuLimit := container.Resources.Limits[v1.ResourceCPU] + if cpuRequest.IsZero() || !cpuRequest.Equal(cpuLimit) { + return false + } + + // Check container-level Memory requests and limits are equal. + memRequest := container.Resources.Requests[v1.ResourceMemory] + memLimit := container.Resources.Limits[v1.ResourceMemory] + if memRequest.IsZero() || !memRequest.Equal(memLimit) { + return false + } + + return true +} diff --git a/pkg/kubelet/cm/qos/helpers_test.go b/pkg/kubelet/cm/qos/helpers_test.go new file mode 100644 index 0000000000000..c82fb863bbf09 --- /dev/null +++ b/pkg/kubelet/cm/qos/helpers_test.go @@ -0,0 +1,154 @@ +/* +Copyright 2025 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 qos + +import ( + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestIsContainerQOSGuaranteed(t *testing.T) { + guaranteedResources := v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + } + burstableCPUResources := v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + } + burstableMemoryResources := v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + } + bestEffortResources := v1.ResourceRequirements{} + + testCases := []struct { + name string + pod *v1.Pod + expected bool + }{ + { + name: "Guaranteed pod, guaranteed container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "c1", Resources: guaranteedResources}}, + }, + Status: v1.PodStatus{ + QOSClass: v1.PodQOSGuaranteed, + }, + }, + expected: true, + }, + { + name: "Burstable pod, guaranteed container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "c1", Resources: guaranteedResources}, + }, + }, + Status: v1.PodStatus{ + QOSClass: v1.PodQOSBurstable, + }, + }, + expected: true, + }, + { + name: "Guaranteed pod, burstable CPU container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "c1", Resources: burstableCPUResources}}, + }, + Status: v1.PodStatus{ + QOSClass: v1.PodQOSGuaranteed, + }, + }, + expected: false, + }, + { + name: "Guaranteed pod, burstable Memory container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "c1", Resources: burstableMemoryResources}}, + }, + Status: v1.PodStatus{ + QOSClass: v1.PodQOSGuaranteed, + }, + }, + expected: false, + }, + { + name: "Guaranteed pod, besteffort container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "c1", Resources: bestEffortResources}}, + }, + Status: v1.PodStatus{ + QOSClass: v1.PodQOSGuaranteed, + }, + }, + expected: false, + }, + { + name: "BestEffort pod, besteffort container", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "c1", Resources: bestEffortResources}}, + }, + Status: v1.PodStatus{ + QOSClass: v1.PodQOSBestEffort, + }, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := IsContainerEquivalentQOSGuaranteed(&tc.pod.Spec.Containers[0]) + assert.Equal(t, tc.expected, result) + }) + } +} diff --git a/pkg/kubelet/cm/qos/types.go b/pkg/kubelet/cm/qos/types.go new file mode 100644 index 0000000000000..931bbdbb39ba5 --- /dev/null +++ b/pkg/kubelet/cm/qos/types.go @@ -0,0 +1,29 @@ +/* +Copyright 2025 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 qos + +// ResourceIsolationLevel defines the level of isolation for a resource. +type ResourceIsolationLevel string + +const ( + // ResourceIsolationHost implies the resource is shared with other containers on the host. + ResourceIsolationHost ResourceIsolationLevel = "host" + // ResourceIsolationPod implies the resource is isolated from other pods but shared within the pod. + ResourceIsolationPod ResourceIsolationLevel = "pod" + // ResourceIsolationContainer implies the resource is exclusive to the container. + ResourceIsolationContainer ResourceIsolationLevel = "container" +) diff --git a/pkg/kubelet/cm/qos_container_manager_linux.go b/pkg/kubelet/cm/qos_container_manager_linux.go index d30283afbc3a9..34e5209d18430 100644 --- a/pkg/kubelet/cm/qos_container_manager_linux.go +++ b/pkg/kubelet/cm/qos_container_manager_linux.go @@ -37,7 +37,9 @@ import ( "k8s.io/component-helpers/resource" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/managed" + kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics" ) const ( @@ -54,13 +56,14 @@ type QOSContainerManager interface { type qosContainerManagerImpl struct { sync.Mutex - qosContainersInfo QOSContainersInfo - subsystems *CgroupSubsystems - cgroupManager CgroupManager - activePods ActivePodsFunc - getNodeAllocatable func() v1.ResourceList - cgroupRoot CgroupName - qosReserved map[v1.ResourceName]int64 + qosContainersInfo QOSContainersInfo + subsystems *CgroupSubsystems + cgroupManager CgroupManager + activePods ActivePodsFunc + getNodeAllocatable func() v1.ResourceList + cgroupRoot CgroupName + qosReserved map[v1.ResourceName]int64 + memoryReservationPolicy kubeletconfig.MemoryReservationPolicy } func NewQOSContainerManager(subsystems *CgroupSubsystems, cgroupRoot CgroupName, nodeConfig NodeConfig, cgroupManager CgroupManager) (QOSContainerManager, error) { @@ -71,10 +74,11 @@ func NewQOSContainerManager(subsystems *CgroupSubsystems, cgroupRoot CgroupName, } return &qosContainerManagerImpl{ - subsystems: subsystems, - cgroupManager: cgroupManager, - cgroupRoot: cgroupRoot, - qosReserved: nodeConfig.QOSReserved, + subsystems: subsystems, + cgroupManager: cgroupManager, + cgroupRoot: cgroupRoot, + qosReserved: nodeConfig.QOSReserved, + memoryReservationPolicy: nodeConfig.MemoryReservationPolicy, }, nil } @@ -142,7 +146,7 @@ func (m *qosContainerManagerImpl) Start(ctx context.Context, getNodeAllocatable go wait.Until(func() { err := m.UpdateCgroups(logger) if err != nil { - klog.InfoS("Failed to reserve QoS requests", "err", err) + logger.Info("Failed to reserve QoS requests", "err", err) } }, periodicQOSCgroupUpdateInterval, wait.NeverStop) @@ -237,23 +241,23 @@ func (m *qosContainerManagerImpl) getQoSMemoryRequests() map[v1.PodQOSClass]int6 // setMemoryReserve sums the memory limits of all pods in a QOS class, // calculates QOS class memory limits, and set those limits in the // CgroupConfig for each QOS class. -func (m *qosContainerManagerImpl) setMemoryReserve(configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) { +func (m *qosContainerManagerImpl) setMemoryReserve(logger klog.Logger, configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) { qosMemoryRequests := m.getQoSMemoryRequests() resources := m.getNodeAllocatable() allocatableResource, ok := resources[v1.ResourceMemory] if !ok { - klog.V(2).InfoS("Allocatable memory value could not be determined, not setting QoS memory limits") + logger.V(2).Info("Allocatable memory value could not be determined, not setting QoS memory limits") return } allocatable := allocatableResource.Value() if allocatable == 0 { - klog.V(2).InfoS("Allocatable memory reported as 0, might be in standalone mode, not setting QoS memory limits") + logger.V(2).Info("Allocatable memory reported as 0, might be in standalone mode, not setting QoS memory limits") return } for qos, limits := range qosMemoryRequests { - klog.V(2).InfoS("QoS pod memory limit", "qos", qos, "limits", limits, "percentReserve", percentReserve) + logger.V(2).Info("QoS pod memory limit", "qos", qos, "limits", limits, "percentReserve", percentReserve) } // Calculate QOS memory limits @@ -266,14 +270,14 @@ func (m *qosContainerManagerImpl) setMemoryReserve(configs map[v1.PodQOSClass]*C // retrySetMemoryReserve checks for any QoS cgroups over the limit // that was attempted to be set in the first Update() and adjusts // their memory limit to the usage to prevent further growth. -func (m *qosContainerManagerImpl) retrySetMemoryReserve(configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) { +func (m *qosContainerManagerImpl) retrySetMemoryReserve(logger klog.Logger, configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) { // Unreclaimable memory usage may already exceeded the desired limit // Attempt to set the limit near the current usage to put pressure // on the cgroup and prevent further growth. for qos, config := range configs { usage, err := m.cgroupManager.MemoryUsage(config.Name) if err != nil { - klog.V(2).InfoS("Failed to get resource stats", "err", err) + logger.V(2).Info("Failed to get resource stats", "err", err) return } @@ -289,32 +293,39 @@ func (m *qosContainerManagerImpl) retrySetMemoryReserve(configs map[v1.PodQOSCla } } -// setMemoryQoS sums the memory requests of all pods in the Burstable class, -// and set the sum memory as the memory.min in the Unified field of CgroupConfig. -func (m *qosContainerManagerImpl) setMemoryQoS(configs map[v1.PodQOSClass]*CgroupConfig) { - qosMemoryRequests := m.getQoSMemoryRequests() - - // Calculate the memory.min: - // for burstable(/kubepods/burstable): sum of all burstable pods - // for guaranteed(/kubepods): sum of all guaranteed and burstable pods - burstableMin := qosMemoryRequests[v1.PodQOSBurstable] - guaranteedMin := qosMemoryRequests[v1.PodQOSGuaranteed] + burstableMin - - if burstableMin > 0 { - if configs[v1.PodQOSBurstable].ResourceParameters.Unified == nil { - configs[v1.PodQOSBurstable].ResourceParameters.Unified = make(map[string]string) +// setMemoryQoS sets cgroup v2 memory protection for QoS-class cgroups. +// Guaranteed pods get memory.min (hard protection), Burstable pods get memory.low (soft protection). +func (m *qosContainerManagerImpl) setMemoryQoS(logger klog.Logger, configs map[v1.PodQOSClass]*CgroupConfig) { + setUnified := func(qos v1.PodQOSClass, key string, value int64) { + if configs[qos].ResourceParameters.Unified == nil { + configs[qos].ResourceParameters.Unified = make(map[string]string) } - configs[v1.PodQOSBurstable].ResourceParameters.Unified[Cgroup2MemoryMin] = strconv.FormatInt(burstableMin, 10) - klog.V(4).InfoS("MemoryQoS config for qos", "qos", v1.PodQOSBurstable, "memoryMin", burstableMin) + configs[qos].ResourceParameters.Unified[key] = strconv.FormatInt(value, 10) + logger.V(4).Info("MemoryQoS config for qos", "qos", qos, "key", key, "value", value) } - if guaranteedMin > 0 { - if configs[v1.PodQOSGuaranteed].ResourceParameters.Unified == nil { - configs[v1.PodQOSGuaranteed].ResourceParameters.Unified = make(map[string]string) - } - configs[v1.PodQOSGuaranteed].ResourceParameters.Unified[Cgroup2MemoryMin] = strconv.FormatInt(guaranteedMin, 10) - klog.V(4).InfoS("MemoryQoS config for qos", "qos", v1.PodQOSGuaranteed, "memoryMin", guaranteedMin) + if m.memoryReservationPolicy != kubeletconfig.TieredReservationMemoryReservationPolicy { + setUnified(v1.PodQOSGuaranteed, Cgroup2MemoryMin, 0) + setUnified(v1.PodQOSBurstable, Cgroup2MemoryLow, 0) + kubeletmetrics.MemoryQoSNodeMemoryMinBytes.Set(0) + kubeletmetrics.MemoryQoSNodeMemoryLowBytes.Set(0) + return } + + qosMemoryRequests := m.getQoSMemoryRequests() + + burstableRequests := qosMemoryRequests[v1.PodQOSBurstable] + guaranteedRequests := qosMemoryRequests[v1.PodQOSGuaranteed] + + kubeletmetrics.MemoryQoSNodeMemoryMinBytes.Set(float64(guaranteedRequests)) + kubeletmetrics.MemoryQoSNodeMemoryLowBytes.Set(float64(burstableRequests)) + + // Guaranteed QoS class: memory.min = sum of guaranteed + burstable requests + // (parent must cover children's protection for it to be effective) + setUnified(v1.PodQOSGuaranteed, Cgroup2MemoryMin, guaranteedRequests+burstableRequests) + + // Burstable QoS class: memory.low = sum of burstable pod requests + setUnified(v1.PodQOSBurstable, Cgroup2MemoryLow, burstableRequests) } func (m *qosContainerManagerImpl) UpdateCgroups(logger logr.Logger) error { @@ -346,17 +357,17 @@ func (m *qosContainerManagerImpl) UpdateCgroups(logger logr.Logger) error { return err } - // update the qos level cgrougs v2 settings of memory qos if feature enabled - if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.MemoryQoS) && - libcontainercgroups.IsCgroup2UnifiedMode() { - m.setMemoryQoS(qosConfigs) + // Update cgroup v2 memory.min settings. Called only when MemoryQoS is + // enabled and cgroups v2 is the unified mode. + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.MemoryQoS) && libcontainercgroups.IsCgroup2UnifiedMode() { + m.setMemoryQoS(logger, qosConfigs) } if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.QOSReserved) { for resource, percentReserve := range m.qosReserved { switch resource { case v1.ResourceMemory: - m.setMemoryReserve(qosConfigs, percentReserve) + m.setMemoryReserve(logger, qosConfigs, percentReserve) } } @@ -368,7 +379,7 @@ func (m *qosContainerManagerImpl) UpdateCgroups(logger logr.Logger) error { } } if updateSuccess { - klog.V(4).InfoS("Updated QoS cgroup configuration") + logger.V(4).Info("Updated QoS cgroup configuration") return nil } @@ -378,7 +389,7 @@ func (m *qosContainerManagerImpl) UpdateCgroups(logger logr.Logger) error { for resource, percentReserve := range m.qosReserved { switch resource { case v1.ResourceMemory: - m.retrySetMemoryReserve(qosConfigs, percentReserve) + m.retrySetMemoryReserve(logger, qosConfigs, percentReserve) } } } @@ -386,12 +397,12 @@ func (m *qosContainerManagerImpl) UpdateCgroups(logger logr.Logger) error { for _, config := range qosConfigs { err := m.cgroupManager.Update(logger, config) if err != nil { - klog.ErrorS(err, "Failed to update QoS cgroup configuration") + logger.Error(err, "Failed to update QoS cgroup configuration") return err } } - klog.V(4).InfoS("Updated QoS cgroup configuration") + logger.V(4).Info("Updated QoS cgroup configuration") return nil } diff --git a/pkg/kubelet/cm/qos_container_manager_linux_test.go b/pkg/kubelet/cm/qos_container_manager_linux_test.go index f06778a208ce8..0eb45df1f3197 100644 --- a/pkg/kubelet/cm/qos_container_manager_linux_test.go +++ b/pkg/kubelet/cm/qos_container_manager_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. @@ -20,16 +19,24 @@ limitations under the License. package cm import ( + "context" "fmt" "strconv" + "strings" + "sync" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" + resource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" + kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" ) func activeTestPods() []*v1.Pod { @@ -118,10 +125,11 @@ func createTestQOSContainerManager(logger klog.Logger) (*qosContainerManagerImpl cgroupRoot = NewCgroupName(cgroupRoot, defaultNodeAllocatableCgroupName) qosContainerManager := &qosContainerManagerImpl{ - subsystems: subsystems, - cgroupManager: NewCgroupManager(logger, subsystems, "cgroupfs"), - cgroupRoot: cgroupRoot, - qosReserved: nil, + subsystems: subsystems, + cgroupManager: NewCgroupManager(logger, subsystems, "cgroupfs"), + cgroupRoot: cgroupRoot, + qosReserved: nil, + memoryReservationPolicy: kubeletconfig.NoneMemoryReservationPolicy, } qosContainerManager.activePods = activeTestPods @@ -130,10 +138,134 @@ func createTestQOSContainerManager(logger klog.Logger) (*qosContainerManagerImpl } func TestQoSContainerCgroup(t *testing.T) { + burstableMin := resource.MustParse("384Mi") + guaranteedMin := resource.MustParse("128Mi") + + tests := []struct { + name string + pods []*v1.Pod + initialGuaranteed string + initialBurstable string + expectedGuaranteed string + expectedBurstable string + }{ + { + name: "writes aggregated memory min", + pods: activeTestPods(), + initialGuaranteed: "", + initialBurstable: "", + expectedGuaranteed: strconv.FormatInt(burstableMin.Value()+guaranteedMin.Value(), 10), + expectedBurstable: strconv.FormatInt(burstableMin.Value(), 10), + }, + { + name: "writes zero memory min for best effort pod", + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{UID: "99999999", Name: "besteffort-pod", Namespace: "test"}, + Spec: v1.PodSpec{Containers: []v1.Container{{Name: "foo", Image: "busybox"}}}, + }, + }, + initialGuaranteed: "", + initialBurstable: "", + expectedGuaranteed: "0", + expectedBurstable: "0", + }, + { + name: "writes zero memory min for burstable pod without memory request", + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{UID: "88888888", Name: "burstable-pod-no-memory-request", Namespace: "test"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + }, + }, + }, + }, + }, + }, + initialGuaranteed: "", + initialBurstable: "", + expectedGuaranteed: "0", + expectedBurstable: "0", + }, + { + name: "clears stale memory min when all pods removed", + pods: nil, + initialGuaranteed: "1234", + initialBurstable: "5678", + expectedGuaranteed: "0", + expectedBurstable: "0", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + m, err := createTestQOSContainerManager(logger) + require.NoError(t, err) + // Set memory reservation policy to TieredReservation to enable memory.min + m.memoryReservationPolicy = kubeletconfig.TieredReservationMemoryReservationPolicy + m.activePods = func() []*v1.Pod { return tc.pods } + + guaranteedUnified := map[string]string{} + if tc.initialGuaranteed != "" { + guaranteedUnified[Cgroup2MemoryMin] = tc.initialGuaranteed + } + burstableUnified := map[string]string{} + if tc.initialBurstable != "" { + burstableUnified[Cgroup2MemoryLow] = tc.initialBurstable + } + + qosConfigs := map[v1.PodQOSClass]*CgroupConfig{ + v1.PodQOSGuaranteed: { + Name: m.qosContainersInfo.Guaranteed, + ResourceParameters: &ResourceConfig{ + Unified: guaranteedUnified, + }, + }, + v1.PodQOSBurstable: { + Name: m.qosContainersInfo.Burstable, + ResourceParameters: &ResourceConfig{ + Unified: burstableUnified, + }, + }, + v1.PodQOSBestEffort: { + Name: m.qosContainersInfo.BestEffort, + ResourceParameters: &ResourceConfig{}, + }, + } + + m.setMemoryQoS(logger, qosConfigs) + + assert.Equal(t, tc.expectedGuaranteed, qosConfigs[v1.PodQOSGuaranteed].ResourceParameters.Unified[Cgroup2MemoryMin]) + assert.Equal(t, tc.expectedBurstable, qosConfigs[v1.PodQOSBurstable].ResourceParameters.Unified[Cgroup2MemoryLow]) + }) + } +} + +func TestQoSContainerCgroupWithMemoryReservationPolicyNone(t *testing.T) { logger, _ := ktesting.NewTestContext(t) - m, err := createTestQOSContainerManager(logger) - assert.NoError(t, err) + fakeCM := &fakeCgroupManager{} + cgroupRoot := ParseCgroupfsToCgroupName("/") + cgroupRoot = NewCgroupName(cgroupRoot, defaultNodeAllocatableCgroupName) + m := &qosContainerManagerImpl{ + cgroupManager: fakeCM, + cgroupRoot: cgroupRoot, + activePods: activeTestPods, + memoryReservationPolicy: kubeletconfig.NoneMemoryReservationPolicy, + qosContainersInfo: QOSContainersInfo{ + Guaranteed: cgroupRoot, + Burstable: NewCgroupName(cgroupRoot, "burstable"), + BestEffort: NewCgroupName(cgroupRoot, "besteffort"), + }, + } + // memoryReservationPolicy defaults to NoneMemoryReservationPolicy, so memory.min should be explicitly reset. qosConfigs := map[v1.PodQOSClass]*CgroupConfig{ v1.PodQOSGuaranteed: { Name: m.qosContainersInfo.Guaranteed, @@ -149,10 +281,380 @@ func TestQoSContainerCgroup(t *testing.T) { }, } - m.setMemoryQoS(qosConfigs) + m.setMemoryQoS(logger, qosConfigs) - burstableMin := resource.MustParse("384Mi") - guaranteedMin := resource.MustParse("128Mi") - assert.Equal(t, qosConfigs[v1.PodQOSGuaranteed].ResourceParameters.Unified["memory.min"], strconv.FormatInt(burstableMin.Value()+guaranteedMin.Value(), 10)) - assert.Equal(t, qosConfigs[v1.PodQOSBurstable].ResourceParameters.Unified["memory.min"], strconv.FormatInt(burstableMin.Value(), 10)) + assert.Equal(t, "0", qosConfigs[v1.PodQOSGuaranteed].ResourceParameters.Unified[Cgroup2MemoryMin]) + assert.Equal(t, "0", qosConfigs[v1.PodQOSBurstable].ResourceParameters.Unified[Cgroup2MemoryLow]) +} + +// fakeCgroupManager is used because Start() requires a functional +// CgroupManager. All methods are stubbed so that Start() can +// complete successfully without using real cgroups. +type fakeCgroupManager struct { + mutex sync.Mutex + created []*CgroupConfig + updates []*CgroupConfig +} + +// Update() is the observation point for this test. +// Capture the updated cgroup config so it can be validated. +func (f *fakeCgroupManager) Update(logger klog.Logger, config *CgroupConfig) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + copiedConfig := *config + f.updates = append(f.updates, &copiedConfig) + return nil +} + +// Create() must succeed for Start() to construct QoS cgroups. +// We do not assert on Create() behavior in this test. +func (f *fakeCgroupManager) Create(l klog.Logger, config *CgroupConfig) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + copiedConfig := *config + f.created = append(f.created, &copiedConfig) + return nil +} + +func (f *fakeCgroupManager) Destroy(l klog.Logger, config *CgroupConfig) error { return nil } +func (f *fakeCgroupManager) Validate(name CgroupName) error { return nil } +func (f *fakeCgroupManager) Exists(name CgroupName) bool { return false } +func (f *fakeCgroupManager) Name(name CgroupName) string { return name.ToCgroupfs() } +func (f *fakeCgroupManager) CgroupName(name string) CgroupName { + return ParseCgroupfsToCgroupName(name) +} +func (f *fakeCgroupManager) Pids(logger klog.Logger, name CgroupName) []int { return nil } +func (f *fakeCgroupManager) ReduceCPULimits(logger klog.Logger, cgroupName CgroupName) error { + return nil +} +func (f *fakeCgroupManager) MemoryUsage(name CgroupName) (int64, error) { return int64(0), nil } +func (f *fakeCgroupManager) GetCgroupConfig(name CgroupName, resource v1.ResourceName) (*ResourceConfig, error) { + return nil, nil +} +func (f *fakeCgroupManager) SetCgroupConfig(logger klog.Logger, name CgroupName, resourceConfig *ResourceConfig) error { + return nil +} +func (f *fakeCgroupManager) SetCPULoadBalanceDisable() {} +func (f *fakeCgroupManager) Version() int { return 1 } + +// TestQOSCPUConfigUpdate verifies that UpdateCgroups() computes and +// updates the correct CPU shares for each QoS class based on the +// currently active pods. +func TestQOSCPUConfigUpdate(t *testing.T) { + + // Guaranteed QoS uses fixed CPU shares (requests == limits), so they are not + // recalculated. BestEffort always uses MinShares, and only Burstable CPU shares + // depend on aggregate burstable CPU requests. + + tests := []struct { + name string + testPods ActivePodsFunc + expectedBurstableCPUShares uint64 // Recalculation will be done only for Burstable QoS class + }{ + { + name: "guaranteed-pods-only", + testPods: func() []*v1.Pod { + return []*v1.Pod{ + + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "guaranteed-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + }, + }, + }, + } + }, + // MinShares will be given to the Burstable QoS class since kubelet + // creates all QoS cgroups regardless of whether pods of that class exist. + expectedBurstableCPUShares: MinShares, + }, + { + name: "burstable-pods-only", + testPods: func() []*v1.Pod { + return []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "burstable-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + }, + }, + }, + }, + } + }, + // 1 CPU Resource = 1024 CPU Shares + expectedBurstableCPUShares: 1024, + }, + { + name: "besteffort-pods-only", + testPods: func() []*v1.Pod { + return []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "besteffort-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + }, + }, + }, + }, + } + }, + expectedBurstableCPUShares: MinShares, + }, + { + name: "guaranteed-and-burstable-pods", + testPods: func() []*v1.Pod { + return []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "guaranteed-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "burstable-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + }, + }, + }, + }, + } + }, + expectedBurstableCPUShares: 1024, + }, + { + name: "besteffort-and-burstable-pods", + testPods: func() []*v1.Pod { + return []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "besteffort-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uuid.NewUUID()), + Name: "burstable-pod", + Namespace: "test", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + }, + }, + }, + }, + } + }, + expectedBurstableCPUShares: 1024, + }, + } + + for _, testCase := range tests { + + t.Run(testCase.name, func(t *testing.T) { + + logger, ctx := ktesting.NewTestContext(t) + + testContainerManager, err := createTestQOSContainerManager(logger) + if err != nil { + t.Fatalf("Unable to create Test Qos Container Manager: %s", err) + return + } + + fakecgroupManager := &fakeCgroupManager{} + testContainerManager.cgroupManager = fakecgroupManager + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + err = testContainerManager.Start(ctx, func() v1.ResourceList { return v1.ResourceList{} }, testCase.testPods) + + if err != nil { + t.Fatalf("Start() failed: %s", err) + } + + // UpdateCgroups() is expected to update all QoS cgroups on each call + // based on the current active pod set. + err = testContainerManager.UpdateCgroups(logger) + if err != nil { + t.Fatalf("Error in UpdateCgroups(): %s", err) + } + cancel() + + // UpdateCgroups() may also be running in the background reconciliation loop (because of Start()). + // Retry until a consistent snapshot containing all QoS cgroup updates is observed. + // + // A small bounded retry count is sufficient here because UpdateCgroups() + // is synchronous and expected to complete quickly. This avoids test flakiness + // without risking an unbounded wait. + maxRetryAttempts := 5 + + var ( + foundBurstable bool + foundBestEffort bool + foundGuaranteed bool + ) + + for i := 0; i < maxRetryAttempts; i++ { + + // These flags will be used to check whether UpdateCgroups() + // is updating the CPU shares for all QoS classes (cgroups) + foundBurstable = false + foundBestEffort = false + foundGuaranteed = false + + // Start() initiates a background UpdateCgroups() goroutine which may + // still be running. Take a snapshot to avoid observing updates from + // the background UpdateCgroups() instead of the explicit UpdateCgroups() call. + fakecgroupManager.mutex.Lock() + updates := append([]*CgroupConfig(nil), fakecgroupManager.updates...) + fakecgroupManager.mutex.Unlock() + + for _, config := range updates { + + if strings.HasSuffix(config.Name.ToCgroupfs(), "burstable") { + foundBurstable = true + if *config.ResourceParameters.CPUShares != testCase.expectedBurstableCPUShares { + t.Fatalf("Expected CPU Shares for Burstable: %d Got: %d", testCase.expectedBurstableCPUShares, *config.ResourceParameters.CPUShares) + } + continue + } + + if strings.HasSuffix(config.Name.ToCgroupfs(), "besteffort") { + foundBestEffort = true + if *config.ResourceParameters.CPUShares != MinShares { + t.Fatalf("Expected CPU Shares for BestEffort: %d Got: %d", MinShares, *config.ResourceParameters.CPUShares) + } + continue + } + + if config.Name.ToCgroupfs() == testContainerManager.cgroupRoot.ToCgroupfs() { + foundGuaranteed = true + if config.ResourceParameters != nil && config.ResourceParameters.CPUShares != nil { + t.Fatalf("Expected CPU Shares for Guaranteed: , Got: %d", *config.ResourceParameters.CPUShares) + } + } + } + + if foundBurstable && foundBestEffort && foundGuaranteed { + break + } + + time.Sleep(time.Millisecond * 10) + } + + if !foundBurstable || !foundBestEffort || !foundGuaranteed { + + t.Fatalf( + "did not observe all QoS cgroup updates after %d retries (guaranteed=%v, burstable=%v, besteffort=%v)", + maxRetryAttempts, foundGuaranteed, foundBurstable, foundBestEffort, + ) + } + }) + } } diff --git a/pkg/kubelet/cm/topologymanager/fake_topology_manager.go b/pkg/kubelet/cm/topologymanager/fake_topology_manager.go index a79a0a388752e..d6ef911e24909 100644 --- a/pkg/kubelet/cm/topologymanager/fake_topology_manager.go +++ b/pkg/kubelet/cm/topologymanager/fake_topology_manager.go @@ -19,7 +19,7 @@ package topologymanager import ( "context" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/lifecycle" @@ -28,6 +28,7 @@ import ( type fakeManager struct { hint *TopologyHint policy Policy + scope string } // NewFakeManager returns an instance of FakeManager @@ -38,6 +39,13 @@ func NewFakeManager() Manager { return &fakeManager{} } +// NewFakeManagerWithScope returns an instance of fake topology manager with specified scope +func NewFakeManagerWithScope(scope string) Manager { + return &fakeManager{ + scope: scope, + } +} + // NewFakeManagerWithHint returns an instance of fake topology manager with specified topology hints func NewFakeManagerWithHint(hint *TopologyHint) Manager { // Use klog.TODO() because changing NewManager requires changes in too many other components @@ -76,6 +84,10 @@ func (m *fakeManager) GetPolicy() Policy { return m.policy } +func (m *fakeManager) Name() string { + return m.scope +} + func (m *fakeManager) AddHintProvider(logger klog.Logger, h HintProvider) { logger.Info("AddHintProvider", "hintProvider", h) } diff --git a/pkg/kubelet/cm/topologymanager/scope.go b/pkg/kubelet/cm/topologymanager/scope.go index ff34253df953c..21103a6a11215 100644 --- a/pkg/kubelet/cm/topologymanager/scope.go +++ b/pkg/kubelet/cm/topologymanager/scope.go @@ -28,12 +28,12 @@ import ( ) const ( - // containerTopologyScope specifies the TopologyManagerScope per container. - containerTopologyScope = "container" - // podTopologyScope specifies the TopologyManagerScope per pod. - podTopologyScope = "pod" + // ContainerTopologyScope specifies the TopologyManagerScope per container. + ContainerTopologyScope = "container" + // PodTopologyScope specifies the TopologyManagerScope per pod. + PodTopologyScope = "pod" // noneTopologyScope specifies the TopologyManagerScope when topologyPolicyName is none. - noneTopologyScope = "none" + NoneTopologyScope = "none" ) type podTopologyHints map[string]map[string]TopologyHint @@ -151,7 +151,7 @@ func (s *scope) admitPolicyNone(pod *v1.Pod) lifecycle.PodAdmitResult { } // It would be better to implement this function in topologymanager instead of scope -// but topologymanager do not track providers anymore +// but topologymanager does not track providers anymore func (s *scope) allocateAlignedResources(pod *v1.Pod, container *v1.Container) error { for _, provider := range s.hintProviders { err := provider.Allocate(pod, container) @@ -161,3 +161,13 @@ func (s *scope) allocateAlignedResources(pod *v1.Pod, container *v1.Container) e } return nil } + +func (s *scope) allocatePodAlignedResources(pod *v1.Pod) error { + for _, provider := range s.hintProviders { + err := provider.AllocatePod(pod) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/kubelet/cm/topologymanager/scope_container.go b/pkg/kubelet/cm/topologymanager/scope_container.go index d8a68ec451558..7c7551f1fcc09 100644 --- a/pkg/kubelet/cm/topologymanager/scope_container.go +++ b/pkg/kubelet/cm/topologymanager/scope_container.go @@ -20,7 +20,10 @@ import ( "context" "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + resourcehelper "k8s.io/component-helpers/resource" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" "k8s.io/kubernetes/pkg/kubelet/lifecycle" @@ -38,7 +41,7 @@ var _ Scope = &containerScope{} func NewContainerScope(policy Policy) Scope { return &containerScope{ scope{ - name: containerTopologyScope, + name: ContainerTopologyScope, podTopologyHints: podTopologyHints{}, policy: policy, podMap: containermap.NewContainerMap(), @@ -58,6 +61,9 @@ func (s *containerScope) Admit(ctx context.Context, pod *v1.Pod) lifecycle.PodAd metrics.ContainerAlignedComputeResourcesFailure.WithLabelValues(metrics.AlignScopeContainer, metrics.AlignedNUMANode).Inc() } metrics.TopologyManagerAdmissionErrorsTotal.Inc() + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + return admission.GetPodAdmitResult(NewPodLevelTopologyAffinityError("pod with pod-level resources failed admission with a container-level topology manager")) + } return admission.GetPodAdmitResult(&TopologyAffinityError{}) } logger.Info("Topology Affinity", "bestHint", bestHint, "pod", klog.KObj(pod), "containerName", container.Name) diff --git a/pkg/kubelet/cm/topologymanager/scope_container_test.go b/pkg/kubelet/cm/topologymanager/scope_container_test.go index bb597169ef619..4b623075badee 100644 --- a/pkg/kubelet/cm/topologymanager/scope_container_test.go +++ b/pkg/kubelet/cm/topologymanager/scope_container_test.go @@ -129,7 +129,7 @@ func TestContainerCalculateAffinity(t *testing.T) { scope{ hintProviders: tc.hp, policy: &mockPolicy{}, - name: podTopologyScope, + name: PodTopologyScope, }, } diff --git a/pkg/kubelet/cm/topologymanager/scope_none.go b/pkg/kubelet/cm/topologymanager/scope_none.go index 44f6c32158f1d..dc054ba3c44af 100644 --- a/pkg/kubelet/cm/topologymanager/scope_none.go +++ b/pkg/kubelet/cm/topologymanager/scope_none.go @@ -35,7 +35,7 @@ var _ Scope = &noneScope{} func NewNoneScope() Scope { return &noneScope{ scope{ - name: noneTopologyScope, + name: NoneTopologyScope, podTopologyHints: podTopologyHints{}, policy: NewNonePolicy(), podMap: containermap.NewContainerMap(), diff --git a/pkg/kubelet/cm/topologymanager/scope_pod.go b/pkg/kubelet/cm/topologymanager/scope_pod.go index 8498ccbd89a04..21df862ef116d 100644 --- a/pkg/kubelet/cm/topologymanager/scope_pod.go +++ b/pkg/kubelet/cm/topologymanager/scope_pod.go @@ -19,8 +19,11 @@ package topologymanager import ( "context" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + resourcehelper "k8s.io/component-helpers/resource" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/cm/admission" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" "k8s.io/kubernetes/pkg/kubelet/lifecycle" @@ -38,7 +41,7 @@ var _ Scope = &podScope{} func NewPodScope(policy Policy) Scope { return &podScope{ scope{ - name: podTopologyScope, + name: PodTopologyScope, podTopologyHints: podTopologyHints{}, policy: policy, podMap: containermap.NewContainerMap(), @@ -47,17 +50,21 @@ func NewPodScope(policy Policy) Scope { } func (s *podScope) Admit(ctx context.Context, pod *v1.Pod) lifecycle.PodAdmitResult { + // If the PodLevelResourceManagers feature is enabled, delegate the resource + // allocation to the hint providers (CPU and Memory managers) via the AllocatePod call. + // This is a one-time allocation for the entire pod. + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) && resourcehelper.IsPodLevelResourcesSet(pod) { + return s.admitUsingPodResources(ctx, pod) + } + return s.admitUsingContainerResources(ctx, pod) +} + +func (s *podScope) admitUsingContainerResources(ctx context.Context, pod *v1.Pod) lifecycle.PodAdmitResult { logger := klog.FromContext(ctx) - bestHint, admit := s.calculateAffinity(logger, pod) - logger.Info("Best TopologyHint", "bestHint", bestHint, "pod", klog.KObj(pod)) + bestHint, admit := s.checkAffinity(logger, pod) if !admit { - if IsAlignmentGuaranteed(s.policy) { - // increment only if we know we allocate aligned resources. - metrics.ContainerAlignedComputeResourcesFailure.WithLabelValues(metrics.AlignScopePod, metrics.AlignedNUMANode).Inc() - } - metrics.TopologyManagerAdmissionErrorsTotal.Inc() - return admission.GetPodAdmitResult(&TopologyAffinityError{}) + return admission.GetPodAdmitResult(NewTopologyAffinityError()) } for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { @@ -70,12 +77,63 @@ func (s *podScope) Admit(ctx context.Context, pod *v1.Pod) lifecycle.PodAdmitRes return admission.GetPodAdmitResult(err) } } + + s.updateSuccessMetrics(logger, pod) + return admission.GetPodAdmitResult(nil) +} + +func (s *podScope) admitUsingPodResources(ctx context.Context, pod *v1.Pod) lifecycle.PodAdmitResult { + logger := klog.FromContext(ctx) + + bestHint, admit := s.checkAffinity(logger, pod) + if !admit { + return admission.GetPodAdmitResult(NewPodLevelTopologyAffinityError("pod with pod-level resources failed admission under pod-scope topology manager")) + } + + // If the PodLevelResourceManagers feature is enabled, delegate the resource + // allocation to the hint providers (CPU and Memory managers) via the AllocatePod call. + // This is a one-time allocation for the entire pod. + logger.V(4).Info("Calling AllocatePod on hint providers", "pod", klog.KObj(pod)) + + // Store the best hint for all containers. + for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { + logger.Info("Topology Affinity", "bestHint", bestHint, "pod", klog.KObj(pod), "containerName", container.Name) + s.setTopologyHints(string(pod.UID), container.Name, bestHint) + } + + err := s.allocatePodAlignedResources(pod) + if err != nil { + logger.Error(err, "Pod-level allocation failed", "pod", klog.KObj(pod)) + metrics.TopologyManagerAdmissionErrorsTotal.Inc() + return admission.GetPodAdmitResult(err) + } + + s.updateSuccessMetrics(logger, pod) + return admission.GetPodAdmitResult(nil) +} + +func (s *podScope) checkAffinity(logger klog.Logger, pod *v1.Pod) (TopologyHint, bool) { + // Calculate the best NUMA affinity for the pod as a whole. + bestHint, admit := s.calculateAffinity(logger, pod) + logger.Info("Best TopologyHint", "bestHint", bestHint, "pod", klog.KObj(pod)) + if !admit { + if IsAlignmentGuaranteed(s.policy) { + // Increment failure metric only if alignment was guaranteed. + metrics.ContainerAlignedComputeResourcesFailure.WithLabelValues(metrics.AlignScopePod, metrics.AlignedNUMANode).Inc() + } + metrics.TopologyManagerAdmissionErrorsTotal.Inc() + + return TopologyHint{}, false + } + return bestHint, true +} + +func (s *podScope) updateSuccessMetrics(logger klog.Logger, pod *v1.Pod) { if IsAlignmentGuaranteed(s.policy) { - // increment only if we know we allocate aligned resources. + // Increment success metric only if alignment was guaranteed. logger.V(4).Info("Resource alignment at pod scope guaranteed", "pod", klog.KObj(pod)) metrics.ContainerAlignedComputeResources.WithLabelValues(metrics.AlignScopePod, metrics.AlignedNUMANode).Inc() } - return admission.GetPodAdmitResult(nil) } func (s *podScope) accumulateProvidersHints(logger klog.Logger, pod *v1.Pod) []map[string][]TopologyHint { diff --git a/pkg/kubelet/cm/topologymanager/scope_pod_test.go b/pkg/kubelet/cm/topologymanager/scope_pod_test.go index c8d347f552f34..ac6c778da9abd 100644 --- a/pkg/kubelet/cm/topologymanager/scope_pod_test.go +++ b/pkg/kubelet/cm/topologymanager/scope_pod_test.go @@ -129,7 +129,7 @@ func TestPodCalculateAffinity(t *testing.T) { scope{ hintProviders: tc.hp, policy: &mockPolicy{}, - name: podTopologyScope, + name: PodTopologyScope, }, } diff --git a/pkg/kubelet/cm/topologymanager/topology_manager.go b/pkg/kubelet/cm/topologymanager/topology_manager.go index 458efe178d9ef..50a4b33a1e739 100644 --- a/pkg/kubelet/cm/topologymanager/topology_manager.go +++ b/pkg/kubelet/cm/topologymanager/topology_manager.go @@ -46,6 +46,11 @@ const ( // TopologyAffinityError represents an resource alignment error type TopologyAffinityError struct{} +// NewTopologyAffinityError returns a new TopologyAffinityError. +func NewTopologyAffinityError() *TopologyAffinityError { + return &TopologyAffinityError{} +} + func (e TopologyAffinityError) Error() string { return "Resources cannot be allocated with Topology locality" } @@ -54,6 +59,27 @@ func (e TopologyAffinityError) Type() string { return ErrorTopologyAffinity } +// PodLevelTopologyAffinityError represents a pod-level resource alignment error. +type PodLevelTopologyAffinityError struct { + message string +} + +// NewPodLevelTopologyAffinityError returns a new PodLevelTopologyAffinityError. +func NewPodLevelTopologyAffinityError(message string) *PodLevelTopologyAffinityError { + return &PodLevelTopologyAffinityError{message: message} +} + +func (e *PodLevelTopologyAffinityError) Error() string { + if e.message != "" { + return "Pod Scope " + e.message + } + return "Pod Scope Resources cannot be allocated with Topology locality" +} + +func (e *PodLevelTopologyAffinityError) Type() string { + return ErrorTopologyAffinity +} + // Manager interface provides methods for Kubelet to manage pod topology hints type Manager interface { // PodAdmitHandler is implemented by Manager @@ -89,6 +115,8 @@ type HintProvider interface { // GetPodTopologyHints returns a map of resource names to a list of possible // concrete resource allocations per Pod in terms of NUMA locality hints. GetPodTopologyHints(pod *v1.Pod) map[string][]TopologyHint + // AllocatePod is called to trigger the allocation of resources to a pod. + AllocatePod(pod *v1.Pod) error // Allocate triggers resource allocation to occur on the HintProvider after // all hints have been gathered and the aggregated Hint is available via a // call to Store.GetAffinity(). @@ -99,6 +127,7 @@ type HintProvider interface { type Store interface { GetAffinity(podUID string, containerName string) TopologyHint GetPolicy() Policy + Name() string } // TopologyHint is a struct containing the NUMANodeAffinity for a Container @@ -179,10 +208,10 @@ func NewManager(topology []cadvisorapi.Node, topologyPolicyName string, topology var scope Scope switch topologyScopeName { - case containerTopologyScope: + case ContainerTopologyScope: scope = NewContainerScope(policy) - case podTopologyScope: + case PodTopologyScope: scope = NewPodScope(policy) default: @@ -214,6 +243,10 @@ func (m *manager) GetPolicy() Policy { return m.scope.GetPolicy() } +func (m *manager) Name() string { + return m.scope.Name() +} + func (m *manager) AddHintProvider(_ klog.Logger, h HintProvider) { m.scope.AddHintProvider(h) } diff --git a/pkg/kubelet/cm/topologymanager/topology_manager_test.go b/pkg/kubelet/cm/topologymanager/topology_manager_test.go index 64cea9f7d3fc7..e1af5f4019241 100644 --- a/pkg/kubelet/cm/topologymanager/topology_manager_test.go +++ b/pkg/kubelet/cm/topologymanager/topology_manager_test.go @@ -218,6 +218,10 @@ func (m *mockHintProvider) GetPodTopologyHints(pod *v1.Pod) map[string][]Topolog return m.th } +func (m *mockHintProvider) AllocatePod(pod *v1.Pod) error { + return nil +} + func (m *mockHintProvider) Allocate(pod *v1.Pod, container *v1.Container) error { //return allocateError return nil diff --git a/pkg/kubelet/cm/util/cgroups_unsupported.go b/pkg/kubelet/cm/util/cgroups_unsupported.go index b655f41388c0e..ccf20092809a3 100644 --- a/pkg/kubelet/cm/util/cgroups_unsupported.go +++ b/pkg/kubelet/cm/util/cgroups_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/config/apiserver.go b/pkg/kubelet/config/apiserver.go index 60c040299c481..c02cc9c8ff485 100644 --- a/pkg/kubelet/config/apiserver.go +++ b/pkg/kubelet/config/apiserver.go @@ -27,14 +27,13 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) // WaitForAPIServerSyncPeriod is the period between checks for the node list/watch initial sync const WaitForAPIServerSyncPeriod = 1 * time.Second // NewSourceApiserver creates a config source that watches and pulls from the apiserver. -func NewSourceApiserver(logger klog.Logger, c clientset.Interface, nodeName types.NodeName, nodeHasSynced func() bool, updates chan<- interface{}) { +func NewSourceApiserver(logger klog.Logger, c clientset.Interface, nodeName types.NodeName, nodeHasSynced func() bool, updates chan<- sourceUpdate) { lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector("spec.nodeName", string(nodeName))) // The Reflector responsible for watching pods at the apiserver should be run only after @@ -55,13 +54,13 @@ func NewSourceApiserver(logger klog.Logger, c clientset.Interface, nodeName type } // newSourceApiserverFromLW holds creates a config source that watches and pulls from the apiserver. -func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) { +func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- sourceUpdate) { send := func(objs []interface{}) { var pods []*v1.Pod for _, o := range objs { pods = append(pods, o.(*v1.Pod)) } - updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource} + updates <- sourceUpdate{Pods: pods} } r := cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0) go r.Run(wait.NeverStop) diff --git a/pkg/kubelet/config/apiserver_test.go b/pkg/kubelet/config/apiserver_test.go index 9bbafecc6eccb..250be91921e16 100644 --- a/pkg/kubelet/config/apiserver_test.go +++ b/pkg/kubelet/config/apiserver_test.go @@ -19,13 +19,12 @@ package config import ( "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/cache" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) type fakePodLW struct { @@ -41,7 +40,7 @@ func (lw fakePodLW) Watch(options metav1.ListOptions) (watch.Interface, error) { return lw.watchResp, nil } -// IsWatchListSemanticsSupported informs the reflector that this client +// IsWatchListSemanticsUnSupported informs the reflector that this client // doesn't support WatchList semantics. // // This is a synthetic method whose sole purpose is to satisfy the optional @@ -70,71 +69,66 @@ func TestNewSourceApiserver_UpdatesAndMultiplePods(t *testing.T) { watchResp: fakeWatch, } - ch := make(chan interface{}) + ch := make(chan sourceUpdate) newSourceApiserverFromLW(lw, ch) - got, ok := <-ch + update, ok := <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update := got.(kubetypes.PodUpdate) - expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1) + expected := createSourceUpdate(pod1v1) if !apiequality.Semantic.DeepEqual(expected, update) { - t.Errorf("Expected %#v; Got %#v", expected, update) + t.Errorf("Expected %#v; update %#v", expected, update) } // Add another pod fakeWatch.Add(pod2) - got, ok = <-ch + update, ok = <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update = got.(kubetypes.PodUpdate) // Could be sorted either of these two ways: - expectedA := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1, pod2) - expectedB := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v1) + expectedA := createSourceUpdate(pod1v1, pod2) + expectedB := createSourceUpdate(pod2, pod1v1) if !apiequality.Semantic.DeepEqual(expectedA, update) && !apiequality.Semantic.DeepEqual(expectedB, update) { - t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update) + t.Errorf("Expected %#v or %#v, update %#v", expectedA, expectedB, update) } // Modify pod1 fakeWatch.Modify(pod1v2) - got, ok = <-ch + update, ok = <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update = got.(kubetypes.PodUpdate) - expectedA = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v2, pod2) - expectedB = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v2) + expectedA = createSourceUpdate(pod1v2, pod2) + expectedB = createSourceUpdate(pod2, pod1v2) if !apiequality.Semantic.DeepEqual(expectedA, update) && !apiequality.Semantic.DeepEqual(expectedB, update) { - t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update) + t.Errorf("Expected %#v or %#v, update %#v", expectedA, expectedB, update) } // Delete pod1 fakeWatch.Delete(pod1v2) - got, ok = <-ch + update, ok = <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update = got.(kubetypes.PodUpdate) - expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2) + expected = createSourceUpdate(pod2) if !apiequality.Semantic.DeepEqual(expected, update) { - t.Errorf("Expected %#v, Got %#v", expected, update) + t.Errorf("Expected %#v, update %#v", expected, update) } // Delete pod2 fakeWatch.Delete(pod2) - got, ok = <-ch + update, ok = <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update = got.(kubetypes.PodUpdate) - expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource) + expected = createSourceUpdate() // Empty update. if !apiequality.Semantic.DeepEqual(expected, update) { - t.Errorf("Expected %#v, Got %#v", expected, update) + t.Errorf("Expected %#v, update %#v", expected, update) } } @@ -153,29 +147,27 @@ func TestNewSourceApiserver_TwoNamespacesSameName(t *testing.T) { watchResp: fakeWatch, } - ch := make(chan interface{}) + ch := make(chan sourceUpdate) newSourceApiserverFromLW(lw, ch) - got, ok := <-ch + update, ok := <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update := got.(kubetypes.PodUpdate) // Make sure that we get both pods. Catches bug #2294. if !(len(update.Pods) == 2) { - t.Errorf("Expected %d, Got %d", 2, len(update.Pods)) + t.Errorf("Expected %d, update %d", 2, len(update.Pods)) } // Delete pod1 fakeWatch.Delete(&pod1) - got, ok = <-ch + update, ok = <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update = got.(kubetypes.PodUpdate) if !(len(update.Pods) == 1) { - t.Errorf("Expected %d, Got %d", 1, len(update.Pods)) + t.Errorf("Expected %d, update %d", 1, len(update.Pods)) } } @@ -187,17 +179,16 @@ func TestNewSourceApiserverInitialEmptySendsEmptyPodUpdate(t *testing.T) { watchResp: fakeWatch, } - ch := make(chan interface{}) + ch := make(chan sourceUpdate) newSourceApiserverFromLW(lw, ch) - got, ok := <-ch + update, ok := <-ch if !ok { t.Errorf("Unable to read from channel when expected") } - update := got.(kubetypes.PodUpdate) - expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource) + expected := createSourceUpdate() // Expect empty update. if !apiequality.Semantic.DeepEqual(expected, update) { - t.Errorf("Expected %#v; Got %#v", expected, update) + t.Errorf("Expected %#v; update %#v", expected, update) } } diff --git a/pkg/kubelet/config/config.go b/pkg/kubelet/config/config.go index dc0728c8f3657..299fdd5497673 100644 --- a/pkg/kubelet/config/config.go +++ b/pkg/kubelet/config/config.go @@ -18,7 +18,6 @@ package config import ( "context" - "fmt" "reflect" "sync" "time" @@ -34,23 +33,6 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/format" ) -// PodConfigNotificationMode describes how changes are sent to the update channel. -type PodConfigNotificationMode int - -const ( - // PodConfigNotificationUnknown is the default value for - // PodConfigNotificationMode when uninitialized. - PodConfigNotificationUnknown PodConfigNotificationMode = iota - // PodConfigNotificationSnapshot delivers the full configuration as a SET whenever - // any change occurs. - PodConfigNotificationSnapshot - // PodConfigNotificationSnapshotAndUpdates delivers an UPDATE and DELETE message whenever pods are - // changed, and a SET message if there are any additions or removals. - PodConfigNotificationSnapshotAndUpdates - // PodConfigNotificationIncremental delivers ADD, UPDATE, DELETE, REMOVE, RECONCILE to the update channel. - PodConfigNotificationIncremental -) - type podStartupSLIObserver interface { ObservedPodOnWatch(pod *v1.Pod, when time.Time) } @@ -70,11 +52,15 @@ type PodConfig struct { sources sets.Set[string] } +type sourceUpdate struct { + Pods []*v1.Pod +} + // NewPodConfig creates an object that can merge many configuration sources into a stream // of normalized updates to a pod configuration. -func NewPodConfig(mode PodConfigNotificationMode, recorder record.EventRecorder, startupSLIObserver podStartupSLIObserver) *PodConfig { +func NewPodConfig(recorder record.EventRecorderLogger, startupSLIObserver podStartupSLIObserver) *PodConfig { updates := make(chan kubetypes.PodUpdate, 50) - storage := newPodStorage(updates, mode, recorder, startupSLIObserver) + storage := newPodStorage(updates, recorder, startupSLIObserver) podConfig := &PodConfig{ pods: storage, mux: newMux(storage), @@ -86,7 +72,7 @@ func NewPodConfig(mode PodConfigNotificationMode, recorder record.EventRecorder, // Channel creates or returns a config source channel. The channel // only accepts PodUpdates -func (c *PodConfig) Channel(ctx context.Context, source string) chan<- interface{} { +func (c *PodConfig) Channel(ctx context.Context, source string) chan<- sourceUpdate { c.sourcesLock.Lock() defer c.sourcesLock.Unlock() c.sources.Insert(source) @@ -113,11 +99,6 @@ func (c *PodConfig) Updates() <-chan kubetypes.PodUpdate { return c.updates } -// Sync requests the full configuration be delivered to the update channel. -func (c *PodConfig) Sync() { - c.pods.sync() -} - // podStorage manages the current pod state at any point in time and ensures updates // to the channel are delivered in order. Note that this object is an in-memory source of // "truth" and on creation contains zero entries. Once all previously read sources are @@ -126,7 +107,6 @@ type podStorage struct { podLock sync.RWMutex // map of source name to pod uid to pod reference pods map[string]map[types.UID]*v1.Pod - mode PodConfigNotificationMode // ensures that updates are delivered in strict order // on the updates channel @@ -138,7 +118,7 @@ type podStorage struct { sourcesSeen sets.Set[string] // the EventRecorder to use - recorder record.EventRecorder + recorder record.EventRecorderLogger startupSLIObserver podStartupSLIObserver } @@ -146,10 +126,9 @@ type podStorage struct { // TODO: PodConfigNotificationMode could be handled by a listener to the updates channel // in the future, especially with multiple listeners. // TODO: allow initialization of the current state of the store with snapshotted version. -func newPodStorage(updates chan<- kubetypes.PodUpdate, mode PodConfigNotificationMode, recorder record.EventRecorder, startupSLIObserver podStartupSLIObserver) *podStorage { +func newPodStorage(updates chan<- kubetypes.PodUpdate, recorder record.EventRecorderLogger, startupSLIObserver podStartupSLIObserver) *podStorage { return &podStorage{ pods: make(map[string]map[types.UID]*v1.Pod), - mode: mode, updates: updates, sourcesSeen: sets.Set[string]{}, recorder: recorder, @@ -157,69 +136,45 @@ func newPodStorage(updates chan<- kubetypes.PodUpdate, mode PodConfigNotificatio } } -// Merge normalizes a set of incoming changes from different sources into a map of all Pods -// and ensures that redundant changes are filtered out, and then pushes zero or more minimal +// Merge normalizes a set of incoming updates from different sources into a map of all Pods +// and ensures that redundant updates are filtered out, and then pushes zero or more minimal // updates onto the update channel. Ensures that updates are delivered in order. -func (s *podStorage) Merge(ctx context.Context, source string, change interface{}) error { +func (s *podStorage) Merge(ctx context.Context, source string, update sourceUpdate) error { s.updateLock.Lock() defer s.updateLock.Unlock() seenBefore := s.sourcesSeen.Has(source) - adds, updates, deletes, removes, reconciles := s.merge(ctx, source, change) + adds, updates, deletes, removes, reconciles := s.merge(ctx, source, update) firstSet := !seenBefore && s.sourcesSeen.Has(source) // deliver update notifications - switch s.mode { - case PodConfigNotificationIncremental: - if len(removes.Pods) > 0 { - s.updates <- *removes - } - if len(adds.Pods) > 0 { - s.updates <- *adds - } - if len(updates.Pods) > 0 { - s.updates <- *updates - } - if len(deletes.Pods) > 0 { - s.updates <- *deletes - } - if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 { - // Send an empty update when first seeing the source and there are - // no ADD or UPDATE or DELETE pods from the source. This signals kubelet that - // the source is ready. - s.updates <- *adds - } - // Only add reconcile support here, because kubelet doesn't support Snapshot update now. - if len(reconciles.Pods) > 0 { - s.updates <- *reconciles - } - - case PodConfigNotificationSnapshotAndUpdates: - if len(removes.Pods) > 0 || len(adds.Pods) > 0 || firstSet { - s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source} - } - if len(updates.Pods) > 0 { - s.updates <- *updates - } - if len(deletes.Pods) > 0 { - s.updates <- *deletes - } - - case PodConfigNotificationSnapshot: - if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || len(removes.Pods) > 0 || firstSet { - s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source} - } - - case PodConfigNotificationUnknown: - fallthrough - default: - panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode)) + if len(removes.Pods) > 0 { + s.updates <- *removes + } + if len(adds.Pods) > 0 { + s.updates <- *adds + } + if len(updates.Pods) > 0 { + s.updates <- *updates + } + if len(deletes.Pods) > 0 { + s.updates <- *deletes + } + if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 { + // Send an empty update when first seeing the source and there are + // no ADD or UPDATE or DELETE pods from the source. This signals kubelet that + // the source is ready. + s.updates <- *adds + } + // Only add reconcile support here, because kubelet doesn't support Snapshot update now. + if len(reconciles.Pods) > 0 { + s.updates <- *reconciles } return nil } -func (s *podStorage) merge(ctx context.Context, source string, change interface{}) (adds, updates, deletes, removes, reconciles *kubetypes.PodUpdate) { +func (s *podStorage) merge(ctx context.Context, source string, update sourceUpdate) (adds, updates, deletes, removes, reconciles *kubetypes.PodUpdate) { s.podLock.Lock() defer s.podLock.Unlock() logger := klog.FromContext(ctx) @@ -268,47 +223,17 @@ func (s *podStorage) merge(ctx context.Context, source string, change interface{ } } - update := change.(kubetypes.PodUpdate) - switch update.Op { - case kubetypes.ADD, kubetypes.UPDATE, kubetypes.DELETE: - if update.Op == kubetypes.ADD { - logger.V(4).Info("Adding new pods from source", "source", source, "pods", klog.KObjSlice(update.Pods)) - } else if update.Op == kubetypes.DELETE { - logger.V(4).Info("Gracefully deleting pods from source", "source", source, "pods", klog.KObjSlice(update.Pods)) - } else { - logger.V(4).Info("Updating pods from source", "source", source, "pods", klog.KObjSlice(update.Pods)) - } - updatePodsFunc(update.Pods, pods, pods) - - case kubetypes.REMOVE: - logger.V(4).Info("Removing pods from source", "source", source, "pods", klog.KObjSlice(update.Pods)) - for _, value := range update.Pods { - if existing, found := pods[value.UID]; found { - // this is a delete - delete(pods, value.UID) - removePods = append(removePods, existing) - continue - } - // this is a no-op + logger.V(4).Info("Setting pods for source", "source", source) + s.markSourceSet(source) + // Clear the old map entries by just creating a new map + oldPods := pods + pods = make(map[types.UID]*v1.Pod) + updatePodsFunc(update.Pods, oldPods, pods) + for uid, existing := range oldPods { + if _, found := pods[uid]; !found { + // this is a delete + removePods = append(removePods, existing) } - - case kubetypes.SET: - logger.V(4).Info("Setting pods for source", "source", source) - s.markSourceSet(source) - // Clear the old map entries by just creating a new map - oldPods := pods - pods = make(map[types.UID]*v1.Pod) - updatePodsFunc(update.Pods, oldPods, pods) - for uid, existing := range oldPods { - if _, found := pods[uid]; !found { - // this is a delete - removePods = append(removePods, existing) - } - } - - default: - logger.Info("Received invalid update type", "type", update) - } s.pods[source] = pods @@ -334,7 +259,7 @@ func (s *podStorage) seenSources(sources ...string) bool { return s.sourcesSeen.HasAll(sources...) } -func filterInvalidPods(logger klog.Logger, pods []*v1.Pod, source string, recorder record.EventRecorder) (filtered []*v1.Pod) { +func filterInvalidPods(logger klog.Logger, pods []*v1.Pod, source string, recorder record.EventRecorderLogger) (filtered []*v1.Pod) { names := sets.Set[string]{} for i, pod := range pods { // Pods from each source are assumed to have passed validation individually. @@ -342,7 +267,7 @@ func filterInvalidPods(logger klog.Logger, pods []*v1.Pod, source string, record name := kubecontainer.GetPodFullName(pod) if names.Has(name) { logger.Info("Pod failed validation due to duplicate pod name, ignoring", "index", i, "pod", klog.KObj(pod), "source", source) - recorder.Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s due to duplicate pod name %q, ignoring", format.Pod(pod), source, pod.Name) + recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s due to duplicate pod name %q, ignoring", format.Pod(pod), source, pod.Name) continue } else { names.Insert(name) @@ -475,25 +400,6 @@ func checkAndUpdatePod(existing, ref *v1.Pod) (needUpdate, needReconcile, needGr return } -// sync sends a copy of the current state through the update channel. -func (s *podStorage) sync() { - s.updateLock.Lock() - defer s.updateLock.Unlock() - s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: kubetypes.AllSource} -} - -func (s *podStorage) mergedState() interface{} { - s.podLock.RLock() - defer s.podLock.RUnlock() - pods := make([]*v1.Pod, 0) - for _, sourcePods := range s.pods { - for _, podRef := range sourcePods { - pods = append(pods, podRef.DeepCopy()) - } - } - return pods -} - func copyPods(sourcePods []*v1.Pod) []*v1.Pod { pods := []*v1.Pod{} for _, source := range sourcePods { diff --git a/pkg/kubelet/config/config_test.go b/pkg/kubelet/config/config_test.go index b2e652c6c2e26..24692fb6eb1d9 100644 --- a/pkg/kubelet/config/config_test.go +++ b/pkg/kubelet/config/config_test.go @@ -42,7 +42,7 @@ const ( TestSource = "test" ) -func expectEmptyChannel(t *testing.T, ch <-chan interface{}) { +func expectEmptyChannel(t *testing.T, ch <-chan sourceUpdate) { select { case update := <-ch: t.Errorf("Expected no update in channel, Got %v", update) @@ -93,15 +93,20 @@ func CreatePodUpdate(op kubetypes.PodOperation, source string, pods ...*v1.Pod) return kubetypes.PodUpdate{Pods: pods, Op: op, Source: source} } -func createPodConfigTester(ctx context.Context, mode PodConfigNotificationMode) (chan<- interface{}, <-chan kubetypes.PodUpdate, *PodConfig) { +func createSourceUpdate(pods ...*v1.Pod) sourceUpdate { + return sourceUpdate{pods} +} + +func createPodConfigTester(ctx context.Context) (chan<- sourceUpdate, <-chan kubetypes.PodUpdate, *PodConfig) { eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) - config := NewPodConfig(mode, eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kubelet"}), &mockPodStartupSLIObserver{}) + config := NewPodConfig(eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kubelet"}), &mockPodStartupSLIObserver{}) channel := config.Channel(ctx, TestSource) ch := config.Updates() return channel, ch, config } func expectPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate, expected ...kubetypes.PodUpdate) { + t.Helper() for i := range expected { update := <-ch sort.Sort(sortedPods(update.Pods)) @@ -139,127 +144,77 @@ func expectNoPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate) { func TestNewPodAdded(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, config := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // see an update - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + podUpdate := createSourceUpdate(CreateValidPod("foo", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) - - config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new"))) } func TestNewPodAddedInvalidNamespace(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, config := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // see an update - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "")) + podUpdate := createSourceUpdate(CreateValidPod("foo", "")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", ""))) - - config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", ""))) } func TestNewPodAddedDefaultNamespace(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, config := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // see an update - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")) + podUpdate := createSourceUpdate(CreateValidPod("foo", "default")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))) - - config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"))) } func TestNewPodAddedDifferentNamespaces(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, config := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // see an update - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")) + pod1 := CreateValidPod("foo", "default") + podUpdate := createSourceUpdate(pod1) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))) // see an update in another namespace - podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + pod2 := CreateValidPod("foo", "new") + podUpdate = createSourceUpdate(pod1, pod2) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) - - config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"), CreateValidPod("foo", "new"))) } func TestInvalidPodFiltered(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // see an update - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + podUpdate := createSourceUpdate(CreateValidPod("foo", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) // add an invalid update, pod with the same name - podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + podUpdate = createSourceUpdate(CreateValidPod("foo", "new")) channel <- podUpdate expectNoPodUpdate(t, ch) } -func TestNewPodAddedSnapshotAndUpdates(t *testing.T) { - tCtx := ktesting.Init(t) - - channel, ch, config := createPodConfigTester(tCtx, PodConfigNotificationSnapshotAndUpdates) - - // see an set - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) - channel <- podUpdate - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new"))) - - config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new"))) - - // container updates are separated as UPDATE - pod := *podUpdate.Pods[0] - pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}} - channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod) - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, &pod)) -} - -func TestNewPodAddedSnapshot(t *testing.T) { - tCtx := ktesting.Init(t) - - channel, ch, config := createPodConfigTester(tCtx, PodConfigNotificationSnapshot) - - // see an set - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) - channel <- podUpdate - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new"))) - - config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new"))) - - // container updates are separated as UPDATE - pod := *podUpdate.Pods[0] - pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}} - channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod) - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, &pod)) -} - func TestNewPodAddedUpdatedRemoved(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // should register an add - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + podUpdate := createSourceUpdate(CreateValidPod("foo", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) @@ -269,11 +224,11 @@ func TestNewPodAddedUpdatedRemoved(t *testing.T) { // an kubetypes.ADD should be converted to kubetypes.UPDATE pod := CreateValidPod("foo", "new") pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}} - podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod) + podUpdate = createSourceUpdate(pod) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) - podUpdate = CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new")) + podUpdate = createSourceUpdate() channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.REMOVE, TestSource, pod)) } @@ -281,11 +236,11 @@ func TestNewPodAddedUpdatedRemoved(t *testing.T) { func TestNewPodAddedDelete(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // should register an add addedPod := CreateValidPod("foo", "new") - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, addedPod) + podUpdate := createSourceUpdate(addedPod) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, addedPod)) @@ -293,7 +248,7 @@ func TestNewPodAddedDelete(t *testing.T) { timestamp := metav1.NewTime(time.Now()) deletedPod := CreateValidPod("foo", "new") deletedPod.ObjectMeta.DeletionTimestamp = ×tamp - podUpdate = CreatePodUpdate(kubetypes.DELETE, TestSource, deletedPod) + podUpdate = createSourceUpdate(deletedPod) channel <- podUpdate // the existing pod should be gracefully deleted expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.DELETE, TestSource, addedPod)) @@ -302,20 +257,21 @@ func TestNewPodAddedDelete(t *testing.T) { func TestNewPodAddedUpdatedSet(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) // should register an add - podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new")) + podUpdate := createSourceUpdate(CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new"))) // should ignore ADDs that are identical + channel <- podUpdate expectNoPodUpdate(t, ch) // should be converted to an kubetypes.ADD, kubetypes.REMOVE, and kubetypes.UPDATE pod := CreateValidPod("foo2", "new") pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}} - podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new")) + podUpdate = createSourceUpdate(pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new")), @@ -342,65 +298,51 @@ func TestNewPodAddedSetReconciled(t *testing.T) { } return pods, pods[0] } - for _, op := range []kubetypes.PodOperation{ - kubetypes.ADD, - kubetypes.SET, - } { - var podWithStatusChange *v1.Pod - pods, _ := newTestPods(false, false) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) - - // Use SET to initialize the config, especially initialize the source set - channel <- CreatePodUpdate(kubetypes.SET, TestSource, pods...) - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pods...)) - - // If status is not changed, no reconcile should be triggered - channel <- CreatePodUpdate(op, TestSource, pods...) - expectNoPodUpdate(t, ch) - - // If the pod status is changed and not updated, a reconcile should be triggered - pods, podWithStatusChange = newTestPods(true, false) - channel <- CreatePodUpdate(op, TestSource, pods...) - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.RECONCILE, TestSource, podWithStatusChange)) - - // If the pod status is changed, but the pod is also updated, no reconcile should be triggered - pods, podWithStatusChange = newTestPods(true, true) - channel <- CreatePodUpdate(op, TestSource, pods...) - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, podWithStatusChange)) - } + var podWithStatusChange *v1.Pod + pods, _ := newTestPods(false, false) + channel, ch, _ := createPodConfigTester(tCtx) + + // Use SET to initialize the config, especially initialize the source set + channel <- createSourceUpdate(pods...) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pods...)) + + // If status is not changed, no reconcile should be triggered + channel <- createSourceUpdate(pods...) + expectNoPodUpdate(t, ch) + + // If the pod status is changed and not updated, a reconcile should be triggered + pods, podWithStatusChange = newTestPods(true, false) + channel <- createSourceUpdate(pods...) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.RECONCILE, TestSource, podWithStatusChange)) + + // If the pod status is changed, but the pod is also updated, no reconcile should be triggered + pods, podWithStatusChange = newTestPods(true, true) + channel <- createSourceUpdate(pods...) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, podWithStatusChange)) } func TestInitialEmptySet(t *testing.T) { tCtx := ktesting.Init(t) - for _, test := range []struct { - mode PodConfigNotificationMode - op kubetypes.PodOperation - }{ - {PodConfigNotificationIncremental, kubetypes.ADD}, - {PodConfigNotificationSnapshot, kubetypes.SET}, - {PodConfigNotificationSnapshotAndUpdates, kubetypes.SET}, - } { - channel, ch, _ := createPodConfigTester(tCtx, test.mode) - - // should register an empty PodUpdate operation - podUpdate := CreatePodUpdate(kubetypes.SET, TestSource) - channel <- podUpdate - expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource)) - - // should ignore following empty sets - podUpdate = CreatePodUpdate(kubetypes.SET, TestSource) - channel <- podUpdate - podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) - channel <- podUpdate - expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource, CreateValidPod("foo", "new"))) - } + channel, ch, _ := createPodConfigTester(tCtx) + + // should register an empty PodUpdate operation + podUpdate := createSourceUpdate() + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource)) + + // should ignore following empty sets + podUpdate = createSourceUpdate() + channel <- podUpdate + podUpdate = createSourceUpdate(CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) } func TestPodUpdateAnnotations(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) pod := CreateValidPod("foo2", "new") pod.Annotations = make(map[string]string) @@ -408,22 +350,22 @@ func TestPodUpdateAnnotations(t *testing.T) { clone := pod.DeepCopy() - podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), clone, CreateValidPod("foo3", "new")) + podUpdate := createSourceUpdate(CreateValidPod("foo1", "new"), clone, CreateValidPod("foo3", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))) pod.Annotations["kubernetes.io/blah"] = "superblah" - podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) + podUpdate = createSourceUpdate(CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) pod.Annotations["kubernetes.io/otherblah"] = "doh" - podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) + podUpdate = createSourceUpdate(CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) delete(pod.Annotations, "kubernetes.io/blah") - podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) + podUpdate = createSourceUpdate(CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) } @@ -431,7 +373,7 @@ func TestPodUpdateAnnotations(t *testing.T) { func TestPodUpdateLabels(t *testing.T) { tCtx := ktesting.Init(t) - channel, ch, _ := createPodConfigTester(tCtx, PodConfigNotificationIncremental) + channel, ch, _ := createPodConfigTester(tCtx) pod := CreateValidPod("foo2", "new") pod.Labels = make(map[string]string) @@ -439,12 +381,12 @@ func TestPodUpdateLabels(t *testing.T) { clone := pod.DeepCopy() - podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, clone) + podUpdate := createSourceUpdate(clone) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pod)) pod.Labels["key"] = "newValue" - podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod) + podUpdate = createSourceUpdate(pod) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) @@ -454,7 +396,7 @@ func TestPodConfigRace(t *testing.T) { tCtx := ktesting.Init(t) eventBroadcaster := record.NewBroadcaster(record.WithContext(tCtx)) - config := NewPodConfig(PodConfigNotificationIncremental, eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kubelet"}), &mockPodStartupSLIObserver{}) + config := NewPodConfig(eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kubelet"}), &mockPodStartupSLIObserver{}) seenSources := sets.New[string](TestSource) var wg sync.WaitGroup const iterations = 100 diff --git a/pkg/kubelet/config/file.go b/pkg/kubelet/config/file.go index 541f41318b20f..dc938b6ec924d 100644 --- a/pkg/kubelet/config/file.go +++ b/pkg/kubelet/config/file.go @@ -31,7 +31,6 @@ import ( "k8s.io/client-go/tools/cache" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubelet/managed" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" utilio "k8s.io/utils/io" ) @@ -56,12 +55,12 @@ type sourceFile struct { period time.Duration store cache.Store fileKeyMapping map[string]string - updates chan<- interface{} + updates chan<- sourceUpdate watchEvents chan *watchEvent } // NewSourceFile watches a config file for changes. -func NewSourceFile(logger klog.Logger, path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) { +func NewSourceFile(logger klog.Logger, path string, nodeName types.NodeName, period time.Duration, updates chan<- sourceUpdate) { // "github.com/sigma/go-inotify" requires a path without trailing "/" path = strings.TrimRight(path, string(os.PathSeparator)) @@ -70,13 +69,13 @@ func NewSourceFile(logger klog.Logger, path string, nodeName types.NodeName, per config.run(logger) } -func newSourceFile(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) *sourceFile { +func newSourceFile(path string, nodeName types.NodeName, period time.Duration, updates chan<- sourceUpdate) *sourceFile { send := func(objs []interface{}) { var pods []*v1.Pod for _, o := range objs { pods = append(pods, o.(*v1.Pod)) } - updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource} + updates <- sourceUpdate{Pods: pods} } store := cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc) return &sourceFile{ @@ -127,7 +126,7 @@ func (s *sourceFile) listConfig(logger klog.Logger) error { return err } // Emit an update with an empty PodList to allow FileSource to be marked as seen - s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource} + s.updates <- sourceUpdate{Pods: []*v1.Pod{}} return fmt.Errorf("path does not exist, ignoring") } @@ -139,7 +138,7 @@ func (s *sourceFile) listConfig(logger klog.Logger) error { } if len(pods) == 0 { // Emit an update with an empty PodList to allow FileSource to be marked as seen - s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource} + s.updates <- sourceUpdate{Pods: pods} return nil } return s.replaceStore(pods...) diff --git a/pkg/kubelet/config/file_linux.go b/pkg/kubelet/config/file_linux.go index f05010f8ec0aa..84ff81c9cd488 100644 --- a/pkg/kubelet/config/file_linux.go +++ b/pkg/kubelet/config/file_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. @@ -27,12 +26,11 @@ import ( "time" "github.com/fsnotify/fsnotify" + v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" - "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/flowcontrol" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) const ( @@ -73,7 +71,7 @@ func (s *sourceFile) doWatch(logger klog.Logger) error { return err } // Emit an update with an empty PodList to allow FileSource to be marked as seen - s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource} + s.updates <- sourceUpdate{Pods: []*v1.Pod{}} return &retryableError{"path does not exist, ignoring"} } diff --git a/pkg/kubelet/config/file_linux_test.go b/pkg/kubelet/config/file_linux_test.go index b7e17eb655058..2da167f8d17e0 100644 --- a/pkg/kubelet/config/file_linux_test.go +++ b/pkg/kubelet/config/file_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. @@ -45,7 +44,7 @@ import ( func TestExtractFromNonExistentFile(t *testing.T) { logger, _ := ktesting.NewTestContext(t) - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) lw := newSourceFile("/some/fake/file", "localhost", time.Millisecond, ch) err := lw.doWatch(logger) if err == nil { @@ -55,12 +54,11 @@ func TestExtractFromNonExistentFile(t *testing.T) { func TestUpdateOnNonExistentFile(t *testing.T) { logger, _ := ktesting.NewTestContext(t) - ch := make(chan interface{}) + ch := make(chan sourceUpdate) NewSourceFile(logger, "random_non_existent_path", "localhost", time.Millisecond, ch) select { - case got := <-ch: - update := got.(kubetypes.PodUpdate) - expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource) + case update := <-ch: + expected := createSourceUpdate() // Expect empty update. if !apiequality.Semantic.DeepDerivative(expected, update) { t.Fatalf("expected %#v, Got %#v", expected, update) } @@ -84,11 +82,10 @@ func TestReadPodsFromFileExistAlready(t *testing.T) { defer os.RemoveAll(dirName) file := testCase.writeToFile(dirName, "test_pod_manifest", t) - ch := make(chan interface{}) + ch := make(chan sourceUpdate) NewSourceFile(logger, file, hostname, time.Millisecond, ch) select { - case got := <-ch: - update := got.(kubetypes.PodUpdate) + case update := <-ch: for _, pod := range update.Pods { // TODO: remove the conversion when validation is performed on versioned objects. internalPod := &api.Pod{} @@ -142,7 +139,7 @@ type testCase struct { lock *sync.Mutex desc string pod runtime.Object - expected kubetypes.PodUpdate + expected sourceUpdate } func getTestCases(hostname types.NodeName) []*testCase { @@ -171,7 +168,7 @@ func getTestCases(hostname types.NodeName) []*testCase { Phase: v1.PodPending, }, }, - expected: CreatePodUpdate(kubetypes.SET, kubetypes.FileSource, &v1.Pod{ + expected: createSourceUpdate(&v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "test-" + string(hostname), UID: "12345", @@ -256,7 +253,7 @@ func watchFileAdded(watchDir bool, symlink bool, t *testing.T) { createSymbolicLink(dirName, linkedDirName, fileName, t) } - ch := make(chan interface{}) + ch := make(chan sourceUpdate) if watchDir { NewSourceFile(logger, dirName, hostname, 100*time.Millisecond, ch) } else { @@ -311,7 +308,7 @@ func watchFileChanged(watchDir bool, symlink bool, period time.Duration, t *test } var file string - ch := make(chan interface{}) + ch := make(chan sourceUpdate) func() { testCase.lock.Lock() defer testCase.lock.Unlock() @@ -365,12 +362,11 @@ func watchFileChanged(watchDir bool, symlink bool, period time.Duration, t *test } } -func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) { +func expectUpdate(t *testing.T, ch chan sourceUpdate, testCase *testCase) { timer := time.After(5 * time.Second) for { select { - case got := <-ch: - update := got.(kubetypes.PodUpdate) + case update := <-ch: if len(update.Pods) == 0 { // filter out the empty updates from reading a non-existing path continue @@ -396,12 +392,11 @@ func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) { } } -func expectEmptyUpdate(t *testing.T, ch chan interface{}) { +func expectEmptyUpdate(t *testing.T, ch chan sourceUpdate) { timer := time.After(5 * time.Second) for { select { - case got := <-ch: - update := got.(kubetypes.PodUpdate) + case update := <-ch: if len(update.Pods) != 0 { t.Fatalf("expected empty update, got %#v", update) } diff --git a/pkg/kubelet/config/file_test.go b/pkg/kubelet/config/file_test.go index 8aecb35af7849..2cad9e15ea0b9 100644 --- a/pkg/kubelet/config/file_test.go +++ b/pkg/kubelet/config/file_test.go @@ -23,7 +23,6 @@ import ( "time" apiequality "k8s.io/apimachinery/pkg/api/equality" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/test/utils/ktesting" ) @@ -41,7 +40,7 @@ func TestExtractFromBadDataFile(t *testing.T) { t.Fatalf("unable to write test file %#v", err) } - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) lw := newSourceFile(fileName, "localhost", time.Millisecond, ch) err = lw.listConfig(logger) if err == nil { @@ -58,18 +57,18 @@ func TestExtractFromEmptyDir(t *testing.T) { defer removeAll(dirName, t) logger, _ := ktesting.NewTestContext(t) - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) lw := newSourceFile(dirName, "localhost", time.Millisecond, ch) err = lw.listConfig(logger) if err != nil { t.Fatalf("unexpected error: %v", err) } - update, ok := (<-ch).(kubetypes.PodUpdate) + update, ok := <-ch if !ok { t.Fatalf("unexpected type: %#v", update) } - expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource) + expected := createSourceUpdate() // Expect empty update. if !apiequality.Semantic.DeepEqual(expected, update) { t.Fatalf("expected %#v, got %#v", expected, update) } diff --git a/pkg/kubelet/config/file_unsupported.go b/pkg/kubelet/config/file_unsupported.go index 1b2c56a6660a6..3ea4bccd74b34 100644 --- a/pkg/kubelet/config/file_unsupported.go +++ b/pkg/kubelet/config/file_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/config/http.go b/pkg/kubelet/config/http.go index 123f5edf01e07..13ed0d6b82684 100644 --- a/pkg/kubelet/config/http.go +++ b/pkg/kubelet/config/http.go @@ -25,7 +25,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" api "k8s.io/kubernetes/pkg/apis/core" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" @@ -36,14 +35,14 @@ type sourceURL struct { url string header http.Header nodeName types.NodeName - updates chan<- interface{} + updates chan<- sourceUpdate data []byte failureLogs int client *http.Client } // NewSourceURL specifies the URL where to read the Pod configuration from, then watches it for changes. -func NewSourceURL(logger klog.Logger, url string, header http.Header, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) { +func NewSourceURL(logger klog.Logger, url string, header http.Header, nodeName types.NodeName, period time.Duration, updates chan<- sourceUpdate) { config := &sourceURL{ url: url, header: header, @@ -102,7 +101,7 @@ func (s *sourceURL) extractFromURL(logger klog.Logger) error { } if len(data) == 0 { // Emit an update with an empty PodList to allow HTTPSource to be marked as seen - s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.HTTPSource} + s.updates <- sourceUpdate{Pods: []*v1.Pod{}} return fmt.Errorf("zero-length data received from %v", s.url) } // Short circuit if the data has not changed since the last time it was read. @@ -118,7 +117,7 @@ func (s *sourceURL) extractFromURL(logger klog.Logger) error { // It parsed but could not be used. return singlePodErr } - s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{pod}, Op: kubetypes.SET, Source: kubetypes.HTTPSource} + s.updates <- sourceUpdate{Pods: []*v1.Pod{pod}} return nil } @@ -133,7 +132,7 @@ func (s *sourceURL) extractFromURL(logger klog.Logger) error { for i := range podList.Items { pods = append(pods, &podList.Items[i]) } - s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.HTTPSource} + s.updates <- sourceUpdate{Pods: pods} return nil } diff --git a/pkg/kubelet/config/http_test.go b/pkg/kubelet/config/http_test.go index d637451e2f2e8..ad867a26446df 100644 --- a/pkg/kubelet/config/http_test.go +++ b/pkg/kubelet/config/http_test.go @@ -23,7 +23,7 @@ import ( "testing" "time" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -39,7 +39,7 @@ import ( func TestURLErrorNotExistNoUpdate(t *testing.T) { logger, _ := ktesting.NewTestContext(t) - ch := make(chan interface{}) + ch := make(chan sourceUpdate) NewSourceURL(logger, "http://localhost:49575/_not_found_", http.Header{}, "localhost", time.Millisecond, ch) select { case got := <-ch: @@ -50,7 +50,7 @@ func TestURLErrorNotExistNoUpdate(t *testing.T) { func TestExtractFromHttpBadness(t *testing.T) { logger, _ := ktesting.NewTestContext(t) - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) c := sourceURL{"http://localhost:49575/_not_found_", http.Header{}, "other", ch, nil, 0, http.DefaultClient} if err := c.extractFromURL(logger); err == nil { t.Errorf("Expected error") @@ -120,7 +120,7 @@ func TestExtractInvalidPods(t *testing.T) { } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) c := sourceURL{testServer.URL, http.Header{}, "localhost", ch, nil, 0, http.DefaultClient} if err := c.extractFromURL(logger); err == nil { t.Errorf("%s: Expected error", testCase.desc) @@ -137,7 +137,7 @@ func TestExtractPodsFromHTTP(t *testing.T) { var testCases = []struct { desc string pods runtime.Object - expected kubetypes.PodUpdate + expected sourceUpdate }{ { desc: "Single pod", @@ -161,8 +161,7 @@ func TestExtractPodsFromHTTP(t *testing.T) { Phase: v1.PodPending, }, }, - expected: CreatePodUpdate(kubetypes.SET, - kubetypes.HTTPSource, + expected: createSourceUpdate( &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ UID: "111", @@ -232,8 +231,7 @@ func TestExtractPodsFromHTTP(t *testing.T) { }, }, }, - expected: CreatePodUpdate(kubetypes.SET, - kubetypes.HTTPSource, + expected: createSourceUpdate( &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ UID: "111", @@ -304,13 +302,13 @@ func TestExtractPodsFromHTTP(t *testing.T) { } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) c := sourceURL{testServer.URL, http.Header{}, types.NodeName(nodeName), ch, nil, 0, http.DefaultClient} if err := c.extractFromURL(logger); err != nil { t.Errorf("%s: Unexpected error: %v", testCase.desc, err) continue } - update := (<-ch).(kubetypes.PodUpdate) + update := <-ch if !apiequality.Semantic.DeepEqual(testCase.expected, update) { t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update) @@ -356,14 +354,14 @@ func TestURLWithHeader(t *testing.T) { defer testServer.Close() logger, _ := ktesting.NewTestContext(t) - ch := make(chan interface{}, 1) + ch := make(chan sourceUpdate, 1) header := make(http.Header) header.Set("Metadata-Flavor", "Google") c := sourceURL{testServer.URL, header, "localhost", ch, nil, 0, http.DefaultClient} if err := c.extractFromURL(logger); err != nil { t.Fatalf("Unexpected error extracting from URL: %v", err) } - update := (<-ch).(kubetypes.PodUpdate) + update := <-ch headerVal := fakeHandler.RequestReceived.Header["Metadata-Flavor"] if len(headerVal) != 1 || headerVal[0] != "Google" { diff --git a/pkg/kubelet/config/mux.go b/pkg/kubelet/config/mux.go index 091148f950fd2..6482562055a99 100644 --- a/pkg/kubelet/config/mux.go +++ b/pkg/kubelet/config/mux.go @@ -28,7 +28,7 @@ type merger interface { // Invoked when a change from a source is received. May also function as an incremental // merger if you wish to consume changes incrementally. Must be reentrant when more than // one source is defined. - Merge(ctx context.Context, source string, update interface{}) error + Merge(ctx context.Context, source string, update sourceUpdate) error } // mux is a class for merging configuration from multiple sources. Changes are @@ -40,13 +40,13 @@ type mux struct { // Sources and their lock. sourceLock sync.RWMutex // Maps source names to channels - sources map[string]chan interface{} + sources map[string]chan sourceUpdate } // newMux creates a new mux that can merge changes from multiple sources. func newMux(merger merger) *mux { mux := &mux{ - sources: make(map[string]chan interface{}), + sources: make(map[string]chan sourceUpdate), merger: merger, } return mux @@ -57,7 +57,7 @@ func newMux(merger merger) *mux { // source will return the same channel. This allows change and state based sources // to use the same channel. Different source names however will be treated as a // union. -func (m *mux) ChannelWithContext(ctx context.Context, source string) chan interface{} { +func (m *mux) ChannelWithContext(ctx context.Context, source string) chan sourceUpdate { if len(source) == 0 { panic("Channel given an empty name") } @@ -67,14 +67,14 @@ func (m *mux) ChannelWithContext(ctx context.Context, source string) chan interf if exists { return channel } - newChannel := make(chan interface{}) + newChannel := make(chan sourceUpdate) m.sources[source] = newChannel go wait.Until(func() { m.listen(ctx, source, newChannel) }, 0, ctx.Done()) return newChannel } -func (m *mux) listen(ctx context.Context, source string, listenChannel <-chan interface{}) { +func (m *mux) listen(ctx context.Context, source string, listenChannel <-chan sourceUpdate) { logger := klog.FromContext(ctx) for update := range listenChannel { if err := m.merger.Merge(ctx, source, update); err != nil { diff --git a/pkg/kubelet/config/mux_test.go b/pkg/kubelet/config/mux_test.go index 6033023c87143..9ac3b0b5492aa 100644 --- a/pkg/kubelet/config/mux_test.go +++ b/pkg/kubelet/config/mux_test.go @@ -18,87 +18,95 @@ package config import ( "context" + "fmt" "reflect" "testing" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/test/utils/ktesting" ) func TestConfigurationChannels(t *testing.T) { - ctx := ktesting.Init(t) - ctx = ktesting.WithCancel(ctx) - defer ctx.Cancel("TestConfigurationChannels completed") + tCtx := ktesting.Init(t) + defer tCtx.Cancel("TestConfigurationChannels completed") mux := newMux(nil) - channelOne := mux.ChannelWithContext(ctx, "one") - if channelOne != mux.ChannelWithContext(ctx, "one") { + channelOne := mux.ChannelWithContext(tCtx, "one") + if channelOne != mux.ChannelWithContext(tCtx, "one") { t.Error("Didn't get the same muxuration channel back with the same name") } - channelTwo := mux.ChannelWithContext(ctx, "two") + channelTwo := mux.ChannelWithContext(tCtx, "two") if channelOne == channelTwo { t.Error("Got back the same muxuration channel for different names") } } -type MergeMock struct { - source string - update interface{} - t *testing.T -} - -func (m MergeMock) Merge(ctx context.Context, source string, update interface{}) error { - if m.source != source { - m.t.Errorf("Expected %s, Got %s", m.source, source) - } - if !reflect.DeepEqual(m.update, update) { - m.t.Errorf("Expected %s, Got %s", m.update, update) - } - return nil -} - func TestMergeInvoked(t *testing.T) { - ctx := ktesting.Init(t) - ctx = ktesting.WithCancel(ctx) - defer ctx.Cancel("TestMergeInvoked completed") + tCtx := ktesting.Init(t) + defer tCtx.Cancel("TestMergeInvoked completed") + + const expectedSource = "one" + done := make(chan interface{}) + var merger mergeFunc = func(ctx context.Context, source string, update sourceUpdate) error { + if expectedSource != source { + t.Errorf("Expected %s, Got %s", expectedSource, source) + } + expectedUpdate := fakeUpdate(expectedSource) + if !reflect.DeepEqual(expectedUpdate, update) { + t.Errorf("Expected %v, Got %v", expectedUpdate, update) + } + close(done) + return nil + } - merger := MergeMock{"one", "test", t} mux := newMux(&merger) - mux.ChannelWithContext(ctx, "one") <- "test" + + mux.ChannelWithContext(tCtx, expectedSource) <- fakeUpdate(expectedSource) + + // Wait for Merge call. + select { + case <-done: + // Test complete. + case <-tCtx.Done(): + t.Fatal("Test context canceled before completion") + } } // mergeFunc implements the Merger interface -type mergeFunc func(ctx context.Context, source string, update interface{}) error +type mergeFunc func(ctx context.Context, source string, update sourceUpdate) error -func (f mergeFunc) Merge(ctx context.Context, source string, update interface{}) error { +func (f mergeFunc) Merge(ctx context.Context, source string, update sourceUpdate) error { return f(ctx, source, update) } func TestSimultaneousMerge(t *testing.T) { - ctx := ktesting.Init(t) - ctx = ktesting.WithCancel(ctx) - defer ctx.Cancel("TestSimultaneousMerge completed") + tCtx := ktesting.Init(t) + defer tCtx.Cancel("TestSimultaneousMerge completed") ch := make(chan bool, 2) - mux := newMux(mergeFunc(func(ctx context.Context, source string, update interface{}) error { - switch source { - case "one": - if update.(string) != "test" { - t.Errorf("Expected %s, Got %s", "test", update) - } - case "two": - if update.(string) != "test2" { - t.Errorf("Expected %s, Got %s", "test2", update) - } - default: - t.Errorf("Unexpected source, Got %s", update) + mux := newMux(mergeFunc(func(ctx context.Context, source string, update sourceUpdate) error { + if nsSource := update.Pods[0].Namespace; nsSource != source { + t.Errorf("Expected %s, Got %s", source, nsSource) } ch <- true return nil })) - source := mux.ChannelWithContext(ctx, "one") - source2 := mux.ChannelWithContext(ctx, "two") - source <- "test" - source2 <- "test2" + source := mux.ChannelWithContext(tCtx, "one") + source2 := mux.ChannelWithContext(tCtx, "two") + source <- fakeUpdate("one") + source2 <- fakeUpdate("two") <-ch <-ch } + +func fakeUpdate(source string) sourceUpdate { + return sourceUpdate{[]*v1.Pod{{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-pod", source), + Namespace: source, + UID: types.UID(fmt.Sprintf("%s-pod-uid", source)), + }, + }}} +} diff --git a/pkg/kubelet/container/cache.go b/pkg/kubelet/container/cache.go index 9b4138634d3f8..5067c9ddc97b8 100644 --- a/pkg/kubelet/container/cache.go +++ b/pkg/kubelet/container/cache.go @@ -32,21 +32,30 @@ import ( // has no states known by the runtime, Cache returns an empty PodStatus object // with ID populated. // -// Cache provides two methods to retrieve the PodStatus: the non-blocking Get() -// and the blocking GetNewerThan() method. The component responsible for -// populating the cache is expected to call Delete() to explicitly free the -// cache entries. +// The component responsible for populating the cache is expected to call +// Delete() to explicitly free the cache entries. type Cache interface { - Get(types.UID) (*PodStatus, error) + ROCache // Set updates the cache by setting the PodStatus for the pod only // if the data is newer than the cache based on the provided // time stamp. Returns if the cache was updated. Set(types.UID, *PodStatus, error, time.Time) (updated bool) + // SetObservedTime modifies the observed timestamp of a pod in the cache. + // This indicates that the pod's state was recently confirmed to be accurate + // by the runtime, even if its status wasn't modified. + SetObservedTime(types.UID, time.Time) + Delete(types.UID) + UpdateTime(time.Time) +} + +// ROCache is a read-only interface to the pod status cache. It provides two +// methods to retrieve the PodStatus: the non-blocking Get() and the blocking +// GetNewerThan() method. +type ROCache interface { + Get(types.UID) (*PodStatus, error) // GetNewerThan is a blocking call that only returns the status // when it is newer than the given time. GetNewerThan(types.UID, time.Time) (*PodStatus, error) - Delete(types.UID) - UpdateTime(time.Time) } type data struct { @@ -56,6 +65,10 @@ type data struct { err error // Time when the data was last modified. modified time.Time + // Time when the data was last individually observed/verified by the runtime. + // The actual observed time should be the more recent of observedTime and the global cache timestamp. + // This is updated when the pod is relisted but its status hasn't changed. + observedTime time.Time } type subRecord struct { @@ -111,11 +124,23 @@ func (c *cache) Set(id types.UID, status *PodStatus, err error, timestamp time.T } } - c.pods[id] = &data{status: status, err: err, modified: timestamp} + c.pods[id] = &data{status: status, err: err, modified: timestamp, observedTime: timestamp} c.notify(id, timestamp) return true } +// SetObservedTime modifies the observed timestamp of a pod in the cache and +// notify subscribers if needed. +func (c *cache) SetObservedTime(id types.UID, timestamp time.Time) { + c.lock.Lock() + defer c.lock.Unlock() + if d, ok := c.pods[id]; ok { + d.observedTime = timestamp + } + // Notify all the subscribers if the condition is met. + c.notify(id, timestamp) +} + // Delete removes the entry of the pod. func (c *cache) Delete(id types.UID) { c.lock.Lock() @@ -163,9 +188,10 @@ func (c *cache) getIfNewerThan(id types.UID, minTime time.Time) *data { // minTime, return the default status. return makeDefaultData(id) } - if ok && (d.modified.After(minTime) || globalTimestampIsNewer) { + if ok && (globalTimestampIsNewer || d.modified.After(minTime) || d.observedTime.After(minTime)) { // Status is cached, return status if either of the following is true. // * status was modified after minTime + // * status was observed after minTime // * the global timestamp of the cache is newer than minTime. return d } diff --git a/pkg/kubelet/container/cache_test.go b/pkg/kubelet/container/cache_test.go index 401b8d9a9286e..eaab8cbf931af 100644 --- a/pkg/kubelet/container/cache_test.go +++ b/pkg/kubelet/container/cache_test.go @@ -20,6 +20,7 @@ import ( "fmt" "strconv" "testing" + "testing/synctest" "time" "github.com/stretchr/testify/assert" @@ -61,6 +62,7 @@ func TestGetIfNewerThanWhenPodExists(t *testing.T) { cases := []struct { cacheTime time.Time modified time.Time + observed time.Time expected bool }{ { @@ -208,3 +210,110 @@ func TestRegisterNotification(t *testing.T) { // The advance of cache timestamp should've triggered the notification. verifyNotification(t, ch, true) } + +func TestGetNewerThan(t *testing.T) { + podID := types.UID("test-pod") + status := &PodStatus{ID: podID} + + cases := []struct { + name string + podExists bool + + action func(*cache, time.Time) + }{ + { + name: "UnblockBySet", + podExists: true, + action: func(c *cache, timestamp time.Time) { c.Set(podID, status, nil, timestamp) }, + }, + { + name: "UnblockBySetObservedTime", + podExists: true, + action: func(c *cache, timestamp time.Time) { c.SetObservedTime(podID, timestamp) }, + }, + { + name: "UnblockByUpdateTimeExists", + podExists: true, + action: func(c *cache, timestamp time.Time) { c.UpdateTime(timestamp) }, + }, + { + name: "Missing:UnblockBySet", + podExists: false, + action: func(c *cache, timestamp time.Time) { c.Set(podID, status, nil, timestamp) }, + }, + { + name: "Missing:UnblockByUpdateTime", + podExists: false, + action: func(c *cache, timestamp time.Time) { c.UpdateTime(timestamp) }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + initial := time.Now() + minTime := initial.Add(10 * time.Second) + + c := newTestCache() + c.UpdateTime(initial) + + if tc.podExists { + c.Set(podID, status, nil, initial) + c.SetObservedTime(podID, initial) + } + + // First call to GetNewerThan - should block initially. + resCh := make(chan *PodStatus, 1) + go func() { + s, err := c.GetNewerThan(podID, minTime) + assert.NoError(t, err) + resCh <- s + }() + + requireBlocked(t, resCh, "Initial") + + // Should still be blocked if the action is before minTime. + tc.action(c, minTime.Add(-time.Second)) + requireBlocked(t, resCh, "Before minTime") + + // Should be unblocked if the action is after minTime. + tc.action(c, minTime.Add(time.Second)) + r := requireUnblocked(t, resCh, "After minTime") + assert.Equal(t, podID, r.ID) + + // Verify a subsequent call returns immediately. + resCh2 := make(chan *PodStatus, 1) + go func() { + s, err := c.GetNewerThan(podID, minTime) + assert.NoError(t, err) + resCh2 <- s + }() + r = requireUnblocked(t, resCh2, "Already unblocked") + assert.Equal(t, podID, r.ID) + }) + }) + } +} + +func requireBlocked[T any](t *testing.T, ch <-chan T, msg string) { + t.Helper() + synctest.Wait() + select { + case r := <-ch: + t.Fatalf("%s: receive should have blocked, but got: %v", msg, r) + default: + // OK. + } +} + +func requireUnblocked[T any](t *testing.T, ch <-chan T, msg string) (received T) { + t.Helper() + synctest.Wait() + select { + case r := <-ch: + return r + default: + t.Fatalf("%s: receive should not have been blocked", msg) + return + } +} diff --git a/pkg/kubelet/container/helpers.go b/pkg/kubelet/container/helpers.go index ddafc82434c93..2608d4dcbad8f 100644 --- a/pkg/kubelet/container/helpers.go +++ b/pkg/kubelet/container/helpers.go @@ -50,19 +50,19 @@ type HandlerRunner interface { // able to get necessary informations like the RunContainerOptions, DNS settings, Host IP. type RuntimeHelper interface { GenerateRunContainerOptions(ctx context.Context, pod *v1.Pod, container *v1.Container, podIP string, podIPs []string, imageVolumes ImageVolumes) (contOpts *RunContainerOptions, cleanupAction func(), err error) - GetPodDNS(pod *v1.Pod) (dnsConfig *runtimeapi.DNSConfig, err error) + GetPodDNS(ctx context.Context, pod *v1.Pod) (dnsConfig *runtimeapi.DNSConfig, err error) // GetPodCgroupParent returns the CgroupName identifier, and its literal cgroupfs form on the host // of a pod. GetPodCgroupParent(pod *v1.Pod) string GetPodDir(podUID types.UID) string - GeneratePodHostNameAndDomain(pod *v1.Pod) (hostname string, hostDomain string, err error) + GeneratePodHostNameAndDomain(logger klog.Logger, pod *v1.Pod) (hostname string, hostDomain string, err error) // GetExtraSupplementalGroupsForPod returns a list of the extra // supplemental groups for the Pod. These extra supplemental groups come // from annotations on persistent volumes that the pod depends on. GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64 // GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace - GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) + GetOrCreateUserNamespaceMappings(logger klog.Logger, pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) // PrepareDynamicResources prepares resources for a pod. PrepareDynamicResources(ctx context.Context, pod *v1.Pod) error @@ -70,11 +70,15 @@ type RuntimeHelper interface { // UnprepareDynamicResources unprepares resources for a a pod. UnprepareDynamicResources(ctx context.Context, pod *v1.Pod) error - // SetPodWatchCondition flags a pod to be inspected until the condition is met. - SetPodWatchCondition(types.UID, string, func(*PodStatus) bool) + // RequestPodReinspect flags a pod to be inspected on the next relist. + RequestPodReinspect(types.UID) // PodCPUAndMemoryStats reads the latest CPU & memory usage stats. PodCPUAndMemoryStats(context.Context, *v1.Pod, *PodStatus) (*statsapi.PodStats, error) + + // OnPodSandboxReady callback is invoked after pod sandbox, networking, volume are ready. + // This is used to update the PodReadyToStartContainers condition. + OnPodSandboxReady(ctx context.Context, pod *v1.Pod) error } // ShouldContainerBeRestarted checks whether a container needs to be restarted. @@ -277,14 +281,20 @@ func ExpandContainerCommandAndArgs(container *v1.Container, envs []EnvVar) (comm } // FilterEventRecorder creates an event recorder to record object's event except implicitly required container's, like infra container. -func FilterEventRecorder(recorder record.EventRecorder) record.EventRecorder { +func FilterEventRecorder(recorder record.EventRecorderLogger) record.EventRecorderLogger { return &innerEventRecorder{ recorder: recorder, } } type innerEventRecorder struct { - recorder record.EventRecorder + recorder record.EventRecorderLogger +} + +func (irecorder *innerEventRecorder) WithLogger(logger klog.Logger) record.EventRecorderLogger { + return &innerEventRecorder{ + recorder: irecorder.recorder.WithLogger(logger), + } } func (irecorder *innerEventRecorder) shouldRecordEvent(object runtime.Object) (*v1.ObjectReference, bool) { diff --git a/pkg/kubelet/container/helpers_test.go b/pkg/kubelet/container/helpers_test.go index 1dc3064481442..0da123163280f 100644 --- a/pkg/kubelet/container/helpers_test.go +++ b/pkg/kubelet/container/helpers_test.go @@ -28,7 +28,9 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + "k8s.io/klog/v2" "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/ptr" ) @@ -1178,6 +1180,10 @@ type fakeRecorder struct { aEvents []string } +func (f *fakeRecorder) WithLogger(logger klog.Logger) record.EventRecorderLogger { + return f +} + func (f *fakeRecorder) Event(object runtime.Object, eventtype, reason, message string) { f.events = append(f.events, eventtype+":"+reason+":"+message) } diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 46deb9da07b80..95f7954fe4d4c 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -19,6 +19,7 @@ package container import ( "context" + "errors" "fmt" "io" "net/url" @@ -89,6 +90,9 @@ type Runtime interface { // specifies whether the runtime returns all containers including those already // exited and dead containers (used for garbage collection). GetPods(ctx context.Context, all bool) ([]*Pod, error) + // GetPod fetches the current sandboxes & containers for a pod. + // Returns ErrPodNotFound when no sandboxes are found for the requested pod. + GetPod(ctx context.Context, podUID types.UID) (*Pod, error) // GarbageCollect removes dead containers using the specified container gc policy // If allSourcesReady is not true, it means that kubelet doesn't have the // complete list of pods from all available sources (e.g., apiserver, http, @@ -109,7 +113,7 @@ type Runtime interface { KillPod(ctx context.Context, pod *v1.Pod, runningPod Pod, gracePeriodOverride *int64) error // GetPodStatus retrieves the status of the pod, including the // information of all containers in the pod that are visible in Runtime. - GetPodStatus(ctx context.Context, uid types.UID, name, namespace string) (*PodStatus, error) + GetPodStatus(ctx context.Context, pod *Pod) (*PodStatus, error) // TODO(vmarmol): Unify pod and containerID args. // GetContainerLogs returns logs of a specific container. By // default, it returns a snapshot of the container log. Set 'follow' to true to @@ -149,6 +153,11 @@ type Runtime interface { UpdateActuatedPodLevelResources(actuatedPod *v1.Pod) error } +var ( + // ErrPodNotFound is returned by GetPod when no sandboxes are found for the requested pod. + ErrPodNotFound = errors.New("pod sandboxes not found") +) + // StreamingRuntime is the interface implemented by runtimes that handle the serving of the // streaming calls (exec/attach/port-forward) themselves. In this case, Kubelet should redirect to // the runtime server. @@ -209,8 +218,11 @@ type Pod struct { // List of sandboxes associated with this pod. The sandboxes are converted // to Container temporarily to avoid substantial changes to other // components. This is only populated by kuberuntime. + // Sandboxes are sorted by creation time, newest -> oldest. // TODO: use the runtimeApi.PodSandbox type directly. Sandboxes []*Container + // Timestamp is the time that this Pod object was read from the runtime. + Timestamp time.Time } // PodPair contains both runtime#Pod and api#Pod @@ -318,6 +330,10 @@ type Container struct { Hash uint64 // State is the state of the container. State State + // ID of the sandbox to which this container belongs. + PodSandboxID string + // Creation time of the container in nanoseconds. + CreatedAt int64 } // PodStatus represents the status of the pod and its containers. @@ -462,7 +478,7 @@ type Image struct { // EnvVar represents the environment variable. type EnvVar struct { Name string - Value string + Value string // TODO: switch to []byte } // Annotation represents an annotation. @@ -652,7 +668,8 @@ func (c *RuntimeCondition) String() string { // RuntimeFeatures contains the set of features implemented by the runtime type RuntimeFeatures struct { - SupplementalGroupsPolicy bool + SupplementalGroupsPolicy bool + UserNamespacesHostNetwork bool } // String formats the runtime condition into a human readable string. @@ -660,7 +677,7 @@ func (f *RuntimeFeatures) String() string { if f == nil { return "nil" } - return fmt.Sprintf("SupplementalGroupsPolicy: %v", f.SupplementalGroupsPolicy) + return fmt.Sprintf("SupplementalGroupsPolicy: %v UserNamespacesHostNetwork: %v", f.SupplementalGroupsPolicy, f.UserNamespacesHostNetwork) } // Pods represents the list of pods diff --git a/pkg/kubelet/container/runtime_test.go b/pkg/kubelet/container/runtime_test.go index bb1c3ea847f2d..45fc66100c536 100644 --- a/pkg/kubelet/container/runtime_test.go +++ b/pkg/kubelet/container/runtime_test.go @@ -603,11 +603,11 @@ func TestRuntimeStatusString(t *testing.T) { {Name: "handler1", SupportsRecursiveReadOnlyMounts: true, SupportsUserNamespaces: false}, {Name: "handler2", SupportsRecursiveReadOnlyMounts: false, SupportsUserNamespaces: true}, }, - Features: &RuntimeFeatures{SupplementalGroupsPolicy: true}, + Features: &RuntimeFeatures{SupplementalGroupsPolicy: true, UserNamespacesHostNetwork: true}, } result := status.String() - expected := "Runtime Conditions: RuntimeReady=true reason:ready message:runtime is ready, NetworkReady=false reason:not ready message:network is not ready; Handlers: Name=handler1 SupportsRecursiveReadOnlyMounts: true SupportsUserNamespaces: false, Name=handler2 SupportsRecursiveReadOnlyMounts: false SupportsUserNamespaces: true, Features: SupplementalGroupsPolicy: true" + expected := "Runtime Conditions: RuntimeReady=true reason:ready message:runtime is ready, NetworkReady=false reason:not ready message:network is not ready; Handlers: Name=handler1 SupportsRecursiveReadOnlyMounts: true SupportsUserNamespaces: false, Name=handler2 SupportsRecursiveReadOnlyMounts: false SupportsUserNamespaces: true, Features: SupplementalGroupsPolicy: true UserNamespacesHostNetwork: true" assert.Equal(t, expected, result, "String()") } @@ -688,18 +688,20 @@ func TestRuntimeFeaturesString(t *testing.T) { expected string }{ { - name: "features with SupplementalGroupsPolicy true", + name: "features with both flags true", features: &RuntimeFeatures{ - SupplementalGroupsPolicy: true, + SupplementalGroupsPolicy: true, + UserNamespacesHostNetwork: true, }, - expected: "SupplementalGroupsPolicy: true", + expected: "SupplementalGroupsPolicy: true UserNamespacesHostNetwork: true", }, { - name: "features with SupplementalGroupsPolicy false", + name: "features with both flags false", features: &RuntimeFeatures{ - SupplementalGroupsPolicy: false, + SupplementalGroupsPolicy: false, + UserNamespacesHostNetwork: false, }, - expected: "SupplementalGroupsPolicy: false", + expected: "SupplementalGroupsPolicy: false UserNamespacesHostNetwork: false", }, { name: "nil features", diff --git a/pkg/kubelet/container/sync_result.go b/pkg/kubelet/container/sync_result.go index af5c67a42c0ae..bb2488ab6738a 100644 --- a/pkg/kubelet/container/sync_result.go +++ b/pkg/kubelet/container/sync_result.go @@ -108,10 +108,6 @@ const ( StartContainer SyncAction = "StartContainer" // KillContainer action KillContainer SyncAction = "KillContainer" - // SetupNetwork action - SetupNetwork SyncAction = "SetupNetwork" - // TeardownNetwork action - TeardownNetwork SyncAction = "TeardownNetwork" // InitContainer action InitContainer SyncAction = "InitContainer" // CreatePodSandbox action diff --git a/pkg/kubelet/container/sync_result_test.go b/pkg/kubelet/container/sync_result_test.go index 5ecc6d46eb0f3..a9a87f0693af2 100644 --- a/pkg/kubelet/container/sync_result_test.go +++ b/pkg/kubelet/container/sync_result_test.go @@ -28,11 +28,11 @@ import ( func TestPodSyncResult(t *testing.T) { okResults := []*SyncResult{ NewSyncResult(StartContainer, "container_0"), - NewSyncResult(SetupNetwork, "pod"), + NewSyncResult(CreatePodSandbox, "pod"), } errResults := []*SyncResult{ NewSyncResult(KillContainer, "container_1"), - NewSyncResult(TeardownNetwork, "pod"), + NewSyncResult(KillPodSandbox, "pod"), } errResults[0].Fail(errors.New("error_0"), "message_0") errResults[1].Fail(errors.New("error_1"), "message_1") diff --git a/pkg/kubelet/container/testing/fake_cache.go b/pkg/kubelet/container/testing/fake_cache.go index ad20e7efca657..9c8c76f48a756 100644 --- a/pkg/kubelet/container/testing/fake_cache.go +++ b/pkg/kubelet/container/testing/fake_cache.go @@ -33,7 +33,11 @@ func NewFakeCache(runtime container.Runtime) container.Cache { } func (c *fakeCache) Get(id types.UID) (*container.PodStatus, error) { - return c.runtime.GetPodStatus(context.TODO(), id, "", "") + pod, err := c.runtime.GetPod(context.TODO(), id) + if err != nil { + pod = &container.Pod{ID: id} + } + return c.runtime.GetPodStatus(context.TODO(), pod) } func (c *fakeCache) GetNewerThan(id types.UID, minTime time.Time) (*container.PodStatus, error) { @@ -49,3 +53,6 @@ func (c *fakeCache) Delete(id types.UID) { func (c *fakeCache) UpdateTime(_ time.Time) { } + +func (c *fakeCache) SetObservedTime(id types.UID, timestamp time.Time) { +} diff --git a/pkg/kubelet/container/testing/fake_runtime.go b/pkg/kubelet/container/testing/fake_runtime.go index 710c7f4d2bda7..4825f1c3ff564 100644 --- a/pkg/kubelet/container/testing/fake_runtime.go +++ b/pkg/kubelet/container/testing/fake_runtime.go @@ -237,6 +237,27 @@ func (f *FakeRuntime) GetPods(_ context.Context, all bool) ([]*kubecontainer.Pod return pods, f.Err } +func (f *FakeRuntime) GetPod(_ context.Context, podUID types.UID) (*kubecontainer.Pod, error) { + f.Lock() + defer f.Unlock() + + if f.Err != nil { + return nil, f.Err + } + + for _, fakePod := range f.PodList { + if fakePod.Pod.ID == podUID { + return fakePod.Pod, nil + } + } + for _, fakePod := range f.AllPodList { + if fakePod.Pod.ID == podUID { + return fakePod.Pod, nil + } + } + return nil, kubecontainer.ErrPodNotFound +} + func (f *FakeRuntime) SyncPod(_ context.Context, pod *v1.Pod, _ *kubecontainer.PodStatus, _ []v1.Secret, backOff *flowcontrol.Backoff, _ bool) (result kubecontainer.PodSyncResult) { f.Lock() defer f.Unlock() @@ -303,7 +324,7 @@ func (f *FakeRuntime) GeneratePodStatus(event *runtimeapi.ContainerEventResponse return &status } -func (f *FakeRuntime) GetPodStatus(_ context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { +func (f *FakeRuntime) GetPodStatus(_ context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { f.Lock() defer f.Unlock() diff --git a/pkg/kubelet/container/testing/fake_runtime_helper.go b/pkg/kubelet/container/testing/fake_runtime_helper.go index 36031528d1b13..aa2871209620d 100644 --- a/pkg/kubelet/container/testing/fake_runtime_helper.go +++ b/pkg/kubelet/container/testing/fake_runtime_helper.go @@ -24,6 +24,7 @@ import ( kubetypes "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + "k8s.io/klog/v2" statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" @@ -54,7 +55,7 @@ func (f *FakeRuntimeHelper) GetPodCgroupParent(pod *v1.Pod) string { return "" } -func (f *FakeRuntimeHelper) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) { +func (f *FakeRuntimeHelper) GetPodDNS(_ context.Context, pod *v1.Pod) (*runtimeapi.DNSConfig, error) { return &runtimeapi.DNSConfig{ Servers: f.DNSServers, Searches: f.DNSSearches, @@ -62,7 +63,7 @@ func (f *FakeRuntimeHelper) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error } // This is not used by docker runtime. -func (f *FakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) { +func (f *FakeRuntimeHelper) GeneratePodHostNameAndDomain(logger klog.Logger, _ *v1.Pod) (string, string, error) { return f.HostName, f.HostDomain, f.Err } @@ -74,7 +75,7 @@ func (f *FakeRuntimeHelper) GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int6 return nil } -func (f *FakeRuntimeHelper) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { +func (f *FakeRuntimeHelper) GetOrCreateUserNamespaceMappings(logger klog.Logger, pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { featureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport) if pod == nil || pod.Spec.HostUsers == nil { return nil, nil @@ -117,7 +118,11 @@ func (f *FakeRuntimeHelper) UnprepareDynamicResources(ctx context.Context, pod * return nil } -func (f *FakeRuntimeHelper) SetPodWatchCondition(_ kubetypes.UID, _ string, _ func(*kubecontainer.PodStatus) bool) { +func (f *FakeRuntimeHelper) RequestPodReinspect(_ kubetypes.UID) { + // Not implemented. +} + +func (f *FakeRuntimeHelper) RequestPodRelist(_ kubetypes.UID) { // Not implemented. } @@ -127,3 +132,8 @@ func (f *FakeRuntimeHelper) PodCPUAndMemoryStats(_ context.Context, pod *v1.Pod, } return nil, fmt.Errorf("stats for pod %q not found", pod.UID) } + +func (f *FakeRuntimeHelper) OnPodSandboxReady(_ context.Context, _ *v1.Pod) error { + // Not implemented + return nil +} diff --git a/pkg/kubelet/container/testing/mocks.go b/pkg/kubelet/container/testing/mocks.go index 0730274372f40..e2a07c3cff932 100644 --- a/pkg/kubelet/container/testing/mocks.go +++ b/pkg/kubelet/container/testing/mocks.go @@ -697,9 +697,77 @@ func (_c *MockRuntime_GetImageSize_Call) RunAndReturn(run func(ctx context.Conte return _c } +// GetPod provides a mock function for the type MockRuntime +func (_mock *MockRuntime) GetPod(ctx context.Context, podUID types.UID) (*container.Pod, error) { + ret := _mock.Called(ctx, podUID) + + if len(ret) == 0 { + panic("no return value specified for GetPod") + } + + var r0 *container.Pod + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, types.UID) (*container.Pod, error)); ok { + return returnFunc(ctx, podUID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, types.UID) *container.Pod); ok { + r0 = returnFunc(ctx, podUID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*container.Pod) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, types.UID) error); ok { + r1 = returnFunc(ctx, podUID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockRuntime_GetPod_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPod' +type MockRuntime_GetPod_Call struct { + *mock.Call +} + +// GetPod is a helper method to define mock.On call +// - ctx context.Context +// - podUID types.UID +func (_e *MockRuntime_Expecter) GetPod(ctx interface{}, podUID interface{}) *MockRuntime_GetPod_Call { + return &MockRuntime_GetPod_Call{Call: _e.mock.On("GetPod", ctx, podUID)} +} + +func (_c *MockRuntime_GetPod_Call) Run(run func(ctx context.Context, podUID types.UID)) *MockRuntime_GetPod_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 types.UID + if args[1] != nil { + arg1 = args[1].(types.UID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockRuntime_GetPod_Call) Return(pod *container.Pod, err error) *MockRuntime_GetPod_Call { + _c.Call.Return(pod, err) + return _c +} + +func (_c *MockRuntime_GetPod_Call) RunAndReturn(run func(ctx context.Context, podUID types.UID) (*container.Pod, error)) *MockRuntime_GetPod_Call { + _c.Call.Return(run) + return _c +} + // GetPodStatus provides a mock function for the type MockRuntime -func (_mock *MockRuntime) GetPodStatus(ctx context.Context, uid types.UID, name string, namespace string) (*container.PodStatus, error) { - ret := _mock.Called(ctx, uid, name, namespace) +func (_mock *MockRuntime) GetPodStatus(ctx context.Context, pod *container.Pod) (*container.PodStatus, error) { + ret := _mock.Called(ctx, pod) if len(ret) == 0 { panic("no return value specified for GetPodStatus") @@ -707,18 +775,18 @@ func (_mock *MockRuntime) GetPodStatus(ctx context.Context, uid types.UID, name var r0 *container.PodStatus var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, types.UID, string, string) (*container.PodStatus, error)); ok { - return returnFunc(ctx, uid, name, namespace) + if returnFunc, ok := ret.Get(0).(func(context.Context, *container.Pod) (*container.PodStatus, error)); ok { + return returnFunc(ctx, pod) } - if returnFunc, ok := ret.Get(0).(func(context.Context, types.UID, string, string) *container.PodStatus); ok { - r0 = returnFunc(ctx, uid, name, namespace) + if returnFunc, ok := ret.Get(0).(func(context.Context, *container.Pod) *container.PodStatus); ok { + r0 = returnFunc(ctx, pod) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*container.PodStatus) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, types.UID, string, string) error); ok { - r1 = returnFunc(ctx, uid, name, namespace) + if returnFunc, ok := ret.Get(1).(func(context.Context, *container.Pod) error); ok { + r1 = returnFunc(ctx, pod) } else { r1 = ret.Error(1) } @@ -732,36 +800,24 @@ type MockRuntime_GetPodStatus_Call struct { // GetPodStatus is a helper method to define mock.On call // - ctx context.Context -// - uid types.UID -// - name string -// - namespace string -func (_e *MockRuntime_Expecter) GetPodStatus(ctx interface{}, uid interface{}, name interface{}, namespace interface{}) *MockRuntime_GetPodStatus_Call { - return &MockRuntime_GetPodStatus_Call{Call: _e.mock.On("GetPodStatus", ctx, uid, name, namespace)} +// - pod *container.Pod +func (_e *MockRuntime_Expecter) GetPodStatus(ctx interface{}, pod interface{}) *MockRuntime_GetPodStatus_Call { + return &MockRuntime_GetPodStatus_Call{Call: _e.mock.On("GetPodStatus", ctx, pod)} } -func (_c *MockRuntime_GetPodStatus_Call) Run(run func(ctx context.Context, uid types.UID, name string, namespace string)) *MockRuntime_GetPodStatus_Call { +func (_c *MockRuntime_GetPodStatus_Call) Run(run func(ctx context.Context, pod *container.Pod)) *MockRuntime_GetPodStatus_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 types.UID + var arg1 *container.Pod if args[1] != nil { - arg1 = args[1].(types.UID) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 string - if args[3] != nil { - arg3 = args[3].(string) + arg1 = args[1].(*container.Pod) } run( arg0, arg1, - arg2, - arg3, ) }) return _c @@ -772,7 +828,7 @@ func (_c *MockRuntime_GetPodStatus_Call) Return(podStatus *container.PodStatus, return _c } -func (_c *MockRuntime_GetPodStatus_Call) RunAndReturn(run func(ctx context.Context, uid types.UID, name string, namespace string) (*container.PodStatus, error)) *MockRuntime_GetPodStatus_Call { +func (_c *MockRuntime_GetPodStatus_Call) RunAndReturn(run func(ctx context.Context, pod *container.Pod) (*container.PodStatus, error)) *MockRuntime_GetPodStatus_Call { _c.Call.Return(run) return _c } diff --git a/pkg/kubelet/events/event.go b/pkg/kubelet/events/event.go index 209cb07639760..817b478781a76 100644 --- a/pkg/kubelet/events/event.go +++ b/pkg/kubelet/events/event.go @@ -88,6 +88,7 @@ const ( // Image manager event reason list const ( + InvalidImageID = "InvalidImageID" InvalidDiskCapacity = "InvalidDiskCapacity" FreeDiskSpaceFailed = "FreeDiskSpaceFailed" ) diff --git a/pkg/kubelet/events/resize.go b/pkg/kubelet/events/resize.go index 1d3ea45cafd3d..7c1310ae6ebf7 100644 --- a/pkg/kubelet/events/resize.go +++ b/pkg/kubelet/events/resize.go @@ -40,27 +40,27 @@ type podResourceSummary struct { } // PodResizeCompletedMsg generates the pod resize completed event message. -func PodResizeCompletedMsg(allocatedPod *v1.Pod, generation int64) string { - return podResizeMessage(allocatedPod, generation, "", "Pod resize completed") +func PodResizeCompletedMsg(logger klog.Logger, allocatedPod *v1.Pod, generation int64) string { + return podResizeMessage(logger, allocatedPod, generation, "", "Pod resize completed") } // PodResizeStartedMsg generates the pod resize in progress event message. -func PodResizeStartedMsg(allocatedPod *v1.Pod, generation int64) string { - return podResizeMessage(allocatedPod, generation, "", "Pod resize started") +func PodResizeStartedMsg(logger klog.Logger, allocatedPod *v1.Pod, generation int64) string { + return podResizeMessage(logger, allocatedPod, generation, "", "Pod resize started") } // PodResizeErrorMsg generates the pod resize in progress error event message. -func PodResizeErrorMsg(allocatedPod *v1.Pod, generation int64, errorMsg string) string { - return podResizeMessage(allocatedPod, generation, errorMsg, "Pod resize error") +func PodResizeErrorMsg(logger klog.Logger, allocatedPod *v1.Pod, generation int64, errorMsg string) string { + return podResizeMessage(logger, allocatedPod, generation, errorMsg, "Pod resize error") } // PodResizePendingMsg generates the pod resize pending event message. -func PodResizePendingMsg(pod *v1.Pod, reason, message string, generation int64) string { - return podResizeMessage(pod, generation, message, fmt.Sprintf("Pod resize %s", reason)) +func PodResizePendingMsg(logger klog.Logger, pod *v1.Pod, reason, message string, generation int64) string { + return podResizeMessage(logger, pod, generation, message, fmt.Sprintf("Pod resize %s", reason)) } -func podResizeMessage(pod *v1.Pod, generation int64, errorMsg, messagePrefix string) string { - resources, err := makeResourceSummaryFromSpec(pod, generation, errorMsg) +func podResizeMessage(logger klog.Logger, pod *v1.Pod, generation int64, errorMsg, messagePrefix string) string { + resources, err := makeResourceSummaryFromSpec(logger, pod, generation, errorMsg) if err != nil { return messagePrefix } @@ -69,7 +69,7 @@ func podResizeMessage(pod *v1.Pod, generation int64, errorMsg, messagePrefix str } // Returns the desired resources from the podspec. -func makeResourceSummaryFromSpec(pod *v1.Pod, generation int64, errorMessage string) (string, error) { +func makeResourceSummaryFromSpec(logger klog.Logger, pod *v1.Pod, generation int64, errorMessage string) (string, error) { specResources := &podResourceSummary{Generation: generation, Error: errorMessage} for container, containerType := range podutil.ContainerIter(&pod.Spec, podutil.InitContainers|podutil.Containers) { allocation := containerAllocation{ @@ -86,7 +86,7 @@ func makeResourceSummaryFromSpec(pod *v1.Pod, generation int64, errorMessage str message, err := json.Marshal(specResources) if err != nil { - klog.ErrorS(err, "Failed to serialize resource summary", "pod", format.Pod(pod)) + logger.Error(err, "Failed to serialize resource summary", "pod", format.Pod(pod)) return "", err } return string(message), nil diff --git a/pkg/kubelet/events/resize_test.go b/pkg/kubelet/events/resize_test.go index 1dc9c38fa36df..51135b387a0af 100644 --- a/pkg/kubelet/events/resize_test.go +++ b/pkg/kubelet/events/resize_test.go @@ -24,10 +24,12 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2/ktesting" "k8s.io/utils/ptr" ) func TestPodResizeCompletedMsg(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string containers []testContainer @@ -80,13 +82,14 @@ func TestPodResizeCompletedMsg(t *testing.T) { } } - msg := PodResizeCompletedMsg(pod, test.observedGeneration) + msg := PodResizeCompletedMsg(logger, pod, test.observedGeneration) assert.Equal(t, test.expected, msg) }) } } func TestPodResizeStartedMsg(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string allocated []testContainer @@ -150,13 +153,14 @@ func TestPodResizeStartedMsg(t *testing.T) { } } - msg := PodResizeStartedMsg(allocatedPod, test.observedGeneration) + msg := PodResizeStartedMsg(logger, allocatedPod, test.observedGeneration) assert.Equal(t, test.expected, msg) }) } } func TestPodResizeErrorMsg(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string allocated []testContainer @@ -223,13 +227,14 @@ func TestPodResizeErrorMsg(t *testing.T) { } } - msg := PodResizeErrorMsg(allocatedPod, test.observedGeneration, test.errMsg) + msg := PodResizeErrorMsg(logger, allocatedPod, test.observedGeneration, test.errMsg) assert.Equal(t, test.expected, msg) }) } } func TestPodResizePendingMsg(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string desired []testContainer @@ -299,7 +304,7 @@ func TestPodResizePendingMsg(t *testing.T) { } } - msg := PodResizePendingMsg(pod, test.reason, test.message, test.observedGeneration) + msg := PodResizePendingMsg(logger, pod, test.reason, test.message, test.observedGeneration) assert.Equal(t, test.expected, msg) }) } diff --git a/pkg/kubelet/eviction/defaults_linux.go b/pkg/kubelet/eviction/defaults_linux.go index 172531427cebb..01e5b08042c40 100644 --- a/pkg/kubelet/eviction/defaults_linux.go +++ b/pkg/kubelet/eviction/defaults_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/defaults_others.go b/pkg/kubelet/eviction/defaults_others.go index 912acf178db9b..082f509d5bf7d 100644 --- a/pkg/kubelet/eviction/defaults_others.go +++ b/pkg/kubelet/eviction/defaults_others.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/defaults_windows.go b/pkg/kubelet/eviction/defaults_windows.go index de6517f6540b3..c00a8d6916df4 100644 --- a/pkg/kubelet/eviction/defaults_windows.go +++ b/pkg/kubelet/eviction/defaults_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/eviction_manager.go b/pkg/kubelet/eviction/eviction_manager.go index 556c0d2a7c4df..4019d6aa3f59e 100644 --- a/pkg/kubelet/eviction/eviction_manager.go +++ b/pkg/kubelet/eviction/eviction_manager.go @@ -617,7 +617,7 @@ func (m *managerImpl) evictPod(logger klog.Logger, pod *v1.Pod, gracePeriodOverr return false } // record that we are evicting the pod - m.recorder.AnnotatedEventf(pod, annotations, v1.EventTypeWarning, Reason, evictMsg) + m.recorder.AnnotatedEventf(pod, annotations, v1.EventTypeWarning, Reason, "%s", evictMsg) // this is a blocking call and should only return when the pod and its containers are killed. logger.V(3).Info("Evicting pod", "pod", klog.KObj(pod), "podUID", pod.UID, "message", evictMsg) err := m.killPodFunc(pod, true, &gracePeriodOverride, func(status *v1.PodStatus) { diff --git a/pkg/kubelet/eviction/helpers_others.go b/pkg/kubelet/eviction/helpers_others.go index b3e9324b51820..902b07d549fcc 100644 --- a/pkg/kubelet/eviction/helpers_others.go +++ b/pkg/kubelet/eviction/helpers_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/helpers_windows.go b/pkg/kubelet/eviction/helpers_windows.go index f37d4f82b3327..978c17367d62f 100644 --- a/pkg/kubelet/eviction/helpers_windows.go +++ b/pkg/kubelet/eviction/helpers_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/memory_threshold_notifier_others.go b/pkg/kubelet/eviction/memory_threshold_notifier_others.go index 140d6be17c716..d4050da163c6c 100644 --- a/pkg/kubelet/eviction/memory_threshold_notifier_others.go +++ b/pkg/kubelet/eviction/memory_threshold_notifier_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/memory_threshold_notifier_test.go b/pkg/kubelet/eviction/memory_threshold_notifier_test.go index 9b1577d7b4a1d..0ceae0957b99b 100644 --- a/pkg/kubelet/eviction/memory_threshold_notifier_test.go +++ b/pkg/kubelet/eviction/memory_threshold_notifier_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/memory_threshold_notifier_windows.go b/pkg/kubelet/eviction/memory_threshold_notifier_windows.go index 96e9b4f0717d8..0984c693e12a0 100644 --- a/pkg/kubelet/eviction/memory_threshold_notifier_windows.go +++ b/pkg/kubelet/eviction/memory_threshold_notifier_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/eviction/threshold_notifier_unsupported.go b/pkg/kubelet/eviction/threshold_notifier_unsupported.go index b06a2839e698f..d692e71270c8d 100644 --- a/pkg/kubelet/eviction/threshold_notifier_unsupported.go +++ b/pkg/kubelet/eviction/threshold_notifier_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/images/image_gc_manager.go b/pkg/kubelet/images/image_gc_manager.go index 8075c1a4d06de..e42d35cb9716a 100644 --- a/pkg/kubelet/images/image_gc_manager.go +++ b/pkg/kubelet/images/image_gc_manager.go @@ -27,6 +27,7 @@ import ( "time" "go.opentelemetry.io/otel/trace" + v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" @@ -383,7 +384,7 @@ func (im *realImageGCManager) GarbageCollect(ctx context.Context, beganGC time.T // Check valid capacity. if capacity == 0 { err := goerrors.New("invalid capacity 0 on image filesystem") - im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidDiskCapacity, err.Error()) + im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidDiskCapacity, "%s", err.Error()) return err } @@ -578,7 +579,7 @@ func (im *realImageGCManager) imagesInEvictionOrder(ctx context.Context, freeTim imageID := getImageIDFromTuple(image) // Ensure imageID is valid or else continue if imageID == "" { - im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, "ImageID is not valid, skipping, ImageID: %v", imageID) + im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidImageID, "ImageID is not valid, skipping, image: %v", image) continue } images = append(images, evictionInfo{ diff --git a/pkg/kubelet/images/image_manager.go b/pkg/kubelet/images/image_manager.go index 1093359ecc1b8..779dd00ae4978 100644 --- a/pkg/kubelet/images/image_manager.go +++ b/pkg/kubelet/images/image_manager.go @@ -315,11 +315,12 @@ func (m *imageManager) makeLookupPullCredentialsFunc(image string, pod *v1.Pod, } func (m *imageManager) pullImage(ctx context.Context, logPrefix string, objRef *v1.ObjectReference, podUID types.UID, image string, imgSpec kubecontainer.ImageSpec, pullCredentials []credentialprovider.TrackedAuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (imageRef, message string, err error) { + logger := klog.FromContext(ctx) var pullSucceeded bool var finalPullCredentials *credentialprovider.TrackedAuthConfig if utilfeature.DefaultFeatureGate.Enabled(features.KubeletEnsureSecretPulledImages) { - if err := m.imagePullManager.RecordPullIntent(image); err != nil { + if err := m.imagePullManager.RecordPullIntent(logger, image); err != nil { return "", fmt.Sprintf("Failed to record image pull intent for container image %q: %v", image, err), err } diff --git a/pkg/kubelet/images/metrics.go b/pkg/kubelet/images/metrics.go index 0a93f12bab1ef..b7e3e766edefc 100644 --- a/pkg/kubelet/images/metrics.go +++ b/pkg/kubelet/images/metrics.go @@ -30,8 +30,8 @@ import ( var ( ensureImageRequestsCounter = metrics.NewCounterVec( &metrics.CounterOpts{ - Subsystem: kubeletmetrics.KubeletSubsystem + "_" + "image_manager", - Name: "ensure_image_requests_total", + Subsystem: kubeletmetrics.KubeletSubsystem, + Name: "image_manager_ensure_image_requests_total", Help: "Number of ensure-image requests processed by the kubelet.", StabilityLevel: metrics.ALPHA, }, diff --git a/pkg/kubelet/images/pullmanager/benchmarks_test.go b/pkg/kubelet/images/pullmanager/benchmarks_test.go index f9750779ba749..8a54fc1c9519f 100644 --- a/pkg/kubelet/images/pullmanager/benchmarks_test.go +++ b/pkg/kubelet/images/pullmanager/benchmarks_test.go @@ -88,8 +88,9 @@ func mustAttemptPullReadFunc(expectHit bool) benchmarkedCheckFunc { // We cannot do direct writes though because FSAccessor is not thread-safe and // would cause errors when the tests run in parallel. func BenchmarkPullManagerWriteImagePullIntent(b *testing.B) { + logger, _ := ktesting.NewTestContext(b) benchmarkAllPullAccessorsWrite(b, func(b *testing.B, pullManager PullManager, imgRef string) { - if err := pullManager.RecordPullIntent("test.repo/org/" + imgRef); err != nil { + if err := pullManager.RecordPullIntent(logger, "test.repo/org/"+imgRef); err != nil { b.Fatalf("failed to write a record: %v", err) } }) @@ -210,6 +211,7 @@ const ( ) func benchmarkPullAccessorCacheRead(b *testing.B, bc recordAccessorBenchmark, benchmarkedCheck benchmarkedCheckFunc) { + logger, _ := ktesting.NewTestContext(b) b.Run(fmt.Sprintf("Type=%s/RecordsStored=%d/ConcurrencyPerCPU=%d", bc.namedInit.name, bc.recordsInCache, bc.concurrencyMultiplier), func(b *testing.B) { genRecords, genRequests := generateRecordsAndRequests(bc.recordsInCache, bc.cacheHit) @@ -218,7 +220,7 @@ func benchmarkPullAccessorCacheRead(b *testing.B, bc recordAccessorBenchmark, be // tempdir cleanup to always trigger before the next benchmark is run accessor := bc.namedInit.accessorInit(b) for _, r := range genRecords { - if err := accessor.WriteImagePulledRecord(r); err != nil { + if err := accessor.WriteImagePulledRecord(logger, r); err != nil { b.Fatalf("failed to prepare cache - write error: %v", err) } } @@ -291,9 +293,10 @@ func generateRecordsAndRequests(recordsNum int, generateHits bool) ([]*kubeletco func setupFSRecordsAccessor(t testing.TB) PullRecordsAccessor { t.Helper() + logger, _ := ktesting.NewTestContext(t) tempDir := t.TempDir() - accessor, err := NewFSPullRecordsAccessor(tempDir) + accessor, err := NewFSPullRecordsAccessor(logger, tempDir) if err != nil { t.Fatalf("failed to setup filesystem pull records accessor: %v", err) } @@ -301,10 +304,11 @@ func setupFSRecordsAccessor(t testing.TB) PullRecordsAccessor { } func setupInMemRecordsAccessor(t testing.TB, cacheSize int, authoritative bool) PullRecordsAccessor { + logger, _ := ktesting.NewTestContext(t) t.Helper() fsAccessor := setupFSRecordsAccessor(t) - memcacheAccessor := NewCachedPullRecordsAccessor(fsAccessor, int32(cacheSize), int32(cacheSize), int32(runtime.NumCPU())) + memcacheAccessor := NewCachedPullRecordsAccessor(logger, fsAccessor, int32(cacheSize), int32(cacheSize), int32(runtime.NumCPU())) gotMeteredAccessor, ok := memcacheAccessor.(*meteringRecordsAccessor) if !ok { t.Fatalf("the tested accessor must be a metered records accessor, got %T", memcacheAccessor) diff --git a/pkg/kubelet/images/pullmanager/fs_pullrecords.go b/pkg/kubelet/images/pullmanager/fs_pullrecords.go index 3e1a249430ca0..c1da5fb304da4 100644 --- a/pkg/kubelet/images/pullmanager/fs_pullrecords.go +++ b/pkg/kubelet/images/pullmanager/fs_pullrecords.go @@ -58,7 +58,7 @@ type fsPullRecordsAccessor struct { // NewFSPullRecordsAccessor returns an accessor for the ImagePullIntent/ImagePulledRecord // records with a filesystem as the backing database. -func NewFSPullRecordsAccessor(kubeletDir string) (PullRecordsAccessor, error) { +func NewFSPullRecordsAccessor(logger klog.Logger, kubeletDir string) (PullRecordsAccessor, error) { kubeletConfigEncoder, kubeletConfigDecoder, err := createKubeletConfigSchemeEncoderDecoder() if err != nil { return nil, err @@ -80,11 +80,11 @@ func NewFSPullRecordsAccessor(kubeletDir string) (PullRecordsAccessor, error) { return nil, err } - accessor.recordsVersionMigration() + accessor.recordsVersionMigration(logger) return NewMeteringRecordsAccessor(accessor, fsPullIntentsSize, fsPulledRecordsSize), nil } -func (f *fsPullRecordsAccessor) recordsVersionMigration() { +func (f *fsPullRecordsAccessor) recordsVersionMigration(logger klog.Logger) { err := processDirFiles(f.pullingDir, func(filePath string, fileContent []byte) error { intent, isCurrentVersion, err := decodeIntent(f.decoder, fileContent) @@ -92,14 +92,14 @@ func (f *fsPullRecordsAccessor) recordsVersionMigration() { return fmt.Errorf("failed to decode ImagePullIntent from file %q: %w", filePath, err) } if !isCurrentVersion { - if err := f.WriteImagePullIntent(intent.Image); err != nil { + if err := f.WriteImagePullIntent(logger, intent.Image); err != nil { return fmt.Errorf("failed to migrate ImagePullIntent for image %q to the current version: %w", intent.Image, err) } } return nil }) if err != nil { - klog.ErrorS(err, "Error migrating image pull intents") + logger.Error(err, "Error migrating image pull intents") } err = processDirFiles(f.pulledDir, func(filePath string, fileContent []byte) error { @@ -108,7 +108,7 @@ func (f *fsPullRecordsAccessor) recordsVersionMigration() { return fmt.Errorf("failed to decode ImagePulledRecord from file %q: %w", filePath, err) } if !isCurrentVersion { - if err := f.WriteImagePulledRecord(pullRecord); err != nil { + if err := f.WriteImagePulledRecord(logger, pullRecord); err != nil { return fmt.Errorf("failed to migrate ImagePulledRecord for image ref %q to the current version: %w", pullRecord.ImageRef, err) } } @@ -116,11 +116,11 @@ func (f *fsPullRecordsAccessor) recordsVersionMigration() { }) if err != nil { - klog.ErrorS(err, "Error migrating image pulled records") + logger.Error(err, "Error migrating image pulled records") } } -func (f *fsPullRecordsAccessor) WriteImagePullIntent(image string) error { +func (f *fsPullRecordsAccessor) WriteImagePullIntent(logger klog.Logger, image string) error { intent := kubeletconfiginternal.ImagePullIntent{ Image: image, } @@ -166,7 +166,7 @@ func (f *fsPullRecordsAccessor) ImagePullIntentExists(image string) (bool, error return intent.Image == image, nil } -func (f *fsPullRecordsAccessor) DeleteImagePullIntent(image string) error { +func (f *fsPullRecordsAccessor) DeleteImagePullIntent(logger klog.Logger, image string) error { err := os.Remove(filepath.Join(f.pullingDir, cacheFilename(image))) if os.IsNotExist(err) { return nil @@ -207,7 +207,7 @@ func (f *fsPullRecordsAccessor) ListImagePulledRecords() ([]*kubeletconfigintern return pullRecords, err } -func (f *fsPullRecordsAccessor) WriteImagePulledRecord(pulledRecord *kubeletconfiginternal.ImagePulledRecord) error { +func (f *fsPullRecordsAccessor) WriteImagePulledRecord(logger klog.Logger, pulledRecord *kubeletconfiginternal.ImagePulledRecord) error { recordBytes := bytes.NewBuffer([]byte{}) if err := f.encoder.Encode(pulledRecord, recordBytes); err != nil { return fmt.Errorf("failed to serialize ImagePulledRecord: %w", err) @@ -216,7 +216,7 @@ func (f *fsPullRecordsAccessor) WriteImagePulledRecord(pulledRecord *kubeletconf return writeFile(f.pulledDir, cacheFilename(pulledRecord.ImageRef), recordBytes.Bytes()) } -func (f *fsPullRecordsAccessor) DeleteImagePulledRecord(imageRef string) error { +func (f *fsPullRecordsAccessor) DeleteImagePulledRecord(logger klog.Logger, imageRef string) error { err := os.Remove(filepath.Join(f.pulledDir, cacheFilename(imageRef))) if os.IsNotExist(err) { return nil diff --git a/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go b/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go index ac7cb387d8ecb..1709613d5b358 100644 --- a/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go +++ b/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go @@ -23,9 +23,11 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "k8s.io/kubernetes/test/utils/ktesting" ) func TestNewFSPullRecordsAccessor(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string initRoot bool @@ -157,7 +159,7 @@ func TestNewFSPullRecordsAccessor(t *testing.T) { } } - _, err := NewFSPullRecordsAccessor(kubeletDir) + _, err := NewFSPullRecordsAccessor(logger, kubeletDir) if (err != nil) != tt.wantErr { t.Errorf("NewFSPullRecordsAccessor() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/kubelet/images/pullmanager/image_pull_manager.go b/pkg/kubelet/images/pullmanager/image_pull_manager.go index 98fe1b033870d..27e97381d870a 100644 --- a/pkg/kubelet/images/pullmanager/image_pull_manager.go +++ b/pkg/kubelet/images/pullmanager/image_pull_manager.go @@ -29,7 +29,6 @@ import ( "k8s.io/klog/v2" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" - "k8s.io/kubernetes/pkg/util/parsers" ) var _ ImagePullManager = &PullManager{} @@ -77,11 +76,11 @@ func NewImagePullManager(ctx context.Context, recordsAccessor PullRecordsAccesso return m, nil } -func (f *PullManager) RecordPullIntent(image string) error { +func (f *PullManager) RecordPullIntent(logger klog.Logger, image string) error { f.intentAccessors.Lock(image) defer f.intentAccessors.Unlock(image) - if err := f.recordsAccessor.WriteImagePullIntent(image); err != nil { + if err := f.recordsAccessor.WriteImagePullIntent(logger, image); err != nil { return fmt.Errorf("failed to record image pull intent: %w", err) } @@ -153,7 +152,7 @@ func (f *PullManager) writePulledRecordIfChanged(ctx context.Context, image, ima return nil } - return f.recordsAccessor.WriteImagePulledRecord(pulledRecord) + return f.recordsAccessor.WriteImagePulledRecord(logger, pulledRecord) } func (f *PullManager) RecordImagePullFailed(ctx context.Context, image string) { @@ -169,7 +168,7 @@ func (f *PullManager) decrementImagePullIntent(ctx context.Context, image string defer f.intentAccessors.Unlock(image) if f.getIntentCounterForImage(image) <= 1 { - if err := f.recordsAccessor.DeleteImagePullIntent(image); err != nil { + if err := f.recordsAccessor.DeleteImagePullIntent(logger, image); err != nil { logger.Error(err, "failed to remove image pull intent", "image", image) return } @@ -350,7 +349,7 @@ func (f *PullManager) PruneUnknownRecords(ctx context.Context, imageList []strin continue } - if err := f.recordsAccessor.DeleteImagePulledRecord(imageRecord.ImageRef); err != nil { + if err := f.recordsAccessor.DeleteImagePulledRecord(logger, imageRecord.ImageRef); err != nil { logger.Error(err, "failed to remove an ImagePulledRecord", "imageRef", imageRecord.ImageRef) } } @@ -398,7 +397,7 @@ func (f *PullManager) initialize(ctx context.Context) { continue } - if err := f.recordsAccessor.DeleteImagePullIntent(image); err != nil { + if err := f.recordsAccessor.DeleteImagePullIntent(logger, image); err != nil { logger.V(2).Info("failed to remove image pull intent file", "imageName", image, "error", err) } } @@ -576,12 +575,6 @@ func imagePullSecretLess(a, b kubeletconfiginternal.ImagePullSecret) int { return strings.Compare(a.UID, b.UID) } -// trimImageTagDigest removes the tag and digest from an image name -func trimImageTagDigest(containerImage string) (string, error) { - imageName, _, _, err := parsers.ParseImageName(containerImage) - return imageName, err -} - // mergePullServiceAccounts merges two slices of ImagePullServiceAccount object into one while // keeping the objects unique per `Namespace, Name, UID` key. // The returned slice is sorted by Namespace, Name and UID (in this order). diff --git a/pkg/kubelet/images/pullmanager/image_pull_manager_test.go b/pkg/kubelet/images/pullmanager/image_pull_manager_test.go index c61eb9f553004..d10432a6a83e4 100644 --- a/pkg/kubelet/images/pullmanager/image_pull_manager_test.go +++ b/pkg/kubelet/images/pullmanager/image_pull_manager_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/container" ctesting "k8s.io/kubernetes/pkg/kubelet/container/testing" @@ -818,12 +819,13 @@ type testWriteCountingFSPullRecordsAccessor struct { fsPullRecordsAccessor } -func (a *testWriteCountingFSPullRecordsAccessor) WriteImagePulledRecord(pulledRecord *kubeletconfiginternal.ImagePulledRecord) error { +func (a *testWriteCountingFSPullRecordsAccessor) WriteImagePulledRecord(logger klog.Logger, pulledRecord *kubeletconfiginternal.ImagePulledRecord) error { a.imagePulledRecordsWrites += 1 - return a.fsPullRecordsAccessor.WriteImagePulledRecord(pulledRecord) + return a.fsPullRecordsAccessor.WriteImagePulledRecord(logger, pulledRecord) } func TestFileBasedImagePullManager_RecordPullIntent(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string inputImage string @@ -869,7 +871,7 @@ func TestFileBasedImagePullManager_RecordPullIntent(t *testing.T) { f.intentCounters.Store(tt.inputImage, tt.startCounter) } - _ = f.RecordPullIntent(tt.inputImage) + _ = f.RecordPullIntent(logger, tt.inputImage) expectFilename := filepath.Join(pullingDir, tt.wantFile) require.FileExists(t, expectFilename) @@ -1145,6 +1147,58 @@ func TestFileBasedImagePullManager_RecordImagePulled(t *testing.T) { }, }, }, + { + name: "record with familiar image name - only the last segment", + image: "testing@sha256:f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af", + imageRef: "testimageref-familiarlast", + creds: &kubeletconfiginternal.ImagePullCredentials{ + KubernetesServiceAccounts: []kubeletconfiginternal.ImagePullServiceAccount{ + {UID: "test-sa-uid", Namespace: "default", Name: "test-sa"}, + }, + }, + existingPulling: []string{"sha256-7ffc99ef20589dc844b202c4cba501d1aaf06d1481935aa1e160029afd639847"}, + expectPulled: []string{"sha256-f2ba1256df29972c4fe11d8af7d302e37631c6077a2ed29d8bee55ee77cf26ad"}, + pullsInFlight: 1, + expectPullingRemoved: "sha256-7ffc99ef20589dc844b202c4cba501d1aaf06d1481935aa1e160029afd639847", + checkedPullFile: "sha256-f2ba1256df29972c4fe11d8af7d302e37631c6077a2ed29d8bee55ee77cf26ad", + expectUpdated: true, + expectedPullRecord: kubeletconfiginternal.ImagePulledRecord{ + ImageRef: "testimageref-familiarlast", + CredentialMapping: map[string]kubeletconfiginternal.ImagePullCredentials{ + "testing": { + KubernetesServiceAccounts: []kubeletconfiginternal.ImagePullServiceAccount{ + {UID: "test-sa-uid", Namespace: "default", Name: "test-sa"}, + }, + }, + }, + }, + }, + { + name: "record with familiar image name - org and name", + image: "secretorg/myimage:customtag", + imageRef: "testimageref-familiarwithorg", + creds: &kubeletconfiginternal.ImagePullCredentials{ + KubernetesServiceAccounts: []kubeletconfiginternal.ImagePullServiceAccount{ + {UID: "test-sa-uid", Namespace: "default", Name: "test-sa"}, + }, + }, + existingPulling: []string{"sha256-5f3ca9e4d82e32203c2fafb410dfae9c8047bc6ebe57d642fe264d479e152955"}, + expectPulled: []string{"sha256-ff66306c3a398c6e6b64d8f7acdd1aebc37fce76add3849f9d3f59274434234a"}, + pullsInFlight: 1, + expectPullingRemoved: "sha256-5f3ca9e4d82e32203c2fafb410dfae9c8047bc6ebe57d642fe264d479e152955", + checkedPullFile: "sha256-ff66306c3a398c6e6b64d8f7acdd1aebc37fce76add3849f9d3f59274434234a", + expectUpdated: true, + expectedPullRecord: kubeletconfiginternal.ImagePulledRecord{ + ImageRef: "testimageref-familiarwithorg", + CredentialMapping: map[string]kubeletconfiginternal.ImagePullCredentials{ + "secretorg/myimage": { + KubernetesServiceAccounts: []kubeletconfiginternal.ImagePullServiceAccount{ + {UID: "test-sa-uid", Namespace: "default", Name: "test-sa"}, + }, + }, + }, + }, + }, } for _, tt := range tests { diff --git a/pkg/kubelet/images/pullmanager/image_pull_policies.go b/pkg/kubelet/images/pullmanager/image_pull_policies.go index 2642275a3d580..be6cf14dfe240 100644 --- a/pkg/kubelet/images/pullmanager/image_pull_policies.go +++ b/pkg/kubelet/images/pullmanager/image_pull_policies.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - dockerref "github.com/distribution/reference" + imageref "github.com/distribution/reference" "k8s.io/apimachinery/pkg/util/sets" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" @@ -110,11 +110,19 @@ func (p *NeverVerifyAllowlistedImages) RequireCredentialVerificationForImage(ima } func (p *NeverVerifyAllowlistedImages) imageMatches(image string) bool { - if p.absoluteURLs.Has(image) { + noTagsName, err := trimImageTagDigest(image) + if err != nil { + // in a best-effort manner, we should still attempt to match with what + // we've got from the user + noTagsName = image + } + + if p.absoluteURLs.Has(noTagsName) { return true } + for _, prefix := range p.prefixes { - if strings.HasPrefix(image, prefix) { + if strings.HasPrefix(noTagsName, prefix) { return true } } @@ -155,12 +163,12 @@ func getAllowlistImagePattern(pattern string) (string, bool, error) { return "", false, fmt.Errorf("at least registry hostname is required") } } else { // not a wildcard - image, err := dockerref.ParseNormalizedNamed(trimmedPattern) + image, err := trimImageTagDigest(trimmedPattern) if err != nil { return "", false, fmt.Errorf("failed to parse as an image name: %w", err) } - if trimmedPattern != image.Name() { // image.Name() returns the image name without tag/digest + if trimmedPattern != image { return "", false, fmt.Errorf("neither tag nor digest is accepted in an image reference: %s", pattern) } @@ -169,3 +177,36 @@ func getAllowlistImagePattern(pattern string) (string, bool, error) { return trimmedPattern, true, nil } + +// trimImageTagDigest trims digest and tag from an image name +func trimImageTagDigest(image string) (string, error) { + imageParsed, err := imageref.Parse(image) + if err != nil { + return "", err + } + var tag, digest string + if tagged, ok := imageParsed.(imageref.Tagged); ok { + tag = tagged.Tag() + } + if digested, ok := imageParsed.(imageref.Digested); ok { + digest = digested.Digest().String() + } + + if len(digest) > 0 { + var foundDigest bool + image, foundDigest = strings.CutSuffix(image, "@"+digest) + if !foundDigest { + return "", fmt.Errorf("digest was not specified last, this does not appear to be a valid image spec: %q", image) + } + } + + if len(tag) > 0 { + var tagFound bool + image, tagFound = strings.CutSuffix(image, ":"+tag) + if !tagFound { + return "", fmt.Errorf("tag %q was not specified last (or before the digest), this does not appear to be a valid image spec: %q", tag, image) + } + } + + return image, nil +} diff --git a/pkg/kubelet/images/pullmanager/image_pull_policies_test.go b/pkg/kubelet/images/pullmanager/image_pull_policies_test.go index eb8af5ee0f611..d00c6116c8f77 100644 --- a/pkg/kubelet/images/pullmanager/image_pull_policies_test.go +++ b/pkg/kubelet/images/pullmanager/image_pull_policies_test.go @@ -40,7 +40,7 @@ func TestNeverVerifyPreloadedPullPolicy(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := NeverVerifyPreloadedPullPolicy()("test-image", tt.imageRecordsExist); got != tt.want { + if got := NeverVerifyPreloadedPullPolicy()("test-image:sometag", tt.imageRecordsExist); got != tt.want { t.Errorf("NeverVerifyPreloadedPullPolicy() = %v, want %v", got, tt.want) } }) @@ -48,9 +48,12 @@ func TestNeverVerifyPreloadedPullPolicy(t *testing.T) { } func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { + const basicTestImage = "test.io/test/test-image:tag" + tests := []struct { name string imageRecordsExist bool + inputImage string allowlist []string expectedAbsolutes int expectedWildcards int @@ -60,6 +63,7 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { { name: "there are no records about the image being pulled, not in allowlist", imageRecordsExist: false, + inputImage: basicTestImage, want: true, allowlist: []string{"test.io/test/image1", "test.io/test/image2", "test.io/test/image3"}, expectedAbsolutes: 3, @@ -67,6 +71,7 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { { name: "there are records about the image being pulled, not in allowlist", imageRecordsExist: true, + inputImage: basicTestImage, want: true, allowlist: []string{"test.io/test/image1", "test.io/test/image3", "test.io/test/image2", "test.io/test/image3"}, expectedAbsolutes: 3, @@ -74,6 +79,7 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { { name: "there are no records about the image being pulled, appears in allowlist", imageRecordsExist: false, + inputImage: basicTestImage, want: false, allowlist: []string{"test.io/test/image1", "test.io/test/image2", "test.io/test/test-image", "test.io/test/image3"}, expectedAbsolutes: 4, @@ -81,62 +87,74 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { { name: "there are records about the image being pulled, appears in allowlist", imageRecordsExist: true, + inputImage: basicTestImage, want: false, allowlist: []string{"test.io/test/image1", "test.io/test/image2", "test.io/test/test-image", "test.io/test/image3"}, expectedAbsolutes: 4, }, { - name: "invalid allowlist pattern - wildcard in the middle", - wantErr: true, - allowlist: []string{"image.repo/pokus*/imagename"}, + name: "invalid allowlist pattern - wildcard in the middle", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"image.repo/pokus*/imagename"}, }, { - name: "invalid allowlist pattern - trailing non-segment wildcard middle", - wantErr: true, - allowlist: []string{"image.repo/pokus*"}, + name: "invalid allowlist pattern - trailing non-segment wildcard middle", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"image.repo/pokus*"}, }, { - name: "invalid allowlist pattern - wildcard path segment in the middle", - wantErr: true, - allowlist: []string{"image.repo/*/imagename"}, + name: "invalid allowlist pattern - wildcard path segment in the middle", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"image.repo/*/imagename"}, }, { - name: "invalid allowlist pattern - only wildcard segment", - wantErr: true, - allowlist: []string{"/*"}, + name: "invalid allowlist pattern - only wildcard segment", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"/*"}, }, { - name: "invalid allowlist pattern - ends with a '/'", - wantErr: true, - allowlist: []string{"image.repo/"}, + name: "invalid allowlist pattern - ends with a '/'", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"image.repo/"}, }, { - name: "invalid allowlist pattern - empty", - wantErr: true, - allowlist: []string{""}, + name: "invalid allowlist pattern - empty", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{""}, }, { - name: "invalid allowlist pattern - asterisk", - wantErr: true, - allowlist: []string{"*"}, + name: "invalid allowlist pattern - asterisk", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"*"}, }, { - name: "invalid allowlist pattern - image with a tag", - wantErr: true, - allowlist: []string{"test.io/test/image1:tagged"}, + name: "invalid allowlist pattern - image with a tag", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"test.io/test/image1:tagged"}, }, { - name: "invalid allowlist pattern - image with a digest", - wantErr: true, - allowlist: []string{"test.io/test/image1@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd"}, + name: "invalid allowlist pattern - image with a digest", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"test.io/test/image1@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd"}, }, { - name: "invalid allowlist pattern - trailing whitespace", - wantErr: true, - allowlist: []string{"test.io/test/image1 "}, + name: "invalid allowlist pattern - trailing whitespace", + inputImage: basicTestImage, + wantErr: true, + allowlist: []string{"test.io/test/image1 "}, }, { name: "there are no records about the image being pulled, not in allowlist - different repo wildcard", + inputImage: basicTestImage, imageRecordsExist: false, want: true, allowlist: []string{"test.io/test/image1", "test.io/test/image2", "different.repo/test/*"}, @@ -145,6 +163,7 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { }, { name: "there are no records about the image being pulled, not in allowlist - matches org wildcard", + inputImage: basicTestImage, imageRecordsExist: false, want: false, allowlist: []string{"test.io/test/image1", "test.io/test/image2", "test.io/test/*"}, @@ -153,12 +172,53 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { }, { name: "there are no records about the image being pulled, not in allowlist - matches repo wildcard", + inputImage: basicTestImage, imageRecordsExist: false, want: false, allowlist: []string{"test.io/test/image1", "test.io/test/image2", "test.io/*"}, expectedAbsolutes: 2, expectedWildcards: 1, }, + { + name: "'familiar' image name with docker-disambiguated fqdn in the allowlist - not a match", + inputImage: "ubuntu", + imageRecordsExist: false, + want: true, + allowlist: []string{"docker.io/library/ubuntu"}, + expectedAbsolutes: 1, + }, + { + name: "'familiar' image name with docker-disambiguated wildcard in the allowlist - not a match", + inputImage: "ubuntu", + imageRecordsExist: false, + want: true, + allowlist: []string{"docker.io/library/*"}, + expectedWildcards: 1, + }, + { + name: "'familiar' image name with 'familiar' image matching it in the allowlist", + inputImage: "ubuntu", + imageRecordsExist: false, + want: false, + allowlist: []string{"ubuntu"}, + expectedAbsolutes: 1, + }, + { + name: "'familiar' image name with a tag with 'familiar' image matching it in the allowlist", + inputImage: "ubuntu:tagged", + imageRecordsExist: false, + want: false, + allowlist: []string{"ubuntu"}, + expectedAbsolutes: 1, + }, + { + name: "'familiar' image name with a digest with 'familiar' image matching it in the allowlist", + inputImage: "ubuntu@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + imageRecordsExist: false, + want: false, + allowlist: []string{"ubuntu"}, + expectedAbsolutes: 1, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -179,10 +239,131 @@ func TestNewNeverVerifyAllowListedPullPolicy(t *testing.T) { t.Errorf("expected %d of wildcard image URLs in the allowlist policy, got %d: %v", tt.expectedWildcards, len(policyEnforcer.prefixes), policyEnforcer.prefixes) } - got := policyEnforcer.RequireCredentialVerificationForImage("test.io/test/test-image", tt.imageRecordsExist) + got := policyEnforcer.RequireCredentialVerificationForImage(tt.inputImage, tt.imageRecordsExist) if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewNeverVerifyAllowListedPullPolicy() = %v, want %v", got, tt.want) } }) } } + +func TestTrimImageTagDigest(t *testing.T) { + tests := []struct { + name string + image string + want string + wantErr bool + }{ + { + name: "'familiar' image name, no tag or digest", + image: "myimage", + want: "myimage", + }, + { + name: "'familiar' image name with tag", + image: "myimage:latest", + want: "myimage", + }, + { + name: "'familiar' image name with digest", + image: "myimage@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "myimage", + }, + { + name: "'familiar' image name with tag and digest", + image: "myimage:v1@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "myimage", + }, + { + name: "single path segment, no tag", + image: "registry.io/myimage", + want: "registry.io/myimage", + }, + { + name: "single path segment with tag", + image: "registry.io/myimage:v1.0", + want: "registry.io/myimage", + }, + { + name: "single path segment with digest", + image: "registry.io/myimage@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "registry.io/myimage", + }, + { + name: "single path segment with tag and digest", + image: "registry.io/myimage:latest@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "registry.io/myimage", + }, + { + name: "domain and two path segments, no tag", + image: "registry.io/myorg/myimage", + want: "registry.io/myorg/myimage", + }, + { + name: "domain and two path segments with tag", + image: "registry.io/myorg/myimage:v2", + want: "registry.io/myorg/myimage", + }, + { + name: "domain and two path segments with digest", + image: "registry.io/myorg/myimage@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "registry.io/myorg/myimage", + }, + { + name: "domain and two path segments with tag and digest", + image: "registry.io/myorg/myimage:v2@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "registry.io/myorg/myimage", + }, + { + name: "deep path and tag", + image: "registry.io/a/b/c/myimage:latest", + want: "registry.io/a/b/c/myimage", + }, + { + name: "image name ends with slash", + image: "registry.io/myorg/", + wantErr: true, + }, + { + name: "last segment is only a tag", + image: "registry.io/myorg/:tag", + wantErr: true, + }, + { + name: "last segment is only a digest", + image: "registry.io/myorg/@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + wantErr: true, + }, + { + name: "domain with port and tag", + image: "localhost:5000/myimage:v1", + want: "localhost:5000/myimage", + }, + { + name: "domain with port, org, and tag", + image: "myregistry:5000/myorg/myimage:v1", + want: "myregistry:5000/myorg/myimage", + }, + { + name: "multiple occurrences of tag", + image: "docker.io/taghere/image:taghere", + want: "docker.io/taghere/image", + }, + { + name: "multiple occurrences of digest", + image: "registry.io/38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd/myorg/image@sha256:38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd", + want: "registry.io/38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd/myorg/image", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := trimImageTagDigest(tt.image) + if (err != nil) != tt.wantErr { + t.Fatalf("removeTagDigest(%q) error = %v, wantErr %v", tt.image, err, tt.wantErr) + } + if !tt.wantErr && got != tt.want { + t.Errorf("removeTagDigest(%q) = %q, want %q", tt.image, got, tt.want) + } + }) + } +} diff --git a/pkg/kubelet/images/pullmanager/interfaces.go b/pkg/kubelet/images/pullmanager/interfaces.go index 7d8812bc735ad..9ec680a1e3910 100644 --- a/pkg/kubelet/images/pullmanager/interfaces.go +++ b/pkg/kubelet/images/pullmanager/interfaces.go @@ -20,6 +20,7 @@ import ( "context" "time" + "k8s.io/klog/v2" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" ) @@ -39,7 +40,7 @@ type ImagePullManager interface { // RecordPullIntent() must match exactly one call of RecordImagePulled()/RecordImagePullFailed(). // // `image` is the content of the pod's container `image` field. - RecordPullIntent(image string) error + RecordPullIntent(logger klog.Logger, image string) error // RecordImagePulled writes a record of an image being successfully pulled // with ImagePullCredentials. // @@ -96,9 +97,9 @@ type PullRecordsAccessor interface { // for the given image. ImagePullIntentExists(image string) (bool, error) // WriteImagePullIntent writes a an intent record for the image into the database - WriteImagePullIntent(image string) error + WriteImagePullIntent(logger klog.Logger, image string) error // DeleteImagePullIntent removes an `image` intent record from the database - DeleteImagePullIntent(image string) error + DeleteImagePullIntent(logger klog.Logger, image string) error // ListImagePulledRecords lists the database ImagePulledRecords. // Records that cannot be decoded will be ignored. @@ -113,8 +114,8 @@ type PullRecordsAccessor interface { // it returns a exists=true with err equal to the decoding error. GetImagePulledRecord(imageRef string) (record *kubeletconfiginternal.ImagePulledRecord, exists bool, err error) // WriteImagePulledRecord writes an ImagePulledRecord into the database. - WriteImagePulledRecord(record *kubeletconfiginternal.ImagePulledRecord) error + WriteImagePulledRecord(logger klog.Logger, record *kubeletconfiginternal.ImagePulledRecord) error // DeleteImagePulledRecord removes an ImagePulledRecord for `imageRef` from the // database. - DeleteImagePulledRecord(imageRef string) error + DeleteImagePulledRecord(logger klog.Logger, imageRef string) error } diff --git a/pkg/kubelet/images/pullmanager/mem_pullrecords.go b/pkg/kubelet/images/pullmanager/mem_pullrecords.go index 3ed9f7fb12017..496623036b634 100644 --- a/pkg/kubelet/images/pullmanager/mem_pullrecords.go +++ b/pkg/kubelet/images/pullmanager/mem_pullrecords.go @@ -98,7 +98,7 @@ type cachedPullRecordsAccessor struct { pulledRecords *lruCache[string, kubeletconfiginternal.ImagePulledRecord] } -func NewCachedPullRecordsAccessor(delegate PullRecordsAccessor, intentsCacheSize, pulledRecordsCacheSize, stripedLocksSize int32) PullRecordsAccessor { +func NewCachedPullRecordsAccessor(logger klog.Logger, delegate PullRecordsAccessor, intentsCacheSize, pulledRecordsCacheSize, stripedLocksSize int32) PullRecordsAccessor { intentsCacheSize = min(intentsCacheSize, 1024) pulledRecordsCacheSize = min(pulledRecordsCacheSize, 2000) @@ -113,11 +113,11 @@ func NewCachedPullRecordsAccessor(delegate PullRecordsAccessor, intentsCacheSize // warm our caches and set authoritative _, err := c.ListImagePullIntents() if err != nil { - klog.InfoS("there was an error initializing the image pull intents cache, the cache will work in a non-authoritative mode until the intents are listed successfully", "error", err) + logger.Info("there was an error initializing the image pull intents cache, the cache will work in a non-authoritative mode until the intents are listed successfully", "error", err) } _, err = c.ListImagePulledRecords() if err != nil { - klog.InfoS("there was an error initializing the image pulled records cache, the cache will work in a non-authoritative mode until the pulled records are listed successfully", "error", err) + logger.Info("there was an error initializing the image pulled records cache, the cache will work in a non-authoritative mode until the pulled records are listed successfully", "error", err) } return NewMeteringRecordsAccessor(c, inMemIntentsPercent, inMemPulledRecordsPercent) } @@ -160,11 +160,11 @@ func (c *cachedPullRecordsAccessor) ImagePullIntentExists(image string) (bool, e return exists, err } -func (c *cachedPullRecordsAccessor) WriteImagePullIntent(image string) error { +func (c *cachedPullRecordsAccessor) WriteImagePullIntent(logger klog.Logger, image string) error { c.intentsLocks.Lock(image) defer c.intentsLocks.Unlock(image) - if err := c.delegate.WriteImagePullIntent(image); err != nil { + if err := c.delegate.WriteImagePullIntent(logger, image); err != nil { return err } c.intents.Set(image, &kubeletconfiginternal.ImagePullIntent{ @@ -174,11 +174,11 @@ func (c *cachedPullRecordsAccessor) WriteImagePullIntent(image string) error { return nil } -func (c *cachedPullRecordsAccessor) DeleteImagePullIntent(image string) error { +func (c *cachedPullRecordsAccessor) DeleteImagePullIntent(logger klog.Logger, image string) error { c.intentsLocks.Lock(image) defer c.intentsLocks.Unlock(image) - if err := c.delegate.DeleteImagePullIntent(image); err != nil { + if err := c.delegate.DeleteImagePullIntent(logger, image); err != nil { return err } c.intents.Delete(image) @@ -223,35 +223,35 @@ func (c *cachedPullRecordsAccessor) GetImagePulledRecord(imageRef string) (*kube return pulledRecord, exists, err } -func (c *cachedPullRecordsAccessor) WriteImagePulledRecord(record *kubeletconfiginternal.ImagePulledRecord) error { +func (c *cachedPullRecordsAccessor) WriteImagePulledRecord(logger klog.Logger, record *kubeletconfiginternal.ImagePulledRecord) error { c.pulledRecordsLocks.Lock(record.ImageRef) defer c.pulledRecordsLocks.Unlock(record.ImageRef) - if err := c.delegate.WriteImagePulledRecord(record); err != nil { + if err := c.delegate.WriteImagePulledRecord(logger, record); err != nil { return err } c.pulledRecords.Set(record.ImageRef, record) return nil } -func (c *cachedPullRecordsAccessor) DeleteImagePulledRecord(imageRef string) error { +func (c *cachedPullRecordsAccessor) DeleteImagePulledRecord(logger klog.Logger, imageRef string) error { c.pulledRecordsLocks.Lock(imageRef) defer c.pulledRecordsLocks.Unlock(imageRef) - if err := c.delegate.DeleteImagePulledRecord(imageRef); err != nil { + if err := c.delegate.DeleteImagePulledRecord(logger, imageRef); err != nil { return err } c.pulledRecords.Delete(imageRef) return nil } -func (f *cachedPullRecordsAccessor) intentsSize() (uint, error) { - intentsUsage := f.intents.Len() * 100 / f.intents.maxSize +func (c *cachedPullRecordsAccessor) intentsSize() (uint, error) { + intentsUsage := c.intents.Len() * 100 / c.intents.maxSize return uint(intentsUsage), nil } -func (f *cachedPullRecordsAccessor) pulledRecordsSize() (uint, error) { - pulledRecordsUsage := f.pulledRecords.Len() * 100 / f.pulledRecords.maxSize +func (c *cachedPullRecordsAccessor) pulledRecordsSize() (uint, error) { + pulledRecordsUsage := c.pulledRecords.Len() * 100 / c.pulledRecords.maxSize return uint(pulledRecordsUsage), nil } diff --git a/pkg/kubelet/images/pullmanager/mem_pullrecords_test.go b/pkg/kubelet/images/pullmanager/mem_pullrecords_test.go index 48fc411afbd20..9ca68180a21b1 100644 --- a/pkg/kubelet/images/pullmanager/mem_pullrecords_test.go +++ b/pkg/kubelet/images/pullmanager/mem_pullrecords_test.go @@ -25,7 +25,9 @@ import ( "strings" "testing" + "k8s.io/klog/v2" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" + "k8s.io/kubernetes/test/utils/ktesting" ) type testPullRecordsAccessor struct { @@ -75,7 +77,7 @@ func (a *testPullRecordsAccessor) GetImagePulledRecord(imageRef string) (*kubele return record, exists, nil } -func (a *testPullRecordsAccessor) WriteImagePullIntent(image string) error { +func (a *testPullRecordsAccessor) WriteImagePullIntent(logger klog.Logger, image string) error { if a.nextGetWriteError != nil { return a.nextGetWriteError } @@ -85,7 +87,7 @@ func (a *testPullRecordsAccessor) WriteImagePullIntent(image string) error { } return nil } -func (a *testPullRecordsAccessor) WriteImagePulledRecord(rec *kubeletconfiginternal.ImagePulledRecord) error { +func (a *testPullRecordsAccessor) WriteImagePulledRecord(logger klog.Logger, rec *kubeletconfiginternal.ImagePulledRecord) error { if a.nextGetWriteError != nil { return a.nextGetWriteError } @@ -94,7 +96,7 @@ func (a *testPullRecordsAccessor) WriteImagePulledRecord(rec *kubeletconfiginter return nil } -func (a *testPullRecordsAccessor) DeleteImagePullIntent(image string) error { +func (a *testPullRecordsAccessor) DeleteImagePullIntent(logger klog.Logger, image string) error { if a.nextGetWriteError != nil { return a.nextGetWriteError } @@ -102,7 +104,7 @@ func (a *testPullRecordsAccessor) DeleteImagePullIntent(image string) error { delete(a.intents, image) return nil } -func (a *testPullRecordsAccessor) DeleteImagePulledRecord(imageRef string) error { +func (a *testPullRecordsAccessor) DeleteImagePulledRecord(logger klog.Logger, imageRef string) error { if a.nextGetWriteError != nil { return a.nextGetWriteError } @@ -171,27 +173,28 @@ func (a *recordingPullRecordsAccessor) GetImagePulledRecord(imageRef string) (*k return a.delegate.GetImagePulledRecord(imageRef) } -func (a *recordingPullRecordsAccessor) WriteImagePullIntent(image string) error { +func (a *recordingPullRecordsAccessor) WriteImagePullIntent(logger klog.Logger, image string) error { a.methodsCalled = append(a.methodsCalled, "WriteImagePullIntent") - return a.delegate.WriteImagePullIntent(image) + return a.delegate.WriteImagePullIntent(logger, image) } -func (a *recordingPullRecordsAccessor) WriteImagePulledRecord(rec *kubeletconfiginternal.ImagePulledRecord) error { +func (a *recordingPullRecordsAccessor) WriteImagePulledRecord(logger klog.Logger, rec *kubeletconfiginternal.ImagePulledRecord) error { a.methodsCalled = append(a.methodsCalled, "WriteImagePulledRecord") - return a.delegate.WriteImagePulledRecord(rec) + return a.delegate.WriteImagePulledRecord(logger, rec) } -func (a *recordingPullRecordsAccessor) DeleteImagePullIntent(image string) error { +func (a *recordingPullRecordsAccessor) DeleteImagePullIntent(logger klog.Logger, image string) error { a.methodsCalled = append(a.methodsCalled, "DeleteImagePullIntent") - return a.delegate.DeleteImagePullIntent(image) + return a.delegate.DeleteImagePullIntent(logger, image) } -func (a *recordingPullRecordsAccessor) DeleteImagePulledRecord(imageRef string) error { +func (a *recordingPullRecordsAccessor) DeleteImagePulledRecord(logger klog.Logger, imageRef string) error { a.methodsCalled = append(a.methodsCalled, "DeleteImagePulledRecord") - return a.delegate.DeleteImagePulledRecord(imageRef) + return a.delegate.DeleteImagePulledRecord(logger, imageRef) } func TestNewCachedPullRecordsAccessor(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) manyPullIntents := generateTestPullIntents(51) slices.SortFunc(manyPullIntents, pullIntentsCmp) @@ -296,7 +299,7 @@ func TestNewCachedPullRecordsAccessor(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotAccessor := NewCachedPullRecordsAccessor(tt.delegate, 50, 100, 10) + gotAccessor := NewCachedPullRecordsAccessor(logger, tt.delegate, 50, 100, 10) gotMeteredAccessor, ok := gotAccessor.(*meteringRecordsAccessor) if !ok { t.Fatalf("the tested accessor must be a metered records accessor, got %T", gotAccessor) @@ -626,6 +629,7 @@ func Test_cachedPullRecordsAccessor_ImagePullIntentExists(t *testing.T) { } func Test_cachedPullRecordsAccessor_WriteImagePullIntent(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) justEnoughIntents := generateTestPullIntents(100) tests := []struct { @@ -731,7 +735,7 @@ func Test_cachedPullRecordsAccessor_WriteImagePullIntent(t *testing.T) { } c.intents.authoritative.Store(tt.initAuthoritative) - if err := c.WriteImagePullIntent(tt.inputImage); !cmpErrorStrings(err, tt.wantErr) { + if err := c.WriteImagePullIntent(logger, tt.inputImage); !cmpErrorStrings(err, tt.wantErr) { t.Errorf("cachedPullRecordsAccessor.WriteImagePullIntent() error = %v, wantErr %v", err, tt.wantErr) } @@ -751,6 +755,7 @@ func Test_cachedPullRecordsAccessor_WriteImagePullIntent(t *testing.T) { } func Test_cachedPullRecordsAccessor_WriteImagePulledRecord(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) justEnoughPulledRecords := generateTestPulledRecords(100) tests := []struct { @@ -898,7 +903,7 @@ func Test_cachedPullRecordsAccessor_WriteImagePulledRecord(t *testing.T) { } c.pulledRecords.authoritative.Store(tt.initAuthoritative) - if err := c.WriteImagePulledRecord(tt.inputRecord); !cmpErrorStrings(err, tt.wantErr) { + if err := c.WriteImagePulledRecord(logger, tt.inputRecord); !cmpErrorStrings(err, tt.wantErr) { t.Errorf("cachedPullRecordsAccessor.WriteImagePulledRecord() error = %v, wantErr %v", err, tt.wantErr) } @@ -918,6 +923,7 @@ func Test_cachedPullRecordsAccessor_WriteImagePulledRecord(t *testing.T) { } func Test_cachedPullRecordsAccessor_DeleteImagePullIntent(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string delegate *testPullRecordsAccessor @@ -960,7 +966,7 @@ func Test_cachedPullRecordsAccessor_DeleteImagePullIntent(t *testing.T) { } c.intents.authoritative.Store(initAuthoritative) - if err := c.DeleteImagePullIntent(tt.inputImage); !cmpErrorStrings(err, tt.wantErr) { + if err := c.DeleteImagePullIntent(logger, tt.inputImage); !cmpErrorStrings(err, tt.wantErr) { t.Errorf("cachedPullRecordsAccessor.DeleteImagePullIntent() error = %v, wantErr %v", err, tt.wantErr) } @@ -986,6 +992,7 @@ func Test_cachedPullRecordsAccessor_DeleteImagePullIntent(t *testing.T) { } func Test_cachedPullRecordsAccessor_DeleteImagePulledRecord(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string delegate *testPullRecordsAccessor @@ -1028,7 +1035,7 @@ func Test_cachedPullRecordsAccessor_DeleteImagePulledRecord(t *testing.T) { } c.pulledRecords.authoritative.Store(initAuthoritative) - if err := c.DeleteImagePulledRecord(tt.imageRef); !cmpErrorStrings(err, tt.wantErr) { + if err := c.DeleteImagePulledRecord(logger, tt.imageRef); !cmpErrorStrings(err, tt.wantErr) { t.Errorf("cachedPullRecordsAccessor.DeleteImagePulledRecord() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/pkg/kubelet/images/pullmanager/metrics.go b/pkg/kubelet/images/pullmanager/metrics.go index 7be9812210f0d..adf16a44e986b 100644 --- a/pkg/kubelet/images/pullmanager/metrics.go +++ b/pkg/kubelet/images/pullmanager/metrics.go @@ -24,8 +24,6 @@ import ( kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics" ) -const imageManagerSubsystem = kubeletmetrics.KubeletSubsystem + "_imagemanager" - type mustAttemptImagePullResult string const ( @@ -38,40 +36,40 @@ const ( var ( fsPullIntentsSize = metrics.NewGauge( &metrics.GaugeOpts{ - Subsystem: imageManagerSubsystem, - Name: "ondisk_pullintents", + Subsystem: kubeletmetrics.KubeletSubsystem, + Name: "imagemanager_ondisk_pullintents", Help: "Number of ImagePullIntents stored on disk.", StabilityLevel: metrics.ALPHA, }, ) fsPulledRecordsSize = metrics.NewGauge( &metrics.GaugeOpts{ - Subsystem: imageManagerSubsystem, - Name: "ondisk_pulledrecords", + Subsystem: kubeletmetrics.KubeletSubsystem, + Name: "imagemanager_ondisk_pulledrecords", Help: "Number of ImagePulledRecords stored on disk.", StabilityLevel: metrics.ALPHA, }, ) inMemIntentsPercent = metrics.NewGauge( &metrics.GaugeOpts{ - Subsystem: imageManagerSubsystem, - Name: "inmemory_pullintents_usage_percent", + Subsystem: kubeletmetrics.KubeletSubsystem, + Name: "imagemanager_inmemory_pullintents_usage_percent", Help: "The ImagePullIntents in-memory cache usage in percent.", StabilityLevel: metrics.ALPHA, }, ) inMemPulledRecordsPercent = metrics.NewGauge( &metrics.GaugeOpts{ - Subsystem: imageManagerSubsystem, - Name: "inmemory_pulledrecords_usage_percent", + Subsystem: kubeletmetrics.KubeletSubsystem, + Name: "imagemanager_inmemory_pulledrecords_usage_percent", Help: "The ImagePulledRecords in-memory cache usage in percent.", StabilityLevel: metrics.ALPHA, }, ) mustPullChecksTotal = metrics.NewCounterVec( &metrics.CounterOpts{ - Subsystem: imageManagerSubsystem, - Name: "image_mustpull_checks_total", + Subsystem: kubeletmetrics.KubeletSubsystem, + Name: "imagemanager_image_mustpull_checks_total", Help: "Counter for how many times kubelet checked whether credentials need to be re-verified to access an image", StabilityLevel: metrics.ALPHA, }, @@ -109,51 +107,51 @@ func NewMeteringRecordsAccessor(pullRecordsAccessor sizeExposedPullRecordsAccess } } -func (m *meteringRecordsAccessor) WriteImagePullIntent(image string) error { - if err := m.sizeExposedPullRecordsAccessor.WriteImagePullIntent(image); err != nil { +func (m *meteringRecordsAccessor) WriteImagePullIntent(logger klog.Logger, image string) error { + if err := m.sizeExposedPullRecordsAccessor.WriteImagePullIntent(logger, image); err != nil { return err } - m.recordIntentsSize() + m.recordIntentsSize(logger) return nil } -func (m *meteringRecordsAccessor) DeleteImagePullIntent(image string) error { - if err := m.sizeExposedPullRecordsAccessor.DeleteImagePullIntent(image); err != nil { +func (m *meteringRecordsAccessor) DeleteImagePullIntent(logger klog.Logger, image string) error { + if err := m.sizeExposedPullRecordsAccessor.DeleteImagePullIntent(logger, image); err != nil { return err } - m.recordIntentsSize() + m.recordIntentsSize(logger) return nil } -func (m *meteringRecordsAccessor) WriteImagePulledRecord(record *kubeletconfiginternal.ImagePulledRecord) error { - if err := m.sizeExposedPullRecordsAccessor.WriteImagePulledRecord(record); err != nil { +func (m *meteringRecordsAccessor) WriteImagePulledRecord(logger klog.Logger, record *kubeletconfiginternal.ImagePulledRecord) error { + if err := m.sizeExposedPullRecordsAccessor.WriteImagePulledRecord(logger, record); err != nil { return err } - m.recordPulledRecordsSize() + m.recordPulledRecordsSize(logger) return nil } -func (m *meteringRecordsAccessor) DeleteImagePulledRecord(imageRef string) error { - if err := m.sizeExposedPullRecordsAccessor.DeleteImagePulledRecord(imageRef); err != nil { +func (m *meteringRecordsAccessor) DeleteImagePulledRecord(logger klog.Logger, imageRef string) error { + if err := m.sizeExposedPullRecordsAccessor.DeleteImagePulledRecord(logger, imageRef); err != nil { return err } - m.recordPulledRecordsSize() + m.recordPulledRecordsSize(logger) return nil } -func (m *meteringRecordsAccessor) recordIntentsSize() { +func (m *meteringRecordsAccessor) recordIntentsSize(logger klog.Logger) { intentsSize, err := m.sizeExposedPullRecordsAccessor.intentsSize() if err != nil { - klog.V(4).ErrorS(err, "failed to read number of ImagePullIntents, can't update metric", "metricName", m.intentsSize.Name) + logger.V(4).Info("failed to read number of ImagePullIntents, can't update metric", "metricName", m.intentsSize.Name, "error", err) return } m.intentsSize.Set(float64(intentsSize)) } -func (m *meteringRecordsAccessor) recordPulledRecordsSize() { +func (m *meteringRecordsAccessor) recordPulledRecordsSize(logger klog.Logger) { pulledRecordsSize, err := m.sizeExposedPullRecordsAccessor.pulledRecordsSize() if err != nil { - klog.V(4).ErrorS(err, "failed to read number of ImagePulledRecords, can't update metric", "metricName", m.pulledRecordsSize.Name) + logger.V(4).Info("failed to read number of ImagePulledRecords, can't update metric", "metricName", m.pulledRecordsSize.Name, "error", err) return } m.pulledRecordsSize.Set(float64(pulledRecordsSize)) diff --git a/pkg/kubelet/images/pullmanager/metrics_test.go b/pkg/kubelet/images/pullmanager/metrics_test.go index 51593292e8414..3524bddf7dd96 100644 --- a/pkg/kubelet/images/pullmanager/metrics_test.go +++ b/pkg/kubelet/images/pullmanager/metrics_test.go @@ -36,39 +36,40 @@ import ( ) func TestFSPullRecordsMetrics(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tempDir := t.TempDir() legacyregistry.Reset() defer legacyregistry.Reset() - fsAccessor, err := NewFSPullRecordsAccessor(tempDir) + fsAccessor, err := NewFSPullRecordsAccessor(logger, tempDir) if err != nil { t.Fatal(err) } const pullIntentsCacheSize, pulledRecordsCacheSize, stripedSetLocksSize int32 = 5, 10, 1 - inMemoryAccessor := NewCachedPullRecordsAccessor(fsAccessor, pullIntentsCacheSize, pulledRecordsCacheSize, stripedSetLocksSize) + inMemoryAccessor := NewCachedPullRecordsAccessor(logger, fsAccessor, pullIntentsCacheSize, pulledRecordsCacheSize, stripedSetLocksSize) cmpFSIntents(t, 0) cmpMemIntents(t, 0) - require.NoError(t, inMemoryAccessor.WriteImagePullIntent("test-image:latest")) + require.NoError(t, inMemoryAccessor.WriteImagePullIntent(logger, "test-image:latest")) cmpFSIntents(t, 1) cmpMemIntents(t, 20) // Test that writing the same record does not increase the count - require.NoError(t, inMemoryAccessor.WriteImagePullIntent("test-image:latest")) - require.NoError(t, inMemoryAccessor.WriteImagePullIntent("test-image:latest")) + require.NoError(t, inMemoryAccessor.WriteImagePullIntent(logger, "test-image:latest")) + require.NoError(t, inMemoryAccessor.WriteImagePullIntent(logger, "test-image:latest")) cmpFSIntents(t, 1) cmpMemIntents(t, 20) // Test adding more records - require.NoError(t, inMemoryAccessor.WriteImagePullIntent("test-image:v1")) - require.NoError(t, inMemoryAccessor.WriteImagePullIntent("test-image:v1.1")) + require.NoError(t, inMemoryAccessor.WriteImagePullIntent(logger, "test-image:v1")) + require.NoError(t, inMemoryAccessor.WriteImagePullIntent(logger, "test-image:v1.1")) cmpFSIntents(t, 3) cmpMemIntents(t, 60) cmpFSPulledRecords(t, 0) cmpMemPulledRecords(t, 0) - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: "test-image-latest-ref", LastUpdatedTime: metav1.NewTime(time.Now()), })) @@ -76,11 +77,11 @@ func TestFSPullRecordsMetrics(t *testing.T) { cmpMemPulledRecords(t, 10) // Test that writing the same record does not increase the count - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: "test-image-latest-ref", LastUpdatedTime: metav1.NewTime(time.Now()), })) - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: "test-image-latest-ref", LastUpdatedTime: metav1.NewTime(time.Now()), })) @@ -88,15 +89,15 @@ func TestFSPullRecordsMetrics(t *testing.T) { cmpMemPulledRecords(t, 10) // Test adding more records - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: "test-image-v1-ref", LastUpdatedTime: metav1.NewTime(time.Now()), })) - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: "test-image-v1.1-ref", LastUpdatedTime: metav1.NewTime(time.Now()), })) - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: "test-image-v1.2-ref", LastUpdatedTime: metav1.NewTime(time.Now()), })) @@ -108,17 +109,17 @@ func TestFSPullRecordsMetrics(t *testing.T) { cmpMemIntents(t, 60) // Test deletions - require.NoError(t, inMemoryAccessor.DeleteImagePullIntent("test-image:latest")) + require.NoError(t, inMemoryAccessor.DeleteImagePullIntent(logger, "test-image:latest")) cmpFSIntents(t, 2) cmpMemIntents(t, 40) - require.NoError(t, inMemoryAccessor.DeleteImagePullIntent("test-image:latest")) - require.NoError(t, inMemoryAccessor.DeleteImagePullIntent("test-image:latest")) + require.NoError(t, inMemoryAccessor.DeleteImagePullIntent(logger, "test-image:latest")) + require.NoError(t, inMemoryAccessor.DeleteImagePullIntent(logger, "test-image:latest")) cmpFSIntents(t, 2) cmpMemIntents(t, 40) - require.NoError(t, inMemoryAccessor.DeleteImagePullIntent("test-image:v1")) - require.NoError(t, inMemoryAccessor.DeleteImagePullIntent("test-image:v1.1")) + require.NoError(t, inMemoryAccessor.DeleteImagePullIntent(logger, "test-image:v1")) + require.NoError(t, inMemoryAccessor.DeleteImagePullIntent(logger, "test-image:v1.1")) cmpFSIntents(t, 0) cmpMemIntents(t, 0) @@ -127,30 +128,30 @@ func TestFSPullRecordsMetrics(t *testing.T) { cmpMemPulledRecords(t, 40) // Test image pulled record deletions - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v1.1-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v1.1-ref")) cmpFSPulledRecords(t, 3) cmpMemPulledRecords(t, 30) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v1.1-ref")) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v1.1-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v1.1-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v1.1-ref")) cmpFSPulledRecords(t, 3) cmpMemPulledRecords(t, 30) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v1.2-ref")) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v1-ref")) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-latest-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v1.2-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v1-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-latest-ref")) cmpFSPulledRecords(t, 0) cmpMemPulledRecords(t, 0) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v1-ref")) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-latest-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v1-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-latest-ref")) cmpFSPulledRecords(t, 0) cmpMemPulledRecords(t, 0) // test exceeding memory cache sizes for i := range 20 { - require.NoError(t, inMemoryAccessor.WriteImagePullIntent(fmt.Sprintf("test-image-%d:latest", i))) - require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(&kubeletconfig.ImagePulledRecord{ + require.NoError(t, inMemoryAccessor.WriteImagePullIntent(logger, fmt.Sprintf("test-image-%d:latest", i))) + require.NoError(t, inMemoryAccessor.WriteImagePulledRecord(logger, &kubeletconfig.ImagePulledRecord{ ImageRef: fmt.Sprintf("test-image-v%d-ref", i), LastUpdatedTime: metav1.NewTime(time.Now()), })) @@ -161,25 +162,25 @@ func TestFSPullRecordsMetrics(t *testing.T) { cmpMemPulledRecords(t, 100) // test removing some of the latest records from the cache - require.NoError(t, inMemoryAccessor.DeleteImagePullIntent("test-image-19:latest")) + require.NoError(t, inMemoryAccessor.DeleteImagePullIntent(logger, "test-image-19:latest")) cmpFSIntents(t, 19) cmpMemIntents(t, 80) - require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord("test-image-v19-ref")) + require.NoError(t, inMemoryAccessor.DeleteImagePulledRecord(logger, "test-image-v19-ref")) cmpFSPulledRecords(t, 19) cmpMemPulledRecords(t, 90) } func TestMustAttemptPullMetrics(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) + logger, ctx := ktesting.NewTestContext(t) tempDir := t.TempDir() pulledDir := filepath.Join(tempDir, "image_manager", "pulled") legacyregistry.Reset() defer legacyregistry.Reset() - fsAccessor, err := NewFSPullRecordsAccessor(tempDir) + fsAccessor, err := NewFSPullRecordsAccessor(logger, tempDir) if err != nil { t.Fatal(err) } diff --git a/pkg/kubelet/images/pullmanager/noop_pull_manager.go b/pkg/kubelet/images/pullmanager/noop_pull_manager.go index be9505824aa57..db9469098126b 100644 --- a/pkg/kubelet/images/pullmanager/noop_pull_manager.go +++ b/pkg/kubelet/images/pullmanager/noop_pull_manager.go @@ -20,6 +20,7 @@ import ( "context" "time" + "k8s.io/klog/v2" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" ) @@ -27,7 +28,7 @@ var _ ImagePullManager = &NoopImagePullManager{} type NoopImagePullManager struct{} -func (m *NoopImagePullManager) RecordPullIntent(string) error { return nil } +func (m *NoopImagePullManager) RecordPullIntent(klog.Logger, string) error { return nil } func (m *NoopImagePullManager) RecordImagePulled(context.Context, string, string, *kubeletconfiginternal.ImagePullCredentials) { } func (m *NoopImagePullManager) RecordImagePullFailed(context.Context, string) {} diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-5f3ca9e4d82e32203c2fafb410dfae9c8047bc6ebe57d642fe264d479e152955 b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-5f3ca9e4d82e32203c2fafb410dfae9c8047bc6ebe57d642fe264d479e152955 new file mode 100644 index 0000000000000..f79faafc4dbd8 --- /dev/null +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-5f3ca9e4d82e32203c2fafb410dfae9c8047bc6ebe57d642fe264d479e152955 @@ -0,0 +1 @@ +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"secretorg/myimage:customtag"} diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-7ffc99ef20589dc844b202c4cba501d1aaf06d1481935aa1e160029afd639847 b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-7ffc99ef20589dc844b202c4cba501d1aaf06d1481935aa1e160029afd639847 new file mode 100644 index 0000000000000..d03b469b77bf6 --- /dev/null +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-7ffc99ef20589dc844b202c4cba501d1aaf06d1481935aa1e160029afd639847 @@ -0,0 +1 @@ +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"testing@sha256:f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af"} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 42a399da40384..e6b1a893f9851 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -43,13 +43,13 @@ import ( "k8s.io/client-go/informers" ndf "k8s.io/component-helpers/nodedeclaredfeatures" - ndffeatures "k8s.io/component-helpers/nodedeclaredfeatures/features" "k8s.io/mount-utils" apiequality "k8s.io/apimachinery/pkg/api/equality" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration" utilfs "k8s.io/kubernetes/pkg/util/filesystem" + utilpod "k8s.io/kubernetes/pkg/util/pod" netutils "k8s.io/utils/net" "k8s.io/utils/ptr" @@ -63,6 +63,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" versionutil "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/server/dynamiccertificates" "k8s.io/apiserver/pkg/server/flagz" utilfeature "k8s.io/apiserver/pkg/util/feature" coreinformersv1 "k8s.io/client-go/informers/core/v1" @@ -71,6 +72,7 @@ import ( corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" + certutil "k8s.io/client-go/util/cert" "k8s.io/client-go/util/certificate" "k8s.io/client-go/util/flowcontrol" cloudprovider "k8s.io/cloud-provider" @@ -89,6 +91,7 @@ import ( kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/apis/config/v1beta1" "k8s.io/kubernetes/pkg/kubelet/apis/podresources" + "k8s.io/kubernetes/pkg/kubelet/apis/pods" "k8s.io/kubernetes/pkg/kubelet/cadvisor" kubeletcertificate "k8s.io/kubernetes/pkg/kubelet/certificate" "k8s.io/kubernetes/pkg/kubelet/clustertrustbundle" @@ -292,11 +295,11 @@ func getContainerEtcHostsPath() string { // SyncHandler is an interface implemented by Kubelet, for testability type SyncHandler interface { - HandlePodAdditions(pods []*v1.Pod) - HandlePodUpdates(pods []*v1.Pod) - HandlePodRemoves(pods []*v1.Pod) - HandlePodReconcile(pods []*v1.Pod) - HandlePodSyncs(pods []*v1.Pod) + HandlePodAdditions(ctx context.Context, pods []*v1.Pod) + HandlePodUpdates(ctx context.Context, pods []*v1.Pod) + HandlePodRemoves(ctx context.Context, pods []*v1.Pod) + HandlePodReconcile(ctx context.Context, pods []*v1.Pod) + HandlePodSyncs(ctx context.Context, pods []*v1.Pod) HandlePodCleanups(ctx context.Context) error } @@ -307,11 +310,12 @@ type Option func(*Kubelet) type Bootstrap interface { GetConfiguration() kubeletconfiginternal.KubeletConfiguration BirthCry() - StartGarbageCollection() - ListenAndServe(ctx context.Context, kubeCfg *kubeletconfiginternal.KubeletConfiguration, tlsOptions *server.TLSOptions, auth server.AuthInterface, tp trace.TracerProvider) + StartGarbageCollection(ctx context.Context) + ListenAndServe(ctx context.Context, kubeCfg *kubeletconfiginternal.KubeletConfiguration, tlsConfig *tls.Config, auth server.AuthInterface, tp trace.TracerProvider) ListenAndServeReadOnly(ctx context.Context, address net.IP, port uint, tp trace.TracerProvider) ListenAndServePodResources(ctx context.Context) - Run(<-chan kubetypes.PodUpdate) + ListenAndServePods(ctx context.Context) + Run(ctx context.Context, updates <-chan kubetypes.PodUpdate) } // Dependencies is a bin for things we might consider "injected dependencies" -- objects constructed @@ -335,12 +339,13 @@ type Dependencies struct { OSInterface kubecontainer.OSInterface PodConfig *config.PodConfig ProbeManager prober.Manager - Recorder record.EventRecorder + Recorder record.EventRecorderLogger Subpather subpath.Interface TracerProvider trace.TracerProvider VolumePlugins []volume.VolumePlugin DynamicPluginProber volume.DynamicPluginProber TLSOptions *server.TLSOptions + TLSConfig *tls.Config RemoteRuntimeService internalapi.RuntimeService RemoteImageService internalapi.ImageManagerService PodStartupLatencyTracker util.PodStartupLatencyTracker @@ -373,7 +378,8 @@ func newCrashLoopBackOff(kubeCfg *kubeletconfiginternal.KubeletConfiguration) (t // makePodSourceConfig creates a config.PodConfig from the given // KubeletConfiguration or returns an error. -func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, nodeName types.NodeName, nodeHasSynced func() bool) (*config.PodConfig, error) { +func makePodSourceConfig(ctx context.Context, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, nodeName types.NodeName, nodeHasSynced func() bool) (*config.PodConfig, error) { + logger := klog.FromContext(ctx) manifestURLHeader := make(http.Header) if len(kubeCfg.StaticPodURLHeader) > 0 { for k, v := range kubeCfg.StaticPodURLHeader { @@ -384,42 +390,39 @@ func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, ku } // source of all configuration - cfg := config.NewPodConfig(config.PodConfigNotificationIncremental, kubeDeps.Recorder, kubeDeps.PodStartupLatencyTracker) + cfg := config.NewPodConfig(kubeDeps.Recorder, kubeDeps.PodStartupLatencyTracker) - // TODO: it needs to be replaced by a proper context in the future - ctx := context.TODO() - logger := klog.FromContext(ctx) // define file config source if kubeCfg.StaticPodPath != "" { - klog.InfoS("Adding static pod path", "path", kubeCfg.StaticPodPath) + logger.Info("Adding static pod path", "path", kubeCfg.StaticPodPath) config.NewSourceFile(logger, kubeCfg.StaticPodPath, nodeName, kubeCfg.FileCheckFrequency.Duration, cfg.Channel(ctx, kubetypes.FileSource)) } // define url config source if kubeCfg.StaticPodURL != "" { - klog.InfoS("Adding pod URL with HTTP header", "URL", kubeCfg.StaticPodURL, "header", manifestURLHeader) + logger.Info("Adding pod URL with HTTP header", "URL", kubeCfg.StaticPodURL, "header", manifestURLHeader) config.NewSourceURL(logger, kubeCfg.StaticPodURL, manifestURLHeader, nodeName, kubeCfg.HTTPCheckFrequency.Duration, cfg.Channel(ctx, kubetypes.HTTPSource)) } if kubeDeps.KubeClient != nil { - klog.InfoS("Adding apiserver pod source") + logger.Info("Adding apiserver pod source") config.NewSourceApiserver(logger, kubeDeps.KubeClient, nodeName, nodeHasSynced, cfg.Channel(ctx, kubetypes.ApiserverSource)) } return cfg, nil } // PreInitRuntimeService will init runtime service before RunKubelet. -func PreInitRuntimeService(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies) error { +func PreInitRuntimeService(ctx context.Context, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies) error { remoteImageEndpoint := kubeCfg.ImageServiceEndpoint if remoteImageEndpoint == "" && kubeCfg.ContainerRuntimeEndpoint != "" { remoteImageEndpoint = kubeCfg.ContainerRuntimeEndpoint } var err error - logger := klog.Background() - if kubeDeps.RemoteRuntimeService, err = remote.NewRemoteRuntimeService(kubeCfg.ContainerRuntimeEndpoint, kubeCfg.RuntimeRequestTimeout.Duration, kubeDeps.TracerProvider, &logger); err != nil { + useStreaming := utilfeature.DefaultFeatureGate.Enabled(features.CRIListStreaming) + if kubeDeps.RemoteRuntimeService, err = remote.NewRemoteRuntimeService(ctx, kubeCfg.ContainerRuntimeEndpoint, kubeCfg.RuntimeRequestTimeout.Duration, kubeDeps.TracerProvider, useStreaming); err != nil { return err } - if kubeDeps.RemoteImageService, err = remote.NewRemoteImageService(remoteImageEndpoint, kubeCfg.RuntimeRequestTimeout.Duration, kubeDeps.TracerProvider, &logger); err != nil { + if kubeDeps.RemoteImageService, err = remote.NewRemoteImageService(ctx, remoteImageEndpoint, kubeCfg.RuntimeRequestTimeout.Duration, kubeDeps.TracerProvider, useStreaming); err != nil { return err } @@ -490,18 +493,18 @@ func NewMainKubelet(ctx context.Context, return kubeInformers.Core().V1().Nodes().Informer().HasSynced() } kubeInformers.Start(wait.NeverStop) - klog.InfoS("Attempting to sync node with API server") + logger.Info("Attempting to sync node with API server") } else { // we don't have a client to sync! nodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) nodeLister = corelisters.NewNodeLister(nodeIndexer) nodeHasSynced = func() bool { return true } - klog.InfoS("Kubelet is running in standalone mode, will skip API server sync") + logger.Info("Kubelet is running in standalone mode, will skip API server sync") } if kubeDeps.PodConfig == nil { var err error - kubeDeps.PodConfig, err = makePodSourceConfig(kubeCfg, kubeDeps, nodeName, nodeHasSynced) + kubeDeps.PodConfig, err = makePodSourceConfig(ctx, kubeCfg, kubeDeps, nodeName, nodeHasSynced) if err != nil { return nil, err } @@ -573,10 +576,10 @@ func NewMainKubelet(ctx context.Context, if utilfeature.DefaultFeatureGate.Enabled(features.KubeletInUserNamespace) { // oomwatcher.NewWatcher returns "open /dev/kmsg: operation not permitted" error, // when running in a user namespace with sysctl value `kernel.dmesg_restrict=1`. - klog.V(2).InfoS("Failed to create an oomWatcher (running in UserNS, ignoring)", "err", err) + logger.V(2).Info("Failed to create an oomWatcher (running in UserNS, ignoring)", "err", err) oomWatcher = nil } else { - klog.ErrorS(err, "Failed to create an oomWatcher (running in UserNS, Hint: enable KubeletInUserNamespace feature flag to ignore the error)") + logger.Error(err, "Failed to create an oomWatcher (running in UserNS, Hint: enable KubeletInUserNamespace feature flag to ignore the error)") return nil, err } } else { @@ -588,7 +591,7 @@ func NewMainKubelet(ctx context.Context, for _, ipEntry := range kubeCfg.ClusterDNS { ip := netutils.ParseIPSloppy(ipEntry) if ip == nil { - klog.InfoS("Invalid clusterDNS IP", "IP", ipEntry) + logger.Info("Invalid clusterDNS IP", "IP", ipEntry) } else { clusterDNS = append(clusterDNS, ip) } @@ -653,6 +656,7 @@ func NewMainKubelet(ctx context.Context, nodeStatusMaxImages: nodeStatusMaxImages, tracer: tracer, nodeStartupLatencyTracker: kubeDeps.NodeStartupLatencyTracker, + podStartupLatencyTracker: kubeDeps.PodStartupLatencyTracker, healthChecker: kubeDeps.HealthChecker, flagz: kubeDeps.Flagz, } @@ -694,18 +698,23 @@ func NewMainKubelet(ctx context.Context, klet.livenessManager = proberesults.NewManager() klet.readinessManager = proberesults.NewManager() klet.startupManager = proberesults.NewManager() - klet.podCache = kubecontainer.NewCache() + podCache := kubecontainer.NewCache() + klet.podCache = podCache klet.mirrorPodClient = kubepod.NewBasicMirrorClient(klet.kubeClient, string(nodeName), nodeLister) klet.podManager = kubepod.NewBasicPodManager() klet.statusManager = status.NewManager(klet.kubeClient, klet.podManager, klet, kubeDeps.PodStartupLatencyTracker) + + if utilfeature.DefaultFeatureGate.Enabled(features.PodsAPI) { + broadcaster := pods.NewBroadcaster() + klet.podsServer = pods.NewPodsServer(broadcaster, klet.podManager, klet.statusManager) + klet.statusManager.AddPodUpdateNotifier(klet.podsServer) + } klet.allocationManager = allocation.NewManager( klet.getRootDir(), - klet.containerManager.GetNodeConfig(), - klet.containerManager.GetNodeAllocatableAbsolute(), klet.statusManager, - func(pod *v1.Pod) { klet.HandlePodSyncs([]*v1.Pod{pod}) }, + klet.syncPodNow, klet.GetActivePods, klet.podManager.GetPodByUID, klet.sourcesReady, @@ -713,7 +722,6 @@ func NewMainKubelet(ctx context.Context, ) klet.resourceAnalyzer = serverstats.NewResourceAnalyzer(ctx, klet, kubeCfg.VolumeStatsAggPeriod.Duration, kubeDeps.Recorder) - klet.runtimeService = kubeDeps.RemoteRuntimeService if managed.IsEnabled() { @@ -815,10 +823,12 @@ func NewMainKubelet(ctx context.Context, kubeCfg.MemorySwap.SwapBehavior, kubeDeps.ContainerManager.GetNodeAllocatableAbsolute, *kubeCfg.MemoryThrottlingFactor, - kubeDeps.PodStartupLatencyTracker, + kubeCfg.MemoryReservationPolicy, + klet.podStartupLatencyTracker, kubeDeps.TracerProvider, tokenManager, getServiceAccount, + klet.podStartupLatencyTracker, ) if err != nil { return nil, err @@ -827,6 +837,7 @@ func NewMainKubelet(ctx context.Context, klet.streamingRuntime = runtime klet.runner = runtime klet.allocationManager.SetContainerRuntime(runtime) + resizeAdmitHandler := allocation.NewPodResizesAdmitHandler(klet.containerManager, runtime, klet.allocationManager, logger) runtimeCache, err := kubecontainer.NewRuntimeCache(klet.containerRuntime, runtimeCacheRefreshPeriod) if err != nil { @@ -871,7 +882,7 @@ func NewMainKubelet(ctx context.Context, RelistPeriod: eventedPlegRelistPeriod, RelistThreshold: eventedPlegRelistThreshold, } - klet.pleg = pleg.NewGenericPLEG(logger, klet.containerRuntime, eventChannel, genericRelistDuration, klet.podCache, clock.RealClock{}) + klet.pleg = pleg.NewGenericPLEG(logger, klet.containerRuntime, eventChannel, genericRelistDuration, podCache, clock.RealClock{}) // In case Evented PLEG has to fall back on Generic PLEG due to an error, // Evented PLEG should be able to reset the Generic PLEG relisting duration // to the default value. @@ -880,7 +891,7 @@ func NewMainKubelet(ctx context.Context, RelistThreshold: genericPlegRelistThreshold, } klet.eventedPleg, err = pleg.NewEventedPLEG(logger, klet.containerRuntime, klet.runtimeService, eventChannel, - klet.podCache, klet.pleg, eventedPlegMaxStreamRetries, eventedRelistDuration, clock.RealClock{}) + podCache, klet.pleg, eventedPlegMaxStreamRetries, eventedRelistDuration, clock.RealClock{}) if err != nil { return nil, err } @@ -889,7 +900,7 @@ func NewMainKubelet(ctx context.Context, RelistPeriod: genericPlegRelistPeriod, RelistThreshold: genericPlegRelistThreshold, } - klet.pleg = pleg.NewGenericPLEG(logger, klet.containerRuntime, eventChannel, genericRelistDuration, klet.podCache, clock.RealClock{}) + klet.pleg = pleg.NewGenericPLEG(logger, klet.containerRuntime, eventChannel, genericRelistDuration, podCache, clock.RealClock{}) } klet.runtimeState = newRuntimeState(maxWaitForContainerRuntime) @@ -898,7 +909,7 @@ func NewMainKubelet(ctx context.Context, klet.runtimeState.addHealthCheck("EventedPLEG", klet.eventedPleg.Healthy) } if _, err := klet.updatePodCIDR(ctx, kubeCfg.PodCIDR); err != nil { - klog.ErrorS(err, "Pod CIDR update failed") + logger.Error(err, "Pod CIDR update failed") } // setup containerGC @@ -917,26 +928,74 @@ func NewMainKubelet(ctx context.Context, klet.imageManager = imageManager if kubeDeps.TLSOptions != nil { + kubeDeps.TLSConfig = &tls.Config{} + + kubeDeps.TLSConfig.MinVersion = kubeDeps.TLSOptions.MinVersion + kubeDeps.TLSConfig.CipherSuites = kubeDeps.TLSOptions.CipherSuites + kubeDeps.TLSConfig.CurvePreferences = kubeDeps.TLSOptions.CurvePreferences + + getServingCertificate := func() *tls.Certificate { return nil } + + // kubelet configuration that automatically rotates serving certs if kubeCfg.ServerTLSBootstrap && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) { - klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, klet.getLastObservedNodeAddresses, certDirectory) + klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, func() []v1.NodeAddress { + return klet.getLastObservedNodeAddresses(ctx) + }, certDirectory) if err != nil { return nil, fmt.Errorf("failed to initialize certificate manager: %w", err) } + getServingCertificate = klet.serverCertificateManager.Current - } else if kubeDeps.TLSOptions.CertFile != "" && kubeDeps.TLSOptions.KeyFile != "" && utilfeature.DefaultFeatureGate.Enabled(features.ReloadKubeletServerCertificateFile) { - klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateDynamicFileManager(kubeDeps.TLSOptions.CertFile, kubeDeps.TLSOptions.KeyFile) - if err != nil { - return nil, fmt.Errorf("failed to initialize file based certificate manager: %w", err) + } else if kubeDeps.TLSOptions.CertFile != "" && kubeDeps.TLSOptions.KeyFile != "" { + + if utilfeature.DefaultFeatureGate.Enabled(features.ReloadKubeletServerCertificateFile) { + // kubelet configuration that reloads serving certs from the disk when updated + klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateDynamicFileManager(kubeDeps.TLSOptions.CertFile, kubeDeps.TLSOptions.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed to initialize file based certificate manager: %w", err) + } + getServingCertificate = klet.serverCertificateManager.Current + + } else { + // kubelet configuration that sets static serving certs + servingCert, err := tls.LoadX509KeyPair(kubeDeps.TLSOptions.CertFile, kubeDeps.TLSOptions.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load certificate: %w", err) + } + getServingCertificate = func() *tls.Certificate { return &servingCert } } } - if klet.serverCertificateManager != nil { - kubeDeps.TLSOptions.Config.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { - cert := klet.serverCertificateManager.Current() - if cert == nil { - return nil, fmt.Errorf("no serving certificate available for the kubelet") + kubeDeps.TLSConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { + cert := getServingCertificate() + if cert == nil { + return nil, fmt.Errorf("no serving certificate available for the kubelet") + } + return cert, nil + } + + if kubeDeps.TLSOptions.ClientCAFile != "" { + // Populate PeerCertificates in requests, but don't reject connections without verified certificates + kubeDeps.TLSConfig.ClientAuth = tls.RequestClientCert + + if utilfeature.DefaultFeatureGate.Enabled(features.ReloadKubeletClientCAFile) { + // kubelet configuration that reloads Client CA Bundle from the disk when updated + klet.clientCAManager = dynamiccertificates.NewDynamicServingCertificateController(kubeDeps.TLSConfig.Clone(), kubeDeps.Auth, nil, nil, nil) + + err := klet.clientCAManager.RunOnce() + if err != nil { + return nil, fmt.Errorf("unable to load client CA file %s: %w", kubeDeps.TLSOptions.ClientCAFile, err) + } + + kubeDeps.TLSConfig.GetConfigForClient = klet.clientCAManager.GetConfigForClient + + } else { + // kubelet configuration that sets static Client CA Bundle + clientCAs, err := certutil.NewPool(kubeDeps.TLSOptions.ClientCAFile) + if err != nil { + return nil, fmt.Errorf("unable to load client CA file %s: %w", kubeDeps.TLSOptions.ClientCAFile, err) } - return cert, nil + kubeDeps.TLSConfig.ClientCAs = clientCAs } } } @@ -956,9 +1015,9 @@ func NewMainKubelet(ctx context.Context, var clusterTrustBundleManager clustertrustbundle.Manager = &clustertrustbundle.NoopManager{} if kubeDeps.KubeClient != nil && utilfeature.DefaultFeatureGate.Enabled(features.ClusterTrustBundleProjection) { clusterTrustBundleManager = clustertrustbundle.NewLazyInformerManager(ctx, kubeDeps.KubeClient, 2*int(kubeCfg.MaxPods)) - klog.InfoS("ClusterTrustBundle informer will be started eventually once a trust bundle is requested") + logger.Info("ClusterTrustBundle informer will be started eventually once a trust bundle is requested") } else { - klog.InfoS("Not starting ClusterTrustBundle informer because we are in static kubelet mode or the ClusterTrustBundleProjection featuregate is disabled") + logger.Info("Not starting ClusterTrustBundle informer because we are in static kubelet mode or the ClusterTrustBundleProjection featuregate is disabled") } if kubeDeps.KubeClient != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodCertificateRequest) { @@ -985,7 +1044,7 @@ func NewMainKubelet(ctx context.Context, metrics.RegisterCollectors(collectors.PodCertificateCollectorFor(podCertificateManager)) } else { klet.podCertificateManager = &podcertificate.NoOpManager{} - klog.InfoS("Not starting PodCertificateRequest manager because we are in static kubelet mode or the PodCertificateProjection feature gate is disabled") + logger.Info("Not starting PodCertificateRequest manager because we are in static kubelet mode or the PodCertificateProjection feature gate is disabled") } // NewInitializedVolumePluginMgr initializes some storageErrors on the Kubelet runtimeState (in csi_plugin.go init) @@ -1031,28 +1090,29 @@ func NewMainKubelet(ctx context.Context, // setup eviction manager evictionManager, evictionAdmitHandler := eviction.NewManager(klet.resourceAnalyzer, evictionConfig, - killPodNow(klet.podWorkers, kubeDeps.Recorder), klet.imageManager, klet.containerGC, kubeDeps.Recorder, nodeRef, klet.clock, kubeCfg.LocalStorageCapacityIsolation) + killPodNow(ctx, klet.podWorkers, kubeDeps.Recorder), klet.imageManager, klet.containerGC, kubeDeps.Recorder, nodeRef, klet.clock, kubeCfg.LocalStorageCapacityIsolation) klet.evictionManager = evictionManager + handlers := []lifecycle.PodAdmitHandler{} + handlers = append(handlers, evictionAdmitHandler) if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) { + if status, err := klet.containerRuntime.Status(ctx); err == nil && status != nil { + klet.runtimeState.setRuntimeFeatures(status.Features) + } else if err != nil { + logger.V(4).Info("Unable to prefetch container runtime features for node declared features", "err", err) + } v, err := versionutil.Parse(version.Get().String()) if err != nil { return nil, fmt.Errorf("failed to parse version: %w", err) } - framework, err := ndf.New(ndffeatures.AllFeatures) - if err != nil { - return nil, fmt.Errorf("failed to create node feature helper: %w", err) - } + framework := ndf.DefaultFramework klet.version = v klet.nodeDeclaredFeaturesFramework = framework klet.nodeDeclaredFeatures = klet.discoverNodeDeclaredFeatures() - klet.nodeDeclaredFeaturesSet = ndf.NewFeatureSet(klet.nodeDeclaredFeatures...) + klet.nodeDeclaredFeaturesSet = framework.MustMapSorted(klet.nodeDeclaredFeatures) } - handlers := []lifecycle.PodAdmitHandler{} - handlers = append(handlers, evictionAdmitHandler) - // Safe, allowed sysctls can always be used as unsafe sysctls in the spec. // Hence, we concatenate those two lists. safeAndUnsafeSysctls := append(sysctl.SafeSysctlAllowlist(ctx), allowedUnsafeSysctls...) @@ -1072,7 +1132,7 @@ func NewMainKubelet(ctx context.Context, handlers = append(handlers, klet.containerManager.GetAllocateResourcesPodAdmitHandler()) - criticalPodAdmissionHandler := preemption.NewCriticalPodAdmissionHandler(klet.getAllocatedPods, killPodNow(klet.podWorkers, kubeDeps.Recorder), kubeDeps.Recorder) + criticalPodAdmissionHandler := preemption.NewCriticalPodAdmissionHandler(klet.getAllocatedPods, killPodNow(ctx, klet.podWorkers, kubeDeps.Recorder), kubeDeps.Recorder) handlers = append(handlers, lifecycle.NewPredicateAdmitHandler(klet.GetCachedNode, criticalPodAdmissionHandler, klet.containerManager.UpdatePluginResources)) // apply functional Option's for _, opt := range kubeDeps.Options { @@ -1111,7 +1171,7 @@ func NewMainKubelet(ctx context.Context, Recorder: kubeDeps.Recorder, NodeRef: nodeRef, GetPodsFunc: klet.GetActivePods, - KillPodFunc: killPodNow(klet.podWorkers, kubeDeps.Recorder), + KillPodFunc: killPodNow(ctx, klet.podWorkers, kubeDeps.Recorder), SyncNodeStatusFunc: klet.syncNodeStatus, ShutdownGracePeriodRequested: kubeCfg.ShutdownGracePeriod.Duration, ShutdownGracePeriodCriticalPods: kubeCfg.ShutdownGracePeriodCriticalPods.Duration, @@ -1120,7 +1180,8 @@ func NewMainKubelet(ctx context.Context, }) klet.shutdownManager = shutdownManager handlers = append(handlers, shutdownManager) - klet.allocationManager.AddPodAdmitHandlers(handlers) + + klet.allocationManager.AddPodAdmitHandlers(append([]lifecycle.PodAdmitHandler{resizeAdmitHandler}, handlers...)) var usernsIDsPerPod *int64 if kubeCfg.UserNamespaces != nil { @@ -1321,7 +1382,7 @@ type Kubelet struct { startupManager proberesults.Manager // The EventRecorder to use - recorder record.EventRecorder + recorder record.EventRecorderLogger // Policy for handling garbage collection of dead containers. containerGC kubecontainer.GC @@ -1339,6 +1400,9 @@ type Kubelet struct { // Handles certificate rotations. serverCertificateManager certificate.Manager + // handles reloading of ClientCA from the disk + clientCAManager *dynamiccertificates.DynamicServingCertificateController + // Indicates that the node initialization happens in an external cloud controller externalCloudProvider bool // Reference to this node. @@ -1415,7 +1479,7 @@ type Kubelet struct { eventedPleg pleg.PodLifecycleEventGenerator // Store kubecontainer.PodStatus for all pods. - podCache kubecontainer.Cache + podCache kubecontainer.ROCache // os is a facade for various syscalls that need to be mocked during testing. os kubecontainer.OSInterface @@ -1530,11 +1594,17 @@ type Kubelet struct { // Track node startup latencies nodeStartupLatencyTracker util.NodeStartupLatencyTracker + // Track pod startup latencies + podStartupLatencyTracker util.PodStartupLatencyTracker + // Health check kubelet healthChecker watchdog.HealthChecker // flagz is the Reader interface to get flags for flagz page. flagz flagz.Reader + + // podsServer is the server that provides the pods gRPC service. + podsServer *pods.PodsServer } // ListPodStats is delegated to StatsProvider, which implements stats.Provider interface @@ -1589,7 +1659,7 @@ func (kl *Kubelet) RlimitStats() (*statsapi.RlimitStats, error) { // 4. the pod-resources directory // 5. the checkpoint directory // 6. the pod logs root directory -func (kl *Kubelet) setupDataDirs() error { +func (kl *Kubelet) setupDataDirs(logger klog.Logger) error { if cleanedRoot := filepath.Clean(kl.rootDirectory); cleanedRoot != kl.rootDirectory { return fmt.Errorf("rootDirectory not in canonical form: expected %s, was %s", cleanedRoot, kl.rootDirectory) } @@ -1624,24 +1694,24 @@ func (kl *Kubelet) setupDataDirs() error { if selinux.GetEnabled() { err := selinux.SetFileLabel(pluginRegistrationDir, kubeletconfig.KubeletPluginsDirSELinuxLabel) if err != nil { - klog.InfoS("Unprivileged containerized plugins might not work, could not set selinux context on plugin registration dir", "path", pluginRegistrationDir, "err", err) + logger.Info("Unprivileged containerized plugins might not work, could not set selinux context on plugin registration dir", "path", pluginRegistrationDir, "err", err) } err = selinux.SetFileLabel(pluginsDir, kubeletconfig.KubeletPluginsDirSELinuxLabel) if err != nil { - klog.InfoS("Unprivileged containerized plugins might not work, could not set selinux context on plugins dir", "path", pluginsDir, "err", err) + logger.Info("Unprivileged containerized plugins might not work, could not set selinux context on plugins dir", "path", pluginsDir, "err", err) } } return nil } // StartGarbageCollection starts garbage collection threads. -func (kl *Kubelet) StartGarbageCollection() { +func (kl *Kubelet) StartGarbageCollection(ctx context.Context) { + logger := klog.FromContext(ctx) loggedContainerGCFailure := false go wait.Until(func() { - ctx := context.Background() if err := kl.containerGC.GarbageCollect(ctx); err != nil { - klog.ErrorS(err, "Container garbage collection failed") - kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ContainerGCFailed, err.Error()) + logger.Error(err, "Container garbage collection failed") + kl.recorder.WithLogger(logger).Eventf(kl.nodeRef, v1.EventTypeWarning, events.ContainerGCFailed, "%s", err.Error()) loggedContainerGCFailure = true } else { var vLevel klog.Level = 4 @@ -1650,28 +1720,27 @@ func (kl *Kubelet) StartGarbageCollection() { loggedContainerGCFailure = false } - klog.V(vLevel).InfoS("Container garbage collection succeeded") + logger.V(int(vLevel)).Info("Container garbage collection succeeded") } }, ContainerGCPeriod, wait.NeverStop) // when the high threshold is set to 100, and the max age is 0 (or the max age feature is disabled) // stub the image GC manager if kl.kubeletConfiguration.ImageGCHighThresholdPercent == 100 && kl.kubeletConfiguration.ImageMaximumGCAge.Duration == 0 { - klog.V(2).InfoS("ImageGCHighThresholdPercent is set 100 and ImageMaximumGCAge is 0, Disable image GC") + logger.V(2).Info("ImageGCHighThresholdPercent is set 100 and ImageMaximumGCAge is 0, Disable image GC") return } prevImageGCFailed := false beganGC := time.Now() go wait.Until(func() { - ctx := context.Background() if err := kl.imageManager.GarbageCollect(ctx, beganGC); err != nil { if prevImageGCFailed { - klog.ErrorS(err, "Image garbage collection failed multiple times in a row") + logger.Error(err, "Image garbage collection failed multiple times in a row") // Only create an event for repeated failures - kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, events.ImageGCFailed, err.Error()) + kl.recorder.WithLogger(logger).Event(kl.nodeRef, v1.EventTypeWarning, events.ImageGCFailed, err.Error()) } else { - klog.ErrorS(err, "Image garbage collection failed once. Stats initialization may not have completed yet") + logger.Error(err, "Image garbage collection failed once. Stats initialization may not have completed yet") } prevImageGCFailed = true } else { @@ -1681,7 +1750,7 @@ func (kl *Kubelet) StartGarbageCollection() { prevImageGCFailed = false } - klog.V(vLevel).InfoS("Image garbage collection succeeded") + logger.V(int(vLevel)).Info("Image garbage collection succeeded") } }, ImageGCPeriod, wait.NeverStop) } @@ -1689,6 +1758,7 @@ func (kl *Kubelet) StartGarbageCollection() { // initializeModules will initialize internal modules that do not require the container runtime to be up. // Note that the modules here must not depend on modules that are not initialized here. func (kl *Kubelet) initializeModules(ctx context.Context) error { + logger := klog.FromContext(ctx) // Prometheus metrics. metrics.Register() metrics.RegisterCollectors( @@ -1699,7 +1769,7 @@ func (kl *Kubelet) initializeModules(ctx context.Context) error { servermetrics.Register() // Setup filesystem directories. - if err := kl.setupDataDirs(); err != nil { + if err := kl.setupDataDirs(logger); err != nil { return err } @@ -1726,6 +1796,11 @@ func (kl *Kubelet) initializeModules(ctx context.Context) error { kl.serverCertificateManager.Start() } + // Start the client CA manager if it was enabled. + if kl.clientCAManager != nil { + go kl.clientCAManager.Run(1, ctx.Done()) + } + // Start out of memory watcher. if kl.oomWatcher != nil { if err := kl.oomWatcher.Start(ctx, kl.nodeRef); err != nil { @@ -1741,9 +1816,10 @@ func (kl *Kubelet) initializeModules(ctx context.Context) error { // initializeRuntimeDependentModules will initialize internal modules that require the container runtime to be up. func (kl *Kubelet) initializeRuntimeDependentModules(ctx context.Context) { + logger := klog.FromContext(ctx) if err := kl.cadvisor.Start(); err != nil { // Fail kubelet and rely on the babysitter to retry starting kubelet. - klog.ErrorS(err, "Failed to start cAdvisor") + logger.Error(err, "Failed to start cAdvisor") os.Exit(1) } @@ -1751,16 +1827,16 @@ func (kl *Kubelet) initializeRuntimeDependentModules(ctx context.Context) { // ignore any errors, since if stats collection is not successful, the container manager will fail to start below. kl.StatsProvider.GetCgroupStats("/", true) // Start container manager. - node, err := kl.getNodeAnyWay() + node, err := kl.getNodeAnyWay(ctx) if err != nil { // Fail kubelet and rely on the babysitter to retry starting kubelet. - klog.ErrorS(err, "Kubelet failed to get node info") + logger.Error(err, "Kubelet failed to get node info") os.Exit(1) } // containerManager must start after cAdvisor because it needs filesystem capacity information if err := kl.containerManager.Start(ctx, node, kl.GetActivePods, kl.getNodeAnyWay, kl.sourcesReady, kl.statusManager, kl.runtimeService, kl.supportLocalStorageCapacityIsolation()); err != nil { // Fail kubelet and rely on the babysitter to retry starting kubelet. - klog.ErrorS(err, "Failed to start ContainerManager") + logger.Error(err, "Failed to start ContainerManager") os.Exit(1) } // eviction manager must start after cadvisor because it needs to know if the container runtime has a dedicated imagefs @@ -1778,19 +1854,19 @@ func (kl *Kubelet) initializeRuntimeDependentModules(ctx context.Context) { } // Start the plugin manager - klog.V(4).InfoS("Starting plugin manager") + logger.V(4).Info("Starting plugin manager") go kl.pluginManager.Run(ctx, kl.sourcesReady, wait.NeverStop) - err = kl.shutdownManager.Start() + err = kl.shutdownManager.Start(ctx) if err != nil { // The shutdown manager is not critical for kubelet, so log failure, but don't block Kubelet startup if there was a failure starting it. - klog.ErrorS(err, "Failed to start node shutdown manager") + logger.Error(err, "Failed to start node shutdown manager") } } // Run starts the kubelet reacting to config updates -func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { - ctx := context.Background() +func (kl *Kubelet) Run(ctx context.Context, updates <-chan kubetypes.PodUpdate) { + logger := klog.FromContext(ctx) if kl.logServer == nil { file := http.FileServer(http.Dir(nodeLogDir)) if utilfeature.DefaultFeatureGate.Enabled(features.NodeLogQuery) && kl.kubeletConfiguration.EnableSystemLogQuery { @@ -1824,17 +1900,17 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { } } if kl.kubeClient == nil { - klog.InfoS("No API server defined - no node status update will be sent") + logger.Info("No API server defined - no node status update will be sent") } if err := kl.initializeModules(ctx); err != nil { - kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.KubeletSetupFailed, err.Error()) - klog.ErrorS(err, "Failed to initialize internal modules") + kl.recorder.WithLogger(logger).Eventf(kl.nodeRef, v1.EventTypeWarning, events.KubeletSetupFailed, "%s", err.Error()) + logger.Error(err, "Failed to initialize internal modules") os.Exit(1) } if err := kl.cgroupVersionCheck(); err != nil { - klog.V(2).InfoS("Warning: cgroup check", "error", err) + logger.V(2).Info("Warning: cgroup check", "error", err) } // Start the allocation manager @@ -1858,13 +1934,15 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { go func() { // Call updateRuntimeUp once before syncNodeStatus to make sure kubelet had already checked runtime state // otherwise when restart kubelet, syncNodeStatus will report node notReady in first report period - kl.updateRuntimeUp() - wait.JitterUntil(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, 0.04, true, wait.NeverStop) + kl.updateRuntimeUp(ctx) + wait.JitterUntil(func() { kl.syncNodeStatus(ctx) }, kl.nodeStatusUpdateFrequency, 0.04, true, wait.NeverStop) }() go kl.fastStatusUpdateOnce() - // start syncing lease + // Keep renewing the node lease until the kubelet exits. + // This intentionally does not use the kubelet context so lease renewal can + // continue during graceful shutdown. go kl.nodeLeaseController.Run(context.Background()) // Mirror pods for static pods may not be created immediately during node startup @@ -1874,11 +1952,11 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { // mirror pods are created as soon as the node registers. go kl.fastStaticPodsRegistration(ctx) } - go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop) + go wait.UntilWithContext(ctx, kl.updateRuntimeUp, 5*time.Second) // Set up iptables util rules if kl.makeIPTablesUtilChains { - kl.initNetworkUtil() + kl.initNetworkUtil(logger) } // Start component sync loops. @@ -1953,7 +2031,7 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { // This operation writes all events that are dispatched in order to provide // the most accurate information possible about an error situation to aid debugging. // Callers should not write an event if this operation returns an error. -func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, err error) { +func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, postSync func(), err error) { ctx, otelSpan := kl.tracer.Start(ctx, "syncPod", trace.WithAttributes( semconv.K8SPodUIDKey.String(string(pod.UID)), attribute.String("k8s.pod", klog.KObj(pod).String()), @@ -1962,13 +2040,13 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType semconv.K8SNamespaceNameKey.String(pod.Namespace), )) logger := klog.FromContext(ctx) - klog.V(4).InfoS("SyncPod enter", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("SyncPod enter", "pod", klog.KObj(pod), "podUID", pod.UID) defer func() { if err != nil { otelSpan.RecordError(err) otelSpan.SetStatus(codes.Error, err.Error()) } - klog.V(4).InfoS("SyncPod exit", "pod", klog.KObj(pod), "podUID", pod.UID, "isTerminal", isTerminal) + logger.V(4).Info("SyncPod exit", "pod", klog.KObj(pod), "podUID", pod.UID, "isTerminal", isTerminal) otelSpan.End() }() @@ -1987,20 +2065,30 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType // since kubelet first saw the pod if firstSeenTime is set. metrics.PodWorkerStartDuration.Observe(metrics.SinceInSeconds(firstSeenTime)) } else { - klog.V(3).InfoS("First seen time not recorded for pod", + logger.V(3).Info("First seen time not recorded for pod", "podUID", pod.UID, "pod", klog.KObj(pod)) } } if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { + // Update the pod with allocated resources from the allocation manager. + // The pod passed to SyncPod comes from HandlePodSyncs, which gets it from the pod manager. + // The pod manager has the desired resources (from the API spec), not the allocated resources. + // Even though the pod worker calls UpdatePodFromAllocation when queueing the update, + // we need to call it again here because: + // 1. There may be a race where the allocation changed between queueing and processing + // 2. HandlePodSyncs calls GetPodAndMirrorPod which returns a fresh pod from pod manager + // 3. We need the allocated resources for IsPodResizeInProgress and generateAPIPodStatus + pod, _ = kl.allocationManager.UpdatePodFromAllocation(pod) + // Check whether a resize is in progress so we can set the PodResizeInProgressCondition accordingly. if kl.containerRuntime.IsPodResizeInProgress(pod, podStatus) { kl.statusManager.SetPodResizeInProgressCondition(pod.UID, "", "", pod.Generation) } else if generation, cleared := kl.statusManager.ClearPodResizeInProgressCondition(pod.UID); cleared { // (Allocated == Actual) => clear the resize in-progress status. - msg := events.PodResizeCompletedMsg(pod, generation) - kl.recorder.Eventf(pod, v1.EventTypeNormal, events.ResizeCompleted, msg) + msg := events.PodResizeCompletedMsg(logger, pod, generation) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeNormal, events.ResizeCompleted, "%s", msg) } // TODO(natasha41575): There is a race condition here, where the goroutine in the // allocation manager may allocate a new resize and unconditionally set the @@ -2008,7 +2096,7 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType } // Generate final API pod status with pod and status manager status - apiPodStatus := kl.generateAPIPodStatus(pod, podStatus, false) + apiPodStatus := kl.generateAPIPodStatus(ctx, pod, podStatus, false) // The pod IP may be changed in generateAPIPodStatus if the pod is using host network. (See #24576) // TODO(random-liu): After writing pod spec into container labels, check whether pod is using host network, and // set pod IP to hostIP directly in runtime.GetPodStatus @@ -2024,7 +2112,7 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType if apiPodStatus.Phase == v1.PodSucceeded || apiPodStatus.Phase == v1.PodFailed { kl.statusManager.SetPodStatus(logger, pod, apiPodStatus) isTerminal = true - return isTerminal, nil + return isTerminal, nil, nil } // Record the time it takes for the pod to become running @@ -2039,8 +2127,8 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType // If the network plugin is not ready, only start the pod if it uses the host network if err := kl.runtimeState.networkErrors(); err != nil && !kubecontainer.IsHostNetworkPod(pod) { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.NetworkNotReady, "%s: %v", NetworkNotReadyErrorMsg, err) - return false, fmt.Errorf("%s: %v", NetworkNotReadyErrorMsg, err) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.NetworkNotReady, "%s: %v", NetworkNotReadyErrorMsg, err) + return false, nil, fmt.Errorf("%s: %v", NetworkNotReadyErrorMsg, err) } // ensure the kubelet knows about referenced secrets or configmaps used by the pod @@ -2081,9 +2169,9 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType podKilled = true } else { if wait.Interrupted(err) { - return false, nil + return false, nil, nil } - klog.ErrorS(err, "KillPod failed", "pod", klog.KObj(pod), "podStatus", podStatus) + logger.Error(err, "KillPod failed", "pod", klog.KObj(pod), "podStatus", podStatus) } } // Create and Update pod's Cgroups @@ -2104,18 +2192,16 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType } if !podKilled || !runOnce { if !pcm.Exists(pod) { - // TODO: Pass logger from context once contextual logging migration is complete - if err := kl.containerManager.UpdateQOSCgroups(klog.TODO()); err != nil { - klog.V(2).InfoS("Failed to update QoS cgroups while syncing pod", "pod", klog.KObj(pod), "err", err) + if err := kl.containerManager.UpdateQOSCgroups(logger); err != nil { + logger.V(2).Info("Failed to update QoS cgroups while syncing pod", "pod", klog.KObj(pod), "err", err) } - // TODO: Pass logger from context once contextual logging migration is complete - if err := pcm.EnsureExists(klog.TODO(), pod); err != nil { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedToCreatePodContainer, "unable to ensure pod container exists: %v", err) - return false, fmt.Errorf("failed to ensure that the pod: %v cgroups exist and are correctly applied: %v", pod.UID, err) + if err := pcm.EnsureExists(logger, pod); err != nil { + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedToCreatePodContainer, "unable to ensure pod container exists: %v", err) + return false, nil, fmt.Errorf("failed to ensure that the pod: %v cgroups exist and are correctly applied: %v", pod.UID, err) } if err = kl.containerRuntime.UpdateActuatedPodLevelResources(pod); err != nil { - return false, fmt.Errorf("failed to update the state of pod-level resources for the pod %v : %w", pod.UID, err) + return false, nil, fmt.Errorf("failed to update the state of pod-level resources for the pod %v : %w", pod.UID, err) } } } @@ -2126,28 +2212,28 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType // Make data directories for the pod if err := kl.makePodDataDirs(pod); err != nil { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedToMakePodDataDirectories, "error making pod data directories: %v", err) - klog.ErrorS(err, "Unable to make pod data directories for pod", "pod", klog.KObj(pod)) - return false, err + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedToMakePodDataDirectories, "error making pod data directories: %v", err) + logger.Error(err, "Unable to make pod data directories for pod", "pod", klog.KObj(pod)) + return false, nil, err } // Wait for volumes to attach/mount if err := kl.volumeManager.WaitForAttachAndMount(ctx, pod); err != nil { var volumeAttachLimitErr *volumemanager.VolumeAttachLimitExceededError if errors.As(err, &volumeAttachLimitErr) { - kl.rejectPod(pod, volumemanager.VolumeAttachmentLimitExceededReason, volumeAttachLimitErr.Error()) + kl.rejectPod(ctx, pod, volumemanager.VolumeAttachmentLimitExceededReason, volumeAttachLimitErr.Error()) recordAdmissionRejection(volumemanager.VolumeAttachmentLimitExceededReason) - return true, nil + return true, nil, nil } if !wait.Interrupted(err) { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to attach or mount volumes: %v", err) - klog.ErrorS(err, "Unable to attach or mount volumes for pod; skipping pod", "pod", klog.KObj(pod)) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to attach or mount volumes: %v", err) + logger.Error(err, "Unable to attach or mount volumes for pod; skipping pod", "pod", klog.KObj(pod)) } - return false, err + return false, nil, err } // Fetch the pull secrets for the pod - pullSecrets := kl.getPullSecretsForPod(pod) + pullSecrets := kl.getPullSecretsForPod(logger, pod) // Ensure the pod is being probed kl.probeManager.AddPod(ctx, pod) @@ -2170,19 +2256,50 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType result := kl.containerRuntime.SyncPod(sctx, pod, podStatus, pullSecrets, kl.crashLoopBackOff, restartingAllContainers) kl.reasonCache.Update(pod.UID, result) + // If we just performed a RestartAllContainers reset, we want to immediately + // trigger another sync to start the containers, rather than waiting for PLEG + // or the periodic resync. + if utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits) && + restartingAllContainers && result.Error() == nil { + shouldRequeue := false + for _, r := range result.SyncResults { + if r.Action == kubecontainer.RemoveContainer && r.Error == nil { + shouldRequeue = true + break + } + } + if shouldRequeue { + // This will not cause an infinite loop because UpdatePod merges concurrent updates + // to the same pod. Because all containers are removed, the subsequent SyncPod + // execution will unset the AllContainersRestarting condition and break the cycle. + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ + Pod: pod, + MirrorPod: mirrorPod, + UpdateType: kubetypes.SyncPodUpdate, + }) + } + } + if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { for _, r := range result.SyncResults { if r.Action == kubecontainer.ResizePodInPlace && r.Error != nil { // If the condition already exists, the observedGeneration does not get updated. if generation, updated := kl.statusManager.SetPodResizeInProgressCondition(pod.UID, v1.PodReasonError, r.Message, pod.Generation); updated { - msg := events.PodResizeErrorMsg(pod, generation, r.Message) - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.ResizeError, msg) + msg := events.PodResizeErrorMsg(logger, pod, generation, r.Message) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.ResizeError, "%s", msg) } } } } - return false, result.Error() + err = result.Error() + if len(result.SyncResults) > 0 && err == nil { + postSync = func() { + kl.RequestPodRelist(pod.UID) + } + } + + return false, postSync, err } // SyncTerminatingPod is expected to terminate all running containers in a pod. Once this method @@ -2194,13 +2311,12 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType // during eviction). This method is not guaranteed to be called if a pod is force deleted from the // configuration and the kubelet is restarted - SyncTerminatingRuntimePod handles those orphaned // pods. -func (kl *Kubelet) SyncTerminatingPod(_ context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, gracePeriod *int64, podStatusFn func(*v1.PodStatus)) (err error) { +func (kl *Kubelet) SyncTerminatingPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, gracePeriod *int64, podStatusFn func(*v1.PodStatus)) (err error) { // TODO(#113606): connect this with the incoming context parameter, which comes from the pod worker. // Currently, using that context causes test failures. - ctx := context.Background() - logger := klog.FromContext(ctx) - klog.V(4).InfoS("SyncTerminatingPod enter", "pod", klog.KObj(pod), "podUID", pod.UID) + ctx = klog.NewContext(context.TODO(), logger) + logger.V(4).Info("SyncTerminatingPod enter", "pod", klog.KObj(pod), "podUID", pod.UID) ctx, otelSpan := kl.tracer.Start(ctx, "syncTerminatingPod", trace.WithAttributes( semconv.K8SPodUIDKey.String(string(pod.UID)), @@ -2214,29 +2330,28 @@ func (kl *Kubelet) SyncTerminatingPod(_ context.Context, pod *v1.Pod, podStatus otelSpan.SetStatus(codes.Error, err.Error()) } otelSpan.End() - klog.V(4).InfoS("SyncTerminatingPod exit", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("SyncTerminatingPod exit", "pod", klog.KObj(pod), "podUID", pod.UID) }() - apiPodStatus := kl.generateAPIPodStatus(pod, podStatus, false) + apiPodStatus := kl.generateAPIPodStatus(ctx, pod, podStatus, false) if podStatusFn != nil { podStatusFn(&apiPodStatus) } kl.statusManager.SetPodStatus(logger, pod, apiPodStatus) if gracePeriod != nil { - klog.V(4).InfoS("Pod terminating with grace period", "pod", klog.KObj(pod), "podUID", pod.UID, "gracePeriod", *gracePeriod) + logger.V(4).Info("Pod terminating with grace period", "pod", klog.KObj(pod), "podUID", pod.UID, "gracePeriod", *gracePeriod) } else { - klog.V(4).InfoS("Pod terminating with grace period", "pod", klog.KObj(pod), "podUID", pod.UID, "gracePeriod", nil) + logger.V(4).Info("Pod terminating with grace period", "pod", klog.KObj(pod), "podUID", pod.UID, "gracePeriod", nil) } kl.probeManager.StopLivenessAndStartup(pod) p := kubecontainer.ConvertPodStatusToRunningPod(kl.getRuntime().Type(), podStatus) if err := kl.killPod(ctx, pod, p, gracePeriod); err != nil { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedToKillPod, "error killing pod: %v", err) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedToKillPod, "error killing pod: %v", err) // there was an error killing the pod, so we return that error directly - utilruntime.HandleError(err) - return err + return fmt.Errorf("error killing terminating pod: %w", err) } // Once the containers are stopped, we can stop probing for liveness and readiness. @@ -2250,10 +2365,23 @@ func (kl *Kubelet) SyncTerminatingPod(_ context.Context, pod *v1.Pod, podStatus // catch race conditions introduced by callers updating pod status out of order. // TODO: have KillPod return the terminal status of stopped containers and write that into the // cache immediately - stoppedPodStatus, err := kl.containerRuntime.GetPodStatus(ctx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err := kl.containerRuntime.GetPod(ctx, pod.UID) if err != nil { - klog.ErrorS(err, "Unable to read pod status prior to final pod termination", "pod", klog.KObj(pod), "podUID", pod.UID) - return err + if errors.Is(err, kubecontainer.ErrPodNotFound) { + // If pod sandboxes were already cleaned up, proceed with an empty runtimePod. + runtimePod = &kubecontainer.Pod{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + Timestamp: kl.clock.Now(), + } + } else { + return fmt.Errorf("unable to get pod prior to final pod termination: %w", err) + } + } + stoppedPodStatus, err := kl.containerRuntime.GetPodStatus(ctx, runtimePod) + if err != nil { + return fmt.Errorf("unable to read pod status prior to final pod termination: %w", err) } preserveDataFromBeforeStopping(stoppedPodStatus, podStatus) var runningContainers []string @@ -2264,19 +2392,19 @@ func (kl *Kubelet) SyncTerminatingPod(_ context.Context, pod *v1.Pod, podStatus FinishedAt string } var containers []container - klogV := klog.V(4) - klogVEnabled := klogV.Enabled() + loggerV := logger.V(4) + loggerVEnabled := loggerV.Enabled() for _, s := range stoppedPodStatus.ContainerStatuses { if s.State == kubecontainer.ContainerStateRunning { runningContainers = append(runningContainers, s.ID.String()) } - if klogVEnabled { + if loggerVEnabled { containers = append(containers, container{Name: s.Name, State: string(s.State), ExitCode: s.ExitCode, FinishedAt: s.FinishedAt.UTC().Format(time.RFC3339Nano)}) } } - if klogVEnabled { + if loggerVEnabled { sort.Slice(containers, func(i, j int) bool { return containers[i].Name < containers[j].Name }) - klog.V(4).InfoS("Post-termination container state", "pod", klog.KObj(pod), "podUID", pod.UID, "containers", containers) + logger.V(4).Info("Post-termination container state", "pod", klog.KObj(pod), "podUID", pod.UID, "containers", containers) } if len(runningContainers) > 0 { return fmt.Errorf("detected running containers after a successful KillPod, CRI violation: %v", runningContainers) @@ -2295,11 +2423,11 @@ func (kl *Kubelet) SyncTerminatingPod(_ context.Context, pod *v1.Pod, podStatus // The computation is done here to ensure the pod status used for it contains // information about the container end states (including exit codes) - when // SyncTerminatedPod is called the containers may already be removed. - apiPodStatus = kl.generateAPIPodStatus(pod, stoppedPodStatus, true) + apiPodStatus = kl.generateAPIPodStatus(ctx, pod, stoppedPodStatus, true) kl.statusManager.SetPodStatus(logger, pod, apiPodStatus) // we have successfully stopped all containers, the pod is terminating, our status is "done" - klog.V(4).InfoS("Pod termination stopped all running containers", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod termination stopped all running containers", "pod", klog.KObj(pod), "podUID", pod.UID) return nil } @@ -2320,25 +2448,26 @@ func preserveDataFromBeforeStopping(stoppedPodStatus, podStatus *kubecontainer.P // that the remnant of the running pod is terminated and allow garbage collection to proceed. We do // not update the status of the pod because with the source of configuration removed, we have no // place to send that status. -func (kl *Kubelet) SyncTerminatingRuntimePod(_ context.Context, runningPod *kubecontainer.Pod) error { +func (kl *Kubelet) SyncTerminatingRuntimePod(ctx context.Context, runningPod *kubecontainer.Pod) error { // TODO(#113606): connect this with the incoming context parameter, which comes from the pod worker. // Currently, using that context causes test failures. - ctx := context.Background() + logger := klog.FromContext(ctx) + ctx = klog.NewContext(context.TODO(), logger) pod := runningPod.ToAPIPod() - klog.V(4).InfoS("SyncTerminatingRuntimePod enter", "pod", klog.KObj(pod), "podUID", pod.UID) - defer klog.V(4).InfoS("SyncTerminatingRuntimePod exit", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("SyncTerminatingRuntimePod enter", "pod", klog.KObj(pod), "podUID", pod.UID) + defer logger.V(4).Info("SyncTerminatingRuntimePod exit", "pod", klog.KObj(pod), "podUID", pod.UID) // we kill the pod directly since we have lost all other information about the pod. - klog.V(4).InfoS("Orphaned running pod terminating without grace period", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Orphaned running pod terminating without grace period", "pod", klog.KObj(pod), "podUID", pod.UID) // TODO: this should probably be zero, to bypass any waiting (needs fixes in container runtime) gracePeriod := int64(1) if err := kl.killPod(ctx, pod, *runningPod, &gracePeriod); err != nil { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedToKillPod, "error killing pod: %v", err) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedToKillPod, "error killing pod: %v", err) // there was an error killing the pod, so we return that error directly utilruntime.HandleError(err) return err } - klog.V(4).InfoS("Pod termination stopped all running orphaned containers", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod termination stopped all running orphaned containers", "pod", klog.KObj(pod), "podUID", pod.UID) return nil } @@ -2360,12 +2489,12 @@ func (kl *Kubelet) SyncTerminatedPod(ctx context.Context, pod *v1.Pod, podStatus )) logger := klog.FromContext(ctx) defer otelSpan.End() - klog.V(4).InfoS("SyncTerminatedPod enter", "pod", klog.KObj(pod), "podUID", pod.UID) - defer klog.V(4).InfoS("SyncTerminatedPod exit", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("SyncTerminatedPod enter", "pod", klog.KObj(pod), "podUID", pod.UID) + defer logger.V(4).Info("SyncTerminatedPod exit", "pod", klog.KObj(pod), "podUID", pod.UID) // generate the final status of the pod // TODO: should we simply fold this into TerminatePod? that would give a single pod update - apiPodStatus := kl.generateAPIPodStatus(pod, podStatus, true) + apiPodStatus := kl.generateAPIPodStatus(ctx, pod, podStatus, true) kl.statusManager.SetPodStatus(logger, pod, apiPodStatus) @@ -2374,20 +2503,20 @@ func (kl *Kubelet) SyncTerminatedPod(ctx context.Context, pod *v1.Pod, podStatus if err := kl.volumeManager.WaitForUnmount(ctx, pod); err != nil { return err } - klog.V(4).InfoS("Pod termination unmounted volumes", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod termination unmounted volumes", "pod", klog.KObj(pod), "podUID", pod.UID) // This waiting loop relies on the background cleanup which starts after pod workers respond // true for ShouldPodRuntimeBeRemoved, which happens after `SyncTerminatingPod` is completed. if err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (bool, error) { - volumesExist := kl.podVolumesExist(pod.UID) + volumesExist := kl.podVolumesExist(logger, pod.UID) if volumesExist { - klog.V(3).InfoS("Pod is terminated, but some volumes have not been cleaned up", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(3).Info("Pod is terminated, but some volumes have not been cleaned up", "pod", klog.KObj(pod), "podUID", pod.UID) } return !volumesExist, nil }); err != nil { return err } - klog.V(3).InfoS("Pod termination cleaned up volume paths", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(3).Info("Pod termination cleaned up volume paths", "pod", klog.KObj(pod), "podUID", pod.UID) // After volume unmount is complete, let the secret and configmap managers know we're done with this pod if kl.secretManager != nil { @@ -2408,14 +2537,14 @@ func (kl *Kubelet) SyncTerminatedPod(ctx context.Context, pod *v1.Pod, podStatus if err := pcm.Destroy(logger, name); err != nil { return err } - klog.V(4).InfoS("Pod termination removed cgroups", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod termination removed cgroups", "pod", klog.KObj(pod), "podUID", pod.UID) } kl.usernsManager.Release(logger, pod.UID) // mark the final pod status kl.statusManager.TerminatePod(logger, pod) - klog.V(4).InfoS("Pod is terminated and will need no more status updates", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod is terminated and will need no more status updates", "pod", klog.KObj(pod), "podUID", pod.UID) return nil } @@ -2462,7 +2591,7 @@ func (kl *Kubelet) getPodsToSync() []*v1.Pod { // // deletePod returns an error if not all sources are ready or the pod is not // found in the runtime cache. -func (kl *Kubelet) deletePod(pod *v1.Pod) error { +func (kl *Kubelet) deletePod(ctx context.Context, pod *v1.Pod) error { if pod == nil { return fmt.Errorf("deletePod does not allow nil pod") } @@ -2471,8 +2600,8 @@ func (kl *Kubelet) deletePod(pod *v1.Pod) error { // for sources that haven't reported yet. return fmt.Errorf("skipping delete because sources aren't ready yet") } - klog.V(3).InfoS("Pod has been deleted and must be killed", "pod", klog.KObj(pod), "podUID", pod.UID) - kl.podWorkers.UpdatePod(UpdatePodOptions{ + klog.FromContext(ctx).V(3).Info("Pod has been deleted and must be killed", "pod", klog.KObj(pod), "podUID", pod.UID) + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodKill, }) @@ -2482,9 +2611,10 @@ func (kl *Kubelet) deletePod(pod *v1.Pod) error { // rejectPod records an event about the pod with the given reason and message, // and updates the pod to the failed phase in the status manager. -func (kl *Kubelet) rejectPod(pod *v1.Pod, reason, message string) { - kl.recorder.Eventf(pod, v1.EventTypeWarning, reason, message) - kl.statusManager.SetPodStatus(klog.TODO(), pod, v1.PodStatus{ +func (kl *Kubelet) rejectPod(ctx context.Context, pod *v1.Pod, reason, message string) { + logger := klog.FromContext(ctx) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, reason, "%s", message) + kl.statusManager.SetPodStatus(logger, pod, v1.PodStatus{ QOSClass: v1qos.GetPodQOS(pod), // keep it as is Phase: v1.PodFailed, Reason: reason, @@ -2513,7 +2643,8 @@ func recordAdmissionRejection(reason string) { // no changes are seen to the configuration, will synchronize the last known desired // state every sync-frequency seconds. Never returns. func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpdate, handler SyncHandler) { - klog.InfoS("Starting kubelet main sync loop") + logger := klog.FromContext(ctx) + logger.Info("Starting kubelet main sync loop") // The syncTicker wakes up kubelet to checks if there are any pod workers // that need to be sync'd. A one-second period is sufficient because the // sync interval is defaulted to 10s. @@ -2537,7 +2668,7 @@ func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpd for { if err := kl.runtimeState.runtimeErrors(); err != nil { - klog.ErrorS(err, "Skipping pod synchronization") + logger.Error(err, "Skipping pod synchronization") // exponential backoff time.Sleep(duration) duration = time.Duration(math.Min(float64(max), factor*float64(duration))) @@ -2594,36 +2725,33 @@ func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubety // Update from a config source; dispatch it to the right handler // callback. if !open { - klog.ErrorS(nil, "Update channel is closed, exiting the sync loop") + logger.Error(nil, "Update channel is closed, exiting the sync loop") return false } switch u.Op { case kubetypes.ADD: - klog.V(2).InfoS("SyncLoop ADD", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) + logger.V(2).Info("SyncLoop ADD", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) // After restarting, kubelet will get all existing pods through // ADD as if they are new pods. These pods will then go through the // admission process and *may* be rejected. This can be resolved // once we have checkpointing. - handler.HandlePodAdditions(u.Pods) + handler.HandlePodAdditions(ctx, u.Pods) case kubetypes.UPDATE: - klog.V(2).InfoS("SyncLoop UPDATE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) - handler.HandlePodUpdates(u.Pods) + logger.V(2).Info("SyncLoop UPDATE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) + handler.HandlePodUpdates(ctx, u.Pods) case kubetypes.REMOVE: - klog.V(2).InfoS("SyncLoop REMOVE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) - handler.HandlePodRemoves(u.Pods) + logger.V(2).Info("SyncLoop REMOVE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) + handler.HandlePodRemoves(ctx, u.Pods) case kubetypes.RECONCILE: - klog.V(4).InfoS("SyncLoop RECONCILE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) - handler.HandlePodReconcile(u.Pods) + logger.V(4).Info("SyncLoop RECONCILE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) + handler.HandlePodReconcile(ctx, u.Pods) case kubetypes.DELETE: - klog.V(2).InfoS("SyncLoop DELETE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) + logger.V(2).Info("SyncLoop DELETE", "source", u.Source, "pods", klog.KObjSlice(u.Pods)) // DELETE is treated as a UPDATE because of graceful deletion. - handler.HandlePodUpdates(u.Pods) - case kubetypes.SET: - // TODO: Do we want to support this? - klog.ErrorS(nil, "Kubelet does not support snapshot update") + handler.HandlePodUpdates(ctx, u.Pods) default: - klog.ErrorS(nil, "Invalid operation type received", "operation", u.Op) + logger.Error(nil, "Invalid operation type received", "operation", u.Op) } kl.sourcesReady.AddSource(u.Source) @@ -2631,17 +2759,17 @@ func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubety if isSyncPodWorthy(e) { // PLEG event for a pod; sync it. if pod, ok := kl.podManager.GetPodByUID(e.ID); ok { - klog.V(2).InfoS("SyncLoop (PLEG): event for pod", "pod", klog.KObj(pod), "event", e) - handler.HandlePodSyncs([]*v1.Pod{pod}) + logger.V(2).Info("SyncLoop (PLEG): event for pod", "pod", klog.KObj(pod), "event", e) + handler.HandlePodSyncs(ctx, []*v1.Pod{pod}) } else { // If the pod no longer exists, ignore the event. - klog.V(4).InfoS("SyncLoop (PLEG): pod does not exist, ignore irrelevant event", "event", e) + logger.V(4).Info("SyncLoop (PLEG): pod does not exist, ignore irrelevant event", "event", e) } } if e.Type == pleg.ContainerDied { if containerID, ok := e.Data.(string); ok { - kl.cleanUpContainersInPod(e.ID, containerID) + kl.cleanUpContainersInPod(ctx, e.ID, containerID) } } case <-syncCh: @@ -2650,11 +2778,11 @@ func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubety if len(podsToSync) == 0 { break } - klog.V(4).InfoS("SyncLoop (SYNC) pods", "total", len(podsToSync), "pods", klog.KObjSlice(podsToSync)) - handler.HandlePodSyncs(podsToSync) + logger.V(4).Info("SyncLoop (SYNC) pods", "total", len(podsToSync), "pods", klog.KObjSlice(podsToSync)) + handler.HandlePodSyncs(ctx, podsToSync) case update := <-kl.livenessManager.Updates(): if update.Result == proberesults.Failure { - handleProbeSync(kl, update, handler, "liveness", "unhealthy") + handleProbeSync(ctx, kl, update, handler, "liveness", "unhealthy") } case update := <-kl.readinessManager.Updates(): ready := update.Result == proberesults.Success @@ -2664,7 +2792,7 @@ func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubety if ready { status = "ready" } - handleProbeSync(kl, update, handler, "readiness", status) + handleProbeSync(ctx, kl, update, handler, "readiness", status) case update := <-kl.startupManager.Updates(): started := update.Result == proberesults.Success kl.statusManager.SetContainerStartup(logger, update.PodUID, update.ContainerID, started) @@ -2673,60 +2801,62 @@ func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubety if started { status = "started" } - handleProbeSync(kl, update, handler, "startup", status) + handleProbeSync(ctx, kl, update, handler, "startup", status) case update := <-kl.containerManager.Updates(): pods := []*v1.Pod{} for _, p := range update.PodUIDs { if pod, ok := kl.podManager.GetPodByUID(types.UID(p)); ok { - klog.V(3).InfoS("SyncLoop (containermanager): event for pod", "pod", klog.KObj(pod), "event", update) + logger.V(3).Info("SyncLoop (containermanager): event for pod", "pod", klog.KObj(pod), "event", update) pods = append(pods, pod) } else { // If the pod no longer exists, ignore the event. - klog.V(4).InfoS("SyncLoop (containermanager): pod does not exist, ignore devices updates", "event", update) + logger.V(4).Info("SyncLoop (containermanager): pod does not exist, ignore devices updates", "event", update) } } if len(pods) > 0 { // Updating the pod by syncing it again // We do not apply the optimization by updating the status directly, but can do it later - handler.HandlePodSyncs(pods) + handler.HandlePodSyncs(ctx, pods) } case <-housekeepingCh: if !kl.sourcesReady.AllReady() { // If the sources aren't ready or volume manager has not yet synced the states, // skip housekeeping, as we may accidentally delete pods from unready sources. - klog.V(4).InfoS("SyncLoop (housekeeping, skipped): sources aren't ready yet") + logger.V(4).Info("SyncLoop (housekeeping, skipped): sources aren't ready yet") } else { start := time.Now() - klog.V(4).InfoS("SyncLoop (housekeeping)") + logger.V(4).Info("SyncLoop (housekeeping)") if err := handler.HandlePodCleanups(ctx); err != nil { - klog.ErrorS(err, "Failed cleaning pods") + logger.Error(err, "Failed cleaning pods") } duration := time.Since(start) if duration > housekeepingWarningDuration { - klog.ErrorS(fmt.Errorf("housekeeping took too long"), "Housekeeping took longer than expected", "expected", housekeepingWarningDuration, "actual", duration.Round(time.Millisecond)) + logger.Error(fmt.Errorf("housekeeping took too long"), "Housekeeping took longer than expected", "expected", housekeepingWarningDuration, "actual", duration.Round(time.Millisecond)) } - klog.V(4).InfoS("SyncLoop (housekeeping) end", "duration", duration.Round(time.Millisecond)) + logger.V(4).Info("SyncLoop (housekeeping) end", "duration", duration.Round(time.Millisecond)) } } return true } -func handleProbeSync(kl *Kubelet, update proberesults.Update, handler SyncHandler, probe, status string) { +func handleProbeSync(ctx context.Context, kl *Kubelet, update proberesults.Update, handler SyncHandler, probe, status string) { + logger := klog.FromContext(ctx) // We should not use the pod from manager, because it is never updated after initialization. pod, ok := kl.podManager.GetPodByUID(update.PodUID) if !ok { // If the pod no longer exists, ignore the update. - klog.V(4).InfoS("SyncLoop (probe): ignore irrelevant update", "probe", probe, "status", status, "update", update) + logger.V(4).Info("SyncLoop (probe): ignore irrelevant update", "probe", probe, "status", status, "update", update) return } - klog.V(1).InfoS("SyncLoop (probe)", "probe", probe, "status", status, "pod", klog.KObj(pod)) - handler.HandlePodSyncs([]*v1.Pod{pod}) + logger.V(1).Info("SyncLoop (probe)", "probe", probe, "status", status, "pod", klog.KObj(pod)) + handler.HandlePodSyncs(ctx, []*v1.Pod{pod}) } // HandlePodAdditions is the callback in SyncHandler for pods being added from // a config source. -func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) { +func (kl *Kubelet) HandlePodAdditions(ctx context.Context, pods []*v1.Pod) { start := kl.clock.Now() + logger := klog.FromContext(ctx) sort.Sort(sliceutils.PodsByCreationTime(pods)) var pendingResizes []types.UID for _, pod := range pods { @@ -2736,15 +2866,15 @@ func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) { // the apiserver and no action (other than cleanup) is required. kl.podManager.AddPod(pod) - kl.podCertificateManager.TrackPod(context.TODO(), pod) + kl.podCertificateManager.TrackPod(ctx, pod) pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod) if wasMirror { if pod == nil { - klog.V(2).InfoS("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) + logger.V(2).Info("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) continue } - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodUpdate, @@ -2765,7 +2895,7 @@ func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) { // We failed pods that we rejected, so activePods include all admitted // pods that are alive. if ok, reason, message := kl.allocationManager.AddPod(kl.GetActivePods(), pod); !ok { - kl.rejectPod(pod, reason, message) + kl.rejectPod(ctx, pod, reason, message) // We avoid recording the metric in canAdmitPod because it's called // repeatedly during a resize, which would inflate the metric. // Instead, we record the metric here in HandlePodAdditions for new pods @@ -2784,7 +2914,7 @@ func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) { } } } - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodCreate, @@ -2804,8 +2934,9 @@ func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) { // HandlePodUpdates is the callback in the SyncHandler interface for pods // being updated from a config source. -func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) { +func (kl *Kubelet) HandlePodUpdates(ctx context.Context, pods []*v1.Pod) { start := kl.clock.Now() + logger := klog.FromContext(ctx) for _, pod := range pods { oldPod, _ := kl.podManager.GetPodByUID(pod.UID) kl.podManager.UpdatePod(pod) @@ -2813,7 +2944,7 @@ func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) { pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod) if wasMirror { if pod == nil { - klog.V(2).InfoS("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) + logger.V(2).Info("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) continue } } @@ -2834,28 +2965,28 @@ func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) { } } - if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) { + if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) && oldPod != nil { oldPodInfo := &ndf.PodInfo{Spec: &oldPod.Spec, Status: &oldPod.Status} newPodInfo := &ndf.PodInfo{Spec: &pod.Spec, Status: &pod.Status} reqs, err := kl.nodeDeclaredFeaturesFramework.InferForPodUpdate(oldPodInfo, newPodInfo, kl.version) if err != nil { - klog.ErrorS(err, "Failed to infer required features for pod update", "pod", klog.KObj(pod)) + logger.Error(err, "Failed to infer required features for pod update", "pod", klog.KObj(pod)) } - if reqs.Len() != 0 { - matchResult, err := ndf.MatchNodeFeatureSet(reqs, kl.nodeDeclaredFeaturesSet) + if !reqs.IsEmpty() { + matchResult, err := kl.nodeDeclaredFeaturesFramework.MatchNodeFeatureSet(reqs, kl.nodeDeclaredFeaturesSet) if err != nil { - klog.ErrorS(err, "Failed to match pod features with the node", "pod", klog.KObj(pod)) + logger.Error(err, "Failed to match pod features with the node", "pod", klog.KObj(pod)) } if !matchResult.IsMatch { missingNodeDeclaredFeatures := strings.Join(matchResult.UnsatisfiedRequirements, ", ") - klog.ErrorS(nil, "Pod requires node features that are not available", "missingFeatures", missingNodeDeclaredFeatures) - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedNodeDeclaredFeaturesCheck, "Pod requires node features that are not available: %s", missingNodeDeclaredFeatures) + logger.Error(nil, "Pod requires node features that are not available", "missingFeatures", missingNodeDeclaredFeatures) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.FailedNodeDeclaredFeaturesCheck, "Pod requires node features that are not available: %s", missingNodeDeclaredFeatures) } } } - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodUpdate, @@ -2864,9 +2995,8 @@ func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) { } } -// recordResizeOperaations records if any of the pod level resources or -// containers need to be resized, and returns -// true if so +// recordResizeOperations records if any of the pod level resources or +// containers need to be resized, and returns true if so func recordResizeOperations(oldPod, newPod *v1.Pod) bool { if oldPod == nil { // This should never happen. @@ -2875,7 +3005,6 @@ func recordResizeOperations(oldPod, newPod *v1.Pod) bool { hasResize := recordContainerResizeOperations(oldPod, newPod) || recordPodLevelResourceResizeOperations(oldPod, newPod) return hasResize - } // recordPodLevelResourceResizeOperations records if any of the pod level resources need to be resized, and returns @@ -2952,20 +3081,21 @@ func resizeOperationForResources(new, old *resource.Quantity) string { // HandlePodRemoves is the callback in the SyncHandler interface for pods // being removed from a config source. -func (kl *Kubelet) HandlePodRemoves(pods []*v1.Pod) { +func (kl *Kubelet) HandlePodRemoves(ctx context.Context, pods []*v1.Pod) { start := kl.clock.Now() + logger := klog.FromContext(ctx) for _, pod := range pods { - kl.podCertificateManager.ForgetPod(context.TODO(), pod) + kl.podCertificateManager.ForgetPod(ctx, pod) kl.podManager.RemovePod(pod) kl.allocationManager.RemovePod(pod.UID) pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod) if wasMirror { if pod == nil { - klog.V(2).InfoS("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) + logger.V(2).Info("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) continue } - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodUpdate, @@ -2976,8 +3106,8 @@ func (kl *Kubelet) HandlePodRemoves(pods []*v1.Pod) { // Deletion is allowed to fail because the periodic cleanup routine // will trigger deletion again. - if err := kl.deletePod(pod); err != nil { - klog.V(2).InfoS("Failed to delete pod", "pod", klog.KObj(pod), "err", err) + if err := kl.deletePod(ctx, pod); err != nil { + logger.V(2).Info("Failed to delete pod", "pod", klog.KObj(pod), "err", err) } } @@ -2989,9 +3119,11 @@ func (kl *Kubelet) HandlePodRemoves(pods []*v1.Pod) { // HandlePodReconcile is the callback in the SyncHandler interface for pods // that should be reconciled. Pods are reconciled when only the status of the // pod is updated in the API. -func (kl *Kubelet) HandlePodReconcile(pods []*v1.Pod) { +func (kl *Kubelet) HandlePodReconcile(ctx context.Context, pods []*v1.Pod) { start := kl.clock.Now() + logger := klog.FromContext(ctx) retryPendingResizes := false + var triggerReason string hasPendingResizes := kl.allocationManager.HasPendingResizes() for _, pod := range pods { // Update the pod in pod manager, status manager will do periodically reconcile according @@ -3002,16 +3134,22 @@ func (kl *Kubelet) HandlePodReconcile(pods []*v1.Pod) { pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod) if wasMirror { if pod == nil { - klog.V(2).InfoS("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) + logger.V(2).Info("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) continue } // Static pods should be reconciled the same way as regular pods } if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { - // If there are pending resizes, check whether the requests shrank as a result of the status - // resources changing. if hasPendingResizes && !retryPendingResizes && oldPod != nil { + // If the pod has reached a terminal phase, we retry all pending resizes. + if podutil.IsPodTerminal(pod) && !podutil.IsPodTerminal(oldPod) { + retryPendingResizes = true + triggerReason = allocation.TriggerReasonPodTerminated + } + + // If there are pending resizes, check whether the requests shrank as a result of the status + // resources changing. opts := resourcehelper.PodResourcesOptions{ UseStatusResources: true, SkipPodLevelResources: !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources), @@ -3026,8 +3164,11 @@ func (kl *Kubelet) HandlePodReconcile(pods []*v1.Pod) { newRequest := resourcehelper.PodRequests(allocatedPod, opts) // If cpu or memory requests shrank, then retry the pending resizes. - retryPendingResizes = newRequest.Memory().Cmp(*oldRequest.Memory()) < 0 || - newRequest.Cpu().Cmp(*oldRequest.Cpu()) < 0 + if newRequest.Memory().Cmp(*oldRequest.Memory()) < 0 || + newRequest.Cpu().Cmp(*oldRequest.Cpu()) < 0 { + retryPendingResizes = true + triggerReason = allocation.TriggerReasonPodResized + } } } @@ -3040,7 +3181,7 @@ func (kl *Kubelet) HandlePodReconcile(pods []*v1.Pod) { // be different than Sync, or if there is a better place for it. For instance, we have // needsReconcile in kubelet/config, here, and in status_manager. if status.NeedToReconcilePodReadiness(pod) { - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodSync, @@ -3062,29 +3203,30 @@ func (kl *Kubelet) HandlePodReconcile(pods []*v1.Pod) { if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { if retryPendingResizes { - kl.allocationManager.RetryPendingResizes(allocation.TriggerReasonPodResized) + kl.allocationManager.RetryPendingResizes(triggerReason) } } } // HandlePodSyncs is the callback in the syncHandler interface for pods // that should be dispatched to pod workers for sync. -func (kl *Kubelet) HandlePodSyncs(pods []*v1.Pod) { +func (kl *Kubelet) HandlePodSyncs(ctx context.Context, pods []*v1.Pod) { start := kl.clock.Now() + logger := klog.FromContext(ctx) for _, pod := range pods { pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod) if wasMirror { if pod == nil { - klog.V(2).InfoS("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) + logger.V(2).Info("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID) continue } // Syncing a mirror pod is a programmer error since the intent of sync is to // batch notify all pending work. We should make it impossible to double sync, // but for now log a programmer error to prevent accidental introduction. - klog.V(3).InfoS("Programmer error, HandlePodSyncs does not expect to receive mirror pods", "podUID", pod.UID, "mirrorPodUID", mirrorPod.UID) + logger.V(3).Info("Programmer error, HandlePodSyncs does not expect to receive mirror pods", "podUID", pod.UID, "mirrorPodUID", mirrorPod.UID) continue } - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodSync, @@ -3120,29 +3262,29 @@ func (kl *Kubelet) SyncLoopHealthCheck(req *http.Request) error { // the runtime dependent modules when the container runtime first comes up, // and returns an error if the status check fails. If the status check is OK, // update the container runtime uptime in the kubelet runtimeState. -func (kl *Kubelet) updateRuntimeUp() { +func (kl *Kubelet) updateRuntimeUp(ctx context.Context) { + logger := klog.FromContext(ctx) kl.updateRuntimeMux.Lock() defer kl.updateRuntimeMux.Unlock() - ctx := context.Background() s, err := kl.containerRuntime.Status(ctx) if err != nil { - klog.ErrorS(err, "Container runtime sanity check failed") + logger.Error(err, "Container runtime sanity check failed") return } if s == nil { - klog.ErrorS(nil, "Container runtime status is nil") + logger.Error(nil, "Container runtime status is nil") return } // Periodically log the whole runtime status for debugging. - klog.V(4).InfoS("Container runtime status", "status", s) - klogErrorS := klog.ErrorS + logger.V(4).Info("Container runtime status", "status", s) + loggerInfo := logger.Info if !kl.containerRuntimeReadyExpected { - klogErrorS = klog.V(4).ErrorS + loggerInfo = logger.V(4).Info } networkReady := s.GetRuntimeCondition(kubecontainer.NetworkReady) if networkReady == nil || !networkReady.Status { - klogErrorS(nil, "Container runtime network not ready", "networkReady", networkReady) + loggerInfo("Container runtime network not ready", "networkReady", networkReady) kl.runtimeState.setNetworkState(fmt.Errorf("container runtime network not ready: %v", networkReady)) } else { // Set nil if the container runtime network is ready. @@ -3152,7 +3294,7 @@ func (kl *Kubelet) updateRuntimeUp() { runtimeReady := s.GetRuntimeCondition(kubecontainer.RuntimeReady) // If RuntimeReady is not set or is false, report an error. if runtimeReady == nil || !runtimeReady.Status { - klogErrorS(nil, "Container runtime not ready", "runtimeReady", runtimeReady) + loggerInfo("Container runtime not ready", "runtimeReady", runtimeReady) kl.runtimeState.setRuntimeState(fmt.Errorf("container runtime not ready: %v", runtimeReady)) return } @@ -3178,9 +3320,9 @@ func (kl *Kubelet) BirthCry() { } // ListenAndServe runs the kubelet HTTP server. -func (kl *Kubelet) ListenAndServe(ctx context.Context, kubeCfg *kubeletconfiginternal.KubeletConfiguration, tlsOptions *server.TLSOptions, +func (kl *Kubelet) ListenAndServe(ctx context.Context, kubeCfg *kubeletconfiginternal.KubeletConfiguration, tlsConfig *tls.Config, auth server.AuthInterface, tp trace.TracerProvider) { - server.ListenAndServeKubeletServer(ctx, kl, kl.resourceAnalyzer, kl.containerManager.GetHealthCheckers(), kl.flagz, kubeCfg, tlsOptions, auth, tp) + server.ListenAndServeKubeletServer(ctx, kl, kl.resourceAnalyzer, kl.containerManager.GetHealthCheckers(), kl.flagz, kubeCfg, tlsConfig, auth, tp) } // ListenAndServeReadOnly runs the kubelet HTTP server in read-only mode. @@ -3201,30 +3343,56 @@ func (pp *kubeletPodsProvider) GetPods() []*v1.Pod { } func (pp *kubeletPodsProvider) GetPodByName(namespace, name string) (*v1.Pod, bool) { - return pp.kl.podManager.GetPodByName(namespace, name) + pod, found := pp.kl.podManager.GetPodByName(namespace, name) + if !found { + return nil, false + } + // use the same filtering logic as GetActivePods + if pp.kl.isPodInactive(pod) { + return nil, false + } + return pod, true } -// ListenAndServePodResources runs the kubelet podresources grpc service +// ListenAndServePodResources runs the kubelet podresources grpc service. func (kl *Kubelet) ListenAndServePodResources(ctx context.Context) { - endpoint, err := util.LocalEndpoint(kl.getPodResourcesDir(), podresources.Socket) - if err != nil { - klog.V(2).InfoS("Failed to get local endpoint for PodResources endpoint", "err", err) - return - } + if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPodResourcesGet) { + endpoint, err := util.LocalEndpoint(kl.getPodResourcesDir(), podresources.Socket) + if err != nil { + klog.FromContext(ctx).V(2).Info("Failed to get local endpoint for PodResources endpoint", "err", err) + return + } + + providers := podresources.PodResourcesProviders{ + Pods: &kubeletPodsProvider{kl: kl}, + Devices: kl.containerManager, + Cpus: kl.containerManager, + Memory: kl.containerManager, + DynamicResources: kl.containerManager, + } - providers := podresources.PodResourcesProviders{ - Pods: &kubeletPodsProvider{kl: kl}, - Devices: kl.containerManager, - Cpus: kl.containerManager, - Memory: kl.containerManager, - DynamicResources: kl.containerManager, + server.ListenAndServePodResources(ctx, endpoint, providers) } +} - server.ListenAndServePodResources(ctx, endpoint, providers) +// ListenAndServePod initializes an HTTP server to serve the Pod API. +func (kl *Kubelet) ListenAndServePods(ctx context.Context) { + if utilfeature.DefaultFeatureGate.Enabled(features.PodsAPI) { + endpoint, err := util.LocalEndpoint(kl.getPodsAPIDir(), pods.Socket) + if err != nil { + klog.FromContext(ctx).Error(err, "Failed to get local endpoint for pod api") + return + } + server.ListenAndServePodsServer( + ctx, + endpoint, + kl.podsServer, + ) + } } // Delete the eligible dead container instances in a pod. Depending on the configuration, the latest dead containers may be kept around. -func (kl *Kubelet) cleanUpContainersInPod(podID types.UID, exitedContainerID string) { +func (kl *Kubelet) cleanUpContainersInPod(ctx context.Context, podID types.UID, exitedContainerID string) { if podStatus, err := kl.podCache.Get(podID); err == nil { // When an evicted or deleted pod has already synced, all containers can be removed. removeAll := kl.podWorkers.ShouldPodContentBeRemoved(podID) @@ -3327,6 +3495,7 @@ func (kl *Kubelet) UnprepareDynamicResources(ctx context.Context, pod *v1.Pod) e // Ensure Mirror Pod for Static Pod exists and matches the current pod definition. // The function logs and ignores any errors. func (kl *Kubelet) tryReconcileMirrorPods(ctx context.Context, staticPod, mirrorPod *v1.Pod) { + logger := klog.FromContext(ctx) if !kubetypes.IsStaticPod(staticPod) { return } @@ -3335,26 +3504,26 @@ func (kl *Kubelet) tryReconcileMirrorPods(ctx context.Context, staticPod, mirror if mirrorPod.DeletionTimestamp != nil || !kubepod.IsMirrorPodOf(mirrorPod, staticPod) { // The mirror pod is semantically different from the static pod. Remove // it. The mirror pod will get recreated later. - klog.InfoS("Trying to delete pod", "pod", klog.KObj(mirrorPod), "podUID", mirrorPod.UID) + logger.Info("Trying to delete pod", "pod", klog.KObj(mirrorPod), "podUID", mirrorPod.UID) podFullName := kubecontainer.GetPodFullName(staticPod) if ok, err := kl.mirrorPodClient.DeleteMirrorPod(ctx, podFullName, &mirrorPod.UID); err != nil { - klog.ErrorS(err, "Failed deleting mirror pod", "pod", klog.KObj(mirrorPod)) + logger.Error(err, "Failed deleting mirror pod", "pod", klog.KObj(mirrorPod)) } else if ok { deleted = ok - klog.InfoS("Deleted mirror pod as it didn't match the static Pod", "pod", klog.KObj(mirrorPod)) + logger.Info("Deleted mirror pod as it didn't match the static Pod", "pod", klog.KObj(mirrorPod)) } } } if mirrorPod == nil || deleted { - node, err := kl.GetNode() + node, err := kl.GetNode(ctx) if err != nil { - klog.ErrorS(err, "No need to create a mirror pod, since failed to get node info from the cluster", "node", klog.KRef("", string(kl.nodeName))) + logger.Error(err, "No need to create a mirror pod, since failed to get node info from the cluster", "node", klog.KRef("", string(kl.nodeName))) } else if node.DeletionTimestamp != nil { - klog.InfoS("No need to create a mirror pod, since node has been removed from the cluster", "node", klog.KRef("", string(kl.nodeName))) + logger.Info("No need to create a mirror pod, since node has been removed from the cluster", "node", klog.KRef("", string(kl.nodeName))) } else { - klog.InfoS("Creating a mirror pod for static pod", "pod", klog.KObj(staticPod)) + logger.Info("Creating a mirror pod for static pod", "pod", klog.KObj(staticPod)) if err := kl.mirrorPodClient.CreateMirrorPod(ctx, staticPod); err != nil { - klog.ErrorS(err, "Failed creating a mirror pod", "pod", klog.KObj(staticPod)) + logger.Error(err, "Failed creating a mirror pod", "pod", klog.KObj(staticPod)) } } } @@ -3362,16 +3531,17 @@ func (kl *Kubelet) tryReconcileMirrorPods(ctx context.Context, staticPod, mirror // Ensure Mirror Pod for Static Pod exists as soon as node is registered. func (kl *Kubelet) fastStaticPodsRegistration(ctx context.Context) { + logger := klog.FromContext(ctx) if err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (bool, error) { - _, err := kl.GetNode() + _, err := kl.GetNode(ctx) if err == nil { return true, nil } - klog.V(4).ErrorS(err, "Unable to register mirror pod because node is not registered yet", "node", klog.KRef("", string(kl.nodeName))) + logger.Error(err, "Unable to register mirror pod because node is not registered yet", "node", klog.KRef("", string(kl.nodeName))) return false, nil }); err != nil { - klog.V(4).ErrorS(err, "Failed to wait until node is registered", "node", klog.KRef("", string(kl.nodeName))) + logger.Error(err, "Failed to wait until node is registered", "node", klog.KRef("", string(kl.nodeName))) } staticPodToMirrorPodMap := kl.podManager.GetStaticPodToMirrorPodMap() @@ -3380,6 +3550,55 @@ func (kl *Kubelet) fastStaticPodsRegistration(ctx context.Context) { } } -func (kl *Kubelet) SetPodWatchCondition(podUID types.UID, conditionKey string, condition pleg.WatchCondition) { - kl.pleg.SetPodWatchCondition(podUID, conditionKey, condition) +func (kl *Kubelet) RequestPodReinspect(podUID types.UID) { + kl.pleg.RequestReinspect(podUID) +} + +func (kl *Kubelet) RequestPodRelist(podUID types.UID) { + kl.pleg.RequestRelist(podUID) +} + +func (kl *Kubelet) syncPodNow(pod *v1.Pod) { + kl.HandlePodSyncs(context.Background(), []*v1.Pod{pod}) + kl.RequestPodRelist(pod.UID) +} + +// OnPodSandboxReady is the callback implementation invoked by the container runtime after +// all three requirements (sandbox, networking, volumes) are ready to immediately update +// the `PodReadyToStartContainers` pod status condition to `True`. +// This method implements the RuntimeHelper interface. +// Ref: https://github.com/kubernetes/kubernetes/issues/134460 +func (kl *Kubelet) OnPodSandboxReady(ctx context.Context, pod *v1.Pod) error { + if !utilfeature.DefaultFeatureGate.Enabled(features.PodReadyToStartContainersCondition) { + return nil + } + + logger := klog.FromContext(ctx) + logger.V(3).Info("OnPodSandboxReady callback invoked", "pod", klog.KObj(pod), "podUID", pod.UID) + + // update the status asynchronously to avoid blocking the SyncPod flow + go func() { + existingStatus, ok := kl.statusManager.GetPodStatus(pod.UID) + if !ok { + existingStatus = pod.Status + } + + cachedStatus := existingStatus.DeepCopy() + + readySandboxCondition := v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + ObservedGeneration: podutil.CalculatePodConditionObservedGeneration(cachedStatus, pod.Generation, v1.PodReadyToStartContainers), + } + + cachedStatus.Conditions = utilpod.ReplaceOrAppendPodCondition(cachedStatus.Conditions, &readySandboxCondition) + + kl.statusManager.SetPodStatus(logger, pod, *cachedStatus) + + logger.V(3).Info("Successfully updated PodReadyToStartContainers condition after sandbox creation", + "pod", klog.KObj(pod), + "podUID", pod.UID) + }() + + return nil } diff --git a/pkg/kubelet/kubelet_getters.go b/pkg/kubelet/kubelet_getters.go index b822d4e9d08df..f35c69677921a 100644 --- a/pkg/kubelet/kubelet_getters.go +++ b/pkg/kubelet/kubelet_getters.go @@ -133,8 +133,8 @@ func (kl *Kubelet) HandlerSupportsUserNamespaces(rtHandler string) (bool, error) } // GetKubeletMappings gets the additional IDs allocated for the Kubelet. -func (kl *Kubelet) GetKubeletMappings(idsPerPod uint32) (uint32, uint32, error) { - return kl.getKubeletMappings(idsPerPod) +func (kl *Kubelet) GetKubeletMappings(logger klog.Logger, idsPerPod uint32) (uint32, uint32, error) { + return kl.getKubeletMappings(logger, idsPerPod) } func (kl *Kubelet) GetMaxPods() int { @@ -216,11 +216,16 @@ func (kl *Kubelet) getPodContainerDir(podUID types.UID, ctrName string) string { return filepath.Join(kl.getPodDir(podUID), kubeletconfig.DefaultKubeletContainersDirName, ctrName) } -// getPodResourcesSocket returns the full path to the directory containing the pod resources socket +// getPodResourcesDir returns the full path to the directory containing the pod resources socket func (kl *Kubelet) getPodResourcesDir() string { return filepath.Join(kl.getRootDir(), kubeletconfig.DefaultKubeletPodResourcesDirName) } +// getPodsAPIDir returns the full path to the directory containing the pods API socket +func (kl *Kubelet) getPodsAPIDir() string { + return filepath.Join(kl.getRootDir(), kubeletconfig.DefaultKubeletPodsAPIDirName) +} + // GetPods returns all pods bound to the kubelet and their spec, and the mirror // pods. func (kl *Kubelet) GetPods() []*v1.Pod { @@ -286,9 +291,9 @@ func (kl *Kubelet) getRuntime() kubecontainer.Runtime { } // GetNode returns the node info for the configured node name of this Kubelet. -func (kl *Kubelet) GetNode() (*v1.Node, error) { +func (kl *Kubelet) GetNode(ctx context.Context) (*v1.Node, error) { if kl.kubeClient == nil { - return kl.initialNode(context.TODO()) + return kl.initialNode(ctx) } return kl.nodeLister.Get(string(kl.nodeName)) } @@ -298,11 +303,11 @@ func (kl *Kubelet) GetNode() (*v1.Node, error) { // Return kubelet's nodeInfo for this node, except on error or if in standalone mode, // in which case return a manufactured nodeInfo representing a node with no pods, // zero capacity, and the default labels. -func (kl *Kubelet) getNodeAnyWay() (*v1.Node, error) { - if n, err := kl.GetNode(); err == nil { +func (kl *Kubelet) getNodeAnyWay(ctx context.Context) (*v1.Node, error) { + if n, err := kl.GetNode(ctx); err == nil { return n, nil } - return kl.initialNode(context.TODO()) + return kl.initialNode(ctx) } // GetNodeConfig returns the container manager node config. @@ -317,8 +322,8 @@ func (kl *Kubelet) GetPodCgroupRoot() string { // getHostIPsAnyWay attempts to return the host IPs from kubelet's nodeInfo, or // the initialNode. -func (kl *Kubelet) getHostIPsAnyWay() ([]net.IP, error) { - node, err := kl.getNodeAnyWay() +func (kl *Kubelet) getHostIPsAnyWay(ctx context.Context) ([]net.IP, error) { + node, err := kl.getNodeAnyWay(ctx) if err != nil { return nil, err } @@ -334,20 +339,20 @@ func (kl *Kubelet) GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64 { // getPodVolumePathListFromDisk returns a list of the volume paths by reading the // volume directories for the given pod from the disk. -func (kl *Kubelet) getPodVolumePathListFromDisk(podUID types.UID) ([]string, error) { +func (kl *Kubelet) getPodVolumePathListFromDisk(logger klog.Logger, podUID types.UID) ([]string, error) { volumes := []string{} podVolDir := kl.getPodVolumesDir(podUID) if pathExists, pathErr := mount.PathExists(podVolDir); pathErr != nil { return volumes, fmt.Errorf("error checking if path %q exists: %v", podVolDir, pathErr) } else if !pathExists { - klog.V(6).InfoS("Path does not exist", "path", podVolDir) + logger.V(6).Info("Path does not exist", "path", podVolDir) return volumes, nil } volumePluginDirs, err := os.ReadDir(podVolDir) if err != nil { - klog.ErrorS(err, "Could not read directory", "path", podVolDir) + logger.Error(err, "Could not read directory", "path", podVolDir) return volumes, err } for _, volumePluginDir := range volumePluginDirs { @@ -378,9 +383,9 @@ func (kl *Kubelet) getPodVolumePathListFromDisk(podUID types.UID) ([]string, err return volumes, nil } -func (kl *Kubelet) getMountedVolumePathListFromDisk(podUID types.UID) ([]string, error) { +func (kl *Kubelet) getMountedVolumePathListFromDisk(logger klog.Logger, podUID types.UID) ([]string, error) { mountedVolumes := []string{} - volumePaths, err := kl.getPodVolumePathListFromDisk(podUID) + volumePaths, err := kl.getPodVolumePathListFromDisk(logger, podUID) if err != nil { return mountedVolumes, err } @@ -403,7 +408,7 @@ func (kl *Kubelet) getMountedVolumePathListFromDisk(podUID types.UID) ([]string, // getPodVolumeSubpathListFromDisk returns a list of the volume-subpath paths by reading the // subpath directories for the given pod from the disk. -func (kl *Kubelet) getPodVolumeSubpathListFromDisk(podUID types.UID) ([]string, error) { +func (kl *Kubelet) getPodVolumeSubpathListFromDisk(logger klog.Logger, podUID types.UID) ([]string, error) { volumes := []string{} podSubpathsDir := kl.getPodVolumeSubpathsDir(podUID) @@ -416,7 +421,7 @@ func (kl *Kubelet) getPodVolumeSubpathListFromDisk(podUID types.UID) ([]string, // Explicitly walks /// volumePluginDirs, err := os.ReadDir(podSubpathsDir) if err != nil { - klog.ErrorS(err, "Could not read directory", "path", podSubpathsDir) + logger.Error(err, "Could not read directory", "path", podSubpathsDir) return volumes, err } for _, volumePluginDir := range volumePluginDirs { @@ -467,10 +472,11 @@ func (kl *Kubelet) setCachedMachineInfo(info *cadvisorapiv1.MachineInfo) { } // getLastStableNodeAddresses returns the last observed node addresses. -func (kl *Kubelet) getLastObservedNodeAddresses() []v1.NodeAddress { - node, err := kl.GetNode() +func (kl *Kubelet) getLastObservedNodeAddresses(ctx context.Context) []v1.NodeAddress { + logger := klog.FromContext(ctx) + node, err := kl.GetNode(ctx) if err != nil || node == nil { - klog.V(4).InfoS("fail to obtain node from local cache", "node", kl.nodeName, "error", err) + logger.V(4).Info("fail to obtain node from local cache", "node", kl.nodeName, "error", err) return nil } return node.Status.Addresses diff --git a/pkg/kubelet/kubelet_getters_test.go b/pkg/kubelet/kubelet_getters_test.go index abd08432b86b4..07d1a5abb431f 100644 --- a/pkg/kubelet/kubelet_getters_test.go +++ b/pkg/kubelet/kubelet_getters_test.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" cloudproviderapi "k8s.io/cloud-provider/api" + "k8s.io/klog/v2/ktesting" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" ) @@ -99,6 +100,10 @@ func TestKubeletDirs(t *testing.T) { exp = filepath.Join(root, "pod-resources") assert.Equal(t, exp, got) + got = kubelet.getPodsAPIDir() + exp = filepath.Join(root, "pods-api") + assert.Equal(t, exp, got) + got = kubelet.getPodVolumeSubpathsDir("abc123") exp = filepath.Join(root, "pods/abc123/volume-subpaths") assert.Equal(t, exp, got) @@ -285,6 +290,7 @@ func Test_getLastObservedNodeAddresses(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -295,7 +301,7 @@ func Test_getLastObservedNodeAddresses(t *testing.T) { nodeLister.nodes = append(nodeLister.nodes, tc.node) } kl.nodeLister = nodeLister - addrs := kl.getLastObservedNodeAddresses() + addrs := kl.getLastObservedNodeAddresses(ctx) if len(addrs) != len(tc.expectedAddrs) { t.Errorf("expected %d addresses, got %d", len(tc.expectedAddrs), len(addrs)) diff --git a/pkg/kubelet/kubelet_network.go b/pkg/kubelet/kubelet_network.go index 45b208f960a1b..b6f0aadaa2d0b 100644 --- a/pkg/kubelet/kubelet_network.go +++ b/pkg/kubelet/kubelet_network.go @@ -28,6 +28,7 @@ import ( // updatePodCIDR updates the pod CIDR in the runtime state if it is different // from the current CIDR. Return true if pod CIDR is actually changed. func (kl *Kubelet) updatePodCIDR(ctx context.Context, cidr string) (bool, error) { + logger := klog.FromContext(ctx) kl.updatePodCIDRMux.Lock() defer kl.updatePodCIDRMux.Unlock() @@ -44,7 +45,7 @@ func (kl *Kubelet) updatePodCIDR(ctx context.Context, cidr string) (bool, error) // But it is better to be on the safe side to still return true here. return true, fmt.Errorf("failed to update pod CIDR: %v", err) } - klog.InfoS("Updating Pod CIDR", "originalPodCIDR", podCIDR, "newPodCIDR", cidr) + logger.Info("Updating Pod CIDR", "originalPodCIDR", podCIDR, "newPodCIDR", cidr) kl.runtimeState.setPodCIDR(cidr) return true, nil } @@ -52,8 +53,6 @@ func (kl *Kubelet) updatePodCIDR(ctx context.Context, cidr string) (bool, error) // GetPodDNS returns DNS settings for the pod. // This function is defined in kubecontainer.RuntimeHelper interface so we // have to implement it. -func (kl *Kubelet) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) { - // Use context.TODO() because we currently do not have a proper context to pass in. - // Replace this with an appropriate context when refactoring this function to accept a context parameter. - return kl.dnsConfigurer.GetPodDNS(context.TODO(), pod) +func (kl *Kubelet) GetPodDNS(ctx context.Context, pod *v1.Pod) (*runtimeapi.DNSConfig, error) { + return kl.dnsConfigurer.GetPodDNS(ctx, pod) } diff --git a/pkg/kubelet/kubelet_network_linux.go b/pkg/kubelet/kubelet_network_linux.go index 05cad40e860fc..b330cce559df5 100644 --- a/pkg/kubelet/kubelet_network_linux.go +++ b/pkg/kubelet/kubelet_network_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -36,7 +35,7 @@ const ( KubeFirewallChain utiliptables.Chain = "KUBE-FIREWALL" ) -func (kl *Kubelet) initNetworkUtil() { +func (kl *Kubelet) initNetworkUtil(logger klog.Logger) { iptClients := utiliptables.NewBestEffort() if len(iptClients) == 0 { // We don't log this as an error because kubelet itself doesn't need any @@ -44,33 +43,33 @@ func (kl *Kubelet) initNetworkUtil() { // and because we *expect* this to fail on hosts where only nftables is // supported (in which case there can't be any other components using // iptables that would need these rules anyway). - klog.InfoS("No iptables support on this system; not creating the KUBE-IPTABLES-HINT chain") + logger.Info("No iptables support on this system; not creating the KUBE-IPTABLES-HINT chain") return } for family := range iptClients { iptClient := iptClients[family] - if kl.syncIPTablesRules(iptClient) { - klog.InfoS("Initialized iptables rules.", "protocol", iptClient.Protocol()) + if kl.syncIPTablesRules(logger, iptClient) { + logger.Info("Initialized iptables rules.", "protocol", iptClient.Protocol()) go iptClient.Monitor( utiliptables.Chain("KUBE-KUBELET-CANARY"), []utiliptables.Table{utiliptables.TableMangle, utiliptables.TableNAT, utiliptables.TableFilter}, - func() { kl.syncIPTablesRules(iptClient) }, + func() { kl.syncIPTablesRules(logger, iptClient) }, 1*time.Minute, wait.NeverStop, ) } else { - klog.InfoS("Failed to initialize iptables rules; some functionality may be missing.", "protocol", iptClient.Protocol()) + logger.Info("Failed to initialize iptables rules; some functionality may be missing.", "protocol", iptClient.Protocol()) } } } // syncIPTablesRules ensures the KUBE-IPTABLES-HINT chain exists, and the martian packet // protection rule is installed. -func (kl *Kubelet) syncIPTablesRules(iptClient utiliptables.Interface) bool { +func (kl *Kubelet) syncIPTablesRules(logger klog.Logger, iptClient utiliptables.Interface) bool { // Create hint chain so other components can see whether we are using iptables-legacy // or iptables-nft. if _, err := iptClient.EnsureChain(utiliptables.TableMangle, KubeIPTablesHintChain); err != nil { - klog.ErrorS(err, "Failed to ensure that iptables hint chain exists") + logger.Error(err, "Failed to ensure that iptables hint chain exists") return false } @@ -84,16 +83,16 @@ func (kl *Kubelet) syncIPTablesRules(iptClient utiliptables.Interface) bool { // created by kube-proxy. if _, err := iptClient.EnsureChain(utiliptables.TableFilter, KubeFirewallChain); err != nil { - klog.ErrorS(err, "Failed to ensure that filter table KUBE-FIREWALL chain exists") + logger.Error(err, "Failed to ensure that filter table KUBE-FIREWALL chain exists") return false } if _, err := iptClient.EnsureRule(utiliptables.Prepend, utiliptables.TableFilter, utiliptables.ChainOutput, "-j", string(KubeFirewallChain)); err != nil { - klog.ErrorS(err, "Failed to ensure that OUTPUT chain jumps to KUBE-FIREWALL") + logger.Error(err, "Failed to ensure that OUTPUT chain jumps to KUBE-FIREWALL") return false } if _, err := iptClient.EnsureRule(utiliptables.Prepend, utiliptables.TableFilter, utiliptables.ChainInput, "-j", string(KubeFirewallChain)); err != nil { - klog.ErrorS(err, "Failed to ensure that INPUT chain jumps to KUBE-FIREWALL") + logger.Error(err, "Failed to ensure that INPUT chain jumps to KUBE-FIREWALL") return false } @@ -110,7 +109,7 @@ func (kl *Kubelet) syncIPTablesRules(iptClient utiliptables.Interface) bool { "-m", "conntrack", "!", "--ctstate", "RELATED,ESTABLISHED,DNAT", "-j", "DROP"); err != nil { - klog.ErrorS(err, "Failed to ensure rule to drop invalid localhost packets in filter table KUBE-FIREWALL chain") + logger.Error(err, "Failed to ensure rule to drop invalid localhost packets in filter table KUBE-FIREWALL chain") return false } } diff --git a/pkg/kubelet/kubelet_network_others.go b/pkg/kubelet/kubelet_network_others.go index 666849166c775..0901debe8442b 100644 --- a/pkg/kubelet/kubelet_network_others.go +++ b/pkg/kubelet/kubelet_network_others.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2018 The Kubernetes Authors. @@ -19,5 +18,7 @@ limitations under the License. package kubelet +import "k8s.io/klog/v2" + // Do nothing. -func (kl *Kubelet) initNetworkUtil() {} +func (kl *Kubelet) initNetworkUtil(logger klog.Logger) {} diff --git a/pkg/kubelet/kubelet_node_declared_features.go b/pkg/kubelet/kubelet_node_declared_features.go index 9bbe246b8f76b..e05b9c46dfd6c 100644 --- a/pkg/kubelet/kubelet_node_declared_features.go +++ b/pkg/kubelet/kubelet_node_declared_features.go @@ -36,15 +36,17 @@ func (a FeatureGateAdapter) Enabled(key string) bool { // discoverNodeDeclaredFeatures determines the final set of node features to be declared by using the discovery library. func (kl *Kubelet) discoverNodeDeclaredFeatures() []string { - staticConfig := nodedeclaredfeatures.StaticConfiguration{ - CPUManagerPolicy: kl.containerManager.GetNodeConfig().CPUManagerPolicy, + adaptedFG := FeatureGateAdapter{FeatureGate: utilfeature.DefaultFeatureGate} + + runtimeFeatures := nodedeclaredfeatures.RuntimeFeatures{} + if features := kl.runtimeState.runtimeFeatures(); features != nil { + runtimeFeatures.UserNamespacesHostNetwork = features.UserNamespacesHostNetwork } - adaptedFG := FeatureGateAdapter{FeatureGate: utilfeature.DefaultFeatureGate} cfg := &nodedeclaredfeatures.NodeConfiguration{ - FeatureGates: adaptedFG, - StaticConfig: staticConfig, - Version: kl.version, + FeatureGates: adaptedFG, + Version: kl.version, + RuntimeFeatures: runtimeFeatures, } return kl.nodeDeclaredFeaturesFramework.DiscoverNodeFeatures(cfg) } diff --git a/pkg/kubelet/kubelet_node_declared_features_test.go b/pkg/kubelet/kubelet_node_declared_features_test.go index 2228845b6a1be..e0450a300edc8 100644 --- a/pkg/kubelet/kubelet_node_declared_features_test.go +++ b/pkg/kubelet/kubelet_node_declared_features_test.go @@ -20,74 +20,122 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" - ndflib "k8s.io/component-helpers/nodedeclaredfeatures" + ndf "k8s.io/component-helpers/nodedeclaredfeatures" ndffeatures "k8s.io/component-helpers/nodedeclaredfeatures/features" + ndftesting "k8s.io/component-helpers/nodedeclaredfeatures/testing" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/cm" ) -func TestGuaranteedPodExclusiveCPUsFeatureDiscovery(t *testing.T) { +func TestDeclaredFeatureDiscovery(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true) + podLevelResourcesIPPRFeatureGate := features.InPlacePodLevelResourcesVerticalScaling + featureMaxVersion := version.MustParseSemantic("v1.36.0") + createMockFeature := func(t *testing.T, name string, cfg *ndf.NodeConfiguration) *ndftesting.MockFeature { + mockFeature := ndftesting.NewMockFeature(t) + mockFeature.SetName(name) + mockFeature.SetMaxVersion(featureMaxVersion) + mockFeature.SetDiscover(func(*ndf.NodeConfiguration) bool { return cfg.FeatureGates.Enabled(name) }) + return mockFeature + } + testcases := []struct { - name string - cpuManagerPolicy string - inplacePodResizeExclusiveCPUsEnable bool - expectedFeature string - expectFeature bool + name string + podLevelResourcesIPPREnabled bool + featureDiscovered bool + kubeletVersion *version.Version }{ { - name: "feature enabled with static cpu manager policy", - cpuManagerPolicy: "static", - inplacePodResizeExclusiveCPUsEnable: true, - expectedFeature: "GuaranteedQoSPodCPUResize", - expectFeature: true, + name: "feature gate enabled, feature discovered", + podLevelResourcesIPPREnabled: true, + featureDiscovered: true, + kubeletVersion: featureMaxVersion.SubtractMinor(1), + }, + { + name: "feature gate enabled, feature not discovered as kubelet version higher than max version", + podLevelResourcesIPPREnabled: true, + featureDiscovered: false, + kubeletVersion: featureMaxVersion.AddMinor(1), }, { - name: "feature enabled with none cpu manager policy", - cpuManagerPolicy: "none", - inplacePodResizeExclusiveCPUsEnable: true, - expectedFeature: "GuaranteedQoSPodCPUResize", - expectFeature: true, + name: "feature disabled and not discovered", + podLevelResourcesIPPREnabled: false, + featureDiscovered: false, + kubeletVersion: featureMaxVersion.SubtractMinor(1), }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, podLevelResourcesIPPRFeatureGate, tc.podLevelResourcesIPPREnabled) + + cfg := &ndf.NodeConfiguration{ + FeatureGates: FeatureGateAdapter{ + FeatureGate: utilfeature.DefaultFeatureGate, + }, + Version: tc.kubeletVersion, + } + registeredFeatures := []ndf.Feature{ + createMockFeature(t, string(podLevelResourcesIPPRFeatureGate), cfg), + } + + testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) + defer testKubelet.Cleanup() + kubelet := testKubelet.kubelet + fakeCM := cm.NewFakeContainerManagerWithNodeConfig(cm.NodeConfig{}) + kubelet.containerManager = fakeCM + framework := ndf.New(registeredFeatures) + kubelet.nodeDeclaredFeaturesFramework = framework + kubelet.version = tc.kubeletVersion + + features := kubelet.discoverNodeDeclaredFeatures() + if tc.featureDiscovered { + assert.Contains(t, features, string(podLevelResourcesIPPRFeatureGate)) + } else { + assert.NotContains(t, features, string(podLevelResourcesIPPRFeatureGate)) + } + }) + } +} + +func TestExtendWebSocketsToKubeletFeatureDiscovery(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true) + + testcases := []struct { + name string + extendWebSocketsToKubeletGate bool + expectFeature bool + }{ { - name: "feature disabled with static cpu manager policy", - cpuManagerPolicy: "static", - inplacePodResizeExclusiveCPUsEnable: false, - expectedFeature: "GuaranteedQoSPodCPUResize", - expectFeature: false, + name: "ExtendWebSocketsToKubelet feature enabled", + extendWebSocketsToKubeletGate: true, + expectFeature: true, }, { - name: "feature disabled with none cpu manager policy", - cpuManagerPolicy: "none", - inplacePodResizeExclusiveCPUsEnable: false, - expectedFeature: "GuaranteedQoSPodCPUResize", - expectFeature: true, + name: "ExtendWebSocketsToKubelet feature disabled", + extendWebSocketsToKubeletGate: false, + expectFeature: false, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingExclusiveCPUs, tc.inplacePodResizeExclusiveCPUsEnable) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tc.extendWebSocketsToKubeletGate) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet - fakeCM := cm.NewFakeContainerManagerWithNodeConfig(cm.NodeConfig{ - CPUManagerPolicy: tc.cpuManagerPolicy, - }) - kubelet.containerManager = fakeCM - framework, err := ndflib.New(ndffeatures.AllFeatures) - require.NoError(t, err) + + framework := ndf.New(ndffeatures.AllFeatures) kubelet.nodeDeclaredFeaturesFramework = framework features := kubelet.discoverNodeDeclaredFeatures() if tc.expectFeature { - assert.Contains(t, features, tc.expectedFeature) + assert.Contains(t, features, "ExtendWebSocketsToKubelet") } else { - assert.NotContains(t, features, tc.expectedFeature) + assert.NotContains(t, features, "ExtendWebSocketsToKubelet") } }) } diff --git a/pkg/kubelet/kubelet_node_status.go b/pkg/kubelet/kubelet_node_status.go index 2f64369bc5f71..fcf8e69c7e509 100644 --- a/pkg/kubelet/kubelet_node_status.go +++ b/pkg/kubelet/kubelet_node_status.go @@ -52,10 +52,11 @@ import ( // registerWithAPIServer registers the node with the cluster master. It is safe // to call multiple times, but not concurrently (kl.registrationCompleted is // not locked). -func (kl *Kubelet) registerWithAPIServer() { +func (kl *Kubelet) registerWithAPIServer(ctx context.Context) { if kl.registrationCompleted { return } + logger := klog.FromContext(ctx) kl.nodeStartupLatencyTracker.RecordAttemptRegisterNode() @@ -68,16 +69,16 @@ func (kl *Kubelet) registerWithAPIServer() { step = 7 * time.Second } - node, err := kl.initialNode(context.TODO()) + node, err := kl.initialNode(ctx) if err != nil { - klog.ErrorS(err, "Unable to construct v1.Node object for kubelet") + logger.Error(err, "Unable to construct v1.Node object for kubelet") continue } - klog.InfoS("Attempting to register node", "node", klog.KObj(node)) - registered := kl.tryRegisterWithAPIServer(node) + logger.Info("Attempting to register node", "node", klog.KObj(node)) + registered := kl.tryRegisterWithAPIServer(ctx, node) if registered { - klog.InfoS("Successfully registered node", "node", klog.KObj(node)) + logger.Info("Successfully registered node", "node", klog.KObj(node)) kl.registrationCompleted = true return } @@ -89,8 +90,9 @@ func (kl *Kubelet) registerWithAPIServer() { // successful. If a node with the same name already exists, it reconciles the // value of the annotation for controller-managed attach-detach of attachable // persistent volumes for the node. -func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool { - _, err := kl.kubeClient.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{}) +func (kl *Kubelet) tryRegisterWithAPIServer(ctx context.Context, node *v1.Node) bool { + logger := klog.FromContext(ctx) + _, err := kl.kubeClient.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}) if err == nil { kl.nodeStartupLatencyTracker.RecordRegisteredNewNode() return true @@ -102,43 +104,43 @@ func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool { case apierrors.IsForbidden(err): // Creating nodes is forbidden, but node may still exist, attempt to get the node. if utilfeature.DefaultFeatureGate.Enabled(features.KubeletRegistrationGetOnExistsOnly) { - klog.ErrorS(err, "Unable to register node with API server, reason is forbidden", "node", klog.KObj(node)) + logger.Error(err, "Unable to register node with API server, reason is forbidden", "node", klog.KObj(node)) return false } default: - klog.ErrorS(err, "Unable to register node with API server", "node", klog.KObj(node)) + logger.Error(err, "Unable to register node with API server", "node", klog.KObj(node)) return false } - existingNode, err := kl.kubeClient.CoreV1().Nodes().Get(context.TODO(), string(kl.nodeName), metav1.GetOptions{}) + existingNode, err := kl.kubeClient.CoreV1().Nodes().Get(ctx, string(kl.nodeName), metav1.GetOptions{}) if err != nil { - klog.ErrorS(err, "Unable to register node with API server, error getting existing node", "node", klog.KObj(node)) + logger.Error(err, "Unable to register node with API server, error getting existing node", "node", klog.KObj(node)) return false } if existingNode == nil { - klog.InfoS("Unable to register node with API server, no node instance returned", "node", klog.KObj(node)) + logger.Info("Unable to register node with API server, no node instance returned", "node", klog.KObj(node)) return false } originalNode := existingNode.DeepCopy() - klog.InfoS("Node was previously registered", "node", klog.KObj(node)) + logger.Info("Node was previously registered", "node", klog.KObj(node)) // Edge case: the node was previously registered; reconcile // the value of the controller-managed attach-detach // annotation. - requiresUpdate := kl.reconcileCMADAnnotationWithExistingNode(node, existingNode) + requiresUpdate := kl.reconcileCMADAnnotationWithExistingNode(logger, node, existingNode) requiresUpdate = kl.updateDefaultLabels(node, existingNode) || requiresUpdate - requiresUpdate = kl.reconcileExtendedResource(node, existingNode) || requiresUpdate - requiresUpdate = kl.reconcileHugePageResource(node, existingNode) || requiresUpdate + requiresUpdate = kl.reconcileExtendedResource(logger, node, existingNode) || requiresUpdate + requiresUpdate = kl.reconcileHugePageResource(logger, node, existingNode) || requiresUpdate if managed.IsEnabled() { requiresUpdate = kl.addManagementNodeCapacity(node, existingNode) || requiresUpdate } requiresUpdate = kl.reconcileSharedCPUsNodeCapacity(node, existingNode) || requiresUpdate if requiresUpdate { if _, _, err := nodeutil.PatchNodeStatus(kl.kubeClient.CoreV1(), types.NodeName(kl.nodeName), originalNode, existingNode); err != nil { - klog.ErrorS(err, "Unable to reconcile node with API server,error updating node", "node", klog.KObj(node)) + logger.Error(err, "Unable to reconcile node with API server,error updating node", "node", klog.KObj(node)) return false } } @@ -185,7 +187,7 @@ func (kl *Kubelet) reconcileSharedCPUsNodeCapacity(initialNode, existingNode *v1 } // reconcileHugePageResource will update huge page capacity for each page size and remove huge page sizes no longer supported -func (kl *Kubelet) reconcileHugePageResource(initialNode, existingNode *v1.Node) bool { +func (kl *Kubelet) reconcileHugePageResource(logger klog.Logger, initialNode, existingNode *v1.Node) bool { requiresUpdate := updateDefaultResources(initialNode, existingNode) supportedHugePageResources := sets.Set[string]{} @@ -224,7 +226,7 @@ func (kl *Kubelet) reconcileHugePageResource(initialNode, existingNode *v1.Node) if !supportedHugePageResources.Has(string(resourceName)) { delete(existingNode.Status.Capacity, resourceName) delete(existingNode.Status.Allocatable, resourceName) - klog.InfoS("Removing huge page resource which is no longer supported", "resourceName", resourceName) + logger.Info("Removing huge page resource which is no longer supported", "resourceName", resourceName) requiresUpdate = true } } @@ -232,13 +234,13 @@ func (kl *Kubelet) reconcileHugePageResource(initialNode, existingNode *v1.Node) } // Zeros out extended resource capacity during reconciliation. -func (kl *Kubelet) reconcileExtendedResource(initialNode, node *v1.Node) bool { +func (kl *Kubelet) reconcileExtendedResource(logger klog.Logger, initialNode, node *v1.Node) bool { requiresUpdate := updateDefaultResources(initialNode, node) // Check with the device manager to see if node has been recreated, in which case extended resources should be zeroed until they are available if kl.containerManager.ShouldResetExtendedResourceCapacity() { for k := range node.Status.Capacity { if v1helper.IsExtendedResourceName(k) { - klog.InfoS("Zero out resource capacity in existing node", "resourceName", k, "node", klog.KObj(node)) + logger.Info("Zero out resource capacity in existing node", "resourceName", k, "node", klog.KObj(node)) node.Status.Capacity[k] = *resource.NewQuantity(int64(0), resource.DecimalSI) node.Status.Allocatable[k] = *resource.NewQuantity(int64(0), resource.DecimalSI) requiresUpdate = true @@ -314,7 +316,7 @@ func (kl *Kubelet) updateDefaultLabels(initialNode, existingNode *v1.Node) bool // reconcileCMADAnnotationWithExistingNode reconciles the controller-managed // attach-detach annotation on a new node and the existing node, returning // whether the existing node must be updated. -func (kl *Kubelet) reconcileCMADAnnotationWithExistingNode(node, existingNode *v1.Node) bool { +func (kl *Kubelet) reconcileCMADAnnotationWithExistingNode(logger klog.Logger, node, existingNode *v1.Node) bool { var ( existingCMAAnnotation = existingNode.Annotations[volutil.ControllerManagedAttachAnnotation] newCMAAnnotation, newSet = node.Annotations[volutil.ControllerManagedAttachAnnotation] @@ -328,10 +330,10 @@ func (kl *Kubelet) reconcileCMADAnnotationWithExistingNode(node, existingNode *v // not have the same value, update the existing node with // the correct value of the annotation. if !newSet { - klog.InfoS("Controller attach-detach setting changed to false; updating existing Node") + logger.Info("Controller attach-detach setting changed to false; updating existing Node") delete(existingNode.Annotations, volutil.ControllerManagedAttachAnnotation) } else { - klog.InfoS("Controller attach-detach setting changed to true; updating existing Node") + logger.Info("Controller attach-detach setting changed to true; updating existing Node") if existingNode.Annotations == nil { existingNode.Annotations = make(map[string]string) } @@ -344,6 +346,7 @@ func (kl *Kubelet) reconcileCMADAnnotationWithExistingNode(node, existingNode *v // initialNode constructs the initial v1.Node for this Kubelet, incorporating node // labels, information from the cloud provider, and Kubelet configuration. func (kl *Kubelet) initialNode(ctx context.Context) (*v1.Node, error) { + logger := klog.FromContext(ctx) node := &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: string(kl.nodeName), @@ -385,16 +388,16 @@ func (kl *Kubelet) initialNode(ctx context.Context) (*v1.Node, error) { node.Annotations = make(map[string]string) } - klog.V(2).InfoS("Setting node annotation to enable volume controller attach/detach") + logger.V(2).Info("Setting node annotation to enable volume controller attach/detach") node.Annotations[volutil.ControllerManagedAttachAnnotation] = "true" } else { - klog.V(2).InfoS("Controller attach/detach is disabled for this node; Kubelet will attach and detach volumes") + logger.V(2).Info("Controller attach/detach is disabled for this node; Kubelet will attach and detach volumes") } // @question: should this be place after the call to the cloud provider? which also applies labels for k, v := range kl.nodeLabels { if cv, found := node.ObjectMeta.Labels[k]; found { - klog.InfoS("the node label will overwrite default setting", "labelKey", k, "labelValue", v, "default", cv) + logger.Info("the node label will overwrite default setting", "labelKey", k, "labelValue", v, "default", cv) } node.ObjectMeta.Labels[k] = v } @@ -419,6 +422,7 @@ func (kl *Kubelet) initialNode(ctx context.Context) (*v1.Node, error) { // syncNodeStatus. Its return value indicates whether the loop running it should exit // (final run), and it also sets kl.containerRuntimeReadyExpected. func (kl *Kubelet) fastNodeStatusUpdate(ctx context.Context, timeout bool) (completed bool) { + logger := klog.FromContext(ctx) kl.syncNodeStatusMux.Lock() defer func() { kl.syncNodeStatusMux.Unlock() @@ -433,19 +437,19 @@ func (kl *Kubelet) fastNodeStatusUpdate(ctx context.Context, timeout bool) (comp }() if timeout { - klog.ErrorS(nil, "Node not becoming ready in time after startup") + logger.Error(nil, "Node not becoming ready in time after startup") return true } - originalNode, err := kl.GetNode() + originalNode, err := kl.GetNode(ctx) if err != nil { - klog.ErrorS(err, "Error getting the current node from lister") + logger.Error(err, "Error getting the current node from lister") return false } readyIdx, originalNodeReady := nodeutil.GetNodeCondition(&originalNode.Status, v1.NodeReady) if readyIdx == -1 { - klog.ErrorS(nil, "Node does not have NodeReady condition", "originalNode", originalNode) + logger.Error(nil, "Node does not have NodeReady condition", "originalNode", originalNode) return false } @@ -455,7 +459,7 @@ func (kl *Kubelet) fastNodeStatusUpdate(ctx context.Context, timeout bool) (comp // This is in addition to the regular syncNodeStatus logic so we can get the container runtime status earlier. // This function itself has a mutex and it doesn't recursively call fastNodeStatusUpdate or syncNodeStatus. - kl.updateRuntimeUp() + kl.updateRuntimeUp(ctx) node, changed := kl.updateNode(ctx, originalNode) @@ -466,7 +470,7 @@ func (kl *Kubelet) fastNodeStatusUpdate(ctx context.Context, timeout bool) (comp readyIdx, nodeReady := nodeutil.GetNodeCondition(&node.Status, v1.NodeReady) if readyIdx == -1 { - klog.ErrorS(nil, "Node does not have NodeReady condition", "node", node) + logger.Error(nil, "Node does not have NodeReady condition", "node", node) return false } @@ -474,15 +478,15 @@ func (kl *Kubelet) fastNodeStatusUpdate(ctx context.Context, timeout bool) (comp return false } - klog.InfoS("Fast updating node status as it just became ready") + logger.Info("Fast updating node status as it just became ready") if _, err := kl.patchNodeStatus(originalNode, node); err != nil { // The originalNode is probably stale, but we know that the current state of kubelet would turn // the node to be ready. Retry using syncNodeStatus() which fetches from the apiserver. - klog.ErrorS(err, "Error updating node status, will retry with syncNodeStatus") + logger.Error(err, "Error updating node status, will retry with syncNodeStatus") // The reversed kl.syncNodeStatusMux.Unlock/Lock() below to allow kl.syncNodeStatus() execution. kl.syncNodeStatusMux.Unlock() - kl.syncNodeStatus() + kl.syncNodeStatus(ctx) // This lock action is unnecessary if we add a flag to check in the defer before unlocking it, // but having it here makes the logic a bit easier to read. kl.syncNodeStatusMux.Lock() @@ -495,33 +499,34 @@ func (kl *Kubelet) fastNodeStatusUpdate(ctx context.Context, timeout bool) (comp // syncNodeStatus should be called periodically from a goroutine. // It synchronizes node status to master if there is any change or enough time // passed from the last sync, registering the kubelet first if necessary. -func (kl *Kubelet) syncNodeStatus() { +func (kl *Kubelet) syncNodeStatus(ctx context.Context) { + logger := klog.FromContext(ctx) kl.syncNodeStatusMux.Lock() defer kl.syncNodeStatusMux.Unlock() - ctx := context.Background() if kl.kubeClient == nil || kl.heartbeatClient == nil { return } if kl.registerNode { // This will exit immediately if it doesn't need to do anything. - kl.registerWithAPIServer() + kl.registerWithAPIServer(ctx) } if err := kl.updateNodeStatus(ctx); err != nil { - klog.ErrorS(err, "Unable to update node status") + logger.Error(err, "Unable to update node status") } } // updateNodeStatus updates node status to master with retries if there is any // change or enough time passed from the last sync. func (kl *Kubelet) updateNodeStatus(ctx context.Context) error { - klog.V(5).InfoS("Updating node status") + logger := klog.FromContext(ctx) + logger.V(5).Info("Updating node status") for i := 0; i < nodeStatusUpdateRetry; i++ { if err := kl.tryUpdateNodeStatus(ctx, i); err != nil { if i > 0 && kl.onRepeatedHeartbeatFailure != nil { kl.onRepeatedHeartbeatFailure() } - klog.ErrorS(err, "Error updating node status, will retry") + logger.Error(err, "Error updating node status, will retry") } else { return nil } @@ -593,6 +598,7 @@ func (kl *Kubelet) calculateDelay() time.Duration { // updateNode creates a copy of originalNode and runs update logic on it. // It returns the updated node object and a bool indicating if anything has been changed. func (kl *Kubelet) updateNode(ctx context.Context, originalNode *v1.Node) (*v1.Node, bool) { + logger := klog.FromContext(ctx) node := originalNode.DeepCopy() podCIDRChanged := false @@ -603,7 +609,7 @@ func (kl *Kubelet) updateNode(ctx context.Context, originalNode *v1.Node) (*v1.N var err error podCIDRs := strings.Join(node.Spec.PodCIDRs, ",") if podCIDRChanged, err = kl.updatePodCIDR(ctx, podCIDRs); err != nil { - klog.ErrorS(err, "Error updating pod CIDR") + logger.Error(err, "Error updating pod CIDR") } } @@ -679,25 +685,26 @@ func (kl *Kubelet) markVolumesFromNode(node *v1.Node) { // recordNodeStatusEvent records an event of the given type with the given // message for the node. -func (kl *Kubelet) recordNodeStatusEvent(eventType, event string) { - klog.V(2).InfoS("Recording event message for node", "node", klog.KRef("", string(kl.nodeName)), "event", event) +func (kl *Kubelet) recordNodeStatusEvent(logger klog.Logger, eventType, event string) { + logger.V(2).Info("Recording event message for node", "node", klog.KRef("", string(kl.nodeName)), "event", event) kl.recorder.Eventf(kl.nodeRef, eventType, event, "Node %s status is now: %s", kl.nodeName, event) } // recordEvent records an event for this node, the Kubelet's nodeRef is passed to the recorder func (kl *Kubelet) recordEvent(eventType, event, message string) { - kl.recorder.Eventf(kl.nodeRef, eventType, event, message) + kl.recorder.Eventf(kl.nodeRef, eventType, event, "%s", message) } // record if node schedulable change. func (kl *Kubelet) recordNodeSchedulableEvent(ctx context.Context, node *v1.Node) error { + logger := klog.FromContext(ctx) kl.lastNodeUnschedulableLock.Lock() defer kl.lastNodeUnschedulableLock.Unlock() if kl.lastNodeUnschedulable != node.Spec.Unschedulable { if node.Spec.Unschedulable { - kl.recordNodeStatusEvent(v1.EventTypeNormal, events.NodeNotSchedulable) + kl.recordNodeStatusEvent(logger, v1.EventTypeNormal, events.NodeNotSchedulable) } else { - kl.recordNodeStatusEvent(v1.EventTypeNormal, events.NodeSchedulable) + kl.recordNodeStatusEvent(logger, v1.EventTypeNormal, events.NodeSchedulable) } kl.lastNodeUnschedulable = node.Spec.Unschedulable } @@ -709,10 +716,11 @@ func (kl *Kubelet) recordNodeSchedulableEvent(ctx context.Context, node *v1.Node // TODO(madhusudancs): Simplify the logic for setting node conditions and // refactor the node status condition code out to a different file. func (kl *Kubelet) setNodeStatus(ctx context.Context, node *v1.Node) { + logger := klog.FromContext(ctx) for i, f := range kl.setNodeStatusFuncs { - klog.V(5).InfoS("Setting node status condition code", "position", i, "node", klog.KObj(node)) + logger.V(5).Info("Setting node status condition code", "position", i, "node", klog.KObj(node)) if err := f(ctx, node); err != nil { - klog.ErrorS(err, "Failed to set some node status fields", "node", klog.KObj(node)) + logger.Error(err, "Failed to set some node status fields", "node", klog.KObj(node)) } } if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) && kl.nodeDeclaredFeatures != nil { diff --git a/pkg/kubelet/kubelet_node_status_others.go b/pkg/kubelet/kubelet_node_status_others.go index 2b4d8b5957400..7c411ba9af9fa 100644 --- a/pkg/kubelet/kubelet_node_status_others.go +++ b/pkg/kubelet/kubelet_node_status_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2019 The Kubernetes Authors. diff --git a/pkg/kubelet/kubelet_node_status_test.go b/pkg/kubelet/kubelet_node_status_test.go index ec87ab284658d..2b949a756c8c8 100644 --- a/pkg/kubelet/kubelet_node_status_test.go +++ b/pkg/kubelet/kubelet_node_status_test.go @@ -64,6 +64,7 @@ import ( kubeletvolume "k8s.io/kubernetes/pkg/kubelet/volumemanager" taintutil "k8s.io/kubernetes/pkg/util/taints" "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/kubernetes/test/utils/ktesting" netutils "k8s.io/utils/net" ) @@ -188,6 +189,7 @@ func (l delegatingNodeLister) List(selector labels.Selector) (ret []*v1.Node, er } func TestUpdateNewNodeStatus(t *testing.T) { + tCtx := ktesting.Init(t) cases := []struct { desc string nodeStatusMaxImages int32 @@ -204,7 +206,6 @@ func TestUpdateNewNodeStatus(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - ctx := context.Background() // generate one more in inputImageList than we configure the Kubelet to report, // or 5 images if unlimited numTestImages := int(tc.nodeStatusMaxImages) + 1 @@ -318,15 +319,15 @@ func TestUpdateNewNodeStatus(t *testing.T) { }, } - kubelet.updateRuntimeUp() - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + kubelet.updateRuntimeUp(tCtx) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() require.Len(t, actions, 2) require.True(t, actions[1].Matches("patch", "nodes")) require.Equal(t, "status", actions[1].GetSubresource()) updatedNode, err := applyNodeStatusPatch(&existingNode, actions[1].(core.PatchActionImpl).GetPatch()) - assert.NoError(t, err) + require.NoError(t, err) for i, cond := range updatedNode.Status.Conditions { assert.False(t, cond.LastHeartbeatTime.IsZero(), "LastHeartbeatTime for %v condition is zero", cond.Type) assert.False(t, cond.LastTransitionTime.IsZero(), "LastTransitionTime for %v condition is zero", cond.Type) @@ -344,7 +345,7 @@ func TestUpdateNewNodeStatus(t *testing.T) { } func TestUpdateExistingNodeStatus(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -508,8 +509,8 @@ func TestUpdateExistingNodeStatus(t *testing.T) { }, } - kubelet.updateRuntimeUp() - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + kubelet.updateRuntimeUp(tCtx) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() assert.Len(t, actions, 2) @@ -537,7 +538,7 @@ func TestUpdateExistingNodeStatus(t *testing.T) { } func TestUpdateExistingNodeStatusTimeout(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) if testing.Short() { t.Skip("skipping test in short mode.") } @@ -547,7 +548,7 @@ func TestUpdateExistingNodeStatusTimeout(t *testing.T) { // set up a listener that hangs connections ln, err := net.Listen("tcp", "127.0.0.1:0") - assert.NoError(t, err) + require.NoError(t, err) defer ln.Close() go func() { // accept connections and just let them hang @@ -567,7 +568,7 @@ func TestUpdateExistingNodeStatusTimeout(t *testing.T) { QPS: -1, Timeout: time.Second, } - assert.NoError(t, err) + require.NoError(t, err) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -591,7 +592,7 @@ func TestUpdateExistingNodeStatusTimeout(t *testing.T) { } // should return an error, but not hang - assert.Error(t, kubelet.updateNodeStatus(ctx)) + require.Error(t, kubelet.updateNodeStatus(tCtx)) // should have attempted multiple times if actualAttempts := atomic.LoadInt64(&attempts); actualAttempts < nodeStatusUpdateRetry { @@ -604,7 +605,7 @@ func TestUpdateExistingNodeStatusTimeout(t *testing.T) { } func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -715,13 +716,13 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { checkNodeStatus := func(status v1.ConditionStatus, reason string) { kubeClient.ClearActions() - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() require.Len(t, actions, 2) require.True(t, actions[1].Matches("patch", "nodes")) require.Equal(t, "status", actions[1].GetSubresource()) - updatedNode, err := kubeClient.CoreV1().Nodes().Get(ctx, testKubeletHostname, metav1.GetOptions{}) + updatedNode, err := kubeClient.CoreV1().Nodes().Get(tCtx, testKubeletHostname, metav1.GetOptions{}) require.NoError(t, err, "can't apply node status patch") for i, cond := range updatedNode.Status.Conditions { @@ -750,17 +751,17 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { // TODO(random-liu): Refactor the unit test to be table driven test. // Should report kubelet not ready if the runtime check is out of date clock.SetTime(time.Now().Add(-maxWaitForContainerRuntime)) - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") // Should report kubelet ready if the runtime check is updated clock.SetTime(time.Now()) - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionTrue, "KubeletReady") // Should report kubelet not ready if the runtime check is out of date clock.SetTime(time.Now().Add(-maxWaitForContainerRuntime)) - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") // Should report kubelet not ready if the runtime check failed @@ -768,19 +769,19 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { // Inject error into fake runtime status check, node should be NotReady fakeRuntime.StatusErr = fmt.Errorf("injected runtime status error") clock.SetTime(time.Now()) - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") fakeRuntime.StatusErr = nil // Should report node not ready if runtime status is nil. fakeRuntime.RuntimeStatus = nil - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") // Should report node not ready if runtime status is empty. fakeRuntime.RuntimeStatus = &kubecontainer.RuntimeStatus{} - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") // Should report node not ready if RuntimeReady is false. @@ -790,7 +791,7 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { {Type: kubecontainer.NetworkReady, Status: true}, }, } - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") // Should report node ready if RuntimeReady is true. @@ -800,7 +801,7 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { {Type: kubecontainer.NetworkReady, Status: true}, }, } - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionTrue, "KubeletReady") // Should report node not ready if NetworkReady is false. @@ -810,24 +811,24 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { {Type: kubecontainer.NetworkReady, Status: false}, }, } - kubelet.updateRuntimeUp() + kubelet.updateRuntimeUp(tCtx) checkNodeStatus(v1.ConditionFalse, "KubeletNotReady") } func TestUpdateNodeStatusError(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet kubelet.kubeClient = nil // ensure only the heartbeat client is used // No matching node for the kubelet testKubelet.fakeKubeClient.ReactionChain = fake.NewSimpleClientset(&v1.NodeList{Items: []v1.Node{}}).ReactionChain - assert.Error(t, kubelet.updateNodeStatus(ctx)) + require.Error(t, kubelet.updateNodeStatus(tCtx)) assert.Len(t, testKubelet.fakeKubeClient.Actions(), nodeStatusUpdateRetry) } func TestUpdateNodeStatusWithLease(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() clock := testKubelet.fakeClock @@ -950,8 +951,8 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { // Update node status when node status is created. // Report node status. - kubelet.updateRuntimeUp() - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + kubelet.updateRuntimeUp(tCtx) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() assert.Len(t, actions, 2) @@ -974,7 +975,7 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { // Update node status again when nothing is changed (except heartbeat time). // Report node status if it has exceeded the duration of nodeStatusReportFrequency. clock.Step(time.Minute) - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) // 2 more action (There were 2 actions before). actions = kubeClient.Actions() @@ -999,7 +1000,7 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { // Update node status again when nothing is changed (except heartbeat time). // Do not report node status if it is within the duration of nodeStatusReportFrequency. clock.Step(10 * time.Second) - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) // Only 1 more action (There were 4 actions before). actions = kubeClient.Actions() @@ -1017,7 +1018,7 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { newMachineInfo := oldMachineInfo.Clone() newMachineInfo.MemoryCapacity = uint64(newMemoryCapacity) kubelet.setCachedMachineInfo(newMachineInfo) - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) // 2 more action (There were 5 actions before). actions = kubeClient.Actions() @@ -1049,7 +1050,7 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { updatedNode.Spec.PodCIDR = podCIDRs[0] updatedNode.Spec.PodCIDRs = podCIDRs kubeClient.ReactionChain = fake.NewSimpleClientset(&v1.NodeList{Items: []v1.Node{*updatedNode}}).ReactionChain - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) assert.Equal(t, strings.Join(podCIDRs, ","), kubelet.runtimeState.podCIDR(), "Pod CIDR should be updated now") // 2 more action (There were 7 actions before). actions = kubeClient.Actions() @@ -1062,7 +1063,7 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { clock.Step(10 * time.Second) assert.Equal(t, strings.Join(podCIDRs, ","), kubelet.runtimeState.podCIDR(), "Pod CIDR should already be updated") - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) // Only 1 more action (There were 9 actions before). actions = kubeClient.Actions() assert.Len(t, actions, 10) @@ -1070,6 +1071,7 @@ func TestUpdateNodeStatusWithLease(t *testing.T) { } func TestUpdateNodeStatusAndVolumesInUseWithNodeLease(t *testing.T) { + tCtx := ktesting.Init(t) cases := []struct { desc string existingVolumes []v1.UniqueVolumeName // volumes to initially populate volumeManager @@ -1118,7 +1120,6 @@ func TestUpdateNodeStatusAndVolumesInUseWithNodeLease(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - ctx := context.Background() // Setup testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -1145,7 +1146,7 @@ func TestUpdateNodeStatusAndVolumesInUseWithNodeLease(t *testing.T) { kubelet.nodeLister = delegatingNodeLister{client: kubeClient} // Execute - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) // Validate actions := kubeClient.Actions() @@ -1327,6 +1328,7 @@ func TestFastStatusUpdateOnce(t *testing.T) { } func TestRegisterWithApiServer(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -1373,7 +1375,7 @@ func TestRegisterWithApiServer(t *testing.T) { done := make(chan struct{}) go func() { - kubelet.registerWithAPIServer() + kubelet.registerWithAPIServer(tCtx) done <- struct{}{} }() select { @@ -1385,6 +1387,7 @@ func TestRegisterWithApiServer(t *testing.T) { } func TestTryRegisterWithApiServer(t *testing.T) { + tCtx := ktesting.Init(t) alreadyExists := &apierrors.StatusError{ ErrStatus: metav1.Status{Reason: metav1.StatusReasonAlreadyExists}, } @@ -1551,7 +1554,7 @@ func TestTryRegisterWithApiServer(t *testing.T) { }) addNotImplatedReaction(kubeClient) - result := kubelet.tryRegisterWithAPIServer(tc.newNode) + result := kubelet.tryRegisterWithAPIServer(tCtx, tc.newNode) require.Equal(t, tc.expectedResult, result, "test [%s]", tc.name) actions := kubeClient.Actions() @@ -1582,7 +1585,7 @@ func TestTryRegisterWithApiServer(t *testing.T) { } func TestUpdateNewNodeStatusTooLargeReservation(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) const nodeStatusMaxImages = 5 // generate one more in inputImageList than we configure the Kubelet to report @@ -1640,15 +1643,15 @@ func TestUpdateNewNodeStatusTooLargeReservation(t *testing.T) { }, } - kubelet.updateRuntimeUp() - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + kubelet.updateRuntimeUp(tCtx) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() require.Len(t, actions, 1) require.True(t, actions[0].Matches("patch", "nodes")) require.Equal(t, "status", actions[0].GetSubresource()) updatedNode, err := applyNodeStatusPatch(&existingNode, actions[0].(core.PatchActionImpl).GetPatch()) - assert.NoError(t, err) + require.NoError(t, err) assert.True(t, apiequality.Semantic.DeepEqual(expectedNode.Status.Allocatable, updatedNode.Status.Allocatable), "%s", cmp.Diff(expectedNode.Status.Allocatable, updatedNode.Status.Allocatable)) } @@ -2168,6 +2171,7 @@ func TestUpdateDefaultResources(t *testing.T) { } func TestReconcileHugePageResource(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) hugePageResourceName64Ki := v1.ResourceName("hugepages-64Ki") hugePageResourceName2Mi := v1.ResourceName("hugepages-2Mi") @@ -2452,7 +2456,7 @@ func TestReconcileHugePageResource(t *testing.T) { defer testKubelet.Cleanup() kubelet := testKubelet.kubelet - needsUpdate := kubelet.reconcileHugePageResource(tc.initialNode, tc.existingNode) + needsUpdate := kubelet.reconcileHugePageResource(logger, tc.initialNode, tc.existingNode) assert.Equal(t, tc.needsUpdate, needsUpdate, tc.name) assert.Equal(t, tc.expectedNode, tc.existingNode, tc.name) }) @@ -2460,6 +2464,7 @@ func TestReconcileHugePageResource(t *testing.T) { } func TestReconcileExtendedResource(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) testKubelet.kubelet.kubeClient = nil // ensure only the heartbeat client is used testKubelet.kubelet.containerManager = cm.NewStubContainerManagerWithExtendedResource(true /* shouldResetExtendedResourceCapacity*/) @@ -2640,7 +2645,7 @@ func TestReconcileExtendedResource(t *testing.T) { defer testKubelet.Cleanup() kubelet := testKubelet.kubelet - needsUpdate := kubelet.reconcileExtendedResource(tc.initialNode, tc.existingNode) + needsUpdate := kubelet.reconcileExtendedResource(logger, tc.initialNode, tc.existingNode) assert.Equal(t, tc.needsUpdate, needsUpdate, tc.name) assert.Equal(t, tc.expectedNode, tc.existingNode, tc.name) } @@ -2730,7 +2735,7 @@ func TestValidateNodeIPParam(t *testing.T) { for _, test := range tests { err := validateNodeIP(netutils.ParseIPSloppy(test.nodeIP)) if test.success { - assert.NoErrorf(t, err, "test %s", test.testName) + require.NoErrorf(t, err, "test %s", test.testName) } else { assert.Errorf(t, err, "test %s", test.testName) } @@ -2738,6 +2743,7 @@ func TestValidateNodeIPParam(t *testing.T) { } func TestRegisterWithApiServerWithTaint(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -2772,7 +2778,7 @@ func TestRegisterWithApiServerWithTaint(t *testing.T) { kubelet.registrationCompleted = false // Register node to apiserver. - kubelet.registerWithAPIServer() + kubelet.registerWithAPIServer(tCtx) // Check the unschedulable taint. got := gotNode.(*v1.Node) @@ -2939,6 +2945,7 @@ func TestNodeStatusHasChanged(t *testing.T) { } func TestUpdateNodeAddresses(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -3053,7 +3060,6 @@ func TestUpdateNodeAddresses(t *testing.T) { for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - ctx := context.Background() oldNode := &v1.Node{ ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname}, Spec: v1.NodeSpec{}, @@ -3069,15 +3075,15 @@ func TestUpdateNodeAddresses(t *testing.T) { }, } - _, err := kubeClient.CoreV1().Nodes().Update(ctx, oldNode, metav1.UpdateOptions{}) - assert.NoError(t, err) + _, err := kubeClient.CoreV1().Nodes().Update(tCtx, oldNode, metav1.UpdateOptions{}) + require.NoError(t, err) kubelet.setNodeStatusFuncs = []func(context.Context, *v1.Node) error{ func(_ context.Context, node *v1.Node) error { node.Status.Addresses = expectedNode.Status.Addresses return nil }, } - assert.NoError(t, kubelet.updateNodeStatus(ctx)) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() lastAction := actions[len(actions)-1] @@ -3163,6 +3169,7 @@ func TestCalculateDelay(t *testing.T) { } func TestSetNodeStatusDeclaredFeatures(t *testing.T) { + tCtx := ktesting.Init(t) testCases := []struct { name string featureGateEnabled bool @@ -3208,7 +3215,7 @@ func TestSetNodeStatusDeclaredFeatures(t *testing.T) { // We need to force a status update, so we make the last status report time old. kubelet.lastStatusReportTime = time.Now().Add(-time.Hour) - require.NoError(t, kubelet.updateNodeStatus(context.TODO())) + require.NoError(t, kubelet.updateNodeStatus(tCtx)) actions := kubeClient.Actions() // We expect a get and a patch diff --git a/pkg/kubelet/kubelet_node_status_windows.go b/pkg/kubelet/kubelet_node_status_windows.go index 524159278e22b..a0a3832faff4e 100644 --- a/pkg/kubelet/kubelet_node_status_windows.go +++ b/pkg/kubelet/kubelet_node_status_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2019 The Kubernetes Authors. diff --git a/pkg/kubelet/kubelet_nodecache.go b/pkg/kubelet/kubelet_nodecache.go index 5491d944c8d9c..0d0ac097c5332 100644 --- a/pkg/kubelet/kubelet_nodecache.go +++ b/pkg/kubelet/kubelet_nodecache.go @@ -38,7 +38,7 @@ func (kl *Kubelet) GetCachedNode(ctx context.Context, useCache bool) (*v1.Node, // and returns the newer one. func (kl *Kubelet) getCachedNode(ctx context.Context) (*v1.Node, error) { logger := klog.FromContext(ctx) - informerNode, err := kl.GetNode() + informerNode, err := kl.GetNode(ctx) if err != nil { if kl.cachedNode != nil { logger.Error(err, "failed to list node; using cached node") diff --git a/pkg/kubelet/kubelet_pod_resize_allocation_test.go b/pkg/kubelet/kubelet_pod_resize_allocation_test.go new file mode 100644 index 0000000000000..6b9624bf3a11c --- /dev/null +++ b/pkg/kubelet/kubelet_pod_resize_allocation_test.go @@ -0,0 +1,168 @@ +/* +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 kubelet + +import ( + "testing" + + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/test/utils/ktesting" +) + +// TestSyncPodUsesAllocatedResources tests that SyncPod calls UpdatePodFromAllocation +// to ensure container status shows allocated resources, not desired resources. +// +// This reproduces the e2e test failure scenario where: +// - Pod spec has desired resources (1991m) +// - Allocation manager has allocated less (186m) +// - Without the fix: container status incorrectly shows 1991m +// - With the fix: container status correctly shows 186m +func TestSyncPodUsesAllocatedResources(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) + + tCtx := ktesting.Init(t) + testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) + defer testKubelet.Cleanup() + kubelet := testKubelet.kubelet + + allocatedCPU := resource.MustParse("186m") + desiredCPU := resource.MustParse("1991m") + + // Create a pod with desired resources in spec + pod := podWithUIDNameNsSpec("test-pod-uid", "testpod2", "test-ns", v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "c", + Image: "test-image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: desiredCPU, // Desired: 1991m + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: desiredCPU, + }, + }, + }, + }, + }) + pod.Status.Phase = v1.PodRunning + + // Set up allocation manager with allocated resources (186m) + allocatedPod := pod.DeepCopy() + allocatedPod.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = allocatedCPU + allocatedPod.Spec.Containers[0].Resources.Limits[v1.ResourceCPU] = allocatedCPU + err := kubelet.allocationManager.SetAllocatedResources(allocatedPod) + require.NoError(t, err, "Failed to set allocated resources") + + // Set pod in pod manager (this is what HandlePodSyncs gets) + kubelet.podManager.SetPods([]*v1.Pod{pod}) + + // Create a running container status from the runtime + testKubelet.fakeRuntime.PodList = []*containertest.FakePod{ + { + Pod: &kubecontainer.Pod{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.ContainerID{Type: "test", ID: "container-id"}, + Name: "c", + Image: "test-image", + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + }, + } + + // Call SyncPod - this is where the fix applies + // SyncPod should call UpdatePodFromAllocation to get allocated resources + _, _, err = kubelet.SyncPod(tCtx, kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{Type: "test", ID: "container-id"}, + Name: "c", + State: kubecontainer.ContainerStateRunning, + Resources: &kubecontainer.ContainerResources{ + CPULimit: &allocatedCPU, // Runtime reports allocated resources + CPURequest: &allocatedCPU, + MemoryLimit: nil, + }, + }, + }, + }) + + // Allow errors from SyncPod (we're mainly testing the status generation) + if err != nil { + t.Logf("SyncPod returned error (expected in test environment): %v", err) + } + + // Get the pod status that was set by SyncPod + status, ok := kubelet.statusManager.GetPodStatus(pod.UID) + require.True(t, ok, "Pod status should be set") + + // Verify that container status has allocated resources, not desired + require.Len(t, status.ContainerStatuses, 1, "Should have one container status") + containerStatus := status.ContainerStatuses[0] + + // Check allocatedResources field + if allocatedRes, ok := containerStatus.AllocatedResources[v1.ResourceCPU]; ok { + t.Logf("Container status AllocatedResources CPU: %v", allocatedRes.String()) + + // With the fix in SyncPod (calling UpdatePodFromAllocation): + // allocatedResources should be 186m (allocated), not 1991m (desired) + if !allocatedRes.Equal(allocatedCPU) { + t.Errorf("BUG: Container status shows AllocatedResources CPU = %v, expected %v (allocated). "+ + "This means SyncPod is not calling UpdatePodFromAllocation before generating pod status. "+ + "The pod spec has desired resources (%v), but the allocation manager allocated only %v.", + allocatedRes.String(), allocatedCPU.String(), desiredCPU.String(), allocatedCPU.String()) + } + + if allocatedRes.Equal(desiredCPU) { + t.Errorf("CRITICAL BUG: Container status shows desired resources (%v) instead of allocated resources (%v). "+ + "This is the exact bug from the failing e2e test! "+ + "SyncPod must call UpdatePodFromAllocation before using the pod for status generation.", + desiredCPU.String(), allocatedCPU.String()) + } + } else { + t.Error("AllocatedResources[CPU] not set in container status") + } + + // Also check the resources field + if containerStatus.Resources != nil { + if cpuLimit, ok := containerStatus.Resources.Limits[v1.ResourceCPU]; ok { + t.Logf("Container status Resources.Limits CPU: %v", cpuLimit.String()) + + if !cpuLimit.Equal(allocatedCPU) { + t.Errorf("Container status Resources.Limits CPU = %v, expected %v", + cpuLimit.String(), allocatedCPU.String()) + } + } + } +} diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 42a58b0dfdcd5..88e98b9f0f73c 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -46,9 +46,9 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" resourcehelper "k8s.io/component-helpers/resource" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + "k8s.io/cri-streaming/pkg/streaming/portforward" + remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand" "k8s.io/klog/v2" - "k8s.io/kubelet/pkg/cri/streaming/portforward" - remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/api/v1/resource" podshelper "k8s.io/kubernetes/pkg/apis/core/pods" @@ -60,6 +60,7 @@ import ( kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/envvars" "k8s.io/kubernetes/pkg/kubelet/images" + "k8s.io/kubernetes/pkg/kubelet/kuberuntime" "k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/pkg/kubelet/status" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" @@ -127,7 +128,7 @@ func parseGetSubIdsOutput(input string) (uint32, uint32, error) { // is installed, then the single mapping specified both for user and group IDs will be used. // If the tool is not installed, or there are no IDs configured, the default mapping is returned. // The default mapping includes the entire IDs range except IDs below idsPerPod. -func (kl *Kubelet) getKubeletMappings(idsPerPod uint32) (uint32, uint32, error) { +func (kl *Kubelet) getKubeletMappings(logger klog.Logger, idsPerPod uint32) (uint32, uint32, error) { // default mappings to return if there is no specific configuration defaultFirstID := idsPerPod // We cast defaultFirstID to 64 bits, as otherwise any operation (including subtraction) @@ -151,7 +152,7 @@ func (kl *Kubelet) getKubeletMappings(idsPerPod uint32) (uint32, uint32, error) var unknownUserErr user.UnknownUserError if goerrors.As(err, &unknownUserErr) { // if the user is not found, we assume that the user is not configured - klog.V(5).InfoS("user namespaces: user not found, using default mappings", "user", kubeletUser) + logger.V(5).Info("user namespaces: user not found, using default mappings", "user", kubeletUser) return defaultFirstID, defaultLen, nil } return 0, 0, err @@ -161,7 +162,7 @@ func (kl *Kubelet) getKubeletMappings(idsPerPod uint32) (uint32, uint32, error) cmd, err := exec.LookPath(execName) if err != nil { if os.IsNotExist(err) { - klog.V(2).InfoS("user namespaces: executable not found, using default mappings", "executable", execName, "err", err) + logger.V(2).Info("user namespaces: executable not found, using default mappings", "executable", execName, "err", err) return defaultFirstID, defaultLen, nil } return 0, 0, err @@ -177,7 +178,7 @@ func (kl *Kubelet) getKubeletMappings(idsPerPod uint32) (uint32, uint32, error) if string(outUids) != string(outGids) { return 0, 0, fmt.Errorf("mismatched subuids and subgids for user %q", kubeletUser) } - klog.V(5).InfoS("user namespaces: user found, using mappings from getsubids", "user", kubeletUser) + logger.V(5).Info("user namespaces: user found, using mappings from getsubids", "user", kubeletUser) return parseGetSubIdsOutput(string(outUids)) } @@ -227,7 +228,7 @@ func (kl *Kubelet) getAllocatedPods() []*v1.Pod { // makeBlockVolumes maps the raw block devices specified in the path of the container // Experimental -func (kl *Kubelet) makeBlockVolumes(pod *v1.Pod, container *v1.Container, podVolumes kubecontainer.VolumeMap, blkutil volumepathhandler.BlockVolumePathHandler) ([]kubecontainer.DeviceInfo, error) { +func (kl *Kubelet) makeBlockVolumes(logger klog.Logger, pod *v1.Pod, container *v1.Container, podVolumes kubecontainer.VolumeMap, blkutil volumepathhandler.BlockVolumePathHandler) ([]kubecontainer.DeviceInfo, error) { var devices []kubecontainer.DeviceInfo for _, device := range container.VolumeDevices { // check path is absolute @@ -236,7 +237,7 @@ func (kl *Kubelet) makeBlockVolumes(pod *v1.Pod, container *v1.Container, podVol } vol, ok := podVolumes[device.Name] if !ok || vol.BlockVolumeMapper == nil { - klog.ErrorS(nil, "Block volume cannot be satisfied for container, because the volume is missing or the volume mapper is nil", "containerName", container.Name, "device", device) + logger.Error(nil, "Block volume cannot be satisfied for container, because the volume is missing or the volume mapper is nil", "containerName", container.Name, "device", device) return nil, fmt.Errorf("cannot find volume %q to pass into container %q", device.Name, container.Name) } // Get a symbolic link associated to a block device under pod device path @@ -250,7 +251,7 @@ func (kl *Kubelet) makeBlockVolumes(pod *v1.Pod, container *v1.Container, podVol if vol.ReadOnly { permission = "r" } - klog.V(4).InfoS("Device will be attached to container in the corresponding path on host", "containerName", container.Name, "path", symlinkPath) + logger.V(4).Info("Device will be attached to container in the corresponding path on host", "containerName", container.Name, "path", symlinkPath) devices = append(devices, kubecontainer.DeviceInfo{PathOnHost: symlinkPath, PathInContainer: device.DevicePath, Permissions: permission}) } } @@ -274,9 +275,9 @@ func shouldMountHostsFile(pod *v1.Pod, podIPs []string) bool { } // makeMounts determines the mount points for the given container. -func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain string, podIPs []string, podVolumes kubecontainer.VolumeMap, hu hostutil.HostUtils, subpather subpath.Interface, expandEnvs []kubecontainer.EnvVar, supportsRRO bool, imageVolumes kubecontainer.ImageVolumes) ([]kubecontainer.Mount, func(), error) { +func makeMounts(logger klog.Logger, pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain string, podIPs []string, podVolumes kubecontainer.VolumeMap, hu hostutil.HostUtils, subpather subpath.Interface, expandEnvs []kubecontainer.EnvVar, supportsRRO bool, imageVolumes kubecontainer.ImageVolumes) ([]kubecontainer.Mount, func(), error) { mountEtcHostsFile := shouldMountHostsFile(pod, podIPs) - klog.V(3).InfoS("Creating hosts mount for container", "pod", klog.KObj(pod), "containerName", container.Name, "podIPs", podIPs, "path", mountEtcHostsFile) + logger.V(3).Info("Creating hosts mount for container", "pod", klog.KObj(pod), "containerName", container.Name, "podIPs", podIPs, "path", mountEtcHostsFile) mounts := []kubecontainer.Mount{} var cleanupAction func() for i, mount := range container.VolumeMounts { @@ -284,7 +285,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h mountEtcHostsFile = mountEtcHostsFile && (mount.MountPath != etcHostsPath) vol, ok := podVolumes[mount.Name] if !ok || vol.Mounter == nil { - klog.ErrorS(nil, "Mount cannot be satisfied for the container, because the volume is missing or the volume mounter (vol.Mounter) is nil", + logger.Error(nil, "Mount cannot be satisfied for the container, because the volume is missing or the volume mounter (vol.Mounter) is nil", "containerName", container.Name, "ok", ok, "volumeMounter", mount) return nil, cleanupAction, fmt.Errorf("cannot find volume %q to mount into container %q", mount.Name, container.Name) } @@ -340,7 +341,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h hostPath = filepath.Join(volumePath, subPath) if subPathExists, err := hu.PathExists(hostPath); err != nil { - klog.ErrorS(nil, "Could not determine if subPath exists, will not attempt to change its permissions", "path", hostPath) + logger.Error(nil, "Could not determine if subPath exists, will not attempt to change its permissions", "path", hostPath) } else if !subPathExists { // Create the sub path now because if it's auto-created later when referenced, it may have an // incorrect ownership and mode. For example, the sub path directory must have at least g+rwx @@ -353,7 +354,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h } if err := subpather.SafeMakeDir(subPath, volumePath, perm); err != nil { // Don't pass detailed error back to the user because it could give information about host filesystem - klog.ErrorS(err, "Failed to create subPath directory for volumeMount of the container", "containerName", container.Name, "volumeMountName", mount.Name) + logger.Error(nil, "Failed to create subPath directory for volumeMount of the container", "containerName", container.Name, "volumeMountName", mount.Name) return nil, cleanupAction, fmt.Errorf("failed to create subPath directory for volumeMount %q of container %q", mount.Name, container.Name) } } @@ -367,7 +368,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h }) if err != nil { // Don't pass detailed error back to the user because it could give information about host filesystem - klog.ErrorS(err, "Failed to prepare subPath for volumeMount of the container", "containerName", container.Name, "volumeMountName", mount.Name) + logger.Error(nil, "Failed to prepare subPath for volumeMount of the container", "containerName", container.Name, "volumeMountName", mount.Name) return nil, cleanupAction, fmt.Errorf("failed to prepare subPath for volumeMount %q of container %q", mount.Name, container.Name) } } @@ -388,7 +389,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h if err != nil { return nil, cleanupAction, err } - klog.V(5).InfoS("Mount has propagation", "pod", klog.KObj(pod), "containerName", container.Name, "volumeMountName", mount.Name, "propagation", propagation) + logger.V(5).Info("Mount has propagation", "pod", klog.KObj(pod), "containerName", container.Name, "volumeMountName", mount.Name, "propagation", propagation) mustMountRO := vol.Mounter.GetAttributes().ReadOnly rro, err := resolveRecursiveReadOnly(mount, supportsRRO) @@ -552,14 +553,14 @@ func hostsEntriesFromHostAliases(hostAliases []v1.HostAlias) []byte { } // truncatePodHostnameIfNeeded truncates the pod hostname if it's longer than 63 chars. -func truncatePodHostnameIfNeeded(podName, hostname string) (string, error) { +func truncatePodHostnameIfNeeded(logger klog.Logger, podName, hostname string) (string, error) { // Cap hostname at 63 chars (specification is 64bytes which is 63 chars and the null terminating char). const hostnameMaxLen = 63 if len(hostname) <= hostnameMaxLen { return hostname, nil } truncated := hostname[:hostnameMaxLen] - klog.ErrorS(nil, "Hostname for pod was too long, truncated it", "podName", podName, "hostnameMaxLen", hostnameMaxLen, "truncatedHostname", truncated) + logger.Error(nil, "Hostname for pod was too long, truncated it", "podName", podName, "hostnameMaxLen", hostnameMaxLen, "truncatedHostname", truncated) // hostname should not end with '-' or '.' truncated = strings.TrimRight(truncated, "-.") if len(truncated) == 0 { @@ -570,15 +571,13 @@ func truncatePodHostnameIfNeeded(podName, hostname string) (string, error) { } // GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace -func (kl *Kubelet) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { - // Use context.TODO() because we currently do not have a proper logger to pass in. - // This should be replaced with an appropriate context when refactoring this function to accept a context parameter. - return kl.usernsManager.GetOrCreateUserNamespaceMappings(context.TODO(), pod, runtimeHandler) +func (kl *Kubelet) GetOrCreateUserNamespaceMappings(logger klog.Logger, pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { + return kl.usernsManager.GetOrCreateUserNamespaceMappings(logger, pod, runtimeHandler) } // GeneratePodHostNameAndDomain creates a hostname and domain name for a pod, // given that pod's spec and annotations or returns an error. -func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) { +func (kl *Kubelet) GeneratePodHostNameAndDomain(logger klog.Logger, pod *v1.Pod) (string, string, error) { clusterDomain := kl.dnsConfigurer.ClusterDomain if utilfeature.DefaultFeatureGate.Enabled(features.HostnameOverride) && pod.Spec.HostnameOverride != nil { @@ -586,7 +585,7 @@ func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, er if msgs := utilvalidation.IsDNS1123Subdomain(hostname); len(msgs) != 0 { return "", "", fmt.Errorf("pod HostnameOverride %q is not a valid DNS subdomain: %s", hostname, strings.Join(msgs, ";")) } - truncatedHostname, err := truncatePodHostnameIfNeeded(pod.Name, hostname) + truncatedHostname, err := truncatePodHostnameIfNeeded(logger, pod.Name, hostname) if err != nil { return "", "", err } @@ -601,7 +600,7 @@ func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, er hostname = pod.Spec.Hostname } - hostname, err := truncatePodHostnameIfNeeded(pod.Name, hostname) + hostname, err := truncatePodHostnameIfNeeded(logger, pod.Name, hostname) if err != nil { return "", "", err } @@ -627,14 +626,15 @@ func (kl *Kubelet) GetPodCgroupParent(pod *v1.Pod) string { // GenerateRunContainerOptions generates the RunContainerOptions, which can be used by // the container runtime to set parameters for launching a container. func (kl *Kubelet) GenerateRunContainerOptions(ctx context.Context, pod *v1.Pod, container *v1.Container, podIP string, podIPs []string, imageVolumes kubecontainer.ImageVolumes) (*kubecontainer.RunContainerOptions, func(), error) { - supportsRRO := kl.runtimeClassSupportsRecursiveReadOnlyMounts(pod) + logger := klog.FromContext(ctx) + supportsRRO := kl.runtimeClassSupportsRecursiveReadOnlyMounts(logger, pod) opts, err := kl.containerManager.GetResources(ctx, pod, container) if err != nil { return nil, nil, err } // The value of hostname is the short host name and it is sent to makeMounts to create /etc/hosts file. - hostname, hostDomainName, err := kl.GeneratePodHostNameAndDomain(pod) + hostname, hostDomainName, err := kl.GeneratePodHostNameAndDomain(logger, pod) if err != nil { return nil, nil, err } @@ -643,20 +643,20 @@ func (kl *Kubelet) GenerateRunContainerOptions(ctx context.Context, pod *v1.Pod, volumes := kl.volumeManager.GetMountedVolumesForPod(podName) blkutil := volumepathhandler.NewBlockVolumePathHandler() - blkVolumes, err := kl.makeBlockVolumes(pod, container, volumes, blkutil) + blkVolumes, err := kl.makeBlockVolumes(logger, pod, container, volumes, blkutil) if err != nil { return nil, nil, err } opts.Devices = append(opts.Devices, blkVolumes...) - envs, err := kl.makeEnvironmentVariables(pod, container, podIP, podIPs, volumes) + envs, err := kl.makeEnvironmentVariables(ctx, pod, container, podIP, podIPs, volumes) if err != nil { return nil, nil, err } opts.Envs = append(opts.Envs, envs...) // only podIPs is sent to makeMounts, as podIPs is populated even if dual-stack feature flag is not enabled. - mounts, cleanupAction, err := makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIPs, volumes, kl.hostutil, kl.subpather, opts.Envs, supportsRRO, imageVolumes) + mounts, cleanupAction, err := makeMounts(logger, pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIPs, volumes, kl.hostutil, kl.subpather, opts.Envs, supportsRRO, imageVolumes) if err != nil { return nil, cleanupAction, err } @@ -667,7 +667,7 @@ func (kl *Kubelet) GenerateRunContainerOptions(ctx context.Context, pod *v1.Pod, if len(container.TerminationMessagePath) != 0 { p := kl.getPodContainerDir(pod.UID, container.Name) if err := os.MkdirAll(p, 0750); err != nil { - klog.ErrorS(err, "Error on creating dir", "path", p) + logger.Error(err, "Error on creating dir", "path", p) } else { opts.PodContainerDir = p } @@ -731,7 +731,8 @@ func (kl *Kubelet) getServiceEnvVarMap(ns string, enableServiceLinks bool) (map[ } // Make the environment variables for a pod in the given namespace. -func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container, podIP string, podIPs []string, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.EnvVar, error) { +func (kl *Kubelet) makeEnvironmentVariables(ctx context.Context, pod *v1.Pod, container *v1.Container, podIP string, podIPs []string, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.EnvVar, error) { + logger := klog.FromContext(ctx) if pod.Spec.EnableServiceLinks == nil { return nil, fmt.Errorf("nil pod.spec.enableServiceLinks encountered, cannot construct envvars") } @@ -766,7 +767,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container var ( configMaps = make(map[string]*v1.ConfigMap) secrets = make(map[string]*v1.Secret) - tmpEnv = make(map[string]string) + tmpEnv = make(map[string]string) // TODO: switch to map[string][]byte ) // Env will override EnvFrom variables. @@ -798,6 +799,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container k = envFrom.Prefix + k } + // TODO: validate no NUL bytes tmpEnv[k] = v } case envFrom.SecretRef != nil: @@ -825,6 +827,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container k = envFrom.Prefix + k } + // TODO: validate no NUL bytes tmpEnv[k] = string(v) } } @@ -851,12 +854,12 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container // Step 1b: resolve alternate env var sources switch { case envVar.ValueFrom.FieldRef != nil: - runtimeVal, err = kl.podFieldSelectorRuntimeValue(envVar.ValueFrom.FieldRef, pod, podIP, podIPs) + runtimeVal, err = kl.podFieldSelectorRuntimeValue(ctx, envVar.ValueFrom.FieldRef, pod, podIP, podIPs) if err != nil { return result, err } case envVar.ValueFrom.ResourceFieldRef != nil: - defaultedPod, defaultedContainer, err := kl.defaultPodLimitsForDownwardAPI(pod, container) + defaultedPod, defaultedContainer, err := kl.defaultPodLimitsForDownwardAPI(ctx, pod, container) if err != nil { return result, err } @@ -918,6 +921,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container } return result, fmt.Errorf("couldn't find key %v in Secret %v/%v", key, pod.Namespace, name) } + // TODO: validate no NUL bytes runtimeVal = string(runtimeValBytes) case utilfeature.DefaultFeatureGate.Enabled(features.EnvFiles) && envVar.ValueFrom.FileKeyRef != nil: f := envVar.ValueFrom.FileKeyRef @@ -943,7 +947,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container runtimeVal, err = envutil.ParseEnv(envFilePath, key) if err != nil { - klog.ErrorS(err, "Failed to parse env file", "pod", klog.KObj(pod)) + logger.Error(err, "Failed to parse env file", "pod", klog.KObj(pod)) return result, fmt.Errorf("couldn't parse env file") } @@ -982,7 +986,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container // podFieldSelectorRuntimeValue returns the runtime value of the given // selector for a pod. -func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod *v1.Pod, podIP string, podIPs []string) (string, error) { +func (kl *Kubelet) podFieldSelectorRuntimeValue(ctx context.Context, fs *v1.ObjectFieldSelector, pod *v1.Pod, podIP string, podIPs []string) (string, error) { internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "") if err != nil { return "", err @@ -1000,13 +1004,13 @@ func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod case "spec.serviceAccountName": return pod.Spec.ServiceAccountName, nil case "status.hostIP": - hostIPs, err := kl.getHostIPsAnyWay() + hostIPs, err := kl.getHostIPsAnyWay(ctx) if err != nil { return "", err } return hostIPs[0].String(), nil case "status.hostIPs": - hostIPs, err := kl.getHostIPsAnyWay() + hostIPs, err := kl.getHostIPsAnyWay(ctx) if err != nil { return "", err } @@ -1036,13 +1040,13 @@ func containerResourceRuntimeValue(fs *v1.ResourceFieldSelector, pod *v1.Pod, co // the pod status contains the result of the last syncPod, otherwise it may fail to // terminate newly created containers and sandboxes. func (kl *Kubelet) killPod(ctx context.Context, pod *v1.Pod, p kubecontainer.Pod, gracePeriodOverride *int64) error { + logger := klog.FromContext(ctx) // Call the container runtime KillPod method which stops all known running containers of the pod if err := kl.containerRuntime.KillPod(ctx, pod, p, gracePeriodOverride); err != nil { return err } - // TODO: Pass logger from context once contextual logging migration is complete - if err := kl.containerManager.UpdateQOSCgroups(klog.TODO()); err != nil { - klog.V(2).InfoS("Failed to update QoS cgroups while killing pod", "err", err) + if err := kl.containerManager.UpdateQOSCgroups(logger); err != nil { + logger.V(2).Info("Failed to update QoS cgroups while killing pod", "err", err) } return nil } @@ -1064,7 +1068,7 @@ func (kl *Kubelet) makePodDataDirs(pod *v1.Pod) error { // getPullSecretsForPod inspects the Pod and retrieves the referenced pull // secrets. -func (kl *Kubelet) getPullSecretsForPod(pod *v1.Pod) []v1.Secret { +func (kl *Kubelet) getPullSecretsForPod(logger klog.Logger, pod *v1.Pod) []v1.Secret { pullSecrets := []v1.Secret{} failedPullSecrets := []string{} @@ -1076,7 +1080,7 @@ func (kl *Kubelet) getPullSecretsForPod(pod *v1.Pod) []v1.Secret { } secret, err := kl.secretManager.GetSecret(pod.Namespace, secretRef.Name) if err != nil { - klog.InfoS("Unable to retrieve pull secret, the image pull may not succeed.", "pod", klog.KObj(pod), "secret", klog.KObj(secret), "err", err) + logger.Info("Unable to retrieve pull secret, the image pull may not succeed.", "pod", klog.KObj(pod), "secret", klog.KObj(secret), "err", err) failedPullSecrets = append(failedPullSecrets, secretRef.Name) continue } @@ -1085,7 +1089,7 @@ func (kl *Kubelet) getPullSecretsForPod(pod *v1.Pod) []v1.Secret { } if len(failedPullSecrets) > 0 { - kl.recorder.Eventf(pod, v1.EventTypeWarning, "FailedToRetrieveImagePullSecret", "Unable to retrieve some image pull secrets (%s); attempting to pull the image may not succeed.", strings.Join(failedPullSecrets, ", ")) + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, "FailedToRetrieveImagePullSecret", "Unable to retrieve some image pull secrets (%s); attempting to pull the image may not succeed.", strings.Join(failedPullSecrets, ", ")) } return pullSecrets @@ -1125,22 +1129,31 @@ func (kl *Kubelet) PodIsFinished(pod *v1.Pod) bool { func (kl *Kubelet) filterOutInactivePods(pods []*v1.Pod) []*v1.Pod { filteredPods := make([]*v1.Pod, 0, len(pods)) for _, p := range pods { - // if a pod is fully terminated by UID, it should be excluded from the - // list of pods - if kl.podWorkers.IsPodKnownTerminated(p.UID) { + if kl.isPodInactive(p) { continue } - - // terminal pods are considered inactive UNLESS they are actively terminating - if kl.isAdmittedPodTerminal(p) && !kl.podWorkers.IsPodTerminationRequested(p.UID) { - continue - } - filteredPods = append(filteredPods, p) } return filteredPods } +// isPodInactive returns true if the pod is in a terminal phase +// or is known to be fully terminated. This method should only be used +// when the pod being processed is upstream of the pod worker, i.e. +// the pods the pod manager is aware of. +func (kl *Kubelet) isPodInactive(p *v1.Pod) bool { + // if a pod is fully terminated by UID, it should be excluded from the + // list of pods + if kl.podWorkers.IsPodKnownTerminated(p.UID) { + return true + } + // terminal pods are considered inactive UNLESS they are actively terminating + if kl.isAdmittedPodTerminal(p) && !kl.podWorkers.IsPodTerminationRequested(p.UID) { + return true + } + return false +} + // isAdmittedPodTerminal returns true if the provided config source pod is in // a terminal phase, or if the Kubelet has already indicated the pod has reached // a terminal phase but the config source has not accepted it yet. This method @@ -1165,7 +1178,7 @@ func (kl *Kubelet) isAdmittedPodTerminal(pod *v1.Pod) bool { // removeOrphanedPodStatuses removes obsolete entries in podStatus where // the pod is no longer considered bound to this node. -func (kl *Kubelet) removeOrphanedPodStatuses(pods []*v1.Pod, mirrorPods []*v1.Pod) { +func (kl *Kubelet) removeOrphanedPodStatuses(logger klog.Logger, pods []*v1.Pod, mirrorPods []*v1.Pod) { podUIDs := make(map[types.UID]bool) for _, pod := range pods { podUIDs[pod.UID] = true @@ -1173,7 +1186,7 @@ func (kl *Kubelet) removeOrphanedPodStatuses(pods []*v1.Pod, mirrorPods []*v1.Po for _, pod := range mirrorPods { podUIDs[pod.UID] = true } - kl.statusManager.RemoveOrphanedStatuses(klog.TODO(), podUIDs) + kl.statusManager.RemoveOrphanedStatuses(logger, podUIDs) } // HandlePodCleanups performs a series of cleanup work, including terminating @@ -1190,6 +1203,7 @@ func (kl *Kubelet) removeOrphanedPodStatuses(pods []*v1.Pod, mirrorPods []*v1.Po // are updated with a fixed UID (most should use a dynamic UID), and no config // updates are delivered to the pod workers while this method is running. func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { + logger := klog.FromContext(ctx) // The kubelet lacks checkpointing, so we need to introspect the set of pods // in the cgroup tree prior to inspecting the set of pods in our pod manager. // this ensures our view of the cgroup tree does not mistakenly observe pods @@ -1220,8 +1234,8 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { // These two conditions could be alleviated by checkpointing kubelet. // Stop the workers for terminated pods not in the config source - klog.V(3).InfoS("Clean up pod workers for terminated pods") - workingPods := kl.podWorkers.SyncKnownPods(allPods) + logger.V(3).Info("Clean up pod workers for terminated pods") + workingPods := kl.podWorkers.SyncKnownPods(logger, allPods) // Reconcile: At this point the pod workers have been pruned to the set of // desired pods. Pods that must be restarted due to UID reuse, or leftover @@ -1260,28 +1274,28 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { // We need the latest state to avoid delaying restarts of static pods that reuse // a UID. if err := kl.runtimeCache.ForceUpdateIfOlder(ctx, kl.clock.Now()); err != nil { - klog.ErrorS(err, "Error listing containers") + logger.Error(err, "Error listing containers") return err } runningRuntimePods, err := kl.runtimeCache.GetPods(ctx) if err != nil { - klog.ErrorS(err, "Error listing containers") + logger.Error(err, "Error listing containers") return err } // Stop probing pods that are not running - klog.V(3).InfoS("Clean up probes for terminated pods") + logger.V(3).Info("Clean up probes for terminated pods") kl.probeManager.CleanupPods(possiblyRunningPods) // Remove orphaned pod statuses not in the total list of known config pods - klog.V(3).InfoS("Clean up orphaned pod statuses") - kl.removeOrphanedPodStatuses(allPods, mirrorPods) + logger.V(3).Info("Clean up orphaned pod statuses") + kl.removeOrphanedPodStatuses(logger, allPods, mirrorPods) kl.allocationManager.RemoveOrphanedPods(allPodsByUID) // Remove orphaned pod user namespace allocations (if any). - klog.V(3).InfoS("Clean up orphaned pod user namespace allocations") + logger.V(3).Info("Clean up orphaned pod user namespace allocations") if err = kl.usernsManager.CleanupOrphanedPodUsernsAllocations(ctx, allPods, runningRuntimePods); err != nil { - klog.ErrorS(err, "Failed cleaning up orphaned pod user namespaces allocations") + logger.Error(err, "Failed cleaning up orphaned pod user namespaces allocations") } // Remove orphaned volumes from pods that are known not to have any @@ -1291,25 +1305,25 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { // TODO: this method could more aggressively cleanup terminated pods // in the future (volumes, mount dirs, logs, and containers could all be // better separated) - klog.V(3).InfoS("Clean up orphaned pod directories") - err = kl.cleanupOrphanedPodDirs(allPods, runningRuntimePods) + logger.V(3).Info("Clean up orphaned pod directories") + err = kl.cleanupOrphanedPodDirs(logger, allPods, runningRuntimePods) if err != nil { // We want all cleanup tasks to be run even if one of them failed. So // we just log an error here and continue other cleanup tasks. // This also applies to the other clean up tasks. - klog.ErrorS(err, "Failed cleaning up orphaned pod directories") + logger.Error(err, "Failed cleaning up orphaned pod directories") } // Remove any orphaned mirror pods (mirror pods are tracked by name via the // pod worker) - klog.V(3).InfoS("Clean up orphaned mirror pods") + logger.V(3).Info("Clean up orphaned mirror pods") for _, podFullname := range orphanedMirrorPodFullnames { if !kl.podWorkers.IsPodForMirrorPodTerminatingByFullName(podFullname) { _, err := kl.mirrorPodClient.DeleteMirrorPod(ctx, podFullname, nil) if err != nil { - klog.ErrorS(err, "Encountered error when deleting mirror pod", "podName", podFullname) + logger.Error(err, "Encountered error when deleting mirror pod", "podName", podFullname) } else { - klog.V(3).InfoS("Deleted mirror pod", "podName", podFullname) + logger.V(3).Info("Deleted mirror pod", "podName", podFullname) } } } @@ -1339,14 +1353,14 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { continue } - klog.V(3).InfoS("Pod will be restarted because it is in the desired set and not known to the pod workers (likely due to UID reuse)", "podUID", desiredPod.UID) + logger.V(3).Info("Pod will be restarted because it is in the desired set and not known to the pod workers (likely due to UID reuse)", "podUID", desiredPod.UID) isStatic := kubetypes.IsStaticPod(desiredPod) pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(desiredPod) if pod == nil || wasMirror { - klog.V(2).InfoS("Programmer error, restartable pod was a mirror pod but activePods should never contain a mirror pod", "podUID", desiredPod.UID) + logger.V(2).Info("Programmer error, restartable pod was a mirror pod but activePods should never contain a mirror pod", "podUID", desiredPod.UID) continue } - kl.podWorkers.UpdatePod(UpdatePodOptions{ + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, Pod: pod, MirrorPod: mirrorPod, @@ -1374,8 +1388,8 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { // force-deletion of such pods, yet preventing re-entry of the routine in the // next invocation of HandlePodCleanups. for _, pod := range kl.filterTerminalPodsToDelete(allPods, runningRuntimePods, workingPods) { - klog.V(3).InfoS("Handling termination and deletion of the pod to pod workers", "pod", klog.KObj(pod), "podUID", pod.UID) - kl.podWorkers.UpdatePod(UpdatePodOptions{ + logger.V(3).Info("Handling termination and deletion of the pod to pod workers", "pod", klog.KObj(pod), "podUID", pod.UID) + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, Pod: pod, }) @@ -1397,8 +1411,8 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { killPodOptions := &KillPodOptions{ PodTerminationGracePeriodSecondsOverride: &one, } - klog.V(2).InfoS("Clean up containers for orphaned pod we had not seen before", "podUID", runningPod.ID, "killPodOptions", killPodOptions) - kl.podWorkers.UpdatePod(UpdatePodOptions{ + logger.V(2).Info("Clean up containers for orphaned pod we had not seen before", "podUID", runningPod.ID, "killPodOptions", killPodOptions) + kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, RunningPod: runningPod, KillPodOptions: killPodOptions, @@ -1437,15 +1451,15 @@ func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error { } if len(counts) > 0 { // in case a combination is lost - klog.V(3).InfoS("Programmer error, did not report a kubelet_working_pods metric for a value returned by SyncKnownPods", "counts", counts) + logger.V(3).Info("Programmer error, did not report a kubelet_working_pods metric for a value returned by SyncKnownPods", "counts", counts) } // Remove any cgroups in the hierarchy for pods that are definitely no longer // running (not in the container runtime). if kl.cgroupsPerQOS { pcm := kl.containerManager.NewPodContainerManager() - klog.V(3).InfoS("Clean up orphaned pod cgroups") - kl.cleanupOrphanedPodCgroups(pcm, cgroupPods, possiblyRunningPods) + logger.V(3).Info("Clean up orphaned pod cgroups") + kl.cleanupOrphanedPodCgroups(logger, pcm, cgroupPods, possiblyRunningPods) } // Cleanup any backoff entries. @@ -1636,7 +1650,7 @@ func (kl *Kubelet) GetKubeletContainerLogs(ctx context.Context, podFullName, con } // getPhase returns the phase of a pod given its container info. -func getPhase(pod *v1.Pod, info []v1.ContainerStatus, podIsTerminal, podHasInitialized bool) v1.PodPhase { +func getPhase(logger klog.Logger, pod *v1.Pod, info []v1.ContainerStatus, podIsTerminal, podHasInitialized bool) v1.PodPhase { spec := pod.Spec pendingRestartableInitContainers := 0 pendingRegularInitContainers := 0 @@ -1804,7 +1818,7 @@ func getPhase(pod *v1.Pod, info []v1.ContainerStatus, podIsTerminal, podHasIniti !podHasInitialized): fallthrough case waiting > 0: - klog.V(5).InfoS("Pod waiting > 0, pending") + logger.V(5).Info("Pod waiting > 0, pending") // One or more containers has not been started return v1.PodPending case running > 0 && unknown == 0: @@ -1855,7 +1869,7 @@ func getPhase(pod *v1.Pod, info []v1.ContainerStatus, podIsTerminal, podHasIniti // and in the process of restarting return v1.PodRunning default: - klog.V(5).InfoS("Pod default case, pending") + logger.V(5).Info("Pod default case, pending") return v1.PodPending } } @@ -1874,28 +1888,29 @@ func (kl *Kubelet) determinePodResizeStatus(allocatedPod *v1.Pod, podIsTerminal // generateAPIPodStatus creates the final API pod status for a pod, given the // internal pod status. This method should only be called from within sync*Pod methods. -func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.PodStatus, podIsTerminal bool) v1.PodStatus { - klog.V(3).InfoS("Generating pod status", "podIsTerminal", podIsTerminal, "pod", klog.KObj(pod)) +func (kl *Kubelet) generateAPIPodStatus(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, podIsTerminal bool) v1.PodStatus { + logger := klog.FromContext(ctx) + logger.V(3).Info("Generating pod status", "podIsTerminal", podIsTerminal, "pod", klog.KObj(pod)) // use the previous pod status, or the api status, as the basis for this pod oldPodStatus, found := kl.statusManager.GetPodStatus(pod.UID) if !found { oldPodStatus = pod.Status } - s := kl.convertStatusToAPIStatus(pod, podStatus, oldPodStatus) + s := kl.convertStatusToAPIStatus(ctx, pod, podStatus, oldPodStatus) // calculate the next phase and preserve reason allStatus := append(append([]v1.ContainerStatus{}, s.ContainerStatuses...), s.InitContainerStatuses...) - s.Phase = getPhase(pod, allStatus, podIsTerminal, kubecontainer.HasAnyActiveRegularContainerStarted(&pod.Spec, podStatus)) - klog.V(4).InfoS("Got phase for pod", "pod", klog.KObj(pod), "oldPhase", oldPodStatus.Phase, "phase", s.Phase) + s.Phase = getPhase(logger, pod, allStatus, podIsTerminal, kubecontainer.HasAnyActiveRegularContainerStarted(&pod.Spec, podStatus)) + logger.V(4).Info("Got phase for pod", "pod", klog.KObj(pod), "oldPhase", oldPodStatus.Phase, "phase", s.Phase) // Perform a three-way merge between the statuses from the status manager, // runtime, and generated status to ensure terminal status is correctly set. if s.Phase != v1.PodFailed && s.Phase != v1.PodSucceeded { switch { case oldPodStatus.Phase == v1.PodFailed || oldPodStatus.Phase == v1.PodSucceeded: - klog.V(4).InfoS("Status manager phase was terminal, updating phase to match", "pod", klog.KObj(pod), "phase", oldPodStatus.Phase) + logger.V(4).Info("Status manager phase was terminal, updating phase to match", "pod", klog.KObj(pod), "phase", oldPodStatus.Phase) s.Phase = oldPodStatus.Phase case pod.Status.Phase == v1.PodFailed || pod.Status.Phase == v1.PodSucceeded: - klog.V(4).InfoS("API phase was terminal, updating phase to match", "pod", klog.KObj(pod), "phase", pod.Status.Phase) + logger.V(4).Info("API phase was terminal, updating phase to match", "pod", klog.KObj(pod), "phase", pod.Status.Phase) s.Phase = pod.Status.Phase } } @@ -1926,16 +1941,13 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po if pod.Status.Phase == v1.PodFailed || pod.Status.Phase == v1.PodSucceeded { // API server shows terminal phase; transitions are not allowed if s.Phase != pod.Status.Phase { - klog.ErrorS(nil, "Pod attempted illegal phase transition", "pod", klog.KObj(pod), "originalStatusPhase", pod.Status.Phase, "apiStatusPhase", s.Phase, "apiStatus", s) + logger.Error(nil, "Pod attempted illegal phase transition", "pod", klog.KObj(pod), "originalStatusPhase", pod.Status.Phase, "apiStatusPhase", s.Phase, "apiStatus", s) // Force back to phase from the API server s.Phase = pod.Status.Phase } } - // Use context.TODO() because we currently do not have a proper context to pass in. - // Replace this with an appropriate context when refactoring this function to accept a context parameter. - // ensure the probe managers have up to date status for containers - kl.probeManager.UpdatePodStatus(context.TODO(), pod, s) + kl.probeManager.UpdatePodStatus(ctx, pod, s) // update the allocated resources status if utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatus) { @@ -1992,13 +2004,13 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po } // set HostIP/HostIPs and initialize PodIP/PodIPs for host network pods if kl.kubeClient != nil { - hostIPs, err := kl.getHostIPsAnyWay() + hostIPs, err := kl.getHostIPsAnyWay(ctx) if err != nil { - klog.V(4).InfoS("Cannot get host IPs", "err", err) + logger.V(4).Info("Cannot get host IPs", "err", err) } else { if s.HostIP != "" { if utilnet.IPFamilyOfString(s.HostIP) != utilnet.IPFamilyOf(hostIPs[0]) { - kl.recorder.Eventf(pod, v1.EventTypeWarning, "HostIPsIPFamilyMismatch", + kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, "HostIPsIPFamilyMismatch", "Kubelet detected an IPv%s node IP (%s), but the cloud provider selected an IPv%s node IP (%s); pass an explicit `--node-ip` to kubelet to fix this.", utilnet.IPFamilyOfString(s.HostIP), s.HostIP, utilnet.IPFamilyOf(hostIPs[0]), hostIPs[0].String()) } @@ -2068,7 +2080,8 @@ func (kl *Kubelet) sortPodIPs(podIPs []string) []string { // convertStatusToAPIStatus initialize an api PodStatus for the given pod from // the given internal pod status and the previous state of the pod from the API. // It is purely transformative and does not alter the kubelet state at all. -func (kl *Kubelet) convertStatusToAPIStatus(pod *v1.Pod, podStatus *kubecontainer.PodStatus, oldPodStatus v1.PodStatus) *v1.PodStatus { +func (kl *Kubelet) convertStatusToAPIStatus(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, oldPodStatus v1.PodStatus) *v1.PodStatus { + logger := klog.FromContext(ctx) var apiPodStatus v1.PodStatus // copy pod status IPs to avoid race conditions with PodStatus #102806 @@ -2092,18 +2105,33 @@ func (kl *Kubelet) convertStatusToAPIStatus(pod *v1.Pod, podStatus *kubecontaine podRestarting = kubecontainer.ShouldAllContainersRestart(pod, podStatus, &oldPodStatus) } + var imageVolumeNames sets.Set[string] + if utilfeature.DefaultFeatureGate.Enabled(features.ImageVolumeWithDigest) { + imageVolumeNames = sets.New[string]() + for _, podVolume := range pod.Spec.Volumes { + if podVolume.Image == nil { + continue + } + imageVolumeNames.Insert(podVolume.Name) + } + } + apiPodStatus.ContainerStatuses = kl.convertToAPIContainerStatuses( + ctx, pod, podStatus, oldPodStatus.ContainerStatuses, pod.Spec.Containers, + imageVolumeNames, len(pod.Spec.InitContainers) > 0, false, podRestarting, ) apiPodStatus.InitContainerStatuses = kl.convertToAPIContainerStatuses( + ctx, pod, podStatus, oldPodStatus.InitContainerStatuses, pod.Spec.InitContainers, + imageVolumeNames, len(pod.Spec.InitContainers) > 0, true, podRestarting, @@ -2116,16 +2144,18 @@ func (kl *Kubelet) convertStatusToAPIStatus(pod *v1.Pod, podStatus *kubecontaine // #80875: By now we've iterated podStatus 3 times. We could refactor this to make a single // pass through podStatus.ContainerStatuses apiPodStatus.EphemeralContainerStatuses = kl.convertToAPIContainerStatuses( + ctx, pod, podStatus, oldPodStatus.EphemeralContainerStatuses, ecSpecs, + imageVolumeNames, len(pod.Spec.InitContainers) > 0, false, podRestarting, ) if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling) { - apiPodStatus.Resources = kl.convertToAPIPodLevelResourcesStatus(pod, oldPodStatus) + apiPodStatus.Resources = kl.convertToAPIPodLevelResourcesStatus(logger, pod, oldPodStatus) opts := resourcehelper.PodResourcesOptions{ SkipPodLevelResources: !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources), } @@ -2136,7 +2166,7 @@ func (kl *Kubelet) convertStatusToAPIStatus(pod *v1.Pod, podStatus *kubecontaine } func getEffectiveAllocatedResources(allocatedPod *v1.Pod) *v1.ResourceRequirements { - allocatedResources := allocatedPod.Spec.Resources + allocatedResources := allocatedPod.Spec.Resources.DeepCopy() if allocatedResources == nil { allocatedResources = &v1.ResourceRequirements{} } @@ -2148,7 +2178,7 @@ func getEffectiveAllocatedResources(allocatedPod *v1.Pod) *v1.ResourceRequiremen return allocatedResources } -func (kl *Kubelet) convertToAPIPodLevelResourcesStatus(allocatedPod *v1.Pod, oldPodStatus v1.PodStatus) *v1.ResourceRequirements { +func (kl *Kubelet) convertToAPIPodLevelResourcesStatus(logger klog.Logger, allocatedPod *v1.Pod, oldPodStatus v1.PodStatus) *v1.ResourceRequirements { if allocatedPod.Status.Phase != v1.PodRunning { return getEffectiveAllocatedResources(allocatedPod) } @@ -2160,12 +2190,12 @@ func (kl *Kubelet) convertToAPIPodLevelResourcesStatus(allocatedPod *v1.Pod, old pcm := kl.containerManager.NewPodContainerManager() memoryConfig, err := pcm.GetPodCgroupConfig(allocatedPod, v1.ResourceMemory) if err != nil { - klog.ErrorS(err, "failed to read memory cgroup config for the pod", "podName", allocatedPod.Name) + logger.Error(err, "failed to read memory cgroup config for the pod", "podName", allocatedPod.Name) } memoryLimit := cm.MemoryLimitsFromConfig(memoryConfig) cpuConfig, err := pcm.GetPodCgroupConfig(allocatedPod, v1.ResourceCPU) if err != nil { - klog.ErrorS(err, "failed to read memory cgroup limits for the pod", "podName", allocatedPod.Name) + logger.Error(err, "failed to read memory cgroup limits for the pod", "podName", allocatedPod.Name) } @@ -2214,7 +2244,9 @@ func (kl *Kubelet) convertToAPIPodLevelResourcesStatus(allocatedPod *v1.Pod, old SkipPodLevelResources: !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources), } aggregatedResources := resourcehelper.PodRequests(allocatedPod, opts) - resources.Requests[v1.ResourceMemory] = aggregatedResources[v1.ResourceMemory] + if val, ok := aggregatedResources[v1.ResourceMemory]; ok { + resources.Requests[v1.ResourceMemory] = val + } } // TODO: Once we begin persisting memory Request from the PodSpec to cgroups, @@ -2244,10 +2276,8 @@ func (kl *Kubelet) convertToAPIPodLevelResourcesStatus(allocatedPod *v1.Pod, old // convertToAPIContainerStatuses converts the given internal container // statuses into API container statuses. -func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecontainer.PodStatus, previousStatus []v1.ContainerStatus, containers []v1.Container, hasInitContainers, isInitContainer, podRestarting bool) []v1.ContainerStatus { - // Use klog.TODO() because we currently do not have a proper logger to pass in. - // This should be replaced with an appropriate logger when refactoring this function to accept a logger parameter. - logger := klog.TODO() +func (kl *Kubelet) convertToAPIContainerStatuses(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, previousStatus []v1.ContainerStatus, containers []v1.Container, imageVolumeNames sets.Set[string], hasInitContainers, isInitContainer bool, podRestarting bool) []v1.ContainerStatus { + logger := klog.FromContext(ctx) convertContainerStatus := func(cs *kubecontainer.Status, oldStatus *v1.ContainerStatus) *v1.ContainerStatus { cid := cs.ID.String() status := &v1.ContainerStatus{ @@ -2266,8 +2296,55 @@ func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecon if oldStatus.RestartCount > status.RestartCount { status.RestartCount = oldStatus.RestartCount } + // Normal container restarts preserve the old container in the runtime so kubelet + // can query its termination status. RestartAllContainers removes old containers, + // so we must manually propagate LastTerminationState when the container ID changes. + if oldStatus.ContainerID != status.ContainerID && oldStatus.State.Terminated != nil { + status.LastTerminationState.Terminated = oldStatus.State.Terminated + } else if oldStatus.LastTerminationState.Terminated != nil { + status.LastTerminationState.Terminated = oldStatus.LastTerminationState.Terminated + } } } + if utilfeature.DefaultFeatureGate.Enabled(features.ImageVolumeWithDigest) && imageVolumeNames.Len() > 0 { + for i := range status.VolumeMounts { + volumeName := status.VolumeMounts[i].Name + if !imageVolumeNames.Has(volumeName) { + continue + } + + var imageSpec kubecontainer.ImageSpec + for _, curVolumeMount := range cs.Mounts { + if curVolumeMount.Image == nil || curVolumeMount.Name != volumeName { + continue + } + imageSpec = kuberuntime.ToKubeContainerImageSpec(&runtimeapi.Image{ + Id: curVolumeMount.Image.Image, + Spec: curVolumeMount.Image, + }) + break + } + + if imageSpec.Image == "" { + logger.Error(fmt.Errorf("image was not found"), "", "pod", pod.Name, "container", cs.Name, "volume", volumeName) + continue + } + + imageRef, err := kl.containerRuntime.GetImageRef(ctx, imageSpec) + if err != nil { + logger.Error(err, "error getting image volume digest", "volume", volumeName) + continue + } + if status.VolumeMounts[i].VolumeStatus == nil { + status.VolumeMounts[i].VolumeStatus = &v1.VolumeStatus{} + } + if status.VolumeMounts[i].VolumeStatus.Image == nil { + status.VolumeMounts[i].VolumeStatus.Image = &v1.ImageVolumeStatus{} + } + status.VolumeMounts[i].VolumeStatus.Image.ImageRef = imageRef + } + } + switch { case cs.State == kubecontainer.ContainerStateRunning: status.State.Running = &v1.ContainerStateRunning{StartedAt: metav1.NewTime(cs.StartedAt)} @@ -2424,7 +2501,7 @@ func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecon defaultWaitingState = v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: PodInitializing}} } - supportsRRO := kl.runtimeClassSupportsRecursiveReadOnlyMounts(pod) + supportsRRO := kl.runtimeClassSupportsRecursiveReadOnlyMounts(logger, pod) for _, container := range containers { status := &v1.ContainerStatus{ @@ -2444,12 +2521,12 @@ func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecon if vol.ReadOnly { rroMode := v1.RecursiveReadOnlyDisabled if b, err := resolveRecursiveReadOnly(vol, supportsRRO); err != nil { - klog.ErrorS(err, "failed to resolve recursive read-only mode", "mode", *vol.RecursiveReadOnly) + logger.Error(err, "failed to resolve recursive read-only mode", "mode", *vol.RecursiveReadOnly) } else if b { if utilfeature.DefaultFeatureGate.Enabled(features.RecursiveReadOnlyMounts) { rroMode = v1.RecursiveReadOnlyEnabled } else { - klog.ErrorS(nil, "recursive read-only mount needs feature gate to be enabled", + logger.Error(nil, "recursive read-only mount needs feature gate to be enabled", "featureGate", features.RecursiveReadOnlyMounts) } } @@ -2620,6 +2697,31 @@ func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecon if s != nil && s.State == kubecontainer.ContainerStateExited && s.ExitCode == 0 { continue } + // When pod status is not persisted, init container status may be lost. + // This can occur in the following scenarios: + // 1. Static pods + // 2. Regular pods where init container status failed to sync to the API server + // (e.g., due to control plane issues, though this is rare) + // + // In these cases, if the init container has already been removed from the runtime, + // kubelet cannot determine its status. However, we can infer that non-restartable + // init containers have completed successfully if regular containers have been created. + // Note: Restartable init containers (sidecars) should remain in waiting state and + // will be restarted by the kubelet. + isSidecar := container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways + if s == nil && + kuberuntime.HasAnyRegularContainerCreated(pod, podStatus) && + !isSidecar && + statuses[container.Name].State.Waiting != nil { + statuses[container.Name].State = v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Reason: "Completed", + Message: "Unable to get init container status from container runtime and pod has been initialized, treat it as exited normally", + ExitCode: 0, + }, + } + continue + } } // If a container should be restarted in next syncpod, it is *Waiting*. if !kubecontainer.ShouldContainerBeRestarted(logger, &container, pod, podStatus) { @@ -2753,7 +2855,7 @@ func (kl *Kubelet) GetPortForward(ctx context.Context, podName, podNamespace str // cleanupOrphanedPodCgroups removes cgroups that should no longer exist. // it reconciles the cached state of cgroupPods with the specified list of runningPods -func (kl *Kubelet) cleanupOrphanedPodCgroups(pcm cm.PodContainerManager, cgroupPods map[types.UID]cm.CgroupName, possiblyRunningPods map[types.UID]sets.Empty) { +func (kl *Kubelet) cleanupOrphanedPodCgroups(logger klog.Logger, pcm cm.PodContainerManager, cgroupPods map[types.UID]cm.CgroupName, possiblyRunningPods map[types.UID]sets.Empty) { // Iterate over all the found pods to verify if they should be running for uid, val := range cgroupPods { // if the pod is in the running set, its not a candidate for cleanup @@ -2765,45 +2867,43 @@ func (kl *Kubelet) cleanupOrphanedPodCgroups(pcm cm.PodContainerManager, cgroupP // so any memory backed volumes don't have their charges propagated to the // parent croup. If the volumes still exist, reduce the cpu shares for any // process in the cgroup to the minimum value while we wait. - if podVolumesExist := kl.podVolumesExist(uid); podVolumesExist { - klog.V(3).InfoS("Orphaned pod found, but volumes not yet removed. Reducing cpu to minimum", "podUID", uid) - // TODO: Pass logger from context once contextual logging migration is complete - if err := pcm.ReduceCPULimits(klog.TODO(), val); err != nil { - klog.InfoS("Failed to reduce cpu time for pod pending volume cleanup", "podUID", uid, "err", err) + if podVolumesExist := kl.podVolumesExist(logger, uid); podVolumesExist { + logger.V(3).Info("Orphaned pod found, but volumes not yet removed. Reducing cpu to minimum", "podUID", uid) + if err := pcm.ReduceCPULimits(logger, val); err != nil { + logger.Info("Failed to reduce cpu time for pod pending volume cleanup", "podUID", uid, "err", err) } continue } - klog.V(3).InfoS("Orphaned pod found, removing pod cgroups", "podUID", uid) + logger.V(3).Info("Orphaned pod found, removing pod cgroups", "podUID", uid) // Destroy all cgroups of pods that should not be running, // by first killing all the attached processes in these cgroups. // The error return value of Destroy is explicitly checked and logged, // but errors are tolerated since the housekeeping loop loop would // again try to delete these unwanted pod cgroups. - // TODO: Pass logger from context once contextual logging migration is complete go func() { - if err := pcm.Destroy(klog.TODO(), val); err != nil { - klog.InfoS("Failed to destroy orphaned pod cgroup", "podUID", uid, "err", err) + if err := pcm.Destroy(logger, val); err != nil { + logger.Info("Failed to destroy orphaned pod cgroup", "podUID", uid, "err", err) } }() } } -func (kl *Kubelet) runtimeClassSupportsRecursiveReadOnlyMounts(pod *v1.Pod) bool { +func (kl *Kubelet) runtimeClassSupportsRecursiveReadOnlyMounts(logger klog.Logger, pod *v1.Pod) bool { if kl.runtimeClassManager == nil { return false } runtimeHandlerName, err := kl.runtimeClassManager.LookupRuntimeHandler(pod.Spec.RuntimeClassName) if err != nil { - klog.ErrorS(err, "failed to look up the runtime handler", "runtimeClassName", pod.Spec.RuntimeClassName) + logger.Error(err, "failed to look up the runtime handler", "runtimeClassName", pod.Spec.RuntimeClassName) return false } runtimeHandlers := kl.runtimeState.runtimeHandlers() - return runtimeHandlerSupportsRecursiveReadOnlyMounts(runtimeHandlerName, runtimeHandlers) + return runtimeHandlerSupportsRecursiveReadOnlyMounts(logger, runtimeHandlerName, runtimeHandlers) } // runtimeHandlerSupportsRecursiveReadOnlyMounts checks whether the runtime handler supports recursive read-only mounts. // The kubelet feature gate is not checked here. -func runtimeHandlerSupportsRecursiveReadOnlyMounts(runtimeHandlerName string, runtimeHandlers []kubecontainer.RuntimeHandler) bool { +func runtimeHandlerSupportsRecursiveReadOnlyMounts(logger klog.Logger, runtimeHandlerName string, runtimeHandlers []kubecontainer.RuntimeHandler) bool { if len(runtimeHandlers) == 0 { // The runtime does not support returning the handler list. // No need to print a warning here. @@ -2814,7 +2914,7 @@ func runtimeHandlerSupportsRecursiveReadOnlyMounts(runtimeHandlerName string, ru return h.SupportsRecursiveReadOnlyMounts } } - klog.ErrorS(nil, "Unknown runtime handler", "runtimeHandlerName", runtimeHandlerName) + logger.Error(nil, "Unknown runtime handler", "runtimeHandlerName", runtimeHandlerName) return false } diff --git a/pkg/kubelet/kubelet_pods_linux_test.go b/pkg/kubelet/kubelet_pods_linux_test.go index 7b76d0a1932d5..379ae3b4e3ffd 100644 --- a/pkg/kubelet/kubelet_pods_linux_test.go +++ b/pkg/kubelet/kubelet_pods_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -26,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/utils/ptr" @@ -38,9 +38,11 @@ import ( volumetest "k8s.io/kubernetes/pkg/volume/testing" "k8s.io/kubernetes/pkg/volume/util/hostutil" "k8s.io/kubernetes/pkg/volume/util/subpath" + "k8s.io/kubernetes/test/utils/ktesting" ) func TestMakeMounts(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) bTrue := true propagationHostToContainer := v1.MountPropagationHostToContainer propagationBidirectional := v1.MountPropagationBidirectional @@ -451,6 +453,10 @@ func TestMakeMounts(t *testing.T) { for _, featureEnabled := range tc.imageVolumeFeatureEnabled { name := fmt.Sprintf("features.ImageVolume is %v, %s", featureEnabled, name) t.Run(name, func(t *testing.T) { + if !featureEnabled { + // Set emulation version to v1.35 (last version before GA) to allow disabling the feature gate + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, featureEnabled) fhu := hostutil.NewFakeHostUtil(nil) fsp := &subpath.FakeSubpath{} @@ -460,7 +466,7 @@ func TestMakeMounts(t *testing.T) { }, } - mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO, tc.imageVolumes) + mounts, _, err := makeMounts(logger, &pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO, tc.imageVolumes) // validate only the error if we expect an error if tc.expectErr { @@ -482,6 +488,7 @@ func TestMakeMounts(t *testing.T) { } func TestMakeMountsEtcHostsFile(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test-ns", @@ -559,7 +566,7 @@ func TestMakeMountsEtcHostsFile(t *testing.T) { tt.containerFn(container) } - mounts, _, err := makeMounts(pod, t.TempDir(), container, "fakepodname", "fakedomain", tt.podIPs, tt.podVolumes, fhu, fsp, nil, false, nil) + mounts, _, err := makeMounts(logger, pod, t.TempDir(), container, "fakepodname", "fakedomain", tt.podIPs, tt.podVolumes, fhu, fsp, nil, false, nil) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -580,6 +587,7 @@ func TestMakeMountsEtcHostsFile(t *testing.T) { } func TestMakeBlockVolumes(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -695,7 +703,7 @@ func TestMakeBlockVolumes(t *testing.T) { }, } blkutil := volumetest.NewBlockVolumePathHandler() - blkVolumes, err := kubelet.makeBlockVolumes(&pod, &tc.container, tc.podVolumes, blkutil) + blkVolumes, err := kubelet.makeBlockVolumes(logger, &pod, &tc.container, tc.podVolumes, blkutil) // validate only the error if we expect an error if tc.expectErr { if err == nil || err.Error() != tc.expectedErrMsg { diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index 7678b70c71548..c3a09e4354901 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -41,6 +41,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" core "k8s.io/client-go/testing" @@ -48,9 +49,8 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/metrics/testutil" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" - "k8s.io/klog/v2/ktesting" - "k8s.io/kubelet/pkg/cri/streaming/portforward" - "k8s.io/kubelet/pkg/cri/streaming/remotecommand" + "k8s.io/cri-streaming/pkg/streaming/portforward" + "k8s.io/cri-streaming/pkg/streaming/remotecommand" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" @@ -61,6 +61,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/secret" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/test/utils/ktesting" netutils "k8s.io/utils/net" "k8s.io/utils/ptr" ) @@ -307,7 +308,7 @@ fd00::6 podFoo.domainFoo podFoo } func TestRunInContainerNoSuchPod(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -318,7 +319,7 @@ func TestRunInContainerNoSuchPod(t *testing.T) { podNamespace := "nsFoo" containerName := "containerFoo" output, err := kubelet.RunInContainer( - ctx, + tCtx, kubecontainer.GetPodFullName(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace}}), "", containerName, @@ -328,7 +329,7 @@ func TestRunInContainerNoSuchPod(t *testing.T) { } func TestRunInContainer(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) for _, testError := range []error{nil, errors.New("bar")} { testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -354,12 +355,12 @@ func TestRunInContainer(t *testing.T) { }}, } cmd := []string{"ls"} - actualOutput, err := kubelet.RunInContainer(ctx, "podFoo_nsFoo", "", "containerFoo", cmd) + actualOutput, err := kubelet.RunInContainer(tCtx, "podFoo_nsFoo", "", "containerFoo", cmd) assert.Equal(t, containerID, fakeCommandRunner.ContainerID, "(testError=%v) ID", testError) assert.Equal(t, cmd, fakeCommandRunner.Cmd, "(testError=%v) command", testError) // this isn't 100% foolproof as a bug in a real CommandRunner where it fails to copy to stdout/stderr wouldn't be caught by this test assert.Equal(t, "foo", string(actualOutput), "(testError=%v) output", testError) - assert.Equal(t, err, testError, "(testError=%v) err", testError) + assert.Equal(t, testError, err, "(testError=%v) err", testError) } } @@ -395,6 +396,7 @@ func buildService(name, namespace, clusterIP, protocol string, port int) *v1.Ser } func TestMakeEnvironmentVariables(t *testing.T) { + tCtx := ktesting.Init(t) trueVal := true services := []*v1.Service{ buildService("kubernetes", metav1.NamespaceDefault, "1.2.3.1", "TCP", 8081), @@ -2034,7 +2036,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { testPod.Annotations[kubetypes.ConfigSourceAnnotationKey] = "file" } - result, err := kl.makeEnvironmentVariables(testPod, tc.container, podIP, tc.podIPs, kubecontainer.VolumeMap{}) + result, err := kl.makeEnvironmentVariables(tCtx, testPod, tc.container, podIP, tc.podIPs, kubecontainer.VolumeMap{}) select { case e := <-fakeRecorder.Events: assert.Equal(t, tc.expectedEvent, e) @@ -2202,8 +2204,13 @@ func withRestartCount(status v1.ContainerStatus, restartCount int32) v1.Containe status.RestartCount = restartCount return status } +func withLastTerminationState(status v1.ContainerStatus, lastState v1.ContainerState) v1.ContainerStatus { + status.LastTerminationState = lastState + return status +} func TestPodPhaseWithRestartAlways(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", Containers: []v1.Container{ @@ -2338,12 +2345,13 @@ func TestPodPhaseWithRestartAlways(t *testing.T) { }, } for _, test := range tests { - status := getPhase(test.pod, test.pod.Status.ContainerStatuses, test.podIsTerminal, false) + status := getPhase(logger, test.pod, test.pod.Status.ContainerStatuses, test.podIsTerminal, false) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartAlwaysInitContainers(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", InitContainers: []v1.Container{ @@ -2442,12 +2450,13 @@ func TestPodPhaseWithRestartAlwaysInitContainers(t *testing.T) { for _, test := range tests { statusInfo := test.pod.Status.InitContainerStatuses statusInfo = append(statusInfo, test.pod.Status.ContainerStatuses...) - status := getPhase(test.pod, statusInfo, false, false) + status := getPhase(logger, test.pod, statusInfo, false, false) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartAlwaysRestartableInitContainers(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", InitContainers: []v1.Container{ @@ -2685,12 +2694,13 @@ func TestPodPhaseWithRestartAlwaysRestartableInitContainers(t *testing.T) { for _, test := range tests { statusInfo := test.pod.Status.InitContainerStatuses statusInfo = append(statusInfo, test.pod.Status.ContainerStatuses...) - status := getPhase(test.pod, statusInfo, test.podIsTerminal, test.podHasInitialized) + status := getPhase(logger, test.pod, statusInfo, test.podIsTerminal, test.podHasInitialized) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartAlwaysAndPodHasRun(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", InitContainers: []v1.Container{ @@ -2798,12 +2808,13 @@ func TestPodPhaseWithRestartAlwaysAndPodHasRun(t *testing.T) { for _, test := range tests { statusInfo := test.pod.Status.InitContainerStatuses statusInfo = append(statusInfo, test.pod.Status.ContainerStatuses...) - status := getPhase(test.pod, statusInfo, false, test.podHasInitialized) + status := getPhase(logger, test.pod, statusInfo, false, test.podHasInitialized) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartNever(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", Containers: []v1.Container{ @@ -2898,12 +2909,13 @@ func TestPodPhaseWithRestartNever(t *testing.T) { }, } for _, test := range tests { - status := getPhase(test.pod, test.pod.Status.ContainerStatuses, false, false) + status := getPhase(logger, test.pod, test.pod.Status.ContainerStatuses, false, false) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartNeverInitContainers(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", InitContainers: []v1.Container{ @@ -3002,12 +3014,13 @@ func TestPodPhaseWithRestartNeverInitContainers(t *testing.T) { for _, test := range tests { statusInfo := test.pod.Status.InitContainerStatuses statusInfo = append(statusInfo, test.pod.Status.ContainerStatuses...) - status := getPhase(test.pod, statusInfo, false, false) + status := getPhase(logger, test.pod, statusInfo, false, false) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartNeverRestartableInitContainers(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", InitContainers: []v1.Container{ @@ -3214,12 +3227,13 @@ func TestPodPhaseWithRestartNeverRestartableInitContainers(t *testing.T) { for _, test := range tests { statusInfo := test.pod.Status.InitContainerStatuses statusInfo = append(statusInfo, test.pod.Status.ContainerStatuses...) - status := getPhase(test.pod, statusInfo, false, test.podHasInitialized) + status := getPhase(logger, test.pod, statusInfo, false, test.podHasInitialized) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithRestartOnFailure(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) desiredState := v1.PodSpec{ NodeName: "machine", Containers: []v1.Container{ @@ -3327,12 +3341,13 @@ func TestPodPhaseWithRestartOnFailure(t *testing.T) { }, } for _, test := range tests { - status := getPhase(test.pod, test.pod.Status.ContainerStatuses, false, false) + status := getPhase(logger, test.pod, test.pod.Status.ContainerStatuses, false, false) assert.Equal(t, test.status, status, "[test %s]", test.test) } } func TestPodPhaseWithContainerRestartPolicy(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) var ( containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways containerRestartPolicyOnFailure = v1.ContainerRestartPolicyOnFailure @@ -3457,13 +3472,14 @@ func TestPodPhaseWithContainerRestartPolicy(t *testing.T) { ContainerStatuses: tc.statuses, }, } - phase := getPhase(pod, tc.statuses, tc.podIsTerminal, true) + phase := getPhase(logger, pod, tc.statuses, tc.podIsTerminal, true) assert.Equal(t, tc.expectedPhase, phase) }) } } func TestPodPhaseWithContainerRestartPolicyInitContainers(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) var ( containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways containerRestartPolicyOnFailure = v1.ContainerRestartPolicyOnFailure @@ -3530,13 +3546,14 @@ func TestPodPhaseWithContainerRestartPolicyInitContainers(t *testing.T) { ContainerStatuses: tc.statuses, }, } - phase := getPhase(pod, tc.statuses, tc.podIsTerminal, true) + phase := getPhase(logger, pod, tc.statuses, tc.podIsTerminal, true) assert.Equal(t, tc.expectedPhase, phase) }) } } func TestPodPhaseWithRestartAllContainers(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.ContainerRestartRules: true, features.NodeDeclaredFeatures: true, @@ -3781,13 +3798,14 @@ func TestPodPhaseWithRestartAllContainers(t *testing.T) { ContainerStatuses: tc.statuses, }, } - phase := getPhase(pod, tc.statuses, false, true) + phase := getPhase(logger, pod, tc.statuses, false, true) assert.Equal(t, tc.expectedPhase, phase) }) } } func TestConvertToAPIContainerStatuses(t *testing.T) { + tCtx := ktesting.Init(t) desiredState := v1.PodSpec{ NodeName: "machine", Containers: []v1.Container{ @@ -3796,6 +3814,29 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { }, RestartPolicy: v1.RestartPolicyAlways, } + desiredStateWithInitContainer := v1.PodSpec{ + NodeName: "machine", + InitContainers: []v1.Container{ + {Name: "init-1"}, + }, + Containers: []v1.Container{ + {Name: "containerA"}, + }, + RestartPolicy: v1.RestartPolicyAlways, + } + desiredStateWithSidecarContainer := v1.PodSpec{ + NodeName: "machine", + InitContainers: []v1.Container{ + { + Name: "sidecar-1", + RestartPolicy: ptr.To(v1.ContainerRestartPolicyAlways), + }, + }, + Containers: []v1.Container{ + {Name: "containerA"}, + }, + RestartPolicy: v1.RestartPolicyAlways, + } now := metav1.Now() tests := []struct { @@ -3877,7 +3918,7 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { }, }, { - name: "currently running with lower reset restartCount; should keeping previous restartCount", + name: "currently running with lower restartCount; should keeping previous restartCount", pod: &v1.Pod{ Spec: desiredState, }, @@ -3902,8 +3943,16 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { // no init containers // is not an init container expected: []v1.ContainerStatus{ - withRestartCount(runningState("containerA"), 6), - withRestartCount(runningState("containerB"), 6), + withRestartCount(withLastTerminationState(runningState("containerA"), v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: -1, + }, + }), 6), + withRestartCount(withLastTerminationState(runningState("containerB"), v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: -1, + }, + }), 6), }, }, { @@ -3997,17 +4046,24 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { currentStatus: &kubecontainer.PodStatus{ ContainerStatuses: []*kubecontainer.Status{{ Name: "containerA", + ID: kubecontainer.ContainerID{ID: "containerA-new"}, State: kubecontainer.ContainerStateRunning, }}, }, previousStatus: []v1.ContainerStatus{ - withRestartCount(waitingState("containerA"), 1), + withRestartCount(waitingStateWithRestartingAllContainers("containerA"), 1), failedStateWithExitCode("containerB", 42), }, containers: desiredState.Containers, podRestarting: true, expected: []v1.ContainerStatus{ - withRestartCount(runningState("containerA"), 1), + withRestartCount(withLastTerminationState(runningState("containerA"), v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Reason: RestartingAllContainers, + Message: "The container is removed because RestartAllContainers in place", + ExitCode: 137, + }, + }), 1), withRestartCount(waitingStateWithRestartingAllContainers("containerB"), 1), }, }, @@ -4057,6 +4113,81 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { withRestartCount(waitingStateWithRestartingAllContainers("containerB"), 1), }, }, + { + name: "Unable to get init container status from container runtime and pod has been initialized, treat it as exited normally", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod", + }, + Spec: desiredStateWithInitContainer, + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{}, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{ID: "foo"}, + Name: "containerA", + StartedAt: time.Unix(1, 0).UTC(), + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + previousStatus: []v1.ContainerStatus{}, + containers: desiredStateWithInitContainer.InitContainers, + expected: []v1.ContainerStatus{ + { + Name: "init-1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Reason: "Completed", + Message: "Unable to get init container status from container runtime and pod has been initialized, treat it as exited normally", + ExitCode: 0, + }, + }, + }, + }, + hasInitContainers: true, + isInitContainer: true, + }, + { + name: "Unable to get sidecar container status from container runtime and pod has been initialized, sidecar container should be waiting", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod", + }, + Spec: desiredStateWithSidecarContainer, + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{}, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{ID: "foo"}, + Name: "containerA", + StartedAt: time.Unix(1, 0).UTC(), + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + previousStatus: []v1.ContainerStatus{}, + containers: desiredStateWithSidecarContainer.InitContainers, + expected: []v1.ContainerStatus{ + { + Name: "sidecar-1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: "PodInitializing", + Message: "", + }, + }, + }, + }, + hasInitContainers: true, + isInitContainer: true, + }, } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.ContainerRestartRules: true, @@ -4070,10 +4201,12 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { defer testKubelet.Cleanup() kl := testKubelet.kubelet containerStatuses := kl.convertToAPIContainerStatuses( + tCtx, test.pod, test.currentStatus, test.previousStatus, test.containers, + nil, test.hasInitContainers, test.isInitContainer, test.podRestarting, @@ -4091,6 +4224,187 @@ func TestConvertToAPIContainerStatuses(t *testing.T) { } } +// imageDigestRuntime is a simple wrapper that returns a fixed digest for image volumes +type imageDigestRuntime struct { + *containertest.FakeRuntime + digest string +} + +func (r *imageDigestRuntime) GetImageRef(ctx context.Context, image kubecontainer.ImageSpec) (string, error) { + return r.digest, nil +} + +func TestConvertToAPIContainerStatusesWithImageVolumeDigest(t *testing.T) { + tCtx := ktesting.Init(t) + const ( + imageVolumeName = "image-volume" + imageVolumeMountPath = "/mock/path" + imageVolumeRef = "registry.k8s.io/example:1.2.3" + imageDigest = "sha256:abcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd" + ) + + now := metav1.Now() + + tests := []struct { + name string + pod *v1.Pod + currentStatus *kubecontainer.PodStatus + previousStatus []v1.ContainerStatus + expected []v1.ContainerStatus + }{ + { + name: "with image volume - should include digest in status", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + NodeName: "node123", + Containers: []v1.Container{ + { + Name: "containerA", + VolumeMounts: []v1.VolumeMount{ + { + Name: imageVolumeName, + MountPath: imageVolumeMountPath, + }, + }, + }, + }, + Volumes: []v1.Volume{ + { + Name: imageVolumeName, + VolumeSource: v1.VolumeSource{ + Image: &v1.ImageVolumeSource{ + Reference: imageVolumeRef, + }, + }, + }, + }, + }, + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + }, + }, + ObjectMeta: metav1.ObjectMeta{Name: "my-pod", DeletionTimestamp: &now}, + }, + currentStatus: &kubecontainer.PodStatus{ + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{Type: "test", ID: "containerA-id"}, + Name: "containerA", + State: kubecontainer.ContainerStateRunning, + Image: imageVolumeRef, + Mounts: []kubecontainer.Mount{ + { + Name: imageVolumeName, + ContainerPath: imageVolumeMountPath, + Image: &runtimeapi.ImageSpec{Image: imageVolumeRef}, + }, + }, + }, + }, + }, + previousStatus: []v1.ContainerStatus{ + func() v1.ContainerStatus { + previousContainerA := runningState("containerA") + previousContainerA.VolumeMounts = []v1.VolumeMountStatus{ + { + Name: imageVolumeName, + MountPath: imageVolumeMountPath, + }, + } + return previousContainerA + }(), + }, + expected: []v1.ContainerStatus{ + func() v1.ContainerStatus { + containerStatus := runningState("containerA") + containerStatus.Image = imageVolumeRef + containerStatus.ContainerID = "test://containerA-id" + containerStatus.Resources = &v1.ResourceRequirements{} + containerStatus.VolumeMounts = []v1.VolumeMountStatus{ + { + Name: imageVolumeName, + MountPath: imageVolumeMountPath, + VolumeStatus: &v1.VolumeStatus{Image: &v1.ImageVolumeStatus{ImageRef: imageDigest}}, + }, + } + return containerStatus + }(), + }, + }, + { + name: "with no image volume - should not include digest in status", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + NodeName: "node123", + Containers: []v1.Container{ + { + Name: "containerA", + }, + }, + }, + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + }, + }, + ObjectMeta: metav1.ObjectMeta{Name: "my-pod", DeletionTimestamp: &now}, + }, + currentStatus: &kubecontainer.PodStatus{ + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{Type: "test", ID: "containerA-id"}, + Name: "containerA", + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + previousStatus: []v1.ContainerStatus{ + runningState("containerA"), + }, + expected: []v1.ContainerStatus{ + func() v1.ContainerStatus { + containerStatus := runningState("containerA") + containerStatus.ContainerID = "test://containerA-id" + containerStatus.Resources = &v1.ResourceRequirements{} + return containerStatus + }(), + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolumeWithDigest, true) + + testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) + defer testKubelet.Cleanup() + kl := testKubelet.kubelet + + // Mock the container runtime to return the expected image digest + kl.containerRuntime = &imageDigestRuntime{ + FakeRuntime: &containertest.FakeRuntime{}, + digest: imageDigest, + } + + containerStatuses := kl.convertToAPIContainerStatuses( + tCtx, + test.pod, + test.currentStatus, + test.previousStatus, + test.pod.Spec.Containers, + sets.New(imageVolumeName), + false, + false, + false, + ) + for i, status := range containerStatuses { + assert.Equal(t, test.expected[i], status, "[test %s]", test.name) + } + }) + } +} + func Test_generateAPIPodStatus(t *testing.T) { desiredState := v1.PodSpec{ NodeName: "machine", @@ -4120,15 +4434,16 @@ func Test_generateAPIPodStatus(t *testing.T) { normalized_now := now.Rfc3339Copy() tests := []struct { - name string - pod *v1.Pod - currentStatus *kubecontainer.PodStatus - unreadyContainer []string - previousStatus v1.PodStatus - isPodTerminal bool - expected v1.PodStatus - expectedPodDisruptionCondition *v1.PodCondition - expectedPodReadyToStartContainersCondition v1.PodCondition + name string + pod *v1.Pod + currentStatus *kubecontainer.PodStatus + unreadyContainer []string + previousStatus v1.PodStatus + isPodTerminal bool + expected v1.PodStatus + expectedPodDisruptionCondition *v1.PodCondition + expectedPodReadyToStartContainersCondition v1.PodCondition + inPlacePodLevelResourcesVerticalScalingEnabled bool }{ { name: "pod disruption condition is copied over and the phase is set to failed when deleted", @@ -4227,7 +4542,7 @@ func Test_generateAPIPodStatus(t *testing.T) { }, }, { - name: "current status ready, with previous statuses and no deletion", + name: "current status ready, with previous statuses and deletion, InPlacePodLevelResourcesVerticalScaling enabled", pod: &v1.Pod{ Spec: desiredState, Status: v1.PodStatus{ @@ -4236,6 +4551,7 @@ func Test_generateAPIPodStatus(t *testing.T) { runningState("containerB"), }, }, + ObjectMeta: metav1.ObjectMeta{Name: "my-pod", DeletionTimestamp: &now}, }, currentStatus: sandboxReadyStatus, previousStatus: v1.PodStatus{ @@ -4256,28 +4572,32 @@ func Test_generateAPIPodStatus(t *testing.T) { {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, ContainerStatuses: []v1.ContainerStatus{ - ready(waitingWithLastTerminationUnknown("containerA", 1)), - ready(waitingWithLastTerminationUnknown("containerB", 1)), + ready(waitingWithLastTerminationUnknown("containerA", 0)), + ready(waitingWithLastTerminationUnknown("containerB", 0)), + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{}, + Limits: v1.ResourceList{}, }, }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, Status: v1.ConditionTrue, }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, }, { - name: "terminal phase cannot be changed (apiserver previous is succeeded)", + name: "current status ready, with previous statuses and no deletion", pod: &v1.Pod{ Spec: desiredState, Status: v1.PodStatus{ - Phase: v1.PodSucceeded, ContainerStatuses: []v1.ContainerStatus{ runningState("containerA"), runningState("containerB"), }, }, }, - currentStatus: &kubecontainer.PodStatus{}, + currentStatus: sandboxReadyStatus, previousStatus: v1.PodStatus{ ContainerStatuses: []v1.ContainerStatus{ runningState("containerA"), @@ -4285,14 +4605,14 @@ func Test_generateAPIPodStatus(t *testing.T) { }, }, expected: v1.PodStatus{ - Phase: v1.PodSucceeded, + Phase: v1.PodRunning, HostIP: "127.0.0.1", HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, QOSClass: v1.PodQOSBestEffort, Conditions: []v1.PodCondition{ - {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, - {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, - {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, ContainerStatuses: []v1.ContainerStatus{ @@ -4302,37 +4622,627 @@ func Test_generateAPIPodStatus(t *testing.T) { }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, - Status: v1.ConditionFalse, + Status: v1.ConditionTrue, }, }, { - name: "terminal phase from previous status must remain terminal, restartAlways", + name: "current status ready, with previous statuses and no deletion, InPlacePodLevelResourcesVerticalScaling enabled", pod: &v1.Pod{ - Spec: desiredState, + Spec: v1.PodSpec{ + NodeName: "machine", + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + { + Name: "containerB", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), + v1.ResourceMemory: resource.MustParse("300Mi"), + }, + }, + RestartPolicy: v1.RestartPolicyAlways, + }, Status: v1.PodStatus{ - Phase: v1.PodRunning, ContainerStatuses: []v1.ContainerStatus{ runningState("containerA"), runningState("containerB"), }, }, }, - currentStatus: &kubecontainer.PodStatus{}, + currentStatus: sandboxReadyStatus, previousStatus: v1.PodStatus{ - Phase: v1.PodSucceeded, ContainerStatuses: []v1.ContainerStatus{ runningState("containerA"), runningState("containerB"), }, - // Reason and message should be preserved - Reason: "Test", - Message: "test", }, + expected: v1.PodStatus{ + Phase: v1.PodRunning, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBurstable, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "containerA", + State: v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: ContainerCreating}}, + LastTerminationState: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 137, + Reason: kubecontainer.ContainerReasonStatusUnknown, + Message: "The container could not be located when the pod was deleted. The container used to be Running", + }, + }, + Ready: true, + RestartCount: 1, + }, + { + Name: "containerB", + State: v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: ContainerCreating}}, + LastTerminationState: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 137, + Reason: kubecontainer.ContainerReasonStatusUnknown, + Message: "The container could not be located when the pod was deleted. The container used to be Running", + }, + }, + Ready: true, + RestartCount: 1, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), + v1.ResourceMemory: resource.MustParse("300Mi"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), + v1.ResourceMemory: resource.MustParse("300Mi"), + }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, + }, + { + name: "terminal phase cannot be changed (apiserver previous is succeeded)", + pod: &v1.Pod{ + Spec: desiredState, + Status: v1.PodStatus{ + Phase: v1.PodSucceeded, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{}, + previousStatus: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + expected: v1.PodStatus{ + Phase: v1.PodSucceeded, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, + {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(waitingWithLastTerminationUnknown("containerA", 1)), + ready(waitingWithLastTerminationUnknown("containerB", 1)), + }, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionFalse, + }, + }, + { + name: "terminal phase from previous status must remain terminal, restartAlways", + pod: &v1.Pod{ + Spec: desiredState, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{}, + previousStatus: v1.PodStatus{ + Phase: v1.PodSucceeded, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + // Reason and message should be preserved + Reason: "Test", + Message: "test", + }, + expected: v1.PodStatus{ + Phase: v1.PodSucceeded, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, + {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(waitingWithLastTerminationUnknown("containerA", 1)), + ready(waitingWithLastTerminationUnknown("containerB", 1)), + }, + Reason: "Test", + Message: "test", + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionFalse, + }, + }, + { + name: "terminal phase from previous status must remain terminal, restartNever", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + NodeName: "machine", + Containers: []v1.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: v1.RestartPolicyNever, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{}, + previousStatus: v1.PodStatus{ + Phase: v1.PodSucceeded, + ContainerStatuses: []v1.ContainerStatus{ + succeededState("containerA"), + succeededState("containerB"), + }, + // Reason and message should be preserved + Reason: "Test", + Message: "test", + }, + expected: v1.PodStatus{ + Phase: v1.PodSucceeded, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, + {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(succeededState("containerA")), + ready(succeededState("containerB")), + }, + Reason: "Test", + Message: "test", + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionFalse, + }, + }, + { + name: "running can revert to pending", + pod: &v1.Pod{ + Spec: desiredState, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + currentStatus: sandboxReadyStatus, + previousStatus: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + waitingState("containerA"), + waitingState("containerB"), + }, + }, + expected: v1.PodStatus{ + Phase: v1.PodPending, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(waitingStateWithReason("containerA", "ContainerCreating")), + ready(waitingStateWithReason("containerB", "ContainerCreating")), + }, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + }, + { + name: "reason and message are preserved when phase doesn't change", + pod: &v1.Pod{ + Spec: desiredState, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + waitingState("containerA"), + waitingState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{ID: "foo"}, + Name: "containerB", + StartedAt: time.Unix(1, 0).UTC(), + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + previousStatus: v1.PodStatus{ + Phase: v1.PodPending, + Reason: "Test", + Message: "test", + ContainerStatuses: []v1.ContainerStatus{ + waitingState("containerA"), + runningState("containerB"), + }, + }, + expected: v1.PodStatus{ + Phase: v1.PodPending, + Reason: "Test", + Message: "test", + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(waitingStateWithReason("containerA", "ContainerCreating")), + withResources(ready(withID(runningStateWithStartedAt("containerB", time.Unix(1, 0).UTC()), "://foo"))), + }, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + }, + { + name: "reason and message are cleared when phase changes", + pod: &v1.Pod{ + Spec: desiredState, + Status: v1.PodStatus{ + Phase: v1.PodPending, + ContainerStatuses: []v1.ContainerStatus{ + waitingState("containerA"), + waitingState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + ID: kubecontainer.ContainerID{ID: "c1"}, + Name: "containerA", + StartedAt: time.Unix(1, 0).UTC(), + State: kubecontainer.ContainerStateRunning, + }, + { + ID: kubecontainer.ContainerID{ID: "c2"}, + Name: "containerB", + StartedAt: time.Unix(2, 0).UTC(), + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + previousStatus: v1.PodStatus{ + Phase: v1.PodPending, + Reason: "Test", + Message: "test", + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + expected: v1.PodStatus{ + Phase: v1.PodRunning, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + withResources(ready(withID(runningStateWithStartedAt("containerA", time.Unix(1, 0).UTC()), "://c1"))), + withResources(ready(withID(runningStateWithStartedAt("containerB", time.Unix(2, 0).UTC()), "://c2"))), + }, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + }, + { + name: "InPlacePodLevelResourcesVerticalScaling enabled, pending pod", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodPending, + }, + }, + currentStatus: sandboxReadyStatus, + expected: v1.PodStatus{ + Phase: v1.PodPending, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBurstable, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "containerA", + State: v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: ContainerCreating}}, + Ready: true, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, + }, + { + name: "InPlacePodLevelResourcesVerticalScaling enabled, running pod", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateRunning, + }, + }, + }, + expected: v1.PodStatus{ + Phase: v1.PodRunning, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBurstable, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "containerA", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, + }, + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, + }, + { + name: "InPlacePodLevelResourcesVerticalScaling enabled, succeeded pod", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodSucceeded, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateExited, + ExitCode: 0, + }, + }, + }, + isPodTerminal: true, expected: v1.PodStatus{ Phase: v1.PodSucceeded, HostIP: "127.0.0.1", HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, - QOSClass: v1.PodQOSBestEffort, + QOSClass: v1.PodQOSBurstable, Conditions: []v1.PodCondition{ {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, @@ -4340,91 +5250,188 @@ func Test_generateAPIPodStatus(t *testing.T) { {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, ContainerStatuses: []v1.ContainerStatus{ - ready(waitingWithLastTerminationUnknown("containerA", 1)), - ready(waitingWithLastTerminationUnknown("containerB", 1)), + { + Name: "containerA", + State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{ExitCode: 0, ContainerID: "test://c1"}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, }, - Reason: "Test", - Message: "test", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, - Status: v1.ConditionFalse, + Status: v1.ConditionTrue, }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, }, { - name: "terminal phase from previous status must remain terminal, restartNever", + name: "InPlacePodLevelResourcesVerticalScaling enabled, pod with init containers", pod: &v1.Pod{ Spec: v1.PodSpec{ - NodeName: "machine", + InitContainers: []v1.Container{ + { + Name: "initA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, Containers: []v1.Container{ - {Name: "containerA"}, - {Name: "containerB"}, + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, }, - RestartPolicy: v1.RestartPolicyNever, }, Status: v1.PodStatus{ Phase: v1.PodRunning, - ContainerStatuses: []v1.ContainerStatus{ - runningState("containerA"), - runningState("containerB"), - }, }, }, - currentStatus: &kubecontainer.PodStatus{}, - previousStatus: v1.PodStatus{ - Phase: v1.PodSucceeded, - ContainerStatuses: []v1.ContainerStatus{ - succeededState("containerA"), - succeededState("containerB"), + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + Name: "initA", + ID: kubecontainer.ContainerID{Type: "test", ID: "i1"}, + State: kubecontainer.ContainerStateExited, + ExitCode: 0, + }, + { + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateRunning, + }, }, - // Reason and message should be preserved - Reason: "Test", - Message: "test", }, expected: v1.PodStatus{ - Phase: v1.PodSucceeded, + Phase: v1.PodRunning, HostIP: "127.0.0.1", HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, - QOSClass: v1.PodQOSBestEffort, + QOSClass: v1.PodQOSBurstable, Conditions: []v1.PodCondition{ - {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, - {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, - {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "initA", + State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{ExitCode: 0, ContainerID: "test://i1"}}, + Ready: false, + ContainerID: "test://i1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, ContainerStatuses: []v1.ContainerStatus{ - ready(succeededState("containerA")), - ready(succeededState("containerB")), + { + Name: "containerA", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, }, - Reason: "Test", - Message: "test", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + EphemeralContainerStatuses: []v1.ContainerStatus{}, }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, - Status: v1.ConditionFalse, + Status: v1.ConditionTrue, }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, }, { - name: "running can revert to pending", + name: "InPlacePodLevelResourcesVerticalScaling enabled, no resources set", pod: &v1.Pod{ Spec: desiredState, Status: v1.PodStatus{ Phase: v1.PodRunning, - ContainerStatuses: []v1.ContainerStatus{ - runningState("containerA"), - runningState("containerB"), - }, }, }, - currentStatus: sandboxReadyStatus, - previousStatus: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - waitingState("containerA"), - waitingState("containerB"), + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateRunning, + }, + { + Name: "containerB", + ID: kubecontainer.ContainerID{Type: "test", ID: "c2"}, + State: kubecontainer.ContainerStateRunning, + }, }, }, expected: v1.PodStatus{ - Phase: v1.PodPending, + Phase: v1.PodRunning, HostIP: "127.0.0.1", HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, QOSClass: v1.PodQOSBestEffort, @@ -4435,54 +5442,70 @@ func Test_generateAPIPodStatus(t *testing.T) { {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, ContainerStatuses: []v1.ContainerStatus{ - ready(waitingStateWithReason("containerA", "ContainerCreating")), - ready(waitingStateWithReason("containerB", "ContainerCreating")), + { + Name: "containerA", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{Requests: v1.ResourceList{}, Limits: v1.ResourceList{}}, + }, + { + Name: "containerB", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c2", + Resources: &v1.ResourceRequirements{Requests: v1.ResourceList{}, Limits: v1.ResourceList{}}, + }, }, + Resources: &v1.ResourceRequirements{Requests: v1.ResourceList{}, Limits: v1.ResourceList{}}, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, Status: v1.ConditionTrue, }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, }, { - name: "reason and message are preserved when phase doesn't change", + name: "InPlacePodLevelResourcesVerticalScaling enabled, only CPU set", pod: &v1.Pod{ - Spec: desiredState, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, Status: v1.PodStatus{ Phase: v1.PodRunning, - ContainerStatuses: []v1.ContainerStatus{ - waitingState("containerA"), - waitingState("containerB"), - }, }, }, currentStatus: &kubecontainer.PodStatus{ SandboxStatuses: sandboxReadyStatus.SandboxStatuses, ContainerStatuses: []*kubecontainer.Status{ { - ID: kubecontainer.ContainerID{ID: "foo"}, - Name: "containerB", - StartedAt: time.Unix(1, 0).UTC(), - State: kubecontainer.ContainerStateRunning, + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateRunning, }, }, }, - previousStatus: v1.PodStatus{ - Phase: v1.PodPending, - Reason: "Test", - Message: "test", - ContainerStatuses: []v1.ContainerStatus{ - waitingState("containerA"), - runningState("containerB"), - }, - }, expected: v1.PodStatus{ - Phase: v1.PodPending, - Reason: "Test", - Message: "test", + Phase: v1.PodRunning, HostIP: "127.0.0.1", HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, - QOSClass: v1.PodQOSBestEffort, + QOSClass: v1.PodQOSBurstable, Conditions: []v1.PodCondition{ {Type: v1.PodInitialized, Status: v1.ConditionTrue}, {Type: v1.PodReady, Status: v1.ConditionTrue}, @@ -4490,51 +5513,154 @@ func Test_generateAPIPodStatus(t *testing.T) { {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, ContainerStatuses: []v1.ContainerStatus{ - ready(waitingStateWithReason("containerA", "ContainerCreating")), - withResources(ready(withID(runningStateWithStartedAt("containerB", time.Unix(1, 0).UTC()), "://foo"))), + { + Name: "containerA", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + }, }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, Status: v1.ConditionTrue, }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, }, { - name: "reason and message are cleared when phase changes", + name: "InPlacePodLevelResourcesVerticalScaling enabled, only Memory set", pod: &v1.Pod{ - Spec: desiredState, - Status: v1.PodStatus{ - Phase: v1.PodPending, - ContainerStatuses: []v1.ContainerStatus{ - waitingState("containerA"), - waitingState("containerB"), + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, }, }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, }, currentStatus: &kubecontainer.PodStatus{ SandboxStatuses: sandboxReadyStatus.SandboxStatuses, ContainerStatuses: []*kubecontainer.Status{ { - ID: kubecontainer.ContainerID{ID: "c1"}, - Name: "containerA", - StartedAt: time.Unix(1, 0).UTC(), - State: kubecontainer.ContainerStateRunning, + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateRunning, }, + }, + }, + expected: v1.PodStatus{ + Phase: v1.PodRunning, + HostIP: "127.0.0.1", + HostIPs: []v1.HostIP{{IP: "127.0.0.1"}, {IP: "::1"}}, + QOSClass: v1.PodQOSBurstable, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue}, + {Type: v1.PodReady, Status: v1.ConditionTrue}, + {Type: v1.ContainersReady, Status: v1.ConditionTrue}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ { - ID: kubecontainer.ContainerID{ID: "c2"}, - Name: "containerB", - StartedAt: time.Unix(2, 0).UTC(), - State: kubecontainer.ContainerStateRunning, + Name: "containerA", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, }, - previousStatus: v1.PodStatus{ - Phase: v1.PodPending, - Reason: "Test", - Message: "test", - ContainerStatuses: []v1.ContainerStatus{ - runningState("containerA"), - runningState("containerB"), + expectedPodReadyToStartContainersCondition: v1.PodCondition{ + Type: v1.PodReadyToStartContainers, + Status: v1.ConditionTrue, + }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, + }, + { + name: "InPlacePodLevelResourcesVerticalScaling enabled, 0 values for CPU and Memory", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "containerA", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("0"), + }, + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("0"), + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + }, + currentStatus: &kubecontainer.PodStatus{ + SandboxStatuses: sandboxReadyStatus.SandboxStatuses, + ContainerStatuses: []*kubecontainer.Status{ + { + Name: "containerA", + ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, + State: kubecontainer.ContainerStateRunning, + }, }, }, expected: v1.PodStatus{ @@ -4549,21 +5675,52 @@ func Test_generateAPIPodStatus(t *testing.T) { {Type: v1.PodScheduled, Status: v1.ConditionTrue}, }, ContainerStatuses: []v1.ContainerStatus{ - withResources(ready(withID(runningStateWithStartedAt("containerA", time.Unix(1, 0).UTC()), "://c1"))), - withResources(ready(withID(runningStateWithStartedAt("containerB", time.Unix(2, 0).UTC()), "://c2"))), + { + Name: "containerA", + State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, + Ready: true, + ContainerID: "test://c1", + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("0"), + }, + Limits: v1.ResourceList{}, + }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("0"), + }, + }, + }, + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("0"), + }, + Limits: v1.ResourceList{}, }, + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("0"), + }, + InitContainerStatuses: []v1.ContainerStatus{}, + EphemeralContainerStatuses: []v1.ContainerStatus{}, }, expectedPodReadyToStartContainersCondition: v1.PodCondition{ Type: v1.PodReadyToStartContainers, Status: v1.ConditionTrue, }, + inPlacePodLevelResourcesVerticalScalingEnabled: true, }, } for _, test := range tests { for _, enablePodReadyToStartContainersCondition := range []bool{false, true} { t.Run(test.name, func(t *testing.T) { - logger, _ := ktesting.NewTestContext(t) + logger, tCtx := ktesting.NewTestContext(t) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodReadyToStartContainersCondition, enablePodReadyToStartContainersCondition) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, test.inPlacePodLevelResourcesVerticalScalingEnabled) + testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -4572,7 +5729,7 @@ func Test_generateAPIPodStatus(t *testing.T) { kl.readinessManager.Set(kubecontainer.BuildContainerID("", findContainerStatusByName(test.expected, name).ContainerID), results.Failure, test.pod) } expected := test.expected.DeepCopy() - actual := kl.generateAPIPodStatus(test.pod, test.currentStatus, test.isPodTerminal) + actual := kl.generateAPIPodStatus(tCtx, test.pod, test.currentStatus, test.isPodTerminal) if enablePodReadyToStartContainersCondition { expected.Conditions = append([]v1.PodCondition{test.expectedPodReadyToStartContainersCondition}, expected.Conditions...) } @@ -4684,14 +5841,14 @@ func Test_generateAPIPodStatusForInPlaceVPAEnabled(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logger, _ := ktesting.NewTestContext(t) + logger, tCtx := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet oldStatus := test.pod.Status kl.statusManager.SetPodStatus(logger, test.pod, oldStatus) - actual := kl.generateAPIPodStatus(test.pod, &testKubecontainerPodStatus /* criStatus */, false /* test.isPodTerminal */) + actual := kl.generateAPIPodStatus(tCtx, test.pod, &testKubecontainerPodStatus /* criStatus */, false /* test.isPodTerminal */) for _, c := range actual.Conditions { if c.Type == v1.PodResizePending || c.Type == v1.PodResizeInProgress { t.Fatalf("unexpected resize status: %v", c) @@ -4771,7 +5928,7 @@ func TestGetExec(t *testing.T) { for _, tc := range testcases { t.Run(tc.description, func(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -4793,7 +5950,7 @@ func TestGetExec(t *testing.T) { kubelet.containerRuntime = fakeRuntime kubelet.streamingRuntime = fakeRuntime - redirect, err := kubelet.GetExec(ctx, tc.podFullName, podUID, tc.container, tc.command, remotecommand.Options{}) + redirect, err := kubelet.GetExec(tCtx, tc.podFullName, podUID, tc.container, tc.command, remotecommand.Options{}) if tc.expectError { assert.Error(t, err, description) } else { @@ -4826,7 +5983,7 @@ func TestGetPortForward(t *testing.T) { }} for _, tc := range testcases { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -4848,7 +6005,7 @@ func TestGetPortForward(t *testing.T) { kubelet.containerRuntime = fakeRuntime kubelet.streamingRuntime = fakeRuntime - redirect, err := kubelet.GetPortForward(ctx, tc.podName, podNamespace, podUID, portforward.V4Options{}) + redirect, err := kubelet.GetPortForward(tCtx, tc.podName, podNamespace, podUID, portforward.V4Options{}) if tc.expectError { assert.Error(t, err, description) } else { @@ -4859,6 +6016,7 @@ func TestGetPortForward(t *testing.T) { } func TestTruncatePodHostname(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) for c, test := range map[string]struct { input string output string @@ -4881,13 +6039,14 @@ func TestTruncatePodHostname(t *testing.T) { }, } { t.Logf("TestCase: %q", c) - output, err := truncatePodHostnameIfNeeded("test-pod", test.input) + output, err := truncatePodHostnameIfNeeded(logger, "test-pod", test.input) assert.NoError(t, err) assert.Equal(t, test.output, output) } } func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { + tCtx := ktesting.Init(t) testcases := []struct { name string nodeAddresses []v1.NodeAddress @@ -5008,7 +6167,7 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { IPs: tc.criPodIPs, } - status := kl.generateAPIPodStatus(pod, criStatus, false) + status := kl.generateAPIPodStatus(tCtx, pod, criStatus, false) if !reflect.DeepEqual(status.PodIPs, tc.podIPs) { t.Fatalf("Expected PodIPs %#v, got %#v", tc.podIPs, status.PodIPs) } @@ -5020,6 +6179,7 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { } func TestNodeAddressUpdatesGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { + tCtx := ktesting.Init(t) testcases := []struct { name string nodeIPs []string @@ -5132,7 +6292,7 @@ func TestNodeAddressUpdatesGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { } podStatus.IPs = tc.nodeIPs - status := kl.generateAPIPodStatus(pod, podStatus, false) + status := kl.generateAPIPodStatus(tCtx, pod, podStatus, false) if !reflect.DeepEqual(status.PodIPs, tc.expectedPodIPs) { t.Fatalf("Expected PodIPs %#v, got %#v", tc.expectedPodIPs, status.PodIPs) } @@ -5144,6 +6304,7 @@ func TestNodeAddressUpdatesGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { } func TestGenerateAPIPodStatusPodIPs(t *testing.T) { + tCtx := ktesting.Init(t) testcases := []struct { name string nodeIP string @@ -5265,7 +6426,7 @@ func TestGenerateAPIPodStatusPodIPs(t *testing.T) { IPs: tc.criPodIPs, } - status := kl.generateAPIPodStatus(pod, criStatus, false) + status := kl.generateAPIPodStatus(tCtx, pod, criStatus, false) if !reflect.DeepEqual(status.PodIPs, tc.podIPs) { t.Fatalf("Expected PodIPs %#v, got %#v", tc.podIPs, status.PodIPs) } @@ -5375,6 +6536,7 @@ func TestSortPodIPs(t *testing.T) { } func TestConvertToAPIContainerStatusesDataRace(t *testing.T) { + tCtx := ktesting.Init(t) pod := podWithUIDNameNs("12345", "test-pod", "test-namespace") testTimestamp := time.Unix(123456789, 987654321) @@ -5399,12 +6561,13 @@ func TestConvertToAPIContainerStatusesDataRace(t *testing.T) { // detection, so would catch a race condition consistently, despite only spawning 2 goroutines for i := 0; i < 2; i++ { go func() { - kl.convertToAPIContainerStatuses(pod, criStatus, []v1.ContainerStatus{}, []v1.Container{}, false, false, false) + kl.convertToAPIContainerStatuses(tCtx, pod, criStatus, []v1.ContainerStatus{}, []v1.Container{}, nil, false, false, false) }() } } func TestConvertToAPIContainerStatusesForResources(t *testing.T) { + tCtx := ktesting.Init(t) if goruntime.GOOS == "windows" { t.Skip("InPlacePodVerticalScaling is not currently supported for Windows") } @@ -5868,8 +7031,7 @@ func TestConvertToAPIContainerStatusesForResources(t *testing.T) { state = tc.State } podStatus := testPodStatus(state, resources) - - cStatuses := kubelet.convertToAPIContainerStatuses(tPod, podStatus, []v1.ContainerStatus{tc.OldStatus}, tPod.Spec.Containers, false, false, false) + cStatuses := kubelet.convertToAPIContainerStatuses(tCtx, tPod, podStatus, []v1.ContainerStatus{tc.OldStatus}, tPod.Spec.Containers, nil, false, false, false) actual := cStatuses[0] // Explicitly test AllocatedResources and Resources separately for debuggability. assert.Equal(t, tc.Expected.AllocatedResources, actual.AllocatedResources, "AllocatedResources") @@ -5881,6 +7043,7 @@ func TestConvertToAPIContainerStatusesForResources(t *testing.T) { } func TestConvertToAPIContainerStatusesForUser(t *testing.T) { + tCtx := ktesting.Init(t) nowTime := time.Now() testContainerName := "ctr0" testContainerID := kubecontainer.ContainerID{Type: "test", ID: testContainerName} @@ -5995,12 +7158,15 @@ func TestConvertToAPIContainerStatusesForUser(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SupplementalGroupsPolicy, tc.featureEnabled) tPod := testPod.DeepCopy() t.Logf("TestCase: %q", tdesc) - cStatuses := kubelet.convertToAPIContainerStatuses(tPod, tc.testPodStatus, tPod.Status.ContainerStatuses, tPod.Spec.Containers, false, false, false) + + cStatuses := kubelet.convertToAPIContainerStatuses(tCtx, tPod, tc.testPodStatus, tPod.Status.ContainerStatuses, tPod.Spec.Containers, nil, false, false, false) + assert.Equal(t, tc.expectedContainerStatus, cStatuses) } } func TestKubelet_HandlePodCleanups(t *testing.T) { + tCtx := ktesting.Init(t) one := int64(1) two := int64(2) deleted := metav1.NewTime(time.Unix(2, 0).UTC()) @@ -6145,7 +7311,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { prepareWorker: func(t *testing.T, w *podWorkers, records map[types.UID][]syncPodRecord) { // send a create pod := simplePod() - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6170,7 +7336,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { }, }, } - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, StartTime: time.Unix(3, 0).UTC(), Pod: updatedPod, @@ -6546,7 +7712,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { }, }, } - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6569,7 +7735,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { }, }, } - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, StartTime: time.Unix(3, 0).UTC(), Pod: updatedPod, @@ -6618,7 +7784,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // block startup of the static pod due to full name collision w.startedStaticPodsByFullname[kubecontainer.GetPodFullName(pod)] = types.UID("2") - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6691,7 +7857,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // block startup of the static pod due to full name collision w.startedStaticPodsByFullname[kubecontainer.GetPodFullName(pod)] = types.UID("2") - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6754,7 +7920,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // block startup of the static pod due to full name collision w.startedStaticPodsByFullname[kubecontainer.GetPodFullName(pod)] = types.UID("2") - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6799,7 +7965,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // send a create of a static pod pod := staticPod() - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6807,14 +7973,14 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { drainAllWorkers(w) // terminate the pod (which won't complete) and then deliver a recreate by that same UID - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, StartTime: time.Unix(2, 0).UTC(), Pod: pod, }) pod = staticPod() pod.Annotations["version"] = "2" - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(3, 0).UTC(), Pod: pod, @@ -6908,7 +8074,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // send a create of a static pod pod := staticPod() - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -6951,7 +8117,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // send a create of a static pod pod := staticPod() - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, @@ -7038,19 +8204,19 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { prepareWorker: func(t *testing.T, w *podWorkers, records map[types.UID][]syncPodRecord) { // simulate a delete and recreate of the static pod pod := simplePod() - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, StartTime: time.Unix(1, 0).UTC(), Pod: pod, }) drainAllWorkers(w) - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, Pod: pod, }) pod2 := simplePod() pod2.Annotations = map[string]string{"version": "2"} - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, Pod: pod2, }) @@ -7139,7 +8305,8 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { defer testKubelet.Cleanup() kl := testKubelet.kubelet - podWorkers, _, processed := createPodWorkers() + logger, _ := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) kl.podWorkers = podWorkers originalPodSyncer := podWorkers.podSyncer syncFuncs := newPodSyncerFuncs(originalPodSyncer) @@ -7171,10 +8338,10 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { if !ok { t.Fatalf("unable to reject pod by UID %v", reject.uid) } - kl.rejectPod(pod, reject.reason, reject.message) + kl.rejectPod(tCtx, pod, reject.reason, reject.message) } - if err := kl.HandlePodCleanups(context.Background()); (err != nil) != tt.wantErr { + if err := kl.HandlePodCleanups(tCtx); (err != nil) != tt.wantErr { t.Errorf("Kubelet.HandlePodCleanups() error = %v, wantErr %v", err, tt.wantErr) } drainAllWorkers(podWorkers) @@ -7189,7 +8356,7 @@ func TestKubelet_HandlePodCleanups(t *testing.T) { // check after the terminating error clears if tt.wantWorkerAfterRetry != nil { podWorkers.podSyncer = originalPodSyncer - if err := kl.HandlePodCleanups(context.Background()); (err != nil) != tt.wantErr { + if err := kl.HandlePodCleanups(tCtx); (err != nil) != tt.wantErr { t.Errorf("Kubelet.HandlePodCleanups() second error = %v, wantErr %v", err, tt.wantErr) } drainAllWorkers(podWorkers) @@ -7212,6 +8379,7 @@ func testMetric(t *testing.T, metricName string, expectedMetric string) { } func TestGetNonExistentImagePullSecret(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) secrets := make([]*v1.Secret, 0) fakeRecorder := record.NewFakeRecorder(1) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) @@ -7234,7 +8402,7 @@ func TestGetNonExistentImagePullSecret(t *testing.T) { }, } - pullSecrets := testKubelet.kubelet.getPullSecretsForPod(testPod) + pullSecrets := testKubelet.kubelet.getPullSecretsForPod(logger, testPod) assert.Empty(t, pullSecrets) assert.Len(t, fakeRecorder.Events, 1) @@ -7465,6 +8633,7 @@ func (tvm *testVolumeMounter) GetPath() string { } func TestMakeEnvironmentVariablesWithFileKeyRef(t *testing.T) { + tCtx := ktesting.Init(t) // Create a temporary directory for test files tmpDir, err := os.MkdirTemp("", "filekeyref-test") require.NoError(t, err) @@ -7820,7 +8989,7 @@ func TestMakeEnvironmentVariablesWithFileKeyRef(t *testing.T) { }, } - envs, err := kl.makeEnvironmentVariables(pod, tc.container, "192.168.1.1", []string{"192.168.1.1"}, tc.podVolumes) + envs, err := kl.makeEnvironmentVariables(tCtx, pod, tc.container, "192.168.1.1", []string{"192.168.1.1"}, tc.podVolumes) if tc.expectedError { require.Error(t, err) @@ -7847,6 +9016,7 @@ func (e Envs) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e Envs) Less(i, j int) bool { return e[i].Name < e[j].Name } func TestGeneratePodHostNameAndDomain(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) kubelet := &Kubelet{} kubelet.dnsConfigurer = &dns.Configurer{ ClusterDomain: "cluster.local", @@ -7980,7 +9150,7 @@ func TestGeneratePodHostNameAndDomain(t *testing.T) { }, } - hostname, domain, err := kubelet.GeneratePodHostNameAndDomain(pod) + hostname, domain, err := kubelet.GeneratePodHostNameAndDomain(logger, pod) if tc.expectError { if err == nil { t.Errorf("expected an error but got none") diff --git a/pkg/kubelet/kubelet_pods_windows_test.go b/pkg/kubelet/kubelet_pods_windows_test.go index dd7377cb30477..8c964d21cdd61 100644 --- a/pkg/kubelet/kubelet_pods_windows_test.go +++ b/pkg/kubelet/kubelet_pods_windows_test.go @@ -27,9 +27,11 @@ import ( kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/volume/util/hostutil" "k8s.io/kubernetes/pkg/volume/util/subpath" + "k8s.io/kubernetes/test/utils/ktesting" ) func TestMakeMountsWindows(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) // TODO: remove skip once the failing test has been fixed. t.Skip("Skip failing test on Windows.") container := v1.Container{ @@ -92,7 +94,7 @@ func TestMakeMountsWindows(t *testing.T) { podDir, err := os.MkdirTemp("", "test-rotate-logs") require.NoError(t, err) defer os.RemoveAll(podDir) - mounts, _, err := makeMounts(&pod, podDir, &container, "fakepodname", "", []string{""}, podVolumes, fhu, fsp, nil, false, nil) + mounts, _, err := makeMounts(logger, &pod, podDir, &container, "fakepodname", "", []string{""}, podVolumes, fhu, fsp, nil, false, nil) require.NoError(t, err) expectedMounts := []kubecontainer.Mount{ diff --git a/pkg/kubelet/kubelet_resources.go b/pkg/kubelet/kubelet_resources.go index 0a7a1ad105255..462cbd996550c 100644 --- a/pkg/kubelet/kubelet_resources.go +++ b/pkg/kubelet/kubelet_resources.go @@ -17,6 +17,7 @@ limitations under the License. package kubelet import ( + "context" "fmt" "k8s.io/klog/v2" @@ -35,12 +36,12 @@ import ( // If a container has no limits specified, it defaults to the pod-level resources. // If neither container-level nor pod-level resources limits are specified, it defaults // to the node's allocatable resources. -func (kl *Kubelet) defaultPodLimitsForDownwardAPI(pod *corev1.Pod, container *corev1.Container) (*corev1.Pod, *corev1.Container, error) { +func (kl *Kubelet) defaultPodLimitsForDownwardAPI(ctx context.Context, pod *corev1.Pod, container *corev1.Container) (*corev1.Pod, *corev1.Container, error) { if pod == nil { return nil, nil, fmt.Errorf("invalid input, pod cannot be nil") } - node, err := kl.getNodeAnyWay() + node, err := kl.getNodeAnyWay(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to find node object, expected a node") } diff --git a/pkg/kubelet/kubelet_resources_test.go b/pkg/kubelet/kubelet_resources_test.go index a39bd4d0784ee..11c69f7939021 100644 --- a/pkg/kubelet/kubelet_resources_test.go +++ b/pkg/kubelet/kubelet_resources_test.go @@ -28,9 +28,11 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" kubefeatures "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/test/utils/ktesting" ) func TestPodResourceLimitsDefaulting(t *testing.T) { + tCtx := ktesting.Init(t) tk := newTestKubelet(t, true) defer tk.Cleanup() tk.kubelet.nodeLister = &testNodeLister{ @@ -98,7 +100,7 @@ func TestPodResourceLimitsDefaulting(t *testing.T) { as := assert.New(t) for idx, tc := range cases { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.PodLevelResources, tc.podLevelResourcesEnabled) - actual, _, err := tk.kubelet.defaultPodLimitsForDownwardAPI(tc.pod, nil) + actual, _, err := tk.kubelet.defaultPodLimitsForDownwardAPI(tCtx, tc.pod, nil) as.NoError(err, "failed to default pod limits: %v", err) if !apiequality.Semantic.DeepEqual(tc.expected, actual) { as.Fail("test case [%d] failed. Expected: %+v, Got: %+v", idx, tc.expected, actual) diff --git a/pkg/kubelet/kubelet_server_journal.go b/pkg/kubelet/kubelet_server_journal.go index cecc6529a9bc9..6e2ea27d21d7e 100644 --- a/pkg/kubelet/kubelet_server_journal.go +++ b/pkg/kubelet/kubelet_server_journal.go @@ -501,7 +501,7 @@ func heuristicsCopyFileLog(ctx context.Context, w io.Writer, logDir, logFileName func safeServiceName(s string) error { // Max length of a service name is 256 across supported OSes if len(s) > maxServiceLength { - return fmt.Errorf("length must be less than 100") + return fmt.Errorf("length must be less than %d", maxServiceLength) } if reServiceNameUnsafeCharacters.MatchString(s) { diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 3f69c2432f96a..45cec1d48a051 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -18,7 +18,7 @@ package kubelet import ( "context" - "crypto/tls" + "crypto/x509" "fmt" "net" "os" @@ -32,12 +32,11 @@ import ( "testing" "time" - "github.com/stretchr/testify/mock" - sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" oteltrace "go.opentelemetry.io/otel/trace" noopoteltrace "go.opentelemetry.io/otel/trace/noop" + "k8s.io/component-base/metrics/legacyregistry" cadvisorapi "github.com/google/cadvisor/info/v1" @@ -59,6 +58,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/cert" "k8s.io/client-go/util/flowcontrol" "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -149,7 +149,8 @@ type fakeImageGCManager struct { } func (f *fakeImageGCManager) GetImageList() ([]kubecontainer.Image, error) { - return f.fakeImageService.ListImages(context.Background()) + // ImageGCManager interface does not accept a context parameter. + return f.fakeImageService.ListImages(context.TODO()) } type TestKubelet struct { @@ -285,7 +286,7 @@ func newTestKubeletWithImageList( }, } kubelet.recorder = fakeRecorder - if err := kubelet.setupDataDirs(); err != nil { + if err := kubelet.setupDataDirs(logger); err != nil { t.Fatalf("can't initialize kubelet data dirs: %v", err) } kubelet.daemonEndpoints = &v1.NodeDaemonEndpoints{} @@ -302,15 +303,16 @@ func newTestKubeletWithImageList( kubelet.configMapManager = configMapManager kubelet.mirrorPodClient = fakeMirrorClient kubelet.podManager = kubepod.NewBasicPodManager() - podStartupLatencyTracker := kubeletutil.NewPodStartupLatencyTracker() - kubelet.statusManager = status.NewManager(fakeKubeClient, kubelet.podManager, &statustest.FakePodDeletionSafetyProvider{}, podStartupLatencyTracker) + kubelet.podStartupLatencyTracker = kubeletutil.NewPodStartupLatencyTracker() + kubelet.statusManager = status.NewManager(fakeKubeClient, kubelet.podManager, &statustest.FakePodDeletionSafetyProvider{}, kubelet.podStartupLatencyTracker) kubelet.nodeStartupLatencyTracker = kubeletutil.NewNodeStartupLatencyTracker() kubelet.podCertificateManager = &podcertificate.NoOpManager{} kubelet.containerRuntime = fakeRuntime kubelet.runtimeCache = containertest.NewFakeRuntimeCache(kubelet.containerRuntime) kubelet.reasonCache = NewReasonCache() - kubelet.podCache = containertest.NewFakeCache(kubelet.containerRuntime) + podCache := containertest.NewFakeCache(kubelet.containerRuntime) + kubelet.podCache = podCache kubelet.podWorkers = &fakePodWorkers{ syncPodFn: kubelet.SyncPod, cache: kubelet.podCache, @@ -332,10 +334,8 @@ func newTestKubeletWithImageList( } kubelet.allocationManager = allocation.NewInMemoryManager( - kubelet.containerManager.GetNodeConfig(), - kubelet.containerManager.GetNodeAllocatableAbsolute(), kubelet.statusManager, - func(pod *v1.Pod) { kubelet.HandlePodSyncs([]*v1.Pod{pod}) }, + func(pod *v1.Pod) { kubelet.HandlePodSyncs(tCtx, []*v1.Pod{pod}) }, kubelet.GetActivePods, kubelet.podManager.GetPodByUID, config.NewSourcesReady(func(_ sets.Set[string]) bool { return enableResizing }), @@ -382,7 +382,7 @@ func newTestKubeletWithImageList( kubelet.resyncInterval = 10 * time.Second kubelet.workQueue = queue.NewBasicWorkQueue(fakeClock) // Relist period does not affect the tests. - kubelet.pleg = pleg.NewGenericPLEG(logger, fakeRuntime, make(chan *pleg.PodLifecycleEvent, 100), &pleg.RelistDuration{RelistPeriod: time.Hour, RelistThreshold: genericPlegRelistThreshold}, kubelet.podCache, clock.RealClock{}) + kubelet.pleg = pleg.NewGenericPLEG(logger, fakeRuntime, make(chan *pleg.PodLifecycleEvent, 100), &pleg.RelistDuration{RelistPeriod: time.Hour, RelistThreshold: genericPlegRelistThreshold}, podCache, clock.RealClock{}) kubelet.clock = fakeClock nodeRef := &v1.ObjectReference{ @@ -393,11 +393,12 @@ func newTestKubeletWithImageList( } // setup eviction manager evictionManager, evictionAdmitHandler := eviction.NewManager(kubelet.resourceAnalyzer, eviction.Config{}, - killPodNow(kubelet.podWorkers, fakeRecorder), kubelet.imageManager, kubelet.containerGC, fakeRecorder, nodeRef, kubelet.clock, kubelet.supportLocalStorageCapacityIsolation()) + killPodNow(tCtx, kubelet.podWorkers, fakeRecorder), kubelet.imageManager, kubelet.containerGC, fakeRecorder, nodeRef, kubelet.clock, kubelet.supportLocalStorageCapacityIsolation()) kubelet.evictionManager = evictionManager handlers := []lifecycle.PodAdmitHandler{} handlers = append(handlers, evictionAdmitHandler) + handlers = append(handlers, allocation.NewPodResizesAdmitHandler(kubelet.containerManager, fakeRuntime, kubelet.allocationManager, logger)) // setup shutdown manager shutdownManager := nodeshutdown.NewManager(&nodeshutdown.Config{ @@ -405,8 +406,8 @@ func newTestKubeletWithImageList( Recorder: fakeRecorder, NodeRef: nodeRef, GetPodsFunc: kubelet.podManager.GetPods, - KillPodFunc: killPodNow(kubelet.podWorkers, fakeRecorder), - SyncNodeStatusFunc: func() {}, + KillPodFunc: killPodNow(tCtx, kubelet.podWorkers, fakeRecorder), + SyncNodeStatusFunc: func(context.Context) {}, ShutdownGracePeriodRequested: 0, ShutdownGracePeriodCriticalPods: 0, }) @@ -483,7 +484,7 @@ func newTestPods(count int) []*v1.Pod { } func TestSyncLoopAbort(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -496,14 +497,15 @@ func TestSyncLoopAbort(t *testing.T) { close(ch) // sanity check (also prevent this test from hanging in the next step) - ok := kubelet.syncLoopIteration(ctx, ch, kubelet, make(chan time.Time), make(chan time.Time), make(chan *pleg.PodLifecycleEvent, 1)) + ok := kubelet.syncLoopIteration(tCtx, ch, kubelet, make(chan time.Time), make(chan time.Time), make(chan *pleg.PodLifecycleEvent, 1)) require.False(t, ok, "Expected syncLoopIteration to return !ok since update chan was closed") // this should terminate immediately; if it hangs then the syncLoopIteration isn't aborting properly - kubelet.syncLoop(ctx, ch, kubelet) + kubelet.syncLoop(tCtx, ch, kubelet) } func TestSyncPodsStartPod(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -516,12 +518,12 @@ func TestSyncPodsStartPod(t *testing.T) { }), } kubelet.podManager.SetPods(pods) - kubelet.HandlePodSyncs(pods) + kubelet.HandlePodSyncs(tCtx, pods) fakeRuntime.AssertStartedPods([]string{string(pods[0].UID)}) } func TestHandlePodCleanupsPerQOS(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -549,7 +551,7 @@ func TestHandlePodCleanupsPerQOS(t *testing.T) { // within a goroutine so a two second delay should be enough time to // mark the pod as killed (within this test case). - kubelet.HandlePodCleanups(ctx) + require.NoError(t, kubelet.HandlePodCleanups(tCtx)) // assert that unwanted pods were killed if actual, expected := kubelet.podWorkers.(*fakePodWorkers).triggeredDeletion, []types.UID{"12345678"}; !reflect.DeepEqual(actual, expected) { @@ -560,9 +562,9 @@ func TestHandlePodCleanupsPerQOS(t *testing.T) { // simulate Runtime.KillPod fakeRuntime.PodList = nil - kubelet.HandlePodCleanups(ctx) - kubelet.HandlePodCleanups(ctx) - kubelet.HandlePodCleanups(ctx) + require.NoError(t, kubelet.HandlePodCleanups(tCtx)) + require.NoError(t, kubelet.HandlePodCleanups(tCtx)) + require.NoError(t, kubelet.HandlePodCleanups(tCtx)) destroyCount := 0 err := wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) { @@ -586,14 +588,15 @@ func TestHandlePodCleanupsPerQOS(t *testing.T) { } func TestDispatchWorkOfCompletedPod(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet var got bool kubelet.podWorkers = &fakePodWorkers{ - syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { + syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { got = true - return false, nil + return false, nil, nil }, cache: kubelet.podCache, t: t, @@ -656,7 +659,7 @@ func TestDispatchWorkOfCompletedPod(t *testing.T) { }, } for _, pod := range pods { - kubelet.podWorkers.UpdatePod(UpdatePodOptions{ + kubelet.podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodSync, StartTime: time.Now(), @@ -669,14 +672,15 @@ func TestDispatchWorkOfCompletedPod(t *testing.T) { } func TestDispatchWorkOfActivePod(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet var got bool kubelet.podWorkers = &fakePodWorkers{ - syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { + syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { got = true - return false, nil + return false, nil, nil }, cache: kubelet.podCache, t: t, @@ -714,7 +718,7 @@ func TestDispatchWorkOfActivePod(t *testing.T) { } for _, pod := range pods { - kubelet.podWorkers.UpdatePod(UpdatePodOptions{ + kubelet.podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodSync, StartTime: time.Now(), @@ -727,7 +731,7 @@ func TestDispatchWorkOfActivePod(t *testing.T) { } func TestHandlePodCleanups(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -746,7 +750,7 @@ func TestHandlePodCleanups(t *testing.T) { } kubelet := testKubelet.kubelet - kubelet.HandlePodCleanups(ctx) + require.NoError(t, kubelet.HandlePodCleanups(tCtx)) // assert that unwanted pods were queued to kill if actual, expected := kubelet.podWorkers.(*fakePodWorkers).triggeredDeletion, []types.UID{"12345678"}; !reflect.DeepEqual(actual, expected) { @@ -756,6 +760,7 @@ func TestHandlePodCleanups(t *testing.T) { } func TestVolumeAttachLimitExceededCleanup(t *testing.T) { + tCtx := ktesting.Init(t) const podCount = 500 tk := newTestKubelet(t, true /* controller-attach-detach enabled */) defer tk.Cleanup() @@ -784,13 +789,11 @@ func TestVolumeAttachLimitExceededCleanup(t *testing.T) { pods, _ := newTestPodsWithResources(podCount) kl.podManager.SetPods(pods) - kl.HandlePodSyncs(pods) - - ctx := context.Background() + kl.HandlePodSyncs(tCtx, pods) // all pods must reach a terminal, Failed state due to VolumeAttachmentLimitExceeded. if err := wait.PollUntilContextTimeout( - ctx, 200*time.Millisecond, 30*time.Second, true, + tCtx, 200*time.Millisecond, 30*time.Second, true, func(ctx context.Context) (bool, error) { for _, p := range pods { st, ok := kl.statusManager.GetPodStatus(p.UID) @@ -805,7 +808,7 @@ func TestVolumeAttachLimitExceededCleanup(t *testing.T) { // validate that SyncTerminatedPod completed successfully for each pod. if err := wait.PollUntilContextTimeout( - ctx, 200*time.Millisecond, 30*time.Second, true, + tCtx, 200*time.Millisecond, 30*time.Second, true, func(ctx context.Context) (bool, error) { for _, p := range pods { if !kl.podWorkers.ShouldPodBeFinished(p.UID) { @@ -858,6 +861,7 @@ func TestHandlePodRemovesWhenSourcesAreReady(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } + tCtx := ktesting.Init(t) ready := false @@ -884,7 +888,7 @@ func TestHandlePodRemovesWhenSourcesAreReady(t *testing.T) { kubelet := testKubelet.kubelet kubelet.sourcesReady = config.NewSourcesReady(func(_ sets.Set[string]) bool { return ready }) - kubelet.HandlePodRemoves(pods) + kubelet.HandlePodRemoves(tCtx, pods) time.Sleep(2 * time.Second) // Sources are not ready yet. Don't remove any pods. @@ -893,7 +897,7 @@ func TestHandlePodRemovesWhenSourcesAreReady(t *testing.T) { } ready = true - kubelet.HandlePodRemoves(pods) + kubelet.HandlePodRemoves(tCtx, pods) time.Sleep(2 * time.Second) // Sources are ready. Remove unwanted pods. @@ -928,6 +932,7 @@ func checkPodStatus(t *testing.T, kl *Kubelet, pod *v1.Pod, phase v1.PodPhase) { // Tests that we handle port conflicts correctly by setting the failed status in status map. func TestHandlePortConflicts(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -969,7 +974,7 @@ func TestHandlePortConflicts(t *testing.T) { pods[1].UID: true, } - kl.HandlePodAdditions(pods) + kl.HandlePodAdditions(tCtx, pods) // Check pod status stored in the status map. checkPodStatus(t, kl, notfittingPod, v1.PodFailed) @@ -978,6 +983,7 @@ func TestHandlePortConflicts(t *testing.T) { // Tests that we handle host name conflicts correctly by setting the failed status in status map. func TestHandleHostNameConflicts(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -1012,7 +1018,7 @@ func TestHandleHostNameConflicts(t *testing.T) { notfittingPod := pods[0] fittingPod := pods[1] - kl.HandlePodAdditions(pods) + kl.HandlePodAdditions(tCtx, pods) // Check pod status stored in the status map. checkPodStatus(t, kl, notfittingPod, v1.PodFailed) @@ -1021,6 +1027,7 @@ func TestHandleHostNameConflicts(t *testing.T) { // Tests that we handle not matching labels selector correctly by setting the failed status in status map. func TestHandleNodeSelector(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -1054,7 +1061,7 @@ func TestHandleNodeSelector(t *testing.T) { notfittingPod := pods[0] fittingPod := pods[1] - kl.HandlePodAdditions(pods) + kl.HandlePodAdditions(tCtx, pods) // Check pod status stored in the status map. checkPodStatus(t, kl, notfittingPod, v1.PodFailed) @@ -1091,6 +1098,7 @@ func TestHandleNodeSelectorBasedOnOS(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -1118,7 +1126,7 @@ func TestHandleNodeSelectorBasedOnOS(t *testing.T) { pod := podWithUIDNameNsSpec("123456789", "podA", "foo", v1.PodSpec{NodeSelector: test.podSelector}) - kl.HandlePodAdditions([]*v1.Pod{pod}) + kl.HandlePodAdditions(tCtx, []*v1.Pod{pod}) // Check pod status stored in the status map. checkPodStatus(t, kl, pod, test.podStatus) @@ -1128,6 +1136,7 @@ func TestHandleNodeSelectorBasedOnOS(t *testing.T) { // Tests that we handle exceeded resources correctly by setting the failed status in status map. func TestHandleMemExceeded(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -1173,7 +1182,7 @@ func TestHandleMemExceeded(t *testing.T) { pods[1].UID: true, } - kl.HandlePodAdditions(pods) + kl.HandlePodAdditions(tCtx, pods) // Check pod status stored in the status map. checkPodStatus(t, kl, notfittingPod, v1.PodFailed) @@ -1183,6 +1192,7 @@ func TestHandleMemExceeded(t *testing.T) { // Tests that we handle result of interface UpdatePluginResources correctly // by setting corresponding status in status map. func TestHandlePluginResources(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubeletExcludeAdmitHandlers(t, false /* controllerAttachDetachEnabled */, false /*enableResizing*/) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -1304,7 +1314,7 @@ func TestHandlePluginResources(t *testing.T) { missingPod := podWithUIDNameNsSpec("3", "missingpod", "foo", missingPodSpec) failedPod := podWithUIDNameNsSpec("4", "failedpod", "foo", failedPodSpec) - kl.HandlePodAdditions([]*v1.Pod{fittingPod, emptyPod, missingPod, failedPod}) + kl.HandlePodAdditions(tCtx, []*v1.Pod{fittingPod, emptyPod, missingPod, failedPod}) // Check pod status stored in the status map. checkPodStatus(t, kl, fittingPod, v1.PodPending) @@ -1315,7 +1325,7 @@ func TestHandlePluginResources(t *testing.T) { // TODO(filipg): This test should be removed once StatusSyncer can do garbage collection without external signal. func TestPurgingObsoleteStatusMapEntries(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -1326,13 +1336,13 @@ func TestPurgingObsoleteStatusMapEntries(t *testing.T) { } podToTest := pods[1] // Run once to populate the status map. - kl.HandlePodAdditions(pods) + kl.HandlePodAdditions(tCtx, pods) if _, found := kl.statusManager.GetPodStatus(podToTest.UID); !found { t.Fatalf("expected to have status cached for pod2") } // Sync with empty pods so that the entry in status map will be removed. kl.podManager.SetPods([]*v1.Pod{}) - kl.HandlePodCleanups(ctx) + require.NoError(t, kl.HandlePodCleanups(tCtx)) if _, found := kl.statusManager.GetPodStatus(podToTest.UID); found { t.Fatalf("expected to not have status cached for pod2") } @@ -1502,8 +1512,8 @@ func TestCreateMirrorPod(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -1513,7 +1523,7 @@ func TestCreateMirrorPod(t *testing.T) { pod.Annotations[kubetypes.ConfigSourceAnnotationKey] = "file" pods := []*v1.Pod{pod} kl.podManager.SetPods(pods) - isTerminal, err := kl.SyncPod(context.Background(), tt.updateType, pod, nil, &kubecontainer.PodStatus{}) + isTerminal, _, err := kl.SyncPod(tCtx, tt.updateType, pod, nil, &kubecontainer.PodStatus{}) assert.NoError(t, err) if isTerminal { t.Fatalf("pod should not be terminal: %#v", pod) @@ -1526,6 +1536,7 @@ func TestCreateMirrorPod(t *testing.T) { } func TestDeleteOutdatedMirrorPod(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -1549,7 +1560,7 @@ func TestDeleteOutdatedMirrorPod(t *testing.T) { pods := []*v1.Pod{pod, mirrorPod} kl.podManager.SetPods(pods) - isTerminal, err := kl.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, mirrorPod, &kubecontainer.PodStatus{}) + isTerminal, _, err := kl.SyncPod(tCtx, kubetypes.SyncPodUpdate, pod, mirrorPod, &kubecontainer.PodStatus{}) assert.NoError(t, err) if isTerminal { t.Fatalf("pod should not be terminal: %#v", pod) @@ -1562,7 +1573,7 @@ func TestDeleteOutdatedMirrorPod(t *testing.T) { } func TestDeleteOrphanedMirrorPods(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() @@ -1612,7 +1623,7 @@ func TestDeleteOrphanedMirrorPods(t *testing.T) { } // Sync with an empty pod list to delete all mirror pods. - kl.HandlePodCleanups(ctx) + require.NoError(t, kl.HandlePodCleanups(tCtx)) assert.Empty(t, manager.GetPods(), "Expected no mirror pods") for i, pod := range orphanPods { name := kubecontainer.GetPodFullName(pod) @@ -1631,6 +1642,7 @@ func TestDeleteOrphanedMirrorPods(t *testing.T) { } func TestNetworkErrorsWithoutHostNetwork(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -1646,7 +1658,7 @@ func TestNetworkErrorsWithoutHostNetwork(t *testing.T) { }) kubelet.podManager.SetPods([]*v1.Pod{pod}) - isTerminal, err := kubelet.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) + isTerminal, _, err := kubelet.SyncPod(tCtx, kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) assert.Error(t, err, "expected pod with hostNetwork=false to fail when network in error") if isTerminal { t.Fatalf("pod should not be terminal: %#v", pod) @@ -1654,7 +1666,7 @@ func TestNetworkErrorsWithoutHostNetwork(t *testing.T) { pod.Annotations[kubetypes.ConfigSourceAnnotationKey] = kubetypes.FileSource pod.Spec.HostNetwork = true - isTerminal, err = kubelet.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) + isTerminal, _, err = kubelet.SyncPod(tCtx, kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) assert.NoError(t, err, "expected pod with hostNetwork=true to succeed when network in error") if isTerminal { t.Fatalf("pod should not be terminal: %#v", pod) @@ -1787,13 +1799,13 @@ func TestCheckpointContainer(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) options := &runtimeapi.CheckpointContainerRequest{} if test.checkpointLocation != "" { options.Location = test.checkpointLocation } status := kubelet.CheckpointContainer( - ctx, + tCtx, fakePod.Pod.ID, fmt.Sprintf( "%s_%s", @@ -1827,6 +1839,7 @@ func TestCheckpointContainer(t *testing.T) { } func TestSyncPodsSetStatusToFailedForPodsThatRunTooLong(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() fakeRuntime := testKubelet.fakeRuntime @@ -1867,7 +1880,7 @@ func TestSyncPodsSetStatusToFailedForPodsThatRunTooLong(t *testing.T) { } // Let the pod worker sets the status to fail after this sync. - kubelet.HandlePodUpdates(pods) + kubelet.HandlePodUpdates(tCtx, pods) status, found := kubelet.statusManager.GetPodStatus(pods[0].UID) assert.True(t, found, "expected to found status for pod %q", pods[0].UID) assert.Equal(t, v1.PodFailed, status.Phase) @@ -1876,6 +1889,7 @@ func TestSyncPodsSetStatusToFailedForPodsThatRunTooLong(t *testing.T) { } func TestSyncPodsDoesNotSetPodsThatDidNotRunTooLongToFailed(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() fakeRuntime := testKubelet.fakeRuntime @@ -1917,7 +1931,7 @@ func TestSyncPodsDoesNotSetPodsThatDidNotRunTooLongToFailed(t *testing.T) { } kubelet.podManager.SetPods(pods) - kubelet.HandlePodUpdates(pods) + kubelet.HandlePodUpdates(tCtx, pods) status, found := kubelet.statusManager.GetPodStatus(pods[0].UID) assert.True(t, found, "expected to found status for pod %q", pods[0].UID) assert.NotEqual(t, v1.PodFailed, status.Phase) @@ -1941,7 +1955,7 @@ func podWithUIDNameNsSpec(uid types.UID, name, namespace string, spec v1.PodSpec } func TestDeletePodDirsForDeletedPods(t *testing.T) { - ctx := context.Background() + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -1952,26 +1966,26 @@ func TestDeletePodDirsForDeletedPods(t *testing.T) { kl.podManager.SetPods(pods) // Sync to create pod directories. - kl.HandlePodSyncs(kl.podManager.GetPods()) + kl.HandlePodSyncs(tCtx, kl.podManager.GetPods()) for i := range pods { assert.True(t, dirExists(kl.getPodDir(pods[i].UID)), "Expected directory to exist for pod %d", i) } // Pod 1 has been deleted and no longer exists. kl.podManager.SetPods([]*v1.Pod{pods[0]}) - kl.HandlePodCleanups(ctx) + require.NoError(t, kl.HandlePodCleanups(tCtx)) assert.True(t, dirExists(kl.getPodDir(pods[0].UID)), "Expected directory to exist for pod 0") assert.False(t, dirExists(kl.getPodDir(pods[1].UID)), "Expected directory to be deleted for pod 1") } func syncAndVerifyPodDir(t *testing.T, testKubelet *TestKubelet, pods []*v1.Pod, podsToCheck []*v1.Pod, shouldExist bool) { - ctx := context.Background() + tCtx := ktesting.Init(t) t.Helper() kl := testKubelet.kubelet kl.podManager.SetPods(pods) - kl.HandlePodSyncs(pods) - kl.HandlePodCleanups(ctx) + kl.HandlePodSyncs(tCtx, pods) + require.NoError(t, kl.HandlePodCleanups(tCtx)) for i, pod := range podsToCheck { exist := dirExists(kl.getPodDir(pod.UID)) assert.Equal(t, shouldExist, exist, "directory of pod %d", i) @@ -2058,6 +2072,7 @@ func TestGetPodsToSync(t *testing.T) { } func TestGenerateAPIPodStatusWithSortedContainers(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -2093,7 +2108,7 @@ func TestGenerateAPIPodStatusWithSortedContainers(t *testing.T) { ContainerStatuses: cStatuses, } for i := 0; i < 5; i++ { - apiStatus := kubelet.generateAPIPodStatus(pod, status, false) + apiStatus := kubelet.generateAPIPodStatus(tCtx, pod, status, false) for i, c := range apiStatus.ContainerStatuses { if expectedOrder[i] != c.Name { t.Fatalf("Container status not sorted, expected %v at index %d, but found %v", expectedOrder[i], i, c.Name) @@ -2111,6 +2126,7 @@ func verifyContainerStatuses(t *testing.T, statuses []v1.ContainerStatus, expect // Test generateAPIPodStatus with different reason cache and old api pod status. func TestGenerateAPIPodStatusWithReasonCache(t *testing.T) { + tCtx := ktesting.Init(t) // The following waiting reason and message are generated in convertStatusToAPIStatus() testTimestamp := time.Unix(123456789, 987654321) testErrorReason := fmt.Errorf("test-error") @@ -2307,7 +2323,7 @@ func TestGenerateAPIPodStatusWithReasonCache(t *testing.T) { pod.Spec.Containers = test.containers pod.Status.ContainerStatuses = test.oldStatuses podStatus.ContainerStatuses = test.statuses - apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) + apiStatus := kubelet.generateAPIPodStatus(tCtx, pod, podStatus, false) verifyContainerStatuses(t, apiStatus.ContainerStatuses, test.expectedState, test.expectedLastTerminationState, fmt.Sprintf("case %d", i)) } @@ -2320,7 +2336,7 @@ func TestGenerateAPIPodStatusWithReasonCache(t *testing.T) { pod.Spec.InitContainers = test.containers pod.Status.InitContainerStatuses = test.oldStatuses podStatus.ContainerStatuses = test.statuses - apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) + apiStatus := kubelet.generateAPIPodStatus(tCtx, pod, podStatus, false) expectedState := test.expectedState if test.expectedInitState != nil { expectedState = test.expectedInitState @@ -2331,6 +2347,7 @@ func TestGenerateAPIPodStatusWithReasonCache(t *testing.T) { // Test generateAPIPodStatus with different restart policies. func TestGenerateAPIPodStatusWithDifferentRestartPolicies(t *testing.T) { + tCtx := ktesting.Init(t) testErrorReason := fmt.Errorf("test-error") emptyContainerID := (&kubecontainer.ContainerID{}).String() testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) @@ -2459,14 +2476,14 @@ func TestGenerateAPIPodStatusWithDifferentRestartPolicies(t *testing.T) { pod.Spec.RestartPolicy = test.restartPolicy // Test normal containers pod.Spec.Containers = containers - apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) + apiStatus := kubelet.generateAPIPodStatus(tCtx, pod, podStatus, false) expectedState, expectedLastTerminationState := test.expectedState, test.expectedLastTerminationState verifyContainerStatuses(t, apiStatus.ContainerStatuses, expectedState, expectedLastTerminationState, fmt.Sprintf("case %d", c)) pod.Spec.Containers = nil // Test init containers pod.Spec.InitContainers = containers - apiStatus = kubelet.generateAPIPodStatus(pod, podStatus, false) + apiStatus = kubelet.generateAPIPodStatus(tCtx, pod, podStatus, false) if test.expectedInitState != nil { expectedState = test.expectedInitState } @@ -2480,6 +2497,7 @@ func TestGenerateAPIPodStatusWithDifferentRestartPolicies(t *testing.T) { // Test generateAPIPodStatus with different container-level restart policies. func TestGenerateAPIPodStatusWithContainerRestartPolicies(t *testing.T) { + tCtx := ktesting.Init(t) var ( containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways containerRestartPolicyOnFailure = v1.ContainerRestartPolicyOnFailure @@ -2675,7 +2693,7 @@ func TestGenerateAPIPodStatusWithContainerRestartPolicies(t *testing.T) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways // Test normal containers pod.Spec.Containers = test.containers - apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) + apiStatus := kubelet.generateAPIPodStatus(tCtx, pod, podStatus, false) expectedState, expectedLastTerminationState := test.expectedState, test.expectedLastTerminationState verifyContainerStatuses(t, apiStatus.ContainerStatuses, expectedState, expectedLastTerminationState, test.desc) pod.Spec.Containers = nil @@ -2700,6 +2718,7 @@ func (a *testPodAdmitHandler) Admit(attrs *lifecycle.PodAdmitAttributes) lifecyc // Test verifies that the kubelet invokes an admission handler during HandlePodAdditions. func TestHandlePodAdditionsInvokesPodAdmitHandlers(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -2736,7 +2755,7 @@ func TestHandlePodAdditionsInvokesPodAdmitHandlers(t *testing.T) { kl.allocationManager.AddPodAdmitHandlers(lifecycle.PodAdmitHandlers{&testPodAdmitHandler{podsToReject: podsToReject}}) - kl.HandlePodAdditions(pods) + kl.HandlePodAdditions(tCtx, pods) // Check pod status stored in the status map. checkPodStatus(t, kl, podToReject, v1.PodFailed) @@ -2752,8 +2771,8 @@ func TestPodResourceAllocationReset(t *testing.T) { // fakePodWorkers triggers syncPodFn synchronously on update. We overwrite it here to // avoid calling kubelet.SyncPod, which performs resize resource allocation. kubelet.podWorkers.(*fakePodWorkers).syncPodFn = - func(_ context.Context, _ kubetypes.SyncPodType, _, _ *v1.Pod, _ *kubecontainer.PodStatus) (bool, error) { - return false, nil + func(_ context.Context, _ kubetypes.SyncPodType, _, _ *v1.Pod, _ *kubecontainer.PodStatus) (bool, func(), error) { + return false, nil, nil } nodes := []*v1.Node{ @@ -2939,6 +2958,7 @@ func TestPodResourceAllocationReset(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + tCtx := ktesting.Init(t) if tc.existingPodAllocation != nil { // when kubelet restarts, AllocatedResources has already existed before adding pod err := kubelet.allocationManager.SetAllocatedResources(tc.existingPodAllocation) @@ -2946,7 +2966,7 @@ func TestPodResourceAllocationReset(t *testing.T) { t.Fatalf("failed to set pod allocation: %v", err) } } - kubelet.HandlePodAdditions([]*v1.Pod{tc.pod}) + kubelet.HandlePodAdditions(tCtx, []*v1.Pod{tc.pod}) allocatedResources, found := kubelet.allocationManager.GetContainerResourceAllocation(tc.pod.UID, tc.pod.Spec.Containers[0].Name) if !found { @@ -3011,6 +3031,7 @@ func (a *testPodSyncHandler) ShouldEvict(pod *v1.Pod) lifecycle.ShouldEvictRespo // TestGenerateAPIPodStatusInvokesPodSyncHandlers invokes the handlers and reports the proper status func TestGenerateAPIPodStatusInvokesPodSyncHandlers(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -3022,13 +3043,14 @@ func TestGenerateAPIPodStatusInvokesPodSyncHandlers(t *testing.T) { Name: pod.Name, Namespace: pod.Namespace, } - apiStatus := kubelet.generateAPIPodStatus(pod, status, false) + apiStatus := kubelet.generateAPIPodStatus(tCtx, pod, status, false) require.Equal(t, v1.PodFailed, apiStatus.Phase) require.Equal(t, "Evicted", apiStatus.Reason) require.Equal(t, "because", apiStatus.Message) } func TestSyncTerminatingPodKillPod(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -3043,7 +3065,7 @@ func TestSyncTerminatingPodKillPod(t *testing.T) { kl.podManager.SetPods(pods) podStatus := &kubecontainer.PodStatus{ID: pod.UID} gracePeriodOverride := int64(0) - err := kl.SyncTerminatingPod(context.Background(), pod, podStatus, &gracePeriodOverride, func(podStatus *v1.PodStatus) { + err := kl.SyncTerminatingPod(tCtx, pod, podStatus, &gracePeriodOverride, func(podStatus *v1.PodStatus) { podStatus.Phase = v1.PodFailed podStatus.Reason = "reason" podStatus.Message = "message" @@ -3084,6 +3106,7 @@ func TestSyncLabels(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false) defer testKubelet.Cleanup() kl := testKubelet.kubelet @@ -3092,7 +3115,7 @@ func TestSyncLabels(t *testing.T) { test.existingNode.Name = string(kl.nodeName) kl.nodeLister = testNodeLister{nodes: []*v1.Node{test.existingNode}} - go func() { kl.syncNodeStatus() }() + go func() { kl.syncNodeStatus(tCtx) }() err := retryWithExponentialBackOff( 100*time.Millisecond, @@ -3251,9 +3274,8 @@ func createAndStartFakeRemoteRuntime(t *testing.T) (*fakeremote.RemoteRuntime, s return fakeRuntime, endpoint } -func createRemoteRuntimeService(endpoint string, t *testing.T, tp oteltrace.TracerProvider) internalapi.RuntimeService { - logger := klog.Background() - runtimeService, err := remote.NewRemoteRuntimeService(endpoint, 15*time.Second, tp, &logger) +func createRemoteRuntimeService(ctx context.Context, endpoint string, t *testing.T, tp oteltrace.TracerProvider) internalapi.RuntimeService { + runtimeService, err := remote.NewRemoteRuntimeService(ctx, endpoint, 15*time.Second, tp, false) require.NoError(t, err) return runtimeService } @@ -3262,8 +3284,32 @@ func TestNewMainKubeletStandAlone(t *testing.T) { tCtx := ktesting.Init(t) tempDir, err := os.MkdirTemp("", "logs") ContainerLogsDir = tempDir - assert.NoError(t, err) - defer os.RemoveAll(ContainerLogsDir) + require.NoError(t, err) + defer func() { + err := os.RemoveAll(ContainerLogsDir) + require.NoError(t, err) + }() + + ca, cert, key, err := generateCAAndCertKeyWithOptions( + "localhost", + []net.IP{{127, 0, 0, 1}}, + []string{"localhost"}, + ) + require.NoError(t, err) + + caPath := filepath.Join(tempDir, "kubelet-client-ca.cert") + certPath := filepath.Join(tempDir, "kubelet.cert") + keyPath := filepath.Join(tempDir, "kubelet.key") + + err = os.WriteFile(caPath, ca, os.FileMode(0644)) + require.NoError(t, err) + + err = os.WriteFile(certPath, cert, os.FileMode(0644)) + require.NoError(t, err) + + err = os.WriteFile(keyPath, key, os.FileMode(0600)) + require.NoError(t, err) + kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ SyncFrequency: metav1.Duration{Duration: time.Minute}, ConfigMapAndSecretChangeDetectionStrategy: kubeletconfiginternal.WatchChangeDetectionStrategy, @@ -3285,16 +3331,17 @@ func TestNewMainKubeletStandAlone(t *testing.T) { Available: 600, }, nil).Maybe() tlsOptions := &server.TLSOptions{ - Config: &tls.Config{ - MinVersion: 0, - }, + MinVersion: 0, + CertFile: certPath, + KeyFile: keyPath, + ClientCAFile: caPath, } fakeRuntime, endpoint := createAndStartFakeRemoteRuntime(t) defer func() { fakeRuntime.Stop() }() fakeRecorder := &record.FakeRecorder{} - rtSvc := createRemoteRuntimeService(endpoint, t, noopoteltrace.NewTracerProvider()) + rtSvc := createRemoteRuntimeService(tCtx, endpoint, t, noopoteltrace.NewTracerProvider()) kubeDep := &Dependencies{ Auth: nil, CAdvisorInterface: cadvisor, @@ -3316,6 +3363,10 @@ func TestNewMainKubeletStandAlone(t *testing.T) { } crOptions := &kubeletconfig.ContainerRuntimeOptions{} + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReloadKubeletServerCertificateFile, false) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RotateKubeletServerCertificate, false) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReloadKubeletClientCAFile, false) + testMainKubelet, err := NewMainKubelet( tCtx, kubeCfg, @@ -3344,11 +3395,12 @@ func TestNewMainKubeletStandAlone(t *testing.T) { 1024, false, ) - assert.NoError(t, err, "NewMainKubelet should succeed") - assert.NotNil(t, testMainKubelet, "testMainKubelet should not be nil") + require.NoError(t, err, "NewMainKubelet should succeed") + require.NotNil(t, testMainKubelet, "testMainKubelet should not be nil") testMainKubelet.BirthCry() - testMainKubelet.StartGarbageCollection() + tCtx = ktesting.Init(t) + testMainKubelet.StartGarbageCollection(tCtx) // Nil pointer panic can be reproduced if configmap manager is not nil. // See https://github.com/kubernetes/kubernetes/issues/113492 // pod := &v1.Pod{ @@ -3372,8 +3424,141 @@ func TestNewMainKubeletStandAlone(t *testing.T) { // } // testMainKubelet.configMapManager.RegisterPod(pod) // testMainKubelet.secretManager.RegisterPod(pod) - assert.Nil(t, testMainKubelet.configMapManager, "configmap manager should be nil if kubelet is in standalone mode") - assert.Nil(t, testMainKubelet.secretManager, "secret manager should be nil if kubelet is in standalone mode") + require.Nil(t, testMainKubelet.configMapManager, "configmap manager should be nil if kubelet is in standalone mode") + require.Nil(t, testMainKubelet.secretManager, "secret manager should be nil if kubelet is in standalone mode") + require.Nil(t, testMainKubelet.serverCertificateManager, "serverCertificateManager should be nil") + require.NotNil(t, kubeDep.TLSConfig.GetCertificate, "GetCertificate should not be nil") + require.Nil(t, kubeDep.TLSConfig.GetConfigForClient, "GetConfigForClient should be nil") + require.NotNil(t, kubeDep.TLSConfig.ClientCAs, "ClientCAs should not be nil") +} + +func TestNewMainKubeletWithCertAndCAReloadingEnabled(t *testing.T) { + tCtx := ktesting.Init(t) + tempDir, err := os.MkdirTemp("", "logs") + ContainerLogsDir = tempDir + require.NoError(t, err) + defer func() { + err := os.RemoveAll(ContainerLogsDir) + require.NoError(t, err) + }() + + ca, cert, key, err := generateCAAndCertKeyWithOptions( + "localhost", + []net.IP{{127, 0, 0, 1}}, + []string{"localhost"}, + ) + require.NoError(t, err) + + caPath := filepath.Join(tempDir, "kubelet-client-ca.cert") + certPath := filepath.Join(tempDir, "kubelet.cert") + keyPath := filepath.Join(tempDir, "kubelet.key") + + err = os.WriteFile(caPath, ca, os.FileMode(0644)) + require.NoError(t, err) + + err = os.WriteFile(certPath, cert, os.FileMode(0644)) + require.NoError(t, err) + + err = os.WriteFile(keyPath, key, os.FileMode(0600)) + require.NoError(t, err) + + kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ + SyncFrequency: metav1.Duration{Duration: time.Minute}, + ConfigMapAndSecretChangeDetectionStrategy: kubeletconfiginternal.WatchChangeDetectionStrategy, + ContainerLogMaxSize: "10Mi", + ContainerLogMaxFiles: 5, + ImagePullCredentialsVerificationPolicy: "NeverVerifyPreloadedImages", + MemoryThrottlingFactor: ptr.To[float64](0), + CrashLoopBackOff: kubeletconfiginternal.CrashLoopBackOffConfig{ + MaxContainerRestartPeriod: &metav1.Duration{Duration: 5 * time.Minute}, + }, + } + var prober volume.DynamicPluginProber + tp := noopoteltrace.NewTracerProvider() + cadvisor := cadvisortest.NewMockInterface(t) + cadvisor.EXPECT().MachineInfo().Return(&cadvisorapi.MachineInfo{}, nil).Maybe() + cadvisor.EXPECT().ImagesFsInfo(tCtx).Return(cadvisorapiv2.FsInfo{ + Usage: 400, + Capacity: 1000, + Available: 600, + }, nil).Maybe() + tlsOptions := &server.TLSOptions{ + MinVersion: 0, + CertFile: certPath, + KeyFile: keyPath, + ClientCAFile: caPath, + } + fakeRuntime, endpoint := createAndStartFakeRemoteRuntime(t) + defer func() { + fakeRuntime.Stop() + }() + fakeRecorder := &record.FakeRecorder{} + rtSvc := createRemoteRuntimeService(tCtx, endpoint, t, noopoteltrace.NewTracerProvider()) + kubeDep := &Dependencies{ + Auth: nil, + CAdvisorInterface: cadvisor, + ContainerManager: cm.NewStubContainerManager(), + KubeClient: nil, // standalone mode + HeartbeatClient: nil, + EventClient: nil, + TracerProvider: tp, + HostUtil: hostutil.NewFakeHostUtil(nil), + Mounter: mount.NewFakeMounter(nil), + Recorder: fakeRecorder, + RemoteRuntimeService: rtSvc, + RemoteImageService: fakeRuntime.ImageService, + Subpather: &subpath.FakeSubpath{}, + OOMAdjuster: oom.NewOOMAdjuster(), + OSInterface: kubecontainer.RealOS{}, + DynamicPluginProber: prober, + TLSOptions: tlsOptions, + } + crOptions := &kubeletconfig.ContainerRuntimeOptions{} + + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReloadKubeletServerCertificateFile, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RotateKubeletServerCertificate, false) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReloadKubeletClientCAFile, true) + + testMainKubelet, err := NewMainKubelet( + tCtx, + kubeCfg, + kubeDep, + crOptions, + "hostname", + "hostname", + []net.IP{}, + "", + "external", + "/tmp/cert", + "/tmp/rootdir", + tempDir, + "", + "", + false, + []v1.Taint{}, + []string{}, + "", + false, + false, + metav1.Duration{Duration: time.Minute}, + 1024, + 110, + map[string]string{}, + 1024, + false, + ) + require.NoError(t, err, "NewMainKubelet should succeed") + require.NotNil(t, testMainKubelet, "testMainKubelet should not be nil") + + testMainKubelet.BirthCry() + ctx := ktesting.Init(t) + testMainKubelet.StartGarbageCollection(ctx) + require.Nil(t, testMainKubelet.configMapManager, "configmap manager should be nil if kubelet is in standalone mode") + require.Nil(t, testMainKubelet.secretManager, "secret manager should be nil if kubelet is in standalone mode") + require.NotNil(t, testMainKubelet.serverCertificateManager, "serverCertificateManager should not be nil") + require.NotNil(t, kubeDep.TLSConfig.GetCertificate, "GetCertificate should not be nil") + require.NotNil(t, kubeDep.TLSConfig.GetConfigForClient, "GetConfigForClient should be nil") + require.Nil(t, kubeDep.TLSConfig.ClientCAs, "ClientCAs should be nil") } func TestSyncPodSpans(t *testing.T) { @@ -3410,13 +3595,12 @@ func TestSyncPodSpans(t *testing.T) { defer func() { fakeRuntime.Stop() }() - runtimeSvc := createRemoteRuntimeService(endpoint, t, tp) + runtimeSvc := createRemoteRuntimeService(tCtx, endpoint, t, tp) kubelet.runtimeService = runtimeSvc fakeRuntime.ImageService.SetFakeImageSize(100) fakeRuntime.ImageService.SetFakeImages([]string{"test:latest"}) - logger := klog.Background() - imageSvc, err := remote.NewRemoteImageService(endpoint, 15*time.Second, tp, &logger) + imageSvc, err := remote.NewRemoteImageService(tCtx, endpoint, 15*time.Second, tp, false) assert.NoError(t, err) kubelet.containerRuntime, _, err = kuberuntime.NewKubeGenericRuntimeManager( @@ -3454,10 +3638,12 @@ func TestSyncPodSpans(t *testing.T) { kubeCfg.MemorySwap.SwapBehavior, kubelet.containerManager.GetNodeAllocatableAbsolute, *kubeCfg.MemoryThrottlingFactor, - kubeletutil.NewPodStartupLatencyTracker(), + kubeCfg.MemoryReservationPolicy, + kubelet.podStartupLatencyTracker, tp, token.NewManager(kubelet.kubeClient), func(string, string) (*v1.ServiceAccount, error) { return nil, nil }, + kubelet.podStartupLatencyTracker, ) assert.NoError(t, err) kubelet.allocationManager.SetContainerRuntime(kubelet.containerRuntime) @@ -3473,7 +3659,7 @@ func TestSyncPodSpans(t *testing.T) { EnableServiceLinks: ptr.To(false), }) - _, err = kubelet.SyncPod(context.Background(), kubetypes.SyncPodCreate, pod, nil, &kubecontainer.PodStatus{}) + _, _, err = kubelet.SyncPod(tCtx, kubetypes.SyncPodCreate, pod, nil, &kubecontainer.PodStatus{}) require.NoError(t, err) assert.NotEmpty(t, exp.GetSpans()) @@ -3806,6 +3992,7 @@ func TestCrashLoopBackOffConfiguration(t *testing.T) { } func TestSyncPodWithErrorsDuringInPlacePodResize(t *testing.T) { + tCtx := ktesting.Init(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -3898,7 +4085,7 @@ func TestSyncPodWithErrorsDuringInPlacePodResize(t *testing.T) { t.Run(tc.name, func(t *testing.T) { testKubelet.fakeRuntime.SyncResults = tc.syncResults testKubelet.fakeRuntime.PodResizeInProgress = tc.podResizeInProgress - isTerminal, err := kubelet.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) + isTerminal, _, err := kubelet.SyncPod(tCtx, kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) require.False(t, isTerminal) if tc.expectedErr == "" { require.NoError(t, err) @@ -3932,6 +4119,7 @@ func TestSyncPodWithErrorsDuringInPlacePodResize(t *testing.T) { } func TestHandlePodUpdates_RecordContainerRequestedResizes(t *testing.T) { + tCtx := ktesting.Init(t) metrics.Register() metrics.ContainerRequestedResizes.Reset() @@ -4513,7 +4701,7 @@ func TestHandlePodUpdates_RecordContainerRequestedResizes(t *testing.T) { kubelet.podManager.AddPod(initialPod) require.NoError(t, kubelet.allocationManager.SetAllocatedResources(initialPod)) - kubelet.HandlePodUpdates([]*v1.Pod{updatedPod}) + kubelet.HandlePodUpdates(tCtx, []*v1.Pod{updatedPod}) tc.updateExpectedFunc(&expectedMetrics) @@ -4576,6 +4764,7 @@ func TestHandlePodReconcile_RetryPendingResizes(t *testing.T) { t.Skip("InPlacePodVerticalScaling is not currently supported for Windows") } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) + tCtx := ktesting.Init(t) testKubelet := newTestKubeletExcludeAdmitHandlers(t, false /* controllerAttachDetachEnabled */, true /*enableResizing*/) defer testKubelet.Cleanup() @@ -4620,6 +4809,9 @@ func TestHandlePodReconcile_RetryPendingResizes(t *testing.T) { } } + terminalPod := makePodWithResources("terminal-pod", v1.ResourceList{v1.ResourceCPU: lowCPU, v1.ResourceMemory: lowMem}, v1.ResourceList{v1.ResourceCPU: lowCPU, v1.ResourceMemory: lowMem}) + terminalPod.Status.Phase = v1.PodFailed + pendingResizeAllocated := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod-pending-resize", @@ -4680,6 +4872,12 @@ func TestHandlePodReconcile_RetryPendingResizes(t *testing.T) { newPod: makePodWithResources("updated-pod", v1.ResourceList{v1.ResourceCPU: enormousCPU, v1.ResourceMemory: enormousMem}, v1.ResourceList{v1.ResourceCPU: lowCPU, v1.ResourceMemory: lowMem}), shouldRetryPendingResize: true, }, + { + name: "pod is marked as terminal", + oldPod: makePodWithResources("terminal-pod", v1.ResourceList{v1.ResourceCPU: lowCPU, v1.ResourceMemory: lowMem}, v1.ResourceList{v1.ResourceCPU: lowCPU, v1.ResourceMemory: lowMem}), + newPod: terminalPod, + shouldRetryPendingResize: true, + }, } for _, tc := range testCases { @@ -4699,7 +4897,7 @@ func TestHandlePodReconcile_RetryPendingResizes(t *testing.T) { kubelet.allocationManager.PushPendingResize(pendingResizeDesired.UID) kubelet.statusManager.ClearPodResizePendingCondition(pendingResizeDesired.UID) - kubelet.HandlePodReconcile([]*v1.Pod{tc.newPod}) + kubelet.HandlePodReconcile(tCtx, []*v1.Pod{tc.newPod}) require.Equal(t, tc.shouldRetryPendingResize, kubelet.statusManager.IsPodResizeDeferred(pendingResizeDesired.UID)) kubelet.allocationManager.RemovePod(pendingResizeDesired.UID) @@ -4710,6 +4908,7 @@ func TestHandlePodReconcile_RetryPendingResizes(t *testing.T) { } func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { + tCtx := ktesting.Init(t) cpu1000m := resource.MustParse("1") mem1000M := resource.MustParse("1Gi") cpu2000m := resource.MustParse("2") @@ -4745,13 +4944,12 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { newPod.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = cpu2000m createMockFeature := func(t *testing.T, name string, inferForUpdate bool, maxVersionStr string) *ndftesting.MockFeature { m := ndftesting.NewMockFeature(t) - m.EXPECT().Name().Return(name).Maybe() - m.EXPECT().InferForUpdate(mock.Anything, mock.Anything).Return(inferForUpdate).Maybe() + m.SetName(name) + m.SetInferForUpdate(func(_, _ *ndf.PodInfo) bool { return inferForUpdate }) if maxVersionStr != "" { - maxVersionStr := utilversion.MustParseSemantic(maxVersionStr) - m.EXPECT().MaxVersion().Return(maxVersionStr).Maybe() + m.SetMaxVersion(utilversion.MustParseSemantic(maxVersionStr)) } else { - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetMaxVersion(nil) } return m } @@ -4760,6 +4958,8 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { name string featureGateEnabled bool nodeFeatures []string + oldPod *v1.Pod + newPod *v1.Pod registeredFeatures []ndf.Feature expectEvent bool expectedEventMsg string @@ -4769,6 +4969,8 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { name: "Feature gate disabled", featureGateEnabled: false, componentVersion: "1.35.0", + oldPod: oldPod, + newPod: newPod, registeredFeatures: []ndf.Feature{}, expectEvent: false, }, @@ -4776,6 +4978,8 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { name: "Feature gate enabled, no requirements from update", featureGateEnabled: true, componentVersion: "1.35.0", + oldPod: oldPod, + newPod: newPod, nodeFeatures: []string{"FeatureA"}, registeredFeatures: []ndf.Feature{createMockFeature(t, "FeatureA", false, "")}, expectEvent: false, @@ -4784,6 +4988,8 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { name: "Feature gate enabled, requirements met", featureGateEnabled: true, componentVersion: "1.35.0", + oldPod: oldPod, + newPod: newPod, nodeFeatures: []string{"FeatureA"}, registeredFeatures: []ndf.Feature{createMockFeature(t, "FeatureA", true, "")}, expectEvent: false, @@ -4792,8 +4998,10 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { name: "Feature gate enabled, requirements not met", featureGateEnabled: true, componentVersion: "1.35.0", + oldPod: oldPod, + newPod: newPod, nodeFeatures: []string{"FeatureB"}, - registeredFeatures: []ndf.Feature{createMockFeature(t, "FeatureA", true, "")}, + registeredFeatures: []ndf.Feature{createMockFeature(t, "FeatureA", true, ""), createMockFeature(t, "FeatureB", false, "")}, expectEvent: true, expectedEventMsg: "Pod requires node features that are not available: FeatureA", }, @@ -4801,14 +5009,27 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { name: "feature generally available - not declared", featureGateEnabled: true, componentVersion: "1.35.0", - nodeFeatures: []string{""}, + oldPod: oldPod, + newPod: newPod, + nodeFeatures: []string{}, registeredFeatures: []ndf.Feature{createMockFeature(t, "FeatureA", true, "1.34.0")}, expectEvent: false, }, + { + name: "Feature gate enabled, old pod state missing, node declared feature check skipped", + featureGateEnabled: true, + componentVersion: "1.35.0", + oldPod: nil, + newPod: newPod, + nodeFeatures: []string{"FeatureB"}, + registeredFeatures: []ndf.Feature{createMockFeature(t, "FeatureA", true, ""), createMockFeature(t, "FeatureB", false, "")}, + expectEvent: false, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + logger := klog.FromContext(tCtx) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, tc.featureGateEnabled) testKubelet := newTestKubelet(t, false) @@ -4819,17 +5040,18 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { recorder := record.NewFakeRecorder(10) kubelet.recorder = recorder - framework, err := ndf.New(tc.registeredFeatures) - require.NoError(t, err) + framework := ndf.New(tc.registeredFeatures) kubelet.nodeDeclaredFeaturesFramework = framework kubelet.nodeDeclaredFeatures = tc.nodeFeatures - kubelet.nodeDeclaredFeaturesSet = ndf.NewFeatureSet(kubelet.nodeDeclaredFeatures...) + kubelet.nodeDeclaredFeaturesSet = framework.MustMapSorted(kubelet.nodeDeclaredFeatures) kubelet.version = utilversion.MustParse("1.35.0") - kubelet.podManager.SetPods([]*v1.Pod{oldPod}) - kubelet.statusManager.SetPodStatus(klog.TODO(), newPod, v1.PodStatus{Phase: v1.PodRunning}) - kubelet.HandlePodUpdates([]*v1.Pod{newPod}) + if tc.oldPod != nil { + kubelet.podManager.SetPods([]*v1.Pod{tc.oldPod}) + } + kubelet.statusManager.SetPodStatus(logger, tc.newPod, v1.PodStatus{Phase: v1.PodRunning}) + kubelet.HandlePodUpdates(tCtx, []*v1.Pod{tc.newPod}) if tc.expectEvent { select { case event := <-recorder.Events: @@ -4848,3 +5070,115 @@ func TestSyncPodNodeDeclaredFeaturesUpdate(t *testing.T) { }) } } + +func generateCAAndCertKeyWithOptions(host string, alternateIPs []net.IP, alternateDNS []string) (ca, certificate, certKey []byte, err error) { + certs, key, err := cert.GenerateSelfSignedCertKeyWithOptions(cert.SelfSignedCertKeyOptions{ + Host: host, + AlternateIPs: alternateIPs, + AlternateDNS: alternateDNS, + }) + if err != nil { + return nil, nil, nil, err + } + + parsedCerts, err := cert.ParseCertsPEM(certs) + if err != nil { + return nil, nil, nil, err + } + + var leafCert *x509.Certificate + var caCert *x509.Certificate + + for _, parsedCert := range parsedCerts { + if parsedCert.IsCA { + caCert = parsedCert + } else { + leafCert = parsedCert + } + } + + caCertBytes, err := cert.EncodeCertificates(caCert) + if err != nil { + return nil, nil, nil, err + } + + leafCertBytes, err := cert.EncodeCertificates(leafCert) + if err != nil { + return nil, nil, nil, err + } + + return caCertBytes, leafCertBytes, key, err +} + +func TestSyncPodRestartAllContainersRequeue(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RestartAllContainersOnContainerExits, true) + + testKubelet := newTestKubelet(t, false) + defer testKubelet.Cleanup() + kubelet := testKubelet.kubelet + + pod := podWithUIDNameNsSpec("12345678", "foo", "new", v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "bar", + RestartPolicyRules: []v1.ContainerRestartRule{ + { + Action: v1.ContainerRestartRuleActionRestartAllContainers, + }, + }, + }, + }, + }) + kubelet.podManager.SetPods([]*v1.Pod{pod}) + + logger := klog.FromContext(context.Background()) + kubelet.statusManager.SetPodStatus(logger, pod, v1.PodStatus{ + Phase: v1.PodRunning, + Conditions: []v1.PodCondition{ + { + Type: v1.AllContainersRestarting, + Status: v1.ConditionTrue, + }, + }, + }) + + testKubelet.fakeRuntime.SyncResults = &kubecontainer.PodSyncResult{ + SyncResults: []*kubecontainer.SyncResult{{ + Action: kubecontainer.RemoveContainer, + Target: pod.UID, + Error: nil, + }}, + } + + callCount := 0 + kubelet.podWorkers = &fakePodWorkers{ + syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { + callCount++ + if callCount > 1 { + // In the second call the SyncResult will not include the RemoveContainer. + // This ensures no infinite loop. + testKubelet.fakeRuntime.SyncResults = &kubecontainer.PodSyncResult{} + } + return kubelet.SyncPod(ctx, updateType, pod, mirrorPod, podStatus) + }, + cache: kubelet.podCache, + t: t, + } + + podStatus := &kubecontainer.PodStatus{ + ContainerStatuses: []*kubecontainer.Status{ + { + Name: "bar", + }, + }, + } + + isTerminal, _, err := kubelet.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, nil, podStatus) + require.False(t, isTerminal) + require.NoError(t, err) + + // We expect SyncPod to be called once by us, and once recursively via UpdatePod -> fakePodWorkers.syncPodFn + // because the SyncResults contained a successful RemoveContainer action. + require.Equal(t, 1, callCount) +} diff --git a/pkg/kubelet/kubelet_volumes.go b/pkg/kubelet/kubelet_volumes.go index 40320dcdf55af..199dcb28e3311 100644 --- a/pkg/kubelet/kubelet_volumes.go +++ b/pkg/kubelet/kubelet_volumes.go @@ -75,20 +75,20 @@ func (kl *Kubelet) ListBlockVolumesForPod(podUID types.UID) (map[string]volume.B // podVolumesExist checks with the volume manager and returns true any of the // pods for the specified volume are mounted or are uncertain. -func (kl *Kubelet) podVolumesExist(podUID types.UID) bool { +func (kl *Kubelet) podVolumesExist(logger klog.Logger, podUID types.UID) bool { if kl.volumeManager.HasPossiblyMountedVolumesForPod(volumetypes.UniquePodName(podUID)) { return true } // TODO: This checks pod volume paths and whether they are mounted. If checking returns error, podVolumesExist will return true // which means we consider volumes might exist and requires further checking. // There are some volume plugins such as flexvolume might not have mounts. See issue #61229 - volumePaths, err := kl.getMountedVolumePathListFromDisk(podUID) + volumePaths, err := kl.getMountedVolumePathListFromDisk(logger, podUID) if err != nil { - klog.ErrorS(err, "Pod found, but error occurred during checking mounted volumes from disk", "podUID", podUID) + logger.Error(err, "Pod found, but error occurred during checking mounted volumes from disk", "podUID", podUID) return true } if len(volumePaths) > 0 { - klog.V(4).InfoS("Pod found, but volumes are still mounted on disk", "podUID", podUID, "paths", volumePaths) + logger.V(4).Info("Pod found, but volumes are still mounted on disk", "podUID", podUID, "paths", volumePaths) return true } @@ -116,11 +116,11 @@ func (kl *Kubelet) newVolumeMounterFromPlugins(spec *volume.Spec, pod *v1.Pod) ( // when this is called, so it effectively does a recursive rmdir instead of // RemoveAll to ensure it only removes empty directories and files that were // used as mount points, but not content of the mount points. -func (kl *Kubelet) removeOrphanedPodVolumeDirs(uid types.UID) []error { +func (kl *Kubelet) removeOrphanedPodVolumeDirs(logger klog.Logger, uid types.UID) []error { orphanVolumeErrors := []error{} // If there are still volume directories, attempt to rmdir them - volumePaths, err := kl.getPodVolumePathListFromDisk(uid) + volumePaths, err := kl.getPodVolumePathListFromDisk(logger, uid) if err != nil { orphanVolumeErrors = append(orphanVolumeErrors, fmt.Errorf("orphaned pod %q found, but error occurred during reading volume dir from disk: %v", uid, err)) return orphanVolumeErrors @@ -136,7 +136,7 @@ func (kl *Kubelet) removeOrphanedPodVolumeDirs(uid types.UID) []error { } // If there are any volume-subpaths, attempt to remove them - subpathVolumePaths, err := kl.getPodVolumeSubpathListFromDisk(uid) + subpathVolumePaths, err := kl.getPodVolumeSubpathListFromDisk(logger, uid) if err != nil { orphanVolumeErrors = append(orphanVolumeErrors, fmt.Errorf("orphaned pod %q found, but error occurred during reading of volume-subpaths dir from disk: %v", uid, err)) return orphanVolumeErrors @@ -145,7 +145,7 @@ func (kl *Kubelet) removeOrphanedPodVolumeDirs(uid types.UID) []error { for _, subpathVolumePath := range subpathVolumePaths { // Remove both files and empty directories here, as the subpath may have been a bind-mount of a file or a directory. if err := os.Remove(subpathVolumePath); err != nil { - orphanVolumeErrors = append(orphanVolumeErrors, fmt.Errorf("orphaned pod %q found, but failed to rmdir() subpath at path %v: %v", uid, subpathVolumePath, err)) + orphanVolumeErrors = append(orphanVolumeErrors, fmt.Errorf("orphaned pod %q found, but failed to remove subpath at path %v: %w", uid, subpathVolumePath, err)) } else { klog.InfoS("Cleaned up orphaned volume subpath from pod", "podUID", uid, "path", subpathVolumePath) } @@ -166,7 +166,7 @@ func (kl *Kubelet) removeOrphanedPodVolumeDirs(uid types.UID) []error { // cleanupOrphanedPodDirs removes the volumes of pods that should not be // running and that have no containers running. Note that we roll up logs here since it runs in the main loop. -func (kl *Kubelet) cleanupOrphanedPodDirs(pods []*v1.Pod, runningPods []*kubecontainer.Pod) error { +func (kl *Kubelet) cleanupOrphanedPodDirs(logger klog.Logger, pods []*v1.Pod, runningPods []*kubecontainer.Pod) error { allPods := sets.New[string]() for _, pod := range pods { allPods.Insert(string(pod.UID)) @@ -195,14 +195,14 @@ func (kl *Kubelet) cleanupOrphanedPodDirs(pods []*v1.Pod, runningPods []*kubecon // Doing so may result in corruption of data. // TODO: getMountedVolumePathListFromDisk() call may be redundant with // kl.getPodVolumePathListFromDisk(). Can this be cleaned up? - if podVolumesExist := kl.podVolumesExist(uid); podVolumesExist { + if podVolumesExist := kl.podVolumesExist(logger, uid); podVolumesExist { errorPods++ - klog.V(3).InfoS("Orphaned pod found, but volumes are not cleaned up", "podUID", uid) + logger.V(3).Info("Orphaned pod found, but volumes are not cleaned up", "podUID", uid) continue } - // Attempt to remove the pod volumes directory and its subdirs - podVolumeErrors := kl.removeOrphanedPodVolumeDirs(uid) + // Attempt to remove the pod volumes directory and its subdirectories + podVolumeErrors := kl.removeOrphanedPodVolumeDirs(logger, uid) if len(podVolumeErrors) > 0 { errorPods++ orphanVolumeErrors = append(orphanVolumeErrors, podVolumeErrors...) @@ -214,7 +214,7 @@ func (kl *Kubelet) cleanupOrphanedPodDirs(pods []*v1.Pod, runningPods []*kubecon continue } - // Call RemoveAllOneFilesystem for remaining subdirs under the pod directory + // Call RemoveAllOneFilesystem for remaining subdirectories under the pod directory podDir := kl.getPodDir(uid) podSubdirs, err := os.ReadDir(podDir) if err != nil { diff --git a/pkg/kubelet/kubelet_volumes_linux_test.go b/pkg/kubelet/kubelet_volumes_linux_test.go index 67836a044bc7d..5b5c23adfb92c 100644 --- a/pkg/kubelet/kubelet_volumes_linux_test.go +++ b/pkg/kubelet/kubelet_volumes_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -29,6 +28,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2/ktesting" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/mount-utils" ) @@ -171,6 +171,7 @@ func TestCleanupOrphanedPodDirs(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -181,7 +182,7 @@ func TestCleanupOrphanedPodDirs(t *testing.T) { } } - err := kubelet.cleanupOrphanedPodDirs(tc.pods, nil) + err := kubelet.cleanupOrphanedPodDirs(logger, tc.pods, nil) if tc.expectErr && err == nil { t.Errorf("%s failed: expected error, got success", name) } @@ -293,6 +294,7 @@ func TestPodVolumesExistWithMount(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) defer testKubelet.Cleanup() kubelet := testKubelet.kubelet @@ -303,7 +305,7 @@ func TestPodVolumesExistWithMount(t *testing.T) { } } - exist := kubelet.podVolumesExist(poduid) + exist := kubelet.podVolumesExist(logger, poduid) if tc.expected != exist { t.Errorf("%s failed: expected %t, got %t", name, tc.expected, exist) } diff --git a/pkg/kubelet/kubelet_volumes_test.go b/pkg/kubelet/kubelet_volumes_test.go index 4f8bf7c45ef9e..d88e88ca04f72 100644 --- a/pkg/kubelet/kubelet_volumes_test.go +++ b/pkg/kubelet/kubelet_volumes_test.go @@ -200,16 +200,17 @@ func TestPodVolumesExist(t *testing.T) { tCtx := ktesting.Init(t) defer tCtx.Cancel("test has completed") + go kubelet.volumeManager.Run(tCtx, kubelet.sourcesReady) kubelet.podManager.SetPods(pods) for _, pod := range pods { - err := kubelet.volumeManager.WaitForAttachAndMount(context.Background(), pod) + err := kubelet.volumeManager.WaitForAttachAndMount(tCtx, pod) assert.NoError(t, err) } for _, pod := range pods { - podVolumesExist := kubelet.podVolumesExist(pod.UID) + podVolumesExist := kubelet.podVolumesExist(tCtx.Logger(), pod.UID) assert.True(t, podVolumesExist, "pod %q", pod.UID) } } diff --git a/pkg/kubelet/kubeletconfig/defaults.go b/pkg/kubelet/kubeletconfig/defaults.go index 7f4577ab15dba..953df2919821b 100644 --- a/pkg/kubelet/kubeletconfig/defaults.go +++ b/pkg/kubelet/kubeletconfig/defaults.go @@ -27,6 +27,7 @@ const ( DefaultKubeletContainersDirName = "containers" DefaultKubeletPluginContainersDirName = "plugin-containers" DefaultKubeletPodResourcesDirName = "pod-resources" + DefaultKubeletPodsAPIDirName = "pods-api" KubeletPluginsDirSELinuxLabel = "system_u:object_r:container_file_t:s0" KubeletContainersSharedSELinuxLabel = "system_u:object_r:container_file_t:s0" DefaultKubeletCheckpointsDirName = "checkpoints" diff --git a/pkg/kubelet/kuberuntime/convert.go b/pkg/kubelet/kuberuntime/convert.go index e64c6bada7e2c..7e6ff5673becb 100644 --- a/pkg/kubelet/kuberuntime/convert.go +++ b/pkg/kubelet/kuberuntime/convert.go @@ -27,7 +27,7 @@ import ( // This file contains help function to kuberuntime types to CRI runtime API types, or vice versa. -func toKubeContainerImageSpec(image *runtimeapi.Image) kubecontainer.ImageSpec { +func ToKubeContainerImageSpec(image *runtimeapi.Image) kubecontainer.ImageSpec { var annotations []kubecontainer.Annotation if image.Spec != nil && len(image.Spec.Annotations) > 0 { @@ -60,7 +60,7 @@ func toKubeContainerImageSpec(image *runtimeapi.Image) kubecontainer.ImageSpec { return spec } -func toRuntimeAPIImageSpec(imageSpec kubecontainer.ImageSpec) *runtimeapi.ImageSpec { +func ToRuntimeAPIImageSpec(imageSpec kubecontainer.ImageSpec) *runtimeapi.ImageSpec { var annotations = make(map[string]string) if imageSpec.Annotations != nil { for _, a := range imageSpec.Annotations { diff --git a/pkg/kubelet/kuberuntime/convert_test.go b/pkg/kubelet/kuberuntime/convert_test.go index 3e01a9f92ff8a..b26ea230529eb 100644 --- a/pkg/kubelet/kuberuntime/convert_test.go +++ b/pkg/kubelet/kuberuntime/convert_test.go @@ -94,7 +94,7 @@ func TestConvertToKubeContainerImageSpec(t *testing.T) { } for _, test := range testCases { - actual := toKubeContainerImageSpec(test.input) + actual := ToKubeContainerImageSpec(test.input) assert.Equal(t, test.expected, actual) } } @@ -155,7 +155,7 @@ func TestConvertToRuntimeAPIImageSpec(t *testing.T) { } for _, test := range testCases { - actual := toRuntimeAPIImageSpec(test.input) + actual := ToRuntimeAPIImageSpec(test.input) assert.Equal(t, test.expected, actual) } } @@ -232,7 +232,7 @@ func TestConvertToKubeContainerImageSpecWithRuntimeHandlerInImageSpecCri(t *test for _, test := range testCases { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClassInImageCriAPI, true) - actual := toKubeContainerImageSpec(test.input) + actual := ToKubeContainerImageSpec(test.input) assert.Equal(t, test.expected, actual) } } @@ -294,7 +294,7 @@ func TestConvertToRuntimeAPIImageSpecWithRuntimeHandlerInImageSpecCri(t *testing for _, test := range testCases { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClassInImageCriAPI, true) - actual := toRuntimeAPIImageSpec(test.input) + actual := ToRuntimeAPIImageSpec(test.input) assert.Equal(t, test.expected, actual) } } diff --git a/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go b/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go index dad8c88c4de5c..8b21bb0949be2 100644 --- a/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go +++ b/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go @@ -97,10 +97,6 @@ func (f *fakePodPullingTimeRecorder) RecordImageFinishedPulling(podUID types.UID func newFakeKubeRuntimeManager(ctx context.Context, runtimeService internalapi.RuntimeService, imageService internalapi.ImageManagerService, machineInfo *cadvisorapi.MachineInfo, osInterface kubecontainer.OSInterface, runtimeHelper kubecontainer.RuntimeHelper, tracer trace.Tracer) (*kubeGenericRuntimeManager, error) { recorder := &record.FakeRecorder{} - logManager, err := logs.NewContainerLogManager(runtimeService, osInterface, "1", 2, 10, metav1.Duration{Duration: 10 * time.Second}) - if err != nil { - return nil, err - } kubeRuntimeManager := &kubeGenericRuntimeManager{ recorder: recorder, cpuCFSQuota: false, @@ -116,7 +112,7 @@ func newFakeKubeRuntimeManager(ctx context.Context, runtimeService internalapi.R seccompProfileRoot: fakeSeccompProfileRoot, internalLifecycle: cm.NewFakeInternalContainerLifecycle(), logReduction: logreduction.NewLogReduction(identicalErrorDelay), - logManager: logManager, + logManager: logs.NewStubContainerLogManager(), memoryThrottlingFactor: 0.9, podLogsDirectory: fakePodLogsDirectory, actuatedState: state.NewStateMemory(nil), diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go index 97eb7055953b2..ad4fb4367a244 100644 --- a/pkg/kubelet/kuberuntime/helpers.go +++ b/pkg/kubelet/kuberuntime/helpers.go @@ -36,26 +36,28 @@ import ( "k8s.io/kubernetes/pkg/security/apparmor" ) -type podsByID []*kubecontainer.Pod +type containerByCreatedThenID []*runtimeapi.Container -func (b podsByID) Len() int { return len(b) } -func (b podsByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b podsByID) Less(i, j int) bool { return b[i].ID < b[j].ID } - -type containersByID []*kubecontainer.Container - -func (b containersByID) Len() int { return len(b) } -func (b containersByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b containersByID) Less(i, j int) bool { return b[i].ID.ID < b[j].ID.ID } +func (b containerByCreatedThenID) Len() int { return len(b) } +func (b containerByCreatedThenID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b containerByCreatedThenID) Less(i, j int) bool { + if b[i].CreatedAt != b[j].CreatedAt { + return b[i].CreatedAt > (b[j].CreatedAt) + } + return b[i].Id < b[j].Id +} // Newest first. -type podSandboxByCreated []*runtimeapi.PodSandbox - -func (p podSandboxByCreated) Len() int { return len(p) } -func (p podSandboxByCreated) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p podSandboxByCreated) Less(i, j int) bool { - if p[i].Metadata == nil || p[j].Metadata == nil { - return p[i].CreatedAt > p[j].CreatedAt +type podSandboxByCreatedThenID []*runtimeapi.PodSandbox + +func (p podSandboxByCreatedThenID) Len() int { return len(p) } +func (p podSandboxByCreatedThenID) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p podSandboxByCreatedThenID) Less(i, j int) bool { + if p[i].Metadata == nil || p[j].Metadata == nil || (p[i].Metadata.Attempt == p[j].Metadata.Attempt) { + if p[i].CreatedAt != p[j].CreatedAt { + return p[i].CreatedAt > p[j].CreatedAt + } + return p[i].Id < p[j].Id } return p[i].Metadata.Attempt > p[j].Metadata.Attempt } @@ -119,6 +121,8 @@ func (m *kubeGenericRuntimeManager) toKubeContainer(ctx context.Context, c *runt Image: c.Image.Image, Hash: annotatedInfo.Hash, State: toKubeContainerState(c.State), + PodSandboxID: c.PodSandboxId, + CreatedAt: c.CreatedAt, }, nil } @@ -132,8 +136,10 @@ func (m *kubeGenericRuntimeManager) sandboxToKubeContainer(s *runtimeapi.PodSand } return &kubecontainer.Container{ - ID: kubecontainer.ContainerID{Type: m.runtimeName, ID: s.Id}, - State: kubecontainer.SandboxToContainerState(s.State), + ID: kubecontainer.ContainerID{Type: m.runtimeName, ID: s.Id}, + State: kubecontainer.SandboxToContainerState(s.State), + PodSandboxID: s.Id, + CreatedAt: s.CreatedAt, }, nil } @@ -255,7 +261,8 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus, handlers []*runtimeap var retFeatures *kubecontainer.RuntimeFeatures if features != nil { retFeatures = &kubecontainer.RuntimeFeatures{ - SupplementalGroupsPolicy: features.SupplementalGroupsPolicy, + SupplementalGroupsPolicy: features.SupplementalGroupsPolicy, + UserNamespacesHostNetwork: features.UserNamespacesHostNetwork, } } return &kubecontainer.RuntimeStatus{Conditions: conditions, Handlers: retHandlers, Features: retFeatures} diff --git a/pkg/kubelet/kuberuntime/helpers_linux.go b/pkg/kubelet/kuberuntime/helpers_linux.go index a3b1ab2c30064..9de005cdbf3e4 100644 --- a/pkg/kubelet/kuberuntime/helpers_linux.go +++ b/pkg/kubelet/kuberuntime/helpers_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -20,29 +19,10 @@ limitations under the License. package kuberuntime import ( - "math" - v1 "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/kubelet/cm" ) -// sharesToMilliCPU converts CpuShares (cpu.shares) to milli-CPU value -func sharesToMilliCPU(shares int64) int64 { - milliCPU := int64(0) - if shares >= int64(cm.MinShares) { - milliCPU = int64(math.Ceil(float64(shares*cm.MilliCPUToCPU) / float64(cm.SharesPerCPU))) - } - return milliCPU -} - -// quotaToMilliCPU converts cpu.cfs_quota_us and cpu.cfs_period_us to milli-CPU value -func quotaToMilliCPU(quota int64, period int64) int64 { - if quota == -1 { - return int64(0) - } - return (quota * cm.MilliCPUToCPU) / period -} - func subtractOverheadFromResourceConfig(resCfg *cm.ResourceConfig, pod *v1.Pod) *cm.ResourceConfig { if resCfg == nil { return nil @@ -59,7 +39,7 @@ func subtractOverheadFromResourceConfig(resCfg *cm.ResourceConfig, pod *v1.Pod) } if rc.CPUShares != nil { - totalCPUMilli := sharesToMilliCPU(int64(*rc.CPUShares)) + totalCPUMilli := cm.SharesToMilliCPU(int64(*rc.CPUShares)) cpuShares := cm.MilliCPUToShares(totalCPUMilli - cpu.MilliValue()) rc.CPUShares = &cpuShares } diff --git a/pkg/kubelet/kuberuntime/helpers_linux_test.go b/pkg/kubelet/kuberuntime/helpers_linux_test.go index ca68180bb26c6..b230eb00eaf3a 100644 --- a/pkg/kubelet/kuberuntime/helpers_linux_test.go +++ b/pkg/kubelet/kuberuntime/helpers_linux_test.go @@ -267,7 +267,7 @@ func TestSharesToMilliCPU(t *testing.T) { if testMilliCPU < 2 { expectedMilliCPU = 2 } - milliCPU := sharesToMilliCPU(shares) + milliCPU := cm.SharesToMilliCPU(shares) if milliCPU != expectedMilliCPU { t.Errorf("Test sharesToMilliCPU: Input shares %v, expected milliCPU %v, but got %v", shares, expectedMilliCPU, milliCPU) } @@ -307,7 +307,7 @@ func TestQuotaToMilliCPU(t *testing.T) { expected: int64(1500), }} { t.Run(tc.name, func(t *testing.T) { - milliCPU := quotaToMilliCPU(tc.quota, tc.period) + milliCPU := cm.QuotaToMilliCPU(tc.quota, tc.period) if milliCPU != tc.expected { t.Errorf("Test %s: Input quota %v and period %v, expected milliCPU %v, but got %v", tc.name, tc.quota, tc.period, tc.expected, milliCPU) } diff --git a/pkg/kubelet/kuberuntime/helpers_test.go b/pkg/kubelet/kuberuntime/helpers_test.go index 836669d4e087a..0f9e7f82680b0 100644 --- a/pkg/kubelet/kuberuntime/helpers_test.go +++ b/pkg/kubelet/kuberuntime/helpers_test.go @@ -18,7 +18,10 @@ package kuberuntime import ( "context" + "fmt" + "sort" "testing" + "time" "github.com/stretchr/testify/assert" @@ -38,10 +41,23 @@ import ( "k8s.io/utils/ptr" ) -type podStatusProviderFunc func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) +type fakePodStatusProvider struct { + pod *kubecontainer.Pod + status *kubecontainer.PodStatus +} + +func (f fakePodStatusProvider) GetPod(_ context.Context, uid types.UID) (*kubecontainer.Pod, error) { + if uid != f.pod.ID { + return nil, fmt.Errorf("unexpected pod UID: got %s, want %s", uid, f.pod.ID) + } + return f.pod, nil +} -func (f podStatusProviderFunc) GetPodStatus(_ context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { - return f(uid, name, namespace) +func (f fakePodStatusProvider) GetPodStatus(_ context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { + if pod != f.pod { + return nil, fmt.Errorf("Unexpected pod: %v", pod) + } + return f.status, nil } func TestIsInitContainerFailed(t *testing.T) { @@ -586,3 +602,218 @@ func TestConvertResourceConfigToLinuxContainerResources(t *testing.T) { assert.Equal(t, *resCfg.Memory, lcr.MemoryLimitInBytes) assert.Equal(t, resCfg.Unified, lcr.Unified) } + +func TestContainerByCreatedThenID(t *testing.T) { + now := time.Now() + + tests := []struct { + name string + input []*runtimeapi.Container + expected []*runtimeapi.Container + }{ + { + name: "different CreatedAt sort by time desc (newest first)", + input: []*runtimeapi.Container{ + {Id: "c1", CreatedAt: now.Add(-30 * time.Minute).Unix()}, + {Id: "c2", CreatedAt: now.Unix()}, + {Id: "c3", CreatedAt: now.Add(-10 * time.Minute).Unix()}, + }, + expected: []*runtimeapi.Container{ + {Id: "c2"}, // newest + {Id: "c3"}, + {Id: "c1"}, // oldest + }, + }, + { + name: "same CreatedAt sort by ID asc", + input: []*runtimeapi.Container{ + {Id: "container-zzz", CreatedAt: now.Unix()}, + {Id: "container-aaa", CreatedAt: now.Unix()}, + {Id: "container-mmm", CreatedAt: now.Unix()}, + }, + expected: []*runtimeapi.Container{ + {Id: "container-aaa"}, + {Id: "container-mmm"}, + {Id: "container-zzz"}, + }, + }, + { + name: "mixed: some same time, some different", + input: []*runtimeapi.Container{ + {Id: "c-old-2", CreatedAt: now.Add(-1 * time.Hour).Unix()}, + {Id: "c-new-1", CreatedAt: now.Unix()}, + {Id: "c-group-b", CreatedAt: now.Unix()}, + {Id: "c-group-a", CreatedAt: now.Unix()}, + {Id: "c-old-1", CreatedAt: now.Add(-1 * time.Hour).Unix()}, + }, + expected: []*runtimeapi.Container{ + {Id: "c-group-a"}, + {Id: "c-group-b"}, + {Id: "c-new-1"}, + {Id: "c-old-1"}, + {Id: "c-old-2"}, + }, + }, + { + name: "empty slice", + input: []*runtimeapi.Container{}, + expected: []*runtimeapi.Container{}, + }, + { + name: "single element", + input: []*runtimeapi.Container{ + {Id: "only-one", CreatedAt: now.Add(-5 * time.Minute).Unix()}, + }, + expected: []*runtimeapi.Container{ + {Id: "only-one"}, + }, + }, + { + name: "all CreatedAt same, IDs already sorted should remain stable", + input: []*runtimeapi.Container{ + {Id: "id-001", CreatedAt: now.Unix()}, + {Id: "id-002", CreatedAt: now.Unix()}, + {Id: "id-003", CreatedAt: now.Unix()}, + }, + expected: []*runtimeapi.Container{ + {Id: "id-001"}, + {Id: "id-002"}, + {Id: "id-003"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + containers := make([]*runtimeapi.Container, len(tt.input)) + copy(containers, tt.input) + + sort.Sort(containerByCreatedThenID(containers)) + + assert.Len(t, containers, len(tt.expected), "length mismatch") + + for i := range tt.expected { + assert.Equal(t, tt.expected[i].Id, containers[i].Id, "ID mismatch at index %d", i) + } + }) + } +} + +func TestPodSandboxByCreatedThenID(t *testing.T) { + now := time.Now() + + tests := []struct { + name string + input []*runtimeapi.PodSandbox + expected []string + }{ + { + name: "different Attempt higher Attempt first", + input: []*runtimeapi.PodSandbox{ + { + Id: "sandbox-c", + CreatedAt: now.UnixNano(), + Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 0}, + }, + { + Id: "sandbox-a", + CreatedAt: now.UnixNano(), + Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 2}, + }, + { + Id: "sandbox-b", + CreatedAt: now.UnixNano(), + Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}, + }, + }, + expected: []string{"sandbox-a", "sandbox-b", "sandbox-c"}, + }, + { + name: "same Attempt newer CreatedAt first", + input: []*runtimeapi.PodSandbox{ + { + Id: "old", + CreatedAt: now.Add(-10 * time.Minute).UnixNano(), + Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 5}, + }, + { + Id: "newest", + CreatedAt: now.UnixNano(), + Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 5}, + }, + { + Id: "medium", + CreatedAt: now.Add(-5 * time.Minute).UnixNano(), + Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 5}, + }, + }, + expected: []string{"newest", "medium", "old"}, + }, + { + name: "same Attempt and CreatedAt sort by Id ascending", + input: []*runtimeapi.PodSandbox{ + {Id: "zzz-3", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}}, + {Id: "aaa-1", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}}, + {Id: "mmm-2", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}}, + }, + expected: []string{"aaa-1", "mmm-2", "zzz-3"}, + }, + { + name: "mixed: Attempt CreatedAt + Id", + input: []*runtimeapi.PodSandbox{ + {Id: "c1", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 0}}, + {Id: "a2", CreatedAt: now.Add(-20 * time.Minute).UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 2}}, + {Id: "b3", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}}, + {Id: "group-b", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}}, + {Id: "group-a", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 1}}, + }, + expected: []string{ + "a2", + "b3", + "group-a", + "group-b", + "c1", + }, + }, + { + name: "empty slice", + input: []*runtimeapi.PodSandbox{}, + expected: []string{}, + }, + { + name: "single sandbox", + input: []*runtimeapi.PodSandbox{ + {Id: "single", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 3}}, + }, + expected: []string{"single"}, + }, + { + name: "Metadata nil fallback only CreatedAt then Id", + input: []*runtimeapi.PodSandbox{ + {Id: "nil-b", CreatedAt: now.UnixNano(), Metadata: nil}, + {Id: "nil-a", CreatedAt: now.UnixNano(), Metadata: nil}, + {Id: "with-meta", CreatedAt: now.UnixNano(), Metadata: &runtimeapi.PodSandboxMetadata{Attempt: 0}}, + }, + expected: []string{"nil-a", "nil-b", "with-meta"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sandboxes := make([]*runtimeapi.PodSandbox, len(tt.input)) + copy(sandboxes, tt.input) + + sort.Sort(podSandboxByCreatedThenID(sandboxes)) + + assert.Len(t, sandboxes, len(tt.expected), "length mismatch") + + actualIDs := make([]string, len(sandboxes)) + for i, s := range sandboxes { + actualIDs[i] = s.Id + } + + assert.Equal(t, tt.expected, actualIDs, "sorting order mismatch") + }) + } +} diff --git a/pkg/kubelet/kuberuntime/helpers_unsupported.go b/pkg/kubelet/kuberuntime/helpers_unsupported.go index 95693fadda9c5..b94aa78e9f2e9 100644 --- a/pkg/kubelet/kuberuntime/helpers_unsupported.go +++ b/pkg/kubelet/kuberuntime/helpers_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/instrumented_services.go b/pkg/kubelet/kuberuntime/instrumented_services.go index 52896d59b1fd6..8b17a034215ee 100644 --- a/pkg/kubelet/kuberuntime/instrumented_services.go +++ b/pkg/kubelet/kuberuntime/instrumented_services.go @@ -181,7 +181,7 @@ func (in instrumentedRuntimeService) RunPodSandbox(ctx context.Context, config * const operation = "run_podsandbox" startTime := time.Now() defer recordOperation(operation, startTime) - defer metrics.RunPodSandboxDuration.WithLabelValues(runtimeHandler).Observe(metrics.SinceInSeconds(startTime)) + defer metrics.RunPodSandboxDuration.ObserveSince(startTime, runtimeHandler)() out, err := in.service.RunPodSandbox(ctx, config, runtimeHandler) recordError(operation, err) @@ -335,11 +335,11 @@ func (in instrumentedImageManagerService) ImageFsInfo(ctx context.Context) (*run return fsInfo, nil } -func (in instrumentedImageManagerService) Close() error { +func (in instrumentedImageManagerService) Close(ctx context.Context) error { const operation = "close" defer recordOperation(operation, time.Now()) - err := in.service.Close() + err := in.service.Close(ctx) recordError(operation, err) return err } @@ -389,11 +389,11 @@ func (in instrumentedRuntimeService) RuntimeConfig(ctx context.Context) (*runtim return out, err } -func (in instrumentedRuntimeService) Close() error { +func (in instrumentedRuntimeService) Close(ctx context.Context) error { const operation = "close" defer recordOperation(operation, time.Now()) - err := in.service.Close() + err := in.service.Close(ctx) recordError(operation, err) return err } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container.go b/pkg/kubelet/kuberuntime/kuberuntime_container.go index 88b34a14ea67a..39d178a900054 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container.go @@ -34,14 +34,12 @@ import ( "time" codes "google.golang.org/grpc/codes" + crierror "k8s.io/cri-api/pkg/errors" "github.com/opencontainers/selinux/go-selinux" grpcstatus "google.golang.org/grpc/status" - "github.com/armon/circbuf" - "k8s.io/klog/v2" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubetypes "k8s.io/apimachinery/pkg/types" @@ -50,6 +48,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" remote "k8s.io/cri-client/pkg" + "k8s.io/klog/v2" kubelettypes "k8s.io/kubelet/pkg/types" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/features" @@ -63,6 +62,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/format" "k8s.io/kubernetes/pkg/util/tail" volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/utils/buffer" ) var ( @@ -100,7 +100,7 @@ func (m *kubeGenericRuntimeManager) recordContainerEvent(ctx context.Context, po if containerID != "" { eventMessage = strings.Replace(eventMessage, containerID, container.Name, -1) } - m.recorder.Event(ref, eventType, reason, eventMessage) + m.recorder.WithLogger(logger).Event(ref, eventType, reason, eventMessage) } // startSpec wraps the spec required to start a container, either a regular/init container @@ -400,7 +400,7 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(ctx context.Context, e := opts.Envs[idx] envs[idx] = &runtimeapi.KeyValue{ Key: e.Name, - Value: e.Value, + Value: []byte(e.Value), } } config.Envs = envs @@ -438,7 +438,12 @@ func (m *kubeGenericRuntimeManager) updatePodSandboxResources(ctx context.Contex _, err := m.runtimeService.UpdatePodSandboxResources(ctx, podResourcesRequest) if err != nil { stat, _ := grpcstatus.FromError(err) - if stat.Code() == codes.Unimplemented { + // Containerd 2.2 returns an error with code 'Unknown' and message 'not implemented yet' instead of + // an error with code 'Unimplemented'. + // This is being fixed in https://github.com/containerd/containerd/pull/13023, so this hardcoded + // string check can be removed once containerd 2.2 is no longer supported. + const unimplementedMsg = "not implemented" + if stat.Code() == codes.Unimplemented || (stat.Code() == codes.Unknown && strings.Contains(stat.Message(), unimplementedMsg)) { logger.V(3).Info("updatePodSandboxResources failed: unimplemented; this call is best-effort: proceeding with resize", "sandboxID", sandboxID) return nil } @@ -535,27 +540,6 @@ func (m *kubeGenericRuntimeManager) makeMounts(opts *kubecontainer.RunContainerO return volumeMounts } -// getKubeletContainers lists containers managed by kubelet. -// The boolean parameter specifies whether returns all containers including -// those already exited and dead containers (used for garbage collection). -func (m *kubeGenericRuntimeManager) getKubeletContainers(ctx context.Context, allContainers bool) ([]*runtimeapi.Container, error) { - logger := klog.FromContext(ctx) - filter := &runtimeapi.ContainerFilter{} - if !allContainers { - filter.State = &runtimeapi.ContainerStateValue{ - State: runtimeapi.ContainerState_CONTAINER_RUNNING, - } - } - - containers, err := m.runtimeService.ListContainers(ctx, filter) - if err != nil { - logger.Error(err, "ListContainers failed") - return nil, err - } - - return containers, nil -} - // makeUID returns a randomly generated string. func makeUID() string { return fmt.Sprintf("%08x", rand.Uint32()) @@ -590,11 +574,11 @@ func getTerminationMessage(status *runtimeapi.ContainerStatus, terminationMessag // by path. It reads up to max log lines. func (m *kubeGenericRuntimeManager) readLastStringFromContainerLogs(ctx context.Context, path string) string { value := int64(kubecontainer.MaxContainerTerminationMessageLogLines) - buf, _ := circbuf.NewBuffer(kubecontainer.MaxContainerTerminationMessageLogLength) + buf, _ := buffer.NewTypedRingFixed[byte](kubecontainer.MaxContainerTerminationMessageLogLength) if err := m.ReadLogs(ctx, path, "", &v1.PodLogOptions{TailLines: &value}, buf, buf); err != nil { return fmt.Sprintf("Error on reading termination message from logs: %v", err) } - return buf.String() + return string(buf.Slice()) } func (m *kubeGenericRuntimeManager) convertToKubeContainerStatus(ctx context.Context, podUID kubetypes.UID, status *runtimeapi.ContainerStatus) (cStatus *kubecontainer.Status) { @@ -620,25 +604,60 @@ func (m *kubeGenericRuntimeManager) convertToKubeContainerStatus(ctx context.Con return cStatus } +type listOptions struct { + // podUID of the pod to filter to. + // Optional. + podUID kubetypes.UID + // onlyRunningReady filters the results to only ready sandboxes or running containers. + // Optional. + onlyRunningReady bool +} + +func (o *listOptions) containerFilter() *runtimeapi.ContainerFilter { + filter := &runtimeapi.ContainerFilter{} + if o.podUID != "" { + filter.LabelSelector = map[string]string{kubelettypes.KubernetesPodUIDLabel: string(o.podUID)} + } + if o.onlyRunningReady { + filter.State = &runtimeapi.ContainerStateValue{ + State: runtimeapi.ContainerState_CONTAINER_RUNNING, + } + } + return filter +} + +func (o *listOptions) sandboxFilter() *runtimeapi.PodSandboxFilter { + filter := &runtimeapi.PodSandboxFilter{} + if o.podUID != "" { + filter.LabelSelector = map[string]string{kubelettypes.KubernetesPodUIDLabel: string(o.podUID)} + } + if o.onlyRunningReady { + filter.State = &runtimeapi.PodSandboxStateValue{ + State: runtimeapi.PodSandboxState_SANDBOX_READY, + } + } + return filter +} + +func (m *kubeGenericRuntimeManager) getContainers(ctx context.Context, opts listOptions) ([]*runtimeapi.Container, error) { + return m.runtimeService.ListContainers(ctx, opts.containerFilter()) +} + +func (m *kubeGenericRuntimeManager) getSandboxes(ctx context.Context, opts listOptions) ([]*runtimeapi.PodSandbox, error) { + return m.runtimeService.ListPodSandbox(ctx, opts.sandboxFilter()) +} + // getPodContainerStatuses gets all containers' statuses for the pod. -func (m *kubeGenericRuntimeManager) getPodContainerStatuses(ctx context.Context, uid kubetypes.UID, name, namespace, activePodSandboxID string) ([]*kubecontainer.Status, []*kubecontainer.Status, error) { +func (m *kubeGenericRuntimeManager) getPodContainerStatuses(ctx context.Context, pod *kubecontainer.Pod, activePodSandboxID string) ([]*kubecontainer.Status, []*kubecontainer.Status, error) { logger := klog.FromContext(ctx) - // Select all containers of the given pod. - containers, err := m.runtimeService.ListContainers(ctx, &runtimeapi.ContainerFilter{ - LabelSelector: map[string]string{kubelettypes.KubernetesPodUIDLabel: string(uid)}, - }) - if err != nil { - logger.Error(err, "ListContainers error") - return nil, nil, err - } isManagedPod := managed.IsManagedPodFromRuntimeService(ctx, m.runtimeService, activePodSandboxID) statuses := []*kubecontainer.Status{} activeContainerStatuses := []*kubecontainer.Status{} // TODO: optimization: set maximum number of containers per container name to examine. - for _, c := range containers { - resp, err := m.runtimeService.ContainerStatus(ctx, c.Id, false) + for _, c := range pod.Containers { + resp, err := m.runtimeService.ContainerStatus(ctx, c.ID.ID, false) // Between List (ListContainers) and check (ContainerStatus) another thread might remove a container, and that is normal. // The previous call (ListContainers) never fails due to a pod container not existing. // Therefore, this method should not either, but instead act as if the previous call failed, @@ -648,19 +667,19 @@ func (m *kubeGenericRuntimeManager) getPodContainerStatuses(ctx context.Context, } if err != nil { // Merely log this here; GetPodStatus will actually report the error out. - logger.V(4).Info("ContainerStatus return error", "containerID", c.Id, "err", err) + logger.V(4).Info("ContainerStatus return error", "containerID", c.ID.ID, "err", err) return nil, nil, err } status := resp.GetStatus() if status == nil { return nil, nil, remote.ErrContainerStatusNil } - cStatus := m.convertToKubeContainerStatus(ctx, uid, status) + cStatus := m.convertToKubeContainerStatus(ctx, pod.ID, status) if isManagedPod && cStatus.Resources != nil { // Clear CPU resources for managed pods (workload-pinned) cStatus.Resources.CPURequest, cStatus.Resources.CPULimit = nil, nil } statuses = append(statuses, cStatus) - if c.PodSandboxId == activePodSandboxID { + if c.PodSandboxID == activePodSandboxID { activeContainerStatuses = append(activeContainerStatuses, cStatus) } } @@ -773,7 +792,7 @@ func (m *kubeGenericRuntimeManager) executePreStopHook(ctx context.Context, pod done := make(chan struct{}) go func() { defer close(done) - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) if _, err := m.runner.Run(ctx, containerID, pod, containerSpec, containerSpec.Lifecycle.PreStop); err != nil { logger.Error(err, "PreStop hook failed", "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", containerSpec.Name, "containerID", containerID.String()) @@ -929,7 +948,7 @@ func (m *kubeGenericRuntimeManager) killContainersWithSyncResult(ctx context.Con } for _, container := range runningPod.Containers { go func(container *kubecontainer.Container) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer wg.Done() killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, container.Name) @@ -981,7 +1000,7 @@ func (m *kubeGenericRuntimeManager) pruneInitContainersBeforeStart(ctx context.C } // prune all other init containers that match this container name logger.V(4).Info("Removing init container", "containerName", status.Name, "containerID", status.ID.ID, "count", count) - if err := m.removeContainer(ctx, status.ID.ID); err != nil { + if err := m.removeContainer(ctx, status.ID.ID, false); err != nil { utilruntime.HandleError(fmt.Errorf("failed to remove pod init container %q: %v; Skipping pod %q", status.Name, err, format.Pod(pod))) continue } @@ -1007,7 +1026,7 @@ func (m *kubeGenericRuntimeManager) purgeInitContainers(ctx context.Context, pod count++ // Purge all init containers that match this container name logger.V(4).Info("Removing init container", "containerName", status.Name, "containerID", status.ID.ID, "count", count) - if err := m.removeContainer(ctx, status.ID.ID); err != nil { + if err := m.removeContainer(ctx, status.ID.ID, false); err != nil { utilruntime.HandleError(fmt.Errorf("failed to remove pod init container %q: %v; Skipping pod %q", status.Name, err, format.Pod(pod))) continue } @@ -1015,9 +1034,9 @@ func (m *kubeGenericRuntimeManager) purgeInitContainers(ctx context.Context, pod } } -// hasAnyRegularContainerCreated returns true if any regular container has been +// HasAnyRegularContainerCreated returns true if any regular container has been // created, which indicates all init containers have been initialized. -func hasAnyRegularContainerCreated(pod *v1.Pod, podStatus *kubecontainer.PodStatus) bool { +func HasAnyRegularContainerCreated(pod *v1.Pod, podStatus *kubecontainer.PodStatus) bool { for _, container := range pod.Spec.Containers { status := podStatus.FindContainerStatusByName(container.Name) if status == nil { @@ -1127,6 +1146,10 @@ func (m *kubeGenericRuntimeManager) computeInitContainerActions(ctx context.Cont case kubecontainer.ContainerStateRunning: if !podutil.IsRestartableInitContainer(container) { + if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScalingInitContainers) { + // computePodResizeAction updates 'changes' if resize policy requires restarting this container + _ = m.computePodResizeAction(ctx, pod, i, true, status, changes) + } break } else { // If container is restartable if container.StartupProbe != nil { @@ -1354,13 +1377,14 @@ func (m *kubeGenericRuntimeManager) RunInContainer(ctx context.Context, id kubec return append(stdout, stderr...), err } -// removeContainer removes the container and the container logs. +// removeContainer removes the container and optionally the container logs. // Notice that we remove the container logs first, so that container will not be removed if // container logs are failed to be removed, and kubelet will retry this later. This guarantees -// that container logs to be removed with the container. +// that container logs to be removed with the container if specified. // Notice that we assume that the container should only be removed in non-running state, and // it will not write container logs anymore in that state. -func (m *kubeGenericRuntimeManager) removeContainer(ctx context.Context, containerID string) error { +// If the logs are kept, they will be preserved until the pod is removed and kubelet GC is triggered. +func (m *kubeGenericRuntimeManager) removeContainer(ctx context.Context, containerID string, keepLogs bool) error { logger := klog.FromContext(ctx) logger.V(4).Info("Removing container", "containerID", containerID) // Call internal container post-stop lifecycle hook. @@ -1370,8 +1394,10 @@ func (m *kubeGenericRuntimeManager) removeContainer(ctx context.Context, contain // Remove the container log. // TODO: Separate log and container lifecycle management. - if err := m.removeContainerLog(ctx, containerID); err != nil { - return err + if !keepLogs { + if err := m.removeContainerLog(ctx, containerID); err != nil { + return err + } } // Remove the container. return m.runtimeService.RemoveContainer(ctx, containerID) @@ -1407,7 +1433,7 @@ func (m *kubeGenericRuntimeManager) removeContainerLog(ctx context.Context, cont // DeleteContainer removes a container. func (m *kubeGenericRuntimeManager) DeleteContainer(ctx context.Context, containerID kubecontainer.ContainerID) error { - return m.removeContainer(ctx, containerID.ID) + return m.removeContainer(ctx, containerID.ID, false) } // setTerminationGracePeriod determines the grace period to use when killing a container diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go index 3a09289b4ca38..b103e840fd193 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -43,6 +42,7 @@ import ( v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" kubeapiqos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" + kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/qos" @@ -79,7 +79,7 @@ func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(ctx con // generateLinuxContainerConfig generates linux container config for kubelet runtime v1. func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(ctx context.Context, container *v1.Container, pod *v1.Pod, uid *int64, username string, nsTarget *kubecontainer.ContainerID, enforceMemoryQoS bool) (*runtimeapi.LinuxContainerConfig, error) { - sc, err := m.determineEffectiveSecurityContext(pod, container, uid, username) + sc, err := m.determineEffectiveSecurityContext(ctx, pod, container, uid, username) if err != nil { return nil, err } @@ -155,8 +155,17 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerResources(ctx context. unified := map[string]string{} memoryRequest := container.Resources.Requests.Memory().Value() memoryLimit := container.Resources.Limits.Memory().Value() - if memoryRequest != 0 { - unified[cm.Cgroup2MemoryMin] = strconv.FormatInt(memoryRequest, 10) + if memoryRequest != 0 && m.memoryReservationPolicy == kubeletconfiginternal.TieredReservationMemoryReservationPolicy { + // Guaranteed pods get memory.min (hard protection). + // Burstable pods get memory.low (soft protection). + if kubeapiqos.GetPodQOS(pod) == v1.PodQOSGuaranteed { + unified[cm.Cgroup2MemoryMin] = strconv.FormatInt(memoryRequest, 10) + } else { + unified[cm.Cgroup2MemoryLow] = strconv.FormatInt(memoryRequest, 10) + } + } else { + unified[cm.Cgroup2MemoryMin] = "0" + unified[cm.Cgroup2MemoryLow] = "0" } // Guaranteed pods by their QoS definition requires that memory request equals memory limit and cpu request must equal cpu limit. @@ -387,13 +396,13 @@ func toKubeContainerResources(statusResources *runtimeapi.ContainerResources) *k if runtimeStatusResources != nil { var cpuLimit, memLimit, cpuRequest *resource.Quantity if runtimeStatusResources.CpuPeriod > 0 { - milliCPU := quotaToMilliCPU(runtimeStatusResources.CpuQuota, runtimeStatusResources.CpuPeriod) + milliCPU := cm.QuotaToMilliCPU(runtimeStatusResources.CpuQuota, runtimeStatusResources.CpuPeriod) if milliCPU > 0 { cpuLimit = resource.NewMilliQuantity(milliCPU, resource.DecimalSI) } } if runtimeStatusResources.CpuShares > 0 { - milliCPU := sharesToMilliCPU(runtimeStatusResources.CpuShares) + milliCPU := cm.SharesToMilliCPU(runtimeStatusResources.CpuShares) if milliCPU > 0 { cpuRequest = resource.NewMilliQuantity(milliCPU, resource.DecimalSI) } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go index 8162dd821632c..f28e487aecd9d 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. @@ -29,24 +28,25 @@ import ( "testing" "time" - "k8s.io/kubernetes/pkg/kubelet/cm" - "k8s.io/kubernetes/pkg/kubelet/types" - libcontainercgroups "github.com/opencontainers/cgroups" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/features" + kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" + "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/utils/cpuset" "k8s.io/utils/ptr" ) @@ -178,6 +178,8 @@ func TestGenerateLinuxContainerConfigResources(t *testing.T) { name string containerResources v1.ResourceRequirements podResources *v1.ResourceRequirements + cpuSets map[string]cpuset.CPUSet + enableFeatures map[featuregate.Feature]bool expected *runtimeapi.LinuxContainerResources }{ { @@ -228,6 +230,9 @@ func TestGenerateLinuxContainerConfigResources(t *testing.T) { v1.ResourceCPU: resource.MustParse("3"), }, }, + enableFeatures: map[featuregate.Feature]bool{ + features.PodLevelResources: true, + }, expected: &runtimeapi.LinuxContainerResources{ CpuPeriod: 100000, CpuQuota: 300000, @@ -235,40 +240,168 @@ func TestGenerateLinuxContainerConfigResources(t *testing.T) { MemoryLimitInBytes: 256 * 1024 * 1024, }, }, + // The implementation of ContainerManager (containerManagerImpl) and its internal + // cpuManager field are not accessible from outside the cm package. Not possible + // to create a real containerManagerImpl and inject our mock CPU sets into it, + // so we are not able to test the disabled quota (-1). Only quota enabled tests were added. + { + name: "No exclusive CPUs, DisableCPUQuotaWithExclusiveCPUs feature gate enabled", + containerResources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + }, + cpuSets: map[string]cpuset.CPUSet{}, + enableFeatures: map[featuregate.Feature]bool{ + features.DisableCPUQuotaWithExclusiveCPUs: true, + }, + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 100000, + CpuShares: 1024, + }, + }, + { + name: "Exclusive CPUs, DisableCPUQuotaWithExclusiveCPUs feature gate disabled", + containerResources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + }, + cpuSets: map[string]cpuset.CPUSet{"12345678/foo": cpuset.New(1)}, + enableFeatures: map[featuregate.Feature]bool{ + features.DisableCPUQuotaWithExclusiveCPUs: false, + }, + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 100000, + CpuShares: 1024, + }, + }, + { + name: "PodLevelResourceManagers enabled, Exclusive CPUs, Guaranteed container, non integer CPUs", + podResources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + }, + containerResources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("500m")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("500m")}, + }, + cpuSets: map[string]cpuset.CPUSet{}, + enableFeatures: map[featuregate.Feature]bool{ + features.PodLevelResources: true, + features.PodLevelResourceManagers: true, + features.DisableCPUQuotaWithExclusiveCPUs: true, + }, + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 50000, + CpuShares: 512, + }, + }, + { + name: "PodLevelResourceManagers enabled, Exclusive CPUs, non-Guaranteed container", + podResources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + }, + containerResources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + }, + cpuSets: map[string]cpuset.CPUSet{}, + enableFeatures: map[featuregate.Feature]bool{ + features.PodLevelResources: true, + features.PodLevelResourceManagers: true, + features.DisableCPUQuotaWithExclusiveCPUs: true, + }, + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 200000, + CpuShares: 1024, + }, + }, + { + name: "PodLevelResourceManagers disabled, Exclusive CPUs, Guaranteed container", + podResources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + }, + containerResources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1"), v1.ResourceMemory: resource.MustParse("100Mi")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1"), v1.ResourceMemory: resource.MustParse("100Mi")}, + }, + cpuSets: map[string]cpuset.CPUSet{}, + enableFeatures: map[featuregate.Feature]bool{ + features.PodLevelResources: true, + features.PodLevelResourceManagers: false, + features.DisableCPUQuotaWithExclusiveCPUs: true, + }, + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 100000, + CpuShares: 1024, + MemoryLimitInBytes: 100 * 1024 * 1024, + }, + }, + { + name: "PodLevelResourceManagers disabled, Exclusive CPUs, Non Guaranteed container", + podResources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + Limits: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, + }, + containerResources: v1.ResourceRequirements{}, + cpuSets: map[string]cpuset.CPUSet{}, + enableFeatures: map[featuregate.Feature]bool{ + features.PodLevelResources: true, + features.PodLevelResourceManagers: false, + features.DisableCPUQuotaWithExclusiveCPUs: true, + }, + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 200000, + CpuShares: 2048, + }, + }, } for _, test := range tests { - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - UID: "12345678", - Name: "bar", - Namespace: "new", - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "foo", - Image: "busybox", - ImagePullPolicy: v1.PullIfNotPresent, - Command: []string{"testCommand"}, - WorkingDir: "testWorkingDir", - Resources: test.containerResources, + t.Run(test.name, func(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "new", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + ImagePullPolicy: v1.PullIfNotPresent, + Command: []string{"testCommand"}, + WorkingDir: "testWorkingDir", + Resources: test.containerResources, + }, }, }, - }, - } + } - if test.podResources != nil { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true) - pod.Spec.Resources = test.podResources - } + for feature, enabled := range test.enableFeatures { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, enabled) - linuxConfig, err := m.generateLinuxContainerConfig(tCtx, &pod.Spec.Containers[0], pod, new(int64), "", nil, false) - assert.NoError(t, err) - assert.Equal(t, test.expected.CpuPeriod, linuxConfig.GetResources().CpuPeriod, test.name) - assert.Equal(t, test.expected.CpuQuota, linuxConfig.GetResources().CpuQuota, test.name) - assert.Equal(t, test.expected.CpuShares, linuxConfig.GetResources().CpuShares, test.name) - assert.Equal(t, test.expected.MemoryLimitInBytes, linuxConfig.GetResources().MemoryLimitInBytes, test.name) + if feature == features.PodLevelResources && test.podResources != nil { + pod.Spec.Resources = test.podResources + } + } + + linuxConfig, err := m.generateLinuxContainerConfig(tCtx, &pod.Spec.Containers[0], pod, new(int64), "", nil, false) + require.NoError(t, err) + + assert.Equal(t, test.expected.CpuPeriod, linuxConfig.GetResources().CpuPeriod, test.name) + assert.Equal(t, test.expected.CpuQuota, linuxConfig.GetResources().CpuQuota, test.name) + assert.Equal(t, test.expected.CpuShares, linuxConfig.GetResources().CpuShares, test.name) + assert.Equal(t, test.expected.MemoryLimitInBytes, linuxConfig.GetResources().MemoryLimitInBytes, test.name) + }) } } @@ -426,6 +559,7 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { tCtx := ktesting.Init(t) _, _, m, err := createTestRuntimeManager(tCtx) assert.NoError(t, err) + m.memoryReservationPolicy = kubeletconfiginternal.TieredReservationMemoryReservationPolicy podRequestMemory := resource.MustParse("128Mi") pod1LimitMemory := resource.MustParse("256Mi") @@ -524,11 +658,46 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { linuxConfig, err := m.generateLinuxContainerConfig(tCtx, &test.pod.Spec.Containers[0], test.pod, new(int64), "", nil, true) assert.NoError(t, err) assert.Equal(t, test.expected.containerConfig, linuxConfig, test.name) - assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.min"], strconv.FormatInt(test.expected.memoryLow, 10), test.name) + assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.low"], strconv.FormatInt(test.expected.memoryLow, 10), test.name) assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.high"], strconv.FormatInt(test.expected.memoryHigh, 10), test.name) } } +func TestGenerateContainerConfigMemoryQoSPolicyNone(t *testing.T) { + tCtx := ktesting.Init(t) + _, _, m, err := createTestRuntimeManager(tCtx) + require.NoError(t, err) + m.memoryReservationPolicy = kubeletconfiginternal.NoneMemoryReservationPolicy + + podRequestMemory := resource.MustParse("128Mi") + podLimitMemory := resource.MustParse("256Mi") + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "new", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + ImagePullPolicy: v1.PullIfNotPresent, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceMemory: podRequestMemory}, + Limits: v1.ResourceList{v1.ResourceMemory: podLimitMemory}, + }, + }, + }, + }, + } + + linuxConfig, err := m.generateLinuxContainerConfig(tCtx, &pod.Spec.Containers[0], pod, new(int64), "", nil, true) + require.NoError(t, err) + assert.Equal(t, "0", linuxConfig.GetResources().GetUnified()["memory.min"]) + assert.NotEmpty(t, linuxConfig.GetResources().GetUnified()["memory.high"]) +} + func TestGetHugepageLimitsFromResources(t *testing.T) { tCtx := ktesting.Init(t) var baseHugepage []*runtimeapi.HugepageLimit @@ -1880,7 +2049,7 @@ func TestGenerateUpdatePodSandboxResourcesRequest(t *testing.T) { expectedLcr := m.calculateSandboxResources(tCtx, tc.pod) expectedLcrOverhead := m.convertOverheadToLinuxResources(tc.pod) - podResourcesCfg := cm.ResourceConfigForPod(tc.pod, tc.enforceCPULimits, uint64((m.cpuCFSQuotaPeriod.Duration)/time.Microsecond), false) + podResourcesCfg := cm.ResourceConfigForPod(tc.pod, tc.enforceCPULimits, uint64((m.cpuCFSQuotaPeriod.Duration)/time.Microsecond), false, kubeletconfiginternal.NoneMemoryReservationPolicy) assert.NotNil(t, podResourcesCfg, "podResourcesCfg is expected to be not nil") if podResourcesCfg.CPUPeriod == nil { @@ -1918,10 +2087,12 @@ func TestUpdatePodSandboxResources(t *testing.T) { } // Create fake sandbox and container - fakeSandbox, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) + fakeSandbox, fakeContainers := makeAndSetFakePod(tCtx, m, fakeRuntime, pod) assert.Len(t, fakeContainers, 1) - _, _, err := m.getPodContainerStatuses(tCtx, pod.UID, pod.Name, pod.Namespace, "") + runtimePod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + _, _, err = m.getPodContainerStatuses(tCtx, runtimePod, "") require.NoError(t, err) resourceConfig := &cm.ResourceConfig{} diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_test.go index ed0f5b534b1fc..5964715efe60c 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_test.go @@ -46,6 +46,7 @@ import ( kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/lifecycle" + "k8s.io/kubernetes/pkg/kubelet/logs" ) // TestRemoveContainer tests removing the container and its corresponding container logs. @@ -53,6 +54,11 @@ func TestRemoveContainer(t *testing.T) { tCtx := ktesting.Init(t) fakeRuntime, _, m, err := createTestRuntimeManager(tCtx) require.NoError(t, err) + // Swap in real ContainerLogManager for the stub. + logManager, err := logs.NewContainerLogManager(m.runtimeService, m.osInterface, "1", 2, 10, metav1.Duration{Duration: 10 * time.Second}) + require.NoError(t, err) + m.logManager = logManager + pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ UID: "12345678", @@ -71,7 +77,7 @@ func TestRemoveContainer(t *testing.T) { } // Create fake sandbox and container - _, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) + _, fakeContainers := makeAndSetFakePod(tCtx, m, fakeRuntime, pod) assert.Len(t, fakeContainers, 1) containerID := fakeContainers[0].Id @@ -89,7 +95,7 @@ func TestRemoveContainer(t *testing.T) { fakeOS.Create(expectedContainerLogPath) fakeOS.Create(expectedContainerLogPathRotated) - err = m.removeContainer(tCtx, containerID) + err = m.removeContainer(tCtx, containerID, false) assert.NoError(t, err) // Verify container log is removed. @@ -104,6 +110,65 @@ func TestRemoveContainer(t *testing.T) { assert.Empty(t, containers) } +func TestRemoveContainer_keepLogs(t *testing.T) { + tCtx := ktesting.Init(t) + fakeRuntime, _, m, err := createTestRuntimeManager(tCtx) + require.NoError(t, err) + // Swap in real ContainerLogManager for the stub. + logManager, err := logs.NewContainerLogManager(m.runtimeService, m.osInterface, "1", 2, 10, metav1.Duration{Duration: 10 * time.Second}) + require.NoError(t, err) + m.logManager = logManager + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "new", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "busybox", + ImagePullPolicy: v1.PullIfNotPresent, + }, + }, + }, + } + + // Create fake sandbox and container + _, fakeContainers := makeAndSetFakePod(tCtx, m, fakeRuntime, pod) + assert.Len(t, fakeContainers, 1) + + containerID := fakeContainers[0].Id + fakeOS := m.osInterface.(*containertest.FakeOS) + fakeOS.GlobFn = func(pattern, path string) bool { + pattern = strings.ReplaceAll(pattern, "*", ".*") + pattern = strings.ReplaceAll(pattern, "\\", "\\\\") + return regexp.MustCompile(pattern).MatchString(path) + } + podLogsDirectory := "/var/log/pods" + expectedContainerLogPath := filepath.Join(podLogsDirectory, "new_bar_12345678", "foo", "0.log") + expectedContainerLogPathRotated := filepath.Join(podLogsDirectory, "new_bar_12345678", "foo", "0.log.20060102-150405") + + _, err = fakeOS.Create(expectedContainerLogPath) + require.NoError(t, err) + _, err = fakeOS.Create(expectedContainerLogPathRotated) + require.NoError(t, err) + + err = m.removeContainer(tCtx, containerID, true) + require.NoError(t, err) + + // Verify container logs are kept. + // We could not predict the order of `fakeOS.Removes`, so we use `assert.ElementsMatch` here. + require.Empty(t, fakeOS.Removes) + // Verify container is removed + require.Contains(t, fakeRuntime.Called, "RemoveContainer") + containers, err := fakeRuntime.ListContainers(tCtx, &runtimeapi.ContainerFilter{Id: containerID}) + require.NoError(t, err) + require.Empty(t, containers) +} + // TestKillContainer tests killing the container in a Pod. func TestKillContainer(t *testing.T) { tCtx := ktesting.Init(t) @@ -563,16 +628,21 @@ func testLifeCycleHook(t *testing.T, testPod *v1.Pod, testContainer *v1.Containe fakeRunner := &containertest.FakeContainerCommandRunner{} fakeHTTP := &fakeHTTP{} - fakePodStatusProvider := podStatusProviderFunc(func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { - return &kubecontainer.PodStatus{ - ID: uid, - Name: name, - Namespace: namespace, + fakePodStatusProvider := fakePodStatusProvider{ + pod: &kubecontainer.Pod{ + ID: testPod.UID, + Name: testPod.Name, + Namespace: testPod.Namespace, + }, + status: &kubecontainer.PodStatus{ + ID: testPod.UID, + Name: testPod.Name, + Namespace: testPod.Namespace, IPs: []string{ "127.0.0.1", }, - }, nil - }) + }, + } lcHanlder := lifecycle.NewHandlerRunner( fakeHTTP, @@ -624,7 +694,7 @@ func testLifeCycleHook(t *testing.T, testPod *v1.Pod, testContainer *v1.Containe t.Run("PostStart-CmdExe", func(t *testing.T) { tCtx := ktesting.Init(t) // Fake all the things you need before trying to create a container - fakeSandBox, _ := makeAndSetFakePod(t, m, fakeRuntime, testPod) + fakeSandBox, _ := makeAndSetFakePod(tCtx, m, fakeRuntime, testPod) fakeSandBoxConfig, _ := m.generatePodSandboxConfig(tCtx, testPod, 0) testContainer.Lifecycle = cmdPostStart fakePodStatus := &kubecontainer.PodStatus{ @@ -1029,11 +1099,13 @@ func TestUpdateContainerResources(t *testing.T) { } // Create fake sandbox and container - _, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) + _, fakeContainers := makeAndSetFakePod(tCtx, m, fakeRuntime, pod) assert.Len(t, fakeContainers, 1) - cStatus, _, err := m.getPodContainerStatuses(tCtx, pod.UID, pod.Name, pod.Namespace, "") - assert.NoError(t, err) + runtimePod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + cStatus, _, err := m.getPodContainerStatuses(tCtx, runtimePod, "") + require.NoError(t, err) containerID := cStatus[0].ID err = m.updateContainerResources(tCtx, pod, &pod.Spec.Containers[0], containerID) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_unsupported.go b/pkg/kubelet/kuberuntime/kuberuntime_container_unsupported.go index 2d388411016da..9b4bb31a51c46 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_unsupported.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_windows.go b/pkg/kubelet/kuberuntime/kuberuntime_container_windows.go index b58fdb7a35ae3..05ae4afd60497 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_windows.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_windows_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_windows_test.go index 46dd01277b1db..92e00f4443d0e 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_windows_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/kuberuntime_gc.go b/pkg/kubelet/kuberuntime/kuberuntime_gc.go index 2691f64d5c378..0bfb961eee4b6 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_gc.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_gc.go @@ -147,7 +147,7 @@ func (cgc *containerGC) removeOldestN(ctx context.Context, containers []containe continue } } - if err := cgc.manager.removeContainer(ctx, containers[i].id); err != nil { + if err := cgc.manager.removeContainer(ctx, containers[i].id, false); err != nil { logger.Error(err, "Failed to remove container", "containerID", containers[i].id) } } @@ -190,7 +190,7 @@ func (cgc *containerGC) removeSandbox(ctx context.Context, sandboxID string) { // evictableContainers gets all containers that are evictable. Evictable containers are: not running // and created more than MinAge ago. func (cgc *containerGC) evictableContainers(ctx context.Context, minAge time.Duration) (containersByEvictUnit, error) { - containers, err := cgc.manager.getKubeletContainers(ctx, true) + containers, err := cgc.manager.getContainers(ctx, listOptions{}) if err != nil { return containersByEvictUnit{}, err } @@ -279,12 +279,12 @@ func (cgc *containerGC) evictContainers(ctx context.Context, gcPolicy kubecontai // 3. belong to a non-existent (i.e., already removed) pod, or is not the // most recently created sandbox for the pod. func (cgc *containerGC) evictSandboxes(ctx context.Context, evictNonDeletedPods bool) error { - containers, err := cgc.manager.getKubeletContainers(ctx, true) + containers, err := cgc.manager.getContainers(ctx, listOptions{}) if err != nil { return err } - sandboxes, err := cgc.manager.getKubeletSandboxes(ctx, true) + sandboxes, err := cgc.manager.getSandboxes(ctx, listOptions{}) if err != nil { return err } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_gc_test.go b/pkg/kubelet/kuberuntime/kuberuntime_gc_test.go index 509c7260bbb83..a531d7cd72d34 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_gc_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_gc_test.go @@ -164,8 +164,8 @@ func TestSandboxGC(t *testing.T) { tCtx := ktesting.Init(t) podStateProvider.removed = make(map[types.UID]struct{}) podStateProvider.terminated = make(map[types.UID]struct{}) - fakeSandboxes := makeFakePodSandboxes(t, m, test.sandboxes) - fakeContainers := makeFakeContainers(t, m, test.containers) + fakeSandboxes := makeFakePodSandboxes(tCtx, m, test.sandboxes) + fakeContainers := makeFakeContainers(tCtx, m, test.containers) for _, s := range test.sandboxes { if !s.running && s.pod.Name == "deleted" { podStateProvider.removed[s.pod.UID] = struct{}{} @@ -393,7 +393,7 @@ func TestContainerGC(t *testing.T) { tCtx := ktesting.Init(t) podStateProvider.removed = make(map[types.UID]struct{}) podStateProvider.terminated = make(map[types.UID]struct{}) - fakeContainers := makeFakeContainers(t, m, test.containers) + fakeContainers := makeFakeContainers(tCtx, m, test.containers) for _, s := range test.containers { if s.pod.Name == "deleted" { podStateProvider.removed[s.pod.UID] = struct{}{} @@ -471,7 +471,7 @@ func TestUnknownStateContainerGC(t *testing.T) { // podStateProvider := m.containerGC.podStateProvider.(*fakePodStateProvider) defaultGCPolicy := kubecontainer.GCPolicy{MinAge: time.Hour, MaxPerPodContainer: 0, MaxContainers: 0} - fakeContainers := makeFakeContainers(t, m, []containerTemplate{ + fakeContainers := makeFakeContainers(tCtx, m, []containerTemplate{ makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_UNKNOWN), }) fakeRuntime.SetFakeContainers(fakeContainers) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_image.go b/pkg/kubelet/kuberuntime/kuberuntime_image.go index 3d9b94cc502bc..2eecc2eee618e 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_image.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_image.go @@ -33,7 +33,7 @@ import ( func (m *kubeGenericRuntimeManager) PullImage(ctx context.Context, image kubecontainer.ImageSpec, credentials []crededentialprovider.TrackedAuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, *crededentialprovider.TrackedAuthConfig, error) { logger := klog.FromContext(ctx) img := image.Image - imgSpec := toRuntimeAPIImageSpec(image) + imgSpec := ToRuntimeAPIImageSpec(image) if len(credentials) == 0 { logger.V(3).Info("Pulling image without credentials", "image", img) @@ -74,7 +74,7 @@ func (m *kubeGenericRuntimeManager) PullImage(ctx context.Context, image kubecon // the local storage. It returns ("", nil) if the image isn't in the local storage. func (m *kubeGenericRuntimeManager) GetImageRef(ctx context.Context, image kubecontainer.ImageSpec) (string, error) { logger := klog.FromContext(ctx) - resp, err := m.imageService.ImageStatus(ctx, toRuntimeAPIImageSpec(image), false) + resp, err := m.imageService.ImageStatus(ctx, ToRuntimeAPIImageSpec(image), false) if err != nil { logger.Error(err, "Failed to get image status", "image", image.Image) return "", err @@ -87,7 +87,7 @@ func (m *kubeGenericRuntimeManager) GetImageRef(ctx context.Context, image kubec func (m *kubeGenericRuntimeManager) GetImageSize(ctx context.Context, image kubecontainer.ImageSpec) (uint64, error) { logger := klog.FromContext(ctx) - resp, err := m.imageService.ImageStatus(ctx, toRuntimeAPIImageSpec(image), false) + resp, err := m.imageService.ImageStatus(ctx, ToRuntimeAPIImageSpec(image), false) if err != nil { logger.Error(err, "Failed to get image status", "image", image.Image) return 0, err @@ -126,7 +126,7 @@ func (m *kubeGenericRuntimeManager) ListImages(ctx context.Context) ([]kubeconta Size: int64(img.Size), RepoTags: img.RepoTags, RepoDigests: img.RepoDigests, - Spec: toKubeContainerImageSpec(img), + Spec: ToKubeContainerImageSpec(img), Pinned: img.Pinned, }) } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_image_test.go b/pkg/kubelet/kuberuntime/kuberuntime_image_test.go index 644dac912e726..a12456206f70f 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_image_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_image_test.go @@ -136,6 +136,37 @@ func TestGetImageRef(t *testing.T) { assert.Equal(t, image, imageRef) } +// TestGetImageRefReturnsImageIdNotRepoDigest verifies that GetImageRef returns +// Image.Id instead of RepoDigests to ensure content-based deduplication. +// This prevents the same image pulled from different registries from being +// treated as different images in pull record lookups. +func TestGetImageRefReturnsImageIdNotRepoDigest(t *testing.T) { + tCtx := ktesting.Init(t) + _, fakeImageService, fakeManager, err := createTestRuntimeManager(tCtx) + require.NoError(t, err) + + // Simulate an image with both Id and RepoDigests set + // In a real scenario, the same image content pulled from different registries + // would have the same Id but different RepoDigests + imageID := "sha256:abcd1234" + repoDigest1 := "registry1.io/myimage@sha256:abcd1234" + repoDigest2 := "registry2.io/myimage@sha256:abcd1234" + + fakeImageService.Lock() + fakeImageService.Images["myimage"] = &runtimeapi.Image{ + Id: imageID, + RepoDigests: []string{repoDigest1, repoDigest2}, + RepoTags: []string{"myimage:latest"}, + Size: 1000, + } + fakeImageService.Unlock() + + // GetImageRef should return the content-based Image.Id, not the location-based RepoDigest + imageRef, err := fakeManager.GetImageRef(tCtx, kubecontainer.ImageSpec{Image: "myimage"}) + require.NoError(t, err) + assert.Equal(t, imageID, imageRef, "GetImageRef should return Image.Id for content-based deduplication") +} + func TestImageSize(t *testing.T) { tCtx := ktesting.Init(t) _, fakeImageService, fakeManager, err := createTestRuntimeManager(tCtx) @@ -247,6 +278,7 @@ func TestImageStatsWithError(t *testing.T) { func TestPullWithSecrets(t *testing.T) { tCtx := ktesting.Init(t) + logger := tCtx.Logger() // auth value is equivalent to: "username":"passed-user","password":"passed-password" dockerCfg := map[string]map[string]string{"index.docker.io/v1/": {"email": "passed-email", "auth": "cGFzc2VkLXVzZXI6cGFzc2VkLXBhc3N3b3Jk"}} dockercfgContent, err := json.Marshal(dockerCfg) @@ -320,13 +352,14 @@ func TestPullWithSecrets(t *testing.T) { _, fakeImageService, fakeManager, err := createTestRuntimeManager(tCtx) require.NoError(t, err) - fsRecordAccessor, err := imagepullmanager.NewFSPullRecordsAccessor(t.TempDir()) + fsRecordAccessor, err := imagepullmanager.NewFSPullRecordsAccessor(logger, t.TempDir()) if err != nil { t.Fatal("failed to setup an file pull records accessor") } const intentsRecordsCacheSize, pulledRecordsCacheSize, stripedLocksSetSize = 50, 100, 10 memCacheRecordsAccessor := imagepullmanager.NewCachedPullRecordsAccessor( + logger, fsRecordAccessor, intentsRecordsCacheSize, pulledRecordsCacheSize, @@ -359,6 +392,7 @@ func TestPullWithSecrets(t *testing.T) { func TestPullWithSecretsWithError(t *testing.T) { tCtx := ktesting.Init(t) + logger := tCtx.Logger() dockerCfg := map[string]map[string]map[string]string{ "auths": { @@ -410,13 +444,14 @@ func TestPullWithSecretsWithError(t *testing.T) { fakeImageService.InjectError("PullImage", fmt.Errorf("test-error")) } - fsRecordAccessor, err := imagepullmanager.NewFSPullRecordsAccessor(t.TempDir()) + fsRecordAccessor, err := imagepullmanager.NewFSPullRecordsAccessor(logger, t.TempDir()) if err != nil { t.Fatal("failed to setup an file pull records accessor") } const intentsRecordsCacheSize, pulledRecordsCacheSize, stripedLocksSetSize = 50, 100, 10 memCacheRecordsAccessor := imagepullmanager.NewCachedPullRecordsAccessor( + logger, fsRecordAccessor, intentsRecordsCacheSize, pulledRecordsCacheSize, diff --git a/pkg/kubelet/kuberuntime/kuberuntime_logs.go b/pkg/kubelet/kuberuntime/kuberuntime_logs.go index 73d26df5c5a99..3123e6bc4bdac 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_logs.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_logs.go @@ -23,7 +23,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/cri-client/pkg/logs" - "k8s.io/klog/v2" ) // ReadLogs read the container log and redirect into stdout and stderr. @@ -31,7 +30,19 @@ import ( // just pass in empty string "". func (m *kubeGenericRuntimeManager) ReadLogs(ctx context.Context, path, containerID string, apiOpts *v1.PodLogOptions, stdout, stderr io.Writer) error { // Convert v1.PodLogOptions into internal log options. - opts := logs.NewLogOptions(apiOpts, time.Now()) - logger := klog.FromContext(ctx) - return logs.ReadLogs(ctx, &logger, path, containerID, opts, m.runtimeService, stdout, stderr) + now := time.Now() + opts := &logs.LogOptions{ + TailLines: apiOpts.TailLines, + LimitBytes: apiOpts.LimitBytes, + Follow: apiOpts.Follow, + Timestamp: apiOpts.Timestamps, + } + if apiOpts.SinceSeconds != nil { + opts.Since = now.Add(-time.Duration(*apiOpts.SinceSeconds) * time.Second) + } + if apiOpts.SinceTime != nil && apiOpts.SinceTime.After(opts.Since) { + opts.Since = apiOpts.SinceTime.Time + } + + return logs.ReadLogs(ctx, path, containerID, opts, m.runtimeService, stdout, stderr) } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_manager.go b/pkg/kubelet/kuberuntime/kuberuntime_manager.go index 818ca633a1a9c..faee7e9338785 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_manager.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_manager.go @@ -106,9 +106,14 @@ type podStateProvider interface { ShouldPodRuntimeBeRemoved(kubetypes.UID) bool } +type PodInitContainerTimeRecorder interface { + RecordInitContainerStarted(podUID kubetypes.UID, startedAt time.Time) + RecordInitContainerFinished(podUID kubetypes.UID, finishedAt time.Time) +} + type kubeGenericRuntimeManager struct { runtimeName string - recorder record.EventRecorder + recorder record.EventRecorderLogger osInterface kubecontainer.OSInterface // machineInfo contains the machine information. @@ -184,6 +189,8 @@ type kubeGenericRuntimeManager struct { // Memory throttling factor for MemoryQoS memoryThrottlingFactor float64 + // Memory reservation policy for MemoryQoS memory.min behavior + memoryReservationPolicy kubeletconfiginternal.MemoryReservationPolicy // Root directory used to store pod logs podLogsDirectory string @@ -191,6 +198,9 @@ type kubeGenericRuntimeManager struct { // Swap controller availability check function (Linux only) // Uses sync.OnceValue for lazy initialization getSwapControllerAvailable func() bool + + // Records first initContainer start time and last initContainer finish time + podInitContainerTimeRecorder PodInitContainerTimeRecorder } // KubeGenericRuntime is a interface contains interfaces for container runtime and command. @@ -203,7 +213,7 @@ type KubeGenericRuntime interface { // NewKubeGenericRuntimeManager creates a new kubeGenericRuntimeManager func NewKubeGenericRuntimeManager( ctx context.Context, - recorder record.EventRecorder, + recorder record.EventRecorderLogger, livenessManager proberesults.Manager, readinessManager proberesults.Manager, startupManager proberesults.Manager, @@ -236,10 +246,12 @@ func NewKubeGenericRuntimeManager( memorySwapBehavior string, getNodeAllocatable func() v1.ResourceList, memoryThrottlingFactor float64, + memoryReservationPolicy kubeletconfiginternal.MemoryReservationPolicy, podPullingTimeRecorder images.ImagePodPullingTimeRecorder, tracerProvider trace.TracerProvider, tokenManager *token.Manager, getServiceAccount plugin.GetServiceAccountFunc, + podInitContainerTimeRecorder PodInitContainerTimeRecorder, ) (KubeGenericRuntime, []images.PostImageGCHook, error) { logger := klog.FromContext(ctx) @@ -247,29 +259,31 @@ func NewKubeGenericRuntimeManager( imageService = newInstrumentedImageManagerService(imageService) tracer := tracerProvider.Tracer(instrumentationScope) kubeRuntimeManager := &kubeGenericRuntimeManager{ - recorder: recorder, - singleProcessOOMKill: singleProcessOOMKill, - cpuCFSQuota: cpuCFSQuota, - cpuCFSQuotaPeriod: cpuCFSQuotaPeriod, - seccompProfileRoot: filepath.Join(rootDirectory, "seccomp"), - livenessManager: livenessManager, - readinessManager: readinessManager, - startupManager: startupManager, - machineInfo: machineInfo, - osInterface: osInterface, - runtimeHelper: runtimeHelper, - runtimeService: runtimeService, - imageService: imageService, - containerManager: containerManager, - internalLifecycle: containerManager.InternalContainerLifecycle(), - logManager: logManager, - runtimeClassManager: runtimeClassManager, - logReduction: logreduction.NewLogReduction(identicalErrorDelay), - seccompDefault: seccompDefault, - memorySwapBehavior: memorySwapBehavior, - getNodeAllocatable: getNodeAllocatable, - memoryThrottlingFactor: memoryThrottlingFactor, - podLogsDirectory: podLogsDirectory, + recorder: recorder, + singleProcessOOMKill: singleProcessOOMKill, + cpuCFSQuota: cpuCFSQuota, + cpuCFSQuotaPeriod: cpuCFSQuotaPeriod, + seccompProfileRoot: filepath.Join(rootDirectory, "seccomp"), + livenessManager: livenessManager, + readinessManager: readinessManager, + startupManager: startupManager, + machineInfo: machineInfo, + osInterface: osInterface, + runtimeHelper: runtimeHelper, + runtimeService: runtimeService, + imageService: imageService, + containerManager: containerManager, + internalLifecycle: containerManager.InternalContainerLifecycle(), + logManager: logManager, + runtimeClassManager: runtimeClassManager, + logReduction: logreduction.NewLogReduction(identicalErrorDelay), + seccompDefault: seccompDefault, + memorySwapBehavior: memorySwapBehavior, + getNodeAllocatable: getNodeAllocatable, + memoryThrottlingFactor: memoryThrottlingFactor, + memoryReservationPolicy: memoryReservationPolicy, + podLogsDirectory: podLogsDirectory, + podInitContainerTimeRecorder: podInitContainerTimeRecorder, } // Initialize swap controller availability check with lazy evaluation @@ -314,7 +328,7 @@ func NewKubeGenericRuntimeManager( return nil, nil, err } - fsRecordAccessor, err := imagepullmanager.NewFSPullRecordsAccessor(rootDirectory) + fsRecordAccessor, err := imagepullmanager.NewFSPullRecordsAccessor(logger, rootDirectory) if err != nil { return nil, nil, fmt.Errorf("failed to setup the FSPullRecordsAccessor: %w", err) } @@ -325,7 +339,7 @@ func NewKubeGenericRuntimeManager( pullRecordsCacheSize = 5 * maxPods ) - memCacheRecordsAccessor := imagepullmanager.NewCachedPullRecordsAccessor(fsRecordAccessor, intentCacheSize, pullRecordsCacheSize, maxParallelPulls) + memCacheRecordsAccessor := imagepullmanager.NewCachedPullRecordsAccessor(logger, fsRecordAccessor, intentCacheSize, pullRecordsCacheSize, maxParallelPulls) imagePullManager, err = imagepullmanager.NewImagePullManager(ctx, memCacheRecordsAccessor, imagePullCredentialsVerificationPolicy, kubeRuntimeManager, maxParallelPulls) if err != nil { @@ -422,16 +436,58 @@ func (m *kubeGenericRuntimeManager) Status(ctx context.Context) (*kubecontainer. return toKubeRuntimeStatus(resp.GetStatus(), resp.GetRuntimeHandlers(), resp.GetFeatures()), nil } +// GetPod fetches the current sandboxes & containers for a pod. +func (m *kubeGenericRuntimeManager) GetPod(ctx context.Context, podUID kubetypes.UID) (*kubecontainer.Pod, error) { + pods, err := m.getPods(ctx, listOptions{podUID: podUID}) + if err != nil { + return nil, err + } + + pod := pods[podUID] + if pod == nil { + return nil, kubecontainer.ErrPodNotFound + } + return pod, nil +} + // GetPods returns a list of containers grouped by pods. The boolean parameter // specifies whether the runtime returns all containers including those already // exited and dead containers (used for garbage collection). func (m *kubeGenericRuntimeManager) GetPods(ctx context.Context, all bool) ([]*kubecontainer.Pod, error) { + logger := klog.FromContext(ctx) + pods, err := m.getPods(ctx, listOptions{onlyRunningReady: !all}) + if err != nil { + return nil, err + } + + // Convert map to list. + var result []*kubecontainer.Pod + for _, pod := range pods { + result = append(result, pod) + } + + // There are scenarios where multiple pods are running in parallel having + // the same name, because one of them have not been fully terminated yet. + // To avoid unexpected behavior on container name based search (for example + // by calling *Kubelet.findContainer() without specifying a pod ID), we now + // return the list of pods ordered by their creation time. + sort.SliceStable(result, func(i, j int) bool { + return result[i].CreatedAt > result[j].CreatedAt + }) + logger.V(4).Info("Retrieved pods from runtime", "all", all) + return result, nil +} + +func (m *kubeGenericRuntimeManager) getPods(ctx context.Context, opts listOptions) (map[kubetypes.UID]*kubecontainer.Pod, error) { logger := klog.FromContext(ctx) pods := make(map[kubetypes.UID]*kubecontainer.Pod) - sandboxes, err := m.getKubeletSandboxes(ctx, all) + timestamp := time.Now() + sandboxes, err := m.getSandboxes(ctx, opts) if err != nil { return nil, err } + // Sort sandboxes by creation time, newest first. + sort.Sort(podSandboxByCreatedThenID(sandboxes)) for i := range sandboxes { s := sandboxes[i] if s.Metadata == nil { @@ -444,6 +500,7 @@ func (m *kubeGenericRuntimeManager) GetPods(ctx context.Context, all bool) ([]*k ID: podUID, Name: s.Metadata.Name, Namespace: s.Metadata.Namespace, + Timestamp: timestamp, } } p := pods[podUID] @@ -456,10 +513,17 @@ func (m *kubeGenericRuntimeManager) GetPods(ctx context.Context, all bool) ([]*k p.CreatedAt = uint64(s.GetCreatedAt()) } - containers, err := m.getKubeletContainers(ctx, all) + containers, err := m.getContainers(ctx, opts) if err != nil { return nil, err } + // Sort containers: newest CreatedAt first, then by container ID for stability. + // There are scenarios where multiple pods are running in parallel having + // the same name, because one of them have not been fully terminated yet. + // To avoid unexpected behavior on container name based search (for example + // by calling *Kubelet.findContainer() without specifying a pod ID), we + // return the list of pods ordered by their creation time. + sort.Sort(containerByCreatedThenID(containers)) for i := range containers { c := containers[i] if c.Metadata == nil { @@ -474,6 +538,7 @@ func (m *kubeGenericRuntimeManager) GetPods(ctx context.Context, all bool) ([]*k ID: labelledInfo.PodUID, Name: labelledInfo.PodName, Namespace: labelledInfo.PodNamespace, + Timestamp: timestamp, } pods[labelledInfo.PodUID] = pod } @@ -487,22 +552,7 @@ func (m *kubeGenericRuntimeManager) GetPods(ctx context.Context, all bool) ([]*k pod.Containers = append(pod.Containers, converted) } - // Convert map to list. - var result []*kubecontainer.Pod - for _, pod := range pods { - result = append(result, pod) - } - - // There are scenarios where multiple pods are running in parallel having - // the same name, because one of them have not been fully terminated yet. - // To avoid unexpected behavior on container name based search (for example - // by calling *Kubelet.findContainer() without specifying a pod ID), we now - // return the list of pods ordered by their creation time. - sort.SliceStable(result, func(i, j int) bool { - return result[i].CreatedAt > result[j].CreatedAt - }) - logger.V(4).Info("Retrieved pods from runtime", "all", all) - return result, nil + return pods, nil } // containerKillReason explains what killed a given container @@ -529,14 +579,6 @@ type containerToKillInfo struct { reason containerKillReason } -// containerResources holds the set of resources applicable to the running container -type containerResources struct { - memoryLimit int64 - memoryRequest int64 - cpuLimit int64 - cpuRequest int64 -} - // containerToUpdateInfo contains necessary information to update a container's resources. type containerToUpdateInfo struct { // The spec of the container. @@ -544,9 +586,9 @@ type containerToUpdateInfo struct { // ID of the runtime container that needs resource update kubeContainerID kubecontainer.ContainerID // Desired resources for the running container - desiredContainerResources containerResources + desiredContainerResources resourceRequirements // Most recently configured resources on the running container - currentContainerResources *containerResources + currentContainerResources *resourceRequirements } // containerToRemoveInfo contains necessary information to update a container's resources. @@ -599,8 +641,9 @@ type podActions struct { UpdatePodLevelResources bool } -// podLevelResources holds the set of resources applicable to the running pod -type podLevelResources struct { +// resourceRequirements summarizes the set of resources applicable to the +// running pod or container in kuberuntime context. +type resourceRequirements struct { memoryLimit int64 memoryRequest int64 cpuLimit int64 @@ -634,8 +677,8 @@ func containerSucceeded(c *v1.Container, podStatus *kubecontainer.PodStatus) boo return cStatus.State == kubecontainer.ContainerStateExited && cStatus.ExitCode == 0 } -func containerResourcesFromRequirements(podRequirements, containerRequirements *v1.ResourceRequirements) containerResources { - resources := containerResources{ +func containerResourcesFromRequirements(podRequirements, containerRequirements *v1.ResourceRequirements) resourceRequirements { + resources := resourceRequirements{ memoryLimit: containerRequirements.Limits.Memory().Value(), memoryRequest: containerRequirements.Requests.Memory().Value(), cpuLimit: containerRequirements.Limits.Cpu().MilliValue(), @@ -652,12 +695,12 @@ func containerResourcesFromRequirements(podRequirements, containerRequirements * return resources } -func podResourcesFromRequirements(requirements *v1.ResourceRequirements) podLevelResources { +func podResourcesFromRequirements(requirements *v1.ResourceRequirements) resourceRequirements { if requirements == nil { - return podLevelResources{} + return resourceRequirements{} } - return podLevelResources{ + return resourceRequirements{ memoryLimit: requirements.Limits.Memory().Value(), memoryRequest: requirements.Requests.Memory().Value(), cpuLimit: requirements.Limits.Cpu().MilliValue(), @@ -668,14 +711,14 @@ func podResourcesFromRequirements(requirements *v1.ResourceRequirements) podLeve // computePodResizeAction determines the actions required (if any) to resize the given container. // Returns whether to keep (true) or restart (false) the container. // TODO(vibansal): Make this function to be agnostic to whether it is dealing with a restartable init container or not (i.e. remove the argument `isRestartableInitContainer`). -func (m *kubeGenericRuntimeManager) computePodResizeAction(ctx context.Context, pod *v1.Pod, containerIdx int, isRestartableInitContainer bool, kubeContainerStatus *kubecontainer.Status, changes *podActions) (keepContainer bool) { +func (m *kubeGenericRuntimeManager) computePodResizeAction(ctx context.Context, pod *v1.Pod, containerIdx int, initContainer bool, kubeContainerStatus *kubecontainer.Status, changes *podActions) (keepContainer bool) { logger := klog.FromContext(ctx) if resizable, _, _ := allocation.IsInPlacePodVerticalScalingAllowed(pod); !resizable { return true } var container v1.Container - if isRestartableInitContainer { + if initContainer { container = pod.Spec.InitContainers[containerIdx] } else { container = pod.Spec.Containers[containerIdx] @@ -750,7 +793,9 @@ func (m *kubeGenericRuntimeManager) computePodResizeAction(ctx context.Context, container: &container, message: fmt.Sprintf("Container %s resize requires restart", container.Name), } - if isRestartableInitContainer { + if initContainer { + // Validation prevents non-restartable init containers from being marked for restart, + // so we know this init container is restartable. changes.InitContainersToStart = append(changes.InitContainersToStart, containerIdx) } else { changes.ContainersToStart = append(changes.ContainersToStart, containerIdx) @@ -788,7 +833,7 @@ func (m *kubeGenericRuntimeManager) doPodResizeAction(ctx context.Context, pod * enforceCPULimits = false logger.V(2).Info("Disabled CFS quota", "pod", klog.KObj(pod)) } - podResources := cm.ResourceConfigForPod(pod, enforceCPULimits, uint64((m.cpuCFSQuotaPeriod.Duration)/time.Microsecond), false) + podResources := cm.ResourceConfigForPod(pod, enforceCPULimits, uint64((m.cpuCFSQuotaPeriod.Duration)/time.Microsecond), false, kubeletconfiginternal.NoneMemoryReservationPolicy) if podResources == nil { logger.Error(nil, "Unable to get resource configuration", "pod", klog.KObj(pod)) resizeResult.Fail(kubecontainer.ErrResizePodInPlace, fmt.Sprintf("unable to get resource configuration processing resize for pod %q", format.Pod(pod))) @@ -888,23 +933,26 @@ func (m *kubeGenericRuntimeManager) doPodResizeAction(ctx context.Context, pod * } resizedResources.Memory = podResources.Memory } + + // Notify the runtime first. If this fails, the runtime has rejected the resize. + mergedPodResources := mergeResourceConfig(currentPodResources, resizedResources) + if err = m.updatePodSandboxResources(ctx, podContainerChanges.SandboxID, pod, mergedPodResources); err != nil { + return fmt.Errorf("failed to notify runtime for UpdatePodSandboxResources (resource=%s); resize rejected: %w", rName, err) + } + + // Actuate the pod-level cgroup change. err = pcm.SetPodCgroupConfig(logger, pod, resizedResources) if err != nil { logger.Error(err, "Failed to set cgroup config", "resource", rName, "pod", klog.KObj(pod)) + // If cgroup adjustment fails, notify the runtime to revert to the previous resources. + if rollbackErr := m.updatePodSandboxResources(ctx, podContainerChanges.SandboxID, pod, currentPodResources); rollbackErr != nil { + logger.Error(rollbackErr, "Failed to notify runtime to rollback UpdatePodSandboxResources", "resource", rName, "pod", klog.KObj(pod)) + } return err } - currentPodResources = mergeResourceConfig(currentPodResources, resizedResources) - if err = m.updatePodSandboxResources(ctx, podContainerChanges.SandboxID, pod, currentPodResources); err != nil { - logger.Error(err, "Failed to notify runtime for UpdatePodSandboxResources", "resource", rName, "pod", klog.KObj(pod)) - // Don't propagate the error since the updatePodSandboxResources call is best-effort. - } - - if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling) { - if err = updateActuatedPodLevelResources(rName); err != nil { - logger.Error(err, "Failed to update pod-level actuated resources", "resource", rName, "pod", klog.KObj(pod)) - } - } + // Update our tracking of the current state. + currentPodResources = mergedPodResources return nil } @@ -933,6 +981,7 @@ func (m *kubeGenericRuntimeManager) doPodResizeAction(ctx context.Context, pod * return err } } + // At downsizing, requests should shrink prior to limits in order to keep "requests <= limits". if newPodCgReqValue < currPodCgReqValue { // TODO: Pass logger from context once contextual logging migration is complete @@ -946,12 +995,18 @@ func (m *kubeGenericRuntimeManager) doPodResizeAction(ctx context.Context, pod * return err } } + + if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling) && podContainerChanges.UpdatePodLevelResources { + if err = updateActuatedPodLevelResources(rName); err != nil { + logger.Error(err, "Failed to update pod-level actuated resources", "resource", rName, "pod", klog.KObj(pod)) + } + } return err } // Always update the pod status once. Even if there was a resize error, the resize may have been // partially actuated. - defer m.runtimeHelper.SetPodWatchCondition(pod.UID, "doPodResizeAction", func(*kubecontainer.PodStatus) bool { return true }) + defer m.runtimeHelper.RequestPodReinspect(pod.UID) if len(podContainerChanges.ContainersToUpdate[v1.ResourceMemory]) > 0 || podContainerChanges.UpdatePodResources || podContainerChanges.UpdatePodLevelResources { if podResources.Memory == nil { @@ -1188,7 +1243,7 @@ func (m *kubeGenericRuntimeManager) computePodActions(ctx context.Context, pod * // If there is any regular container, it means all init containers have // been initialized. - hasInitialized = hasAnyRegularContainerCreated(pod, podStatus) + hasInitialized = HasAnyRegularContainerCreated(pod, podStatus) if hasInitialized { changes.CreateSandbox = false @@ -1387,10 +1442,11 @@ func (m *kubeGenericRuntimeManager) computePodLevelResourcesResizeAction(ctx con // 2. Kill pod sandbox if necessary. // 3. Kill any containers that should not be running. // 4. Create sandbox if necessary. -// 5. Create ephemeral containers. -// 6. Create init containers. -// 7. Resize running containers (if InPlacePodVerticalScaling==true) -// 8. Create normal containers. +// 5. Invoke OnPodSandboxReady to notify Kubelet to update pod status. +// 6. Create ephemeral containers. +// 7. Create init containers. +// 8. Resize running containers (if InPlacePodVerticalScaling==true) +// 9. Create normal containers. func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff, restartAllContainers bool) (result kubecontainer.PodSyncResult) { logger := klog.FromContext(ctx) // Step 1: Compute sandbox and container changes. @@ -1402,7 +1458,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po logger.Error(err, "Couldn't make a ref to pod", "pod", klog.KObj(pod)) } if podContainerChanges.SandboxID != "" { - m.recorder.Eventf(ref, v1.EventTypeNormal, events.SandboxChanged, "Pod sandbox changed, it will be killed and re-created.") + m.recorder.WithLogger(logger).Eventf(ref, v1.EventTypeNormal, events.SandboxChanged, "Pod sandbox changed, it will be killed and re-created.") } else { logger.V(4).Info("SyncPod received new pod, will create a sandbox for it", "pod", klog.KObj(pod)) } @@ -1456,8 +1512,8 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po return } } - // TODO(yuanwang04): Revisit whether container logs should be persisted. - if err := m.removeContainer(ctx, containerInfo.containerID.ID); err != nil { + // The logs of removed containers will be preserved until the pod is deleted and GC is triggered. + if err := m.removeContainer(ctx, containerInfo.containerID.ID, true); err != nil { removeContainerResult.Fail(kubecontainer.ErrRemoveContainer, err.Error()) logger.Error(err, "removeContainer for pod failed", "containerName", cName, "containerID", containerInfo.containerID, "pod", klog.KObj(pod)) return @@ -1534,7 +1590,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po logger.Error(referr, "Couldn't make a ref to pod", "pod", klog.KObj(pod)) return } - m.recorder.Eventf(ref, v1.EventTypeWarning, events.FailedPrepareDynamicResources, "Failed to prepare dynamic resources: %v", err) + m.recorder.WithLogger(logger).Eventf(ref, v1.EventTypeWarning, events.FailedPrepareDynamicResources, "Failed to prepare dynamic resources: %v", err) logger.Error(err, "Failed to prepare dynamic resources", "pod", klog.KObj(pod)) return } @@ -1560,7 +1616,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po if referr != nil { logger.Error(referr, "Couldn't make a ref to pod", "pod", klog.KObj(pod)) } - m.recorder.Eventf(ref, v1.EventTypeWarning, events.FailedCreatePodSandBox, "Failed to create pod sandbox: %v", err) + m.recorder.WithLogger(logger).Eventf(ref, v1.EventTypeWarning, events.FailedCreatePodSandBox, "Failed to create pod sandbox: %v", err) return } logger.V(4).Info("Created PodSandbox for pod", "podSandboxID", podSandboxID, "pod", klog.KObj(pod)) @@ -1571,7 +1627,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po if referr != nil { logger.Error(referr, "Couldn't make a ref to pod", "pod", klog.KObj(pod)) } - m.recorder.Eventf(ref, v1.EventTypeWarning, events.FailedStatusPodSandBox, "Unable to get pod sandbox status: %v", err) + m.recorder.WithLogger(logger).Eventf(ref, v1.EventTypeWarning, events.FailedStatusPodSandBox, "Unable to get pod sandbox status: %v", err) logger.Error(err, "Failed to get pod sandbox status; Skipping pod", "pod", klog.KObj(pod)) result.Fail(err) return @@ -1588,6 +1644,16 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po podIPs = m.determinePodSandboxIPs(ctx, pod.Namespace, pod.Name, resp.GetStatus()) logger.V(4).Info("Determined the ip for pod after sandbox changed", "IPs", podIPs, "pod", klog.KObj(pod)) } + + // Step 5: invoke the sandbox ready callback before image pulling . + // At this point, dynamic resources are prepared (at PrepareDynamicResources() call above) + // and volumes are already mounted (at the kubelet SyncPod() level), so, + // all requirements (sandbox, networking, volumes) are met to set `PodReadyToStartContainers=True` condition. + logger.V(4).Info("Pod sandbox and network ready, invoking callback", "pod", klog.KObj(pod), "podIPs", podIPs) + if err := m.runtimeHelper.OnPodSandboxReady(ctx, pod); err != nil { + // log the error but continue the pod creation process, to retain the existing behaviour + logger.Error(err, "Failed to invoke sandbox ready callback, continuing with pod creation", "pod", klog.KObj(pod)) + } } // the start containers routines depend on pod ip(as in primary pod ip) @@ -1599,20 +1665,17 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po } // Get podSandboxConfig for containers to start. - configPodSandboxResult := kubecontainer.NewSyncResult(kubecontainer.ConfigPodSandbox, podSandboxID) - result.AddSyncResult(configPodSandboxResult) podSandboxConfig, err := m.generatePodSandboxConfig(ctx, pod, podContainerChanges.Attempt) if err != nil { - message := fmt.Sprintf("GeneratePodSandboxConfig for pod %q failed: %v", format.Pod(pod), err) logger.Error(err, "GeneratePodSandboxConfig for pod failed", "pod", klog.KObj(pod)) - configPodSandboxResult.Fail(kubecontainer.ErrConfigPodSandbox, message) + result.Fail(fmt.Errorf("GeneratePodSandboxConfig for pod %q failed: %w", format.Pod(pod), err)) return } imageVolumePullResults, err := m.getImageVolumes(ctx, pod, podSandboxConfig, pullSecrets) if err != nil { logger.Error(err, "Get image volumes for pod failed", "pod", klog.KObj(pod)) - configPodSandboxResult.Fail(kubecontainer.ErrConfigPodSandbox, err.Error()) + result.Fail(err) return } @@ -1665,11 +1728,19 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po } return err } + if typeName == "init container" { + // Don't measure restartable init containers (sidecars) + if !podutil.IsRestartableInitContainer(spec.container) { + if m.podInitContainerTimeRecorder != nil { + m.podInitContainerTimeRecorder.RecordInitContainerStarted(pod.UID, time.Now()) + } + } + } return nil } - // Step 5: start ephemeral containers + // Step 6: start ephemeral containers // These are started "prior" to init containers to allow running ephemeral containers even when there // are errors starting an init container. In practice init containers will start first since ephemeral // containers cannot be specified on pod creation. @@ -1677,7 +1748,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po start(ctx, "ephemeral container", metrics.EphemeralContainer, ephemeralContainerStartSpec(&pod.Spec.EphemeralContainers[idx])) } - // Step 6: start init containers. + // Step 7: start init containers. for _, idx := range podContainerChanges.InitContainersToStart { container := &pod.Spec.InitContainers[idx] // Start the next init container. @@ -1694,19 +1765,34 @@ func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, po logger.V(4).Info("Completed init container for pod", "containerName", container.Name, "pod", klog.KObj(pod)) } - // Step 7: For containers in podContainerChanges.ContainersToUpdate[CPU,Memory] list, invoke UpdateContainerResources + for _, cs := range podStatus.ContainerStatuses { + // Check if this is an init container + for _, init := range pod.Spec.InitContainers { + // Don't measure restartable init containers (sidecars) + if podutil.IsRestartableInitContainer(&init) { + continue + } + if cs.Name == init.Name && cs.State == kubecontainer.ContainerStateExited && !cs.FinishedAt.IsZero() { + if m.podInitContainerTimeRecorder != nil { + m.podInitContainerTimeRecorder.RecordInitContainerFinished(pod.UID, cs.FinishedAt) + } + } + } + } + + // Step 8: For containers in podContainerChanges.ContainersToUpdate[CPU,Memory] list, invoke UpdateContainerResources if resizable, _, _ := allocation.IsInPlacePodVerticalScalingAllowed(pod); resizable { if len(podContainerChanges.ContainersToUpdate) > 0 || podContainerChanges.UpdatePodResources || podContainerChanges.UpdatePodLevelResources { - result.SyncResults = append(result.SyncResults, m.doPodResizeAction(ctx, pod, podStatus, podContainerChanges)) + result.AddSyncResult(m.doPodResizeAction(ctx, pod, podStatus, podContainerChanges)) } } - // Step 8: start containers in podContainerChanges.ContainersToStart. + // Step 9: start containers in podContainerChanges.ContainersToStart. for _, idx := range podContainerChanges.ContainersToStart { start(ctx, "container", metrics.Container, containerStartSpec(&pod.Spec.Containers[idx])) } - return + return result } // incrementImageVolumeMetrics increments the image volume mount metrics @@ -1840,8 +1926,8 @@ func (m *kubeGenericRuntimeManager) doBackOff(ctx context.Context, pod *v1.Pod, key := GetBackoffKey(pod, container) if backOff.IsInBackOffSince(key, ts) { if containerRef, err := kubecontainer.GenerateContainerRef(pod, container); err == nil { - m.recorder.Eventf(containerRef, v1.EventTypeWarning, events.BackOffStartContainer, - fmt.Sprintf("Back-off restarting failed container %s in pod %s", container.Name, format.Pod(pod))) + m.recorder.WithLogger(logger).Eventf(containerRef, v1.EventTypeWarning, events.BackOffStartContainer, + "Back-off restarting failed container %s in pod %s", container.Name, format.Pod(pod)) } backoff := backOff.Get(key) err := fmt.Errorf("back-off %s restarting failed container=%s pod=%s", backoff, container.Name, format.Pod(pod)) @@ -1909,7 +1995,7 @@ func (m *kubeGenericRuntimeManager) GeneratePodStatus(event *runtimeapi.Containe // GetPodStatus retrieves the status of the pod, including the // information of all containers in the pod that are visible in Runtime. -func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubetypes.UID, name, namespace string) (*kubecontainer.PodStatus, error) { +func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { logger := klog.FromContext(ctx) // Now we retain restart count of container as a container label. Each time a container // restarts, pod will read the restart count from the registered dead container, increment @@ -1924,33 +2010,30 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety // Anyhow, we only promised "best-effort" restart count reporting, we can just ignore // these limitations now. // TODO: move this comment to SyncPod. - podSandboxIDs, err := m.getSandboxIDByPodUID(ctx, uid, nil) - if err != nil { - return nil, err - } + podFullName := format.PodDesc(pod.Name, pod.Namespace, pod.ID) + logger = logger.WithValues("pod", podFullName) + ctx = klog.NewContext(ctx, logger) - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - UID: uid, - }, + podSandboxIDs := make([]string, len(pod.Sandboxes)) + for i, s := range pod.Sandboxes { + podSandboxIDs[i] = s.ID.ID } - podFullName := format.Pod(pod) - - logger.V(4).Info("getSandboxIDByPodUID got sandbox IDs for pod", "podSandboxID", podSandboxIDs, "pod", klog.KObj(pod)) - sandboxStatuses := []*runtimeapi.PodSandboxStatus{} containerStatuses := []*kubecontainer.Status{} activeContainerStatuses := []*kubecontainer.Status{} - timestamp := time.Now() + + // Since we rely on the sandbox & container IDs in the kubecontainer.Pod, we must be + // conservative and use it's timestamp as the status timestamp. Otherwise, we risk reporting a + // newer PodStatus timestamp that is missing Sandboxes or Containers that should have been + // present at that time. + timestamp := pod.Timestamp podIPs := []string{} var activePodSandboxID string for idx, podSandboxID := range podSandboxIDs { resp, err := m.runtimeService.PodSandboxStatus(ctx, podSandboxID, false) - // Between List (getSandboxIDByPodUID) and check (PodSandboxStatus) another thread might remove a container, and that is normal. + // Between List (ListPodSandbox) and check (PodSandboxStatus) another thread might remove a container, and that is normal. // The previous call (getSandboxIDByPodUID) never fails due to a pod sandbox not existing. // Therefore, this method should not either, but instead act as if the previous call failed, // which means the error should be ignored. @@ -1958,7 +2041,7 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety continue } if err != nil { - logger.Error(err, "PodSandboxStatus of sandbox for pod", "podSandboxID", podSandboxID, "pod", klog.KObj(pod)) + logger.Error(err, "PodSandboxStatus of sandbox for pod", "podSandboxID", podSandboxID) return nil, err } if resp.GetStatus() == nil { @@ -1968,7 +2051,7 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety sandboxStatuses = append(sandboxStatuses, resp.Status) // Only get pod IP from latest sandbox if idx == 0 && resp.Status.State == runtimeapi.PodSandboxState_SANDBOX_READY { - podIPs = m.determinePodSandboxIPs(ctx, namespace, name, resp.Status) + podIPs = m.determinePodSandboxIPs(ctx, pod.Namespace, pod.Name, resp.Status) activePodSandboxID = podSandboxID } @@ -1979,11 +2062,11 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety // e.g. CI job 'pull-kubernetes-e2e-gce-alpha-features' will runs with // features gate enabled, which includes Evented PLEG, but uses the // runtime without Evented PLEG support. - logger.V(4).Info("Runtime does not set pod status timestamp", "pod", klog.KObj(pod)) - containerStatuses, activeContainerStatuses, err = m.getPodContainerStatuses(ctx, uid, name, namespace, activePodSandboxID) + logger.V(4).Info("Runtime does not set pod status timestamp") + containerStatuses, activeContainerStatuses, err = m.getPodContainerStatuses(ctx, pod, activePodSandboxID) if err != nil { if m.logReduction.ShouldMessageBePrinted(err.Error(), podFullName) { - logger.Error(err, "getPodContainerStatuses for pod failed", "pod", klog.KObj(pod)) + logger.Error(err, "getPodContainerStatuses for pod failed") } return nil, err } @@ -1992,7 +2075,7 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety // timestamp from sandboxStatus. timestamp = time.Unix(0, resp.Timestamp) for _, cs := range resp.ContainersStatuses { - cStatus := m.convertToKubeContainerStatus(ctx, uid, cs) + cStatus := m.convertToKubeContainerStatus(ctx, pod.ID, cs) containerStatuses = append(containerStatuses, cStatus) } } @@ -2001,10 +2084,11 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety if !utilfeature.DefaultFeatureGate.Enabled(features.EventedPLEG) { // Get statuses of all containers visible in the pod. - containerStatuses, activeContainerStatuses, err = m.getPodContainerStatuses(ctx, uid, name, namespace, activePodSandboxID) + var err error + containerStatuses, activeContainerStatuses, err = m.getPodContainerStatuses(ctx, pod, activePodSandboxID) if err != nil { if m.logReduction.ShouldMessageBePrinted(err.Error(), podFullName) { - logger.Error(err, "getPodContainerStatuses for pod failed", "pod", klog.KObj(pod)) + logger.Error(err, "getPodContainerStatuses for pod failed") } return nil, err } @@ -2012,9 +2096,9 @@ func (m *kubeGenericRuntimeManager) GetPodStatus(ctx context.Context, uid kubety m.logReduction.ClearID(podFullName) return &kubecontainer.PodStatus{ - ID: uid, - Name: name, - Namespace: namespace, + ID: pod.ID, + Name: pod.Name, + Namespace: pod.Namespace, IPs: podIPs, SandboxStatuses: sandboxStatuses, ContainerStatuses: containerStatuses, @@ -2142,11 +2226,15 @@ func (m *kubeGenericRuntimeManager) isPodLevelResourcesResizeInProgress(allocate return false } - if allocatedPod.Spec.Resources == nil { + if allocatedPod.Spec.Resources == nil || podStatus == nil { + return false + } + + actuatedPodResources, found := m.actuatedState.GetPodLevelResources(allocatedPod.UID) + if !found || actuatedPodResources == nil { return false } - actuatedPodResources, _ := m.actuatedState.GetPodLevelResources(allocatedPod.UID) allocatedPodResources := allocatedPod.Spec.Resources return !cpuMemoryResourcesEqual(actuatedPodResources, allocatedPodResources) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go b/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go index b27dcdc128b55..caf77bb5901e4 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go @@ -70,11 +70,11 @@ var ( containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways ) -func createTestRuntimeManager(ctx context.Context) (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) { - return createTestRuntimeManagerWithErrors(ctx, nil) +func createTestRuntimeManager(tCtx ktesting.TContext) (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) { + return createTestRuntimeManagerWithErrors(tCtx, nil) } -func createTestRuntimeManagerWithErrors(ctx context.Context, errors map[string][]error) (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) { +func createTestRuntimeManagerWithErrors(tCtx ktesting.TContext, errors map[string][]error) (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) { fakeRuntimeService := apitest.NewFakeRuntimeService() if errors != nil { fakeRuntimeService.Errors = errors @@ -88,7 +88,7 @@ func createTestRuntimeManagerWithErrors(ctx context.Context, errors map[string][ MemoryCapacity: uint64(memoryCapacityQuantity.Value()), } osInterface := &containertest.FakeOS{} - manager, err := newFakeKubeRuntimeManager(ctx, fakeRuntimeService, fakeImageService, machineInfo, osInterface, &containertest.FakeRuntimeHelper{}, noopoteltrace.NewTracerProvider().Tracer("")) + manager, err := newFakeKubeRuntimeManager(tCtx, fakeRuntimeService, fakeImageService, machineInfo, osInterface, &containertest.FakeRuntimeHelper{}, noopoteltrace.NewTracerProvider().Tracer("")) return fakeRuntimeService, fakeImageService, manager, err } @@ -114,10 +114,10 @@ type containerTemplate struct { // makeAndSetFakePod is a helper function to create and set one fake sandbox for a pod and // one fake container for each of its container. -func makeAndSetFakePod(t *testing.T, m *kubeGenericRuntimeManager, fakeRuntime *apitest.FakeRuntimeService, +func makeAndSetFakePod(tCtx ktesting.TContext, m *kubeGenericRuntimeManager, fakeRuntime *apitest.FakeRuntimeService, pod *v1.Pod, ) (*apitest.FakePodSandbox, []*apitest.FakeContainer) { - sandbox := makeFakePodSandbox(t, m, sandboxTemplate{ + sandbox := makeFakePodSandbox(tCtx, m, sandboxTemplate{ pod: pod, createdAt: fakeCreatedAt, state: runtimeapi.PodSandboxState_SANDBOX_READY, @@ -133,7 +133,7 @@ func makeAndSetFakePod(t *testing.T, m *kubeGenericRuntimeManager, fakeRuntime * } } podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool { - containers = append(containers, makeFakeContainer(t, m, newTemplate(c))) + containers = append(containers, makeFakeContainer(tCtx, m, newTemplate(c))) return true }) @@ -143,8 +143,8 @@ func makeAndSetFakePod(t *testing.T, m *kubeGenericRuntimeManager, fakeRuntime * } // makeFakePodSandbox creates a fake pod sandbox based on a sandbox template. -func makeFakePodSandbox(t *testing.T, m *kubeGenericRuntimeManager, template sandboxTemplate) *apitest.FakePodSandbox { - tCtx := ktesting.Init(t) +func makeFakePodSandbox(tCtx ktesting.TContext, m *kubeGenericRuntimeManager, template sandboxTemplate) *apitest.FakePodSandbox { + t := tCtx.TB() config, err := m.generatePodSandboxConfig(tCtx, template.pod, template.attempt) assert.NoError(t, err, "generatePodSandboxConfig for sandbox template %+v", template) @@ -177,17 +177,17 @@ func makeFakePodSandbox(t *testing.T, m *kubeGenericRuntimeManager, template san // makeFakePodSandboxes creates a group of fake pod sandboxes based on the sandbox templates. // The function guarantees the order of the fake pod sandboxes is the same with the templates. -func makeFakePodSandboxes(t *testing.T, m *kubeGenericRuntimeManager, templates []sandboxTemplate) []*apitest.FakePodSandbox { +func makeFakePodSandboxes(tCtx ktesting.TContext, m *kubeGenericRuntimeManager, templates []sandboxTemplate) []*apitest.FakePodSandbox { var fakePodSandboxes []*apitest.FakePodSandbox for _, template := range templates { - fakePodSandboxes = append(fakePodSandboxes, makeFakePodSandbox(t, m, template)) + fakePodSandboxes = append(fakePodSandboxes, makeFakePodSandbox(tCtx, m, template)) } return fakePodSandboxes } // makeFakeContainer creates a fake container based on a container template. -func makeFakeContainer(t *testing.T, m *kubeGenericRuntimeManager, template containerTemplate) *apitest.FakeContainer { - tCtx := ktesting.Init(t) +func makeFakeContainer(tCtx ktesting.TContext, m *kubeGenericRuntimeManager, template containerTemplate) *apitest.FakeContainer { + t := tCtx.TB() sandboxConfig, err := m.generatePodSandboxConfig(tCtx, template.pod, template.sandboxAttempt) assert.NoError(t, err, "generatePodSandboxConfig for container template %+v", template) @@ -215,10 +215,10 @@ func makeFakeContainer(t *testing.T, m *kubeGenericRuntimeManager, template cont // makeFakeContainers creates a group of fake containers based on the container templates. // The function guarantees the order of the fake containers is the same with the templates. -func makeFakeContainers(t *testing.T, m *kubeGenericRuntimeManager, templates []containerTemplate) []*apitest.FakeContainer { +func makeFakeContainers(tCtx ktesting.TContext, m *kubeGenericRuntimeManager, templates []containerTemplate) []*apitest.FakeContainer { var fakeContainers []*apitest.FakeContainer for _, template := range templates { - fakeContainers = append(fakeContainers, makeFakeContainer(t, m, template)) + fakeContainers = append(fakeContainers, makeFakeContainer(tCtx, m, template)) } return fakeContainers } @@ -245,27 +245,6 @@ func makeTestPod(podName, podNamespace, podUID string, containers []v1.Container } } -// verifyPods returns true if the two pod slices are equal. -func verifyPods(a, b []*kubecontainer.Pod) bool { - if len(a) != len(b) { - return false - } - - // Sort the containers within a pod. - for i := range a { - sort.Sort(containersByID(a[i].Containers)) - } - for i := range b { - sort.Sort(containersByID(b[i].Containers)) - } - - // Sort the pods by UID. - sort.Sort(podsByID(a)) - sort.Sort(podsByID(b)) - - return reflect.DeepEqual(a, b) -} - func verifyFakeContainerList(fakeRuntime *apitest.FakeRuntimeService, expected sets.Set[string]) (sets.Set[string], bool) { actual := sets.New[string]() for _, c := range fakeRuntime.Containers { @@ -356,10 +335,12 @@ func TestGetPodStatus(t *testing.T) { } // Set fake sandbox and faked containers to fakeRuntime. - makeAndSetFakePod(t, m, fakeRuntime, pod) + makeAndSetFakePod(tCtx, m, fakeRuntime, pod) - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err := m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) assert.Equal(t, pod.UID, podStatus.ID) assert.Equal(t, pod.Name, podStatus.Name) assert.Equal(t, pod.Namespace, podStatus.Namespace) @@ -395,9 +376,11 @@ func TestStopContainerWithNotFoundError(t *testing.T) { } // Set fake sandbox and faked containers to fakeRuntime. - makeAndSetFakePod(t, m, fakeRuntime, pod) + makeAndSetFakePod(tCtx, m, fakeRuntime, pod) fakeRuntime.InjectError("StopContainer", status.Error(codes.NotFound, "No such container")) - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err := m.GetPodStatus(tCtx, runtimePod) require.NoError(t, err) p := kubecontainer.ConvertPodStatusToRunningPod("", podStatus) gracePeriod := int64(1) @@ -434,9 +417,11 @@ func TestGetPodStatusWithNotFoundError(t *testing.T) { } // Set fake sandbox and faked containers to fakeRuntime. - makeAndSetFakePod(t, m, fakeRuntime, pod) + makeAndSetFakePod(tCtx, m, fakeRuntime, pod) fakeRuntime.InjectError("ContainerStatus", status.Error(codes.NotFound, "No such container")) - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err := m.GetPodStatus(tCtx, runtimePod) require.NoError(t, err) require.Equal(t, pod.UID, podStatus.ID) require.Equal(t, pod.Name, podStatus.Name) @@ -446,8 +431,13 @@ func TestGetPodStatusWithNotFoundError(t *testing.T) { func TestGetPods(t *testing.T) { tCtx := ktesting.Init(t) + tCtx.SyncTest("", testGetPods) +} + +func testGetPods(tCtx ktesting.TContext) { + t := tCtx.TB() fakeRuntime, _, m, err := createTestRuntimeManager(tCtx) - assert.NoError(t, err) + require.NoError(t, err) pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -470,20 +460,23 @@ func TestGetPods(t *testing.T) { } // Set fake sandbox and fake containers to fakeRuntime. - fakeSandbox, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) + fakeSandbox, fakeContainers := makeAndSetFakePod(tCtx, m, fakeRuntime, pod) // Convert the fakeContainers to kubecontainer.Container containers := make([]*kubecontainer.Container, len(fakeContainers)) for i := range containers { fakeContainer := fakeContainers[i] c, err := m.toKubeContainer(tCtx, &runtimeapi.Container{ - Id: fakeContainer.Id, - Metadata: fakeContainer.Metadata, - State: fakeContainer.State, - Image: fakeContainer.Image, - ImageRef: fakeContainer.ImageRef, - Labels: fakeContainer.Labels, - Annotations: fakeContainer.Annotations, + Id: fakeContainer.Id, + Metadata: fakeContainer.Metadata, + State: fakeContainer.State, + Image: fakeContainer.Image, + ImageRef: fakeContainer.ImageRef, + ImageId: fakeContainer.ImageId, + Labels: fakeContainer.Labels, + Annotations: fakeContainer.Annotations, + PodSandboxId: fakeSandbox.Id, + CreatedAt: fakeCreatedAt, }) if err != nil { t.Fatalf("unexpected error %v", err) @@ -503,23 +496,28 @@ func TestGetPods(t *testing.T) { t.Fatalf("unexpected error %v", err) } - expected := []*kubecontainer.Pod{ - { - ID: types.UID("12345678"), - Name: "foo", - Namespace: "new", - CreatedAt: uint64(fakeSandbox.CreatedAt), - Containers: []*kubecontainer.Container{containers[0], containers[1]}, - Sandboxes: []*kubecontainer.Container{sandbox}, - }, + expectedPod := &kubecontainer.Pod{ + ID: types.UID("12345678"), + Name: "foo", + Namespace: "new", + CreatedAt: uint64(fakeSandbox.CreatedAt), + Containers: []*kubecontainer.Container{containers[0], containers[1]}, + Sandboxes: []*kubecontainer.Container{sandbox}, + Timestamp: time.Now(), } + expected := []*kubecontainer.Pod{expectedPod} actual, err := m.GetPods(tCtx, false) - assert.NoError(t, err) + require.NoError(t, err) - if !verifyPods(expected, actual) { - t.Errorf("expected %#v, got %#v", expected, actual) - } + assert.ElementsMatch(t, expected, actual) + + actualPod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + assert.Equal(t, expectedPod, actualPod) + + _, err = m.GetPod(tCtx, "non-existent-uid") + assert.ErrorIs(t, err, kubecontainer.ErrPodNotFound) } func TestGetPodsSorted(t *testing.T) { @@ -533,7 +531,7 @@ func TestGetPodsSorted(t *testing.T) { fakeSandboxes := []*apitest.FakePodSandbox{} for i, createdAt := range createdTimestamps { pod.UID = types.UID(fmt.Sprint(i)) - fakeSandboxes = append(fakeSandboxes, makeFakePodSandbox(t, m, sandboxTemplate{ + fakeSandboxes = append(fakeSandboxes, makeFakePodSandbox(tCtx, m, sandboxTemplate{ pod: pod, createdAt: int64(createdAt), state: runtimeapi.PodSandboxState_SANDBOX_READY, @@ -586,7 +584,7 @@ func TestKillPod(t *testing.T) { } // Set fake sandbox and fake containers to fakeRuntime. - fakeSandbox, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) + fakeSandbox, fakeContainers := makeAndSetFakePod(tCtx, m, fakeRuntime, pod) // Convert the fakeContainers to kubecontainer.Container containers := make([]*kubecontainer.Container, len(fakeContainers)) @@ -759,10 +757,12 @@ func TestPruneInitContainers(t *testing.T) { {pod: pod, container: &init2, attempt: 0, createdAt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED}, {pod: pod, container: &init1, attempt: 0, createdAt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED}, } - fakes := makeFakeContainers(t, m, templates) + fakes := makeFakeContainers(tCtx, m, templates) fakeRuntime.SetFakeContainers(fakes) - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err := m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err := m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) m.pruneInitContainersBeforeStart(tCtx, pod, podStatus) expectedContainers := sets.New[string](fakes[0].Id, fakes[2].Id) @@ -810,25 +810,35 @@ func TestSyncPodWithInitContainers(t *testing.T) { backOff := flowcontrol.NewBackOff(time.Second, time.Minute) // 1. should only create the init container. - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err := m.GetPod(tCtx, pod.UID) + if err != nil { + runtimePod = &kubecontainer.Pod{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + } + } + podStatus, err := m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) result := m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) - assert.NoError(t, result.Error()) + require.NoError(t, result.Error()) expected := []*cRecord{ {name: initContainers[0].Name, attempt: 0, state: runtimeapi.ContainerState_CONTAINER_RUNNING}, } verifyContainerStatuses(t, fakeRuntime, expected, "start only the init container") // 2. should not create app container because init container is still running. - podStatus, err = m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err = m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err = m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) result = m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) - assert.NoError(t, result.Error()) + require.NoError(t, result.Error()) verifyContainerStatuses(t, fakeRuntime, expected, "init container still running; do nothing") // 3. should create all app containers because init container finished. // Stop init container instance 0. - sandboxIDs, err := m.getSandboxIDByPodUID(tCtx, pod.UID, nil) + sandboxIDs, err := m.getSandboxIDByPodUID(tCtx, pod.UID) require.NoError(t, err) sandboxID := sandboxIDs[0] initID0, err := fakeRuntime.GetContainerID(sandboxID, initContainers[0].Name, 0) @@ -836,10 +846,12 @@ func TestSyncPodWithInitContainers(t *testing.T) { err = fakeRuntime.StopContainer(tCtx, initID0, 0) require.NoError(t, err) // Sync again. - podStatus, err = m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err = m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err = m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) result = m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) - assert.NoError(t, result.Error()) + require.NoError(t, result.Error()) expected = []*cRecord{ {name: initContainers[0].Name, attempt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED}, {name: containers[0].Name, attempt: 0, state: runtimeapi.ContainerState_CONTAINER_RUNNING}, @@ -852,10 +864,12 @@ func TestSyncPodWithInitContainers(t *testing.T) { err = fakeRuntime.StopPodSandbox(tCtx, sandboxID) require.NoError(t, err) // Sync again. - podStatus, err = m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err = m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err = m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) result = m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) - assert.NoError(t, result.Error()) + require.NoError(t, result.Error()) expected = []*cRecord{ // The first init container instance is purged and no longer visible. // The second (attempt == 1) instance has been started and is running. @@ -922,7 +936,15 @@ func TestSyncPodWithRestartAllContainers(t *testing.T) { backOff := flowcontrol.NewBackOff(time.Second, time.Minute) // 1. Run the pod first. First SyncPod should execute the init container. - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err := m.GetPod(tCtx, pod.UID) + if err != nil { + runtimePod = &kubecontainer.Pod{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + } + } + podStatus, err := m.GetPodStatus(tCtx, runtimePod) require.NoError(t, err) result := m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) require.NoError(t, result.Error()) @@ -933,7 +955,7 @@ func TestSyncPodWithRestartAllContainers(t *testing.T) { // 2. should run all app containers because init container finished. // Stop init container instance 0. - sandboxIDs, err := m.getSandboxIDByPodUID(tCtx, pod.UID, nil) + sandboxIDs, err := m.getSandboxIDByPodUID(tCtx, pod.UID) require.NoError(t, err) sandboxID := sandboxIDs[0] initID0, err := fakeRuntime.GetContainerID(sandboxID, initContainers[0].Name, 0) @@ -941,7 +963,9 @@ func TestSyncPodWithRestartAllContainers(t *testing.T) { err = fakeRuntime.StopContainer(tCtx, initID0, 0) require.NoError(t, err) // Sync again. - podStatus, err = m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err = m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err = m.GetPodStatus(tCtx, runtimePod) require.NoError(t, err) result = m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) require.NoError(t, result.Error()) @@ -954,7 +978,7 @@ func TestSyncPodWithRestartAllContainers(t *testing.T) { // 3. Exits the container foo2 with code 42, the pod should be marked for RestartAllContainers, and // should remove all containers. - sandboxIDs, err = m.getSandboxIDByPodUID(tCtx, pod.UID, nil) + sandboxIDs, err = m.getSandboxIDByPodUID(tCtx, pod.UID) require.NoError(t, err) sandboxID = sandboxIDs[0] foo2ID, err := fakeRuntime.GetContainerID(sandboxID, containers[1].Name, 0) @@ -964,7 +988,9 @@ func TestSyncPodWithRestartAllContainers(t *testing.T) { failedFoo2.ExitCode = 42 fakeRuntime.Containers[foo2ID] = failedFoo2 - podStatus, err = m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err = m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err = m.GetPodStatus(tCtx, runtimePod) require.NoError(t, err) result = m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, true) require.NoError(t, result.Error()) @@ -972,7 +998,9 @@ func TestSyncPodWithRestartAllContainers(t *testing.T) { verifyContainerStatuses(t, fakeRuntime, expected, "kill all containers") // 4. Unmark the pod. Now it should start the init container first. - podStatus, err = m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err = m.GetPod(tCtx, pod.UID) + require.NoError(t, err) + podStatus, err = m.GetPodStatus(tCtx, runtimePod) require.NoError(t, err) result = m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) require.NoError(t, result.Error()) @@ -1749,6 +1777,11 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { _, _, m, err := createTestRuntimeManager(tCtx) require.NoError(t, err) + cpu400m := resource.MustParse("400m") + memory400Mi := resource.MustParse("400Mi") + cpu800m := resource.MustParse("800m") + memory800Mi := resource.MustParse("800Mi") + // Creating a pair reference pod and status for the test cases to refer // the specific fields. basePod, baseStatus := makeBasePodAndStatusWithInitContainers() @@ -1759,9 +1792,11 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { } for desc, test := range map[string]struct { - mutatePodFn func(*v1.Pod) - mutateStatusFn func(*kubecontainer.PodStatus) - actions podActions + mutatePodFn func(*v1.Pod) + mutateStatusFn func(*kubecontainer.PodStatus) + actions podActions + disableIPPRInitCtrFG bool + skipWindows bool }{ "initialization completed; start all containers": { actions: podActions{ @@ -1947,9 +1982,115 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}), }, }, + "resize request exists on a not yet running non-sidecar init container; start it": { + mutatePodFn: func(pod *v1.Pod) { + pod.Spec.InitContainers[0].Resources.Requests = v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + } + }, + mutateStatusFn: func(status *kubecontainer.PodStatus) { + status.ContainerStatuses = nil + }, + actions: podActions{ + KillPod: false, + SandboxID: baseStatus.SandboxStatuses[0].Id, + InitContainersToStart: []int{0}, + ContainersToStart: []int{}, + ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}), + }, + }, + "resize of a running non-sidecar init container": { + mutatePodFn: func(pod *v1.Pod) { + pod.Spec.InitContainers[0].Resources.Requests = v1.ResourceList{ + v1.ResourceCPU: cpu800m, + v1.ResourceMemory: memory800Mi, + } + }, + mutateStatusFn: func(status *kubecontainer.PodStatus) { + status.ContainerStatuses = status.ContainerStatuses[:1] + status.ContainerStatuses[0].State = kubecontainer.ContainerStateRunning + }, + actions: podActions{ + KillPod: false, + SandboxID: baseStatus.SandboxStatuses[0].Id, + InitContainersToStart: nil, + ContainersToStart: []int{}, + ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}), + ContainersToUpdate: map[v1.ResourceName][]containerToUpdateInfo{ + v1.ResourceMemory: { + { + kubeContainerID: baseStatus.ContainerStatuses[0].ID, + desiredContainerResources: resourceRequirements{ + memoryRequest: memory800Mi.Value(), + cpuRequest: cpu800m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{ + memoryRequest: memory400Mi.Value(), + cpuRequest: cpu400m.MilliValue(), + }, + }, + }, + v1.ResourceCPU: { + { + kubeContainerID: baseStatus.ContainerStatuses[0].ID, + desiredContainerResources: resourceRequirements{ + memoryRequest: memory800Mi.Value(), + cpuRequest: cpu800m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{ + memoryRequest: memory400Mi.Value(), + cpuRequest: cpu400m.MilliValue(), + }, + }, + }, + }, + }, + skipWindows: true, // Windows does not support resize. + }, + "resize of a running non-sidecar init container with FG disabled": { + mutatePodFn: func(pod *v1.Pod) { + pod.Spec.InitContainers[0].Resources.Requests = v1.ResourceList{ + v1.ResourceCPU: cpu800m, + v1.ResourceMemory: memory800Mi, + } + }, + mutateStatusFn: func(status *kubecontainer.PodStatus) { + status.ContainerStatuses = status.ContainerStatuses[:1] + status.ContainerStatuses[0].State = kubecontainer.ContainerStateRunning + }, + actions: podActions{ + KillPod: false, + SandboxID: baseStatus.SandboxStatuses[0].Id, + InitContainersToStart: nil, + ContainersToStart: []int{}, + ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}), + ContainersToUpdate: map[v1.ResourceName][]containerToUpdateInfo{}, + }, + skipWindows: true, // Windows does not support resize. + disableIPPRInitCtrFG: true, + }, } { t.Run(desc, func(t *testing.T) { + if test.skipWindows && goruntime.GOOS == "windows" { + t.Skip("Skipping test since Windows does not support resize") + } + if test.disableIPPRInitCtrFG { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingInitContainers, false) + } pod, status := makeBasePodAndStatusWithInitContainers() + if test.actions.ContainersToUpdate != nil { + for res := range test.actions.ContainersToUpdate { + for i := range test.actions.ContainersToUpdate[res] { + // Link the expected action to the mutated pod container + test.actions.ContainersToUpdate[res][i].container = &pod.Spec.InitContainers[0] + } + } + // Sync the state manager with the base (old) values before the resize + resources := pod.Spec.InitContainers[0].Resources + require.NoError(t, m.actuatedState.SetContainerResources(pod.UID, pod.Spec.InitContainers[0].Name, resources)) + } + if test.mutatePodFn != nil { test.mutatePodFn(pod) } @@ -1969,6 +2110,12 @@ func makeBasePodAndStatusWithInitContainers() (*v1.Pod, *kubecontainer.PodStatus { Name: "init1", Image: "bar-image", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("400m"), + v1.ResourceMemory: resource.MustParse("400Mi"), + }, + }, }, { Name: "init2", @@ -2352,23 +2499,25 @@ func TestComputePodActionsWithRestartableInitContainers(t *testing.T) { }, }, } { - pod, status := makeBasePodAndStatusWithRestartableInitContainers() - m.livenessManager.Set(status.ContainerStatuses[1].ID, proberesults.Success, basePod) - m.startupManager.Set(status.ContainerStatuses[1].ID, proberesults.Success, basePod) - m.livenessManager.Set(status.ContainerStatuses[2].ID, proberesults.Success, basePod) - m.startupManager.Set(status.ContainerStatuses[2].ID, proberesults.Success, basePod) - if test.mutatePodFn != nil { - test.mutatePodFn(pod) - } - if test.mutateStatusFn != nil { - test.mutateStatusFn(pod, status) - } - tCtx := ktesting.Init(t) - actions := m.computePodActions(tCtx, pod, status, false) - verifyActions(t, &test.actions, &actions, desc) - if test.resetStatusFn != nil { - test.resetStatusFn(status) - } + t.Run(desc, func(t *testing.T) { + pod, status := makeBasePodAndStatusWithRestartableInitContainers() + m.livenessManager.Set(status.ContainerStatuses[1].ID, proberesults.Success, basePod) + m.startupManager.Set(status.ContainerStatuses[1].ID, proberesults.Success, basePod) + m.livenessManager.Set(status.ContainerStatuses[2].ID, proberesults.Success, basePod) + m.startupManager.Set(status.ContainerStatuses[2].ID, proberesults.Success, basePod) + if test.mutatePodFn != nil { + test.mutatePodFn(pod) + } + if test.mutateStatusFn != nil { + test.mutateStatusFn(pod, status) + } + tCtx := ktesting.Init(t) + actions := m.computePodActions(tCtx, pod, status, false) + verifyActions(t, &test.actions, &actions, desc) + if test.resetStatusFn != nil { + test.resetStatusFn(status) + } + }) } } @@ -2566,6 +2715,7 @@ func TestComputePodActionsWithInitAndEphemeralContainers(t *testing.T) { } func TestComputePodActionsWithContainerRestartRules(t *testing.T) { + tCtx := ktesting.Init(t) // Make sure existing test cases pass with feature enabled featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ContainerRestartRules, true) TestComputePodActions(t) @@ -2576,8 +2726,7 @@ func TestComputePodActionsWithContainerRestartRules(t *testing.T) { containerRestartPolicyOnFailure = v1.ContainerRestartPolicyOnFailure containerRestartPolicyNever = v1.ContainerRestartPolicyNever ) - ctx := context.Background() - _, _, m, err := createTestRuntimeManager(ctx) + _, _, m, err := createTestRuntimeManager(tCtx) require.NoError(t, err) // Creating a pair reference pod and status for the test cases to refer @@ -2738,11 +2887,19 @@ func TestSyncPodWithSandboxAndDeletedPod(t *testing.T) { // GetPodStatus and the following SyncPod will not return errors in the // case where the pod has been deleted. We are not adding any pods into // the fakePodProvider so they are 'deleted'. - podStatus, err := m.GetPodStatus(tCtx, pod.UID, pod.Name, pod.Namespace) - assert.NoError(t, err) + runtimePod, err := m.GetPod(tCtx, pod.UID) + if err != nil { + runtimePod = &kubecontainer.Pod{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + } + } + podStatus, err := m.GetPodStatus(tCtx, runtimePod) + require.NoError(t, err) result := m.SyncPod(tCtx, pod, podStatus, []v1.Secret{}, backOff, false) // This will return an error if the pod has _not_ been deleted. - assert.NoError(t, result.Error()) + require.NoError(t, result.Error()) } func makeBasePodAndStatusWithInitAndEphemeralContainers() (*v1.Pod, *kubecontainer.PodStatus) { @@ -2912,11 +3069,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, @@ -2926,11 +3083,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, @@ -2984,11 +3141,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, @@ -2998,11 +3155,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, @@ -3060,11 +3217,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem600M.Value(), cpuLimit: cpu600m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem300M.Value(), cpuLimit: cpu300m.MilliValue(), }, @@ -3074,11 +3231,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem600M.Value(), cpuLimit: cpu600m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem300M.Value(), cpuLimit: cpu300m.MilliValue(), }, @@ -3172,11 +3329,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, @@ -3186,11 +3343,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, @@ -3225,11 +3382,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu200m.MilliValue(), }, @@ -3264,11 +3421,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[2], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu200m.MilliValue(), }, @@ -3279,6 +3436,91 @@ func TestComputePodActionsForPodResize(t *testing.T) { return &pa }, }, + "Update pod-level limits does not mutate container resources": { + setupFn: func(pod *v1.Pod) { + pod.Spec.Resources = &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: cpu100m.DeepCopy(), + v1.ResourceMemory: mem100M.DeepCopy(), + }, + } + c := &pod.Spec.Containers[1] + c.Resources = v1.ResourceRequirements{ + Limits: v1.ResourceList{}, + } + setupActuatedResources(pod, c, v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: cpu100m.DeepCopy(), + }, + }) + }, + getExpectedPodActionsFn: func(pod *v1.Pod, podStatus *kubecontainer.PodStatus) *podActions { + kcs1 := podStatus.FindContainerStatusByName(pod.Spec.Containers[0].Name) + kcs2 := podStatus.FindContainerStatusByName(pod.Spec.Containers[1].Name) + kcs3 := podStatus.FindContainerStatusByName(pod.Spec.Containers[2].Name) + pa := podActions{ + SandboxID: podStatus.SandboxStatuses[0].Id, + ContainersToStart: []int{}, + ContainersToKill: getKillMap(pod, podStatus, []int{}), + ContainersToUpdate: map[v1.ResourceName][]containerToUpdateInfo{ + v1.ResourceCPU: { + { + container: &pod.Spec.Containers[0], + kubeContainerID: kcs1.ID, + desiredContainerResources: resourceRequirements{ + memoryLimit: mem100M.Value(), + cpuLimit: cpu100m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{}, + }, + { + container: &pod.Spec.Containers[2], + kubeContainerID: kcs3.ID, + desiredContainerResources: resourceRequirements{ + memoryLimit: mem100M.Value(), + cpuLimit: cpu100m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{}, + }, + }, + v1.ResourceMemory: { + { + container: &pod.Spec.Containers[0], + kubeContainerID: kcs1.ID, + desiredContainerResources: resourceRequirements{ + memoryLimit: mem100M.Value(), + cpuLimit: cpu100m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{}, + }, + { + container: &pod.Spec.Containers[1], + kubeContainerID: kcs2.ID, + desiredContainerResources: resourceRequirements{ + memoryLimit: mem100M.Value(), + cpuLimit: cpu100m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{ + cpuLimit: cpu100m.MilliValue(), + }, + }, + { + container: &pod.Spec.Containers[2], + kubeContainerID: kcs3.ID, + desiredContainerResources: resourceRequirements{ + memoryLimit: mem100M.Value(), + cpuLimit: cpu100m.MilliValue(), + }, + currentContainerResources: &resourceRequirements{}, + }, + }, + }, + UpdatePodLevelResources: true, + } + return &pa + }, + podLevelResizeEnabled: true, + }, "Nothing when spec.Resources and status.Resources are equal": { setupFn: func(pod *v1.Pod) { c := &pod.Spec.Containers[1] @@ -3404,11 +3646,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[1], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem200M.Value(), cpuLimit: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, @@ -3444,11 +3686,11 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[2], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu200m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem100M.Value(), cpuLimit: cpu100m.MilliValue(), }, @@ -3530,13 +3772,13 @@ func TestComputePodActionsForPodResize(t *testing.T) { { container: &pod.Spec.Containers[2], kubeContainerID: kcs.ID, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: mem200M.Value(), memoryRequest: mem100M.Value(), cpuLimit: cpu200m.MilliValue(), cpuRequest: cpu100m.MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: mem200M.Value(), memoryRequest: mem200M.Value(), cpuLimit: cpu200m.MilliValue(), @@ -3551,9 +3793,7 @@ func TestComputePodActionsForPodResize(t *testing.T) { }, } { t.Run(desc, func(t *testing.T) { - if test.podLevelResizeEnabled { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true) - } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, test.podLevelResizeEnabled) pod, status := makeBasePodAndStatus() for idx := range pod.Spec.Containers { // default resize policy when pod resize feature is enabled @@ -3573,7 +3813,22 @@ func TestComputePodActionsForPodResize(t *testing.T) { tCtx := ktesting.Init(t) expectedActions := test.getExpectedPodActionsFn(pod, status) + + // Capture container resource limits before computePodActions to check for mutation + preComputeLimits := make([]v1.ResourceList, len(pod.Spec.Containers)) + for i := range pod.Spec.Containers { + preComputeLimits[i] = pod.Spec.Containers[i].Resources.Limits.DeepCopy() + } + actions := m.computePodActions(tCtx, pod, status, false) + + // Mutation check: Ensure pod.Spec.Containers[i].Resources.Limits is not mutated + for i := range pod.Spec.Containers { + if !reflect.DeepEqual(preComputeLimits[i], pod.Spec.Containers[i].Resources.Limits) { + t.Errorf("pod.Spec.Containers[%d].Resources.Limits was mutated! pre: %v, post: %v", i, preComputeLimits[i], pod.Spec.Containers[i].Resources.Limits) + } + } + verifyActions(t, expectedActions, &actions, desc) }) } @@ -3612,7 +3867,7 @@ func TestUpdatePodContainerResources(t *testing.T) { res350m350Mi := v1.ResourceList{v1.ResourceCPU: cpu350m, v1.ResourceMemory: mem350M} pod, _ := makeBasePodAndStatusWithRestartableInitContainers() - makeAndSetFakePod(t, m, fakeRuntime, pod) + makeAndSetFakePod(tCtx, m, fakeRuntime, pod) for dsc, tc := range map[string]struct { resourceName v1.ResourceName @@ -3664,13 +3919,13 @@ func TestUpdatePodContainerResources(t *testing.T) { return containerToUpdateInfo{ container: container, kubeContainerID: kubecontainer.ContainerID{}, - desiredContainerResources: containerResources{ + desiredContainerResources: resourceRequirements{ memoryLimit: tc.apiSpecResources[idx].Limits.Memory().Value(), memoryRequest: tc.apiSpecResources[idx].Requests.Memory().Value(), cpuLimit: tc.apiSpecResources[idx].Limits.Cpu().MilliValue(), cpuRequest: tc.apiSpecResources[idx].Requests.Cpu().MilliValue(), }, - currentContainerResources: &containerResources{ + currentContainerResources: &resourceRequirements{ memoryLimit: tc.apiStatusResources[idx].Limits.Memory().Value(), memoryRequest: tc.apiStatusResources[idx].Requests.Memory().Value(), cpuLimit: tc.apiStatusResources[idx].Limits.Cpu().MilliValue(), @@ -3863,23 +4118,28 @@ func TestDoPodResizeAction(t *testing.T) { metrics.PodResizeDurationMilliseconds.Reset() for i, tc := range []struct { - testName string - currentResources containerResources - desiredResources containerResources - updatedResources []v1.ResourceName - otherContainersHaveLimits bool - runtimeErrors map[string][]error - expectedError string - expectedErrorMessage string - expectPodCgroupUpdates int + testName string + currentResources resourceRequirements + desiredResources resourceRequirements + updatedResources []v1.ResourceName + otherContainersHaveLimits bool + runtimeErrors map[string][]error + expectedError string + expectedErrorMessage string + expectPodCgroupUpdates int + injectPodUpdateCgroupsError error + currentPodLevelResources *resourceRequirements + desiredPodLevelResources *resourceRequirements + updatedPodLevelResources bool + enablePLR bool }{ { testName: "Increase cpu and memory requests and limits, with computed pod limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, cpuLimit: 100, memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 200, cpuLimit: 200, memoryRequest: 200, memoryLimit: 200, }, @@ -3889,11 +4149,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Increase cpu and memory requests and limits, with computed pod limits and set a runtime error", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, cpuLimit: 100, memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 200, cpuLimit: 200, memoryRequest: 200, memoryLimit: 200, }, @@ -3906,11 +4166,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Increase cpu and memory requests and limits, without computed pod limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, cpuLimit: 100, memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 200, cpuLimit: 200, memoryRequest: 200, memoryLimit: 200, }, @@ -3921,11 +4181,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Increase cpu and memory requests only", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, cpuLimit: 200, memoryRequest: 100, memoryLimit: 200, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 150, cpuLimit: 200, memoryRequest: 150, memoryLimit: 200, }, @@ -3934,11 +4194,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Resize memory request no limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 200, }, @@ -3947,11 +4207,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Resize cpu request no limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 200, memoryRequest: 100, }, @@ -3960,11 +4220,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Add limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 100, cpuLimit: 100, memoryRequest: 100, memoryLimit: 100, }, @@ -3973,11 +4233,11 @@ func TestDoPodResizeAction(t *testing.T) { }, { testName: "Add limits and pod limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 100, cpuLimit: 100, memoryRequest: 100, memoryLimit: 100, }, @@ -3985,12 +4245,239 @@ func TestDoPodResizeAction(t *testing.T) { updatedResources: []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory}, expectPodCgroupUpdates: 2, // cpu lim, memory lim }, + { + testName: "Fail updatePodSandboxResources blocks resize", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 200, cpuLimit: 200, + }, + updatedResources: []v1.ResourceName{v1.ResourceCPU}, + expectPodCgroupUpdates: 0, + runtimeErrors: map[string][]error{ + "UpdatePodSandboxResources": {fmt.Errorf("runtime sandbox update failed")}, + }, + expectedError: "ResizePodInPlaceError", + expectedErrorMessage: "failed to notify runtime for UpdatePodSandboxResources (resource=cpu); resize rejected: updatePodSandboxResources failed for sanboxID \"sandbox-id\": runtime sandbox update failed", + }, + { + testName: "Fail SetPodCgroupConfig triggers rollback of Sandbox resources", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 200, cpuLimit: 200, + }, + updatedResources: []v1.ResourceName{v1.ResourceCPU}, + expectPodCgroupUpdates: 1, + injectPodUpdateCgroupsError: fmt.Errorf("cgroup update failed"), + expectedError: "ResizePodInPlaceError", + expectedErrorMessage: "cgroup update failed", + }, + { + testName: "Ignore Unimplemented error from updatePodSandboxResources", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 200, cpuLimit: 200, + }, + updatedResources: []v1.ResourceName{v1.ResourceCPU}, + expectPodCgroupUpdates: 1, // Should proceed to cgroups despite the error + runtimeErrors: map[string][]error{ + // Use a GRPC Unimplemented error + "UpdatePodSandboxResources": {status.Error(codes.Unimplemented, "not supported")}, + }, + // No error expected because we swallow Unimplemented + expectedError: "", + expectedErrorMessage: "", + }, + { + testName: "Ignore Unimplemented message from updatePodSandboxResources", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 200, cpuLimit: 200, + }, + updatedResources: []v1.ResourceName{v1.ResourceCPU}, + expectPodCgroupUpdates: 1, // Should proceed to cgroups despite the error + runtimeErrors: map[string][]error{ + // Use a GRPC Unimplemented error + "UpdatePodSandboxResources": {status.Error(codes.Unknown, "not implemented yet")}, + }, + // No error expected because we swallow Unimplemented + expectedError: "", + expectedErrorMessage: "", + }, + { + testName: "Resize pod-level memory request only (skips cgroup write, updates actuated)", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 100, + }, + currentPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 100, + }, + desiredPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 200, memoryLimit: 100, // Memory request increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{}, + enablePLR: true, + }, + { + testName: "Resize pod-level memory limit (updates cgroups and actuated)", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 100, + }, + currentPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 100, + }, + desiredPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + memoryRequest: 100, memoryLimit: 200, // Memory limit increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{}, + expectPodCgroupUpdates: 1, + enablePLR: true, + }, + { + testName: "Resize pod-level CPU request (updates cgroups and actuated)", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + currentPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredPodLevelResources: &resourceRequirements{ + cpuRequest: 200, cpuLimit: 100, // CPU request increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{}, + expectPodCgroupUpdates: 1, + enablePLR: true, + }, + { + testName: "Resize pod-level CPU limit (updates cgroups and actuated)", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + currentPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 200, // CPU limit increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{}, + expectPodCgroupUpdates: 1, + enablePLR: true, + }, + { + testName: "Resize pod-level CPU limit and container-level CPU limit (updates cgroups and actuated)", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 200, // Container limit increase + }, + currentPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 100, + }, + desiredPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 300, // Pod limit increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{v1.ResourceCPU}, + expectPodCgroupUpdates: 1, // Pod level cgroup update + enablePLR: true, + }, + { + testName: "Resize pod-level memory request and container-level memory request (updates actuated)", + currentResources: resourceRequirements{ + memoryRequest: 100, memoryLimit: 200, + }, + desiredResources: resourceRequirements{ + memoryRequest: 150, memoryLimit: 200, // Container request increase + }, + currentPodLevelResources: &resourceRequirements{ + memoryRequest: 100, memoryLimit: 200, + }, + desiredPodLevelResources: &resourceRequirements{ + memoryRequest: 200, memoryLimit: 200, // Pod request increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{v1.ResourceMemory}, + expectPodCgroupUpdates: 0, // Memory request doesn't update cgroup + enablePLR: true, + }, + { + testName: "Resize pod-level CPU request and container-level CPU request (updates cgroups and actuated)", + currentResources: resourceRequirements{ + cpuRequest: 100, cpuLimit: 200, + }, + desiredResources: resourceRequirements{ + cpuRequest: 150, cpuLimit: 200, // Container request increase + }, + currentPodLevelResources: &resourceRequirements{ + cpuRequest: 100, cpuLimit: 200, + }, + desiredPodLevelResources: &resourceRequirements{ + cpuRequest: 200, cpuLimit: 200, // Pod request increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{v1.ResourceCPU}, + expectPodCgroupUpdates: 1, // Pod level cgroup update for cpu shares + enablePLR: true, + }, + { + testName: "Resize pod-level memory limit and container-level memory limit (updates cgroups and actuated)", + currentResources: resourceRequirements{ + memoryLimit: 200, + }, + desiredResources: resourceRequirements{ + memoryLimit: 250, // Container limit increase + }, + currentPodLevelResources: &resourceRequirements{ + memoryLimit: 200, + }, + desiredPodLevelResources: &resourceRequirements{ + memoryLimit: 300, // Pod limit increase + }, + updatedPodLevelResources: true, + updatedResources: []v1.ResourceName{v1.ResourceMemory}, + expectPodCgroupUpdates: 1, // Pod level cgroup update for memory limit + enablePLR: true, + }, } { t.Run(tc.testName, func(t *testing.T) { _, _, m, err := createTestRuntimeManagerWithErrors(tCtx, tc.runtimeErrors) require.NoError(t, err) m.cpuCFSQuota = true // Enforce CPU Limits + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, tc.enablePLR) + mockCM := cmtesting.NewMockContainerManager(t) mockCM.EXPECT().PodHasExclusiveCPUs(mock.Anything).Return(false).Maybe() mockCM.EXPECT().ContainerHasExclusiveCPUs(mock.Anything, mock.Anything).Return(false).Maybe() @@ -4015,11 +4502,42 @@ func TestDoPodResizeAction(t *testing.T) { }, nil).Maybe() if tc.expectPodCgroupUpdates > 0 { // TODO: Update to use proper logger once contextual logging migration is complete - mockPCM.EXPECT().SetPodCgroupConfig(klog.TODO(), mock.Anything, mock.Anything).Return(nil).Times(tc.expectPodCgroupUpdates) + call := mockPCM.EXPECT().SetPodCgroupConfig(klog.TODO(), mock.Anything, mock.Anything) + if tc.injectPodUpdateCgroupsError != nil { + call.Return(tc.injectPodUpdateCgroupsError).Times(1) + } else { + call.Return(nil).Times(tc.expectPodCgroupUpdates) + } } pod, kps := makeBasePodAndStatus() - // pod spec and allocated resources are already updated as desired when doPodResizeAction() is called. + if tc.desiredPodLevelResources != nil { + // pod spec and allocated resources are already updated as desired when doPodResizeAction() is called. + pod.Spec.Resources = &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(tc.desiredPodLevelResources.cpuRequest, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(tc.desiredPodLevelResources.memoryRequest, resource.BinarySI), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(tc.desiredPodLevelResources.cpuLimit, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(tc.desiredPodLevelResources.memoryLimit, resource.BinarySI), + }, + } + } + if tc.currentPodLevelResources != nil { + // Seed initial actuated state + initialActuated := &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(tc.currentPodLevelResources.cpuRequest, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(tc.currentPodLevelResources.memoryRequest, resource.BinarySI), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(tc.currentPodLevelResources.cpuLimit, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(tc.currentPodLevelResources.memoryLimit, resource.BinarySI), + }, + } + require.NoError(t, m.actuatedState.SetPodLevelResources(pod.UID, initialActuated)) + } pod.Spec.Containers[0].Resources = v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: *resource.NewMilliQuantity(tc.desiredResources.cpuRequest, resource.DecimalSI), @@ -4076,7 +4594,9 @@ func TestDoPodResizeAction(t *testing.T) { } actions := podActions{ - ContainersToUpdate: containersToUpdate, + ContainersToUpdate: containersToUpdate, + UpdatePodLevelResources: tc.updatedPodLevelResources, + SandboxID: "sandbox-id", } resizeResult := m.doPodResizeAction(tCtx, pod, kps, actions) @@ -4086,6 +4606,18 @@ func TestDoPodResizeAction(t *testing.T) { require.Equal(t, tc.expectedErrorMessage, resizeResult.Message) } else { require.NoError(t, resizeResult.Error, resizeResult.Message) + if tc.desiredPodLevelResources != nil { + // VERIFY Actuated State for successful resizes + updatedActuated, found := m.actuatedState.GetPodLevelResources(pod.UID) + require.True(t, found, "actuated resources should exist for pod") + if tc.enablePLR { + assert.Equal(t, tc.desiredPodLevelResources.memoryRequest, updatedActuated.Requests.Memory().Value(), tc.testName) + assert.Equal(t, tc.desiredPodLevelResources.cpuRequest, updatedActuated.Requests.Cpu().MilliValue(), tc.testName) + } else { + assert.Equal(t, tc.currentPodLevelResources.memoryRequest, updatedActuated.Requests.Memory().Value(), tc.testName) + assert.Equal(t, tc.currentPodLevelResources.cpuRequest, updatedActuated.Requests.Cpu().MilliValue(), tc.testName) + } + } } mock.AssertExpectationsForObjects(t, mockPCM) @@ -4103,18 +4635,18 @@ func TestValidatePodResizeAction(t *testing.T) { for _, tc := range []struct { testName string - currentResources, desiredResources containerResources + currentResources, desiredResources resourceRequirements currentPodMemLimit, desiredPodMemLimit *int64 containerMemoryUsage, podMemoryUsage *uint64 expectedError bool }{ { testName: "Resize memory request no limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ cpuRequest: 100, memoryRequest: 200, }, @@ -4122,10 +4654,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Add container limits, low usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, containerMemoryUsage: ptr.To[uint64](10), @@ -4134,10 +4666,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Add container limits, high usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, containerMemoryUsage: ptr.To[uint64](200), @@ -4146,10 +4678,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Add container limits, missing container usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, podMemoryUsage: ptr.To[uint64](10), @@ -4157,10 +4689,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Add container limits, missing pod usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, containerMemoryUsage: ptr.To[uint64](10), @@ -4168,20 +4700,20 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Increase container limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 200, }, expectedError: false, }, { testName: "Decrease container limits, low usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 200, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, containerMemoryUsage: ptr.To[uint64](20), @@ -4190,10 +4722,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Decrease container limits, high usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 200, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, containerMemoryUsage: ptr.To[uint64](150), @@ -4202,10 +4734,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Add pod limit, low usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, desiredPodMemLimit: ptr.To[int64](100), @@ -4215,10 +4747,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Add pod limit, high usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, desiredPodMemLimit: ptr.To[int64](100), @@ -4228,10 +4760,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Increase pod limits", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, currentPodMemLimit: ptr.To[int64](100), @@ -4242,10 +4774,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Decrease pod limits, low usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, currentPodMemLimit: ptr.To[int64](200), @@ -4256,10 +4788,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Decrease pod limits, high usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, currentPodMemLimit: ptr.To[int64](200), @@ -4270,10 +4802,10 @@ func TestValidatePodResizeAction(t *testing.T) { }, { testName: "Decrease pod limits, missing usage", - currentResources: containerResources{ + currentResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, - desiredResources: containerResources{ + desiredResources: resourceRequirements{ memoryRequest: 100, memoryLimit: 100, }, currentPodMemLimit: ptr.To[int64](200), @@ -4735,6 +5267,17 @@ func TestIsPodResizeInProgress(t *testing.T) { }}, expectHasResize: true, inplacePodLevelResizeEnabled: true, + }, { + name: "plr mismatch during initial creation", + podLevelResources: &testPLR{ + allocated: testResources{cpuReq: 200}, + }, + containers: []testContainer{{ + allocated: testResources{cpuReq: 100}, + isRunning: false, + }}, + expectHasResize: false, + inplacePodLevelResizeEnabled: true, }} mkRequirements := func(r testResources) v1.ResourceRequirements { @@ -4769,9 +5312,7 @@ func TestIsPodResizeInProgress(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if test.inplacePodLevelResizeEnabled { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true) - } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, test.inplacePodLevelResizeEnabled) tCtx := ktesting.Init(t) _, _, m, err := createTestRuntimeManager(tCtx) require.NoError(t, err) @@ -5121,3 +5662,287 @@ func TestCmpActuatedAllocated(t *testing.T) { }) } } + +// testRuntimeHelper implements the RuntimeHelper interface for testing OnPodSandboxReady invocation. +type testRuntimeHelper struct { + *containertest.FakeRuntimeHelper + onPodSandboxReadyCalled bool + onPodSandboxReadyPod *v1.Pod + onPodSandboxReadyCtx context.Context + onPodSandboxReadyError error + captureStateFunc func() // optional function to capture state when OnPodSandboxReady is called + prepareDynamicResourcesCalled bool + prepareDynamicResourcesError error +} + +func (t *testRuntimeHelper) OnPodSandboxReady(ctx context.Context, pod *v1.Pod) error { + t.onPodSandboxReadyCalled = true + t.onPodSandboxReadyPod = pod + t.onPodSandboxReadyCtx = ctx + if t.captureStateFunc != nil { + t.captureStateFunc() + } + return t.onPodSandboxReadyError +} + +func (t *testRuntimeHelper) PrepareDynamicResources(ctx context.Context, pod *v1.Pod) error { + t.prepareDynamicResourcesCalled = true + return t.prepareDynamicResourcesError +} + +// TestOnPodSandboxReadyInvocation verifies OnPodSandboxReady is called at the correct time +// and validates the order between the DRA allocate calls and PodReadytoStartContainers condition. +// It works in the following order: +// 1. setup test helper and inject errors +// 2. create pod (with/without devices) +// 3. run SyncPod +// 4. verify device allocation +// 5. verify OnPodSandboxReady invocation +// 6. verify final pod state +func TestOnPodSandboxReadyInvocation(t *testing.T) { + tCtx := ktesting.Init(t) + + tests := []struct { + name string + onPodSandboxReadyShouldErr bool + deviceAllocationShouldErr bool + expectOnPodSandboxReady bool + expectSyncPodSuccess bool + expectDeviceAllocation bool + enablePodReadyToStartContainers bool + description string + }{ + { + name: "OnPodSandboxReady succeeds with feature enabled", + onPodSandboxReadyShouldErr: false, + deviceAllocationShouldErr: false, + expectOnPodSandboxReady: true, + expectSyncPodSuccess: true, + expectDeviceAllocation: false, + enablePodReadyToStartContainers: true, + description: "Verifies OnPodSandboxReady is called and succeeds with PodReadyToStartContainersCondition feature gate enabled", + }, + { + name: "OnPodSandboxReady succeeds with feature disabled", + onPodSandboxReadyShouldErr: false, + deviceAllocationShouldErr: false, + expectOnPodSandboxReady: true, + expectSyncPodSuccess: true, + expectDeviceAllocation: false, + enablePodReadyToStartContainers: false, + description: "Verifies OnPodSandboxReady is called and succeeds with PodReadyToStartContainersCondition feature gate disabled", + }, + { + name: "OnPodSandboxReady fails but SyncPod continues with feature enabled", + onPodSandboxReadyShouldErr: true, + deviceAllocationShouldErr: false, + expectOnPodSandboxReady: true, + expectSyncPodSuccess: true, // SyncPod still succeed even if OnPodSandboxReady fails + expectDeviceAllocation: false, + enablePodReadyToStartContainers: true, + description: "Verifies OnPodSandboxReady errors don't block pod creation with PodReadyToStartContainersCondition feature gate enabled", + }, + { + name: "OnPodSandboxReady fails but SyncPod continues with feature disabled", + onPodSandboxReadyShouldErr: true, + deviceAllocationShouldErr: false, + expectOnPodSandboxReady: true, + expectSyncPodSuccess: true, // SyncPod still succeed even if OnPodSandboxReady fails + expectDeviceAllocation: false, + enablePodReadyToStartContainers: false, + description: "Verifies OnPodSandboxReady errors don't block pod creation with PodReadyToStartContainersCondition feature gate disabled", + }, + { + name: "PrepareDynamicResources (device allocation) called before OnPodSandboxReady with feature enabled", + onPodSandboxReadyShouldErr: false, + deviceAllocationShouldErr: false, + expectOnPodSandboxReady: true, + expectSyncPodSuccess: true, + expectDeviceAllocation: true, + enablePodReadyToStartContainers: true, + description: "Verifies the order (PrepareDynamicResources -> OnPodSandboxReady) in case of pod with ResourceClaims with PodReadyToStartContainersCondition feature gate enabled", + }, + { + name: "PrepareDynamicResources (device allocation) called before OnPodSandboxReady with feature disabled", + onPodSandboxReadyShouldErr: false, + deviceAllocationShouldErr: false, + expectOnPodSandboxReady: true, + expectSyncPodSuccess: true, + expectDeviceAllocation: true, + enablePodReadyToStartContainers: false, + description: "Verifies the order (PrepareDynamicResources -> OnPodSandboxReady) in case of pod with ResourceClaims with PodReadyToStartContainersCondition feature gate disabled", + }, + { + name: "PrepareDynamicResources (device allocation) failure prevents sandbox creation with feature enabled", + onPodSandboxReadyShouldErr: false, + deviceAllocationShouldErr: true, + expectOnPodSandboxReady: false, + expectSyncPodSuccess: true, // SyncPod doesn't return error, just returns early if `PrepareDynamicResources` call ends up failing + expectDeviceAllocation: true, + enablePodReadyToStartContainers: true, + description: "Verifies PrepareDynamicResources failure causes early return in case of pod with ResourceClaims with PodReadyToStartContainersCondition feature gate enabled", + }, + { + name: "PrepareDynamicResources (device allocation) failure prevents sandbox creation with feature disabled", + onPodSandboxReadyShouldErr: false, + deviceAllocationShouldErr: true, + expectOnPodSandboxReady: false, + expectSyncPodSuccess: true, // SyncPod doesn't return error, just returns early if `PrepareDynamicResources` call ends up failing + expectDeviceAllocation: true, + enablePodReadyToStartContainers: false, + description: "Verifies PrepareDynamicResources failure causes early return in case of pod with ResourceClaims with PodReadyToStartContainersCondition feature gate disabled", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodReadyToStartContainersCondition, test.enablePodReadyToStartContainers) + + // step 1 - setup test helper and inject errors + fakeRuntime, fakeImage, m, err := createTestRuntimeManager(tCtx) + require.NoError(t, err) + + testHelper := &testRuntimeHelper{ + FakeRuntimeHelper: &containertest.FakeRuntimeHelper{}, + } + if test.onPodSandboxReadyShouldErr { + testHelper.onPodSandboxReadyError = fmt.Errorf("OnPodSandboxReady intentionally failed for testing") + } + if test.deviceAllocationShouldErr { + testHelper.prepareDynamicResourcesError = fmt.Errorf("PrepareDynamicResources intentionally failed for testing") + } + m.runtimeHelper = testHelper + + // step 2 - create pod (with/without devices) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "test-pod-uid", + Name: "test-pod", + Namespace: "test-namespace", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test-container", + Image: "busybox", + ImagePullPolicy: v1.PullIfNotPresent, + }, + }, + }, + } + if test.expectDeviceAllocation { + pod.Spec.ResourceClaims = []v1.PodResourceClaim{ + { + Name: "test-device", + }, + } + } + + // step 3 - run SyncPod + backOff := flowcontrol.NewBackOff(time.Second, time.Minute) + result := m.SyncPod(tCtx, pod, &kubecontainer.PodStatus{}, []v1.Secret{}, backOff, false) + + if test.expectSyncPodSuccess { + require.NoError(t, result.Error(), test.description) + } else { + require.Error(t, result.Error(), test.description) + } + + // step 4 - verify device allocation + if test.expectDeviceAllocation { + require.True(t, testHelper.prepareDynamicResourcesCalled, + "PrepareDynamicResources should be called for pods with resource claims") + + if test.expectOnPodSandboxReady && testHelper.onPodSandboxReadyCalled { + require.True(t, testHelper.prepareDynamicResourcesCalled, + "PrepareDynamicResources must be called before OnPodSandboxReady") + } + } + + // step 5 - verify OnPodSandboxReady invocation + assert.Equal(t, test.expectOnPodSandboxReady, testHelper.onPodSandboxReadyCalled, "OnPodSandboxReady invocation mismatch: "+test.description) + + if test.expectOnPodSandboxReady { + assert.NotNil(t, testHelper.onPodSandboxReadyPod, "OnPodSandboxReady should receive pod") + assert.Equal(t, pod.UID, testHelper.onPodSandboxReadyPod.UID, "OnPodSandboxReady should receive correct pod UID") + assert.Equal(t, pod.Name, testHelper.onPodSandboxReadyPod.Name, "OnPodSandboxReady should receive correct pod name") + assert.Equal(t, pod.Namespace, testHelper.onPodSandboxReadyPod.Namespace, "OnPodSandboxReady should receive correct pod namespace") + assert.NotNil(t, testHelper.onPodSandboxReadyCtx, "OnPodSandboxReady should receive context") + + require.Len(t, fakeRuntime.Sandboxes, 1, "sandbox should be created before OnPodSandboxReady") + for _, sandbox := range fakeRuntime.Sandboxes { + require.Equal(t, runtimeapi.PodSandboxState_SANDBOX_READY, sandbox.State, "sandbox should be ready when OnPodSandboxReady is invoked") + } + } + + // step 6 - verify the final pod state + if test.expectSyncPodSuccess && !test.deviceAllocationShouldErr { + assert.Len(t, fakeRuntime.Containers, 1, "container should be created") + assert.Len(t, fakeImage.Images, 1, "image should be pulled") + for _, c := range fakeRuntime.Containers { + assert.Equal(t, runtimeapi.ContainerState_CONTAINER_RUNNING, c.State, "container should be running") + } + } + + if test.deviceAllocationShouldErr { + require.Empty(t, fakeRuntime.Sandboxes, "sandbox should not be created when device allocation fails") + require.Empty(t, fakeRuntime.Containers, "containers should not be created when device allocation fails") + } + }) + } +} + +// TestOnPodSandboxReadyTiming tests that OnPodSandboxReady is invoked after sandbox +// creation and network setup but before image pulling. +func TestOnPodSandboxReadyTiming(t *testing.T) { + tCtx := ktesting.Init(t) + fakeRuntime, fakeImage, m, err := createTestRuntimeManager(tCtx) + require.NoError(t, err) + + // track the state of pod when OnPodSandboxReady is invoked + var sandboxCount int + var containerCount int + var imageCount int + + testHelper := &testRuntimeHelper{ + FakeRuntimeHelper: &containertest.FakeRuntimeHelper{}, + captureStateFunc: func() { + sandboxCount = len(fakeRuntime.Sandboxes) + containerCount = len(fakeRuntime.Containers) + imageCount = len(fakeImage.Images) + }, + } + + m.runtimeHelper = testHelper + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "timing-test-pod", + Name: "timing-test", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test-container", + Image: "busybox", + ImagePullPolicy: v1.PullIfNotPresent, + }, + }, + }, + } + + backOff := flowcontrol.NewBackOff(time.Second, time.Minute) + result := m.SyncPod(tCtx, pod, &kubecontainer.PodStatus{}, []v1.Secret{}, backOff, false) + require.NoError(t, result.Error()) + + // verify the order that OnPodSandboxReady should be invoked after sandbox creation but before containers + assert.Equal(t, 1, sandboxCount, "sandbox should exist when OnPodSandboxReady is invoked") + assert.Equal(t, 0, containerCount, "containers should not exist yet when OnPodSandboxReady is invoked") + // Note that image may or may not be pulled at OnPodSandboxReady time depending on whether image exists + t.Logf("At OnPodSandboxReady time: sandboxes=%d, containers=%d, images=%d", sandboxCount, containerCount, imageCount) + + // verify the final state of pod + assert.Len(t, fakeRuntime.Sandboxes, 1, "final sandbox count") + assert.Len(t, fakeRuntime.Containers, 1, "final container count") +} diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go index 0b9293c780d88..843838cae2fe8 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go @@ -27,7 +27,6 @@ import ( kubetypes "k8s.io/apimachinery/pkg/types" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/klog/v2" - "k8s.io/kubelet/pkg/types" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" runtimeutil "k8s.io/kubernetes/pkg/kubelet/kuberuntime/util" "k8s.io/kubernetes/pkg/kubelet/util" @@ -92,7 +91,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(ctx context.Context Annotations: newPodAnnotations(pod), } - dnsConfig, err := m.runtimeHelper.GetPodDNS(pod) + dnsConfig, err := m.runtimeHelper.GetPodDNS(ctx, pod) if err != nil { return nil, err } @@ -100,7 +99,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(ctx context.Context if !kubecontainer.IsHostNetworkPod(pod) { // TODO: Add domain support in new runtime interface - podHostname, podDomain, err := m.runtimeHelper.GeneratePodHostNameAndDomain(pod) + podHostname, podDomain, err := m.runtimeHelper.GeneratePodHostNameAndDomain(logger, pod) if err != nil { return nil, err } @@ -136,7 +135,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(ctx context.Context podSandboxConfig.PortMappings = portMappings } - lc, err := m.generatePodSandboxLinuxConfig(pod) + lc, err := m.generatePodSandboxLinuxConfig(ctx, pod) if err != nil { return nil, err } @@ -161,7 +160,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(ctx context.Context // We've to call PodSandboxLinuxConfig always irrespective of the underlying OS as securityContext is not part of // podSandboxConfig. It is currently part of LinuxPodSandboxConfig. In future, if we have securityContext pulled out // in podSandboxConfig we should be able to use it. -func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *v1.Pod) (*runtimeapi.LinuxPodSandboxConfig, error) { +func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(ctx context.Context, pod *v1.Pod) (*runtimeapi.LinuxPodSandboxConfig, error) { cgroupParent := m.runtimeHelper.GetPodCgroupParent(pod) lc := &runtimeapi.LinuxPodSandboxConfig{ CgroupParent: cgroupParent, @@ -193,7 +192,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *v1.Pod) ( if sc.RunAsGroup != nil && runtime.GOOS != "windows" { lc.SecurityContext.RunAsGroup = &runtimeapi.Int64Value{Value: int64(*sc.RunAsGroup)} } - namespaceOptions, err := runtimeutil.NamespacesForPod(pod, m.runtimeHelper, m.runtimeClassManager) + namespaceOptions, err := runtimeutil.NamespacesForPod(ctx, pod, m.runtimeHelper, m.runtimeClassManager) if err != nil { return nil, err } @@ -279,28 +278,6 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxWindowsConfig(pod *v1.Pod) return wc, nil } -// getKubeletSandboxes lists all (or just the running) sandboxes managed by kubelet. -func (m *kubeGenericRuntimeManager) getKubeletSandboxes(ctx context.Context, all bool) ([]*runtimeapi.PodSandbox, error) { - logger := klog.FromContext(ctx) - var filter *runtimeapi.PodSandboxFilter - if !all { - readyState := runtimeapi.PodSandboxState_SANDBOX_READY - filter = &runtimeapi.PodSandboxFilter{ - State: &runtimeapi.PodSandboxStateValue{ - State: readyState, - }, - } - } - - resp, err := m.runtimeService.ListPodSandbox(ctx, filter) - if err != nil { - logger.Error(err, "Failed to list pod sandboxes") - return nil, err - } - - return resp, nil -} - // determinePodSandboxIP determines the IP addresses of the given pod sandbox. func (m *kubeGenericRuntimeManager) determinePodSandboxIPs(ctx context.Context, podNamespace, podName string, podSandbox *runtimeapi.PodSandboxStatus) []string { logger := klog.FromContext(ctx) @@ -334,19 +311,11 @@ func (m *kubeGenericRuntimeManager) determinePodSandboxIPs(ctx context.Context, return podIPs } -// getPodSandboxID gets the sandbox id by podUID and returns ([]sandboxID, error). +// getPodSandboxIDByPodUID gets the sandbox id by podUID and returns ([]sandboxID, error). // Param state could be nil in order to get all sandboxes belonging to same pod. -func (m *kubeGenericRuntimeManager) getSandboxIDByPodUID(ctx context.Context, podUID kubetypes.UID, state *runtimeapi.PodSandboxState) ([]string, error) { +func (m *kubeGenericRuntimeManager) getSandboxIDByPodUID(ctx context.Context, podUID kubetypes.UID) ([]string, error) { logger := klog.FromContext(ctx) - filter := &runtimeapi.PodSandboxFilter{ - LabelSelector: map[string]string{types.KubernetesPodUIDLabel: string(podUID)}, - } - if state != nil { - filter.State = &runtimeapi.PodSandboxStateValue{ - State: *state, - } - } - sandboxes, err := m.runtimeService.ListPodSandbox(ctx, filter) + sandboxes, err := m.getSandboxes(ctx, listOptions{podUID: podUID}) if err != nil { logger.Error(err, "Failed to list sandboxes for pod", "podUID", podUID) return nil, err @@ -358,7 +327,7 @@ func (m *kubeGenericRuntimeManager) getSandboxIDByPodUID(ctx context.Context, po // Sort with newest first. sandboxIDs := make([]string, len(sandboxes)) - sort.Sort(podSandboxByCreated(sandboxes)) + sort.Sort(podSandboxByCreatedThenID(sandboxes)) for i, s := range sandboxes { sandboxIDs[i] = s.Id } @@ -368,7 +337,7 @@ func (m *kubeGenericRuntimeManager) getSandboxIDByPodUID(ctx context.Context, po // GetPortForward gets the endpoint the runtime will serve the port-forward request from. func (m *kubeGenericRuntimeManager) GetPortForward(ctx context.Context, podName, podNamespace string, podUID kubetypes.UID, ports []int32) (*url.URL, error) { - sandboxIDs, err := m.getSandboxIDByPodUID(ctx, podUID, nil) + sandboxIDs, err := m.getSandboxIDByPodUID(ctx, podUID) if err != nil { return nil, fmt.Errorf("failed to find sandboxID for pod %s: %v", format.PodDesc(podName, podNamespace, podUID), err) } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go index 35acabb9c4891..a3ae9c1337466 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux_test.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux_test.go index 8a64d07dae3f3..cb224dbde641d 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2021 The Kubernetes Authors. @@ -257,7 +256,7 @@ func TestGeneratePodSandboxLinuxConfigSupplementalGroupsPolicy(t *testing.T) { } for i, test := range tests { - config, err := m.generatePodSandboxLinuxConfig(test.pod) + config, err := m.generatePodSandboxLinuxConfig(tCtx, test.pod) if test.expectedErr { assert.NotEmptyf(t, err, "TestCase[%d]: %s", i, test.description) assert.Emptyf(t, config, "TestCase[%d]: %s", i, test.description) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_test.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_test.go index b82face5675db..bea6eb6ae6d74 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_test.go @@ -133,7 +133,7 @@ func TestGeneratePodSandboxLinuxConfigSeccomp(t *testing.T) { } for i, test := range tests { - config, _ := m.generatePodSandboxLinuxConfig(test.pod) + config, _ := m.generatePodSandboxLinuxConfig(tCtx, test.pod) actualProfile := config.SecurityContext.Seccomp.ProfileType.String() assert.EqualValues(t, test.expectedProfile, actualProfile, "TestCase[%d]: %s", i, test.description) } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_unsupported.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_unsupported.go index e3d6a829a0aaf..a269c808ab2c4 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_unsupported.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2021 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_windows.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_windows.go index d71d980c1a991..a3000fe67585b 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_windows.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2021 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/kuberuntime_termination_order_test.go b/pkg/kubelet/kuberuntime/kuberuntime_termination_order_test.go index 67a93d8ac346e..a183d6bcb25ba 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_termination_order_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_termination_order_test.go @@ -55,9 +55,11 @@ func TestTerminationOrderingSidecarStopAfterMain(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - var sidecarWaitDelay int64 - var mainWaitDelay int64 + var sidecarWaitDelay, mainWaitDelay int64 + syncChannel := make(chan struct{}) + go func() { + close(syncChannel) // notifies other goroutine that this goroutine scheduled. sidecarWaitDelay = int64(to.waitForTurn("init", 30)) to.containerTerminated("init") wg.Done() @@ -65,6 +67,7 @@ func TestTerminationOrderingSidecarStopAfterMain(t *testing.T) { wg.Add(1) go func() { + <-syncChannel // make sure the other goroutine is scheduled. mainWaitDelay = int64(to.waitForTurn("main", 0)) time.Sleep(1 * time.Second) to.containerTerminated("main") diff --git a/pkg/kubelet/kuberuntime/security_context.go b/pkg/kubelet/kuberuntime/security_context.go index 6842eee02c644..cd4bac5fdf439 100644 --- a/pkg/kubelet/kuberuntime/security_context.go +++ b/pkg/kubelet/kuberuntime/security_context.go @@ -17,6 +17,7 @@ limitations under the License. package kuberuntime import ( + "context" "fmt" v1 "k8s.io/api/core/v1" @@ -26,7 +27,7 @@ import ( ) // determineEffectiveSecurityContext gets container's security context from v1.Pod and v1.Container. -func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Pod, container *v1.Container, uid *int64, username string) (*runtimeapi.LinuxContainerSecurityContext, error) { +func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(ctx context.Context, pod *v1.Pod, container *v1.Container, uid *int64, username string) (*runtimeapi.LinuxContainerSecurityContext, error) { effectiveSc := securitycontext.DetermineEffectiveSecurityContext(pod, container) synthesized := convertToRuntimeSecurityContext(effectiveSc) if synthesized == nil { @@ -57,7 +58,7 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po } // set namespace options and supplemental groups. - namespaceOptions, err := runtimeutil.NamespacesForPod(pod, m.runtimeHelper, m.runtimeClassManager) + namespaceOptions, err := runtimeutil.NamespacesForPod(ctx, pod, m.runtimeHelper, m.runtimeClassManager) if err != nil { return nil, err } diff --git a/pkg/kubelet/kuberuntime/security_context_others.go b/pkg/kubelet/kuberuntime/security_context_others.go index 5b8ff561f9346..e16ba7b7c78d6 100644 --- a/pkg/kubelet/kuberuntime/security_context_others.go +++ b/pkg/kubelet/kuberuntime/security_context_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/security_context_others_test.go b/pkg/kubelet/kuberuntime/security_context_others_test.go index 304db4a13753e..20bb94edc79ad 100644 --- a/pkg/kubelet/kuberuntime/security_context_others_test.go +++ b/pkg/kubelet/kuberuntime/security_context_others_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/security_context_windows.go b/pkg/kubelet/kuberuntime/security_context_windows.go index c2b3daa07b6a3..77348045d2870 100644 --- a/pkg/kubelet/kuberuntime/security_context_windows.go +++ b/pkg/kubelet/kuberuntime/security_context_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/security_context_windows_test.go b/pkg/kubelet/kuberuntime/security_context_windows_test.go index 1f0b30ddf82ea..3e8dd6ea7adac 100644 --- a/pkg/kubelet/kuberuntime/security_context_windows_test.go +++ b/pkg/kubelet/kuberuntime/security_context_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/kuberuntime/util/util.go b/pkg/kubelet/kuberuntime/util/util.go index a3c7ce42baaca..7987d3854e064 100644 --- a/pkg/kubelet/kuberuntime/util/util.go +++ b/pkg/kubelet/kuberuntime/util/util.go @@ -108,7 +108,8 @@ type RuntimeHandlerResolver interface { // namespacesForPod returns the runtimeapi.NamespaceOption for a given pod. // An empty or nil pod can be used to get the namespace defaults for v1.Pod. -func NamespacesForPod(pod *v1.Pod, runtimeHelper kubecontainer.RuntimeHelper, rcManager RuntimeHandlerResolver) (*runtimeapi.NamespaceOption, error) { +func NamespacesForPod(ctx context.Context, pod *v1.Pod, runtimeHelper kubecontainer.RuntimeHelper, rcManager RuntimeHandlerResolver) (*runtimeapi.NamespaceOption, error) { + logger := klog.FromContext(ctx) runtimeHandler := "" if pod != nil && rcManager != nil { var err error @@ -117,7 +118,7 @@ func NamespacesForPod(pod *v1.Pod, runtimeHelper kubecontainer.RuntimeHelper, rc return nil, err } } - userNs, err := runtimeHelper.GetOrCreateUserNamespaceMappings(pod, runtimeHandler) + userNs, err := runtimeHelper.GetOrCreateUserNamespaceMappings(logger, pod, runtimeHandler) if err != nil { return nil, err } diff --git a/pkg/kubelet/kuberuntime/util/util_test.go b/pkg/kubelet/kuberuntime/util/util_test.go index b067781cf586c..195a2526a62c9 100644 --- a/pkg/kubelet/kuberuntime/util/util_test.go +++ b/pkg/kubelet/kuberuntime/util/util_test.go @@ -22,12 +22,14 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" pkgfeatures "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/test/utils/ktesting" ) func TestPodSandboxChanged(t *testing.T) { @@ -172,6 +174,7 @@ func (*fakeRuntimeHandlerResolver) LookupRuntimeHandler(s *string) (string, erro } func TestNamespacesForPod(t *testing.T) { + tCtx := ktesting.Init(t) usernsIDs := &runtimeapi.IDMapping{ HostId: 65536, ContainerId: 0, @@ -305,12 +308,16 @@ func TestNamespacesForPod(t *testing.T) { }, } { t.Run(desc, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, test.usernsEnabled) + if !test.usernsEnabled { + // Set emulation version so that the feature gate can be disabled in the test + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) + } fakeRuntimeHelper := kubecontainertest.FakeRuntimeHelper{ RuntimeHandlers: test.runtimeHandlers, } - actual, err := NamespacesForPod(test.input, &fakeRuntimeHelper, &fakeRuntimeHandlerResolver{}) + actual, err := NamespacesForPod(tCtx, test.input, &fakeRuntimeHelper, &fakeRuntimeHandlerResolver{}) if test.expErr { require.Error(t, err) } else { diff --git a/pkg/kubelet/lifecycle/features_linux.go b/pkg/kubelet/lifecycle/features_linux.go index 137d652d1a850..6f9bbdcb2dd3b 100644 --- a/pkg/kubelet/lifecycle/features_linux.go +++ b/pkg/kubelet/lifecycle/features_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2025 The Kubernetes Authors. diff --git a/pkg/kubelet/lifecycle/features_unsupported.go b/pkg/kubelet/lifecycle/features_unsupported.go index 3ece076ba381d..a286606e9c407 100644 --- a/pkg/kubelet/lifecycle/features_unsupported.go +++ b/pkg/kubelet/lifecycle/features_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2025 The Kubernetes Authors. diff --git a/pkg/kubelet/lifecycle/features_windows.go b/pkg/kubelet/lifecycle/features_windows.go index 1db1b527b3bf7..e55520f1009df 100644 --- a/pkg/kubelet/lifecycle/features_windows.go +++ b/pkg/kubelet/lifecycle/features_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2025 The Kubernetes Authors. diff --git a/pkg/kubelet/lifecycle/handlers.go b/pkg/kubelet/lifecycle/handlers.go index 9ff769a648ea0..c2de1e8c03046 100644 --- a/pkg/kubelet/lifecycle/handlers.go +++ b/pkg/kubelet/lifecycle/handlers.go @@ -60,7 +60,8 @@ type handlerRunner struct { } type podStatusProvider interface { - GetPodStatus(ctx context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) + GetPod(ctx context.Context, podUID types.UID) (*kubecontainer.Pod, error) + GetPodStatus(ctx context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) } // NewHandlerRunner returns a configured lifecycle handler for a container. @@ -129,10 +130,13 @@ func (hr *handlerRunner) runHTTPHandler(ctx context.Context, pod *v1.Pod, contai host := handler.HTTPGet.Host podIP := host if len(host) == 0 { - status, err := hr.containerManager.GetPodStatus(ctx, pod.UID, pod.Name, pod.Namespace) + runtimePod, err := hr.containerManager.GetPod(ctx, pod.UID) if err != nil { - logger.Error(err, "Unable to get pod info, event handlers may be invalid.", "pod", klog.KObj(pod)) - return err + return fmt.Errorf("unable to get pod, event handlers may be invalid: %w", err) + } + status, err := hr.containerManager.GetPodStatus(ctx, runtimePod) + if err != nil { + return fmt.Errorf("unable to get pod status, event handlers may be invalid: %w", err) } if len(status.IPs) == 0 { return fmt.Errorf("failed to find networking container: %v", status) @@ -267,11 +271,11 @@ func (c *declaredFeaturesAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmit } } - if reqs.Len() == 0 { + if reqs.IsEmpty() { return PodAdmitResult{Admit: true} } - matchResult, err := ndf.MatchNodeFeatureSet(reqs, c.ndfSet) + matchResult, err := c.ndfFramework.MatchNodeFeatureSet(reqs, c.ndfSet) if err != nil { return PodAdmitResult{ Admit: false, diff --git a/pkg/kubelet/lifecycle/handlers_test.go b/pkg/kubelet/lifecycle/handlers_test.go index d4441054f5f00..25f3dce2b9fb4 100644 --- a/pkg/kubelet/lifecycle/handlers_test.go +++ b/pkg/kubelet/lifecycle/handlers_test.go @@ -29,8 +29,8 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -61,20 +61,22 @@ func (f *fakeContainerCommandRunner) RunInContainer(_ context.Context, id kubeco } func stubPodStatusProvider(podIP string) podStatusProvider { - return podStatusProviderFunc(func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { - return &kubecontainer.PodStatus{ - ID: uid, - Name: name, - Namespace: namespace, - IPs: []string{podIP}, - }, nil - }) + return &fakePodStatusProvider{podIP: podIP} } -type podStatusProviderFunc func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) +type fakePodStatusProvider struct { + podIP string +} + +func (f *fakePodStatusProvider) GetPod(_ context.Context, uid types.UID) (*kubecontainer.Pod, error) { + return &kubecontainer.Pod{ID: uid}, nil +} -func (f podStatusProviderFunc) GetPodStatus(_ context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { - return f(uid, name, namespace) +func (f *fakePodStatusProvider) GetPodStatus(_ context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { + return &kubecontainer.PodStatus{ + ID: pod.ID, + IPs: []string{f.podIP}, + }, nil } func TestRunHandlerExec(t *testing.T) { @@ -847,13 +849,12 @@ func TestDeclaredFeaturesAdmitHandler(t *testing.T) { } createMockFeature := func(t *testing.T, name string, inferForSched bool, maxVersionStr string) *ndftesting.MockFeature { m := ndftesting.NewMockFeature(t) - m.EXPECT().Name().Return(name).Maybe() - m.EXPECT().InferForScheduling(mock.Anything).Return(inferForSched).Maybe() + m.SetName(name) + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { return inferForSched }) if maxVersionStr != "" { - maxVersionStr := version.MustParseSemantic(maxVersionStr) - m.EXPECT().MaxVersion().Return(maxVersionStr).Maybe() + m.SetMaxVersion(version.MustParseSemantic(maxVersionStr)) } else { - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetMaxVersion(nil) } return m } @@ -908,9 +909,9 @@ func TestDeclaredFeaturesAdmitHandler(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - framework, err := ndf.New(tc.registeredFeatures) - require.NoError(t, err) - handler := NewDeclaredFeaturesAdmitHandler(framework, ndf.NewFeatureSet(tc.nodeDeclaredFeatures...), tc.version) + framework := ndf.New(tc.registeredFeatures) + fs := framework.MustMapSorted(tc.nodeDeclaredFeatures) + handler := NewDeclaredFeaturesAdmitHandler(framework, fs, tc.version) attrs := &PodAdmitAttributes{Pod: pod} result := handler.Admit(attrs) diff --git a/pkg/kubelet/lifecycle/interfaces.go b/pkg/kubelet/lifecycle/interfaces.go index 3be7adade546f..864c69dc8fc69 100644 --- a/pkg/kubelet/lifecycle/interfaces.go +++ b/pkg/kubelet/lifecycle/interfaces.go @@ -16,7 +16,7 @@ limitations under the License. package lifecycle -import "k8s.io/api/core/v1" +import v1 "k8s.io/api/core/v1" // PodAdmitAttributes is the context for a pod admission decision. // The member fields of this struct should never be mutated. @@ -25,8 +25,20 @@ type PodAdmitAttributes struct { Pod *v1.Pod // all pods bound to the kubelet excluding the pod being evaluated OtherPods []*v1.Pod + // the operation being performed; either "add" or "resize" + Operation Operation } +// Operation represents the type of operation being performed on a pod. +type Operation string + +const ( + // AddOperation indicates that the pod is being added to the kubelet. + AddOperation Operation = "add" + // ResizeOperation indicates that the pod is being resized. + ResizeOperation Operation = "resize" +) + // PodAdmitResult provides the result of a pod admission decision. type PodAdmitResult struct { // if true, the pod should be admitted. diff --git a/pkg/kubelet/lifecycle/predicate.go b/pkg/kubelet/lifecycle/predicate.go index 62c9c30008067..0052a824efe94 100644 --- a/pkg/kubelet/lifecycle/predicate.go +++ b/pkg/kubelet/lifecycle/predicate.go @@ -33,6 +33,7 @@ import ( schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" "k8s.io/utils/ptr" ) @@ -346,6 +347,20 @@ func rejectPodAdmissionBasedOnSupplementalGroupsPolicy(pod *v1.Pod, node *v1.Nod } func removeMissingExtendedResources(pod *v1.Pod, nodeInfo *schedulerframework.NodeInfo) *v1.Pod { + isResourceBackedByDRA := func(resourceName v1.ResourceName, containerName string) bool { + if !utilfeature.DefaultFeatureGate.Enabled(features.DRAExtendedResource) { + return false + } + if pod.Status.ExtendedResourceClaimStatus == nil { + return false + } + for _, resourceMapping := range pod.Status.ExtendedResourceClaimStatus.RequestMappings { + if v1.ResourceName(resourceMapping.ResourceName) == resourceName && resourceMapping.ContainerName == containerName { + return true + } + } + return false + } filterExtendedResources := func(containers []v1.Container) { for i, c := range containers { // We only handle requests in Requests but not Limits because the @@ -353,6 +368,9 @@ func removeMissingExtendedResources(pod *v1.Pod, nodeInfo *schedulerframework.No // does not use Limits. filteredResources := make(v1.ResourceList) for rName, rQuant := range c.Resources.Requests { + if schedutil.IsDRAExtendedResourceName(rName) && isResourceBackedByDRA(rName, c.Name) { + continue + } if v1helper.IsExtendedResourceName(rName) { if _, found := nodeInfo.Allocatable.ScalarResources[rName]; !found { continue diff --git a/pkg/kubelet/lifecycle/predicate_test.go b/pkg/kubelet/lifecycle/predicate_test.go index 477b1debb2f86..8c8902ac71f44 100644 --- a/pkg/kubelet/lifecycle/predicate_test.go +++ b/pkg/kubelet/lifecycle/predicate_test.go @@ -29,6 +29,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/types" schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity" @@ -39,49 +40,52 @@ import ( ) var ( - quantity = *resource.NewQuantity(1, resource.DecimalSI) + quantity = *resource.NewQuantity(1, resource.DecimalSI) + extendedResource = v1.ResourceName("foo.com/bar") + implicitDRAExtendedResource = v1.ResourceName("deviceclass.resource.kubernetes.io/bar") ) func TestRemoveMissingExtendedResources(t *testing.T) { for _, test := range []struct { - desc string - pod *v1.Pod - node *v1.Node + desc string + pod *v1.Pod + node *v1.Node + enableDRAExtendedResource bool expectedPod *v1.Pod }{ { desc: "requests in Limits should be ignored", pod: makeTestPod( - v1.ResourceList{}, // Requests - v1.ResourceList{"foo.com/bar": quantity}, // Limits + v1.ResourceList{}, // Requests + v1.ResourceList{extendedResource: quantity}, // Limits ), node: makeTestNode( v1.ResourceList{"foo.com/baz": quantity}, // Allocatable ), expectedPod: makeTestPod( - v1.ResourceList{}, // Requests - v1.ResourceList{"foo.com/bar": quantity}, // Limits + v1.ResourceList{}, // Requests + v1.ResourceList{extendedResource: quantity}, // Limits ), }, { desc: "requests for resources available in node should not be removed", pod: makeTestPod( - v1.ResourceList{"foo.com/bar": quantity}, // Requests - v1.ResourceList{}, // Limits + v1.ResourceList{extendedResource: quantity}, // Requests + v1.ResourceList{}, // Limits ), node: makeTestNode( - v1.ResourceList{"foo.com/bar": quantity}, // Allocatable + v1.ResourceList{extendedResource: quantity}, // Allocatable ), expectedPod: makeTestPod( - v1.ResourceList{"foo.com/bar": quantity}, // Requests - v1.ResourceList{}), // Limits + v1.ResourceList{extendedResource: quantity}, // Requests + v1.ResourceList{}), // Limits }, { desc: "requests for resources unavailable in node should be removed", pod: makeTestPod( - v1.ResourceList{"foo.com/bar": quantity}, // Requests - v1.ResourceList{}, // Limits + v1.ResourceList{extendedResource: quantity}, // Requests + v1.ResourceList{}, // Limits ), node: makeTestNode( v1.ResourceList{"foo.com/baz": quantity}, // Allocatable @@ -91,13 +95,71 @@ func TestRemoveMissingExtendedResources(t *testing.T) { v1.ResourceList{}, // Limits ), }, + { + desc: "requests for extended resources with Allocatable 0 should not be removed", + pod: makeTestPod( + v1.ResourceList{extendedResource: quantity}, // Requests + v1.ResourceList{extendedResource: quantity}, // Limits + ), + node: makeTestNode( + v1.ResourceList{extendedResource: *resource.NewQuantity(0, resource.DecimalSI)}, // Allocatable = 0 + ), + expectedPod: makeTestPod( + v1.ResourceList{extendedResource: quantity}, // Requests + v1.ResourceList{extendedResource: quantity}, // Limits + ), + }, + { + desc: "requests for extended resources that are not available in node should be removed", + pod: makeTestPod( + v1.ResourceList{extendedResource: quantity}, // Requests + v1.ResourceList{extendedResource: quantity}, // Limits + ), + node: makeTestNode( + v1.ResourceList{}, // Allocatable is absent for foo.com/bar + ), + expectedPod: makeTestPod( + v1.ResourceList{}, // Requests are filtered + v1.ResourceList{extendedResource: quantity}, // Limits + ), + }, + { + desc: "request for extended resource backed by DRA should be removed if Allocatable is 0", + pod: makeTestPodWithDRAResource(extendedResource, quantity), + node: makeTestNode( + v1.ResourceList{extendedResource: *resource.NewQuantity(0, resource.DecimalSI)}, // Allocatable = 0 + ), + enableDRAExtendedResource: true, + expectedPod: emptyRequests(makeTestPodWithDRAResource(extendedResource, quantity)), + }, + { + desc: "request for extended resource backed by DRA should be removed if Allocatable is absent", + pod: makeTestPodWithDRAResource(extendedResource, quantity), + node: makeTestNode( + v1.ResourceList{}, // Allocatable is absent for foo.com/bar + ), + enableDRAExtendedResource: true, + expectedPod: emptyRequests(makeTestPodWithDRAResource(extendedResource, quantity)), + }, + { + desc: "request for implicit extended resource backed by DRA should be removed", + pod: makeTestPodWithDRAResource(implicitDRAExtendedResource, quantity), + node: makeTestNode( + v1.ResourceList{}, + ), + enableDRAExtendedResource: true, + expectedPod: emptyRequests(makeTestPodWithDRAResource(implicitDRAExtendedResource, quantity)), + }, } { - nodeInfo := schedulerframework.NewNodeInfo() - nodeInfo.SetNode(test.node) - pod := removeMissingExtendedResources(test.pod, nodeInfo) - if diff := cmp.Diff(test.expectedPod, pod); diff != "" { - t.Errorf("unexpected pod (-want, +got):\n%s", diff) - } + t.Run(test.desc, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.enableDRAExtendedResource) + nodeInfo := schedulerframework.NewNodeInfo() + nodeInfo.SetNode(test.node) + pod := removeMissingExtendedResources(test.pod, nodeInfo) + if diff := cmp.Diff(test.expectedPod, pod); diff != "" { + t.Errorf("unexpected pod (-want, +got):\n%s", diff) + } + }) } } @@ -124,6 +186,44 @@ func makeTestPod(requests, limits v1.ResourceList) *v1.Pod { } } +func makeTestPodWithDRAResource(resourceName v1.ResourceName, quantity resource.Quantity) *v1.Pod { + pod := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test-container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{resourceName: quantity}, + Limits: v1.ResourceList{resourceName: quantity}, + }, + }, + }, + }, + } + pod.Status = v1.PodStatus{ + ExtendedResourceClaimStatus: &v1.PodExtendedResourceClaimStatus{ + RequestMappings: []v1.ContainerExtendedResourceRequest{ + { + ResourceName: string(resourceName), + ContainerName: pod.Spec.Containers[0].Name, + }, + }, + }, + } + return pod +} + +func emptyRequests(pod *v1.Pod) *v1.Pod { + newPod := pod.DeepCopy() + for i := range newPod.Spec.Containers { + newPod.Spec.Containers[i].Resources.Requests = v1.ResourceList{} + } + for i := range newPod.Spec.InitContainers { + newPod.Spec.InitContainers[i].Resources.Requests = v1.ResourceList{} + } + return newPod +} + func makeTestNode(allocatable v1.ResourceList) *v1.Node { return &v1.Node{ Status: v1.NodeStatus{ diff --git a/pkg/kubelet/managed/managed.go b/pkg/kubelet/managed/managed.go index bd78adeee1385..e990656b0955a 100644 --- a/pkg/kubelet/managed/managed.go +++ b/pkg/kubelet/managed/managed.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "strings" + "sync/atomic" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -29,7 +30,7 @@ import ( ) var ( - pinnedManagementEnabled bool + pinnedManagementEnabled atomic.Bool pinnedManagementFilename = "/etc/kubernetes/openshift-workload-pinning" ) @@ -62,18 +63,18 @@ func init() { func readEnablementFile() { if _, err := os.Stat(pinnedManagementFilename); err == nil { - pinnedManagementEnabled = true + pinnedManagementEnabled.Store(true) } } // TestOnlySetEnabled allows changing the state of management partition enablement // This method MUST NOT be used outside of test code func TestOnlySetEnabled(enabled bool) { - pinnedManagementEnabled = enabled + pinnedManagementEnabled.Store(enabled) } func IsEnabled() bool { - return pinnedManagementEnabled + return pinnedManagementEnabled.Load() } // IsPodManaged returns true and the name of the workload if enabled. diff --git a/pkg/kubelet/metrics/metrics.go b/pkg/kubelet/metrics/metrics.go index 7ae4c1b669a2f..5e0fc20e3322b 100644 --- a/pkg/kubelet/metrics/metrics.go +++ b/pkg/kubelet/metrics/metrics.go @@ -51,6 +51,7 @@ const ( PLEGDiscardEventsKey = "pleg_discard_events" PLEGRelistIntervalKey = "pleg_relist_interval_seconds" PLEGLastSeenKey = "pleg_last_seen_seconds" + PLEGPodRelistDurationKey = "pleg_pod_relist_duration_seconds" EventedPLEGConnErrKey = "evented_pleg_connection_error_count" EventedPLEGConnKey = "evented_pleg_connection_success_count" EventedPLEGConnLatencyKey = "evented_pleg_connection_latency_seconds" @@ -100,6 +101,7 @@ const ( StartedPodsTotalKey = "started_pods_total" StartedPodsErrorsTotalKey = "started_pods_errors_total" StartedContainersTotalKey = "started_containers_total" + TerminatedContainersTotalKey = "terminated_containers_total" StartedContainersErrorsTotalKey = "started_containers_errors_total" // Metrics to track HostProcess container usage by this kubelet @@ -113,6 +115,23 @@ const ( // Metrics to track ephemeral container usage by this kubelet ManagedEphemeralContainersKey = "managed_ephemeral_containers" + // Metrics to track resource managers allocation + ResourceManagerAllocationTotalKey = "resource_manager_allocations_total" + + ResourceManagerAllocationErrorsTotalKey = "resource_manager_allocation_errors_total" + + ResourceManagerContainerAssignmentsKey = "resource_manager_container_assignments" + + ResourceManagerPod = "pod" + ResourceManagerNode = "node" + + ResourceManagerCPU = "cpu" + ResourceManagerMemory = "memory" + + ResourceManagerExclusiveNode = "node_exclusive" + ResourceManagerSharedPod = "pod_shared" + ResourceManagerExclusivePod = "pod_exclusive" + // Metrics to track the CPU manager behavior CPUManagerPinningRequestsTotalKey = "cpu_manager_pinning_requests_total" CPUManagerPinningErrorsTotalKey = "cpu_manager_pinning_errors_total" @@ -146,6 +165,10 @@ const ( DRAOperationsDurationKey = "operations_duration_seconds" DRAGRPCOperationsDurationKey = "grpc_operations_duration_seconds" + // Metric keys for MemoryQoS + MemoryQoSNodeMemoryMinBytesKey = "memory_qos_node_memory_min_bytes" + MemoryQoSNodeMemoryLowBytesKey = "memory_qos_node_memory_low_bytes" + // Values used in metric labels Container = "container" InitContainer = "init_container" @@ -179,6 +202,9 @@ const ( // Metric key for podcertificate states. PodCertificateStatesKey = "podcertificate_states" + + // Metric key for podsapi + PodWatchEventsDroppedKey = "pod_watch_events_dropped_total" ) type imageSizeBucket struct { @@ -386,6 +412,17 @@ var ( StabilityLevel: metrics.ALPHA, }, ) + // PLEGPodRelistDuration is a Histogram that tracks the duration (in seconds) it takes for relisting a single pod in the Kubelet's + // Pod Lifecycle Event Generator (PLEG). + PLEGPodRelistDuration = metrics.NewHistogram( + &metrics.HistogramOpts{ + Subsystem: KubeletSubsystem, + Name: PLEGPodRelistDurationKey, + Help: "Duration in seconds for relisting a single pod in PLEG.", + Buckets: metrics.DefBuckets, + StabilityLevel: metrics.ALPHA, + }, + ) // EventedPLEGConnErr is a Counter that tracks the number of errors encountered during // the establishment of streaming connection with the CRI runtime. @@ -729,6 +766,16 @@ var ( }, []string{"container_type"}, ) + // TerminatedContainersTotal is a counter that tracks the number of container terminations + TerminatedContainersTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: KubeletSubsystem, + Name: TerminatedContainersTotalKey, + Help: "Cumulative number of container terminations.", + StabilityLevel: metrics.ALPHA, + }, + []string{"container_type", "exit_code", "reason"}, + ) // StartedContainersTotal is a counter that tracks the number of errors creating containers StartedContainersErrorsTotal = metrics.NewCounterVec( &metrics.CounterOpts{ @@ -907,6 +954,26 @@ var ( }, ) + // MemoryQoSNodeMemoryMinBytes tracks total cgroup v2 memory.min (hard protection) for Guaranteed pods. + MemoryQoSNodeMemoryMinBytes = metrics.NewGauge( + &metrics.GaugeOpts{ + Subsystem: KubeletSubsystem, + Name: MemoryQoSNodeMemoryMinBytesKey, + Help: "Total cgroup v2 memory.min in bytes for Guaranteed pods. This memory is hard-reserved and never reclaimed by the kernel.", + StabilityLevel: metrics.ALPHA, + }, + ) + + // MemoryQoSNodeMemoryLowBytes tracks total cgroup v2 memory.low (soft protection) for Burstable pods. + MemoryQoSNodeMemoryLowBytes = metrics.NewGauge( + &metrics.GaugeOpts{ + Subsystem: KubeletSubsystem, + Name: MemoryQoSNodeMemoryLowBytesKey, + Help: "Total cgroup v2 memory.low in bytes for Burstable pods. This memory is soft-reserved and may be reclaimed under extreme pressure.", + StabilityLevel: metrics.ALPHA, + }, + ) + // TopologyManagerAdmissionRequestsTotal tracks the number of times the pod spec will cause the topology manager to admit a pod TopologyManagerAdmissionRequestsTotal = metrics.NewCounter( &metrics.CounterOpts{ @@ -1193,6 +1260,49 @@ var ( }, []string{"retry_trigger"}, ) + + // ResourceManagerAllocationsTotal counts the total number of exclusive resource + // allocations performed by a manager. The `source` label distinguishes between + // allocations drawn from the node-level pool versus a pre-allocated pod-level pool. + ResourceManagerAllocationsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Name: ResourceManagerAllocationTotalKey, + Help: "Number of exclusive resource allocations performed by a resource manager.", + }, + []string{"resource_name", "source"}, + ) + + // ResourceManagerAllocationErrorsTotal counts errors encountered during exclusive + // resource allocation, distinguished by the intended allocation source. + ResourceManagerAllocationErrorsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Name: ResourceManagerAllocationErrorsTotalKey, + Help: "Number of errors encountered during exclusive resource allocation.", + }, + []string{"resource_name", "source"}, + ) + + // ResourceManagerContainerAssignments counts the total number of containers with + // a specific type of resource assignment. This provides visibility into how many + // containers were allocated with exclusive resources (from the node or pod pool) + // versus the pod-level shared pool. + ResourceManagerContainerAssignments = metrics.NewCounterVec( + &metrics.CounterOpts{ + Name: ResourceManagerContainerAssignmentsKey, + Help: "Number of containers with a specific type of resource assignment.", + }, + []string{"resource_name", "assignment_type"}, + ) + + // PodWatchEventsDroppedTotal tracks the number of dropped pod watch events. + PodWatchEventsDroppedTotal = metrics.NewCounter( + &metrics.CounterOpts{ + Subsystem: KubeletSubsystem, + Name: PodWatchEventsDroppedKey, + Help: "Cumulative number of pod watch events dropped.", + StabilityLevel: metrics.ALPHA, + }, + ) ) var registerMetrics sync.Once @@ -1222,6 +1332,7 @@ func Register() { legacyregistry.MustRegister(PLEGDiscardEvents) legacyregistry.MustRegister(PLEGRelistInterval) legacyregistry.MustRegister(PLEGLastSeen) + legacyregistry.MustRegister(PLEGPodRelistDuration) legacyregistry.MustRegister(EventedPLEGConnErr) legacyregistry.MustRegister(EventedPLEGConn) legacyregistry.MustRegister(EventedPLEGConnLatency) @@ -1247,10 +1358,8 @@ func Register() { legacyregistry.MustRegister(PodResourcesEndpointRequestsGetAllocatableCount) legacyregistry.MustRegister(PodResourcesEndpointErrorsListCount) legacyregistry.MustRegister(PodResourcesEndpointErrorsGetAllocatableCount) - if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPodResourcesGet) { - legacyregistry.MustRegister(PodResourcesEndpointRequestsGetCount) - legacyregistry.MustRegister(PodResourcesEndpointErrorsGetCount) - } + legacyregistry.MustRegister(PodResourcesEndpointRequestsGetCount) + legacyregistry.MustRegister(PodResourcesEndpointErrorsGetCount) if utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport) { legacyregistry.MustRegister(StartedUserNamespacedPodsTotal) legacyregistry.MustRegister(StartedUserNamespacedPodsErrorsTotal) @@ -1258,6 +1367,7 @@ func Register() { legacyregistry.MustRegister(StartedPodsTotal) legacyregistry.MustRegister(StartedPodsErrorsTotal) legacyregistry.MustRegister(StartedContainersTotal) + legacyregistry.MustRegister(TerminatedContainersTotal) legacyregistry.MustRegister(StartedContainersErrorsTotal) legacyregistry.MustRegister(StartedHostProcessContainersTotal) legacyregistry.MustRegister(StartedHostProcessContainersErrorsTotal) @@ -1272,6 +1382,10 @@ func Register() { legacyregistry.MustRegister(ContainerAlignedComputeResourcesFailure) legacyregistry.MustRegister(MemoryManagerPinningRequestTotal) legacyregistry.MustRegister(MemoryManagerPinningErrorsTotal) + if utilfeature.DefaultFeatureGate.Enabled(features.MemoryQoS) { + legacyregistry.MustRegister(MemoryQoSNodeMemoryMinBytes) + legacyregistry.MustRegister(MemoryQoSNodeMemoryLowBytes) + } legacyregistry.MustRegister(TopologyManagerAdmissionRequestsTotal) legacyregistry.MustRegister(TopologyManagerAdmissionErrorsTotal) legacyregistry.MustRegister(TopologyManagerAdmissionDuration) @@ -1311,6 +1425,14 @@ func Register() { legacyregistry.MustRegister(PodInProgressResizes) legacyregistry.MustRegister(PodDeferredAcceptedResizes) } + + if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) { + legacyregistry.MustRegister(ResourceManagerAllocationsTotal) + legacyregistry.MustRegister(ResourceManagerAllocationErrorsTotal) + legacyregistry.MustRegister(ResourceManagerContainerAssignments) + } + + legacyregistry.MustRegister(PodWatchEventsDroppedTotal) }) } diff --git a/pkg/kubelet/network/dns/dns.go b/pkg/kubelet/network/dns/dns.go index 64d0f0fb0e2b0..03e8d72cd9ab6 100644 --- a/pkg/kubelet/network/dns/dns.go +++ b/pkg/kubelet/network/dns/dns.go @@ -58,7 +58,7 @@ const ( // Configurer is used for setting up DNS resolver configuration when launching pods. type Configurer struct { - recorder record.EventRecorder + recorder record.EventRecorderLogger getHostDNSConfig func(klog.Logger, string) (*runtimeapi.DNSConfig, error) nodeRef *v1.ObjectReference nodeIPs []net.IP @@ -74,7 +74,7 @@ type Configurer struct { } // NewConfigurer returns a DNS configurer for launching pods. -func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, nodeIPs []net.IP, clusterDNS []net.IP, clusterDomain, resolverConfig string) *Configurer { +func NewConfigurer(recorder record.EventRecorderLogger, nodeRef *v1.ObjectReference, nodeIPs []net.IP, clusterDNS []net.IP, clusterDomain, resolverConfig string) *Configurer { return &Configurer{ recorder: recorder, getHostDNSConfig: getHostDNSConfig, @@ -140,7 +140,7 @@ func (c *Configurer) formDNSSearchFitsLimits(logger klog.Logger, composedSearch if limitsExceeded { err := fmt.Errorf("Search Line limits were exceeded, some search paths have been omitted, the applied search line is: %s", strings.Join(composedSearch, " ")) - c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", err.Error()) + c.recorder.WithLogger(logger).Event(pod, v1.EventTypeWarning, "DNSConfigForming", err.Error()) logger.Error(err, "Search Line limits exceeded") } return composedSearch @@ -150,7 +150,7 @@ func (c *Configurer) formDNSNameserversFitsLimits(logger klog.Logger, nameserver if len(nameservers) > validation.MaxDNSNameservers { nameservers = nameservers[0:validation.MaxDNSNameservers] err := fmt.Errorf("Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: %s", strings.Join(nameservers, " ")) - c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", err.Error()) + c.recorder.WithLogger(logger).Event(pod, v1.EventTypeWarning, "DNSConfigForming", err.Error()) logger.Error(err, "Nameserver limits exceeded") } return nameservers @@ -178,7 +178,7 @@ func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod func (c *Configurer) CheckLimitsForResolvConf(logger klog.Logger) { f, err := os.Open(c.ResolverConfig) if err != nil { - c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error()) + c.recorder.WithLogger(logger).Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error()) logger.V(4).Info("Check limits for resolv.conf failed at file open", "err", err) return } @@ -186,7 +186,7 @@ func (c *Configurer) CheckLimitsForResolvConf(logger klog.Logger) { _, hostSearch, _, err := parseResolvConf(f) if err != nil { - c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error()) + c.recorder.WithLogger(logger).Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error()) logger.V(4).Info("Check limits for resolv.conf failed at parse resolv.conf", "err", err) return } @@ -199,7 +199,7 @@ func (c *Configurer) CheckLimitsForResolvConf(logger klog.Logger) { if len(hostSearch) > domainCountLimit { log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCountLimit) - c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) + c.recorder.WithLogger(logger).Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) logger.V(4).Info("Check limits for resolv.conf failed", "eventlog", log) return } @@ -207,7 +207,7 @@ func (c *Configurer) CheckLimitsForResolvConf(logger klog.Logger) { for _, search := range hostSearch { if len(search) > utilvalidation.DNS1123SubdomainMaxLength { log := fmt.Sprintf("Resolv.conf file %q contains a search path which length is more than allowed %d chars!", c.ResolverConfig, utilvalidation.DNS1123SubdomainMaxLength) - c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) + c.recorder.WithLogger(logger).Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) logger.V(4).Info("Check limits for resolv.conf failed", "eventlog", log) return } @@ -215,7 +215,7 @@ func (c *Configurer) CheckLimitsForResolvConf(logger klog.Logger) { if len(strings.Join(hostSearch, " ")) > maxDNSSearchListChars { log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, maxDNSSearchListChars) - c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) + c.recorder.WithLogger(logger).Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) logger.V(4).Info("Check limits for resolv.conf failed", "eventlog", log) return } @@ -416,8 +416,8 @@ func (c *Configurer) GetPodDNS(ctx context.Context, pod *v1.Pod) (*runtimeapi.DN } // clusterDNS is not known. Pod with ClusterDNSFirst Policy cannot be created. nodeErrorMsg := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to %q policy.", v1.DNSClusterFirst, v1.DNSDefault) - c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", nodeErrorMsg) - c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "pod: %q. %s", format.Pod(pod), nodeErrorMsg) + c.recorder.WithLogger(logger).Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", "%s", nodeErrorMsg) + c.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "pod: %q. %s", format.Pod(pod), nodeErrorMsg) // Fallback to DNSDefault. fallthrough case podDNSHost: diff --git a/pkg/kubelet/network/dns/dns_other.go b/pkg/kubelet/network/dns/dns_other.go index 8d5f5caa7d345..df2b81b86a921 100644 --- a/pkg/kubelet/network/dns/dns_other.go +++ b/pkg/kubelet/network/dns/dns_other.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/network/dns/dns_other_test.go b/pkg/kubelet/network/dns/dns_other_test.go index a2440558334b5..7085963c14fcf 100644 --- a/pkg/kubelet/network/dns/dns_other_test.go +++ b/pkg/kubelet/network/dns/dns_other_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/network/dns/dns_windows.go b/pkg/kubelet/network/dns/dns_windows.go index cb1651bf5e6be..79d69f761a490 100644 --- a/pkg/kubelet/network/dns/dns_windows.go +++ b/pkg/kubelet/network/dns/dns_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/network/dns/dns_windows_test.go b/pkg/kubelet/network/dns/dns_windows_test.go index 4ecf40f6ea80b..127560b17b1e3 100644 --- a/pkg/kubelet/network/dns/dns_windows_test.go +++ b/pkg/kubelet/network/dns/dns_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go index 2d7786f71f16c..fa36cd11c1455 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager.go @@ -42,7 +42,7 @@ type Manager interface { lifecycle.PodAdmitHandler Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult - Start() error + Start(ctx context.Context) error ShutdownStatus() error } @@ -54,7 +54,7 @@ type Config struct { NodeRef *v1.ObjectReference GetPodsFunc eviction.ActivePodsFunc KillPodFunc eviction.KillPodFunc - SyncNodeStatusFunc func() + SyncNodeStatusFunc func(context.Context) ShutdownGracePeriodRequested time.Duration ShutdownGracePeriodCriticalPods time.Duration ShutdownGracePeriodByPodPriority []kubeletconfig.ShutdownGracePeriodByPodPriority @@ -71,7 +71,7 @@ func (managerStub) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmit } // Start is a no-op always returning nil for non linux platforms. -func (managerStub) Start() error { +func (managerStub) Start(context.Context) error { return nil } diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go index 8a78f874cb94b..cb01d053117fd 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2020 The Kubernetes Authors. @@ -21,6 +20,7 @@ limitations under the License. package nodeshutdown import ( + "context" "fmt" "path/filepath" "sync" @@ -63,7 +63,7 @@ type managerImpl struct { nodeRef *v1.ObjectReference getPods eviction.ActivePodsFunc - syncNodeStatus func() + syncNodeStatus func(context.Context) dbusCon dbusInhibiter inhibitLock systemd.InhibitLock @@ -144,8 +144,8 @@ func (m *managerImpl) setMetrics() { } // Start starts the node shutdown manager and will start watching the node for shutdown events. -func (m *managerImpl) Start() error { - stop, err := m.start() +func (m *managerImpl) Start(ctx context.Context) error { + stop, err := m.start(ctx) if err != nil { return err } @@ -157,7 +157,7 @@ func (m *managerImpl) Start() error { time.Sleep(dbusReconnectPeriod) m.logger.V(1).Info("Restarting watch for node shutdown events") - stop, err = m.start() + stop, err = m.start(ctx) if err != nil { m.logger.Error(err, "Unable to watch the node for shutdown events") } @@ -168,7 +168,7 @@ func (m *managerImpl) Start() error { return nil } -func (m *managerImpl) start() (chan struct{}, error) { +func (m *managerImpl) start(ctx context.Context) (chan struct{}, error) { systemBus, err := systemDbus() if err != nil { return nil, err @@ -275,7 +275,7 @@ func (m *managerImpl) start() (chan struct{}, error) { if isShuttingDown { // Update node status and ready condition - go m.syncNodeStatus() + go m.syncNodeStatus(ctx) m.processShutdownEvent() } else { diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go index 9662d7f4c27c8..2f07ea7f85441 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2020 The Kubernetes Authors. @@ -20,6 +19,7 @@ limitations under the License. package nodeshutdown import ( + "context" "fmt" "os" "strings" @@ -303,7 +303,7 @@ func TestManager(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - logger, _ := ktesting.NewTestContext(t) + logger, tCtx := ktesting.NewTestContext(t) activePodsFunc := func() []*v1.Pod { return tc.activePods @@ -344,14 +344,14 @@ func TestManager(t *testing.T) { NodeRef: nodeRef, GetPodsFunc: activePodsFunc, KillPodFunc: killPodsFunc, - SyncNodeStatusFunc: func() {}, + SyncNodeStatusFunc: func(context context.Context) {}, ShutdownGracePeriodRequested: tc.shutdownGracePeriodRequested, ShutdownGracePeriodCriticalPods: tc.shutdownGracePeriodCriticalPods, Clock: testingclock.NewFakeClock(time.Now()), StateDirectory: os.TempDir(), }) - err := manager.Start() + err := manager.Start(tCtx) lock.Unlock() if tc.expectedError != nil { @@ -449,7 +449,7 @@ func TestFeatureEnabled(t *testing.T) { NodeRef: nodeRef, GetPodsFunc: activePodsFunc, KillPodFunc: killPodsFunc, - SyncNodeStatusFunc: func() {}, + SyncNodeStatusFunc: func(context.Context) {}, ShutdownGracePeriodRequested: tc.shutdownGracePeriodRequested, ShutdownGracePeriodCriticalPods: 0, StateDirectory: os.TempDir(), @@ -460,7 +460,7 @@ func TestFeatureEnabled(t *testing.T) { } func TestRestart(t *testing.T) { - logger, _ := ktesting.NewTestContext(t) + logger, tCtx := ktesting.NewTestContext(t) systemDbusTmp := systemDbus defer func() { systemDbus = systemDbusTmp @@ -476,7 +476,7 @@ func TestRestart(t *testing.T) { killPodsFunc := func(pod *v1.Pod, isEvicted bool, gracePeriodOverride *int64, fn func(*v1.PodStatus)) error { return nil } - syncNodeStatus := func() {} + syncNodeStatus := func(context.Context) {} var shutdownChan chan bool var shutdownChanMut sync.Mutex @@ -511,7 +511,7 @@ func TestRestart(t *testing.T) { StateDirectory: os.TempDir(), }) - err := manager.Start() + err := manager.Start(tCtx) lock.Unlock() if err != nil { @@ -535,7 +535,7 @@ func Test_managerImpl_processShutdownEvent(t *testing.T) { var ( fakeRecorder = &record.FakeRecorder{} fakeVolumeManager = volumemanager.NewFakeVolumeManager([]v1.UniqueVolumeName{}, 0, nil, false) - syncNodeStatus = func() {} + syncNodeStatus = func(context.Context) {} nodeRef = &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} fakeclock = testingclock.NewFakeClock(time.Now()) ) @@ -547,7 +547,7 @@ func Test_managerImpl_processShutdownEvent(t *testing.T) { shutdownGracePeriodByPodPriority []kubeletconfig.ShutdownGracePeriodByPodPriority getPods eviction.ActivePodsFunc killPodFunc eviction.KillPodFunc - syncNodeStatus func() + syncNodeStatus func(context.Context) dbusCon dbusInhibiter inhibitLock systemd.InhibitLock nodeShuttingDownNow bool @@ -641,7 +641,7 @@ func Test_managerImpl_processShutdownEvent(t *testing.T) { func Test_processShutdownEvent_VolumeUnmountTimeout(t *testing.T) { var ( fakeRecorder = &record.FakeRecorder{} - syncNodeStatus = func() {} + syncNodeStatus = func(context.Context) {} nodeRef = &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} fakeclock = testingclock.NewFakeClock(time.Now()) shutdownGracePeriodSeconds = 2 diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_others.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_others.go index ca43e737fe4e2..7a1185b9ce82f 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_others.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_others.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows.go index 4c129d499dd59..a47de9386d814 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. @@ -21,6 +20,7 @@ limitations under the License. package nodeshutdown import ( + "context" "fmt" "path/filepath" "strings" @@ -56,7 +56,7 @@ type managerImpl struct { nodeRef *v1.ObjectReference getPods eviction.ActivePodsFunc - syncNodeStatus func() + syncNodeStatus func(context.Context) nodeShuttingDownMutex sync.Mutex nodeShuttingDownNow bool @@ -136,7 +136,7 @@ func (m *managerImpl) setMetrics() { } // Start starts the node shutdown manager and will start watching the node for shutdown events. -func (m *managerImpl) Start() error { +func (m *managerImpl) Start(_ context.Context) error { m.logger.V(1).Info("Shutdown manager get started") _, err := m.start() @@ -247,7 +247,7 @@ func (m *managerImpl) ProcessShutdownEvent() error { m.nodeShuttingDownNow = true m.nodeShuttingDownMutex.Unlock() - go m.syncNodeStatus() + go m.syncNodeStatus(context.TODO()) m.logger.V(1).Info("Shutdown manager processing preshutdown event") activePods := m.getPods() diff --git a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows_test.go b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows_test.go index f0598a7025e4e..fc278c5d4d3ce 100644 --- a/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows_test.go +++ b/pkg/kubelet/nodeshutdown/nodeshutdown_manager_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. @@ -20,6 +19,7 @@ limitations under the License. package nodeshutdown import ( + "context" "fmt" "os" "strings" @@ -91,7 +91,7 @@ func TestFeatureEnabled(t *testing.T) { NodeRef: nodeRef, GetPodsFunc: activePodsFunc, KillPodFunc: killPodsFunc, - SyncNodeStatusFunc: func() {}, + SyncNodeStatusFunc: func(context.Context) {}, ShutdownGracePeriodRequested: tc.shutdownGracePeriodRequested, ShutdownGracePeriodCriticalPods: 0, StateDirectory: os.TempDir(), @@ -105,7 +105,7 @@ func Test_managerImpl_ProcessShutdownEvent(t *testing.T) { var ( fakeRecorder = &record.FakeRecorder{} fakeVolumeManager = volumemanager.NewFakeVolumeManager([]v1.UniqueVolumeName{}, 0, nil, false) - syncNodeStatus = func() {} + syncNodeStatus = func(context.Context) {} nodeRef = &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} fakeclock = testingclock.NewFakeClock(time.Now()) ) @@ -117,7 +117,7 @@ func Test_managerImpl_ProcessShutdownEvent(t *testing.T) { shutdownGracePeriodByPodPriority []kubeletconfig.ShutdownGracePeriodByPodPriority getPods eviction.ActivePodsFunc killPodFunc eviction.KillPodFunc - syncNodeStatus func() + syncNodeStatus func(context.Context) nodeShuttingDownNow bool clock clock.Clock } diff --git a/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go b/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go index 18b6b9be86928..0223d65ba04dc 100644 --- a/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go +++ b/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/nodeshutdown/systemd/inhibit_linux_test.go b/pkg/kubelet/nodeshutdown/systemd/inhibit_linux_test.go index bd00aa70bb8f5..497edc2790389 100644 --- a/pkg/kubelet/nodeshutdown/systemd/inhibit_linux_test.go +++ b/pkg/kubelet/nodeshutdown/systemd/inhibit_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2020 The Kubernetes Authors. @@ -162,7 +161,6 @@ func TestMonitorShutdown(t *testing.T) { } for _, tc := range tests { - tc := tc t.Run(tc.desc, func(t *testing.T) { fakeSystemBus := &fakeSystemDBus{} bus := DBusCon{ diff --git a/pkg/kubelet/nodeshutdown/systemd/inhibit_others.go b/pkg/kubelet/nodeshutdown/systemd/inhibit_others.go index f077b00afc518..c35b8c9c8487b 100644 --- a/pkg/kubelet/nodeshutdown/systemd/inhibit_others.go +++ b/pkg/kubelet/nodeshutdown/systemd/inhibit_others.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/nodestatus/setters.go b/pkg/kubelet/nodestatus/setters.go index 4a22a830febf7..f7bc8c77293c8 100644 --- a/pkg/kubelet/nodestatus/setters.go +++ b/pkg/kubelet/nodestatus/setters.go @@ -473,10 +473,11 @@ func ReadyCondition( storageErrorsFunc func() error, // typically Kubelet.runtimeState.storageErrors cmStatusFunc func() cm.Status, // typically Kubelet.containerManager.Status nodeShutdownManagerErrorsFunc func() error, // typically kubelet.shutdownManager.errors. - recordEventFunc func(eventType, event string), // typically Kubelet.recordNodeStatusEvent + recordEventFunc func(logger klog.Logger, eventType, event string), // typically Kubelet.recordNodeStatusEvent localStorageCapacityIsolation bool, ) Setter { return func(ctx context.Context, node *v1.Node) error { + logger := klog.FromContext(ctx) // NOTE(aaronlevy): NodeReady condition needs to be the last in the list of node conditions. // This is due to an issue with version skewed kubelet and master components. // ref: https://github.com/kubernetes/kubernetes/issues/16961 @@ -539,9 +540,9 @@ func ReadyCondition( } if needToRecordEvent { if newNodeReadyCondition.Status == v1.ConditionTrue { - recordEventFunc(v1.EventTypeNormal, events.NodeReady) + recordEventFunc(logger, v1.EventTypeNormal, events.NodeReady) } else { - recordEventFunc(v1.EventTypeNormal, events.NodeNotReady) + recordEventFunc(logger, v1.EventTypeNormal, events.NodeNotReady) logger := klog.FromContext(ctx) logger.Info("Node became not ready", "node", klog.KObj(node), "condition", newNodeReadyCondition) } @@ -553,9 +554,10 @@ func ReadyCondition( // MemoryPressureCondition returns a Setter that updates the v1.NodeMemoryPressure condition on the node. func MemoryPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.Now pressureFunc func() bool, // typically Kubelet.evictionManager.IsUnderMemoryPressure - recordEventFunc func(eventType, event string), // typically Kubelet.recordNodeStatusEvent + recordEventFunc func(logger klog.Logger, eventType, event string), // typically Kubelet.recordNodeStatusEvent ) Setter { return func(ctx context.Context, node *v1.Node) error { + logger := klog.FromContext(ctx) currentTime := metav1.NewTime(nowFunc()) var condition *v1.NodeCondition @@ -594,14 +596,14 @@ func MemoryPressureCondition(nowFunc func() time.Time, // typically Kubelet.cloc condition.Reason = "KubeletHasInsufficientMemory" condition.Message = "kubelet has insufficient memory available" condition.LastTransitionTime = currentTime - recordEventFunc(v1.EventTypeNormal, "NodeHasInsufficientMemory") + recordEventFunc(logger, v1.EventTypeNormal, "NodeHasInsufficientMemory") } } else if condition.Status != v1.ConditionFalse { condition.Status = v1.ConditionFalse condition.Reason = "KubeletHasSufficientMemory" condition.Message = "kubelet has sufficient memory available" condition.LastTransitionTime = currentTime - recordEventFunc(v1.EventTypeNormal, "NodeHasSufficientMemory") + recordEventFunc(logger, v1.EventTypeNormal, "NodeHasSufficientMemory") } if newCondition { @@ -614,9 +616,10 @@ func MemoryPressureCondition(nowFunc func() time.Time, // typically Kubelet.cloc // PIDPressureCondition returns a Setter that updates the v1.NodePIDPressure condition on the node. func PIDPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.Now pressureFunc func() bool, // typically Kubelet.evictionManager.IsUnderPIDPressure - recordEventFunc func(eventType, event string), // typically Kubelet.recordNodeStatusEvent + recordEventFunc func(logger klog.Logger, eventType, event string), // typically Kubelet.recordNodeStatusEvent ) Setter { return func(ctx context.Context, node *v1.Node) error { + logger := klog.FromContext(ctx) currentTime := metav1.NewTime(nowFunc()) var condition *v1.NodeCondition @@ -655,14 +658,14 @@ func PIDPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.N condition.Reason = "KubeletHasInsufficientPID" condition.Message = "kubelet has insufficient PID available" condition.LastTransitionTime = currentTime - recordEventFunc(v1.EventTypeNormal, "NodeHasInsufficientPID") + recordEventFunc(logger, v1.EventTypeNormal, "NodeHasInsufficientPID") } } else if condition.Status != v1.ConditionFalse { condition.Status = v1.ConditionFalse condition.Reason = "KubeletHasSufficientPID" condition.Message = "kubelet has sufficient PID available" condition.LastTransitionTime = currentTime - recordEventFunc(v1.EventTypeNormal, "NodeHasSufficientPID") + recordEventFunc(logger, v1.EventTypeNormal, "NodeHasSufficientPID") } if newCondition { @@ -675,9 +678,10 @@ func PIDPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.N // DiskPressureCondition returns a Setter that updates the v1.NodeDiskPressure condition on the node. func DiskPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.Now pressureFunc func() bool, // typically Kubelet.evictionManager.IsUnderDiskPressure - recordEventFunc func(eventType, event string), // typically Kubelet.recordNodeStatusEvent + recordEventFunc func(logger klog.Logger, eventType, event string), // typically Kubelet.recordNodeStatusEvent ) Setter { return func(ctx context.Context, node *v1.Node) error { + logger := klog.FromContext(ctx) currentTime := metav1.NewTime(nowFunc()) var condition *v1.NodeCondition @@ -716,14 +720,14 @@ func DiskPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock. condition.Reason = "KubeletHasDiskPressure" condition.Message = "kubelet has disk pressure" condition.LastTransitionTime = currentTime - recordEventFunc(v1.EventTypeNormal, "NodeHasDiskPressure") + recordEventFunc(logger, v1.EventTypeNormal, "NodeHasDiskPressure") } } else if condition.Status != v1.ConditionFalse { condition.Status = v1.ConditionFalse condition.Reason = "KubeletHasNoDiskPressure" condition.Message = "kubelet has no disk pressure" condition.LastTransitionTime = currentTime - recordEventFunc(v1.EventTypeNormal, "NodeHasNoDiskPressure") + recordEventFunc(logger, v1.EventTypeNormal, "NodeHasNoDiskPressure") } if newCondition { diff --git a/pkg/kubelet/nodestatus/setters_test.go b/pkg/kubelet/nodestatus/setters_test.go index c7f441a9dc3f5..ce490274f0c2c 100644 --- a/pkg/kubelet/nodestatus/setters_test.go +++ b/pkg/kubelet/nodestatus/setters_test.go @@ -39,8 +39,8 @@ import ( "k8s.io/apimachinery/pkg/util/uuid" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/featuregate" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/version" + "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" @@ -969,7 +969,6 @@ func TestVersionInfo(t *testing.T) { runtimeVersionError error expectNode *v1.Node expectError error - kubeProxyVersion bool }{ { desc: "versions set in node info", @@ -989,11 +988,10 @@ func TestVersionInfo(t *testing.T) { OSImage: "ContainerOSVersion", ContainerRuntimeVersion: "RuntimeType://RuntimeVersion", KubeletVersion: version.Get().String(), - KubeProxyVersion: version.Get().String(), + KubeProxyVersion: "", }, }, }, - kubeProxyVersion: true, }, { desc: "error getting version info", @@ -1001,7 +999,6 @@ func TestVersionInfo(t *testing.T) { versionInfoError: fmt.Errorf("foo"), expectNode: &v1.Node{}, expectError: fmt.Errorf("error getting version info: foo"), - kubeProxyVersion: true, }, { desc: "error getting runtime version results in Unknown runtime", @@ -1014,11 +1011,10 @@ func TestVersionInfo(t *testing.T) { NodeInfo: v1.NodeSystemInfo{ ContainerRuntimeVersion: "RuntimeType://Unknown", KubeletVersion: version.Get().String(), - KubeProxyVersion: version.Get().String(), + KubeProxyVersion: "", }, }, }, - kubeProxyVersion: true, }, { desc: "DisableNodeKubeProxyVersion FeatureGate enable, versions set in node info", @@ -1038,10 +1034,10 @@ func TestVersionInfo(t *testing.T) { OSImage: "ContainerOSVersion", ContainerRuntimeVersion: "RuntimeType://RuntimeVersion", KubeletVersion: version.Get().String(), + KubeProxyVersion: "", }, }, }, - kubeProxyVersion: false, }, { desc: "DisableNodeKubeProxyVersion FeatureGate enable, KubeProxyVersion will be cleared if it is set.", @@ -1052,7 +1048,7 @@ func TestVersionInfo(t *testing.T) { OSImage: "ContainerOSVersion", ContainerRuntimeVersion: "RuntimeType://RuntimeVersion", KubeletVersion: version.Get().String(), - KubeProxyVersion: version.Get().String(), + KubeProxyVersion: "", }, }, }, @@ -1071,17 +1067,15 @@ func TestVersionInfo(t *testing.T) { OSImage: "ContainerOSVersion", ContainerRuntimeVersion: "RuntimeType://RuntimeVersion", KubeletVersion: version.Get().String(), + KubeProxyVersion: "", }, }, }, - kubeProxyVersion: false, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DisableNodeKubeProxyVersion, !tc.kubeProxyVersion) - ctx := ktesting.Init(t) versionInfoFunc := func() (*cadvisorapiv1.VersionInfo, error) { return tc.versionInfo, tc.versionInfoError @@ -1342,7 +1336,7 @@ func TestReadyCondition(t *testing.T) { return tc.nodeShutdownManagerErrors } events := []testEvent{} - recordEventFunc := func(eventType, event string) { + recordEventFunc := func(logger klog.Logger, eventType, event string) { events = append(events, testEvent{ eventType: eventType, event: event, @@ -1461,7 +1455,7 @@ func TestMemoryPressureCondition(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { ctx := ktesting.Init(t) events := []testEvent{} - recordEventFunc := func(eventType, event string) { + recordEventFunc := func(logger klog.Logger, eventType, event string) { events = append(events, testEvent{ eventType: eventType, event: event, @@ -1583,7 +1577,7 @@ func TestPIDPressureCondition(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { ctx := ktesting.Init(t) events := []testEvent{} - recordEventFunc := func(eventType, event string) { + recordEventFunc := func(logger klog.Logger, eventType, event string) { events = append(events, testEvent{ eventType: eventType, event: event, @@ -1705,7 +1699,7 @@ func TestDiskPressureCondition(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { ctx := ktesting.Init(t) events := []testEvent{} - recordEventFunc := func(eventType, event string) { + recordEventFunc := func(logger klog.Logger, eventType, event string) { events = append(events, testEvent{ eventType: eventType, event: event, diff --git a/pkg/kubelet/oom/oom_watcher_linux.go b/pkg/kubelet/oom/oom_watcher_linux.go index c0344c52a3308..b0e4027b59ecf 100644 --- a/pkg/kubelet/oom/oom_watcher_linux.go +++ b/pkg/kubelet/oom/oom_watcher_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -38,7 +37,7 @@ type streamer interface { var _ streamer = &oomparser.OomParser{} type realWatcher struct { - recorder record.EventRecorder + recorder record.EventRecorderLogger oomStreamer streamer } @@ -46,7 +45,7 @@ var _ Watcher = &realWatcher{} // NewWatcher creates and initializes a OOMWatcher backed by Cadvisor as // the oom streamer. -func NewWatcher(recorder record.EventRecorder) (Watcher, error) { +func NewWatcher(recorder record.EventRecorderLogger) (Watcher, error) { // for test purpose _, ok := recorder.(*record.FakeRecorder) if ok { @@ -78,7 +77,7 @@ func (ow *realWatcher) Start(ctx context.Context, ref *v1.ObjectReference) error go func() { logger := klog.FromContext(ctx) - defer runtime.HandleCrash() + defer runtime.HandleCrashWithContext(ctx) for event := range outStream { if event.VictimContainerName == recordEventContainerName { @@ -87,7 +86,7 @@ func (ow *realWatcher) Start(ctx context.Context, ref *v1.ObjectReference) error if event.ProcessName != "" && event.Pid != 0 { eventMsg = fmt.Sprintf("%s, victim process: %s, pid: %d", eventMsg, event.ProcessName, event.Pid) } - ow.recorder.Eventf(ref, v1.EventTypeWarning, systemOOMEvent, eventMsg) + ow.recorder.WithLogger(logger).Eventf(ref, v1.EventTypeWarning, systemOOMEvent, "%s", eventMsg) } } logger.Error(nil, "Unexpectedly stopped receiving OOM notifications") diff --git a/pkg/kubelet/oom/oom_watcher_unsupported.go b/pkg/kubelet/oom/oom_watcher_unsupported.go index d0abd9253b077..38767c4a18447 100644 --- a/pkg/kubelet/oom/oom_watcher_unsupported.go +++ b/pkg/kubelet/oom/oom_watcher_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2019 The Kubernetes Authors. diff --git a/pkg/kubelet/pleg/evented.go b/pkg/kubelet/pleg/evented.go index c0a3ca5c98769..2fea1ebb84ca2 100644 --- a/pkg/kubelet/pleg/evented.go +++ b/pkg/kubelet/pleg/evented.go @@ -118,6 +118,10 @@ func (e *EventedPLEG) Relist() { e.genericPleg.Relist() } +func (e *EventedPLEG) RequestRelist(podUID types.UID) { + e.genericPleg.RequestRelist(podUID) +} + // Start starts the Evented PLEG func (e *EventedPLEG) Start() { e.runningMu.Lock() @@ -416,6 +420,6 @@ func (e *EventedPLEG) updateLatencyMetric(event *runtimeapi.ContainerEventRespon metrics.EventedPLEGConnLatency.Observe(duration.Seconds()) } -func (e *EventedPLEG) SetPodWatchCondition(podUID types.UID, conditionKey string, condition WatchCondition) { - e.genericPleg.SetPodWatchCondition(podUID, conditionKey, condition) +func (e *EventedPLEG) RequestReinspect(podUID types.UID) { + e.genericPleg.RequestReinspect(podUID) } diff --git a/pkg/kubelet/pleg/generic.go b/pkg/kubelet/pleg/generic.go index 5db6ae40e4a5d..ffa2e75b24cb2 100644 --- a/pkg/kubelet/pleg/generic.go +++ b/pkg/kubelet/pleg/generic.go @@ -25,7 +25,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/klog/v2" @@ -42,7 +41,7 @@ import ( // // Note that GenericPLEG assumes that a container would not be created, // terminated, and garbage collected within one relist period. If such an -// incident happens, GenenricPLEG would miss all events regarding this +// incident happens, GenericPLEG would miss all events regarding this // container. In the case of relisting failure, the window may become longer. // Note that this assumption is not unique -- many kubelet internal components // rely on terminated containers as tombstones for bookkeeping purposes. The @@ -56,6 +55,7 @@ type GenericPLEG struct { // The channel from which the subscriber listens events. eventChannel chan *PodLifecycleEvent // The internal cache for pod/container information. + // Guarded by relistLock. podRecords podRecords // Time of the last relisting. relistTime atomic.Value @@ -65,7 +65,7 @@ type GenericPLEG struct { clock clock.Clock // Pods that failed to have their status retrieved during a relist. These pods will be // retried during the next relisting. - podsToReinspect map[types.UID]*kubecontainer.Pod + podsToReinspect sync.Map // map: podUID -> empty // Stop the Generic PLEG by closing the channel. stopCh chan struct{} // Locks the relisting of the Generic PLEG @@ -78,17 +78,14 @@ type GenericPLEG struct { relistDuration *RelistDuration // logger is used for contextual logging logger klog.Logger - // watchConditions tracks pod watch conditions, guarded by watchConditionsLock - // watchConditions is a map of pod UID -> condition key -> condition - watchConditions map[types.UID]map[string]versionedWatchCondition - watchConditionsLock sync.Mutex + // relistRequests is the queue of on-demand pod relist requests. + relistRequests chan relistRequest + // globalRelistTimer controls the periodic global relist. + globalRelistTimer clock.Timer } -type versionedWatchCondition struct { - key string - condition WatchCondition - version uint32 -} +// Empty placeholder value for podsToReinspect (shared pointer reduces allocations). +var empty = &struct{}{} // plegContainerState has a one-to-one mapping to the // kubecontainer.State except for the non-existent state. This state @@ -118,6 +115,15 @@ func convertState(state kubecontainer.State) plegContainerState { } } +// relistRequest is used to track on-demand single-pod relist requests. +type relistRequest struct { + // podUID is the UID of the pod to relist. + podUID types.UID + // timestamp is the time the request was made. If the pod has already been relisted by the time + // this request is executed, the request will be skipped. + timestamp time.Time +} + type podRecord struct { old *kubecontainer.Pod current *kubecontainer.Pod @@ -133,14 +139,14 @@ func NewGenericPLEG(logger klog.Logger, runtime kubecontainer.Runtime, eventChan panic("cache cannot be nil") } return &GenericPLEG{ - logger: logger, - relistDuration: relistDuration, - runtime: runtime, - eventChannel: eventChannel, - podRecords: make(podRecords), - cache: cache, - clock: clock, - watchConditions: make(map[types.UID]map[string]versionedWatchCondition), + logger: logger, + relistDuration: relistDuration, + runtime: runtime, + eventChannel: eventChannel, + podRecords: make(podRecords), + cache: cache, + clock: clock, + relistRequests: make(chan relistRequest, 200), } } @@ -155,11 +161,20 @@ func (g *GenericPLEG) Watch() chan *PodLifecycleEvent { func (g *GenericPLEG) Start() { g.runningMu.Lock() defer g.runningMu.Unlock() - if !g.isRunning { - g.isRunning = true - g.stopCh = make(chan struct{}) - go wait.Until(g.Relist, g.relistDuration.RelistPeriod, g.stopCh) + if g.isRunning { + return } + + g.isRunning = true + g.stopCh = make(chan struct{}) + // Set 0 to trigger the first relist immediately + // The RelistPeriod is set after the first relist. + g.globalRelistTimer = g.clock.NewTimer(0) + + go func() { + for g.workerLoopIteration() { + } + }() } func (g *GenericPLEG) Stop() { @@ -168,9 +183,52 @@ func (g *GenericPLEG) Stop() { if g.isRunning { close(g.stopCh) g.isRunning = false + g.globalRelistTimer.Stop() } } +// workerLoopIteration is the primary work dispatcher for the PLEG. It uses non-blocking channel +// reads for a prioritized work selection: +// 1. stop: exit the workerLoop if stopCh has been closed. +// 2. global relist: the global relist timer takes priority over single-pod relists. +// 3. single pod relist: execute a request from the single-pod relist queue. +func (g *GenericPLEG) workerLoopIteration() bool { + // Use non-blocking selects for prioritized selection. + + // First priority: stopCh + select { + case <-g.stopCh: + return false + default: + } + + // Second priority: global Relist + select { + case <-g.globalRelistTimer.C(): + g.Relist() + g.globalRelistTimer.Reset(g.relistDuration.RelistPeriod) + return true + default: + } + + // Fallback: blocking select + select { + case <-g.stopCh: + return false + case <-g.globalRelistTimer.C(): + g.Relist() + g.globalRelistTimer.Reset(g.relistDuration.RelistPeriod) + case req := <-g.relistRequests: + if req.timestamp.After(g.getRelistTime()) { + // The request hasn't already been served by the global relist. + g.relistPod(req.podUID) + } + } + + return true +} + +// This must not be called while the GenericPLEG is running. func (g *GenericPLEG) Update(relistDuration *RelistDuration) { g.relistDuration = relistDuration } @@ -261,111 +319,136 @@ func (g *GenericPLEG) Relist() { // update running pod and container count updateRunningPodAndContainerMetrics(pods) g.podRecords.setCurrent(pods) - g.cleanupOrphanedWatchConditions() - - needsReinspection := make(map[types.UID]*kubecontainer.Pod) for pid := range g.podRecords { - // Compare the old and the current pods, and generate events. - oldPod := g.podRecords.getOld(pid) - pod := g.podRecords.getCurrent(pid) - // Get all containers in the old and the new pod. - allContainers := getContainersFromPods(oldPod, pod) - var events []*PodLifecycleEvent - for _, container := range allContainers { - containerEvents := computeEvents(g.logger, oldPod, pod, &container.ID) - events = append(events, containerEvents...) - } + g.reconcilePodRecord(ctx, pid) + } - watchConditions := g.getPodWatchConditions(pid) - _, reinspect := g.podsToReinspect[pid] + // Update the cache timestamp. This needs to happen *after* + // all pods have been properly updated in the cache. + g.cache.UpdateTime(timestamp) +} - if len(events) == 0 && len(watchConditions) == 0 && !reinspect { - // Nothing else needed for this pod. - continue - } +// reconcilePodRecord compares the old & current pod record and checks for event-worthy state +// changes. If any events are generated, or the pod needs reinspection, it fetches the updated +// status, updates the pod cache, and delivers the events. This function should only be called after +// the current pod record has been updated. +func (g *GenericPLEG) reconcilePodRecord(ctx context.Context, pid types.UID) { + // Compare the old and the current pods, and generate events. + oldPod := g.podRecords.getOld(pid) + pod := g.podRecords.getCurrent(pid) + // Get all containers in the old and the new pod. + allContainers := getContainersFromPods(oldPod, pod) + var events []*PodLifecycleEvent + for _, container := range allContainers { + containerEvents := computeEvents(g.logger, oldPod, pod, &container.ID) + events = append(events, containerEvents...) + } + + _, reinspect := g.podsToReinspect.LoadAndDelete(pid) + + if len(events) == 0 && !reinspect { + // Nothing else needed for this pod. + return + } - // updateCache() will inspect the pod and update the cache. If an - // error occurs during the inspection, we want PLEG to retry again - // in the next relist. To achieve this, we do not update the - // associated podRecord of the pod, so that the change will be - // detect again in the next relist. - // TODO: If many pods changed during the same relist period, - // inspecting the pod and getting the PodStatus to update the cache - // serially may take a while. We should be aware of this and - // parallelize if needed. - status, updated, err := g.updateCache(ctx, pod, pid) - if err != nil { - // Rely on updateCache calling GetPodStatus to log the actual error. - g.logger.V(4).Info("PLEG: Ignoring events for pod", "pod", klog.KRef(pod.Namespace, pod.Name), "err", err) - - // make sure we try to reinspect the pod during the next relisting - needsReinspection[pid] = pod + // updateCache() will inspect the pod and update the cache. If an + // error occurs during the inspection, we want PLEG to retry again + // in the next relist. To achieve this, we do not update the + // associated podRecord of the pod, so that the change will be + // detect again in the next relist. + // TODO: If many pods changed during the same relist period, + // inspecting the pod and getting the PodStatus to update the cache + // serially may take a while. We should be aware of this and + // parallelize if needed. + status, updated, err := g.updateCache(ctx, pod, pid) + if err != nil { + // Rely on updateCache calling GetPodStatus to log the actual error. + g.logger.V(4).Info("PLEG: Ignoring events for pod", "pod", klog.KRef(pod.Namespace, pod.Name), "err", err) - continue - } else if utilfeature.DefaultFeatureGate.Enabled(features.EventedPLEG) { - if !updated { - continue - } - } + // make sure we try to reinspect the pod during the next relisting + g.podsToReinspect.Store(pid, empty) - var completedConditions []versionedWatchCondition - for _, condition := range watchConditions { - if condition.condition(status) { - // condition was met: add it to the list of completed conditions. - completedConditions = append(completedConditions, condition) - } - } - if len(completedConditions) > 0 { - g.completeWatchConditions(pid, completedConditions) - // If at least 1 condition completed, emit a ConditionMet event to trigger a pod sync. - // We only emit 1 event even if multiple conditions are met, since SyncPod reevaluates - // all containers in the pod with the latest status. - events = append(events, &PodLifecycleEvent{ID: pid, Type: ConditionMet}) + return + } else if utilfeature.DefaultFeatureGate.Enabled(features.EventedPLEG) { + if !updated { + return } + } - // Update the internal storage and send out the events. - g.podRecords.update(pid) + if len(events) == 0 { + // Make sure we always trigger a PodSync after a full reinspection. + events = append(events, &PodLifecycleEvent{ID: pid, Type: PodSync}) + } - // Map from containerId to exit code; used as a temporary cache for lookup - containerExitCode := make(map[string]int) + // Update the internal storage and send out the events. + g.podRecords.update(pid) - for i := range events { - // Filter out events that are not reliable and no other components use yet. - if events[i].Type == ContainerChanged { - continue - } - select { - case g.eventChannel <- events[i]: - default: - metrics.PLEGDiscardEvents.Inc() - g.logger.Error(nil, "Event channel is full, discard this relist() cycle event") - } - // Log exit code of containers when they finished in a particular event - if events[i].Type == ContainerDied { - // Fill up containerExitCode map for ContainerDied event when first time appeared - if len(containerExitCode) == 0 && pod != nil { - if err == nil { - for _, containerStatus := range status.ContainerStatuses { - containerExitCode[containerStatus.ID.ID] = containerStatus.ExitCode - } + // Map from containerId to exit code; used as a temporary cache for lookup + containerExitCode := make(map[string]int) + + for i := range events { + // Filter out events that are not reliable and no other components use yet. + if events[i].Type == ContainerChanged { + continue + } + select { + case g.eventChannel <- events[i]: + default: + metrics.PLEGDiscardEvents.Inc() + g.logger.Error(nil, "Event channel is full, discard this relist() cycle event") + } + // Log exit code of containers when they finished in a particular event + if events[i].Type == ContainerDied { + // Fill up containerExitCode map for ContainerDied event when first time appeared + if len(containerExitCode) == 0 && pod != nil { + if err == nil { + for _, containerStatus := range status.ContainerStatuses { + containerExitCode[containerStatus.ID.ID] = containerStatus.ExitCode } } - if containerID, ok := events[i].Data.(string); ok { - if exitCode, ok := containerExitCode[containerID]; ok && pod != nil { - g.logger.V(2).Info("Generic (PLEG): container finished", "podID", pod.ID, "containerID", containerID, "exitCode", exitCode) - } + } + if containerID, ok := events[i].Data.(string); ok { + if exitCode, ok := containerExitCode[containerID]; ok && pod != nil { + g.logger.V(2).Info("Generic (PLEG): container finished", "podID", pod.ID, "containerID", containerID, "exitCode", exitCode) } } } } +} - // Update the cache timestamp. This needs to happen *after* - // all pods have been properly updated in the cache. - g.cache.UpdateTime(timestamp) +func (g *GenericPLEG) relistPod(podUID types.UID) { + g.relistLock.Lock() + defer g.relistLock.Unlock() + + ctx := context.Background() + + g.logger.V(5).Info("GenericPLEG: Relisting Pod", "podUID", podUID) + + startTime := g.clock.Now() + defer func() { + metrics.PLEGPodRelistDuration.Observe(metrics.SinceInSeconds(startTime)) + }() + pod, err := g.runtime.GetPod(ctx, podUID) + if err != nil { + // We don't need to do anything other than log the error here, since it will be retried by + // the next global relist. + g.logger.Error(err, "GenericPLEG: Unable to retrieve pod", "podUID", podUID) + return + } + + // Update current pod record + if pr, ok := g.podRecords[pod.ID]; ok { + pr.current = pod + } else { + g.podRecords[pod.ID] = &podRecord{current: pod} + } + + g.reconcilePodRecord(ctx, podUID) - // make sure we retain the list of pods that need reinspecting the next time relist is called - g.podsToReinspect = needsReinspection + // Since we're not setting the global timestamp, we need to mark this pod as observed in case it + // wasn't modified. + g.cache.SetObservedTime(podUID, pod.Timestamp) } func getContainersFromPods(pods ...*kubecontainer.Pod) []*kubecontainer.Container { @@ -442,9 +525,8 @@ func (g *GenericPLEG) updateCache(ctx context.Context, pod *kubecontainer.Pod, p return nil, true, nil } - timestamp := g.clock.Now() - - status, err := g.runtime.GetPodStatus(ctx, pod.ID, pod.Name, pod.Namespace) + timestamp := pod.Timestamp + status, err := g.runtime.GetPodStatus(ctx, pod) if err != nil { // nolint:logcheck // Not using the result of klog.V inside the // if branch is okay, we just use it to determine whether the @@ -484,86 +566,19 @@ func (g *GenericPLEG) updateCache(ctx context.Context, pod *kubecontainer.Pod, p return status, g.cache.Set(pod.ID, status, err, timestamp), err } -// SetPodWatchCondition flags the pod for reinspection on every Relist iteration until the watch -// condition is met. The condition is keyed so it can be updated before the condition -// is met. -func (g *GenericPLEG) SetPodWatchCondition(podUID types.UID, conditionKey string, condition WatchCondition) { - g.watchConditionsLock.Lock() - defer g.watchConditionsLock.Unlock() - - conditions, ok := g.watchConditions[podUID] - if !ok { - conditions = make(map[string]versionedWatchCondition) - } - - versioned, found := conditions[conditionKey] - if found { - // Watch condition was already set. Increment its version & update the condition function. - versioned.version++ - versioned.condition = condition - conditions[conditionKey] = versioned - } else { - conditions[conditionKey] = versionedWatchCondition{ - key: conditionKey, - condition: condition, - } - } - - g.watchConditions[podUID] = conditions -} - -// getPodWatchConditions returns a list of the active watch conditions for the pod. -func (g *GenericPLEG) getPodWatchConditions(podUID types.UID) []versionedWatchCondition { - g.watchConditionsLock.Lock() - defer g.watchConditionsLock.Unlock() - - podConditions, ok := g.watchConditions[podUID] - if !ok { - return nil - } - - // Flatten the map into a list of conditions. This also serves to create a copy, so the lock can - // be released. - conditions := make([]versionedWatchCondition, 0, len(podConditions)) - for _, condition := range podConditions { - conditions = append(conditions, condition) - } - return conditions +func (g *GenericPLEG) RequestReinspect(podUID types.UID) { + g.podsToReinspect.Store(podUID, empty) } -// completeWatchConditions removes the completed watch conditions, unless they have been updated -// since the condition was checked. -func (g *GenericPLEG) completeWatchConditions(podUID types.UID, completedConditions []versionedWatchCondition) { - g.watchConditionsLock.Lock() - defer g.watchConditionsLock.Unlock() - - conditions, ok := g.watchConditions[podUID] - if !ok { - // Pod was deleted, nothing to do. +func (g *GenericPLEG) RequestRelist(podUID types.UID) { + if !utilfeature.DefaultFeatureGate.Enabled(features.PLEGOnDemandRelist) { return } - for _, completed := range completedConditions { - condition := conditions[completed.key] - // Only clear the condition if it has not been updated. - if condition.version == completed.version { - delete(conditions, completed.key) - } - } - g.watchConditions[podUID] = conditions -} - -// cleanupOrphanedWatchConditions purges the watchConditions map of any pods that were removed from -// the pod records. Events are not emitted for removed pods. -func (g *GenericPLEG) cleanupOrphanedWatchConditions() { - g.watchConditionsLock.Lock() - defer g.watchConditionsLock.Unlock() - - for podUID := range g.watchConditions { - if g.podRecords.getCurrent(podUID) == nil { - // Pod was deleted, remove it from the watch conditions. - delete(g.watchConditions, podUID) - } + select { + case g.relistRequests <- relistRequest{podUID, time.Now()}: + default: + g.logger.Error(nil, "Relist request channel full; dropping relist request", "podUID", podUID) } } diff --git a/pkg/kubelet/pleg/generic_test.go b/pkg/kubelet/pleg/generic_test.go index 7c02483453711..e699ca93466dc 100644 --- a/pkg/kubelet/pleg/generic_test.go +++ b/pkg/kubelet/pleg/generic_test.go @@ -24,6 +24,7 @@ import ( "sort" "strings" "testing" + "testing/synctest" "time" "github.com/google/go-cmp/cmp" @@ -32,8 +33,12 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/metrics" @@ -364,10 +369,10 @@ func TestRelistWithCache(t *testing.T) { pods, statuses, events := createTestPodsStatusesAndEvents(2) runtimeMock.EXPECT().GetPods(ctx, true).Return(pods, nil).Maybe() - runtimeMock.EXPECT().GetPodStatus(ctx, pods[0].ID, "", "").Return(statuses[0], nil).Times(1) + runtimeMock.EXPECT().GetPodStatus(ctx, pods[0]).Return(statuses[0], nil).Times(1) // Inject an error when querying runtime for the pod status for pods[1]. statusErr := fmt.Errorf("unable to get status") - runtimeMock.EXPECT().GetPodStatus(ctx, pods[1].ID, "", "").Return(&kubecontainer.PodStatus{}, statusErr).Times(1) + runtimeMock.EXPECT().GetPodStatus(ctx, pods[1]).Return(&kubecontainer.PodStatus{}, statusErr).Times(1) pleg.Relist() actualEvents := getEventsFromChannel(ch) @@ -389,7 +394,7 @@ func TestRelistWithCache(t *testing.T) { assert.Exactly(t, []*PodLifecycleEvent{events[0]}, actualEvents) // Return normal status for pods[1]. - runtimeMock.EXPECT().GetPodStatus(ctx, pods[1].ID, "", "").Return(statuses[1], nil).Times(1) + runtimeMock.EXPECT().GetPodStatus(ctx, pods[1]).Return(statuses[1], nil).Times(1) pleg.Relist() actualEvents = getEventsFromChannel(ch) cases = []struct { @@ -417,7 +422,7 @@ func TestRemoveCacheEntry(t *testing.T) { pods, statuses, _ := createTestPodsStatusesAndEvents(1) runtimeMock.EXPECT().GetPods(ctx, true).Return(pods, nil).Times(1) - runtimeMock.EXPECT().GetPodStatus(ctx, pods[0].ID, "", "").Return(statuses[0], nil).Times(1) + runtimeMock.EXPECT().GetPodStatus(ctx, pods[0]).Return(statuses[0], nil).Times(1) // Does a relist to populate the cache. pleg.Relist() // Delete the pod from runtime. Verify that the cache entry has been @@ -456,75 +461,168 @@ func TestHealthy(t *testing.T) { assert.False(t, ok, "pleg should be unhealthy") } -func TestRelistWithReinspection(t *testing.T) { +func TestReinspect(t *testing.T) { ctx := context.Background() - runtimeMock := containertest.NewMockRuntime(t) - - pleg := newTestGenericPLEGWithRuntimeMock(runtimeMock) - ch := pleg.Watch() - - infraContainer := createTestContainer("infra", kubecontainer.ContainerStateRunning) - - podID := types.UID("test-pod") - pods := []*kubecontainer.Pod{{ - ID: podID, - Containers: []*kubecontainer.Container{infraContainer}, - }} - runtimeMock.EXPECT().GetPods(ctx, true).Return(pods, nil).Times(1) - goodStatus := &kubecontainer.PodStatus{ - ID: podID, - ContainerStatuses: []*kubecontainer.Status{{ID: infraContainer.ID, State: infraContainer.State}}, + tests := []struct { + name string + requestReinspect bool + alreadyReinspect bool + updateCacheError error + podDeleted bool + expectUpdateCache bool + expectReinspect bool // value in podsToReinspect AFTER relist + expectEvent bool + expectStatus bool + }{ + { + name: "RequestReinspect a pod not previously listed, success", + requestReinspect: true, + alreadyReinspect: false, + updateCacheError: nil, + expectUpdateCache: true, + expectReinspect: false, + expectEvent: true, + expectStatus: true, + }, + { + name: "RequestReinspect a pod not previously listed, failure", + requestReinspect: true, + alreadyReinspect: false, + updateCacheError: errors.New("fail"), + expectUpdateCache: true, + expectReinspect: true, + expectEvent: false, + expectStatus: true, + }, + { + name: "RequestReinspect of a pod already listed for reinspection, success", + requestReinspect: true, + alreadyReinspect: true, + updateCacheError: nil, + expectUpdateCache: true, + expectReinspect: false, + expectEvent: true, + expectStatus: true, + }, + { + name: "RequestReinspect of a pod already listed for reinspection, failure", + requestReinspect: true, + alreadyReinspect: true, + updateCacheError: errors.New("fail"), + expectUpdateCache: true, + expectReinspect: true, + expectEvent: false, + expectStatus: true, + }, + { + name: "Don't request reinspection", + requestReinspect: false, + alreadyReinspect: false, + updateCacheError: nil, + expectUpdateCache: false, + expectReinspect: false, + expectEvent: false, + expectStatus: false, + }, + { + name: "Don't request reinspection, but already listed for reinspection, success", + requestReinspect: false, + alreadyReinspect: true, + updateCacheError: nil, + expectUpdateCache: true, + expectReinspect: false, + expectEvent: true, + expectStatus: true, + }, + { + name: "Don't request reinspection, but already listed for reinspection, failure", + requestReinspect: false, + alreadyReinspect: true, + updateCacheError: errors.New("fail"), + expectUpdateCache: true, + expectReinspect: true, + expectEvent: false, + expectStatus: true, + }, + { + name: "Pod deleted, should clear reinspect", + requestReinspect: false, + alreadyReinspect: true, + podDeleted: true, + updateCacheError: nil, + expectUpdateCache: true, + expectReinspect: false, + expectEvent: true, + expectStatus: false, // Deleted from cache + }, } - runtimeMock.EXPECT().GetPodStatus(ctx, podID, "", "").Return(goodStatus, nil).Times(1) - goodEvent := &PodLifecycleEvent{ID: podID, Type: ContainerStarted, Data: infraContainer.ID.ID} + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runtimeMock := containertest.NewMockRuntime(t) + pleg := newTestGenericPLEGWithRuntimeMock(runtimeMock) + ch := pleg.Watch() + + podID := types.UID("test-pod") + if tc.alreadyReinspect || tc.requestReinspect { + pleg.RequestReinspect(podID) + } - // listing 1 - everything ok, infra container set up for pod - pleg.Relist() - actualEvents := getEventsFromChannel(ch) - actualStatus, actualErr := pleg.cache.Get(podID) - assert.Equal(t, goodStatus, actualStatus) - assert.NoError(t, actualErr) - assert.Exactly(t, []*PodLifecycleEvent{goodEvent}, actualEvents) + pod := &kubecontainer.Pod{ID: podID, Name: "name", Namespace: "ns"} + // Populate podRecords. + pleg.podRecords[podID] = &podRecord{old: pod, current: pod} - // listing 2 - pretend runtime was in the middle of creating the non-infra container for the pod - // and return an error during inspection - transientContainer := createTestContainer("transient", kubecontainer.ContainerStateUnknown) - podsWithTransientContainer := []*kubecontainer.Pod{{ - ID: podID, - Containers: []*kubecontainer.Container{infraContainer, transientContainer}, - }} - runtimeMock.EXPECT().GetPods(ctx, true).Return(podsWithTransientContainer, nil).Times(1) + if tc.podDeleted { + runtimeMock.EXPECT().GetPods(ctx, true).Return([]*kubecontainer.Pod{}, nil) + } else { + runtimeMock.EXPECT().GetPods(ctx, true).Return([]*kubecontainer.Pod{pod}, nil) + } - badStatus := &kubecontainer.PodStatus{ - ID: podID, - ContainerStatuses: []*kubecontainer.Status{}, - } - runtimeMock.EXPECT().GetPodStatus(ctx, podID, "", "").Return(badStatus, errors.New("inspection error")).Times(1) + var expectedStatus *kubecontainer.PodStatus + if tc.updateCacheError == nil { + expectedStatus = &kubecontainer.PodStatus{ID: podID, TimeStamp: time.Now()} + } + if tc.expectUpdateCache { + if tc.podDeleted { + // updateCache(ctx, nil, podID) will be called, it doesn't call GetPodStatus + } else { + runtimeMock.EXPECT().GetPodStatus(ctx, pod).Return(expectedStatus, tc.updateCacheError) + } + } - pleg.Relist() - actualEvents = getEventsFromChannel(ch) - actualStatus, actualErr = pleg.cache.Get(podID) - assert.Equal(t, badStatus, actualStatus) - assert.Equal(t, errors.New("inspection error"), actualErr) - assert.Exactly(t, []*PodLifecycleEvent{}, actualEvents) + pleg.Relist() - // listing 3 - pretend the transient container has now disappeared, leaving just the infra - // container. Make sure the pod is reinspected for its status and the cache is updated. - runtimeMock.EXPECT().GetPods(ctx, true).Return(pods, nil).Times(1) - runtimeMock.EXPECT().GetPodStatus(ctx, podID, "", "").Return(goodStatus, nil).Times(1) + _, actualReinspect := pleg.podsToReinspect.Load(podID) + assert.Equal(t, tc.expectReinspect, actualReinspect) + + actualEvents := getEventsFromChannel(ch) + if tc.expectEvent { + assert.NotEmpty(t, actualEvents, "Expected events to be emitted") + // We expect at least a PodSync event since pod state didn't change in test setup + hasPodSync := false + for _, e := range actualEvents { + if e.ID == podID && e.Type == PodSync { + hasPodSync = true + break + } + } + assert.True(t, hasPodSync, "Expected PodSync event for pod") + } else { + assert.Empty(t, actualEvents, "Expected no events to be emitted") + } - pleg.Relist() - actualEvents = getEventsFromChannel(ch) - actualStatus, actualErr = pleg.cache.Get(podID) - assert.Equal(t, goodStatus, actualStatus) - assert.NoError(t, actualErr) - // no events are expected because relist #1 set the old pod record which has the infra container - // running. relist #2 had the inspection error and therefore didn't modify either old or new. - // relist #3 forced the reinspection of the pod to retrieve its status, but because the list of - // containers was the same as relist #1, nothing "changed", so there are no new events. - assert.Exactly(t, []*PodLifecycleEvent{}, actualEvents) + if tc.expectStatus { + actualStatus, actualErr := pleg.cache.Get(podID) + assert.Equal(t, expectedStatus, actualStatus) + assert.Equal(t, tc.updateCacheError, actualErr) + } else if tc.podDeleted { + actualStatus, _ := pleg.cache.Get(podID) + // If deleted, Get returns an empty status with the ID + assert.Equal(t, &kubecontainer.PodStatus{ID: podID}, actualStatus) + } + }) + } } // Test detecting sandbox state changes. @@ -633,7 +731,7 @@ func TestRelistIPChange(t *testing.T) { event := &PodLifecycleEvent{ID: pod.ID, Type: ContainerStarted, Data: container.ID.ID} runtimeMock.EXPECT().GetPods(ctx, true).Return([]*kubecontainer.Pod{pod}, nil).Times(1) - runtimeMock.EXPECT().GetPodStatus(ctx, pod.ID, "", "").Return(status, nil).Times(1) + runtimeMock.EXPECT().GetPodStatus(ctx, pod).Return(status, nil).Times(1) pleg.Relist() actualEvents := getEventsFromChannel(ch) @@ -654,7 +752,7 @@ func TestRelistIPChange(t *testing.T) { } event = &PodLifecycleEvent{ID: pod.ID, Type: ContainerDied, Data: container.ID.ID} runtimeMock.EXPECT().GetPods(ctx, true).Return([]*kubecontainer.Pod{pod}, nil).Times(1) - runtimeMock.EXPECT().GetPodStatus(ctx, pod.ID, "", "").Return(status, nil).Times(1) + runtimeMock.EXPECT().GetPodStatus(ctx, pod).Return(status, nil).Times(1) pleg.Relist() actualEvents = getEventsFromChannel(ch) @@ -739,259 +837,144 @@ kubelet_running_pods 2 } } -func TestWatchConditions(t *testing.T) { - pods := []*kubecontainer.Pod{{ - Name: "running-pod", - ID: "running", - Sandboxes: []*kubecontainer.Container{ - createTestContainer("s", kubecontainer.ContainerStateRunning), - }, - Containers: []*kubecontainer.Container{ - createTestContainer("c", kubecontainer.ContainerStateRunning), - }, - }, { - Name: "running-pod-2", - ID: "running-2", - Sandboxes: []*kubecontainer.Container{ - createTestContainer("s", kubecontainer.ContainerStateRunning), - }, - Containers: []*kubecontainer.Container{ - createTestContainer("c-exited", kubecontainer.ContainerStateExited), - createTestContainer("c-running", kubecontainer.ContainerStateRunning), - }, - }, { - Name: "terminating-pod", - ID: "terminating", - Sandboxes: []*kubecontainer.Container{ - createTestContainer("s", kubecontainer.ContainerStateExited), - }, - }, { - Name: "reinspect-pod", - ID: "reinspect", - Sandboxes: []*kubecontainer.Container{ - createTestContainer("s", kubecontainer.ContainerStateRunning), - }, - }} - initialPods := pods - initialPods = append(initialPods, &kubecontainer.Pod{ - Name: "terminated-pod", - ID: "terminated", - Sandboxes: []*kubecontainer.Container{ - createTestContainer("s", kubecontainer.ContainerStateExited), - }, +func TestWorkerLoop(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PLEGOnDemandRelist, true) + synctest.Test(t, func(t *testing.T) { + runtimeMock := containertest.NewMockRuntime(t) + cache := kubecontainer.NewCache() + pleg := NewGenericPLEG( + ktesting.NewLogger(t, ktesting.DefaultConfig), + runtimeMock, + make(chan *PodLifecycleEvent, 100), + &RelistDuration{RelistPeriod: 2 * time.Second}, + cache, + clock.RealClock{}, + ).(*GenericPLEG) + + pod1 := &kubecontainer.Pod{ID: "pod1", Name: "pod1", Containers: []*kubecontainer.Container{{ID: kubecontainer.ContainerID{Type: "test", ID: "c1"}, State: kubecontainer.ContainerStateRunning}}} + pod2 := &kubecontainer.Pod{ID: "pod2", Name: "pod2", Containers: []*kubecontainer.Container{{ID: kubecontainer.ContainerID{Type: "test", ID: "c2"}, State: kubecontainer.ContainerStateRunning}}} + + var call *mock.Call // Used to assert order of mock calls. + + startTime := time.Now() + pleg.globalRelistTimer = pleg.clock.NewTimer(2 * time.Second) + + // pod1 and pod2 requests should initially be blocked. + p1res := getNewerThanAsync(t, cache, pod1.ID, startTime) + requireBlocked(t, p1res) + p2res := getNewerThanAsync(t, cache, pod2.ID, startTime) + requireBlocked(t, p2res) + + t.Log("Test single-pod relist (no reinspect)") + pleg.RequestRelist(pod1.ID) + + mctx := context.Background() + call = runtimeMock.EXPECT().GetPod(mctx, pod1.ID).RunAndReturn(func(ctx context.Context, uid types.UID) (*kubecontainer.Pod, error) { + assert.Equal(t, pod1.ID, uid) + pod1.Timestamp = time.Now() + return pod1, nil + }).Once() + call = runtimeMock.EXPECT().GetPodStatus(mctx, pod1).RunAndReturn(func(_ context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { + assert.Equal(t, pod1, pod) + return &kubecontainer.PodStatus{ID: pod1.ID, TimeStamp: time.Now()}, nil + }).NotBefore(call).Once() + + pleg.workerLoopIteration() + + p1Status := requireUnblocked(t, p1res) // pod1 is now unblocked + assert.Equal(t, pod1.ID, p1Status.ID) + assert.Equal(t, time.Now(), p1Status.TimeStamp) + requireBlocked(t, p2res) // pod2 is still blocked + + p1NewRes := getNewerThanAsync(t, cache, pod1.ID, startTime.Add(2*time.Second)) + requireBlocked(t, p1NewRes) + + t.Log("Test triggering both global relist and pod2 relist to ensure the global relist gets priority.") + pleg.RequestRelist(pod2.ID) + time.Sleep(2 * time.Second) + + call = runtimeMock.EXPECT().GetPods(mctx, true).RunAndReturn(func(_ context.Context, _ bool) ([]*kubecontainer.Pod, error) { + pod1.Timestamp = time.Now() + pod2.Timestamp = time.Now() + return []*kubecontainer.Pod{pod1, pod2}, nil + }).NotBefore(call).Once() + call = runtimeMock.EXPECT().GetPodStatus(mctx, pod2).RunAndReturn(func(_ context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { + assert.Equal(t, pod2, pod) + return &kubecontainer.PodStatus{ID: pod2.ID, TimeStamp: time.Now()}, nil + }).NotBefore(call).Once() + + pleg.workerLoopIteration() + + // The global relist should have unblocked p2res and p1NewRes. + p2Status := requireUnblocked(t, p2res) + assert.Equal(t, pod2.ID, p2Status.ID) + p1NewStatus := requireUnblocked(t, p1NewRes) + assert.Equal(t, p1Status, p1NewStatus) // Status timestamp should be unchanged + + // The pod2 relist request should NOT trigger a relist, since it was made before the global + // relist occurred. Drain it from the channel to verify (the mock will trigger an error if GetPod is called for it). + pleg.workerLoopIteration() + + t.Log("Test reinspection") + p1ReinpsectRes := getNewerThanAsync(t, cache, pod1.ID, time.Now()) + requireBlocked(t, p1ReinpsectRes) + + // Queue up the next test case: reinspection of pod1. + pleg.RequestReinspect(pod1.ID) + pleg.RequestRelist(pod1.ID) + time.Sleep(2 * time.Second) + + call = runtimeMock.EXPECT().GetPods(mctx, true).RunAndReturn(func(_ context.Context, _ bool) ([]*kubecontainer.Pod, error) { + pod1.Timestamp = time.Now() + pod2.Timestamp = time.Now() + return []*kubecontainer.Pod{pod1, pod2}, nil + }).NotBefore(call).Once() + runtimeMock.EXPECT().GetPodStatus(mctx, pod1).RunAndReturn(func(_ context.Context, pod *kubecontainer.Pod) (*kubecontainer.PodStatus, error) { + assert.Equal(t, pod1, pod) + return &kubecontainer.PodStatus{ID: pod1.ID, TimeStamp: time.Now()}, nil + }).NotBefore(call).Once() + + pleg.workerLoopIteration() + + p1ReinspectStatus := requireUnblocked(t, p1ReinpsectRes) + assert.Equal(t, pod1.ID, p1ReinspectStatus.ID) + assert.Equal(t, time.Now(), p1ReinspectStatus.TimeStamp) // Status timestamp should be updated. + + // The pod1 relist request should NOT trigger a relist, since it was made before the global + // relist occurred. Drain it from the channel to verify (the mock will trigger an error if GetPod is called for it). + pleg.workerLoopIteration() }) +} - alwaysComplete := func(_ *kubecontainer.PodStatus) bool { - return true - } - neverComplete := func(_ *kubecontainer.PodStatus) bool { - return false - } - - var pleg *GenericPLEG - var updatingCond WatchCondition - // updatingCond always completes, but updates the condition first. - updatingCond = func(_ *kubecontainer.PodStatus) bool { - pleg.SetPodWatchCondition("running", "updating", updatingCond) - return true - } +func getNewerThanAsync(t *testing.T, cache kubecontainer.ROCache, podID types.UID, minTime time.Time) <-chan *kubecontainer.PodStatus { + resCh := make(chan *kubecontainer.PodStatus, 1) + go func() { + s, err := cache.GetNewerThan(podID, minTime) + assert.NoError(t, err) + resCh <- s + }() + return resCh +} - // resettingCond decrements the version before it completes. - var resettingCond = func(_ *kubecontainer.PodStatus) bool { - versioned := pleg.watchConditions["running"]["resetting"] - versioned.version = 0 - pleg.watchConditions["running"]["resetting"] = versioned - return true +func requireBlocked[T any](t *testing.T, ch <-chan T) { + t.Helper() + synctest.Wait() + select { + case r := <-ch: + t.Fatalf("Receive should have blocked, but got: %v", r) + default: + // OK. } +} - // makeContainerCond returns a RunningContainerWatchCondition that asserts the expected container status - makeContainerCond := func(expectedContainerName string, complete bool) WatchCondition { - return RunningContainerWatchCondition(expectedContainerName, func(status *kubecontainer.Status) bool { - if status.Name != expectedContainerName { - panic(fmt.Sprintf("unexpected container name: got %q, want %q", status.Name, expectedContainerName)) - } - return complete - }) - } - - testCases := []struct { - name string - podUID types.UID - watchConditions map[string]WatchCondition - incrementInitialVersion bool // Whether to call SetPodWatchCondition multiple times to increment the version - expectEvaluated bool // Whether the watch conditions should be evaluated - expectRemoved bool // Whether podUID should be present in the watch conditions map - expectWatchConditions map[string]versionedWatchCondition // The expected watch conditions for the podUID (only key & version checked) - }{{ - name: "no watch conditions", - podUID: "running", - }, { - name: "running pod with conditions", - podUID: "running", - watchConditions: map[string]WatchCondition{ - "completing": alwaysComplete, - "watching": neverComplete, - "updating": updatingCond, - }, - expectEvaluated: true, - expectWatchConditions: map[string]versionedWatchCondition{ - "watching": {version: 0}, - "updating": {version: 1}, - }, - }, { - name: "conditions with incremented versions", - podUID: "running", - incrementInitialVersion: true, - watchConditions: map[string]WatchCondition{ - "completing": alwaysComplete, - "watching": neverComplete, - "updating": updatingCond, - }, - expectEvaluated: true, - expectWatchConditions: map[string]versionedWatchCondition{ - "watching": {version: 1}, - "updating": {version: 2}, - }, - }, { - name: "completed watch condition with older version", - podUID: "running", - incrementInitialVersion: true, - watchConditions: map[string]WatchCondition{ - "resetting": resettingCond, - }, - expectEvaluated: true, - expectWatchConditions: map[string]versionedWatchCondition{ - "resetting": {version: 0}, - }, - }, { - name: "non-existent pod", - podUID: "non-existent", - watchConditions: map[string]WatchCondition{ - "watching": neverComplete, - }, - expectEvaluated: false, - expectRemoved: true, - }, { - name: "terminated pod", - podUID: "terminated", - watchConditions: map[string]WatchCondition{ - "watching": neverComplete, - }, - expectEvaluated: false, - expectRemoved: true, - }, { - name: "reinspecting pod", - podUID: "reinspect", - watchConditions: map[string]WatchCondition{ - "watching": neverComplete, - }, - expectEvaluated: true, - expectWatchConditions: map[string]versionedWatchCondition{ - "watching": {version: 0}, - }, - }, { - name: "single container conditions", - podUID: "running", - watchConditions: map[string]WatchCondition{ - "completing": makeContainerCond("c", true), - "watching": makeContainerCond("c", false), - }, - expectEvaluated: true, - expectWatchConditions: map[string]versionedWatchCondition{ - "watching": {version: 0}, - }, - }, { - name: "multi-container conditions", - podUID: "running-2", - watchConditions: map[string]WatchCondition{ - "completing:exited": makeContainerCond("c-exited", true), - "watching:exited": makeContainerCond("c-exited", false), - "completing:running": makeContainerCond("c-running", true), - "watching:running": makeContainerCond("c-running", false), - "completing:dne": makeContainerCond("c-dne", true), - "watching:dne": makeContainerCond("c-dne", false), - }, - expectEvaluated: true, - expectWatchConditions: map[string]versionedWatchCondition{ - "watching:running": {version: 0}, - }, - }} - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - runtimeMock := containertest.NewMockRuntime(t) - pleg = newTestGenericPLEGWithRuntimeMock(runtimeMock) - - // Mock pod statuses - for _, pod := range initialPods { - podStatus := &kubecontainer.PodStatus{ - ID: pod.ID, - Name: pod.Name, - Namespace: pod.Namespace, - } - for _, c := range pod.Containers { - podStatus.ContainerStatuses = append(podStatus.ContainerStatuses, &kubecontainer.Status{ - ID: c.ID, - Name: c.Name, - State: c.State, - }) - } - runtimeMock.EXPECT(). - GetPodStatus(mock.Anything, pod.ID, pod.Name, pod.Namespace). - Return(podStatus, nil).Maybe() - } - - // Setup initial pod records. - runtimeMock.EXPECT().GetPods(mock.Anything, true).Return(initialPods, nil).Once() - pleg.Relist() - pleg.podsToReinspect["reinspect"] = nil - - // Remove "terminated" pod. - runtimeMock.EXPECT().GetPods(mock.Anything, true).Return(pods, nil).Once() - - var evaluatedConditions []string - for key, condition := range test.watchConditions { - wrappedCondition := func(status *kubecontainer.PodStatus) bool { - defer func() { - if r := recover(); r != nil { - require.Fail(t, "condition error", r) - } - }() - assert.Equal(t, test.podUID, status.ID, "podUID") - if !test.expectEvaluated { - assert.Fail(t, "conditions should not be evaluated") - } else { - evaluatedConditions = append(evaluatedConditions, key) - } - return condition(status) - } - pleg.SetPodWatchCondition(test.podUID, key, wrappedCondition) - if test.incrementInitialVersion { - // Set the watch condition a second time to increment the version. - pleg.SetPodWatchCondition(test.podUID, key, wrappedCondition) - } - } - pleg.Relist() - - if test.expectEvaluated { - assert.Len(t, evaluatedConditions, len(test.watchConditions), "all conditions should be evaluated") - } - - if test.expectRemoved { - assert.NotContains(t, pleg.watchConditions, test.podUID, "Pod should be removed from watch conditions") - } else { - actualConditions := pleg.watchConditions[test.podUID] - assert.Len(t, actualConditions, len(test.expectWatchConditions), "expected number of conditions") - for key, expected := range test.expectWatchConditions { - if !assert.Contains(t, actualConditions, key) { - continue - } - actual := actualConditions[key] - assert.Equal(t, key, actual.key) - assert.Equal(t, expected.version, actual.version) - } - } - - }) +func requireUnblocked[T any](t *testing.T, ch <-chan T) (received T) { + t.Helper() + synctest.Wait() + select { + case r := <-ch: + return r + default: + t.Fatal("Receive should not have been blocked") + return } } diff --git a/pkg/kubelet/pleg/pleg.go b/pkg/kubelet/pleg/pleg.go index 08625aba9cc36..fd13867c163f3 100644 --- a/pkg/kubelet/pleg/pleg.go +++ b/pkg/kubelet/pleg/pleg.go @@ -20,7 +20,6 @@ import ( "time" "k8s.io/apimachinery/pkg/types" - kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" ) // PodLifeCycleEventType define the event type of pod life cycle events. @@ -47,8 +46,6 @@ const ( PodSync PodLifeCycleEventType = "PodSync" // ContainerChanged - event type when the new state of container is unknown. ContainerChanged PodLifeCycleEventType = "ContainerChanged" - // ConditionMet - event type triggered when any number of watch conditions are met. - ConditionMet PodLifeCycleEventType = "ConditionMet" ) // PodLifecycleEvent is an event that reflects the change of the pod state. @@ -68,10 +65,10 @@ type PodLifecycleEventGenerator interface { Start() Watch() chan *PodLifecycleEvent Healthy() (bool, error) - // SetPodWatchCondition flags the pod for reinspection on every Relist iteration until the watch - // condition is met. The condition is keyed so it can be updated before the condition - // is met. - SetPodWatchCondition(podUID types.UID, conditionKey string, condition WatchCondition) + // RequestReinspect flags the pod for reinspection on the next Relist iteration. + RequestReinspect(podUID types.UID) + // RequestRelist queues up the pod for an on-demand relist. + RequestRelist(podUID types.UID) } // podLifecycleEventGeneratorHandler contains functions that are useful for different PLEGs @@ -82,19 +79,3 @@ type podLifecycleEventGeneratorHandler interface { Update(relistDuration *RelistDuration) Relist() } - -// WatchCondition takes the latest PodStatus, and returns whether the condition is met. -type WatchCondition = func(*kubecontainer.PodStatus) bool - -// RunningContainerWatchCondition wraps a condition on the container status to make a pod -// WatchCondition. If the container is no longer running, the condition is implicitly cleared. -func RunningContainerWatchCondition(containerName string, condition func(*kubecontainer.Status) bool) WatchCondition { - return func(podStatus *kubecontainer.PodStatus) bool { - status := podStatus.FindContainerStatusByName(containerName) - if status == nil || status.State != kubecontainer.ContainerStateRunning { - // Container isn't running. Consider the condition "completed" so it is cleared. - return true - } - return condition(status) - } -} diff --git a/pkg/kubelet/pluginmanager/operationexecutor/operation_executor_test.go b/pkg/kubelet/pluginmanager/operationexecutor/operation_executor_test.go index f9e62c66ddc4c..24b6550d94501 100644 --- a/pkg/kubelet/pluginmanager/operationexecutor/operation_executor_test.go +++ b/pkg/kubelet/pluginmanager/operationexecutor/operation_executor_test.go @@ -34,6 +34,10 @@ import ( const ( numPluginsToRegister = 2 numPluginsToUnregister = 2 + // maxConcurrentOperations is the buffer size for the ch channel. + // Must be >= max number of goroutines any test spawns to prevent + // goroutine leaks when timeout fires before all sends complete. + maxConcurrentOperations = max(numPluginsToRegister, numPluginsToUnregister) ) var _ OperationGenerator = &fakeOperationGenerator{} @@ -178,13 +182,16 @@ loop: func setup(t *testing.T) (context.Context, chan interface{}, chan interface{}, OperationExecutor) { tCtx := ktesting.Init(t) - ch, quit := make(chan interface{}), make(chan interface{}) + ch, quit := make(chan interface{}, maxConcurrentOperations), make(chan interface{}) return tCtx, ch, quit, NewOperationExecutor(newFakeOperationGenerator(ch, quit)) } // This function starts by writing to ch and blocks on the quit channel // until it is closed by the currently running test func startOperationAndBlock(ch chan<- interface{}, quit <-chan interface{}) { - ch <- nil - <-quit + select { + case ch <- nil: + <-quit + case <-quit: + } } diff --git a/pkg/kubelet/pluginmanager/operationexecutor/operation_generator.go b/pkg/kubelet/pluginmanager/operationexecutor/operation_generator.go index 745ca23154712..725197323dd87 100644 --- a/pkg/kubelet/pluginmanager/operationexecutor/operation_generator.go +++ b/pkg/kubelet/pluginmanager/operationexecutor/operation_generator.go @@ -130,11 +130,14 @@ func (og *operationGenerator) GenerateRegisterPluginFunc( logger.Error(err, "RegisterPlugin error -- failed to add plugin", "path", socketPath) } if err := handler.RegisterPlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions, nil); err != nil { + actualStateOfWorldUpdater.RemovePlugin(socketPath) return og.notifyPlugin(ctx, client, false, fmt.Sprintf("RegisterPlugin error -- plugin registration failed with err: %v", err)) } // Notify is called after register to guarantee that even if notify throws an error Register will always be called after validate if err := og.notifyPlugin(ctx, client, true, ""); err != nil { + actualStateOfWorldUpdater.RemovePlugin(socketPath) + handler.DeRegisterPlugin(infoResp.Name, infoResp.Endpoint) return fmt.Errorf("RegisterPlugin error -- failed to send registration status at socket %s, err: %v", socketPath, err) } return nil diff --git a/pkg/kubelet/pluginmanager/plugin_manager.go b/pkg/kubelet/pluginmanager/plugin_manager.go index 754c30ba31599..060664ee3de08 100644 --- a/pkg/kubelet/pluginmanager/plugin_manager.go +++ b/pkg/kubelet/pluginmanager/plugin_manager.go @@ -107,7 +107,7 @@ type pluginManager struct { var _ PluginManager = &pluginManager{} func (pm *pluginManager) Run(ctx context.Context, sourcesReady config.SourcesReady, stopCh <-chan struct{}) { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithContext(ctx) logger := klog.FromContext(ctx) diff --git a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others.go b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others.go index 2ecb570238ed2..7edcea12f6b9d 100644 --- a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others.go +++ b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others_test.go b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others_test.go index 6d743b2309eb5..66c11e6d70c7d 100644 --- a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others_test.go +++ b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_others_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows.go b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows.go index 476ae4fc0c71d..ae9edc2e03891 100644 --- a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows.go +++ b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows_test.go b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows_test.go index ea4586eb1cdcd..f9b5935001f71 100644 --- a/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows_test.go +++ b/pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/pod_workers.go b/pkg/kubelet/pod_workers.go index d2539a8bd70b4..0f3034e26225e 100644 --- a/pkg/kubelet/pod_workers.go +++ b/pkg/kubelet/pod_workers.go @@ -19,6 +19,7 @@ package kubelet import ( "context" "encoding/json" + "errors" "fmt" "strings" "sync" @@ -164,7 +165,7 @@ type PodWorkers interface { // UpdatePod() calls will be ignored for that pod until it has been forgotten // due to significant time passing. A pod that is terminated will never be // restarted. - UpdatePod(options UpdatePodOptions) + UpdatePod(ctx context.Context, options UpdatePodOptions) // SyncKnownPods removes workers for pods that are not in the desiredPods set // and have been terminated for a significant period of time. Once this method // has been called once, the workers are assumed to be fully initialized and @@ -172,7 +173,7 @@ type PodWorkers interface { // true. It returns a map describing the state of each known pod worker. It // is the responsibility of the caller to re-add any desired pods that are not // returned as knownPods. - SyncKnownPods(desiredPods []*v1.Pod) (knownPods map[types.UID]PodWorkerSync) + SyncKnownPods(logger klog.Logger, desiredPods []*v1.Pod) (knownPods map[types.UID]PodWorkerSync) // IsPodKnownTerminated returns true once SyncTerminatingPod completes // successfully - the provided pod UID it is known by the pod @@ -266,7 +267,7 @@ type podSyncer interface { // pod has reached a terminal state and the presence of the error indicates succeeded or failed. // If an error is returned, the sync was not successful and should be rerun in the future. This // is a long running method and should exit early with context.Canceled if the context is canceled. - SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) + SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) // SyncTerminatingPod attempts to ensure the pod's containers are no longer running and to collect // any final status. This method is repeatedly invoked with diminishing grace periods until it exits // without error. Once this method exits with no error other components are allowed to tear down @@ -284,7 +285,7 @@ type podSyncer interface { SyncTerminatedPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus) error } -type syncPodFnType func(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) +type syncPodFnType func(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) type syncTerminatingPodFnType func(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, gracePeriod *int64, podStatusFn func(*v1.PodStatus)) error type syncTerminatingRuntimePodFnType func(ctx context.Context, runningPod *kubecontainer.Pod) error type syncTerminatedPodFnType func(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus) error @@ -308,7 +309,7 @@ func newPodSyncerFuncs(s podSyncer) podSyncerFuncs { var _ podSyncer = podSyncerFuncs{} -func (f podSyncerFuncs) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { +func (f podSyncerFuncs) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { return f.syncPod(ctx, updateType, pod, mirrorPod, podStatus) } func (f podSyncerFuncs) SyncTerminatingPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, gracePeriod *int64, podStatusFn func(*v1.PodStatus)) error { @@ -335,11 +336,6 @@ const ( // podSyncStatus tracks per-pod transitions through the three phases of pod // worker sync (setup, terminating, terminated). type podSyncStatus struct { - // ctx is the context that is associated with the current pod sync. - // TODO: remove this from the struct by having the context initialized - // in startPodSync, the cancelFn used by UpdatePod, and cancellation of - // a parent context for tearing down workers (if needed) on shutdown - ctx context.Context // cancelFn if set is expected to cancel the current podSyncer operation. cancelFn context.CancelFunc @@ -606,7 +602,7 @@ type podWorkers struct { resyncInterval time.Duration // podCache stores kubecontainer.PodStatus for all pods. - podCache kubecontainer.Cache + podCache kubecontainer.ROCache // allocationManager is used to allocate resources for pods allocationManager allocation.Manager @@ -620,7 +616,7 @@ func newPodWorkers( recorder record.EventRecorder, workQueue queue.WorkQueue, resyncInterval, backOffPeriod time.Duration, - podCache kubecontainer.Cache, + podCache kubecontainer.ROCache, allocationManager allocation.Manager, ) PodWorkers { return &podWorkers{ @@ -752,7 +748,8 @@ func isPodStatusCacheTerminal(status *kubecontainer.PodStatus) bool { // UpdatePod carries a configuration change or termination state to a pod. A pod is either runnable, // terminating, or terminated, and will transition to terminating if: deleted on the apiserver, // discovered to have a terminal phase (Succeeded or Failed), or evicted by the kubelet. -func (p *podWorkers) UpdatePod(options UpdatePodOptions) { +func (p *podWorkers) UpdatePod(ctx context.Context, options UpdatePodOptions) { + logger := klog.FromContext(ctx) // Handle when the pod is an orphan (no config) and we only have runtime status by running only // the terminating part of the lifecycle. A running pod contains only a minimal set of information // about the pod @@ -763,7 +760,7 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { if options.Pod == nil { // the sythetic pod created here is used only as a placeholder and not tracked if options.UpdateType != kubetypes.SyncPodKill { - klog.InfoS("Pod update is ignored, runtime pods can only be killed", "pod", klog.KRef(runningPod.Namespace, runningPod.Name), "podUID", runningPod.ID, "updateType", options.UpdateType) + logger.Info("Pod update is ignored, runtime pods can only be killed", "pod", klog.KRef(runningPod.Namespace, runningPod.Name), "podUID", runningPod.ID, "updateType", options.UpdateType) return } uid, ns, name = runningPod.ID, runningPod.Namespace, runningPod.Name @@ -771,12 +768,13 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { } else { options.RunningPod = nil uid, ns, name = options.Pod.UID, options.Pod.Namespace, options.Pod.Name - klog.InfoS("Pod update included RunningPod which is only valid when Pod is not specified", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + logger.Info("Pod update included RunningPod which is only valid when Pod is not specified", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) } } else { uid, ns, name = options.Pod.UID, options.Pod.Namespace, options.Pod.Name } + updateLogger := logger.WithValues("pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) p.podLock.Lock() defer p.podLock.Unlock() @@ -785,7 +783,7 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { now := p.clock.Now() status, ok := p.podSyncStatuses[uid] if !ok { - klog.V(4).InfoS("Pod is being synced for the first time", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is being synced for the first time") firstTime = true status = &podSyncStatus{ syncedAt: now, @@ -850,14 +848,14 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { if !firstTime && status.IsTerminationRequested() { if options.UpdateType == kubetypes.SyncPodCreate { status.restartRequested = true - klog.V(4).InfoS("Pod is terminating but has been requested to restart with same UID, will be reconciled later", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is terminating but has been requested to restart with same UID, will be reconciled later") return } } // once a pod is terminated by UID, it cannot reenter the pod worker (until the UID is purged by housekeeping) if status.IsFinished() { - klog.V(4).InfoS("Pod is finished processing, no further updates", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is finished processing, no further updates") return } @@ -866,25 +864,25 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { if !status.IsTerminationRequested() { switch { case isRuntimePod: - klog.V(4).InfoS("Pod is orphaned and must be torn down", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is orphaned and must be torn down") status.deleted = true status.terminatingAt = now becameTerminating = true case pod.DeletionTimestamp != nil: - klog.V(4).InfoS("Pod is marked for graceful deletion, begin teardown", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is marked for graceful deletion, begin teardown") status.deleted = true status.terminatingAt = now becameTerminating = true case pod.Status.Phase == v1.PodFailed, pod.Status.Phase == v1.PodSucceeded: - klog.V(4).InfoS("Pod is in a terminal phase (success/failed), begin teardown", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is in a terminal phase (success/failed), begin teardown") status.terminatingAt = now becameTerminating = true case options.UpdateType == kubetypes.SyncPodKill: if options.KillPodOptions != nil && options.KillPodOptions.Evict { - klog.V(4).InfoS("Pod is being evicted by the kubelet, begin teardown", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is being evicted by the kubelet, begin teardown") status.evicted = true } else { - klog.V(4).InfoS("Pod is being removed by the kubelet, begin teardown", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(4).Info("Pod is being removed by the kubelet, begin teardown") } status.terminatingAt = now becameTerminating = true @@ -899,7 +897,7 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { // due to housekeeping seeing an older cached version of the runtime pod simply ignore it until // after the pod worker completes. if isRuntimePod { - klog.V(3).InfoS("Pod is waiting for termination, ignoring runtime-only kill until after pod worker is fully terminated", "pod", klog.KRef(ns, name), "podUID", uid, "updateType", options.UpdateType) + updateLogger.V(3).Info("Pod is waiting for termination, ignoring runtime-only kill until after pod worker is fully terminated") return } @@ -966,8 +964,8 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { // TODO: this should be a wait.Until with backoff to handle panics, and // accept a context for shutdown defer runtime.HandleCrash() - defer klog.V(3).InfoS("Pod worker has stopped", "podUID", uid) - p.podWorkerLoop(uid, outCh) + defer logger.V(3).Info("Pod worker has stopped", "podUID", uid) + p.podWorkerLoop(ctx, uid, outCh) }() } @@ -983,14 +981,14 @@ func (p *podWorkers) UpdatePod(options UpdatePodOptions) { status.pendingUpdate.Pod, _ = p.allocationManager.UpdatePodFromAllocation(options.Pod) } status.working = true - klog.V(4).InfoS("Notifying pod of pending update", "pod", klog.KRef(ns, name), "podUID", uid, "workType", status.WorkType()) + updateLogger.V(4).Info("Notifying pod of pending update", "workType", status.WorkType()) select { case podUpdates <- struct{}{}: default: } if (becameTerminating || wasGracePeriodShortened) && status.cancelFn != nil { - klog.V(3).InfoS("Cancelling current pod sync", "pod", klog.KRef(ns, name), "podUID", uid, "workType", status.WorkType()) + updateLogger.V(3).Info("Cancelling current pod sync", "workType", status.WorkType()) status.cancelFn() return } @@ -1035,7 +1033,7 @@ func calculateEffectiveGracePeriod(status *podSyncStatus, pod *v1.Pod, options * // it requeues the pod and returns false. If the pod will never be able to start // because data is missing, or the pod was terminated before start, canEverStart // is false. This method can only be called while holding the pod lock. -func (p *podWorkers) allowPodStart(pod *v1.Pod) (canStart bool, canEverStart bool) { +func (p *podWorkers) allowPodStart(logger klog.Logger, pod *v1.Pod) (canStart bool, canEverStart bool) { if !kubetypes.IsStaticPod(pod) { // TODO: Do we want to allow non-static pods with the same full name? // Note that it may disable the force deletion of pods. @@ -1043,7 +1041,7 @@ func (p *podWorkers) allowPodStart(pod *v1.Pod) (canStart bool, canEverStart boo } status, ok := p.podSyncStatuses[pod.UID] if !ok { - klog.ErrorS(nil, "Pod sync status does not exist, the worker should not be running", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.Error(nil, "Pod sync status does not exist, the worker should not be running", "pod", klog.KObj(pod), "podUID", pod.UID) return false, false } if status.IsTerminationRequested() { @@ -1094,14 +1092,14 @@ func (p *podWorkers) allowStaticPodStart(fullname string, uid types.UID) bool { // cleanupUnstartedPod is invoked if a pod that has never been started receives a termination // signal before it can be started. This method must be called holding the pod lock. -func (p *podWorkers) cleanupUnstartedPod(pod *v1.Pod, status *podSyncStatus) { +func (p *podWorkers) cleanupUnstartedPod(logger klog.Logger, pod *v1.Pod, status *podSyncStatus) { p.cleanupPodUpdates(pod.UID) if status.terminatingAt.IsZero() { - klog.V(4).InfoS("Pod worker is complete but did not have terminatingAt set, likely programmer error", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod worker is complete but did not have terminatingAt set, likely programmer error", "pod", klog.KObj(pod), "podUID", pod.UID) } if !status.terminatedAt.IsZero() { - klog.V(4).InfoS("Pod worker is complete and had terminatedAt set, likely programmer error", "pod", klog.KObj(pod), "podUID", pod.UID) + logger.V(4).Info("Pod worker is complete and had terminatedAt set, likely programmer error", "pod", klog.KObj(pod), "podUID", pod.UID) } status.finished = true status.working = false @@ -1121,7 +1119,7 @@ func (p *podWorkers) cleanupUnstartedPod(pod *v1.Pod, status *podSyncStatus) { // This method should ensure that either status.pendingUpdate is cleared and merged into status.activeUpdate, // or when a pod cannot be started status.pendingUpdate remains the same. Pods that have not been started // should never have an activeUpdate because that is exposed to downstream components on started pods. -func (p *podWorkers) startPodSync(podUID types.UID) (ctx context.Context, update podWork, canStart, canEverStart, ok bool) { +func (p *podWorkers) startPodSync(parentCtx context.Context, podUID types.UID) (ctx context.Context, update podWork, canStart, canEverStart, ok bool) { p.podLock.Lock() defer p.podLock.Unlock() @@ -1129,18 +1127,18 @@ func (p *podWorkers) startPodSync(podUID types.UID) (ctx context.Context, update status, ok := p.podSyncStatuses[podUID] if !ok { // pod status has disappeared, the worker should exit - klog.V(4).InfoS("Pod worker no longer has status, worker should exit", "podUID", podUID) return nil, update, false, false, false } + logger := klog.FromContext(parentCtx) if !status.working { // working is used by unit tests to observe whether a worker is currently acting on this pod - klog.V(4).InfoS("Pod should be marked as working by the pod worker, programmer error", "podUID", podUID) + logger.V(4).Info("Pod should be marked as working by the pod worker, programmer error", "podUID", podUID) } if status.pendingUpdate == nil { // no update available, this means we were queued without work being added or there is a // race condition, both of which are unexpected status.working = false - klog.V(4).InfoS("Pod worker received no pending work, programmer error?", "podUID", podUID) + logger.V(4).Info("Pod worker received no pending work, programmer error?", "podUID", podUID) return nil, update, false, false, false } @@ -1154,11 +1152,7 @@ func (p *podWorkers) startPodSync(podUID types.UID) (ctx context.Context, update default: } - // initialize a context for the worker if one does not exist - if status.ctx == nil || status.ctx.Err() == context.Canceled { - status.ctx, status.cancelFn = context.WithCancel(context.Background()) - } - ctx = status.ctx + ctx, status.cancelFn = context.WithCancel(parentCtx) // if we are already started, make our state visible to downstream components if status.IsStarted() { @@ -1178,26 +1172,26 @@ func (p *podWorkers) startPodSync(podUID types.UID) (ctx context.Context, update // asked to start such a pod, but guard here just in case an accident occurs. if update.Options.Pod == nil { status.mergeLastUpdate(update.Options) - klog.V(4).InfoS("Running pod cannot start ever, programmer error", "pod", klog.KObj(update.Options.Pod), "podUID", podUID, "updateType", update.WorkType) + logger.V(4).Info("Running pod cannot start ever, programmer error", "pod", klog.KObj(update.Options.Pod), "podUID", podUID, "updateType", update.WorkType) return ctx, update, false, false, true } // verify we can start - canStart, canEverStart = p.allowPodStart(update.Options.Pod) + canStart, canEverStart = p.allowPodStart(logger, update.Options.Pod) switch { case !canEverStart: - p.cleanupUnstartedPod(update.Options.Pod, status) + p.cleanupUnstartedPod(logger, update.Options.Pod, status) status.working = false if start := update.Options.StartTime; !start.IsZero() { metrics.PodWorkerDuration.WithLabelValues("terminated").Observe(metrics.SinceInSeconds(start)) } - klog.V(4).InfoS("Pod cannot start ever", "pod", klog.KObj(update.Options.Pod), "podUID", podUID, "updateType", update.WorkType) + logger.V(4).Info("Pod cannot start ever", "pod", klog.KObj(update.Options.Pod), "podUID", podUID, "updateType", update.WorkType) return ctx, update, canStart, canEverStart, true case !canStart: // this is the only path we don't start the pod, so we need to put the change back in pendingUpdate status.pendingUpdate = &update.Options status.working = false - klog.V(4).InfoS("Pod cannot start yet", "pod", klog.KObj(update.Options.Pod), "podUID", podUID) + logger.V(4).Info("Pod cannot start yet", "pod", klog.KObj(update.Options.Pod), "podUID", podUID) return ctx, update, canStart, canEverStart, true } @@ -1234,14 +1228,15 @@ func podUIDAndRefForUpdate(update UpdatePodOptions) (types.UID, klog.ObjectRef) // to trigger new UpdatePod calls. SyncKnownPods will only retry pods that are no longer known to the // caller. When a pod transitions working->terminating or terminating->terminated, the next update is // queued immediately and no kubelet action is required. -func (p *podWorkers) podWorkerLoop(podUID types.UID, podUpdates <-chan struct{}) { +func (p *podWorkers) podWorkerLoop(parentCtx context.Context, podUID types.UID, podUpdates <-chan struct{}) { var lastSyncTime time.Time for range podUpdates { - ctx, update, canStart, canEverStart, ok := p.startPodSync(podUID) + ctx, update, canStart, canEverStart, ok := p.startPodSync(parentCtx, podUID) // If we had no update waiting, it means someone initialized the channel without filling out pendingUpdate. if !ok { continue } + logger := klog.FromContext(ctx) // If the pod was terminated prior to the pod being allowed to start, we exit the loop. if !canEverStart { return @@ -1253,7 +1248,7 @@ func (p *podWorkers) podWorkerLoop(podUID types.UID, podUpdates <-chan struct{}) podUID, podRef := podUIDAndRefForUpdate(update.Options) - klog.V(4).InfoS("Processing pod event", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) + logger.V(4).Info("Processing pod event", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) var isTerminal bool err := func() error { // The worker is responsible for ensuring the sync method sees the appropriate @@ -1287,6 +1282,7 @@ func (p *podWorkers) podWorkerLoop(podUID types.UID, podUpdates <-chan struct{}) } // Take the appropriate action (illegal phases are prevented by UpdatePod) + var postSync func() switch { case update.WorkType == TerminatedPod: err = p.podSyncer.SyncTerminatedPod(ctx, update.Options.Pod, status) @@ -1296,7 +1292,7 @@ func (p *podWorkers) podWorkerLoop(podUID types.UID, podUpdates <-chan struct{}) if opt := update.Options.KillPodOptions; opt != nil { gracePeriod = opt.PodTerminationGracePeriodSecondsOverride } - podStatusFn := p.acknowledgeTerminating(podUID) + podStatusFn := p.acknowledgeTerminating(logger, podUID) // if we only have a running pod, terminate it directly if update.Options.RunningPod != nil { @@ -1306,66 +1302,70 @@ func (p *podWorkers) podWorkerLoop(podUID types.UID, podUpdates <-chan struct{}) } default: - isTerminal, err = p.podSyncer.SyncPod(ctx, update.Options.UpdateType, update.Options.Pod, update.Options.MirrorPod, status) + isTerminal, postSync, err = p.podSyncer.SyncPod(ctx, update.Options.UpdateType, update.Options.Pod, update.Options.MirrorPod, status) } lastSyncTime = p.clock.Now() + if postSync != nil { + postSync() + } + return err }() var phaseTransition bool switch { - case err == context.Canceled: + case errors.Is(err, context.Canceled): // when the context is cancelled we expect an update to already be queued - klog.V(2).InfoS("Sync exited with context cancellation error", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) + logger.V(2).Info("Sync exited with context cancellation error", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) case err != nil: // we will queue a retry - klog.ErrorS(err, "Error syncing pod, skipping", "pod", podRef, "podUID", podUID) + logger.Error(err, "Error syncing pod, skipping", "pod", podRef, "podUID", podUID) case update.WorkType == TerminatedPod: // we can shut down the worker - p.completeTerminated(podUID) + p.completeTerminated(logger, podUID) if start := update.Options.StartTime; !start.IsZero() { metrics.PodWorkerDuration.WithLabelValues("terminated").Observe(metrics.SinceInSeconds(start)) } - klog.V(4).InfoS("Processing pod event done", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) + logger.V(4).Info("Processing pod event done", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) return case update.WorkType == TerminatingPod: // pods that don't exist in config don't need to be terminated, other loops will clean them up if update.Options.RunningPod != nil { - p.completeTerminatingRuntimePod(podUID) + p.completeTerminatingRuntimePod(logger, podUID) if start := update.Options.StartTime; !start.IsZero() { metrics.PodWorkerDuration.WithLabelValues(update.Options.UpdateType.String()).Observe(metrics.SinceInSeconds(start)) } - klog.V(4).InfoS("Processing pod event done", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) + logger.V(4).Info("Processing pod event done", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) return } // otherwise we move to the terminating phase - p.completeTerminating(podUID) + p.completeTerminating(logger, podUID) phaseTransition = true case isTerminal: // if syncPod indicated we are now terminal, set the appropriate pod status to move to terminating - klog.V(4).InfoS("Pod is terminal", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) - p.completeSync(podUID) + logger.V(4).Info("Pod is terminal", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) + p.completeSync(logger, podUID) phaseTransition = true } // queue a retry if necessary, then put the next event in the channel if any - p.completeWork(podUID, phaseTransition, err) + p.completeWork(logger, podUID, phaseTransition, err) if start := update.Options.StartTime; !start.IsZero() { metrics.PodWorkerDuration.WithLabelValues(update.Options.UpdateType.String()).Observe(metrics.SinceInSeconds(start)) } - klog.V(4).InfoS("Processing pod event done", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) + logger.V(4).Info("Processing pod event done", "pod", podRef, "podUID", podUID, "updateType", update.WorkType) } } // acknowledgeTerminating sets the terminating flag on the pod status once the pod worker sees // the termination state so that other components know no new containers will be started in this // pod. It then returns the status function, if any, that applies to this pod. -func (p *podWorkers) acknowledgeTerminating(podUID types.UID) PodStatusFunc { +func (p *podWorkers) acknowledgeTerminating(logger klog.Logger, podUID types.UID) PodStatusFunc { p.podLock.Lock() defer p.podLock.Unlock() @@ -1375,7 +1375,7 @@ func (p *podWorkers) acknowledgeTerminating(podUID types.UID) PodStatusFunc { } if !status.terminatingAt.IsZero() && !status.startedTerminating { - klog.V(4).InfoS("Pod worker has observed request to terminate", "podUID", podUID) + logger.V(4).Info("Pod worker has observed request to terminate", "podUID", podUID) status.startedTerminating = true } @@ -1389,15 +1389,15 @@ func (p *podWorkers) acknowledgeTerminating(podUID types.UID) PodStatusFunc { // be terminated. This happens when the natural pod lifecycle completes - any pod which is not RestartAlways // exits. Unnatural completions, such as evictions, API driven deletion or phase transition, are handled by // UpdatePod. -func (p *podWorkers) completeSync(podUID types.UID) { +func (p *podWorkers) completeSync(logger klog.Logger, podUID types.UID) { p.podLock.Lock() defer p.podLock.Unlock() - klog.V(4).InfoS("Pod indicated lifecycle completed naturally and should now terminate", "podUID", podUID) + logger.V(4).Info("Pod indicated lifecycle completed naturally and should now terminate", "podUID", podUID) status, ok := p.podSyncStatuses[podUID] if !ok { - klog.V(4).InfoS("Pod had no status in completeSync, programmer error?", "podUID", podUID) + logger.V(4).Info("Pod had no status in completeSync, programmer error?", "podUID", podUID) return } @@ -1405,7 +1405,7 @@ func (p *podWorkers) completeSync(podUID types.UID) { if status.terminatingAt.IsZero() { status.terminatingAt = p.clock.Now() } else { - klog.V(4).InfoS("Pod worker attempted to set terminatingAt twice, likely programmer error", "podUID", podUID) + logger.V(4).Info("Pod worker attempted to set terminatingAt twice, likely programmer error", "podUID", podUID) } status.startedTerminating = true @@ -1418,11 +1418,11 @@ func (p *podWorkers) completeSync(podUID types.UID) { // no container is running, no container will be started in the future, and we are ready for // cleanup. This updates the termination state which prevents future syncs and will ensure // other kubelet loops know this pod is not running any containers. -func (p *podWorkers) completeTerminating(podUID types.UID) { +func (p *podWorkers) completeTerminating(logger klog.Logger, podUID types.UID) { p.podLock.Lock() defer p.podLock.Unlock() - klog.V(4).InfoS("Pod terminated all containers successfully", "podUID", podUID) + logger.V(4).Info("Pod terminated all containers successfully", "podUID", podUID) status, ok := p.podSyncStatuses[podUID] if !ok { @@ -1431,7 +1431,7 @@ func (p *podWorkers) completeTerminating(podUID types.UID) { // update the status of the pod if status.terminatingAt.IsZero() { - klog.V(4).InfoS("Pod worker was terminated but did not have terminatingAt set, likely programmer error", "podUID", podUID) + logger.V(4).Info("Pod worker was terminated but did not have terminatingAt set, likely programmer error", "podUID", podUID) } status.terminatedAt = p.clock.Now() for _, ch := range status.notifyPostTerminating { @@ -1449,11 +1449,11 @@ func (p *podWorkers) completeTerminating(podUID types.UID) { // which means an orphaned pod (no config) is terminated and we can exit. Since orphaned // pods have no API representation, we want to exit the loop at this point and ensure no // status is present afterwards - the running pod is truly terminated when this is invoked. -func (p *podWorkers) completeTerminatingRuntimePod(podUID types.UID) { +func (p *podWorkers) completeTerminatingRuntimePod(logger klog.Logger, podUID types.UID) { p.podLock.Lock() defer p.podLock.Unlock() - klog.V(4).InfoS("Pod terminated all orphaned containers successfully and worker can now stop", "podUID", podUID) + logger.V(4).Info("Pod terminated all orphaned containers successfully and worker can now stop", "podUID", podUID) p.cleanupPodUpdates(podUID) @@ -1462,7 +1462,7 @@ func (p *podWorkers) completeTerminatingRuntimePod(podUID types.UID) { return } if status.terminatingAt.IsZero() { - klog.V(4).InfoS("Pod worker was terminated but did not have terminatingAt set, likely programmer error", "podUID", podUID) + logger.V(4).Info("Pod worker was terminated but did not have terminatingAt set, likely programmer error", "podUID", podUID) } status.terminatedAt = p.clock.Now() status.finished = true @@ -1479,11 +1479,11 @@ func (p *podWorkers) completeTerminatingRuntimePod(podUID types.UID) { // completeTerminated is invoked after syncTerminatedPod completes successfully and means we // can stop the pod worker. The pod is finalized at this point. -func (p *podWorkers) completeTerminated(podUID types.UID) { +func (p *podWorkers) completeTerminated(logger klog.Logger, podUID types.UID) { p.podLock.Lock() defer p.podLock.Unlock() - klog.V(4).InfoS("Pod is complete and the worker can now stop", "podUID", podUID) + logger.V(4).Info("Pod is complete and the worker can now stop", "podUID", podUID) p.cleanupPodUpdates(podUID) @@ -1492,10 +1492,10 @@ func (p *podWorkers) completeTerminated(podUID types.UID) { return } if status.terminatingAt.IsZero() { - klog.V(4).InfoS("Pod worker is complete but did not have terminatingAt set, likely programmer error", "podUID", podUID) + logger.V(4).Info("Pod worker is complete but did not have terminatingAt set, likely programmer error", "podUID", podUID) } if status.terminatedAt.IsZero() { - klog.V(4).InfoS("Pod worker is complete but did not have terminatedAt set, likely programmer error", "podUID", podUID) + logger.V(4).Info("Pod worker is complete but did not have terminatedAt set, likely programmer error", "podUID", podUID) } status.finished = true status.working = false @@ -1507,7 +1507,7 @@ func (p *podWorkers) completeTerminated(podUID types.UID) { // completeWork requeues on error or the next sync interval and then immediately executes any pending // work. -func (p *podWorkers) completeWork(podUID types.UID, phaseTransition bool, syncErr error) { +func (p *podWorkers) completeWork(logger klog.Logger, podUID types.UID, phaseTransition bool, syncErr error) { // Requeue the last update if the last sync returned error. switch { case phaseTransition: @@ -1541,9 +1541,9 @@ func (p *podWorkers) completeWork(podUID types.UID, phaseTransition bool, syncEr if status.pendingUpdate != nil { select { case p.podUpdates[podUID] <- struct{}{}: - klog.V(4).InfoS("Requeuing pod due to pending update", "podUID", podUID) + logger.V(4).Info("Requeuing pod due to pending update", "podUID", podUID) default: - klog.V(4).InfoS("Pending update already queued", "podUID", podUID) + logger.V(4).Info("Pending update already queued", "podUID", podUID) } } else { status.working = false @@ -1559,7 +1559,7 @@ func (p *podWorkers) completeWork(podUID types.UID, phaseTransition bool, syncEr // of known workers that are not finished with a value of SyncPodTerminated, // SyncPodKill, or SyncPodSync depending on whether the pod is terminated, terminating, // or syncing. -func (p *podWorkers) SyncKnownPods(desiredPods []*v1.Pod) map[types.UID]PodWorkerSync { +func (p *podWorkers) SyncKnownPods(logger klog.Logger, desiredPods []*v1.Pod) map[types.UID]PodWorkerSync { workers := make(map[types.UID]PodWorkerSync) known := make(map[types.UID]struct{}) for _, pod := range desiredPods { @@ -1588,7 +1588,7 @@ func (p *podWorkers) SyncKnownPods(desiredPods []*v1.Pod) map[types.UID]PodWorke _, knownPod := known[uid] orphan := !knownPod if status.restartRequested || orphan { - if p.removeTerminatedWorker(uid, status, orphan) { + if p.removeTerminatedWorker(logger, uid, status, orphan) { // no worker running, we won't return it continue } @@ -1622,11 +1622,11 @@ func (p *podWorkers) SyncKnownPods(desiredPods []*v1.Pod) map[types.UID]PodWorke // terminated pods to prevent accidentally restarting a terminal pod, which is // proportional to the number of pods described in the pod config. The method // returns true if the worker was completely removed. -func (p *podWorkers) removeTerminatedWorker(uid types.UID, status *podSyncStatus, orphaned bool) bool { +func (p *podWorkers) removeTerminatedWorker(logger klog.Logger, uid types.UID, status *podSyncStatus, orphaned bool) bool { if !status.finished { // If the pod worker has not reached terminal state and the pod is still known, we wait. if !orphaned { - klog.V(4).InfoS("Pod worker has been requested for removal but is still not fully terminated", "podUID", uid) + logger.V(4).Info("Pod worker has been requested for removal but is still not fully terminated", "podUID", uid) return false } @@ -1640,7 +1640,7 @@ func (p *podWorkers) removeTerminatedWorker(uid types.UID, status *podSyncStatus case !status.IsStarted() && !status.observedRuntime: // The pod has not been started, which means we can safely clean up the pod - the // pod worker will shutdown as a result of this change without executing a sync. - klog.V(4).InfoS("Pod is orphaned and has not been started", "podUID", uid) + logger.V(4).Info("Pod is orphaned and has not been started", "podUID", uid) case !status.IsTerminationRequested(): // The pod has been started but termination has not been requested - set the appropriate // timestamp and notify the pod worker. Because the pod has been synced at least once, @@ -1652,22 +1652,22 @@ func (p *podWorkers) removeTerminatedWorker(uid types.UID, status *podSyncStatus status.gracePeriod = 1 } p.requeueLastPodUpdate(uid, status) - klog.V(4).InfoS("Pod is orphaned and still running, began terminating", "podUID", uid) + logger.V(4).Info("Pod is orphaned and still running, began terminating", "podUID", uid) return false default: // The pod is already moving towards termination, notify the pod worker. Because the pod // has been synced at least once, the value of status.activeUpdate will be the fallback for // the next sync. p.requeueLastPodUpdate(uid, status) - klog.V(4).InfoS("Pod is orphaned and still terminating, notified the pod worker", "podUID", uid) + logger.V(4).Info("Pod is orphaned and still terminating, notified the pod worker", "podUID", uid) return false } } if status.restartRequested { - klog.V(4).InfoS("Pod has been terminated but another pod with the same UID was created, remove history to allow restart", "podUID", uid) + logger.V(4).Info("Pod has been terminated but another pod with the same UID was created, remove history to allow restart", "podUID", uid) } else { - klog.V(4).InfoS("Pod has been terminated and is no longer known to the kubelet, remove all history", "podUID", uid) + logger.V(4).Info("Pod has been terminated and is no longer known to the kubelet, remove all history", "podUID", uid) } delete(p.podSyncStatuses, uid) p.cleanupPodUpdates(uid) @@ -1680,7 +1680,7 @@ func (p *podWorkers) removeTerminatedWorker(uid types.UID, status *podSyncStatus // killPodNow returns a KillPodFunc that can be used to kill a pod. // It is intended to be injected into other modules that need to kill a pod. -func killPodNow(podWorkers PodWorkers, recorder record.EventRecorder) eviction.KillPodFunc { +func killPodNow(ctx context.Context, podWorkers PodWorkers, recorder record.EventRecorder) eviction.KillPodFunc { return func(pod *v1.Pod, isEvicted bool, gracePeriodOverride *int64, statusFn func(*v1.PodStatus)) error { // determine the grace period to use when killing the pod gracePeriod := int64(0) @@ -1701,16 +1701,18 @@ func killPodNow(podWorkers PodWorkers, recorder record.EventRecorder) eviction.K // open a channel we block against until we get a result ch := make(chan struct{}, 1) - podWorkers.UpdatePod(UpdatePodOptions{ - Pod: pod, - UpdateType: kubetypes.SyncPodKill, - KillPodOptions: &KillPodOptions{ - CompletedCh: ch, - Evict: isEvicted, - PodStatusFunc: statusFn, - PodTerminationGracePeriodSecondsOverride: gracePeriodOverride, - }, - }) + podWorkers.UpdatePod( + ctx, + UpdatePodOptions{ + Pod: pod, + UpdateType: kubetypes.SyncPodKill, + KillPodOptions: &KillPodOptions{ + CompletedCh: ch, + Evict: isEvicted, + PodStatusFunc: statusFn, + PodTerminationGracePeriodSecondsOverride: gracePeriodOverride, + }, + }) // wait for either a response, or a timeout select { diff --git a/pkg/kubelet/pod_workers_test.go b/pkg/kubelet/pod_workers_test.go index 4dc418744a000..ac446fe133f08 100644 --- a/pkg/kubelet/pod_workers_test.go +++ b/pkg/kubelet/pod_workers_test.go @@ -34,12 +34,13 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/kubelet/allocation" - "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util/queue" + "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/clock" clocktesting "k8s.io/utils/clock/testing" ) @@ -49,7 +50,7 @@ import ( type fakePodWorkers struct { lock sync.Mutex syncPodFn syncPodFnType - cache kubecontainer.Cache + cache kubecontainer.ROCache t TestingInterface triggeredDeletion []types.UID @@ -66,7 +67,7 @@ type fakePodWorkers struct { terminatingStaticPods map[string]bool } -func (f *fakePodWorkers) UpdatePod(options UpdatePodOptions) { +func (f *fakePodWorkers) UpdatePod(ctx context.Context, options UpdatePodOptions) { f.lock.Lock() defer f.lock.Unlock() var uid types.UID @@ -86,7 +87,7 @@ func (f *fakePodWorkers) UpdatePod(options UpdatePodOptions) { case kubetypes.SyncPodKill: f.triggeredDeletion = append(f.triggeredDeletion, uid) default: - isTerminal, err := f.syncPodFn(context.Background(), options.UpdateType, options.Pod, options.MirrorPod, status) + isTerminal, _, err := f.syncPodFn(ctx, options.UpdateType, options.Pod, options.MirrorPod, status) if err != nil { f.t.Errorf("Unexpected error: %v", err) } @@ -96,7 +97,7 @@ func (f *fakePodWorkers) UpdatePod(options UpdatePodOptions) { } } -func (f *fakePodWorkers) SyncKnownPods(desiredPods []*v1.Pod) map[types.UID]PodWorkerSync { +func (f *fakePodWorkers) SyncKnownPods(_ klog.Logger, desiredPods []*v1.Pod) map[types.UID]PodWorkerSync { return map[types.UID]PodWorkerSync{} } @@ -261,11 +262,11 @@ type timeIncrementingWorkers struct { // UpdatePod increments the clock after UpdatePod is called, but before the workers // are invoked, and then drains all workers before returning. The provided functions // are invoked while holding the lock to prevent workers from receiving updates. -func (w *timeIncrementingWorkers) UpdatePod(options UpdatePodOptions, afterFns ...func()) { +func (w *timeIncrementingWorkers) UpdatePod(ctx context.Context, options UpdatePodOptions, afterFns ...func()) { func() { w.lock.Lock() defer w.lock.Unlock() - w.w.UpdatePod(options) + w.w.UpdatePod(ctx, options) w.w.clock.(*clocktesting.FakePassiveClock).SetTime(w.w.clock.Now().Add(time.Second)) for _, fn := range afterFns { fn() @@ -276,11 +277,11 @@ func (w *timeIncrementingWorkers) UpdatePod(options UpdatePodOptions, afterFns . // SyncKnownPods increments the clock after SyncKnownPods is called, but before the workers // are invoked, and then drains all workers before returning. -func (w *timeIncrementingWorkers) SyncKnownPods(desiredPods []*v1.Pod) (knownPods map[types.UID]PodWorkerSync) { +func (w *timeIncrementingWorkers) SyncKnownPods(logger klog.Logger, desiredPods []*v1.Pod) (knownPods map[types.UID]PodWorkerSync) { func() { w.lock.Lock() defer w.lock.Unlock() - knownPods = w.w.SyncKnownPods(desiredPods) + knownPods = w.w.SyncKnownPods(logger, desiredPods) w.w.clock.(*clocktesting.FakePassiveClock).SetTime(w.w.clock.Now().Add(time.Second)) }() w.drainUnpausedWorkers() @@ -369,8 +370,8 @@ func (w *timeIncrementingWorkers) tick() { // createTimeIncrementingPodWorkers will guarantee that each call to UpdatePod and each worker goroutine invocation advances the clock by one second, // although multiple workers will advance the clock in an unpredictable order. Use to observe // successive internal updates to each update pod state when only a single pod is being updated. -func createTimeIncrementingPodWorkers() (*timeIncrementingWorkers, map[types.UID][]syncPodRecord) { - nested, runtime, processed := createPodWorkers() +func createTimeIncrementingPodWorkers(logger klog.Logger) (*timeIncrementingWorkers, map[types.UID][]syncPodRecord) { + nested, runtime, processed := createPodWorkers(logger) w := &timeIncrementingWorkers{ w: nested, runtime: runtime, @@ -392,7 +393,11 @@ func createTimeIncrementingPodWorkers() (*timeIncrementingWorkers, map[types.UID return w, processed } -func createPodWorkers() (*podWorkers, *containertest.FakeRuntime, map[types.UID][]syncPodRecord) { +func createPodWorkers(logger klog.Logger) (*podWorkers, *containertest.FakeRuntime, map[types.UID][]syncPodRecord) { + return createPodWorkersWithLogger(logger) +} + +func createPodWorkersWithLogger(_ klog.Logger) (*podWorkers, *containertest.FakeRuntime, map[types.UID][]syncPodRecord) { lock := sync.Mutex{} processed := make(map[types.UID][]syncPodRecord) fakeRecorder := &record.FakeRecorder{} @@ -402,7 +407,7 @@ func createPodWorkers() (*podWorkers, *containertest.FakeRuntime, map[types.UID] clock := clocktesting.NewFakePassiveClock(time.Unix(1, 0)) w := newPodWorkers( &podSyncerFuncs{ - syncPod: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { + syncPod: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { func() { lock.Lock() defer lock.Unlock() @@ -412,7 +417,7 @@ func createPodWorkers() (*podWorkers, *containertest.FakeRuntime, map[types.UID] updateType: updateType, }) }() - return false, nil + return false, nil, nil }, syncTerminatingPod: func(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, gracePeriod *int64, podStatusFn func(*v1.PodStatus)) error { func() { @@ -455,7 +460,7 @@ func createPodWorkers() (*podWorkers, *containertest.FakeRuntime, map[types.UID] time.Second, time.Millisecond, fakeCache, - allocation.NewInMemoryManager(cm.NodeConfig{}, nil, nil, nil, nil, nil, nil, nil), + allocation.NewInMemoryManager(nil, nil, nil, nil, nil, nil), ) workers := w.(*podWorkers) workers.clock = clock @@ -524,12 +529,13 @@ func drainAllWorkers(podWorkers *podWorkers) { } func TestUpdatePodParallel(t *testing.T) { - podWorkers, _, processed := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) numPods := 20 for i := 0; i < numPods; i++ { for j := i; j < numPods; j++ { - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod(strconv.Itoa(j), "ns", strconv.Itoa(i), false), UpdateType: kubetypes.SyncPodCreate, }) @@ -558,8 +564,8 @@ func TestUpdatePodParallel(t *testing.T) { func TestUpdatePod(t *testing.T) { one := int64(1) - hasContext := func(status *podSyncStatus) *podSyncStatus { - status.ctx, status.cancelFn = context.Background(), func() {} + hasCancelFn := func(status *podSyncStatus) *podSyncStatus { + status.cancelFn = func() {} return status } withLabel := func(pod *v1.Pod, label, value string) *v1.Pod { @@ -581,11 +587,16 @@ func TestUpdatePod(t *testing.T) { t.Helper() // handle special non-comparable fields if status != nil { - if e, a := expected.ctx != nil, status.ctx != nil; e != a { - t.Errorf("expected context %t, has context %t", e, a) - } else { - expected.ctx, status.ctx = nil, nil + clearLogger := func(opts *UpdatePodOptions) { + if opts == nil { + return + } } + clearLogger(expected.pendingUpdate) + clearLogger(expected.activeUpdate) + clearLogger(status.pendingUpdate) + clearLogger(status.activeUpdate) + if e, a := expected.cancelFn != nil, status.cancelFn != nil; e != a { t.Errorf("expected cancelFn %t, has cancelFn %t", e, a) } else { @@ -600,7 +611,7 @@ func TestUpdatePod(t *testing.T) { name string update UpdatePodOptions runtimeStatus *kubecontainer.PodStatus - prepare func(t *testing.T, w *timeIncrementingWorkers) (afterUpdateFn func()) + prepare func(t *testing.T, tCtx context.Context, w *timeIncrementingWorkers) (afterUpdateFn func()) expect *podSyncStatus expectBeforeWorker *podSyncStatus @@ -612,7 +623,7 @@ func TestUpdatePod(t *testing.T) { UpdateType: kubetypes.SyncPodCreate, Pod: newNamedPod("1", "ns", "running-pod", false), }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "running-pod_ns", syncedAt: time.Unix(1, 0), startedAt: time.Unix(3, 0), @@ -627,19 +638,19 @@ func TestUpdatePod(t *testing.T) { UpdateType: kubetypes.SyncPodCreate, Pod: withLabel(newNamedPod("1", "ns", "running-pod", false), "updated", "value"), }, - prepare: func(t *testing.T, w *timeIncrementingWorkers) func() { - w.UpdatePod(UpdatePodOptions{ + prepare: func(t *testing.T, tCtx context.Context, w *timeIncrementingWorkers) func() { + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, Pod: newNamedPod("1", "ns", "running-pod", false), }) w.PauseWorkers("1") - w.UpdatePod(UpdatePodOptions{ + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, Pod: newNamedPod("1", "ns", "running-pod", false), }) return func() { w.ReleaseWorkersUnderLock("1") } }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "running-pod_ns", syncedAt: time.Unix(1, 0), startedAt: time.Unix(3, 0), @@ -663,7 +674,7 @@ func TestUpdatePod(t *testing.T) { Pod: newNamedPod("1", "ns", "running-pod", false), RunningPod: &kubecontainer.Pod{ID: "1", Name: "orphaned-pod", Namespace: "ns"}, }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "running-pod_ns", syncedAt: time.Unix(1, 0), startedAt: time.Unix(3, 0), @@ -678,14 +689,14 @@ func TestUpdatePod(t *testing.T) { UpdateType: kubetypes.SyncPodUpdate, Pod: withDeletionTimestamp(newNamedPod("1", "ns", "running-pod", false), time.Unix(1, 0), intp(15)), }, - prepare: func(t *testing.T, w *timeIncrementingWorkers) func() { - w.UpdatePod(UpdatePodOptions{ + prepare: func(t *testing.T, tCtx context.Context, w *timeIncrementingWorkers) func() { + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, Pod: newNamedPod("1", "ns", "running-pod", false), }) return nil }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "running-pod_ns", syncedAt: time.Unix(1, 0), startedAt: time.Unix(3, 0), @@ -709,14 +720,14 @@ func TestUpdatePod(t *testing.T) { Pod: newNamedPod("1", "ns", "running-pod", false), KillPodOptions: &KillPodOptions{Evict: true}, }, - prepare: func(t *testing.T, w *timeIncrementingWorkers) func() { - w.UpdatePod(UpdatePodOptions{ + prepare: func(t *testing.T, tCtx context.Context, w *timeIncrementingWorkers) func() { + w.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, Pod: newNamedPod("1", "ns", "running-pod", false), }) return nil }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "running-pod_ns", syncedAt: time.Unix(1, 0), startedAt: time.Unix(3, 0), @@ -742,7 +753,7 @@ func TestUpdatePod(t *testing.T) { UpdateType: kubetypes.SyncPodCreate, Pod: newPodWithPhase("1", "done-pod", v1.PodSucceeded), }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "done-pod_ns", syncedAt: time.Unix(1, 0), terminatingAt: time.Unix(1, 0), @@ -778,7 +789,7 @@ func TestUpdatePod(t *testing.T) { startedTerminating: true, working: true, }, - expect: hasContext(&podSyncStatus{ + expect: hasCancelFn(&podSyncStatus{ fullname: "done-pod_ns", syncedAt: time.Unix(1, 0), terminatingAt: time.Unix(1, 0), @@ -843,7 +854,8 @@ func TestUpdatePod(t *testing.T) { var fns []func() - podWorkers, _ := createTimeIncrementingPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _ := createTimeIncrementingPodWorkers(logger) if tc.expectBeforeWorker != nil { fns = append(fns, func() { @@ -852,7 +864,7 @@ func TestUpdatePod(t *testing.T) { } if tc.prepare != nil { - if fn := tc.prepare(t, podWorkers); fn != nil { + if fn := tc.prepare(t, tCtx, podWorkers); fn != nil { fns = append(fns, fn) } } @@ -871,7 +883,7 @@ func TestUpdatePod(t *testing.T) { podWorkers.runtime.Err = nil }) - podWorkers.UpdatePod(tc.update, fns...) + podWorkers.UpdatePod(tCtx, tc.update, fns...) if podWorkers.w.IsPodKnownTerminated(uid) != tc.expectKnownTerminated { t.Errorf("podWorker.IsPodKnownTerminated expected to be %t", tc.expectKnownTerminated) @@ -997,7 +1009,8 @@ func TestCompleteWork_Enqueue(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - podWorkers, _, _ := createPodWorkers() + logger, _ := ktesting.NewTestContext(t) + podWorkers, _, _ := createPodWorkers(logger) podWorkers.clock = clock fakeQueue := podWorkers.workQueue.(*fakeQueue) podUID := types.UID("12345") @@ -1005,7 +1018,7 @@ func TestCompleteWork_Enqueue(t *testing.T) { podWorkers.resyncInterval = resyncInterval podWorkers.backOffPeriod = defaultBackoff podWorkers.podSyncStatuses[podUID] = &podSyncStatus{} - podWorkers.completeWork(podUID, tc.phaseTransition, tc.syncErr) + podWorkers.completeWork(logger, podUID, tc.phaseTransition, tc.syncErr) if fakeQueue.Empty() { t.Fatalf("work queue should not be empty") @@ -1031,13 +1044,14 @@ func TestCompleteWork_PendingUpdate(t *testing.T) { podUID := types.UID("pod-with-pending-update-check") t.Run("with nil pendingUpdate, clears working status", func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) p := &podWorkers{ podSyncStatuses: make(map[types.UID]*podSyncStatus), workQueue: &fakeQueue{}, } p.podSyncStatuses[podUID] = &podSyncStatus{working: true, pendingUpdate: nil} - p.completeWork(podUID, false, nil) + p.completeWork(logger, podUID, false, nil) p.podLock.Lock() defer p.podLock.Unlock() @@ -1047,6 +1061,7 @@ func TestCompleteWork_PendingUpdate(t *testing.T) { }) t.Run("with non-nil pendingUpdate, queues an update signal", func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) p := &podWorkers{ podSyncStatuses: make(map[types.UID]*podSyncStatus), podUpdates: make(map[types.UID]chan struct{}), @@ -1059,7 +1074,7 @@ func TestCompleteWork_PendingUpdate(t *testing.T) { } p.podSyncStatuses[podUID] = &podSyncStatus{working: true, pendingUpdate: dummyUpdate} - p.completeWork(podUID, false, nil) + p.completeWork(logger, podUID, false, nil) select { case <-p.podUpdates[podUID]: @@ -1071,10 +1086,11 @@ func TestCompleteWork_PendingUpdate(t *testing.T) { } func TestUpdatePodForRuntimePod(t *testing.T) { - podWorkers, _, processed := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) // ignores running pod of wrong sync type - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodCreate, RunningPod: &kubecontainer.Pod{ID: "1", Name: "1", Namespace: "test"}, }) @@ -1084,7 +1100,7 @@ func TestUpdatePodForRuntimePod(t *testing.T) { } // creates synthetic pod - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, RunningPod: &kubecontainer.Pod{ID: "1", Name: "1", Namespace: "test"}, }) @@ -1102,7 +1118,8 @@ func TestUpdatePodForRuntimePod(t *testing.T) { } func TestUpdatePodForTerminatedRuntimePod(t *testing.T) { - podWorkers, _, processed := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) now := time.Now() podWorkers.podSyncStatuses[types.UID("1")] = &podSyncStatus{ @@ -1113,7 +1130,7 @@ func TestUpdatePodForTerminatedRuntimePod(t *testing.T) { } // creates synthetic pod - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ UpdateType: kubetypes.SyncPodKill, RunningPod: &kubecontainer.Pod{ID: "1", Name: "1", Namespace: "test"}, }) @@ -1128,19 +1145,20 @@ func TestUpdatePodForTerminatedRuntimePod(t *testing.T) { } func TestUpdatePodDoesNotForgetSyncPodKill(t *testing.T) { - podWorkers, _, processed := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) numPods := 20 for i := 0; i < numPods; i++ { pod := newNamedPod(strconv.Itoa(i), "ns", strconv.Itoa(i), false) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodCreate, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodKill, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodUpdate, }) @@ -1186,17 +1204,17 @@ type terminalPhaseSync struct { terminal sets.Set[string] } -func (s *terminalPhaseSync) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { - isTerminal, err := s.fn(ctx, updateType, pod, mirrorPod, podStatus) +func (s *terminalPhaseSync) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { + isTerminal, _, err := s.fn(ctx, updateType, pod, mirrorPod, podStatus) if err != nil { - return false, err + return false, nil, err } if !isTerminal { s.lock.Lock() defer s.lock.Unlock() isTerminal = s.terminal.Has(string(pod.UID)) } - return isTerminal, nil + return isTerminal, nil, nil } func (s *terminalPhaseSync) SetTerminal(uid types.UID) { @@ -1213,14 +1231,15 @@ func newTerminalPhaseSync(fn syncPodFnType) *terminalPhaseSync { } func TestTerminalPhaseTransition(t *testing.T) { - podWorkers, _, _ := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, _ := createPodWorkers(logger) var channels WorkChannel podWorkers.workerChannelFn = channels.Intercept terminalPhaseSyncer := newTerminalPhaseSync(podWorkers.podSyncer.(*podSyncerFuncs).syncPod) podWorkers.podSyncer.(*podSyncerFuncs).syncPod = terminalPhaseSyncer.SyncPod // start pod - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("1", "test1", "pod1", false), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1233,7 +1252,7 @@ func TestTerminalPhaseTransition(t *testing.T) { } // send another update to the pod - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("1", "test1", "pod1", false), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1247,7 +1266,7 @@ func TestTerminalPhaseTransition(t *testing.T) { // the next sync should result in a transition to terminal terminalPhaseSyncer.SetTerminal(types.UID("1")) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("1", "test1", "pod1", false), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1265,7 +1284,8 @@ func TestStaticPodExclusion(t *testing.T) { t.Skip("skipping test in short mode.") } - podWorkers, _, processed := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) var channels WorkChannel podWorkers.workerChannelFn = channels.Intercept @@ -1275,11 +1295,11 @@ func TestStaticPodExclusion(t *testing.T) { } // start two pods with the same name, one static, one apiserver - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("1-normal", "test1", "pod1", false), UpdateType: kubetypes.SyncPodUpdate, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("2-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1308,11 +1328,11 @@ func TestStaticPodExclusion(t *testing.T) { } // attempt to start a second and third static pod, which should not start - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("3-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("4-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1367,7 +1387,7 @@ func TestStaticPodExclusion(t *testing.T) { // send a basic update for 3-static podWorkers.workQueue.GetWork() - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("3-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1387,7 +1407,7 @@ func TestStaticPodExclusion(t *testing.T) { // mark 3-static as deleted while 2-static is still running podWorkers.workQueue.GetWork() - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("3-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodKill, }) @@ -1412,7 +1432,7 @@ func TestStaticPodExclusion(t *testing.T) { } // terminate 2-static - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("2-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodKill, }) @@ -1431,7 +1451,7 @@ func TestStaticPodExclusion(t *testing.T) { } // simulate a periodic event from the work queue for 4-static - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("4-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1450,7 +1470,7 @@ func TestStaticPodExclusion(t *testing.T) { } // initiate a sync with all pods remaining - state := podWorkers.SyncKnownPods([]*v1.Pod{ + state := podWorkers.SyncKnownPods(logger, []*v1.Pod{ newNamedPod("1-normal", "test1", "pod1", false), newNamedPod("2-static", "test1", "pod1", true), newNamedPod("3-static", "test1", "pod1", true), @@ -1473,7 +1493,7 @@ func TestStaticPodExclusion(t *testing.T) { } // initiate a sync with 3-static removed - state = podWorkers.SyncKnownPods([]*v1.Pod{ + state = podWorkers.SyncKnownPods(logger, []*v1.Pod{ newNamedPod("1-normal", "test1", "pod1", false), newNamedPod("2-static", "test1", "pod1", true), newNamedPod("4-static", "test1", "pod1", true), @@ -1494,18 +1514,18 @@ func TestStaticPodExclusion(t *testing.T) { // start a static pod, kill it, then add another one, but ensure the pod worker // for pod 5 doesn't see the kill event (so it remains waiting to start) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("5-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) // Wait for the previous work to be delivered to the worker drainAllWorkers(podWorkers) channels.Channel("5-static").Hold() - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("5-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodKill, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("6-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1524,12 +1544,12 @@ func TestStaticPodExclusion(t *testing.T) { } // terminate 4-static and wake 6-static - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("4-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodKill, }) drainWorkersExcept(podWorkers, "5-static") - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("6-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1564,30 +1584,30 @@ func TestStaticPodExclusion(t *testing.T) { // start three more static pods, kill the previous static pod blocking start, // and simulate the second pod of three (8) getting to run first - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("7-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("8-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("9-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) drainAllWorkers(podWorkers) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("6-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodKill, }) drainAllWorkers(podWorkers) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("6-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodCreate, }) drainAllWorkers(podWorkers) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("8-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1623,12 +1643,12 @@ func TestStaticPodExclusion(t *testing.T) { } // terminate 7-static and wake 8-static - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("7-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodKill, }) drainAllWorkers(podWorkers) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod("8-static", "test1", "pod1", true), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1642,7 +1662,7 @@ func TestStaticPodExclusion(t *testing.T) { } // initiate a sync with all but 8-static pods undesired - state = podWorkers.SyncKnownPods([]*v1.Pod{ + state = podWorkers.SyncKnownPods(logger, []*v1.Pod{ newNamedPod("8-static", "test1", "pod1", true), }) drainAllWorkers(podWorkers) @@ -1741,11 +1761,12 @@ func (w *WorkChannel) Intercept(uid types.UID, ch chan struct{}) (outCh <-chan s } func TestSyncKnownPods(t *testing.T) { - podWorkers, _, _ := createPodWorkers() + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, _ := createPodWorkers(logger) numPods := 20 for i := 0; i < numPods; i++ { - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: newNamedPod(strconv.Itoa(i), "ns", "name", false), UpdateType: kubetypes.SyncPodUpdate, }) @@ -1771,7 +1792,7 @@ func TestSyncKnownPods(t *testing.T) { now := metav1.Now() pod.DeletionTimestamp = &now } - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodKill, }) @@ -1824,7 +1845,7 @@ func TestSyncKnownPods(t *testing.T) { t.Errorf("Expected pod to not be suitable for removal (does not exist but not yet synced)") } - podWorkers.SyncKnownPods(desiredPodList) + podWorkers.SyncKnownPods(logger, desiredPodList) if len(podWorkers.podUpdates) != 2 { t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) } @@ -1850,7 +1871,7 @@ func TestSyncKnownPods(t *testing.T) { // verify workers that are not terminated stay open even if config no longer // sees them - podWorkers.SyncKnownPods(nil) + podWorkers.SyncKnownPods(logger, nil) drainAllWorkers(podWorkers) if len(podWorkers.podUpdates) != 0 { t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) @@ -1861,7 +1882,7 @@ func TestSyncKnownPods(t *testing.T) { for uid := range desiredPods { pod := newNamedPod(string(uid), "ns", "name", false) - podWorkers.UpdatePod(UpdatePodOptions{ + podWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: pod, UpdateType: kubetypes.SyncPodKill, }) @@ -1869,7 +1890,7 @@ func TestSyncKnownPods(t *testing.T) { drainWorkers(podWorkers, numPods) // verify once those pods terminate (via some other flow) the workers are cleared - podWorkers.SyncKnownPods(nil) + podWorkers.SyncKnownPods(logger, nil) if len(podWorkers.podUpdates) != 0 { t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) } @@ -2048,7 +2069,16 @@ func Test_removeTerminatedWorker(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - podWorkers, _, _ := createPodWorkers() + normalizeUpdatePodOptions := func(opts *UpdatePodOptions) *UpdatePodOptions { + if opts == nil { + return nil + } + normalized := *opts + return &normalized + } + + logger, _ := ktesting.NewTestContext(t) + podWorkers, _, _ := createPodWorkers(logger) podWorkers.podSyncStatuses[podUID] = tc.podSyncStatus podWorkers.podUpdates[podUID] = make(chan struct{}, 1) if tc.podSyncStatus.working { @@ -2057,7 +2087,7 @@ func Test_removeTerminatedWorker(t *testing.T) { podWorkers.startedStaticPodsByFullname = tc.startedStaticPodsByFullname podWorkers.waitingToStartStaticPodsByFullname = tc.waitingToStartStaticPodsByFullname - podWorkers.removeTerminatedWorker(podUID, podWorkers.podSyncStatuses[podUID], tc.orphan) + podWorkers.removeTerminatedWorker(logger, podUID, podWorkers.podSyncStatuses[podUID], tc.orphan) status, exists := podWorkers.podSyncStatuses[podUID] if tc.removed && exists { t.Fatalf("Expected pod worker to be removed") @@ -2071,8 +2101,10 @@ func Test_removeTerminatedWorker(t *testing.T) { if tc.expectGracePeriod > 0 && status.gracePeriod != tc.expectGracePeriod { t.Errorf("Unexpected grace period %d", status.gracePeriod) } - if !reflect.DeepEqual(tc.expectPending, status.pendingUpdate) { - t.Errorf("Unexpected pending: %s", cmp.Diff(tc.expectPending, status.pendingUpdate)) + expectedPending := normalizeUpdatePodOptions(tc.expectPending) + actualPending := normalizeUpdatePodOptions(status.pendingUpdate) + if !reflect.DeepEqual(expectedPending, actualPending) { + t.Errorf("Unexpected pending: %s", cmp.Diff(expectedPending, actualPending)) } if tc.expectPending != nil { if !status.working { @@ -2093,15 +2125,15 @@ type simpleFakeKubelet struct { wg sync.WaitGroup } -func (kl *simpleFakeKubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { +func (kl *simpleFakeKubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { kl.pod, kl.mirrorPod, kl.podStatus = pod, mirrorPod, podStatus - return false, nil + return false, nil, nil } -func (kl *simpleFakeKubelet) SyncPodWithWaitGroup(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { +func (kl *simpleFakeKubelet) SyncPodWithWaitGroup(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, func(), error) { kl.pod, kl.mirrorPod, kl.podStatus = pod, mirrorPod, podStatus kl.wg.Done() - return false, nil + return false, nil, nil } func (kl *simpleFakeKubelet) SyncTerminatingPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, gracePeriod *int64, podStatusFn func(*v1.PodStatus)) error { @@ -2119,6 +2151,7 @@ func (kl *simpleFakeKubelet) SyncTerminatedPod(ctx context.Context, pod *v1.Pod, // TestFakePodWorkers verifies that the fakePodWorkers behaves the same way as the real podWorkers // for their invocation of the syncPodFn. func TestFakePodWorkers(t *testing.T) { + tCtx := ktesting.Init(t) fakeRecorder := &record.FakeRecorder{} fakeRuntime := &containertest.FakeRuntime{} fakeCache := containertest.NewFakeCache(fakeRuntime) @@ -2135,7 +2168,7 @@ func TestFakePodWorkers(t *testing.T) { time.Second, time.Second, fakeCache, - allocation.NewInMemoryManager(cm.NodeConfig{}, nil, nil, nil, nil, nil, nil, nil), + allocation.NewInMemoryManager(nil, nil, nil, nil, nil, nil), ) fakePodWorkers := &fakePodWorkers{ syncPodFn: kubeletForFakeWorkers.SyncPod, @@ -2163,12 +2196,12 @@ func TestFakePodWorkers(t *testing.T) { for i, tt := range tests { kubeletForRealWorkers.wg.Add(1) - realPodWorkers.UpdatePod(UpdatePodOptions{ + realPodWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: tt.pod, MirrorPod: tt.mirrorPod, UpdateType: kubetypes.SyncPodUpdate, }) - fakePodWorkers.UpdatePod(UpdatePodOptions{ + fakePodWorkers.UpdatePod(tCtx, UpdatePodOptions{ Pod: tt.pod, MirrorPod: tt.mirrorPod, UpdateType: kubetypes.SyncPodUpdate, @@ -2193,8 +2226,9 @@ func TestFakePodWorkers(t *testing.T) { // TestKillPodNowFunc tests the blocking kill pod function works with pod workers as expected. func TestKillPodNowFunc(t *testing.T) { fakeRecorder := &record.FakeRecorder{} - podWorkers, _, processed := createPodWorkers() - killPodFunc := killPodNow(podWorkers, fakeRecorder) + logger, tCtx := ktesting.NewTestContext(t) + podWorkers, _, processed := createPodWorkers(logger) + killPodFunc := killPodNow(tCtx, podWorkers, fakeRecorder) pod := newNamedPod("test", "ns", "test", false) gracePeriodOverride := int64(0) err := killPodFunc(pod, false, &gracePeriodOverride, func(status *v1.PodStatus) { @@ -2525,7 +2559,8 @@ func Test_allowPodStart(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - podWorkers, _, _ := createPodWorkers() + logger, _ := ktesting.NewTestContext(t) + podWorkers, _, _ := createPodWorkers(logger) if tc.podSyncStatuses != nil { podWorkers.podSyncStatuses = tc.podSyncStatuses } @@ -2535,7 +2570,7 @@ func Test_allowPodStart(t *testing.T) { if tc.waitingToStartStaticPodsByFullname != nil { podWorkers.waitingToStartStaticPodsByFullname = tc.waitingToStartStaticPodsByFullname } - allowed, allowedEver := podWorkers.allowPodStart(tc.pod) + allowed, allowedEver := podWorkers.allowPodStart(logger, tc.pod) if allowed != tc.allowed { if tc.allowed { t.Errorf("Pod should be allowed") diff --git a/pkg/kubelet/podcertificate/podcertificatemanager.go b/pkg/kubelet/podcertificate/podcertificatemanager.go index ec8c5127e85fa..9d0048c8499ec 100644 --- a/pkg/kubelet/podcertificate/podcertificatemanager.go +++ b/pkg/kubelet/podcertificate/podcertificatemanager.go @@ -25,7 +25,6 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" - "crypto/sha256" "crypto/x509" "encoding/pem" "fmt" @@ -337,7 +336,8 @@ func (m *IssuingManager) queueAllProjectionsForPod(uid types.UID) { } func (m *IssuingManager) Run(ctx context.Context) { - klog.InfoS("podcertificate.IssuingManager starting up") + logger := klog.FromContext(ctx) + logger.Info("podcertificate.IssuingManager starting up") if !cache.WaitForCacheSync(ctx.Done(), m.pcrInformer.HasSynced, m.nodeInformer.HasSynced) { return } @@ -348,7 +348,7 @@ func (m *IssuingManager) Run(ctx context.Context) { m.projectionQueue.ShutDown() - klog.InfoS("podcertificate.IssuingManager shut down") + logger.Info("podcertificate.IssuingManager shut down") } func (m *IssuingManager) runProjectionProcessor(ctx context.Context) { @@ -378,6 +378,8 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey // Remember, returning nil from this function indicates that the work item // was successfully processed, and should be dropped from the queue. + logger := klog.FromContext(ctx) + pod, ok := m.podManager.GetPodByUID(types.UID(key.PodUID)) if !ok { // If we can't find the pod anymore, it's been deleted. Clear all our @@ -462,7 +464,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey // Return nil to remove the projection from the workqueue --- it will be // readded once the PodCertificateRequest appears in the informer cache, // and goes through status updates. - klog.V(4).InfoS("PodCertificateRequest created, moving to credStateWait", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest created, moving to credStateWait", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) return nil case *credStateWait: @@ -495,7 +497,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey Reason: cond.Reason, Message: cond.Message, } - klog.V(4).InfoS("PodCertificateRequest denied, moving to credStateDenied", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest denied, moving to credStateDenied", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) eventMessage := fmt.Sprintf("PodCertificateRequest %s was denied, reason=%q, message=%q", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name, cond.Reason, cond.Message) m.recorder.Eventf(pod, corev1.EventTypeWarning, certificatesv1beta1.PodCertificateRequestConditionTypeDenied, cond.Reason, eventMessage) return nil @@ -504,7 +506,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey Reason: cond.Reason, Message: cond.Message, } - klog.V(4).InfoS("PodCertificateRequest failed, moving to credStateFailed", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest failed, moving to credStateFailed", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) eventMessage := fmt.Sprintf("PodCertificateRequest %s failed, reason=%q, message=%q", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name, cond.Reason, cond.Message) m.recorder.Eventf(pod, corev1.EventTypeWarning, certificatesv1beta1.PodCertificateRequestConditionTypeFailed, cond.Reason, eventMessage) return nil @@ -515,7 +517,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey beginRefreshAt: pcr.Status.BeginRefreshAt.Time.Add(jitterDuration()), notAfter: pcr.Status.NotAfter.Time, } - klog.V(4).InfoS("PodCertificateRequest issued, moving to credStateFresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest issued, moving to credStateFresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) return nil } } @@ -523,17 +525,17 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey // Nothing -- the request is still pending. Return nil to remove the // projection from the workqueue. It will be redriven when the // PodCertificateRequest gets an update. - klog.V(4).InfoS("PodCertificateRequest not in terminal state, remaining in credStateWait", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest not in terminal state, remaining in credStateWait", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) return nil case *credStateDenied: // Nothing to do; this is a permanent error state for the pod. - klog.V(4).InfoS("staying in credStateDenied", "key", key) + logger.V(4).Info("staying in credStateDenied", "key", key) return nil case *credStateFailed: // Nothing to do; this is a permanent error state for the pod. - klog.V(4).InfoS("staying in credStateFailed", "key", key) + logger.V(4).Info("staying in credStateFailed", "key", key) return nil case *credStateFresh: @@ -545,18 +547,18 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey return nil } - klog.V(4).InfoS("Time to refresh", "key", key) + logger.V(4).Info("Time to refresh", "key", key) // The current time is more than 10 minutes past the most recently issued certificate's `beginRefreshAt` timestamp but the state has not been labeled with overdue for refresh. if m.clock.Now().After(state.beginRefreshAt.Add(refreshOverdueDuration)) && !state.eventEmittedForOverdueForRefresh { - klog.V(4).InfoS("Refresh overdue", "key", key) + logger.V(4).Info("Refresh overdue", "key", key) m.recorder.Eventf(pod, corev1.EventTypeWarning, "CertificateOverdueForRefresh", "PodCertificate refresh overdue") state.eventEmittedForOverdueForRefresh = true } // The current time is past the most recently issued certificate's `notAfter` timestamp but the state has not been labelled with expired. if m.clock.Now().After(state.notAfter) && !state.eventEmittedForExpiration { - klog.V(4).InfoS("Certificates expired", "key", key) + logger.V(4).Info("Certificates expired", "key", key) m.recorder.Eventf(pod, corev1.EventTypeWarning, "CertificateExpired", "PodCertificate expired") state.eventEmittedForExpiration = true } @@ -602,7 +604,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey // Return nil to remove the projection from the workqueue --- it will be // readded once the PodCertificateRequest appears in the informer cache, // and goes through status updates. - klog.V(4).InfoS("PodCertificateRequest created, moving to credStateWaitRefresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest created, moving to credStateWaitRefresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) return nil case *credStateWaitRefresh: @@ -640,7 +642,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey Reason: cond.Reason, Message: cond.Message, } - klog.V(4).InfoS("PodCertificateRequest denied, moving to credStateDenied", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest denied, moving to credStateDenied", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) eventMessage := fmt.Sprintf("PodCertificateRequest %s was denied, reason=%q, message=%q", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name, cond.Reason, cond.Message) m.recorder.Eventf(pod, corev1.EventTypeWarning, certificatesv1beta1.PodCertificateRequestConditionTypeDenied, cond.Reason, eventMessage) return nil @@ -649,7 +651,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey Reason: cond.Reason, Message: cond.Message, } - klog.V(4).InfoS("PodCertificateRequest failed, moving to credStateFailed", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest failed, moving to credStateFailed", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) eventMessage := fmt.Sprintf("PodCertificateRequest %s failed, reason=%q, message=%q", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name, cond.Reason, cond.Message) m.recorder.Eventf(pod, corev1.EventTypeWarning, certificatesv1beta1.PodCertificateRequestConditionTypeFailed, cond.Reason, eventMessage) return nil @@ -660,7 +662,7 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey beginRefreshAt: pcr.Status.BeginRefreshAt.Time.Add(jitterDuration()), notAfter: pcr.Status.NotAfter.Time, } - klog.V(4).InfoS("PodCertificateRequest issued, moving to credStateFresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest issued, moving to credStateFresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) return nil } } @@ -668,18 +670,18 @@ func (m *IssuingManager) handleProjection(ctx context.Context, key projectionKey // Nothing -- the request is still pending. Return nil to remove the // projection from the workqueue. It will be redriven when the // PodCertificateRequest gets an update. - klog.V(4).InfoS("PodCertificateRequest not in terminal state, remaining in credStateWaitRefresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) + logger.V(4).Info("PodCertificateRequest not in terminal state, remaining in credStateWaitRefresh", "key", key, "pcr", pcr.ObjectMeta.Namespace+"/"+pcr.ObjectMeta.Name) // The current time is more than 10 minutes past the most recently issued certificate's `beginRefreshAt` timestamp but the state has not been labeled with overdue for refresh. if m.clock.Now().After(state.beginRefreshAt.Add(refreshOverdueDuration)) && !state.eventEmittedForOverdueForRefresh { - klog.V(4).InfoS("Refresh overdue", "key", key) + logger.V(4).Info("Refresh overdue", "key", key) m.recorder.Eventf(pod, corev1.EventTypeWarning, "CertificateOverdueForRefresh", "PodCertificate refresh overdue") state.eventEmittedForOverdueForRefresh = true } // The current time is past the most recently issued certificate's `notAfter` timestamp but the state has not been labeled with expired. if m.clock.Now().After(state.notAfter) && !state.eventEmittedForExpiration { - klog.V(4).InfoS("Certificates expired", "key", key) + logger.V(4).Info("Certificates expired", "key", key) m.recorder.Eventf(pod, corev1.EventTypeWarning, "CertificateExpired", "PodCertificate expired") state.eventEmittedForExpiration = true } @@ -739,18 +741,14 @@ func (m *IssuingManager) createPodCertificateRequest( podName string, podUID types.UID, serviceAccountName string, serviceAccountUID types.UID, nodeName types.NodeName, nodeUID types.UID, - signerName, keyType string, maxExpirationSeconds *int32, userAnnotations map[string]string) ([]byte, *certificatesv1beta1.PodCertificateRequest, error) { - - privateKey, publicKey, proof, err := generateKeyAndProof(keyType, []byte(podUID)) + signerName, keyType string, maxExpirationSeconds *int32, + userAnnotations map[string]string, +) ([]byte, *certificatesv1beta1.PodCertificateRequest, error) { + privateKey, pkcs10Req, err := generateKeyAndProof(keyType) if err != nil { return nil, nil, fmt.Errorf("while generating keypair: %w", err) } - pkixPublicKey, err := x509.MarshalPKIXPublicKey(publicKey) - if err != nil { - return nil, nil, fmt.Errorf("while marshaling public key: %w", err) - } - keyPEM, err := pemEncodeKey(privateKey) if err != nil { return nil, nil, fmt.Errorf("while PEM-encoding private key: %w", err) @@ -762,7 +760,7 @@ func (m *IssuingManager) createPodCertificateRequest( GenerateName: "req-", OwnerReferences: []metav1.OwnerReference{ { - APIVersion: "core/v1", + APIVersion: "v1", Kind: "Pod", Name: podName, UID: podUID, @@ -778,8 +776,7 @@ func (m *IssuingManager) createPodCertificateRequest( NodeName: nodeName, NodeUID: nodeUID, MaxExpirationSeconds: maxExpirationSeconds, - PKIXPublicKey: pkixPublicKey, - ProofOfPossession: proof, + StubPKCS10Request: pkcs10Req, UnverifiedUserAnnotations: userAnnotations, }, } @@ -874,73 +871,57 @@ func (m *IssuingManager) MetricReport() *MetricReport { return report } -func hashBytes(in []byte) []byte { - out := sha256.Sum256(in) - return out[:] -} +func generateKeyAndProof(keyType string) (crypto.PrivateKey, []byte, error) { + var privKey crypto.PrivateKey -func generateKeyAndProof(keyType string, toBeSigned []byte) (privKey crypto.PrivateKey, pubKey crypto.PublicKey, sig []byte, err error) { switch keyType { case "RSA3072": - key, err := rsa.GenerateKey(rand.Reader, 3072) - if err != nil { - return nil, nil, nil, fmt.Errorf("while generating RSA 3072 key: %w", err) - } - sig, err := rsa.SignPSS(rand.Reader, key, crypto.SHA256, hashBytes(toBeSigned), nil) + priv, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil { - return nil, nil, nil, fmt.Errorf("while signing proof: %w", err) + return nil, nil, fmt.Errorf("while generating RSA 3072 key: %w", err) } - return key, &key.PublicKey, sig, nil + privKey = priv case "RSA4096": - key, err := rsa.GenerateKey(rand.Reader, 4096) - if err != nil { - return nil, nil, nil, fmt.Errorf("while generating RSA 4096 key: %w", err) - } - sig, err := rsa.SignPSS(rand.Reader, key, crypto.SHA256, hashBytes(toBeSigned), nil) + priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { - return nil, nil, nil, fmt.Errorf("while signing proof: %w", err) + return nil, nil, fmt.Errorf("while generating RSA 4096 key: %w", err) } - return key, &key.PublicKey, sig, nil + privKey = priv case "ECDSAP256": - key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return nil, nil, nil, fmt.Errorf("while generating ECDSA P256 key: %w", err) + return nil, nil, fmt.Errorf("while generating ECDSA P256 key: %w", err) } - sig, err := ecdsa.SignASN1(rand.Reader, key, hashBytes(toBeSigned)) - if err != nil { - return nil, nil, nil, fmt.Errorf("while signing proof: %w", err) - } - return key, &key.PublicKey, sig, nil + privKey = priv case "ECDSAP384": - key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { - return nil, nil, nil, fmt.Errorf("while generating ECDSA P384 key: %w", err) + return nil, nil, fmt.Errorf("while generating ECDSA P384 key: %w", err) } - sig, err := ecdsa.SignASN1(rand.Reader, key, hashBytes(toBeSigned)) - if err != nil { - return nil, nil, nil, fmt.Errorf("while signing proof: %w", err) - } - return key, &key.PublicKey, sig, nil + privKey = priv case "ECDSAP521": - key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) - if err != nil { - return nil, nil, nil, fmt.Errorf("while generating ECDSA P521 key: %w", err) - } - sig, err := ecdsa.SignASN1(rand.Reader, key, hashBytes(toBeSigned)) + priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) if err != nil { - return nil, nil, nil, fmt.Errorf("while signing proof: %w", err) + return nil, nil, fmt.Errorf("while generating ECDSA P521 key: %w", err) } - return key, &key.PublicKey, sig, nil + privKey = priv case "ED25519": - pub, priv, err := ed25519.GenerateKey(rand.Reader) + _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { - return nil, nil, nil, fmt.Errorf("while generating Ed25519 key: %w", err) + return nil, nil, fmt.Errorf("while generating Ed25519 key: %w", err) } - sig := ed25519.Sign(priv, toBeSigned) - return priv, pub, sig, nil + privKey = priv default: - return nil, nil, nil, fmt.Errorf("unknown key type %q", keyType) + return nil, nil, fmt.Errorf("unknown key type %q", keyType) } + + tmpl := &x509.CertificateRequest{} + pkcs10Req, err := x509.CreateCertificateRequest(rand.Reader, tmpl, privKey) + if err != nil { + return nil, nil, fmt.Errorf("while generating stub PKCS#10 request: %w", err) + } + + return privKey, pkcs10Req, nil } func pemEncodeKey(key crypto.PrivateKey) ([]byte, error) { diff --git a/pkg/kubelet/podcertificate/podcertificatemanager_test.go b/pkg/kubelet/podcertificate/podcertificatemanager_test.go index 331a2c4e8e700..7dca0cab4f236 100644 --- a/pkg/kubelet/podcertificate/podcertificatemanager_test.go +++ b/pkg/kubelet/podcertificate/podcertificatemanager_test.go @@ -21,7 +21,9 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "math/rand" "slices" + "sync/atomic" "testing" "time" @@ -30,12 +32,15 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" certlistersv1beta1 "k8s.io/client-go/listers/certificates/v1beta1" corelistersv1 "k8s.io/client-go/listers/core/v1" + k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/test/utils/hermeticpodcertificatesigner" "k8s.io/kubernetes/test/utils/ktesting" @@ -172,10 +177,24 @@ func TestTransitionInitialToWait(t *testing.T) { gotPCRClone.ObjectMeta.Namespace = gotPCR.ObjectMeta.Namespace gotPCRClone.Spec.PKIXPublicKey = nil gotPCRClone.Spec.ProofOfPossession = nil + gotPCRClone.Spec.StubPKCS10Request = nil gotPCRClone.Status = certsv1beta1.PodCertificateRequestStatus{} if diff := cmp.Diff(gotPCRClone, wantPCR); diff != "" { t.Fatalf("PodCertificateManager created a bad PCR; diff (-got +want)\n%s", diff) } + + // Verify OwnerReferences are set correctly for garbage collection. + wantOwnerRefs := []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Pod", + Name: workloadPod.ObjectMeta.Name, + UID: workloadPod.ObjectMeta.UID, + }, + } + if diff := cmp.Diff(gotPCR.ObjectMeta.OwnerReferences, wantOwnerRefs); diff != "" { + t.Fatalf("PodCertificateRequest has incorrect OwnerReferences; diff (-got +want)\n%s", diff) + } } func TestPCRDeletedWhileWaiting(t *testing.T) { @@ -293,9 +312,51 @@ func TestFullFlow(t *testing.T) { ctx, cancel := context.WithCancel(ktesting.Init(t)) defer cancel() + clock := testclock.NewFakeClock(mustRFC3339(t, "2010-01-01T00:00:00Z")) kc := fake.NewSimpleClientset() + + // Assign PCR name and creationTimeStamp + kc.Fake.PrependReactor("create", "podcertificaterequests", + func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + obj := action.(k8stesting.CreateAction).GetObject().(*certsv1beta1.PodCertificateRequest) + // Simulate server-side GenerateName behavior + if obj.Name == "" { + obj.Name = fmt.Sprintf("%s-pcr-%d", obj.Spec.PodName, rand.Int63n(1_000_000)) + } + obj.CreationTimestamp = metav1.NewTime(clock.Now()) + + return false, obj, nil // allow normal processing + }) + + // fake.Clientset suffers from a race condition related to informers: + // it does not implement resource version support in its Watch + // implementation and instead assumes that watches are set up + // before further changes are made. + // + // If a test waits for caches to be synced and then immediately + // adds an object, that new object will never be seen by event handlers + // if the race goes wrong and the Watch call hadn't completed yet + // + // To work around this, we count all watches and only proceed when all of them are in place + var numWatches atomic.Int32 + kc.Fake.PrependWatchReactor("*", func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) { + // Recreate the default fake-client watch behaviour, but count watches + var opts metav1.ListOptions + if wa, ok := action.(k8stesting.WatchActionImpl); ok { + opts = wa.ListOptions + } + + gvr := action.GetResource() + ns := action.GetNamespace() + w, err := kc.Tracker().Watch(gvr, ns, opts) + if err != nil { + return false, nil, err + } + + numWatches.Add(1) + return true, w, nil + }) informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0) - clock := testclock.NewFakeClock(mustRFC3339(t, "2010-01-01T00:00:00Z")) // // Configure and boot up a fake podcertificaterequest signing controller. @@ -309,7 +370,6 @@ func TestFullFlow(t *testing.T) { } pcrSigner := hermeticpodcertificatesigner.New(clock, signerName, caKeys, caCerts, kc) go pcrSigner.Run(ctx) - // // Configure and boot up enough Kubelet subsystems to run an IssuingManager. // @@ -336,8 +396,25 @@ func TestFullFlow(t *testing.T) { types.NodeName(node1.ObjectMeta.Name), clock, ) - + // Start informers informerFactory.Start(ctx.Done()) + + // wait until the informers' watches are actually registered, + const expectedWatches = 3 // Pods, Nodes, PodCertificateRequests + if err := wait.PollUntilContextTimeout( + ctx, + 100*time.Millisecond, + 5*time.Second, + true, + func(ctx context.Context) (bool, error) { + return numWatches.Load() >= expectedWatches, nil + }, + ); err != nil { + t.Fatalf("timed out waiting for informer watches, got %d (want >= %d): %v", + numWatches.Load(), expectedWatches, err) + } + + // Now it's safe to run the manager – informers are watching. go node1PodCertificateManager.Run(ctx) // @@ -367,6 +444,7 @@ func TestFullFlow(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: workloadNS.ObjectMeta.Name, Name: "workload", + UID: "test-workload-uid", }, Spec: corev1.PodSpec{ ServiceAccountName: workloadSA.ObjectMeta.Name, @@ -408,7 +486,6 @@ func TestFullFlow(t *testing.T) { if err != nil { t.Fatalf("Unexpected error creating workload pod: %v", err) } - // Because our fake podManager is based on an informer, we need to poll // until workloadPod is reflected in the informer. err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) { @@ -465,6 +542,7 @@ func TestFullFlow(t *testing.T) { gotPCRClone.ObjectMeta.Namespace = gotPCR.ObjectMeta.Namespace gotPCRClone.Spec.PKIXPublicKey = nil gotPCRClone.Spec.ProofOfPossession = nil + gotPCRClone.Spec.StubPKCS10Request = nil gotPCRClone.Status = certsv1beta1.PodCertificateRequestStatus{} if diff := cmp.Diff(gotPCRClone, wantPCR); diff != "" { t.Fatalf("PodCertificateManager created a bad PCR; diff (-got +want)\n%s", diff) diff --git a/pkg/kubelet/prober/prober.go b/pkg/kubelet/prober/prober.go index 554f607cbcec7..467ba4f2971f5 100644 --- a/pkg/kubelet/prober/prober.go +++ b/pkg/kubelet/prober/prober.go @@ -48,14 +48,14 @@ type prober struct { grpc grpcprobe.Prober runner kubecontainer.CommandRunner - recorder record.EventRecorder + recorder record.EventRecorderLogger } // NewProber creates a Prober, it takes a command runner and // several container info managers. func newProber( runner kubecontainer.CommandRunner, - recorder record.EventRecorder) *prober { + recorder record.EventRecorderLogger) *prober { const followNonLocalRedirects = false return &prober{ @@ -76,7 +76,7 @@ func (pb *prober) recordContainerEvent(ctx context.Context, pod *v1.Pod, contain logger.Error(err, "Can't make a ref to pod and container", "pod", klog.KObj(pod), "containerName", container.Name) return } - pb.recorder.Eventf(ref, eventType, reason, message, args...) + pb.recorder.WithLogger(logger).Eventf(ref, eventType, reason, message, args...) } // probe probes the container. diff --git a/pkg/kubelet/prober/prober_manager.go b/pkg/kubelet/prober/prober_manager.go index 7595c0f7c5763..7d07479ca9351 100644 --- a/pkg/kubelet/prober/prober_manager.go +++ b/pkg/kubelet/prober/prober_manager.go @@ -124,7 +124,7 @@ func NewManager( readinessManager results.Manager, startupManager results.Manager, runner kubecontainer.CommandRunner, - recorder record.EventRecorder) Manager { + recorder record.EventRecorderLogger) Manager { prober := newProber(runner, recorder) return &manager{ diff --git a/pkg/kubelet/prober/scale_test.go b/pkg/kubelet/prober/scale_test.go index 47997a201e731..e989e878381c1 100644 --- a/pkg/kubelet/prober/scale_test.go +++ b/pkg/kubelet/prober/scale_test.go @@ -80,7 +80,7 @@ func TestTCPPortExhaustion(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - logger, tCtx := ktesting.NewTestContext(t) + tCtx := ktesting.Init(t) podManager := kubepod.NewBasicPodManager() podStartupLatencyTracker := kubeletutil.NewPodStartupLatencyTracker() m := NewManager( @@ -134,12 +134,12 @@ func TestTCPPortExhaustion(t *testing.T) { }) } podManager.AddPod(&pod) - m.statusManager.SetPodStatus(logger, &pod, pod.Status) + m.statusManager.SetPodStatus(tCtx.Logger(), &pod, pod.Status) m.AddPod(tCtx, &pod) } t.Logf("Adding %d pods with %d containers each in %v", numTestPods, numContainers, time.Since(now)) - ctx := ktesting.WithTimeout(tCtx, 59*time.Second, "timeout 59 Second") + ctx := tCtx.WithTimeout(59*time.Second, "timeout 59 Second") defer ctx.Cancel("TestTCPPortExhaustion completed") var wg sync.WaitGroup diff --git a/pkg/kubelet/prober/worker.go b/pkg/kubelet/prober/worker.go index 2cbda7f20d043..4863cf91f7a53 100644 --- a/pkg/kubelet/prober/worker.go +++ b/pkg/kubelet/prober/worker.go @@ -214,7 +214,7 @@ func (w *worker) stop() { // Returns whether the worker should continue. func (w *worker) doProbe(ctx context.Context) (keepGoing bool) { defer func() { recover() }() // Actually eat panics (HandleCrash takes care of logging) - defer runtime.HandleCrash(func(_ interface{}) { keepGoing = true }) + defer runtime.HandleCrashWithContext(ctx, func(ctx context.Context, _ interface{}) { keepGoing = true }) logger := klog.FromContext(ctx) startTime := time.Now() @@ -243,12 +243,17 @@ func (w *worker) doProbe(ctx context.Context) (keepGoing bool) { } } + isRestartableInitContainer := w.isInitContainer() && + w.container.RestartPolicy != nil && + *w.container.RestartPolicy == v1.ContainerRestartPolicyAlways + if w.containerID.String() != c.ContainerID { if !w.containerID.IsEmpty() { w.resultsManager.Remove(w.containerID) } w.containerID = kubecontainer.ParseContainerID(c.ContainerID) + if !utilfeature.DefaultFeatureGate.Enabled(features.ChangeContainerStatusOnKubeletRestart) { // On kubelet restart, we don't want to immediately set the probe result to Failure, // as this could cause a container that was Ready to become NotReady. @@ -259,6 +264,18 @@ func (w *worker) doProbe(ctx context.Context) (keepGoing bool) { isRestart = true } } + + // For restartable init containers (sidecars) with a startup probe, seed the + // startup result with Success if the container had already started before the + // kubelet restarted. This ensures startupManager.Get() returns a valid result, + // allowing computeInitContainerActions to detect pod initialization. + // See https://github.com/kubernetes/kubernetes/issues/136910 + if isRestartableInitContainer && w.probeType == startup { + if c.Started != nil && *c.Started { + w.resultsManager.Set(w.containerID, results.Success, w.pod) + } + } + if !isRestart { w.resultsManager.Set(w.containerID, w.initialValue, w.pod) } @@ -282,9 +299,6 @@ func (w *worker) doProbe(ctx context.Context) (keepGoing bool) { w.resultsManager.Set(w.containerID, results.Failure, w.pod) } - isRestartableInitContainer := w.isInitContainer() && - w.container.RestartPolicy != nil && *w.container.RestartPolicy == v1.ContainerRestartPolicyAlways - // Abort if the container will not be restarted. if utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules) { if c.State.Terminated == nil { diff --git a/pkg/kubelet/prober/worker_test.go b/pkg/kubelet/prober/worker_test.go index 243140b7a1082..a9d5e8802d117 100644 --- a/pkg/kubelet/prober/worker_test.go +++ b/pkg/kubelet/prober/worker_test.go @@ -908,6 +908,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType probeType initialValue results.Result expectSet bool + isSidecar bool + expectedResult results.Result // only checked if expectSet is true }{ { name: "feature enabled, is restart, readiness", @@ -916,6 +918,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: readiness, initialValue: results.Failure, expectSet: true, + isSidecar: false, + expectedResult: results.Failure, }, { name: "feature enabled, is restart, liveness", @@ -924,6 +928,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: liveness, initialValue: results.Success, expectSet: true, + isSidecar: false, + expectedResult: results.Success, }, { name: "feature enabled, is restart, startup", @@ -932,6 +938,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: startup, initialValue: results.Unknown, expectSet: true, + isSidecar: false, + expectedResult: results.Unknown, }, { name: "feature enabled, not restart, readiness", @@ -940,6 +948,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: readiness, initialValue: results.Failure, expectSet: true, + isSidecar: false, + expectedResult: results.Failure, }, { name: "feature enabled, not restart, liveness", @@ -948,6 +958,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: liveness, initialValue: results.Success, expectSet: true, + isSidecar: false, + expectedResult: results.Success, }, { name: "feature enabled, not restart, startup", @@ -956,6 +968,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: startup, initialValue: results.Unknown, expectSet: true, + isSidecar: false, + expectedResult: results.Unknown, }, { name: "feature disabled, is restart, readiness", @@ -963,6 +977,7 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { isRestart: true, probeType: readiness, expectSet: false, + isSidecar: false, }, { name: "feature disabled, is restart, liveness", @@ -970,6 +985,7 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { isRestart: true, probeType: liveness, expectSet: false, + isSidecar: false, }, { name: "feature disabled, is restart, startup", @@ -977,6 +993,7 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { isRestart: true, probeType: startup, expectSet: false, + isSidecar: false, }, { name: "feature disabled, not restart, readiness", @@ -985,6 +1002,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: readiness, initialValue: results.Failure, expectSet: true, + isSidecar: false, + expectedResult: results.Failure, }, { name: "feature disabled, not restart, liveness", @@ -993,6 +1012,8 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: liveness, initialValue: results.Success, expectSet: true, + isSidecar: false, + expectedResult: results.Success, }, { name: "feature disabled, not restart, startup", @@ -1001,6 +1022,37 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { probeType: startup, initialValue: results.Unknown, expectSet: true, + isSidecar: false, + expectedResult: results.Unknown, + }, + // Sidecar tests - regression for https://github.com/kubernetes/kubernetes/issues/136910 + { + name: "feature disabled, is restart, startup, sidecar", + featureEnabled: false, + isRestart: true, + probeType: startup, + initialValue: results.Unknown, + expectSet: true, + isSidecar: true, + expectedResult: results.Success, // Sidecars get Success, not initialValue + }, + { + name: "feature disabled, is restart, liveness, sidecar", + featureEnabled: false, + isRestart: true, + probeType: liveness, + initialValue: results.Success, + expectSet: false, // Readiness/liveness probes not set for sidecars on restart + isSidecar: true, + }, + { + name: "feature disabled, is restart, readiness, sidecar", + featureEnabled: false, + isRestart: true, + probeType: readiness, + initialValue: results.Failure, + expectSet: false, // Readiness/liveness probes not set for sidecars on restart + isSidecar: true, }, } @@ -1009,27 +1061,48 @@ func TestChangeContainerStatusOnKubeletRestart(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ChangeContainerStatusOnKubeletRestart, tc.featureEnabled) m := newTestManager() - podStatus := getTestRunningStatus() - podStatus.ContainerStatuses[0].ContainerID = "test://container-id" - if tc.isRestart { - podStatus.ContainerStatuses[0].State.Running.StartedAt = metav1.Time{Time: m.start.Add(-5 * time.Minute)} + + var w *worker + var containerID kubecontainer.ContainerID + + if tc.isSidecar { + w = newTestWorkerWithRestartableInitContainer(m, tc.probeType) + w.spec = &v1.Probe{InitialDelaySeconds: 1000} + // For sidecar, we need init container status + podStatus := getTestRunningStatus() + // Move container from regular to init containers (it's a sidecar) + podStatus.InitContainerStatuses = []v1.ContainerStatus{podStatus.ContainerStatuses[0]} + podStatus.ContainerStatuses = nil + podStatus.InitContainerStatuses[0].ContainerID = "test://container-id" + if tc.isRestart { + podStatus.InitContainerStatuses[0].State.Running.StartedAt = metav1.Time{Time: m.start.Add(-5 * time.Minute)} + } else { + podStatus.InitContainerStatuses[0].State.Running.StartedAt = metav1.Time{Time: m.start.Add(5 * time.Minute)} + } + m.statusManager.SetPodStatus(logger, w.pod, podStatus) + containerID = kubecontainer.ParseContainerID(podStatus.InitContainerStatuses[0].ContainerID) } else { - podStatus.ContainerStatuses[0].State.Running.StartedAt = metav1.Time{Time: m.start.Add(5 * time.Minute)} + podStatus := getTestRunningStatus() + podStatus.ContainerStatuses[0].ContainerID = "test://container-id" + if tc.isRestart { + podStatus.ContainerStatuses[0].State.Running.StartedAt = metav1.Time{Time: m.start.Add(-5 * time.Minute)} + } else { + podStatus.ContainerStatuses[0].State.Running.StartedAt = metav1.Time{Time: m.start.Add(5 * time.Minute)} + } + w = newTestWorker(m, tc.probeType, v1.Probe{InitialDelaySeconds: 1000}) + m.statusManager.SetPodStatus(logger, w.pod, podStatus) + containerID = kubecontainer.ParseContainerID(podStatus.ContainerStatuses[0].ContainerID) } - w := newTestWorker(m, tc.probeType, v1.Probe{InitialDelaySeconds: 1000}) - m.statusManager.SetPodStatus(logger, w.pod, podStatus) - w.doProbe(ctx) - containerID := kubecontainer.ParseContainerID(podStatus.ContainerStatuses[0].ContainerID) result, ok := resultsManager(m, tc.probeType).Get(containerID) if ok != tc.expectSet { t.Errorf("Expected result to be set: %v, but got: %v", tc.expectSet, ok) } - if tc.expectSet && result != tc.initialValue { - t.Errorf("Expected result %v, but got: %v", tc.initialValue, result) + if tc.expectSet && result != tc.expectedResult { + t.Errorf("Expected result %v, but got: %v", tc.expectedResult, result) } }) } diff --git a/pkg/kubelet/runtime.go b/pkg/kubelet/runtime.go index 7f9da7f7dbf02..4d6c183238ca5 100644 --- a/pkg/kubelet/runtime.go +++ b/pkg/kubelet/runtime.go @@ -19,6 +19,7 @@ package kubelet import ( "errors" "fmt" + "sort" "sync" "time" @@ -75,7 +76,11 @@ func (s *runtimeState) setRuntimeState(err error) { func (s *runtimeState) setRuntimeHandlers(rtHandlers []kubecontainer.RuntimeHandler) { s.Lock() defer s.Unlock() - s.rtHandlers = rtHandlers + // Copy and sort to ensure deterministic ordering of runtime handlers and avoid spurious Node status updates. + s.rtHandlers = append([]kubecontainer.RuntimeHandler(nil), rtHandlers...) + sort.Slice(s.rtHandlers, func(i, j int) bool { + return s.rtHandlers[i].Name < s.rtHandlers[j].Name + }) } func (s *runtimeState) runtimeHandlers() []kubecontainer.RuntimeHandler { diff --git a/pkg/kubelet/runtime_test.go b/pkg/kubelet/runtime_test.go new file mode 100644 index 0000000000000..d24d409665fb5 --- /dev/null +++ b/pkg/kubelet/runtime_test.go @@ -0,0 +1,80 @@ +/* +Copyright 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 kubelet + +import ( + "reflect" + "testing" + + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +func TestRuntimeStateSetRuntimeHandlersSortsAndCopies(t *testing.T) { + testCases := []struct { + name string + handlers []kubecontainer.RuntimeHandler + expected []string + }{ + { + name: "unsortedWithDefault", + handlers: []kubecontainer.RuntimeHandler{ + {Name: "runc"}, + {Name: ""}, + {Name: "crun"}, + }, + expected: []string{"", "crun", "runc"}, + }, + { + name: "alreadySorted", + handlers: []kubecontainer.RuntimeHandler{ + {Name: ""}, + {Name: "crun"}, + {Name: "runc"}, + }, + expected: []string{"", "crun", "runc"}, + }, + { + name: "emptyHandlers", + handlers: nil, + expected: []string{}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + state := &runtimeState{} + input := append([]kubecontainer.RuntimeHandler(nil), tt.handlers...) + original := append([]kubecontainer.RuntimeHandler(nil), input...) + + state.setRuntimeHandlers(input) + + if !reflect.DeepEqual(input, original) { + t.Fatalf("setRuntimeHandlers mutated input slice: got %#v, want %#v", input, original) + } + + got := state.runtimeHandlers() + if len(got) != len(tt.expected) { + t.Fatalf("unexpected handler count: got %d, want %d", len(got), len(tt.expected)) + } + for i, name := range tt.expected { + if got[i].Name != name { + t.Errorf("unexpected handler order at %d: got %q, want %q", i, got[i].Name, name) + } + } + }) + } +} diff --git a/pkg/kubelet/server/auth.go b/pkg/kubelet/server/auth.go index b5d8255d4ca49..e385a63c11f96 100644 --- a/pkg/kubelet/server/auth.go +++ b/pkg/kubelet/server/auth.go @@ -25,6 +25,7 @@ import ( "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/server/dynamiccertificates" "k8s.io/apiserver/pkg/server/flagz" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/statusz" @@ -42,11 +43,13 @@ type KubeletAuth struct { NodeRequestAttributesGetter // authorizer determines whether a given authorization.Attributes is allowed authorizer.Authorizer + // CAContentProvider returns CA content for client CA authorization + dynamiccertificates.CAContentProvider } // NewKubeletAuth returns a kubelet.AuthInterface composed of the given authenticator, attribute getter, and authorizer -func NewKubeletAuth(authenticator authenticator.Request, authorizerAttributeGetter NodeRequestAttributesGetter, authorizer authorizer.Authorizer) AuthInterface { - return &KubeletAuth{authenticator, authorizerAttributeGetter, authorizer} +func NewKubeletAuth(authenticator authenticator.Request, authorizerAttributeGetter NodeRequestAttributesGetter, authorizer authorizer.Authorizer, clientCAProvider dynamiccertificates.CAContentProvider) AuthInterface { + return &KubeletAuth{authenticator, authorizerAttributeGetter, authorizer, clientCAProvider} } // NewNodeAuthorizerAttributesGetter creates a new authorizer.RequestAttributesGetter for the node. diff --git a/pkg/kubelet/server/auth_test.go b/pkg/kubelet/server/auth_test.go index 27f001f99fa7f..dcb5e7d0a96ae 100644 --- a/pkg/kubelet/server/auth_test.go +++ b/pkg/kubelet/server/auth_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -67,6 +68,9 @@ func TestIsSubPath(t *testing.T) { func TestGetRequestAttributes(t *testing.T) { tCtx := ktesting.Init(t) for _, fineGrained := range []bool{false, true} { + if !fineGrained { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KubeletFineGrainedAuthz, fineGrained) for _, test := range AuthzTestCases(fineGrained) { t.Run(test.Method+":"+test.Path, func(t *testing.T) { diff --git a/pkg/kubelet/server/metrics/metrics.go b/pkg/kubelet/server/metrics/metrics.go index e9a2b5fca761a..7f4f9bd69c138 100644 --- a/pkg/kubelet/server/metrics/metrics.go +++ b/pkg/kubelet/server/metrics/metrics.go @@ -26,6 +26,9 @@ import ( const ( kubeletSubsystem = "kubelet" + + CAdvisorMetricsProvider MetricsProviderType = "cadvisor" + CRIMetricsProvider MetricsProviderType = "cri" ) var ( @@ -76,6 +79,26 @@ var ( }, []string{"metric_source"}, ) + MetricsProvider = metrics.NewGaugeVec( + &metrics.GaugeOpts{ + Subsystem: kubeletSubsystem, + Name: "metrics_provider", + Help: "Metrics provider used by kubelet to collect container stats. Values can be 'cadvisor' and 'cri'", + StabilityLevel: metrics.ALPHA, + }, + []string{"provider"}, + ) + // WebSocketStreamingRequests counts WebSocket streaming requests received by the kubelet, + // labeled by subresource. + WebSocketStreamingRequests = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: kubeletSubsystem, + Name: "websocket_streaming_requests_total", + Help: "Total number of WebSocket streaming requests (exec/attach/portforward) received by the kubelet.", + StabilityLevel: metrics.ALPHA, + }, + []string{"subresource"}, + ) ) var registerMetrics sync.Once @@ -87,9 +110,16 @@ func Register() { legacyregistry.MustRegister(HTTPRequestsDuration) legacyregistry.MustRegister(HTTPInflightRequests) legacyregistry.MustRegister(VolumeStatCalDuration) + legacyregistry.MustRegister(MetricsProvider) + legacyregistry.MustRegister(WebSocketStreamingRequests) }) } +// ResetForTest resets metrics that support reset, for use in unit tests. +func ResetForTest() { + WebSocketStreamingRequests.Reset() +} + // SinceInSeconds gets the time since the specified start in seconds. func SinceInSeconds(start time.Time) float64 { return time.Since(start).Seconds() @@ -99,3 +129,17 @@ func SinceInSeconds(start time.Time) float64 { func CollectVolumeStatCalDuration(metricSource string, start time.Time) { VolumeStatCalDuration.WithLabelValues(metricSource).Observe(SinceInSeconds(start)) } + +type MetricsProviderType string + +// SetMetricsProvider sets the metrics provider. +func SetMetricsProvider(provider MetricsProviderType) { + MetricsProvider.Reset() + MetricsProvider.WithLabelValues(string(provider)).Set(1) +} + +// IncWebSocketStreamingRequest increments the count of WebSocket streaming requests received +// by the kubelet for the given subresource (exec/attach/portforward). +func IncWebSocketStreamingRequest(subresource string) { + WebSocketStreamingRequests.WithLabelValues(subresource).Inc() +} diff --git a/pkg/kubelet/server/metrics/metrics_test.go b/pkg/kubelet/server/metrics/metrics_test.go new file mode 100644 index 0000000000000..cc68afa68f3ed --- /dev/null +++ b/pkg/kubelet/server/metrics/metrics_test.go @@ -0,0 +1,65 @@ +/* +Copyright 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 metrics + +import ( + "strings" + "testing" + + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" +) + +func TestSetMetricsProvider(t *testing.T) { + Register() + MetricsProvider.Reset() + + tests := []struct { + name string + provider MetricsProviderType + want string + }{ + { + name: "cadvisor provider", + provider: CAdvisorMetricsProvider, + want: ` +# HELP kubelet_metrics_provider [ALPHA] Metrics provider used by kubelet to collect container stats. Values can be 'cadvisor' and 'cri' +# TYPE kubelet_metrics_provider gauge +kubelet_metrics_provider{provider="cadvisor"} 1 +`, + }, + { + name: "cri provider", + provider: CRIMetricsProvider, + want: ` +# HELP kubelet_metrics_provider [ALPHA] Metrics provider used by kubelet to collect container stats. Values can be 'cadvisor' and 'cri' +# TYPE kubelet_metrics_provider gauge +kubelet_metrics_provider{provider="cri"} 1 +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetMetricsProvider(tt.provider) + + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tt.want), "kubelet_metrics_provider"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + }) + } +} diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go index 31ecfe6567270..fe96a4ab366ef 100644 --- a/pkg/kubelet/server/server.go +++ b/pkg/kubelet/server/server.go @@ -19,6 +19,7 @@ package server import ( "context" "crypto/tls" + "errors" "fmt" "io" "net" @@ -50,6 +51,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/httpstream/wsstream" "k8s.io/apimachinery/pkg/util/proxy" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -59,11 +61,14 @@ import ( "k8s.io/apiserver/pkg/server/flagz" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/httplog" + + "k8s.io/apiserver/pkg/server/dynamiccertificates" "k8s.io/apiserver/pkg/server/routes" "k8s.io/apiserver/pkg/server/statusz" "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/flushwriter" + translator "k8s.io/apiserver/pkg/util/proxy" "k8s.io/component-base/configz" "k8s.io/component-base/logs" compbasemetrics "k8s.io/component-base/metrics" @@ -73,11 +78,12 @@ import ( zpagesfeatures "k8s.io/component-base/zpages/features" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/cri-client/pkg/util" + "k8s.io/cri-streaming/pkg/streaming" + "k8s.io/cri-streaming/pkg/streaming/portforward" + remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand" podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1" podresourcesapiv1alpha1 "k8s.io/kubelet/pkg/apis/podresources/v1alpha1" - "k8s.io/kubelet/pkg/cri/streaming" - "k8s.io/kubelet/pkg/cri/streaming/portforward" - remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand" + podsv1alpha1 "k8s.io/kubelet/pkg/apis/pods/v1alpha1" kubelettypes "k8s.io/kubelet/pkg/types" "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" @@ -86,6 +92,8 @@ import ( kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" apisgrpc "k8s.io/kubernetes/pkg/kubelet/apis/grpc" "k8s.io/kubernetes/pkg/kubelet/apis/podresources" + "k8s.io/kubernetes/pkg/kubelet/apis/pods" + kubeletcadvisor "k8s.io/kubernetes/pkg/kubelet/cadvisor" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/metrics/collectors" "k8s.io/kubernetes/pkg/kubelet/prober" @@ -130,9 +138,12 @@ type Server struct { // TLSOptions holds the TLS options. type TLSOptions struct { - Config *tls.Config - CertFile string - KeyFile string + MinVersion uint16 + CipherSuites []uint16 + CurvePreferences []tls.CurveID + CertFile string + KeyFile string + ClientCAFile string } // containerInterface defines the restful.Container functions used on the root container @@ -172,7 +183,7 @@ func ListenAndServeKubeletServer( checkers []healthz.HealthChecker, flagz flagz.Reader, kubeCfg *kubeletconfiginternal.KubeletConfiguration, - tlsOptions *TLSOptions, + tlsConfig *tls.Config, auth AuthInterface, tp oteltrace.TracerProvider) { @@ -192,15 +203,14 @@ func ListenAndServeKubeletServer( MaxHeaderBytes: 1 << 20, } - if tlsOptions != nil { - s.TLSConfig = tlsOptions.Config - // Passing empty strings as the cert and key files means no - // cert/keys are specified and GetCertificate in the TLSConfig - // should be called instead. - if err := s.ListenAndServeTLS(tlsOptions.CertFile, tlsOptions.KeyFile); err != nil { + if tlsConfig != nil { + s.TLSConfig = tlsConfig + if err := s.ListenAndServeTLS("", ""); err != nil { logger.Error(err, "Failed to listen and serve") os.Exit(1) } + + // support a hollow node with plain HTTP } else if err := s.ListenAndServe(); err != nil { logger.Error(err, "Failed to listen and serve") os.Exit(1) @@ -258,6 +268,32 @@ func ListenAndServePodResources(ctx context.Context, endpoint string, providers } } +// ListenAndServePodsServer initializes an HTTP server to serve the Pod API. +func ListenAndServePodsServer(ctx context.Context, endpoint string, srv podsv1alpha1.PodsServer) { + logger := klog.FromContext(ctx) + server := grpc.NewServer(apisgrpc.WithRateLimiter(ctx, "pods", pods.DefaultQPS, pods.DefaultBurstTokens)) + + podsv1alpha1.RegisterPodsServer(server, srv) + + l, err := util.CreateListener(endpoint) + if err != nil { + logger.Error(err, "Failed to create listener for pods API endpoint") + os.Exit(1) + } + + logger.Info("Starting to serve the pods API", "endpoint", endpoint) + go func() { + if err := server.Serve(l); err != nil && !errors.Is(err, grpc.ErrServerStopped) { + logger.Error(err, "Failed to serve") + os.Exit(1) + } + }() + + <-ctx.Done() + logger.Info("Shutting down pods API server") + server.GracefulStop() +} + type NodeRequestAttributesGetter interface { GetRequestAttributes(ctx context.Context, u user.Info, r *http.Request) []authorizer.Attributes } @@ -267,6 +303,7 @@ type AuthInterface interface { authenticator.Request NodeRequestAttributesGetter authorizer.Authorizer + dynamiccertificates.CAContentProvider } // HostInterface contains all the kubelet methods required by the server. @@ -315,18 +352,17 @@ func NewServer( server.InstallAuthNotRequiredHandlers(ctx) if kubeCfg != nil && kubeCfg.EnableDebuggingHandlers { logger.Info("Adding debug handlers to kubelet server") - server.InstallAuthRequiredHandlers(ctx) // To maintain backward compatibility serve logs and pprof only when enableDebuggingHandlers is also enabled // see https://github.com/kubernetes/kubernetes/pull/87273 server.InstallSystemLogHandler(kubeCfg.EnableSystemLogHandler, kubeCfg.EnableSystemLogQuery) server.InstallProfilingHandler(kubeCfg.EnableProfilingHandler, kubeCfg.EnableContentionProfiling) server.InstallDebugFlagsHandler(kubeCfg.EnableDebugFlagsHandler) + // must be done last so all paths are included in /statusz + server.InstallAuthRequiredHandlers(ctx) } else { server.InstallDebuggingDisabledHandlers() } - server.installStatusZ() - return server } @@ -390,7 +426,14 @@ func (s *Server) InstallTracingFilter(tp oteltrace.TracerProvider, opts ...otelr s.restfulCont.Filter(otelrestful.OTelFilter("kubelet", append(opts, otelrestful.WithTracerProvider(tp))...)) } -func (s *Server) installStatusZ() { +func (s *Server) InstallZPages() { + if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentFlagz) { + if s.flagz != nil { + s.addMetricsBucketMatcher("flagz") + flagz.Install(s.restfulCont, ComponentKubelet, s.flagz) + } + } + // must be done last so all paths are included in /statusz if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) { s.addMetricsBucketMatcher("statusz") statusz.Install(s.restfulCont, ComponentKubelet, statusz.NewRegistry(compatibility.DefaultBuildEffectiveVersion(), statusz.WithListedPaths(s.restfulCont.RegisteredHandlePaths()))) @@ -471,7 +514,9 @@ func (s *Server) InstallAuthNotRequiredHandlers(ctx context.Context) { } if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPSI) { - includedMetrics[cadvisormetrics.PressureMetrics] = struct{}{} + if kubeletcadvisor.IsPsiEnabled(logger) { + includedMetrics[cadvisormetrics.PressureMetrics] = struct{}{} + } } // cAdvisor metrics are exposed under the secured handler as well @@ -479,6 +524,7 @@ func (s *Server) InstallAuthNotRequiredHandlers(ctx context.Context) { r.RawMustRegister(metrics.NewPrometheusMachineCollector(prometheusHostAdapter{s.host}, includedMetrics)) if utilfeature.DefaultFeatureGate.Enabled(features.PodAndContainerStatsFromCRI) { r.CustomRegister(collectors.NewCRIMetricsCollector(context.TODO(), s.host.ListPodSandboxMetrics, s.host.ListMetricDescriptors)) + servermetrics.SetMetricsProvider(servermetrics.CRIMetricsProvider) } else { cadvisorOpts := cadvisorv2.RequestOptions{ IdType: cadvisorv2.TypeName, @@ -486,6 +532,7 @@ func (s *Server) InstallAuthNotRequiredHandlers(ctx context.Context) { Recursive: true, } r.RawMustRegister(metrics.NewPrometheusCollector(prometheusHostAdapter{s.host}, containerPrometheusLabelsFunc(s.host), includedMetrics, clock.RealClock{}, cadvisorOpts)) + servermetrics.SetMetricsProvider(servermetrics.CAdvisorMetricsProvider) } s.restfulCont.Handle(cadvisorMetricsPath, compbasemetrics.HandlerFor(r, compbasemetrics.HandlerOpts{ErrorHandling: compbasemetrics.ContinueOnError}), @@ -599,13 +646,6 @@ func (s *Server) InstallAuthRequiredHandlers(ctx context.Context) { s.addMetricsBucketMatcher("configz") configz.InstallHandler(s.restfulCont) - if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentFlagz) { - if s.flagz != nil { - s.addMetricsBucketMatcher("flagz") - flagz.Install(s.restfulCont, ComponentKubelet, s.flagz) - } - } - // The /runningpods endpoint is used for testing only. s.addMetricsBucketMatcher("runningpods") ws = new(restful.WebService) @@ -627,6 +667,9 @@ func (s *Server) InstallAuthRequiredHandlers(ctx context.Context) { Operation("checkpoint")) s.restfulCont.Add(ws) } + + // must be done last so all paths are included in /statusz + s.InstallZPages() } // InstallDebuggingDisabledHandlers registers the HTTP request patterns that provide better error message @@ -932,13 +975,6 @@ func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) { http.Error(w, err.Error(), http.StatusInternalServerError) } -// proxyStream proxies stream to url. -func proxyStream(w http.ResponseWriter, r *http.Request, url *url.URL) { - // TODO(random-liu): Set MaxBytesPerSec to throttle the stream. - handler := proxy.NewUpgradeAwareHandler(url, nil /*transport*/, false /*wrapTransport*/, true /*upgradeRequired*/, &responder{}) - handler.ServeHTTP(w, r) -} - // getAttach handles requests to attach to a container. func (s *Server) getAttach(request *restful.Request, response *restful.Response) { params := getExecRequestParams(request) @@ -960,8 +996,20 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response) streaming.WriteError(err, response.ResponseWriter) return } - - proxyStream(response.ResponseWriter, request.Request, url) + var handler http.Handler = proxy.NewUpgradeAwareHandler(url, nil /*transport*/, false /*wrapTransport*/, true /*upgradeRequired*/, &responder{}) + // If upgrade request is for V5 websockets, wrap with websocket/spdy stream translation. + if utilfeature.DefaultFeatureGate.Enabled(features.ExtendWebSocketsToKubelet) && + wsstream.IsWebSocketRequestWithStreamCloseProtocol(request.Request) { + servermetrics.IncWebSocketStreamingRequest("attach") + streamOptions := translator.Options{ + Stdin: streamOpts.Stdin, + Stdout: streamOpts.Stdout, + Stderr: streamOpts.Stderr, + Tty: streamOpts.TTY, + } + handler = translator.NewStreamTranslatorHandler(url, nil, 0, streamOptions) + } + handler.ServeHTTP(response.ResponseWriter, request.Request) } // getExec handles requests to run a command inside a container. @@ -985,7 +1033,20 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) { streaming.WriteError(err, response.ResponseWriter) return } - proxyStream(response.ResponseWriter, request.Request, url) + var handler http.Handler = proxy.NewUpgradeAwareHandler(url, nil /*transport*/, false /*wrapTransport*/, true /*upgradeRequired*/, &responder{}) + // If upgrade request is for V5 websockets, wrap with websocket/spdy stream translation. + if utilfeature.DefaultFeatureGate.Enabled(features.ExtendWebSocketsToKubelet) && + wsstream.IsWebSocketRequestWithStreamCloseProtocol(request.Request) { + servermetrics.IncWebSocketStreamingRequest("exec") + streamOptions := translator.Options{ + Stdin: streamOpts.Stdin, + Stdout: streamOpts.Stdout, + Stderr: streamOpts.Stderr, + Tty: streamOpts.TTY, + } + handler = translator.NewStreamTranslatorHandler(url, nil, 0, streamOptions) + } + handler.ServeHTTP(response.ResponseWriter, request.Request) } // getRun handles requests to run a command inside a container. @@ -1026,12 +1087,21 @@ func writeJSONResponse(logger klog.Logger, response *restful.Response, data []by func (s *Server) getPortForward(request *restful.Request, response *restful.Response) { params := getPortForwardRequestParams(request) - portForwardOptions, err := portforward.NewV4Options(request.Request) - if err != nil { - utilruntime.HandleError(err) - response.WriteError(http.StatusBadRequest, err) - return + websocketSPDYTunnel := utilfeature.DefaultFeatureGate.Enabled(features.ExtendWebSocketsToKubelet) && + wsstream.IsWebSocketRequestWithTunnelingProtocol(request.Request) + var portForwardOptions *portforward.V4Options + var err error + if websocketSPDYTunnel { + portForwardOptions = &portforward.V4Options{} + } else { + portForwardOptions, err = portforward.NewV4Options(request.Request) + if err != nil { + utilruntime.HandleError(err) + response.WriteError(http.StatusBadRequest, err) //nolint:errcheck + return + } } + pod, ok := s.host.GetPodByName(params.podNamespace, params.podName) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) @@ -1047,7 +1117,13 @@ func (s *Server) getPortForward(request *restful.Request, response *restful.Resp streaming.WriteError(err, response.ResponseWriter) return } - proxyStream(response.ResponseWriter, request.Request, url) + var handler http.Handler = proxy.NewUpgradeAwareHandler(url, nil /*transport*/, false /*wrapTransport*/, true /*upgradeRequired*/, &responder{}) + // If upgrade request is for tunneling websockets, wrap with tunneling handling. + if websocketSPDYTunnel { + servermetrics.IncWebSocketStreamingRequest("portforward") + handler = translator.NewTunnelingHandler(handler) + } + handler.ServeHTTP(response.ResponseWriter, request.Request) } // checkpoint handles the checkpoint API request. It checks if the requested @@ -1193,8 +1269,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { servermetrics.HTTPInflightRequests.WithLabelValues(method, path, serverType, longRunning).Inc() defer servermetrics.HTTPInflightRequests.WithLabelValues(method, path, serverType, longRunning).Dec() - startTime := time.Now() - defer servermetrics.HTTPRequestsDuration.WithLabelValues(method, path, serverType, longRunning).Observe(servermetrics.SinceInSeconds(startTime)) + // Use ObserveSince to ensure the duration is calculated when the defer executes, + // not when it's declared. In Go, arguments to deferred functions are evaluated + // immediately at the defer statement, which would cause the metric to record + // near-zero values (~2 microseconds) instead of actual request handling time. + defer servermetrics.HTTPRequestsDuration.ObserveSince(time.Now(), method, path, serverType, longRunning)() handler.ServeHTTP(w, req) } diff --git a/pkg/kubelet/server/server_test.go b/pkg/kubelet/server/server_test.go index 97091722ef537..1875897f01b0b 100644 --- a/pkg/kubelet/server/server_test.go +++ b/pkg/kubelet/server/server_test.go @@ -17,8 +17,11 @@ limitations under the License. package server import ( + "bytes" "context" "crypto/tls" + "crypto/x509" + "encoding/json" "errors" "fmt" "io" @@ -36,39 +39,52 @@ import ( cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapiv2 "github.com/google/cadvisor/info/v2" + gwebsocket "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/component-base/configz" + "k8s.io/kubelet/config/v1beta1" "k8s.io/utils/ptr" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" - "k8s.io/client-go/tools/remotecommand" + remotecommand "k8s.io/client-go/tools/remotecommand" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/spdy" // Do some initialization to decode the query parameters correctly. + "k8s.io/apiserver/pkg/server/dynamiccertificates" "k8s.io/apiserver/pkg/server/flagz" "k8s.io/apiserver/pkg/server/healthz" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" + metricsfeatures "k8s.io/component-base/metrics/features" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" zpagesfeatures "k8s.io/component-base/zpages/features" - "k8s.io/kubelet/pkg/cri/streaming" - "k8s.io/kubelet/pkg/cri/streaming/portforward" - remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand" + "k8s.io/cri-streaming/pkg/streaming" + "k8s.io/cri-streaming/pkg/streaming/portforward" + remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/features" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/cm" + kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics" + servermetrics "k8s.io/kubernetes/pkg/kubelet/server/metrics" "k8s.io/kubernetes/pkg/kubelet/server/stats" "k8s.io/kubernetes/pkg/volume" ) @@ -166,16 +182,16 @@ func (fk *fakeKubelet) SyncLoopHealthCheck(req *http.Request) error { } type fakeRuntime struct { - execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error - attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error + execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommandserver.TerminalSize) error + attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommandserver.TerminalSize) error portForwardFunc func(string, int32, io.ReadWriteCloser) error } -func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { +func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { return f.execFunc(containerID, cmd, stdin, stdout, stderr, tty, resize) } -func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { +func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { return f.attachFunc(containerID, stdin, stdout, stderr, tty, resize) } @@ -271,7 +287,7 @@ func (fk *fakeKubelet) GetPortForward(ctx context.Context, podName, podNamespace } // Unused functions -func (*fakeKubelet) GetNode() (*v1.Node, error) { return nil, nil } +func (*fakeKubelet) GetNode(context.Context) (*v1.Node, error) { return nil, nil } func (*fakeKubelet) GetNodeConfig() cm.NodeConfig { return cm.NodeConfig{} } func (*fakeKubelet) GetPodCgroupRoot() string { return "" } func (*fakeKubelet) GetPodByCgroupfs(cgroupfs string) (*v1.Pod, bool) { return nil, false } @@ -301,9 +317,12 @@ func (*fakeKubelet) GetCgroupCPUAndMemoryStats(cgroupName string, updateStats bo } type fakeAuth struct { - authenticateFunc func(*http.Request) (*authenticator.Response, bool, error) - attributesFunc func(user.Info, *http.Request) []authorizer.Attributes - authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) + authenticateFunc func(*http.Request) (*authenticator.Response, bool, error) + attributesFunc func(user.Info, *http.Request) []authorizer.Attributes + authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) + currentCABundleContent func() []byte + name string + verifyOptions func() (x509.VerifyOptions, bool) } func (f *fakeAuth) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { @@ -315,6 +334,16 @@ func (f *fakeAuth) GetRequestAttributes(ctx context.Context, u user.Info, req *h func (f *fakeAuth) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { return f.authorizeFunc(a) } +func (f *fakeAuth) AddListener(listener dynamiccertificates.Listener) {} +func (f *fakeAuth) CurrentCABundleContent() []byte { + return f.currentCABundleContent() +} +func (f *fakeAuth) Name() string { + return f.name +} +func (f *fakeAuth) VerifyOptions() (x509.VerifyOptions, bool) { + return f.verifyOptions() +} type serverTestFramework struct { serverUnderTest *Server @@ -551,9 +580,72 @@ func TestStatusz(t *testing.T) { t.Fatalf("Got error reading response body: %v", err) } - reg := regexp.MustCompile(`Paths([:=\s]+)/configz /debug /healthz /metrics\n$`) + reg := regexp.MustCompile(`Paths([:=\s]+)/configz /debug /flagz /healthz /metrics\n$`) if reg.FindStringSubmatch(string(resBody)) == nil { - t.Errorf("statusz paths missing: %s\n\nExpected: %q", string(resBody), "Paths /configz /debug /healthz /metrics") + t.Errorf("statusz paths missing: %s\n\nExpected: %q", string(resBody), "Paths /configz /debug /flagz /healthz /metrics") + } +} + +func TestConfigz(t *testing.T) { + tCtx := ktesting.Init(t) + + configz.Delete("kubeletconfig") + cz, err := configz.New("kubeletconfig") + if err != nil { + t.Fatalf("unable to register configz: %v", err) + } + defer configz.Delete("kubeletconfig") + kc := &v1beta1.KubeletConfiguration{} + kc.TypeMeta.APIVersion = "kubelet.config.k8s.io/v1beta1" + kc.TypeMeta.Kind = "KubeletConfiguration" + if err := cz.Set(kc); err != nil { + t.Fatalf("unable to set configz: %v", err) + } + + fw := newServerTest(tCtx) + defer fw.testHTTPServer.Close() + + req, err := http.NewRequest(http.MethodGet, fw.testHTTPServer.URL+"/configz", nil) + if err != nil { + t.Fatalf("Got error creating request: %v", err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer func() { + _ = resp.Body.Close() + }() + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Got error reading response body: %v", err) + } + + var configz map[string]unstructured.Unstructured + if err := json.Unmarshal(body, &configz); err != nil { + t.Fatalf("failed to unmarshal configz: %v", err) + } + cfg, ok := configz["kubeletconfig"] + if !ok { + t.Fatalf("configz missing 'kubeletconfig' key") + } + if cfg.GetAPIVersion() != "kubelet.config.k8s.io/v1beta1" { + t.Errorf("unexpected APIVersion: %s", cfg.GetAPIVersion()) + } + if cfg.GetKind() != "KubeletConfiguration" { + t.Errorf("unexpected Kind: %s", cfg.GetKind()) + } + + // confirm that they expose public config type + var kubeletConfig v1beta1.KubeletConfiguration + err = json.Unmarshal(body, &struct { + ComponentConfig *v1beta1.KubeletConfiguration `json:"kubeletconfig"` + }{ComponentConfig: &kubeletConfig}) + if err != nil { + t.Errorf("failed to deserialize into public config type: %v", err) } } @@ -564,6 +656,9 @@ func TestAuthzCoverage(t *testing.T) { defer fw.testHTTPServer.Close() for _, fineGrained := range []bool{false, true} { + if !fineGrained { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } t.Run(fmt.Sprintf("fineGrained=%v", fineGrained), func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KubeletFineGrainedAuthz, fineGrained) // method:path -> has coverage @@ -686,9 +781,12 @@ func TestAuthFilters(t *testing.T) { attributesGetter := NewNodeAuthorizerAttributesGetter(authzTestNodeName) for _, fineGraned := range []bool{false, true} { + if !fineGraned { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KubeletFineGrainedAuthz, fineGraned) for _, tc := range AuthzTestCases(fineGraned) { - t.Run(fmt.Sprintf("method=%v:path=%v:fineGrained=%v", tc.Method, tc.Method, fineGraned), func(t *testing.T) { + t.Run(fmt.Sprintf("method=%v:path=%v:fineGrained=%v", tc.Method, tc.Path, fineGraned), func(t *testing.T) { var ( expectedUser = AuthzTestUser() @@ -1448,12 +1546,12 @@ func testExecAttach(t *testing.T, verb string) { return nil } - ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { + ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd") return testStream(containerID, stdin, stdout, stderr, tty, done) } - ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { + ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { return testStream(containerID, stdin, stdout, stderr, tty, done) } @@ -1571,6 +1669,63 @@ func TestServeAttachContainer(t *testing.T) { testExecAttach(t, "attach") } +func TestWebsocketExecAttach(t *testing.T) { + tCtx := ktesting.Init(t) + ss, err := newTestStreamingServer(0) + require.NoError(t, err) + defer ss.testHTTPServer.Close() + fw := newServerTestWithDebug(tCtx, true, ss) + defer fw.testHTTPServer.Close() + + podNamespace := "other" + podName := "foo" + expectedContainerName := "baz" + expectedStdin := "stdin" + done := make(chan struct{}) + attachInvoked := false + + fw.fakeKubelet.getAttachCheck = func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) { + attachInvoked = true + } + + ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error { + defer close(done) + defer stdout.Close() //nolint:errcheck + _, err := io.Copy(stdout, stdin) + return err + } + + requestURL := fw.testHTTPServer.URL + "/attach/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?input=1&output=1" + + websocketLocation, err := url.Parse(fw.testHTTPServer.URL) + require.NoError(t, err) + + exec, err := remotecommand.NewWebSocketExecutor(&rest.Config{Host: websocketLocation.Host}, "GET", requestURL) + require.NoError(t, err) + + var stdout bytes.Buffer + options := &remotecommand.StreamOptions{ + Stdin: bytes.NewReader([]byte(expectedStdin)), + Stdout: &stdout, + } + + errorChan := make(chan error) + go func() { + errorChan <- exec.StreamWithContext(context.Background(), *options) + }() + + select { + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("expect stream to be closed after connection is closed.") + case err := <-errorChan: + require.NoError(t, err) + } + + assert.Equal(t, expectedStdin, stdout.String()) + assert.True(t, attachInvoked, "attach should be invoked") + <-done +} + func TestServePortForwardIdleTimeout(t *testing.T) { tCtx := ktesting.Init(t) ss, err := newTestStreamingServer(100 * time.Millisecond) @@ -2014,3 +2169,342 @@ func TestNewServerRegistersMetricsSLIsEndpointTwice(t *testing.T) { assert.Contains(t, server1.restfulCont.RegisteredHandlePaths(), "/metrics/slis", "First server should register /metrics/slis") assert.Contains(t, server2.restfulCont.RegisteredHandlePaths(), "/metrics/slis", "Second server should register /metrics/slis") } + +// This test verifies that the HTTP request duration metric captures the actual +// request handling time. +func TestServeHTTPRequestDurationMetric(t *testing.T) { + tCtx := ktesting.Init(t) + fw := newServerTest(tCtx) + defer fw.testHTTPServer.Close() + + // Register and reset the metric before the test + servermetrics.Register() + servermetrics.HTTPRequestsDuration.Reset() + + // Add a delay to the pods handler to simulate request processing time. + // We use 50ms which is long enough to be clearly distinguishable from + // the ~2 microsecond bug, but short enough to not slow down tests. + handlerDelay := 50 * time.Millisecond + fw.fakeKubelet.podsFunc = func() []*v1.Pod { + time.Sleep(handlerDelay) + return []*v1.Pod{} + } + + // Make a request to the pods endpoint + resp, err := http.Get(fw.testHTTPServer.URL + "/pods/") + require.NoError(t, err) + defer resp.Body.Close() //nolint:errcheck + require.Equal(t, http.StatusOK, resp.StatusCode) + + // Read the body to ensure the request is fully processed + _, err = io.ReadAll(resp.Body) + require.NoError(t, err) + + // Get the recorded duration from the metric + observerMetric := servermetrics.HTTPRequestsDuration.WithLabelValues("GET", "pods", "readwrite", "false") + metricValue, err := testutil.GetHistogramMetricValue(observerMetric) + require.NoError(t, err) + + // Use the handler delay as the minimum expected duration. This avoids any + // timing-sensitive percentage-based checks. + minExpectedDuration := handlerDelay.Seconds() + assert.GreaterOrEqual(t, metricValue, minExpectedDuration, + "HTTP request duration metric recorded %v seconds, expected at least %v seconds. ", + metricValue, minExpectedDuration, + ) +} + +// TestGetExecWebSocketHandlerSelection verifies that getExec selects the +// translating handler (WebSocket v5 ↔ SPDY) when ExtendWebSocketsToKubelet +// is enabled and a v5 request arrives, and the plain UpgradeAwareHandler +// otherwise. +// +// Observable difference: the translating handler accepts v5 WebSocket directly +// and echoes "v5.channel.k8s.io" back in the 101 response. Without it, the +// kubelet proxies the v5 request to the streaming server whose wsstream layer +// only lists v4/legacy subprotocols and rejects the v5 upgrade entirely +// (gorilla receives a non-101 response → "bad handshake"). +// When the client uses v4, the UpgradeAwareHandler proxies the upgrade to the +// streaming server which accepts v4, so the connection succeeds even when the +// gate is enabled. +func TestGetExecWebSocketHandlerSelection(t *testing.T) { + tests := []struct { + name string + enableExtendWebSockets bool + subprotocols []string // defaults to v5 if nil + expectDialError bool + expectedSubprotocol string + expectMetricInc bool + }{ + { + name: "feature gate enabled uses translating handler, v5 negotiated", + enableExtendWebSockets: true, + expectDialError: false, + expectedSubprotocol: "v5.channel.k8s.io", + expectMetricInc: true, + }, + { + name: "feature gate disabled uses UpgradeAwareHandler, v5 rejected", + enableExtendWebSockets: false, + expectDialError: true, + }, + { + // When the gate is enabled but the request is not v5, IsWebSocketRequestWithStreamCloseProtocol + // returns false so getExec falls through to the plain UpgradeAwareHandler, which proxies the + // request to the streaming server. The streaming server understands v4 and accepts it. + name: "feature gate enabled, v4 request uses UpgradeAwareHandler, v4 negotiated", + enableExtendWebSockets: true, + subprotocols: []string{"v4.channel.k8s.io"}, + expectDialError: false, + expectedSubprotocol: "v4.channel.k8s.io", + }, + } + + servermetrics.Register() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + servermetrics.ResetForTest() + tCtx := ktesting.Init(t) + ss, err := newTestStreamingServer(0) + require.NoError(t, err) + defer ss.testHTTPServer.Close() + fw := newServerTestWithDebug(tCtx, true, ss) + defer fw.testHTTPServer.Close() + + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableExtendWebSockets) + + ss.fakeRuntime.execFunc = func(_ string, _ []string, _ io.Reader, stdout, _ io.WriteCloser, _ bool, _ <-chan remotecommandserver.TerminalSize) error { + stdout.Close() //nolint:errcheck + return nil + } + + execURL := strings.Replace(fw.testHTTPServer.URL, "http://", "ws://", 1) + + "/exec/default/foo/baz?command=echo&output=1" + + subprotocols := tt.subprotocols + if len(subprotocols) == 0 { + subprotocols = []string{"v5.channel.k8s.io"} + } + dialer := gwebsocket.Dialer{ + Subprotocols: subprotocols, + } + conn, resp, err := dialer.Dial(execURL, nil) + if tt.expectDialError { + require.Error(t, err, "expected dial to fail when feature gate is disabled") + return + } + require.NoError(t, err) + defer conn.Close() //nolint:errcheck + + assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode) + assert.Equal(t, tt.expectedSubprotocol, conn.Subprotocol()) + + if tt.expectMetricInc { + expected := ` +# HELP kubelet_websocket_streaming_requests_total [ALPHA] Total number of WebSocket streaming requests (exec/attach/portforward) received by the kubelet. +# TYPE kubelet_websocket_streaming_requests_total counter +kubelet_websocket_streaming_requests_total{subresource="exec"} 1 +` + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expected), + "kubelet_websocket_streaming_requests_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + } + }) + } +} + +// TestGetAttachWebSocketHandlerSelection verifies that getAttach selects the +// translating handler when ExtendWebSocketsToKubelet is enabled and a v5 +// request arrives, analogous to TestGetExecWebSocketHandlerSelection. +func TestGetAttachWebSocketHandlerSelection(t *testing.T) { + tests := []struct { + name string + enableExtendWebSockets bool + subprotocols []string // defaults to v5 if nil + expectDialError bool + expectedSubprotocol string + expectMetricInc bool + }{ + { + name: "feature gate enabled uses translating handler, v5 negotiated", + enableExtendWebSockets: true, + expectDialError: false, + expectedSubprotocol: "v5.channel.k8s.io", + expectMetricInc: true, + }, + { + name: "feature gate disabled uses UpgradeAwareHandler, v5 rejected", + enableExtendWebSockets: false, + expectDialError: true, + }, + { + // When the gate is enabled but the request is not v5, IsWebSocketRequestWithStreamCloseProtocol + // returns false so getAttach falls through to the plain UpgradeAwareHandler, which proxies the + // request to the streaming server. The streaming server understands v4 and accepts it. + name: "feature gate enabled, v4 request uses UpgradeAwareHandler, v4 negotiated", + enableExtendWebSockets: true, + subprotocols: []string{"v4.channel.k8s.io"}, + expectDialError: false, + expectedSubprotocol: "v4.channel.k8s.io", + }, + } + + servermetrics.Register() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + servermetrics.ResetForTest() + tCtx := ktesting.Init(t) + ss, err := newTestStreamingServer(0) + require.NoError(t, err) + defer ss.testHTTPServer.Close() + fw := newServerTestWithDebug(tCtx, true, ss) + defer fw.testHTTPServer.Close() + + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableExtendWebSockets) + + ss.fakeRuntime.attachFunc = func(_ string, _ io.Reader, stdout, _ io.WriteCloser, _ bool, _ <-chan remotecommandserver.TerminalSize) error { + stdout.Close() //nolint:errcheck + return nil + } + + attachURL := strings.Replace(fw.testHTTPServer.URL, "http://", "ws://", 1) + + "/attach/default/foo/baz?output=1" + + subprotocols := tt.subprotocols + if len(subprotocols) == 0 { + subprotocols = []string{"v5.channel.k8s.io"} + } + dialer := gwebsocket.Dialer{ + Subprotocols: subprotocols, + } + conn, resp, err := dialer.Dial(attachURL, nil) + if tt.expectDialError { + require.Error(t, err, "expected dial to fail when feature gate is disabled") + return + } + require.NoError(t, err) + defer conn.Close() //nolint:errcheck + + assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode) + assert.Equal(t, tt.expectedSubprotocol, conn.Subprotocol()) + + if tt.expectMetricInc { + expected := ` +# HELP kubelet_websocket_streaming_requests_total [ALPHA] Total number of WebSocket streaming requests (exec/attach/portforward) received by the kubelet. +# TYPE kubelet_websocket_streaming_requests_total counter +kubelet_websocket_streaming_requests_total{subresource="attach"} 1 +` + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expected), + "kubelet_websocket_streaming_requests_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + } + }) + } +} + +// TestGetPortForwardWebSocketHandlerSelection verifies that getPortForward +// selects the tunneling handler when ExtendWebSocketsToKubelet is enabled and +// the client sends the WebSocket tunneling subprotocol ("SPDY/3.1+portforward.k8s.io"), +// and the plain UpgradeAwareHandler otherwise. +// +// When the gate is enabled, getPortForward skips portforward.NewV4Options (which +// would reject a request with no port parameter) and wraps the proxy with a +// TunnelingHandler that translates the WebSocket tunneling protocol to SPDY. +// The streaming server speaks SPDY portforward and responds with 101; the +// tunneling handler translates that back to a WebSocket 101 with the tunneling +// subprotocol. +// +// When the gate is disabled, portforward.NewV4Options is called on the WebSocket +// request, which has no port query parameter, causing a 400 Bad Request before +// the WebSocket upgrade is attempted. +func TestGetPortForwardWebSocketHandlerSelection(t *testing.T) { + tests := []struct { + name string + enableExtendWebSockets bool + expectDialError bool + expectedSubprotocol string + expectMetricInc bool + }{ + { + name: "feature gate enabled uses tunneling handler, tunneling subprotocol negotiated", + enableExtendWebSockets: true, + expectDialError: false, + expectedSubprotocol: "SPDY/3.1+portforward.k8s.io", + expectMetricInc: true, + }, + { + name: "feature gate disabled rejects tunneling request (no port parameter)", + enableExtendWebSockets: false, + expectDialError: true, + }, + } + + servermetrics.Register() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + servermetrics.ResetForTest() + tCtx := ktesting.Init(t) + ss, err := newTestStreamingServer(0) + require.NoError(t, err) + defer ss.testHTTPServer.Close() + fw := newServerTestWithDebug(tCtx, true, ss) + defer fw.testHTTPServer.Close() + + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableExtendWebSockets) + + portForwardURL := strings.Replace(fw.testHTTPServer.URL, "http://", "ws://", 1) + + "/portForward/default/foo" + + dialer := gwebsocket.Dialer{ + Subprotocols: []string{"SPDY/3.1+portforward.k8s.io"}, + } + conn, resp, err := dialer.Dial(portForwardURL, nil) + if tt.expectDialError { + require.Error(t, err, "expected dial to fail when feature gate is disabled") + return + } + require.NoError(t, err) + defer conn.Close() //nolint:errcheck + + assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode) + assert.Equal(t, tt.expectedSubprotocol, conn.Subprotocol()) + + if tt.expectMetricInc { + expected := ` +# HELP kubelet_websocket_streaming_requests_total [ALPHA] Total number of WebSocket streaming requests (exec/attach/portforward) received by the kubelet. +# TYPE kubelet_websocket_streaming_requests_total counter +kubelet_websocket_streaming_requests_total{subresource="portforward"} 1 +` + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expected), + "kubelet_websocket_streaming_requests_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + } + }) + } +} + +func TestKubeletNativeHistogramMetrics(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, metricsfeatures.NativeHistograms, true) + metricsfeatures.ApplyFeatureGates(utilfeature.DefaultFeatureGate) + kubeletmetrics.Register() + + tCtx := ktesting.Init(t) + fw := newServerTest(tCtx) + defer fw.testHTTPServer.Close() + + histogramMetric := "kubelet_pod_start_duration_seconds" + metrics, err := testutil.ScrapeMetricsProto(fw.testHTTPServer.URL+"/metrics", fw.testHTTPServer.Client()) + if err != nil { + t.Fatalf("failed to scrape metrics: %v", err) + } + + mf, ok := metrics[histogramMetric] + if !ok { + t.Fatalf("metric %q not found in kubelet metrics endpoint", histogramMetric) + } + + testutil.AssertHasNativeHistogram(t, mf, nil) +} diff --git a/pkg/kubelet/server/server_websocket_test.go b/pkg/kubelet/server/server_websocket_test.go index 3a32091299f0c..800d469463df3 100644 --- a/pkg/kubelet/server/server_websocket_test.go +++ b/pkg/kubelet/server/server_websocket_test.go @@ -29,7 +29,7 @@ import ( "golang.org/x/net/websocket" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubelet/pkg/cri/streaming/portforward" + "k8s.io/cri-streaming/pkg/streaming/portforward" "k8s.io/kubernetes/test/utils/ktesting" ) diff --git a/pkg/kubelet/server/stats/handler.go b/pkg/kubelet/server/stats/handler.go index 61b7576c9f828..1ac76920c9e76 100644 --- a/pkg/kubelet/server/stats/handler.go +++ b/pkg/kubelet/server/stats/handler.go @@ -79,7 +79,7 @@ type Provider interface { // namespace. GetPodByName(namespace, name string) (*v1.Pod, bool) // GetNode returns the spec of the local node. - GetNode() (*v1.Node, error) + GetNode(context.Context) (*v1.Node, error) // GetNodeConfig returns the configuration of the local node. GetNodeConfig() cm.NodeConfig // ListVolumesForPod returns the stats of the volume used by the pod with diff --git a/pkg/kubelet/server/stats/summary.go b/pkg/kubelet/server/stats/summary.go index d371b7042c314..35b95754feedb 100644 --- a/pkg/kubelet/server/stats/summary.go +++ b/pkg/kubelet/server/stats/summary.go @@ -72,7 +72,7 @@ func NewSummaryProvider(ctx context.Context, statsProvider Provider) SummaryProv func (sp *summaryProviderImpl) Get(ctx context.Context, updateStats bool) (*statsapi.Summary, error) { // TODO(timstclair): Consider returning a best-effort response if any of // the following errors occur. - node, err := sp.provider.GetNode() + node, err := sp.provider.GetNode(ctx) if err != nil { return nil, fmt.Errorf("failed to get node info: %v", err) } @@ -129,7 +129,7 @@ func (sp *summaryProviderImpl) Get(ctx context.Context, updateStats bool) (*stat func (sp *summaryProviderImpl) GetCPUAndMemoryStats(ctx context.Context) (*statsapi.Summary, error) { // TODO(timstclair): Consider returning a best-effort response if any of // the following errors occur. - node, err := sp.provider.GetNode() + node, err := sp.provider.GetNode(ctx) if err != nil { return nil, fmt.Errorf("failed to get node info: %v", err) } diff --git a/pkg/kubelet/server/stats/summary_sys_containers.go b/pkg/kubelet/server/stats/summary_sys_containers.go index 8ec6ca1f30b2a..10fe13cd9f9a7 100644 --- a/pkg/kubelet/server/stats/summary_sys_containers.go +++ b/pkg/kubelet/server/stats/summary_sys_containers.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/server/stats/summary_sys_containers_windows.go b/pkg/kubelet/server/stats/summary_sys_containers_windows.go index ac6fae1f42c89..ea07f41542bb0 100644 --- a/pkg/kubelet/server/stats/summary_sys_containers_windows.go +++ b/pkg/kubelet/server/stats/summary_sys_containers_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/server/stats/summary_test.go b/pkg/kubelet/server/stats/summary_test.go index bbca2fa501f08..30aa1b8a68e66 100644 --- a/pkg/kubelet/server/stats/summary_test.go +++ b/pkg/kubelet/server/stats/summary_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2016 The Kubernetes Authors. @@ -77,7 +76,7 @@ func TestSummaryProviderGetStatsNoSplitFileSystem(t *testing.T) { mockStatsProvider := statstest.NewMockProvider(t) - mockStatsProvider.EXPECT().GetNode().Return(node, nil) + mockStatsProvider.EXPECT().GetNode(ctx).Return(node, nil) mockStatsProvider.EXPECT().GetNodeConfig().Return(nodeConfig) mockStatsProvider.EXPECT().GetPodCgroupRoot().Return(cgroupRoot) mockStatsProvider.EXPECT().ListPodStats(ctx).Return(podStats, nil).Maybe() @@ -178,7 +177,7 @@ func TestSummaryProviderGetStatsSplitImageFs(t *testing.T) { mockStatsProvider := statstest.NewMockProvider(t) - mockStatsProvider.EXPECT().GetNode().Return(node, nil) + mockStatsProvider.EXPECT().GetNode(ctx).Return(node, nil) mockStatsProvider.EXPECT().GetNodeConfig().Return(nodeConfig) mockStatsProvider.EXPECT().GetPodCgroupRoot().Return(cgroupRoot) mockStatsProvider.EXPECT().ListPodStats(ctx).Return(podStats, nil).Maybe() @@ -278,7 +277,7 @@ func TestSummaryProviderGetCPUAndMemoryStats(t *testing.T) { mockStatsProvider := statstest.NewMockProvider(t) - mockStatsProvider.EXPECT().GetNode().Return(node, nil) + mockStatsProvider.EXPECT().GetNode(ctx).Return(node, nil) mockStatsProvider.EXPECT().GetNodeConfig().Return(nodeConfig) mockStatsProvider.EXPECT().GetPodCgroupRoot().Return(cgroupRoot) mockStatsProvider.EXPECT().ListPodCPUAndMemoryStats(ctx).Return(podStats, nil) diff --git a/pkg/kubelet/server/stats/summary_windows_test.go b/pkg/kubelet/server/stats/summary_windows_test.go index db88528b5cad4..07d9c02c0e340 100644 --- a/pkg/kubelet/server/stats/summary_windows_test.go +++ b/pkg/kubelet/server/stats/summary_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. @@ -55,7 +54,7 @@ func TestSummaryProvider(t *testing.T) { assert := assert.New(t) mockStatsProvider := statstest.NewMockProvider(t) - mockStatsProvider.EXPECT().GetNode().Return(node, nil).Maybe() + mockStatsProvider.EXPECT().GetNode(ctx).Return(node, nil).Maybe() mockStatsProvider.EXPECT().GetNodeConfig().Return(nodeConfig).Maybe() mockStatsProvider.EXPECT().GetPodCgroupRoot().Return(cgroupRoot).Maybe() mockStatsProvider.EXPECT().ListPodStats(ctx).Return(podStats, nil).Maybe() @@ -71,23 +70,23 @@ func TestSummaryProvider(t *testing.T) { summary, err := provider.Get(ctx, true) assert.NoError(err) - assert.Equal(summary.Node.NodeName, "test-node") - assert.Equal(summary.Node.StartTime, systemBootTime) - assert.Equal(summary.Node.CPU, cgroupStatsMap["/"].cs.CPU) - assert.Equal(summary.Node.Memory, cgroupStatsMap["/"].cs.Memory) - assert.Equal(summary.Node.Network, cgroupStatsMap["/"].ns) - assert.Equal(summary.Node.Fs, rootFsStats) - assert.Equal(summary.Node.Runtime, &statsapi.RuntimeStats{ContainerFs: imageFsStats, ImageFs: imageFsStats}) + assert.Equal("test-node", summary.Node.NodeName) + assert.Equal(systemBootTime, summary.Node.StartTime) + assert.Equal(cgroupStatsMap["/"].cs.CPU, summary.Node.CPU) + assert.Equal(cgroupStatsMap["/"].cs.Memory, summary.Node.Memory) + assert.Equal(cgroupStatsMap["/"].ns, summary.Node.Network) + assert.Equal(rootFsStats, summary.Node.Fs) + assert.Equal(&statsapi.RuntimeStats{ContainerFs: imageFsStats, ImageFs: imageFsStats}, summary.Node.Runtime) assert.NoError(err) - assert.Equal(len(summary.Node.SystemContainers), 2) - assert.Equal(summary.Node.SystemContainers[0].Name, "pods") - assert.Equal(summary.Node.SystemContainers[0].CPU.UsageCoreNanoSeconds, podStats[0].CPU.UsageCoreNanoSeconds) - assert.Equal(summary.Node.SystemContainers[0].CPU.UsageNanoCores, podStats[0].CPU.UsageNanoCores) - assert.Equal(summary.Node.SystemContainers[0].Memory.WorkingSetBytes, podStats[0].Memory.WorkingSetBytes) - assert.Equal(summary.Node.SystemContainers[0].Memory.UsageBytes, podStats[0].Memory.UsageBytes) - assert.Equal(summary.Node.SystemContainers[0].Memory.AvailableBytes, podStats[0].Memory.AvailableBytes) - assert.Equal(summary.Node.SystemContainers[1].Name, statsapi.SystemContainerWindowsGlobalCommitMemory) + assert.Len(summary.Node.SystemContainers, 2) + assert.Equal("pods", summary.Node.SystemContainers[0].Name) + assert.Equal(podStats[0].CPU.UsageCoreNanoSeconds, summary.Node.SystemContainers[0].CPU.UsageCoreNanoSeconds) + assert.Equal(podStats[0].CPU.UsageNanoCores, summary.Node.SystemContainers[0].CPU.UsageNanoCores) + assert.Equal(podStats[0].Memory.WorkingSetBytes, summary.Node.SystemContainers[0].Memory.WorkingSetBytes) + assert.Equal(podStats[0].Memory.UsageBytes, summary.Node.SystemContainers[0].Memory.UsageBytes) + assert.Equal(podStats[0].Memory.AvailableBytes, summary.Node.SystemContainers[0].Memory.AvailableBytes) + assert.Equal(statsapi.SystemContainerWindowsGlobalCommitMemory, summary.Node.SystemContainers[1].Name) assert.NotEqual(nil, summary.Node.SystemContainers[1].Memory) assert.NotEqual(nil, summary.Node.SystemContainers[1].Memory.AvailableBytes) assert.NotEqual(nil, summary.Node.SystemContainers[1].Memory.UsageBytes) diff --git a/pkg/kubelet/server/stats/testing/mocks.go b/pkg/kubelet/server/stats/testing/mocks.go index f1742d3ca6d9a..de7a0608b8a88 100644 --- a/pkg/kubelet/server/stats/testing/mocks.go +++ b/pkg/kubelet/server/stats/testing/mocks.go @@ -205,8 +205,8 @@ func (_c *MockProvider_GetCgroupStats_Call) RunAndReturn(run func(cgroupName str } // GetNode provides a mock function for the type MockProvider -func (_mock *MockProvider) GetNode() (*v1.Node, error) { - ret := _mock.Called() +func (_mock *MockProvider) GetNode(context1 context.Context) (*v1.Node, error) { + ret := _mock.Called(context1) if len(ret) == 0 { panic("no return value specified for GetNode") @@ -214,18 +214,18 @@ func (_mock *MockProvider) GetNode() (*v1.Node, error) { var r0 *v1.Node var r1 error - if returnFunc, ok := ret.Get(0).(func() (*v1.Node, error)); ok { - return returnFunc() + if returnFunc, ok := ret.Get(0).(func(context.Context) (*v1.Node, error)); ok { + return returnFunc(context1) } - if returnFunc, ok := ret.Get(0).(func() *v1.Node); ok { - r0 = returnFunc() + if returnFunc, ok := ret.Get(0).(func(context.Context) *v1.Node); ok { + r0 = returnFunc(context1) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*v1.Node) } } - if returnFunc, ok := ret.Get(1).(func() error); ok { - r1 = returnFunc() + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(context1) } else { r1 = ret.Error(1) } @@ -238,13 +238,20 @@ type MockProvider_GetNode_Call struct { } // GetNode is a helper method to define mock.On call -func (_e *MockProvider_Expecter) GetNode() *MockProvider_GetNode_Call { - return &MockProvider_GetNode_Call{Call: _e.mock.On("GetNode")} +// - context1 context.Context +func (_e *MockProvider_Expecter) GetNode(context1 interface{}) *MockProvider_GetNode_Call { + return &MockProvider_GetNode_Call{Call: _e.mock.On("GetNode", context1)} } -func (_c *MockProvider_GetNode_Call) Run(run func()) *MockProvider_GetNode_Call { +func (_c *MockProvider_GetNode_Call) Run(run func(context1 context.Context)) *MockProvider_GetNode_Call { _c.Call.Run(func(args mock.Arguments) { - run() + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) }) return _c } @@ -254,7 +261,7 @@ func (_c *MockProvider_GetNode_Call) Return(node *v1.Node, err error) *MockProvi return _c } -func (_c *MockProvider_GetNode_Call) RunAndReturn(run func() (*v1.Node, error)) *MockProvider_GetNode_Call { +func (_c *MockProvider_GetNode_Call) RunAndReturn(run func(context1 context.Context) (*v1.Node, error)) *MockProvider_GetNode_Call { _c.Call.Return(run) return _c } diff --git a/pkg/kubelet/stats/cri_stats_provider.go b/pkg/kubelet/stats/cri_stats_provider.go index a2f2d2fd368eb..14f2fb3171c72 100644 --- a/pkg/kubelet/stats/cri_stats_provider.go +++ b/pkg/kubelet/stats/cri_stats_provider.go @@ -165,10 +165,7 @@ func (p *criStatsProvider) listPodStats(ctx context.Context, updateCPUNanoCoreUs return nil, err } // CRI implementation doesn't support ListPodSandboxStats, warn and fallback. - logger.V(5).Info( - "CRI implementation must be updated to support ListPodSandboxStats if PodAndContainerStatsFromCRI feature gate is enabled. Falling back to populating with cAdvisor; this call will fail in the future.", - "err", err, - ) + logger.V(5).Info("CRI implementation must be updated to support ListPodSandboxStats if PodAndContainerStatsFromCRI feature gate is enabled. Falling back to populating with cAdvisor; this call will fail in the future.", "err", err) } return p.listPodStatsPartiallyFromCRI(ctx, updateCPUNanoCoreUsage, containerMap, podSandboxMap, &rootFsInfo) } @@ -288,6 +285,7 @@ func (p *criStatsProvider) listPodStatsStrictlyFromCRI(ctx context.Context, upda } func (p *criStatsProvider) PodCPUAndMemoryStats(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus) (*statsapi.PodStats, error) { + logger := klog.FromContext(ctx) if len(podStatus.SandboxStatuses) == 0 { return nil, fmt.Errorf("missing sandbox for pod %s", format.Pod(pod)) } @@ -311,7 +309,7 @@ func (p *criStatsProvider) PodCPUAndMemoryStats(ctx context.Context, pod *v1.Pod return nil, err } // CRI implementation doesn't support PodSandboxStats, warn and fallback. - klog.ErrorS(err, + logger.Error(err, "CRI implementation must be updated to support PodSandboxStats if PodAndContainerStatsFromCRI feature gate is enabled. Falling back to populating with cAdvisor; this call will fail in the future.", ) } else { @@ -332,7 +330,7 @@ func (p *criStatsProvider) PodCPUAndMemoryStats(ctx context.Context, pod *v1.Pod for _, stats := range resp { containerStatus := podStatus.FindContainerStatusByName(stats.Attributes.Metadata.Name) if containerStatus == nil { - klog.V(4).InfoS("Received stats for unknown container", "pod", klog.KObj(pod), "container", stats.Attributes.Metadata) + logger.V(4).Info("Received stats for unknown container", "pod", klog.KObj(pod), "container", stats.Attributes.Metadata) continue } @@ -399,8 +397,12 @@ func (p *criStatsProvider) ListPodCPUAndMemoryStats(ctx context.Context) ([]stat continue } ps := buildPodStats(podSandbox) + // Add container-level CPU and memory stats from CRI + p.addCRIPodContainerCPUAndMemoryStats(criSandboxStat, ps, containerMap) addCRIPodCPUStats(ps, criSandboxStat) addCRIPodMemoryStats(ps, criSandboxStat) + // Aggregate pod swap from container swap stats (CRI doesn't have pod-level swap) + aggregatePodSwapStats(ps) result = append(result, *ps) } return result, err @@ -700,6 +702,33 @@ func (p *criStatsProvider) addSwapStats( } } +// aggregatePodSwapStats aggregates pod-level swap stats from container swap stats. +// This is used when CRI doesn't provide pod-level swap stats (e.g., LinuxPodSandboxStats doesn't have a Swap field). +func aggregatePodSwapStats(ps *statsapi.PodStats) { + if len(ps.Containers) == 0 { + return + } + var swapAvailableBytes, swapUsageBytes uint64 + var hasSwapStats bool + var swapTime metav1.Time + for _, cs := range ps.Containers { + if cs.Swap != nil { + hasSwapStats = true + // TODO: Consider picking the newest time across containers instead of just using the last one. + swapTime = cs.Swap.Time + swapAvailableBytes += ptr.Deref(cs.Swap.SwapAvailableBytes, 0) + swapUsageBytes += ptr.Deref(cs.Swap.SwapUsageBytes, 0) + } + } + if hasSwapStats { + ps.Swap = &statsapi.SwapStats{ + Time: swapTime, + SwapAvailableBytes: &swapAvailableBytes, + SwapUsageBytes: &swapUsageBytes, + } + } +} + func (p *criStatsProvider) addIOStats( ps *statsapi.PodStats, podUID types.UID, @@ -783,6 +812,15 @@ func (p *criStatsProvider) makeContainerStats( if stats.Memory.RssBytes != nil { result.Memory.RSSBytes = &stats.Memory.RssBytes.Value } + if stats.Memory.AvailableBytes != nil { + result.Memory.AvailableBytes = &stats.Memory.AvailableBytes.Value + } + if stats.Memory.PageFaults != nil { + result.Memory.PageFaults = &stats.Memory.PageFaults.Value + } + if stats.Memory.MajorPageFaults != nil { + result.Memory.MajorPageFaults = &stats.Memory.MajorPageFaults.Value + } result.Memory.PSI = makePSIStats(stats.Memory.Psi) } else { result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano())) @@ -1157,6 +1195,11 @@ func (p *criStatsProvider) addCadvisorContainerCPUAndMemoryStats( if memory != nil { cs.Memory = memory } + + swap := cadvisorInfoToSwapStats(caPodStats) + if swap != nil { + cs.Swap = swap + } } func getCRICadvisorStats(logger klog.Logger, infos map[string]cadvisorapiv2.ContainerInfo) (map[string]cadvisorapiv2.ContainerInfo, map[string]cadvisorapiv2.ContainerInfo) { diff --git a/pkg/kubelet/stats/cri_stats_provider_linux.go b/pkg/kubelet/stats/cri_stats_provider_linux.go index 576df556de56e..81dc9c76cdb98 100644 --- a/pkg/kubelet/stats/cri_stats_provider_linux.go +++ b/pkg/kubelet/stats/cri_stats_provider_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2023 The Kubernetes Authors. @@ -57,6 +56,27 @@ func (p *criStatsProvider) addCRIPodContainerStats( return nil } +// addCRIPodContainerCPUAndMemoryStats adds container CPU and memory stats from CRI to the PodStats. +// This is a lighter-weight version of addCRIPodContainerStats that only populates CPU and memory +// stats needed for the resource metrics endpoint. +func (p *criStatsProvider) addCRIPodContainerCPUAndMemoryStats( + criSandboxStat *runtimeapi.PodSandboxStats, + ps *statsapi.PodStats, + containerMap map[string]*runtimeapi.Container) { + if criSandboxStat == nil || criSandboxStat.Linux == nil { + return + } + for _, criContainerStat := range criSandboxStat.Linux.Containers { + container, found := containerMap[criContainerStat.Attributes.Id] + if !found { + continue + } + // Fill available CPU and memory stats for resource metrics + cs := p.makeContainerCPUAndMemoryStats(criContainerStat, time.Unix(0, container.CreatedAt), true) + ps.Containers = append(ps.Containers, *cs) + } +} + func addCRIPodNetworkStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandboxStats) { if criPodStat == nil || criPodStat.Linux == nil || criPodStat.Linux.Network == nil { return diff --git a/pkg/kubelet/stats/cri_stats_provider_others.go b/pkg/kubelet/stats/cri_stats_provider_others.go index f8188fb48329b..4d075114db430 100644 --- a/pkg/kubelet/stats/cri_stats_provider_others.go +++ b/pkg/kubelet/stats/cri_stats_provider_others.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2019 The Kubernetes Authors. @@ -59,3 +58,9 @@ func addCRIPodProcessStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSand func addCRIPodIOStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandboxStats) { } + +func (p *criStatsProvider) addCRIPodContainerCPUAndMemoryStats( + *runtimeapi.PodSandboxStats, + *statsapi.PodStats, + map[string]*runtimeapi.Container) { +} diff --git a/pkg/kubelet/stats/cri_stats_provider_test.go b/pkg/kubelet/stats/cri_stats_provider_test.go index b1deca169c154..eda49127a32b3 100644 --- a/pkg/kubelet/stats/cri_stats_provider_test.go +++ b/pkg/kubelet/stats/cri_stats_provider_test.go @@ -688,6 +688,7 @@ func TestCRIListPodCPUAndMemoryStats(t *testing.T) { c0 := containerStatsMap[cName0] assert.Equal(container0.CreatedAt, c0.StartTime.UnixNano()) checkCRICPUAndMemoryStats(assert, c0, infos[container0.ContainerStatus.Id].Stats[0]) + checkSwapStats(t, cName0, seedContainer0, infos[container0.ContainerStatus.Id], c0.Swap) assert.Nil(c0.Rootfs) assert.Nil(c0.Logs) assert.Nil(c0.Accelerators) @@ -696,6 +697,7 @@ func TestCRIListPodCPUAndMemoryStats(t *testing.T) { c1 := containerStatsMap[cName1] assert.Equal(container1.CreatedAt, c1.StartTime.UnixNano()) checkCRICPUAndMemoryStats(assert, c1, infos[container1.ContainerStatus.Id].Stats[0]) + checkSwapStats(t, cName1, seedContainer1, infos[container1.ContainerStatus.Id], c1.Swap) assert.Nil(c1.Rootfs) assert.Nil(c1.Logs) assert.Nil(c1.Accelerators) @@ -715,6 +717,7 @@ func TestCRIListPodCPUAndMemoryStats(t *testing.T) { assert.Equal(cName2, c2.Name) assert.Equal(container2.CreatedAt, c2.StartTime.UnixNano()) checkCRICPUAndMemoryStats(assert, c2, infos[container2.ContainerStatus.Id].Stats[0]) + checkSwapStats(t, cName2, seedContainer2, infos[container2.ContainerStatus.Id], c2.Swap) assert.Nil(c2.Rootfs) assert.Nil(c2.Logs) assert.Nil(c2.Accelerators) @@ -734,6 +737,7 @@ func TestCRIListPodCPUAndMemoryStats(t *testing.T) { assert.Equal(cName3, c3.Name) assert.Equal(container4.CreatedAt, c3.StartTime.UnixNano()) checkCRICPUAndMemoryStats(assert, c3, infos[container4.ContainerStatus.Id].Stats[0]) + checkSwapStats(t, cName3, seedContainer3, infos[container4.ContainerStatus.Id], c3.Swap) assert.Nil(c2.Rootfs) assert.Nil(c2.Logs) assert.Nil(c2.Accelerators) diff --git a/pkg/kubelet/stats/cri_stats_provider_windows.go b/pkg/kubelet/stats/cri_stats_provider_windows.go index 367a46516b827..48fad4582a1b7 100644 --- a/pkg/kubelet/stats/cri_stats_provider_windows.go +++ b/pkg/kubelet/stats/cri_stats_provider_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2019 The Kubernetes Authors. @@ -146,11 +145,15 @@ func (p *criStatsProvider) makeWinContainerStats( if stats.Memory.PageFaults != nil { result.Memory.PageFaults = &stats.Memory.PageFaults.Value } + if stats.Memory.CommitMemoryBytes != nil { + result.Memory.UsageBytes = &stats.Memory.CommitMemoryBytes.Value + } } else { result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano())) result.Memory.WorkingSetBytes = ptr.To[uint64](0) result.Memory.AvailableBytes = ptr.To[uint64](0) result.Memory.PageFaults = ptr.To[uint64](0) + result.Memory.UsageBytes = ptr.To[uint64](0) } if stats.WritableLayer != nil { result.Rootfs.Time = metav1.NewTime(time.Unix(0, stats.WritableLayer.Timestamp)) @@ -233,6 +236,7 @@ func addCRIPodMemoryStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandb ps.Memory = &statsapi.MemoryStats{ Time: metav1.NewTime(time.Unix(0, criMemory.Timestamp)), AvailableBytes: valueOfUInt64Value(criMemory.AvailableBytes), + UsageBytes: valueOfUInt64Value(criMemory.CommitMemoryBytes), WorkingSetBytes: valueOfUInt64Value(criMemory.WorkingSetBytes), PageFaults: valueOfUInt64Value(criMemory.PageFaults), } @@ -274,6 +278,66 @@ func criInterfaceToWinSummary(criIface *runtimeapi.WindowsNetworkInterfaceUsage) } } +// addCRIPodContainerCPUAndMemoryStats adds container CPU and memory stats from CRI to the PodStats. +func (p *criStatsProvider) addCRIPodContainerCPUAndMemoryStats( + criSandboxStat *runtimeapi.PodSandboxStats, + ps *statsapi.PodStats, + containerMap map[string]*runtimeapi.Container) { + if criSandboxStat == nil || criSandboxStat.Windows == nil { + return + } + for _, criContainerStat := range criSandboxStat.Windows.Containers { + container, found := containerMap[criContainerStat.Attributes.Id] + if !found { + continue + } + // Fill available CPU and memory stats for resource metrics + cs := p.makeWinContainerCPUAndMemoryStats(criContainerStat, time.Unix(0, container.CreatedAt)) + ps.Containers = append(ps.Containers, *cs) + } +} + +// makeWinContainerCPUAndMemoryStats creates container stats with only CPU and memory populated. +// This is a lighter-weight version for the resource metrics endpoint. +func (p *criStatsProvider) makeWinContainerCPUAndMemoryStats( + stats *runtimeapi.WindowsContainerStats, + startTime time.Time, +) *statsapi.ContainerStats { + result := &statsapi.ContainerStats{ + Name: stats.Attributes.Metadata.Name, + StartTime: metav1.NewTime(startTime), + } + if stats.Cpu != nil { + result.CPU = &statsapi.CPUStats{ + Time: metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp)), + UsageCoreNanoSeconds: ptr.To(stats.Cpu.UsageCoreNanoSeconds.GetValue()), + UsageNanoCores: ptr.To(stats.Cpu.UsageNanoCores.GetValue()), + } + } else { + result.CPU = &statsapi.CPUStats{ + Time: metav1.NewTime(time.Unix(0, time.Now().UnixNano())), + UsageCoreNanoSeconds: ptr.To[uint64](0), + UsageNanoCores: ptr.To[uint64](0), + } + } + if stats.Memory != nil { + result.Memory = &statsapi.MemoryStats{ + Time: metav1.NewTime(time.Unix(0, stats.Memory.Timestamp)), + WorkingSetBytes: ptr.To(stats.Memory.WorkingSetBytes.GetValue()), + AvailableBytes: ptr.To(stats.Memory.AvailableBytes.GetValue()), + PageFaults: ptr.To(stats.Memory.PageFaults.GetValue()), + } + } else { + result.Memory = &statsapi.MemoryStats{ + Time: metav1.NewTime(time.Unix(0, time.Now().UnixNano())), + WorkingSetBytes: ptr.To[uint64](0), + AvailableBytes: ptr.To[uint64](0), + PageFaults: ptr.To[uint64](0), + } + } + return result +} + // newNetworkStatsProvider uses the real windows hnslib if not provided otherwise if the interface is provided // by the cristatsprovider in testing scenarios it uses that one func newNetworkStatsProvider(p *criStatsProvider) windowsNetworkStatsProvider { diff --git a/pkg/kubelet/stats/cri_stats_provider_windows_test.go b/pkg/kubelet/stats/cri_stats_provider_windows_test.go index d7c77522c669b..988b7b46c1bca 100644 --- a/pkg/kubelet/stats/cri_stats_provider_windows_test.go +++ b/pkg/kubelet/stats/cri_stats_provider_windows_test.go @@ -459,6 +459,7 @@ func Test_criStatsProvider_makeWinContainerStats(t *testing.T) { memoryUsageTimestamp := int64(666666) memoryUsageWorkingSetBytes := uint64(0x11223344) memoryUsageAvailableBytes := uint64(0x55667788) + memoryCommitBytes := uint64(0x99AABBCC) memoryUsagePageFaults := uint64(200) logStatsUsed := uint64(5000) logStatsInodesUsed := uint64(5050) @@ -502,6 +503,9 @@ func Test_criStatsProvider_makeWinContainerStats(t *testing.T) { WorkingSetBytes: &runtimeapi.UInt64Value{ Value: memoryUsageWorkingSetBytes, }, + CommitMemoryBytes: &runtimeapi.UInt64Value{ + Value: memoryCommitBytes, + }, PageFaults: &runtimeapi.UInt64Value{ Value: memoryUsagePageFaults, }, @@ -538,6 +542,7 @@ func Test_criStatsProvider_makeWinContainerStats(t *testing.T) { Memory: &statsapi.MemoryStats{ Time: v1.NewTime(time.Unix(0, memoryUsageTimestamp)), AvailableBytes: ptr.To[uint64](memoryUsageAvailableBytes), + UsageBytes: ptr.To[uint64](memoryCommitBytes), WorkingSetBytes: ptr.To[uint64](memoryUsageWorkingSetBytes), PageFaults: ptr.To[uint64](memoryUsagePageFaults), }, diff --git a/pkg/kubelet/stats/pidlimit/pidlimit_linux.go b/pkg/kubelet/stats/pidlimit/pidlimit_linux.go index 25e7655203211..40fa8b73b224d 100644 --- a/pkg/kubelet/stats/pidlimit/pidlimit_linux.go +++ b/pkg/kubelet/stats/pidlimit/pidlimit_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/stats/pidlimit/pidlimit_unsupported.go b/pkg/kubelet/stats/pidlimit/pidlimit_unsupported.go index 805c276d997c1..b195a771d1510 100644 --- a/pkg/kubelet/stats/pidlimit/pidlimit_unsupported.go +++ b/pkg/kubelet/stats/pidlimit/pidlimit_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/status/status_manager.go b/pkg/kubelet/status/status_manager.go index f4334b59ce9ca..bac491cb7d2e1 100644 --- a/pkg/kubelet/status/status_manager.go +++ b/pkg/kubelet/status/status_manager.go @@ -22,6 +22,7 @@ import ( "fmt" "reflect" "sort" + "strconv" "strings" "sync" "time" @@ -79,6 +80,7 @@ type manager struct { podDeletionSafety PodDeletionSafetyProvider podStartupLatencyHelper PodStartupLatencyStateHelper + notifiers []PodUpdateNotifier } type podResizeConditions struct { @@ -126,6 +128,21 @@ type PodStartupLatencyStateHelper interface { DeletePodStartupState(podUID types.UID) } +// PodUpdateNotifier is an interface for receiving pod status updates. +type PodUpdateNotifier interface { + // OnPodUpdated is called when a pod's status is updated. + OnPodUpdated(pod *v1.Pod, status v1.PodStatus, isAdded bool) + // OnPodRemoved is called when a pod is terminated. + OnPodRemoved(pod *v1.Pod) +} + +type podStatusNotification struct { + pod *v1.Pod + status v1.PodStatus + isAdded bool + podIsFinished bool +} + // Manager is the Source of truth for kubelet pod status, and should be kept up-to-date with // the latest v1.PodStatus. It also syncs updates back to the API server. type Manager interface { @@ -134,8 +151,11 @@ type Manager interface { // Start the API server status sync loop. Start(ctx context.Context) + // AddPodUpdateNotifier adds a subscriber to receive pod status updates. + AddPodUpdateNotifier(notifier PodUpdateNotifier) + // SetPodStatus caches updates the cached status for the given pod, and triggers a status update. - SetPodStatus(logger klog.Logger, pod *v1.Pod, status v1.PodStatus) + SetPodStatus(logger klog.Logger, pod *v1.Pod, status v1.PodStatus) (v1.PodStatus, bool) // SetContainerReadiness updates the cached container status with the given readiness, and // triggers a status update. @@ -202,10 +222,11 @@ func NewManager(kubeClient clientset.Interface, podManager PodManager, podDeleti } } -// isPodStatusByKubeletEqual returns true if the given pod statuses are equal when non-kubelet-owned -// pod conditions are excluded. -// This method normalizes the status before comparing so as to make sure that meaningless -// changes will be ignored. +// isPodStatusByKubeletEqual returns true if the given pod statuses are equal, ignoring +// fields not managed by the kubelet (including non-kubelet-owned pod conditions, +// ResourceClaimStatuses, ExtendedResourceClaimStatus, and +// NodeAllocatableResourceClaimStatuses). Statuses are assumed to be +// normalized before calling this function. func isPodStatusByKubeletEqual(oldStatus, status *v1.PodStatus) bool { oldCopy := oldStatus.DeepCopy() @@ -233,6 +254,13 @@ func isPodStatusByKubeletEqual(oldStatus, status *v1.PodStatus) bool { } oldCopy.Conditions = status.Conditions + // ResourceClaimStatuses is not owned and not modified by kubelet. + oldCopy.ResourceClaimStatuses = status.ResourceClaimStatuses + // ExtendedResourceClaimStatus is not owned and not modified by kubelet. + oldCopy.ExtendedResourceClaimStatus = status.ExtendedResourceClaimStatus + // NodeAllocatableResourceClaimStatuses is not owned and not modified by kubelet. + oldCopy.NodeAllocatableResourceClaimStatuses = status.NodeAllocatableResourceClaimStatuses + return apiequality.Semantic.DeepEqual(oldCopy, status) } @@ -266,6 +294,12 @@ func (m *manager) Start(ctx context.Context) { }, 0) } +func (m *manager) AddPodUpdateNotifier(notifier PodUpdateNotifier) { + m.podStatusesLock.Lock() + defer m.podStatusesLock.Unlock() + m.notifiers = append(m.notifiers, notifier) +} + // GetPodResizeConditions returns the last cached ResizeStatus value. func (m *manager) GetPodResizeConditions(podUID types.UID) []*v1.PodCondition { m.podStatusesLock.RLock() @@ -281,6 +315,11 @@ func (m *manager) SetPodResizePendingCondition(podUID types.UID, reason, message previousCondition := m.podResizeConditions[podUID].PodResizePending + if reason != v1.PodReasonInfeasible { + // For all other "pending" reasons, we set the reason to "Deferred". + reason = v1.PodReasonDeferred + } + m.podResizeConditions[podUID] = podResizeConditions{ PodResizePending: updatedPodResizeCondition(v1.PodResizePending, m.podResizeConditions[podUID].PodResizePending, reason, message, observedGeneration), PodResizeInProgress: m.podResizeConditions[podUID].PodResizeInProgress, @@ -422,7 +461,14 @@ func (m *manager) GetPodStatus(uid types.UID) (v1.PodStatus, bool) { return status.status, ok } -func (m *manager) SetPodStatus(logger klog.Logger, pod *v1.Pod, status v1.PodStatus) { +func (m *manager) SetPodStatus(logger klog.Logger, pod *v1.Pod, status v1.PodStatus) (v1.PodStatus, bool) { + var notification *podStatusNotification + defer func() { + if notification != nil { + m.sendNotification(notification) + } + }() + m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() @@ -435,10 +481,20 @@ func (m *manager) SetPodStatus(logger klog.Logger, pod *v1.Pod, status v1.PodSta // Force a status update if deletion timestamp is set. This is necessary // because if the pod is in the non-running state, the pod worker still // needs to be able to trigger an update and/or deletion. - m.updateStatusInternal(logger, pod, status, pod.DeletionTimestamp != nil, false) + changed, notif := m.updateStatusInternal(logger, pod, status, pod.DeletionTimestamp != nil, false) + notification = notif + + return m.podStatuses[pod.UID].status, changed } func (m *manager) SetContainerReadiness(logger klog.Logger, podUID types.UID, containerID kubecontainer.ContainerID, ready bool) { + var notification *podStatusNotification + defer func() { + if notification != nil { + m.sendNotification(notification) + } + }() + m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() @@ -498,10 +554,17 @@ func (m *manager) SetContainerReadiness(logger klog.Logger, podUID types.UID, co allContainerStatuses := append(status.InitContainerStatuses, status.ContainerStatuses...) updateConditionFunc(v1.PodReady, GeneratePodReadyCondition(pod, &oldStatus.status, status.Conditions, allContainerStatuses, status.Phase)) updateConditionFunc(v1.ContainersReady, GenerateContainersReadyCondition(pod, &oldStatus.status, allContainerStatuses, status.Phase)) - m.updateStatusInternal(logger, pod, status, false, false) + _, notification = m.updateStatusInternal(logger, pod, status, false, false) } func (m *manager) SetContainerStartup(logger klog.Logger, podUID types.UID, containerID kubecontainer.ContainerID, started bool) { + var notification *podStatusNotification + defer func() { + if notification != nil { + m.sendNotification(notification) + } + }() + m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() @@ -540,7 +603,7 @@ func (m *manager) SetContainerStartup(logger klog.Logger, podUID types.UID, cont containerStatus, _, _ = findContainerStatus(&status, containerID.String()) containerStatus.Started = &started - m.updateStatusInternal(logger, pod, status, false, false) + _, notification = m.updateStatusInternal(logger, pod, status, false, false) } func findContainerStatus(status *v1.PodStatus, containerID string) (containerStatus *v1.ContainerStatus, init bool, ok bool) { @@ -572,6 +635,13 @@ func findContainerStatus(status *v1.PodStatus, containerID string) (containerSta // It also makes sure that pods are transitioned to a terminal phase (Failed or Succeeded) before // their deletion. func (m *manager) TerminatePod(logger klog.Logger, pod *v1.Pod) { + var notification *podStatusNotification + defer func() { + if notification != nil { + m.sendNotification(notification) + } + }() + m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() @@ -634,7 +704,7 @@ func (m *manager) TerminatePod(logger klog.Logger, pod *v1.Pod) { } logger.V(5).Info("TerminatePod calling updateStatusInternal", "pod", klog.KObj(pod), "podUID", pod.UID) - m.updateStatusInternal(logger, pod, status, true, true) + _, notification = m.updateStatusInternal(logger, pod, status, true, true) } // hasPodInitialized returns true if the pod has no evidence of ever starting a regular container, which @@ -776,7 +846,7 @@ func checkContainerStateTransition(oldStatuses, newStatuses *v1.PodStatus, podSp // updateStatusInternal updates the internal status cache, and queues an update to the api server if // necessary. // This method IS NOT THREAD SAFE and must be called from a locked function. -func (m *manager) updateStatusInternal(logger klog.Logger, pod *v1.Pod, status v1.PodStatus, forceUpdate, podIsFinished bool) { +func (m *manager) updateStatusInternal(logger klog.Logger, pod *v1.Pod, status v1.PodStatus, forceUpdate, podIsFinished bool) (bool, *podStatusNotification) { var oldStatus v1.PodStatus cachedStatus, isCached := m.podStatuses[pod.UID] if isCached { @@ -797,7 +867,7 @@ func (m *manager) updateStatusInternal(logger klog.Logger, pod *v1.Pod, status v // Check for illegal state transition in containers if err := checkContainerStateTransition(&oldStatus, &status, &pod.Spec); err != nil { logger.Error(err, "Status update on pod aborted", "pod", klog.KObj(pod)) - return + return false, nil } // Set ContainersReadyCondition.LastTransitionTime. @@ -823,7 +893,7 @@ func (m *manager) updateStatusInternal(logger klog.Logger, pod *v1.Pod, status v // ensure that the start time does not change across updates. if oldStatus.StartTime != nil && !oldStatus.StartTime.IsZero() { - status.StartTime = oldStatus.StartTime + status.StartTime = oldStatus.StartTime.DeepCopy() } else if status.StartTime.IsZero() { // if the status has no start time, we need to set an initial time now := metav1.Now() @@ -869,11 +939,38 @@ func (m *manager) updateStatusInternal(logger klog.Logger, pod *v1.Pod, status v loggerV.Info("updateStatusInternal", "version", cachedStatus.version+1, "podIsFinished", podIsFinished, "pod", klog.KObj(pod), "podUID", pod.UID, "containers", strings.Join(containers, " ")) } + // Increment container termination metric for newly terminated containers. + // We only want to count terminations once per container per pod. + // By comparing the new status to the old status before we apply it to the cache, + // we can detect exactly when a container in this pod transitions to the + // Terminated state and record the metric. + checkAndRecordTerminations := func(oldStatuses, newStatuses []v1.ContainerStatus, containerType string) { + for _, newCStat := range newStatuses { + if newCStat.State.Terminated != nil { + // Find the old status for this container. + var oldCStat *v1.ContainerStatus + for i := range oldStatuses { + if oldStatuses[i].Name == newCStat.Name { + oldCStat = &oldStatuses[i] + break + } + } + // If it wasn't terminated before (or is brand new and already terminated), record it. + if oldCStat == nil || oldCStat.State.Terminated == nil { + metrics.TerminatedContainersTotal.WithLabelValues(containerType, strconv.Itoa(int(newCStat.State.Terminated.ExitCode)), newCStat.State.Terminated.Reason).Inc() + } + } + } + } + checkAndRecordTerminations(oldStatus.InitContainerStatuses, status.InitContainerStatuses, metrics.InitContainer) + checkAndRecordTerminations(oldStatus.ContainerStatuses, status.ContainerStatuses, metrics.Container) + checkAndRecordTerminations(oldStatus.EphemeralContainerStatuses, status.EphemeralContainerStatuses, metrics.EphemeralContainer) + // The intent here is to prevent concurrent updates to a pod's status from // clobbering each other so the phase of a pod progresses monotonically. if isCached && isPodStatusByKubeletEqual(&cachedStatus.status, &status) && !forceUpdate { logger.V(3).Info("Ignoring same status for pod", "pod", klog.KObj(pod), "status", status) - return + return false, nil } newStatus := versionedPodStatus{ @@ -900,6 +997,28 @@ func (m *manager) updateStatusInternal(logger klog.Logger, pod *v1.Pod, status v default: // there's already a status update pending } + + podCopy := *pod + podCopy.Status = status + + notification := &podStatusNotification{ + pod: &podCopy, + status: status, + isAdded: !isCached && pod.DeletionTimestamp == nil, + podIsFinished: podIsFinished, + } + + return true, notification +} + +func (m *manager) sendNotification(n *podStatusNotification) { + for _, notifier := range m.notifiers { + if !n.podIsFinished { + notifier.OnPodUpdated(n.pod, n.status, n.isAdded) + } else { + notifier.OnPodRemoved(n.pod) + } + } } // updateLastTransitionTime updates the LastTransitionTime of a pod condition. @@ -1277,6 +1396,8 @@ func mergePodStatus(pod *v1.Pod, oldPodStatus, newPodStatus v1.PodStatus, couldH newPodStatus.ResourceClaimStatuses = oldPodStatus.ResourceClaimStatuses // ExtendedResourceClaimStatus is not owned and not modified by kubelet. newPodStatus.ExtendedResourceClaimStatus = oldPodStatus.ExtendedResourceClaimStatus + // NodeAllocatableResourceClaimStatuses is not owned and not modified by kubelet. + newPodStatus.NodeAllocatableResourceClaimStatuses = oldPodStatus.NodeAllocatableResourceClaimStatuses // Delay transitioning a pod to a terminal status unless the pod is actually terminal. // The Kubelet should never transition a pod to terminal status that could have running diff --git a/pkg/kubelet/status/status_manager_test.go b/pkg/kubelet/status/status_manager_test.go index 0b3c7da275dc1..74b31866b7eb9 100644 --- a/pkg/kubelet/status/status_manager_test.go +++ b/pkg/kubelet/status/status_manager_test.go @@ -33,6 +33,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -52,6 +53,7 @@ import ( statustest "k8s.io/kubernetes/pkg/kubelet/status/testing" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util" + "k8s.io/utils/ptr" ) type mutablePodManager interface { @@ -180,8 +182,8 @@ func TestNewStatusPreservesPodStartTime(t *testing.T) { syncer.SetPodStatus(logger, pod, getRandomPodStatus()) status := expectPodStatus(t, syncer, pod) - if !status.StartTime.Time.Equal(startTime.Time) { - t.Errorf("Unexpected start time, expected %v, actual %v", startTime, status.StartTime) + if !status.StartTime.Time.Equal(startTime.Rfc3339Copy().Time) { + t.Errorf("Unexpected start time, expected %v, actual %v", startTime.Rfc3339Copy(), status.StartTime) } } @@ -511,6 +513,32 @@ func TestStatusEquality(t *testing.T) { if !isPodStatusByKubeletEqual(&oldPodStatus, &podStatus) { t.Fatalf("Differences in pod condition not owned by kubelet should not affect normalized equality.") } + + claimStatusA := v1.PodResourceClaimStatus{ + Name: "my-claim", + ResourceClaimName: ptr.To("claim"), + } + extendedClaimStatusA := &v1.PodExtendedResourceClaimStatus{ + RequestMappings: []v1.ContainerExtendedResourceRequest{ + {RequestName: "request", ContainerName: "c", ResourceName: "example.com/gpu"}, + }, + ResourceClaimName: "claim", + } + oldPodStatus.ResourceClaimStatuses = []v1.PodResourceClaimStatus{claimStatusA} + oldPodStatus.ExtendedResourceClaimStatus = extendedClaimStatusA + oldPodStatus.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "my-claim", + Containers: []string{"ctr0"}, + Resources: map[v1.ResourceName]resource.Quantity{v1.ResourceMemory: resource.MustParse("100Mi")}, + }, + } + + normalizeStatus(&pod, &oldPodStatus) + normalizeStatus(&pod, &podStatus) + if !isPodStatusByKubeletEqual(&oldPodStatus, &podStatus) { + t.Fatalf("Differences in pod resource claim statuses not owned by kubelet should not affect normalized equality.") + } } func TestStatusNormalizationEnforcesMaxBytes(t *testing.T) { @@ -2073,6 +2101,99 @@ func TestMergePodStatus(t *testing.T) { } } +func TestContainerTerminationMetric(t *testing.T) { + metrics.Register() + manager := newTestManager(&fake.Clientset{}) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "metric-pod", + Namespace: "test", + UID: "12345", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "test-container"}, + }, + }, + } + manager.podManager.(mutablePodManager).AddPod(pod) + + metrics.TerminatedContainersTotal.Reset() + + initialStatus := v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "test-container", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + } + manager.SetPodStatus(klog.Background(), pod, initialStatus) + + manager.testSyncBatch(context.Background()) + + // Test successful termination (exit code 0) + successStatus := v1.PodStatus{ + Phase: v1.PodSucceeded, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "test-container", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 0, + Reason: "Completed", + }, + }, + }, + }, + } + manager.SetPodStatus(klog.Background(), pod, successStatus) + manager.testSyncBatch(context.Background()) + + count, err := testutil.GetCounterMetricValue(metrics.TerminatedContainersTotal.WithLabelValues(metrics.Container, "0", "Completed")) + require.NoError(t, err) + assert.InDelta(t, 1.0, count, 0) + + // Test error termination (exit code 1) for a second container to verify cumulative behavior + errorPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "error-pod", + Namespace: "test", + UID: "67890", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "error-container"}, + }, + }, + } + manager.podManager.(mutablePodManager).AddPod(errorPod) + + errorStatus := v1.PodStatus{ + Phase: v1.PodFailed, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "error-container", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 1, + Reason: "Error", + }, + }, + }, + }, + } + manager.SetPodStatus(klog.Background(), errorPod, errorStatus) + manager.testSyncBatch(context.Background()) + + count, err = testutil.GetCounterMetricValue(metrics.TerminatedContainersTotal.WithLabelValues(metrics.Container, "1", "Error")) + require.NoError(t, err) + assert.InDelta(t, 1.0, count, 0) +} + func TestPodResizeConditions(t *testing.T) { m := NewManager(&fake.Clientset{}, kubepod.NewBasicPodManager(), &statustest.FakePodDeletionSafetyProvider{}, util.NewPodStartupLatencyTracker()) podUID := types.UID("12345") @@ -2161,7 +2282,7 @@ func TestPodResizeConditions(t *testing.T) { { name: "set pod resize pending condition to deferred with message", updateFunc: func(podUID types.UID) bool { - return m.SetPodResizePendingCondition(podUID, v1.PodReasonDeferred, "some-message", 1) + return m.SetPodResizePendingCondition(podUID, "some-reason", "some-message", 1) }, expectedUpdateFuncReturnVal: true, expected: []*v1.PodCondition{ diff --git a/pkg/kubelet/sysctl/allowlist_test.go b/pkg/kubelet/sysctl/allowlist_test.go index 29cd8cb0e6d06..47e943bf176e7 100644 --- a/pkg/kubelet/sysctl/allowlist_test.go +++ b/pkg/kubelet/sysctl/allowlist_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/types/pod_update.go b/pkg/kubelet/types/pod_update.go index ee4bd58c7e6a0..281efd20d3356 100644 --- a/pkg/kubelet/types/pod_update.go +++ b/pkg/kubelet/types/pod_update.go @@ -38,10 +38,8 @@ type PodOperation int // These constants identify the PodOperations that can be made on a pod configuration. const ( - // SET is the current pod configuration. - SET PodOperation = iota // ADD signifies pods that are new to this source. - ADD + ADD PodOperation = iota // DELETE signifies pods that are gracefully deleted from this source. DELETE // REMOVE signifies pods that have been removed from this source. diff --git a/pkg/kubelet/userns/types.go b/pkg/kubelet/userns/types.go index e494ca212e6d1..d2c515d700636 100644 --- a/pkg/kubelet/userns/types.go +++ b/pkg/kubelet/userns/types.go @@ -16,7 +16,10 @@ limitations under the License. package userns -import "k8s.io/apimachinery/pkg/types" +import ( + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" +) // Here go types that are common for all supported OS (windows, linux). @@ -24,6 +27,6 @@ type userNsPodsManager interface { HandlerSupportsUserNamespaces(runtimeHandler string) (bool, error) GetPodDir(podUID types.UID) string ListPodsFromDisk() ([]types.UID, error) - GetKubeletMappings(idsPerPod uint32) (uint32, uint32, error) + GetKubeletMappings(logger klog.Logger, idsPerPod uint32) (uint32, uint32, error) GetMaxPods() int } diff --git a/pkg/kubelet/userns/userns_manager.go b/pkg/kubelet/userns/userns_manager.go index edd2e4e97d40b..6a79ac78f457c 100644 --- a/pkg/kubelet/userns/userns_manager.go +++ b/pkg/kubelet/userns/userns_manager.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. @@ -141,7 +140,7 @@ func MakeUserNsManager(logger klog.Logger, kl userNsPodsManager, idsPerPod *int6 // cast it. userNsLength = uint32(*idsPerPod) } - kubeletMappingID, kubeletMappingLen, err := kl.GetKubeletMappings(userNsLength) + kubeletMappingID, kubeletMappingLen, err := kl.GetKubeletMappings(logger, userNsLength) if err != nil { return nil, fmt.Errorf("kubelet mappings: %w", err) } @@ -394,8 +393,7 @@ func (m *UsernsManager) createUserNs(logger klog.Logger, pod *v1.Pod) (userNs us } // GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace -func (m *UsernsManager) GetOrCreateUserNamespaceMappings(ctx context.Context, pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { - logger := klog.FromContext(ctx) +func (m *UsernsManager) GetOrCreateUserNamespaceMappings(logger klog.Logger, pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { featureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport) // TODO: If the default value for hostUsers ever changes, change the default value of diff --git a/pkg/kubelet/userns/userns_manager_disabled_test.go b/pkg/kubelet/userns/userns_manager_disabled_test.go index 67f633009efad..73c2b015b2f18 100644 --- a/pkg/kubelet/userns/userns_manager_disabled_test.go +++ b/pkg/kubelet/userns/userns_manager_disabled_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. @@ -25,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" pkgfeatures "k8s.io/kubernetes/pkg/features" @@ -35,6 +35,7 @@ import ( func TestMakeUserNsManagerDisabled(t *testing.T) { logger, _ := ktesting.NewTestContext(t) + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) testUserNsPodsManager := &testUserNsPodsManager{} @@ -44,6 +45,7 @@ func TestMakeUserNsManagerDisabled(t *testing.T) { func TestReleaseDisabled(t *testing.T) { logger, _ := ktesting.NewTestContext(t) + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) testUserNsPodsManager := &testUserNsPodsManager{} @@ -54,6 +56,7 @@ func TestReleaseDisabled(t *testing.T) { } func TestGetOrCreateUserNamespaceMappingsDisabled(t *testing.T) { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) trueVal := true @@ -98,12 +101,12 @@ func TestGetOrCreateUserNamespaceMappingsDisabled(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) + logger, _ := ktesting.NewTestContext(t) testUserNsPodsManager := &testUserNsPodsManager{} m, err := MakeUserNsManager(logger, testUserNsPodsManager, nil) require.NoError(t, err) - userns, err := m.GetOrCreateUserNamespaceMappings(ctx, test.pod, "") + userns, err := m.GetOrCreateUserNamespaceMappings(logger, test.pod, "") assert.Nil(t, userns) if test.success { assert.NoError(t, err) @@ -116,6 +119,7 @@ func TestGetOrCreateUserNamespaceMappingsDisabled(t *testing.T) { func TestCleanupOrphanedPodUsernsAllocationsDisabled(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) testUserNsPodsManager := &testUserNsPodsManager{} diff --git a/pkg/kubelet/userns/userns_manager_switch_test.go b/pkg/kubelet/userns/userns_manager_switch_test.go index 0177914023d2b..ee4634733b24c 100644 --- a/pkg/kubelet/userns/userns_manager_switch_test.go +++ b/pkg/kubelet/userns/userns_manager_switch_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2024 The Kubernetes Authors. @@ -27,6 +26,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" pkgfeatures "k8s.io/kubernetes/pkg/features" @@ -34,7 +34,7 @@ import ( ) func TestMakeUserNsManagerSwitch(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) + logger, _ := ktesting.NewTestContext(t) // Create the manager with the feature gate enabled, to record some pods on disk. featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) @@ -52,12 +52,13 @@ func TestMakeUserNsManagerSwitch(t *testing.T) { // Record the pods on disk. for _, podUID := range pods { pod := v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}} - _, err := m.GetOrCreateUserNamespaceMappings(ctx, &pod, "") + _, err := m.GetOrCreateUserNamespaceMappings(logger, &pod, "") require.NoError(t, err, "failed to record userns range for pod %v", podUID) } // Test re-init works when the feature gate is disabled and there were some // pods written on disk. + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) m2, err := MakeUserNsManager(logger, testUserNsPodsManager, nil) require.NoError(t, err) @@ -70,7 +71,7 @@ func TestMakeUserNsManagerSwitch(t *testing.T) { } func TestGetOrCreateUserNamespaceMappingsSwitch(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) + logger, _ := ktesting.NewTestContext(t) // Enable the feature gate to create some pods on disk. featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) @@ -88,12 +89,13 @@ func TestGetOrCreateUserNamespaceMappingsSwitch(t *testing.T) { // Record the pods on disk. for _, podUID := range pods { pod := v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}} - _, err := m.GetOrCreateUserNamespaceMappings(ctx, &pod, "") + _, err := m.GetOrCreateUserNamespaceMappings(logger, &pod, "") require.NoError(t, err, "failed to record userns range for pod %v", podUID) } // Test no-op when the feature gate is disabled and there were some // pods registered on disk. + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) // Create a new manager with the feature gate off and verify the userns range is nil. m2, err := MakeUserNsManager(logger, testUserNsPodsManager, nil) @@ -101,7 +103,7 @@ func TestGetOrCreateUserNamespaceMappingsSwitch(t *testing.T) { for _, podUID := range pods { pod := v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}} - userns, err := m2.GetOrCreateUserNamespaceMappings(ctx, &pod, "") + userns, err := m2.GetOrCreateUserNamespaceMappings(logger, &pod, "") assert.NoError(t, err, "failed to record userns range for pod %v", podUID) assert.Nil(t, userns, "userns range should be nil for pod %v", podUID) @@ -126,12 +128,13 @@ func TestCleanupOrphanedPodUsernsAllocationsSwitch(t *testing.T) { // Record the pods on disk. for _, podUID := range pods { pod := v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}} - _, err := m.GetOrCreateUserNamespaceMappings(ctx, &pod, "") + _, err := m.GetOrCreateUserNamespaceMappings(logger, &pod, "") require.NoError(t, err, "failed to record userns range for pod %v", podUID) } // Test cleanup works when the feature gate is disabled and there were some // pods registered. + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, false) err = m.CleanupOrphanedPodUsernsAllocations(ctx, nil, nil) require.NoError(t, err) diff --git a/pkg/kubelet/userns/userns_manager_test.go b/pkg/kubelet/userns/userns_manager_test.go index 90e56418c8d5e..babcbb5deb27e 100644 --- a/pkg/kubelet/userns/userns_manager_test.go +++ b/pkg/kubelet/userns/userns_manager_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. @@ -33,6 +32,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + "k8s.io/klog/v2" pkgfeatures "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/test/utils/ktesting" @@ -78,7 +78,7 @@ func (m *testUserNsPodsManager) HandlerSupportsUserNamespaces(runtimeHandler str return m.userns, nil } -func (m *testUserNsPodsManager) GetKubeletMappings(idsPerPod uint32) (uint32, uint32, error) { +func (m *testUserNsPodsManager) GetKubeletMappings(logger klog.Logger, idsPerPod uint32) (uint32, uint32, error) { if m.mappingFirstID != 0 { return m.mappingFirstID, m.mappingLen, nil } @@ -385,16 +385,16 @@ func TestGetOrCreateUserNamespaceMappings(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) // These tests will create the userns file, so use an existing podDir. testUserNsPodsManager := &testUserNsPodsManager{ podDir: t.TempDir(), userns: tc.runtimeUserns, } - logger, ctx := ktesting.NewTestContext(t) m, err := MakeUserNsManager(logger, testUserNsPodsManager, nil) assert.NoError(t, err) - userns, err := m.GetOrCreateUserNamespaceMappings(ctx, tc.pod, tc.runtimeHandler) + userns, err := m.GetOrCreateUserNamespaceMappings(logger, tc.pod, tc.runtimeHandler) if (tc.success && err != nil) || (!tc.success && err == nil) { t.Errorf("expected success: %v but got error: %v", tc.success, err) } diff --git a/pkg/kubelet/userns/userns_manager_windows.go b/pkg/kubelet/userns/userns_manager_windows.go index 2bd5c8390d45f..c83403b92d747 100644 --- a/pkg/kubelet/userns/userns_manager_windows.go +++ b/pkg/kubelet/userns/userns_manager_windows.go @@ -37,7 +37,7 @@ func (m *UsernsManager) Release(klog.Logger, types.UID) { return } -func (m *UsernsManager) GetOrCreateUserNamespaceMappings(context.Context, *v1.Pod, string) (*runtimeapi.UserNamespace, error) { +func (m *UsernsManager) GetOrCreateUserNamespaceMappings(klog.Logger, *v1.Pod, string) (*runtimeapi.UserNamespace, error) { return nil, nil } diff --git a/pkg/kubelet/util/boottime_util_darwin.go b/pkg/kubelet/util/boottime_util_darwin.go index b6a1cc38c813a..42fb097733bb2 100644 --- a/pkg/kubelet/util/boottime_util_darwin.go +++ b/pkg/kubelet/util/boottime_util_darwin.go @@ -1,5 +1,4 @@ //go:build darwin -// +build darwin /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/util/boottime_util_darwin_test.go b/pkg/kubelet/util/boottime_util_darwin_test.go index 1138deeeb69e2..4fe1b5176165c 100644 --- a/pkg/kubelet/util/boottime_util_darwin_test.go +++ b/pkg/kubelet/util/boottime_util_darwin_test.go @@ -1,5 +1,4 @@ //go:build darwin -// +build darwin /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/util/boottime_util_freebsd.go b/pkg/kubelet/util/boottime_util_freebsd.go index 7d605d7611c1d..b02f9d8d33cca 100644 --- a/pkg/kubelet/util/boottime_util_freebsd.go +++ b/pkg/kubelet/util/boottime_util_freebsd.go @@ -1,5 +1,4 @@ //go:build freebsd -// +build freebsd /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/util/boottime_util_freebsd_test.go b/pkg/kubelet/util/boottime_util_freebsd_test.go index b79eda6cb6a40..dcd7204f159b2 100644 --- a/pkg/kubelet/util/boottime_util_freebsd_test.go +++ b/pkg/kubelet/util/boottime_util_freebsd_test.go @@ -1,5 +1,4 @@ //go:build freebsd -// +build freebsd /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/kubelet/util/boottime_util_linux.go b/pkg/kubelet/util/boottime_util_linux.go index 67c9b191cfe7d..028b01d3d5522 100644 --- a/pkg/kubelet/util/boottime_util_linux.go +++ b/pkg/kubelet/util/boottime_util_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/util/boottime_util_linux_test.go b/pkg/kubelet/util/boottime_util_linux_test.go index 9f77dfd0287ba..882b7f8d9f2d1 100644 --- a/pkg/kubelet/util/boottime_util_linux_test.go +++ b/pkg/kubelet/util/boottime_util_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/util/manager/cache_based_manager.go b/pkg/kubelet/util/manager/cache_based_manager.go index 5f478cf3f57a7..65595cb52d627 100644 --- a/pkg/kubelet/util/manager/cache_based_manager.go +++ b/pkg/kubelet/util/manager/cache_based_manager.go @@ -17,6 +17,7 @@ limitations under the License. package manager import ( + "context" "fmt" "strconv" "sync" @@ -129,9 +130,11 @@ func (s *objectStore) DeleteReference(namespace, name string, _ types.UID) { // GetObjectTTLFromNodeFunc returns a function that returns TTL value // from a given Node object. -func GetObjectTTLFromNodeFunc(getNode func() (*v1.Node, error)) GetObjectTTLFunc { +func GetObjectTTLFromNodeFunc(getNode func(context.Context) (*v1.Node, error)) GetObjectTTLFunc { return func() (time.Duration, bool) { - node, err := getNode() + // TODO: Pass context from upper level instead of using context.Background(). + // This requires changing the GetObjectTTLFunc signature to accept a context parameter. + node, err := getNode(context.Background()) if err != nil { return time.Duration(0), false } diff --git a/pkg/kubelet/util/manager/cache_based_manager_test.go b/pkg/kubelet/util/manager/cache_based_manager_test.go index 38e174651a65d..d386033fb08dd 100644 --- a/pkg/kubelet/util/manager/cache_based_manager_test.go +++ b/pkg/kubelet/util/manager/cache_based_manager_test.go @@ -319,7 +319,7 @@ func TestParseNodeAnnotation(t *testing.T) { }, } for i, testCase := range testCases { - getNode := func() (*v1.Node, error) { return testCase.node, testCase.err } + getNode := func(context.Context) (*v1.Node, error) { return testCase.node, testCase.err } ttl, exists := GetObjectTTLFromNodeFunc(getNode)() if exists != testCase.exists { t.Errorf("%d: incorrect parsing: %t", i, exists) diff --git a/pkg/kubelet/util/pod_startup_latency_tracker.go b/pkg/kubelet/util/pod_startup_latency_tracker.go index d972815a7817d..d083464f4dd26 100644 --- a/pkg/kubelet/util/pod_startup_latency_tracker.go +++ b/pkg/kubelet/util/pod_startup_latency_tracker.go @@ -34,6 +34,8 @@ type PodStartupLatencyTracker interface { ObservedPodOnWatch(pod *v1.Pod, when time.Time) RecordImageStartedPulling(podUID types.UID) RecordImageFinishedPulling(podUID types.UID) + RecordInitContainerStarted(podUID types.UID, startedAt time.Time) + RecordInitContainerFinished(podUID types.UID, finishedAt time.Time) RecordStatusUpdated(pod *v1.Pod) DeletePodStartupState(podUID types.UID) } @@ -42,15 +44,26 @@ type basicPodStartupLatencyTracker struct { // protect against concurrent read and write on pods map lock sync.Mutex pods map[types.UID]*perPodState + // Track pods that were excluded from SLI due to unschedulability + // These pods should never be re-added even if they later become schedulable + excludedPods map[types.UID]bool // metrics for the first network pod only firstNetworkPodSeen bool // For testability clock clock.Clock } +type imagePullSession struct { + start time.Time + end time.Time +} type perPodState struct { - firstStartedPulling time.Time - lastFinishedPulling time.Time + // Session-based image pulling tracking for accurate overlap handling + imagePullSessions []imagePullSession + imagePullSessionsStarts []time.Time // Track multiple concurrent pull starts + // Init container tracking + totalInitContainerRuntime time.Duration + currentInitContainerStart time.Time // first time, when pod status changed into Running observedRunningTime time.Time // log, if pod latency was already Observed @@ -60,8 +73,9 @@ type perPodState struct { // NewPodStartupLatencyTracker creates an instance of PodStartupLatencyTracker func NewPodStartupLatencyTracker() PodStartupLatencyTracker { return &basicPodStartupLatencyTracker{ - pods: map[types.UID]*perPodState{}, - clock: clock.RealClock{}, + pods: map[types.UID]*perPodState{}, + excludedPods: map[types.UID]bool{}, + clock: clock.RealClock{}, } } @@ -77,13 +91,29 @@ func (p *basicPodStartupLatencyTracker) ObservedPodOnWatch(pod *v1.Pod, when tim state := p.pods[pod.UID] if state == nil { - // create a new record for pod, only if it was not yet acknowledged by the Kubelet - // this is required, as we want to log metric only for those pods, that where scheduled - // after Kubelet started + // if pod was previously unschedulable, don't track it again + if p.excludedPods[pod.UID] { + return + } + + // create a new record for pod if pod.Status.StartTime.IsZero() { - p.pods[pod.UID] = &perPodState{} + if isPodUnschedulable(pod) { + p.excludedPods[pod.UID] = true + return + } + + // if pod is schedulable then track it + state = &perPodState{} + p.pods[pod.UID] = state } + return + } + // remove existing pods from tracking (this handles cases where scheduling state becomes known later) + if isPodUnschedulable(pod) { + delete(p.pods, pod.UID) + p.excludedPods[pod.UID] = true return } @@ -102,29 +132,74 @@ func (p *basicPodStartupLatencyTracker) ObservedPodOnWatch(pod *v1.Pod, when tim ctx := context.TODO() logger := klog.FromContext(ctx) podStartingDuration := when.Sub(pod.CreationTimestamp.Time) - imagePullingDuration := state.lastFinishedPulling.Sub(state.firstStartedPulling) - podStartSLOduration := (podStartingDuration - imagePullingDuration).Seconds() + podStartSLOduration := podStartingDuration + + totalImagesPullingTime := calculateImagePullingTime(state.imagePullSessions) + if totalImagesPullingTime > 0 { + podStartSLOduration -= totalImagesPullingTime + } + + if state.totalInitContainerRuntime > 0 { + podStartSLOduration -= state.totalInitContainerRuntime + } + + podIsStateful := isStatefulPod(pod) logger.Info("Observed pod startup duration", "pod", klog.KObj(pod), - "podStartSLOduration", podStartSLOduration, + "podStartSLOduration", podStartSLOduration.Seconds(), "podStartE2EDuration", podStartingDuration, + "totalImagesPullingTime", totalImagesPullingTime, + "totalInitContainerRuntime", state.totalInitContainerRuntime, + "isStatefulPod", podIsStateful, "podCreationTimestamp", pod.CreationTimestamp.Time, - "firstStartedPulling", state.firstStartedPulling, - "lastFinishedPulling", state.lastFinishedPulling, + "imagePullSessionsCount", len(state.imagePullSessions), + "imagePullSessionsStartsCount", len(state.imagePullSessionsStarts), "observedRunningTime", state.observedRunningTime, "watchObservedRunningTime", when) - metrics.PodStartSLIDuration.WithLabelValues().Observe(podStartSLOduration) metrics.PodStartTotalDuration.WithLabelValues().Observe(podStartingDuration.Seconds()) + if !podIsStateful { + metrics.PodStartSLIDuration.WithLabelValues().Observe(podStartSLOduration.Seconds()) + // if is the first Pod with network track the start values + // these metrics will help to identify problems with the CNI plugin + if !pod.Spec.HostNetwork && !p.firstNetworkPodSeen { + metrics.FirstNetworkPodStartSLIDuration.Set(podStartSLOduration.Seconds()) + p.firstNetworkPodSeen = true + } + } state.metricRecorded = true - // if is the first Pod with network track the start values - // these metrics will help to identify problems with the CNI plugin - if !pod.Spec.HostNetwork && !p.firstNetworkPodSeen { - metrics.FirstNetworkPodStartSLIDuration.Set(podStartSLOduration) - p.firstNetworkPodSeen = true + } +} + +// calculateImagePullingTime computes the total time spent pulling images, +// accounting for overlapping pull sessions properly +func calculateImagePullingTime(sessions []imagePullSession) time.Duration { + if len(sessions) == 0 { + return 0 + } + + var totalTime time.Duration + var currentEnd time.Time + + for i, session := range sessions { + if session.end.IsZero() { + continue } + + if i == 0 || session.start.After(currentEnd) { + // First session or no overlap with previous session + totalTime += session.end.Sub(session.start) + currentEnd = session.end + } else if session.end.After(currentEnd) { + // Partial overlap - add only the non-overlapping part + totalTime += session.end.Sub(currentEnd) + currentEnd = session.end + } + // If session.end <= currentEnd, it's completely overlapped } + + return totalTime } func (p *basicPodStartupLatencyTracker) RecordImageStartedPulling(podUID types.UID) { @@ -136,9 +211,8 @@ func (p *basicPodStartupLatencyTracker) RecordImageStartedPulling(podUID types.U return } - if state.firstStartedPulling.IsZero() { - state.firstStartedPulling = p.clock.Now() - } + now := p.clock.Now() + state.imagePullSessionsStarts = append(state.imagePullSessionsStarts, now) } func (p *basicPodStartupLatencyTracker) RecordImageFinishedPulling(podUID types.UID) { @@ -150,8 +224,48 @@ func (p *basicPodStartupLatencyTracker) RecordImageFinishedPulling(podUID types. return } - if !state.firstStartedPulling.IsZero() { - state.lastFinishedPulling = p.clock.Now() // Now is always grater than values from the past. + now := p.clock.Now() + + // Complete the oldest pull session if we have active starts + if len(state.imagePullSessionsStarts) > 0 { + // Take the first (oldest) start and create a session + startTime := state.imagePullSessionsStarts[0] + session := imagePullSession{ + start: startTime, + end: now, + } + state.imagePullSessions = append(state.imagePullSessions, session) + state.imagePullSessionsStarts = state.imagePullSessionsStarts[1:] + } +} + +func (p *basicPodStartupLatencyTracker) RecordInitContainerStarted(podUID types.UID, startedAt time.Time) { + p.lock.Lock() + defer p.lock.Unlock() + + state := p.pods[podUID] + if state == nil { + return + } + + state.currentInitContainerStart = startedAt +} + +func (p *basicPodStartupLatencyTracker) RecordInitContainerFinished(podUID types.UID, finishedAt time.Time) { + p.lock.Lock() + defer p.lock.Unlock() + + state := p.pods[podUID] + if state == nil { + return + } + + if !state.currentInitContainerStart.IsZero() { + initDuration := finishedAt.Sub(state.currentInitContainerStart) + if initDuration > 0 { + state.totalInitContainerRuntime += initDuration + } + state.currentInitContainerStart = time.Time{} } } @@ -197,9 +311,43 @@ func hasPodStartedSLO(pod *v1.Pod) bool { return true } +// isStatefulPod determines if a pod is stateful according to the SLI documentation: +// "A stateful pod is defined as a pod that mounts at least one volume with sources +// other than secrets, config maps, downward API and empty dir." +// We also include Projected volumes since they are a collection of ephemeral types. +// ref: https://github.com/kubernetes/community/blob/master/sig-scalability/slos/pod_startup_latency.md +func isStatefulPod(pod *v1.Pod) bool { + for _, volume := range pod.Spec.Volumes { + // Check if this volume is NOT a stateless/ephemeral type + if volume.Secret == nil && + volume.ConfigMap == nil && + volume.DownwardAPI == nil && + volume.EmptyDir == nil && + volume.Projected == nil { + return true + } + } + return false +} + +// isPodUnschedulable determines if a pod should be excluded from SLI tracking +// according to the SLI definition: "By schedulable pod we mean a pod that has to be +// immediately (without actions from any other components) schedulable in the cluster +// without causing any preemption." +// Any pod with PodScheduled=False is not immediately schedulable and should be excluded. +func isPodUnschedulable(pod *v1.Pod) bool { + for _, condition := range pod.Status.Conditions { + if condition.Type == v1.PodScheduled && condition.Status == v1.ConditionFalse { + return true + } + } + return false +} + func (p *basicPodStartupLatencyTracker) DeletePodStartupState(podUID types.UID) { p.lock.Lock() defer p.lock.Unlock() delete(p.pods, podUID) + delete(p.excludedPods, podUID) } diff --git a/pkg/kubelet/util/pod_startup_latency_tracker_test.go b/pkg/kubelet/util/pod_startup_latency_tracker_test.go index 5679f0568ddc3..578d849881e73 100644 --- a/pkg/kubelet/util/pod_startup_latency_tracker_test.go +++ b/pkg/kubelet/util/pod_startup_latency_tracker_test.go @@ -47,7 +47,8 @@ func TestNoEvents(t *testing.T) { metrics.Register() tracker := &basicPodStartupLatencyTracker{ - pods: map[types.UID]*perPodState{}, + pods: map[types.UID]*perPodState{}, + excludedPods: map[types.UID]bool{}, } if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wants), metricsName); err != nil { @@ -68,7 +69,8 @@ func TestPodsRunningBeforeKubeletStarted(t *testing.T) { metrics.Register() tracker := &basicPodStartupLatencyTracker{ - pods: map[types.UID]*perPodState{}, + pods: map[types.UID]*perPodState{}, + excludedPods: map[types.UID]bool{}, } if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wants), metricsName); err != nil { @@ -129,7 +131,7 @@ kubelet_pod_start_sli_duration_seconds_count 1 metrics.Register() tracker := &basicPodStartupLatencyTracker{ - pods: map[types.UID]*perPodState{}, + pods: map[types.UID]*perPodState{}, excludedPods: map[types.UID]bool{}, clock: fakeClock, } @@ -145,9 +147,9 @@ kubelet_pod_start_sli_duration_seconds_count 1 if !ok { t.Errorf("expected to track pod: %s, but pod not found", podInit.UID) } - if !podState.lastFinishedPulling.Equal(podState.firstStartedPulling.Add(time.Millisecond * 100)) { - t.Errorf("expected pod firstStartedPulling: %s and lastFinishedPulling: %s but got firstStartedPulling: %s and lastFinishedPulling: %s", - podState.firstStartedPulling, podState.firstStartedPulling.Add(time.Millisecond*100), podState.firstStartedPulling, podState.lastFinishedPulling) + + if len(podState.imagePullSessions) != 1 { + t.Errorf("expected one image pull session to be recorded") } podStarted := buildRunningPod() @@ -170,7 +172,7 @@ kubelet_pod_start_sli_duration_seconds_count 1 func TestSinglePodMultipleDownloadsAndRestartsRecorded(t *testing.T) { - t.Run("single pod; started in 30s, image pulling between 10th and 20th seconds", func(t *testing.T) { + t.Run("single pod; started in 30s, overlapping image pulling between 10th and 20th seconds", func(t *testing.T) { wants := ` # HELP kubelet_pod_start_sli_duration_seconds [ALPHA] Duration in seconds to start a pod, excluding time to pull images and run init containers, measured from pod creation timestamp to when all its containers are reported as started and observed via watch @@ -210,27 +212,25 @@ kubelet_pod_start_sli_duration_seconds_count 1 metrics.Register() tracker := &basicPodStartupLatencyTracker{ - pods: map[types.UID]*perPodState{}, + pods: map[types.UID]*perPodState{}, excludedPods: map[types.UID]bool{}, clock: fakeClock, } podInitializing := buildInitializingPod() tracker.ObservedPodOnWatch(podInitializing, frozenTime) - - // image pulling started at 10s and the last one finished at 30s - // first image starts pulling at 10s + // Image 1: 10-16s fakeClock.SetTime(frozenTime.Add(time.Second * 10)) tracker.RecordImageStartedPulling(podInitializing.UID) - // second image starts pulling at 11s + // Image 2: 11-18s fakeClock.SetTime(frozenTime.Add(time.Second * 11)) tracker.RecordImageStartedPulling(podInitializing.UID) - // third image starts pulling at 14s + // Image 3: 14-20s fakeClock.SetTime(frozenTime.Add(time.Second * 14)) tracker.RecordImageStartedPulling(podInitializing.UID) - // first image finished pulling at 18s + fakeClock.SetTime(frozenTime.Add(time.Second * 16)) + tracker.RecordImageFinishedPulling(podInitializing.UID) fakeClock.SetTime(frozenTime.Add(time.Second * 18)) tracker.RecordImageFinishedPulling(podInitializing.UID) - // second and third finished pulling at 20s fakeClock.SetTime(frozenTime.Add(time.Second * 20)) tracker.RecordImageFinishedPulling(podInitializing.UID) @@ -238,13 +238,13 @@ kubelet_pod_start_sli_duration_seconds_count 1 if !ok { t.Errorf("expected to track pod: %s, but pod not found", podInitializing.UID) } - if !podState.firstStartedPulling.Equal(frozenTime.Add(time.Second * 10)) { // second and third image start pulling should not affect pod firstStartedPulling - t.Errorf("expected pod firstStartedPulling: %s but got firstStartedPulling: %s", - podState.firstStartedPulling.Add(time.Second*10), podState.firstStartedPulling) + if len(podState.imagePullSessions) != 3 { + t.Errorf("expected 3 image pull sessions but got %d", len(podState.imagePullSessions)) } - if !podState.lastFinishedPulling.Equal(frozenTime.Add(time.Second * 20)) { // should be updated when the pod's last image finished pulling - t.Errorf("expected pod lastFinishedPulling: %s but got lastFinishedPulling: %s", - podState.lastFinishedPulling.Add(time.Second*20), podState.lastFinishedPulling) + totalTime := calculateImagePullingTime(podState.imagePullSessions) + expectedTime := time.Second * 10 + if totalTime != expectedTime { + t.Errorf("expected total pulling time: %v but got %v", expectedTime, totalTime) } // pod started @@ -275,6 +275,456 @@ kubelet_pod_start_sli_duration_seconds_count 1 }) } +func TestPodWithInitContainersAndMainContainers(t *testing.T) { + + t.Run("single pod with multiple init containers and main containers", func(t *testing.T) { + + wants := ` +# HELP kubelet_pod_start_sli_duration_seconds [ALPHA] Duration in seconds to start a pod, excluding time to pull images and run init containers, measured from pod creation timestamp to when all its containers are reported as started and observed via watch +# TYPE kubelet_pod_start_sli_duration_seconds histogram +kubelet_pod_start_sli_duration_seconds_bucket{le="0.5"} 0 +kubelet_pod_start_sli_duration_seconds_bucket{le="1"} 0 +kubelet_pod_start_sli_duration_seconds_bucket{le="2"} 0 +kubelet_pod_start_sli_duration_seconds_bucket{le="3"} 0 +kubelet_pod_start_sli_duration_seconds_bucket{le="4"} 0 +kubelet_pod_start_sli_duration_seconds_bucket{le="5"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="6"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="8"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="10"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="20"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="30"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="45"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="60"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="120"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="180"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="240"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="300"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="360"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="480"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="600"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="900"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="1200"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="1800"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="2700"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="3600"} 1 +kubelet_pod_start_sli_duration_seconds_bucket{le="+Inf"} 1 +kubelet_pod_start_sli_duration_seconds_sum 4.2 +kubelet_pod_start_sli_duration_seconds_count 1 + ` + + fakeClock := testingclock.NewFakeClock(frozenTime) + + metrics.Register() + + tracker := &basicPodStartupLatencyTracker{ + pods: map[types.UID]*perPodState{}, excludedPods: map[types.UID]bool{}, + clock: fakeClock, + } + + podInit := buildInitializingPod("init-1", "init-2") + tracker.ObservedPodOnWatch(podInit, frozenTime) + + // Init container 1 image pull: 0.5s-1.5s + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 500)) + tracker.RecordImageStartedPulling(podInit.UID) + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 1500)) + tracker.RecordImageFinishedPulling(podInit.UID) + // Init container 1 runtime: 2s-4s + fakeClock.SetTime(frozenTime.Add(time.Second * 2)) + tracker.RecordInitContainerStarted(types.UID(uid), fakeClock.Now()) + fakeClock.SetTime(frozenTime.Add(time.Second * 4)) + tracker.RecordInitContainerFinished(types.UID(uid), fakeClock.Now()) + + // Init container 2 image pull: 4.2s-5.2s + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 4200)) + tracker.RecordImageStartedPulling(podInit.UID) + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 5200)) + tracker.RecordImageFinishedPulling(podInit.UID) + // Init container 2 runtime: 5.5s-7s + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 5500)) + tracker.RecordInitContainerStarted(types.UID(uid), fakeClock.Now()) + fakeClock.SetTime(frozenTime.Add(time.Second * 7)) + tracker.RecordInitContainerFinished(types.UID(uid), fakeClock.Now()) + + // Main container 1 image pull: 7.2s-8.2s + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 7200)) + tracker.RecordImageStartedPulling(podInit.UID) + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 8200)) + tracker.RecordImageFinishedPulling(podInit.UID) + // Main container 2 image pull: 7.3s-8.5s + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 7300)) + tracker.RecordImageStartedPulling(podInit.UID) + fakeClock.SetTime(frozenTime.Add(time.Millisecond * 8500)) + tracker.RecordImageFinishedPulling(podInit.UID) + + // Pod becomes running at 11s + podStarted := buildRunningPod() + tracker.RecordStatusUpdated(podStarted) + tracker.ObservedPodOnWatch(podStarted, frozenTime.Add(time.Second*11)) + + if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wants), metricsName); err != nil { + t.Fatal(err) + } + + state := tracker.pods[types.UID(uid)] + assert.NotNil(t, state, "Pod state should exist") + + expectedInitRuntime := 2*time.Second + 1500*time.Millisecond + assert.Equal(t, expectedInitRuntime, state.totalInitContainerRuntime, + "Total init container runtime should be 3.5s (2s + 1.5s)") + + totalImageTime := calculateImagePullingTime(state.imagePullSessions) + expectedImageTime := 3300 * time.Millisecond // 1s + 1s + 1.3s concurrent overlap + assert.Equal(t, expectedImageTime, totalImageTime, + "Image pulling time should be 3.3s: init-1(1s) + init-2(1s) + concurrent-main(1.3s)") + + assert.Len(t, state.imagePullSessions, 4, "Should have 4 image pull sessions") + + // cleanup + tracker.DeletePodStartupState(podStarted.UID) + + assert.Empty(t, tracker.pods) + metrics.PodStartSLIDuration.Reset() + }) +} + +func TestImmediatelySchedulablePods(t *testing.T) { + t.Run("pods not immediately schedulable should be excluded from tracking", func(t *testing.T) { + wants := "" + + fakeClock := testingclock.NewFakeClock(frozenTime) + metrics.Register() + + tracker := &basicPodStartupLatencyTracker{ + pods: map[types.UID]*perPodState{}, + excludedPods: map[types.UID]bool{}, + clock: fakeClock, + } + + // pod that is not immediately schedulable (PodScheduled=False) + podNotSchedulable := buildInitializingPod() + podNotSchedulable.UID = "not-schedulable-pod" + podNotSchedulable.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodScheduled, + Status: corev1.ConditionFalse, + Reason: "Unschedulable", + }, + } + tracker.ObservedPodOnWatch(podNotSchedulable, frozenTime) + assert.Empty(t, tracker.pods, "Pod with PodScheduled=False should not be tracked") + + // pod that is immediately schedulable (PodScheduled=True) + podSchedulable := buildInitializingPod() + podSchedulable.UID = "schedulable-pod" + podSchedulable.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodScheduled, + Status: corev1.ConditionTrue, + Reason: "Scheduled", + }, + } + tracker.ObservedPodOnWatch(podSchedulable, frozenTime) + assert.Len(t, tracker.pods, 1, "Pod with PodScheduled=True should be tracked") + + // pod without PodScheduled condition + podNoCondition := buildInitializingPod() + podNoCondition.UID = "no-condition-pod" + tracker.ObservedPodOnWatch(podNoCondition, frozenTime) + assert.Len(t, tracker.pods, 2, "Pod without PodScheduled condition should be tracked by default") + + // Verify metrics are empty + if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wants), metricsName); err != nil { + t.Fatal(err) + } + + // cleanup + tracker.DeletePodStartupState(podSchedulable.UID) + tracker.DeletePodStartupState(podNoCondition.UID) + assert.Empty(t, tracker.pods) + metrics.PodStartSLIDuration.Reset() + }) + + t.Run("pod observed as schedulable first, then becomes unschedulable should not be tracked", func(t *testing.T) { + wants := "" + + fakeClock := testingclock.NewFakeClock(frozenTime) + metrics.Register() + + tracker := &basicPodStartupLatencyTracker{ + pods: map[types.UID]*perPodState{}, excludedPods: map[types.UID]bool{}, + clock: fakeClock, + } + + // First observe pod as schedulable + podSchedulable := buildInitializingPod() + podSchedulable.UID = "becomes-unschedulable" + tracker.ObservedPodOnWatch(podSchedulable, frozenTime) + assert.Len(t, tracker.pods, 1, "Pod should be tracked initially") + + // Later observe the same pod as unschedulable + podUnschedulable := buildInitializingPod() + podUnschedulable.UID = "becomes-unschedulable" + podUnschedulable.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodScheduled, + Status: corev1.ConditionFalse, + Reason: "Unschedulable", + }, + } + + tracker.ObservedPodOnWatch(podUnschedulable, frozenTime.Add(time.Second)) + assert.Empty(t, tracker.pods, "Pod should be removed when it becomes unschedulable") + + // Verify no metrics + if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wants), metricsName); err != nil { + t.Fatal(err) + } + + metrics.PodStartSLIDuration.Reset() + }) + + t.Run("pod observed as unschedulable first, then becomes schedulable should remain not tracked", func(t *testing.T) { + wants := "" + + fakeClock := testingclock.NewFakeClock(frozenTime) + metrics.Register() + + tracker := &basicPodStartupLatencyTracker{ + pods: map[types.UID]*perPodState{}, + excludedPods: map[types.UID]bool{}, + clock: fakeClock, + } + + // First observe pod as unschedulable + podUnschedulable := buildInitializingPod() + podUnschedulable.UID = "unschedulable-first" + podUnschedulable.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodScheduled, + Status: corev1.ConditionFalse, + Reason: "Unschedulable", + }, + } + + tracker.ObservedPodOnWatch(podUnschedulable, frozenTime) + assert.Empty(t, tracker.pods, "Pod should not be tracked when first observed as unschedulable") + assert.True(t, tracker.excludedPods[podUnschedulable.UID], "Pod should be in excludedPods map") + + // Later observe the same pod as schedulable (e.g., after cluster autoscaling) + podSchedulable := buildInitializingPod() + podSchedulable.UID = "unschedulable-first" + podSchedulable.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodScheduled, + Status: corev1.ConditionTrue, + Reason: "Scheduled", + }, + } + + tracker.ObservedPodOnWatch(podSchedulable, frozenTime.Add(time.Second*5)) + assert.Empty(t, tracker.pods, "Pod should remain excluded even after becoming schedulable") + assert.True(t, tracker.excludedPods[podSchedulable.UID], "Pod should remain in excludedPods map") + + // Complete the startup process - should not record metrics + podRunning := buildRunningPod() + podRunning.UID = "unschedulable-first" + tracker.RecordStatusUpdated(podRunning) + tracker.ObservedPodOnWatch(podRunning, frozenTime.Add(time.Second*10)) + + // Verify no SLI metrics recorded + if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wants), metricsName); err != nil { + t.Fatal(err) + } + + // cleanup + tracker.DeletePodStartupState(podRunning.UID) + assert.Empty(t, tracker.pods) + assert.False(t, tracker.excludedPods[podRunning.UID], "Pod should be removed from excludedPods on cleanup") + metrics.PodStartSLIDuration.Reset() + }) +} + +func TestStatefulPodExclusion(t *testing.T) { + t.Run("stateful pods should be excluded from SLI metrics", func(t *testing.T) { + wantsSLI := "" + + fakeClock := testingclock.NewFakeClock(frozenTime) + metrics.Register() + + tracker := &basicPodStartupLatencyTracker{ + pods: map[types.UID]*perPodState{}, excludedPods: map[types.UID]bool{}, + clock: fakeClock, + } + + // Create a stateful pod (with PVC volume) + statefulPod := buildInitializingPod() + statefulPod.UID = "stateful-pod" + statefulPod.Spec.Volumes = []corev1.Volume{ + { + Name: "persistent-storage", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-pvc", + }, + }, + }, + } + + tracker.ObservedPodOnWatch(statefulPod, frozenTime) + + statefulPodRunning := buildRunningPod() + statefulPodRunning.UID = "stateful-pod" + statefulPodRunning.Spec.Volumes = statefulPod.Spec.Volumes + tracker.RecordStatusUpdated(statefulPodRunning) + + // Observe pod as running after 3 seconds + tracker.ObservedPodOnWatch(statefulPodRunning, frozenTime.Add(time.Second*3)) + + // Verify no SLI metrics for stateful pod ( + if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(wantsSLI), metricsName); err != nil { + t.Fatal(err) + } + + // cleanup + tracker.DeletePodStartupState(statefulPod.UID) + assert.Empty(t, tracker.pods) + metrics.PodStartSLIDuration.Reset() + }) +} + +func TestIsStatefulPod(t *testing.T) { + tests := []struct { + name string + volumes []corev1.Volume + expected bool + }{ + { + name: "no volumes is stateless", + volumes: nil, + expected: false, + }, + { + name: "projected volume (such as service account token) is stateless", + volumes: []corev1.Volume{ + { + Name: "kube-api-access-abcde", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + {ServiceAccountToken: &corev1.ServiceAccountTokenProjection{Path: "token"}}, + {ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{Name: "kube-root-ca.crt"}, + }}, + {DownwardAPI: &corev1.DownwardAPIProjection{}}, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "projected volume with a secret and configmap is stateless", + volumes: []corev1.Volume{ + { + Name: "kube-api-access-abcde", + VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{}}, + }, + { + Name: "my-secret", + VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "s"}}, + }, + { + Name: "my-config", + VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: "c"}}}, + }, + }, + expected: false, + }, + { + name: "PVC volume is stateful", + volumes: []corev1.Volume{ + { + Name: "data", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}, + }, + }, + }, + expected: true, + }, + { + name: "PVC with a projected volume is stateful", + volumes: []corev1.Volume{ + { + Name: "kube-api-access-abcde", + VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{}}, + }, + { + Name: "data", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}, + }, + }, + }, + expected: true, + }, + { + name: "hostPath volume is stateful", + volumes: []corev1.Volume{ + { + Name: "host-vol", + VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/data"}}, + }, + }, + expected: true, + }, + { + name: "emptyDir is stateless", + volumes: []corev1.Volume{ + { + Name: "scratch", + VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, + }, + }, + expected: false, + }, + { + name: "downwardAPI is stateless", + volumes: []corev1.Volume{ + { + Name: "podinfo", + VolumeSource: corev1.VolumeSource{DownwardAPI: &corev1.DownwardAPIVolumeSource{}}, + }, + }, + expected: false, + }, + { + name: "secret is stateless", + volumes: []corev1.Volume{ + { + Name: "my-secret", + VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "s"}}, + }, + }, + expected: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + pod := &corev1.Pod{ + Spec: corev1.PodSpec{ + Volumes: tc.volumes, + }, + } + result := isStatefulPod(pod) + assert.Equal(t, tc.expected, result, "isStatefulPod mismatch for %q", tc.name) + }) + } +} + func TestFirstNetworkPodMetrics(t *testing.T) { t.Run("first network pod; started in 30s, image pulling between 10th and 20th seconds", func(t *testing.T) { @@ -290,7 +740,7 @@ kubelet_first_network_pod_start_sli_duration_seconds 30 metrics.Register() tracker := &basicPodStartupLatencyTracker{ - pods: map[types.UID]*perPodState{}, + pods: map[types.UID]*perPodState{}, excludedPods: map[types.UID]bool{}, clock: fakeClock, } @@ -338,10 +788,20 @@ kubelet_first_network_pod_start_sli_duration_seconds 30 }) } -func buildInitializingPod() *corev1.Pod { - return buildPodWithStatus([]corev1.ContainerStatus{ +func buildInitializingPod(initContainerNames ...string) *corev1.Pod { + pod := buildPodWithStatus([]corev1.ContainerStatus{ {State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "PodInitializing"}}}, }) + + // Add init containers if specified + if len(initContainerNames) > 0 { + pod.Spec.InitContainers = make([]corev1.Container, len(initContainerNames)) + for i, name := range initContainerNames { + pod.Spec.InitContainers[i] = corev1.Container{Name: name} + } + } + + return pod } func buildRunningPod() *corev1.Pod { @@ -361,3 +821,78 @@ func buildPodWithStatus(cs []corev1.ContainerStatus) *corev1.Pod { }, } } + +func TestCalculateImagePullingTime(t *testing.T) { + baseTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) + + t.Run("empty sessions should return zero", func(t *testing.T) { + sessions := []imagePullSession{} + result := calculateImagePullingTime(sessions) + assert.Equal(t, time.Duration(0), result) + }) + + t.Run("incomplete sessions should be ignored (for example: kubelet restart)", func(t *testing.T) { + sessions := []imagePullSession{ + {start: baseTime, end: baseTime.Add(3 * time.Second)}, // valid: 3s + {start: baseTime.Add(5 * time.Second), end: time.Time{}}, // incomplete, ignored + {start: baseTime.Add(10 * time.Second), end: baseTime.Add(12 * time.Second)}, // valid: 2s + } + result := calculateImagePullingTime(sessions) + assert.Equal(t, 5*time.Second, result) // 3s + 2s + }) + + t.Run("non-overlapping sessions", func(t *testing.T) { + sessions := []imagePullSession{ + {start: baseTime, end: baseTime.Add(2 * time.Second)}, // 2s + {start: baseTime.Add(5 * time.Second), end: baseTime.Add(8 * time.Second)}, // 3s + {start: baseTime.Add(10 * time.Second), end: baseTime.Add(15 * time.Second)}, // 5s + } + result := calculateImagePullingTime(sessions) + assert.Equal(t, 10*time.Second, result) // 2s + 3s + 5s + }) + + t.Run("partially overlapping sessions", func(t *testing.T) { + sessions := []imagePullSession{ + {start: baseTime, end: baseTime.Add(5 * time.Second)}, // 0-5s + {start: baseTime.Add(3 * time.Second), end: baseTime.Add(8 * time.Second)}, // 3-8s (overlap 3-5s) + {start: baseTime.Add(7 * time.Second), end: baseTime.Add(12 * time.Second)}, // 7-12s (overlap 7-8s) + } + result := calculateImagePullingTime(sessions) + assert.Equal(t, 12*time.Second, result) // 12s + }) + + t.Run("completely overlapped sessions", func(t *testing.T) { + sessions := []imagePullSession{ + {start: baseTime, end: baseTime.Add(10 * time.Second)}, // 0-10s + {start: baseTime.Add(2 * time.Second), end: baseTime.Add(8 * time.Second)}, // 2-8s (completely inside) + {start: baseTime.Add(3 * time.Second), end: baseTime.Add(5 * time.Second)}, // 3-5s (completely inside) + } + result := calculateImagePullingTime(sessions) + assert.Equal(t, 10*time.Second, result) // Only outer session: 10s + }) + + t.Run("partially and completely overlapping sessions", func(t *testing.T) { + sessions := []imagePullSession{ + {start: baseTime, end: baseTime.Add(10 * time.Second)}, // 0-10s + {start: baseTime.Add(2 * time.Second), end: baseTime.Add(5 * time.Second)}, // 2-5s (completely inside first) + {start: baseTime.Add(3 * time.Second), end: baseTime.Add(7 * time.Second)}, // 3-7s (completely inside first and partially overlapping second) + } + result := calculateImagePullingTime(sessions) + assert.Equal(t, 10*time.Second, result) // 10s + }) + + t.Run("completely overlapped and partially overlapped sessions with gap", func(t *testing.T) { + // Test mixing completely overlapped sessions with separate sessions + sessions := []imagePullSession{ + {start: baseTime, end: baseTime.Add(8 * time.Second)}, // 0-8s + {start: baseTime.Add(2 * time.Second), end: baseTime.Add(6 * time.Second)}, // 2-6s (completely inside first) + {start: baseTime.Add(3 * time.Second), end: baseTime.Add(5 * time.Second)}, // 3-5s (completely inside first) + {start: baseTime.Add(15 * time.Second), end: baseTime.Add(20 * time.Second)}, // 15-20s (separate) + {start: baseTime.Add(16 * time.Second), end: baseTime.Add(18 * time.Second)}, // 16-18s (completely inside fourth) + {start: baseTime.Add(17 * time.Second), end: baseTime.Add(21 * time.Second)}, // 17-21s (partially overlapping fourth and fifth) + } + result := calculateImagePullingTime(sessions) + // 8s (first group) + 6s (second group) = 14s + assert.Equal(t, 14*time.Second, result) + }) +} diff --git a/pkg/kubelet/util/util.go b/pkg/kubelet/util/util.go index 8aa025abe3124..9039797b18ef8 100644 --- a/pkg/kubelet/util/util.go +++ b/pkg/kubelet/util/util.go @@ -84,7 +84,7 @@ func GetLimits(res *ResourceOpts) v1.ResourceList { if containerResources == nil || containerResources.Limits == nil { return v1.ResourceList{} } - return containerResources.Limits + return containerResources.Limits.DeepCopy() } containerLimits := limitsOrEmpty(res.ContainerResources) @@ -99,13 +99,13 @@ func GetLimits(res *ResourceOpts) v1.ResourceList { // When container-level CPU limit is not set, the pod-level // limit CPU is applied at container-level if containerLimits.Cpu().IsZero() && !podLevelLimits.Cpu().IsZero() { - containerLimits[v1.ResourceCPU] = podLevelLimits[v1.ResourceCPU] + containerLimits[v1.ResourceCPU] = podLevelLimits[v1.ResourceCPU].DeepCopy() } // When container-level Memory limit is not set, the pod-level // limit Memory is applied at container-level if containerLimits.Memory().IsZero() && !podLevelLimits.Memory().IsZero() { - containerLimits[v1.ResourceMemory] = podLevelLimits[v1.ResourceMemory] + containerLimits[v1.ResourceMemory] = podLevelLimits[v1.ResourceMemory].DeepCopy() } return containerLimits } diff --git a/pkg/kubelet/util/util_linux.go b/pkg/kubelet/util/util_linux.go index 98efbb46fe260..12c11cbeff750 100644 --- a/pkg/kubelet/util/util_linux.go +++ b/pkg/kubelet/util/util_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_others.go b/pkg/kubelet/util/util_others.go index e2e1c71bac68d..35541c404be3d 100644 --- a/pkg/kubelet/util/util_others.go +++ b/pkg/kubelet/util/util_others.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_unix.go b/pkg/kubelet/util/util_unix.go index 0664b6a4248d1..e1a310c0a73ea 100644 --- a/pkg/kubelet/util/util_unix.go +++ b/pkg/kubelet/util/util_unix.go @@ -1,5 +1,4 @@ //go:build freebsd || linux || darwin -// +build freebsd linux darwin /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_unix_test.go b/pkg/kubelet/util/util_unix_test.go index 1206ef4128a7f..32c56a1c2f878 100644 --- a/pkg/kubelet/util/util_unix_test.go +++ b/pkg/kubelet/util/util_unix_test.go @@ -1,5 +1,4 @@ //go:build freebsd || linux || darwin -// +build freebsd linux darwin /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_unsupported.go b/pkg/kubelet/util/util_unsupported.go index e95a7c583a899..983cbf931df8e 100644 --- a/pkg/kubelet/util/util_unsupported.go +++ b/pkg/kubelet/util/util_unsupported.go @@ -1,5 +1,4 @@ //go:build !freebsd && !linux && !windows && !darwin -// +build !freebsd,!linux,!windows,!darwin /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_windows.go b/pkg/kubelet/util/util_windows.go index c944a7d22f273..80d8211326a32 100644 --- a/pkg/kubelet/util/util_windows.go +++ b/pkg/kubelet/util/util_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_windows_test.go b/pkg/kubelet/util/util_windows_test.go index e3f1cf500af79..319ea9fd8d535 100644 --- a/pkg/kubelet/util/util_windows_test.go +++ b/pkg/kubelet/util/util_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/kubelet/volume_host.go b/pkg/kubelet/volume_host.go index d0648825cbe34..5dd5694777731 100644 --- a/pkg/kubelet/volume_host.go +++ b/pkg/kubelet/volume_host.go @@ -219,12 +219,9 @@ func (kvh *kubeletVolumeHost) GetMounter() mount.Interface { return kvh.kubelet.mounter } -func (kvh *kubeletVolumeHost) GetHostName() string { - return kvh.kubelet.hostname -} - func (kvh *kubeletVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) { - node, err := kvh.kubelet.getNodeAnyWay() + // TODO: Pass proper context when VolumeHost interface methods support context parameters + node, err := kvh.kubelet.getNodeAnyWay(context.TODO()) if err != nil { return nil, fmt.Errorf("error retrieving node: %v", err) } @@ -270,7 +267,8 @@ func (kvh *kubeletVolumeHost) GetPodCertificateCredentialBundle(ctx context.Cont } func (kvh *kubeletVolumeHost) GetNodeLabels() (map[string]string, error) { - node, err := kvh.kubelet.GetNode() + // TODO: Pass proper context when VolumeHost interface methods support context parameters + node, err := kvh.kubelet.GetNode(context.TODO()) if err != nil { return nil, fmt.Errorf("error retrieving node: %v", err) } @@ -278,7 +276,8 @@ func (kvh *kubeletVolumeHost) GetNodeLabels() (map[string]string, error) { } func (kvh *kubeletVolumeHost) GetAttachedVolumesFromNodeStatus() (map[v1.UniqueVolumeName]string, error) { - node, err := kvh.kubelet.GetNode() + // TODO: Pass proper context when VolumeHost interface methods support context parameters + node, err := kvh.kubelet.GetNode(context.TODO()) if err != nil { return nil, fmt.Errorf("error retrieving node: %v", err) } diff --git a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go index 005c3bf71c1b9..6ee8b64d1c847 100644 --- a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go +++ b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go @@ -1613,7 +1613,7 @@ func createDswpWithVolumeWithCustomPluginMgr(pv *v1.PersistentVolume, pvc *v1.Pe actualStateOfWorld: fakeASW, pods: processedPods{ processedPods: make(map[types.UniquePodName]bool)}, - csiMigratedPluginManager: csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate), + csiMigratedPluginManager: csimigration.NewPluginManager(csiTranslator), intreeToCSITranslator: csiTranslator, volumePluginMgr: fakeVolumePluginMgr, } diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go index ec907e886bb60..79932984fba57 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go @@ -18,6 +18,7 @@ package reconciler import ( "context" + "errors" "fmt" "hash/fnv" "path/filepath" @@ -2008,6 +2009,27 @@ func waitForMount( } } +func waitForUnmount( + t *testing.T, + volumeName v1.UniqueVolumeName, + podName types.UniquePodName, + asw cache.ActualStateOfWorld) { + err := retryWithExponentialBackOff( + testOperationBackOffDuration, + func() (bool, error) { + if asw.PodHasMountedVolumes(podName) { + return false, nil + } + + return true, nil + }, + ) + + if err != nil { + t.Fatalf("Timed out waiting for pod %q to be unmount from volume %q.", podName, volumeName) + } +} + func waitForVolumeToExistInASW(t *testing.T, volumeName v1.UniqueVolumeName, asw cache.ActualStateOfWorld) { err := retryWithExponentialBackOff( testOperationBackOffDuration, @@ -2432,3 +2454,145 @@ func TestReconcileWithUpdateReconstructedFromAPIServer(t *testing.T) { } } } + +func TestReconstructedVolumeShouldUnmountSucceedAfterSetupFailed(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + // Arrange + volumePluginMgr, fakePlugin := volumetesting.GetTestKubeletVolumePluginMgr(t) + // fake Setup error + fakePlugin.SetUpHook = func(plugin volume.VolumePlugin, mounterArgs volume.MounterArgs) error { + if !mounterArgs.ReconstructedVolume { + // mock cleaned volume files while it's not a reconstructed volume + plugin.(*volumetesting.FakeVolumePlugin).NewUnmounterError = errors.New("unmounter failed to load volume data file") + } + return errors.New("csiRPCError") + } + seLinuxTranslator := util.NewFakeSELinuxLabelTranslator() + dsw := cache.NewDesiredStateOfWorld(volumePluginMgr, seLinuxTranslator) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + kubeClient := createTestClient() + fakeRecorder := &record.FakeRecorder{} + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + fakeHandler)) + rc := NewReconciler( + kubeClient, + true, /* controllerAttachDetachEnabled */ + reconcilerLoopSleepDuration, + waitForAttachTimeout, + nodeName, + dsw, + asw, + hasAddedPods, + oex, + mount.NewFakeMounter(nil), + hostutil.NewFakeHostUtil(nil), + volumePluginMgr, + kubeletPodsDir) + reconciler := rc.(*reconciler) + + mode := v1.PersistentVolumeFilesystem + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pv", + UID: "pvuid", + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Name: "pvc"}, + Capacity: v1.ResourceList{ + v1.ResourceStorage: resource.MustParse("1G"), + }, + VolumeMode: &mode, + }, + } + pvc := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + UID: "pvcuid", + }, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.VolumeResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceStorage: resource.MustParse("1G"), + }, + }, + VolumeName: "pv", + VolumeMode: &mode, + }, + Status: v1.PersistentVolumeClaimStatus{ + Capacity: v1.ResourceList{ + v1.ResourceStorage: resource.MustParse("1G"), + }, + }, + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "volume-name", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvc.Name, + }, + }, + }, + }, + }, + } + + volumeSpec := &volume.Spec{PersistentVolume: pv} + podName := util.GetUniquePodName(pod) + generatedVolumeName, err := dsw.AddPodToVolume(logger, + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + err = asw.AddAttachUncertainReconstructedVolume(logger, generatedVolumeName, volumeSpec, "" /* nodeName */, "fake/device/path") + if err != nil { + t.Fatalf("MarkVolumeAsAttached failed. Expected: Actual: <%v>", err) + } + + mounter, err := fakePlugin.NewMounter(volumeSpec, pod) + if err != nil { + t.Fatalf("NewMounter failed. Expected: Actual: <%v>", err) + } + + mapper, err := fakePlugin.NewBlockVolumeMapper(volumeSpec, pod) + if err != nil { + t.Fatalf("NewBlockVolumeMapper failed. Expected: Actual: <%v>", err) + } + markVolumeOpts := operationexecutor.MarkVolumeOpts{ + PodName: podName, + PodUID: pod.UID, + VolumeName: generatedVolumeName, + Mounter: mounter, + BlockVolumeMapper: mapper, + VolumeSpec: volumeSpec, + VolumeMountState: operationexecutor.VolumeMountUncertain, + } + _, err = asw.CheckAndMarkVolumeAsUncertainViaReconstruction(markVolumeOpts) + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + // Act first reconcile to trigger mount reconstructed volume + reconciler.reconcile(ctx) + waitForUncertainPodMount(t, generatedVolumeName, podName, asw) + + // mock remove pod + dsw.DeletePodFromVolume(podName, generatedVolumeName) + // Act second reconcile to trigger unmount reconstructed volume after removed pod from volume + reconciler.reconcile(ctx) + + // assert volume unmount succeed + waitForUnmount(t, generatedVolumeName, podName, asw) +} diff --git a/pkg/kubelet/volumemanager/volume_manager.go b/pkg/kubelet/volumemanager/volume_manager.go index f0c58f3bad1d2..16d580ce94e33 100644 --- a/pkg/kubelet/volumemanager/volume_manager.go +++ b/pkg/kubelet/volumemanager/volume_manager.go @@ -23,7 +23,6 @@ import ( "slices" "strconv" "strings" - "sync" "time" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -208,7 +207,7 @@ func NewVolumeManager( } intreeToCSITranslator := csitrans.New() - csiMigratedPluginManager := csimigration.NewPluginManager(intreeToCSITranslator, utilfeature.DefaultFeatureGate) + csiMigratedPluginManager := csimigration.NewPluginManager(intreeToCSITranslator) vm.intreeToCSITranslator = intreeToCSITranslator vm.csiMigratedPluginManager = csiMigratedPluginManager @@ -298,7 +297,7 @@ func (e *VolumeAttachLimitExceededError) Error() string { func (vm *volumeManager) Run(ctx context.Context, sourcesReady config.SourcesReady) { logger := klog.FromContext(ctx) - defer runtime.HandleCrash() + defer runtime.HandleCrashWithContext(ctx) if vm.kubeClient != nil { // start informer for CSIDriver @@ -511,21 +510,18 @@ func (vm *volumeManager) WaitForUnmount(ctx context.Context, pod *v1.Pod) error } func (vm *volumeManager) WaitForAllPodsUnmount(ctx context.Context, pods []*v1.Pod) error { - var ( - errors []error - wg sync.WaitGroup - ) - wg.Add(len(pods)) + if len(pods) == 0 { + return nil + } + + funcs := make([]func() error, 0, len(pods)) for _, pod := range pods { - go func(pod *v1.Pod) { - defer wg.Done() - if err := vm.WaitForUnmount(ctx, pod); err != nil { - errors = append(errors, err) - } - }(pod) + funcs = append(funcs, func() error { + return vm.WaitForUnmount(ctx, pod) + }) } - wg.Wait() - return utilerrors.NewAggregate(errors) + + return utilerrors.AggregateGoroutines(funcs...) } func (vm *volumeManager) getVolumesNotInDSW(uniquePodName types.UniquePodName, expectedVolumes []string) []string { diff --git a/pkg/kubelet/volumemanager/volume_manager_test.go b/pkg/kubelet/volumemanager/volume_manager_test.go index c9946e7b95caf..b0345ed1d66e9 100644 --- a/pkg/kubelet/volumemanager/volume_manager_test.go +++ b/pkg/kubelet/volumemanager/volume_manager_test.go @@ -18,6 +18,7 @@ package volumemanager import ( "context" + "fmt" "os" "reflect" "strconv" @@ -28,7 +29,9 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" kubetypes "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -656,29 +659,44 @@ func delayClaimBecomesBound( } func TestWaitForAllPodsUnmount(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("volumeManagerTest") - require.NoError(t, err, "Failed to create temp directory") - defer func() { - if err := os.RemoveAll(tmpDir); err != nil { - t.Errorf("Failed to remove temp directory: %v", err) - } - }() + tmpDir := t.TempDir() tests := []struct { name string + numPods int podMode v1.PersistentVolumeMode expectedError bool }{ { - name: "successful unmount", + name: "successful unmount - single pod", + numPods: 1, podMode: "", expectedError: false, }, { - name: "timeout waiting for unmount", + name: "timeout waiting for unmount - single pod", + numPods: 1, + podMode: v1.PersistentVolumeFilesystem, + expectedError: true, + }, + { + name: "concurrent unmount - multiple pods (10) with timeout errors", + numPods: 10, + podMode: v1.PersistentVolumeFilesystem, + expectedError: true, + }, + { + name: "concurrent unmount - many pods (20) with timeout errors", + numPods: 20, podMode: v1.PersistentVolumeFilesystem, expectedError: true, }, + { + name: "concurrent unmount - multiple pods without volumes", + numPods: 10, + podMode: "", + expectedError: false, + }, } for _, test := range tests { @@ -686,33 +704,190 @@ func TestWaitForAllPodsUnmount(t *testing.T) { var ctx context.Context = ktesting.Init(t) podManager := kubepod.NewBasicPodManager() - node, pod, pv, claim := createObjects(test.podMode, test.podMode) - kubeClient := fake.NewSimpleClientset(node, pod, pv, claim) + node, pods, pvs, claims := createMultiplePodsWithVolumes(test.numPods, test.podMode) + + objects := []runtime.Object{node} + for i := 0; i < test.numPods; i++ { + objects = append(objects, pods[i], pvs[i], claims[i]) + } + kubeClient := fake.NewClientset(objects...) manager := newTestVolumeManager(t, tmpDir, podManager, kubeClient, node) sourcesReady := config.NewSourcesReady(func(_ sets.Set[string]) bool { return true }) go manager.Run(ctx, sourcesReady) - podManager.SetPods([]*v1.Pod{pod}) + podManager.SetPods(pods) - go simulateVolumeInUseUpdate( - v1.UniqueVolumeName(node.Status.VolumesAttached[0].Name), - ctx.Done(), - manager) + if test.podMode != "" { + for i := 0; i < test.numPods; i++ { + volumeName := v1.UniqueVolumeName(node.Status.VolumesAttached[i].Name) + go simulateVolumeInUseUpdate(volumeName, ctx.Done(), manager) + } + + volumeMarkTimeout := 10*time.Second + time.Duration(test.numPods/10)*5*time.Second + err := wait.PollUntilContextTimeout(ctx, 50*time.Millisecond, volumeMarkTimeout, true, func(context.Context) (bool, error) { + inUseVolumes := manager.GetVolumesInUse() + return len(inUseVolumes) == test.numPods, nil + }) + + require.NoError(t, err, "Timeout waiting for all %d volumes to be marked as in-use", test.numPods) - err := manager.WaitForAttachAndMount(ctx, pod) - require.NoError(t, err, "Failed to wait for attach and mount") + type attachResult struct { + podName string + err error + } + resultChan := make(chan attachResult, test.numPods) + + for _, pod := range pods { + go func() { + err := manager.WaitForAttachAndMount(ctx, pod) + resultChan <- attachResult{ + podName: pod.Name, + err: err, + } + }() + } - ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + for i := 0; i < test.numPods; i++ { + result := <-resultChan + require.NoError(t, result.err, + "Failed to wait for attach and mount for pod %s", result.podName) + } + } + + unmountCtx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() - err = manager.WaitForAllPodsUnmount(ctx, []*v1.Pod{pod}) + + err := manager.WaitForAllPodsUnmount(unmountCtx, pods) if test.expectedError { require.ErrorIs(t, err, context.DeadlineExceeded, "Expected error due to timeout") + // Verify that we get exactly numPods errors in the aggregate + var aggErr utilerrors.Aggregate + require.ErrorAs(t, err, &aggErr, "Expected error to be an Aggregate error") + errs := aggErr.Errors() + require.Len(t, errs, test.numPods, "Expected %d errors but got %d", test.numPods, len(errs)) + + // Verify that each pod's volume name appears in the error messages, + // which proves different pods are being processed + errString := err.Error() + for i := 0; i < test.numPods; i++ { + volumeName := fmt.Sprintf("fake/fake-device-%d", i) + require.Contains(t, errString, volumeName, "Expected error to contain volume name %s for pod-%d", volumeName, i) + } } else { require.NoError(t, err, "Expected no error") } }) } } + +func createMultiplePodsWithVolumes(numPods int, pvMode v1.PersistentVolumeMode) (*v1.Node, []*v1.Pod, []*v1.PersistentVolume, []*v1.PersistentVolumeClaim) { + attachedVolumes := make([]v1.AttachedVolume, numPods) + for i := 0; i < numPods; i++ { + attachedVolumes[i] = v1.AttachedVolume{ + Name: v1.UniqueVolumeName(fmt.Sprintf("fake/fake-device-%d", i)), + DevicePath: fmt.Sprintf("fake/path-%d", i), + } + } + + node := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: testHostname}, + Status: v1.NodeStatus{ + VolumesAttached: attachedVolumes, + }, + } + + pods := make([]*v1.Pod, numPods) + pvs := make([]*v1.PersistentVolume, numPods) + claims := make([]*v1.PersistentVolumeClaim, numPods) + + for i := 0; i < numPods; i++ { + podName := fmt.Sprintf("pod-%d", i) + claimName := fmt.Sprintf("claim-%d", i) + pvName := fmt.Sprintf("pv-%d", i) + volumeName := fmt.Sprintf("vol-%d", i) + uid := fmt.Sprintf("uid-%d", i) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: "nsA", + UID: kubetypes.UID(uid), + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container1", + }, + }, + Volumes: []v1.Volume{ + { + Name: volumeName, + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: claimName, + }, + }, + }, + }, + }, + } + + switch pvMode { + case v1.PersistentVolumeFilesystem: + pod.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{ + { + Name: volumeName, + MountPath: fmt.Sprintf("/mnt/%s", volumeName), + }, + } + case v1.PersistentVolumeBlock: + pod.Spec.Containers[0].VolumeDevices = []v1.VolumeDevice{ + { + Name: volumeName, + DevicePath: fmt.Sprintf("/dev/%s", volumeName), + }, + } + } + + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + RBD: &v1.RBDPersistentVolumeSource{ + RBDImage: fmt.Sprintf("fake-device-%d", i), + }, + }, + ClaimRef: &v1.ObjectReference{ + Namespace: "nsA", + Name: claimName, + }, + VolumeMode: &pvMode, + }, + } + + claim := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: claimName, + Namespace: "nsA", + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: pvName, + VolumeMode: &pvMode, + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + + pods[i] = pod + pvs[i] = pv + claims[i] = claim + } + + return node, pods, pvs, claims +} diff --git a/pkg/kubelet/watchdog/watchdog_linux.go b/pkg/kubelet/watchdog/watchdog_linux.go index 912e7e5ebcc01..9237950961a95 100644 --- a/pkg/kubelet/watchdog/watchdog_linux.go +++ b/pkg/kubelet/watchdog/watchdog_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/watchdog/watchdog_linux_test.go b/pkg/kubelet/watchdog/watchdog_linux_test.go index 36f273a9d61f0..f45258d19094b 100644 --- a/pkg/kubelet/watchdog/watchdog_linux_test.go +++ b/pkg/kubelet/watchdog/watchdog_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. @@ -30,6 +29,11 @@ import ( "k8s.io/kubernetes/test/utils/ktesting/initoption" ) +// Some of these tests capture log output. Don't reduce the verbosity or they will fail! +func init() { + ktesting.SetDefaultVerbosity(5) +} + // Mock syncLoopHealthChecker type mockSyncLoopHealthChecker struct { healthCheckErr error diff --git a/pkg/kubelet/watchdog/watchdog_unsupported.go b/pkg/kubelet/watchdog/watchdog_unsupported.go index 2595549e6ec77..b0c000acb5f96 100644 --- a/pkg/kubelet/watchdog/watchdog_unsupported.go +++ b/pkg/kubelet/watchdog/watchdog_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/cpu_topology.go b/pkg/kubelet/winstats/cpu_topology.go index 771e2315ae76d..bee3f5f60d0e9 100644 --- a/pkg/kubelet/winstats/cpu_topology.go +++ b/pkg/kubelet/winstats/cpu_topology.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/cpu_topology_test.go b/pkg/kubelet/winstats/cpu_topology_test.go index 33f3f1c854887..06bb16162ab33 100644 --- a/pkg/kubelet/winstats/cpu_topology_test.go +++ b/pkg/kubelet/winstats/cpu_topology_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/network_stats.go b/pkg/kubelet/winstats/network_stats.go index 6ce7a730a56e6..ee23c41bdd5b7 100644 --- a/pkg/kubelet/winstats/network_stats.go +++ b/pkg/kubelet/winstats/network_stats.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2019 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/network_stats_test.go b/pkg/kubelet/winstats/network_stats_test.go index 124de535f967f..cb1740c3009ca 100644 --- a/pkg/kubelet/winstats/network_stats_test.go +++ b/pkg/kubelet/winstats/network_stats_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/perfcounter_nodestats_test.go b/pkg/kubelet/winstats/perfcounter_nodestats_test.go index a16393b18f151..e6832c1b4bbde 100644 --- a/pkg/kubelet/winstats/perfcounter_nodestats_test.go +++ b/pkg/kubelet/winstats/perfcounter_nodestats_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/perfcounter_nodestats_windows.go b/pkg/kubelet/winstats/perfcounter_nodestats_windows.go index 7083aea47ec24..f3c67052eabd6 100644 --- a/pkg/kubelet/winstats/perfcounter_nodestats_windows.go +++ b/pkg/kubelet/winstats/perfcounter_nodestats_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/perfcounters.go b/pkg/kubelet/winstats/perfcounters.go index 4b483d41d86fb..8d37b31bedd01 100644 --- a/pkg/kubelet/winstats/perfcounters.go +++ b/pkg/kubelet/winstats/perfcounters.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/perfcounters_test.go b/pkg/kubelet/winstats/perfcounters_test.go index ecb93e678785e..077cd6a8d3340 100644 --- a/pkg/kubelet/winstats/perfcounters_test.go +++ b/pkg/kubelet/winstats/perfcounters_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/version.go b/pkg/kubelet/winstats/version.go index 80e9442f8ceac..fbee466151e44 100644 --- a/pkg/kubelet/winstats/version.go +++ b/pkg/kubelet/winstats/version.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/winstats.go b/pkg/kubelet/winstats/winstats.go index 12ee8edfa96bf..1169c7c80b8ba 100644 --- a/pkg/kubelet/winstats/winstats.go +++ b/pkg/kubelet/winstats/winstats.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/winstats/winstats_test.go b/pkg/kubelet/winstats/winstats_test.go index 29776f89394e3..a0b84a7593528 100644 --- a/pkg/kubelet/winstats/winstats_test.go +++ b/pkg/kubelet/winstats/winstats_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubemark/hollow_kubelet.go b/pkg/kubemark/hollow_kubelet.go index ba2f619eb5202..a1f07a291c5f6 100644 --- a/pkg/kubemark/hollow_kubelet.go +++ b/pkg/kubemark/hollow_kubelet.go @@ -52,7 +52,6 @@ import ( "k8s.io/kubernetes/pkg/volume/iscsi" "k8s.io/kubernetes/pkg/volume/local" "k8s.io/kubernetes/pkg/volume/nfs" - "k8s.io/kubernetes/pkg/volume/portworx" "k8s.io/kubernetes/pkg/volume/projected" "k8s.io/kubernetes/pkg/volume/secret" "k8s.io/kubernetes/pkg/volume/util/hostutil" @@ -78,7 +77,6 @@ func volumePlugins() []volume.VolumePlugin { allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...) allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...) - allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...) allPlugins = append(allPlugins, local.ProbeVolumePlugins()...) allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) return allPlugins diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 079786473fbff..40bf85f9e45c9 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -26,22 +26,21 @@ import ( "time" apiserverinternalv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1" - appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" + autoscalingv2 "k8s.io/api/autoscaling/v2" batchv1 "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" // should this change, too? there are still certv1beta1.CSR printers, but not their v1 versions certificatesv1beta1 "k8s.io/api/certificates/v1beta1" coordinationv1 "k8s.io/api/coordination/v1" coordinationv1alpha2 "k8s.io/api/coordination/v1alpha2" apiv1 "k8s.io/api/core/v1" - discoveryv1beta1 "k8s.io/api/discovery/v1beta1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + discoveryv1 "k8s.io/api/discovery/v1" flowcontrolv1 "k8s.io/api/flowcontrol/v1" - networkingv1beta1 "k8s.io/api/networking/v1beta1" + networkingv1 "k8s.io/api/networking/v1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" resourceapi "k8s.io/api/resource/v1" schedulingv1 "k8s.io/api/scheduling/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -67,6 +66,7 @@ import ( "k8s.io/kubernetes/pkg/apis/flowcontrol" apihelpers "k8s.io/kubernetes/pkg/apis/flowcontrol/util" "k8s.io/kubernetes/pkg/apis/networking" + networkingutil "k8s.io/kubernetes/pkg/apis/networking/util" nodeapi "k8s.io/kubernetes/pkg/apis/node" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/rbac" @@ -142,29 +142,29 @@ func AddHandlers(h printers.PrintHandler) { replicaSetColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Desired", Type: "integer", Description: extensionsv1beta1.ReplicaSetSpec{}.SwaggerDoc()["replicas"]}, - {Name: "Current", Type: "integer", Description: extensionsv1beta1.ReplicaSetStatus{}.SwaggerDoc()["replicas"]}, - {Name: "Ready", Type: "integer", Description: extensionsv1beta1.ReplicaSetStatus{}.SwaggerDoc()["readyReplicas"]}, + {Name: "Desired", Type: "integer", Description: appsv1.ReplicaSetSpec{}.SwaggerDoc()["replicas"]}, + {Name: "Current", Type: "integer", Description: appsv1.ReplicaSetStatus{}.SwaggerDoc()["replicas"]}, + {Name: "Ready", Type: "integer", Description: appsv1.ReplicaSetStatus{}.SwaggerDoc()["readyReplicas"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, {Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."}, {Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."}, - {Name: "Selector", Type: "string", Priority: 1, Description: extensionsv1beta1.ReplicaSetSpec{}.SwaggerDoc()["selector"]}, + {Name: "Selector", Type: "string", Priority: 1, Description: appsv1.ReplicaSetSpec{}.SwaggerDoc()["selector"]}, } _ = h.TableHandler(replicaSetColumnDefinitions, printReplicaSet) _ = h.TableHandler(replicaSetColumnDefinitions, printReplicaSetList) daemonSetColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Desired", Type: "integer", Description: extensionsv1beta1.DaemonSetStatus{}.SwaggerDoc()["desiredNumberScheduled"]}, - {Name: "Current", Type: "integer", Description: extensionsv1beta1.DaemonSetStatus{}.SwaggerDoc()["currentNumberScheduled"]}, - {Name: "Ready", Type: "integer", Description: extensionsv1beta1.DaemonSetStatus{}.SwaggerDoc()["numberReady"]}, - {Name: "Up-to-date", Type: "integer", Description: extensionsv1beta1.DaemonSetStatus{}.SwaggerDoc()["updatedNumberScheduled"]}, - {Name: "Available", Type: "integer", Description: extensionsv1beta1.DaemonSetStatus{}.SwaggerDoc()["numberAvailable"]}, + {Name: "Desired", Type: "integer", Description: appsv1.DaemonSetStatus{}.SwaggerDoc()["desiredNumberScheduled"]}, + {Name: "Current", Type: "integer", Description: appsv1.DaemonSetStatus{}.SwaggerDoc()["currentNumberScheduled"]}, + {Name: "Ready", Type: "integer", Description: appsv1.DaemonSetStatus{}.SwaggerDoc()["numberReady"]}, + {Name: "Up-to-date", Type: "integer", Description: appsv1.DaemonSetStatus{}.SwaggerDoc()["updatedNumberScheduled"]}, + {Name: "Available", Type: "integer", Description: appsv1.DaemonSetStatus{}.SwaggerDoc()["numberAvailable"]}, {Name: "Node Selector", Type: "string", Description: apiv1.PodSpec{}.SwaggerDoc()["nodeSelector"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, {Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."}, {Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."}, - {Name: "Selector", Type: "string", Priority: 1, Description: extensionsv1beta1.DaemonSetSpec{}.SwaggerDoc()["selector"]}, + {Name: "Selector", Type: "string", Priority: 1, Description: appsv1.DaemonSetSpec{}.SwaggerDoc()["selector"]}, } _ = h.TableHandler(daemonSetColumnDefinitions, printDaemonSet) _ = h.TableHandler(daemonSetColumnDefinitions, printDaemonSetList) @@ -184,11 +184,11 @@ func AddHandlers(h printers.PrintHandler) { cronJobColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Schedule", Type: "string", Description: batchv1beta1.CronJobSpec{}.SwaggerDoc()["schedule"]}, - {Name: "Timezone", Type: "string", Description: batchv1beta1.CronJobSpec{}.SwaggerDoc()["timeZone"]}, - {Name: "Suspend", Type: "boolean", Description: batchv1beta1.CronJobSpec{}.SwaggerDoc()["suspend"]}, - {Name: "Active", Type: "integer", Description: batchv1beta1.CronJobStatus{}.SwaggerDoc()["active"]}, - {Name: "Last Schedule", Type: "string", Description: batchv1beta1.CronJobStatus{}.SwaggerDoc()["lastScheduleTime"]}, + {Name: "Schedule", Type: "string", Description: batchv1.CronJobSpec{}.SwaggerDoc()["schedule"]}, + {Name: "Timezone", Type: "string", Description: batchv1.CronJobSpec{}.SwaggerDoc()["timeZone"]}, + {Name: "Suspend", Type: "boolean", Description: batchv1.CronJobSpec{}.SwaggerDoc()["suspend"]}, + {Name: "Active", Type: "integer", Description: batchv1.CronJobStatus{}.SwaggerDoc()["active"]}, + {Name: "Last Schedule", Type: "string", Description: batchv1.CronJobStatus{}.SwaggerDoc()["lastScheduleTime"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, {Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."}, {Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."}, @@ -345,23 +345,23 @@ func AddHandlers(h printers.PrintHandler) { deploymentColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Ready", Type: "string", Description: "Number of the pod with ready state"}, - {Name: "Up-to-date", Type: "string", Description: extensionsv1beta1.DeploymentStatus{}.SwaggerDoc()["updatedReplicas"]}, - {Name: "Available", Type: "string", Description: extensionsv1beta1.DeploymentStatus{}.SwaggerDoc()["availableReplicas"]}, + {Name: "Up-to-date", Type: "string", Description: appsv1.DeploymentStatus{}.SwaggerDoc()["updatedReplicas"]}, + {Name: "Available", Type: "string", Description: appsv1.DeploymentStatus{}.SwaggerDoc()["availableReplicas"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, {Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."}, {Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."}, - {Name: "Selector", Type: "string", Priority: 1, Description: extensionsv1beta1.DeploymentSpec{}.SwaggerDoc()["selector"]}, + {Name: "Selector", Type: "string", Priority: 1, Description: appsv1.DeploymentSpec{}.SwaggerDoc()["selector"]}, } _ = h.TableHandler(deploymentColumnDefinitions, printDeployment) _ = h.TableHandler(deploymentColumnDefinitions, printDeploymentList) horizontalPodAutoscalerColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Reference", Type: "string", Description: autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["scaleTargetRef"]}, - {Name: "Targets", Type: "string", Description: autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["metrics"]}, - {Name: "MinPods", Type: "string", Description: autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["minReplicas"]}, - {Name: "MaxPods", Type: "string", Description: autoscalingv2beta1.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["maxReplicas"]}, - {Name: "Replicas", Type: "string", Description: autoscalingv2beta1.HorizontalPodAutoscalerStatus{}.SwaggerDoc()["currentReplicas"]}, + {Name: "Reference", Type: "string", Description: autoscalingv2.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["scaleTargetRef"]}, + {Name: "Targets", Type: "string", Description: autoscalingv2.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["metrics"]}, + {Name: "MinPods", Type: "string", Description: autoscalingv2.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["minReplicas"]}, + {Name: "MaxPods", Type: "string", Description: autoscalingv2.HorizontalPodAutoscalerSpec{}.SwaggerDoc()["maxReplicas"]}, + {Name: "Replicas", Type: "string", Description: autoscalingv2.HorizontalPodAutoscalerStatus{}.SwaggerDoc()["currentReplicas"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } _ = h.TableHandler(horizontalPodAutoscalerColumnDefinitions, printHorizontalPodAutoscaler) @@ -377,7 +377,7 @@ func AddHandlers(h printers.PrintHandler) { networkPolicyColumnDefinitioins := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Pod-Selector", Type: "string", Description: extensionsv1beta1.NetworkPolicySpec{}.SwaggerDoc()["podSelector"]}, + {Name: "Pod-Selector", Type: "string", Description: networkingv1.NetworkPolicySpec{}.SwaggerDoc()["podSelector"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } _ = h.TableHandler(networkPolicyColumnDefinitioins, printNetworkPolicy) @@ -485,7 +485,7 @@ func AddHandlers(h printers.PrintHandler) { controllerRevisionColumnDefinition := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Controller", Type: "string", Description: "Controller of the object"}, - {Name: "Revision", Type: "string", Description: appsv1beta1.ControllerRevision{}.SwaggerDoc()["revision"]}, + {Name: "Revision", Type: "string", Description: appsv1.ControllerRevision{}.SwaggerDoc()["revision"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } _ = h.TableHandler(controllerRevisionColumnDefinition, printControllerRevision) @@ -531,9 +531,9 @@ func AddHandlers(h printers.PrintHandler) { endpointSliceColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "AddressType", Type: "string", Description: discoveryv1beta1.EndpointSlice{}.SwaggerDoc()["addressType"]}, - {Name: "Ports", Type: "string", Description: discoveryv1beta1.EndpointSlice{}.SwaggerDoc()["ports"]}, - {Name: "Endpoints", Type: "string", Description: discoveryv1beta1.EndpointSlice{}.SwaggerDoc()["endpoints"]}, + {Name: "AddressType", Type: "string", Description: discoveryv1.EndpointSlice{}.SwaggerDoc()["addressType"]}, + {Name: "Ports", Type: "string", Description: discoveryv1.EndpointSlice{}.SwaggerDoc()["ports"]}, + {Name: "Endpoints", Type: "string", Description: discoveryv1.EndpointSlice{}.SwaggerDoc()["endpoints"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } _ = h.TableHandler(endpointSliceColumnDefinitions, printEndpointSlice) @@ -697,6 +697,21 @@ func AddHandlers(h printers.PrintHandler) { _ = h.TableHandler(nodeResourceSliceColumnDefinitions, printResourceSlice) _ = h.TableHandler(nodeResourceSliceColumnDefinitions, printResourceSliceList) + resourcePoolStatusRequestColumnDefinitions := []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "Driver", Type: "string", Description: "The driver to query pools for."}, + {Name: "Total", Type: "string", Description: "Total number of devices across all pools."}, + {Name: "Available", Type: "string", Description: "Number of available devices across all pools."}, + {Name: "Allocated", Type: "string", Description: "Number of allocated devices across all pools."}, + {Name: "Unavailable", Type: "string", Description: "Number of unavailable devices across all pools."}, + {Name: "Errors", Type: "string", Description: "Number of pools with validation errors."}, + {Name: "Pools", Type: "integer", Description: "Total number of matching pools."}, + {Name: "Status", Type: "string", Description: "Processing status."}, + {Name: "Completed", Type: "string", Description: "Time since the observation completed."}, + } + _ = h.TableHandler(resourcePoolStatusRequestColumnDefinitions, printResourcePoolStatusRequest) + _ = h.TableHandler(resourcePoolStatusRequestColumnDefinitions, printResourcePoolStatusRequestList) + deviceTaintColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, // The filter criteria are not printed. They could be lengthy (CEL!) and in practice many of them @@ -715,7 +730,7 @@ func AddHandlers(h printers.PrintHandler) { serviceCIDRColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "CIDRs", Type: "string", Description: networkingv1beta1.ServiceCIDRSpec{}.SwaggerDoc()["cidrs"]}, + {Name: "CIDRs", Type: "string", Description: networkingv1.ServiceCIDRSpec{}.SwaggerDoc()["cidrs"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } @@ -724,7 +739,7 @@ func AddHandlers(h printers.PrintHandler) { ipAddressColumnDefinitions := []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "ParentRef", Type: "string", Description: networkingv1beta1.IPAddressSpec{}.SwaggerDoc()["parentRef"]}, + {Name: "ParentRef", Type: "string", Description: networkingv1.IPAddressSpec{}.SwaggerDoc()["parentRef"]}, } _ = h.TableHandler(ipAddressColumnDefinitions, printIPAddress) @@ -744,6 +759,16 @@ func AddHandlers(h printers.PrintHandler) { } _ = h.TableHandler(workloadColumnDefinitions, printWorkload) _ = h.TableHandler(workloadColumnDefinitions, printWorkloadList) + + podGroupColumnDefinitions := []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "Policy", Type: "string", Description: schedulingv1alpha2.PodGroupSpec{}.SwaggerDoc()["schedulingPolicy"]}, + {Name: "Workload", Type: "string", Description: "Name of the referenced Workload object"}, + {Name: "Status", Type: "string", Description: "Status of the PodGroup"}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + } + _ = h.TableHandler(podGroupColumnDefinitions, printPodGroup) + _ = h.TableHandler(podGroupColumnDefinitions, printPodGroupList) } // Pass ports=nil for all ports. @@ -1502,6 +1527,11 @@ func printIngressClass(obj *networking.IngressClass, options printers.GenerateOp row := metav1.TableRow{ Object: runtime.RawExtension{Object: obj}, } + + name := obj.Name + if networkingutil.HasDefaultAnnotation(obj.ObjectMeta) { + name += " (default)" + } parameters := "" if obj.Spec.Parameters != nil { parameters = obj.Spec.Parameters.Kind @@ -1511,7 +1541,7 @@ func printIngressClass(obj *networking.IngressClass, options printers.GenerateOp parameters = parameters + "/" + obj.Spec.Parameters.Name } createTime := translateTimestampSince(obj.CreationTimestamp) - row.Cells = append(row.Cells, obj.Name, obj.Spec.Controller, parameters, createTime) + row.Cells = append(row.Cells, name, obj.Spec.Controller, parameters, createTime) return []metav1.TableRow{row}, nil } @@ -1990,6 +2020,10 @@ func printNode(obj *api.Node, options printers.GenerateOptions) ([]metav1.TableR if crVersion == "" { crVersion = "" } + // append arch to kernelVersion + if obj.Status.NodeInfo.Architecture != "" { + kernelVersion += " (" + obj.Status.NodeInfo.Architecture + ")" + } row.Cells = append(row.Cells, getNodeInternalIP(obj), getNodeExternalIP(obj), osImage, kernelVersion, crVersion) } @@ -3252,6 +3286,91 @@ func printResourceSliceList(list *resource.ResourceSliceList, options printers.G return rows, nil } +func printResourcePoolStatusRequest(obj *resource.ResourcePoolStatusRequest, options printers.GenerateOptions) ([]metav1.TableRow, error) { + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + + status := "Pending" + var poolCount int32 + var totalDevices, availableDevices, allocatedDevices, unavailableDevices, validationErrors string + totalDevices = "-" + availableDevices = "-" + allocatedDevices = "-" + unavailableDevices = "-" + validationErrors = "-" + + var completedTime *metav1.Time + if obj.Status != nil { + isFailed := false + for _, cond := range obj.Status.Conditions { + if cond.Type == resource.ResourcePoolStatusRequestConditionComplete && cond.Status == metav1.ConditionTrue { + status = "Complete" + completedTime = &cond.LastTransitionTime + break + } + if cond.Type == resource.ResourcePoolStatusRequestConditionFailed && cond.Status == metav1.ConditionTrue { + status = "Failed" + isFailed = true + break + } + } + if obj.Status.PoolCount != nil { + poolCount = *obj.Status.PoolCount + } + // Show truncation info in status + if status == "Complete" && poolCount > int32(len(obj.Status.Pools)) { + status = fmt.Sprintf("Complete (%d/%d pools)", len(obj.Status.Pools), poolCount) + } + // Aggregate device counts if not failed + if !isFailed { + var sumTotal, sumAvail, sumAlloc, sumUnavail int32 + var errorCount int32 + for _, p := range obj.Status.Pools { + if p.ValidationError != nil { + errorCount++ + } + if p.TotalDevices != nil { + sumTotal += *p.TotalDevices + } + if p.AvailableDevices != nil { + sumAvail += *p.AvailableDevices + } + if p.AllocatedDevices != nil { + sumAlloc += *p.AllocatedDevices + } + if p.UnavailableDevices != nil { + sumUnavail += *p.UnavailableDevices + } + } + totalDevices = strconv.Itoa(int(sumTotal)) + availableDevices = strconv.Itoa(int(sumAvail)) + allocatedDevices = strconv.Itoa(int(sumAlloc)) + unavailableDevices = strconv.Itoa(int(sumUnavail)) + validationErrors = strconv.Itoa(int(errorCount)) + } + } + completed := "" + if completedTime != nil { + completed = translateTimestampSince(*completedTime) + } + row.Cells = append(row.Cells, obj.Name, obj.Spec.Driver, totalDevices, availableDevices, allocatedDevices, unavailableDevices, validationErrors, poolCount, status, completed) + + return []metav1.TableRow{row}, nil +} + +func printResourcePoolStatusRequestList(list *resource.ResourcePoolStatusRequestList, options printers.GenerateOptions) ([]metav1.TableRow, error) { + rows := make([]metav1.TableRow, 0, len(list.Items)) + for i := range list.Items { + r, err := printResourcePoolStatusRequest(&list.Items[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + func printDeviceTaint(obj *resource.DeviceTaintRule, options printers.GenerateOptions) ([]metav1.TableRow, error) { row := metav1.TableRow{ Object: runtime.RawExtension{Object: obj}, @@ -3334,6 +3453,55 @@ func printWorkloadList(list *scheduling.WorkloadList, options printers.GenerateO } return rows, nil } + +func printPodGroup(obj *scheduling.PodGroup, options printers.GenerateOptions) ([]metav1.TableRow, error) { + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + + policy := "Basic" + if obj.Spec.SchedulingPolicy.Gang != nil { + policy = "Gang" + } + + status := "Pending" + conditionMap := make(map[string]*metav1.Condition) + for i := range obj.Status.Conditions { + cond := obj.Status.Conditions[i] + conditionMap[cond.Type] = &cond + } + if cond, ok := conditionMap[scheduling.PodGroupScheduled]; ok { + status = "Unschedulable" + if cond.Status == metav1.ConditionTrue { + status = "Scheduled" + } + } + if cond, ok := conditionMap[scheduling.DisruptionTarget]; ok { + status = cond.Reason + } + + workload := "" + if ref := obj.Spec.PodGroupTemplateRef; ref != nil { + workload = ref.Workload.WorkloadName + } + + row.Cells = append(row.Cells, obj.Name, policy, workload, status, translateTimestampSince(obj.CreationTimestamp)) + + return []metav1.TableRow{row}, nil +} + +func printPodGroupList(list *scheduling.PodGroupList, options printers.GenerateOptions) ([]metav1.TableRow, error) { + rows := make([]metav1.TableRow, 0, len(list.Items)) + for i := range list.Items { + r, err := printPodGroup(&list.Items[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + func printBoolPtr(value *bool) string { if value != nil { return printBool(*value) diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index bbf83082e3460..48f6aa8136921 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -26,6 +26,7 @@ import ( "github.com/google/go-cmp/cmp" apiv1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -754,6 +755,76 @@ func TestPrintNodeKernelVersion(t *testing.T) { } } +func TestPrintNodeArch(t *testing.T) { + + table := []struct { + node api.Node + expected []metav1.TableRow + }{ + { + node: api.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{KernelVersion: "fake-kernel-version", Architecture: "fake-arch-amd64"}, + Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, + }, + }, + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1.TableRow{ + { + // case 1: KernelVersion with Arch + Cells: []interface{}{"foo", "Unknown", "", "", "", "", "1.1.1.1", "", "fake-kernel-version (fake-arch-amd64)", ""}, + }, + }, + }, + { + node: api.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{Architecture: "fake-arch-arm64"}, + Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, + }, + }, + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1.TableRow{ + { + // case 2: KernelVersion unknown with Arch + Cells: []interface{}{"foo", "Unknown", "", "", "", "", "1.1.1.1", "", " (fake-arch-arm64)", ""}, + }, + }, + }, + { + node: api.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{KernelVersion: "fake-kernel-version"}, + Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, + }, + }, + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1.TableRow{ + { + // case 3: KernelVersion with Arch unknowngs + Cells: []interface{}{"foo", "Unknown", "", "", "", "", "1.1.1.1", "", "fake-kernel-version", ""}, + }, + }, + }, + } + + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) + if err != nil { + t.Fatalf("An error occurred generating table rows Node: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) + } + } +} + func TestPrintNodeContainerRuntimeVersion(t *testing.T) { table := []struct { @@ -1055,6 +1126,21 @@ func TestPrintIngressClass(t *testing.T) { }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test2", "example.com/controller2", "", "11y"}}}, + }, { + name: "example with default annotation", + ingressClass: &networking.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-default", + CreationTimestamp: metav1.Time{Time: time.Now().Add(time.Duration(-9 * 365 * 24 * time.Hour))}, + Annotations: map[string]string{ + networkingv1.AnnotationIsDefaultIngressClass: "true", + }, + }, + Spec: networking.IngressClassSpec{ + Controller: "example.com/controller", + }, + }, + expected: []metav1.TableRow{{Cells: []interface{}{"test-default (default)", "example.com/controller", "", "9y"}}}, }} for _, testCase := range testCases { @@ -7919,10 +8005,10 @@ func TestPrintWorkload(t *testing.T) { Namespace: "ns1", }, Spec: scheduling.WorkloadSpec{ - PodGroups: []scheduling.PodGroup{ + PodGroupTemplates: []scheduling.PodGroupTemplate{ { Name: "foo", - Policy: scheduling.PodGroupPolicy{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ Gang: &scheduling.GangSchedulingPolicy{ MinCount: 5, }, @@ -7954,10 +8040,10 @@ func TestPrintWorkloadList(t *testing.T) { Namespace: "ns1", }, Spec: scheduling.WorkloadSpec{ - PodGroups: []scheduling.PodGroup{ + PodGroupTemplates: []scheduling.PodGroupTemplate{ { Name: "foo", - Policy: scheduling.PodGroupPolicy{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ Gang: &scheduling.GangSchedulingPolicy{ MinCount: 5, }, @@ -7973,10 +8059,10 @@ func TestPrintWorkloadList(t *testing.T) { CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: scheduling.WorkloadSpec{ - PodGroups: []scheduling.PodGroup{ + PodGroupTemplates: []scheduling.PodGroupTemplate{ { Name: "bar", - Policy: scheduling.PodGroupPolicy{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ Gang: &scheduling.GangSchedulingPolicy{ MinCount: 5, }, @@ -8007,3 +8093,401 @@ func TestPrintWorkloadList(t *testing.T) { t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) } } + +func TestPrintPodGroup(t *testing.T) { + podGroup := &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup1", + Namespace: "ns1", + }, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w", + PodGroupTemplateName: "t1", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + }, + } + + // Columns: Name, Policy, Workload, Status, Age + expected := []metav1.TableRow{{Cells: []interface{}{"podgroup1", "Gang", "w", "Pending", ""}}} + + rows, err := printPodGroup(podGroup, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error generating table rows for PodGroup: %#v", err) + } + rows[0].Object.Object = nil + if !reflect.DeepEqual(expected, rows) { + t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) + } +} + +func TestPrintPodGroupList(t *testing.T) { + podGroupList := &scheduling.PodGroupList{ + Items: []scheduling.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup1", + Namespace: "ns1", + }, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w1", + PodGroupTemplateName: "t1", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup2", + Namespace: "ns1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w1", + PodGroupTemplateName: "t2", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup3", + Namespace: "ns1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w2", + PodGroupTemplateName: "t1", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup4", + Namespace: "ns1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: scheduling.PodGroupSpec{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup5", + Namespace: "ns1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: scheduling.PodGroupSpec{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + }, + }, + Status: scheduling.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionTrue, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup6", + Namespace: "ns1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: scheduling.PodGroupSpec{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + }, + }, + Status: scheduling.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podgroup7", + Namespace: "ns1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: scheduling.PodGroupSpec{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + }, + }, + Status: scheduling.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionTrue, + }, + { + Type: scheduling.DisruptionTarget, + Status: metav1.ConditionTrue, + Reason: scheduling.PodGroupReasonPreemptionByScheduler, + }, + }, + }, + }, + }, + } + + // Columns: Name, Policy, Workload, Status, Age + expected := []metav1.TableRow{ + {Cells: []interface{}{"podgroup1", "Gang", "w1", "Pending", ""}}, + {Cells: []interface{}{"podgroup2", "Gang", "w1", "Pending", "0s"}}, + {Cells: []interface{}{"podgroup3", "Basic", "w2", "Pending", "0s"}}, + {Cells: []interface{}{"podgroup4", "Basic", "", "Pending", "0s"}}, + {Cells: []interface{}{"podgroup5", "Basic", "", "Scheduled", "0s"}}, + {Cells: []interface{}{"podgroup6", "Basic", "", "Unschedulable", "0s"}}, + {Cells: []interface{}{"podgroup7", "Basic", "", "PreemptionByScheduler", "0s"}}, + } + + rows, err := printPodGroupList(podGroupList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error generating table rows for PodGroupList: %#v", err) + } + + for i := range rows { + rows[i].Object.Object = nil + } + + if !reflect.DeepEqual(expected, rows) { + t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) + } +} + +func TestPrintResourcePoolStatusRequest(t *testing.T) { + tests := []struct { + name string + request resourceapis.ResourcePoolStatusRequest + expected []metav1.TableRow + }{ + { + name: "ResourcePoolStatusRequest with Pending status", + request: resourceapis.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: resourceapis.ResourcePoolStatusRequestSpec{ + Driver: "driver.example.com", + }, + }, + // Columns: Name, Driver, Total, Available, Allocated, Unavailable, Errors, Pools, Status, Completed + expected: []metav1.TableRow{{Cells: []interface{}{"test-request", "driver.example.com", "-", "-", "-", "-", "-", int32(0), "Pending", ""}}}, + }, + { + name: "ResourcePoolStatusRequest with Complete status", + request: resourceapis.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: resourceapis.ResourcePoolStatusRequestSpec{ + Driver: "driver.example.com", + }, + Status: &resourceapis.ResourcePoolStatusRequestStatus{ + PoolCount: ptr.To[int32](3), + Pools: []resourceapis.PoolStatus{ + { + Driver: "driver.example.com", + PoolName: "pool-1", + Generation: 1, + TotalDevices: ptr.To[int32](10), + AvailableDevices: ptr.To[int32](5), + AllocatedDevices: ptr.To[int32](3), + UnavailableDevices: ptr.To[int32](2), + }, + { + Driver: "driver.example.com", + PoolName: "pool-2", + Generation: 1, + TotalDevices: ptr.To[int32](8), + AvailableDevices: ptr.To[int32](4), + AllocatedDevices: ptr.To[int32](2), + UnavailableDevices: ptr.To[int32](2), + }, + { + Driver: "driver.example.com", + PoolName: "pool-3", + Generation: 1, + TotalDevices: ptr.To[int32](6), + AvailableDevices: ptr.To[int32](3), + AllocatedDevices: ptr.To[int32](1), + UnavailableDevices: ptr.To[int32](2), + }, + }, + Conditions: []metav1.Condition{ + { + Type: resourceapis.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + }, + }, + }, + // Columns: Name, Driver, Total, Available, Allocated, Unavailable, Errors, Pools, Status, Completed + expected: []metav1.TableRow{{Cells: []interface{}{"test-request", "driver.example.com", "24", "12", "6", "6", "0", int32(3), "Complete", "5m"}}}, + }, + { + name: "ResourcePoolStatusRequest with Complete status and truncated pools", + request: resourceapis.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: resourceapis.ResourcePoolStatusRequestSpec{ + Driver: "driver.example.com", + }, + Status: &resourceapis.ResourcePoolStatusRequestStatus{ + PoolCount: ptr.To[int32](5), + Pools: []resourceapis.PoolStatus{ + { + Driver: "driver.example.com", + PoolName: "pool-1", + Generation: 1, + TotalDevices: ptr.To[int32](10), + AvailableDevices: ptr.To[int32](5), + AllocatedDevices: ptr.To[int32](3), + UnavailableDevices: ptr.To[int32](2), + }, + { + Driver: "driver.example.com", + PoolName: "pool-2", + Generation: 1, + TotalDevices: ptr.To[int32](8), + AvailableDevices: ptr.To[int32](4), + AllocatedDevices: ptr.To[int32](2), + UnavailableDevices: ptr.To[int32](2), + }, + }, + Conditions: []metav1.Condition{ + { + Type: resourceapis.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + }, + }, + }, + // Columns: Name, Driver, Total, Available, Allocated, Unavailable, Errors, Pools, Status, Completed + // Status shows truncation: "Complete (2/5 pools)" because len(Pools)=2 < PoolCount=5 + expected: []metav1.TableRow{{Cells: []interface{}{"test-request", "driver.example.com", "18", "9", "5", "4", "0", int32(5), "Complete (2/5 pools)", "5m"}}}, + }, + { + name: "ResourcePoolStatusRequest with Failed status", + request: resourceapis.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: resourceapis.ResourcePoolStatusRequestSpec{ + Driver: "driver.example.com", + }, + Status: &resourceapis.ResourcePoolStatusRequestStatus{ + Conditions: []metav1.Condition{ + { + Type: resourceapis.ResourcePoolStatusRequestConditionFailed, + Status: metav1.ConditionTrue, + }, + }, + }, + }, + // Columns: Name, Driver, Total, Available, Allocated, Unavailable, Errors, Pools, Status, Completed + expected: []metav1.TableRow{{Cells: []interface{}{"test-request", "driver.example.com", "-", "-", "-", "-", "-", int32(0), "Failed", ""}}}, + }, + { + name: "Complete with validation error pool", + request: resourceapis.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-request", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: resourceapis.ResourcePoolStatusRequestSpec{ + Driver: "driver.example.com", + }, + Status: &resourceapis.ResourcePoolStatusRequestStatus{ + PoolCount: ptr.To[int32](2), + Pools: []resourceapis.PoolStatus{ + { + Driver: "driver.example.com", + PoolName: "pool-healthy", + Generation: 1, + TotalDevices: ptr.To[int32](10), + AvailableDevices: ptr.To[int32](5), + AllocatedDevices: ptr.To[int32](3), + UnavailableDevices: ptr.To[int32](2), + }, + { + Driver: "driver.example.com", + PoolName: "pool-error", + Generation: 2, + ValidationError: ptr.To("pool data could not be validated"), + }, + }, + Conditions: []metav1.Condition{ + { + Type: resourceapis.ResourcePoolStatusRequestConditionComplete, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + }, + }, + }, + // Columns: Name, Driver, Total, Available, Allocated, Unavailable, Errors, Pools, Status, Completed + // Only the healthy pool's device counts are summed; the error pool has nil counts. + expected: []metav1.TableRow{{Cells: []interface{}{"test-request", "driver.example.com", "10", "5", "3", "2", "1", int32(2), "Complete", "5m"}}}, + }, + } + + for i, test := range tests { + rows, err := printResourcePoolStatusRequest(&test.request, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) + } + } +} diff --git a/pkg/probe/dialer_others.go b/pkg/probe/dialer_others.go index 7b02daff340eb..63a529a43dda7 100644 --- a/pkg/probe/dialer_others.go +++ b/pkg/probe/dialer_others.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/probe/dialer_windows.go b/pkg/probe/dialer_windows.go index ab0d020478c57..6bad852237e4e 100644 --- a/pkg/probe/dialer_windows.go +++ b/pkg/probe/dialer_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/probe/util_test.go b/pkg/probe/util_test.go index c56ee5b742c08..8da1eb0d3b4e5 100644 --- a/pkg/probe/util_test.go +++ b/pkg/probe/util_test.go @@ -78,7 +78,6 @@ func TestFindPortByName(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := findPortByName(tt.args.container, tt.args.portName) @@ -181,7 +180,6 @@ func TestResolveContainerPort(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := ResolveContainerPort(tt.args.param, tt.args.container) diff --git a/pkg/proxy/conntrack/cleanup.go b/pkg/proxy/conntrack/cleanup.go index 95e82317d265b..8035fcbdb90ab 100644 --- a/pkg/proxy/conntrack/cleanup.go +++ b/pkg/proxy/conntrack/cleanup.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2023 The Kubernetes Authors. @@ -113,7 +112,7 @@ func CleanStaleEntries(ct Interface, ipFamily v1.IPFamily, } } - var filters []netlink.CustomConntrackFilter + var flows []*netlink.ConntrackFlow for _, entry := range entries { // we only deal with UDP protocol entries if entry.Forward.Protocol != unix.IPPROTO_UDP { @@ -133,7 +132,8 @@ func CleanStaleEntries(ct Interface, ipFamily v1.IPFamily, // represent a serving endpoint of the service, we clear the entry. endpoints, ok := serviceIPEndpoints[net.JoinHostPort(origDst, origPortDstStr)] if ok && !endpoints.Has(net.JoinHostPort(replySrc, replyPortSrcStr)) { - filters = append(filters, filterForIPPortNAT(entry.Forward.DstIP, entry.Forward.DstPort, entry.Reverse.SrcIP, entry.Reverse.SrcPort, v1.ProtocolUDP)) + flows = append(flows, entry) + continue } // if the original destination port (--orig-port-dst) of the entry is service @@ -141,12 +141,13 @@ func CleanStaleEntries(ct Interface, ipFamily v1.IPFamily, // does not represent a serving endpoint of the service, we clear the entry. endpoints, ok = serviceNodePortEndpoints[origPortDst] if ok && !endpoints.Has(net.JoinHostPort(replySrc, replyPortSrcStr)) { - filters = append(filters, filterForPortNAT(entry.Forward.DstPort, entry.Reverse.SrcIP, entry.Reverse.SrcPort, v1.ProtocolUDP)) + flows = append(flows, entry) + continue } } var n int - if n, err = ct.ClearEntries(ipFamilyMap[ipFamily], filters...); err != nil { + if n, err = ct.DeleteEntries(ipFamilyMap[ipFamily], flows); err != nil { klog.ErrorS(err, "Failed to clear all conntrack entries", "ipFamily", ipFamily, "entriesDeleted", n, "took", time.Since(start)) } else { klog.V(4).InfoS("Finished reconciling conntrack entries", "ipFamily", ipFamily, "entriesDeleted", n, "took", time.Since(start)) @@ -160,45 +161,3 @@ var ipFamilyMap = map[v1.IPFamily]uint8{ v1.IPv4Protocol: unix.AF_INET, v1.IPv6Protocol: unix.AF_INET6, } - -// protocolMap maps v1.Protocol to the Assigned Internet Protocol Number. -// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml -var protocolMap = map[v1.Protocol]uint8{ - v1.ProtocolTCP: unix.IPPROTO_TCP, - v1.ProtocolUDP: unix.IPPROTO_UDP, - v1.ProtocolSCTP: unix.IPPROTO_SCTP, -} - -// filterForIPPortNAT returns *conntrackFilter to delete the conntrack entries for connections -// specified by the destination IP (original direction) and destination port (original direction) -// and source IP (reply direction) and source port (reply direction). -func filterForIPPortNAT(origDst net.IP, origPortDst uint16, replySrc net.IP, replyPortSrc uint16, protocol v1.Protocol) *conntrackFilter { - klog.V(6).InfoS("Adding conntrack filter for cleanup", "orig-dst", origDst.String(), "orig-port-dst", origPortDst, "reply-src", replySrc.String(), "reply-port-src", replyPortSrc, "protocol", protocol) - return &conntrackFilter{ - protocol: protocolMap[protocol], - original: &connectionTuple{ - dstIP: origDst, - dstPort: origPortDst, - }, - reply: &connectionTuple{ - srcIP: replySrc, - srcPort: replyPortSrc, - }, - } -} - -// filterForPortNAT returns *conntrackFilter to delete the conntrack entries for connections specified by the -// destination Port (original direction) and source IP (reply direction) and source port (reply direction). -func filterForPortNAT(origPortDst uint16, replySrc net.IP, replyPortSrc uint16, protocol v1.Protocol) *conntrackFilter { - klog.V(6).InfoS("Adding conntrack filter for cleanup", "orig-port-dst", origPortDst, "reply-src", replySrc.String(), "reply-port-src", replyPortSrc, "protocol", protocol) - return &conntrackFilter{ - protocol: protocolMap[protocol], - original: &connectionTuple{ - dstPort: origPortDst, - }, - reply: &connectionTuple{ - srcIP: replySrc, - srcPort: replyPortSrc, - }, - } -} diff --git a/pkg/proxy/conntrack/cleanup_test.go b/pkg/proxy/conntrack/cleanup_test.go index 369bc35a8af55..98afa791064bc 100644 --- a/pkg/proxy/conntrack/cleanup_test.go +++ b/pkg/proxy/conntrack/cleanup_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2023 The Kubernetes Authors. @@ -22,7 +21,6 @@ package conntrack import ( "fmt" "math/rand" - "net" "sort" "testing" @@ -318,12 +316,15 @@ func TestCleanStaleEntries(t *testing.T) { } t.Logf("entries before cleanup %d after cleanup %d", len(entriesBeforeCleanup), len(entriesAfterCleanup)) - fake := NewFake() - fake.entries = entriesBeforeCleanup + ct := newConntracker( + &fakeHandler{ + entries: entriesBeforeCleanup, + }, + ) legacyregistry.MustRegister(metrics.ReconcileConntrackFlowsDeletedEntriesTotal) - CleanStaleEntries(fake, testIPFamily, svcPortMap, endpointsMap) - actualEntries, _ := fake.ListEntries(ipFamilyMap[testIPFamily]) + CleanStaleEntries(ct, testIPFamily, svcPortMap, endpointsMap) + actualEntries, _ := ct.ListEntries(ipFamilyMap[testIPFamily]) metricCount, err := testutil.GetCounterMetricValue(metrics.ReconcileConntrackFlowsDeletedEntriesTotal.WithLabelValues(string(testIPFamily))) require.NoError(t, err) @@ -408,12 +409,12 @@ func TestPerformanceCleanStaleEntries(t *testing.T) { endpointsMap := make(proxy.EndpointsMap) _ = endpointsMap.Update(ect) - fake := NewFake() + flows := []*netlink.ConntrackFlow{} // 1 valid entry - fake.entries = append(fake.entries, generateConntrackEntry(testExternalIP, testServicePort, testServingEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) + flows = append(flows, generateConntrackEntry(testExternalIP, testServicePort, testServingEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) expectedEntries := 1 // 1 stale entry - fake.entries = append(fake.entries, generateConntrackEntry(testExternalIP, testServicePort, testDeletedEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) + flows = append(flows, generateConntrackEntry(testExternalIP, testServicePort, testDeletedEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) expectedDeleted := 1 // 1 M to the Service IP with random ports for i := 0; i < 1000*1000; i++ { @@ -423,11 +424,14 @@ func TestPerformanceCleanStaleEntries(t *testing.T) { } else { expectedEntries++ } - fake.entries = append(fake.entries, generateConntrackEntry(testExternalIP, port, testDeletedEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) + flows = append(flows, generateConntrackEntry(testExternalIP, port, testDeletedEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) } - CleanStaleEntries(fake, testIPFamily, svcPortMap, endpointsMap) - actualEntries, _ := fake.ListEntries(ipFamilyMap[testIPFamily]) + fake := &fakeHandler{entries: flows} + ct := newConntracker(fake) + + CleanStaleEntries(ct, testIPFamily, svcPortMap, endpointsMap) + actualEntries, _ := ct.ListEntries(ipFamilyMap[testIPFamily]) if len(actualEntries) != expectedEntries { t.Errorf("unexpected number of entries, got %d expected %d", len(actualEntries), expectedEntries) } @@ -503,103 +507,20 @@ func TestServiceWithoutEndpoints(t *testing.T) { endpointsMap := make(proxy.EndpointsMap) _ = endpointsMap.Update(ect) - fake := NewFake() + flows := []*netlink.ConntrackFlow{} // 1 valid entry - fake.entries = append(fake.entries, generateConntrackEntry(testExternalIP, testServicePort, testServingEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) + flows = append(flows, generateConntrackEntry(testExternalIP, testServicePort, testServingEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) // 1 stale entry - fake.entries = append(fake.entries, generateConntrackEntry(testExternalIP, testServicePort, testDeletedEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) + flows = append(flows, generateConntrackEntry(testExternalIP, testServicePort, testDeletedEndpointIP, testEndpointPort, unix.IPPROTO_UDP)) + ct := newConntracker( + &fakeHandler{ + entries: flows, + }, + ) - CleanStaleEntries(fake, testIPFamily, svcPortMap, endpointsMap) - actualEntries, _ := fake.ListEntries(ipFamilyMap[testIPFamily]) + CleanStaleEntries(ct, testIPFamily, svcPortMap, endpointsMap) + actualEntries, _ := ct.ListEntries(ipFamilyMap[testIPFamily]) if len(actualEntries) != 2 { t.Errorf("unexpected number of entries, got %d expected %d", len(actualEntries), 2) } } - -func TestFilterForIPPortNAT(t *testing.T) { - testCases := []struct { - name string - origDst net.IP - origPortDst uint16 - replySrc net.IP - replySrcPort uint16 - protocol v1.Protocol - expectedFilter *conntrackFilter - }{ - { - name: "ipv4 + SCTP", - origDst: netutils.ParseIPSloppy("10.96.0.10"), - origPortDst: 80, - replySrc: netutils.ParseIPSloppy("10.244.0.3"), - replySrcPort: 3000, - protocol: v1.ProtocolSCTP, - expectedFilter: &conntrackFilter{ - protocol: 132, - original: &connectionTuple{dstIP: netutils.ParseIPSloppy("10.96.0.10"), dstPort: 80}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("10.244.0.3"), srcPort: 3000}, - }, - }, - { - name: "ipv6 + UDP", - origDst: netutils.ParseIPSloppy("2001:db8:1::2"), - origPortDst: 443, - replySrc: netutils.ParseIPSloppy("4001:ab8::2"), - replySrcPort: 5000, - protocol: v1.ProtocolUDP, - expectedFilter: &conntrackFilter{ - protocol: 17, - original: &connectionTuple{dstIP: netutils.ParseIPSloppy("2001:db8:1::2"), dstPort: 443}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("4001:ab8::2"), srcPort: 5000}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expectedFilter, filterForIPPortNAT(tc.origDst, tc.origPortDst, tc.replySrc, tc.replySrcPort, tc.protocol)) - }) - } -} - -func TestFilterForPortNAT(t *testing.T) { - testCases := []struct { - name string - origPortDst uint16 - replySrc net.IP - replySrcPort uint16 - protocol v1.Protocol - expectedFamily netlink.InetFamily - expectedFilter *conntrackFilter - }{ - { - name: "ipv4 + TCP", - origPortDst: 80, - replySrc: netutils.ParseIPSloppy("10.96.0.10"), - replySrcPort: 3000, - protocol: v1.ProtocolTCP, - expectedFilter: &conntrackFilter{ - protocol: 6, - original: &connectionTuple{dstPort: 80}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("10.96.0.10"), srcPort: 3000}, - }, - }, - { - name: "ipv6 + UDP", - origPortDst: 8000, - replySrc: netutils.ParseIPSloppy("2001:db8:1::2"), - replySrcPort: 5000, - protocol: v1.ProtocolUDP, - expectedFilter: &conntrackFilter{ - protocol: 17, - original: &connectionTuple{dstPort: 8000}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("2001:db8:1::2"), srcPort: 5000}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expectedFilter, filterForPortNAT(tc.origPortDst, tc.replySrc, tc.replySrcPort, tc.protocol)) - }) - } -} diff --git a/pkg/proxy/conntrack/conntrack.go b/pkg/proxy/conntrack/conntrack.go index 53cf6ba57f3ff..e276d32aa88bf 100644 --- a/pkg/proxy/conntrack/conntrack.go +++ b/pkg/proxy/conntrack/conntrack.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. @@ -25,16 +24,14 @@ import ( "github.com/vishvananda/netlink" "k8s.io/client-go/util/retry" - "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/proxy/util" ) // Interface for dealing with conntrack type Interface interface { ListEntries(ipFamily uint8) ([]*netlink.ConntrackFlow, error) - // ClearEntries deletes conntrack entries for connections of the given IP family, - // filtered by the given filters. - ClearEntries(ipFamily uint8, filters ...netlink.CustomConntrackFilter) (int, error) + // DeleteEntries deletes the given conntrack entries. + DeleteEntries(ipFamily uint8, flows []*netlink.ConntrackFlow) (int, error) } // netlinkHandler allows consuming real and mockable implementation for testing. @@ -67,19 +64,19 @@ func (ct *conntracker) ListEntries(ipFamily uint8) (entries []*netlink.Conntrack return entries, err } -// ClearEntries deletes conntrack entries for connections of the given IP family, -// filtered by the given filters. -func (ct *conntracker) ClearEntries(ipFamily uint8, filters ...netlink.CustomConntrackFilter) (int, error) { - if len(filters) == 0 { - klog.V(7).InfoS("no conntrack filters provided") +// DeleteEntries deletes the given conntrack entries on the specified IP family. +// It returns the number of deleted entries and an error if any. +func (ct *conntracker) DeleteEntries(ipFamily uint8, flows []*netlink.ConntrackFlow) (int, error) { + if len(flows) == 0 { return 0, nil } + filter := newFlowFilter(flows) var n uint var err error err = retry.OnError(util.MaxAttemptsEINTR, util.ShouldRetryOnEINTR, func() error { var count uint - count, err = ct.handler.ConntrackDeleteFilters(netlink.ConntrackTable, netlink.InetFamily(ipFamily), filters...) + count, err = ct.handler.ConntrackDeleteFilters(netlink.ConntrackTable, netlink.InetFamily(ipFamily), filter) n += count return err }) diff --git a/pkg/proxy/conntrack/conntrack_test.go b/pkg/proxy/conntrack/conntrack_test.go index 98f005a186cc1..19e9281da8ef5 100644 --- a/pkg/proxy/conntrack/conntrack_test.go +++ b/pkg/proxy/conntrack/conntrack_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -22,85 +21,146 @@ package conntrack import ( "testing" - "github.com/stretchr/testify/require" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" netutils "k8s.io/utils/net" ) -type fakeHandler struct { - tableType netlink.ConntrackTableType - ipFamily netlink.InetFamily - filters []*conntrackFilter -} - -func (f *fakeHandler) ConntrackTableList(_ netlink.ConntrackTableType, _ netlink.InetFamily) ([]*netlink.ConntrackFlow, error) { - return nil, nil -} - -func (f *fakeHandler) ConntrackDeleteFilters(tableType netlink.ConntrackTableType, family netlink.InetFamily, netlinkFilters ...netlink.CustomConntrackFilter) (uint, error) { - f.tableType = tableType - f.ipFamily = family - f.filters = make([]*conntrackFilter, 0, len(netlinkFilters)) - for _, netlinkFilter := range netlinkFilters { - f.filters = append(f.filters, netlinkFilter.(*conntrackFilter)) +func TestConntracker_DeleteEntries(t *testing.T) { + // Define some common flows + flow1 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("1.1.1.1"), + DstIP: netutils.ParseIPSloppy("2.2.2.2"), + SrcPort: 1000, + DstPort: 2000, + Protocol: 6, + }, + Reverse: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("2.2.2.2"), + DstIP: netutils.ParseIPSloppy("1.1.1.1"), + SrcPort: 2000, + DstPort: 1000, + Protocol: 6, + }, + } + flow2 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("3.3.3.3"), + DstIP: netutils.ParseIPSloppy("4.4.4.4"), + SrcPort: 3000, + DstPort: 4000, + Protocol: 17, + }, + Reverse: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("4.4.4.4"), + DstIP: netutils.ParseIPSloppy("3.3.3.3"), + SrcPort: 4000, + DstPort: 3000, + Protocol: 17, + }, + } + flow3 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("5.5.5.5"), + DstIP: netutils.ParseIPSloppy("6.6.6.6"), + SrcPort: 5000, + DstPort: 6000, + Protocol: 132, + }, + Reverse: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("6.6.6.6"), + DstIP: netutils.ParseIPSloppy("5.5.5.5"), + SrcPort: 6000, + DstPort: 5000, + Protocol: 132, + }, + } + // Edge case: Partial flow (missing some fields) + flowEmpty := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + Protocol: 6, + }, } - return uint(len(f.filters)), nil -} - -var _ netlinkHandler = (*fakeHandler)(nil) -func TestConntracker_ClearEntries(t *testing.T) { testCases := []struct { - name string - ipFamily uint8 - filters []netlink.CustomConntrackFilter + name string + initialEntries []*netlink.ConntrackFlow + deleteFlows []*netlink.ConntrackFlow + expectedDeleted int + expectedLeft int }{ { - name: "single IPv6 filter", - ipFamily: unix.AF_INET6, - filters: []netlink.CustomConntrackFilter{ - &conntrackFilter{ - protocol: 17, - original: &connectionTuple{dstPort: 8000}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("2001:db8:1::2")}, - }, - }, + name: "Delete single flow", + initialEntries: []*netlink.ConntrackFlow{flow1, flow2, flow3}, + deleteFlows: []*netlink.ConntrackFlow{flow1}, + expectedDeleted: 1, + expectedLeft: 2, }, { - name: "multiple IPv4 filters", - ipFamily: unix.AF_INET, - filters: []netlink.CustomConntrackFilter{ - &conntrackFilter{ - protocol: 6, - original: &connectionTuple{dstPort: 3000}, - }, - &conntrackFilter{ - protocol: 17, - original: &connectionTuple{dstPort: 5000}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("10.244.0.3")}, - }, - &conntrackFilter{ - protocol: 132, - original: &connectionTuple{dstIP: netutils.ParseIPSloppy("10.96.0.10")}, - reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("10.244.0.3")}, - }, - }, + name: "Delete multiple flows", + initialEntries: []*netlink.ConntrackFlow{flow1, flow2, flow3}, + deleteFlows: []*netlink.ConntrackFlow{flow1, flow3}, + expectedDeleted: 2, + expectedLeft: 1, + }, + { + name: "Delete non-existent flow", + initialEntries: []*netlink.ConntrackFlow{flow1, flow2}, + deleteFlows: []*netlink.ConntrackFlow{flow3}, + expectedDeleted: 0, + expectedLeft: 2, + }, + { + name: "Delete flow with empty fields (should match nothing)", + initialEntries: []*netlink.ConntrackFlow{flow1, flow2, flow3}, + deleteFlows: []*netlink.ConntrackFlow{flowEmpty}, + expectedDeleted: 0, + expectedLeft: 3, + }, + { + name: "Delete all flows", + initialEntries: []*netlink.ConntrackFlow{flow1, flow2, flow3}, + deleteFlows: []*netlink.ConntrackFlow{flow1, flow2, flow3}, + expectedDeleted: 3, + expectedLeft: 0, + }, + { + name: "Delete with empty list", + initialEntries: []*netlink.ConntrackFlow{flow1, flow2}, + deleteFlows: []*netlink.ConntrackFlow{}, + expectedDeleted: 0, + expectedLeft: 2, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - handler := &fakeHandler{} + // avoid mutations of entries between tests + entries := make([]*netlink.ConntrackFlow, len(tc.initialEntries)) + copy(entries, tc.initialEntries) + + handler := &fakeHandler{ + entries: entries, + } ct := newConntracker(handler) - _, err := ct.ClearEntries(tc.ipFamily, tc.filters...) - require.NoError(t, err) - require.Equal(t, netlink.ConntrackTableType(netlink.ConntrackTable), handler.tableType) - require.Equal(t, netlink.InetFamily(tc.ipFamily), handler.ipFamily) - require.Equal(t, len(tc.filters), len(handler.filters)) - for i := 0; i < len(tc.filters); i++ { - require.Equal(t, tc.filters[i], handler.filters[i]) + + n, err := ct.DeleteEntries(unix.AF_INET, tc.deleteFlows) + if err != nil { + t.Fatalf("DeleteEntries() error = %v, want nil", err) + } + + if n != tc.expectedDeleted { + t.Errorf("DeleteEntries() = %d, want %d", n, tc.expectedDeleted) + } + + left, err := ct.ListEntries(unix.AF_INET) + if err != nil { + t.Fatalf("ListEntries() error = %v", err) + } + if len(left) != tc.expectedLeft { + t.Errorf("ListEntries() left = %d, want %d", len(left), tc.expectedLeft) } }) } diff --git a/pkg/proxy/conntrack/fake.go b/pkg/proxy/conntrack/fake.go index 8902da7a6f2bb..8374f775226b6 100644 --- a/pkg/proxy/conntrack/fake.go +++ b/pkg/proxy/conntrack/fake.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. @@ -20,55 +19,60 @@ limitations under the License. package conntrack import ( + "sync" + "github.com/vishvananda/netlink" ) -// FakeInterface implements Interface by just recording entries that have been cleared. -type FakeInterface struct { - entries []*netlink.ConntrackFlow - netlinkRequests int // try to get the estimated number of netlink request +// NewFake creates a new FakeInterface +func NewFake() Interface { + return newConntracker( + &fakeHandler{ + entries: make([]*netlink.ConntrackFlow, 0), + }, + ) } -var _ Interface = &FakeInterface{} +var _ netlinkHandler = (*fakeHandler)(nil) -// NewFake creates a new FakeInterface -func NewFake() *FakeInterface { - return &FakeInterface{entries: make([]*netlink.ConntrackFlow, 0)} +type fakeHandler struct { + mu sync.Mutex + entries []*netlink.ConntrackFlow + netlinkRequests int // try to get the estimated number of netlink request } -// ListEntries is part of Interface -func (fake *FakeInterface) ListEntries(_ uint8) ([]*netlink.ConntrackFlow, error) { - entries := make([]*netlink.ConntrackFlow, len(fake.entries)) - copy(entries, fake.entries) - // 1 netlink request to dump the table - // https://github.com/vishvananda/netlink/blob/0af32151e72b990c271ef6268e8aadb7e015f2bd/conntrack_linux.go#L93-L94 - fake.netlinkRequests++ - return entries, nil +func (f *fakeHandler) ConntrackTableList(_ netlink.ConntrackTableType, _ netlink.InetFamily) ([]*netlink.ConntrackFlow, error) { + f.mu.Lock() + defer f.mu.Unlock() + f.netlinkRequests++ + return f.entries, nil } -// ClearEntries is part of Interface -func (fake *FakeInterface) ClearEntries(_ uint8, filters ...netlink.CustomConntrackFilter) (int, error) { - var flows []*netlink.ConntrackFlow - before := len(fake.entries) +func (f *fakeHandler) ConntrackDeleteFilters(tableType netlink.ConntrackTableType, family netlink.InetFamily, netlinkFilters ...netlink.CustomConntrackFilter) (uint, error) { + f.mu.Lock() + defer f.mu.Unlock() + // 1 netlink request to dump the table // https://github.com/vishvananda/netlink/blob/0af32151e72b990c271ef6268e8aadb7e015f2bd/conntrack_linux.go#L163 - fake.netlinkRequests++ + f.netlinkRequests++ + var dataplaneFlows []*netlink.ConntrackFlow + before := len(f.entries) - for _, flow := range fake.entries { + for _, flow := range f.entries { var matched bool - for _, filter := range filters { + for _, filter := range netlinkFilters { matched = filter.MatchConntrackFlow(flow) if matched { // 1 netlink request to delete the flow // https://github.com/vishvananda/netlink/blob/0af32151e72b990c271ef6268e8aadb7e015f2bd/conntrack_linux.go#L182 - fake.netlinkRequests++ - break + f.netlinkRequests++ } } + // no filter matched, keep the flow if !matched { - flows = append(flows, flow) + dataplaneFlows = append(dataplaneFlows, flow) } } - fake.entries = flows - return before - len(fake.entries), nil + f.entries = dataplaneFlows + return uint(before - len(f.entries)), nil } diff --git a/pkg/proxy/conntrack/filter.go b/pkg/proxy/conntrack/filter.go index 9bb20ffd5a8b4..6b451f828a673 100644 --- a/pkg/proxy/conntrack/filter.go +++ b/pkg/proxy/conntrack/filter.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. @@ -20,82 +19,50 @@ limitations under the License. package conntrack import ( - "net" + "fmt" "github.com/vishvananda/netlink" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" ) -type connectionTuple struct { - srcIP net.IP - srcPort uint16 - dstIP net.IP - dstPort uint16 +type flowFilter struct { + flows sets.Set[string] } -type conntrackFilter struct { - protocol uint8 - original *connectionTuple - reply *connectionTuple -} - -var _ netlink.CustomConntrackFilter = (*conntrackFilter)(nil) +var _ netlink.CustomConntrackFilter = (*flowFilter)(nil) -// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter -// false otherwise. -func (f *conntrackFilter) MatchConntrackFlow(flow *netlink.ConntrackFlow) bool { - // return false in case of empty filter - if f.protocol == 0 && f.original == nil && f.reply == nil { - return false +func newFlowFilter(flows []*netlink.ConntrackFlow) *flowFilter { + f := &flowFilter{ + flows: sets.New[string](), } - - // -p, --protonum proto [Layer 4 Protocol, eg. 'tcp'] - if f.protocol != 0 && f.protocol != flow.Forward.Protocol { - return false + for _, flow := range flows { + f.flows.Insert(flowKey(flow)) } + return f +} - // filter on original direction - if f.original != nil { - // --orig-src ip [Source address from original direction] - if f.original.srcIP != nil && !f.original.srcIP.Equal(flow.Forward.SrcIP) { - return false - } - // --orig-dst ip [Destination address from original direction] - if f.original.dstIP != nil && !f.original.dstIP.Equal(flow.Forward.DstIP) { - return false - } - // --orig-port-src port [Source port from original direction] - if f.original.srcPort != 0 && f.original.srcPort != flow.Forward.SrcPort { - return false - } - // --orig-port-dst port [Destination port from original direction] - if f.original.dstPort != 0 && f.original.dstPort != flow.Forward.DstPort { - return false - } - } +// flowKey returns a unique key for the given flow. +func flowKey(flow *netlink.ConntrackFlow) string { + return fmt.Sprintf("%d#%s#%d#%s#%d#%s#%d#%s#%d", + flow.Forward.Protocol, + flow.Forward.SrcIP.String(), + flow.Forward.SrcPort, + flow.Forward.DstIP.String(), + flow.Forward.DstPort, + flow.Reverse.SrcIP.String(), + flow.Reverse.SrcPort, + flow.Reverse.DstIP.String(), + flow.Reverse.DstPort) +} - // filter on reply direction - if f.reply != nil { - // --reply-src ip [Source NAT ip] - if f.reply.srcIP != nil && !f.reply.srcIP.Equal(flow.Reverse.SrcIP) { - return false - } - // --reply-dst ip [Destination NAT ip] - if f.reply.dstIP != nil && !f.reply.dstIP.Equal(flow.Reverse.DstIP) { - return false - } - // --reply-port-src port [Source port from reply direction] - if f.reply.srcPort != 0 && f.reply.srcPort != flow.Reverse.SrcPort { - return false - } - // --reply-port-dst port [Destination port from reply direction] - if f.reply.dstPort != 0 && f.reply.dstPort != flow.Reverse.DstPort { - return false - } +// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter +func (f *flowFilter) MatchConntrackFlow(flow *netlink.ConntrackFlow) bool { + if f.flows.Has(flowKey(flow)) { + // for debugging conntrack issues and understanding what entries are deleted + klog.V(6).InfoS("Deleting conntrack entry", "flow", flow) + return true } - - // appending a new line to the flow makes klog print multiline log which is easier to debug and understand. - klog.V(5).InfoS("Deleting conntrack entry", "flow", flow.String()+"\n") - return true + return false } diff --git a/pkg/proxy/conntrack/filter_test.go b/pkg/proxy/conntrack/filter_test.go index a8b8a4b3f8704..ad85afd7f53c7 100644 --- a/pkg/proxy/conntrack/filter_test.go +++ b/pkg/proxy/conntrack/filter_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. @@ -22,151 +21,154 @@ package conntrack import ( "testing" - "github.com/stretchr/testify/require" "github.com/vishvananda/netlink" - "golang.org/x/sys/unix" netutils "k8s.io/utils/net" ) -func applyFilter(flowList []netlink.ConntrackFlow, ipv4Filter *conntrackFilter, ipv6Filter *conntrackFilter) (ipv4Match, ipv6Match int) { - for _, flow := range flowList { - if ipv4Filter.MatchConntrackFlow(&flow) == true { - ipv4Match++ - } - if ipv6Filter.MatchConntrackFlow(&flow) == true { - ipv6Match++ - } +func TestFlowFilter(t *testing.T) { + flow1 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("1.1.1.1"), + DstIP: netutils.ParseIPSloppy("2.2.2.2"), + SrcPort: 1000, + DstPort: 2000, + Protocol: 6, + }, + } + flow2 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("3.3.3.3"), + DstIP: netutils.ParseIPSloppy("4.4.4.4"), + SrcPort: 3000, + DstPort: 4000, + Protocol: 17, + }, + } + flowEmpty := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + Protocol: 6, + }, } - return ipv4Match, ipv6Match -} - -func TestConntrackFilter(t *testing.T) { - var flowList []netlink.ConntrackFlow - flow1 := netlink.ConntrackFlow{} - flow1.FamilyType = unix.AF_INET - flow1.Forward.SrcIP = netutils.ParseIPSloppy("10.0.0.1") - flow1.Forward.DstIP = netutils.ParseIPSloppy("20.0.0.1") - flow1.Forward.SrcPort = 1000 - flow1.Forward.DstPort = 2000 - flow1.Forward.Protocol = 17 - flow1.Reverse.SrcIP = netutils.ParseIPSloppy("20.0.0.1") - flow1.Reverse.DstIP = netutils.ParseIPSloppy("192.168.1.1") - flow1.Reverse.SrcPort = 2000 - flow1.Reverse.DstPort = 1000 - flow1.Reverse.Protocol = 17 - - flow2 := netlink.ConntrackFlow{} - flow2.FamilyType = unix.AF_INET - flow2.Forward.SrcIP = netutils.ParseIPSloppy("10.0.0.2") - flow2.Forward.DstIP = netutils.ParseIPSloppy("20.0.0.2") - flow2.Forward.SrcPort = 5000 - flow2.Forward.DstPort = 6000 - flow2.Forward.Protocol = 6 - flow2.Reverse.SrcIP = netutils.ParseIPSloppy("20.0.0.2") - flow2.Reverse.DstIP = netutils.ParseIPSloppy("192.168.1.1") - flow2.Reverse.SrcPort = 6000 - flow2.Reverse.DstPort = 5000 - flow2.Reverse.Protocol = 6 - - flow3 := netlink.ConntrackFlow{} - flow3.FamilyType = unix.AF_INET6 - flow3.Forward.SrcIP = netutils.ParseIPSloppy("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee") - flow3.Forward.DstIP = netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd") - flow3.Forward.SrcPort = 1000 - flow3.Forward.DstPort = 2000 - flow3.Forward.Protocol = 132 - flow3.Reverse.SrcIP = netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd") - flow3.Reverse.DstIP = netutils.ParseIPSloppy("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee") - flow3.Reverse.SrcPort = 2000 - flow3.Reverse.DstPort = 1000 - flow3.Reverse.Protocol = 132 - flowList = append(flowList, flow1, flow2, flow3) testCases := []struct { - name string - filterV4 *conntrackFilter - filterV6 *conntrackFilter - expectedV4Matches int - expectedV6Matches int + name string + filterFlows []*netlink.ConntrackFlow + matchFlow *netlink.ConntrackFlow + expectedMatch bool }{ { - name: "Empty filter", - filterV4: &conntrackFilter{}, - filterV6: &conntrackFilter{}, - expectedV4Matches: 0, - expectedV6Matches: 0, + name: "Match existing flow", + filterFlows: []*netlink.ConntrackFlow{flow1, flow2}, + matchFlow: flow1, + expectedMatch: true, }, { - name: "Protocol filter", - filterV4: &conntrackFilter{protocol: 6}, - filterV6: &conntrackFilter{protocol: 17}, - expectedV4Matches: 1, - expectedV6Matches: 1, + name: "No match for non-existent flow", + filterFlows: []*netlink.ConntrackFlow{flow1}, + matchFlow: flow2, + expectedMatch: false, }, { - name: "Original Source IP filter", - filterV4: &conntrackFilter{original: &connectionTuple{srcIP: netutils.ParseIPSloppy("10.0.0.1")}}, - filterV6: &conntrackFilter{original: &connectionTuple{srcIP: netutils.ParseIPSloppy("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + name: "No match for empty flow against specific filter", + filterFlows: []*netlink.ConntrackFlow{flow1}, + matchFlow: flowEmpty, + expectedMatch: false, }, - { - name: "Original Destination IP filter", - filterV4: &conntrackFilter{original: &connectionTuple{dstIP: netutils.ParseIPSloppy("20.0.0.1")}}, - filterV6: &conntrackFilter{original: &connectionTuple{dstIP: netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + f := newFlowFilter(tc.filterFlows) + match := f.MatchConntrackFlow(tc.matchFlow) + if match != tc.expectedMatch { + t.Errorf("MatchConntrackFlow() = %v, want %v", match, tc.expectedMatch) + } + }) + } +} + +func TestFlowFilter_Exhaustive(t *testing.T) { + flow1 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("1.1.1.1"), + DstIP: netutils.ParseIPSloppy("2.2.2.2"), + SrcPort: 1000, + DstPort: 2000, + Protocol: 6, }, - { - name: "Original Source Port Filter", - filterV4: &conntrackFilter{protocol: 6, original: &connectionTuple{srcPort: 5000}}, - filterV6: &conntrackFilter{protocol: 132, original: &connectionTuple{srcPort: 1000}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + Reverse: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("2.2.2.2"), + DstIP: netutils.ParseIPSloppy("1.1.1.1"), + SrcPort: 2000, + DstPort: 1000, + Protocol: 6, }, - { - name: "Original Destination Port Filter", - filterV4: &conntrackFilter{protocol: 6, original: &connectionTuple{dstPort: 6000}}, - filterV6: &conntrackFilter{protocol: 132, original: &connectionTuple{dstPort: 2000}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + } + flow2 := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("3.3.3.3"), + DstIP: netutils.ParseIPSloppy("4.4.4.4"), + SrcPort: 3000, + DstPort: 4000, + Protocol: 17, + }, + Reverse: netlink.IPTuple{ + SrcIP: netutils.ParseIPSloppy("4.4.4.4"), + DstIP: netutils.ParseIPSloppy("3.3.3.3"), + SrcPort: 4000, + DstPort: 3000, + Protocol: 17, }, + } + flowEmpty := &netlink.ConntrackFlow{ + Forward: netlink.IPTuple{ + Protocol: 6, + }, + Reverse: netlink.IPTuple{ + Protocol: 6, + }, + } + + testCases := []struct { + name string + filterFlows []*netlink.ConntrackFlow + matchFlow *netlink.ConntrackFlow + expectedMatch bool + }{ { - name: "Reply Source IP filter", - filterV4: &conntrackFilter{reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("20.0.0.1")}}, - filterV6: &conntrackFilter{reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + name: "Match existing flow", + filterFlows: []*netlink.ConntrackFlow{flow1, flow2}, + matchFlow: flow1, + expectedMatch: true, }, { - name: "Reply Destination IP filter", - filterV4: &conntrackFilter{reply: &connectionTuple{dstIP: netutils.ParseIPSloppy("192.168.1.1")}}, - filterV6: &conntrackFilter{reply: &connectionTuple{dstIP: netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")}}, - expectedV4Matches: 2, - expectedV6Matches: 0, + name: "No match for non-existent flow", + filterFlows: []*netlink.ConntrackFlow{flow1}, + matchFlow: flow2, + expectedMatch: false, }, { - name: "Reply Source Port filter", - filterV4: &conntrackFilter{protocol: 17, reply: &connectionTuple{srcPort: 2000}}, - filterV6: &conntrackFilter{protocol: 132, reply: &connectionTuple{srcPort: 2000}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + name: "No match for empty flow against specific filter", + filterFlows: []*netlink.ConntrackFlow{flow1}, + matchFlow: flowEmpty, + expectedMatch: false, }, { - name: "Reply Destination Port filter", - filterV4: &conntrackFilter{protocol: 6, reply: &connectionTuple{dstPort: 5000}}, - filterV6: &conntrackFilter{protocol: 132, reply: &connectionTuple{dstPort: 1000}}, - expectedV4Matches: 1, - expectedV6Matches: 1, + name: "Match empty flow if filter has empty flow", + filterFlows: []*netlink.ConntrackFlow{flowEmpty}, + matchFlow: flowEmpty, + expectedMatch: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - v4Matches, v6Matches := applyFilter(flowList, tc.filterV4, tc.filterV6) - require.Equal(t, tc.expectedV4Matches, v4Matches) - require.Equal(t, tc.expectedV6Matches, v6Matches) + f := newFlowFilter(tc.filterFlows) + match := f.MatchConntrackFlow(tc.matchFlow) + if match != tc.expectedMatch { + t.Errorf("MatchConntrackFlow() = %v, want %v", match, tc.expectedMatch) + } }) } } diff --git a/pkg/proxy/conntrack/sysctls.go b/pkg/proxy/conntrack/sysctls.go new file mode 100644 index 0000000000000..31e2534baeb33 --- /dev/null +++ b/pkg/proxy/conntrack/sysctls.go @@ -0,0 +1,209 @@ +//go:build linux + +/* +Copyright 2015 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 conntrack + +import ( + "context" + "os" + "runtime" + "strconv" + "strings" + "time" + + "github.com/google/cadvisor/machine" + "github.com/google/cadvisor/utils/sysfs" + + "k8s.io/component-helpers/node/util/sysctl" + "k8s.io/klog/v2" + proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config" +) + +func SetSysctls(ctx context.Context, config *proxyconfigapi.KubeProxyConntrackConfiguration) error { + return setSysctls(ctx, realConntrackConfigurer{}, config) +} + +// conntrackConfigurer is a mockable interface for setting conntrack sysctls. +// +// Descriptions of the various sysctl fields can be found here: +// https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt +type conntrackConfigurer interface { + // SetMax adjusts nf_conntrack_max. + SetMax(ctx context.Context, max int) error + // SetTCPEstablishedTimeout adjusts nf_conntrack_tcp_timeout_established. + SetTCPEstablishedTimeout(ctx context.Context, seconds int) error + // SetTCPCloseWaitTimeout adjusts nf_conntrack_tcp_timeout_close_wait. + SetTCPCloseWaitTimeout(ctx context.Context, seconds int) error + // SetTCPBeLiberal adjusts nf_conntrack_tcp_be_liberal. + SetTCPBeLiberal(ctx context.Context, value int) error + // SetUDPTimeout adjusts nf_conntrack_udp_timeout. + SetUDPTimeout(ctx context.Context, seconds int) error + // SetUDPStreamTimeout adjusts nf_conntrack_udp_timeout_stream. + SetUDPStreamTimeout(ctx context.Context, seconds int) error +} + +func setSysctls(ctx context.Context, ct conntrackConfigurer, config *proxyconfigapi.KubeProxyConntrackConfiguration) error { + max, err := getConntrackMax(ctx, config, detectNumCPU()) + if err != nil { + return err + } + if max > 0 { + err := ct.SetMax(ctx, max) + if err != nil { + return err + } + } + + if config.TCPEstablishedTimeout != nil && config.TCPEstablishedTimeout.Duration > 0 { + timeout := int(config.TCPEstablishedTimeout.Duration / time.Second) + if err := ct.SetTCPEstablishedTimeout(ctx, timeout); err != nil { + return err + } + } + + if config.TCPCloseWaitTimeout != nil && config.TCPCloseWaitTimeout.Duration > 0 { + timeout := int(config.TCPCloseWaitTimeout.Duration / time.Second) + if err := ct.SetTCPCloseWaitTimeout(ctx, timeout); err != nil { + return err + } + } + + if config.TCPBeLiberal { + if err := ct.SetTCPBeLiberal(ctx, 1); err != nil { + return err + } + } + + if config.UDPTimeout.Duration > 0 { + timeout := int(config.UDPTimeout.Duration / time.Second) + if err := ct.SetUDPTimeout(ctx, timeout); err != nil { + return err + } + } + + if config.UDPStreamTimeout.Duration > 0 { + timeout := int(config.UDPStreamTimeout.Duration / time.Second) + if err := ct.SetUDPStreamTimeout(ctx, timeout); err != nil { + return err + } + } + + return nil +} + +func getConntrackMax(ctx context.Context, config *proxyconfigapi.KubeProxyConntrackConfiguration, numCPU int) (int, error) { + logger := klog.FromContext(ctx) + if config.MaxPerCore != nil && *config.MaxPerCore > 0 { + floor := 0 + if config.Min != nil { + floor = int(*config.Min) + } + scaled := int(*config.MaxPerCore) * numCPU + // Cap the value to 1M to avoid excessive memory usage on high-core machines + const maxLimit = 1048576 + if scaled > maxLimit { + logger.V(3).Info("GetConntrackMax: capping scaled conntrack-max-per-core", "scaled", scaled, "limit", maxLimit) + return maxLimit, nil + } + if scaled > floor { + logger.V(3).Info("GetConntrackMax: using scaled conntrack-max-per-core") + return scaled, nil + } + logger.V(3).Info("GetConntrackMax: using conntrack-min") + return floor, nil + } + return 0, nil +} + +func detectNumCPU() int { + // try get numCPU from /sys firstly due to a known issue (https://github.com/kubernetes/kubernetes/issues/99225) + _, numCPU, err := machine.GetTopology(sysfs.NewRealSysFs()) + if err != nil || numCPU < 1 { + return runtime.NumCPU() + } + return numCPU +} + +type realConntrackConfigurer struct { +} + +func (rct realConntrackConfigurer) SetMax(ctx context.Context, max int) error { + logger := klog.FromContext(ctx) + logger.Info("Setting nf_conntrack_max", "nfConntrackMax", max) + if err := rct.setIntSysCtl(ctx, "nf_conntrack_max", max); err != nil { + return err + } + + // Check if hashsize is large enough for the nf_conntrack_max value. + hashsize, err := readIntStringFile("/sys/module/nf_conntrack/parameters/hashsize") + if err != nil { + return err + } + if hashsize >= (max / 4) { + return nil + } + + logger.Info("Setting conntrack hashsize", "conntrackHashsize", max/4) + return writeIntStringFile("/sys/module/nf_conntrack/parameters/hashsize", max/4) +} + +func (rct realConntrackConfigurer) SetTCPEstablishedTimeout(ctx context.Context, seconds int) error { + return rct.setIntSysCtl(ctx, "nf_conntrack_tcp_timeout_established", seconds) +} + +func (rct realConntrackConfigurer) SetTCPCloseWaitTimeout(ctx context.Context, seconds int) error { + return rct.setIntSysCtl(ctx, "nf_conntrack_tcp_timeout_close_wait", seconds) +} + +func (rct realConntrackConfigurer) SetTCPBeLiberal(ctx context.Context, value int) error { + return rct.setIntSysCtl(ctx, "nf_conntrack_tcp_be_liberal", value) +} + +func (rct realConntrackConfigurer) SetUDPTimeout(ctx context.Context, seconds int) error { + return rct.setIntSysCtl(ctx, "nf_conntrack_udp_timeout", seconds) +} + +func (rct realConntrackConfigurer) SetUDPStreamTimeout(ctx context.Context, seconds int) error { + return rct.setIntSysCtl(ctx, "nf_conntrack_udp_timeout_stream", seconds) +} + +func (rct realConntrackConfigurer) setIntSysCtl(ctx context.Context, name string, value int) error { + logger := klog.FromContext(ctx) + entry := "net/netfilter/" + name + + sys := sysctl.New() + if val, _ := sys.GetSysctl(entry); val != value { + logger.Info("Set sysctl", "entry", entry, "value", value) + if err := sys.SetSysctl(entry, value); err != nil { + return err + } + } + return nil +} + +func readIntStringFile(filename string) (int, error) { + b, err := os.ReadFile(filename) + if err != nil { + return -1, err + } + return strconv.Atoi(strings.TrimSpace(string(b))) +} + +func writeIntStringFile(filename string, value int) error { + return os.WriteFile(filename, []byte(strconv.Itoa(value)), 0640) +} diff --git a/pkg/proxy/conntrack/sysctls_test.go b/pkg/proxy/conntrack/sysctls_test.go new file mode 100644 index 0000000000000..f7bbc53fa02ca --- /dev/null +++ b/pkg/proxy/conntrack/sysctls_test.go @@ -0,0 +1,264 @@ +//go:build linux + +/* +Copyright 2025 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 conntrack + +import ( + "context" + "errors" + "fmt" + "runtime" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config" + "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/utils/ptr" +) + +func TestGetConntrackMax(t *testing.T) { + ncores := runtime.NumCPU() + const maxLimit = 1048576 + testCases := []struct { + min int32 + maxPerCore int32 + expected int + err string + }{ + { + expected: 0, + }, + { + maxPerCore: 67890, // use this if Max is 0 + min: 1, // avoid 0 default + expected: min(67890*ncores, maxLimit), + }, + { + maxPerCore: 1, // ensure that Min is considered + min: 123456, + expected: 123456, + }, + { + maxPerCore: 0, // leave system setting + min: 123456, + expected: 0, + }, + } + + for i, tc := range testCases { + cfg := proxyconfigapi.KubeProxyConntrackConfiguration{ + Min: ptr.To(tc.min), + MaxPerCore: ptr.To(tc.maxPerCore), + } + _, ctx := ktesting.NewTestContext(t) + x, e := getConntrackMax(ctx, &cfg, ncores) + if e != nil { + if tc.err == "" { + t.Errorf("[%d] unexpected error: %v", i, e) + } else if !strings.Contains(e.Error(), tc.err) { + t.Errorf("[%d] expected an error containing %q: %v", i, tc.err, e) + } + } else if x != tc.expected { + t.Errorf("[%d] expected %d, got %d", i, tc.expected, x) + } + } +} + +type fakeConntracker struct { + called []string + err error +} + +// SetMax value is calculated based on the number of CPUs by getConntrackMax() +func (fc *fakeConntracker) SetMax(ctx context.Context, max int) error { + fc.called = append(fc.called, "SetMax") + return fc.err +} +func (fc *fakeConntracker) SetTCPEstablishedTimeout(ctx context.Context, seconds int) error { + fc.called = append(fc.called, fmt.Sprintf("SetTCPEstablishedTimeout(%d)", seconds)) + return fc.err +} +func (fc *fakeConntracker) SetTCPCloseWaitTimeout(ctx context.Context, seconds int) error { + fc.called = append(fc.called, fmt.Sprintf("SetTCPCloseWaitTimeout(%d)", seconds)) + return fc.err +} +func (fc *fakeConntracker) SetTCPBeLiberal(ctx context.Context, value int) error { + fc.called = append(fc.called, fmt.Sprintf("SetTCPBeLiberal(%d)", value)) + return fc.err +} +func (fc *fakeConntracker) SetUDPTimeout(ctx context.Context, seconds int) error { + fc.called = append(fc.called, fmt.Sprintf("SetUDPTimeout(%d)", seconds)) + return fc.err +} +func (fc *fakeConntracker) SetUDPStreamTimeout(ctx context.Context, seconds int) error { + fc.called = append(fc.called, fmt.Sprintf("SetUDPStreamTimeout(%d)", seconds)) + return fc.err +} + +func TestSetupConntrack(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + tests := []struct { + name string + config proxyconfigapi.KubeProxyConntrackConfiguration + expect []string + conntrackErr error + wantErr bool + }{ + { + name: "do nothing if conntrack config is empty", + config: proxyconfigapi.KubeProxyConntrackConfiguration{}, + expect: nil, + }, + { + name: "SetMax is called if conntrack.maxPerCore is specified", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + MaxPerCore: ptr.To(int32(12)), + }, + expect: []string{"SetMax"}, + }, + { + name: "SetMax is not called if conntrack.maxPerCore is 0", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + MaxPerCore: ptr.To(int32(0)), + }, + expect: nil, + }, + { + name: "SetTCPEstablishedTimeout is called if conntrack.tcpEstablishedTimeout is specified", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + expect: []string{"SetTCPEstablishedTimeout(5)"}, + }, + { + name: "SetTCPEstablishedTimeout is not called if conntrack.tcpEstablishedTimeout is 0", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second}, + }, + expect: nil, + }, + { + name: "SetTCPCloseWaitTimeout is called if conntrack.tcpCloseWaitTimeout is specified", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + expect: []string{"SetTCPCloseWaitTimeout(5)"}, + }, + { + name: "SetTCPCloseWaitTimeout is not called if conntrack.tcpCloseWaitTimeout is 0", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second}, + }, + expect: nil, + }, + { + name: "SetTCPBeLiberal is called if conntrack.tcpBeLiberal is true", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPBeLiberal: true, + }, + expect: []string{"SetTCPBeLiberal(1)"}, + }, + { + name: "SetTCPBeLiberal is not called if conntrack.tcpBeLiberal is false", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPBeLiberal: false, + }, + expect: nil, + }, + { + name: "SetUDPTimeout is called if conntrack.udpTimeout is specified", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, + }, + expect: []string{"SetUDPTimeout(5)"}, + }, + { + name: "SetUDPTimeout is called if conntrack.udpTimeout is zero", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, + }, + expect: nil, + }, + { + name: "SetUDPStreamTimeout is called if conntrack.udpStreamTimeout is specified", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, + }, + expect: []string{"SetUDPStreamTimeout(5)"}, + }, + { + name: "SetUDPStreamTimeout is called if conntrack.udpStreamTimeout is zero", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + UDPStreamTimeout: metav1.Duration{Duration: 0 * time.Second}, + }, + expect: nil, + }, + { + name: "an error is returned if conntrack.SetTCPEstablishedTimeout fails", + config: proxyconfigapi.KubeProxyConntrackConfiguration{ + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + expect: []string{"SetTCPEstablishedTimeout(5)"}, + conntrackErr: errors.New("random error"), + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fc := &fakeConntracker{err: test.conntrackErr} + err := setSysctls(ctx, fc, &test.config) + if test.wantErr && err == nil { + t.Errorf("Test %q: Expected error, got nil", test.name) + } + if !test.wantErr && err != nil { + t.Errorf("Test %q: Expected no error, got %v", test.name, err) + } + if !cmp.Equal(fc.called, test.expect) { + t.Errorf("Test %q: Expected conntrack calls: %v, got: %v", test.name, test.expect, fc.called) + } + }) + } +} + +func TestGetConntrackMax_Capped(t *testing.T) { + // Simulate 512 cores + numCPU := 512 + + maxPerCore := int32(32768) + cfg := proxyconfigapi.KubeProxyConntrackConfiguration{ + MaxPerCore: ptr.To(maxPerCore), + Min: ptr.To(int32(0)), + } + + _, ctx := ktesting.NewTestContext(t) + val, err := getConntrackMax(ctx, &cfg, numCPU) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // 512 * 32768 = 16,777,216. Cap is 1,048,576 + expected := 1048576 + if val != expected { + t.Errorf("Expected capped value %d, got %d", expected, val) + } +} diff --git a/pkg/proxy/endpointschangetracker_test.go b/pkg/proxy/endpointschangetracker_test.go index 0f1a408205d83..923c3ccf6e24e 100644 --- a/pkg/proxy/endpointschangetracker_test.go +++ b/pkg/proxy/endpointschangetracker_test.go @@ -1321,8 +1321,8 @@ func TestEndpointSliceUpdate(t *testing.T) { // test additions to existing state with partially overlapping slices and ports "add a slice that overlaps with existing state and partial ports": { startingSlices: []*discovery.EndpointSlice{ - generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}), - generateEndpointSlice("svc1", "ns1", 2, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}), + generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}), + generateEndpointSlice("svc1", "ns1", 2, 2, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}), }, endpointsChangeTracker: NewEndpointsChangeTracker(v1.IPv4Protocol, "host1", nil, nil), namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"}, @@ -1336,14 +1336,14 @@ func TestEndpointSliceUpdate(t *testing.T) { &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: true, ready: true, serving: true, terminating: false}, &BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: true, ready: true, serving: true, terminating: false}, &BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: true, ready: true, serving: true, terminating: false}, - &BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: false, ready: true, serving: true, terminating: false}, + &BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: true, ready: true, serving: true, terminating: false}, &BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: true, ready: true, serving: true, terminating: false}, }, makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): { - &BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: false, ready: true, serving: true, terminating: false}, + &BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: true, ready: true, serving: true, terminating: false}, &BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false}, - &BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: false, ready: true, serving: true, terminating: false}, - &BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: false, ready: true, serving: true, terminating: false}, + &BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: true, ready: true, serving: true, terminating: false}, + &BaseEndpointInfo{ip: "10.0.2.1", port: 443, endpoint: "10.0.2.1:443", isLocal: true, ready: true, serving: true, terminating: false}, &BaseEndpointInfo{ip: "10.0.2.2", port: 443, endpoint: "10.0.2.2:443", isLocal: true, ready: true, serving: true, terminating: false}, }, }, diff --git a/pkg/proxy/endpointslicecache.go b/pkg/proxy/endpointslicecache.go index 1d1eea4f40a7c..abccefd43dd9f 100644 --- a/pkg/proxy/endpointslicecache.go +++ b/pkg/proxy/endpointslicecache.go @@ -232,10 +232,19 @@ func (cache *EndpointSliceCache) addEndpoints(svcPortName *ServicePortName, port endpointInfo := newBaseEndpointInfo(endpointIP, portNum, isLocal, ready, serving, terminating, zoneHints, nodeHints) - // This logic ensures we're deduplicating potential overlapping endpoints - // isLocal should not vary between matching endpoints, but if it does, we - // favor a true value here if it exists. - if _, exists := endpointSet[endpointInfo.String()]; !exists || isLocal { + // If an Endpoint gets moved from one slice to another, we may temporarily + // see it in both slices. Ideally we want to prefer the Endpoint from the + // more-recently-updated EndpointSlice, since it may have newer + // conditions. But we can't easily figure that out, and the situation will + // resolve itself once we receive the second EndpointSlice update anyway. + // + // On the other hand, there maybe also be two *different* Endpoints (i.e., + // with different targetRefs) that point to the same IP, if the pod + // network reuses the IP from a terminating pod before the Pod object is + // fully deleted. In this case we want to prefer the running pod over the + // terminating one. (If there are multiple non-terminating pods with the + // same podIP, then the result is undefined.) + if _, exists := endpointSet[endpointInfo.String()]; !exists || !terminating { endpointSet[endpointInfo.String()] = cache.makeEndpointInfo(endpointInfo, svcPortName) } } diff --git a/pkg/proxy/endpointslicecache_test.go b/pkg/proxy/endpointslicecache_test.go index 610bb271c16a7..54a37ec3baddc 100644 --- a/pkg/proxy/endpointslicecache_test.go +++ b/pkg/proxy/endpointslicecache_test.go @@ -311,6 +311,48 @@ func TestEndpointInfoByServicePort(t *testing.T) { }, }, }, + "with duplicate Endpoints, prefer non-terminating": { + namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"}, + hostname: "host1", + endpointSlices: []*discovery.EndpointSlice{ + func() *discovery.EndpointSlice { + es := generateEndpointSliceWithOffset("svc1", "ns1", 1, 1, 1, 999, 999, []string{"host1"}, []*int32{ptr.To[int32](80)}) + + // Duplicate the endpoint + ep1 := es.Endpoints[0] + ep1.Conditions.Ready = ptr.To(false) + ep1.Conditions.Serving = ptr.To(true) + ep1.Conditions.Terminating = ptr.To(true) + + ep2 := es.Endpoints[0] + ep2.Conditions.Ready = ptr.To(true) + ep2.Conditions.Serving = ptr.To(true) + ep2.Conditions.Terminating = ptr.To(false) + + ep3 := es.Endpoints[0] + ep3.Conditions.Ready = ptr.To(false) + ep3.Conditions.Serving = ptr.To(false) + ep3.Conditions.Terminating = ptr.To(true) + + // ep2 should win since it's the non-terminating one + es.Endpoints = []discovery.Endpoint{ep1, ep2, ep3} + return es + }(), + }, + expectedMap: spToEndpointMap{ + makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): { + "10.0.1.1:80": &BaseEndpointInfo{ + ip: "10.0.1.1", + port: 80, + endpoint: "10.0.1.1:80", + isLocal: true, + ready: true, + serving: true, + terminating: false, + }, + }, + }, + }, } for name, tc := range testCases { diff --git a/pkg/proxy/healthcheck/common.go b/pkg/proxy/healthcheck/common.go index 5e02648466583..785fe22a00d20 100644 --- a/pkg/proxy/healthcheck/common.go +++ b/pkg/proxy/healthcheck/common.go @@ -26,9 +26,8 @@ import ( // listener allows for testing of ServiceHealthServer and ProxyHealthServer. type listener interface { - // Listen is very much like netutils.MultiListen, except the second arg (network) is - // fixed to be "tcp". - Listen(ctx context.Context, addrs ...string) (net.Listener, error) + // Listen creates a netutils.MultiListen for the given network and addresses. + Listen(ctx context.Context, network string, addrs ...string) (net.Listener, error) } // httpServerFactory allows for testing of ServiceHealthServer and ProxyHealthServer. @@ -48,8 +47,8 @@ type httpServer interface { // Implement listener in terms of net.Listen. type stdNetListener struct{} -func (stdNetListener) Listen(ctx context.Context, addrs ...string) (net.Listener, error) { - return netutils.MultiListen(ctx, "tcp", addrs...) +func (stdNetListener) Listen(ctx context.Context, network string, addrs ...string) (net.Listener, error) { + return netutils.MultiListen(ctx, network, addrs...) } var _ listener = stdNetListener{} diff --git a/pkg/proxy/healthcheck/healthcheck_test.go b/pkg/proxy/healthcheck/healthcheck_test.go index e5924296dcdd5..36e3d0f1a14e4 100644 --- a/pkg/proxy/healthcheck/healthcheck_test.go +++ b/pkg/proxy/healthcheck/healthcheck_test.go @@ -31,15 +31,13 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/sets" - clientsetfake "k8s.io/client-go/kubernetes/fake" basemetrics "k8s.io/component-base/metrics" "k8s.io/component-base/metrics/testutil" - "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy/metrics" proxyutil "k8s.io/kubernetes/pkg/proxy/util" testingclock "k8s.io/utils/clock/testing" + "k8s.io/utils/dump" "k8s.io/utils/ptr" ) @@ -59,7 +57,7 @@ func (fake *fakeListener) hasPort(addr string) bool { return fake.openPorts.Has(addr) } -func (fake *fakeListener) Listen(_ context.Context, addrs ...string) (net.Listener, error) { +func (fake *fakeListener) Listen(_ context.Context, network string, addrs ...string) (net.Listener, error) { fake.openPorts.Insert(addrs...) return &fakeNetListener{ parent: fake, @@ -151,7 +149,7 @@ func TestServer(t *testing.T) { nodePortAddresses := proxyutil.NewNodePortAddresses(v1.IPv4Protocol, []string{}) proxyChecker := &fakeProxyHealthChecker{true} - hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses, proxyChecker) + hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses, proxyChecker, v1.IPv4Protocol) hcs := hcsi.(*server) if len(hcs.services) != 0 { t.Errorf("expected 0 services, got %d", len(hcs.services)) @@ -471,14 +469,25 @@ type serverTest struct { tracking503 int } +type testNodeManager struct { + node *v1.Node +} + +func (t *testNodeManager) Node() *v1.Node { + return t.node +} + +func (t *testNodeManager) update(node *v1.Node) { + t.node = node +} + func TestHealthzServer(t *testing.T) { metrics.RegisterMetrics("") listener := newFakeListener() httpFactory := newFakeHTTPServerFactory() fakeClock := testingclock.NewFakeClock(time.Now()) - client := clientsetfake.NewClientset(makeNode()) - nodeManager, _ := proxy.NewNodeManager(context.TODO(), client, time.Second, testNodeName, false) + nodeManager := &testNodeManager{node: makeNode()} hs := newProxyHealthServer(listener, httpFactory, fakeClock, "127.0.0.1:10256", 10*time.Second, nodeManager) server := hs.httpFactory.New(healthzHandler{hs: hs}) @@ -495,7 +504,7 @@ func TestHealthzServer(t *testing.T) { testProxyHealthUpdater(hs, hsTest, fakeClock, ptr.To(true), t) // Should return 200 "OK" if we've synced a node, tainted in any other way - nodeManager.OnNodeChange(makeNode(tweakTainted("other"))) + nodeManager.update(makeNode(tweakTainted("other"))) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -509,7 +518,7 @@ func TestHealthzServer(t *testing.T) { testHTTPHandler(hsTest, http.StatusOK, expectedPayload, t) // Should return 503 "ServiceUnavailable" if we've synced a ToBeDeletedTaint node - nodeManager.OnNodeChange(makeNode(tweakTainted(ToBeDeletedTaint))) + nodeManager.update(makeNode(tweakTainted(ToBeDeletedTaint))) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -523,7 +532,7 @@ func TestHealthzServer(t *testing.T) { testHTTPHandler(hsTest, http.StatusServiceUnavailable, expectedPayload, t) // Should return 200 "OK" if we've synced a node, tainted in any other way - nodeManager.OnNodeChange(makeNode(tweakTainted("other"))) + nodeManager.update(makeNode(tweakTainted("other"))) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -537,7 +546,7 @@ func TestHealthzServer(t *testing.T) { testHTTPHandler(hsTest, http.StatusOK, expectedPayload, t) // Should return 503 "ServiceUnavailable" if we've synced a deleted node - nodeManager.OnNodeChange(makeNode(tweakDeleted())) + nodeManager.update(makeNode(tweakDeleted())) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -556,9 +565,8 @@ func TestLivezServer(t *testing.T) { listener := newFakeListener() httpFactory := newFakeHTTPServerFactory() fakeClock := testingclock.NewFakeClock(time.Now()) - client := clientsetfake.NewClientset(makeNode()) - nodeManager, _ := proxy.NewNodeManager(context.TODO(), client, time.Second, testNodeName, false) + nodeManager := &testNodeManager{node: makeNode()} hs := newProxyHealthServer(listener, httpFactory, fakeClock, "127.0.0.1:10256", 10*time.Second, nodeManager) server := hs.httpFactory.New(livezHandler{hs: hs}) @@ -575,7 +583,7 @@ func TestLivezServer(t *testing.T) { testProxyHealthUpdater(hs, hsTest, fakeClock, nil, t) // Should return 200 "OK" irrespective of node syncs - nodeManager.OnNodeChange(makeNode(tweakTainted("other"))) + nodeManager.update(makeNode(tweakTainted("other"))) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -588,7 +596,7 @@ func TestLivezServer(t *testing.T) { testHTTPHandler(hsTest, http.StatusOK, expectedPayload, t) // Should return 200 "OK" irrespective of node syncs - nodeManager.OnNodeChange(makeNode(tweakTainted(ToBeDeletedTaint))) + nodeManager.update(makeNode(tweakTainted(ToBeDeletedTaint))) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -601,7 +609,7 @@ func TestLivezServer(t *testing.T) { testHTTPHandler(hsTest, http.StatusOK, expectedPayload, t) // Should return 200 "OK" irrespective of node syncs - nodeManager.OnNodeChange(makeNode(tweakTainted("other"))) + nodeManager.update(makeNode(tweakTainted("other"))) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -614,7 +622,7 @@ func TestLivezServer(t *testing.T) { testHTTPHandler(hsTest, http.StatusOK, expectedPayload, t) // Should return 200 "OK" irrespective of node syncs - nodeManager.OnNodeChange(makeNode(tweakDeleted())) + nodeManager.update(makeNode(tweakDeleted())) expectedPayload = ProxyHealth{ CurrentTime: fakeClock.Now(), LastUpdated: fakeClock.Now(), @@ -964,7 +972,7 @@ func TestServerWithSelectiveListeningAddress(t *testing.T) { // machine nor testing ipv6 || ipv4. using loop back guarantees the test will work on any machine nodePortAddresses := proxyutil.NewNodePortAddresses(v1.IPv4Protocol, []string{"127.0.0.0/8"}) - hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses, proxyChecker) + hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses, proxyChecker, v1.IPv4Protocol) hcs := hcsi.(*server) if len(hcs.services) != 0 { t.Errorf("expected 0 services, got %d", len(hcs.services)) diff --git a/pkg/proxy/healthcheck/proxy_health.go b/pkg/proxy/healthcheck/proxy_health.go index ee420db4d6f08..210fde19e3192 100644 --- a/pkg/proxy/healthcheck/proxy_health.go +++ b/pkg/proxy/healthcheck/proxy_health.go @@ -26,7 +26,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy/metrics" "k8s.io/utils/clock" "k8s.io/utils/ptr" @@ -71,7 +70,7 @@ type ProxyHealthServer struct { httpFactory httpServerFactory clock clock.Clock - nodeManager *proxy.NodeManager + nodeManager nodeManager addr string healthTimeout time.Duration @@ -81,12 +80,17 @@ type ProxyHealthServer struct { oldestPendingQueuedMap map[v1.IPFamily]time.Time } +// interface to break an import chain between pkg/proxy and pkg/proxy/healthcheck +type nodeManager interface { + Node() *v1.Node +} + // NewProxyHealthServer returns a proxy health http server. -func NewProxyHealthServer(addr string, healthTimeout time.Duration, nodeManager *proxy.NodeManager) *ProxyHealthServer { +func NewProxyHealthServer(addr string, healthTimeout time.Duration, nodeManager nodeManager) *ProxyHealthServer { return newProxyHealthServer(stdNetListener{}, stdHTTPServerFactory{}, clock.RealClock{}, addr, healthTimeout, nodeManager) } -func newProxyHealthServer(listener listener, httpServerFactory httpServerFactory, c clock.Clock, addr string, healthTimeout time.Duration, nodeManager *proxy.NodeManager) *ProxyHealthServer { +func newProxyHealthServer(listener listener, httpServerFactory httpServerFactory, c clock.Clock, addr string, healthTimeout time.Duration, nodeManager nodeManager) *ProxyHealthServer { return &ProxyHealthServer{ listener: listener, httpFactory: httpServerFactory, @@ -199,7 +203,8 @@ func (hs *ProxyHealthServer) Run(ctx context.Context) error { serveMux.Handle("/livez", livezHandler{hs: hs}) server := hs.httpFactory.New(serveMux) - listener, err := hs.listener.Listen(ctx, hs.addr) + // using MultiListen to support both IPv4 and IPv6 addresses. + listener, err := hs.listener.Listen(ctx, "tcp", hs.addr) if err != nil { return fmt.Errorf("failed to start proxy healthz on %s: %w", hs.addr, err) } diff --git a/pkg/proxy/healthcheck/service_health.go b/pkg/proxy/healthcheck/service_health.go index e470f94c36dcd..99b6d0c320178 100644 --- a/pkg/proxy/healthcheck/service_health.go +++ b/pkg/proxy/healthcheck/service_health.go @@ -57,10 +57,24 @@ type proxyHealthChecker interface { Health() ProxyHealth } -func newServiceHealthServer(nodeName string, recorder events.EventRecorder, listener listener, factory httpServerFactory, nodePortAddresses *proxyutil.NodePortAddresses, healthzServer proxyHealthChecker) ServiceHealthServer { - // It doesn't matter whether we listen on "0.0.0.0", "::", or ""; go - // treats them all the same. - nodeIPs := []net.IP{net.IPv4zero} +func newServiceHealthServer(nodeName string, recorder events.EventRecorder, listener listener, factory httpServerFactory, nodePortAddresses *proxyutil.NodePortAddresses, healthzServer proxyHealthChecker, serviceFamily v1.IPFamily) ServiceHealthServer { + // Set listen addresses and network based on IP family; if unknown, + // let the system choose + var nodeIPs []net.IP + var network string + + switch serviceFamily { + case v1.IPv4Protocol: + nodeIPs = []net.IP{net.IPv4zero} + network = "tcp4" + case v1.IPv6Protocol: + nodeIPs = []net.IP{net.IPv6zero} + network = "tcp6" + default: + // Let the system choose + nodeIPs = []net.IP{net.IPv4zero} + network = "tcp" + } if !nodePortAddresses.MatchAll() { ips, err := nodePortAddresses.GetNodeIPs(proxyutil.RealNetwork{}) @@ -79,12 +93,13 @@ func newServiceHealthServer(nodeName string, recorder events.EventRecorder, list healthzServer: healthzServer, services: map[types.NamespacedName]*hcInstance{}, nodeIPs: nodeIPs, + network: network, } } // NewServiceHealthServer allocates a new service healthcheck server manager -func NewServiceHealthServer(nodeName string, recorder events.EventRecorder, nodePortAddresses *proxyutil.NodePortAddresses, healthzServer proxyHealthChecker) ServiceHealthServer { - return newServiceHealthServer(nodeName, recorder, stdNetListener{}, stdHTTPServerFactory{}, nodePortAddresses, healthzServer) +func NewServiceHealthServer(nodeName string, recorder events.EventRecorder, nodePortAddresses *proxyutil.NodePortAddresses, healthzServer proxyHealthChecker, serviceFamily v1.IPFamily) ServiceHealthServer { + return newServiceHealthServer(nodeName, recorder, stdNetListener{}, stdHTTPServerFactory{}, nodePortAddresses, healthzServer, serviceFamily) } type server struct { @@ -99,6 +114,9 @@ type server struct { lock sync.RWMutex services map[types.NamespacedName]*hcInstance + + // network type of the socket when listening for health checks + network string } func (hcs *server) SyncServices(newServices map[types.NamespacedName]uint16) error { @@ -172,7 +190,7 @@ func (hcI *hcInstance) listenAndServeAll(hcs *server) error { // create http server httpSrv := hcs.httpFactory.New(hcHandler{name: hcI.nsn, hcs: hcs}) // start listener - listener, err = hcs.listener.Listen(context.TODO(), addr) + listener, err = hcs.listener.Listen(context.TODO(), hcs.network, addr) if err != nil { // must close whatever have been previously opened // to allow a retry/or port ownership change as needed diff --git a/pkg/proxy/iptables/cleanup.go b/pkg/proxy/iptables/cleanup.go new file mode 100644 index 0000000000000..5057497f775bc --- /dev/null +++ b/pkg/proxy/iptables/cleanup.go @@ -0,0 +1,126 @@ +//go:build linux + +/* +Copyright 2015 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 iptables + +import ( + "bytes" + "context" + + "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/proxy/metrics" + proxyutil "k8s.io/kubernetes/pkg/proxy/util" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" +) + +// CleanupLeftovers removes all iptables rules and chains created by the Proxier +// It returns true if an error was encountered. Errors are logged. +func CleanupLeftovers(ctx context.Context) (encounteredError bool) { + ipts := utiliptables.NewBestEffort() + for _, ipt := range ipts { + encounteredError = cleanupLeftoversForFamily(ctx, ipt) || encounteredError + } + return +} + +func cleanupLeftoversForFamily(ctx context.Context, ipt utiliptables.Interface) (encounteredError bool) { + logger := klog.FromContext(ctx) + // Unlink our chains + for _, jump := range append(iptablesJumpChains, iptablesCleanupOnlyChains...) { + args := append([]string{}, jump.extraArgs...) + args = append(args, + "-m", "comment", "--comment", jump.comment, + "-j", string(jump.dstChain), + ) + if err := ipt.DeleteRule(jump.table, jump.srcChain, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + logger.Error(err, "Error removing pure-iptables proxy rule") + encounteredError = true + } + } + } + + // Flush and remove all of our "-t nat" chains. + iptablesData := bytes.NewBuffer(nil) + if err := ipt.SaveInto(utiliptables.TableNAT, iptablesData); err != nil { + logger.Error(err, "Failed to execute iptables-save", "table", utiliptables.TableNAT) + encounteredError = true + } else { + existingNATChains := utiliptables.GetChainsFromTable(iptablesData.Bytes()) + natChains := proxyutil.NewLineBuffer() + natRules := proxyutil.NewLineBuffer() + natChains.Write("*nat") + // Start with chains we know we need to remove. + for _, chain := range []utiliptables.Chain{kubeServicesChain, kubeNodePortsChain, kubePostroutingChain, kubeMarkMasqChain, kubeProxyCanaryChain} { + if existingNATChains.Has(chain) { + chainString := string(chain) + natChains.Write(utiliptables.MakeChainLine(chain)) // flush + natRules.Write("-X", chainString) // delete + } + } + // Hunt for service and endpoint chains. + for chain := range existingNATChains { + chainString := string(chain) + if isServiceChainName(chainString) { + natChains.Write(utiliptables.MakeChainLine(chain)) // flush + natRules.Write("-X", chainString) // delete + } + } + natRules.Write("COMMIT") + natLines := append(natChains.Bytes(), natRules.Bytes()...) + // Write it. + err = ipt.Restore(utiliptables.TableNAT, natLines, utiliptables.NoFlushTables, utiliptables.RestoreCounters) + if err != nil { + logger.Error(err, "Failed to execute iptables-restore", "table", utiliptables.TableNAT) + metrics.IPTablesRestoreFailuresTotal.WithLabelValues(string(ipt.Protocol())).Inc() + encounteredError = true + } + } + + // Flush and remove all of our "-t filter" chains. + iptablesData.Reset() + if err := ipt.SaveInto(utiliptables.TableFilter, iptablesData); err != nil { + logger.Error(err, "Failed to execute iptables-save", "table", utiliptables.TableFilter) + encounteredError = true + } else { + existingFilterChains := utiliptables.GetChainsFromTable(iptablesData.Bytes()) + filterChains := proxyutil.NewLineBuffer() + filterRules := proxyutil.NewLineBuffer() + filterChains.Write("*filter") + for _, chain := range []utiliptables.Chain{kubeServicesChain, kubeExternalServicesChain, kubeForwardChain, kubeNodePortsChain, kubeProxyFirewallChain, kubeProxyCanaryChain} { + if existingFilterChains.Has(chain) { + chainString := string(chain) + filterChains.Write(utiliptables.MakeChainLine(chain)) + filterRules.Write("-X", chainString) + } + } + filterRules.Write("COMMIT") + filterLines := append(filterChains.Bytes(), filterRules.Bytes()...) + // Write it. + if err := ipt.Restore(utiliptables.TableFilter, filterLines, utiliptables.NoFlushTables, utiliptables.RestoreCounters); err != nil { + logger.Error(err, "Failed to execute iptables-restore", "table", utiliptables.TableFilter) + metrics.IPTablesRestoreFailuresTotal.WithLabelValues(string(ipt.Protocol())).Inc() + encounteredError = true + } + } + + // Remove our "-t mangle" canary chain; ignore errors since it may not exist. + _ = ipt.DeleteChain(utiliptables.TableMangle, kubeProxyCanaryChain) + + return encounteredError +} diff --git a/pkg/proxy/iptables/cleanup_test.go b/pkg/proxy/iptables/cleanup_test.go new file mode 100644 index 0000000000000..dc71b15668c1b --- /dev/null +++ b/pkg/proxy/iptables/cleanup_test.go @@ -0,0 +1,216 @@ +//go:build linux + +/* +Copyright 2015 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 iptables + +import ( + "bytes" + "testing" + + "github.com/lithammer/dedent" + + klogtesting "k8s.io/klog/v2/ktesting" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" + iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing" +) + +func TestCleanupLeftovers(t *testing.T) { + _, ctx := klogtesting.NewTestContext(t) + ipt := iptablestest.NewFake() + + initial := dedent.Dedent(` + *filter + :INPUT - [0:0] + :FORWARD - [0:0] + :OUTPUT - [0:0] + :KUBE-EXTERNAL-SERVICES - [0:0] + :KUBE-FIREWALL - [0:0] + :KUBE-FORWARD - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + :KUBE-PROXY-CANARY - [0:0] + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-PROXY-FIREWALL - [0:0] + -A INPUT -j KUBE-FIREWALL + -A INPUT -m comment --comment kubernetes health check service ports -j KUBE-NODEPORTS + -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP + -A INPUT -m conntrack --ctstate NEW -m comment --comment kubernetes externally-visible service portals -j KUBE-EXTERNAL-SERVICES + -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP + -A FORWARD -m comment --comment kubernetes forwarding rules -j KUBE-FORWARD + -A FORWARD -m conntrack --ctstate NEW -m comment --comment kubernetes service portals -j KUBE-SERVICES + -A FORWARD -m conntrack --ctstate NEW -m comment --comment kubernetes externally-visible service portals -j KUBE-EXTERNAL-SERVICES + -A OUTPUT -j KUBE-FIREWALL + -A OUTPUT -m conntrack --ctstate NEW -m comment --comment kubernetes service portals -j KUBE-SERVICES + -A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT + -A KUBE-SERVICES -m comment --comment "ns6/svc6:p80 has no endpoints" -m tcp -p tcp -d 172.30.0.46 --dport 80 -j REJECT + -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns2/svc2:p80 has no local endpoints" -m tcp -p tcp -d 192.168.99.22 --dport 80 -j DROP + -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns2/svc2:p80 has no local endpoints" -m tcp -p tcp -d 1.2.3.4 --dport 80 -j DROP + -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns2/svc2:p80 has no local endpoints" -m addrtype --dst-type LOCAL -m tcp -p tcp --dport 3001 -j DROP + -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP + -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + -A KUBE-PROXY-FIREWALL -m comment --comment "ns5/svc5:p80 traffic not accepted by KUBE-FW-NUKIZ6OKUXPJNT4C" -m tcp -p tcp -d 5.6.7.8 --dport 80 -j DROP + -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP + COMMIT + *nat + :PREROUTING - [0:0] + :INPUT - [0:0] + :OUTPUT - [0:0] + :POSTROUTING - [0:0] + :KUBE-EXT-4SW47YFZTEDKD3PK - [0:0] + :KUBE-EXT-GNZBNJ2PO5MGZ6GT - [0:0] + :KUBE-EXT-NUKIZ6OKUXPJNT4C - [0:0] + :KUBE-EXT-X27LE4BHSL4DOUIK - [0:0] + :KUBE-FW-NUKIZ6OKUXPJNT4C - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + :KUBE-MARK-MASQ - [0:0] + :KUBE-NODEPORTS - [0:0] + :KUBE-POSTROUTING - [0:0] + :KUBE-PROXY-CANARY - [0:0] + :KUBE-SEP-C6EBXVWJJZMIWKLZ - [0:0] + :KUBE-SEP-I77PXRDZVX7PMWMN - [0:0] + :KUBE-SEP-OYPFS5VJICHGATKP - [0:0] + :KUBE-SEP-RS4RBKLTHTF2IUXJ - [0:0] + :KUBE-SEP-SXIVWICOYRO3J4NJ - [0:0] + :KUBE-SEP-UKSFD7AGPMPPLUHC - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-SVC-4SW47YFZTEDKD3PK - [0:0] + :KUBE-SVC-GNZBNJ2PO5MGZ6GT - [0:0] + :KUBE-SVC-NUKIZ6OKUXPJNT4C - [0:0] + :KUBE-SVC-X27LE4BHSL4DOUIK - [0:0] + :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] + -A PREROUTING -m comment --comment kubernetes service portals -j KUBE-SERVICES + -A OUTPUT -m comment --comment kubernetes service portals -j KUBE-SERVICES + -A POSTROUTING -m comment --comment kubernetes postrouting rules -j KUBE-POSTROUTING + -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN + -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 + -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE + -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 + -A KUBE-NODEPORTS -m comment --comment ns2/svc2:p80 -m tcp -p tcp --dport 3001 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT + -A KUBE-NODEPORTS -m comment --comment ns3/svc3:p80 -m tcp -p tcp --dport 3003 -j KUBE-EXT-X27LE4BHSL4DOUIK + -A KUBE-NODEPORTS -m comment --comment ns5/svc5:p80 -m tcp -p tcp --dport 3002 -j KUBE-EXT-NUKIZ6OKUXPJNT4C + -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "ns2/svc2:p80 cluster IP" -m tcp -p tcp -d 172.30.0.42 --dport 80 -j KUBE-SVC-GNZBNJ2PO5MGZ6GT + -A KUBE-SERVICES -m comment --comment "ns2/svc2:p80 external IP" -m tcp -p tcp -d 192.168.99.22 --dport 80 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT + -A KUBE-SERVICES -m comment --comment "ns2/svc2:p80 loadbalancer IP" -m tcp -p tcp -d 1.2.3.4 --dport 80 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT + -A KUBE-SERVICES -m comment --comment "ns3/svc3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 -j KUBE-SVC-X27LE4BHSL4DOUIK + -A KUBE-SERVICES -m comment --comment "ns4/svc4:p80 cluster IP" -m tcp -p tcp -d 172.30.0.44 --dport 80 -j KUBE-SVC-4SW47YFZTEDKD3PK + -A KUBE-SERVICES -m comment --comment "ns4/svc4:p80 external IP" -m tcp -p tcp -d 192.168.99.33 --dport 80 -j KUBE-EXT-4SW47YFZTEDKD3PK + -A KUBE-SERVICES -m comment --comment "ns5/svc5:p80 cluster IP" -m tcp -p tcp -d 172.30.0.45 --dport 80 -j KUBE-SVC-NUKIZ6OKUXPJNT4C + -A KUBE-SERVICES -m comment --comment "ns5/svc5:p80 loadbalancer IP" -m tcp -p tcp -d 5.6.7.8 --dport 80 -j KUBE-FW-NUKIZ6OKUXPJNT4C + -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS + -A KUBE-EXT-4SW47YFZTEDKD3PK -m comment --comment "masquerade traffic for ns4/svc4:p80 external destinations" -j KUBE-MARK-MASQ + -A KUBE-EXT-4SW47YFZTEDKD3PK -j KUBE-SVC-4SW47YFZTEDKD3PK + -A KUBE-EXT-GNZBNJ2PO5MGZ6GT -m comment --comment "pod traffic for ns2/svc2:p80 external destinations" -s 10.0.0.0/8 -j KUBE-SVC-GNZBNJ2PO5MGZ6GT + -A KUBE-EXT-GNZBNJ2PO5MGZ6GT -m comment --comment "masquerade LOCAL traffic for ns2/svc2:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ + -A KUBE-EXT-GNZBNJ2PO5MGZ6GT -m comment --comment "route LOCAL traffic for ns2/svc2:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-GNZBNJ2PO5MGZ6GT + -A KUBE-EXT-NUKIZ6OKUXPJNT4C -m comment --comment "masquerade traffic for ns5/svc5:p80 external destinations" -j KUBE-MARK-MASQ + -A KUBE-EXT-NUKIZ6OKUXPJNT4C -j KUBE-SVC-NUKIZ6OKUXPJNT4C + -A KUBE-EXT-X27LE4BHSL4DOUIK -m comment --comment "masquerade traffic for ns3/svc3:p80 external destinations" -j KUBE-MARK-MASQ + -A KUBE-EXT-X27LE4BHSL4DOUIK -j KUBE-SVC-X27LE4BHSL4DOUIK + -A KUBE-FW-NUKIZ6OKUXPJNT4C -m comment --comment "ns5/svc5:p80 loadbalancer IP" -s 203.0.113.0/25 -j KUBE-EXT-NUKIZ6OKUXPJNT4C + -A KUBE-FW-NUKIZ6OKUXPJNT4C -m comment --comment "other traffic to ns5/svc5:p80 will be dropped by KUBE-PROXY-FIREWALL" + -A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -s 10.180.0.5 -j KUBE-MARK-MASQ + -A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.5:80 + -A KUBE-SEP-I77PXRDZVX7PMWMN -m comment --comment ns5/svc5:p80 -s 10.180.0.3 -j KUBE-MARK-MASQ + -A KUBE-SEP-I77PXRDZVX7PMWMN -m comment --comment ns5/svc5:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.3:80 + -A KUBE-SEP-OYPFS5VJICHGATKP -m comment --comment ns3/svc3:p80 -s 10.180.0.3 -j KUBE-MARK-MASQ + -A KUBE-SEP-OYPFS5VJICHGATKP -m comment --comment ns3/svc3:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.3:80 + -A KUBE-SEP-RS4RBKLTHTF2IUXJ -m comment --comment ns2/svc2:p80 -s 10.180.0.2 -j KUBE-MARK-MASQ + -A KUBE-SEP-RS4RBKLTHTF2IUXJ -m comment --comment ns2/svc2:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.2:80 + -A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -s 10.180.0.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.1:80 + -A KUBE-SEP-UKSFD7AGPMPPLUHC -m comment --comment ns4/svc4:p80 -s 10.180.0.4 -j KUBE-MARK-MASQ + -A KUBE-SEP-UKSFD7AGPMPPLUHC -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.4:80 + -A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 cluster IP" -m tcp -p tcp -d 172.30.0.44 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ + -A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 -> 10.180.0.4:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-UKSFD7AGPMPPLUHC + -A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 -> 10.180.0.5:80" -j KUBE-SEP-C6EBXVWJJZMIWKLZ + -A KUBE-SVC-GNZBNJ2PO5MGZ6GT -m comment --comment "ns2/svc2:p80 cluster IP" -m tcp -p tcp -d 172.30.0.42 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ + -A KUBE-SVC-GNZBNJ2PO5MGZ6GT -m comment --comment "ns2/svc2:p80 -> 10.180.0.2:80" -j KUBE-SEP-RS4RBKLTHTF2IUXJ + -A KUBE-SVC-NUKIZ6OKUXPJNT4C -m comment --comment "ns5/svc5:p80 cluster IP" -m tcp -p tcp -d 172.30.0.45 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ + -A KUBE-SVC-NUKIZ6OKUXPJNT4C -m comment --comment "ns5/svc5:p80 -> 10.180.0.3:80" -j KUBE-SEP-I77PXRDZVX7PMWMN + -A KUBE-SVC-X27LE4BHSL4DOUIK -m comment --comment "ns3/svc3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ + -A KUBE-SVC-X27LE4BHSL4DOUIK -m comment --comment "ns3/svc3:p80 -> 10.180.0.3:80" -j KUBE-SEP-OYPFS5VJICHGATKP + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.180.0.1:80" -j KUBE-SEP-SXIVWICOYRO3J4NJ + COMMIT + *mangle + :KUBE-IPTABLES-HINT - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + :KUBE-PROXY-CANARY - [0:0] + COMMIT + `) + + err := ipt.RestoreAll([]byte(initial), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters) + if err != nil { + t.Fatalf("Unexpected error setting up iptables state: %v", err) + } + + encounteredError := cleanupLeftoversForFamily(ctx, ipt) + if encounteredError { + t.Fatal("Unexpected error from cleanupLeftoversForFamily") + } + + var buf bytes.Buffer + err = ipt.SaveInto("", &buf) + if err != nil { + t.Fatalf("Unexpected error reading filter table: %v", err) + } + + expected := dedent.Dedent(` + *filter + :INPUT - [0:0] + :FORWARD - [0:0] + :OUTPUT - [0:0] + :KUBE-FIREWALL - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + + # These are created by both kubelet and kube-proxy and intentionally not + # cleaned up by kube-proxy. + -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP + -A INPUT -j KUBE-FIREWALL + -A OUTPUT -j KUBE-FIREWALL + + # Non-Kubernetes rules are preserved + -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP + -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP + -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP + + COMMIT + + *nat + :PREROUTING - [0:0] + :INPUT - [0:0] + :OUTPUT - [0:0] + :POSTROUTING - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + COMMIT + + *mangle + :KUBE-IPTABLES-HINT - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + COMMIT + `) + expected, err = sortIPTablesRules(expected) + if err != nil { + t.Fatalf("Unexpected error sorting expected output: %v", err) + } + + assertIPTablesRulesEqual(t, getLine(), false, expected, buf.String()) +} diff --git a/pkg/proxy/iptables/number_generated_rules_test.go b/pkg/proxy/iptables/number_generated_rules_test.go index dd318d2081406..008c239e92da7 100644 --- a/pkg/proxy/iptables/number_generated_rules_test.go +++ b/pkg/proxy/iptables/number_generated_rules_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/proxy/iptables/proxier.go b/pkg/proxy/iptables/proxier.go index d4f8cf13dba2a..4f620ad65bdf3 100644 --- a/pkg/proxy/iptables/proxier.go +++ b/pkg/proxy/iptables/proxier.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -264,7 +263,7 @@ func NewProxier(ctx context.Context, masqueradeMark := fmt.Sprintf("%#08x", masqueradeValue) logger.V(2).Info("Using iptables mark for masquerade", "mark", masqueradeMark) - serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer) + serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer, ipFamily) nfacctRunner, err := nfacct.New() if err != nil { logger.Error(err, "Failed to create nfacct runner, nfacct based metrics won't be available") @@ -401,102 +400,6 @@ var iptablesKubeletJumpChains = []iptablesJumpChain{ // on upgrade. var iptablesCleanupOnlyChains = []iptablesJumpChain{} -// CleanupLeftovers removes all iptables rules and chains created by the Proxier -// It returns true if an error was encountered. Errors are logged. -func CleanupLeftovers(ctx context.Context) (encounteredError bool) { - ipts := utiliptables.NewBestEffort() - for _, ipt := range ipts { - encounteredError = cleanupLeftoversForFamily(ctx, ipt) || encounteredError - } - return -} - -func cleanupLeftoversForFamily(ctx context.Context, ipt utiliptables.Interface) (encounteredError bool) { - logger := klog.FromContext(ctx) - // Unlink our chains - for _, jump := range append(iptablesJumpChains, iptablesCleanupOnlyChains...) { - args := append(jump.extraArgs, - "-m", "comment", "--comment", jump.comment, - "-j", string(jump.dstChain), - ) - if err := ipt.DeleteRule(jump.table, jump.srcChain, args...); err != nil { - if !utiliptables.IsNotFoundError(err) { - logger.Error(err, "Error removing pure-iptables proxy rule") - encounteredError = true - } - } - } - - // Flush and remove all of our "-t nat" chains. - iptablesData := bytes.NewBuffer(nil) - if err := ipt.SaveInto(utiliptables.TableNAT, iptablesData); err != nil { - logger.Error(err, "Failed to execute iptables-save", "table", utiliptables.TableNAT) - encounteredError = true - } else { - existingNATChains := utiliptables.GetChainsFromTable(iptablesData.Bytes()) - natChains := proxyutil.NewLineBuffer() - natRules := proxyutil.NewLineBuffer() - natChains.Write("*nat") - // Start with chains we know we need to remove. - for _, chain := range []utiliptables.Chain{kubeServicesChain, kubeNodePortsChain, kubePostroutingChain, kubeMarkMasqChain, kubeProxyCanaryChain} { - if existingNATChains.Has(chain) { - chainString := string(chain) - natChains.Write(utiliptables.MakeChainLine(chain)) // flush - natRules.Write("-X", chainString) // delete - } - } - // Hunt for service and endpoint chains. - for chain := range existingNATChains { - chainString := string(chain) - if isServiceChainName(chainString) { - natChains.Write(utiliptables.MakeChainLine(chain)) // flush - natRules.Write("-X", chainString) // delete - } - } - natRules.Write("COMMIT") - natLines := append(natChains.Bytes(), natRules.Bytes()...) - // Write it. - err = ipt.Restore(utiliptables.TableNAT, natLines, utiliptables.NoFlushTables, utiliptables.RestoreCounters) - if err != nil { - logger.Error(err, "Failed to execute iptables-restore", "table", utiliptables.TableNAT) - metrics.IPTablesRestoreFailuresTotal.WithLabelValues(string(ipt.Protocol())).Inc() - encounteredError = true - } - } - - // Flush and remove all of our "-t filter" chains. - iptablesData.Reset() - if err := ipt.SaveInto(utiliptables.TableFilter, iptablesData); err != nil { - logger.Error(err, "Failed to execute iptables-save", "table", utiliptables.TableFilter) - encounteredError = true - } else { - existingFilterChains := utiliptables.GetChainsFromTable(iptablesData.Bytes()) - filterChains := proxyutil.NewLineBuffer() - filterRules := proxyutil.NewLineBuffer() - filterChains.Write("*filter") - for _, chain := range []utiliptables.Chain{kubeServicesChain, kubeExternalServicesChain, kubeForwardChain, kubeNodePortsChain, kubeProxyFirewallChain, kubeProxyCanaryChain} { - if existingFilterChains.Has(chain) { - chainString := string(chain) - filterChains.Write(utiliptables.MakeChainLine(chain)) - filterRules.Write("-X", chainString) - } - } - filterRules.Write("COMMIT") - filterLines := append(filterChains.Bytes(), filterRules.Bytes()...) - // Write it. - if err := ipt.Restore(utiliptables.TableFilter, filterLines, utiliptables.NoFlushTables, utiliptables.RestoreCounters); err != nil { - logger.Error(err, "Failed to execute iptables-restore", "table", utiliptables.TableFilter) - metrics.IPTablesRestoreFailuresTotal.WithLabelValues(string(ipt.Protocol())).Inc() - encounteredError = true - } - } - - // Remove our "-t mangle" canary chain; ignore errors since it may not exist. - _ = ipt.DeleteChain(utiliptables.TableMangle, kubeProxyCanaryChain) - - return encounteredError -} - func computeProbability(n int) string { return fmt.Sprintf("%0.10f", 1.0/float64(n)) } @@ -745,7 +648,9 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { // Keep track of how long syncs take. start := time.Now() - doFullSync := proxier.needFullSync || (time.Since(proxier.lastFullSync) > proxyutil.FullSyncPeriod) + doFullSync := proxier.needFullSync || + // Avoid regular full syncs for large clusters. + ((time.Since(proxier.lastFullSync) > proxyutil.FullSyncPeriod) && !proxier.largeClusterMode) defer func() { metrics.SyncProxyRulesLatency.WithLabelValues(string(proxier.ipFamily)).Observe(metrics.SinceInSeconds(start)) diff --git a/pkg/proxy/iptables/proxier_test.go b/pkg/proxy/iptables/proxier_test.go index ca8d9a168cc80..f0020c10d9b9f 100644 --- a/pkg/proxy/iptables/proxier_test.go +++ b/pkg/proxy/iptables/proxier_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -5640,6 +5639,21 @@ func TestSyncProxyRulesLargeClusterMode(t *testing.T) { t.Errorf("numComments (%d) != 0 after partial resync when numEndpoints (%d) > threshold (%d)", numComments, expectedEndpoints+3, largeClusterEndpointsThreshold) } + // Even if FullSyncPeriod has elapsed, large-cluster mode should keep this as + // a partial resync when there are no explicit changes requiring a full sync. + if !fp.largeClusterMode { + t.Fatalf("expected to be in large cluster mode") + } + expectedLastFullSync := time.Now().Add(-proxyutil.FullSyncPeriod).Add(-time.Second) + fp.lastFullSync = expectedLastFullSync + err := fp.syncProxyRules() + if err != nil { + t.Fatalf("syncProxyRules failed: %v", err) + } + if !fp.lastFullSync.Equal(expectedLastFullSync) { + t.Fatalf("expected periodic sync in large cluster mode to skip full sync: lastFullSync changed from %v to %v", expectedLastFullSync, fp.lastFullSync) + } + // Now force a full resync and confirm that it rewrites the older services with // no comments as well. fp.forceSyncProxyRules() @@ -6697,189 +6711,3 @@ func TestBadIPs(t *testing.T) { assertIPTablesRulesEqual(t, getLine(), true, expected, fp.iptablesData.String()) } - -func TestCleanupLeftovers(t *testing.T) { - _, ctx := klogtesting.NewTestContext(t) - ipt := iptablestest.NewFake() - - initial := dedent.Dedent(` - *filter - :INPUT - [0:0] - :FORWARD - [0:0] - :OUTPUT - [0:0] - :KUBE-EXTERNAL-SERVICES - [0:0] - :KUBE-FIREWALL - [0:0] - :KUBE-FORWARD - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - :KUBE-PROXY-CANARY - [0:0] - :KUBE-NODEPORTS - [0:0] - :KUBE-SERVICES - [0:0] - :KUBE-PROXY-FIREWALL - [0:0] - -A INPUT -j KUBE-FIREWALL - -A INPUT -m comment --comment kubernetes health check service ports -j KUBE-NODEPORTS - -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP - -A INPUT -m conntrack --ctstate NEW -m comment --comment kubernetes externally-visible service portals -j KUBE-EXTERNAL-SERVICES - -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP - -A FORWARD -m comment --comment kubernetes forwarding rules -j KUBE-FORWARD - -A FORWARD -m conntrack --ctstate NEW -m comment --comment kubernetes service portals -j KUBE-SERVICES - -A FORWARD -m conntrack --ctstate NEW -m comment --comment kubernetes externally-visible service portals -j KUBE-EXTERNAL-SERVICES - -A OUTPUT -j KUBE-FIREWALL - -A OUTPUT -m conntrack --ctstate NEW -m comment --comment kubernetes service portals -j KUBE-SERVICES - -A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT - -A KUBE-SERVICES -m comment --comment "ns6/svc6:p80 has no endpoints" -m tcp -p tcp -d 172.30.0.46 --dport 80 -j REJECT - -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns2/svc2:p80 has no local endpoints" -m tcp -p tcp -d 192.168.99.22 --dport 80 -j DROP - -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns2/svc2:p80 has no local endpoints" -m tcp -p tcp -d 1.2.3.4 --dport 80 -j DROP - -A KUBE-EXTERNAL-SERVICES -m comment --comment "ns2/svc2:p80 has no local endpoints" -m addrtype --dst-type LOCAL -m tcp -p tcp --dport 3001 -j DROP - -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP - -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP - -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT - -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - -A KUBE-PROXY-FIREWALL -m comment --comment "ns5/svc5:p80 traffic not accepted by KUBE-FW-NUKIZ6OKUXPJNT4C" -m tcp -p tcp -d 5.6.7.8 --dport 80 -j DROP - -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP - COMMIT - *nat - :PREROUTING - [0:0] - :INPUT - [0:0] - :OUTPUT - [0:0] - :POSTROUTING - [0:0] - :KUBE-EXT-4SW47YFZTEDKD3PK - [0:0] - :KUBE-EXT-GNZBNJ2PO5MGZ6GT - [0:0] - :KUBE-EXT-NUKIZ6OKUXPJNT4C - [0:0] - :KUBE-EXT-X27LE4BHSL4DOUIK - [0:0] - :KUBE-FW-NUKIZ6OKUXPJNT4C - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - :KUBE-MARK-MASQ - [0:0] - :KUBE-NODEPORTS - [0:0] - :KUBE-POSTROUTING - [0:0] - :KUBE-PROXY-CANARY - [0:0] - :KUBE-SEP-C6EBXVWJJZMIWKLZ - [0:0] - :KUBE-SEP-I77PXRDZVX7PMWMN - [0:0] - :KUBE-SEP-OYPFS5VJICHGATKP - [0:0] - :KUBE-SEP-RS4RBKLTHTF2IUXJ - [0:0] - :KUBE-SEP-SXIVWICOYRO3J4NJ - [0:0] - :KUBE-SEP-UKSFD7AGPMPPLUHC - [0:0] - :KUBE-SERVICES - [0:0] - :KUBE-SVC-4SW47YFZTEDKD3PK - [0:0] - :KUBE-SVC-GNZBNJ2PO5MGZ6GT - [0:0] - :KUBE-SVC-NUKIZ6OKUXPJNT4C - [0:0] - :KUBE-SVC-X27LE4BHSL4DOUIK - [0:0] - :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] - -A PREROUTING -m comment --comment kubernetes service portals -j KUBE-SERVICES - -A OUTPUT -m comment --comment kubernetes service portals -j KUBE-SERVICES - -A POSTROUTING -m comment --comment kubernetes postrouting rules -j KUBE-POSTROUTING - -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN - -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 - -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE - -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 - -A KUBE-NODEPORTS -m comment --comment ns2/svc2:p80 -m tcp -p tcp --dport 3001 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT - -A KUBE-NODEPORTS -m comment --comment ns3/svc3:p80 -m tcp -p tcp --dport 3003 -j KUBE-EXT-X27LE4BHSL4DOUIK - -A KUBE-NODEPORTS -m comment --comment ns5/svc5:p80 -m tcp -p tcp --dport 3002 -j KUBE-EXT-NUKIZ6OKUXPJNT4C - -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O - -A KUBE-SERVICES -m comment --comment "ns2/svc2:p80 cluster IP" -m tcp -p tcp -d 172.30.0.42 --dport 80 -j KUBE-SVC-GNZBNJ2PO5MGZ6GT - -A KUBE-SERVICES -m comment --comment "ns2/svc2:p80 external IP" -m tcp -p tcp -d 192.168.99.22 --dport 80 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT - -A KUBE-SERVICES -m comment --comment "ns2/svc2:p80 loadbalancer IP" -m tcp -p tcp -d 1.2.3.4 --dport 80 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT - -A KUBE-SERVICES -m comment --comment "ns3/svc3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 -j KUBE-SVC-X27LE4BHSL4DOUIK - -A KUBE-SERVICES -m comment --comment "ns4/svc4:p80 cluster IP" -m tcp -p tcp -d 172.30.0.44 --dport 80 -j KUBE-SVC-4SW47YFZTEDKD3PK - -A KUBE-SERVICES -m comment --comment "ns4/svc4:p80 external IP" -m tcp -p tcp -d 192.168.99.33 --dport 80 -j KUBE-EXT-4SW47YFZTEDKD3PK - -A KUBE-SERVICES -m comment --comment "ns5/svc5:p80 cluster IP" -m tcp -p tcp -d 172.30.0.45 --dport 80 -j KUBE-SVC-NUKIZ6OKUXPJNT4C - -A KUBE-SERVICES -m comment --comment "ns5/svc5:p80 loadbalancer IP" -m tcp -p tcp -d 5.6.7.8 --dport 80 -j KUBE-FW-NUKIZ6OKUXPJNT4C - -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS - -A KUBE-EXT-4SW47YFZTEDKD3PK -m comment --comment "masquerade traffic for ns4/svc4:p80 external destinations" -j KUBE-MARK-MASQ - -A KUBE-EXT-4SW47YFZTEDKD3PK -j KUBE-SVC-4SW47YFZTEDKD3PK - -A KUBE-EXT-GNZBNJ2PO5MGZ6GT -m comment --comment "pod traffic for ns2/svc2:p80 external destinations" -s 10.0.0.0/8 -j KUBE-SVC-GNZBNJ2PO5MGZ6GT - -A KUBE-EXT-GNZBNJ2PO5MGZ6GT -m comment --comment "masquerade LOCAL traffic for ns2/svc2:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ - -A KUBE-EXT-GNZBNJ2PO5MGZ6GT -m comment --comment "route LOCAL traffic for ns2/svc2:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-GNZBNJ2PO5MGZ6GT - -A KUBE-EXT-NUKIZ6OKUXPJNT4C -m comment --comment "masquerade traffic for ns5/svc5:p80 external destinations" -j KUBE-MARK-MASQ - -A KUBE-EXT-NUKIZ6OKUXPJNT4C -j KUBE-SVC-NUKIZ6OKUXPJNT4C - -A KUBE-EXT-X27LE4BHSL4DOUIK -m comment --comment "masquerade traffic for ns3/svc3:p80 external destinations" -j KUBE-MARK-MASQ - -A KUBE-EXT-X27LE4BHSL4DOUIK -j KUBE-SVC-X27LE4BHSL4DOUIK - -A KUBE-FW-NUKIZ6OKUXPJNT4C -m comment --comment "ns5/svc5:p80 loadbalancer IP" -s 203.0.113.0/25 -j KUBE-EXT-NUKIZ6OKUXPJNT4C - -A KUBE-FW-NUKIZ6OKUXPJNT4C -m comment --comment "other traffic to ns5/svc5:p80 will be dropped by KUBE-PROXY-FIREWALL" - -A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -s 10.180.0.5 -j KUBE-MARK-MASQ - -A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.5:80 - -A KUBE-SEP-I77PXRDZVX7PMWMN -m comment --comment ns5/svc5:p80 -s 10.180.0.3 -j KUBE-MARK-MASQ - -A KUBE-SEP-I77PXRDZVX7PMWMN -m comment --comment ns5/svc5:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.3:80 - -A KUBE-SEP-OYPFS5VJICHGATKP -m comment --comment ns3/svc3:p80 -s 10.180.0.3 -j KUBE-MARK-MASQ - -A KUBE-SEP-OYPFS5VJICHGATKP -m comment --comment ns3/svc3:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.3:80 - -A KUBE-SEP-RS4RBKLTHTF2IUXJ -m comment --comment ns2/svc2:p80 -s 10.180.0.2 -j KUBE-MARK-MASQ - -A KUBE-SEP-RS4RBKLTHTF2IUXJ -m comment --comment ns2/svc2:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.2:80 - -A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -s 10.180.0.1 -j KUBE-MARK-MASQ - -A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.1:80 - -A KUBE-SEP-UKSFD7AGPMPPLUHC -m comment --comment ns4/svc4:p80 -s 10.180.0.4 -j KUBE-MARK-MASQ - -A KUBE-SEP-UKSFD7AGPMPPLUHC -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.4:80 - -A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 cluster IP" -m tcp -p tcp -d 172.30.0.44 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ - -A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 -> 10.180.0.4:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-UKSFD7AGPMPPLUHC - -A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 -> 10.180.0.5:80" -j KUBE-SEP-C6EBXVWJJZMIWKLZ - -A KUBE-SVC-GNZBNJ2PO5MGZ6GT -m comment --comment "ns2/svc2:p80 cluster IP" -m tcp -p tcp -d 172.30.0.42 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ - -A KUBE-SVC-GNZBNJ2PO5MGZ6GT -m comment --comment "ns2/svc2:p80 -> 10.180.0.2:80" -j KUBE-SEP-RS4RBKLTHTF2IUXJ - -A KUBE-SVC-NUKIZ6OKUXPJNT4C -m comment --comment "ns5/svc5:p80 cluster IP" -m tcp -p tcp -d 172.30.0.45 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ - -A KUBE-SVC-NUKIZ6OKUXPJNT4C -m comment --comment "ns5/svc5:p80 -> 10.180.0.3:80" -j KUBE-SEP-I77PXRDZVX7PMWMN - -A KUBE-SVC-X27LE4BHSL4DOUIK -m comment --comment "ns3/svc3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ - -A KUBE-SVC-X27LE4BHSL4DOUIK -m comment --comment "ns3/svc3:p80 -> 10.180.0.3:80" -j KUBE-SEP-OYPFS5VJICHGATKP - -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ - -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.180.0.1:80" -j KUBE-SEP-SXIVWICOYRO3J4NJ - COMMIT - *mangle - :KUBE-IPTABLES-HINT - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - :KUBE-PROXY-CANARY - [0:0] - COMMIT - `) - - err := ipt.RestoreAll([]byte(initial), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters) - if err != nil { - t.Fatalf("Unexpected error setting up iptables state: %v", err) - } - - encounteredError := cleanupLeftoversForFamily(ctx, ipt) - if encounteredError { - t.Fatal("Unexpected error from cleanupLeftoversForFamily") - } - - var buf bytes.Buffer - err = ipt.SaveInto("", &buf) - if err != nil { - t.Fatalf("Unexpected error reading filter table: %v", err) - } - - expected := dedent.Dedent(` - *filter - :INPUT - [0:0] - :FORWARD - [0:0] - :OUTPUT - [0:0] - :KUBE-FIREWALL - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - - # These are created by both kubelet and kube-proxy and intentionally not - # cleaned up by kube-proxy. - -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP - -A INPUT -j KUBE-FIREWALL - -A OUTPUT -j KUBE-FIREWALL - - # Non-Kubernetes rules are preserved - -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP - -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP - -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP - - COMMIT - - *nat - :PREROUTING - [0:0] - :INPUT - [0:0] - :OUTPUT - [0:0] - :POSTROUTING - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - COMMIT - - *mangle - :KUBE-IPTABLES-HINT - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - COMMIT - `) - expected, err = sortIPTablesRules(expected) - if err != nil { - t.Fatalf("Unexpected error sorting expected output: %v", err) - } - - assertIPTablesRulesEqual(t, getLine(), false, expected, buf.String()) -} diff --git a/pkg/proxy/ipvs/cleanup.go b/pkg/proxy/ipvs/cleanup.go new file mode 100644 index 0000000000000..fd348cfc7b52e --- /dev/null +++ b/pkg/proxy/ipvs/cleanup.go @@ -0,0 +1,129 @@ +//go:build linux + +/* +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. +*/ + +package ipvs + +import ( + "context" + "os/exec" + + v1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + utilipset "k8s.io/kubernetes/pkg/proxy/ipvs/ipset" + utilipvs "k8s.io/kubernetes/pkg/proxy/ipvs/util" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" +) + +// CleanupIptablesLeftovers removes all iptables rules and chains created by the Proxier +// It returns true if an error was encountered. Errors are logged. +func cleanupIptablesLeftovers(ctx context.Context, ipt utiliptables.Interface) (encounteredError bool) { + logger := klog.FromContext(ctx) + // Unlink the iptables chains created by ipvs Proxier + for _, jc := range iptablesJumpChain { + args := []string{ + "-m", "comment", "--comment", jc.comment, + "-j", string(jc.to), + } + if err := ipt.DeleteRule(jc.table, jc.from, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + logger.Error(err, "Error removing iptables rules in ipvs proxier") + encounteredError = true + } + } + } + + // Flush and remove all of our chains. Flushing all chains before removing them also removes all links between chains first. + for _, ch := range iptablesCleanupChains { + if err := ipt.FlushChain(ch.table, ch.chain); err != nil { + if !utiliptables.IsNotFoundError(err) { + logger.Error(err, "Error removing iptables rules in ipvs proxier") + encounteredError = true + } + } + } + + // Remove all of our chains. + for _, ch := range iptablesCleanupChains { + if err := ipt.DeleteChain(ch.table, ch.chain); err != nil { + if !utiliptables.IsNotFoundError(err) { + logger.Error(err, "Error removing iptables rules in ipvs proxier") + encounteredError = true + } + } + } + + return encounteredError +} + +// CleanupLeftovers clean up all ipvs and iptables rules created by ipvs Proxier. +func CleanupLeftovers(ctx context.Context) (encounteredError bool) { + // libipvs.New() will log errors if the "ip_vs" kernel module (or the "modprobe" + // binary) is not available. Logging an extra error is fine if we were actually + // trying to run the ipvs proxier, but it's confusing to see when just doing + // best-effort cleanup (eg, when starting the nftables proxier), so we do the same + // check libipvs does here, and bail out without calling libipvs if it fails. + if _, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil { + return false + } + + ipts := utiliptables.NewBestEffort() + ipsetInterface := utilipset.New() + ipvsInterface := utilipvs.New() + + return cleanupLeftovers(ctx, ipvsInterface, ipts, ipsetInterface) +} + +func cleanupLeftovers(ctx context.Context, ipvs utilipvs.Interface, ipts map[v1.IPFamily]utiliptables.Interface, ipset utilipset.Interface) (encounteredError bool) { + logger := klog.FromContext(ctx) + // Clear all ipvs rules + if ipvs != nil { + err := ipvs.Flush() + if err != nil { + logger.Error(err, "Error flushing ipvs rules") + encounteredError = true + } + } + // Delete dummy interface created by ipvs Proxier. + nl := NewNetLinkHandle(false) + err := nl.DeleteDummyDevice(defaultDummyDevice) + if err != nil { + logger.Error(err, "Error deleting dummy device created by ipvs proxier", "device", defaultDummyDevice) + encounteredError = true + } + + // Clear iptables created by ipvs Proxier. + for _, ipt := range ipts { + encounteredError = cleanupIptablesLeftovers(ctx, ipt) || encounteredError + } + + // Destroy ip sets created by ipvs Proxier. We should call it after cleaning up + // iptables since we can NOT delete ip set which is still referenced by iptables. + if _, err := ipset.GetVersion(); err == nil { + for _, set := range ipsetInfo { + err = ipset.DestroySet(set.name) + if err != nil { + if !utilipset.IsNotFoundError(err) { + logger.Error(err, "Error removing ipset", "ipset", set.name) + encounteredError = true + } + } + } + } + + return encounteredError +} diff --git a/pkg/proxy/ipvs/cleanup_test.go b/pkg/proxy/ipvs/cleanup_test.go new file mode 100644 index 0000000000000..b2526720e94f4 --- /dev/null +++ b/pkg/proxy/ipvs/cleanup_test.go @@ -0,0 +1,163 @@ +//go:build linux + +/* +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. +*/ + +package ipvs + +import ( + "bytes" + "strings" + "testing" + + "github.com/lithammer/dedent" + + v1 "k8s.io/api/core/v1" + discovery "k8s.io/api/discovery/v1" + "k8s.io/kubernetes/pkg/proxy" + ipsettest "k8s.io/kubernetes/pkg/proxy/ipvs/ipset/testing" + ipvstest "k8s.io/kubernetes/pkg/proxy/ipvs/util/testing" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" + iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing" + "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/utils/ptr" +) + +func TestCleanupLeftovers(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ipt := iptablestest.NewFake() + ipts := map[v1.IPFamily]utiliptables.Interface{ + v1.IPv4Protocol: ipt, + } + ipvs := ipvstest.NewFake() + ipset := ipsettest.NewFake(testIPSetVersion) + + // Preload some iptables rules + initial := dedent.Dedent(` + *filter + :INPUT - [0:0] + :FORWARD - [0:0] + :OUTPUT - [0:0] + :KUBE-FIREWALL - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + -A INPUT -j KUBE-FIREWALL + -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP + -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP + -A OUTPUT -j KUBE-FIREWALL + -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP + -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP + COMMIT + *nat + :PREROUTING - [0:0] + :INPUT - [0:0] + :OUTPUT - [0:0] + :POSTROUTING - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + COMMIT + *mangle + :KUBE-IPTABLES-HINT - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + COMMIT + `) + err := ipt.RestoreAll([]byte(initial), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters) + if err != nil { + t.Fatalf("Unexpected error setting up iptables state: %v", err) + } + + fp := NewFakeProxier(ctx, ipt, ipvs, ipset, nil, nil, v1.IPv4Protocol) + svcIP := "10.20.30.41" + svcPort := 80 + svcNodePort := 3001 + svcPortName := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + } + + makeServiceMap(fp, + makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName.Port, + Port: int32(svcPort), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort), + }} + }), + ) + epIP := "10.180.0.1" + populateEndpointSlices(fp, + makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{epIP}, + }} + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName.Port), + Port: ptr.To(int32(svcPort)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + ) + + _ = fp.syncProxyRules() + + // test cleanup left over + encounteredError := cleanupLeftovers(ctx, ipvs, ipts, ipset) + if encounteredError { + t.Errorf("Cleanup leftovers failed") + } + + var buf bytes.Buffer + err = ipt.SaveInto("", &buf) + if err != nil { + t.Fatalf("Unexpected error reading filter table: %v", err) + } + got := buf.String() + + expected := strings.TrimLeft(dedent.Dedent(` + *nat + :PREROUTING - [0:0] + :INPUT - [0:0] + :OUTPUT - [0:0] + :POSTROUTING - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + COMMIT + *filter + :INPUT - [0:0] + :FORWARD - [0:0] + :OUTPUT - [0:0] + :KUBE-FIREWALL - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + -A INPUT -j KUBE-FIREWALL + -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP + -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP + -A OUTPUT -j KUBE-FIREWALL + -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP + -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP + COMMIT + *mangle + :KUBE-IPTABLES-HINT - [0:0] + :KUBE-KUBELET-CANARY - [0:0] + COMMIT + `), "\n") + + if got != expected { + t.Fatalf("expected post-cleanup rules to be:\n%s\n\ngot:\n%s\n", expected, got) + } + + // FIXME: check ipvs and ipset state +} diff --git a/pkg/proxy/ipvs/graceful_termination.go b/pkg/proxy/ipvs/graceful_termination.go index 45eaebb5158f6..93d6174e3caeb 100644 --- a/pkg/proxy/ipvs/graceful_termination.go +++ b/pkg/proxy/ipvs/graceful_termination.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/graceful_termination_test.go b/pkg/proxy/ipvs/graceful_termination_test.go index 3b3d0d6c58ebc..8eb02c19b62e1 100644 --- a/pkg/proxy/ipvs/graceful_termination_test.go +++ b/pkg/proxy/ipvs/graceful_termination_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2019 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset.go b/pkg/proxy/ipvs/ipset.go index 193fc634b636d..306f8a680d886 100644 --- a/pkg/proxy/ipvs/ipset.go +++ b/pkg/proxy/ipvs/ipset.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset/ipset.go b/pkg/proxy/ipvs/ipset/ipset.go index 041e927816710..c2b5c74707ec6 100644 --- a/pkg/proxy/ipvs/ipset/ipset.go +++ b/pkg/proxy/ipvs/ipset/ipset.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset/ipset_test.go b/pkg/proxy/ipvs/ipset/ipset_test.go index a2e45327519be..a03ebba243905 100644 --- a/pkg/proxy/ipvs/ipset/ipset_test.go +++ b/pkg/proxy/ipvs/ipset/ipset_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset/testing/fake.go b/pkg/proxy/ipvs/ipset/testing/fake.go index 84377a832ee1a..a4a9608b60fed 100644 --- a/pkg/proxy/ipvs/ipset/testing/fake.go +++ b/pkg/proxy/ipvs/ipset/testing/fake.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset/testing/fake_test.go b/pkg/proxy/ipvs/ipset/testing/fake_test.go index a77215ae6a7b8..84ef3b1f8f36a 100644 --- a/pkg/proxy/ipvs/ipset/testing/fake_test.go +++ b/pkg/proxy/ipvs/ipset/testing/fake_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset/types.go b/pkg/proxy/ipvs/ipset/types.go index 45632d278943f..e40a7cdc3a7b8 100644 --- a/pkg/proxy/ipvs/ipset/types.go +++ b/pkg/proxy/ipvs/ipset/types.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/ipset_test.go b/pkg/proxy/ipvs/ipset_test.go index 6f5842f2e9631..35806ea9f9f09 100644 --- a/pkg/proxy/ipvs/ipset_test.go +++ b/pkg/proxy/ipvs/ipset_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/netlink_linux.go b/pkg/proxy/ipvs/netlink_linux.go index 632b5078ad3a2..fcf009e69f0f3 100644 --- a/pkg/proxy/ipvs/netlink_linux.go +++ b/pkg/proxy/ipvs/netlink_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/proxier.go b/pkg/proxy/ipvs/proxier.go index 9e80d35a4f11f..b0a71bad37239 100644 --- a/pkg/proxy/ipvs/proxier.go +++ b/pkg/proxy/ipvs/proxier.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. @@ -26,7 +25,6 @@ import ( "fmt" "io" "net" - "os/exec" "strconv" "strings" "sync" @@ -356,7 +354,7 @@ func NewProxier( nodePortAddresses := proxyutil.NewNodePortAddresses(ipFamily, nodePortAddressStrings) - serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer) + serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer, ipFamily) // excludeCIDRs has been validated before, here we just parse it to IPNet list parsedExcludeCIDRs, _ := netutils.ParseCIDRs(excludeCIDRs) @@ -563,196 +561,6 @@ func getFirstColumn(r io.Reader) ([]string, error) { return words, nil } -// CanUseIPVSProxier checks if we can use the ipvs Proxier. -// The ipset version and the scheduler are checked. If any virtual servers (VS) -// already exist with the configured scheduler, we just return. Otherwise -// we check if a dummy VS can be configured with the configured scheduler. -// Kernel modules will be loaded automatically if necessary. -func CanUseIPVSProxier(ctx context.Context, ipvs utilipvs.Interface, ipsetver IPSetVersioner, scheduler string) error { - logger := klog.FromContext(ctx) - // BUG: https://github.com/moby/ipvs/issues/27 - // If ipvs is not compiled into the kernel no error is returned and handle==nil. - // This in turn causes ipvs.GetVirtualServers and ipvs.AddVirtualServer - // to return ok (err==nil). If/when this bug is fixed parameter "ipvs" will be nil - // if ipvs is not supported by the kernel. Until then a re-read work-around is used. - if ipvs == nil { - return fmt.Errorf("Ipvs not supported by the kernel") - } - - // Check ipset version - versionString, err := ipsetver.GetVersion() - if err != nil { - return fmt.Errorf("error getting ipset version, error: %v", err) - } - if !checkMinVersion(versionString) { - return fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion) - } - - if scheduler == "" { - scheduler = defaultScheduler - } - - // If any virtual server (VS) using the scheduler exist we skip the checks. - vservers, err := ipvs.GetVirtualServers() - if err != nil { - logger.Error(err, "Can't read the ipvs") - return err - } - logger.V(5).Info("Virtual Servers", "count", len(vservers)) - if len(vservers) > 0 { - // This is most likely a kube-proxy re-start. We know that ipvs works - // and if any VS uses the configured scheduler, we are done. - for _, vs := range vservers { - if vs.Scheduler == scheduler { - logger.V(5).Info("VS exist, Skipping checks") - return nil - } - } - logger.V(5).Info("No existing VS uses the configured scheduler", "scheduler", scheduler) - } - - // Try to insert a dummy VS with the passed scheduler. - // We should use a VIP address that is not used on the node. - // An address "198.51.100.0" from the TEST-NET-2 rage in https://datatracker.ietf.org/doc/html/rfc5737 - // is used. These addresses are reserved for documentation. If the user is using - // this address for a VS anyway we *will* mess up, but that would be an invalid configuration. - // If the user have configured the address to an interface on the node (but not a VS) - // then traffic will temporary be routed to ipvs during the probe and dropped. - // The later case is also and invalid configuration, but the traffic impact will be minor. - // This should not be a problem if users honors reserved addresses, but cut/paste - // from documentation is not unheard of, so the restriction to not use the TEST-NET-2 range - // must be documented. - vs := utilipvs.VirtualServer{ - Address: netutils.ParseIPSloppy("198.51.100.0"), - Protocol: "TCP", - Port: 20000, - Scheduler: scheduler, - } - if err := ipvs.AddVirtualServer(&vs); err != nil { - logger.Error(err, "Could not create dummy VS", "scheduler", scheduler) - return err - } - - // To overcome the BUG described above we check that the VS is *really* added. - vservers, err = ipvs.GetVirtualServers() - if err != nil { - logger.Error(err, "ipvs.GetVirtualServers") - return err - } - logger.V(5).Info("Virtual Servers after adding dummy", "count", len(vservers)) - if len(vservers) == 0 { - logger.Info("Dummy VS not created", "scheduler", scheduler) - return fmt.Errorf("Ipvs not supported") // This is a BUG work-around - } - logger.V(5).Info("Dummy VS created", "vs", vs) - - if err := ipvs.DeleteVirtualServer(&vs); err != nil { - logger.Error(err, "Could not delete dummy VS") - return err - } - - return nil -} - -// CleanupIptablesLeftovers removes all iptables rules and chains created by the Proxier -// It returns true if an error was encountered. Errors are logged. -func cleanupIptablesLeftovers(ctx context.Context, ipt utiliptables.Interface) (encounteredError bool) { - logger := klog.FromContext(ctx) - // Unlink the iptables chains created by ipvs Proxier - for _, jc := range iptablesJumpChain { - args := []string{ - "-m", "comment", "--comment", jc.comment, - "-j", string(jc.to), - } - if err := ipt.DeleteRule(jc.table, jc.from, args...); err != nil { - if !utiliptables.IsNotFoundError(err) { - logger.Error(err, "Error removing iptables rules in ipvs proxier") - encounteredError = true - } - } - } - - // Flush and remove all of our chains. Flushing all chains before removing them also removes all links between chains first. - for _, ch := range iptablesCleanupChains { - if err := ipt.FlushChain(ch.table, ch.chain); err != nil { - if !utiliptables.IsNotFoundError(err) { - logger.Error(err, "Error removing iptables rules in ipvs proxier") - encounteredError = true - } - } - } - - // Remove all of our chains. - for _, ch := range iptablesCleanupChains { - if err := ipt.DeleteChain(ch.table, ch.chain); err != nil { - if !utiliptables.IsNotFoundError(err) { - logger.Error(err, "Error removing iptables rules in ipvs proxier") - encounteredError = true - } - } - } - - return encounteredError -} - -// CleanupLeftovers clean up all ipvs and iptables rules created by ipvs Proxier. -func CleanupLeftovers(ctx context.Context) (encounteredError bool) { - // libipvs.New() will log errors if the "ip_vs" kernel module (or the "modprobe" - // binary) is not available. Logging an extra error is fine if we were actually - // trying to run the ipvs proxier, but it's confusing to see when just doing - // best-effort cleanup (eg, when starting the nftables proxier), so we do the same - // check libipvs does here, and bail out without calling libipvs if it fails. - if _, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil { - return false - } - - ipts := utiliptables.NewBestEffort() - ipsetInterface := utilipset.New() - ipvsInterface := utilipvs.New() - - return cleanupLeftovers(ctx, ipvsInterface, ipts, ipsetInterface) -} - -func cleanupLeftovers(ctx context.Context, ipvs utilipvs.Interface, ipts map[v1.IPFamily]utiliptables.Interface, ipset utilipset.Interface) (encounteredError bool) { - logger := klog.FromContext(ctx) - // Clear all ipvs rules - if ipvs != nil { - err := ipvs.Flush() - if err != nil { - logger.Error(err, "Error flushing ipvs rules") - encounteredError = true - } - } - // Delete dummy interface created by ipvs Proxier. - nl := NewNetLinkHandle(false) - err := nl.DeleteDummyDevice(defaultDummyDevice) - if err != nil { - logger.Error(err, "Error deleting dummy device created by ipvs proxier", "device", defaultDummyDevice) - encounteredError = true - } - - // Clear iptables created by ipvs Proxier. - for _, ipt := range ipts { - encounteredError = cleanupIptablesLeftovers(ctx, ipt) || encounteredError - } - - // Destroy ip sets created by ipvs Proxier. We should call it after cleaning up - // iptables since we can NOT delete ip set which is still referenced by iptables. - if _, err := ipset.GetVersion(); err == nil { - for _, set := range ipsetInfo { - err = ipset.DestroySet(set.name) - if err != nil { - if !utilipset.IsNotFoundError(err) { - logger.Error(err, "Error removing ipset", "ipset", set.name) - encounteredError = true - } - } - } - } - - return encounteredError -} - // Sync is called to synchronize the proxier state to iptables and ipvs as soon as possible. func (proxier *Proxier) Sync() { if proxier.healthzServer != nil { diff --git a/pkg/proxy/ipvs/proxier_test.go b/pkg/proxy/ipvs/proxier_test.go index 16187009c9e83..a27bf1f89b0b9 100644 --- a/pkg/proxy/ipvs/proxier_test.go +++ b/pkg/proxy/ipvs/proxier_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. @@ -30,7 +29,6 @@ import ( "testing" "time" - "github.com/lithammer/dedent" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" @@ -223,190 +221,6 @@ func makeTestEndpointSlice(namespace, name string, sliceNum int, epsFunc func(*d return eps } -func TestCleanupLeftovers(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ipt := iptablestest.NewFake() - ipts := map[v1.IPFamily]utiliptables.Interface{ - v1.IPv4Protocol: ipt, - } - ipvs := ipvstest.NewFake() - ipset := ipsettest.NewFake(testIPSetVersion) - - // Preload some iptables rules - initial := dedent.Dedent(` - *filter - :INPUT - [0:0] - :FORWARD - [0:0] - :OUTPUT - [0:0] - :KUBE-FIREWALL - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - -A INPUT -j KUBE-FIREWALL - -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP - -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP - -A OUTPUT -j KUBE-FIREWALL - -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP - -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP - COMMIT - *nat - :PREROUTING - [0:0] - :INPUT - [0:0] - :OUTPUT - [0:0] - :POSTROUTING - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - COMMIT - *mangle - :KUBE-IPTABLES-HINT - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - COMMIT - `) - err := ipt.RestoreAll([]byte(initial), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters) - if err != nil { - t.Fatalf("Unexpected error setting up iptables state: %v", err) - } - - fp := NewFakeProxier(ctx, ipt, ipvs, ipset, nil, nil, v1.IPv4Protocol) - svcIP := "10.20.30.41" - svcPort := 80 - svcNodePort := 3001 - svcPortName := proxy.ServicePortName{ - NamespacedName: makeNSN("ns1", "svc1"), - Port: "p80", - } - - makeServiceMap(fp, - makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { - svc.Spec.Type = "NodePort" - svc.Spec.ClusterIP = svcIP - svc.Spec.Ports = []v1.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: v1.ProtocolTCP, - NodePort: int32(svcNodePort), - }} - }), - ) - epIP := "10.180.0.1" - populateEndpointSlices(fp, - makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { - eps.AddressType = discovery.AddressTypeIPv4 - eps.Endpoints = []discovery.Endpoint{{ - Addresses: []string{epIP}, - }} - eps.Ports = []discovery.EndpointPort{{ - Name: ptr.To(svcPortName.Port), - Port: ptr.To(int32(svcPort)), - Protocol: ptr.To(v1.ProtocolTCP), - }} - }), - ) - - fp.syncProxyRules() - - // test cleanup left over - encounteredError := cleanupLeftovers(ctx, ipvs, ipts, ipset) - if encounteredError { - t.Errorf("Cleanup leftovers failed") - } - - var buf bytes.Buffer - err = ipt.SaveInto("", &buf) - if err != nil { - t.Fatalf("Unexpected error reading filter table: %v", err) - } - got := buf.String() - - expected := strings.TrimLeft(dedent.Dedent(` - *nat - :PREROUTING - [0:0] - :INPUT - [0:0] - :OUTPUT - [0:0] - :POSTROUTING - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - COMMIT - *filter - :INPUT - [0:0] - :FORWARD - [0:0] - :OUTPUT - [0:0] - :KUBE-FIREWALL - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - -A INPUT -j KUBE-FIREWALL - -A INPUT -m comment --comment "someone else's input rule" -s 1.2.3.4 -j DROP - -A FORWARD -m comment --comment "someone else's forward rule" -s 1.2.3.4 -j DROP - -A OUTPUT -j KUBE-FIREWALL - -A OUTPUT -m comment --comment "someone else's output rule" -s 1.2.3.4 -j DROP - -A KUBE-FIREWALL -m comment --comment "block incoming localnet connections" -d 127.0.0.0/8 ! -s 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP - COMMIT - *mangle - :KUBE-IPTABLES-HINT - [0:0] - :KUBE-KUBELET-CANARY - [0:0] - COMMIT - `), "\n") - - if got != expected { - t.Fatalf("expected post-cleanup rules to be:\n%s\n\ngot:\n%s\n", expected, got) - } - - // FIXME: check ipvs and ipset state -} - -func TestCanUseIPVSProxier(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - testCases := []struct { - name string - scheduler string - ipsetVersion string - ipsetErr error - ipvsErr string - ok bool - }{ - { - name: "happy days", - ipsetVersion: MinIPSetCheckVersion, - ok: true, - }, - { - name: "ipset error", - scheduler: "", - ipsetVersion: MinIPSetCheckVersion, - ipsetErr: fmt.Errorf("oops"), - ok: false, - }, - { - name: "ipset version too low", - scheduler: "rr", - ipsetVersion: "4.3.0", - ok: false, - }, - { - name: "GetVirtualServers fail", - ipsetVersion: MinIPSetCheckVersion, - ipvsErr: "GetVirtualServers", - ok: false, - }, - { - name: "AddVirtualServer fail", - ipsetVersion: MinIPSetCheckVersion, - ipvsErr: "AddVirtualServer", - ok: false, - }, - { - name: "DeleteVirtualServer fail", - ipsetVersion: MinIPSetCheckVersion, - ipvsErr: "DeleteVirtualServer", - ok: false, - }, - } - - for _, tc := range testCases { - ipvs := &fakeIpvs{tc.ipvsErr, false} - versioner := &fakeIPSetVersioner{version: tc.ipsetVersion, err: tc.ipsetErr} - err := CanUseIPVSProxier(ctx, ipvs, versioner, tc.scheduler) - if (err == nil) != tc.ok { - t.Errorf("Case [%s], expect %v, got err: %v", tc.name, tc.ok, err) - } - } -} - func TestGetNodeIPs(t *testing.T) { testCases := []struct { isIPv6 bool diff --git a/pkg/proxy/ipvs/supported.go b/pkg/proxy/ipvs/supported.go new file mode 100644 index 0000000000000..ffeca1fa92a40 --- /dev/null +++ b/pkg/proxy/ipvs/supported.go @@ -0,0 +1,119 @@ +//go:build linux + +/* +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. +*/ + +package ipvs + +import ( + "context" + "fmt" + + "k8s.io/klog/v2" + utilipvs "k8s.io/kubernetes/pkg/proxy/ipvs/util" + netutils "k8s.io/utils/net" +) + +// CanUseIPVSProxier checks if we can use the ipvs Proxier. +// The ipset version and the scheduler are checked. If any virtual servers (VS) +// already exist with the configured scheduler, we just return. Otherwise +// we check if a dummy VS can be configured with the configured scheduler. +// Kernel modules will be loaded automatically if necessary. +func CanUseIPVSProxier(ctx context.Context, ipvs utilipvs.Interface, ipsetver IPSetVersioner, scheduler string) error { + logger := klog.FromContext(ctx) + // BUG: https://github.com/moby/ipvs/issues/27 + // If ipvs is not compiled into the kernel no error is returned and handle==nil. + // This in turn causes ipvs.GetVirtualServers and ipvs.AddVirtualServer + // to return ok (err==nil). If/when this bug is fixed parameter "ipvs" will be nil + // if ipvs is not supported by the kernel. Until then a re-read work-around is used. + if ipvs == nil { + return fmt.Errorf("ipvs not supported by the kernel") + } + + // Check ipset version + versionString, err := ipsetver.GetVersion() + if err != nil { + return fmt.Errorf("error getting ipset version, error: %w", err) + } + if !checkMinVersion(versionString) { + return fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion) + } + + if scheduler == "" { + scheduler = defaultScheduler + } + + // If any virtual server (VS) using the scheduler exist we skip the checks. + vservers, err := ipvs.GetVirtualServers() + if err != nil { + logger.Error(err, "Can't read the ipvs") + return err + } + logger.V(5).Info("Virtual Servers", "count", len(vservers)) + if len(vservers) > 0 { + // This is most likely a kube-proxy re-start. We know that ipvs works + // and if any VS uses the configured scheduler, we are done. + for _, vs := range vservers { + if vs.Scheduler == scheduler { + logger.V(5).Info("VS exist, Skipping checks") + return nil + } + } + logger.V(5).Info("No existing VS uses the configured scheduler", "scheduler", scheduler) + } + + // Try to insert a dummy VS with the passed scheduler. + // We should use a VIP address that is not used on the node. + // An address "198.51.100.0" from the TEST-NET-2 rage in https://datatracker.ietf.org/doc/html/rfc5737 + // is used. These addresses are reserved for documentation. If the user is using + // this address for a VS anyway we *will* mess up, but that would be an invalid configuration. + // If the user have configured the address to an interface on the node (but not a VS) + // then traffic will temporary be routed to ipvs during the probe and dropped. + // The later case is also and invalid configuration, but the traffic impact will be minor. + // This should not be a problem if users honors reserved addresses, but cut/paste + // from documentation is not unheard of, so the restriction to not use the TEST-NET-2 range + // must be documented. + vs := utilipvs.VirtualServer{ + Address: netutils.ParseIPSloppy("198.51.100.0"), + Protocol: "TCP", + Port: 20000, + Scheduler: scheduler, + } + if err := ipvs.AddVirtualServer(&vs); err != nil { + logger.Error(err, "Could not create dummy VS", "scheduler", scheduler) + return err + } + + // To overcome the BUG described above we check that the VS is *really* added. + vservers, err = ipvs.GetVirtualServers() + if err != nil { + logger.Error(err, "ipvs.GetVirtualServers") + return err + } + logger.V(5).Info("Virtual Servers after adding dummy", "count", len(vservers)) + if len(vservers) == 0 { + logger.Info("Dummy VS not created", "scheduler", scheduler) + return fmt.Errorf("ipvs not supported") // This is a BUG work-around + } + logger.V(5).Info("Dummy VS created", "vs", vs) + + if err := ipvs.DeleteVirtualServer(&vs); err != nil { + logger.Error(err, "Could not delete dummy VS") + return err + } + + return nil +} diff --git a/pkg/proxy/ipvs/supported_test.go b/pkg/proxy/ipvs/supported_test.go new file mode 100644 index 0000000000000..ca0697e6c4417 --- /dev/null +++ b/pkg/proxy/ipvs/supported_test.go @@ -0,0 +1,84 @@ +//go:build linux + +/* +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. +*/ + +package ipvs + +import ( + "fmt" + "testing" + + "k8s.io/kubernetes/test/utils/ktesting" +) + +func TestCanUseIPVSProxier(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + testCases := []struct { + name string + scheduler string + ipsetVersion string + ipsetErr error + ipvsErr string + ok bool + }{ + { + name: "happy days", + ipsetVersion: MinIPSetCheckVersion, + ok: true, + }, + { + name: "ipset error", + scheduler: "", + ipsetVersion: MinIPSetCheckVersion, + ipsetErr: fmt.Errorf("oops"), + ok: false, + }, + { + name: "ipset version too low", + scheduler: "rr", + ipsetVersion: "4.3.0", + ok: false, + }, + { + name: "GetVirtualServers fail", + ipsetVersion: MinIPSetCheckVersion, + ipvsErr: "GetVirtualServers", + ok: false, + }, + { + name: "AddVirtualServer fail", + ipsetVersion: MinIPSetCheckVersion, + ipvsErr: "AddVirtualServer", + ok: false, + }, + { + name: "DeleteVirtualServer fail", + ipsetVersion: MinIPSetCheckVersion, + ipvsErr: "DeleteVirtualServer", + ok: false, + }, + } + + for _, tc := range testCases { + ipvs := &fakeIpvs{tc.ipvsErr, false} + versioner := &fakeIPSetVersioner{version: tc.ipsetVersion, err: tc.ipsetErr} + err := CanUseIPVSProxier(ctx, ipvs, versioner, tc.scheduler) + if (err == nil) != tc.ok { + t.Errorf("Case [%s], expect %v, got err: %v", tc.name, tc.ok, err) + } + } +} diff --git a/pkg/proxy/ipvs/testing/fake.go b/pkg/proxy/ipvs/testing/fake.go index 7b7544fa9c724..ab22d5f1eb778 100644 --- a/pkg/proxy/ipvs/testing/fake.go +++ b/pkg/proxy/ipvs/testing/fake.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/testing/fake_test.go b/pkg/proxy/ipvs/testing/fake_test.go index 227facceb0c9e..06ce335d11f44 100644 --- a/pkg/proxy/ipvs/testing/fake_test.go +++ b/pkg/proxy/ipvs/testing/fake_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/testing/util.go b/pkg/proxy/ipvs/testing/util.go index 599532e1d8933..29baf19c08c9d 100644 --- a/pkg/proxy/ipvs/testing/util.go +++ b/pkg/proxy/ipvs/testing/util.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/util/ipvs.go b/pkg/proxy/ipvs/util/ipvs.go index ac8853c440691..3c1805856617b 100644 --- a/pkg/proxy/ipvs/util/ipvs.go +++ b/pkg/proxy/ipvs/util/ipvs.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/util/ipvs_linux.go b/pkg/proxy/ipvs/util/ipvs_linux.go index e038fad07b642..ed447ba1c7e1b 100644 --- a/pkg/proxy/ipvs/util/ipvs_linux.go +++ b/pkg/proxy/ipvs/util/ipvs_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/util/ipvs_linux_test.go b/pkg/proxy/ipvs/util/ipvs_linux_test.go index f3595f260aa61..06bca7d88b09b 100644 --- a/pkg/proxy/ipvs/util/ipvs_linux_test.go +++ b/pkg/proxy/ipvs/util/ipvs_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/util/ipvs_test.go b/pkg/proxy/ipvs/util/ipvs_test.go index 7cf3b25d61569..782c543d22fd0 100644 --- a/pkg/proxy/ipvs/util/ipvs_test.go +++ b/pkg/proxy/ipvs/util/ipvs_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/util/testing/fake.go b/pkg/proxy/ipvs/util/testing/fake.go index 2d946517d0485..bed59256dd1f3 100644 --- a/pkg/proxy/ipvs/util/testing/fake.go +++ b/pkg/proxy/ipvs/util/testing/fake.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/ipvs/util/testing/fake_test.go b/pkg/proxy/ipvs/util/testing/fake_test.go index 69aac3b1c85e2..23fec029e682a 100644 --- a/pkg/proxy/ipvs/util/testing/fake_test.go +++ b/pkg/proxy/ipvs/util/testing/fake_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/proxy/nftables/cleanup.go b/pkg/proxy/nftables/cleanup.go new file mode 100644 index 0000000000000..8de243f1e4566 --- /dev/null +++ b/pkg/proxy/nftables/cleanup.go @@ -0,0 +1,49 @@ +//go:build linux + +/* +Copyright 2015 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 nftables + +import ( + "context" + + "k8s.io/klog/v2" + "sigs.k8s.io/knftables" +) + +// CleanupLeftovers removes all nftables rules and chains created by the Proxier +// It returns true if an error was encountered. Errors are logged. +func CleanupLeftovers(ctx context.Context) bool { + logger := klog.FromContext(ctx) + var encounteredError bool + + for _, family := range []knftables.Family{knftables.IPv4Family, knftables.IPv6Family} { + nft, err := knftables.New(family, kubeProxyTable) + if err != nil { + continue + } + tx := nft.NewTransaction() + tx.Delete(&knftables.Table{}) + err = nft.Run(ctx, tx) + if err != nil && !knftables.IsNotFound(err) { + logger.Error(err, "Error cleaning up nftables rules") + encounteredError = true + } + } + + return encounteredError +} diff --git a/pkg/proxy/nftables/helpers_test.go b/pkg/proxy/nftables/helpers_test.go index 7cc34bf403b89..daf49b88fd510 100644 --- a/pkg/proxy/nftables/helpers_test.go +++ b/pkg/proxy/nftables/helpers_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -160,8 +159,10 @@ type nftablesTracer struct { // the return value of tracePacket. outputs []string - // markMasq tracks whether the packet has been marked for masquerading - markMasq bool + // additional info about rules we've hit + markMasq bool + masquerade bool + dnat bool } // newNFTablesTracer creates an nftablesTracer. nodeIPs are the IP to treat as local node @@ -269,14 +270,21 @@ var sourceAddrRegexp = regexp.MustCompile(`^ip6* saddr (!= )?(\S+)`) var sourceAddrLookupRegexp = regexp.MustCompile(`^ip6* saddr (!= )?\{([^}]*)\}`) var sourceAddrLocalRegexp = regexp.MustCompile(`^fib saddr type local`) -var endpointVMAPRegexp = regexp.MustCompile(`^numgen random mod \d+ vmap \{(.*)\}$`) +var endpointVMapRegexp = regexp.MustCompile(`^numgen random mod \d+ vmap \{(.*)\}$`) var endpointVMapEntryRegexp = regexp.MustCompile(`\d+ : goto (\S+)`) - -var masqueradeRegexp = regexp.MustCompile(`^jump ` + markMasqChain + `$`) +var endpointDNATRegexp = regexp.MustCompile(`^dnat ip6* addr \. port to numgen random mod \d+ map \{(.*)\}$`) +var endpointDNATEntryRegexp = regexp.MustCompile(`\d+ : (\S+) \. (\d+)`) + +var masqMarkRegexp = regexp.MustCompile(`^mark set mark or 0x[[:xdigit:]]+$`) +var masqCheckRegexp = regexp.MustCompile(`^mark and 0x[[:xdigit:]]+ != 0 mark set mark xor 0x[[:xdigit:]]+`) +var masqueradeRegexp = regexp.MustCompile(`^masquerade fully-random$`) +var dnatCheckRegexp = regexp.MustCompile(`^ct status dnat`) +var hairpinCheckRegexp = regexp.MustCompile(`^ip6* saddr \. ip6* daddr \. meta l4proto \. th dport @hairpin-connections`) var jumpRegexp = regexp.MustCompile(`^(jump|goto) (\S+)$`) var returnRegexp = regexp.MustCompile(`^return$`) var verdictRegexp = regexp.MustCompile(`^(drop|reject)$`) -var dnatRegexp = regexp.MustCompile(`^meta l4proto (tcp|udp|sctp) dnat to (\S+)$`) +var l4protoRegexp = regexp.MustCompile(`^meta l4proto (tcp|udp|sctp)`) +var dnatRegexp = regexp.MustCompile(`^dnat to (\S+)$`) var ignoredRegexp = regexp.MustCompile(strings.Join( []string{ @@ -304,12 +312,32 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP for rule != "" { rule = strings.TrimLeft(rule, " ") - // Note that the order of (some of) the cases is important. e.g., - // masqueradeRegexp must be checked before jumpRegexp, since - // jumpRegexp would also match masqueradeRegexp but do the wrong - // thing with it. + // Note that the order of (some of) the cases is important since + // some of the regexes might match parts of others. switch { + case dnatCheckRegexp.MatchString(rule): + // `^ct status dnat` + // Part of the hairpin check + match := dnatCheckRegexp.FindStringSubmatch(rule) + rule = strings.TrimPrefix(rule, match[0]) + if !tracer.dnat { + rule = "" + break + } + + case hairpinCheckRegexp.MatchString(rule): + // `^ip6* saddr \. ip6* daddr \. meta l4proto \. th dport @hairpin-connections` + // Tests whether the packet is hairpinning back to its client. + match := hairpinCheckRegexp.FindStringSubmatch(rule) + rule = strings.TrimPrefix(rule, match[0]) + if sourceIP != destIP { + rule = "" + break + } + // HACK: we don't actually bother doing the full lookup; we + // assume that if src and dst matched, it's hairpin. + case destIPOnlyLookupRegexp.MatchString(rule): // `^ip6* daddr @(\S+)` // Tests whether destIP is a member of the indicated set. @@ -424,17 +452,44 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP break } - case masqueradeRegexp.MatchString(rule): - // `^jump mark-for-masquerade$` - // Mark for masquerade: we just treat the jump rule itself as - // being what creates the mark, rather than trying to handle - // the rules inside that chain and the "masquerading" chain. - match := jumpRegexp.FindStringSubmatch(rule) + case l4protoRegexp.MatchString(rule): + // `meta l4proto (tcp|udp|sctp)` + match := l4protoRegexp.FindStringSubmatch(rule) + rule = strings.TrimPrefix(rule, match[0]) + if match[1] != protocol { + rule = "" + break + } + + case masqMarkRegexp.MatchString(rule): + // `^mark set mark or 0x[[:xdigit:]]+$` + // Mark for masquerade. + match := masqMarkRegexp.FindStringSubmatch(rule) rule = strings.TrimPrefix(rule, match[0]) tracer.matches = append(tracer.matches, ruleObj.Rule) tracer.markMasq = true + case masqCheckRegexp.MatchString(rule): + // `^mark and 0x[[:xdigit:]]+ != 0 mark set mark xor 0x[[:xdigit:]]+ + // Checks and clears the mark + match := masqCheckRegexp.FindStringSubmatch(rule) + rule = strings.TrimPrefix(rule, match[0]) + if !tracer.markMasq { + rule = "" + break + } + tracer.markMasq = false + + case masqueradeRegexp.MatchString(rule): + // ^masquerade fully-random$ + // Actually perform the masquerading + match := masqueradeRegexp.FindStringSubmatch(rule) + rule = strings.TrimPrefix(rule, match[0]) + + tracer.matches = append(tracer.matches, ruleObj.Rule) + tracer.masquerade = true + case jumpRegexp.MatchString(rule): // `^(jump|goto) (\S+)$` // Jumps to another chain. @@ -472,20 +527,20 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP return false case dnatRegexp.MatchString(rule): - // `meta l4proto (tcp|udp|sctp) dnat to (\S+)` + // `^dnat to (\S+)$` // DNAT to an endpoint IP and terminate processing. match := dnatRegexp.FindStringSubmatch(rule) - destEndpoint := match[2] + destEndpoint := match[1] tracer.matches = append(tracer.matches, ruleObj.Rule) tracer.outputs = append(tracer.outputs, destEndpoint) return true - case endpointVMAPRegexp.MatchString(rule): + case endpointVMapRegexp.MatchString(rule): // `^numgen random mod \d+ vmap \{(.*)\}$` // Selects a random endpoint and jumps to it. For tracePacket's // purposes, we jump to *all* of the endpoints. - match := endpointVMAPRegexp.FindStringSubmatch(rule) + match := endpointVMapRegexp.FindStringSubmatch(rule) elements := match[1] for _, match = range endpointVMapEntryRegexp.FindAllStringSubmatch(elements, -1) { @@ -500,6 +555,22 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP } return true + case endpointDNATRegexp.MatchString(rule): + // `^dnat ip6* addr \. port to numgen random mod \d+ map \{(.*)\}$` + // Selects a random endpoint and DNATs to it. For tracePacket's + // purposes, we DNAT to *all* of the endpoints. + match := endpointDNATRegexp.FindStringSubmatch(rule) + elements := match[1] + + for _, match = range endpointDNATEntryRegexp.FindAllStringSubmatch(elements, -1) { + // `\d+ : (\S+) \. (\d+)` + endpointIP, endpointPort := match[1], match[2] + + tracer.matches = append(tracer.matches, ruleObj.Rule) + tracer.outputs = append(tracer.outputs, net.JoinHostPort(endpointIP, endpointPort)) + } + return true + default: tracer.t.Errorf("unmatched rule: %s", ruleObj.Rule) rule = "" @@ -534,6 +605,7 @@ func tracePacket(t *testing.T, nft *knftables.Fake, sourceIP, protocol, destIP, if err != nil { t.Errorf("failed to parse host port '%s': %s", tracer.outputs[0], err.Error()) } + tracer.dnat = true } // Run filter-forward, return if packet is terminated. @@ -545,8 +617,11 @@ func tracePacket(t *testing.T, nft *knftables.Fake, sourceIP, protocol, destIP, tracer.runChain("filter-input", sourceIP, protocol, destIP, destPort) // Skip filter-output-pre-dnat, filter-output and nat-output as they ought to be fully redundant with the prerouting chains. - // Skip nat-postrouting because it only does masquerading and we handle that separately. - return tracer.matches, strings.Join(tracer.outputs, ", "), tracer.markMasq + + // Run nat-postrouting to handle masquerading + tracer.runChain("nat-postrouting", sourceIP, protocol, destIP, destPort) + + return tracer.matches, strings.Join(tracer.outputs, ", "), tracer.masquerade } type packetFlowTest struct { @@ -587,12 +662,8 @@ func runPacketFlowTests(t *testing.T, line string, nft *knftables.Fake, nodeIPs var testInput = dedent.Dedent(` add table ip testing { comment "rules for kube-proxy" ; } - add chain ip testing mark-for-masquerade - add rule ip testing mark-for-masquerade mark set mark or 0x4000 add chain ip testing masquerading - add rule ip testing masquerading mark and 0x4000 == 0 return - add rule ip testing masquerading mark set mark xor 0x4000 - add rule ip testing masquerading masquerade fully-random + add rule ip testing masquerading mark and 0x4000 != 0 mark set mark xor 0x4000 masquerade fully-random add set ip testing firewall { type ipv4_addr . inet_proto . inet_service ; comment "destinations that are subject to LoadBalancerSourceRanges" ; } add set ip testing firewall-allow { type ipv4_addr . inet_proto . inet_service . ipv4_addr ; flags interval ; comment "destinations+sources that are allowed by LoadBalancerSourceRanges" ; } @@ -604,25 +675,25 @@ var testInput = dedent.Dedent(` # svc1 add chain ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 } add chain ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 - add rule ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade + add rule ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 mark set mark or 0x4000 add rule ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 add element ip testing service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } # svc2 add chain ip testing service-42NFTM6N-ns2/svc2/tcp/p80 - add rule ip testing service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip testing service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip testing service-42NFTM6N-ns2/svc2/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 } add chain ip testing external-42NFTM6N-ns2/svc2/tcp/p80 add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 ip saddr 10.0.0.0/8 goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit pod traffic" - add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local jump mark-for-masquerade comment "masquerade local traffic" + add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local mark set mark or 0x4000 comment "masquerade local traffic" add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit local traffic" add chain ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 - add rule ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade + add rule ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 mark set mark or 0x4000 add rule ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80 add element ip testing service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 } @@ -642,27 +713,23 @@ var testExpected = dedent.Dedent(` add chain ip testing external-42NFTM6N-ns2/svc2/tcp/p80 add chain ip testing firewall-allow-check add chain ip testing firewall-check - add chain ip testing mark-for-masquerade add chain ip testing masquerading add chain ip testing service-42NFTM6N-ns2/svc2/tcp/p80 add chain ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade + add rule ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 mark set mark or 0x4000 add rule ip testing endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 - add rule ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade + add rule ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 mark set mark or 0x4000 add rule ip testing endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80 add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 ip saddr 10.0.0.0/8 goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit pod traffic" - add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local jump mark-for-masquerade comment "masquerade local traffic" + add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local mark set mark or 0x4000 comment "masquerade local traffic" add rule ip testing external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit local traffic" add rule ip testing firewall-allow-check ip daddr . meta l4proto . th dport . ip saddr @firewall-allow return add rule ip testing firewall-allow-check drop add rule ip testing firewall-check ip daddr . meta l4proto . th dport @firewall jump firewall-allow-check - add rule ip testing mark-for-masquerade mark set mark or 0x4000 - add rule ip testing masquerading mark and 0x4000 == 0 return - add rule ip testing masquerading mark set mark xor 0x4000 - add rule ip testing masquerading masquerade fully-random - add rule ip testing service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip testing masquerading mark and 0x4000 != 0 mark set mark xor 0x4000 masquerade fully-random + add rule ip testing service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip testing service-42NFTM6N-ns2/svc2/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 } - add rule ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip testing service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 } add set ip testing firewall { type ipv4_addr . inet_proto . inet_service ; comment "destinations that are subject to LoadBalancerSourceRanges" ; } add set ip testing firewall-allow { type ipv4_addr . inet_proto . inet_service . ipv4_addr ; flags interval ; comment "destinations+sources that are allowed by LoadBalancerSourceRanges" ; } @@ -773,7 +840,6 @@ func TestTracePacketV4(t *testing.T) { rules := dedent.Dedent(` add table ip kube-proxy { comment "rules for kube-proxy" ; } - add chain ip kube-proxy mark-for-masquerade add chain ip kube-proxy masquerading add chain ip kube-proxy services add chain ip kube-proxy firewall-check @@ -804,10 +870,7 @@ func TestTracePacketV4(t *testing.T) { add chain ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 add chain ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 - add rule ip kube-proxy mark-for-masquerade mark set mark or 0x4000 - add rule ip kube-proxy masquerading mark and 0x4000 == 0 return - add rule ip kube-proxy masquerading mark set mark xor 0x4000 - add rule ip kube-proxy masquerading masquerade fully-random + add rule ip kube-proxy masquerading mark and 0x4000 != 0 mark set mark xor 0x4000 masquerade fully-random add rule ip kube-proxy filter-prerouting-pre-dnat ct state new jump firewall-check add rule ip kube-proxy filter-forward ct state new jump endpoints-check add rule ip kube-proxy filter-input ct state new jump endpoints-check @@ -834,21 +897,21 @@ func TestTracePacketV4(t *testing.T) { add rule ip kube-proxy services fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @service-nodeports # svc1 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 } - add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade + add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 mark set mark or 0x4000 add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } # svc2 - add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 } add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 ip saddr 10.0.0.0/8 goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit pod traffic" - add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local jump mark-for-masquerade comment "masquerade local traffic" + add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local mark set mark or 0x4000 comment "masquerade local traffic" add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit local traffic" - add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade + add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 mark set mark or 0x4000 add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80 add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 } @@ -861,24 +924,24 @@ func TestTracePacketV4(t *testing.T) { add element ip kube-proxy no-endpoint-services { 192.168.99.22 . tcp . 80 comment "ns2/svc2:p80" : drop } # svc3 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 } - add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 mark set mark or 0x4000 add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 goto service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade + add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 mark set mark or 0x4000 add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-nodeports { tcp . 3003 : goto external-4AT6LBPK-ns3/svc3/tcp/p80 } # svc4 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 , 1 : goto endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 } - add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 mark set mark or 0x4000 add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 goto service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 ip saddr 10.180.0.5 jump mark-for-masquerade + add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 ip saddr 10.180.0.5 mark set mark or 0x4000 add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 meta l4proto tcp dnat to 10.180.0.5:80 - add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 jump mark-for-masquerade + add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 mark set mark or 0x4000 add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 meta l4proto tcp dnat to 10.180.0.4:80 add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } @@ -886,13 +949,13 @@ func TestTracePacketV4(t *testing.T) { # svc5 add set ip kube-proxy affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { type ipv4_addr ; flags dynamic,timeout ; timeout 10800s ; } - add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip daddr 172.30.0.45 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip daddr 172.30.0.45 tcp dport 80 ip saddr != 10.0.0.0/8 mark set mark or 0x4000 add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 } - add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 mark set mark or 0x4000 add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 goto service-HVFWP5L3-ns5/svc5/tcp/p80 - add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade + add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 mark set mark or 0x4000 add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 update @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { ip saddr } add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 @@ -993,7 +1056,6 @@ func TestTracePacketV6(t *testing.T) { add chain ip6 kube-proxy filter-output { type filter hook output priority 0 ; } add chain ip6 kube-proxy filter-output-post-dnat { type filter hook output priority -90 ; } add chain ip6 kube-proxy firewall-check - add chain ip6 kube-proxy mark-for-masquerade add chain ip6 kube-proxy masquerading add chain ip6 kube-proxy nat-output { type nat hook output priority -100 ; } add chain ip6 kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; } @@ -1012,11 +1074,11 @@ func TestTracePacketV6(t *testing.T) { add map ip6 kube-proxy service-nodeports { type inet_proto . inet_service : verdict ; comment "NodePort traffic" ; } add rule ip6 kube-proxy cluster-ips-check ip6 daddr @cluster-ips reject comment "Reject traffic to invalid ports of ClusterIPs" add rule ip6 kube-proxy cluster-ips-check ip6 daddr { fd00:10:96::/112 } drop comment "Drop traffic to unallocated ClusterIPs" - add rule ip6 kube-proxy endpoint-2CRNCTTE-ns1/svc1/tcp/p80__fd00.10.180..2.1/80 ip6 saddr fd00:10:180::2:1 jump mark-for-masquerade + add rule ip6 kube-proxy endpoint-2CRNCTTE-ns1/svc1/tcp/p80__fd00.10.180..2.1/80 ip6 saddr fd00:10:180::2:1 mark set mark or 0x4000 add rule ip6 kube-proxy endpoint-2CRNCTTE-ns1/svc1/tcp/p80__fd00.10.180..2.1/80 meta l4proto tcp dnat to [fd00:10:180::2:1]:80 - add rule ip6 kube-proxy endpoint-ZVRFLKHO-ns1/svc1/tcp/p80__fd00.10.180..1/80 ip6 saddr fd00:10:180::1 jump mark-for-masquerade + add rule ip6 kube-proxy endpoint-ZVRFLKHO-ns1/svc1/tcp/p80__fd00.10.180..1/80 ip6 saddr fd00:10:180::1 mark set mark or 0x4000 add rule ip6 kube-proxy endpoint-ZVRFLKHO-ns1/svc1/tcp/p80__fd00.10.180..1/80 meta l4proto tcp dnat to [fd00:10:180::1]:80 - add rule ip6 kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 jump mark-for-masquerade + add rule ip6 kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 mark set mark or 0x4000 add rule ip6 kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 goto service-ULMVA6XW-ns1/svc1/tcp/p80 add rule ip6 kube-proxy filter-forward ct state new jump service-endpoints-check add rule ip6 kube-proxy filter-forward ct state new jump cluster-ips-check @@ -1027,16 +1089,13 @@ func TestTracePacketV6(t *testing.T) { add rule ip6 kube-proxy filter-output-post-dnat ct state new jump cluster-ips-check add rule ip6 kube-proxy filter-prerouting-pre-dnat ct state new jump firewall-check add rule ip6 kube-proxy firewall-check ip6 daddr . meta l4proto . th dport vmap @firewall-ips - add rule ip6 kube-proxy mark-for-masquerade mark set mark or 0x4000 - add rule ip6 kube-proxy masquerading mark and 0x4000 == 0 return - add rule ip6 kube-proxy masquerading mark set mark xor 0x4000 - add rule ip6 kube-proxy masquerading masquerade fully-random + add rule ip6 kube-proxy masquerading mark and 0x4000 != 0 mark set mark xor 0x4000 masquerade fully-random add rule ip6 kube-proxy nat-output jump services add rule ip6 kube-proxy nat-postrouting jump masquerading add rule ip6 kube-proxy nat-prerouting jump services add rule ip6 kube-proxy nodeport-endpoints-check ip6 daddr @nodeport-ips meta l4proto . th dport vmap @no-endpoint-nodeports add rule ip6 kube-proxy reject-chain reject - add rule ip6 kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip6 daddr fd00:172:30::41 tcp dport 80 ip6 saddr != fd00:10::/64 jump mark-for-masquerade + add rule ip6 kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip6 daddr fd00:172:30::41 tcp dport 80 ip6 saddr != fd00:10::/64 mark set mark or 0x4000 add rule ip6 kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-ZVRFLKHO-ns1/svc1/tcp/p80__fd00.10.180..1/80 , 1 : goto endpoint-2CRNCTTE-ns1/svc1/tcp/p80__fd00.10.180..2.1/80 } add rule ip6 kube-proxy service-endpoints-check ip6 daddr . meta l4proto . th dport vmap @no-endpoint-services add rule ip6 kube-proxy services ip6 daddr . meta l4proto . th dport vmap @service-ips diff --git a/pkg/proxy/nftables/proxier.go b/pkg/proxy/nftables/proxier.go index 4f88ec0cf266f..8dc23c812c54f 100644 --- a/pkg/proxy/nftables/proxier.go +++ b/pkg/proxy/nftables/proxier.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -25,7 +24,6 @@ import ( "encoding/base32" "fmt" "net" - "os" "os/exec" "strconv" "strings" @@ -39,7 +37,6 @@ import ( discovery "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/events" "k8s.io/klog/v2" @@ -50,7 +47,6 @@ import ( "k8s.io/kubernetes/pkg/proxy/metrics" "k8s.io/kubernetes/pkg/proxy/runner" proxyutil "k8s.io/kubernetes/pkg/proxy/util" - utilkernel "k8s.io/kubernetes/pkg/util/kernel" netutils "k8s.io/utils/net" "k8s.io/utils/ptr" "sigs.k8s.io/knftables" @@ -97,8 +93,8 @@ const ( firewallCheckChain = "firewall-check" // masquerading - markMasqChain = "mark-for-masquerade" - masqueradingChain = "masquerading" + masqueradingChain = "masquerading" + hairpinConnectionsSet = "hairpin-connections" ) // NewDualStackProxier creates a MetaProxier instance, with IPv4 and IPv6 proxies. @@ -159,7 +155,6 @@ type Proxier struct { // updating nftables with some partial data after kube-proxy restart. endpointSlicesSynced bool servicesSynced bool - syncedOnce bool lastFullSync time.Time needFullSync bool initialized int32 @@ -171,6 +166,7 @@ type Proxier struct { nftables knftables.Interface masqueradeAll bool masqueradeMark string + masqueradeRule string conntrack conntrack.Interface localDetector proxyutil.LocalTrafficDetector nodeName string @@ -201,6 +197,7 @@ type Proxier struct { noEndpointServices *nftElementStorage noEndpointNodePorts *nftElementStorage serviceNodePorts *nftElementStorage + hairpinConnections *nftElementStorage } // Proxier implements proxy.Provider @@ -240,7 +237,7 @@ func NewProxier(ctx context.Context, nodePortAddresses := proxyutil.NewNodePortAddresses(ipFamily, nodePortAddressStrings) - serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer) + serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer, ipFamily) proxier := &Proxier{ ipFamily: ipFamily, @@ -253,6 +250,7 @@ func NewProxier(ctx context.Context, nftables: nft, masqueradeAll: masqueradeAll, masqueradeMark: masqueradeMark, + masqueradeRule: fmt.Sprintf("mark set mark or %s", masqueradeMark), conntrack: conntrack.New(), localDetector: localDetector, nodeName: nodeName, @@ -270,6 +268,7 @@ func NewProxier(ctx context.Context, noEndpointServices: newNFTElementStorage("map", noEndpointServicesMap), noEndpointNodePorts: newNFTElementStorage("map", noEndpointNodePortsMap), serviceNodePorts: newNFTElementStorage("map", serviceNodePortsMap), + hairpinConnections: newNFTElementStorage("set", hairpinConnectionsSet), } logger.V(2).Info("NFTables sync params", "minSyncPeriod", minSyncPeriod, "syncPeriod", syncPeriod, "maxSyncPeriod", proxyutil.FullSyncPeriod) @@ -279,52 +278,6 @@ func NewProxier(ctx context.Context, return proxier, nil } -// Create a knftables.Interface and check if we can use the nftables proxy mode on this host. -func getNFTablesInterface(ipFamily v1.IPFamily) (knftables.Interface, error) { - var nftablesFamily knftables.Family - if ipFamily == v1.IPv4Protocol { - nftablesFamily = knftables.IPv4Family - } else { - nftablesFamily = knftables.IPv6Family - } - - // We require (or rather, knftables.New does) that the nft binary be version 1.0.1 - // or later, because versions before that would always attempt to parse the entire - // nft ruleset at startup, even if you were only operating on a single table. - // That's bad, because in some cases, new versions of nft have added new rule - // types in ways that triggered bugs in older versions of nft, causing them to - // crash. Thus, if kube-proxy used nft < 1.0.1, it could potentially get locked - // out of its rules because of something some other component had done in a - // completely different table. - nft, err := knftables.New(nftablesFamily, kubeProxyTable) - if err != nil { - return nil, err - } - - // Likewise, we want to ensure that the host filesystem has nft >= 1.0.1, so that - // it's not possible that *our* rules break *the system's* nft. (In particular, we - // know that if kube-proxy uses nft >= 1.0.3 and the system has nft <= 0.9.8, that - // the system nft will become completely unusable.) Unfortunately, we can't easily - // figure out the version of nft installed on the host filesystem, so instead, we - // check the kernel version, under the assumption that the distro will have an nft - // binary that supports the same features as its kernel does, and so kernel 5.13 - // or later implies nft 1.0.1 or later. https://issues.k8s.io/122743 - // - // However, we allow the user to bypass this check by setting - // `KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK` to anything non-empty. - if os.Getenv("KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK") == "" { - kernelVersion, err := utilkernel.GetVersion() - if err != nil { - return nil, fmt.Errorf("could not check kernel version: %w", err) - } - if kernelVersion.LessThan(version.MustParseGeneric(utilkernel.NFTablesKubeProxyKernelVersion)) { - return nil, fmt.Errorf("kube-proxy in nftables mode requires kernel %s or later", utilkernel.NFTablesKubeProxyKernelVersion) - } - } - - return nft, nil -} - // internal struct for string service information type servicePortInfo struct { *proxy.BaseServicePortInfo @@ -508,35 +461,34 @@ func (proxier *Proxier) setupNFTables(tx *knftables.Transaction) { } // Ensure all of our other "top-level" chains exist - for _, chain := range []string{servicesChain, clusterIPsCheckChain, masqueradingChain, markMasqChain} { + for _, chain := range []string{servicesChain, clusterIPsCheckChain, masqueradingChain} { ensureChain(chain, tx, createdChains, false) } - // Add the rules in the mark-for-masquerade and masquerading chains - tx.Add(&knftables.Rule{ - Chain: markMasqChain, - Rule: knftables.Concat( - "mark", "set", "mark", "or", proxier.masqueradeMark, - ), + // add hairpin-connections set + tx.Add(&knftables.Set{ + Name: hairpinConnectionsSet, + Type: ipvX_addr + " . " + ipvX_addr + " . inet_proto . inet_service", + Comment: ptr.To("service hairpin connections"), }) + // Add the rules in the masquerading chain tx.Add(&knftables.Rule{ Chain: masqueradingChain, Rule: knftables.Concat( - "mark", "and", proxier.masqueradeMark, "==", "0", - "return", + "mark", "and", proxier.masqueradeMark, "!=", "0", + "mark", "set", "mark", "xor", proxier.masqueradeMark, + "masquerade fully-random", ), }) tx.Add(&knftables.Rule{ Chain: masqueradingChain, Rule: knftables.Concat( - "mark", "set", "mark", "xor", proxier.masqueradeMark, + "ct status dnat", + ipX, "saddr", ".", ipX, "daddr", ".", "meta l4proto", ".", "th dport", "@", hairpinConnectionsSet, + "masquerade fully-random", ), }) - tx.Add(&knftables.Rule{ - Chain: masqueradingChain, - Rule: "masquerade fully-random", - }) // add cluster-ips set. tx.Add(&knftables.Set{ @@ -679,6 +631,33 @@ func (proxier *Proxier) setupNFTables(tx *knftables.Transaction) { Type: "inet_proto . inet_service : verdict", Comment: ptr.To("NodePort traffic"), }) + + if proxier.masqueradeAll { + tx.Add(&knftables.Rule{ + Chain: servicesChain, + Rule: knftables.Concat( + ipX, "daddr", "@", clusterIPsSet, + proxier.masqueradeRule, + ), + Comment: ptr.To("masquerade all clusterIP traffic"), + }) + } else if proxier.localDetector.IsImplemented() { + // This masquerades off-cluster traffic to a service VIP. The + // idea is that you can establish a static route for your + // Service range, routing to any node, and that node will + // bridge into the Service for you. Since that might bounce + // off-node, we masquerade here. + tx.Add(&knftables.Rule{ + Chain: servicesChain, + Rule: knftables.Concat( + ipX, "daddr", "@", clusterIPsSet, + proxier.localDetector.IfNotLocalNFT(), + proxier.masqueradeRule, + ), + Comment: ptr.To("masquerade clusterIP traffic from outside cluster"), + }) + } + tx.Add(&knftables.Rule{ Chain: servicesChain, Rule: knftables.Concat( @@ -714,29 +693,7 @@ func (proxier *Proxier) setupNFTables(tx *knftables.Transaction) { proxier.noEndpointServices.readOrReset(tx, proxier.nftables, proxier.logger) proxier.noEndpointNodePorts.readOrReset(tx, proxier.nftables, proxier.logger) proxier.serviceNodePorts.readOrReset(tx, proxier.nftables, proxier.logger) -} - -// CleanupLeftovers removes all nftables rules and chains created by the Proxier -// It returns true if an error was encountered. Errors are logged. -func CleanupLeftovers(ctx context.Context) bool { - logger := klog.FromContext(ctx) - var encounteredError bool - - for _, family := range []knftables.Family{knftables.IPv4Family, knftables.IPv6Family} { - nft, err := knftables.New(family, kubeProxyTable) - if err != nil { - continue - } - tx := nft.NewTransaction() - tx.Delete(&knftables.Table{}) - err = nft.Run(ctx, tx) - if err != nil && !knftables.IsNotFound(err) { - logger.Error(err, "Error cleaning up nftables rules") - encounteredError = true - } - } - - return encounteredError + proxier.hairpinConnections.readOrReset(tx, proxier.nftables, proxier.logger) } // Sync is called to synchronize the proxier state to nftables as soon as possible. @@ -1122,7 +1079,6 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { doFullSync := proxier.needFullSync || (time.Since(proxier.lastFullSync) > proxyutil.FullSyncPeriod) defer func() { - proxier.syncedOnce = true metrics.SyncProxyRulesLatency.WithLabelValues(string(proxier.ipFamily)).Observe(metrics.SinceInSeconds(start)) if !doFullSync { metrics.SyncPartialProxyRulesLatency.WithLabelValues(string(proxier.ipFamily)).Observe(metrics.SinceInSeconds(start)) @@ -1197,24 +1153,20 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { ipvX_addr = "ipv6_addr" } + var err error + var existingChains sets.Set[string] - existingChainsList, err := proxier.nftables.List(context.TODO(), "chain") - if err == nil { - existingChains = sets.New(existingChainsList...) - } else { - proxier.logger.Error(err, "Failed to list existing chains") - } var existingAffinitySets sets.Set[string] - existingSets, err := proxier.nftables.List(context.TODO(), "sets") - if err == nil { + if allObjects, err := proxier.nftables.ListAll(context.TODO()); err == nil { + existingChains = sets.New(allObjects["chain"]...) existingAffinitySets = sets.New[string]() - for _, set := range existingSets { + for _, set := range allObjects["set"] { if isAffinitySetName(set) { existingAffinitySets.Insert(set) } } } else { - proxier.logger.Error(err, "Failed to list existing sets") + proxier.logger.Error(err, "Failed to list existing nftables objects") } // Accumulate service/endpoint chains and affinity sets to keep. @@ -1257,15 +1209,34 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { !serviceUpdateResult.UpdatedServices.Has(svcName.NamespacedName) && !endpointUpdateResult.UpdatedServices.Has(svcName.NamespacedName) - // Note the endpoint chains that will be used + // We only use separate endpoint chains when adding affinity rules + serviceUsesAffinity := svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP + + // Note the endpoints that will be used for _, ep := range allLocallyReachableEndpoints { - if epInfo, ok := ep.(*endpointInfo); ok { - ensureChain(epInfo.chainName, tx, activeChains, skipServiceUpdate || - proxier.epChainSkipUpdate(existingChains, existingAffinitySets, svcInfo, epInfo)) - // Note the affinity sets that will be used - if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { - activeAffinitySets.Insert(epInfo.affinitySetName) - } + epInfo, ok := ep.(*endpointInfo) + if !ok { + continue + } + + // If using affinity, note the endpoint chain and affinity set + // names, and ensure that the chain exists. + if serviceUsesAffinity { + ensureChain(epInfo.chainName, tx, activeChains, skipServiceUpdate) + activeAffinitySets.Insert(epInfo.affinitySetName) + } + + // Add local endpoints to the hairpin set. + if epInfo.IsLocal() { + proxier.hairpinConnections.ensureElem(tx, &knftables.Element{ + Set: hairpinConnectionsSet, + Key: []string{ + epInfo.IP(), + epInfo.IP(), + protocol, + strconv.Itoa(epInfo.Port()), + }, + }) } } @@ -1518,33 +1489,6 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { continue } - // Set up internal traffic handling. - if hasInternalEndpoints { - if proxier.masqueradeAll { - tx.Add(&knftables.Rule{ - Chain: internalTrafficChain, - Rule: knftables.Concat( - ipX, "daddr", svcInfo.ClusterIP(), - "jump", markMasqChain, - ), - }) - } else if proxier.localDetector.IsImplemented() { - // This masquerades off-cluster traffic to a service VIP. The - // idea is that you can establish a static route for your - // Service range, routing to any node, and that node will - // bridge into the Service for you. Since that might bounce - // off-node, we masquerade here. - tx.Add(&knftables.Rule{ - Chain: internalTrafficChain, - Rule: knftables.Concat( - ipX, "daddr", svcInfo.ClusterIP(), - proxier.localDetector.IfNotLocalNFT(), - "jump", markMasqChain, - ), - }) - } - } - // Set up external traffic handling (if any "external" destinations are // enabled). All captured traffic for all external destinations should // jump to externalTrafficChain, which will handle some special cases and @@ -1556,8 +1500,9 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { tx.Add(&knftables.Rule{ Chain: externalTrafficChain, Rule: knftables.Concat( - "jump", markMasqChain, + proxier.masqueradeRule, ), + Comment: ptr.To("masquerade"), }) } else { // If we are only using same-node endpoints, we can retain the @@ -1585,7 +1530,7 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { Chain: externalTrafficChain, Rule: knftables.Concat( "fib", "saddr", "type", "local", - "jump", markMasqChain, + proxier.masqueradeRule, ), Comment: ptr.To("masquerade local traffic"), }) @@ -1681,43 +1626,32 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { } } - // If Cluster policy is in use, create the chain and create rules jumping - // from clusterPolicyChain to the clusterEndpoints - if usesClusterPolicyChain { - proxier.writeServiceToEndpointRules(tx, svcInfo, clusterPolicyChain, clusterEndpoints) - } - - // If Local policy is in use, create rules jumping from localPolicyChain - // to the localEndpoints - if usesLocalPolicyChain { - proxier.writeServiceToEndpointRules(tx, svcInfo, localPolicyChain, localEndpoints) - } - - // Generate the per-endpoint chains - for _, ep := range allLocallyReachableEndpoints { - epInfo, ok := ep.(*endpointInfo) - if !ok { - proxier.logger.Error(nil, "Failed to cast endpointInfo", "endpointInfo", ep) - continue + // Write the endpoint rules and/or chains + if !serviceUsesAffinity { + if usesClusterPolicyChain { + proxier.writeServiceToEndpointDNATs(tx, svcInfo, clusterPolicyChain, clusterEndpoints) } - - if proxier.epChainSkipUpdate(existingChains, existingAffinitySets, svcInfo, epInfo) { - // If the EP chain is already updated, we can skip it. - continue + if usesLocalPolicyChain { + proxier.writeServiceToEndpointDNATs(tx, svcInfo, localPolicyChain, localEndpoints) + } + } else { + if usesClusterPolicyChain { + proxier.writeServiceToEndpointJumps(tx, svcInfo, clusterPolicyChain, clusterEndpoints) + } + if usesLocalPolicyChain { + proxier.writeServiceToEndpointJumps(tx, svcInfo, localPolicyChain, localEndpoints) } - endpointChain := epInfo.chainName - // Handle traffic that loops back to the originator with SNAT. - tx.Add(&knftables.Rule{ - Chain: endpointChain, - Rule: knftables.Concat( - ipX, "saddr", epInfo.IP(), - "jump", markMasqChain, - ), - }) + // And generate the per-endpoint chains and affinity sets + for _, ep := range allLocallyReachableEndpoints { + epInfo, ok := ep.(*endpointInfo) + if !ok { + continue + } + + endpointChain := epInfo.chainName - // Handle session affinity - if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { + // Handle session affinity tx.Add(&knftables.Rule{ Chain: endpointChain, Rule: knftables.Concat( @@ -1725,16 +1659,16 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { "{", ipX, "saddr", "}", ), }) - } - // DNAT to final destination. - tx.Add(&knftables.Rule{ - Chain: endpointChain, - Rule: knftables.Concat( - "meta l4proto", protocol, - "dnat to", epInfo.String(), - ), - }) + // DNAT to final destination. + tx.Add(&knftables.Rule{ + Chain: endpointChain, + Rule: knftables.Concat( + "meta l4proto", protocol, + "dnat to", epInfo.String(), + ), + }) + } } } @@ -1772,6 +1706,7 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { proxier.noEndpointServices.cleanupLeftoverKeys(tx) proxier.noEndpointNodePorts.cleanupLeftoverKeys(tx) proxier.serviceNodePorts.cleanupLeftoverKeys(tx) + proxier.hairpinConnections.cleanupLeftoverKeys(tx) // Sync rules. proxier.logger.V(2).Info("Reloading service nftables data", @@ -1830,62 +1765,59 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { return } -// epChainSkipUpdate returns true if the EP chain doesn't need to be updated. -func (proxier *Proxier) epChainSkipUpdate(existingChains, existingAffinitySets sets.Set[string], svcInfo *servicePortInfo, epInfo *endpointInfo) bool { - if proxier.syncedOnce { - // We only skip updating EP chains during the first sync to speed up kube-proxy restart, otherwise return false. - return false - } - if existingChains == nil || existingAffinitySets == nil { - // listing existing objects failed, can't skip updating - return false +func (proxier *Proxier) writeServiceToEndpointDNATs(tx *knftables.Transaction, svcInfo *servicePortInfo, svcChain string, endpoints []proxy.Endpoint) { + ipX := "ip" + if proxier.ipFamily == v1.IPv6Protocol { + ipX = "ip6" } - // EP chain can have up to 3 rules: - // - loopback masquerade rule - // - includes the endpoint IP - // - affinity rule when session affinity is set to ClusterIP - // - includes the affinity set name - // - DNAT rule - // - includes the endpoint IP + port - // EP chain name includes the endpoint IP + port => loopback and DNAT rules are pre-defined by the chain name. - // When session affinity is set to ClusterIP, the affinity set is created for local endpoints. - // Therefore, we can check that sessions affinity hasn't changed by checking if the affinity set exists. - wantAffinitySet := svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP - return existingChains.Has(epInfo.chainName) && wantAffinitySet == existingAffinitySets.Has(epInfo.affinitySetName) -} -func (proxier *Proxier) writeServiceToEndpointRules(tx *knftables.Transaction, svcInfo *servicePortInfo, svcChain string, endpoints []proxy.Endpoint) { - // First write session affinity rules, if applicable. - if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { - ipX := "ip" - if proxier.ipFamily == v1.IPv6Protocol { - ipX = "ip6" + var elements []string + for i, ep := range endpoints { + epInfo, ok := ep.(*endpointInfo) + if !ok { + continue } - for _, ep := range endpoints { - epInfo, ok := ep.(*endpointInfo) - if !ok { - continue - } - - tx.Add(&knftables.Rule{ - Chain: svcChain, - Rule: knftables.Concat( - ipX, "saddr", "@", epInfo.affinitySetName, - "goto", epInfo.chainName, - ), - }) + elements = append(elements, + strconv.Itoa(i), ":", epInfo.IP(), ".", strconv.Itoa(epInfo.Port()), + ) + if i != len(endpoints)-1 { + elements = append(elements, ",") } } + tx.Add(&knftables.Rule{ + Chain: svcChain, + Rule: knftables.Concat( + "meta l4proto", strings.ToLower(string(svcInfo.Protocol())), + "dnat", ipX, "addr . port to", + "numgen random mod", len(endpoints), "map", + "{", elements, "}", + ), + }) +} + +func (proxier *Proxier) writeServiceToEndpointJumps(tx *knftables.Transaction, svcInfo *servicePortInfo, svcChain string, endpoints []proxy.Endpoint) { + ipX := "ip" + if proxier.ipFamily == v1.IPv6Protocol { + ipX = "ip6" + } - // Now write loadbalancing rule var elements []string + // Write the affinity rules, construct the vmap elements for i, ep := range endpoints { epInfo, ok := ep.(*endpointInfo) if !ok { continue } + tx.Add(&knftables.Rule{ + Chain: svcChain, + Rule: knftables.Concat( + ipX, "saddr", "@", epInfo.affinitySetName, + "goto", epInfo.chainName, + ), + }) + elements = append(elements, strconv.Itoa(i), ":", "goto", epInfo.chainName, ) @@ -1893,6 +1825,7 @@ func (proxier *Proxier) writeServiceToEndpointRules(tx *knftables.Transaction, s elements = append(elements, ",") } } + // Now write the vmap, for the case where no affinity rule matched tx.Add(&knftables.Rule{ Chain: svcChain, Rule: knftables.Concat( diff --git a/pkg/proxy/nftables/proxier_test.go b/pkg/proxy/nftables/proxier_test.go index be9ca16a9c9ff..1a591114026e1 100644 --- a/pkg/proxy/nftables/proxier_test.go +++ b/pkg/proxy/nftables/proxier_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -120,6 +119,7 @@ func NewFakeProxier(ipFamily v1.IPFamily) (*knftables.Fake, *Proxier) { needFullSync: true, nftables: nft, masqueradeMark: "0x4000", + masqueradeRule: "mark set mark or 0x4000", conntrack: conntrack.NewFake(), localDetector: detectLocal, nodeName: testNodeName, @@ -136,6 +136,7 @@ func NewFakeProxier(ipFamily v1.IPFamily) (*knftables.Fake, *Proxier) { noEndpointServices: newNFTElementStorage("map", noEndpointServicesMap), noEndpointNodePorts: newNFTElementStorage("map", noEndpointNodePortsMap), serviceNodePorts: newNFTElementStorage("map", serviceNodePortsMap), + hairpinConnections: newNFTElementStorage("set", hairpinConnectionsSet), } p.setInitialized(true) p.syncRunner = runner.NewBoundedFrequencyRunner("test-sync-runner", p.syncProxyRules, 0, 30*time.Second, time.Minute) @@ -148,6 +149,7 @@ var baseRules = dedent.Dedent(` add set ip kube-proxy cluster-ips { type ipv4_addr ; comment "Active ClusterIPs" ; } add set ip kube-proxy nodeport-ips { type ipv4_addr ; comment "IPs that accept NodePort traffic" ; } + add set ip kube-proxy hairpin-connections { type ipv4_addr . ipv4_addr . inet_proto . inet_service ; comment "service hairpin connections" ; } add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; } add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; } @@ -162,7 +164,6 @@ var baseRules = dedent.Dedent(` add chain ip kube-proxy filter-input { type filter hook input priority 0 ; } add chain ip kube-proxy filter-output { type filter hook output priority 0 ; } add chain ip kube-proxy firewall-check - add chain ip kube-proxy mark-for-masquerade add chain ip kube-proxy masquerading add chain ip kube-proxy nat-output { type nat hook output priority -100 ; } add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; } @@ -183,15 +184,14 @@ var baseRules = dedent.Dedent(` add rule ip kube-proxy filter-output-pre-dnat ct state new jump firewall-check add rule ip kube-proxy filter-output ct state new jump cluster-ips-check add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips - add rule ip kube-proxy mark-for-masquerade mark set mark or 0x4000 - add rule ip kube-proxy masquerading mark and 0x4000 == 0 return - add rule ip kube-proxy masquerading mark set mark xor 0x4000 - add rule ip kube-proxy masquerading masquerade fully-random + add rule ip kube-proxy masquerading mark and 0x4000 != 0 mark set mark xor 0x4000 masquerade fully-random + add rule ip kube-proxy masquerading ct status dnat ip saddr . ip daddr . meta l4proto . th dport @hairpin-connections masquerade fully-random add rule ip kube-proxy nat-output jump services add rule ip kube-proxy nat-postrouting jump masquerading add rule ip kube-proxy nat-prerouting jump services add rule ip kube-proxy nodeport-endpoints-check ip daddr @nodeport-ips meta l4proto . th dport vmap @no-endpoint-nodeports add rule ip kube-proxy reject-chain reject + add rule ip kube-proxy services ip daddr @cluster-ips ip saddr != 10.0.0.0/8 mark set mark or 0x4000 comment "masquerade clusterIP traffic from outside cluster" add rule ip kube-proxy services ip daddr . meta l4proto . th dport vmap @service-ips add rule ip kube-proxy services ip daddr @nodeport-ips meta l4proto . th dport vmap @service-nodeports @@ -300,6 +300,7 @@ func TestOverallNFTablesRules(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.0.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -307,12 +308,14 @@ func TestOverallNFTablesRules(t *testing.T) { Protocol: ptr.To(v1.ProtocolTCP), }} }), - // create Local LoadBalancer endpoints. Note that since we aren't setting - // its NodeName, this endpoint will be considered non-local and ignored. + // create Local LoadBalancer endpoints. Note that we set NodeName to the + // "wrong" value, so this endpoint will be considered non-local and + // ignored. makeTestEndpointSlice("ns2", "svc2", 1, func(eps *discovery.EndpointSlice) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.0.2"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -325,6 +328,7 @@ func TestOverallNFTablesRules(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.0.3"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -337,6 +341,7 @@ func TestOverallNFTablesRules(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.0.4"}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{"10.180.0.5"}, NodeName: ptr.To(testNodeName), @@ -352,6 +357,7 @@ func TestOverallNFTablesRules(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.0.3"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -366,27 +372,19 @@ func TestOverallNFTablesRules(t *testing.T) { expected := baseRules + dedent.Dedent(` # svc1 add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 } - - add chain ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 - add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.180.0.1 . 80 } add element ip kube-proxy cluster-ips { 172.30.0.41 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.180.0.1 . 10.180.0.1 . tcp . 80 } # svc2 add chain ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 - add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 } + add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.180.0.2 . 80 } add chain ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 ip saddr 10.0.0.0/8 goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit pod traffic" - add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local jump mark-for-masquerade comment "masquerade local traffic" + add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local mark set mark or 0x4000 comment "masquerade local traffic" add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit local traffic" - add chain ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 - add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade - add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80 add element ip kube-proxy cluster-ips { 172.30.0.42 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 } @@ -400,49 +398,38 @@ func TestOverallNFTablesRules(t *testing.T) { # svc3 add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 } + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.180.0.3 . 80 } add chain ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 mark set mark or 0x4000 comment "masquerade" add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 goto service-4AT6LBPK-ns3/svc3/tcp/p80 - add chain ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 - add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade - add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 add element ip kube-proxy cluster-ips { 172.30.0.43 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-nodeports { tcp . 3003 : goto external-4AT6LBPK-ns3/svc3/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.180.0.3 . 10.180.0.3 . tcp . 80 } # svc4 add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 , 1 : goto endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 } + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 2 map { 0 : 10.180.0.4 . 80 , 1 : 10.180.0.5 . 80 } add chain ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 mark set mark or 0x4000 comment "masquerade" add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 goto service-LAUZTJTB-ns4/svc4/tcp/p80 - add chain ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 - add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 ip saddr 10.180.0.5 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 meta l4proto tcp dnat to 10.180.0.5:80 - add chain ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 - add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 jump mark-for-masquerade - add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 meta l4proto tcp dnat to 10.180.0.4:80 add element ip kube-proxy cluster-ips { 172.30.0.44 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 192.168.99.33 . tcp . 80 : goto external-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.180.0.5 . 10.180.0.5 . tcp . 80 } # svc5 add set ip kube-proxy affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { type ipv4_addr ; flags dynamic,timeout ; timeout 10800s ; } add chain ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 - add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip daddr 172.30.0.45 ip saddr != 10.0.0.0/8 jump mark-for-masquerade add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 } add chain ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 - add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 mark set mark or 0x4000 comment "masquerade" add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 goto service-HVFWP5L3-ns5/svc5/tcp/p80 add chain ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 - add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 update @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { ip saddr } add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 @@ -454,6 +441,7 @@ func TestOverallNFTablesRules(t *testing.T) { add element ip kube-proxy service-ips { 5.6.7.8 . tcp . 80 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 } add element ip kube-proxy service-nodeports { tcp . 3002 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 } add element ip kube-proxy firewall-ips { 5.6.7.8 . tcp . 80 : goto firewall-HVFWP5L3-ns5/svc5/tcp/p80 } + # 10.180.0.3:80 is already in hairpin-connections, so we don't want another "add element" line... # svc6 add element ip kube-proxy cluster-ips { 172.30.0.46 } @@ -774,6 +762,7 @@ func TestLoadBalancer(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{epIP}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To(svcPortName.Port), @@ -984,7 +973,7 @@ func TestNodePorts(t *testing.T) { } eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{epIP1}, - NodeName: nil, + NodeName: ptr.To("other-node"), }, { Addresses: []string{epIP2}, NodeName: ptr.To(testNodeName), @@ -1110,6 +1099,7 @@ func TestExternalTrafficPolicyLocal(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{epIP1}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{epIP2}, NodeName: ptr.To(testNodeName), @@ -1225,7 +1215,7 @@ func TestExternalTrafficPolicyCluster(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{epIP1}, - NodeName: nil, + NodeName: ptr.To("other-node"), }, { Addresses: []string{epIP2}, NodeName: ptr.To(testNodeName), @@ -1607,6 +1597,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.1"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p11"), @@ -1618,6 +1609,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.2"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p12"), @@ -1649,6 +1641,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.1"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p11-2"), @@ -1663,6 +1656,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.1"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p11"), @@ -1677,6 +1671,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.1"}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{"10.1.1.2"}, NodeName: ptr.To(testNodeName), @@ -1732,6 +1727,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.3"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p13"), @@ -1747,6 +1743,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.1"}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{"10.1.1.2"}, NodeName: ptr.To(testNodeName), @@ -1765,6 +1762,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.3"}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{"10.1.1.4"}, NodeName: ptr.To(testNodeName), @@ -1783,6 +1781,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.2.2.1"}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{"10.2.2.2"}, NodeName: ptr.To(testNodeName), @@ -1860,8 +1859,10 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.1"}, + NodeName: ptr.To("other-node"), }, { Addresses: []string{"10.1.1.11"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p11"), @@ -1873,6 +1874,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.1.1.2"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p12"), @@ -1888,6 +1890,7 @@ func TestUpdateEndpointsMap(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.3.3.3"}, + NodeName: ptr.To("other-node"), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p33"), @@ -3812,6 +3815,20 @@ func TestInternalExternalMasquerade(t *testing.T) { } } +// assertNumOperations asserts that the last transaction on nft had a number of operations +// equal to the sum of the values in ops[]. (ops is multiple values just for ease of +// explaining the math; see the callers below.) +func assertNumOperations(t *testing.T, nft *knftables.Fake, ops ...int) { + t.Helper() + expectedOps := 0 + for _, n := range ops { + expectedOps += n + } + if nft.LastTransaction.NumOperations() != expectedOps { + t.Errorf("Expected transaction with %d operations, got %d", expectedOps, nft.LastTransaction.NumOperations()) + } +} + // Test calling syncProxyRules() multiple times with various changes func TestSyncProxyRulesRepeated(t *testing.T) { nft, fp := NewFakeProxier(v1.IPv4Protocol) @@ -3854,6 +3871,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.0.1.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -3865,6 +3883,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.0.2.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p8080"), @@ -3881,20 +3900,14 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy cluster-ips { 172.30.0.42 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.2.1 . 10.0.2.1 . tcp . 8080 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 ip daddr 172.30.0.42 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 numgen random mod 1 vmap { 0 : goto endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 } - add chain ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 ip saddr 10.0.2.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 meta l4proto tcp dnat to 10.0.2.1:8080 + add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.2.1 . 8080 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) @@ -3917,6 +3930,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.0.3.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -3934,35 +3948,24 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.2.1 . 10.0.2.1 . tcp . 8080 } + add element ip kube-proxy hairpin-connections { 10.0.3.1 . 10.0.3.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 ip daddr 172.30.0.42 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 numgen random mod 1 vmap { 0 : goto endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 } - add chain ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 ip saddr 10.0.2.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 meta l4proto tcp dnat to 10.0.2.1:8080 + add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.2.1 . 8080 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 } - add chain ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 ip saddr 10.0.3.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 meta l4proto tcp dnat to 10.0.3.1:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.3.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // add 1 element to cluster-ips and service-ips = 2 operations - // add+flush 2 chains for service and endpoint, add 2 rules in each = 8 operations - // 10 operations total. - if nft.LastTransaction.NumOperations() != 10 { - t.Errorf("Expected 10 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 3, // add 1 element each to cluster-ips, service-ips, and hairpin-connections + 3, // add+flush service chain, add 1 rule + ) // Delete a service; its chains will be flushed, but not immediately deleted. fp.OnServiceDelete(svc2) @@ -3972,31 +3975,22 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy cluster-ips { 172.30.0.43 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.1 . 10.0.3.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 - add chain ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 } - add chain ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 ip saddr 10.0.3.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 meta l4proto tcp dnat to 10.0.3.1:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.3.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // delete 1 element from cluster-ips and service-ips = 2 operations - // flush 2 chains for service and endpoint = 2 operations - // 4 operations total. - if nft.LastTransaction.NumOperations() != 4 { - t.Errorf("Expected 4 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 3, // delete 1 element each from cluster-ips, service-ips, hairpin-connections + 1, // flush service chain + ) // Fake the passage of time and confirm that the stale chains get deleted. ageStaleChains() @@ -4006,26 +4000,18 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy cluster-ips { 172.30.0.43 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.1 . 10.0.3.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 } - add chain ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 ip saddr 10.0.3.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 meta l4proto tcp dnat to 10.0.3.1:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.3.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // delete stale chains happens in a separate transaction, nothing else changed => last transaction will have 0 operations. - if nft.LastTransaction.NumOperations() != 0 { - t.Errorf("Expected 0 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + // delete stale chains happens in a separate transaction, nothing else changed + assertNumOperations(t, nft, 0) // Add a service, sync, then add its endpoints. makeServiceMap(fp, @@ -4046,34 +4032,28 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy cluster-ips { 172.30.0.44 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.1 . 10.0.3.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 } - add chain ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 ip saddr 10.0.3.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 meta l4proto tcp dnat to 10.0.3.1:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.3.1 . 80 } add element ip kube-proxy no-endpoint-services { 172.30.0.44 . tcp . 80 comment "ns4/svc4:p80" : goto reject-chain } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // add 1 element to cluster-ips and no-endpoint-services = 2 operations - if nft.LastTransaction.NumOperations() != 2 { - t.Errorf("Expected 2 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 2, // add 1 element each to cluster-ips and no-endpoint-services + ) populateEndpointSlices(fp, makeTestEndpointSlice("ns4", "svc4", 1, func(eps *discovery.EndpointSlice) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.0.4.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -4090,34 +4070,25 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.1 . 10.0.3.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.4.1 . 10.0.4.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 } - add chain ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 ip saddr 10.0.3.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 meta l4proto tcp dnat to 10.0.3.1:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.3.1 . 80 } add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 } - add chain ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 ip saddr 10.0.4.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 meta l4proto tcp dnat to 10.0.4.1:80 + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.4.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // add 1 element to service-ips, remove 1 element from no-endpoint-services = 2 operations - // add+flush 2 chains for service and endpoint, add 2 rules in each = 8 operations - if nft.LastTransaction.NumOperations() != 10 { - t.Errorf("Expected 10 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 2, // add 1 element to service-ips, remove 1 element from no-endpoint-services + 1, // add 1 element to hairpin-connections + 3, // add+flush service chain, add 1 rule + ) // Change an endpoint of an existing service. eps3update := eps3.DeepCopy() @@ -4133,42 +4104,34 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.2 . 10.0.3.2 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.4.1 . 10.0.4.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 } - add chain ip kube-proxy endpoint-2OCDJSZQ-ns3/svc3/tcp/p80__10.0.3.1/80 - add chain ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 - add rule ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 ip saddr 10.0.3.2 jump mark-for-masquerade - add rule ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 meta l4proto tcp dnat to 10.0.3.2:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.3.2 . 80 } add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 } - add chain ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 ip saddr 10.0.4.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 meta l4proto tcp dnat to 10.0.4.1:80 + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.4.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // add+flush 2 chains for service and endpoint, add 2 rules in each = 8 operations - // flush old endpoint chain = 1 operation - if nft.LastTransaction.NumOperations() != 9 { - t.Errorf("Expected 9 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 2, // remove 1 old element from hairpin-connections, add 1 new one + 3, // add+flush service chain, add 1 rule + ) // (Ensure the old svc3 chain gets deleted in the next sync.) ageStaleChains() // Add an endpoint to a service. eps3update2 := eps3update.DeepCopy() - eps3update2.Endpoints = append(eps3update2.Endpoints, discovery.Endpoint{Addresses: []string{"10.0.3.3"}}) + eps3update2.Endpoints = append(eps3update2.Endpoints, discovery.Endpoint{ + Addresses: []string{"10.0.3.3"}, + NodeName: ptr.To(testNodeName), + }) fp.OnEndpointSliceUpdate(eps3update, eps3update2) fp.syncProxyRules() @@ -4179,36 +4142,26 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.2 . 10.0.3.2 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.3 . 10.0.3.3 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.4.1 . 10.0.4.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 , 1 : goto endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 } - add chain ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 - add rule ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 ip saddr 10.0.3.2 jump mark-for-masquerade - add rule ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 meta l4proto tcp dnat to 10.0.3.2:80 - add chain ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 - add rule ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 ip saddr 10.0.3.3 jump mark-for-masquerade - add rule ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 meta l4proto tcp dnat to 10.0.3.3:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 2 map { 0 : 10.0.3.2 . 80 , 1 : 10.0.3.3 . 80 } add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 } - add chain ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 ip saddr 10.0.4.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 meta l4proto tcp dnat to 10.0.4.1:80 + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.4.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // add+flush 3 chains for 1 service and 2 endpoints, add 2 rules in each = 12 operations - if nft.LastTransaction.NumOperations() != 12 { - t.Errorf("Expected 12 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + // (The first endpoint chain is unchanged, but the code recreates it anyway.) + assertNumOperations(t, nft, + 1, // add 1 element to hairpin-connections + 3, // add+flush service chain, add 1 rule + ) // Empty a service's endpoints; its chains will be flushed, but not immediately deleted. eps3update3 := eps3update2.DeepCopy() @@ -4222,33 +4175,25 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy no-endpoint-services { 172.30.0.43 . tcp . 80 comment "ns3/svc3:p80" : goto reject-chain } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.4.1 . 10.0.4.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add chain ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 - add chain ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 } - add chain ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 ip saddr 10.0.4.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 meta l4proto tcp dnat to 10.0.4.1:80 + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.4.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // remove 1 element from service-ips, add 1 element to no-endpoint-services = 2 operations - // flush 3 chains = 3 operations - if nft.LastTransaction.NumOperations() != 5 { - t.Errorf("Expected 5 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 2, // remove 1 element from service-ips, add 1 element to no-endpoint-services + 2, // remove 2 elements from hairpin-connections + 1, // flush service chain + ) - expectedStaleChains := sets.NewString("service-4AT6LBPK-ns3/svc3/tcp/p80", "endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80", "endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80") + expectedStaleChains := sets.NewString("service-4AT6LBPK-ns3/svc3/tcp/p80") gotStaleChains := sets.StringKeySet(fp.staleChains) if !expectedStaleChains.Equal(gotStaleChains) { t.Errorf("expected stale chains %v, got %v", expectedStaleChains, gotStaleChains) @@ -4263,37 +4208,26 @@ func TestSyncProxyRulesRepeated(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.2 . 10.0.3.2 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.3.3 . 10.0.3.3 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.4.1 . 10.0.4.1 . tcp . 80 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 , 1 : goto endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 } - add chain ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 - add rule ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 ip saddr 10.0.3.2 jump mark-for-masquerade - add rule ip kube-proxy endpoint-SWWHDC7X-ns3/svc3/tcp/p80__10.0.3.2/80 meta l4proto tcp dnat to 10.0.3.2:80 - add chain ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 - add rule ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 ip saddr 10.0.3.3 jump mark-for-masquerade - add rule ip kube-proxy endpoint-TQ2QKHCZ-ns3/svc3/tcp/p80__10.0.3.3/80 meta l4proto tcp dnat to 10.0.3.3:80 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 2 map { 0 : 10.0.3.2 . 80 , 1 : 10.0.3.3 . 80 } add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 } - add chain ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 ip saddr 10.0.4.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-WAHRBT2B-ns4/svc4/tcp/p80__10.0.4.1/80 meta l4proto tcp dnat to 10.0.4.1:80 + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.4.1 . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // remove 1 element from no-endpoint-services, add 1 element to service-ips = 2 operations - // add+flush 3 chains for 1 service and 2 endpoints, add 2 rules in each = 12 operations - if nft.LastTransaction.NumOperations() != 14 { - t.Errorf("Expected 14 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + 2, // remove 1 element from no-endpoint-services, add 1 element to service-ips + 2, // add 2 elements to hairpin-connections + 3, // add+flush service chain, add 1 rule + ) if len(fp.staleChains) != 0 { t.Errorf("unexpected stale chains: %v", fp.staleChains) @@ -4313,9 +4247,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) { // Sync with no new changes, so same expected rules as last time fp.syncProxyRules() assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - if nft.LastTransaction.NumOperations() != 0 { - t.Errorf("Expected 0 trasaction operations, got %d", nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, 0) } func TestSyncProxyRulesStartup(t *testing.T) { @@ -4330,24 +4262,18 @@ func TestSyncProxyRulesStartup(t *testing.T) { // put a part of desired state to nftables err := nft.ParseDump(baseRules + dedent.Dedent(` add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 add chain ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 - add chain ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 } - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.1.1 . 80 } - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 ip daddr 172.30.0.42 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 numgen random mod 1 vmap { 0 : goto endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 } - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 ip saddr 10.0.2.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 meta l4proto tcp dnat to 10.0.2.1:8080 + add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.2.1 . 8080 } add element ip kube-proxy cluster-ips { 172.30.0.41 } add element ip kube-proxy cluster-ips { 172.30.0.42 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.2.1 . 10.0.2.1 . tcp . 8080 } `)) if err != nil { @@ -4391,8 +4317,14 @@ func TestSyncProxyRulesStartup(t *testing.T) { makeTestEndpointSlice("ns1", "svc1", 1, func(eps *discovery.EndpointSlice) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{ - {Addresses: []string{"10.0.1.1"}}, - {Addresses: []string{"10.0.1.2"}}, + { + Addresses: []string{"10.0.1.1"}, + NodeName: ptr.To(testNodeName), + }, + { + Addresses: []string{"10.0.1.2"}, + NodeName: ptr.To(testNodeName), + }, } eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -4404,6 +4336,7 @@ func TestSyncProxyRulesStartup(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.0.2.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p8080"), @@ -4422,36 +4355,23 @@ func TestSyncProxyRulesStartup(t *testing.T) { add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 } add element ip kube-proxy no-endpoint-services { 172.30.0.43 . tcp . 80 comment "ns3/svc3:p80" : goto reject-chain } + add element ip kube-proxy hairpin-connections { 10.0.1.1 . 10.0.1.1 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.1.2 . 10.0.1.2 . tcp . 80 } + add element ip kube-proxy hairpin-connections { 10.0.2.1 . 10.0.2.1 . tcp . 8080 } add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 , 1 : goto endpoint-ZCZBVNAZ-ns1/svc1/tcp/p80__10.0.1.2/80 } - add chain ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 ip saddr 10.0.1.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5TPGNJF2-ns1/svc1/tcp/p80__10.0.1.1/80 meta l4proto tcp dnat to 10.0.1.1:80 - add chain ip kube-proxy endpoint-ZCZBVNAZ-ns1/svc1/tcp/p80__10.0.1.2/80 - add rule ip kube-proxy endpoint-ZCZBVNAZ-ns1/svc1/tcp/p80__10.0.1.2/80 ip saddr 10.0.1.2 jump mark-for-masquerade - add rule ip kube-proxy endpoint-ZCZBVNAZ-ns1/svc1/tcp/p80__10.0.1.2/80 meta l4proto tcp dnat to 10.0.1.2:80 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 2 map { 0 : 10.0.1.1 . 80 , 1 : 10.0.1.2 . 80 } add chain ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 ip daddr 172.30.0.42 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 numgen random mod 1 vmap { 0 : goto endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 } - add chain ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 ip saddr 10.0.2.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-7RVP4LUQ-ns2/svc2/tcp/p8080__10.0.2.1/8080 meta l4proto tcp dnat to 10.0.2.1:8080 + add rule ip kube-proxy service-MHHHYRWA-ns2/svc2/tcp/p8080 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.0.2.1 . 8080 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) - // initial transaction consists of: - // 1. nft setup, total ops = setupOps - // 2. services setup (should skip adding existing set/map elements and endpoint chains+rules) - // - add svc3 IP to the cluster-ips, and to the no-endpoint-services set = 2 ops - // - add+flush 2 service chains + 2 rules each = 8 ops - // - add+flush svc1 endpoint chain + 2 rules = 4 ops - // total: 14 ops - if nft.LastTransaction.NumOperations() != setupOps+14 { - fmt.Println(nft.LastTransaction) - t.Errorf("Expected %v trasaction operations, got %d", setupOps+14, nft.LastTransaction.NumOperations()) - } + assertNumOperations(t, nft, + setupOps, // nft setup + 1, // add new svc1 endpoint to hairpin-connections + 2, // add svc3 IP to the cluster-ips, and to the no-endpoint-services set + 6, // add+flush 2 service chains + 1 rule each + ) } func TestNoEndpointsMetric(t *testing.T) { @@ -4674,6 +4594,7 @@ func TestLoadBalancerIngressRouteTypeProxy(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.0.1"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -4893,6 +4814,7 @@ func TestBadIPs(t *testing.T) { eps.AddressType = discovery.AddressTypeIPv4 eps.Endpoints = []discovery.Endpoint{{ Addresses: []string{"10.180.00.001"}, + NodeName: ptr.To(testNodeName), }} eps.Ports = []discovery.EndpointPort{{ Name: ptr.To("p80"), @@ -4907,16 +4829,12 @@ func TestBadIPs(t *testing.T) { expected := baseRules + dedent.Dedent(` # svc1 add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 ip saddr != 10.0.0.0/8 jump mark-for-masquerade - add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 } + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 meta l4proto tcp dnat ip addr . port to numgen random mod 1 map { 0 : 10.180.0.1 . 80 } add chain ip kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 - add rule ip kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 mark set mark or 0x4000 comment "masquerade" add rule ip kube-proxy external-ULMVA6XW-ns1/svc1/tcp/p80 goto service-ULMVA6XW-ns1/svc1/tcp/p80 - add chain ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 - add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade - add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 add chain ip kube-proxy firewall-ULMVA6XW-ns1/svc1/tcp/p80 add rule ip kube-proxy firewall-ULMVA6XW-ns1/svc1/tcp/p80 ip saddr != { 203.0.113.0/25 } drop @@ -4927,6 +4845,7 @@ func TestBadIPs(t *testing.T) { add element ip kube-proxy service-ips { 1.2.3.4 . tcp . 80 : goto external-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-nodeports { tcp . 3001 : goto external-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy firewall-ips { 1.2.3.4 . tcp . 80 : goto firewall-ULMVA6XW-ns1/svc1/tcp/p80 } + add element ip kube-proxy hairpin-connections { 10.180.0.1 . 10.180.0.1 . tcp . 80 } `) assertNFTablesTransactionEqual(t, getLine(), expected, nft.Dump()) diff --git a/pkg/proxy/nftables/supported.go b/pkg/proxy/nftables/supported.go new file mode 100644 index 0000000000000..78fb9eceffae0 --- /dev/null +++ b/pkg/proxy/nftables/supported.go @@ -0,0 +1,75 @@ +//go:build linux + +/* +Copyright 2015 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 nftables + +import ( + "fmt" + "os" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/version" + utilkernel "k8s.io/kubernetes/pkg/util/kernel" + "sigs.k8s.io/knftables" +) + +// Create a knftables.Interface and check if we can use the nftables proxy mode on this host. +func getNFTablesInterface(ipFamily v1.IPFamily) (knftables.Interface, error) { + var nftablesFamily knftables.Family + if ipFamily == v1.IPv4Protocol { + nftablesFamily = knftables.IPv4Family + } else { + nftablesFamily = knftables.IPv6Family + } + + // We require (or rather, knftables.New does) that the nft binary be version 1.0.1 + // or later, because versions before that would always attempt to parse the entire + // nft ruleset at startup, even if you were only operating on a single table. + // That's bad, because in some cases, new versions of nft have added new rule + // types in ways that triggered bugs in older versions of nft, causing them to + // crash. Thus, if kube-proxy used nft < 1.0.1, it could potentially get locked + // out of its rules because of something some other component had done in a + // completely different table. + nft, err := knftables.New(nftablesFamily, kubeProxyTable) + if err != nil { + return nil, err + } + + // Likewise, we want to ensure that the host filesystem has nft >= 1.0.1, so that + // it's not possible that *our* rules break *the system's* nft. (In particular, we + // know that if kube-proxy uses nft >= 1.0.3 and the system has nft <= 0.9.8, that + // the system nft will become completely unusable.) Unfortunately, we can't easily + // figure out the version of nft installed on the host filesystem, so instead, we + // check the kernel version, under the assumption that the distro will have an nft + // binary that supports the same features as its kernel does, and so kernel 5.13 + // or later implies nft 1.0.1 or later. https://issues.k8s.io/122743 + // + // However, we allow the user to bypass this check by setting + // `KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK` to anything non-empty. + if os.Getenv("KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK") == "" { + kernelVersion, err := utilkernel.GetVersion() + if err != nil { + return nil, fmt.Errorf("could not check kernel version: %w", err) + } + if kernelVersion.LessThan(version.MustParseGeneric(utilkernel.NFTablesKubeProxyKernelVersion)) { + return nil, fmt.Errorf("kube-proxy in nftables mode requires kernel %s or later", utilkernel.NFTablesKubeProxyKernelVersion) + } + } + + return nft, nil +} diff --git a/pkg/proxy/node.go b/pkg/proxy/node.go index 556ca5905f0b4..3dfb786c2f650 100644 --- a/pkg/proxy/node.go +++ b/pkg/proxy/node.go @@ -118,9 +118,9 @@ func newNodeManager(ctx context.Context, client clientset.Interface, resyncInter // For backward-compatibility, we keep going even if we didn't find a node (in // non-watchPodCIDRs mode) or it didn't have IPs. if node == nil { - klog.FromContext(ctx).Error(nil, "Timed out waiting for node %q to exist", nodeName) + klog.FromContext(ctx).Error(nil, "Timed out waiting for node to exist", "node", klog.KRef("", nodeName)) } else if len(nodeIPs) == 0 { - klog.FromContext(ctx).Error(nil, "Timed out waiting for node %q to be assigned IPs", nodeName) + klog.FromContext(ctx).Error(nil, "Timed out waiting for node to be assigned IPs", "node", klog.KRef("", nodeName)) } return &NodeManager{ diff --git a/pkg/proxy/servicechangetracker_test.go b/pkg/proxy/servicechangetracker_test.go index 21e645db10dac..28bfdba9879b1 100644 --- a/pkg/proxy/servicechangetracker_test.go +++ b/pkg/proxy/servicechangetracker_test.go @@ -25,8 +25,8 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/dump" netutils "k8s.io/utils/net" ) diff --git a/pkg/proxy/serviceport.go b/pkg/proxy/serviceport.go index f7f33e0ca5700..1ff5de2f11d87 100644 --- a/pkg/proxy/serviceport.go +++ b/pkg/proxy/serviceport.go @@ -23,8 +23,10 @@ import ( "strings" v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" apiservice "k8s.io/kubernetes/pkg/api/v1/service" + "k8s.io/kubernetes/pkg/features" proxyutil "k8s.io/kubernetes/pkg/proxy/util" netutils "k8s.io/utils/net" ) @@ -136,6 +138,10 @@ func (bsvcPortInfo *BaseServicePortInfo) NodePort() int { // ExternalIPs is part of ServicePort interface. func (bsvcPortInfo *BaseServicePortInfo) ExternalIPs() []net.IP { + // Only program rules for externalIPs if the feature gate is enabled + if !utilfeature.DefaultFeatureGate.Enabled(features.AllowServiceExternalIPs) { + return []net.IP{} + } return bsvcPortInfo.externalIPs } diff --git a/pkg/proxy/topology.go b/pkg/proxy/topology.go index c8b447971eddb..48b2e54113664 100644 --- a/pkg/proxy/topology.go +++ b/pkg/proxy/topology.go @@ -162,12 +162,7 @@ func CategorizeEndpoints(endpoints []Endpoint, svcInfo ServicePort, nodeName str // hinted for this node's zone, then it returns "PreferSameZone". // - Otherwise it returns "" (meaning, no topology / default traffic distribution). func topologyModeFromHints(svcInfo ServicePort, endpoints []Endpoint, nodeName, zone string) string { - if len(endpoints) == 0 { - // The code below assumes at least 1 endpoint; if there are no endpoints, - // there are no hints. - return "" - } - + hasReadyEndpoints := false hasEndpointForNode := false allEndpointsHaveNodeHints := true hasEndpointForZone := false @@ -176,6 +171,7 @@ func topologyModeFromHints(svcInfo ServicePort, endpoints []Endpoint, nodeName, if !endpoint.IsReady() { continue } + hasReadyEndpoints = true if endpoint.NodeHints().Len() == 0 { allEndpointsHaveNodeHints = false @@ -190,6 +186,11 @@ func topologyModeFromHints(svcInfo ServicePort, endpoints []Endpoint, nodeName, } } + // If no ready endpoints exist, there are no hints to consider + if !hasReadyEndpoints { + return "" + } + if utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution) { if allEndpointsHaveNodeHints { if hasEndpointForNode { diff --git a/pkg/proxy/topology_test.go b/pkg/proxy/topology_test.go index 8e9ed4333d2ae..e15337a173246 100644 --- a/pkg/proxy/topology_test.go +++ b/pkg/proxy/topology_test.go @@ -398,6 +398,15 @@ func TestCategorizeEndpoints(t *testing.T) { clusterEndpoints: nil, localEndpoints: nil, allEndpoints: nil, + }, { + name: "single endpoint not ready, not serving, not terminating", + nodeLabels: map[string]string{v1.LabelTopologyZone: "zone-a"}, + serviceInfo: &BaseServicePortInfo{}, + endpoints: []Endpoint{ + &BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: false, serving: false, terminating: false}, + }, + clusterEndpoints: sets.New[string](), + localEndpoints: nil, }} for _, tc := range testCases { diff --git a/pkg/proxy/util/nfacct/handler.go b/pkg/proxy/util/nfacct/handler.go index ec14ea4183362..bd639a6966821 100644 --- a/pkg/proxy/util/nfacct/handler.go +++ b/pkg/proxy/util/nfacct/handler.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/proxy/util/nfacct/nfacct_linux.go b/pkg/proxy/util/nfacct/nfacct_linux.go index 4a91e84aa63d0..12a76bc5479a8 100644 --- a/pkg/proxy/util/nfacct/nfacct_linux.go +++ b/pkg/proxy/util/nfacct/nfacct_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/proxy/util/nfacct/nfacct_linux_test.go b/pkg/proxy/util/nfacct/nfacct_linux_test.go index 54185098dc500..1da3471e80017 100644 --- a/pkg/proxy/util/nfacct/nfacct_linux_test.go +++ b/pkg/proxy/util/nfacct/nfacct_linux_test.go @@ -1,5 +1,4 @@ //go:build linux && !s390x -// +build linux,!s390x /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/proxy/util/nfacct/nfacct_others.go b/pkg/proxy/util/nfacct/nfacct_others.go index 0ca20a2d37b26..198c438933658 100644 --- a/pkg/proxy/util/nfacct/nfacct_others.go +++ b/pkg/proxy/util/nfacct/nfacct_others.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index 59d85a54d6205..b55efd8eea266 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -19,6 +19,7 @@ package util import ( "fmt" "net" + "strconv" "strings" "time" @@ -169,10 +170,7 @@ func AppendPortIfNeeded(addr string, port int32) string { } // Append port to address. - if ip.To4() != nil { - return fmt.Sprintf("%s:%d", addr, port) - } - return fmt.Sprintf("[%s]:%d", addr, port) + return net.JoinHostPort(addr, strconv.Itoa(int(port))) } // EnsureSysctl sets a kernel sysctl to a given numeric value. diff --git a/pkg/proxy/util/utils_linux.go b/pkg/proxy/util/utils_linux.go index 6d43a01aae515..f83a3cf3174d9 100644 --- a/pkg/proxy/util/utils_linux.go +++ b/pkg/proxy/util/utils_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2025 The Kubernetes Authors. diff --git a/pkg/proxy/winkernel/hcnutils.go b/pkg/proxy/winkernel/hcnutils.go index 5c86a113bc09b..a73c54ac76408 100644 --- a/pkg/proxy/winkernel/hcnutils.go +++ b/pkg/proxy/winkernel/hcnutils.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/proxy/winkernel/hns.go b/pkg/proxy/winkernel/hns.go index 6c9aaa857903a..44f00d471e752 100644 --- a/pkg/proxy/winkernel/hns.go +++ b/pkg/proxy/winkernel/hns.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. @@ -32,7 +31,10 @@ import ( type HostNetworkService interface { getNetworkByName(name string) (*hnsNetworkInfo, error) - getAllEndpointsByNetwork(networkName string) (map[string]*endpointInfo, error) + // Returns a map of endpoints keyed by both endpoint ID and IP address for all endpoints on the specified network, and a map of remote endpoints with duplicate IPs to be deleted. + getAllEndpointsByNetwork(networkName string) (map[string]*endpointInfo, map[string]bool, error) + // deleteAllRemoteEndpointsWithDupIP deletes all remote endpoints with duplicate IPs that were found in getAllEndpointsByNetwork. This is needed to clean up stale remote endpoints that can be left behind due to a Windows bug. + deleteAllRemoteEndpointsWithDupIP(remoteEPsWithDupIP map[string]bool) getEndpointByID(id string) (*endpointInfo, error) getEndpointByIpAddress(ip string, networkName string) (*endpointInfo, error) getEndpointByName(id string) (*endpointInfo, error) @@ -115,17 +117,20 @@ func (hns hns) getNetworkByName(name string) (*hnsNetworkInfo, error) { }, nil } -func (hns hns) getAllEndpointsByNetwork(networkName string) (map[string]*(endpointInfo), error) { +func (hns hns) getAllEndpointsByNetwork(networkName string) (map[string]*(endpointInfo), map[string]bool, error) { hcnnetwork, err := hns.hcn.GetNetworkByName(networkName) if err != nil { klog.ErrorS(err, "failed to get HNS network by name", "name", networkName) - return nil, err + return nil, nil, err } endpoints, err := hns.hcn.ListEndpointsOfNetwork(hcnnetwork.Id) if err != nil { - return nil, fmt.Errorf("failed to list endpoints: %w", err) + return nil, nil, fmt.Errorf("failed to list endpoints: %w", err) } + endpointInfos := make(map[string]*(endpointInfo)) + remoteEPsWithDupIP := make(map[string]bool) + for _, ep := range endpoints { if len(ep.IpConfigurations) == 0 { @@ -143,14 +148,22 @@ func (hns hns) getAllEndpointsByNetwork(networkName string) (map[string]*(endpoi break } - isLocal := uint32(ep.Flags&hcn.EndpointFlagsRemoteEndpoint) == 0 - - if existingEp, ok := endpointInfos[ipConfig.IpAddress]; ok && isLocal { - // If the endpoint is already part of the queried endpoints map and is local, - // then we should not add it again to the map - // This is to avoid overwriting the remote endpoint info with a local endpoint. - klog.V(3).InfoS("Endpoint already exists in queried endpoints map; skipping.", "newLocalEndpoint", ep, "ipConfig", ipConfig, "existingEndpoint", existingEp) - continue + curEpIsLocal := uint32(ep.Flags&hcn.EndpointFlagsRemoteEndpoint) == 0 + + if existingEp, ok := endpointInfos[ipConfig.IpAddress]; ok { + if curEpIsLocal && !existingEp.isLocal { + // Local found, stale remote in map → delete remote from HNS, overwrite + remoteEPsWithDupIP[existingEp.hnsID] = true + delete(endpointInfos, existingEp.hnsID) + delete(endpointInfos, existingEp.ip) + // fall through to add local + } else if !curEpIsLocal && existingEp.isLocal { + // Local already in map, remote arriving → delete remote from HNS, skip + remoteEPsWithDupIP[ep.Id] = true + continue + } else { + continue // same type, keep existing + } } // Add to map with key endpoint ID or IP address @@ -158,7 +171,7 @@ func (hns hns) getAllEndpointsByNetwork(networkName string) (map[string]*(endpoi // TODO: Store by IP only and remove any lookups by endpoint ID. epInfo := &endpointInfo{ ip: ipConfig.IpAddress, - isLocal: isLocal, + isLocal: curEpIsLocal, macAddress: ep.MacAddress, hnsID: ep.Id, hns: hns, @@ -173,7 +186,17 @@ func (hns hns) getAllEndpointsByNetwork(networkName string) (map[string]*(endpoi } klog.V(3).InfoS("Queried endpoints from network", "network", networkName, "count", len(endpointInfos)) klog.V(5).InfoS("Queried endpoints details", "network", networkName, "endpointInfos", endpointInfos) - return endpointInfos, nil + return endpointInfos, remoteEPsWithDupIP, nil +} + +func (hns hns) deleteAllRemoteEndpointsWithDupIP(remoteEPsWithDupIP map[string]bool) { + for hnsID := range remoteEPsWithDupIP { + klog.V(3).InfoS("Deleting stale remote endpoint with duplicate IP", "hnsID", hnsID) + err := hns.deleteEndpoint(hnsID) + if err != nil { + klog.ErrorS(err, "Failed to delete stale remote endpoint with duplicate IP", "hnsID", hnsID) + } + } } func (hns hns) getEndpointByID(id string) (*endpointInfo, error) { diff --git a/pkg/proxy/winkernel/hns_test.go b/pkg/proxy/winkernel/hns_test.go index 604eec7444396..069125477b08d 100644 --- a/pkg/proxy/winkernel/hns_test.go +++ b/pkg/proxy/winkernel/hns_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. @@ -95,7 +94,7 @@ func TestGetAllEndpointsByNetwork(t *testing.T) { t.Error(err) } - mapEndpointsInfo, err := hns.getAllEndpointsByNetwork(Network.Name) + mapEndpointsInfo, _, err := hns.getAllEndpointsByNetwork(Network.Name) if err != nil { t.Error(err) } @@ -157,24 +156,25 @@ func TestGetAllEndpointsByNetworkWithDupEP(t *testing.T) { t.Error(err) } - mapEndpointsInfo, err := hns.getAllEndpointsByNetwork(Network.Name) + mapEndpointsInfo, remoteEPsWithDupIP, err := hns.getAllEndpointsByNetwork(Network.Name) if err != nil { t.Error(err) } + hns.deleteAllRemoteEndpointsWithDupIP(remoteEPsWithDupIP) endpointIpv4, ipv4EpPresent := mapEndpointsInfo[ipv4Config.IpAddress] assert.True(t, ipv4EpPresent, "IPV4 endpoint is missing in Dualstack mode") assert.Equal(t, endpointIpv4.ip, epIpAddress, "IPV4 IP is missing in Dualstack mode") - assert.Equal(t, endpointIpv4.hnsID, remoteEndpoint.Id, "HNS ID is not matching with remote endpoint") + assert.Equal(t, endpointIpv4.hnsID, dupLocalEndpoint.Id, "HNS ID is not matching with local endpoint") endpointIpv6, ipv6EpPresent := mapEndpointsInfo[ipv6Config.IpAddress] assert.True(t, ipv6EpPresent, "IPV6 endpoint is missing in Dualstack mode") assert.Equal(t, endpointIpv6.ip, epIpv6Address, "IPV6 IP is missing in Dualstack mode") - assert.Equal(t, endpointIpv6.hnsID, remoteEndpoint.Id, "HNS ID is not matching with remote endpoint") + assert.Equal(t, endpointIpv6.hnsID, dupLocalEndpoint.Id, "HNS ID is not matching with local endpoint") - err = hns.hcn.DeleteEndpoint(remoteEndpoint) - if err != nil { - t.Error(err) - } + remoteEpExists, _ := hns.hcn.GetEndpointByID(remoteEndpoint.Id) + assert.Nil(t, remoteEpExists, "Remote endpoint with duplicate IP should have been deleted") + + // Clean up the duplicate local endpoint err = hns.hcn.DeleteEndpoint(dupLocalEndpoint) if err != nil { t.Error(err) diff --git a/pkg/proxy/winkernel/proxier.go b/pkg/proxy/winkernel/proxier.go index 46ed064b62fef..98fddb2650848 100644 --- a/pkg/proxy/winkernel/proxier.go +++ b/pkg/proxy/winkernel/proxier.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. @@ -52,38 +51,10 @@ import ( netutils "k8s.io/utils/net" ) -// KernelCompatTester tests whether the required kernel capabilities are -// present to run the windows kernel proxier. -type KernelCompatTester interface { - IsCompatible() error -} - type HostMacProvider interface { GetHostMac(nodeIP net.IP) string } -// CanUseWinKernelProxier returns true if we should use the Kernel Proxier -// instead of the "classic" userspace Proxier. This is determined by checking -// the windows kernel version and for the existence of kernel features. -func CanUseWinKernelProxier(kcompat KernelCompatTester) (bool, error) { - // Check that the kernel supports what we need. - if err := kcompat.IsCompatible(); err != nil { - return false, err - } - return true, nil -} - -type WindowsKernelCompatTester struct{} - -// IsCompatible returns true if winkernel can support this mode of proxy -func (lkct WindowsKernelCompatTester) IsCompatible() error { - _, err := hnslib.HNSListPolicyListRequest() - if err != nil { - return fmt.Errorf("Windows kernel is not compatible for Kernel mode") - } - return nil -} - type externalIPInfo struct { ip string hnsID string @@ -184,7 +155,8 @@ type remoteSubnetInfo struct { } const ( - NETWORK_TYPE_OVERLAY = "overlay" + NETWORK_TYPE_OVERLAY = "overlay" + NETWORK_TYPE_L2BRIDGE = "L2Bridge" // MAX_COUNT_STALE_LOADBALANCERS is the maximum number of stale loadbalancers which cleanedup in single syncproxyrules. // If there are more stale loadbalancers to clean, it will go to next iteration of syncproxyrules. MAX_COUNT_STALE_LOADBALANCERS = 20 @@ -267,50 +239,6 @@ func isOverlay(hnsNetworkInfo *hnsNetworkInfo) bool { return strings.EqualFold(hnsNetworkInfo.networkType, NETWORK_TYPE_OVERLAY) } -// StackCompatTester tests whether the required kernel and network are dualstack capable -type StackCompatTester interface { - DualStackCompatible(networkName string) bool -} - -type DualStackCompatTester struct{} - -func (t DualStackCompatTester) DualStackCompatible(networkName string) bool { - hcnImpl := newHcnImpl() - // First tag of hnslib that has a proper check for dual stack support is v0.8.22 due to a bug. - if err := hcnImpl.Ipv6DualStackSupported(); err != nil { - // Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing - // to see if dual stack is supported), but the only time this can happen, at least that can be discerned, is if the host - // is pre-1803 and hcn didn't exist. hnslib should truthfully return a known error if this happened that we can - // check against, and the case where 'err != this known error' would be the 'this feature isn't supported' case, as is being - // used here. For now, seeming as how nothing before ws2019 (1809) is listed as supported for k8s we can pretty much assume - // any error here isn't because the query failed, it's just that dualstack simply isn't supported on the host. With all - // that in mind, just log as info and not error to let the user know we're falling back. - klog.InfoS("This version of Windows does not support dual-stack, falling back to single-stack", "err", err.Error()) - return false - } - - // check if network is using overlay - hns, _ := newHostNetworkService(hcnImpl) - networkName, err := getNetworkName(networkName) - if err != nil { - klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") - return false - } - networkInfo, err := getNetworkInfo(hns, networkName) - if err != nil { - klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") - return false - } - - if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WinOverlay) && isOverlay(networkInfo) { - // Overlay (VXLAN) networks on Windows do not support dual-stack networking today - klog.InfoS("Winoverlay does not support dual-stack, falling back to single-stack") - return false - } - - return true -} - // internal struct for endpoints information type endpointInfo struct { ip string @@ -725,7 +653,7 @@ func NewProxier( ) (*Proxier, error) { // windows listens to all node addresses nodePortAddresses := proxyutil.NewNodePortAddresses(ipFamily, nil) - serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer) + serviceHealthServer := healthcheck.NewServiceHealthServer(nodeName, recorder, nodePortAddresses, healthzServer, ipFamily) var healthzPort int if len(healthzBindAddress) > 0 { @@ -1243,7 +1171,9 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { _ = proxier.endpointsMap.Update(proxier.endpointsChanges) // Query HNS for endpoints and load balancers - queriedEndpoints, err := hns.getAllEndpointsByNetwork(hnsNetworkName) + queriedEndpoints, remoteEPsWithDupIP, err := hns.getAllEndpointsByNetwork(hnsNetworkName) + defer hns.deleteAllRemoteEndpointsWithDupIP(remoteEPsWithDupIP) + if err != nil { klog.ErrorS(err, "Querying HNS for endpoints failed") return @@ -1788,23 +1718,30 @@ func (proxier *Proxier) syncProxyRules() (retryError error) { } // remove stale endpoint refcount entries + proxier.deleteTerminatedEndpoints(queriedEndpoints) + + // This will cleanup stale load balancers which are pending delete + // in last iteration + proxier.cleanupStaleLoadbalancers() + return +} + +func (proxier *Proxier) deleteTerminatedEndpoints(queriedEndpoints map[string]*(endpointInfo)) { for epIP := range proxier.terminatedEndpoints { klog.V(5).InfoS("Terminated endpoints ready for deletion", "epIP", epIP) if epToDelete := queriedEndpoints[epIP]; epToDelete != nil && epToDelete.hnsID != "" && !epToDelete.IsLocal() { - if refCount := proxier.endPointsRefCount.getRefCount(epToDelete.hnsID); refCount == nil || *refCount == 0 { - err := proxier.hns.deleteEndpoint(epToDelete.hnsID) - if err != nil { + refCount := proxier.endPointsRefCount.getRefCount(epToDelete.hnsID) + if refCount == nil || *refCount == 0 { + if err := proxier.hns.deleteEndpoint(epToDelete.hnsID); err != nil { klog.ErrorS(err, "Deleting unreferenced remote endpoint failed", "hnsID", epToDelete.hnsID) } else { klog.V(3).InfoS("Deleting unreferenced remote endpoint succeeded", "hnsID", epToDelete.hnsID, "IP", epToDelete.ip) } + } else { + klog.V(3).InfoS("Not deleting remote endpoint as it is still referenced", "hnsID", epToDelete.hnsID, "IP", epToDelete.ip, "refCount", refCount) } } } - // This will cleanup stale load balancers which are pending delete - // in last iteration - proxier.cleanupStaleLoadbalancers() - return } // deleteExistingLoadBalancer checks whether loadbalancer delete is needed or not. diff --git a/pkg/proxy/winkernel/proxier_test.go b/pkg/proxy/winkernel/proxier_test.go index d6492a929704d..dace6e9fb7cd7 100644 --- a/pkg/proxy/winkernel/proxier_test.go +++ b/pkg/proxy/winkernel/proxier_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2021 The Kubernetes Authors. @@ -2004,3 +2003,674 @@ type testHostMacProvider struct { func (r *testHostMacProvider) GetHostMac(nodeIP net.IP) string { return r.macAddress } + +// TestRemoteAndLocalEndpointsSameIP demonstrates a reference counting issue +// when two services share an endpoint with the same IP address, where one +// service treats it as local (NodeName matches proxy hostname) and the other +// treats it as remote (NodeName doesn't match). The remote proxy endpoint +// resolves to the local HNS endpoint, causing its refCount to never be +// incremented via the shared endPointsRefCount map. +func TestRemoteAndLocalEndpointsSameIP(t *testing.T) { + proxier := NewFakeProxier(t, testNodeName, netutils.ParseIPSloppy("10.0.0.1"), NETWORK_TYPE_L2BRIDGE, false) + if proxier == nil { + t.Error("Failed to create proxier") + } + + sharedEPIP := epIpAddressLocal1 // "192.168.4.4" — same IP for both services + + svcIP1 := "10.20.30.41" + svcPort1 := 80 + svcPortName1 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + svcIP2 := "10.20.30.42" + svcPort2 := 80 + svcPortName2 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc2"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + makeServiceMap(proxier, + // svc1 uses the endpoint as LOCAL + makeTestService(svcPortName1.Namespace, svcPortName1.Name, func(svc *v1.Service) { + svc.Spec.Type = v1.ServiceTypeClusterIP + svc.Spec.ClusterIP = svcIP1 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName1.Port, + Port: int32(svcPort1), + Protocol: v1.ProtocolTCP, + }} + }), + // svc2 uses the endpoint as REMOTE + makeTestService(svcPortName2.Namespace, svcPortName2.Name, func(svc *v1.Service) { + svc.Spec.Type = v1.ServiceTypeClusterIP + svc.Spec.ClusterIP = svcIP2 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName2.Port, + Port: int32(svcPort2), + Protocol: v1.ProtocolTCP, + }} + }), + ) + + populateEndpointSlices(proxier, + // svc1's endpoint: local (NodeName = "testhost" matches proxy hostname) + makeTestEndpointSlice(svcPortName1.Namespace, svcPortName1.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{sharedEPIP}, + NodeName: ptr.To(testNodeName), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName1.Port), + Port: ptr.To(int32(svcPort1)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + // svc2's endpoint: remote (NodeName = "testhost2" doesn't match proxy hostname) + makeTestEndpointSlice(svcPortName2.Namespace, svcPortName2.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{sharedEPIP}, + NodeName: ptr.To("testhost2"), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName2.Port), + Port: ptr.To(int32(svcPort2)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + ) + + // Pre-populate the local HNS endpoint at sharedEPIP (as CNI would create it) + hcnMock := (proxier.hcn).(*fakehcn.HcnMock) + hcnMock.PopulateQueriedEndpoints(endpointLocal1, networkId, sharedEPIP, macAddressLocal1, prefixLen) + + proxier.setInitialized(true) + proxier.syncProxyRules() + + // Find each service's endpoint + var localEp, remoteEp *endpointInfo + for _, ep := range proxier.endpointsMap[svcPortName1] { + if epI, ok := ep.(*endpointInfo); ok && epI.ip == sharedEPIP { + localEp = epI + } + } + for _, ep := range proxier.endpointsMap[svcPortName2] { + if epI, ok := ep.(*endpointInfo); ok && epI.ip == sharedEPIP { + remoteEp = epI + } + } + + assert.NotNil(t, localEp, "Expected to find local endpoint for svc1") + assert.NotNil(t, remoteEp, "Expected to find remote endpoint for svc2") + + // Both should resolve to the same local HNS endpoint + assert.Equal(t, endpointLocal1, localEp.hnsID, + "Local ep should have the pre-populated HNS endpoint ID") + assert.Equal(t, endpointLocal1, remoteEp.hnsID, + "Remote ep should resolve to the same local HNS endpoint ID") + + // Verify the endpoint locality as seen by the proxy layer + assert.True(t, localEp.IsLocal(), "svc1's endpoint should be local") + assert.False(t, remoteEp.IsLocal(), "svc2's endpoint should be remote") + + // The remote ep's refCount was never incremented via endPointsRefCount + // because the resolved HNS endpoint is local (newHnsEndpoint.IsLocal()=true), + // so the code took the hnsLocalEndpoints branch instead of incrementing the + // shared refCount. The remote ep retains its private refCount (value 0). + assert.NotNil(t, remoteEp.refCount, "Remote ep refCount pointer should not be nil") + assert.Equal(t, uint16(0), *remoteEp.refCount, + "Remote ep refCount should be 0 — it was never incremented because "+ + "the HNS endpoint is local, exposing a refCount tracking gap") + + // The shared endPointsRefCount map should not have an entry for this + // HNS endpoint (or if it does from some other path, it should be 0), + // because refCounts are only tracked for remote HNS endpoints. + if refCountPtr, exists := proxier.endPointsRefCount[endpointLocal1]; exists { + assert.Equal(t, uint16(0), *refCountPtr, + "Shared refCount for the local HNS endpoint should be 0") + } + // If the entry doesn't exist at all, that also confirms the issue: + // the remote proxy endpoint has no shared refCount tracking. +} + +// TestRemoteEndpointDeleteDoesNotAffectLocalEndpointWithSameIP verifies that +// when a remote endpoint is deleted (due to endpoint map changes), a local +// endpoint with the same IP address is not incorrectly deleted. +// +// Flow: +// 1. Create a local endpoint (IP-A) and a remote endpoint (IP-B). +// 2. Two services (svc1, svc2) both use IP-A and IP-B. Remote endpoint refCount = 2. +// 3. Add a third service (svc3) with a local endpoint at IP-B (same IP as the remote). +// Remote refCount stays 2; local endpoint refCount is 0 (locals are not ref-counted). +// 4. Remove endpoints for svc1 and svc2. The remote endpoint at IP-B should be +// cleaned up (refCount drops to 0), but the local endpoint at IP-B for svc3 +// must survive. +func TestRemoteEndpointDeleteDoesNotAffectLocalEndpointWithSameIP(t *testing.T) { + proxier := NewFakeProxier(t, testNodeName, netutils.ParseIPSloppy("10.0.0.1"), NETWORK_TYPE_L2BRIDGE, false) + if proxier == nil { + t.Fatal("Failed to create proxier") + } + + localIP := epIpAddressLocal1 // "192.168.4.4" + remoteIP := epIpAddressRemote // "192.168.2.3" + + svcIP1 := "10.20.30.41" + svcPort1 := 80 + svcNodePort1 := 3001 + svcPortName1 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + svcIP2 := "10.20.30.42" + svcPort2 := 80 + svcNodePort2 := 3002 + svcPortName2 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc2"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + svcIP3 := "10.20.30.43" + svcPort3 := 80 + svcPortName3 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc3"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + // Step 1+2: Create two services (svc1, svc2) each with a local endpoint (IP-A) + // and a remote endpoint (IP-B). + makeServiceMap(proxier, + makeTestService(svcPortName1.Namespace, svcPortName1.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP1 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName1.Port, + Port: int32(svcPort1), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort1), + }} + }), + makeTestService(svcPortName2.Namespace, svcPortName2.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP2 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName2.Port, + Port: int32(svcPort2), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort2), + }} + }), + ) + + populateEndpointSlices(proxier, + makeTestEndpointSlice(svcPortName1.Namespace, svcPortName1.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + { + Addresses: []string{localIP}, + NodeName: ptr.To(testNodeName), + }, + { + Addresses: []string{remoteIP}, + }, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName1.Port), + Port: ptr.To(int32(svcPort1)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + makeTestEndpointSlice(svcPortName2.Namespace, svcPortName2.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + { + Addresses: []string{localIP}, + NodeName: ptr.To(testNodeName), + }, + { + Addresses: []string{remoteIP}, + }, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName2.Port), + Port: ptr.To(int32(svcPort2)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + ) + + // Pre-populate the local HNS endpoint (as CNI would create it). + hcnMock := (proxier.hcn).(*fakehcn.HcnMock) + hcnMock.PopulateQueriedEndpoints(endpointLocal1, networkId, localIP, macAddressLocal1, prefixLen) + + proxier.setInitialized(true) + proxier.syncProxyRules() + + // Find the remote endpoint for svc1 and assert refCount == 2. + var remoteEpInfo *endpointInfo + var remoteEpHnsID string + for _, ep := range proxier.endpointsMap[svcPortName1] { + epI, ok := ep.(*endpointInfo) + if ok && epI.ip == remoteIP { + remoteEpInfo = epI + remoteEpHnsID = epI.hnsID + } + } + assert.NotNil(t, remoteEpInfo, "Expected to find remote endpoint for svc1") + assert.NotEmpty(t, remoteEpHnsID, "Remote endpoint should have an HNS ID") + assert.Equal(t, uint16(2), *remoteEpInfo.refCount, + "Remote endpoint refCount should be 2 after two services reference it") + assert.Equal(t, *proxier.endPointsRefCount[remoteEpHnsID], *remoteEpInfo.refCount, + "Global and endpoint refCounts should match") + + // Step 3: Add svc3 with a local endpoint at IP-B (same IP as the remote endpoint). + proxier.setInitialized(false) + + proxier.OnServiceAdd( + makeTestService(svcPortName3.Namespace, svcPortName3.Name, func(svc *v1.Service) { + svc.Spec.Type = v1.ServiceTypeClusterIP + svc.Spec.ClusterIP = svcIP3 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName3.Port, + Port: int32(svcPort3), + Protocol: v1.ProtocolTCP, + }} + })) + proxier.mu.Lock() + proxier.servicesSynced = true + proxier.mu.Unlock() + + proxier.OnEndpointSliceAdd( + makeTestEndpointSlice(svcPortName3.Namespace, svcPortName3.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{remoteIP}, + NodeName: ptr.To(testNodeName), // Local because NodeName matches + }} + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName3.Port), + Port: ptr.To(int32(svcPort3)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + })) + proxier.mu.Lock() + proxier.endpointSlicesSynced = true + proxier.mu.Unlock() + + // Pre-populate the new local HNS endpoint at IP-B. + hcnMock.PopulateQueriedEndpoints(endpointLocal2, networkId, remoteIP, macAddressLocal2, prefixLen) + + proxier.setInitialized(true) + proxier.syncProxyRules() + + // The remote endpoint refCount should still be 2 (svc1 and svc2 still reference it). + remoteEpInfo = nil + for _, ep := range proxier.endpointsMap[svcPortName1] { + epI, ok := ep.(*endpointInfo) + if ok && epI.ip == remoteIP { + remoteEpInfo = epI + } + } + assert.NotNil(t, remoteEpInfo, "Expected remote endpoint still present for svc1") + assert.Equal(t, uint16(2), *remoteEpInfo.refCount, + "Remote endpoint refCount should still be 2 after adding local endpoint with same IP") + + // Find svc3's local endpoint at IP-B. Its refCount should be 0 + // (local endpoints are not ref-counted). + var localEpAtRemoteIP *endpointInfo + for _, ep := range proxier.endpointsMap[svcPortName3] { + epI, ok := ep.(*endpointInfo) + if ok && epI.ip == remoteIP { + localEpAtRemoteIP = epI + } + } + assert.NotNil(t, localEpAtRemoteIP, "Expected to find local endpoint for svc3 at IP-B") + assert.True(t, localEpAtRemoteIP.IsLocal(), "svc3's endpoint should be local") + assert.NotNil(t, localEpAtRemoteIP.refCount, "Local endpoint refCount pointer should not be nil") + assert.Equal(t, uint16(0), *localEpAtRemoteIP.refCount, + "Local endpoint refCount should be 0 (locals are not ref-counted)") + + // Step 4: Remove endpoint slices for svc1 and svc2 (IPs A and B disappear). + // This should cause the remote endpoint at IP-B to be cleaned up, + // but the local endpoint at IP-B (used by svc3) must NOT be deleted. + proxier.setInitialized(false) + + deleteEndpointSlices(proxier, + makeTestEndpointSlice(svcPortName1.Namespace, svcPortName1.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + { + Addresses: []string{localIP}, + NodeName: ptr.To(testNodeName), + }, + { + Addresses: []string{remoteIP}, + }, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName1.Port), + Port: ptr.To(int32(svcPort1)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + makeTestEndpointSlice(svcPortName2.Namespace, svcPortName2.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + { + Addresses: []string{localIP}, + NodeName: ptr.To(testNodeName), + }, + { + Addresses: []string{remoteIP}, + }, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName2.Port), + Port: ptr.To(int32(svcPort2)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + ) + + proxier.setInitialized(true) + proxier.syncProxyRules() + + // svc1 and svc2 should have no endpoints now. + assert.Empty(t, proxier.endpointsMap[svcPortName1], + "svc1 should have no endpoints after deletion") + assert.Empty(t, proxier.endpointsMap[svcPortName2], + "svc2 should have no endpoints after deletion") + + // svc3's local endpoint at IP-B should still be present and functional. + var svc3Ep *endpointInfo + for _, ep := range proxier.endpointsMap[svcPortName3] { + epI, ok := ep.(*endpointInfo) + if ok && epI.ip == remoteIP { + svc3Ep = epI + } + } + assert.NotNil(t, svc3Ep, "svc3's local endpoint at IP-B should still exist") + assert.True(t, svc3Ep.IsLocal(), "svc3's endpoint should still be local") + assert.NotEmpty(t, svc3Ep.hnsID, "svc3's local endpoint should still have a valid HNS ID") + + // Verify the local HNS endpoint was NOT deleted from HNS. + _, err := hcnMock.GetEndpointByID(svc3Ep.hnsID) + assert.NoError(t, err, "Local HNS endpoint at IP-B should still exist in HNS (not deleted)") +} + +// TestEndpointTransitionWithLBFailures verifies endpoint reference counting +// when an endpoint set transitions from [A,B,C,D] to [A,B] while load +// balancer delete and create operations both fail. +// +// Flow: +// 1. Create a local endpoint (A) and 3 remote endpoints (B, C, D). +// 2. Two services (svc1, svc2) each reference all 4 endpoints. +// Assert remote endpoint refCounts B=2, C=2, D=2. +// 3. Transition endpoints from [A,B,C,D] to [A,B] while LB delete is +// injected to fail. LB create then also fails because the old LB +// (same VIP:port) was never removed. +// 4. Assert that C and D refCounts drop from 2 to 0. B gets a new HNS +// endpoint with refCount=2. A remains local with refCount=0. +func TestEndpointTransitionWithLBFailures(t *testing.T) { + proxier := NewFakeProxier(t, testNodeName, netutils.ParseIPSloppy("10.0.0.1"), NETWORK_TYPE_L2BRIDGE, false) + if proxier == nil { + t.Fatal("Failed to create proxier") + } + + localIP := epIpAddressLocal1 // "192.168.4.4" + remoteIPB := epIpAddressRemote // "192.168.2.3" + remoteIPC := "192.168.2.4" + remoteIPD := "192.168.2.5" + + svcIP1 := "10.20.30.41" + svcPort1 := 80 + svcNodePort1 := 3001 + svcPortName1 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + svcIP2 := "10.20.30.42" + svcPort2 := 80 + svcNodePort2 := 3002 + svcPortName2 := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc2"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + // Step 1+2: Create two services with a local endpoint (A) and + // three remote endpoints (B, C, D). + makeServiceMap(proxier, + makeTestService(svcPortName1.Namespace, svcPortName1.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP1 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName1.Port, + Port: int32(svcPort1), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort1), + }} + }), + makeTestService(svcPortName2.Namespace, svcPortName2.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP2 + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName2.Port, + Port: int32(svcPort2), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort2), + }} + }), + ) + + populateEndpointSlices(proxier, + makeTestEndpointSlice(svcPortName1.Namespace, svcPortName1.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + {Addresses: []string{localIP}, NodeName: ptr.To(testNodeName)}, + {Addresses: []string{remoteIPB}}, + {Addresses: []string{remoteIPC}}, + {Addresses: []string{remoteIPD}}, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName1.Port), + Port: ptr.To(int32(svcPort1)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + makeTestEndpointSlice(svcPortName2.Namespace, svcPortName2.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + {Addresses: []string{localIP}, NodeName: ptr.To(testNodeName)}, + {Addresses: []string{remoteIPB}}, + {Addresses: []string{remoteIPC}}, + {Addresses: []string{remoteIPD}}, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName2.Port), + Port: ptr.To(int32(svcPort2)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }), + ) + + // Pre-populate the local HNS endpoint (as CNI would create it). + hcnMock := (proxier.hcn).(*fakehcn.HcnMock) + hcnMock.PopulateQueriedEndpoints(endpointLocal1, networkId, localIP, macAddressLocal1, prefixLen) + + proxier.setInitialized(true) + proxier.syncProxyRules() + + // Collect the HNS IDs for B, C, D after the first sync. + // For L2Bridge without overlay, remote endpoints are created sequentially: + // B → EPID-1, C → EPID-2, D → EPID-3 + findRemoteEp := func(svcPortName proxy.ServicePortName, ip string) *endpointInfo { + for _, ep := range proxier.endpointsMap[svcPortName] { + if epI, ok := ep.(*endpointInfo); ok && epI.ip == ip { + return epI + } + } + return nil + } + + epB := findRemoteEp(svcPortName1, remoteIPB) + epC := findRemoteEp(svcPortName1, remoteIPC) + epD := findRemoteEp(svcPortName1, remoteIPD) + epA := findRemoteEp(svcPortName1, localIP) + + assert.NotNil(t, epB, "Expected to find remote endpoint B") + assert.NotNil(t, epC, "Expected to find remote endpoint C") + assert.NotNil(t, epD, "Expected to find remote endpoint D") + assert.NotNil(t, epA, "Expected to find local endpoint A") + + hnsIDB := epB.hnsID + hnsIDC := epC.hnsID + hnsIDD := epD.hnsID + + // Assert all remote endpoint refCounts are 2 (shared by svc1 and svc2). + assert.Equal(t, uint16(2), *proxier.endPointsRefCount[hnsIDB], + "B refCount should be 2") + assert.Equal(t, uint16(2), *proxier.endPointsRefCount[hnsIDC], + "C refCount should be 2") + assert.Equal(t, uint16(2), *proxier.endPointsRefCount[hnsIDD], + "D refCount should be 2") + + // Record the old LB IDs for both services. + svc1Info := proxier.svcPortMap[svcPortName1].(*serviceInfo) + svc2Info := proxier.svcPortMap[svcPortName2].(*serviceInfo) + oldLBID1 := svc1Info.hnsID + oldLBID2 := svc2Info.hnsID + assert.NotEmpty(t, oldLBID1, "svc1 should have a ClusterIP LB after first sync") + assert.NotEmpty(t, oldLBID2, "svc2 should have a ClusterIP LB after first sync") + + // Step 3: Inject LB delete failure and transition endpoints from + // [A,B,C,D] to [A,B]. + hcnMock.ShouldFailDeleteLoadBalancer = true + proxier.setInitialized(false) + + // Build the old and new endpoint slices for the update. + oldSlice1 := makeTestEndpointSlice(svcPortName1.Namespace, svcPortName1.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + {Addresses: []string{localIP}, NodeName: ptr.To(testNodeName)}, + {Addresses: []string{remoteIPB}}, + {Addresses: []string{remoteIPC}}, + {Addresses: []string{remoteIPD}}, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName1.Port), + Port: ptr.To(int32(svcPort1)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }) + newSlice1 := makeTestEndpointSlice(svcPortName1.Namespace, svcPortName1.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + {Addresses: []string{localIP}, NodeName: ptr.To(testNodeName)}, + {Addresses: []string{remoteIPB}}, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName1.Port), + Port: ptr.To(int32(svcPort1)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }) + oldSlice2 := makeTestEndpointSlice(svcPortName2.Namespace, svcPortName2.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + {Addresses: []string{localIP}, NodeName: ptr.To(testNodeName)}, + {Addresses: []string{remoteIPB}}, + {Addresses: []string{remoteIPC}}, + {Addresses: []string{remoteIPD}}, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName2.Port), + Port: ptr.To(int32(svcPort2)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }) + newSlice2 := makeTestEndpointSlice(svcPortName2.Namespace, svcPortName2.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{ + {Addresses: []string{localIP}, NodeName: ptr.To(testNodeName)}, + {Addresses: []string{remoteIPB}}, + } + eps.Ports = []discovery.EndpointPort{{ + Name: ptr.To(svcPortName2.Port), + Port: ptr.To(int32(svcPort2)), + Protocol: ptr.To(v1.ProtocolTCP), + }} + }) + + proxier.OnEndpointSliceUpdate(oldSlice1, newSlice1) + proxier.OnEndpointSliceUpdate(oldSlice2, newSlice2) + + proxier.mu.Lock() + proxier.endpointSlicesSynced = true + proxier.mu.Unlock() + + proxier.setInitialized(true) + proxier.syncProxyRules() + + // Step 4: Assert the results. + // + // LB delete was injected to fail, so the old LBs are still in HNS. + // LB create naturally fails because the old LBs (same VIP:port) were + // never removed, causing a "port already exists" conflict. + svc1Info = proxier.svcPortMap[svcPortName1].(*serviceInfo) + svc2Info = proxier.svcPortMap[svcPortName2].(*serviceInfo) + + // svcInfo.hnsID should still hold the old stale LB ID (delete failed, create skipped). + assert.Empty(t, svc1Info.hnsID, "svc1 LB ID should be empty because create failed due to existing LB") + assert.Empty(t, svc2Info.hnsID, "svc2 LB ID should be empty because create failed due to existing LB") + + // The old LBs should still exist in HNS because deletion failed. + lb1, err1 := hcnMock.GetLoadBalancerByID(oldLBID1) + assert.NoError(t, err1, "Old LB for svc1 should still exist in HNS (delete failed)") + assert.NotNil(t, lb1) + lb2, err2 := hcnMock.GetLoadBalancerByID(oldLBID2) + assert.NoError(t, err2, "Old LB for svc2 should still exist in HNS (delete failed)") + assert.NotNil(t, lb2) + + // C and D refCounts should have dropped from 2 to 0. + // The endpointsMapChange callback calls cleanupAllPolicies with the + // OLD endpoint set (since the map hasn't been updated yet at callback time). + // This decrements C and D's refCounts twice (once per service), reaching 0. + // They stay in terminatedEndpoints (not in the new map) and get deleted by + // cleanupTerminatedEndpoints. + assert.Equal(t, uint16(2), *proxier.endPointsRefCount[hnsIDB], + "B refCount should be 2") + assert.Equal(t, uint16(0), *proxier.endPointsRefCount[hnsIDC], + "C refCount should be 0 after transition") + assert.Equal(t, uint16(0), *proxier.endPointsRefCount[hnsIDD], + "D refCount should be 0 after transition") + + // C and D HNS endpoints should have been deleted by cleanupTerminatedEndpoints. + _, errC := hcnMock.GetEndpointByID(hnsIDC) + assert.Error(t, errC, "C HNS endpoint should have been deleted") + _, errD := hcnMock.GetEndpointByID(hnsIDD) + assert.Error(t, errD, "D HNS endpoint should have been deleted") + + epBNew := findRemoteEp(svcPortName1, remoteIPB) + assert.NotNil(t, epBNew, "B endpoint should exist for svc1") + assert.Equal(t, hnsIDB, epBNew.hnsID, + "B should keep the same HNS ID (reused, not recreated)") + assert.Equal(t, uint16(2), *proxier.endPointsRefCount[hnsIDB], + "B refCount should be 2 after reuse by both services") + + // A is local — its private refCount should be 0 (locals are not ref-counted). + epANew := findRemoteEp(svcPortName1, localIP) + assert.NotNil(t, epANew, "Local endpoint A should still exist") + assert.True(t, epANew.IsLocal(), "A should be local") +} diff --git a/pkg/proxy/winkernel/supported.go b/pkg/proxy/winkernel/supported.go new file mode 100644 index 0000000000000..a050512805140 --- /dev/null +++ b/pkg/proxy/winkernel/supported.go @@ -0,0 +1,101 @@ +//go:build windows + +/* +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. +*/ + +package winkernel + +import ( + "fmt" + + "github.com/Microsoft/hnslib" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/klog/v2" + kubefeatures "k8s.io/kubernetes/pkg/features" +) + +// KernelCompatTester tests whether the required kernel capabilities are +// present to run the windows kernel proxier. +type KernelCompatTester interface { + IsCompatible() error +} + +// CanUseWinKernelProxier returns true if we should use the Kernel Proxier +// instead of the "classic" userspace Proxier. This is determined by checking +// the windows kernel version and for the existence of kernel features. +func CanUseWinKernelProxier(kcompat KernelCompatTester) (bool, error) { + // Check that the kernel supports what we need. + if err := kcompat.IsCompatible(); err != nil { + return false, err + } + return true, nil +} + +type WindowsKernelCompatTester struct{} + +// IsCompatible returns true if winkernel can support this mode of proxy +func (lkct WindowsKernelCompatTester) IsCompatible() error { + _, err := hnslib.HNSListPolicyListRequest() + if err != nil { + return fmt.Errorf("Windows kernel is not compatible for Kernel mode") + } + return nil +} + +// StackCompatTester tests whether the required kernel and network are dualstack capable +type StackCompatTester interface { + DualStackCompatible(networkName string) bool +} + +type DualStackCompatTester struct{} + +func (t DualStackCompatTester) DualStackCompatible(networkName string) bool { + hcnImpl := newHcnImpl() + // First tag of hnslib that has a proper check for dual stack support is v0.8.22 due to a bug. + if err := hcnImpl.Ipv6DualStackSupported(); err != nil { + // Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing + // to see if dual stack is supported), but the only time this can happen, at least that can be discerned, is if the host + // is pre-1803 and hcn didn't exist. hnslib should truthfully return a known error if this happened that we can + // check against, and the case where 'err != this known error' would be the 'this feature isn't supported' case, as is being + // used here. For now, seeming as how nothing before ws2019 (1809) is listed as supported for k8s we can pretty much assume + // any error here isn't because the query failed, it's just that dualstack simply isn't supported on the host. With all + // that in mind, just log as info and not error to let the user know we're falling back. + klog.InfoS("This version of Windows does not support dual-stack, falling back to single-stack", "err", err.Error()) + return false + } + + // check if network is using overlay + hns, _ := newHostNetworkService(hcnImpl) + networkName, err := getNetworkName(networkName) + if err != nil { + klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") + return false + } + networkInfo, err := getNetworkInfo(hns, networkName) + if err != nil { + klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") + return false + } + + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WinOverlay) && isOverlay(networkInfo) { + // Overlay (VXLAN) networks on Windows do not support dual-stack networking today + klog.InfoS("Winoverlay does not support dual-stack, falling back to single-stack") + return false + } + + return true +} diff --git a/pkg/proxy/winkernel/testing/hcnutils_mock.go b/pkg/proxy/winkernel/testing/hcnutils_mock.go index bea3eb1753904..5a724fd799428 100644 --- a/pkg/proxy/winkernel/testing/hcnutils_mock.go +++ b/pkg/proxy/winkernel/testing/hcnutils_mock.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. @@ -34,8 +33,9 @@ var ( ) type HcnMock struct { - supportedFeatures hcn.SupportedFeatures - network *hcn.HostComputeNetwork + supportedFeatures hcn.SupportedFeatures + network *hcn.HostComputeNetwork + ShouldFailDeleteLoadBalancer bool } func (hcnObj HcnMock) generateEndpointGuid() (endpointId string, endpointName string) { @@ -217,6 +217,9 @@ func (hcnObj HcnMock) DeleteLoadBalancer(loadBalancer *hcn.HostComputeLoadBalanc if _, ok := loadbalancerMap[loadBalancer.Id]; !ok { return hcn.LoadBalancerNotFoundError{LoadBalancerId: loadBalancer.Id} } + if hcnObj.ShouldFailDeleteLoadBalancer { + return fmt.Errorf("injected DeleteLoadBalancer failure for %s", loadBalancer.Id) + } delete(loadbalancerMap, loadBalancer.Id) return nil } diff --git a/pkg/quota/v1/evaluator/core/persistent_volume_claims_test.go b/pkg/quota/v1/evaluator/core/persistent_volume_claims_test.go index 9462ecb298609..c5530de5d468b 100644 --- a/pkg/quota/v1/evaluator/core/persistent_volume_claims_test.go +++ b/pkg/quota/v1/evaluator/core/persistent_volume_claims_test.go @@ -146,7 +146,6 @@ func TestPersistentVolumeClaimEvaluatorMatchingScopes(t *testing.T) { } for testName, testCase := range testCases { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true) t.Run(testName, func(t *testing.T) { gotSelectors, err := evaluator.MatchingScopes(testCase.claim, testCase.selectors) if err != nil { diff --git a/pkg/quota/v1/evaluator/core/registry.go b/pkg/quota/v1/evaluator/core/registry.go index c75f877421a0c..9887904d845da 100644 --- a/pkg/quota/v1/evaluator/core/registry.go +++ b/pkg/quota/v1/evaluator/core/registry.go @@ -18,8 +18,10 @@ package core import ( "context" + "fmt" corev1 "k8s.io/api/core/v1" + resourcev1 "k8s.io/api/resource/v1" "k8s.io/apimachinery/pkg/runtime/schema" quota "k8s.io/apiserver/pkg/quota/v1" "k8s.io/apiserver/pkg/quota/v1/generic" @@ -41,14 +43,24 @@ var legacyObjectCountAliases = map[schema.GroupVersionResource]corev1.ResourceNa } // NewEvaluators returns the list of static evaluators that manage more than counts -func NewEvaluators(f quota.ListerForResourceFunc, i informers.SharedInformerFactory) []quota.Evaluator { +func NewEvaluators(f quota.ListerForResourceFunc, i informers.SharedInformerFactory, isEnabled func(schema.GroupVersionResource) bool) ([]quota.Evaluator, error) { + if isEnabled == nil { + isEnabled = func(schema.GroupVersionResource) bool { return true } + } + // these evaluators have special logic - result := []quota.Evaluator{ - NewPodEvaluator(f, clock.RealClock{}), - NewServiceEvaluator(f), - NewPersistentVolumeClaimEvaluator(f), + result := []quota.Evaluator{} + if isEnabled(corev1.SchemeGroupVersion.WithResource("pods")) { + result = append(result, NewPodEvaluator(f, clock.RealClock{})) + } + if isEnabled(corev1.SchemeGroupVersion.WithResource("services")) { + result = append(result, NewServiceEvaluator(f)) } - if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) { + if isEnabled(corev1.SchemeGroupVersion.WithResource("persistentvolumeclaims")) { + result = append(result, NewPersistentVolumeClaimEvaluator(f)) + } + if isEnabled(resourcev1.SchemeGroupVersion.WithResource("resourceclaims")) && utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) { + var claimGetter resourceClaimPodOwnerGetter var podLister corev1listers.PodLister var deviceClassMapping *extendedresourcecache.ExtendedResourceCache if utilfeature.DefaultFeatureGate.Enabled(features.DRAExtendedResource) { @@ -56,10 +68,15 @@ func NewEvaluators(f quota.ListerForResourceFunc, i informers.SharedInformerFact logger := klog.FromContext(context.Background()) deviceClassMapping = extendedresourcecache.NewExtendedResourceCache(logger) if _, err := i.Resource().V1().DeviceClasses().Informer().AddEventHandler(deviceClassMapping); err != nil { - logger.Error(err, "failed to add device class informer event handler") + return nil, fmt.Errorf("failed to add device class informer event handler: %w", err) + } + var err error + claimGetter, err = makeResourceClaimPodOwnerGetter(i.Resource().V1().ResourceClaims()) + if err != nil { + return nil, err } } - result = append(result, NewResourceClaimEvaluator(f, deviceClassMapping, podLister)) + result = append(result, NewResourceClaimEvaluator(f, deviceClassMapping, podLister, claimGetter)) } // these evaluators require an alias for backwards compatibility @@ -67,5 +84,5 @@ func NewEvaluators(f quota.ListerForResourceFunc, i informers.SharedInformerFact result = append(result, generic.NewObjectCountEvaluator(gvr.GroupResource(), generic.ListResourceUsingListerFunc(f, gvr), alias)) } - return result + return result, nil } diff --git a/pkg/quota/v1/evaluator/core/resource_claims.go b/pkg/quota/v1/evaluator/core/resource_claims.go index f9bf5c1498a6f..b4fc8bfe24c6c 100644 --- a/pkg/quota/v1/evaluator/core/resource_claims.go +++ b/pkg/quota/v1/evaluator/core/resource_claims.go @@ -26,11 +26,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission" quota "k8s.io/apiserver/pkg/quota/v1" "k8s.io/apiserver/pkg/quota/v1/generic" utilfeature "k8s.io/apiserver/pkg/util/feature" + v1 "k8s.io/client-go/informers/resource/v1" corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" "k8s.io/dynamic-resource-allocation/deviceclass/extendedresourcecache" resourceinternal "k8s.io/kubernetes/pkg/apis/resource" resourceversioned "k8s.io/kubernetes/pkg/apis/resource/v1" @@ -50,9 +53,9 @@ func V1ResourceByDeviceClass(className string) corev1.ResourceName { } // NewResourceClaimEvaluator returns an evaluator that can evaluate resource claims -func NewResourceClaimEvaluator(f quota.ListerForResourceFunc, m *extendedresourcecache.ExtendedResourceCache, podsGetter corev1listers.PodLister) quota.Evaluator { +func NewResourceClaimEvaluator(f quota.ListerForResourceFunc, m *extendedresourcecache.ExtendedResourceCache, podsGetter corev1listers.PodLister, claimGetter resourceClaimPodOwnerGetter) quota.Evaluator { listFuncByNamespace := generic.ListResourceUsingListerFunc(f, resourceapi.SchemeGroupVersion.WithResource("resourceclaims")) - claimEvaluator := &claimEvaluator{listFuncByNamespace: listFuncByNamespace, deviceClassMapping: m, podsGetter: podsGetter} + claimEvaluator := &claimEvaluator{listFuncByNamespace: listFuncByNamespace, deviceClassMapping: m, podsGetter: podsGetter, claimGetter: claimGetter} return claimEvaluator } @@ -64,6 +67,8 @@ type claimEvaluator struct { deviceClassMapping *extendedresourcecache.ExtendedResourceCache // podsGetter is used to get pods podsGetter corev1listers.PodLister + // claimGetter is used to get claims for extended resources by namespace and pod owner uid + claimGetter resourceClaimPodOwnerGetter } // Constraints verifies that all required resources are present on the item. @@ -167,7 +172,7 @@ func (p *claimEvaluator) getVerifiedPodUsage(claim *resourceapi.ResourceClaim) c if claim.Annotations[resourceapi.ExtendedResourceClaimAnnotation] != "true" { return nil } - controllerRef := metav1.GetControllerOf(claim) + controllerRef := metav1.GetControllerOfNoCopy(claim) if controllerRef == nil { return nil } @@ -187,6 +192,30 @@ func (p *claimEvaluator) getVerifiedPodUsage(claim *resourceapi.ResourceClaim) c if pod.Status.ExtendedResourceClaimStatus != nil && pod.Status.ExtendedResourceClaimStatus.ResourceClaimName != claim.Name { return nil } + // if the pod doesn't identify its extended resource claim, make sure we're the first or only extended resource claim for this pod + if pod.Status.ExtendedResourceClaimStatus == nil { + ownedClaims, err := p.claimGetter(claim.Namespace, pod.UID) + if err != nil { + return nil + } + for _, ownedClaim := range ownedClaims { + switch ownedClaim.CreationTimestamp.Time.Compare(claim.CreationTimestamp.Time) { + case -1: + // There's another owned claim created earlier than this one. + // Don't exempt this one based on pod usage. + return nil + case 0: + if ownedClaim.Name < claim.Name { + // There's another owned claim created at the same time as this one with an earlier name. + // Don't exempt this one based on pod usage. + return nil + } + case 1: + continue + } + } + } + quotaReqs, err := PodUsageFunc(pod, clock.RealClock{}) if err != nil { return nil @@ -288,3 +317,54 @@ func toExternalResourceClaimOrError(obj runtime.Object) (*resourceapi.ResourceCl } return claim, nil } + +const extendedResourceClaimPodOwnerIndexName = "extendedResourceClaimPodUIDOwner" + +func resourceClaimPodOwnerKey(namespace string, podUID types.UID) string { + return namespace + "/" + string(podUID) +} + +type resourceClaimPodOwnerGetter func(namespace string, podUID types.UID) ([]*resourceapi.ResourceClaim, error) + +func makeResourceClaimPodOwnerGetter(resourceClaimInformer v1.ResourceClaimInformer) (resourceClaimPodOwnerGetter, error) { + indexers := cache.Indexers{extendedResourceClaimPodOwnerIndexName: extendedResourceClaimPodUIDOwnerIndex} + if err := resourceClaimInformer.Informer().AddIndexers(indexers); err != nil { + _, exists := resourceClaimInformer.Informer().GetIndexer().GetIndexers()[extendedResourceClaimPodOwnerIndexName] + if !exists { + return nil, fmt.Errorf("failed to add resource claim pod owner indexer: %w", err) + } + } + indexer := resourceClaimInformer.Informer().GetIndexer() + return func(namespace string, podUID types.UID) ([]*resourceapi.ResourceClaim, error) { + objects, err := indexer.ByIndex(extendedResourceClaimPodOwnerIndexName, resourceClaimPodOwnerKey(namespace, podUID)) + if err != nil { + return nil, err + } + claims := make([]*resourceapi.ResourceClaim, 0, len(objects)) + for _, obj := range objects { + if claim, ok := obj.(*resourceapi.ResourceClaim); ok { + claims = append(claims, claim) + } else { + return nil, fmt.Errorf("failed to get resource claim from indexer") + } + } + return claims, nil + }, nil +} +func extendedResourceClaimPodUIDOwnerIndex(obj interface{}) ([]string, error) { + claim, ok := obj.(*resourceapi.ResourceClaim) + if !ok { + return nil, nil + } + if claim.Annotations[resourceapi.ExtendedResourceClaimAnnotation] != "true" { + return nil, nil + } + controllerRef := metav1.GetControllerOfNoCopy(claim) + if controllerRef == nil { + return nil, nil + } + if controllerRef.Kind != "Pod" || controllerRef.APIVersion != "v1" { + return nil, nil + } + return []string{resourceClaimPodOwnerKey(claim.Namespace, controllerRef.UID)}, nil +} diff --git a/pkg/quota/v1/evaluator/core/resource_claims_test.go b/pkg/quota/v1/evaluator/core/resource_claims_test.go index eeefb16fe3247..5ebc6531dbff8 100644 --- a/pkg/quota/v1/evaluator/core/resource_claims_test.go +++ b/pkg/quota/v1/evaluator/core/resource_claims_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission" quota "k8s.io/apiserver/pkg/quota/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -167,6 +168,11 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { }, }, }) + nilStatusExtendedResourceClaimExternal, err := toExternalResourceClaimOrError(nilStatusExtendedResourceClaim) + if err != nil { + t.Fatal(err) + } + nilStatusExtendedResourceClaim.Name = "foo-copy" initExtendedResourceClaim := testResourceClaim("foo", "ns", true, "pod-init", api.ResourceClaimSpec{ Devices: api.DeviceClaim{ Requests: []api.DeviceRequest{ @@ -374,9 +380,13 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { informerFactory := informers.NewSharedInformerFactory(client, 0) deviceclassmapping := extendedresourcecache.NewExtendedResourceCache(logger) if _, err := informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(deviceclassmapping); err != nil { - logger.Error(err, "failed to add device class informer event handler") + t.Fatal(err) + } + var otherOwnedClaims []*resourceapi.ResourceClaim + claimGetter := func(namespace string, podUID types.UID) ([]*resourceapi.ResourceClaim, error) { + return otherOwnedClaims, nil } - evaluatorWithDeviceMapping := NewResourceClaimEvaluator(nil, deviceclassmapping, informerFactory.Core().V1().Pods().Lister()) + evaluatorWithDeviceMapping := NewResourceClaimEvaluator(nil, deviceclassmapping, informerFactory.Core().V1().Pods().Lister(), claimGetter) informerFactory.Start(tCtx.Done()) t.Cleanup(func() { @@ -390,12 +400,13 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { // wait for informer sync time.Sleep(1 * time.Second) - evaluator := NewResourceClaimEvaluator(nil, nil, nil) + evaluator := NewResourceClaimEvaluator(nil, nil, nil, claimGetter) testCases := map[string]struct { - evaluator quota.Evaluator - claim *api.ResourceClaim - usage corev1.ResourceList - errMsg string + evaluator quota.Evaluator + claim *api.ResourceClaim + otherClaims []*resourceapi.ResourceClaim + usage corev1.ResourceList + errMsg string }{ "implicit-extended-resource-claim": { evaluator: evaluatorWithDeviceMapping, @@ -425,7 +436,7 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { "requests.example.com/gpu": resource.MustParse("1"), }, }, - "ni-status-extended-resource-claim": { + "nil-status-extended-resource-claim": { evaluator: evaluatorWithDeviceMapping, claim: nilStatusExtendedResourceClaim, usage: corev1.ResourceList{ @@ -435,6 +446,21 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { "requests.example.com/gpu": resource.MustParse("1"), }, }, + // both claims set the same pod as owner, the second claim cannot get the usage + // subtraction from pod requests. + "nil-status-two-extended-resource-claims": { + evaluator: evaluatorWithDeviceMapping, + claim: nilStatusExtendedResourceClaim, + otherClaims: []*resourceapi.ResourceClaim{ + nilStatusExtendedResourceClaimExternal, + }, + usage: corev1.ResourceList{ + "count/resourceclaims.resource.k8s.io": resource.MustParse("1"), + "gpu.deviceclass.resource.k8s.io/devices": resource.MustParse("2"), + "requests.deviceclass.resource.kubernetes.io/gpu": resource.MustParse("2"), + "requests.example.com/gpu": resource.MustParse("2"), + }, + }, "init-extended-resource-claim": { evaluator: evaluatorWithDeviceMapping, claim: initExtendedResourceClaim, @@ -578,6 +604,7 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { } for testName, testCase := range testCases { t.Run(testName, func(t *testing.T) { + otherOwnedClaims = testCase.otherClaims featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, true) if testCase.evaluator == nil { testCase.evaluator = evaluator @@ -620,7 +647,7 @@ func TestResourceClaimEvaluatorMatchingResources(t *testing.T) { if _, err := informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(deviceclassmapping); err != nil { logger.Error(err, "failed to add device class informer event handler") } - evaluator := NewResourceClaimEvaluator(nil, deviceclassmapping, informerFactory.Core().V1().Pods().Lister()) + evaluator := NewResourceClaimEvaluator(nil, deviceclassmapping, informerFactory.Core().V1().Pods().Lister(), nil) informerFactory.Start(tCtx.Done()) t.Cleanup(func() { @@ -677,7 +704,7 @@ func TestResourceClaimEvaluatorMatchingResources(t *testing.T) { } func TestResourceClaimEvaluatorHandles(t *testing.T) { - evaluator := NewResourceClaimEvaluator(nil, nil, nil) + evaluator := NewResourceClaimEvaluator(nil, nil, nil, nil) testCases := []struct { name string attrs admission.Attributes diff --git a/pkg/quota/v1/install/registry.go b/pkg/quota/v1/install/registry.go index dae800f6e3393..5e5e856f2cecf 100644 --- a/pkg/quota/v1/install/registry.go +++ b/pkg/quota/v1/install/registry.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" quota "k8s.io/apiserver/pkg/quota/v1" "k8s.io/apiserver/pkg/quota/v1/generic" + "k8s.io/apiserver/pkg/server/storage" "k8s.io/client-go/informers" "k8s.io/kubernetes/pkg/apis/authentication" "k8s.io/kubernetes/pkg/apis/authorization" @@ -28,15 +29,25 @@ import ( ) // NewQuotaConfigurationForAdmission returns a quota configuration for admission control. -func NewQuotaConfigurationForAdmission(i informers.SharedInformerFactory) quota.Configuration { - evaluators := core.NewEvaluators(nil, i) - return generic.NewConfiguration(evaluators, DefaultIgnoredResources()) +func NewQuotaConfigurationForAdmission(i informers.SharedInformerFactory, apiResourceConfig storage.APIResourceConfigSource) (quota.Configuration, error) { + var isEnabled func(schema.GroupVersionResource) bool + if apiResourceConfig != nil { + isEnabled = apiResourceConfig.ResourceEnabled + } + evaluators, err := core.NewEvaluators(nil, i, isEnabled) + if err != nil { + return nil, err + } + return generic.NewConfiguration(evaluators, DefaultIgnoredResources()), nil } // NewQuotaConfigurationForControllers returns a quota configuration for controllers. -func NewQuotaConfigurationForControllers(f quota.ListerForResourceFunc, i informers.SharedInformerFactory) quota.Configuration { - evaluators := core.NewEvaluators(f, i) - return generic.NewConfiguration(evaluators, DefaultIgnoredResources()) +func NewQuotaConfigurationForControllers(f quota.ListerForResourceFunc, i informers.SharedInformerFactory) (quota.Configuration, error) { + evaluators, err := core.NewEvaluators(f, i, nil) + if err != nil { + return nil, err + } + return generic.NewConfiguration(evaluators, DefaultIgnoredResources()), nil } // ignoredResources are ignored by quota by default diff --git a/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy.go b/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy.go index 698eb8adf6992..f413e9fc4f692 100644 --- a/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy.go +++ b/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy.go @@ -23,7 +23,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" @@ -74,7 +76,11 @@ func (v *mutatingAdmissionPolicyStrategy) PrepareForUpdate(ctx context.Context, // Validate validates a new MutatingAdmissionPolicy. func (v *mutatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - errs := validation.ValidateMutatingAdmissionPolicy(obj.(*admissionregistration.MutatingAdmissionPolicy)) + ic := obj.(*admissionregistration.MutatingAdmissionPolicy) + errs := validation.ValidateMutatingAdmissionPolicy(ic) + if utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + errs = append(errs, validation.ValidateStaticSuffix(ic.Name, field.NewPath("metadata", "name"))...) + } if len(errs) == 0 { // if the object is well-formed, also authorize the paramKind if err := v.authorizeCreate(ctx, obj); err != nil { @@ -86,6 +92,10 @@ func (v *mutatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runt // WarningsOnCreate returns warnings for the creation of the given object. func (v *mutatingAdmissionPolicyStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + ic := obj.(*admissionregistration.MutatingAdmissionPolicy) + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return validation.WarningsForStaticSuffix(ic.Name) + } return nil } @@ -100,7 +110,9 @@ func (v *mutatingAdmissionPolicyStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (v *mutatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - errs := validation.ValidateMutatingAdmissionPolicyUpdate(obj.(*admissionregistration.MutatingAdmissionPolicy), old.(*admissionregistration.MutatingAdmissionPolicy)) + newIC := obj.(*admissionregistration.MutatingAdmissionPolicy) + oldIC := old.(*admissionregistration.MutatingAdmissionPolicy) + errs := validation.ValidateMutatingAdmissionPolicyUpdate(newIC, oldIC) if len(errs) == 0 { // if the object is well-formed, also authorize the paramKind if err := v.authorizeUpdate(ctx, obj, old); err != nil { @@ -112,7 +124,8 @@ func (v *mutatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, ob // WarningsOnUpdate returns warnings for the given update. func (v *mutatingAdmissionPolicyStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { - return nil + newIC := obj.(*admissionregistration.MutatingAdmissionPolicy) + return validation.WarningsForStaticSuffix(newIC.Name) } // AllowUnconditionalUpdate is the default update policy for MutatingAdmissionPolicy objects. Status update should diff --git a/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy_test.go b/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy_test.go index 2b181cf74a570..9077fe761f04f 100644 --- a/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy_test.go +++ b/pkg/registry/admissionregistration/mutatingadmissionpolicy/strategy_test.go @@ -22,6 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/registry/admissionregistration/resolver" ) @@ -112,3 +115,70 @@ func validMutatingAdmissionPolicy() *admissionregistration.MutatingAdmissionPoli }, } } + +func TestStaticSuffixWarningsAndValidation(t *testing.T) { + strategy := NewStrategy(nil, replicaLimitsResolver) + ctx := genericapirequest.NewDefaultContext() + staticName := "my-policy.static.k8s.io" + + makeConfig := func(name string) *admissionregistration.MutatingAdmissionPolicy { + cfg := validMutatingAdmissionPolicy() + cfg.Name = name + return cfg + } + + t.Run("feature gate disabled warns on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled no warning for normal name", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig("normal-policy")) + if len(warnings) != 0 { + t.Errorf("Expected no warnings for normal name, got: %v", warnings) + } + }) + + t.Run("feature gate enabled rejects static suffix on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + errs := strategy.Validate(ctx, makeConfig(staticName)) + found := false + for _, e := range errs { + if e.Field == "metadata.name" { + found = true + break + } + } + if !found { + t.Error("Expected validation error for .static.k8s.io suffix when feature gate is enabled") + } + }) + + t.Run("feature gate enabled no warning on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) != 0 { + t.Errorf("Expected no warnings when feature gate is enabled (validation handles it), got: %v", warnings) + } + }) + + t.Run("feature gate enabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix on update even when feature gate is enabled") + } + }) +} diff --git a/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy.go b/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy.go index 16be56cbc8a92..575515b1617fb 100644 --- a/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy.go +++ b/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy.go @@ -23,7 +23,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" @@ -82,7 +84,11 @@ func (v *mutatingAdmissionPolicyBindingStrategy) PrepareForUpdate(ctx context.Co // Validate validates a new MutatingAdmissionPolicyBinding. func (v *mutatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - errs := validation.ValidateMutatingAdmissionPolicyBinding(obj.(*admissionregistration.MutatingAdmissionPolicyBinding)) + ic := obj.(*admissionregistration.MutatingAdmissionPolicyBinding) + errs := validation.ValidateMutatingAdmissionPolicyBinding(ic) + if utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + errs = append(errs, validation.ValidateStaticSuffix(ic.Name, field.NewPath("metadata", "name"))...) + } if len(errs) == 0 { // if the object is well-formed, also authorize the paramRef if err := v.authorizeCreate(ctx, obj); err != nil { @@ -94,6 +100,10 @@ func (v *mutatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, o // WarningsOnCreate returns warnings for the creation of the given object. func (v *mutatingAdmissionPolicyBindingStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + ic := obj.(*admissionregistration.MutatingAdmissionPolicyBinding) + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return validation.WarningsForStaticSuffix(ic.Name) + } return nil } @@ -108,7 +118,9 @@ func (v *mutatingAdmissionPolicyBindingStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (v *mutatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - errs := validation.ValidateMutatingAdmissionPolicyBindingUpdate(obj.(*admissionregistration.MutatingAdmissionPolicyBinding), old.(*admissionregistration.MutatingAdmissionPolicyBinding)) + newIC := obj.(*admissionregistration.MutatingAdmissionPolicyBinding) + oldIC := old.(*admissionregistration.MutatingAdmissionPolicyBinding) + errs := validation.ValidateMutatingAdmissionPolicyBindingUpdate(newIC, oldIC) if len(errs) == 0 { // if the object is well-formed, also authorize the paramRef if err := v.authorizeUpdate(ctx, obj, old); err != nil { @@ -120,7 +132,8 @@ func (v *mutatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Cont // WarningsOnUpdate returns warnings for the given update. func (v *mutatingAdmissionPolicyBindingStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { - return nil + newIC := obj.(*admissionregistration.MutatingAdmissionPolicyBinding) + return validation.WarningsForStaticSuffix(newIC.Name) } // AllowUnconditionalUpdate is the default update policy for MutatingAdmissionPolicyBinding objects. Status update should diff --git a/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy_test.go b/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy_test.go index ddc2f010c20c6..adfc88b7f7977 100644 --- a/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy_test.go +++ b/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/strategy_test.go @@ -22,6 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/registry/admissionregistration/resolver" "k8s.io/kubernetes/pkg/apis/admissionregistration" @@ -125,3 +128,74 @@ func validPolicyBindings() []*admissionregistration.MutatingAdmissionPolicyBindi }, } } + +func validPolicyBinding() *admissionregistration.MutatingAdmissionPolicyBinding { + return validPolicyBindings()[0] +} + +func TestStaticSuffixWarningsAndValidation(t *testing.T) { + strategy := NewStrategy(nil, nil, replicaLimitsResolver) + ctx := genericapirequest.NewDefaultContext() + staticName := "my-binding.static.k8s.io" + + makeConfig := func(name string) *admissionregistration.MutatingAdmissionPolicyBinding { + cfg := validPolicyBinding() + cfg.Name = name + return cfg + } + + t.Run("feature gate disabled warns on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled no warning for normal name", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig("normal-binding")) + if len(warnings) != 0 { + t.Errorf("Expected no warnings for normal name, got: %v", warnings) + } + }) + + t.Run("feature gate enabled rejects static suffix on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + errs := strategy.Validate(ctx, makeConfig(staticName)) + found := false + for _, e := range errs { + if e.Field == "metadata.name" { + found = true + break + } + } + if !found { + t.Error("Expected validation error for .static.k8s.io suffix when feature gate is enabled") + } + }) + + t.Run("feature gate enabled no warning on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) != 0 { + t.Errorf("Expected no warnings when feature gate is enabled (validation handles it), got: %v", warnings) + } + }) + + t.Run("feature gate enabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix on update even when feature gate is enabled") + } + }) +} diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go index fb8d672b0c40a..52e2cbea4aab5 100644 --- a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go @@ -22,7 +22,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" @@ -50,6 +52,10 @@ func (mutatingWebhookConfigurationStrategy) PrepareForCreate(ctx context.Context // WarningsOnCreate returns warnings for the creation of the given object. func (mutatingWebhookConfigurationStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + ic := obj.(*admissionregistration.MutatingWebhookConfiguration) + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return validation.WarningsForStaticSuffix(ic.Name) + } return nil } @@ -69,7 +75,11 @@ func (mutatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Context // Validate validates a new mutatingWebhookConfiguration. func (mutatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { ic := obj.(*admissionregistration.MutatingWebhookConfiguration) - return validation.ValidateMutatingWebhookConfiguration(ic) + errs := validation.ValidateMutatingWebhookConfiguration(ic) + if utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + errs = append(errs, validation.ValidateStaticSuffix(ic.Name, field.NewPath("metadata", "name"))...) + } + return errs } // Canonicalize normalizes the object after validation. @@ -83,12 +93,16 @@ func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration)) + newIC := obj.(*admissionregistration.MutatingWebhookConfiguration) + oldIC := old.(*admissionregistration.MutatingWebhookConfiguration) + errs := validation.ValidateMutatingWebhookConfigurationUpdate(newIC, oldIC) + return errs } // WarningsOnUpdate returns warnings for the given update. func (mutatingWebhookConfigurationStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { - return nil + newIC := obj.(*admissionregistration.MutatingWebhookConfiguration) + return validation.WarningsForStaticSuffix(newIC.Name) } // AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy_test.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy_test.go new file mode 100644 index 0000000000000..940b0e66a86b7 --- /dev/null +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2021 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 mutatingwebhookconfiguration + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +func validMutatingWebhookConfiguration() *admissionregistration.MutatingWebhookConfiguration { + ignore := admissionregistration.Ignore + exact := admissionregistration.Exact + thirty := int32(30) + none := admissionregistration.SideEffectClassNone + servicePath := "/" + return &admissionregistration.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Webhooks: []admissionregistration.MutatingWebhook{{ + Name: "foo.example.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Name: "foo", + Namespace: "bar", + Path: &servicePath, + Port: 443, + }, + }, + FailurePolicy: &ignore, + MatchPolicy: &exact, + TimeoutSeconds: &thirty, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + SideEffects: &none, + AdmissionReviewVersions: []string{"v1beta1"}, + }}, + } +} + +func TestStaticSuffixWarningsAndValidation(t *testing.T) { + ctx := genericapirequest.NewDefaultContext() + staticName := "my-webhook.static.k8s.io" + + makeConfig := func(name string) *admissionregistration.MutatingWebhookConfiguration { + cfg := validMutatingWebhookConfiguration() + cfg.Name = name + return cfg + } + + t.Run("feature gate disabled warns on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := Strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := Strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled no warning for normal name", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := Strategy.WarningsOnCreate(ctx, makeConfig("normal-webhook")) + if len(warnings) != 0 { + t.Errorf("Expected no warnings for normal name, got: %v", warnings) + } + }) + + t.Run("feature gate enabled rejects static suffix on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + errs := Strategy.Validate(ctx, makeConfig(staticName)) + found := false + for _, e := range errs { + if e.Field == "metadata.name" { + found = true + break + } + } + if !found { + t.Error("Expected validation error for .static.k8s.io suffix when feature gate is enabled") + } + }) + + t.Run("feature gate enabled no warning on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := Strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) != 0 { + t.Errorf("Expected no warnings when feature gate is enabled (validation handles it), got: %v", warnings) + } + }) + + t.Run("feature gate enabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := Strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix on update even when feature gate is enabled") + } + }) +} diff --git a/pkg/registry/admissionregistration/rest/storage_apiserver.go b/pkg/registry/admissionregistration/rest/storage_apiserver.go index 4d46bd99f41b4..4e606283feab7 100644 --- a/pkg/registry/admissionregistration/rest/storage_apiserver.go +++ b/pkg/registry/admissionregistration/rest/storage_apiserver.go @@ -116,6 +116,25 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API storage[resource] = policyBindingStorage } + // mutatingadmissionpolicies + if resource := "mutatingadmissionpolicies"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1.SchemeGroupVersion.WithResource(resource)) { + policyStorage, err := mutatingadmissionpolicystorage.NewREST(restOptionsGetter, p.Authorizer, r) + if err != nil { + return storage, err + } + policyGetter = policyStorage + storage[resource] = policyStorage + } + + // mutatingadmissionpolicybindings + if resource := "mutatingadmissionpolicybindings"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1.SchemeGroupVersion.WithResource(resource)) { + mutationpolicybindingstorage, err := mutationpolicybindingstorage.NewREST(restOptionsGetter, p.Authorizer, &mutationpolicybindingstorage.DefaultPolicyGetter{Getter: policyGetter}, r) + if err != nil { + return storage, err + } + storage[resource] = mutationpolicybindingstorage + } + return storage, nil } diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy.go b/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy.go index 7b31c9a43fad2..ce707d1074eb8 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy.go @@ -26,7 +26,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" @@ -81,7 +83,11 @@ func (v *validatingAdmissionPolicyStrategy) PrepareForUpdate(ctx context.Context // Validate validates a new validatingAdmissionPolicy. func (v *validatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - errs := validation.ValidateValidatingAdmissionPolicy(obj.(*admissionregistration.ValidatingAdmissionPolicy)) + ic := obj.(*admissionregistration.ValidatingAdmissionPolicy) + errs := validation.ValidateValidatingAdmissionPolicy(ic) + if utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + errs = append(errs, validation.ValidateStaticSuffix(ic.Name, field.NewPath("metadata", "name"))...) + } if len(errs) == 0 { // if the object is well-formed, also authorize the paramKind if err := v.authorizeCreate(ctx, obj); err != nil { @@ -93,6 +99,10 @@ func (v *validatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj ru // WarningsOnCreate returns warnings for the creation of the given object. func (v *validatingAdmissionPolicyStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + ic := obj.(*admissionregistration.ValidatingAdmissionPolicy) + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return validation.WarningsForStaticSuffix(ic.Name) + } return nil } @@ -107,7 +117,9 @@ func (v *validatingAdmissionPolicyStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (v *validatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - errs := validation.ValidateValidatingAdmissionPolicyUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicy), old.(*admissionregistration.ValidatingAdmissionPolicy)) + newIC := obj.(*admissionregistration.ValidatingAdmissionPolicy) + oldIC := old.(*admissionregistration.ValidatingAdmissionPolicy) + errs := validation.ValidateValidatingAdmissionPolicyUpdate(newIC, oldIC) if len(errs) == 0 { // if the object is well-formed, also authorize the paramKind if err := v.authorizeUpdate(ctx, obj, old); err != nil { @@ -119,7 +131,8 @@ func (v *validatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, // WarningsOnUpdate returns warnings for the given update. func (v *validatingAdmissionPolicyStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { - return nil + newIC := obj.(*admissionregistration.ValidatingAdmissionPolicy) + return validation.WarningsForStaticSuffix(newIC.Name) } // AllowUnconditionalUpdate is the default update policy for validatingAdmissionPolicy objects. Status update should diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy_test.go b/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy_test.go index 032825967798d..ddc05195f673d 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy_test.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicy/strategy_test.go @@ -22,6 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/registry/admissionregistration/resolver" ) @@ -104,3 +107,70 @@ func validValidatingAdmissionPolicy() *admissionregistration.ValidatingAdmission }, } } + +func TestStaticSuffixWarningsAndValidation(t *testing.T) { + strategy := NewStrategy(nil, replicaLimitsResolver) + ctx := genericapirequest.NewDefaultContext() + staticName := "my-policy.static.k8s.io" + + makeConfig := func(name string) *admissionregistration.ValidatingAdmissionPolicy { + cfg := validValidatingAdmissionPolicy() + cfg.Name = name + return cfg + } + + t.Run("feature gate disabled warns on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled no warning for normal name", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig("normal-policy")) + if len(warnings) != 0 { + t.Errorf("Expected no warnings for normal name, got: %v", warnings) + } + }) + + t.Run("feature gate enabled rejects static suffix on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + errs := strategy.Validate(ctx, makeConfig(staticName)) + found := false + for _, e := range errs { + if e.Field == "metadata.name" { + found = true + break + } + } + if !found { + t.Error("Expected validation error for .static.k8s.io suffix when feature gate is enabled") + } + }) + + t.Run("feature gate enabled no warning on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) != 0 { + t.Errorf("Expected no warnings when feature gate is enabled (validation handles it), got: %v", warnings) + } + }) + + t.Run("feature gate enabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix on update even when feature gate is enabled") + } + }) +} diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go new file mode 100644 index 0000000000000..5b816ec23d1f5 --- /dev/null +++ b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go @@ -0,0 +1,152 @@ +/* +Copyright 2025 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 validatingadmissionpolicybinding + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/admissionregistration" + "testing" +) + +var apiVersions = []string{"v1beta1", "v1", "v1alpha1"} + +func TestDeclarativeValidate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidate(t, apiVersion) + }) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "admissionregistration.k8s.io", + APIVersion: apiVersion, + Resource: "validatingadmissionpolicybindings", + IsResourceRequest: true, + Verb: "create", + }) + + testCases := map[string]struct { + input admissionregistration.ValidatingAdmissionPolicyBinding + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidBinding(), + }, + "spec.policyName is required": { + input: mkValidBinding(tweakPolicyName("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "policyName"), "").MarkAlpha(), + }, + }, + "spec.validationActions is required": { + input: mkValidBinding(tweakValidateActions([]admissionregistration.ValidationAction{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "validationActions"), "").MarkAlpha(), + }, + }, + // TODO: Add more test cases + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + testCases := map[string]struct { + oldObj admissionregistration.ValidatingAdmissionPolicyBinding + updateObj admissionregistration.ValidatingAdmissionPolicyBinding + expectedErrs field.ErrorList + }{ + "valid update": { + oldObj: mkValidBinding(), + updateObj: mkValidBinding(), + }, + "update with empty spec.policyName": { + oldObj: mkValidBinding(func(obj *admissionregistration.ValidatingAdmissionPolicyBinding) { obj.ResourceVersion = "1" }), + updateObj: mkValidBinding(tweakPolicyName("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "policyName"), "").MarkAlpha(), + }, + }, + "update with empty spec.validationActions": { + oldObj: mkValidBinding(), + updateObj: mkValidBinding(tweakValidateActions([]admissionregistration.ValidationAction{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "validationActions"), "").MarkAlpha(), + }, + }, + // TODO: Add more test cases + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "admissionregistration.k8s.io", + APIVersion: apiVersion, + Resource: "validatingadmissionpolicybindings", + Name: "valid-binding", + IsResourceRequest: true, + Verb: "update", + }) + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func mkValidBinding(tweaks ...func(obj *admissionregistration.ValidatingAdmissionPolicyBinding)) admissionregistration.ValidatingAdmissionPolicyBinding { + obj := admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-binding", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "test-policy", + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + }, + } + obj.ResourceVersion = "1" + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +func tweakPolicyName(policyName string) func(obj *admissionregistration.ValidatingAdmissionPolicyBinding) { + return func(obj *admissionregistration.ValidatingAdmissionPolicyBinding) { + obj.Spec.PolicyName = policyName + } +} + +func tweakValidateActions(actions []admissionregistration.ValidationAction) func(*admissionregistration.ValidatingAdmissionPolicyBinding) { + return func(obj *admissionregistration.ValidatingAdmissionPolicyBinding) { + obj.Spec.ValidationActions = actions + } +} diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy.go b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy.go index 7fa86ef1d909a..c8fe6897be434 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy.go @@ -18,12 +18,16 @@ package validatingadmissionpolicybinding import ( "context" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apiserver/pkg/registry/rest" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" @@ -39,6 +43,8 @@ type validatingAdmissionPolicyBindingStrategy struct { resourceResolver resolver.ResourceResolver } +var Strategy = validatingAdmissionPolicyBindingStrategy{} + type PolicyGetter interface { // GetValidatingAdmissionPolicy returns a GetValidatingAdmissionPolicy // by its name. There is no namespace because it is cluster-scoped. @@ -82,18 +88,26 @@ func (v *validatingAdmissionPolicyBindingStrategy) PrepareForUpdate(ctx context. // Validate validates a new ValidatingAdmissionPolicyBinding. func (v *validatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - errs := validation.ValidateValidatingAdmissionPolicyBinding(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)) + ic := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding) + errs := validation.ValidateValidatingAdmissionPolicyBinding(ic) + if utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + errs = append(errs, validation.ValidateStaticSuffix(ic.Name, field.NewPath("metadata", "name"))...) + } if len(errs) == 0 { // if the object is well-formed, also authorize the paramRef if err := v.authorizeCreate(ctx, obj); err != nil { errs = append(errs, field.Forbidden(field.NewPath("spec", "paramRef"), err.Error())) } } - return errs + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, errs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. func (v *validatingAdmissionPolicyBindingStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + ic := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding) + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return validation.WarningsForStaticSuffix(ic.Name) + } return nil } @@ -108,19 +122,22 @@ func (v *validatingAdmissionPolicyBindingStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (v *validatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - errs := validation.ValidateValidatingAdmissionPolicyBindingUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding), old.(*admissionregistration.ValidatingAdmissionPolicyBinding)) + newIC := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding) + oldIC := old.(*admissionregistration.ValidatingAdmissionPolicyBinding) + errs := validation.ValidateValidatingAdmissionPolicyBindingUpdate(newIC, oldIC) if len(errs) == 0 { // if the object is well-formed, also authorize the paramRef if err := v.authorizeUpdate(ctx, obj, old); err != nil { errs = append(errs, field.Forbidden(field.NewPath("spec", "paramRef"), err.Error())) } } - return errs + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, errs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. func (v *validatingAdmissionPolicyBindingStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { - return nil + newIC := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding) + return validation.WarningsForStaticSuffix(newIC.Name) } // AllowUnconditionalUpdate is the default update policy for ValidatingAdmissionPolicyBinding objects. Status update should diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy_test.go b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy_test.go index cf26c09075e81..44e9c7dc6289b 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy_test.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/strategy_test.go @@ -22,6 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/registry/admissionregistration/resolver" "k8s.io/kubernetes/pkg/apis/admissionregistration" @@ -129,3 +132,74 @@ func validPolicyBindings() []*admissionregistration.ValidatingAdmissionPolicyBin }, } } + +func validPolicyBinding() *admissionregistration.ValidatingAdmissionPolicyBinding { + return validPolicyBindings()[0] +} + +func TestStaticSuffixWarningsAndValidation(t *testing.T) { + strategy := NewStrategy(nil, nil, replicaLimitsResolver) + ctx := genericapirequest.NewDefaultContext() + staticName := "my-binding.static.k8s.io" + + makeConfig := func(name string) *admissionregistration.ValidatingAdmissionPolicyBinding { + cfg := validPolicyBinding() + cfg.Name = name + return cfg + } + + t.Run("feature gate disabled warns on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled no warning for normal name", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := strategy.WarningsOnCreate(ctx, makeConfig("normal-binding")) + if len(warnings) != 0 { + t.Errorf("Expected no warnings for normal name, got: %v", warnings) + } + }) + + t.Run("feature gate enabled rejects static suffix on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + errs := strategy.Validate(ctx, makeConfig(staticName)) + found := false + for _, e := range errs { + if e.Field == "metadata.name" { + found = true + break + } + } + if !found { + t.Error("Expected validation error for .static.k8s.io suffix when feature gate is enabled") + } + }) + + t.Run("feature gate enabled no warning on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) != 0 { + t.Errorf("Expected no warnings when feature gate is enabled (validation handles it), got: %v", warnings) + } + }) + + t.Run("feature gate enabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix on update even when feature gate is enabled") + } + }) +} diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go index 93e83833616ce..d206d36bf6c99 100644 --- a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go @@ -22,7 +22,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" @@ -63,11 +65,20 @@ func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Conte // Validate validates a new validatingWebhookConfiguration. func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - return validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration)) + ic := obj.(*admissionregistration.ValidatingWebhookConfiguration) + errs := validation.ValidateValidatingWebhookConfiguration(ic) + if utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + errs = append(errs, validation.ValidateStaticSuffix(ic.Name, field.NewPath("metadata", "name"))...) + } + return errs } // WarningsOnCreate returns warnings for the creation of the given object. func (validatingWebhookConfigurationStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + ic := obj.(*admissionregistration.ValidatingWebhookConfiguration) + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return validation.WarningsForStaticSuffix(ic.Name) + } return nil } @@ -82,12 +93,16 @@ func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration)) + newIC := obj.(*admissionregistration.ValidatingWebhookConfiguration) + oldIC := old.(*admissionregistration.ValidatingWebhookConfiguration) + errs := validation.ValidateValidatingWebhookConfigurationUpdate(newIC, oldIC) + return errs } // WarningsOnUpdate returns warnings for the given update. func (validatingWebhookConfigurationStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { - return nil + newIC := obj.(*admissionregistration.ValidatingWebhookConfiguration) + return validation.WarningsForStaticSuffix(newIC.Name) } // AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy_test.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy_test.go index 0d5408d2892f6..9dffdebe248d7 100644 --- a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy_test.go +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy_test.go @@ -21,6 +21,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/apis/admissionregistration" ) @@ -78,3 +81,69 @@ func validValidatingWebhookConfiguration() *admissionregistration.ValidatingWebh }}, } } + +func TestStaticSuffixWarningsAndValidation(t *testing.T) { + ctx := genericapirequest.NewDefaultContext() + staticName := "my-webhook.static.k8s.io" + + makeConfig := func(name string) *admissionregistration.ValidatingWebhookConfiguration { + cfg := validValidatingWebhookConfiguration() + cfg.Name = name + return cfg + } + + t.Run("feature gate disabled warns on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := Strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := Strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix when feature gate is disabled") + } + }) + + t.Run("feature gate disabled no warning for normal name", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, false) + warnings := Strategy.WarningsOnCreate(ctx, makeConfig("normal-webhook")) + if len(warnings) != 0 { + t.Errorf("Expected no warnings for normal name, got: %v", warnings) + } + }) + + t.Run("feature gate enabled rejects static suffix on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + errs := Strategy.Validate(ctx, makeConfig(staticName)) + found := false + for _, e := range errs { + if e.Field == "metadata.name" { + found = true + break + } + } + if !found { + t.Error("Expected validation error for .static.k8s.io suffix when feature gate is enabled") + } + }) + + t.Run("feature gate enabled no warning on create", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := Strategy.WarningsOnCreate(ctx, makeConfig(staticName)) + if len(warnings) != 0 { + t.Errorf("Expected no warnings when feature gate is enabled (validation handles it), got: %v", warnings) + } + }) + + t.Run("feature gate enabled warns on update", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, true) + warnings := Strategy.WarningsOnUpdate(ctx, makeConfig(staticName), makeConfig(staticName)) + if len(warnings) == 0 { + t.Error("Expected warning for .static.k8s.io suffix on update even when feature gate is enabled") + } + }) +} diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/declarative_validation_test.go b/pkg/registry/autoscaling/horizontalpodautoscaler/declarative_validation_test.go new file mode 100644 index 0000000000000..8590012ddd44d --- /dev/null +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/declarative_validation_test.go @@ -0,0 +1,219 @@ +/* +Copyright 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 horizontalpodautoscaler + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/features" + "k8s.io/utils/ptr" +) + +var apiVersions = []string{"v1", "v2"} + +func TestDeclarativeValidate(t *testing.T) { + for _, apiVersion := range apiVersions { + testDeclarativeValidate(t, apiVersion) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "autoscaling", + APIVersion: apiVersion, + Resource: "horizontalpodautoscalers", + }) + testCases := map[string]struct { + input api.HorizontalPodAutoscaler + expectedErrs field.ErrorList + enableScaleToZero bool + }{ + "valid: minReplicas = 5": { + input: makeValidHPA(tweakMinReplicas(5)), + }, + "valid: minReplicas = 0 (gate enabled)": { + input: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)), + enableScaleToZero: true, + }, + "valid: minReplicas not set (nil)": { + input: makeValidHPA(), // Default, no minReplicas set + }, + "invalid: maxReplicas = 0 (required)": { + input: makeValidHPA(tweakMaxReplicas(0)), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "maxReplicas"), "").MarkAlpha(), + }, + }, + "invalid: maxReplicas negative": { + input: makeValidHPA(tweakMaxReplicas(-1)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "maxReplicas"), int32(-1), "must be greater than or equal to 1").WithOrigin("minimum").MarkAlpha(), + }, + }, + "invalid: minReplicas = 0 (gate disabled)": { + input: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "minReplicas"), int32(0), "must be greater than or equal to 1").WithOrigin("minimum").MarkAlpha(), + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, tc.enableScaleToZero) + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + testCases := map[string]struct { + oldObj api.HorizontalPodAutoscaler + updateObj api.HorizontalPodAutoscaler + expectedErrs field.ErrorList + enableScaleToZero bool + }{ + "valid update": { + oldObj: makeValidHPA(), + updateObj: makeValidHPA(tweakMaxReplicas(20)), + }, + "valid update: change minReplicas": { + oldObj: makeValidHPA(tweakMinReplicas(1)), + updateObj: makeValidHPA(tweakMinReplicas(5)), + }, + "valid update: minReplicas 1 -> 0 (gate enabled)": { + oldObj: makeValidHPA(tweakMinReplicas(1), tweakMetrics(validScaleToZeroMetrics...)), + updateObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)), + enableScaleToZero: true, + }, + "invalid update: maxReplicas = 0 (required)": { + oldObj: makeValidHPA(), + updateObj: makeValidHPA(tweakMaxReplicas(0)), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "maxReplicas"), "").MarkAlpha(), + }, + }, + "invalid update: maxReplicas negative": { + oldObj: makeValidHPA(), + updateObj: makeValidHPA(tweakMaxReplicas(-1)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "maxReplicas"), int32(-1), "must be greater than or equal to 1").WithOrigin("minimum").MarkAlpha(), + }, + }, + "invalid update: minReplicas 1 -> 0 (gate disabled)": { + oldObj: makeValidHPA(tweakMinReplicas(1), tweakMetrics(validScaleToZeroMetrics...)), + updateObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "minReplicas"), int32(0), "must be greater than or equal to 1").WithOrigin("minimum").MarkAlpha(), + }, + }, + "valid update: ratcheting minReplicas=0 when gate disabled": { + oldObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)), + updateObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...), tweakMaxReplicas(20)), + enableScaleToZero: false, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, tc.enableScaleToZero) + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "autoscaling", + APIVersion: apiVersion, + Resource: "horizontalpodautoscalers", + Name: "test-hpa", + IsResourceRequest: true, + Verb: "update", + }) + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func makeValidHPA(mutators ...func(*api.HorizontalPodAutoscaler)) api.HorizontalPodAutoscaler { + hpa := api.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hpa", + Namespace: "default", + ResourceVersion: "1", + }, + Spec: api.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: api.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "test-deployment", + APIVersion: "apps/v1", + }, + MaxReplicas: 10, + }, + } + for _, mutate := range mutators { + mutate(&hpa) + } + return hpa +} + +func tweakMinReplicas(replicas int32) func(*api.HorizontalPodAutoscaler) { + return func(hpa *api.HorizontalPodAutoscaler) { + hpa.Spec.MinReplicas = &replicas + } +} + +func tweakMaxReplicas(replicas int32) func(*api.HorizontalPodAutoscaler) { + return func(hpa *api.HorizontalPodAutoscaler) { + hpa.Spec.MaxReplicas = replicas + } +} + +func tweakMetrics(metrics ...api.MetricSpec) func(*api.HorizontalPodAutoscaler) { + return func(hpa *api.HorizontalPodAutoscaler) { + hpa.Spec.Metrics = metrics + } +} + +var validScaleToZeroMetrics = []api.MetricSpec{ + { + Type: api.ObjectMetricSourceType, + Object: &api.ObjectMetricSource{ + Metric: api.MetricIdentifier{ + Name: "requests-per-second", + }, + Target: api.MetricTarget{ + Type: api.ValueMetricType, + Value: ptr.To(resource.MustParse("10k")), + }, + DescribedObject: api.CrossVersionObjectReference{ + Kind: "Ingress", + Name: "main-route", + APIVersion: "networking.k8s.io/v1", + }, + }, + }, +} diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go index eaaaf747addd0..a10f3fa49c5df 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go @@ -19,8 +19,10 @@ package horizontalpodautoscaler import ( "context" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -55,12 +57,6 @@ func (autoscalerStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.S "autoscaling/v2": fieldpath.NewSet( fieldpath.MakePathOrDie("status"), ), - "autoscaling/v2beta1": fieldpath.NewSet( - fieldpath.MakePathOrDie("status"), - ), - "autoscaling/v2beta2": fieldpath.NewSet( - fieldpath.MakePathOrDie("status"), - ), } return fields @@ -80,7 +76,12 @@ func (autoscalerStrategy) PrepareForCreate(ctx context.Context, obj runtime.Obje func (autoscalerStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { autoscaler := obj.(*autoscaling.HorizontalPodAutoscaler) opts := validationOptionsForHorizontalPodAutoscaler(autoscaler, nil) - return validation.ValidateHorizontalPodAutoscaler(autoscaler, opts) + allErrs := validation.ValidateHorizontalPodAutoscaler(autoscaler, opts) + var options []string + if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) { + options = append(options, "HPAScaleToZero") + } + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, autoscaler, nil, allErrs, operation.Create, rest.WithOptions(options)) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -112,7 +113,13 @@ func (autoscalerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O newHPA := obj.(*autoscaling.HorizontalPodAutoscaler) oldHPA := old.(*autoscaling.HorizontalPodAutoscaler) opts := validationOptionsForHorizontalPodAutoscaler(newHPA, oldHPA) - return validation.ValidateHorizontalPodAutoscalerUpdate(newHPA, oldHPA, opts) + errs := validation.ValidateHorizontalPodAutoscalerUpdate(newHPA, oldHPA, opts) + var options []string + oldHasZeroMinReplicas := oldHPA.Spec.MinReplicas != nil && *oldHPA.Spec.MinReplicas == 0 + if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) || oldHasZeroMinReplicas { + options = append(options, "HPAScaleToZero") + } + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newHPA, oldHPA, errs, operation.Update, rest.WithOptions(options)) } // WarningsOnUpdate returns warnings for the given update. @@ -141,12 +148,6 @@ func (autoscalerStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*field "autoscaling/v2": fieldpath.NewSet( fieldpath.MakePathOrDie("spec"), ), - "autoscaling/v2beta1": fieldpath.NewSet( - fieldpath.MakePathOrDie("spec"), - ), - "autoscaling/v2beta2": fieldpath.NewSet( - fieldpath.MakePathOrDie("spec"), - ), } return fields diff --git a/pkg/registry/batch/cronjob/declarative_validation_test.go b/pkg/registry/batch/cronjob/declarative_validation_test.go new file mode 100644 index 0000000000000..85e4ffba4740e --- /dev/null +++ b/pkg/registry/batch/cronjob/declarative_validation_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2025 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 cronjob + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/batch" +) + +var apiVersions = []string{"v1", "v1beta1"} + +func TestDeclarativeValidateForDeclarative(t *testing.T) { + for _, apiVersion := range apiVersions { + testDeclarativeValidateForDeclarative(t, apiVersion) + } +} + +func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "batch", + APIVersion: apiVersion, + }) + testCases := map[string]struct { + input batch.CronJob + expectedErrs field.ErrorList + }{ + "valid": { + input: mkCronJob(), + }, + "schedule: empty": { + input: mkCronJob(tweakSchedule("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "schedule"), "").MarkAlpha(), + }, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestValidateUpdateForDeclarative(t *testing.T) { + for _, apiVersion := range apiVersions { + testValidateUpdateForDeclarative(t, apiVersion) + } +} + +func testValidateUpdateForDeclarative(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "batch", + APIVersion: apiVersion, + }) + testCases := map[string]struct { + old batch.CronJob + update batch.CronJob + expectedErrs field.ErrorList + }{ + "valid (no changes)": { + old: mkCronJob(), + update: mkCronJob(), + }, + + "schedule: updated to empty": { + old: mkCronJob(), + update: mkCronJob(tweakSchedule("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "schedule"), "").MarkAlpha(), + }, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + tc.old.ResourceVersion = "1" + tc.update.ResourceVersion = "2" + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func mkCronJob(mutators ...func(*batch.CronJob)) batch.CronJob { + job := batch.CronJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-class", + Namespace: "test-namespace", + }, + Spec: validCronjobSpec, + } + + for _, mutate := range mutators { + mutate(&job) + } + + return job +} + +func tweakSchedule(schedule string) func(*batch.CronJob) { + return func(job *batch.CronJob) { + job.Spec.Schedule = schedule + } +} diff --git a/pkg/registry/batch/cronjob/strategy.go b/pkg/registry/batch/cronjob/strategy.go index 650b13bcf3977..50075094bc2c5 100644 --- a/pkg/registry/batch/cronjob/strategy.go +++ b/pkg/registry/batch/cronjob/strategy.go @@ -23,6 +23,7 @@ import ( batchv1beta1 "k8s.io/api/batch/v1beta1" apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilvalidation "k8s.io/apimachinery/pkg/util/validation" @@ -112,7 +113,8 @@ func (cronJobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob func (cronJobStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { cronJob := obj.(*batch.CronJob) opts := pod.GetValidationOptionsFromPodTemplate(&cronJob.Spec.JobTemplate.Spec.Template, nil) - return batchvalidation.ValidateCronJobCreate(cronJob, opts) + allErrs := batchvalidation.ValidateCronJobCreate(cronJob, opts) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, allErrs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -145,7 +147,8 @@ func (cronJobStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obje oldCronJob := old.(*batch.CronJob) opts := pod.GetValidationOptionsFromPodTemplate(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template) - return batchvalidation.ValidateCronJobUpdate(newCronJob, oldCronJob, opts) + allErrs := batchvalidation.ValidateCronJobUpdate(newCronJob, oldCronJob, opts) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newCronJob, oldCronJob, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/batch/job/strategy.go b/pkg/registry/batch/job/strategy.go index d0b9f4f3d8a43..5d55bbafe0385 100644 --- a/pkg/registry/batch/job/strategy.go +++ b/pkg/registry/batch/job/strategy.go @@ -172,6 +172,16 @@ func (jobStrategy) Validate(ctx context.Context, obj runtime.Object) field.Error return batchvalidation.ValidateJob(job, opts) } +// shouldAllowMutablePodTemplate returns true if the Job's pod template should +// be mutable. This is safe for suspended jobs with no active pods that either +// have never started or have the JobSuspended condition set. +func shouldAllowMutablePodTemplate(job *batch.Job) bool { + suspended := job.Spec.Suspend != nil && *job.Spec.Suspend + notStarted := job.Status.StartTime == nil + hasSuspendedCondition := batchvalidation.IsConditionTrue(job.Status.Conditions, batch.JobSuspended) + return suspended && (notStarted || hasSuspendedCondition) && job.Status.Active == 0 +} + func validationOptionsForJob(newJob, oldJob *batch.Job) batchvalidation.JobValidationOptions { var newPodTemplate, oldPodTemplate *core.PodTemplateSpec if newJob != nil { @@ -191,11 +201,12 @@ func validationOptionsForJob(newJob, oldJob *batch.Job) batchvalidation.JobValid suspended := oldJob.Spec.Suspend != nil && *oldJob.Spec.Suspend notStarted := oldJob.Status.StartTime == nil opts.AllowMutableSchedulingDirectives = suspended && notStarted + allowMutablePodTemplate := shouldAllowMutablePodTemplate(oldJob) if utilfeature.DefaultFeatureGate.Enabled(features.MutablePodResourcesForSuspendedJobs) { - opts.AllowMutablePodResources = batchvalidation.IsConditionTrue(oldJob.Status.Conditions, batch.JobSuspended) && oldJob.Status.Active == 0 + opts.AllowMutablePodResources = allowMutablePodTemplate } if utilfeature.DefaultFeatureGate.Enabled(features.MutableSchedulingDirectivesForSuspendedJobs) { - opts.AllowMutableSchedulingDirectives = suspended && batchvalidation.IsConditionTrue(oldJob.Status.Conditions, batch.JobSuspended) && oldJob.Status.Active == 0 + opts.AllowMutableSchedulingDirectives = allowMutablePodTemplate } // Validation should not fail jobs if they don't have the new labels. // This can be removed once we have high confidence that both labels exist (1.30 at least) diff --git a/pkg/registry/batch/job/strategy_test.go b/pkg/registry/batch/job/strategy_test.go index 58da8cd5a5776..b947159789a75 100644 --- a/pkg/registry/batch/job/strategy_test.go +++ b/pkg/registry/batch/job/strategy_test.go @@ -1447,6 +1447,66 @@ func TestJobStrategy_ValidateUpdate_MutablePodResources(t *testing.T) { {Type: field.ErrorTypeInvalid, Field: "spec.template.spec"}, }, }, + "feature gate enabled - pod resource update allowed for suspended Job without Suspended condition but never started": { + enableFeatureGate: true, + job: &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myjob", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "0", + }, + Spec: batch.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + ManualSelector: ptr.To(true), + Parallelism: ptr.To[int32](1), + Suspend: ptr.To(true), + }, + Status: batch.JobStatus{ + Active: 0, + StartTime: nil, + }, + }, + update: func(job *batch.Job) { + job.Spec.Template.Spec.Containers[0].Resources.Requests = api.ResourceList{ + api.ResourceCPU: resource.MustParse("200m"), + } + }, + }, + "feature gate enabled - job resuming (suspend=false) but JobSuspended condition still true - resource updates rejected": { + enableFeatureGate: true, + job: &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myjob", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "0", + }, + Spec: batch.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + ManualSelector: ptr.To(true), + Parallelism: ptr.To[int32](1), + Suspend: ptr.To(false), + }, + Status: batch.JobStatus{ + Active: 0, + Conditions: []batch.JobCondition{ + { + Type: batch.JobSuspended, + Status: api.ConditionStatus(metav1.ConditionTrue), + }, + }, + }, + }, + update: func(job *batch.Job) { + job.Spec.Template.Spec.Containers[0].Resources.Requests = api.ResourceList{ + api.ResourceCPU: resource.MustParse("200m"), + } + }, + wantErrs: field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "spec.template"}, + }, + }, } for name, tc := range cases { @@ -2810,7 +2870,7 @@ func TestStatusStrategy_ValidateUpdate(t *testing.T) { }, }, wantErrs: field.ErrorList{ - {Type: field.ErrorTypeRequired, Field: "status.startTime"}, + {Type: field.ErrorTypeInvalid, Field: "status.startTime"}, }, }, "verify startTime cannot be updated for unsuspended job": { @@ -2828,7 +2888,7 @@ func TestStatusStrategy_ValidateUpdate(t *testing.T) { }, }, wantErrs: field.ErrorList{ - {Type: field.ErrorTypeRequired, Field: "status.startTime"}, + {Type: field.ErrorTypeInvalid, Field: "status.startTime"}, }, }, "verify startTime can be updated when resuming job (JobSuspended: True -> False)": { @@ -4251,6 +4311,66 @@ func TestJobStrategy_ValidateUpdate_MutableSchedulingDirectives(t *testing.T) { {Type: field.ErrorTypeInvalid, Field: "spec.template"}, }, }, + "feature gate enabled - job resuming (suspend=false) but JobSuspended condition still true - scheduling directives update rejected": { + enableFeatureGate: true, + job: &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myjob", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "0", + }, + Spec: batch.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + ManualSelector: ptr.To(true), + Parallelism: ptr.To[int32](1), + Suspend: ptr.To(false), + }, + Status: batch.JobStatus{ + Active: 0, + Conditions: []batch.JobCondition{ + { + Type: batch.JobSuspended, + Status: api.ConditionStatus(metav1.ConditionTrue), + }, + }, + }, + }, + update: func(job *batch.Job) { + job.Spec.Template.Spec.NodeSelector = map[string]string{ + "key": "value", + } + }, + wantErrs: field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "spec.template"}, + }, + }, + "feature gate enabled - scheduling directives update allowed for suspended Job without Suspended condition but never started": { + enableFeatureGate: true, + job: &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myjob", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "0", + }, + Spec: batch.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + ManualSelector: ptr.To(true), + Parallelism: ptr.To[int32](1), + Suspend: ptr.To(true), + }, + Status: batch.JobStatus{ + Active: 0, + StartTime: nil, + }, + }, + update: func(job *batch.Job) { + job.Spec.Template.Spec.NodeSelector = map[string]string{ + "key": "value", + } + }, + }, } for name, tc := range cases { diff --git a/pkg/registry/certificates/certificates/declarative_validation_test.go b/pkg/registry/certificates/certificates/declarative_validation_test.go index 4039bc682c5ab..ce2b8f885ab52 100644 --- a/pkg/registry/certificates/certificates/declarative_validation_test.go +++ b/pkg/registry/certificates/certificates/declarative_validation_test.go @@ -78,13 +78,13 @@ func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) { "status.conditions: Approved+Denied = invalid": { input: makeValidCSR(withApprovedCondition(), withDeniedCondition()), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "conditions"), nil, "").WithOrigin("zeroOrOneOf"), + field.Invalid(field.NewPath("status", "conditions"), nil, "").WithOrigin("zeroOrOneOf").MarkAlpha(), }, }, "status.conditions: Denied+Approved = invalid": { input: makeValidCSR(withDeniedCondition(), withApprovedCondition()), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "conditions"), nil, "").WithOrigin("zeroOrOneOf"), + field.Invalid(field.NewPath("status", "conditions"), nil, "").WithOrigin("zeroOrOneOf").MarkAlpha(), }, }, } @@ -154,7 +154,7 @@ func testValidateUpdateForDeclarative(t *testing.T, apiVersion string) { old: makeValidCSR(), update: makeValidCSR(withApprovedCondition(), withDeniedCondition()), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "conditions"), nil, "").WithOrigin("zeroOrOneOf"), + field.Invalid(field.NewPath("status", "conditions"), nil, "").WithOrigin("zeroOrOneOf").MarkAlpha(), }, subresources: []string{"/approval"}, // Can only add Approved and Denied conditions on /approval subresource }, diff --git a/pkg/registry/certificates/certificates/storage/metrics_test.go b/pkg/registry/certificates/certificates/storage/metrics_test.go index 97927af115bb7..078995c28c7e6 100644 --- a/pkg/registry/certificates/certificates/storage/metrics_test.go +++ b/pkg/registry/certificates/certificates/storage/metrics_test.go @@ -384,7 +384,6 @@ func Test_countCSRDurationMetric(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/registry/certificates/certificates/strategy_test.go b/pkg/registry/certificates/certificates/strategy_test.go index 63065b210c5a6..6fe8b8970ddd4 100644 --- a/pkg/registry/certificates/certificates/strategy_test.go +++ b/pkg/registry/certificates/certificates/strategy_test.go @@ -132,7 +132,6 @@ func TestStrategyCreate(t *testing.T) { } for k, tc := range tests { - tc := tc t.Run(k, func(t *testing.T) { obj := tc.obj Strategy.PrepareForCreate(tc.ctx, obj) diff --git a/pkg/registry/certificates/podcertificaterequest/strategy.go b/pkg/registry/certificates/podcertificaterequest/strategy.go index 4ab196f6bab6d..0fec66de62fb8 100644 --- a/pkg/registry/certificates/podcertificaterequest/strategy.go +++ b/pkg/registry/certificates/podcertificaterequest/strategy.go @@ -119,7 +119,7 @@ func NewStatusStrategy(strategy *Strategy, authorizer authorizer.Authorizer, clo // and should not be modified by the user. func (s *StatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { fields := map[fieldpath.APIVersion]*fieldpath.Set{ - "certificates.k8s.io/v1alpha1": fieldpath.NewSet( + "certificates.k8s.io/v1beta1": fieldpath.NewSet( fieldpath.MakePathOrDie("spec"), ), } diff --git a/pkg/registry/core/pod/rest/subresources.go b/pkg/registry/core/pod/rest/subresources.go index e6ffa3408ff39..f5160574bb40c 100644 --- a/pkg/registry/core/pod/rest/subresources.go +++ b/pkg/registry/core/pod/rest/subresources.go @@ -21,9 +21,9 @@ import ( "fmt" "net/http" "net/url" + "slices" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -31,11 +31,13 @@ import ( "k8s.io/apiserver/pkg/registry/rest" utilfeature "k8s.io/apiserver/pkg/util/feature" translator "k8s.io/apiserver/pkg/util/proxy" + proxymetrics "k8s.io/apiserver/pkg/util/proxy/metrics" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/registry/core/pod" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // ProxyREST implements the proxy subresource for a Pod @@ -90,7 +92,7 @@ var upgradeableMethods = []string{"GET", "POST"} // AttachREST implements the attach subresource for a Pod type AttachREST struct { - Store *genericregistry.Store + Store pod.ResourceGetter KubeletConn client.ConnectionInfoGetter Authorizer authorizer.Authorizer } @@ -123,12 +125,21 @@ func (r *AttachREST) Connect(ctx context.Context, name string, opts runtime.Obje if !ok { return nil, fmt.Errorf("Invalid options object: %#v", opts) } - location, transport, err := pod.AttachLocation(ctx, r.Store, r.KubeletConn, name, attachOpts) + + location, connInfo, err := pod.AttachLocation(ctx, r.Store, r.KubeletConn, name, attachOpts) if err != nil { return nil, err } + transport := connInfo.Transport + nodeSupportsWebsockets := checkNodeSupportsWebsockets(connInfo.NodeFeatures) handler := newThrottledUpgradeAwareProxyHandler(location, transport, false, true, responder) if utilfeature.DefaultFeatureGate.Enabled(features.TranslateStreamCloseWebsocketRequests) { + // If the node supports websocket translation, revert to legacy proxy, and let kubelet handle it. + if utilfeature.DefaultFeatureGate.Enabled(features.ExtendWebSocketsToKubelet) && nodeSupportsWebsockets { + proxymetrics.IncWebSocketStreamingRequest(ctx, "attach", "proxied_to_kubelet") + return handler, nil + } + proxymetrics.IncWebSocketStreamingRequest(ctx, "attach", "translated_at_apiserver") // Wrap the upgrade aware handler to implement stream translation // for WebSocket/V5 upgrade requests. streamOptions := translator.Options{ @@ -156,7 +167,7 @@ func (r *AttachREST) ConnectMethods() []string { // ExecREST implements the exec subresource for a Pod type ExecREST struct { - Store *genericregistry.Store + Store pod.ResourceGetter KubeletConn client.ConnectionInfoGetter Authorizer authorizer.Authorizer } @@ -189,12 +200,21 @@ func (r *ExecREST) Connect(ctx context.Context, name string, opts runtime.Object if !ok { return nil, fmt.Errorf("invalid options object: %#v", opts) } - location, transport, err := pod.ExecLocation(ctx, r.Store, r.KubeletConn, name, execOpts) + + location, connInfo, err := pod.ExecLocation(ctx, r.Store, r.KubeletConn, name, execOpts) if err != nil { return nil, err } + transport := connInfo.Transport + nodeSupportsWebsockets := checkNodeSupportsWebsockets(connInfo.NodeFeatures) handler := newThrottledUpgradeAwareProxyHandler(location, transport, false, true, responder) if utilfeature.DefaultFeatureGate.Enabled(features.TranslateStreamCloseWebsocketRequests) { + // If the node supports websocket translation, revert to legacy proxy, and let kubelet handle it. + if utilfeature.DefaultFeatureGate.Enabled(features.ExtendWebSocketsToKubelet) && nodeSupportsWebsockets { + proxymetrics.IncWebSocketStreamingRequest(ctx, "exec", "proxied_to_kubelet") + return handler, nil + } + proxymetrics.IncWebSocketStreamingRequest(ctx, "exec", "translated_at_apiserver") // Wrap the upgrade aware handler to implement stream translation // for WebSocket/V5 upgrade requests. streamOptions := translator.Options{ @@ -222,7 +242,7 @@ func (r *ExecREST) ConnectMethods() []string { // PortForwardREST implements the portforward subresource for a Pod type PortForwardREST struct { - Store *genericregistry.Store + Store pod.ResourceGetter KubeletConn client.ConnectionInfoGetter Authorizer authorizer.Authorizer } @@ -266,12 +286,20 @@ func (r *PortForwardREST) Connect(ctx context.Context, name string, opts runtime if !ok { return nil, fmt.Errorf("invalid options object: %#v", opts) } - location, transport, err := pod.PortForwardLocation(ctx, r.Store, r.KubeletConn, name, portForwardOpts) + location, connInfo, err := pod.PortForwardLocation(ctx, r.Store, r.KubeletConn, name, portForwardOpts) if err != nil { return nil, err } + transport := connInfo.Transport + nodeSupportsWebsockets := checkNodeSupportsWebsockets(connInfo.NodeFeatures) handler := newThrottledUpgradeAwareProxyHandler(location, transport, false, true, responder) if utilfeature.DefaultFeatureGate.Enabled(features.PortForwardWebsockets) { + // If the node supports websocket tunneling, revert to legacy proxy, and let kubelet handle it. + if utilfeature.DefaultFeatureGate.Enabled(features.ExtendWebSocketsToKubelet) && nodeSupportsWebsockets { + proxymetrics.IncWebSocketStreamingRequest(ctx, "portforward", "proxied_to_kubelet") + return handler, nil + } + proxymetrics.IncWebSocketStreamingRequest(ctx, "portforward", "translated_at_apiserver") tunnelingHandler := translator.NewTunnelingHandler(handler) handler = translator.NewTranslatingHandler(handler, tunnelingHandler, wsstream.IsWebSocketRequestWithTunnelingProtocol) } @@ -283,3 +311,10 @@ func newThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.Roun handler.MaxBytesPerSec = capabilities.Get().PerConnectionBandwidthLimitBytesPerSec return handler } + +// Given features node supports, return true if kubelet supports websocket +// translation/tunneling, false otherwise. +func checkNodeSupportsWebsockets(nodeFeatures []string) bool { + nodeSupportsWebsockets := slices.Contains(nodeFeatures, string(features.ExtendWebSocketsToKubelet)) + return nodeSupportsWebsockets +} diff --git a/pkg/registry/core/pod/rest/subresources_test.go b/pkg/registry/core/pod/rest/subresources_test.go new file mode 100644 index 0000000000000..d5c2ebb07d2fb --- /dev/null +++ b/pkg/registry/core/pod/rest/subresources_test.go @@ -0,0 +1,570 @@ +/* +Copyright 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 rest + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/proxy" + "k8s.io/apiserver/pkg/authorization/authorizer" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + proxymetrics "k8s.io/apiserver/pkg/util/proxy/metrics" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/kubelet/client" + pod "k8s.io/kubernetes/pkg/registry/core/pod" +) + +type fakeKubeletConn struct { + features []string + err error +} + +func (f *fakeKubeletConn) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*client.ConnectionInfo, error) { + if f.err != nil { + return nil, f.err + } + return &client.ConnectionInfo{ + Scheme: "http", + Hostname: "localhost", + Port: "1234", + Transport: http.DefaultTransport, + NodeFeatures: f.features, + }, nil +} + +type fakePodGetter struct { + pod runtime.Object + err error +} + +func (f *fakePodGetter) Get(_ context.Context, _ string, _ *metav1.GetOptions) (runtime.Object, error) { + return f.pod, f.err +} + +type fakeAuthorizer struct{} + +func (f *fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + return authorizer.DecisionAllow, "", nil +} + +type fakeResponder struct{} + +func (f *fakeResponder) Error(err error) {} + +func (f *fakeResponder) Object(statusCode int, obj runtime.Object) {} + +func TestCheckNodeSupportsWebsockets(t *testing.T) { + tests := []struct { + name string + nodeFeatures []string + expected bool + }{ + { + name: "feature supported", + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + expected: true, + }, + { + name: "feature not supported", + nodeFeatures: []string{}, + expected: false, + }, + { + name: "multiple features, one supported", + nodeFeatures: []string{"SomeOtherFeature", string(features.ExtendWebSocketsToKubelet)}, + expected: true, + }, + { + name: "multiple features, none supported", + nodeFeatures: []string{"SomeOtherFeature", "AnotherFeature"}, + expected: false, + }, + { + name: "nil features", + nodeFeatures: nil, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checkNodeSupportsWebsockets(tt.nodeFeatures); got != tt.expected { + t.Errorf("checkNodeSupportsWebsockets() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestExecRESTConnect(t *testing.T) { + testPod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}, + Spec: api.PodSpec{ + NodeName: "test-node", + Containers: []api.Container{{Name: "test-container", Image: "test-image"}}, + }, + } + + tests := []struct { + name string + store pod.ResourceGetter + kubeletConn client.ConnectionInfoGetter + nodeFeatures []string + enableWebSocketsKubelet bool + enableTranslateWebSockets bool + expectProxy bool + expectErr bool + expectedProxyType string // empty means no metric expected + }{ + // Scenario 1: Both feature gates disabled, node does not support websockets. + // Expect UpgradeAwareHandler as no translation or extension is active. + { + name: "Kubelet disabled, Translate disabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: false, + enableTranslateWebSockets: false, + expectProxy: true, + }, + // Scenario 2: ExtendWebSocketsToKubelet enabled, TranslateStreamCloseWebsocketRequests disabled, + // node supports websockets. Expect UpgradeAwareHandler as translation is disabled. + { + name: "Kubelet enabled, Translate disabled, Node with websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + enableWebSocketsKubelet: true, + enableTranslateWebSockets: false, + expectProxy: true, + }, + // Scenario 3: ExtendWebSocketsToKubelet disabled, TranslateStreamCloseWebsocketRequests enabled, + // node does not support websockets. Expect translatingHandler as translation is active but extension is not. + { + name: "Kubelet disabled, Translate enabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: false, + enableTranslateWebSockets: true, + expectProxy: false, + expectedProxyType: "translated_at_apiserver", + }, + // Scenario 4: ExtendWebSocketsToKubelet enabled, TranslateStreamCloseWebsocketRequests enabled, + // node supports websockets. Expect UpgradeAwareHandler as extension is active and node supports it. + { + name: "Kubelet enabled, Translate enabled, Node with websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + enableWebSocketsKubelet: true, + enableTranslateWebSockets: true, + expectProxy: true, + expectedProxyType: "proxied_to_kubelet", + }, + // Scenario 5: ExtendWebSocketsToKubelet enabled, TranslateStreamCloseWebsocketRequests enabled, + // node does not support websockets. Expect translatingHandler as extension is active but node does not support it. + { + name: "Kubelet enabled, Translate enabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: true, + enableTranslateWebSockets: true, + expectProxy: false, + expectedProxyType: "translated_at_apiserver", + }, + // Scenario 6: Pod getter returns error; Connect must propagate it. + { + name: "pod getter error", + store: &fakePodGetter{err: fmt.Errorf("pod not found")}, + expectErr: true, + }, + // Scenario 7: ConnectionInfo getter returns error; Connect must propagate it. + { + name: "connection info getter error", + store: &fakePodGetter{pod: testPod}, + kubeletConn: &fakeKubeletConn{err: fmt.Errorf("connection error")}, + expectErr: true, + }, + } + + proxymetrics.Register() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proxymetrics.ResetForTest() + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableWebSocketsKubelet) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TranslateStreamCloseWebsocketRequests, tt.enableTranslateWebSockets) + + info := &genericapirequest.RequestInfo{Verb: "create", Name: "test-pod", Namespace: "default"} + ctx := genericapirequest.WithRequestInfo(context.Background(), info) + + kubeletConn := tt.kubeletConn + if kubeletConn == nil { + kubeletConn = &fakeKubeletConn{features: tt.nodeFeatures} + } + execRest := &ExecREST{ + Store: tt.store, + KubeletConn: kubeletConn, + Authorizer: &fakeAuthorizer{}, + } + + handler, err := execRest.Connect(ctx, "test-pod", &api.PodExecOptions{}, &fakeResponder{}) + if tt.expectErr { + if err == nil { + t.Error("expected error but got nil") + } + return + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if handler == nil { + t.Fatal("Handler should not be nil") + } + + if tt.expectProxy { + if _, ok := handler.(*proxy.UpgradeAwareHandler); !ok { + t.Errorf("expected handler of type *proxy.UpgradeAwareHandler, got %T", handler) + } + } else { + expected := "*proxy.translatingHandler" + got := reflect.TypeOf(handler).String() + if got != expected { + t.Errorf("expected handler of type %s, got %s", expected, got) + } + } + + if tt.expectedProxyType != "" { + expectedMetric := fmt.Sprintf(` +# HELP apiserver_websocket_streaming_requests_total [ALPHA] Total number of WebSocket streaming requests (exec/attach/portforward) routed by the API server, labeled by subresource and proxy_type. proxy_type is proxied_to_kubelet when the kubelet handles the request directly; otherwise translated_at_apiserver. +# TYPE apiserver_websocket_streaming_requests_total counter +apiserver_websocket_streaming_requests_total{proxy_type=%q,subresource="exec"} 1 +`, tt.expectedProxyType) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedMetric), + "apiserver_websocket_streaming_requests_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + } + }) + } +} + +func TestAttachRESTConnect(t *testing.T) { + testPod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}, + Spec: api.PodSpec{ + NodeName: "test-node", + Containers: []api.Container{{Name: "test-container", Image: "test-image"}}, + }, + } + + tests := []struct { + name string + store pod.ResourceGetter + kubeletConn client.ConnectionInfoGetter + nodeFeatures []string + enableWebSocketsKubelet bool + enableTranslateWebSockets bool + expectProxy bool + expectErr bool + expectedProxyType string // empty means no metric expected + }{ + // Scenario 1: Both feature gates disabled, node does not support websockets. + // Expect UpgradeAwareHandler as no translation or extension is active. + { + name: "Kubelet disabled, Translate disabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: false, + enableTranslateWebSockets: false, + expectProxy: true, + }, + // Scenario 2: ExtendWebSocketsToKubelet enabled, TranslateStreamCloseWebsocketRequests disabled, + // node supports websockets. Expect UpgradeAwareHandler as translation is disabled. + { + name: "Kubelet enabled, Translate disabled, Node with websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + enableWebSocketsKubelet: true, + enableTranslateWebSockets: false, + expectProxy: true, + }, + // Scenario 3: ExtendWebSocketsToKubelet disabled, TranslateStreamCloseWebsocketRequests enabled, + // node does not support websockets. Expect translatingHandler as translation is active but extension is not. + { + name: "Kubelet disabled, Translate enabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: false, + enableTranslateWebSockets: true, + expectProxy: false, + expectedProxyType: "translated_at_apiserver", + }, + // Scenario 4: ExtendWebSocketsToKubelet enabled, TranslateStreamCloseWebsocketRequests enabled, + // node supports websockets. Expect UpgradeAwareHandler as extension is active and node supports it. + { + name: "Kubelet enabled, Translate enabled, Node with websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + enableWebSocketsKubelet: true, + enableTranslateWebSockets: true, + expectProxy: true, + expectedProxyType: "proxied_to_kubelet", + }, + // Scenario 5: ExtendWebSocketsToKubelet enabled, TranslateStreamCloseWebsocketRequests enabled, + // node does not support websockets. Expect translatingHandler as extension is active but node does not support it. + { + name: "Kubelet enabled, Translate enabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: true, + enableTranslateWebSockets: true, + expectProxy: false, + expectedProxyType: "translated_at_apiserver", + }, + // Scenario 6: Pod getter returns error; Connect must propagate it. + { + name: "pod getter error", + store: &fakePodGetter{err: fmt.Errorf("pod not found")}, + expectErr: true, + }, + // Scenario 7: ConnectionInfo getter returns error; Connect must propagate it. + { + name: "connection info getter error", + store: &fakePodGetter{pod: testPod}, + kubeletConn: &fakeKubeletConn{err: fmt.Errorf("connection error")}, + expectErr: true, + }, + } + + proxymetrics.Register() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proxymetrics.ResetForTest() + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableWebSocketsKubelet) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TranslateStreamCloseWebsocketRequests, tt.enableTranslateWebSockets) + + info := &genericapirequest.RequestInfo{Verb: "create", Name: "test-pod", Namespace: "default"} + ctx := genericapirequest.WithRequestInfo(context.Background(), info) + + kubeletConn := tt.kubeletConn + if kubeletConn == nil { + kubeletConn = &fakeKubeletConn{features: tt.nodeFeatures} + } + attachRest := &AttachREST{ + Store: tt.store, + KubeletConn: kubeletConn, + Authorizer: &fakeAuthorizer{}, + } + + handler, err := attachRest.Connect(ctx, "test-pod", &api.PodAttachOptions{}, &fakeResponder{}) + if tt.expectErr { + if err == nil { + t.Error("expected error but got nil") + } + return + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if handler == nil { + t.Fatal("Handler should not be nil") + } + + if tt.expectProxy { + if _, ok := handler.(*proxy.UpgradeAwareHandler); !ok { + t.Errorf("expected handler of type *proxy.UpgradeAwareHandler, got %T", handler) + } + } else { + expected := "*proxy.translatingHandler" + got := reflect.TypeOf(handler).String() + if got != expected { + t.Errorf("expected handler of type %s, got %s", expected, got) + } + } + + if tt.expectedProxyType != "" { + expectedMetric := fmt.Sprintf(` +# HELP apiserver_websocket_streaming_requests_total [ALPHA] Total number of WebSocket streaming requests (exec/attach/portforward) routed by the API server, labeled by subresource and proxy_type. proxy_type is proxied_to_kubelet when the kubelet handles the request directly; otherwise translated_at_apiserver. +# TYPE apiserver_websocket_streaming_requests_total counter +apiserver_websocket_streaming_requests_total{proxy_type=%q,subresource="attach"} 1 +`, tt.expectedProxyType) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedMetric), + "apiserver_websocket_streaming_requests_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + } + }) + } +} + +func TestPortForwardRESTConnect(t *testing.T) { + testPod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}, + Spec: api.PodSpec{ + NodeName: "test-node", + Containers: []api.Container{{Name: "test-container", Image: "test-image"}}, + }, + } + + tests := []struct { + name string + store pod.ResourceGetter + kubeletConn client.ConnectionInfoGetter + nodeFeatures []string + enableWebSocketsKubelet bool + enablePortForward bool + expectProxy bool + expectErr bool + expectedProxyType string // empty means no metric expected + }{ + // Scenario 1: Both feature gates disabled, node does not support websockets. + // Expect UpgradeAwareHandler as no tunneling or extension is active. + { + name: "Kubelet disabled, PortForward disabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: false, + enablePortForward: false, + expectProxy: true, + }, + // Scenario 2: ExtendWebSocketsToKubelet enabled, PortForwardWebsockets disabled, + // node supports websockets. Expect UpgradeAwareHandler as tunneling is disabled. + { + name: "Kubelet enabled, PortForward disabled, Node with websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + enableWebSocketsKubelet: true, + enablePortForward: false, + expectProxy: true, + }, + // Scenario 3: ExtendWebSocketsToKubelet disabled, PortForwardWebsockets enabled, + // node does not support websockets. Expect translatingHandler as tunneling is active but extension is not. + { + name: "Kubelet disabled, PortForward enabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: false, + enablePortForward: true, + expectProxy: false, + expectedProxyType: "translated_at_apiserver", + }, + // Scenario 4: ExtendWebSocketsToKubelet enabled, PortForwardWebsockets enabled, + // node supports websockets. Expect UpgradeAwareHandler as extension is active and node supports it. + { + name: "Kubelet enabled, PortForward enabled, Node with websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{string(features.ExtendWebSocketsToKubelet)}, + enableWebSocketsKubelet: true, + enablePortForward: true, + expectProxy: true, + expectedProxyType: "proxied_to_kubelet", + }, + // Scenario 5: ExtendWebSocketsToKubelet enabled, PortForwardWebsockets enabled, + // node does not support websockets. Expect translatingHandler as extension is active but node does not support it. + { + name: "Kubelet enabled, PortForward enabled, Node no websocket", + store: &fakePodGetter{pod: testPod}, + nodeFeatures: []string{}, + enableWebSocketsKubelet: true, + enablePortForward: true, + expectProxy: false, + expectedProxyType: "translated_at_apiserver", + }, + // Scenario 6: Pod getter returns error; Connect must propagate it. + { + name: "pod getter error", + store: &fakePodGetter{err: fmt.Errorf("pod not found")}, + expectErr: true, + }, + // Scenario 7: ConnectionInfo getter returns error; Connect must propagate it. + { + name: "connection info getter error", + store: &fakePodGetter{pod: testPod}, + kubeletConn: &fakeKubeletConn{err: fmt.Errorf("connection error")}, + expectErr: true, + }, + } + + proxymetrics.Register() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proxymetrics.ResetForTest() + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableWebSocketsKubelet) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PortForwardWebsockets, tt.enablePortForward) + + info := &genericapirequest.RequestInfo{Verb: "create", Name: "test-pod", Namespace: "default"} + ctx := genericapirequest.WithRequestInfo(context.Background(), info) + + kubeletConn := tt.kubeletConn + if kubeletConn == nil { + kubeletConn = &fakeKubeletConn{features: tt.nodeFeatures} + } + portForwardRest := &PortForwardREST{ + Store: tt.store, + KubeletConn: kubeletConn, + Authorizer: &fakeAuthorizer{}, + } + + handler, err := portForwardRest.Connect(ctx, "test-pod", &api.PodPortForwardOptions{}, &fakeResponder{}) + if tt.expectErr { + if err == nil { + t.Error("expected error but got nil") + } + return + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if handler == nil { + t.Fatal("Handler should not be nil") + } + + if tt.expectProxy { + if _, ok := handler.(*proxy.UpgradeAwareHandler); !ok { + t.Errorf("expected handler of type *proxy.UpgradeAwareHandler, got %T", handler) + } + } else { + expected := "*proxy.translatingHandler" + got := reflect.TypeOf(handler).String() + if got != expected { + t.Errorf("expected handler of type %s, got %s", expected, got) + } + } + + if tt.expectedProxyType != "" { + expectedMetric := fmt.Sprintf(` +# HELP apiserver_websocket_streaming_requests_total [ALPHA] Total number of WebSocket streaming requests (exec/attach/portforward) routed by the API server, labeled by subresource and proxy_type. proxy_type is proxied_to_kubelet when the kubelet handles the request directly; otherwise translated_at_apiserver. +# TYPE apiserver_websocket_streaming_requests_total counter +apiserver_websocket_streaming_requests_total{proxy_type=%q,subresource="portforward"} 1 +`, tt.expectedProxyType) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedMetric), + "apiserver_websocket_streaming_requests_total"); err != nil { + t.Errorf("unexpected metric output: %v", err) + } + } + }) + } +} diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index a77ad05142310..191278b586752 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -735,7 +735,7 @@ func AttachLocation( connInfo client.ConnectionInfoGetter, name string, opts *api.PodAttachOptions, -) (*url.URL, http.RoundTripper, error) { +) (*url.URL, *client.ConnectionInfo, error) { return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "attach") } @@ -747,7 +747,7 @@ func ExecLocation( connInfo client.ConnectionInfoGetter, name string, opts *api.PodExecOptions, -) (*url.URL, http.RoundTripper, error) { +) (*url.URL, *client.ConnectionInfo, error) { return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "exec") } @@ -759,7 +759,7 @@ func streamLocation( opts runtime.Object, container, path string, -) (*url.URL, http.RoundTripper, error) { +) (*url.URL, *client.ConnectionInfo, error) { pod, err := getPod(ctx, getter, name) if err != nil { return nil, nil, err @@ -791,7 +791,7 @@ func streamLocation( Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container), RawQuery: params.Encode(), } - return loc, nodeInfo.Transport, nil + return loc, nodeInfo, nil } // PortForwardLocation returns the port-forward URL for a pod. @@ -801,7 +801,7 @@ func PortForwardLocation( connInfo client.ConnectionInfoGetter, name string, opts *api.PodPortForwardOptions, -) (*url.URL, http.RoundTripper, error) { +) (*url.URL, *client.ConnectionInfo, error) { pod, err := getPod(ctx, getter, name) if err != nil { return nil, nil, err @@ -826,7 +826,7 @@ func PortForwardLocation( Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name), RawQuery: params.Encode(), } - return loc, nodeInfo.Transport, nil + return loc, nodeInfo, nil } // validateContainer validate container is valid for pod, return valid container diff --git a/pkg/registry/core/pod/strategy_test.go b/pkg/registry/core/pod/strategy_test.go index 6af91ed615b7c..9ce380bc7dcd2 100644 --- a/pkg/registry/core/pod/strategy_test.go +++ b/pkg/registry/core/pod/strategy_test.go @@ -644,13 +644,16 @@ func (g mockConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeNam func TestPortForwardLocation(t *testing.T) { ctx := genericapirequest.NewDefaultContext() tcs := []struct { - in *api.Pod - info *client.ConnectionInfo - opts *api.PodPortForwardOptions - expectedErr error - expectedURL *url.URL + name string + in *api.Pod + info *client.ConnectionInfo + opts *api.PodPortForwardOptions + expectedErr error + expectedURL *url.URL + expectedConnInfo *client.ConnectionInfo }{ { + name: "pod without host returns error", in: &api.Pod{ Spec: api.PodSpec{}, }, @@ -658,6 +661,7 @@ func TestPortForwardLocation(t *testing.T) { expectedErr: errors.NewBadRequest("pod test does not have a host assigned"), }, { + name: "valid pod with no ports", in: &api.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns", @@ -667,11 +671,13 @@ func TestPortForwardLocation(t *testing.T) { NodeName: "node1", }, }, - info: &client.ConnectionInfo{}, - opts: &api.PodPortForwardOptions{}, - expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"}, + info: &client.ConnectionInfo{}, + opts: &api.PodPortForwardOptions{}, + expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"}, + expectedConnInfo: &client.ConnectionInfo{}, }, { + name: "valid pod with port", in: &api.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns", @@ -681,21 +687,201 @@ func TestPortForwardLocation(t *testing.T) { NodeName: "node1", }, }, + info: &client.ConnectionInfo{}, + opts: &api.PodPortForwardOptions{Ports: []int32{80}}, + expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1", RawQuery: "port=80"}, + expectedConnInfo: &client.ConnectionInfo{}, + }, + { + name: "node features propagated through connection info", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pod1", + }, + Spec: api.PodSpec{ + NodeName: "node1", + }, + }, + info: &client.ConnectionInfo{ + NodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, + opts: &api.PodPortForwardOptions{}, + expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"}, + expectedConnInfo: &client.ConnectionInfo{ + NodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + getter := &mockPodGetter{tc.in} + connectionGetter := &mockConnectionInfoGetter{tc.info} + loc, connInfo, err := PortForwardLocation(ctx, getter, connectionGetter, "test", tc.opts) + if !reflect.DeepEqual(err, tc.expectedErr) { + t.Errorf("expected err %v, got %v", tc.expectedErr, err) + } + if !reflect.DeepEqual(loc, tc.expectedURL) { + t.Errorf("expected URL %v, got %v", tc.expectedURL, loc) + } + if !reflect.DeepEqual(connInfo, tc.expectedConnInfo) { + t.Errorf("expected connInfo %+v, got %+v", tc.expectedConnInfo, connInfo) + } + }) + } +} + +func TestExecLocation(t *testing.T) { + ctx := genericapirequest.NewDefaultContext() + tcs := []struct { + name string + in *api.Pod + info *client.ConnectionInfo + opts *api.PodExecOptions + expectedErr error + expectedURL *url.URL + expectedConnInfo *client.ConnectionInfo + }{ + { + // Container validation runs before the host check, so the pod must have + // a matching container. The "no host assigned" error uses the name + // argument ("test"), not pod.Name. + name: "pod without host returns error", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "c1"}}, + // NodeName intentionally absent + }, + }, + opts: &api.PodExecOptions{Container: "c1"}, + expectedErr: errors.NewBadRequest("pod test does not have a host assigned"), + }, + { + // validateContainer uses pod.Name ("pod1") in its error, not the name argument. + name: "invalid container returns error", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, + Spec: api.PodSpec{ + NodeName: "node1", + Containers: []api.Container{{Name: "c1"}}, + }, + }, info: &client.ConnectionInfo{}, - opts: &api.PodPortForwardOptions{Ports: []int32{80}}, - expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1", RawQuery: "port=80"}, + opts: &api.PodExecOptions{Container: "unknown"}, + expectedErr: errors.NewBadRequest("container unknown is not valid for pod pod1"), + }, + { + name: "valid exec location with node features propagated", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, + Spec: api.PodSpec{ + NodeName: "node1", + Containers: []api.Container{{Name: "c1"}}, + }, + }, + info: &client.ConnectionInfo{ + NodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, + opts: &api.PodExecOptions{Container: "c1"}, + expectedURL: &url.URL{Host: ":", Path: "/exec/ns/pod1/c1"}, + expectedConnInfo: &client.ConnectionInfo{ + NodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, }, } for _, tc := range tcs { - getter := &mockPodGetter{tc.in} - connectionGetter := &mockConnectionInfoGetter{tc.info} - loc, _, err := PortForwardLocation(ctx, getter, connectionGetter, "test", tc.opts) - if !reflect.DeepEqual(err, tc.expectedErr) { - t.Errorf("expected %v, got %v", tc.expectedErr, err) - } - if !reflect.DeepEqual(loc, tc.expectedURL) { - t.Errorf("expected %v, got %v", tc.expectedURL, loc) - } + t.Run(tc.name, func(t *testing.T) { + getter := &mockPodGetter{tc.in} + connectionGetter := &mockConnectionInfoGetter{tc.info} + loc, connInfo, err := ExecLocation(ctx, getter, connectionGetter, "test", tc.opts) + if !reflect.DeepEqual(err, tc.expectedErr) { + t.Errorf("expected err %v, got %v", tc.expectedErr, err) + } + if !reflect.DeepEqual(loc, tc.expectedURL) { + t.Errorf("expected URL %v, got %v", tc.expectedURL, loc) + } + if !reflect.DeepEqual(connInfo, tc.expectedConnInfo) { + t.Errorf("expected connInfo %+v, got %+v", tc.expectedConnInfo, connInfo) + } + }) + } +} + +func TestAttachLocation(t *testing.T) { + ctx := genericapirequest.NewDefaultContext() + tcs := []struct { + name string + in *api.Pod + info *client.ConnectionInfo + opts *api.PodAttachOptions + expectedErr error + expectedURL *url.URL + expectedConnInfo *client.ConnectionInfo + }{ + { + // Container validation runs before the host check, so the pod must have + // a matching container. The "no host assigned" error uses the name + // argument ("test"), not pod.Name. + name: "pod without host returns error", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "c1"}}, + // NodeName intentionally absent + }, + }, + opts: &api.PodAttachOptions{Container: "c1"}, + expectedErr: errors.NewBadRequest("pod test does not have a host assigned"), + }, + { + // validateContainer uses pod.Name ("pod1") in its error, not the name argument. + name: "invalid container returns error", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, + Spec: api.PodSpec{ + NodeName: "node1", + Containers: []api.Container{{Name: "c1"}}, + }, + }, + info: &client.ConnectionInfo{}, + opts: &api.PodAttachOptions{Container: "unknown"}, + expectedErr: errors.NewBadRequest("container unknown is not valid for pod pod1"), + }, + { + name: "valid attach location with node features propagated", + in: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, + Spec: api.PodSpec{ + NodeName: "node1", + Containers: []api.Container{{Name: "c1"}}, + }, + }, + info: &client.ConnectionInfo{ + NodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, + opts: &api.PodAttachOptions{Container: "c1"}, + expectedURL: &url.URL{Host: ":", Path: "/attach/ns/pod1/c1"}, + expectedConnInfo: &client.ConnectionInfo{ + NodeFeatures: []string{"ExtendWebSocketsToKubelet"}, + }, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + getter := &mockPodGetter{tc.in} + connectionGetter := &mockConnectionInfoGetter{tc.info} + loc, connInfo, err := AttachLocation(ctx, getter, connectionGetter, "test", tc.opts) + if !reflect.DeepEqual(err, tc.expectedErr) { + t.Errorf("expected err %v, got %v", tc.expectedErr, err) + } + if !reflect.DeepEqual(loc, tc.expectedURL) { + t.Errorf("expected URL %v, got %v", tc.expectedURL, loc) + } + if !reflect.DeepEqual(connInfo, tc.expectedConnInfo) { + t.Errorf("expected connInfo %+v, got %+v", tc.expectedConnInfo, connInfo) + } + }) } } @@ -3716,6 +3902,68 @@ func TestEphemeralContainersPrepareForUpdate(t *testing.T) { } } +func TestSchedulingGroupEnablement(t *testing.T) { + var ( + ctx = genericapirequest.NewDefaultContext() + pgName = "pg" + pod = podtest.MakePod("pod", + podtest.SetSchedulingGroup(&api.PodSchedulingGroup{ + PodGroupName: new(pgName), + }), + ) + ) + + // Enable the Feature Gate during the Pod creation. + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, true) + + errs := Strategy.Validate(ctx, pod) + if len(errs) != 0 { + t.Errorf("Unexpected error: %v", errs.ToAggregate()) + } + + createdPod := pod.DeepCopy() + Strategy.PrepareForCreate(ctx, createdPod) + + if sg := createdPod.Spec.SchedulingGroup; sg == nil || ptr.Deref(sg.PodGroupName, "") != pgName { + t.Error("SchedulingGroup created with unexpected result") + } + + // Disable the Feature Gate and check that the SchedulingGroup field still exists after updating. + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, false) + + updatedPod := createdPod.DeepCopy() + updatedPod.Labels = map[string]string{"foo": "bar"} + updatedPod.ResourceVersion = "2" + + errs = Strategy.ValidateUpdate(ctx, updatedPod, createdPod) + if len(errs) != 0 { + t.Errorf("Unexpected error: %v", errs.ToAggregate()) + } + + Strategy.PrepareForUpdate(ctx, updatedPod, createdPod) + + if sg := createdPod.Spec.SchedulingGroup; sg == nil || ptr.Deref(sg.PodGroupName, "") != pgName { + t.Error("SchedulingGroup updated with unexpected result") + } + + // Enable the Feature Gate again to check that the SchedulingGroup field still exist after updating. + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, true) + + updatedPod2 := updatedPod.DeepCopy() + updatedPod2.Labels = map[string]string{"foo": "baz"} + updatedPod2.ResourceVersion = "3" + + errs = Strategy.ValidateUpdate(ctx, updatedPod2, updatedPod) + if len(errs) != 0 { + t.Errorf("Unexpected error: %v", errs.ToAggregate()) + } + + Strategy.PrepareForUpdate(ctx, updatedPod2, updatedPod) + if sg := createdPod.Spec.SchedulingGroup; sg == nil || ptr.Deref(sg.PodGroupName, "") != pgName { + t.Error("SchedulingGroup updated with unexpected result") + } +} + func TestStatusPrepareForUpdate(t *testing.T) { testCases := []struct { description string diff --git a/pkg/registry/core/replicationcontroller/declarative_validation_test.go b/pkg/registry/core/replicationcontroller/declarative_validation_test.go index e3ad7f6efaff1..050a3c2435cc2 100644 --- a/pkg/registry/core/replicationcontroller/declarative_validation_test.go +++ b/pkg/registry/core/replicationcontroller/declarative_validation_test.go @@ -49,7 +49,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = "" }), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("metadata.name"), ""), + field.Required(field.NewPath("metadata.name"), "").MarkFromImperative(), }, }, "name: label format": { @@ -67,7 +67,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = "-this-is-not-a-label" }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: invalid subdomain format": { @@ -75,7 +75,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = ".this.is.not.a.subdomain" }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: label format with trailing dash": { @@ -83,7 +83,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = "this-is-a-label-" }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: subdomain format with trailing dash": { @@ -91,7 +91,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = "this.is.a.subdomain-" }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: long label format": { @@ -109,7 +109,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = strings.Repeat("x", 254) }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: too long subdomain format": { @@ -117,7 +117,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Name = strings.Repeat("x.", 126) + "xx" }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata.name"), nil, "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, // metadata.generateName (note: it's is not really validated) @@ -127,8 +127,8 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.GenerateName = "name" }), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("metadata.name"), ""), - field.InternalError(field.NewPath("metadata.name"), fmt.Errorf("")), + field.Required(field.NewPath("metadata.name"), "").MarkFromImperative(), + field.InternalError(field.NewPath("metadata.name"), fmt.Errorf("")).MarkFromImperative(), }, }, @@ -158,7 +158,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { rc.Spec.Replicas = nil }), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec.replicas"), ""), + field.Required(field.NewPath("spec.replicas"), "").MarkAlpha(), }, }, "replicas: 0": { @@ -170,7 +170,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { "replicas: negative": { input: mkValidReplicationController(setSpecReplicas(-1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum").MarkAlpha(), }, }, // spec.minReadySeconds @@ -183,7 +183,7 @@ func TestDeclarativeValidateForDeclarative(t *testing.T) { "minReadySeconds: negative": { input: mkValidReplicationController(setSpecMinReadySeconds(-1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec.minReadySeconds"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("spec.minReadySeconds"), nil, "").WithOrigin("minimum").MarkAlpha(), }, }, } @@ -216,7 +216,7 @@ func TestValidateUpdateForDeclarative(t *testing.T) { rc.Name += "x" }), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata.name"), nil, ""), + field.Invalid(field.NewPath("metadata.name"), nil, "").MarkFromImperative(), }, }, // metadata.generateName @@ -235,7 +235,7 @@ func TestValidateUpdateForDeclarative(t *testing.T) { rc.Spec.Replicas = nil }), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec.replicas"), ""), + field.Required(field.NewPath("spec.replicas"), "").MarkAlpha(), }, }, "replicas: 0": { @@ -250,7 +250,7 @@ func TestValidateUpdateForDeclarative(t *testing.T) { old: mkValidReplicationController(), update: mkValidReplicationController(setSpecReplicas(-1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum").MarkAlpha(), }, }, // spec.minReadySeconds @@ -266,7 +266,7 @@ func TestValidateUpdateForDeclarative(t *testing.T) { old: mkValidReplicationController(), update: mkValidReplicationController(setSpecMinReadySeconds(-1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec.minReadySeconds"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("spec.minReadySeconds"), nil, "").WithOrigin("minimum").MarkAlpha(), }, }, } diff --git a/pkg/registry/core/replicationcontroller/storage/scale_declarative_validation_test.go b/pkg/registry/core/replicationcontroller/storage/scale_declarative_validation_test.go index daae0911072e6..2116d6c6a3053 100644 --- a/pkg/registry/core/replicationcontroller/storage/scale_declarative_validation_test.go +++ b/pkg/registry/core/replicationcontroller/storage/scale_declarative_validation_test.go @@ -24,6 +24,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" @@ -48,7 +49,7 @@ func TestValidateScaleForDeclarative(t *testing.T) { "spec.replicas: negative replicas": { input: mkScale(setScaleSpecReplicas(-1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("spec.replicas"), nil, "").WithOrigin("minimum").MarkAlpha(), }, }, } @@ -77,6 +78,9 @@ func TestValidateScaleForDeclarative(t *testing.T) { // We only need to test both gate enabled and disabled together, because // 1) the DeclarativeValidationTakeover won't take effect if DeclarativeValidation is disabled. // 2) the validation output, when only DeclarativeValidation is enabled, is the same as when both gates are disabled. + if !gateVal { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.DeclarativeValidation: gateVal, features.DeclarativeValidationTakeover: gateVal, diff --git a/pkg/registry/core/service/ipallocator/controller/repairip.go b/pkg/registry/core/service/ipallocator/controller/repairip.go index feb7fce307679..ddd32f3cfe2c0 100644 --- a/pkg/registry/core/service/ipallocator/controller/repairip.go +++ b/pkg/registry/core/service/ipallocator/controller/repairip.go @@ -247,7 +247,18 @@ func (r *RepairIPAddress) RunUntil(onFirstSuccess func(), stopCh chan struct{}) // runOnce verifies the state of the ClusterIP allocations and returns an error if an unrecoverable problem occurs. func (r *RepairIPAddress) runOnce() error { - return retry.RetryOnConflict(retry.DefaultBackoff, r.doRunOnce) + return retry.OnError(retry.DefaultBackoff, func(err error) bool { + // When trying to repair the ClusterIP allocations, we may get a conflict or forbidden error. + // IsForbidden depends on the admission chain to be ready that may depend on the + // Namespace informer to be ready. + // Ref: https://issues.k8s.io/136288 + if apierrors.IsConflict(err) || apierrors.IsForbidden(err) { + klog.ErrorS(err, "Running ipallocator repair failed ... retrying") + return true + } + klog.ErrorS(err, "Running ipallocator repair failed with not retryable error") + return false + }, r.doRunOnce) } // doRunOnce verifies the state of the ClusterIP allocations and returns an error if an unrecoverable problem occurs. @@ -499,6 +510,10 @@ func (r *RepairIPAddress) syncIPAddress(key string) error { return nil } + // When a Service is created, the IP allocator creates an IPAddress object for it, and + // at that moment the Service object is not created yet, so we have to wait for it to be created. + // In addition, if the apiserver "dies" in the middle of the creation of the Service, the IPAddress object will be orphaned. + // If the time since it was created is greater than 60 seconds (default timeout value on the kube-apiserver), the IPAddress is deleted. svc, err := r.serviceLister.Services(ipAddress.Spec.ParentRef.Namespace).Get(ipAddress.Spec.ParentRef.Name) if apierrors.IsNotFound(err) { // cleaning all IPAddress without an owner reference IF the time since it was created is greater than 60 seconds (default timeout value on the kube-apiserver) @@ -531,6 +546,21 @@ func (r *RepairIPAddress) syncIPAddress(key string) error { } } + // There is a special case when we "break the immutability" of the Service ClusterIPs, + // and we allocate IP addresses on Update. This is when a Service is mutated from Type ExternalName + // to any of the Types that require ClusterIP creation. Similar to the same logic we use to handle + // the case when the Service is created after the IPAddress, we delay the processing of the new + // IPAddress object by the 60 seconds timeout of the kube-apiserver. + if svc.Spec.Type == v1.ServiceTypeExternalName { + ipLifetime := r.clock.Now().Sub(ipAddress.CreationTimestamp.Time) + gracePeriod := 60 * time.Second + if ipLifetime < gracePeriod { + // requeue after the grace period + r.ipQueue.AddAfter(key, gracePeriod-ipLifetime) + return nil + } + } + // Service storage implements transactions. It creates an IPAddress object first and then creates // the Service object, and if the Service object already exists the complete transaction is // reverted. There can be race conditions when the repair loop picks up the new IPAddress object diff --git a/pkg/registry/core/service/ipallocator/controller/repairip_test.go b/pkg/registry/core/service/ipallocator/controller/repairip_test.go index 1e44687f02576..e95096a12545f 100644 --- a/pkg/registry/core/service/ipallocator/controller/repairip_test.go +++ b/pkg/registry/core/service/ipallocator/controller/repairip_test.go @@ -24,6 +24,7 @@ import ( "github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -477,10 +478,58 @@ func TestRepairIPAddress_syncIPAddress(t *testing.T) { tests := []struct { name string ip *networkingv1.IPAddress + svc *v1.Service testTime time.Time actions [][]string // verb and resource wantErr bool }{ + { + name: "ExternalName Service with late IPAddress", + ip: &networkingv1.IPAddress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "10.0.1.1", + Labels: map[string]string{ + networkingv1.LabelIPAddressFamily: string(v1.IPv4Protocol), + networkingv1.LabelManagedBy: ipallocator.ControllerName, + }, + CreationTimestamp: metav1.Time{Time: testTimeNow.Add(10 * time.Second)}, + }, + Spec: networkingv1.IPAddressSpec{ + ParentRef: &networkingv1.ParentReference{ + Group: "", + Resource: "services", + Name: "foo", + Namespace: "bar", + }, + }, + }, + svc: newExternalNameService("foo", testTimeNow), + testTime: testTimeNow.Add(2 * time.Second), + }, + { + name: "ExternalName Service with late IPAddress after threshold", + ip: &networkingv1.IPAddress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "10.0.1.1", + Labels: map[string]string{ + networkingv1.LabelIPAddressFamily: string(v1.IPv4Protocol), + networkingv1.LabelManagedBy: ipallocator.ControllerName, + }, + CreationTimestamp: metav1.Time{Time: testTimeNow.Add(10 * time.Second)}, + }, + Spec: networkingv1.IPAddressSpec{ + ParentRef: &networkingv1.ParentReference{ + Group: "", + Resource: "services", + Name: "foo", + Namespace: "bar", + }, + }, + }, + svc: newExternalNameService("foo", testTimeNow), + testTime: testTimeNow.Add(71 * time.Second), + actions: [][]string{{"delete", "ipaddresses"}}, + }, { name: "correct ipv4 address", ip: &networkingv1.IPAddress{ @@ -602,7 +651,11 @@ func TestRepairIPAddress_syncIPAddress(t *testing.T) { if err != nil { t.Fatal(err) } - err = r.serviceStore.Add(newService("foo", []string{tt.ip.Name})) + svc := tt.svc + if svc == nil { + svc = newService("foo", []string{tt.ip.Name}) + } + err = r.serviceStore.Add(svc) if err != nil { t.Fatal(err) } @@ -621,6 +674,20 @@ func TestRepairIPAddress_syncIPAddress(t *testing.T) { } } +func newExternalNameService(name string, timestamp time.Time) *v1.Service { + return &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "bar", + Name: name, + CreationTimestamp: metav1.Time{Time: timestamp}, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeExternalName, + ExternalName: "foo.bar.com", + }, + } +} + func newIPAddressWithCreationTimestamp(name string, svc *v1.Service, timestamp time.Time) *networkingv1.IPAddress { ipAddress := newIPAddress(name, svc) ipAddress.CreationTimestamp = metav1.Time{Time: timestamp} @@ -707,3 +774,83 @@ func expectEvents(t *testing.T, actual <-chan string, expected []string) { } } } + +func TestRepairIPAddress_runOnce(t *testing.T) { + tests := []struct { + name string + conflictCount int + errorType func(string) error + expectedRuns int + expectedErr bool + }{ + { + name: "Retry on Conflict", + conflictCount: 2, + errorType: func(name string) error { + return apierrors.NewConflict(networkingv1.Resource("ipaddresses"), name, fmt.Errorf("conflict")) + }, + expectedRuns: 3, + expectedErr: false, + }, + { + name: "Retry on Forbidden", + conflictCount: 2, + errorType: func(name string) error { + return apierrors.NewForbidden(networkingv1.Resource("ipaddresses"), name, fmt.Errorf("forbidden")) + }, + expectedRuns: 3, + expectedErr: false, + }, + { + name: "No Retry on InternalError", + conflictCount: 1, // It will fail once and stop + errorType: func(name string) error { + return apierrors.NewInternalError(fmt.Errorf("internal error")) + }, + expectedRuns: 1, + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, r := newFakeRepair(testTimeNow) + // Add a service that needs repair (missing IPAddress) + svc := newService("test-svc", []string{"10.0.1.1"}) + err := r.serviceStore.Add(svc) + if err != nil { + t.Fatalf("Unexpected error adding service: %v", err) + } + r.servicesSynced = func() bool { return true } + r.ipAddressSynced = func() bool { return true } + r.serviceCIDRSynced = func() bool { return true } + + // Add a default ServiceCIDR + cidr := newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6) + err = r.serviceCIDRStore.Add(cidr) + if err != nil { + t.Fatalf("Unexpected error adding ServiceCIDR: %v", err) + } + + // Track how many times create is called + createCalls := 0 + client.PrependReactor("create", "ipaddresses", func(action k8stesting.Action) (bool, runtime.Object, error) { + createCalls++ + if createCalls <= tt.conflictCount { + return true, nil, tt.errorType("10.0.1.1") + } + // Pass through to the default reactor (which creates the object) + return false, nil, nil + }) + + err = r.runOnce() + if (err != nil) != tt.expectedErr { + t.Errorf("runOnce() error = %v, expectedErr %v", err, tt.expectedErr) + } + + if createCalls != tt.expectedRuns { + t.Errorf("Expected %d create calls, got %d", tt.expectedRuns, createCalls) + } + }) + } +} diff --git a/pkg/registry/core/service/portallocator/storage/storage_test.go b/pkg/registry/core/service/portallocator/storage/storage_test.go index 4629bed0002f8..01ec8f90623b3 100644 --- a/pkg/registry/core/service/portallocator/storage/storage_test.go +++ b/pkg/registry/core/service/portallocator/storage/storage_test.go @@ -129,7 +129,6 @@ func TestAllocate(t *testing.T) { }, } for _, tt := range tests { - tt := tt // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables t.Run(tt.name, func(t *testing.T) { err := storage.Allocate(tt.port) if (err == nil) != (tt.errMsg == "") { diff --git a/pkg/registry/core/service/storage/alloc.go b/pkg/registry/core/service/storage/alloc.go index 2fc4873753895..4fcbe442bc554 100644 --- a/pkg/registry/core/service/storage/alloc.go +++ b/pkg/registry/core/service/storage/alloc.go @@ -419,7 +419,7 @@ func (al *Allocators) allocIPs(service *api.Service, toAlloc map[api.IPFamily]st el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIPs"), service.Spec.ClusterIPs, fmt.Sprintf("failed to allocate IP: %v", err))} return allocated, apierrors.NewInvalid(api.Kind("Service"), service.Name, el) } - return allocated, apierrors.NewInternalError(fmt.Errorf("failed to allocate a serviceIP: %w", err)) + return allocated, apierrors.NewInternalError(fmt.Errorf("failed to allocate a serviceIP for Service %q: %w", service.Name, err)) } allocated[family] = allocatedIP.String() } else { diff --git a/pkg/registry/core/service/strategy.go b/pkg/registry/core/service/strategy.go index fec2ae4920be7..e41a10bb045eb 100644 --- a/pkg/registry/core/service/strategy.go +++ b/pkg/registry/core/service/strategy.go @@ -30,12 +30,10 @@ import ( "k8s.io/apiserver/pkg/registry/generic" pkgstorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" serviceapi "k8s.io/kubernetes/pkg/api/service" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/validation" - "k8s.io/kubernetes/pkg/features" "sigs.k8s.io/structured-merge-diff/v6/fieldpath" ) @@ -125,13 +123,7 @@ func (svcStrategy) AllowUnconditionalUpdate() bool { // if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) { // newSvc.Spec.MyFeature = nil // } -func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) { - // Drop condition for TrafficDistribution field. - isTrafficDistributionInUse := (oldSvc != nil && oldSvc.Spec.TrafficDistribution != nil) - if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceTrafficDistribution) && !isTrafficDistributionInUse { - newSvc.Spec.TrafficDistribution = nil - } -} +func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) {} type serviceStatusStrategy struct { svcStrategy diff --git a/pkg/registry/discovery/endpointslice/declarative_validation_test.go b/pkg/registry/discovery/endpointslice/declarative_validation_test.go new file mode 100644 index 0000000000000..e4c7f54c41af8 --- /dev/null +++ b/pkg/registry/discovery/endpointslice/declarative_validation_test.go @@ -0,0 +1,191 @@ +/* +Copyright 2025 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 endpointslice + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/discovery" +) + +var apiVersions = []string{"v1", "v1beta1"} + +func TestDeclarativeValidate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidate(t, apiVersion) + }) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "discovery.k8s.io", + APIVersion: apiVersion, + Resource: "endpointslices", + IsResourceRequest: true, + Verb: "create", + }) + + testCases := map[string]struct { + input discovery.EndpointSlice + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidEndpointSlice(), + }, + "valid at limit endpoint addresses": { + input: mkValidEndpointSlice(tweakAddresses(100)), + }, + "invalid missing endpoint addresses": { + input: mkValidEndpointSlice(func(obj *discovery.EndpointSlice) { + obj.Endpoints[0].Addresses = nil + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("endpoints").Index(0).Child("addresses"), "").MarkAlpha(), + }, + }, + "invalid too many endpoint addresses": { + input: mkValidEndpointSlice(tweakAddresses(101)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("endpoints").Index(0).Child("addresses"), 101, 100).WithOrigin("maxItems").MarkAlpha(), + }, + }, + "invalid missing addressType": { + input: mkValidEndpointSlice(tweakAddressType("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("addressType"), "").MarkAlpha(), + }, + }, + "invalid addressType not supported": { + input: mkValidEndpointSlice(tweakAddressType("invalid")), + expectedErrs: field.ErrorList{ + field.NotSupported(field.NewPath("addressType"), discovery.AddressType("invalid"), []string{string(discovery.AddressTypeIPv4), string(discovery.AddressTypeIPv6), string(discovery.AddressTypeFQDN)}).MarkAlpha(), + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + testCases := map[string]struct { + oldObj discovery.EndpointSlice + updateObj discovery.EndpointSlice + expectedErrs field.ErrorList + }{ + "valid update": { + oldObj: mkValidEndpointSlice(), + updateObj: mkValidEndpointSlice(), + }, + "valid update at limit endpoint addresses": { + oldObj: mkValidEndpointSlice(), + updateObj: mkValidEndpointSlice(tweakAddresses(100)), + }, + "invalid update missing addresses": { + oldObj: mkValidEndpointSlice(), + updateObj: mkValidEndpointSlice(func(obj *discovery.EndpointSlice) { + obj.Endpoints[0].Addresses = nil + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("endpoints").Index(0).Child("addresses"), "").MarkAlpha(), + }, + }, + "invalid update too many addresses": { + oldObj: mkValidEndpointSlice(), + updateObj: mkValidEndpointSlice(tweakAddresses(101)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("endpoints").Index(0).Child("addresses"), 101, 100).WithOrigin("maxItems").MarkAlpha(), + }, + }, + "invalid update addressType immutable": { + oldObj: mkValidEndpointSlice(), + updateObj: mkValidEndpointSlice(tweakAddressType(discovery.AddressTypeIPv6)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("addressType"), discovery.AddressTypeIPv6, "field is immutable").WithOrigin("immutable").MarkAlpha(), + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "discovery.k8s.io", + APIVersion: apiVersion, + Resource: "endpointslices", + Name: "valid-endpointslice", + IsResourceRequest: true, + Verb: "update", + }) + tc.oldObj.ResourceVersion = "1" + tc.updateObj.ResourceVersion = "2" + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func mkValidEndpointSlice(tweaks ...func(obj *discovery.EndpointSlice)) discovery.EndpointSlice { + obj := discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-endpointslice", + Namespace: metav1.NamespaceDefault, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"10.1.2.1"}, + }, + }, + } + + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +func tweakAddresses(count int) func(*discovery.EndpointSlice) { + return func(obj *discovery.EndpointSlice) { + addrs := make([]string, count) + for i := range addrs { + addrs[i] = fmt.Sprintf("10.0.0.%d", i%255) + } + obj.Endpoints[0].Addresses = addrs + } +} + +func tweakAddressType(addrType discovery.AddressType) func(*discovery.EndpointSlice) { + return func(obj *discovery.EndpointSlice) { + obj.AddressType = addrType + } +} diff --git a/pkg/registry/discovery/endpointslice/strategy.go b/pkg/registry/discovery/endpointslice/strategy.go index 51d8f3c5ea91e..09269868411eb 100644 --- a/pkg/registry/discovery/endpointslice/strategy.go +++ b/pkg/registry/discovery/endpointslice/strategy.go @@ -24,6 +24,7 @@ import ( discoveryv1 "k8s.io/api/discovery/v1" discoveryv1beta1 "k8s.io/api/discovery/v1beta1" apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/operation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -31,6 +32,7 @@ import ( utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -91,8 +93,8 @@ func (endpointSliceStrategy) PrepareForUpdate(ctx context.Context, obj, old runt // Validate validates a new EndpointSlice. func (endpointSliceStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { endpointSlice := obj.(*discovery.EndpointSlice) - err := validation.ValidateEndpointSliceCreate(endpointSlice) - return err + allErrs := validation.ValidateEndpointSliceCreate(endpointSlice) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, endpointSlice, nil, allErrs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -120,7 +122,8 @@ func (endpointSliceStrategy) AllowCreateOnUpdate() bool { func (endpointSliceStrategy) ValidateUpdate(ctx context.Context, new, old runtime.Object) field.ErrorList { newEPS := new.(*discovery.EndpointSlice) oldEPS := old.(*discovery.EndpointSlice) - return validation.ValidateEndpointSliceUpdate(newEPS, oldEPS) + allErrs := validation.ValidateEndpointSliceUpdate(newEPS, oldEPS) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newEPS, oldEPS, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. @@ -141,16 +144,12 @@ func (endpointSliceStrategy) AllowUnconditionalUpdate() bool { // dropDisabledConditionsOnCreate will drop any fields that are disabled. func dropDisabledFieldsOnCreate(endpointSlice *discovery.EndpointSlice) { - dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) - dropNodeHints := !utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution) - if !dropHints && !dropNodeHints { + if utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution) { return } for i := range endpointSlice.Endpoints { - if dropHints { - endpointSlice.Endpoints[i].Hints = nil - } else if endpointSlice.Endpoints[i].Hints != nil { + if endpointSlice.Endpoints[i].Hints != nil { endpointSlice.Endpoints[i].Hints.ForNodes = nil } } @@ -159,27 +158,18 @@ func dropDisabledFieldsOnCreate(endpointSlice *discovery.EndpointSlice) { // dropDisabledFieldsOnUpdate will drop any disable fields that have not already // been set on the EndpointSlice. func dropDisabledFieldsOnUpdate(oldEPS, newEPS *discovery.EndpointSlice) { - dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) - dropNodeHints := !utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution) - if dropHints || dropNodeHints { - for _, ep := range oldEPS.Endpoints { - if ep.Hints != nil { - dropHints = false - if ep.Hints.ForNodes != nil { - dropNodeHints = false - break - } - } - } - } - if !dropHints && !dropNodeHints { + if utilfeature.DefaultFeatureGate.Enabled(features.PreferSameTrafficDistribution) { return } + for _, ep := range oldEPS.Endpoints { + if ep.Hints != nil && ep.Hints.ForNodes != nil { + return + } + } + for i := range newEPS.Endpoints { - if dropHints { - newEPS.Endpoints[i].Hints = nil - } else if newEPS.Endpoints[i].Hints != nil { + if newEPS.Endpoints[i].Hints != nil { newEPS.Endpoints[i].Hints.ForNodes = nil } } diff --git a/pkg/registry/discovery/endpointslice/strategy_test.go b/pkg/registry/discovery/endpointslice/strategy_test.go index ad8e2bfcd8ad8..4227c13af46bf 100644 --- a/pkg/registry/discovery/endpointslice/strategy_test.go +++ b/pkg/registry/discovery/endpointslice/strategy_test.go @@ -133,15 +133,13 @@ func Test_dropDisabledFieldsOnCreate(t *testing.T) { func Test_dropDisabledFieldsOnUpdate(t *testing.T) { testcases := []struct { name string - hintsGateEnabled bool preferSameEnabled bool oldEPS *discovery.EndpointSlice newEPS *discovery.EndpointSlice expectedEPS *discovery.EndpointSlice }{ { - name: "hints gate enabled, set on new EPS", - hintsGateEnabled: true, + name: "PreferSameTrafficDistribution disabled, can still set zone hints", oldEPS: &discovery.EndpointSlice{ Endpoints: []discovery.Endpoint{ { @@ -181,93 +179,8 @@ func Test_dropDisabledFieldsOnUpdate(t *testing.T) { }, }, }, - { - name: "hints gate disabled, set on new EPS", - hintsGateEnabled: false, - oldEPS: &discovery.EndpointSlice{ - Endpoints: []discovery.Endpoint{ - { - Hints: nil, - }, - { - Hints: nil, - }, - }, - }, - newEPS: &discovery.EndpointSlice{ - Endpoints: []discovery.Endpoint{ - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-a"}}, - }, - }, - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-b"}}, - }, - }, - }, - }, - expectedEPS: &discovery.EndpointSlice{ - Endpoints: []discovery.Endpoint{ - { - Hints: nil, - }, - { - Hints: nil, - }, - }, - }, - }, - { - name: "hints gate disabled, set on new and old EPS", - hintsGateEnabled: false, - oldEPS: &discovery.EndpointSlice{ - Endpoints: []discovery.Endpoint{ - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-a-old"}}, - }, - }, - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-b-old"}}, - }, - }, - }, - }, - newEPS: &discovery.EndpointSlice{ - Endpoints: []discovery.Endpoint{ - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-a"}}, - }, - }, - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-b"}}, - }, - }, - }, - }, - expectedEPS: &discovery.EndpointSlice{ - Endpoints: []discovery.Endpoint{ - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-a"}}, - }, - }, - { - Hints: &discovery.EndpointHints{ - ForZones: []discovery.ForZone{{Name: "zone-b"}}, - }, - }, - }, - }, - }, { name: "PreferSameTrafficDistribution gate enabled, set on new EPS", - hintsGateEnabled: true, preferSameEnabled: true, oldEPS: &discovery.EndpointSlice{ Endpoints: []discovery.Endpoint{ @@ -314,7 +227,6 @@ func Test_dropDisabledFieldsOnUpdate(t *testing.T) { }, { name: "PreferSameTrafficDistribution gate disabled, set on new EPS", - hintsGateEnabled: true, preferSameEnabled: false, oldEPS: &discovery.EndpointSlice{ Endpoints: []discovery.Endpoint{ @@ -359,7 +271,6 @@ func Test_dropDisabledFieldsOnUpdate(t *testing.T) { }, { name: "PreferSameTrafficDiscovery gate disabled, set on new and old EPS", - hintsGateEnabled: true, preferSameEnabled: false, oldEPS: &discovery.EndpointSlice{ Endpoints: []discovery.Endpoint{ @@ -414,10 +325,7 @@ func Test_dropDisabledFieldsOnUpdate(t *testing.T) { for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { - if !testcase.hintsGateEnabled { - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, testcase.hintsGateEnabled) - } else if !testcase.preferSameEnabled { + if !testcase.preferSameEnabled { featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.34")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PreferSameTrafficDistribution, testcase.preferSameEnabled) } diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go new file mode 100644 index 0000000000000..4a36e699811be --- /dev/null +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go @@ -0,0 +1,267 @@ +/* +Copyright 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 prioritylevelconfiguration + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/flowcontrol" +) + +var apiVersions = []string{"v1", "v1beta1", "v1beta2", "v1beta3"} + +func TestDeclarativeValidate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidate(t, apiVersion) + }) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "flowcontrol.apiserver.k8s.io", + APIVersion: apiVersion, + }) + + specPath := field.NewPath("spec") + + testCases := map[string]struct { + input flowcontrol.PriorityLevelConfiguration + expectedErrs field.ErrorList + }{ + "valid: Limited allows Reject without queuing": { + input: mkPLC(), + }, + "valid: Queue requires queuing": { + input: mkPLC(tweakLimitResponseType(flowcontrol.LimitResponseTypeQueue)), + }, + "valid: Exempt without exempt config": { + input: mkPLC(tweakExempt()), + }, + "valid: Exempt with exempt config": { + input: mkPLC(tweakExempt(), tweakExemptConfig(&flowcontrol.ExemptPriorityLevelConfiguration{ + NominalConcurrencyShares: ptrInt32(100), + })), + }, + "spec.type: Limited with limited=nil": { + input: mkPLC(tweakLimited(nil)), + expectedErrs: field.ErrorList{ + field.Required(specPath.Child("limited"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "spec.type: Exempt with limited set": { + input: mkPLC(tweakExempt(), tweakLimited(&flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeReject, + }, + })), + expectedErrs: field.ErrorList{ + // Mandatory object check: spec of 'exempt' differs from bootstrap (HW-only) + field.Invalid(specPath, nil, "").MarkFromImperative(), + field.Forbidden(specPath.Child("limited"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "spec.type: Limited with exempt set": { + input: mkPLC(tweakExemptConfig(&flowcontrol.ExemptPriorityLevelConfiguration{})), + expectedErrs: field.ErrorList{ + field.Forbidden(specPath.Child("exempt"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "limitResponse.type: Queue with queuing=nil": { + input: mkPLC(tweakLimitResponseType(flowcontrol.LimitResponseTypeQueue), tweakQueuing(nil)), + expectedErrs: field.ErrorList{ + field.Required(specPath.Child("limited", "limitResponse", "queuing"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "limitResponse.type: Reject with queuing set": { + input: mkPLC(tweakQueuing(&flowcontrol.QueuingConfiguration{ + Queues: 64, + HandSize: 8, + QueueLengthLimit: 50, + })), + expectedErrs: field.ErrorList{ + field.Forbidden(specPath.Child("limited", "limitResponse", "queuing"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + specPath := field.NewPath("spec") + + testCases := map[string]struct { + old flowcontrol.PriorityLevelConfiguration + update flowcontrol.PriorityLevelConfiguration + expectedErrs field.ErrorList + }{ + "valid update (no changes)": { + old: mkPLC(), + update: mkPLC(), + }, + "valid update: Reject to Queue": { + old: mkPLC(), + update: mkPLC(tweakLimitResponseType(flowcontrol.LimitResponseTypeQueue)), + }, + "update: limited set to nil": { + old: mkPLC(), + update: mkPLC(tweakLimited(nil)), + expectedErrs: field.ErrorList{ + field.Required(specPath.Child("limited"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "update: add exempt field to Limited PLC": { + old: mkPLC(), + update: mkPLC(tweakExemptConfig(&flowcontrol.ExemptPriorityLevelConfiguration{})), + expectedErrs: field.ErrorList{ + field.Forbidden(specPath.Child("exempt"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "update: Queue with queuing set to nil": { + old: mkPLC(tweakLimitResponseType(flowcontrol.LimitResponseTypeQueue)), + update: mkPLC(tweakLimitResponseType(flowcontrol.LimitResponseTypeQueue), tweakQueuing(nil)), + expectedErrs: field.ErrorList{ + field.Required(specPath.Child("limited", "limitResponse", "queuing"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + "update: Reject with queuing added": { + old: mkPLC(), + update: mkPLC(tweakQueuing(&flowcontrol.QueuingConfiguration{ + Queues: 64, + HandSize: 8, + QueueLengthLimit: 50, + })), + expectedErrs: field.ErrorList{ + field.Forbidden(specPath.Child("limited", "limitResponse", "queuing"), "").MarkCoveredByDeclarative().MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + tc.old.ResourceVersion = "1" + tc.update.ResourceVersion = "2" + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "flowcontrol.apiserver.k8s.io", + APIVersion: apiVersion, + Resource: "prioritylevelconfigurations", + Name: tc.old.Name, + IsResourceRequest: true, + Verb: "update", + }) + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +// mkPLC creates a valid Limited PriorityLevelConfiguration with Reject limit response. +func mkPLC(tweaks ...func(*flowcontrol.PriorityLevelConfiguration)) flowcontrol.PriorityLevelConfiguration { + obj := flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-limited", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeReject, + }, + }, + }, + } + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +// tweakExempt switches the PLC to Exempt type with the mandatory "exempt" name. +func tweakExempt() func(*flowcontrol.PriorityLevelConfiguration) { + return func(obj *flowcontrol.PriorityLevelConfiguration) { + obj.Name = "exempt" + obj.Spec.Type = flowcontrol.PriorityLevelEnablementExempt + obj.Spec.Limited = nil + } +} + +// tweakLimited sets the Limited field directly. +func tweakLimited(limited *flowcontrol.LimitedPriorityLevelConfiguration) func(*flowcontrol.PriorityLevelConfiguration) { + return func(obj *flowcontrol.PriorityLevelConfiguration) { + obj.Spec.Limited = limited + } +} + +// tweakExemptConfig sets the Exempt configuration field. +func tweakExemptConfig(exempt *flowcontrol.ExemptPriorityLevelConfiguration) func(*flowcontrol.PriorityLevelConfiguration) { + return func(obj *flowcontrol.PriorityLevelConfiguration) { + obj.Spec.Exempt = exempt + } +} + +// tweakLimitResponseType sets the LimitResponse type and auto-populates Queuing if Queue. +func tweakLimitResponseType(lrt flowcontrol.LimitResponseType) func(*flowcontrol.PriorityLevelConfiguration) { + return func(obj *flowcontrol.PriorityLevelConfiguration) { + if obj.Spec.Limited == nil { + return + } + obj.Spec.Limited.LimitResponse.Type = lrt + if lrt == flowcontrol.LimitResponseTypeQueue { + obj.Spec.Limited.LimitResponse.Queuing = &flowcontrol.QueuingConfiguration{ + Queues: 64, + HandSize: 8, + QueueLengthLimit: 50, + } + } else { + obj.Spec.Limited.LimitResponse.Queuing = nil + } + } +} + +// tweakQueuing sets the Queuing field directly, overriding auto-population. +func tweakQueuing(queuing *flowcontrol.QueuingConfiguration) func(*flowcontrol.PriorityLevelConfiguration) { + return func(obj *flowcontrol.PriorityLevelConfiguration) { + if obj.Spec.Limited == nil { + return + } + obj.Spec.Limited.LimitResponse.Queuing = queuing + } +} + +func ptrInt32(v int32) *int32 { + return &v +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go index 575287bb7bf27..0c1ec288255f2 100644 --- a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go @@ -20,10 +20,12 @@ import ( "context" apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/flowcontrol" @@ -94,8 +96,10 @@ func (priorityLevelConfigurationStrategy) Validate(ctx context.Context, obj runt // That means we should not allow 0 values to be introduced, either // via v1 or v1beta3(with the roundtrip annotation) until we know // all servers are at 1.29+ and will honor the zero value correctly. + plc := obj.(*flowcontrol.PriorityLevelConfiguration) opts := validation.PriorityLevelValidationOptions{} - return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration), getRequestGroupVersion(ctx), opts) + allErrs := validation.ValidatePriorityLevelConfiguration(plc, getRequestGroupVersion(ctx), opts) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, plc, nil, allErrs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -119,6 +123,7 @@ func (priorityLevelConfigurationStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (priorityLevelConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { newPL := obj.(*flowcontrol.PriorityLevelConfiguration) + oldPL := old.(*flowcontrol.PriorityLevelConfiguration) // 1.28 server is not aware of the roundtrip annotation, and will // default any 0 value persisted (for the NominalConcurrencyShares @@ -128,7 +133,8 @@ func (priorityLevelConfigurationStrategy) ValidateUpdate(ctx context.Context, ob // via v1 or v1beta3(with the roundtrip annotation) until we know // all servers are at 1.29+ and will honor the zero value correctly. opts := validation.PriorityLevelValidationOptions{} - return validation.ValidatePriorityLevelConfiguration(newPL, getRequestGroupVersion(ctx), opts) + allErrs := validation.ValidatePriorityLevelConfiguration(newPL, getRequestGroupVersion(ctx), opts) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newPL, oldPL, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/networking/ingressclass/declarative_validation_test.go b/pkg/registry/networking/ingressclass/declarative_validation_test.go new file mode 100644 index 0000000000000..d622c0ebd0ba6 --- /dev/null +++ b/pkg/registry/networking/ingressclass/declarative_validation_test.go @@ -0,0 +1,191 @@ +/* +Copyright 2025 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 ingressclass + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + networking "k8s.io/kubernetes/pkg/apis/networking" +) + +var apiVersions = []string{"v1", "v1beta1"} + +func TestDeclarativeValidateParameter(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "networking.k8s.io", + APIVersion: apiVersion, + Resource: "ingressclasses", + IsResourceRequest: true, + Verb: "create", + }, + ) + + testCases := map[string]struct { + input networking.IngressClass + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidIngressClass(), + }, + "nil parameters": { + input: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.Spec.Parameters = nil + }), + }, + "missing parameter name": { + input: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.Spec.Parameters.Name = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parameters", "name"), "").MarkAlpha()}, + }, + "missing parameter kind": { + input: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.Spec.Parameters.Kind = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parameters", "kind"), "").MarkAlpha(), + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence( + t, + ctx, + &tc.input, + Strategy.Validate, + tc.expectedErrs, + ) + }) + } + }) + } +} + +func TestDeclarativeValidateUpdateParameters(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testCases := map[string]struct { + oldObj networking.IngressClass + updateObj networking.IngressClass + expectedErrs field.ErrorList + }{ + "valid update": { + oldObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + }), + updateObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + }), + }, + "nil parameters update": { + oldObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + }), + updateObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + obj.Spec.Parameters = nil + }), + }, + "update fails when parameters name is cleared": { + oldObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + }), + updateObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + obj.Spec.Parameters.Name = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parameters", "name"), "").MarkAlpha(), + }, + }, + "update fails when parameters kind is cleared": { + oldObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + }), + updateObj: mkValidIngressClass(func(obj *networking.IngressClass) { + obj.ResourceVersion = "1" + obj.Spec.Parameters.Kind = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parameters", "kind"), "").MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "networking.k8s.io", + APIVersion: apiVersion, + Resource: "ingressclasses", + Name: "valid-ingress-class", + IsResourceRequest: true, + Verb: "update", + }, + ) + apitesting.VerifyUpdateValidationEquivalence( + t, + ctx, + &tc.updateObj, + &tc.oldObj, + Strategy.ValidateUpdate, + tc.expectedErrs, + ) + }) + } + }) + } +} + +func mkValidIngressClass(tweaks ...func(obj *networking.IngressClass)) networking.IngressClass { + apiGroup := "example.com" + scope := networking.IngressClassParametersReferenceScopeCluster + + obj := networking.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-ingress-class", + }, + Spec: networking.IngressClassSpec{ + Controller: "example.com/ingress-controller", + Parameters: &networking.IngressClassParametersReference{ + APIGroup: &apiGroup, + Kind: "IngressParameters", + Name: "valid-params", + Scope: &scope, + Namespace: nil, + }, + }, + } + + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} diff --git a/pkg/registry/networking/ingressclass/strategy.go b/pkg/registry/networking/ingressclass/strategy.go index 4e1b22688caec..15ca00da8e08b 100644 --- a/pkg/registry/networking/ingressclass/strategy.go +++ b/pkg/registry/networking/ingressclass/strategy.go @@ -18,6 +18,8 @@ package ingressclass import ( "context" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apiserver/pkg/registry/rest" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" @@ -67,7 +69,8 @@ func (ingressClassStrategy) PrepareForUpdate(ctx context.Context, obj, old runti // Validate validates a new IngressClass. func (ingressClassStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { ingressClass := obj.(*networking.IngressClass) - return validation.ValidateIngressClass(ingressClass) + allErrs := validation.ValidateIngressClass(ingressClass) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, ingressClass, nil, allErrs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -90,7 +93,8 @@ func (ingressClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime newIngressClass := obj.(*networking.IngressClass) oldIngressClass := old.(*networking.IngressClass) - return validation.ValidateIngressClassUpdate(newIngressClass, oldIngressClass) + allErrs := validation.ValidateIngressClassUpdate(newIngressClass, oldIngressClass) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newIngressClass, oldIngressClass, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/networking/ipaddress/declarative_validation_test.go b/pkg/registry/networking/ipaddress/declarative_validation_test.go new file mode 100644 index 0000000000000..d1af4953252f0 --- /dev/null +++ b/pkg/registry/networking/ipaddress/declarative_validation_test.go @@ -0,0 +1,222 @@ +/* +Copyright 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 ipaddress + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + networking "k8s.io/kubernetes/pkg/apis/networking" +) + +var apiVersions = []string{"v1beta1", "v1"} + +func TestDeclarativeValidateIPAddress(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "networking.k8s.io", + APIVersion: apiVersion, + Resource: "ipaddresses", + IsResourceRequest: true, + Verb: "create", + }, + ) + + testCases := map[string]struct { + input networking.IPAddress + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidIPAddress(), + }, + "missing parentRef": { + input: mkValidIPAddress(withNilParentRef), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parentRef"), "").MarkAlpha(), + }, + }, + "missing parentRef resource": { + input: mkValidIPAddress(withEmptyParentRefResource), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parentRef", "resource"), "").MarkAlpha(), + }, + }, + "missing parentRef name": { + input: mkValidIPAddress(withEmptyParentRefName), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parentRef", "name"), "").MarkAlpha(), + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence( + t, + ctx, + &tc.input, + Strategy.Validate, + tc.expectedErrs, + ) + }) + } + }) + } +} + +func TestDeclarativeValidateIPAddressUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testCases := map[string]struct { + oldObj networking.IPAddress + updateObj networking.IPAddress + expectedErrs field.ErrorList + }{ + "valid update": { + oldObj: mkValidIPAddress(withResourceVersion("1")), + updateObj: mkValidIPAddress(withResourceVersion("1")), + }, + "immutable parentRef": { + oldObj: mkValidIPAddress(withResourceVersion("1")), + updateObj: mkValidIPAddress( + withResourceVersion("1"), + withParentRefName("new-name"), + ), + expectedErrs: field.ErrorList{ + func() *field.Error { + e := field.Invalid(field.NewPath("spec", "parentRef"), &networking.ParentReference{ + Group: "apps", + Resource: "deployments", + Name: "new-name", + }, "field is immutable") + e.Origin = "immutable" + return e + }().MarkAlpha(), + }, + }, + "set parentRef": { + oldObj: mkValidIPAddress(withResourceVersion("1"), withNilParentRef), + updateObj: mkValidIPAddress( + withResourceVersion("2"), + withParentRefName("new-name"), + ), + expectedErrs: field.ErrorList{ + func() *field.Error { + e := field.Invalid(field.NewPath("spec", "parentRef"), &networking.ParentReference{ + Group: "apps", + Resource: "deployments", + Name: "new-name", + }, "field is immutable") + e.Origin = "immutable" + return e + }().MarkAlpha(), + }, + }, + "unset parentRef": { + oldObj: mkValidIPAddress(withResourceVersion("1")), + updateObj: mkValidIPAddress( + withResourceVersion("1"), + withNilParentRef, + ), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "parentRef"), "").MarkAlpha(), + func() *field.Error { + e := field.Invalid(field.NewPath("spec", "parentRef"), nil, "field is immutable") + e.Origin = "immutable" + return e + }().MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "networking.k8s.io", + APIVersion: apiVersion, + Resource: "ipaddresses", + Name: "192.168.1.1", + IsResourceRequest: true, + Verb: "update", + }, + ) + + apitesting.VerifyUpdateValidationEquivalence( + t, + ctx, + &tc.updateObj, + &tc.oldObj, + Strategy.ValidateUpdate, + tc.expectedErrs, + ) + }) + } + }) + } +} + +func mkValidIPAddress(tweaks ...func(obj *networking.IPAddress)) networking.IPAddress { + obj := networking.IPAddress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "192.168.1.1", + }, + Spec: networking.IPAddressSpec{ + ParentRef: &networking.ParentReference{ + Group: "apps", + Resource: "deployments", + Name: "my-deployment", + }, + }, + } + + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +func withNilParentRef(obj *networking.IPAddress) { + obj.Spec.ParentRef = nil +} + +func withEmptyParentRefResource(obj *networking.IPAddress) { + obj.Spec.ParentRef.Resource = "" +} + +func withEmptyParentRefName(obj *networking.IPAddress) { + obj.Spec.ParentRef.Name = "" +} + +func withResourceVersion(rv string) func(*networking.IPAddress) { + return func(obj *networking.IPAddress) { + obj.ResourceVersion = rv + } +} + +func withParentRefName(name string) func(*networking.IPAddress) { + return func(obj *networking.IPAddress) { + obj.Spec.ParentRef.Name = name + } +} diff --git a/pkg/registry/networking/ipaddress/strategy.go b/pkg/registry/networking/ipaddress/strategy.go index ef21e011e2785..9afbf50184803 100644 --- a/pkg/registry/networking/ipaddress/strategy.go +++ b/pkg/registry/networking/ipaddress/strategy.go @@ -19,6 +19,7 @@ package ipaddress import ( "context" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/registry/rest" @@ -73,7 +74,7 @@ func (ipAddressStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime. func (ipAddressStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { ipAddress := obj.(*networking.IPAddress) err := validation.ValidateIPAddress(ipAddress) - return err + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, ipAddress, nil, err, operation.Create) } // Canonicalize normalizes the object after validation. @@ -91,7 +92,7 @@ func (ipAddressStrategy) ValidateUpdate(ctx context.Context, new, old runtime.Ob oldIPAddress := old.(*networking.IPAddress) errList := validation.ValidateIPAddress(newIPAddress) errList = append(errList, validation.ValidateIPAddressUpdate(newIPAddress, oldIPAddress)...) - return errList + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newIPAddress, oldIPAddress, errList, operation.Update) } // AllowUnconditionalUpdate is the default update policy for IPAddress objects. diff --git a/pkg/registry/networking/ipaddress/strategy_test.go b/pkg/registry/networking/ipaddress/strategy_test.go index 41de8fcecc416..86045eb1adc53 100644 --- a/pkg/registry/networking/ipaddress/strategy_test.go +++ b/pkg/registry/networking/ipaddress/strategy_test.go @@ -42,7 +42,17 @@ func newIPAddress() networking.IPAddress { } func TestIPAddressStrategy(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "networking.k8s.io", + APIVersion: "v1", + Resource: "ipaddresses", + Name: "192.168.1.1", + IsResourceRequest: true, + Verb: "create", + }, + ) if Strategy.NamespaceScoped() { t.Errorf("ipAddress must not be namespace scoped") } diff --git a/pkg/registry/networking/networkpolicy/declarative_validation_test.go b/pkg/registry/networking/networkpolicy/declarative_validation_test.go new file mode 100644 index 0000000000000..49d6dd5768451 --- /dev/null +++ b/pkg/registry/networking/networkpolicy/declarative_validation_test.go @@ -0,0 +1,214 @@ +/* +Copyright 2025 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 networkpolicy + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + networking "k8s.io/kubernetes/pkg/apis/networking" +) + +var apiVersions = []string{"v1"} + +func TestDeclarativeValidateIPBlockCIDR(t *testing.T) { + + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "networking.k8s.io", + APIVersion: apiVersion, + Resource: "networkpolicies", + IsResourceRequest: true, + Verb: "create", + }, + ) + + testCases := map[string]struct { + input networking.NetworkPolicy + expectedErrs field.ErrorList + }{ + "valid ingress rule with CIDR": { + input: mkValidNetworkPolicy("ingress"), + }, + "valid egress rule with CIDR": { + input: mkValidNetworkPolicy("egress"), + }, + "ingress rule rejects empty CIDR in ipBlock": { + input: mkValidNetworkPolicy("ingress", tweakIngressFromIPBlock("")), + expectedErrs: field.ErrorList{ + field.Required( + field.NewPath("spec", "ingress").Index(0).Child("from").Index(0).Child("ipBlock", "cidr"), + "", + ).MarkAlpha(), + }, + }, + "egress rule rejects empty CIDR in ipBlock": { + input: mkValidNetworkPolicy("egress", tweakEgressToIPBlock("")), + expectedErrs: field.ErrorList{ + field.Required( + field.NewPath("spec", "egress").Index(0).Child("to").Index(0).Child("ipBlock", "cidr"), + "", + ).MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence( + t, + ctx, + &tc.input, + Strategy.Validate, + tc.expectedErrs, + ) + }) + } + }) + } +} + +func TestDeclarativeValidateIPBlockCIDRUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testCases := map[string]struct { + oldObj networking.NetworkPolicy + updateObj networking.NetworkPolicy + expectedErrs field.ErrorList + }{ + "valid update ingress rule with CIDR": { + oldObj: mkValidNetworkPolicy("ingress"), + updateObj: mkValidNetworkPolicy("ingress", tweakIngressFromIPBlock("10.0.0.0/8")), + }, + "valid update egress rule with CIDR": { + oldObj: mkValidNetworkPolicy("egress"), + updateObj: mkValidNetworkPolicy("egress", tweakEgressToIPBlock("10.0.0.0/8")), + }, + "update ingress rule rejects empty CIDR in ipBlock": { + oldObj: mkValidNetworkPolicy("ingress"), + updateObj: mkValidNetworkPolicy("ingress", tweakIngressFromIPBlock("")), + expectedErrs: field.ErrorList{ + field.Required( + field.NewPath("spec", "ingress").Index(0).Child("from").Index(0).Child("ipBlock", "cidr"), + "", + ).MarkAlpha(), + }, + }, + + "update egress rule rejects empty CIDR in ipBlock": { + oldObj: mkValidNetworkPolicy("egress"), + updateObj: mkValidNetworkPolicy("egress", tweakEgressToIPBlock("")), + expectedErrs: field.ErrorList{ + field.Required( + field.NewPath("spec", "egress").Index(0).Child("to").Index(0).Child("ipBlock", "cidr"), + "", + ).MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "networking.k8s.io", + APIVersion: apiVersion, + Resource: "networkpolicies", + Name: "valid-network-policy", + IsResourceRequest: true, + Verb: "update", + }, + ) + + apitesting.VerifyUpdateValidationEquivalence( + t, + ctx, + &tc.updateObj, + &tc.oldObj, + Strategy.ValidateUpdate, + tc.expectedErrs, + ) + }) + } + }) + } +} + +func tweakIngressFromIPBlock(cidr string) func(obj *networking.NetworkPolicy) { + return func(obj *networking.NetworkPolicy) { + obj.Spec.Ingress[0].From[0].IPBlock.CIDR = cidr + } +} + +func tweakEgressToIPBlock(cidr string) func(obj *networking.NetworkPolicy) { + return func(obj *networking.NetworkPolicy) { + obj.Spec.Egress[0].To[0].IPBlock.CIDR = cidr + } +} +func mkValidNetworkPolicy(ruleType string, tweaks ...func(obj *networking.NetworkPolicy)) networking.NetworkPolicy { + obj := networking.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-network-policy", + Namespace: "default", + }, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + }, + } + + switch ruleType { + case "ingress": + obj.Spec.Ingress = []networking.NetworkPolicyIngressRule{ + { + From: []networking.NetworkPolicyPeer{ + { + IPBlock: &networking.IPBlock{ + CIDR: "192.168.1.0/24", + }, + }, + }, + }, + } + case "egress": + obj.Spec.Egress = []networking.NetworkPolicyEgressRule{ + { + To: []networking.NetworkPolicyPeer{ + { + IPBlock: &networking.IPBlock{ + CIDR: "192.168.1.0/24", + }, + }, + }, + }, + } + } + + obj.ResourceVersion = "1" + + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} diff --git a/pkg/registry/networking/networkpolicy/strategy.go b/pkg/registry/networking/networkpolicy/strategy.go index 22b8deb144d1d..d2fb8c5902ebc 100644 --- a/pkg/registry/networking/networkpolicy/strategy.go +++ b/pkg/registry/networking/networkpolicy/strategy.go @@ -18,6 +18,8 @@ package networkpolicy import ( "context" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apiserver/pkg/registry/rest" "reflect" "k8s.io/apimachinery/pkg/runtime" @@ -66,7 +68,9 @@ func (networkPolicyStrategy) PrepareForUpdate(ctx context.Context, obj, old runt func (networkPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { networkPolicy := obj.(*networking.NetworkPolicy) ops := validation.ValidationOptionsForNetworking(networkPolicy, nil) - return validation.ValidateNetworkPolicy(networkPolicy, ops) + allErrs := validation.ValidateNetworkPolicy(networkPolicy, ops) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, networkPolicy, nil, allErrs, operation.Create) + } // WarningsOnCreate returns warnings for the creation of the given object. @@ -85,9 +89,8 @@ func (networkPolicyStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (networkPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { opts := validation.ValidationOptionsForNetworking(obj.(*networking.NetworkPolicy), old.(*networking.NetworkPolicy)) - validationErrorList := validation.ValidateNetworkPolicy(obj.(*networking.NetworkPolicy), opts) - updateErrorList := validation.ValidateNetworkPolicyUpdate(obj.(*networking.NetworkPolicy), old.(*networking.NetworkPolicy), opts) - return append(validationErrorList, updateErrorList...) + allErrs := validation.ValidateNetworkPolicyUpdate(obj.(*networking.NetworkPolicy), old.(*networking.NetworkPolicy), opts) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/networking/servicecidr/strategy.go b/pkg/registry/networking/servicecidr/strategy.go index 3c80115ba9ee6..c48e00007a961 100644 --- a/pkg/registry/networking/servicecidr/strategy.go +++ b/pkg/registry/networking/servicecidr/strategy.go @@ -19,15 +19,18 @@ package servicecidr import ( "context" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/networking/validation" - "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "k8s.io/kubernetes/pkg/features" ) // serviceCIDRStrategy implements verification logic for ServiceCIDR allocators. @@ -54,20 +57,22 @@ func (serviceCIDRStrategy) NamespaceScoped() bool { // and should not be modified by the user. func (serviceCIDRStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { fields := map[fieldpath.APIVersion]*fieldpath.Set{ - "networking/v1": fieldpath.NewSet( - fieldpath.MakePathOrDie("status"), - ), - "networking/v1beta1": fieldpath.NewSet( - fieldpath.MakePathOrDie("status"), - ), + "networking.k8s.io/v1": fieldpath.NewSet(), + "networking.k8s.io/v1beta1": fieldpath.NewSet(), + } + if utilfeature.DefaultFeatureGate.Enabled(features.ServiceCIDRStatusFieldWiping) { + fields["networking.k8s.io/v1"].Insert(fieldpath.MakePathOrDie("status")) + fields["networking.k8s.io/v1beta1"].Insert(fieldpath.MakePathOrDie("status")) } return fields } // PrepareForCreate clears the status of an ServiceCIDR before creation. func (serviceCIDRStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { - _ = obj.(*networking.ServiceCIDR) - + cidr := obj.(*networking.ServiceCIDR) + if utilfeature.DefaultFeatureGate.Enabled(features.ServiceCIDRStatusFieldWiping) { + cidr.Status = networking.ServiceCIDRStatus{} + } } // PrepareForUpdate clears fields that are not allowed to be set by end users on update. @@ -75,7 +80,9 @@ func (serviceCIDRStrategy) PrepareForUpdate(ctx context.Context, obj, old runtim newServiceCIDR := obj.(*networking.ServiceCIDR) oldServiceCIDR := old.(*networking.ServiceCIDR) - _, _ = newServiceCIDR, oldServiceCIDR + if utilfeature.DefaultFeatureGate.Enabled(features.ServiceCIDRStatusFieldWiping) { + newServiceCIDR.Status = oldServiceCIDR.Status + } } // Validate validates a new ServiceCIDR. @@ -128,10 +135,12 @@ var StatusStrategy = serviceCIDRStatusStrategy{Strategy} // and should not be modified by the user. func (serviceCIDRStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { fields := map[fieldpath.APIVersion]*fieldpath.Set{ - "networking/v1": fieldpath.NewSet( + "networking.k8s.io/v1": fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata"), fieldpath.MakePathOrDie("spec"), ), - "networking/v1beta1": fieldpath.NewSet( + "networking.k8s.io/v1beta1": fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata"), fieldpath.MakePathOrDie("spec"), ), } diff --git a/pkg/registry/node/runtimeclass/declarative_validation_test.go b/pkg/registry/node/runtimeclass/declarative_validation_test.go new file mode 100644 index 0000000000000..b0633f6279afc --- /dev/null +++ b/pkg/registry/node/runtimeclass/declarative_validation_test.go @@ -0,0 +1,156 @@ +/* +Copyright 2025 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 runtimeclass + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + node "k8s.io/kubernetes/pkg/apis/node" + "k8s.io/kubernetes/pkg/apis/node/validation" +) + +var apiVersions = []string{"v1", "v1beta1", "v1alpha1"} + +func mkRuntimeClassHandlerOnly(tweaks ...func(*node.RuntimeClass)) node.RuntimeClass { + rc := node.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myrc", + }, + Handler: "runc", + } + for _, f := range tweaks { + f(&rc) + } + return rc +} + +func TestRuntimeClass_DeclarativeValidate_Create(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "node.k8s.io", + APIVersion: apiVersion, + }, + ) + + tests := map[string]struct { + obj node.RuntimeClass + expectedErrs field.ErrorList + }{ + "valid handler": { + obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "test" + }), + expectedErrs: field.ErrorList{}, + }, + "empty handler": { + obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "" + }), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("handler"), "must be a valid DNS label").MarkAlpha(), + }, + }, + "handler with special characters": { + obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "asasdasda&^%" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("handler"), + "", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + "handler with uppercase and special characters": { + obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "asasdasda&^%&^%$UUUUUUU" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("handler"), + "", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + "handler exceeds length with invalid characters": { + obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "asasdasda&^%&^%$UUUUUUUaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccccccccccccc" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("handler"), + "", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.obj, Strategy.Validate, tc.expectedErrs, + apitesting.WithNormalizationRules(validation.NodeNormalizationRules...)) + }) + } + }) + } +} + +func TestRuntimeClass_DeclarativeValidate_Update(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "node.k8s.io", + APIVersion: apiVersion, + }, + ) + + tests := map[string]struct { + oldObj, newObj node.RuntimeClass + expectedErrs field.ErrorList + }{ + "no-op update": { + oldObj: mkRuntimeClassHandlerOnly(), + newObj: mkRuntimeClassHandlerOnly(), + expectedErrs: field.ErrorList{}, + }, + "handler changed (immutable)": { + oldObj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "runc" + }), + newObj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) { + rc.Handler = "asadsasdas" + }), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("handler"), "", + "").WithOrigin("immutable").MarkAlpha(), + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.oldObj.ObjectMeta.ResourceVersion = "1" + tc.newObj.ObjectMeta.ResourceVersion = "1" + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.newObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs, apitesting.WithNormalizationRules(validation.NodeNormalizationRules...)) + }) + } + }) + } +} diff --git a/pkg/registry/node/runtimeclass/strategy.go b/pkg/registry/node/runtimeclass/strategy.go index 6acc70d9b30df..bbfece5cdd047 100644 --- a/pkg/registry/node/runtimeclass/strategy.go +++ b/pkg/registry/node/runtimeclass/strategy.go @@ -18,6 +18,7 @@ package runtimeclass import ( "context" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" @@ -70,7 +71,9 @@ func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { // Validate validates a new RuntimeClass. Validation must check for a correct signature. func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { runtimeClass := obj.(*node.RuntimeClass) - return validation.ValidateRuntimeClass(runtimeClass) + allErrs := validation.ValidateRuntimeClass(runtimeClass) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, runtimeClass, nil, allErrs, operation.Create, rest.WithNormalizationRules(validation.NodeNormalizationRules)) + } // WarningsOnCreate returns warnings for the creation of the given object. @@ -87,7 +90,9 @@ func (strategy) Canonicalize(obj runtime.Object) { func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { newObj := obj.(*node.RuntimeClass) errorList := validation.ValidateRuntimeClass(newObj) - return append(errorList, validation.ValidateRuntimeClassUpdate(newObj, old.(*node.RuntimeClass))...) + errorList = append(errorList, validation.ValidateRuntimeClassUpdate(newObj, old.(*node.RuntimeClass))...) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newObj, old, errorList, operation.Update, rest.WithNormalizationRules(validation.NodeNormalizationRules)) + } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/rbac/clusterrole/declarative_validation_test.go b/pkg/registry/rbac/clusterrole/declarative_validation_test.go index 89689554eee50..f401bdcc6006e 100644 --- a/pkg/registry/rbac/clusterrole/declarative_validation_test.go +++ b/pkg/registry/rbac/clusterrole/declarative_validation_test.go @@ -19,6 +19,7 @@ package clusterrole import ( "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" apitesting "k8s.io/kubernetes/pkg/api/testing" @@ -42,6 +43,21 @@ func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) { input rbac.ClusterRole expectedErrs field.ErrorList }{ + "valid": { + input: mkValidClusterRole(), + }, + "invalid ClusterRole missing verbs": { + input: mkValidClusterRole(tweakVerbs(nil)), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("rules").Index(0).Child("verbs"), "").MarkAlpha(), + }, + }, + "invalid ClusterRole empty verbs": { + input: mkValidClusterRole(tweakVerbs([]string{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("rules").Index(0).Child("verbs"), "").MarkAlpha(), + }, + }, // TODO: Add more test cases } for k, tc := range testCases { @@ -67,11 +83,50 @@ func testValidateUpdateForDeclarative(t *testing.T, apiVersion string) { update rbac.ClusterRole expectedErrs field.ErrorList }{ + "valid update": { + old: mkValidClusterRole(), + update: mkValidClusterRole(), + }, + "invalid update clearing verbs": { + old: mkValidClusterRole(), + update: mkValidClusterRole(tweakVerbs([]string{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("rules").Index(0).Child("verbs"), "").MarkAlpha(), + }, + }, // TODO: Add more test cases } for k, tc := range testCases { t.Run(k, func(t *testing.T) { + tc.old.ResourceVersion = "1" + tc.update.ResourceVersion = "2" apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs) }) } } + +func mkValidClusterRole(tweaks ...func(*rbac.ClusterRole)) rbac.ClusterRole { + cr := rbac.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-cluster-role", + }, + Rules: []rbac.PolicyRule{{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list"}, + }}, + } + + for _, tweak := range tweaks { + tweak(&cr) + } + return cr +} + +func tweakVerbs(verbs []string) func(*rbac.ClusterRole) { + return func(cr *rbac.ClusterRole) { + if len(cr.Rules) > 0 { + cr.Rules[0].Verbs = verbs + } + } +} diff --git a/pkg/registry/rbac/role/declarative_validation_test.go b/pkg/registry/rbac/role/declarative_validation_test.go new file mode 100644 index 0000000000000..73ce636f1274e --- /dev/null +++ b/pkg/registry/rbac/role/declarative_validation_test.go @@ -0,0 +1,146 @@ +/* +Copyright 2025 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 role + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + rbac "k8s.io/kubernetes/pkg/apis/rbac" +) + +var apiVersions = []string{"v1", "v1alpha1", "v1beta1"} + +func TestDeclarativeValidateForDeclarative(t *testing.T) { + for _, v := range apiVersions { + t.Run("version="+v, func(t *testing.T) { + testDeclarativeValidateForDeclarative(t, v) + }) + } +} + +func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "rbac.authorization.k8s.io", + APIVersion: apiVersion, + }, + ) + + testCases := map[string]struct { + input rbac.Role + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidRole(), + }, + "invalid Role missing verbs": { + input: mkValidRole(tweakVerbs(nil)), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("rules").Index(0).Child("verbs"), "").MarkAlpha(), + }, + }, + "invalid Role empty verbs": { + input: mkValidRole(tweakVerbs([]string{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("rules").Index(0).Child("verbs"), "").MarkAlpha(), + }, + }, + // TODO: Add more test cases + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestValidateUpdateForDeclarative(t *testing.T) { + for _, v := range apiVersions { + t.Run("version="+v, func(t *testing.T) { + testValidateUpdateForDeclarative(t, v) + }) + } +} + +func testValidateUpdateForDeclarative(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo( + genericapirequest.NewDefaultContext(), + &genericapirequest.RequestInfo{ + APIGroup: "rbac.authorization.k8s.io", + APIVersion: apiVersion, + }, + ) + + testCases := map[string]struct { + old, update rbac.Role + expectedErrs field.ErrorList + }{ + "valid update": { + old: mkValidRole(), + update: mkValidRole(), + }, + "invalid update clearing verbs": { + old: mkValidRole(), + update: mkValidRole(tweakVerbs([]string{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("rules").Index(0).Child("verbs"), "").MarkAlpha(), + }, + }, + // TODO: Add more test cases + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + tc.old.ResourceVersion = "1" + tc.update.ResourceVersion = "2" + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func mkValidRole(tweaks ...func(*rbac.Role)) rbac.Role { + r := rbac.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-role", + Namespace: "default", + }, + Rules: []rbac.PolicyRule{{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list"}, + }}, + } + + for _, tweak := range tweaks { + tweak(&r) + } + return r +} + +func tweakVerbs(verbs []string) func(*rbac.Role) { + return func(r *rbac.Role) { + if len(r.Rules) > 0 { + r.Rules[0].Verbs = verbs + } + } +} diff --git a/pkg/registry/rbac/role/strategy.go b/pkg/registry/rbac/role/strategy.go index d3688a73e4f77..b8098fad6b044 100644 --- a/pkg/registry/rbac/role/strategy.go +++ b/pkg/registry/rbac/role/strategy.go @@ -19,6 +19,7 @@ package role import ( "context" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/registry/rest" @@ -71,7 +72,8 @@ func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { // Validate validates a new Role. Validation must check for a correct signature. func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { role := obj.(*rbac.Role) - return validation.ValidateRole(role) + allErrs := validation.ValidateRole(role) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, role, nil, allErrs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -84,7 +86,10 @@ func (strategy) Canonicalize(obj runtime.Object) { // ValidateUpdate is the default update validation for an end user. func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateRoleUpdate(obj.(*rbac.Role), old.(*rbac.Role)) + newObj := obj.(*rbac.Role) + oldObj := old.(*rbac.Role) + errs := validation.ValidateRoleUpdate(newObj, oldObj) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newObj, oldObj, errs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/rbac/rolebinding/declarative_validation_test.go b/pkg/registry/rbac/rolebinding/declarative_validation_test.go index e26671ee10f3e..5dba716828c42 100644 --- a/pkg/registry/rbac/rolebinding/declarative_validation_test.go +++ b/pkg/registry/rbac/rolebinding/declarative_validation_test.go @@ -59,7 +59,7 @@ func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) { }, }, expectedErrs: field.ErrorList{ - field.Required(field.NewPath("roleRef", "name"), "name is required"), + field.Required(field.NewPath("roleRef", "name"), "name is required").MarkAlpha(), }, }, @@ -76,7 +76,7 @@ func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) { }, }, expectedErrs: field.ErrorList{ - field.Required(field.NewPath("subjects").Index(0).Child("name"), "name is required"), + field.Required(field.NewPath("subjects").Index(0).Child("name"), "name is required").MarkAlpha(), }, }, diff --git a/pkg/registry/resource/deviceclass/declarative_validation_test.go b/pkg/registry/resource/deviceclass/declarative_validation_test.go index 85fc6f175ce48..f4bb221a4ec75 100644 --- a/pkg/registry/resource/deviceclass/declarative_validation_test.go +++ b/pkg/registry/resource/deviceclass/declarative_validation_test.go @@ -55,7 +55,7 @@ func TestDeclarativeValidate(t *testing.T) { "name: empty": { input: mkDeviceClass(tweakName("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("metadata", "name"), ""), + field.Required(field.NewPath("metadata", "name"), "").MarkFromImperative(), }, }, "name: valid": { @@ -64,13 +64,13 @@ func TestDeclarativeValidate(t *testing.T) { "name: invalid (uppercase)": { input: mkDeviceClass(tweakName("Invalid-Name")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "Invalid-Name", "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata", "name"), "Invalid-Name", "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: invalid (start with dash)": { input: mkDeviceClass(tweakName("-invalid")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "-invalid", "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata", "name"), "-invalid", "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "name: max length": { @@ -79,7 +79,7 @@ func TestDeclarativeValidate(t *testing.T) { "name: too long": { input: mkDeviceClass(tweakName(strings.Repeat("a", 254))), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), strings.Repeat("a", 254), "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("metadata", "name"), strings.Repeat("a", 254), "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, // spec.selectors. @@ -89,14 +89,14 @@ func TestDeclarativeValidate(t *testing.T) { "too many selectors": { input: mkDeviceClass(tweakSelectors(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "selectors"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "selectors"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.config "too many configs": { input: mkDeviceClass(tweakConfig(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "config"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "config"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "valid: at limit configs": { @@ -110,31 +110,31 @@ func TestDeclarativeValidate(t *testing.T) { "invalid extended resource name": { input: mkDeviceClass(tweakExtendedResourceName("invalid_name")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "invalid_name", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "invalid_name", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name, no slash": { input: mkDeviceClass(tweakExtendedResourceName("noslash")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "noslash", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "noslash", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name, kubernetes.io domain": { input: mkDeviceClass(tweakExtendedResourceName("kubernetes.io/foo")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "kubernetes.io/foo", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "kubernetes.io/foo", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name, requests. prefix": { input: mkDeviceClass(tweakExtendedResourceName("requests.example.com/foo")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "requests.example.com/foo", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "requests.example.com/foo", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name, too long": { input: mkDeviceClass(tweakExtendedResourceName("example.com/" + strings.Repeat("a", 64))), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "example.com/"+strings.Repeat("a", 64), "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "example.com/"+strings.Repeat("a", 64), "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, // TODO: Add more test cases @@ -174,7 +174,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkDeviceClass(), update: mkDeviceClass(tweakName("test-classx")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "test-classx", "field is immutable"), + field.Invalid(field.NewPath("metadata", "name"), "test-classx", "field is immutable").MarkFromImperative(), }, }, "valid update: at limit selectors": { @@ -185,7 +185,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkDeviceClass(), update: mkDeviceClass(tweakSelectors(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "selectors"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "selectors"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "valid update: at limit configs": { @@ -196,7 +196,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkDeviceClass(), update: mkDeviceClass(tweakConfig(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "config"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "config"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, @@ -209,35 +209,35 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkDeviceClass(), update: mkDeviceClass(tweakExtendedResourceName("invalid_name")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "invalid_name", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "invalid_name", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name update, no slash": { old: mkDeviceClass(), update: mkDeviceClass(tweakExtendedResourceName("noslash")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "noslash", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "noslash", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name update, kubernetes.io domain": { old: mkDeviceClass(), update: mkDeviceClass(tweakExtendedResourceName("kubernetes.io/foo")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "kubernetes.io/foo", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "kubernetes.io/foo", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name update, requests. prefix": { old: mkDeviceClass(), update: mkDeviceClass(tweakExtendedResourceName("requests.example.com/foo")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "requests.example.com/foo", "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "requests.example.com/foo", "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, "invalid extended resource name update, too long": { old: mkDeviceClass(), update: mkDeviceClass(tweakExtendedResourceName("example.com/" + strings.Repeat("a", 64))), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "extendedResourceName"), "example.com/"+strings.Repeat("a", 64), "").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("spec", "extendedResourceName"), "example.com/"+strings.Repeat("a", 64), "").WithOrigin("format=k8s-extended-resource-name").MarkAlpha(), }, }, // TODO: Add more test cases diff --git a/pkg/registry/resource/devicetaintrule/strategy.go b/pkg/registry/resource/devicetaintrule/strategy.go index dc8325ea0518a..5be8912bd5be0 100644 --- a/pkg/registry/resource/devicetaintrule/strategy.go +++ b/pkg/registry/resource/devicetaintrule/strategy.go @@ -18,6 +18,7 @@ package devicetaintrule import ( "context" + "time" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,6 +54,9 @@ func (*deviceTaintRuleStrategy) GetResetFields() map[fieldpath.APIVersion]*field "resource.k8s.io/v1alpha3": fieldpath.NewSet( fieldpath.MakePathOrDie("status"), ), + "resource.k8s.io/v1beta2": fieldpath.NewSet( + fieldpath.MakePathOrDie("status"), + ), } return fields @@ -86,6 +90,23 @@ func (*deviceTaintRuleStrategy) PrepareForUpdate(ctx context.Context, obj, old r oldRule := old.(*resource.DeviceTaintRule) rule.Status = oldRule.Status + // Automatically bump the TimeAdded when the effect changes and the + // client hasn't already changed it. This makes the TimeAdded track + // "when was this *effect* added" instead of "when was this + // *DeviceTaintRule* added". This is relevant for computing the + // toleration duration of affected claims. + // + // The downside is that clients cannot keep the old time even if that + // is what they want - this seems extremely unlikely compared to "I + // want TimeAdded to work automatically". + if rule.Spec.Taint.Effect != oldRule.Spec.Taint.Effect && + rule.Spec.Taint.TimeAdded.Equal(oldRule.Spec.Taint.TimeAdded) { + // Encoding only has seconds as resolution, so don't even + // generate something which does not survive encode/decode (can cause + // random unit test failures). + rule.Spec.Taint.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)} + } + // Any changes to the spec increment the generation number. if !apiequality.Semantic.DeepEqual(oldRule.Spec, rule.Spec) { rule.Generation = oldRule.Generation + 1 @@ -116,6 +137,10 @@ func (*deviceTaintRuleStatusStrategy) GetResetFields() map[fieldpath.APIVersion] fieldpath.MakePathOrDie("metadata"), fieldpath.MakePathOrDie("spec"), ), + "resource.k8s.io/v1beta2": fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata"), + fieldpath.MakePathOrDie("spec"), + ), } return fields diff --git a/pkg/registry/resource/devicetaintrule/strategy_test.go b/pkg/registry/resource/devicetaintrule/strategy_test.go index 19993748c0983..80a602828602f 100644 --- a/pkg/registry/resource/devicetaintrule/strategy_test.go +++ b/pkg/registry/resource/devicetaintrule/strategy_test.go @@ -18,14 +18,22 @@ package devicetaintrule import ( "testing" + "testing/synctest" + "time" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" + apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/kubernetes/pkg/apis/resource" ) +// synctestEpoch is when the clock inside a synctest bubble starts (part of the +// package API, but not defined there in Go). +var synctestEpoch = &metav1.Time{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)} + var obj = &resource.DeviceTaintRule{ ObjectMeta: metav1.ObjectMeta{ Name: "valid-patch", @@ -33,8 +41,9 @@ var obj = &resource.DeviceTaintRule{ }, Spec: resource.DeviceTaintRuleSpec{ Taint: resource.DeviceTaint{ - Key: "example.com/tainted", - Effect: resource.DeviceTaintEffectNoExecute, + Key: "example.com/tainted", + Effect: resource.DeviceTaintEffectNoExecute, + TimeAdded: synctestEpoch, }, }, } @@ -46,8 +55,9 @@ var objWithStatus = &resource.DeviceTaintRule{ }, Spec: resource.DeviceTaintRuleSpec{ Taint: resource.DeviceTaint{ - Key: "example.com/tainted", - Effect: resource.DeviceTaintEffectNoExecute, + Key: "example.com/tainted", + Effect: resource.DeviceTaintEffectNoExecute, + TimeAdded: synctestEpoch, }, }, Status: resource.DeviceTaintRuleStatus{ @@ -132,6 +142,11 @@ func TestDeviceTaintRuleStrategyCreate(t *testing.T) { func TestDeviceTaintRuleStrategyUpdate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() + // The update happens some time after the epoch. + updateDelay := 10 * time.Second + updateTime := &metav1.Time{Time: synctestEpoch.Add(updateDelay)} + oldTime := &metav1.Time{Time: synctestEpoch.Add(updateDelay / 2)} + testcases := map[string]struct { oldObj *resource.DeviceTaintRule newObj *resource.DeviceTaintRule @@ -157,7 +172,7 @@ func TestDeviceTaintRuleStrategyUpdate(t *testing.T) { newObj: objWithStatus, expectObj: obj, }, - "bump-generation": { + "change-effect": { oldObj: obj, newObj: func() *resource.DeviceTaintRule { obj := obj.DeepCopy() @@ -167,6 +182,51 @@ func TestDeviceTaintRuleStrategyUpdate(t *testing.T) { expectObj: func() *resource.DeviceTaintRule { obj := obj.DeepCopy() obj.Spec.Taint.Effect = resource.DeviceTaintEffectNone + // Automatically bumped. + obj.Spec.Taint.TimeAdded = updateTime + obj.Generation++ + return obj + }(), + }, + "change-key": { + oldObj: obj, + newObj: func() *resource.DeviceTaintRule { + obj := obj.DeepCopy() + obj.Spec.Taint.Key += "-other" + return obj + }(), + expectObj: func() *resource.DeviceTaintRule { + obj := obj.DeepCopy() + obj.Spec.Taint.Key += "-other" + obj.Generation++ + return obj + }(), + }, + "change-value": { + oldObj: obj, + newObj: func() *resource.DeviceTaintRule { + obj := obj.DeepCopy() + obj.Spec.Taint.Value = "value" + return obj + }(), + expectObj: func() *resource.DeviceTaintRule { + obj := obj.DeepCopy() + obj.Spec.Taint.Value = "value" + obj.Generation++ + return obj + }(), + }, + "change-time-added": { + oldObj: obj, + newObj: func() *resource.DeviceTaintRule { + obj := obj.DeepCopy() + obj.Spec.Taint.TimeAdded = oldTime + return obj + }(), + expectObj: func() *resource.DeviceTaintRule { + obj := obj.DeepCopy() + // Stored as requested. + obj.Spec.Taint.TimeAdded = oldTime obj.Generation++ return obj }(), @@ -175,28 +235,34 @@ func TestDeviceTaintRuleStrategyUpdate(t *testing.T) { for name, tc := range testcases { t.Run(name, func(t *testing.T) { - oldObj := tc.oldObj.DeepCopy() - newObj := tc.newObj.DeepCopy() - newObj.ResourceVersion = "4" + synctest.Test(t, func(t *testing.T) { + oldObj := tc.oldObj.DeepCopy() + newObj := tc.newObj.DeepCopy() + newObj.ResourceVersion = "4" - Strategy.PrepareForUpdate(ctx, newObj, oldObj) - if errs := Strategy.ValidateUpdate(ctx, newObj, oldObj); len(errs) != 0 { - if tc.expectValidationError == "" { - t.Fatalf("unexpected error(s): %v", errs) + time.Sleep(updateDelay) + + Strategy.PrepareForUpdate(ctx, newObj, oldObj) + if errs := Strategy.ValidateUpdate(ctx, newObj, oldObj); len(errs) != 0 { + if tc.expectValidationError == "" { + t.Fatalf("unexpected error(s): %v", errs) + } + assert.ErrorContains(t, errs[0], tc.expectValidationError, "the error message should have contained the expected error message") + return } - assert.ErrorContains(t, errs[0], tc.expectValidationError, "the error message should have contained the expected error message") - return - } - if tc.expectValidationError != "" { - t.Fatal("expected validation error(s), got none") - } - if warnings := Strategy.WarningsOnUpdate(ctx, newObj, oldObj); len(warnings) != 0 { - t.Fatalf("unexpected warnings: %q", warnings) - } - Strategy.Canonicalize(newObj) - expectObj := tc.expectObj.DeepCopy() - expectObj.ResourceVersion = "4" - assert.Equal(t, expectObj, newObj) + if tc.expectValidationError != "" { + t.Fatal("expected validation error(s), got none") + } + if warnings := Strategy.WarningsOnUpdate(ctx, newObj, oldObj); len(warnings) != 0 { + t.Fatalf("unexpected warnings: %q", warnings) + } + Strategy.Canonicalize(newObj) + expectObj := tc.expectObj.DeepCopy() + expectObj.ResourceVersion = "4" + if !apiequality.Semantic.DeepEqual(expectObj, newObj) { + t.Error(cmp.Diff(expectObj, newObj)) + } + }) }) } } diff --git a/pkg/registry/resource/resourceclaim/declarative_validation_test.go b/pkg/registry/resource/resourceclaim/declarative_validation_test.go index 6527fe379af3b..eb90bad89938a 100644 --- a/pkg/registry/resource/resourceclaim/declarative_validation_test.go +++ b/pkg/registry/resource/resourceclaim/declarative_validation_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/authentication/user" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/client-go/kubernetes/fake" apitesting "k8s.io/kubernetes/pkg/api/testing" @@ -57,7 +58,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { }) fakeClient := fake.NewClientset() mockNSClient := fakeClient.CoreV1().Namespaces() - Strategy := NewStrategy(mockNSClient) + Strategy := NewStrategy(mockNSClient, nil) opaqueDriverPath := field.NewPath("spec", "devices", "config").Index(0).Child("opaque", "driver") @@ -82,82 +83,82 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { "invalid requests, too many": { input: mkValidResourceClaim(tweakDevicesRequests(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid requests, duplicate name": { input: mkValidResourceClaim(tweakAddDeviceRequest(mkDeviceRequest("req-0"))), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec", "devices", "requests").Index(1), "req-0"), + field.Duplicate(field.NewPath("spec", "devices", "requests").Index(1), "req-0").MarkAlpha(), }, }, "invalid requests, too many AND duplicate name (short-circuit check)": { input: mkValidResourceClaim(tweakDevicesRequests(33), tweakAddDeviceRequest(mkDeviceRequest("req-0"))), expectedErrs: field.ErrorList{ // We expect ONLY TooMany, suppressing the Duplicate error because of short-circuiting - field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid constraints, too many": { input: mkValidResourceClaim(tweakDevicesConstraints(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "constraints"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "constraints"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid config, too many": { input: mkValidResourceClaim(tweakDevicesConfigs(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "config"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "config"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid firstAvailable, too many": { input: mkValidResourceClaim(tweakFirstAvailable(9)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid firstAvailable, duplicate name": { input: mkValidResourceClaim(tweakDuplicateFirstAvailableName("sub-0")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(1), "sub-0"), + field.Duplicate(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(1), "sub-0").MarkAlpha(), }, }, "invalid selectors, too many": { input: mkValidResourceClaim(tweakExactlySelectors(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "selectors"), 33, 32).WithOrigin("maxItems").MarkCoveredByDeclarative(), + field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "selectors"), 33, 32).WithOrigin("maxItems").MarkCoveredByDeclarative().MarkAlpha(), }, }, "invalid subrequest selectors, too many": { input: mkValidResourceClaim(tweakSubRequestSelectors(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("selectors"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("selectors"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid constraint requests, too many": { input: mkValidResourceClaim(tweakConstraintRequests(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"), - field.TooMany(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), + field.TooMany(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid config requests, too many": { input: mkValidResourceClaim(tweakConfigRequests(33)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"), - field.TooMany(field.NewPath("spec", "devices", "config").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), + field.TooMany(field.NewPath("spec", "devices", "config").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid constraint requests, duplicate name": { input: mkValidResourceClaim(tweakDuplicateConstraintRequest("req-0")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), "req-0"), + field.Duplicate(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), "req-0").MarkAlpha(), }, }, "invalid config requests, duplicate name": { input: mkValidResourceClaim(tweakDuplicateConfigRequest("req-0")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), "req-0"), + field.Duplicate(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), "req-0").MarkAlpha(), }, }, "valid firstAvailable, max allowed": { @@ -187,25 +188,32 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { "invalid opaque driver, empty": { input: mkValidResourceClaim(tweakDeviceConfigWithDriver("")), expectedErrs: field.ErrorList{ - field.Required(opaqueDriverPath, ""), + field.Required(opaqueDriverPath, "").MarkAlpha(), }, }, - "invalid opaque driver, too long": { + "invalid opaque driver, too long - 64 characters": { input: mkValidResourceClaim(tweakDeviceConfigWithDriver(strings.Repeat("a", 64))), expectedErrs: field.ErrorList{ - field.TooLong(opaqueDriverPath, "", 63), + field.TooLong(opaqueDriverPath, "", 63).MarkAlpha().WithOrigin("maxLength").MarkAlpha(), + }, + }, + "invalid opaque driver, too long - 255 characters": { + input: mkValidResourceClaim(tweakDeviceConfigWithDriver(strings.Repeat("a", 255))), + expectedErrs: field.ErrorList{ + field.TooLong(opaqueDriverPath, "", 63).WithOrigin("maxLength").MarkAlpha(), + field.Invalid(opaqueDriverPath, "", "").WithOrigin("format=k8s-long-name-caseless").MarkAlpha(), }, }, "invalid opaque driver, invalid character": { input: mkValidResourceClaim(tweakDeviceConfigWithDriver("dra_example.com")), expectedErrs: field.ErrorList{ - field.Invalid(opaqueDriverPath, "dra_example.com", "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(opaqueDriverPath, "dra_example.com", "").WithOrigin("format=k8s-long-name-caseless").MarkAlpha(), }, }, "invalid opaque driver, invalid DNS name (leading dot)": { input: mkValidResourceClaim(tweakDeviceConfigWithDriver(".example.com")), expectedErrs: field.ErrorList{ - field.Invalid(opaqueDriverPath, ".example.com", "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(opaqueDriverPath, ".example.com", "").WithOrigin("format=k8s-long-name-caseless").MarkAlpha(), }, }, // spec.Devices.Requests[%d].Exactly.Tolerations.Key @@ -224,7 +232,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { {Key: "invalid_key!", Operator: resource.DeviceTolerationOpExists, Effect: resource.DeviceTaintEffectNoSchedule}, })), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("key"), "invalid_key!", "").WithOrigin("format=k8s-label-key"), + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("key"), "invalid_key!", "").WithOrigin("format=k8s-label-key").MarkAlpha(), }, }, "invalid Exactly.Tolerations.Key - multiple slashes": { @@ -232,7 +240,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { {Key: "a/b/c", Operator: resource.DeviceTolerationOpExists, Effect: resource.DeviceTaintEffectNoSchedule}, })), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("key"), "a/b/c", "").WithOrigin("format=k8s-label-key"), + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("key"), "a/b/c", "").WithOrigin("format=k8s-label-key").MarkAlpha(), }, }, // spec.Devices.Requests[%d].FirsAvailable[%d].Tolerations.Key @@ -251,7 +259,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { {Key: "invalid_key!", Operator: resource.DeviceTolerationOpExists, Effect: resource.DeviceTaintEffectNoSchedule}, })), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("key"), "invalid_key!", "").WithOrigin("format=k8s-label-key"), + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("key"), "invalid_key!", "").WithOrigin("format=k8s-label-key").MarkAlpha(), }, }, "invalid FirstAvailable.Tolerations.Key - multiple slashes": { @@ -259,7 +267,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { {Key: "a/b/c", Operator: resource.DeviceTolerationOpExists, Effect: resource.DeviceTaintEffectNoSchedule}, })), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("key"), "a/b/c", "").WithOrigin("format=k8s-label-key"), + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("key"), "a/b/c", "").WithOrigin("format=k8s-label-key").MarkAlpha(), }, }, "valid DeviceAllocationMode - All": { @@ -272,7 +280,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "allocationMode"), resource.DeviceAllocationMode("InvalidMode"), []string{"All", "ExactCount"}, - ), + ).MarkAlpha(), }, }, "valid DeviceAllocationMode - FirstAvailable": { @@ -285,7 +293,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("allocationMode"), resource.DeviceAllocationMode("InvalidMode"), []string{"All", "ExactCount"}, - ), + ).MarkAlpha(), }, }, // spec.devices.requests[%d].firstAvailable[%d].deviceClassName @@ -296,19 +304,19 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { "invalid firstAvailable class name - invalid characters": { input: mkValidResourceClaim(tweakFirstAvailableDeviceClassName("Class&")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), "Class&", "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), "Class&", "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "invalid firstAvailable class name - long name": { input: mkValidResourceClaim(tweakFirstAvailableDeviceClassName(strings.Repeat("a", 254))), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), "Class&", "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), "Class&", "").WithOrigin("format=k8s-long-name").MarkAlpha(), }, }, "invalid firstAvailable class name - empty": { input: mkValidResourceClaim(tweakFirstAvailableDeviceClassName("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), ""), + field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), "").MarkAlpha(), }, }, "valid DeviceTolerationOperator/Effect - Exactly": { @@ -321,6 +329,14 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { }, })), }, + "invalid DeviceTolerationOperator empty - Exactly": { + input: mkValidResourceClaim( + tweakExactlyTolerations([]resource.DeviceToleration{{Key: "key", Value: "value", Effect: resource.DeviceTaintEffectNoSchedule, Operator: ""}}), + ), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("operator"), "").MarkAlpha(), + }, + }, "invalid DeviceTolerationOperator - Exactly": { input: mkValidResourceClaim(tweakExactlyTolerations([]resource.DeviceToleration{ { @@ -335,7 +351,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("operator"), resource.DeviceTolerationOperator("InvalidOp"), []string{"Equal", "Exists"}, - ), + ).MarkAlpha(), }, }, "invalid DeviceTaintEffect - Exactly": { @@ -352,7 +368,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "tolerations").Index(0).Child("effect"), resource.DeviceTaintEffect("InvalidEffect"), []string{"NoExecute", "NoSchedule"}, - ), + ).MarkAlpha(), }, }, "valid DeviceTolerationOperator/Effect - FirstAvailable": { @@ -365,6 +381,15 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { }, })), }, + "invalid DeviceTolerationOperator empty - FirstAvailable": { + input: mkValidResourceClaim( + tweakFirstAvailable(1), + tweakFirstAvailableTolerations([]resource.DeviceToleration{{Key: "key", Value: "value", Effect: resource.DeviceTaintEffectNoSchedule, Operator: ""}}), + ), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("operator"), "").MarkAlpha(), + }, + }, "invalid DeviceTolerationOperator - FirstAvailable": { input: mkValidResourceClaim(tweakFirstAvailable(1), tweakFirstAvailableTolerations([]resource.DeviceToleration{ { @@ -379,7 +404,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("operator"), resource.DeviceTolerationOperator("InvalidOp"), []string{"Equal", "Exists"}, - ), + ).MarkAlpha(), }, }, "invalid DeviceTaintEffect - FirstAvailable": { @@ -396,7 +421,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations").Index(0).Child("effect"), resource.DeviceTaintEffect("InvalidEffect"), []string{"NoExecute", "NoSchedule"}, - ), + ).MarkAlpha(), }, }, // Spec.Devices.Constraints[%d].MatchAttribute @@ -405,7 +430,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("invalid!"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "invalid!", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "invalid!", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute without domain": { @@ -413,7 +438,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("nodomain"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "nodomain", "a fully qualified name must be a domain and a name separated by a slash").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "nodomain", "a fully qualified name must be a domain and a name separated by a slash").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute empty": { @@ -421,7 +446,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute(""), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute with empty domain": { @@ -429,7 +454,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("/foo"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute with empty name": { @@ -437,7 +462,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("foo/"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute with invalid domain": { @@ -445,7 +470,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("invalid_domain/foo"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "invalid_domain", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "invalid_domain", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute with invalid name": { @@ -453,7 +478,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("domain/invalid-name"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "invalid-name", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "invalid-name", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute with long name": { @@ -461,7 +486,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute("domain/" + strings.Repeat("a", 65)), ), expectedErrs: field.ErrorList{ - field.TooLong(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", 64).WithOrigin("format=k8s-resource-fully-qualified-name"), + field.TooLong(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", 64).WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, "match attribute with long domain": { @@ -469,8 +494,8 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { tweakMatchAttribute(strings.Repeat("a", 254) + "/name"), ), expectedErrs: field.ErrorList{ - field.TooLong(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", 63).WithOrigin("format=k8s-resource-fully-qualified-name"), - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.TooLong(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", 63).WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "", "").WithOrigin("format=k8s-resource-fully-qualified-name").MarkAlpha(), }, }, // TODO: Add more test cases @@ -693,7 +718,7 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { }) fakeClient := fake.NewClientset() mockNSClient := fakeClient.CoreV1().Namespaces() - Strategy := NewStrategy(mockNSClient) + Strategy := NewStrategy(mockNSClient, nil) validClaim := mkValidResourceClaim() // TODO: As we accumulate more and more test cases, consider breaking this // up into smaller tests for maintainability. @@ -710,35 +735,35 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { update: mkValidResourceClaim(tweakSpecChangeClassName("another-class")), old: validClaim, expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: add request": { update: mkValidResourceClaim(tweakAddDeviceRequest(mkDeviceRequest("req-1"))), old: validClaim, expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: remove request": { update: mkValidResourceClaim(tweakSpecRemoveRequest(0)), old: validClaim, expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: add constraint": { update: mkValidResourceClaim(tweakSpecAddConstraint(mkDeviceConstraint())), old: validClaim, expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: short-circuits other errors (e.g. TooMany)": { update: mkValidResourceClaim(tweakDevicesRequests(33)), old: mkValidResourceClaim(), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: add Exactly.Tolerations": { @@ -747,7 +772,7 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { })), old: validClaim, expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: change Exactly.Tolerations.Key": { @@ -758,7 +783,7 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { {Key: "valid-key", Operator: resource.DeviceTolerationOpExists, Effect: resource.DeviceTaintEffectNoSchedule}, })), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, // spec.Devices.Requests[%d].FirsAvailable[%d].Tolerations.Key @@ -768,7 +793,7 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { })), old: mkValidResourceClaim(tweakFirstAvailable(1)), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: change FirstAvailable.Tolerations.Key": { @@ -779,14 +804,14 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { {Key: "valid-key", Operator: resource.DeviceTolerationOpExists, Effect: resource.DeviceTaintEffectNoSchedule}, })), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, "spec immutable: short-circuits deviceClassName error": { update: mkValidResourceClaim(tweakFirstAvailableDeviceClassName("Class")), old: mkValidResourceClaim(), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable"), + field.Invalid(field.NewPath("spec"), "field is immutable", "").WithOrigin("immutable").MarkAlpha(), }, }, // TODO: Add more test cases @@ -811,7 +836,7 @@ func TestValidateStatusUpdateForDeclarative(t *testing.T) { func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { fakeClient := fake.NewClientset() mockNSClient := fakeClient.CoreV1().Namespaces() - Strategy := NewStrategy(mockNSClient) + Strategy := NewStrategy(mockNSClient, &fakeAuthorizer{true}) strategy := NewStatusStrategy(Strategy) ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ @@ -820,6 +845,8 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { Resource: "resourceclaims", Subresource: "status", }) + ctx = genericapirequest.WithUser(ctx, &user.DefaultInfo{Name: "test-user"}) + poolPath := field.NewPath("status", "allocation", "devices", "results").Index(0).Child("pool") configSourcePath := field.NewPath("status", "allocation", "devices", "config").Index(0).Child("source") driverPath := field.NewPath("status", "allocation", "devices", "results").Index(0).Child("driver") @@ -846,28 +873,28 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultDriver("")), expectedErrs: field.ErrorList{ - field.Required(driverPath, ""), + field.Required(driverPath, "").MarkAlpha(), }, }, - "invalid driver name, too long": { + "invalid driver name, too long - 64 characters": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultDriver(strings.Repeat("a", 64))), expectedErrs: field.ErrorList{ - field.TooLong(driverPath, "", 63), + field.TooLong(driverPath, "", 63).MarkAlpha().WithOrigin("maxLength"), }, }, "invalid driver name, invalid character": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultDriver("dra_example.com")), expectedErrs: field.ErrorList{ - field.Invalid(driverPath, "dra_example.com", "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(driverPath, "dra_example.com", "").WithOrigin("format=k8s-long-name-caseless").MarkAlpha(), }, }, "invalid driver name, invalid DNS name (leading dot)": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultDriver(".example.com")), expectedErrs: field.ErrorList{ - field.Invalid(driverPath, ".example.com", "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(driverPath, ".example.com", "").WithOrigin("format=k8s-long-name-caseless").MarkAlpha(), }, }, // .Status.Allocation.Devices.Results[%d].Pool @@ -883,42 +910,42 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultPool("")), expectedErrs: field.ErrorList{ - field.Required(poolPath, ""), + field.Required(poolPath, "").MarkAlpha(), }, }, "invalid pool name, too long": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultPool(strings.Repeat("a", 253) + "/" + strings.Repeat("a", 253))), expectedErrs: field.ErrorList{ - field.TooLong(poolPath, "", 253).WithOrigin("format=k8s-resource-pool-name"), + field.TooLong(poolPath, "", 253).WithOrigin("format=k8s-resource-pool-name").MarkAlpha(), }, }, "invalid pool name, format": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultPool("a/Not_Valid")), expectedErrs: field.ErrorList{ - field.Invalid(poolPath, "Not_Valid", "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(poolPath, "Not_Valid", "").WithOrigin("format=k8s-resource-pool-name").MarkAlpha(), }, }, "invalid pool name, leading slash": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultPool("/a")), expectedErrs: field.ErrorList{ - field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name").MarkAlpha(), }, }, "invalid pool name, trailing slash": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultPool("a/")), expectedErrs: field.ErrorList{ - field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name").MarkAlpha(), }, }, "invalid pool name, double slash": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultPool("a//b")), expectedErrs: field.ErrorList{ - field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name").MarkAlpha(), }, }, // .Status.Allocation.Devices.Results[%d].ShareID @@ -930,14 +957,14 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultShareID("invalid-uid")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "").WithOrigin("format=k8s-uuid").MarkAlpha(), }, }, "invalid uppercase status.Allocation.Devices.Results[].ShareID ": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusDeviceRequestAllocationResultShareID("123e4567-E89b-12d3-A456-426614174000")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "").WithOrigin("format=k8s-uuid").MarkAlpha(), }, }, // .Status.Devices[%d].ShareID @@ -957,8 +984,8 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusAllocatedDeviceStatusShareID("invalid-uid"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), - field.Invalid(field.NewPath("status", "devices").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid").MarkAlpha(), + field.Invalid(field.NewPath("status", "devices").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid").MarkAlpha(), }, }, "invalid upper case status.Devices[].ShareID": { @@ -969,8 +996,8 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusAllocatedDeviceStatusShareID("123e4567-E89b-12d3-A456-426614174000"), ), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), - field.Invalid(field.NewPath("status", "devices").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid").MarkAlpha(), + field.Invalid(field.NewPath("status", "devices").Index(0).Child("shareID"), "invalid-uid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid").MarkAlpha(), }, }, // UID in status.ReservedFor @@ -983,7 +1010,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { resourceClaimReference(validUUID), )), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("status", "reservedFor").Index(2), ""), + field.Duplicate(field.NewPath("status", "reservedFor").Index(2), "").MarkAlpha(), }, }, "multiple- duplicate uid in status.ReservedFor": { @@ -997,8 +1024,8 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { resourceClaimReference(uuid.New().String()), )), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("status", "reservedFor").Index(2), ""), - field.Duplicate(field.NewPath("status", "reservedFor").Index(4), ""), + field.Duplicate(field.NewPath("status", "reservedFor").Index(2), "").MarkAlpha(), + field.Duplicate(field.NewPath("status", "reservedFor").Index(4), "").MarkAlpha(), }, }, "invalid status.ReservedFor, too many": { @@ -1007,7 +1034,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusReservedFor(generateResourceClaimReferences(257)...), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "reservedFor"), 257, 256).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "reservedFor"), 257, 256).WithOrigin("maxItems").MarkAlpha(), }, }, "valid status.ReservedFor, max items": { @@ -1038,35 +1065,35 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { old: mkResourceClaimWithStatus(), update: tweakStatusAllocationDevice(mkResourceClaimWithStatus(), "device-different"), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update"), + field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update").MarkAlpha(), }, }, "invalid status.allocation changed driver (NoModify)": { old: mkResourceClaimWithStatus(), update: tweakStatusAllocationDriver(mkResourceClaimWithStatus(), "different.example.com"), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update"), + field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update").MarkAlpha(), }, }, "invalid status.allocation changed pool (NoModify)": { old: mkResourceClaimWithStatus(), update: tweakStatusAllocationPool(mkResourceClaimWithStatus(), "different-pool"), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update"), + field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update").MarkAlpha(), }, }, "invalid status.allocation added result (NoModify)": { old: mkResourceClaimWithStatus(), update: addStatusAllocationResult(mkResourceClaimWithStatus()), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update"), + field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update").MarkAlpha(), }, }, "invalid status.allocation removed result (NoModify)": { old: addStatusAllocationResult(mkResourceClaimWithStatus()), update: mkResourceClaimWithStatus(), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update"), + field.Invalid(field.NewPath("status", "allocation"), nil, "field is immutable").WithOrigin("update").MarkAlpha(), }, }, "invalid status.allocation.devices.results, too many": { @@ -1075,7 +1102,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusAllocationDevicesResults(33), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "allocation", "devices", "results"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "allocation", "devices", "results"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "valid status.allocation.devices.config, max items": { @@ -1090,7 +1117,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusAllocationDevicesConfig(65), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "allocation", "devices", "config"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "allocation", "devices", "config"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid status.allocation.devices.config requests, duplicate": { @@ -1099,7 +1126,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakAddStatusAllocationConfigRequest("req-0"), ), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), "req-0"), + field.Duplicate(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), "req-0").MarkAlpha(), }, }, // .Status.Allocation.Devices.Config[%d].Source @@ -1115,14 +1142,14 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusAllocationConfigSource("")), expectedErrs: field.ErrorList{ - field.Required(configSourcePath, "").MarkCoveredByDeclarative(), + field.Required(configSourcePath, "").MarkCoveredByDeclarative().MarkAlpha(), }, }, "invalid status.allocation.devices.config source invalid": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus(tweakStatusAllocationConfigSource("invalid")), expectedErrs: field.ErrorList{ - field.NotSupported(configSourcePath, resource.AllocationConfigSource("invalid"), []string{string(resource.AllocationConfigSourceClaim), string(resource.AllocationConfigSourceClass)}).MarkCoveredByDeclarative(), + field.NotSupported(configSourcePath, resource.AllocationConfigSource("invalid"), []string{string(resource.AllocationConfigSourceClaim), string(resource.AllocationConfigSourceClass)}).MarkCoveredByDeclarative().MarkAlpha(), }, }, // .Status.Devices @@ -1164,7 +1191,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("status", "devices").Index(1), "driver1/pool1/device1"), + field.Duplicate(field.NewPath("status", "devices").Index(1), "driver1/pool1/device1").MarkAlpha(), }, }, "invalid devices, duplicate with share ID": { @@ -1179,7 +1206,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("status", "devices").Index(1), "driver1/pool1/device1"), + field.Duplicate(field.NewPath("status", "devices").Index(1), "driver1/pool1/device1").MarkAlpha(), }, }, // .Status.Allocation.Devices.Results[%d].BindingConditions @@ -1197,7 +1224,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusBindingFailureConditions(1), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems").MarkAlpha(), }, }, "valid binding failure conditions, max items": { @@ -1214,7 +1241,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusBindingFailureConditions(resource.BindingFailureConditionsMaxSize+1), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems").MarkAlpha(), }, }, // .Status.Devices[%d].NetworkData.InterfaceName @@ -1233,6 +1260,21 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), }, + "valid networkdevicedata interfacename with multi-byte characters": { + old: mkValidResourceClaim(), + update: mkResourceClaimWithStatus( + tweakStatusDevices( + resource.AllocatedDeviceStatus{ + Driver: "dra.example.com", + Pool: "pool-0", + Device: "device-0", + NetworkData: &resource.NetworkDeviceData{ + InterfaceName: strings.Repeat("𝄞", resource.NetworkDeviceDataInterfaceNameMaxLength/4), // the G clef unicode character is exactly 4 bytes so repeating this 256/4 times means that it is exactly 256 bytes worth of length and should be valid. + }, + }, + ), + ), + }, "invalid networkdevicedata interfacename too long": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus( @@ -1248,7 +1290,28 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), expectedErrs: field.ErrorList{ - field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "interfaceName"), "", resource.NetworkDeviceDataInterfaceNameMaxLength).MarkCoveredByDeclarative().WithOrigin("maxLength"), + field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "interfaceName"), "", resource.NetworkDeviceDataInterfaceNameMaxLength).MarkCoveredByDeclarative().WithOrigin("maxBytes").MarkAlpha(), + }, + }, + "invalid networkdevicedata interfacename too long with multi-byte characters repeating max bytes length times": { + old: mkValidResourceClaim(), + update: mkResourceClaimWithStatus( + tweakStatusDevices( + resource.AllocatedDeviceStatus{ + Driver: "dra.example.com", + Pool: "pool-0", + Device: "device-0", + NetworkData: &resource.NetworkDeviceData{ + // The G clef unicode character is exactly 4 bytes in length so repeating the character + // the same number of times as the maxBytes payload means that we are guaranteed to fail + // a byte-based length check, but not a character based check. + InterfaceName: strings.Repeat("𝄞", resource.NetworkDeviceDataInterfaceNameMaxLength), + }, + }, + ), + ), + expectedErrs: field.ErrorList{ + field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "interfaceName"), "", resource.NetworkDeviceDataInterfaceNameMaxLength).MarkCoveredByDeclarative().WithOrigin("maxBytes").MarkAlpha(), }, }, "valid status.devices.networkData.hardwareAddress": { @@ -1266,6 +1329,21 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), }, + "valid status.devices.networkData.hardwareAddress with multi-byte characters": { + old: mkValidResourceClaim(), + update: mkResourceClaimWithStatus( + tweakStatusDevices( + resource.AllocatedDeviceStatus{ + Driver: "dra.example.com", + Pool: "pool-0", + Device: "device-0", + NetworkData: &resource.NetworkDeviceData{ + HardwareAddress: strings.Repeat("𝄞", resource.NetworkDeviceDataHardwareAddressMaxLength/4), // the G clef unicode character is exactly 4 bytes so repeating this 256/4 times means that it is exactly 256 bytes worth of length and should be valid. + }, + }, + ), + ), + }, "invalid status.devices.networkData.hardwareAddress too long": { old: mkValidResourceClaim(), update: mkResourceClaimWithStatus( @@ -1281,7 +1359,28 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), expectedErrs: field.ErrorList{ - field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "hardwareAddress"), "", resource.NetworkDeviceDataHardwareAddressMaxLength).MarkCoveredByDeclarative().WithOrigin("maxLength"), + field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "hardwareAddress"), "", resource.NetworkDeviceDataHardwareAddressMaxLength).MarkCoveredByDeclarative().WithOrigin("maxBytes").MarkAlpha(), + }, + }, + "invalid status.devices.networkData.hardwareAddress too long with multi-byte characters repeating max bytes length times": { + old: mkValidResourceClaim(), + update: mkResourceClaimWithStatus( + tweakStatusDevices( + resource.AllocatedDeviceStatus{ + Driver: "dra.example.com", + Pool: "pool-0", + Device: "device-0", + NetworkData: &resource.NetworkDeviceData{ + // The G clef unicode character is exactly 4 bytes in length so repeating the character + // the same number of times as the maxBytes payload means that we are guaranteed to fail + // a byte-based length check, but not a character based check. + HardwareAddress: strings.Repeat("𝄞", resource.NetworkDeviceDataHardwareAddressMaxLength), + }, + }, + ), + ), + expectedErrs: field.ErrorList{ + field.TooLong(field.NewPath("status", "devices").Index(0).Child("networkData", "hardwareAddress"), "", resource.NetworkDeviceDataHardwareAddressMaxLength).MarkCoveredByDeclarative().WithOrigin("maxBytes").MarkAlpha(), }, }, "invalid status.devices.networkData.ips duplicate": { @@ -1299,7 +1398,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { ), ), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("status", "devices").Index(0).Child("networkData", "ips").Index(1), "1.2.3.4/32"), + field.Duplicate(field.NewPath("status", "devices").Index(0).Child("networkData", "ips").Index(1), "1.2.3.4/32").MarkAlpha(), }, }, "invalid status.allocation.devices.config.requests too many": { @@ -1308,7 +1407,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusAllocationConfigRequests(33), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems").MarkAlpha(), }, }, "valid status.allocation.devices.config.requests, max allowed": { @@ -1323,7 +1422,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) { tweakStatusDevicesTooManyIPs(17), ), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("status", "devices").Index(0).Child("networkData", "ips"), 17, 16).WithOrigin("maxItems"), + field.TooMany(field.NewPath("status", "devices").Index(0).Child("networkData", "ips"), 17, 16).WithOrigin("maxItems").MarkAlpha(), }, }, "invalid status.devices.networkData.ips, max allowed": { diff --git a/pkg/registry/resource/resourceclaim/storage/storage.go b/pkg/registry/resource/resourceclaim/storage/storage.go index 308c779788442..885724c95ccc3 100644 --- a/pkg/registry/resource/resourceclaim/storage/storage.go +++ b/pkg/registry/resource/resourceclaim/storage/storage.go @@ -22,10 +22,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/client-go/kubernetes/typed/core/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/kubernetes/pkg/apis/resource" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" @@ -40,11 +41,15 @@ type REST struct { } // NewREST returns a RESTStorage object that will work against ResourceClaims. -func NewREST(optsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (*REST, *StatusREST, error) { +func NewREST(optsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface, authorizer authorizer.Authorizer) (*REST, *StatusREST, error) { if nsClient == nil { return nil, nil, fmt.Errorf("namespace client is required") } - strategy := resourceclaim.NewStrategy(nsClient) + if authorizer == nil { + return nil, nil, fmt.Errorf("authorizer is required") + } + + strategy := resourceclaim.NewStrategy(nsClient, authorizer) store := &genericregistry.Store{ NewFunc: func() runtime.Object { return &resource.ResourceClaim{} }, NewListFunc: func() runtime.Object { return &resource.ResourceClaimList{} }, diff --git a/pkg/registry/resource/resourceclaim/storage/storage_test.go b/pkg/registry/resource/resourceclaim/storage/storage_test.go index 83fc7a8cdfeb1..1f91288be1ee1 100644 --- a/pkg/registry/resource/resourceclaim/storage/storage_test.go +++ b/pkg/registry/resource/resourceclaim/storage/storage_test.go @@ -17,6 +17,7 @@ limitations under the License. package storage import ( + "context" "testing" "github.com/google/go-cmp/cmp" @@ -25,6 +26,8 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" @@ -36,6 +39,13 @@ import ( "k8s.io/kubernetes/pkg/registry/registrytest" ) +type fakeAuthorizer struct { +} + +func (f *fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + return authorizer.DecisionAllow, "ok", nil +} + func newStorage(t *testing.T) (*REST, *StatusREST, *etcd3testing.EtcdTestServer) { etcdStorage, server := registrytest.NewEtcdStorage(t, resource.GroupName) restOptions := generic.RESTOptions{ @@ -46,7 +56,7 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcd3testing.EtcdTestServer) } fakeClient := fake.NewSimpleClientset() mockNSClient := fakeClient.CoreV1().Namespaces() - resourceClaimStorage, statusStorage, err := NewREST(restOptions, mockNSClient) + resourceClaimStorage, statusStorage, err := NewREST(restOptions, mockNSClient, &fakeAuthorizer{}) if err != nil { t.Fatalf("unexpected error from REST storage: %v", err) } @@ -159,6 +169,17 @@ func TestUpdateStatus(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() + ctx = genericapirequest.WithUser(ctx, &user.DefaultInfo{Name: "system:serviceaccount:kube-system:test"}) + ctx = genericapirequest.WithRequestInfo(ctx, &genericapirequest.RequestInfo{ + IsResourceRequest: true, + Verb: "update", + APIGroup: "resource.k8s.io", + APIVersion: "v1", + Resource: "resourceclaims", + Subresource: "status", + Namespace: metav1.NamespaceDefault, + Name: "foo", + }) key, _ := storage.KeyFunc(ctx, "foo") claimStart := validNewClaim("foo", metav1.NamespaceDefault) diff --git a/pkg/registry/resource/resourceclaim/strategy.go b/pkg/registry/resource/resourceclaim/strategy.go index 8ddb811be1e6e..c72251f7f267a 100644 --- a/pkg/registry/resource/resourceclaim/strategy.go +++ b/pkg/registry/resource/resourceclaim/strategy.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" @@ -50,15 +51,17 @@ import ( type resourceclaimStrategy struct { runtime.ObjectTyper names.NameGenerator - nsClient v1.NamespaceInterface + nsClient v1.NamespaceInterface + authorizer authorizer.Authorizer } // NewStrategy is the default logic that applies when creating and updating ResourceClaim objects. -func NewStrategy(nsClient v1.NamespaceInterface) *resourceclaimStrategy { +func NewStrategy(nsClient v1.NamespaceInterface, authorizer authorizer.Authorizer) *resourceclaimStrategy { return &resourceclaimStrategy{ legacyscheme.Scheme, names.SimpleNameGenerator, nsClient, + authorizer, } } @@ -194,6 +197,17 @@ func (r *resourceclaimStatusStrategy) ValidateUpdate(ctx context.Context, obj, o oldAllocationResult = oldClaim.Status.Allocation.Devices.Results } errs := resourceutils.AuthorizedForAdminStatus(ctx, newAllocationResult, oldAllocationResult, newClaim.Namespace, r.nsClient) + + if utilfeature.DefaultFeatureGate.Enabled(features.DRAResourceClaimGranularStatusAuthorization) { + errs = append(errs, resourceutils.AuthorizedForBinding(ctx, field.NewPath("status", "allocation"), r.authorizer, newClaim.Status, oldClaim.Status)...) + + // Only authorize driver device status if the claim is actually allocated since + // we drop all the device status when the claim is deallocated. + if utilfeature.DefaultFeatureGate.Enabled(features.DRAResourceClaimDeviceStatus) && + newClaim.Status.Allocation != nil { + errs = append(errs, resourceutils.AuthorizedForDeviceStatus(ctx, field.NewPath("status", "devices"), r.authorizer, newClaim.Status, oldClaim.Status)...) + } + } errs = append(errs, validation.ValidateResourceClaimStatusUpdate(newClaim, oldClaim)...) return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClaim, oldClaim, errs, operation.Update) } diff --git a/pkg/registry/resource/resourceclaim/strategy_test.go b/pkg/registry/resource/resourceclaim/strategy_test.go index 4481b620f3fa1..3ca2c6ab6f447 100644 --- a/pkg/registry/resource/resourceclaim/strategy_test.go +++ b/pkg/registry/resource/resourceclaim/strategy_test.go @@ -17,6 +17,7 @@ limitations under the License. package resourceclaim import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -25,12 +26,16 @@ import ( apiresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" testclient "k8s.io/client-go/testing" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/resource" "k8s.io/kubernetes/pkg/features" "k8s.io/utils/ptr" @@ -373,6 +378,9 @@ var fieldImmutableError = "field is immutable" var metadataError = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters" var deviceRequestError = "exactly one of `exactly` or `firstAvailable` is required" var constraintError = "matchAttribute: Required value" +var bindingUpdateError = `User "test-user" cannot update resource "resourceclaims/binding" in API group "resource.k8s.io" at the cluster scope: denied` +var deviceAssociatedNodeUpdateError = `User "system:serviceaccount:kube-system:dra-driver" cannot associated-node:update resource "resourceclaims/driver" in API group "resource.k8s.io" in the namespace "default": denied` +var deviceArbitraryNodeUpdateError = `User "test-user" cannot arbitrary-node:update resource "resourceclaims/driver" in API group "resource.k8s.io" in the namespace "default": denied` const ( req0 = "req-0" @@ -383,6 +391,7 @@ const ( testDriver = "test-driver" testPool = "test-pool" testDevice = "test-device" + testUser = "test-user" ) var ( @@ -396,7 +405,7 @@ var testCapacity = map[resource.QualifiedName]apiresource.Quantity{ func TestStrategy(t *testing.T) { fakeClient := fake.NewSimpleClientset() mockNSClient := fakeClient.CoreV1().Namespaces() - strategy := NewStrategy(mockNSClient) + strategy := NewStrategy(mockNSClient, nil) if !strategy.NamespaceScoped() { t.Errorf("ResourceClaim must be namespace scoped") } @@ -619,13 +628,16 @@ func TestStrategyCreate(t *testing.T) { t.Run(name, func(t *testing.T) { fakeClient := fake.NewSimpleClientset(ns1, ns2) mockNSClient := fakeClient.CoreV1().Namespaces() + if !tc.adminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.DRAAdminAccess: tc.adminAccess, features.DRADeviceTaints: tc.deviceTaints, features.DRAPrioritizedList: tc.prioritizedList, features.DRAConsumableCapacity: tc.consumableCapacity, }) - strategy := NewStrategy(mockNSClient) + strategy := NewStrategy(mockNSClient, nil) obj := tc.obj.DeepCopy() strategy.PrepareForCreate(ctx, obj) @@ -950,6 +962,9 @@ func TestStrategyUpdate(t *testing.T) { fakeClient := fake.NewSimpleClientset(ns1, ns2) mockNSClient := fakeClient.CoreV1().Namespaces() + if !tc.adminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.DRAAdminAccess: tc.adminAccess, features.DRADeviceTaints: tc.deviceTaints, @@ -957,7 +972,7 @@ func TestStrategyUpdate(t *testing.T) { features.DRAConsumableCapacity: tc.consumableCapacity, }) - strategy := NewStrategy(mockNSClient) + strategy := NewStrategy(mockNSClient, nil) oldObj := tc.oldObj.DeepCopy() newObj := tc.newObj.DeepCopy() @@ -989,15 +1004,30 @@ func TestStrategyUpdate(t *testing.T) { func TestStatusStrategyUpdate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() + ctx = genericapirequest.WithUser(ctx, &user.DefaultInfo{ + Name: testUser, + Groups: []string{"system:authenticated"}, + }) + ctx = genericapirequest.WithRequestInfo(ctx, &genericapirequest.RequestInfo{ + IsResourceRequest: true, + Verb: "update", + APIGroup: "resource.k8s.io", + APIVersion: "v1", + Resource: "resourceclaims", + Subresource: "status", + Namespace: metav1.NamespaceDefault, + }) testcases := map[string]struct { oldObj *resource.ResourceClaim newObj *resource.ResourceClaim + authz authorizer.Authorizer + ctxOverride func(context.Context) context.Context // if set, transforms the default ctx adminAccess bool deviceStatusFeatureGate bool consumableCapacityFeatureGate bool prioritizedListFeatureGate bool bindingConditions bool - expectValidationError string + expectValidationErrors []string expectObj *resource.ResourceClaim verify func(*testing.T, []testclient.Action) }{ @@ -1018,7 +1048,7 @@ func TestStatusStrategyUpdate(t *testing.T) { obj.Name += "-2" return obj }(), - expectValidationError: fieldImmutableError, + expectValidationErrors: []string{fieldImmutableError}, verify: func(t *testing.T, as []testclient.Action) { if len(as) != 0 { t.Errorf("expected no action to be taken") @@ -1075,10 +1105,10 @@ func TestStatusStrategyUpdate(t *testing.T) { }, }, "keep-fields-admin-access-NonAdminNamespace": { - oldObj: objInNonAdminNamespace, - newObj: objWithAdminAccessStatusInNonAdminNamespace, - adminAccess: true, - expectValidationError: adminAccessError, + oldObj: objInNonAdminNamespace, + newObj: objWithAdminAccessStatusInNonAdminNamespace, + adminAccess: true, + expectValidationErrors: []string{adminAccessError}, verify: func(t *testing.T, as []testclient.Action) { if len(as) != 1 { t.Errorf("expected one action but got %d", len(as)) @@ -1238,6 +1268,123 @@ func TestStatusStrategyUpdate(t *testing.T) { } }, }, + "fail-update-fields-devices-status-without-permissions": { + oldObj: func() *resource.ResourceClaim { + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + return obj + }(), + newObj: func() *resource.ResourceClaim { // Status is added + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + addStatusDevices(obj, testDriver, testPool, testDevice, nil) + return obj + }(), + adminAccess: true, // Keep emulation version at 1.36 so DRAResourceClaimGranularStatusAuthorization is active + deviceStatusFeatureGate: true, + expectValidationErrors: []string{deviceArbitraryNodeUpdateError}, + expectObj: func() *resource.ResourceClaim { // Status is not updated + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + return obj + }(), + authz: &fakeAuthorizer{false}, + verify: func(t *testing.T, as []testclient.Action) { + if len(as) != 0 { + t.Errorf("expected no action to be taken") + } + }, + }, + "fail-update-fields-devices-status-associated-node-without-permissions": { + oldObj: func() *resource.ResourceClaim { + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + obj.Status.Allocation.NodeSelector = &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"test-node"}, + }}, + }}, + } + return obj + }(), + newObj: func() *resource.ResourceClaim { + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + obj.Status.Allocation.NodeSelector = &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"test-node"}, + }}, + }}, + } + addStatusDevices(obj, testDriver, testPool, testDevice, nil) + return obj + }(), + ctxOverride: func(ctx context.Context) context.Context { + return genericapirequest.WithUser(ctx, &user.DefaultInfo{ + Name: "system:serviceaccount:kube-system:dra-driver", + Groups: []string{"system:authenticated"}, + Extra: map[string][]string{"authentication.kubernetes.io/node-name": {"test-node"}}, + }) + }, + adminAccess: true, + deviceStatusFeatureGate: true, + authz: &fakeAuthorizer{false}, + expectValidationErrors: []string{deviceAssociatedNodeUpdateError}, + expectObj: func() *resource.ResourceClaim { + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + obj.Status.Allocation.NodeSelector = &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"test-node"}, + }}, + }}, + } + return obj + }(), + verify: func(t *testing.T, as []testclient.Action) { + if len(as) != 0 { + t.Errorf("expected no action to be taken") + } + }, + }, + "fail-drop-status-deallocated-device-without-permissions": { + oldObj: func() *resource.ResourceClaim { + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusAllocationDevicesResults(obj, testDriver, testPool, testDevice, testRequest, nil, nil) + addStatusDevices(obj, testDriver, testPool, testDevice, nil) + return obj + }(), + newObj: func() *resource.ResourceClaim { // device is deallocated + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + addStatusDevices(obj, testDriver, testPool, testDevice, nil) + return obj + }(), + adminAccess: true, // Keep emulation version at 1.36 so DRAResourceClaimGranularStatusAuthorization is active + authz: &fakeAuthorizer{false}, + deviceStatusFeatureGate: true, + expectValidationErrors: []string{bindingUpdateError}, + expectObj: func() *resource.ResourceClaim { // Status is no longer there + obj := obj.DeepCopy() + addSpecDevicesRequest(obj, testRequest) + return obj + }(), + verify: func(t *testing.T, as []testclient.Action) { + if len(as) != 0 { + t.Errorf("expected no action to be taken") + } + }, + }, "drop-status-deallocated-device-disable-feature-gate": { oldObj: func() *resource.ResourceClaim { obj := obj.DeepCopy() @@ -1555,8 +1702,15 @@ func TestStatusStrategyUpdate(t *testing.T) { t.Run(name, func(t *testing.T) { fakeClient := fake.NewSimpleClientset(ns1, ns2) mockNSClient := fakeClient.CoreV1().Namespaces() - strategy := NewStrategy(mockNSClient) + authz := tc.authz + if tc.authz == nil { + authz = &fakeAuthorizer{true} + } + strategy := NewStrategy(mockNSClient, authz) + if !tc.adminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.DRAAdminAccess: tc.adminAccess, features.DRAResourceClaimDeviceStatus: tc.deviceStatusFeatureGate, @@ -1570,20 +1724,27 @@ func TestStatusStrategyUpdate(t *testing.T) { }) statusStrategy := NewStatusStrategy(strategy) + ctx := ctx + if tc.ctxOverride != nil { + ctx = tc.ctxOverride(ctx) + } + oldObj := tc.oldObj.DeepCopy() newObj := tc.newObj.DeepCopy() newObj.ResourceVersion = "4" statusStrategy.PrepareForUpdate(ctx, newObj, oldObj) if errs := statusStrategy.ValidateUpdate(ctx, newObj, oldObj); len(errs) != 0 { - if tc.expectValidationError == "" { + if len(tc.expectValidationErrors) == 0 { t.Fatalf("unexpected error(s): %v", errs) } - assert.Len(t, errs, 1, "exactly one error expected") - assert.ErrorContains(t, errs[0], tc.expectValidationError, "the error message should have contained the expected error message") + assert.Len(t, errs, len(tc.expectValidationErrors), "wrong number of validation errors") + for i, expectValidationError := range tc.expectValidationErrors { + assert.ErrorContains(t, errs[i], expectValidationError, "the error message should have contained the expected error message") + } return } - if tc.expectValidationError != "" { + if len(tc.expectValidationErrors) != 0 { t.Fatal("expected validation error(s), got none") } if warnings := statusStrategy.WarningsOnUpdate(ctx, newObj, oldObj); len(warnings) != 0 { @@ -1651,3 +1812,14 @@ func addStatusDevices(resourceClaim *resource.ResourceClaim, driver string, pool ShareID: (*string)(shareID), }) } + +type fakeAuthorizer struct { + verdict bool +} + +func (f *fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + if !f.verdict { + return authorizer.DecisionDeny, "denied", nil + } + return authorizer.DecisionAllow, "default accept", nil +} diff --git a/pkg/registry/resource/resourceclaimtemplate/strategy_test.go b/pkg/registry/resource/resourceclaimtemplate/strategy_test.go index 73f2fae829685..a4254ba65aac3 100644 --- a/pkg/registry/resource/resourceclaimtemplate/strategy_test.go +++ b/pkg/registry/resource/resourceclaimtemplate/strategy_test.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" apiresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" @@ -476,6 +477,9 @@ func TestClaimTemplateStrategyCreate(t *testing.T) { t.Run(name, func(t *testing.T) { fakeClient := fake.NewSimpleClientset(ns1, ns2) mockNSClient := fakeClient.CoreV1().Namespaces() + if !tc.adminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.DRAAdminAccess: tc.adminAccess, features.DRADeviceTaints: tc.deviceTaints, @@ -549,7 +553,6 @@ func TestClaimTemplateStrategyUpdate(t *testing.T) { }) t.Run("adminaccess-update-not-allowed", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, true) ctx := genericapirequest.NewDefaultContext() fakeClient := fake.NewSimpleClientset(ns1, ns2) mockNSClient := fakeClient.CoreV1().Namespaces() @@ -849,6 +852,9 @@ func TestStrategyUpdate(t *testing.T) { fakeClient := fake.NewSimpleClientset(ns1, ns2) mockNSClient := fakeClient.CoreV1().Namespaces() + if !tc.adminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRADeviceTaints, tc.deviceTaints) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList) diff --git a/pkg/registry/resource/resourcepoolstatusrequest/declarative_validation_test.go b/pkg/registry/resource/resourcepoolstatusrequest/declarative_validation_test.go new file mode 100644 index 0000000000000..6085bc0c2a368 --- /dev/null +++ b/pkg/registry/resource/resourcepoolstatusrequest/declarative_validation_test.go @@ -0,0 +1,426 @@ +/* +Copyright 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 resourcepoolstatusrequest + +import ( + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/resource" + "k8s.io/utils/ptr" + + // Ensure all API groups are registered with the scheme + _ "k8s.io/kubernetes/pkg/apis/resource/install" +) + +func TestDeclarativeValidate(t *testing.T) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "resource.k8s.io", + APIVersion: "v1alpha3", + Resource: "resourcepoolstatusrequests", + IsResourceRequest: true, + Verb: "create", + }) + + testCases := map[string]struct { + input resource.ResourcePoolStatusRequest + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidRPSR(), + }, + "empty driver": { + // +k8s:required (standard DV) + input: mkValidRPSR(setDriver("")), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "driver"), "")}, + }, + "invalid driver format": { + // +k8s:format=k8s-long-name-caseless (standard DV) + input: mkValidRPSR(setDriver("invalid driver!")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "driver"), nil, "").WithOrigin("format=k8s-long-name-caseless")}, + }, + "empty pool name": { + // +k8s:format=k8s-resource-pool-name (standard DV) + input: mkValidRPSR(setPoolName("")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "poolName"), nil, "").WithOrigin("format=k8s-resource-pool-name")}, + }, + "invalid pool name format": { + // +k8s:format=k8s-resource-pool-name (standard DV) + input: mkValidRPSR(setPoolName("invalid pool!")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "poolName"), nil, "").WithOrigin("format=k8s-resource-pool-name")}, + }, + "limit zero": { + // +k8s:minimum=1 (standard DV) + input: mkValidRPSR(setLimit(0)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "limit"), nil, "").WithOrigin("minimum")}, + }, + "limit negative": { + // +k8s:minimum=1 (standard DV) + input: mkValidRPSR(setLimit(-1)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "limit"), nil, "").WithOrigin("minimum")}, + }, + "limit too high": { + // +k8s:maximum=1000 (standard DV) + input: mkValidRPSR(setLimit(1001)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "limit"), nil, "").WithOrigin("maximum")}, + }, + "pool name nil": { + // PoolName is optional, nil should be valid + input: mkValidRPSR(func(r *resource.ResourcePoolStatusRequest) { + r.Spec.PoolName = nil + }), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "resource.k8s.io", + APIVersion: "v1alpha3", + Resource: "resourcepoolstatusrequests", + Name: "valid-rpsr", + IsResourceRequest: true, + Verb: "update", + }) + + testCases := map[string]struct { + oldObj resource.ResourcePoolStatusRequest + updateObj resource.ResourcePoolStatusRequest + expectedErrs field.ErrorList + }{ + "valid no-change": { + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(), + }, + "spec changed": { + // +k8s:immutable + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(setDriver("other.example.com")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec"), nil, "field is immutable").WithOrigin("immutable"), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func TestDeclarativeValidateStatusUpdate(t *testing.T) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "resource.k8s.io", + APIVersion: "v1alpha3", + Resource: "resourcepoolstatusrequests", + Name: "valid-rpsr", + IsResourceRequest: true, + Verb: "update", + Subresource: "status", + }) + + testCases := map[string]struct { + oldObj resource.ResourcePoolStatusRequest + updateObj resource.ResourcePoolStatusRequest + expectedErrs field.ErrorList + }{ + "valid status": { + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withValidStatus()), + }, + "pool empty driver": { + // +k8s:required (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.Driver = "" + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Required(field.NewPath("status", "pools").Index(0).Child("driver"), "")}, + }, + "pool invalid driver": { + // +k8s:format=k8s-long-name-caseless (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.Driver = "invalid driver!" + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("driver"), nil, "").WithOrigin("format=k8s-long-name-caseless")}, + }, + "pool empty poolName": { + // +k8s:required (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.PoolName = "" + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Required(field.NewPath("status", "pools").Index(0).Child("poolName"), "")}, + }, + "pool invalid poolName": { + // +k8s:format=k8s-resource-pool-name (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.PoolName = "invalid pool!" + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("poolName"), nil, "").WithOrigin("format=k8s-resource-pool-name")}, + }, + "pool nil optional fields valid": { + // Device count fields are optional (may be unset when validationError is set) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + s.Pools = []resource.PoolStatus{ + { + Driver: "test.example.com", + PoolName: "pool-1", + ResourceSliceCount: ptr.To[int32](1), + Generation: 1, + // All optional pointer fields are nil - this is valid + }, + } + })), + }, + "pool negative values": { + // +k8s:minimum=0 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.TotalDevices = ptr.To[int32](-1) + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("totalDevices"), nil, "").WithOrigin("minimum")}, + }, + "pool zero resourceSliceCount": { + // +k8s:minimum=1 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.ResourceSliceCount = ptr.To[int32](0) + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("resourceSliceCount"), nil, "").WithOrigin("minimum")}, + }, + "negative poolCount": { + // +k8s:minimum=0 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + s.PoolCount = ptr.To[int32](-1) + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "poolCount"), nil, "").WithOrigin("minimum")}, + }, + "pool validationError too long": { + // +k8s:maxBytes=256 + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + longErr := strings.Repeat("x", 257) + pool.ValidationError = &longErr + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.TooLong(field.NewPath("status", "pools").Index(0).Child("validationError"), "", 256).WithOrigin("maxBytes")}, + }, + "poolCount zero": { + // +k8s:minimum=0 boundary — zero should be valid + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + s.PoolCount = ptr.To[int32](0) + })), + }, + "pools maxItems exceeded": { + // +k8s:maxItems=1000 + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + s.PoolCount = ptr.To[int32](1001) + pools := make([]resource.PoolStatus, 1001) + for i := range pools { + pools[i] = mkValidPoolStatus() + } + s.Pools = pools + })), + expectedErrs: field.ErrorList{field.TooMany(field.NewPath("status", "pools"), 1001, 1000).WithOrigin("maxItems")}, + }, + "conditions maxItems exceeded": { + // +k8s:maxItems=10 + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + now := metav1.Now() + conditions := make([]metav1.Condition, 11) + for i := range conditions { + conditions[i] = metav1.Condition{ + Type: "Type" + strings.Repeat("x", i), + Status: metav1.ConditionTrue, + LastTransitionTime: now, + Reason: "Reason", + } + } + s.Conditions = conditions + })), + expectedErrs: field.ErrorList{field.TooMany(field.NewPath("status", "conditions"), 11, 10).WithOrigin("maxItems")}, + }, + "pool negative allocatedDevices": { + // +k8s:minimum=0 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.AllocatedDevices = ptr.To[int32](-1) + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("allocatedDevices"), nil, "").WithOrigin("minimum")}, + }, + "pool negative availableDevices": { + // +k8s:minimum=0 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.AvailableDevices = ptr.To[int32](-1) + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("availableDevices"), nil, "").WithOrigin("minimum")}, + }, + "pool negative unavailableDevices": { + // +k8s:minimum=0 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.UnavailableDevices = ptr.To[int32](-1) + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("unavailableDevices"), nil, "").WithOrigin("minimum")}, + }, + "pool invalid nodeName": { + // +k8s:format=k8s-long-name (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.NodeName = ptr.To("invalid node!") + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("nodeName"), nil, "").WithOrigin("format=k8s-long-name")}, + }, + "pool negative generation": { + // +k8s:minimum=0 (standard DV) + oldObj: mkValidRPSRForUpdate(), + updateObj: mkValidRPSRForUpdate(withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + pool := mkValidPoolStatus() + pool.Generation = -1 + s.Pools = []resource.PoolStatus{pool} + })), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("status", "pools").Index(0).Child("generation"), nil, "").WithOrigin("minimum")}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, StatusStrategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +// mkValidRPSR creates a valid ResourcePoolStatusRequest with optional tweaks. +func mkValidRPSR(tweaks ...func(*resource.ResourcePoolStatusRequest)) resource.ResourcePoolStatusRequest { + obj := resource.ResourcePoolStatusRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-rpsr", + }, + Spec: resource.ResourcePoolStatusRequestSpec{ + Driver: "test.example.com", + PoolName: ptr.To("pool-1"), + Limit: ptr.To[int32](100), + }, + } + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +// mkValidRPSRForUpdate creates a valid ResourcePoolStatusRequest for update scenarios. +func mkValidRPSRForUpdate(tweaks ...func(*resource.ResourcePoolStatusRequest)) resource.ResourcePoolStatusRequest { + obj := mkValidRPSR(tweaks...) + obj.ResourceVersion = "1" + return obj +} + +func mkValidPoolStatus() resource.PoolStatus { + return resource.PoolStatus{ + Driver: "test.example.com", + PoolName: "pool-1", + TotalDevices: ptr.To[int32](4), + AllocatedDevices: ptr.To[int32](2), + AvailableDevices: ptr.To[int32](2), + UnavailableDevices: ptr.To[int32](0), + ResourceSliceCount: ptr.To[int32](1), + Generation: 1, + } +} + +func setDriver(driver string) func(*resource.ResourcePoolStatusRequest) { + return func(r *resource.ResourcePoolStatusRequest) { + r.Spec.Driver = driver + } +} + +func setPoolName(name string) func(*resource.ResourcePoolStatusRequest) { + return func(r *resource.ResourcePoolStatusRequest) { + r.Spec.PoolName = &name + } +} + +func setLimit(limit int32) func(*resource.ResourcePoolStatusRequest) { + return func(r *resource.ResourcePoolStatusRequest) { + r.Spec.Limit = &limit + } +} + +func withValidStatus() func(*resource.ResourcePoolStatusRequest) { + return withStatus(func(s *resource.ResourcePoolStatusRequestStatus) { + s.Pools = []resource.PoolStatus{mkValidPoolStatus()} + s.PoolCount = ptr.To[int32](1) + }) +} + +// mkBaseStatus creates a status with all required fields populated. +func mkBaseStatus() *resource.ResourcePoolStatusRequestStatus { + return &resource.ResourcePoolStatusRequestStatus{ + PoolCount: ptr.To[int32](0), + } +} + +func withStatus(fn func(*resource.ResourcePoolStatusRequestStatus)) func(*resource.ResourcePoolStatusRequest) { + return func(r *resource.ResourcePoolStatusRequest) { + if r.Status == nil { + r.Status = mkBaseStatus() + } + fn(r.Status) + } +} diff --git a/pkg/registry/resource/resourcepoolstatusrequest/storage/storage.go b/pkg/registry/resource/resourcepoolstatusrequest/storage/storage.go new file mode 100644 index 0000000000000..3f07cb8e222ee --- /dev/null +++ b/pkg/registry/resource/resourcepoolstatusrequest/storage/storage.go @@ -0,0 +1,98 @@ +/* +Copyright 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 storage + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kubernetes/pkg/apis/resource" + "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + printerstorage "k8s.io/kubernetes/pkg/printers/storage" + "k8s.io/kubernetes/pkg/registry/resource/resourcepoolstatusrequest" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// REST implements a RESTStorage for ResourcePoolStatusRequest. +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against ResourcePoolStatusRequest. +func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &resource.ResourcePoolStatusRequest{} }, + NewListFunc: func() runtime.Object { return &resource.ResourcePoolStatusRequestList{} }, + DefaultQualifiedResource: resource.Resource("resourcepoolstatusrequests"), + SingularQualifiedResource: resource.Resource("resourcepoolstatusrequest"), + + CreateStrategy: resourcepoolstatusrequest.Strategy, + UpdateStrategy: resourcepoolstatusrequest.Strategy, + DeleteStrategy: resourcepoolstatusrequest.Strategy, + ReturnDeletedObject: true, + ResetFieldsStrategy: resourcepoolstatusrequest.Strategy, + + TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + return nil, nil, err + } + + statusStore := *store + statusStore.UpdateStrategy = resourcepoolstatusrequest.StatusStrategy + statusStore.ResetFieldsStrategy = resourcepoolstatusrequest.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore}, nil +} + +// StatusREST implements the REST endpoint for changing the status of a ResourcePoolStatusRequest. +type StatusREST struct { + store *genericregistry.Store +} + +// New creates a new ResourcePoolStatusRequest object. +func (r *StatusREST) New() runtime.Object { + return &resource.ResourcePoolStatusRequest{} +} + +func (r *StatusREST) Destroy() { + // Given that underlying store is shared with REST, + // we don't destroy it here explicitly. +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return r.store.GetResetFields() +} diff --git a/pkg/registry/resource/resourcepoolstatusrequest/strategy.go b/pkg/registry/resource/resourcepoolstatusrequest/strategy.go new file mode 100644 index 0000000000000..061e4fba4d8d1 --- /dev/null +++ b/pkg/registry/resource/resourcepoolstatusrequest/strategy.go @@ -0,0 +1,139 @@ +/* +Copyright 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 resourcepoolstatusrequest + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/resource" + "k8s.io/kubernetes/pkg/apis/resource/validation" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// resourcePoolStatusRequestStrategy implements behavior for ResourcePoolStatusRequest objects +type resourcePoolStatusRequestStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +var ( + Strategy = &resourcePoolStatusRequestStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + StatusStrategy = &resourcePoolStatusRequestStatusStrategy{resourcePoolStatusRequestStrategy: Strategy} +) + +func (resourcePoolStatusRequestStrategy) NamespaceScoped() bool { + return false +} + +// GetResetFields returns the set of fields that get reset by the strategy and +// should not be modified by the user. For a new ResourcePoolStatusRequest that is the +// status. +func (*resourcePoolStatusRequestStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + "resource.k8s.io/v1alpha3": fieldpath.NewSet( + fieldpath.MakePathOrDie("status"), + ), + } + + return fields +} + +func (*resourcePoolStatusRequestStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + request := obj.(*resource.ResourcePoolStatusRequest) + // Status must not be set by user on create. + request.Status = nil +} + +func (*resourcePoolStatusRequestStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + request := obj.(*resource.ResourcePoolStatusRequest) + allErrs := validation.ValidateResourcePoolStatusRequest(request) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, allErrs, operation.Create, rest.WithDeclarativeEnforcement()) +} + +func (*resourcePoolStatusRequestStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + +func (*resourcePoolStatusRequestStrategy) Canonicalize(obj runtime.Object) { +} + +func (*resourcePoolStatusRequestStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (*resourcePoolStatusRequestStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + request := obj.(*resource.ResourcePoolStatusRequest) + oldRequest := old.(*resource.ResourcePoolStatusRequest) + // Status is not updated via the main resource endpoint + request.Status = oldRequest.Status +} + +func (*resourcePoolStatusRequestStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + allErrs := validation.ValidateResourcePoolStatusRequestUpdate(obj.(*resource.ResourcePoolStatusRequest), old.(*resource.ResourcePoolStatusRequest)) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, allErrs, operation.Update, rest.WithDeclarativeEnforcement()) +} + +func (*resourcePoolStatusRequestStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + +func (*resourcePoolStatusRequestStrategy) AllowUnconditionalUpdate() bool { + return false +} + +type resourcePoolStatusRequestStatusStrategy struct { + *resourcePoolStatusRequestStrategy +} + +// GetResetFields returns the set of fields that get reset by the strategy and +// should not be modified by the user. For a status update that is the spec. +func (*resourcePoolStatusRequestStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + "resource.k8s.io/v1alpha3": fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata"), + fieldpath.MakePathOrDie("spec"), + ), + } + + return fields +} + +func (*resourcePoolStatusRequestStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newRequest := obj.(*resource.ResourcePoolStatusRequest) + oldRequest := old.(*resource.ResourcePoolStatusRequest) + newRequest.Spec = oldRequest.Spec + metav1.ResetObjectMetaForStatus(&newRequest.ObjectMeta, &oldRequest.ObjectMeta) +} + +func (r *resourcePoolStatusRequestStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + newRequest := obj.(*resource.ResourcePoolStatusRequest) + oldRequest := old.(*resource.ResourcePoolStatusRequest) + allErrs := validation.ValidateResourcePoolStatusRequestStatusUpdate(newRequest, oldRequest) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, allErrs, operation.Update, rest.WithDeclarativeEnforcement()) +} + +// WarningsOnUpdate returns warnings for the given update. +func (*resourcePoolStatusRequestStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/resource/resourceslice/declarative_validation_test.go b/pkg/registry/resource/resourceslice/declarative_validation_test.go index 307efe7f44889..8494e5e5e4d4a 100644 --- a/pkg/registry/resource/resourceslice/declarative_validation_test.go +++ b/pkg/registry/resource/resourceslice/declarative_validation_test.go @@ -60,7 +60,7 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: too many binding conditions": { input: mkResourceSliceWithDevices(tweakBindingConditions(resource.BindingConditionsMaxSize + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.devices[%d].bindingFailureConditions @@ -73,7 +73,7 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: too many binding failure conditions": { input: mkResourceSliceWithDevices(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.Devices[%d].Taints[%d].Effect @@ -88,13 +88,13 @@ func TestDeclarativeValidate(t *testing.T) { expectedErrs: field.ErrorList{ field.NotSupported( field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), - resource.DeviceTaintEffect("Invalid"), []string{}), + resource.DeviceTaintEffect("Invalid"), []string{}).MarkAlpha(), }, }, "invalid: taint empty": { input: mkResourceSliceWithDevices(tweakDeviceTaintEffect("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), ""), + field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), "").MarkAlpha(), }, }, // spec.Devices[%].attribute @@ -110,16 +110,36 @@ func TestDeclarativeValidate(t *testing.T) { "valid: device attribute version": { input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/version", resource.DeviceAttribute{VersionValue: ptr.To("1.2.3")})), }, + "valid: device attribute list of ints": { + input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/list_of_ints", resource.DeviceAttribute{IntValues: []int64{1, 2, 3}})), + }, + "valid: device attribute list of bools": { + input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/list_of_bools", resource.DeviceAttribute{BoolValues: []bool{true, false, true}})), + }, + "valid: device attribute list of strings": { + input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/list_of_strings", resource.DeviceAttribute{StringValues: []string{"a", "b", "c"}})), + }, + "valid: device attribute list of versions": { + input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/list_of_versions", resource.DeviceAttribute{VersionValues: []string{"1.2.3", "2.3.4"}})), + }, "invalid: device attribute with multiple values": { input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""), + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "").WithOrigin("union").MarkAlpha(), }, }, "invalid: device attribute no value": { input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{})), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""), + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "").WithOrigin("union").MarkAlpha(), + }, + }, + "invalid: device attribute list with multiple value types": { + input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/list_of_multiple", resource.DeviceAttribute{IntValues: []int64{1, 2}, BoolValues: []bool{true, false}})), + expectedErrs: field.ErrorList{ + field.Invalid( + field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/list_of_multiple"), "", "", + ).WithOrigin("union").MarkAlpha(), }, }, // spec.sharedCounters @@ -129,7 +149,7 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: too many shared counters": { input: mkResourceSliceWithSharedCounters(tweakSharedCounters(resource.ResourceSliceMaxCounterSets + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec").Child("sharedCounters"), resource.ResourceSliceMaxCounterSets+1, resource.ResourceSliceMaxCounterSets).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec").Child("sharedCounters"), resource.ResourceSliceMaxCounterSets+1, resource.ResourceSliceMaxCounterSets).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.devices.consumesCounters @@ -139,7 +159,7 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: too many device consumes counters": { input: mkResourceSliceWithDevices(tweakDeviceConsumesCounters(resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.sharedCounters.name @@ -149,13 +169,13 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: counter set name": { input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("InvalidKey")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "InvalidKey", "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), }, }, "invalid: counter set name not set": { input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), ""), + field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "").MarkAlpha(), }, }, // spec.devices.consumesCounters.counterSet @@ -165,13 +185,13 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: device consumes counters counter set name": { input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("InvalidKey")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "InvalidKey", "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), }, }, "invalid: device consumes counters counter set name not set": { input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), ""), + field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "").MarkAlpha(), }, }, // spec.sharedCounters @@ -181,7 +201,7 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: duplicate names for shared counters": { input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("duplicate-key", "duplicate-key")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec").Child("sharedCounters").Index(1), "duplicate-key"), + field.Duplicate(field.NewPath("spec").Child("sharedCounters").Index(1), "duplicate-key").MarkAlpha(), }, }, // spec.devices.consumesCounters @@ -191,15 +211,39 @@ func TestDeclarativeValidate(t *testing.T) { "invalid: duplicate names for counter set in device counter consumption": { input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("duplicate-key", "duplicate-key")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec").Child("devices").Index(0).Child("consumesCounters").Index(1), "duplicate-key"), + field.Duplicate(field.NewPath("spec").Child("devices").Index(0).Child("consumesCounters").Index(1), "duplicate-key").MarkAlpha(), }, }, + // spec.sharedCounters.counters + "invalid: shared counter key with uppercase": { + input: mkResourceSliceWithSharedCounters(tweakSharedCounter(counters("InvalidKey"))), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + "valid: shared counter key": { + input: mkResourceSliceWithSharedCounters(tweakSharedCounter(counters("valid-key"))), + }, + // spec.devices.consumesCounters.counters + "invalid: device counter key with uppercase": { + input: mkResourceSliceWithDevices(tweakDeviceCounter(counters("InvalidKey"))), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + "valid: device counter key": { + input: mkResourceSliceWithDevices(tweakDeviceCounter(counters("valid-key"))), + }, // TODO: Add more test cases } for k, tc := range testCases { t.Run(k, func(t *testing.T) { - apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, strategy.Validate, tc.expectedErrs, apitesting.WithNormalizationRules(validation.ResourceNormalizationRules...)) + apitesting.VerifyValidationEquivalence( + t, ctx, &tc.input, strategy.Validate, tc.expectedErrs, + apitesting.WithNormalizationRules(validation.ResourceNormalizationRules...), + apitesting.WithIgnoreObjectConversionErrors(), + ) }) } }) @@ -235,7 +279,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakBindingConditions(resource.BindingConditionsMaxSize + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.devices[%d].bindingFailureConditions @@ -247,7 +291,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.devices.taints.effect @@ -263,14 +307,14 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceTaintEffect("InvalidEffect")), expectedErrs: field.ErrorList{ - field.NotSupported(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), "InvalidEffect", []string{string(resource.DeviceTaintEffectNoSchedule), string(resource.DeviceTaintEffectNoExecute)}), + field.NotSupported(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), "InvalidEffect", []string{string(resource.DeviceTaintEffectNoSchedule), string(resource.DeviceTaintEffectNoExecute)}).MarkAlpha(), }, }, "invalid update: empty taint effect": { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceTaintEffect("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), ""), + field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), "").MarkAlpha(), }, }, "valid update: device attribute": { @@ -281,7 +325,14 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "may have only one of the following fields set: bool, int, string, version"), + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "").WithOrigin("union").MarkAlpha(), + }, + }, + "invalid update: device attribute no value": { + old: mkResourceSliceWithDevices(), + update: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/empty", resource.DeviceAttribute{})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/empty"), "", "").WithOrigin("union").MarkAlpha(), }, }, // spec.sharedCounters @@ -293,7 +344,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithSharedCounters(), update: mkResourceSliceWithSharedCounters(tweakSharedCounters(resource.ResourceSliceMaxCounterSets + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec").Child("sharedCounters"), resource.ResourceSliceMaxCounterSets+1, resource.ResourceSliceMaxCounterSets).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec").Child("sharedCounters"), resource.ResourceSliceMaxCounterSets+1, resource.ResourceSliceMaxCounterSets).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.devices.consumesCounters @@ -305,7 +356,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceConsumesCounters(resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice + 1)), expectedErrs: field.ErrorList{ - field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).WithOrigin("maxItems"), + field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).WithOrigin("maxItems").MarkAlpha(), }, }, // spec.sharedCounters.name @@ -317,14 +368,14 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithSharedCounters(), update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("InvalidKey")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "InvalidKey", "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), }, }, "invalid update: counter set name not set": { old: mkResourceSliceWithSharedCounters(), update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), ""), + field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "").MarkAlpha(), }, }, // spec.devices.consumesCounters.counterSet @@ -336,14 +387,14 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("InvalidKey")), expectedErrs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "InvalidKey", "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), }, }, "invalidupdate: device consumes counters counter set name not set": { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("")), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), ""), + field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "").MarkAlpha(), }, }, // spec.sharedCounters @@ -355,7 +406,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithSharedCounters(), update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("duplicate-key", "duplicate-key")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec").Child("sharedCounters").Index(1), "duplicate-key"), + field.Duplicate(field.NewPath("spec").Child("sharedCounters").Index(1), "duplicate-key").MarkAlpha(), }, }, // spec.devices.consumesCounters @@ -367,11 +418,44 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkResourceSliceWithDevices(), update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("duplicate-key", "duplicate-key")), expectedErrs: field.ErrorList{ - field.Duplicate(field.NewPath("spec").Child("devices").Index(0).Child("consumesCounters").Index(1), "duplicate-key"), + field.Duplicate(field.NewPath("spec").Child("devices").Index(0).Child("consumesCounters").Index(1), "duplicate-key").MarkAlpha(), + }, + }, + // spec.sharedCounters.counters + "invalid update: shared counter key with uppercase": { + old: mkResourceSliceWithSharedCounters(), + update: mkResourceSliceWithSharedCounters(tweakSharedCounter(counters("InvalidKey"))), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + // spec.sharedCounters.counters: nil -> invalid + "invalid update: shared counter key nil to invalid": { + old: mkResourceSliceWithSharedCounters(tweakSharedCounter(nil)), + update: mkResourceSliceWithSharedCounters(tweakSharedCounter(counters("InvalidKey"))), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + // spec.devices.consumesCounters.counters + "invalid update: device counter key with uppercase": { + old: mkResourceSliceWithDevices(), + update: mkResourceSliceWithDevices(tweakDeviceCounter(counters("InvalidKey"))), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), + }, + }, + // spec.devices.consumesCounters.counters: nil -> invalid + "invalid update: device counter key nil to invalid": { + old: mkResourceSliceWithDevices(tweakDeviceCounter(nil)), + update: mkResourceSliceWithDevices(tweakDeviceCounter(counters("InvalidKey"))), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name").MarkAlpha(), }, }, } for k, tc := range testCases { + t.Run(k, func(t *testing.T) { tc.old.ResourceVersion = "1" tc.update.ResourceVersion = "1" @@ -540,3 +624,31 @@ func tweakDeviceConsumesCountersCounterSetName(counterSets ...string) func(*reso rs.Spec.Devices[0].ConsumesCounters = consumesCounters } } + +func tweakSharedCounter(counters map[string]resource.Counter) func(*resource.ResourceSlice) { + return func(rs *resource.ResourceSlice) { + rs.Spec.SharedCounters = []resource.CounterSet{ + { + Name: "shared-counter-set", + Counters: counters, + }, + } + } +} + +func tweakDeviceCounter(counters map[string]resource.Counter) func(*resource.ResourceSlice) { + return func(rs *resource.ResourceSlice) { + rs.Spec.Devices[0].ConsumesCounters = []resource.DeviceCounterConsumption{ + { + CounterSet: "shared-counter-set", + Counters: counters, + }, + } + } +} + +func counters(key string) map[string]resource.Counter { + return map[string]resource.Counter{ + key: {}, + } +} diff --git a/pkg/registry/resource/resourceslice/strategy.go b/pkg/registry/resource/resourceslice/strategy.go index 54f054b4dd3da..9c51468246aa8 100644 --- a/pkg/registry/resource/resourceslice/strategy.go +++ b/pkg/registry/resource/resourceslice/strategy.go @@ -196,6 +196,8 @@ func dropDisabledFields(newSlice, oldSlice *resource.ResourceSlice) { dropDisabledDRAPartitionableDevicesFields(newSlice, oldSlice) dropDisabledDRADeviceBindingConditionsFields(newSlice, oldSlice) dropDisabledDRAConsumableCapacityFields(newSlice, oldSlice) + dropDisabledDRANodeAllocatableResourcesFields(newSlice, oldSlice) + dropDisableDRAListTypeAttributesFields(newSlice, oldSlice) } func dropDisabledDRADeviceTaintsFields(newSlice, oldSlice *resource.ResourceSlice) { @@ -324,3 +326,70 @@ func dropDisabledDRAConsumableCapacityFields(newSlice, oldSlice *resource.Resour } } } + +func dropDisabledDRANodeAllocatableResourcesFields(newSlice, oldSlice *resource.ResourceSlice) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources) || draNodeAllocatableResourcesFeatureInUse(oldSlice) { + return + } + + for i := range newSlice.Spec.Devices { + newSlice.Spec.Devices[i].NodeAllocatableResourceMappings = nil + } +} + +func draNodeAllocatableResourcesFeatureInUse(slice *resource.ResourceSlice) bool { + if slice == nil { + return false + } + + for _, device := range slice.Spec.Devices { + if len(device.NodeAllocatableResourceMappings) > 0 { + return true + } + } + return false +} + +func dropDisableDRAListTypeAttributesFields(newSlice, oldSlice *resource.ResourceSlice) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRAListTypeAttributes) || + draListTypeAttributesFeatureInUse(oldSlice) { + return + } + + for i := range newSlice.Spec.Devices { + for k, deviceAttribute := range newSlice.Spec.Devices[i].Attributes { + if deviceAttribute.BoolValues != nil { + deviceAttribute.BoolValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + if deviceAttribute.IntValues != nil { + deviceAttribute.IntValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + if deviceAttribute.StringValues != nil { + deviceAttribute.StringValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + if deviceAttribute.VersionValues != nil { + deviceAttribute.VersionValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + } + } +} + +func draListTypeAttributesFeatureInUse(slice *resource.ResourceSlice) bool { + if slice == nil { + return false + } + + for _, device := range slice.Spec.Devices { + for _, deviceAttribute := range device.Attributes { + if deviceAttribute.BoolValues != nil || deviceAttribute.IntValues != nil || deviceAttribute.StringValues != nil || deviceAttribute.VersionValues != nil { + return true + } + } + } + + return false +} diff --git a/pkg/registry/resource/resourceslice/strategy_test.go b/pkg/registry/resource/resourceslice/strategy_test.go index 9ae889d5f1a31..2eb24743ec8b1 100644 --- a/pkg/registry/resource/resourceslice/strategy_test.go +++ b/pkg/registry/resource/resourceslice/strategy_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" k8sresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" @@ -187,6 +188,27 @@ var sliceWithConsumableCapacity = func() *resource.ResourceSlice { return obj }() +var sliceWithNodeAllocatableResources = func() *resource.ResourceSlice { + obj := slice.DeepCopy() + instanceQuantity := k8sresource.MustParse("1") + obj.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resource.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + AllocationMultiplier: &instanceQuantity, + }, + } + return obj +}() + +var sliceWithListTypeAttributes = func() *resource.ResourceSlice { + obj := slice.DeepCopy() + obj.Spec.Devices[0].Attributes = map[resource.QualifiedName]resource.DeviceAttribute{ + resource.QualifiedName("list_attribute"): { + StringValues: []string{"value1", "value2"}, + }, + } + return obj +}() + func TestResourceSliceStrategy(t *testing.T) { if Strategy.NamespaceScoped() { t.Errorf("ResourceSlice must not be namespace scoped") @@ -199,14 +221,16 @@ func TestResourceSliceStrategy(t *testing.T) { func TestResourceSliceStrategyCreate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() testCases := map[string]struct { - obj *resource.ResourceSlice - deviceTaints bool - partitionableDevices bool - bindingConditions bool - deviceStatus bool - consumableCapacity bool - expectedValidationError bool - expectObj *resource.ResourceSlice + obj *resource.ResourceSlice + deviceTaints bool + partitionableDevices bool + bindingConditions bool + deviceStatus bool + consumableCapacity bool + draNodeAllocatableResources bool + listTypeAttributes bool + expectedValidationError bool + expectObj *resource.ResourceSlice }{ "simple": { obj: slice, @@ -351,6 +375,38 @@ func TestResourceSliceStrategyCreate(t *testing.T) { return obj }(), }, + "keep-fields-node-allocatable-dra-claims": { + obj: sliceWithNodeAllocatableResources, + draNodeAllocatableResources: true, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithNodeAllocatableResources.DeepCopy() + obj.Generation = 1 + return obj + }(), + }, + "drop-fields-node-allocatable-dra-claims-disabled-feature": { + obj: sliceWithNodeAllocatableResources, + draNodeAllocatableResources: false, + expectObj: func() *resource.ResourceSlice { + obj := slice.DeepCopy() + obj.Generation = 1 + return obj + }(), + }, + "keep-fields-list-type-attributes": { + obj: sliceWithListTypeAttributes, + listTypeAttributes: true, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.Generation = 1 + return obj + }(), + }, + "drop-fields-list-type-attributes": { + obj: sliceWithListTypeAttributes, + listTypeAttributes: false, + expectedValidationError: true, + }, } for name, tc := range testCases { @@ -361,6 +417,8 @@ func TestResourceSliceStrategyCreate(t *testing.T) { features.DRADeviceBindingConditions: tc.bindingConditions, features.DRAResourceClaimDeviceStatus: tc.deviceStatus, features.DRAConsumableCapacity: tc.consumableCapacity, + features.DRANodeAllocatableResources: tc.draNodeAllocatableResources, + features.DRAListTypeAttributes: tc.listTypeAttributes, }) obj := tc.obj.DeepCopy() @@ -392,6 +450,7 @@ func TestResourceSliceStrategyUpdate(t *testing.T) { deviceStatus bool bindingConditions bool consumableCapacity bool + listTypeAttributes bool expectValidationError bool expectObj *resource.ResourceSlice }{ @@ -750,6 +809,44 @@ func TestResourceSliceStrategyUpdate(t *testing.T) { return obj }(), }, + "drop-list-type-attributes": { + oldObj: slice.DeepCopy(), + newObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + listTypeAttributes: false, + expectValidationError: true, + }, + "keep-list-type-attributes": { + oldObj: sliceWithListTypeAttributes, + newObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + listTypeAttributes: true, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + }, + "keep-existing-list-type-attributes-without-featuregate-enabled": { + oldObj: sliceWithListTypeAttributes, + newObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + listTypeAttributes: false, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + }, } for name, tc := range testcases { diff --git a/pkg/registry/resource/rest/storage_resource.go b/pkg/registry/resource/rest/storage_resource.go index f960b6a6462bc..434ea936c2529 100644 --- a/pkg/registry/resource/rest/storage_resource.go +++ b/pkg/registry/resource/rest/storage_resource.go @@ -21,6 +21,7 @@ import ( resourcev1alpha3 "k8s.io/api/resource/v1alpha3" resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" @@ -32,6 +33,7 @@ import ( devicetaintrulestore "k8s.io/kubernetes/pkg/registry/resource/devicetaintrule/storage" resourceclaimstore "k8s.io/kubernetes/pkg/registry/resource/resourceclaim/storage" resourceclaimtemplatestore "k8s.io/kubernetes/pkg/registry/resource/resourceclaimtemplate/storage" + resourcepoolstatusrequeststore "k8s.io/kubernetes/pkg/registry/resource/resourcepoolstatusrequest/storage" resourceslicestore "k8s.io/kubernetes/pkg/registry/resource/resourceslice/storage" ) @@ -41,6 +43,7 @@ import ( type RESTStorageProvider struct { NamespaceClient v1.NamespaceInterface + Authorizer authorizer.Authorizer } func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) { @@ -87,7 +90,7 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API } if resource := "resourceclaims"; apiResourceConfigSource.ResourceEnabled(resourcev1.SchemeGroupVersion.WithResource(resource)) { - resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient) + resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient, p.Authorizer) if err != nil { return nil, err } @@ -126,6 +129,15 @@ func (p RESTStorageProvider) v1alpha3Storage(apiResourceConfigSource serverstora storage[resource+"/status"] = deviceTaintStatusStorage } + if resource := "resourcepoolstatusrequests"; apiResourceConfigSource.ResourceEnabled(resourcev1alpha3.SchemeGroupVersion.WithResource(resource)) { + resourcePoolStatusRequestStorage, resourcePoolStatusRequestStatusStorage, err := resourcepoolstatusrequeststore.NewREST(restOptionsGetter) + if err != nil { + return nil, err + } + storage[resource] = resourcePoolStatusRequestStorage + storage[resource+"/status"] = resourcePoolStatusRequestStatusStorage + } + return storage, nil } @@ -141,7 +153,7 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag } if resource := "resourceclaims"; apiResourceConfigSource.ResourceEnabled(resourcev1beta1.SchemeGroupVersion.WithResource(resource)) { - resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient) + resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient, p.Authorizer) if err != nil { return nil, err } @@ -179,8 +191,17 @@ func (p RESTStorageProvider) v1beta2Storage(apiResourceConfigSource serverstorag storage[resource] = deviceclassStorage } + if resource := "devicetaintrules"; apiResourceConfigSource.ResourceEnabled(resourcev1beta2.SchemeGroupVersion.WithResource(resource)) { + deviceTaintRuleStorage, deviceTaintRuleStatusStorage, err := devicetaintrulestore.NewREST(restOptionsGetter) + if err != nil { + return nil, err + } + storage[resource] = deviceTaintRuleStorage + storage[resource+"/status"] = deviceTaintRuleStatusStorage + } + if resource := "resourceclaims"; apiResourceConfigSource.ResourceEnabled(resourcev1beta2.SchemeGroupVersion.WithResource(resource)) { - resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient) + resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient, p.Authorizer) if err != nil { return nil, err } diff --git a/pkg/registry/resource/utils.go b/pkg/registry/resource/utils.go index fe848efb557a5..9df0a651be2f8 100644 --- a/pkg/registry/resource/utils.go +++ b/pkg/registry/resource/utils.go @@ -20,9 +20,19 @@ import ( "context" "fmt" + resourcev1 "k8s.io/api/resource/v1" + "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/authentication/serviceaccount" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/endpoints/filters" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" v1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/resource" ) @@ -102,3 +112,221 @@ func adminRequested(deviceRequestResults []resource.DeviceRequestAllocationResul } return false, nil } + +// AuthorizedForBinding checks if the caller is authorized to update +// status.allocation and status.reservedFor by verifying permission on the +// synthetic resourceclaims/binding subresource. +func AuthorizedForBinding(ctx context.Context, fieldPath *field.Path, authz authorizer.Authorizer, newStatus, oldStatus resource.ResourceClaimStatus) field.ErrorList { + var allErrs field.ErrorList + + if equality.Semantic.DeepEqual(newStatus.Allocation, oldStatus.Allocation) && + equality.Semantic.DeepEqual(newStatus.ReservedFor, oldStatus.ReservedFor) { + return allErrs + } + + baseAttrs, err := filters.GetAuthorizerAttributes(ctx) + if err != nil { + return append(allErrs, field.InternalError(fieldPath, fmt.Errorf("cannot build authorizer attributes: %w", err))) + } + + attrs := &syntheticSubresourceAttrs{ + Attributes: baseAttrs, + verb: baseAttrs.GetVerb(), // verb is unchanged but must be specified + subresource: resourcev1.SubresourceBinding, // the scheduler calls this "bind claim" + namespace: "", // cluster-wide + name: "", // all names + } + + if err := checkAuthorization(ctx, authz, attrs); err != nil { + return append(allErrs, field.Forbidden(fieldPath, fmt.Sprintf(`changing status.allocation or status.reservedFor requires resource="resourceclaims/binding", verb="%s" permission: %s`, attrs.verb, err))) + } + return allErrs +} + +// AuthorizedForDeviceStatus checks if the caller is authorized to update +// status.devices by performing per-driver authorization checks using the +// associated-node / arbitrary-node verb prefix pattern on the synthetic +// resourceclaims/driver subresource. +func AuthorizedForDeviceStatus(ctx context.Context, fieldPath *field.Path, a authorizer.Authorizer, newStatus, oldStatus resource.ResourceClaimStatus) field.ErrorList { + var allErrs field.ErrorList + + driversToAuthz := getModifiedDrivers(newStatus, oldStatus) + if len(driversToAuthz) == 0 { + return allErrs + } + + baseAttrs, err := filters.GetAuthorizerAttributes(ctx) + if err != nil { + return append(allErrs, field.InternalError(fieldPath, fmt.Errorf("cannot build authorizer attributes: %w", err))) + } + + // if service account is on the same node as the claim, check associated-node verb first, fall back to arbitrary-node + // Otherwise, only try arbitrary-node + requestVerb := baseAttrs.GetVerb() + var verbs []string + if saAssociatedWithAllocatedNode(baseAttrs.GetUser(), nodeNameFromAllocation(newStatus.Allocation)) { + verbs = []string{resourcev1.VerbPrefixAssociatedNode + requestVerb, resourcev1.VerbPrefixArbitraryNode + requestVerb} + } else { + verbs = []string{resourcev1.VerbPrefixArbitraryNode + requestVerb} + } + + for _, driverName := range sets.List(driversToAuthz) { + if err := checkDriverAuthorization(ctx, baseAttrs, verbs, driverName, a); err != nil { + allErrs = append(allErrs, field.Forbidden(fieldPath, fmt.Sprintf(`changing status.devices requires resource="resourceclaims/driver", verb="%s" permission: %s`, verbs, err))) + } + + } + + return allErrs +} + +func checkDriverAuthorization(ctx context.Context, baseAttrs authorizer.Attributes, verbs []string, driverName string, a authorizer.Authorizer) error { + if len(verbs) == 0 { + return fmt.Errorf("no verbs set for driver %s", driverName) // impossible for all inputs today + } + + var firstErr error + for _, verb := range verbs { // verbs are OR'd with each other + attrs := &syntheticSubresourceAttrs{ + Attributes: baseAttrs, + verb: verb, + subresource: resourcev1.SubresourceDriver, + namespace: baseAttrs.GetNamespace(), + name: driverName, + } + err := checkAuthorization(ctx, a, attrs) + if err == nil { + return nil + } + if firstErr == nil { + firstErr = err + } + } + return firstErr +} + +// getModifiedDrivers identifies all drivers whose status entries were added, +// removed, or changed between the old and new ResourceClaim objects. +func getModifiedDrivers(newAllocatedDeviceStatus, oldAllocatedDeviceStatus resource.ResourceClaimStatus) sets.Set[string] { + driversToAuthz := sets.Set[string]{} + + oldDevices := make(map[deviceKey]resource.AllocatedDeviceStatus) + for _, d := range oldAllocatedDeviceStatus.Devices { + oldDevices[makeDeviceKey(d)] = d + } + + // Check for new or modified device entries + for _, d := range newAllocatedDeviceStatus.Devices { + key := makeDeviceKey(d) + oldDevice, ok := oldDevices[key] + delete(oldDevices, key) // Remove from map to track processed devices + + // If entry is new or changed, we need to authorize this driver. + if !ok || !equality.Semantic.DeepEqual(oldDevice, d) { + driversToAuthz.Insert(d.Driver) + } + } + + // Check for removed device entries + for _, d := range oldDevices { + // Any remaining device in oldDevices was removed in rcNew. + driversToAuthz.Insert(d.Driver) + } + + return driversToAuthz +} + +type deviceKey struct { + driver string + pool string + device string + shareID string +} + +func makeDeviceKey(d resource.AllocatedDeviceStatus) deviceKey { + key := deviceKey{ + driver: d.Driver, + pool: d.Pool, + device: d.Device, + } + if d.ShareID != nil { + key.shareID = *d.ShareID + } + return key +} + +func nodeNameFromAllocation(allocation *resource.AllocationResult) string { + if allocation == nil || allocation.NodeSelector == nil { + return "" + } + ns := allocation.NodeSelector + if len(ns.NodeSelectorTerms) != 1 { + return "" + } + term := ns.NodeSelectorTerms[0] + if len(term.MatchExpressions) != 0 || len(term.MatchFields) != 1 { + return "" + } + f := term.MatchFields[0] + if f.Key != "metadata.name" || f.Operator != core.NodeSelectorOpIn || len(f.Values) != 1 { + return "" + } + return f.Values[0] +} + +func saAssociatedWithAllocatedNode(u user.Info, allocatedNodeName string) bool { + if len(allocatedNodeName) == 0 { + return false + } + + // Must be a ServiceAccount + if _, _, err := serviceaccount.SplitUsername(u.GetName()); err != nil { + return false + } + + // Must have exactly one node-name extra attribute + nodeNames := u.GetExtra()[serviceaccount.NodeNameKey] + if len(nodeNames) != 1 { + return false + } + nodeName := nodeNames[0] + + // Must be a valid node name format + if len(validation.ValidateNodeName(nodeName, false)) != 0 { + return false + } + + return nodeName == allocatedNodeName +} + +func checkAuthorization(ctx context.Context, a authorizer.Authorizer, attributes authorizer.Attributes) error { + authorized, reason, err := a.Authorize(ctx, attributes) + + // an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here. + if authorized == authorizer.DecisionAllow { + return nil + } + + msg := reason + switch { + case err != nil && len(reason) > 0: + msg = fmt.Sprintf("%v: %s", err, reason) + case err != nil: + msg = err.Error() + } + + return responsewriters.ForbiddenStatusError(attributes, msg) +} + +type syntheticSubresourceAttrs struct { + authorizer.Attributes + verb string + subresource string + namespace string + name string +} + +func (a *syntheticSubresourceAttrs) GetVerb() string { return a.verb } +func (a *syntheticSubresourceAttrs) GetSubresource() string { return a.subresource } +func (a *syntheticSubresourceAttrs) GetNamespace() string { return a.namespace } +func (a *syntheticSubresourceAttrs) GetName() string { return a.name } diff --git a/pkg/registry/resource/utils_test.go b/pkg/registry/resource/utils_test.go new file mode 100644 index 0000000000000..597f56fcdf7af --- /dev/null +++ b/pkg/registry/resource/utils_test.go @@ -0,0 +1,740 @@ +/* +Copyright 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 resource + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/authentication/serviceaccount" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/resource" +) + +// TestGetModifiedDrivers contains the unit tests for the getModifiedDrivers function. +func TestGetModifiedDrivers(t *testing.T) { + // Helper to create AllocatedDeviceStatus + devStatus := func(driver, pool, device string, network *resource.NetworkDeviceData) resource.AllocatedDeviceStatus { + return resource.AllocatedDeviceStatus{ + Driver: driver, + Pool: pool, + Device: device, + NetworkData: network, + } + } + + // Helper to create ResourceClaimStatus + claimStatus := func(devices ...resource.AllocatedDeviceStatus) resource.ResourceClaimStatus { + return resource.ResourceClaimStatus{ + Devices: devices, + } + } + + testCases := map[string]struct { + newStatus resource.ResourceClaimStatus + oldStatus resource.ResourceClaimStatus + expected sets.Set[string] + }{ + "no changes": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + devStatus("driver-b", "pool-1", "dev-2", nil), + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + devStatus("driver-b", "pool-1", "dev-2", nil), + ), + expected: sets.Set[string]{}, + }, + "add one device": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + devStatus("driver-b", "pool-1", "dev-2", nil), // New + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + ), + expected: sets.New[string]("driver-b"), + }, + "add device for existing driver": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + devStatus("driver-a", "pool-1", "dev-2", nil), // New + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + ), + expected: sets.New[string]("driver-a"), + }, + "remove one device": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + devStatus("driver-b", "pool-1", "dev-2", nil), // Removed + ), + expected: sets.New[string]("driver-b"), + }, + "remove device for driver that still has other devices": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + devStatus("driver-a", "pool-1", "dev-2", nil), // Removed + ), + expected: sets.New[string]("driver-a"), + }, + "modify one device": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0", IPs: []string{"192.168.7.1/24"}}), // Modified + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0"}), + ), + expected: sets.New[string]("driver-a"), + }, + "modify device for driver, no change for other driver": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0", IPs: []string{"192.168.7.1/24"}}), // Modified + devStatus("driver-b", "pool-1", "dev-2", nil), + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0"}), + devStatus("driver-b", "pool-1", "dev-2", nil), + ), + expected: sets.New[string]("driver-a"), + }, + "complex change (add, remove, modify)": { + newStatus: claimStatus( + // driver-a: dev-1 modified + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0", IPs: []string{"192.168.7.1/24"}}), // Modified + // driver-b: dev-2 unchanged + devStatus("driver-b", "pool-1", "dev-2", nil), + // driver-c: dev-3 added + devStatus("driver-c", "pool-1", "dev-3", nil), + ), + oldStatus: claimStatus( + // driver-a: dev-1 old state + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0"}), + // driver-b: dev-2 unchanged + devStatus("driver-b", "pool-1", "dev-2", nil), + // driver-d: dev-4 removed + devStatus("driver-d", "pool-1", "dev-4", nil), + ), + expected: sets.New[string]("driver-a", "driver-c", "driver-d"), + }, + "empty to empty": { + newStatus: claimStatus(), + oldStatus: claimStatus(), + expected: sets.Set[string]{}, + }, + "empty to one device": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + ), + oldStatus: claimStatus(), + expected: sets.New[string]("driver-a"), + }, + "one device to empty": { + newStatus: claimStatus(), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", nil), + ), + expected: sets.New[string]("driver-a"), + }, + "replace device with same key but different content": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0", IPs: []string{"192.168.7.1/24"}}), + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-1", &resource.NetworkDeviceData{InterfaceName: "eth0"}), + ), + expected: sets.New[string]("driver-a"), + }, + "replace device with different key for same driver": { + newStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-NEW", nil), + ), + oldStatus: claimStatus( + devStatus("driver-a", "pool-1", "dev-OLD", nil), + ), + expected: sets.New[string]("driver-a"), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result := getModifiedDrivers(tc.newStatus, tc.oldStatus) + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("Expected driver set %v, but got %v", tc.expected, result) + } + }) + } +} + +// fakeAuthorizer records authorization calls and returns preconfigured decisions. +// The key format is "verb/resource/subresource/name". +type fakeAuthorizer struct { + rules map[string]authorizer.Decision + err error + callCounts map[string]int +} + +func (f *fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + if f.callCounts == nil { + f.callCounts = make(map[string]int) + } + + key := fmt.Sprintf("%s/%s/%s/%s", a.GetVerb(), a.GetResource(), a.GetSubresource(), a.GetName()) + f.callCounts[key]++ + + if f.err != nil { + return authorizer.DecisionDeny, "forced error", f.err + } + if decision, ok := f.rules[key]; ok { + return decision, "", nil + } + return authorizer.DecisionDeny, "no rule matched", nil +} + +// withRequestContext builds a context with user info and request info set, +// simulating what GetAuthorizerAttributes expects. +func withRequestContext(ctx context.Context, u user.Info, verb string) context.Context { + ctx = genericapirequest.WithUser(ctx, u) + ctx = genericapirequest.WithRequestInfo(ctx, &genericapirequest.RequestInfo{ + IsResourceRequest: true, + Verb: verb, + APIGroup: "resource.k8s.io", + APIVersion: "v1", + Resource: "resourceclaims", + Subresource: "status", + Namespace: "default", + Name: "test-claim", + }) + // GetAuthorizerAttributes also needs an http.Request in context for audit, but + // the function doesn't fail without it — we simulate by using a dummy request. + req, _ := http.NewRequestWithContext(ctx, http.MethodPut, "/", nil) + ctx = req.Context() + return ctx +} + +func singleNodeAllocation(nodeName string) *resource.AllocationResult { + return &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{nodeName}}, + }, + }, + }, + }, + } +} + +func TestAuthorizedForBinding(t *testing.T) { + saName := "system:serviceaccount:kube-system:scheduler" + testUser := &user.DefaultInfo{Name: saName} + fp := field.NewPath("status") + + testcases := []struct { + name string + newStatus resource.ResourceClaimStatus + oldStatus resource.ResourceClaimStatus + authz *fakeAuthorizer + expectErrs int + }{ + { + name: "no allocation or reservedFor change, no check needed", + newStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: "d", Pool: "p", Device: "dev"}}, + }, + oldStatus: resource.ResourceClaimStatus{}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{}}, + expectErrs: 0, + }, + { + name: "allocation changed, authorized", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation("node-1"), + }, + oldStatus: resource.ResourceClaimStatus{}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + "update/resourceclaims/binding/": authorizer.DecisionAllow, + }}, + expectErrs: 0, + }, + { + name: "allocation changed, not authorized", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation("node-1"), + }, + oldStatus: resource.ResourceClaimStatus{}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{}}, + expectErrs: 1, + }, + { + name: "reservedFor changed, authorized", + newStatus: resource.ResourceClaimStatus{ + ReservedFor: []resource.ResourceClaimConsumerReference{{Resource: "pods", Name: "pod-1", UID: "uid-1"}}, + }, + oldStatus: resource.ResourceClaimStatus{}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + "update/resourceclaims/binding/": authorizer.DecisionAllow, + }}, + expectErrs: 0, + }, + { + name: "reservedFor changed, not authorized", + newStatus: resource.ResourceClaimStatus{ + ReservedFor: []resource.ResourceClaimConsumerReference{{Resource: "pods", Name: "pod-1", UID: "uid-1"}}, + }, + oldStatus: resource.ResourceClaimStatus{}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{}}, + expectErrs: 1, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := withRequestContext(context.Background(), testUser, "update") + errs := AuthorizedForBinding(ctx, fp, tc.authz, tc.newStatus, tc.oldStatus) + if len(errs) != tc.expectErrs { + t.Errorf("expected %d errors, got %d: %v", tc.expectErrs, len(errs), errs) + } + }) + } +} + +func TestAuthorizedForDeviceStatus(t *testing.T) { + saName := "system:serviceaccount:kube-system:dra-driver" + nodeName := "test-node" + driverName := "test-driver" + fp := field.NewPath("status", "devices") + + testcases := []struct { + name string + newStatus resource.ResourceClaimStatus + oldStatus resource.ResourceClaimStatus + user user.Info + authz *fakeAuthorizer + verb string + expectErrs int + }{ + { + name: "no drivers modified", + newStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{ + {Driver: driverName, Pool: "pool", Device: "device"}, + }, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{ + {Driver: driverName, Pool: "pool", Device: "device"}, + }, + }, + user: &user.DefaultInfo{Name: saName}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{}}, + verb: "update", + expectErrs: 0, + }, + { + name: "associated-node: SA on same node, allowed by associated-node verb", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {nodeName}}}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("associated-node:update/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "update", + expectErrs: 0, + }, + { + name: "associated-node: SA on same node, allowed by arbitrary-node fallback", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {nodeName}}}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("arbitrary-node:update/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "update", + expectErrs: 0, + }, + { + name: "associated-node: SA on same node, neither verb allowed", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {nodeName}}}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{}}, + verb: "update", + expectErrs: 1, + }, + { + name: "SA on different node, only arbitrary-node checked", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation("other-node"), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {nodeName}}}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("arbitrary-node:update/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "update", + expectErrs: 0, + }, + { + name: "SA on different node, associated-node not checked, denied", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation("other-node"), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {nodeName}}}, + // Only grant associated-node, which should NOT be checked since nodes differ + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("associated-node:update/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "update", + expectErrs: 1, + }, + { + name: "no node association (controller), only arbitrary-node checked, allowed", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName}, // no node-name extra + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("arbitrary-node:update/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "update", + expectErrs: 0, + }, + { + name: "no node association (controller), denied", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{}}, + verb: "update", + expectErrs: 1, + }, + { + name: "multi-node claim (no single node in selector), only arbitrary-node", + newStatus: resource.ResourceClaimStatus{ + Allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"node-a", "node-b"}}, + }}, + }, + }, + }, + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {"node-a"}}}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("arbitrary-node:update/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "update", + expectErrs: 0, + }, + { + name: "patch verb propagated", + newStatus: resource.ResourceClaimStatus{ + Allocation: singleNodeAllocation(nodeName), + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-new"}}, + }, + oldStatus: resource.ResourceClaimStatus{ + Devices: []resource.AllocatedDeviceStatus{{Driver: driverName, Pool: "pool", Device: "dev-old"}}, + }, + user: &user.DefaultInfo{Name: saName, Extra: map[string][]string{serviceaccount.NodeNameKey: {nodeName}}}, + authz: &fakeAuthorizer{rules: map[string]authorizer.Decision{ + fmt.Sprintf("associated-node:patch/resourceclaims/driver/%s", driverName): authorizer.DecisionAllow, + }}, + verb: "patch", + expectErrs: 0, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := withRequestContext(context.Background(), tc.user, tc.verb) + errs := AuthorizedForDeviceStatus(ctx, fp, tc.authz, tc.newStatus, tc.oldStatus) + if len(errs) != tc.expectErrs { + t.Errorf("expected %d errors, got %d: %v", tc.expectErrs, len(errs), errs) + } + }) + } +} + +func TestNodeNameFromAllocation(t *testing.T) { + testCases := []struct { + name string + allocation *resource.AllocationResult + expected string + }{ + { + name: "nil allocation", + allocation: nil, + expected: "", + }, + { + name: "nil node selector", + allocation: &resource.AllocationResult{}, + expected: "", + }, + { + name: "exact single-node match", + allocation: singleNodeAllocation("worker-1"), + expected: "worker-1", + }, + { + name: "multiple values", + allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"node-a", "node-b"}}, + }}, + }, + }, + }, + expected: "", + }, + { + name: "match expressions instead of match fields", + allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchExpressions: []core.NodeSelectorRequirement{ + {Key: "kubernetes.io/hostname", Operator: core.NodeSelectorOpIn, Values: []string{"node-1"}}, + }}, + }, + }, + }, + expected: "", + }, + { + name: "multiple terms", + allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"node-1"}}, + }}, + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"node-2"}}, + }}, + }, + }, + }, + expected: "", + }, + { + name: "wrong key", + allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.namespace", Operator: core.NodeSelectorOpIn, Values: []string{"node-1"}}, + }}, + }, + }, + }, + expected: "", + }, + { + name: "wrong operator", + allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpNotIn, Values: []string{"node-1"}}, + }}, + }, + }, + }, + expected: "", + }, + { + name: "extra match fields", + allocation: &resource.AllocationResult{ + NodeSelector: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + {MatchFields: []core.NodeSelectorRequirement{ + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"node-1"}}, + {Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"node-1"}}, + }}, + }, + }, + }, + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := nodeNameFromAllocation(tc.allocation) + if result != tc.expected { + t.Errorf("expected %q, got %q", tc.expected, result) + } + }) + } +} + +func TestSAAssociatedWithAllocatedNode(t *testing.T) { + validSA := &user.DefaultInfo{ + Name: "system:serviceaccount:default:dra-driver-sa", + Extra: map[string][]string{ + serviceaccount.NodeNameKey: {"worker-node-1"}, + }, + } + + testCases := []struct { + name string + userInfo user.Info + allocatedNodeName string + expected bool + }{ + { + name: "SA on matching node", + userInfo: validSA, + allocatedNodeName: "worker-node-1", + expected: true, + }, + { + name: "SA on different node", + userInfo: validSA, + allocatedNodeName: "worker-node-2", + expected: false, + }, + { + name: "empty allocated node name", + userInfo: validSA, + allocatedNodeName: "", + expected: false, + }, + { + name: "not a service account (kubelet identity)", + userInfo: &user.DefaultInfo{ + Name: "system:node:worker-node-1", + Extra: map[string][]string{ + serviceaccount.NodeNameKey: {"worker-node-1"}, + }, + }, + allocatedNodeName: "worker-node-1", + expected: false, + }, + { + name: "not a service account (regular user)", + userInfo: &user.DefaultInfo{ + Name: "jane-doe", + Extra: map[string][]string{ + serviceaccount.NodeNameKey: {"worker-node-1"}, + }, + }, + allocatedNodeName: "worker-node-1", + expected: false, + }, + { + name: "service account missing node name extra attribute", + userInfo: &user.DefaultInfo{ + Name: "system:serviceaccount:default:dra-driver-sa", + Extra: map[string][]string{}, + }, + allocatedNodeName: "worker-node-1", + expected: false, + }, + { + name: "service account with multiple node names in extra attribute", + userInfo: &user.DefaultInfo{ + Name: "system:serviceaccount:default:dra-driver-sa", + Extra: map[string][]string{ + serviceaccount.NodeNameKey: {"worker-node-1", "worker-node-2"}, + }, + }, + allocatedNodeName: "worker-node-1", + expected: false, + }, + { + name: "service account with invalid node name format", + userInfo: &user.DefaultInfo{ + Name: "system:serviceaccount:default:dra-driver-sa", + Extra: map[string][]string{ + serviceaccount.NodeNameKey: {"invalid_node_name!"}, + }, + }, + allocatedNodeName: "invalid_node_name!", + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := saAssociatedWithAllocatedNode(tc.userInfo, tc.allocatedNodeName) + if result != tc.expected { + t.Errorf("Expected %v, got %v", tc.expected, result) + } + }) + } +} diff --git a/pkg/registry/scheduling/podgroup/declarative_validation_test.go b/pkg/registry/scheduling/podgroup/declarative_validation_test.go new file mode 100644 index 0000000000000..2ffa55429c2c4 --- /dev/null +++ b/pkg/registry/scheduling/podgroup/declarative_validation_test.go @@ -0,0 +1,834 @@ +/* +Copyright 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 podgroup + +import ( + "strconv" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" + + // Ensure all API groups are registered with the scheme + _ "k8s.io/kubernetes/pkg/apis/scheduling/install" +) + +var allowedDisruptionModes = sets.New( + scheduling.DisruptionModePod, + scheduling.DisruptionModePodGroup, +) + +func TestDeclarativeValidate(t *testing.T) { + apiVersions := []string{"v1alpha2"} // PodGroup is currently only in v1alpha2 + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidate(t, apiVersion) + }) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: apiVersion, + Resource: "podgroups", + IsResourceRequest: true, + Verb: "create", + }) + strategy := NewStrategy() + + testCases := map[string]struct { + input scheduling.PodGroup + enableTopologyAwareScheduling bool + enableDRAWorkloadResourceClaims bool + enableWorkloadAwarePreemption bool + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidPodGroup(), + }, + "valid with basic policy": { + input: mkValidPodGroup(setBasicPolicy()), + }, + // Declarative validation treats 0 as "missing" and returns Required error + // instead of checking minimum constraint and returning Invalid error. + "gang minCount zero": { + input: mkValidPodGroup(setPodGroupMinCount(0)), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "schedulingPolicy", "gang", "minCount"), "")}, + }, + "gang minCount negative": { + input: mkValidPodGroup(setPodGroupMinCount(-1)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingPolicy", "gang", "minCount"), nil, "").WithOrigin("minimum")}, + }, + "no podGroupTemplateRef": { + input: mkValidPodGroup(unsetPodGroupTemplateRef()), + }, + "empty podGroupTemplateRef": { + input: mkValidPodGroup(setEmptyPodGroupTemplateRef()), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplateRef"), nil, "").WithOrigin("union")}, + }, + "podGroupTemplateRef with empty template name": { + input: mkValidPodGroup(setPodGroupTemplateRef("", "workload")), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "podGroupTemplateRef", "workload", "podGroupTemplateName"), "")}, + }, + "podGroupTemplateRef invalid template name": { + input: mkValidPodGroup(setPodGroupTemplateRef("temp/late", "workload")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplateRef", "workload", "podGroupTemplateName"), nil, "").WithOrigin("format=k8s-short-name")}, + }, + "podGroupTemplateRef with empty workload name": { + input: mkValidPodGroup(setPodGroupTemplateRef("template", "")), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "podGroupTemplateRef", "workload", "workloadName"), "")}, + }, + "podGroupTemplateRef invalid workload name": { + input: mkValidPodGroup(setPodGroupTemplateRef("template", "work/load")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplateRef", "workload", "workloadName"), nil, "").WithOrigin("format=k8s-long-name")}, + }, + "podGroupTemplateRef too long workload name": { + input: mkValidPodGroup(setPodGroupTemplateRef("template", strings.Repeat("g", 254))), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplateRef", "workload", "workloadName"), nil, "").WithOrigin("format=k8s-long-name")}, + }, + "podGroupTemplateRef too long template name": { + input: mkValidPodGroup(setPodGroupTemplateRef(strings.Repeat("g", 254), "workload")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplateRef", "workload", "podGroupTemplateName"), nil, "").WithOrigin("format=k8s-short-name")}, + }, + "policy with neither basic nor gang": { + input: mkValidPodGroup(clearPodGroupPolicy()), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingPolicy"), nil, "").WithOrigin("union")}, + }, + "policy with both basic and gang": { + input: mkValidPodGroup(setBothPolicies()), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingPolicy"), nil, "").WithOrigin("union")}, + }, + "with schedulingConstraints and TAS disabled": { + input: mkValidPodGroup(addTopologyConstraint("foo")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "schedulingConstraints"), "")}, + }, + "valid with schedulingConstraints": { + input: mkValidPodGroup(addTopologyConstraint("foo")), + enableTopologyAwareScheduling: true, + }, + "schedulingConstraints with multiple topology constraints": { + input: mkValidPodGroup(addTopologyConstraint("foo"), addTopologyConstraint("bar")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.TooMany(field.NewPath("spec", "schedulingConstraints", "topology"), 2, 1).WithOrigin("maxItems")}, + }, + "valid with empty schedulingConstraints": { + input: mkValidPodGroup(setSchedulingConstraints()), + enableTopologyAwareScheduling: true, + }, + "topologyConstraint with empty topology key": { + input: mkValidPodGroup(addTopologyConstraint("")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "schedulingConstraints", "topology").Index(0).Child("key"), "")}, + }, + "valid with topology key with DNS prefix": { + input: mkValidPodGroup(addTopologyConstraint("example.com/Foo")), + enableTopologyAwareScheduling: true, + }, + "valid with topology key with prefix with max length": { + input: mkValidPodGroup(addTopologyConstraint(strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63))), + enableTopologyAwareScheduling: true, + }, + "with topology key with prefix exceending max prefix length": { + input: mkValidPodGroup(addTopologyConstraint(strings.Repeat("a", 254) + "/foo")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "with topology key with prefix exceending max name length": { + input: mkValidPodGroup(addTopologyConstraint("foo/" + strings.Repeat("b", 64))), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "with topology key without prefix exceeding max length": { + input: mkValidPodGroup(addTopologyConstraint(strings.Repeat("b", 64))), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "with topology key with invalid characters": { + input: mkValidPodGroup(addTopologyConstraint("Example.com/Foo")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "pod disruption mode, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod)), + enableWorkloadAwarePreemption: true, + }, + "pod disruption mode, workload aware preemption disabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")}, + }, + "pod group disruption mode, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePodGroup)), + enableWorkloadAwarePreemption: true, + }, + "pod group disruption mode, workload aware preemption disabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePodGroup)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")}, + }, + "invalid disruption mode, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode("Invalid")), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.NotSupported(field.NewPath("spec", "disruptionMode"), "Invalid", sets.List(allowedDisruptionModes))}, + }, + "invalid disruption mode, workload aware preemption disabled": { + input: mkValidPodGroup(setDisruptionMode("Invalid")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")}, + }, + "valid pod group without disruption mode, workload aware preemption enabled": { + input: mkValidPodGroup(), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "disruptionMode"), "")}, + }, + "valid priority class name, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriorityClassName("high-priority")), + enableWorkloadAwarePreemption: true, + }, + "valid priority class name, workload aware preemption disabled": { + input: mkValidPodGroup(setPriorityClassName("high-priority")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priorityClassName"), "")}, + }, + "invalid priority class name, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriorityClassName("high/priority")), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "priorityClassName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "invalid priority class name, workload aware preemption disabled": { + input: mkValidPodGroup(setPriorityClassName("high/priority")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priorityClassName"), "")}, + }, + "valid priority, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriority(1000)), + enableWorkloadAwarePreemption: true, + }, + "valid priority, workload aware preemption disabled": { + input: mkValidPodGroup(setPriority(1000)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")}, + }, + "valid negative priority, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriority(-2147483648)), + enableWorkloadAwarePreemption: true, + }, + "valid negative priority, workload aware preemption disabled": { + input: mkValidPodGroup(setPriority(-2147483648)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")}, + }, + "too high priority, workload aware preemption enabled": { + input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriority(scheduling.HighestUserDefinablePriority+1)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "priority"), nil, "").WithOrigin("maximum"), + }, + }, + "too high priority, workload aware preemption disabled": { + input: mkValidPodGroup(setPriority(scheduling.HighestUserDefinablePriority + 1)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")}, + }, + "ok resourceClaimName reference": { + input: mkValidPodGroup(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "claim", ResourceClaimName: new("resource-claim")})), + }, + "ok resourceClaimTemplateName reference": { + input: mkValidPodGroup(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "claim", ResourceClaimTemplateName: new("resource-claim-template")})), + }, + "ok multiple claims": { + input: mkValidPodGroup(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "claim-1", ResourceClaimName: new("resource-claim-1")}, + scheduling.PodGroupResourceClaim{Name: "claim-2", ResourceClaimName: new("resource-claim-2")}, + )), + }, + "claim name with prefix": { + input: mkValidPodGroup(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "../my-claim", ResourceClaimName: new("resource-claim")})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "resourceClaims").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name"), + }, + }, + "claim name with path": { + input: mkValidPodGroup(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "my/claim", ResourceClaimName: new("resource-claim")})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "resourceClaims").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name"), + }, + }, + "duplicate claim entries": { + input: mkValidPodGroup(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimName: new("resource-claim-1")}, + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimName: new("resource-claim-2")}, + )), + expectedErrs: field.ErrorList{ + field.Duplicate(field.NewPath("spec", "resourceClaims").Index(1), nil), + }, + }, + "resource claim source empty": { + input: mkValidPodGroup(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim"}, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "resourceClaims").Index(0), nil, "").WithOrigin("union"), + }, + }, + "resource claim reference and template": { + input: mkValidPodGroup(addResourceClaims( + scheduling.PodGroupResourceClaim{ + Name: "my-claim", + ResourceClaimName: new("resource-claim"), + ResourceClaimTemplateName: new("resource-claim-template"), + }, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "resourceClaims").Index(0), nil, "").WithOrigin("union"), + }, + }, + "invalid claim reference name": { + input: mkValidPodGroup(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimName: new(".foo_bar")}, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "resourceClaims").Index(0).Child("resourceClaimName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "invalid claim template name": { + input: mkValidPodGroup(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new(".foo_bar")}, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "resourceClaims").Index(0).Child("resourceClaimTemplateName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "too many claims": { + input: mkValidPodGroup(func(pg *scheduling.PodGroup) { + pg.Spec.ResourceClaims = make([]scheduling.PodGroupResourceClaim, scheduling.MaxPodGroupResourceClaims+1) + for i := range pg.Spec.ResourceClaims { + pg.Spec.ResourceClaims[i] = scheduling.PodGroupResourceClaim{ + Name: "my-claim-" + strconv.Itoa(i), + ResourceClaimName: new("resource-claim"), + } + } + }), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "resourceClaims"), scheduling.MaxPodGroupResourceClaims+1, scheduling.MaxPodGroupResourceClaims).WithOrigin("maxItems"), + }, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.DRAWorkloadResourceClaims: tc.enableDRAWorkloadResourceClaims, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, strategy.Validate, tc.expectedErrs, apitesting.WithMinEmulationVersion(version.MustParse("1.36"))) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + apiVersions := []string{"v1alpha2"} // PodGroup is currently only in v1alpha2 + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "scheduling.k8s.io", + APIVersion: apiVersion, + Resource: "podgroups", + Name: "valid-podgroup", + IsResourceRequest: true, + Verb: "update", + }) + testCases := map[string]struct { + oldObj scheduling.PodGroup + updateObj scheduling.PodGroup + enableTopologyAwareScheduling bool + enableDRAWorkloadResourceClaims bool + enableWorkloadAwarePreemption bool + expectedErrs field.ErrorList + }{ + "valid update": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1")), + }, + "invalid update empty podGroupTemplateRef": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setEmptyPodGroupTemplateRef()), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplateRef"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update unset podGroupTemplateRef": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), unsetPodGroupTemplateRef()), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplateRef"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update change podGroupTemplateRef template name": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPodGroupTemplateRef("other-template", "workload")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplateRef"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update change podGroupTemplateRef workload name": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPodGroupTemplateRef("template", "other-workload")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplateRef"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update with neither basic nor gang": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), clearPodGroupPolicy()), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "schedulingPolicy"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update with both basic and gang": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setBothPolicies()), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "schedulingPolicy"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update of gang minCount": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPodGroupMinCount(10)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "schedulingPolicy"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update from gang to basic policy": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setBasicPolicy()), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingPolicy"), nil, "").WithOrigin("immutable")}, + }, + "valid update with unchanged scheduling constraints and TAS disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + }, + "valid update with unchanged scheduling constraints": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + enableTopologyAwareScheduling: true, + }, + "invalid update to scheduling constraints": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setSchedulingConstraints()), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to topology constraints": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo"), addTopologyConstraint("bar")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to topology key": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("bar")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to scheduling constraints with TAS disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setSchedulingConstraints()), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "schedulingConstraints"), "")}, + }, + "invalid update to topology constraints with TAS disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo"), addTopologyConstraint("bar")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "schedulingConstraints"), "")}, + }, + "invalid update to topology key with TAS disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("foo")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addTopologyConstraint("bar")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "schedulingConstraints"), "")}, + }, + "invalid add of resource claims, DRA workload resource claims disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "resourceClaims"), nil, "").WithOrigin("immutable")}, + }, + "invalid add of resource claims, DRA workload resource claims enabled": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + enableDRAWorkloadResourceClaims: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "resourceClaims"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of resource claims, DRA workload resource claims disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + )), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "resourceClaims"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of resource claims, DRA workload resource claims enabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + )), + enableDRAWorkloadResourceClaims: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "resourceClaims"), nil, "").WithOrigin("immutable")}, + }, + "invalid remove of resource claims, DRA workload resource claims disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidPodGroup(setResourceVersion("1")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "resourceClaims"), nil, "").WithOrigin("immutable")}, + }, + "invalid remove of resource claims, DRA workload resource claims enabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidPodGroup(setResourceVersion("1")), + enableDRAWorkloadResourceClaims: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "resourceClaims"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of disruption mode, workload aware preemption enabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), setDisruptionMode(scheduling.DisruptionModePod)), + updateObj: mkValidPodGroup(setResourceVersion("1"), setDisruptionMode(scheduling.DisruptionModePodGroup)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "disruptionMode"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of disruption mode, workload aware preemption disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), setDisruptionMode(scheduling.DisruptionModePod)), + updateObj: mkValidPodGroup(setResourceVersion("1"), setDisruptionMode(scheduling.DisruptionModePodGroup)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")}, + }, + "invalid update of priority class name, workload aware preemption enabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), setPriorityClassName("low-priority")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPriorityClassName("high-priority")), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "priorityClassName"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of priority class name, workload aware preemption disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), setPriorityClassName("low-priority")), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPriorityClassName("high-priority")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priorityClassName"), "")}, + }, + "invalid update of priority, workload aware preemption enabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), setPriority(1000)), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPriority(2000)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "priority"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of priority, workload aware preemption disabled": { + oldObj: mkValidPodGroup(setResourceVersion("1"), setPriority(1000)), + updateObj: mkValidPodGroup(setResourceVersion("1"), setPriority(2000)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")}, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.DRAWorkloadResourceClaims: tc.enableDRAWorkloadResourceClaims, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + strategy := NewStrategy() + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, strategy.ValidateUpdate, tc.expectedErrs, apitesting.WithMinEmulationVersion(version.MustParse("1.36"))) + }) + } +} + +func TestDeclarativeValidateStatusUpdate(t *testing.T) { + apiVersions := []string{"v1alpha2"} // PodGroup is currently only in v1alpha2 + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateStatusUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateStatusUpdate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "scheduling.k8s.io", + APIVersion: apiVersion, + Resource: "podgroups", + Subresource: "status", + Verb: "update", + }) + testCases := map[string]struct { + oldObj scheduling.PodGroup + updateObj scheduling.PodGroup + expectedErrs field.ErrorList + }{ + "valid noop update": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1")), + }, + "valid status update": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addCondition(scheduling.PodGroupScheduled)), + }, + "duplicate condition types": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup(setResourceVersion("1"), addCondition(scheduling.PodGroupScheduled), addCondition(scheduling.PodGroupScheduled)), + expectedErrs: field.ErrorList{ + field.Duplicate(field.NewPath("status", "conditions").Index(1).Child("type"), scheduling.PodGroupScheduled).MarkFromImperative(), + }, + }, + "valid resource claim status update": { + oldObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + ), + ), + updateObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + ), + addResourceClaimStatuses( + scheduling.PodGroupResourceClaimStatus{Name: "my-claim", ResourceClaimName: new("foo-my-claim-12345")}, + scheduling.PodGroupResourceClaimStatus{Name: "my-other-claim", ResourceClaimName: nil}, + ), + ), + }, + "non-existent resource claim in status": { + oldObj: mkValidPodGroup(setResourceVersion("1")), + updateObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaimStatuses( + scheduling.PodGroupResourceClaimStatus{Name: "no-such-claim", ResourceClaimName: new("my-template")}, + ), + ), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("status", "resourceClaimStatuses").Index(0).Child("name"), nil, "").MarkFromImperative(), + }, + }, + "invalid resource claim name": { + oldObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + ), + ), + updateObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + ), + addResourceClaimStatuses( + scheduling.PodGroupResourceClaimStatus{Name: "my-claim", ResourceClaimName: new("%$!#5")}, + ), + ), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("status", "resourceClaimStatuses").Index(0).Child("resourceClaimName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "duplicate claim name": { + oldObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + ), + ), + updateObj: mkValidPodGroup( + setResourceVersion("1"), + addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + ), + addResourceClaimStatuses( + scheduling.PodGroupResourceClaimStatus{Name: "my-claim", ResourceClaimName: new("foo-my-claim-12345")}, + scheduling.PodGroupResourceClaimStatus{Name: "my-other-claim", ResourceClaimName: nil}, + scheduling.PodGroupResourceClaimStatus{Name: "my-other-claim", ResourceClaimName: nil}, + ), + ), + expectedErrs: field.ErrorList{ + field.Duplicate(field.NewPath("status", "resourceClaimStatuses").Index(2), nil), + }, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + strategy := NewStatusStrategy(NewStrategy()) + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +// mkValidPodGroup produces a PodGroup which passes validation with no tweaks. +func mkValidPodGroup(tweaks ...func(pg *scheduling.PodGroup)) scheduling.PodGroup { + obj := scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "podgroup", Namespace: "ns"}, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "workload", + PodGroupTemplateName: "template", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + }, + } + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +func setResourceVersion(v string) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.ResourceVersion = v + } +} + +func unsetPodGroupTemplateRef() func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.PodGroupTemplateRef = nil + } +} + +func setEmptyPodGroupTemplateRef() func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.PodGroupTemplateRef = &scheduling.PodGroupTemplateReference{} + } +} + +func setPodGroupMinCount(min int) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.SchedulingPolicy.Gang.MinCount = int32(min) + } +} + +func clearPodGroupPolicy() func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{} + } +} + +func setBasicPolicy() func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + } + } +} + +func setBothPolicies() func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + Gang: &scheduling.GangSchedulingPolicy{MinCount: 1}, + } + } +} + +func setPodGroupTemplateRef(templateName, workloadName string) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.PodGroupTemplateRef = &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + PodGroupTemplateName: templateName, + WorkloadName: workloadName, + }, + } + } +} + +func addResourceClaims(claims ...scheduling.PodGroupResourceClaim) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.ResourceClaims = append(obj.Spec.ResourceClaims, claims...) + } +} + +func addResourceClaimStatuses(statuses ...scheduling.PodGroupResourceClaimStatus) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Status.ResourceClaimStatuses = append(obj.Status.ResourceClaimStatuses, statuses...) + } +} + +func addCondition(conditionType string) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Status.Conditions = append(obj.Status.Conditions, metav1.Condition{ + Type: conditionType, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupScheduled, + Message: "Test status condition message", + LastTransitionTime: metav1.Now(), + }) + } +} + +func setSchedulingConstraints() func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{} + } +} + +func addTopologyConstraint(value string) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + if obj.Spec.SchedulingConstraints == nil { + setSchedulingConstraints()(obj) + } + obj.Spec.SchedulingConstraints.Topology = append(obj.Spec.SchedulingConstraints.Topology, scheduling.TopologyConstraint{Key: value}) + } +} + +func setDisruptionMode(mode scheduling.DisruptionMode) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.DisruptionMode = &mode + } +} + +func setPriorityClassName(priorityClassName string) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.PriorityClassName = priorityClassName + } +} + +func setPriority(priority int32) func(obj *scheduling.PodGroup) { + return func(obj *scheduling.PodGroup) { + obj.Spec.Priority = new(priority) + } +} diff --git a/pkg/registry/scheduling/podgroup/doc.go b/pkg/registry/scheduling/podgroup/doc.go new file mode 100644 index 0000000000000..8d4dd43b34d63 --- /dev/null +++ b/pkg/registry/scheduling/podgroup/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 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 podgroup diff --git a/pkg/registry/scheduling/podgroup/storage/storage.go b/pkg/registry/scheduling/podgroup/storage/storage.go new file mode 100644 index 0000000000000..81df75db9f291 --- /dev/null +++ b/pkg/registry/scheduling/podgroup/storage/storage.go @@ -0,0 +1,99 @@ +/* +Copyright 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 storage + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + printerstorage "k8s.io/kubernetes/pkg/printers/storage" + "k8s.io/kubernetes/pkg/registry/scheduling/podgroup" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// REST implements a RESTStorage for podgroups against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against podgroups. +func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { + strategy := podgroup.NewStrategy() + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &scheduling.PodGroup{} }, + NewListFunc: func() runtime.Object { return &scheduling.PodGroupList{} }, + DefaultQualifiedResource: scheduling.Resource("podgroups"), + SingularQualifiedResource: scheduling.Resource("podgroup"), + + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, + + TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, + } + + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + return nil, nil, err + } + + statusStore := *store + statusStrategy := podgroup.NewStatusStrategy(strategy) + statusStore.UpdateStrategy = statusStrategy + statusStore.ResetFieldsStrategy = statusStrategy + + return &REST{store}, &StatusREST{store: &statusStore}, nil +} + +// StatusREST implements the REST endpoint for changing the status of a PodGroup. +type StatusREST struct { + store *genericregistry.Store +} + +// New creates a new PodGroup object. +func (r *StatusREST) New() runtime.Object { + return &scheduling.PodGroup{} +} + +func (r *StatusREST) Destroy() { + // Given that underlying store is shared with REST, + // we don't destroy it here explicitly. +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return r.store.GetResetFields() +} diff --git a/pkg/registry/scheduling/podgroup/storage/storage_test.go b/pkg/registry/scheduling/podgroup/storage/storage_test.go new file mode 100644 index 0000000000000..8cbb6423c957f --- /dev/null +++ b/pkg/registry/scheduling/podgroup/storage/storage_test.go @@ -0,0 +1,246 @@ +/* +Copyright 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 storage + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" + "k8s.io/apiserver/pkg/registry/rest" + etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" + "k8s.io/kubernetes/pkg/apis/scheduling" + _ "k8s.io/kubernetes/pkg/apis/scheduling/install" + "k8s.io/kubernetes/pkg/registry/registrytest" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcd3testing.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorageForResource(t, scheduling.Resource("podgroups")) + restOptions := generic.RESTOptions{ + StorageConfig: etcdStorage, + Decorator: generic.UndecoratedStorage, + DeleteCollectionWorkers: 1, + ResourcePrefix: "podgroups", + } + rest, statusRest, err := NewREST(restOptions) + if err != nil { + t.Fatalf("Unable to create REST %v", err) + } + return rest, statusRest, server +} + +func validNewPodGroup() *scheduling.PodGroup { + return &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w", + PodGroupTemplateName: "t", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + }, + } +} + +func newTester(t *testing.T, storage *genericregistry.Store) *genericregistrytest.Tester { + return genericregistrytest.New(t, storage).SetRequestInfo(&genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: "v1alpha2", + Resource: "podgroups", + }) +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := newTester(t, storage.Store) + podGroup := validNewPodGroup() + podGroup.ObjectMeta = metav1.ObjectMeta{} + test.TestCreate( + // valid + podGroup, + // invalid cases + &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "*badName", + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := newTester(t, storage.Store) + test.TestUpdate( + // valid + validNewPodGroup(), + // valid update + // Update Status + func(obj runtime.Object) runtime.Object { + pg := obj.(*scheduling.PodGroup) + pg.Status = scheduling.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + } + return pg + }, + // invalid update + // Update MinCount + func(obj runtime.Object) runtime.Object { + pg := obj.(*scheduling.PodGroup) + pg.Spec.SchedulingPolicy.Gang.MinCount = 4 + return pg + }, + // invalid update + // Update PodGroupTemplateRef + func(obj runtime.Object) runtime.Object { + pg := obj.(*scheduling.PodGroup) + pg.Spec.PodGroupTemplateRef = &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "foo", + PodGroupTemplateName: "baz", + }, + } + return pg + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := newTester(t, storage.Store) + test.TestDelete(validNewPodGroup()) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := newTester(t, storage.Store) + test.TestGet(validNewPodGroup()) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := newTester(t, storage.Store) + test.TestList(validNewPodGroup()) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := newTester(t, storage.Store) + test.TestWatch( + validNewPodGroup(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} + +func TestUpdateStatus(t *testing.T) { + storage, statusStorage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + ctx := genericapirequest.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + podGroupStart := validNewPodGroup() + err := storage.Storage.Create(ctx, key, podGroupStart, nil, 0, false) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + podGroup := podGroupStart.DeepCopy() + podGroup.Status = scheduling.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: metav1.NewTime(time.Now()).Rfc3339Copy(), + }, + }, + } + statusCtx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "scheduling.k8s.io", + APIVersion: "v1alpha2", + Resource: "podgroups", + Subresource: "status", + Verb: "update", + }) + _, _, err = statusStorage.Update(statusCtx, podGroup.Name, rest.DefaultUpdatedObjectInfo(podGroup), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + obj, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + podGroupOut := obj.(*scheduling.PodGroup) + // only compare relevant changes b/c of difference in metadata + if !apiequality.Semantic.DeepEqual(podGroup.Status, podGroupOut.Status) { + t.Errorf("unexpected object: %s", cmp.Diff(podGroup.Status, podGroupOut.Status)) + } +} diff --git a/pkg/registry/scheduling/podgroup/strategy.go b/pkg/registry/scheduling/podgroup/strategy.go new file mode 100644 index 0000000000000..ef86023e167f8 --- /dev/null +++ b/pkg/registry/scheduling/podgroup/strategy.go @@ -0,0 +1,279 @@ +/* +Copyright 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 podgroup + +import ( + "context" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + + "k8s.io/apimachinery/pkg/api/operation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/apis/scheduling/validation" + "k8s.io/kubernetes/pkg/features" +) + +// podGroupStrategy implements behavior for PodGroup objects. +type podGroupStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// NewStrategy is the default logic that applies when creating and updating PodGroup objects. +func NewStrategy() *podGroupStrategy { + return &podGroupStrategy{ + legacyscheme.Scheme, + names.SimpleNameGenerator, + } +} + +func (*podGroupStrategy) NamespaceScoped() bool { + return true +} + +// GetResetFields returns the set of fields that get reset by the strategy and +// should not be modified by the user. For a new PodGroup that is the status. +func (*podGroupStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + "scheduling.k8s.io/v1alpha2": fieldpath.NewSet( + fieldpath.MakePathOrDie("status"), + ), + } + + return fields +} + +func (*podGroupStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + podGroup := obj.(*scheduling.PodGroup) + // Status must not be set by user on create. + podGroup.Status = scheduling.PodGroupStatus{} + dropDisabledPodGroupFields(podGroup, nil) +} + +func (*podGroupStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + podGroup := obj.(*scheduling.PodGroup) + allErrs := validation.ValidatePodGroup(podGroup) + opts := []string{} + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) { + opts = append(opts, string(features.TopologyAwareWorkloadScheduling)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) { + opts = append(opts, string(features.DRAWorkloadResourceClaims)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) { + opts = append(opts, string(features.WorkloadAwarePreemption)) + } + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, allErrs, operation.Create, rest.WithDeclarativeEnforcement(), rest.WithOptions(opts)) +} + +func (*podGroupStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + +func (*podGroupStrategy) Canonicalize(obj runtime.Object) {} + +func (*podGroupStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (*podGroupStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newPodGroup := obj.(*scheduling.PodGroup) + oldPodGroup := old.(*scheduling.PodGroup) + newPodGroup.Status = oldPodGroup.Status + dropDisabledPodGroupFields(newPodGroup, oldPodGroup) +} + +func (*podGroupStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + newPodGroup := obj.(*scheduling.PodGroup) + oldPodGroup := old.(*scheduling.PodGroup) + allErrs := validation.ValidatePodGroupUpdate(newPodGroup, oldPodGroup) + opts := []string{} + // Declarative validation will always allow fields to remain unchanged, so if any + // of the fields which are covered by these gates are set, we will not re-validate them + // (even if the gates are disabled) as long as they do not change values. If a gate + // is disabled, they will not be allowed to change values. + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) { + opts = append(opts, string(features.TopologyAwareWorkloadScheduling)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) { + opts = append(opts, string(features.DRAWorkloadResourceClaims)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) { + opts = append(opts, string(features.WorkloadAwarePreemption)) + } + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newPodGroup, oldPodGroup, allErrs, operation.Update, rest.WithDeclarativeEnforcement(), rest.WithOptions(opts)) +} + +func (*podGroupStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + +func (*podGroupStrategy) AllowUnconditionalUpdate() bool { + return true +} + +type podGroupStatusStrategy struct { + *podGroupStrategy +} + +// NewStatusStrategy creates a strategy for operating the status object. +func NewStatusStrategy(podGroupStrategy *podGroupStrategy) *podGroupStatusStrategy { + return &podGroupStatusStrategy{podGroupStrategy} +} + +// GetResetFields returns the set of fields that get reset by the strategy and +// should not be modified by the user. For a status update that is the spec. +func (*podGroupStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + "scheduling.k8s.io/v1alpha2": fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata"), + fieldpath.MakePathOrDie("spec"), + ), + } + + return fields +} + +func (*podGroupStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newPodGroup := obj.(*scheduling.PodGroup) + oldPodGroup := old.(*scheduling.PodGroup) + newPodGroup.Spec = oldPodGroup.Spec + metav1.ResetObjectMetaForStatus(&newPodGroup.ObjectMeta, &oldPodGroup.ObjectMeta) + dropDisabledPodGroupStatusFields(newPodGroup, oldPodGroup) +} + +func (r *podGroupStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + newPodGroup := obj.(*scheduling.PodGroup) + oldPodGroup := old.(*scheduling.PodGroup) + errs := validation.ValidatePodGroupStatusUpdate(newPodGroup, oldPodGroup) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newPodGroup, oldPodGroup, errs, operation.Update, rest.WithDeclarativeEnforcement()) +} + +// WarningsOnUpdate returns warnings for the given update. +func (*podGroupStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + +// dropDisabledPodGroupFields removes fields which are covered by a feature gate. +func dropDisabledPodGroupFields(podGroup, oldPodGroup *scheduling.PodGroup) { + var podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec + if podGroup != nil { + podGroupSpec = &podGroup.Spec + } + if oldPodGroup != nil { + oldPodGroupSpec = &oldPodGroup.Spec + } + dropDisabledPodGroupSpecFields(podGroupSpec, oldPodGroupSpec) +} + +func dropDisabledPodGroupSpecFields(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) { + dropDisabledSchedulingConstraintsFields(podGroupSpec, oldPodGroupSpec) + dropDisabledDRAWorkloadResourceClaimsFields(podGroupSpec, oldPodGroupSpec) + dropDisabledDisruptionModeField(podGroupSpec, oldPodGroupSpec) + dropDisabledPriorityClassNameField(podGroupSpec, oldPodGroupSpec) + dropDisabledPriorityField(podGroupSpec, oldPodGroupSpec) +} + +func dropDisabledPodGroupStatusFields(newPodGroup, oldPodGroup *scheduling.PodGroup) { + var oldPodGroupSpec *scheduling.PodGroupSpec + if oldPodGroup != nil { + oldPodGroupSpec = &oldPodGroup.Spec + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) || draWorkloadResourceClaimsInUse(oldPodGroupSpec) { + // No need to drop anything. + return + } + newPodGroup.Status.ResourceClaimStatuses = nil +} + +// dropDisabledSchedulingConstraintsFields drops the SchedulingConstraints field +// from the new PodGroup if the TopologyAwareWorkloadScheduling feature gate is disabled +// and it was not used in the old PodGroup. +func dropDisabledSchedulingConstraintsFields(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) { + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) || schedulingConstraintsInUse(oldPodGroupSpec) { + // No need to drop anything. + return + } + podGroupSpec.SchedulingConstraints = nil +} + +// dropDisabledDRAWorkloadResourceClaimsFields removes resource claim references +// unless they are already used by the old PodGroup spec. +func dropDisabledDRAWorkloadResourceClaimsFields(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) || draWorkloadResourceClaimsInUse(oldPodGroupSpec) { + // No need to drop anything. + return + } + podGroupSpec.ResourceClaims = nil +} + +// dropDisabledDisruptionModeField removes the DisruptionMode field unless it is +// already used in the old PodGroup spec. +func dropDisabledDisruptionModeField(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) { + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || disruptionModeInUse(oldPodGroupSpec) { + // No need to drop anything. + return + } + podGroupSpec.DisruptionMode = nil +} + +// dropDisabledPriorityClassNameField removes the PriorityClassName field unless +// it is already used in the old PodGroup spec. +func dropDisabledPriorityClassNameField(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) { + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || priorityClassNameInUse(oldPodGroupSpec) { + // No need to drop anything. + return + } + podGroupSpec.PriorityClassName = "" +} + +// dropDisabledPriorityField removes the Priority field unless it is already used +// in the old PodGroup spec. +func dropDisabledPriorityField(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) { + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || priorityInUse(oldPodGroupSpec) { + // No need to drop anything. + return + } + podGroupSpec.Priority = nil +} + +func schedulingConstraintsInUse(podGroupSpec *scheduling.PodGroupSpec) bool { + return podGroupSpec != nil && podGroupSpec.SchedulingConstraints != nil +} + +func draWorkloadResourceClaimsInUse(podGroupSpec *scheduling.PodGroupSpec) bool { + return podGroupSpec != nil && len(podGroupSpec.ResourceClaims) > 0 +} + +func disruptionModeInUse(podGroupSpec *scheduling.PodGroupSpec) bool { + return podGroupSpec != nil && podGroupSpec.DisruptionMode != nil +} + +func priorityClassNameInUse(podGroupSpec *scheduling.PodGroupSpec) bool { + return podGroupSpec != nil && podGroupSpec.PriorityClassName != "" +} + +func priorityInUse(podGroupSpec *scheduling.PodGroupSpec) bool { + return podGroupSpec != nil && podGroupSpec.Priority != nil +} diff --git a/pkg/registry/scheduling/podgroup/strategy_test.go b/pkg/registry/scheduling/podgroup/strategy_test.go new file mode 100644 index 0000000000000..c7f33fb412c62 --- /dev/null +++ b/pkg/registry/scheduling/podgroup/strategy_test.go @@ -0,0 +1,847 @@ +/* +Copyright 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 podgroup + +import ( + "context" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" +) + +var podGroup = &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "w", + PodGroupTemplateName: "t", + }, + }, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, + }, + }, +} + +func podGroupWithSchedulingConstraints(keys ...string) *scheduling.PodGroup { + pg := podGroup.DeepCopy() + pg.Spec.SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{}, + } + for _, key := range keys { + constraint := scheduling.TopologyConstraint{Key: key} + pg.Spec.SchedulingConstraints.Topology = append(pg.Spec.SchedulingConstraints.Topology, constraint) + } + return pg +} + +func podGroupWithDisruptionMode(mode scheduling.DisruptionMode) *scheduling.PodGroup { + pg := podGroup.DeepCopy() + pg.Spec.DisruptionMode = &mode + return pg +} + +var ( + fieldImmutableError = "field is immutable" + minCountError = "must be greater than or equal to 1" + oneOfError = "must specify one of: `basic`, `gang`" + multipleFieldsSetError = "must specify exactly one of: `basic`, `gang`" + tooManyItemsError = "must have at most 1 item" + maximumError = "must be less than or equal to 1000000000" + subdomainNameError = "lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters" + forbiddenError = "Forbidden" + supportedModesError = `supported values: "Pod", "PodGroup"` +) + +func TestStrategy(t *testing.T) { + strategy := NewStrategy() + if !strategy.NamespaceScoped() { + t.Errorf("PodGroup must be namespace scoped") + } + if strategy.AllowCreateOnUpdate() { + t.Errorf("PodGroup should not allow create on update") + } +} + +func ctxWithRequestInfo() context.Context { + return genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: "v1alpha2", + Resource: "podgroups", + IsResourceRequest: true, + }) +} + +func TestStrategyCreate(t *testing.T) { + ctx := ctxWithRequestInfo() + now := metav1.Now() + testCases := map[string]struct { + obj *scheduling.PodGroup + expectObj *scheduling.PodGroup + enableTopologyAwareScheduling bool + enableWorkloadAwarePreemption bool + expectValidationError string + }{ + "simple": { + obj: podGroup, + expectObj: podGroup, + }, + "negative min count": { + obj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Spec.SchedulingPolicy.Gang.MinCount = -1 + return newPodGroup + }(), + expectValidationError: minCountError, + }, + "two scheduling policies": { + obj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Spec.SchedulingPolicy.Basic = &scheduling.BasicSchedulingPolicy{} + return newPodGroup + }(), + expectValidationError: multipleFieldsSetError, + }, + "no scheduling policy is set": { + obj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Spec.SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{} + return newPodGroup + }(), + expectValidationError: oneOfError, + }, + "drop status on creation": { + obj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Status.Conditions = []metav1.Condition{ + { + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: now, + }, + } + return newPodGroup + }(), + expectObj: podGroup, + }, + "multiple topology constraints, topology aware scheduling enabled": { + obj: podGroupWithSchedulingConstraints("foo", "bar"), + enableTopologyAwareScheduling: true, + expectValidationError: tooManyItemsError, + }, + "multiple topology constraints, topology aware scheduling disabled": { + obj: podGroupWithSchedulingConstraints("foo", "bar"), + expectObj: podGroup, + }, + "invalid topology key, topology aware scheduling enabled": { + obj: podGroupWithSchedulingConstraints("foo-"), + enableTopologyAwareScheduling: true, + expectValidationError: "Invalid value: \"foo-\"", + }, + "invalid topology key, topology aware scheduling disabled": { + obj: podGroupWithSchedulingConstraints("foo-"), + expectObj: podGroup, + }, + "with TAS feature gate disabled, drop scheduling constraints on creation": { + obj: podGroupWithSchedulingConstraints("foo-"), + expectObj: podGroup, + }, + "workload aware preemption disabled - drop disruption mode": { + obj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + expectObj: podGroup, + }, + "workload aware preemption enabled - preserve disruption mode (pod)": { + obj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + expectObj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + enableWorkloadAwarePreemption: true, + }, + "workload aware preemption enabled - preserve disruption mode (pod group)": { + obj: podGroupWithDisruptionMode(scheduling.DisruptionModePodGroup), + expectObj: podGroupWithDisruptionMode(scheduling.DisruptionModePodGroup), + enableWorkloadAwarePreemption: true, + }, + "workload aware preemption enabled - unknown disruption mode": { + obj: podGroupWithDisruptionMode(scheduling.DisruptionMode("Invalid")), + enableWorkloadAwarePreemption: true, + expectValidationError: supportedModesError, + }, + "workload aware preemption disabled - drop priorityClassName": { + obj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "high-priority" + return pg + }(), + expectObj: podGroup, + }, + "workload aware preemption enabled - invalid priorityClassName": { + obj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "invalid/priority/class/name" + return pg + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: subdomainNameError, + }, + "workload aware preemption enabled - preserve priorityClassName": { + obj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "high-priority" + return pg + }(), + expectObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "high-priority" + return pg + }(), + enableWorkloadAwarePreemption: true, + }, + "workload aware preemption disabled - drop priority": { + obj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(1000)) + return pg + }(), + expectObj: podGroup, + }, + "workload aware preemption enabled - preserve priority": { + obj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(1000)) + return pg + }(), + expectObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(1000)) + return pg + }(), + enableWorkloadAwarePreemption: true, + }, + "workload aware preemption enabled - too high priority": { + obj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(scheduling.HighestUserDefinablePriority + 1)) + return pg + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: maximumError, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + podGroup := tc.obj.DeepCopy() + + strategy := NewStrategy() + strategy.PrepareForCreate(ctx, podGroup) + if errs := strategy.Validate(ctx, podGroup); len(errs) != 0 { + if tc.expectValidationError == "" { + t.Fatalf("unexpected error(s): %v", errs) + } + if len(errs) != 1 { + t.Fatalf("exactly one error expected") + } + if errMsg := errs[0].Error(); !strings.Contains(errMsg, tc.expectValidationError) { + t.Fatalf("error %#v does not contain the expected message %q", errMsg, tc.expectValidationError) + } + return + } + if tc.expectValidationError != "" { + t.Fatal("expected validation error(s), got none") + } + if warnings := strategy.WarningsOnCreate(ctx, podGroup); len(warnings) != 0 { + t.Fatalf("unexpected warnings: %q", warnings) + } + strategy.Canonicalize(podGroup) + if tc.expectObj != nil { + if diff := cmp.Diff(tc.expectObj, podGroup); diff != "" { + t.Errorf("got unexpected podGroup object (-want, +got): %s", diff) + } + } + }) + } +} + +func TestStrategyUpdate(t *testing.T) { + ctx := ctxWithRequestInfo() + testCases := map[string]struct { + oldObj *scheduling.PodGroup + newObj *scheduling.PodGroup + enableTopologyAwareScheduling bool + enableWorkloadAwarePreemption bool + expectValidationError string + }{ + "no changes": { + oldObj: podGroup, + newObj: podGroup, + }, + "name change not allowed": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Name += "bar" + return newPodGroup + }(), + expectValidationError: fieldImmutableError, + }, + "updating pod group template ref not allowed": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Spec.PodGroupTemplateRef = &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: "foo", + PodGroupTemplateName: "baz", + }, + } + return newPodGroup + }(), + expectValidationError: fieldImmutableError, + }, + "changing min count in gang scheduling policy not allowed": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Spec.SchedulingPolicy.Gang.MinCount = 4 + return newPodGroup + }(), + expectValidationError: fieldImmutableError, + }, + "changing scheduling policy not allowed": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + newPodGroup := podGroup.DeepCopy() + newPodGroup.Spec.SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + } + return newPodGroup + }(), + expectValidationError: fieldImmutableError, + }, + "changing scheduling constraints not allowed": { + oldObj: podGroupWithSchedulingConstraints("foo"), + newObj: podGroupWithSchedulingConstraints(), + enableTopologyAwareScheduling: true, + expectValidationError: fieldImmutableError, + }, + "changing topology constraints not allowed": { + oldObj: podGroupWithSchedulingConstraints("foo"), + newObj: podGroupWithSchedulingConstraints(), + enableTopologyAwareScheduling: true, + expectValidationError: fieldImmutableError, + }, + "changing topology key not allowed": { + oldObj: podGroupWithSchedulingConstraints("foo"), + newObj: podGroupWithSchedulingConstraints("foobar"), + enableTopologyAwareScheduling: true, + expectValidationError: fieldImmutableError, + }, + "changing scheduling constraints not allowed with TAS disabled": { + oldObj: podGroupWithSchedulingConstraints("foo"), + newObj: podGroupWithSchedulingConstraints(), + expectValidationError: forbiddenError, + }, + "changing topology constraints not allowed with TAS disabled": { + oldObj: podGroupWithSchedulingConstraints("foo"), + newObj: podGroupWithSchedulingConstraints(), + expectValidationError: forbiddenError, + }, + "changing topology key not allowed with TAS disabled": { + oldObj: podGroupWithSchedulingConstraints("foo"), + newObj: podGroupWithSchedulingConstraints("foobar"), + expectValidationError: forbiddenError, + }, + "disruption mode update, workload aware preemption disabled": { + oldObj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + newObj: podGroupWithDisruptionMode(scheduling.DisruptionModePodGroup), + expectValidationError: forbiddenError, + }, + "disruption mode update, workload aware preemption enabled": { + oldObj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + newObj: podGroupWithDisruptionMode(scheduling.DisruptionModePodGroup), + enableWorkloadAwarePreemption: true, + expectValidationError: fieldImmutableError, + }, + "priority class name update, workload aware preemption disabled": { + oldObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "low-priority" + return pg + }(), + newObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "high-priority" + return pg + }(), + expectValidationError: forbiddenError, + }, + "priority class name update, workload aware preemption enabled": { + oldObj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + newObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.PriorityClassName = "high-priority" + return pg + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: fieldImmutableError, + }, + "priority update, workload aware preemption disabled": { + oldObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(1000)) + return pg + }(), + newObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(2000)) + return pg + }(), + expectValidationError: forbiddenError, + }, + "priority update, workload aware preemption enabled": { + oldObj: podGroupWithDisruptionMode(scheduling.DisruptionModePod), + newObj: func() *scheduling.PodGroup { + pg := podGroupWithDisruptionMode(scheduling.DisruptionModePod) + pg.Spec.Priority = new(int32(2000)) + return pg + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: fieldImmutableError, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + podGroup := tc.oldObj.DeepCopy() + newPodGroup := tc.newObj.DeepCopy() + newPodGroup.ResourceVersion = "4" + + strategy := NewStrategy() + strategy.PrepareForUpdate(ctx, newPodGroup, podGroup) + if errs := strategy.ValidateUpdate(ctx, newPodGroup, podGroup); len(errs) != 0 { + if tc.expectValidationError == "" { + t.Fatalf("unexpected error(s): %v", errs) + } + if len(errs) != 1 { + t.Fatalf("exactly one error expected") + } + if errMsg := errs[0].Error(); !strings.Contains(errMsg, tc.expectValidationError) { + t.Fatalf("error %#v does not contain the expected message %q", errMsg, tc.expectValidationError) + } + return + } + if tc.expectValidationError != "" { + t.Fatal("expected validation error(s), got none") + } + }) + } +} + +func TestStatusStrategyUpdate(t *testing.T) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: "v1alpha2", + Resource: "podgroups", + Subresource: "status", + }) + now := metav1.Now() + testCases := map[string]struct { + oldObj *scheduling.PodGroup + newObj *scheduling.PodGroup + expectObj *scheduling.PodGroup + expectValidationError string + }{ + "no changes": { + oldObj: podGroup, + newObj: podGroup, + expectObj: podGroup, + }, + "valid status change": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + podGroup := podGroup.DeepCopy() + podGroup.Status.Conditions = append(podGroup.Status.Conditions, metav1.Condition{ + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: now, + }) + return podGroup + }(), + expectObj: func() *scheduling.PodGroup { + podGroup := podGroup.DeepCopy() + podGroup.Status.Conditions = append(podGroup.Status.Conditions, metav1.Condition{ + Type: scheduling.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: scheduling.PodGroupReasonUnschedulable, + Message: "Test status condition message", + LastTransitionTime: now, + }) + return podGroup + }(), + }, + "name change not allowed": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + podGroup := podGroup.DeepCopy() + podGroup.Name += "-2" + return podGroup + }(), + expectValidationError: fieldImmutableError, + }, + // Cannot add finalizers, annotations and labels during status update. + "drop meta changes": { + oldObj: podGroup, + newObj: func() *scheduling.PodGroup { + podGroup := podGroup.DeepCopy() + podGroup.Finalizers = []string{"foo"} + podGroup.Annotations = map[string]string{"foo": "bar"} + podGroup.Labels = map[string]string{"foo": "bar"} + return podGroup + }(), + expectObj: podGroup, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + strategy := NewStrategy() + + statusStrategy := NewStatusStrategy(strategy) + + oldObj := tc.oldObj.DeepCopy() + newObj := tc.newObj.DeepCopy() + newObj.ResourceVersion = "4" + + statusStrategy.PrepareForUpdate(ctx, newObj, oldObj) + if errs := statusStrategy.ValidateUpdate(ctx, newObj, oldObj); len(errs) != 0 { + if tc.expectValidationError == "" { + t.Fatalf("unexpected error(s): %v", errs) + } + if len(errs) != 1 { + t.Fatalf("exactly one error expected") + } + if errMsg := errs[0].Error(); !strings.Contains(errMsg, tc.expectValidationError) { + t.Fatalf("error %#v does not contain the expected message %q", errMsg, tc.expectValidationError) + } + return + } + if tc.expectValidationError != "" { + t.Fatal("expected validation error(s), got none") + } + if warnings := statusStrategy.WarningsOnUpdate(ctx, newObj, oldObj); len(warnings) != 0 { + t.Fatalf("unexpected warnings: %q", warnings) + } + statusStrategy.Canonicalize(newObj) + + expectObj := tc.expectObj.DeepCopy() + expectObj.ResourceVersion = "4" + if diff := cmp.Diff(expectObj, newObj); diff != "" { + t.Errorf("PodGroup mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestDropPodGroupTemplateResourceClaims(t *testing.T) { + var noPodGroup *scheduling.PodGroup + podGroupWithoutClaims := podGroup + + t.Run("spec", func(t *testing.T) { + podGroupWithClaims := func() *scheduling.PodGroup { + w := podGroupWithoutClaims.DeepCopy() + w.Spec.ResourceClaims = []scheduling.PodGroupResourceClaim{ + { + Name: "my-claim", + ResourceClaimName: new("resource-claim"), + }, + } + return w + }() + + tests := []struct { + description string + enabled bool + oldPodGroup *scheduling.PodGroup + newPodGroup *scheduling.PodGroup + wantPodGroup *scheduling.PodGroup + }{ + { + description: "old with claims / new with claims / disabled", + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + { + description: "old without claims / new with claims / disabled", + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "no old PodGroup / new with claims / disabled", + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithoutClaims, + }, + + { + description: "old with claims / new without claims / disabled", + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "old without claims / new without claims / disabled", + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "no old PodGroup / new without claims / disabled", + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + + { + description: "old with claims / new with claims / enabled", + enabled: true, + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + { + description: "old without claims / new with claims / enabled", + enabled: true, + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + { + description: "no old PodGroup / new with claims / enabled", + enabled: true, + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + + { + description: "old with claims / new without claims / enabled", + enabled: true, + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "old without claims / new without claims / enabled", + enabled: true, + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "no old PodGroup / new without claims / enabled", + enabled: true, + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRAWorkloadResourceClaims: tc.enabled, + features.GenericWorkload: tc.enabled, + }) + + oldPodGroup := tc.oldPodGroup.DeepCopy() + newPodGroup := tc.newPodGroup.DeepCopy() + wantPodGroup := tc.wantPodGroup + dropDisabledPodGroupFields(newPodGroup, oldPodGroup) + + // old PodGroup should never be changed + if diff := cmp.Diff(oldPodGroup, tc.oldPodGroup); diff != "" { + t.Errorf("old PodGroup changed: %s", diff) + } + + if diff := cmp.Diff(wantPodGroup, newPodGroup); diff != "" { + t.Errorf("new PodGroup changed (- want, + got): %s", diff) + } + }) + } + }) + + t.Run("status", func(t *testing.T) { + podGroupWithClaims := func() *scheduling.PodGroup { + w := podGroupWithoutClaims.DeepCopy() + w.Spec.ResourceClaims = []scheduling.PodGroupResourceClaim{ + { + Name: "my-claim", + ResourceClaimName: new("resource-claim"), + }, + } + w.Status.ResourceClaimStatuses = []scheduling.PodGroupResourceClaimStatus{ + { + Name: "my-claim", + ResourceClaimName: new("generated-claim"), + }, + } + return w + }() + + tests := []struct { + description string + enabled bool + oldPodGroup *scheduling.PodGroup + newPodGroup *scheduling.PodGroup + wantPodGroup *scheduling.PodGroup + }{ + { + description: "old with claims / new with claims / disabled", + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + { + description: "old without claims / new with claims / disabled", + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "no old PodGroup / new with claims / disabled", + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithoutClaims, + }, + + { + description: "old with claims / new without claims / disabled", + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "old without claims / new without claims / disabled", + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "no old PodGroup / new without claims / disabled", + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + + { + description: "old with claims / new with claims / enabled", + enabled: true, + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + { + description: "old without claims / new with claims / enabled", + enabled: true, + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + { + description: "no old PodGroup / new with claims / enabled", + enabled: true, + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithClaims, + wantPodGroup: podGroupWithClaims, + }, + + { + description: "old with claims / new without claims / enabled", + enabled: true, + oldPodGroup: podGroupWithClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "old without claims / new without claims / enabled", + enabled: true, + oldPodGroup: podGroupWithoutClaims, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + { + description: "no old PodGroup / new without claims / enabled", + enabled: true, + oldPodGroup: noPodGroup, + newPodGroup: podGroupWithoutClaims, + wantPodGroup: podGroupWithoutClaims, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRAWorkloadResourceClaims: tc.enabled, + features.GenericWorkload: tc.enabled, + }) + + oldPodGroup := tc.oldPodGroup.DeepCopy() + newPodGroup := tc.newPodGroup.DeepCopy() + wantPodGroup := tc.wantPodGroup + dropDisabledPodGroupStatusFields(newPodGroup, oldPodGroup) + + // old PodGroup should never be changed + if diff := cmp.Diff(oldPodGroup, tc.oldPodGroup); diff != "" { + t.Errorf("old PodGroup changed: %s", diff) + } + + if diff := cmp.Diff(wantPodGroup.Status, newPodGroup.Status); diff != "" { + t.Errorf("new PodGroup changed (- want, + got): %s", diff) + } + }) + } + }) +} diff --git a/pkg/registry/scheduling/rest/storage_scheduling.go b/pkg/registry/scheduling/rest/storage_scheduling.go index 3fb62fbfddfa9..3e303bbaaeaea 100644 --- a/pkg/registry/scheduling/rest/storage_scheduling.go +++ b/pkg/registry/scheduling/rest/storage_scheduling.go @@ -36,8 +36,9 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/scheduling" schedulingapiv1 "k8s.io/kubernetes/pkg/apis/scheduling/v1" - schedulingapiv1alpha1 "k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1" + schedulingapiv1alpha2 "k8s.io/kubernetes/pkg/apis/scheduling/v1alpha2" "k8s.io/kubernetes/pkg/features" + podgroupstore "k8s.io/kubernetes/pkg/registry/scheduling/podgroup/storage" priorityclassstore "k8s.io/kubernetes/pkg/registry/scheduling/priorityclass/storage" workloadstore "k8s.io/kubernetes/pkg/registry/scheduling/workload/storage" ) @@ -57,10 +58,10 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag apiGroupInfo.VersionedResourcesStorageMap[schedulingapiv1.SchemeGroupVersion.Version] = storageMap } - if storageMap, err := p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter); err != nil { + if storageMap, err := p.v1alpha2Storage(apiResourceConfigSource, restOptionsGetter); err != nil { return genericapiserver.APIGroupInfo{}, err } else if len(storageMap) > 0 { - apiGroupInfo.VersionedResourcesStorageMap[schedulingapiv1alpha1.SchemeGroupVersion.Version] = storageMap + apiGroupInfo.VersionedResourcesStorageMap[schedulingapiv1alpha2.SchemeGroupVersion.Version] = storageMap } return apiGroupInfo, nil @@ -81,10 +82,10 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API return storage, nil } -func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) { +func (p RESTStorageProvider) v1alpha2Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) { storage := map[string]rest.Storage{} - if resource := "workloads"; apiResourceConfigSource.ResourceEnabled(schedulingapiv1alpha1.SchemeGroupVersion.WithResource(resource)) { + if resource := "workloads"; apiResourceConfigSource.ResourceEnabled(schedulingapiv1alpha2.SchemeGroupVersion.WithResource(resource)) { if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { workloadStorage, err := workloadstore.NewREST(restOptionsGetter) if err != nil { @@ -96,6 +97,19 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstora } } + if resource := "podgroups"; apiResourceConfigSource.ResourceEnabled(schedulingapiv1alpha2.SchemeGroupVersion.WithResource(resource)) { + if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + podGroupStorage, podGroupStatusStorage, err := podgroupstore.NewREST(restOptionsGetter) + if err != nil { + return nil, err + } + storage[resource] = podGroupStorage + storage[resource+"/status"] = podGroupStatusStorage + } else { + klog.Warning("PodGroup storage is disabled because the GenericWorkload feature gate is disabled") + } + } + return storage, nil } diff --git a/pkg/registry/scheduling/workload/declarative_validation_test.go b/pkg/registry/scheduling/workload/declarative_validation_test.go new file mode 100644 index 0000000000000..bb58d82331e9e --- /dev/null +++ b/pkg/registry/scheduling/workload/declarative_validation_test.go @@ -0,0 +1,768 @@ +/* +Copyright 2025 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 workload + +import ( + "fmt" + "strconv" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" + + // Ensure all API groups are registered with the scheme + _ "k8s.io/kubernetes/pkg/apis/scheduling/install" +) + +var allowedDisruptionModes = sets.New( + scheduling.DisruptionModePod, + scheduling.DisruptionModePodGroup, +) + +func TestDeclarativeValidate(t *testing.T) { + apiVersions := []string{"v1alpha2"} // Workload is currently only in v1alpha2 + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidate(t, apiVersion) + }) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: apiVersion, + Resource: "workloads", + IsResourceRequest: true, + Verb: "create", + }) + + testCases := map[string]struct { + input scheduling.Workload + enableTopologyAwareScheduling bool + enableDRAWorkloadResourceClaims bool + enableWorkloadAwarePreemption bool + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidWorkload(), + }, + "empty podGroupTemplates": { + input: mkValidWorkload(clearPodGroupTemplates()), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "podGroupTemplates"), "must have at least one item")}, + }, + "too many podGroupTemplates": { + input: mkValidWorkload(setManyPodGroupTemplates(scheduling.WorkloadMaxPodGroupTemplates + 1)), + expectedErrs: field.ErrorList{field.TooMany(field.NewPath("spec", "podGroupTemplates"), scheduling.WorkloadMaxPodGroupTemplates+1, scheduling.WorkloadMaxPodGroupTemplates).WithOrigin("maxItems")}, + }, + "empty podGroupTemplate name": { + input: mkValidWorkload(setPodGroupName(0, "")), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "podGroupTemplates").Index(0).Child("name"), "")}, + }, + "invalid podGroupTemplate name": { + input: mkValidWorkload(setPodGroupName(0, "Invalid_Name")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, + "duplicate podGroupTemplate names": { + input: mkValidWorkload(addPodGroupTemplate("main")), + expectedErrs: field.ErrorList{field.Duplicate(field.NewPath("spec", "podGroupTemplates").Index(1), nil)}, + }, + // Declarative validation treats 0 as "missing" and returns Required error + // instead of checking minimum constraint and returning Invalid error. + "gang minCount zero": { + input: mkValidWorkload(setPodGroupMinCount(0, 0)), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingPolicy", "gang", "minCount"), "")}, + }, + "gang minCount negative": { + input: mkValidWorkload(setPodGroupMinCount(0, -1)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingPolicy", "gang", "minCount"), nil, "").WithOrigin("minimum")}, + }, + "valid with controllerRef": { + input: mkValidWorkload(setControllerRef("apps", "Deployment", "my-deployment")), + }, + "controllerRef with empty APIGroup": { + input: mkValidWorkload(setControllerRef("", "Pod", "my-pod")), + }, + "controllerRef invalid APIGroup": { + input: mkValidWorkload(setControllerRef("invalid_api_group", "Deployment", "my-deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef", "apiGroup"), nil, "").WithOrigin("format=k8s-long-name")}, + }, + "controllerRef too long APIGroup": { + input: mkValidWorkload(setControllerRef(strings.Repeat("g", 254), "Deployment", "my-deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef", "apiGroup"), nil, "").WithOrigin("format=k8s-long-name")}, + }, + "controllerRef missing kind": { + input: mkValidWorkload(setControllerRef("apps", "", "my-deployment")), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "controllerRef", "kind"), "")}, + }, + "controllerRef invalid kind with slash": { + input: mkValidWorkload(setControllerRef("apps", "Deploy/ment", "my-deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef", "kind"), nil, "").WithOrigin("format=k8s-path-segment-name")}, + }, + "controllerRef missing name": { + input: mkValidWorkload(setControllerRef("apps", "Deployment", "")), + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "controllerRef", "name"), "")}, + }, + "controllerRef invalid name": { + input: mkValidWorkload(setControllerRef("apps", "Deployment", "/invalid-name")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef", "name"), nil, "").WithOrigin("format=k8s-path-segment-name")}, + }, + "controllerRef invalid kind with percent": { + input: mkValidWorkload(setControllerRef("apps", "Deploy%ment", "my-deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef", "kind"), nil, "").WithOrigin("format=k8s-path-segment-name")}, + }, + "controllerRef invalid name with percent": { + input: mkValidWorkload(setControllerRef("apps", "Deployment", "my%deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef", "name"), nil, "").WithOrigin("format=k8s-path-segment-name")}, + }, + "policy with neither basic nor gang": { + input: mkValidWorkload(clearPodGroupPolicy(0)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingPolicy"), nil, "").WithOrigin("union")}, + }, + "policy with both basic and gang": { + input: mkValidWorkload(setBothPolicies(0)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingPolicy"), nil, "").WithOrigin("union")}, + }, + "valid with basic policy": { + input: mkValidWorkload(setBasicPolicy(0)), + }, + "valid with schedulingConstraints": { + input: mkValidWorkload(addTopologyConstraint(0, "foo")), + enableTopologyAwareScheduling: true, + }, + "valid with empty schedulingConstraints": { + input: mkValidWorkload(setSchedulingConstraints(0)), + enableTopologyAwareScheduling: true, + }, + "with multiple topology constraints": { + input: mkValidWorkload(addTopologyConstraint(0, "foo"), addTopologyConstraint(0, "bar")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.TooMany(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology"), 2, 1).WithOrigin("maxItems")}, + }, + "with empty topology key": { + input: mkValidWorkload(addTopologyConstraint(0, "")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology").Index(0).Child("key"), "")}, + }, + "valid with topology key with DNS prefix": { + input: mkValidWorkload(addTopologyConstraint(0, "example.com/Foo")), + enableTopologyAwareScheduling: true, + }, + "valid with topology key with prefix with max length": { + input: mkValidWorkload(addTopologyConstraint(0, strings.Repeat("a", 253)+"/"+strings.Repeat("b", 63))), + enableTopologyAwareScheduling: true, + }, + "with topology key with prefix exceending max prefix length": { + input: mkValidWorkload(addTopologyConstraint(0, strings.Repeat("a", 254)+"/foo")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "with topology key with prefix exceending max name length": { + input: mkValidWorkload(addTopologyConstraint(0, "foo/"+strings.Repeat("b", 64))), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "with topology key without prefix exceeding max length": { + input: mkValidWorkload(addTopologyConstraint(0, strings.Repeat("b", 64))), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "with topology key with invalid characters": { + input: mkValidWorkload(addTopologyConstraint(0, "Example.com/Foo")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")}, + }, + "pod disruption mode, workload aware preemption enabled": { + input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode)), + enableWorkloadAwarePreemption: true, + }, + "pod disruption mode, workload aware preemption disabled": { + input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), "")}, + }, + "pod group disruption mode, workload aware preemption enabled": { + input: mkValidWorkload(setDisruptionMode(0, podGroupDisruptionMode)), + enableWorkloadAwarePreemption: true, + }, + "pod group disruption mode, workload aware preemption disabled": { + input: mkValidWorkload(setDisruptionMode(0, podGroupDisruptionMode)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), "")}, + }, + "invalid disruption mode, workload aware preemption enabled": { + input: mkValidWorkload(setDisruptionMode(0, invalidDisruptionMode)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.NotSupported(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), invalidDisruptionMode, sets.List(allowedDisruptionModes))}, + }, + "invalid disruption mode, workload aware preemption disabled": { + input: mkValidWorkload(setDisruptionMode(0, invalidDisruptionMode)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), "")}, + }, + "valid priorityClassName, workload aware preemption enabled": { + input: mkValidWorkload(setPriorityClassName(0, "high-priority")), + enableWorkloadAwarePreemption: true, + }, + "valid priorityClassName, workload aware preemption disabled": { + input: mkValidWorkload(setPriorityClassName(0, "high-priority")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priorityClassName"), "")}, + }, + "invalid priorityClassName, workload aware preemption enabled": { + input: mkValidWorkload(setPriorityClassName(0, "high/priority")), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priorityClassName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "invalid priorityClassName, workload aware preemption disabled": { + input: mkValidWorkload(setPriorityClassName(0, "high/priority")), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priorityClassName"), "")}, + }, + + "valid priority, workload aware preemption enabled": { + input: mkValidWorkload(setPriority(0, 1000)), + enableWorkloadAwarePreemption: true, + }, + "valid priority, workload aware preemption disabled": { + input: mkValidWorkload(setPriority(0, 1000)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), "")}, + }, + "valid negative priority, workload aware preemption enabled": { + input: mkValidWorkload(setPriority(0, -2147483648)), + enableWorkloadAwarePreemption: true, + }, + "valid negative priority, workload aware preemption disabled": { + input: mkValidWorkload(setPriority(0, -2147483648)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), "")}, + }, + "too high priority, workload aware preemption enabled": { + input: mkValidWorkload(setPriority(0, scheduling.HighestUserDefinablePriority+1)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), nil, "").WithOrigin("maximum"), + }, + }, + "too high priority, workload aware preemption disabled": { + input: mkValidWorkload(setPriority(0, scheduling.HighestUserDefinablePriority+1)), + expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), "")}, + }, + "ok resourceClaimName reference": { + input: mkValidWorkload(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "claim", ResourceClaimName: new("resource-claim")})), + }, + "ok resourceClaimTemplateName reference": { + input: mkValidWorkload(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "claim", ResourceClaimTemplateName: new("resource-claim-template")})), + }, + "ok multiple claims": { + input: mkValidWorkload(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "claim-1", ResourceClaimName: new("resource-claim-1")}, + scheduling.PodGroupResourceClaim{Name: "claim-2", ResourceClaimName: new("resource-claim-2")}, + )), + }, + "claim name with prefix": { + input: mkValidWorkload(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "../my-claim", ResourceClaimName: new("resource-claim")})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name"), + }, + }, + "claim name with path": { + input: mkValidWorkload(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "my/claim", ResourceClaimName: new("resource-claim")})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name"), + }, + }, + "duplicate claim entries": { + input: mkValidWorkload(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimName: new("resource-claim-1")}, + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimName: new("resource-claim-2")}, + )), + expectedErrs: field.ErrorList{ + field.Duplicate(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(1), nil), + }, + }, + "resource claim source empty": { + input: mkValidWorkload(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim"}, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(0), nil, "").WithOrigin("union"), + }, + }, + "resource claim reference and template": { + input: mkValidWorkload(addResourceClaims( + scheduling.PodGroupResourceClaim{ + Name: "my-claim", + ResourceClaimName: new("resource-claim"), + ResourceClaimTemplateName: new("resource-claim-template"), + }, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(0), nil, "").WithOrigin("union"), + }, + }, + "invalid claim reference name": { + input: mkValidWorkload(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimName: new(".foo_bar")}, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(0).Child("resourceClaimName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "invalid claim template name": { + input: mkValidWorkload(addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new(".foo_bar")}, + )), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims").Index(0).Child("resourceClaimTemplateName"), nil, "").WithOrigin("format=k8s-long-name"), + }, + }, + "too many claims": { + input: mkValidWorkload(func(pg *scheduling.Workload) { + for i := range scheduling.MaxPodGroupResourceClaims + 1 { + pg.Spec.PodGroupTemplates[0].ResourceClaims = append(pg.Spec.PodGroupTemplates[0].ResourceClaims, scheduling.PodGroupResourceClaim{ + Name: "my-claim-" + strconv.Itoa(i), + ResourceClaimName: new("resource-claim"), + }) + } + }), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "podGroupTemplates").Index(0).Child("resourceClaims"), scheduling.MaxPodGroupResourceClaims+1, scheduling.MaxPodGroupResourceClaims).WithOrigin("maxItems"), + }, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.DRAWorkloadResourceClaims: tc.enableDRAWorkloadResourceClaims, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs, apitesting.WithMinEmulationVersion(version.MustParse("1.36"))) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + apiVersions := []string{"v1alpha2"} // Workload is currently only in v1alpha2 + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + testCases := map[string]struct { + oldObj scheduling.Workload + updateObj scheduling.Workload + enableTopologyAwareScheduling bool + enableDRAWorkloadResourceClaims bool + enableWorkloadAwarePreemption bool + expectedErrs field.ErrorList + }{ + "valid update": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1")), + }, + "valid update with unchanged controllerRef": { + oldObj: mkValidWorkload(setResourceVersion("1"), setControllerRef("apps", "Deployment", "my-deployment")), + updateObj: mkValidWorkload(setResourceVersion("1"), setControllerRef("apps", "Deployment", "my-deployment")), + }, + "set controllerRef": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), setControllerRef("apps", "Deployment", "different-deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef"), nil, "").WithOrigin("immutable")}, + }, + "invalid update controllerRef": { + oldObj: mkValidWorkload(setResourceVersion("1"), setControllerRef("apps", "Deployment", "my-deployment")), + updateObj: mkValidWorkload(setResourceVersion("1"), setControllerRef("apps", "Deployment", "different-deployment")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef"), nil, "").WithOrigin("immutable")}, + }, + "unset controllerRef": { + oldObj: mkValidWorkload(setResourceVersion("1"), setControllerRef("apps", "Deployment", "different-deployment")), + updateObj: mkValidWorkload(setResourceVersion("1")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "controllerRef"), nil, "").WithOrigin("immutable")}, + }, + "invalid update empty podGroupTemplates": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), setEmptyPodGroupTemplates()), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "podGroupTemplates"), "must have at least one item"), + field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable"), + }, + }, + "change podGroupTemplate name": { + oldObj: mkValidWorkload(setResourceVersion("1"), addPodGroupTemplate("worker1")), + updateObj: mkValidWorkload(setResourceVersion("1"), addPodGroupTemplate("worker2")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update too many podGroupTemplates": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), setManyPodGroupTemplates(scheduling.WorkloadMaxPodGroupTemplates+1)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "podGroupTemplates"), scheduling.WorkloadMaxPodGroupTemplates+1, scheduling.WorkloadMaxPodGroupTemplates).WithOrigin("maxItems"), + field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable"), + }, + }, + "add podGroupTemplate": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), addPodGroupTemplate("worker1")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "remove podGroupTemplate": { + oldObj: mkValidWorkload(setResourceVersion("1"), addPodGroupTemplate("worker1")), + updateObj: mkValidWorkload(setResourceVersion("1")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update with neither basic nor gang": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), clearPodGroupPolicy(0)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update with both basic and gang": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), setBothPolicies(0)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable"), + }, + }, + "invalid update of gang minCount": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), setPodGroupMinCount(0, 10)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "valid update from gang to basic policy": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), setBasicPolicy(0)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "valid update with unchanged scheduling constraints": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + enableTopologyAwareScheduling: true, + }, + "invalid update to scheduling constraints": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), setSchedulingConstraints(0)), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to topology constraints": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo"), addTopologyConstraint(0, "bar")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to topology key": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "bar")), + enableTopologyAwareScheduling: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "valid update with unchanged scheduling constraints with TAS disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + }, + "invalid update to scheduling constraints with TAS disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), setSchedulingConstraints(0)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to topology constraints with TAS disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo"), addTopologyConstraint(0, "bar")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid update to topology key with TAS disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "foo")), + updateObj: mkValidWorkload(setResourceVersion("1"), addTopologyConstraint(0, "bar")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "field is immutable").WithOrigin("immutable")}, + }, + "invalid add of resource claims, DRA workload resource claims disabled": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid add of resource claims, DRA workload resource claims enabled": { + oldObj: mkValidWorkload(setResourceVersion("1")), + updateObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + enableDRAWorkloadResourceClaims: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of resource claims, DRA workload resource claims disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + )), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of resource claims, DRA workload resource claims enabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-other-claim", ResourceClaimTemplateName: new("my-template")}, + )), + enableDRAWorkloadResourceClaims: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid remove of resource claims, DRA workload resource claims disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidWorkload(setResourceVersion("1")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid remove of resource claims, DRA workload resource claims enabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), addResourceClaims( + scheduling.PodGroupResourceClaim{Name: "my-claim", ResourceClaimTemplateName: new("my-template")}, + )), + updateObj: mkValidWorkload(setResourceVersion("1")), + enableDRAWorkloadResourceClaims: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of disruption mode, workload aware preemption enabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), setDisruptionMode(0, podDisruptionMode)), + updateObj: mkValidWorkload(setResourceVersion("1"), setDisruptionMode(0, podGroupDisruptionMode)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of disruption mode, workload aware preemption disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), setDisruptionMode(0, podDisruptionMode)), + updateObj: mkValidWorkload(setResourceVersion("1"), setDisruptionMode(0, podGroupDisruptionMode)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of priority class name, workload aware preemption enabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), setPriorityClassName(0, "low-priority")), + updateObj: mkValidWorkload(setResourceVersion("1"), setPriorityClassName(0, "high-priority")), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of priority class name, workload aware preemption disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), setPriorityClassName(0, "low-priority")), + updateObj: mkValidWorkload(setResourceVersion("1"), setPriorityClassName(0, "high-priority")), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of priority, workload aware preemption enabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), setPriority(0, 1000)), + updateObj: mkValidWorkload(setResourceVersion("1"), setPriority(0, 2000)), + enableWorkloadAwarePreemption: true, + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + "invalid update of priority, workload aware preemption disabled": { + oldObj: mkValidWorkload(setResourceVersion("1"), setPriority(0, 1000)), + updateObj: mkValidWorkload(setResourceVersion("1"), setPriority(0, 2000)), + expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")}, + }, + } + for k, tc := range testCases { + t.Run(k, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.DRAWorkloadResourceClaims: tc.enableDRAWorkloadResourceClaims, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIPrefix: "apis", + APIGroup: "scheduling.k8s.io", + APIVersion: apiVersion, + Resource: "workloads", + Name: "valid-workload", + IsResourceRequest: true, + Verb: "update", + }) + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs, apitesting.WithMinEmulationVersion(version.MustParse("1.36"))) + }) + } +} + +func mkValidWorkload(tweaks ...func(obj *scheduling.Workload)) scheduling.Workload { + obj := scheduling.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-workload", + Namespace: "default", + }, + Spec: scheduling.WorkloadSpec{ + PodGroupTemplates: []scheduling.PodGroupTemplate{ + { + Name: "main", + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 1, + }, + }, + }, + }, + }, + } + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} + +func setResourceVersion(v string) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.ResourceVersion = v + } +} + +func clearPodGroupTemplates() func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates = nil + } +} + +func setEmptyPodGroupTemplates() func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates = []scheduling.PodGroupTemplate{} + } +} + +func setManyPodGroupTemplates(n int) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates = make([]scheduling.PodGroupTemplate, n) + for i := range obj.Spec.PodGroupTemplates { + obj.Spec.PodGroupTemplates[i] = scheduling.PodGroupTemplate{ + Name: fmt.Sprintf("group-%d", i), + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 1, + }, + }, + } + } + } +} + +func addPodGroupTemplate(name string) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates = append(obj.Spec.PodGroupTemplates, scheduling.PodGroupTemplate{ + Name: name, + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 1, + }, + }, + }) + } +} + +func setPodGroupName(pgIdx int, name string) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].Name = name + } +} + +func setPodGroupMinCount(pgIdx, min int) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].SchedulingPolicy.Gang.MinCount = int32(min) + } +} + +func clearPodGroupPolicy(pgIdx int) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{} + } +} + +func setBasicPolicy(pgIdx int) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + } + } +} + +func setBothPolicies(pgIdx int) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].SchedulingPolicy = scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + Gang: &scheduling.GangSchedulingPolicy{MinCount: 1}, + } + } +} + +func setControllerRef(apiGroup, kind, name string) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.ControllerRef = &scheduling.TypedLocalObjectReference{ + APIGroup: apiGroup, + Kind: kind, + Name: name, + } + } +} + +func setSchedulingConstraints(pgIdx int) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{} + } +} + +func addTopologyConstraint(pgIdx int, topologyKey string) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + if obj.Spec.PodGroupTemplates[pgIdx].SchedulingConstraints == nil { + setSchedulingConstraints(pgIdx)(obj) + } + obj.Spec.PodGroupTemplates[pgIdx].SchedulingConstraints.Topology = append(obj.Spec.PodGroupTemplates[pgIdx].SchedulingConstraints.Topology, + scheduling.TopologyConstraint{Key: topologyKey}) + } +} + +func addResourceClaims(claims ...scheduling.PodGroupResourceClaim) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[0].ResourceClaims = append(obj.Spec.PodGroupTemplates[0].ResourceClaims, claims...) + } +} + +func setDisruptionMode(pgIdx int, mode scheduling.DisruptionMode) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].DisruptionMode = &mode + } +} + +func setPriorityClassName(pgIdx int, priorityClassName string) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].PriorityClassName = priorityClassName + } +} + +func setPriority(pgIdx int, priority int32) func(obj *scheduling.Workload) { + return func(obj *scheduling.Workload) { + obj.Spec.PodGroupTemplates[pgIdx].Priority = new(priority) + } +} diff --git a/pkg/registry/scheduling/workload/storage/storage_test.go b/pkg/registry/scheduling/workload/storage/storage_test.go index 36c646a82eb21..8720b3e70ca6b 100644 --- a/pkg/registry/scheduling/workload/storage/storage_test.go +++ b/pkg/registry/scheduling/workload/storage/storage_test.go @@ -23,7 +23,9 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" "k8s.io/kubernetes/pkg/apis/scheduling" @@ -52,10 +54,10 @@ func validNewWorkload() *scheduling.Workload { Name: "foo", }, Spec: scheduling.WorkloadSpec{ - PodGroups: []scheduling.PodGroup{ + PodGroupTemplates: []scheduling.PodGroupTemplate{ { Name: "bar", - Policy: scheduling.PodGroupPolicy{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ Gang: &scheduling.GangSchedulingPolicy{ MinCount: 5, }, @@ -66,11 +68,19 @@ func validNewWorkload() *scheduling.Workload { } } +func newTester(t *testing.T, storage *genericregistry.Store) *genericregistrytest.Tester { + return genericregistrytest.New(t, storage).SetRequestInfo(&genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: "v1alpha2", + Resource: "workloads", + }) +} + func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := genericregistrytest.New(t, storage.Store) + test := newTester(t, storage.Store) test.TestCreate( validNewWorkload(), // invalid cases @@ -86,11 +96,16 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := genericregistrytest.New(t, storage.Store) + test := newTester(t, storage.Store) test.TestUpdate( // valid validNewWorkload(), // valid update + // noop change + func(obj runtime.Object) runtime.Object { + return obj + }, + // invalid update // Set ControllerRef func(obj runtime.Object) runtime.Object { w := obj.(*scheduling.Workload) @@ -104,7 +119,7 @@ func TestUpdate(t *testing.T) { // Update MinCount func(obj runtime.Object) runtime.Object { w := obj.(*scheduling.Workload) - w.Spec.PodGroups[0].Policy.Gang.MinCount = 4 + w.Spec.PodGroupTemplates[0].SchedulingPolicy.Gang.MinCount = 4 return w }, ) @@ -114,7 +129,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := genericregistrytest.New(t, storage.Store) + test := newTester(t, storage.Store) test.TestDelete(validNewWorkload()) } @@ -122,7 +137,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := genericregistrytest.New(t, storage.Store) + test := newTester(t, storage.Store) test.TestGet(validNewWorkload()) } @@ -130,7 +145,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := genericregistrytest.New(t, storage.Store) + test := newTester(t, storage.Store) test.TestList(validNewWorkload()) } @@ -138,7 +153,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := genericregistrytest.New(t, storage.Store) + test := newTester(t, storage.Store) test.TestWatch( validNewWorkload(), // matching labels diff --git a/pkg/registry/scheduling/workload/strategy.go b/pkg/registry/scheduling/workload/strategy.go index 4878cfeb44e20..b42531fb49437 100644 --- a/pkg/registry/scheduling/workload/strategy.go +++ b/pkg/registry/scheduling/workload/strategy.go @@ -19,12 +19,16 @@ package workload import ( "context" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/scheduling/validation" + "k8s.io/kubernetes/pkg/features" ) // workloadStrategy implements behavior for Workload objects. @@ -40,10 +44,24 @@ func (workloadStrategy) NamespaceScoped() bool { return true } -func (workloadStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {} +func (workloadStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + dropDisabledWorkloadFields(obj.(*scheduling.Workload), nil) +} func (workloadStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - return validation.ValidateWorkload(obj.(*scheduling.Workload)) + workloadScheduling := obj.(*scheduling.Workload) + allErrs := validation.ValidateWorkload(workloadScheduling) + opts := []string{} + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) { + opts = append(opts, string(features.TopologyAwareWorkloadScheduling)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) { + opts = append(opts, string(features.DRAWorkloadResourceClaims)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) { + opts = append(opts, string(features.WorkloadAwarePreemption)) + } + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, allErrs, operation.Create, rest.WithDeclarativeEnforcement(), rest.WithOptions(opts)) } func (workloadStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { @@ -56,10 +74,27 @@ func (workloadStrategy) AllowCreateOnUpdate() bool { return false } -func (workloadStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {} +func (workloadStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + dropDisabledWorkloadFields(obj.(*scheduling.Workload), old.(*scheduling.Workload)) +} func (workloadStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateWorkloadUpdate(obj.(*scheduling.Workload), old.(*scheduling.Workload)) + allErrs := validation.ValidateWorkloadUpdate(obj.(*scheduling.Workload), old.(*scheduling.Workload)) + opts := []string{} + // Declarative validation will always allow fields to remain unchanged, so if any + // of the fields which are covered by these gates are set, we will not re-validate them + // (even if the gates are disabled) as long as they do not change values. If a gate + // is disabled, they will not be allowed to change values. + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) { + opts = append(opts, string(features.TopologyAwareWorkloadScheduling)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) { + opts = append(opts, string(features.DRAWorkloadResourceClaims)) + } + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) { + opts = append(opts, string(features.WorkloadAwarePreemption)) + } + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, allErrs, operation.Update, rest.WithDeclarativeEnforcement(), rest.WithOptions(opts)) } func (workloadStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { @@ -69,3 +104,110 @@ func (workloadStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.O func (workloadStrategy) AllowUnconditionalUpdate() bool { return true } + +// dropDisabledWorkloadFields removes fields which are covered by a feature gate. +func dropDisabledWorkloadFields(workload, oldWorkload *scheduling.Workload) { + var workloadSpec, oldWorkloadSpec *scheduling.WorkloadSpec + if workload != nil { + workloadSpec = &workload.Spec + } + if oldWorkload != nil { + oldWorkloadSpec = &oldWorkload.Spec + } + dropDisabledWorkloadSpecFields(workloadSpec, oldWorkloadSpec) +} + +func dropDisabledWorkloadSpecFields(workloadSpec, oldWorkloadSpec *scheduling.WorkloadSpec) { + var templates, oldTemplates []scheduling.PodGroupTemplate + if workloadSpec != nil { + templates = workloadSpec.PodGroupTemplates + } + if oldWorkloadSpec != nil { + oldTemplates = oldWorkloadSpec.PodGroupTemplates + } + dropDisabledPodGroupTemplatesFields(templates, oldTemplates) +} + +func dropDisabledPodGroupTemplatesFields(templates, oldTemplates []scheduling.PodGroupTemplate) { + m := len(oldTemplates) + for i := range templates { + var oldTemplate *scheduling.PodGroupTemplate + if i < m { + oldTemplate = &oldTemplates[i] + } + template := &templates[i] + dropDisabledSchedulingConstraintsFields(template, oldTemplate) + dropDisabledDRAWorkloadResourceClaimsFields(template, oldTemplate) + dropDisabledDisruptionModeField(template, oldTemplate) + dropDisabledPriorityClassNameField(template, oldTemplate) + dropDisabledPriorityField(template, oldTemplate) + } +} + +// dropDisabledSchedulingConstraintsFields drops the SchedulingConstraints field +// from the PodGroupTemplate if the TopologyAwareWorkloadScheduling feature gate is disabled. +func dropDisabledSchedulingConstraintsFields(template, oldTemplate *scheduling.PodGroupTemplate) { + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) || schedulingConstraintsInUse(oldTemplate) { + return + } + template.SchedulingConstraints = nil +} + +// dropDisabledDRAWorkloadResourceClaimsFields removes resource claim references from +// podGroupTemplates unless they are already used by the old Workload spec. +func dropDisabledDRAWorkloadResourceClaimsFields(template, oldTemplate *scheduling.PodGroupTemplate) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) || draWorkloadResourceClaimsInUse(oldTemplate) { + return + } + template.ResourceClaims = nil +} + +// dropDisabledDisruptionModeField removes the DisruptionMode field from a template +// unless it is already used in the old template. +func dropDisabledDisruptionModeField(template, oldTemplate *scheduling.PodGroupTemplate) { + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || disruptionModeInUse(oldTemplate) { + // No need to drop anything. + return + } + template.DisruptionMode = nil +} + +// dropDisabledPriorityClassNameField removes the PriorityClassName field from a template +// unless it is already used in the old template. +func dropDisabledPriorityClassNameField(template, oldTemplate *scheduling.PodGroupTemplate) { + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || priorityClassNameInUse(oldTemplate) { + // No need to drop anything. + return + } + template.PriorityClassName = "" +} + +// dropDisabledPriorityField removes the Priority field from a template unless it is +// already used in the old template. +func dropDisabledPriorityField(template, oldTemplate *scheduling.PodGroupTemplate) { + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || priorityInUse(oldTemplate) { + // No need to drop anything. + return + } + template.Priority = nil +} + +func schedulingConstraintsInUse(pgt *scheduling.PodGroupTemplate) bool { + return pgt != nil && pgt.SchedulingConstraints != nil +} + +func draWorkloadResourceClaimsInUse(pgt *scheduling.PodGroupTemplate) bool { + return pgt != nil && len(pgt.ResourceClaims) > 0 +} + +func disruptionModeInUse(pgt *scheduling.PodGroupTemplate) bool { + return pgt != nil && pgt.DisruptionMode != nil +} + +func priorityClassNameInUse(pgt *scheduling.PodGroupTemplate) bool { + return pgt != nil && pgt.PriorityClassName != "" +} + +func priorityInUse(pgt *scheduling.PodGroupTemplate) bool { + return pgt != nil && pgt.Priority != nil +} diff --git a/pkg/registry/scheduling/workload/strategy_test.go b/pkg/registry/scheduling/workload/strategy_test.go index 3378b9b8a1a1b..0e1eecb5ca4ea 100644 --- a/pkg/registry/scheduling/workload/strategy_test.go +++ b/pkg/registry/scheduling/workload/strategy_test.go @@ -17,31 +17,50 @@ limitations under the License. package workload import ( + "context" + "strings" "testing" + "github.com/google/go-cmp/cmp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" ) -var workload = &scheduling.Workload{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: scheduling.WorkloadSpec{ - PodGroups: []scheduling.PodGroup{ - { - Name: "bar", - Policy: scheduling.PodGroupPolicy{ - Gang: &scheduling.GangSchedulingPolicy{ - MinCount: 5, +var ( + workload = &scheduling.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: scheduling.WorkloadSpec{ + PodGroupTemplates: []scheduling.PodGroupTemplate{ + { + Name: "bar", + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Gang: &scheduling.GangSchedulingPolicy{ + MinCount: 5, + }, }, }, }, }, - }, -} + } + + podDisruptionMode = scheduling.DisruptionModePod + podGroupDisruptionMode = scheduling.DisruptionModePodGroup + invalidDisruptionMode = scheduling.DisruptionMode("Invalid") + + fieldImmutableError = "field is immutable" + minCountError = "must be greater than or equal to 1" + tooManyItemsError = "must have at most 1 item" + requiredError = "Required value" + subdomainNameError = "lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters" + supportedModesError = `supported values: "Pod", "PodGroup"` +) func TestWorkloadStrategy(t *testing.T) { if !Strategy.NamespaceScoped() { @@ -52,87 +71,627 @@ func TestWorkloadStrategy(t *testing.T) { } } -func TestPodSchedulingStrategyCreate(t *testing.T) { - t.Run("simple", func(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - workload := workload.DeepCopy() - - Strategy.PrepareForCreate(ctx, workload) - errs := Strategy.Validate(ctx, workload) - if len(errs) != 0 { - t.Errorf("Unexpected validation error: %v", errs) - } +func ctxWithRequestInfo() context.Context { + return genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "scheduling.k8s.io", + APIVersion: "v1alpha2", + Resource: "workloads", + IsResourceRequest: true, }) +} - t.Run("failed validation", func(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - workload := workload.DeepCopy() - workload.Spec.PodGroups[0].Policy.Gang.MinCount = -1 +func TestStrategyCreate(t *testing.T) { + ctx := ctxWithRequestInfo() - Strategy.PrepareForCreate(ctx, workload) - errs := Strategy.Validate(ctx, workload) - if len(errs) == 0 { - t.Errorf("Expected validation error") - } - }) + testCases := map[string]struct { + obj *scheduling.Workload + expectObj *scheduling.Workload + enableTopologyAwareScheduling bool + enableWorkloadAwarePreemption bool + expectValidationError string + }{ + "simple": { + obj: workload, + expectObj: workload, + }, + "failed validation": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].SchedulingPolicy.Gang.MinCount = -1 + return w + }(), + expectValidationError: minCountError, + }, + "drops field with SchedulingConstraints set and TAS disabled": { + obj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + expectObj: workload, + }, + "valid with SchedulingConstraints set and TAS enabled": { + obj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + expectObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + enableTopologyAwareScheduling: true, + }, + "invalid with multiple topology constraints": { + obj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{ + {Key: "foo"}, + {Key: "bar"}, + }, + } + return workload + }(), + enableTopologyAwareScheduling: true, + expectValidationError: tooManyItemsError, + }, + "invalid with invalid topology key": { + obj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{ + {Key: ""}, + }, + } + return workload + }(), + enableTopologyAwareScheduling: true, + expectValidationError: requiredError, + }, + "workload aware preemption disabled - drop disruption mode": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podDisruptionMode + return w + }(), + expectObj: workload, + }, + "workload aware preemption enabled - preserve disruption mode (pod)": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podDisruptionMode + return w + }(), + enableWorkloadAwarePreemption: true, + expectObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podDisruptionMode + return w + }(), + }, + "workload aware preemption enabled - preserve disruption mode (pod group)": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podGroupDisruptionMode + return w + }(), + enableWorkloadAwarePreemption: true, + expectObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podGroupDisruptionMode + return w + }(), + }, + "workload aware preemption enabled - unknown disruption mode": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &invalidDisruptionMode + return w + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: supportedModesError, + }, + "workload aware preemption enabled - preserve priorityClassName": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "high-priority" + return w + }(), + enableWorkloadAwarePreemption: true, + expectObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "high-priority" + return w + }(), + }, + "workload aware preemption disabled - drop priorityClassName": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "high-priority" + return w + }(), + expectObj: workload, + }, + "workload aware preemption enabled - invalid priorityClassName": { + obj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "invalid/priority/class/name" + return w + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: subdomainNameError, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + workload := tc.obj.DeepCopy() + + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + + Strategy.PrepareForCreate(ctx, workload) + if errs := Strategy.Validate(ctx, workload); len(errs) != 0 { + if tc.expectValidationError == "" { + t.Fatalf("unexpected error(s): %v", errs) + } + if len(errs) != 1 { + t.Fatalf("exactly one error expected") + } + if errMsg := errs[0].Error(); !strings.Contains(errMsg, tc.expectValidationError) { + t.Fatalf("error %#v does not contain the expected message %q", errMsg, tc.expectValidationError) + } + } + if tc.expectObj != nil { + if diff := cmp.Diff(tc.expectObj, workload); diff != "" { + t.Errorf("got unexpected workload object (-want, +got): %s", diff) + } + } + }) + } } -func TestPodSchedulingStrategyUpdate(t *testing.T) { - t.Run("no changes", func(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - workload := workload.DeepCopy() - newWorkload := workload.DeepCopy() - newWorkload.ResourceVersion = "4" - - Strategy.PrepareForUpdate(ctx, newWorkload, workload) - errs := Strategy.ValidateUpdate(ctx, newWorkload, workload) - if len(errs) != 0 { - t.Errorf("Unexpected validation error: %v", errs) - } - }) +func TestStrategyUpdate(t *testing.T) { + ctx := ctxWithRequestInfo() - t.Run("name update", func(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - workload := workload.DeepCopy() - newWorkload := workload.DeepCopy() - newWorkload.Name += "bar" - newWorkload.ResourceVersion = "4" - - Strategy.PrepareForUpdate(ctx, newWorkload, workload) - errs := Strategy.ValidateUpdate(ctx, newWorkload, workload) - if len(errs) == 0 { - t.Errorf("Expected validation error") - } - }) + testCases := map[string]struct { + oldObj *scheduling.Workload + newObj *scheduling.Workload + enableTopologyAwareScheduling bool + enableWorkloadAwarePreemption bool + expectValidationError string + expectWorkload *scheduling.Workload + }{ + "no changes": { + oldObj: workload, + newObj: workload, + expectWorkload: workload, + }, + "name update": { + oldObj: workload, + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Name += "bar" + return w + }(), + expectValidationError: fieldImmutableError, + }, + "invalid spec update - controllerRef": { + oldObj: workload, + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.ControllerRef = &scheduling.TypedLocalObjectReference{ + Kind: "foo", + Name: "baz", + } + return w + }(), + expectValidationError: fieldImmutableError, + }, + "invalid spec update - podGroupTemplates": { + oldObj: workload, + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].SchedulingPolicy.Gang.MinCount = 4 + return w + }(), + expectValidationError: fieldImmutableError, + }, + "valid update with scheduling constraints unchanged and TAS disabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + expectWorkload: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + }, + "valid update with scheduling constraints unchanged and TAS enabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + expectWorkload: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + enableTopologyAwareScheduling: true, + }, + "changing topology key not allowed with TAS disabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "bar"}}, + } + return workload + }(), + expectValidationError: fieldImmutableError, + }, + "changing topology key not allowed with TAS enabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "bar"}}, + } + return workload + }(), + enableTopologyAwareScheduling: true, + expectValidationError: fieldImmutableError, + }, + "topology constraint addition is dropped with TAS disabled": { + oldObj: workload, + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + expectWorkload: workload, + }, + "changing topology constraints not allowed with TAS enabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + workload.Spec.PodGroupTemplates[1].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + enableTopologyAwareScheduling: true, + expectValidationError: fieldImmutableError, + }, + "adding scheduling constraints not allowed with TAS disabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + workload.Spec.PodGroupTemplates[1].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + expectValidationError: fieldImmutableError, + }, + "adding scheduling constraints not allowed with TAS enabled": { + oldObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + newObj: func() *scheduling.Workload { + workload := workload.DeepCopy() + workload.Spec.PodGroupTemplates = append(workload.Spec.PodGroupTemplates, *workload.Spec.PodGroupTemplates[0].DeepCopy()) + workload.Spec.PodGroupTemplates[0].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + workload.Spec.PodGroupTemplates[1].SchedulingConstraints = &scheduling.PodGroupSchedulingConstraints{ + Topology: []scheduling.TopologyConstraint{{Key: "foo"}}, + } + return workload + }(), + enableTopologyAwareScheduling: true, + expectValidationError: fieldImmutableError, + }, + "disruption mode update, workload aware preemption disabled": { + oldObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podGroupDisruptionMode + return w + }(), + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podDisruptionMode + return w + }(), + expectValidationError: fieldImmutableError, + }, + "disruption mode update, workload aware preemption enabled": { + oldObj: workload, + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].DisruptionMode = &podDisruptionMode + return w + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: fieldImmutableError, + }, + "priorityClassName update, workload aware preemption disabled": { + oldObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "high-priority" + return w + }(), + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "low-priority" + return w + }(), + expectValidationError: fieldImmutableError, + }, + "priorityClassName update, workload aware preemption enabled": { + oldObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "high-priority" + return w + }(), + newObj: func() *scheduling.Workload { + w := workload.DeepCopy() + w.Spec.PodGroupTemplates[0].PriorityClassName = "low-priority" + return w + }(), + enableWorkloadAwarePreemption: true, + expectValidationError: fieldImmutableError, + }, + } - t.Run("spec update", func(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - workload := workload.DeepCopy() - newWorkload := workload.DeepCopy() - newWorkload.Spec.ControllerRef = &scheduling.TypedLocalObjectReference{ - Kind: "foo", - Name: "baz", - } - newWorkload.ResourceVersion = "4" + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: tc.enableTopologyAwareScheduling, + features.GangScheduling: tc.enableWorkloadAwarePreemption, + features.WorkloadAwarePreemption: tc.enableWorkloadAwarePreemption, + }) + oldWorkload := tc.oldObj.DeepCopy() + newWorkload := tc.newObj.DeepCopy() + newWorkload.ResourceVersion = "4" - Strategy.PrepareForUpdate(ctx, newWorkload, workload) - errs := Strategy.ValidateUpdate(ctx, newWorkload, workload) - if len(errs) != 0 { - t.Errorf("Unexpected validation error: %v", errs) - } - }) + Strategy.PrepareForUpdate(ctx, newWorkload, oldWorkload) + if errs := Strategy.ValidateUpdate(ctx, newWorkload, oldWorkload); len(errs) != 0 { + if tc.expectValidationError == "" { + t.Fatalf("unexpected error(s): %v", errs) + } + if len(errs) != 1 { + t.Fatalf("exactly one error expected") + } + if errMsg := errs[0].Error(); !strings.Contains(errMsg, tc.expectValidationError) { + t.Fatalf("error %#v does not contain the expected message %q", errMsg, tc.expectValidationError) + } + return + } + if tc.expectValidationError != "" { + t.Fatal("expected validation error(s), got none") + } + if warnings := Strategy.WarningsOnUpdate(ctx, newWorkload, oldWorkload); len(warnings) != 0 { + t.Fatalf("unexpected warnings: %q", warnings) + } + Strategy.Canonicalize(newWorkload) + + expectWorkload := tc.expectWorkload.DeepCopy() + expectWorkload.ResourceVersion = "4" + if diff := cmp.Diff(expectWorkload, newWorkload); diff != "" { + t.Errorf("Workload mismatch (-want +got):\n%s", diff) + } + }) + } +} - t.Run("invalid spec update", func(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - workload := workload.DeepCopy() - newWorkload := workload.DeepCopy() - newWorkload.Spec.PodGroups[0].Policy.Gang.MinCount = 4 - newWorkload.ResourceVersion = "4" - - Strategy.PrepareForUpdate(ctx, newWorkload, workload) - errs := Strategy.ValidateUpdate(ctx, newWorkload, workload) - if len(errs) == 0 { - t.Errorf("Expected validation error") +func TestDropPodGroupTemplateResourceClaims(t *testing.T) { + var noWorkload *scheduling.Workload + workloadWithoutClaims := workload + workloadWithClaims := func() *scheduling.Workload { + w := workloadWithoutClaims.DeepCopy() + w.Spec.PodGroupTemplates[0].ResourceClaims = []scheduling.PodGroupResourceClaim{ + { + Name: "my-claim", + ResourceClaimName: new("resource-claim"), + }, } - }) + return w + }() + + tests := []struct { + description string + enabled bool + oldWorkload *scheduling.Workload + newWorkload *scheduling.Workload + wantWorkload *scheduling.Workload + }{ + { + description: "old with claims / new with claims / disabled", + oldWorkload: workloadWithClaims, + newWorkload: workloadWithClaims, + wantWorkload: workloadWithClaims, + }, + { + description: "old without claims / new with claims / disabled", + oldWorkload: workloadWithoutClaims, + newWorkload: workloadWithClaims, + wantWorkload: workloadWithoutClaims, + }, + { + description: "no old workload / new with claims / disabled", + oldWorkload: noWorkload, + newWorkload: workloadWithClaims, + wantWorkload: workloadWithoutClaims, + }, + + { + description: "old with claims / new without claims / disabled", + oldWorkload: workloadWithClaims, + newWorkload: workloadWithoutClaims, + wantWorkload: workloadWithoutClaims, + }, + { + description: "old without claims / new without claims / disabled", + oldWorkload: workloadWithoutClaims, + newWorkload: workloadWithoutClaims, + wantWorkload: workloadWithoutClaims, + }, + { + description: "no old workload / new without claims / disabled", + oldWorkload: noWorkload, + newWorkload: workloadWithoutClaims, + wantWorkload: workloadWithoutClaims, + }, + + { + description: "old with claims / new with claims / enabled", + enabled: true, + oldWorkload: workloadWithClaims, + newWorkload: workloadWithClaims, + wantWorkload: workloadWithClaims, + }, + { + description: "old without claims / new with claims / enabled", + enabled: true, + oldWorkload: workloadWithoutClaims, + newWorkload: workloadWithClaims, + wantWorkload: workloadWithClaims, + }, + { + description: "no old workload / new with claims / enabled", + enabled: true, + oldWorkload: noWorkload, + newWorkload: workloadWithClaims, + wantWorkload: workloadWithClaims, + }, + + { + description: "old with claims / new without claims / enabled", + enabled: true, + oldWorkload: workloadWithClaims, + newWorkload: workloadWithoutClaims, + wantWorkload: workloadWithoutClaims, + }, + { + description: "old without claims / new without claims / enabled", + enabled: true, + oldWorkload: workloadWithoutClaims, + newWorkload: workloadWithoutClaims, + wantWorkload: workloadWithoutClaims, + }, + { + description: "no old workload / new without claims / enabled", + enabled: true, + oldWorkload: noWorkload, + newWorkload: workloadWithoutClaims, + wantWorkload: workloadWithoutClaims, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRAWorkloadResourceClaims: tc.enabled, + features.GenericWorkload: tc.enabled, + }) + + oldWorkload := tc.oldWorkload.DeepCopy() + newWorkload := tc.newWorkload.DeepCopy() + wantWorkload := tc.wantWorkload + dropDisabledWorkloadFields(newWorkload, oldWorkload) + + // old Workload should never be changed + if diff := cmp.Diff(oldWorkload, tc.oldWorkload); diff != "" { + t.Errorf("old Workload changed: %s", diff) + } + + if diff := cmp.Diff(wantWorkload, newWorkload); diff != "" { + t.Errorf("new Workload changed (- want, + got): %s", diff) + } + }) + } } diff --git a/pkg/registry/storage/csidriver/strategy.go b/pkg/registry/storage/csidriver/strategy.go index 03cd1963c1c04..510784acbe36d 100644 --- a/pkg/registry/storage/csidriver/strategy.go +++ b/pkg/registry/storage/csidriver/strategy.go @@ -60,6 +60,9 @@ func (csiDriverStrategy) PrepareForCreate(ctx context.Context, obj runtime.Objec if !utilfeature.DefaultFeatureGate.Enabled(features.CSIServiceAccountTokenSecrets) { csiDriver.Spec.ServiceAccountTokenInSecrets = nil } + if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeLimitScaling) { + csiDriver.Spec.PreventPodSchedulingIfMissing = nil + } } func (csiDriverStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { @@ -113,6 +116,11 @@ func (csiDriverStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime. newCSIDriver.Spec.NodeAllocatableUpdatePeriodSeconds = nil } + if oldCSIDriver.Spec.PreventPodSchedulingIfMissing == nil && + !utilfeature.DefaultFeatureGate.Enabled(features.VolumeLimitScaling) { + newCSIDriver.Spec.PreventPodSchedulingIfMissing = nil + } + // Any changes to the spec increment the generation number. if !apiequality.Semantic.DeepEqual(oldCSIDriver.Spec, newCSIDriver.Spec) { newCSIDriver.Generation = oldCSIDriver.Generation + 1 diff --git a/pkg/registry/storage/csidriver/strategy_test.go b/pkg/registry/storage/csidriver/strategy_test.go index ae69c69edf96e..95bc67629cf4d 100644 --- a/pkg/registry/storage/csidriver/strategy_test.go +++ b/pkg/registry/storage/csidriver/strategy_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -38,11 +39,12 @@ func getValidCSIDriver(name string) *storage.CSIDriver { Name: name, }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, } } @@ -212,6 +214,23 @@ func TestCSIDriverPrepareForUpdate(t *testing.T) { }, } + driverWithPreventPodSchedulingIfMissingEnabled := &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + PreventPodSchedulingIfMissing: &enabled, + }, + } + driverWithPreventPodSchedulingIfMissingDisabled := &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + PreventPodSchedulingIfMissing: &disabled, + }, + } + thirty := int64(30) sixty := int64(60) driverWithNodeAllocatableUpdatePeriodSeconds30 := &storage.CSIDriver{ @@ -247,6 +266,8 @@ func TestCSIDriverPrepareForUpdate(t *testing.T) { wantSELinuxMount *bool wantNodeAllocatableUpdatePeriodSeconds *int64 wantServiceAccountTokenInSecrets *bool + volumeLimitScalingEnabled bool + wantPreventPodSchedulingIfMissing *bool }{ { name: "podInfoOnMount feature enabled, before: none, update: enabled", @@ -451,14 +472,53 @@ func TestCSIDriverPrepareForUpdate(t *testing.T) { wantTokenRequests: []storage.TokenRequest{{Audience: gcp}}, wantGeneration: 0, }, + { + name: "VolumeLimitScaling feature enabled, before: nil, update: enabled", + volumeLimitScalingEnabled: true, + old: driverWithNothing, + update: driverWithPreventPodSchedulingIfMissingEnabled, + wantPreventPodSchedulingIfMissing: &enabled, + wantGeneration: 1, + }, + { + name: "VolumeLimitScaling feature enabled, before: enabled, update: disabled", + volumeLimitScalingEnabled: true, + old: driverWithPreventPodSchedulingIfMissingEnabled, + update: driverWithPreventPodSchedulingIfMissingDisabled, + wantPreventPodSchedulingIfMissing: &disabled, + wantGeneration: 1, + }, + { + name: "VolumeLimitScaling feature disabled, before: nil, update: enabled", + volumeLimitScalingEnabled: false, + old: driverWithNothing, + update: driverWithPreventPodSchedulingIfMissingEnabled, + wantPreventPodSchedulingIfMissing: nil, + wantGeneration: 0, + }, + { + name: "VolumeLimitScaling feature disabled, before: enabled, update: enabled", + volumeLimitScalingEnabled: false, + old: driverWithPreventPodSchedulingIfMissingEnabled, + update: driverWithPreventPodSchedulingIfMissingEnabled, + wantPreventPodSchedulingIfMissing: &enabled, + wantGeneration: 0, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if !test.csiServiceAccountTokenSecretsEnabled || !test.seLinuxMountReadWriteOncePodEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } + if !test.mutableCSINodeAllocatableCountEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.SELinuxMountReadWriteOncePod: test.seLinuxMountReadWriteOncePodEnabled, features.MutableCSINodeAllocatableCount: test.mutableCSINodeAllocatableCountEnabled, features.CSIServiceAccountTokenSecrets: test.csiServiceAccountTokenSecretsEnabled, + features.VolumeLimitScaling: test.volumeLimitScalingEnabled, }) csiDriver := test.update.DeepCopy() @@ -471,6 +531,7 @@ func TestCSIDriverPrepareForUpdate(t *testing.T) { require.Equal(t, test.wantSELinuxMount, csiDriver.Spec.SELinuxMount) require.Equal(t, test.wantNodeAllocatableUpdatePeriodSeconds, csiDriver.Spec.NodeAllocatableUpdatePeriodSeconds) require.Equal(t, test.wantServiceAccountTokenInSecrets, csiDriver.Spec.ServiceAccountTokenInSecrets) + require.Equal(t, test.wantPreventPodSchedulingIfMissing, csiDriver.Spec.PreventPodSchedulingIfMissing) }) } } @@ -500,11 +561,12 @@ func TestCSIDriverValidation(t *testing.T) { Name: "foo", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, false, @@ -516,12 +578,12 @@ func TestCSIDriverValidation(t *testing.T) { Name: "foo", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &disabled, - - PodInfoOnMount: &disabled, - StorageCapacity: &disabled, - RequiresRepublish: &disabled, - SELinuxMount: &disabled, + AttachRequired: &disabled, + PodInfoOnMount: &disabled, + StorageCapacity: &disabled, + RequiresRepublish: &disabled, + SELinuxMount: &disabled, + PreventPodSchedulingIfMissing: &disabled, }, }, false, @@ -533,11 +595,12 @@ func TestCSIDriverValidation(t *testing.T) { Name: "*foo#", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, true, @@ -555,8 +618,9 @@ func TestCSIDriverValidation(t *testing.T) { VolumeLifecycleModes: []storage.VolumeLifecycleMode{ storage.VolumeLifecycleMode("no-such-mode"), }, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, true, @@ -574,8 +638,9 @@ func TestCSIDriverValidation(t *testing.T) { VolumeLifecycleModes: []storage.VolumeLifecycleMode{ storage.VolumeLifecyclePersistent, }, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, false, @@ -593,8 +658,9 @@ func TestCSIDriverValidation(t *testing.T) { VolumeLifecycleModes: []storage.VolumeLifecycleMode{ storage.VolumeLifecycleEphemeral, }, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, false, @@ -613,8 +679,9 @@ func TestCSIDriverValidation(t *testing.T) { storage.VolumeLifecyclePersistent, storage.VolumeLifecycleEphemeral, }, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, false, @@ -626,12 +693,13 @@ func TestCSIDriverValidation(t *testing.T) { Name: "foo", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - TokenRequests: []storage.TokenRequest{{Audience: gcp}}, - RequiresRepublish: &enabled, - SELinuxMount: &enabled, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + TokenRequests: []storage.TokenRequest{{Audience: gcp}}, + RequiresRepublish: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, }, }, false, @@ -643,10 +711,11 @@ func TestCSIDriverValidation(t *testing.T) { Name: "foo", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - SELinuxMount: nil, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + SELinuxMount: nil, + PreventPodSchedulingIfMissing: &enabled, }, }, true, @@ -662,6 +731,7 @@ func TestCSIDriverValidation(t *testing.T) { PodInfoOnMount: &enabled, StorageCapacity: &enabled, SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, NodeAllocatableUpdatePeriodSeconds: &validNodeAllocatableUpdatePeriodSeconds, }, }, @@ -678,6 +748,7 @@ func TestCSIDriverValidation(t *testing.T) { PodInfoOnMount: &enabled, StorageCapacity: &enabled, SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, NodeAllocatableUpdatePeriodSeconds: &invalidNodeAllocatableUpdatePeriodSeconds, }, }, @@ -690,12 +761,13 @@ func TestCSIDriverValidation(t *testing.T) { Name: "foo", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - SELinuxMount: &enabled, - ServiceAccountTokenInSecrets: &enabled, - TokenRequests: tokenRequests, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, + ServiceAccountTokenInSecrets: &enabled, + TokenRequests: tokenRequests, }, }, false, @@ -707,11 +779,59 @@ func TestCSIDriverValidation(t *testing.T) { Name: "foo", }, Spec: storage.CSIDriverSpec{ - AttachRequired: &enabled, - PodInfoOnMount: &enabled, - StorageCapacity: &enabled, - SELinuxMount: &enabled, - ServiceAccountTokenInSecrets: &enabled, + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, + ServiceAccountTokenInSecrets: &enabled, + }, + }, + true, + }, + { + "valid PreventPodSchedulingIfMissing set to true", + &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &enabled, + }, + }, + false, + }, + { + "valid PreventPodSchedulingIfMissing set to false", + &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + SELinuxMount: &enabled, + PreventPodSchedulingIfMissing: &disabled, + }, + }, + false, + }, + { + "invalid PreventPodSchedulingIfMissing not set (nil)", + &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + AttachRequired: &enabled, + PodInfoOnMount: &enabled, + StorageCapacity: &enabled, + SELinuxMount: &enabled, }, }, true, @@ -722,10 +842,12 @@ func TestCSIDriverValidation(t *testing.T) { t.Run(test.name, func(t *testing.T) { // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationSELinuxMountEnabledDisabled // and TestCSIDriverValidationServiceAccountTokenInSecretsEnabledDisabled + // and TestCSIDriverValidationPreventPodSchedulingIfMissingEnabledDisabled featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.SELinuxMountReadWriteOncePod: true, features.MutableCSINodeAllocatableCount: true, features.CSIServiceAccountTokenSecrets: true, + features.VolumeLimitScaling: true, }) testValidation := func(csiDriver *storage.CSIDriver, apiVersion string) field.ErrorList { @@ -807,6 +929,9 @@ func TestWarningsOnCreate(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if !test.csiServiceAccountTokenSecretsEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.CSIServiceAccountTokenSecrets: test.csiServiceAccountTokenSecretsEnabled, }) @@ -976,6 +1101,9 @@ func TestWarningsOnUpdate(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if !test.csiServiceAccountTokenSecretsEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.CSIServiceAccountTokenSecrets: test.csiServiceAccountTokenSecretsEnabled, }) @@ -1005,6 +1133,8 @@ func TestCSIDriverPrepareForCreate(t *testing.T) { csiDriver *storage.CSIDriver csiServiceAccountTokenSecretsEnabled bool wantServiceAccountTokenInSecrets *bool + volumeLimitScalingEnabled bool + wantPreventPodSchedulingIfMissing *bool }{ { name: "ServiceAccountTokenInSecrets feature enabled, field set to true", @@ -1060,17 +1190,70 @@ func TestCSIDriverPrepareForCreate(t *testing.T) { csiServiceAccountTokenSecretsEnabled: false, wantServiceAccountTokenInSecrets: nil, }, + { + name: "VolumeLimitScaling feature enabled, field set to true", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + PreventPodSchedulingIfMissing: &enabled, + }, + }, + volumeLimitScalingEnabled: true, + wantPreventPodSchedulingIfMissing: &enabled, + }, + { + name: "VolumeLimitScaling feature disabled, field set to true should be cleared", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{ + PreventPodSchedulingIfMissing: &enabled, + }, + }, + volumeLimitScalingEnabled: false, + wantPreventPodSchedulingIfMissing: nil, + }, + { + name: "VolumeLimitScaling feature enabled, field not set", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{}, + }, + volumeLimitScalingEnabled: true, + wantPreventPodSchedulingIfMissing: nil, + }, + { + name: "VolumeLimitScaling feature disabled, field not set", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: storage.CSIDriverSpec{}, + }, + volumeLimitScalingEnabled: false, + wantPreventPodSchedulingIfMissing: nil, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if !test.csiServiceAccountTokenSecretsEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.CSIServiceAccountTokenSecrets: test.csiServiceAccountTokenSecretsEnabled, + features.VolumeLimitScaling: test.volumeLimitScalingEnabled, }) csiDriver := test.csiDriver.DeepCopy() Strategy.PrepareForCreate(ctx, csiDriver) require.Equal(t, test.wantServiceAccountTokenInSecrets, csiDriver.Spec.ServiceAccountTokenInSecrets) + require.Equal(t, test.wantPreventPodSchedulingIfMissing, csiDriver.Spec.PreventPodSchedulingIfMissing) }) } } diff --git a/pkg/registry/storage/csinode/strategy.go b/pkg/registry/storage/csinode/strategy.go index 5341f1bd36d89..3caf939551814 100644 --- a/pkg/registry/storage/csinode/strategy.go +++ b/pkg/registry/storage/csinode/strategy.go @@ -47,13 +47,7 @@ func (csiNodeStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) func (csiNodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { csiNode := obj.(*storage.CSINode) - validateOptions := validation.CSINodeValidationOptions{ - AllowLongNodeID: true, - } - - errs := validation.ValidateCSINode(csiNode, validateOptions) - - return errs + return validation.ValidateCSINode(csiNode) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -74,12 +68,7 @@ func (csiNodeStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob func (csiNodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { newCSINodeObj := obj.(*storage.CSINode) oldCSINodeObj := old.(*storage.CSINode) - validateOptions := validation.CSINodeValidationOptions{ - AllowLongNodeID: true, - } - - errorList := validation.ValidateCSINode(newCSINodeObj, validateOptions) - return append(errorList, validation.ValidateCSINodeUpdate(newCSINodeObj, oldCSINodeObj, validateOptions)...) + return validation.ValidateCSINodeUpdate(newCSINodeObj, oldCSINodeObj) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/storage/storageclass/declarative_validation_test.go b/pkg/registry/storage/storageclass/declarative_validation_test.go index 47c598b259c2a..4e1f717179aad 100644 --- a/pkg/registry/storage/storageclass/declarative_validation_test.go +++ b/pkg/registry/storage/storageclass/declarative_validation_test.go @@ -27,8 +27,9 @@ import ( "k8s.io/kubernetes/pkg/apis/storage" ) +var apiVersions = []string{"v1beta1", "v1"} // StorageClass.Provisioner not in v1alpha1 + func TestDeclarativeValidate(t *testing.T) { - apiVersions := []string{"v1beta1", "v1"} // StorageClass.Provisioner not in v1alpha1 for _, apiVersion := range apiVersions { t.Run(apiVersion, func(t *testing.T) { testDeclarativeValidate(t, apiVersion) @@ -57,7 +58,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { obj.Provisioner = "" }), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("provisioner"), ""), + field.Required(field.NewPath("provisioner"), "").MarkAlpha(), }, }, // TODO: Add more test cases @@ -70,7 +71,6 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { } func TestDeclarativeValidateUpdate(t *testing.T) { - apiVersions := []string{"v1beta1", "v1"} // StorageClass.Provisioner not in v1alpha1 for _, apiVersion := range apiVersions { t.Run(apiVersion, func(t *testing.T) { testDeclarativeValidateUpdate(t, apiVersion) @@ -85,23 +85,100 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { expectedErrs field.ErrorList }{ "valid update": { - oldObj: mkValidStorageClass(func(obj *storage.StorageClass) { obj.ResourceVersion = "1" }), - updateObj: mkValidStorageClass(func(obj *storage.StorageClass) { obj.ResourceVersion = "1" }), + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(), }, - "invalid update provisioner": { - oldObj: mkValidStorageClass(func(obj *storage.StorageClass) { obj.ResourceVersion = "1" }), - updateObj: mkValidStorageClass(func(obj *storage.StorageClass) { - obj.ResourceVersion = "1" - obj.Provisioner = "" - }), + "invalid update provisioner changed": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakProvisioner("kubernetes.io/aws-ebs")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("provisioner"), "", "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update provisioner unset to set": { + oldObj: mkValidStorageClass(TweakProvisioner("")), + updateObj: mkValidStorageClass(), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("provisioner"), "", "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update provisioner set to unset": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakProvisioner("")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("provisioner"), "", "").WithOrigin("immutable").MarkAlpha(), + field.Required(field.NewPath("provisioner"), "").MarkAlpha(), + }, + }, + "invalid update parameters changed": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakParameters(map[string]string{"new": "value"})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("parameters"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update parameters unset to set": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakParameters(map[string]string{"foo": "bar"})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("parameters"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update parameters set to unset": { + oldObj: mkValidStorageClass(TweakParameters(map[string]string{"foo": "bar"})), + updateObj: mkValidStorageClass(), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("parameters"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update reclaimPolicy changed": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakReclaimPolicy(api.PersistentVolumeReclaimRetain)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("reclaimPolicy"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update reclaimPolicy unset to set": { + oldObj: mkValidStorageClass(TweakReclaimPolicyNil()), + updateObj: mkValidStorageClass(), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("reclaimPolicy"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update reclaimPolicy set to unset": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakReclaimPolicyNil()), expectedErrs: field.ErrorList{ - field.Required(field.NewPath("provisioner"), ""), - field.Forbidden(field.NewPath("provisioner"), "updates to provisioner are forbidden."), + field.Invalid(field.NewPath("reclaimPolicy"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update volumeBindingMode changed": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakVolumeBindingMode(storage.VolumeBindingWaitForFirstConsumer)), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("volumeBindingMode"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update volumeBindingMode unset to set": { + oldObj: mkValidStorageClass(TweakVolumeBindingModeNil()), + updateObj: mkValidStorageClass(), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("volumeBindingMode"), nil, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + "invalid update volumeBindingMode set to unset": { + oldObj: mkValidStorageClass(), + updateObj: mkValidStorageClass(TweakVolumeBindingModeNil()), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("volumeBindingMode"), "").MarkFromImperative(), + field.Invalid(field.NewPath("volumeBindingMode"), nil, "").WithOrigin("immutable").MarkAlpha(), }, }, } for k, tc := range testCases { t.Run(k, func(t *testing.T) { + tc.oldObj.ResourceVersion = "1" + tc.updateObj.ResourceVersion = "2" ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ APIPrefix: "apis", APIGroup: "storage.k8s.io", @@ -132,3 +209,39 @@ func mkValidStorageClass(tweaks ...func(obj *storage.StorageClass)) storage.Stor } return obj } + +func TweakProvisioner(provisioner string) func(obj *storage.StorageClass) { + return func(obj *storage.StorageClass) { + obj.Provisioner = provisioner + } +} + +func TweakParameters(parameters map[string]string) func(obj *storage.StorageClass) { + return func(obj *storage.StorageClass) { + obj.Parameters = parameters + } +} + +func TweakReclaimPolicy(reclaimPolicy api.PersistentVolumeReclaimPolicy) func(obj *storage.StorageClass) { + return func(obj *storage.StorageClass) { + obj.ReclaimPolicy = &reclaimPolicy + } +} + +func TweakReclaimPolicyNil() func(obj *storage.StorageClass) { + return func(obj *storage.StorageClass) { + obj.ReclaimPolicy = nil + } +} + +func TweakVolumeBindingMode(volumeBindingMode storage.VolumeBindingMode) func(obj *storage.StorageClass) { + return func(obj *storage.StorageClass) { + obj.VolumeBindingMode = &volumeBindingMode + } +} + +func TweakVolumeBindingModeNil() func(obj *storage.StorageClass) { + return func(obj *storage.StorageClass) { + obj.VolumeBindingMode = nil + } +} diff --git a/pkg/registry/storage/volumeattachment/declarative_validation_test.go b/pkg/registry/storage/volumeattachment/declarative_validation_test.go new file mode 100644 index 0000000000000..03bfeb6f7accc --- /dev/null +++ b/pkg/registry/storage/volumeattachment/declarative_validation_test.go @@ -0,0 +1,155 @@ +/* +Copyright 2025 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 volumeattachment + +import ( + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + apitesting "k8s.io/kubernetes/pkg/api/testing" + storage "k8s.io/kubernetes/pkg/apis/storage" +) + +var apiVersions = []string{"v1", "v1alpha1", "v1beta1"} + +func TestDeclarativeValidate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidate(t, apiVersion) + }) + } +} + +func TestDeclarativeValidateUpdate(t *testing.T) { + for _, apiVersion := range apiVersions { + t.Run(apiVersion, func(t *testing.T) { + testDeclarativeValidateUpdate(t, apiVersion) + }) + } +} + +func testDeclarativeValidate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "storage.k8s.io", + APIVersion: apiVersion, + Resource: "volumeattachments", + IsResourceRequest: true, + Verb: "create", + }) + + testCases := map[string]struct { + input storage.VolumeAttachment + expectedErrs field.ErrorList + }{ + "valid": { + input: mkValidVolumeAttachment(), + }, + "attacher with upper characters": { + input: mkValidVolumeAttachment(TweakAttacher("AAAAAAAA")), + }, + "attacher with 63 characters": { + input: mkValidVolumeAttachment(TweakAttacher(strings.Repeat("a", 63))), + }, + "invalid attacher (required)": { + input: mkValidVolumeAttachment(TweakAttacher("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "attacher"), "").MarkAlpha(), + }, + }, + "attacher with special characters": { + input: mkValidVolumeAttachment(TweakAttacher("asdadasd&@!")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "attacher"), "", "").WithOrigin("format=k8s-long-name-caseless").MarkAlpha(), + }, + }, + "attacher with number of characters exceeds 63": { + input: mkValidVolumeAttachment(TweakAttacher(strings.Repeat("a", 64))), + expectedErrs: field.ErrorList{ + field.TooLong(field.NewPath("spec", "attacher"), strings.Repeat("a", 64), 63).WithOrigin("maxLength").MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs) + }) + } +} + +func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { + ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{ + APIGroup: "storage.k8s.io", + APIVersion: apiVersion, + Resource: "volumeattachments", + IsResourceRequest: true, + Verb: "update", + }) + + testCases := map[string]struct { + oldInput storage.VolumeAttachment + newInput storage.VolumeAttachment + expectedErrs field.ErrorList + }{ + "valid update": { + oldInput: mkValidVolumeAttachment(), + newInput: mkValidVolumeAttachment(), + }, + "immutable spec.attacher": { + oldInput: mkValidVolumeAttachment(), + newInput: mkValidVolumeAttachment(TweakAttacher("different.com")), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec"), nil, "field is immutable").WithOrigin("immutable").MarkAlpha(), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.newInput, &tc.oldInput, Strategy.ValidateUpdate, tc.expectedErrs) + }) + } +} + +func TweakAttacher(attacher string) func(obj *storage.VolumeAttachment) { + return func(obj *storage.VolumeAttachment) { + obj.Spec.Attacher = attacher + } +} + +func mkValidVolumeAttachment(tweaks ...func(obj *storage.VolumeAttachment)) storage.VolumeAttachment { + pvName := "pv-001" + obj := storage.VolumeAttachment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-volume-attachment", + }, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "example.com", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + NodeName: "node-1", + }, + } + for _, tweak := range tweaks { + tweak(&obj) + } + return obj +} diff --git a/pkg/registry/storage/volumeattachment/strategy.go b/pkg/registry/storage/volumeattachment/strategy.go index a79d7406e2e45..4f0d41013bbc6 100644 --- a/pkg/registry/storage/volumeattachment/strategy.go +++ b/pkg/registry/storage/volumeattachment/strategy.go @@ -18,6 +18,8 @@ package volumeattachment import ( "context" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apiserver/pkg/registry/rest" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -65,12 +67,8 @@ func (volumeAttachmentStrategy) PrepareForCreate(ctx context.Context, obj runtim func (volumeAttachmentStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { volumeAttachment := obj.(*storage.VolumeAttachment) - errs := validation.ValidateVolumeAttachment(volumeAttachment) - - // tighten up validation of newly created v1 attachments - errs = append(errs, validation.ValidateVolumeAttachmentV1(volumeAttachment)...) - return errs + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, errs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -99,7 +97,8 @@ func (volumeAttachmentStrategy) PrepareForUpdate(ctx context.Context, obj, old r func (volumeAttachmentStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { newVolumeAttachmentObj := obj.(*storage.VolumeAttachment) oldVolumeAttachmentObj := old.(*storage.VolumeAttachment) - return validation.ValidateVolumeAttachmentUpdate(newVolumeAttachmentObj, oldVolumeAttachmentObj) + allErrs := validation.ValidateVolumeAttachmentUpdate(newVolumeAttachmentObj, oldVolumeAttachmentObj) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/pkg/registry/storage/volumeattachment/strategy_test.go b/pkg/registry/storage/volumeattachment/strategy_test.go index 82fc4720875d7..a07f11bb97e39 100644 --- a/pkg/registry/storage/volumeattachment/strategy_test.go +++ b/pkg/registry/storage/volumeattachment/strategy_test.go @@ -24,6 +24,7 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -182,6 +183,7 @@ func TestVolumeAttachmentStatusStrategy(t *testing.T) { } // Verify that error codes are dropped when the feature gate is disabled. + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.35")) featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.MutableCSINodeAllocatableCount, false) statusWithError := volumeAttachment.DeepCopy() diff --git a/pkg/routes/const_other.go b/pkg/routes/const_other.go index fff6446e54ff0..458e506e81860 100644 --- a/pkg/routes/const_other.go +++ b/pkg/routes/const_other.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/scheduler/apis/config/scheme/scheme_test.go b/pkg/scheduler/apis/config/scheme/scheme_test.go index 2096bcd8ba371..4bd5bf171dffd 100644 --- a/pkg/scheduler/apis/config/scheme/scheme_test.go +++ b/pkg/scheduler/apis/config/scheme/scheme_test.go @@ -239,7 +239,10 @@ profiles: }, { Name: "DynamicResources", - Args: &config.DynamicResourcesArgs{FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}}, + Args: &config.DynamicResourcesArgs{ + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, + }, }, { Name: "InterPodAffinity", @@ -392,7 +395,10 @@ profiles: }, { Name: "DynamicResources", - Args: &config.DynamicResourcesArgs{FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}}, + Args: &config.DynamicResourcesArgs{ + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, + }, }, { Name: "InterPodAffinity", @@ -468,7 +474,10 @@ profiles: }, { Name: "DynamicResources", - Args: &config.DynamicResourcesArgs{FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}}, + Args: &config.DynamicResourcesArgs{ + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, + }, }, { Name: "NodeAffinity", diff --git a/pkg/scheduler/apis/config/testing/defaults/defaults.go b/pkg/scheduler/apis/config/testing/defaults/defaults.go index 237b7f2738b8f..c1c55e0702631 100644 --- a/pkg/scheduler/apis/config/testing/defaults/defaults.go +++ b/pkg/scheduler/apis/config/testing/defaults/defaults.go @@ -47,6 +47,7 @@ var PluginsV1 = &config.Plugins{ {Name: names.NodeResourcesBalancedAllocation, Weight: 1}, {Name: names.ImageLocality, Weight: 1}, {Name: names.DefaultBinder}, + {Name: names.NodeDeclaredFeatures}, }, }, } @@ -77,6 +78,7 @@ var ExpandedPluginsV1 = &config.Plugins{ {Name: names.PodTopologySpread}, {Name: names.InterPodAffinity}, {Name: names.DynamicResources}, + {Name: names.NodeDeclaredFeatures}, }, }, Filter: config.PluginSet{ @@ -94,6 +96,7 @@ var ExpandedPluginsV1 = &config.Plugins{ {Name: names.PodTopologySpread}, {Name: names.InterPodAffinity}, {Name: names.DynamicResources}, + {Name: names.NodeDeclaredFeatures}, }, }, PostFilter: config.PluginSet{ @@ -156,6 +159,11 @@ var ExpandedPluginsV1 = &config.Plugins{ {Name: names.DefaultBinder}, }, }, + PlacementScore: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: names.NodeResourcesFit, Weight: 1}, + }, + }, } // PluginConfigsV1 default plugin configurations. @@ -170,7 +178,8 @@ var PluginConfigsV1 = []config.PluginConfig{ { Name: "DynamicResources", Args: &config.DynamicResourcesArgs{ - FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, { diff --git a/pkg/scheduler/apis/config/types.go b/pkg/scheduler/apis/config/types.go index b0c94a18eef15..9c86ce264739c 100644 --- a/pkg/scheduler/apis/config/types.go +++ b/pkg/scheduler/apis/config/types.go @@ -176,6 +176,12 @@ type Plugins struct { // MultiPoint is a simplified config field for enabling plugins for all valid extension points MultiPoint PluginSet + + // PlacementGenerate is a list of plugins that should be invoked during pod group scheduling cycle when determining placements for a pod group. + PlacementGenerate PluginSet + + // PlacementScore is a list of plugins that should be invoked during workload scheduling cycle when ranking pod group assignments. + PlacementScore PluginSet } // PluginSet specifies enabled and disabled plugins for an extension point. @@ -189,11 +195,11 @@ type PluginSet struct { Disabled []Plugin } -// Plugin specifies a plugin name and its weight when applicable. Weight is used only for Score plugins. +// Plugin specifies a plugin name and its weight when applicable. Weight is used only for Score and PlacementScore plugins. type Plugin struct { // Name defines the name of plugin Name string - // Weight defines the weight of plugin, only used for Score plugins. + // Weight defines the weight of plugin, only used for Score and PlacementScore plugins. Weight int32 } @@ -244,6 +250,8 @@ func (p *Plugins) Names() []string { p.PostBind, p.Permit, p.QueueSort, + p.PlacementGenerate, + p.PlacementScore, } n := sets.New[string]() for _, e := range extensions { diff --git a/pkg/scheduler/apis/config/v1/default_plugins.go b/pkg/scheduler/apis/config/v1/default_plugins.go index 7920ee6f1d395..9475cdfd7b682 100644 --- a/pkg/scheduler/apis/config/v1/default_plugins.go +++ b/pkg/scheduler/apis/config/v1/default_plugins.go @@ -67,6 +67,9 @@ func applyFeatureGates(config *v1.Plugins) { if utilfeature.DefaultFeatureGate.Enabled(features.GangScheduling) { applyGangScheduling(config) } + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) { + applyTopologyAwareWorkloadScheduling(config) + } } func applyDynamicResources(config *v1.Plugins) { @@ -91,6 +94,11 @@ func applyGangScheduling(config *v1.Plugins) { config.MultiPoint.Enabled = append(config.MultiPoint.Enabled, v1.Plugin{Name: names.GangScheduling}) } +func applyTopologyAwareWorkloadScheduling(config *v1.Plugins) { + config.MultiPoint.Enabled = append(config.MultiPoint.Enabled, v1.Plugin{Name: names.TopologyPlacementGenerator}) + config.MultiPoint.Enabled = append(config.MultiPoint.Enabled, v1.Plugin{Name: names.PodGroupPodsCount, Weight: ptr.To[int32](1)}) +} + // mergePlugins merges the custom set into the given default one, handling disabled sets. func mergePlugins(logger klog.Logger, defaultPlugins, customPlugins *v1.Plugins) *v1.Plugins { if customPlugins == nil { @@ -110,6 +118,8 @@ func mergePlugins(logger klog.Logger, defaultPlugins, customPlugins *v1.Plugins) defaultPlugins.PreBind = mergePluginSet(logger, defaultPlugins.PreBind, customPlugins.PreBind) defaultPlugins.Bind = mergePluginSet(logger, defaultPlugins.Bind, customPlugins.Bind) defaultPlugins.PostBind = mergePluginSet(logger, defaultPlugins.PostBind, customPlugins.PostBind) + defaultPlugins.PlacementGenerate = mergePluginSet(logger, defaultPlugins.PlacementGenerate, customPlugins.PlacementGenerate) + defaultPlugins.PlacementScore = mergePluginSet(logger, defaultPlugins.PlacementScore, customPlugins.PlacementScore) return defaultPlugins } diff --git a/pkg/scheduler/apis/config/v1/default_plugins_test.go b/pkg/scheduler/apis/config/v1/default_plugins_test.go index 07deb47f71fe0..35b668fdfba72 100644 --- a/pkg/scheduler/apis/config/v1/default_plugins_test.go +++ b/pkg/scheduler/apis/config/v1/default_plugins_test.go @@ -63,6 +63,7 @@ func TestApplyFeatureGates(t *testing.T) { {Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)}, {Name: names.ImageLocality, Weight: ptr.To[int32](1)}, {Name: names.DefaultBinder}, + // NodeDeclaredFeatures not added as we set emmulation version in the test to 1.34 when DRA is disabled. }, }, }, @@ -94,6 +95,7 @@ func TestApplyFeatureGates(t *testing.T) { {Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)}, {Name: names.ImageLocality, Weight: ptr.To[int32](1)}, {Name: names.DefaultBinder}, + {Name: names.NodeDeclaredFeatures}, }, }, }, @@ -189,11 +191,47 @@ func TestApplyFeatureGates(t *testing.T) { {Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)}, {Name: names.ImageLocality, Weight: ptr.To[int32](1)}, {Name: names.DefaultBinder}, + {Name: names.NodeDeclaredFeatures}, {Name: names.GangScheduling}, }, }, }, }, + { + name: "Feature gate TopologyAwareWorkloadScheduling enabled", + features: map[featuregate.Feature]bool{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: true, + }, + wantConfig: &v1.Plugins{ + MultiPoint: v1.PluginSet{ + Enabled: []v1.Plugin{ + {Name: names.SchedulingGates}, + {Name: names.PrioritySort}, + {Name: names.NodeUnschedulable}, + {Name: names.NodeName}, + {Name: names.TaintToleration, Weight: ptr.To[int32](3)}, + {Name: names.NodeAffinity, Weight: ptr.To[int32](2)}, + {Name: names.NodePorts}, + {Name: names.NodeResourcesFit, Weight: ptr.To[int32](1)}, + {Name: names.VolumeRestrictions}, + {Name: names.NodeVolumeLimits}, + {Name: names.VolumeBinding}, + {Name: names.VolumeZone}, + {Name: names.PodTopologySpread, Weight: ptr.To[int32](2)}, + {Name: names.InterPodAffinity, Weight: ptr.To[int32](2)}, + {Name: names.DynamicResources, Weight: ptr.To[int32](2)}, + {Name: names.DefaultPreemption}, + {Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)}, + {Name: names.ImageLocality, Weight: ptr.To[int32](1)}, + {Name: names.DefaultBinder}, + {Name: names.NodeDeclaredFeatures}, + {Name: names.TopologyPlacementGenerator}, + {Name: names.PodGroupPodsCount, Weight: ptr.To[int32](1)}, + }, + }, + }, + }, } for _, test := range tests { diff --git a/pkg/scheduler/apis/config/v1/defaults.go b/pkg/scheduler/apis/config/v1/defaults.go index 225ce267d45f9..5ec3d720cf66a 100644 --- a/pkg/scheduler/apis/config/v1/defaults.go +++ b/pkg/scheduler/apis/config/v1/defaults.go @@ -57,6 +57,8 @@ func pluginsNames(p *configv1.Plugins) []string { p.Permit, p.PreEnqueue, p.QueueSort, + p.PlacementGenerate, + p.PlacementScore, } n := sets.New[string]() for _, e := range extensions { diff --git a/pkg/scheduler/apis/config/v1/defaults_test.go b/pkg/scheduler/apis/config/v1/defaults_test.go index 437467bfe0c19..45a39c83bcbbe 100644 --- a/pkg/scheduler/apis/config/v1/defaults_test.go +++ b/pkg/scheduler/apis/config/v1/defaults_test.go @@ -57,7 +57,8 @@ var pluginConfigs = []configv1.PluginConfig{ Kind: "DynamicResourcesArgs", APIVersion: "kubescheduler.config.k8s.io/v1", }, - FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }}, }, { @@ -279,7 +280,8 @@ func TestSchedulerDefaults(t *testing.T) { Kind: "DynamicResourcesArgs", APIVersion: "kubescheduler.config.k8s.io/v1", }, - FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + FilterTimeout: &metav1.Duration{Duration: 10 * time.Second}, + BindingTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }}, }, { @@ -371,6 +373,7 @@ func TestSchedulerDefaults(t *testing.T) { {Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)}, {Name: names.ImageLocality, Weight: ptr.To[int32](1)}, {Name: names.DefaultBinder}, + {Name: names.NodeDeclaredFeatures}, }, }, Bind: configv1.PluginSet{ diff --git a/pkg/scheduler/apis/config/v1/zz_generated.conversion.go b/pkg/scheduler/apis/config/v1/zz_generated.conversion.go index 63240b7d1125d..d4cf46a477010 100644 --- a/pkg/scheduler/apis/config/v1/zz_generated.conversion.go +++ b/pkg/scheduler/apis/config/v1/zz_generated.conversion.go @@ -785,6 +785,12 @@ func autoConvert_v1_Plugins_To_config_Plugins(in *configv1.Plugins, out *config. if err := Convert_v1_PluginSet_To_config_PluginSet(&in.MultiPoint, &out.MultiPoint, s); err != nil { return err } + if err := Convert_v1_PluginSet_To_config_PluginSet(&in.PlacementGenerate, &out.PlacementGenerate, s); err != nil { + return err + } + if err := Convert_v1_PluginSet_To_config_PluginSet(&in.PlacementScore, &out.PlacementScore, s); err != nil { + return err + } return nil } @@ -833,6 +839,12 @@ func autoConvert_config_Plugins_To_v1_Plugins(in *config.Plugins, out *configv1. if err := Convert_config_PluginSet_To_v1_PluginSet(&in.MultiPoint, &out.MultiPoint, s); err != nil { return err } + if err := Convert_config_PluginSet_To_v1_PluginSet(&in.PlacementGenerate, &out.PlacementGenerate, s); err != nil { + return err + } + if err := Convert_config_PluginSet_To_v1_PluginSet(&in.PlacementScore, &out.PlacementScore, s); err != nil { + return err + } return nil } diff --git a/pkg/scheduler/apis/config/validation/validation_pluginargs.go b/pkg/scheduler/apis/config/validation/validation_pluginargs.go index d50881630d614..ec19f18806beb 100644 --- a/pkg/scheduler/apis/config/validation/validation_pluginargs.go +++ b/pkg/scheduler/apis/config/validation/validation_pluginargs.go @@ -319,9 +319,17 @@ func ValidateNodeResourcesFitArgs(path *field.Path, args *config.NodeResourcesFi allErrs = append(allErrs, field.NotSupported(strategyPath.Child("type"), args.ScoringStrategy.Type, sets.List(supportedScoringStrategyTypes))) } allErrs = append(allErrs, validateResources(args.ScoringStrategy.Resources, strategyPath.Child("resources"))...) - if args.ScoringStrategy.RequestedToCapacityRatio != nil { - allErrs = append(allErrs, validateFunctionShape(args.ScoringStrategy.RequestedToCapacityRatio.Shape, strategyPath.Child("shape"))...) + if args.ScoringStrategy.Type == config.RequestedToCapacityRatio { + if args.ScoringStrategy.RequestedToCapacityRatio == nil { + allErrs = append(allErrs, field.Required(strategyPath.Child("requestedToCapacityRatio"), "must be specified when type is RequestedToCapacityRatio")) + } else { + allErrs = append(allErrs, validateFunctionShape(args.ScoringStrategy.RequestedToCapacityRatio.Shape, strategyPath.Child("requestedToCapacityRatio").Child("shape"))...) + } + } else if args.ScoringStrategy.RequestedToCapacityRatio != nil { + allErrs = append(allErrs, field.Forbidden(strategyPath.Child("requestedToCapacityRatio"), "must be nil when type is not RequestedToCapacityRatio")) } + } else { + allErrs = append(allErrs, field.Required(strategyPath, "ScoringStrategy field is required")) } if len(allErrs) == 0 { diff --git a/pkg/scheduler/apis/config/validation/validation_pluginargs_test.go b/pkg/scheduler/apis/config/validation/validation_pluginargs_test.go index aec89d5a4a6fd..214338681c361 100644 --- a/pkg/scheduler/apis/config/validation/validation_pluginargs_test.go +++ b/pkg/scheduler/apis/config/validation/validation_pluginargs_test.go @@ -754,12 +754,28 @@ func TestValidateFitArgs(t *testing.T) { }, expect: `Unsupported value: "Invalid"`, }, + { + name: "ScoringStrategy: requestedToCapacityRatio field is missing", + args: config.NodeResourcesFitArgs{ + ScoringStrategy: &config.ScoringStrategy{ + Type: config.RequestedToCapacityRatio, + }, + }, + expect: "must be specified when type is RequestedToCapacityRatio", + }, } for _, test := range argsTest { t.Run(test.name, func(t *testing.T) { - if err := ValidateNodeResourcesFitArgs(nil, &test.args); err != nil && (!strings.Contains(err.Error(), test.expect)) { - t.Errorf("case[%v]: error details do not include %v", test.name, err) + err := ValidateNodeResourcesFitArgs(nil, &test.args) + if err != nil { + if test.expect == "" { + t.Errorf("case[%v]: unexpected validation error %v", test.name, err) + } else if !strings.Contains(err.Error(), test.expect) { + t.Errorf("case[%v]: error details do not include %v", test.name, err) + } + } else if test.expect != "" { + t.Errorf("case[%v]: expected validation error", test.name) } }) } @@ -974,7 +990,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeRequired, - Field: "scoringStrategy.shape", + Field: "scoringStrategy.requestedToCapacityRatio.shape", }, }, }, @@ -1026,7 +1042,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "scoringStrategy.shape[0].utilization", + Field: "scoringStrategy.requestedToCapacityRatio.shape[0].utilization", }, }, }, @@ -1041,7 +1057,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "scoringStrategy.shape[0].utilization", + Field: "scoringStrategy.requestedToCapacityRatio.shape[0].utilization", }, }, }, @@ -1060,7 +1076,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "scoringStrategy.shape[1].utilization", + Field: "scoringStrategy.requestedToCapacityRatio.shape[1].utilization", }, }, }, @@ -1101,7 +1117,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "scoringStrategy.shape[2].utilization", + Field: "scoringStrategy.requestedToCapacityRatio.shape[2].utilization", }, }, }, @@ -1116,7 +1132,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "scoringStrategy.shape[0].score", + Field: "scoringStrategy.requestedToCapacityRatio.shape[0].score", }, }, }, @@ -1131,7 +1147,7 @@ func TestValidateRequestedToCapacityRatioScoringStrategy(t *testing.T) { wantErrs: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "scoringStrategy.shape[0].score", + Field: "scoringStrategy.requestedToCapacityRatio.shape[0].score", }, }, }, diff --git a/pkg/scheduler/apis/config/zz_generated.deepcopy.go b/pkg/scheduler/apis/config/zz_generated.deepcopy.go index 2d78d58e58749..ad5699db2ea7a 100644 --- a/pkg/scheduler/apis/config/zz_generated.deepcopy.go +++ b/pkg/scheduler/apis/config/zz_generated.deepcopy.go @@ -443,6 +443,8 @@ func (in *Plugins) DeepCopyInto(out *Plugins) { in.Bind.DeepCopyInto(&out.Bind) in.PostBind.DeepCopyInto(&out.PostBind) in.MultiPoint.DeepCopyInto(&out.MultiPoint) + in.PlacementGenerate.DeepCopyInto(&out.PlacementGenerate) + in.PlacementScore.DeepCopyInto(&out.PlacementScore) return } diff --git a/pkg/scheduler/backend/cache/cache.go b/pkg/scheduler/backend/cache/cache.go index a55f8440d67f7..3a6cd16b02bd6 100644 --- a/pkg/scheduler/backend/cache/cache.go +++ b/pkg/scheduler/backend/cache/cache.go @@ -30,22 +30,20 @@ import ( "k8s.io/klog/v2" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework" - "k8s.io/kubernetes/pkg/scheduler/framework/api_calls" + apicalls "k8s.io/kubernetes/pkg/scheduler/framework/api_calls" "k8s.io/kubernetes/pkg/scheduler/metrics" ) var ( - cleanAssumedPeriod = 1 * time.Second + updateMetricsPeriod = 1 * time.Second ) // New returns a Cache implementation. -// It automatically starts a go routine that manages expiration of assumed pods. -// "ttl" is how long the assumed pod will get expired. +// It automatically starts a go routine that exports cache metrics. // "ctx" is the context that would close the background goroutine. -func New(ctx context.Context, ttl time.Duration, apiDispatcher fwk.APIDispatcher) Cache { - logger := klog.FromContext(ctx) - cache := newCache(ctx, ttl, cleanAssumedPeriod, apiDispatcher) - cache.run(logger) +func New(ctx context.Context, apiDispatcher fwk.APIDispatcher, genericWorkloadEnabled bool) Cache { + cache := newCache(ctx, updateMetricsPeriod, apiDispatcher, genericWorkloadEnabled) + cache.run() return cache } @@ -60,7 +58,6 @@ type nodeInfoListItem struct { type cacheImpl struct { stop <-chan struct{} - ttl time.Duration period time.Duration // This mutex guards all fields within this cache struct. @@ -77,7 +74,10 @@ type cacheImpl struct { nodeTree *nodeTree // A map from image name to its ImageStateSummary. imageStates map[string]*fwk.ImageStateSummary - + // podGroupStates stores the runtime state for each known pod group (only if GenericWorkload feature gate is enabled). + podGroupStates map[podGroupKey]*podGroupState + // genericWorkloadEnabled stores the GenericWorkload feature gate value. + genericWorkloadEnabled bool // apiDispatcher is used for the methods that are expected to send API calls. // It's non-nil only if the SchedulerAsyncAPICalls feature gate is enabled. apiDispatcher fwk.APIDispatcher @@ -85,26 +85,22 @@ type cacheImpl struct { type podState struct { pod *v1.Pod - // Used by assumedPod to determinate expiration. - // If deadline is nil, assumedPod will never expire. - deadline *time.Time - // Used to block cache from expiring assumedPod if binding still runs - bindingFinished bool } -func newCache(ctx context.Context, ttl, period time.Duration, apiDispatcher fwk.APIDispatcher) *cacheImpl { +func newCache(ctx context.Context, period time.Duration, apiDispatcher fwk.APIDispatcher, genericWorkloadEnabled bool) *cacheImpl { logger := klog.FromContext(ctx) return &cacheImpl{ - ttl: ttl, period: period, stop: ctx.Done(), - nodes: make(map[string]*nodeInfoListItem), - nodeTree: newNodeTree(logger, nil), - assumedPods: sets.New[string](), - podStates: make(map[string]*podState), - imageStates: make(map[string]*fwk.ImageStateSummary), - apiDispatcher: apiDispatcher, + nodes: make(map[string]*nodeInfoListItem), + nodeTree: newNodeTree(logger, nil), + assumedPods: sets.New[string](), + podStates: make(map[string]*podState), + imageStates: make(map[string]*fwk.ImageStateSummary), + podGroupStates: make(map[podGroupKey]*podGroupState), + genericWorkloadEnabled: genericWorkloadEnabled, + apiDispatcher: apiDispatcher, } } @@ -195,6 +191,11 @@ func (cache *cacheImpl) UpdateSnapshot(logger klog.Logger, nodeSnapshot *Snapsho cache.mu.Lock() defer cache.mu.Unlock() + if nodeSnapshot.placementNodes != nil { + logger.Error(nil, "UpdateSnapshot called with assumed placement. This is unexpected. Placement will be cleared.") + nodeSnapshot.ForgetPlacement() + } + // Get the last generation of the snapshot. snapshotGeneration := nodeSnapshot.generation @@ -213,6 +214,10 @@ func (cache *cacheImpl) UpdateSnapshot(logger klog.Logger, nodeSnapshot *Snapsho // last snapshot generation. updateUsedPVCSet := false + // Forget all assumed pods from a previous snapshot version. + // This is a safety check in case any pod wasn't forgotten in the previous scheduling cycle. + nodeSnapshot.forgetAllAssumedPods(logger) + // Start from the head of the NodeInfo doubly linked list and update snapshot // of NodeInfos updated after the last snapshot. for node := cache.headNode; node != nil; node = node.next { @@ -284,9 +289,32 @@ func (cache *cacheImpl) UpdateSnapshot(logger klog.Logger, nodeSnapshot *Snapsho return errors.New(errMsg) } + // Take a snapshot of pod group states for this scheduling cycle. + cache.updatePodGroupStateSnapshot(nodeSnapshot) + return nil } +// updatePodGroupStateSnapshot updates the pod group state portion of the given snapshot. +// It assumes that the cache lock is already held. +// It removes entries that no longer exist in the live cache +// and clones entries whose generation has advanced since the last snapshot. +func (cache *cacheImpl) updatePodGroupStateSnapshot(snapshot *Snapshot) { + // Remove pod group states from snapshot that no longer exist in cache. + for key := range snapshot.podGroupStates { + if _, exists := cache.podGroupStates[key]; !exists { + delete(snapshot.podGroupStates, key) + } + } + // Clone only pod group states that changed since the last snapshot. + for key, podGroupState := range cache.podGroupStates { + if existing, ok := snapshot.podGroupStates[key]; ok && existing.generation == podGroupState.generation { + continue + } + snapshot.podGroupStates[key] = podGroupState.snapshot() + } +} + func (cache *cacheImpl) updateNodeInfoSnapshotList(logger klog.Logger, snapshot *Snapshot, updateAll bool) { snapshot.havePodsWithAffinityNodeInfoList = make([]fwk.NodeInfo, 0, cache.nodeTree.numNodes) snapshot.havePodsWithRequiredAntiAffinityNodeInfoList = make([]fwk.NodeInfo, 0, cache.nodeTree.numNodes) @@ -381,34 +409,6 @@ func (cache *cacheImpl) AssumePod(logger klog.Logger, pod *v1.Pod) error { return cache.addPod(logger, pod, true) } -func (cache *cacheImpl) FinishBinding(logger klog.Logger, pod *v1.Pod) error { - return cache.finishBinding(logger, pod, time.Now()) -} - -// finishBinding exists to make tests deterministic by injecting now as an argument -func (cache *cacheImpl) finishBinding(logger klog.Logger, pod *v1.Pod, now time.Time) error { - key, err := framework.GetPodKey(pod) - if err != nil { - return err - } - - cache.mu.RLock() - defer cache.mu.RUnlock() - - logger.V(5).Info("Finished binding for pod, can be expired", "podKey", key, "pod", klog.KObj(pod)) - currState, ok := cache.podStates[key] - if ok && cache.assumedPods.Has(key) { - if cache.ttl == time.Duration(0) { - currState.deadline = nil - } else { - dl := now.Add(cache.ttl) - currState.deadline = &dl - } - currState.bindingFinished = true - } - return nil -} - func (cache *cacheImpl) ForgetPod(logger klog.Logger, pod *v1.Pod) error { key, err := framework.GetPodKey(pod) if err != nil { @@ -428,7 +428,7 @@ func (cache *cacheImpl) ForgetPod(logger klog.Logger, pod *v1.Pod) error { } // Only assumed pod can be forgotten. if cache.assumedPods.Has(key) { - return cache.removePod(logger, pod) + return cache.removePod(logger, pod, true) } return fmt.Errorf("pod %v(%v) wasn't assumed so cannot be forgotten", key, klog.KObj(pod)) } @@ -453,12 +453,21 @@ func (cache *cacheImpl) addPod(logger klog.Logger, pod *v1.Pod, assumePod bool) if assumePod { cache.assumedPods.Insert(key) } + + if !cache.isPodGroupMember(pod) { + return nil + } + if assumePod { + cache.assumePodGroupMember(pod) + } else { + cache.addPodGroupMember(pod) + } return nil } // Assumes that lock is already acquired. func (cache *cacheImpl) updatePod(logger klog.Logger, oldPod, newPod *v1.Pod) error { - if err := cache.removePod(logger, oldPod); err != nil { + if err := cache.removePod(logger, oldPod, false); err != nil { return err } return cache.addPod(logger, newPod, false) @@ -468,7 +477,7 @@ func (cache *cacheImpl) updatePod(logger klog.Logger, oldPod, newPod *v1.Pod) er // Removes a pod from the cached node info. If the node information was already // removed and there are no more pods left in the node, cleans up the node from // the cache. -func (cache *cacheImpl) removePod(logger klog.Logger, pod *v1.Pod) error { +func (cache *cacheImpl) removePod(logger klog.Logger, pod *v1.Pod, forgetPod bool) error { key, err := framework.GetPodKey(pod) if err != nil { return err @@ -490,6 +499,16 @@ func (cache *cacheImpl) removePod(logger klog.Logger, pod *v1.Pod) error { delete(cache.podStates, key) delete(cache.assumedPods, key) + + if !cache.isPodGroupMember(pod) { + return nil + } + if forgetPod { + cache.forgetPodGroupMember(logger, pod) + } else { + cache.removePodGroupMember(pod) + } + return nil } @@ -516,7 +535,6 @@ func (cache *cacheImpl) AddPod(logger klog.Logger, pod *v1.Pod) error { return nil } case !ok: - // Pod was expired. We should add it back. if err = cache.addPod(logger, pod, false); err != nil { utilruntime.HandleErrorWithLogger(logger, err, "Error occurred while adding pod") } @@ -575,7 +593,7 @@ func (cache *cacheImpl) RemovePod(logger klog.Logger, pod *v1.Pod) error { klog.FlushAndExit(klog.ExitFlushTimeout, 1) } } - return cache.removePod(logger, currState.pod) + return cache.removePod(logger, currState.pod, false) } func (cache *cacheImpl) IsAssumedPod(pod *v1.Pod) (bool, error) { @@ -678,6 +696,19 @@ func (cache *cacheImpl) RemoveNode(logger klog.Logger, node *v1.Node) error { return nil } +// GetNode returns the copy of node stored in the cache. +// DO NOT use outside of tests. +func (cache *cacheImpl) GetNode(name string) (*framework.NodeInfo, error) { + cache.mu.RLock() + defer cache.mu.RUnlock() + + n, ok := cache.nodes[name] + if !ok { + return nil, fmt.Errorf("node %v does not exist in scheduler cache", name) + } + return n.info.SnapshotConcrete(), nil +} + // addNodeImageStates adds states of the images on given node to the given nodeInfo and update the imageStates in // scheduler cache. This function assumes the lock to scheduler cache has been acquired. func (cache *cacheImpl) addNodeImageStates(node *v1.Node, nodeInfo *framework.NodeInfo) { @@ -729,44 +760,142 @@ func (cache *cacheImpl) removeNodeImageStates(node *v1.Node) { } } -func (cache *cacheImpl) run(logger klog.Logger) { - go wait.Until(func() { - cache.cleanupAssumedPods(logger, time.Now()) - }, cache.period, cache.stop) +func (cache *cacheImpl) run() { + go wait.Until(cache.updateMetrics, cache.period, cache.stop) +} + +// updateMetrics updates cache size metric values for pods, assumed pods, and nodes +func (cache *cacheImpl) updateMetrics() { + cache.mu.RLock() + defer cache.mu.RUnlock() + + metrics.CacheSize.WithLabelValues("assumed_pods").Set(float64(len(cache.assumedPods))) + metrics.CacheSize.WithLabelValues("pods").Set(float64(len(cache.podStates))) + metrics.CacheSize.WithLabelValues("nodes").Set(float64(len(cache.nodes))) +} + +// isPodGroupMember returns true if the pod belongs to a pod group, +// provided that GenericWorkload feature gate is enabled. +func (cache *cacheImpl) isPodGroupMember(pod *v1.Pod) bool { + return cache.genericWorkloadEnabled && pod.Spec.SchedulingGroup != nil +} + +// AddPodGroupMember adds not assigned and not assumed pod to its pod group state in the cache. +func (cache *cacheImpl) AddPodGroupMember(pod *v1.Pod) { + if !cache.isPodGroupMember(pod) { + return + } + cache.mu.Lock() + defer cache.mu.Unlock() + + cache.addPodGroupMember(pod) +} + +// UpdatePodGroupMember updates a pod's entry inside its pod group state in the cache. +func (cache *cacheImpl) UpdatePodGroupMember(logger klog.Logger, oldPod, newPod *v1.Pod) { + if !cache.isPodGroupMember(newPod) { + return + } + cache.mu.Lock() + defer cache.mu.Unlock() + + cache.updatePodGroupMember(logger, oldPod, newPod) } -// cleanupAssumedPods exists for making test deterministic by taking time as input argument. -// It also reports metrics on the cache size for nodes, pods, and assumed pods. -func (cache *cacheImpl) cleanupAssumedPods(logger klog.Logger, now time.Time) { +// RemovePodGroupMember removes the pod from its pod group state in the cache. +func (cache *cacheImpl) RemovePodGroupMember(pod *v1.Pod) { + if !cache.isPodGroupMember(pod) { + return + } cache.mu.Lock() defer cache.mu.Unlock() - defer cache.updateMetrics() - // The size of assumedPods should be small - for key := range cache.assumedPods { - ps, ok := cache.podStates[key] - if !ok { - utilruntime.HandleErrorWithLogger(logger, nil, "Key found in assumed set but not in podStates, potentially a logical error") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - if !ps.bindingFinished { - logger.V(5).Info("Could not expire cache for pod as binding is still in progress", "podKey", key, "pod", klog.KObj(ps.pod)) - continue - } - if cache.ttl != 0 && now.After(*ps.deadline) { - logger.Info("Pod expired", "podKey", key, "pod", klog.KObj(ps.pod)) - if err := cache.removePod(logger, ps.pod); err != nil { - utilruntime.HandleErrorWithLogger(logger, err, "ExpirePod failed", "podKey", key, "pod", klog.KObj(ps.pod)) - } - } + cache.removePodGroupMember(pod) +} + +// addPodGroupMember adds the pod to its pod group state, creating the group entry if it doesn't exist yet. +// Assumes that the cache lock is already held. +func (cache *cacheImpl) addPodGroupMember(pod *v1.Pod) { + key := newPodGroupKey(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + podGroupState, exists := cache.podGroupStates[key] + if !exists { + podGroupState = newPodGroupState() + cache.podGroupStates[key] = podGroupState + } + + podGroupState.addPod(pod) +} + +// updatePodGroupMember updates the pod entry inside its pod group state. +// Assumes that the cache lock is already held. +func (cache *cacheImpl) updatePodGroupMember(logger klog.Logger, oldPod, newPod *v1.Pod) { + key := newPodGroupKey(newPod.Namespace, *newPod.Spec.SchedulingGroup.PodGroupName) + podGroupState, exists := cache.podGroupStates[key] + if !exists { + // This should not happen: the pod group state should have been already created by a prior pod add action. + utilruntime.HandleErrorWithLogger(logger, nil, "Pod group state not found for update, this indicates a missed add event", "pod", klog.KObj(newPod), "podGroupKey", key) + return } + + podGroupState.updatePod(oldPod, newPod) } -// updateMetrics updates cache size metric values for pods, assumed pods, and nodes -func (cache *cacheImpl) updateMetrics() { - metrics.CacheSize.WithLabelValues("assumed_pods").Set(float64(len(cache.assumedPods))) - metrics.CacheSize.WithLabelValues("pods").Set(float64(len(cache.podStates))) - metrics.CacheSize.WithLabelValues("nodes").Set(float64(len(cache.nodes))) +// removePodGroupMember removes the pod from its pod group state, deleting the group entry when empty. +// Assumes that the cache lock is already held. +func (cache *cacheImpl) removePodGroupMember(pod *v1.Pod) { + key := newPodGroupKey(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + podGroupState, exists := cache.podGroupStates[key] + if !exists { + return + } + podGroupState.deletePod(pod.UID) + if podGroupState.empty() { + delete(cache.podGroupStates, key) + } +} + +// assumePodGroupMember marks the pod as assumed in its pod group state. +// Assumes that the cache lock is already held. +func (cache *cacheImpl) assumePodGroupMember(pod *v1.Pod) { + key := newPodGroupKey(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + podGroupState, exists := cache.podGroupStates[key] + if !exists { + podGroupState = newPodGroupState() + podGroupState.allPods[pod.UID] = pod + cache.podGroupStates[key] = podGroupState + } + podGroupState.assumePod(pod) +} + +// forgetPodGroupMember moves the pod back from assumed to unscheduled in its pod group state. +// Assumes that the cache lock is already held. +func (cache *cacheImpl) forgetPodGroupMember(logger klog.Logger, pod *v1.Pod) { + key := newPodGroupKey(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + pgs, exists := cache.podGroupStates[key] + if !exists { + // This should not happen: the pod group state should have been already created by a prior pod add or assume action. + utilruntime.HandleErrorWithLogger(logger, nil, "Pod group state not found for forget, this indicates a missed add or assume event", "pod", klog.KObj(pod), "podGroupKey", key) + return + } + pgs.forgetPod(pod.UID) +} + +// PodGroupStates returns the PodGroupStateLister for this cache. +func (cache *cacheImpl) PodGroupStates() fwk.PodGroupStateLister { + return cache +} + +// Get returns the pod group state for the given pod group. +func (cache *cacheImpl) Get(namespace string, podGroupName string) (fwk.PodGroupState, error) { + cache.mu.RLock() + defer cache.mu.RUnlock() + + key := newPodGroupKey(namespace, podGroupName) + podGroupState, exists := cache.podGroupStates[key] + if !exists { + return nil, fmt.Errorf("pod group state not found for pod group %s", key) + } + return podGroupState, nil } // BindPod handles the pod binding by adding a bind API call to the dispatcher. diff --git a/pkg/scheduler/backend/cache/cache_test.go b/pkg/scheduler/backend/cache/cache_test.go index 925ee9c759439..0a855eafe0da2 100644 --- a/pkg/scheduler/backend/cache/cache_test.go +++ b/pkg/scheduler/backend/cache/cache_test.go @@ -47,6 +47,12 @@ var nodeInfoCmpOpts = []cmp.Option{ cmpopts.IgnoreFields(framework.PodInfo{}, "cachedResource"), } +var podGroupStateCmpOpts = []cmp.Option{ + cmp.AllowUnexported(podGroupStateSnapshot{}, podGroupStateData{}, podGroupKey{}), + cmpopts.IgnoreFields(podGroupStateData{}, "generation"), + cmpopts.EquateEmpty(), +} + func init() { metrics.Register() } @@ -235,7 +241,7 @@ func TestAssumePodScheduled(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, time.Second, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, pod := range tc.pods { if err := cache.AssumePod(logger, pod); err != nil { t.Fatalf("AssumePod failed: %v", err) @@ -262,128 +268,9 @@ func TestAssumePodScheduled(t *testing.T) { } } -type testExpirePodStruct struct { - pod *v1.Pod - finishBind bool - assumedTime time.Time -} - -func assumeAndFinishBinding(logger klog.Logger, cache *cacheImpl, pod *v1.Pod, assumedTime time.Time) error { - if err := cache.AssumePod(logger, pod); err != nil { - return err - } - return cache.finishBinding(logger, pod, assumedTime) -} - -// TestExpirePod tests that assumed pods will be removed if expired. -// The removal will be reflected in node info. -func TestExpirePod(t *testing.T) { - nodeName := "node" - testPods := []*v1.Pod{ - makeBasePod(t, nodeName, "test-1", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), - makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), - makeBasePod(t, nodeName, "test-3", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), - } - now := time.Now() - defaultTTL := 10 * time.Second - tests := []struct { - name string - pods []*testExpirePodStruct - cleanupTime time.Time - ttl time.Duration - wNodeInfo *framework.NodeInfo - }{ - { - name: "assumed pod would expire", - pods: []*testExpirePodStruct{ - {pod: testPods[0], finishBind: true, assumedTime: now}, - }, - cleanupTime: now.Add(2 * defaultTTL), - wNodeInfo: nil, - ttl: defaultTTL, - }, - { - name: "first one would expire, second and third would not", - pods: []*testExpirePodStruct{ - {pod: testPods[0], finishBind: true, assumedTime: now}, - {pod: testPods[1], finishBind: true, assumedTime: now.Add(3 * defaultTTL / 2)}, - {pod: testPods[2]}, - }, - cleanupTime: now.Add(2 * defaultTTL), - wNodeInfo: newNodeInfo( - &framework.Resource{ - MilliCPU: 400, - Memory: 2048, - }, - &framework.Resource{ - MilliCPU: 400, - Memory: 2048, - }, - // Order gets altered when removing pods. - []*v1.Pod{testPods[2], testPods[1]}, - newHostPortInfoBuilder().add("TCP", "127.0.0.1", 8080).build(), - make(map[string]*fwk.ImageStateSummary), - ), - ttl: defaultTTL, - }, - { - name: "assumed pod would never expire", - pods: []*testExpirePodStruct{ - {pod: testPods[0], finishBind: true, assumedTime: now}, - }, - cleanupTime: now.Add(3 * defaultTTL), - wNodeInfo: newNodeInfo( - &framework.Resource{ - MilliCPU: 100, - Memory: 500, - }, - &framework.Resource{ - MilliCPU: 100, - Memory: 500, - }, - []*v1.Pod{testPods[0]}, - newHostPortInfoBuilder().add("TCP", "127.0.0.1", 80).build(), - make(map[string]*fwk.ImageStateSummary), - ), - ttl: time.Duration(0), - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - cache := newCache(ctx, tc.ttl, time.Second, nil) - - for _, pod := range tc.pods { - if err := cache.AssumePod(logger, pod.pod); err != nil { - t.Fatal(err) - } - if !pod.finishBind { - continue - } - if err := cache.finishBinding(logger, pod.pod, pod.assumedTime); err != nil { - t.Fatal(err) - } - } - // pods that got bound and have assumedTime + ttl < cleanupTime will get - // expired and removed - cache.cleanupAssumedPods(logger, tc.cleanupTime) - n := cache.nodes[nodeName] - if err := deepEqualWithoutGeneration(n, tc.wNodeInfo); err != nil { - t.Error(err) - } - }) - } -} - // TestAddPodWillConfirm tests that a pod being Add()ed will be confirmed if assumed. -// The pod info should still exist after manually expiring unconfirmed pods. func TestAddPodWillConfirm(t *testing.T) { nodeName := "node" - now := time.Now() - ttl := 10 * time.Second testPods := []*v1.Pod{ makeBasePod(t, nodeName, "test-1", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), @@ -415,9 +302,9 @@ func TestAddPodWillConfirm(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, podToAssume := range test.podsToAssume { - if err := assumeAndFinishBinding(logger, cache, podToAssume, now); err != nil { + if err := cache.AssumePod(logger, podToAssume); err != nil { t.Fatalf("assumePod failed: %v", err) } } @@ -430,8 +317,20 @@ func TestAddPodWillConfirm(t *testing.T) { t.Error("expected error, no error found") } } - cache.cleanupAssumedPods(logger, now.Add(2*ttl)) - // check after expiration. confirmed pods shouldn't be expired. + + for _, podToAssume := range test.podsToAssume { + assumed, err := cache.IsAssumedPod(podToAssume) + if err != nil { + t.Fatalf("IsAssumedPod failed: %v", err) + } + if !assumed { + continue + } + if err = cache.ForgetPod(logger, podToAssume); err != nil { + t.Fatalf("ForgetPod failed: %v", err) + } + } + n := cache.nodes[nodeName] if err := deepEqualWithoutGeneration(n, test.wNodeInfo); err != nil { t.Error(err) @@ -440,8 +339,6 @@ func TestAddPodWillConfirm(t *testing.T) { func TestDump(t *testing.T) { nodeName := "node" - now := time.Now() - ttl := 10 * time.Second testPods := []*v1.Pod{ makeBasePod(t, nodeName, "test-1", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), @@ -458,9 +355,9 @@ func TestDump(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, podToAssume := range test.podsToAssume { - if err := assumeAndFinishBinding(logger, cache, podToAssume, now); err != nil { + if err := cache.AssumePod(logger, podToAssume); err != nil { t.Errorf("assumePod failed: %v", err) } } @@ -489,11 +386,9 @@ func TestDump(t *testing.T) { // TestAddPodAlwaysUpdatePodInfoInNodeInfo tests that AddPod method always updates PodInfo in NodeInfo, // even when the Pod is assumed one. func TestAddPodAlwaysUpdatesPodInfoInNodeInfo(t *testing.T) { - ttl := 10 * time.Second logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - now := time.Now() p1 := makeBasePod(t, "node1", "test-1", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}) p2 := p1.DeepCopy() @@ -526,9 +421,9 @@ func TestAddPodAlwaysUpdatesPodInfoInNodeInfo(t *testing.T) { }, } - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, podToAssume := range test.podsToAssume { - if err := assumeAndFinishBinding(logger, cache, podToAssume, now); err != nil { + if err := cache.AssumePod(logger, podToAssume); err != nil { t.Fatalf("assumePod failed: %v", err) } } @@ -547,9 +442,6 @@ func TestAddPodAlwaysUpdatesPodInfoInNodeInfo(t *testing.T) { // TestAddPodWillReplaceAssumed tests that a pod being Add()ed will replace any assumed pod. func TestAddPodWillReplaceAssumed(t *testing.T) { - now := time.Now() - ttl := 10 * time.Second - assumedPod := makeBasePod(t, "assumed-node-1", "test-1", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}) addedPod := makeBasePod(t, "actual-node", "test-1", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}) updatedPod := makeBasePod(t, "actual-node", "test-1", "200m", "500", "", []v1.ContainerPort{{HostPort: 90}}) @@ -585,9 +477,9 @@ func TestAddPodWillReplaceAssumed(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, podToAssume := range test.podsToAssume { - if err := assumeAndFinishBinding(logger, cache, podToAssume, now); err != nil { + if err := cache.AssumePod(logger, podToAssume); err != nil { t.Fatalf("assumePod failed: %v", err) } } @@ -609,58 +501,9 @@ func TestAddPodWillReplaceAssumed(t *testing.T) { } } -// TestAddPodAfterExpiration tests that a pod being Add()ed will be added back if expired. -func TestAddPodAfterExpiration(t *testing.T) { - nodeName := "node" - ttl := 10 * time.Second - basePod := makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}) - test := struct { - pod *v1.Pod - wNodeInfo *framework.NodeInfo - }{ - pod: basePod, - wNodeInfo: newNodeInfo( - &framework.Resource{ - MilliCPU: 100, - Memory: 500, - }, - &framework.Resource{ - MilliCPU: 100, - Memory: 500, - }, - []*v1.Pod{basePod}, - newHostPortInfoBuilder().add("TCP", "127.0.0.1", 80).build(), - make(map[string]*fwk.ImageStateSummary), - ), - } - - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - now := time.Now() - cache := newCache(ctx, ttl, time.Second, nil) - if err := assumeAndFinishBinding(logger, cache, test.pod, now); err != nil { - t.Fatalf("assumePod failed: %v", err) - } - cache.cleanupAssumedPods(logger, now.Add(2*ttl)) - // It should be expired and removed. - if err := isForgottenFromCache(test.pod, cache); err != nil { - t.Error(err) - } - if err := cache.AddPod(logger, test.pod); err != nil { - t.Fatalf("AddPod failed: %v", err) - } - // check after expiration. confirmed pods shouldn't be expired. - n := cache.nodes[nodeName] - if err := deepEqualWithoutGeneration(n, test.wNodeInfo); err != nil { - t.Error(err) - } -} - // TestUpdatePod tests that a pod will be updated if added before. func TestUpdatePod(t *testing.T) { nodeName := "node" - ttl := 10 * time.Second testPods := []*v1.Pod{ makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), makeBasePod(t, nodeName, "test", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), @@ -703,7 +546,7 @@ func TestUpdatePod(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, podToAdd := range test.podsToAdd { if err := cache.AddPod(logger, podToAdd); err != nil { t.Fatalf("AddPod failed: %v", err) @@ -717,7 +560,6 @@ func TestUpdatePod(t *testing.T) { if err := cache.UpdatePod(logger, test.podsToUpdate[j-1], test.podsToUpdate[j]); err != nil { t.Fatalf("UpdatePod failed: %v", err) } - // check after expiration. confirmed pods shouldn't be expired. n := cache.nodes[nodeName] if err := deepEqualWithoutGeneration(n, test.wNodeInfo[j-1]); err != nil { t.Errorf("update %d: %v", j, err) @@ -728,7 +570,6 @@ func TestUpdatePod(t *testing.T) { // TestUpdatePodAndGet tests get always return latest pod state func TestUpdatePodAndGet(t *testing.T) { nodeName := "node" - ttl := 10 * time.Second testPods := []*v1.Pod{ makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), makeBasePod(t, nodeName, "test", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), @@ -765,7 +606,7 @@ func TestUpdatePodAndGet(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) // trying to get an unknown pod should return an error // podToUpdate has not been added yet if _, err := cache.GetPod(tc.podToUpdate); err == nil { @@ -799,130 +640,436 @@ func TestUpdatePodAndGet(t *testing.T) { } } -// TestExpireAddUpdatePod test the sequence that a pod is expired, added, then updated -func TestExpireAddUpdatePod(t *testing.T) { +func makePodWithEphemeralStorage(nodeName, ephemeralStorage string) *v1.Pod { + return st.MakePod().Name("pod-with-ephemeral-storage").Namespace("default-namespace").UID("pod-with-ephemeral-storage").Req( + map[v1.ResourceName]string{ + v1.ResourceEphemeralStorage: ephemeralStorage, + }, + ).Node(nodeName).Obj() +} + +func TestEphemeralStorageResource(t *testing.T) { nodeName := "node" - ttl := 10 * time.Second - testPods := []*v1.Pod{ - makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), - makeBasePod(t, nodeName, "test", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), - } + podE := makePodWithEphemeralStorage(nodeName, "500") test := struct { - podsToAssume []*v1.Pod - podsToAdd []*v1.Pod - podsToUpdate []*v1.Pod - - wNodeInfo []*framework.NodeInfo - }{ // Pod is assumed, expired, and added. Then it would be updated twice. - podsToAssume: []*v1.Pod{testPods[0]}, - podsToAdd: []*v1.Pod{testPods[0]}, - podsToUpdate: []*v1.Pod{testPods[0], testPods[1], testPods[0]}, - wNodeInfo: []*framework.NodeInfo{newNodeInfo( - &framework.Resource{ - MilliCPU: 200, - Memory: 1024, - }, - &framework.Resource{ - MilliCPU: 200, - Memory: 1024, - }, - []*v1.Pod{testPods[1]}, - newHostPortInfoBuilder().add("TCP", "127.0.0.1", 8080).build(), - make(map[string]*fwk.ImageStateSummary), - ), newNodeInfo( + pod *v1.Pod + wNodeInfo *framework.NodeInfo + }{ + pod: podE, + wNodeInfo: newNodeInfo( &framework.Resource{ - MilliCPU: 100, - Memory: 500, + EphemeralStorage: 500, }, &framework.Resource{ - MilliCPU: 100, - Memory: 500, + MilliCPU: schedutil.DefaultMilliCPURequest, + Memory: schedutil.DefaultMemoryRequest, }, - []*v1.Pod{testPods[0]}, - newHostPortInfoBuilder().add("TCP", "127.0.0.1", 80).build(), + []*v1.Pod{podE}, + fwk.HostPortInfo{}, make(map[string]*fwk.ImageStateSummary), - )}, + ), } - logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - now := time.Now() - cache := newCache(ctx, ttl, time.Second, nil) - for _, podToAssume := range test.podsToAssume { - if err := assumeAndFinishBinding(logger, cache, podToAssume, now); err != nil { - t.Fatalf("assumePod failed: %v", err) - } + cache := newCache(ctx, time.Second, nil, false) + if err := cache.AddPod(logger, test.pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + n := cache.nodes[nodeName] + if err := deepEqualWithoutGeneration(n, test.wNodeInfo); err != nil { + t.Error(err) } - cache.cleanupAssumedPods(logger, now.Add(2*ttl)) - for _, podToAdd := range test.podsToAdd { - if err := cache.AddPod(logger, podToAdd); err != nil { - t.Fatalf("AddPod failed: %v", err) - } + if err := cache.RemovePod(logger, test.pod); err != nil { + t.Fatalf("RemovePod failed: %v", err) } + if _, err := cache.GetPod(test.pod); err == nil { + t.Errorf("pod was not deleted") + } +} - for j := range test.podsToUpdate { - if j == 0 { - continue - } - if err := cache.UpdatePod(logger, test.podsToUpdate[j-1], test.podsToUpdate[j]); err != nil { - t.Fatalf("UpdatePod failed: %v", err) - } - // check after expiration. confirmed pods shouldn't be expired. - n := cache.nodes[nodeName] - if err := deepEqualWithoutGeneration(n, test.wNodeInfo[j-1]); err != nil { - t.Errorf("update %d: %v", j, err) - } +func Test_AddPodGroupMember(t *testing.T) { + podGroupName := "pg" + // Pod with no pod group name. + pod1 := st.MakePod().Namespace("namespace").Name("non-workload-pod").Obj() + // Unscheduled pod with a pod group name. + pod2 := st.MakePod().Namespace("namespace").Name("unscheduled-pod").PodGroupName(podGroupName).Obj() + // Assigned pod with the same pod group name. + pod3 := st.MakePod().Namespace("namespace").Name("assigned-pod").Node("node1").PodGroupName(podGroupName).Obj() + + tests := []struct { + name string + pod *v1.Pod + genericWorkloadEnabled bool + expectInUnscheduledPods bool + expectInAssignedPods bool + }{ + { + name: "generic workload disabled", + pod: pod2, + genericWorkloadEnabled: false, + }, + { + name: "pod with no pod group name", + pod: pod1, + genericWorkloadEnabled: true, + }, + { + name: "unscheduled pod with a pod group name", + pod: pod2, + genericWorkloadEnabled: true, + expectInUnscheduledPods: true, + }, + { + name: "assigned pod with a pod group name", + pod: pod3, + genericWorkloadEnabled: true, + expectInAssignedPods: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cache := newCache(context.Background(), time.Second, nil, tt.genericWorkloadEnabled) + cache.AddPodGroupMember(tt.pod) + + if tt.pod.Spec.SchedulingGroup == nil { + if tt.expectInAssignedPods || tt.expectInUnscheduledPods { + t.Errorf("Expected pod group to exist, but pod has no pod group") + } + return + } + + podGroupState, err := cache.PodGroupStates().Get(tt.pod.Namespace, *tt.pod.Spec.SchedulingGroup.PodGroupName) + if err != nil { + if tt.genericWorkloadEnabled { + t.Errorf("Expected pod group to exist, but got error: %v", err) + } + return + } + + _, inUnscheduledPods := podGroupState.UnscheduledPods()[tt.pod.Name] + if inUnscheduledPods != tt.expectInUnscheduledPods { + t.Errorf("expected pod in UnscheduledPods: %v, got %v", tt.expectInUnscheduledPods, inUnscheduledPods) + } + + if inAssignedPods := podGroupState.AssignedPods().Has(tt.pod.UID); inAssignedPods != tt.expectInAssignedPods { + t.Errorf("expected pod in AssignedPods: %v, got %v", tt.expectInAssignedPods, inAssignedPods) + } + }) + } +} + +func Test_UpdatePodGroupMember(t *testing.T) { + podGroupName := "pg" + // unscheduled pod with a pod group name + pod := st.MakePod().Namespace("namespace").Name("unscheduled-pod").UID("pod1"). + PodGroupName(podGroupName).Obj() + // updated unscheduled pod with a pod group name + updatedPod := st.MakePod().Namespace("namespace").Name("unscheduled-pod").UID("pod1"). + Labels(map[string]string{"foo": "bar"}).PodGroupName(podGroupName).Obj() + // assigned pod with a pod group name + assignedPod := st.MakePod().Namespace("namespace").Name("assigned-pod").UID("pod2").Node("node").PodGroupName(podGroupName).Obj() + // pod with no pod group name + noPodGroupPod := st.MakePod().Namespace("namespace").Name("no-pod-group-pod").UID("pod3").Obj() + // updated pod with no pod group name + updatedNoPodGroupPod := st.MakePod().Namespace("namespace").Name("no-pod-group-pod").UID("pod3"). + Labels(map[string]string{"foo": "bar"}).Obj() + + tests := []struct { + name string + isAssumedPod bool + oldPod *v1.Pod + newPod *v1.Pod + genericWorkloadEnabled bool + expectInAssumedPods bool + expectInUnscheduledPods bool + expectInAssignedPods bool + }{ + { + name: "updating a pod with genericWorkload disabled should be a no-op", + oldPod: pod, + newPod: updatedPod, + genericWorkloadEnabled: false, + expectInUnscheduledPods: true, + }, + { + name: "update a pod with no pod group name should be a no-op", + oldPod: noPodGroupPod, + newPod: updatedNoPodGroupPod, + genericWorkloadEnabled: true, + }, + { + name: "update a pod", + isAssumedPod: true, + oldPod: pod, + newPod: updatedPod, + genericWorkloadEnabled: true, + expectInUnscheduledPods: true, + }, + { + name: "update a pod, move to assigned", + isAssumedPod: true, + oldPod: pod, + newPod: assignedPod, + genericWorkloadEnabled: true, + expectInAssignedPods: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + cache := newCache(context.Background(), time.Second, nil, true) + cache.AddPodGroupMember(tt.oldPod) + cache.genericWorkloadEnabled = tt.genericWorkloadEnabled + + newPod := tt.newPod + if newPod == nil { + newPod = tt.oldPod + } + cache.UpdatePodGroupMember(logger, tt.oldPod, newPod) + + if newPod.Spec.SchedulingGroup == nil { + if tt.expectInAssumedPods || tt.expectInUnscheduledPods || tt.expectInAssignedPods { + t.Errorf("Expected pod group to exist, but pod has no SchedulingGroup") + } + return + } + + podGroupState, err := cache.PodGroupStates().Get(newPod.Namespace, *newPod.Spec.SchedulingGroup.PodGroupName) + if err != nil { + return + } + + _, inUnscheduledPods := podGroupState.UnscheduledPods()[newPod.Name] + if inUnscheduledPods != tt.expectInUnscheduledPods { + t.Errorf("expected pod in UnscheduledPods: %v, got %v", tt.expectInUnscheduledPods, inUnscheduledPods) + } + + if inAssignedPods := podGroupState.AssignedPods().Has(newPod.UID); inAssignedPods != tt.expectInAssignedPods { + t.Errorf("expected pod in AssignedPods: %v, got %v", tt.expectInAssignedPods, inAssignedPods) + } + + if inAssumedPods := podGroupState.AssumedPods().Has(newPod.UID); inAssumedPods != tt.expectInAssumedPods { + t.Errorf("expected pod in AssumedPods: %v, got %v", tt.expectInAssumedPods, inAssumedPods) + } + + if !tt.genericWorkloadEnabled { + return + } + + podGroupKey := newPodGroupKey(newPod.Namespace, *newPod.Spec.SchedulingGroup.PodGroupName) + gotPod := cache.podGroupStates[podGroupKey].allPods[newPod.UID] + if diff := cmp.Diff(tt.newPod, gotPod); diff != "" { + t.Errorf("stored pod does not match newPod (-want +got):\n%s", diff) + } + }) + } +} + +func Test_RemovePodGroupMember(t *testing.T) { + podGroupName := "pg" + pod1 := st.MakePod().Namespace("namespace").Name("unscheduled-pod").UID("pod1"). + PodGroupName(podGroupName).Obj() + pod2 := st.MakePod().Namespace("namespace").Name("assigned-pod").UID("pod2").Node("node"). + PodGroupName(podGroupName).Obj() + + tests := []struct { + name string + initPods []*v1.Pod + podToDelete *v1.Pod + expectPodGroupStateCount int + genericWorkloadEnabled bool + }{ + { + name: "remove a pod from a group with multiple pods", + initPods: []*v1.Pod{pod1, pod2}, + podToDelete: pod1, + expectPodGroupStateCount: 1, + genericWorkloadEnabled: true, + }, + { + name: "remove a last pod from a group", + initPods: []*v1.Pod{pod1}, + podToDelete: pod1, + expectPodGroupStateCount: 0, + genericWorkloadEnabled: true, + }, + { + name: "remove a non-existent pod from a group should be a no-op", + podToDelete: pod1, + expectPodGroupStateCount: 0, + genericWorkloadEnabled: true, + }, + { + name: "remove a non-existent pod from a group should be a no-op", + podToDelete: pod1, + expectPodGroupStateCount: 0, + genericWorkloadEnabled: true, + }, + { + name: "remove a pod while generic workload disabled should be a no-op", + initPods: []*v1.Pod{pod1}, + expectPodGroupStateCount: 0, + podToDelete: pod1, + genericWorkloadEnabled: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cache := newCache(context.Background(), time.Second, nil, tt.genericWorkloadEnabled) + + for _, pod := range tt.initPods { + cache.AddPodGroupMember(pod) + } + + cache.RemovePodGroupMember(tt.podToDelete) + + podGroupStateCount := len(cache.podGroupStates) + if podGroupStateCount != tt.expectPodGroupStateCount { + t.Errorf("expected %d pod groups remaining, got %d", tt.expectPodGroupStateCount, podGroupStateCount) + } + + if podGroupStateCount == 0 { + return + } + + podGroupState, err := cache.PodGroupStates().Get(tt.podToDelete.Namespace, *tt.podToDelete.Spec.SchedulingGroup.PodGroupName) + if err != nil { + t.Fatalf("Unexpected error getting pod group state: %v", err) + } + + if podGroupState.AllPods().Has(tt.podToDelete.UID) { + t.Errorf("Expected pod %s to be deleted from pod group but it still exists", tt.podToDelete.UID) + } + }) + } +} + +// TestUpdatePodGroupStateSnapshot tests that pod group states of the snapshot have +// their data and generations updated properly. +func TestUpdatePodGroupStateSnapshot(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + cache := newCache(ctx, time.Second, nil, true) + + podGroupName1 := "pg1" + podGroupName2 := "pg2" + pod1 := st.MakePod().Namespace("ns").Name("pod1").UID("uid1").PodGroupName(podGroupName1).Obj() + pod2 := st.MakePod().Namespace("ns").Name("pod2").UID("uid2").PodGroupName(podGroupName1).Obj() + pod3 := st.MakePod().Namespace("ns").Name("pod3").UID("uid3").PodGroupName(podGroupName2).Obj() + + snapshot := NewEmptySnapshot() + + tests := []struct { + name string + action func() + expectedPods []*v1.Pod + }{ + { + name: "add a pod group member and update snapshot", + action: func() { cache.AddPodGroupMember(pod1) }, + expectedPods: []*v1.Pod{pod1}, + }, + { + name: "add a pod with different pod group and update snapshot", + action: func() { cache.AddPodGroupMember(pod3) }, + expectedPods: []*v1.Pod{pod1, pod3}, + }, + { + name: "remove a last pod group member and update snapshot", + action: func() { cache.RemovePodGroupMember(pod1) }, + expectedPods: []*v1.Pod{pod3}, + }, + { + name: "add a pod to a recently deleted pod group and update snapshot", + action: func() { cache.AddPodGroupMember(pod2) }, + expectedPods: []*v1.Pod{pod2, pod3}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture cache generations before snapshot update to detect which pod groups are going to be modified. + prevCacheGenerations := make(map[podGroupKey]int64, len(cache.podGroupStates)) + for key, pgs := range cache.podGroupStates { + prevCacheGenerations[key] = pgs.generation + } + + tt.action() + if err := cache.UpdateSnapshot(logger, snapshot); err != nil { + t.Fatalf("UpdateSnapshot failed: %v", err) + } + + // For each pod group that the action modified (its cache generation advanced), the snapshot generation must have advanced too. + // Unmodified pod groups keep their previous generation. + for key, pgs := range snapshot.podGroupStates { + cachePgs, ok := cache.podGroupStates[key] + if !ok { + continue + } + if cachePgs.generation > prevCacheGenerations[key] { + if pgs.generation <= prevCacheGenerations[key] { + t.Errorf("pod group %s was modified but snapshot generation (%d) was not incremented (%d)", key, pgs.generation, prevCacheGenerations[key]) + } + } + } + + expectedPodGroupStatesSnapshot := createPodGroupStates(tt.expectedPods) + if diff := cmp.Diff(expectedPodGroupStatesSnapshot, snapshot.podGroupStates, podGroupStateCmpOpts...); diff != "" { + t.Errorf("snapshot data mismatch (-want +got):\n%s", diff) + } + }) } } -func makePodWithEphemeralStorage(nodeName, ephemeralStorage string) *v1.Pod { - return st.MakePod().Name("pod-with-ephemeral-storage").Namespace("default-namespace").UID("pod-with-ephemeral-storage").Req( - map[v1.ResourceName]string{ - v1.ResourceEphemeralStorage: ephemeralStorage, - }, - ).Node(nodeName).Obj() -} +// TestBindingPodGroupMember simulates binding and tests that when an assumed pod +// gets bound, its state within pod group transitions from assumed to assigned. +func TestBindingPodGroupMember(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + cache := newCache(ctx, time.Second, nil, true) + podGroupName := "pg" + pod := st.MakePod().Namespace("namespace").Name("pod1").UID("pod1-uid"). + PodGroupName(podGroupName).Obj() -func TestEphemeralStorageResource(t *testing.T) { - nodeName := "node" - podE := makePodWithEphemeralStorage(nodeName, "500") - test := struct { - pod *v1.Pod - wNodeInfo *framework.NodeInfo - }{ - pod: podE, - wNodeInfo: newNodeInfo( - &framework.Resource{ - EphemeralStorage: 500, - }, - &framework.Resource{ - MilliCPU: schedutil.DefaultMilliCPURequest, - Memory: schedutil.DefaultMemoryRequest, - }, - []*v1.Pod{podE}, - fwk.HostPortInfo{}, - make(map[string]*fwk.ImageStateSummary), - ), + // Simulate the informer firing an Add event for an unscheduled + // pod (no NodeName set) reflecting on PodGroupStates. + cache.AddPodGroupMember(pod) + + // Simulate the scheduler assuming the pod on a node. + assumedPod := pod.DeepCopy() + assumedPod.Spec.NodeName = "node1" + if err := cache.AssumePod(logger, assumedPod); err != nil { + t.Fatalf("AssumePod failed: %v", err) } - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - cache := newCache(ctx, time.Second, time.Second, nil) - if err := cache.AddPod(logger, test.pod); err != nil { - t.Fatalf("AddPod failed: %v", err) + + podGroupState, err := cache.PodGroupStates().Get(pod.Namespace, podGroupName) + if err != nil { + t.Fatalf("Unexpected error getting pod group state after AssumePod: %v", err) } - n := cache.nodes[nodeName] - if err := deepEqualWithoutGeneration(n, test.wNodeInfo); err != nil { - t.Error(err) + if !podGroupState.AssumedPods().Has(assumedPod.UID) { + t.Errorf("Expected pod to be in AssumedPods after AssumePod") + } + if podGroupState.AssignedPods().Has(assumedPod.UID) { + t.Errorf("Expected pod NOT to be in AssignedPods after AssumePod") } - if err := cache.RemovePod(logger, test.pod); err != nil { - t.Fatalf("RemovePod failed: %v", err) + // Simulate binding confirmation: the informer fires an Add event with NodeName set. + if err := cache.AddPod(logger, assumedPod); err != nil { + t.Fatalf("AddPod (binding confirmation) failed: %v", err) } - if _, err := cache.GetPod(test.pod); err == nil { - t.Errorf("pod was not deleted") + + podGroupState, err = cache.PodGroupStates().Get(pod.Namespace, podGroupName) + if err != nil { + t.Fatalf("Unexpected error getting pod group state after AddPod: %v", err) + } + if podGroupState.AssumedPods().Has(assumedPod.UID) { + t.Errorf("Expected pod not to be in AssumedPods after binding confirmation") + } + if !podGroupState.AssignedPods().Has(assumedPod.UID) { + t.Errorf("Expected pod to be in AssignedPods after binding confirmation") } } @@ -963,7 +1110,7 @@ func TestRemovePod(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() nodeName := pod.Spec.NodeName - cache := newCache(ctx, time.Second, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) // Add/Assume pod succeeds even before adding the nodes. if tt.assume { if err := cache.AddPod(logger, pod); err != nil { @@ -1007,15 +1154,13 @@ func TestForgetPod(t *testing.T) { nodeName := "node" basePod := makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}) pods := []*v1.Pod{basePod} - now := time.Now() - ttl := 10 * time.Second logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, ttl, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, pod := range pods { - if err := assumeAndFinishBinding(logger, cache, pod, now); err != nil { + if err := cache.AssumePod(logger, pod); err != nil { t.Fatalf("assumePod failed: %v", err) } isAssumed, err := cache.IsAssumedPod(pod) @@ -1226,7 +1371,7 @@ func TestNodeOperators(t *testing.T) { imageStates := buildImageStates(tc.nodes) expected := buildNodeInfo(node, tc.pods, imageStates) - cache := newCache(ctx, time.Second, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for _, nodeItem := range tc.nodes { cache.AddNode(logger, nodeItem) } @@ -1360,9 +1505,180 @@ func TestNodeOperators(t *testing.T) { } } +// TestPodGroupPodOperations tests that operations (Add, Update, Remove, Assume, Forget) on +// pods with pod group name properly update PodGroupStates only when GenericWorkload feature gate is enabled. +func TestPodGroupPodOperations(t *testing.T) { + groupName := "pg" + pod := st.MakePod().Namespace("test-ns").Name("pod-0").UID("uid-0"). + PodGroupName(groupName).Obj() + + type state struct { + podGroupStatesCount int + assignedCount int + unscheduledCount int + assumedCount int + } + + tests := []struct { + name string + genericWorkloadEnabled bool + setup func(*testing.T, *cacheImpl, context.Context) + operation func(*testing.T, *cacheImpl, context.Context) + expected state + }{ + { + name: "AddPod with GenericWorkload disabled", + genericWorkloadEnabled: false, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AddPod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 0}, + }, + { + name: "AddPod with GenericWorkload enabled", + genericWorkloadEnabled: true, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AddPod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 1, unscheduledCount: 1, assignedCount: 0, assumedCount: 0}, + }, + { + name: "AssumePod with GenericWorkload disabled", + genericWorkloadEnabled: false, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AssumePod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AssumePod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 0}, + }, + { + name: "AssumePod with GenericWorkload enabled", + genericWorkloadEnabled: true, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AssumePod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AssumePod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 1, assignedCount: 0, unscheduledCount: 0, assumedCount: 1}, + }, + { + name: "ForgetPod with GenericWorkload disabled", + genericWorkloadEnabled: false, + setup: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AssumePod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AssumePod failed: %v", err) + } + }, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.ForgetPod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("ForgetPod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 1, assignedCount: 0, unscheduledCount: 0, assumedCount: 1}, + }, + { + name: "ForgetPod with GenericWorkload enabled", + genericWorkloadEnabled: true, + setup: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AssumePod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AssumePod failed: %v", err) + } + }, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.ForgetPod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("ForgetPod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 1, unscheduledCount: 1}, + }, + { + name: "RemovePod with GenericWorkload disabled", + genericWorkloadEnabled: false, + setup: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AddPod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + }, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.RemovePod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("RemovePod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 1, assignedCount: 0, unscheduledCount: 1, assumedCount: 0}, + }, + { + name: "RemovePod with GenericWorkload enabled", + genericWorkloadEnabled: true, + setup: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.AddPod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + }, + operation: func(t *testing.T, cache *cacheImpl, ctx context.Context) { + if err := cache.RemovePod(klog.FromContext(ctx), pod); err != nil { + t.Fatalf("RemovePod failed: %v", err) + } + }, + expected: state{podGroupStatesCount: 0}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Initialize cache with feature gate enabled to ensure group state is + // properly established for operations that require it. + cache := newCache(ctx, time.Second, nil, true) + if tt.setup != nil { + tt.setup(t, cache, ctx) + } + cache.genericWorkloadEnabled = tt.genericWorkloadEnabled + tt.operation(t, cache, ctx) + + if count := len(cache.podGroupStates); count != tt.expected.podGroupStatesCount { + t.Errorf("expected %d pod group states, got %d", tt.expected.podGroupStatesCount, count) + } + + if tt.expected.podGroupStatesCount == 0 { + return + } + + pgs, err := cache.PodGroupStates().Get("test-ns", groupName) + if err != nil { + t.Fatalf("unexpected error getting pod group state: %v", err) + } + + assignedCount := pgs.AssignedPods().Len() + if assignedCount != tt.expected.assignedCount { + t.Errorf("expected %d pods in assignedPods, got %d", tt.expected.assignedCount, assignedCount) + } + + unscheduledCount := len(pgs.UnscheduledPods()) + if unscheduledCount != tt.expected.unscheduledCount { + t.Errorf("expected %d pods in unscheduledPods, got %d", tt.expected.unscheduledCount, unscheduledCount) + } + + assumedCount := pgs.AssumedPods().Len() + if assumedCount != tt.expected.assumedCount { + t.Errorf("expected %d pods in assumedPods, got %d", tt.expected.assumedCount, assumedCount) + } + }) + } +} + func TestSchedulerCache_UpdateSnapshot(t *testing.T) { logger, _ := ktesting.NewTestContext(t) + var podGroupName = "pg" + // Create a few nodes to be used in tests. var nodes []*v1.Node for i := 0; i < 10; i++ { @@ -1407,20 +1723,26 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { updatedPods = append(updatedPods, updatedPod) } - // Add a couple of pods with affinity, on the first and seconds nodes. + // Add a few of pods with affinity var podsWithAffinity []*v1.Pod - for i := 0; i < 2; i++ { + for i := range 20 { pod := st.MakePod().Name(fmt.Sprintf("p-affinity-%v", i)).Namespace("test-ns").UID(fmt.Sprintf("puid-affinity-%v", i)). PodAffinityExists("foo", "", st.PodAffinityWithRequiredReq).Node(fmt.Sprintf("test-node%v", i)).Obj() podsWithAffinity = append(podsWithAffinity, pod) } - // Add a few of pods with PVC - var podsWithPVC []*v1.Pod - for i := 0; i < 8; i++ { - pod := st.MakePod().Name(fmt.Sprintf("p-pvc-%v", i)).Namespace("test-ns").UID(fmt.Sprintf("puid-pvc-%v", i)). - PVC(fmt.Sprintf("test-pvc%v", i%4)).Node(fmt.Sprintf("test-node%v", i%2)).Obj() - podsWithPVC = append(podsWithPVC, pod) + makePodWithPVC := func(podID int, node int, pvcID int) *v1.Pod { + return st.MakePod().Name(fmt.Sprintf("p-pvc-%v", podID)).Namespace("test-ns").UID(fmt.Sprintf("puid-pvc-%v", podID)). + PVC(fmt.Sprintf("test-pvc%v", pvcID)).Node(fmt.Sprintf("test-node%v", node)).Obj() + } + + // Add a few pods with a pod group name + var podsWithPodGroupName []*v1.Pod + for i := range 20 { + pod := st.MakePod().Name(fmt.Sprintf("p-podgroup-%v", i)).Namespace("test-ns").UID(fmt.Sprintf("puid-podgroup-%v", i)). + PodGroupName(fmt.Sprintf("%s-%v", podGroupName, i)). + Node(fmt.Sprintf("test-node%v", i)).Obj() + podsWithPodGroupName = append(podsWithPodGroupName, pod) } var cache *cacheImpl @@ -1458,9 +1780,17 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { } } } - addPodWithPVC := func(i int) operation { + addPodWithPVC := func(podID int, node int, pvcID int) operation { + return func(t *testing.T) { + pod := makePodWithPVC(podID, node, pvcID) + if err := cache.AddPod(logger, pod); err != nil { + t.Error(err) + } + } + } + addPodWithPodGroupName := func(i int) operation { return func(t *testing.T) { - if err := cache.AddPod(logger, podsWithPVC[i]); err != nil { + if err := cache.AddPod(logger, podsWithPodGroupName[i]); err != nil { t.Error(err) } } @@ -1479,9 +1809,17 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { } } } - removePodWithPVC := func(i int) operation { + removePodWithPVC := func(podID int, node int, pvcID int) operation { + return func(t *testing.T) { + pod := makePodWithPVC(podID, node, pvcID) + if err := cache.RemovePod(logger, pod); err != nil { + t.Error(err) + } + } + } + removePodWithPodGroupName := func(i int) operation { return func(t *testing.T) { - if err := cache.RemovePod(logger, podsWithPVC[i]); err != nil { + if err := cache.RemovePod(logger, podsWithPodGroupName[i]); err != nil { t.Error(err) } } @@ -1493,6 +1831,104 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { } } } + updatePodWithPodGroupName := func(i int) operation { + return func(t *testing.T) { + if err := cache.UpdatePod(logger, podsWithPodGroupName[i], podsWithPodGroupName[i]); err != nil { + t.Error(err) + } + } + } + assumePod := func(i int) operation { + return func(t *testing.T) { + if err := cache.AssumePod(logger, pods[i]); err != nil { + t.Error(err) + } + } + } + assumePodWithAffinity := func(i int) operation { + return func(t *testing.T) { + if err := cache.AssumePod(logger, podsWithAffinity[i]); err != nil { + t.Error(err) + } + } + } + assumePodWithPVC := func(podID int, node int, pvcID int) operation { + return func(t *testing.T) { + pod := makePodWithPVC(podID, node, pvcID) + if err := cache.AssumePod(logger, pod); err != nil { + t.Error(err) + } + } + } + assumePodInSnapshot := func(i int) operation { + return func(t *testing.T) { + podInfo, _ := framework.NewPodInfo(pods[i]) + if err := snapshot.AssumePod(podInfo); err != nil { + t.Error(err) + } + } + } + assumePodWithAffinityInSnapshot := func(i int) operation { + return func(t *testing.T) { + podInfo, _ := framework.NewPodInfo(podsWithAffinity[i]) + if err := snapshot.AssumePod(podInfo); err != nil { + t.Error(err) + } + } + } + assumePodWithPVCInSnapshot := func(podID int, node int, pvcID int) operation { + return func(t *testing.T) { + pod := makePodWithPVC(podID, node, pvcID) + podInfo, _ := framework.NewPodInfo(pod) + if err := snapshot.AssumePod(podInfo); err != nil { + t.Error(err) + } + } + } + forgetPod := func(i int) operation { + return func(t *testing.T) { + if err := cache.ForgetPod(logger, pods[i]); err != nil { + t.Error(err) + } + } + } + forgetPodWithAffinity := func(i int) operation { + return func(t *testing.T) { + if err := cache.ForgetPod(logger, podsWithAffinity[i]); err != nil { + t.Error(err) + } + } + } + forgetPodWithPVC := func(podID int, node int, pvcID int) operation { + return func(t *testing.T) { + pod := makePodWithPVC(podID, node, pvcID) + if err := cache.ForgetPod(logger, pod); err != nil { + t.Error(err) + } + } + } + forgetPodInSnapshot := func(i int) operation { + return func(t *testing.T) { + if err := snapshot.ForgetPod(logger, pods[i]); err != nil { + t.Error(err) + } + } + } + forgetPodWithAffinityInSnapshot := func(i int) operation { + return func(t *testing.T) { + if err := snapshot.ForgetPod(logger, podsWithAffinity[i]); err != nil { + t.Error(err) + } + } + } + forgetPodWithPVCInSnapshot := func(podID int, node int, pvcID int) operation { + return func(t *testing.T) { + pod := makePodWithPVC(podID, node, pvcID) + if err := snapshot.ForgetPod(logger, pod); err != nil { + t.Error(err) + } + } + } updateSnapshot := func() operation { return func(t *testing.T) { cache.UpdateSnapshot(logger, snapshot) @@ -1503,11 +1939,12 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { } tests := []struct { - name string - operations []operation - expected []*v1.Node - expectedHavePodsWithAffinity int - expectedUsedPVCSet sets.Set[string] + name string + operations []operation + expected []*v1.Node + expectedHavePodsWithAffinity int + expectedPodGroupStatesSnapshot map[podGroupKey]*podGroupStateSnapshot + expectedUsedPVCSet sets.Set[string] }{ { name: "Empty cache", @@ -1646,7 +2083,7 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { { name: "Add Pods with PVC", operations: []operation{ - addNode(0), addPodWithPVC(0), updateSnapshot(), addNode(1), + addNode(0), addPodWithPVC(0, 0, 0), updateSnapshot(), addNode(1), }, expected: []*v1.Node{nodes[1], nodes[0]}, expectedUsedPVCSet: sets.New("test-ns/test-pvc0"), @@ -1663,7 +2100,7 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { { name: "Add multiple nodes with pods with PVC", operations: []operation{ - addNode(0), addPodWithPVC(0), updateSnapshot(), addNode(1), addPodWithPVC(1), updateSnapshot(), + addNode(0), addPodWithPVC(0, 0, 0), updateSnapshot(), addNode(1), addPodWithPVC(1, 1, 1), updateSnapshot(), }, expected: []*v1.Node{nodes[1], nodes[0]}, expectedUsedPVCSet: sets.New("test-ns/test-pvc0", "test-ns/test-pvc1"), @@ -1680,7 +2117,7 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { { name: "Add then Remove pod with PVC", operations: []operation{ - addNode(0), addPodWithPVC(0), updateSnapshot(), removePodWithPVC(0), addPodWithPVC(2), updateSnapshot(), + addNode(0), addPodWithPVC(0, 0, 0), updateSnapshot(), removePodWithPVC(0, 0, 0), addPodWithPVC(2, 0, 2), updateSnapshot(), }, expected: []*v1.Node{nodes[0]}, expectedUsedPVCSet: sets.New("test-ns/test-pvc2"), @@ -1688,7 +2125,7 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { { name: "Add then Remove pod with PVC and add same pod again", operations: []operation{ - addNode(0), addPodWithPVC(0), updateSnapshot(), removePodWithPVC(0), addPodWithPVC(0), updateSnapshot(), + addNode(0), addPodWithPVC(0, 0, 0), updateSnapshot(), removePodWithPVC(0, 0, 0), addPodWithPVC(0, 0, 0), updateSnapshot(), }, expected: []*v1.Node{nodes[0]}, expectedUsedPVCSet: sets.New("test-ns/test-pvc0"), @@ -1696,23 +2133,81 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { { name: "Add and Remove multiple pods with PVC with same ref count length different content", operations: []operation{ - addNode(0), addNode(1), addPodWithPVC(0), addPodWithPVC(1), updateSnapshot(), - removePodWithPVC(0), removePodWithPVC(1), addPodWithPVC(2), addPodWithPVC(3), updateSnapshot(), + addNode(0), addNode(1), addPodWithPVC(0, 0, 0), addPodWithPVC(1, 1, 1), updateSnapshot(), + removePodWithPVC(0, 0, 0), removePodWithPVC(1, 1, 1), addPodWithPVC(2, 0, 2), addPodWithPVC(3, 1, 3), updateSnapshot(), }, expected: []*v1.Node{nodes[1], nodes[0]}, expectedUsedPVCSet: sets.New("test-ns/test-pvc2", "test-ns/test-pvc3"), }, + { + name: "Add, Update and Remove multiple pods with SchedulingGroup", + operations: []operation{ + addNode(0), addNode(1), addNode(2), addPodWithPodGroupName(0), addPodWithPodGroupName(1), + addPodWithPodGroupName(2), updateSnapshot(), + updatePodWithPodGroupName(0), removePodWithPodGroupName(1), updateSnapshot(), + }, + expected: []*v1.Node{nodes[1], nodes[0], nodes[2]}, + expectedPodGroupStatesSnapshot: map[podGroupKey]*podGroupStateSnapshot{ + newPodGroupKey("test-ns", "pg-0"): { + podGroupStateData: podGroupStateData{ + allPods: map[types.UID]*v1.Pod{"puid-podgroup-0": podsWithPodGroupName[0]}, + assignedPods: sets.New[types.UID]("puid-podgroup-0"), + unscheduledPods: sets.New[types.UID](), + assumedPods: make(map[types.UID]*v1.Pod), + }, + }, + newPodGroupKey("test-ns", "pg-2"): { + podGroupStateData: podGroupStateData{ + allPods: map[types.UID]*v1.Pod{"puid-podgroup-2": podsWithPodGroupName[2]}, + assignedPods: sets.New[types.UID]("puid-podgroup-2"), + unscheduledPods: sets.New[types.UID](), + assumedPods: make(map[types.UID]*v1.Pod), + }, + }, + }, + }, { name: "Add and Remove multiple pods with PVC", operations: []operation{ - addNode(0), addNode(1), addPodWithPVC(0), addPodWithPVC(1), addPodWithPVC(2), updateSnapshot(), - removePodWithPVC(0), removePodWithPVC(1), updateSnapshot(), addPodWithPVC(0), updateSnapshot(), - addPodWithPVC(3), addPodWithPVC(4), addPodWithPVC(5), updateSnapshot(), - removePodWithPVC(0), removePodWithPVC(3), removePodWithPVC(4), updateSnapshot(), + addNode(0), addNode(1), addPodWithPVC(0, 0, 0), addPodWithPVC(1, 1, 1), addPodWithPVC(2, 0, 2), updateSnapshot(), + removePodWithPVC(0, 0, 0), removePodWithPVC(1, 1, 1), updateSnapshot(), addPodWithPVC(0, 0, 0), updateSnapshot(), + addPodWithPVC(3, 1, 3), addPodWithPVC(4, 0, 0), addPodWithPVC(5, 1, 1), updateSnapshot(), + removePodWithPVC(0, 0, 0), removePodWithPVC(3, 1, 3), removePodWithPVC(4, 0, 0), updateSnapshot(), }, expected: []*v1.Node{nodes[0], nodes[1]}, expectedUsedPVCSet: sets.New("test-ns/test-pvc1", "test-ns/test-pvc2"), }, + { + name: "Assume and forget in cache, and in snapshot", + operations: []operation{ + addNode(0), addNode(2), addNode(4), addNode(8), updateSnapshot(), + assumePod(8), assumePodInSnapshot(4), assumePod(0), forgetPod(0), + assumePodInSnapshot(2), forgetPodInSnapshot(4), updateSnapshot(), + }, + expected: []*v1.Node{nodes[0], nodes[8], nodes[4], nodes[2]}, + expectedUsedPVCSet: sets.New[string](), + }, + { + name: "Assume and forget in cache, and in snapshot, with affinity", + operations: []operation{ + addNode(0), addNode(2), addNode(4), addNode(8), updateSnapshot(), + assumePodWithAffinity(8), assumePodWithAffinityInSnapshot(4), assumePodWithAffinity(0), forgetPodWithAffinity(0), + assumePodWithAffinityInSnapshot(2), forgetPodWithAffinityInSnapshot(4), updateSnapshot(), + }, + expected: []*v1.Node{nodes[0], nodes[8], nodes[4], nodes[2]}, + expectedHavePodsWithAffinity: 1, + expectedUsedPVCSet: sets.New[string](), + }, + { + name: "Assume and forget in cache, and in snapshot, with PVC", + operations: []operation{ + addNode(0), addNode(2), addNode(4), addNode(8), updateSnapshot(), + assumePodWithPVC(8, 8, 8), assumePodWithPVCInSnapshot(4, 4, 4), assumePodWithPVC(0, 0, 0), forgetPodWithPVC(0, 0, 0), + assumePodWithPVCInSnapshot(2, 2, 2), forgetPodWithPVCInSnapshot(4, 4, 4), updateSnapshot(), + }, + expected: []*v1.Node{nodes[0], nodes[8], nodes[4], nodes[2]}, + expectedUsedPVCSet: sets.New("test-ns/test-pvc8"), + }, } for _, test := range tests { @@ -1720,7 +2215,7 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache = newCache(ctx, time.Second, time.Second, nil) + cache = newCache(ctx, time.Second, nil, true) snapshot = NewEmptySnapshot() for _, op := range test.operations { @@ -1743,6 +2238,11 @@ func TestSchedulerCache_UpdateSnapshot(t *testing.T) { t.Errorf("Not all the nodes were visited by following the NodeInfo linked list. Expected to see %v nodes, saw %v.", len(cache.nodes), i) } + // Check pod group states in the snapshot. + if diff := cmp.Diff(test.expectedPodGroupStatesSnapshot, snapshot.podGroupStates, podGroupStateCmpOpts...); diff != "" { + t.Errorf("unexpected podGroupStates in snapshot (-want, +got):\n%s", diff) + } + // Check number of nodes with pods with affinity if len(snapshot.havePodsWithAffinityNodeInfoList) != test.expectedHavePodsWithAffinity { t.Errorf("unexpected number of HavePodsWithAffinity nodes. Expected: %v, got: %v", test.expectedHavePodsWithAffinity, len(snapshot.havePodsWithAffinityNodeInfoList)) @@ -1954,7 +2454,7 @@ func TestSchedulerCache_updateNodeInfoSnapshotList(t *testing.T) { _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache = newCache(ctx, time.Second, time.Second, nil) + cache = newCache(ctx, time.Second, nil, false) snapshot = NewEmptySnapshot() test.operations(t) @@ -1985,31 +2485,6 @@ func BenchmarkUpdate1kNodes30kPods(b *testing.B) { } } -func BenchmarkExpirePods(b *testing.B) { - podNums := []int{ - 100, - 1000, - 10000, - } - for _, podNum := range podNums { - name := fmt.Sprintf("%dPods", podNum) - b.Run(name, func(b *testing.B) { - benchmarkExpire(b, podNum) - }) - } -} - -func benchmarkExpire(b *testing.B, podNum int) { - logger, _ := ktesting.NewTestContext(b) - now := time.Now() - for n := 0; n < b.N; n++ { - b.StopTimer() - cache := setupCacheWithAssumedPods(b, podNum, now) - b.StartTimer() - cache.cleanupAssumedPods(logger, now.Add(2*time.Second)) - } -} - type testingMode interface { Fatalf(format string, args ...interface{}) } @@ -2060,7 +2535,7 @@ func setupCacheOf1kNodes30kPods(b *testing.B) Cache { logger, ctx := ktesting.NewTestContext(b) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := newCache(ctx, time.Second, time.Second, nil) + cache := newCache(ctx, time.Second, nil, false) for i := 0; i < 1000; i++ { nodeName := fmt.Sprintf("node-%d", i) cache.AddNode(logger, st.MakeNode().Name(nodeName).Obj()) @@ -2076,29 +2551,6 @@ func setupCacheOf1kNodes30kPods(b *testing.B) Cache { return cache } -func setupCacheWithAssumedPods(b *testing.B, podNum int, assumedTime time.Time) *cacheImpl { - logger, ctx := ktesting.NewTestContext(b) - ctx, cancel := context.WithCancel(ctx) - addedNodes := make(map[string]struct{}) - defer cancel() - cache := newCache(ctx, time.Second, time.Second, nil) - for i := 0; i < podNum; i++ { - nodeName := fmt.Sprintf("node-%d", i/10) - if _, ok := addedNodes[nodeName]; !ok { - cache.AddNode(logger, st.MakeNode().Name(nodeName).Obj()) - addedNodes[nodeName] = struct{}{} - } - objName := fmt.Sprintf("%s-pod-%d", nodeName, i%10) - pod := makeBasePod(b, nodeName, objName, "0", "0", "", nil) - - err := assumeAndFinishBinding(logger, cache, pod, assumedTime) - if err != nil { - b.Fatalf("assumePod failed: %v", err) - } - } - return cache -} - func isForgottenFromCache(p *v1.Pod, c *cacheImpl) error { if assumed, err := c.IsAssumedPod(p); err != nil { return err diff --git a/pkg/scheduler/backend/cache/debugger/signal.go b/pkg/scheduler/backend/cache/debugger/signal.go index df5c83cea0d02..78acf3acb451b 100644 --- a/pkg/scheduler/backend/cache/debugger/signal.go +++ b/pkg/scheduler/backend/cache/debugger/signal.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/scheduler/backend/cache/fake/fake_cache.go b/pkg/scheduler/backend/cache/fake/fake_cache.go index b6507df487021..adabf222cb906 100644 --- a/pkg/scheduler/backend/cache/fake/fake_cache.go +++ b/pkg/scheduler/backend/cache/fake/fake_cache.go @@ -25,10 +25,11 @@ import ( // Cache is used for testing type Cache struct { internalcache.Cache - AssumeFunc func(*v1.Pod) - ForgetFunc func(*v1.Pod) - IsAssumedPodFunc func(*v1.Pod) bool - GetPodFunc func(*v1.Pod) *v1.Pod + AssumeFunc func(*v1.Pod) + ForgetFunc func(*v1.Pod) + IsAssumedPodFunc func(*v1.Pod) bool + GetPodFunc func(*v1.Pod) *v1.Pod + UpdateSnapshotFunc func(nodeSnapshot *internalcache.Snapshot) error } // AssumePod allows to mock this method for testing. @@ -64,3 +65,11 @@ func (c *Cache) GetPod(pod *v1.Pod) (*v1.Pod, error) { } return c.Cache.GetPod(pod) } + +// UpdateSnapshot allows to mock this method for testing. +func (c *Cache) UpdateSnapshot(logger klog.Logger, nodeSnapshot *internalcache.Snapshot) error { + if c.UpdateSnapshotFunc != nil { + return c.UpdateSnapshotFunc(nodeSnapshot) + } + return c.Cache.UpdateSnapshot(logger, nodeSnapshot) +} diff --git a/pkg/scheduler/backend/cache/interface.go b/pkg/scheduler/backend/cache/interface.go index 74d0bd2279704..de44609a4933e 100644 --- a/pkg/scheduler/backend/cache/interface.go +++ b/pkg/scheduler/backend/cache/interface.go @@ -20,6 +20,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" + fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework" ) @@ -37,26 +38,21 @@ import ( // | | | | Update // + Assume Add v v | // -// Initial +--------> Assumed +------------+---> Added <--+ +// Initial +--------> Assumed +----------- ---> Added <--+ // -// ^ + + | + -// | | | | | -// | | | Add | | Remove -// | | | | | -// | | | + | -// +----------------+ +-----------> Expired +----> Deleted -// Forget Expire +// ^ + + +// | | | +// | | | Remove +// | | | +// | | v +// +--------------------+ Deleted +// Forget // -// Note that an assumed pod can expire, because if we haven't received Add event notifying us -// for a while, there might be some problems and we shouldn't keep the pod in cache anymore. -// -// Note that "Initial", "Expired", and "Deleted" pods do not actually exist in cache. +// Note that "Initial" and "Deleted" pods do not actually exist in cache. // Based on existing use cases, we are making the following assumptions: // - No pod would be assumed twice // - A pod could be added without going through scheduler. In this case, we will see Add but not Assume event. // - If a pod wasn't added, it wouldn't be removed or updated. -// - Both "Expired" and "Deleted" are valid end states. In case of some problems, e.g. network issue, -// a pod might have changed its state (e.g. added and deleted) without delivering notification to the cache. type Cache interface { // NodeCount returns the number of nodes in the cache. // DO NOT use outside of tests. @@ -66,19 +62,17 @@ type Cache interface { // DO NOT use outside of tests. PodCount() (int, error) + // GetNode returns the copy of node stored in the cache. + // DO NOT use outside of tests. + GetNode(name string) (*framework.NodeInfo, error) + // AssumePod assumes a pod scheduled and aggregates the pod's information into its node. - // The implementation also decides the policy to expire pod before being confirmed (receiving Add event). - // After expiration, its information would be subtracted. AssumePod(logger klog.Logger, pod *v1.Pod) error - // FinishBinding signals that cache for assumed pod can be expired - FinishBinding(logger klog.Logger, pod *v1.Pod) error - // ForgetPod removes an assumed pod from cache. ForgetPod(logger klog.Logger, pod *v1.Pod) error - // AddPod either confirms a pod if it's assumed, or adds it back if it's expired. - // If added back, the pod's information would be added again. + // AddPod confirms an assumed pod, or adds a newly assigned pod to the cache. AddPod(logger klog.Logger, pod *v1.Pod) error // UpdatePod removes oldPod's information and adds newPod's information. @@ -91,7 +85,7 @@ type Cache interface { // same name of the specified pod. GetPod(pod *v1.Pod) (*v1.Pod, error) - // IsAssumedPod returns true if the pod is assumed and not expired. + // IsAssumedPod returns true if the pod is assumed. IsAssumedPod(pod *v1.Pod) (bool, error) // AddNode adds overall information about node. @@ -118,6 +112,18 @@ type Cache interface { // BindPod handles the pod binding by adding a bind API call to the dispatcher. // This method should be used only if the SchedulerAsyncAPICalls feature gate is enabled. BindPod(binding *v1.Binding) (<-chan error, error) + + // PodGroupStates returns a PodGroupStateLister. + PodGroupStates() fwk.PodGroupStateLister + + // AddPodGroupMember adds not assigned and not assumed pod to its pod group state. + AddPodGroupMember(pod *v1.Pod) + + // UpdatePodGroupMember updates a pod in its pod group state. + UpdatePodGroupMember(logger klog.Logger, oldPod, newPod *v1.Pod) + + // RemovePodGroupMember removes a pod from its pod group state. + RemovePodGroupMember(pod *v1.Pod) } // Dump is a dump of the cache state. diff --git a/pkg/scheduler/backend/cache/podgroupstate.go b/pkg/scheduler/backend/cache/podgroupstate.go new file mode 100644 index 0000000000000..3c6efb831a4ef --- /dev/null +++ b/pkg/scheduler/backend/cache/podgroupstate.go @@ -0,0 +1,397 @@ +/* +Copyright 2025 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 cache + +import ( + "maps" + "sync" + "sync/atomic" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" +) + +var generation atomic.Int64 + +// nextPodGroupGeneration increments generation numbers monotonically for a pod group state (instead of per-instance increment) +// to prevent generation reset or collision when a pod group is deleted and recreated with the same name. +func nextPodGroupGeneration() int64 { + return generation.Add(1) +} + +// podGroupKey uniquely identifies a specific instance of a PodGroup. +type podGroupKey struct { + name string + namespace string +} + +func (pgk podGroupKey) GetName() string { + return pgk.name +} + +func (pgk podGroupKey) GetNamespace() string { + return pgk.namespace +} + +func (pgk podGroupKey) String() string { + return pgk.namespace + "/" + pgk.GetName() +} + +var _ klog.KMetadata = &podGroupKey{} + +func newPodGroupKey(namespace string, name string) podGroupKey { + return podGroupKey{ + namespace: namespace, + name: name, + } +} + +// podGroupStateData holds data and functionality shared between podGroupState and podGroupStateSnapshot. +type podGroupStateData struct { + // generation gets bumped whenever the data is changed. + // It's used to detect changes and avoid unnecessary cloning when taking a snapshot. + generation int64 + // allPods tracks all pods belonging to the group that are known to the scheduler. + allPods map[types.UID]*v1.Pod + // unscheduledPods tracks all pods that are unscheduled for this group, + // i.e., are neither assumed nor assigned. + unscheduledPods sets.Set[types.UID] + // assumedPods tracks pods that have reached the Reserve stage and are waiting + // for the rest of the gang to arrive before being allowed to bind. + assumedPods map[types.UID]*v1.Pod + // assignedPods tracks all pods belonging to the group that are assigned (bound). + assignedPods sets.Set[types.UID] +} + +func newPodGroupStateData() podGroupStateData { + return podGroupStateData{ + allPods: make(map[types.UID]*v1.Pod), + unscheduledPods: sets.New[types.UID](), + assumedPods: make(map[types.UID]*v1.Pod), + assignedPods: sets.New[types.UID](), + } +} + +// addPod adds the pod to this group. +// Depending on the NodeName, it can insert the pod into either assignedPods or unscheduledPods. +func (d *podGroupStateData) addPod(pod *v1.Pod) { + d.generation = nextPodGroupGeneration() + d.allPods[pod.UID] = pod + if pod.Spec.NodeName != "" { + d.assignedPods.Insert(pod.UID) + } else { + d.unscheduledPods.Insert(pod.UID) + } +} + +// updatePod updates the pod in this group. +// In case of binding, it moves the pod to assignedPods. +func (d *podGroupStateData) updatePod(oldPod, newPod *v1.Pod) { + d.generation = nextPodGroupGeneration() + d.allPods[newPod.UID] = newPod + if oldPod.Spec.NodeName == "" && newPod.Spec.NodeName != "" { + d.assignedPods.Insert(newPod.UID) + // Clear pod from unscheduled and assumed when it is assigned. + d.unscheduledPods.Delete(newPod.UID) + delete(d.assumedPods, newPod.UID) + } +} + +// deletePod removes the pod from this pod group state. +func (d *podGroupStateData) deletePod(podUID types.UID) { + d.generation = nextPodGroupGeneration() + delete(d.allPods, podUID) + d.unscheduledPods.Delete(podUID) + delete(d.assumedPods, podUID) + d.assignedPods.Delete(podUID) +} + +// assumePod marks a pod as assumed within the pod group state. +func (d *podGroupStateData) assumePod(pod *v1.Pod) { + storedPod, ok := d.allPods[pod.UID] + // A scheduling pod may be removed from the cluster. + // In that case, we just ignore it. + if !ok { + return + } + + d.generation = nextPodGroupGeneration() + // If the pod stored in the state is already assigned, put it into assignedPods. + // Otherwise put it to assumedPods. + if storedPod.Spec.NodeName != "" { + d.assignedPods.Insert(pod.UID) + } else { + d.assumedPods[pod.UID] = pod + } + d.unscheduledPods.Delete(pod.UID) +} + +// forgetPod moves a pod back from the assumed state to unscheduled within the pod group state. +func (d *podGroupStateData) forgetPod(podUID types.UID) { + + pod := d.allPods[podUID] + // A scheduling pod may be removed from the cluster. + // In that case, we just ignore it. + if pod == nil { + return + } + + d.generation = nextPodGroupGeneration() + + delete(d.assumedPods, podUID) + + // If the pod is already assigned, put it into assignedPods. + // Otherwise, put it into unscheduledPods. + if pod.Spec.NodeName != "" { + d.assignedPods.Insert(podUID) + } else { + d.unscheduledPods.Insert(podUID) + } +} + +// scheduledPods returns the pods that are either assumed or assigned for this pod group. +func (d *podGroupStateData) scheduledPods() []*v1.Pod { + scheduledPods := make([]*v1.Pod, 0, len(d.assignedPods)+len(d.assumedPods)) + for uid := range d.assignedPods { + scheduledPods = append(scheduledPods, d.allPods[uid]) + } + for _, pod := range d.assumedPods { + scheduledPods = append(scheduledPods, pod) + } + return scheduledPods +} + +// empty returns true when the pod group state contains no pods. +func (d *podGroupStateData) empty() bool { + return len(d.allPods) == 0 +} + +// allPodsCount returns the number of all pods known to the scheduler for this group. +func (d *podGroupStateData) allPodsCount() int { + return len(d.allPods) +} + +// scheduledPodsCount returns the number of pods for this group that are either assumed or assigned. +func (d *podGroupStateData) scheduledPodsCount() int { + return len(d.assumedPods) + len(d.assignedPods) +} + +// deepCopy returns a deep copy of the pod group state data. +func (d *podGroupStateData) deepCopy() podGroupStateData { + return podGroupStateData{ + generation: d.generation, + allPods: maps.Clone(d.allPods), + unscheduledPods: d.unscheduledPods.Clone(), + assumedPods: maps.Clone(d.assumedPods), + assignedPods: d.assignedPods.Clone(), + } +} + +// unscheduledPodsMap returns all unscheduled pods for this pod group. +func (d *podGroupStateData) unscheduledPodsMap() map[string]*v1.Pod { + result := make(map[string]*v1.Pod, len(d.unscheduledPods)) + for podUID := range d.unscheduledPods { + pod := d.allPods[podUID] + result[pod.Name] = pod + } + return result +} + +// podGroupState holds the runtime state of a pod group. +type podGroupState struct { + lock sync.RWMutex + podGroupStateData +} + +func newPodGroupState() *podGroupState { + return &podGroupState{podGroupStateData: newPodGroupStateData()} +} + +// snapshot returns a deep copy of the live pod group state as an immutable snapshot. +// It must be called under the cache lock. +func (pgs *podGroupState) snapshot() *podGroupStateSnapshot { + return &podGroupStateSnapshot{podGroupStateData: pgs.podGroupStateData.deepCopy()} +} + +// empty returns true when the group contains no pods. +// It must be called under the cache lock. +func (pgs *podGroupState) empty() bool { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return pgs.podGroupStateData.empty() +} + +// addPod adds the pod to this group. +// Depending on the NodeName, it can insert the pod into either assignedPods or unscheduledPods. +// It must be called under the cache lock. +func (pgs *podGroupState) addPod(pod *v1.Pod) { + pgs.lock.Lock() + defer pgs.lock.Unlock() + + pgs.podGroupStateData.addPod(pod) +} + +// updatePod updates the pod in this group. +// In case of binding, it moves the pod to assignedPods. +// It must be called under the cache lock. +func (pgs *podGroupState) updatePod(oldPod, newPod *v1.Pod) { + pgs.lock.Lock() + defer pgs.lock.Unlock() + + pgs.podGroupStateData.updatePod(oldPod, newPod) +} + +// deletePod removes the pod from this pod group state. +// It must be called under the cache lock. +func (pgs *podGroupState) deletePod(podUID types.UID) { + pgs.lock.Lock() + defer pgs.lock.Unlock() + + pgs.podGroupStateData.deletePod(podUID) +} + +// assumePod marks a pod as assumed within the pod group state. +// It must be called under the cache lock. +func (pgs *podGroupState) assumePod(pod *v1.Pod) { + pgs.lock.Lock() + defer pgs.lock.Unlock() + + pgs.podGroupStateData.assumePod(pod) +} + +// forgetPod moves a pod back from the assumed state to unscheduled within the pod group state. +// It must be called under the cache lock. +func (pgs *podGroupState) forgetPod(podUID types.UID) { + pgs.lock.Lock() + defer pgs.lock.Unlock() + + pgs.podGroupStateData.forgetPod(podUID) +} + +// AllPods returns the UIDs of all pods known to the scheduler for this group. +func (pgs *podGroupState) AllPods() sets.Set[types.UID] { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return sets.KeySet(pgs.podGroupStateData.allPods) +} + +// AllPodsCount returns the number of all pods known to the scheduler for this group. +func (pgs *podGroupState) AllPodsCount() int { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return pgs.podGroupStateData.allPodsCount() +} + +// UnscheduledPods returns all pods that are unscheduled for this group, +// i.e., are neither assumed nor assigned. +// The returned map type corresponds to the argument of the PodActivator.Activate method. +func (pgs *podGroupState) UnscheduledPods() map[string]*v1.Pod { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return pgs.podGroupStateData.unscheduledPodsMap() +} + +// AssumedPods returns the UIDs of all pods for this group in the assumed state, +// i.e., that have passed the Reserve stage. +func (pgs *podGroupState) AssumedPods() sets.Set[types.UID] { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return sets.KeySet(pgs.podGroupStateData.assumedPods) +} + +// AssignedPods returns the UIDs of all pods already assigned (bound) for this group. +func (pgs *podGroupState) AssignedPods() sets.Set[types.UID] { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return pgs.podGroupStateData.assignedPods.Clone() +} + +// ScheduledPods returns the pods that are either assumed or assigned for this pod group. +func (pgs *podGroupState) ScheduledPods() []*v1.Pod { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return pgs.podGroupStateData.scheduledPods() +} + +// ScheduledPodsCount returns the number of pods for this group that are either assumed or assigned. +func (pgs *podGroupState) ScheduledPodsCount() int { + pgs.lock.RLock() + defer pgs.lock.RUnlock() + + return pgs.podGroupStateData.scheduledPodsCount() +} + +// podGroupStateSnapshot is an immutable, point-in-time copy of a podGroupState. +// It is taken before a pod group scheduling cycle and used to track states of pods +// during the cycle without modifying the live state of pods. +type podGroupStateSnapshot struct { + podGroupStateData +} + +// assumePod marks a pod within the pod group state snapshot as assumed. +func (s *podGroupStateSnapshot) assumePod(pod *v1.Pod) { + s.podGroupStateData.assumePod(pod) +} + +// forgetPod removes a pod from the assumed state within the snapshot. +func (s *podGroupStateSnapshot) forgetPod(podUID types.UID) { + s.podGroupStateData.forgetPod(podUID) +} + +// AllPods returns the UIDs of all pods known to the scheduler for this group. +func (s *podGroupStateSnapshot) AllPods() sets.Set[types.UID] { + return sets.KeySet(s.podGroupStateData.allPods) +} + +// UnscheduledPods returns all pods that are unscheduled for this group. +func (s *podGroupStateSnapshot) UnscheduledPods() map[string]*v1.Pod { + return s.podGroupStateData.unscheduledPodsMap() +} + +// AssumedPods returns the UIDs of all assumed pods for this group. +func (s *podGroupStateSnapshot) AssumedPods() sets.Set[types.UID] { + return sets.KeySet(s.podGroupStateData.assumedPods) +} + +// AssignedPods returns the UIDs of all assigned (bound) pods for this group. +func (s *podGroupStateSnapshot) AssignedPods() sets.Set[types.UID] { + return s.podGroupStateData.assignedPods +} + +// ScheduledPods returns the pods that are either assumed or assigned for this pod group. +func (s *podGroupStateSnapshot) ScheduledPods() []*v1.Pod { + return s.podGroupStateData.scheduledPods() +} + +// AllPodsCount returns the number of all pods known to the scheduler for this group. +func (s *podGroupStateSnapshot) AllPodsCount() int { + return s.podGroupStateData.allPodsCount() +} + +// ScheduledPodsCount returns the number of pods for this group that are either assumed or assigned. +func (s *podGroupStateSnapshot) ScheduledPodsCount() int { + return s.podGroupStateData.scheduledPodsCount() +} diff --git a/pkg/scheduler/backend/cache/podgroupstate_test.go b/pkg/scheduler/backend/cache/podgroupstate_test.go new file mode 100644 index 0000000000000..9274a82977c5c --- /dev/null +++ b/pkg/scheduler/backend/cache/podgroupstate_test.go @@ -0,0 +1,244 @@ +/* +Copyright 2025 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 cache + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +func TestPodGroupState_AssumeForget(t *testing.T) { + pgs := newPodGroupState() + pod := st.MakePod().Namespace("ns1").Name("p1").UID("p1").PodGroupName("pg1").Obj() + + pgs.addPod(pod) + if pgs.AssumedPods().Has(pod.UID) { + t.Fatal("AssumedPods should be initially empty") + } + if !pgs.unscheduledPods.Has(pod.UID) { + t.Fatal("Pod should be initially in UnscheduledPods") + } + + pgs.assumePod(pod) + if !pgs.AssumedPods().Has(pod.UID) { + t.Fatal("Pod should be in AssumedPods after AssumePod") + } + if pgs.unscheduledPods.Has(pod.UID) { + t.Fatal("UnscheduledPods should be empty after AssumePod") + } + + pgs.forgetPod(pod.UID) + if pgs.AssumedPods().Has(pod.UID) { + t.Fatal("Pod should not be in AssumedPods after ForgetPod") + } + if !pgs.unscheduledPods.Has(pod.UID) { + t.Fatal("Pod should be in UnscheduledPods after ForgetPod") + } +} + +func TestPodGroupState_Clone(t *testing.T) { + pgs := newPodGroupState() + + pod1 := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). + PodGroupName("pg").Obj() + pod2 := st.MakePod().Namespace("ns1").Name("p2").UID("p2"). + PodGroupName("pg").Obj() + + pgs.addPod(pod1) + pgs.addPod(pod2) + pgs.assumePod(pod2) + + snap := pgs.snapshot() + + // Clone has the same generation. + if snap.generation != pgs.generation { + t.Errorf("expected clone generation %d, got %d", pgs.generation, snap.generation) + } + + // Clone contains both pods. + if !snap.AllPods().Has(pod1.UID) || !snap.AllPods().Has(pod2.UID) { + t.Error("expected both pods in clone's AllPods") + } + + // Clone preserves pod1 as unscheduled. + if _, ok := snap.UnscheduledPods()[pod1.Name]; !ok { + t.Error("expected pod1 in clone's UnscheduledPods") + } + + // Clone preserves pod2 as assumed. + if !snap.AssumedPods().Has(pod2.UID) { + t.Error("expected pod2 in clone's AssumedPods") + } + + // Mutating the clone does not affect the original. + snap.assumePod(pod1) + if _, ok := pgs.assumedPods[pod1.UID]; ok { + t.Error("mutation to clone should not affect original's assumedPods") + } + + // Mutating the original does not affect the clone. + pod3 := st.MakePod().Namespace("ns1").Name("p3").UID("p3"). + PodGroupName("pg").Obj() + pgs.addPod(pod3) + if snap.AllPods().Has(pod3.UID) { + t.Error("mutation to original should not affect clone's AllPods") + } +} + +func TestPodGroupState_PodCounts(t *testing.T) { + pgs := newPodGroupState() + pod1 := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). + PodGroupName("pg1").Obj() + pod2 := st.MakePod().Namespace("ns1").Name("p2").UID("p2"). + PodGroupName("pg1").Obj() + pod3 := st.MakePod().Namespace("ns1").Name("p3").UID("p3").Node("node1"). + PodGroupName("pg1").Obj() + pod4 := st.MakePod().Namespace("ns1").Name("p4").UID("p4"). + PodGroupName("pg1").Obj() + + if count := pgs.AllPodsCount(); count != 0 { + t.Errorf("Expected AllPodsCount to be 0, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 0 { + t.Errorf("Expected ScheduledPodsCount to be 0, got %d", count) + } + + pgs.addPod(pod1) + pgs.addPod(pod2) + pgs.addPod(pod3) + + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 1 { + t.Errorf("Expected ScheduledPodsCount to be 1, got %d", count) + } + + // Assuming a pod should move it from unscheduled to assumed, increasing the count of scheduled pods. + pgs.assumePod(pod1) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 2 { + t.Errorf("Expected ScheduledPodsCount to be 2, got %d", count) + } + + // Assuming a pod that is already scheduled should not change the counts. + pgs.assumePod(pod3) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 2 { + t.Errorf("Expected ScheduledPodsCount to be 2, got %d", count) + } + + // Assuming a pod that is not in the state should not change the counts. + pgs.assumePod(pod4) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 2 { + t.Errorf("Expected ScheduledPodsCount to be 2, got %d", count) + } + + // Forgetting a pod that is already scheduled should not change the counts. + pgs.forgetPod(pod3.UID) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 2 { + t.Errorf("Expected ScheduledPodsCount to be 2, got %d", count) + } + + // Forgetting a pod that is in the assumed state should move it back to unscheduled, + // decreasing the count of scheduled pods. + pgs.forgetPod(pod1.UID) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 1 { + t.Errorf("Expected ScheduledPodsCount to be 1, got %d", count) + } + + // Forgetting a pod that is not assumed should not change the counts. + pgs.forgetPod(pod1.UID) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 1 { + t.Errorf("Expected ScheduledPodsCount to be 1, got %d", count) + } + + // Assuming a pod again should move it back to assumed, increasing the count of scheduled pods. + pgs.assumePod(pod2) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 2 { + t.Errorf("Expected ScheduledPodsCount to be 2, got %d", count) + } + + // Forgetting a pod that is not in the state should not change the counts. + pgs.forgetPod(pod4.UID) + if count := pgs.AllPodsCount(); count != 3 { + t.Errorf("Expected AllPodsCount to be 3, got %d", count) + } + if count := pgs.ScheduledPodsCount(); count != 2 { + t.Errorf("Expected ScheduledPodsCount to be 2, got %d", count) + } +} + +// TestPodGroupState_ScheduledPods tests that ScheduledPods returns pods that +// are currently either assumed or assigned altogether. +func TestPodGroupState_ScheduledPods(t *testing.T) { + + pgs := newPodGroupState() + unscheduledPod := st.MakePod().Namespace("ns").Name("p1").UID("p1"). + PodGroupName("pg").Obj() + assumedPod := st.MakePod().Namespace("ns").Name("p2").UID("p2"). + PodGroupName("pg").Obj() + assignedPod := st.MakePod().Namespace("ns").Name("p3").UID("p3").Node("node1"). + PodGroupName("pg").Obj() + + pgs.addPod(assignedPod) + pgs.addPod(unscheduledPod) + pgs.addPod(assumedPod) + + // Simulate the scheduler assuming the pod on a node. + assumedPodWithNodeName := assumedPod.DeepCopy() + assumedPodWithNodeName.Spec.NodeName = "node2" + + pgs.assumePod(assumedPodWithNodeName) + scheduledPods := pgs.ScheduledPods() + + snapshot := pgs.snapshot() + pgs.assumePod(unscheduledPod) + snapshotScheduledPods := snapshot.ScheduledPods() + + expectedScheduledPods := []*v1.Pod{assignedPod, assumedPodWithNodeName} + + if diff := cmp.Diff(expectedScheduledPods, scheduledPods); diff != "" { + t.Errorf("unexpected ScheduledPods result (-want,+got):\n%s", diff) + } + + if diff := cmp.Diff(expectedScheduledPods, snapshotScheduledPods); diff != "" { + t.Errorf("unexpected snapshot ScheduledPods result (-want,+got):\n%s", diff) + } +} diff --git a/pkg/scheduler/backend/cache/snapshot.go b/pkg/scheduler/backend/cache/snapshot.go index b8d34da321f12..d4b2b92fee161 100644 --- a/pkg/scheduler/backend/cache/snapshot.go +++ b/pkg/scheduler/backend/cache/snapshot.go @@ -20,11 +20,26 @@ import ( "fmt" v1 "k8s.io/api/core/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/klog/v2" fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/framework" ) +// placementNodes stores nodes that are present in the current placement. +// Placement is a limited set of nodes that is used in the pod group scheduling cycle. +type placementNodes struct { + // nodeInfoList contains the list of nodes in the placement. + // This is useful for quickly returning the entire list to the caller. + nodeInfoList []fwk.NodeInfo + // nodeInfoSet contains the set of nodes in the placement. + // This is useful for quickly checking if a node belongs the the placement. + nodeInfoSet sets.Set[string] +} + // Snapshot is a snapshot of cache NodeInfo and NodeTree order. The scheduler takes a // snapshot at the beginning of each scheduling cycle and uses it for its operations in that cycle. type Snapshot struct { @@ -41,6 +56,21 @@ type Snapshot struct { // keyed in the format "namespace/name". usedPVCSet sets.Set[string] generation int64 + // assumedPods maps a pod key to an assumed pod object during a single pod group scheduling cycle. + // This map should be emptied before the next cycle starts. + assumedPods map[string]*v1.Pod + // podGroupStates maps a pod group key to a snapshot of its state, used during a pod group scheduling cycle. + podGroupStates map[podGroupKey]*podGroupStateSnapshot + // placementNodes stores nodes that are present in the current placement. + // If placement is not set, this is nil. + // It should only be set in the pod group scheduling cycle, when checking if pod group can be scheduled within the placement. + // This field should be cleared once the pod group has been checked for the placement. + placementNodes *placementNodes + // genericWorkloadEnabled stores the GenericWorkload feature gate value. + genericWorkloadEnabled bool + // hasBackup holds information whether backup was performed and + // restore was not performed yet. + hasBackup bool } var _ fwk.SharedLister = &Snapshot{} @@ -48,8 +78,11 @@ var _ fwk.SharedLister = &Snapshot{} // NewEmptySnapshot initializes a Snapshot struct and returns it. func NewEmptySnapshot() *Snapshot { return &Snapshot{ - nodeInfoMap: make(map[string]*framework.NodeInfo), - usedPVCSet: sets.New[string](), + nodeInfoMap: make(map[string]*framework.NodeInfo), + usedPVCSet: sets.New[string](), + assumedPods: make(map[string]*v1.Pod), + podGroupStates: make(map[podGroupKey]*podGroupStateSnapshot), + genericWorkloadEnabled: utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload), } } @@ -75,10 +108,86 @@ func NewSnapshot(pods []*v1.Pod, nodes []*v1.Node) *Snapshot { s.havePodsWithAffinityNodeInfoList = havePodsWithAffinityNodeInfoList s.havePodsWithRequiredAntiAffinityNodeInfoList = havePodsWithRequiredAntiAffinityNodeInfoList s.usedPVCSet = createUsedPVCSet(pods) + if s.genericWorkloadEnabled { + s.podGroupStates = createPodGroupStates(pods) + } return s } +// createPodGroupStates builds the initial pod group state snapshot map from a list of pods. +func createPodGroupStates(pods []*v1.Pod) map[podGroupKey]*podGroupStateSnapshot { + podGroupStates := make(map[podGroupKey]*podGroupStateSnapshot) + for _, pod := range pods { + if pod.Spec.SchedulingGroup == nil { + continue + } + key := newPodGroupKey(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + pgs, ok := podGroupStates[key] + if !ok { + pgs = &podGroupStateSnapshot{podGroupStateData: newPodGroupStateData()} + podGroupStates[key] = pgs + } + pgs.addPod(pod) + } + return podGroupStates +} + +// RestoreSnapshot is a function that can be used to restore the snapshot to the state +// before the backup was taken. +type RestoreSnapshot func() + +// BackupSnapshot provides a way to temporarily backup the snapshot's state +// and returns a restore function. This is primarily used in workload-aware +// preemption to simulate pod group preemption by mutating deep copies of NodeInfos. +// Backups cannot be stacked, i.e., only one backup can be made without restoring +// the snapshot first. +// Restoring backup when the placement is set is not supported and can lead to +// undefined behavior. +func (s *Snapshot) BackupSnapshot() (RestoreSnapshot, error) { + if s.hasBackup { + return nil, fmt.Errorf("cannot stack backups") + } + origNodeInfoMap := s.nodeInfoMap + origNodeInfoList := s.nodeInfoList + origHavePodsWithAffinityNodeInfoList := s.havePodsWithAffinityNodeInfoList + origHavePodsWithRequiredAntiAffinityNodeInfoList := s.havePodsWithRequiredAntiAffinityNodeInfoList + + clonedNodeInfoMap := make(map[string]*framework.NodeInfo, len(s.nodeInfoMap)) + for k, v := range s.nodeInfoMap { + clonedNodeInfoMap[k] = v.Snapshot().(*framework.NodeInfo) + } + + clonedNodeInfoList := make([]fwk.NodeInfo, 0, len(clonedNodeInfoMap)) + clonedHavePodsWithAffinityNodeInfoList := make([]fwk.NodeInfo, 0, len(clonedNodeInfoMap)) + clonedHavePodsWithRequiredAntiAffinityNodeInfoList := make([]fwk.NodeInfo, 0, len(clonedNodeInfoMap)) + + for _, v := range s.nodeInfoList { + clonedNode := clonedNodeInfoMap[v.Node().Name] + clonedNodeInfoList = append(clonedNodeInfoList, clonedNode) + if len(clonedNode.PodsWithAffinity) > 0 { + clonedHavePodsWithAffinityNodeInfoList = append(clonedHavePodsWithAffinityNodeInfoList, clonedNode) + } + if len(clonedNode.PodsWithRequiredAntiAffinity) > 0 { + clonedHavePodsWithRequiredAntiAffinityNodeInfoList = append(clonedHavePodsWithRequiredAntiAffinityNodeInfoList, clonedNode) + } + } + + s.hasBackup = true + s.nodeInfoMap = clonedNodeInfoMap + s.nodeInfoList = clonedNodeInfoList + s.havePodsWithAffinityNodeInfoList = clonedHavePodsWithAffinityNodeInfoList + s.havePodsWithRequiredAntiAffinityNodeInfoList = clonedHavePodsWithRequiredAntiAffinityNodeInfoList + + return func() { + s.hasBackup = false + s.nodeInfoMap = origNodeInfoMap + s.nodeInfoList = origNodeInfoList + s.havePodsWithAffinityNodeInfoList = origHavePodsWithAffinityNodeInfoList + s.havePodsWithRequiredAntiAffinityNodeInfoList = origHavePodsWithRequiredAntiAffinityNodeInfoList + }, nil +} + // createNodeInfoMap obtains a list of pods and pivots that list into a map // where the keys are node names and the values are the aggregated information // for that node. @@ -165,8 +274,34 @@ func (s *Snapshot) StorageInfos() fwk.StorageInfoLister { return s } -// NumNodes returns the number of nodes in the snapshot. -func (s *Snapshot) NumNodes() int { +// PodGroupStates returns a PodGroupStateLister. +func (s *Snapshot) PodGroupStates() fwk.PodGroupStateLister { + return &podGroupStateSnapshotLister{podGroupStates: s.podGroupStates} +} + +var _ fwk.PodGroupStateLister = &podGroupStateSnapshotLister{} + +type podGroupStateSnapshotLister struct { + podGroupStates map[podGroupKey]*podGroupStateSnapshot +} + +// Get returns the pod group state from the snapshot for the given pod group. +func (l *podGroupStateSnapshotLister) Get(namespace string, podGroupName string) (fwk.PodGroupState, error) { + key := newPodGroupKey(namespace, podGroupName) + state, ok := l.podGroupStates[key] + if !ok { + return nil, fmt.Errorf("pod group state not found for pod group %s", key) + } + return state, nil +} + +// NumNodesInPlacement returns the number of nodes in the snapshot for the current placement. +// If no placement is set, it returns the number of nodes in the snapshot. +// This function is not thread safe so it should be executed when no other routines can write to the snapshot. +func (s *Snapshot) NumNodesInPlacement() int { + if s.placementNodes != nil { + return len(s.placementNodes.nodeInfoList) + } return len(s.nodeInfoList) } @@ -197,3 +332,151 @@ func (s *Snapshot) Get(nodeName string) (fwk.NodeInfo, error) { func (s *Snapshot) IsPVCUsedByPods(key string) bool { return s.usedPVCSet.Has(key) } + +// AssumePod assumes a given pod in the snapshot. +// ForgetPod should be called on the snapshot before syncing it with the cache. +// This function is not thread safe, so it should be executed when no other routines can write/read from the snapshot. +func (s *Snapshot) AssumePod(podInfo *framework.PodInfo) error { + pod := podInfo.Pod + key, err := framework.GetPodKey(pod) + if err != nil { + return err + } + nodeInfo, ok := s.nodeInfoMap[pod.Spec.NodeName] + if !ok { + nodeInfo = framework.NewNodeInfo() + s.nodeInfoMap[pod.Spec.NodeName] = nodeInfo + } + // Calling AddPodInfo increases the Generation number of the nodeInfo. + // Since this operation only affects the snapshot, + // we should keep the old number to remain consistent with the cached value. + oldGeneration := nodeInfo.Generation + nodeInfo.AddPodInfo(podInfo) + nodeInfo.Generation = oldGeneration + s.assumedPods[key] = pod + // Update the pod group state in the snapshot if the pod belongs to a pod group. + if !s.genericWorkloadEnabled || pod.Spec.SchedulingGroup == nil { + return nil + } + pgKey := newPodGroupKey(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + if pgs, ok := s.podGroupStates[pgKey]; ok { + pgs.assumePod(pod) + } + return nil +} + +// ForgetPod forgets a given pod from the snapshot. +// This function is not thread safe, so it should be executed when no other routines can write/read from the snapshot. +func (s *Snapshot) ForgetPod(logger klog.Logger, pod *v1.Pod) error { + key, err := framework.GetPodKey(pod) + if err != nil { + return err + } + assumedPod, ok := s.assumedPods[key] + if !ok { + return fmt.Errorf("assumed pod %q not found in the snapshot", key) + } + delete(s.assumedPods, key) + nodeName := assumedPod.Spec.NodeName + if nodeInfo, ok := s.nodeInfoMap[nodeName]; ok { + // Calling RemovePod increases the Generation number of the nodeInfo. + // Since this operation only affects the snapshot, + // we should keep the old number to remain consistent with the cached value. + oldGeneration := nodeInfo.Generation + err := nodeInfo.RemovePod(logger, pod) + if err != nil { + return err + } + nodeInfo.Generation = oldGeneration + if len(nodeInfo.Pods) == 0 && nodeInfo.Node() == nil { + delete(s.nodeInfoMap, nodeName) + } + } + // Update the pod group state in the snapshot if the pod belongs to a pod group. + if !s.genericWorkloadEnabled || pod.Spec.SchedulingGroup == nil { + return nil + } + pgKey := newPodGroupKey(assumedPod.Namespace, *assumedPod.Spec.SchedulingGroup.PodGroupName) + if pgs, ok := s.podGroupStates[pgKey]; ok { + pgs.forgetPod(assumedPod.UID) + } + return nil +} + +// forgetAllAssumedPods forgets all assumed pods from the snapshot. +// This function is not thread safe, so it should be executed when no other routines can write/read from the snapshot. +func (s *Snapshot) forgetAllAssumedPods(logger klog.Logger) { + if len(s.assumedPods) == 0 { + return + } + for _, pod := range s.assumedPods { + err := s.ForgetPod(logger, pod) + if err != nil { + utilruntime.HandleErrorWithLogger(logger, err, "Failed to forget assumed pod") + } + } + logger.Error(nil, "Found assumed pods in the snapshot that were not forgotten", "assumedPodsCount", len(s.assumedPods)) +} + +// AssumePlacement sets placement context in the snapshot. +// The snapshot should not be updated if a placement is assumed. +// The placement should be unset with ForgetPlacement once it's no longer needed. +// This function should only be used by the scheduler to limit the node candidates for scheduling. +// This function is not thread safe, so it should be executed when no other routines can write/read from the snapshot. +func (s *Snapshot) AssumePlacement(placement *fwk.Placement) error { + if len(placement.Nodes) == len(s.nodeInfoList) { + // All nodes in placement, meaning we can treat it the same as no placement and avoid copying the buffer. + s.ForgetPlacement() + return nil + } + s.placementNodes = &placementNodes{ + nodeInfoList: placement.Nodes, + nodeInfoSet: sets.New[string](), + } + for _, node := range placement.Nodes { + snapshotNode, ok := s.nodeInfoMap[node.Node().Name] + if !ok { + s.ForgetPlacement() + return fmt.Errorf("node %s in placement is not present in snapshot", node.Node().Name) + } + if snapshotNode != node { + s.ForgetPlacement() + return fmt.Errorf("node %s in placement is not the same instance as in the snapshot", node.Node().Name) + } + s.placementNodes.nodeInfoSet.Insert(node.Node().Name) + } + return nil +} + +// ForgetPlacement clears placement. +// This function should only be used by the scheduler once the pods have been considered for that placement. +// This function is not thread safe, so it should be executed when no other routines can write/read from the snapshot. +func (s *Snapshot) ForgetPlacement() { + s.placementNodes = nil +} + +// GetNodeInPlacement returns the NodeInfo of the given node name within the current placement. +// If no placement is set, this is equivalent to Get. +// Placement is typically set in the pod group scheduling cycle. +// This function should only be used by the scheduler to limit the node candidates for scheduling. +// Plugins normally do not need this information. +// This function is not thread safe so it should be executed when no other routines can write to the snapshot. +func (s *Snapshot) GetNodeInPlacement(nodeName string) (fwk.NodeInfo, error) { + if s.placementNodes == nil || s.placementNodes.nodeInfoSet.Has(nodeName) { + return s.Get(nodeName) + } + return nil, fmt.Errorf("node %q not found in placement", nodeName) +} + +// ListNodesInPlacement returns the list of nodes in the snapshot within the current placement. +// If no placement is set, this is equivalent to List. +// Placement is typically set in the pod group scheduling cycle. +// This function should only be used by the scheduler to limit the node candidates for scheduling. +// Plugins normally do not need this information. +// This function is not thread safe so it should be executed when no other routines can write to the snapshot. +func (s *Snapshot) ListNodesInPlacement() ([]fwk.NodeInfo, error) { + if s.placementNodes == nil { + return s.List() + } + return s.placementNodes.nodeInfoList, nil +} diff --git a/pkg/scheduler/backend/cache/snapshot_test.go b/pkg/scheduler/backend/cache/snapshot_test.go index 95b9ae9666ea1..5907e9534beeb 100644 --- a/pkg/scheduler/backend/cache/snapshot_test.go +++ b/pkg/scheduler/backend/cache/snapshot_test.go @@ -27,6 +27,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework" st "k8s.io/kubernetes/pkg/scheduler/testing" @@ -396,8 +398,8 @@ func TestNewSnapshot(t *testing.T) { t.Run(test.name, func(t *testing.T) { snapshot := NewSnapshot(test.pods, test.nodes) - if test.expectedNumNodes != snapshot.NumNodes() { - t.Errorf("unexpected number of nodes, want: %v, got: %v", test.expectedNumNodes, snapshot.NumNodes()) + if test.expectedNumNodes != snapshot.NumNodesInPlacement() { + t.Errorf("unexpected number of nodes, want: %v, got: %v", test.expectedNumNodes, snapshot.NumNodesInPlacement()) } for i, node := range test.nodes { @@ -443,3 +445,500 @@ func TestNewSnapshot(t *testing.T) { }) } } + +func TestSnapshot_AssumeForget(t *testing.T) { + node1 := st.MakeNode().Name("node-1").Obj() + node2 := st.MakeNode().Name("node-2").Obj() + + pod1 := st.MakePod().Name("pod-1").UID("pod-1").Node("node-1").Obj() + pod2 := st.MakePod().Name("pod-2").UID("pod-2").Node("node-1").Obj() + pod3 := st.MakePod().Name("pod-3").UID("pod-3").Node("node-2").Obj() + podOnWrongNode := st.MakePod().Name("pod-x").UID("pod-x").Node("node-x").Obj() + + pod2Info, _ := framework.NewPodInfo(pod2) + pod3Info, _ := framework.NewPodInfo(pod3) + podOnWrongNodeInfo, _ := framework.NewPodInfo(podOnWrongNode) + + tests := []struct { + name string + initialPods []*v1.Pod + initialNodes []*v1.Node + podsToAssume []*framework.PodInfo + podsToForget []*v1.Pod + forgetAll bool + expectAssumeErr bool + expectForgetErr bool + expectedPodsOnNodes map[string]sets.Set[string] + }{ + { + name: "assume a pod successfully", + initialPods: []*v1.Pod{pod1}, + initialNodes: []*v1.Node{node1, node2}, + podsToAssume: []*framework.PodInfo{pod2Info, pod3Info}, + expectedPodsOnNodes: map[string]sets.Set[string]{ + "node-1": sets.New("pod-1", "pod-2"), + "node-2": sets.New("pod-3"), + }, + }, + { + name: "assume a pod on a non-existing node", + initialPods: []*v1.Pod{pod1}, + initialNodes: []*v1.Node{node1}, + podsToAssume: []*framework.PodInfo{podOnWrongNodeInfo}, + }, + { + name: "forget a pod successfully", + initialPods: []*v1.Pod{pod1}, + initialNodes: []*v1.Node{node1, node2}, + podsToAssume: []*framework.PodInfo{pod2Info, pod3Info}, + podsToForget: []*v1.Pod{pod2}, + expectedPodsOnNodes: map[string]sets.Set[string]{ + "node-1": sets.New("pod-1"), + "node-2": sets.New("pod-3"), + }, + }, + { + name: "forget a pod that was not assumed", + initialPods: []*v1.Pod{pod1}, + initialNodes: []*v1.Node{node1, node2}, + podsToForget: []*v1.Pod{pod2}, + expectForgetErr: true, + }, + { + name: "forget all assumed pods", + initialPods: []*v1.Pod{pod1}, + initialNodes: []*v1.Node{node1, node2}, + podsToAssume: []*framework.PodInfo{pod2Info, pod3Info}, + forgetAll: true, + expectedPodsOnNodes: map[string]sets.Set[string]{ + "node-1": sets.New("pod-1"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + + snapshot := NewSnapshot(tt.initialPods, tt.initialNodes) + + for _, p := range tt.podsToAssume { + err := snapshot.AssumePod(p) + if tt.expectAssumeErr { + if err == nil { + t.Fatalf("Expected AssumePod to fail but is hasn't") + } + return + } + if err != nil { + t.Fatalf("Failed to assume pod %q: %v", p.Pod.Name, err) + } + } + + if tt.forgetAll { + snapshot.forgetAllAssumedPods(logger) + if len(snapshot.assumedPods) != 0 { + t.Errorf("Expected assumedPods to be empty, but has %d pods", len(snapshot.assumedPods)) + } + } else { + for _, p := range tt.podsToForget { + err := snapshot.ForgetPod(logger, p) + if tt.expectForgetErr { + if err == nil { + t.Fatalf("Expected ForgetPod to fail but is hasn't") + } + return + } + if err != nil { + t.Fatalf("Failed to forget pod %q: %v", p.Name, err) + } + } + } + + nodeInfos, err := snapshot.List() + if err != nil { + t.Fatalf("Failed to list snapshotted nodes: %v", err) + } + for nodeName, expectedPods := range tt.expectedPodsOnNodes { + nodeInfo, err := snapshot.Get(nodeName) + if err != nil { + t.Fatalf("Failed to get node %q from snapshot: %v", nodeName, err) + } + gotPods := nodeInfo.GetPods() + if len(expectedPods) != len(gotPods) { + t.Errorf("Unexpected number of pods on node %q: want %d, got %d", nodeName, len(expectedPods), len(gotPods)) + } + for _, p := range nodeInfo.GetPods() { + podName := p.GetPod().Name + if !expectedPods.Has(podName) { + t.Errorf("Unexpected pod %q on node %q", podName, nodeName) + } + } + // Safety check that nodeInfoList's pods were also updated. + for _, nInfo := range nodeInfos { + if nInfo.Node().Name == nodeInfo.Node().Name { + if diff := cmp.Diff(nodeInfo.GetPods(), nInfo.GetPods(), cmpopts.IgnoreUnexported(framework.PodInfo{})); diff != "" { + t.Errorf("Unexpected nodeInfo state in nodeInfoList (-want +got):\n%s", diff) + } + } + } + } + }) + } +} + +func TestSnapshot_Placement(t *testing.T) { + tt := []struct { + name string + initialNodes []string + placementNodes []string + testFn func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) + }{ + { + name: "When placement is not set, nodes in placement are same as nodes in snapshot", + initialNodes: []string{"n1", "n2", "n3"}, + placementNodes: []string{}, // unused + testFn: func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) { + // We intentionally don't use snapshot.AssumePlacement here + numNodes := snapshot.NumNodesInPlacement() + if numNodes != 3 { + t.Errorf("unexpected number of nodes from NumNodesInPlacement, want: %v, got: %v", 3, numNodes) + } + nodes, err := snapshot.ListNodesInPlacement() + if err != nil { + t.Fatalf("unexpected error from ListNodesInPlacement %v", err) + } + if len(nodes) != 3 { + t.Errorf("unexpected number of nodes from ListNodesInPlacement, want: %v, got: %v", 3, len(nodes)) + } + _, err = snapshot.GetNodeInPlacement("n1") + if err != nil { + t.Errorf("expected GetNodeInPlacement to find node but instead got unexpected error %v", err) + } + }, + }, + { + name: "When placement is set, nodes in placement are the nodes from the provided placement", + initialNodes: []string{"n1", "n2", "n3"}, + placementNodes: []string{"n2", "n3"}, + testFn: func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) { + err := snapshot.AssumePlacement(placement) + if err != nil { + t.Fatalf("got unexpected error from AssumePlacement %v", err) + } + numNodes := snapshot.NumNodesInPlacement() + if numNodes != 2 { + t.Errorf("unexpected number of nodes from NumNodesInPlacement, want: %v, got: %v", 2, numNodes) + } + nodes, err := snapshot.ListNodesInPlacement() + if err != nil { + t.Fatalf("unexpected error from ListNodesInPlacement %v", err) + } + if len(nodes) != 2 { + t.Errorf("unexpected number of nodes from ListNodesInPlacement, want: %v, got: %v", 2, len(nodes)) + } + _, err = snapshot.GetNodeInPlacement("n1") + if err == nil { + t.Errorf("expected GetNodeInPlacement not to find node but instead got nil error") + } + _, err = snapshot.GetNodeInPlacement("n3") + if err != nil { + t.Errorf("expected GetNodeInPlacement to find node but instead got unexpected error %v", err) + } + }, + }, + { + name: "When placement is set, List and Get are not affected and still use snapshot nodes", + initialNodes: []string{"n1", "n2", "n3"}, + placementNodes: []string{"n2", "n3"}, + testFn: func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) { + err := snapshot.AssumePlacement(placement) + if err != nil { + t.Fatalf("got unexpected error from AssumePlacement %v", err) + } + nodes, err := snapshot.List() + if err != nil { + t.Fatalf("unexpected error from List %v", err) + } + if len(nodes) != 3 { + t.Errorf("unexpected number of nodes from ListNodesInPlacement, want: %v, got: %v", 3, len(nodes)) + } + _, err = snapshot.Get("n1") + if err != nil { + t.Errorf("expected Get to find node but instead got unexpected error %v", err) + } + }, + }, + { + name: "When placement is cleared through ForgetPlacement, nodes in placement are same as nodes in snapshot", + initialNodes: []string{"n1", "n2", "n3"}, + placementNodes: []string{"n2", "n3"}, + testFn: func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) { + err := snapshot.AssumePlacement(placement) + if err != nil { + t.Fatalf("got unexpected error from AssumePlacement %v", err) + } + snapshot.ForgetPlacement() + numNodes := snapshot.NumNodesInPlacement() + if numNodes != 3 { + t.Errorf("unexpected number of nodes from NumNodesInPlacement, want: %v, got: %v", 3, numNodes) + } + nodes, err := snapshot.ListNodesInPlacement() + if err != nil { + t.Fatalf("unexpected error from ListNodesInPlacement %v", err) + } + if len(nodes) != 3 { + t.Errorf("unexpected number of nodes from ListNodesInPlacement, want: %v, got: %v", 3, len(nodes)) + } + _, err = snapshot.GetNodeInPlacement("n1") + if err != nil { + t.Errorf("expected GetNodeInPlacement to find node but instead got unexpected error %v", err) + } + }, + }, + { + name: "When placement uses nodes that point to a different instance than snapshot, AssumePlacement returns error and placement is not set", + initialNodes: []string{"n1", "n2", "n3"}, + placementNodes: []string{"n2"}, + testFn: func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) { + placement.Nodes[0] = placement.Nodes[0].Snapshot() + err := snapshot.AssumePlacement(placement) + if err == nil { + t.Fatalf("expected AssumePlacement to return error due to no match between placement and snapshot node instance but got nil") + } + // ensure the placement is cleared + numNodes := snapshot.NumNodesInPlacement() + if numNodes != 3 { + t.Errorf("unexpected number of nodes from NumNodesInPlacement, want: %v, got: %v", 3, numNodes) + } + nodes, err := snapshot.ListNodesInPlacement() + if err != nil { + t.Fatalf("unexpected error from ListNodesInPlacement %v", err) + } + if len(nodes) != 3 { + t.Errorf("unexpected number of nodes from ListNodesInPlacement, want: %v, got: %v", 3, len(nodes)) + } + _, err = snapshot.GetNodeInPlacement("n1") + if err != nil { + t.Errorf("expected GetNodeInPlacement to find node but instead got unexpected error %v", err) + } + }, + }, + { + name: "When placement is set, the effect of AssumePod is visible in nodes in placement", + initialNodes: []string{"n1", "n2", "n3"}, + placementNodes: []string{"n2"}, + testFn: func(t *testing.T, snapshot *Snapshot, placement *fwk.Placement) { + err := snapshot.AssumePlacement(placement) + if err != nil { + t.Fatalf("unexpected error from AssumePlacement %v", err) + } + pod := st.MakePod().Name("pod-1").UID("pod-1").Node("n2").Obj() + podInfo, err := framework.NewPodInfo(pod) + if err != nil { + t.Fatalf("unexpected error from NewPodInfo %v", err) + } + err = snapshot.AssumePod(podInfo) + if err != nil { + t.Fatalf("unexpected error from AssumePod: %v", err) + } + nodes, err := snapshot.ListNodesInPlacement() + if err != nil { + t.Fatalf("unexpected error from ListNodesInPlacement %v", err) + } + if len(nodes) != 1 { + t.Fatalf("unexpected number of nodes from ListNodesInPlacement, want: %v, got: %v", 1, len(nodes)) + } + node, err := snapshot.GetNodeInPlacement("n2") + if err != nil { + t.Fatalf("expected GetNodeInPlacement to find node but instead got unexpected error %v", err) + } + if nodes[0] != node { + t.Errorf("node from ListNodesInPlacement is not the same instance as from GetNodeInPlacement") + } + snapshotNode, err := snapshot.Get("n2") + if err != nil { + t.Fatalf("expected Get to find node but instead of unexpected error: %v", err) + } + if snapshotNode != node { + t.Fatalf("node from Get is not the same instance as from GetNodeInPlacement") + } + pods := node.GetPods() + if len(pods) != 1 { + t.Fatalf("unexpected number of pods on node, want: %v, got: %v", 1, len(pods)) + } + if diff := cmp.Diff(podInfo, pods[0], cmpopts.IgnoreUnexported(framework.PodInfo{})); diff != "" { + t.Errorf("pod from placement is not equal to pod from snapshot (-want +got):\n%s", diff) + } + }, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + initialNodes := []*v1.Node{} + for _, n := range tc.initialNodes { + initialNodes = append(initialNodes, &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: n}}) + } + + snapshot := NewSnapshot([]*v1.Pod{}, initialNodes) + placementNodes := []fwk.NodeInfo{} + for _, n := range tc.placementNodes { + node, ok := snapshot.nodeInfoMap[n] + if !ok { + node = framework.NewNodeInfo() + node.SetNode(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: n}}) + } + placementNodes = append(placementNodes, node) + } + + tc.testFn(t, snapshot, &fwk.Placement{ + Nodes: placementNodes, + }) + }) + } +} + +func TestSnapshot_BackupRestore(t *testing.T) { + podWithAffinity := st.MakePod().Name("p-aff").Namespace("ns").UID("p-aff").PodAffinity("key", &metav1.LabelSelector{MatchLabels: map[string]string{"key": "value"}}, st.PodAffinityWithRequiredReq).Node("node-1").Obj() + podWithAntiAffinity := st.MakePod().Name("p-anti").Namespace("ns").UID("p-anti").PodAntiAffinity("key", &metav1.LabelSelector{MatchLabels: map[string]string{"key": "value"}}, st.PodAntiAffinityWithRequiredReq).Node("node-1").Obj() + + tests := []struct { + name string + initialPods []*v1.Pod + initialNodes []*v1.Node + modifySnapshot func(klog.Logger, *Snapshot) + }{ + { + name: "Modify NodeInfo (Add Pod)", + initialNodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "node-1"}}, + }, + modifySnapshot: func(_ klog.Logger, s *Snapshot) { + node := s.nodeInfoMap["node-1"] + pod := st.MakePod().Name("p1").Node("node-1").Obj() + node.AddPod(pod) + }, + }, + { + name: "Modify havePodsWithAffinityNodeInfoList (Add)", + initialNodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "node-1", Labels: map[string]string{"key": "value"}}}, + }, + modifySnapshot: func(_ klog.Logger, s *Snapshot) { + node := s.nodeInfoMap["node-1"] + node.AddPod(podWithAffinity) + s.havePodsWithAffinityNodeInfoList = append(s.havePodsWithAffinityNodeInfoList, node) + }, + }, + { + name: "Modify havePodsWithRequiredAntiAffinityNodeInfoList (Remove)", + initialPods: []*v1.Pod{ + podWithAntiAffinity, + }, + initialNodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "node-1", Labels: map[string]string{"key": "value"}}}, + }, + modifySnapshot: func(logger klog.Logger, s *Snapshot) { + node := s.nodeInfoMap["node-1"] + if err := node.RemovePod(logger, podWithAntiAffinity); err != nil { + t.Fatalf("Failed to remove pod: %v", err) + } + s.havePodsWithRequiredAntiAffinityNodeInfoList = []fwk.NodeInfo{} + }, + }, + { + name: "Modify nodeInfoList directly", + initialNodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "node-1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "node-2"}}, + }, + modifySnapshot: func(_ klog.Logger, s *Snapshot) { + // Reverse the list + s.nodeInfoList[0], s.nodeInfoList[1] = s.nodeInfoList[1], s.nodeInfoList[0] + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + s := NewSnapshot(tt.initialPods, tt.initialNodes) + + // Store original state for deep verification + origNodeInfoMap, origNodeInfoList, origAffinityList, origAntiAffinityList := simplifySnapshot(s) + + restore, err := s.BackupSnapshot() + if err != nil { + t.Fatalf("failed to prepare a backup") + } + tt.modifySnapshot(logger, s) + restore() + + // Get state after for verification + postRestoreNodeInfoMap, postRestoreNodeInfoList, postRestoreAffinityList, postRestoreAntiAffinityList := simplifySnapshot(s) + + if cmp.Diff(origNodeInfoMap, postRestoreNodeInfoMap) != "" { + t.Errorf("nodeInfoMap mismatch: want %v, got %v", origNodeInfoMap, postRestoreNodeInfoMap) + } + if cmp.Diff(origNodeInfoList, postRestoreNodeInfoList) != "" { + t.Errorf("nodeInfoList mismatch: want %v, got %v", origNodeInfoList, postRestoreNodeInfoList) + } + if cmp.Diff(origAffinityList, postRestoreAffinityList) != "" { + t.Errorf("havePodsWithAffinityNodeInfoList mismatch: want %v, got %v", origAffinityList, postRestoreAffinityList) + } + if cmp.Diff(origAntiAffinityList, postRestoreAntiAffinityList) != "" { + t.Errorf("havePodsWithRequiredAntiAffinityNodeInfoList mismatch: want %v, got %v", origAntiAffinityList, postRestoreAntiAffinityList) + } + }) + } +} + +// simplifySnapshot for comparison in unit tests +func simplifySnapshot(s *Snapshot) (map[string][]string, []string, []string, []string) { + nodeInfoMap := make(map[string][]string) + var nodeInfoList []string + var affinityList []string + var antiAffinityList []string + for _, nodeInfo := range s.nodeInfoMap { + for _, p := range nodeInfo.GetPods() { + nodeInfoMap[nodeInfo.Node().Name] = append(nodeInfoMap[nodeInfo.Node().Name], p.GetPod().Name) + } + } + for _, nodeInfo := range s.nodeInfoList { + nodeInfoList = append(nodeInfoList, nodeInfo.Node().Name) + } + for _, nodeInfo := range s.havePodsWithAffinityNodeInfoList { + affinityList = append(affinityList, nodeInfo.Node().Name) + } + for _, nodeInfo := range s.havePodsWithRequiredAntiAffinityNodeInfoList { + antiAffinityList = append(antiAffinityList, nodeInfo.Node().Name) + } + return nodeInfoMap, nodeInfoList, affinityList, antiAffinityList +} + +func TestSnapshot_MultipleBackups(t *testing.T) { + s := NewSnapshot(nil, nil) + + restore, err := s.BackupSnapshot() + if err != nil { + t.Fatalf("failed to prepare a backup: %v", err) + } + + _, err = s.BackupSnapshot() + if err == nil { + t.Fatalf("expected error when stacking backups, got nil") + } + + expectedErr := "cannot stack backups" + if err.Error() != expectedErr { + t.Errorf("expected error %q, got %q", expectedErr, err.Error()) + } + + // Restore the previous backup, and now it should work again + restore() + + _, err = s.BackupSnapshot() + if err != nil { + t.Fatalf("failed to prepare a backup after restoring: %v", err) + } +} diff --git a/pkg/scheduler/backend/queue/active_queue.go b/pkg/scheduler/backend/queue/active_queue.go index de722833d4446..060ee8c3640af 100644 --- a/pkg/scheduler/backend/queue/active_queue.go +++ b/pkg/scheduler/backend/queue/active_queue.go @@ -43,6 +43,7 @@ type activeQueuer interface { len() int has(pInfo *framework.QueuedPodInfo) bool + movePodToInFlight(pInfo *framework.QueuedPodInfo) error listInFlightEvents() []interface{} listInFlightPods() []*v1.Pod clusterEventsForPod(logger klog.Logger, pInfo *framework.QueuedPodInfo) ([]*clusterEvent, error) @@ -266,6 +267,39 @@ func (aq *activeQueue) delete(pInfo *framework.QueuedPodInfo) error { return aq.queue.Delete(pInfo) } +// movePodToInFlight moves the pod to the in-flight state. +// It assumes the pod is already popped from the queue. +// It updates the metrics and in-flight tracking. +func (aq *activeQueue) movePodToInFlight(pInfo *framework.QueuedPodInfo) error { + aq.lock.Lock() + defer aq.lock.Unlock() + return aq.unlockedMovePodToInFlight(pInfo) +} + +// unlockedMovePodToInFlight moves the pod to the in-flight state. +// This method should be called under the lock. +func (aq *activeQueue) unlockedMovePodToInFlight(pInfo *framework.QueuedPodInfo) error { + pInfo.Attempts++ + // In flight, no concurrent events yet. + if aq.isSchedulingQueueHintEnabled { + // If the pod is already in the map, we shouldn't overwrite the inFlightPods otherwise it'd lead to a memory leak. + // https://github.com/kubernetes/kubernetes/pull/127016 + if _, ok := aq.inFlightPods[pInfo.Pod.UID]; ok { + return fmt.Errorf("the same pod is tracked in multiple places in the scheduler: %s", klog.KObj(pInfo.Pod)) + } + + aq.metricsRecorder.ObserveInFlightEventsAsync(metrics.PodPoppedInFlightEvent, 1, false) + aq.inFlightPods[pInfo.Pod.UID] = aq.inFlightEvents.PushBack(pInfo.Pod) + } + aq.schedCycle++ + + // Update metrics for unschedulable plugins. + for plugin := range pInfo.UnschedulablePlugins.Union(pInfo.PendingPlugins) { + metrics.UnschedulableReason(plugin, pInfo.Pod.Spec.SchedulerName).Dec() + } + return nil +} + // pop removes the head of the queue and returns it. // It blocks if the queue is empty and waits until a new item is added to the queue. // It increments scheduling cycle when a pod is popped. @@ -305,32 +339,14 @@ func (aq *activeQueue) unlockedPop(logger klog.Logger) (*framework.QueuedPodInfo } metrics.SchedulerQueueIncomingPods.WithLabelValues("active", framework.PopFromBackoffQ).Inc() } - pInfo.Attempts++ - // In flight, no concurrent events yet. - if aq.isSchedulingQueueHintEnabled { - // If the pod is already in the map, we shouldn't overwrite the inFlightPods otherwise it'd lead to a memory leak. - // https://github.com/kubernetes/kubernetes/pull/127016 - if _, ok := aq.inFlightPods[pInfo.Pod.UID]; ok { - // Just report it as an error, but no need to stop the scheduler - // because it likely doesn't cause any visible issues from the scheduling perspective. - utilruntime.HandleErrorWithLogger(logger, nil, "The same pod is tracked in multiple places in the scheduler, and just discard it", "pod", klog.KObj(pInfo.Pod)) - // Just ignore/discard this duplicated pod and try to pop the next one. - return aq.unlockedPop(logger) - } - - aq.metricsRecorder.ObserveInFlightEventsAsync(metrics.PodPoppedInFlightEvent, 1, false) - aq.inFlightPods[pInfo.Pod.UID] = aq.inFlightEvents.PushBack(pInfo.Pod) - } - aq.schedCycle++ - - // Update metrics and reset the set of unschedulable plugins for the next attempt. - for plugin := range pInfo.UnschedulablePlugins.Union(pInfo.PendingPlugins) { - metrics.UnschedulableReason(plugin, pInfo.Pod.Spec.SchedulerName).Dec() + err = aq.unlockedMovePodToInFlight(pInfo) + if err != nil { + // Just report it as an error, but no need to stop the scheduler + // because it likely doesn't cause any visible issues from the scheduling perspective. + utilruntime.HandleErrorWithLogger(logger, err, "Discarding the popped pod") + // Just ignore/discard this duplicated pod and try to pop the next one. + return aq.unlockedPop(logger) } - pInfo.UnschedulablePlugins.Clear() - pInfo.PendingPlugins.Clear() - pInfo.GatingPlugin = "" - pInfo.GatingPluginEvents = nil return pInfo, nil } diff --git a/pkg/scheduler/backend/queue/scheduling_queue.go b/pkg/scheduler/backend/queue/scheduling_queue.go index 717b55656f142..81c5488cb44af 100644 --- a/pkg/scheduler/backend/queue/scheduling_queue.go +++ b/pkg/scheduler/backend/queue/scheduling_queue.go @@ -88,12 +88,17 @@ const ( // by the checking result. type PreEnqueueCheck func(pod *v1.Pod) bool +// PodSigner creates a scheduling signature for a pod that represents its scheduling requirements. +// The signature is used by the opportunistic batching feature (KEP-5598) to reuse scheduling decisions. +// Returns nil if the pod is "unsignable" (some plugins cannot create a stable signature for it). +type PodSigner func(ctx context.Context, pod *v1.Pod) fwk.PodSignature + // SchedulingQueue is an interface for a queue to store pods waiting to be scheduled. // The interface follows a pattern similar to cache.FIFO and cache.Heap and // makes it easy to use those data structures as a SchedulingQueue. type SchedulingQueue interface { fwk.PodNominator - Add(logger klog.Logger, pod *v1.Pod) + Add(ctx context.Context, pod *v1.Pod) // Activate moves the given pods to activeQ. // If a pod isn't found in unschedulablePods or backoffQ and it's in-flight, // the wildcard event is registered so that the pod will be requeued when it comes back. @@ -111,10 +116,14 @@ type SchedulingQueue interface { // Pop removes the head of the queue and returns it. It blocks if the // queue is empty and waits until a new item is added to the queue. Pop(logger klog.Logger) (*framework.QueuedPodInfo, error) + // PopSpecificPod removes the pod from the queue and returns it. + // It behaves like Pop for the popped pod (metrics, in-flight tracking, etc.). + // It returns nil if the pod is not in the queue. + PopSpecificPod(logger klog.Logger, pod *v1.Pod) *framework.QueuedPodInfo // Done must be called for pod returned by Pop. This allows the queue to // keep track of which pods are currently being processed. Done(types.UID) - Update(logger klog.Logger, oldPod, newPod *v1.Pod) + Update(ctx context.Context, oldPod, newPod *v1.Pod) Delete(pod *v1.Pod) // Important Note: preCheck shouldn't include anything that depends on the in-tree plugins' logic. // (e.g., filter Pods based on added/updated Node's capacity, etc.) @@ -202,10 +211,17 @@ type PriorityQueue struct { // It's non-nil only if the SchedulerAsyncAPICalls feature gate is enabled. apiDispatcher fwk.APIDispatcher + // podSigners maps a profile name to a signing function for that profile. + podSigners map[string]PodSigner + // isSchedulingQueueHintEnabled indicates whether the feature gate for the scheduling queue is enabled. isSchedulingQueueHintEnabled bool // isPopFromBackoffQEnabled indicates whether the feature gate SchedulerPopFromBackoffQ is enabled. isPopFromBackoffQEnabled bool + // isGenericWorkloadEnabled indicates whether the feature gate GenericWorkload is enabled. + isGenericWorkloadEnabled bool + // isOpportunisticBatchingEnabled indicates whether the OpportunisticBatching feature gate is enabled. + isOpportunisticBatchingEnabled bool } // QueueingHintFunction is the wrapper of QueueingHintFn that has PluginName. @@ -234,6 +250,7 @@ type priorityQueueOptions struct { preEnqueuePluginMap map[string]map[string]fwk.PreEnqueuePlugin queueingHintMap QueueingHintMapPerProfile apiDispatcher fwk.APIDispatcher + podSigners map[string]PodSigner } // Option configures a PriorityQueue @@ -315,6 +332,16 @@ func WithAPIDispatcher(apiDispatcher fwk.APIDispatcher) Option { } } +// WithPodSigners sets the pod signing functions per scheduler profile. +// Each profile can have its own signing function for computing pod signatures. +// Pod signatures enable opportunistic batching (KEP-5598) by allowing the scheduler +// to cache and reuse filtering/scoring results for identical pods. +func WithPodSigners(signers map[string]PodSigner) Option { + return func(o *priorityQueueOptions) { + o.podSigners = signers + } +} + var defaultPriorityQueueOptions = priorityQueueOptions{ clock: clock.RealClock{}, podInitialBackoffDuration: DefaultPodInitialBackoffDuration, @@ -351,6 +378,8 @@ func NewPriorityQueue( isSchedulingQueueHintEnabled := utilfeature.DefaultFeatureGate.Enabled(features.SchedulerQueueingHints) isPopFromBackoffQEnabled := utilfeature.DefaultFeatureGate.Enabled(features.SchedulerPopFromBackoffQ) + isGenericWorkloadEnabled := utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) + isOpportunisticBatchingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.OpportunisticBatching) lessConverted := convertLessFn(lessFn) backoffQ := newBackoffQueue(options.clock, options.podInitialBackoffDuration, options.podMaxBackoffDuration, lessFn, isPopFromBackoffQEnabled) @@ -367,8 +396,11 @@ func NewPriorityQueue( pluginMetricsSamplePercent: options.pluginMetricsSamplePercent, moveRequestCycle: -1, apiDispatcher: options.apiDispatcher, + podSigners: options.podSigners, isSchedulingQueueHintEnabled: isSchedulingQueueHintEnabled, isPopFromBackoffQEnabled: isPopFromBackoffQEnabled, + isGenericWorkloadEnabled: isGenericWorkloadEnabled, + isOpportunisticBatchingEnabled: isOpportunisticBatchingEnabled, } var backoffQPopper backoffQPopper if isPopFromBackoffQEnabled { @@ -572,11 +604,10 @@ func (p *PriorityQueue) runPreEnqueuePlugins(ctx context.Context, pInfo *framewo }() shouldRecordMetric := rand.Intn(100) < p.pluginMetricsSamplePercent - logger := klog.FromContext(ctx) gatingPlugin := pInfo.GatingPlugin if gatingPlugin != "" { // Run the gating plugin first - s := p.runPreEnqueuePlugin(ctx, logger, p.preEnqueuePluginMap[pod.Spec.SchedulerName][gatingPlugin], pInfo, shouldRecordMetric) + s := p.runPreEnqueuePlugin(ctx, p.preEnqueuePluginMap[pod.Spec.SchedulerName][gatingPlugin], pInfo, shouldRecordMetric) if !s.IsSuccess() { // No need to iterate other plugins return @@ -588,7 +619,7 @@ func (p *PriorityQueue) runPreEnqueuePlugins(ctx context.Context, pInfo *framewo // should be run already above. continue } - s := p.runPreEnqueuePlugin(ctx, logger, pl, pInfo, shouldRecordMetric) + s := p.runPreEnqueuePlugin(ctx, pl, pInfo, shouldRecordMetric) if !s.IsSuccess() { // No need to iterate other plugins return @@ -599,7 +630,7 @@ func (p *PriorityQueue) runPreEnqueuePlugins(ctx context.Context, pInfo *framewo } // runPreEnqueuePlugin runs the PreEnqueue plugin and update pInfo's fields accordingly if needed. -func (p *PriorityQueue) runPreEnqueuePlugin(ctx context.Context, logger klog.Logger, pl fwk.PreEnqueuePlugin, pInfo *framework.QueuedPodInfo, shouldRecordMetric bool) *fwk.Status { +func (p *PriorityQueue) runPreEnqueuePlugin(ctx context.Context, pl fwk.PreEnqueuePlugin, pInfo *framework.QueuedPodInfo, shouldRecordMetric bool) *fwk.Status { pod := pInfo.Pod startTime := p.clock.Now() s := pl.PreEnqueue(ctx, pod) @@ -610,14 +641,17 @@ func (p *PriorityQueue) runPreEnqueuePlugin(ctx context.Context, logger klog.Log // No need to change GatingPlugin; it's overwritten by the next PreEnqueue plugin if they gate this pod, or it's overwritten with an empty string if all PreEnqueue plugins pass. return s } + // Only increment metric and insert if not already incremented for this plugin + if !pInfo.UnschedulablePlugins.Has(pl.Name()) && !pInfo.PendingPlugins.Has(pl.Name()) { + metrics.UnschedulableReason(pl.Name(), pod.Spec.SchedulerName).Inc() + } pInfo.UnschedulablePlugins.Insert(pl.Name()) - metrics.UnschedulableReason(pl.Name(), pod.Spec.SchedulerName).Inc() pInfo.GatingPlugin = pl.Name() pInfo.GatingPluginEvents = p.pluginToEventsMap[pInfo.GatingPlugin] if s.Code() == fwk.Error { utilruntime.HandleErrorWithContext(ctx, s.AsError(), "Unexpected error running PreEnqueue plugin", "pod", klog.KObj(pod), "plugin", pl.Name()) } else { - logger.V(4).Info("Status after running PreEnqueue plugin", "pod", klog.KObj(pod), "plugin", pl.Name(), "status", s) + klog.FromContext(ctx).V(4).Info("Status after running PreEnqueue plugin", "pod", klog.KObj(pod), "plugin", pl.Name(), "status", s) } return s @@ -655,11 +689,11 @@ func (p *PriorityQueue) moveToActiveQ(logger klog.Logger, pInfo *framework.Queue if p.backoffQ.has(pInfo) { return } - if p.unschedulablePods.get(pInfo.Pod) != nil { - return + + if p.unschedulablePods.get(pInfo.Pod) == nil { + logger.V(5).Info("Pod moved to an internal scheduling queue, because the pod is gated", "pod", klog.KObj(pInfo.Pod), "event", event, "queue", unschedulableQ) } - p.unschedulablePods.addOrUpdate(pInfo, event) - logger.V(5).Info("Pod moved to an internal scheduling queue, because the pod is gated", "pod", klog.KObj(pInfo.Pod), "event", event, "queue", unschedulableQ) + p.unschedulablePods.addOrUpdate(pInfo, gatedBefore, event) return } if pInfo.InitialAttemptTimestamp == nil { @@ -690,9 +724,9 @@ func (p *PriorityQueue) moveToBackoffQ(logger klog.Logger, pInfo *framework.Queu p.runPreEnqueuePlugins(context.Background(), pInfo) if pInfo.Gated() { if p.unschedulablePods.get(pInfo.Pod) == nil { - p.unschedulablePods.addOrUpdate(pInfo, event) logger.V(5).Info("Pod moved to an internal scheduling queue", "pod", klog.KObj(pInfo.Pod), "event", event, "queue", unschedulableQ) } + p.unschedulablePods.addOrUpdate(pInfo, gatedBefore, event) return false } } @@ -704,11 +738,12 @@ func (p *PriorityQueue) moveToBackoffQ(logger klog.Logger, pInfo *framework.Queu // Add adds a pod to the active queue. It should be called only when a new pod // is added so there is no chance the pod is already in active/unschedulable/backoff queues -func (p *PriorityQueue) Add(logger klog.Logger, pod *v1.Pod) { +func (p *PriorityQueue) Add(ctx context.Context, pod *v1.Pod) { p.lock.Lock() defer p.lock.Unlock() - pInfo := p.newQueuedPodInfo(pod) + pInfo := p.newQueuedPodInfo(ctx, pod) + logger := klog.FromContext(ctx) if added := p.moveToActiveQ(logger, pInfo, framework.EventUnscheduledPodAdd.Label(), false); added { p.activeQ.broadcast() } @@ -787,6 +822,12 @@ func (p *PriorityQueue) determineSchedulingHintForInFlightPod(logger klog.Logger // In this case, we should retry scheduling it because this Pod may not be retried until the next flush. return queueAfterBackoff } + if p.isGenericWorkloadEnabled && pInfo.Pod.Spec.SchedulingGroup != nil { + // When the pod is a member of the pod group, i.e., needs pod group scheduling, + // its retry logic should be based solely on backoff, because event-based requeueing logic + // is not yet implemented for pod group level. + return queueAfterBackoff + } events, err := p.activeQ.clusterEventsForPod(logger, pInfo) if err != nil { @@ -850,7 +891,7 @@ func (p *PriorityQueue) addUnschedulableWithoutQueueingHint(logger klog.Logger, } } } else { - p.unschedulablePods.addOrUpdate(pInfo, framework.ScheduleAttemptFailure) + p.unschedulablePods.addOrUpdate(pInfo, false /* the Pod was absent from all queues */, framework.ScheduleAttemptFailure) logger.V(5).Info("Pod moved to an internal scheduling queue", "pod", klog.KObj(pod), "event", framework.ScheduleAttemptFailure, "queue", unschedulableQ) } @@ -894,6 +935,8 @@ func (p *PriorityQueue) AddUnschedulableIfNotPresent(logger klog.Logger, pInfo * // We changed ConsecutiveErrorsCount or UnschedulableCount plus Timestamp, and now the calculated backoff time should be different, // removing the cached backoff time. pInfo.BackoffExpiration = time.Time{} + // Clear the flush flag since the pod is returning to the queue after a scheduling attempt. + pInfo.WasFlushedFromUnschedulable = false if !p.isSchedulingQueueHintEnabled { // fall back to the old behavior which doesn't depend on the queueing hint. @@ -948,6 +991,8 @@ func (p *PriorityQueue) flushUnschedulablePodsLeftover(logger klog.Logger) { for _, pInfo := range p.unschedulablePods.podInfoMap { lastScheduleTime := pInfo.Timestamp if currentTime.Sub(lastScheduleTime) > p.podMaxInUnschedulablePodsDuration { + // Mark this pod as flushed so we can detect if it schedules soon after + pInfo.WasFlushedFromUnschedulable = true podsToMove = append(podsToMove, pInfo) } } @@ -966,6 +1011,41 @@ func (p *PriorityQueue) Pop(logger klog.Logger) (*framework.QueuedPodInfo, error return p.activeQ.pop(logger) } +// PopSpecificPod removes the pod from the queue and returns it. +// It behaves like Pop for the popped pod (metrics, in-flight tracking, etc.). +// It returns nil if the pod is not in the queue. +func (p *PriorityQueue) PopSpecificPod(logger klog.Logger, pod *v1.Pod) *framework.QueuedPodInfo { + p.lock.Lock() + defer p.lock.Unlock() + + pInfoLookup := newQueuedPodInfoForLookup(pod) + + // Try to activate the pod, then obtain it from the activeQ. + // This way, the pod behaves almost the same as it would be activated by some event, + // moved to the activeQ and popped from there. + _ = p.activate(logger, pod) + + var pInfo *framework.QueuedPodInfo + p.activeQ.underRLock(func(unlockedActiveQ unlockedActiveQueueReader) { + pInfo, _ = unlockedActiveQ.get(pInfoLookup) + }) + if pInfo == nil { + return nil + } + if err := p.activeQ.delete(pInfo); err != nil { + // Error means that the pod disappeared from the activeQ in the meantime. + // Returning nil is fine. + return nil + } + err := p.activeQ.movePodToInFlight(pInfo) + if err != nil { + utilruntime.HandleErrorWithLogger(logger, err, "Discarding the popped pod") + return nil + } + + return pInfo +} + // Done must be called for pod returned by Pop. This allows the queue to // keep track of which pods are currently being processed. func (p *PriorityQueue) Done(pod types.UID) { @@ -1011,9 +1091,10 @@ func isPodUpdated(oldPod, newPod *v1.Pod) bool { // the item from the unschedulable queue if pod is updated in a way that it may // become schedulable and adds the updated one to the active queue. // If pod is not present in any of the queues, it is added to the active queue. -func (p *PriorityQueue) Update(logger klog.Logger, oldPod, newPod *v1.Pod) { +func (p *PriorityQueue) Update(ctx context.Context, oldPod, newPod *v1.Pod) { p.lock.Lock() defer p.lock.Unlock() + logger := klog.FromContext(ctx) var events []fwk.ClusterEvent if p.isSchedulingQueueHintEnabled { @@ -1043,6 +1124,7 @@ func (p *PriorityQueue) Update(logger klog.Logger, oldPod, newPod *v1.Pod) { // If the pod is already in the active queue, just update it there. if pInfo := unlockedActiveQ.update(newPod, oldPodInfo); pInfo != nil { p.UpdateNominatedPod(logger, oldPod, pInfo.PodInfo) + pInfo.PodSignature = p.signPod(ctx, newPod) updated = true return } @@ -1050,6 +1132,7 @@ func (p *PriorityQueue) Update(logger klog.Logger, oldPod, newPod *v1.Pod) { // If the pod is in the backoff queue, update it there. if pInfo := p.backoffQ.update(newPod, oldPodInfo); pInfo != nil { p.UpdateNominatedPod(logger, oldPod, pInfo.PodInfo) + pInfo.PodSignature = p.signPod(ctx, newPod) updated = true return } @@ -1063,6 +1146,8 @@ func (p *PriorityQueue) Update(logger klog.Logger, oldPod, newPod *v1.Pod) { if pInfo := p.unschedulablePods.get(newPod); pInfo != nil { _ = pInfo.Update(newPod) p.UpdateNominatedPod(logger, oldPod, pInfo.PodInfo) + pInfo.PodSignature = p.signPod(ctx, newPod) + if p.isSchedulingQueueHintEnabled { // When unscheduled Pods are updated, we check with QueueingHint // whether the update may make the pods schedulable. @@ -1100,11 +1185,12 @@ func (p *PriorityQueue) Update(logger klog.Logger, oldPod, newPod *v1.Pod) { } // Pod update didn't make it schedulable, keep it in the unschedulable queue. - p.unschedulablePods.addOrUpdate(pInfo, framework.EventUnscheduledPodUpdate.Label()) + // Use pInfo.Gated() to avoid double-counting "Gated" metrics during an in-place update. + p.unschedulablePods.addOrUpdate(pInfo, pInfo.Gated(), framework.EventUnscheduledPodUpdate.Label()) return } // If pod is not in any of the queues, we put it in the active queue. - pInfo := p.newQueuedPodInfo(newPod) + pInfo := p.newQueuedPodInfo(ctx, newPod) if added := p.moveToActiveQ(logger, pInfo, framework.EventUnscheduledPodUpdate.Label(), false); added { p.activeQ.broadcast() } @@ -1125,6 +1211,10 @@ func (p *PriorityQueue) Delete(pod *v1.Pod) { } if pInfo = p.unschedulablePods.get(pod); pInfo != nil { p.unschedulablePods.delete(pod, pInfo.Gated()) + // Drop metric for deleted pod. + for plugin := range pInfo.UnschedulablePlugins.Union(pInfo.PendingPlugins) { + metrics.UnschedulableReason(plugin, pInfo.Pod.Spec.SchedulerName).Dec() + } } } @@ -1192,7 +1282,8 @@ func (p *PriorityQueue) MoveAllToActiveOrBackoffQueue(logger klog.Logger, event // NOTE: this function assumes lock has been acquired in caller func (p *PriorityQueue) requeuePodWithQueueingStrategy(logger klog.Logger, pInfo *framework.QueuedPodInfo, strategy queueingStrategy, event string) string { if strategy == queueSkip { - p.unschedulablePods.addOrUpdate(pInfo, event) + // Current Gate status is required for already exisiting pods. For new pods, this parameter is ignored/unused by addOrUpdate. + p.unschedulablePods.addOrUpdate(pInfo, pInfo.Gated(), event) return unschedulableQ } @@ -1296,6 +1387,8 @@ func (p *PriorityQueue) PodsInBackoffQ() []*v1.Pod { // UnschedulablePods returns all the pods in unschedulable state. func (p *PriorityQueue) UnschedulablePods() []*v1.Pod { + p.lock.RLock() + defer p.lock.RUnlock() var result []*v1.Pod for _, pInfo := range p.unschedulablePods.podInfoMap { result = append(result, pInfo.Pod) @@ -1413,8 +1506,28 @@ func (p *PriorityQueue) NominatedPodsForNode(nodeName string) []fwk.PodInfo { return pods } +// signPod computes the scheduling signature for the given pod when OpportunisticBatching is enabled. +// The signature is used to cache and reuse scheduling results for identical pods. +func (p *PriorityQueue) signPod(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + if !p.isOpportunisticBatchingEnabled { + return nil + } + + if p.podSigners == nil { + return nil + } + + signer, ok := p.podSigners[pod.Spec.SchedulerName] + if !ok { + utilruntime.HandleErrorWithContext(ctx, nil, "No signer registered for scheduler profile", "pod", klog.KObj(pod), "schedulerName", pod.Spec.SchedulerName) + return nil + } + + return signer(ctx, pod) +} + // newQueuedPodInfo builds a QueuedPodInfo object. -func (p *PriorityQueue) newQueuedPodInfo(pod *v1.Pod, plugins ...string) *framework.QueuedPodInfo { +func (p *PriorityQueue) newQueuedPodInfo(ctx context.Context, pod *v1.Pod, plugins ...string) *framework.QueuedPodInfo { now := p.clock.Now() // ignore this err since apiserver doesn't properly validate affinity terms // and we can't fix the validation for backwards compatibility. @@ -1423,6 +1536,7 @@ func (p *PriorityQueue) newQueuedPodInfo(pod *v1.Pod, plugins ...string) *framew PodInfo: podInfo, Timestamp: now, InitialAttemptTimestamp: nil, + PodSignature: p.signPod(ctx, pod), UnschedulablePlugins: sets.New(plugins...), } } diff --git a/pkg/scheduler/backend/queue/scheduling_queue_test.go b/pkg/scheduler/backend/queue/scheduling_queue_test.go index 505d2bbe8b99a..13da015ad2703 100644 --- a/pkg/scheduler/backend/queue/scheduling_queue_test.go +++ b/pkg/scheduler/backend/queue/scheduling_queue_test.go @@ -17,6 +17,7 @@ limitations under the License. package queue import ( + "bytes" "context" "fmt" "strings" @@ -38,7 +39,6 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2" - "k8s.io/klog/v2/ktesting" fwk "k8s.io/kube-scheduler/framework" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/features" @@ -49,6 +49,7 @@ import ( "k8s.io/kubernetes/pkg/scheduler/framework/plugins/schedulinggates" "k8s.io/kubernetes/pkg/scheduler/metrics" st "k8s.io/kubernetes/pkg/scheduler/testing" + "k8s.io/kubernetes/test/utils/ktesting" testingclock "k8s.io/utils/clock/testing" ) @@ -137,9 +138,9 @@ func TestPriorityQueue_Add(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) - q.Add(logger, medPriorityPodInfo.Pod) - q.Add(logger, unschedulablePodInfo.Pod) - q.Add(logger, highPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) + q.Add(ctx, unschedulablePodInfo.Pod) + q.Add(ctx, highPriorityPodInfo.Pod) expectedNominatedPods := &nominator{ nominatedPodToNode: map[types.UID]string{ medPriorityPodInfo.Pod.UID: "node1", @@ -152,14 +153,20 @@ func TestPriorityQueue_Add(t *testing.T) { if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff) } - if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } - if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(medPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } - if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(unschedulablePodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } if len(q.nominator.nominatedPods["node1"]) != 2 { t.Errorf("Expected medPriorityPodInfo and unschedulablePodInfo to be still present in nominatedPods: %v", q.nominator.nominatedPods["node1"]) @@ -177,13 +184,17 @@ func TestPriorityQueue_AddWithReversePriorityLessFunc(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) - q.Add(logger, medPriorityPodInfo.Pod) - q.Add(logger, highPriorityPodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) - } - if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, medPriorityPodInfo.Pod) + q.Add(ctx, highPriorityPodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(medPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } } @@ -649,7 +660,7 @@ func Test_InFlightPods(t *testing.T) { }, }, { - name: "popped pod must have empty UnschedulablePlugins and PendingPlugins", + name: "popped pod preserves UnschedulablePlugins and PendingPlugins", isSchedulingQueueHintEnabled: true, initialPods: []*v1.Pod{pod1}, actions: []action{ @@ -666,8 +677,13 @@ func Test_InFlightPods(t *testing.T) { {eventHappens: &pvAdd}, // Active again. {callback: func(t *testing.T, q *PriorityQueue) { poppedPod = popPod(t, logger, q, pod1) - if len(poppedPod.UnschedulablePlugins) > 0 { - t.Errorf("QueuedPodInfo from Pop should have empty UnschedulablePlugins, got instead: %+v", poppedPod) + // UnschedulablePlugins should be preserved for logging/debugging + if !poppedPod.UnschedulablePlugins.Equal(sets.New("fooPlugin2")) { + t.Errorf("QueuedPodInfo from Pop should preserve UnschedulablePlugins, expected fooPlugin2, got: %+v", poppedPod.UnschedulablePlugins) + } + // PendingPlugins are preserved after Pop() for logging + if !poppedPod.PendingPlugins.Equal(sets.New("fooPlugin1")) { + t.Errorf("QueuedPodInfo from Pop should preserve PendingPlugins, expected fooPlugin1, got: %+v", poppedPod.PendingPlugins) } }}, {callback: func(t *testing.T, q *PriorityQueue) { @@ -793,14 +809,14 @@ func Test_InFlightPods(t *testing.T) { // solve this issue on Windows unit test runs. // For more details on the Windows clock resolution issue, see: https://github.com/golang/go/issues/8687 for _, p := range test.initialPods { - q.Add(logger, p) + q.Add(ctx, p) fakeClock.Step(time.Second) } for _, action := range test.actions { switch { case action.podCreated != nil: - q.Add(logger, action.podCreated) + q.Add(ctx, action.podCreated) case action.podPopped != nil: popPod(t, logger, q, action.podPopped) case action.eventHappens != nil: @@ -920,7 +936,7 @@ func TestPop(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{pod}, WithQueueingHintMapPerProfile(queueingHintMap)) - q.Add(logger, pod) + q.Add(ctx, pod) // Simulate failed attempt that makes the pod unschedulable. poppedPod := popPod(t, logger, q, pod) @@ -935,8 +951,10 @@ func TestPop(t *testing.T) { // Now check result of Pop. poppedPod = popPod(t, logger, q, pod) - if len(poppedPod.PendingPlugins) > 0 { - t.Errorf("QueuedPodInfo from Pop should have empty PendingPlugins, got instead: %+v", poppedPod) + // PendingPlugins are preserved after Pop() so they can be logged if scheduling + // succeeds, or cleared in handleSchedulingFailure() if it fails. + if !poppedPod.PendingPlugins.Equal(sets.New("fooPlugin1")) { + t.Errorf("QueuedPodInfo from Pop should preserve PendingPlugins, expected fooPlugin1, got instead: %+v", poppedPod) } }) } @@ -950,25 +968,29 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) { q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) // insert unschedulablePodInfo and pop right after that // because the scheduling queue records unschedulablePod as in-flight Pod. - q.Add(logger, unschedulablePodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, unschedulablePodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(unschedulablePodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } - q.Add(logger, highPriNominatedPodInfo.Pod) + q.Add(ctx, highPriNominatedPodInfo.Pod) err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } - if p, err := q.Pop(logger); err != nil || p.Pod != highPriNominatedPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriNominatedPodInfo.Pod.Name, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriNominatedPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } if len(q.nominator.nominatedPods) != 1 { t.Errorf("Expected nominatedPods to have one element: %v", q.nominator) } // unschedulablePodInfo is inserted to unschedulable pod pool because no events happened during scheduling. - if getUnschedulablePod(q, unschedulablePodInfo.Pod) != unschedulablePodInfo.Pod { - t.Errorf("Pod %v was not found in the unschedulablePods.", unschedulablePodInfo.Pod.Name) + if diff := cmp.Diff(unschedulablePodInfo.Pod, getUnschedulablePod(q, unschedulablePodInfo.Pod)); diff != "" { + t.Errorf("Unexpected pod in unschedulablePods (-want, +got):\n%s", diff) } } @@ -988,7 +1010,7 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff(t *testing.T) { p := st.MakePod().Name(fmt.Sprintf("pod%d", i)).Namespace(fmt.Sprintf("ns%d", i)).UID(fmt.Sprintf("upns%d", i)).Priority(priority).Obj() expectedPods = append(expectedPods, *p) // priority is to make pods ordered in the PriorityQueue - q.Add(logger, p) + q.Add(ctx, p) } // Pop all pods except for the first one @@ -1094,16 +1116,16 @@ func TestPriorityQueue_Pop(t *testing.T) { q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) // Add medium priority pod to the activeQ - q.Add(logger, medPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) // Add high priority pod to the backoffQ - backoffPodInfo := q.newQueuedPodInfo(highPriorityPodInfo.Pod, "plugin") + backoffPodInfo := q.newQueuedPodInfo(ctx, highPriorityPodInfo.Pod, "plugin") q.backoffQ.add(logger, backoffPodInfo, framework.EventUnscheduledPodAdd.Label()) // Add high priority pod to the errorBackoffQ - errorBackoffPodInfo := q.newQueuedPodInfo(highPriorityPodInfo2.Pod) + errorBackoffPodInfo := q.newQueuedPodInfo(ctx, highPriorityPodInfo2.Pod) q.backoffQ.add(logger, errorBackoffPodInfo, framework.EventUnscheduledPodAdd.Label()) // Add pod to the unschedulablePods - unschedulablePodInfo := q.newQueuedPodInfo(unschedulablePodInfo.Pod, "plugin") - q.unschedulablePods.addOrUpdate(unschedulablePodInfo, framework.EventUnscheduledPodAdd.Label()) + unschedulablePodInfo := q.newQueuedPodInfo(ctx, unschedulablePodInfo.Pod, "plugin") + q.unschedulablePods.addOrUpdate(unschedulablePodInfo, false, framework.EventUnscheduledPodAdd.Label()) var gotPods []string for i := 0; i < len(tt.wantPods)+1; i++ { @@ -1120,6 +1142,121 @@ func TestPriorityQueue_Pop(t *testing.T) { } } +func TestPriorityQueue_PopSpecificPod(t *testing.T) { + activePod := st.MakePod().Name("pod-active").Namespace("ns1").UID("pod-active").Label("foo", "").Obj() + backoffPod := st.MakePod().Name("pod-backoff").Namespace("ns1").UID("pod-backoff").Label("foo", "").Obj() + unschedulablePod := st.MakePod().Name("pod-unschedulable").Namespace("ns1").UID("pod-unschedulable").Label("foo", "").Obj() + gatedPod := st.MakePod().Name("pod-gated").Namespace("ns1").UID("pod-gated").Obj() + temporarilyGatedPod := st.MakePod().Name("pod-temporarily-gated").Namespace("ns1").UID("pod-temporarily-gated").Label("bar", "").Obj() + otherPod := st.MakePod().Name("pod-other").Namespace("ns1").UID("pod-other").Label("foo", "").Obj() + + objs := []runtime.Object{activePod, backoffPod, unschedulablePod, gatedPod, temporarilyGatedPod, otherPod} + + tests := []struct { + name string + podToPop *v1.Pod + gated bool + wantPopped bool + }{ + { + name: "Pod is in activeQ", + podToPop: activePod, + wantPopped: true, + }, + { + name: "Pod is in backoffQ", + podToPop: backoffPod, + wantPopped: true, + }, + { + name: "Pod is in unschedulablePods", + podToPop: unschedulablePod, + wantPopped: true, + }, + { + name: "Pod is gated in unschedulablePods", + podToPop: gatedPod, + wantPopped: false, + }, + { + name: "Pod was gated, but should be no longer", + podToPop: temporarilyGatedPod, + wantPopped: true, + }, + { + name: "Pod is not in any queue", + podToPop: otherPod, + wantPopped: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + plugin := &preEnqueuePlugin{allowlists: []string{"foo"}} + m := map[string]map[string]fwk.PreEnqueuePlugin{ + "": { + plugin.Name(): plugin, + }, + } + q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs, WithPreEnqueuePluginMap(m)) + + q.Add(ctx, activePod) + + backoffPodInfo := q.newQueuedPodInfo(ctx, backoffPod) + q.backoffQ.add(logger, backoffPodInfo, framework.EventUnscheduledPodAdd.Label()) + + unschedPodInfo := q.newQueuedPodInfo(ctx, unschedulablePod) + q.unschedulablePods.addOrUpdate(unschedPodInfo, false, framework.EventUnscheduledPodAdd.Label()) + + q.Add(ctx, gatedPod) + q.Add(ctx, temporarilyGatedPod) + + // Modify the PreEnqueue allowlists to allow the "bar" label of temporarilyGatedPod. + plugin.allowlists = []string{"foo", "bar"} + + gotInfo := q.PopSpecificPod(logger, tt.podToPop) + if tt.wantPopped { + if gotInfo == nil { + t.Errorf("Expected pod info to be popped, got nil") + } else if gotInfo.Pod.Name != tt.podToPop.Name { + t.Errorf("Expected pod %s, got %s", tt.podToPop.Name, gotInfo.Pod.Name) + } + _, ok := q.GetPod(tt.podToPop.Name, tt.podToPop.Namespace) + if ok { + t.Errorf("Expected pod not to exist in the scheduling queue after being popped") + } + } else { + if gotInfo != nil { + t.Errorf("Expected nil pod info, got %v", gotInfo.Pod.Name) + } + if tt.podToPop != otherPod { + _, ok := q.GetPod(tt.podToPop.Name, tt.podToPop.Namespace) + if !ok { + t.Errorf("Expected pod to exist in the scheduling queue after not being popped") + } + } + } + + // Verify if it's in inFlight pods. + inFlightPods := q.InFlightPods() + found := false + for _, p := range inFlightPods { + if p.UID == tt.podToPop.UID { + found = true + break + } + } + if found != tt.wantPopped { + t.Errorf("Expected pod inFlight: %v, got: %v", tt.wantPopped, found) + } + }) + } +} + func TestPriorityQueue_Update(t *testing.T) { c := testingclock.NewFakeClock(time.Now()) @@ -1149,14 +1286,14 @@ func TestPriorityQueue_Update(t *testing.T) { // prepareFunc is the function called to prepare pods in the queue before the test case calls Update(). // This function returns three values; // - oldPod/newPod: each test will call Update() with these oldPod and newPod. - prepareFunc func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) + prepareFunc func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) // schedulingHintsEnablement shows which value of QHint feature gate we test a test case with. schedulingHintsEnablement []bool }{ { name: "Update pod that didn't exist in the queue", wantQ: activeQ, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { updatedPod := medPriorityPodInfo.Pod.DeepCopy() updatedPod.Annotations["foo"] = "test" return medPriorityPodInfo.Pod, updatedPod @@ -1167,7 +1304,7 @@ func TestPriorityQueue_Update(t *testing.T) { name: "Update highPriorityPodInfo and add a nominatedNodeName to it", wantQ: activeQ, wantAddedToNominated: true, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { return highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod }, schedulingHintsEnablement: []bool{false, true}, @@ -1175,8 +1312,8 @@ func TestPriorityQueue_Update(t *testing.T) { { name: "When updating a pod that is already in activeQ, the pod should remain in activeQ after Update()", wantQ: activeQ, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { - q.Add(logger, highPriorityPodInfo.Pod) + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + q.Add(tCtx, highPriorityPodInfo.Pod) return highPriorityPodInfo.Pod, highPriorityPodInfo.Pod }, schedulingHintsEnablement: []bool{false, true}, @@ -1184,21 +1321,21 @@ func TestPriorityQueue_Update(t *testing.T) { { name: "When updating a pod that is in backoff queue and is still backing off, it will be updated in backoff queue", wantQ: backoffQ, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { - podInfo := q.newQueuedPodInfo(medPriorityPodInfo.Pod) - q.backoffQ.add(logger, podInfo, framework.EventUnscheduledPodAdd.Label()) + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + podInfo := q.newQueuedPodInfo(tCtx, medPriorityPodInfo.Pod) + q.backoffQ.add(klog.FromContext(tCtx), podInfo, framework.EventUnscheduledPodAdd.Label()) return podInfo.Pod, podInfo.Pod }, schedulingHintsEnablement: []bool{false, true}, }, { - name: "when updating a pod which is in unschedulable queue and is backing off, it will be moved to backoff queue", + name: "when updating a pod in unschedulablePods, if its backoff timer has not yet expired, it moves to backoffQ", wantQ: backoffQ, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { - pInfo := q.newQueuedPodInfo(medPriorityPodInfo.Pod, queuePlugin) + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + pInfo := q.newQueuedPodInfo(tCtx, medPriorityPodInfo.Pod, queuePlugin) // needs to increment to make the pod backing off pInfo.UnschedulableCount++ - q.unschedulablePods.addOrUpdate(pInfo, framework.EventUnscheduledPodAdd.Label()) + q.unschedulablePods.addOrUpdate(pInfo, false, framework.EventUnscheduledPodAdd.Label()) updatedPod := medPriorityPodInfo.Pod.DeepCopy() updatedPod.Annotations["foo"] = "test" return medPriorityPodInfo.Pod, updatedPod @@ -1206,13 +1343,13 @@ func TestPriorityQueue_Update(t *testing.T) { schedulingHintsEnablement: []bool{false, true}, }, { - name: "when updating a pod which is in unschedulable queue and is not backing off, it will be moved to active queue", + name: "when updating a pod in unschedulablePods, if its backoff timer has expired, it moves to activeQ", wantQ: activeQ, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { - pInfo := q.newQueuedPodInfo(medPriorityPodInfo.Pod, queuePlugin) + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + pInfo := q.newQueuedPodInfo(tCtx, medPriorityPodInfo.Pod, queuePlugin) // needs to increment to make the pod backing off pInfo.UnschedulableCount++ - q.unschedulablePods.addOrUpdate(pInfo, framework.EventUnscheduledPodAdd.Label()) + q.unschedulablePods.addOrUpdate(pInfo, false, framework.EventUnscheduledPodAdd.Label()) updatedPod := medPriorityPodInfo.Pod.DeepCopy() updatedPod.Annotations["foo"] = "test1" // Move clock by podMaxBackoffDuration, so that pods in the unschedulablePods would pass the backing off, @@ -1223,10 +1360,10 @@ func TestPriorityQueue_Update(t *testing.T) { schedulingHintsEnablement: []bool{false, true}, }, { - name: "when updating a pod which is in unschedulable pods but the plugin returns skip, it will remain in unschedulablePods", + name: "when updating a pod in unschedulablePods, if the scheduling hint returns QueueSkip, it remains in unschedulablePods", wantQ: unschedulableQ, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { - q.unschedulablePods.addOrUpdate(q.newQueuedPodInfo(medPriorityPodInfo.Pod, skipPlugin), framework.EventUnscheduledPodAdd.Label()) + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + q.unschedulablePods.addOrUpdate(q.newQueuedPodInfo(tCtx, medPriorityPodInfo.Pod, skipPlugin), false, framework.EventUnscheduledPodAdd.Label()) updatedPod := medPriorityPodInfo.Pod.DeepCopy() updatedPod.Annotations["foo"] = "test1" return medPriorityPodInfo.Pod, updatedPod @@ -1236,11 +1373,13 @@ func TestPriorityQueue_Update(t *testing.T) { { name: "when updating a pod which is in flightPods, the pod will not be added to any queue", wantQ: notInAnyQueue, - prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) { + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) (oldPod, newPod *v1.Pod) { // We need to once add this Pod to activeQ and Pop() it so that this Pod is registered correctly in inFlightPods. - q.Add(logger, medPriorityPodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) + q.Add(tCtx, medPriorityPodInfo.Pod) + if p, err := q.Pop(klog.FromContext(tCtx)); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(medPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } updatedPod := medPriorityPodInfo.Pod.DeepCopy() updatedPod.Annotations["foo"] = "bar" @@ -1258,15 +1397,13 @@ func TestPriorityQueue_Update(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, false) } - logger, ctx := ktesting.NewTestContext(t) + tCtx := ktesting.Init(t) objs := []runtime.Object{highPriorityPodInfo.Pod, unschedulablePodInfo.Pod, medPriorityPodInfo.Pod} - ctx, cancel := context.WithCancel(ctx) - defer cancel() - q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs, WithClock(c), WithQueueingHintMapPerProfile(queueingHintMap)) + q := NewTestQueueWithObjects(tCtx, newDefaultQueueSort(), objs, WithClock(c), WithQueueingHintMapPerProfile(queueingHintMap)) - oldPod, newPod := tt.prepareFunc(t, logger, q) + oldPod, newPod := tt.prepareFunc(tCtx, q) - q.Update(logger, oldPod, newPod) + q.Update(tCtx, oldPod, newPod) var pInfo *framework.QueuedPodInfo @@ -1333,9 +1470,11 @@ func TestPriorityQueue_UpdateWhenInflight(t *testing.T) { // test-pod is created and popped out from the queue testPod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("test-uid").Obj() - q.Add(logger, testPod) - if p, err := q.Pop(logger); err != nil || p.Pod != testPod { - t.Errorf("Expected: %v after Pop, but got: %v", testPod.Name, p.Pod.Name) + q.Add(ctx, testPod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(testPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // testPod is updated while being scheduled. @@ -1347,7 +1486,7 @@ func TestPriorityQueue_UpdateWhenInflight(t *testing.T) { }, } - q.Update(logger, testPod, updatedPod) + q.Update(ctx, testPod, updatedPod) // test-pod got rejected by fakePlugin, // but the update event that it just got may change this scheduling result, // and hence we should put this pod to activeQ/backoffQ. @@ -1367,12 +1506,12 @@ func TestPriorityQueue_UpdateWhenInflight(t *testing.T) { func TestPriorityQueue_Delete(t *testing.T) { objs := []runtime.Object{highPriorityPodInfo.Pod, unschedulablePodInfo.Pod} - logger, ctx := ktesting.NewTestContext(t) + _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) - q.Update(logger, highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod) - q.Add(logger, unschedulablePodInfo.Pod) + q.Update(ctx, highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod) + q.Add(ctx, unschedulablePodInfo.Pod) q.Delete(highPriNominatedPodInfo.Pod) if !q.activeQ.has(newQueuedPodInfoForLookup(unschedulablePodInfo.Pod)) { t.Errorf("Expected %v to be in activeQ.", unschedulablePodInfo.Pod.Name) @@ -1474,11 +1613,11 @@ func TestPriorityQueue_Activate(t *testing.T) { // Prepare activeQ/unschedulablePods/backoffQ according to the table for _, qPod := range tt.qPodInActiveQ { - q.Add(logger, qPod) + q.Add(ctx, qPod) } for _, qPodInfo := range tt.qPodInfoInUnschedulablePods { - q.unschedulablePods.addOrUpdate(qPodInfo, framework.EventUnscheduledPodAdd.Label()) + q.unschedulablePods.addOrUpdate(qPodInfo, false, framework.EventUnscheduledPodAdd.Label()) } for _, qPodInfo := range tt.qPodInfoInBackoffQ { @@ -1527,9 +1666,13 @@ func TestPriorityQueue_Activate(t *testing.T) { type preEnqueuePlugin struct { allowlists []string + name string } func (pl *preEnqueuePlugin) Name() string { + if pl.name != "" { + return pl.name + } return "preEnqueuePlugin" } @@ -1664,7 +1807,7 @@ func TestPriorityQueue_moveToActiveQ(t *testing.T) { } q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{tt.pod}, WithPreEnqueuePluginMap(m), WithPodInitialBackoffDuration(time.Second*30), WithPodMaxBackoffDuration(time.Second*60)) - got := q.moveToActiveQ(logger, q.newQueuedPodInfo(tt.pod), tt.event, tt.movesFromBackoffQ) + got := q.moveToActiveQ(logger, q.newQueuedPodInfo(ctx, tt.pod), tt.event, tt.movesFromBackoffQ) if got != tt.wantSuccess { t.Errorf("Unexpected result: want %v, but got %v", tt.wantSuccess, got) } @@ -1675,7 +1818,7 @@ func TestPriorityQueue_moveToActiveQ(t *testing.T) { // Simulate an update event. clone := tt.pod.DeepCopy() metav1.SetMetaDataAnnotation(&clone.ObjectMeta, "foo", "") - q.Update(logger, tt.pod, clone) + q.Update(ctx, tt.pod, clone) // Ensure the pod is still located in unschedulablePods. if tt.wantUnschedulablePods != len(q.unschedulablePods.podInfoMap) { t.Errorf("Unexpected unschedulablePods: want %v, but got %v", tt.wantUnschedulablePods, len(q.unschedulablePods.podInfoMap)) @@ -1760,7 +1903,7 @@ func TestPriorityQueue_moveToBackoffQ(t *testing.T) { } q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{tt.pod}, WithPreEnqueuePluginMap(m), WithPodInitialBackoffDuration(time.Second*30), WithPodMaxBackoffDuration(time.Second*60)) - pInfo := q.newQueuedPodInfo(tt.pod) + pInfo := q.newQueuedPodInfo(ctx, tt.pod) got := q.moveToBackoffQ(logger, pInfo, framework.EventUnscheduledPodAdd.Label()) if got != tt.wantSuccess { t.Errorf("Unexpected result: want %v, but got %v", tt.wantSuccess, got) @@ -1880,13 +2023,13 @@ func BenchmarkMoveAllToActiveOrBackoffQueue(b *testing.B) { // - in random case, each pod failed by a random plugin, and also the moveEvent // is randomized. if tt.name == "baseline" { - podInfo = q.newQueuedPodInfo(p) + podInfo = q.newQueuedPodInfo(ctx, p) } else if tt.name == "worst" { // Each pod failed by all plugins. - podInfo = q.newQueuedPodInfo(p, plugins...) + podInfo = q.newQueuedPodInfo(ctx, p, plugins...) } else { // Random case. - podInfo = q.newQueuedPodInfo(p, plugins[j%len(plugins)]) + podInfo = q.newQueuedPodInfo(ctx, p, plugins[j%len(plugins)]) } err := q.AddUnschedulableIfNotPresent(logger, podInfo, q.SchedulingCycle()) if err != nil { @@ -1993,9 +2136,11 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithQueueingHint(t *testing. "foo": &preEnqueuePlugin{allowlists: []string{"foo"}}, }} q := NewTestQueue(ctx, newDefaultQueueSort(), WithQueueingHintMapPerProfile(m), WithClock(cl), WithPreEnqueuePluginMap(preEnqM)) - q.Add(logger, test.podInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != test.podInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", test.podInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, test.podInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(test.podInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // add to unsched pod pool err := q.AddUnschedulableIfNotPresent(logger, test.podInfo, q.SchedulingCycle()) @@ -2036,34 +2181,40 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) { } q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. - q.Add(logger, unschedulablePodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, unschedulablePodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(unschedulablePodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, unschedulablePodInfo.Pod.UID) - q.Add(logger, highPriorityPodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, highPriorityPodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID) - err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) + err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } - err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } expectInFlightPods(t, q) // Construct a Pod, but don't associate its scheduler failure to any plugin hpp1 := clonePod(highPriorityPodInfo.Pod, "hpp1") - q.Add(logger, hpp1) - if p, err := q.Pop(logger); err != nil || p.Pod != hpp1 { - t.Errorf("Expected: %v after Pop, but got: %v", hpp1, p.Pod.Name) + q.Add(ctx, hpp1) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(hpp1, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, hpp1.UID) // This Pod will go to backoffQ because no failure plugin is associated with it. - hpp1PodInfo := q.newQueuedPodInfo(hpp1) + hpp1PodInfo := q.newQueuedPodInfo(ctx, hpp1) hpp1PodInfo.UnschedulableCount++ err = q.AddUnschedulableIfNotPresent(logger, hpp1PodInfo, q.SchedulingCycle()) if err != nil { @@ -2072,13 +2223,15 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) { expectInFlightPods(t, q) // Construct another Pod, and associate its scheduler failure to plugin "barPlugin". hpp2 := clonePod(highPriorityPodInfo.Pod, "hpp2") - q.Add(logger, hpp2) - if p, err := q.Pop(logger); err != nil || p.Pod != hpp2 { - t.Errorf("Expected: %v after Pop, but got: %v", hpp2, p.Pod.Name) + q.Add(ctx, hpp2) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(hpp2, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, hpp2.UID) // This Pod will go to the unschedulable Pod pool. - err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp2, "barPlugin"), q.SchedulingCycle()) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, hpp2, "barPlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } @@ -2086,13 +2239,15 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) { // This NodeAdd event moves unschedulablePodInfo and highPriorityPodInfo to the backoffQ, // because of the queueing hint function registered for NodeAdd/fooPlugin. q.MoveAllToActiveOrBackoffQueue(logger, nodeAdd, nil, nil, nil) - q.Add(logger, medPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) if q.activeQ.len() != 1 { t.Errorf("Expected 1 item to be in activeQ, but got: %v", q.activeQ.len()) } // Pop out the medPriorityPodInfo in activeQ. - if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(medPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) // hpp2 won't be moved. @@ -2106,23 +2261,29 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) { _ = q.backoffQ.popAllBackoffCompleted(logger) expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) - q.Add(logger, unschedulablePodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, unschedulablePodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(unschedulablePodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID) - q.Add(logger, highPriorityPodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, highPriorityPodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID) - q.Add(logger, hpp1) - if p, err := q.Pop(logger); err != nil || p.Pod != hpp1 { - t.Errorf("Expected: %v after Pop, but got: %v", hpp1, p.Pod.Name) - } - unschedulableQueuedPodInfo := q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin") - highPriorityQueuedPodInfo := q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin") - hpp1QueuedPodInfo := q.newQueuedPodInfo(hpp1) + q.Add(ctx, hpp1) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(hpp1, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + unschedulableQueuedPodInfo := q.newQueuedPodInfo(ctx, unschedulablePodInfo.Pod, "fooPlugin") + highPriorityQueuedPodInfo := q.newQueuedPodInfo(ctx, highPriorityPodInfo.Pod, "fooPlugin") + hpp1QueuedPodInfo := q.newQueuedPodInfo(ctx, hpp1) expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID, hpp1.UID) err = q.AddUnschedulableIfNotPresent(logger, unschedulableQueuedPodInfo, q.SchedulingCycle()) if err != nil { @@ -2139,7 +2300,7 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) - q.Add(logger, medPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) // hpp1 will go to backoffQ because no failure plugin is associated with it. // All plugins other than hpp1 are enqueued to the unschedulable Pod pool. for _, pod := range []*v1.Pod{unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, hpp2} { @@ -2189,20 +2350,20 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithoutQueueingHint(t *testi } q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. - q.Add(logger, medPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) - err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) + err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } - err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } // Construct a Pod, but don't associate its scheduler failure to any plugin hpp1 := clonePod(highPriorityPodInfo.Pod, "hpp1") // This Pod will go to backoffQ because no failure plugin is associated with it. - hpp1PodInfo := q.newQueuedPodInfo(hpp1) + hpp1PodInfo := q.newQueuedPodInfo(ctx, hpp1) hpp1PodInfo.UnschedulableCount++ err = q.AddUnschedulableIfNotPresent(logger, hpp1PodInfo, q.SchedulingCycle()) if err != nil { @@ -2211,7 +2372,7 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithoutQueueingHint(t *testi // Construct another Pod, and associate its scheduler failure to plugin "barPlugin". hpp2 := clonePod(highPriorityPodInfo.Pod, "hpp2") // This Pod will go to the unschedulable Pod pool. - err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp2, "barPlugin"), q.SchedulingCycle()) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, hpp2, "barPlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } @@ -2222,8 +2383,10 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithoutQueueingHint(t *testi t.Errorf("Expected 1 item to be in activeQ, but got: %v", q.activeQ.len()) } // Pop out the medPriorityPodInfo in activeQ. - if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(medPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // hpp2 won't be moved. if q.backoffQ.len() != 3 { @@ -2235,9 +2398,9 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithoutQueueingHint(t *testi c.Step(q.backoffQ.podMaxBackoffDuration()) _ = q.backoffQ.popAllBackoffCompleted(logger) - unschedulableQueuedPodInfo := q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin") - highPriorityQueuedPodInfo := q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin") - hpp1QueuedPodInfo := q.newQueuedPodInfo(hpp1) + unschedulableQueuedPodInfo := q.newQueuedPodInfo(ctx, unschedulablePodInfo.Pod, "fooPlugin") + highPriorityQueuedPodInfo := q.newQueuedPodInfo(ctx, highPriorityPodInfo.Pod, "fooPlugin") + hpp1QueuedPodInfo := q.newQueuedPodInfo(ctx, hpp1) err = q.AddUnschedulableIfNotPresent(logger, unschedulableQueuedPodInfo, q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) @@ -2250,7 +2413,7 @@ func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithoutQueueingHint(t *testi if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } - q.Add(logger, medPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) // hpp1 will go to backoffQ because no failure plugin is associated with it. // All plugins other than hpp1 are enqueued to the unschedulable Pod pool. for _, pod := range []*v1.Pod{unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, hpp2} { @@ -2386,12 +2549,14 @@ func TestPriorityQueue_AssignedPodAdded_(t *testing.T) { q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. - q.Add(logger, tt.unschedPod) - if p, err := q.Pop(logger); err != nil || p.Pod != tt.unschedPod { - t.Errorf("Expected: %v after Pop, but got: %v", tt.unschedPod.Name, p.Pod.Name) + q.Add(ctx, tt.unschedPod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(tt.unschedPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } - err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(tt.unschedPod, tt.unschedPlugin), q.SchedulingCycle()) + err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, tt.unschedPod, tt.unschedPlugin), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } @@ -2500,12 +2665,14 @@ func TestPriorityQueue_AssignedPodUpdated(t *testing.T) { q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. - q.Add(logger, tt.unschedPod) - if p, err := q.Pop(logger); err != nil || p.Pod != tt.unschedPod { - t.Errorf("Expected: %v after Pop, but got: %v", tt.unschedPod.Name, p.Pod.Name) + q.Add(ctx, tt.unschedPod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(tt.unschedPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } - err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(tt.unschedPod, tt.unschedPlugin), q.SchedulingCycle()) + err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, tt.unschedPod, tt.unschedPlugin), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } @@ -2528,11 +2695,13 @@ func TestPriorityQueue_NominatedPodsForNode(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) - q.Add(logger, medPriorityPodInfo.Pod) - q.Add(logger, unschedulablePodInfo.Pod) - q.Add(logger, highPriorityPodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, medPriorityPodInfo.Pod) + q.Add(ctx, unschedulablePodInfo.Pod) + q.Add(ctx, highPriorityPodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } expectedList := []fwk.PodInfo{medPriorityPodInfo, unschedulablePodInfo} podInfos := q.NominatedPodsForNode("node1") @@ -2615,20 +2784,24 @@ func TestPriorityQueue_PendingPods(t *testing.T) { defer cancel() q := NewTestQueue(ctx, newDefaultQueueSort()) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. - q.Add(logger, unschedulablePodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) - } - q.Add(logger, highPriorityPodInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) - } - q.Add(logger, medPriorityPodInfo.Pod) - err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle()) + q.Add(ctx, unschedulablePodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(unschedulablePodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + q.Add(ctx, highPriorityPodInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + q.Add(ctx, medPriorityPodInfo.Pod) + err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } - err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "plugin"), q.SchedulingCycle()) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, highPriorityPodInfo.Pod, "plugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } @@ -2658,7 +2831,7 @@ func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) { defer cancel() objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, scheduledPodInfo.Pod} q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) - q.Add(logger, medPriorityPodInfo.Pod) + q.Add(ctx, medPriorityPodInfo.Pod) // Update unschedulablePodInfo on a different node than specified in the pod. q.AddNominatedPod(logger, mustNewTestPodInfo(t, unschedulablePodInfo.Pod), &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride, NominatedNodeName: "node5"}) @@ -2681,8 +2854,10 @@ func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) { if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff) } - if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(medPriorityPodInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // List of nominated pods shouldn't change after popping them from the queue. if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { @@ -2793,7 +2968,7 @@ func TestRecentlyTriedPodsGoBack(t *testing.T) { // Add a few pods to priority queue. for i := 0; i < 5; i++ { p := st.MakePod().Name(fmt.Sprintf("test-pod-%v", i)).Namespace("ns1").UID(fmt.Sprintf("tp00%v", i)).Priority(highPriority).Node("node1").NominatedNodeName("node1").Obj() - q.Add(logger, p) + q.Add(ctx, p) } c.Step(time.Microsecond) // Simulate a pod being popped by the scheduler, determined unschedulable, and @@ -2857,9 +3032,11 @@ func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) { }) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. - q.Add(logger, unschedulablePod) - if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePod { - t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Pod.Name) + q.Add(ctx, unschedulablePod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(unschedulablePod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // Put in the unschedulable queue err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), q.SchedulingCycle()) @@ -2876,15 +3053,14 @@ func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) { p1, err := q.Pop(logger) if err != nil { t.Errorf("Error while popping the head of the queue: %v", err) - } - if p1.Pod != unschedulablePod { - t.Errorf("Expected that test-pod-unscheduled was popped, got %v", p1.Pod.Name) + } else if diff := cmp.Diff(unschedulablePod, p1.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // Assume newer pod was added just after unschedulable pod // being popped and before being pushed back to the queue. newerPod := st.MakePod().Name("test-newer-pod").Namespace("ns1").UID("tp002").CreationTimestamp(metav1.Now()).Priority(highPriority).NominatedNodeName("node1").Obj() - q.Add(logger, newerPod) + q.Add(ctx, newerPod) // And then unschedulablePodInfo was determined as unschedulable AGAIN. podutil.UpdatePodCondition(&unschedulablePod.Status, &v1.PodCondition{ @@ -2909,9 +3085,8 @@ func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) { p2, err2 := q.Pop(logger) if err2 != nil { t.Errorf("Error while popping the head of the queue: %v", err2) - } - if p2.Pod != newerPod { - t.Errorf("Expected that test-newer-pod was popped, got %v", p2.Pod.Name) + } else if diff := cmp.Diff(newerPod, p2.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } } @@ -2925,16 +3100,15 @@ func TestHighPriorityBackoff(t *testing.T) { midPod := st.MakePod().Name("test-midpod").Namespace("ns1").UID("tp-mid").Priority(midPriority).NominatedNodeName("node1").Obj() highPod := st.MakePod().Name("test-highpod").Namespace("ns1").UID("tp-high").Priority(highPriority).NominatedNodeName("node1").Obj() - q.Add(logger, midPod) - q.Add(logger, highPod) + q.Add(ctx, midPod) + q.Add(ctx, highPod) // Simulate a pod being popped by the scheduler, determined unschedulable, and // then moved back to the active queue. p, err := q.Pop(logger) if err != nil { t.Errorf("Error while popping the head of the queue: %v", err) - } - if p.Pod != highPod { - t.Errorf("Expected to get high priority pod, got: %v", p) + } else if diff := cmp.Diff(highPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // Update pod condition to unschedulable. podutil.UpdatePodCondition(&p.Pod.Status, &v1.PodCondition{ @@ -2954,10 +3128,9 @@ func TestHighPriorityBackoff(t *testing.T) { p, err = q.Pop(logger) if err != nil { t.Errorf("Error while popping the head of the queue: %v", err) - } - if p.Pod != midPod { + } else if diff := cmp.Diff(midPod, p.Pod); diff != "" { // high pod should be in backoffQ - t.Errorf("Expected to get mid priority pod, got: %v", p.Pod.Name) + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } } @@ -2996,30 +3169,97 @@ func TestHighPriorityFlushUnschedulablePodsLeftover(t *testing.T) { }) // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. - q.Add(logger, highPod) - if p, err := q.Pop(logger); err != nil || p.Pod != highPod { - t.Errorf("Expected: %v after Pop, but got: %v", highPod.Name, p.Pod.Name) - } - q.Add(logger, midPod) - if p, err := q.Pop(logger); err != nil || p.Pod != midPod { - t.Errorf("Expected: %v after Pop, but got: %v", midPod.Name, p.Pod.Name) - } - err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPod, "fakePlugin"), q.SchedulingCycle()) + q.Add(ctx, highPod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + q.Add(ctx, midPod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(midPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, highPod, "fakePlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } - err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(midPod, "fakePlugin"), q.SchedulingCycle()) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, midPod, "fakePlugin"), q.SchedulingCycle()) if err != nil { t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) } c.Step(DefaultPodMaxInUnschedulablePodsDuration + time.Second) q.flushUnschedulablePodsLeftover(logger) - if p, err := q.Pop(logger); err != nil || p.Pod != highPod { - t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(highPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(midPod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) + } +} + +// TestFlushUnschedulablePodsLeftoverSetsFlag verifies that the WasFlushedFromUnschedulable +// flag is correctly set when pods are flushed and cleared when they return to the queue. +func TestFlushUnschedulablePodsLeftoverSetsFlag(t *testing.T) { + c := testingclock.NewFakeClock(time.Now()) + m := makeEmptyQueueingHintMapPerProfile() + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) + + pod := st.MakePod().Name("test-pod").Namespace("ns1").UID("tp-1").Priority(midPriority).NominatedNodeName("node1").Obj() + + // Add pod to activeQ and pop it to simulate a scheduling attempt + q.Add(ctx, pod) + pInfo, err := q.Pop(logger) + if err != nil { + t.Fatalf("Unexpected error from Pop: %v", err) + } + + // Verify flag is initially false + if pInfo.WasFlushedFromUnschedulable { + t.Errorf("Expected WasFlushedFromUnschedulable to be false initially, but got true") + } + + // Add pod to unschedulablePods (simulating failed scheduling) + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, pod, "fakePlugin"), q.SchedulingCycle()) + if err != nil { + t.Fatalf("Unexpected error from AddUnschedulableIfNotPresent: %v", err) + } + + // Advance time past the flush duration and flush + c.Step(DefaultPodMaxInUnschedulablePodsDuration + time.Second) + q.flushUnschedulablePodsLeftover(logger) + + // Pop the pod and verify flag is now true + pInfo, err = q.Pop(logger) + if err != nil { + t.Fatalf("Unexpected error from Pop after flush: %v", err) } - if p, err := q.Pop(logger); err != nil || p.Pod != midPod { - t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) + if !pInfo.WasFlushedFromUnschedulable { + t.Errorf("Expected WasFlushedFromUnschedulable to be true after flush, but got false") + } + + // Simulate pod failing to schedule again and returning to queue + err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(ctx, pInfo.Pod, "fakePlugin"), q.SchedulingCycle()) + if err != nil { + t.Fatalf("Unexpected error from AddUnschedulableIfNotPresent: %v", err) + } + + // Verify flag is cleared when pod returns to queue + internalPInfo := q.unschedulablePods.get(pod) + if internalPInfo == nil { + t.Fatalf("pod should be in unschedulablePods") + } + if internalPInfo.WasFlushedFromUnschedulable { + t.Errorf("Expected WasFlushedFromUnschedulable to be cleared (false) after returning to queue, but got true") } } @@ -3069,23 +3309,22 @@ func TestPriorityQueue_initPodMaxInUnschedulablePodsDuration(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() + tCtx := ktesting.Init(t) + logger := klog.FromContext(tCtx) var queue *PriorityQueue if test.podMaxInUnschedulablePodsDuration > 0 { - queue = NewTestQueue(ctx, newDefaultQueueSort(), + queue = NewTestQueue(tCtx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)), WithPodMaxInUnschedulablePodsDuration(test.podMaxInUnschedulablePodsDuration)) } else { - queue = NewTestQueue(ctx, newDefaultQueueSort(), + queue = NewTestQueue(tCtx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) } var podInfoList []*framework.QueuedPodInfo for i, op := range test.operations { - op(t, logger, queue, test.operands[i]) + op(tCtx, queue, test.operands[i]) } expectedLen := len(test.expected) @@ -3110,56 +3349,62 @@ func TestPriorityQueue_initPodMaxInUnschedulablePodsDuration(t *testing.T) { } } -type operation func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) +type operation func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) var ( - add = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { - queue.Add(logger, pInfo.Pod) + add = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + queue.Add(tCtx, pInfo.Pod) } - popAndRequeueAsUnschedulable = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + pop = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + _, err := queue.Pop(klog.FromContext(tCtx)) + if err != nil { + tCtx.Fatalf("Unexpected error during Pop: %v", err) + } + } + popAndRequeueAsUnschedulable = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. // UnschedulablePlugins will get cleared by Pop, so make a copy first. + logger := klog.FromContext(tCtx) unschedulablePlugins := pInfo.UnschedulablePlugins.Clone() - queue.Add(logger, pInfo.Pod) + queue.Add(tCtx, pInfo.Pod) p, err := queue.Pop(logger) if err != nil { - t.Fatalf("Unexpected error during Pop: %v", err) - } - if p.Pod != pInfo.Pod { - t.Fatalf("Expected: %v after Pop, but got: %v", pInfo.Pod.Name, p.Pod.Name) + tCtx.Fatalf("Unexpected error during Pop: %v", err) + } else if diff := cmp.Diff(pInfo.Pod, p.Pod); diff != "" { + tCtx.Fatalf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // Simulate plugins that are waiting for some events. p.UnschedulablePlugins = unschedulablePlugins if err := queue.AddUnschedulableIfNotPresent(logger, p, 1); err != nil { - t.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err) + tCtx.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err) } } - popAndRequeueAsBackoff = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + popAndRequeueAsBackoff = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. - queue.Add(logger, pInfo.Pod) + logger := klog.FromContext(tCtx) + queue.Add(tCtx, pInfo.Pod) p, err := queue.Pop(logger) if err != nil { - t.Fatalf("Unexpected error during Pop: %v", err) - } - if p.Pod != pInfo.Pod { - t.Fatalf("Expected: %v after Pop, but got: %v", pInfo.Pod.Name, p.Pod.Name) + tCtx.Fatalf("Unexpected error during Pop: %v", err) + } else if diff := cmp.Diff(pInfo.Pod, p.Pod); diff != "" { + tCtx.Fatalf("Unexpected pod after Pop (-want, +got):\n%s", diff) } // needs to increment it to make it backoff p.UnschedulableCount++ // When there is no known unschedulable plugin, pods always go to the backoff queue. if err := queue.AddUnschedulableIfNotPresent(logger, p, 1); err != nil { - t.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err) + tCtx.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err) } } - addPodActiveQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { - queue.Add(logger, pInfo.Pod) + addPodActiveQ = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + queue.Add(tCtx, pInfo.Pod) } - addPodActiveQDirectly = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + addPodActiveQDirectly = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { queue.activeQ.underLock(func(unlockedActiveQ unlockedActiveQueuer) { - unlockedActiveQ.add(logger, pInfo, framework.EventUnscheduledPodAdd.Label()) + unlockedActiveQ.add(klog.FromContext(tCtx), pInfo, framework.EventUnscheduledPodAdd.Label()) }) } - addPodUnschedulablePods = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + addPodUnschedulablePods = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { if !pInfo.Gated() { // Update pod condition to unschedulable. podutil.UpdatePodCondition(&pInfo.Pod.Status, &v1.PodCondition{ @@ -3171,32 +3416,38 @@ var ( // needs to increment it to make it backoff pInfo.UnschedulableCount++ } - queue.unschedulablePods.addOrUpdate(pInfo, framework.EventUnscheduledPodAdd.Label()) + queue.unschedulablePods.addOrUpdate(pInfo, false, framework.EventUnscheduledPodAdd.Label()) } - deletePod = func(t *testing.T, _ klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + deletePod = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { queue.Delete(pInfo.Pod) } - updatePodQueueable = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + updatePodQueueable = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { newPod := pInfo.Pod.DeepCopy() newPod.Labels = map[string]string{"queueable": ""} - queue.Update(logger, pInfo.Pod, newPod) + queue.Update(tCtx, pInfo.Pod, newPod) } - addPodBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { - queue.backoffQ.add(logger, pInfo, framework.EventUnscheduledPodAdd.Label()) + addPodBackoffQ = func(tCtx ktesting.TContext, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { + queue.backoffQ.add(klog.FromContext(tCtx), pInfo, framework.EventUnscheduledPodAdd.Label()) } - moveAllToActiveOrBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { - queue.MoveAllToActiveOrBackoffQueue(logger, framework.EventUnschedulableTimeout, nil, nil, nil) + moveAllToActiveOrBackoffQ = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + queue.MoveAllToActiveOrBackoffQueue(klog.FromContext(tCtx), framework.EventUnschedulableTimeout, nil, nil, nil) } - flushBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + flushBackoffQ = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { queue.clock.(*testingclock.FakeClock).Step(3 * time.Second) - queue.flushBackoffQCompleted(logger) + queue.flushBackoffQCompleted(klog.FromContext(tCtx)) } - moveClockForward = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + moveClockForward = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { queue.clock.(*testingclock.FakeClock).Step(3 * time.Second) } - flushUnscheduledQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + flushUnscheduledQ = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { queue.clock.(*testingclock.FakeClock).Step(queue.podMaxInUnschedulablePodsDuration) - queue.flushUnschedulablePodsLeftover(logger) + queue.flushUnschedulablePodsLeftover(klog.FromContext(tCtx)) + } + updatePluginToGateAllPods = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + queue.preEnqueuePluginMap[""]["preEnqueuePlugin"] = &preEnqueuePlugin{allowlists: []string{""}} + } + updatePluginToUngateAllPods = func(tCtx ktesting.TContext, queue *PriorityQueue, _ *framework.QueuedPodInfo) { + queue.preEnqueuePluginMap[""]["preEnqueuePlugin"] = &preEnqueuePlugin{allowlists: []string{"queueable"}} } ) @@ -3258,14 +3509,13 @@ func TestPodTimestamp(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) + tCtx := ktesting.Init(t) + logger := klog.FromContext(tCtx) + queue := NewTestQueue(tCtx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) var podInfoList []*framework.QueuedPodInfo for i, op := range test.operations { - op(t, logger, queue, test.operands[i]) + op(tCtx, queue, test.operands[i]) } expectedLen := len(test.expected) @@ -3290,8 +3540,8 @@ func TestPodTimestamp(t *testing.T) { } } -// TestPendingPodsMetric tests Prometheus metrics related with pending pods -func TestPendingPodsMetric(t *testing.T) { +// TestSchedulerPodsMetric tests Prometheus metrics +func TestSchedulerPodsMetric(t *testing.T) { timestamp := time.Now() preenqueuePluginName := "preEnqueuePlugin" metrics.Register() @@ -3328,6 +3578,7 @@ func TestPendingPodsMetric(t *testing.T) { operands [][]*framework.QueuedPodInfo metricsName string pluginMetricsSamplePercent int + disablePopFromBackoffQ bool wants string }{ { @@ -3543,6 +3794,165 @@ scheduler_plugin_execution_duration_seconds_sum{extension_point="PreEnqueue",plu scheduler_plugin_execution_duration_seconds_count{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success"} 1 `, // the observed value will always be 0, because we don't proceed the fake clock. }, + { + name: "Gated metric should be 1 when Ungated to Gated transition into moveToActiveQ", + operations: []operation{ + addPodUnschedulablePods, + moveClockForward, + updatePluginToGateAllPods, + updatePodQueueable, + }, + operands: [][]*framework.QueuedPodInfo{ + pInfos[:1], + {nil}, + {nil}, + pInfos[:1], + }, + metricsName: "scheduler_pending_pods", + pluginMetricsSamplePercent: 100, + wants: ` +# HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. +# TYPE scheduler_pending_pods gauge +scheduler_pending_pods{queue="active"} 0 +scheduler_pending_pods{queue="backoff"} 0 +scheduler_pending_pods{queue="gated"} 1 +scheduler_pending_pods{queue="unschedulable"} 0 +`, + }, + { + name: "Gated metric should be 1 when Ungated to Gated transition into moveToBackoffQ", + operations: []operation{ + addPodUnschedulablePods, + updatePluginToGateAllPods, + updatePodQueueable, + }, + operands: [][]*framework.QueuedPodInfo{ + pInfos[:1], + {nil}, + pInfos[:1], + }, + metricsName: "scheduler_pending_pods", + pluginMetricsSamplePercent: 100, + wants: ` +# HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. +# TYPE scheduler_pending_pods gauge +scheduler_pending_pods{queue="active"} 0 +scheduler_pending_pods{queue="backoff"} 0 +scheduler_pending_pods{queue="gated"} 1 +scheduler_pending_pods{queue="unschedulable"} 0 +`, + }, + { + name: "Gated metric should be 1 when Ungated to Gated transition when popFromBackoffQ is disabled", + operations: []operation{ + addPodUnschedulablePods, + moveClockForward, + updatePluginToGateAllPods, + updatePodQueueable, + }, + operands: [][]*framework.QueuedPodInfo{ + pInfos[:1], + {nil}, + {nil}, + pInfos[:1], + }, + metricsName: "scheduler_pending_pods", + pluginMetricsSamplePercent: 100, + disablePopFromBackoffQ: true, + wants: ` +# HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. +# TYPE scheduler_pending_pods gauge +scheduler_pending_pods{queue="active"} 0 +scheduler_pending_pods{queue="backoff"} 0 +scheduler_pending_pods{queue="gated"} 1 +scheduler_pending_pods{queue="unschedulable"} 0 +`, + }, + { + name: "Gated metric should be 0 when Ungated -> Gated -> Ungated (ActiveQ) transition", + operations: []operation{ + addPodUnschedulablePods, + moveClockForward, + updatePluginToGateAllPods, + updatePodQueueable, + updatePluginToUngateAllPods, + updatePodQueueable, + }, + operands: [][]*framework.QueuedPodInfo{ + pInfos[:1], + {nil}, + {nil}, + pInfos[:1], + {nil}, + pInfos[:1], + }, + pluginMetricsSamplePercent: 100, + metricsName: "scheduler_pending_pods", + wants: ` +# HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. +# TYPE scheduler_pending_pods gauge +scheduler_pending_pods{queue="active"} 1 +scheduler_pending_pods{queue="backoff"} 0 +scheduler_pending_pods{queue="gated"} 0 +scheduler_pending_pods{queue="unschedulable"} 0 +`, + }, + { + name: "Gated metric should be 0 when Ungated -> Gated -> Ungated (BackoffQ) transition", + operations: []operation{ + addPodUnschedulablePods, + updatePluginToGateAllPods, + updatePodQueueable, + updatePluginToUngateAllPods, + updatePodQueueable, + }, + operands: [][]*framework.QueuedPodInfo{ + pInfos[:1], + {nil}, + pInfos[:1], + {nil}, + pInfos[:1], + }, + pluginMetricsSamplePercent: 100, + metricsName: "scheduler_pending_pods", + wants: ` +# HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. +# TYPE scheduler_pending_pods gauge +scheduler_pending_pods{queue="active"} 0 +scheduler_pending_pods{queue="backoff"} 1 +scheduler_pending_pods{queue="gated"} 0 +scheduler_pending_pods{queue="unschedulable"} 0 +`, + }, + { + name: "Gated metric should be 0 when Ungated -> Gated -> Ungated transition, when popFromBackoffQ is disabled", + operations: []operation{ + addPodUnschedulablePods, + moveClockForward, + updatePluginToGateAllPods, + updatePodQueueable, + updatePluginToUngateAllPods, + updatePodQueueable, + }, + operands: [][]*framework.QueuedPodInfo{ + pInfos[:1], + {nil}, + {nil}, + pInfos[:1], + {nil}, + pInfos[:1], + }, + pluginMetricsSamplePercent: 100, + disablePopFromBackoffQ: true, + wants: ` +# HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. +# TYPE scheduler_pending_pods gauge +scheduler_pending_pods{queue="active"} 1 +scheduler_pending_pods{queue="backoff"} 0 +scheduler_pending_pods{queue="gated"} 0 +scheduler_pending_pods{queue="unschedulable"} 0 +`, + }, } resetMetrics := func() { @@ -3555,9 +3965,7 @@ scheduler_plugin_execution_duration_seconds_count{extension_point="PreEnqueue",p for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() + tCtx := ktesting.Init(t) resetMetrics() resetPodInfos() @@ -3569,11 +3977,12 @@ scheduler_plugin_execution_duration_seconds_count{extension_point="PreEnqueue",p }, } preenq := map[string]map[string]fwk.PreEnqueuePlugin{"": {(&preEnqueuePlugin{}).Name(): &preEnqueuePlugin{allowlists: []string{queueable}}}} - recorder := metrics.NewMetricsAsyncRecorder(3, 20*time.Microsecond, ctx.Done()) - queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)), WithPreEnqueuePluginMap(preenq), WithPluginMetricsSamplePercent(test.pluginMetricsSamplePercent), WithMetricsRecorder(recorder), WithQueueingHintMapPerProfile(m)) + recorder := metrics.NewMetricsAsyncRecorder(3, 20*time.Microsecond, tCtx.Done()) + queue := NewTestQueue(tCtx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)), WithPreEnqueuePluginMap(preenq), WithPluginMetricsSamplePercent(test.pluginMetricsSamplePercent), WithMetricsRecorder(recorder), WithQueueingHintMapPerProfile(m)) + queue.isPopFromBackoffQEnabled = !test.disablePopFromBackoffQ for i, op := range test.operations { for _, pInfo := range test.operands[i] { - op(t, logger, queue, pInfo) + op(tCtx, queue, pInfo) } } @@ -3605,7 +4014,7 @@ func TestPerPodSchedulingMetrics(t *testing.T) { // The queue operations are Add -> Pop. name: "pod is created and scheduled after 1 attempt", perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { - queue.Add(logger, pod) + queue.Add(ctx, pod) }, wantAttempts: 1, wantInitialAttemptTs: timestamp, @@ -3614,7 +4023,7 @@ func TestPerPodSchedulingMetrics(t *testing.T) { // The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Pop. name: "pod is created and scheduled after 2 attempts", perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { - queue.Add(logger, pod) + queue.Add(ctx, pod) pInfo, err := queue.Pop(logger) if err != nil { t.Fatalf("Failed to pop a pod %v", err) @@ -3634,7 +4043,7 @@ func TestPerPodSchedulingMetrics(t *testing.T) { // The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Update -> Pop. name: "pod is created and scheduled after 2 attempts but before the second pop, call update", perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { - queue.Add(logger, pod) + queue.Add(ctx, pod) pInfo, err := queue.Pop(logger) if err != nil { t.Fatalf("Failed to pop a pod %v", err) @@ -3649,7 +4058,7 @@ func TestPerPodSchedulingMetrics(t *testing.T) { queue.flushUnschedulablePodsLeftover(logger) newPod := pod.DeepCopy() newPod.Generation = 1 - queue.Update(logger, pod, newPod) + queue.Update(ctx, pod, newPod) }, wantAttempts: 2, wantInitialAttemptTs: timestamp, @@ -3661,10 +4070,10 @@ func TestPerPodSchedulingMetrics(t *testing.T) { // Create a queue with PreEnqueuePlugin queue.preEnqueuePluginMap = map[string]map[string]fwk.PreEnqueuePlugin{"": {(&preEnqueuePlugin{}).Name(): &preEnqueuePlugin{allowlists: []string{"foo"}}}} queue.pluginMetricsSamplePercent = 0 - queue.Add(logger, pod) + queue.Add(ctx, pod) // Check pod is added to the unschedulablePods queue. - if getUnschedulablePod(queue, pod) != pod { - t.Errorf("Pod %v was not found in the unschedulablePods.", pod.Name) + if diff := cmp.Diff(pod, getUnschedulablePod(queue, pod)); diff != "" { + t.Errorf("Unexpected pod in unschedulablePods (-want, +got):\n%s", diff) } // Override clock to get different InitialAttemptTimestamp c.Step(1 * time.Minute) @@ -3672,7 +4081,7 @@ func TestPerPodSchedulingMetrics(t *testing.T) { // Update pod with the required label to get it out of unschedulablePods queue. updateGatedPod := pod.DeepCopy() updateGatedPod.Labels = map[string]string{"foo": ""} - queue.Update(logger, pod, updateGatedPod) + queue.Update(ctx, pod, updateGatedPod) }, wantAttempts: 1, wantInitialAttemptTs: timestamp.Add(1 * time.Minute), @@ -3776,14 +4185,12 @@ func TestIncomingPodsMetrics(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() + tCtx := ktesting.Init(t) metrics.SchedulerQueueIncomingPods.Reset() - queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) + queue := NewTestQueue(tCtx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) for _, op := range test.operations { for _, pInfo := range pInfos { - op(t, logger, queue, pInfo) + op(tCtx, queue, pInfo) } } metricName := metrics.SchedulerSubsystem + "_" + metrics.SchedulerQueueIncomingPods.Name @@ -3821,7 +4228,7 @@ func TestBackOffFlow(t *testing.T) { Namespace: pod.Namespace, Name: pod.Name, } - q.Add(logger, pod) + q.Add(ctx, pod) for i, step := range steps { t.Run(fmt.Sprintf("step %d popFromBackoffQEnabled(%v)", i, popFromBackoffQEnabled), func(t *testing.T) { @@ -3941,9 +4348,11 @@ func TestMoveAllToActiveOrBackoffQueue_PreEnqueueChecks(t *testing.T) { q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c)) for _, podInfo := range tt.podInfos { // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. - q.Add(logger, podInfo.Pod) - if p, err := q.Pop(logger); err != nil || p.Pod != podInfo.Pod { - t.Errorf("Expected: %v after Pop, but got: %v", podInfo.Pod.Name, p.Pod.Name) + q.Add(ctx, podInfo.Pod) + if p, err := q.Pop(logger); err != nil { + t.Errorf("Pop failed: %v", err) + } else if diff := cmp.Diff(podInfo.Pod, p.Pod); diff != "" { + t.Errorf("Unexpected pod after Pop (-want, +got):\n%s", diff) } podInfo.UnschedulablePlugins = sets.New("plugin") err := q.AddUnschedulableIfNotPresent(logger, podInfo, q.activeQ.schedulingCycle()) @@ -4313,7 +4722,7 @@ func Test_queuedPodInfo_gatedSetUponCreationAndUnsetUponUpdate(t *testing.T) { q := NewTestQueue(ctx, newDefaultQueueSort(), WithPreEnqueuePluginMap(m)) gatedPod := st.MakePod().SchedulingGates([]string{"hello world"}).Obj() - q.Add(logger, gatedPod) + q.Add(ctx, gatedPod) if !q.unschedulablePods.get(gatedPod).Gated() { t.Error("Expected pod to be gated") @@ -4321,7 +4730,7 @@ func Test_queuedPodInfo_gatedSetUponCreationAndUnsetUponUpdate(t *testing.T) { ungatedPod := gatedPod.DeepCopy() ungatedPod.Spec.SchedulingGates = nil - q.Update(logger, gatedPod, ungatedPod) + q.Update(ctx, gatedPod, ungatedPod) ungatedPodInfo, _ := q.Pop(logger) if ungatedPodInfo.Gated() { @@ -4355,7 +4764,7 @@ func TestPriorityQueue_GetPod(t *testing.T) { unlockedActiveQ.add(logger, newQueuedPodInfoForLookup(activeQPod), framework.EventUnscheduledPodAdd.Label()) }) q.backoffQ.add(logger, newQueuedPodInfoForLookup(backoffQPod), framework.EventUnscheduledPodAdd.Label()) - q.unschedulablePods.addOrUpdate(newQueuedPodInfoForLookup(unschedPod), framework.EventUnscheduledPodAdd.Label()) + q.unschedulablePods.addOrUpdate(newQueuedPodInfoForLookup(unschedPod), false, framework.EventUnscheduledPodAdd.Label()) tests := []struct { name string @@ -4414,3 +4823,398 @@ func TestPriorityQueue_GetPod(t *testing.T) { }) } } + +func TestUnschedulablePodsMetric(t *testing.T) { + type step func(tCtx ktesting.TContext, q *PriorityQueue) + + addPod := func(pInfo *framework.QueuedPodInfo) step { + return func(tCtx ktesting.TContext, q *PriorityQueue) { + add(tCtx, q, pInfo) + } + } + deletePod := func(pInfo *framework.QueuedPodInfo) step { + return func(tCtx ktesting.TContext, q *PriorityQueue) { + deletePod(tCtx, q, pInfo) + } + } + popPod := func() step { + return func(tCtx ktesting.TContext, q *PriorityQueue) { + pop(tCtx, q, nil) + } + } + moveAllToActiveOrBackoffQ := func() step { + return func(tCtx ktesting.TContext, q *PriorityQueue) { + moveAllToActiveOrBackoffQ(tCtx, q, nil) + } + } + updatePluginAllowList := func(pluginName string, list []string) step { + return func(tCtx ktesting.TContext, q *PriorityQueue) { + q.preEnqueuePluginMap[""][pluginName].(*preEnqueuePlugin).allowlists = list + } + } + + pluginName1 := "plugin1" + pluginName2 := "plugin2" + queueable := "queueable" + timestamp := time.Now() + pod := &framework.QueuedPodInfo{ + PodInfo: mustNewPodInfo( + st.MakePod().Name("podA").Namespace("namespaceA").Label(queueable, "").UID("someUid").Obj()), + Timestamp: timestamp, + UnschedulablePlugins: sets.New[string](), + } + + resetMetrics := func() { + metrics.UnschedulableReason(pluginName1, "").Set(0) + metrics.UnschedulableReason(pluginName2, "").Set(0) + } + + makeGated := func(pInfo *framework.QueuedPodInfo) *framework.QueuedPodInfo { + return setQueuedPodInfoGated(pInfo.DeepCopy(), pluginName1, []fwk.ClusterEvent{framework.EventUnschedulableTimeout}) + } + + tests := []struct { + name string + steps []step + expectedMetrics []int + }{ + { + name: "Unschedulable pods metric must be 0 after a pod is gated, ungated, re-queued, and eventually popped from the scheduling queue", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(pod), + moveAllToActiveOrBackoffQ(), + updatePluginAllowList(pluginName1, []string{queueable}), + moveAllToActiveOrBackoffQ(), + popPod(), + }, + expectedMetrics: []int{0, 0}, + }, + { + name: "Unschedulable pods metric must be 0 after pod is gated and then deleted", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(pod), + moveAllToActiveOrBackoffQ(), + deletePod(pod), + }, + expectedMetrics: []int{0, 0}, + }, + { + name: "Unschedulable pods metric must be 1 after pod is gated multiple time by the same plugin", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(pod), + moveAllToActiveOrBackoffQ(), + }, + expectedMetrics: []int{1, 0}, + }, + { + name: "Unschedulable pods metric must be 0 after non gated pods is added and then deleted", + steps: []step{ + addPod(pod), + deletePod(pod), + }, + expectedMetrics: []int{0, 0}, + }, + { + name: "Unschedulable pods metric should not be duplicate if gated pods added and then gated with the same plugin again", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(makeGated(pod)), + moveAllToActiveOrBackoffQ(), + }, + expectedMetrics: []int{1, 0}, + }, + { + name: "Unschedulable pods metric should be 0 if pod was gated by two plugins sequentially and then ungated and popped", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(pod), + updatePluginAllowList(pluginName1, []string{queueable}), + updatePluginAllowList(pluginName2, []string{}), + moveAllToActiveOrBackoffQ(), + updatePluginAllowList(pluginName2, []string{queueable}), + moveAllToActiveOrBackoffQ(), + popPod(), + }, + expectedMetrics: []int{0, 0}, + }, + { + name: "Unschedulable pods metric should be 0 if pod was gated by two plugins sequentially and then deleted", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(pod), + updatePluginAllowList(pluginName1, []string{queueable}), + updatePluginAllowList(pluginName2, []string{}), + moveAllToActiveOrBackoffQ(), + deletePod(pod), + }, + expectedMetrics: []int{0, 0}, + }, + { + name: "Unschedulable pods metric should be 1 for both plugins if pod was gated by two plugins sequentially", + steps: []step{ + updatePluginAllowList(pluginName1, []string{}), + addPod(pod), + updatePluginAllowList(pluginName1, []string{queueable}), + updatePluginAllowList(pluginName2, []string{}), + moveAllToActiveOrBackoffQ(), + }, + expectedMetrics: []int{1, 1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tCtx := ktesting.Init(t) + resetMetrics() + + m := makeEmptyQueueingHintMapPerProfile() + m[""][framework.EventUnschedulableTimeout] = []*QueueingHintFunction{ + { + PluginName: pluginName1, + QueueingHintFn: queueHintReturnQueue, + }, + { + PluginName: pluginName2, + QueueingHintFn: queueHintReturnQueue, + }, + } + + plugin1 := preEnqueuePlugin{name: pluginName1, allowlists: []string{queueable}} + plugin2 := preEnqueuePlugin{name: pluginName2, allowlists: []string{queueable}} + + preenq := map[string]map[string]fwk.PreEnqueuePlugin{"": {pluginName1: &plugin1, pluginName2: &plugin2}} + recorder := metrics.NewMetricsAsyncRecorder(3, 20*time.Microsecond, tCtx.Done()) + q := NewTestQueue(tCtx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)), WithPreEnqueuePluginMap(preenq), WithMetricsRecorder(recorder), WithQueueingHintMapPerProfile(m)) + + for _, step := range tt.steps { + step(tCtx, q) + } + + for i, pluginName := range []string{pluginName1, pluginName2} { + val, err := testutil.GetGaugeMetricValue(metrics.UnschedulableReason(pluginName, "")) + + if err != nil { + t.Errorf("Error while collection metric value:\n%s", err) + } + if int(val) != tt.expectedMetrics[i] { + t.Errorf("Unexpected metric for plugin %s result expected %d, actual %d", pluginName, tt.expectedMetrics[i], int(val)) + } + } + + }) + } +} + +func TestPriorityQueue_signPod(t *testing.T) { + tests := []struct { + name string + enableFeatureGate bool + signers map[string]PodSigner + pod *v1.Pod + expectedSignature fwk.PodSignature + }{ + { + name: "Feature gate disabled", + enableFeatureGate: false, + signers: map[string]PodSigner{ + "default-scheduler": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + return fwk.PodSignature("sig-1") + }, + }, + pod: st.MakePod().Name("pod1").SchedulerName("default-scheduler").Obj(), + }, + { + name: "No signers configured", + enableFeatureGate: true, + signers: nil, + pod: st.MakePod().Name("pod1").SchedulerName("default-scheduler").Obj(), + }, + { + name: "Signer not found for scheduler", + enableFeatureGate: true, + signers: map[string]PodSigner{ + "default-scheduler": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + return fwk.PodSignature("sig-1") + }, + }, + pod: st.MakePod().Name("pod1").SchedulerName("custom-scheduler").Obj(), + }, + { + name: "Successful signature computation", + enableFeatureGate: true, + signers: map[string]PodSigner{ + "default-scheduler": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + return fwk.PodSignature("sig-1") + }, + }, + pod: st.MakePod().Name("pod1").SchedulerName("default-scheduler").Obj(), + expectedSignature: fwk.PodSignature("sig-1"), + }, + { + name: "Signer returns nil (unsignable pod)", + enableFeatureGate: true, + signers: map[string]PodSigner{ + "default-scheduler": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + return nil + }, + }, + pod: st.MakePod().Name("pod1").SchedulerName("default-scheduler").Obj(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.OpportunisticBatching, tt.enableFeatureGate) + + tCtx := ktesting.Init(t) + q := NewTestQueue(tCtx, newDefaultQueueSort(), WithPodSigners(tt.signers)) + + signature := q.signPod(tCtx, tt.pod) + + if !bytes.Equal(signature, tt.expectedSignature) { + t.Errorf("Expected signature '%s', got '%s'", string(tt.expectedSignature), string(signature)) + } + }) + } +} + +func TestPriorityQueue_AddComputesSignature(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.OpportunisticBatching, true) + + signers := map[string]PodSigner{ + "default-scheduler": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + if val, ok := pod.Labels["key"]; ok { + return fwk.PodSignature(fmt.Sprintf("sig-%s", val)) + } + return fwk.PodSignature("sig-default") + }, + } + + tCtx := ktesting.Init(t) + q := NewTestQueue(tCtx, newDefaultQueueSort(), WithPodSigners(signers)) + + pod := st.MakePod().Name("pod1").SchedulerName("default-scheduler").Label("key", "value1").Obj() + q.Add(tCtx, pod) + + pInfo, exists := q.GetPod(pod.Name, pod.Namespace) + if !exists { + t.Fatal("Pod not found in queue after Add") + } + if !bytes.Equal(pInfo.PodSignature, fwk.PodSignature("sig-value1")) { + t.Errorf("Expected signature 'sig-value1', got '%s'", string(pInfo.PodSignature)) + } +} + +func TestPriorityQueue_UpdateRecomputesSignature(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.OpportunisticBatching, true) + + signers := map[string]PodSigner{ + "default-scheduler": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + if val, ok := pod.Labels["key"]; ok { + return fwk.PodSignature(fmt.Sprintf("sig-%s", val)) + } + return fwk.PodSignature("sig-default") + }, + } + + pod1 := st.MakePod().Name("pod1").SchedulerName("default-scheduler").Label("key", "value1").Obj() + pod2 := pod1.DeepCopy() + pod2.Labels["key"] = "value2" + + tests := []struct { + name string + prepareFunc func(tCtx ktesting.TContext, q *PriorityQueue) + }{ + { + name: "pod in activeQ", + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) { + q.Add(tCtx, pod1) + }, + }, + { + name: "pod in backoffQ", + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) { + pInfo := q.newQueuedPodInfo(tCtx, pod1) + q.backoffQ.add(klog.FromContext(tCtx), pInfo, framework.EventUnscheduledPodAdd.Label()) + }, + }, + { + name: "pod in unschedulablePods", + prepareFunc: func(tCtx ktesting.TContext, q *PriorityQueue) { + pInfo := q.newQueuedPodInfo(tCtx, pod1) + q.unschedulablePods.addOrUpdate(pInfo, false, framework.EventUnscheduledPodAdd.Label()) + }, + }, + { + name: "pod not in any queue", + prepareFunc: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tCtx := ktesting.Init(t) + q := NewTestQueue(tCtx, newDefaultQueueSort(), WithPodSigners(signers)) + + if tt.prepareFunc != nil { + tt.prepareFunc(tCtx, q) + } + + // Update pod with different label + q.Update(tCtx, pod1, pod2) + + // Check signature was recomputed + pInfo, exists := q.GetPod(pod2.Name, pod2.Namespace) + if !exists { + t.Fatal("Pod not found in queue after update") + } + if !bytes.Equal(pInfo.PodSignature, fwk.PodSignature("sig-value2")) { + t.Errorf("Expected signature 'sig-value2', got '%s'", string(pInfo.PodSignature)) + } + }) + } +} + +func TestPriorityQueue_MultipleProfiles(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.OpportunisticBatching, true) + + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + signers := map[string]PodSigner{ + "scheduler-1": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + return fwk.PodSignature("sig-scheduler-1") + }, + "scheduler-2": func(ctx context.Context, pod *v1.Pod) fwk.PodSignature { + return fwk.PodSignature("sig-scheduler-2") + }, + } + + q := NewTestQueue(ctx, newDefaultQueueSort(), WithPodSigners(signers)) + + pod1 := st.MakePod().Name("pod1").SchedulerName("scheduler-1").Obj() + pod2 := st.MakePod().Name("pod2").SchedulerName("scheduler-2").Obj() + pod3 := st.MakePod().Name("pod3").SchedulerName("scheduler-3").Obj() // No signer + + q.Add(ctx, pod1) + q.Add(ctx, pod2) + q.Add(ctx, pod3) + + pInfo1, _ := q.GetPod(pod1.Name, pod1.Namespace) + pInfo2, _ := q.GetPod(pod2.Name, pod2.Namespace) + pInfo3, _ := q.GetPod(pod3.Name, pod3.Namespace) + + if !bytes.Equal(pInfo1.PodSignature, fwk.PodSignature("sig-scheduler-1")) { + t.Errorf("Pod1: expected 'sig-scheduler-1', got '%s'", string(pInfo1.PodSignature)) + } + if !bytes.Equal(pInfo2.PodSignature, fwk.PodSignature("sig-scheduler-2")) { + t.Errorf("Pod2: expected 'sig-scheduler-2', got '%s'", string(pInfo2.PodSignature)) + } + if pInfo3.PodSignature != nil { + t.Errorf("Pod3: expected nil signature (no signer), got '%s'", string(pInfo3.PodSignature)) + } +} diff --git a/pkg/scheduler/backend/queue/unschedulable_pods.go b/pkg/scheduler/backend/queue/unschedulable_pods.go index 4c248bfcd5646..2fff92d499e52 100644 --- a/pkg/scheduler/backend/queue/unschedulable_pods.go +++ b/pkg/scheduler/backend/queue/unschedulable_pods.go @@ -28,8 +28,10 @@ type unschedulablePods struct { // podInfoMap is a map key by a pod's full-name and the value is a pointer to the QueuedPodInfo. podInfoMap map[string]*framework.QueuedPodInfo keyFunc func(*v1.Pod) string - // unschedulableRecorder/gatedRecorder updates the counter when elements of an unschedulablePods - // get added or removed, and it does nothing if it's nil. + // unschedulableRecorder and gatedRecorder track the number of pods in the unschedulable queue. + // unschedulableRecorder tracks standard unschedulable pods, while gatedRecorder tracks pods + // that are specifically blocked by scheduling gates. These recorders handle + // increments, decrements, and transitions (Gated <-> Ungated). unschedulableRecorder, gatedRecorder metrics.MetricRecorder } @@ -43,14 +45,34 @@ func newUnschedulablePods(unschedulableRecorder, gatedRecorder metrics.MetricRec } } +// updateMetricsOnStateChange handles the metric accounting when a pod changes +// between Gated and Unschedulable states. +func (u *unschedulablePods) updateMetricsOnStateChange(gatedBefore, isGated bool) { + if gatedBefore == isGated { + return + } + + if gatedBefore { + // Transition: Gated -> Ungated + u.gatedRecorder.Dec() + u.unschedulableRecorder.Inc() + } else { + // Transition: Ungated -> Gated + u.gatedRecorder.Inc() + u.unschedulableRecorder.Dec() + } +} + // addOrUpdate adds a pod to the unschedulable podInfoMap. // The event should show which event triggered the addition and is used for the metric recording. -func (u *unschedulablePods) addOrUpdate(pInfo *framework.QueuedPodInfo, event string) { +func (u *unschedulablePods) addOrUpdate(pInfo *framework.QueuedPodInfo, gatedBefore bool, event string) { podID := u.keyFunc(pInfo.Pod) - if _, exists := u.podInfoMap[podID]; !exists { - if pInfo.Gated() && u.gatedRecorder != nil { + if _, exists := u.podInfoMap[podID]; exists { + u.updateMetricsOnStateChange(gatedBefore, pInfo.Gated()) + } else { + if pInfo.Gated() { u.gatedRecorder.Inc() - } else if !pInfo.Gated() && u.unschedulableRecorder != nil { + } else { u.unschedulableRecorder.Inc() } metrics.SchedulerQueueIncomingPods.WithLabelValues("unschedulable", event).Inc() @@ -63,9 +85,9 @@ func (u *unschedulablePods) addOrUpdate(pInfo *framework.QueuedPodInfo, event st func (u *unschedulablePods) delete(pod *v1.Pod, gated bool) { podID := u.keyFunc(pod) if _, exists := u.podInfoMap[podID]; exists { - if gated && u.gatedRecorder != nil { + if gated { u.gatedRecorder.Dec() - } else if !gated && u.unschedulableRecorder != nil { + } else { u.unschedulableRecorder.Dec() } } @@ -85,10 +107,6 @@ func (u *unschedulablePods) get(pod *v1.Pod) *framework.QueuedPodInfo { // clear removes all the entries from the unschedulable podInfoMap. func (u *unschedulablePods) clear() { u.podInfoMap = make(map[string]*framework.QueuedPodInfo) - if u.unschedulableRecorder != nil { - u.unschedulableRecorder.Clear() - } - if u.gatedRecorder != nil { - u.gatedRecorder.Clear() - } + u.unschedulableRecorder.Clear() + u.gatedRecorder.Clear() } diff --git a/pkg/scheduler/backend/queue/unschedulable_pods_test.go b/pkg/scheduler/backend/queue/unschedulable_pods_test.go index c1e126ed8eb06..37bb55d354f10 100644 --- a/pkg/scheduler/backend/queue/unschedulable_pods_test.go +++ b/pkg/scheduler/backend/queue/unschedulable_pods_test.go @@ -17,6 +17,7 @@ limitations under the License. package queue import ( + "sync/atomic" "testing" "github.com/google/go-cmp/cmp" @@ -24,115 +25,389 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/metrics" st "k8s.io/kubernetes/pkg/scheduler/testing" "k8s.io/kubernetes/pkg/scheduler/util" ) +var _ metrics.MetricRecorder = &mockMetricRecorder{} + +type mockMetricRecorder struct { + val atomic.Int64 +} + +func (m *mockMetricRecorder) Inc() { + m.val.Add(1) +} + +func (m *mockMetricRecorder) Dec() { + m.val.Add(-1) +} + +func (m *mockMetricRecorder) Clear() { + m.val.Store(0) +} + +func (m *mockMetricRecorder) value() int64 { + return m.val.Load() +} + func TestUnschedulablePods(t *testing.T) { + type action string + + const ( + add action = "adding" + update action = "updating" + delete action = "deleting" + clear action = "clearing" + ) + + type step struct { + action action + pods []*framework.QueuedPodInfo + expectedPods []*framework.QueuedPodInfo + } + + var actionToOperation = map[action]func(upm *unschedulablePods, pInfo *framework.QueuedPodInfo, gatedBefore bool){ + add: func(upm *unschedulablePods, pInfo *framework.QueuedPodInfo, _ bool) { + upm.addOrUpdate(pInfo, false, framework.EventUnscheduledPodAdd.Label()) + }, + update: func(upm *unschedulablePods, pInfo *framework.QueuedPodInfo, gatedBefore bool) { + upm.addOrUpdate(pInfo, gatedBefore, framework.EventUnscheduledPodUpdate.Label()) + }, + delete: func(upm *unschedulablePods, pInfo *framework.QueuedPodInfo, gatedBefore bool) { + upm.delete(pInfo.Pod, gatedBefore) + }, + clear: func(upm *unschedulablePods, _ *framework.QueuedPodInfo, _ bool) { + upm.clear() + }, + } + var pods = []*v1.Pod{ st.MakePod().Name("p0").Namespace("ns1").Annotation("annot1", "val1").NominatedNodeName("node1").Obj(), st.MakePod().Name("p1").Namespace("ns1").Annotation("annot", "val").Obj(), st.MakePod().Name("p2").Namespace("ns2").Annotation("annot2", "val2").Annotation("annot3", "val3").NominatedNodeName("node3").Obj(), - st.MakePod().Name("p3").Namespace("ns4").Annotation("annot2", "val2").Annotation("annot3", "val3").NominatedNodeName("node1").Obj(), + st.MakePod().Name("p3").Namespace("ns1").Annotation("annot4", "val4").Obj(), + st.MakePod().Name("p4").Namespace("ns2").Annotation("annot5", "val5").NominatedNodeName("node4").Obj(), + st.MakePod().Name("p5").Namespace("ns1").Annotation("annot6", "val6").Obj(), + } + + gated := func(p *v1.Pod, upm *unschedulablePods) bool { + pInfo := upm.get(p) + return pInfo != nil && pInfo.Gated() + } + + makePodInfo := func(p *v1.Pod, gated bool) *framework.QueuedPodInfo { + info := &framework.QueuedPodInfo{ + PodInfo: mustNewTestPodInfo(t, p), + UnschedulablePlugins: sets.New[string](), + } + if gated { + info.GatingPlugin = "test" + info.UnschedulablePlugins.Insert("test") + } + return info + } + + makePodInfoMap := func(pods []*framework.QueuedPodInfo) map[string]*framework.QueuedPodInfo { + podInfoMap := make(map[string]*framework.QueuedPodInfo) + for _, p := range pods { + podInfoMap[util.GetPodFullName(p.Pod)] = p + } + return podInfoMap } - var updatedPods = make([]*v1.Pod, len(pods)) - updatedPods[0] = pods[0].DeepCopy() - updatedPods[1] = pods[1].DeepCopy() - updatedPods[3] = pods[3].DeepCopy() tests := []struct { - name string - podsToAdd []*v1.Pod - expectedMapAfterAdd map[string]*framework.QueuedPodInfo - podsToUpdate []*v1.Pod - expectedMapAfterUpdate map[string]*framework.QueuedPodInfo - podsToDelete []*v1.Pod - expectedMapAfterDelete map[string]*framework.QueuedPodInfo + name string + steps []step }{ { - name: "create, update, delete subset of pods", - podsToAdd: []*v1.Pod{pods[0], pods[1], pods[2], pods[3]}, - expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, + name: "create, update, delete subset of pods", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + }, + }, + { + action: update, + pods: []*framework.QueuedPodInfo{makePodInfo(pods[0], false)}, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + }, + }, + { + action: delete, + pods: []*framework.QueuedPodInfo{makePodInfo(pods[0], false), makePodInfo(pods[1], false)}, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + }, + }, }, - podsToUpdate: []*v1.Pod{updatedPods[0]}, - expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, updatedPods[0]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, + }, + { + name: "create, update, delete all", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{makePodInfo(pods[0], false), makePodInfo(pods[3], false)}, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[3], false), + }, + }, + { + action: update, + pods: []*framework.QueuedPodInfo{makePodInfo(pods[3], false)}, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[3], false), + }, + }, + { + action: delete, + pods: []*framework.QueuedPodInfo{makePodInfo(pods[0], false), makePodInfo(pods[3], false)}, + expectedPods: []*framework.QueuedPodInfo{}, + }, }, - podsToDelete: []*v1.Pod{pods[0], pods[1]}, - expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, + }, + { + name: "delete non-existing and existing pods", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + }, + }, + { + action: update, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[1], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + }, + }, + { + action: delete, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[1], false), + }, + }, }, }, { - name: "create, update, delete all", - podsToAdd: []*v1.Pod{pods[0], pods[3]}, - expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, + name: "add/delete gated pods", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + makePodInfo(pods[1], true), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + makePodInfo(pods[1], true), + }, + }, + { + action: delete, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[1], true), + }, + }, }, - podsToUpdate: []*v1.Pod{updatedPods[3]}, - expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, updatedPods[3]), UnschedulablePlugins: sets.New[string]()}, + }, + { + name: "add gated and non-gated pods, then delete", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], true), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], true), + }, + }, + { + action: delete, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], true), + }, + expectedPods: []*framework.QueuedPodInfo{}, + }, }, - podsToDelete: []*v1.Pod{pods[0], pods[3]}, - expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{}, }, { - name: "delete non-existing and existing pods", - podsToAdd: []*v1.Pod{pods[1], pods[2]}, - expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, + name: "add gated pod, update it to non-gated", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + }, + }, + { + action: update, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + }, + }, + { + action: delete, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + }, + expectedPods: []*framework.QueuedPodInfo{}, + }, }, - podsToUpdate: []*v1.Pod{updatedPods[1]}, - expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, updatedPods[1]), UnschedulablePlugins: sets.New[string]()}, - util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, + }, + { + name: "add non-gated pod, update it to gated", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + }, + }, + { + action: update, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + }, + }, }, - podsToDelete: []*v1.Pod{pods[2], pods[3]}, - expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{ - util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, updatedPods[1]), UnschedulablePlugins: sets.New[string]()}, + }, + { + name: "add 4 ungated and 2 gated, update 2 to gated and 1 to ungated", + steps: []step{ + { + action: add, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + makePodInfo(pods[4], true), + makePodInfo(pods[5], true), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], false), + makePodInfo(pods[1], false), + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + makePodInfo(pods[4], true), + makePodInfo(pods[5], true), + }, + }, + { + action: update, + pods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + makePodInfo(pods[1], true), + makePodInfo(pods[4], false), + }, + expectedPods: []*framework.QueuedPodInfo{ + makePodInfo(pods[0], true), + makePodInfo(pods[1], true), + makePodInfo(pods[2], false), + makePodInfo(pods[3], false), + makePodInfo(pods[4], false), + makePodInfo(pods[5], true), + }, + }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - upm := newUnschedulablePods(nil, nil) - for _, p := range test.podsToAdd { - upm.addOrUpdate(newQueuedPodInfoForLookup(p), framework.EventUnscheduledPodAdd.Label()) - } - if diff := cmp.Diff(test.expectedMapAfterAdd, upm.podInfoMap, cmpopts.IgnoreUnexported(framework.PodInfo{})); diff != "" { - t.Errorf("Unexpected map after adding pods(-want, +got):\n%s", diff) - } + unschedulableRecorder := &mockMetricRecorder{} + gatedRecorder := &mockMetricRecorder{} + upm := newUnschedulablePods(unschedulableRecorder, gatedRecorder) + assertMetrics := func(expectedPods []*framework.QueuedPodInfo, action string) { + t.Helper() - if len(test.podsToUpdate) > 0 { - for _, p := range test.podsToUpdate { - upm.addOrUpdate(newQueuedPodInfoForLookup(p), framework.EventUnscheduledPodUpdate.Label()) + expectedUnschedulableMetric := 0 + expectedGatedMetric := 0 + for _, p := range expectedPods { + if p.Gated() { + expectedGatedMetric++ + } else { + expectedUnschedulableMetric++ + } } - if diff := cmp.Diff(test.expectedMapAfterUpdate, upm.podInfoMap, cmpopts.IgnoreUnexported(framework.PodInfo{})); diff != "" { - t.Errorf("Unexpected map after updating pods (-want, +got):\n%s", diff) + if unschedulableRecorder.value() != int64(expectedUnschedulableMetric) { + t.Errorf("Expected unschedulable metric to be %d, but got %d after %s", expectedUnschedulableMetric, unschedulableRecorder.value(), action) + } + if gatedRecorder.value() != int64(expectedGatedMetric) { + t.Errorf("Expected gated metric to be %d, but got %d after %s", expectedGatedMetric, gatedRecorder.value(), action) } } - for _, p := range test.podsToDelete { - upm.delete(p, false) - } - if diff := cmp.Diff(test.expectedMapAfterDelete, upm.podInfoMap, cmpopts.IgnoreUnexported(framework.PodInfo{})); diff != "" { - t.Errorf("Unexpected map after deleting pods (-want, +got):\n%s", diff) + + for _, step := range test.steps { + op := actionToOperation[step.action] + for _, p := range step.pods { + op(upm, p, gated(p.Pod, upm)) + } + if diff := cmp.Diff(makePodInfoMap(step.expectedPods), upm.podInfoMap, cmpopts.IgnoreUnexported(framework.PodInfo{})); diff != "" { + t.Errorf("Unexpected map after %s pods(-want, +got):\n%s", step.action, diff) + } + + assertMetrics(step.expectedPods, string(step.action)) } + upm.clear() if len(upm.podInfoMap) != 0 { t.Errorf("Expected the map to be empty, but has %v elements.", len(upm.podInfoMap)) } + assertMetrics([]*framework.QueuedPodInfo{}, string(clear)) }) } } diff --git a/pkg/scheduler/backend/workloadmanager/podgroupinfo.go b/pkg/scheduler/backend/workloadmanager/podgroupinfo.go deleted file mode 100644 index 5d60db349eb05..0000000000000 --- a/pkg/scheduler/backend/workloadmanager/podgroupinfo.go +++ /dev/null @@ -1,197 +0,0 @@ -/* -Copyright 2025 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 workloadmanager - -import ( - "sync" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/ptr" -) - -// DefaultSchedulingTimeoutDuration defines how long the gang pods should wait at the -// Permit stage for a quorum before being rejected. -// Variable is exported only for testing purposes. -var DefaultSchedulingTimeoutDuration = 5 * time.Minute - -// podGroupKey uniquely identifies a specific instance of a PodGroup. -type podGroupKey struct { - namespace string - workloadName string - podGroupName string - replicaKey string -} - -func newPodGroupKey(namespace string, workloadRef *v1.WorkloadReference) podGroupKey { - return podGroupKey{ - namespace: namespace, - workloadName: workloadRef.Name, - podGroupName: workloadRef.PodGroup, - replicaKey: workloadRef.PodGroupReplicaKey, - } -} - -// podGroupInfo holds the runtime state of a pod group. -type podGroupInfo struct { - lock sync.RWMutex - // allPods tracks all pods belonging to the group that are known to the scheduler. - allPods map[types.UID]*v1.Pod - // unscheduledPods tracks all pods that are unscheduled for this group, - // i.e., are neither assumed nor scheduled. - unscheduledPods sets.Set[types.UID] - // assumedPods tracks pods that have reached the Reserve stage and are waiting - // for the rest of the gang to arrive before being allowed to bind. - assumedPods sets.Set[types.UID] - // assignedPods tracks all pods belonging to the group that are assigned (bound). - assignedPods sets.Set[types.UID] - // schedulingDeadline stores the time at which the gang will time out. - // It is initialized when the first pod from the group enters the Permit stage. - schedulingDeadline *time.Time -} - -func newPodGroupInfo() *podGroupInfo { - return &podGroupInfo{ - allPods: make(map[types.UID]*v1.Pod), - unscheduledPods: sets.New[types.UID](), - assumedPods: sets.New[types.UID](), - assignedPods: sets.New[types.UID](), - } -} - -// addPod adds the pod to this group. -// Depending on the NodeName, it can insert the pod to assignedPods set. -func (pgs *podGroupInfo) addPod(pod *v1.Pod) { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - pgs.allPods[pod.UID] = pod - if pod.Spec.NodeName != "" { - pgs.assignedPods.Insert(pod.UID) - } else { - pgs.unscheduledPods.Insert(pod.UID) - } -} - -// updatePod updates the pod in this group. -// In case of binding, it moves the pod to assignedPods. -func (pgs *podGroupInfo) updatePod(oldPod, newPod *v1.Pod) { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - pgs.allPods[newPod.UID] = newPod - if oldPod.Spec.NodeName == "" && newPod.Spec.NodeName != "" { - pgs.assignedPods.Insert(newPod.UID) - // Clear pod from unscheduled and assumed when it is assigned. - pgs.unscheduledPods.Delete(newPod.UID) - pgs.assumedPods.Delete(newPod.UID) - } -} - -// deletePod completely deletes the pod from this group. -func (pgs *podGroupInfo) deletePod(podUID types.UID) { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - delete(pgs.allPods, podUID) - pgs.unscheduledPods.Delete(podUID) - pgs.assumedPods.Delete(podUID) - pgs.assignedPods.Delete(podUID) -} - -// empty returns true when the group is empty. -func (pgs *podGroupInfo) empty() bool { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - return len(pgs.allPods) == 0 -} - -// AllPods returns the UIDs of all pods known to the scheduler for this group. -func (pgs *podGroupInfo) AllPods() sets.Set[types.UID] { - pgs.lock.RLock() - defer pgs.lock.RUnlock() - - return sets.KeySet(pgs.allPods) -} - -// UnscheduledPods returns all pods that are unscheduled for this group, -// i.e., are neither assumed nor assigned. -// The returned map type corresponds to the argument of the PodActivator.Activate method. -func (pgs *podGroupInfo) UnscheduledPods() map[string]*v1.Pod { - pgs.lock.RLock() - defer pgs.lock.RUnlock() - - unscheduledPods := make(map[string]*v1.Pod, len(pgs.unscheduledPods)) - for podUID := range pgs.unscheduledPods { - pod := pgs.allPods[podUID] - unscheduledPods[pod.Name] = pod - } - return unscheduledPods -} - -// AssumedPods returns the UIDs of all pods for this group in the assumed state, -// i.e., passed the Reserve gate. -func (pgs *podGroupInfo) AssumedPods() sets.Set[types.UID] { - pgs.lock.RLock() - defer pgs.lock.RUnlock() - - return pgs.assumedPods.Clone() -} - -// AssignedPods returns the UIDs of all pods already assigned (bound) for this group. -func (pgs *podGroupInfo) AssignedPods() sets.Set[types.UID] { - pgs.lock.RLock() - defer pgs.lock.RUnlock() - - return pgs.assignedPods.Clone() -} - -// SchedulingTimeout returns the remaining time until the pod group scheduling times out. -// A new deadline is created if one doesn't exist, or if the previous one has expired. -func (pgs *podGroupInfo) SchedulingTimeout() time.Duration { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - now := time.Now() - // A new deadline is set if one doesn't exist, or if the old one has passed. - // This allows a new attempt to form a gang after a previous attempt timed out. - if pgs.schedulingDeadline == nil || pgs.schedulingDeadline.Before(now) { - pgs.schedulingDeadline = ptr.To(now.Add(DefaultSchedulingTimeoutDuration)) - } - return pgs.schedulingDeadline.Sub(now) -} - -// AssumePod marks a pod as having reached the Reserve stage. -func (pgs *podGroupInfo) AssumePod(podUID types.UID) { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - pgs.assumedPods.Insert(podUID) - pgs.unscheduledPods.Delete(podUID) -} - -// ForgetPod removes a pod from the assumed state. -func (pgs *podGroupInfo) ForgetPod(podUID types.UID) { - pgs.lock.Lock() - defer pgs.lock.Unlock() - - pgs.unscheduledPods.Insert(podUID) - pgs.assumedPods.Delete(podUID) -} diff --git a/pkg/scheduler/backend/workloadmanager/podgroupinfo_test.go b/pkg/scheduler/backend/workloadmanager/podgroupinfo_test.go deleted file mode 100644 index ce47acb2eb8aa..0000000000000 --- a/pkg/scheduler/backend/workloadmanager/podgroupinfo_test.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2025 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 workloadmanager - -import ( - "testing" - "time" - - v1 "k8s.io/api/core/v1" - st "k8s.io/kubernetes/pkg/scheduler/testing" - "k8s.io/utils/ptr" -) - -func TestPodGroupInfo_AssumeForget(t *testing.T) { - pgi := newPodGroupInfo() - pod := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - - pgi.addPod(pod) - if pgi.AssumedPods().Has(pod.UID) { - t.Fatal("AssumedPods should be initially empty") - } - if !pgi.unscheduledPods.Has(pod.UID) { - t.Fatal("Pod should be initially in UnscheduledPods") - } - - pgi.AssumePod(pod.UID) - if !pgi.AssumedPods().Has(pod.UID) { - t.Fatal("Pod should be in AssumedPods after AssumePod") - } - if pgi.unscheduledPods.Has(pod.UID) { - t.Fatal("UnscheduledPods should be empty after AssumePod") - } - - pgi.ForgetPod(pod.UID) - if pgi.AssumedPods().Has(pod.UID) { - t.Fatal("Pod should not be in AssumedPods after ForgetPod") - } - if !pgi.unscheduledPods.Has(pod.UID) { - t.Fatal("Pod should be in UnscheduledPods after ForgetPod") - } -} - -func TestPodGroupInfo_SchedulingTimeout(t *testing.T) { - pgi := newPodGroupInfo() - - timeout := pgi.SchedulingTimeout() - if pgi.schedulingDeadline == nil { - t.Fatal("Scheduling deadline should be set after SchedulingTimeout call, but is nil") - } - if timeout <= 0 { - t.Errorf("Expected positive timeout duration, got %v", timeout) - } - - // Sleep for a while to ensure that the time has increased, - // especially when testing on Windows machines with lower resolution. - time.Sleep(10 * time.Millisecond) - - deadline := *pgi.schedulingDeadline - newTimeout := pgi.SchedulingTimeout() - if !deadline.Equal(*pgi.schedulingDeadline) { - t.Errorf("Previous deadline should not be changed: previous: %v, current: %v", deadline, *pgi.schedulingDeadline) - } - if newTimeout >= timeout { - t.Errorf("Expected lower timeout duration: previous: %v, current: %v", timeout, newTimeout) - } - - // Sleep for a while to ensure that the time has increased, - // especially when testing on Windows machines with lower resolution. - time.Sleep(10 * time.Millisecond) - - pgi.schedulingDeadline = ptr.To(time.Now().Add(-1 * time.Second)) - newTimeout = pgi.SchedulingTimeout() - if deadline.Equal(*pgi.schedulingDeadline) { - t.Error("Deadline should be reset after it has expired, but it wasn't") - } - if newTimeout <= 0 { - t.Errorf("Expected positive timeout duration after reset, got %v", timeout) - } -} diff --git a/pkg/scheduler/backend/workloadmanager/workloadmanager.go b/pkg/scheduler/backend/workloadmanager/workloadmanager.go deleted file mode 100644 index 088d852cb2154..0000000000000 --- a/pkg/scheduler/backend/workloadmanager/workloadmanager.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -Copyright 2025 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 workloadmanager - -import ( - "fmt" - "sync" - - v1 "k8s.io/api/core/v1" - fwk "k8s.io/kube-scheduler/framework" -) - -// WorkloadManager is the central source of truth for the state of pods belonging to Workload objects. -// It is designed to be driven explicitly by the scheduler's event handlers to ensure thread safety -// and avoid race conditions with the main scheduling queue. -// Note: The current implementation assumes that pod.Spec.Workload is immutable. -// Allowing mutability would require changes to the manager, e.g., by properly handling pod updates. -type WorkloadManager interface { - fwk.WorkloadManager - - // AddPod is called by the scheduler when a Pod/Add event is observed. - AddPod(pod *v1.Pod) - // UpdatePod is called by the scheduler when a Pod/Update event is observed. - UpdatePod(oldPod, newPod *v1.Pod) - // DeletePod is called by the scheduler when a Pod/Delete event is observed. - DeletePod(pod *v1.Pod) -} - -// workloadManager is the concrete implementation of the WorkloadManager. -type workloadManager struct { - lock sync.RWMutex - - // podGroupInfos stores the runtime state for each known pod group. - podGroupInfos map[podGroupKey]*podGroupInfo -} - -// New initializes a new workload manager and returns it. -func New() *workloadManager { - return &workloadManager{ - podGroupInfos: make(map[podGroupKey]*podGroupInfo), - } -} - -// AddPod adds a pod to the workload manager if it has a workload reference. -// Pod is added to the available pods set for its corresponding pod group. -func (wm *workloadManager) AddPod(pod *v1.Pod) { - if pod.Spec.WorkloadRef == nil { - return - } - wm.lock.Lock() - defer wm.lock.Unlock() - - key := newPodGroupKey(pod.Namespace, pod.Spec.WorkloadRef) - info, ok := wm.podGroupInfos[key] - if !ok { - info = newPodGroupInfo() - wm.podGroupInfos[key] = info - } - info.addPod(pod) -} - -// UpdatePod updates a pod in the workload manager if it has a workload reference. -// Note: The current implementation assumes that newPod.Spec.Workload is immutable. -func (wm *workloadManager) UpdatePod(oldPod, newPod *v1.Pod) { - if newPod.Spec.WorkloadRef == nil { - return - } - wm.lock.Lock() - defer wm.lock.Unlock() - - key := newPodGroupKey(newPod.Namespace, newPod.Spec.WorkloadRef) - info, ok := wm.podGroupInfos[key] - if !ok { - // Shouldn't happen, but handling this case gracefully. - info = newPodGroupInfo() - wm.podGroupInfos[key] = info - info.addPod(newPod) - return - } - info.updatePod(oldPod, newPod) -} - -// DeletePod removes a pod from the workload manager if it has a workload reference. -// Pod is removed from the pods sets for its corresponding pod group. -func (wm *workloadManager) DeletePod(pod *v1.Pod) { - if pod.Spec.WorkloadRef == nil { - return - } - wm.lock.Lock() - defer wm.lock.Unlock() - - key := newPodGroupKey(pod.Namespace, pod.Spec.WorkloadRef) - info, ok := wm.podGroupInfos[key] - if !ok { - // The pod group may have already been cleaned up, or the pod was never added. - return - } - info.deletePod(pod.UID) - // Clean up the map entry if no pods are left in the group. - if info.empty() { - delete(wm.podGroupInfos, key) - } -} - -// PodGroupInfo returns the state of a pod group. -func (wm *workloadManager) PodGroupInfo(namespace string, workloadRef *v1.WorkloadReference) (fwk.PodGroupInfo, error) { - wm.lock.RLock() - defer wm.lock.RUnlock() - - state, ok := wm.podGroupInfos[newPodGroupKey(namespace, workloadRef)] - if !ok { - return nil, fmt.Errorf("internal pod group state doesn't exist for a pod's workload") - } - return state, nil -} diff --git a/pkg/scheduler/backend/workloadmanager/workloadmanager_test.go b/pkg/scheduler/backend/workloadmanager/workloadmanager_test.go deleted file mode 100644 index 00af92c2d38b0..0000000000000 --- a/pkg/scheduler/backend/workloadmanager/workloadmanager_test.go +++ /dev/null @@ -1,278 +0,0 @@ -/* -Copyright 2025 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 workloadmanager - -import ( - "testing" - - v1 "k8s.io/api/core/v1" - st "k8s.io/kubernetes/pkg/scheduler/testing" -) - -func TestWorkloadManager_AddPod(t *testing.T) { - p1 := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - // Assigned - p2 := st.MakePod().Namespace("ns1").Name("p2").UID("p2").Node("node1"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - // Different ns - p3 := st.MakePod().Namespace("ns2").Name("p3").UID("p3"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - nonWorkloadPod := st.MakePod().Namespace("ns1").Name("non-workload").Obj() - - tests := []struct { - name string - initPods []*v1.Pod - podToAdd *v1.Pod - - expectedPodGroups int - expectInAllPods bool - expectInUnscheduledPods bool - expectInAssumedPods bool - expectInAssignedPods bool - }{ - { - name: "adding an unscheduled pod", - podToAdd: p1, - expectedPodGroups: 1, - expectInAllPods: true, - expectInUnscheduledPods: true, - }, - { - name: "adding an assigned pod", - podToAdd: p2, - expectedPodGroups: 1, - expectInAllPods: true, - expectInAssignedPods: true, - }, - { - name: "adding pod with different namespace", - initPods: []*v1.Pod{p1}, - podToAdd: p3, - expectedPodGroups: 2, - expectInAllPods: true, - expectInUnscheduledPods: true, - }, - { - name: "adding a non-workload pod is a no-op", - podToAdd: nonWorkloadPod, - expectedPodGroups: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manager := New() - for _, p := range tt.initPods { - manager.AddPod(p) - } - - manager.AddPod(tt.podToAdd) - - gotPodGroups := len(manager.podGroupInfos) - if gotPodGroups != tt.expectedPodGroups { - t.Fatalf("Expected %v pod group(s), got %v", tt.expectedPodGroups, gotPodGroups) - } - if gotPodGroups == 0 { - return - } - info, err := manager.PodGroupInfo(tt.podToAdd.Namespace, tt.podToAdd.Spec.WorkloadRef) - if err != nil { - t.Fatalf("Unexpected error getting pod group info: %v", err) - } - if inAll := info.AllPods().Has(tt.podToAdd.UID); inAll != tt.expectInAllPods { - t.Errorf("Unexpected AllPods state, want: %v, got: %v", tt.expectInAllPods, inAll) - } - if inAssumed := info.AssumedPods().Has(tt.podToAdd.UID); inAssumed != tt.expectInAssumedPods { - t.Errorf("Unexpected AssumedPods state, want: %v, got: %v", tt.expectInAssumedPods, inAssumed) - } - if inAssigned := info.AssignedPods().Has(tt.podToAdd.UID); inAssigned != tt.expectInAssignedPods { - t.Errorf("Unexpected AssignedPods state, want: %v, got: %v", tt.expectInAssignedPods, inAssigned) - } - }) - } -} - -func TestWorkloadManager_UpdatePod(t *testing.T) { - pod := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - updatedPod := st.MakePod().Namespace("ns1").Name("p1").UID("p1").Labels(map[string]string{"foo": "bar"}). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - - assignedPod := st.MakePod().Namespace("ns1").Name("p2").UID("p2").Node("node1"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - updatedAssignedPod := st.MakePod().Namespace("ns1").Name("p2").UID("p2").Node("node1").Labels(map[string]string{"foo": "bar"}). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - - nonWorkloadPod := st.MakePod().Namespace("ns1").Name("non-workload").Obj() - updatedNonWorkloadPod := st.MakePod().Namespace("ns1").Name("non-workload").Labels(map[string]string{"foo": "bar"}).Obj() - - tests := []struct { - name string - assumePod bool - oldPod *v1.Pod - newPod *v1.Pod - - expectInAllPods bool - expectInUnscheduledPods bool - expectInAssumedPods bool - expectInAssignedPods bool - }{ - { - name: "updating an unscheduled pod", - oldPod: pod, - newPod: updatedPod, - expectInAllPods: true, - expectInUnscheduledPods: true, - }, - { - name: "updating an assumed pod", - assumePod: true, - oldPod: pod, - newPod: updatedPod, - expectInAllPods: true, - expectInAssumedPods: true, - }, - { - name: "updating an assigned pod", - oldPod: assignedPod, - newPod: updatedAssignedPod, - expectInAllPods: true, - expectInAssignedPods: true, - }, - { - name: "binding an unscheduled pod", - oldPod: pod, - newPod: assignedPod, - expectInAllPods: true, - expectInAssignedPods: true, - }, - { - name: "binding an assumed pod", - assumePod: true, - oldPod: pod, - newPod: assignedPod, - expectInAllPods: true, - expectInAssignedPods: true, - }, - { - name: "updating a non-workload pod is a no-op", - oldPod: nonWorkloadPod, - newPod: updatedNonWorkloadPod, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manager := New() - - manager.AddPod(tt.oldPod) - if tt.assumePod { - info, err := manager.PodGroupInfo(tt.oldPod.Namespace, tt.oldPod.Spec.WorkloadRef) - if err != nil { - t.Fatalf("Unexpected error getting pod group info: %v", err) - } - info.AssumePod(tt.oldPod.UID) - } - - manager.UpdatePod(tt.oldPod, tt.newPod) - - gotPodGroups := len(manager.podGroupInfos) - if gotPodGroups == 0 { - if tt.expectInAllPods { - t.Fatalf("Expected pod group, but got none") - } - return - } - if !tt.expectInAllPods { - t.Fatalf("Expected no pod groups, but got %v", gotPodGroups) - } - info, err := manager.PodGroupInfo(tt.newPod.Namespace, tt.newPod.Spec.WorkloadRef) - if err != nil { - t.Fatalf("Unexpected error getting pod group info: %v", err) - } - if inAll := info.AllPods().Has(tt.newPod.UID); inAll != tt.expectInAllPods { - t.Errorf("Unexpected AllPods state, want: %v, got: %v", tt.expectInAllPods, inAll) - } - if inAssumed := info.AssumedPods().Has(tt.newPod.UID); inAssumed != tt.expectInAssumedPods { - t.Errorf("Unexpected AssumedPods state, want: %v, got: %v", tt.expectInAssumedPods, inAssumed) - } - if inAssigned := info.AssignedPods().Has(tt.newPod.UID); inAssigned != tt.expectInAssignedPods { - t.Errorf("Unexpected AssignedPods state, want: %v, got: %v", tt.expectInAssignedPods, inAssigned) - } - }) - } -} - -func TestWorkloadManager_DeletePod(t *testing.T) { - p1 := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - p2 := st.MakePod().Namespace("ns1").Name("p2").UID("p2"). - WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg1"}).Obj() - - tests := []struct { - name string - initPods []*v1.Pod - podToDelete *v1.Pod - - expectedPodGroups int - }{ - { - name: "deleting a pod from a group with multiple pods", - initPods: []*v1.Pod{p1, p2}, - podToDelete: p1, - expectedPodGroups: 1, - }, - { - name: "deleting the last pod cleans up the info", - initPods: []*v1.Pod{p1}, - podToDelete: p1, - }, - { - name: "deleting a non-existent pod is a no-op", - podToDelete: p1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manager := New() - for _, p := range tt.initPods { - manager.AddPod(p) - } - manager.DeletePod(tt.podToDelete) - - gotPodGroups := len(manager.podGroupInfos) - if gotPodGroups != tt.expectedPodGroups { - t.Fatalf("Expected %v pod group(s), got %v", tt.expectedPodGroups, gotPodGroups) - } - if gotPodGroups == 0 { - return - } - info, err := manager.PodGroupInfo(tt.podToDelete.Namespace, tt.podToDelete.Spec.WorkloadRef) - if err != nil { - t.Fatalf("Unexpected error getting pod group info: %v", err) - } - if len(info.AllPods()) == 0 { - t.Errorf("Expected AllPods to be non-empty") - } - if info.AllPods().Has(p1.UID) { - t.Errorf("Expected pod to be deleted") - } - }) - } -} diff --git a/pkg/scheduler/eventhandlers.go b/pkg/scheduler/eventhandlers.go index 2febf790f5370..232fa4b403313 100644 --- a/pkg/scheduler/eventhandlers.go +++ b/pkg/scheduler/eventhandlers.go @@ -52,8 +52,7 @@ import ( func (sched *Scheduler) addNodeToCache(obj interface{}) { evt := fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Add} - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(evt.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), evt.Label())() logger := sched.logger node, ok := obj.(*v1.Node) if !ok { @@ -99,8 +98,7 @@ func (sched *Scheduler) updateNodeInCache(oldObj, newObj interface{}) { func (sched *Scheduler) deleteNodeFromCache(obj interface{}) { evt := fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Delete} - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(evt.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), evt.Label())() logger := sched.logger var node *v1.Node @@ -135,10 +133,6 @@ func (sched *Scheduler) addPod(obj interface{}) { return } - if sched.WorkloadManager != nil { - // Register pod into workload manager before adding to the cache or scheduling queue. - sched.WorkloadManager.AddPod(pod) - } if assignedPod(pod) { sched.addAssignedPodToCache(pod) } else if responsibleForPod(pod, sched.Profiles) { @@ -159,10 +153,6 @@ func (sched *Scheduler) updatePod(oldObj, newObj interface{}) { return } - if sched.WorkloadManager != nil { - // Update pod in workload manager before updating it in the cache or scheduling queue. - sched.WorkloadManager.UpdatePod(oldPod, newPod) - } if assignedPod(oldPod) { sched.updateAssignedPodInCache(oldPod, newPod) } else if assignedPod(newPod) { @@ -187,10 +177,6 @@ func (sched *Scheduler) deletePod(obj interface{}) { switch t := obj.(type) { case *v1.Pod: pod = t - if sched.WorkloadManager != nil { - // Delete pod from workload manager before deleting the pod from cache or scheduling queue. - sched.WorkloadManager.DeletePod(pod) - } if assignedPod(pod) { sched.deleteAssignedPodFromCache(pod) } else if responsibleForPod(pod, sched.Profiles) { @@ -206,10 +192,6 @@ func (sched *Scheduler) deletePod(obj interface{}) { utilruntime.HandleErrorWithLogger(logger, nil, "Cannot convert to *v1.Pod", "obj", t.Obj) return } - if sched.WorkloadManager != nil { - // Delete pod from workload manager before deleting the pod from cache or scheduling queue. - sched.WorkloadManager.DeletePod(pod) - } // The carried object may be stale, so we don't use it to check if // it's assigned or not. Attempting to cleanup anyways. sched.deleteAssignedPodFromCache(pod) @@ -226,12 +208,12 @@ func (sched *Scheduler) deletePod(obj interface{}) { } func (sched *Scheduler) addPodToSchedulingQueue(pod *v1.Pod) { - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(framework.EventUnscheduledPodAdd.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), framework.EventUnscheduledPodAdd.Label())() logger := sched.logger logger.V(3).Info("Add event for unscheduled pod", "pod", klog.KObj(pod)) - sched.SchedulingQueue.Add(logger, pod) + sched.Cache.AddPodGroupMember(pod) + sched.SchedulingQueue.Add(klog.NewContext(context.Background(), logger), pod) if utilfeature.DefaultFeatureGate.Enabled(features.GangScheduling) { sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventUnscheduledPodAdd, nil, pod, nil) } @@ -303,10 +285,10 @@ func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) { return } - defer metrics.EventHandlingLatency.WithLabelValues(framework.EventUnscheduledPodUpdate.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(start, framework.EventUnscheduledPodUpdate.Label())() for _, evt := range framework.PodSchedulingPropertiesChange(newPod, oldPod) { if evt.Label() != framework.EventUnscheduledPodUpdate.Label() { - defer metrics.EventHandlingLatency.WithLabelValues(evt.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(start, evt.Label())() } } @@ -316,6 +298,8 @@ func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) { _ = sched.syncPodWithDispatcher(newPod) } + sched.Cache.UpdatePodGroupMember(logger, oldPod, newPod) + isAssumed, err := sched.Cache.IsAssumedPod(newPod) if err != nil { utilruntime.HandleErrorWithLogger(logger, err, "Failed to check whether pod is assumed", "pod", klog.KObj(newPod)) @@ -330,7 +314,7 @@ func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) { } logger.V(4).Info("Update event for unscheduled pod", "pod", klog.KObj(newPod)) - sched.SchedulingQueue.Update(logger, oldPod, newPod) + sched.SchedulingQueue.Update(klog.NewContext(context.Background(), logger), oldPod, newPod) if hasNominatedNodeNameChanged(oldPod, newPod) { // Nominated node changed in pod, so we need to treat it as if the pod was deleted from the old nominated node, // because the scheduler treats such a pod as if it was already assigned when scheduling lower or equal priority pods. @@ -344,8 +328,7 @@ func hasNominatedNodeNameChanged(oldPod, newPod *v1.Pod) bool { } func (sched *Scheduler) deletePodFromSchedulingQueue(pod *v1.Pod, inBinding bool) { - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(framework.EventUnscheduledPodDelete.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), framework.EventUnscheduledPodDelete.Label())() logger := sched.logger @@ -358,6 +341,7 @@ func (sched *Scheduler) deletePodFromSchedulingQueue(pod *v1.Pod, inBinding bool // once the https://github.com/kubernetes/kubernetes/issues/134859 is fixed. return } + sched.Cache.RemovePodGroupMember(pod) isAssumed, err := sched.Cache.IsAssumedPod(pod) if err != nil { utilruntime.HandleErrorWithLogger(logger, err, "Failed to check whether pod is assumed", "pod", klog.KObj(pod)) @@ -383,8 +367,7 @@ func getLEPriorityPreCheck(priority int32) queue.PreEnqueueCheck { } func (sched *Scheduler) addAssignedPodToCache(pod *v1.Pod) { - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(framework.EventAssignedPodAdd.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), framework.EventAssignedPodAdd.Label())() logger := sched.logger @@ -411,7 +394,7 @@ func (sched *Scheduler) addAssignedPodToCache(pod *v1.Pod) { func (sched *Scheduler) updateAssignedPodInCache(oldPod, newPod *v1.Pod) { start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(framework.EventAssignedPodUpdate.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(start, framework.EventAssignedPodUpdate.Label())() logger := sched.logger @@ -453,8 +436,7 @@ func (sched *Scheduler) updateAssignedPodInCache(oldPod, newPod *v1.Pod) { } func (sched *Scheduler) deleteAssignedPodFromCache(pod *v1.Pod) { - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(framework.EventAssignedPodDelete.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), framework.EventAssignedPodDelete.Label())() logger := sched.logger @@ -538,8 +520,7 @@ func addAllEventHandlers( if at&fwk.Add != 0 { evt := fwk.ClusterEvent{Resource: resource, ActionType: fwk.Add} funcs.AddFunc = func(obj interface{}) { - start := time.Now() - defer metrics.EventHandlingLatency.WithLabelValues(evt.Label()).Observe(metrics.SinceInSeconds(start)) + defer metrics.EventHandlingLatency.ObserveSince(time.Now(), evt.Label())() if resource == fwk.StorageClass && !utilfeature.DefaultFeatureGate.Enabled(features.SchedulerQueueingHints) { sc, ok := obj.(*storagev1.StorageClass) if !ok { @@ -683,10 +664,10 @@ func addAllEventHandlers( return err } handlers = append(handlers, handlerRegistration) - case fwk.Workload: + case fwk.PodGroup: if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { - if handlerRegistration, err = informerFactory.Scheduling().V1alpha1().Workloads().Informer().AddEventHandler( - buildEvtResHandler(at, fwk.Workload), + if handlerRegistration, err = informerFactory.Scheduling().V1alpha2().PodGroups().Informer().AddEventHandler( + buildEvtResHandler(at, fwk.PodGroup), ); err != nil { return err } @@ -754,8 +735,9 @@ func preCheckForNode(logger klog.Logger, nodeInfo *framework.NodeInfo) queue.Pre func AdmissionCheck(pod *v1.Pod, nodeInfo *framework.NodeInfo, includeAllFailures bool) []AdmissionResult { var admissionResults []AdmissionResult insufficientResources := noderesources.Fits(pod, nodeInfo, nil, noderesources.ResourceRequestsOptions{ - EnablePodLevelResources: utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources), - EnableDRAExtendedResource: utilfeature.DefaultFeatureGate.Enabled(features.DRAExtendedResource), + EnablePodLevelResources: utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources), + EnableDRAExtendedResource: utilfeature.DefaultFeatureGate.Enabled(features.DRAExtendedResource), + EnableDRANodeAllocatableResources: utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources), }) if len(insufficientResources) != 0 { for i := range insufficientResources { diff --git a/pkg/scheduler/eventhandlers_test.go b/pkg/scheduler/eventhandlers_test.go index 2684074b6ec3b..966bab58e8b79 100644 --- a/pkg/scheduler/eventhandlers_test.go +++ b/pkg/scheduler/eventhandlers_test.go @@ -29,8 +29,8 @@ import ( batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" - resourcealphaapi "k8s.io/api/resource/v1alpha3" - schedulingapi "k8s.io/api/scheduling/v1alpha1" + resourcebetaapi "k8s.io/api/resource/v1beta2" + schedulingapi "k8s.io/api/scheduling/v1alpha2" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" @@ -184,11 +184,11 @@ func TestEventHandlers_MoveToActiveOnNominatedNodeUpdate(t *testing.T) { // disable backoff queue internalqueue.WithPodInitialBackoffDuration(0), internalqueue.WithPodMaxBackoffDuration(0)) - schedulerCache := internalcache.New(ctx, 30*time.Second, nil) + schedulerCache := internalcache.New(ctx, nil, false) // Put test pods into unschedulable queue for _, pod := range unschedulablePods { - queue.Add(logger, pod) + queue.Add(ctx, pod) poppedPod, err := queue.Pop(logger) if err != nil { t.Fatalf("Pop failed: %v", err) @@ -230,7 +230,6 @@ func newDefaultQueueSort() fwk.LessFunc { } func TestUpdateAssignedPodInCache(t *testing.T) { - ttl := 10 * time.Second nodeName := "node" tests := []struct { @@ -255,7 +254,7 @@ func TestUpdateAssignedPodInCache(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() sched := &Scheduler{ - Cache: internalcache.New(ctx, ttl, nil), + Cache: internalcache.New(ctx, nil, false), SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), logger: logger, } @@ -522,20 +521,20 @@ func TestAddAllEventHandlers(t *testing.T) { enableDRADeviceTaints: true, enableDRADeviceTaintRules: true, expectStaticInformers: map[reflect.Type]bool{ - reflect.TypeOf(&v1.Pod{}): true, - reflect.TypeOf(&v1.Node{}): true, - reflect.TypeOf(&v1.Namespace{}): true, - reflect.TypeOf(&resourceapi.ResourceClaim{}): true, - reflect.TypeOf(&resourceapi.ResourceSlice{}): true, - reflect.TypeOf(&resourcealphaapi.DeviceTaintRule{}): true, - reflect.TypeOf(&resourceapi.DeviceClass{}): true, + reflect.TypeOf(&v1.Pod{}): true, + reflect.TypeOf(&v1.Node{}): true, + reflect.TypeOf(&v1.Namespace{}): true, + reflect.TypeOf(&resourceapi.ResourceClaim{}): true, + reflect.TypeOf(&resourceapi.ResourceSlice{}): true, + reflect.TypeOf(&resourcebetaapi.DeviceTaintRule{}): true, + reflect.TypeOf(&resourceapi.DeviceClass{}): true, }, expectDynamicInformers: map[schema.GroupVersionResource]bool{}, }, { - name: "Workload events disabled", + name: "PodGroup events disabled", gvkMap: map[fwk.EventResource]fwk.ActionType{ - fwk.Workload: fwk.Add, + fwk.PodGroup: fwk.Add, }, expectStaticInformers: map[reflect.Type]bool{ reflect.TypeOf(&v1.Pod{}): true, @@ -545,9 +544,9 @@ func TestAddAllEventHandlers(t *testing.T) { expectDynamicInformers: map[schema.GroupVersionResource]bool{}, }, { - name: "Workload events enabled", + name: "PodGroup events enabled", gvkMap: map[fwk.EventResource]fwk.ActionType{ - fwk.Workload: fwk.Add, + fwk.PodGroup: fwk.Add, }, enableDRA: true, enableGenericWorkload: true, @@ -557,7 +556,7 @@ func TestAddAllEventHandlers(t *testing.T) { reflect.TypeOf(&v1.Namespace{}): true, reflect.TypeOf(&resourceapi.ResourceClaim{}): true, reflect.TypeOf(&resourceapi.ResourceSlice{}): true, - reflect.TypeOf(&schedulingapi.Workload{}): true, + reflect.TypeOf(&schedulingapi.PodGroup{}): true, }, expectDynamicInformers: map[schema.GroupVersionResource]bool{}, }, @@ -659,7 +658,7 @@ func TestAddAllEventHandlers(t *testing.T) { SliceInformer: informerFactory.Resource().V1().ResourceSlices(), } if opts.EnableDeviceTaintRules { - opts.TaintInformer = informerFactory.Resource().V1alpha3().DeviceTaintRules() + opts.TaintInformer = informerFactory.Resource().V1beta2().DeviceTaintRules() opts.ClassInformer = informerFactory.Resource().V1().DeviceClasses() } @@ -697,13 +696,18 @@ func TestAdmissionCheck(t *testing.T) { nodenameError := AdmissionResult{Name: nodename.Name, Reason: nodename.ErrReason} nodeportsError := AdmissionResult{Name: nodeports.Name, Reason: nodeports.ErrReason} podOverheadError := AdmissionResult{InsufficientResource: &noderesources.InsufficientResource{ResourceName: v1.ResourceCPU, Reason: "Insufficient cpu", Requested: 2000, Used: 7000, Capacity: 8000}} - cpu := map[v1.ResourceName]string{v1.ResourceCPU: "8"} + extendedResourceError := AdmissionResult{InsufficientResource: &noderesources.InsufficientResource{ResourceName: "foo.com/bar", Reason: "Insufficient foo.com/bar", Requested: 1, Unresolvable: true}} + nodeCPUCapacity := map[v1.ResourceName]string{v1.ResourceCPU: "8"} + extendedResource := map[v1.ResourceName]string{"foo.com/bar": "1"} + nodeAllocatableResourceError := AdmissionResult{InsufficientResource: &noderesources.InsufficientResource{ResourceName: v1.ResourceCPU, Reason: "Insufficient cpu", Requested: 9000, Used: 0, Capacity: 8000, Unresolvable: true}} tests := []struct { - name string - node *v1.Node - existingPods []*v1.Pod - pod *v1.Pod - wantAdmissionResults [][]AdmissionResult + name string + node *v1.Node + existingPods []*v1.Pod + pod *v1.Pod + wantAdmissionResults [][]AdmissionResult + enableDRAExtendedResource bool + enableDRANodeAllocatableResources bool }{ { name: "check nodeAffinity and nodeports, nodeAffinity need fail quickly if includeAllFailures is false", @@ -716,7 +720,7 @@ func TestAdmissionCheck(t *testing.T) { }, { name: "check PodOverhead and nodeAffinity, PodOverhead need fail quickly if includeAllFailures is false", - node: st.MakeNode().Name("fake-node").Label("foo", "bar").Capacity(cpu).Obj(), + node: st.MakeNode().Name("fake-node").Label("foo", "bar").Capacity(nodeCPUCapacity).Obj(), pod: st.MakePod().Name("pod2").Container("c").Overhead(v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}).Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).NodeSelector(map[string]string{"foo": "bar1"}).Obj(), existingPods: []*v1.Pod{ st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "7"}).Node("fake-node").Obj(), @@ -732,9 +736,82 @@ func TestAdmissionCheck(t *testing.T) { }, wantAdmissionResults: [][]AdmissionResult{{nodenameError, nodeportsError}, {nodenameError}}, }, + { + name: "check extended resource handling when node Allocatable doesn't have the resource", + node: st.MakeNode().Name("fake-node").Obj(), + pod: st.MakePod().Name("pod1").Req(extendedResource).Obj(), + wantAdmissionResults: [][]AdmissionResult{{extendedResourceError}, {extendedResourceError}}, + }, + { + name: "check extended resource handling when node Allocatable doesn't have the resource and DRAExtendedResource is enabled", + node: st.MakeNode().Name("fake-node").Obj(), + pod: st.MakePod().Name("pod1").Req(extendedResource).Obj(), + wantAdmissionResults: [][]AdmissionResult{{extendedResourceError}, {extendedResourceError}}, + enableDRAExtendedResource: true, + }, + { + name: "pod not rejected when DRANodeAllocatableResources flag is disabled", + node: st.MakeNode().Name("fake-node").Capacity(nodeCPUCapacity).Obj(), + pod: func() *v1.Pod { + p := st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Obj() + p.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("8"), + }, + }, + } + return p + }(), + wantAdmissionResults: [][]AdmissionResult{nil, nil}, + enableDRANodeAllocatableResources: false, + }, + { + name: "pod rejected when DRANodeAllocatableResources flag is enabled and pod's resource request exceeds node capacity", + node: st.MakeNode().Name("fake-node").Capacity(nodeCPUCapacity).Obj(), + pod: func() *v1.Pod { + p := st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Obj() + p.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse(nodeCPUCapacity[v1.ResourceCPU]), // We should exceed node capacity since we also request 1 CPU in standard request. + }, + }, + } + return p + }(), + wantAdmissionResults: [][]AdmissionResult{{nodeAllocatableResourceError}, {nodeAllocatableResourceError}}, + enableDRANodeAllocatableResources: true, + }, + { + name: "pod not rejected when DRANodeAllocatableResources flag is enabled and pod's resource request fits within node capacity", + node: st.MakeNode().Name("fake-node").Capacity(nodeCPUCapacity).Obj(), + pod: func() *v1.Pod { + p := st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Obj() + cpuQty := resource.MustParse(nodeCPUCapacity[v1.ResourceCPU]) + cpuQty.Sub(resource.MustParse("1")) + p.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpuQty, + }, + }, + } + return p + }(), + wantAdmissionResults: [][]AdmissionResult{nil, nil}, + enableDRANodeAllocatableResources: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRAExtendedResource: tt.enableDRAExtendedResource, + features.DRANodeAllocatableResources: tt.enableDRANodeAllocatableResources, + }) nodeInfo := framework.NewNodeInfo(tt.existingPods...) nodeInfo.SetNode(tt.node) @@ -779,7 +856,7 @@ func TestAddPod(t *testing.T) { defer cancel() sched := &Scheduler{ - Cache: internalcache.New(ctx, 0, nil), + Cache: internalcache.New(ctx, nil, false), SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), logger: logger, Profiles: profile.Map{ @@ -914,7 +991,7 @@ func TestUpdatePod(t *testing.T) { t.Fatalf("Failed to create framework: %v", err) } sched := &Scheduler{ - Cache: internalcache.New(ctx, 0, nil), + Cache: internalcache.New(ctx, nil, false), SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), logger: logger, Profiles: profile.Map{ @@ -1033,7 +1110,7 @@ func TestDeletePod(t *testing.T) { t.Fatalf("Failed to create framework: %v", err) } sched := &Scheduler{ - Cache: internalcache.New(ctx, 0, nil), + Cache: internalcache.New(ctx, nil, false), SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), logger: logger, Profiles: profile.Map{ diff --git a/pkg/scheduler/extender_test.go b/pkg/scheduler/extender_test.go index c701ba84a511c..e65e5f4a4bf2a 100644 --- a/pkg/scheduler/extender_test.go +++ b/pkg/scheduler/extender_test.go @@ -19,7 +19,6 @@ package scheduler import ( "context" "testing" - "time" "github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" @@ -333,7 +332,7 @@ func TestSchedulerWithExtenders(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := internalcache.New(ctx, time.Duration(0), nil) + cache := internalcache.New(ctx, nil, false) for _, name := range test.nodes { cache.AddNode(logger, createNode(name)) } @@ -359,8 +358,12 @@ func TestSchedulerWithExtenders(t *testing.T) { } sched.applyDefaultHandlers() - podIgnored := &v1.Pod{} - result, err := sched.SchedulePod(ctx, fwk, framework.NewCycleState(), podIgnored) + if err := sched.Cache.UpdateSnapshot(logger, sched.nodeInfoSnapshot); err != nil { + t.Fatalf("Unexpected error updating snapshot: %v", err) + } + + podInfoIgnored := queuedPodInfoForPod(&v1.Pod{}) + result, err := sched.SchedulePod(ctx, fwk, framework.NewCycleState(), podInfoIgnored) if test.expectsErr { if err == nil { t.Errorf("Unexpected non-error, result %+v", result) diff --git a/pkg/scheduler/framework/autoscaler_contract/lister_contract_test.go b/pkg/scheduler/framework/autoscaler_contract/lister_contract_test.go index ad1b79e2e3715..bcb522c9a2328 100644 --- a/pkg/scheduler/framework/autoscaler_contract/lister_contract_test.go +++ b/pkg/scheduler/framework/autoscaler_contract/lister_contract_test.go @@ -33,6 +33,8 @@ var _ fwk.NodeInfoLister = &nodeInfoListerContract{} var _ fwk.StorageInfoLister = &storageInfoListerContract{} var _ fwk.SharedLister = &shareListerContract{} var _ fwk.ResourceSliceLister = &resourceSliceListerContract{} +var _ fwk.PodGroupStateLister = &podGroupStateListerContract{} +var _ fwk.PodGroupState = &podGroupStateContract{} var _ fwk.DeviceClassLister = &deviceClassListerContract{} var _ fwk.ResourceClaimTracker = &resourceClaimTrackerContract{} var _ fwk.DeviceClassResolver = &deviceClassResolverContract{} @@ -72,6 +74,46 @@ func (c *shareListerContract) StorageInfos() fwk.StorageInfoLister { return nil } +func (c *shareListerContract) PodGroupStates() fwk.PodGroupStateLister { + return nil +} + +type podGroupStateListerContract struct{} + +func (c *podGroupStateListerContract) Get(_ string, _ string) (fwk.PodGroupState, error) { + return nil, nil +} + +type podGroupStateContract struct{} + +func (c *podGroupStateContract) AllPods() sets.Set[types.UID] { + return nil +} + +func (c *podGroupStateContract) UnscheduledPods() map[string]*v1.Pod { + return nil +} + +func (c *podGroupStateContract) AssumedPods() sets.Set[types.UID] { + return nil +} + +func (c *podGroupStateContract) AssignedPods() sets.Set[types.UID] { + return nil +} + +func (c *podGroupStateContract) AllPodsCount() int { + return 0 +} + +func (c *podGroupStateContract) ScheduledPodsCount() int { + return 0 +} + +func (c *podGroupStateContract) ScheduledPods() []*v1.Pod { + return nil +} + type resourceSliceListerContract struct{} func (c *resourceSliceListerContract) ListWithDeviceTaintRules() ([]*resourceapi.ResourceSlice, error) { @@ -106,15 +148,15 @@ func (r *resourceClaimTrackerContract) GatherAllocatedState() (*schedulerapi.All return nil, nil } -func (r *resourceClaimTrackerContract) SignalClaimPendingAllocation(_ types.UID, _ *resourceapi.ResourceClaim) error { +func (r *resourceClaimTrackerContract) GetPendingAllocation(_ types.UID) *resourceapi.AllocationResult { return nil } -func (r *resourceClaimTrackerContract) ClaimHasPendingAllocation(_ types.UID) bool { - return false +func (r *resourceClaimTrackerContract) SignalClaimPendingAllocation(_ types.UID, _ *resourceapi.ResourceClaim) error { + return nil } -func (r *resourceClaimTrackerContract) RemoveClaimPendingAllocation(_ types.UID) (deleted bool) { +func (r *resourceClaimTrackerContract) MaybeRemoveClaimPendingAllocation(_ types.UID, _ bool) (deleted bool) { return false } @@ -143,6 +185,10 @@ func (s *sharedDRAManagerContract) DeviceClassResolver() fwk.DeviceClassResolver return nil } +func (s *sharedDRAManagerContract) PodGroups() fwk.PodGroupLister { + return nil +} + type deviceClassResolverContract struct{} func (d *deviceClassResolverContract) GetDeviceClass(_ v1.ResourceName) *resourceapi.DeviceClass { diff --git a/pkg/scheduler/framework/cycle_state.go b/pkg/scheduler/framework/cycle_state.go index 690bce50d216e..31b787253568f 100644 --- a/pkg/scheduler/framework/cycle_state.go +++ b/pkg/scheduler/framework/cycle_state.go @@ -36,6 +36,16 @@ type CycleState struct { skipScorePlugins sets.Set[string] // skipPreBindPlugins are plugins that will be skipped in the PreBind extension point. skipPreBindPlugins sets.Set[string] + // skipAllPostFilterPlugins indicates whether to skip all plugins in the PostFilter extension point. + skipAllPostFilterPlugins bool + // GetParallelPreBindPlugins returns plugins that can be run in parallel with other plugins + // in the PreBind extension point. + parallelPreBindPlugins sets.Set[string] + // podGroupCycleState contains the CycleState for this pod's PodGroup. + // If set to nil, it means that the pod referencing this CycleState either passed the pod group cycle + // or doesn't belong to any pod group. + // This field can only be non-nil when GenericWorkload feature flag is enabled. + podGroupCycleState fwk.PodGroupCycleState } // NewCycleState initializes a new CycleState and returns its pointer. @@ -83,6 +93,34 @@ func (c *CycleState) GetSkipPreBindPlugins() sets.Set[string] { return c.skipPreBindPlugins } +func (c *CycleState) SetParallelPreBindPlugins(plugins sets.Set[string]) { + c.parallelPreBindPlugins = plugins +} + +func (c *CycleState) GetParallelPreBindPlugins() sets.Set[string] { + return c.parallelPreBindPlugins +} + +func (c *CycleState) IsPodGroupSchedulingCycle() bool { + return c.podGroupCycleState != nil +} + +func (c *CycleState) SetPodGroupSchedulingCycle(podGroupCycleState fwk.PodGroupCycleState) { + c.podGroupCycleState = podGroupCycleState +} + +func (c *CycleState) GetPodGroupSchedulingCycle() fwk.PodGroupCycleState { + return c.podGroupCycleState +} + +func (c *CycleState) SetSkipAllPostFilterPlugins(flag bool) { + c.skipAllPostFilterPlugins = flag +} + +func (c *CycleState) ShouldSkipAllPostFilterPlugins() bool { + return c.skipAllPostFilterPlugins +} + // Clone creates a copy of CycleState and returns its pointer. Clone returns // nil if the context being cloned is nil. func (c *CycleState) Clone() fwk.CycleState { @@ -100,6 +138,9 @@ func (c *CycleState) Clone() fwk.CycleState { copy.skipFilterPlugins = c.skipFilterPlugins copy.skipScorePlugins = c.skipScorePlugins copy.skipPreBindPlugins = c.skipPreBindPlugins + copy.parallelPreBindPlugins = c.parallelPreBindPlugins + copy.podGroupCycleState = c.podGroupCycleState + copy.skipAllPostFilterPlugins = c.skipAllPostFilterPlugins return copy } diff --git a/pkg/scheduler/framework/cycle_state_test.go b/pkg/scheduler/framework/cycle_state_test.go index e6680c5303fb1..70b70085fdfd2 100644 --- a/pkg/scheduler/framework/cycle_state_test.go +++ b/pkg/scheduler/framework/cycle_state_test.go @@ -40,7 +40,7 @@ var key fwk.StateKey = "fakedata_key" // createCycleStateWithFakeData creates *CycleState with fakeData. // The given data is used in stored fakeData. -func createCycleStateWithFakeData(data string, recordPluginMetrics bool, skipPlugins ...[]string) *CycleState { +func createCycleStateWithFakeData(data string, recordPluginMetrics bool, skipAllPostFilterPlugins bool, skipPlugins ...[]string) *CycleState { c := NewCycleState() c.Write(key, &fakeData{ data: data, @@ -52,6 +52,7 @@ func createCycleStateWithFakeData(data string, recordPluginMetrics bool, skipPlu if len(skipPlugins) > 1 { c.SetSkipScorePlugins(sets.New(skipPlugins[1]...)) } + c.SetSkipAllPostFilterPlugins(skipAllPostFilterPlugins) return c } @@ -74,6 +75,9 @@ func isCycleStateEqual(a, b *CycleState) (bool, string) { if diff := cmp.Diff(a.skipScorePlugins, b.skipScorePlugins); diff != "" { return false, fmt.Sprintf("CycleState A and B have different SkipScorePlugins sets. -wanted,+got:\n%s", diff) } + if diff := cmp.Diff(a.skipAllPostFilterPlugins, b.skipAllPostFilterPlugins); diff != "" { + return false, fmt.Sprintf("CycleState A and B have different SkipAllPostFilterPlugins sets. -wanted,+got:\n%s", diff) + } var msg string isEqual := true @@ -129,28 +133,33 @@ func TestCycleStateClone(t *testing.T) { }{ { name: "clone with recordPluginMetrics true", - state: createCycleStateWithFakeData("data", true), - wantClonedState: createCycleStateWithFakeData("data", true), + state: createCycleStateWithFakeData("data", true, false), + wantClonedState: createCycleStateWithFakeData("data", true, false), }, { name: "clone with recordPluginMetrics false", - state: createCycleStateWithFakeData("data", false), - wantClonedState: createCycleStateWithFakeData("data", false), + state: createCycleStateWithFakeData("data", false, false), + wantClonedState: createCycleStateWithFakeData("data", false, false), }, { name: "clone with SkipFilterPlugins", - state: createCycleStateWithFakeData("data", true, []string{"p1", "p2", "p3"}), - wantClonedState: createCycleStateWithFakeData("data", true, []string{"p1", "p2", "p3"}), + state: createCycleStateWithFakeData("data", true, false, []string{"p1", "p2", "p3"}), + wantClonedState: createCycleStateWithFakeData("data", true, false, []string{"p1", "p2", "p3"}), }, { name: "clone with SkipScorePlugins", - state: createCycleStateWithFakeData("data", false, []string{}, []string{"p1", "p2", "p3"}), - wantClonedState: createCycleStateWithFakeData("data", false, []string{}, []string{"p1", "p2", "p3"}), + state: createCycleStateWithFakeData("data", false, false, []string{}, []string{"p1", "p2", "p3"}), + wantClonedState: createCycleStateWithFakeData("data", false, false, []string{}, []string{"p1", "p2", "p3"}), }, { name: "clone with SkipScorePlugins and SkipFilterPlugins", - state: createCycleStateWithFakeData("data", true, []string{"p0"}, []string{"p1", "p2", "p3"}), - wantClonedState: createCycleStateWithFakeData("data", true, []string{"p0"}, []string{"p1", "p2", "p3"}), + state: createCycleStateWithFakeData("data", true, false, []string{"p0"}, []string{"p1", "p2", "p3"}), + wantClonedState: createCycleStateWithFakeData("data", true, false, []string{"p0"}, []string{"p1", "p2", "p3"}), + }, + { + name: "clone with SkipAllPostFilterPlugins", + state: createCycleStateWithFakeData("data", true, true, []string{"p0"}, []string{"p1", "p2", "p3"}), + wantClonedState: createCycleStateWithFakeData("data", true, true, []string{"p0"}, []string{"p1", "p2", "p3"}), }, { name: "clone with nil CycleState", diff --git a/pkg/scheduler/framework/interface.go b/pkg/scheduler/framework/interface.go index bca3dfab900ef..bbdfa4890cfdc 100644 --- a/pkg/scheduler/framework/interface.go +++ b/pkg/scheduler/framework/interface.go @@ -21,8 +21,10 @@ package framework import ( "context" "sync" + "time" v1 "k8s.io/api/core/v1" + "k8s.io/api/scheduling/v1alpha2" "k8s.io/apimachinery/pkg/util/sets" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/apis/config" @@ -159,6 +161,16 @@ type SortedScoredNodes interface { Len() int } +// PodGroupPostFilterPlugin is an interface for plugins that are called +// after a PodGroup cannot be scheduled. +// It should not be used by any other plugin but DefaultPreemption. +type PodGroupPostFilterPlugin interface { + fwk.Plugin + + // PodGroupPostFilter is called after a PodGroup cannot be scheduled. + PodGroupPostFilter(ctx context.Context, pg *v1alpha2.PodGroup, pods []*v1.Pod, pgSchedulingFunc func(ctx context.Context) *fwk.Status) *fwk.Status +} + // Framework manages the set of plugins in use by the scheduling framework. // Configured plugins are called at specified points in a scheduling context. type Framework interface { @@ -173,12 +185,12 @@ type Framework interface { // QueueSortFunc returns the function to sort pods in scheduling queue QueueSortFunc() fwk.LessFunc - // Create a scheduling signature for a given pod, if possible. Two pods with the same signature + // SignPod creates a scheduling signature for a given pod, if possible. Two pods with the same signature // should get the same feasibility and scores for any given set of nodes even after one of them gets assigned. If some plugins // are unable to create a signature, the pod may be "unsignable" which disables results caching // and gang scheduling optimizations. // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-scheduling/5598-opportunistic-batching - SignPod(ctx context.Context, pod *v1.Pod, recordPluginStats bool) fwk.PodSignature + SignPod(ctx context.Context, pod *v1.Pod) fwk.PodSignature // RunPreFilterPlugins runs the set of configured PreFilter plugins. It returns // *fwk.Status and its code is set to non-success if any of the plugins returns @@ -197,15 +209,19 @@ type Framework interface { // cluster state to make the pod potentially schedulable in a future scheduling cycle. RunPostFilterPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, filteredNodeStatusMap fwk.NodeToStatusReader) (*fwk.PostFilterResult, *fwk.Status) - // Get a "node hint" for a given pod. A node hint is the name of a node provided by the batching code when information + // GetNodeHint returns a "node hint" for a given pod. A node hint is the name of a node provided by the batching code when information // from the previous scheduling cycle can be reused for this cycle. // If the batching code cannot provide a hint, the function returns "". // See git.k8s.io/enhancements/keps/sig-scheduling/5598-opportunistic-batching - GetNodeHint(ctx context.Context, pod *v1.Pod, state fwk.CycleState, cycleCount int64) (hint string, signature fwk.PodSignature) + GetNodeHint(ctx context.Context, pod *v1.Pod, signature fwk.PodSignature, state fwk.CycleState, cycleCount int64) string // StoreScheduleResults stores the results after we have sorted and filtered nodes. StoreScheduleResults(ctx context.Context, signature fwk.PodSignature, hintedNode, chosenNode string, otherNodes SortedScoredNodes, cycleCount int64) + // RunPlacementGeneratePlugins runs the set of configured PlacementGenerate plugins. + // It returns the combined list of generated Placements. + RunPlacementGeneratePlugins(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, nodes []fwk.NodeInfo) ([]*fwk.Placement, *fwk.Status) + // RunPreBindPlugins runs the set of configured PreBind plugins. It returns // *fwk.Status and its code is set to non-success if any of the plugins returns // anything but Success. If the Status code is "Unschedulable", it is @@ -233,10 +249,15 @@ type Framework interface { // RunPermitPlugins runs the set of configured Permit plugins. If any of these // plugins returns a status other than "Success" or "Wait", it does not continue // running the remaining plugins and returns an error. Otherwise, if any of the - // plugins returns "Wait", then this function will create and add waiting pod - // to a map of currently waiting pods and return status with "Wait" code. + // plugins returns "Wait", then this function will construct the pluginsWaitTime and return status with "Wait" code. + // This function itself will NOT create a waiting pod object and the caller should call AddWaitingPod method to do this. + RunPermitPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (pluginsWaitTime map[string]time.Duration, status *fwk.Status) + + // AddWaitingPod creates a waiting pod instance and adds it to the framework. + // It takes the pluginsWaitTime map returned by the RunPermitPlugins. // Pod will remain waiting pod for the minimum duration returned by the Permit plugins. - RunPermitPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status + // This method should only be called when RunPermitPlugins returns a Wait status and WaitOnPermit is expected to execute soon. + AddWaitingPod(pod *v1.Pod, pluginsWaitTime map[string]time.Duration) // WillWaitOnPermit returns whether this pod will wait on permit by checking if the pod is a waiting pod. WillWaitOnPermit(ctx context.Context, pod *v1.Pod) bool @@ -251,6 +272,12 @@ type Framework interface { // code=5("skip") status. RunBindPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status + // RunPlacementScorePlugins runs the set of configured placement scoring plugins. + // It returns a list that stores scores from each plugin and total score for each Placement. + // It also returns *Status, which is set to non-success if any of the plugins returns + // a non-success status. + RunPlacementScorePlugins(ctx context.Context, state fwk.PodGroupCycleState, podGroupInfo fwk.PodGroupInfo, placements []*fwk.PodGroupAssignments) (ns []fwk.PlacementPluginScores, status *fwk.Status) + // HasFilterPlugins returns true if at least one Filter plugin is defined. HasFilterPlugins() bool @@ -260,6 +287,9 @@ type Framework interface { // HasScorePlugins returns true if at least one Score plugin is defined. HasScorePlugins() bool + // PodGroupPostFilterPlugins returns registered PodGroupPostFilter plugins. + PodGroupPostFilterPlugins() []PodGroupPostFilterPlugin + // ListPlugins returns a map of extension point name to list of configured Plugins. ListPlugins() *config.Plugins diff --git a/pkg/scheduler/framework/parallelize/error_channel.go b/pkg/scheduler/framework/parallelize/error_channel.go deleted file mode 100644 index 2eff825bae838..0000000000000 --- a/pkg/scheduler/framework/parallelize/error_channel.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2019 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 parallelize - -import "context" - -// ErrorChannel supports non-blocking send and receive operation to capture error. -// A maximum of one error is kept in the channel and the rest of the errors sent -// are ignored, unless the existing error is received and the channel becomes empty -// again. -type ErrorChannel struct { - errCh chan error -} - -// SendError sends an error without blocking the sender. -func (e *ErrorChannel) SendError(err error) { - select { - case e.errCh <- err: - default: - } -} - -// SendErrorWithCancel sends an error without blocking the sender and calls -// cancel function. -func (e *ErrorChannel) SendErrorWithCancel(err error, cancel context.CancelFunc) { - e.SendError(err) - cancel() -} - -// ReceiveError receives an error from channel without blocking on the receiver. -func (e *ErrorChannel) ReceiveError() error { - select { - case err := <-e.errCh: - return err - default: - return nil - } -} - -// NewErrorChannel returns a new ErrorChannel. -func NewErrorChannel() *ErrorChannel { - return &ErrorChannel{ - errCh: make(chan error, 1), - } -} diff --git a/pkg/scheduler/framework/parallelize/error_channel_test.go b/pkg/scheduler/framework/parallelize/error_channel_test.go deleted file mode 100644 index 9700d39ca952b..0000000000000 --- a/pkg/scheduler/framework/parallelize/error_channel_test.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2019 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 parallelize - -import ( - "context" - "errors" - "testing" - - "k8s.io/klog/v2/ktesting" -) - -func TestErrorChannel(t *testing.T) { - errCh := NewErrorChannel() - - if actualErr := errCh.ReceiveError(); actualErr != nil { - t.Errorf("expect nil from err channel, but got %v", actualErr) - } - - err := errors.New("unknown error") - errCh.SendError(err) - if actualErr := errCh.ReceiveError(); actualErr != err { - t.Errorf("expect %v from err channel, but got %v", err, actualErr) - } - - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - errCh.SendErrorWithCancel(err, cancel) - if actualErr := errCh.ReceiveError(); actualErr != err { - t.Errorf("expect %v from err channel, but got %v", err, actualErr) - } - - if ctxErr := ctx.Err(); ctxErr != context.Canceled { - t.Errorf("expect context canceled, but got %v", ctxErr) - } -} diff --git a/pkg/scheduler/framework/parallelize/parallelism_test.go b/pkg/scheduler/framework/parallelize/parallelism_test.go index 8449c4f451065..7ed5b7c04e154 100644 --- a/pkg/scheduler/framework/parallelize/parallelism_test.go +++ b/pkg/scheduler/framework/parallelize/parallelism_test.go @@ -18,7 +18,12 @@ package parallelize import ( "fmt" + "sync" "testing" + + "k8s.io/component-base/metrics/testutil" + "k8s.io/klog/v2/ktesting" + "k8s.io/kubernetes/pkg/scheduler/metrics" ) func TestChunkSize(t *testing.T) { @@ -52,3 +57,36 @@ func TestChunkSize(t *testing.T) { }) } } + +func TestGoroutinesMetric(t *testing.T) { + metrics.Register() + metrics.Goroutines.Reset() + + const ( + operation = "test-operation" + pieces = 32 + ) + + var ( + mu sync.Mutex + peakValue float64 + ) + + _, ctx := ktesting.NewTestContext(t) + p := NewParallelizer(DefaultParallelism) + p.Until(ctx, pieces, func(_ int) { + val, err := testutil.GetGaugeMetricValue(metrics.Goroutines.WithLabelValues(operation)) + if err != nil { + t.Fatalf("failed to read goroutines metric inside Until: %v", err) + } + mu.Lock() + if val > peakValue { + peakValue = val + } + mu.Unlock() + }, operation) + + if peakValue <= 0 { + t.Errorf("expected goroutines metric to be >0 during Until, peak was %v", peakValue) + } +} diff --git a/pkg/scheduler/framework/parallelize/result_channel.go b/pkg/scheduler/framework/parallelize/result_channel.go new file mode 100644 index 0000000000000..fd0981e9be2d7 --- /dev/null +++ b/pkg/scheduler/framework/parallelize/result_channel.go @@ -0,0 +1,60 @@ +/* +Copyright 2019 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 parallelize + +import "context" + +// ResultChannel supports non-blocking send and receive operation to capture result. +// A maximum of one result is kept in the channel and the rest of the results sent +// are ignored, unless the existing result is received and the channel becomes empty +// again. +type ResultChannel[T comparable] struct { + ch chan T +} + +// Send sends a result without blocking the sender. +func (e *ResultChannel[T]) Send(result T) { + select { + case e.ch <- result: + default: + } +} + +// SendWithCancel sends a result without blocking the sender and calls +// cancel function. +func (e *ResultChannel[T]) SendWithCancel(result T, cancel context.CancelFunc) { + e.Send(result) + cancel() +} + +// Receive receives a result from channel without blocking on the receiver. +func (e *ResultChannel[T]) Receive() T { + select { + case result := <-e.ch: + return result + default: + var zeroValue T + return zeroValue + } +} + +// NewResultChannel returns a new ResultChannel. +func NewResultChannel[T comparable]() *ResultChannel[T] { + return &ResultChannel[T]{ + ch: make(chan T, 1), + } +} diff --git a/pkg/scheduler/framework/parallelize/result_channel_test.go b/pkg/scheduler/framework/parallelize/result_channel_test.go new file mode 100644 index 0000000000000..7419fdbf1915a --- /dev/null +++ b/pkg/scheduler/framework/parallelize/result_channel_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2019 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 parallelize + +import ( + "context" + "errors" + "testing" + + "k8s.io/klog/v2/ktesting" +) + +func TestErrorChannel(t *testing.T) { + errCh := NewResultChannel[error]() + + if actualErr := errCh.Receive(); actualErr != nil { + t.Errorf("expect nil from err channel, but got %v", actualErr) + } + + err := errors.New("unknown error") + errCh.Send(err) + if actualErr := errCh.Receive(); !errors.Is(actualErr, err) { + t.Errorf("expect %v from err channel, but got %v", err, actualErr) + } + + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + errCh.SendWithCancel(err, cancel) + if actualErr := errCh.Receive(); !errors.Is(actualErr, err) { + t.Errorf("expect %v from err channel, but got %v", err, actualErr) + } + + if ctxErr := ctx.Err(); ctxErr != context.Canceled { + t.Errorf("expect context canceled, but got %v", ctxErr) + } +} diff --git a/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go b/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go index 71b72df7c395d..3b6ab93dc031b 100644 --- a/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go +++ b/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "testing" - "time" "github.com/google/go-cmp/cmp" @@ -95,7 +94,7 @@ func TestDefaultBinder(t *testing.T) { t.Fatal(err) } if asyncAPICallsEnabled { - cache := internalcache.New(ctx, time.Duration(0), apiDispatcher) + cache := internalcache.New(ctx, apiDispatcher, false) fh.SetAPICacher(apicache.New(nil, cache)) } diff --git a/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go b/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go index bc3d55b49e6e2..d35ea3b308cfb 100644 --- a/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go +++ b/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go @@ -24,9 +24,11 @@ import ( v1 "k8s.io/api/core/v1" policy "k8s.io/api/policy/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/listers/scheduling/v1alpha2" corev1helpers "k8s.io/component-helpers/scheduling/corev1" "k8s.io/klog/v2" extenderv1 "k8s.io/kube-scheduler/extender/v1" @@ -62,10 +64,14 @@ type MoreImportantPodFunc func(pod1, pod2 *v1.Pod) bool // DefaultPreemption is a PostFilter plugin implements the preemption logic. type DefaultPreemption struct { - fh fwk.Handle - fts feature.Features - args config.DefaultPreemptionArgs - Evaluator *preemption.Evaluator + fh fwk.Handle + fts feature.Features + args config.DefaultPreemptionArgs + + Executor *preemption.Executor + Evaluator *preemption.Evaluator + pgLister v1alpha2.PodGroupLister + podGroupEvaluator *preemption.PodGroupEvaluator // IsEligiblePod returns whether a victim pod is allowed to be preempted by a preemptor pod. // This filtering is in addition to the internal requirement that the victim pod have lower @@ -103,7 +109,15 @@ func New(_ context.Context, dpArgs runtime.Object, fh fwk.Handle, fts feature.Fe fts: fts, args: *args, } - pl.Evaluator = preemption.NewEvaluator(Name, fh, &pl, fts.EnableAsyncPreemption) + pl.Executor = preemption.NewExecutor(fh, fts) + pl.Evaluator = preemption.NewEvaluator(Name, fh, &pl, pl.Executor) + + if pl.fts.EnableWorkloadAwarePreemption || pl.fts.EnableTopologyAwareWorkloadScheduling { + pl.pgLister = fh.SharedInformerFactory().Scheduling().V1alpha2().PodGroups().Lister() + } + if pl.fts.EnableWorkloadAwarePreemption { + pl.podGroupEvaluator = preemption.NewPodGroupEvaluator(fh, pl.Executor) + } // Default behavior: No additional filtering, beyond the internal requirement that the victim pod // have lower priority than the preemptor pod. @@ -123,6 +137,23 @@ func (pl *DefaultPreemption) PostFilter(ctx context.Context, state fwk.CycleStat metrics.PreemptionAttempts.Inc() }() + if pod.Spec.SchedulingGroup != nil && pl.fts.EnableTopologyAwareWorkloadScheduling { + pg, err := pl.pgLister.PodGroups(pod.Namespace).Get(*pod.Spec.SchedulingGroup.PodGroupName) + if err != nil { + return nil, fwk.NewStatus(fwk.Unschedulable, "preemption: pod group for pod not found") + } + if pg.Spec.SchedulingConstraints != nil { + // When TAS is enabled, the default preemption logic needs to be disabled to avoid performing preemption multiple times for each topology option. + // The TAS-compatible preemption logic will be implemented in Delayed Preemption KEP 4671 or Workload-aware preemption KEP 5710 features. + return nil, fwk.NewStatus(fwk.Unschedulable, "preemption: not eligible due to placement-based pod group scheduling limitation") + } + } + if pod.Spec.SchedulingGroup != nil && pl.fts.EnableWorkloadAwarePreemption { + // When WAP is enabled, the default preemption logic needs to be disabled for pod group scheduling to avoid performing preemption in pod by pod cycle + // of pod group scheduling. Instead the WAP will be called to perform preemption for the entire pod group. + return nil, fwk.NewStatus(fwk.Unschedulable, "preemption: not eligible due to workload aware preemption enabled") + } + result, status := pl.Evaluator.Preempt(ctx, state, pod, m) msg := status.Message() if len(msg) > 0 { @@ -135,7 +166,20 @@ func (pl *DefaultPreemption) PreEnqueue(ctx context.Context, p *v1.Pod) *fwk.Sta if !pl.fts.EnableAsyncPreemption { return nil } - if pl.Evaluator.IsPodRunningPreemption(p.GetUID()) { + if p.Spec.SchedulingGroup != nil && pl.fts.EnableWorkloadAwarePreemption { + pg, err := pl.pgLister.PodGroups(p.Namespace).Get(*p.Spec.SchedulingGroup.PodGroupName) + // If the pg is not found do not block the pod. It's not a default preemption responsibility + // to block pods from pod group without pg from entering the queue. + if err != nil { + return nil + } + if pl.Executor.IsPodGroupRunningPreemption(pg.GetUID()) { + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for the preemption for this pod group to be finished") + } + return nil + } + + if pl.Executor.IsPodRunningPreemption(p.GetUID()) { return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for the preemption for this pod to be finished") } return nil @@ -235,9 +279,11 @@ func (pl *DefaultPreemption) SelectVictimsOnNode( for _, pi := range nodeInfo.GetPods() { if pl.isPreemptionAllowed(nodeInfo, pi, pod) { potentialVictims = append(potentialVictims, pi) - if err := removePod(pi); err != nil { - return nil, 0, fwk.AsStatus(err) - } + } + } + for _, pi := range potentialVictims { + if err := removePod(pi); err != nil { + return nil, 0, fwk.AsStatus(err) } } @@ -417,3 +463,8 @@ func filterPodsWithPDBViolation(podInfos []fwk.PodInfo, pdbs []*policy.PodDisrup } return violatingPodInfos, nonViolatingPodInfos } + +// PodGroupPostFilter runs a default preemption for the pod group. +func (pl *DefaultPreemption) PodGroupPostFilter(ctx context.Context, pg *schedulingapi.PodGroup, pods []*v1.Pod, pgSchedulingFunc func(ctx context.Context) *fwk.Status) *fwk.Status { + return pl.podGroupEvaluator.Preempt(ctx, pg, pods, pgSchedulingFunc) +} diff --git a/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go b/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go index 83204427db492..4aa81652b4c4c 100644 --- a/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go +++ b/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go @@ -34,6 +34,7 @@ import ( v1 "k8s.io/api/core/v1" policy "k8s.io/api/policy/v1" + "k8s.io/api/scheduling/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -52,12 +53,12 @@ import ( apipod "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/scheduler/apis/config" configv1 "k8s.io/kubernetes/pkg/scheduler/apis/config/v1" - "k8s.io/kubernetes/pkg/scheduler/backend/api_cache" - "k8s.io/kubernetes/pkg/scheduler/backend/api_dispatcher" + apicache "k8s.io/kubernetes/pkg/scheduler/backend/api_cache" + apidispatcher "k8s.io/kubernetes/pkg/scheduler/backend/api_dispatcher" internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" internalqueue "k8s.io/kubernetes/pkg/scheduler/backend/queue" "k8s.io/kubernetes/pkg/scheduler/framework" - "k8s.io/kubernetes/pkg/scheduler/framework/api_calls" + apicalls "k8s.io/kubernetes/pkg/scheduler/framework/api_calls" "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" @@ -172,7 +173,9 @@ func TestPostFilter(t *testing.T) { pods []*v1.Pod pdbs []*policy.PodDisruptionBudget nodes []*v1.Node + podGroups []*v1alpha2.PodGroup filteredNodesStatuses *framework.NodeToStatus + features feature.Features extender fwk.Extender wantResult *fwk.PostFilterResult wantStatus *fwk.Status @@ -391,6 +394,114 @@ func TestPostFilter(t *testing.T) { wantResult: framework.NewPostFilterResultWithNominatedNode("node2"), wantStatus: fwk.NewStatus(fwk.Success), }, + { + name: "pod with SchedulingGroup with TAS with scheduling constraint enabled should not preempt", + pod: st.MakePod().Name("p-with-podgroup").Namespace(v1.NamespaceDefault).PodGroupName("foo").Priority(highPriority).Obj(), + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(onePodRes).Obj(), + }, + podGroups: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("foo").Namespace(v1.NamespaceDefault).TopologyKey("rack").Obj(), + }, + filteredNodesStatuses: framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)), + features: feature.Features{EnableTopologyAwareWorkloadScheduling: true}, + wantResult: nil, + wantStatus: fwk.NewStatus(fwk.Unschedulable, "preemption: not eligible due to placement-based pod group scheduling limitation"), + }, + { + name: "pod with SchedulingGroup with TAS with scheduling constraint enabled should not preempt even when WAP is enabled", + pod: st.MakePod().Name("p-with-podgroup").Namespace(v1.NamespaceDefault).PodGroupName("foo").Priority(highPriority).Obj(), + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(onePodRes).Obj(), + }, + podGroups: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("foo").Namespace(v1.NamespaceDefault).TopologyKey("rack").Obj(), + }, + filteredNodesStatuses: framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)), + features: feature.Features{EnableTopologyAwareWorkloadScheduling: true, EnableWorkloadAwarePreemption: true}, + wantResult: nil, + wantStatus: fwk.NewStatus(fwk.Unschedulable, "preemption: not eligible due to placement-based pod group scheduling limitation"), + }, + { + name: "pod with SchedulingGroup with TAS without scheduling constraint enabled should preempt", + pod: st.MakePod().Name("p-with-podgroup").Namespace(v1.NamespaceDefault).PodGroupName("foo").Priority(highPriority).Obj(), + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(onePodRes).Obj(), + }, + podGroups: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("foo").Namespace(v1.NamespaceDefault).Obj(), + }, + filteredNodesStatuses: framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)), + features: feature.Features{EnableTopologyAwareWorkloadScheduling: true}, + wantResult: framework.NewPostFilterResultWithNominatedNode("node1"), + wantStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "pod with SchedulingGroup with TAS with WAP enabled should not preempt", + pod: st.MakePod().Name("p-with-podgroup").Namespace(v1.NamespaceDefault).PodGroupName("foo").Priority(highPriority).Obj(), + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(onePodRes).Obj(), + }, + podGroups: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("foo").Namespace(v1.NamespaceDefault).Obj(), + }, + filteredNodesStatuses: framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)), + features: feature.Features{EnableTopologyAwareWorkloadScheduling: true, EnableWorkloadAwarePreemption: true}, + wantResult: nil, + wantStatus: fwk.NewStatus(fwk.Unschedulable, "preemption: not eligible due to workload aware preemption enabled"), + }, + { + name: "pod with SchedulingGroup with WAP enabled should not preempt", + pod: st.MakePod().Name("p-with-podgroup").PodGroupName("foo").Priority(highPriority).Obj(), + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(onePodRes).Obj(), + }, + filteredNodesStatuses: framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)), + features: feature.Features{EnableWorkloadAwarePreemption: true}, + wantResult: nil, + wantStatus: fwk.NewStatus(fwk.Unschedulable, "preemption: not eligible due to workload aware preemption enabled"), + }, + { + name: "pod with SchedulingGroup with TAS and WAP disabled should preempt", + pod: st.MakePod().Name("p-with-podgroup").PodGroupName("foo").Priority(highPriority).Obj(), + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(onePodRes).Obj(), + }, + filteredNodesStatuses: framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)), + features: feature.Features{EnableTopologyAwareWorkloadScheduling: false, EnableWorkloadAwarePreemption: false}, + wantResult: framework.NewPostFilterResultWithNominatedNode("node1"), + wantStatus: fwk.NewStatus(fwk.Success), + }, } for _, asyncAPICallsEnabled := range []bool{true, false} { @@ -418,6 +529,12 @@ func TestPostFilter(t *testing.T) { t.Fatal(err) } } + pgInformer := informerFactory.Scheduling().V1alpha2().PodGroups().Informer() + for i := range tt.podGroups { + if err := pgInformer.GetStore().Add(tt.podGroups[i]); err != nil { + t.Fatal(err) + } + } // Register NodeResourceFit as the Filter & PreFilter plugin. registeredPlugins := []tf.RegisterPluginFunc{ @@ -450,16 +567,17 @@ func TestPostFilter(t *testing.T) { frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(tt.pods, tt.nodes)), frameworkruntime.WithLogger(logger), frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), ) if err != nil { t.Fatal(err) } if asyncAPICallsEnabled { - cache := internalcache.New(ctx, 100*time.Millisecond, apiDispatcher) + cache := internalcache.New(ctx, apiDispatcher, false) f.SetAPICacher(apicache.New(nil, cache)) } - p, err := New(ctx, getDefaultDefaultPreemptionArgs(), f, feature.Features{}) + p, err := New(ctx, getDefaultDefaultPreemptionArgs(), f, tt.features) if err != nil { t.Fatal(err) } @@ -2169,7 +2287,7 @@ func TestPreempt(t *testing.T) { defer apiDispatcher.Close() } - cache := internalcache.New(ctx, time.Duration(0), apiDispatcher) + cache := internalcache.New(ctx, apiDispatcher, false) for _, pod := range testPods { if err := cache.AddPod(logger, pod.DeepCopy()); err != nil { t.Fatalf("Failed to add pod %s: %v", pod.Name, err) @@ -2217,6 +2335,7 @@ func TestPreempt(t *testing.T) { frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(testPods, nodes)), frameworkruntime.WithInformerFactory(informerFactory), frameworkruntime.WithWaitingPods(waitingPods), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), frameworkruntime.WithLogger(logger), frameworkruntime.WithPodActivator(&fakePodActivator{}), ) @@ -2339,3 +2458,190 @@ type fakePodActivator struct { } func (f *fakePodActivator) Activate(logger klog.Logger, pods map[string]*v1.Pod) {} + +func TestPreEnqueue(t *testing.T) { + onePodRes := map[v1.ResourceName]string{v1.ResourcePods: "1"} + tests := []struct { + name string + podToTriggerPreemption *v1.Pod + podToCheck *v1.Pod + pgs []*v1alpha2.PodGroup + features feature.Features + expectPreemption bool + wantStatus *fwk.Status + }{ + { + name: "AsyncPreemption disabled, returns nil", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + features: feature.Features{EnableAsyncPreemption: false}, + expectPreemption: false, + wantStatus: nil, + }, + { + name: "AsyncPreemption enabled, same pod, returns UnschedulableAndUnresolvable", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + features: feature.Features{EnableAsyncPreemption: true}, + expectPreemption: true, + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for the preemption for this pod to be finished"), + }, + { + name: "AsyncPreemption enabled, different pod, returns nil", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p_other").UID("p_other").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + features: feature.Features{EnableAsyncPreemption: true}, + expectPreemption: true, + wantStatus: nil, + }, + { + name: "WAP enabled, pod in same PodGroup, returns UnschedulableAndUnresolvable", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).PodGroupName("pg1").Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p_other").UID("p_other").Namespace(v1.NamespaceDefault).PodGroupName("pg1").Priority(highPriority).Obj(), + pgs: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + }, + features: feature.Features{EnableAsyncPreemption: true, EnableWorkloadAwarePreemption: true}, + expectPreemption: true, + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for the preemption for this pod group to be finished"), + }, + { + name: "WAP disabled, pod in same PodGroup, returns nil", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).PodGroupName("pg1").Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p_other").UID("p_other").Namespace(v1.NamespaceDefault).PodGroupName("pg1").Priority(highPriority).Obj(), + pgs: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").Namespace(v1.NamespaceDefault).Obj(), + }, + features: feature.Features{EnableAsyncPreemption: true, EnableWorkloadAwarePreemption: false}, + expectPreemption: true, + wantStatus: nil, + }, + { + name: "WAP enabled, pod in different PodGroup, returns nil", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).PodGroupName("pg1").Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p_other").UID("p_other").Namespace(v1.NamespaceDefault).PodGroupName("pg2").Priority(highPriority).Obj(), + pgs: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + st.MakePodGroup().Name("pg2").UID("pg2").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + }, + features: feature.Features{EnableAsyncPreemption: true, EnableWorkloadAwarePreemption: true}, + expectPreemption: true, + wantStatus: nil, + }, + { + name: "WAP enabled, pod group not found, returns nil", + podToTriggerPreemption: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).PodGroupName("pg1").Priority(highPriority).Obj(), + podToCheck: st.MakePod().Name("p_other").UID("p_other").Namespace(v1.NamespaceDefault).PodGroupName("pg_missing").Priority(highPriority).Obj(), + pgs: []*v1alpha2.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").Namespace(v1.NamespaceDefault).Priority(highPriority).Obj(), + }, + features: feature.Features{EnableAsyncPreemption: true, EnableWorkloadAwarePreemption: true}, + expectPreemption: false, + wantStatus: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pods := []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Namespace(v1.NamespaceDefault).Node("node1").Obj(), + } + filteredNodesStatuses := framework.NewNodeToStatus(map[string]*fwk.Status{ + "node1": fwk.NewStatus(fwk.Unschedulable), + }, fwk.NewStatus(fwk.UnschedulableAndUnresolvable)) + + podItems := []v1.Pod{} + for _, pod := range pods { + podItems = append(podItems, *pod) + } + cs := clientsetfake.NewClientset(&v1.PodList{Items: podItems}) + informerFactory := informers.NewSharedInformerFactory(cs, 0) + podInformer := informerFactory.Core().V1().Pods().Informer() + if err := podInformer.GetStore().Add(tt.podToTriggerPreemption); err != nil { + t.Fatal(err) + } + for i := range pods { + if err := podInformer.GetStore().Add(pods[i]); err != nil { + t.Fatal(err) + } + } + pgInformer := informerFactory.Scheduling().V1alpha2().PodGroups().Informer() + for i := range tt.pgs { + if err := pgInformer.GetStore().Add(tt.pgs[i]); err != nil { + t.Fatal(err) + } + } + + registeredPlugins := []tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterPluginAsExtensions(noderesources.Name, nodeResourcesFitFunc, "Filter", "PreFilter"), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + } + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + f, err := tf.NewFramework(ctx, registeredPlugins, "", + frameworkruntime.WithClientSet(cs), + frameworkruntime.WithEventRecorder(&events.FakeRecorder{}), + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)), + frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(pods, []*v1.Node{st.MakeNode().Name("node1").Capacity(onePodRes).Obj()})), + frameworkruntime.WithLogger(logger), + frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), + ) + if err != nil { + t.Fatal(err) + } + + p, err := New(ctx, getDefaultDefaultPreemptionArgs(), f, tt.features) + if err != nil { + t.Fatal(err) + } + + finishPreemption := make(chan struct{}) + + p.Executor.PreemptPod = func(ctx context.Context, c preemption.Candidate, preemptor preemption.ExecutorPreemptor, victim *v1.Pod, pluginName string) error { + if !tt.features.EnableAsyncPreemption { + return nil + } + <-finishPreemption + return nil + } + + // Fill the cycle state + state := framework.NewCycleState() + if _, status, _ := f.RunPreFilterPlugins(ctx, state, tt.podToTriggerPreemption); !status.IsSuccess() { + t.Errorf("Unexpected PreFilter Status: %v", status) + } + + // Trigger preemption. Given custom PreemptPod implementation, the async preemption will not finish until + // finishPreemption is closed. + if tt.features.EnableWorkloadAwarePreemption && tt.podToTriggerPreemption.Spec.SchedulingGroup != nil { + pg, err := informerFactory.Scheduling().V1alpha2().PodGroups().Lister().PodGroups(tt.podToTriggerPreemption.Namespace).Get(*tt.podToTriggerPreemption.Spec.SchedulingGroup.PodGroupName) + if err != nil { + t.Fatalf("could not find pg: %v", err) + } + podsToPreempt := []*v1.Pod{tt.podToTriggerPreemption} + pgSchedulingFunc := func(ctx context.Context) *fwk.Status { + nodeInfo, _ := f.SnapshotSharedLister().NodeInfos().Get("node1") + if len(nodeInfo.GetPods()) == 0 { + return fwk.NewStatus(fwk.Success) + } + return fwk.NewStatus(fwk.Unschedulable, "need to preempt") + } + p.PodGroupPostFilter(ctx, pg, podsToPreempt, pgSchedulingFunc) + } else { + p.PostFilter(ctx, state, tt.podToTriggerPreemption, filteredNodesStatuses) + } + + status := p.PreEnqueue(ctx, tt.podToCheck) + close(finishPreemption) + + if diff := cmp.Diff(tt.wantStatus, status); diff != "" { + t.Errorf("Unexpected status (-want, +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/scheduler/framework/plugins/dynamicresources/allocateddevices.go b/pkg/scheduler/framework/plugins/dynamicresources/allocateddevices.go index 21d192ffc8930..f74e466a40f3f 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/allocateddevices.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/allocateddevices.go @@ -120,6 +120,13 @@ func (a *allocatedDevices) Get() (sets.Set[structured.DeviceID], int64) { return a.ids.Clone(), a.revision } +func (a *allocatedDevices) GetSharedDeviceIDs() (sets.Set[structured.SharedDeviceID], int64) { + a.mutex.RLock() + defer a.mutex.RUnlock() + + return a.shareIDs.Clone(), a.revision +} + func (a *allocatedDevices) Capacities() (structured.ConsumedCapacityCollection, int64) { a.mutex.RLock() defer a.mutex.RUnlock() diff --git a/pkg/scheduler/framework/plugins/dynamicresources/claims.go b/pkg/scheduler/framework/plugins/dynamicresources/claims.go index cb2c15efc9c5f..66968a374d8cb 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/claims.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/claims.go @@ -20,6 +20,7 @@ import ( "fmt" "iter" + v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" "k8s.io/apimachinery/pkg/types" ) @@ -32,6 +33,10 @@ import ( type claimStore struct { // claims contains all user-owned claims, optionally followed by the special ResourceClaim for extended resources. claims []*resourceapi.ResourceClaim + // bindTo lists the objects to which each of the claims should be bound: + // either a Pod or its PodGroup. It is always empty when PodGroup claims are + // disabled. + bindTo []resourceapi.ResourceClaimConsumerReference // numUserOwned is the number of claims without the special ResourceClaim. numUserOwned int // initialExtendedResourceClaimUID is the initial extended resource claim UID from PreFilter phase. @@ -40,9 +45,10 @@ type claimStore struct { } // newClaimStore stores the list of user-owned claims and the optional claim owned by the scheduler. -func newClaimStore(claims []*resourceapi.ResourceClaim, extendedResourceClaim *resourceapi.ResourceClaim) claimStore { +func newClaimStore(claims []*resourceapi.ResourceClaim, extendedResourceClaim *resourceapi.ResourceClaim, claimBindings []resourceapi.ResourceClaimConsumerReference) claimStore { cs := claimStore{ claims: claims, + bindTo: claimBindings, numUserOwned: len(claims), initialExtendedResourceClaimUID: "", } @@ -127,6 +133,21 @@ func (cs claimStore) set(i int, c *resourceapi.ResourceClaim) { cs.claims[i] = c } +// getBinding returns a reference to the object to which the ResourceClaim at +// the given index should be bound: either a Pod or its PodGroup. For extended +// resource claims, it always returns a reference to the Pod. +func (cs claimStore) getBinding(i int, pod *v1.Pod) resourceapi.ResourceClaimConsumerReference { + if i >= len(cs.bindTo) { + return resourceapi.ResourceClaimConsumerReference{ + APIGroup: v1.GroupName, + Resource: "pods", + Name: pod.Name, + UID: pod.UID, + } + } + return cs.bindTo[i] +} + // updateExtendedResourceClaim updates the input claim as extended resource // claim in the internal claims slice. // It returns error when there is no extended resource claim in the internal diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dra_manager.go b/pkg/scheduler/framework/plugins/dynamicresources/dra_manager.go index 2be14d10ec691..a1703319f6c0c 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dra_manager.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dra_manager.go @@ -20,18 +20,26 @@ import ( "context" "errors" "fmt" + "iter" + "slices" "sync" + "github.com/go-logr/logr" + resourceapi "k8s.io/api/resource/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" resourcelisters "k8s.io/client-go/listers/resource/v1" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" "k8s.io/dynamic-resource-allocation/deviceclass/extendedresourcecache" resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker" "k8s.io/dynamic-resource-allocation/structured" + "k8s.io/dynamic-resource-allocation/structured/schedulerapi" "k8s.io/klog/v2" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/features" @@ -47,6 +55,7 @@ type DefaultDRAManager struct { resourceClaimTracker *claimTracker resourceSliceLister *resourceSliceLister deviceClassLister *deviceClassLister + podGroupLister *podGroupLister extendedResourceCache *extendedresourcecache.ExtendedResourceCache } @@ -55,7 +64,7 @@ func NewDRAManager(ctx context.Context, claimsCache *assumecache.AssumeCache, re manager := &DefaultDRAManager{ resourceClaimTracker: &claimTracker{ cache: claimsCache, - inFlightAllocations: &sync.Map{}, + inFlightAllocations: make(map[types.UID]inFlightAllocation), allocatedDevices: newAllocatedDevices(logger), logger: logger, }, @@ -67,6 +76,12 @@ func NewDRAManager(ctx context.Context, claimsCache *assumecache.AssumeCache, re manager.extendedResourceCache = extendedresourcecache.NewExtendedResourceCache(logger) } + pgLister := &podGroupLister{} + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) { + pgLister.podGroupLister = informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + } + manager.podGroupLister = pgLister + // Reacting to events is more efficient than iterating over the list // repeatedly in PreFilter. manager.resourceClaimTracker.cache.AddEventHandler(manager.resourceClaimTracker.allocatedDevices.handlers()) @@ -86,6 +101,10 @@ func (s *DefaultDRAManager) DeviceClasses() fwk.DeviceClassLister { return s.deviceClassLister } +func (s *DefaultDRAManager) PodGroups() fwk.PodGroupLister { + return s.podGroupLister +} + // DeviceClassResolver will always return a valid interface implementation. It // wraps a nil extendedresourcecache.ExtendedResourceCache if the feature is // disabled. @@ -120,6 +139,21 @@ func (l *deviceClassLister) List() ([]*resourceapi.DeviceClass, error) { return l.classLister.List(labels.Everything()) } +var _ fwk.PodGroupLister = &podGroupLister{} + +type podGroupLister struct { + podGroupLister schedulinglisters.PodGroupLister +} + +// Get implements [fwk.PodGroupLister]. When the inner podGroupLister is nil, +// Get always returns a new BadRequest error. +func (l *podGroupLister) Get(namespace, podGroupName string) (*schedulingapi.PodGroup, error) { + if l.podGroupLister == nil { + return nil, apierrors.NewBadRequest("listing podgroups is disabled") + } + return l.podGroupLister.PodGroups(namespace).Get(podGroupName) +} + var _ fwk.ResourceClaimTracker = &claimTracker{} type claimTracker struct { @@ -142,12 +176,16 @@ type claimTracker struct { // - would make integration with cluster autoscaler harder because it would need // to trigger informer callbacks. cache *assumecache.AssumeCache + // inFlightMutex syncs access to inFlightAllocations. + inFlightMutex sync.RWMutex // inFlightAllocations is a map from claim UUIDs to claim objects for those claims // for which allocation was triggered during a scheduling cycle and the // corresponding claim status update call in PreBind has not been done - // yet. If another pod needs the claim, the pod is treated as "not - // schedulable yet". The cluster event for the claim status update will - // make it schedulable. + // yet. It also includes a reference count tracking how many actively + // scheduling Pods in a PodGroup are using that pending allocation. If + // another pod outside the PodGroup needs the claim, the pod is treated as + // "not schedulable yet". For those pods, the cluster event for the + // claim status update will make them schedulable. // // This mechanism avoids the following problem: // - Pod A triggers allocation for claim X. @@ -161,37 +199,92 @@ type claimTracker struct { // problem: // - Pod A and B get scheduled as above. // - PreBind for pod A gets called first, then fails with a temporary API error. - // It removes the updated claim from the assume cache because of that. + // It removes the updated claim from the in-flight claims because of that. // - PreBind for pod B gets called next and succeeds with adding the // allocation and its own reservedFor entry. // - The assume cache is now not reflecting that the claim is allocated, // which could lead to reusing the same resource for some other claim. // + // For pods in a PodGroup, a pending allocation may be shared among several + // pods in the group. In the PreBind phase, the allocation will be written + // for the first pod that succeeds. The scenario above is prevented by + // keeping the pending allocation in-flight as long as it has not been + // unreserved for every pod in the group, as tracked by + // inFlightAllocationSharers. + // // A sync.Map is used because in practice sharing of a claim between // pods is expected to be rare compared to per-pod claim, so we end up // hitting the "multiple goroutines read, write, and overwrite entries // for disjoint sets of keys" case that sync.Map is optimized for. - inFlightAllocations *sync.Map + inFlightAllocations map[types.UID]inFlightAllocation allocatedDevices *allocatedDevices logger klog.Logger } -func (c *claimTracker) ClaimHasPendingAllocation(claimUID types.UID) bool { - _, found := c.inFlightAllocations.Load(claimUID) - return found +type inFlightAllocation struct { + claim *resourceapi.ResourceClaim + sharers int +} + +func (c *claimTracker) GetPendingAllocation(claimUID types.UID) *resourceapi.AllocationResult { + c.inFlightMutex.RLock() + defer c.inFlightMutex.RUnlock() + + inFlight, found := c.inFlightAllocations[claimUID] + if !found || inFlight.claim == nil { + return nil + } + return inFlight.claim.Status.Allocation } func (c *claimTracker) SignalClaimPendingAllocation(claimUID types.UID, allocatedClaim *resourceapi.ResourceClaim) error { - c.inFlightAllocations.Store(claimUID, allocatedClaim) + c.inFlightMutex.Lock() + defer c.inFlightMutex.Unlock() + + inFlight, found := c.inFlightAllocations[claimUID] + if found { + inFlight.sharers++ + c.inFlightAllocations[claimUID] = inFlight + + claim := inFlight.claim + c.logger.V(5).Info("Added share for in-flight claim", "claim", klog.KObj(claim), "uid", claimUID, "version", claim.ResourceVersion, "sharers", inFlight.sharers) + return nil + } + + c.inFlightAllocations[claimUID] = inFlightAllocation{ + claim: allocatedClaim, + sharers: 1, + } + // This is the same verbosity as the corresponding log in the assume cache. + c.logger.V(5).Info("Added in-flight claim", "claim", klog.KObj(allocatedClaim), "uid", claimUID, "version", allocatedClaim.ResourceVersion) // There's no reason to return an error in this implementation, but the error is helpful for other implementations. // For example, implementations that have to deal with fake claims might want to return an error if the allocation // is for an invalid claim. return nil } -func (c *claimTracker) RemoveClaimPendingAllocation(claimUID types.UID) (deleted bool) { - _, found := c.inFlightAllocations.LoadAndDelete(claimUID) - return found +func (c *claimTracker) MaybeRemoveClaimPendingAllocation(claimUID types.UID, forceRemove bool) (deleted bool) { + c.inFlightMutex.Lock() + defer c.inFlightMutex.Unlock() + + inFlight, found := c.inFlightAllocations[claimUID] + // The assume cache doesn't log this, but maybe it should. + if !found { + c.logger.V(5).Info("Redundant remove of in-flight claim, not found", "uid", claimUID) + return false + } + claim := inFlight.claim + + if forceRemove || inFlight.sharers == 1 { + delete(c.inFlightAllocations, claimUID) + c.logger.V(5).Info("Removed in-flight claim", "claim", klog.KObj(claim), "uid", claimUID, "version", claim.ResourceVersion) + return true + } + + inFlight.sharers-- + c.inFlightAllocations[claimUID] = inFlight + c.logger.V(5).Info("Claim is still shared by other pods, not removing in-flight claim", "claim", klog.KObj(claim), "uid", claimUID, "version", claim.ResourceVersion, "sharers", inFlight.sharers) + return false } func (c *claimTracker) Get(namespace, claimName string) (*resourceapi.ResourceClaim, error) { @@ -238,20 +331,24 @@ func (c *claimTracker) List() ([]*resourceapi.ResourceClaim, error) { // so we don't need to re-check the in-flight claims. var errClaimTrackerConcurrentModification = errors.New("conflicting concurrent modification") -func (c *claimTracker) ListAllAllocatedDevices() (sets.Set[structured.DeviceID], error) { +func (c *claimTracker) ListAllAllocatedDevices() (a sets.Set[structured.DeviceID], err error) { + c.logger.V(6).Info("Starting ListAllAllocatedDevices") + defer func() { + c.logger.V(6).Info("Finished ListAllAllocatedDevices", "allocatedDevices", logAllocatedDevices(c.logger, a), "err", err) + }() + // Start with a fresh set that matches the current known state of the // world according to the informers. allocated, revision := c.allocatedDevices.Get() // Whatever is in flight also has to be checked. - c.inFlightAllocations.Range(func(key, value any) bool { - claim := value.(*resourceapi.ResourceClaim) + for _, inFlight := range c.allInFlightAllocationsRLocked() { + claim := inFlight.claim foreachAllocatedDevice(claim, func(deviceID structured.DeviceID) { c.logger.V(6).Info("Device is in flight for allocation", "device", deviceID, "claim", klog.KObj(claim)) allocated.Insert(deviceID) }, false, func(structured.SharedDeviceID) {}, func(structured.DeviceConsumedCapacity) {}) - return true - }) + } if revision == c.allocatedDevices.Revision() { // Our current result is valid, nothing changed in the meantime. @@ -261,38 +358,79 @@ func (c *claimTracker) ListAllAllocatedDevices() (sets.Set[structured.DeviceID], return nil, errClaimTrackerConcurrentModification } -func (c *claimTracker) GatherAllocatedState() (*structured.AllocatedState, error) { +// GatherAllocatedState collects and returns the current allocation state of all devices +// across the cluster. This includes: +// - AllocatedDevices: Set of device IDs that are fully allocated (dedicated mode) +// - AllocatedSharedDeviceIDs: Set of shared device IDs when consumable capacity is enabled +// - AggregatedCapacity: Consumed capacity across all devices when consumable capacity is enabled +// +// The function handles two allocation models: +// 1. Legacy dedicated mode (DRAConsumableCapacity disabled): Devices are allocated exclusively +// to a single claim. Shared devices are converted to their base device IDs. +// 2. Consumable capacity mode (DRAConsumableCapacity enabled): Devices can be shared across +// multiple claims with capacity tracking. +// +// The function ensures consistency by: +// - Reading allocation state from informer-backed cache +// - Including in-flight allocations that haven't been persisted yet +// - Using revision numbers to detect concurrent modifications and retry if needed +// +// Returns errClaimTrackerConcurrentModification if the state changed during collection, +// indicating the caller should retry. +func (c *claimTracker) GatherAllocatedState() (s *structured.AllocatedState, err error) { + c.logger.V(6).Info("Starting GatherAllocatedState") + defer func() { + var a sets.Set[structured.DeviceID] + if s != nil { + a = s.AllocatedDevices + } + c.logger.V(6).Info("Finished GatherAllocatedState", "allocatedDevices", logAllocatedDevices(c.logger, a), "err", err) + }() + // Start with a fresh set that matches the current known state of the // world according to the informers. + enabledConsumableCapacity := utilfeature.DefaultFeatureGate.Enabled(features.DRAConsumableCapacity) + allocated, revision1 := c.allocatedDevices.Get() - allocatedSharedDeviceIDs := sets.New[structured.SharedDeviceID]() - aggregatedCapacity, revision2 := c.allocatedDevices.Capacities() + allocatedSharedDeviceIDs, revision2 := c.allocatedDevices.GetSharedDeviceIDs() + aggregatedCapacity, revision3 := c.allocatedDevices.Capacities() - if revision1 != revision2 { + if revision1 != revision2 || revision2 != revision3 { // Already not consistent. Try again. return nil, errClaimTrackerConcurrentModification } - enabledConsumableCapacity := utilfeature.DefaultFeatureGate.Enabled(features.DRAConsumableCapacity) + if !enabledConsumableCapacity { + // When the DRAConsumableCapacity feature is disabled, we fall back to the legacy + // dedicated device allocation model. + // This ensures backward compatibility with the original DRA behavior where devices + // could only be allocated exclusively to a single claim. + for sharedDeviceID := range allocatedSharedDeviceIDs { + allocated.Insert(sharedDeviceID.GetDeviceID()) + } + // Reset allocatedSharedDeviceIDs and aggregatedCapacity + allocatedSharedDeviceIDs = sets.New[structured.SharedDeviceID]() + aggregatedCapacity = make(schedulerapi.ConsumedCapacityCollection) + } // Whatever is in flight also has to be checked. - c.inFlightAllocations.Range(func(key, value any) bool { - claim := value.(*resourceapi.ResourceClaim) - foreachAllocatedDevice(claim, func(deviceID structured.DeviceID) { - c.logger.V(6).Info("Device is in flight for allocation", "device", deviceID, "claim", klog.KObj(claim)) - allocated.Insert(deviceID) - }, + for _, inFlight := range c.allInFlightAllocationsRLocked() { + claim := inFlight.claim + foreachAllocatedDevice(claim, + func(deviceID structured.DeviceID) { // dedicatedDeviceCallback + c.logger.V(6).Info("Device is in flight for allocation", "device", deviceID, "claim", klog.KObj(claim)) + allocated.Insert(deviceID) + }, enabledConsumableCapacity, - func(sharedDeviceID structured.SharedDeviceID) { + func(sharedDeviceID structured.SharedDeviceID) { // sharedDeviceCallback c.logger.V(6).Info("Device is in flight for allocation", "shared device", sharedDeviceID, "claim", klog.KObj(claim)) allocatedSharedDeviceIDs.Insert(sharedDeviceID) - }, func(capacity structured.DeviceConsumedCapacity) { + }, + func(capacity structured.DeviceConsumedCapacity) { // consumedCapacityCallback c.logger.V(6).Info("Device is in flight for allocation", "consumed capacity", capacity, "claim", klog.KObj(claim)) aggregatedCapacity.Insert(capacity) }) - return true - }) - + } if revision1 == c.allocatedDevices.Revision() { // Our current result is valid, nothing changed in the meantime. return &structured.AllocatedState{ @@ -305,6 +443,18 @@ func (c *claimTracker) GatherAllocatedState() (*structured.AllocatedState, error return nil, errClaimTrackerConcurrentModification } +func (c *claimTracker) allInFlightAllocationsRLocked() iter.Seq2[types.UID, inFlightAllocation] { + return func(yield func(types.UID, inFlightAllocation) bool) { + c.inFlightMutex.RLock() + defer c.inFlightMutex.RUnlock() + for uid, inFlight := range c.inFlightAllocations { + if !yield(uid, inFlight) { + return + } + } + } +} + func (c *claimTracker) AssumeClaimAfterAPICall(claim *resourceapi.ResourceClaim) error { return c.cache.Assume(claim) } @@ -312,3 +462,64 @@ func (c *claimTracker) AssumeClaimAfterAPICall(claim *resourceapi.ResourceClaim) func (c *claimTracker) AssumedClaimRestore(namespace, claimName string) { c.cache.Restore(namespace + "/" + claimName) } + +// At V(6), log only a limited number of devices to avoid blowing up logs. For +// many E2E tests, 10 devices is enough for all devices without having to +// truncate, at least when running the tests sequentially. +const maxDevicesLevel6 = 10 + +// logAllocatedDevices returns a handle for the value in a structured log call which +// includes varying amounts of information about the allocated devices, depending on +// the verbosity of the logger. +func logAllocatedDevices(logger klog.Logger, allocatedDevices sets.Set[structured.DeviceID]) any { + // We need to check verbosity here because our caller's source code + // location may be relevant (-vmodule !). + helper, logger := logger.WithCallStackHelper() + helper() + + // We always produce the same output at V <= 5. 6 adds all IDs. + verbosity := 5 + for i := 7; i > verbosity; i-- { + if loggerV := logger.V(i); loggerV.Enabled() { + verbosity = i + break + } + } + + return &allocatedDevicesLogger{verbosity, allocatedDevices} +} + +type allocatedDevicesLogger struct { + verbosity int + devices sets.Set[structured.DeviceID] +} + +var _ logr.Marshaler = &allocatedDevicesLogger{} + +func (a *allocatedDevicesLogger) MarshalLog() any { + if a.verbosity < 6 { + return nil + } + + info := struct { + Count int `json:"count"` + Devices []string `json:"devices"` + }{ + Count: len(a.devices), + } + ids := make([]string, 0, len(a.devices)) + for id := range a.devices { + ids = append(ids, id.String()) + } + slices.Sort(ids) + if a.verbosity == 6 && len(ids) > maxDevicesLevel6 { + truncated := make([]string, 0, maxDevicesLevel6+1) + truncated = append(truncated, ids[:maxDevicesLevel6/2]...) + truncated = append(truncated, "...") + truncated = append(truncated, ids[len(ids)-maxDevicesLevel6/2:]...) + ids = truncated + } + info.Devices = ids + + return info +} diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dra_manager_test.go b/pkg/scheduler/framework/plugins/dynamicresources/dra_manager_test.go new file mode 100644 index 0000000000000..8fc8abf325ca2 --- /dev/null +++ b/pkg/scheduler/framework/plugins/dynamicresources/dra_manager_test.go @@ -0,0 +1,206 @@ +/* +Copyright 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 dynamicresources + +import ( + "maps" + "testing" + + "github.com/onsi/gomega" + resourceapi "k8s.io/api/resource/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/test/utils/ktesting" +) + +const ( + claimUID = types.UID("claim-uid-1") + otherClaimUID = types.UID("claim-uid-2") +) + +func TestSignalClaimPendingAllocation(t *testing.T) { + testSignalClaimPendingAllocation(ktesting.Init(t)) +} +func testSignalClaimPendingAllocation(tCtx ktesting.TContext) { + tests := map[string]struct { + inFlightAllocations map[types.UID]inFlightAllocation + claimUID types.UID + allocatedClaim *resourceapi.ResourceClaim + expectedInFlightAllocations map[types.UID]inFlightAllocation + }{ + "empty": { + claimUID: claimUID, + allocatedClaim: allocatedClaim, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 1}, + }, + }, + "already-exists": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 1}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + claimUID: claimUID, + allocatedClaim: allocatedClaim, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 2}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + }, + } + + for name, test := range tests { + tCtx.Run(name, func(tCtx ktesting.TContext) { + c := &claimTracker{ + logger: tCtx.Logger(), + inFlightAllocations: make(map[types.UID]inFlightAllocation), + } + maps.Copy(c.inFlightAllocations, test.inFlightAllocations) + + err := c.SignalClaimPendingAllocation(test.claimUID, test.allocatedClaim) + tCtx.ExpectNoError(err) + tCtx.Expect(c.inFlightAllocations).To(gomega.Equal(test.expectedInFlightAllocations)) + }) + } +} + +func TestGetPendingAllocation(t *testing.T) { + testGetPendingAllocation(ktesting.Init(t)) +} +func testGetPendingAllocation(tCtx ktesting.TContext) { + tests := map[string]struct { + inFlightAllocations map[types.UID]inFlightAllocation + claimUID types.UID + expected *resourceapi.AllocationResult + }{ + "empty": { + claimUID: claimUID, + expected: nil, + }, + "nil-claim": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: nil}, + }, + claimUID: claimUID, + expected: nil, + }, + "claim": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 1}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + claimUID: claimUID, + expected: allocationResult, + }, + } + + for name, test := range tests { + tCtx.Run(name, func(tCtx ktesting.TContext) { + c := &claimTracker{ + logger: tCtx.Logger(), + inFlightAllocations: make(map[types.UID]inFlightAllocation), + } + maps.Copy(c.inFlightAllocations, test.inFlightAllocations) + beforeInFlight := maps.Clone(c.inFlightAllocations) + + actual := c.GetPendingAllocation(test.claimUID) + tCtx.Expect(actual).To(gomega.Equal(test.expected)) + // Get is strictly read-only + tCtx.Expect(c.inFlightAllocations).To(gomega.Equal(beforeInFlight)) + }) + } +} + +func TestMaybeRemoveClaimPendingAllocation(t *testing.T) { + testMaybeRemoveClaimPendingAllocation(ktesting.Init(t)) +} +func testMaybeRemoveClaimPendingAllocation(tCtx ktesting.TContext) { + tests := map[string]struct { + inFlightAllocations map[types.UID]inFlightAllocation + claimUID types.UID + forceRemove bool + expected bool + expectedInFlightAllocations map[types.UID]inFlightAllocation + }{ + "empty": { + claimUID: claimUID, + forceRemove: true, + expected: false, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{}, + }, + "delete-last-sharer": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 1}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + claimUID: claimUID, + expected: true, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{ + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + }, + "force-delete-last-sharer": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 1}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + claimUID: claimUID, + forceRemove: true, + expected: true, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{ + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + }, + "decrement-remaining-sharers": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 2}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + claimUID: claimUID, + expected: false, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 1}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + }, + "force-delete-remaining-sharers": { + inFlightAllocations: map[types.UID]inFlightAllocation{ + claimUID: {claim: allocatedClaim, sharers: 2}, + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + claimUID: claimUID, + forceRemove: true, + expected: true, + expectedInFlightAllocations: map[types.UID]inFlightAllocation{ + otherClaimUID: {claim: allocatedClaim2, sharers: 10}, + }, + }, + } + + for name, test := range tests { + tCtx.Run(name, func(tCtx ktesting.TContext) { + c := &claimTracker{ + logger: tCtx.Logger(), + inFlightAllocations: make(map[types.UID]inFlightAllocation), + } + maps.Copy(c.inFlightAllocations, test.inFlightAllocations) + + actual := c.MaybeRemoveClaimPendingAllocation(test.claimUID, test.forceRemove) + tCtx.Expect(actual).To(gomega.Equal(test.expected), "wrong value for deletion indicator") + tCtx.Expect(c.inFlightAllocations).To(gomega.Equal(test.expectedInFlightAllocations)) + }) + } +} diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go index 75551dc98055d..c298b1766684e 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go @@ -28,6 +28,7 @@ import ( v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" @@ -50,6 +51,7 @@ import ( "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" + schedmetrics "k8s.io/kubernetes/pkg/scheduler/metrics" schedutil "k8s.io/kubernetes/pkg/scheduler/util" "k8s.io/utils/ptr" ) @@ -113,11 +115,32 @@ func (d *stateData) Clone() fwk.StateData { return d } +// This state is persisted in the PodGroup CycleState. Because we save the +// pointer in fwk.CycleState, in the later phases we don't need to call Write +// method to update the value. +type podGroupStateData struct { + // pendingAllocations stores the UIDs of ResourceClaims that are currently + // pending for *this* PodGroup scheduling cycle. The DRAManager continues to + // track inter-cycle sharing of pending allocations, including the + // allocations themselves and how many Pods are sharing them. + // + // It does not need to be protected by a lock because a PodGroup's + // CycleState is only available during synchronous scheduling phases and is + // cleared before asynchronous phases begin. + pendingAllocations sets.Set[types.UID] +} + +func (d *podGroupStateData) Clone() fwk.StateData { + return d +} + type informationForClaim struct { // Node selector based on the claim status if allocated. availableOnNodes *nodeaffinity.NodeSelector - // Set by Reserved, published by PreBind, empty if nothing had to be allocated. + // Set by Reserve, published by PreBind, empty if nothing had to be allocated. + // May be set by PreFilter if this allocation was made by a previous Pod's + // Reserve but not yet published in PreBind. allocation *resourceapi.AllocationResult } @@ -132,6 +155,9 @@ type nodeAllocation struct { // containerResourceRequestMappings has the container, extended resource, and device request mappings // calculated at the Filter phase, and used at the PreBind phase. containerResourceRequestMappings []v1.ContainerExtendedResourceRequest + // nodeAllocatableResourceClaimStatuses stores the calculated node allocatable resource allocations through DRA. + // This is populated during Filter stage and passed to PreBind. + nodeAllocatableResourceClaimStatuses []v1.NodeAllocatableResourceClaimStatus } // DynamicResources is a plugin that ensures that ResourceClaims are allocated. @@ -146,6 +172,11 @@ type DynamicResources struct { draManager fwk.SharedDRAManager } +var ( + ErrDeviceBindingTimeout = errors.New("device binding timeout") + ErrDeviceBindingFailed = errors.New("device binding failed") +) + // New initializes a new plugin and returns it. func New(ctx context.Context, plArgs runtime.Object, fh fwk.Handle, fts feature.Features) (fwk.Plugin, error) { if !fts.EnableDynamicResourceAllocation { @@ -174,7 +205,10 @@ func New(ctx context.Context, plArgs runtime.Object, fh fwk.Handle, fts feature. // This is a LRU cache for compiled CEL expressions. The most // recent 10 of them get reused across different scheduling // cycles. - celCache: cel.NewCache(10, cel.Features{EnableConsumableCapacity: fts.EnableDRAConsumableCapacity}), + celCache: cel.NewCache(10, cel.Features{ + EnableConsumableCapacity: fts.EnableDRAConsumableCapacity, + EnableListTypeAttributes: fts.EnableDRAListTypeAttributes, + }), draManager: fh.SharedDRAManager(), } @@ -361,6 +395,29 @@ func (pl *DynamicResources) podResourceClaims(pod *v1.Pod) ([]*resourceapi.Resou return claims, nil } +// podResourceClaimBindings returns a list of references to objects to which +// each of the Pod's claims should be bound, as recorded in ResourceClaims' +// Status.ReservedFor. References are either to the Pod itself or its PodGroup. +func (pl *DynamicResources) podResourceClaimBindings(pod *v1.Pod) ([]resourceapi.ResourceClaimConsumerReference, error) { + if !pl.fts.EnableDRAWorkloadResourceClaims { + return nil, nil + } + + bindings := make([]resourceapi.ResourceClaimConsumerReference, 0, len(pod.Spec.ResourceClaims)) + podGroup, err := pl.getPodGroup(pod) + if err != nil { + return nil, err + } + for _, podClaim := range pod.Spec.ResourceClaims { + bindTo, err := resourceclaim.BindTo(pod, podGroup, &podClaim) + if err != nil { + return nil, err + } + bindings = append(bindings, bindTo) + } + return bindings, nil +} + // foreachPodResourceClaim checks that each ResourceClaim for the pod exists. // It calls an optional handler for those claims that it finds. func (pl *DynamicResources) foreachPodResourceClaim(pod *v1.Pod, cb func(podResourceName string, claim *resourceapi.ResourceClaim)) error { @@ -385,7 +442,7 @@ func (pl *DynamicResources) foreachPodResourceClaim(pod *v1.Pod, cb func(podReso } if mustCheckOwner { - if err := resourceclaim.IsForPod(pod, claim); err != nil { + if err := resourceclaim.IsForPod(pod, claim, pl.fts.EnableDRAWorkloadResourceClaims); err != nil { return err } } @@ -412,6 +469,11 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state fwk.CycleState, s := &stateData{} state.Write(stateKey, s) + podGroupState, err := getPodGroupStateData(state) + if err != nil { + return nil, statusError(logger, err) + } + userClaims, err := pl.podResourceClaims(pod) if err != nil { return nil, statusUnschedulable(logger, err.Error()) @@ -421,7 +483,11 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state fwk.CycleState, if status != nil { return nil, status } - claims := newClaimStore(userClaims, extendedResourceClaim) + claimBindings, err := pl.podResourceClaimBindings(pod) + if err != nil { + return nil, statusUnschedulable(logger, err.Error()) + } + claims := newClaimStore(userClaims, extendedResourceClaim, claimBindings) // This check covers user and extended ResourceClaim. if claims.empty() { @@ -434,7 +500,7 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state fwk.CycleState, for index, claim := range claims.all() { if claim.Status.Allocation != nil && !resourceclaim.CanBeReserved(claim) && - !resourceclaim.IsReservedForPod(pod, claim) { + !resourceclaim.IsReservedForPod(pod, claim, pl.fts.EnableDRAWorkloadResourceClaims) { // Resource is in use. The pod has to wait. return nil, statusUnschedulable(logger, "resourceclaim in use", "pod", klog.KObj(pod), "resourceclaim", klog.KObj(claim)) } @@ -450,10 +516,38 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state fwk.CycleState, } else { numClaimsToAllocate++ + // Gang scheduling works by waiting for all Pods in a gang to + // succeed through the Reserve phase, then binding all of the Pods. + // Since a claim's allocation is not recorded in [stateData.claims] + // until PreBind, we look for a pending allocation made in Reserve + // here to avoid blocking this Pod on waiting for the allocation to + // complete. + // + // Claims with pending allocations are considered "unallocated" in + // that the claim's Status.Allocation needs to be populated. Claims + // with pending allocations are not input to the allocator, though + // we numClaimsToAllocate increment to create an allocator anyway to + // signal that some claims need their status updated in PreBind. + // + // Currently, sharing is limited to a single PodGroup scheduling + // cycle. Ungrouped Pods and Pods across different PodGroup + // scheduling cycles cannot share claims with pending allocations + // for now to limit potential impact outside of the alpha + // GenericWorkload feature gate. Sharing claims this way more + // broadly may have benefits: + // https://github.com/kubernetes/kubernetes/issues/137932 + if podGroupState != nil && podGroupState.pendingAllocations.Has(claim.UID) { + if pendingAllocation := pl.draManager.ResourceClaims().GetPendingAllocation(claim.UID); pendingAllocation != nil { + s.informationsForClaim[index].allocation = pendingAllocation + logger.V(5).Info("reusing pending allocation", "pod", klog.KObj(pod), "resourceclaim", klog.KObj(claim), "uid", claim.UID, "allocation", klog.Format(pendingAllocation)) + continue + } + } + // Allocation in flight? Better wait for that // to finish, see inFlightAllocations // documentation for details. - if pl.draManager.ResourceClaims().ClaimHasPendingAllocation(claim.UID) { + if pl.draManager.ResourceClaims().GetPendingAllocation(claim.UID) != nil { return nil, statusUnschedulable(logger, fmt.Sprintf("resource claim %s is in the process of being allocated", klog.KObj(claim))) } @@ -619,6 +713,29 @@ func getStateData(cs fwk.CycleState) (*stateData, error) { return s, nil } +func getPodGroupStateData(cs fwk.CycleState) (*podGroupStateData, error) { + podGroupCycleState := cs.GetPodGroupSchedulingCycle() + if podGroupCycleState == nil { + return nil, nil + } + state, err := podGroupCycleState.Read(stateKey) + if errors.Is(err, fwk.ErrNotFound) { + podGroupState := &podGroupStateData{ + pendingAllocations: make(sets.Set[types.UID]), + } + podGroupCycleState.Write(stateKey, podGroupState) + return podGroupState, nil + } + if err != nil { + return nil, err + } + s, ok := state.(*podGroupStateData) + if !ok { + return nil, errors.New("state is not podGroupStateData") + } + return s, nil +} + // Filter invoked at the filter extension point. // It evaluates if a pod can fit due to the resources it requests, // for both allocated and unallocated claims. @@ -675,14 +792,26 @@ func (pl *DynamicResources) Filter(ctx context.Context, cs fwk.CycleState, pod * // If the claim is not ready yet (ready false, no error) and binding has timed out // or binding has failed (err non-nil), then the scheduler should consider deallocating this // claim in PostFilter to unblock trying other devices. - if err != nil || !ready && pl.isClaimTimeout(claim) { + if err != nil { + logger.V(5).Info("Claim failed binding conditions check", "pod", klog.KObj(pod), "node", klog.KObj(node), "resourceclaim", klog.KObj(claim), "err", err) + unavailableClaims = append(unavailableClaims, index) + } else if !ready && pl.isClaimTimeout(claim) { + logger.V(5).Info("Claim timed out waiting for binding conditions", "pod", klog.KObj(pod), "node", klog.KObj(node), "resourceclaim", klog.KObj(claim)) unavailableClaims = append(unavailableClaims, index) } } - } + // The claim is allocated, check if its a node-allocatable resource claim that is already allocated. + if pl.fts.EnableDRANodeAllocatableResources { + status := pl.validateNodeAllocatableDRAClaimSharing(pod, nodeInfo, claim) + if status != nil { + return status + } + } + } // Use allocator to check the node and cache the result in case that the node is picked. var allocations []resourceapi.AllocationResult + var nodeAllocatableClaimStatus []v1.NodeAllocatableResourceClaimStatus if state.allocator != nil { allocCtx := ctx if loggerV := logger.V(5); loggerV.Enabled() { @@ -701,17 +830,31 @@ func (pl *DynamicResources) Filter(ctx context.Context, cs fwk.CycleState, pod * // This replaces the special ResourceClaim for extended resources with one // matching the node. claimsToAllocate := make([]*resourceapi.ResourceClaim, 0, state.claims.len()) + // pendingResult holds pending allocations that were made for a previous + // Pod in Reserve, but before that allocation is recorded in the status + // by PreBind. + var pendingResult []resourceapi.AllocationResult extendedResourceClaim := state.claims.extendedResourceClaim() - for _, claim := range state.claims.toAllocate() { + for index, claim := range state.claims.toAllocate() { if claim == extendedResourceClaim && nodeExtendedResourceClaim != nil { claim = nodeExtendedResourceClaim } + if state.informationsForClaim[index].allocation != nil { + pendingResult = append(pendingResult, *state.informationsForClaim[index].allocation) + continue + } claimsToAllocate = append(claimsToAllocate, claim) } - a, err := state.allocator.Allocate(allocCtx, node, claimsToAllocate) + allocationResult, err := state.allocator.Allocate(allocCtx, node, claimsToAllocate) switch { case errors.Is(err, context.DeadlineExceeded): - return statusUnschedulable(logger, "timed out trying to allocate devices", "pod", klog.KObj(pod), "node", klog.KObj(node), "resourceclaims", klog.KObjSlice(claimsToAllocate)) + // Timeouts are potentially transient. Return Error + // so the pod retries via backoff instead of sitting in the + // unschedulable queue waiting for a cluster event. + // + // The timeout might be caused by ResourceSlices for the node, + // so including the node name may help with diagnosing the failure. + return statusError(logger, fmt.Errorf("node %s: timed out trying to allocate devices", node.Name), "pod", klog.KObj(pod), "node", klog.KObj(node), "resourceclaims", klog.KObjSlice(claimsToAllocate)) case errors.Is(err, structured.ErrFailedAllocationOnNode): // Not a fatal error, allocation on other nodes may proceed. // The error is only surfaced if allocation fails on all nodes. @@ -732,11 +875,27 @@ func (pl *DynamicResources) Filter(ctx context.Context, cs fwk.CycleState, pod * return statusError(logger, err, "pod", klog.KObj(pod), "node", klog.KObj(node), "resourceclaims", klog.KObjSlice(claimsToAllocate)) } // Check for exact length just to be sure. In practice this is all-or-nothing. - if len(a) != len(claimsToAllocate) { + if len(allocationResult) != len(claimsToAllocate) { return statusUnschedulable(logger, "cannot allocate all claims", "pod", klog.KObj(pod), "node", klog.KObj(node), "resourceclaims", klog.KObjSlice(claimsToAllocate)) } + + if pl.fts.EnableDRANodeAllocatableResources { + allocationsMap := make(map[types.UID]*resourceapi.AllocationResult) + for i, claim := range claimsToAllocate { + allocationsMap[claim.UID] = &allocationResult[i] + } + for i, claim := range state.claims.toAllocate() { + if state.informationsForClaim[i].allocation != nil { + allocationsMap[claim.UID] = state.informationsForClaim[i].allocation + } + } + nodeAllocatableClaimStatus, status = pl.calculateAndCheckNodeAllocatableResources(ctx, state, pod, nodeInfo, allocationsMap) + if status != nil { + return status + } + } // Reserve uses this information. - allocations = a + allocations = append(allocationResult, pendingResult...) } // Store information in state while holding the mutex. @@ -761,9 +920,10 @@ func (pl *DynamicResources) Filter(ctx context.Context, cs fwk.CycleState, pod * if state.allocator != nil { state.nodeAllocations[node.Name] = nodeAllocation{ - allocationResults: allocations, - extendedResourceClaim: nodeExtendedResourceClaim, - containerResourceRequestMappings: containerResourceRequestMappings, + allocationResults: allocations, + extendedResourceClaim: nodeExtendedResourceClaim, + containerResourceRequestMappings: containerResourceRequestMappings, + nodeAllocatableResourceClaimStatuses: nodeAllocatableClaimStatus, } } @@ -821,16 +981,97 @@ func (pl *DynamicResources) PostFilter(ctx context.Context, cs fwk.CycleState, p // If the special resource claim for extended resource backed by DRA // is reserved or allocated at prior scheduling cycle, then it should be deleted. extendedResourceClaim := extendedResourceClaim.DeepCopy() - if err := pl.deleteClaim(ctx, extendedResourceClaim, logger); err != nil { + if err := pl.deleteClaim(ctx, extendedResourceClaim); err != nil { return nil, statusError(logger, err) } return nil, fwk.NewStatus(fwk.Unschedulable, "deletion of ResourceClaim completed") } + + // If we make it here we know that not all pods of the PodGroup could be scheduled. + // To let the scheduler try to find new devices in case binding the current allocation + // failed in some previous cycle, remove the PodGroup from its claims' status.reservedFor. + // Note that more Pods may exist than a PodGroup's minCount, but performing + // a cleanup after a previous cycle is safe as long as there are no assumed + // nor assigned pods yet. + if pl.fts.EnableDRAWorkloadResourceClaims { + status := pl.unreservePodGroupClaims(ctx, pod) + if status != nil { + return nil, status + } + } + return nil, fwk.NewStatus(fwk.Unschedulable, "still not schedulable") } +func (pl *DynamicResources) unreservePodGroupClaims(ctx context.Context, pod *v1.Pod) *fwk.Status { + if pod.Spec.SchedulingGroup == nil || pod.Spec.SchedulingGroup.PodGroupName == nil { + return nil + } + + logger := klog.FromContext(ctx) + + // The podGroupState lister is based on the live cache and does not consider + // pods assumed within the PodGroup scheduling cycle, but the ones that + // happened before or outside the scheduling cycle. We use it to check + // whether there were no assumed or assigned pods that would use the + // ResourceClaim. + podGroupState, err := pl.fh.PodGroupManager().PodGroupStates().Get(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) + if err != nil { + return statusError(logger, err) + } + if podGroupState.ScheduledPodsCount() > 0 { + return nil + } + podGroup, err := pl.getPodGroup(pod) + if err != nil { + return statusError(logger, err) + } + + for _, podGroupClaim := range podGroup.Spec.ResourceClaims { + claimName, mustCheckOwner, err := resourceclaim.NameFromPodGroup(podGroup, &podGroupClaim) + if err != nil { + return statusError(logger, err) + } + if claimName == nil { + continue + } + claim, err := pl.draManager.ResourceClaims().Get(podGroup.Namespace, *claimName) + if err != nil { + return statusError(logger, err) + } + if mustCheckOwner { + if err := resourceclaim.IsForPodGroup(podGroup, claim); err != nil { + continue + } + } + + if claim.Status.Allocation != nil { + if resourceclaim.IsReservedForPod(pod, claim, pl.fts.EnableDRAWorkloadResourceClaims) { + // Remove PodGroup from ReservedFor. A strategic-merge-patch is used + // because that allows removing an individual entry without having + // the latest ResourceClaim. + patch := fmt.Sprintf(`{"metadata": {"uid": %q}, "status": { "reservedFor": [ {"$patch": "delete", "uid": %q} ] }}`, + claim.UID, + podGroup.UID, + ) + logger.V(5).Info("postfilter unreserved claim", "resourceclaim", klog.KObj(claim), "pod", klog.KObj(pod), "podgroup", klog.KObj(podGroup)) + claim, err := pl.clientset.ResourceV1().ResourceClaims(claim.Namespace).Patch(ctx, claim.Name, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{}, "status") + if err != nil { + // We will get here again when pod scheduling is retried. + logger.Error(err, "postfilter unreserving claim", "resourceclaim", klog.KObj(claim)) + continue + } else { + return statusUnschedulable(logger, "ResourceClaim unreserved for PodGroup") + } + } + } + } + + return nil +} + func (pl *DynamicResources) Score(ctx context.Context, cs fwk.CycleState, pod *v1.Pod, nodeInfo fwk.NodeInfo) (int64, *fwk.Status) { - if !pl.enabled { + if !pl.enabled || !pl.fts.EnableDRAPrioritizedList { return 0, nil } logger := klog.FromContext(ctx) @@ -859,13 +1100,29 @@ func (pl *DynamicResources) Score(ctx context.Context, cs fwk.CycleState, pod *v func computeScore(iterator iter.Seq2[int, *resourceapi.ResourceClaim], allocations nodeAllocation) (int64, error) { var score int64 - for i, claim := range iterator { + unallocatedIndex := 0 + for _, claim := range iterator { // Collect the names for all allocated subrequests. allocatedSubRequests := sets.New[string]() - if i >= len(allocations.allocationResults) { - return 0, fmt.Errorf("number of allocations %d is smaller than number of claims", len(allocations.allocationResults)) + + var allocation *resourceapi.AllocationResult + // The allocation for a claim can be in two places: + // 1. For claims allocated in a previous cycle (e.g. PodGroup claims), the allocation + // is already in claim.Status.Allocation. + // 2. For claims allocated in this cycle (in Filter), the allocation is in + // allocations.allocationResults. + // Since we iterate over all claims, we must check both and maintain a separate index + // for claims that needed allocation in this cycle. + if claim.Status.Allocation != nil { + allocation = claim.Status.Allocation + } else { + if unallocatedIndex >= len(allocations.allocationResults) { + return 0, fmt.Errorf("number of allocations %d is smaller than number of claims needing allocation", len(allocations.allocationResults)) + } + allocation = &allocations.allocationResults[unallocatedIndex] + unallocatedIndex++ } - allocation := allocations.allocationResults[i] + for _, res := range allocation.Devices.Results { request := res.Request if resourceclaim.IsSubRequestRef(request) { @@ -914,6 +1171,11 @@ func (pl *DynamicResources) Reserve(ctx context.Context, cs fwk.CycleState, pod logger := klog.FromContext(ctx) + podGroupState, err := getPodGroupStateData(cs) + if err != nil { + return statusError(logger, err) + } + numClaimsWithAllocator := 0 for _, claim := range state.claims.all() { if claim.Status.Allocation != nil { @@ -922,6 +1184,7 @@ func (pl *DynamicResources) Reserve(ctx context.Context, cs fwk.CycleState, pod // updating the ResourceClaim status, we assume that reserving // will work and only do it for real during binding. If it fails at // that time, some other pod was faster and we have to try again. + continue } @@ -989,8 +1252,12 @@ func (pl *DynamicResources) Reserve(ctx context.Context, cs fwk.CycleState, pod if err != nil { return statusError(logger, fmt.Errorf("internal error, couldn't signal allocation for claim %s", claim.Name)) } - logger.V(5).Info("Reserved resource in allocation result", "claim", klog.KObj(claim), "allocation", klog.Format(allocation)) + logger.V(5).Info("Reserved resource in allocation result", "claim", klog.KObj(claim), "uid", claim.UID, "resourceVersion", claim.ResourceVersion, "allocation", klog.Format(allocation)) allocIndex++ + + if podGroupState != nil { + podGroupState.pendingAllocations.Insert(claim.UID) + } } } @@ -1010,19 +1277,41 @@ func (pl *DynamicResources) Unreserve(ctx context.Context, cs fwk.CycleState, po if state.claims.empty() { return } + podGroupState, err := getPodGroupStateData(cs) + if err != nil { + return + } logger := klog.FromContext(ctx) // we process user claims here first, extendedResourceClaim if any is handled below. for _, claim := range state.claims.allUserClaims() { - // If allocation was in-flight, then it's not anymore and we need to revert the - // claim object in the assume cache to what it was before. - if deleted := pl.draManager.ResourceClaims().RemoveClaimPendingAllocation(claim.UID); deleted { + // If allocation was in-flight, then it might not be anymore if no pods + // still need the pending allocation. If the allocation was removed from + // in-flight, we need to revert the claim object in the assume cache to + // what it was before. The allocation is not removed until the last Pod + // sharing it is Bound or Unreserved. + if deleted := pl.draManager.ResourceClaims().MaybeRemoveClaimPendingAllocation(claim.UID, false); deleted { + logger.V(5).Info("Released resource in allocation result", "claim", klog.KObj(claim), "uid", claim.UID, "resourceVersion", claim.ResourceVersion, "allocation", klog.Format(claim.Status.Allocation)) pl.draManager.ResourceClaims().AssumedClaimRestore(claim.Namespace, claim.Name) + + // If we are currently asynchronously Binding Pods in a PodGroup, + // then the pendingAllocations set does not need to be updated. New + // PodGroup scheduling cycles will start with an empty set and not + // share pending allocations started in *this* cycle until Unreserve + // completes for all the Pods sharing that pending allocation and + // they can be Reserved again in another cycle. + if podGroupState != nil { + podGroupState.pendingAllocations.Delete(claim.UID) + } } + // Ignore claims reserved for the PodGroup because other Pods in the + // group may still need this claim. The PodGroup may be unreserved and + // dellocated (in api-server) in the next scheduling cycle if the + // PodGroup is not schedulable (in PostFilter) and not used yet. if claim.Status.Allocation != nil && - resourceclaim.IsReservedForPod(pod, claim) { + resourceclaim.IsReservedForPod(pod, claim, false /* acceptPodGroupReservation */) { // Remove pod from ReservedFor. A strategic-merge-patch is used // because that allows removing an individual entry without having // the latest ResourceClaim. @@ -1038,7 +1327,14 @@ func (pl *DynamicResources) Unreserve(ctx context.Context, cs fwk.CycleState, po } } } - pl.unreserveExtendedResourceClaim(ctx, logger, pod, state) + pl.unreserveExtendedResourceClaim(ctx, pod, state) + + if pl.fts.EnableDRANodeAllocatableResources { + nodeAllocations, ok := state.nodeAllocations[nodeName] + if ok && len(nodeAllocations.nodeAllocatableResourceClaimStatuses) > 0 { + pl.clearNodeAllocatableResourceClaimStatus(ctx, pod) + } + } } // PreBind gets called in a separate goroutine after it has been determined @@ -1063,9 +1359,14 @@ func (pl *DynamicResources) PreBind(ctx context.Context, cs fwk.CycleState, pod logger := klog.FromContext(ctx) + podGroupState, err := getPodGroupStateData(cs) + if err != nil { + return statusError(logger, err) + } + for index, claim := range state.claims.all() { - if !resourceclaim.IsReservedForPod(pod, claim) { - claim, err := pl.bindClaim(ctx, state, index, pod, nodeName) + if !resourceclaim.IsReservedForPod(pod, claim, pl.fts.EnableDRAWorkloadResourceClaims) { + claim, err := pl.bindClaim(ctx, state, podGroupState, index, pod, nodeName) if err != nil { return statusError(logger, err) } @@ -1074,6 +1375,15 @@ func (pl *DynamicResources) PreBind(ctx context.Context, cs fwk.CycleState, pod } } + if pl.fts.EnableDRANodeAllocatableResources { + nodeAllocations, ok := state.nodeAllocations[nodeName] + if ok && len(nodeAllocations.nodeAllocatableResourceClaimStatuses) > 0 { + if status := pl.patchNodeAllocatableResourceClaimStatus(ctx, pod, nodeAllocations.nodeAllocatableResourceClaimStatuses); status != nil { + return status + } + } + } + if !pl.fts.EnableDRADeviceBindingConditions || !pl.fts.EnableDRAResourceClaimDeviceStatus { // If we don't have binding conditions, we can return early. // The claim is now reserved for the pod and the scheduler can proceed with binding. @@ -1089,19 +1399,62 @@ func (pl *DynamicResources) PreBind(ctx context.Context, cs fwk.CycleState, pod } // We need to wait for the device to be attached to the node. - pl.fh.EventRecorder().Eventf(pod, nil, v1.EventTypeNormal, "BindingConditionsPending", "Scheduling", "waiting for binding conditions for device on node %s", nodeName) + pl.fh.EventRecorder().WithLogger(logger).Eventf(pod, nil, v1.EventTypeNormal, "BindingConditionsPending", "Scheduling", "waiting for binding conditions for device on node %s", nodeName) + // START: Record start time for metrics duration calculation + start := time.Now() err = wait.PollUntilContextTimeout(ctx, 5*time.Second, pl.bindingTimeout, true, func(ctx context.Context) (bool, error) { return pl.isPodReadyForBinding(state) }) - if err != nil { - if errors.Is(err, context.DeadlineExceeded) { - err = errors.New("device binding timeout") + // GOAL: Calculate duration since start for metrics recording + duration := time.Since(start) + if errors.Is(err, context.DeadlineExceeded) { + err = fmt.Errorf("%w: %w", ErrDeviceBindingTimeout, err) + } + + // Determine status label based on error + statusLabel := schedmetrics.BindingConditionsStatusSuccess + switch { + case err == nil: + logger.V(5).Info("BindingConditions is met", + "pod", klog.KObj(pod), + "node", nodeName, + "devices", bindingConditionsStatusFormatter{state, statusLabel}, + ) + // keep success + case errors.Is(err, ErrDeviceBindingTimeout): + statusLabel = schedmetrics.BindingConditionsStatusTimeout + case errors.Is(err, ErrDeviceBindingFailed): + statusLabel = schedmetrics.BindingConditionsStatusFailed + default: + statusLabel = schedmetrics.BindingConditionsStatusError + } + + // Record metrics for binding conditions outcome (success/failure/timeout/error) + profileLabel := pl.fh.ProfileName() + for _, claim := range state.claims.all() { + if claim.Status.Allocation == nil { + continue + } + for _, res := range claim.Status.Allocation.Devices.Results { + schedmetrics.DRABindingConditionsPreBindDuration. + WithLabelValues(profileLabel, res.Driver, statusLabel). + Observe(duration.Seconds()) + } + + // Count scheduling attempts that used devices with BindingConditions. + for _, bcStatus := range listBindingConditionsStatus(claim) { + schedmetrics.DRABindingConditionsAllocationsTotal. + WithLabelValues(profileLabel, bcStatus.Driver, statusLabel). + Inc() } + } + + if err != nil { // Returning an error here causes another scheduling attempt. // In that next attempt, PreFilter will detect the timeout or // error and try to recover. - return statusError(logger, err) + return statusError(logger, err, "devices", formatBCStatusOneLine(getBindingConditionsStatusForStatus(state, statusLabel))) } // If we get here, we know that reserving the claim for @@ -1111,26 +1464,28 @@ func (pl *DynamicResources) PreBind(ctx context.Context, cs fwk.CycleState, pod // PreBindPreFlight is called before PreBind, and determines whether PreBind is going to do something for this pod, or not. // It just checks state.claims to determine whether there are any claims and hence the plugin has to handle them at PreBind. -func (pl *DynamicResources) PreBindPreFlight(ctx context.Context, cs fwk.CycleState, p *v1.Pod, nodeName string) *fwk.Status { +func (pl *DynamicResources) PreBindPreFlight(ctx context.Context, cs fwk.CycleState, p *v1.Pod, nodeName string) (*fwk.PreBindPreFlightResult, *fwk.Status) { + result := &fwk.PreBindPreFlightResult{AllowParallel: true} if !pl.enabled { - return fwk.NewStatus(fwk.Skip) + return result, fwk.NewStatus(fwk.Skip) } state, err := getStateData(cs) if err != nil { - return statusError(klog.FromContext(ctx), err) + return result, statusError(klog.FromContext(ctx), err) } if state.claims.empty() { - return fwk.NewStatus(fwk.Skip) + return result, fwk.NewStatus(fwk.Skip) } - return nil + return result, nil } // bindClaim gets called by PreBind for claim which is not reserved for the pod yet. // It might not even be allocated. bindClaim then ensures that the allocation // and reservation are recorded. This finishes the work started in Reserve. -func (pl *DynamicResources) bindClaim(ctx context.Context, state *stateData, index int, pod *v1.Pod, nodeName string) (*resourceapi.ResourceClaim, error) { +func (pl *DynamicResources) bindClaim(ctx context.Context, state *stateData, podGroupState *podGroupStateData, index int, pod *v1.Pod, nodeName string) (*resourceapi.ResourceClaim, error) { logger := klog.FromContext(ctx) claim := state.claims.get(index) + binding := state.claims.getBinding(index, pod) allocation := state.informationsForClaim[index].allocation isExtendedResourceClaim := false if claim == state.claims.extendedResourceClaim() { @@ -1143,22 +1498,57 @@ func (pl *DynamicResources) bindClaim(ctx context.Context, state *stateData, ind claimUIDs := []types.UID{claim.UID} resourceClaimModified := false defer func() { + // Creating the claim may have failed. + resourceVersion := "" + claimUID := types.UID("") + if claim != nil { + resourceVersion = claim.ResourceVersion + claimUID = claim.UID + } + // The scheduler was handling allocation. Now that has // completed, either successfully or with a failure. + + // The claim should remain in-flight in case any more Pods in the + // PodGroup are still using the pending allocation. If binding failed + // (e.g. due to a conflict in writing the allocation), then we signal + // that this Pod no longer needs the pending allocation without removing it. + forceRemovePendingAllocation := false + if resourceClaimModified { if isExtendedResourceClaim { - pl.waitForExtendedClaimInAssumeCache(ctx, logger, claim) + pl.waitForExtendedClaimInAssumeCache(ctx, claim) } else { // This can fail, but only for reasons that are okay (concurrent delete or update). // Shouldn't happen in this case. if err := pl.draManager.ResourceClaims().AssumeClaimAfterAPICall(claim); err != nil { - logger.V(5).Info("Claim not stored in assume cache", "err", err) + logger.V(5).Info("Claim not stored in assume cache", "err", err, "claim", klog.KObj(claim), "uid", claimUID, "resourceVersion", resourceVersion) + } else { + logger.V(5).Info("Claim stored in assume cache", "claim", klog.KObj(claim), "uid", claimUID, "resourceVersion", resourceVersion) + + // Once the pending allocation is recorded in the + // AssumeCache, we no longer need to keep it in-flight. + // Even if it happens to still be shared, the allocation in + // the AssumeCache is authoritative. + forceRemovePendingAllocation = true } } } if allocation != nil { for _, claimUID := range claimUIDs { - pl.draManager.ResourceClaims().RemoveClaimPendingAllocation(claimUID) + if deleted := pl.draManager.ResourceClaims().MaybeRemoveClaimPendingAllocation(claimUID, forceRemovePendingAllocation); deleted { + // If we are currently asynchronously Binding Pods in a + // PodGroup, then the pendingAllocations set does not need + // to be updated. New PodGroup scheduling cycles will start + // with an empty set and not share pending allocations + // started in *this* cycle until Unreserve completes for all + // the Pods sharing that pending allocation and they can be + // Reserved again in another cycle. + if podGroupState != nil { + podGroupState.pendingAllocations.Delete(claim.UID) + } + logger.V(5).Info("Removed claim from in-flight claims", "claim", klog.KObj(claim), "uid", claimUID, "resourceVersion", resourceVersion, "allocation", klog.Format(allocation)) + } } } }() @@ -1166,7 +1556,7 @@ func (pl *DynamicResources) bindClaim(ctx context.Context, state *stateData, ind // Create the special claim for extended resource backed by DRA if isExtendedResourceClaim && isSpecialClaimName(claim.Name) { var err error - claim, err = pl.createExtendedResourceClaimInAPI(ctx, logger, pod, nodeName, state) + claim, err = pl.createExtendedResourceClaimInAPI(ctx, pod, nodeName, state) if err != nil { return nil, err } @@ -1225,9 +1615,18 @@ func (pl *DynamicResources) bindClaim(ctx context.Context, state *stateData, ind // We can simply try to add the pod here without checking // preconditions. The apiserver will tell us with a // non-conflict error if this isn't possible. - claim.Status.ReservedFor = append(claim.Status.ReservedFor, resourceapi.ResourceClaimConsumerReference{Resource: "pods", Name: pod.Name, UID: pod.UID}) - if pl.fts.EnableDRADeviceBindingConditions && pl.fts.EnableDRAResourceClaimDeviceStatus && claim.Status.Allocation.AllocationTimestamp == nil { - claim.Status.Allocation.AllocationTimestamp = &metav1.Time{Time: time.Now()} + claim.Status.ReservedFor = append(claim.Status.ReservedFor, binding) + if pl.fts.EnableDRADeviceBindingConditions && + pl.fts.EnableDRAResourceClaimDeviceStatus { + // Once the feature gate checks get removed, this code can be moved into an else branch + // for `if allocation != nil` above. For now it is kept here to avoid changing normal + // behavior. + if claim.Status.Allocation == nil { + return fmt.Errorf("claim %s got deallocated elsewhere in the meantime", klog.KObj(claim)) + } + if claim.Status.Allocation.AllocationTimestamp == nil { + claim.Status.Allocation.AllocationTimestamp = &metav1.Time{Time: time.Now()} + } } updatedClaim, err := pl.clientset.ResourceV1().ResourceClaims(claim.Namespace).UpdateStatus(ctx, claim, metav1.UpdateOptions{}) if err != nil { @@ -1280,7 +1679,8 @@ func (pl *DynamicResources) isClaimReadyForBinding(claim *resourceapi.ResourceCl for _, cond := range deviceRequest.BindingFailureConditions { failedCond := apimeta.FindStatusCondition(deviceStatus.Conditions, cond) if failedCond != nil && failedCond.Status == metav1.ConditionTrue { - return false, fmt.Errorf("claim %s binding failed: reason=%s, message=%q", + return false, fmt.Errorf("%w: claim=%s, reason=%s, message=%q", + ErrDeviceBindingFailed, claim.Name, failedCond.Reason, failedCond.Message) @@ -1336,7 +1736,7 @@ func (pl *DynamicResources) isPodReadyForBinding(state *stateData) (bool, error) } if !ready { if pl.isClaimTimeout(claim) { - return false, fmt.Errorf("claim %s binding timeout", claim.Name) + return false, fmt.Errorf("%w: claim=%s", ErrDeviceBindingTimeout, claim.Name) } return false, nil } @@ -1344,6 +1744,14 @@ func (pl *DynamicResources) isPodReadyForBinding(state *stateData) (bool, error) return true, nil } +func (pl *DynamicResources) getPodGroup(pod *v1.Pod) (*schedulingapi.PodGroup, error) { + if !pl.fts.EnableDRAWorkloadResourceClaims || + pod.Spec.SchedulingGroup == nil || pod.Spec.SchedulingGroup.PodGroupName == nil { + return nil, nil + } + return pl.draManager.PodGroups().Get(pod.Namespace, *pod.Spec.SchedulingGroup.PodGroupName) +} + // hasBindingConditions checks whether any of the claims in the state // has binding conditions. // It returns true if at least one claim has binding conditions. @@ -1395,3 +1803,130 @@ func getAllocatedDeviceStatus(claim *resourceapi.ResourceClaim, deviceRequest *r } return nil } + +// bindingConditionsStatusFormatter implements fmt.Stringer for lazy evaluation of device status formatting. +// This avoids expensive processing when logging is disabled. +type bindingConditionsStatusFormatter struct { + state *stateData + statusLabel string +} + +func (d bindingConditionsStatusFormatter) String() string { + return formatBCStatusOneLine(getBindingConditionsStatusForStatus(d.state, d.statusLabel)) +} + +// BindingConditionsStatus is a compact per-device summary for BindingConditions logging. +type BindingConditionsStatus struct { + Driver string `json:"driver"` + Pool string `json:"pool"` + Device string `json:"device"` + Pending []string `json:"pending,omitempty"` + Failed []string `json:"failed,omitempty"` // only failure conditions that are true +} + +// listBindingConditionsStatus inspects ONE claim and returns a summary per allocated device +// that has BindingConditions. +// - Pending: BindingConditions that are not yet true (or all conditions if device status is missing) +// - Failed: BindingFailureConditions that are true +func listBindingConditionsStatus(claim *resourceapi.ResourceClaim) []BindingConditionsStatus { + if claim == nil || claim.Status.Allocation == nil { + return nil + } + + var out []BindingConditionsStatus + for _, res := range claim.Status.Allocation.Devices.Results { + if len(res.BindingConditions) == 0 { + continue + } + + sum := BindingConditionsStatus{ + Driver: res.Driver, + Pool: res.Pool, + Device: res.Device, + } + + ds := getAllocatedDeviceStatus(claim, &res) + if ds == nil { + // No status yet => all BindingConditions are pending. + sum.Pending = append(sum.Pending, res.BindingConditions...) + out = append(out, sum) + continue + } + + // Failed conditions that are true. + for _, c := range res.BindingFailureConditions { + if apimeta.IsStatusConditionTrue(ds.Conditions, c) { + sum.Failed = append(sum.Failed, c) + } + } + + // Pending conditions: those not yet true. + for _, c := range res.BindingConditions { + if !apimeta.IsStatusConditionTrue(ds.Conditions, c) { + sum.Pending = append(sum.Pending, c) + } + } + + out = append(out, sum) + } + + return out +} + +// getBindingConditionsStatusForStatus selects which devices to log across ALL claims in the state. +func getBindingConditionsStatusForStatus(state *stateData, statusLabel string) []BindingConditionsStatus { + var out []BindingConditionsStatus + for _, claim := range state.claims.all() { + for _, s := range listBindingConditionsStatus(claim) { + switch statusLabel { + case schedmetrics.BindingConditionsStatusSuccess: + // success: show all BC devices + if len(s.Failed) == 0 && len(s.Pending) == 0 { + out = append(out, s) + } + case schedmetrics.BindingConditionsStatusFailed: + // failure: only devices that have at least one failure condition true + if len(s.Failed) > 0 { + out = append(out, BindingConditionsStatus{ + Driver: s.Driver, + Pool: s.Pool, + Device: s.Device, + Failed: append([]string(nil), s.Failed...), + }) + } + + case schedmetrics.BindingConditionsStatusTimeout: + // timeout/error: only devices still pending, and not failed + if len(s.Pending) > 0 && len(s.Failed) == 0 { + out = append(out, s) + } + + default: + // Treat unknown like timeout/error + if len(s.Pending) > 0 && len(s.Failed) == 0 { + out = append(out, s) + } + } + } + } + return out +} + +// formatBCStatusOneLine formats the selected devices into a single log-friendly line. +func formatBCStatusOneLine(devs []BindingConditionsStatus) string { + if len(devs) == 0 { + return "" + } + parts := make([]string, 0, len(devs)) + for _, d := range devs { + base := fmt.Sprintf(" %s", d.Driver, d.Pool, d.Device) + if len(d.Failed) > 0 { + base += fmt.Sprintf(" failed=[%s]", strings.Join(d.Failed, ",")) + } + if len(d.Pending) > 0 { + base += fmt.Sprintf(" pending=[%s]", strings.Join(d.Pending, ",")) + } + parts = append(parts, base) + } + return strings.Join(parts, "; ") +} diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go index fc089251eab1c..0b33065a38bc8 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go @@ -36,10 +36,14 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" + apiresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apiruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" @@ -53,11 +57,14 @@ import ( "k8s.io/dynamic-resource-allocation/deviceclass/extendedresourcecache" resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker" "k8s.io/dynamic-resource-allocation/structured" + "k8s.io/dynamic-resource-allocation/structured/schedulerapi" + "k8s.io/klog/v2" kubeschedulerconfigv1 "k8s.io/kube-scheduler/config/v1" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/apis/config" configv1 "k8s.io/kubernetes/pkg/scheduler/apis/config/v1" + internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" "k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" "k8s.io/kubernetes/pkg/scheduler/framework/runtime" @@ -73,25 +80,32 @@ func init() { } var ( - podKind = v1.SchemeGroupVersion.WithKind("Pod") - - nodeName = "worker" - node2Name = "worker-2" - node3Name = "worker-3" - driver = "some-driver" - driver2 = "some-driver-2" - podName = "my-pod" - podUID = "1234" - resourceName = "my-resource" - resourceName2 = resourceName + "-2" - claimName = podName + "-" + resourceName - claimName2 = podName + "-" + resourceName2 - className = "my-resource-class" - namespace = "default" - attrName = resourceapi.QualifiedName("healthy") // device attribute only available on non-default node - extendedResourceName = "example.com/gpu" - extendedResourceName2 = "example.com/gpu2" - implicitExtendedResourceName = "deviceclass.resource.kubernetes.io/my-resource-class" + podKind = v1.SchemeGroupVersion.WithKind("Pod") + podGroupKind = schedulingapi.SchemeGroupVersion.WithKind("PodGroup") + + nodeName = "worker" + node2Name = "worker-2" + node3Name = "worker-3" + driver = "some-driver" + driver2 = "some-driver-2" + sharedDeviceName = "shared-instance" + podName = "my-pod" + podUID = "1234" + podGroupName = "my-podgroup" + podGroupUID = "5678" + resourceName = "my-resource" + resourceName2 = resourceName + "-2" + capacityName = resourceapi.QualifiedName("my-cap") + claimName = podName + "-" + resourceName + claimName2 = podName + "-" + resourceName2 + className = "my-resource-class" + namespace = "default" + attrName = resourceapi.QualifiedName("healthy") // device attribute only available on non-default node + extendedResourceName = "example.com/gpu" + extendedResourceName2 = "example.com/gpu2" + implicitExtendedResourceName = "deviceclass.resource.kubernetes.io/my-resource-class" + nodeAllocatableResourceDriver = "node-allocatable-resource-driver" + nodeAllocatableResourceCapacityName = resourceapi.QualifiedName("example.com/cpus") deviceClass = &resourceapi.DeviceClass{ ObjectMeta: metav1.ObjectMeta{ @@ -171,11 +185,74 @@ var ( v1.ResourceName(extendedResourceName): "2", }). Obj() + podWithClaimReferenceInContainer = func() *v1.Pod { + pod := podWithClaimName.DeepCopy() + container := v1.Container{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Claims: []v1.ResourceClaim{ + { + Name: resourceName, + }, + }, + }, + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + return pod + }() + groupedPodWithClaimName = st.MakePod().Name(podName).Namespace(namespace). + UID(podUID). + PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &claimName}). + PodGroupName(podGroupName). + Obj() + groupedPodWithTwoClaimNames = st.MakePod().Name(podName).Namespace(namespace). + UID(podUID). + PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &claimName}). + PodResourceClaims(v1.PodResourceClaim{Name: resourceName2, ResourceClaimName: &claimName2}). + PodGroupName(podGroupName). + Obj() + groupedPodWithClaimTemplateInStatus = func() *v1.Pod { + pod := podWithClaimTemplateInStatus.DeepCopy() + pod.Spec.SchedulingGroup = &v1.PodSchedulingGroup{ + PodGroupName: new(podGroupName), + } + return pod + }() + podGroupReservation = resourceapi.ResourceClaimConsumerReference{ + APIGroup: schedulingapi.GroupName, + Resource: "podgroups", + Name: podGroupName, + UID: types.UID(podGroupUID), + } + + podGroupWithClaimName = st.MakePodGroup().Name(podGroupName).Namespace(namespace). + UID(types.UID(podGroupUID)). + ResourceClaims(schedulingapi.PodGroupResourceClaim{Name: resourceName, ResourceClaimName: &claimName}). + Obj() + podGroupWithClaimTemplate = st.MakePodGroup().Name(podGroupName).Namespace(namespace). + UID(types.UID(podGroupUID)). + ResourceClaims(schedulingapi.PodGroupResourceClaim{Name: resourceName, ResourceClaimTemplateName: &claimName}). + Obj() // Node with "instance-1" device and no device attributes. - workerNode = &st.MakeNode().Name(nodeName).Label("kubernetes.io/hostname", nodeName).Node - workerNodeSlice = st.MakeResourceSlice(nodeName, driver).Device("instance-1").Obj() - largeWorkerNodeSlice = st.MakeResourceSlice(nodeName, driver).Device("instance-1").Device("instance-2").Device("instance-3").Device("instance-4").Obj() + workerNode = &st.MakeNode().Name(nodeName).Label("kubernetes.io/hostname", nodeName).Node + workerNodeSlice = st.MakeResourceSlice(nodeName, driver).Device("instance-1").Obj() + largeWorkerNodeSlice = st.MakeResourceSlice(nodeName, driver).Device("instance-1").Device("instance-2").Device("instance-3").Device("instance-4").Obj() + workerNodeWithCapacity = &st.MakeNode().Name(nodeName).Label("kubernetes.io/hostname", nodeName).Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "10", v1.ResourceMemory: "10Gi"}).Node + workerNodeSliceWithNodeAllocatableResource = func() *resourceapi.ResourceSlice { + nativeSlice := st.MakeResourceSlice(nodeName, nodeAllocatableResourceDriver).Device("numa-0-cpus", + map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{ + nodeAllocatableResourceCapacityName: {Value: apiresource.MustParse("10")}, + }, + ).Obj() + nativeSlice.Spec.Devices[0].AllowMultipleAllocations = ptr.To(true) + nativeSlice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: &nodeAllocatableResourceCapacityName, + }, + } + return nativeSlice + } // Node with same device, but now with a "healthy" boolean attribute. workerNode2 = &st.MakeNode().Name(node2Name).Label("kubernetes.io/hostname", node2Name).Node @@ -248,6 +325,22 @@ var ( st.SubRequest("subreq-3", className, 2), st.SubRequest("subreq-4", className, 1), ).Obj() + nodeAllocatableClaimWithCapacity = func() *resourceapi.ResourceClaim { + claim := st.MakeResourceClaim(). + Name(claimName). + Namespace(namespace). + Request(className). + Obj() + claim.Spec.Devices.Requests[0].Exactly.Capacity = &resourceapi.CapacityRequirements{ + Requests: map[resourceapi.QualifiedName]apiresource.Quantity{ + nodeAllocatableResourceCapacityName: apiresource.MustParse("1"), + }, + } + return claim + }() + pendingNodeAllocatableClaimWithCapacity = st.FromResourceClaim(nodeAllocatableClaimWithCapacity). + OwnerReference(podName, podUID, podKind). + Obj() pendingClaim = st.FromResourceClaim(claim). OwnerReference(podName, podUID, podKind). @@ -255,6 +348,10 @@ var ( pendingClaim2 = st.FromResourceClaim(claim2). OwnerReference(podName, podUID, podKind). Obj() + pendingPodGroupClaim = st.FromResourceClaim(claim). + OwnerReference(podGroupName, podGroupUID, podGroupKind). + Obj() + pendingClaimWithPrioritizedList = st.FromResourceClaim(claimWithPrioritzedList). OwnerReference(podName, podUID, podKind). Obj() @@ -280,6 +377,54 @@ var ( return st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj() }(), } + allocationResultWithSharedDevice = &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{{ + Driver: driver, + Pool: nodeName, + Device: sharedDeviceName, + Request: "req-1", + ShareID: ptr.To(types.UID("share-123")), // Shared device allocation + }}, + }, + NodeSelector: func() *v1.NodeSelector { + return st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj() + }(), + } + allocationResultWithConsumedCapacity = &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{{ + Driver: driver, + Pool: nodeName, + Device: sharedDeviceName, + Request: "req-1", + ShareID: ptr.To(types.UID("share-123")), // Shared device allocation + ConsumedCapacity: map[resourceapi.QualifiedName]apiresource.Quantity{ + capacityName: apiresource.MustParse("1"), + }, + }}, + }, + NodeSelector: func() *v1.NodeSelector { + return st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj() + }(), + } + allocationResultWithConsumedCapacity2 = &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{{ + Driver: driver, + Pool: nodeName, + Device: sharedDeviceName, + Request: "req-1", + ShareID: ptr.To(types.UID("share-456")), // Shared device allocation + ConsumedCapacity: map[resourceapi.QualifiedName]apiresource.Quantity{ + capacityName: apiresource.MustParse("1"), + }, + }}, + }, + NodeSelector: func() *v1.NodeSelector { + return st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj() + }(), + } allocationResult2 = &resourceapi.AllocationResult{ Devices: resourceapi.DeviceAllocationResult{ Results: []resourceapi.DeviceRequestAllocationResult{{ @@ -477,6 +622,10 @@ var ( Allocation(allocationResult). ReservedForPod(podName, types.UID(podUID)). Obj() + inUseClaimByPodGroup = st.FromResourceClaim(pendingPodGroupClaim). + Allocation(allocationResult). + ReservedForPodGroup(podGroupName, types.UID(podGroupUID)). + Obj() inUseClaimWithPrioritizedList = st.FromResourceClaim(pendingClaimWithPrioritizedList). Allocation(allocationResultWithPrioritizedList). ReservedForPod(podName, types.UID(podUID)). @@ -499,6 +648,9 @@ var ( allocatedClaim2 = st.FromResourceClaim(pendingClaim2). Allocation(allocationResult2). Obj() + allocatedPodGroupClaim = st.FromResourceClaim(pendingPodGroupClaim). + Allocation(allocationResult). + Obj() allocatedClaimWithPrioritizedList = st.FromResourceClaim(pendingClaimWithPrioritizedList). Allocation(allocationResultWithPrioritizedList). Obj() @@ -514,9 +666,22 @@ var ( allocatedClaimWithWrongTopology = st.FromResourceClaim(allocatedClaim). Allocation(&resourceapi.AllocationResult{NodeSelector: st.MakeNodeSelector().In("no-such-label", []string{"no-such-value"}, st.NodeSelectorTypeMatchExpressions).Obj()}). Obj() + allocatedPodGroupClaimWithWrongTopology = st.FromResourceClaim(allocatedPodGroupClaim). + Allocation(&resourceapi.AllocationResult{NodeSelector: st.MakeNodeSelector().In("no-such-label", []string{"no-such-value"}, st.NodeSelectorTypeMatchExpressions).Obj()}). + ReservedForPodGroup(podGroupName, types.UID(podGroupUID)). + Obj() allocatedClaimWithGoodTopology = st.FromResourceClaim(allocatedClaim). Allocation(&resourceapi.AllocationResult{NodeSelector: st.MakeNodeSelector().In("kubernetes.io/hostname", []string{nodeName}, st.NodeSelectorTypeMatchExpressions).Obj()}). Obj() + allocatedClaimWithSharedDevice = st.FromResourceClaim(pendingClaim). + Allocation(allocationResultWithSharedDevice). + Obj() + allocatedClaimWithConsumedCapacity = st.FromResourceClaim(pendingClaim). + Allocation(allocationResultWithConsumedCapacity). + Obj() + allocatedClaimWithConsumedCapacity2 = st.FromResourceClaim(pendingClaim). + Allocation(allocationResultWithConsumedCapacity2). + Obj() otherClaim = st.MakeResourceClaim(). Name("not-my-claim"). Namespace(namespace). @@ -525,6 +690,11 @@ var ( otherAllocatedClaim = st.FromResourceClaim(otherClaim). Allocation(allocationResult). Obj() + otherAllocatedClaimOtherDevice = func() *resourceapi.ResourceClaim { + claim := otherAllocatedClaim.DeepCopy() + claim.Status.Allocation.Devices.Results[0].Device += "-other" + return claim + }() extendedResourceClaim = st.MakeResourceClaim(). Name("my-pod-extended-resources-0"). GenerateName("my-pod-extended-resources-"). @@ -703,6 +873,7 @@ var ( }() allocationResultWithBindingConditions = &resourceapi.AllocationResult{ + AllocationTimestamp: new(metav1.Time), // Non-nil, actual value not checked. Devices: resourceapi.DeviceAllocationResult{ Results: []resourceapi.DeviceRequestAllocationResult{{ Driver: driver, @@ -717,6 +888,7 @@ var ( } allocationResultWithBindingConditions2 = &resourceapi.AllocationResult{ + AllocationTimestamp: new(metav1.Time), // Non-nil, actual value not checked. Devices: resourceapi.DeviceAllocationResult{ Results: []resourceapi.DeviceRequestAllocationResult{{ Driver: driver2, @@ -730,6 +902,10 @@ var ( NodeSelector: st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj(), } + bindClaim = st.FromResourceClaim(allocatedClaim). + Allocation(allocationResultWithBindingConditions). + Obj() + boundClaim = st.FromResourceClaim(allocatedClaim). Allocation(allocationResultWithBindingConditions). AllocatedDeviceStatuses([]resourceapi.AllocatedDeviceStatus{ @@ -789,6 +965,22 @@ var ( }, }). Obj() + + nodeAllocatableResAllocationResult = &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{{ + Driver: nodeAllocatableResourceDriver, + Pool: nodeName, + Device: "numa-0-cpus", + Request: "req-1", + ShareID: ptr.To(types.UID("share-123")), + ConsumedCapacity: map[resourceapi.QualifiedName]apiresource.Quantity{ + nodeAllocatableResourceCapacityName: apiresource.MustParse("1"), + }, + }}, + }, + NodeSelector: st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj(), + } ) func taintDevices(slice *resourceapi.ResourceSlice) *resourceapi.ResourceSlice { @@ -805,6 +997,23 @@ func reserve(claim *resourceapi.ResourceClaim, pod *v1.Pod) *resourceapi.Resourc Obj() } +func reserveFor(claim *resourceapi.ResourceClaim, bindings ...resourceapi.ResourceClaimConsumerReference) *resourceapi.ResourceClaim { + return st.FromResourceClaim(claim). + ReservedFor(bindings...). + Obj() +} + +// addAllocationTimestamp adds an AllocationTimestamp to a claim. +// Non-nil is all that matters for the go-cmp comparison. +// Test cases involving binding conditions must ensure that they +// have such a non-nil time stamp in their expected claims starting +// with PreBind because PreBind adds it when the feature is on. +func addAllocationTimestamp(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + claim = claim.DeepCopy() + claim.Status.Allocation.AllocationTimestamp = new(metav1.Time) + return claim +} + func adminAccess(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { claim = claim.DeepCopy() for i := range claim.Spec.Devices.Requests { @@ -898,6 +1107,7 @@ type result struct { // make changes only to a particular instance, then it must check the name. type change struct { claim func(*resourceapi.ResourceClaim) *resourceapi.ResourceClaim + pod func(*v1.Pod) *v1.Pod } type perNodeResult map[string]result @@ -918,22 +1128,22 @@ func (p perNodeScoreResult) forNode(nodeName string) int64 { } type want struct { - preenqueue result - preFilterResult *fwk.PreFilterResult - prefilter result - filter perNodeResult - prescore result - scoreResult perNodeScoreResult - score perNodeResult - normalizeScoreResult fwk.NodeScoreList - normalizeScore result - reserve result - unreserve result - prebindPreFlight *fwk.Status - prebind result - postbind result - postFilterResult *fwk.PostFilterResult - postfilter result + preenqueue result + preFilterResult *fwk.PreFilterResult + prefilter result + filter perNodeResult + prescore result + scoreResult perNodeScoreResult + score perNodeResult + normalizeScoreResult fwk.NodeScoreList + normalizeScore result + reserve result + unreserve result + preBindPreFlightStatus *fwk.Status + prebind result + postbind result + postFilterResult *fwk.PostFilterResult + postfilter result // unreserveAfterBindFailure, if set, triggers a call to Unreserve // after PreBind, as if the actual Bind had failed. @@ -958,43 +1168,61 @@ type prepare struct { postfilter change } +type testPluginCase struct { + // patchTestCase gets called right before the test case is tested. + // It can be used to update time stamps in those test cases + // which are sensitive to the current time. + patchTestCase func(tc *testPluginCase) + + args *config.DynamicResourcesArgs + nodes []*v1.Node // default if unset is workerNode + pod *v1.Pod + claims []*resourceapi.ResourceClaim + classes []*resourceapi.DeviceClass + podGroups []*schedulingapi.PodGroup + + inFlightClaims []*resourceapi.ResourceClaim + + // objs get stored directly in the fake client, without passing + // through reactors, in contrast to the types above. + objs []apiruntime.Object + + prepare prepare + want want + + // Invoke Filter with a canceled context. + cancelFilter bool + + // disableDRAAdminAccess is set to true to test behavior with the DRAAdminAccess feature gate disabled (emulates v1.35). + disableDRAAdminAccess bool + // disableDRADeviceBindingConditions is set to true if the DRADeviceBindingConditions feature gate is disabled. + disableDRADeviceBindingConditions bool + // disableDRAResourceClaimDeviceStatus is set to true if the DRAResourceClaimDeviceStatus feature gate is disabled. + disableDRAResourceClaimDeviceStatus bool + // enableDRANodeAllocatableResourcesis set to true if the DRANodeAllocatableResources feature gate is enabled. + enableDRANodeAllocatableResources bool + // enableDRAConsumableCapacity is set to true if the DRAConsumableCapacity feature gate is enabled. + enableDRAConsumableCapacity bool + // Feature gates. False is chosen so that the uncommon case + // doesn't need to be set. + disableDRA bool + + enableDRAExtendedResource bool + enableDRAPrioritizedList bool + enableDRADeviceTaints bool + enableDRAWorkloadResourceClaims bool + disableDRASchedulerFilterTimeout bool + skipOnWindows string + failPatch bool + reactors []cgotesting.Reactor + metrics func(ktesting.TContext, compbasemetrics.Gatherer) +} + func TestPlugin(t *testing.T) { - testcases := map[string]struct { - args *config.DynamicResourcesArgs - nodes []*v1.Node // default if unset is workerNode - pod *v1.Pod - claims []*resourceapi.ResourceClaim - classes []*resourceapi.DeviceClass - - // objs get stored directly in the fake client, without passing - // through reactors, in contrast to the types above. - objs []apiruntime.Object - - prepare prepare - want want - - // Invoke Filter with a canceled context. - cancelFilter bool - - // enableDRAAdminAccess is set to true if the DRAAdminAccess feature gate is enabled. - enableDRAAdminAccess bool - // enableDRADeviceBindingConditions is set to true if the DRADeviceBindingConditions feature gate is enabled. - enableDRADeviceBindingConditions bool - // EnableDRAResourceClaimDeviceStatus is set to true if the DRAResourceClaimDeviceStatus feature gate is enabled. - enableDRAResourceClaimDeviceStatus bool - // Feature gates. False is chosen so that the uncommon case - // doesn't need to be set. - disableDRA bool - - enableDRAExtendedResource bool - enableDRAPrioritizedList bool - enableDRADeviceTaints bool - disableDRASchedulerFilterTimeout bool - skipOnWindows string - failPatch bool - reactors []cgotesting.Reactor - metrics func(*testing.T, compbasemetrics.Gatherer) - }{ + testPlugin(ktesting.Init(t)) +} +func testPlugin(tCtx ktesting.TContext) { + testcases := map[string]testPluginCase{ "empty": { pod: st.MakePod().Name("foo").Namespace("default").Obj(), want: want{ @@ -1004,7 +1232,7 @@ func TestPlugin(t *testing.T) { postfilter: result{ status: fwk.NewStatus(fwk.Unschedulable), }, - prebindPreFlight: fwk.NewStatus(fwk.Skip), + preBindPreFlightStatus: fwk.NewStatus(fwk.Skip), }, }, "empty-with-extended-resources-enabled": { @@ -1017,7 +1245,7 @@ func TestPlugin(t *testing.T) { postfilter: result{ status: fwk.NewStatus(fwk.Unschedulable), }, - prebindPreFlight: fwk.NewStatus(fwk.Skip), + preBindPreFlightStatus: fwk.NewStatus(fwk.Skip), }, }, "claim-reference": { @@ -1025,12 +1253,13 @@ func TestPlugin(t *testing.T) { claims: []*resourceapi.ResourceClaim{allocatedClaim, otherClaim}, want: want{ prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Status.ReservedFor = inUseClaim.Status.ReservedFor + claim = addAllocationTimestamp(claim) } return claim }, @@ -1043,12 +1272,76 @@ func TestPlugin(t *testing.T) { claims: []*resourceapi.ResourceClaim{allocatedClaim, otherClaim}, want: want{ prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimTemplateInStatus), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimTemplateInStatus)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Status.ReservedFor = inUseClaim.Status.ReservedFor + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + }, + }, + "podgroup-claim-reference-feature-disabled-reserves-for-pod": { + enableDRAWorkloadResourceClaims: false, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaim, otherClaim}, + want: want{ + prebind: result{ + assumedClaim: addAllocationTimestamp(reserve(allocatedPodGroupClaim, groupedPodWithClaimName)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Status.ReservedFor = inUseClaim.Status.ReservedFor + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + }, + }, + "podgroup-claim-reference": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaim, otherClaim}, + want: want{ + prebind: result{ + assumedClaim: addAllocationTimestamp(reserveFor(allocatedPodGroupClaim, podGroupReservation)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Status.ReservedFor = inUseClaimByPodGroup.Status.ReservedFor + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + }, + }, + "podgroup-claim-template": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimTemplateInStatus, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimTemplate}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaim, otherClaim}, + want: want{ + prebind: result{ + assumedClaim: addAllocationTimestamp(reserveFor(allocatedPodGroupClaim, podGroupReservation)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Status.ReservedFor = inUseClaimByPodGroup.Status.ReservedFor + claim = addAllocationTimestamp(claim) } return claim }, @@ -1087,7 +1380,7 @@ func TestPlugin(t *testing.T) { }(), want: want{ preenqueue: result{ - status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `ResourceClaim default/my-pod-my-resource was not created for pod default/my-pod (pod is not owner)`), + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "ResourceClaim default/my-pod-my-resource was not created for Pod default/my-pod (Pod is not owner)"), }, }, }, @@ -1116,13 +1409,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1133,6 +1427,60 @@ func TestPlugin(t *testing.T) { }, }, }, + "podgroup-with-resources": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + claims: []*resourceapi.ResourceClaim{pendingPodGroupClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + reserve: result{ + inFlightClaims: []metav1.Object{allocatedPodGroupClaim}, + }, + prebind: result{ + assumedClaim: addAllocationTimestamp(reserveFor(allocatedPodGroupClaim, podGroupReservation)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Finalizers = allocatedPodGroupClaim.Finalizers + claim.Status = inUseClaimByPodGroup.Status + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + }, + }, + "podgroup-with-resources-feature-disabled-reserves-for-pod": { + enableDRAWorkloadResourceClaims: false, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + claims: []*resourceapi.ResourceClaim{pendingPodGroupClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + reserve: result{ + inFlightClaims: []metav1.Object{allocatedPodGroupClaim}, + }, + prebind: result{ + assumedClaim: addAllocationTimestamp(reserve(allocatedPodGroupClaim, groupedPodWithClaimName)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Finalizers = allocatedClaim.Finalizers + claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + }, + }, "with-resources-has-finalizer": { // As before. but the finalizer is already set. Could happen if // the scheduler got interrupted. @@ -1149,12 +1497,13 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1189,13 +1538,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1226,12 +1576,13 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1254,7 +1605,7 @@ func TestPlugin(t *testing.T) { unreserveBeforePreBind: &result{}, }, }, - "exhausted-resources": { + "exhausted-resources-in-informer-cache": { pod: podWithClaimName, claims: []*resourceapi.ResourceClaim{pendingClaim, otherAllocatedClaim}, classes: []*resourceapi.DeviceClass{deviceClass}, @@ -1270,6 +1621,72 @@ func TestPlugin(t *testing.T) { }, }, }, + "exhausted-resources-in-flight-claim": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaim, otherClaim}, + inFlightClaims: []*resourceapi.ResourceClaim{otherAllocatedClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + preenqueue: result{ + inFlightClaims: []metav1.Object{otherAllocatedClaim}, + }, + prefilter: result{ + inFlightClaims: []metav1.Object{otherAllocatedClaim}, + }, + filter: perNodeResult{ + workerNode.Name: { + inFlightClaims: []metav1.Object{otherAllocatedClaim}, + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `cannot allocate all claims`), + }, + }, + postfilter: result{ + inFlightClaims: []metav1.Object{otherAllocatedClaim}, + status: fwk.NewStatus(fwk.Unschedulable, `still not schedulable`), + }, + }, + }, + "other-resources-in-flight-claim": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaim, otherClaim}, + inFlightClaims: []*resourceapi.ResourceClaim{otherAllocatedClaimOtherDevice}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + preenqueue: result{ + inFlightClaims: []metav1.Object{otherAllocatedClaimOtherDevice}, + }, + prefilter: result{ + inFlightClaims: []metav1.Object{otherAllocatedClaimOtherDevice}, + }, + filter: perNodeResult{ + workerNode.Name: { + inFlightClaims: []metav1.Object{otherAllocatedClaimOtherDevice}, + }, + }, + reserve: result{ + inFlightClaims: []metav1.Object{allocatedClaim, otherAllocatedClaimOtherDevice}, + }, + prebind: result{ + inFlightClaims: []metav1.Object{otherAllocatedClaimOtherDevice}, + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + postbind: result{ + inFlightClaims: []metav1.Object{allocatedClaim, otherAllocatedClaimOtherDevice}, + assumedClaim: reserve(allocatedClaim, podWithClaimName), + }, + }, + }, // The two test cases for device tainting only need to cover // whether the feature gate is passed through to the allocator @@ -1286,13 +1703,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1334,13 +1752,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{adminAccess(allocatedClaim)}, }, prebind: result{ - assumedClaim: reserve(adminAccess(allocatedClaim), podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(adminAccess(allocatedClaim), podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = adminAccess(inUseClaim).Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1350,7 +1769,6 @@ func TestPlugin(t *testing.T) { assumedClaim: reserve(adminAccess(allocatedClaim), podWithClaimName), }, }, - enableDRAAdminAccess: true, }, "request-admin-access-without-DRAAdminAccess-featuregate": { // When the DRAAdminAccess feature gate is disabled, @@ -1367,7 +1785,7 @@ func TestPlugin(t *testing.T) { }, }, }, - enableDRAAdminAccess: false, + disableDRAAdminAccess: true, }, "structured-ignore-allocated-admin-access": { @@ -1382,13 +1800,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1509,12 +1928,12 @@ func TestPlugin(t *testing.T) { claims: []*resourceapi.ResourceClaim{allocatedClaimWithGoodTopology}, want: want{ prebind: result{ - assumedClaim: reserve(allocatedClaimWithGoodTopology, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithGoodTopology, podWithClaimName)), changes: change{ claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { - return st.FromResourceClaim(in). + return addAllocationTimestamp(st.FromResourceClaim(in). ReservedFor(resourceapi.ResourceClaimConsumerReference{Resource: "pods", Name: podName, UID: types.UID(podUID)}). - Obj() + Obj()) }, }, }, @@ -1525,17 +1944,17 @@ func TestPlugin(t *testing.T) { claims: []*resourceapi.ResourceClaim{allocatedClaimWithGoodTopology}, want: want{ prebind: result{ - assumedClaim: reserve(allocatedClaimWithGoodTopology, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithGoodTopology, podWithClaimName)), changes: change{ claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { - return st.FromResourceClaim(in). + return addAllocationTimestamp(st.FromResourceClaim(in). ReservedFor(resourceapi.ResourceClaimConsumerReference{Resource: "pods", Name: podName, UID: types.UID(podUID)}). - Obj() + Obj()) }, }, }, unreserveAfterBindFailure: &result{ - assumedClaim: reserve(allocatedClaimWithGoodTopology, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithGoodTopology, podWithClaimName)), changes: change{ claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { out := in.DeepCopy() @@ -1546,10 +1965,187 @@ func TestPlugin(t *testing.T) { }, }, }, + "bind-podgroup-failure": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaim}, + want: want{ + prebind: result{ + assumedClaim: addAllocationTimestamp(reserveFor(allocatedPodGroupClaim, podGroupReservation)), + changes: change{ + claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + claim := st.FromResourceClaim(in). + ReservedFor(podGroupReservation). + Obj() + claim = addAllocationTimestamp(claim) + return claim + }, + }, + }, + unreserveAfterBindFailure: &result{ + assumedClaim: addAllocationTimestamp(reserveFor(allocatedPodGroupClaim, podGroupReservation)), + // The PodGroup is not removed from ReservedFor + }, + }, + }, + "postfilter-unreserve-podgroup": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + objs: []apiruntime.Object{ + // Pods in the PodGroup + groupedPodWithClaimName, + }, + classes: []*resourceapi.DeviceClass{deviceClass}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaimWithWrongTopology}, + want: want{ + filter: perNodeResult{ + workerNode.Name: { + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `resourceclaim not available on the node`), + }, + }, + postfilter: result{ + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + claim = claim.DeepCopy() + claim.Status.ReservedFor = []resourceapi.ResourceClaimConsumerReference{} + return claim + }, + }, + status: fwk.NewStatus(fwk.Unschedulable, `ResourceClaim unreserved for PodGroup`), + }, + }, + }, + "postfilter-skip-unreserve-podgroup-feature-disabled": { + enableDRAWorkloadResourceClaims: false, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + objs: []apiruntime.Object{ + // Pods in the PodGroup + groupedPodWithClaimName, + }, + classes: []*resourceapi.DeviceClass{deviceClass}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaimWithWrongTopology}, + want: want{ + filter: perNodeResult{ + workerNode.Name: { + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `resourceclaim not available on the node`), + }, + }, + postfilter: result{ + status: fwk.NewStatus(fwk.Unschedulable, `still not schedulable`), + }, + }, + }, + "postfilter-deallocate-podgroup": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + objs: []apiruntime.Object{ + // Pods in the PodGroup + groupedPodWithClaimName, + }, + classes: []*resourceapi.DeviceClass{deviceClass}, + claims: []*resourceapi.ResourceClaim{ + func() *resourceapi.ResourceClaim { + claim := allocatedPodGroupClaimWithWrongTopology.DeepCopy() + claim.Status.ReservedFor = nil + return claim + }(), + }, + want: want{ + filter: perNodeResult{ + workerNode.Name: { + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `resourceclaim not available on the node`), + }, + }, + postfilter: result{ + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + claim = claim.DeepCopy() + claim.Status.ReservedFor = nil + claim.Status.Allocation = nil + return claim + }, + }, + status: fwk.NewStatus(fwk.Unschedulable, `deallocation of ResourceClaim completed`), + }, + }, + }, + "postfilter-skip-unreserve-podgroup-when-pods-active": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + objs: []apiruntime.Object{ + // Pods in the PodGroup + groupedPodWithClaimName, + st.MakePod().Name(podName + "-2").Namespace(namespace). + Node(nodeName). // Scheduled, this Pod still needs the PodGroup's claim + PodGroupName(podGroupName). + Obj(), + }, + classes: []*resourceapi.DeviceClass{deviceClass}, + claims: []*resourceapi.ResourceClaim{allocatedPodGroupClaimWithWrongTopology}, + want: want{ + filter: perNodeResult{ + workerNode.Name: { + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `resourceclaim not available on the node`), + }, + }, + postfilter: result{ + status: fwk.NewStatus(fwk.Unschedulable, `still not schedulable`), + }, + }, + }, + "postfilter-skip-unreserve-podgroup-other-claim-deallocated": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithTwoClaimNames, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + objs: []apiruntime.Object{ + // Pods in the PodGroup + groupedPodWithClaimName, + }, + classes: []*resourceapi.DeviceClass{deviceClass}, + claims: []*resourceapi.ResourceClaim{ + allocatedPodGroupClaimWithWrongTopology, + func() *resourceapi.ResourceClaim { + claim := allocatedClaimWithWrongTopology.DeepCopy() + claim.Name = claimName2 + return claim + }(), + }, + want: want{ + filter: perNodeResult{ + workerNode.Name: { + status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `resourceclaim not available on the node`), + }, + }, + postfilter: result{ + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName2 { + return st.FromResourceClaim(claim). + Allocation(nil). + Obj() + } + return claim + }, + }, + status: fwk.NewStatus(fwk.Unschedulable, `deallocation of ResourceClaim completed`), + }, + }, + }, "reserved-okay": { pod: podWithClaimName, claims: []*resourceapi.ResourceClaim{inUseClaim}, }, + "reserved-okay-podgroup": { + enableDRAWorkloadResourceClaims: true, + pod: groupedPodWithClaimName, + podGroups: []*schedulingapi.PodGroup{podGroupWithClaimName}, + claims: []*resourceapi.ResourceClaim{inUseClaimByPodGroup}, + }, "DRA-disabled": { pod: podWithClaimName, claims: []*resourceapi.ResourceClaim{inUseClaim}, @@ -1560,7 +2156,7 @@ func TestPlugin(t *testing.T) { postfilter: result{ status: fwk.NewStatus(fwk.Unschedulable, `plugin disabled`), }, - prebindPreFlight: fwk.NewStatus(fwk.Skip), + preBindPreFlightStatus: fwk.NewStatus(fwk.Skip), }, disableDRA: true, }, @@ -1614,13 +2210,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaimWithPrioritizedList}, }, prebind: result{ - assumedClaim: reserve(allocatedClaimWithPrioritizedList, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithPrioritizedList, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaimWithPrioritizedList.Finalizers claim.Status = inUseClaimWithPrioritizedList.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -1629,36 +2226,32 @@ func TestPlugin(t *testing.T) { }, }, "extended-resource-name-with-node-resource": { - enableDRAExtendedResource: true, - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - nodes: []*v1.Node{workerNodeWithExtendedResource}, - pod: podWithExtendedResourceName, - classes: []*resourceapi.DeviceClass{deviceClassWithExtendResourceName}, - want: want{}, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + enableDRAExtendedResource: true, + nodes: []*v1.Node{workerNodeWithExtendedResource}, + pod: podWithExtendedResourceName, + classes: []*resourceapi.DeviceClass{deviceClassWithExtendResourceName}, + want: want{}, + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { _, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.ErrorContains(t, err, "not found") + require.ErrorContains(tCtx, err, "not found") }, }, "extended-resource-one-device-plugin-one-dra": { - enableDRAExtendedResource: true, - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - nodes: []*v1.Node{workerNodeWithExtendedResource}, - pod: podWithExtendedResourceName2, - classes: []*resourceapi.DeviceClass{deviceClassWithExtendResourceName, deviceClassWithExtendResourceName2}, - objs: []apiruntime.Object{workerNodeSlice, podWithExtendedResourceName2}, + enableDRAExtendedResource: true, + nodes: []*v1.Node{workerNodeWithExtendedResource}, + pod: podWithExtendedResourceName2, + classes: []*resourceapi.DeviceClass{deviceClassWithExtendResourceName, deviceClassWithExtendResourceName2}, + objs: []apiruntime.Object{workerNodeSlice, podWithExtendedResourceName2}, want: want{ reserve: result{ inFlightClaims: []metav1.Object{extendedResourceClaimNoName2}, }, prebind: result{ - assumedClaim: reserve(extendedResourceClaim2, podWithExtendedResourceName2), - added: []metav1.Object{reserve(extendedResourceClaim2, podWithExtendedResourceName2)}, + assumedClaim: addAllocationTimestamp(reserve(extendedResourceClaim2, podWithExtendedResourceName2)), + added: []metav1.Object{addAllocationTimestamp(reserve(extendedResourceClaim2, podWithExtendedResourceName2))}, }, postbind: result{ - assumedClaim: reserve(extendedResourceClaim2, podWithExtendedResourceName2), + assumedClaim: addAllocationTimestamp(reserve(extendedResourceClaim2, podWithExtendedResourceName2)), }, }, }, @@ -1673,8 +2266,8 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{extendedResourceClaimNoName}, }, prebind: result{ - assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), - added: []metav1.Object{reserve(extendedResourceClaim, podWithExtendedResourceName)}, + assumedClaim: addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName)), + added: []metav1.Object{addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName))}, }, postbind: result{ assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), @@ -1691,7 +2284,7 @@ func TestPlugin(t *testing.T) { prefilter: result{ status: fwk.NewStatus(fwk.Skip), }, - prebindPreFlight: fwk.NewStatus(fwk.Skip), + preBindPreFlightStatus: fwk.NewStatus(fwk.Skip), }, }, "extended-resource-name-no-resource": { @@ -1708,9 +2301,9 @@ func TestPlugin(t *testing.T) { status: fwk.NewStatus(fwk.Unschedulable, `still not schedulable`), }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { _, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.ErrorContains(t, err, "not found") + require.ErrorContains(tCtx, err, "not found") }, }, "extended-resource-name-with-resources": { @@ -1723,17 +2316,17 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{extendedResourceClaimNoName}, }, prebind: result{ - assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), - added: []metav1.Object{reserve(extendedResourceClaim, podWithExtendedResourceName)}, + assumedClaim: addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName)), + added: []metav1.Object{addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName))}, }, postbind: result{ assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["success"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["success"])) }, }, "implicit-extended-resource-name-with-resources": { @@ -1746,17 +2339,17 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{implicitExtendedResourceClaimNoName}, }, prebind: result{ - assumedClaim: reserve(implicitExtendedResourceClaim, podWithImplicitExtendedResourceName), - added: []metav1.Object{reserve(implicitExtendedResourceClaim, podWithImplicitExtendedResourceName)}, + assumedClaim: addAllocationTimestamp(reserve(implicitExtendedResourceClaim, podWithImplicitExtendedResourceName)), + added: []metav1.Object{addAllocationTimestamp(reserve(implicitExtendedResourceClaim, podWithImplicitExtendedResourceName))}, }, postbind: result{ assumedClaim: reserve(implicitExtendedResourceClaim, podWithImplicitExtendedResourceName), }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["success"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["success"])) }, }, "implicit-extended-resource-name-two-containers-with-resources": { @@ -1769,17 +2362,17 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{implicitExtendedResourceClaimNoNameTwoContainers}, }, prebind: result{ - assumedClaim: reserve(implicitExtendedResourceClaimTwoContainers, podWithImplicitExtendedResourceNameTwoContainers), - added: []metav1.Object{reserve(implicitExtendedResourceClaimTwoContainers, podWithImplicitExtendedResourceNameTwoContainers)}, + assumedClaim: addAllocationTimestamp(reserve(implicitExtendedResourceClaimTwoContainers, podWithImplicitExtendedResourceNameTwoContainers)), + added: []metav1.Object{addAllocationTimestamp(reserve(implicitExtendedResourceClaimTwoContainers, podWithImplicitExtendedResourceNameTwoContainers))}, }, postbind: result{ assumedClaim: reserve(implicitExtendedResourceClaimTwoContainers, podWithImplicitExtendedResourceNameTwoContainers), }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["success"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["success"])) }, }, "extended-resource-name-with-resources-fail-patch": { @@ -1793,18 +2386,18 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{extendedResourceClaimNoName}, }, prebind: result{ - assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), - added: []metav1.Object{reserve(extendedResourceClaim, podWithExtendedResourceName)}, + assumedClaim: addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName)), + added: []metav1.Object{addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName))}, status: fwk.NewStatus(fwk.Unschedulable, `patch error`), }, postbind: result{ assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["success"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["success"])) }, }, "extended-resource-name-with-resources-has-claim": { @@ -1824,9 +2417,9 @@ func TestPlugin(t *testing.T) { removed: []metav1.Object{extendedResourceClaim}, }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { _, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.ErrorContains(t, err, "not found") + require.ErrorContains(tCtx, err, "not found") }, }, "extended-resource-name-with-resources-delete-claim": { @@ -1846,9 +2439,9 @@ func TestPlugin(t *testing.T) { removed: []metav1.Object{extendedResourceClaimNode2}, }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { _, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.ErrorContains(t, err, "not found") + require.ErrorContains(tCtx, err, "not found") }, }, "extended-resource-name-bind-failure": { @@ -1861,17 +2454,17 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{extendedResourceClaimNoName}, }, prebind: result{ - assumedClaim: reserve(extendedResourceClaim, podWithExtendedResourceName), - added: []metav1.Object{reserve(extendedResourceClaim, podWithExtendedResourceName)}, + assumedClaim: addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName)), + added: []metav1.Object{addAllocationTimestamp(reserve(extendedResourceClaim, podWithExtendedResourceName))}, }, unreserveAfterBindFailure: &result{ removed: []metav1.Object{reserve(extendedResourceClaim, podWithExtendedResourceName)}, }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["success"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["success"])) }, }, "extended-resource-name-skip-bind": { @@ -1885,10 +2478,10 @@ func TestPlugin(t *testing.T) { }, unreserveBeforePreBind: &result{}, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["success"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["success"])) }, }, "extended-resource-name-claim-creation-failure": { @@ -1916,10 +2509,10 @@ func TestPlugin(t *testing.T) { }, }, }, - metrics: func(t *testing.T, g compbasemetrics.Gatherer) { + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { metric, err := testutil.GetCounterValuesFromGatherer(g, "scheduler_resourceclaim_creates_total", map[string]string{}, "status") - require.NoError(t, err) - require.Equal(t, 1, int(metric["failure"])) + require.NoError(tCtx, err) + require.Equal(tCtx, 1, int(metric["failure"])) }, }, "canceled": { @@ -1953,12 +2546,11 @@ func TestPlugin(t *testing.T) { want: want{ filter: perNodeResult{ workerNode.Name: { - status: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `timed out trying to allocate devices`), + // Timeouts return Error so the pod retries via backoff. + status: fwk.AsStatus(fmt.Errorf("node %s: timed out trying to allocate devices", workerNode.Name)), }, }, - postfilter: result{ - status: fwk.NewStatus(fwk.Unschedulable, `still not schedulable`), - }, + // No postfilter: Error aborts scheduling immediately. }, // Skipping this test case on Windows as a 1ns timeout is not guaranteed to // expire immediately on Windows due to its coarser timer granularity - @@ -1981,13 +2573,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -2011,13 +2604,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaim}, }, prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaim.Finalizers claim.Status = inUseClaim.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -2028,11 +2622,129 @@ func TestPlugin(t *testing.T) { }, }, }, + "dont-add-allocation-timestamp": { + disableDRADeviceBindingConditions: true, + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + reserve: result{ + inFlightClaims: []metav1.Object{allocatedClaim}, + }, + prebind: result{ + assumedClaim: reserve(allocatedClaim, podWithClaimName), + changes: change{ + claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + return reserve(allocatedClaim, podWithClaimName) + }, + }, + }, + }, + }, + "add-allocation-timestamp": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + reserve: result{ + inFlightClaims: []metav1.Object{allocatedClaim}, + }, + prebind: result{ + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)), + changes: change{ + claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + return addAllocationTimestamp(reserve(allocatedClaim, podWithClaimName)) + }, + }, + }, + }, + }, + "add-allocation-timestamp-failure": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{allocatedClaim}, + prepare: prepare{ + prebind: change{ + claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + // Simulate deallocation before PreBind runs. + return st.FromResourceClaim(in). + Allocation(nil). + Obj() + }, + }, + }, + want: want{ + prebind: result{ + status: fwk.AsStatus(fmt.Errorf("claim %s got deallocated elsewhere in the meantime", klog.KObj(allocatedClaim))), + }, + }, + }, + "bind-claim-with-binding-conditions": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{fabricSlice}, + args: &config.DynamicResourcesArgs{ + // Time out quickly in PreBind. There's no controller which sets the + // binding conditions. + BindingTimeout: &metav1.Duration{Duration: time.Second}, + }, + want: want{ + reserve: result{ + inFlightClaims: []metav1.Object{func() *resourceapi.ResourceClaim { + claim := bindClaim.DeepCopy() + // Will get set in PreBind. + claim.Status.Allocation.AllocationTimestamp = nil + return claim + }()}, + }, + prebind: result{ + assumedClaim: reserve(bindClaim, podWithClaimName), + changes: change{ + claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + return reserve(bindClaim, podWithClaimName) + }, + }, + // From PreBind itself, when checking isPodReadyForBinding times out. + status: fwk.AsStatus(errors.New("device binding timeout")), + }, + }, + }, + "bind-failure-concurrent-deallocation": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{fabricSlice}, + args: &config.DynamicResourcesArgs{ + // Time out quickly in PreBind. There's no controller which sets the + // binding conditions. + BindingTimeout: &metav1.Duration{Duration: time.Second}, + }, + want: want{ + reserve: result{ + inFlightClaims: []metav1.Object{func() *resourceapi.ResourceClaim { + claim := bindClaim.DeepCopy() + // Will get set in PreBind. + claim.Status.Allocation.AllocationTimestamp = nil + return claim + }()}, + }, + prebind: result{ + assumedClaim: reserve(bindClaim, podWithClaimName), + changes: change{ + claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + return reserve(bindClaim, podWithClaimName) + }, + }, + // From PreBind itself, when checking isPodReadyForBinding times out. + status: fwk.AsStatus(errors.New("device binding timeout")), + }, + }, + }, "bound-claim-with-succeeded-binding-conditions": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - pod: podWithClaimName, - claims: []*resourceapi.ResourceClaim{boundClaim}, + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{boundClaim}, want: want{ prebind: result{ assumedClaim: reserve(boundClaim, podWithClaimName), @@ -2046,13 +2758,40 @@ func TestPlugin(t *testing.T) { status: nil, }, }, + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { + // Counter: allocations_total should have exactly one event + allocs, err := testutil.GetCounterValuesFromGatherer( + g, + "scheduler_dra_bindingconditions_allocations_total", + map[string]string{ + "status": "success", + }, + "driver", // group by driver label + ) + require.NoError(tCtx, err) + + var totalAllocs float64 + for _, v := range allocs { + totalAllocs += v + } + require.InEpsilon(tCtx, float64(1), totalAllocs, 0.1, "expected exactly one successful allocation with BindingConditions") + + // Histogram: one success sample with requires_bindingconditions=true + hist, err := testutil.GetHistogramVecFromGatherer( + g, + "scheduler_dra_bindingconditions_wait_duration_seconds", + map[string]string{ + "status": "success", + }, + ) + require.NoError(tCtx, err) + require.Equal(tCtx, uint64(1), hist.GetAggregatedSampleCount(), "expected one success sample in wait duration histogram") + }, }, "bound-claim-with-failed-binding": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - pod: podWithClaimName, - claims: []*resourceapi.ResourceClaim{failedBindingClaim}, - objs: []apiruntime.Object{workerNodeSlice}, + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{failedBindingClaim}, + objs: []apiruntime.Object{workerNodeSlice}, want: want{ filter: perNodeResult{ workerNode.Name: { @@ -2073,9 +2812,7 @@ func TestPlugin(t *testing.T) { }, }, "bound-claim-with-timed-out-binding": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - pod: podWithClaimName, + pod: podWithClaimName, claims: func() []*resourceapi.ResourceClaim { claim := allocatedClaim.DeepCopy() claim.Status.Allocation = allocationResultWithBindingConditions.DeepCopy() @@ -2110,17 +2847,22 @@ func TestPlugin(t *testing.T) { }, }, "prebind-fail-with-binding-timeout": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - args: &config.DynamicResourcesArgs{ - BindingTimeout: &metav1.Duration{Duration: 600 * time.Second}, - }, - pod: podWithClaimName, - claims: func() []*resourceapi.ResourceClaim { + patchTestCase: func(tc *testPluginCase) { + // The time stamps must be injected into the test case right + // before it starts to get tested. + now := time.Now() + + // Set the allocation time so that the claim is not timed out + // yet when the test starts, but then times out relatively quickly (the 10 seconds) + // when the test executes PreBind. + bindingTimeout := tc.args.BindingTimeout.Duration + timeoutAfter := 10 * time.Second + allocatedAt := now.Add(-bindingTimeout).Add(timeoutAfter) + claim := allocatedClaim.DeepCopy() claim.Status.Allocation = allocationResultWithBindingConditions.DeepCopy() // This claim has binding conditions but is not timed out. - claim.Status.Allocation.AllocationTimestamp = ptr.To(metav1.NewTime(time.Now().Add(-9*time.Minute - 50*time.Second))) + claim.Status.Allocation.AllocationTimestamp = ptr.To(metav1.NewTime(allocatedAt)) claim.Status.Devices = []resourceapi.AllocatedDeviceStatus{ { Driver: driver, @@ -2128,24 +2870,27 @@ func TestPlugin(t *testing.T) { Device: "instance-1", }, } - return []*resourceapi.ResourceClaim{claim} - }(), + tc.claims = []*resourceapi.ResourceClaim{claim} + + claim = claim.DeepCopy() + claim.Status.Devices = []resourceapi.AllocatedDeviceStatus{ + { + Driver: driver, + Pool: nodeName, + Device: "instance-1", + }, + } + tc.want.prebind.assumedClaim = reserve(claim, podWithClaimName) + }, + + args: &config.DynamicResourcesArgs{ + BindingTimeout: &metav1.Duration{Duration: 600 * time.Second}, + }, + pod: podWithClaimName, + claims: nil, // Set in patchTestCase. want: want{ prebind: result{ - assumedClaim: reserve(func() *resourceapi.ResourceClaim { - claim := allocatedClaim.DeepCopy() - claim.Status.Allocation = allocationResultWithBindingConditions.DeepCopy() - // This claim has binding conditions but is not timed out. - claim.Status.Allocation.AllocationTimestamp = ptr.To(metav1.NewTime(time.Now().Add(-9*time.Minute - 50*time.Second))) - claim.Status.Devices = []resourceapi.AllocatedDeviceStatus{ - { - Driver: driver, - Pool: nodeName, - Device: "instance-1", - }, - } - return claim - }(), podWithClaimName), + assumedClaim: nil, // Set in patchTestCase. changes: change{ claim: func(in *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { return st.FromResourceClaim(in). @@ -2153,14 +2898,42 @@ func TestPlugin(t *testing.T) { Obj() }, }, - status: fwk.AsStatus(errors.New("claim " + claim.Name + " binding timeout")), + // From isPodReadyForBinding. + status: fwk.AsStatus(fmt.Errorf("%w: claim=%s", ErrDeviceBindingTimeout, claim.Name)), }, }, + metrics: func(tCtx ktesting.TContext, g compbasemetrics.Gatherer) { + // Counter: timeouts_total should have exactly one event + timeouts, err := testutil.GetCounterValuesFromGatherer( + g, + "scheduler_dra_bindingconditions_allocations_total", + map[string]string{ + "status": "timeout", + }, + "driver", + ) + require.NoError(tCtx, err) + + var totalTimeouts float64 + for _, v := range timeouts { + totalTimeouts += v + } + require.InEpsilon(tCtx, float64(1), totalTimeouts, 0.1, "expected exactly one timeout with BindingConditions") + + // Histogram: one timeout sample with requires_bindingconditions=true + hist, err := testutil.GetHistogramVecFromGatherer( + g, + "scheduler_dra_bindingconditions_wait_duration_seconds", + map[string]string{ + "status": "timeout", + }, + ) + require.NoError(tCtx, err) + require.Equal(tCtx, uint64(1), hist.GetAggregatedSampleCount(), "expected one timeout sample in wait duration histogram") + }, }, "bound-claim-with-mixed-binding-conditions": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - pod: podWithClaimName, + pod: podWithClaimName, claims: func() []*resourceapi.ResourceClaim { claim := allocatedClaim.DeepCopy() claim.Status.Allocation = allocationResultWithBindingConditions.DeepCopy() @@ -2199,8 +2972,6 @@ func TestPlugin(t *testing.T) { }, }, "bound-claim-without-binding-conditions": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, // This test ensures that when DRADeviceBindingConditions is enabled, // but the claim has no binding conditions or binding failures, // the plugin proceeds as if all conditions are satisfied. @@ -2208,12 +2979,13 @@ func TestPlugin(t *testing.T) { claims: []*resourceapi.ResourceClaim{allocatedClaim, otherClaim}, want: want{ prebind: result{ - assumedClaim: reserve(allocatedClaim, podWithClaimTemplateInStatus), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimTemplateInStatus)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Status.ReservedFor = inUseClaim.Status.ReservedFor + claim = addAllocationTimestamp(claim) } return claim }, @@ -2223,13 +2995,11 @@ func TestPlugin(t *testing.T) { }, }, "multi-claims-binding-conditions-all-success": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - pod: podWithTwoClaimNames, - claims: []*resourceapi.ResourceClaim{boundClaim, boundClaim2}, - classes: []*resourceapi.DeviceClass{deviceClass}, - nodes: []*v1.Node{workerNode}, - objs: []apiruntime.Object{fabricSlice, fabricSlice2}, + pod: podWithTwoClaimNames, + claims: []*resourceapi.ResourceClaim{boundClaim, boundClaim2}, + classes: []*resourceapi.DeviceClass{deviceClass}, + nodes: []*v1.Node{workerNode}, + objs: []apiruntime.Object{fabricSlice, fabricSlice2}, want: want{ prebind: result{ assumedClaim: reserve(boundClaim, podWithTwoClaimNames), @@ -2245,13 +3015,11 @@ func TestPlugin(t *testing.T) { }, }, "multi-claims-binding-conditions-one-fail": { - enableDRADeviceBindingConditions: true, - enableDRAResourceClaimDeviceStatus: true, - pod: podWithTwoClaimNames, - claims: []*resourceapi.ResourceClaim{boundClaim, failedBindingClaim2}, - classes: []*resourceapi.DeviceClass{deviceClass}, - nodes: []*v1.Node{workerNode}, - objs: []apiruntime.Object{fabricSlice, fabricSlice2}, + pod: podWithTwoClaimNames, + claims: []*resourceapi.ResourceClaim{boundClaim, failedBindingClaim2}, + classes: []*resourceapi.DeviceClass{deviceClass}, + nodes: []*v1.Node{workerNode}, + objs: []apiruntime.Object{fabricSlice, fabricSlice2}, want: want{ filter: perNodeResult{ workerNode.Name: { @@ -2304,13 +3072,14 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaimWithPrioritizedListAndSelector}, }, prebind: result{ - assumedClaim: reserve(allocatedClaimWithPrioritizedListAndSelector, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithPrioritizedListAndSelector, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = allocatedClaimWithPrioritizedListAndSelector.Finalizers claim.Status = inUseClaimWithPrioritizedListAndSelector.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -2360,18 +3129,20 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaimWithPrioritizedList, allocatedClaim2WithPrioritizedListAndMultipleSubrequests}, }, prebind: result{ - assumedClaim: reserve(allocatedClaimWithPrioritizedList, podWithTwoClaimNames), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithPrioritizedList, podWithTwoClaimNames)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = inUseClaimWithPrioritizedList.Finalizers claim.Status = inUseClaimWithPrioritizedList.Status + claim = addAllocationTimestamp(claim) } if claim.Name == claimName2 { claim = claim.DeepCopy() claim.Finalizers = inUseClaim2WithPrioritizedListAndMultipleSubrequests.Finalizers claim.Status = inUseClaim2WithPrioritizedListAndMultipleSubrequests.Status + claim = addAllocationTimestamp(claim) } return claim }, @@ -2423,84 +3194,375 @@ func TestPlugin(t *testing.T) { inFlightClaims: []metav1.Object{allocatedClaimWithMultiplePrioritizedListRequests}, }, prebind: result{ - assumedClaim: reserve(allocatedClaimWithMultiplePrioritizedListRequests, podWithClaimName), + assumedClaim: addAllocationTimestamp(reserve(allocatedClaimWithMultiplePrioritizedListRequests, podWithClaimName)), changes: change{ claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { if claim.Name == claimName { claim = claim.DeepCopy() claim.Finalizers = inUseClaimWithMultiplePrioritizedListRequests.Finalizers claim.Status = inUseClaimWithMultiplePrioritizedListRequests.Status + claim = addAllocationTimestamp(claim) + } + return claim + }, + }, + }, + }, + }, + "node-allocatable-resource-multiplier": { + enableDRANodeAllocatableResources: true, + nodes: []*v1.Node{workerNodeWithCapacity}, + pod: podWithClaimReferenceInContainer, + patchTestCase: func(tc *testPluginCase) { + // In a real scheduling cycle, the Assume phase pre-populates the Pod's + // NodeAllocatableResourceClaimStatuses in the cache before PreBind runs. + // Because this plugin unit test skips the Assume phase, we must manually + // inject the expected status into the mock Pod to satisfy the equality check + // in patchNodeAllocatableResourceClaimStatus. + tc.pod = tc.pod.DeepCopy() + tc.pod.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: claimName, + Containers: []string{tc.pod.Spec.Containers[0].Name}, + Resources: map[v1.ResourceName]apiresource.Quantity{ + v1.ResourceCPU: apiresource.MustParse("1"), + v1.ResourceMemory: apiresource.MustParse("1Gi"), + }, + }, + } + }, + claims: []*resourceapi.ResourceClaim{pendingClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: func() []apiruntime.Object { + slice := st.MakeResourceSlice(nodeName, driver).Device("instance-1").Obj() + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: {AllocationMultiplier: ptr.To(apiresource.MustParse("1"))}, + v1.ResourceMemory: {AllocationMultiplier: ptr.To(apiresource.MustParse("1Gi"))}, + } + return []apiruntime.Object{slice, podWithClaimReferenceInContainer} + }(), + want: want{ + filter: perNodeResult{ + workerNodeWithCapacity.Name: {status: nil}, // Expect Success + }, + reserve: result{ + inFlightClaims: []metav1.Object{allocatedClaim}, + }, + prebind: result{ + assumedClaim: addAllocationTimestamp(reserve(allocatedClaim, podWithClaimReferenceInContainer)), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Status.Allocation = allocationResult + claim.Status.ReservedFor = inUseClaim.Status.ReservedFor + claim.Finalizers = allocatedClaim.Finalizers + claim = addAllocationTimestamp(claim) + } + return claim + }, + pod: func(pod *v1.Pod) *v1.Pod { + if pod.Name == podName { + p := pod.DeepCopy() + p.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: claimName, + Containers: []string{podWithClaimReferenceInContainer.Spec.Containers[0].Name}, + Resources: map[v1.ResourceName]apiresource.Quantity{ + v1.ResourceCPU: apiresource.MustParse("1"), + v1.ResourceMemory: apiresource.MustParse("1Gi"), + }, + }, + } + return p + } + return pod + }, + }, + }, + }, + }, + "node-allocatable-resource-capacitykey": { + enableDRANodeAllocatableResources: true, + enableDRAConsumableCapacity: true, + nodes: []*v1.Node{workerNodeWithCapacity}, + pod: podWithClaimReferenceInContainer, + patchTestCase: func(tc *testPluginCase) { + // Simulate the Assume phase by pre-populating the status + tc.pod = tc.pod.DeepCopy() + tc.pod.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: claimName, + Containers: []string{tc.pod.Spec.Containers[0].Name}, + Resources: map[v1.ResourceName]apiresource.Quantity{ + v1.ResourceCPU: apiresource.MustParse("1"), + }, + }, + } + }, + claims: []*resourceapi.ResourceClaim{st.FromResourceClaim(nodeAllocatableClaimWithCapacity).OwnerReference(podName, podUID, podKind).Obj()}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSliceWithNodeAllocatableResource(), podWithClaimReferenceInContainer}, + want: want{ + filter: perNodeResult{ + workerNodeWithCapacity.Name: {status: nil}, + }, + reserve: result{ + inFlightClaims: []metav1.Object{ + st.FromResourceClaim(pendingNodeAllocatableClaimWithCapacity). + Allocation(nodeAllocatableResAllocationResult). + Obj(), + }, + }, + prebind: result{ + assumedClaim: addAllocationTimestamp(st.FromResourceClaim(pendingNodeAllocatableClaimWithCapacity). + Allocation(nodeAllocatableResAllocationResult). + ReservedForPod(podName, types.UID(podUID)). + Obj()), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + c := claim.DeepCopy() + c.Finalizers = []string{resourceapi.Finalizer} + alloc := nodeAllocatableResAllocationResult.DeepCopy() + c.Status.Allocation = alloc + c.Status.ReservedFor = []resourceapi.ResourceClaimConsumerReference{{Resource: "pods", Name: podName, UID: types.UID(podUID)}} + c = addAllocationTimestamp(c) + return c + } + return claim + }, + pod: func(pod *v1.Pod) *v1.Pod { + if pod.Name == podName { + p := pod.DeepCopy() + p.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: claimName, + Containers: []string{podWithClaimReferenceInContainer.Spec.Containers[0].Name}, + Resources: map[v1.ResourceName]apiresource.Quantity{ + v1.ResourceCPU: apiresource.MustParse("1"), + }, + }, + } + return p + } + return pod + }, + }, + }, + }, + }, + "node-allocatable-resource-capacitykey-and-multiplier": { + enableDRANodeAllocatableResources: true, + enableDRAConsumableCapacity: true, + nodes: []*v1.Node{workerNodeWithCapacity}, + pod: podWithClaimReferenceInContainer, + patchTestCase: func(tc *testPluginCase) { + // Simulate the Assume phase by pre-populating the status + tc.pod = tc.pod.DeepCopy() + tc.pod.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: claimName, + Containers: []string{tc.pod.Spec.Containers[0].Name}, + Resources: map[v1.ResourceName]apiresource.Quantity{ + v1.ResourceCPU: apiresource.MustParse("1"), + v1.ResourceMemory: apiresource.MustParse("1Gi"), + }, + }, + } + }, + claims: []*resourceapi.ResourceClaim{st.FromResourceClaim(nodeAllocatableClaimWithCapacity).OwnerReference(podName, podUID, podKind).Obj()}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: func() []apiruntime.Object { + slice := workerNodeSliceWithNodeAllocatableResource() + slice.Spec.Devices[0].NodeAllocatableResourceMappings[v1.ResourceMemory] = resourceapi.NodeAllocatableResourceMapping{ + AllocationMultiplier: ptr.To(apiresource.MustParse("1Gi")), + } + return []apiruntime.Object{slice, podWithClaimReferenceInContainer} + }(), + want: want{ + filter: perNodeResult{ + workerNodeWithCapacity.Name: {status: nil}, + }, + reserve: result{ + inFlightClaims: []metav1.Object{ + st.FromResourceClaim(pendingNodeAllocatableClaimWithCapacity). + Allocation(nodeAllocatableResAllocationResult). + Obj(), + }, + }, + prebind: result{ + assumedClaim: addAllocationTimestamp(st.FromResourceClaim(pendingNodeAllocatableClaimWithCapacity). + Allocation(nodeAllocatableResAllocationResult). + ReservedForPod(podName, types.UID(podUID)). + Obj()), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + c := claim.DeepCopy() + c.Finalizers = []string{resourceapi.Finalizer} + alloc := nodeAllocatableResAllocationResult.DeepCopy() + c.Status.Allocation = alloc + c.Status.ReservedFor = []resourceapi.ResourceClaimConsumerReference{{Resource: "pods", Name: podName, UID: types.UID(podUID)}} + c = addAllocationTimestamp(c) + return c } return claim }, + pod: func(pod *v1.Pod) *v1.Pod { + if pod.Name == podName { + p := pod.DeepCopy() + p.Status.NodeAllocatableResourceClaimStatuses = []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: claimName, + Containers: []string{podWithClaimReferenceInContainer.Spec.Containers[0].Name}, + Resources: map[v1.ResourceName]apiresource.Quantity{ + v1.ResourceCPU: apiresource.MustParse("1"), + v1.ResourceMemory: apiresource.MustParse("1Gi"), + }, + }, + } + return p + } + return pod + }, }, }, }, }, + "node-allocatable-resource-insufficient-resources-with-multiplier": { + enableDRANodeAllocatableResources: true, + nodes: []*v1.Node{workerNodeWithCapacity}, + pod: podWithClaimReferenceInContainer, + claims: []*resourceapi.ResourceClaim{pendingClaim}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: func() []apiruntime.Object { + slice := st.MakeResourceSlice(nodeName, driver).Device("instance-1").Obj() + slice.Spec.Devices[0].NodeAllocatableResourceMappings = map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: {AllocationMultiplier: ptr.To(apiresource.MustParse("11"))}, // Exceeds node capacity of 10 + } + return []apiruntime.Object{slice, podWithClaimReferenceInContainer} + }(), + want: want{ + filter: perNodeResult{ + workerNodeWithCapacity.Name: {status: fwk.NewStatus(fwk.Unschedulable, `Insufficient cpu`)}, + }, + postfilter: result{ + status: fwk.NewStatus(fwk.Unschedulable, "still not schedulable"), + }, + }, + }, + "node-allocatable-resource-insufficient-with-capacityKey": { + enableDRANodeAllocatableResources: true, + enableDRAConsumableCapacity: true, + nodes: []*v1.Node{workerNodeWithCapacity}, + pod: podWithClaimReferenceInContainer, + claims: func() []*resourceapi.ResourceClaim { + claim := nodeAllocatableClaimWithCapacity.DeepCopy() + claim.Spec.Devices.Requests[0].Exactly.Capacity.Requests[nodeAllocatableResourceCapacityName] = apiresource.MustParse("11") // Exceeds node capacity of 10 + return []*resourceapi.ResourceClaim{st.FromResourceClaim(claim).OwnerReference(podName, podUID, podKind).Obj()} + }(), + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSliceWithNodeAllocatableResource(), podWithClaimReferenceInContainer}, + want: want{ + filter: perNodeResult{ + workerNodeWithCapacity.Name: {status: fwk.NewStatus(fwk.Unschedulable, `cannot allocate all claims`)}, + }, + postfilter: result{ + status: fwk.NewStatus(fwk.Unschedulable, "still not schedulable"), + }, + }, + }, } - for name, tc := range testcases { if len(tc.skipOnWindows) > 0 && goruntime.GOOS == "windows" { - t.Skipf("Skipping '%s' test case on Windows, reason: %s", name, tc.skipOnWindows) + tCtx.Skipf("Skipping '%s' test case on Windows, reason: %s", name, tc.skipOnWindows) } - tc := tc - t.Run(name, func(t *testing.T) { + tCtx.Run(name, func(tCtx ktesting.TContext) { + if tc.patchTestCase != nil { + tc.patchTestCase(&tc) + } + nodes := tc.nodes if nodes == nil { nodes = []*v1.Node{workerNode} } feats := feature.Features{ - EnableDRAAdminAccess: tc.enableDRAAdminAccess, - EnableDRADeviceBindingConditions: tc.enableDRADeviceBindingConditions, - EnableDRAResourceClaimDeviceStatus: tc.enableDRAResourceClaimDeviceStatus, + EnableDRAAdminAccess: !tc.disableDRAAdminAccess, + EnableDRADeviceBindingConditions: !tc.disableDRADeviceBindingConditions, + EnableDRAResourceClaimDeviceStatus: !tc.disableDRAResourceClaimDeviceStatus, EnableDRADeviceTaints: tc.enableDRADeviceTaints, EnableDRASchedulerFilterTimeout: !tc.disableDRASchedulerFilterTimeout, EnableDynamicResourceAllocation: !tc.disableDRA, EnableDRAPrioritizedList: tc.enableDRAPrioritizedList, EnableDRAExtendedResource: tc.enableDRAExtendedResource, + EnableDRANodeAllocatableResources: tc.enableDRANodeAllocatableResources, + EnableDRAConsumableCapacity: tc.enableDRAConsumableCapacity, + EnableDRAWorkloadResourceClaims: tc.enableDRAWorkloadResourceClaims, + } + + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.enableDRAExtendedResource) + if tc.disableDRAAdminAccess { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(tCtx, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, false) + + require.False(tCtx, tc.enableDRAWorkloadResourceClaims, "DRAWorkloadResourceClaims cannot be enabled when DRAAdminAccess is disabled") + } else { + // These features can't be set with pre-1.36 emulation + featuregatetesting.SetFeatureGatesDuringTest(tCtx, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.DRAWorkloadResourceClaims: tc.enableDRAWorkloadResourceClaims, + features.GenericWorkload: tc.enableDRAWorkloadResourceClaims, // dependency of DRAWorkloadResourceClaims + }) + } + if tc.disableDRADeviceBindingConditions { + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRADeviceBindingConditions, false) + } + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.enableDRAExtendedResource) + testCtx := setup(tCtx, tc.args, nodes, tc.claims, tc.classes, tc.podGroups, tc.objs, feats, tc.failPatch, tc.reactors) + for _, claim := range tc.inFlightClaims { + tCtx.ExpectNoError(testCtx.draManager.ResourceClaims().SignalClaimPendingAllocation(claim.UID, claim)) } - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.enableDRAExtendedResource) - testCtx := setup(t, tc.args, nodes, tc.claims, tc.classes, tc.objs, feats, tc.failPatch, tc.reactors) - initialObjects := testCtx.listAll(t) + initialObjects := testCtx.listAll(tCtx) var registry compbasemetrics.KubeRegistry if tc.metrics != nil { registry = setupMetrics(feats) } - status := testCtx.p.PreEnqueue(testCtx.ctx, tc.pod) - t.Run("PreEnqueue", func(t *testing.T) { - testCtx.verify(t, tc.want.preenqueue, initialObjects, nil, status) + status := testCtx.p.PreEnqueue(tCtx, tc.pod) + tCtx.Run("PreEnqueue", func(tCtx ktesting.TContext) { + testCtx.verify(tCtx, tc.want.preenqueue, initialObjects, tc.pod, nil, status) }) if !status.IsSuccess() { return } nodeInfo := framework.NewNodeInfo() - result, status := testCtx.p.PreFilter(testCtx.ctx, testCtx.state, tc.pod, []fwk.NodeInfo{nodeInfo}) - t.Run("prefilter", func(t *testing.T) { - assert.Equal(t, tc.want.preFilterResult, result) - testCtx.verify(t, tc.want.prefilter, initialObjects, result, status) + result, status := testCtx.p.PreFilter(tCtx, testCtx.state, tc.pod, []fwk.NodeInfo{nodeInfo}) + tCtx.Run("prefilter", func(tCtx ktesting.TContext) { + assert.Equal(tCtx, tc.want.preFilterResult, result) + testCtx.verify(tCtx, tc.want.prefilter, initialObjects, tc.pod, result, status) }) unschedulable := status.IsRejected() var potentialNodes []fwk.NodeInfo - initialObjects = testCtx.listAll(t) - testCtx.updateAPIServer(t, initialObjects, tc.prepare.filter) + initialObjects = testCtx.listAll(tCtx) + testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.filter) if !unschedulable { for _, nodeInfo := range testCtx.nodeInfos { - initialObjects = testCtx.listAll(t) - ctx := testCtx.ctx - if tc.cancelFilter { - c, cancel := context.WithCancelCause(ctx) - ctx = c - cancel(errors.New("test canceling Filter")) - } - status := testCtx.p.Filter(ctx, testCtx.state, tc.pod, nodeInfo) - nodeName := nodeInfo.Node().Name - t.Run(fmt.Sprintf("filter/%s", nodeInfo.Node().Name), func(t *testing.T) { - testCtx.verify(t, tc.want.filter.forNode(nodeName), initialObjects, nil, status) + var status *fwk.Status + tCtx.Run(fmt.Sprintf("filter/%s", nodeInfo.Node().Name), func(tCtx ktesting.TContext) { + initialObjects = testCtx.listAll(tCtx) + ctx := context.Context(tCtx) + if tc.cancelFilter { + c, cancel := context.WithCancelCause(ctx) + ctx = c + cancel(errors.New("test canceling Filter")) + } + status = testCtx.p.Filter(ctx, testCtx.state, tc.pod, nodeInfo) + nodeName := nodeInfo.Node().Name + testCtx.verify(tCtx, tc.want.filter.forNode(nodeName), initialObjects, tc.pod, nil, status) }) if status.Code() == fwk.Success { potentialNodes = append(potentialNodes, nodeInfo) @@ -2517,25 +3579,25 @@ func TestPlugin(t *testing.T) { var scores fwk.NodeScoreList if !unschedulable && len(potentialNodes) > 1 { - initialObjects = testCtx.listAll(t) - initialObjects = testCtx.updateAPIServer(t, initialObjects, tc.prepare.prescore) + initialObjects = testCtx.listAll(tCtx) + initialObjects = testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.prescore) for _, potentialNode := range potentialNodes { - initialObjects = testCtx.listAll(t) - score, status := testCtx.p.Score(testCtx.ctx, testCtx.state, tc.pod, potentialNode) + initialObjects = testCtx.listAll(tCtx) + score, status := testCtx.p.Score(tCtx, testCtx.state, tc.pod, potentialNode) nodeName := potentialNode.Node().Name - t.Run(fmt.Sprintf("score/%s", nodeName), func(t *testing.T) { - assert.Equal(t, tc.want.scoreResult.forNode(nodeName), score) - testCtx.verify(t, tc.want.score.forNode(nodeName), initialObjects, nil, status) + tCtx.Run(fmt.Sprintf("score/%s", nodeName), func(tCtx ktesting.TContext) { + assert.Equal(tCtx, tc.want.scoreResult.forNode(nodeName), score) + testCtx.verify(tCtx, tc.want.score.forNode(nodeName), initialObjects, tc.pod, nil, status) }) scores = append(scores, fwk.NodeScore{Name: nodeName, Score: score}) } - initialObjects = testCtx.listAll(t) - status := testCtx.p.NormalizeScore(testCtx.ctx, testCtx.state, tc.pod, scores) - t.Run("normalizeScore", func(t *testing.T) { - assert.Equal(t, tc.want.normalizeScoreResult, scores) - testCtx.verify(t, tc.want.normalizeScore, initialObjects, nil, status) + initialObjects = testCtx.listAll(tCtx) + status := testCtx.p.NormalizeScore(tCtx, testCtx.state, tc.pod, scores) + tCtx.Run("normalizeScore", func(tCtx ktesting.TContext) { + assert.Equal(tCtx, tc.want.normalizeScoreResult, scores) + testCtx.verify(tCtx, tc.want.normalizeScore, initialObjects, tc.pod, nil, status) }) } @@ -2553,11 +3615,11 @@ func TestPlugin(t *testing.T) { selectedNodeName = potentialNodes[0].Node().Name } - initialObjects = testCtx.listAll(t) - initialObjects = testCtx.updateAPIServer(t, initialObjects, tc.prepare.reserve) - status := testCtx.p.Reserve(testCtx.ctx, testCtx.state, tc.pod, selectedNodeName) - t.Run("reserve", func(t *testing.T) { - testCtx.verify(t, tc.want.reserve, initialObjects, nil, status) + initialObjects = testCtx.listAll(tCtx) + initialObjects = testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.reserve) + status := testCtx.p.Reserve(tCtx, testCtx.state, tc.pod, selectedNodeName) + tCtx.Run("reserve", func(tCtx ktesting.TContext) { + testCtx.verify(tCtx, tc.want.reserve, initialObjects, tc.pod, nil, status) }) if status.Code() != fwk.Success { unschedulable = true @@ -2566,54 +3628,57 @@ func TestPlugin(t *testing.T) { if selectedNodeName != "" { if unschedulable { - initialObjects = testCtx.listAll(t) - initialObjects = testCtx.updateAPIServer(t, initialObjects, tc.prepare.unreserve) - testCtx.p.Unreserve(testCtx.ctx, testCtx.state, tc.pod, selectedNodeName) - t.Run("unreserve", func(t *testing.T) { - testCtx.verify(t, tc.want.unreserve, initialObjects, nil, status) + initialObjects = testCtx.listAll(tCtx) + initialObjects = testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.unreserve) + testCtx.p.Unreserve(tCtx, testCtx.state, tc.pod, selectedNodeName) + tCtx.Run("unreserve", func(tCtx ktesting.TContext) { + testCtx.verify(tCtx, tc.want.unreserve, initialObjects, tc.pod, nil, status) }) } else { if tc.want.unreserveBeforePreBind != nil { - initialObjects = testCtx.listAll(t) - testCtx.p.Unreserve(testCtx.ctx, testCtx.state, tc.pod, selectedNodeName) - t.Run("unreserveBeforePreBind", func(t *testing.T) { - testCtx.verify(t, *tc.want.unreserveBeforePreBind, initialObjects, nil, status) + initialObjects = testCtx.listAll(tCtx) + testCtx.p.Unreserve(tCtx, testCtx.state, tc.pod, selectedNodeName) + tCtx.Run("unreserveBeforePreBind", func(tCtx ktesting.TContext) { + testCtx.verify(tCtx, *tc.want.unreserveBeforePreBind, initialObjects, tc.pod, nil, status) }) return } - initialObjects = testCtx.listAll(t) - initialObjects = testCtx.updateAPIServer(t, initialObjects, tc.prepare.prebind) - preBindPreFlightStatus := testCtx.p.PreBindPreFlight(testCtx.ctx, testCtx.state, tc.pod, selectedNodeName) - t.Run("prebindPreFlight", func(t *testing.T) { - assert.Equal(t, tc.want.prebindPreFlight, preBindPreFlightStatus) + initialObjects = testCtx.listAll(tCtx) + initialObjects = testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.prebind) + preBindPreFlightResult, preBindPreFlightStatus := testCtx.p.PreBindPreFlight(tCtx, testCtx.state, tc.pod, selectedNodeName) + tCtx.Run("preBindPreFlightStatus", func(tContext ktesting.TContext) { + assert.Equal(tCtx, tc.want.preBindPreFlightStatus, preBindPreFlightStatus) + }) + tCtx.Run("preBindPreFlightResult", func(tContext ktesting.TContext) { + assert.Equal(tCtx, &fwk.PreBindPreFlightResult{AllowParallel: true}, preBindPreFlightResult) }) - preBindStatus := testCtx.p.PreBind(testCtx.ctx, testCtx.state, tc.pod, selectedNodeName) - t.Run("prebind", func(t *testing.T) { - testCtx.verify(t, tc.want.prebind, initialObjects, nil, preBindStatus) + preBindStatus := testCtx.p.PreBind(tCtx, testCtx.state, tc.pod, selectedNodeName) + tCtx.Run("prebind", func(tCtx ktesting.TContext) { + testCtx.verify(tCtx, tc.want.prebind, initialObjects, tc.pod, nil, preBindStatus) }) if tc.want.unreserveAfterBindFailure != nil { - initialObjects = testCtx.listAll(t) - testCtx.p.Unreserve(testCtx.ctx, testCtx.state, tc.pod, selectedNodeName) - t.Run("unreserverAfterBindFailure", func(t *testing.T) { - testCtx.verify(t, *tc.want.unreserveAfterBindFailure, initialObjects, nil, status) + initialObjects = testCtx.listAll(tCtx) + testCtx.p.Unreserve(tCtx, testCtx.state, tc.pod, selectedNodeName) + tCtx.Run("unreserverAfterBindFailure", func(tCtx ktesting.TContext) { + testCtx.verify(tCtx, *tc.want.unreserveAfterBindFailure, initialObjects, tc.pod, nil, status) }) } else if status.IsSuccess() { - initialObjects = testCtx.listAll(t) - initialObjects = testCtx.updateAPIServer(t, initialObjects, tc.prepare.postbind) + initialObjects = testCtx.listAll(tCtx) + initialObjects = testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.postbind) } } } else if len(potentialNodes) == 0 { - initialObjects = testCtx.listAll(t) - initialObjects = testCtx.updateAPIServer(t, initialObjects, tc.prepare.postfilter) - result, status := testCtx.p.PostFilter(testCtx.ctx, testCtx.state, tc.pod, nil /* filteredNodeStatusMap not used by plugin */) - t.Run("postfilter", func(t *testing.T) { - assert.Equal(t, tc.want.postFilterResult, result) - testCtx.verify(t, tc.want.postfilter, initialObjects, nil, status) + initialObjects = testCtx.listAll(tCtx) + initialObjects = testCtx.updateAPIServer(tCtx, initialObjects, tc.prepare.postfilter) + result, status := testCtx.p.PostFilter(tCtx, testCtx.state, tc.pod, nil /* filteredNodeStatusMap not used by plugin */) + tCtx.Run("postfilter", func(tCtx ktesting.TContext) { + assert.Equal(tCtx, tc.want.postFilterResult, result) + testCtx.verify(tCtx, tc.want.postfilter, initialObjects, tc.pod, nil, status) }) } if tc.metrics != nil { - tc.metrics(t, registry) + tc.metrics(tCtx, registry) } }) } @@ -2627,31 +3692,39 @@ func setupMetrics(features feature.Features) compbasemetrics.KubeRegistry { testRegistry.MustRegister(metrics.ResourceClaimCreatesTotal) metrics.ResourceClaimCreatesTotal.Reset() } + // DRA DeviceBindingConditions metrics. + if features.EnableDRADeviceBindingConditions { + testRegistry.MustRegister(metrics.DRABindingConditionsAllocationsTotal) + testRegistry.MustRegister(metrics.DRABindingConditionsPreBindDuration) + + metrics.DRABindingConditionsAllocationsTotal.Reset() + metrics.DRABindingConditionsPreBindDuration.Reset() + } return testRegistry } type testContext struct { - ctx context.Context client *fake.Clientset informerFactory informers.SharedInformerFactory draManager *DefaultDRAManager + podGroupManager internalcache.Cache p *DynamicResources nodeInfos []fwk.NodeInfo state fwk.CycleState } -func (tc *testContext) verify(t *testing.T, expected result, initialObjects []metav1.Object, result interface{}, status *fwk.Status) { - t.Helper() +func (tc *testContext) verify(tCtx ktesting.TContext, expected result, initialObjects []metav1.Object, testPod *v1.Pod, result interface{}, status *fwk.Status) { + tCtx.Helper() if expected.status == nil { - assert.Nil(t, status) + assert.Nil(tCtx, status) } else if actualErr := status.AsError(); actualErr != nil { // Compare only the error strings. - assert.ErrorContains(t, actualErr, expected.status.AsError().Error()) + assert.ErrorContains(tCtx, actualErr, expected.status.AsError().Error()) } else { - assert.Equal(t, expected.status, status) + assert.Equal(tCtx, expected.status, status) } - objects := tc.listAll(t) - wantObjects := update(t, initialObjects, expected.changes) + objects := tc.listAll(tCtx) + wantObjects := update(initialObjects, expected.changes) wantObjects = append(wantObjects, expected.added...) for _, remove := range expected.removed { for i, obj := range wantObjects { @@ -2671,15 +3744,65 @@ func (tc *testContext) verify(t *testing.T, expected result, initialObjects []me objects = []metav1.Object{} } + // Separate objects by type for easier comparison + var wantClaims, actualClaims []metav1.Object + var wantPods, actualPods []metav1.Object + + for _, obj := range wantObjects { + switch obj.(type) { + case *resourceapi.ResourceClaim: + wantClaims = append(wantClaims, obj) + case *v1.Pod: + wantPods = append(wantPods, obj) + } + } + + for _, obj := range objects { + switch obj.(type) { + case *resourceapi.ResourceClaim: + actualClaims = append(actualClaims, obj) + case *v1.Pod: + actualPods = append(actualPods, obj) + } + } + + if diff := cmp.Diff(wantPods, actualPods); diff != "" { + tCtx.Errorf("Stored objects are different (- expected, + actual):\n%s", diff) + } + // Sometimes assert strips the diff too much, let's do it ourselves... ignoreFieldsInResourceClaims := []cmp.Option{ cmpopts.IgnoreFields(metav1.ObjectMeta{}, "UID", "ResourceVersion"), - cmpopts.IgnoreFields(resourceapi.AllocationResult{}, "AllocationTimestamp"), + cmp.Transformer("AllocationTimestamp", func(result resourceapi.AllocationResult) resourceapi.AllocationResult { + // Replace all allocation timestamps with the empty timestamp before comparison + // because the actual value is unpredictable (not running in a synctest bubble). + if result.AllocationTimestamp != nil { + result.AllocationTimestamp = new(metav1.Time) + } + return result + }), // It does not matter which specific device is allocated for the testing purpose. cmpopts.IgnoreFields(resourceapi.DeviceRequestAllocationResult{}, "Device"), + // ShareID is dynamically generated by the allocator for shared devices, so it cannot set in expected results. + cmpopts.IgnoreFields(resourceapi.DeviceRequestAllocationResult{}, "ShareID"), + } + if diff := cmp.Diff(wantClaims, actualClaims, ignoreFieldsInResourceClaims...); diff != "" { + tCtx.Errorf("Stored objects are different (- expected, + actual):\n%s", diff) } - if diff := cmp.Diff(wantObjects, objects, ignoreFieldsInResourceClaims...); diff != "" { - t.Errorf("Stored objects are different (- expected, + actual):\n%s", diff) + + if expected.changes.pod != nil && testPod != nil { + wantPod := expected.changes.pod(testPod.DeepCopy()) + actualPod, err := tc.client.CoreV1().Pods(testPod.Namespace).Get(tCtx, testPod.Name, metav1.GetOptions{}) + if err != nil { + tCtx.Fatalf("Failed to get pod %s/%s: %v", testPod.Namespace, testPod.Name, err) + } + ignorePodFields := []cmp.Option{ + cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion", "UID", "CreationTimestamp", "ManagedFields"), + cmpopts.EquateEmpty(), + } + if diff := cmp.Diff(wantPod, actualPod, ignorePodFields...); diff != "" { + tCtx.Errorf("Stored Pod %s/%s is different (- expected, + actual):\n%s", testPod.Namespace, testPod.Name, diff) + } } var expectAssumedClaims []metav1.Object @@ -2694,13 +3817,13 @@ func (tc *testContext) verify(t *testing.T, expected result, initialObjects []me if len(expectAssumedClaims) == 0 && len(actualAssumedClaims) != 0 { // In case we delete the claim API object, wait for assumed cache to sync with informer, // then assumed cache should be empty. - err := wait.PollUntilContextTimeout(tc.ctx, 200*time.Millisecond, time.Minute, true, + err := wait.PollUntilContextTimeout(tCtx, 200*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { actualAssumedClaims, sameAssumedClaims = tc.listAssumedClaims() return len(actualAssumedClaims) == 0, nil }) if err != nil || len(actualAssumedClaims) != 0 { - t.Errorf("Assumed claims are different, err=%v, expected: nil, actual:\n%v", err, actualAssumedClaims) + tCtx.Errorf("Assumed claims are different, err=%v, expected: nil, actual:\n%v", err, actualAssumedClaims) } } if len(expectAssumedClaims) > 0 { @@ -2719,23 +3842,22 @@ func (tc *testContext) verify(t *testing.T, expected result, initialObjects []me } } if !seen { - t.Errorf("Assumed claims are different, expected: %v not found", expected) + tCtx.Errorf("Assumed claims are different, expected: %v not found", expected) } } } actualInFlightClaims := tc.listInFlightClaims() if diff := cmp.Diff(expected.inFlightClaims, actualInFlightClaims, ignoreFieldsInResourceClaims...); diff != "" { - t.Errorf("In-flight claims are different (- expected, + actual):\n%s", diff) + tCtx.Errorf("In-flight claims are different (- expected, + actual):\n%s", diff) } } -func (tc *testContext) listAll(t *testing.T) (objects []metav1.Object) { - t.Helper() - claims, err := tc.client.ResourceV1().ResourceClaims("").List(tc.ctx, metav1.ListOptions{}) - require.NoError(t, err, "list claims") +func (tc *testContext) listAll(tCtx ktesting.TContext) (objects []metav1.Object) { + tCtx.Helper() + claims, err := tc.client.ResourceV1().ResourceClaims("").List(tCtx, metav1.ListOptions{}) + tCtx.ExpectNoError(err, "list claims") for _, claim := range claims.Items { - claim := claim objects = append(objects, &claim) } sortObjects(objects) @@ -2762,30 +3884,31 @@ func (tc *testContext) listAssumedClaims() ([]metav1.Object, []metav1.Object) { func (tc *testContext) listInFlightClaims() []metav1.Object { var inFlightClaims []metav1.Object - tc.draManager.resourceClaimTracker.inFlightAllocations.Range(func(key, value any) bool { - inFlightClaims = append(inFlightClaims, value.(*resourceapi.ResourceClaim)) - return true - }) + for _, inFlight := range tc.draManager.resourceClaimTracker.allInFlightAllocationsRLocked() { + inFlightClaims = append(inFlightClaims, inFlight.claim) + } sortObjects(inFlightClaims) return inFlightClaims } // updateAPIServer modifies objects and stores any changed object in the API server. -func (tc *testContext) updateAPIServer(t *testing.T, objects []metav1.Object, updates change) []metav1.Object { - modified := update(t, objects, updates) +func (tc *testContext) updateAPIServer(tCtx ktesting.TContext, objects []metav1.Object, updates change) []metav1.Object { + modified := update(objects, updates) for i := range modified { obj := modified[i] if diff := cmp.Diff(objects[i], obj); diff != "" { - t.Logf("Updating %T %q, diff (-old, +new):\n%s", obj, obj.GetName(), diff) + tCtx.Logf("Updating %T %q, diff (-old, +new):\n%s", obj, obj.GetName(), diff) switch obj := obj.(type) { case *resourceapi.ResourceClaim: - obj, err := tc.client.ResourceV1().ResourceClaims(obj.Namespace).Update(tc.ctx, obj, metav1.UpdateOptions{}) - if err != nil { - t.Fatalf("unexpected error during prepare update: %v", err) - } + obj, err := tc.client.ResourceV1().ResourceClaims(obj.Namespace).Update(tCtx, obj, metav1.UpdateOptions{}) + tCtx.ExpectNoError(err, "prepare update") + modified[i] = obj + case *v1.Pod: + obj, err := tc.client.CoreV1().Pods(obj.Namespace).Update(tCtx, obj, metav1.UpdateOptions{}) + tCtx.ExpectNoError(err, "prepare update") modified[i] = obj default: - t.Fatalf("unsupported object type %T", obj) + tCtx.Fatalf("unsupported object type %T", obj) } } } @@ -2804,7 +3927,7 @@ func sortObjects(objects []metav1.Object) { // update walks through all existing objects, finds the corresponding update // function based on name and kind, and replaces those objects that have an // update function. The rest is left unchanged. -func update(t *testing.T, objects []metav1.Object, updates change) []metav1.Object { +func update(objects []metav1.Object, updates change) []metav1.Object { var updated []metav1.Object for _, obj := range objects { @@ -2813,6 +3936,10 @@ func update(t *testing.T, objects []metav1.Object, updates change) []metav1.Obje if updates.claim != nil { obj = updates.claim(in) } + case *v1.Pod: + if updates.pod != nil { + obj = updates.pod(in) + } } updated = append(updated, obj) } @@ -2820,13 +3947,10 @@ func update(t *testing.T, objects []metav1.Object, updates change) []metav1.Obje return updated } -func setup(t *testing.T, args *config.DynamicResourcesArgs, nodes []*v1.Node, claims []*resourceapi.ResourceClaim, classes []*resourceapi.DeviceClass, objs []apiruntime.Object, features feature.Features, failPatch bool, apiReactors []cgotesting.Reactor) (result *testContext) { - t.Helper() +func setup(tCtx ktesting.TContext, args *config.DynamicResourcesArgs, nodes []*v1.Node, claims []*resourceapi.ResourceClaim, classes []*resourceapi.DeviceClass, podGroups []*schedulingapi.PodGroup, objs []apiruntime.Object, features feature.Features, failPatch bool, apiReactors []cgotesting.Reactor) (result *testContext) { + tCtx.Helper() tc := &testContext{} - tCtx := ktesting.Init(t) - tc.ctx = tCtx - tc.client = fake.NewSimpleClientset(objs...) reactor := createReactor(tc.client.Tracker(), failPatch) tc.client.PrependReactor("*", "*", reactor) @@ -2834,15 +3958,17 @@ func setup(t *testing.T, args *config.DynamicResourcesArgs, nodes []*v1.Node, cl tc.client.ReactionChain = append(apiReactors, tc.client.ReactionChain...) tc.informerFactory = informers.NewSharedInformerFactory(tc.client, 0) + var doneCheckers []cache.DoneChecker resourceSliceTrackerOpts := resourceslicetracker.Options{ EnableDeviceTaintRules: true, SliceInformer: tc.informerFactory.Resource().V1().ResourceSlices(), - TaintInformer: tc.informerFactory.Resource().V1alpha3().DeviceTaintRules(), + TaintInformer: tc.informerFactory.Resource().V1beta2().DeviceTaintRules(), ClassInformer: tc.informerFactory.Resource().V1().DeviceClasses(), KubeClient: tc.client, } resourceSliceTracker, err := resourceslicetracker.StartTracker(tCtx, resourceSliceTrackerOpts) - require.NoError(t, err, "couldn't start resource slice tracker") + require.NoError(tCtx, err, "couldn't start resource slice tracker") + doneCheckers = append(doneCheckers, resourceSliceTracker.HasSyncedChecker()) claimsCache := assumecache.NewAssumeCache(tCtx.Logger(), tc.informerFactory.Resource().V1().ResourceClaims().Informer(), "resource claim", "", nil) // NewAssumeCache calls the informer's AddEventHandler method to register @@ -2855,13 +3981,20 @@ func setup(t *testing.T, args *config.DynamicResourcesArgs, nodes []*v1.Node, cl // This is not the registered handler that is used by the DRA // manager, but it is close enough because the assume cache // uses a single boolean for "is synced" for all handlers. - registeredHandler := claimsCache.AddEventHandler(cache.ResourceEventHandlerFuncs{}) + doneCheckers = append(doneCheckers, claimsCache.AddEventHandler(cache.ResourceEventHandlerFuncs{}).HasSyncedChecker()) tc.draManager = NewDRAManager(tCtx, claimsCache, resourceSliceTracker, tc.informerFactory) if features.EnableDRAExtendedResource { cache := tc.draManager.DeviceClassResolver().(*extendedresourcecache.ExtendedResourceCache) - if _, err := tc.informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(cache); err != nil { - tCtx.Logger().Error(err, "failed to add device class informer event handler") + deviceClassHandlerRegistration, err := tc.informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(cache) + require.NoError(tCtx, err, "failed to add device class informer event handler") + doneCheckers = append(doneCheckers, deviceClassHandlerRegistration.HasSyncedChecker()) + } + + tc.podGroupManager = internalcache.New(tCtx, nil, true) + for _, obj := range objs { + if pod, ok := obj.(*v1.Pod); ok { + tc.podGroupManager.AddPodGroupMember(pod) } } @@ -2870,46 +4003,51 @@ func setup(t *testing.T, args *config.DynamicResourcesArgs, nodes []*v1.Node, cl runtime.WithInformerFactory(tc.informerFactory), runtime.WithEventRecorder(&events.FakeRecorder{}), runtime.WithSharedDRAManager(tc.draManager), + runtime.WithPodGroupManager(tc.podGroupManager), } fh, err := runtime.NewFramework(tCtx, nil, nil, opts...) - if err != nil { - t.Fatal(err) - } + tCtx.ExpectNoError(err, "create scheduler framework") + tCtx.Cleanup(func() { + tCtx.Cancel("test has completed") + runtime.WaitForShutdown(fh) + }) if args == nil { args = getDefaultDynamicResourcesArgs() } pl, err := New(tCtx, args, fh, features) - if err != nil { - t.Fatal(err) - } + tCtx.ExpectNoError(err, "create plugin") tc.p = pl.(*DynamicResources) // The tests use the API to create the objects because then reactors // get triggered. for _, claim := range claims { - _, err := tc.client.ResourceV1().ResourceClaims(claim.Namespace).Create(tc.ctx, claim, metav1.CreateOptions{}) - require.NoError(t, err, "create resource claim") + _, err := tc.client.ResourceV1().ResourceClaims(claim.Namespace).Create(tCtx, claim, metav1.CreateOptions{}) + tCtx.ExpectNoError(err, "create resource claim") } for _, class := range classes { - _, err := tc.client.ResourceV1().DeviceClasses().Create(tc.ctx, class, metav1.CreateOptions{}) - require.NoError(t, err, "create resource class") + _, err := tc.client.ResourceV1().DeviceClasses().Create(tCtx, class, metav1.CreateOptions{}) + tCtx.ExpectNoError(err, "create resource class") + } + for _, podGroup := range podGroups { + _, err := tc.client.SchedulingV1alpha2().PodGroups(podGroup.Namespace).Create(tCtx, podGroup, metav1.CreateOptions{}) + tCtx.ExpectNoError(err, "create pod group") } - tc.informerFactory.Start(tc.ctx.Done()) - t.Cleanup(func() { + tc.informerFactory.Start(tCtx.Done()) + tCtx.Cleanup(func() { // Need to cancel before waiting for the shutdown. tCtx.Cancel("test is done") // Now we can wait for all goroutines to stop. tc.informerFactory.Shutdown() }) - tc.informerFactory.WaitForCacheSync(tc.ctx.Done()) - // The above does not tell us if the registered handler (from NewAssumeCache) - // is synced, we need to wait until HasSynced of the handler returns - // true, this ensures that the assume cache is in sync with the informer's + tc.informerFactory.WaitForCacheSync(tCtx.Done()) + // The above does not tell us if the registered handlers (e.g. from NewAssumeCache) + // are synced, we need to wait until the event handlers confirm that they are synced. + // This ensures that the assume cache is in sync with the informer's // store which has been informed by at least one full LIST of the underlying storage. - cache.WaitForCacheSync(tc.ctx.Done(), registeredHandler.HasSynced, resourceSliceTracker.HasSynced) + cache.WaitFor(tCtx, "event handlers", doneCheckers...) for _, node := range nodes { nodeInfo := framework.NewNodeInfo() @@ -2929,8 +4067,11 @@ func createReactor(tracker cgotesting.ObjectTracker, failPatch bool) func(action var nameCounter int var uidCounter int var resourceVersionCounter int + inUseUIDs := sets.Set[types.UID]{} var mutex sync.Mutex + podGroupGVR := schedulingapi.SchemeGroupVersion.WithResource("podgroups") + return func(action cgotesting.Action) (handled bool, ret apiruntime.Object, err error) { if failPatch { if _, ok := action.(cgotesting.PatchAction); ok { @@ -2951,14 +4092,23 @@ func createReactor(tracker cgotesting.ObjectTracker, failPatch bool) func(action defer mutex.Unlock() switch action.GetVerb() { case "create": - if obj.GetUID() != "" { + // PodGroups are created before Pods and some tests need + // predetermined UIDs for those PodGroups to match a ResourceClaim's + // status.reservedFor list. + if obj.GetUID() != "" && action.GetResource() != podGroupGVR { return true, nil, errors.New("UID must not be set on create") } if obj.GetResourceVersion() != "" { return true, nil, errors.New("ResourceVersion must not be set on create") } - obj.SetUID(types.UID(fmt.Sprintf("UID-%d", uidCounter))) - uidCounter++ + if obj.GetUID() == "" { + obj.SetUID(types.UID(fmt.Sprintf("UID-%d", uidCounter))) + uidCounter++ + } + if inUseUIDs.Has(obj.GetUID()) { + return true, nil, errors.New("UID %s already in use") + } + inUseUIDs.Insert(obj.GetUID()) obj.SetResourceVersion(fmt.Sprintf("%d", resourceVersionCounter)) resourceVersionCounter++ if obj.GetName() == "" { @@ -2984,7 +4134,7 @@ func createReactor(tracker cgotesting.ObjectTracker, failPatch bool) func(action return true, nil, errors.New("internal error: unexpected old object type") } if oldObjMeta.GetResourceVersion() != resourceVersion { - return true, nil, errors.New("ResourceVersion must match the object that gets updated") + return true, nil, apierrors.NewConflict(action.GetResource().GroupResource(), obj.GetName(), errors.New("ResourceVersion must match the object that gets updated")) } obj.SetResourceVersion(fmt.Sprintf("%d", resourceVersionCounter)) @@ -2994,7 +4144,10 @@ func createReactor(tracker cgotesting.ObjectTracker, failPatch bool) func(action } } -func Test_isSchedulableAfterClaimChange(t *testing.T) { +func TestIsSchedulableAfterClaimChange(t *testing.T) { + testIsSchedulableAfterClaimChange(ktesting.Init(t)) +} +func testIsSchedulableAfterClaimChange(tCtx ktesting.TContext) { testcases := map[string]struct { pod *v1.Pod claims []*resourceapi.ResourceClaim @@ -3081,13 +4234,14 @@ func Test_isSchedulableAfterClaimChange(t *testing.T) { } for name, tc := range testcases { - t.Run(name, func(t *testing.T) { - logger, tCtx := ktesting.NewTestContext(t) + tCtx.SyncTest(name, func(tCtx ktesting.TContext) { features := feature.Features{ - EnableDRASchedulerFilterTimeout: true, - EnableDynamicResourceAllocation: true, + EnableDRASchedulerFilterTimeout: true, + EnableDynamicResourceAllocation: true, + EnableDRADeviceBindingConditions: true, + EnableDRAResourceClaimDeviceStatus: true, } - testCtx := setup(t, nil, nil, tc.claims, nil, nil, features, false, nil) + testCtx := setup(tCtx, nil, nil, tc.claims, nil, nil, nil, features, false, nil) oldObj := tc.oldObj newObj := tc.newObj if claim, ok := tc.newObj.(*resourceapi.ResourceClaim); ok { @@ -3099,13 +4253,13 @@ func Test_isSchedulableAfterClaimChange(t *testing.T) { createClaim.UID = "" storedClaim, err := testCtx.client.ResourceV1().ResourceClaims(createClaim.Namespace).Create(tCtx, createClaim, metav1.CreateOptions{}) if err != nil { - t.Fatalf("create claim: expected no error, got: %v", err) + tCtx.Fatalf("create claim: expected no error, got: %v", err) } claim = storedClaim } else { cachedClaim, err := testCtx.draManager.resourceClaimTracker.cache.Get(claimKey) if err != nil { - t.Fatalf("retrieve old claim: expected no error, got: %v", err) + tCtx.Fatalf("retrieve old claim: expected no error, got: %v", err) } updateClaim := claim.DeepCopy() // The test claim doesn't have those (generated dynamically), so copy them. @@ -3114,44 +4268,46 @@ func Test_isSchedulableAfterClaimChange(t *testing.T) { storedClaim, err := testCtx.client.ResourceV1().ResourceClaims(updateClaim.Namespace).Update(tCtx, updateClaim, metav1.UpdateOptions{}) if err != nil { - t.Fatalf("update claim: expected no error, got: %v", err) + tCtx.Fatalf("update claim: expected no error, got: %v", err) } claim = storedClaim } // Eventually the assume cache will have it, too. - require.EventuallyWithT(t, func(t *assert.CollectT) { - cachedClaim, err := testCtx.draManager.resourceClaimTracker.cache.Get(claimKey) - require.NoError(t, err, "retrieve claim") - if cachedClaim.(*resourceapi.ResourceClaim).ResourceVersion != claim.ResourceVersion { - t.Errorf("cached claim not updated yet") - } - }, time.Minute, time.Second, "claim assume cache must have new or updated claim") + tCtx.Wait() + cachedClaim, err := testCtx.draManager.resourceClaimTracker.cache.Get(claimKey) + tCtx.ExpectNoError(err, "retrieve claim") + if cachedClaim.(*resourceapi.ResourceClaim).ResourceVersion != claim.ResourceVersion { + tCtx.Errorf("cached claim not updated yet") + } // This has the actual UID and ResourceVersion, // which is relevant for // isSchedulableAfterClaimChange. newObj = claim } - gotHint, err := testCtx.p.isSchedulableAfterClaimChange(logger, tc.pod, oldObj, newObj) + gotHint, err := testCtx.p.isSchedulableAfterClaimChange(tCtx.Logger(), tc.pod, oldObj, newObj) if tc.wantErr { if err == nil { - t.Fatal("want an error, got none") + tCtx.Fatal("want an error, got none") } return } if err != nil { - t.Fatalf("want no error, got: %v", err) + tCtx.Fatalf("want no error, got: %v", err) } if tc.wantHint != gotHint { - t.Fatalf("want %#v, got %#v", tc.wantHint.String(), gotHint.String()) + tCtx.Fatalf("want %#v, got %#v", tc.wantHint.String(), gotHint.String()) } }) } } -func Test_isSchedulableAfterPodChange(t *testing.T) { +func TestIsSchedulableAfterPodChange(t *testing.T) { + testIsSchedulableAfterPodChange(ktesting.Init(t)) +} +func testIsSchedulableAfterPodChange(tCtx ktesting.TContext) { testcases := map[string]struct { objs []apiruntime.Object pod *v1.Pod @@ -3205,26 +4361,27 @@ func Test_isSchedulableAfterPodChange(t *testing.T) { } for name, tc := range testcases { - t.Run(name, func(t *testing.T) { - logger, _ := ktesting.NewTestContext(t) + tCtx.Run(name, func(tCtx ktesting.TContext) { features := feature.Features{ - EnableDRASchedulerFilterTimeout: true, - EnableDynamicResourceAllocation: true, + EnableDRASchedulerFilterTimeout: true, + EnableDynamicResourceAllocation: true, + EnableDRADeviceBindingConditions: true, + EnableDRAResourceClaimDeviceStatus: true, } - testCtx := setup(t, nil, nil, tc.claims, nil, tc.objs, features, false, nil) - gotHint, err := testCtx.p.isSchedulableAfterPodChange(logger, tc.pod, nil, tc.obj) + testCtx := setup(tCtx, nil, nil, tc.claims, nil, nil, tc.objs, features, false, nil) + gotHint, err := testCtx.p.isSchedulableAfterPodChange(tCtx.Logger(), tc.pod, nil, tc.obj) if tc.wantErr { if err == nil { - t.Fatal("want an error, got none") + tCtx.Fatal("want an error, got none") } return } if err != nil { - t.Fatalf("want no error, got: %v", err) + tCtx.Fatalf("want no error, got: %v", err) } if tc.wantHint != gotHint { - t.Fatalf("want %#v, got %#v", tc.wantHint.String(), gotHint.String()) + tCtx.Fatalf("want %#v, got %#v", tc.wantHint.String(), gotHint.String()) } }) } @@ -3265,7 +4422,7 @@ func TestAllocatorSelection(t *testing.T) { // Alpha features need the experimental implementation. "alpha": { features: "AllAlpha=true,AllBeta=true", - expectImplementation: "experimental", + expectImplementation: "incubating", }, } { t.Run(name, func(t *testing.T) { @@ -3310,6 +4467,19 @@ func Test_computesScore(t *testing.T) { allocations: nodeAllocation{}, expectErr: true, }, + "mix-of-allocated-and-unallocated-claims": { + claims: []*resourceapi.ResourceClaim{ + allocatedClaim, + pendingClaim2, + }, + allocations: nodeAllocation{ + allocationResults: []resourceapi.AllocationResult{ + *allocationResult2, + }, + }, + expectedScore: 0, + expectErr: false, + }, "single-request-only-subrequest-allocated": { claims: []*resourceapi.ResourceClaim{ st.MakeResourceClaim(). @@ -3713,3 +4883,174 @@ func TestNormalizeScore(t *testing.T) { }) } } + +func TestGatherAllocatedState(t *testing.T) { + tCtx := ktesting.Init(t) + testGatherAllocatedState(tCtx) +} +func testGatherAllocatedState(tCtx ktesting.TContext) { + testcases := map[string]struct { + allocatedResourceClaims []*resourceapi.ResourceClaim + inflightResourceClaims map[types.UID]*resourceapi.ResourceClaim + enabledConsumableCapacity bool + expectErr bool + expectedIDAllocated int + expectedSharedIDAllocated int + expectedConsumedCapacity string + }{ + "no-claims": { + expectedIDAllocated: 0, + expectedSharedIDAllocated: 0, + }, + "single-allocated-claim": { + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaim, + }, + expectedIDAllocated: 1, + expectedSharedIDAllocated: 0, + }, + "single-allocated-claim-with-shared-device": { + enabledConsumableCapacity: true, + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaimWithSharedDevice, + }, + expectedIDAllocated: 0, + expectedSharedIDAllocated: 1, + }, + "single-allocated-claim-with-capacity": { + enabledConsumableCapacity: true, + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaimWithConsumedCapacity, + }, + expectedIDAllocated: 0, + expectedSharedIDAllocated: 1, + expectedConsumedCapacity: "1", + }, + "disabled-single-allocated-claim-with-capacity": { + enabledConsumableCapacity: false, + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaimWithConsumedCapacity, + }, + expectedIDAllocated: 1, + expectedSharedIDAllocated: 0, + expectedConsumedCapacity: "", + }, + "mixed-allocated-claim": { + enabledConsumableCapacity: true, + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaim, + allocatedClaimWithConsumedCapacity, + }, + expectedIDAllocated: 1, + expectedSharedIDAllocated: 1, + expectedConsumedCapacity: "1", + }, + "add-inflight-allocated-claim-with-capacity": { + enabledConsumableCapacity: true, + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaim, + allocatedClaimWithConsumedCapacity, + }, + inflightResourceClaims: map[types.UID]*resourceapi.ResourceClaim{ + "claim-2-uid": allocatedClaimWithConsumedCapacity2, + }, + expectedIDAllocated: 1, + expectedSharedIDAllocated: 2, + expectedConsumedCapacity: "2", + }, + "disabled-inflight-allocated-claim-with-capacity": { + enabledConsumableCapacity: false, + allocatedResourceClaims: []*resourceapi.ResourceClaim{ + allocatedClaim, + allocatedClaimWithConsumedCapacity, + }, + inflightResourceClaims: map[types.UID]*resourceapi.ResourceClaim{ + "claim-2-uid": allocatedClaimWithConsumedCapacity2, + }, + expectedIDAllocated: 2, + expectedSharedIDAllocated: 0, + expectedConsumedCapacity: "", + }, + } + for name, tc := range testcases { + tCtx.Run(name, func(tCtx ktesting.TContext) { + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAConsumableCapacity, tc.enabledConsumableCapacity) + + tCtx.Helper() + logger := klog.FromContext(tCtx) + draManager := &DefaultDRAManager{ + resourceClaimTracker: &claimTracker{ + inFlightAllocations: make(map[types.UID]inFlightAllocation), + allocatedDevices: newAllocatedDevices(logger), + }, + } + for _, obj := range tc.allocatedResourceClaims { + draManager.resourceClaimTracker.allocatedDevices.handlers().OnAdd(obj, false) + } + if tc.inflightResourceClaims != nil { + for claimUID, obj := range tc.inflightResourceClaims { + err := draManager.resourceClaimTracker.SignalClaimPendingAllocation(claimUID, obj) + if err != nil { + if !tc.expectErr { + tCtx.Fatalf("unexpected error: %v", err) + return + } + } + if tc.expectErr { + tCtx.Fatal("expected error, got none") + } + } + } + + // Start the test from here + allocatedState, err := draManager.ResourceClaims().GatherAllocatedState() + if err != nil { + if !tc.expectErr { + tCtx.Fatalf("unexpected error: %v", err) + return + } + } + if tc.expectErr { + tCtx.Fatal("expected error, got none") + } + allocatedDeviceIDs := allocatedState.AllocatedDevices + allocatedSharedDeviceIDs := allocatedState.AllocatedSharedDeviceIDs + aggregatedCapacity := allocatedState.AggregatedCapacity + + // Verify the counts match expectations + if allocatedDeviceIDs.Len() != tc.expectedIDAllocated { + tCtx.Errorf("expected %d allocated device IDs, got %d", tc.expectedIDAllocated, allocatedDeviceIDs.Len()) + } + if allocatedSharedDeviceIDs.Len() != tc.expectedSharedIDAllocated { + tCtx.Errorf("expected %d allocated shared device IDs, got %d", tc.expectedSharedIDAllocated, allocatedSharedDeviceIDs.Len()) + } + + // Verify aggregated capacity is initialized + if aggregatedCapacity == nil { + tCtx.Error("aggregatedCapacity should not be nil") + } + if tc.expectedConsumedCapacity != "" { + if len(aggregatedCapacity) == 0 { + tCtx.Errorf("expected consumed capacity, got empty") + } + deviceID := schedulerapi.MakeDeviceID(driver, nodeName, sharedDeviceName) + capacity := aggregatedCapacity[deviceID] + if capacity == nil { + tCtx.Errorf("expected aggregated capacity of %s, got nil", deviceID) + return + } + value := capacity[capacityName] + if value == nil { + tCtx.Errorf("expected value of %s, got nil", capacityName) + return + } + if value.Cmp(apiresource.MustParse(tc.expectedConsumedCapacity)) != 0 { + tCtx.Errorf("expected value of %s to be %s, got %s", capacityName, tc.expectedConsumedCapacity, value) + } + } else if len(aggregatedCapacity) > 0 { + tCtx.Errorf("got unexpected consumed capacity") + } + }) + } + +} diff --git a/pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go b/pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go index 1c677e728cd9a..8709dee6e3f8b 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go @@ -41,7 +41,6 @@ import ( "k8s.io/kubernetes/pkg/scheduler/framework" schedutil "k8s.io/kubernetes/pkg/scheduler/util" "k8s.io/kubernetes/pkg/scheduler/util/assumecache" - "k8s.io/kubernetes/pkg/util/slice" "k8s.io/utils/ptr" ) @@ -283,7 +282,7 @@ func isSpecialClaimName(name string) bool { } // deleteClaim deletes the claim after removing the finalizer from the claim, if there is any. -func (pl *DynamicResources) deleteClaim(ctx context.Context, claim *resourceapi.ResourceClaim, logger klog.Logger) error { +func (pl *DynamicResources) deleteClaim(ctx context.Context, claim *resourceapi.ResourceClaim) error { refreshClaim := false retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { if refreshClaim { @@ -311,7 +310,7 @@ func (pl *DynamicResources) deleteClaim(ctx context.Context, claim *resourceapi. return retryErr } - logger.V(5).Info("Delete", "resourceclaim", klog.KObj(claim)) + klog.FromContext(ctx).V(5).Info("Delete", "resourceclaim", klog.KObj(claim)) err := pl.clientset.ResourceV1().ResourceClaims(claim.Namespace).Delete(ctx, claim.Name, metav1.DeleteOptions{}) if err != nil { return err @@ -374,7 +373,7 @@ func createResourceRequestAndMappings(containerIndex int, container *v1.Containe } // resource requests in a container is a map, their names must // be sorted to determine the resource's index order. - slice.SortStrings(keys) + slices.Sort(keys) ridx := 0 for j := range keys { if keys[j] == rName.String() { @@ -419,9 +418,7 @@ func createRequestsAndMappings(pod *v1.Pod, extendedResources map[v1.ResourceNam for resource := range extendedResources { resourceNames = append(resourceNames, resource) } - sort.Slice(resourceNames, func(i, j int) bool { - return resourceNames[i] < resourceNames[j] - }) + slices.Sort(resourceNames) for _, resource := range resourceNames { class := deviceClassMapping.GetDeviceClass(resource) @@ -500,9 +497,9 @@ func createRequestsAndMappings(pod *v1.Pod, extendedResources map[v1.ResourceNam // so we poll with a timeout. func (pl *DynamicResources) waitForExtendedClaimInAssumeCache( ctx context.Context, - logger klog.Logger, claim *resourceapi.ResourceClaim, ) { + logger := klog.FromContext(ctx) pollErr := wait.PollUntilContextTimeout( ctx, 1*time.Second, @@ -529,11 +526,11 @@ func (pl *DynamicResources) waitForExtendedClaimInAssumeCache( // createExtendedResourceClaimInAPI creates an extended resource claim in the API server. func (pl *DynamicResources) createExtendedResourceClaimInAPI( ctx context.Context, - logger klog.Logger, pod *v1.Pod, nodeName string, state *stateData, ) (*resourceapi.ResourceClaim, error) { + logger := klog.FromContext(ctx) logger.V(5).Info("preparing to create claim for extended resources", "pod", klog.KObj(pod), "node", nodeName) // Get the node-specific claim that was prepared during Filter phase nodeAllocation, ok := state.nodeAllocations[nodeName] @@ -591,7 +588,7 @@ func (pl *DynamicResources) patchPodExtendedResourceClaimStatus( // unreserveExtendedResourceClaim cleans up the scheduler-owned extended resource claim // when scheduling fails. It reverts the assume cache, and deletes the claim from the API // server if it was already created. -func (pl *DynamicResources) unreserveExtendedResourceClaim(ctx context.Context, logger klog.Logger, pod *v1.Pod, state *stateData) { +func (pl *DynamicResources) unreserveExtendedResourceClaim(ctx context.Context, pod *v1.Pod, state *stateData) { extendedResourceClaim := state.claims.extendedResourceClaim() if extendedResourceClaim == nil { // there is no extended resource claim @@ -600,7 +597,7 @@ func (pl *DynamicResources) unreserveExtendedResourceClaim(ctx context.Context, // If the claim was marked as pending allocation (in-flight), remove that marker and restore // the assumed claim state to what it was before this scheduling attempt. - if deleted := pl.draManager.ResourceClaims().RemoveClaimPendingAllocation(state.claims.getInitialExtendedResourceClaimUID()); deleted { + if deleted := pl.draManager.ResourceClaims().MaybeRemoveClaimPendingAllocation(state.claims.getInitialExtendedResourceClaimUID(), false); deleted { pl.draManager.ResourceClaims().AssumedClaimRestore(extendedResourceClaim.Namespace, extendedResourceClaim.Name) } if isSpecialClaimName(extendedResourceClaim.Name) { @@ -608,9 +605,10 @@ func (pl *DynamicResources) unreserveExtendedResourceClaim(ctx context.Context, return } // Claim was written to API server, need to delete it to prevent orphaned resources. + logger := klog.FromContext(ctx) logger.V(5).Info("delete extended resource backed by DRA", "resourceclaim", klog.KObj(extendedResourceClaim), "pod", klog.KObj(pod), "claim.UID", extendedResourceClaim.UID) extendedResourceClaim = extendedResourceClaim.DeepCopy() - if err := pl.deleteClaim(ctx, extendedResourceClaim, logger); err != nil { + if err := pl.deleteClaim(ctx, extendedResourceClaim); err != nil { logger.Error(err, "delete", "resourceclaim", klog.KObj(extendedResourceClaim)) } } diff --git a/pkg/scheduler/framework/plugins/dynamicresources/nodeallocatabledynamicresources.go b/pkg/scheduler/framework/plugins/dynamicresources/nodeallocatabledynamicresources.go new file mode 100644 index 0000000000000..d509445d7f4a3 --- /dev/null +++ b/pkg/scheduler/framework/plugins/dynamicresources/nodeallocatabledynamicresources.go @@ -0,0 +1,449 @@ +/* +Copyright 2025 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 dynamicresources + +import ( + "context" + "errors" + "fmt" + "sort" + + v1 "k8s.io/api/core/v1" + resourceapi "k8s.io/api/resource/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + resourcehelper "k8s.io/component-helpers/resource" + "k8s.io/klog/v2" + fwk "k8s.io/kube-scheduler/framework" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" +) + +// ExtractPodNodeAllocatableResourceClaimStatus returns the pod nodeAllocatable claim status stored in state +func ExtractPodNodeAllocatableResourceClaimStatus(logger klog.Logger, state fwk.CycleState, nodeName string) []v1.NodeAllocatableResourceClaimStatus { + s, err := state.Read(names.DynamicResources) + if err != nil { + // DynamicResources plugin didn't run or no state + return nil + } + + draState, ok := s.(*stateData) + if !ok { + logger.Error(errors.New("invalid DynamicResources state type"), "Failed to cast CycleState data") + return nil + } + + if nodeAlloc, exists := draState.nodeAllocations[nodeName]; exists { + return nodeAlloc.nodeAllocatableResourceClaimStatuses + } + + return nil +} + +// calculateAndCheckNodeAllocatableResources calculates the total node-allocatable resources (e.g., CPU, memory) +// requested by a pod, considering both its standard container requests and any additional resources +// derived from its DRA claims. It then checks if this aggregated demand fits within the node's remaining allocatable capacity. +func (pl *DynamicResources) calculateAndCheckNodeAllocatableResources(ctx context.Context, state *stateData, pod *v1.Pod, nodeInfo fwk.NodeInfo, allocations map[types.UID]*resourceapi.AllocationResult) ([]v1.NodeAllocatableResourceClaimStatus, *fwk.Status) { + logger := klog.FromContext(ctx) + + nodeAllocatableClaims := []*resourceapi.ResourceClaim{} + nodeAllocatableClaimUIDs := sets.New[types.UID]() + + for _, claim := range state.claims.all() { + if alloc, ok := allocations[claim.UID]; ok { + for _, result := range alloc.Devices.Results { + device, err := getDeviceFromManager(pl.draManager, result.Pool, result.Device) + if err != nil { + logger.Error(err, "Failed to get device from manager", "claim", klog.KObj(claim), "device", result.Device, "pool", result.Pool) + continue + } + if device != nil && len(device.NodeAllocatableResourceMappings) > 0 { + if !nodeAllocatableClaimUIDs.Has(claim.UID) { + nodeAllocatableClaims = append(nodeAllocatableClaims, claim) + nodeAllocatableClaimUIDs.Insert(claim.UID) + } + break + } + } + } + } + + if len(nodeAllocatableClaims) == 0 { + return nil, nil // No nodeAllocatable resources to check + } + + totalPodDemand, nodeAllocatableClaimStatus, status := pl.getPodNodeAllocatableResourceFootprint(logger, nodeInfo, pod, state, allocations, nodeAllocatableClaims) + if status != nil { + logger.V(5).Info("calculateAndCheckNodeAllocatableResources: getPodNodeAllocatableResourceFootprint failed", "status", status) + return nil, status + } + + if status := pl.nodeFitsResources(nodeInfo, totalPodDemand); status != nil { + return nil, status + } + logger.V(5).Info("Pod fits on node ( including DRA Node Allocatable Resources)", "pod", klog.KObj(pod), "node", nodeInfo.Node().Name) + return nodeAllocatableClaimStatus, nil +} + +// getDeviceFromManager retrieves a specific Device object from the DRA manager's cache and +// looks for the device matching the given poolName and deviceName. +func getDeviceFromManager(draManager fwk.SharedDRAManager, poolName, deviceName string) (*resourceapi.Device, error) { + slices, err := draManager.ResourceSlices().ListWithDeviceTaintRules() + if err != nil { + return nil, fmt.Errorf("listing resource slices: %w", err) + } + for _, slice := range slices { + if slice.Spec.Pool.Name == poolName { + for i := range slice.Spec.Devices { + if slice.Spec.Devices[i].Name == deviceName { + return &slice.Spec.Devices[i], nil + } + } + } + } + return nil, fmt.Errorf("device %s not found in pool %s", deviceName, poolName) +} + +// buildNodeAllocatableDRAInfo processes the node allocatable resource allocations for a pod. +// It translates the allocated devices and quantities from DRA claims into a list of v1.NodeAllocatableResourceClaimStatus. +func (pl *DynamicResources) buildNodeAllocatableDRAInfo(pod *v1.Pod, nodeAllocatableClaimAllocations map[v1.ObjectReference]*resourceapi.AllocationResult, claimNametoUID map[string]types.UID) ([]v1.NodeAllocatableResourceClaimStatus, error) { + allContainers := make([]v1.Container, 0, len(pod.Spec.InitContainers)+len(pod.Spec.Containers)) + allContainers = append(allContainers, pod.Spec.InitContainers...) + allContainers = append(allContainers, pod.Spec.Containers...) + claimToStatus := make(map[types.UID]v1.NodeAllocatableResourceClaimStatus) + + for key, alloc := range nodeAllocatableClaimAllocations { + currentClaimStatus := v1.NodeAllocatableResourceClaimStatus{ + ResourceClaimName: key.Name, + Containers: []string{}, + Resources: map[v1.ResourceName]resource.Quantity{}, + } + + hasNodeAllocatableClaims := false + for _, result := range alloc.Devices.Results { + device, err := getDeviceFromManager(pl.draManager, result.Pool, result.Device) + if err != nil { + return nil, fmt.Errorf("claim %s/%s, device %s: %w", key.Namespace, key.Name, result.Device, err) + } + if device == nil || device.NodeAllocatableResourceMappings == nil { + continue + } + + for resourceName, resourceMap := range device.NodeAllocatableResourceMappings { + quantity := resource.Quantity{} + + if resourceMap.CapacityKey != nil && *resourceMap.CapacityKey != "" { + capacityKey := *resourceMap.CapacityKey + if result.ConsumedCapacity == nil { + return nil, fmt.Errorf("claim %s/%s, device %s: ConsumedCapacity is nil, but Capacity key '%s' is set in NodeAllocatableResourceMappings for resource %s", key.Namespace, key.Name, result.Device, capacityKey, resourceName) + } + if consumed, exists := result.ConsumedCapacity[capacityKey]; exists { + quantity = consumed.DeepCopy() + if resourceMap.AllocationMultiplier != nil { + qDec := quantity.AsDec() + multiplier := resourceMap.AllocationMultiplier.DeepCopy() + qDec.Mul(qDec, multiplier.AsDec()) + quantity = *resource.NewDecimalQuantity(*qDec, quantity.Format) + } + } else { + // If the capacityKey is not in ConsumedCapacity, this mapping is not relevant for this allocation + continue + } + } else if resourceMap.AllocationMultiplier != nil { + quantity = resourceMap.AllocationMultiplier.DeepCopy() + } + + if currentClaimStatus.Resources == nil { + currentClaimStatus.Resources = make(map[v1.ResourceName]resource.Quantity) + } + curQuantity, ok := currentClaimStatus.Resources[resourceName] + if !ok { + currentClaimStatus.Resources[resourceName] = quantity + } else { + curQuantity.Add(quantity) + currentClaimStatus.Resources[resourceName] = curQuantity + } + + hasNodeAllocatableClaims = true + + } + } + + if hasNodeAllocatableClaims { + claimToStatus[key.UID] = currentClaimStatus + } + } + + for _, container := range allContainers { + for _, podClaim := range container.Resources.Claims { + if claimUID, ok := claimNametoUID[podClaim.Name]; ok { + if nodeAllocatableClaimStatus, ok := claimToStatus[claimUID]; ok { + nodeAllocatableClaimStatus.Containers = append(nodeAllocatableClaimStatus.Containers, container.Name) + claimToStatus[claimUID] = nodeAllocatableClaimStatus + } + } + } + } + + nodeAllocatableClaimInfoList := make([]v1.NodeAllocatableResourceClaimStatus, 0, len(claimToStatus)) + for _, status := range claimToStatus { + nodeAllocatableClaimInfoList = append(nodeAllocatableClaimInfoList, status) + } + + // Sort the results for consistent output. + sort.Slice(nodeAllocatableClaimInfoList, func(i, j int) bool { + return nodeAllocatableClaimInfoList[i].ResourceClaimName < nodeAllocatableClaimInfoList[j].ResourceClaimName + }) + + return nodeAllocatableClaimInfoList, nil +} + +// validateNodeAllocatableDRAClaimSharing ensures that a node-allocatable DRA claim is not already in use by another pod on this node. +func (pl *DynamicResources) validateNodeAllocatableDRAClaimSharing(pod *v1.Pod, nodeInfo fwk.NodeInfo, claim *resourceapi.ResourceClaim) *fwk.Status { + if claim == nil { + return nil + } + claimKey := types.NamespacedName{Namespace: pod.Namespace, Name: claim.Name} + claimStates := nodeInfo.GetNodeAllocatableDRAClaimState() + if state, ok := claimStates[claimKey]; ok && state != nil { + if state.ConsumerPods.Len() > 1 { + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("node allocatable resource claim %s shared by multiple pods", claimKey.Name)) + } + if state.ConsumerPods.Len() == 1 && !state.ConsumerPods.Has(pod.UID) { + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("node allocatable resource claim %s is already used by another pod", claimKey.Name)) + } + } + return nil +} + +// validatePodLevelRequestsCoverDRA checks if the pod-level requests, if specified, are sufficient to cover +// the container level and DRA claim requests. +func (pl *DynamicResources) validatePodLevelRequestsCoverDRA(logger klog.Logger, pod *v1.Pod, requestWithPodLevel v1.ResourceList) *fwk.Status { + if !pl.fts.EnablePodLevelResources || pod.Spec.Resources == nil || pod.Spec.Resources.Requests == nil { + return nil + } + + // Calculate Sum(Containers) + DRA + overhead resources.. Skip pod level resources for this sum + optsSum := resourcehelper.PodResourcesOptions{ + SkipPodLevelResources: true, + UseDRANodeAllocatableResourceClaimStatus: true, + } + requestWithoutPodLevel := resourcehelper.PodRequests(pod, optsSum) + + // For resources specified at pod level, check if container and DRA aggregates does not exceed pod level budget. + for resName, podLevelReq := range requestWithPodLevel { + val, ok := requestWithoutPodLevel[resName] + if !ok { + continue + } + if val.Cmp(podLevelReq) > 0 { + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("pod level request for %s is insufficient to cover the aggregated container and node-allocatable DRA requests", resName)) + } + } + return nil +} + +// getPodNodeAllocatableResourceFootprint determines the total nodeAllocatable resource demand of a pod. +func (pl *DynamicResources) getPodNodeAllocatableResourceFootprint(logger klog.Logger, nodeInfo fwk.NodeInfo, pod *v1.Pod, state *stateData, allocations map[types.UID]*resourceapi.AllocationResult, nodeAllocatableClaims []*resourceapi.ResourceClaim) (*framework.Resource, []v1.NodeAllocatableResourceClaimStatus, *fwk.Status) { + nodeAllocatableDRAAllocations := make(map[v1.ObjectReference]*resourceapi.AllocationResult) + // Add pre-allocated claims + for _, claim := range nodeAllocatableClaims { + key := v1.ObjectReference{ + Namespace: pod.Namespace, + Name: claim.Name, + UID: claim.UID, + } + if claim.Status.Allocation != nil { + nodeAllocatableDRAAllocations[key] = claim.Status.Allocation + } + if alloc, ok := allocations[claim.UID]; ok { + nodeAllocatableDRAAllocations[key] = alloc + } + } + + claimNametoUID := make(map[string]types.UID) + if err := pl.foreachPodResourceClaim(pod, func(name string, claim *resourceapi.ResourceClaim) { + claimNametoUID[name] = claim.UID + }); err != nil { + return nil, nil, statusError(logger, fmt.Errorf("processing pod resource claims: %w", err)) + } + + nodeAllocatableStatus, err := pl.buildNodeAllocatableDRAInfo(pod, nodeAllocatableDRAAllocations, claimNametoUID) + if err != nil { + return nil, nil, statusError(logger, err) + } + + for _, status := range nodeAllocatableStatus { + // TODO(KEP-5517): Evaluate if its ok to have no containers referencing a node allocatable resource claim. + // This is pending on defining kubelet cgroup enforcement. + if len(status.Containers) == 0 { + return nil, nil, fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("claim %s: node-allocatable resource claim not referenced by any container within the pod", status.ResourceClaimName)) + } + } + + // Calculate the final totalPodDemand to be used for node fitting + optsTotal := resourcehelper.PodResourcesOptions{ + SkipPodLevelResources: !pl.fts.EnablePodLevelResources, + UseDRANodeAllocatableResourceClaimStatus: true, + } + podCopy := pod.DeepCopy() + podCopy.Status.NodeAllocatableResourceClaimStatuses = nodeAllocatableStatus + totalPodDemandRes := resourcehelper.PodRequests(podCopy, optsTotal) + + // Validate that pod-level requests, if specified, cover the aggregated container + DRA requests. + // The API validation in pkg/apis/core/validation/validation.go only checks pod.Spec.Resources against container + // requests within the Spec. It cannot account for DRA-derived resources, which are determined after device allocation. + if status := pl.validatePodLevelRequestsCoverDRA(logger, podCopy, totalPodDemandRes); status != nil { + return nil, nil, status + } + + totalPodDemand := framework.NewResource(totalPodDemandRes) + logger.V(5).Info("Total Pod Demand After DRA", "pod", klog.KObj(pod), "demand", totalPodDemand) + + return totalPodDemand, nodeAllocatableStatus, nil +} + +// insufficientResource describes what kind of resource limit is hit and caused the pod to not fit the node. +type insufficientResource struct { + resourceName v1.ResourceName + // We explicitly have a parameter for reason to avoid formatting a message on the fly + // for common resources, which is expensive for cluster autoscaler simulations. + reason string + requested int64 + used int64 + capacity int64 + // unresolvable indicates whether this node could be schedulable for the pod by the preemption, + // which is determined by comparing the node's size and the pod's request. + unresolvable bool +} + +// nodeFitsResources checks if the node has sufficient capacity to accommodate the pod's nodeAllocatable resource requirements. +// TODO(pravk03): Unify this with the fitsRequest() in pkg/scheduler/framework/plugins/noderesources/fit.go. +func (pl *DynamicResources) nodeFitsResources(nodeInfo fwk.NodeInfo, podRequest *framework.Resource) *fwk.Status { + insufficientResources := make([]insufficientResource, 0, 4) + + if podRequest.MilliCPU > 0 && podRequest.MilliCPU > (nodeInfo.GetAllocatable().GetMilliCPU()-nodeInfo.GetRequested().GetMilliCPU()) { + insufficientResources = append(insufficientResources, insufficientResource{ + resourceName: v1.ResourceCPU, + reason: "Insufficient cpu", + requested: podRequest.MilliCPU, + used: nodeInfo.GetRequested().GetMilliCPU(), + capacity: nodeInfo.GetAllocatable().GetMilliCPU(), + unresolvable: podRequest.MilliCPU > nodeInfo.GetAllocatable().GetMilliCPU(), + }) + } + if podRequest.Memory > 0 && podRequest.Memory > (nodeInfo.GetAllocatable().GetMemory()-nodeInfo.GetRequested().GetMemory()) { + insufficientResources = append(insufficientResources, insufficientResource{ + resourceName: v1.ResourceMemory, + reason: "Insufficient memory", + requested: podRequest.Memory, + used: nodeInfo.GetRequested().GetMemory(), + capacity: nodeInfo.GetAllocatable().GetMemory(), + unresolvable: podRequest.Memory > nodeInfo.GetAllocatable().GetMemory(), + }) + } + if podRequest.EphemeralStorage > 0 && + podRequest.EphemeralStorage > (nodeInfo.GetAllocatable().GetEphemeralStorage()-nodeInfo.GetRequested().GetEphemeralStorage()) { + insufficientResources = append(insufficientResources, insufficientResource{ + resourceName: v1.ResourceEphemeralStorage, + reason: "Insufficient ephemeral-storage", + requested: podRequest.EphemeralStorage, + used: nodeInfo.GetRequested().GetEphemeralStorage(), + capacity: nodeInfo.GetAllocatable().GetEphemeralStorage(), + unresolvable: podRequest.GetEphemeralStorage() > nodeInfo.GetAllocatable().GetEphemeralStorage(), + }) + } + + for resName, reqQuant := range podRequest.ScalarResources { + if v1helper.IsHugePageResourceName(resName) { + nodeCapacity := nodeInfo.GetAllocatable().GetScalarResources()[resName] + nodeRequested := nodeInfo.GetRequested().GetScalarResources()[resName] + available := nodeCapacity - nodeRequested + + if reqQuant > available { + insufficientResources = append(insufficientResources, insufficientResource{ + resourceName: resName, + reason: fmt.Sprintf("Insufficient %s", resName), + requested: reqQuant, + used: nodeRequested, + capacity: nodeCapacity, + unresolvable: reqQuant > nodeCapacity, + }) + } + } + } + + if len(insufficientResources) > 0 { + failureReasons := make([]string, 0, len(insufficientResources)) + statusCode := fwk.Unschedulable + for i := range insufficientResources { + failureReasons = append(failureReasons, insufficientResources[i].reason) + if insufficientResources[i].unresolvable { + statusCode = fwk.UnschedulableAndUnresolvable + } + } + return fwk.NewStatus(statusCode, failureReasons...) + } + return nil +} + +func (pl *DynamicResources) patchNodeAllocatableResourceClaimStatus(ctx context.Context, pod *v1.Pod, nodeAllocatableClaimStatus []v1.NodeAllocatableResourceClaimStatus) *fwk.Status { + + if len(nodeAllocatableClaimStatus) == 0 { + return nil + } + logger := klog.FromContext(ctx) + + // The incoming 'pod' is from the scheduler cache and would have NodeAllocatableResourceClaimStatus + // pre-populated in the assume phase without persisting to the API server. + // schedutil.PatchPodStatus skips patching if the old and new status are identical. + // To ensure the status is persisted to the API server we clear it in the baseStatus, forcing a patch. + baseStatus := pod.Status.DeepCopy() + if !apiequality.Semantic.DeepEqual(baseStatus.NodeAllocatableResourceClaimStatuses, nodeAllocatableClaimStatus) { + logger.V(5).Info("NodeAllocatableResourceClaimStatuses difference: assumed pod status does not match calculated status", "pod", klog.KObj(pod)) + return statusError(logger, errors.New("assumed pod status does not match calculated status to be patched")) + } + baseStatus.NodeAllocatableResourceClaimStatuses = nil + + targetStatus := pod.Status.DeepCopy() + + targetStatus.NodeAllocatableResourceClaimStatuses = nodeAllocatableClaimStatus + if err := schedutil.PatchPodStatus(ctx, pl.clientset, pod.Name, pod.Namespace, baseStatus, targetStatus); err != nil { + return statusError(logger, fmt.Errorf("updating pod %s/%s NodeAllocatableResourceClaimStatuses: %w", pod.Namespace, pod.Name, err)) + } + logger.V(5).Info("Patched pod status with NodeAllocatableResourceClaimStatuses", "pod", klog.KObj(pod), "status", targetStatus.NodeAllocatableResourceClaimStatuses) + + return nil +} + +func (pl *DynamicResources) clearNodeAllocatableResourceClaimStatus(ctx context.Context, pod *v1.Pod) { + logger := klog.FromContext(ctx) + logger.V(5).Info("Clearing NodeAllocatableResourceClaimStatuses on Unreserve", "pod", klog.KObj(pod)) + + targetStatus := pod.Status.DeepCopy() + targetStatus.NodeAllocatableResourceClaimStatuses = nil + + if err := schedutil.PatchPodStatus(ctx, pl.clientset, pod.Name, pod.Namespace, &pod.Status, targetStatus); err != nil { + logger.Error(err, "Failed to clear NodeAllocatableResourceClaimStatuses on Unreserve", "pod", klog.KObj(pod)) + } else { + logger.V(5).Info("Cleared NodeAllocatableResourceClaimStatuses", "pod", klog.KObj(pod)) + } +} diff --git a/pkg/scheduler/framework/plugins/dynamicresources/nodeallocatabledynamicresources_test.go b/pkg/scheduler/framework/plugins/dynamicresources/nodeallocatabledynamicresources_test.go new file mode 100644 index 0000000000000..a240bb685d8c0 --- /dev/null +++ b/pkg/scheduler/framework/plugins/dynamicresources/nodeallocatabledynamicresources_test.go @@ -0,0 +1,1182 @@ +/* +Copyright 2025 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 dynamicresources + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + resourceapi "k8s.io/api/resource/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" + "k8s.io/dynamic-resource-allocation/structured" + "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/utils/ptr" + + fwk "k8s.io/kube-scheduler/framework" + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +var ( + hugePages2Mi = v1.ResourceName(v1.ResourceHugePagesPrefix + "2Mi") + hugePages1Gi = v1.ResourceName(v1.ResourceHugePagesPrefix + "1Gi") +) + +// mockDRAManager is a mock implementation of fwk.SharedDRAManager. +type mockDRAManager struct { + fwk.SharedDRAManager + claims []*resourceapi.ResourceClaim + resourceSlices []*resourceapi.ResourceSlice +} + +func (m *mockDRAManager) ResourceClaims() fwk.ResourceClaimTracker { + return m +} + +func (m *mockDRAManager) Get(namespace, name string) (*resourceapi.ResourceClaim, error) { + for _, claim := range m.claims { + if claim.Namespace == namespace && claim.Name == name { + return claim, nil + } + } + return nil, fmt.Errorf("claim %s/%s not found", namespace, name) +} + +func (m *mockDRAManager) AssumeClaimAfterAPICall(claim *resourceapi.ResourceClaim) error { + return nil +} + +func (m *mockDRAManager) AssumedClaimRestore(namespace, name string) {} + +func (m *mockDRAManager) GetPendingAllocation(uid types.UID) *resourceapi.AllocationResult { + return nil +} + +func (m *mockDRAManager) SignalClaimPendingAllocation(uid types.UID, claim *resourceapi.ResourceClaim) error { + return nil +} + +func (m *mockDRAManager) MaybeRemoveClaimPendingAllocation(_ types.UID, _ bool) (deleted bool) { + return false +} + +func (m *mockDRAManager) List() ([]*resourceapi.ResourceClaim, error) { + return nil, nil +} + +func (m *mockDRAManager) GatherAllocatedState() (*structured.AllocatedState, error) { + return nil, nil +} + +func (m *mockDRAManager) ListAllAllocatedDevices() (sets.Set[structured.DeviceID], error) { + return nil, nil +} + +func (m *mockDRAManager) ResourceSlices() fwk.ResourceSliceLister { + return m +} + +func (m *mockDRAManager) ListWithDeviceTaintRules() ([]*resourceapi.ResourceSlice, error) { + return m.resourceSlices, nil +} + +func TestValidateNodeAllocatableDRAClaimSharing(t *testing.T) { + claimName := "node-allocatable-claim" + claimNameSpace := "test-ns" + claim1Key := types.NamespacedName{Namespace: claimNameSpace, Name: claimName} + + tests := []struct { + name string + pod *v1.Pod + claim *resourceapi.ResourceClaim + nodeInfo *framework.NodeInfo + wantStatus *fwk.Status + }{ + { + name: "empty claim", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid").Obj(), + claim: nil, + nodeInfo: &framework.NodeInfo{ + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, + }, + wantStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "claim not in node info", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("pod-uid").Obj(), + claim: &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: claimName, + Namespace: claimNameSpace, + UID: "claim-uid", + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.NodeAllocatableDRAClaimStates = map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{} + return ni + }(), + wantStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "claim shared, current pod not in consumers", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("pod-uid").Obj(), + claim: &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: claimName, + Namespace: claimNameSpace, + UID: "claim-uid", + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.NodeAllocatableDRAClaimStates = map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1Key: {ConsumerPods: sets.New[types.UID]("other-pod-uid", "another-pod-uid")}, + } + return ni + }(), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node allocatable resource claim node-allocatable-claim shared by multiple pods"), + }, + { + name: "claim shared, current pod in consumers", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("pod-uid").Obj(), + claim: &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: claimName, + Namespace: claimNameSpace, + UID: "claim-uid", + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.NodeAllocatableDRAClaimStates = map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1Key: {ConsumerPods: sets.New[types.UID]("pod-uid", "another-pod-uid")}, + } + return ni + }(), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node allocatable resource claim node-allocatable-claim shared by multiple pods"), + }, + { + name: "claim only used by other pod", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("pod-uid").Obj(), + claim: &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: claimName, + Namespace: claimNameSpace, + UID: "claim-uid", + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.NodeAllocatableDRAClaimStates = map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1Key: {ConsumerPods: sets.New[types.UID]("other-pod-uid")}, + } + return ni + }(), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node allocatable resource claim node-allocatable-claim is already used by another pod"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := &DynamicResources{} + gotStatus := pl.validateNodeAllocatableDRAClaimSharing(tt.pod, tt.nodeInfo, tt.claim) + if diff := cmp.Diff(tt.wantStatus, gotStatus); diff != "" { + t.Errorf("validateDRAClaimShareState() returned diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestBuildNodeAllocatableDRAInfo(t *testing.T) { + cpuDevicePerInstance := resourceapi.Device{ + Name: "cpu0", + NodeAllocatableResourceMappings: map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + AllocationMultiplier: ptr.To(resource.MustParse("1")), + }, + }, + } + + cpuDeviceCapacity := resourceapi.Device{ + Name: "cpu0", + NodeAllocatableResourceMappings: map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: ptr.To(resourceapi.QualifiedName("dra.example.com/cpu")), + }, + }, + } + + cpuMemDeviceCapacity := resourceapi.Device{ + Name: "device1", + NodeAllocatableResourceMappings: map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: ptr.To(resourceapi.QualifiedName("dra.example.com/cpu")), + }, + v1.ResourceMemory: { + CapacityKey: ptr.To(resourceapi.QualifiedName("dra.example.com/memory")), + }, + }, + } + + gpuDeviceAux := resourceapi.Device{ + Name: "gpu0", + NodeAllocatableResourceMappings: map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + AllocationMultiplier: ptr.To(resource.MustParse("2")), + }, + v1.ResourceMemory: { + AllocationMultiplier: ptr.To(resource.MustParse("4Gi")), + }, + }, + } + + makeSlice := func(name string, devices ...resourceapi.Device) *resourceapi.ResourceSlice { + return &resourceapi.ResourceSlice{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test-ns"}, + Spec: resourceapi.ResourceSliceSpec{ + Pool: resourceapi.ResourcePool{Name: "pool1"}, + NodeName: ptr.To("test-node"), + Driver: "dra.example.com", + Devices: devices, + }, + } + } + + makeClaim := func(name, uid string) *resourceapi.ResourceClaim { + return &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test-ns", UID: types.UID(uid)}, + } + } + + allocResult := func(pool, device string, consumed ...map[resourceapi.QualifiedName]resource.Quantity) *resourceapi.AllocationResult { + res := resourceapi.DeviceRequestAllocationResult{ + Pool: pool, + Device: device, + } + if len(consumed) > 0 { + res.ConsumedCapacity = consumed[0] + } + return &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{res}, + }, + } + } + + claimNameSpace := "test-ns" + + tests := []struct { + name string + pod *v1.Pod + claims []*resourceapi.ResourceClaim + resourceSlices []*resourceapi.ResourceSlice + nodeAllocatableClaimAllocations map[v1.ObjectReference]*resourceapi.AllocationResult + want []v1.NodeAllocatableResourceClaimStatus + wantErr bool + }{ + { + name: "empty", + pod: &v1.Pod{}, + want: []v1.NodeAllocatableResourceClaimStatus{}, + }, + { + name: "one container, one claim, per instance quantity", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Claims: []v1.ResourceClaim{{Name: "node-allocatable-claim"}}, + }, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("node-allocatable-claim", "claim-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("slice1", cpuDevicePerInstance)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "node-allocatable-claim", UID: "claim-uid"}: allocResult("pool1", "cpu0"), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "node-allocatable-claim", + + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }}, + }, + { + name: "Pod with Standard and DRA CPU and Memory Request", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Claims: []v1.ResourceClaim{{Name: "node-allocatable-claim"}}, + }, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("node-allocatable-claim", "claim-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("slice1", cpuMemDeviceCapacity)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "node-allocatable-claim", UID: "claim-uid"}: allocResult("pool1", "device1", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/cpu": resource.MustParse("4"), + "dra.example.com/memory": resource.MustParse("8Gi"), + }), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "node-allocatable-claim", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("4"), + v1.ResourceMemory: resource.MustParse("8Gi"), + }, + }}, + }, + { + name: "Fungible GPU/CPU claim - GPU selected", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Claims: []v1.ResourceClaim{{Name: "fungible-claim"}}, + }, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("fungible-claim", "claim-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("gpu-slice", resourceapi.Device{Name: "gpu0"})}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "fungible-claim", UID: "claim-uid"}: allocResult("pool1", "gpu0"), + }, + want: []v1.NodeAllocatableResourceClaimStatus{}, + }, + { + name: "Fungible GPU/CPU claim - CPU selected", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, + Claims: []v1.ResourceClaim{{Name: "fungible-claim"}}, + }, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("fungible-claim", "claim-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("cpu-slice", cpuDeviceCapacity)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "fungible-claim", UID: "claim-uid"}: allocResult("pool1", "cpu0", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/cpu": resource.MustParse("30"), + }), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "fungible-claim", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("30"), + }, + }}, + }, + { + name: "Combined CPU request and Auxiliary Request", + pod: st.MakePod().Name("test-pod").Namespace("test-ns").UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("100m")}, + Claims: []v1.ResourceClaim{{Name: "gpu-claim"}, {Name: "cpu-claim"}}, + }, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{ + makeClaim("cpu-claim", "cpu-claim-uid"), + makeClaim("gpu-claim", "gpu-claim-uid"), + }, + resourceSlices: []*resourceapi.ResourceSlice{ + makeSlice("cpu-slice", cpuDeviceCapacity), + makeSlice("gpu-slice", gpuDeviceAux), + }, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "cpu-claim", UID: "cpu-claim-uid"}: allocResult("pool1", "cpu0", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/cpu": resource.MustParse("10"), + }), + {Name: "gpu-claim", UID: "gpu-claim-uid"}: allocResult("pool1", "gpu0"), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "cpu-claim", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("10"), + }, + }, { + ResourceClaimName: "gpu-claim", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("4Gi"), + }, + }}, + }, + { + name: "Pod Level Resources with shared CPU claim and sidecar containers", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Resources(v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("11"), + v1.ResourceMemory: resource.MustParse("10Gi"), + }, + }). + Containers([]v1.Container{ + {Name: "c1", Resources: v1.ResourceRequirements{Claims: []v1.ResourceClaim{{Name: "cpu-claim"}}}}, + {Name: "c2", Resources: v1.ResourceRequirements{Claims: []v1.ResourceClaim{{Name: "cpu-claim"}}}}, + {Name: "sidecar1"}, + {Name: "sidecar2"}, + }). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("cpu-claim", "cpu-claim-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("cpu-slice", cpuDeviceCapacity)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "cpu-claim", UID: "cpu-claim-uid"}: allocResult("pool1", "cpu0", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/cpu": resource.MustParse("10"), + }), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "cpu-claim", + Containers: []string{"c1", "c2"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("10"), + }, + }}, + }, + { + name: "Multiple Claims per Container", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("100m")}, + Claims: []v1.ResourceClaim{{Name: "claim1"}, {Name: "claim2"}}, + }, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{ + makeClaim("claim1", "claim1-uid"), + makeClaim("claim2", "claim2-uid"), + }, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("slice1", cpuDeviceCapacity), makeSlice("slice2", cpuMemDeviceCapacity)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "claim1", UID: "claim1-uid"}: allocResult("pool1", "cpu0", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/cpu": resource.MustParse("4"), + }), + {Name: "claim2", UID: "claim2-uid"}: allocResult("pool1", "device1", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/memory": resource.MustParse("8Gi"), + }), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, { + ResourceClaimName: "claim2", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceMemory: resource.MustParse("8Gi"), + }, + }}, + }, + { + name: "Unreferenced Claims", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{Name: "c1"}}). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("unref-claim", "unref-claim-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("slice1", cpuDeviceCapacity)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "unref-claim", UID: "unref-claim-uid"}: allocResult("pool1", "cpu0", map[resourceapi.QualifiedName]resource.Quantity{ + "dra.example.com/cpu": resource.MustParse("4"), + }), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "unref-claim", + Containers: []string{}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }}, + }, + { + name: "Combined Capacity and PerAllocatedUnitQuantity", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid"). + Containers([]v1.Container{{ + Name: "c1", + Resources: v1.ResourceRequirements{Claims: []v1.ResourceClaim{{Name: "claim1"}}}, + }}). + Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("claim1", "claim1-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{ + makeSlice("slice1", resourceapi.Device{ + Name: "device1", + NodeAllocatableResourceMappings: map[v1.ResourceName]resourceapi.NodeAllocatableResourceMapping{ + v1.ResourceCPU: { + CapacityKey: ptr.To(resourceapi.QualifiedName("dra.example.com/cores")), + AllocationMultiplier: ptr.To(resource.MustParse("2")), + }, + }, + }), + }, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "claim1", UID: "claim1-uid"}: allocResult("pool1", "device1", map[resourceapi.QualifiedName]resource.Quantity{"dra.example.com/cores": resource.MustParse("4")}), + }, + want: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("8"), + }, + }}, + }, + { + name: "Capacity Key Missing in Allocation - Should be Ignored", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid").Containers([]v1.Container{{Name: "c1", Resources: v1.ResourceRequirements{Claims: []v1.ResourceClaim{{Name: "claim1"}}}}}).Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("claim1", "claim1-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("slice1", cpuDeviceCapacity)}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "claim1", UID: "claim1-uid"}: allocResult("pool1", "cpu0", map[resourceapi.QualifiedName]resource.Quantity{"dra.example.com/wrong": resource.MustParse("4")}), + }, + want: []v1.NodeAllocatableResourceClaimStatus{}, + }, + { + name: "Invalid - Device Not Found", + pod: st.MakePod().Name("test-pod").Namespace(claimNameSpace).UID("test-uid").Containers([]v1.Container{{Name: "c1", Resources: v1.ResourceRequirements{Claims: []v1.ResourceClaim{{Name: "claim1"}}}}}).Obj(), + claims: []*resourceapi.ResourceClaim{makeClaim("claim1", "claim1-uid")}, + resourceSlices: []*resourceapi.ResourceSlice{makeSlice("slice1", resourceapi.Device{Name: "device1"})}, + nodeAllocatableClaimAllocations: map[v1.ObjectReference]*resourceapi.AllocationResult{ + {Name: "claim1", UID: "claim1-uid"}: allocResult("pool1", "nonexistent-device"), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + draManager := &mockDRAManager{ + claims: tt.claims, + resourceSlices: tt.resourceSlices, + } + + pl := &DynamicResources{ + draManager: draManager, + } + + claimNametoUID := make(map[string]types.UID) + for _, claim := range tt.claims { + claimNametoUID[claim.Name] = claim.UID + } + + got, err := pl.buildNodeAllocatableDRAInfo(tt.pod, tt.nodeAllocatableClaimAllocations, claimNametoUID) + if (err != nil) != tt.wantErr { + t.Errorf("buildNodeAllocatableDRAInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + return + } + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf(`buildNodeAllocatableDRAInfo() diff (-want +got): +%s`, diff) + } + }) + } +} + +func TestPatchNodeAllocatableResourceClaimStatus(t *testing.T) { + pod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("pod-uid").Obj() + + tests := []struct { + name string + assumedPodStatus v1.PodStatus + finalPodNodeAllocatableClaimStatus []v1.NodeAllocatableResourceClaimStatus + wantPatch bool + setPatchError error + wantStatus *fwk.Status + }{ + { + name: "no node allocatable resource claims for this pod", + wantPatch: false, + wantStatus: nil, + }, + { + name: "assumed pod status same as new status", + assumedPodStatus: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + finalPodNodeAllocatableClaimStatus: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + wantPatch: true, + wantStatus: nil, + }, + { + name: "assumed pod status different from new status", + assumedPodStatus: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + finalPodNodeAllocatableClaimStatus: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + wantPatch: false, + wantStatus: statusError(klog.TODO(), errors.New("assumed pod status does not match calculated status to be patched")), + }, + { + name: "pod status patch error", + assumedPodStatus: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + finalPodNodeAllocatableClaimStatus: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + wantPatch: true, + setPatchError: errors.New("inject patch error"), + wantStatus: statusError(klog.TODO(), fmt.Errorf("updating pod test-ns/test-pod NodeAllocatableResourceClaimStatuses: %w", errors.New("inject patch error"))), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + podToUpdate := pod.DeepCopy() + podToUpdate.Status = *tt.assumedPodStatus.DeepCopy() + + fakeClient := fake.NewSimpleClientset(podToUpdate) + pl := &DynamicResources{ + clientset: fakeClient, + fts: feature.NewSchedulerFeaturesFromGates(utilfeature.DefaultFeatureGate), + } + if tt.setPatchError != nil { + fakeClient.PrependReactor("patch", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, tt.setPatchError + }) + } + status := pl.patchNodeAllocatableResourceClaimStatus(ctx, podToUpdate, tt.finalPodNodeAllocatableClaimStatus) + + if tt.wantStatus != nil && status != nil { + if tt.wantStatus.Code() != status.Code() { + t.Errorf("patchNodeAllocatableResourceClaimStatus() status code = %v, want %v", status.Code(), tt.wantStatus.Code()) + } + if tt.wantStatus.AsError().Error() != status.AsError().Error() { + t.Errorf("patchNodeAllocatableResourceClaimStatus() status error = %v, want %v", status.AsError().Error(), tt.wantStatus.AsError().Error()) + } + } else if tt.wantStatus != status { + t.Errorf("patchNodeAllocatableResourceClaimStatus() status = %v, want %v", status, tt.wantStatus) + } + + actions := fakeClient.Actions() + gotPatch := false + for _, action := range actions { + if action.Matches("patch", "pods") && action.GetSubresource() == "status" { + gotPatch = true + break + } + } + + if gotPatch != tt.wantPatch { + t.Errorf("patchNodeAllocatableResourceClaimStatus() gotPatch = %v, want %v", gotPatch, tt.wantPatch) + } + }) + } +} + +func TestClearNodeAllocatableResourceClaimStatus(t *testing.T) { + pod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("pod-uid").Obj() + + tests := []struct { + name string + initialPodStatus v1.PodStatus + wantPatch bool + }{ + { + name: "no status to clear", + initialPodStatus: v1.PodStatus{}, + wantPatch: false, + }, + { + name: "status cleared", + initialPodStatus: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "claim1", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + wantPatch: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + podToUpdate := pod.DeepCopy() + podToUpdate.Status = *tt.initialPodStatus.DeepCopy() + + fakeClient := fake.NewSimpleClientset(podToUpdate) + pl := &DynamicResources{ + clientset: fakeClient, + fts: feature.NewSchedulerFeaturesFromGates(utilfeature.DefaultFeatureGate), + } + + pl.clearNodeAllocatableResourceClaimStatus(ctx, podToUpdate) + + actions := fakeClient.Actions() + gotPatch := false + for _, action := range actions { + if action.Matches("patch", "pods") && action.GetSubresource() == "status" { + gotPatch = true + break + } + } + + if gotPatch != tt.wantPatch { + t.Errorf("patchNodeAllocatableResourceClaimStatus() gotPatch = %v, want %v", gotPatch, tt.wantPatch) + } + }) + } +} + +func TestNodeFitsNativeResources(t *testing.T) { + tests := []struct { + name string + podRequest *framework.Resource + nodeInfo *framework.NodeInfo + wantStatus *fwk.Status + }{ + { + name: "empty pod request", + podRequest: &framework.Resource{}, + nodeInfo: framework.NewNodeInfo(), + wantStatus: nil, + }, + { + name: "sufficient cpu and memory", + podRequest: &framework.Resource{ + MilliCPU: 1000, + Memory: 1024 * 1024 * 1024, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + v1.ResourceCPU: "2000m", + v1.ResourceMemory: "2Gi", + }).Obj()) + return ni + }(), + wantStatus: nil, + }, + { + name: "insufficient cpu", + podRequest: &framework.Resource{ + MilliCPU: 3000, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + v1.ResourceCPU: "2000m", + }).Obj()) + return ni + }(), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `Insufficient cpu`), + }, + { + name: "insufficient memory", + podRequest: &framework.Resource{ + Memory: 3 * 1024 * 1024 * 1024, + }, + nodeInfo: func() *framework.NodeInfo { + existigPod := v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("4Gi"), + }, + }, + }, + }, + }, + } + ni := framework.NewNodeInfo(&existigPod) + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + v1.ResourceMemory: "5Gi", + }).Obj()) + return ni + }(), + wantStatus: fwk.NewStatus(fwk.Unschedulable, `Insufficient memory`), + }, + { + name: "sufficient hugepages", + podRequest: &framework.Resource{ + ScalarResources: map[v1.ResourceName]int64{ + hugePages2Mi: 1, + hugePages1Gi: 2, + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + hugePages2Mi: "1", + hugePages1Gi: "2", + }).Obj()) + return ni + }(), + wantStatus: nil, + }, + { + name: "insufficient hugepages 2Mi", + podRequest: &framework.Resource{ + ScalarResources: map[v1.ResourceName]int64{ + hugePages2Mi: 2, + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + hugePages2Mi: "1", + }).Obj()) + return ni + }(), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `Insufficient hugepages-2Mi`), + }, + { + name: "insufficient hugepages 1Gi", + podRequest: &framework.Resource{ + ScalarResources: map[v1.ResourceName]int64{ + hugePages1Gi: 3, + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + hugePages1Gi: "2", + }).Obj()) + return ni + }(), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, `Insufficient hugepages-1Gi`), + }, + { + name: "all resources sufficient", + podRequest: &framework.Resource{ + MilliCPU: 1000, + Memory: 1024 * 1024 * 1024, + ScalarResources: map[v1.ResourceName]int64{ + hugePages2Mi: 1, + }, + }, + nodeInfo: func() *framework.NodeInfo { + ni := framework.NewNodeInfo() + ni.SetNode(st.MakeNode().Name("test-node").Capacity(map[v1.ResourceName]string{ + v1.ResourceCPU: "2000m", + v1.ResourceMemory: "2Gi", + hugePages2Mi: "1", + }).Obj()) + return ni + }(), + wantStatus: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := &DynamicResources{} + status := pl.nodeFitsResources(tt.nodeInfo, tt.podRequest) + if diff := cmp.Diff(tt.wantStatus, status, cmp.Comparer(func(a, b *fwk.Status) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + if a.Code() != b.Code() { + return false + } + // Only compare reasons if the code is Unschedulable + if a.Code() == fwk.Unschedulable { + return cmp.Diff(a.Reasons(), b.Reasons()) == "" + } + return true + })); diff != "" { + t.Errorf(`nodeFitsNativeResources() diff (-want +got): +%s`, diff) + } + }) + } +} + +func TestValidatePodLevelRequestsCoverDRA(t *testing.T) { + tests := []struct { + name string + pod *v1.Pod + nodeAllocatableStatus []v1.NodeAllocatableResourceClaimStatus + requestWithPodLevel v1.ResourceList + wantStatusCode fwk.Code + wantErrorMessage string + }{ + { + name: "PodLevelResources enabled, no pod resources set", + pod: st.MakePod().Name("test-pod"). + Containers([]v1.Container{{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Claims: []v1.ResourceClaim{{Name: "dra-claim"}}, + }, + }}). + Obj(), + nodeAllocatableStatus: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "dra-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("500m"), + v1.ResourceMemory: resource.MustParse("512Mi"), + }, + }}, + requestWithPodLevel: v1.ResourceList{}, + wantStatusCode: fwk.Success, + }, + { + name: "PodLevel resources sufficient", + pod: st.MakePod().Name("test-pod"). + Resources(v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + }). + Containers([]v1.Container{{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Claims: []v1.ResourceClaim{{Name: "dra-claim"}}, + }, + }}). + Obj(), + nodeAllocatableStatus: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "dra-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("500m"), + v1.ResourceMemory: resource.MustParse("512Mi"), + }, + }}, + requestWithPodLevel: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + wantStatusCode: fwk.Success, + }, + { + name: "PodLevel insufficient for one of the resource", + pod: st.MakePod().Name("test-pod"). + Resources(v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + }). + Containers([]v1.Container{{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("800m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Claims: []v1.ResourceClaim{{Name: "dra-claim"}}, + }, + }}). + Obj(), + nodeAllocatableStatus: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "dra-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("300m"), // 800 + 300 = 1100 > 1000 + v1.ResourceMemory: resource.MustParse("512Mi"), + }, + }}, + requestWithPodLevel: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + wantStatusCode: fwk.UnschedulableAndUnresolvable, + wantErrorMessage: "pod level request for cpu is insufficient to cover the aggregated container and node-allocatable DRA requests", + }, + { + name: "PodLevel insufficient for multiple resources", + pod: st.MakePod().Name("test-pod"). + Resources(v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + }). + Containers([]v1.Container{{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1100m"), + v1.ResourceMemory: resource.MustParse("1100Mi"), + }, + Claims: []v1.ResourceClaim{{Name: "dra-claim"}}, + }, + }}). + Obj(), + nodeAllocatableStatus: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "dra-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }}, + requestWithPodLevel: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + wantStatusCode: fwk.UnschedulableAndUnresolvable, + wantErrorMessage: "insufficient to cover the aggregated container and node-allocatable DRA requests", + }, + { + name: "PodLevel resources sufficient with overheads", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + v1.ResourceMemory: resource.MustParse("3Gi"), + }, + }, + Containers: []v1.Container{ + { + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Claims: []v1.ResourceClaim{{Name: "dra-claim"}}, + }, + }, + }, + Overhead: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("500m"), + v1.ResourceMemory: resource.MustParse("512Mi"), + }, + }, + }, + nodeAllocatableStatus: []v1.NodeAllocatableResourceClaimStatus{{ + ResourceClaimName: "dra-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + }}, + requestWithPodLevel: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + v1.ResourceMemory: resource.MustParse("3Gi"), + }, + wantStatusCode: fwk.Success, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := &DynamicResources{ + fts: feature.Features{EnablePodLevelResources: true}, + } + tt.pod.Status.NodeAllocatableResourceClaimStatuses = tt.nodeAllocatableStatus + gotStatus := pl.validatePodLevelRequestsCoverDRA(klog.TODO(), tt.pod, tt.requestWithPodLevel) + if diff := cmp.Diff(tt.wantStatusCode, gotStatus.Code()); diff != "" { + t.Errorf("validatePodLevelRequestsCoverDRA() returned diff (-want +got):\n%s", diff) + } + if tt.wantStatusCode != fwk.Success && tt.wantErrorMessage != "" { + if !strings.Contains(gotStatus.Message(), tt.wantErrorMessage) { + t.Errorf("validatePodLevelRequestsCoverDRA() returned error %q, want it to contain %q", gotStatus.Message(), tt.wantErrorMessage) + } + } + }) + } +} diff --git a/pkg/scheduler/framework/plugins/feature/feature.go b/pkg/scheduler/framework/plugins/feature/feature.go index f15c64edbd6f1..233c20542bf36 100644 --- a/pkg/scheduler/framework/plugins/feature/feature.go +++ b/pkg/scheduler/framework/plugins/feature/feature.go @@ -31,12 +31,14 @@ type Features struct { EnableDRAConsumableCapacity bool EnableDRADeviceTaints bool EnableDRADeviceBindingConditions bool + EnableDRAListTypeAttributes bool + EnableDRANodeAllocatableResources bool EnableDRAPartitionableDevices bool EnableDRAResourceClaimDeviceStatus bool EnableDRASchedulerFilterTimeout bool + EnableDRAWorkloadResourceClaims bool EnableDynamicResourceAllocation bool EnableVolumeAttributesClass bool - EnableCSIMigrationPortworx bool EnableVolumeLimitScaling bool EnableNodeInclusionPolicyInPodTopologySpread bool EnableMatchLabelKeysInPodTopologySpread bool @@ -48,8 +50,11 @@ type Features struct { EnableStorageCapacityScoring bool EnableNodeDeclaredFeatures bool EnableGangScheduling bool + EnableGenericWorkload bool EnableTaintTolerationComparisonOperators bool EnableInPlacePodLevelResourcesVerticalScaling bool + EnableTopologyAwareWorkloadScheduling bool + EnableWorkloadAwarePreemption bool } // NewSchedulerFeaturesFromGates copies the current state of the feature gates into the struct. @@ -60,12 +65,13 @@ func NewSchedulerFeaturesFromGates(featureGate featuregate.FeatureGate) Features EnableDRAAdminAccess: featureGate.Enabled(features.DRAAdminAccess), EnableDRAConsumableCapacity: featureGate.Enabled(features.DRAConsumableCapacity), EnableDRADeviceTaints: featureGate.Enabled(features.DRADeviceTaints), + EnableDRAListTypeAttributes: featureGate.Enabled(features.DRAListTypeAttributes), EnableDRASchedulerFilterTimeout: featureGate.Enabled(features.DRASchedulerFilterTimeout), EnableDRAResourceClaimDeviceStatus: featureGate.Enabled(features.DRAResourceClaimDeviceStatus), EnableDRADeviceBindingConditions: featureGate.Enabled(features.DRADeviceBindingConditions), + EnableDRAWorkloadResourceClaims: featureGate.Enabled(features.DRAWorkloadResourceClaims), EnableDynamicResourceAllocation: featureGate.Enabled(features.DynamicResourceAllocation), EnableVolumeAttributesClass: featureGate.Enabled(features.VolumeAttributesClass), - EnableCSIMigrationPortworx: featureGate.Enabled(features.CSIMigrationPortworx), EnableVolumeLimitScaling: featureGate.Enabled(features.VolumeLimitScaling), EnableNodeInclusionPolicyInPodTopologySpread: featureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread), EnableMatchLabelKeysInPodTopologySpread: featureGate.Enabled(features.MatchLabelKeysInPodTopologySpread), @@ -78,7 +84,11 @@ func NewSchedulerFeaturesFromGates(featureGate featuregate.FeatureGate) Features EnableStorageCapacityScoring: featureGate.Enabled(features.StorageCapacityScoring), EnableNodeDeclaredFeatures: featureGate.Enabled(features.NodeDeclaredFeatures), EnableGangScheduling: featureGate.Enabled(features.GangScheduling), + EnableGenericWorkload: featureGate.Enabled(features.GenericWorkload), EnableTaintTolerationComparisonOperators: featureGate.Enabled(features.TaintTolerationComparisonOperators), EnableInPlacePodLevelResourcesVerticalScaling: featureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling), + EnableTopologyAwareWorkloadScheduling: featureGate.Enabled(features.TopologyAwareWorkloadScheduling), + EnableDRANodeAllocatableResources: featureGate.Enabled(features.DRANodeAllocatableResources), + EnableWorkloadAwarePreemption: featureGate.Enabled(features.WorkloadAwarePreemption), } } diff --git a/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling.go b/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling.go index df9f0dd32dcea..130b379f2eef5 100644 --- a/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling.go +++ b/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling.go @@ -22,14 +22,14 @@ import ( "time" v1 "k8s.io/api/core/v1" - schedulingapi "k8s.io/api/scheduling/v1alpha1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha1" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" "k8s.io/klog/v2" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" "k8s.io/kubernetes/pkg/scheduler/util" ) @@ -37,25 +37,31 @@ import ( const ( // Name is the name of the plugin used in the plugin registry and configurations. Name = names.GangScheduling + // permitTimeoutDuration defines how long the gang pods should + // wait at the permit stage for a quorum before being rejected. + permitTimeoutDuration = 5 * time.Minute ) // GangScheduling is a plugin that enforces "all-or-nothing" scheduling for pods -// belonging to a Workload with a Gang scheduling policy. +// belonging to a PodGroup with a Gang scheduling policy. type GangScheduling struct { - handle fwk.Handle - workloadLister schedulinglisters.WorkloadLister + handle fwk.Handle + podGroupLister schedulinglisters.PodGroupLister + podGroupManager fwk.PodGroupManager + snapshotLister fwk.SharedLister } var _ fwk.EnqueueExtensions = &GangScheduling{} var _ fwk.PreEnqueuePlugin = &GangScheduling{} -var _ fwk.ReservePlugin = &GangScheduling{} var _ fwk.PermitPlugin = &GangScheduling{} // New initializes a new plugin and returns it. func New(_ context.Context, _ runtime.Object, fh fwk.Handle, fts feature.Features) (fwk.Plugin, error) { return &GangScheduling{ - handle: fh, - workloadLister: fh.SharedInformerFactory().Scheduling().V1alpha1().Workloads().Lister(), + handle: fh, + podGroupLister: fh.SharedInformerFactory().Scheduling().V1alpha2().PodGroups().Lister(), + podGroupManager: fh.PodGroupManager(), + snapshotLister: fh.SnapshotSharedLister(), }, nil } @@ -68,88 +74,80 @@ func (pl *GangScheduling) Name() string { func (pl *GangScheduling) EventsToRegister(_ context.Context) ([]fwk.ClusterEventWithHint, error) { return []fwk.ClusterEventWithHint{ // A new pod being added might be the one that completes a gang, meeting its MinCount requirement. - // Workload reference is immutable, so there is no need to subscribe on Pod/Update event. + // PodSchedulingGroup field is immutable, so there is no need to subscribe on Pod/Update event. {Event: fwk.ClusterEvent{Resource: fwk.Pod, ActionType: fwk.Add}, QueueingHintFn: pl.isSchedulableAfterPodAdded}, - // A Workload being added can be making a waiting gang schedulable. - // Workload's PodGroups are immutable, so there's no need to handle Workload/Update event. - {Event: fwk.ClusterEvent{Resource: fwk.Workload, ActionType: fwk.Add}, QueueingHintFn: pl.isSchedulableAfterWorkloadAdded}, + // A PodGroup being added can be making a waiting gang schedulable. + // PodGroups are immutable, so there's no need to handle PodGroup/Update event. + {Event: fwk.ClusterEvent{Resource: fwk.PodGroup, ActionType: fwk.Add}, QueueingHintFn: pl.isSchedulableAfterPodGroupAdded}, }, nil } -// matchingWorkloadReference returns true if two pods belong to the same workload, including their pod group and replica key. -func matchingWorkloadReference(pod1, pod2 *v1.Pod) bool { - return pod1.Spec.WorkloadRef != nil && pod2.Spec.WorkloadRef != nil && pod1.Namespace == pod2.Namespace && *pod1.Spec.WorkloadRef == *pod2.Spec.WorkloadRef -} - func (pl *GangScheduling) isSchedulableAfterPodAdded(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (fwk.QueueingHint, error) { _, addedPod, err := util.As[*v1.Pod](oldObj, newObj) if err != nil { return fwk.Queue, err } - if !matchingWorkloadReference(pod, addedPod) { - logger.V(5).Info("another pod was added but it doesn't match the target pod's workload", - "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef, "addedPod", klog.KObj(addedPod), "addedWorkloadRef", pod.Spec.WorkloadRef) + if !helper.MatchingSchedulingGroup(pod, addedPod) { + logger.V(5).Info("another pod was added but it doesn't match the target pod's scheduling group", + "pod", klog.KObj(pod), "schedulingGroup", pod.Spec.SchedulingGroup, "addedPod", klog.KObj(addedPod), "addedPodSchedulingGroup", addedPod.Spec.SchedulingGroup) return fwk.QueueSkip, nil } - logger.V(5).Info("another pod was added and it matches the target pod's workload, which may make the pod schedulable", - "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef, "addedPod", klog.KObj(addedPod), "addedWorkloadRef", pod.Spec.WorkloadRef) + logger.V(5).Info("another pod was added and it matches the target pod's scheduling group, which may make the pod schedulable", + "pod", klog.KObj(pod), "schedulingGroup", pod.Spec.SchedulingGroup, "addedPod", klog.KObj(addedPod), "addedPodSchedulingGroup", addedPod.Spec.SchedulingGroup) return fwk.Queue, nil } -func (pl *GangScheduling) isSchedulableAfterWorkloadAdded(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (fwk.QueueingHint, error) { - _, addedWorkload, err := util.As[*schedulingapi.Workload](oldObj, newObj) +func (pl *GangScheduling) isSchedulableAfterPodGroupAdded(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (fwk.QueueingHint, error) { + _, addedPodGroup, err := util.As[*schedulingapi.PodGroup](oldObj, newObj) if err != nil { return fwk.Queue, err } - if pod.Spec.WorkloadRef == nil || pod.Namespace != addedWorkload.Namespace || pod.Spec.WorkloadRef.Name != addedWorkload.Name { - logger.V(5).Info("workload was added but it doesn't match the target pod's workloadRef", - "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef, "addedWorkload", klog.KObj(addedWorkload)) + if pod.Spec.SchedulingGroup == nil || pod.Namespace != addedPodGroup.Namespace || *pod.Spec.SchedulingGroup.PodGroupName != addedPodGroup.Name { + logger.V(5).Info("pod group was added but it doesn't match the target pod's scheduling group", + "pod", klog.KObj(pod), "schedulingGroup", pod.Spec.SchedulingGroup, "addedPodGroup", klog.KObj(addedPodGroup)) return fwk.QueueSkip, nil } - logger.V(5).Info("workload was added and it matches the target pod's workload, which may make the pod schedulable", - "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef, "addedWorkload", klog.KObj(addedWorkload)) + logger.V(5).Info("pod group was added and it matches the target pod's scheduling group, which may make the pod schedulable", + "pod", klog.KObj(pod), "schedulingGroup", pod.Spec.SchedulingGroup, "addedPodGroup", klog.KObj(addedPodGroup)) return fwk.Queue, nil } // PreEnqueue checks if the pod belongs to a gang and, if so, whether the gang has met its MinCount of available pods. // If not, the pod is rejected until more pods arrive. func (pl *GangScheduling) PreEnqueue(ctx context.Context, pod *v1.Pod) *fwk.Status { - if pod.Spec.WorkloadRef == nil { + if pod.Spec.SchedulingGroup == nil { return nil } namespace := pod.Namespace - workloadRef := pod.Spec.WorkloadRef + schedulingGroup := pod.Spec.SchedulingGroup - workload, err := pl.workloadLister.Workloads(namespace).Get(workloadRef.Name) + podGroup, err := pl.podGroupLister.PodGroups(namespace).Get(*schedulingGroup.PodGroupName) if err != nil { if apierrors.IsNotFound(err) { - // The pod is unschedulable until its Workload object is created. - return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("waiting for pods's workload %q to appear in scheduling queue", workloadRef.Name)) + // The pod is unschedulable until its PodGroup object is created. + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("waiting for pods's pod group %q to appear in scheduling queue", *schedulingGroup.PodGroupName)) } - klog.FromContext(ctx).Error(err, "Failed to get workload", "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef) - return fwk.AsStatus(fmt.Errorf("failed to get workload %s/%s", namespace, workloadRef.Name)) + klog.FromContext(ctx).Error(err, "Failed to get pod group", "pod", klog.KObj(pod), "schedulingGroup", schedulingGroup) + return fwk.AsStatus(fmt.Errorf("failed to get pod group %s/%s", namespace, *schedulingGroup.PodGroupName)) } - policy, ok := podGroupPolicy(workload, workloadRef.PodGroup) - if !ok { - return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("pod group %q doesn't exist for a workload %q", workloadRef.PodGroup, workload.Name)) - } + policy := podGroup.Spec.SchedulingPolicy // This plugin only cares about pods with a Gang scheduling policy. if policy.Gang == nil { return nil } - podGroupInfo, err := pl.handle.WorkloadManager().PodGroupInfo(namespace, workloadRef) + podGroupState, err := pl.podGroupManager.PodGroupStates().Get(namespace, *schedulingGroup.PodGroupName) if err != nil { return fwk.AsStatus(err) } - allPods := podGroupInfo.AllPods() - if len(allPods) < int(policy.Gang.MinCount) { + allPodsCount := podGroupState.AllPodsCount() + if allPodsCount < int(policy.Gang.MinCount) { return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for minCount pods from a gang to appear in scheduling queue") } @@ -157,87 +155,54 @@ func (pl *GangScheduling) PreEnqueue(ctx context.Context, pod *v1.Pod) *fwk.Stat return nil } -// Reserve is called after a node has been selected for the pod. For gang pods, -// this stage marks the pod as "assumed" in the WorkloadManager, -// contributing to the count of pods ready to be co-scheduled at the Permit stage. -func (pl *GangScheduling) Reserve(ctx context.Context, cs fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status { - if pod.Spec.WorkloadRef == nil { - return nil - } - podGroupInfo, err := pl.handle.WorkloadManager().PodGroupInfo(pod.Namespace, pod.Spec.WorkloadRef) - if err != nil { - return fwk.AsStatus(err) - } - podGroupInfo.AssumePod(pod.UID) - return nil -} - -// Unreserve removes the gang pod from the "assumed" state in the WorkloadManager, -// ensuring it doesn't count towards the Permit quorum. -func (pl *GangScheduling) Unreserve(ctx context.Context, cs fwk.CycleState, pod *v1.Pod, nodeName string) { - if pod.Spec.WorkloadRef == nil { - return - } - podGroupInfo, err := pl.handle.WorkloadManager().PodGroupInfo(pod.Namespace, pod.Spec.WorkloadRef) - if err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Failed to get pod group info", "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef) - return - } - podGroupInfo.ForgetPod(pod.UID) -} - -// podGroupPolicy is a helper to find the policy for a specific pod group name in a workload. -func podGroupPolicy(workload *schedulingapi.Workload, podGroupName string) (schedulingapi.PodGroupPolicy, bool) { - for _, podGroup := range workload.Spec.PodGroups { - if podGroup.Name == podGroupName { - return podGroup.Policy, true - } - } - return schedulingapi.PodGroupPolicy{}, false -} - // Permit forces all pods in a gang to wait at this stage. Once the number of waiting (assumed) pods // reaches the gang's MinCount, all pods in the gang are permitted to proceed to binding simultaneously. func (pl *GangScheduling) Permit(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (*fwk.Status, time.Duration) { - if pod.Spec.WorkloadRef == nil { + if pod.Spec.SchedulingGroup == nil { return nil, 0 } logger := klog.FromContext(ctx) namespace := pod.Namespace - workloadRef := pod.Spec.WorkloadRef + schedulingGroup := pod.Spec.SchedulingGroup - workload, err := pl.workloadLister.Workloads(namespace).Get(workloadRef.Name) + podGroup, err := pl.podGroupLister.PodGroups(namespace).Get(*schedulingGroup.PodGroupName) if err != nil { - // It's likely that the workload was removed or another error happened. - // Returning error to retry the Pod when the Workload is added again. - return fwk.AsStatus(fmt.Errorf("failed to get workload %s/%s: %w", namespace, workloadRef.Name, err)), 0 + // It's likely that the pod group was removed or another error happened. + // Returning error to retry the Pod when the pod group is added again. + return fwk.AsStatus(fmt.Errorf("failed to get podGroup %s/%s: %w", namespace, *schedulingGroup.PodGroupName, err)), 0 } - policy, ok := podGroupPolicy(workload, workloadRef.PodGroup) - if !ok { - return fwk.AsStatus(fmt.Errorf("pod group %q doesn't exist for a workload %q", workloadRef.PodGroup, workload.Name)), 0 - } + policy := podGroup.Spec.SchedulingPolicy // This plugin only cares about pods with a Gang scheduling policy. if policy.Gang == nil { return nil, 0 } - podGroupInfo, err := pl.handle.WorkloadManager().PodGroupInfo(namespace, workloadRef) + // Select a lister for the pod group state based on the currently executed scheduling phase. + // In the pod group scheduling cycle, it reads from the snapshot. + // Otherwise, it reads the runtime state of the pod group from the cache. + var podGroupStateLister fwk.PodGroupStateLister + if state.IsPodGroupSchedulingCycle() { + podGroupStateLister = pl.snapshotLister.PodGroupStates() + } else { + podGroupStateLister = pl.podGroupManager.PodGroupStates() + } + podGroupState, err := podGroupStateLister.Get(namespace, *schedulingGroup.PodGroupName) if err != nil { return fwk.AsStatus(err), 0 } - assumedPods := podGroupInfo.AssumedPods() - assumedOrAssignedPods := assumedPods.Union(podGroupInfo.AssignedPods()) - if len(assumedOrAssignedPods) < int(policy.Gang.MinCount) { + scheduledPodsCount := podGroupState.ScheduledPodsCount() + if scheduledPodsCount < int(policy.Gang.MinCount) { // Activate unscheduled pods from this pod group in case they were waiting for this pod to be scheduled. - unscheduledPods := podGroupInfo.UnscheduledPods() + unscheduledPods := podGroupState.UnscheduledPods() pl.handle.Activate(klog.FromContext(ctx), unscheduledPods) - logger.V(4).Info("Quorum is not met for a gang. Waiting for another pod to allow", "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef, "activatedPods", len(unscheduledPods)) - return fwk.NewStatus(fwk.Wait, "waiting for minCount pods from a gang to be waiting on permit"), podGroupInfo.SchedulingTimeout() + logger.V(4).Info("Quorum is not met for a gang. Waiting for another pod to allow", "pod", klog.KObj(pod), "schedulingGroup", schedulingGroup, "activatedPods", len(unscheduledPods)) + return fwk.NewStatus(fwk.Wait, "waiting for minCount pods from a gang to be scheduled"), permitTimeoutDuration } - logger.V(4).Info("Quorum is met for a gang. Allowing other pods from a gang waiting on permit", "pod", klog.KObj(pod), "workloadRef", pod.Spec.WorkloadRef, "allowedPods", len(assumedPods)) + assumedPods := podGroupState.AssumedPods() + logger.V(4).Info("Quorum is met for a gang. Allowing other pods from a gang waiting on permit", "pod", klog.KObj(pod), "schedulingGroup", schedulingGroup, "allowedPods", len(assumedPods)) // The quorum is met. Allow this pod and signal all other waiting pods from the same gang to proceed. for podUID := range assumedPods { diff --git a/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling_test.go b/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling_test.go index 39db244116ead..98cf7d9edf143 100644 --- a/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling_test.go +++ b/pkg/scheduler/framework/plugins/gangscheduling/gangscheduling_test.go @@ -17,25 +17,34 @@ limitations under the License. package gangscheduling import ( - "context" "testing" "github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" - schedulingapi "k8s.io/api/scheduling/v1alpha1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" fwk "k8s.io/kube-scheduler/framework" - "k8s.io/kubernetes/pkg/scheduler/backend/workloadmanager" + "k8s.io/kubernetes/pkg/features" + internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" + schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime" + "k8s.io/kubernetes/pkg/scheduler/metrics" st "k8s.io/kubernetes/pkg/scheduler/testing" ) +func init() { + // This is required for tests where cache is initialized, and cache attempts to update metrics. + metrics.Register() +} + func Test_isSchedulableAfterPodAdded(t *testing.T) { tests := []struct { name string @@ -44,39 +53,21 @@ func Test_isSchedulableAfterPodAdded(t *testing.T) { expectedHint fwk.QueueingHint }{ { - name: "add a newPod which matches the pod's workload and pod group", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - newPod: st.MakePod().WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - expectedHint: fwk.Queue, - }, - { - name: "add a newPod which matches the pod's workload, pod group and replica key", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg", PodGroupReplicaKey: "3"}).Obj(), - newPod: st.MakePod().WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg", PodGroupReplicaKey: "3"}).Obj(), + name: "add a newPod which matches the pod's scheduling group", + pod: st.MakePod().Name("p").PodGroupName("pg").Obj(), + newPod: st.MakePod().PodGroupName("pg").Obj(), expectedHint: fwk.Queue, }, { name: "add a newPod which doesn't match the pod's namespace", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - newPod: st.MakePod().Namespace("foo").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - expectedHint: fwk.QueueSkip, - }, - { - name: "add a newPod which doesn't match the pod's workload name", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - newPod: st.MakePod().WorkloadRef(&v1.WorkloadReference{Name: "w2", PodGroup: "pg"}).Obj(), + pod: st.MakePod().Name("p").PodGroupName("pg").Obj(), + newPod: st.MakePod().Namespace("foo").PodGroupName("pg").Obj(), expectedHint: fwk.QueueSkip, }, { name: "add a newPod which doesn't match the pod's pod group name", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - newPod: st.MakePod().WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg2"}).Obj(), - expectedHint: fwk.QueueSkip, - }, - { - name: "add a newPod which doesn't match the pod's replica key", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg", PodGroupReplicaKey: "3"}).Obj(), - newPod: st.MakePod().WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg", PodGroupReplicaKey: "4"}).Obj(), + pod: st.MakePod().Name("p").PodGroupName("pg1").Obj(), + newPod: st.MakePod().PodGroupName("pg2").Obj(), expectedHint: fwk.QueueSkip, }, } @@ -107,29 +98,29 @@ func Test_isSchedulableAfterPodAdded(t *testing.T) { } } -func Test_isSchedulableAfterWorkloadAdded(t *testing.T) { +func Test_isSchedulableAfterPodGroupAdded(t *testing.T) { tests := []struct { name string pod *v1.Pod - newWorkload *schedulingapi.Workload + newPodGroup *schedulingapi.PodGroup expectedHint fwk.QueueingHint }{ { - name: "add a workload which matches the pod's workload name", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - newWorkload: st.MakeWorkload().Name("w1").PodGroup(st.MakePodGroup().Name("pg").MinCount(1).Obj()).Obj(), + name: "add a pod group which matches the pod's pod group name", + pod: st.MakePod().Name("p").PodGroupName("pg").Obj(), + newPodGroup: st.MakePodGroup().Name("pg").MinCount(1).TemplateRef("t", "w").Obj(), expectedHint: fwk.Queue, }, { - name: "add a workload which doesn't match the pod's workload name", - pod: st.MakePod().Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w2", PodGroup: "pg"}).Obj(), - newWorkload: st.MakeWorkload().Name("w1").PodGroup(st.MakePodGroup().Name("pg").MinCount(1).Obj()).Obj(), + name: "add a pod group which doesn't match the pod's scheduling group name", + pod: st.MakePod().Name("p").PodGroupName("pg1").Obj(), + newPodGroup: st.MakePodGroup().Name("pg2").MinCount(1).TemplateRef("t", "w").Obj(), expectedHint: fwk.QueueSkip, }, { - name: "add a workload which doesn't match the pod's workload namespace", - pod: st.MakePod().Namespace("ns1").Name("p").WorkloadRef(&v1.WorkloadReference{Name: "w1", PodGroup: "pg"}).Obj(), - newWorkload: st.MakeWorkload().Namespace("ns2").Name("w1").PodGroup(st.MakePodGroup().Name("pg").MinCount(1).Obj()).Obj(), + name: "add a pod group which doesn't match the pod's scheduling group namespace", + pod: st.MakePod().Namespace("ns1").Name("p").PodGroupName("pg").Obj(), + newPodGroup: st.MakePodGroup().Namespace("ns2").Name("pg").MinCount(1).TemplateRef("t", "w").Obj(), expectedHint: fwk.QueueSkip, }, } @@ -149,7 +140,7 @@ func Test_isSchedulableAfterWorkloadAdded(t *testing.T) { if err != nil { t.Fatal(err) } - actualHint, err := p.(*GangScheduling).isSchedulableAfterWorkloadAdded(logger, tc.pod, nil, tc.newWorkload) + actualHint, err := p.(*GangScheduling).isSchedulableAfterPodGroupAdded(logger, tc.pod, nil, tc.newPodGroup) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -171,127 +162,128 @@ func (pam *podActivatorMock) Activate(_ klog.Logger, pods map[string]*v1.Pod) { } func TestGangSchedulingFlow(t *testing.T) { - workload := st.MakeWorkload().Namespace("ns1").Name("gang-wl"). - PodGroup(st.MakePodGroup().Name("pg1").MinCount(3).Obj()). - PodGroup(st.MakePodGroup().Name("pg2").MinCount(4).Obj()).Obj() - - basicPolicyWorkload := st.MakeWorkload().Namespace("ns1").Name("basic-wl"). - PodGroup(st.MakePodGroup().Name("pg1").BasicPolicy().Obj()).Obj() - - p1 := st.MakePod().Namespace("ns1").Name("p1").UID("p1"). - WorkloadRef(&v1.WorkloadReference{Name: "gang-wl", PodGroup: "pg1"}).Obj() - p2 := st.MakePod().Namespace("ns1").Name("p2").UID("p2"). - WorkloadRef(&v1.WorkloadReference{Name: "gang-wl", PodGroup: "pg1"}).Obj() - p3 := st.MakePod().Namespace("ns1").Name("p3").UID("p3"). - WorkloadRef(&v1.WorkloadReference{Name: "gang-wl", PodGroup: "pg1"}).Obj() + gangPodGroup1 := st.MakePodGroup().Namespace("ns1").Name("pg1").TemplateRef("t1", "gang-wl").MinCount(3).Obj() + gangPodGroup2 := st.MakePodGroup().Namespace("ns1").Name("pg2").TemplateRef("t2", "gang-wl").MinCount(4).Obj() + basicPodGroup := st.MakePodGroup().Namespace("ns1").Name("pg3").TemplateRef("1", "basic-wl").BasicPolicy().Obj() - p4 := st.MakePod().Namespace("ns1").Name("p4").UID("p4"). - WorkloadRef(&v1.WorkloadReference{Name: "gang-wl", PodGroup: "pg2"}).Obj() + p1 := st.MakePod().Namespace("ns1").Name("p1").UID("p1").PodGroupName("pg1").Obj() + p2 := st.MakePod().Namespace("ns1").Name("p2").UID("p2").PodGroupName("pg1").Obj() + p3 := st.MakePod().Namespace("ns1").Name("p3").UID("p3").PodGroupName("pg1").Obj() - p5 := st.MakePod().Namespace("ns1").Name("p5").UID("p5"). - WorkloadRef(&v1.WorkloadReference{Name: "gang-wl", PodGroup: "pg1", PodGroupReplicaKey: "2"}).Obj() + p4 := st.MakePod().Namespace("ns1").Name("p4").UID("p4").PodGroupName("pg2").Obj() + p5 := st.MakePod().Namespace("ns1").Name("p5").UID("p5").PodGroupName("pg2").Obj() - basicPolicyPod := st.MakePod().Namespace("ns1").Name("basic-pod").UID("basic-pod"). - WorkloadRef(&v1.WorkloadReference{Name: "basic-wl", PodGroup: "pg1"}).Obj() + basicPolicyPod := st.MakePod().Namespace("ns1").Name("basic-pod").UID("basic-pod").PodGroupName("pg3").Obj() nonGangPod := st.MakePod().Namespace("ns1").Name("non-gang").UID("non-gang").Obj() tests := []struct { - name string - pod *v1.Pod - initialPods []*v1.Pod - initialWorkloads []*schedulingapi.Workload - podsWaitingOnPermit []*v1.Pod - wantPreEnqueueStatus *fwk.Status - wantPermitStatus *fwk.Status - wantActivatedPods []*v1.Pod - wantAllowedPods []types.UID + name string + pod *v1.Pod + initialPods []*v1.Pod + initialPodGroups []*schedulingapi.PodGroup + podsWaitingOnPermit []*v1.Pod + isDuringPodGroupSchedulingCycle bool + wantPreEnqueueStatus *fwk.Status + wantPermitStatus *fwk.Status + wantActivatedPods []*v1.Pod + wantAllowedPods []types.UID }{ { name: "non-gang pod succeeds immediately", pod: nonGangPod, - initialWorkloads: []*schedulingapi.Workload{workload, basicPolicyWorkload}, + initialPodGroups: []*schedulingapi.PodGroup{gangPodGroup1, gangPodGroup2, basicPodGroup}, wantPreEnqueueStatus: nil, wantPermitStatus: nil, }, { name: "basic policy pod succeeds immediately", pod: basicPolicyPod, - initialWorkloads: []*schedulingapi.Workload{workload, basicPolicyWorkload}, + initialPodGroups: []*schedulingapi.PodGroup{gangPodGroup1, gangPodGroup2, basicPodGroup}, wantPreEnqueueStatus: nil, wantPermitStatus: nil, }, { - name: "gang pod fails PreEnqueue when workload is not yet created", + name: "gang pod fails PreEnqueue when pod group is not yet created", pod: p1, initialPods: []*v1.Pod{p2, p3, p4, p5}, - initialWorkloads: []*schedulingapi.Workload{}, - wantPreEnqueueStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for pods's workload \"gang-wl\" to appear in scheduling queue"), + initialPodGroups: []*schedulingapi.PodGroup{}, + wantPreEnqueueStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for pods's pod group \"pg1\" to appear in scheduling queue"), }, { name: "gang pod fails PreEnqueue when quorum is not met", pod: p1, initialPods: []*v1.Pod{p2, p4, p5}, // Only p1 and p2 exist from their gang, minCount is 3. - initialWorkloads: []*schedulingapi.Workload{workload}, + initialPodGroups: []*schedulingapi.PodGroup{gangPodGroup1, gangPodGroup2}, wantPreEnqueueStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "waiting for minCount pods from a gang to appear in scheduling queue"), }, { name: "gang pod passes PreEnqueue, but waits at Permit", pod: p1, initialPods: []*v1.Pod{p2, p3, p4, p5}, // All pods are available. - initialWorkloads: []*schedulingapi.Workload{workload}, + initialPodGroups: []*schedulingapi.PodGroup{gangPodGroup1, gangPodGroup2}, podsWaitingOnPermit: []*v1.Pod{p2, p4, p5}, wantPreEnqueueStatus: nil, wantActivatedPods: []*v1.Pod{p3}, // At Permit, p1 will be assumed, but the count (2) is less than the quorum (3), so it must wait. - wantPermitStatus: fwk.NewStatus(fwk.Wait, "waiting for minCount pods from a gang to be waiting on permit"), + wantPermitStatus: fwk.NewStatus(fwk.Wait, "waiting for minCount pods from a gang to be scheduled"), }, { name: "final gang pod arrives at Permit and allows all waiting pods from a gang", pod: p1, // p3 is the pod being scheduled in this cycle. initialPods: []*v1.Pod{p2, p3, p4, p5}, - initialWorkloads: []*schedulingapi.Workload{workload}, + initialPodGroups: []*schedulingapi.PodGroup{gangPodGroup1, gangPodGroup2}, podsWaitingOnPermit: []*v1.Pod{p2, p3, p4, p5}, wantPreEnqueueStatus: nil, wantPermitStatus: nil, wantAllowedPods: []types.UID{"p1", "p2", "p3"}, }, + { + name: "final gang pod arrives at Permit during pod group scheduling cycle", + pod: p1, + initialPods: []*v1.Pod{p2, p3, p4, p5}, + initialPodGroups: []*schedulingapi.PodGroup{gangPodGroup1, gangPodGroup2}, + podsWaitingOnPermit: []*v1.Pod{p2, p3, p4, p5}, + isDuringPodGroupSchedulingCycle: true, + wantPreEnqueueStatus: nil, + wantPermitStatus: nil, + wantAllowedPods: []types.UID{"p1", "p2", "p3"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - manager := workloadmanager.New() + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, true) + logger, ctx := ktesting.NewTestContext(t) + cache := internalcache.New(ctx, nil, true) informerFactory := informers.NewSharedInformerFactory(fake.NewClientset(), 0) - workloadInformer := informerFactory.Scheduling().V1alpha1().Workloads() - + podGroupInformer := informerFactory.Scheduling().V1alpha2().PodGroups() fakeActivator := &podActivatorMock{} - + snapshot := internalcache.NewEmptySnapshot() fh, err := frameworkruntime.NewFramework(ctx, nil, nil, frameworkruntime.WithInformerFactory(informerFactory), - frameworkruntime.WithWorkloadManager(manager), + frameworkruntime.WithPodGroupManager(cache), frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), frameworkruntime.WithPodActivator(fakeActivator), + frameworkruntime.WithSnapshotSharedLister(snapshot), ) if err != nil { t.Fatalf("Failed to create framework: %v", err) } // Populate informers and manager state for the test case. - for _, wl := range tt.initialWorkloads { - err := workloadInformer.Informer().GetStore().Add(wl) + for _, wl := range tt.initialPodGroups { + err := podGroupInformer.Informer().GetStore().Add(wl) if err != nil { - t.Fatalf("Failed to add workload %s to store: %v", wl.Name, err) + t.Fatalf("Failed to add podGroup %s to store: %v", wl.Name, err) } } + for _, p := range tt.initialPods { - manager.AddPod(p) + cache.AddPodGroupMember(p) } - manager.AddPod(tt.pod) + cache.AddPodGroupMember(tt.pod) p, err := New(ctx, nil, fh, feature.Features{EnableGangScheduling: true}) if err != nil { @@ -310,32 +302,64 @@ func TestGangSchedulingFlow(t *testing.T) { // Simulate that other pods have already hit Permit and are now waiting. for _, p := range tt.podsWaitingOnPermit { - // Run Reserve and Permit for these pods to get them into the "assumed" state inside the manager. - status := pl.Reserve(ctx, nil, p, "some-node") - if !status.IsSuccess() { - t.Fatalf("Unexpected Reserve status for pod %q: %v", p.Name, status) + pod := p.DeepCopy() + pod.Spec.NodeName = "some-node" + if err := cache.AssumePod(logger, pod); err != nil { + t.Fatalf("Failed to assume pod %q: %v", pod.Name, err) } - status, _ = pl.Permit(ctx, nil, p, "some-node") + status, _ := pl.Permit(ctx, schedulerframework.NewCycleState(), pod, "some-node") if status.Code() != fwk.Wait { - t.Fatalf("Expected Wait status while permitting a pod %q: %v", p.Name, status) + t.Fatalf("Expected Wait status while permitting a pod %q: %v", pod.Name, status) } } - status := pl.Reserve(ctx, nil, tt.pod, "some-node") - if !status.IsSuccess() { - t.Fatalf("Unexpected Reserve status: %v", status) - } - // Clear activated pods to assert those activated in tt.pod Permit. fakeActivator.activatedPods = nil - gotPermitStatus, _ := pl.Permit(ctx, nil, tt.pod, "some-node") + cycleState := schedulerframework.NewCycleState() + if tt.isDuringPodGroupSchedulingCycle { + cycleState.SetPodGroupSchedulingCycle(cycleState) + } + + pod := tt.pod.DeepCopy() + pod.Spec.NodeName = "some-node" + + // In a pod group scheduling cycle, a snapshot is taken after all + // waiting pods are assumed, so that Permit can read from it. + if tt.isDuringPodGroupSchedulingCycle { + if err := cache.UpdateSnapshot(logger, snapshot); err != nil { + t.Fatalf("Failed to update snapshot: %v", err) + } + podInfo, err := schedulerframework.NewPodInfo(pod) + if err != nil { + t.Fatalf("Failed to create pod info for %q: %v", pod.Name, err) + } + // Assume pod in the snapshot, as in a pod group scheduling cycle. + if err := snapshot.AssumePod(podInfo); err != nil { + t.Fatalf("Failed to assume pod %q in snapshot: %v", pod.Name, err) + } + } else { + // Assume pod in the cache, as in a pod-by-pod scheduling cycle, where Permit reads from cache. + if err := cache.AssumePod(logger, pod); err != nil { + t.Fatalf("Failed to assume pod %q in cache: %v", pod.Name, err) + } + } + + gotPermitStatus, _ := pl.Permit(ctx, cycleState, pod, "some-node") if diff := cmp.Diff(tt.wantPermitStatus, gotPermitStatus); diff != "" { t.Fatalf("Unexpected Permit status (-want, +got):\n%s", diff) } if gotPermitStatus.Code() == fwk.Wait { - // Pod waits for others from a gang. Simulate its eventual Unreserve. - pl.Unreserve(ctx, nil, tt.pod, "some-node") + // Pod waits for others from a gang. Simulate its eventual forget. + if tt.isDuringPodGroupSchedulingCycle { + if err := snapshot.ForgetPod(logger, pod); err != nil { + t.Fatalf("Failed to forget pod %q from snapshot: %v", pod.Name, err) + } + } else { + if err := cache.ForgetPod(logger, pod); err != nil { + t.Fatalf("Failed to forget pod %q from cache: %v", pod.Name, err) + } + } return } diff --git a/pkg/scheduler/framework/plugins/helper/podgroup.go b/pkg/scheduler/framework/plugins/helper/podgroup.go new file mode 100644 index 0000000000000..5acc4e6f41b9a --- /dev/null +++ b/pkg/scheduler/framework/plugins/helper/podgroup.go @@ -0,0 +1,29 @@ +/* +Copyright 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 helper + +import ( + v1 "k8s.io/api/core/v1" +) + +// MatchingSchedulingGroup returns true if two pods belong to the same scheduling group. +func MatchingSchedulingGroup(pod1, pod2 *v1.Pod) bool { + return pod1.Namespace == pod2.Namespace && + pod1.Spec.SchedulingGroup != nil && + pod2.Spec.SchedulingGroup != nil && + *pod1.Spec.SchedulingGroup.PodGroupName == *pod2.Spec.SchedulingGroup.PodGroupName +} diff --git a/pkg/scheduler/framework/plugins/helper/podgroup_test.go b/pkg/scheduler/framework/plugins/helper/podgroup_test.go new file mode 100644 index 0000000000000..d8cbcc4db9196 --- /dev/null +++ b/pkg/scheduler/framework/plugins/helper/podgroup_test.go @@ -0,0 +1,77 @@ +/* +Copyright 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 helper + +import ( + "testing" + + v1 "k8s.io/api/core/v1" + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +func TestMatchingSchedulingGroup(t *testing.T) { + testCases := []struct { + name string + pod1 *v1.Pod + pod2 *v1.Pod + expected bool + }{ + { + name: "same pod with schedulingGroup", + pod1: st.MakePod().Name("pod1").Namespace("test").PodGroupName("name").Obj(), + pod2: st.MakePod().Name("pod1").Namespace("test").PodGroupName("name").Obj(), + expected: true, + }, + { + name: "different pods, same schedulingGroup", + pod1: st.MakePod().Name("pod1").Namespace("test").PodGroupName("name").Obj(), + pod2: st.MakePod().Name("pod2").Namespace("test").PodGroupName("name").Obj(), + expected: true, + }, + { + name: "same pod but no schedulingGroup", + pod1: st.MakePod().Name("pod1").Namespace("test").Obj(), + pod2: st.MakePod().Name("pod1").Namespace("test").Obj(), + expected: false, + }, + { + name: "different pods, only one with schedulingGroup", + pod1: st.MakePod().Name("pod1").Namespace("test").PodGroupName("name").Obj(), + pod2: st.MakePod().Name("pod2").Namespace("test").Obj(), + expected: false, + }, + { + name: "same schedulingGroup but different namespaces", + pod1: st.MakePod().Name("pod1").Namespace("test1").PodGroupName("name").Obj(), + pod2: st.MakePod().Name("pod2").Namespace("test2").PodGroupName("name").Obj(), + expected: false, + }, + { + name: "same namespace but different pod group", + pod1: st.MakePod().Name("pod1").Namespace("test").PodGroupName("name1").Obj(), + pod2: st.MakePod().Name("pod2").Namespace("test").PodGroupName("name2").Obj(), + expected: false, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + if got := MatchingSchedulingGroup(tt.pod1, tt.pod2); got != tt.expected { + t.Errorf("MatchingSchedulingGroup() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/scheduler/framework/plugins/imagelocality/image_locality.go b/pkg/scheduler/framework/plugins/imagelocality/image_locality.go index f23d9d9e7e0ce..6733c09851e32 100644 --- a/pkg/scheduler/framework/plugins/imagelocality/image_locality.go +++ b/pkg/scheduler/framework/plugins/imagelocality/image_locality.go @@ -103,7 +103,8 @@ func calculatePriority(sumScores int64, numContainers int) int64 { return fwk.MaxNodeScore * (sumScores - minThreshold) / (maxThreshold - minThreshold) } -// sumImageScores returns the sum of image scores of all the containers that are already on the node. +// sumImageScores returns the total image score for all container images in the Pod spec, +// including regular containers, init containers, and image volumes, that already exist on the node. // Each image receives a raw score of its size, scaled by scaledImageScore. The raw scores are later used to calculate // the final score. func sumImageScores(nodeInfo fwk.NodeInfo, pod *v1.Pod, totalNumNodes int) int64 { @@ -118,6 +119,14 @@ func sumImageScores(nodeInfo fwk.NodeInfo, pod *v1.Pod, totalNumNodes int) int64 sum += scaledImageScore(state, totalNumNodes) } } + for _, volume := range pod.Spec.Volumes { + if volume.Image == nil { + continue + } + if state, ok := nodeInfo.GetImageStates()[normalizedImageName(volume.Image.Reference)]; ok { + sum += scaledImageScore(state, totalNumNodes) + } + } return sum } diff --git a/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go b/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go index 27c37be8b3a39..193a427848f42 100644 --- a/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go +++ b/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go @@ -93,6 +93,24 @@ func TestImageLocalityPriority(t *testing.T) { }, } + testImageVolume := v1.PodSpec{ + Containers: []v1.Container{ + { + Image: "gcr.io/30", + }, + }, + Volumes: []v1.Volume{ + { + Name: "imageVolume", + VolumeSource: v1.VolumeSource{ + Image: &v1.ImageVolumeSource{ + Reference: "gcr.io/300", + }, + }, + }, + }, + } + test30Init300 := v1.PodSpec{ Containers: []v1.Container{ { @@ -341,6 +359,21 @@ func TestImageLocalityPriority(t *testing.T) { expectedList: []fwk.NodeScore{{Name: "node1", Score: 1}, {Name: "node2", Score: 0}}, name: "pod with multiple small images", }, + { + // Pod: gcr.io/300 gcr.io/30 + + // Node1 + // Image: gcr.io/300:latest 300MB + // Score: 100 * (300M * 1/2 - 23M) / (1000M - 23M) = 12 + + // Node2 + // Image: gcr.io/30:latest 30MB + // Score: 100 * (30M - 23M) / (1000M - 23M) = 0 + pod: &v1.Pod{Spec: testImageVolume}, + nodes: []*v1.Node{makeImageNode("node1", node300600900), makeImageNode("node2", node400030)}, + expectedList: []fwk.NodeScore{{Name: "node1", Score: 12}, {Name: "node2", Score: 0}}, + name: "pod with ImageVolume", + }, { // Pod: gcr.io/30 InitContainers: gcr.io/300 diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go b/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go index e5d8463c7241b..7b521680af501 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go +++ b/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go @@ -173,7 +173,17 @@ func (pl *InterPodAffinity) isSchedulableAfterPodChange(logger klog.Logger, pod if err != nil { return fwk.Queue, err } - if (modifiedPod != nil && modifiedPod.Spec.NodeName == "") || (originalPod != nil && originalPod.Spec.NodeName == "") { + if modifiedPod != nil && originalPod != nil && pod.UID == modifiedPod.UID { + // The only update event we listen on is UpdatePodLabel, so we can assume the labels have changed. + // The pod can become schedulable after update to its labels because e.g. some other pod might have + // anti-affinity on the old labels but not on the new ones. + logger.V(5).Info("the target pod labels have changed and it may be schedulable now", + "pod", klog.KObj(pod)) + return fwk.Queue, nil + } + // We don't need to check cases where NodeName or NominatedNodeName changes on a pod because in those cases Add/Delete event is fired + if (modifiedPod != nil && modifiedPod.Spec.NodeName == "" && modifiedPod.Status.NominatedNodeName == "") || + (originalPod != nil && originalPod.Spec.NodeName == "" && originalPod.Status.NominatedNodeName == "") { logger.V(5).Info("the added/updated/deleted pod is unscheduled, so it doesn't make the target pod schedulable", "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod), "modifiedPod", klog.KObj(modifiedPod)) return fwk.QueueSkip, nil @@ -220,15 +230,27 @@ func (pl *InterPodAffinity) isSchedulableAfterPodChange(logger klog.Logger, pod return fwk.QueueSkip, nil } - // Pod is deleted. Return Queue when the deleted pod matching the target pod's anti-affinity. - if !podMatchesAllAffinityTerms(antiTerms, originalPod) { - logger.V(5).Info("a scheduled pod was deleted but it doesn't match the target pod's anti-affinity", - "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod)) - return fwk.QueueSkip, nil + // Pod is deleted. Return Queue when the deleted pod matches the target pod's anti-affinity or vice versa. + + if podMatchesAllAffinityTerms(antiTerms, originalPod) { + logger.V(5).Info("a scheduled pod was deleted and it matches the target pod's anti-affinity. The pod may be schedulable now", + "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod)) + return fwk.Queue, nil } - logger.V(5).Info("a scheduled pod was deleted and it matches the target pod's anti-affinity. The pod may be schedulable now", - "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod)) - return fwk.Queue, nil + + originalPodAntiTerms, err := fwk.GetAffinityTerms(originalPod, fwk.GetPodAntiAffinityTerms(originalPod.Spec.Affinity)) + if err != nil { + return fwk.Queue, err + } + if podMatchesAllAffinityTerms(originalPodAntiTerms, pod) { + logger.V(5).Info("a scheduled pod was deleted and the target pod matches the deleted pod's anti-affinity. The pod may be schedulable now", + "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod)) + return fwk.Queue, nil + } + + logger.V(5).Info("a scheduled pod was deleted but it doesn't match the target pod's anti-affinity, nor vice versa", + "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod)) + return fwk.QueueSkip, nil } func (pl *InterPodAffinity) isSchedulableAfterNodeChange(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (fwk.QueueingHint, error) { diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go b/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go index 7e7753a4b9222..fb2faf75789c7 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go +++ b/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go @@ -43,90 +43,127 @@ func Test_isSchedulableAfterPodChange(t *testing.T) { }{ { name: "add a pod which matches the pod affinity", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), expectedHint: fwk.Queue, }, { name: "add an un-scheduled pod", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Label("service", "securityscan").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Label("service", "securityscan").Obj(), expectedHint: fwk.QueueSkip, }, { name: "add a pod which doesn't match the pod affinity", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("aaa", "a").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "a").Obj(), expectedHint: fwk.QueueSkip, }, { name: "update a pod from non-match to match pod affinity", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("aaa", "a").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "a").Obj(), expectedHint: fwk.Queue, }, { name: "the updating pod matches target pod's affinity both before and after label changes", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("service", "value2").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("service", "value2").Obj(), expectedHint: fwk.QueueSkip, }, { name: "update an un-scheduled pod", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Label("service", "securityscan").Obj(), - oldPod: st.MakePod().Label("service", "securityscan").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().UID("other").Label("aaa", "a").Obj(), expectedHint: fwk.QueueSkip, }, { name: "update a pod from match to non-match the pod affinity", - pod: st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("aaa", "a").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "a").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), expectedHint: fwk.QueueSkip, }, { name: "update a pod from match to non-match pod's affinity - multiple terms case", - pod: st.MakePod().Name("p").PodAffinityExists("aaa", "hostname", st.PodAffinityWithRequiredReq). + pod: st.MakePod().UID("p").Name("p").PodAffinityExists("aaa", "hostname", st.PodAffinityWithRequiredReq). PodAffinityExists("bbb", "hostname", st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("aaa", "a").Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "a").Obj(), expectedHint: fwk.QueueSkip, }, { name: "update a pod from non-match to match pod's affinity - multiple terms case", - pod: st.MakePod().Name("p").PodAffinityExists("aaa", "hostname", st.PodAffinityWithRequiredReq). + pod: st.MakePod().UID("p").Name("p").PodAffinityExists("aaa", "hostname", st.PodAffinityWithRequiredReq). PodAffinityExists("bbb", "hostname", st.PodAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("aaa", "").Label("bbb", "").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("aaa", "a").Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "").Label("bbb", "").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "a").Obj(), + expectedHint: fwk.Queue, + }, + { + name: "updated pod is the target pod", + pod: st.MakePod().UID("p").Name("p").Label("foo", "baz").Obj(), + newPod: st.MakePod().UID("p").Name("p").Label("foo", "baz").Obj(), + oldPod: st.MakePod().UID("p").Name("p").Label("foo", "bar").Obj(), expectedHint: fwk.Queue, }, { name: "modify pod label to change it from satisfying pod anti-affinity to not satisfying anti-affinity", - pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("service", "aaaa").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("service", "aaaa").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), expectedHint: fwk.Queue, }, { name: "modify pod label to change it from not satisfying pod anti-affinity to satisfying anti-affinity", - pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), - newPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), - oldPod: st.MakePod().Node("fake-node").Label("service", "bbb").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + newPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("service", "bbb").Obj(), expectedHint: fwk.QueueSkip, }, { name: "delete a pod which doesn't match pod's anti-affinity", - pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), - oldPod: st.MakePod().Node("fake-node").Label("aaa", "a").Obj(), + pod: st.MakePod().UID("p").Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("aaa", "a").Obj(), expectedHint: fwk.QueueSkip, }, { name: "delete a pod which matches pod's anti-affinity", + pod: st.MakePod().UID("p").Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + oldPod: st.MakePod().UID("other").Node("fake-node").Label("service", "securityscan").Obj(), + expectedHint: fwk.Queue, + }, + { + name: "delete a pod with anti-affinity that matches pending pod", + pod: st.MakePod().Name("p").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().Node("fake-node").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + expectedHint: fwk.Queue, + }, + { + name: "delete a pod with anti-affinity that doesn't match pending pod", + pod: st.MakePod().Name("p").Label("service", "foo").Obj(), + oldPod: st.MakePod().Node("fake-node").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + expectedHint: fwk.QueueSkip, + }, + { + name: "delete a pod which doesn't match pending pod's anti-affinity and has anti-affinity that doesn't match pending pod", + pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Label("service", "foo").Obj(), + oldPod: st.MakePod().Node("fake-node").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Label("service", "foo").Obj(), + expectedHint: fwk.QueueSkip, + }, + { + name: "delete a nominated pod which doesn't match pod's anti-affinity", + pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + oldPod: st.MakePod().NominatedNodeName("fake-node").Label("aaa", "a").Obj(), + expectedHint: fwk.QueueSkip, + }, + { + name: "delete a nominated pod which matches pod's anti-affinity", pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), - oldPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().NominatedNodeName("fake-node").Label("service", "securityscan").Obj(), expectedHint: fwk.Queue, }, } diff --git a/pkg/scheduler/framework/plugins/names/names.go b/pkg/scheduler/framework/plugins/names/names.go index 60915c352ad1b..e63efba701755 100644 --- a/pkg/scheduler/framework/plugins/names/names.go +++ b/pkg/scheduler/framework/plugins/names/names.go @@ -38,4 +38,6 @@ const ( VolumeBinding = "VolumeBinding" VolumeRestrictions = "VolumeRestrictions" VolumeZone = "VolumeZone" + TopologyPlacementGenerator = "TopologyPlacementGenerator" + PodGroupPodsCount = "PodGroupPodsCount" ) diff --git a/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures.go b/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures.go index 330f8ed7a35db..faceea9b93772 100644 --- a/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures.go +++ b/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures.go @@ -20,15 +20,12 @@ import ( "context" "fmt" "slices" - "strings" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" versionutil "k8s.io/apimachinery/pkg/util/version" "k8s.io/component-base/version" ndf "k8s.io/component-helpers/nodedeclaredfeatures" - "k8s.io/component-helpers/nodedeclaredfeatures/features" "k8s.io/klog/v2" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" @@ -41,6 +38,9 @@ const ( Name = names.NodeDeclaredFeatures // preFilterStateKey is the key in CycleState used to store the pod's feature requirements. preFilterStateKey fwk.StateKey = "PreFilter" + Name + // errReasonUnsatisfiedRequirements is the status reason given when a node's declared features + // doesn't meet the pod's required features. + errReasonUnsatisfiedRequirements = "node(s) didn't match Pod's required features" ) // preFilterState computed at PreFilter and used at Filter. @@ -76,10 +76,7 @@ func New(ctx context.Context, plArgs runtime.Object, fh fwk.Handle, fts feature. // Disabled, won't do anything. return &NodeDeclaredFeatures{}, nil } - ndfFramework, err := ndf.New(features.AllFeatures) - if err != nil { - return nil, fmt.Errorf("failed to create node feature framework: %w", err) - } + ndfFramework := ndf.DefaultFramework ver, err := versionutil.Parse(version.Get().String()) if err != nil { return nil, fmt.Errorf("failed to parse version: %w", err) @@ -98,7 +95,7 @@ func (pl *NodeDeclaredFeatures) PreFilter(ctx context.Context, cycleState fwk.Cy if err != nil { return nil, fwk.AsStatus(err) } - if reqs.Len() == 0 { + if reqs.IsEmpty() { return nil, fwk.NewStatus(fwk.Skip) } cycleState.Write(preFilterStateKey, &preFilterState{reqs: reqs}) @@ -119,12 +116,13 @@ func (pl *NodeDeclaredFeatures) Filter(ctx context.Context, cycleState fwk.Cycle if err != nil { return fwk.AsStatus(err) } - result, err := ndf.MatchNodeFeatureSet(s.reqs, nodeInfo.GetNodeDeclaredFeatures()) + // Don't use ndf.MatchNodeFeatureSet(...) here since we don't want to pay the cost of computing the feature diff. + isMatch, err := s.reqs.IsSubset(nodeInfo.GetNodeDeclaredFeatures()) if err != nil { return fwk.AsStatus(err) } - if !result.IsMatch { - return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, fmt.Sprintf("node declared features check failed - unsatisfied requirements: %s", strings.Join(result.UnsatisfiedRequirements, ", "))) + if !isMatch { + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, errReasonUnsatisfiedRequirements) } return nil } @@ -135,9 +133,8 @@ func (pl *NodeDeclaredFeatures) SignPod(ctx context.Context, pod *v1.Pod) ([]fwk if err != nil { return nil, fwk.AsStatus(err) } - featuresList := sets.List(fs.Set) return []fwk.SignFragment{ - {Key: fwk.FeaturesSignerName, Value: featuresList}, + {Key: fwk.FeaturesSignerName, Value: fs.String()}, }, nil } diff --git a/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures_test.go b/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures_test.go index 5bdf3f8344ee4..f9776242301ab 100644 --- a/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures_test.go +++ b/pkg/scheduler/framework/plugins/nodedeclaredfeatures/nodedeclaredfeatures_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/mock" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/version" "k8s.io/klog/v2/ktesting" @@ -40,18 +40,25 @@ import ( // createMockFeature is a helper function to create and configure a MockFeature. func createMockFeature(t *testing.T, name string, infer bool, maxVersionStr string) *ndftesting.MockFeature { m := ndftesting.NewMockFeature(t) - m.EXPECT().Name().Return(name).Maybe() - m.EXPECT().InferForScheduling(mock.Anything).Return(infer).Maybe() + m.SetName(name) + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { return infer }) if maxVersionStr != "" { - minVersion := version.MustParseSemantic(maxVersionStr) - m.EXPECT().MaxVersion().Return(minVersion).Maybe() + m.SetMaxVersion(version.MustParseSemantic(maxVersionStr)) } else { - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetMaxVersion(nil) } return m } func TestPreFilter(t *testing.T) { + const ( + feature1 = "TestFeature1" + feature2 = "TestFeature2" + ) + mapper := ndf.NewFeatureMapper([]string{feature1, feature2}) + newFS := func(features ...string) ndf.FeatureSet { + return mapper.MustMapSorted(features) + } _, ctx := ktesting.NewTestContext(t) testCases := []struct { name string @@ -68,7 +75,8 @@ func TestPreFilter(t *testing.T) { pod: st.MakePod().Name("test-pod").Obj(), componenetVersion: "1.35.0", nodeFeatures: []ndf.Feature{ - createMockFeature(t, "TestFeature", true, ""), + createMockFeature(t, feature1, true, ""), + createMockFeature(t, feature2, false, ""), }, expectedStatus: fwk.NewStatus(fwk.Skip), expectedState: nil, @@ -79,10 +87,11 @@ func TestPreFilter(t *testing.T) { pod: st.MakePod().Name("test-pod").Obj(), componenetVersion: "1.35.0", nodeFeatures: []ndf.Feature{ - createMockFeature(t, "TestFeature", true, ""), + createMockFeature(t, feature1, true, ""), + createMockFeature(t, feature2, false, ""), }, expectedStatus: fwk.NewStatus(fwk.Success), - expectedState: &preFilterState{reqs: ndf.NewFeatureSet("TestFeature")}, + expectedState: &preFilterState{reqs: newFS(feature1)}, }, { name: "Pod with multiple feature requirements", @@ -90,11 +99,11 @@ func TestPreFilter(t *testing.T) { pod: st.MakePod().Name("test-pod").Obj(), componenetVersion: "1.35.0", nodeFeatures: []ndf.Feature{ - createMockFeature(t, "TestFeature1", true, "1.38.0"), - createMockFeature(t, "TestFeature2", true, "1.38.0"), + createMockFeature(t, feature1, true, "1.38.0"), + createMockFeature(t, feature2, true, "1.38.0"), }, expectedStatus: fwk.NewStatus(fwk.Success), - expectedState: &preFilterState{reqs: ndf.NewFeatureSet("TestFeature1", "TestFeature2")}, + expectedState: &preFilterState{reqs: newFS(feature1, feature2)}, }, { name: "Pod with no requirements", @@ -102,7 +111,8 @@ func TestPreFilter(t *testing.T) { pod: st.MakePod().Name("test-pod").Obj(), componenetVersion: "1.35.0", nodeFeatures: []ndf.Feature{ - createMockFeature(t, "TestFeature", false, ""), + createMockFeature(t, feature1, false, ""), + createMockFeature(t, feature2, false, ""), }, expectedStatus: fwk.NewStatus(fwk.Skip), expectedState: nil, @@ -113,7 +123,8 @@ func TestPreFilter(t *testing.T) { pod: st.MakePod().Name("test-pod").Obj(), componenetVersion: "1.34.0", nodeFeatures: []ndf.Feature{ - createMockFeature(t, "TestFeature", true, "1.33.0"), + createMockFeature(t, feature1, true, "1.33.0"), + createMockFeature(t, feature2, false, ""), }, expectedStatus: fwk.NewStatus(fwk.Skip), expectedState: nil, @@ -122,10 +133,7 @@ func TestPreFilter(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ndfFramework, err := ndf.New(tc.nodeFeatures) - if err != nil { - t.Fatalf("Failed to create framework: %v", err) - } + ndfFramework := ndf.New(tc.nodeFeatures) plugin := &NodeDeclaredFeatures{ ndfFramework: ndfFramework, @@ -163,6 +171,16 @@ func TestPreFilter(t *testing.T) { func TestFilter(t *testing.T) { _, ctx := ktesting.NewTestContext(t) + const ( + featureA = "FeatureA" + featureB = "FeatureB" + featureC = "FeatureC" + ) + f, _ := ndftesting.NewMockFramework(t, featureA, featureB, featureC) + ndftesting.SetFrameworkDuringTest(t, f) + newFS := func(features ...string) ndf.FeatureSet { + return f.MustMapSorted(features) + } testCases := []struct { name string @@ -176,7 +194,7 @@ func TestFilter(t *testing.T) { name: "plugin disabled", pluginEnabled: false, pod: st.MakePod().Name("test-pod").Obj(), - node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{"FeatureA", "FeatureB"}).Obj(), + node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{featureA, featureB}).Obj(), preFilterReqs: nil, expectedStatus: nil, }, @@ -184,23 +202,23 @@ func TestFilter(t *testing.T) { name: "Node matches requirements", pluginEnabled: true, pod: st.MakePod().Name("test-pod").Obj(), - node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{"FeatureA", "FeatureB"}).Obj(), - preFilterReqs: []string{"FeatureA"}, + node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{featureA, featureB}).Obj(), + preFilterReqs: []string{featureA}, expectedStatus: fwk.NewStatus(fwk.Success), }, { name: "Node does not match requirements", pluginEnabled: true, pod: st.MakePod().Name("test-pod").Obj(), - node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{"FeatureB"}).Obj(), - preFilterReqs: []string{"FeatureA"}, - expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node declared features check failed - unsatisfied requirements: FeatureA"), + node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{featureB}).Obj(), + preFilterReqs: []string{featureA}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, errReasonUnsatisfiedRequirements), }, { name: "Node with multiple features, pod requires subset", pod: st.MakePod().Name("test-pod").Obj(), - node: st.MakeNode().Name("node-multi").DeclaredFeatures([]string{"FeatureA", "FeatureB", "FeatureC"}).Obj(), - preFilterReqs: []string{"FeatureA", "FeatureC"}, + node: st.MakeNode().Name("node-multi").DeclaredFeatures([]string{featureA, featureB, featureC}).Obj(), + preFilterReqs: []string{featureA, featureC}, expectedStatus: fwk.NewStatus(fwk.Success), }, { @@ -208,16 +226,16 @@ func TestFilter(t *testing.T) { pluginEnabled: true, pod: st.MakePod().Name("test-pod").Obj(), node: st.MakeNode().Name("node-1").Obj(), - preFilterReqs: []string{"FeatureA"}, - expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node declared features check failed - unsatisfied requirements: FeatureA"), + preFilterReqs: []string{featureA}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, errReasonUnsatisfiedRequirements), }, { name: "Node with some but not all required features", pluginEnabled: true, pod: st.MakePod().Name("test-pod").Obj(), - node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{"FeatureA"}).Obj(), - preFilterReqs: []string{"FeatureA", "FeatureB"}, - expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node declared features check failed - unsatisfied requirements: FeatureB"), + node: st.MakeNode().Name("node-1").DeclaredFeatures([]string{featureA}).Obj(), + preFilterReqs: []string{featureA, featureB}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, errReasonUnsatisfiedRequirements), }, { name: "Error getting pre-filter state", @@ -236,18 +254,14 @@ func TestFilter(t *testing.T) { nodeInfo := framework.NewNodeInfo() nodeInfo.SetNode(tc.node) - ndfFramework, err := ndf.New([]ndf.Feature{}) - if err != nil { - t.Fatalf("Failed to create framework: %v", err) - } plugin := &NodeDeclaredFeatures{ - ndfFramework: ndfFramework, + ndfFramework: ndf.DefaultFramework, version: version.MustParseSemantic("1.35.0"), enabled: tc.pluginEnabled, } cycleState := framework.NewCycleState() if tc.preFilterReqs != nil { - cycleState.Write(preFilterStateKey, &preFilterState{reqs: ndf.NewFeatureSet(tc.preFilterReqs...)}) + cycleState.Write(preFilterStateKey, &preFilterState{reqs: newFS(tc.preFilterReqs...)}) } status := plugin.Filter(ctx, cycleState, tc.pod, nodeInfo) @@ -298,10 +312,7 @@ func TestEnqueueExtensionsNodeUpdate(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ndfFramework, err := ndf.New([]ndf.Feature{}) - if err != nil { - t.Fatalf("Failed to create framework: %v", err) - } + ndfFramework := ndf.New([]ndf.Feature{}) plugin := &NodeDeclaredFeatures{ ndfFramework: ndfFramework, version: version.MustParseSemantic("1.35.0"), @@ -342,10 +353,21 @@ func TestEnqueueExtensionsPodUpdate(t *testing.T) { newPod: st.MakePod().Name(targetPodName).UID(targetPodUID).Label("foo", "bar").Obj(), componenetVersion: version.MustParseSemantic("1.35.0"), setupMock: func(m *ndftesting.MockFeature) { - m.EXPECT().InferForScheduling(mock.Anything).Return(false).Once() - m.EXPECT().InferForScheduling(mock.Anything).Return(true).Once() - m.EXPECT().Name().Return("TestFeature").Maybe() - m.EXPECT().MaxVersion().Return(nil).Maybe() + i := 0 + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { + switch i { + case 0: + i++ + return false + case 1: + i++ + return true + default: + panic("unexpected calls to SetInferForScheduling") + } + }) + m.SetName("TestFeature") + m.SetMaxVersion(nil) }, expectedHint: fwk.Queue, }, @@ -355,10 +377,21 @@ func TestEnqueueExtensionsPodUpdate(t *testing.T) { newPod: st.MakePod().Name(targetPodName).UID(targetPodUID).Obj(), componenetVersion: version.MustParseSemantic("1.35.0"), setupMock: func(m *ndftesting.MockFeature) { - m.EXPECT().InferForScheduling(mock.Anything).Return(true).Once() - m.EXPECT().InferForScheduling(mock.Anything).Return(false).Once() - m.EXPECT().Name().Return("TestFeature").Maybe() - m.EXPECT().MaxVersion().Return(nil).Maybe() + i := 0 + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { + switch i { + case 0: + i++ + return true + case 1: + i++ + return false + default: + panic("unexpected calls to SetInferForScheduling") + } + }) + m.SetName("TestFeature") + m.SetMaxVersion(nil) }, expectedHint: fwk.Queue, }, @@ -368,9 +401,9 @@ func TestEnqueueExtensionsPodUpdate(t *testing.T) { newPod: st.MakePod().Name(targetPodName).UID(targetPodUID).Obj(), componenetVersion: version.MustParseSemantic("1.35.0"), setupMock: func(m *ndftesting.MockFeature) { - m.EXPECT().InferForScheduling(mock.Anything).Return(false) - m.EXPECT().Name().Return("TestFeature").Maybe() - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { return false }) + m.SetName("TestFeature") + m.SetMaxVersion(nil) }, expectedHint: fwk.QueueSkip, }, @@ -380,9 +413,9 @@ func TestEnqueueExtensionsPodUpdate(t *testing.T) { newPod: st.MakePod().Name("another-test-pod").UID("456").Obj(), componenetVersion: version.MustParseSemantic("1.35.0"), setupMock: func(m *ndftesting.MockFeature) { - m.EXPECT().InferForScheduling(mock.Anything).Return(false).Maybe() - m.EXPECT().Name().Return("TestFeature").Maybe() - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { return false }) + m.SetName("TestFeature") + m.SetMaxVersion(nil) }, expectedHint: fwk.QueueSkip, }, @@ -392,9 +425,9 @@ func TestEnqueueExtensionsPodUpdate(t *testing.T) { newPod: st.MakePod().Name(targetPodName).UID(targetPodUID).Label("foo", "bar").Obj(), componenetVersion: nil, setupMock: func(m *ndftesting.MockFeature) { - m.EXPECT().InferForScheduling(mock.Anything).Return(true).Maybe() - m.EXPECT().Name().Return("TestFeature").Maybe() - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetInferForScheduling(func(podInfo *ndf.PodInfo) bool { return true }) + m.SetName("TestFeature") + m.SetMaxVersion(nil) }, expectedHint: fwk.Queue, // Queued again in case of error expectedErr: "target version cannot be nil", @@ -406,10 +439,7 @@ func TestEnqueueExtensionsPodUpdate(t *testing.T) { mockF := ndftesting.NewMockFeature(t) tc.setupMock(mockF) - ndfFramework, err := ndf.New([]ndf.Feature{mockF}) - if err != nil { - t.Fatalf("Failed to create framework: %v", err) - } + ndfFramework := ndf.New([]ndf.Feature{mockF}) plugin := &NodeDeclaredFeatures{ ndfFramework: ndfFramework, version: tc.componenetVersion, diff --git a/pkg/scheduler/framework/plugins/noderesources/balanced_allocation.go b/pkg/scheduler/framework/plugins/noderesources/balanced_allocation.go index 035c77b334889..6a87b04226dbb 100644 --- a/pkg/scheduler/framework/plugins/noderesources/balanced_allocation.go +++ b/pkg/scheduler/framework/plugins/noderesources/balanced_allocation.go @@ -123,9 +123,18 @@ func (pl *BalancedAllocation) SignPod(ctx context.Context, pod *v1.Pod) ([]fwk.S EnablePodLevelResources: pl.enablePodLevelResources, EnableDRAExtendedResource: pl.enableDRAExtendedResource, } - if pl.enableDRAExtendedResource { - return nil, fwk.NewStatus(fwk.Unschedulable, "signature disabled when dra extended resources enabled") + podRequest := computePodResourceRequest(pod, opts) + for rName, rQuant := range podRequest.ScalarResources { + // Skip in case request quantity is zero + if rQuant == 0 { + continue + } + + if shouldDelegateResourceToDRA(rName, nil, pl.draManager, opts) { + return nil, fwk.NewStatus(fwk.Unschedulable, "signature disabled when dra extended resources are used in the cluster") + } + } } return []fwk.SignFragment{ @@ -187,18 +196,35 @@ func NewBalancedAllocation(_ context.Context, baArgs runtime.Object, h fwk.Handl useRequested: true, resources: args.Resources, enableInPlacePodLevelResourcesVerticalScaling: fts.EnableInPlacePodLevelResourcesVerticalScaling, + draManager: h.SharedDRAManager(), }, }, nil } -func balancedResourceScorer(requested, allocable []int64) int64 { +func balancedResourceScorer(requested, allocated, allocatable []int64) int64 { + // Compute the score for the node with pending pod requests + scoreWithPod := balancedResourceScore(requested, allocatable) + // Compute the score for the node without pending pod requests + scoreWithoutPod := balancedResourceScore(allocated, allocatable) + // Score based on the improvement to the node balance (before vs after adding the pod). + // Scores with/without pod are in range [MaxNodeScore/2, MaxNodeScore]. + // Therefore, their difference will be in range [-MaxNodeScore/2, MaxNodeScore/2]. + // This operation brings the range back to [MaxNodeScore/2, MaxNodeScore] + // to be closer to the original implementation that only considered the score after adding the pod. + // If the difference to the balance is small, the score will be around 75. + // If the balance is improved, the score will move towards 100. + // If the balance is decreased, the score will move towards 50. + return fwk.MaxNodeScore/2 + (fwk.MaxNodeScore/2+scoreWithPod-scoreWithoutPod)/2 +} + +func balancedResourceScore(requested, allocatable []int64) int64 { var resourceToFractions []float64 var totalFraction float64 for i := range requested { - if allocable[i] == 0 { + if allocatable[i] == 0 { continue } - fraction := float64(requested[i]) / float64(allocable[i]) + fraction := float64(requested[i]) / float64(allocatable[i]) if fraction > 1 { fraction = 1 } diff --git a/pkg/scheduler/framework/plugins/noderesources/balanced_allocation_test.go b/pkg/scheduler/framework/plugins/noderesources/balanced_allocation_test.go index 998c68f711765..fbe258037c1bc 100644 --- a/pkg/scheduler/framework/plugins/noderesources/balanced_allocation_test.go +++ b/pkg/scheduler/framework/plugins/noderesources/balanced_allocation_test.go @@ -17,227 +17,186 @@ limitations under the License. package noderesources import ( - "context" "testing" "github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/klog/v2/ktesting" + apiruntime "k8s.io/apimachinery/pkg/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/backend/cache" "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" "k8s.io/kubernetes/pkg/scheduler/framework/runtime" st "k8s.io/kubernetes/pkg/scheduler/testing" tf "k8s.io/kubernetes/pkg/scheduler/testing/framework" + "k8s.io/kubernetes/test/utils/ktesting" ) func TestNodeResourcesBalancedAllocation(t *testing.T) { - cpuAndMemoryAndGPU := v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000m"), - v1.ResourceMemory: resource.MustParse("2000"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2000m"), - v1.ResourceMemory: resource.MustParse("3000"), - "nvidia.com/gpu": resource.MustParse("3"), - }, - }, - }, - }, - NodeName: "node1", - } - cpuOnly := v1.PodSpec{ - NodeName: "node1", - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000m"), - v1.ResourceMemory: resource.MustParse("0"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2000m"), - v1.ResourceMemory: resource.MustParse("0"), - }, - }, - }, - }, - } - cpuOnly2 := cpuOnly - cpuOnly2.NodeName = "node2" - cpuAndMemory := v1.PodSpec{ - NodeName: "node2", - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000m"), - v1.ResourceMemory: resource.MustParse("2000"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2000m"), - v1.ResourceMemory: resource.MustParse("3000"), - }, - }, - }, - }, - } - + testNodeResourcesBalancedAllocation(ktesting.Init(t)) +} +func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) { defaultResourceBalancedAllocationSet := []config.ResourceSpec{ {Name: string(v1.ResourceCPU), Weight: 1}, {Name: string(v1.ResourceMemory), Weight: 1}, } - scalarResource := map[string]int64{ - "nvidia.com/gpu": 8, - } tests := []struct { - pod *v1.Pod - pods []*v1.Pod - nodes []*v1.Node - expectedList fwk.NodeScoreList - name string - args config.NodeResourcesBalancedAllocationArgs - runPreScore bool - wantPreScoreStatusCode fwk.Code + name string + requestedPod *v1.Pod + nodes []*v1.Node + existingPods []*v1.Pod + expectedScores fwk.NodeScoreList + args config.NodeResourcesBalancedAllocationArgs + runPreScore bool + expectedPreScoreStatusCode fwk.Code + draObjects []apiruntime.Object }{ { // bestEffort pods, skip in PreScore - pod: st.MakePod().Obj(), - nodes: []*v1.Node{makeNode("node1", 4000, 10000, nil), makeNode("node2", 4000, 10000, nil)}, - name: "nothing scheduled, nothing requested, skip in PreScore", - args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, - runPreScore: true, - wantPreScoreStatusCode: fwk.Skip, + name: "nothing scheduled, nothing requested, skip in PreScore", + requestedPod: st.MakePod().Obj(), + nodes: []*v1.Node{makeNode("node1", 4000, 10000, nil), makeNode("node2", 4000, 10000, nil)}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: true, + expectedPreScoreStatusCode: fwk.Skip, }, { - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 3000 / 4000= 75% - // Memory Fraction: 5000 / 10000 = 50% - // Node1 std: (0.75 - 0.5) / 2 = 0.125 - // Node1 Score: (1 - 0.125)*MaxNodeScore = 87 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 3000 / 6000= 50% - // Memory Fraction: 5000/10000 = 50% - // Node2 std: 0 - // Node2 Score: (1-0) * MaxNodeScore = MaxNodeScore - pod: &v1.Pod{Spec: cpuAndMemory}, - nodes: []*v1.Node{makeNode("node1", 4000, 10000, nil), makeNode("node2", 6000, 10000, nil)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 87}, {Name: "node2", Score: fwk.MaxNodeScore}}, + // Node1 + // CPU: 0 -> 3000/4000 (0% -> 75%) + // Memory: 0 -> 5000/10000 (0% -> 50%) + // Score: 68 (100 -> 87) + // Node2 + // CPU: 0 -> 3000/6000 (0% -> 50%) + // Memory: 0 -> 5000/10000 (0% -> 50%) + // Score: 75 (100 -> 100) name: "nothing scheduled, resources requested, differently sized nodes", - args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, - runPreScore: true, + requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 4000, 10000, nil), + makeNode("node2", 6000, 10000, nil)}, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 68}, {Name: "node2", Score: 75}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: true, }, { - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 10000 = 60% - // Memory Fraction: 5000 / 20000 = 25% - // Node1 std: (0.6 - 0.25) / 2 = 0.175 - // Node1 Score: (1 - 0.175)*MaxNodeScore = 82 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 10000 = 60% - // Memory Fraction: 10000 / 20000 = 50% - // Node2 std: (0.6 - 0.5) / 2 = 0.05 - // Node2 Score: (1 - 0.05)*MaxNodeScore = 95 - pod: &v1.Pod{Spec: cpuAndMemory}, - nodes: []*v1.Node{makeNode("node1", 10000, 20000, nil), makeNode("node2", 10000, 20000, nil)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 82}, {Name: "node2", Score: 95}}, + // Node1 + // CPU: 3000 -> 6000/10000 (30% -> 60%) + // Memory: 0 -> 5000/20000 (0% -> 25%) + // Score: 73 (85 -> 82) + // Node2 + // CPU: 3000 -> 6000/10000 (30% -> 60%) + // Memory: 5000 -> 10000/20000 (25% -> 50%) + // Score: 74 (97 -> 95) name: "resources requested, pods scheduled with resources", - pods: []*v1.Pod{ - {Spec: cpuOnly}, - {Spec: cpuAndMemory}, + requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 10000, 20000, nil), + makeNode("node2", 10000, 20000, nil), }, - args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, - runPreScore: true, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 73}, {Name: "node2", Score: 74}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: true, }, { - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 10000 = 60% - // Memory Fraction: 5000 / 20000 = 25% - // Node1 std: (0.6 - 0.25) / 2 = 0.175 - // Node1 Score: (1 - 0.175)*MaxNodeScore = 82 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 10000 = 60% - // Memory Fraction: 10000 / 50000 = 20% - // Node2 std: (0.6 - 0.2) / 2 = 0.2 - // Node2 Score: (1 - 0.2)*MaxNodeScore = 80 - pod: &v1.Pod{Spec: cpuAndMemory}, - nodes: []*v1.Node{makeNode("node1", 10000, 20000, nil), makeNode("node2", 10000, 50000, nil)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 82}, {Name: "node2", Score: 80}}, + // Node1 + // CPU: 3000 -> 6000/10000 (30% -> 60%) + // Memory: 0 -> 5000/20000 (0% -> 25%) + // Score: 73 (85 -> 82) + // Node2 + // CPU: 3000 -> 6000/10000 (30% -> 60%) + // Memory: 5000 -> 10000/50000 (10% -> 20%) + // Score: 70 (90 -> 80) name: "resources requested, pods scheduled with resources, differently sized nodes", - pods: []*v1.Pod{ - {Spec: cpuOnly}, - {Spec: cpuAndMemory}, + requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 10000, 20000, nil), + makeNode("node2", 10000, 50000, nil), }, - args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, - runPreScore: true, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 73}, {Name: "node2", Score: 70}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: true, }, { - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 6000 = 1 - // Memory Fraction: 0 / 10000 = 0 - // Node1 std: (1 - 0) / 2 = 0.5 - // Node1 Score: (1 - 0.5)*MaxNodeScore = 50 - // Node1 Score: MaxNodeScore - (1 - 0) * MaxNodeScore = 0 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 6000 = 1 - // Memory Fraction 5000 / 10000 = 50% - // Node2 std: (1 - 0.5) / 2 = 0.25 - // Node2 Score: (1 - 0.25)*MaxNodeScore = 75 - pod: &v1.Pod{Spec: cpuOnly}, - nodes: []*v1.Node{makeNode("node1", 6000, 10000, nil), makeNode("node2", 6000, 10000, nil)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 75}}, + // Node1 + // CPU: 3000 -> 3000/3000 (100% -> 100%) + // Memory: 0 -> 5000/5000 (0% -> 100%) + // Score: 100 (50 -> 100) + // Node2 + // CPU: 0 -> 0/10000 (0% -> 0%) + // Memory: 0 -> 5000/5000 (0% -> 100%) + // Score: 50 (100 -> 50) + name: "resources requested, pods scheduled with resources, nodes to reach min/max score", + requestedPod: st.MakePod().Req(map[v1.ResourceName]string{"memory": "2000"}).Req(map[v1.ResourceName]string{"memory": "3000"}).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 3000, 5000, nil), + makeNode("node2", 3000, 5000, nil), + }, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 100}, {Name: "node2", Score: 50}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: true, + }, + { + // Node1 + // CPU: 3000 -> 6000/6000 (50% -> 100%) + // Memory: 0 -> 0/10000 (0% -> 0%) + // Score: 62 (75 -> 50) + // Node2 + // CPU: 3000 -> 6000/6000 (50% -> 100%) + // Memory: 5000 -> 5000/10000 (50% -> 50%) + // Score: 62 (100 -> 75) name: "requested resources at node capacity", - pods: []*v1.Pod{ - {Spec: cpuOnly}, - {Spec: cpuAndMemory}, + requestedPod: st.MakePod().Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 6000, 10000, nil), + makeNode("node2", 6000, 10000, nil), }, - args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, - runPreScore: true, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 62}, {Name: "node2", Score: 62}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: true, }, - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 3000 / 3500 = 85.71% - // Memory Fraction: 5000 / 40000 = 12.5% - // GPU Fraction: 4 / 8 = 0.5% - // Node1 std: sqrt(((0.8571 - 0.503) * (0.8571 - 0.503) + (0.503 - 0.125) * (0.503 - 0.125) + (0.503 - 0.5) * (0.503 - 0.5)) / 3) = 0.3002 - // Node1 Score: (1 - 0.3002)*MaxNodeScore = 70 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 3000 / 3500 = 85.71% - // Memory Fraction: 5000 / 40000 = 12.5% - // GPU Fraction: 1 / 8 = 12.5% - // Node2 std: sqrt(((0.8571 - 0.378) * (0.8571 - 0.378) + (0.378 - 0.125) * (0.378 - 0.125)) + (0.378 - 0.125) * (0.378 - 0.125)) / 3) = 0.345 - // Node2 Score: (1 - 0.358)*MaxNodeScore = 65 { - pod: st.MakePod().Req(map[v1.ResourceName]string{ - v1.ResourceMemory: "0", - "nvidia.com/gpu": "1", - }).Obj(), - nodes: []*v1.Node{makeNode("node1", 3500, 40000, scalarResource), makeNode("node2", 3500, 40000, scalarResource)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 70}, {Name: "node2", Score: 65}}, - name: "include scalar resource on a node for balanced resource allocation", - pods: []*v1.Pod{ - {Spec: cpuAndMemory}, - {Spec: cpuAndMemoryAndGPU}, + // Node1 + // CPU: 3000 -> 3000/3500 (85.7% -> 85.7%) + // Memory: 5000 -> 5000/40000 (12.5% -> 12.5%) + // GPU: 3 -> 4/8 (37.5% -> 50%) + // Score: 75 (69 -> 70) + // Node2 + // CPU: 3000 -> 3000/3500 (85.7% -> 85.7%) + // Memory: 5000 -> 5000/40000 (12.5% -> 12.5%) + // GPU: 0 -> 1/8 (0% -> 12.5%) + // Score: 76 (62 -> 65) + name: "scalar resource is included in the score computation if pod requests the scalar resource", + requestedPod: st.MakePod().Req(cpuAndMemoryAndGpu("0", "0", "1")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 3500, 40000, gpu(8)), + makeNode("node2", 3500, 40000, gpu(8)), }, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemoryAndGpu("2000m", "3000", "3")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 75}, {Name: "node2", Score: 76}}, args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{ {Name: string(v1.ResourceCPU), Weight: 1}, {Name: string(v1.ResourceMemory), Weight: 1}, @@ -245,23 +204,24 @@ func TestNodeResourcesBalancedAllocation(t *testing.T) { }}, runPreScore: true, }, - // Only one node (node1) has the scalar resource, pod doesn't request the scalar resource and the scalar resource should be skipped for consideration. - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 3000 / 3500 = 85.71% - // Memory Fraction: 5000 / 40000 = 12.5% - // Node1 std: (0.8571 - 0.125) / 2 = 0.36605 - // Node1 Score: (1 - 0.22705)*MaxNodeScore = 63 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 3000 / 3500 = 85.71% - // Memory Fraction: 5000 / 40000 = 12.5% - // Node2 std: (0.8571 - 0.125) / 2 = 0.36605 - // Node2 Score: (1 - 0.22705)*MaxNodeScore = 63 { - pod: &v1.Pod{Spec: cpuAndMemory}, - nodes: []*v1.Node{makeNode("node1", 3500, 40000, scalarResource), makeNode("node2", 3500, 40000, nil)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 63}, {Name: "node2", Score: 63}}, - name: "node without the scalar resource should skip the scalar resource", - pods: []*v1.Pod{}, + // Node1 + // CPU: 0 -> 3000/3500 (0% -> 85.7%) + // Memory: 0 -> 5000/40000 (0% -> 12.5%) + // GPU: 0 -> 0/8 (scalar resource not requested by pod, ignored) + // Score: 56 (100 -> 63) + // Node2 + // CPU: 0 -> 3000/3500 (0% -> 85.7%) + // Memory: 0 -> 5000/40000 (0% -> 12.5%) + // Score: 56 (100 -> 63) + name: "scalar resource is not included in the score computation if pod doesn't request the scalar resource", + requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 3500, 40000, gpu(8)), + makeNode("node2", 3500, 40000, nil), + }, + existingPods: []*v1.Pod{}, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 56}, {Name: "node2", Score: 56}}, args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{ {Name: string(v1.ResourceCPU), Weight: 1}, {Name: string(v1.ResourceMemory), Weight: 1}, @@ -270,61 +230,253 @@ func TestNodeResourcesBalancedAllocation(t *testing.T) { runPreScore: true, }, { - // Node1 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 10000 = 60% - // Memory Fraction: 5000 / 20000 = 25% - // Node1 std: (0.6 - 0.25) / 2 = 0.175 - // Node1 Score: (1 - 0.175)*MaxNodeScore = 82 - // Node2 scores on 0-MaxNodeScore scale - // CPU Fraction: 6000 / 10000 = 60% - // Memory Fraction: 10000 / 20000 = 50% - // Node2 std: (0.6 - 0.5) / 2 = 0.05 - // Node2 Score: (1 - 0.05)*MaxNodeScore = 95 - pod: &v1.Pod{Spec: cpuAndMemory}, - nodes: []*v1.Node{makeNode("node1", 10000, 20000, nil), makeNode("node2", 10000, 20000, nil)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 82}, {Name: "node2", Score: 95}}, + // Whether or not prescore was called, the end result should be the same. + // Node1 + // CPU: 3000 -> 6000/10000 (30% -> 60%) + // Memory: 0 -> 5000/20000 (0% -> 25%) + // Score: 73 (85 -> 82) + // Node2 + // CPU: 3000 -> 6000/10000 (30% -> 60%) + // Memory: 5000 -> 10000/20000 (25% -> 50%) + // Score: 74 (97 -> 95) name: "resources requested, pods scheduled with resources if PreScore not called", - pods: []*v1.Pod{ - {Spec: cpuOnly}, - {Spec: cpuAndMemory}, + requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + nodes: []*v1.Node{ + makeNode("node1", 10000, 20000, nil), + makeNode("node2", 10000, 20000, nil), + }, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 73}, {Name: "node2", Score: 74}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, + runPreScore: false, + }, + { + // Node1 + // CPU: 3000 -> 3000/3500 (86% -> 86%) + // Memory: 0 -> 0/40000 (0% -> 0%) + // DRA: 0 -> 1/8 (0% -> 12%) + // Score: 76 (60 -> 62) + // Node2 + // CPU: 3000 -> 3000/3500 (86% -> 86%) + // Memory: 5000 -> 5000/40000 (12% -> 12%) + // DRA: unsatisfiable + // Score: 75 (63 -> 63) + name: "include DRA resource on a node for balanced resource allocation", + requestedPod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(), + nodes: []*v1.Node{makeNode("node1", 3500, 40000, nil), makeNode("node2", 3500, 40000, nil)}, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 76}, {Name: "node2", Score: 75}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{ + {Name: string(v1.ResourceCPU), Weight: 1}, + {Name: string(v1.ResourceMemory), Weight: 1}, + {Name: extendedResourceName, Weight: 1}, + }}, + draObjects: []apiruntime.Object{ + deviceClassWithExtendResourceName, + st.MakeResourceSlice("node1", "test-driver").Device("device-1").Device("device-2").Device("device-3").Device("device-4").Device("device-5").Device("device-6").Device("device-7").Device("device-8").Obj(), + }, + runPreScore: true, + }, + { + // Node1 + // CPU: 3000 -> 3000/3500 (86% -> 86%) + // Memory: 0 -> 0/40000 (0% -> 0%) + // DRA: 0 -> 1/8 (0% -> 12%) + // Score: 76 (60 -> 62) + // Node2 + // CPU: 3000 -> 3000/3500 (86% -> 86%) + // Memory: 5000 -> 5000/40000 (12% -> 12%) + // DRA: unsatisfiable + // Score: 75 (63 -> 63) + name: "include DRA resource on a node for balanced resource allocation if PreScore not called", + requestedPod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(), + nodes: []*v1.Node{makeNode("node1", 3500, 40000, nil), makeNode("node2", 3500, 40000, nil)}, + existingPods: []*v1.Pod{ + st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(), + st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(), + }, + expectedScores: []fwk.NodeScore{{Name: "node1", Score: 76}, {Name: "node2", Score: 75}}, + args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{ + {Name: string(v1.ResourceCPU), Weight: 1}, + {Name: string(v1.ResourceMemory), Weight: 1}, + {Name: extendedResourceName, Weight: 1}, + }}, + draObjects: []apiruntime.Object{ + deviceClassWithExtendResourceName, + st.MakeResourceSlice("node1", "test-driver").Device("device-1").Device("device-2").Device("device-3").Device("device-4").Device("device-5").Device("device-6").Device("device-7").Device("device-8").Obj(), }, - args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, runPreScore: false, }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - snapshot := cache.NewSnapshot(test.pods, test.nodes) - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot)) - p, _ := NewBalancedAllocation(ctx, &test.args, fh, feature.Features{}) + tCtx.SyncTest(test.name, func(tCtx ktesting.TContext) { + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.draObjects != nil) + snapshot := cache.NewSnapshot(test.existingPods, test.nodes) + fh, _ := runtime.NewFramework(tCtx, nil, nil, runtime.WithSnapshotSharedLister(snapshot)) + defer func() { + tCtx.Cancel("test has completed") + runtime.WaitForShutdown(fh) + }() + p, _ := NewBalancedAllocation(tCtx, &test.args, fh, feature.Features{ + EnableDRAExtendedResource: test.draObjects != nil, + }) + + draManager := newTestDRAManager(tCtx, test.draObjects...) + p.(*BalancedAllocation).draManager = draManager + state := framework.NewCycleState() if test.runPreScore { - status := p.(fwk.PreScorePlugin).PreScore(ctx, state, test.pod, tf.BuildNodeInfos(test.nodes)) - if status.Code() != test.wantPreScoreStatusCode { - t.Errorf("unexpected status code, want: %v, got: %v", test.wantPreScoreStatusCode, status.Code()) + status := p.(fwk.PreScorePlugin).PreScore(tCtx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes)) + if status.Code() != test.expectedPreScoreStatusCode { + tCtx.Errorf("unexpected status code, want: %v, got: %v", test.expectedPreScoreStatusCode, status.Code()) } if status.Code() == fwk.Skip { - t.Log("skipping score test as PreScore returned skip") + tCtx.Log("skipping score test as PreScore returned skip") return } } for i := range test.nodes { nodeInfo, err := snapshot.Get(test.nodes[i].Name) if err != nil { - t.Errorf("failed to get node %q from snapshot: %v", test.nodes[i].Name, err) + tCtx.Errorf("failed to get node %q from snapshot: %v", test.nodes[i].Name, err) } - hostResult, status := p.(fwk.ScorePlugin).Score(ctx, state, test.pod, nodeInfo) + hostResult, status := p.(fwk.ScorePlugin).Score(tCtx, state, test.requestedPod, nodeInfo) if !status.IsSuccess() { - t.Errorf("Score is expected to return success, but didn't. Got status: %v", status) + tCtx.Errorf("Score is expected to return success, but didn't. Got status: %v", status) } - if diff := cmp.Diff(test.expectedList[i].Score, hostResult); diff != "" { - t.Errorf("unexpected score for host %v (-want,+got):\n%s", test.nodes[i].Name, diff) + if diff := cmp.Diff(test.expectedScores[i].Score, hostResult); diff != "" { + tCtx.Errorf("unexpected score for host %v (-want,+got):\n%s", test.nodes[i].Name, diff) } } }) } } + +func TestBalancedAllocationSignPod(t *testing.T) { + testBalancedAllocationSignPod(ktesting.Init(t)) +} +func testBalancedAllocationSignPod(tCtx ktesting.TContext) { + tests := map[string]struct { + name string + pod *v1.Pod + disableDRAExtendedResource bool + expectedFragments []fwk.SignFragment + expectedStatusCode fwk.Code + }{ + "pod with CPU and memory requests": { + pod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest( + st.MakePod().Req(cpuAndMemory("1000m", "2000")).Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + "best-effort pod with no requests": { + pod: st.MakePod().Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + "pod with multiple containers": { + pod: st.MakePod(). + Container("container1").Req(cpuAndMemory("500m", "1000")). + Container("container2").Req(cpuAndMemory("1500m", "3000")).Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod(). + Container("container1").Req(cpuAndMemory("500m", "1000")). + Container("container2").Req(cpuAndMemory("1500m", "3000")).Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + "DRA extended resource enabled - returns unschedulable": { + pod: st.MakePod().Req( + map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + v1.ResourceName(extendedResourceName): "1", + }).Obj(), + disableDRAExtendedResource: false, + expectedFragments: nil, + expectedStatusCode: fwk.Unschedulable, + }, + "DRA extended resource disabled": { + pod: st.MakePod().Req( + map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + v1.ResourceName(extendedResourceName): "1", + }).Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod(). + Container("container1").Req( + map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + v1.ResourceName(extendedResourceName): "1", + }).Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + } + + for name, test := range tests { + tCtx.SyncTest(name, func(tCtx ktesting.TContext) { + runOpts := []runtime.Option{} + var testDRAManager *dynamicresources.DefaultDRAManager + if !test.disableDRAExtendedResource { + testDRAManager = newTestDRAManager(tCtx, deviceClassWithExtendResourceName) + runOpts = append(runOpts, runtime.WithSharedDRAManager(testDRAManager)) + } + fh, _ := runtime.NewFramework(tCtx, nil, nil, runOpts...) + defer func() { + tCtx.Cancel("test has completed") + runtime.WaitForShutdown(fh) + }() + + p, err := NewBalancedAllocation(tCtx, &config.NodeResourcesBalancedAllocationArgs{}, fh, feature.Features{ + EnableDRAExtendedResource: !test.disableDRAExtendedResource, + }) + if err != nil { + tCtx.Fatalf("failed to create plugin: %v", err) + } + + ba := p.(*BalancedAllocation) + fragments, status := ba.SignPod(tCtx, test.pod) + + if status.Code() != test.expectedStatusCode { + tCtx.Errorf("unexpected status code, want: %v, got: %v, message: %v", test.expectedStatusCode, status.Code(), status.Message()) + } + + if test.expectedStatusCode == fwk.Success { + if diff := cmp.Diff(test.expectedFragments, fragments); diff != "" { + tCtx.Errorf("unexpected fragments, diff (-want,+got):\n%s", diff) + } + } + }) + } +} + +func cpuOnly(req string) map[v1.ResourceName]string { + return map[v1.ResourceName]string{v1.ResourceCPU: req} +} +func cpuAndMemory(cpuReq, memoryReq string) map[v1.ResourceName]string { + return map[v1.ResourceName]string{v1.ResourceCPU: cpuReq, v1.ResourceMemory: memoryReq} +} +func cpuAndMemoryAndGpu(cpuReq, memoryReq, gpuReq string) map[v1.ResourceName]string { + return map[v1.ResourceName]string{v1.ResourceCPU: cpuReq, v1.ResourceMemory: memoryReq, "nvidia.com/gpu": gpuReq} +} +func gpu(count int64) map[string]int64 { + return map[string]int64{"nvidia.com/gpu": count} +} diff --git a/pkg/scheduler/framework/plugins/noderesources/fit.go b/pkg/scheduler/framework/plugins/noderesources/fit.go index a423986d5c460..5c2bcc4497a72 100644 --- a/pkg/scheduler/framework/plugins/noderesources/fit.go +++ b/pkg/scheduler/framework/plugins/noderesources/fit.go @@ -47,6 +47,7 @@ var _ fwk.EnqueueExtensions = &Fit{} var _ fwk.PreScorePlugin = &Fit{} var _ fwk.ScorePlugin = &Fit{} var _ fwk.SignPlugin = &Fit{} +var _ fwk.PlacementScorePlugin = &Fit{} const ( // Name is the name of the plugin used in the plugin registry and configurations. @@ -62,27 +63,27 @@ const ( // nodeResourceStrategyTypeMap maps strategy to scorer implementation var nodeResourceStrategyTypeMap = map[config.ScoringStrategyType]scorer{ - config.LeastAllocated: func(args *config.NodeResourcesFitArgs) *resourceAllocationScorer { - resources := args.ScoringStrategy.Resources + config.LeastAllocated: func(strategy *config.ScoringStrategy) *resourceAllocationScorer { + resources := strategy.Resources return &resourceAllocationScorer{ Name: string(config.LeastAllocated), scorer: leastResourceScorer(resources), resources: resources, } }, - config.MostAllocated: func(args *config.NodeResourcesFitArgs) *resourceAllocationScorer { - resources := args.ScoringStrategy.Resources + config.MostAllocated: func(strategy *config.ScoringStrategy) *resourceAllocationScorer { + resources := strategy.Resources return &resourceAllocationScorer{ Name: string(config.MostAllocated), scorer: mostResourceScorer(resources), resources: resources, } }, - config.RequestedToCapacityRatio: func(args *config.NodeResourcesFitArgs) *resourceAllocationScorer { - resources := args.ScoringStrategy.Resources + config.RequestedToCapacityRatio: func(strategy *config.ScoringStrategy) *resourceAllocationScorer { + resources := strategy.Resources return &resourceAllocationScorer{ Name: string(config.RequestedToCapacityRatio), - scorer: requestedToCapacityRatioScorer(resources, args.ScoringStrategy.RequestedToCapacityRatio.Shape), + scorer: requestedToCapacityRatioScorer(resources, strategy.RequestedToCapacityRatio.Shape), resources: resources, } }, @@ -100,6 +101,7 @@ type Fit struct { enableInPlacePodLevelResourcesVerticalScaling bool handle fwk.Handle *resourceAllocationScorer + placementScorer *resourceAllocationScorer } // ScoreExtensions of the Score plugin. @@ -177,13 +179,34 @@ func (pl *Fit) SignPod(ctx context.Context, pod *v1.Pod) ([]fwk.SignFragment, *f EnableDRAExtendedResource: pl.enableDRAExtendedResource, } if pl.enableDRAExtendedResource { - return nil, fwk.NewStatus(fwk.Unschedulable, "signature disabled when dra extended resources enabled") + podRequest := computePodResourceRequest(pod, opts) + for rName, rQuant := range podRequest.ScalarResources { + // Skip in case request quantity is zero + if rQuant == 0 { + continue + } + if shouldDelegateResourceToDRA(rName, nil, pl.draManager, opts) { + return nil, fwk.NewStatus(fwk.Unschedulable, "signature disabled when dra extended resources are used in the cluster") + } + } } + return []fwk.SignFragment{ {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(pod, opts)}, }, nil } +func getScorer(strategy *config.ScoringStrategy) (*resourceAllocationScorer, error) { + if strategy == nil { + return nil, fmt.Errorf("scoring strategy not specified") + } + scorerFactory, exists := nodeResourceStrategyTypeMap[strategy.Type] + if !exists { + return nil, fmt.Errorf("scoring strategy %s is not supported", strategy.Type) + } + return scorerFactory(strategy), nil +} + // NewFit initializes a new plugin and returns it. func NewFit(_ context.Context, plArgs runtime.Object, h fwk.Handle, fts feature.Features) (fwk.Plugin, error) { args, ok := plArgs.(*config.NodeResourcesFitArgs) @@ -194,27 +217,23 @@ func NewFit(_ context.Context, plArgs runtime.Object, h fwk.Handle, fts feature. return nil, err } - if args.ScoringStrategy == nil { - return nil, fmt.Errorf("scoring strategy not specified") - } - - strategy := args.ScoringStrategy.Type - scorePlugin, exists := nodeResourceStrategyTypeMap[strategy] - if !exists { - return nil, fmt.Errorf("scoring strategy %s is not supported", strategy) + scorer, err := getScorer(args.ScoringStrategy) + if err != nil { + return nil, err } - - scorer := scorePlugin(args) if fts.EnableDRAExtendedResource { scorer.enableDRAExtendedResource = true scorer.draManager = h.SharedDRAManager() scorer.draFeatures = dynamicresources.AllocatorFeatures(fts) // Create a CEL cache for device class selector compilation // This cache improves performance by avoiding recompilation of the same CEL expressions - scorer.DRACaches.celCache = cel.NewCache(10, cel.Features{EnableConsumableCapacity: fts.EnableDRAConsumableCapacity}) + scorer.DRACaches.celCache = cel.NewCache(10, cel.Features{ + EnableConsumableCapacity: fts.EnableDRAConsumableCapacity, + EnableListTypeAttributes: fts.EnableDRAListTypeAttributes, + }) } - return &Fit{ + pl := &Fit{ ignoredResources: sets.New(args.IgnoredResources...), ignoredResourceGroups: sets.New(args.IgnoredResourceGroups...), enableInPlacePodVerticalScaling: fts.EnableInPlacePodVerticalScaling, @@ -225,42 +244,51 @@ func NewFit(_ context.Context, plArgs runtime.Object, h fwk.Handle, fts feature. enableDRAExtendedResource: fts.EnableDRAExtendedResource, enableInPlacePodLevelResourcesVerticalScaling: fts.EnableInPlacePodLevelResourcesVerticalScaling, resourceAllocationScorer: scorer, - }, nil + } + + if fts.EnableTopologyAwareWorkloadScheduling { + placementScorer, err := getScorer(&config.ScoringStrategy{ + Type: config.MostAllocated, + Resources: args.ScoringStrategy.Resources, + }) + if err != nil { + return nil, err + } + pl.placementScorer = placementScorer + } + + return pl, nil } // ResourceRequestsOptions contains feature gate flags for resource request computation. type ResourceRequestsOptions struct { - EnablePodLevelResources bool - EnableDRAExtendedResource bool + EnablePodLevelResources bool + EnableDRAExtendedResource bool + EnableDRANodeAllocatableResources bool } // shouldDelegateResourceToDRA checks if the given resource should be delegated to the DRA plugin. // It returns true if: // 1. The resource is not a scalar resource in the node's allocatable (not provided by device plugin) -// 2. Either: -// a. A device class mapping exists for the resource in the cache (when draManager is available), OR -// b. draManager is nil (e.g., kubelet admission check) and the resource name suggests it's a DRA resource +// 2. A device class mapping exists for the resource in the cache (when draManager is available) func shouldDelegateResourceToDRA(rName v1.ResourceName, nodeInfo fwk.NodeInfo, draManager fwk.SharedDRAManager, opts ResourceRequestsOptions) bool { if !opts.EnableDRAExtendedResource { return false } - if allocatable := nodeInfo.GetAllocatable().GetScalarResources()[rName]; allocatable > 0 { - return false + if nodeInfo != nil { + if allocatable := nodeInfo.GetAllocatable().GetScalarResources()[rName]; allocatable > 0 { + return false + } } - // If draManager is available, check the cache for a mapping - if draManager != nil { - cache := draManager.DeviceClassResolver() - return cache.GetDeviceClass(rName) != nil + if draManager == nil { + return false } - // If draManager is nil (e.g., kubelet admission check), delegate resources that are not in - // the node's allocatable. This allows pod to be admitted even when the kubelet - // doesn't have access to the device class cache. - // This can be removed once we have the fix for kubelet to admit pods with - // implicit extended resources or node's allocatable for the extended resource is zero. - return true + // If draManager is available, check the cache for a mapping + cache := draManager.DeviceClassResolver() + return cache.GetDeviceClass(rName) != nil } // computePodResourceRequest returns a framework.Resource that covers the largest @@ -294,7 +322,8 @@ func computePodResourceRequest(pod *v1.Pod, opts ResourceRequestsOptions) *preFi // pod hasn't scheduled yet so we don't need to worry about InPlacePodVerticalScalingEnabled reqs := resource.PodRequests(pod, resource.PodResourcesOptions{ // SkipPodLevelResources is set to false when PodLevelResources feature is enabled. - SkipPodLevelResources: !opts.EnablePodLevelResources, + SkipPodLevelResources: !opts.EnablePodLevelResources, + UseDRANodeAllocatableResourceClaimStatus: opts.EnableDRANodeAllocatableResources, }) result := &preFilterState{} result.SetMaxResource(reqs) @@ -755,3 +784,13 @@ func (f *Fit) Score(ctx context.Context, state fwk.CycleState, pod *v1.Pod, node return f.score(ctx, pod, nodeInfo, s.podRequests, s.draPreScoreState) } + +// PlacementScoreExtensions is not used by this plugin. +func (f *Fit) PlacementScoreExtensions() fwk.PlacementScoreExtensions { + return nil +} + +// ScorePlacement scores capacity ratio on all nodes in the placement including all assigned pod group pods' resource requests. +func (f *Fit) ScorePlacement(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placement *fwk.PodGroupAssignments) (int64, *fwk.Status) { + return f.placementScorer.scorePlacement(ctx, podGroup, placement) +} diff --git a/pkg/scheduler/framework/plugins/noderesources/fit_test.go b/pkg/scheduler/framework/plugins/noderesources/fit_test.go index 2953b71876a6e..1200713fe3807 100644 --- a/pkg/scheduler/framework/plugins/noderesources/fit_test.go +++ b/pkg/scheduler/framework/plugins/noderesources/fit_test.go @@ -17,7 +17,6 @@ limitations under the License. package noderesources import ( - "context" "fmt" "testing" @@ -29,13 +28,10 @@ import ( resourceapi "k8s.io/api/resource/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes/fake" featuregatetesting "k8s.io/component-base/featuregate/testing" - "k8s.io/dynamic-resource-allocation/deviceclass/extendedresourcecache" - "k8s.io/klog/v2/ktesting" - _ "k8s.io/klog/v2/ktesting/init" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/apis/config" @@ -47,7 +43,7 @@ import ( "k8s.io/kubernetes/pkg/scheduler/framework/runtime" st "k8s.io/kubernetes/pkg/scheduler/testing" tf "k8s.io/kubernetes/pkg/scheduler/testing/framework" - "k8s.io/kubernetes/pkg/scheduler/util/assumecache" + "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/ptr" ) @@ -150,7 +146,8 @@ func newPodLevelResourcesPod(pod *v1.Pod, podResources v1.ResourceRequirements) return pod } -func TestEnoughRequests(t *testing.T) { +func TestEnoughRequests(t *testing.T) { testEnoughRequests(ktesting.Init(t)) } +func testEnoughRequests(tCtx ktesting.TContext) { enoughPodsTests := []struct { pod *v1.Pod nodeInfo *framework.NodeInfo @@ -651,7 +648,7 @@ func TestEnoughRequests(t *testing.T) { nodeInfo: framework.NewNodeInfo(newResourcePod(framework.Resource{MilliCPU: 0, Memory: 0})), name: "extended resource backed by DRA, nil draManager", // When DRAExtendedResource is enabled but draManager is nil (e.g., kubelet path), - // the noderesources plugin delegates to DRA instead of reporting insufficient resources. + // the noderesources plugin must not delegate to DRA and process the request itself. wantInsufficientResources: []InsufficientResource{}, }, { @@ -705,9 +702,8 @@ func TestEnoughRequests(t *testing.T) { } for _, test := range enoughPodsTests { - t.Run(test.name, func(t *testing.T) { - - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.draExtendedResourceEnabled) + tCtx.SyncTest(test.name, func(tCtx ktesting.TContext) { + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.draExtendedResourceEnabled) node := v1.Node{Status: v1.NodeStatus{Capacity: makeResources(10, 20, 32, 5, 20, 5), Allocatable: makeAllocatableResources(10, 20, 32, 5, 20, 5)}} test.nodeInfo.SetNode(&node) @@ -715,80 +711,58 @@ func TestEnoughRequests(t *testing.T) { test.args.ScoringStrategy = defaultScoringStrategy } - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - client := fake.NewSimpleClientset(deviceClassWithExtendResourceName) - informerFactory := informers.NewSharedInformerFactory(client, 0) - claimsCache := assumecache.NewAssumeCache(logger, informerFactory.Resource().V1().ResourceClaims().Informer(), "resource claim", "", nil) - draManager := dynamicresources.NewDRAManager(ctx, claimsCache, nil, informerFactory) - if test.draExtendedResourceEnabled { - cache := draManager.DeviceClassResolver().(*extendedresourcecache.ExtendedResourceCache) - if _, err := informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(cache); err != nil { - logger.Error(err, "failed to add device class informer event handler") - } - } - informerFactory.Start(ctx.Done()) - t.Cleanup(func() { - // Now we can wait for all goroutines to stop. - informerFactory.Shutdown() - }) - informerFactory.WaitForCacheSync(ctx.Done()) - runOpts := []runtime.Option{ - runtime.WithSharedDRAManager(draManager), - } - fh, _ := runtime.NewFramework(ctx, nil, nil, runOpts...) - p, err := NewFit(ctx, &test.args, fh, plfeature.Features{EnablePodLevelResources: test.podLevelResourcesEnabled, EnableDRAExtendedResource: test.draExtendedResourceEnabled}) - if err != nil { - t.Fatal(err) + runOpts := []runtime.Option{} + var testDRAManager *dynamicresources.DefaultDRAManager + if !test.nilDRAManager && test.draExtendedResourceEnabled { + testDRAManager = newTestDRAManager(tCtx, deviceClassWithExtendResourceName) + runOpts = append(runOpts, runtime.WithSharedDRAManager(testDRAManager)) } + fh, _ := runtime.NewFramework(tCtx, nil, nil, runOpts...) + defer func() { + tCtx.Cancel("test has completed") + runtime.WaitForShutdown(fh) + }() + p, err := NewFit(tCtx, &test.args, fh, plfeature.Features{EnablePodLevelResources: test.podLevelResourcesEnabled, EnableDRAExtendedResource: test.draExtendedResourceEnabled}) + tCtx.ExpectNoError(err, "create fit plugin") cycleState := framework.NewCycleState() - _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil) + _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(tCtx, cycleState, test.pod, nil) if !preFilterStatus.IsSuccess() { - t.Errorf("prefilter failed with status: %v", preFilterStatus) + tCtx.Errorf("prefilter failed with status: %v", preFilterStatus) } - gotStatus := p.(fwk.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo) + gotStatus := p.(fwk.FilterPlugin).Filter(tCtx, cycleState, test.pod, test.nodeInfo) if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" { - t.Errorf("status does not match (-want,+got):\n%s", diff) + tCtx.Errorf("status does not match (-want,+got):\n%s", diff) } opts := ResourceRequestsOptions{EnablePodLevelResources: test.podLevelResourcesEnabled, EnableDRAExtendedResource: test.draExtendedResourceEnabled} state := computePodResourceRequest(test.pod, opts) - var testDRAManager fwk.SharedDRAManager - if !test.nilDRAManager { - testDRAManager = draManager - } gotInsufficientResources := fitsRequest(state, test.nodeInfo, p.(*Fit).ignoredResources, p.(*Fit).ignoredResourceGroups, testDRAManager, opts) if diff := cmp.Diff(test.wantInsufficientResources, gotInsufficientResources); diff != "" { - t.Errorf("insufficient resources do not match (-want,+got):\n%s", diff) + tCtx.Errorf("insufficient resources do not match (-want,+got):\n%s", diff) } }) } } -func TestPreFilterDisabled(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() +func TestPreFilterDisabled(t *testing.T) { testPreFilterDisabled(ktesting.Init(t)) } +func testPreFilterDisabled(tCtx ktesting.TContext) { pod := &v1.Pod{} nodeInfo := framework.NewNodeInfo() node := v1.Node{} nodeInfo.SetNode(&node) - p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{}) - if err != nil { - t.Fatal(err) - } + p, err := NewFit(tCtx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{}) + tCtx.ExpectNoError(err, "create fit plugin") cycleState := framework.NewCycleState() - gotStatus := p.(fwk.FilterPlugin).Filter(ctx, cycleState, pod, nodeInfo) + gotStatus := p.(fwk.FilterPlugin).Filter(tCtx, cycleState, pod, nodeInfo) wantStatus := fwk.AsStatus(fwk.ErrNotFound) if diff := cmp.Diff(wantStatus, gotStatus); diff != "" { - t.Errorf("status does not match (-want,+got):\n%s", diff) + tCtx.Errorf("status does not match (-want,+got):\n%s", diff) } } -func TestNotEnoughRequests(t *testing.T) { +func TestNotEnoughRequests(t *testing.T) { testNotEnoughRequests(ktesting.Init(t)) } +func testNotEnoughRequests(tCtx ktesting.TContext) { notEnoughPodsTests := []struct { pod *v1.Pod nodeInfo *framework.NodeInfo @@ -822,33 +796,29 @@ func TestNotEnoughRequests(t *testing.T) { }, } for _, test := range notEnoughPodsTests { - t.Run(test.name, func(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() + tCtx.Run(test.name, func(tCtx ktesting.TContext) { node := v1.Node{Status: v1.NodeStatus{Capacity: v1.ResourceList{}, Allocatable: makeAllocatableResources(10, 20, 1, 0, 0, 0)}} test.nodeInfo.SetNode(&node) - p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{}) - if err != nil { - t.Fatal(err) - } + p, err := NewFit(tCtx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{}) + tCtx.ExpectNoError(err, "create fit plugin") cycleState := framework.NewCycleState() - _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil) + _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(tCtx, cycleState, test.pod, nil) if !preFilterStatus.IsSuccess() { - t.Errorf("prefilter failed with status: %v", preFilterStatus) + tCtx.Errorf("prefilter failed with status: %v", preFilterStatus) } - gotStatus := p.(fwk.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo) + gotStatus := p.(fwk.FilterPlugin).Filter(tCtx, cycleState, test.pod, test.nodeInfo) if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" { - t.Errorf("status does not match (-want,+got):\n%s", diff) + tCtx.Errorf("status does not match (-want,+got):\n%s", diff) } }) } } -func TestStorageRequests(t *testing.T) { +func TestStorageRequests(t *testing.T) { testStorageRequests(ktesting.Init(t)) } +func testStorageRequests(tCtx ktesting.TContext) { storagePodsTests := []struct { pod *v1.Pod nodeInfo *framework.NodeInfo @@ -883,33 +853,29 @@ func TestStorageRequests(t *testing.T) { } for _, test := range storagePodsTests { - t.Run(test.name, func(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() + tCtx.Run(test.name, func(tCtx ktesting.TContext) { node := v1.Node{Status: v1.NodeStatus{Capacity: makeResources(10, 20, 32, 5, 20, 5), Allocatable: makeAllocatableResources(10, 20, 32, 5, 20, 5)}} test.nodeInfo.SetNode(&node) - p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{}) - if err != nil { - t.Fatal(err) - } + p, err := NewFit(tCtx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{}) + tCtx.ExpectNoError(err, "create fit plugin") cycleState := framework.NewCycleState() - _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil) + _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(tCtx, cycleState, test.pod, nil) if !preFilterStatus.IsSuccess() { - t.Errorf("prefilter failed with status: %v", preFilterStatus) + tCtx.Errorf("prefilter failed with status: %v", preFilterStatus) } - gotStatus := p.(fwk.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo) + gotStatus := p.(fwk.FilterPlugin).Filter(tCtx, cycleState, test.pod, test.nodeInfo) if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" { - t.Errorf("status does not match (-want,+got):\n%s", diff) + tCtx.Errorf("status does not match (-want,+got):\n%s", diff) } }) } } -func TestRestartableInitContainers(t *testing.T) { +func TestRestartableInitContainers(t *testing.T) { testRestartableInitContainers(ktesting.Init(t)) } +func testRestartableInitContainers(tCtx ktesting.TContext) { newPod := func() *v1.Pod { return &v1.Pod{ Spec: v1.PodSpec{ @@ -992,30 +958,25 @@ func TestRestartableInitContainers(t *testing.T) { } for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() + tCtx.Run(test.name, func(tCtx ktesting.TContext) { node := v1.Node{Status: v1.NodeStatus{Capacity: v1.ResourceList{}, Allocatable: makeAllocatableResources(2, 0, 1, 0, 0, 0)}} nodeInfo := framework.NewNodeInfo() nodeInfo.SetNode(&node) - p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{EnableSidecarContainers: test.enableSidecarContainers}) - if err != nil { - t.Fatal(err) - } + p, err := NewFit(tCtx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{EnableSidecarContainers: test.enableSidecarContainers}) + tCtx.ExpectNoError(err, "create fit plugin") cycleState := framework.NewCycleState() - _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil) + _, preFilterStatus := p.(fwk.PreFilterPlugin).PreFilter(tCtx, cycleState, test.pod, nil) if diff := cmp.Diff(test.wantPreFilterStatus, preFilterStatus); diff != "" { - t.Error("prefilter status does not match (-expected +actual):\n", diff) + tCtx.Error("prefilter status does not match (-expected +actual):\n", diff) } if !preFilterStatus.IsSuccess() { return } - filterStatus := p.(fwk.FilterPlugin).Filter(ctx, cycleState, test.pod, nodeInfo) + filterStatus := p.(fwk.FilterPlugin).Filter(tCtx, cycleState, test.pod, nodeInfo) if diff := cmp.Diff(test.wantFilterStatus, filterStatus); diff != "" { - t.Error("filter status does not match (-expected +actual):\n", diff) + tCtx.Error("filter status does not match (-expected +actual):\n", diff) } }) } @@ -1023,6 +984,9 @@ func TestRestartableInitContainers(t *testing.T) { } func TestFitScore(t *testing.T) { + testFitScore(ktesting.Init(t)) +} +func testFitScore(tCtx ktesting.TContext) { tests := []struct { name string requestedPod *v1.Pod @@ -1031,6 +995,7 @@ func TestFitScore(t *testing.T) { expectedPriorities fwk.NodeScoreList nodeResourcesFitArgs config.NodeResourcesFitArgs runPreScore bool + draObjects []apiruntime.Object }{ { name: "test case for ScoringStrategy RequestedToCapacityRatio case1", @@ -1250,44 +1215,97 @@ func TestFitScore(t *testing.T) { }, runPreScore: true, }, + { + name: "test case for ScoringStrategy LeastAllocated with Extended resources", + requestedPod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(), + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + }, + expectedPriorities: []fwk.NodeScore{{Name: "node1", Score: 66}, {Name: "node2", Score: 50}}, + draObjects: []apiruntime.Object{ + deviceClassWithExtendResourceName, + st.MakeResourceSlice("node1", "test-driver").Device("device-1").Device("device-2").Device("device-3").Obj(), + st.MakeResourceSlice("node2", "test-driver").Device("device-1").Device("device-2").Obj(), + }, + nodeResourcesFitArgs: config.NodeResourcesFitArgs{ + ScoringStrategy: &config.ScoringStrategy{ + Type: config.LeastAllocated, + Resources: []config.ResourceSpec{ + {Name: extendedResourceName, Weight: 1}, + }, + }, + }, + runPreScore: true, + }, + { + name: "test case for ScoringStrategy LeastAllocated with Extended resources if PreScore is not called", + requestedPod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(), + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + }, + expectedPriorities: []fwk.NodeScore{{Name: "node1", Score: 66}, {Name: "node2", Score: 50}}, + draObjects: []apiruntime.Object{ + deviceClassWithExtendResourceName, + st.MakeResourceSlice("node1", "test-driver").Device("device-1").Device("device-2").Device("device-3").Obj(), + st.MakeResourceSlice("node2", "test-driver").Device("device-1").Device("device-2").Obj(), + }, + nodeResourcesFitArgs: config.NodeResourcesFitArgs{ + ScoringStrategy: &config.ScoringStrategy{ + Type: config.LeastAllocated, + Resources: []config.ResourceSpec{ + {Name: extendedResourceName, Weight: 1}, + }, + }, + }, + }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - + tCtx.SyncTest(test.name, func(tCtx ktesting.TContext) { + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.draObjects != nil) state := framework.NewCycleState() snapshot := cache.NewSnapshot(test.existingPods, test.nodes) - fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot)) + fh, _ := runtime.NewFramework(tCtx, nil, nil, runtime.WithSnapshotSharedLister(snapshot)) + defer func() { + tCtx.Cancel("test has completed") + runtime.WaitForShutdown(fh) + }() args := test.nodeResourcesFitArgs - p, err := NewFit(ctx, &args, fh, plfeature.Features{}) + p, err := NewFit(tCtx, &args, fh, plfeature.Features{ + EnableDRAExtendedResource: test.draObjects != nil, + }) if err != nil { - t.Fatalf("unexpected error: %v", err) + tCtx.Fatalf("unexpected error: %v", err) + } + + if test.draObjects != nil { + draManager := newTestDRAManager(tCtx, test.draObjects...) + p.(*Fit).draManager = draManager } var gotPriorities fwk.NodeScoreList for _, n := range test.nodes { if test.runPreScore { - status := p.(fwk.PreScorePlugin).PreScore(ctx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes)) + status := p.(fwk.PreScorePlugin).PreScore(tCtx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes)) if !status.IsSuccess() { - t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status) + tCtx.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status) } } nodeInfo, err := snapshot.Get(n.Name) if err != nil { - t.Errorf("failed to get node %q from snapshot: %v", n.Name, err) + tCtx.Errorf("failed to get node %q from snapshot: %v", n.Name, err) } - score, status := p.(fwk.ScorePlugin).Score(ctx, state, test.requestedPod, nodeInfo) + score, status := p.(fwk.ScorePlugin).Score(tCtx, state, test.requestedPod, nodeInfo) if !status.IsSuccess() { - t.Errorf("Score is expected to return success, but didn't. Got status: %v", status) + tCtx.Errorf("Score is expected to return success, but didn't. Got status: %v", status) } gotPriorities = append(gotPriorities, fwk.NodeScore{Name: n.Name, Score: score}) } if diff := cmp.Diff(test.expectedPriorities, gotPriorities); diff != "" { - t.Errorf("expected:\n\t%+v,\ngot:\n\t%+v", test.expectedPriorities, gotPriorities) + tCtx.Errorf("expected:\n\t%+v,\ngot:\n\t%+v", test.expectedPriorities, gotPriorities) } }) } @@ -1382,8 +1400,6 @@ func BenchmarkTestFitScore(b *testing.B) { for _, test := range tests { b.Run(test.name, func(b *testing.B) { _, ctx := ktesting.NewTestContext(b) - ctx, cancel := context.WithCancel(ctx) - defer cancel() existingPods := []*v1.Pod{ st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "4000"}).Obj(), } @@ -1515,6 +1531,12 @@ func Test_isSchedulableAfterPodChange(t *testing.T) { enableInPlacePodVerticalScaling: true, expectedHint: fwk.QueueSkip, }, + "queue-on-nominated-pod-deleted": { + pod: st.MakePod().Name("pod1").UID("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).UID("uid0").Obj(), + oldObj: st.MakePod().Name("pod2").UID("pod2").Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).NominatedNodeName("fake").UID("uid1").Obj(), + enableInPlacePodVerticalScaling: true, + expectedHint: fwk.Queue, + }, "skip-queue-on-disable-inplace-pod-vertical-scaling": { pod: st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Obj(), oldObj: st.MakePod().Name("pod2").Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Node("fake").Obj(), @@ -1936,6 +1958,9 @@ func TestIsFit(t *testing.T) { } func TestHaveAnyRequestedResourcesIncreased(t *testing.T) { + testHaveAnyRequestedResourcesIncreased(ktesting.Init(t)) +} +func testHaveAnyRequestedResourcesIncreased(tCtx ktesting.TContext) { testCases := map[string]struct { pod *v1.Pod originalNode *v1.Node @@ -1984,19 +2009,19 @@ func TestHaveAnyRequestedResourcesIncreased(t *testing.T) { MilliCPU: 2, Memory: 2, EphemeralStorage: 2, - ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}, + ScalarResources: map[v1.ResourceName]int64{extendedResourceDRA: 2}, }), originalNode: st.MakeNode().Capacity(map[v1.ResourceName]string{ v1.ResourceCPU: "1", v1.ResourceMemory: "2", v1.ResourceEphemeralStorage: "3", - extendedResourceA: "4", + extendedResourceDRA: "4", }).Obj(), modifiedNode: st.MakeNode().Capacity(map[v1.ResourceName]string{ v1.ResourceCPU: "1", v1.ResourceMemory: "2", v1.ResourceEphemeralStorage: "1", - extendedResourceA: "1", + extendedResourceDRA: "1", }).Obj(), draExtendedResourceEnabled: true, expected: false, @@ -2006,7 +2031,7 @@ func TestHaveAnyRequestedResourcesIncreased(t *testing.T) { MilliCPU: 2, Memory: 2, EphemeralStorage: 2, - ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}, + ScalarResources: map[v1.ResourceName]int64{extendedResourceDRA: 2}, }), originalNode: st.MakeNode().Capacity(map[v1.ResourceName]string{ v1.ResourceCPU: "1", @@ -2023,13 +2048,13 @@ func TestHaveAnyRequestedResourcesIncreased(t *testing.T) { }, "requested-resources-dra-zero-capacity": { pod: newResourcePod(framework.Resource{ - ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}, + ScalarResources: map[v1.ResourceName]int64{extendedResourceDRA: 1}, }), originalNode: st.MakeNode().Capacity(map[v1.ResourceName]string{ - extendedResourceA: "0", + extendedResourceDRA: "0", }).Obj(), modifiedNode: st.MakeNode().Capacity(map[v1.ResourceName]string{ - extendedResourceA: "0", + extendedResourceDRA: "0", }).Obj(), draExtendedResourceEnabled: true, expected: true, @@ -2051,8 +2076,7 @@ func TestHaveAnyRequestedResourcesIncreased(t *testing.T) { v1.ResourceMemory: "2", v1.ResourceEphemeralStorage: "1", }).Obj(), - draExtendedResourceEnabled: false, - expected: false, + expected: false, }, "requested-resources-decreased": { pod: newResourcePod(framework.Resource{ @@ -2136,9 +2160,518 @@ func TestHaveAnyRequestedResourcesIncreased(t *testing.T) { }, } for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - if got := haveAnyRequestedResourcesIncreased(tc.pod, tc.originalNode, tc.modifiedNode, nil, ResourceRequestsOptions{EnableDRAExtendedResource: tc.draExtendedResourceEnabled}); got != tc.expected { - t.Errorf("expected: %v, got: %v", tc.expected, got) + tCtx.SyncTest(name, func(tCtx ktesting.TContext) { + var draManager *dynamicresources.DefaultDRAManager + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.draExtendedResourceEnabled) + if tc.draExtendedResourceEnabled { + draManager = newTestDRAManager(tCtx, deviceClassWithExtendResourceName) + } + if got := haveAnyRequestedResourcesIncreased(tc.pod, tc.originalNode, tc.modifiedNode, draManager, ResourceRequestsOptions{EnableDRAExtendedResource: tc.draExtendedResourceEnabled}); got != tc.expected { + tCtx.Errorf("expected: %v, got: %v", tc.expected, got) + } + }) + } +} + +func TestFitSignPod(t *testing.T) { + testFitSignPod(ktesting.Init(t)) +} +func testFitSignPod(tCtx ktesting.TContext) { + tests := map[string]struct { + name string + pod *v1.Pod + disableDRAExtendedResource bool + expectedFragments []fwk.SignFragment + expectedStatusCode fwk.Code + }{ + "pod with CPU and memory requests": { + pod: st.MakePod().Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + }).Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + }).Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + "best-effort pod with no requests": { + pod: st.MakePod().Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + "pod with multiple containers": { + pod: st.MakePod().Container("container1").Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "500m", + v1.ResourceMemory: "1000", + }).Container("container2").Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "1500m", + v1.ResourceMemory: "3000", + }).Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().Container("container1").Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "500m", + v1.ResourceMemory: "1000", + }).Container("container2").Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "1500m", + v1.ResourceMemory: "3000", + }).Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + "DRA extended resource enabled - returns unschedulable": { + pod: st.MakePod().Req(map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + v1.ResourceName(extendedResourceName): "1", + }).Obj(), + disableDRAExtendedResource: false, + expectedFragments: nil, + expectedStatusCode: fwk.Unschedulable, + }, + "DRA extended resource disabled": { + pod: st.MakePod().Req( + map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + v1.ResourceName(extendedResourceName): "1", + }).Obj(), + disableDRAExtendedResource: true, + expectedFragments: []fwk.SignFragment{ + {Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod(). + Container("container1").Req( + map[v1.ResourceName]string{ + v1.ResourceCPU: "1000m", + v1.ResourceMemory: "2000", + v1.ResourceName(extendedResourceName): "1", + }).Obj(), ResourceRequestsOptions{})}, + }, + expectedStatusCode: fwk.Success, + }, + } + + for name, test := range tests { + tCtx.SyncTest(name, func(tCtx ktesting.TContext) { + runOpts := []runtime.Option{} + var testDRAManager *dynamicresources.DefaultDRAManager + if !test.disableDRAExtendedResource { + testDRAManager = newTestDRAManager(tCtx, deviceClassWithExtendResourceName) + runOpts = append(runOpts, runtime.WithSharedDRAManager(testDRAManager)) + } + fh, _ := runtime.NewFramework(tCtx, nil, nil, runOpts...) + defer func() { + tCtx.Cancel("test has completed") + runtime.WaitForShutdown(fh) + }() + + p, err := NewFit(tCtx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, fh, plfeature.Features{ + EnableDRAExtendedResource: !test.disableDRAExtendedResource, + }) + if err != nil { + tCtx.Fatalf("failed to create plugin: %v", err) + } + + fit := p.(*Fit) + fragments, status := fit.SignPod(tCtx, test.pod) + + if status.Code() != test.expectedStatusCode { + tCtx.Errorf("unexpected status code, want: %v, got: %v, message: %v", test.expectedStatusCode, status.Code(), status.Message()) + } + + if test.expectedStatusCode == fwk.Success { + if diff := cmp.Diff(test.expectedFragments, fragments); diff != "" { + tCtx.Errorf("unexpected fragments, diff (-want,+got):\n%s", diff) + } + } + }) + } +} + +type podAssignment struct { + pod *v1.Pod + nodeName string +} + +func (pa *podAssignment) GetPod() *v1.Pod { + return pa.pod +} + +func (pa *podAssignment) GetNodeName() string { + return pa.nodeName +} + +func TestScorePlacement_Resources(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: true, + }) + + testCases := []struct { + name string + nodes []*v1.Node + placementNodeNames []string + podGroupPods []*v1.Pod + podGroupAssignments map[types.UID]string + preExistingPods []*v1.Pod + resourceSpec []config.ResourceSpec + expectedScore int64 + }{ + { + name: "Computes score based on total requests", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemory("1000m", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemory("2000m", "2000")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node2", + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 1}, + {Name: "memory", Weight: 1}, + }, + // CPU: (1000m + 2000m) / (5000m + 5000m) = 0.3 + // Memory: (0 + 2000) / (5000 + 5000) = 0.2 + // Score: MaxScore * (0.3 + 0.2) / 2 = 25 + expectedScore: 25, + }, + { + name: "Computes score using weights", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemory("1000m", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemory("2000m", "2000")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node2", + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 4}, + {Name: "memory", Weight: 1}, + }, + // CPU: (1000m + 2000m) / (5000m + 5000m) = 0.3 + // Memory: (0 + 2000) / (5000 + 5000) = 0.2 + // Score: MaxScore * (4 * 0.3 + 0.2) / 5 = 28 + expectedScore: 28, + }, + { + name: "Handles multiple pods per node", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemory("1000m", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemory("2000m", "2000")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node1", + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 4}, + {Name: "memory", Weight: 1}, + }, + // CPU: (1000m + 2000m) / (5000m + 5000m) = 0.3 + // Memory: (0 + 2000) / (5000 + 5000) = 0.2 + // Score: MaxScore * (4 * 0.3 + 0.2) / 5 = 28 + expectedScore: 28, + }, + { + name: "Does not include nodes from outside of placement", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + st.MakeNode().Name("node3").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemory("1000m", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemory("2000m", "2000")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node2", + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 1}, + {Name: "memory", Weight: 1}, + }, + expectedScore: 25, + }, + { + name: "Includes pre-existing pods from outside of pod group", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemory("1000m", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemory("2000m", "2000")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node2", + }, + preExistingPods: []*v1.Pod{ + st.MakePod().Node("node1").UID("baz").Req(cpuAndMemory("1000m", "0")).Obj(), + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 1}, + {Name: "memory", Weight: 1}, + }, + // CPU: (1000m + 2000m + 1000m) / (5000m + 5000m) = 0.4 + // Memory: (0 + 2000 + 0) / (5000 + 5000) = 0.2 + // Score: MaxScore * (0.3 + 0.2) / 2 = 30 + expectedScore: 30, + }, + { + name: "Includes scalar resources if any pod in pod group has nonzero requests", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemoryAndGpu("5000m", "5000", "5")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemoryAndGpu("5000m", "5000", "5")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemoryAndGpu("1000m", "0", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemoryAndGpu("1000m", "2000", "1")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node2", + }, + preExistingPods: []*v1.Pod{ + st.MakePod().Node("node1").UID("baz").Req(cpuAndMemoryAndGpu("1000m", "0", "0")).Obj(), + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 1}, + {Name: "memory", Weight: 1}, + {Name: "nvidia.com/gpu", Weight: 1}, + }, + // CPU: (1000m + 1000m + 1000m) / (5000m + 5000m) = 0.3 + // Memory: (0 + 2000 + 0) / (5000 + 5000) = 0.2 + // GPU: (0 + 1 + 0) / (5 + 5) = 0.1 + // Score: MaxScore * (0.3 + 0.2 + 0.1) / 3 = 20 + expectedScore: 20, + }, + { + name: "Does not include scalar resources if all pods in pod group have zero requests", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Capacity(cpuAndMemoryAndGpu("5000m", "5000", "5")).Obj(), + st.MakeNode().Name("node2").Capacity(cpuAndMemoryAndGpu("5000m", "5000", "5")).Obj(), + }, + placementNodeNames: []string{"node1", "node2"}, + podGroupPods: []*v1.Pod{ + st.MakePod().UID("foo").Req(cpuAndMemoryAndGpu("1000m", "0", "0")).Obj(), + st.MakePod().UID("bar").Req(cpuAndMemoryAndGpu("1000m", "2000", "0")).Obj(), + }, + podGroupAssignments: map[types.UID]string{ + "foo": "node1", + "bar": "node2", + }, + preExistingPods: []*v1.Pod{ + st.MakePod().Node("node1").UID("baz").Req(cpuAndMemoryAndGpu("1000m", "0", "1")).Obj(), + }, + resourceSpec: []config.ResourceSpec{ + {Name: "cpu", Weight: 1}, + {Name: "memory", Weight: 1}, + {Name: "nvidia.com/gpu", Weight: 1}, + }, + // CPU: (1000m + 1000m + 1000m) / (5000m + 5000m) = 0.3 + // Memory: (0 + 2000 + 0) / (5000 + 5000) = 0.2 + // GPU: not included as it isn't requested by the pods + // Score: MaxScore * (0.3 + 0.2) / 2 = 25 + expectedScore: 25, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tCtx := ktesting.Init(t) + snapshot := cache.NewSnapshot(tc.preExistingPods, tc.nodes) + fh, _ := runtime.NewFramework(tCtx, nil, nil, runtime.WithSnapshotSharedLister(snapshot)) + scoringStrategy := defaultScoringStrategy.DeepCopy() + scoringStrategy.Resources = tc.resourceSpec + plugin, err := NewFit(tCtx, &config.NodeResourcesFitArgs{ + ScoringStrategy: scoringStrategy, + }, fh, plfeature.Features{EnableTopologyAwareWorkloadScheduling: true}) + + if err != nil { + t.Fatal(err) + } + + placementNodes := make([]fwk.NodeInfo, 0, len(tc.placementNodeNames)) + for _, name := range tc.placementNodeNames { + nodeInfo, err := snapshot.NodeInfos().Get(name) + if err != nil { + t.Fatal(err) + } + placementNodes = append(placementNodes, nodeInfo) + } + proposedAssignments := make([]fwk.ProposedAssignment, 0, len(tc.podGroupPods)) + for _, pod := range tc.podGroupPods { + if nodeName, ok := tc.podGroupAssignments[pod.UID]; ok { + proposedAssignments = append(proposedAssignments, &podAssignment{ + pod: pod, + nodeName: nodeName, + }) + } + } + podGroupInfo := &framework.PodGroupInfo{ + UnscheduledPods: tc.podGroupPods, + } + podGroupAssignments := &fwk.PodGroupAssignments{ + Placement: &fwk.Placement{ + Nodes: placementNodes, + }, + ProposedAssignments: proposedAssignments, + } + + score, status := plugin.(*Fit).ScorePlacement(tCtx, framework.NewCycleState(), podGroupInfo, podGroupAssignments) + + if !status.IsSuccess() { + t.Fatalf("ScorePlacement failed: %v", status.AsError()) + } + if score != tc.expectedScore { + t.Fatalf("Unexpected score, want %v got %v", tc.expectedScore, score) + } + }) + } +} + +func TestComputePodResourceRequestWithNodeAllocatableDRA(t *testing.T) { + testComputePodResourceRequestWithNodeAllocatableDRA(ktesting.Init(t)) +} +func testComputePodResourceRequestWithNodeAllocatableDRA(tCtx ktesting.TContext) { + tests := []struct { + name string + pod *v1.Pod + expected *preFilterState + enableDRANodeAllocatableResources bool + }{ + { + name: "Pod with DRA claim and EnableDRANodeAllocatableResources enabled", + enableDRANodeAllocatableResources: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim", + }, + }, + }, + }, + }, + ResourceClaims: []v1.PodResourceClaim{ + { + Name: "node-allocatable-claim", + ResourceClaimName: ptr.To("node-allocatable-claim"), + }, + }, + }, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("50m"), + }, + }, + }, + }, + }, + expected: &preFilterState{ + Resource: framework.Resource{ + MilliCPU: 150, // NodeAllocatableResourceClaimStatus + standard request + Memory: 1024 * 1024 * 1024, + }, + }, + }, + { + name: "Pod with DRA claim and EnableDRANodeAllocatableResources disabled", + enableDRANodeAllocatableResources: false, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "c1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim", + }, + }, + }, + }, + }, + ResourceClaims: []v1.PodResourceClaim{ + { + Name: "node-allocatable-claim", + ResourceClaimName: ptr.To("node-allocatable-claim"), + }, + }, + }, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Containers: []string{"c1"}, + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: resource.MustParse("50m"), + }, + }, + }, + }, + }, + expected: &preFilterState{ + Resource: framework.Resource{ + MilliCPU: 100, // only standard request + Memory: 1024 * 1024 * 1024, + }, + }, + }, + } + + for _, tc := range tests { + tCtx.Run(tc.name, func(tCtx ktesting.TContext) { + opts := ResourceRequestsOptions{ + EnableDRANodeAllocatableResources: tc.enableDRANodeAllocatableResources, + } + result := computePodResourceRequest(tc.pod, opts) + + if diff := cmp.Diff(tc.expected.Resource, result.Resource); diff != "" { + tCtx.Errorf("computePodResourceRequest() returned diff (-want +got):\n%s", diff) } }) } diff --git a/pkg/scheduler/framework/plugins/noderesources/least_allocated.go b/pkg/scheduler/framework/plugins/noderesources/least_allocated.go index 20a456d9636d5..19fa2761d3fb5 100644 --- a/pkg/scheduler/framework/plugins/noderesources/least_allocated.go +++ b/pkg/scheduler/framework/plugins/noderesources/least_allocated.go @@ -27,8 +27,8 @@ import ( // // Details: // (cpu((capacity-requested)*MaxNodeScore*cpuWeight/capacity) + memory((capacity-requested)*MaxNodeScore*memoryWeight/capacity) + ...)/weightSum -func leastResourceScorer(resources []config.ResourceSpec) func([]int64, []int64) int64 { - return func(requested, allocable []int64) int64 { +func leastResourceScorer(resources []config.ResourceSpec) func([]int64, []int64, []int64) int64 { + return func(requested, _, allocable []int64) int64 { var nodeScore, weightSum int64 for i := range requested { if allocable[i] == 0 { diff --git a/pkg/scheduler/framework/plugins/noderesources/most_allocated.go b/pkg/scheduler/framework/plugins/noderesources/most_allocated.go index 3e694d1b22f2c..2d514c0540e94 100644 --- a/pkg/scheduler/framework/plugins/noderesources/most_allocated.go +++ b/pkg/scheduler/framework/plugins/noderesources/most_allocated.go @@ -27,8 +27,8 @@ import ( // // Details: // (cpu(MaxNodeScore * requested * cpuWeight / capacity) + memory(MaxNodeScore * requested * memoryWeight / capacity) + ...) / weightSum -func mostResourceScorer(resources []config.ResourceSpec) func(requested, allocable []int64) int64 { - return func(requested, allocable []int64) int64 { +func mostResourceScorer(resources []config.ResourceSpec) func(requested, _, allocable []int64) int64 { + return func(requested, _, allocable []int64) int64 { var nodeScore, weightSum int64 for i := range requested { if allocable[i] == 0 { diff --git a/pkg/scheduler/framework/plugins/noderesources/requested_to_capacity_ratio.go b/pkg/scheduler/framework/plugins/noderesources/requested_to_capacity_ratio.go index f6c7838be3e66..a15500d4bc316 100644 --- a/pkg/scheduler/framework/plugins/noderesources/requested_to_capacity_ratio.go +++ b/pkg/scheduler/framework/plugins/noderesources/requested_to_capacity_ratio.go @@ -28,7 +28,7 @@ const maxUtilization = 100 // buildRequestedToCapacityRatioScorerFunction allows users to apply bin packing // on core resources like CPU, Memory as well as extended resources like accelerators. -func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.FunctionShape, resources []config.ResourceSpec) func([]int64, []int64) int64 { +func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.FunctionShape, resources []config.ResourceSpec) func([]int64, []int64, []int64) int64 { rawScoringFunction := helper.BuildBrokenLinearFunction(scoringFunctionShape) resourceScoringFunction := func(requested, capacity int64) int64 { if capacity == 0 || requested > capacity { @@ -37,7 +37,7 @@ func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.Fun return rawScoringFunction(requested * maxUtilization / capacity) } - return func(requested, allocable []int64) int64 { + return func(requested, _, allocable []int64) int64 { var nodeScore, weightSum int64 for i := range requested { if allocable[i] == 0 { @@ -57,7 +57,7 @@ func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.Fun } } -func requestedToCapacityRatioScorer(resources []config.ResourceSpec, shape []config.UtilizationShapePoint) func([]int64, []int64) int64 { +func requestedToCapacityRatioScorer(resources []config.ResourceSpec, shape []config.UtilizationShapePoint) func([]int64, []int64, []int64) int64 { shapes := make([]helper.FunctionShapePoint, 0, len(shape)) for _, point := range shape { shapes = append(shapes, helper.FunctionShapePoint{ diff --git a/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go b/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go index b4f6f16b4bf9b..ab694a4441be9 100644 --- a/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go +++ b/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go @@ -36,7 +36,7 @@ import ( ) // scorer is decorator for resourceAllocationScorer -type scorer func(args *config.NodeResourcesFitArgs) *resourceAllocationScorer +type scorer func(strategy *config.ScoringStrategy) *resourceAllocationScorer // DRACaches holds various caches used for DRA-related computations type DRACaches struct { @@ -58,7 +58,7 @@ type resourceAllocationScorer struct { // used to decide whether to use Requested or NonZeroRequested for // cpu and memory. useRequested bool - scorer func(requested, allocable []int64) int64 + scorer func(requested, allocated, allocatable []int64) int64 resources []config.ResourceSpec draFeatures structured.Features draManager fwk.SharedDRAManager @@ -150,19 +150,9 @@ func (r *resourceAllocationScorer) score( return 0, fwk.NewStatus(fwk.Error, "resources not found") } - requested := make([]int64, len(r.resources)) - allocatable := make([]int64, len(r.resources)) - for i := range r.resources { - alloc, req := r.calculateResourceAllocatableRequest(ctx, nodeInfo, v1.ResourceName(r.resources[i].Name), podRequests[i], draPreScoreState) - // Only fill the extended resource entry when it's non-zero. - if alloc == 0 { - continue - } - allocatable[i] = alloc - requested[i] = req - } + requested, allocated, allocatable := r.calculateNodeAllocatableRequest(ctx, nodeInfo, podRequests, draPreScoreState) - score := r.scorer(requested, allocatable) + score := r.scorer(requested, allocated, allocatable) if loggerV := logger.V(10); loggerV.Enabled() { // Serializing these maps is costly. loggerV.Info("Listed internal info for allocatable resources, requested resources and score", "pod", @@ -174,34 +164,55 @@ func (r *resourceAllocationScorer) score( return score, nil } +func (r *resourceAllocationScorer) calculateNodeAllocatableRequest( + ctx context.Context, + nodeInfo fwk.NodeInfo, + podRequests []int64, + draPreScoreState *draPreScoreState, +) (requested []int64, allocated []int64, allocatable []int64) { + requested = make([]int64, len(r.resources)) + allocated = make([]int64, len(r.resources)) + allocatable = make([]int64, len(r.resources)) + for i := range r.resources { + resource := v1.ResourceName(r.resources[i].Name) + // If it's an extended resource, and the pod doesn't request it. + // We don't fill the resource entry as an implication to bypass scoring on it. + if podRequests[i] == 0 && schedutil.IsScalarResourceName(resource) { + continue + } + nodeAllocatable, nodeAllocated := r.calculateResourceAllocatableRequest(ctx, nodeInfo, resource, draPreScoreState) + // Only fill the extended resource entry when it's non-zero. + if nodeAllocatable == 0 { + continue + } + allocatable[i] = nodeAllocatable + allocated[i] = nodeAllocated + requested[i] = allocated[i] + podRequests[i] + } + return requested, allocated, allocatable +} + // calculateResourceAllocatableRequest returns 2 parameters: // - 1st param: quantity of allocatable resource on the node. // - 2nd param: aggregated quantity of requested resource on the node. -// Note: if it's an extended resource, and the pod doesn't request it, (0, 0) is returned. func (r *resourceAllocationScorer) calculateResourceAllocatableRequest( ctx context.Context, nodeInfo fwk.NodeInfo, resource v1.ResourceName, - podRequest int64, draPreScoreState *draPreScoreState, -) (int64, int64) { +) (allocatable int64, allocated int64) { requested := nodeInfo.GetNonZeroRequested() if r.useRequested { requested = nodeInfo.GetRequested() } - // If it's an extended resource, and the pod doesn't request it. We return (0, 0) - // as an implication to bypass scoring on this resource. - if podRequest == 0 && schedutil.IsScalarResourceName(resource) { - return 0, 0 - } switch resource { case v1.ResourceCPU: - return nodeInfo.GetAllocatable().GetMilliCPU(), (requested.GetMilliCPU() + podRequest) + return nodeInfo.GetAllocatable().GetMilliCPU(), requested.GetMilliCPU() case v1.ResourceMemory: - return nodeInfo.GetAllocatable().GetMemory(), (requested.GetMemory() + podRequest) + return nodeInfo.GetAllocatable().GetMemory(), requested.GetMemory() case v1.ResourceEphemeralStorage: - return nodeInfo.GetAllocatable().GetEphemeralStorage(), (nodeInfo.GetRequested().GetEphemeralStorage() + podRequest) + return nodeInfo.GetAllocatable().GetEphemeralStorage(), nodeInfo.GetRequested().GetEphemeralStorage() default: allocatable, exists := nodeInfo.GetAllocatable().GetScalarResources()[resource] if allocatable == 0 && r.enableDRAExtendedResource && draPreScoreState != nil { @@ -209,11 +220,11 @@ func (r *resourceAllocationScorer) calculateResourceAllocatableRequest( // Calculate allocatable and requested for resources backed by DRA. allocatable, allocated := r.calculateDRAExtendedResourceAllocatableRequest(ctx, nodeInfo.Node(), resource, draPreScoreState) if allocatable > 0 { - return allocatable, allocated + podRequest + return allocatable, allocated } } if exists { - return allocatable, (nodeInfo.GetRequested().GetScalarResources()[resource] + podRequest) + return allocatable, nodeInfo.GetRequested().GetScalarResources()[resource] } } klog.FromContext(ctx).V(10).Info("Requested resource is omitted for node score calculation", "resourceName", resource) @@ -490,3 +501,44 @@ func (r *resourceAllocationScorer) deviceMatchesClass(ctx context.Context, devic return true, nil } + +func (r *resourceAllocationScorer) scorePlacement( + ctx context.Context, + podGroupInfo fwk.PodGroupInfo, + podGroupAssignments *fwk.PodGroupAssignments, +) (int64, *fwk.Status) { + requested := make([]int64, len(r.resources)) + // Calculate requests for the pod group pods scheduled for this placement + for _, assignment := range podGroupAssignments.ProposedAssignments { + pod := assignment.GetPod() + podRequests := r.calculatePodResourceRequestList(pod, r.resources) + for i := range len(r.resources) { + requested[i] += podRequests[i] + } + } + allocatable := make([]int64, len(r.resources)) + allocated := make([]int64, len(r.resources)) + // Calculate resources on all nodes in this placement + for _, node := range podGroupAssignments.Nodes { + _, nodeAllocated, nodeAllocatable := r.calculateNodeAllocatableRequest(ctx, node, requested, nil) + for i := range r.resources { + allocatable[i] += nodeAllocatable[i] + allocated[i] += nodeAllocated[i] + // requested includes both the already existing pods + // and the pod group pods that are being scheduled for this placement + requested[i] += nodeAllocated[i] + } + } + + score := r.scorer(requested, allocated, allocatable) + + logger := klog.FromContext(ctx) + if loggerV := logger.V(10); loggerV.Enabled() { // Serializing these maps is costly. + loggerV.Info("Listed internal info for placement's allocatable resources, requested resources and placement score", "podGroup", + klog.KRef(podGroupInfo.GetNamespace(), podGroupInfo.GetName()), "placement", podGroupAssignments.Name, "resourceAllocationScorer", r.Name, + "allocatableResource", allocatable, "requestedResource", requested, "resourceScore", score, + ) + } + + return score, nil +} diff --git a/pkg/scheduler/framework/plugins/noderesources/resource_allocation_test.go b/pkg/scheduler/framework/plugins/noderesources/resource_allocation_test.go index f6b6c2aa3ae27..98b0c3b472949 100644 --- a/pkg/scheduler/framework/plugins/noderesources/resource_allocation_test.go +++ b/pkg/scheduler/framework/plugins/noderesources/resource_allocation_test.go @@ -17,7 +17,6 @@ limitations under the License. package noderesources import ( - "context" "testing" "github.com/google/go-cmp/cmp" @@ -36,7 +35,6 @@ import ( "k8s.io/dynamic-resource-allocation/deviceclass/extendedresourcecache" "k8s.io/dynamic-resource-allocation/resourceslice/tracker" "k8s.io/dynamic-resource-allocation/structured" - "k8s.io/klog/v2/ktesting" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/apis/config" @@ -45,6 +43,7 @@ import ( st "k8s.io/kubernetes/pkg/scheduler/testing" "k8s.io/kubernetes/pkg/scheduler/util" "k8s.io/kubernetes/pkg/scheduler/util/assumecache" + "k8s.io/kubernetes/test/utils/ktesting" "k8s.io/utils/ptr" ) @@ -263,6 +262,9 @@ func TestResourceAllocationScorerCalculateRequests(t *testing.T) { } func TestCalculateResourceAllocatableRequest(t *testing.T) { + testCalculateResourceAllocatableRequest(ktesting.Init(t)) +} +func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) { // Initialize test variables nodeName := "resource-node" driverName := "test-driver" @@ -284,44 +286,40 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { enableDRAExtendedResource bool node *v1.Node extendedResource v1.ResourceName - objects []apiruntime.Object - podRequest int64 + draObjects []apiruntime.Object expectedAllocatable int64 - expectedRequested int64 + expectedAllocated int64 }{ "device-plugin-resource-feature-disabled": { enableDRAExtendedResource: false, node: st.MakeNode().Name(nodeName).Capacity(map[v1.ResourceName]string{explicitExtendedResource: "4"}).Obj(), extendedResource: explicitExtendedResource, - podRequest: 1, expectedAllocatable: 4, - expectedRequested: 1, + expectedAllocated: 0, }, "device-plugin-resource-feature-enabled": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Capacity(map[v1.ResourceName]string{explicitExtendedResource: "4"}).Obj(), extendedResource: explicitExtendedResource, - podRequest: 1, expectedAllocatable: 4, - expectedRequested: 1, + expectedAllocated: 0, }, "DRA-backed-resource-explicit": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Obj(), extendedResource: explicitExtendedResource, - objects: []apiruntime.Object{ + draObjects: []apiruntime.Object{ deviceClassWithExtendResourceName, st.MakeResourceSlice(nodeName, driverName).Device("device-1").Obj(), }, - podRequest: 1, - expectedAllocatable: 1, - expectedRequested: 1, + expectedAllocatable: 1, // 1 device `device-1` + expectedAllocated: 0, }, "DRA-backed-resource-implicit": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Obj(), extendedResource: implicitExtendedResource, - objects: []apiruntime.Object{ + draObjects: []apiruntime.Object{ &resourceapi.DeviceClass{ ObjectMeta: metav1.ObjectMeta{ Name: deviceClassName, @@ -329,24 +327,24 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { }, st.MakeResourceSlice(nodeName, driverName).Device("device-1").Obj(), }, - podRequest: 1, - expectedAllocatable: 1, - expectedRequested: 1, + expectedAllocatable: 1, // 1 device `device-1` + expectedAllocated: 0, }, "DRA-backed-resource-no-slices": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Obj(), extendedResource: explicitExtendedResource, - objects: []apiruntime.Object{deviceClassWithExtendResourceName}, - podRequest: 1, - expectedAllocatable: 0, - expectedRequested: 0, + draObjects: []apiruntime.Object{ + deviceClassWithExtendResourceName, + }, + expectedAllocatable: 0, + expectedAllocated: 0, }, "DRA-backed-resource-with-allocated-device": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Obj(), extendedResource: explicitExtendedResource, - objects: []apiruntime.Object{ + draObjects: []apiruntime.Object{ deviceClassWithExtendResourceName, st.MakeResourceSlice(nodeName, driverName).Devices("device-1", "device-2").Obj(), // Create a resource claim that fully allocates device-1 @@ -367,15 +365,14 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { }). Obj(), }, - podRequest: 1, - expectedAllocatable: 2, - expectedRequested: 2, // 1 allocated + 1 requested + expectedAllocatable: 2, // 2 allocatable devices `device-1`, `device-2` + expectedAllocated: 1, // 1 allocated by `testClaim` }, "DRA-backed-resource-with-shared-device-allocation": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Obj(), extendedResource: explicitExtendedResource, - objects: []apiruntime.Object{ + draObjects: []apiruntime.Object{ deviceClassWithExtendResourceName, st.MakeResourceSlice(nodeName, driverName).Devices("device-1", "device-2").Obj(), // Create a resource claim with shared device allocation (consumable capacity) @@ -397,15 +394,14 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { }). Obj(), }, - podRequest: 1, - expectedAllocatable: 2, - expectedRequested: 2, // 1 allocated (shared) + 1 requested + expectedAllocatable: 2, // 2 allocatable devices `device-1`, `device-2` + expectedAllocated: 1, // 1 allocated (shared) by `testClaim` }, "DRA-backed-resource-multiple-devices-mixed-allocation": { enableDRAExtendedResource: true, node: st.MakeNode().Name(nodeName).Obj(), extendedResource: explicitExtendedResource, - objects: []apiruntime.Object{ + draObjects: []apiruntime.Object{ deviceClassWithExtendResourceName, st.MakeResourceSlice(nodeName, driverName).Devices("device-1", "device-2", "device-3").Obj(), // Mix of fully allocated and shared device allocations @@ -445,15 +441,14 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { Obj(), // device-3 remains unallocated }, - podRequest: 1, - expectedAllocatable: 3, - expectedRequested: 3, // 2 allocated (1 full + 1 shared) + 1 requested + expectedAllocatable: 3, // 3 allocatable devices `device-1`, `device-2`, `device-3` + expectedAllocated: 2, // 2 allocated (1 full `test-claim-1` + 1 shared `test-claim-2`) }, "DRA-backed-resource-with-per-device-node-selection": { enableDRAExtendedResource: true, - node: st.MakeNode().Name(nodeName).Obj(), + node: st.MakeNode().Name(nodeName).Label("zone", "us-east-1a").Obj(), extendedResource: explicitExtendedResource, - objects: []apiruntime.Object{ + draObjects: []apiruntime.Object{ &resourceapi.DeviceClass{ ObjectMeta: metav1.ObjectMeta{ Name: deviceClassName, @@ -500,13 +495,26 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { }, }, { - Name: "device-3", - AllNodes: ptr.To(true), // This device matches all nodes + Name: "device-3", + // Use a node selector to ensure nodeMatches is exercised for this device + NodeSelector: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "zone", + Operator: v1.NodeSelectorOpIn, + Values: []string{"us-east-1a"}, + }, + }, + }, + }, + }, Attributes: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ "model": {StringValue: ptr.To("SOME-XZY")}, }, Capacity: map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{ - "memory": {Value: resource.MustParse("24Gi")}, // 24GB GPU - matches CEL (>= 8GB) + "memory": {Value: resource.MustParse("24Gi")}, // 24GB GPU - matches CEL (>= 8GiB) }, }, }, @@ -530,57 +538,17 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { }). Obj(), }, - podRequest: 1, - expectedAllocatable: 2, // Only device-1 (matches test-node) and device-3 (matches all nodes) - expectedRequested: 2, // 1 allocated (device-1) + 1 requested + expectedAllocatable: 2, // device-1 matches the test node and device-3 matches via its selector + expectedAllocated: 1, // 1 allocated (device-1) }, } for name, tc := range tests { - t.Run(name, func(t *testing.T) { + tCtx.SyncTest(name, func(tCtx ktesting.TContext) { // Setup environment, create required objects - logger, tCtx := ktesting.NewTestContext(t) - tCtx, cancel := context.WithCancel(tCtx) - defer cancel() - - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.enableDRAExtendedResource) - - client := fake.NewClientset(tc.objects...) - informerFactory := informers.NewSharedInformerFactory(client, 0) - resourceSliceTrackerOpts := tracker.Options{ - SliceInformer: informerFactory.Resource().V1().ResourceSlices(), - TaintInformer: informerFactory.Resource().V1alpha3().DeviceTaintRules(), - ClassInformer: informerFactory.Resource().V1().DeviceClasses(), - KubeClient: client, - } - resourceSliceTracker, err := tracker.StartTracker(tCtx, resourceSliceTrackerOpts) - if err != nil { - t.Fatalf("couldn't start resource slice tracker: %v", err) - } - draManager := dynamicresources.NewDRAManager( - tCtx, - assumecache.NewAssumeCache( - logger, - informerFactory.Resource().V1().ResourceClaims().Informer(), - "resource claim", - "", - nil), - resourceSliceTracker, - informerFactory) - - if tc.enableDRAExtendedResource { - cache := draManager.DeviceClassResolver().(*extendedresourcecache.ExtendedResourceCache) - if _, err := informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(cache); err != nil { - logger.Error(err, "failed to add device class informer event handler") - } - } + featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.enableDRAExtendedResource) - informerFactory.Start(tCtx.Done()) - t.Cleanup(func() { - // Now we can wait for all goroutines to stop. - informerFactory.Shutdown() - }) - informerFactory.WaitForCacheSync(tCtx.Done()) + draManager := newTestDRAManager(tCtx, tc.draObjects...) nodeInfo := framework.NewNodeInfo() nodeInfo.SetNode(tc.node) @@ -599,22 +567,69 @@ func TestCalculateResourceAllocatableRequest(t *testing.T) { var status *fwk.Status draPreScoreState, status = getDRAPreScoredParams(draManager, []config.ResourceSpec{{Name: string(tc.extendedResource)}}) if status != nil { - t.Fatalf("getting DRA pre-scored params failed: %v", status) + tCtx.Fatalf("getting DRA pre-scored params failed: %v", status) } } // Test calculateResourceAllocatableRequest API - allocatable, requested := scorer.calculateResourceAllocatableRequest(tCtx, nodeInfo, tc.extendedResource, tc.podRequest, draPreScoreState) + allocatable, allocated := scorer.calculateResourceAllocatableRequest(tCtx, nodeInfo, tc.extendedResource, draPreScoreState) if !cmp.Equal(allocatable, tc.expectedAllocatable) { - t.Errorf("Expected allocatable=%v, but got allocatable=%v", tc.expectedAllocatable, allocatable) + tCtx.Errorf("Expected allocatable=%v, but got %v", tc.expectedAllocatable, allocatable) } - if !cmp.Equal(requested, tc.expectedRequested) { - t.Errorf("Expected requested=%v, but got requested=%v", tc.expectedRequested, requested) + if !cmp.Equal(allocated, tc.expectedAllocated) { + tCtx.Errorf("Expected allocated=%v, but got %v", tc.expectedAllocated, allocated) } }) } } +// newTestDRAManager creates a DefaultDRAManager for testing purposes. +// Only usable in a syntest bubble. +func newTestDRAManager(tCtx ktesting.TContext, objects ...apiruntime.Object) *dynamicresources.DefaultDRAManager { + tCtx = tCtx.WithCancel() + client := fake.NewClientset(objects...) + informerFactory := informers.NewSharedInformerFactory(client, 0) + resourceSliceTrackerOpts := tracker.Options{ + SliceInformer: informerFactory.Resource().V1().ResourceSlices(), + TaintInformer: informerFactory.Resource().V1beta2().DeviceTaintRules(), + ClassInformer: informerFactory.Resource().V1().DeviceClasses(), + KubeClient: client, + } + resourceSliceTracker, err := tracker.StartTracker(tCtx, resourceSliceTrackerOpts) + tCtx.ExpectNoError(err, "couldn't start resource slice tracker") + draManager := dynamicresources.NewDRAManager( + tCtx, + assumecache.NewAssumeCache( + tCtx.Logger(), + informerFactory.Resource().V1().ResourceClaims().Informer(), + "resource claim", + "", + nil), + resourceSliceTracker, + informerFactory) + + cache := draManager.DeviceClassResolver().(*extendedresourcecache.ExtendedResourceCache) + handle, err := informerFactory.Resource().V1().DeviceClasses().Informer().AddEventHandler(cache) + tCtx.ExpectNoError(err, "add device class informer event handler") + tCtx.Cleanup(func() { + _ = informerFactory.Resource().V1().DeviceClasses().Informer().RemoveEventHandler(handle) + }) + + informerFactory.Start(tCtx.Done()) + tCtx.Cleanup(func() { + tCtx.Cancel("test has completed") + // Now we can wait for all goroutines to stop. + informerFactory.Shutdown() + }) + informerFactory.WaitForCacheSync(tCtx.Done()) + + // Wait for full initialization of manager, including + // processing of all informer events. + tCtx.Wait() + + return draManager +} + // getCachedDeviceMatch checks the cache for a DeviceMatches result // returns (matches, found) func (r *resourceAllocationScorer) getCachedDeviceMatch(expression string, driver string, poolName string, deviceName string) (bool, bool) { @@ -842,6 +857,45 @@ func TestNodeMatchCaching(t *testing.T) { } } +func TestNodeMatchesCacheHit(t *testing.T) { + scorer := &resourceAllocationScorer{ + DRACaches: DRACaches{ + celCache: cel.NewCache(1, cel.Features{}), + }, + } + + node := st.MakeNode().Name("cache-node").Label("zone", "us-east-1a").Obj() + selector := &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "zone", + Operator: v1.NodeSelectorOpIn, + Values: []string{"us-east-1a"}, + }, + }, + }, + }, + } + + firstMatch, err := scorer.nodeMatches(node, "", false, selector) + if err != nil { + t.Fatalf("unexpected error while evaluating selector: %v", err) + } + if !firstMatch { + t.Fatalf("expected nodeMatches to return true for the selector") + } + + secondMatch, err := scorer.nodeMatches(node, "", false, selector) + if err != nil { + t.Fatalf("unexpected error while hitting cache: %v", err) + } + if !secondMatch { + t.Fatalf("expected cached nodeMatches to be true") + } +} + func BenchmarkNodeMatchCaching(b *testing.B) { // Create test node testNode := &v1.Node{ diff --git a/pkg/scheduler/framework/plugins/noderesources/test_util.go b/pkg/scheduler/framework/plugins/noderesources/util_test.go similarity index 100% rename from pkg/scheduler/framework/plugins/noderesources/test_util.go rename to pkg/scheduler/framework/plugins/noderesources/util_test.go diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go b/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go index 07be0e040b792..7ba415589ba6b 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go @@ -23,11 +23,12 @@ import ( v1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/rand" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" corelisters "k8s.io/client-go/listers/core/v1" storagelisters "k8s.io/client-go/listers/storage/v1" + "k8s.io/client-go/tools/cache" ephemeral "k8s.io/component-helpers/storage/ephemeral" storagehelpers "k8s.io/component-helpers/storage/volume" csitrans "k8s.io/csi-translation-lib" @@ -63,11 +64,11 @@ type CSILimits struct { scLister storagelisters.StorageClassLister vaLister storagelisters.VolumeAttachmentLister csiDriverLister storagelisters.CSIDriverLister + vaIndexer cache.Indexer - enableCSIMigrationPortworx bool - randomVolumeIDPrefix string - enableVolumeLimitScaling bool - translator InTreeToCSITranslator + randomVolumeIDPrefix string + enableVolumeLimitScaling bool + translator InTreeToCSITranslator } var _ fwk.PreFilterPlugin = &CSILimits{} @@ -356,7 +357,7 @@ func (pl *CSILimits) Filter(ctx context.Context, _ fwk.CycleState, pod *v1.Pod, func (pl *CSILimits) checkCSIDriverOnNode(pluginName string, csiNode *storagev1.CSINode) (bool, error) { // the registered driver must be a CSI driver to enforce this limit, if we can't find the driver, // we assume the driver may not be a CSI driver and allow the pod to be scheduled. - _, err := pl.csiDriverLister.Get(pluginName) + csiDriver, err := pl.csiDriverLister.Get(pluginName) if err != nil { if apierrors.IsNotFound(err) { return true, nil @@ -364,6 +365,12 @@ func (pl *CSILimits) checkCSIDriverOnNode(pluginName string, csiNode *storagev1. return false, fmt.Errorf("error getting CSIDriver for provider %s: %w", pluginName, err) } + driverOptin := csiDriver.Spec.PreventPodSchedulingIfMissing + + if driverOptin == nil || !*driverOptin { + return true, nil + } + if csiNode == nil { return false, nil } @@ -458,7 +465,7 @@ func (pl *CSILimits) checkAttachableInlineVolume(logger klog.Logger, vol *v1.Vol if err != nil { return fmt.Errorf("looking up provisioner name for volume %s: %w", vol.Name, err) } - if !isCSIMigrationOn(csiNode, inTreeProvisionerName, pl.enableCSIMigrationPortworx) { + if !isCSIMigrationOn(csiNode, inTreeProvisionerName) { csiNodeName := "" if csiNode != nil { csiNodeName = csiNode.Name @@ -519,7 +526,7 @@ func (pl *CSILimits) getCSIDriverInfo(logger klog.Logger, csiNode *storagev1.CSI return "", "" } - if !isCSIMigrationOn(csiNode, pluginName, pl.enableCSIMigrationPortworx) { + if !isCSIMigrationOn(csiNode, pluginName) { logger.V(5).Info("CSI Migration of plugin is not enabled", "plugin", pluginName) return "", "" } @@ -567,7 +574,7 @@ func (pl *CSILimits) getCSIDriverInfoFromSC(logger klog.Logger, csiNode *storage provisioner := storageClass.Provisioner if pl.translator.IsMigratableIntreePluginByName(provisioner) { - if !isCSIMigrationOn(csiNode, provisioner, pl.enableCSIMigrationPortworx) { + if !isCSIMigrationOn(csiNode, provisioner) { logger.V(5).Info("CSI Migration of provisioner is not enabled", "provisioner", provisioner) return "", "" } @@ -583,6 +590,14 @@ func (pl *CSILimits) getCSIDriverInfoFromSC(logger klog.Logger, csiNode *storage return provisioner, volumeHandle } +func volumeAttachmentIndexer(obj interface{}) ([]string, error) { + va, ok := obj.(*storagev1.VolumeAttachment) + if !ok { + return []string{}, nil + } + return []string{va.Spec.NodeName}, nil +} + // NewCSI initializes a new plugin and returns it. func NewCSI(_ context.Context, _ runtime.Object, handle fwk.Handle, fts feature.Features) (fwk.Plugin, error) { informerFactory := handle.SharedInformerFactory() @@ -591,19 +606,25 @@ func NewCSI(_ context.Context, _ runtime.Object, handle fwk.Handle, fts feature. scLister := informerFactory.Storage().V1().StorageClasses().Lister() vaLister := informerFactory.Storage().V1().VolumeAttachments().Lister() csiDriverLister := informerFactory.Storage().V1().CSIDrivers().Lister() + vaInformer := informerFactory.Storage().V1().VolumeAttachments().Informer() + if err := vaInformer.AddIndexers(cache.Indexers{vaIndexKey: volumeAttachmentIndexer}); err != nil { + if vaInformer.GetIndexer().GetIndexers()[vaIndexKey] == nil { + return nil, fmt.Errorf("failed to add index to VA informer: %w", err) + } + } csiTranslator := csitrans.New() return &CSILimits{ - csiManager: handle.SharedCSIManager(), - pvLister: pvLister, - pvcLister: pvcLister, - scLister: scLister, - vaLister: vaLister, - csiDriverLister: csiDriverLister, - enableCSIMigrationPortworx: fts.EnableCSIMigrationPortworx, - enableVolumeLimitScaling: fts.EnableVolumeLimitScaling, - randomVolumeIDPrefix: rand.String(32), - translator: csiTranslator, + csiManager: handle.SharedCSIManager(), + pvLister: pvLister, + pvcLister: pvcLister, + scLister: scLister, + vaLister: vaLister, + csiDriverLister: csiDriverLister, + enableVolumeLimitScaling: fts.EnableVolumeLimitScaling, + randomVolumeIDPrefix: rand.String(32), + translator: csiTranslator, + vaIndexer: vaInformer.GetIndexer(), }, nil } @@ -623,14 +644,22 @@ func getVolumeLimits(csiNode *storagev1.CSINode) map[string]int64 { return nodeVolumeLimits } +const vaIndexKey = "va.spec.nodename" + // getNodeVolumeAttachmentInfo returns a map of volumeID to driver name for the given node. func (pl *CSILimits) getNodeVolumeAttachmentInfo(logger klog.Logger, nodeName string) (map[string]string, error) { volumeAttachments := make(map[string]string) - vas, err := pl.vaLister.List(labels.Everything()) + vas, err := pl.vaIndexer.ByIndex(vaIndexKey, nodeName) if err != nil { return nil, err } - for _, va := range vas { + for _, vao := range vas { + va, ok := vao.(*storagev1.VolumeAttachment) + if !ok { + utilruntime.HandleErrorWithLogger(logger, fmt.Errorf("unexpected object type in volume attachment indexer: %v", vao), + "volume indexer not available") + continue + } if va.Spec.NodeName == nodeName { if va.Spec.Attacher == "" { logger.V(5).Info("VolumeAttachment has no attacher", "VolumeAttachment", klog.KObj(va)) diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go b/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go index dd0a9eb6c2116..d2c18915bfa07 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go @@ -29,9 +29,13 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" csitrans "k8s.io/csi-translation-lib" csilibplugins "k8s.io/csi-translation-lib/plugins" fwk "k8s.io/kube-scheduler/framework" @@ -641,16 +645,23 @@ func TestCSILimits(t *testing.T) { enableMigrationOnNode(csiNode, csilibplugins.AWSEBSInTreePluginName) } csiTranslator := csitrans.New() + fakecli := buildFakeClientWithVALister(test.vaCount, test.driverNames...) + informerFactory := informers.NewSharedInformerFactory(fakecli, 0) + if err := informerFactory.Storage().V1().VolumeAttachments().Informer().AddIndexers(cache.Indexers{vaIndexKey: volumeAttachmentIndexer}); err != nil { + t.Error(err) + } + _, ctx := ktesting.NewTestContext(t) + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) p := &CSILimits{ csiManager: NewCSIManager(getFakeCSINodeLister(csiNode)), pvLister: getFakeCSIPVLister(test.filterName, test.driverNames...), pvcLister: append(getFakeCSIPVCLister(test.filterName, scName, test.driverNames...), test.extraClaims...), scLister: getFakeCSIStorageClassLister(scName, test.driverNames[0]), - vaLister: getFakeVolumeAttachmentLister(test.vaCount, test.driverNames...), + vaIndexer: informerFactory.Storage().V1().VolumeAttachments().Informer().GetIndexer(), randomVolumeIDPrefix: rand.String(32), translator: csiTranslator, } - _, ctx := ktesting.NewTestContext(t) _, gotPreFilterStatus := p.PreFilter(ctx, nil, test.newPod, nil) if diff := cmp.Diff(test.wantPreFilterStatus, gotPreFilterStatus, statusCmpOpts...); diff != "" { t.Errorf("PreFilter status does not match (-want, +got):\n%s", diff) @@ -1074,12 +1085,12 @@ func TestCSILimitsAfterCSINodeUpdatedQHint(t *testing.T) { } } -func getFakeVolumeAttachmentLister(count int, driverNames ...string) tf.VolumeAttachmentLister { - vaLister := tf.VolumeAttachmentLister{} +func buildFakeClientWithVALister(count int, driverNames ...string) *fake.Clientset { + vas := []runtime.Object{} for _, driver := range driverNames { for j := 0; j < count; j++ { pvName := fmt.Sprintf("csi-%s-%d", driver, j) - va := storagev1.VolumeAttachment{ + va := &storagev1.VolumeAttachment{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("va-%s-%d", driver, j), }, @@ -1091,11 +1102,13 @@ func getFakeVolumeAttachmentLister(count int, driverNames ...string) tf.VolumeAt }, }, } - vaLister = append(vaLister, va) + vas = append(vas, va) } } - return vaLister + fakeCli := fake.NewClientset(vas...) + return fakeCli } + func getFakeCSIPVLister(volumeName string, driverNames ...string) tf.PersistentVolumeLister { pvLister := tf.PersistentVolumeLister{} for _, driver := range driverNames { @@ -1275,55 +1288,90 @@ func getFakeCSIDriverLister(driverNames ...string) fakeCSIDriverLister { return list } +func getFakeCSIDriverListerWithPreventPodSchedulingIfMissing(driverNames ...string) fakeCSIDriverLister { + var list fakeCSIDriverLister + for _, name := range driverNames { + list = append(list, storagev1.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: storagev1.CSIDriverSpec{ + PreventPodSchedulingIfMissing: ptr.To(true), + }, + }) + } + return list +} + func TestVolumeLimitScalingGate(t *testing.T) { // Pod uses a PVC that resolves to the EBS CSI driver via PV newPod := st.MakePod().PVC("csi-ebs.csi.aws.com-0").Obj() cases := []struct { - name string - enableVolumeLimitScaling bool - limitSource string - limit int32 - csiDriverPresent bool - wantStatus *fwk.Status + name string + enableVolumeLimitScaling bool + limitSource string + limit int32 + csiDriverPresent bool + preventPodSchedulingIfMissing bool + wantStatus *fwk.Status }{ { - name: "gate enabled - fail when driver not installed and CSIDriver exists", - enableVolumeLimitScaling: true, - limitSource: "no-csi-driver", - limit: 0, - csiDriverPresent: true, - wantStatus: fwk.NewStatus(fwk.Unschedulable, fmt.Sprintf("%s CSI driver is not installed on the node", ebsCSIDriverName)), + name: "gate enabled - allow scheduling when CSIDriver exists but PreventPodSchedulingIfMissing is not set", + enableVolumeLimitScaling: true, + limitSource: "no-csi-driver", + limit: 0, + csiDriverPresent: true, + preventPodSchedulingIfMissing: false, + wantStatus: nil, + }, + { + name: "gate enabled - fail when driver not installed and PreventPodSchedulingIfMissing is true", + enableVolumeLimitScaling: true, + limitSource: "no-csi-driver", + limit: 0, + csiDriverPresent: true, + preventPodSchedulingIfMissing: true, + wantStatus: fwk.NewStatus(fwk.Unschedulable, fmt.Sprintf("%s CSI driver is not installed on the node", ebsCSIDriverName)), }, { - name: "gate disabled - skip driver presence check (regardless of CSIDriver presence)", - enableVolumeLimitScaling: false, - limitSource: "no-csi-driver", - limit: 0, - csiDriverPresent: true, - wantStatus: nil, + name: "gate disabled - skip driver presence check (regardless of CSIDriver presence)", + enableVolumeLimitScaling: false, + limitSource: "no-csi-driver", + limit: 0, + csiDriverPresent: true, + preventPodSchedulingIfMissing: true, + wantStatus: nil, }, { - name: "gate enabled - driver installed within limit", - enableVolumeLimitScaling: true, - limitSource: "csinode", - limit: 2, - csiDriverPresent: true, - wantStatus: nil, + name: "gate enabled - driver installed within limit", + enableVolumeLimitScaling: true, + limitSource: "csinode", + limit: 2, + csiDriverPresent: true, + preventPodSchedulingIfMissing: true, + wantStatus: nil, }, { - name: "gate enabled - allow scheduling when CSIDriver object missing", - enableVolumeLimitScaling: true, - limitSource: "no-csi-driver", - limit: 0, - csiDriverPresent: false, - wantStatus: nil, + name: "gate enabled - allow scheduling when CSIDriver object missing", + enableVolumeLimitScaling: true, + limitSource: "no-csi-driver", + limit: 0, + csiDriverPresent: false, + preventPodSchedulingIfMissing: false, + wantStatus: nil, }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { node, csiNode := getNodeWithPodAndVolumeLimits(tt.limitSource, []*v1.Pod{}, tt.limit, ebsCSIDriverName) + fakecli := buildFakeClientWithVALister(0, ebsCSIDriverName) + informerFactory := informers.NewSharedInformerFactory(fakecli, 0) + if err := informerFactory.Storage().V1().VolumeAttachments().Informer().AddIndexers(cache.Indexers{vaIndexKey: volumeAttachmentIndexer}); err != nil { + t.Error(err) + } + _, ctx := ktesting.NewTestContext(t) + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) csiTranslator := csitrans.New() p := &CSILimits{ @@ -1331,9 +1379,13 @@ func TestVolumeLimitScalingGate(t *testing.T) { pvLister: getFakeCSIPVLister("csi", ebsCSIDriverName), pvcLister: getFakeCSIPVCLister("csi", scName, ebsCSIDriverName), scLister: getFakeCSIStorageClassLister(scName, ebsCSIDriverName), - vaLister: getFakeVolumeAttachmentLister(0, ebsCSIDriverName), + vaLister: informerFactory.Storage().V1().VolumeAttachments().Lister(), + vaIndexer: informerFactory.Storage().V1().VolumeAttachments().Informer().GetIndexer(), csiDriverLister: func() fakeCSIDriverLister { if tt.csiDriverPresent { + if tt.preventPodSchedulingIfMissing { + return getFakeCSIDriverListerWithPreventPodSchedulingIfMissing(ebsCSIDriverName) + } return getFakeCSIDriverLister(ebsCSIDriverName) } return getFakeCSIDriverLister() @@ -1343,7 +1395,6 @@ func TestVolumeLimitScalingGate(t *testing.T) { translator: csiTranslator, } - _, ctx := ktesting.NewTestContext(t) // Ensure PreFilter doesn't skip _, preStatus := p.PreFilter(ctx, nil, newPod, nil) if preStatus.Code() == fwk.Skip { diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go b/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go index 7cb761379d300..a672895b2e381 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go @@ -17,17 +17,13 @@ limitations under the License. package nodevolumelimits import ( - "strings" - - v1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/util/sets" csilibplugins "k8s.io/csi-translation-lib/plugins" ) // isCSIMigrationOn returns a boolean value indicating whether // the CSI migration has been enabled for a particular storage plugin. -func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string, enableCSIMigrationPortworx bool) bool { +func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string) bool { if csiNode == nil || len(pluginName) == 0 { return false } @@ -38,9 +34,7 @@ func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string, enableCSIMi case csilibplugins.AWSEBSInTreePluginName: return true case csilibplugins.PortworxVolumePluginName: - if !enableCSIMigrationPortworx { - return false - } + return true case csilibplugins.GCEPDInTreePluginName: return true case csilibplugins.AzureDiskInTreePluginName: @@ -50,22 +44,4 @@ func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string, enableCSIMi default: return false } - - // The plugin name should be listed in the CSINode object annotation. - // This indicates that the plugin has been migrated to a CSI driver in the node. - csiNodeAnn := csiNode.GetAnnotations() - if csiNodeAnn == nil { - return false - } - - var mpaSet sets.Set[string] - mpa := csiNodeAnn[v1.MigratedPluginsAnnotationKey] - if len(mpa) == 0 { - mpaSet = sets.New[string]() - } else { - tok := strings.Split(mpa, ",") - mpaSet = sets.New(tok...) - } - - return mpaSet.Has(pluginName) } diff --git a/pkg/scheduler/framework/plugins/podgrouppodscount/podgroup_pods_count.go b/pkg/scheduler/framework/plugins/podgrouppodscount/podgroup_pods_count.go new file mode 100644 index 0000000000000..143e66d7636f3 --- /dev/null +++ b/pkg/scheduler/framework/plugins/podgrouppodscount/podgroup_pods_count.go @@ -0,0 +1,87 @@ +/* +Copyright 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 podgrouppodscount + +import ( + "context" + "errors" + + "k8s.io/apimachinery/pkg/runtime" + fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" +) + +// PodGroupPodsCount is a placement score plugin that favors placements that can accommodate more pods from the considered PodGroup. +type PodGroupPodsCount struct { + handle fwk.Handle +} + +var _ fwk.PlacementScorePlugin = &PodGroupPodsCount{} + +const Name = names.PodGroupPodsCount + +// New initializes a new plugin and returns it. +func New(_ context.Context, _ runtime.Object, h fwk.Handle, _ feature.Features) (fwk.Plugin, error) { + return &PodGroupPodsCount{handle: h}, nil +} + +// Name returns name of the plugin. It is used in logs, etc. +func (pl *PodGroupPodsCount) Name() string { + return Name +} + +// ScorePlacement calculates a score for a given Placement. +// Both scheduled (assumed/assigned) pods and the proposed assignments are taken into consideration +// when computing the score. This ensures that the relative difference between choices is reduced, +// and small changes to the total count result in small changes to the score. +func (pl *PodGroupPodsCount) ScorePlacement(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placement *fwk.PodGroupAssignments) (int64, *fwk.Status) { + pgState, err := pl.handle.SnapshotSharedLister().PodGroupStates().Get(podGroup.GetNamespace(), podGroup.GetName()) + if err != nil { + return 0, fwk.AsStatus(err) + } + + return int64(pgState.ScheduledPodsCount() + len(placement.ProposedAssignments)), nil +} + +// PlacementScoreExtensions returns a PlacementScoreExtensions interface if it implements one, or nil if does not. +// PodGroupPodsCount implements this interface. +func (pl *PodGroupPodsCount) PlacementScoreExtensions() fwk.PlacementScoreExtensions { + return pl +} + +// NormalizePlacementScore normalizes the scores to a range of [MinScore, MaxScore]. +// The normalization is based on the maximum count among all candidate placements. +// We purposely do not consider MinCount (the minimum pods required for the group) during normalization +// to avoid large gaps in scores when there are minimal differences in pod counts. +func (pl *PodGroupPodsCount) NormalizePlacementScore(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, scores []fwk.PlacementScore) *fwk.Status { + maxCount := int64(0) + + for _, score := range scores { + maxCount = max(maxCount, score.Score) + } + + if maxCount == 0 { + return fwk.AsStatus(errors.New("no pods from pod group are assigned to any of the candidate placements")) + } + + for i := range scores { + scores[i].Score = fwk.MinScore + scores[i].Score*(fwk.MaxScore-fwk.MinScore)/maxCount + } + + return nil +} diff --git a/pkg/scheduler/framework/plugins/podgrouppodscount/podgroup_pods_count_test.go b/pkg/scheduler/framework/plugins/podgrouppodscount/podgroup_pods_count_test.go new file mode 100644 index 0000000000000..8df2aeebf3218 --- /dev/null +++ b/pkg/scheduler/framework/plugins/podgrouppodscount/podgroup_pods_count_test.go @@ -0,0 +1,302 @@ +/* +Copyright 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 podgrouppodscount + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/klog/v2/ktesting" + fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/features" + internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" + "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime" + "k8s.io/kubernetes/pkg/scheduler/metrics" + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +func init() { + metrics.Register() +} + +type mockProposedAssignment struct { + nodeName string + pod *v1.Pod +} + +var _ fwk.ProposedAssignment = &mockProposedAssignment{} + +func (pa *mockProposedAssignment) GetNodeName() string { + return pa.nodeName +} + +func (pa *mockProposedAssignment) GetPod() *v1.Pod { + return pa.pod +} + +func TestScorePlacement(t *testing.T) { + podGroupName := "pg1" + + createPod := func(podName, podGroupName, nodeName string) *v1.Pod { + return st.MakePod().Name(podName).Namespace("default").UID(podName).PodGroupName(podGroupName).Node(nodeName).Obj() + } + + createPodWithoutNode := func(podName, podGroupName string) *v1.Pod { + return createPod(podName, podGroupName, "") + } + + proposedAssignments := []fwk.ProposedAssignment{ + &mockProposedAssignment{ + nodeName: "node1", + pod: createPodWithoutNode("proposed-pod-1", podGroupName), + }, + &mockProposedAssignment{ + nodeName: "node2", + pod: createPodWithoutNode("proposed-pod-2", podGroupName), + }, + } + + tests := []struct { + name string + pod *v1.Pod + assignedPods []*v1.Pod // Pods to be added to the snapshot + assumedPods []*v1.Pod // Pods to be assumed in the snapshot + placement *fwk.PodGroupAssignments + expectedScore int64 + }{ + { + name: "existing assigned and assumed pods", + pod: createPodWithoutNode("p-new", podGroupName), + assignedPods: []*v1.Pod{ + // Assigned pods + createPod("p2", podGroupName, "node2"), + createPod("p3", podGroupName, "node3"), + }, + assumedPods: []*v1.Pod{ + // Assumed pod + createPod("p1", podGroupName, "node1"), + }, + placement: &fwk.PodGroupAssignments{ + ProposedAssignments: proposedAssignments, + }, + expectedScore: 5, // 1 assumed + 2 assigned + 2 proposed = 5 + }, + { + name: "no assumed pods", + pod: createPodWithoutNode("p-new", podGroupName), + assignedPods: []*v1.Pod{ + createPod("p1", podGroupName, "node1"), + }, + placement: &fwk.PodGroupAssignments{ + ProposedAssignments: proposedAssignments, + }, + expectedScore: 3, // 1 assigned + 2 proposed = 3 + }, + { + name: "no assigned pods", + pod: createPodWithoutNode("p-new", podGroupName), + assumedPods: []*v1.Pod{ + // Assumed pod + createPod("p1", podGroupName, "node1"), + }, + placement: &fwk.PodGroupAssignments{ + ProposedAssignments: proposedAssignments, + }, + expectedScore: 3, // 1 assumed + 2 proposed = 3 + }, + { + name: "no assigned pods, no assumed pods", + pod: createPodWithoutNode("p-new", podGroupName), + placement: &fwk.PodGroupAssignments{ + ProposedAssignments: proposedAssignments, + }, + expectedScore: 2, // 2 proposed + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Enable GenericWorkload feature gate to populate PodGroupState in cache + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: true, + }) + + logger, ctx := ktesting.NewTestContext(t) + + // Setup cache, snapshot and framework + snapshot := internalcache.NewEmptySnapshot() + cache := internalcache.New(ctx, nil, true) + informerFactory := informers.NewSharedInformerFactory(fake.NewClientset(), 0) + + fh, err := frameworkruntime.NewFramework(ctx, nil, nil, + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithSnapshotSharedLister(snapshot), + ) + if err != nil { + t.Fatalf("Failed to create framework: %v", err) + } + + // Add assigned pods to cache + for _, p := range tt.assignedPods { + if err := cache.AddPod(logger, p); err != nil { + t.Fatalf("Failed to add pod %v: %v", p.Name, err) + } + } + + // Add assumed pods to cache + for _, p := range tt.assumedPods { + cache.AddPodGroupMember(p) + if err := cache.AssumePod(logger, p); err != nil { + t.Fatalf("Failed to assume pod %v: %v", p.Name, err) + } + } + // Add proposed pods to cache + for _, assignment := range tt.placement.ProposedAssignments { + cache.AddPodGroupMember(assignment.GetPod()) + } + + // Update snapshot + if err := cache.UpdateSnapshot(logger, snapshot); err != nil { + t.Fatalf("Failed to update snapshot: %v", err) + } + + // Create the plugin + plugin, err := New(ctx, nil, fh, feature.Features{}) + if err != nil { + t.Fatalf("Failed to create plugin: %v", err) + } + pl := plugin.(*PodGroupPodsCount) + + // Construct PodGroupInfo for the test pod + pgInfo := &framework.PodGroupInfo{ + Namespace: tt.pod.Namespace, + Name: *tt.pod.Spec.SchedulingGroup.PodGroupName, + } + + // Run ScorePlacement + score, status := pl.ScorePlacement(ctx, nil, pgInfo, tt.placement) + if !status.IsSuccess() { + t.Errorf("ScorePlacement failed: %v", status.Message()) + } + if score != tt.expectedScore { + t.Errorf("Expected score %d, got %d", tt.expectedScore, score) + } + }) + } +} + +func TestNormalizePlacementScore(t *testing.T) { + tests := []struct { + name string + scores []fwk.PlacementScore + expectedNormalizedScores []fwk.PlacementScore + expectedError string + }{ + { + name: "distinct scores", + scores: []fwk.PlacementScore{ + {Score: 10}, + {Score: 50}, + {Score: 110}, + }, + // Normalized score is calculated as: score * (MaxScore - MinScore) / maxCount. + // With MinScore=0, MaxScore=100, and maxCount=110 (using integer division): + // 10 * 100 / 110 = 9 + // 50 * 100 / 110 = 45 + // 110 * 100 / 110 = 100 + expectedNormalizedScores: []fwk.PlacementScore{ + {Score: 9}, + {Score: 45}, + {Score: 100}, + }, + }, + { + name: "equal scores", + scores: []fwk.PlacementScore{ + {Score: 50}, + {Score: 50}, + }, + expectedNormalizedScores: []fwk.PlacementScore{ + {Score: 100}, + {Score: 100}, + }, + }, + { + name: "single score", + scores: []fwk.PlacementScore{ + {Score: 50}, + }, + expectedNormalizedScores: []fwk.PlacementScore{ + {Score: 100}, + }, + }, + { + name: "some minimal score that is far from a group of scores located closely", // to test that normalization will not distribute it evenly + scores: []fwk.PlacementScore{ + {Score: 11}, + {Score: 100}, + {Score: 101}, + {Score: 102}, + }, + expectedNormalizedScores: []fwk.PlacementScore{ + {Score: 10}, + {Score: 98}, + {Score: 99}, + {Score: 100}, + }, + }, + { + name: "zero scores", + scores: []fwk.PlacementScore{{Score: 0}, {Score: 0}}, + expectedError: `no pods from pod group are assigned to any of the candidate placements`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := &PodGroupPodsCount{} + pgInfo := &framework.PodGroupInfo{Name: "pg1"} + status := pl.NormalizePlacementScore(context.Background(), nil, pgInfo, tt.scores) + if tt.expectedError != "" { + if status.IsSuccess() { + t.Fatal("Expected error, but got success") + } + if tt.expectedError != status.Message() { + t.Errorf("Unexpected error message. Want %s\n, got %s", tt.expectedError, status.Message()) + } + return + } + + if !status.IsSuccess() { + t.Errorf("NormalizePlacementScore failed: %v", status.Message()) + } + + if diff := cmp.Diff(tt.expectedNormalizedScores, tt.scores); diff != "" { + t.Errorf("Unexpected scores (-want, +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/plugin.go b/pkg/scheduler/framework/plugins/podtopologyspread/plugin.go index 18e7be733d5d7..af93f64c68d32 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/plugin.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/plugin.go @@ -184,8 +184,13 @@ func (pl *PodTopologySpread) EventsToRegister(_ context.Context) ([]fwk.ClusterE // involvedInTopologySpreading returns true if the incomingPod is involved in the topology spreading of podWithSpreading. func involvedInTopologySpreading(incomingPod, podWithSpreading *v1.Pod) bool { - return incomingPod.UID == podWithSpreading.UID || - (incomingPod.Spec.NodeName != "" && incomingPod.Namespace == podWithSpreading.Namespace) + if incomingPod.UID == podWithSpreading.UID { + return true + } + if incomingPod.Spec.NodeName == "" && incomingPod.Status.NominatedNodeName == "" { + return false + } + return incomingPod.Namespace == podWithSpreading.Namespace } // hasConstraintWithNodeTaintsPolicyHonor returns true if any constraint has `NodeTaintsPolicy: Honor`. diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/plugin_test.go b/pkg/scheduler/framework/plugins/podtopologyspread/plugin_test.go index 491bb49444ed6..6d9f5056fbe4d 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/plugin_test.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/plugin_test.go @@ -236,6 +236,22 @@ func Test_isSchedulableAfterPodChange(t *testing.T) { oldPod: st.MakePod().UID("p2").Node("fake-node").Label("foo", "").Obj(), expectedHint: fwk.Queue, }, + { + name: "delete nominated pod with labels that don't match topologySpreadConstraints selector", + pod: st.MakePod().UID("p").Name("p").Label("foo", ""). + SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). + Obj(), + oldPod: st.MakePod().UID("p2").NominatedNodeName("fake-node").Label("bar", "").Obj(), + expectedHint: fwk.QueueSkip, + }, + { + name: "delete nominated pod with labels that match topologySpreadConstraints selector", + pod: st.MakePod().UID("p").Name("p").Label("foo", ""). + SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). + Obj(), + oldPod: st.MakePod().UID("p2").NominatedNodeName("fake-node").Label("foo", "").Obj(), + expectedHint: fwk.Queue, + }, { name: "delete pod with labels that don't match topologySpreadConstraints selector", pod: st.MakePod().UID("p").Name("p").Label("foo", ""). diff --git a/pkg/scheduler/framework/plugins/registry.go b/pkg/scheduler/framework/plugins/registry.go index 8d945f6319f1f..c09c34919c8a1 100644 --- a/pkg/scheduler/framework/plugins/registry.go +++ b/pkg/scheduler/framework/plugins/registry.go @@ -32,10 +32,12 @@ import ( "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeunschedulable" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodevolumelimits" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/podgrouppodscount" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/schedulinggates" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/topologyaware" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumerestrictions" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumezone" @@ -69,6 +71,8 @@ func NewInTreeRegistry() runtime.Registry { defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New), schedulinggates.Name: runtime.FactoryAdapter(fts, schedulinggates.New), gangscheduling.Name: runtime.FactoryAdapter(fts, gangscheduling.New), + topologyaware.Name: runtime.FactoryAdapter(fts, topologyaware.New), + podgrouppodscount.Name: runtime.FactoryAdapter(fts, podgrouppodscount.New), } return registry diff --git a/pkg/scheduler/framework/plugins/topologyaware/topology_placement.go b/pkg/scheduler/framework/plugins/topologyaware/topology_placement.go new file mode 100644 index 0000000000000..ccf7978748b6c --- /dev/null +++ b/pkg/scheduler/framework/plugins/topologyaware/topology_placement.go @@ -0,0 +1,142 @@ +/* +Copyright 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 topologyaware + +import ( + "context" + "fmt" + + v1 "k8s.io/api/core/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + "k8s.io/apimachinery/pkg/runtime" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" + "k8s.io/klog/v2" + fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" +) + +const ( + // Name is the name of the plugin used in the plugin registry and configurations. + Name = names.TopologyPlacementGenerator +) + +// TopologyPlacement is a plugin that generates placements for a pod group based on its topology constraints. +type TopologyPlacement struct { + handle fwk.Handle + podGroupLister schedulinglisters.PodGroupLister +} + +var _ fwk.PlacementGeneratePlugin = &TopologyPlacement{} + +// New initializes a new plugin and returns it. +func New(_ context.Context, _ runtime.Object, fh fwk.Handle, fts feature.Features) (*TopologyPlacement, error) { + return &TopologyPlacement{ + handle: fh, + podGroupLister: fh.SharedInformerFactory().Scheduling().V1alpha2().PodGroups().Lister(), + }, nil +} + +// Name returns name of the plugin. +func (pl *TopologyPlacement) Name() string { + return Name +} + +// GeneratePlacements generates placements for a pod group based on the topology constraints in the pod group spec. +// It uses the parent placement to find the nodes that are available for placement. +func (pl *TopologyPlacement) GeneratePlacements(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, parentPlacement *fwk.Placement) (*fwk.GeneratePlacementsResult, *fwk.Status) { + podGroupResource, err := pl.podGroupLister.PodGroups(podGroup.GetNamespace()).Get(podGroup.GetName()) + if err != nil { + return nil, fwk.AsStatus(err) + } + topologyKey, ok := pl.getTopologyKey(podGroupResource) + if !ok { + // No topology constraints, return a single placement with no constraints. + return &fwk.GeneratePlacementsResult{Placements: []*fwk.Placement{parentPlacement}}, nil + } + + var requiredDomain *string + scheduledPods, err := pl.getScheduledPods(podGroup) + if err != nil { + return nil, fwk.AsStatus(err) + } + if len(scheduledPods) > 0 { + scheduledDomain, err := pl.getScheduledPodsTopologyDomain(topologyKey, scheduledPods) + if err != nil { + return nil, fwk.AsStatus(fmt.Errorf("cannot determine domain for already scheduled pods: %w", err)) + } + requiredDomain = &scheduledDomain + } + + nodesPerTopologyDomain := make(map[string][]fwk.NodeInfo) + for _, node := range parentPlacement.Nodes { + if domain, ok := node.Node().Labels[topologyKey]; ok { + if requiredDomain == nil || *requiredDomain == domain { + nodesPerTopologyDomain[domain] = append(nodesPerTopologyDomain[domain], node) + } + } + } + + placements := make([]*fwk.Placement, 0, len(nodesPerTopologyDomain)) + for topologyDomain, nodes := range nodesPerTopologyDomain { + if len(nodes) > 0 { + placements = append(placements, &fwk.Placement{ + Name: topologyDomain, + Nodes: nodes, + }) + } + } + + return &fwk.GeneratePlacementsResult{Placements: placements}, nil +} + +func (pl *TopologyPlacement) getScheduledPodsTopologyDomain(topologyKey string, scheduledPods []*v1.Pod) (string, error) { + topologyDomain := "" + for _, pod := range scheduledPods { + node, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(pod.Spec.NodeName) + if err != nil { + return "", fmt.Errorf("getting node for pod %v: %w", klog.KObj(pod), err) + } + domain, ok := node.Node().Labels[topologyKey] + if !ok { + return "", fmt.Errorf("no topology domain found for pod %v", klog.KObj(pod)) + } + if topologyDomain != "" && topologyDomain != domain { + return "", fmt.Errorf("more than 1 domain found for pod group: %v, %v", topologyDomain, domain) + } + topologyDomain = domain + } + return topologyDomain, nil +} + +// getTopologyKey returns the topology key for the pod group if there's any specified. +func (pl *TopologyPlacement) getTopologyKey(podGroupResource *schedulingapi.PodGroup) (string, bool) { + if schedulingConstraints := podGroupResource.Spec.SchedulingConstraints; schedulingConstraints != nil && len(schedulingConstraints.Topology) > 0 { + // Right now, we only support a single topology constraint on the API level. + return schedulingConstraints.Topology[0].Key, true + } + return "", false +} + +func (pl *TopologyPlacement) getScheduledPods(podGroup fwk.PodGroupInfo) ([]*v1.Pod, error) { + name := podGroup.GetName() + podGroupState, err := pl.handle.SnapshotSharedLister().PodGroupStates().Get(podGroup.GetNamespace(), name) + if err != nil { + return nil, err + } + return podGroupState.ScheduledPods(), nil +} diff --git a/pkg/scheduler/framework/plugins/topologyaware/topology_placement_test.go b/pkg/scheduler/framework/plugins/topologyaware/topology_placement_test.go new file mode 100644 index 0000000000000..f42498f7f39f4 --- /dev/null +++ b/pkg/scheduler/framework/plugins/topologyaware/topology_placement_test.go @@ -0,0 +1,275 @@ +/* +Copyright 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 topologyaware + +import ( + "testing" + + v1 "k8s.io/api/core/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + clientsetfake "k8s.io/client-go/kubernetes/fake" + featuregatetesting "k8s.io/component-base/featuregate/testing" + fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/scheduler/backend/cache" + "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/kubernetes/pkg/scheduler/framework/runtime" + st "k8s.io/kubernetes/pkg/scheduler/testing" + "k8s.io/kubernetes/test/utils/ktesting" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +func TestGeneratePlacements(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.TopologyAwareWorkloadScheduling: true, + }) + + initialPlacementName := "test-placement" + tests := map[string]struct { + podGroup *schedulingapi.PodGroup + scheduledPodGroupPods map[string]string + placementNodes []*v1.Node + otherNodes []*v1.Node + wantPlacementNodes map[string][]string + wantStatus fwk.Code + }{ + "without constraint returns placement matching all nodes": { + podGroup: &schedulingapi.PodGroup{ + Spec: schedulingapi.PodGroupSpec{}, + }, + placementNodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Label("foo", "bar").Obj(), + }, + otherNodes: []*v1.Node{ + st.MakeNode().Name("node3").Obj(), + }, + wantPlacementNodes: map[string][]string{ + initialPlacementName: {"node1", "node2"}, + }, + wantStatus: fwk.Success, + }, + "with topology key constraint, returns placement for each topology domain": { + podGroup: makePodGroup("topology1"), + placementNodes: []*v1.Node{ + st.MakeNode().Name("node0").Label("topology2", "d1").Obj(), + st.MakeNode().Name("node1").Label("topology2", "d4").Obj(), + st.MakeNode().Name("node2").Label("topology1", "d1").Obj(), + st.MakeNode().Name("node3").Label("topology1", "d2").Obj(), + st.MakeNode().Name("node4").Label("topology1", "d1").Obj(), + st.MakeNode().Name("node5").Label("topology1", "d3").Obj(), + }, + wantPlacementNodes: map[string][]string{ + "d1": {"node2", "node4"}, + "d2": {"node3"}, + "d3": {"node5"}, + }, + wantStatus: fwk.Success, + }, + "without matching topology label, returns empty": { + podGroup: makePodGroup("topology3"), + placementNodes: []*v1.Node{ + st.MakeNode().Name("node0").Label("topology2", "d1").Obj(), + st.MakeNode().Name("node1").Label("topology2", "d4").Obj(), + st.MakeNode().Name("node2").Label("topology1", "d1").Obj(), + st.MakeNode().Name("node3").Label("topology1", "d2").Obj(), + st.MakeNode().Name("node4").Label("topology1", "d1").Obj(), + st.MakeNode().Name("node5").Label("topology1", "d3").Obj(), + }, + wantPlacementNodes: map[string][]string{}, + wantStatus: fwk.Success, + }, + "with pods already scheduled in a single domain, returns that domain": { + podGroup: makePodGroup("topology"), + scheduledPodGroupPods: map[string]string{ + "pod1": "node2", + "pod2": "node3", + }, + placementNodes: []*v1.Node{ + st.MakeNode().Name("node0").Label("topology", "d2").Obj(), + st.MakeNode().Name("node1").Label("topology", "d1").Obj(), + }, + otherNodes: []*v1.Node{ + st.MakeNode().Name("node2").Label("topology", "d1").Obj(), + st.MakeNode().Name("node3").Label("topology", "d1").Obj(), + }, + wantPlacementNodes: map[string][]string{ + "d1": {"node1"}, + }, + wantStatus: fwk.Success, + }, + "with pods already scheduled in a single domain not present in current placement, returns empty": { + podGroup: makePodGroup("topology"), + scheduledPodGroupPods: map[string]string{ + "pod1": "node2", + "pod2": "node3", + }, + placementNodes: []*v1.Node{ + st.MakeNode().Name("node0").Label("topology", "d2").Obj(), + }, + otherNodes: []*v1.Node{ + st.MakeNode().Name("node2").Label("topology", "d1").Obj(), + st.MakeNode().Name("node3").Label("topology", "d1").Obj(), + }, + wantPlacementNodes: map[string][]string{}, + wantStatus: fwk.Success, + }, + "with pods already scheduled in conflicting domains, returns error": { + podGroup: makePodGroup("topology"), + scheduledPodGroupPods: map[string]string{ + "pod1": "node2", + "pod2": "node3", + }, + placementNodes: []*v1.Node{ + st.MakeNode().Name("node0").Label("topology", "d2").Obj(), + st.MakeNode().Name("node1").Label("topology", "d1").Obj(), + }, + otherNodes: []*v1.Node{ + st.MakeNode().Name("node2").Label("topology", "d0").Obj(), + st.MakeNode().Name("node3").Label("topology", "d1").Obj(), + }, + wantStatus: fwk.Error, + }, + "with already scheduled pod on node outside of snapshot, returns error": { + podGroup: makePodGroup("topology"), + scheduledPodGroupPods: map[string]string{ + "pod1": "node2", + "pod2": "node4", + }, + placementNodes: []*v1.Node{ + st.MakeNode().Name("node0").Label("topology", "d2").Obj(), + st.MakeNode().Name("node1").Label("topology", "d1").Obj(), + }, + otherNodes: []*v1.Node{ + st.MakeNode().Name("node2").Label("topology", "d1").Obj(), + st.MakeNode().Name("node3").Label("topology", "d1").Obj(), + }, + wantStatus: fwk.Error, + }, + "with already scheduled pod on node without topology label, returns error": { + podGroup: makePodGroup("topology"), + scheduledPodGroupPods: map[string]string{ + "pod1": "node2", + }, + placementNodes: []*v1.Node{ + st.MakeNode().Name("node1").Label("topology", "d2").Obj(), + }, + otherNodes: []*v1.Node{ + st.MakeNode().Name("node2").Label("foo", "bar").Obj(), + }, + wantStatus: fwk.Error, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + _, tCtx := ktesting.NewTestContext(t) + + nodes := make([]v1.Node, 0, len(tt.placementNodes)+len(tt.otherNodes)) + for _, node := range append(tt.placementNodes, tt.otherNodes...) { + nodes = append(nodes, *node) + } + + cs := clientsetfake.NewClientset( + &schedulingapi.PodGroupList{Items: []schedulingapi.PodGroup{*tt.podGroup}}, + &v1.NodeList{Items: nodes}, + ) + informerFactory := informers.NewSharedInformerFactory(cs, 0) + _ = informerFactory.Scheduling().V1alpha2().PodGroups().Informer() + _ = informerFactory.Core().V1().Nodes().Informer() + informerFactory.StartWithContext(tCtx) + informerFactory.WaitForCacheSyncWithContext(tCtx) + + pods := make([]*v1.Pod, 0, len(tt.scheduledPodGroupPods)+1) + pods = append(pods, st.MakePod().Name("unscheduled").UID("unscheduled").Namespace(tt.podGroup.Namespace).PodGroupName(tt.podGroup.Name).Obj()) + for podName, nodeName := range tt.scheduledPodGroupPods { + pod := st.MakePod().Name(podName).UID(podName).Node(nodeName).Namespace(tt.podGroup.Namespace).PodGroupName(tt.podGroup.Name).Obj() + pods = append(pods, pod) + } + snapshot := cache.NewSnapshot(pods, append(tt.placementNodes, tt.otherNodes...)) + + fh, _ := runtime.NewFramework(tCtx, nil, nil, + runtime.WithInformerFactory(informerFactory), + runtime.WithSnapshotSharedLister(snapshot), + ) + + pl, err := New(tCtx, nil, fh, feature.Features{}) + if err != nil { + t.Fatalf("failed when creating plugin: %v", err) + } + + placement := &fwk.Placement{ + Name: initialPlacementName, + Nodes: make([]fwk.NodeInfo, len(tt.placementNodes)), + } + for i, node := range tt.placementNodes { + ni := framework.NewNodeInfo() + ni.SetNode(node) + placement.Nodes[i] = ni + } + podGroupInfo := &framework.PodGroupInfo{ + Name: tt.podGroup.Name, + Namespace: tt.podGroup.Namespace, + } + + result, status := pl.GeneratePlacements(tCtx, framework.NewCycleState(), podGroupInfo, placement) + + if status.Code() != tt.wantStatus { + t.Fatalf("expected status %v, got %v", tt.wantStatus, status.AsError()) + } + + if status.IsSuccess() { + gotPlacementNodes := make(map[string][]string) + for _, placement := range result.Placements { + gotPlacementNodes[placement.Name] = make([]string, len(placement.Nodes)) + for i, node := range placement.Nodes { + gotPlacementNodes[placement.Name][i] = node.Node().Name + } + } + + if diff := cmp.Diff(tt.wantPlacementNodes, gotPlacementNodes, cmpopts.EquateEmpty()); diff != "" { + t.Errorf("Unexpected placements (-want,+got):\n%s", diff) + } + } + }) + } +} + +func makePodGroup(topologyKey string) *schedulingapi.PodGroup { + return &schedulingapi.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pg1", + Namespace: "default", + }, + Spec: schedulingapi.PodGroupSpec{ + SchedulingConstraints: &schedulingapi.PodGroupSchedulingConstraints{ + Topology: []schedulingapi.TopologyConstraint{ + { + Key: topologyKey, + }, + }, + }, + }, + } +} diff --git a/pkg/scheduler/framework/plugins/volumebinding/binder.go b/pkg/scheduler/framework/plugins/volumebinding/binder.go index f4c9a22fc1f0d..3efccb1f82e54 100644 --- a/pkg/scheduler/framework/plugins/volumebinding/binder.go +++ b/pkg/scheduler/framework/plugins/volumebinding/binder.go @@ -209,7 +209,6 @@ type PodVolumeClaims struct { type volumeBinder struct { kubeClient clientset.Interface enableVolumeAttributesClass bool - enableCSIMigrationPortworx bool classLister storagelisters.StorageClassLister podLister corelisters.PodLister @@ -262,7 +261,6 @@ func NewVolumeBinder( b := &volumeBinder{ kubeClient: kubeClient, enableVolumeAttributesClass: fts.EnableVolumeAttributesClass, - enableCSIMigrationPortworx: fts.EnableCSIMigrationPortworx, podLister: podInformer.Lister(), classLister: storageClassInformer.Lister(), nodeLister: nodeInformer.Lister(), @@ -1066,7 +1064,7 @@ func (a byPVCSize) Less(i, j int) bool { } // isCSIMigrationOnForPlugin checks if CSI migration is enabled for a given plugin. -func isCSIMigrationOnForPlugin(pluginName string, enableCSIMigrationPortworx bool) bool { +func isCSIMigrationOnForPlugin(pluginName string) bool { switch pluginName { case csiplugins.AWSEBSInTreePluginName: return true @@ -1077,7 +1075,7 @@ func isCSIMigrationOnForPlugin(pluginName string, enableCSIMigrationPortworx boo case csiplugins.CinderInTreePluginName: return true case csiplugins.PortworxVolumePluginName: - return enableCSIMigrationPortworx + return true } return false } @@ -1116,7 +1114,7 @@ func (b *volumeBinder) tryTranslatePVToCSI(logger klog.Logger, pv *v1.Persistent return nil, fmt.Errorf("could not get plugin name from pv: %v", err) } - if !isCSIMigrationOnForPlugin(pluginName, b.enableCSIMigrationPortworx) { + if !isCSIMigrationOnForPlugin(pluginName) { return pv, nil } diff --git a/pkg/scheduler/framework/plugins/volumebinding/binder_test.go b/pkg/scheduler/framework/plugins/volumebinding/binder_test.go index 1273c10a6f7e4..d57bf0dd8b233 100644 --- a/pkg/scheduler/framework/plugins/volumebinding/binder_test.go +++ b/pkg/scheduler/framework/plugins/volumebinding/binder_test.go @@ -2104,7 +2104,6 @@ func TestBindPodVolumes(t *testing.T) { } for name, scenario := range scenarios { - scenario := scenario t.Run(name, func(t *testing.T) { t.Parallel() run(t, scenario) diff --git a/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go b/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go index 0774ea781087d..83111927c28ee 100644 --- a/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go +++ b/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go @@ -552,20 +552,21 @@ var errNoPodVolumeForNode = fmt.Errorf("no pod volume found for node") // PreBindPreFlight is called before PreBind, and determines whether PreBind is going to do something for this pod, or not. // It checks state.podVolumesByNode to determine whether there are any pod volumes for the node and hence the plugin has to handle them at PreBind. -func (pl *VolumeBinding) PreBindPreFlight(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status { +func (pl *VolumeBinding) PreBindPreFlight(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (*fwk.PreBindPreFlightResult, *fwk.Status) { + result := &fwk.PreBindPreFlightResult{AllowParallel: true} s, err := getStateData(state) if err != nil { - return fwk.AsStatus(err) + return result, fwk.AsStatus(err) } if s.allBound { // no need to bind volumes - return fwk.NewStatus(fwk.Skip) + return result, fwk.NewStatus(fwk.Skip) } if _, ok := s.podVolumesByNode[nodeName]; !ok { - return fwk.AsStatus(fmt.Errorf("%w %q", errNoPodVolumeForNode, nodeName)) + return result, fwk.AsStatus(fmt.Errorf("%w %q", errNoPodVolumeForNode, nodeName)) } - return nil + return result, nil } // PreBind will make the API update with the assumed bindings and wait until diff --git a/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go b/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go index 13f2923919018..ba947b22289fa 100644 --- a/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go +++ b/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go @@ -1133,10 +1133,10 @@ func TestVolumeBinding(t *testing.T) { func Test_PreBindPreFlight(t *testing.T) { table := []struct { - name string - nodeName string - state *stateData - want *fwk.Status + name string + nodeName string + state *stateData + wantStatus *fwk.Status }{ { name: "all bound", @@ -1144,7 +1144,7 @@ func Test_PreBindPreFlight(t *testing.T) { state: &stateData{ allBound: true, }, - want: fwk.NewStatus(fwk.Skip), + wantStatus: fwk.NewStatus(fwk.Skip), }, { name: "volume to be bound", @@ -1154,12 +1154,12 @@ func Test_PreBindPreFlight(t *testing.T) { "node-a": {}, }, }, - want: nil, + wantStatus: nil, }, { - name: "error: state is nil", - nodeName: "node-a", - want: fwk.AsStatus(fwk.ErrNotFound), + name: "error: state is nil", + nodeName: "node-a", + wantStatus: fwk.AsStatus(fwk.ErrNotFound), }, { name: "error: node is not found in podVolumesByNode", @@ -1169,7 +1169,7 @@ func Test_PreBindPreFlight(t *testing.T) { "node-b": {}, }, }, - want: fwk.AsStatus(errNoPodVolumeForNode), + wantStatus: fwk.AsStatus(errNoPodVolumeForNode), }, } @@ -1181,10 +1181,11 @@ func Test_PreBindPreFlight(t *testing.T) { if item.state != nil { state.Write(stateKey, item.state) } - status := pl.PreBindPreFlight(ctx, state, &v1.Pod{}, item.nodeName) - if !status.Equal(item.want) { - t.Errorf("PreBindPreFlight failed - got: %v, want: %v", status, item.want) + result, status := pl.PreBindPreFlight(ctx, state, &v1.Pod{}, item.nodeName) + if !status.Equal(item.wantStatus) { + t.Errorf("PreBindPreFlight failed - got: %v, want: %v", status, item.wantStatus) } + assert.Equal(t, &fwk.PreBindPreFlightResult{AllowParallel: true}, result) }) } } diff --git a/pkg/scheduler/framework/preemption/executor.go b/pkg/scheduler/framework/preemption/executor.go new file mode 100644 index 0000000000000..319e9a9027987 --- /dev/null +++ b/pkg/scheduler/framework/preemption/executor.go @@ -0,0 +1,512 @@ +/* +Copyright 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 preemption + +import ( + "context" + "fmt" + "sync" + "time" + + v1 "k8s.io/api/core/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + corev1helpers "k8s.io/component-helpers/scheduling/corev1" + "k8s.io/klog/v2" + extenderv1 "k8s.io/kube-scheduler/extender/v1" + fwk "k8s.io/kube-scheduler/framework" + apipod "k8s.io/kubernetes/pkg/api/v1/pod" + "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/kubernetes/pkg/scheduler/metrics" + "k8s.io/kubernetes/pkg/scheduler/util" +) + +type pendingVictim struct { + namespace string + name string +} + +// ExecutorPreemptor is an interface that represents a preemptor. +// It's mainly used to abstract away fields needed for logging +// purposes during preemption calls done by Executor. +type ExecutorPreemptor interface { + klog.KMetadata + // UID returns the UID of the object that is triggering preemption. + UID() types.UID + // SchedulerName returns the scheduler name assigned to the object that is triggering preemption. + SchedulerName() string + // Obj returns the object that is triggering preemption. + Obj() runtime.Object + // Pods returns a map of pod name to pods that form a preemptor. + Pods() map[string]*v1.Pod + // Priority returns the priority of the preemptor. + Priority() int32 + // Type returns the type of the preemptor. + Type() string +} + +// Executor is responsible for actuating the preemption process based on the provided candidate. +// It supports both synchronous as well as asynchronous preemption. +type Executor struct { + mu sync.RWMutex + fh fwk.Handle + + podLister corelisters.PodLister + + fts feature.Features + + // preempting is a set that records the pods/podgroups that are currently triggering preemption asynchronously, + // which is used to prevent the pods and pods from podgroups from entering the scheduling cycle meanwhile. + preempting sets.Set[types.UID] + // lastVictimsPendingPreemption is a map that records the victim pods that are currently being preempted for a given preemptor pod/podgroup, + // with a condition that the preemptor is waiting for one last victim to be preempted. This is used together with `preempting` + // to prevent the pods/podgroups from entering the scheduling cycle while waiting for preemption to complete. + lastVictimsPendingPreemption map[types.UID]pendingVictim + + // PreemptPod is a function that actually makes API calls to preempt a specific Pod. + // This is exposed to be replaced during tests. + PreemptPod func(ctx context.Context, c Candidate, preemptor ExecutorPreemptor, victim *v1.Pod, pluginName string) error +} + +// NewExecutor creates a new preemption executor. +func NewExecutor(fh fwk.Handle, fts feature.Features) *Executor { + e := &Executor{ + fh: fh, + podLister: fh.SharedInformerFactory().Core().V1().Pods().Lister(), + preempting: sets.New[types.UID](), + lastVictimsPendingPreemption: make(map[types.UID]pendingVictim), + fts: fts, + } + + e.PreemptPod = func(ctx context.Context, c Candidate, preemptor ExecutorPreemptor, victim *v1.Pod, pluginName string) error { + logger := klog.FromContext(ctx) + + skipAPICall := false + eventMessage := fmt.Sprintf("Preempted by %s %v on node %v", preemptor.Type(), preemptor.UID(), c.Name()) + // If the victim is a WaitingPod, try to preempt it without a delete call (victim will go back to backoff queue). + // Otherwise we should delete the victim. + if waitingPod := e.fh.GetWaitingPod(victim.UID); waitingPod != nil { + if waitingPod.Preempt(pluginName, "preempted") { + logger.V(2).Info("Preemptor preempted a waiting pod", "preemptorType", preemptor.Type(), "preemptor", klog.KObj(preemptor), "waitingPod", klog.KObj(victim), "node", c.Name()) + skipAPICall = true + } + } else if podInPreBind := e.fh.GetPodInPreBind(victim.UID); podInPreBind != nil { + // If the victim is in the preBind cancel the binding process. + if podInPreBind.CancelPod(fmt.Sprintf("preempted by %s", pluginName)) { + logger.V(2).Info("Preemptor rejected a pod in preBind", "preemptorType", preemptor.Type(), "preemptor", klog.KObj(preemptor), "podInPreBind", klog.KObj(victim), "node", c.Name()) + skipAPICall = true + } else { + logger.V(5).Info("Failed to reject a pod in preBind, falling back to deletion via api call", "preemptor", klog.KObj(preemptor), "podInPreBind", klog.KObj(victim), "node", c.Name()) + } + } + if !skipAPICall { + condition := &v1.PodCondition{ + Type: v1.DisruptionTarget, + ObservedGeneration: apipod.CalculatePodConditionObservedGeneration(&victim.Status, victim.Generation, v1.DisruptionTarget), + Status: v1.ConditionTrue, + Reason: v1.PodReasonPreemptionByScheduler, + Message: fmt.Sprintf("%s: preempting to accommodate a higher priority %s", preemptor.SchedulerName(), preemptor.Type()), + } + newStatus := victim.Status.DeepCopy() + updated := apipod.UpdatePodCondition(newStatus, condition) + if updated { + if err := util.PatchPodStatus(ctx, fh.ClientSet(), victim.Name, victim.Namespace, &victim.Status, newStatus); err != nil { + if !apierrors.IsNotFound(err) { + logger.Error(err, "Could not add DisruptionTarget condition due to preemption", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim)) + return err + } + logger.V(2).Info("Victim Pod is already deleted", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) + return nil + } + } + if err := util.DeletePod(ctx, fh.ClientSet(), victim); err != nil { + if !apierrors.IsNotFound(err) { + logger.Error(err, "Tried to preempted pod", "pod", klog.KObj(victim), "preemptor", klog.KObj(preemptor)) + return err + } + logger.V(2).Info("Victim Pod is already deleted", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) + return nil + } + logger.V(2).Info("Preemptor Pod preempted victim Pod", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) + } else { + eventMessage += " (in kube-scheduler memory)." + } + + fh.EventRecorder().WithLogger(logger).Eventf(victim, preemptor.Obj(), v1.EventTypeNormal, "Preempted", "Preempting", eventMessage) + + return nil + } + + return e +} + +// actuatePodPreemption actuates the preemption given preemptorPod to be scheduled on targetNode and a list of +// victims to be evicted. +func (e *Executor) actuatePodPreemption(ctx context.Context, targetNode string, victims *extenderv1.Victims, preemptorPod *v1.Pod, pluginName string) *fwk.Status { + candidate := &candidate{ + victims: victims, + name: targetNode, + } + + podPreemptor := &podExecutorPreemptor{Pod: preemptorPod} + if e.fts.EnableAsyncPreemption { + e.prepareCandidateAsync(candidate, podPreemptor, pluginName) + return nil + } + return e.prepareCandidate(ctx, candidate, podPreemptor, pluginName) +} + +// actuatePodGroupPreemption actuates the preemption given preemptor pods, pod group and a list of victims to be evicted. +func (e *Executor) actuatePodGroupPreemption(ctx context.Context, victims *extenderv1.Victims, preemptorPods []*v1.Pod, preemptor *schedulingapi.PodGroup, pluginName string) *fwk.Status { + candidate := &candidate{ + victims: victims, + name: "cluster", + } + + podGroupPreemptor := &podGroupExecutorPreemptor{pg: preemptor, pods: preemptorPods} + if e.fts.EnableAsyncPreemption { + e.prepareCandidateAsync(candidate, podGroupPreemptor, pluginName) + return nil + } + return e.prepareCandidate(ctx, candidate, podGroupPreemptor, pluginName) +} + +// prepareCandidateAsync triggers a goroutine for some preparation work: +// - Evict the victim pods +// - Reject the victim pods if they are in waitingPod map +// - Clear the low-priority pods' nominatedNodeName status if needed +// The Pod won't be retried until the goroutine triggered here completes. +// +// See http://kep.k8s.io/4832 for how the async preemption works. +func (e *Executor) prepareCandidateAsync(c Candidate, preemptor ExecutorPreemptor, pluginName string) { + // Intentionally create a new context, not using a ctx from the scheduling cycle, to create ctx, + // because this process could continue even after this scheduling cycle finishes. + ctx, cancel := context.WithCancel(context.Background()) + logger := klog.FromContext(ctx) + + victimPods := make([]*v1.Pod, 0, len(c.Victims().Pods)) + for _, victim := range c.Victims().Pods { + if victim.DeletionTimestamp != nil { + // Graceful pod deletion has already started. Sending another API call is unnecessary. + logger.V(2).Info("Victim Pod is already being deleted, skipping the API call for it", "preemptor", klog.KObj(preemptor), "node", c.Name(), "victim", klog.KObj(victim)) + continue + } + victimPods = append(victimPods, victim) + } + if len(victimPods) == 0 { + cancel() + return + } + + metrics.PreemptionVictims.Observe(float64(len(c.Victims().Pods))) + + errCh := parallelize.NewResultChannel[error]() + preemptPod := func(index int) { + victim := victimPods[index] + if err := e.PreemptPod(ctx, c, preemptor, victim, pluginName); err != nil { + errCh.SendWithCancel(err, cancel) + } + } + + e.mu.Lock() + e.preempting.Insert(preemptor.UID()) + e.mu.Unlock() + + go func() { + logger := klog.FromContext(ctx) + startTime := time.Now() + result := metrics.GoroutineResultSuccess + defer metrics.PreemptionGoroutinesDuration.WithLabelValues(result).Observe(metrics.SinceInSeconds(startTime)) + defer metrics.PreemptionGoroutinesExecutionTotal.WithLabelValues(result).Inc() + defer func() { + if result == metrics.GoroutineResultError { + // When API call isn't successful, the preemptor's Pods may get stuck in the unschedulable pod pool in the worst case. + // So, we should move the preemptor's Pods to the activeQ. + e.fh.Activate(logger, preemptor.Pods()) + } + }() + defer cancel() + logger.V(2).Info("Start the preemption asynchronously", "preemptor", klog.KObj(preemptor), "node", c.Name(), "numVictims", len(c.Victims().Pods), "numVictimsToDelete", len(victimPods)) + + // Lower priority pods nominated to run on this node, may no longer fit on + // this node. So, we should remove their nomination. Removing their + // nomination updates these pods and moves them to the active queue. It + // lets scheduler find another place for them sooner than after waiting for preemption completion. + nominatedPods := getLowerPriorityNominatedPods(e.fh, preemptor.Priority(), c.Name()) + if err := clearNominatedNodeName(ctx, e.fh.ClientSet(), e.fh.APICacher(), nominatedPods...); err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "Cannot clear 'NominatedNodeName' field from lower priority pods on the same target node", "node", c.Name()) + result = metrics.GoroutineResultError + // We do not return as this error is not critical. + } + + preemptLastVictim := true + if len(victimPods) > 1 { + // In order to prevent requesting preemption of the same pod multiple times for the same preemptor, + // preemptor is marked as "waiting for preemption of a victim" (by adding it to preempting map). + // We can evict all victims in parallel, but the last one. + // While deleting the last victim, we want the PreEnqueue check to be able to verify if the eviction + // is in fact ongoing, or if it has already completed, but the function has not returned yet. + // In the latter case, PreEnqueue (in `IsPodRunningPreemption`) reads the state of the last victim in + // `lastVictimsPendingPreemption` and does not block the pod. + // This helps us avoid the situation where pod removal might be notified to the scheduling queue before + // the preemptor completes the deletion API calls and is removed from the `preempting` map - that way + // the preemptor could end up stuck in the unschedulable pool, with all pod removal events being ignored. + e.fh.Parallelizer().Until(ctx, len(victimPods)-1, preemptPod, pluginName) + if err := errCh.Receive(); err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption") + result = metrics.GoroutineResultError + preemptLastVictim = false + } + } + + // If any of the previous victims failed to be preempted, then we can skip + // the preemption attempt for the last victim Pod to expedite the preemptor's + // re-entry to the scheduling cycle. + if preemptLastVictim { + lastVictim := victimPods[len(victimPods)-1] + e.mu.Lock() + e.lastVictimsPendingPreemption[preemptor.UID()] = pendingVictim{namespace: lastVictim.Namespace, name: lastVictim.Name} + e.mu.Unlock() + + if err := e.PreemptPod(ctx, c, preemptor, lastVictim, pluginName); err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption of the last victim") + result = metrics.GoroutineResultError + } + } + e.mu.Lock() + e.preempting.Delete(preemptor.UID()) + delete(e.lastVictimsPendingPreemption, preemptor.UID()) + e.mu.Unlock() + + logger.V(2).Info("Async Preemption finished completely", "preemptor", klog.KObj(preemptor), "node", c.Name(), "result", result) + }() +} + +// prepareCandidate does some preparation work before nominating the selected candidate: +// - Evict the victim pods +// - Reject the victim pods if they are in waitingPod map +// - Clear the low-priority pods' nominatedNodeName status if needed +func (e *Executor) prepareCandidate(ctx context.Context, c Candidate, preemptor ExecutorPreemptor, pluginName string) *fwk.Status { + metrics.PreemptionVictims.Observe(float64(len(c.Victims().Pods))) + + fh := e.fh + cs := e.fh.ClientSet() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + logger := klog.FromContext(ctx) + errCh := parallelize.NewResultChannel[error]() + fh.Parallelizer().Until(ctx, len(c.Victims().Pods), func(index int) { + victim := c.Victims().Pods[index] + if victim.DeletionTimestamp != nil { + // Graceful pod deletion has already started. Sending another API call is unnecessary. + logger.V(2).Info("Victim Pod is already being deleted, skipping the API call for it", "preemptor", klog.KObj(preemptor), "node", c.Name(), "victim", klog.KObj(victim)) + return + } + if err := e.PreemptPod(ctx, c, preemptor, victim, pluginName); err != nil { + errCh.SendWithCancel(err, cancel) + } + }, pluginName) + if err := errCh.Receive(); err != nil { + return fwk.AsStatus(err) + } + + // Lower priority pods nominated to run on this node, may no longer fit on + // this node. So, we should remove their nomination. Removing their + // nomination updates these pods and moves them to the active queue. It + // lets scheduler find another place for them sooner than after waiting for preemption completion. + nominatedPods := getLowerPriorityNominatedPods(fh, preemptor.Priority(), c.Name()) + if err := clearNominatedNodeName(ctx, cs, fh.APICacher(), nominatedPods...); err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "Cannot clear 'NominatedNodeName' field") + // We do not return as this error is not critical. + } + + return nil +} + +// IsPodRunningPreemption returns true if the pod is currently triggering preemption asynchronously. +func (e *Executor) IsPodRunningPreemption(podUID types.UID) bool { + return e.isRunningPreemption(podUID) +} + +// IsPodGroupRunningPreemption returns true if the pod group is currently triggering preemption asynchronously. +func (e *Executor) IsPodGroupRunningPreemption(podGroupUID types.UID) bool { + return e.isRunningPreemption(podGroupUID) +} + +func (e *Executor) isRunningPreemption(uid types.UID) bool { + e.mu.RLock() + defer e.mu.RUnlock() + + if !e.preempting.Has(uid) { + return false + } + + victim, ok := e.lastVictimsPendingPreemption[uid] + if !ok { + // Since preemptor is in `preempting` but last victim is not registered yet, the async preemption is pending. + return true + } + // Preemptor is waiting for preemption of one last victim. We can check if the victim has already been deleted. + victimPod, err := e.podLister.Pods(victim.namespace).Get(victim.name) + if err != nil { + // Victim already deleted, preemption is done. + return false + } + if victimPod.DeletionTimestamp != nil { + // Victim deletion has started, preemption is done. + return false + } + // Preemption of the last pod is still ongoing. + return true +} + +// clearNominatedNodeName internally submit a patch request to API server +// to set each pods[*].Status.NominatedNodeName> to "". +func clearNominatedNodeName(ctx context.Context, cs clientset.Interface, apiCacher fwk.APICacher, pods ...*v1.Pod) utilerrors.Aggregate { + var errs []error + for _, p := range pods { + if apiCacher != nil { + // When API cacher is available, use it to clear the NominatedNodeName. + _, err := apiCacher.PatchPodStatus(p, nil, &fwk.NominatingInfo{NominatedNodeName: "", NominatingMode: fwk.ModeOverride}) + if err != nil { + errs = append(errs, err) + } + } else { + if len(p.Status.NominatedNodeName) == 0 { + continue + } + podStatusCopy := p.Status.DeepCopy() + podStatusCopy.NominatedNodeName = "" + if err := util.PatchPodStatus(ctx, cs, p.Name, p.Namespace, &p.Status, podStatusCopy); err != nil { + errs = append(errs, err) + } + } + } + return utilerrors.NewAggregate(errs) +} + +// getLowerPriorityNominatedPods returns pods whose priority is smaller than the +// priority of the given "pod" and are nominated to run on the given node. +// Note: We could possibly check if the nominated lower priority pods still fit +// and return those that no longer fit, but that would require lots of +// manipulation of NodeInfo and PreFilter state per nominated pod. It may not be +// worth the complexity, especially because we generally expect to have a very +// small number of nominated pods per node. +func getLowerPriorityNominatedPods(pn fwk.PodNominator, priority int32, nodeName string) []*v1.Pod { + podInfos := pn.NominatedPodsForNode(nodeName) + + if len(podInfos) == 0 { + return nil + } + + var lowerPriorityPods []*v1.Pod + for _, pi := range podInfos { + if corev1helpers.PodPriority(pi.GetPod()) < priority { + lowerPriorityPods = append(lowerPriorityPods, pi.GetPod()) + } + } + return lowerPriorityPods +} + +// podExecutorPreemptor is a wrapper around single pod used by preemption execution. +type podExecutorPreemptor struct { + *v1.Pod +} + +func (p *podExecutorPreemptor) UID() types.UID { + return p.Pod.UID +} + +func (p *podExecutorPreemptor) SchedulerName() string { + return p.Spec.SchedulerName +} + +func (p *podExecutorPreemptor) GetName() string { + return p.Name +} + +func (p *podExecutorPreemptor) GetNamespace() string { + return p.Namespace +} + +func (p *podExecutorPreemptor) Obj() runtime.Object { + return p +} + +func (p *podExecutorPreemptor) Pods() map[string]*v1.Pod { + return map[string]*v1.Pod{p.Name: p.Pod} +} + +func (p *podExecutorPreemptor) Priority() int32 { + return corev1helpers.PodPriority(p.Pod) +} + +func (p *podExecutorPreemptor) Type() string { + return "pod" +} + +// podGroupExecutorPreemptor is a wrapper around pod group used by preemption execution. +type podGroupExecutorPreemptor struct { + pg *schedulingapi.PodGroup + pods []*v1.Pod +} + +func (p *podGroupExecutorPreemptor) UID() types.UID { + return p.pg.UID +} + +func (p *podGroupExecutorPreemptor) SchedulerName() string { + // All pods in a pod group should use the same scheduler name. + return p.pods[0].Spec.SchedulerName +} + +func (p *podGroupExecutorPreemptor) GetName() string { + return p.pg.Name +} + +func (p *podGroupExecutorPreemptor) GetNamespace() string { + return p.pg.Namespace +} + +func (p *podGroupExecutorPreemptor) Obj() runtime.Object { + return p.pg +} + +func (p *podGroupExecutorPreemptor) Priority() int32 { + return util.PodGroupPriority(p.pg) +} + +func (p *podGroupExecutorPreemptor) Pods() map[string]*v1.Pod { + m := make(map[string]*v1.Pod, len(p.pods)) + for _, pod := range p.pods { + m[pod.Name] = pod + } + return m +} + +func (p *podGroupExecutorPreemptor) Type() string { + return "podgroup" +} diff --git a/pkg/scheduler/framework/preemption/executor_test.go b/pkg/scheduler/framework/preemption/executor_test.go new file mode 100644 index 0000000000000..c4d7f42592c8f --- /dev/null +++ b/pkg/scheduler/framework/preemption/executor_test.go @@ -0,0 +1,1570 @@ +/* +Copyright 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 preemption + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + clientsetfake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + clienttesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/events" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + extenderv1 "k8s.io/kube-scheduler/extender/v1" + fwk "k8s.io/kube-scheduler/framework" + apicache "k8s.io/kubernetes/pkg/scheduler/backend/api_cache" + apidispatcher "k8s.io/kubernetes/pkg/scheduler/backend/api_dispatcher" + internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" + internalqueue "k8s.io/kubernetes/pkg/scheduler/backend/queue" + "k8s.io/kubernetes/pkg/scheduler/framework" + apicalls "k8s.io/kubernetes/pkg/scheduler/framework/api_calls" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort" + frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime" + "k8s.io/kubernetes/pkg/scheduler/metrics" + st "k8s.io/kubernetes/pkg/scheduler/testing" + tf "k8s.io/kubernetes/pkg/scheduler/testing/framework" +) + +type fakeHandleForLister struct { + fwk.Handle + informerFactory informers.SharedInformerFactory +} + +func (f *fakeHandleForLister) SharedInformerFactory() informers.SharedInformerFactory { + return f.informerFactory +} + +func TestIsPodRunningPreemption(t *testing.T) { + var ( + victim1 = st.MakePod().Namespace("ns").Name("victim1").UID("victim1"). + Node("node").SchedulerName("sch").Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victim2 = st.MakePod().Namespace("ns").Name("victim2").UID("victim2"). + Node("node").SchedulerName("sch").Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victimWithDeletionTimestamp = st.MakePod().Namespace("ns").Name("victim-deleted").UID("victim-deleted"). + Node("node").SchedulerName("sch").Priority(midPriority). + Terminating(). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + ) + + tests := []struct { + name string + preemptorUID types.UID + preemptingSet sets.Set[types.UID] + lastVictimSet map[types.UID]pendingVictim + podsInPodLister map[string]*v1.Pod + expectedResult bool + }{ + { + name: "preemptor not in preemptingSet", + preemptorUID: "preemptor", + preemptingSet: sets.New[types.UID](), + lastVictimSet: map[types.UID]pendingVictim{}, + expectedResult: false, + }, + { + name: "preemptor not in preemptingSet, lastVictimSet not empty", + preemptorUID: "preemptor", + preemptingSet: sets.New[types.UID](), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim1", + }, + }, + expectedResult: false, + }, + { + name: "preemptor in preemptingSet, no lastVictim for preemptor", + preemptorUID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "otherPod": { + namespace: "ns", + name: "victim1", + }, + }, + expectedResult: true, + }, + { + name: "preemptor in preemptingSet, victim in lastVictimSet, not in PodLister", + preemptorUID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim1", + }, + }, + podsInPodLister: map[string]*v1.Pod{}, + expectedResult: false, + }, + { + name: "preemptor in preemptingSet, victim in lastVictimSet and in PodLister", + preemptorUID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim1", + }, + }, + podsInPodLister: map[string]*v1.Pod{ + "victim1": victim1, + "victim2": victim2, + }, + expectedResult: true, + }, + { + name: "preemptor in preemptingSet, victim in lastVictimSet and in PodLister with deletion timestamp", + preemptorUID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim-deleted", + }, + }, + podsInPodLister: map[string]*v1.Pod{ + "victim1": victim1, + "victim-deleted": victimWithDeletionTimestamp, + }, + expectedResult: false, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%v", tt.name), func(t *testing.T) { + + client := clientsetfake.NewSimpleClientset() + informerFactory := informers.NewSharedInformerFactory(client, 0) + for _, pod := range tt.podsInPodLister { + err := informerFactory.Core().V1().Pods().Informer().GetIndexer().Add(pod) + if err != nil { + t.Errorf("Failed to add pod %v to indexer: %v", pod, err) + } + } + a := &Executor{ + fh: &fakeHandleForLister{informerFactory: informerFactory}, + podLister: informerFactory.Core().V1().Pods().Lister(), + preempting: tt.preemptingSet, + lastVictimsPendingPreemption: tt.lastVictimSet, + } + + if result := a.IsPodRunningPreemption(tt.preemptorUID); tt.expectedResult != result { + t.Errorf("Expected IsPodRunningPreemption to return %v but got %v", tt.expectedResult, result) + } + }) + } +} + +type fakePodActivator struct { + activatedPods map[string]*v1.Pod + mu *sync.RWMutex +} + +func (f *fakePodActivator) Activate(logger klog.Logger, pods map[string]*v1.Pod) { + f.mu.Lock() + defer f.mu.Unlock() + for name, pod := range pods { + f.activatedPods[name] = pod + } +} + +type fakePodNominator struct { + // embed it so that we can only override NominatedPodsForNode + internalqueue.SchedulingQueue + + // fakePodNominator doesn't respond to NominatedPodsForNode() until the channel is closed. + requestStopper chan struct{} +} + +func (f *fakePodNominator) NominatedPodsForNode(nodeName string) []fwk.PodInfo { + <-f.requestStopper + return nil +} + +func TestPrepareCandidate(t *testing.T) { + var ( + node1Name = "node1" + defaultSchedulerName = "default-scheduler" + ) + condition := v1.PodCondition{ + Type: v1.DisruptionTarget, + Status: v1.ConditionTrue, + Reason: v1.PodReasonPreemptionByScheduler, + Message: fmt.Sprintf("%s: preempting to accommodate a higher priority pod", defaultSchedulerName), + } + podGroupCondition := v1.PodCondition{ + Type: v1.DisruptionTarget, + Status: v1.ConditionTrue, + Reason: v1.PodReasonPreemptionByScheduler, + Message: fmt.Sprintf("%s: preempting to accommodate a higher priority podgroup", defaultSchedulerName), + } + + var ( + victim1 = st.MakePod().Name("victim1").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + notFoundVictim1 = st.MakePod().Name("not-found-victim").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + failVictim = st.MakePod().Name("fail-victim").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victim2 = st.MakePod().Name("victim2").UID("victim2"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(50000). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victim1WithMatchingCondition = st.MakePod().Name("victim1").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Conditions([]v1.PodCondition{condition}). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + failVictim1WithMatchingCondition = st.MakePod().Name("fail-victim").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Conditions([]v1.PodCondition{condition}). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + failVictim1WithMatchingPodGroupCondition = st.MakePod().Name("fail-victim").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Conditions([]v1.PodCondition{podGroupCondition}). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + preemptor = st.MakePod().Name("preemptor").UID("preemptor"). + SchedulerName(defaultSchedulerName).Priority(highPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + podGroupPreemptor = &schedulingapi.PodGroup{ObjectMeta: metav1.ObjectMeta{Name: "pg1", Namespace: "default", UID: "pg1"}} + + errDeletePodFailed = errors.New("delete pod failed") + errPatchStatusFailed = errors.New("patch pod status failed") + ) + + victimWithDeletionTimestamp := victim1.DeepCopy() + victimWithDeletionTimestamp.Name = "victim1-with-deletion-timestamp" + victimWithDeletionTimestamp.UID = "victim1-with-deletion-timestamp" + victimWithDeletionTimestamp.DeletionTimestamp = &metav1.Time{Time: time.Now().Add(-100 * time.Second)} + victimWithDeletionTimestamp.Finalizers = []string{"test"} + + tests := []struct { + name string + nodeNames []string + candidate Candidate + preemptor *v1.Pod + preemptorPodGroup *schedulingapi.PodGroup + testPods []*v1.Pod + // expectedDeletedPod is the pod name that is expected to be deleted. + // + // You can set multiple pod name if there're multiple possibilities. + // Both empty and "" means no pod is expected to be deleted. + expectedDeletedPod []string + expectedDeletionError bool + expectedPatchError bool + // Only compared when async preemption is disabled. + expectedStatus *fwk.Status + // Only compared when async preemption is enabled. + expectedPreemptingMap sets.Set[types.UID] + expectedActivatedPods map[string]*v1.Pod + }{ + { + name: "no victims", + candidate: &candidate{ + victims: &extenderv1.Victims{}, + }, + preemptor: preemptor, + testPods: []*v1.Pod{ + victim1, + }, + nodeNames: []string{node1Name}, + expectedStatus: nil, + }, + { + name: "one victim without condition", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{ + victim1, + }, + nodeNames: []string{node1Name}, + expectedDeletedPod: []string{"victim1"}, + expectedStatus: nil, + expectedPreemptingMap: sets.New(types.UID("preemptor")), + }, + { + name: "podgroup preemptor, one victim without condition", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1, + }, + }, + }, + preemptor: preemptor, + preemptorPodGroup: podGroupPreemptor, + testPods: []*v1.Pod{ + victim1, + }, + nodeNames: []string{node1Name}, + expectedDeletedPod: []string{"victim1"}, + expectedStatus: nil, + expectedPreemptingMap: sets.New(types.UID("pg1")), + }, + { + name: "one victim, but victim is already being deleted", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victimWithDeletionTimestamp, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{ + victimWithDeletionTimestamp, + }, + nodeNames: []string{node1Name}, + expectedStatus: nil, + }, + { + name: "one victim, but victim is already deleted", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + notFoundVictim1, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{}, + nodeNames: []string{node1Name}, + expectedStatus: nil, + expectedPreemptingMap: sets.New(types.UID("preemptor")), + }, + { + name: "one victim with same condition", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1WithMatchingCondition, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{ + victim1WithMatchingCondition, + }, + nodeNames: []string{node1Name}, + expectedDeletedPod: []string{"victim1"}, + expectedStatus: nil, + expectedPreemptingMap: sets.New(types.UID("preemptor")), + }, + { + name: "one victim, not-found victim error is ignored when patching", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1WithMatchingCondition, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{}, + nodeNames: []string{node1Name}, + expectedDeletedPod: []string{"victim1"}, + expectedStatus: nil, + expectedPreemptingMap: sets.New(types.UID("preemptor")), + }, + { + name: "one victim, but pod deletion failed", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + failVictim1WithMatchingCondition, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{}, + expectedDeletionError: true, + nodeNames: []string{node1Name}, + expectedStatus: fwk.AsStatus(errDeletePodFailed), + expectedPreemptingMap: sets.New(types.UID("preemptor")), + expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, + }, + { + name: "podgroup preemptor, one victim, but pod deletion failed", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + failVictim1WithMatchingPodGroupCondition, + }, + }, + }, + preemptorPodGroup: podGroupPreemptor, + preemptor: preemptor, + testPods: []*v1.Pod{}, + expectedDeletionError: true, + nodeNames: []string{node1Name}, + expectedStatus: fwk.AsStatus(errDeletePodFailed), + expectedPreemptingMap: sets.New(types.UID("pg1")), + expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, + }, + { + name: "one victim, not-found victim error is ignored when deleting", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{}, + nodeNames: []string{node1Name}, + expectedDeletedPod: []string{"victim1"}, + expectedStatus: nil, + expectedPreemptingMap: sets.New(types.UID("preemptor")), + }, + { + name: "one victim, but patch pod failed", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + failVictim, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{}, + expectedPatchError: true, + nodeNames: []string{node1Name}, + expectedStatus: fwk.AsStatus(errPatchStatusFailed), + expectedPreemptingMap: sets.New(types.UID("preemptor")), + expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, + }, + { + name: "two victims without condition, one passes successfully and the second fails", + + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + failVictim, + victim2, + }, + }, + }, + preemptor: preemptor, + testPods: []*v1.Pod{ + victim1, + }, + nodeNames: []string{node1Name}, + expectedPatchError: true, + expectedDeletedPod: []string{ + "victim2", + // The first victim could fail before the deletion of the second victim happens, + // which results in the second victim not being deleted. + "", + }, + expectedStatus: fwk.AsStatus(errPatchStatusFailed), + expectedPreemptingMap: sets.New(types.UID("preemptor")), + expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, + }, + } + + for _, asyncPreemptionEnabled := range []bool{true, false} { + for _, asyncAPICallsEnabled := range []bool{true, false} { + for _, tt := range tests { + t.Run(fmt.Sprintf("%v (Async preemption enabled: %v, Async API calls enabled: %v)", tt.name, asyncPreemptionEnabled, asyncAPICallsEnabled), func(t *testing.T) { + metrics.Register() + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + nodes := make([]*v1.Node, len(tt.nodeNames)) + for i, nodeName := range tt.nodeNames { + nodes[i] = st.MakeNode().Name(nodeName).Capacity(veryLargeRes).Obj() + } + registeredPlugins := append([]tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New)}, + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ) + var objs []runtime.Object + for _, pod := range tt.testPods { + objs = append(objs, pod) + } + + mu := &sync.RWMutex{} + deletedPods := sets.New[string]() + deletionFailure := false // whether any request to delete pod failed + patchFailure := false // whether any request to patch pod status failed + + cs := clientsetfake.NewClientset(objs...) + cs.PrependReactor("delete", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + mu.Lock() + defer mu.Unlock() + name := action.(clienttesting.DeleteAction).GetName() + if name == "fail-victim" { + deletionFailure = true + return true, nil, errDeletePodFailed + } + // fake clientset does not return an error for not-found pods, so we simulate it here. + if name == "not-found-victim" { + // Simulate a not-found error. + return true, nil, apierrors.NewNotFound(v1.Resource("pods"), name) + } + + deletedPods.Insert(name) + return true, nil, nil + }) + + cs.PrependReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + mu.Lock() + defer mu.Unlock() + if action.(clienttesting.PatchAction).GetName() == "fail-victim" { + patchFailure = true + return true, nil, errPatchStatusFailed + } + // fake clientset does not return an error for not-found pods, so we simulate it here. + if action.(clienttesting.PatchAction).GetName() == "not-found-victim" { + return true, nil, apierrors.NewNotFound(v1.Resource("pods"), "not-found-victim") + } + return true, nil, nil + }) + + informerFactory := informers.NewSharedInformerFactory(cs, 0) + eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: cs.EventsV1()}) + fakeActivator := &fakePodActivator{activatedPods: make(map[string]*v1.Pod), mu: mu} + + // Note: NominatedPodsForNode is called at the beginning of the goroutine in any case. + // fakePodNominator can delay the response of NominatedPodsForNode until the channel is closed, + // which allows us to test the preempting map before the goroutine does nothing yet. + requestStopper := make(chan struct{}) + nominator := &fakePodNominator{ + SchedulingQueue: internalqueue.NewSchedulingQueue(nil, informerFactory), + requestStopper: requestStopper, + } + var apiDispatcher *apidispatcher.APIDispatcher + if asyncAPICallsEnabled { + apiDispatcher = apidispatcher.New(cs, 16, apicalls.Relevances) + apiDispatcher.Run(logger) + defer apiDispatcher.Close() + } + + framework, err := tf.NewFramework( + ctx, + registeredPlugins, "", + frameworkruntime.WithClientSet(cs), + frameworkruntime.WithAPIDispatcher(apiDispatcher), + frameworkruntime.WithLogger(logger), + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), + frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(tt.testPods, nodes)), + frameworkruntime.WithPodNominator(nominator), + frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, "test-scheduler")), + frameworkruntime.WithPodActivator(fakeActivator), + ) + if err != nil { + t.Fatal(err) + } + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + if asyncAPICallsEnabled { + cache := internalcache.New(ctx, apiDispatcher, false) + framework.SetAPICacher(apicache.New(nil, cache)) + } + + executor := NewExecutor(framework, feature.Features{EnableAsyncPreemption: asyncPreemptionEnabled}) + + if asyncPreemptionEnabled { + if tt.preemptorPodGroup != nil { + executor.prepareCandidateAsync(tt.candidate, &podGroupExecutorPreemptor{pg: tt.preemptorPodGroup, pods: []*v1.Pod{tt.preemptor}}, "test-plugin") + } else { + executor.prepareCandidateAsync(tt.candidate, &podExecutorPreemptor{Pod: tt.preemptor}, "test-plugin") + } + executor.mu.Lock() + + expectedMap := tt.expectedPreemptingMap + + // The preempting map should be registered synchronously + // so we don't need wait.Poll. + if !expectedMap.Equal(executor.preempting) { + t.Errorf("expected preempting map %v, got %v", expectedMap, executor.preempting) + close(requestStopper) + executor.mu.Unlock() + return + } + executor.mu.Unlock() + // make the requests complete + close(requestStopper) + } else { + close(requestStopper) // no need to stop requests + var status *fwk.Status + if tt.preemptorPodGroup != nil { + status = executor.prepareCandidate(ctx, tt.candidate, &podGroupExecutorPreemptor{pg: tt.preemptorPodGroup, pods: []*v1.Pod{tt.preemptor}}, "test-plugin") + } else { + status = executor.prepareCandidate(ctx, tt.candidate, &podExecutorPreemptor{Pod: tt.preemptor}, "test-plugin") + } + if tt.expectedStatus == nil { + if status != nil { + t.Errorf("expect nil status, but got %v", status) + } + } else { + if !cmp.Equal(status, tt.expectedStatus) { + t.Errorf("expect status %v, but got %v", tt.expectedStatus, status) + } + } + } + + var lastErrMsg string + if err := wait.PollUntilContextTimeout(ctx, time.Millisecond*200, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) { + mu.RLock() + defer mu.RUnlock() + + executor.mu.Lock() + defer executor.mu.Unlock() + if len(executor.preempting) != 0 { + // The preempting map should be empty after the goroutine in all test cases. + lastErrMsg = fmt.Sprintf("expected no preempting pods, got %v", executor.preempting) + return false, nil + } + + if tt.expectedDeletionError != deletionFailure { + lastErrMsg = fmt.Sprintf("expected deletion error %v, got %v", tt.expectedDeletionError, deletionFailure) + return false, nil + } + if tt.expectedPatchError != patchFailure { + lastErrMsg = fmt.Sprintf("expected patch error %v, got %v", tt.expectedPatchError, patchFailure) + return false, nil + } + + if asyncPreemptionEnabled { + if diff := cmp.Diff(tt.expectedActivatedPods, fakeActivator.activatedPods); tt.expectedActivatedPods != nil && diff != "" { + lastErrMsg = fmt.Sprintf("Unexpected activated pods (-want,+got):\n%s", diff) + return false, nil + } + if tt.expectedActivatedPods == nil && len(fakeActivator.activatedPods) != 0 { + lastErrMsg = fmt.Sprintf("expected no activated pods, got %v", fakeActivator.activatedPods) + return false, nil + } + } + + if deletedPods.Len() > 1 { + // For now, we only expect at most one pod to be deleted in all test cases. + // If we need to test multiple pods deletion, we need to update the test table definition. + return false, fmt.Errorf("expected at most one pod to be deleted, got %v", deletedPods.UnsortedList()) + } + + if len(tt.expectedDeletedPod) == 0 { + if deletedPods.Len() != 0 { + // When tt.expectedDeletedPod is empty, we expect no pod to be deleted. + return false, fmt.Errorf("expected no pod to be deleted, got %v", deletedPods.UnsortedList()) + } + // nothing further to check. + return true, nil + } + + found := false + for _, podName := range tt.expectedDeletedPod { + if deletedPods.Has(podName) || + // If podName is empty, we expect no pod to be deleted. + (deletedPods.Len() == 0 && podName == "") { + found = true + } + } + if !found { + lastErrMsg = fmt.Sprintf("expected pod %v to be deleted, but %v is deleted", strings.Join(tt.expectedDeletedPod, " or "), deletedPods.UnsortedList()) + return false, nil + } + + return true, nil + }); err != nil { + t.Fatal(lastErrMsg) + } + }) + } + } + + } +} + +func TestPrepareCandidateAsyncSetsPreemptingSets(t *testing.T) { + var ( + node1Name = "node1" + defaultSchedulerName = "default-scheduler" + ) + + var ( + victim1 = st.MakePod().Name("victim1").UID("victim1"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victim2 = st.MakePod().Name("victim2").UID("victim2"). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + preemptor = st.MakePod().Name("preemptor").UID("preemptor"). + SchedulerName(defaultSchedulerName).Priority(highPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + preemptorPodGroup = &schedulingapi.PodGroup{ObjectMeta: metav1.ObjectMeta{Name: "pg1", Namespace: "default", UID: "pg1"}} + testPods = []*v1.Pod{ + victim1, + victim2, + } + nodeNames = []string{node1Name} + ) + + tests := []struct { + name string + candidate Candidate + lastVictim *v1.Pod + preemptor *v1.Pod + }{ + { + name: "no victims", + candidate: &candidate{ + victims: &extenderv1.Victims{}, + }, + lastVictim: nil, + preemptor: preemptor, + }, + { + name: "one victim", + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1, + }, + }, + }, + lastVictim: victim1, + preemptor: preemptor, + }, + { + name: "two victims", + candidate: &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: []*v1.Pod{ + victim1, + victim2, + }, + }, + }, + lastVictim: victim2, + preemptor: preemptor, + }, + } + + for _, isPodGroup := range []bool{false, true} { + for _, asyncAPICallsEnabled := range []bool{true, false} { + for _, tt := range tests { + t.Run(fmt.Sprintf("%v (isPodGroup: %v, Async API calls enabled: %v)", tt.name, isPodGroup, asyncAPICallsEnabled), func(t *testing.T) { + metrics.Register() + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + nodes := make([]*v1.Node, len(nodeNames)) + for i, nodeName := range nodeNames { + nodes[i] = st.MakeNode().Name(nodeName).Capacity(veryLargeRes).Obj() + } + registeredPlugins := append([]tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New)}, + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ) + var objs []runtime.Object + for _, pod := range testPods { + objs = append(objs, pod) + } + + cs := clientsetfake.NewClientset(objs...) + + informerFactory := informers.NewSharedInformerFactory(cs, 0) + eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: cs.EventsV1()}) + + var apiDispatcher *apidispatcher.APIDispatcher + if asyncAPICallsEnabled { + apiDispatcher = apidispatcher.New(cs, 16, apicalls.Relevances) + apiDispatcher.Run(logger) + defer apiDispatcher.Close() + } + + fwk, err := tf.NewFramework( + ctx, + registeredPlugins, "", + frameworkruntime.WithClientSet(cs), + frameworkruntime.WithAPIDispatcher(apiDispatcher), + frameworkruntime.WithLogger(logger), + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), + frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(testPods, nodes)), + frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, "test-scheduler")), + frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)), + ) + if err != nil { + t.Fatal(err) + } + informerFactory.Start(ctx.Done()) + if asyncAPICallsEnabled { + cache := internalcache.New(ctx, apiDispatcher, false) + fwk.SetAPICacher(apicache.New(nil, cache)) + } + + executor := NewExecutor(fwk, feature.Features{EnableAsyncPreemption: true}) + + expectedPreemptorUID := tt.preemptor.UID + if isPodGroup { + expectedPreemptorUID = preemptorPodGroup.UID + } + // preemptPodCallsCounter helps verify if the last victim pod gets preempted after other victims. + preemptPodCallsCounter := 0 + preemptFunc := executor.PreemptPod + executor.PreemptPod = func(ctx context.Context, c Candidate, preemptor ExecutorPreemptor, victim *v1.Pod, pluginName string) error { + // Verify contents of the sets: preempting and lastVictimsPendingPreemption before preemption of subsequent pods. + executor.mu.RLock() + preemptPodCallsCounter++ + + if !executor.preempting.Has(expectedPreemptorUID) { + t.Errorf("Expected preempting set to be contain %v before preempting victim %v but got set: %v", expectedPreemptorUID, victim.Name, executor.preempting) + } + + victimCount := len(tt.candidate.Victims().Pods) + if victim.Name == tt.lastVictim.Name { + if victimCount != preemptPodCallsCounter { + t.Errorf("Expected PreemptPod for last victim %v to be called last (call no. %v), but it was called as no. %v", victim.Name, victimCount, preemptPodCallsCounter) + } + + if v, ok := executor.lastVictimsPendingPreemption[expectedPreemptorUID]; !ok || tt.lastVictim.Name != v.name { + t.Errorf("Expected lastVictimsPendingPreemption map to contain victim %v for preemptor UID %v when preempting the last victim, but got map: %v", + tt.lastVictim.Name, expectedPreemptorUID, executor.lastVictimsPendingPreemption) + } + } else { + if preemptPodCallsCounter >= victimCount { + t.Errorf("Expected PreemptPod for victim %v to be called earlier, but it was called as last - no. %v", victim.Name, preemptPodCallsCounter) + } + if _, ok := executor.lastVictimsPendingPreemption[expectedPreemptorUID]; ok { + t.Errorf("Expected lastVictimsPendingPreemption map to not contain values for preemptor UID %v when not preempting the last victim, but got map: %v", + expectedPreemptorUID, executor.lastVictimsPendingPreemption) + } + } + executor.mu.RUnlock() + + return preemptFunc(ctx, c, preemptor, victim, pluginName) + } + + executor.mu.RLock() + if len(executor.preempting) > 0 { + t.Errorf("Expected preempting set to be empty before prepareCandidateAsync but got %v", executor.preempting) + } + if len(executor.lastVictimsPendingPreemption) > 0 { + t.Errorf("Expected lastVictimsPendingPreemption map to be empty before prepareCandidateAsync but got %v", executor.lastVictimsPendingPreemption) + } + executor.mu.RUnlock() + + var preemptor ExecutorPreemptor = &podExecutorPreemptor{Pod: tt.preemptor} + if isPodGroup { + preemptor = &podGroupExecutorPreemptor{pg: preemptorPodGroup, pods: []*v1.Pod{tt.preemptor}} + } + executor.prepareCandidateAsync(tt.candidate, preemptor, "test-plugin") + + // Perform the checks when there are no victims left to preempt. + t.Log("Waiting for async preemption goroutine to finish cleanup...") + + err = wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 2*time.Second, false, func(ctx context.Context) (bool, error) { + // Check if the preemptor is removed from the ev.preempting set. + executor.mu.RLock() + defer executor.mu.RUnlock() + return !executor.preempting.Has(expectedPreemptorUID), nil + }) + if err != nil { + t.Errorf("Timed out waiting for preemptingSet to become empty. %v", err) + } + + executor.mu.RLock() + if _, ok := executor.lastVictimsPendingPreemption[expectedPreemptorUID]; ok { + t.Errorf("Expected lastVictimsPendingPreemption map to not contain values for %v after completing preemption, but got map: %v", + expectedPreemptorUID, executor.lastVictimsPendingPreemption) + } + if victimCount := len(tt.candidate.Victims().Pods); victimCount != preemptPodCallsCounter { + t.Errorf("Expected PreemptPod to be called %v times during prepareCandidateAsync but got %v", victimCount, preemptPodCallsCounter) + } + executor.mu.RUnlock() + }) + } + } + } +} + +func TestAsyncPreemptionFailure(t *testing.T) { + metrics.Register() + var ( + node1Name = "node1" + defaultSchedulerName = "default-scheduler" + failVictimNamePrefix = "fail-victim" + ) + + makePod := func(name string, priority int32) *v1.Pod { + return st.MakePod().Name(name).UID(name). + Node(node1Name).SchedulerName(defaultSchedulerName).Priority(priority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + } + + preemptor := makePod("preemptor", highPriority) + + makeVictim := func(name string) *v1.Pod { + return makePod(name, midPriority) + } + + tests := []struct { + name string + victims []*v1.Pod + preemptorPodGroup *schedulingapi.PodGroup + preemptorPods []*v1.Pod + expectSuccessfulPreemption bool + expectPreemptionAttemptForLastVictim bool + }{ + { + name: "Failure with a single victim", + victims: []*v1.Pod{ + makeVictim(failVictimNamePrefix), + }, + expectSuccessfulPreemption: false, + expectPreemptionAttemptForLastVictim: true, + }, + { + name: "Success with a single victim", + victims: []*v1.Pod{ + makeVictim("victim1"), + }, + expectSuccessfulPreemption: true, + expectPreemptionAttemptForLastVictim: true, + }, + { + name: "Failure in first of three victims", + victims: []*v1.Pod{ + makeVictim(failVictimNamePrefix), + makeVictim("victim2"), + makeVictim("victim3"), + }, + expectSuccessfulPreemption: false, + expectPreemptionAttemptForLastVictim: false, + }, + { + name: "Failure in second of three victims", + victims: []*v1.Pod{ + makeVictim("victim1"), + makeVictim(failVictimNamePrefix), + makeVictim("victim3"), + }, + expectSuccessfulPreemption: false, + expectPreemptionAttemptForLastVictim: false, + }, + { + name: "Failure in first two of three victims", + victims: []*v1.Pod{ + makeVictim(failVictimNamePrefix + "1"), + makeVictim(failVictimNamePrefix + "2"), + makeVictim("victim3"), + }, + expectSuccessfulPreemption: false, + expectPreemptionAttemptForLastVictim: false, + }, + { + name: "Failure in third of three victims", + victims: []*v1.Pod{ + makeVictim("victim1"), + makeVictim("victim2"), + makeVictim(failVictimNamePrefix), + }, + expectSuccessfulPreemption: false, + expectPreemptionAttemptForLastVictim: true, + }, + { + name: "Success with three victims", + victims: []*v1.Pod{ + makeVictim("victim1"), + makeVictim("victim2"), + makeVictim("victim3"), + }, + expectSuccessfulPreemption: true, + expectPreemptionAttemptForLastVictim: true, + }, + { + name: "Failure with a single victim for pod group", + victims: []*v1.Pod{ + makeVictim(failVictimNamePrefix), + }, + preemptorPodGroup: &schedulingapi.PodGroup{ObjectMeta: metav1.ObjectMeta{Name: "pg1", Namespace: "default"}}, + preemptorPods: []*v1.Pod{makePod("pod1", highPriority), makePod("pod2", highPriority)}, + expectSuccessfulPreemption: false, + expectPreemptionAttemptForLastVictim: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + candidate := &candidate{ + name: node1Name, + victims: &extenderv1.Victims{ + Pods: tt.victims, + }, + } + + // Set up the fake clientset. + preemptionAttemptedPods := sets.New[string]() + deletedPods := sets.New[string]() + mu := &sync.RWMutex{} + objs := []runtime.Object{preemptor} + for _, v := range tt.victims { + objs = append(objs, v) + } + for _, p := range tt.preemptorPods { + objs = append(objs, p) + } + + cs := clientsetfake.NewClientset(objs...) + cs.PrependReactor("delete", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + mu.Lock() + defer mu.Unlock() + name := action.(clienttesting.DeleteAction).GetName() + preemptionAttemptedPods.Insert(name) + if strings.HasPrefix(name, failVictimNamePrefix) { + return true, nil, errors.New("delete pod failed") + } + deletedPods.Insert(name) + return true, nil, nil + }) + + // Set up the framework. + informerFactory := informers.NewSharedInformerFactory(cs, 0) + eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: cs.EventsV1()}) + fakeActivator := &fakePodActivator{activatedPods: make(map[string]*v1.Pod), mu: mu} + + registeredPlugins := append([]tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New)}, + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ) + + snapshotPods := append([]*v1.Pod{preemptor}, tt.victims...) + fwk, err := tf.NewFramework( + ctx, + registeredPlugins, "", + frameworkruntime.WithClientSet(cs), + frameworkruntime.WithLogger(logger), + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)), + frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, "test-scheduler")), + frameworkruntime.WithPodActivator(fakeActivator), + frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), + frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(snapshotPods, []*v1.Node{st.MakeNode().Name(node1Name).Obj()})), + ) + if err != nil { + t.Fatal(err) + } + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + + executor := NewExecutor(fwk, feature.Features{EnableAsyncPreemption: true}) + + // Run the actual preemption. + if tt.preemptorPodGroup != nil { + executor.prepareCandidateAsync(candidate, &podGroupExecutorPreemptor{pg: tt.preemptorPodGroup, pods: tt.preemptorPods}, "test-plugin") + } else { + executor.prepareCandidateAsync(candidate, &podExecutorPreemptor{Pod: preemptor}, "test-plugin") + } + + // Wait for the async preemption to finish. + err = wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 5*time.Second, false, func(ctx context.Context) (bool, error) { + // Check if the preemptor is removed from the executor.preempting set. + executor.mu.RLock() + defer executor.mu.RUnlock() + return len(executor.preempting) == 0, nil + }) + if err != nil { + t.Fatalf("Timed out waiting for async preemption to finish: %v", err) + } + + mu.RLock() + defer mu.RUnlock() + + lastVictimName := tt.victims[len(tt.victims)-1].Name + if tt.expectPreemptionAttemptForLastVictim != preemptionAttemptedPods.Has(lastVictimName) { + t.Errorf("Last victim's preemption attempted - wanted: %v, got: %v", tt.expectPreemptionAttemptForLastVictim, preemptionAttemptedPods.Has(lastVictimName)) + } + // Verify that the preemption of the last victim is attempted if and only if + // the preemption of all of the preceding victims succeeds. + precedingVictimsPreempted := true + for _, victim := range tt.victims[:len(tt.victims)-1] { + if !preemptionAttemptedPods.Has(victim.Name) || !deletedPods.Has(victim.Name) { + precedingVictimsPreempted = false + } + } + if precedingVictimsPreempted != preemptionAttemptedPods.Has(lastVictimName) { + t.Errorf("Last victim's preemption attempted - wanted: %v, got: %v", precedingVictimsPreempted, preemptionAttemptedPods.Has(lastVictimName)) + } + + // Verify that the preemptor is activated if and only if the async preemption fails. + if tt.preemptorPodGroup != nil { + if len(fakeActivator.activatedPods) != len(tt.preemptorPods) { + t.Errorf("Expected %d pods to be activated, but got %v", len(tt.preemptorPods), fakeActivator.activatedPods) + } + for _, p := range tt.preemptorPods { + if _, ok := fakeActivator.activatedPods[p.Name]; !ok { + t.Errorf("Expected preemptor pod %s to be activated, but got %v", p.Name, fakeActivator.activatedPods) + } + } + } else { + if _, ok := fakeActivator.activatedPods[preemptor.Name]; ok != !tt.expectSuccessfulPreemption { + t.Errorf("Preemptor activated - wanted: %v, got: %v", !tt.expectSuccessfulPreemption, ok) + } + } + + // Verify if the last victim got deleted as expected. + if tt.expectSuccessfulPreemption != deletedPods.Has(lastVictimName) { + t.Errorf("Last victim deleted - wanted: %v, got: %v", tt.expectSuccessfulPreemption, deletedPods.Has(lastVictimName)) + } + }) + } +} + +func TestRemoveNominatedNodeName(t *testing.T) { + tests := []struct { + name string + currentNominatedNodeName string + newNominatedNodeName string + expectPatchRequest bool + expectedPatchData string + }{ + { + name: "Should make patch request to clear node name", + currentNominatedNodeName: "node1", + expectPatchRequest: true, + expectedPatchData: `{"status":{"nominatedNodeName":null}}`, + }, + { + name: "Should not make patch request if nominated node is already cleared", + currentNominatedNodeName: "", + expectPatchRequest: false, + }, + } + for _, asyncAPICallsEnabled := range []bool{true, false} { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + actualPatchRequests := 0 + var actualPatchData string + cs := &clientsetfake.Clientset{} + patchCalled := make(chan struct{}, 1) + cs.AddReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + actualPatchRequests++ + patch := action.(clienttesting.PatchAction) + actualPatchData = string(patch.GetPatch()) + patchCalled <- struct{}{} + // For this test, we don't care about the result of the patched pod, just that we got the expected + // patch request, so just returning &v1.Pod{} here is OK because scheduler doesn't use the response. + return true, &v1.Pod{}, nil + }) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Status: v1.PodStatus{NominatedNodeName: test.currentNominatedNodeName}, + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + var apiCacher fwk.APICacher + if asyncAPICallsEnabled { + apiDispatcher := apidispatcher.New(cs, 16, apicalls.Relevances) + apiDispatcher.Run(logger) + defer apiDispatcher.Close() + + informerFactory := informers.NewSharedInformerFactory(cs, 0) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory, internalqueue.WithAPIDispatcher(apiDispatcher)) + apiCacher = apicache.New(queue, nil) + } + + if err := clearNominatedNodeName(ctx, cs, apiCacher, pod); err != nil { + t.Fatalf("Error calling removeNominatedNodeName: %v", err) + } + + if test.expectPatchRequest { + select { + case <-patchCalled: + case <-time.After(time.Second): + t.Fatalf("Timed out while waiting for patch to be called") + } + if actualPatchData != test.expectedPatchData { + t.Fatalf("Patch data mismatch: Actual was %v, but expected %v", actualPatchData, test.expectedPatchData) + } + } else { + select { + case <-patchCalled: + t.Fatalf("Expected patch not to be called, actual patch data: %v", actualPatchData) + case <-time.After(time.Second): + } + } + }) + } + } +} + +func TestPreemptPod(t *testing.T) { + preemptorPod := st.MakePod().Name("p").UID("p").Priority(highPriority).Obj() + preemptorPodGroup := &schedulingapi.PodGroup{ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: "default"}} + preemptorPods := []*v1.Pod{st.MakePod().Name("p1").UID("p1").Priority(highPriority).Obj(), st.MakePod().Name("p2").UID("p2").Priority(highPriority).Obj()} + + victimPod := st.MakePod().Name("v").UID("v").Priority(midPriority).Obj() + + tests := []struct { + name string + addVictimToPrebind bool + addVictimToWaiting bool + expectCancel bool + expectedActions []string + }{ + { + name: "victim is in preBind, context should be cancelled", + addVictimToPrebind: true, + addVictimToWaiting: false, + expectCancel: true, + expectedActions: []string{}, + }, + { + name: "victim is in waiting pods, it should be rejected (no calls to apiserver)", + addVictimToPrebind: false, + addVictimToWaiting: true, + expectCancel: false, + expectedActions: []string{}, + }, + { + name: "victim is not in waiting/preBind pods, pod should be deleted", + addVictimToPrebind: false, + addVictimToWaiting: false, + expectCancel: false, + expectedActions: []string{"patch", "delete"}, + }, + } + + for _, isPodGroup := range []bool{false, true} { + for _, tt := range tests { + t.Run(fmt.Sprintf("%v (isPodGroup: %v)", tt.name, isPodGroup), func(t *testing.T) { + podsInPreBind := frameworkruntime.NewPodsInPreBindMap() + waitingPods := frameworkruntime.NewWaitingPodsMap() + registeredPlugins := append([]tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New)}, + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + tf.RegisterPermitPlugin(waitingPermitPluginName, newWaitingPermitPlugin), + ) + objs := []runtime.Object{preemptorPod, victimPod} + if isPodGroup { + for _, p := range preemptorPods { + objs = append(objs, p) + } + } + cs := clientsetfake.NewClientset(objs...) + informerFactory := informers.NewSharedInformerFactory(cs, 0) + eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: cs.EventsV1()}) + logger, ctx := ktesting.NewTestContext(t) + + fwk, err := tf.NewFramework( + ctx, + registeredPlugins, "", + frameworkruntime.WithClientSet(cs), + frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot([]*v1.Pod{}, []*v1.Node{})), + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithWaitingPods(waitingPods), + frameworkruntime.WithPodsInPreBind(podsInPreBind), + frameworkruntime.WithLogger(logger), + frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, "test-scheduler")), + ) + if err != nil { + t.Fatal(err) + } + var victimCtx context.Context + var cancel context.CancelCauseFunc + if tt.addVictimToPrebind { + victimCtx, cancel = context.WithCancelCause(context.Background()) + fwk.AddPodInPreBind(victimPod.UID, cancel) + } + if tt.addVictimToWaiting { + pluginsWaitTime, status := fwk.RunPermitPlugins(ctx, framework.NewCycleState(), victimPod, "fake-node") + if !status.IsWait() { + t.Fatalf("Failed to add a pod to waiting list") + } + fwk.AddWaitingPod(victimPod, pluginsWaitTime) + } + pe := NewExecutor(fwk, feature.Features{}) + + var preemptor ExecutorPreemptor = &podExecutorPreemptor{Pod: preemptorPod} + if isPodGroup { + preemptor = &podGroupExecutorPreemptor{pg: preemptorPodGroup, pods: preemptorPods} + } + + err = pe.PreemptPod(ctx, &candidate{name: "fake-node"}, preemptor, victimPod, "test-plugin") + if err != nil { + t.Fatal(err) + } + if tt.expectCancel { + if victimCtx.Err() == nil { + t.Errorf("Context of a binding pod should be cancelled") + } + } else { + if victimCtx != nil && victimCtx.Err() != nil { + t.Errorf("Context of a normal pod should not be cancelled") + } + } + + // check if the API call was made + actions := cs.Actions() + if len(actions) != len(tt.expectedActions) { + t.Errorf("Expected %d actions, but got %d", len(tt.expectedActions), len(actions)) + } + for i, action := range actions { + if action.GetVerb() != tt.expectedActions[i] { + t.Errorf("Expected action %s, but got %s", tt.expectedActions[i], action.GetVerb()) + } + } + }) + } + } +} + +// waitingPermitPlugin is a PermitPlugin that always returns Wait. +type waitingPermitPlugin struct{} + +var _ fwk.PermitPlugin = &waitingPermitPlugin{} + +const waitingPermitPluginName = "waitingPermitPlugin" + +func newWaitingPermitPlugin(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &waitingPermitPlugin{}, nil +} + +func (pl *waitingPermitPlugin) Name() string { + return waitingPermitPluginName +} + +func (pl *waitingPermitPlugin) Permit(ctx context.Context, _ fwk.CycleState, _ *v1.Pod, nodeName string) (*fwk.Status, time.Duration) { + return fwk.NewStatus(fwk.Wait, ""), 10 * time.Second +} + +func TestIsPodGroupRunningPreemption(t *testing.T) { + var ( + victim1 = st.MakePod().Namespace("ns").Name("victim1").UID("victim1"). + Node("node").SchedulerName("sch").Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victim2 = st.MakePod().Namespace("ns").Name("victim2").UID("victim2"). + Node("node").SchedulerName("sch").Priority(midPriority). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + + victimWithDeletionTimestamp = st.MakePod().Namespace("ns").Name("victim-deleted").UID("victim-deleted"). + Node("node").SchedulerName("sch").Priority(midPriority). + Terminating(). + Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). + Obj() + ) + + tests := []struct { + name string + preemptorID types.UID + preemptingSet sets.Set[types.UID] + lastVictimSet map[types.UID]pendingVictim + podsInPodLister map[string]*v1.Pod + expectedResult bool + }{ + { + name: "preemptor not in preemptingSet", + preemptorID: "preemptor", + preemptingSet: sets.New[types.UID](), + lastVictimSet: map[types.UID]pendingVictim{}, + expectedResult: false, + }, + { + name: "preemptor not in preemptingSet, lastVictimSet not empty", + preemptorID: "preemptor", + preemptingSet: sets.New[types.UID](), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim1", + }, + }, + expectedResult: false, + }, + { + name: "preemptor in preemptingSet, no lastVictim for preemptor", + preemptorID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "otherPod": { + namespace: "ns", + name: "victim1", + }, + }, + expectedResult: true, + }, + { + name: "preemptor in preemptingSet, victim in lastVictimSet, not in PodLister", + preemptorID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim1", + }, + }, + podsInPodLister: map[string]*v1.Pod{}, + expectedResult: false, + }, + { + name: "preemptor in preemptingSet, victim in lastVictimSet and in PodLister", + preemptorID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim1", + }, + }, + podsInPodLister: map[string]*v1.Pod{ + "victim1": victim1, + "victim2": victim2, + }, + expectedResult: true, + }, + { + name: "preemptor in preemptingSet, victim in lastVictimSet and in PodLister with deletion timestamp", + preemptorID: "preemptor", + preemptingSet: sets.New[types.UID]("preemptor"), + lastVictimSet: map[types.UID]pendingVictim{ + "preemptor": { + namespace: "ns", + name: "victim-deleted", + }, + }, + podsInPodLister: map[string]*v1.Pod{ + "victim1": victim1, + "victim-deleted": victimWithDeletionTimestamp, + }, + expectedResult: false, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%v", tt.name), func(t *testing.T) { + + client := clientsetfake.NewSimpleClientset() + informerFactory := informers.NewSharedInformerFactory(client, 0) + for _, pod := range tt.podsInPodLister { + err := informerFactory.Core().V1().Pods().Informer().GetIndexer().Add(pod) + if err != nil { + t.Errorf("Failed to add pod %v to indexer: %v", pod, err) + } + } + a := &Executor{ + fh: &fakeHandleForLister{informerFactory: informerFactory}, + podLister: informerFactory.Core().V1().Pods().Lister(), + preempting: tt.preemptingSet, + lastVictimsPendingPreemption: tt.lastVictimSet, + } + + if result := a.IsPodGroupRunningPreemption(tt.preemptorID); tt.expectedResult != result { + t.Errorf("Expected IsPodGroupRunningPreemption to return %v but got %v", tt.expectedResult, result) + } + }) + } +} diff --git a/pkg/scheduler/framework/preemption/podgrouppreemption.go b/pkg/scheduler/framework/preemption/podgrouppreemption.go new file mode 100644 index 0000000000000..65d41a37a278c --- /dev/null +++ b/pkg/scheduler/framework/preemption/podgrouppreemption.go @@ -0,0 +1,289 @@ +/* +Copyright 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 preemption + +import ( + "context" + "fmt" + "sort" + "strings" + + v1 "k8s.io/api/core/v1" + policy "k8s.io/api/policy/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + policylisters "k8s.io/client-go/listers/policy/v1" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" + + "k8s.io/klog/v2" + extenderv1 "k8s.io/kube-scheduler/extender/v1" + fwk "k8s.io/kube-scheduler/framework" +) + +// PodGroupEvaluator is a preemption evaluator that knows how to run +// preemption where a preemptor is a pod group and the domain is the whole cluster. +type PodGroupEvaluator struct { + Handle fwk.Handle + pdbLister policylisters.PodDisruptionBudgetLister + podGroupLister schedulinglisters.PodGroupLister + + Executor *Executor +} + +// NewPodGroupEvaluator creates a new PodGroupEvaluator. +func NewPodGroupEvaluator(fh fwk.Handle, executor *Executor) *PodGroupEvaluator { + return &PodGroupEvaluator{ + Handle: fh, + pdbLister: fh.SharedInformerFactory().Policy().V1().PodDisruptionBudgets().Lister(), + podGroupLister: fh.SharedInformerFactory().Scheduling().V1alpha2().PodGroups().Lister(), + Executor: executor, + } +} + +// Preempt implements the preemption logic where the preemptor is a pod group +// and the domain is the whole cluster. It preempts pod from the cluster +// in order to make enough room for the pod group to be scheduled. +// It returns a status of the whole preemption process. +// The preemption logic modifies the NodeInfo provided by a Handle +// podGroupSchedulingFunc is a function that will be run to check feasibility of a pod group +// scheduling after modifying the node state. +// The caller is expected to backup the NodeInfo before calling this function +// And rollback the state to the backup after function is finished. +func (ev *PodGroupEvaluator) Preempt(ctx context.Context, pg *schedulingapi.PodGroup, pods []*v1.Pod, podGroupSchedulingFunc func(context.Context) *fwk.Status) *fwk.Status { + // In case of workload-aware preemption, the domain is whole cluster. + // We do not make a snapshot of node info. Those nodes will be shared + // with the PodGroup scheduling algorithm passed as podGroupSchedulingFunc. + allNodes, err := ev.Handle.SnapshotSharedLister().NodeInfos().List() + if err != nil { + return fwk.AsStatus(fmt.Errorf("failed to list node infos: %w", err)) + } + domain := newDomainForWorkloadPreemption(allNodes, ev.podGroupLister, "cluster-domain") + preemptor := newPodGroupPreemptor(pg, pods) + pdbs, err := getPodDisruptionBudgets(ev.pdbLister) + if err != nil { + return fwk.AsStatus(fmt.Errorf("failed to get pod disruption budgets: %w", err)) + } + + victims, status := ev.selectVictimsOnDomain(ctx, preemptor, domain, pdbs, podGroupSchedulingFunc) + if !status.IsSuccess() { + return status + } + return ev.Executor.actuatePodGroupPreemption(ctx, victims, preemptor.pods, preemptor.podGroup, names.DefaultPreemption) +} + +// selectVictimsOnDomain selects a set of victims that can be removed from the +// domain in order to make enough room for the preemptor to be scheduled. +// It prioritizes victims that are not protected by a PDB. +func (ev *PodGroupEvaluator) selectVictimsOnDomain( + ctx context.Context, + preemptor *podGroupPreemptor, + domain *domain, + pdbs []*policy.PodDisruptionBudget, + podGroupSchedulingFunc func(context.Context) *fwk.Status) (*extenderv1.Victims, *fwk.Status) { + logger := klog.FromContext(ctx) + + // Ensure the preemptor is eligible to preempt other pods. + if ok, msg := ev.preemptorEligibleToPreemptOthers(ctx, preemptor); !ok { + logger.V(5).Info("Preemptor is not eligible for preemption", "preemptor", klog.KObj(preemptor.podGroup), "reason", msg) + return nil, fwk.NewStatus(fwk.Unschedulable, msg) + } + + nameToNode := make(map[string]fwk.NodeInfo) + for _, nodeInfo := range domain.Nodes() { + nameToNode[nodeInfo.Node().Name] = nodeInfo + } + + // Compared to the default preemption algorithm do not run the runPreFilterExtensionRemovePod + // or runPreFilterExtensionAddPod as pod group scheduling does prefilter anyway. + removePods := func(v *victim) error { + for _, pi := range v.Pods() { + nodeInfo := nameToNode[pi.GetPod().Spec.NodeName] + if err := nodeInfo.RemovePod(logger, pi.GetPod()); err != nil { + return err + } + } + + return nil + } + addPods := func(v *victim) error { + for _, pi := range v.Pods() { + nodeInfo := nameToNode[pi.GetPod().Spec.NodeName] + nodeInfo.AddPodInfo(pi) + } + + return nil + } + + var potentialVictims []*victim + allPossiblyAffectedVictims := domain.GetAllPossibleVictims() + for _, victim := range allPossiblyAffectedVictims { + if ev.isPreemptionAllowed(victim, preemptor) { + potentialVictims = append(potentialVictims, victim) + } + } + + // No preemption victims found for incoming preemptor. + if len(potentialVictims) == 0 { + return nil, fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "No preemption victims found for incoming preemptor") + } + + // PodGroup being a victim can have pods spread between current domain and other domains + // We need to pull data about nodes from outside of the current domain + // if a pod group has a pod on it. + for _, victim := range potentialVictims { + for name, nodeInfo := range victim.AffectedNodes() { + _, ok := nameToNode[name] + if !ok { + nameToNode[name] = nodeInfo + } + } + + if err := removePods(victim); err != nil { + return nil, fwk.AsStatus(err) + } + } + + // If the scheduling failed after removing all potential victims, return the status. + if status := podGroupSchedulingFunc(ctx); !status.IsSuccess() { + return nil, status + } + + sort.Slice(potentialVictims, func(i, j int) bool { + return moreImportantVictim(potentialVictims[i], potentialVictims[j]) + }) + + violatingVictims, nonViolatingVictims := filterVictimsWithPDBViolation(potentialVictims, pdbs) + numViolatingVictim := 0 + + reprieveVictim := func(v *victim) (bool, error) { + if err := addPods(v); err != nil { + return false, err + } + + status := podGroupSchedulingFunc(ctx) + fits := status.IsSuccess() + if !fits { + if err := removePods(v); err != nil { + return false, err + } + var names []string + for _, p := range v.Pods() { + names = append(names, p.GetPod().Name) + } + pods := strings.Join(names, ",") + logger.V(6).Info("Pods are potential preemption victims on domain", "pods", pods, "domain", domain.GetName()) + } + + return fits, nil + } + + // Try to reprieve as many pods as possible. We first try to reprieve the PDB + // violating victims and then other non-violating ones. In both cases, we start + // from the highest importance victims. + var victimsToPreempt []*victim + for _, v := range violatingVictims { + if fits, err := reprieveVictim(v); err != nil { + return nil, fwk.AsStatus(err) + } else if !fits { + victimsToPreempt = append(victimsToPreempt, v) + numViolatingVictim++ + } + } + + for _, v := range nonViolatingVictims { + if fits, err := reprieveVictim(v); err != nil { + return nil, fwk.AsStatus(err) + } else if !fits { + victimsToPreempt = append(victimsToPreempt, v) + } + } + + sort.Slice(victimsToPreempt, func(i, j int) bool { + return moreImportantVictim(victimsToPreempt[i], victimsToPreempt[j]) + }) + var podsToPreempt []*v1.Pod + for _, v := range victimsToPreempt { + for _, pi := range v.Pods() { + podsToPreempt = append(podsToPreempt, pi.GetPod()) + } + } + + v := &extenderv1.Victims{ + Pods: podsToPreempt, + } + return v, nil +} + +// isPreemptionAllowed returns whether the victim residing on nodeInfo can be preempted by the preemptor +func (ev *PodGroupEvaluator) isPreemptionAllowed(victim *victim, preemptor *podGroupPreemptor) bool { + // The victim must have lower priority than the preemptor. + return victim.Priority() < preemptor.Priority() +} + +// preemptorEligibleToPreemptOthers returns one bool and one string. The bool +// indicates whether this preemptor should be considered for preempting other pods or +// not. The string includes the reason if this preemptor isn't eligible. +func (ev *PodGroupEvaluator) preemptorEligibleToPreemptOthers(_ context.Context, preemptor *podGroupPreemptor) (bool, string) { + if preemptor.PreemptionPolicy() == v1.PreemptNever { + return false, "not eligible due to preemptionPolicy=Never." + } + return true, "" +} + +// moreImportantVictim decides which of two preemption units is considered more critical. +// This function is dedidcated only for PodGroup preemption. +// +// The comparison logic follows this strict hierarchy: +// +// 1. Priority: Higher priority units are always more important. +// +// 2. Workload Type (if WorkloadAwarePreemption is enabled): +// Atomic workloads (PodGroups) are considered more important than individual Pods +// of the same priority. +// +// 3. Start Time (for Single Pods): +// If both units are single Pods, the one with the older StartTime is more important. +// This honors "first-come, first-served". +// +// 4. Group Size (for PodGroups): +// If both units are PodGroups, the one with more members (larger size) is considered +// more important. This avoids the high cost of rescheduling massive jobs. +// +// 5. Start Time (Tie-breaker for PodGroups): +// If sizes are equal, the group that started earlier (has the oldest pod) +// is more important. +func moreImportantVictim(vi1, vi2 *victim) bool { + if vi1.Priority() != vi2.Priority() { + return vi1.Priority() > vi2.Priority() + } + + if vi1.IsPodGroup() != vi2.IsPodGroup() { + return vi1.IsPodGroup() + } + + if !vi1.IsPodGroup() { + return vi1.EarliestStartTime().Before(vi2.EarliestStartTime()) + } + + if len(vi1.Pods()) != len(vi2.Pods()) { + return len(vi1.Pods()) > len(vi2.Pods()) + } + + t1 := vi1.EarliestStartTime() + t2 := vi2.EarliestStartTime() + return t1.Before(t2) +} diff --git a/pkg/scheduler/framework/preemption/podgrouppreemption_test.go b/pkg/scheduler/framework/preemption/podgrouppreemption_test.go new file mode 100644 index 0000000000000..700eb6cf304fa --- /dev/null +++ b/pkg/scheduler/framework/preemption/podgrouppreemption_test.go @@ -0,0 +1,819 @@ +/* +Copyright 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 preemption + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + policy "k8s.io/api/policy/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" + "k8s.io/klog/v2/ktesting" + fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework" + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +type mockPodGroupLister struct { + schedulinglisters.PodGroupLister + podGroups map[string]*schedulingapi.PodGroup +} + +func (m *mockPodGroupLister) PodGroups(namespace string) schedulinglisters.PodGroupNamespaceLister { + return &mockPodGroupNamespaceLister{podGroups: m.podGroups, namespace: namespace} +} + +type mockPodGroupNamespaceLister struct { + schedulinglisters.PodGroupNamespaceLister + podGroups map[string]*schedulingapi.PodGroup + namespace string +} + +func (m *mockPodGroupNamespaceLister) Get(name string) (*schedulingapi.PodGroup, error) { + if pg, ok := m.podGroups[name]; ok { + return pg, nil + } + return nil, fmt.Errorf("pod group %s not found", name) +} + +func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) { + // blockingRule mocks complex scheduling constraints for tests. + // It states: "Node 'nodeName' can host 'capacity' preempting pods, + // but ONLY IF the pods in 'blockingVictims' are removed first." + type blockingRule struct { + nodeName string + capacity int // Available slots for preemptor once victims are removed. + blockingVictims sets.Set[string] // Pods on the node preventing capacity usage. + } + + tests := []struct { + name string + nodeNames []string + initPods []*v1.Pod + initPodGroups []*schedulingapi.PodGroup + preemptor *podGroupPreemptor + pdbs []*policy.PodDisruptionBudget + blockingRules []blockingRule + expectedPods []string + expectedStatus *fwk.Status + }{ + { + name: "Mix of no-group and single-pod-groups", + nodeNames: []string{"node1", "node2", "node3"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + st.MakePodGroup().Name("pg2").UID("pg2").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")}, + {nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")}, + }, + expectedPods: []string{"p1"}, // p1 is less important than p2 because it's not part of a pod group + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Priority: Shared group vs no group", + nodeNames: []string{"node1", "node2", "node3"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).PodGroupName("pg1").StartTime(metav1.Unix(1, 0)).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").StartTime(metav1.Unix(0, 0)).Obj(), + st.MakePod().Name("p3").UID("v3").Node("node3").Priority(midPriority).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")}, + {nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")}, + }, + expectedPods: []string{"p1"}, // p1 is less important than p2 because of later StartTime + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Shared Group: Preempt separately", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).PodGroupName("pg1").StartTime(metav1.Unix(1, 0)).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").StartTime(metav1.Unix(0, 0)).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")}, + }, + expectedPods: []string{"p1"}, // p1 is less important than p2 + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Complex Mixed: Shared, different, and no groups", + nodeNames: []string{"node1", "node2", "node3", "node4", "node5"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).PodGroupName("pg1").StartTime(metav1.Unix(2, 0)).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").StartTime(metav1.Unix(1, 0)).Obj(), + st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").StartTime(metav1.Unix(0, 0)).Obj(), + st.MakePod().Name("p4").UID("v4").Node("node4").Priority(midPriority).Obj(), + st.MakePod().Name("p5").UID("v5").Node("node5").Priority(highPriority).PodGroupName("pg3").StartTime(metav1.Unix(0, 0)).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + st.MakePodGroup().Name("pg2").UID("pg2").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + st.MakePodGroup().Name("pg3").UID("pg3").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{ + st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).Obj(), + st.MakePod().Name("p-2").UID("p-2").Priority(highPriority).Obj(), + st.MakePod().Name("p-3").UID("p-3").Priority(highPriority).Obj(), + }, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")}, + {nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")}, + {nodeName: "node4", capacity: 1, blockingVictims: sets.New("p4")}, + {nodeName: "node5", capacity: 1, blockingVictims: sets.New("p5")}, + }, + expectedPods: []string{"p1", "p2", "p3"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PDB: Mixed groups", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("victim-pdb").UID("v1").Node("node1").Label("app", "foo").Priority(lowPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("victim-no-pdb").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(), + }, + pdbs: []*policy.PodDisruptionBudget{ + { + Spec: policy.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}}, + Status: policy.PodDisruptionBudgetStatus{DisruptionsAllowed: 0}, + }, + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("victim-pdb")}, + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("victim-no-pdb")}, + }, + expectedPods: []string{"victim-no-pdb"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PodGroup with PreemptNever does not perform preemption", + nodeNames: []string{"node1", "node2", "node3"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{ + st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).PreemptionPolicy(v1.PreemptNever).Obj(), + st.MakePod().Name("p-2").UID("p-2").Priority(highPriority).Obj(), + }, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")}, + {nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")}, + }, + expectedPods: []string{}, + expectedStatus: fwk.NewStatus(fwk.Unschedulable), + }, + { + name: "Preempt single lower priority pod", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("p1"), capacity: 1}, + }, + expectedPods: []string{"p1"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Priority: Prefer lower priority victim", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node1").Priority(midPriority).Obj(), + st.MakePod().Name("p3").UID("v3").Node("node1").Priority(highPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("p1"), capacity: 1}, + {nodeName: "node1", blockingVictims: sets.New("p2"), capacity: 1}, + {nodeName: "node1", blockingVictims: sets.New("p3"), capacity: 1}, + }, + expectedPods: []string{"p1"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Efficiency: Preempt minimum number of victims", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p3").UID("v3").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p4").UID("v4").Node("node1").Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("p1", "p2"), capacity: 1}, + {nodeName: "node1", blockingVictims: sets.New("p1"), capacity: 1}, + }, + expectedPods: []string{"p1"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PDB: Prefer non-violating victim", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("victim-pdb").UID("v1").Node("node1").Label("app", "foo").Priority(lowPriority).Obj(), + st.MakePod().Name("victim-no-pdb").UID("v2").Node("node1").Priority(lowPriority).Obj(), + }, + pdbs: []*policy.PodDisruptionBudget{ + { + Spec: policy.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}}, + Status: policy.PodDisruptionBudgetStatus{DisruptionsAllowed: 0}, + }, + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("victim-pdb"), capacity: 1}, + {nodeName: "node1", blockingVictims: sets.New("victim-no-pdb"), capacity: 1}, + }, + expectedPods: []string{"victim-no-pdb"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PodGroup with PreemptNever does not perform preemption", + nodeNames: []string{"node1", "node2", "node3"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Priority(lowPriority).Obj(), + st.MakePodGroup().Name("pg2").UID("pg2").DisruptionMode(schedulingapi.DisruptionModePod).Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{ + st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).PreemptionPolicy(v1.PreemptNever).Obj(), + st.MakePod().Name("p-2").UID("p-2").Priority(highPriority).Obj(), + }, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + {nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")}, + {nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")}, + }, + expectedPods: []string{}, + expectedStatus: fwk.NewStatus(fwk.Unschedulable), + }, + { + name: "PDB: Prefer lower priority pod for preemption, when preemption without pdb violation is not possible", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Label("app", "foo").Priority(lowPriority).Obj(), + st.MakePod().Name("p2").UID("v2").Node("node1").Label("app", "foo").Priority(midPriority).Obj(), + }, + pdbs: []*policy.PodDisruptionBudget{ + { + Spec: policy.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}}, + Status: policy.PodDisruptionBudgetStatus{DisruptionsAllowed: 0}, + }, + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("p1"), capacity: 1}, + {nodeName: "node1", blockingVictims: sets.New("p2"), capacity: 1}, + }, + expectedPods: []string{"p1"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PodGroup: Preempt group as a whole", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{"p1", "p2"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PodGroup: Prefer single pod over podGroup for preemption candidate", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("g1-1").UID("g1").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + st.MakePod().Name("g1-2").UID("g2").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("g1-1", "g1-2"), capacity: 1}, + {nodeName: "node1", blockingVictims: sets.New("p1"), capacity: 1}, + }, + expectedPods: []string{"p1"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PodGroup: Preempt group as a whole on single node", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("g1-1").UID("g1").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + st.MakePod().Name("g1-2").UID("g2").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + st.MakePod().Name("p1").UID("p1").Node("node1").Priority(midPriority).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("g1-1")}, // Only g1-1 is blocking + }, + expectedPods: []string{"g1-1", "g1-2"}, // Both must be preempted + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PDB: Unit violation if any member violates", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("g1-1").UID("g1").Node("node1").Label("app", "foo").PodGroupName("pg1").Priority(lowPriority).Obj(), + st.MakePod().Name("g1-2").UID("g2").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + st.MakePod().Name("p1").UID("p1").Node("node1").Priority(lowPriority).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(lowPriority).Obj(), + }, + pdbs: []*policy.PodDisruptionBudget{ + { + Spec: policy.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}}, + Status: policy.PodDisruptionBudgetStatus{DisruptionsAllowed: 0}, + }, + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("g1-1")}, // Only g1-1 is blocking + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, // p1 is also blocking + }, + expectedPods: []string{"p1"}, // p1 is preferred because pg1 unit-violates PDB (via g1-1) + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "PodGroup: Prefer preempting single pod over group of same priority", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Node("node1").Priority(lowPriority).Obj(), + st.MakePod().Name("g1-1").UID("g1").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + st.MakePod().Name("g1-2").UID("g2").Node("node1").PodGroupName("pg1").Priority(lowPriority).Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(lowPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("g1-1", "g1-2")}, + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{"p1"}, // p1 is preempted because the PodGroup is "more important" at the same priority level + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Failure: Cannot preempt the victim with higher priority", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(highPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(midPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(midPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable), + }, + { + name: "Failure: Cannot preempt if node is empty", + nodeNames: []string{"node1"}, + initPods: []*v1.Pod{}, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(midPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(midPriority).Obj()}, + ), + blockingRules: []blockingRule{}, + expectedPods: []string{}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable), + }, + { + name: "Priority divergence: candidate victim PodGroup has lower priority than the Pods from that group", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(highPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(highPriority).PodGroupName("pg1").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(midPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{"p1", "p2"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Priority divergence: candidate victim PodGroup has higher priority than the Pods from that group", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(midPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(midPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(midPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable), + }, + { + name: "Priority divergence: preemptor PodGroup has higher priority than the Pod from preemptor PodGroup", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(midPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(midPriority).PodGroupName("pg1").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(midPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(lowPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{"p1", "p2"}, + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "Priority divergence: preemptor PodGroup has lower priority than the Pod from preemptor PodGroup", + nodeNames: []string{"node1", "node2"}, + initPods: []*v1.Pod{ + st.MakePod().Name("p1").UID("v1").Node("node1").Priority(midPriority).PodGroupName("pg1").Obj(), + st.MakePod().Name("p2").UID("v2").Node("node2").Priority(midPriority).PodGroupName("pg1").Obj(), + }, + initPodGroups: []*schedulingapi.PodGroup{ + st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(midPriority).Obj(), + }, + preemptor: newPodGroupPreemptor( + st.MakePodGroup().Name("preemptor-pg").Priority(lowPriority).Obj(), + []*v1.Pod{st.MakePod().Name("p").UID("p").Priority(highPriority).Obj()}, + ), + blockingRules: []blockingRule{ + {nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")}, + }, + expectedPods: []string{}, + expectedStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + nodes := make([]*v1.Node, len(tt.nodeNames)) + for i, nodeName := range tt.nodeNames { + nodes[i] = st.MakeNode().Name(nodeName).Obj() + } + + // Build nodes with pods + nodeInfos := make(map[string]fwk.NodeInfo) + for _, node := range nodes { + nodeInfos[node.Name] = framework.NewNodeInfo() + nodeInfos[node.Name].SetNode(node) + } + for _, p := range tt.initPods { + podInfo, _ := framework.NewPodInfo(p) + nodeInfos[p.Spec.NodeName].AddPodInfo(podInfo) + } + + var domainNodes []fwk.NodeInfo + for _, name := range tt.nodeNames { + domainNodes = append(domainNodes, nodeInfos[name]) + } + + podGroups := make(map[string]*schedulingapi.PodGroup) + for _, pg := range tt.initPodGroups { + podGroups[pg.Name] = pg + } + pgLister := &mockPodGroupLister{podGroups: podGroups} + domain := newDomainForWorkloadPreemption(domainNodes, pgLister, "test-domain") + + // Create a mock podGroupSchedulingFunc. + // This simulates whether the preempting PodGroup can schedule given the current + // hypothetical state of the cluster (where some candidate victims might be removed). + mockSchedulingFunc := func(ctx context.Context) *fwk.Status { + neededSlots := len(tt.preemptor.Members()) + availableSlots := 0 + nodeMap := make(map[string]fwk.NodeInfo) + for _, n := range domainNodes { + nodeMap[n.Node().Name] = n + } + + for _, rule := range tt.blockingRules { + node, exists := nodeMap[rule.nodeName] + if !exists { + continue + } + + isBlocked := false + for _, pod := range node.GetPods() { + // If any of the blocking victims are still on the simulated node, it provides 0 capacity. + if rule.blockingVictims.Has(pod.GetPod().Name) { + isBlocked = true + break + } + } + + // If all blocking victims are removed, the node provides its capacity. + if !isBlocked { + availableSlots += rule.capacity + } + } + + if availableSlots >= neededSlots { + return fwk.NewStatus(fwk.Success) + } + return fwk.NewStatus(fwk.Unschedulable) + } + + pl := &PodGroupEvaluator{} + + victims, gotStatus := pl.selectVictimsOnDomain(ctx, tt.preemptor, domain, tt.pdbs, mockSchedulingFunc) + if !gotStatus.IsSuccess() { + t.Logf("SelectVictimsOnDomain failed: %v", gotStatus.Message()) + } + + wantCode := tt.expectedStatus.Code() + gotCode := gotStatus.Code() + if gotCode != wantCode { + t.Errorf("Status mismatch. Want %v, Got %v", wantCode, gotCode) + } + if wantCode != fwk.Success { + return + } + if victims == nil { + t.Fatalf("expected non-nil victims on success") + } + + gotNames := sets.Set[string]{} + for _, p := range victims.Pods { + gotNames.Insert(p.Name) + } + wantNames := sets.New(tt.expectedPods...) + if diff := cmp.Diff(wantNames, gotNames); diff != "" { + t.Errorf("Victims mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestMoreImportantVictim(t *testing.T) { + newPodInfo := func(p *v1.Pod) fwk.PodInfo { + pi, _ := framework.NewPodInfo(p) + return pi + } + + now := &metav1.Time{Time: time.Unix(1000, 0)} + before := &metav1.Time{Time: time.Unix(500, 0)} + + tests := []struct { + name string + vi1 *victim + vi2 *victim + want bool + }{ + { + name: "vi1 has higher priority", + vi1: &victim{priority: 20}, + vi2: &victim{priority: 10}, + want: true, + }, + { + name: "vi2 has higher priority", + vi1: &victim{priority: 10}, + vi2: &victim{priority: 20}, + want: false, + }, + { + name: "vi1 is PG, vi2 is Pod, same priority", + vi1: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().PodGroupName("pg").Obj())}}, + vi2: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().Obj())}}, + want: true, + }, + { + name: "vi1 is Pod, vi2 is PG, same priority", + vi1: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().Obj())}}, + vi2: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().PodGroupName("pg").Obj())}}, + want: false, + }, + { + name: "both Pods, vi1 older", + vi1: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().Obj())}, earliestStartTime: before}, + vi2: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().Obj())}, earliestStartTime: now}, + want: true, + }, + { + name: "both Pods, vi2 older", + vi1: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().Obj())}, earliestStartTime: now}, + vi2: &victim{priority: 10, pods: []fwk.PodInfo{newPodInfo(st.MakePod().Obj())}, earliestStartTime: before}, + want: false, + }, + { + name: "both PGs, vi1 larger", + vi1: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + }, + vi2: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + }, + want: true, + }, + { + name: "both PGs, vi2 larger", + vi1: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + }, + vi2: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + }, + want: false, + }, + { + name: "both PGs, same size, vi1 older", + vi1: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + earliestStartTime: before, + }, + vi2: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + earliestStartTime: now, + }, + want: true, + }, + { + name: "both PGs, same size, vi2 older", + vi1: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + earliestStartTime: now, + }, + vi2: &victim{ + priority: 10, + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + newPodInfo(st.MakePod().PodGroupName("pg").Obj()), + }, + earliestStartTime: before, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := moreImportantVictim(tt.vi1, tt.vi2) + if got != tt.want { + t.Errorf("MoreImportantVictim() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/scheduler/framework/preemption/preemption.go b/pkg/scheduler/framework/preemption/preemption.go index 05918dd778ad1..50106f53a2ec4 100644 --- a/pkg/scheduler/framework/preemption/preemption.go +++ b/pkg/scheduler/framework/preemption/preemption.go @@ -22,89 +22,23 @@ import ( "fmt" "math" "sync" - "sync/atomic" - "time" v1 "k8s.io/api/core/v1" policy "k8s.io/api/policy/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" corelisters "k8s.io/client-go/listers/core/v1" policylisters "k8s.io/client-go/listers/policy/v1" corev1helpers "k8s.io/component-helpers/scheduling/corev1" "k8s.io/klog/v2" extenderv1 "k8s.io/kube-scheduler/extender/v1" fwk "k8s.io/kube-scheduler/framework" - apipod "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/scheduler/framework" - "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" - "k8s.io/kubernetes/pkg/scheduler/metrics" "k8s.io/kubernetes/pkg/scheduler/util" ) -// Candidate represents a nominated node on which the preemptor can be scheduled, -// along with the list of victims that should be evicted for the preemptor to fit the node. -type Candidate interface { - // Victims wraps a list of to-be-preempted Pods and the number of PDB violation. - Victims() *extenderv1.Victims - // Name returns the target node name where the preemptor gets nominated to run. - Name() string -} - -type candidate struct { - victims *extenderv1.Victims - name string -} - -// Victims returns s.victims. -func (s *candidate) Victims() *extenderv1.Victims { - return s.victims -} - -// Name returns s.name. -func (s *candidate) Name() string { - return s.name -} - -type candidateList struct { - idx int32 - items []Candidate -} - -func newCandidateList(size int32) *candidateList { - return &candidateList{idx: -1, items: make([]Candidate, size)} -} - -// add adds a new candidate to the internal array atomically. -func (cl *candidateList) add(c *candidate) { - if idx := atomic.AddInt32(&cl.idx, 1); idx < int32(len(cl.items)) { - cl.items[idx] = c - } -} - -// size returns the number of candidate stored. Note that some add() operations -// might still be executing when this is called, so care must be taken to -// ensure that all add() operations complete before accessing the elements of -// the list. -func (cl *candidateList) size() int32 { - n := atomic.LoadInt32(&cl.idx) + 1 - if n >= int32(len(cl.items)) { - n = int32(len(cl.items)) - } - return n -} - -// get returns the internal candidate array. This function is NOT atomic and -// assumes that all add() operations have been completed. -func (cl *candidateList) get() []Candidate { - return cl.items[:cl.size()] -} - // Interface is expected to be implemented by different preemption plugins as all those member // methods might have different behavior compared with the default preemption. type Interface interface { @@ -127,126 +61,27 @@ type Interface interface { OrderedScoreFuncs(ctx context.Context, nodesToVictims map[string]*extenderv1.Victims) []func(node string) int64 } +// Evaluator is a preemption evaluator. It runs preemption logic with a given Interface. type Evaluator struct { PluginName string Handler fwk.Handle PodLister corelisters.PodLister PdbLister policylisters.PodDisruptionBudgetLister - enableAsyncPreemption bool - mu sync.RWMutex - // preempting is a set that records the pods that are currently triggering preemption asynchronously, - // which is used to prevent the pods from entering the scheduling cycle meanwhile. - preempting sets.Set[types.UID] - // lastVictimsPendingPreemption is a map that records the victim pods that are currently being preempted for a given preemptor pod, - // with a condition that the preemptor is waiting for one last victim to be preempted. This is used together with `preempting` - // to prevent the pods from entering the scheduling cycle while waiting for preemption to complete. - lastVictimsPendingPreemption map[types.UID]pendingVictim - - // PreemptPod is a function that actually makes API calls to preempt a specific Pod. - // This is exposed to be replaced during tests. - PreemptPod func(ctx context.Context, c Candidate, preemptor, victim *v1.Pod, pluginName string) error - Interface + executor *Executor } -type pendingVictim struct { - namespace string - name string -} - -func NewEvaluator(pluginName string, fh fwk.Handle, i Interface, enableAsyncPreemption bool) *Evaluator { - podLister := fh.SharedInformerFactory().Core().V1().Pods().Lister() - pdbLister := fh.SharedInformerFactory().Policy().V1().PodDisruptionBudgets().Lister() - - ev := &Evaluator{ - PluginName: pluginName, - Handler: fh, - PodLister: podLister, - PdbLister: pdbLister, - Interface: i, - enableAsyncPreemption: enableAsyncPreemption, - preempting: sets.New[types.UID](), - lastVictimsPendingPreemption: make(map[types.UID]pendingVictim), +// NewEvaluator creates a new Evaluator. +func NewEvaluator(pluginName string, fh fwk.Handle, i Interface, executor *Executor) *Evaluator { + return &Evaluator{ + PluginName: pluginName, + Handler: fh, + PodLister: fh.SharedInformerFactory().Core().V1().Pods().Lister(), + PdbLister: fh.SharedInformerFactory().Policy().V1().PodDisruptionBudgets().Lister(), + Interface: i, + executor: executor, } - - // PreemptPod actually makes API calls to preempt a specific Pod. - // - // We implement it here directly, rather than creating a separate method like ev.preemptPod(...) - // to prevent the misuse of the PreemptPod function. - ev.PreemptPod = func(ctx context.Context, c Candidate, preemptor, victim *v1.Pod, pluginName string) error { - logger := klog.FromContext(ctx) - - // If the victim is a WaitingPod, send a reject message to the PermitPlugin. - // Otherwise we should delete the victim. - if waitingPod := ev.Handler.GetWaitingPod(victim.UID); waitingPod != nil { - waitingPod.Reject(pluginName, "preempted") - logger.V(2).Info("Preemptor pod rejected a waiting pod", "preemptor", klog.KObj(preemptor), "waitingPod", klog.KObj(victim), "node", c.Name()) - } else { - condition := &v1.PodCondition{ - Type: v1.DisruptionTarget, - ObservedGeneration: apipod.CalculatePodConditionObservedGeneration(&victim.Status, victim.Generation, v1.DisruptionTarget), - Status: v1.ConditionTrue, - Reason: v1.PodReasonPreemptionByScheduler, - Message: fmt.Sprintf("%s: preempting to accommodate a higher priority pod", preemptor.Spec.SchedulerName), - } - newStatus := victim.Status.DeepCopy() - updated := apipod.UpdatePodCondition(newStatus, condition) - if updated { - if err := util.PatchPodStatus(ctx, ev.Handler.ClientSet(), victim.Name, victim.Namespace, &victim.Status, newStatus); err != nil { - if !apierrors.IsNotFound(err) { - logger.Error(err, "Could not add DisruptionTarget condition due to preemption", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim)) - return err - } - logger.V(2).Info("Victim Pod is already deleted", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) - return nil - } - } - if err := util.DeletePod(ctx, ev.Handler.ClientSet(), victim); err != nil { - if !apierrors.IsNotFound(err) { - logger.Error(err, "Tried to preempted pod", "pod", klog.KObj(victim), "preemptor", klog.KObj(preemptor)) - return err - } - logger.V(2).Info("Victim Pod is already deleted", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) - return nil - } - logger.V(2).Info("Preemptor Pod preempted victim Pod", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) - } - - ev.Handler.EventRecorder().Eventf(victim, preemptor, v1.EventTypeNormal, "Preempted", "Preempting", "Preempted by pod %v on node %v", preemptor.UID, c.Name()) - - return nil - } - - return ev -} - -// IsPodRunningPreemption returns true if the pod is currently triggering preemption asynchronously. -func (ev *Evaluator) IsPodRunningPreemption(podUID types.UID) bool { - ev.mu.RLock() - defer ev.mu.RUnlock() - - if !ev.preempting.Has(podUID) { - return false - } - - victim, ok := ev.lastVictimsPendingPreemption[podUID] - if !ok { - // Since pod is in `preempting` but last victim is not registered yet, the async preemption is pending. - return true - } - // Pod is waiting for preemption of one last victim. We can check if the victim has already been deleted. - victimPod, err := ev.PodLister.Pods(victim.namespace).Get(victim.name) - if err != nil { - // Victim already deleted, preemption is done. - return false - } - if victimPod.DeletionTimestamp != nil { - // Victim deletion has started, preemption is done. - return false - } - // Preemption of the last pod is still ongoing. - return true } // Preempt returns a PostFilterResult carrying suggested nominatedNodeName, along with a Status. @@ -326,13 +161,9 @@ func (ev *Evaluator) Preempt(ctx context.Context, state fwk.CycleState, pod *v1. logger.V(2).Info("the target node for the preemption is determined", "node", bestCandidate.Name(), "pod", klog.KObj(pod)) - // 5) Perform preparation work before nominating the selected candidate. - if ev.enableAsyncPreemption { - ev.prepareCandidateAsync(bestCandidate, pod, ev.PluginName) - } else { - if status := ev.prepareCandidate(ctx, bestCandidate, pod, ev.PluginName); !status.IsSuccess() { - return nil, status - } + // 5) Actuate the preemption. + if status := ev.executor.actuatePodPreemption(ctx, bestCandidate.Name(), bestCandidate.Victims(), pod, ev.PluginName); !status.IsSuccess() { + return nil, status } return framework.NewPostFilterResultWithNominatedNode(bestCandidate.Name()), fwk.NewStatus(fwk.Success) @@ -457,177 +288,6 @@ func (ev *Evaluator) SelectCandidate(ctx context.Context, candidates []Candidate return candidates[0] } -// prepareCandidate does some preparation work before nominating the selected candidate: -// - Evict the victim pods -// - Reject the victim pods if they are in waitingPod map -// - Clear the low-priority pods' nominatedNodeName status if needed -func (ev *Evaluator) prepareCandidate(ctx context.Context, c Candidate, pod *v1.Pod, pluginName string) *fwk.Status { - fh := ev.Handler - cs := ev.Handler.ClientSet() - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - logger := klog.FromContext(ctx) - errCh := parallelize.NewErrorChannel() - fh.Parallelizer().Until(ctx, len(c.Victims().Pods), func(index int) { - victim := c.Victims().Pods[index] - if victim.DeletionTimestamp != nil { - // Graceful pod deletion has already started. Sending another API call is unnecessary. - logger.V(2).Info("Victim Pod is already being deleted, skipping the API call for it", "preemptor", klog.KObj(pod), "node", c.Name(), "victim", klog.KObj(victim)) - return - } - if err := ev.PreemptPod(ctx, c, pod, victim, pluginName); err != nil { - errCh.SendErrorWithCancel(err, cancel) - } - }, ev.PluginName) - if err := errCh.ReceiveError(); err != nil { - return fwk.AsStatus(err) - } - - metrics.PreemptionVictims.Observe(float64(len(c.Victims().Pods))) - - // Lower priority pods nominated to run on this node, may no longer fit on - // this node. So, we should remove their nomination. Removing their - // nomination updates these pods and moves them to the active queue. It - // lets scheduler find another place for them sooner than after waiting for preemption completion. - nominatedPods := getLowerPriorityNominatedPods(logger, fh, pod, c.Name()) - if err := clearNominatedNodeName(ctx, cs, ev.Handler.APICacher(), nominatedPods...); err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Cannot clear 'NominatedNodeName' field") - // We do not return as this error is not critical. - } - - return nil -} - -// clearNominatedNodeName internally submit a patch request to API server -// to set each pods[*].Status.NominatedNodeName> to "". -func clearNominatedNodeName(ctx context.Context, cs clientset.Interface, apiCacher fwk.APICacher, pods ...*v1.Pod) utilerrors.Aggregate { - var errs []error - for _, p := range pods { - if apiCacher != nil { - // When API cacher is available, use it to clear the NominatedNodeName. - _, err := apiCacher.PatchPodStatus(p, nil, &fwk.NominatingInfo{NominatedNodeName: "", NominatingMode: fwk.ModeOverride}) - if err != nil { - errs = append(errs, err) - } - } else { - if len(p.Status.NominatedNodeName) == 0 { - continue - } - podStatusCopy := p.Status.DeepCopy() - podStatusCopy.NominatedNodeName = "" - if err := util.PatchPodStatus(ctx, cs, p.Name, p.Namespace, &p.Status, podStatusCopy); err != nil { - errs = append(errs, err) - } - } - } - return utilerrors.NewAggregate(errs) -} - -// prepareCandidateAsync triggers a goroutine for some preparation work: -// - Evict the victim pods -// - Reject the victim pods if they are in waitingPod map -// - Clear the low-priority pods' nominatedNodeName status if needed -// The Pod won't be retried until the goroutine triggered here completes. -// -// See http://kep.k8s.io/4832 for how the async preemption works. -func (ev *Evaluator) prepareCandidateAsync(c Candidate, pod *v1.Pod, pluginName string) { - metrics.PreemptionVictims.Observe(float64(len(c.Victims().Pods))) - - // Intentionally create a new context, not using a ctx from the scheduling cycle, to create ctx, - // because this process could continue even after this scheduling cycle finishes. - ctx, cancel := context.WithCancel(context.Background()) - logger := klog.FromContext(ctx) - - victimPods := make([]*v1.Pod, 0, len(c.Victims().Pods)) - for _, victim := range c.Victims().Pods { - if victim.DeletionTimestamp != nil { - // Graceful pod deletion has already started. Sending another API call is unnecessary. - logger.V(2).Info("Victim Pod is already being deleted, skipping the API call for it", "preemptor", klog.KObj(pod), "node", c.Name(), "victim", klog.KObj(victim)) - continue - } - victimPods = append(victimPods, victim) - } - if len(victimPods) == 0 { - cancel() - return - } - - errCh := parallelize.NewErrorChannel() - preemptPod := func(index int) { - victim := victimPods[index] - if err := ev.PreemptPod(ctx, c, pod, victim, pluginName); err != nil { - errCh.SendErrorWithCancel(err, cancel) - } - } - - ev.mu.Lock() - ev.preempting.Insert(pod.UID) - ev.mu.Unlock() - - go func() { - logger := klog.FromContext(ctx) - startTime := time.Now() - result := metrics.GoroutineResultSuccess - defer metrics.PreemptionGoroutinesDuration.WithLabelValues(result).Observe(metrics.SinceInSeconds(startTime)) - defer metrics.PreemptionGoroutinesExecutionTotal.WithLabelValues(result).Inc() - defer func() { - if result == metrics.GoroutineResultError { - // When API call isn't successful, the Pod may get stuck in the unschedulable pod pool in the worst case. - // So, we should move the Pod to the activeQ. - ev.Handler.Activate(logger, map[string]*v1.Pod{pod.Name: pod}) - } - }() - defer cancel() - logger.V(2).Info("Start the preemption asynchronously", "preemptor", klog.KObj(pod), "node", c.Name(), "numVictims", len(c.Victims().Pods), "numVictimsToDelete", len(victimPods)) - - // Lower priority pods nominated to run on this node, may no longer fit on - // this node. So, we should remove their nomination. Removing their - // nomination updates these pods and moves them to the active queue. It - // lets scheduler find another place for them sooner than after waiting for preemption completion. - nominatedPods := getLowerPriorityNominatedPods(logger, ev.Handler, pod, c.Name()) - if err := clearNominatedNodeName(ctx, ev.Handler.ClientSet(), ev.Handler.APICacher(), nominatedPods...); err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Cannot clear 'NominatedNodeName' field from lower priority pods on the same target node", "node", c.Name()) - result = metrics.GoroutineResultError - // We do not return as this error is not critical. - } - - if len(victimPods) > 1 { - // We can evict all victims in parallel, but the last one. - // We have to remove the pod from the preempting map before the last one is evicted - // because, otherwise, the pod removal might be notified to the scheduling queue before - // we remove this pod from the preempting map, - // and the pod could end up stucking at the unschedulable pod pool - // by all the pod removal events being ignored. - // In order to prevent requesting preemption of the same pod multiple times for the same preemptor, - // preemptor is marked as "waiting for preemption of a victim" (by adding it to preempting map). - // For optimization purposes the last victim is recorded in lastVictimsPendingPreemption. - ev.Handler.Parallelizer().Until(ctx, len(victimPods)-1, preemptPod, ev.PluginName) - if err := errCh.ReceiveError(); err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption") - result = metrics.GoroutineResultError - } - } - - lastVictim := victimPods[len(victimPods)-1] - ev.mu.Lock() - ev.lastVictimsPendingPreemption[pod.UID] = pendingVictim{namespace: lastVictim.Namespace, name: lastVictim.Name} - ev.mu.Unlock() - - if err := ev.PreemptPod(ctx, c, pod, lastVictim, pluginName); err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption") - result = metrics.GoroutineResultError - } - - ev.mu.Lock() - ev.preempting.Delete(pod.UID) - delete(ev.lastVictimsPendingPreemption, pod.UID) - ev.mu.Unlock() - - logger.V(2).Info("Async Preemption finished completely", "preemptor", klog.KObj(pod), "node", c.Name(), "result", result) - }() -} - func getPodDisruptionBudgets(pdbLister policylisters.PodDisruptionBudgetLister) ([]*policy.PodDisruptionBudget, error) { if pdbLister != nil { return pdbLister.List(labels.Everything()) @@ -736,30 +396,6 @@ func pickOneNodeForPreemption(logger klog.Logger, nodesToVictims map[string]*ext return allCandidates[0] } -// getLowerPriorityNominatedPods returns pods whose priority is smaller than the -// priority of the given "pod" and are nominated to run on the given node. -// Note: We could possibly check if the nominated lower priority pods still fit -// and return those that no longer fit, but that would require lots of -// manipulation of NodeInfo and PreFilter state per nominated pod. It may not be -// worth the complexity, especially because we generally expect to have a very -// small number of nominated pods per node. -func getLowerPriorityNominatedPods(logger klog.Logger, pn fwk.PodNominator, pod *v1.Pod, nodeName string) []*v1.Pod { - podInfos := pn.NominatedPodsForNode(nodeName) - - if len(podInfos) == 0 { - return nil - } - - var lowerPriorityPods []*v1.Pod - podPriority := corev1helpers.PodPriority(pod) - for _, pi := range podInfos { - if corev1helpers.PodPriority(pi.GetPod()) < podPriority { - lowerPriorityPods = append(lowerPriorityPods, pi.GetPod()) - } - } - return lowerPriorityPods -} - // DryRunPreemption simulates Preemption logic on in parallel, // returns preemption candidates and a map indicating filtered nodes statuses. // The number of candidates depends on the constraints defined in the plugin's args. In the returned list of @@ -819,3 +455,64 @@ func (ev *Evaluator) DryRunPreemption(ctx context.Context, state fwk.CycleState, fh.Parallelizer().Until(ctx, len(potentialNodes), checkNode, ev.PluginName) return append(nonViolatingCandidates.get(), violatingCandidates.get()...), nodeStatuses, utilerrors.NewAggregate(errs) } + +func filterVictimsWithPDBViolation(victims []*victim, pdbs []*policy.PodDisruptionBudget) (violatingVictims, nonViolatingVictims []*victim) { + pdbsAllowed := make([]int32, len(pdbs)) + podIsViolating := func(pod *v1.Pod) bool { + if len(pod.Labels) == 0 { + return false + } + + for i, pdb := range pdbs { + if pdb.Namespace != pod.Namespace { + continue + } + selector, err := metav1.LabelSelectorAsSelector(pdb.Spec.Selector) + if err != nil { + // This object has an invalid selector, it does not match the pod + continue + } + // A PDB with a nil or empty selector matches nothing. + if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { + continue + } + + // Existing in DisruptedPods means it has been processed in API server, + // we don't treat it as a violating case. + if _, exist := pdb.Status.DisruptedPods[pod.Name]; exist { + continue + } + // Only decrement the matched pdb when it's not in its ; + // otherwise we may over-decrement the budget number. + pdbsAllowed[i]-- + // We have found a matching PDB. + if pdbsAllowed[i] < 0 { + return true + } + } + + return false + } + + for i, pdb := range pdbs { + pdbsAllowed[i] = pdb.Status.DisruptionsAllowed + } + + for _, victim := range victims { + isUnitViolating := false + + for _, pi := range victim.Pods() { + if podIsViolating(pi.GetPod()) { + isUnitViolating = true + break + } + } + if isUnitViolating { + violatingVictims = append(violatingVictims, victim) + } else { + nonViolatingVictims = append(nonViolatingVictims, victim) + } + } + + return violatingVictims, nonViolatingVictims +} diff --git a/pkg/scheduler/framework/preemption/preemption_test.go b/pkg/scheduler/framework/preemption/preemption_test.go index 11c6ae73ab82c..abb5b48555b03 100644 --- a/pkg/scheduler/framework/preemption/preemption_test.go +++ b/pkg/scheduler/framework/preemption/preemption_test.go @@ -21,8 +21,6 @@ import ( "errors" "fmt" "sort" - "strings" - "sync" "testing" "time" @@ -30,19 +28,10 @@ import ( v1 "k8s.io/api/core/v1" policy "k8s.io/api/policy/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/informers" clientsetfake "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/kubernetes/scheme" - corelisters "k8s.io/client-go/listers/core/v1" - clienttesting "k8s.io/client-go/testing" - "k8s.io/client-go/tools/events" - "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" extenderv1 "k8s.io/kube-scheduler/extender/v1" fwk "k8s.io/kube-scheduler/framework" @@ -62,7 +51,7 @@ import ( ) var ( - midPriority, highPriority = int32(100), int32(1000) + lowPriority, midPriority, highPriority = int32(10), int32(100), int32(1000) veryLargeRes = map[v1.ResourceName]string{ v1.ResourceCPU: "500m", @@ -100,19 +89,6 @@ func (pl *FakePostFilterPlugin) OrderedScoreFuncs(ctx context.Context, nodesToVi return nil } -type fakePodActivator struct { - activatedPods map[string]*v1.Pod - mu *sync.RWMutex -} - -func (f *fakePodActivator) Activate(logger klog.Logger, pods map[string]*v1.Pod) { - f.mu.Lock() - defer f.mu.Unlock() - for name, pod := range pods { - f.activatedPods[name] = pod - } -} - type FakePreemptionScorePostFilterPlugin struct{} func (pl *FakePreemptionScorePostFilterPlugin) SelectVictimsOnNode( @@ -389,508 +365,6 @@ func TestSelectCandidate(t *testing.T) { } } -type fakeCandidate struct { - victims *extenderv1.Victims - name string -} - -// Victims returns s.victims. -func (s *fakeCandidate) Victims() *extenderv1.Victims { - return s.victims -} - -// Name returns s.name. -func (s *fakeCandidate) Name() string { - return s.name -} - -func TestPrepareCandidate(t *testing.T) { - var ( - node1Name = "node1" - defaultSchedulerName = "default-scheduler" - ) - condition := v1.PodCondition{ - Type: v1.DisruptionTarget, - Status: v1.ConditionTrue, - Reason: v1.PodReasonPreemptionByScheduler, - Message: fmt.Sprintf("%s: preempting to accommodate a higher priority pod", defaultSchedulerName), - } - - var ( - victim1 = st.MakePod().Name("victim1").UID("victim1"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - notFoundVictim1 = st.MakePod().Name("not-found-victim").UID("victim1"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - failVictim = st.MakePod().Name("fail-victim").UID("victim1"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - victim2 = st.MakePod().Name("victim2").UID("victim2"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(50000). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - victim1WithMatchingCondition = st.MakePod().Name("victim1").UID("victim1"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Conditions([]v1.PodCondition{condition}). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - failVictim1WithMatchingCondition = st.MakePod().Name("fail-victim").UID("victim1"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Conditions([]v1.PodCondition{condition}). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - preemptor = st.MakePod().Name("preemptor").UID("preemptor"). - SchedulerName(defaultSchedulerName).Priority(highPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - errDeletePodFailed = errors.New("delete pod failed") - errPatchStatusFailed = errors.New("patch pod status failed") - ) - - victimWithDeletionTimestamp := victim1.DeepCopy() - victimWithDeletionTimestamp.Name = "victim1-with-deletion-timestamp" - victimWithDeletionTimestamp.UID = "victim1-with-deletion-timestamp" - victimWithDeletionTimestamp.DeletionTimestamp = &metav1.Time{Time: time.Now().Add(-100 * time.Second)} - victimWithDeletionTimestamp.Finalizers = []string{"test"} - - tests := []struct { - name string - nodeNames []string - candidate *fakeCandidate - preemptor *v1.Pod - testPods []*v1.Pod - // expectedDeletedPod is the pod name that is expected to be deleted. - // - // You can set multiple pod name if there're multiple possibilities. - // Both empty and "" means no pod is expected to be deleted. - expectedDeletedPod []string - expectedDeletionError bool - expectedPatchError bool - // Only compared when async preemption is disabled. - expectedStatus *fwk.Status - // Only compared when async preemption is enabled. - expectedPreemptingMap sets.Set[types.UID] - expectedActivatedPods map[string]*v1.Pod - }{ - { - name: "no victims", - candidate: &fakeCandidate{ - victims: &extenderv1.Victims{}, - }, - preemptor: preemptor, - testPods: []*v1.Pod{ - victim1, - }, - nodeNames: []string{node1Name}, - expectedStatus: nil, - }, - { - name: "one victim without condition", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victim1, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{ - victim1, - }, - nodeNames: []string{node1Name}, - expectedDeletedPod: []string{"victim1"}, - expectedStatus: nil, - expectedPreemptingMap: sets.New(types.UID("preemptor")), - }, - { - name: "one victim, but victim is already being deleted", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victimWithDeletionTimestamp, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{ - victimWithDeletionTimestamp, - }, - nodeNames: []string{node1Name}, - expectedStatus: nil, - }, - { - name: "one victim, but victim is already deleted", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - notFoundVictim1, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{}, - nodeNames: []string{node1Name}, - expectedStatus: nil, - expectedPreemptingMap: sets.New(types.UID("preemptor")), - }, - { - name: "one victim with same condition", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victim1WithMatchingCondition, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{ - victim1WithMatchingCondition, - }, - nodeNames: []string{node1Name}, - expectedDeletedPod: []string{"victim1"}, - expectedStatus: nil, - expectedPreemptingMap: sets.New(types.UID("preemptor")), - }, - { - name: "one victim, not-found victim error is ignored when patching", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victim1WithMatchingCondition, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{}, - nodeNames: []string{node1Name}, - expectedDeletedPod: []string{"victim1"}, - expectedStatus: nil, - expectedPreemptingMap: sets.New(types.UID("preemptor")), - }, - { - name: "one victim, but pod deletion failed", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - failVictim1WithMatchingCondition, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{}, - expectedDeletionError: true, - nodeNames: []string{node1Name}, - expectedStatus: fwk.AsStatus(errDeletePodFailed), - expectedPreemptingMap: sets.New(types.UID("preemptor")), - expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, - }, - { - name: "one victim, not-found victim error is ignored when deleting", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victim1, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{}, - nodeNames: []string{node1Name}, - expectedDeletedPod: []string{"victim1"}, - expectedStatus: nil, - expectedPreemptingMap: sets.New(types.UID("preemptor")), - }, - { - name: "one victim, but patch pod failed", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - failVictim, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{}, - expectedPatchError: true, - nodeNames: []string{node1Name}, - expectedStatus: fwk.AsStatus(errPatchStatusFailed), - expectedPreemptingMap: sets.New(types.UID("preemptor")), - expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, - }, - { - name: "two victims without condition, one passes successfully and the second fails", - - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - failVictim, - victim2, - }, - }, - }, - preemptor: preemptor, - testPods: []*v1.Pod{ - victim1, - }, - nodeNames: []string{node1Name}, - expectedPatchError: true, - expectedDeletedPod: []string{ - "victim2", - // The first victim could fail before the deletion of the second victim happens, - // which results in the second victim not being deleted. - "", - }, - expectedStatus: fwk.AsStatus(errPatchStatusFailed), - expectedPreemptingMap: sets.New(types.UID("preemptor")), - expectedActivatedPods: map[string]*v1.Pod{preemptor.Name: preemptor}, - }, - } - - for _, asyncPreemptionEnabled := range []bool{true, false} { - for _, asyncAPICallsEnabled := range []bool{true, false} { - for _, tt := range tests { - t.Run(fmt.Sprintf("%v (Async preemption enabled: %v, Async API calls enabled: %v)", tt.name, asyncPreemptionEnabled, asyncAPICallsEnabled), func(t *testing.T) { - metrics.Register() - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - nodes := make([]*v1.Node, len(tt.nodeNames)) - for i, nodeName := range tt.nodeNames { - nodes[i] = st.MakeNode().Name(nodeName).Capacity(veryLargeRes).Obj() - } - registeredPlugins := append([]tf.RegisterPluginFunc{ - tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New)}, - tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), - ) - var objs []runtime.Object - for _, pod := range tt.testPods { - objs = append(objs, pod) - } - - mu := &sync.RWMutex{} - deletedPods := sets.New[string]() - deletionFailure := false // whether any request to delete pod failed - patchFailure := false // whether any request to patch pod status failed - - cs := clientsetfake.NewClientset(objs...) - cs.PrependReactor("delete", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { - mu.Lock() - defer mu.Unlock() - name := action.(clienttesting.DeleteAction).GetName() - if name == "fail-victim" { - deletionFailure = true - return true, nil, errDeletePodFailed - } - // fake clientset does not return an error for not-found pods, so we simulate it here. - if name == "not-found-victim" { - // Simulate a not-found error. - return true, nil, apierrors.NewNotFound(v1.Resource("pods"), name) - } - - deletedPods.Insert(name) - return true, nil, nil - }) - - cs.PrependReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { - mu.Lock() - defer mu.Unlock() - if action.(clienttesting.PatchAction).GetName() == "fail-victim" { - patchFailure = true - return true, nil, errPatchStatusFailed - } - // fake clientset does not return an error for not-found pods, so we simulate it here. - if action.(clienttesting.PatchAction).GetName() == "not-found-victim" { - return true, nil, apierrors.NewNotFound(v1.Resource("pods"), "not-found-victim") - } - return true, nil, nil - }) - - informerFactory := informers.NewSharedInformerFactory(cs, 0) - eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: cs.EventsV1()}) - fakeActivator := &fakePodActivator{activatedPods: make(map[string]*v1.Pod), mu: mu} - - // Note: NominatedPodsForNode is called at the beginning of the goroutine in any case. - // fakePodNominator can delay the response of NominatedPodsForNode until the channel is closed, - // which allows us to test the preempting map before the goroutine does nothing yet. - requestStopper := make(chan struct{}) - nominator := &fakePodNominator{ - SchedulingQueue: internalqueue.NewSchedulingQueue(nil, informerFactory), - requestStopper: requestStopper, - } - var apiDispatcher *apidispatcher.APIDispatcher - if asyncAPICallsEnabled { - apiDispatcher = apidispatcher.New(cs, 16, apicalls.Relevances) - apiDispatcher.Run(logger) - defer apiDispatcher.Close() - } - - fwk, err := tf.NewFramework( - ctx, - registeredPlugins, "", - frameworkruntime.WithClientSet(cs), - frameworkruntime.WithAPIDispatcher(apiDispatcher), - frameworkruntime.WithLogger(logger), - frameworkruntime.WithInformerFactory(informerFactory), - frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), - frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(tt.testPods, nodes)), - frameworkruntime.WithPodNominator(nominator), - frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, "test-scheduler")), - frameworkruntime.WithPodActivator(fakeActivator), - ) - if err != nil { - t.Fatal(err) - } - informerFactory.Start(ctx.Done()) - informerFactory.WaitForCacheSync(ctx.Done()) - fakePreemptionScorePostFilterPlugin := &FakePreemptionScorePostFilterPlugin{} - if asyncAPICallsEnabled { - cache := internalcache.New(ctx, 100*time.Millisecond, apiDispatcher) - fwk.SetAPICacher(apicache.New(nil, cache)) - } - - pe := NewEvaluator("FakePreemptionScorePostFilter", fwk, fakePreemptionScorePostFilterPlugin, asyncPreemptionEnabled) - - if asyncPreemptionEnabled { - pe.prepareCandidateAsync(tt.candidate, tt.preemptor, "test-plugin") - pe.mu.Lock() - // The preempting map should be registered synchronously - // so we don't need wait.Poll. - if !tt.expectedPreemptingMap.Equal(pe.preempting) { - t.Errorf("expected preempting map %v, got %v", tt.expectedPreemptingMap, pe.preempting) - close(requestStopper) - pe.mu.Unlock() - return - } - pe.mu.Unlock() - // make the requests complete - close(requestStopper) - } else { - close(requestStopper) // no need to stop requests - status := pe.prepareCandidate(ctx, tt.candidate, tt.preemptor, "test-plugin") - if tt.expectedStatus == nil { - if status != nil { - t.Errorf("expect nil status, but got %v", status) - } - } else { - if !cmp.Equal(status, tt.expectedStatus) { - t.Errorf("expect status %v, but got %v", tt.expectedStatus, status) - } - } - } - - var lastErrMsg string - if err := wait.PollUntilContextTimeout(ctx, time.Millisecond*200, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) { - mu.RLock() - defer mu.RUnlock() - - pe.mu.Lock() - defer pe.mu.Unlock() - if len(pe.preempting) != 0 { - // The preempting map should be empty after the goroutine in all test cases. - lastErrMsg = fmt.Sprintf("expected no preempting pods, got %v", pe.preempting) - return false, nil - } - - if tt.expectedDeletionError != deletionFailure { - lastErrMsg = fmt.Sprintf("expected deletion error %v, got %v", tt.expectedDeletionError, deletionFailure) - return false, nil - } - if tt.expectedPatchError != patchFailure { - lastErrMsg = fmt.Sprintf("expected patch error %v, got %v", tt.expectedPatchError, patchFailure) - return false, nil - } - - if asyncPreemptionEnabled { - if diff := cmp.Diff(tt.expectedActivatedPods, fakeActivator.activatedPods); tt.expectedActivatedPods != nil && diff != "" { - lastErrMsg = fmt.Sprintf("Unexpected activated pods (-want,+got):\n%s", diff) - return false, nil - } - if tt.expectedActivatedPods == nil && len(fakeActivator.activatedPods) != 0 { - lastErrMsg = fmt.Sprintf("expected no activated pods, got %v", fakeActivator.activatedPods) - return false, nil - } - } - - if deletedPods.Len() > 1 { - // For now, we only expect at most one pod to be deleted in all test cases. - // If we need to test multiple pods deletion, we need to update the test table definition. - return false, fmt.Errorf("expected at most one pod to be deleted, got %v", deletedPods.UnsortedList()) - } - - if len(tt.expectedDeletedPod) == 0 { - if deletedPods.Len() != 0 { - // When tt.expectedDeletedPod is empty, we expect no pod to be deleted. - return false, fmt.Errorf("expected no pod to be deleted, got %v", deletedPods.UnsortedList()) - } - // nothing further to check. - return true, nil - } - - found := false - for _, podName := range tt.expectedDeletedPod { - if deletedPods.Has(podName) || - // If podName is empty, we expect no pod to be deleted. - (deletedPods.Len() == 0 && podName == "") { - found = true - } - } - if !found { - lastErrMsg = fmt.Sprintf("expected pod %v to be deleted, but %v is deleted", strings.Join(tt.expectedDeletedPod, " or "), deletedPods.UnsortedList()) - return false, nil - } - - return true, nil - }); err != nil { - t.Fatal(lastErrMsg) - } - }) - } - } - } -} - -type fakePodNominator struct { - // embed it so that we can only override NominatedPodsForNode - internalqueue.SchedulingQueue - - // fakePodNominator doesn't respond to NominatedPodsForNode() until the channel is closed. - requestStopper chan struct{} -} - -func (f *fakePodNominator) NominatedPodsForNode(nodeName string) []fwk.PodInfo { - <-f.requestStopper - return nil -} - type fakeExtender struct { ignorable bool errProcessPreemption bool @@ -996,7 +470,7 @@ func TestCallExtenders(t *testing.T) { Obj() makeCandidates = func(nodeName string, pods ...*v1.Pod) []Candidate { return []Candidate{ - &fakeCandidate{ + &candidate{ name: nodeName, victims: &extenderv1.Victims{ Pods: pods, @@ -1126,7 +600,7 @@ func TestCallExtenders(t *testing.T) { } informerFactory.Start(ctx.Done()) informerFactory.WaitForCacheSync(ctx.Done()) - cache := internalcache.New(ctx, 100*time.Millisecond, apiDispatcher) + cache := internalcache.New(ctx, apiDispatcher, false) fwk.SetAPICacher(apicache.New(nil, cache)) fakePreemptionScorePostFilterPlugin := &FakePreemptionScorePostFilterPlugin{} @@ -1157,432 +631,211 @@ func TestCallExtenders(t *testing.T) { } } -func TestRemoveNominatedNodeName(t *testing.T) { - tests := []struct { - name string - currentNominatedNodeName string - newNominatedNodeName string - expectPatchRequest bool - expectedPatchData string - }{ - { - name: "Should make patch request to clear node name", - currentNominatedNodeName: "node1", - expectPatchRequest: true, - expectedPatchData: `{"status":{"nominatedNodeName":null}}`, - }, - { - name: "Should not make patch request if nominated node is already cleared", - currentNominatedNodeName: "", - expectPatchRequest: false, - }, +func TestFilterVictimsWithPDBViolation(t *testing.T) { + newPodInfo := func(p *v1.Pod) fwk.PodInfo { + pi, _ := framework.NewPodInfo(p) + return pi } - for _, asyncAPICallsEnabled := range []bool{true, false} { - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - actualPatchRequests := 0 - var actualPatchData string - cs := &clientsetfake.Clientset{} - patchCalled := make(chan struct{}, 1) - cs.AddReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { - actualPatchRequests++ - patch := action.(clienttesting.PatchAction) - actualPatchData = string(patch.GetPatch()) - patchCalled <- struct{}{} - // For this test, we don't care about the result of the patched pod, just that we got the expected - // patch request, so just returning &v1.Pod{} here is OK because scheduler doesn't use the response. - return true, &v1.Pod{}, nil - }) - - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Status: v1.PodStatus{NominatedNodeName: test.currentNominatedNodeName}, - } - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - var apiCacher fwk.APICacher - if asyncAPICallsEnabled { - apiDispatcher := apidispatcher.New(cs, 16, apicalls.Relevances) - apiDispatcher.Run(logger) - defer apiDispatcher.Close() - - informerFactory := informers.NewSharedInformerFactory(cs, 0) - queue := internalqueue.NewSchedulingQueue(nil, informerFactory, internalqueue.WithAPIDispatcher(apiDispatcher)) - apiCacher = apicache.New(queue, nil) - } - - if err := clearNominatedNodeName(ctx, cs, apiCacher, pod); err != nil { - t.Fatalf("Error calling removeNominatedNodeName: %v", err) - } - - if test.expectPatchRequest { - select { - case <-patchCalled: - case <-time.After(time.Second): - t.Fatalf("Timed out while waiting for patch to be called") - } - if actualPatchData != test.expectedPatchData { - t.Fatalf("Patch data mismatch: Actual was %v, but expected %v", actualPatchData, test.expectedPatchData) - } - } else { - select { - case <-patchCalled: - t.Fatalf("Expected patch not to be called, actual patch data: %v", actualPatchData) - case <-time.After(time.Second): - } - } - }) - } + viNoPDBMatch := &victim{pods: []fwk.PodInfo{newPodInfo(st.MakePod().Name("p1").Label("app", "foo").Obj())}} + viMatchPDB := &victim{pods: []fwk.PodInfo{newPodInfo(st.MakePod().Name("p1").Namespace(metav1.NamespaceDefault).Label("app", "foo").Obj())}} + viMatchPDB2 := &victim{pods: []fwk.PodInfo{newPodInfo(st.MakePod().Name("p2").Namespace(metav1.NamespaceDefault).Label("app", "foo").Obj())}} + viPodGroup := &victim{ + pods: []fwk.PodInfo{ + newPodInfo(st.MakePod().Name("p1").Namespace(metav1.NamespaceDefault).Label("app", "foo").Obj()), + newPodInfo(st.MakePod().Name("p2").Namespace(metav1.NamespaceDefault).Label("app", "foo").Obj()), + }, } -} - -func TestPrepareCandidateAsyncSetsPreemptingSets(t *testing.T) { - var ( - node1Name = "node1" - defaultSchedulerName = "default-scheduler" - ) - - var ( - victim1 = st.MakePod().Name("victim1").UID("victim1"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - victim2 = st.MakePod().Name("victim2").UID("victim2"). - Node(node1Name).SchedulerName(defaultSchedulerName).Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - preemptor = st.MakePod().Name("preemptor").UID("preemptor"). - SchedulerName(defaultSchedulerName).Priority(highPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - testPods = []*v1.Pod{ - victim1, - victim2, - } - nodeNames = []string{node1Name} - ) + viMatchMultiplePDBs := &victim{pods: []fwk.PodInfo{newPodInfo(st.MakePod().Name("p1").Namespace(metav1.NamespaceDefault).Label("app", "foo").Label("tier", "backend").Obj())}} tests := []struct { - name string - candidate *fakeCandidate - lastVictim *v1.Pod - preemptor *v1.Pod + name string + victims []*victim + pdbs []*policy.PodDisruptionBudget + expectedViolating []*victim + expectedNonViolating []*victim }{ { - name: "no victims", - candidate: &fakeCandidate{ - victims: &extenderv1.Victims{}, - }, - lastVictim: nil, - preemptor: preemptor, + name: "no victims, no PDBs", + victims: nil, + pdbs: nil, + expectedViolating: nil, + expectedNonViolating: nil, }, { - name: "one victim", - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victim1, + name: "victim with no matching PDBs", + victims: []*victim{viNoPDBMatch}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "bar"}}, }, }, }, - lastVictim: victim1, - preemptor: preemptor, + expectedViolating: nil, + expectedNonViolating: []*victim{viNoPDBMatch}, }, { - name: "two victims", - candidate: &fakeCandidate{ - name: node1Name, - victims: &extenderv1.Victims{ - Pods: []*v1.Pod{ - victim1, - victim2, + name: "victim matching PDB, adequate DisruptionsAllowed", + victims: []*victim{viMatchPDB}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 1, }, }, }, - lastVictim: victim2, - preemptor: preemptor, + expectedViolating: nil, + expectedNonViolating: []*victim{viMatchPDB}, }, - } - - for _, asyncAPICallsEnabled := range []bool{true, false} { - for _, tt := range tests { - t.Run(fmt.Sprintf("%v (Async API calls enabled: %v)", tt.name, asyncAPICallsEnabled), func(t *testing.T) { - metrics.Register() - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - nodes := make([]*v1.Node, len(nodeNames)) - for i, nodeName := range nodeNames { - nodes[i] = st.MakeNode().Name(nodeName).Capacity(veryLargeRes).Obj() - } - registeredPlugins := append([]tf.RegisterPluginFunc{ - tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New)}, - tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), - ) - var objs []runtime.Object - for _, pod := range testPods { - objs = append(objs, pod) - } - - cs := clientsetfake.NewClientset(objs...) - - informerFactory := informers.NewSharedInformerFactory(cs, 0) - eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: cs.EventsV1()}) - - var apiDispatcher *apidispatcher.APIDispatcher - if asyncAPICallsEnabled { - apiDispatcher = apidispatcher.New(cs, 16, apicalls.Relevances) - apiDispatcher.Run(logger) - defer apiDispatcher.Close() - } - - fwk, err := tf.NewFramework( - ctx, - registeredPlugins, "", - frameworkruntime.WithClientSet(cs), - frameworkruntime.WithAPIDispatcher(apiDispatcher), - frameworkruntime.WithLogger(logger), - frameworkruntime.WithInformerFactory(informerFactory), - frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), - frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(testPods, nodes)), - frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, "test-scheduler")), - frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)), - ) - if err != nil { - t.Fatal(err) - } - informerFactory.Start(ctx.Done()) - fakePreemptionScorePostFilterPlugin := &FakePreemptionScorePostFilterPlugin{} - if asyncAPICallsEnabled { - cache := internalcache.New(ctx, 100*time.Millisecond, apiDispatcher) - fwk.SetAPICacher(apicache.New(nil, cache)) - } - - pe := NewEvaluator("FakePreemptionScorePostFilter", fwk, fakePreemptionScorePostFilterPlugin, true /* asyncPreemptionEnabled */) - // preemptPodCallsCounter helps verify if the last victim pod gets preempted after other victims. - preemptPodCallsCounter := 0 - preemptFunc := pe.PreemptPod - pe.PreemptPod = func(ctx context.Context, c Candidate, preemptor, victim *v1.Pod, pluginName string) error { - // Verify contents of the sets: preempting and lastVictimsPendingPreemption before preemption of subsequent pods. - pe.mu.RLock() - preemptPodCallsCounter++ - - if !pe.preempting.Has(tt.preemptor.UID) { - t.Errorf("Expected preempting set to be contain %v before preempting victim %v but got set: %v", tt.preemptor.UID, victim.Name, pe.preempting) - } - - victimCount := len(tt.candidate.Victims().Pods) - if victim.Name == tt.lastVictim.Name { - if victimCount != preemptPodCallsCounter { - t.Errorf("Expected PreemptPod for last victim %v to be called last (call no. %v), but it was called as no. %v", victim.Name, victimCount, preemptPodCallsCounter) - } - if v, ok := pe.lastVictimsPendingPreemption[tt.preemptor.UID]; !ok || tt.lastVictim.Name != v.name { - t.Errorf("Expected lastVictimsPendingPreemption map to contain victim %v for preemptor UID %v when preempting the last victim, but got map: %v", - tt.lastVictim.Name, tt.preemptor.UID, pe.lastVictimsPendingPreemption) - } - } else { - if preemptPodCallsCounter >= victimCount { - t.Errorf("Expected PreemptPod for victim %v to be called earlier, but it was called as last - no. %v", victim.Name, preemptPodCallsCounter) - } - if _, ok := pe.lastVictimsPendingPreemption[tt.preemptor.UID]; ok { - t.Errorf("Expected lastVictimsPendingPreemption map to not contain values for preemptor UID %v when not preempting the last victim, but got map: %v", - tt.preemptor.UID, pe.lastVictimsPendingPreemption) - } - } - pe.mu.RUnlock() - - return preemptFunc(ctx, c, preemptor, victim, pluginName) - } - - pe.mu.RLock() - if len(pe.preempting) > 0 { - t.Errorf("Expected preempting set to be empty before prepareCandidateAsync but got %v", pe.preempting) - } - if len(pe.lastVictimsPendingPreemption) > 0 { - t.Errorf("Expected lastVictimsPendingPreemption map to be empty before prepareCandidateAsync but got %v", pe.lastVictimsPendingPreemption) - } - pe.mu.RUnlock() - - pe.prepareCandidateAsync(tt.candidate, tt.preemptor, "test-plugin") - - // Perform the checks when there are no victims left to preempt. - t.Log("Waiting for async preemption goroutine to finish cleanup...") - err = wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 2*time.Second, false, func(ctx context.Context) (bool, error) { - // Check if the preemptor is removed from the ev.preempting set. - pe.mu.RLock() - defer pe.mu.RUnlock() - return !pe.preempting.Has(tt.preemptor.UID), nil - }) - if err != nil { - t.Errorf("Timed out waiting for preemptingSet to become empty. %v", err) - } - - pe.mu.RLock() - if _, ok := pe.lastVictimsPendingPreemption[tt.preemptor.UID]; ok { - t.Errorf("Expected lastVictimsPendingPreemption map to not contain values for %v after completing preemption, but got map: %v", - tt.preemptor.UID, pe.lastVictimsPendingPreemption) - } - if victimCount := len(tt.candidate.Victims().Pods); victimCount != preemptPodCallsCounter { - t.Errorf("Expected PreemptPod to be called %v times during prepareCandidateAsync but got %v", victimCount, preemptPodCallsCounter) - } - pe.mu.RUnlock() - }) - } - } -} - -// fakePodLister helps test IsPodRunningPreemption logic without worrying about cache synchronization issues. -// Current list of pods is set using field pods. -type fakePodLister struct { - corelisters.PodLister - pods map[string]*v1.Pod -} - -func (m *fakePodLister) Pods(namespace string) corelisters.PodNamespaceLister { - return &fakePodNamespaceLister{pods: m.pods} -} - -// fakePodNamespaceLister helps test IsPodRunningPreemption logic without worrying about cache synchronization issues. -// Current list of pods is set using field pods. -type fakePodNamespaceLister struct { - corelisters.PodNamespaceLister - pods map[string]*v1.Pod -} - -func (m *fakePodNamespaceLister) Get(name string) (*v1.Pod, error) { - if pod, ok := m.pods[name]; ok { - return pod, nil - } - // Important: Return the standard IsNotFound error for a fake cache miss. - return nil, apierrors.NewNotFound(v1.Resource("pods"), name) -} - -func TestIsPodRunningPreemption(t *testing.T) { - var ( - victim1 = st.MakePod().Name("victim1").UID("victim1"). - Node("node").SchedulerName("sch").Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - victim2 = st.MakePod().Name("victim2").UID("victim2"). - Node("node").SchedulerName("sch").Priority(midPriority). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - - victimWithDeletionTimestamp = st.MakePod().Name("victim-deleted").UID("victim-deleted"). - Node("node").SchedulerName("sch").Priority(midPriority). - Terminating(). - Containers([]v1.Container{st.MakeContainer().Name("container1").Obj()}). - Obj() - ) - - tests := []struct { - name string - preemptorUID types.UID - preemptingSet sets.Set[types.UID] - lastVictimSet map[types.UID]pendingVictim - podsInPodLister map[string]*v1.Pod - expectedResult bool - }{ { - name: "preemptor not in preemptingSet", - preemptorUID: "preemptor", - preemptingSet: sets.New[types.UID](), - lastVictimSet: map[types.UID]pendingVictim{}, - expectedResult: false, + name: "victim matching PDB, no DisruptionsAllowed", + victims: []*victim{viMatchPDB}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 0, + }, + }, + }, + expectedViolating: []*victim{viMatchPDB}, + expectedNonViolating: nil, }, { - name: "preemptor not in preemptingSet, lastVictimSet not empty", - preemptorUID: "preemptor", - preemptingSet: sets.New[types.UID](), - lastVictimSet: map[types.UID]pendingVictim{ - "preemptor": { - namespace: "ns", - name: "victim1", + name: "podgroup victim with multiple pods, one violating", + victims: []*victim{viPodGroup}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 1, + }, }, }, - expectedResult: false, + expectedViolating: []*victim{viPodGroup}, + expectedNonViolating: nil, }, { - name: "preemptor in preemptingSet, no lastVictim for preemptor", - preemptorUID: "preemptor", - preemptingSet: sets.New[types.UID]("preemptor"), - lastVictimSet: map[types.UID]pendingVictim{ - "otherPod": { - namespace: "ns", - name: "victim1", + name: "podgroup victim with multiple pods, none violating", + victims: []*victim{viPodGroup}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 2, + }, }, }, - expectedResult: true, + expectedViolating: nil, + expectedNonViolating: []*victim{viPodGroup}, }, { - name: "preemptor in preemptingSet, victim in lastVictimSet, not in PodLister", - preemptorUID: "preemptor", - preemptingSet: sets.New[types.UID]("preemptor"), - lastVictimSet: map[types.UID]pendingVictim{ - "preemptor": { - namespace: "ns", - name: "victim1", + name: "multiple victims matching the same PDB", + victims: []*victim{viMatchPDB, viMatchPDB2}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 1, + }, }, }, - podsInPodLister: map[string]*v1.Pod{}, - expectedResult: false, + expectedViolating: []*victim{viMatchPDB2}, + expectedNonViolating: []*victim{viMatchPDB}, }, { - name: "preemptor in preemptingSet, victim in lastVictimSet and in PodLister", - preemptorUID: "preemptor", - preemptingSet: sets.New[types.UID]("preemptor"), - lastVictimSet: map[types.UID]pendingVictim{ - "preemptor": { - namespace: "ns", - name: "victim1", + name: "pod in DisruptedPods is ignored", + victims: []*victim{viMatchPDB}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 0, + DisruptedPods: map[string]metav1.Time{ + "p1": {Time: time.Now()}, + }, + }, }, }, - podsInPodLister: map[string]*v1.Pod{ - "victim1": victim1, - "victim2": victim2, - }, - expectedResult: true, + expectedViolating: nil, + expectedNonViolating: []*victim{viMatchPDB}, }, { - name: "preemptor in preemptingSet, victim in lastVictimSet and in PodLister with deletion timestamp", - preemptorUID: "preemptor", - preemptingSet: sets.New[types.UID]("preemptor"), - lastVictimSet: map[types.UID]pendingVictim{ - "preemptor": { - namespace: "ns", - name: "victim-deleted", + name: "PDB with empty selector", + victims: []*victim{viMatchPDB}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{}, // matches nothing + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 0, + }, }, }, - podsInPodLister: map[string]*v1.Pod{ - "victim1": victim1, - "victim-deleted": victimWithDeletionTimestamp, + expectedViolating: nil, + expectedNonViolating: []*victim{viMatchPDB}, + }, + { + name: "Multiple PDBs", + victims: []*victim{viMatchMultiplePDBs}, + pdbs: []*policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 1, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault}, + Spec: policy.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"tier": "backend"}}, + }, + Status: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 0, + }, + }, }, - expectedResult: false, + expectedViolating: []*victim{viMatchMultiplePDBs}, + expectedNonViolating: nil, }, } for _, tt := range tests { - t.Run(fmt.Sprintf("%v", tt.name), func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { + violating, nonViolating := filterVictimsWithPDBViolation(tt.victims, tt.pdbs) - fakeLister := &fakePodLister{ - pods: tt.podsInPodLister, - } - pe := Evaluator{ - PodLister: fakeLister, - preempting: tt.preemptingSet, - lastVictimsPendingPreemption: tt.lastVictimSet, + if diff := cmp.Diff(tt.expectedViolating, violating, cmp.Comparer(func(a, b *victim) bool { return a == b })); diff != "" { + t.Errorf("violating victims mismatch (-want, +got):\n%s", diff) } - if result := pe.IsPodRunningPreemption(tt.preemptorUID); tt.expectedResult != result { - t.Errorf("Expected IsPodRunningPreemption to return %v but got %v", tt.expectedResult, result) + if diff := cmp.Diff(tt.expectedNonViolating, nonViolating, cmp.Comparer(func(a, b *victim) bool { return a == b })); diff != "" { + t.Errorf("nonViolating victims mismatch (-want, +got):\n%s", diff) } }) } diff --git a/pkg/scheduler/framework/preemption/types.go b/pkg/scheduler/framework/preemption/types.go new file mode 100644 index 0000000000000..f8034e03c1995 --- /dev/null +++ b/pkg/scheduler/framework/preemption/types.go @@ -0,0 +1,276 @@ +/* +Copyright 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 preemption + +import ( + "maps" + "slices" + "sync/atomic" + + v1 "k8s.io/api/core/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" + corev1helpers "k8s.io/component-helpers/scheduling/corev1" + extenderv1 "k8s.io/kube-scheduler/extender/v1" + fwk "k8s.io/kube-scheduler/framework" + + "k8s.io/kubernetes/pkg/scheduler/util" +) + +type podGroupPreemptor struct { + priority int32 + pods []*v1.Pod + podGroup *schedulingapi.PodGroup + preemptionPolicy v1.PreemptionPolicy +} + +func newPodGroupPreemptor(pg *schedulingapi.PodGroup, pods []*v1.Pod) *podGroupPreemptor { + preemptionPolicy := v1.PreemptLowerPriority + for _, pod := range pods { + if p := pod.Spec.PreemptionPolicy; p != nil && *p == v1.PreemptNever { + preemptionPolicy = *p + } + } + return &podGroupPreemptor{ + priority: util.PodGroupPriority(pg), + pods: pods, + podGroup: pg, + preemptionPolicy: preemptionPolicy, + } +} + +// Priority returns the scheduling priority of the preemptor. +// This value is used to identify potential victims (which must have lower priority). +func (p *podGroupPreemptor) Priority() int32 { + return p.priority +} + +// Members returns the list of Pods that belong to this preemptor. +func (p *podGroupPreemptor) Members() []*v1.Pod { + return p.pods +} + +// PodGroup returns a pod group connected with this preemptor. +func (p *podGroupPreemptor) PodGroup() *schedulingapi.PodGroup { + return p.podGroup +} + +// PreemptionPolicy returns a preemption policy of this preemptor. +func (p *podGroupPreemptor) PreemptionPolicy() v1.PreemptionPolicy { + return p.preemptionPolicy +} + +// domain represents the boundary or scope within which the preemption logic is evaluated. +type domain struct { + nodes []fwk.NodeInfo + name string + allPossibleVictims []*victim +} + +// isPodGroupPreemptiblePod checks if a pod is a part of a pod group that should +// be treated as a single unit for preemption purposes. +// If the pod is a part of such a pod group, it returns the pod group and true. +// In all other cases, it returns nil and false. +func isPodGroupPreemptiblePod(p *v1.Pod, pgLister schedulinglisters.PodGroupLister) (*schedulingapi.PodGroup, bool) { + if p.Spec.SchedulingGroup == nil { + return nil, false + } + pgName := p.Spec.SchedulingGroup.PodGroupName + pg, err := pgLister.PodGroups(p.Namespace).Get(*pgName) + if err != nil { + return nil, false + } + if mode := pg.Spec.DisruptionMode; mode == nil || *mode != schedulingapi.DisruptionModePodGroup { + return nil, false + } + return pg, true +} + +// newDomainForWorkloadPreemption creates a new domain for workload preemption. +// The domain is the whole cluster and it contains victims that are computed based +// on the pods and their scheduling groups. +// Pods that are part of a pod group with the PodGroup disruption mode are grouped +// together into a single victim. Otherwise, they are treated as individual victims. +func newDomainForWorkloadPreemption(nodes []fwk.NodeInfo, pgLister schedulinglisters.PodGroupLister, name string) *domain { + victimMap := map[types.UID]*victim{} + for _, node := range nodes { + for _, p := range node.GetPods() { + // TODO: Calling the lister here is not ideal given we do this + // for every pod in the cluster. Instead, we should be getting + // this information from the snapshot. + pg, ok := isPodGroupPreemptiblePod(p.GetPod(), pgLister) + if !ok { + victimMap[p.GetPod().UID] = newVictim([]fwk.PodInfo{p}, corev1helpers.PodPriority(p.GetPod()), []fwk.NodeInfo{node}) + continue + } + victim, ok := victimMap[pg.UID] + if ok { + victim.pods = append(victim.pods, p) + victim.affectedNodes[node.Node().Name] = node + continue + } + victimMap[pg.UID] = newVictim([]fwk.PodInfo{p}, util.PodGroupPriority(pg), []fwk.NodeInfo{node}) + } + } + + allPossibleVictims := slices.Collect(maps.Values(victimMap)) + return &domain{ + nodes: nodes, + allPossibleVictims: allPossibleVictims, + name: name, + } +} + +// Nodes returns a list of NodeInfo objects that belong to this domain. +func (d *domain) Nodes() []fwk.NodeInfo { + return d.nodes +} + +// // GetAllPossibleVictims returns all potential victims running within this domain. +func (d *domain) GetAllPossibleVictims() []*victim { + return d.allPossibleVictims +} + +// GetName returns a unique identifier for the domain. +// This is primarily used for logging and debugging purposes. +func (d *domain) GetName() string { + return d.name +} + +// victim represents an atomic entity that can be preempted (a victim). +// It abstracts individual Pods and PodGroup, ensuring that +// atomic entities are treated as a single unit during eviction. +type victim struct { + pods []fwk.PodInfo + priority int32 + affectedNodes map[string]fwk.NodeInfo + earliestStartTime *metav1.Time +} + +// newVictim creates a new Victim representing a set of Pods (or a PodGroup) that can be preempted together. +// It calculates the earliest start time among all provided Pods and identifies all nodes +// affected by the potential eviction of these Pods. +func newVictim(pods []fwk.PodInfo, priority int32, nodeInfos []fwk.NodeInfo) *victim { + nodes := make(map[string]fwk.NodeInfo) + for _, node := range nodeInfos { + nodes[node.Node().Name] = node + } + + var earliest *metav1.Time + for _, pInfo := range pods { + t := util.GetPodStartTime(pInfo.GetPod()) + if earliest == nil || (t != nil && t.Before(earliest)) { + earliest = t + } + } + + return &victim{ + affectedNodes: nodes, + priority: priority, + pods: pods, + earliestStartTime: earliest, + } +} + +// Pods returns the list of all Pods that belong to this preemption unit. +// Evicting this unit implies evicting all Pods in this list. +func (v *victim) Pods() []fwk.PodInfo { + return v.pods +} + +// Priority returns the priority of the preemption unit. +// For a single Pod, this is the Pod's priority. +// For a PodGroup, this is the priority of the PodGroup. +func (v *victim) Priority() int32 { + return v.priority +} + +// AffectedNodes returns a map of Node names to NodeInfo for all nodes +// where members of this preemption unit are currently running. +// This allows the preemption logic to identify the blast radius of evicting this unit. +func (v *victim) AffectedNodes() map[string]fwk.NodeInfo { + return v.affectedNodes +} + +// EarliestStartTime returns the earliest start time of all Pods in this victim. +func (v *victim) EarliestStartTime() *metav1.Time { + return v.earliestStartTime +} + +// IsPodGroup returns true if the preemption unit represents a PodGroup. +func (v *victim) IsPodGroup() bool { + return v.pods[0].GetPod().Spec.SchedulingGroup != nil +} + +// Candidate represents a nominated node on which the preemptor can be scheduled, +// along with the list of victims that should be evicted for the preemptor to fit the node. +type Candidate interface { + // Victims wraps a list of to-be-preempted Pods and the number of PDB violation. + Victims() *extenderv1.Victims + // Name returns the target domain(for pod group)/node name where the preemptor gets nominated to run. + Name() string +} + +type candidate struct { + victims *extenderv1.Victims + name string +} + +// Victims returns s.victims. +func (s *candidate) Victims() *extenderv1.Victims { + return s.victims +} + +// Name returns s.name. +func (s *candidate) Name() string { + return s.name +} + +type candidateList struct { + idx int32 + items []Candidate +} + +// newCandidateList creates a new candidate list with the given capacity. +func newCandidateList(capacity int32) *candidateList { + return &candidateList{idx: -1, items: make([]Candidate, capacity)} +} + +// add adds a new candidate to the internal array atomically. +// Note: in case the list has reached its capacity, the candidate is disregarded +// and not added to the internal array. +func (cl *candidateList) add(c *candidate) { + if idx := atomic.AddInt32(&cl.idx, 1); idx < int32(len(cl.items)) { + cl.items[idx] = c + } +} + +// size returns the number of candidate stored. Note that some add() operations +// might still be executing when this is called, so care must be taken to +// ensure that all add() operations complete before accessing the elements of +// the list. +func (cl *candidateList) size() int32 { + return min(atomic.LoadInt32(&cl.idx)+1, int32(len(cl.items))) +} + +// get returns the internal candidate array. This function is NOT atomic and +// assumes that all add() operations have been completed. +func (cl *candidateList) get() []Candidate { + return cl.items[:cl.size()] +} diff --git a/pkg/scheduler/framework/preemption/types_test.go b/pkg/scheduler/framework/preemption/types_test.go new file mode 100644 index 0000000000000..c53349f4962a7 --- /dev/null +++ b/pkg/scheduler/framework/preemption/types_test.go @@ -0,0 +1,263 @@ +/* +Copyright 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 preemption + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" + "k8s.io/apimachinery/pkg/util/sets" + fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework" + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +func TestIsPodGroupPreemptiblePod(t *testing.T) { + tests := []struct { + name string + pod *v1.Pod + podGroups map[string]*schedulingapi.PodGroup + wantPodGroup *schedulingapi.PodGroup + wantOk bool + }{ + { + name: "pod without scheduling group", + pod: st.MakePod().Name("p1").Namespace("default").Obj(), + wantPodGroup: nil, + wantOk: false, + }, + { + name: "pod group not found", + pod: st.MakePod().Name("p1").Namespace("default").PodGroupName("pg1").Obj(), + podGroups: map[string]*schedulingapi.PodGroup{}, + wantPodGroup: nil, + wantOk: false, + }, + { + name: "pod group with nil disruption mode", + pod: st.MakePod().Name("p1").Namespace("default").PodGroupName("pg1").Obj(), + podGroups: map[string]*schedulingapi.PodGroup{ + "pg1": st.MakePodGroup().Name("pg1").Namespace("default").Obj(), + }, + wantPodGroup: nil, + wantOk: false, + }, + { + name: "pod group with DisruptionModePod", + pod: st.MakePod().Name("p1").Namespace("default").PodGroupName("pg1").Obj(), + podGroups: map[string]*schedulingapi.PodGroup{ + "pg1": st.MakePodGroup().Name("pg1").Namespace("default").DisruptionMode(schedulingapi.DisruptionModePod).Obj(), + }, + wantPodGroup: nil, + wantOk: false, + }, + { + name: "pod group with DisruptionModePodGroup", + pod: st.MakePod().Name("p1").Namespace("default").PodGroupName("pg1").Obj(), + podGroups: map[string]*schedulingapi.PodGroup{ + "pg1": st.MakePodGroup().Name("pg1").Namespace("default").DisruptionMode(schedulingapi.DisruptionModePodGroup).Obj(), + }, + wantPodGroup: st.MakePodGroup().Name("pg1").Namespace("default").DisruptionMode(schedulingapi.DisruptionModePodGroup).Obj(), + wantOk: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pgLister := &mockPodGroupLister{podGroups: tt.podGroups} + podGroup, ok := isPodGroupPreemptiblePod(tt.pod, pgLister) + if ok != tt.wantOk { + t.Errorf("isPodGroupPreemptiblePod() gotOk = %v, want %v", ok, tt.wantOk) + } + if diff := cmp.Diff(tt.wantPodGroup, podGroup); diff != "" { + t.Errorf("isPodGroupPreemptiblePod() gotPodGroup mismatch (-want +got):\n%s", diff) + } + }) + } +} + +type expectedVictim struct { + pods sets.Set[string] + affectedNodes sets.Set[string] + priority int32 +} + +func TestNewDomainForWorkloadPreemption(t *testing.T) { + tests := []struct { + name string + nodes []*v1.Node + pods []*v1.Pod + podGroups map[string]*schedulingapi.PodGroup + domainName string + wantVictims []expectedVictim + }{ + { + name: "no pods", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + }, + pods: nil, + podGroups: nil, + domainName: "test-domain", + wantVictims: nil, + }, + { + name: "pods without pod groups", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + }, + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Node("node1").Priority(10).Obj(), + st.MakePod().Name("p2").UID("p2").Node("node2").Priority(20).Obj(), + }, + podGroups: nil, + domainName: "test-domain", + wantVictims: []expectedVictim{ + {pods: sets.New("p1"), affectedNodes: sets.New("node1"), priority: 10}, + {pods: sets.New("p2"), affectedNodes: sets.New("node2"), priority: 20}, + }, + }, + { + name: "pods with pod group (DisruptionModePodGroup)", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + }, + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Node("node1").PodGroupName("pg1").Priority(10).Obj(), + st.MakePod().Name("p2").UID("p2").Node("node2").PodGroupName("pg1").Priority(10).Obj(), + }, + podGroups: map[string]*schedulingapi.PodGroup{ + "pg1": st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(50).Obj(), + }, + domainName: "test-domain", + wantVictims: []expectedVictim{ + {pods: sets.New("p1", "p2"), affectedNodes: sets.New("node1", "node2"), priority: 50}, + }, + }, + { + name: "pods with pod group (DisruptionModePod)", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + }, + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Node("node1").PodGroupName("pg1").Priority(10).Obj(), + st.MakePod().Name("p2").UID("p2").Node("node2").PodGroupName("pg1").Priority(20).Obj(), + }, + podGroups: map[string]*schedulingapi.PodGroup{ + "pg1": st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Priority(50).Obj(), + }, + domainName: "test-domain", + wantVictims: []expectedVictim{ + {pods: sets.New("p1"), affectedNodes: sets.New("node1"), priority: 10}, + {pods: sets.New("p2"), affectedNodes: sets.New("node2"), priority: 20}, + }, + }, + { + name: "mix of pod groups and individual pods", + nodes: []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + }, + pods: []*v1.Pod{ + st.MakePod().Name("p1").UID("p1").Node("node1").PodGroupName("pg1").Priority(10).Obj(), + st.MakePod().Name("p2").UID("p2").Node("node2").PodGroupName("pg1").Priority(10).Obj(), + st.MakePod().Name("p3").UID("p3").Node("node1").PodGroupName("pg2").Priority(20).Obj(), + st.MakePod().Name("p4").UID("p4").Node("node2").Priority(30).Obj(), + }, + podGroups: map[string]*schedulingapi.PodGroup{ + "pg1": st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePodGroup).Priority(50).Obj(), + "pg2": st.MakePodGroup().Name("pg2").UID("pg2").DisruptionMode(schedulingapi.DisruptionModePod).Priority(60).Obj(), + }, + domainName: "test-domain", + wantVictims: []expectedVictim{ + {pods: sets.New("p1", "p2"), affectedNodes: sets.New("node1", "node2"), priority: 50}, + {pods: sets.New("p3"), affectedNodes: sets.New("node1"), priority: 20}, + {pods: sets.New("p4"), affectedNodes: sets.New("node2"), priority: 30}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodeInfos := make(map[string]fwk.NodeInfo) + for _, node := range tt.nodes { + ni := framework.NewNodeInfo() + ni.SetNode(node) + nodeInfos[node.Name] = ni + } + for _, p := range tt.pods { + if ni, ok := nodeInfos[p.Spec.NodeName]; ok { + pi, _ := framework.NewPodInfo(p) + ni.AddPodInfo(pi) + } + } + + var domainNodes []fwk.NodeInfo + for _, node := range tt.nodes { + if ni, ok := nodeInfos[node.Name]; ok { + domainNodes = append(domainNodes, ni) + } + } + + pgLister := &mockPodGroupLister{podGroups: tt.podGroups} + domain := newDomainForWorkloadPreemption(domainNodes, pgLister, tt.domainName) + + if domain.GetName() != tt.domainName { + t.Errorf("expected domain name %q, got %q", tt.domainName, domain.GetName()) + } + + victims := domain.GetAllPossibleVictims() + + var gotVictims []expectedVictim + for _, v := range victims { + ev := expectedVictim{ + pods: sets.New[string](), + affectedNodes: sets.New[string](), + priority: v.Priority(), + } + for _, p := range v.Pods() { + ev.pods.Insert(p.GetPod().Name) + } + for n := range v.AffectedNodes() { + ev.affectedNodes.Insert(n) + } + gotVictims = append(gotVictims, ev) + } + + sortVictims := func(vs []expectedVictim) { + sort.Slice(vs, func(i, j int) bool { + if vs[i].pods.Len() == 0 || vs[j].pods.Len() == 0 { + return vs[i].pods.Len() < vs[j].pods.Len() + } + return sets.List(vs[i].pods)[0] < sets.List(vs[j].pods)[0] + }) + } + sortVictims(gotVictims) + sortVictims(tt.wantVictims) + + if diff := cmp.Diff(tt.wantVictims, gotVictims, cmp.AllowUnexported(expectedVictim{})); diff != "" { + t.Errorf("victims mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/scheduler/framework/runtime/batch.go b/pkg/scheduler/framework/runtime/batch.go index ad296f8a0103d..67c9fc5051efc 100644 --- a/pkg/scheduler/framework/runtime/batch.go +++ b/pkg/scheduler/framework/runtime/batch.go @@ -31,10 +31,9 @@ import ( // OpportunisticBatching caches results from filtering and scoring when possible to optimize // scheduling of common pods. type OpportunisticBatch struct { - state *batchState - lastCycle schedulingCycle - signatureFunc SignatureFunc - handle fwk.Handle + state *batchState + lastCycle schedulingCycle + handle fwk.Handle // Used primarily for tests, count the total pods we // have successfully batched. @@ -57,17 +56,11 @@ const ( maxBatchAge = 500 * time.Millisecond ) -type SignatureFunc func(h fwk.Handle, ctx context.Context, p *v1.Pod, recordPluginStats bool) fwk.PodSignature - -func signUsingFramework(h fwk.Handle, ctx context.Context, p *v1.Pod, recordPluginStats bool) fwk.PodSignature { - return h.SignPod(ctx, p, recordPluginStats) -} - -// Provide a hint for the pod based on filtering an scoring results of previous cycles. Caching works only for consecutive pods +// GetNodeHint provides a hint for the pod based on filtering a scoring results of previous cycles. Caching works only for consecutive pods // with the same signature that are scheduled in 1-pod-per-node manner (otherwise previous scores could not be reused). -// It's assured by checking the top rated node is no longer feasible (meaning the previous pod was successfully scheduled and the +// It's assured by checking the top-rated node is no longer feasible (meaning the previous pod was successfully scheduled and the // current one does not fit). -func (b *OpportunisticBatch) GetNodeHint(ctx context.Context, pod *v1.Pod, state fwk.CycleState, cycleCount int64) (string, fwk.PodSignature) { +func (b *OpportunisticBatch) GetNodeHint(ctx context.Context, pod *v1.Pod, signature fwk.PodSignature, state fwk.CycleState, cycleCount int64) string { logger := klog.FromContext(ctx) var hint string @@ -80,16 +73,14 @@ func (b *OpportunisticBatch) GetNodeHint(ctx context.Context, pod *v1.Pod, state metrics.GetNodeHintDuration.WithLabelValues(hinted, b.handle.ProfileName()).Observe(metrics.SinceInSeconds(startTime)) }() - signature := b.signatureFunc(b.handle, ctx, pod, state.ShouldRecordPluginMetrics()) - nodeInfos := b.handle.SnapshotSharedLister().NodeInfos() // If we don't have state that we can use, then return an empty hint. - if !b.batchStateCompatible(ctx, logger, pod, signature, cycleCount, state, nodeInfos) { + if !b.batchStateCompatible(ctx, pod, signature, cycleCount, state, nodeInfos) { metrics.BatchAttemptStats.WithLabelValues(b.handle.ProfileName(), metrics.BatchAttemptNoHint).Inc() logger.V(4).Info("OpportunisticBatch no node hint available for pod", "profile", b.handle.ProfileName(), "pod", klog.KObj(pod), "cycleCount", cycleCount) - return "", signature + return "" } // Otherwise, pop the head of the list in our state and return it as @@ -99,16 +90,14 @@ func (b *OpportunisticBatch) GetNodeHint(ctx context.Context, pod *v1.Pod, state "profile", b.handle.ProfileName(), "pod", klog.KObj(pod), "cycleCount", cycleCount, "hint", hint, "remainingNodes", b.state.sortedNodes.Len()) - return hint, signature + return hint } -// Store results from scheduling for use as a batch later. +// StoreScheduleResults stores results from scheduling for use as a batch later. func (b *OpportunisticBatch) StoreScheduleResults(ctx context.Context, signature fwk.PodSignature, hintedNode, chosenNode string, otherNodes framework.SortedScoredNodes, cycleCount int64) { logger := klog.FromContext(ctx) - startTime := time.Now() - defer metrics.StoreScheduleResultsDuration.WithLabelValues(b.handle.ProfileName()).Observe(metrics.SinceInSeconds(startTime)) - + defer metrics.StoreScheduleResultsDuration.ObserveSince(time.Now(), b.handle.ProfileName())() // Set our cycle information for next time. b.lastCycle = schedulingCycle{ cycleCount: cycleCount, @@ -170,11 +159,12 @@ func (b *OpportunisticBatch) logUnusableState(logger klog.Logger, cycleCount int } // Update the batch state based on a the arrival of a new pod and the chosen node from the last pod. -func (b *OpportunisticBatch) batchStateCompatible(ctx context.Context, logger klog.Logger, pod *v1.Pod, signature fwk.PodSignature, cycleCount int64, state fwk.CycleState, nodeInfos fwk.NodeInfoLister) bool { +func (b *OpportunisticBatch) batchStateCompatible(ctx context.Context, pod *v1.Pod, signature fwk.PodSignature, cycleCount int64, state fwk.CycleState, nodeInfos fwk.NodeInfoLister) bool { // Just return if we don't have any state to use. if b.stateEmpty() { return false } + logger := klog.FromContext(ctx) // In this case, a previous pod was scheduled by another profile, meaning we can't use the state anymore. if cycleCount != b.lastCycle.cycleCount+1 { @@ -232,9 +222,8 @@ func (b *OpportunisticBatch) stateEmpty() bool { return b.state == nil || b.state.sortedNodes == nil || b.state.sortedNodes.Len() == 0 } -func newOpportunisticBatch(h fwk.Handle, signatureFunc SignatureFunc) *OpportunisticBatch { +func newOpportunisticBatch(h fwk.Handle) *OpportunisticBatch { return &OpportunisticBatch{ - signatureFunc: signatureFunc, - handle: h, + handle: h, } } diff --git a/pkg/scheduler/framework/runtime/batch_test.go b/pkg/scheduler/framework/runtime/batch_test.go index b053fb6bebdfd..7743ae15a1b32 100644 --- a/pkg/scheduler/framework/runtime/batch_test.go +++ b/pkg/scheduler/framework/runtime/batch_test.go @@ -82,6 +82,10 @@ func (s sharedLister) StorageInfos() fwk.StorageInfoLister { return &storageInfoListerContract{} } +func (s sharedLister) PodGroupStates() fwk.PodGroupStateLister { + return nil +} + var batchRegistry = func() Registry { r := make(Registry) err := r.Register("batchTest", newBatchTestPlugin) @@ -180,12 +184,6 @@ func newTestNodes(n []string) *testSortedScoredNodes { return &testSortedScoredNodes{Nodes: n} } -func newTestSigFunc(sig *fwk.PodSignature) SignatureFunc { - return func(h fwk.Handle, ctx context.Context, p *v1.Pod, shouldRecordStats bool) fwk.PodSignature { - return *sig - } -} - func TestBatchBasic(t *testing.T) { // This test first let OpportunisticBatch handle the first pod, and then see how it behaves with the second pod. tests := []struct { @@ -327,11 +325,11 @@ func TestBatchBasic(t *testing.T) { } signature := fwk.PodSignature(tt.firstSig) - batch := newOpportunisticBatch(testFwk, newTestSigFunc(&signature)) + batch := newOpportunisticBatch(testFwk) state := framework.NewCycleState() // Run the first "pod" through - hint, _ := batch.GetNodeHint(ctx, pod, state, 1) + hint := batch.GetNodeHint(ctx, pod, signature, state, 1) if hint != "" { t.Fatalf("Got unexpected hint %s", hint) } @@ -360,7 +358,7 @@ func TestBatchBasic(t *testing.T) { lister.nodes = nodeInfoLister{lastChosenNode} signature = fwk.PodSignature(tt.secondSig) - hint, _ = batch.GetNodeHint(ctx, pod2, state, cycleCount) + hint = batch.GetNodeHint(ctx, pod2, signature, state, cycleCount) if hint != tt.expectedHint { t.Fatalf("Got hint '%s' expected '%s' for test '%s'", hint, tt.expectedHint, tt.name) diff --git a/pkg/scheduler/framework/runtime/framework.go b/pkg/scheduler/framework/runtime/framework.go index 350c8e0c7fe19..aafdce0e27620 100644 --- a/pkg/scheduler/framework/runtime/framework.go +++ b/pkg/scheduler/framework/runtime/framework.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "reflect" + "slices" "sort" "time" @@ -43,8 +44,8 @@ import ( apidispatcher "k8s.io/kubernetes/pkg/scheduler/backend/api_dispatcher" "k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" "k8s.io/kubernetes/pkg/scheduler/metrics" - "k8s.io/kubernetes/pkg/util/slice" ) const ( @@ -55,34 +56,40 @@ const ( // frameworkImpl is the component responsible for initializing and running scheduler // plugins. type frameworkImpl struct { - registry Registry - snapshotSharedLister fwk.SharedLister - waitingPods *waitingPodsMap - scorePluginWeight map[string]int - preEnqueuePlugins []fwk.PreEnqueuePlugin - enqueueExtensions []fwk.EnqueueExtensions - queueSortPlugins []fwk.QueueSortPlugin - preFilterPlugins []fwk.PreFilterPlugin - filterPlugins []fwk.FilterPlugin - postFilterPlugins []fwk.PostFilterPlugin - preScorePlugins []fwk.PreScorePlugin - scorePlugins []fwk.ScorePlugin - reservePlugins []fwk.ReservePlugin - preBindPlugins []fwk.PreBindPlugin - bindPlugins []fwk.BindPlugin - postBindPlugins []fwk.PostBindPlugin - permitPlugins []fwk.PermitPlugin - batchablePlugins []fwk.SignPlugin + registry Registry + snapshotSharedLister fwk.SharedLister + waitingPods *waitingPodsMap + podsInPreBind *podsInPreBindMap + scorePluginWeight map[string]int + preEnqueuePlugins []fwk.PreEnqueuePlugin + enqueueExtensions []fwk.EnqueueExtensions + queueSortPlugins []fwk.QueueSortPlugin + preFilterPlugins []fwk.PreFilterPlugin + filterPlugins []fwk.FilterPlugin + postFilterPlugins []fwk.PostFilterPlugin + preScorePlugins []fwk.PreScorePlugin + scorePlugins []fwk.ScorePlugin + reservePlugins []fwk.ReservePlugin + preBindPlugins []fwk.PreBindPlugin + bindPlugins []fwk.BindPlugin + postBindPlugins []fwk.PostBindPlugin + permitPlugins []fwk.PermitPlugin + batchablePlugins []fwk.SignPlugin + podGroupPostFilterPlugins []framework.PodGroupPostFilterPlugin + + placementGeneratePlugins []fwk.PlacementGeneratePlugin + placementScorePlugins []fwk.PlacementScorePlugin + placementScorePluginWeight map[string]int // pluginsMap contains all plugins, by name. pluginsMap map[string]fwk.Plugin clientSet clientset.Interface kubeConfig *restclient.Config - eventRecorder events.EventRecorder + eventRecorder events.EventRecorderLogger informerFactory informers.SharedInformerFactory sharedDRAManager fwk.SharedDRAManager - workloadManager fwk.WorkloadManager + podGroupManager fwk.PodGroupManager logger klog.Logger sharedCSIManager fwk.CSIManager @@ -129,6 +136,8 @@ func (f *frameworkImpl) getExtensionPoints(plugins *config.Plugins) []extensionP {&plugins.Permit, &f.permitPlugins}, {&plugins.PreEnqueue, &f.preEnqueuePlugins}, {&plugins.QueueSort, &f.queueSortPlugins}, + {&plugins.PlacementGenerate, &f.placementGeneratePlugins}, + {&plugins.PlacementScore, &f.placementScorePlugins}, } } @@ -141,7 +150,7 @@ type frameworkOptions struct { componentConfigVersion string clientSet clientset.Interface kubeConfig *restclient.Config - eventRecorder events.EventRecorder + eventRecorder events.EventRecorderLogger informerFactory informers.SharedInformerFactory sharedDRAManager fwk.SharedDRAManager sharedCSIManager fwk.CSIManager @@ -153,8 +162,9 @@ type frameworkOptions struct { captureProfile CaptureProfile parallelizer parallelize.Parallelizer waitingPods *waitingPodsMap + podsInPreBind *podsInPreBindMap apiDispatcher *apidispatcher.APIDispatcher - workloadManager fwk.WorkloadManager + podGroupManager fwk.PodGroupManager logger *klog.Logger } @@ -186,7 +196,7 @@ func WithKubeConfig(kubeConfig *restclient.Config) Option { } // WithEventRecorder sets clientSet for the scheduling frameworkImpl. -func WithEventRecorder(recorder events.EventRecorder) Option { +func WithEventRecorder(recorder events.EventRecorderLogger) Option { return func(o *frameworkOptions) { o.eventRecorder = recorder } @@ -254,10 +264,10 @@ func WithAPIDispatcher(apiDispatcher *apidispatcher.APIDispatcher) Option { } } -// WithWorkloadManager sets Workload manager for the scheduling frameworkImpl. -func WithWorkloadManager(workloadManager fwk.WorkloadManager) Option { +// WithPodGroupManager sets the PodGroupManager for the scheduling frameworkImpl. +func WithPodGroupManager(podGroupManager fwk.PodGroupManager) Option { return func(o *frameworkOptions) { - o.workloadManager = workloadManager + o.podGroupManager = podGroupManager } } @@ -285,6 +295,13 @@ func WithWaitingPods(wp *waitingPodsMap) Option { } } +// WithPodsInPreBind sets podsInPreBind for the scheduling frameworkImpl. +func WithPodsInPreBind(bp *podsInPreBindMap) Option { + return func(o *frameworkOptions) { + o.podsInPreBind = bp + } +} + // WithLogger overrides the default logger from k8s.io/klog. func WithLogger(logger klog.Logger) Option { return func(o *frameworkOptions) { @@ -303,6 +320,10 @@ func defaultFrameworkOptions(stopCh <-chan struct{}) frameworkOptions { var _ framework.Framework = &frameworkImpl{} // NewFramework initializes plugins given the configuration and the registry. +// +// It creates background goroutines (for example, via defaultFrameworkOptions -> metrics.NewMetricsAsyncRecorder) +// which continue running until the context gets canceled. WaitForShutdown can be used to block +// until they have terminated. func NewFramework(ctx context.Context, r Registry, profile *config.KubeSchedulerProfile, opts ...Option) (framework.Framework, error) { options := defaultFrameworkOptions(ctx.Done()) for _, opt := range opts { @@ -317,8 +338,8 @@ func NewFramework(ctx context.Context, r Registry, profile *config.KubeScheduler registry: r, snapshotSharedLister: options.snapshotSharedLister, sharedCSIManager: options.sharedCSIManager, - scorePluginWeight: make(map[string]int), waitingPods: options.waitingPods, + podsInPreBind: options.podsInPreBind, clientSet: options.clientSet, kubeConfig: options.kubeConfig, eventRecorder: options.eventRecorder, @@ -329,13 +350,13 @@ func NewFramework(ctx context.Context, r Registry, profile *config.KubeScheduler PodNominator: options.podNominator, PodActivator: options.podActivator, apiDispatcher: options.apiDispatcher, - workloadManager: options.workloadManager, + podGroupManager: options.podGroupManager, parallelizer: options.parallelizer, logger: logger, } if utilfeature.DefaultFeatureGate.Enabled(features.OpportunisticBatching) { - f.batch = newOpportunisticBatch(f, signUsingFramework) + f.batch = newOpportunisticBatch(f) } if len(f.extenders) > 0 { @@ -417,10 +438,15 @@ func NewFramework(ctx context.Context, r Registry, profile *config.KubeScheduler if len(f.bindPlugins) == 0 { return nil, fmt.Errorf("at least one bind plugin is needed for profile with scheduler name %q", profile.SchedulerName) } + if len(f.placementGeneratePlugins) > 1 { + return nil, fmt.Errorf("at most one placement generate plugin is allowed for profile with scheduler name %q", profile.SchedulerName) + } - if err := getScoreWeights(f, append(profile.Plugins.Score.Enabled, profile.Plugins.MultiPoint.Enabled...)); err != nil { - return nil, err + podScoreWeights, err := getValidScoreWeights(f, reflect.TypeFor[fwk.ScorePlugin](), append(profile.Plugins.Score.Enabled, profile.Plugins.MultiPoint.Enabled...)) + if err != nil { + return nil, fmt.Errorf("score plugins: %w", err) } + f.scorePluginWeight = podScoreWeights // Verifying the score weights again since Plugin.Name() could return a different // value from the one used in the configuration. @@ -430,10 +456,37 @@ func NewFramework(ctx context.Context, r Registry, profile *config.KubeScheduler } } + placementScoreWeights, err := getValidScoreWeights(f, reflect.TypeFor[fwk.PlacementScorePlugin](), append(profile.Plugins.PlacementScore.Enabled, profile.Plugins.MultiPoint.Enabled...)) + if err != nil { + return nil, fmt.Errorf("placement score plugins: %w", err) + } + f.placementScorePluginWeight = placementScoreWeights + + // Verifying the placement score weights again since Plugin.Name() could return a different + // value from the one used in the configuration. + for _, placementScorePlugin := range f.placementScorePlugins { + if f.placementScorePluginWeight[placementScorePlugin.Name()] == 0 { + return nil, fmt.Errorf("placement score plugin %q is not configured with weight", placementScorePlugin.Name()) + } + } + if utilfeature.DefaultFeatureGate.Enabled(features.OpportunisticBatching) { f.computeBatchablePlugins() } + // Put default preemption as the only PodGroupPostFilterPlugin + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) { + if dp, ok := f.pluginsMap[names.DefaultPreemption]; ok { + if _, ok := dp.(framework.PodGroupPostFilterPlugin); ok { + f.podGroupPostFilterPlugins = append(f.podGroupPostFilterPlugins, dp.(framework.PodGroupPostFilterPlugin)) + } else { + logger.V(2).Info("Workload Aware Preemption is enabled, but default preemption plugin does not fulfill PodGroupPostFilterPlugin interface. Workload Aware Preemption will not be used.") + } + } else { + logger.V(2).Info("Workload Aware Preemption is enabled, but default preemption plugin is not set. Workload Aware Preemption will not be used.") + } + } + if options.captureProfile != nil { if len(outputProfile.PluginConfig) != 0 { sort.Slice(outputProfile.PluginConfig, func(i, j int) bool { @@ -451,6 +504,15 @@ func NewFramework(ctx context.Context, r Registry, profile *config.KubeScheduler return f, nil } +// WaitForShutdown waits for completion of all background goroutines of a framework +// instance created by NewFramework. The context given to NewFramework must be canceled +// to stop those background goroutines. +func WaitForShutdown(f framework.Framework) { + if f.(*frameworkImpl).metricsRecorder != nil { + <-f.(*frameworkImpl).metricsRecorder.IsStoppedCh + } +} + // setInstrumentedPlugins initializes instrumented plugins from current plugins that frameworkImpl has. func (f *frameworkImpl) setInstrumentedPlugins() { // Cache metric streams for prefilter and filter plugins. @@ -509,12 +571,12 @@ func (f *frameworkImpl) Close() error { return errors.Join(errs...) } -// getScoreWeights makes sure that, between MultiPoint-Score plugin weights and individual Score +// getValidScoreWeights makes sure that, between MultiPoint-Score plugin weights and individual Score // plugin weights there is not an overflow of MaxTotalScore. -func getScoreWeights(f *frameworkImpl, plugins []config.Plugin) error { +// pluginType is the type of the plugin for which it's valid to define weight, such as Score or PlacementScore. +func getValidScoreWeights(f *frameworkImpl, pluginType reflect.Type, plugins []config.Plugin) (map[string]int, error) { + weights := make(map[string]int) var totalPriority int64 - scorePlugins := reflect.ValueOf(&f.scorePlugins).Elem() - pluginType := scorePlugins.Type().Elem() for _, e := range plugins { pg := f.pluginsMap[e.Name] if !reflect.TypeOf(pg).Implements(pluginType) { @@ -523,23 +585,23 @@ func getScoreWeights(f *frameworkImpl, plugins []config.Plugin) error { // We append MultiPoint plugins to the list of Score plugins. So if this plugin has already been // encountered, let the individual Score weight take precedence. - if _, ok := f.scorePluginWeight[e.Name]; ok { + if _, ok := weights[e.Name]; ok { continue } // a weight of zero is not permitted, plugins can be disabled explicitly // when configured. - f.scorePluginWeight[e.Name] = int(e.Weight) - if f.scorePluginWeight[e.Name] == 0 { - f.scorePluginWeight[e.Name] = 1 + weights[e.Name] = int(e.Weight) + if weights[e.Name] == 0 { + weights[e.Name] = 1 } // Checks totalPriority against MaxTotalScore to avoid overflow - if int64(f.scorePluginWeight[e.Name])*fwk.MaxNodeScore > fwk.MaxTotalScore-totalPriority { - return fmt.Errorf("total score of Score plugins could overflow") + if int64(weights[e.Name])*fwk.MaxScore > fwk.MaxTotalScore-totalPriority { + return nil, fmt.Errorf("total score of Score plugins could overflow") } - totalPriority += int64(f.scorePluginWeight[e.Name]) * fwk.MaxNodeScore + totalPriority += int64(weights[e.Name]) * fwk.MaxScore } - return nil + return weights, nil } type orderedSet struct { @@ -641,7 +703,7 @@ func (f *frameworkImpl) expandMultiPointPlugins(logger klog.Logger, profile *con // - part 3: other plugins (excluded by part 1 & 2) in regular extension point. newPlugins := reflect.New(reflect.TypeOf(e.slicePtr).Elem()).Elem() // part 1 - for _, name := range slice.CopyStrings(enabledSet.list) { + for _, name := range slices.Clone(enabledSet.list) { if overridePlugins.has(name) { newPlugins = reflect.Append(newPlugins, reflect.ValueOf(f.pluginsMap[name])) enabledSet.delete(name) @@ -807,16 +869,14 @@ func (f *frameworkImpl) computeBatchablePlugins() { // is unable to construct a signature for the pod, the result will be nil, which means // there is no way to compare this pod against others, and will turn off a number of optimizations // for this pod. -func (f *frameworkImpl) SignPod(ctx context.Context, pod *v1.Pod, recordPluginStats bool) fwk.PodSignature { +func (f *frameworkImpl) SignPod(ctx context.Context, pod *v1.Pod) fwk.PodSignature { logger := klog.FromContext(ctx) var status *fwk.Status - if recordPluginStats { - startTime := time.Now() - defer func() { - metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Sign, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) - }() - } + startTime := time.Now() + defer func() { + f.metricsRecorder.ObserveFrameworkExtensionPointDurationAsync(metrics.Sign, status.Code().String(), f.profileName, metrics.SinceInSeconds(startTime)) + }() if !f.enableSignatures { return nil @@ -1083,6 +1143,10 @@ func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, state fwk.Cycl metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PostFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) }() + if state.ShouldSkipAllPostFilterPlugins() { + return nil, fwk.NewStatus(fwk.Unschedulable, "All PostFilter plugins are skipped") + } + logger := klog.FromContext(ctx) verboseLogs := logger.V(4).Enabled() if verboseLogs { @@ -1270,6 +1334,8 @@ func (f *frameworkImpl) runPreScorePlugin(ctx context.Context, pl fwk.PreScorePl // It returns a list that stores scores from each plugin and total score for each Node. // It also returns *Status, which is set to non-success if any of the plugins returns // a non-success status. +// +// This function mostly duplicates RunPlacementScorePlugins. Any changes to it should likely be reflected in both places. func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodes []fwk.NodeInfo) (ns []fwk.NodePluginScores, status *fwk.Status) { startTime := time.Now() defer func() { @@ -1288,7 +1354,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleStat } ctx, cancel := context.WithCancel(ctx) defer cancel() - errCh := parallelize.NewErrorChannel() + errCh := parallelize.NewResultChannel[error]() if len(plugins) > 0 { logger := klog.FromContext(ctx) @@ -1313,7 +1379,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleStat s, status := f.runScorePlugin(ctx, pl, state, pod, nodeInfo) if !status.IsSuccess() { err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError()) - errCh.SendErrorWithCancel(err, cancel) + errCh.SendWithCancel(err, cancel) return } pluginToNodeScores[pl.Name()][index] = fwk.NodeScore{ @@ -1322,7 +1388,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleStat } } }, metrics.Score) - if err := errCh.ReceiveError(); err != nil { + if err := errCh.Receive(); err != nil { return nil, fwk.AsStatus(fmt.Errorf("running Score plugins: %w", err)) } } @@ -1337,11 +1403,11 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleStat status := f.runScoreExtension(ctx, pl, state, pod, nodeScoreList) if !status.IsSuccess() { err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError()) - errCh.SendErrorWithCancel(err, cancel) + errCh.SendWithCancel(err, cancel) return } }, metrics.Score) - if err := errCh.ReceiveError(); err != nil { + if err := errCh.Receive(); err != nil { return nil, fwk.AsStatus(fmt.Errorf("running Normalize on Score plugins: %w", err)) } @@ -1360,7 +1426,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleStat if score > fwk.MaxNodeScore || score < fwk.MinNodeScore { err := fmt.Errorf("plugin %q returns an invalid score %v, it should in the range of [%v, %v] after normalizing", pl.Name(), score, fwk.MinNodeScore, fwk.MaxNodeScore) - errCh.SendErrorWithCancel(err, cancel) + errCh.SendWithCancel(err, cancel) return } weightedScore := score * int64(weight) @@ -1372,7 +1438,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state fwk.CycleStat } allNodePluginScores[index] = nodePluginScores }, metrics.Score) - if err := errCh.ReceiveError(); err != nil { + if err := errCh.Receive(); err != nil { return nil, fwk.AsStatus(fmt.Errorf("applying score defaultWeights on Score plugins: %w", err)) } @@ -1399,8 +1465,140 @@ func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl fwk.ScorePlugi return status } -func (f *frameworkImpl) GetNodeHint(ctx context.Context, pod *v1.Pod, state fwk.CycleState, cycleCount int64) (hint string, signature fwk.PodSignature) { - return f.batch.GetNodeHint(ctx, pod, state, cycleCount) +// RunPlacementScorePlugins runs the set of configured scoring plugins. +// It returns a list that stores scores from each plugin and total score for each Placement. +// It also returns *Status, which is set to non-success if any of the plugins returns +// a non-success status. +// +// This function mostly duplicates RunScorePlugins. Any changes to it should likely be reflected in both places. +func (f *frameworkImpl) RunPlacementScorePlugins(ctx context.Context, state fwk.PodGroupCycleState, podGroupInfo fwk.PodGroupInfo, podGroupAssignments []*fwk.PodGroupAssignments) (ps []fwk.PlacementPluginScores, status *fwk.Status) { + startTime := time.Now() + defer func() { + metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PlacementScore, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) + }() + + allPlacementPluginScores := make([]fwk.PlacementPluginScores, len(podGroupAssignments)) + numPlugins := len(f.placementScorePlugins) + plugins := make([]fwk.PlacementScorePlugin, 0, numPlugins) + pluginToPlacementScores := make(map[string][]fwk.PlacementScore, numPlugins) + for _, pl := range f.placementScorePlugins { + plugins = append(plugins, pl) + pluginToPlacementScores[pl.Name()] = make([]fwk.PlacementScore, len(podGroupAssignments)) + } + ctx, cancel := context.WithCancel(ctx) + defer cancel() + errCh := parallelize.NewResultChannel[error]() + + if len(plugins) > 0 { + logger := klog.FromContext(ctx) + verboseLogs := logger.V(4).Enabled() + if verboseLogs { + logger = klog.LoggerWithName(logger, "PlacementScore") + } + // Run ScorePlacement method for each placement in parallel. + f.Parallelizer().Until(ctx, len(podGroupAssignments), func(index int) { + pga := podGroupAssignments[index] + logger := logger + if verboseLogs { + logger = klog.LoggerWithValues(logger, "placement", pga.Placement.Name) + } + for _, pl := range plugins { + ctx := ctx + if verboseLogs { + logger := klog.LoggerWithName(logger, pl.Name()) + ctx = klog.NewContext(ctx, logger) + } + s, status := f.runPlacementScorePlugin(ctx, pl, state, podGroupInfo, pga) + if !status.IsSuccess() { + err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError()) + errCh.SendWithCancel(err, cancel) + return + } + pluginToPlacementScores[pl.Name()][index] = fwk.PlacementScore{ + Placement: pga.Placement, + Score: s, + } + } + }, metrics.PlacementScore) + if err := errCh.Receive(); err != nil { + return nil, fwk.AsStatus(fmt.Errorf("running PlacementScore plugins: %w", err)) + } + } + + // Run NormalizePlacementScore method for each PlacementScorePlugin in parallel. + f.Parallelizer().Until(ctx, len(plugins), func(index int) { + pl := plugins[index] + if pl.PlacementScoreExtensions() == nil { + return + } + placementScoreList := pluginToPlacementScores[pl.Name()] + status := f.runPlacementScoreExtension(ctx, pl, state, podGroupInfo, placementScoreList) + if !status.IsSuccess() { + err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError()) + errCh.SendWithCancel(err, cancel) + return + } + }, metrics.PlacementScore) + if err := errCh.Receive(); err != nil { + return nil, fwk.AsStatus(fmt.Errorf("running NormalizePlacementScore on PlacementScore plugins: %w", err)) + } + + // Apply score weight for each PlacementScorePlugin in parallel, + // and then, build allPlacementPluginScores. + f.Parallelizer().Until(ctx, len(podGroupAssignments), func(index int) { + placementPluginScores := fwk.PlacementPluginScores{ + Placement: podGroupAssignments[index].Placement, + Scores: make([]fwk.PluginScore, len(plugins)), + } + + for i, pl := range plugins { + weight := f.placementScorePluginWeight[pl.Name()] + placementScoreList := pluginToPlacementScores[pl.Name()] + score := placementScoreList[index].Score + + if score > fwk.MaxScore || score < fwk.MinScore { + err := fmt.Errorf("plugin %q returns an invalid score %v, it should in the range of [%v, %v] after normalizing", pl.Name(), score, fwk.MinScore, fwk.MaxScore) + errCh.SendWithCancel(err, cancel) + return + } + weightedScore := score * int64(weight) + placementPluginScores.Scores[i] = fwk.PluginScore{ + Name: pl.Name(), + Score: weightedScore, + } + placementPluginScores.TotalScore += weightedScore + } + allPlacementPluginScores[index] = placementPluginScores + }, metrics.PlacementScore) + if err := errCh.Receive(); err != nil { + return nil, fwk.AsStatus(fmt.Errorf("applying score defaultWeights on PlacementScore plugins: %w", err)) + } + + return allPlacementPluginScores, nil +} + +func (f *frameworkImpl) runPlacementScorePlugin(ctx context.Context, pl fwk.PlacementScorePlugin, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placement *fwk.PodGroupAssignments) (int64, *fwk.Status) { + if !state.ShouldRecordPluginMetrics() { + return pl.ScorePlacement(ctx, state, podGroup, placement) + } + startTime := time.Now() + s, status := pl.ScorePlacement(ctx, state, podGroup, placement) + f.metricsRecorder.ObservePluginDurationAsync(metrics.PlacementScore, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) + return s, status +} + +func (f *frameworkImpl) runPlacementScoreExtension(ctx context.Context, pl fwk.PlacementScorePlugin, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placementScoreList []fwk.PlacementScore) *fwk.Status { + if !state.ShouldRecordPluginMetrics() { + return pl.PlacementScoreExtensions().NormalizePlacementScore(ctx, state, podGroup, placementScoreList) + } + startTime := time.Now() + status := pl.PlacementScoreExtensions().NormalizePlacementScore(ctx, state, podGroup, placementScoreList) + f.metricsRecorder.ObservePluginDurationAsync(metrics.PlacementScoreExtensionNormalize, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) + return status +} + +func (f *frameworkImpl) GetNodeHint(ctx context.Context, pod *v1.Pod, signature fwk.PodSignature, state fwk.CycleState, cycleCount int64) string { + return f.batch.GetNodeHint(ctx, pod, signature, state, cycleCount) } func (f *frameworkImpl) StoreScheduleResults(ctx context.Context, signature fwk.PodSignature, hintedNode, chosenNode string, otherNodes framework.SortedScoredNodes, cycleCount int64) { @@ -1421,31 +1619,89 @@ func (f *frameworkImpl) RunPreBindPlugins(ctx context.Context, state fwk.CycleSt logger = klog.LoggerWithName(logger, "PreBind") logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) } - for _, pl := range f.preBindPlugins { - if state.GetSkipPreBindPlugins().Has(pl.Name()) { - continue - } + ctx, cancel := context.WithCancelCause(ctx) + defer cancel(errors.New("RunPreBindPlugins has completed")) + statusCh := parallelize.NewResultChannel[*fwk.Status]() + skippedPlugins := state.GetSkipPreBindPlugins() + parallelPlugins := state.GetParallelPreBindPlugins() + + // run operates directly on the plugin. + run := func(pl fwk.PreBindPlugin) *fwk.Status { ctx := ctx if verboseLogs { logger := klog.LoggerWithName(logger, pl.Name()) ctx = klog.NewContext(ctx, logger) } - status = f.runPreBindPlugin(ctx, pl, state, pod, nodeName) - if !status.IsSuccess() { - if status.IsRejected() { - logger.V(4).Info("Pod rejected by PreBind plugin", "pod", klog.KObj(pod), "node", nodeName, "plugin", pl.Name(), "status", status.Message()) - status.SetPlugin(pl.Name()) - return status + plStatus := f.runPreBindPlugin(ctx, pl, state, pod, nodeName) + if !plStatus.IsSuccess() { + if plStatus.IsRejected() { + logger.V(4).Info("Pod rejected by PreBind plugin", "pod", klog.KObj(pod), "node", nodeName, "plugin", pl.Name(), "status", plStatus.Message()) + plStatus.SetPlugin(pl.Name()) + return plStatus } - err := status.AsError() + err := plStatus.AsError() + if errors.Is(err, context.Canceled) { + err = context.Cause(ctx) + } + logger.Error(err, "Plugin failed", "plugin", pl.Name(), "pod", klog.KObj(pod), "node", nodeName) return fwk.AsStatus(fmt.Errorf("running PreBind plugin %q: %w", pl.Name(), err)) } + return nil + } + + pluginGroups := f.getPreBindPluginGroups(state, skippedPlugins, parallelPlugins) + for _, plugins := range pluginGroups { + // If there is just a single plugin in the group, running it in a separate goroutine + // does not provide any benefit. + if len(plugins) == 1 { + if status := run(plugins[0]); status != nil { + return status + } + continue + } + f.Parallelizer().Until(ctx, len(plugins), func(index int) { + if status := run(plugins[index]); !status.IsSuccess() { + statusCh.SendWithCancel(status, func() { + cancel(errors.New("some other PreBind operation failed")) + }) + } + }, metrics.PreBind) + if status := statusCh.Receive(); !status.IsSuccess() { + return status + } } return nil } +// getPreBindPluginGroups chunks the plugins implementing the PreBind extension point +// into groups of plugins that can be run in parallel. +// The returned value consists of non-skipped plugins only. +func (f *frameworkImpl) getPreBindPluginGroups(state fwk.CycleState, skippedPlugins, parallelPlugins sets.Set[string]) [][]fwk.PreBindPlugin { + var groups [][]fwk.PreBindPlugin + var group []fwk.PreBindPlugin + for _, pl := range f.preBindPlugins { + if parallelPlugins.Has(pl.Name()) { + if !skippedPlugins.Has(pl.Name()) { + group = append(group, pl) + } + continue + } + if len(group) > 0 { + groups = append(groups, group) + group = nil + } + if !skippedPlugins.Has(pl.Name()) { + groups = append(groups, []fwk.PreBindPlugin{pl}) + } + } + if len(group) > 0 { + groups = append(groups, group) + } + return groups +} + func (f *frameworkImpl) runPreBindPlugin(ctx context.Context, pl fwk.PreBindPlugin, state fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status { if !state.ShouldRecordPluginMetrics() { return pl.PreBind(ctx, state, pod, nodeName) @@ -1473,6 +1729,7 @@ func (f *frameworkImpl) RunPreBindPreFlights(ctx context.Context, state fwk.Cycl logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) } skipPlugins := sets.New[string]() + parallelPlugins := sets.New[string]() returningStatus := fwk.NewStatus(fwk.Skip) for _, pl := range f.preBindPlugins { ctx := ctx @@ -1480,7 +1737,10 @@ func (f *frameworkImpl) RunPreBindPreFlights(ctx context.Context, state fwk.Cycl logger := klog.LoggerWithName(logger, pl.Name()) ctx = klog.NewContext(ctx, logger) } - status = f.runPreBindPreFlight(ctx, pl, state, pod, nodeName) + result, status := f.runPreBindPreFlight(ctx, pl, state, pod, nodeName) + if result != nil && result.AllowParallel { + parallelPlugins.Insert(pl.Name()) + } switch { case status.Code() == fwk.Error: err := status.AsError() @@ -1497,17 +1757,18 @@ func (f *frameworkImpl) RunPreBindPreFlights(ctx context.Context, state fwk.Cycl } } state.SetSkipPreBindPlugins(skipPlugins) + state.SetParallelPreBindPlugins(parallelPlugins) return returningStatus } -func (f *frameworkImpl) runPreBindPreFlight(ctx context.Context, pl fwk.PreBindPlugin, state fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status { +func (f *frameworkImpl) runPreBindPreFlight(ctx context.Context, pl fwk.PreBindPlugin, state fwk.CycleState, pod *v1.Pod, nodeName string) (*fwk.PreBindPreFlightResult, *fwk.Status) { if !state.ShouldRecordPluginMetrics() { return pl.PreBindPreFlight(ctx, state, pod, nodeName) } startTime := time.Now() - status := pl.PreBindPreFlight(ctx, state, pod, nodeName) + result, status := pl.PreBindPreFlight(ctx, state, pod, nodeName) f.metricsRecorder.ObservePluginDurationAsync(metrics.PreBindPreFlight, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) - return status + return result, status } // RunBindPlugins runs the set of configured bind plugins until one returns a non `Skip` status. @@ -1673,19 +1934,18 @@ func (f *frameworkImpl) runReservePluginUnreserve(ctx context.Context, pl fwk.Re f.metricsRecorder.ObservePluginDurationAsync(metrics.Unreserve, pl.Name(), fwk.Success.String(), metrics.SinceInSeconds(startTime)) } -// RunPermitPlugins runs the set of configured permit plugins. If any of these +// RunPermitPlugins runs the set of configured Permit plugins. If any of these // plugins returns a status other than "Success" or "Wait", it does not continue // running the remaining plugins and returns an error. Otherwise, if any of the -// plugins returns "Wait", then this function will create and add waiting pod -// to a map of currently waiting pods and return status with "Wait" code. -// Pod will remain waiting pod for the minimum duration returned by the permit plugins. -func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (status *fwk.Status) { +// plugins returns "Wait", then this function will construct the pluginsWaitTime and return status with "Wait" code. +// This function itself will NOT create a waiting pod object and the caller should call AddWaitingPod method to do this. +func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (pluginsWaitTime map[string]time.Duration, status *fwk.Status) { startTime := time.Now() defer func() { metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Permit, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) }() - pluginsWaitTime := make(map[string]time.Duration) - statusCode := fwk.Success + var waitStatus *fwk.Status + pluginsWaitTime = make(map[string]time.Duration) logger := klog.FromContext(ctx) verboseLogs := logger.V(4).Enabled() if verboseLogs { @@ -1702,7 +1962,7 @@ func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, state fwk.CycleSta if !status.IsSuccess() { if status.IsRejected() { logger.V(4).Info("Pod rejected by plugin", "pod", klog.KObj(pod), "plugin", pl.Name(), "status", status.Message()) - return status.WithPlugin(pl.Name()) + return nil, status.WithPlugin(pl.Name()) } if status.IsWait() { // Not allowed to be greater than maxTimeout. @@ -1710,22 +1970,20 @@ func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, state fwk.CycleSta timeout = maxTimeout } pluginsWaitTime[pl.Name()] = timeout - statusCode = fwk.Wait + waitStatus = status } else { err := status.AsError() logger.Error(err, "Plugin failed", "plugin", pl.Name(), "pod", klog.KObj(pod)) - return fwk.AsStatus(fmt.Errorf("running Permit plugin %q: %w", pl.Name(), err)).WithPlugin(pl.Name()) + return nil, fwk.AsStatus(fmt.Errorf("running Permit plugin %q: %w", pl.Name(), err)).WithPlugin(pl.Name()) } } } - if statusCode == fwk.Wait { - waitingPod := newWaitingPod(pod, pluginsWaitTime) - f.waitingPods.add(waitingPod) - msg := fmt.Sprintf("one or more plugins asked to wait and no plugin rejected pod %q", pod.Name) + if waitStatus.IsWait() { logger.V(4).Info("One or more plugins asked to wait and no plugin rejected pod", "pod", klog.KObj(pod)) - return fwk.NewStatus(fwk.Wait, msg) + waitStatus.AppendReason("one or more plugins asked to wait and no plugin rejected pod") + return pluginsWaitTime, waitStatus } - return nil + return nil, nil } func (f *frameworkImpl) runPermitPlugin(ctx context.Context, pl fwk.PermitPlugin, state fwk.CycleState, pod *v1.Pod, nodeName string) (*fwk.Status, time.Duration) { @@ -1738,6 +1996,55 @@ func (f *frameworkImpl) runPermitPlugin(ctx context.Context, pl fwk.PermitPlugin return status, timeout } +// AddWaitingPod creates a waiting pod instance and adds it to the framework. +// It takes the pluginsWaitTime map returned by the RunPermitPlugins. +// Pod will remain waiting pod for the minimum duration returned by the Permit plugins. +// This method should only be called when RunPermitPlugins returns a Wait status and WaitOnPermit is expected to execute soon. +func (f *frameworkImpl) AddWaitingPod(pod *v1.Pod, pluginsWaitTime map[string]time.Duration) { + waitingPod := newWaitingPod(pod, pluginsWaitTime) + f.waitingPods.add(waitingPod) +} + +// RunPlacementGeneratePlugins runs the set of configured PlacementGeneratePlugins and returns the generated placements. +// If no plugins are defined, the input placement is returned instead. +func (f *frameworkImpl) RunPlacementGeneratePlugins(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, nodes []fwk.NodeInfo) (placements []*fwk.Placement, status *fwk.Status) { + startTime := time.Now() + defer func() { + metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PlacementGenerate, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) + }() + + placement := &fwk.Placement{ + Nodes: nodes, + } + + if len(f.placementGeneratePlugins) == 0 { + return []*fwk.Placement{placement}, nil + } + + plugin := f.placementGeneratePlugins[0] + + result, status := f.runPlacementGeneratePlugin(ctx, plugin, state, podGroup, placement) + if !status.IsSuccess() { + return nil, status.WithPlugin(plugin.Name()) + } + + if len(result.Placements) == 0 { + return nil, fwk.NewStatus(fwk.Unschedulable, "no feasible placements found").WithPlugin(plugin.Name()) + } + + return result.Placements, nil +} + +func (f *frameworkImpl) runPlacementGeneratePlugin(ctx context.Context, pl fwk.PlacementGeneratePlugin, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, parentPlacement *fwk.Placement) (*fwk.GeneratePlacementsResult, *fwk.Status) { + if !state.ShouldRecordPluginMetrics() { + return pl.GeneratePlacements(ctx, state, podGroup, parentPlacement) + } + startTime := time.Now() + placements, status := pl.GeneratePlacements(ctx, state, podGroup, parentPlacement) + f.metricsRecorder.ObservePluginDurationAsync(metrics.PlacementGenerate, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) + return placements, status +} + func (f *frameworkImpl) WillWaitOnPermit(ctx context.Context, pod *v1.Pod) bool { return f.waitingPods.get(pod.UID) != nil } @@ -1791,15 +2098,32 @@ func (f *frameworkImpl) GetWaitingPod(uid types.UID) fwk.WaitingPod { } // RejectWaitingPod rejects a WaitingPod given its UID. -// The returned value indicates if the given pod is waiting or not. +// The returned value indicates if the rejection was successful. func (f *frameworkImpl) RejectWaitingPod(uid types.UID) bool { if waitingPod := f.waitingPods.get(uid); waitingPod != nil { - waitingPod.Reject("", "removed") - return true + return waitingPod.Reject("", "removed") } return false } +// AddPodInPreBind adds a pod to the pods in preBind list. +func (f *frameworkImpl) AddPodInPreBind(uid types.UID, cancel context.CancelCauseFunc) { + f.podsInPreBind.add(uid, cancel) +} + +// GetPodInPreBind returns a pod that is in the binding cycle but before it is bound given its UID. +func (f *frameworkImpl) GetPodInPreBind(uid types.UID) fwk.PodInPreBind { + if bp := f.podsInPreBind.get(uid); bp != nil { + return bp + } + return nil +} + +// RemovePodInPreBind removes a pod from the pods in preBind list. +func (f *frameworkImpl) RemovePodInPreBind(uid types.UID) { + f.podsInPreBind.remove(uid) +} + // HasFilterPlugins returns true if at least one filter plugin is defined. func (f *frameworkImpl) HasFilterPlugins() bool { return len(f.filterPlugins) > 0 @@ -1815,6 +2139,11 @@ func (f *frameworkImpl) HasScorePlugins() bool { return len(f.scorePlugins) > 0 } +// PodGroupPostFilterPlugins returns registered PodGroup PostFilter plugins. +func (f *frameworkImpl) PodGroupPostFilterPlugins() []framework.PodGroupPostFilterPlugin { + return f.podGroupPostFilterPlugins +} + // ListPlugins returns a map of extension point name to plugin names configured at each extension // point. Returns nil if no plugins where configured. func (f *frameworkImpl) ListPlugins() *config.Plugins { @@ -1827,10 +2156,13 @@ func (f *frameworkImpl) ListPlugins() *config.Plugins { for i := 0; i < plugins.Len(); i++ { name := plugins.Index(i).Interface().(fwk.Plugin).Name() p := config.Plugin{Name: name} + // Weights apply only to score and placement score plugins. if extName == "ScorePlugin" { - // Weights apply only to score plugins. p.Weight = int32(f.scorePluginWeight[name]) } + if extName == "PlacementScorePlugin" { + p.Weight = int32(f.placementScorePluginWeight[name]) + } cfgs = append(cfgs, p) } if len(cfgs) > 0 { @@ -1851,7 +2183,7 @@ func (f *frameworkImpl) KubeConfig() *restclient.Config { } // EventRecorder returns an event recorder. -func (f *frameworkImpl) EventRecorder() events.EventRecorder { +func (f *frameworkImpl) EventRecorder() events.EventRecorderLogger { return f.eventRecorder } @@ -1870,9 +2202,9 @@ func (f *frameworkImpl) SharedCSIManager() fwk.CSIManager { return f.sharedCSIManager } -// WorkloadManager returns the WorkloadManager of the framework. -func (f *frameworkImpl) WorkloadManager() fwk.WorkloadManager { - return f.workloadManager +// PodGroupManager returns the PodGroupManager of the framework. +func (f *frameworkImpl) PodGroupManager() fwk.PodGroupManager { + return f.podGroupManager } func (f *frameworkImpl) pluginsNeeded(plugins *config.Plugins) sets.Set[string] { @@ -1931,7 +2263,7 @@ func (f *frameworkImpl) APICacher() fwk.APICacher { return f.apiCacher } -// Used only for tests +// TotalBatchedPods returns the total number of batched pods. Used only for tests func (f *frameworkImpl) TotalBatchedPods() int64 { return f.batch.batchedPods } diff --git a/pkg/scheduler/framework/runtime/framework_test.go b/pkg/scheduler/framework/runtime/framework_test.go index 5bdf95e52f66f..c84ba0a5e6dd4 100644 --- a/pkg/scheduler/framework/runtime/framework_test.go +++ b/pkg/scheduler/framework/runtime/framework_test.go @@ -25,22 +25,29 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" v1 "k8s.io/api/core/v1" + "k8s.io/api/scheduling/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" clientsetfake "k8s.io/client-go/kubernetes/fake" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2/ktesting" fwk "k8s.io/kube-scheduler/framework" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/backend/cache" internalqueue "k8s.io/kubernetes/pkg/scheduler/backend/queue" "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" "k8s.io/kubernetes/pkg/scheduler/metrics" + st "k8s.io/kubernetes/pkg/scheduler/testing" "k8s.io/utils/ptr" ) @@ -51,6 +58,7 @@ const ( scoreWithNormalizePlugin2 = "score-with-normalize-plugin-2" scorePlugin1 = "score-plugin-1" scorePlugin2 = "score-plugin-2" + placementScorePlugin1 = "placement-score-plugin-1" pluginNotImplementingScore = "plugin-not-implementing-score" preFilterPluginName = "prefilter-plugin" preFilterWithExtensionsPluginName = "prefilter-with-extensions-plugin" @@ -59,6 +67,8 @@ const ( permitPlugin = "permit-plugin" bindPlugin = "bind-plugin" testCloseErrorPlugin = "test-close-error-plugin" + placementGeneratePlugin = "placement-generate-plugin" + defaultPreemptionPlugin = names.DefaultPreemption testProfileName = "test-profile" testPercentageOfNodesToScore = 35 @@ -121,6 +131,12 @@ func newScorePlugin2(_ context.Context, injArgs runtime.Object, f fwk.Handle) (f return &TestScorePlugin{scorePlugin2, inj}, nil } +func newPlacementScorePluginFactory(name string) func(context.Context, runtime.Object, fwk.Handle) (fwk.Plugin, error) { + return func(context.Context, runtime.Object, fwk.Handle) (fwk.Plugin, error) { + return &testPlacementScorePlugin{name: name}, nil + } +} + func newPluginNotImplementingScore(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { return &PluginNotImplementingScore{}, nil } @@ -235,8 +251,8 @@ func (pl *TestPlugin) Reserve(ctx context.Context, state fwk.CycleState, p *v1.P func (pl *TestPlugin) Unreserve(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) { } -func (pl *TestPlugin) PreBindPreFlight(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) *fwk.Status { - return fwk.NewStatus(fwk.Code(pl.inj.PreBindPreFlightStatus), injectReason) +func (pl *TestPlugin) PreBindPreFlight(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) (*fwk.PreBindPreFlightResult, *fwk.Status) { + return &fwk.PreBindPreFlightResult{AllowParallel: false}, fwk.NewStatus(fwk.Code(pl.inj.PreBindPreFlightStatus), injectReason) } func (pl *TestPlugin) PreBind(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) *fwk.Status { @@ -247,13 +263,29 @@ func (pl *TestPlugin) PostBind(ctx context.Context, state fwk.CycleState, p *v1. } func (pl *TestPlugin) Permit(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) (*fwk.Status, time.Duration) { - return fwk.NewStatus(fwk.Code(pl.inj.PermitStatus), injectReason), time.Duration(0) + return fwk.NewStatus(fwk.Code(pl.inj.PermitStatus), injectReason), pl.inj.PermitTimeout } func (pl *TestPlugin) Bind(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) *fwk.Status { return fwk.NewStatus(fwk.Code(pl.inj.BindStatus), injectReason) } +func (pl *TestPlugin) GeneratePlacements(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, parentPlacement *fwk.Placement) (*fwk.GeneratePlacementsResult, *fwk.Status) { + return &fwk.GeneratePlacementsResult{Placements: pl.inj.GeneratePlacementsResult}, fwk.NewStatus(fwk.Code(pl.inj.GeneratePlacementsStatus), injectReason) +} + +func (pl *TestPlugin) ScorePlacement(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placement *fwk.PodGroupAssignments) (int64, *fwk.Status) { + return 0, fwk.NewStatus(fwk.Code(pl.inj.PlacementScoreStatus), injectReason) +} + +func (pl *TestPlugin) PlacementScoreExtensions() fwk.PlacementScoreExtensions { + return nil +} + +func (pl *TestPlugin) PodGroupPostFilter(ctx context.Context, pg *v1alpha2.PodGroup, pods []*v1.Pod, pgSchedulingFunc func(ctx context.Context) *fwk.Status) *fwk.Status { + return nil +} + func newTestCloseErrorPlugin(_ context.Context, injArgs runtime.Object, f fwk.Handle) (fwk.Plugin, error) { return &TestCloseErrorPlugin{name: testCloseErrorPlugin}, nil } @@ -402,6 +434,28 @@ func (t TestBindPlugin) Bind(ctx context.Context, state fwk.CycleState, p *v1.Po return nil } +// TestPlacementGeneratePlugin only implements GeneratePlacements extension point. +type TestPlacementGeneratePlugin struct { + name string + inj injectedResult +} + +func (pl *TestPlacementGeneratePlugin) Name() string { + return pl.name +} + +func (pl *TestPlacementGeneratePlugin) GeneratePlacements(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, parentPlacement *fwk.Placement) (*fwk.GeneratePlacementsResult, *fwk.Status) { + return &fwk.GeneratePlacementsResult{Placements: pl.inj.GeneratePlacementsResult}, fwk.NewStatus(fwk.Code(pl.inj.GeneratePlacementsStatus), injectReason) +} + +func newTestPlacementGeneratePlugin(_ context.Context, injArgs runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + var inj injectedResult + if err := DecodeInto(injArgs, &inj); err != nil { + return nil, err + } + return &TestPlacementGeneratePlugin{placementGeneratePlugin, inj}, nil +} + // nolint:errcheck // Ignore the error returned by Register as before var registry = func() Registry { r := make(Registry) @@ -415,6 +469,9 @@ var registry = func() Registry { r.Register(queueSortPlugin, newQueueSortPlugin) r.Register(bindPlugin, newBindPlugin) r.Register(testCloseErrorPlugin, newTestCloseErrorPlugin) + r.Register(placementGeneratePlugin, newTestPlacementGeneratePlugin) + r.Register(placementScorePlugin1, newPlacementScorePluginFactory(placementScorePlugin1)) + r.Register(defaultPreemptionPlugin, newTestPlugin) return r }() @@ -558,6 +615,34 @@ func TestNewFrameworkErrors(t *testing.T) { }, wantErr: "repeated config for plugin", }, + { + name: "more than one PlacementGeneratePlugin", + plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: queueSortPlugin}, + }, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: bindPlugin}, + }, + }, + PlacementGenerate: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: testPlugin}, + {Name: placementGeneratePlugin}, + }, + }, + }, + pluginCfg: []config.PluginConfig{ + {Name: queueSortPlugin}, + {Name: bindPlugin}, + {Name: testPlugin}, + {Name: placementGeneratePlugin}, + }, + wantErr: "at most one placement generate plugin is allowed", + }, } for _, tc := range tests { @@ -577,6 +662,127 @@ func TestNewFrameworkErrors(t *testing.T) { } } +func TestPodGroupPostFilterPlugins(t *testing.T) { + tests := []struct { + name string + plugins *config.Plugins + featureGate bool + wantPodGroupPostFilter bool + }{ + { + name: "should fill pod group post filter with feature gate and default preemption", + plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: queueSortPlugin}, + }, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: bindPlugin}, + }, + }, + PostFilter: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: defaultPreemptionPlugin}, + }, + }, + }, + featureGate: true, + wantPodGroupPostFilter: true, + }, + { + name: "should not fill pod group post filter when feature gate is disabled", + plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: queueSortPlugin}, + }, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: bindPlugin}, + }, + }, + PostFilter: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: defaultPreemptionPlugin}, + }, + }, + }, + featureGate: false, + wantPodGroupPostFilter: false, + }, + { + name: "should not fill pod group post filter when post filter plugin is not default preemption", + plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: queueSortPlugin}, + }, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: bindPlugin}, + }, + }, + PostFilter: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: testPlugin}, + }, + }, + }, + featureGate: false, + wantPodGroupPostFilter: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.featureGate { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.GangScheduling: true, + features.WorkloadAwarePreemption: true, + }) + } + + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + profile := &config.KubeSchedulerProfile{ + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: queueSortPlugin}, + }, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: bindPlugin}, + }, + }, + PostFilter: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: defaultPreemptionPlugin}, + }, + }, + }, + } + f, _ := NewFramework(ctx, registry, profile) + + if tc.wantPodGroupPostFilter && len(f.PodGroupPostFilterPlugins()) != 1 { + t.Errorf("Expected 1 pod group post filter plugin, got %d", len(f.PodGroupPostFilterPlugins())) + } + if !tc.wantPodGroupPostFilter && len(f.PodGroupPostFilterPlugins()) != 0 { + t.Errorf("Expected 0 pod group post filter plugin, got %d", len(f.PodGroupPostFilterPlugins())) + } + }) + } + +} + func TestNewFrameworkMultiPointExpansion(t *testing.T) { tests := []struct { name string @@ -594,17 +800,19 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { }, }, wantPlugins: &config.Plugins{ - QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Score: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 5}}}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Score: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 5}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 5}}}, }, }, { @@ -625,17 +833,23 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin}, }, }, + PlacementScore: config.PluginSet{ + Disabled: []config.Plugin{ + {Name: testPlugin}, + }, + }, }, wantPlugins: &config.Plugins{ - QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, }, }, { @@ -645,6 +859,7 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { Enabled: []config.Plugin{ {Name: testPlugin}, {Name: scorePlugin1}, + {Name: placementScorePlugin1}, }, }, }, @@ -661,11 +876,16 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 1}, {Name: scorePlugin1, Weight: 1}, }}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{ + {Name: testPlugin, Weight: 1}, + {Name: placementScorePlugin1, Weight: 1}, + }}, }, }, { @@ -692,11 +912,13 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 1}, {Name: scorePlugin1, Weight: 1}, }}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}}, }, }, { @@ -730,11 +952,13 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 1}, {Name: scoreWithNormalizePlugin1, Weight: 1}, }}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}}, }, }, { @@ -768,11 +992,13 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 1}, {Name: scoreWithNormalizePlugin1, Weight: 1}, }}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}}, }, }, { @@ -790,6 +1016,11 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 3}, }, }, + PlacementScore: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: testPlugin, Weight: 2}, + }, + }, }, wantPlugins: &config.Plugins{ QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, @@ -804,11 +1035,13 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: scorePlugin1, Weight: 5}, {Name: testPlugin, Weight: 3}, }}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 2}}}, }, }, { @@ -819,6 +1052,7 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: queueSortPlugin}, {Name: bindPlugin}, {Name: scorePlugin1}, + {Name: placementScorePlugin1}, }, Disabled: []config.Plugin{ {Name: "*"}, @@ -834,6 +1068,9 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: scorePlugin1, Weight: 1}, }}, Bind: config.PluginSet{Enabled: []config.Plugin{{Name: bindPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{ + {Name: placementScorePlugin1, Weight: 1}, + }}, }, }, { @@ -880,6 +1117,7 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { Enabled: []config.Plugin{ {Name: testPlugin}, {Name: scorePlugin1}, + {Name: placementScorePlugin1}, }, }, Score: config.PluginSet{ @@ -889,6 +1127,12 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 3}, }, }, + PlacementScore: config.PluginSet{ + Enabled: []config.Plugin{ + {Name: testPlugin, Weight: 2}, + {Name: placementScorePlugin1, Weight: 6}, + }, + }, }, wantPlugins: &config.Plugins{ QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, @@ -904,11 +1148,16 @@ func TestNewFrameworkMultiPointExpansion(t *testing.T) { {Name: testPlugin, Weight: 3}, {Name: scorePlugin2, Weight: 5}, }}, - Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, - PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementGenerate: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, + PlacementScore: config.PluginSet{Enabled: []config.Plugin{ + {Name: testPlugin, Weight: 2}, + {Name: placementScorePlugin1, Weight: 6}, + }}, }, }, } @@ -1094,7 +1343,6 @@ func TestRunPreScorePlugins(t *testing.T) { r := make(Registry) enabled := make([]config.Plugin, len(tt.plugins)) for i, p := range tt.plugins { - p := p enabled[i].Name = p.name if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { return p, nil @@ -1731,7 +1979,6 @@ func TestRunPreFilterPlugins(t *testing.T) { r := make(Registry) enabled := make([]config.Plugin, len(tt.plugins)) for i, p := range tt.plugins { - p := p enabled[i].Name = p.name if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { return p, nil @@ -1826,7 +2073,6 @@ func TestRunPreFilterExtensionRemovePod(t *testing.T) { r := make(Registry) enabled := make([]config.Plugin, len(tt.plugins)) for i, p := range tt.plugins { - p := p enabled[i].Name = p.name if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { return p, nil @@ -1914,7 +2160,6 @@ func TestRunPreFilterExtensionAddPod(t *testing.T) { r := make(Registry) enabled := make([]config.Plugin, len(tt.plugins)) for i, p := range tt.plugins { - p := p enabled[i].Name = p.name if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { return p, nil @@ -2158,9 +2403,10 @@ func TestFilterPlugins(t *testing.T) { func TestPostFilterPlugins(t *testing.T) { tests := []struct { - name string - plugins []*TestPlugin - wantStatus *fwk.Status + name string + skipPlugins bool + plugins []*TestPlugin + wantStatus *fwk.Status }{ { name: "a single plugin makes a Pod schedulable", @@ -2172,6 +2418,21 @@ func TestPostFilterPlugins(t *testing.T) { }, wantStatus: fwk.NewStatus(fwk.Success, injectReason), }, + { + name: "skips all post filters if state has SkipPostFilterPlugins", + skipPlugins: true, + plugins: []*TestPlugin{ + { + name: "TestPlugin1", + inj: injectedResult{PostFilterStatus: int(fwk.Unschedulable)}, + }, + { + name: "TestPlugin2", + inj: injectedResult{PostFilterStatus: int(fwk.Success)}, + }, + }, + wantStatus: fwk.NewStatus(fwk.Unschedulable, "All PostFilter plugins are skipped"), + }, { name: "plugin1 failed to make a Pod schedulable, followed by plugin2 which makes the Pod schedulable", plugins: []*TestPlugin{ @@ -2274,6 +2535,8 @@ func TestPostFilterPlugins(t *testing.T) { defer func() { _ = f.Close() }() + state := framework.NewCycleState() + state.SetSkipAllPostFilterPlugins(tt.skipPlugins) _, gotStatus := f.RunPostFilterPlugins(ctx, state, pod, nil) if diff := cmp.Diff(tt.wantStatus, gotStatus, statusCmpOpts...); diff != "" { @@ -2613,6 +2876,189 @@ func TestPreBindPlugins(t *testing.T) { } } +func TestGetPreBindPluginGroups(t *testing.T) { + tests := []struct { + name string + plugins []fwk.PreBindPlugin + skippedPlugins sets.Set[string] + parallelPlugins sets.Set[string] + expectedGroups [][]string + }{ + { + name: "No plugins", + plugins: []fwk.PreBindPlugin{}, + expectedGroups: nil, + }, + { + name: "Single plugin, not skipped, not parallel", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, + }, + expectedGroups: [][]string{{"p1"}}, + }, + { + name: "Single plugin, skipped", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, + }, + skippedPlugins: sets.New("p1"), + expectedGroups: nil, + }, + { + name: "Single plugin, parallel", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, + }, + parallelPlugins: sets.New("p1"), + expectedGroups: [][]string{{"p1"}}, + }, + { + name: "Single plugin, parallel & skipped", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, + }, + parallelPlugins: sets.New("p1"), + skippedPlugins: sets.New("p1"), + expectedGroups: nil, + }, + { + name: "Multiple plugins, consecutive serial", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, + &TestPlugin{name: "p2"}, + }, + expectedGroups: [][]string{ + {"p1"}, + {"p2"}, + }, + }, + { + name: "Multiple plugins, consecutive parallel", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, + &TestPlugin{name: "p2"}, + }, + parallelPlugins: sets.New("p1", "p2"), + expectedGroups: [][]string{ + {"p1", "p2"}, + }, + }, + { + name: "Multiple plugins, mixed parallel and serial", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, // serial + &TestPlugin{name: "p2"}, // parallel + &TestPlugin{name: "p3"}, // parallel + &TestPlugin{name: "p4"}, // serial + }, + parallelPlugins: sets.New("p2", "p3"), + expectedGroups: [][]string{ + {"p1"}, + {"p2", "p3"}, + {"p4"}, + }, + }, + { + name: "Parallel plugins separated by serial", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, // parallel + &TestPlugin{name: "p2"}, // serial + &TestPlugin{name: "p3"}, // parallel + }, + parallelPlugins: sets.New("p1", "p3"), + expectedGroups: [][]string{ + {"p1"}, + {"p2"}, + {"p3"}, + }, + }, + { + name: "Skipped plugins", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, // serial + &TestPlugin{name: "p2"}, // serial & skipped + &TestPlugin{name: "p3"}, // serial + }, + skippedPlugins: sets.New("p2"), + expectedGroups: [][]string{ + {"p1"}, + {"p3"}, + }, + }, + { + name: "Parallel plugins with one skipped in between", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, // parallel + &TestPlugin{name: "p2"}, // parallel & skipped + &TestPlugin{name: "p3"}, // parallel + }, + parallelPlugins: sets.New("p1", "p2", "p3"), + skippedPlugins: sets.New("p2"), + expectedGroups: [][]string{{"p1", "p3"}}, + }, + { + name: "Parallel plugins with one serial & skipped in between", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, // parallel + &TestPlugin{name: "p2"}, // serial & skipped + &TestPlugin{name: "p3"}, // parallel + }, + parallelPlugins: sets.New("p1", "p3"), + skippedPlugins: sets.New("p2"), + expectedGroups: [][]string{ + {"p1"}, + {"p3"}, + }, + }, + { + name: "Complex mix", + plugins: []fwk.PreBindPlugin{ + &TestPlugin{name: "p1"}, // serial + &TestPlugin{name: "p2"}, // parallel + &TestPlugin{name: "p3"}, // serial & skipped + &TestPlugin{name: "p4"}, // parallel + &TestPlugin{name: "p5"}, // parallel & skipped + &TestPlugin{name: "p6"}, // parallel + &TestPlugin{name: "p7"}, // serial + &TestPlugin{name: "p8"}, // serial + }, + parallelPlugins: sets.New("p2", "p4", "p5", "p6"), + skippedPlugins: sets.New("p3", "p5"), + expectedGroups: [][]string{ + {"p1"}, + {"p2"}, + {"p4", "p6"}, + {"p7"}, + {"p8"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &frameworkImpl{ + preBindPlugins: tt.plugins, + } + state := framework.NewCycleState() + + groups := f.getPreBindPluginGroups(state, tt.skippedPlugins, tt.parallelPlugins) + + var gotGroups [][]string + for _, g := range groups { + var groupNames []string + for _, p := range g { + groupNames = append(groupNames, p.Name()) + } + gotGroups = append(gotGroups, groupNames) + } + + if diff := cmp.Diff(tt.expectedGroups, gotGroups); diff != "" { + t.Errorf("getPreBindPluginGroups() mismatch (-want +got):\n%s", diff) + } + }) + } +} + func TestPreBindPreFlightPlugins(t *testing.T) { tests := []struct { name string @@ -2881,14 +3327,15 @@ func TestReservePlugins(t *testing.T) { func TestPermitPlugins(t *testing.T) { tests := []struct { - name string - plugins []*TestPlugin - want *fwk.Status + name string + plugins []*TestPlugin + wantPluginsWaitTime map[string]time.Duration + wantStatus *fwk.Status }{ { - name: "NilPermitPlugin", - plugins: []*TestPlugin{}, - want: nil, + name: "NilPermitPlugin", + plugins: []*TestPlugin{}, + wantStatus: nil, }, { name: "SuccessPermitPlugin", @@ -2898,7 +3345,7 @@ func TestPermitPlugins(t *testing.T) { inj: injectedResult{PermitStatus: int(fwk.Success)}, }, }, - want: nil, + wantStatus: nil, }, { name: "UnschedulablePermitPlugin", @@ -2908,7 +3355,7 @@ func TestPermitPlugins(t *testing.T) { inj: injectedResult{PermitStatus: int(fwk.Unschedulable)}, }, }, - want: fwk.NewStatus(fwk.Unschedulable, injectReason).WithPlugin("TestPlugin"), + wantStatus: fwk.NewStatus(fwk.Unschedulable, injectReason).WithPlugin("TestPlugin"), }, { name: "ErrorPermitPlugin", @@ -2918,7 +3365,7 @@ func TestPermitPlugins(t *testing.T) { inj: injectedResult{PermitStatus: int(fwk.Error)}, }, }, - want: fwk.AsStatus(fmt.Errorf(`running Permit plugin "TestPlugin": %w`, errInjectedStatus)).WithPlugin("TestPlugin"), + wantStatus: fwk.AsStatus(fmt.Errorf(`running Permit plugin "TestPlugin": %w`, errInjectedStatus)).WithPlugin("TestPlugin"), }, { name: "UnschedulableAndUnresolvablePermitPlugin", @@ -2928,7 +3375,7 @@ func TestPermitPlugins(t *testing.T) { inj: injectedResult{PermitStatus: int(fwk.UnschedulableAndUnresolvable)}, }, }, - want: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, injectReason).WithPlugin("TestPlugin"), + wantStatus: fwk.NewStatus(fwk.UnschedulableAndUnresolvable, injectReason).WithPlugin("TestPlugin"), }, { name: "WaitPermitPlugin", @@ -2937,8 +3384,16 @@ func TestPermitPlugins(t *testing.T) { name: "TestPlugin", inj: injectedResult{PermitStatus: int(fwk.Wait)}, }, + { + name: "TestPlugin2", + inj: injectedResult{PermitStatus: int(fwk.Wait), PermitTimeout: time.Second}, + }, }, - want: fwk.NewStatus(fwk.Wait, `one or more plugins asked to wait and no plugin rejected pod ""`), + wantPluginsWaitTime: map[string]time.Duration{ + "TestPlugin": 0, + "TestPlugin2": time.Second, + }, + wantStatus: fwk.NewStatus(fwk.Wait, "injected status, one or more plugins asked to wait and no plugin rejected pod"), }, { name: "SuccessSuccessPermitPlugin", @@ -2952,7 +3407,7 @@ func TestPermitPlugins(t *testing.T) { inj: injectedResult{PermitStatus: int(fwk.Success)}, }, }, - want: nil, + wantStatus: nil, }, { name: "ErrorAndErrorPlugins", @@ -2966,7 +3421,7 @@ func TestPermitPlugins(t *testing.T) { inj: injectedResult{PermitStatus: int(fwk.Error)}, }, }, - want: fwk.AsStatus(fmt.Errorf(`running Permit plugin "TestPlugin": %w`, errInjectedStatus)).WithPlugin("TestPlugin"), + wantStatus: fwk.AsStatus(fmt.Errorf(`running Permit plugin "TestPlugin": %w`, errInjectedStatus)).WithPlugin("TestPlugin"), }, } @@ -3002,10 +3457,13 @@ func TestPermitPlugins(t *testing.T) { t.Fatalf("fail to create framework: %s", err) } - status := f.RunPermitPlugins(ctx, state, pod, "") - if diff := cmp.Diff(tt.want, status, statusCmpOpts...); diff != "" { + pluginsWaitTime, status := f.RunPermitPlugins(ctx, state, pod, "") + if diff := cmp.Diff(tt.wantStatus, status, statusCmpOpts...); diff != "" { t.Errorf("Wrong status code (-want,+got):\n%s", diff) } + if diff := cmp.Diff(tt.wantPluginsWaitTime, pluginsWaitTime); diff != "" { + t.Errorf("Wrong plugins wait time map (-want,+got):\n%s", diff) + } }) } } @@ -3082,6 +3540,23 @@ func TestRecordingMetrics(t *testing.T) { wantExtensionPoint: "Permit", wantStatus: fwk.Success, }, + { + name: "PlacementGenerate - Success", + action: func(ctx context.Context, f framework.Framework) { + f.RunPlacementGeneratePlugins(ctx, state, nil, []fwk.NodeInfo{framework.NewNodeInfo()}) + }, + inject: injectedResult{GeneratePlacementsResult: []*fwk.Placement{{}}}, + wantExtensionPoint: "PlacementGenerate", + wantStatus: fwk.Success, + }, + { + name: "PlacementScore - Success", + action: func(ctx context.Context, f framework.Framework) { + f.RunPlacementScorePlugins(ctx, state, nil, []*fwk.PodGroupAssignments{{Placement: &fwk.Placement{}}}) + }, + wantExtensionPoint: "PlacementScore", + wantStatus: fwk.Success, + }, { name: "PreFilter - Error", @@ -3141,6 +3616,24 @@ func TestRecordingMetrics(t *testing.T) { wantExtensionPoint: "Permit", wantStatus: fwk.Wait, }, + { + name: "PlacementGenerate - Error", + action: func(ctx context.Context, f framework.Framework) { + f.RunPlacementGeneratePlugins(ctx, state, nil, []fwk.NodeInfo{framework.NewNodeInfo()}) + }, + inject: injectedResult{GeneratePlacementsStatus: int(fwk.Error)}, + wantExtensionPoint: "PlacementGenerate", + wantStatus: fwk.Error, + }, + { + name: "PlacementScore - Error", + action: func(ctx context.Context, f framework.Framework) { + f.RunPlacementScorePlugins(ctx, state, nil, []*fwk.PodGroupAssignments{{Placement: &fwk.Placement{}}}) + }, + inject: injectedResult{PlacementScoreStatus: int(fwk.Error)}, + wantExtensionPoint: "PlacementScore", + wantStatus: fwk.Error, + }, } for _, tt := range tests { @@ -3158,15 +3651,17 @@ func TestRecordingMetrics(t *testing.T) { }) pluginSet := config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}} plugins := &config.Plugins{ - Score: pluginSet, - PreFilter: pluginSet, - Filter: pluginSet, - PreScore: pluginSet, - Reserve: pluginSet, - Permit: pluginSet, - PreBind: pluginSet, - Bind: pluginSet, - PostBind: pluginSet, + Score: pluginSet, + PreFilter: pluginSet, + Filter: pluginSet, + PreScore: pluginSet, + Reserve: pluginSet, + Permit: pluginSet, + PreBind: pluginSet, + Bind: pluginSet, + PostBind: pluginSet, + PlacementGenerate: pluginSet, + PlacementScore: pluginSet, } recorder := metrics.NewMetricsAsyncRecorder(100, time.Nanosecond, ctx.Done()) @@ -3364,8 +3859,16 @@ func TestPermitWaitDurationMetric(t *testing.T) { _ = f.Close() }() - f.RunPermitPlugins(ctx, state, pod, "") - f.WaitOnPermit(ctx, pod) + pluginsWaitTime, status := f.RunPermitPlugins(ctx, state, pod, "") + if status.IsWait() { + f.AddWaitingPod(pod, pluginsWaitTime) + } else if !status.IsSuccess() { + t.Fatalf("Failed to run permit plugins: %v", status) + } + status = f.WaitOnPermit(ctx, pod) + if !status.IsSuccess() && tt.wantRes != "Unschedulable" { + t.Fatalf("Failed to wait on permit: %v", status) + } collectAndComparePermitWaitDuration(t, tt.wantRes) }) @@ -3426,11 +3929,12 @@ func TestWaitOnPermit(t *testing.T) { _ = f.Close() }() - runPermitPluginsStatus := f.RunPermitPlugins(ctx, state, pod, "") + pluginsWaitTime, runPermitPluginsStatus := f.RunPermitPlugins(ctx, state, pod, "") if runPermitPluginsStatus.Code() != fwk.Wait { t.Fatalf("Expected RunPermitPlugins to return status %v, but got %v", fwk.Wait, runPermitPluginsStatus.Code()) } + f.AddWaitingPod(pod, pluginsWaitTime) go tt.action(f) @@ -3566,6 +4070,10 @@ type injectedResult struct { PreBindStatus int `json:"preBindStatus,omitempty"` BindStatus int `json:"bindStatus,omitempty"` PermitStatus int `json:"permitStatus,omitempty"` + PermitTimeout time.Duration `json:"permitTimeout,omitempty"` + GeneratePlacementsResult []*fwk.Placement `json:"generatePlacementsResult,omitempty"` + GeneratePlacementsStatus int `json:"generatePlacementsStatus,omitempty"` + PlacementScoreStatus int `json:"placementScoreStatus,omitempty"` } func setScoreRes(inj injectedResult) (int64, *fwk.Status) { @@ -3660,3 +4168,511 @@ func BuildNodeInfos(nodes []*v1.Node) []fwk.NodeInfo { } return res } + +func TestRunPlacementGeneratePlugins(t *testing.T) { + nodeResources := []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + st.MakeNode().Name("node3").Obj(), + } + nodesInCluster := make([]fwk.NodeInfo, len(nodeResources)) + for i, node := range nodeResources { + nodesInCluster[i] = framework.NewNodeInfo() + nodesInCluster[i].SetNode(node) + } + tests := map[string]struct { + pluginResults []injectedResult + wantPlacements []*fwk.Placement + wantStatusCode fwk.Code + }{ + "When no plugins provided, returns a single placement with initial nodes": { + pluginResults: []injectedResult{}, + wantPlacements: []*fwk.Placement{ + { + Name: "", + Nodes: nodesInCluster, + }, + }, + wantStatusCode: fwk.Success, + }, + "When one plugin provided, returns placements generated by the plugin": { + pluginResults: []injectedResult{ + { + GeneratePlacementsResult: []*fwk.Placement{ + { + Name: "foo", + Nodes: []fwk.NodeInfo{ + nodesInCluster[0], + nodesInCluster[1], + }, + }, + { + Name: "bar", + Nodes: []fwk.NodeInfo{ + nodesInCluster[1], + nodesInCluster[2], + }, + }, + }, + }, + }, + wantPlacements: []*fwk.Placement{ + { + Name: "foo", + Nodes: []fwk.NodeInfo{ + nodesInCluster[0], + nodesInCluster[1], + }, + }, + { + Name: "bar", + Nodes: []fwk.NodeInfo{ + nodesInCluster[1], + nodesInCluster[2], + }, + }, + }, + wantStatusCode: fwk.Success, + }, + "When plugin fails, returns error status": { + pluginResults: []injectedResult{ + {GeneratePlacementsStatus: int(fwk.Error)}, + }, + wantStatusCode: fwk.Error, + }, + "When plugin returns no placements, returns unschedulable": { + pluginResults: []injectedResult{ + {GeneratePlacementsResult: []*fwk.Placement{}}, + }, + wantStatusCode: fwk.Unschedulable, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + r := make(Registry) + pluginSet := config.PluginSet{} + plugins := make([]*TestPlacementGeneratePlugin, len(tt.pluginResults)) + for i, inj := range tt.pluginResults { + pluginName := fmt.Sprintf("plugin[%d]", i) + pluginSet.Enabled = append(pluginSet.Enabled, config.Plugin{Name: pluginName}) + plugins[i] = &TestPlacementGeneratePlugin{ + name: pluginName, + inj: inj, + } + err := r.Register(pluginName, func(ctx context.Context, _ runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { + return plugins[i], nil + }) + if err != nil { + t.Fatalf("failed to register PlacementGeneratePlugin") + } + } + + profile := config.KubeSchedulerProfile{Plugins: &config.Plugins{PlacementGenerate: pluginSet}} + fw, err := newFrameworkWithQueueSortAndBind(ctx, r, profile, WithSnapshotSharedLister(cache.NewEmptySnapshot())) + if err != nil { + t.Fatalf("Unexpected error during calling NewFramework, got %v", err) + } + + result, status := fw.RunPlacementGeneratePlugins(ctx, framework.NewCycleState(), nil, nodesInCluster) + + if status.Code() != tt.wantStatusCode { + t.Errorf("Unexpected status code, want %v, got %v", tt.wantStatusCode, status.Code()) + } + if diff := cmp.Diff(tt.wantPlacements, result, cmp.AllowUnexported(framework.NodeInfo{})); diff != "" { + t.Errorf("Unexpected placements (-want,+got):\n%s", diff) + } + }) + } +} + +type placementScoreResult struct { + score int64 + status *fwk.Status +} + +type testPlacementScorePlugin struct { + name string + weight int32 + results map[*fwk.Placement]placementScoreResult + normalizeFn func(scores []fwk.PlacementScore) *fwk.Status +} + +func (pl *testPlacementScorePlugin) Name() string { + return pl.name +} + +func (pl *testPlacementScorePlugin) ScorePlacement(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placement *fwk.PodGroupAssignments) (int64, *fwk.Status) { + r := pl.results[placement.Placement] + return r.score, r.status +} + +func (pl *testPlacementScorePlugin) PlacementScoreExtensions() fwk.PlacementScoreExtensions { + if pl.normalizeFn == nil { + return nil + } + return pl +} + +func (pl *testPlacementScorePlugin) NormalizePlacementScore(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, scores []fwk.PlacementScore) *fwk.Status { + return pl.normalizeFn(scores) +} + +func TestRunPlacementScorePlugins(t *testing.T) { + // 3 placements, the content doesn't matter as they're not used in the test + placements := []*fwk.Placement{{}, {}, {}} + + tests := []struct { + name string + plugins []testPlacementScorePlugin + wantScore []fwk.PlacementPluginScores + wantStatusCode fwk.Code + }{ + { + name: "all success", + plugins: []testPlacementScorePlugin{ + { + name: "plugin1", + weight: 1, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + }, + { + name: "plugin2", + weight: 2, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: nil}, + placements[2]: {score: 0, status: nil}, + }, + }, + }, + wantScore: []fwk.PlacementPluginScores{ + { + Placement: placements[0], + Scores: []fwk.PluginScore{ + {Name: "plugin1", Score: 0}, + {Name: "plugin2", Score: 0}, + }, + TotalScore: 0, + }, + { + Placement: placements[1], + Scores: []fwk.PluginScore{ + {Name: "plugin1", Score: 50}, + {Name: "plugin2", Score: 100}, + }, + TotalScore: 150, + }, + { + Placement: placements[2], + Scores: []fwk.PluginScore{ + {Name: "plugin1", Score: 100}, + {Name: "plugin2", Score: 0}, + }, + TotalScore: 100, + }, + }, + wantStatusCode: fwk.Success, + }, + { + name: "any error", + plugins: []testPlacementScorePlugin{ + { + name: "plugin1", + weight: 1, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + }, + { + name: "plugin2", + weight: 2, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: fwk.NewStatus(fwk.Error, "error for test")}, + placements[2]: {score: 0, status: nil}, + }, + }, + }, + wantStatusCode: fwk.Error, + }, + { + name: "no plugins", + plugins: []testPlacementScorePlugin{}, + wantScore: []fwk.PlacementPluginScores{ + {Placement: placements[0]}, + {Placement: placements[1]}, + {Placement: placements[2]}, + }, + wantStatusCode: fwk.Success, + }, + { + name: "normalize success", + plugins: []testPlacementScorePlugin{ + { + name: "plugin1", + weight: 1, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + }, + { + name: "plugin2", + weight: 2, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 100, status: nil}, + placements[1]: {score: 200, status: nil}, + placements[2]: {score: 400, status: nil}, + }, + normalizeFn: func(scores []fwk.PlacementScore) *fwk.Status { + for i := range scores { + scores[i].Score = int64(float32(scores[i].Score) / float32(scores[2].Score) * 100) + } + return nil + }, + }}, + wantScore: []fwk.PlacementPluginScores{ + { + Placement: placements[0], + Scores: []fwk.PluginScore{ + {Name: "plugin1", Score: 0}, + {Name: "plugin2", Score: 50}, + }, + TotalScore: 50, + }, + { + Placement: placements[1], + Scores: []fwk.PluginScore{ + {Name: "plugin1", Score: 50}, + {Name: "plugin2", Score: 100}, + }, + TotalScore: 150, + }, + { + Placement: placements[2], + Scores: []fwk.PluginScore{ + {Name: "plugin1", Score: 100}, + {Name: "plugin2", Score: 200}, + }, + TotalScore: 300, + }, + }, + wantStatusCode: fwk.Success, + }, + { + name: "normalize failure", + plugins: []testPlacementScorePlugin{ + { + name: "plugin1", + weight: 1, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + }, + { + name: "plugin2", + weight: 2, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 50, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + normalizeFn: func(scores []fwk.PlacementScore) *fwk.Status { + return fwk.NewStatus(fwk.Error, "error for test") + }, + }}, + wantStatusCode: fwk.Error, + }, + { + name: "plugin result is greater than max score", + plugins: []testPlacementScorePlugin{ + { + name: "plugin1", + weight: 1, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: 101, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + }, + }, + wantStatusCode: fwk.Error, + }, + { + name: "plugin result is less than min score", + plugins: []testPlacementScorePlugin{ + { + name: "plugin1", + weight: 1, + results: map[*fwk.Placement]placementScoreResult{ + placements[0]: {score: 0, status: nil}, + placements[1]: {score: -1, status: nil}, + placements[2]: {score: 100, status: nil}, + }, + }, + }, + wantStatusCode: fwk.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + r := make(Registry) + pluginSet := config.PluginSet{} + for _, p := range tt.plugins { + pluginSet.Enabled = append(pluginSet.Enabled, config.Plugin{Name: p.name, Weight: p.weight}) + err := r.Register(p.name, func(ctx context.Context, _ runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { + return &p, nil + }) + if err != nil { + t.Fatalf("Unexpected error during call to Register, got %v", err) + } + } + profile := config.KubeSchedulerProfile{Plugins: &config.Plugins{PlacementScore: pluginSet}} + fw, err := newFrameworkWithQueueSortAndBind(ctx, r, profile, WithSnapshotSharedLister(cache.NewEmptySnapshot())) + if err != nil { + t.Fatalf("Unexpected error during calling NewFramework, got %v", err) + } + assumedPlacements := make([]*fwk.PodGroupAssignments, len(placements)) + for i := range placements { + assumedPlacements[i] = &fwk.PodGroupAssignments{Placement: placements[i]} + } + + result, status := fw.RunPlacementScorePlugins(ctx, framework.NewCycleState(), nil, assumedPlacements) + if status.Code() != tt.wantStatusCode { + t.Errorf("got status code %s, want %s", status.Code(), tt.wantStatusCode) + } + if diff := cmp.Diff(tt.wantScore, result, cmpopts.EquateEmpty()); diff != "" { + t.Errorf("Unexpected placement score (-want,+got):\n%s", diff) + } + }) + } +} + +func TestPluginEvaluationTotalMetric(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + metrics.PluginEvaluationTotal.Reset() + + registry := Registry{} + + const ( + preFilterPluginName = "plugin-eval-prefilter" + filterPluginNameA = "plugin-eval-filter-a" + filterPluginNameB = "plugin-eval-filter-b" + preScorePluginName = "plugin-eval-prescore" + scorePluginName = "plugin-eval-score" + profileName2 = "test-profile-2" + ) + + preFilterPl := &TestPlugin{name: preFilterPluginName, inj: injectedResult{PreFilterStatus: int(fwk.Success)}} + if err := registry.Register(preFilterPluginName, func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return preFilterPl, nil + }); err != nil { + t.Fatalf("failed to register prefilter plugin %q: %v", preFilterPluginName, err) + } + + filterPlA := &TestPlugin{name: filterPluginNameA, inj: injectedResult{FilterStatus: int(fwk.Success)}} + if err := registry.Register(filterPluginNameA, func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return filterPlA, nil + }); err != nil { + t.Fatalf("failed to register filter plugin %q: %v", filterPluginNameA, err) + } + + filterPlB := &TestPlugin{name: filterPluginNameB, inj: injectedResult{FilterStatus: int(fwk.Success)}} + if err := registry.Register(filterPluginNameB, func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return filterPlB, nil + }); err != nil { + t.Fatalf("failed to register filter plugin %q: %v", filterPluginNameB, err) + } + + preScorePl := &TestPlugin{name: preScorePluginName, inj: injectedResult{PreScoreStatus: int(fwk.Success)}} + if err := registry.Register(preScorePluginName, func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return preScorePl, nil + }); err != nil { + t.Fatalf("failed to register prescore plugin %q: %v", preScorePluginName, err) + } + + scorePl := &TestPlugin{name: scorePluginName, inj: injectedResult{}} + if err := registry.Register(scorePluginName, func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return scorePl, nil + }); err != nil { + t.Fatalf("failed to register score plugin %q: %v", scorePluginName, err) + } + + // Profile 1: exercise PreFilter, Filter, PreScore and Score extension points. + cfgPls1 := &config.Plugins{} + cfgPls1.PreFilter.Enabled = append(cfgPls1.PreFilter.Enabled, config.Plugin{Name: preFilterPluginName}) + cfgPls1.Filter.Enabled = append(cfgPls1.Filter.Enabled, config.Plugin{Name: filterPluginNameA}) + cfgPls1.PreScore.Enabled = append(cfgPls1.PreScore.Enabled, config.Plugin{Name: preScorePluginName}) + cfgPls1.Score.Enabled = append(cfgPls1.Score.Enabled, config.Plugin{Name: scorePluginName}) + profile1 := config.KubeSchedulerProfile{ + SchedulerName: testProfileName, + Plugins: cfgPls1, + } + + f1, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile1, WithSnapshotSharedLister(cache.NewEmptySnapshot())) + if err != nil { + t.Fatalf("failed to create framework (profile=%q): %v", testProfileName, err) + } + defer func() { _ = f1.Close() }() + + state1 := framework.NewCycleState() + if _, st, _ := f1.RunPreFilterPlugins(ctx, state1, pod); st != nil && !st.IsSuccess() { + t.Fatalf("RunPreFilterPlugins returned unexpected status: %v", st) + } + if st := f1.RunFilterPlugins(ctx, state1, pod, nil); st != nil && !st.IsSuccess() { + t.Fatalf("RunFilterPlugins returned unexpected status: %v", st) + } + if st := f1.RunPreScorePlugins(ctx, state1, pod, nil); st != nil && !st.IsSuccess() { + t.Fatalf("RunPreScorePlugins returned unexpected status: %v", st) + } + if _, st := f1.RunScorePlugins(ctx, state1, pod, BuildNodeInfos(nodes)); st != nil && !st.IsSuccess() { + t.Fatalf("RunScorePlugins returned unexpected status: %v", st) + } + + // Profile 2: exercise a different plugin and profile label on Filter. + cfgPls2 := &config.Plugins{} + cfgPls2.Filter.Enabled = append(cfgPls2.Filter.Enabled, config.Plugin{Name: filterPluginNameB}) + profile2 := config.KubeSchedulerProfile{ + SchedulerName: profileName2, + Plugins: cfgPls2, + } + + f2, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile2, WithSnapshotSharedLister(cache.NewEmptySnapshot())) + if err != nil { + t.Fatalf("failed to create framework (profile=%q): %v", profileName2, err) + } + defer func() { _ = f2.Close() }() + + state2 := framework.NewCycleState() + if st := f2.RunFilterPlugins(ctx, state2, pod, nil); st != nil && !st.IsSuccess() { + t.Fatalf("RunFilterPlugins returned unexpected status: %v", st) + } + + want := `# HELP scheduler_plugin_evaluation_total Number of attempts to schedule pods by each plugin and the extension point (available only in PreFilter, Filter, PreScore, and Score). +# TYPE scheduler_plugin_evaluation_total counter +scheduler_plugin_evaluation_total{extension_point="Filter",plugin="plugin-eval-filter-a",profile="test-profile"} 1 +scheduler_plugin_evaluation_total{extension_point="Filter",plugin="plugin-eval-filter-b",profile="test-profile-2"} 1 +scheduler_plugin_evaluation_total{extension_point="PreFilter",plugin="plugin-eval-prefilter",profile="test-profile"} 1 +scheduler_plugin_evaluation_total{extension_point="PreScore",plugin="plugin-eval-prescore",profile="test-profile"} 1 +scheduler_plugin_evaluation_total{extension_point="Score",plugin="plugin-eval-score",profile="test-profile"} 1 +` + if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(want), metrics.PluginEvaluationTotal.Name); err != nil { + t.Fatalf("unexpected plugin_evaluation_total metric output:\n%v", err) + } +} diff --git a/pkg/scheduler/framework/runtime/pods_in_prebind_map.go b/pkg/scheduler/framework/runtime/pods_in_prebind_map.go new file mode 100644 index 0000000000000..41f369e51dacd --- /dev/null +++ b/pkg/scheduler/framework/runtime/pods_in_prebind_map.go @@ -0,0 +1,98 @@ +/* +Copyright 2025 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 runtime + +import ( + "context" + "errors" + "sync" + + "k8s.io/apimachinery/pkg/types" + fwk "k8s.io/kube-scheduler/framework" +) + +// podsInPreBindMap is a thread-safe map of pods currently in the preBind phase. +type podsInPreBindMap struct { + pods map[types.UID]*podInPreBind + mu sync.RWMutex +} + +// NewPodsInPreBindMap creates an empty podsInPreBindMap. +func NewPodsInPreBindMap() *podsInPreBindMap { + return &podsInPreBindMap{ + pods: make(map[types.UID]*podInPreBind), + } +} + +// get returns a pod from the map if it exists. +func (pbm *podsInPreBindMap) get(uid types.UID) *podInPreBind { + pbm.mu.RLock() + defer pbm.mu.RUnlock() + return pbm.pods[uid] +} + +// add adds a pod to map, overwriting existing one. +func (pbm *podsInPreBindMap) add(uid types.UID, cancel context.CancelCauseFunc) { + pbm.mu.Lock() + defer pbm.mu.Unlock() + pbm.pods[uid] = &podInPreBind{cancel: cancel} +} + +// remove removes a pod from the map. +func (pbm *podsInPreBindMap) remove(uid types.UID) { + pbm.mu.Lock() + defer pbm.mu.Unlock() + delete(pbm.pods, uid) +} + +var _ fwk.PodInPreBind = &podInPreBind{} + +// podInPreBind describes a pod in the preBind phase, before the bind was called for a pod. +type podInPreBind struct { + finished bool + canceled bool + cancel context.CancelCauseFunc + mu sync.Mutex +} + +// CancelPod cancels the context running the preBind phase +// for a given pod. +func (bp *podInPreBind) CancelPod(message string) bool { + bp.mu.Lock() + defer bp.mu.Unlock() + if bp.finished { + return false + } + if !bp.canceled { + bp.cancel(errors.New(message)) + } + bp.canceled = true + return true +} + +// MarkPrebound marks the pod as finished with preBind phase +// of binding cycle, making it impossible to cancel +// the binding cycle for it. +func (bp *podInPreBind) MarkPrebound() bool { + bp.mu.Lock() + defer bp.mu.Unlock() + if bp.canceled { + return false + } + bp.finished = true + return true +} diff --git a/pkg/scheduler/framework/runtime/pods_in_prebind_map_test.go b/pkg/scheduler/framework/runtime/pods_in_prebind_map_test.go new file mode 100644 index 0000000000000..add5800f98312 --- /dev/null +++ b/pkg/scheduler/framework/runtime/pods_in_prebind_map_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2025 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 runtime + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2/ktesting" +) + +const ( + testPodUID = types.UID("pod-1") +) + +func TestPodsInBindingMap(t *testing.T) { + tests := []struct { + name string + scenario func(t *testing.T, ctx context.Context, cancel context.CancelCauseFunc, m *podsInPreBindMap) + }{ + { + name: "add and get", + scenario: func(t *testing.T, ctx context.Context, cancel context.CancelCauseFunc, m *podsInPreBindMap) { + m.add(testPodUID, cancel) + pod := m.get(testPodUID) + if pod == nil { + t.Fatalf("expected pod to be in map") + } + }, + }, + { + name: "add, remove and get", + scenario: func(t *testing.T, ctx context.Context, cancel context.CancelCauseFunc, m *podsInPreBindMap) { + m.add(testPodUID, cancel) + m.remove(testPodUID) + if m.get(testPodUID) != nil { + t.Errorf("expected pod to be removed from map") + } + }, + }, + { + name: "Verify CancelPod logic", + scenario: func(t *testing.T, ctx context.Context, cancel context.CancelCauseFunc, m *podsInPreBindMap) { + m.add(testPodUID, cancel) + pod := m.get(testPodUID) + + // First cancel should succeed + if !pod.CancelPod("test cancel") { + t.Errorf("First CancelPod should return true") + } + if ctx.Err() == nil { + t.Errorf("Context should be cancelled") + } + + // Second cancel should also return true + if !pod.CancelPod("test cancel") { + t.Errorf("Second CancelPod should return true") + } + }, + }, + { + name: "Verify MarkBound logic", + scenario: func(t *testing.T, ctx context.Context, cancel context.CancelCauseFunc, m *podsInPreBindMap) { + m.add(testPodUID, cancel) + pod := m.get(testPodUID) + + if !pod.MarkPrebound() { + t.Errorf("MarkBound should return true for fresh pod") + } + if !pod.finished { + t.Errorf("finished should be true") + } + + // Try to cancel after binding + if pod.CancelPod("test cancel") { + t.Errorf("CancelPod should return false after MarkBound") + } + if ctx.Err() != nil { + t.Errorf("Context should NOT be cancelled if MarkBound succeeded first") + } + }, + }, + { + name: "Verify MarkBound fails on cancelled pod", + scenario: func(t *testing.T, ctx context.Context, cancel context.CancelCauseFunc, m *podsInPreBindMap) { + m.add(testPodUID, cancel) + pod := m.get(testPodUID) + + pod.CancelPod("test cancel") + if pod.MarkPrebound() { + t.Errorf("MarkBound should return false on cancelled pod") + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewPodsInPreBindMap() + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancelCause(ctx) + defer cancel(nil) + tt.scenario(t, ctx, cancel, m) + }) + } +} diff --git a/pkg/scheduler/framework/runtime/util_others_test.go b/pkg/scheduler/framework/runtime/util_others_test.go index af5f3d1af53bf..07cb13fa09b25 100644 --- a/pkg/scheduler/framework/runtime/util_others_test.go +++ b/pkg/scheduler/framework/runtime/util_others_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/scheduler/framework/runtime/util_windows_test.go b/pkg/scheduler/framework/runtime/util_windows_test.go index ccb3bca33216f..031c13c04490b 100644 --- a/pkg/scheduler/framework/runtime/util_windows_test.go +++ b/pkg/scheduler/framework/runtime/util_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/scheduler/framework/runtime/waiting_pods_map.go b/pkg/scheduler/framework/runtime/waiting_pods_map.go index dc054ee7f5ded..4bc2bbeb9fb5e 100644 --- a/pkg/scheduler/framework/runtime/waiting_pods_map.go +++ b/pkg/scheduler/framework/runtime/waiting_pods_map.go @@ -75,6 +75,7 @@ type waitingPod struct { pendingPlugins map[string]*time.Timer s chan *fwk.Status mu sync.RWMutex + done bool } var _ fwk.WaitingPod = &waitingPod{} @@ -141,15 +142,26 @@ func (w *waitingPod) Allow(pluginName string) { } // The select clause works as a non-blocking send. - // If there is no receiver, it's a no-op (default case). + // If there is no place in the buffer, it's a no-op (default case). select { case w.s <- fwk.NewStatus(fwk.Success, ""): default: } + w.done = true } // Reject declares the waiting pod unschedulable. -func (w *waitingPod) Reject(pluginName, msg string) { +func (w *waitingPod) Reject(pluginName, msg string) bool { + return w.stopWithStatus(fwk.Unschedulable, pluginName, msg) +} + +// Preempt declares the waiting pod is preempted. Compared to reject it does not mark the pod as unschedulable, +// allowing it to be rescheduled. +func (w *waitingPod) Preempt(pluginName, msg string) bool { + return w.stopWithStatus(fwk.Error, pluginName, msg) +} + +func (w *waitingPod) stopWithStatus(status fwk.Code, pluginName, msg string) bool { w.mu.RLock() defer w.mu.RUnlock() for _, timer := range w.pendingPlugins { @@ -157,9 +169,14 @@ func (w *waitingPod) Reject(pluginName, msg string) { } // The select clause works as a non-blocking send. - // If there is no receiver, it's a no-op (default case). + // If there is no place in the buffer, it's a no-op (default case). select { - case w.s <- fwk.NewStatus(fwk.Unschedulable, msg).WithPlugin(pluginName): + case w.s <- fwk.NewStatus(status, msg).WithPlugin(pluginName): default: } + if w.done { + return false + } + w.done = true + return true } diff --git a/pkg/scheduler/framework/runtime/waiting_pods_map_test.go b/pkg/scheduler/framework/runtime/waiting_pods_map_test.go new file mode 100644 index 0000000000000..8e49952468d8b --- /dev/null +++ b/pkg/scheduler/framework/runtime/waiting_pods_map_test.go @@ -0,0 +1,103 @@ +/* +Copyright 2025 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 runtime + +import ( + "testing" + "time" + + st "k8s.io/kubernetes/pkg/scheduler/testing" +) + +func TestWaitingPodReturnFalseOnAllowed(t *testing.T) { + tests := []struct { + name string + action func(wp *waitingPod) bool + actionName string + }{ + { + name: "Preempt returns false on allowed pod", + action: func(wp *waitingPod) bool { + return wp.Preempt("preemption-plugin", "preempted") + }, + actionName: "Preempt", + }, + { + name: "Reject returns false on allowed pod", + action: func(wp *waitingPod) bool { + return wp.Reject("reject-plugin", "rejected") + }, + actionName: "Reject", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pod := st.MakePod().Name("test-pod").UID("test-uid").Obj() + wp := newWaitingPod(pod, map[string]time.Duration{"plugin1": 10 * time.Second}) + + // 1. Simulate Allow from Plugin + wp.Allow("plugin1") + + // 2. Perform Action + if success := tt.action(wp); success { + t.Errorf("Expected %s to return false (failed), but it returned true (success)", tt.actionName) + } + + // 3. Check what signal the pod received (should be Success from Allow) + select { + case status := <-wp.s: + if !status.IsSuccess() { + t.Errorf("Expected Pod to stay Allowed (Success), but got status: %v", status) + } + default: + t.Fatal("No status received") + } + }) + } +} + +func TestWaitingPodMultipleActions(t *testing.T) { + pod := st.MakePod().Name("test-pod").UID("test-uid").Obj() + wp := newWaitingPod(pod, map[string]time.Duration{"plugin1": 10 * time.Second}) + startWaitOnPermit := make(chan struct{}) + endWaitOnPermit := make(chan struct{}) + + // Simulate WaitOnPermit + go func() { + close(startWaitOnPermit) + <-wp.s + close(endWaitOnPermit) + }() + + <-startWaitOnPermit + // 1. Simulate Allow from Plugin, it should be consumed by WaitOnPermit + wp.Allow("plugin1") + <-endWaitOnPermit + + // 2. Simulate Rejection from Plugin + res := wp.Reject("plugin2", "rejected") + if res { + t.Fatalf("Expected reject to fail, but it succeeded") + } + + // 3. Simulate Preempt from Plugin + res = wp.Preempt("preemption-plugin", "preempted") + if res { + t.Fatalf("Expected preempt to fail, but it succeeded") + } +} diff --git a/pkg/scheduler/framework/types.go b/pkg/scheduler/framework/types.go index 9e92527be172b..30a72742edb29 100644 --- a/pkg/scheduler/framework/types.go +++ b/pkg/scheduler/framework/types.go @@ -26,6 +26,7 @@ import ( "time" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -76,7 +77,7 @@ var ( fwk.ResourceClaim, fwk.ResourceSlice, fwk.DeviceClass, - fwk.Workload, + fwk.PodGroup, } ) @@ -156,7 +157,7 @@ func UnrollWildCardResource() []fwk.ClusterEventWithHint { {Event: fwk.ClusterEvent{Resource: fwk.DeviceClass, ActionType: fwk.All}}, } if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { - events = append(events, fwk.ClusterEventWithHint{Event: fwk.ClusterEvent{Resource: fwk.Workload, ActionType: fwk.All}}) + events = append(events, fwk.ClusterEventWithHint{Event: fwk.ClusterEvent{Resource: fwk.PodGroup, ActionType: fwk.All}}) } return events } @@ -205,6 +206,11 @@ type NodeInfo struct { // DeclaredFeatures is a set of features published by the node DeclaredFeatures ndf.FeatureSet + + // NodeAllocatableDRAClaimStates tracks the state of claims requesting node-allocatable resources + // (resources published in Node.Status.Allocatable like cpu, memory. etc.). + // This is used to enforce sharing policies for these claims on the node. + NodeAllocatableDRAClaimStates map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState } func (n *NodeInfo) GetPods() []fwk.PodInfo { @@ -252,6 +258,10 @@ func (n *NodeInfo) GetNodeDeclaredFeatures() ndf.FeatureSet { return n.DeclaredFeatures } +func (n *NodeInfo) GetNodeAllocatableDRAClaimState() map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState { + return n.NodeAllocatableDRAClaimStates +} + // NodeInfo implements KMetadata, so for example klog.KObjSlice(nodes) works // when nodes is a []*NodeInfo. var _ klog.KMetadata = &NodeInfo{} @@ -290,15 +300,16 @@ func (n *NodeInfo) Snapshot() fwk.NodeInfo { // SnapshotConcrete returns a copy of this node, Except that ImageStates is copied without the Nodes field. func (n *NodeInfo) SnapshotConcrete() *NodeInfo { clone := &NodeInfo{ - node: n.node, - Requested: n.Requested.Clone(), - NonZeroRequested: n.NonZeroRequested.Clone(), - Allocatable: n.Allocatable.Clone(), - UsedPorts: make(fwk.HostPortInfo), - ImageStates: make(map[string]*fwk.ImageStateSummary), - PVCRefCounts: make(map[string]int), - Generation: n.Generation, - DeclaredFeatures: n.DeclaredFeatures.Clone(), + node: n.node, + Requested: n.Requested.Clone(), + NonZeroRequested: n.NonZeroRequested.Clone(), + Allocatable: n.Allocatable.Clone(), + UsedPorts: make(fwk.HostPortInfo), + ImageStates: make(map[string]*fwk.ImageStateSummary), + PVCRefCounts: make(map[string]int), + Generation: n.Generation, + DeclaredFeatures: n.DeclaredFeatures.Clone(), + NodeAllocatableDRAClaimStates: make(map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState), } if len(n.Pods) > 0 { clone.Pods = append([]fwk.PodInfo(nil), n.Pods...) @@ -329,6 +340,9 @@ func (n *NodeInfo) SnapshotConcrete() *NodeInfo { for key, value := range n.PVCRefCounts { clone.PVCRefCounts[key] = value } + for key, value := range n.NodeAllocatableDRAClaimStates { + clone.NodeAllocatableDRAClaimStates[key] = value.Snapshot() + } return clone } @@ -386,6 +400,8 @@ func removeFromSlice(logger klog.Logger, s []fwk.PodInfo, k string) ([]fwk.PodIn removedPod = s[i] // delete the element s[i] = s[len(s)-1] + // clear the reference to prevent potential memory leak + s[len(s)-1] = nil s = s[:len(s)-1] break } @@ -439,6 +455,42 @@ func (n *NodeInfo) update(podInfo fwk.PodInfo, sign int64) { n.updatePVCRefCounts(podInfo.GetPod(), sign > 0) n.Generation = nextGeneration() + + if utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources) { + n.updateNodeAllocatableDRAClaimState(podInfo, sign) + } +} + +// updateNodeAllocatableDRAClaimState updates the NodeInfo based on DRA node allocatable resource claims in the pod. +func (n *NodeInfo) updateNodeAllocatableDRAClaimState(podInfo fwk.PodInfo, sign int64) { + pod := podInfo.GetPod() + + if n.NodeAllocatableDRAClaimStates == nil && len(pod.Status.NodeAllocatableResourceClaimStatuses) > 0 { + n.NodeAllocatableDRAClaimStates = make(map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState, len(pod.Status.NodeAllocatableResourceClaimStatuses)) + } + + for _, claimStatus := range pod.Status.NodeAllocatableResourceClaimStatuses { + resourceClaimNamespacedName := types.NamespacedName{ + Namespace: pod.Namespace, + Name: claimStatus.ResourceClaimName, + } + + if _, exists := n.NodeAllocatableDRAClaimStates[resourceClaimNamespacedName]; !exists { + n.NodeAllocatableDRAClaimStates[resourceClaimNamespacedName] = &fwk.NodeAllocatableDRAClaimState{ + ConsumerPods: sets.New[types.UID](), + } + } + state := n.NodeAllocatableDRAClaimStates[resourceClaimNamespacedName] + + if sign > 0 { + state.ConsumerPods.Insert(pod.UID) + } else { + state.ConsumerPods.Delete(pod.UID) + if state.ConsumerPods.Len() == 0 { + delete(n.NodeAllocatableDRAClaimStates, resourceClaimNamespacedName) + } + } + } } // updateUsedPorts updates the UsedPorts of NodeInfo. @@ -476,7 +528,10 @@ func (n *NodeInfo) SetNode(node *v1.Node) { n.node = node n.Allocatable = NewResource(node.Status.Allocatable) if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) { - n.DeclaredFeatures = ndf.NewFeatureSet(node.Status.DeclaredFeatures...) + // Use TryMap rather than Map here in case the node has unknown features. Since we're only + // concerned with matching known features to the node, there is no risk to discarding + // unknown features. + n.DeclaredFeatures = ndf.DefaultFramework.TryMap(node.Status.DeclaredFeatures) } n.Generation = nextGeneration() } @@ -526,6 +581,12 @@ type QueuedPodInfo struct { // That's why we need to distinguish ConsecutiveErrorsCount for the error status and UnschedulableCount for the unschedulable status. // See https://github.com/kubernetes/kubernetes/issues/128744 for the discussion. ConsecutiveErrorsCount int + // WasFlushedFromUnschedulable tracks whether this pod was most recently moved to activeQ + // by the periodic flush from unschedulablePods due to timeout (rather than by an event). + // This is used to detect if the pod becomes schedulable soon after flush, which may + // indicate queueing hint misconfigurations or event handling bugs. + // This flag is cleared when the pod returns to the queue for any reason. + WasFlushedFromUnschedulable bool // The time when the pod is added to the queue for the first time. The pod may be added // back to the queue multiple times before it's successfully scheduled. // It shouldn't be updated once initialized. It's used to record the e2e scheduling @@ -545,6 +606,8 @@ type QueuedPodInfo struct { // GatingPluginEvents records the events registered by the plugin that gated the Pod at PreEnqueue. // We have it as a cache purpose to avoid re-computing which event(s) might ungate the Pod. GatingPluginEvents []fwk.ClusterEvent + // PodSignature for opportunistic batching + PodSignature fwk.PodSignature } func (pqi *QueuedPodInfo) GetPodInfo() fwk.PodInfo { @@ -610,9 +673,61 @@ func (pqi *QueuedPodInfo) DeepCopy() *QueuedPodInfo { GatingPluginEvents: slices.Clone(pqi.GatingPluginEvents), PendingPlugins: pqi.PendingPlugins.Clone(), ConsecutiveErrorsCount: pqi.ConsecutiveErrorsCount, + PodSignature: pqi.PodSignature, } } +// Update updates the pod in QueuedPodInfo and clears the cached PodSignature, +// since the updated pod may no longer match the signature computed for the previous version. +func (pqi *QueuedPodInfo) Update(pod *v1.Pod) error { + pqi.PodSignature = nil + return pqi.PodInfo.Update(pod) +} + +// ClearRejectorPlugins clears the plugin-related fields that track why a pod +// was rejected in a previous scheduling attempt. +func (pqi *QueuedPodInfo) ClearRejectorPlugins() { + pqi.UnschedulablePlugins.Clear() + pqi.PendingPlugins.Clear() + pqi.GatingPlugin = "" + pqi.GatingPluginEvents = nil +} + +// QueuedPodGroupInfo is a PodGroupInfo wrapper with additional information related to +// the pod group's status in the scheduling queue and stores all queued pods from that pod group. +type QueuedPodGroupInfo struct { + *PodGroupInfo + // QueuedPodInfos are the pod group's pods that are currently queued. + // The order of the pods is deterministic and based on the priority and InitialAttemptTimestamp. + QueuedPodInfos []*QueuedPodInfo +} + +// PodGroupInfo is a wrapper around the PodGroup API object together with a list of pods that belong to the pod group. +// Typically used as an input to pod group scheduling cycle plugins. +type PodGroupInfo struct { + // Namespace is a namespace of this pod group. + Namespace string + // Name is a name of this pod group. + Name string + // UnscheduledPods are pods that are currently being considered for scheduling. + // It can be useful to also retrieve the scheduled (assumed or assigned) pods. + // PodGroupManager.PodGroupState can be used for that. + // The order of the pods is deterministic and based on signature, priority and timestamp. + UnscheduledPods []*v1.Pod +} + +func (pgi *PodGroupInfo) GetName() string { + return pgi.Name +} + +func (pgi *PodGroupInfo) GetNamespace() string { + return pgi.Namespace +} + +func (pgi *PodGroupInfo) GetUnscheduledPods() []*v1.Pod { + return pgi.UnscheduledPods +} + // PodInfo is a wrapper to a Pod with additional pre-computed information to // accelerate processing. This information is typically immutable (e.g., pre-processed // inter-pod affinity selectors). @@ -721,11 +836,14 @@ func (pi *PodInfo) CalculateResource() fwk.PodResource { inPlacePodVerticalScalingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) podLevelResourcesEnabled := utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) inPlacePodLevelResourcesVerticalScalingEnabled := utilfeature.DefaultMutableFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling) + nodeAllocatableResourcesDRAEnabled := utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources) + requests := resourcehelper.PodRequests(pi.Pod, resourcehelper.PodResourcesOptions{ UseStatusResources: inPlacePodVerticalScalingEnabled, InPlacePodLevelResourcesVerticalScalingEnabled: inPlacePodLevelResourcesVerticalScalingEnabled, // SkipPodLevelResources is set to false when PodLevelResources feature is enabled. - SkipPodLevelResources: !podLevelResourcesEnabled, + SkipPodLevelResources: !podLevelResourcesEnabled, + UseDRANodeAllocatableResourceClaimStatus: nodeAllocatableResourcesDRAEnabled, }) isPodLevelResourcesSet := podLevelResourcesEnabled && resourcehelper.IsPodLevelRequestsSet(pi.Pod) nonMissingContainerRequests := getNonMissingContainerRequests(requests, isPodLevelResourcesSet) @@ -735,8 +853,9 @@ func (pi *PodInfo) CalculateResource() fwk.PodResource { UseStatusResources: inPlacePodVerticalScalingEnabled, InPlacePodLevelResourcesVerticalScalingEnabled: inPlacePodLevelResourcesVerticalScalingEnabled, // SkipPodLevelResources is set to false when PodLevelResources feature is enabled. - SkipPodLevelResources: !podLevelResourcesEnabled, - NonMissingContainerRequests: nonMissingContainerRequests, + SkipPodLevelResources: !podLevelResourcesEnabled, + NonMissingContainerRequests: nonMissingContainerRequests, + UseDRANodeAllocatableResourceClaimStatus: nodeAllocatableResourcesDRAEnabled, }) } non0CPU := non0Requests[v1.ResourceCPU] @@ -987,13 +1106,14 @@ func (r *Resource) SetMaxResource(rl v1.ResourceList) { // the returned object. func NewNodeInfo(pods ...*v1.Pod) *NodeInfo { ni := &NodeInfo{ - Requested: &Resource{}, - NonZeroRequested: &Resource{}, - Allocatable: &Resource{}, - Generation: nextGeneration(), - UsedPorts: make(fwk.HostPortInfo), - ImageStates: make(map[string]*fwk.ImageStateSummary), - PVCRefCounts: make(map[string]int), + Requested: &Resource{}, + NonZeroRequested: &Resource{}, + Allocatable: &Resource{}, + Generation: nextGeneration(), + UsedPorts: make(fwk.HostPortInfo), + ImageStates: make(map[string]*fwk.ImageStateSummary), + PVCRefCounts: make(map[string]int), + NodeAllocatableDRAClaimStates: make(map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState), } for _, pod := range pods { ni.AddPod(pod) diff --git a/pkg/scheduler/framework/types_test.go b/pkg/scheduler/framework/types_test.go index fe910a19341e0..87311679bfe34 100644 --- a/pkg/scheduler/framework/types_test.go +++ b/pkg/scheduler/framework/types_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/util/sets" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -30,6 +31,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" ndf "k8s.io/component-helpers/nodedeclaredfeatures" + ndftesting "k8s.io/component-helpers/nodedeclaredfeatures/testing" "k8s.io/klog/v2" fwk "k8s.io/kube-scheduler/framework" "k8s.io/kubernetes/pkg/features" @@ -269,8 +271,9 @@ func TestNewNodeInfo(t *testing.T) { {Protocol: "TCP", Port: 8080}: {}, }, }, - ImageStates: map[string]*fwk.ImageStateSummary{}, - PVCRefCounts: map[string]int{}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{}, + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, Pods: []fwk.PodInfo{ &PodInfo{ Pod: &v1.Pod{ @@ -362,6 +365,8 @@ func TestNewNodeInfo(t *testing.T) { func TestNodeInfoClone(t *testing.T) { nodeName := "test-node" + declaredFeatureSet := ndf.NewFeatureMapper([]string{"A", "B", "C"}).MustMapSorted([]string{"A", "C"}) + tests := []struct { nodeInfo *NodeInfo expected *NodeInfo @@ -378,8 +383,9 @@ func TestNodeInfoClone(t *testing.T) { {Protocol: "TCP", Port: 8080}: {}, }, }, - ImageStates: map[string]*fwk.ImageStateSummary{}, - PVCRefCounts: map[string]int{}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{}, + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, Pods: []fwk.PodInfo{ &PodInfo{ Pod: &v1.Pod{ @@ -468,8 +474,9 @@ func TestNodeInfoClone(t *testing.T) { {Protocol: "TCP", Port: 8080}: {}, }, }, - ImageStates: map[string]*fwk.ImageStateSummary{}, - PVCRefCounts: map[string]int{}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{}, + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, Pods: []fwk.PodInfo{ &PodInfo{ Pod: &v1.Pod{ @@ -548,6 +555,30 @@ func TestNodeInfoClone(t *testing.T) { }, }, }, + { + nodeInfo: &NodeInfo{ + Requested: &Resource{}, + NonZeroRequested: &Resource{}, + Allocatable: &Resource{}, + Generation: 3, + UsedPorts: fwk.HostPortInfo{}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{}, + DeclaredFeatures: declaredFeatureSet.Clone(), + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, + }, + expected: &NodeInfo{ + Requested: &Resource{}, + NonZeroRequested: &Resource{}, + Allocatable: &Resource{}, + Generation: 3, + UsedPorts: fwk.HostPortInfo{}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{}, + DeclaredFeatures: declaredFeatureSet, + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, + }, + }, { nodeInfo: &NodeInfo{ Requested: &Resource{}, @@ -557,7 +588,10 @@ func TestNodeInfoClone(t *testing.T) { UsedPorts: fwk.HostPortInfo{}, ImageStates: map[string]*fwk.ImageStateSummary{}, PVCRefCounts: map[string]int{}, - DeclaredFeatures: ndf.NewFeatureSet("FeatureA", "FeatureB"), + DeclaredFeatures: declaredFeatureSet.Clone(), + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + {Name: "claim1", Namespace: "default"}: {ConsumerPods: sets.New[types.UID]("pod1uid", "pod2uid")}, + }, }, expected: &NodeInfo{ Requested: &Resource{}, @@ -567,7 +601,10 @@ func TestNodeInfoClone(t *testing.T) { UsedPorts: fwk.HostPortInfo{}, ImageStates: map[string]*fwk.ImageStateSummary{}, PVCRefCounts: map[string]int{}, - DeclaredFeatures: ndf.NewFeatureSet("FeatureA", "FeatureB"), + DeclaredFeatures: declaredFeatureSet, + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + {Name: "claim1", Namespace: "default"}: {ConsumerPods: sets.New[types.UID]("pod1uid", "pod2uid")}, + }, }, }, } @@ -744,8 +781,10 @@ func TestNodeInfoAddPod(t *testing.T) { {Protocol: "TCP", Port: 8080}: {}, }, }, - ImageStates: map[string]*fwk.ImageStateSummary{}, - PVCRefCounts: map[string]int{"node_info_cache_test/pvc-1": 2, "node_info_cache_test/pvc-2": 1}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{"node_info_cache_test/pvc-1": 2, "node_info_cache_test/pvc-2": 1}, + DeclaredFeatures: ndf.DefaultFramework.NewFeatureSet(), // Empty FeatureSet. + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, Pods: []fwk.PodInfo{ &PodInfo{ Pod: &v1.Pod{ @@ -994,8 +1033,10 @@ func TestNodeInfoRemovePod(t *testing.T) { {Protocol: "TCP", Port: 8080}: {}, }, }, - ImageStates: map[string]*fwk.ImageStateSummary{}, - PVCRefCounts: map[string]int{"node_info_cache_test/pvc-1": 1}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{"node_info_cache_test/pvc-1": 1}, + DeclaredFeatures: ndf.DefaultFramework.NewFeatureSet(), // Empty FeatureSet. + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, Pods: []fwk.PodInfo{ &PodInfo{ Pod: &v1.Pod{ @@ -1160,8 +1201,10 @@ func TestNodeInfoRemovePod(t *testing.T) { {Protocol: "TCP", Port: 8080}: {}, }, }, - ImageStates: map[string]*fwk.ImageStateSummary{}, - PVCRefCounts: map[string]int{}, + ImageStates: map[string]*fwk.ImageStateSummary{}, + PVCRefCounts: map[string]int{}, + DeclaredFeatures: ndf.DefaultFramework.NewFeatureSet(), // Empty FeatureSet. + NodeAllocatableDRAClaimStates: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, Pods: []fwk.PodInfo{ &PodInfo{ Pod: &v1.Pod{ @@ -1240,11 +1283,13 @@ func TestNodeInfoRemovePod(t *testing.T) { } func TestSetNodeDeclaredFeatures(t *testing.T) { + ndfFramework, _ := ndftesting.NewMockFramework(t, "FeatureA", "FeatureB") + ndftesting.SetFrameworkDuringTest(t, ndfFramework) tests := []struct { name string featureGateEnabled bool nodeStatus v1.NodeStatus - expectedFeatures ndf.FeatureSet + expectedFeatures []string }{ { name: "Feature gate disabled", @@ -1252,7 +1297,7 @@ func TestSetNodeDeclaredFeatures(t *testing.T) { nodeStatus: v1.NodeStatus{ DeclaredFeatures: []string{"FeatureA", "FeatureB"}, }, - expectedFeatures: ndf.NewFeatureSet(), + expectedFeatures: nil, }, { name: "Feature gate enabled, node has features", @@ -1260,7 +1305,7 @@ func TestSetNodeDeclaredFeatures(t *testing.T) { nodeStatus: v1.NodeStatus{ DeclaredFeatures: []string{"FeatureA", "FeatureB"}, }, - expectedFeatures: ndf.NewFeatureSet("FeatureA", "FeatureB"), + expectedFeatures: []string{"FeatureA", "FeatureB"}, }, { name: "Feature gate enabled, node has no features", @@ -1268,7 +1313,15 @@ func TestSetNodeDeclaredFeatures(t *testing.T) { nodeStatus: v1.NodeStatus{ DeclaredFeatures: []string{}, }, - expectedFeatures: ndf.NewFeatureSet(), + expectedFeatures: nil, + }, + { + name: "Feature gate enabled, node has an unknown feature", + featureGateEnabled: true, + nodeStatus: v1.NodeStatus{ + DeclaredFeatures: []string{"FeatureA", "OtherFeature"}, + }, + expectedFeatures: []string{"FeatureA"}, }, { name: "Feature gate enabled, node status has nil features", @@ -1276,7 +1329,7 @@ func TestSetNodeDeclaredFeatures(t *testing.T) { nodeStatus: v1.NodeStatus{ DeclaredFeatures: nil, }, - expectedFeatures: ndf.NewFeatureSet(), + expectedFeatures: nil, }, } @@ -1290,8 +1343,23 @@ func TestSetNodeDeclaredFeatures(t *testing.T) { } ni.SetNode(node) gotFeatures := ni.GetNodeDeclaredFeatures() - if !gotFeatures.Equal(tt.expectedFeatures) { - t.Errorf("SetNode() or GetNodeDeclaredFeatures() unexpected result, got: %v, want: %v", gotFeatures, tt.expectedFeatures) + if !tt.featureGateEnabled { + if !gotFeatures.IsEmpty() { + got, err := ndfFramework.Unmap(gotFeatures) + if err != nil { + t.Fatalf("Failed to unmap features: %v", err) + } + t.Errorf("Expected GetNodeDeclaredFeatures() to return nil; got %v", got) + } + return + } + expected := ndfFramework.MustMapSorted(tt.expectedFeatures) + if !gotFeatures.Equal(expected) { + got, err := ndfFramework.Unmap(gotFeatures) + if err != nil { + t.Fatalf("Failed to unmap features: %v", err) + } + t.Errorf("SetNode() or GetNodeDeclaredFeatures() unexpected result, got: %v, want: %v", got, tt.expectedFeatures) } }) } @@ -1439,10 +1507,13 @@ func TestFitError_Error(t *testing.T) { } var ( + cpu100m = resource.MustParse("100m") + mem200M = resource.MustParse("200Mi") cpu500m = resource.MustParse("500m") mem500M = resource.MustParse("500Mi") cpu700m = resource.MustParse("700m") mem800M = resource.MustParse("800Mi") + cpu1000m = resource.MustParse("1000m") cpu1200m = resource.MustParse("1200m") mem1200M = resource.MustParse("1200Mi") restartAlways = v1.ContainerRestartPolicyAlways @@ -1450,12 +1521,15 @@ var ( func TestPodInfoCalculateResources(t *testing.T) { testCases := []struct { - name string - containers []v1.Container - podResources *v1.ResourceRequirements - podLevelResourcesEnabled bool - expectedResource fwk.PodResource - initContainers []v1.Container + name string + containers []v1.Container + podResources *v1.ResourceRequirements + podLevelResourcesEnabled bool + nodeAllocatableResourcesDRAEnabled bool + nodeAllocatableResourceClaimStatuses []v1.NodeAllocatableResourceClaimStatus + expectedResource fwk.PodResource + initContainers []v1.Container + overhead *v1.ResourceList }{ { name: "requestless container", @@ -1680,17 +1754,316 @@ func TestPodInfoCalculateResources(t *testing.T) { Non0Mem: schedutil.DefaultMemoryRequest, }, }, + { + name: "DRA gate disabled, with node allocatable resource claim", + nodeAllocatableResourcesDRAEnabled: false, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem500M, + }, + // We do not set Pod.Spec.ResourceClaims in this test. + // We assume the name maps to Pod.Spec.ResourceClaims[].ResourceClaimName. + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim", + }, + }, + }, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + }, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu500m.MilliValue(), + Memory: mem500M.Value(), + }, + Non0CPU: cpu500m.MilliValue(), + Non0Mem: mem500M.Value(), + }, + }, + { + name: "container with DRA node allocatable resource claim", + nodeAllocatableResourcesDRAEnabled: true, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem500M, + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim", + }, + }, + }, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + }, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu500m.MilliValue() + cpu100m.MilliValue(), + Memory: mem500M.Value() + mem200M.Value(), + }, + Non0CPU: cpu500m.MilliValue() + cpu100m.MilliValue(), + Non0Mem: mem500M.Value() + mem200M.Value(), + }, + }, + { + name: "Multiple DRA node allocatable resource claims", + nodeAllocatableResourcesDRAEnabled: true, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem500M, + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim-1", + }, + { + Name: "node-allocatable-claim-2", + }, + }, + }, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + }, + { + ResourceClaimName: "node-allocatable-claim-2", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + }, + }, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu500m.MilliValue() + cpu100m.MilliValue() + cpu100m.MilliValue(), + Memory: mem500M.Value() + mem200M.Value(), + }, + Non0CPU: cpu500m.MilliValue() + cpu100m.MilliValue() + cpu100m.MilliValue(), + Non0Mem: mem500M.Value() + mem200M.Value(), + }, + }, + { + name: "Single DRA claim with multiple resources", + nodeAllocatableResourcesDRAEnabled: true, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem500M, + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim-1", + }, + }, + }, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu1000m, + v1.ResourceMemory: mem200M, + }, + }, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu500m.MilliValue() + cpu1000m.MilliValue(), + Memory: mem500M.Value() + mem200M.Value(), + }, + Non0CPU: cpu500m.MilliValue() + cpu1000m.MilliValue(), + Non0Mem: mem500M.Value() + mem200M.Value(), + }, + }, + { + name: "DRA node allocatable Resources with Init Container", + nodeAllocatableResourcesDRAEnabled: true, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu1200m, // Higher than app container + v1.ResourceMemory: mem500M, + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim-1", + }, + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem1200M, // Higher than init container + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim-1", + }, + }, + }, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + }, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu1200m.MilliValue() + cpu100m.MilliValue(), // max(cpu500m, cpu1200m) + draCpu + Memory: mem1200M.Value() + mem200M.Value(), // max(mem500M, mem1200M) + draMem + }, + Non0CPU: cpu1200m.MilliValue() + cpu100m.MilliValue(), + Non0Mem: mem1200M.Value() + mem200M.Value(), + }, + }, + { + name: "DRA node allocatable Resources with Pod Level Resources", + nodeAllocatableResourcesDRAEnabled: true, + podLevelResourcesEnabled: true, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem500M, + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim-1", + }, + }, + }, + }, + }, + podResources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu700m, + v1.ResourceMemory: mem800M, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + }, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu700m.MilliValue(), // pod level requests determines the overall footprint + Memory: mem800M.Value(), // pod level requests determines the overall footprint + }, + Non0CPU: cpu700m.MilliValue(), + Non0Mem: mem800M.Value(), + }, + }, + { + name: "DRA node allocatable Resources with Pod Overhead", + nodeAllocatableResourcesDRAEnabled: true, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu500m, + v1.ResourceMemory: mem500M, + }, + Claims: []v1.ResourceClaim{ + { + Name: "node-allocatable-claim-1", + }, + }, + }, + }, + }, + nodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + Resources: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + }, + }, + // Pod Overhead + overhead: &v1.ResourceList{ + v1.ResourceCPU: cpu100m, + v1.ResourceMemory: mem200M, + }, + expectedResource: fwk.PodResource{ + Resource: &Resource{ + MilliCPU: cpu500m.MilliValue() + cpu100m.MilliValue() + cpu100m.MilliValue(), // container + dra + overhead + Memory: mem500M.Value() + mem200M.Value() + mem200M.Value(), // container + dra + overhead + }, + Non0CPU: cpu500m.MilliValue() + cpu100m.MilliValue() + cpu100m.MilliValue(), + Non0Mem: mem500M.Value() + mem200M.Value() + mem200M.Value(), + }, + }, } - for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, tc.podLevelResourcesEnabled) + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.PodLevelResources: tc.podLevelResourcesEnabled, + features.DRANodeAllocatableResources: tc.nodeAllocatableResourcesDRAEnabled, + }) + podSpec := v1.PodSpec{ + Resources: tc.podResources, + Containers: tc.containers, + InitContainers: tc.initContainers, + } + if tc.overhead != nil { + podSpec.Overhead = *tc.overhead + } podInfo := PodInfo{ Pod: &v1.Pod{ - Spec: v1.PodSpec{ - Resources: tc.podResources, - Containers: tc.containers, - InitContainers: tc.initContainers, + Spec: podSpec, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: tc.nodeAllocatableResourceClaimStatuses, }, }, } @@ -2424,3 +2797,199 @@ func TestUpdateUsedPorts_PodRemove(t *testing.T) { } } } + +func TestQueuedPodInfo_UpdateInvalidatesSignature(t *testing.T) { + pod1 := st.MakePod().Name("pod1").Label("version", "1").Obj() + pod2 := pod1.DeepCopy() + pod2.Labels["version"] = "2" + + podInfo, _ := NewPodInfo(pod1) + queuedPodInfo := &QueuedPodInfo{ + PodInfo: podInfo, + PodSignature: fwk.PodSignature("sig-1"), + } + + err := queuedPodInfo.Update(pod2) + if err != nil { + t.Fatalf("Update failed: %v", err) + } + + if queuedPodInfo.PodSignature != nil { + t.Errorf("Expected signature to be nil after Update, got '%s'", string(queuedPodInfo.PodSignature)) + } +} + +func TestUpdateNodeAllocatableDRAClaimState(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRANodeAllocatableResources, true) + claim1NamespacedName := types.NamespacedName{ + Name: "node-allocatable-claim-1", + Namespace: "default", + } + claim2NamespacedName := types.NamespacedName{ + Name: "node-allocatable-claim-2", + Namespace: "default", + } + tests := []struct { + name string + initialState map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState + pod *v1.Pod + sign int64 + expectedState map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState + }{ + { + name: "Add pod with single claim", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Namespace: "default"}, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + {ResourceClaimName: "node-allocatable-claim-1"}, + }, + }, + }, + sign: 1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + }, + { + name: "Add pod with multiple claims", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Namespace: "default"}, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + }, + { + ResourceClaimName: "node-allocatable-claim-2", + }, + }, + }, + }, + sign: 1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + claim2NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + }, + { + name: "Add multiple pods with the same claim", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod2-uid", Namespace: "default"}, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + {ResourceClaimName: "node-allocatable-claim-1"}, + }, + }, + }, + sign: 1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid", "pod2-uid")}, + }, + }, + { + name: "Add multiple pods with different claims", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod2-uid", Namespace: "default"}, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-2", + }, + }, + }, + }, + sign: 1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + claim2NamespacedName: {ConsumerPods: sets.New[types.UID]("pod2-uid")}, + }, + }, + { + name: "Remove pod with single claim", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod1-uid", Namespace: "default"}, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + { + ResourceClaimName: "node-allocatable-claim-1", + }, + }, + }, + }, + sign: -1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{}, + }, + { + name: "Remove pod with shared claim", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid", "pod2-uid")}, + claim2NamespacedName: {ConsumerPods: sets.New[types.UID]("pod2-uid")}, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod2-uid", Namespace: "default"}, + Status: v1.PodStatus{ + NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{ + {ResourceClaimName: "node-allocatable-claim-1"}, + {ResourceClaimName: "node-allocatable-claim-2"}, + }, + }, + }, + sign: -1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + }, + { + name: "Add pod with no claims", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod2-uid", Namespace: "default"}, + }, + sign: 1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + }, + { + name: "Remove pod with no claims", + initialState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{UID: "pod2-uid", Namespace: "default"}, + }, + sign: -1, + expectedState: map[types.NamespacedName]*fwk.NodeAllocatableDRAClaimState{ + claim1NamespacedName: {ConsumerPods: sets.New[types.UID]("pod1-uid")}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ni := &NodeInfo{ + NodeAllocatableDRAClaimStates: tt.initialState, + } + podInfo, _ := NewPodInfo(tt.pod) + ni.updateNodeAllocatableDRAClaimState(podInfo, tt.sign) + + if diff := cmp.Diff(tt.expectedState, ni.NodeAllocatableDRAClaimStates, cmp.AllowUnexported(fwk.NodeAllocatableDRAClaimState{})); diff != "" { + t.Errorf("updateNodeAllocatableDRAClaimState() returned diff (-want +got):\\n%s", diff) + } + }) + } +} diff --git a/pkg/scheduler/metrics/metric_recorder.go b/pkg/scheduler/metrics/metric_recorder.go index f59ea1e96f701..8377ce3c15225 100644 --- a/pkg/scheduler/metrics/metric_recorder.go +++ b/pkg/scheduler/metrics/metric_recorder.go @@ -138,6 +138,12 @@ func NewMetricsAsyncRecorder(bufferSize int, interval time.Duration, stopCh <-ch return recorder } +// ObserveFrameworkExtensionPointDurationAsync observes the framework_extension_point_duration_seconds metric. +// The metric will be flushed to Prometheus asynchronously. +func (r *MetricAsyncRecorder) ObserveFrameworkExtensionPointDurationAsync(extensionPoint, status, profileName string, value float64) { + r.observeMetricAsync(FrameworkExtensionPointDuration, value, extensionPoint, status, profileName) +} + // ObservePluginDurationAsync observes the plugin_execution_duration_seconds metric. // The metric will be flushed to Prometheus asynchronously. func (r *MetricAsyncRecorder) ObservePluginDurationAsync(extensionPoint, pluginName, status string, value float64) { diff --git a/pkg/scheduler/metrics/metrics.go b/pkg/scheduler/metrics/metrics.go index 8fbdc0d98ab64..46b8d51302017 100644 --- a/pkg/scheduler/metrics/metrics.go +++ b/pkg/scheduler/metrics/metrics.go @@ -63,25 +63,29 @@ var ExtensionPoints = []string{ Unreserve, Permit, Sign, + PlacementGenerate, } const ( - PreFilter = "PreFilter" - Filter = "Filter" - PreFilterExtensionAddPod = "PreFilterExtensionAddPod" - PreFilterExtensionRemovePod = "PreFilterExtensionRemovePod" - PostFilter = "PostFilter" - PreScore = "PreScore" - Score = "Score" - ScoreExtensionNormalize = "ScoreExtensionNormalize" - PreBind = "PreBind" - PreBindPreFlight = "PreBindPreFlight" - Bind = "Bind" - PostBind = "PostBind" - Reserve = "Reserve" - Unreserve = "Unreserve" - Permit = "Permit" - Sign = "Sign" + PreFilter = "PreFilter" + Filter = "Filter" + PreFilterExtensionAddPod = "PreFilterExtensionAddPod" + PreFilterExtensionRemovePod = "PreFilterExtensionRemovePod" + PostFilter = "PostFilter" + PreScore = "PreScore" + Score = "Score" + ScoreExtensionNormalize = "ScoreExtensionNormalize" + PreBind = "PreBind" + PreBindPreFlight = "PreBindPreFlight" + Bind = "Bind" + PostBind = "PostBind" + Reserve = "Reserve" + Unreserve = "Unreserve" + Permit = "Permit" + Sign = "Sign" + PlacementGenerate = "PlacementGenerate" + PlacementScore = "PlacementScore" + PlacementScoreExtensionNormalize = "PlacementScoreExtensionNormalize" ) const ( @@ -113,6 +117,14 @@ const ( BatchFlushPodNotBatchable = "pod_not_batchable" ) +// DRADeviceBindingConditions status labels +const ( + BindingConditionsStatusSuccess = "success" + BindingConditionsStatusFailed = "failure" + BindingConditionsStatusTimeout = "timeout" + BindingConditionsStatusError = "error" +) + // All the histogram based metrics have 1ms as size for the smallest bucket. var ( scheduleAttempts *metrics.CounterVec @@ -131,6 +143,7 @@ var ( PodSchedulingSLIDuration *metrics.HistogramVec PodSchedulingAttempts *metrics.Histogram + PodScheduledAfterFlush *metrics.Counter FrameworkExtensionPointDuration *metrics.HistogramVec PluginExecutionDuration *metrics.HistogramVec @@ -155,6 +168,13 @@ var ( // The below is only available when the DRAExtendedResource feature gate is enabled. ResourceClaimCreatesTotal *metrics.CounterVec + podGroupScheduleAttempts *metrics.CounterVec + podGroupSchedulingLatency *metrics.HistogramVec + PodGroupSchedulingAlgorithmLatency *metrics.Histogram + // The below are only available when the DRADeviceBindingConditions feature gate is enabled. + DRABindingConditionsAllocationsTotal *metrics.CounterVec + DRABindingConditionsPreBindDuration *metrics.HistogramVec + // metricsList is a list of all metrics that should be registered always, regardless of any feature gate's value. metricsList []metrics.Registerable ) @@ -185,6 +205,19 @@ func Register() { if utilfeature.DefaultFeatureGate.Enabled(features.DRAExtendedResource) { RegisterMetrics(ResourceClaimCreatesTotal) } + if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + RegisterMetrics( + podGroupScheduleAttempts, + podGroupSchedulingLatency, + PodGroupSchedulingAlgorithmLatency, + ) + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceBindingConditions) { + RegisterMetrics( + DRABindingConditionsAllocationsTotal, + DRABindingConditionsPreBindDuration, + ) + } }) } @@ -259,7 +292,7 @@ func InitMetrics() { Subsystem: SchedulerSubsystem, Name: "goroutines", Help: "Number of running goroutines split by the work they do such as binding.", - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"operation"}) BatchAttemptStats = metrics.NewCounterVec( &metrics.CounterOpts{ @@ -296,6 +329,14 @@ func InitMetrics() { StabilityLevel: metrics.STABLE, }) + PodScheduledAfterFlush = metrics.NewCounter( + &metrics.CounterOpts{ + Subsystem: SchedulerSubsystem, + Name: "pod_scheduled_after_flush_total", + Help: "Number of pods that were successfully scheduled after being flushed from unschedulablePods due to timeout. This metric helps detect potential queueing hint misconfigurations or event handling issues.", + StabilityLevel: metrics.ALPHA, + }) + FrameworkExtensionPointDuration = metrics.NewHistogramVec( &metrics.HistogramOpts{ Subsystem: SchedulerSubsystem, @@ -346,7 +387,7 @@ func InitMetrics() { Name: "permit_wait_duration_seconds", Help: "Duration of waiting on permit.", Buckets: metrics.ExponentialBuckets(0.001, 2, 15), - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"result"}) @@ -363,7 +404,7 @@ func InitMetrics() { Subsystem: SchedulerSubsystem, Name: "unschedulable_pods", Help: "The number of unschedulable pods broken down by plugin name. A pod will increment the gauge for all plugins that caused it to not schedule and so this metric have meaning only when broken down by plugin.", - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"plugin", "profile"}) PluginEvaluationTotal = metrics.NewCounterVec( @@ -371,7 +412,7 @@ func InitMetrics() { Subsystem: SchedulerSubsystem, Name: "plugin_evaluation_total", Help: "Number of attempts to schedule pods by each plugin and the extension point (available only in PreFilter, Filter, PreScore, and Score).", - StabilityLevel: metrics.ALPHA, + StabilityLevel: metrics.BETA, }, []string{"plugin", "extension_point", "profile"}) PreemptionGoroutinesDuration = metrics.NewHistogramVec( @@ -431,6 +472,27 @@ func InitMetrics() { }, []string{"status"}) + DRABindingConditionsAllocationsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: SchedulerSubsystem, + Name: "dra_bindingconditions_allocations_total", + Help: "Number of allocations using devices with BindingConditions, counted per driver per scheduling attempt", + StabilityLevel: metrics.ALPHA, + }, + []string{"profile", "driver", "status"}, + ) + + DRABindingConditionsPreBindDuration = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Subsystem: SchedulerSubsystem, + Name: "dra_bindingconditions_wait_duration_seconds", + Help: "Time in seconds spent waiting for BindingConditions to be satisfied during PreBind.", + Buckets: metrics.ExponentialBuckets(0.1, 2, 14), + StabilityLevel: metrics.ALPHA, + }, + []string{"profile", "driver", "status"}, + ) + GetNodeHintDuration = metrics.NewHistogramVec( &metrics.HistogramOpts{ Subsystem: SchedulerSubsystem, @@ -453,6 +515,31 @@ func InitMetrics() { }, []string{"profile"}) + // The below (podGroupScheduleAttempts, podGroupSchedulingLatency and PodGroupSchedulingAlgorithmLatency) are only available when the GenericWorkload feature gate is enabled. + podGroupScheduleAttempts = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: SchedulerSubsystem, + Name: "podgroup_schedule_attempts_total", + Help: "Number of attempts to schedule pod group, by the result. 'unschedulable' means a pod group could not be scheduled, while 'error' means an internal scheduler problem.", + StabilityLevel: metrics.ALPHA, + }, []string{"result", "profile"}) + podGroupSchedulingLatency = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Subsystem: SchedulerSubsystem, + Name: "podgroup_scheduling_attempt_duration_seconds", + Help: "Pod group scheduling attempt latency in seconds (scheduling algorithm + binding)", + Buckets: metrics.ExponentialBuckets(0.001, 2, 15), // TBD: Correct buckets + StabilityLevel: metrics.ALPHA, + }, []string{"result", "profile"}) + PodGroupSchedulingAlgorithmLatency = metrics.NewHistogram( + &metrics.HistogramOpts{ + Subsystem: SchedulerSubsystem, + Name: "podgroup_scheduling_algorithm_duration_seconds", + Help: "Pod group scheduling algorithm latency in seconds", + Buckets: metrics.ExponentialBuckets(0.001, 2, 15), // TBD: Correct buckets + StabilityLevel: metrics.ALPHA, + }) + metricsList = []metrics.Registerable{ scheduleAttempts, schedulingLatency, @@ -463,6 +550,7 @@ func InitMetrics() { pendingPods, PodSchedulingSLIDuration, PodSchedulingAttempts, + PodScheduledAfterFlush, FrameworkExtensionPointDuration, PluginExecutionDuration, SchedulerQueueIncomingPods, diff --git a/pkg/scheduler/metrics/profile_metrics.go b/pkg/scheduler/metrics/profile_metrics.go index d8e36e9df0a84..16db37c9e6f0d 100644 --- a/pkg/scheduler/metrics/profile_metrics.go +++ b/pkg/scheduler/metrics/profile_metrics.go @@ -19,9 +19,10 @@ package metrics // This file contains helpers for metrics that are associated to a profile. var ( - ScheduledResult = "scheduled" - UnschedulableResult = "unschedulable" - ErrorResult = "error" + ScheduledResult = "scheduled" + UnschedulableResult = "unschedulable" + WaitingOnPreemptionResult = "waiting_on_preemption" + ErrorResult = "error" ) // PodScheduled can records a successful scheduling attempt and the duration @@ -46,3 +47,32 @@ func observeScheduleAttemptAndLatency(result, profile string, duration float64) schedulingLatency.WithLabelValues(result, profile).Observe(duration) scheduleAttempts.WithLabelValues(result, profile).Inc() } + +// PodGroupScheduled can records a successful pod group scheduling attempt and the duration +// since `start`. +func PodGroupScheduled(profile string, duration float64) { + observePodGroupScheduleAttemptAndLatency(ScheduledResult, profile, duration) +} + +// PodGroupUnschedulable can records a pod group scheduling attempt for an unschedulable pod group +// and the duration since `start`. +func PodGroupUnschedulable(profile string, duration float64) { + observePodGroupScheduleAttemptAndLatency(UnschedulableResult, profile, duration) +} + +// PodGroupWaitingOnPreemption can records a pod group scheduling attempt for an unschedulable pod group +// waiting on preemption, and the duration since `start`. +func PodGroupWaitingOnPreemption(profile string, duration float64) { + observePodGroupScheduleAttemptAndLatency(WaitingOnPreemptionResult, profile, duration) +} + +// PodGroupScheduleError records a pod group scheduling attempt that had an error, and the +// duration since `start`. +func PodGroupScheduleError(profile string, duration float64) { + observePodGroupScheduleAttemptAndLatency(ErrorResult, profile, duration) +} + +func observePodGroupScheduleAttemptAndLatency(result, profile string, duration float64) { + podGroupSchedulingLatency.WithLabelValues(result, profile).Observe(duration) + podGroupScheduleAttempts.WithLabelValues(result, profile).Inc() +} diff --git a/pkg/scheduler/profile/profile.go b/pkg/scheduler/profile/profile.go index 3ac39035ee437..3fc38492bc392 100644 --- a/pkg/scheduler/profile/profile.go +++ b/pkg/scheduler/profile/profile.go @@ -32,7 +32,7 @@ import ( ) // RecorderFactory builds an EventRecorder for a given scheduler name. -type RecorderFactory func(string) events.EventRecorder +type RecorderFactory func(string) events.EventRecorderLogger // newProfile builds a Profile for the given configuration. func newProfile(ctx context.Context, cfg config.KubeSchedulerProfile, r frameworkruntime.Registry, recorderFact RecorderFactory, @@ -84,7 +84,7 @@ func (m Map) Close() error { // NewRecorderFactory returns a RecorderFactory for the broadcaster. func NewRecorderFactory(b events.EventBroadcaster) RecorderFactory { - return func(name string) events.EventRecorder { + return func(name string) events.EventRecorderLogger { return b.NewRecorder(scheme.Scheme, name) } } diff --git a/pkg/scheduler/profile/profile_test.go b/pkg/scheduler/profile/profile_test.go index 32dd6117205d6..c3bf28d2a15ce 100644 --- a/pkg/scheduler/profile/profile_test.go +++ b/pkg/scheduler/profile/profile_test.go @@ -296,7 +296,7 @@ func newFakePlugin(name string) func(ctx context.Context, object runtime.Object, } } -func nilRecorderFactory(_ string) events.EventRecorder { +func nilRecorderFactory(_ string) events.EventRecorderLogger { return nil } diff --git a/pkg/scheduler/schedule_one.go b/pkg/scheduler/schedule_one.go index 683bfaed690e0..164eb41f3dd27 100644 --- a/pkg/scheduler/schedule_one.go +++ b/pkg/scheduler/schedule_one.go @@ -41,6 +41,7 @@ import ( "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources" "k8s.io/kubernetes/pkg/scheduler/metrics" "k8s.io/kubernetes/pkg/scheduler/util" utiltrace "k8s.io/utils/trace" @@ -61,19 +62,42 @@ const ( minFeasibleNodesPercentageToFind = 5 ) -// ScheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting. +// ScheduleOne does the entire scheduling workflow for a single scheduling entity (either a pod or a pod group). +// It is serialized on the scheduling algorithm's host fitting. func (sched *Scheduler) ScheduleOne(ctx context.Context) { logger := klog.FromContext(ctx) podInfo, err := sched.NextPod(logger) if err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Error while retrieving next pod from scheduling queue") + utilruntime.HandleErrorWithLogger(logger, err, "Error while retrieving next pod from scheduling queue") return } // pod could be nil when schedulerQueue is closed if podInfo == nil || podInfo.Pod == nil { return } + if sched.genericWorkloadEnabled && podInfo.Pod.Spec.SchedulingGroup != nil { + podGroupInfo, err := sched.podGroupInfoForPod(ctx, podInfo) + if err != nil { + podFwk, err := sched.frameworkForPod(podInfo.Pod) + if err != nil { + // This shouldn't happen, because we only accept for scheduling the pods + // which specify a scheduler name that matches one of the profiles. + klog.FromContext(ctx).Error(err, "Error occurred") + sched.SchedulingQueue.Done(podInfo.Pod.UID) + return + } + sched.FailureHandler(ctx, podFwk, podInfo, fwk.AsStatus(err), nil, time.Now()) + return + } + sched.scheduleOnePodGroup(ctx, podGroupInfo) + } else { + sched.scheduleOnePod(ctx, podInfo) + } +} +// scheduleOnePod does the entire scheduling workflow for a single pod. +func (sched *Scheduler) scheduleOnePod(ctx context.Context, podInfo *framework.QueuedPodInfo) { + logger := klog.FromContext(ctx) pod := podInfo.Pod // TODO(knelasevero): Remove duplicated keys from log entry calls // When contextualized logging hits GA @@ -120,19 +144,29 @@ func (sched *Scheduler) ScheduleOne(ctx context.Context) { } // bind the pod to its host asynchronously (we can do this b/c of the assumption step above). - go func() { - bindingCycleCtx, cancel := context.WithCancel(ctx) - defer cancel() + go sched.runBindingCycle(ctx, state, fwk, scheduleResult, assumedPodInfo, start, podsToActivate) +} - metrics.Goroutines.WithLabelValues(metrics.Binding).Inc() - defer metrics.Goroutines.WithLabelValues(metrics.Binding).Dec() +// runBindingCycle runs a binding cycle algorithm. +func (sched *Scheduler) runBindingCycle( + ctx context.Context, + state fwk.CycleState, + schedFramework framework.Framework, + scheduleResult ScheduleResult, + assumedPodInfo *framework.QueuedPodInfo, + start time.Time, + podsToActivate *framework.PodsToActivate) { + bindingCycleCtx, cancel := context.WithCancel(ctx) + defer cancel() - status := sched.bindingCycle(bindingCycleCtx, state, fwk, scheduleResult, assumedPodInfo, start, podsToActivate) - if !status.IsSuccess() { - sched.handleBindingCycleError(bindingCycleCtx, state, fwk, assumedPodInfo, start, scheduleResult, status) - return - } - }() + metrics.Goroutines.WithLabelValues(metrics.Binding).Inc() + defer metrics.Goroutines.WithLabelValues(metrics.Binding).Dec() + + status := sched.bindingCycle(bindingCycleCtx, state, schedFramework, scheduleResult, assumedPodInfo, start, podsToActivate) + if !status.IsSuccess() { + sched.handleBindingCycleError(bindingCycleCtx, state, schedFramework, assumedPodInfo, start, scheduleResult, status) + return + } } var clearNominatedNode = &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride, NominatedNodeName: ""} @@ -146,22 +180,104 @@ func (sched *Scheduler) schedulingCycle( start time.Time, podsToActivate *framework.PodsToActivate, ) (ScheduleResult, *framework.QueuedPodInfo, *fwk.Status) { - logger := klog.FromContext(ctx) + if err := sched.Cache.UpdateSnapshot(klog.FromContext(ctx), sched.nodeInfoSnapshot); err != nil { + return ScheduleResult{nominatingInfo: clearNominatedNode}, podInfo, fwk.AsStatus(err) + } + + scheduleResult, status := sched.schedulingAlgorithm(ctx, state, schedFramework, podInfo, start) + if !status.IsSuccess() { + return scheduleResult, podInfo, status + } + + assumedPodInfo, status := sched.prepareForBindingCycle(ctx, state, schedFramework, podInfo, podsToActivate, scheduleResult) + if !status.IsSuccess() { + return ScheduleResult{nominatingInfo: clearNominatedNode}, assumedPodInfo, status + } + + return scheduleResult, assumedPodInfo, nil +} + +// prepareForBindingCycle applies the schedule result, generated by the scheduling algorithm, in memory, +// preparing for a binding cycle. +func (sched *Scheduler) prepareForBindingCycle( + ctx context.Context, + state fwk.CycleState, + schedFramework framework.Framework, + podInfo *framework.QueuedPodInfo, + podsToActivate *framework.PodsToActivate, + scheduleResult ScheduleResult, +) (*framework.QueuedPodInfo, *fwk.Status) { + assumedPodInfo, status := sched.assumeAndReserve(ctx, state, schedFramework, podInfo, scheduleResult) + if !status.IsSuccess() { + return assumedPodInfo, status + } + assumedPod := assumedPodInfo.Pod + + // Run "permit" plugins. + pluginsWaitTime, runPermitStatus := schedFramework.RunPermitPlugins(ctx, state, assumedPod, scheduleResult.SuggestedHost) + if runPermitStatus.IsWait() { + schedFramework.AddWaitingPod(assumedPod, pluginsWaitTime) + } else if !runPermitStatus.IsSuccess() { + // trigger un-reserve plugins to clean up state associated with the reserved Pod + err := sched.unreserveAndForget(ctx, state, schedFramework, assumedPodInfo, scheduleResult.SuggestedHost) + if err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "ForgetPod failed") + } + + if runPermitStatus.IsRejected() { + fitErr := &framework.FitError{ + NumAllNodes: 1, + Pod: podInfo.Pod, + Diagnosis: framework.Diagnosis{ + NodeToStatus: framework.NewDefaultNodeToStatus(), + }, + } + fitErr.Diagnosis.NodeToStatus.Set(scheduleResult.SuggestedHost, runPermitStatus) + fitErr.Diagnosis.AddPluginStatus(runPermitStatus) + return assumedPodInfo, fwk.NewStatus(runPermitStatus.Code()).WithError(fitErr) + } + + return assumedPodInfo, runPermitStatus + } + + // At the end of a successful scheduling cycle, pop and move up Pods if needed. + if len(podsToActivate.Map) != 0 { + logger := klog.FromContext(ctx) + sched.SchedulingQueue.Activate(logger, podsToActivate.Map) + // Clear the entries after activation. + podsToActivate.Map = make(map[string]*v1.Pod) + } + + return assumedPodInfo, nil +} + +// schedulingAlgorithm runs fitering and scoring phases for a single pod, +// together with post filter when the pod is unschedulable. +func (sched *Scheduler) schedulingAlgorithm( + ctx context.Context, + state fwk.CycleState, + schedFramework framework.Framework, + podInfo *framework.QueuedPodInfo, + start time.Time, +) (ScheduleResult, *fwk.Status) { + defer func() { + metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInSeconds(start)) + }() + pod := podInfo.Pod - scheduleResult, err := sched.SchedulePod(ctx, schedFramework, state, pod) + + logger := klog.FromContext(ctx) + scheduleResult, err := sched.SchedulePod(ctx, schedFramework, state, podInfo) if err != nil { - defer func() { - metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInSeconds(start)) - }() if err == ErrNoNodesAvailable { status := fwk.NewStatus(fwk.UnschedulableAndUnresolvable).WithError(err) - return ScheduleResult{nominatingInfo: clearNominatedNode}, podInfo, status + return ScheduleResult{nominatingInfo: clearNominatedNode}, status } fitError, ok := err.(*framework.FitError) if !ok { logger.Error(err, "Error selecting node for pod", "pod", klog.KObj(pod)) - return ScheduleResult{nominatingInfo: clearNominatedNode}, podInfo, fwk.AsStatus(err) + return ScheduleResult{nominatingInfo: clearNominatedNode}, fwk.AsStatus(err) } // SchedulePod() may have failed because the pod would not fit on any host, so we try to @@ -171,7 +287,7 @@ func (sched *Scheduler) schedulingCycle( if !schedFramework.HasPostFilterPlugins() { logger.V(3).Info("No PostFilter plugins are registered, so no preemption will be performed") - return ScheduleResult{nominatingInfo: clearNominatedNode}, podInfo, fwk.NewStatus(fwk.Unschedulable).WithError(err) + return ScheduleResult{nominatingInfo: clearNominatedNode}, fwk.NewStatus(fwk.Unschedulable).WithError(err) } // Run PostFilter plugins to attempt to make the pod schedulable in a future scheduling cycle. @@ -188,81 +304,93 @@ func (sched *Scheduler) schedulingCycle( if result != nil { nominatingInfo = result.NominatingInfo } - return ScheduleResult{nominatingInfo: nominatingInfo}, podInfo, fwk.NewStatus(fwk.Unschedulable).WithError(err) + return ScheduleResult{nominatingInfo: nominatingInfo}, fwk.NewStatus(fwk.Unschedulable).WithError(err) } + return scheduleResult, nil +} - metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInSeconds(start)) +// assumeAndReserve assumes and reserves the pod in scheduler's memory. +func (sched *Scheduler) assumeAndReserve( + ctx context.Context, + state fwk.CycleState, + schedFramework framework.Framework, + podInfo *framework.QueuedPodInfo, + scheduleResult ScheduleResult, +) (*framework.QueuedPodInfo, *fwk.Status) { + logger := klog.FromContext(ctx) // Tell the cache to assume that a pod now is running on a given node, even though it hasn't been bound yet. // This allows us to keep scheduling without waiting on binding to occur. assumedPodInfo := podInfo.DeepCopy() assumedPod := assumedPodInfo.Pod // assume modifies `assumedPod` by setting NodeName=scheduleResult.SuggestedHost - err = sched.assume(logger, assumedPod, scheduleResult.SuggestedHost) + err := sched.assume(logger, state, assumedPodInfo, scheduleResult.SuggestedHost) if err != nil { // This is most probably result of a BUG in retrying logic. // We report an error here so that pod scheduling can be retried. // This relies on the fact that Error will check if the pod has been bound // to a node and if so will not add it back to the unscheduled pods queue // (otherwise this would cause an infinite loop). - return ScheduleResult{nominatingInfo: clearNominatedNode}, assumedPodInfo, fwk.AsStatus(err) + return assumedPodInfo, fwk.AsStatus(err) } // Run the Reserve method of reserve plugins. if sts := schedFramework.RunReservePluginsReserve(ctx, state, assumedPod, scheduleResult.SuggestedHost); !sts.IsSuccess() { // trigger un-reserve to clean up state associated with the reserved Pod - schedFramework.RunReservePluginsUnreserve(ctx, state, assumedPod, scheduleResult.SuggestedHost) - if forgetErr := sched.Cache.ForgetPod(logger, assumedPod); forgetErr != nil { - utilruntime.HandleErrorWithContext(ctx, forgetErr, "Scheduler cache ForgetPod failed") + err := sched.unreserveAndForget(ctx, state, schedFramework, assumedPodInfo, scheduleResult.SuggestedHost) + if err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "ForgetPod failed") } if sts.IsRejected() { fitErr := &framework.FitError{ NumAllNodes: 1, - Pod: pod, + Pod: podInfo.Pod, Diagnosis: framework.Diagnosis{ NodeToStatus: framework.NewDefaultNodeToStatus(), }, } fitErr.Diagnosis.NodeToStatus.Set(scheduleResult.SuggestedHost, sts) fitErr.Diagnosis.AddPluginStatus(sts) - return ScheduleResult{nominatingInfo: clearNominatedNode}, assumedPodInfo, fwk.NewStatus(sts.Code()).WithError(fitErr) + return assumedPodInfo, fwk.NewStatus(sts.Code()).WithError(fitErr) } - return ScheduleResult{nominatingInfo: clearNominatedNode}, assumedPodInfo, sts + return assumedPodInfo, sts } + return assumedPodInfo, nil +} - // Run "permit" plugins. - runPermitStatus := schedFramework.RunPermitPlugins(ctx, state, assumedPod, scheduleResult.SuggestedHost) - if !runPermitStatus.IsWait() && !runPermitStatus.IsSuccess() { - // trigger un-reserve to clean up state associated with the reserved Pod - schedFramework.RunReservePluginsUnreserve(ctx, state, assumedPod, scheduleResult.SuggestedHost) - if forgetErr := sched.Cache.ForgetPod(logger, assumedPod); forgetErr != nil { - utilruntime.HandleErrorWithContext(ctx, forgetErr, "Scheduler cache ForgetPod failed") - } +// unreserveAndForget unreserves and forgets the pod from scheduler's memory. +// This function shouldn't be called during binding cycle with a state, where IsPodGroupSchedulingCycle is set to true, +// but this shouldn't happen, because such pods with such state cannot reach binding. +func (sched *Scheduler) unreserveAndForget( + ctx context.Context, + state fwk.CycleState, + schedFramework framework.Framework, + assumedPodInfo *framework.QueuedPodInfo, + nodeName string, +) error { + logger := klog.FromContext(ctx) - if runPermitStatus.IsRejected() { - fitErr := &framework.FitError{ - NumAllNodes: 1, - Pod: pod, - Diagnosis: framework.Diagnosis{ - NodeToStatus: framework.NewDefaultNodeToStatus(), - }, + schedFramework.RunReservePluginsUnreserve(ctx, state, assumedPodInfo.Pod, nodeName) + if state.IsPodGroupSchedulingCycle() { + err := sched.nodeInfoSnapshot.ForgetPod(logger, assumedPodInfo.Pod) + if err != nil { + return err + } + if assumedPodInfo.Pod.Status.NominatedNodeName != "" { + // Assume method removed the nomination, but since we are reverting that stage for pod groups, + // we need to revert that operation as well. + if sched.SchedulingQueue != nil { + nominatingInfo := &fwk.NominatingInfo{ + NominatedNodeName: assumedPodInfo.Pod.Status.NominatedNodeName, + NominatingMode: fwk.ModeOverride, + } + // AssumedPodInfo can be used here, because the whole pod object is not stored in the nominator. + sched.SchedulingQueue.AddNominatedPod(logger, assumedPodInfo.PodInfo, nominatingInfo) } - fitErr.Diagnosis.NodeToStatus.Set(scheduleResult.SuggestedHost, runPermitStatus) - fitErr.Diagnosis.AddPluginStatus(runPermitStatus) - return ScheduleResult{nominatingInfo: clearNominatedNode}, assumedPodInfo, fwk.NewStatus(runPermitStatus.Code()).WithError(fitErr) } - - return ScheduleResult{nominatingInfo: clearNominatedNode}, assumedPodInfo, runPermitStatus - } - - // At the end of a successful scheduling cycle, pop and move up Pods if needed. - if len(podsToActivate.Map) != 0 { - sched.SchedulingQueue.Activate(logger, podsToActivate.Map) - // Clear the entries after activation. - podsToActivate.Map = make(map[string]*v1.Pod) + return nil } - - return scheduleResult, assumedPodInfo, nil + return sched.Cache.ForgetPod(logger, assumedPodInfo.Pod) } // bindingCycle tries to bind an assumed Pod. @@ -278,8 +406,9 @@ func (sched *Scheduler) bindingCycle( assumedPod := assumedPodInfo.Pod + var preFlightStatus *fwk.Status if sched.nominatedNodeNameForExpectationEnabled { - preFlightStatus := schedFramework.RunPreBindPreFlights(ctx, state, assumedPod, scheduleResult.SuggestedHost) + preFlightStatus = schedFramework.RunPreBindPreFlights(ctx, state, assumedPod, scheduleResult.SuggestedHost) if preFlightStatus.Code() == fwk.Error || // Unschedulable status is not supported in PreBindPreFlight and hence we regard it as an error. preFlightStatus.IsRejected() { @@ -323,11 +452,26 @@ func (sched *Scheduler) bindingCycle( // we can free the cluster events stored in the scheduling queue sooner, which is worth for busy clusters memory consumption wise. sched.SchedulingQueue.Done(assumedPod.UID) + // If we are going to run prebind plugins we put the pod in binding map to optimize preemption. + if preFlightStatus.IsSuccess() { + var podInPreBindCancel context.CancelCauseFunc + ctx, podInPreBindCancel = context.WithCancelCause(ctx) + defer podInPreBindCancel(nil) + defer schedFramework.RemovePodInPreBind(assumedPod.UID) + schedFramework.AddPodInPreBind(assumedPod.UID, podInPreBindCancel) + } // Run "prebind" plugins. if status := schedFramework.RunPreBindPlugins(ctx, state, assumedPod, scheduleResult.SuggestedHost); !status.IsSuccess() { return status } + // Verify that pod was not preempted during prebinding. + bindingPod := schedFramework.GetPodInPreBind(assumedPod.UID) + if bindingPod != nil && !bindingPod.MarkPrebound() { + err := context.Cause(ctx) + return fwk.AsStatus(err) + } + // Run "bind" plugins. if status := sched.bind(ctx, schedFramework, assumedPod, scheduleResult.SuggestedHost, state); !status.IsSuccess() { return status @@ -340,6 +484,11 @@ func (sched *Scheduler) bindingCycle( if assumedPodInfo.InitialAttemptTimestamp != nil { metrics.PodSchedulingSLIDuration.WithLabelValues(getAttemptsLabel(assumedPodInfo)).Observe(metrics.SinceInSeconds(*assumedPodInfo.InitialAttemptTimestamp)) } + // Count pods scheduled after being flushed from unschedulablePods + if assumedPodInfo.WasFlushedFromUnschedulable { + logger.V(4).Info("Pod scheduled after flush from unschedulablePods", "pod", klog.KObj(assumedPodInfo.Pod), "unschedulablePlugins", assumedPodInfo.UnschedulablePlugins, "pendingPlugins", assumedPodInfo.PendingPlugins) + metrics.PodScheduledAfterFlush.Inc() + } // Run "postbind" plugins. schedFramework.RunPostBindPlugins(ctx, state, assumedPod, scheduleResult.SuggestedHost) @@ -365,9 +514,8 @@ func (sched *Scheduler) handleBindingCycleError( assumedPod := podInfo.Pod // trigger un-reserve plugins to clean up state associated with the reserved Pod - fwk.RunReservePluginsUnreserve(ctx, state, assumedPod, scheduleResult.SuggestedHost) - if forgetErr := sched.Cache.ForgetPod(logger, assumedPod); forgetErr != nil { - utilruntime.HandleErrorWithContext(ctx, forgetErr, "scheduler cache ForgetPod failed") + if forgetErr := sched.unreserveAndForget(ctx, state, fwk, podInfo, scheduleResult.SuggestedHost); forgetErr != nil { + utilruntime.HandleErrorWithContext(ctx, forgetErr, "ForgetPod failed") } else { // "Forget"ing an assumed Pod in binding cycle should be treated as a PodDelete event, // as the assumed Pod had occupied a certain amount of resources in scheduler cache. @@ -399,8 +547,9 @@ func (sched *Scheduler) frameworkForPod(pod *v1.Pod) (framework.Framework, error func (sched *Scheduler) skipPodSchedule(ctx context.Context, fwk framework.Framework, pod *v1.Pod) bool { // Case 1: pod is being deleted. if pod.DeletionTimestamp != nil { - fwk.EventRecorder().Eventf(pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name) - klog.FromContext(ctx).V(3).Info("Skip schedule deleting pod", "pod", klog.KObj(pod)) + logger := klog.FromContext(ctx) + fwk.EventRecorder().WithLogger(logger).Eventf(pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name) + logger.V(3).Info("Skip schedule deleting pod", "pod", klog.KObj(pod)) return true } @@ -418,19 +567,16 @@ func (sched *Scheduler) skipPodSchedule(ctx context.Context, fwk framework.Frame // schedulePod tries to schedule the given pod to one of the nodes in the node list. // If it succeeds, it will return the name of the node. // If it fails, it will return a FitError with reasons. -func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state fwk.CycleState, pod *v1.Pod) (result ScheduleResult, err error) { +func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state fwk.CycleState, podInfo *framework.QueuedPodInfo) (result ScheduleResult, err error) { + pod := podInfo.Pod trace := utiltrace.New("Scheduling", utiltrace.Field{Key: "namespace", Value: pod.Namespace}, utiltrace.Field{Key: "name", Value: pod.Name}) defer trace.LogIfLong(100 * time.Millisecond) - if err := sched.Cache.UpdateSnapshot(klog.FromContext(ctx), sched.nodeInfoSnapshot); err != nil { - return result, err - } - trace.Step("Snapshotting scheduler cache and node infos done") - if sched.nodeInfoSnapshot.NumNodes() == 0 { + if sched.nodeInfoSnapshot.NumNodesInPlacement() == 0 { return result, ErrNoNodesAvailable } - feasibleNodes, diagnosis, nodeHint, signature, err := sched.findNodesThatFitPod(ctx, fwk, state, pod) + feasibleNodes, diagnosis, nodeHint, err := sched.findNodesThatFitPod(ctx, fwk, state, podInfo) if err != nil { return result, err } @@ -439,7 +585,7 @@ func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework if len(feasibleNodes) == 0 { return result, &framework.FitError{ Pod: pod, - NumAllNodes: sched.nodeInfoSnapshot.NumNodes(), + NumAllNodes: sched.nodeInfoSnapshot.NumNodesInPlacement(), Diagnosis: diagnosis, } } @@ -448,7 +594,7 @@ func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework if len(feasibleNodes) == 1 { node := feasibleNodes[0].Node().Name if utilfeature.DefaultFeatureGate.Enabled(features.OpportunisticBatching) { - fwk.StoreScheduleResults(ctx, signature, nodeHint, node, nil, sched.CurrentCycle()) + fwk.StoreScheduleResults(ctx, podInfo.PodSignature, nodeHint, node, nil, sched.CurrentCycle()) } return ScheduleResult{ SuggestedHost: node, @@ -467,7 +613,7 @@ func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework trace.Step("Prioritizing done") if utilfeature.DefaultFeatureGate.Enabled(features.OpportunisticBatching) { - fwk.StoreScheduleResults(ctx, signature, nodeHint, node, sortedPrioritizedNodes, sched.CurrentCycle()) + fwk.StoreScheduleResults(ctx, podInfo.PodSignature, nodeHint, node, sortedPrioritizedNodes, sched.CurrentCycle()) } return ScheduleResult{ @@ -479,22 +625,22 @@ func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework // Filters the nodes to find the ones that fit the pod based on the framework // filter plugins and filter extenders. -func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework framework.Framework, state fwk.CycleState, pod *v1.Pod) ([]fwk.NodeInfo, framework.Diagnosis, string, fwk.PodSignature, error) { +func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework framework.Framework, state fwk.CycleState, podInfo *framework.QueuedPodInfo) ([]fwk.NodeInfo, framework.Diagnosis, string, error) { logger := klog.FromContext(ctx) diagnosis := framework.Diagnosis{ NodeToStatus: framework.NewDefaultNodeToStatus(), } - - allNodes, err := sched.nodeInfoSnapshot.NodeInfos().List() + allNodes, err := sched.nodeInfoSnapshot.ListNodesInPlacement() if err != nil { - return nil, diagnosis, "", nil, err + return nil, diagnosis, "", err } // Run "prefilter" plugins. + pod := podInfo.Pod preRes, s, unscheduledPlugins := schedFramework.RunPreFilterPlugins(ctx, state, pod) diagnosis.UnschedulablePlugins = unscheduledPlugins if !s.IsSuccess() { if !s.IsRejected() { - return nil, diagnosis, "", nil, s.AsError() + return nil, diagnosis, "", s.AsError() } // All nodes in NodeToStatus will have the same status so that they can be handled in the preemption. diagnosis.NodeToStatus.SetAbsentNodesStatus(s) @@ -504,15 +650,14 @@ func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework diagnosis.PreFilterMsg = msg logger.V(5).Info("Status after running PreFilter plugins for pod", "pod", klog.KObj(pod), "status", msg) diagnosis.AddPluginStatus(s) - return nil, diagnosis, "", nil, nil + return nil, diagnosis, "", nil } var nodeHint string - var signature fwk.PodSignature if utilfeature.DefaultFeatureGate.Enabled(features.OpportunisticBatching) { // We get the node hint even if we have a nominated name for simplicity, but we could potentially avoid it // in this scenario in the future. - nodeHint, signature = schedFramework.GetNodeHint(ctx, pod, state, sched.CurrentCycle()) + nodeHint = schedFramework.GetNodeHint(ctx, pod, podInfo.PodSignature, state, sched.CurrentCycle()) } // "NominatedNodeName" can potentially be set in a previous scheduling cycle as a result of preemption. @@ -525,7 +670,7 @@ func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework } // Nominated node passes all the filters, scheduler is good to assign this node to the pod. if len(feasibleNodes) != 0 { - return feasibleNodes, diagnosis, nodeHint, signature, nil + return feasibleNodes, diagnosis, nodeHint, nil } } @@ -534,8 +679,8 @@ func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework nodes = make([]fwk.NodeInfo, 0, len(preRes.NodeNames)) for nodeName := range preRes.NodeNames { // PreRes may return nodeName(s) which do not exist; we verify - // node exists in the Snapshot. - if nodeInfo, err := sched.nodeInfoSnapshot.Get(nodeName); err == nil { + // node exists in the Snapshot within the selected placement. + if nodeInfo, err := sched.nodeInfoSnapshot.GetNodeInPlacement(nodeName); err == nil { nodes = append(nodes, nodeInfo) } } @@ -547,12 +692,12 @@ func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework processedNodes := len(feasibleNodes) + diagnosis.NodeToStatus.Len() sched.nextStartNodeIndex = (sched.nextStartNodeIndex + processedNodes) % len(allNodes) if err != nil { - return nil, diagnosis, nodeHint, signature, err + return nil, diagnosis, nodeHint, err } feasibleNodesAfterExtender, err := findNodesThatPassExtenders(ctx, sched.Extenders, pod, feasibleNodes, diagnosis.NodeToStatus) if err != nil { - return nil, diagnosis, nodeHint, signature, err + return nil, diagnosis, nodeHint, err } if len(feasibleNodesAfterExtender) != len(feasibleNodes) { // Extenders filtered out some nodes. @@ -569,7 +714,7 @@ func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, schedFramework diagnosis.UnschedulablePlugins.Insert(framework.ExtenderName) } - return feasibleNodesAfterExtender, diagnosis, nodeHint, signature, nil + return feasibleNodesAfterExtender, diagnosis, nodeHint, nil } func (sched *Scheduler) evaluateNominatedNode(ctx context.Context, pod *v1.Pod, schedFramework framework.Framework, state fwk.CycleState, nodeHint string, diagnosis framework.Diagnosis) ([]fwk.NodeInfo, error) { @@ -580,9 +725,16 @@ func (sched *Scheduler) evaluateNominatedNode(ctx context.Context, pod *v1.Pod, nnn = nodeHint } - nodeInfo, err := sched.nodeInfoSnapshot.Get(nnn) + nodeInfo, err := sched.nodeInfoSnapshot.GetNodeInPlacement(nnn) if err != nil { - return nil, err + if _, err := sched.nodeInfoSnapshot.Get(nnn); err != nil { + return nil, err + } + // It's not an error if NNN is in the cluster but not in the placement. + // This can happen during the pod group placement scheduling cycle, where we simulate multiple potential placements. + logger := klog.FromContext(ctx) + logger.V(4).Info("Pod's nominated node is present in the cluster but not available in the current placement", "pod", klog.KObj(pod), "node", pod.Status.NominatedNodeName) + return nil, nil } node := []fwk.NodeInfo{nodeInfo} feasibleNodes, err := sched.findNodesThatPassFilters(ctx, schedFramework, state, pod, &diagnosis, node) @@ -646,7 +798,7 @@ func (sched *Scheduler) findNodesThatPassFilters( return feasibleNodes, nil } - errCh := parallelize.NewErrorChannel() + errCh := parallelize.NewResultChannel[error]() var feasibleNodesLen int32 ctx, cancel := context.WithCancelCause(ctx) defer cancel(errors.New("findNodesThatPassFilters has completed")) @@ -662,7 +814,7 @@ func (sched *Scheduler) findNodesThatPassFilters( nodeInfo := nodes[(sched.nextStartNodeIndex+i)%numAllNodes] status := schedFramework.RunFilterPluginsWithNominatedPods(ctx, state, pod, nodeInfo) if status.Code() == fwk.Error { - errCh.SendErrorWithCancel(status.AsError(), func() { + errCh.SendWithCancel(status.AsError(), func() { cancel(errors.New("some other Filter operation failed")) }) return @@ -700,7 +852,7 @@ func (sched *Scheduler) findNodesThatPassFilters( diagnosis.NodeToStatus.Set(item.node, item.status) diagnosis.AddPluginStatus(item.status) } - if err := errCh.ReceiveError(); err != nil { + if err := errCh.Receive(); err != nil { statusCode = fwk.Error return feasibleNodes, err } @@ -952,21 +1104,39 @@ func (h *nodeScoreHeap) Pop() interface{} { } // assume signals to the cache that a pod is already in the cache, so that binding can be asynchronous. -// assume modifies `assumed`. -func (sched *Scheduler) assume(logger klog.Logger, assumed *v1.Pod, host string) error { +// When called during pod group scheduling cycle, pod is assumed in the snapshot instead. +func (sched *Scheduler) assume(logger klog.Logger, state fwk.CycleState, assumedPodInfo *framework.QueuedPodInfo, host string) error { // Optimistically assume that the binding will succeed and send it to apiserver // in the background. // If the binding fails, scheduler will release resources allocated to assumed pod // immediately. - assumed.Spec.NodeName = host - - if err := sched.Cache.AssumePod(logger, assumed); err != nil { - logger.Error(err, "Scheduler cache AssumePod failed") - return err + assumedPodInfo.Pod.Spec.NodeName = host + if utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources) { + // If DRANodeAllocatableResources is enabled, copy the calculated node allocatable resource claim status + // from the cycle state to the assumed pod's status. This ensures that the scheduler's + // cached version of the pod reflects the node allocatable resources allocated by the DRA plugin + // for this scheduling cycle, making this information available for NodeInfo cache update. + // Any potential NodeAllocatableResourceClaimStatuses from a previously failed scheduling attempt is overwritten. + // This field is not explicitly cleared as the Pod object is reconstructed in handleSchedulingFailure() + // before re-queueing. + assumedPodInfo.Pod.Status.NodeAllocatableResourceClaimStatuses = dynamicresources.ExtractPodNodeAllocatableResourceClaimStatus(logger, state, host) + } + + if state.IsPodGroupSchedulingCycle() { + err := sched.nodeInfoSnapshot.AssumePod(assumedPodInfo.PodInfo) + if err != nil { + logger.Error(err, "Scheduler snapshot AssumePod failed") + return err + } + } else { + if err := sched.Cache.AssumePod(logger, assumedPodInfo.Pod); err != nil { + logger.Error(err, "Scheduler cache AssumePod failed") + return err + } } // if "assumed" is a nominated pod, we should remove it from internal cache if sched.SchedulingQueue != nil { - sched.SchedulingQueue.DeleteNominatedPodIfExists(assumed) + sched.SchedulingQueue.DeleteNominatedPodIfExists(assumedPodInfo.Pod) } return nil @@ -1008,15 +1178,12 @@ func (sched *Scheduler) extendersBinding(logger klog.Logger, pod *v1.Pod, node s } func (sched *Scheduler) finishBinding(logger klog.Logger, fwk framework.Framework, assumed *v1.Pod, targetNode string, status *fwk.Status) { - if finErr := sched.Cache.FinishBinding(logger, assumed); finErr != nil { - utilruntime.HandleErrorWithLogger(logger, finErr, "Scheduler cache FinishBinding failed") - } if !status.IsSuccess() { logger.V(1).Info("Failed to bind pod", "pod", klog.KObj(assumed)) return } - fwk.EventRecorder().Eventf(assumed, nil, v1.EventTypeNormal, "Scheduled", "Binding", "Successfully assigned %v/%v to %v", assumed.Namespace, assumed.Name, targetNode) + fwk.EventRecorder().WithLogger(logger).Eventf(assumed, nil, v1.EventTypeNormal, "Scheduled", "Binding", "Successfully assigned %v/%v to %v", assumed.Namespace, assumed.Name, targetNode) } func getAttemptsLabel(p *framework.QueuedPodInfo) string { @@ -1030,7 +1197,7 @@ func getAttemptsLabel(p *framework.QueuedPodInfo) string { // handleSchedulingFailure records an event for the pod that indicates the // pod has failed to schedule. Also, update the pod condition and nominated node name if set. -func (sched *Scheduler) handleSchedulingFailure(ctx context.Context, fwk framework.Framework, podInfo *framework.QueuedPodInfo, status *fwk.Status, nominatingInfo *fwk.NominatingInfo, start time.Time) { +func (sched *Scheduler) handleSchedulingFailure(ctx context.Context, podFwk framework.Framework, podInfo *framework.QueuedPodInfo, status *fwk.Status, nominatingInfo *fwk.NominatingInfo, start time.Time) { calledDone := false defer func() { if !calledDone { @@ -1049,27 +1216,35 @@ func (sched *Scheduler) handleSchedulingFailure(ctx context.Context, fwk framewo switch reason { case v1.PodReasonUnschedulable: - metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) + metrics.PodUnschedulable(podFwk.ProfileName(), metrics.SinceInSeconds(start)) case v1.PodReasonSchedulerError: - metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) + metrics.PodScheduleError(podFwk.ProfileName(), metrics.SinceInSeconds(start)) } pod := podInfo.Pod err := status.AsError() errMsg := status.Message() + // Clear plugin-related fields to avoid stale data from previous scheduling attempts. + // These fields will be repopulated below for FitError cases. + // We clear them here (rather than at Pop) because we sometimes want to use them + // for logging when a pod schedules successfully (e.g., after being flushed). + podInfo.ClearRejectorPlugins() + if err == ErrNoNodesAvailable { logger.V(2).Info("Unable to schedule pod; no nodes are registered to the cluster; waiting", "pod", klog.KObj(pod)) } else if fitError, ok := err.(*framework.FitError); ok { // Inject UnschedulablePlugins to PodInfo, which will be used later for moving Pods between queues efficiently. podInfo.UnschedulablePlugins = fitError.Diagnosis.UnschedulablePlugins podInfo.PendingPlugins = fitError.Diagnosis.PendingPlugins logger.V(2).Info("Unable to schedule pod; no fit; waiting", "pod", klog.KObj(pod), "err", errMsg) + } else if errors.Is(err, errPodGroupUnschedulable) { + logger.V(2).Info("Unable to schedule pod belonging to a pod group; waiting", "pod", klog.KObj(pod), "err", errMsg) } else { utilruntime.HandleErrorWithContext(ctx, err, "Error scheduling pod; retrying", "pod", klog.KObj(pod)) } // Check if the Pod exists in informer cache. - podLister := fwk.SharedInformerFactory().Core().V1().Pods().Lister() + podLister := podFwk.SharedInformerFactory().Core().V1().Pods().Lister() cachedPod, e := podLister.Pods(pod.Namespace).Get(pod.Name) if e != nil { logger.Info("Pod doesn't exist in informer cache", "pod", klog.KObj(pod), "err", e) @@ -1081,10 +1256,15 @@ func (sched *Scheduler) handleSchedulingFailure(ctx context.Context, fwk framewo logger.Info("Pod has been assigned to node. Abort adding it back to queue.", "pod", klog.KObj(pod), "node", cachedPod.Spec.NodeName) // We need to call DonePod here because we don't call AddUnschedulableIfNotPresent in this case. } else { + if cachedPod.UID != podInfo.Pod.UID { + logger.V(2).Info("Pod was recreated while handling scheduling failure. Skip requeueing and status updates.", "pod", klog.KObj(pod), "oldUID", podInfo.Pod.UID, "newUID", cachedPod.UID) + return + } // As is from SharedInformer, we need to do a DeepCopy() here. // ignore this err since apiserver doesn't properly validate affinity terms // and we can't fix the validation for backwards compatibility. podInfo.PodInfo, _ = framework.NewPodInfo(cachedPod.DeepCopy()) + pod = podInfo.Pod if err := sched.SchedulingQueue.AddUnschedulableIfNotPresent(logger, podInfo, sched.SchedulingQueue.SchedulingCycle()); err != nil { utilruntime.HandleErrorWithContext(ctx, err, "Error occurred") } @@ -1106,8 +1286,8 @@ func (sched *Scheduler) handleSchedulingFailure(ctx context.Context, fwk framewo } msg := truncateMessage(errMsg) - fwk.EventRecorder().Eventf(pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", msg) - if err := updatePod(ctx, sched.client, fwk.APICacher(), pod, &v1.PodCondition{ + podFwk.EventRecorder().WithLogger(logger).Eventf(pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", msg) + if err := updatePod(ctx, sched.client, podFwk.APICacher(), pod, &v1.PodCondition{ Type: v1.PodScheduled, ObservedGeneration: podutil.CalculatePodConditionObservedGeneration(&pod.Status, pod.Generation, v1.PodScheduled), Status: v1.ConditionFalse, diff --git a/pkg/scheduler/schedule_one_podgroup.go b/pkg/scheduler/schedule_one_podgroup.go new file mode 100644 index 0000000000000..0954d06736d0f --- /dev/null +++ b/pkg/scheduler/schedule_one_podgroup.go @@ -0,0 +1,779 @@ +/* +Copyright 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 scheduler + +import ( + "context" + "fmt" + "math/rand" + "sort" + "time" + + v1 "k8s.io/api/core/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + corev1helpers "k8s.io/component-helpers/scheduling/corev1" + "k8s.io/klog/v2" + fwk "k8s.io/kube-scheduler/framework" + schedulingapi "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/metrics" + "k8s.io/kubernetes/pkg/scheduler/util" + "k8s.io/utils/ptr" +) + +// errPodGroupUnschedulable is used to describe that the pod group is unschedulable. +var errPodGroupUnschedulable = fmt.Errorf("pod group is unschedulable") + +// scheduleOnePodGroup does the entire workload-aware scheduling workflow for a single pod group. +func (sched *Scheduler) scheduleOnePodGroup(ctx context.Context, podGroupInfo *framework.QueuedPodGroupInfo) { + logger := klog.FromContext(ctx) + // TODO(knelasevero): Remove duplicated keys from log entry calls + // When contextualized logging hits GA + // https://github.com/kubernetes/kubernetes/issues/111672 + logger = klog.LoggerWithValues(logger, "podGroup", klog.KObj(podGroupInfo)) + ctx = klog.NewContext(ctx, logger) + + schedFwk, err := sched.frameworkForPodGroup(podGroupInfo) + if err != nil { + for _, podInfo := range podGroupInfo.QueuedPodInfos { + podFwk, podFwkErr := sched.frameworkForPod(podInfo.Pod) + if podFwkErr != nil { + // This shouldn't happen, because we only accept for scheduling the pods + // which specify a scheduler name that matches one of the profiles. + logger.Error(podFwkErr, "Error occurred") + sched.SchedulingQueue.Done(podInfo.Pod.UID) + return + } + sched.FailureHandler(ctx, podFwk, podInfo, fwk.AsStatus(err), clearNominatedNode, time.Now()) + } + return + } + sched.skipPodGroupPodSchedule(ctx, schedFwk, podGroupInfo) + // skipPodGroupPodSchedule could remove some pods from the pod group. + // Pod group constraints will be re-evaluated on a Permit phase. + // Now, verify if it has any pods left. + if len(podGroupInfo.QueuedPodInfos) == 0 { + return + } + + logger.V(3).Info("Attempting to schedule pod group", "podGroup", klog.KObj(podGroupInfo)) + + sched.podGroupCycle(ctx, schedFwk, framework.NewCycleState(), podGroupInfo) +} + +// frameworkForPodGroup obtains the concrete scheduler framework for the entire pod group. +func (sched *Scheduler) frameworkForPodGroup(podGroupInfo *framework.QueuedPodGroupInfo) (framework.Framework, error) { + schedulerName := "" + for _, pInfo := range podGroupInfo.QueuedPodInfos { + if schedulerName == "" { + schedulerName = pInfo.Pod.Spec.SchedulerName + } else if pInfo.Pod.Spec.SchedulerName != schedulerName { + return nil, fmt.Errorf("all pods in a single pod group should have the same .spec.schedulerName set, got: %q and %q", pInfo.Pod.Spec.SchedulerName, schedulerName) + } + } + fwk, ok := sched.Profiles[schedulerName] + if !ok { + return nil, fmt.Errorf("profile not found for scheduler name %q", schedulerName) + } + return fwk, nil +} + +// skipPodGroupPodSchedule skips the scheduling of particular pods from the group when they should no longer be considered. +// This can happen when the pod is already being deleted (i.e., when its deletionTimestamp is set) +// or when the pod has already been assumed. +func (sched *Scheduler) skipPodGroupPodSchedule(ctx context.Context, schedFwk framework.Framework, podGroupInfo *framework.QueuedPodGroupInfo) { + filteredQueuedPodInfos := make([]*framework.QueuedPodInfo, 0, len(podGroupInfo.QueuedPodInfos)) + for _, podInfo := range podGroupInfo.QueuedPodInfos { + if sched.skipPodSchedule(ctx, schedFwk, podInfo.Pod) { + // We don't put this Pod back to the queue, but we have to cleanup the in-flight pods/events. + sched.SchedulingQueue.Done(podInfo.Pod.UID) + continue + } + filteredQueuedPodInfos = append(filteredQueuedPodInfos, podInfo) + } + if len(filteredQueuedPodInfos) != len(podGroupInfo.QueuedPodInfos) { + podGroupInfo.QueuedPodInfos = filteredQueuedPodInfos + podGroupInfo.UnscheduledPods = make([]*v1.Pod, 0, len(podGroupInfo.QueuedPodInfos)) + for _, pInfo := range podGroupInfo.QueuedPodInfos { + podGroupInfo.UnscheduledPods = append(podGroupInfo.UnscheduledPods, pInfo.Pod) + } + } +} + +// podGroupInfoForPod is a temporary function that obtains the QueuedPodGroupInfo based on the pod that got popped from the scheduling queue. +// Ultimately, scheduling queue, adjusted with workload-awareness will return such QueuedPodGroupInfo directly. +func (sched *Scheduler) podGroupInfoForPod(ctx context.Context, pInfo *framework.QueuedPodInfo) (*framework.QueuedPodGroupInfo, error) { + logger := klog.FromContext(ctx) + + // Get the actual pod group state + podGroupState, err := sched.Cache.PodGroupStates().Get(pInfo.Pod.Namespace, *pInfo.Pod.Spec.SchedulingGroup.PodGroupName) + if err != nil { + return nil, fmt.Errorf("error while retrieving pod group state: %w", err) + } + unscheduledPods := podGroupState.UnscheduledPods() + + podGroupInfo := &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{ + Namespace: pInfo.Pod.Namespace, + Name: *pInfo.Pod.Spec.SchedulingGroup.PodGroupName, + }, + QueuedPodInfos: make([]*framework.QueuedPodInfo, 0, len(unscheduledPods)+1), + } + podGroupInfo.QueuedPodInfos = append(podGroupInfo.QueuedPodInfos, pInfo) + + // Pop all unscheduled pods from the scheduling queue + for _, pod := range unscheduledPods { + if pod.Name == pInfo.Pod.Name { + continue + } + unscheduledPodInfo := sched.SchedulingQueue.PopSpecificPod(logger, pod) + if unscheduledPodInfo == nil { + logger.V(5).Info("Pod available in pod group state not available in scheduling queue", "podGroup", klog.KObj(podGroupInfo), "pod", klog.KObj(pod)) + continue + } + podGroupInfo.QueuedPodInfos = append(podGroupInfo.QueuedPodInfos, unscheduledPodInfo) + } + // Sort the pods in deterministic order. First by priority, then by their InitialAttemptTimestamp. + sort.Slice(podGroupInfo.QueuedPodInfos, func(i, j int) bool { + pInfo1 := podGroupInfo.QueuedPodInfos[i] + pInfo2 := podGroupInfo.QueuedPodInfos[j] + p1 := corev1helpers.PodPriority(pInfo1.GetPodInfo().GetPod()) + p2 := corev1helpers.PodPriority(pInfo2.GetPodInfo().GetPod()) + // Timestamps should be set, but dereferencing them for safety. + p1Timestamp := ptr.Deref(pInfo1.InitialAttemptTimestamp, time.Time{}) + p2Timestamp := ptr.Deref(pInfo2.InitialAttemptTimestamp, time.Time{}) + return (p1 > p2) || (p1 == p2 && p1Timestamp.Before(p2Timestamp)) + }) + + // Populate UnscheduledPods based on the QueuedPodInfos. + podGroupInfo.UnscheduledPods = make([]*v1.Pod, 0, len(podGroupInfo.QueuedPodInfos)) + for _, pInfo := range podGroupInfo.QueuedPodInfos { + podGroupInfo.UnscheduledPods = append(podGroupInfo.UnscheduledPods, pInfo.Pod) + } + + return podGroupInfo, nil +} + +// podSchedulingContext holds the precomputed data needed to handle the pod scheduling. +// Each scheduling attempt in the same pod group scheduling cycle for the same pod +// should use a new podSchedulingContext. +type podSchedulingContext struct { + logger klog.Logger + state *framework.CycleState + podsToActivate *framework.PodsToActivate +} + +// initPodSchedulingContext initializes the scheduling context of a single pod for pod group scheduling cycle. +func initPodSchedulingContext(ctx context.Context, pod *v1.Pod, podGroupState *framework.CycleState, postFilterMode podGroupPostFilterMode) *podSchedulingContext { + logger := klog.FromContext(ctx) + // TODO(knelasevero): Remove duplicated keys from log entry calls + // When contextualized logging hits GA + // https://github.com/kubernetes/kubernetes/issues/111672 + logger = klog.LoggerWithValues(logger, "pod", klog.KObj(pod)) + + // Synchronously attempt to find a fit for the pod. + state := framework.NewCycleState() + // For the sake of performance, scheduler does not measure and export the scheduler_plugin_execution_duration metric + // for every plugin execution in each scheduling cycle. Instead it samples a portion of scheduling cycles - percentage + // determined by pluginMetricsSamplePercent. The line below helps to randomly pick appropriate scheduling cycles. + state.SetRecordPluginMetrics(rand.Intn(100) < pluginMetricsSamplePercent) + + // Initialize an empty podsToActivate struct, which will be filled up by plugins or stay empty. + podsToActivate := framework.NewPodsToActivate() + state.Write(framework.PodsToActivateKey, podsToActivate) + + // Marks this cycle as a pod group scheduling cycle. + state.SetPodGroupSchedulingCycle(podGroupState) + + // Skip post filters if requested. + switch postFilterMode { + case runWithoutPostFilters: + state.SetSkipAllPostFilterPlugins(true) + case runAllPostFilters: + // Default podCtx state will run all post filters. + } + + return &podSchedulingContext{ + logger: logger, + state: state, + podsToActivate: podsToActivate, + } +} + +// podGroupCycle runs a pod group scheduling cycle for the given pod group in a single cluster snapshot. +func (sched *Scheduler) podGroupCycle(ctx context.Context, schedFwk framework.Framework, podGroupCycleState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo) { + // Synchronously attempt to find a fit for the pod group. + start := time.Now() + + logger := klog.FromContext(ctx) + if err := sched.Cache.UpdateSnapshot(logger, sched.nodeInfoSnapshot); err != nil { + logger.Error(err, "Error updating snapshot", "podGroup", klog.KObj(podGroupInfo)) + result := podGroupAlgorithmResult{ + status: fwk.AsStatus(err), + } + sched.submitPodGroupAlgorithmResult(ctx, schedFwk, podGroupCycleState, podGroupInfo, result, start) + return + } + + result := sched.podGroupSchedulingAlgorithm(ctx, schedFwk, podGroupCycleState, podGroupInfo, runAllPostFilters) + metrics.PodGroupSchedulingAlgorithmLatency.Observe(metrics.SinceInSeconds(start)) + + // Run workload aware preemption if required. If the preemption is successful, + // we need to put the pods from pod group back into the scheduling queue. + if sched.workloadAwarePreemptionEnabled && result.status.Code() == fwk.Unschedulable { + status := sched.runWorkloadAwarePreemption(ctx, schedFwk, podGroupCycleState, podGroupInfo) + if status.IsSuccess() { + result.waitingOnPreemption = true + } else if status.IsError() { + result.status = status + } else { + result.status.AppendReason(status.String()) + } + } + + sched.submitPodGroupAlgorithmResult(ctx, schedFwk, podGroupCycleState, podGroupInfo, result, start) +} + +// runWorkloadAwarePreemption runs workload-aware preemption for the given pod group. +// It saves the current snapshot of node infos, runs a PodGroupPostFilter +// which modifies the node infos to check feasibility of the +// pod group scheduling with some pods removed and reverts the snapshot to the +// original state. +// The function used for evaluating feasibility of pod group scheduling is +// scheduler.podGroupSchedulingAlgorithm run without any post filters. +func (sched *Scheduler) runWorkloadAwarePreemption(ctx context.Context, schedFwk framework.Framework, podGroupCycleState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo) *fwk.Status { + // Default preemption should be the only pod group post filter registered plugin. + plugins := schedFwk.PodGroupPostFilterPlugins() + if len(plugins) == 0 { + return fwk.NewStatus(fwk.Unschedulable, "default preemption plugin is not registered, workload aware preemption is disabled") + } + + pg, err := schedFwk.SharedInformerFactory().Scheduling().V1alpha2().PodGroups().Lister().PodGroups(podGroupInfo.Namespace).Get(podGroupInfo.Name) + if err != nil { + return fwk.AsStatus(fmt.Errorf("failed to get pod group object: %w", err)) + } + if pg.Spec.SchedulingConstraints != nil && len(pg.Spec.SchedulingConstraints.Topology) > 0 { + return fwk.NewStatus(fwk.Unschedulable, "workload aware preemption is not supported for pod groups with scheduling constraints") + } + + restoreFn, err := sched.nodeInfoSnapshot.BackupSnapshot() + if err != nil { + return fwk.AsStatus(fmt.Errorf("failed to backup snapshot: %w", err)) + } + defer restoreFn() + + return plugins[0].PodGroupPostFilter(ctx, pg, podGroupInfo.UnscheduledPods, func(ctx context.Context) *fwk.Status { + res := sched.podGroupSchedulingAlgorithm(ctx, schedFwk, podGroupCycleState, podGroupInfo, runWithoutPostFilters) + return res.status + }) +} + +// algorithmResult stores the scheduling result and status for a scheduling attempt of a single pod. +type algorithmResult struct { + // pod is the pod the result applies to. + pod *v1.Pod + // scheduleResult is a scheduling algorithm result. + scheduleResult ScheduleResult + // podCtx is a specific pod scheduling context used for the scheduling algorithm. + podCtx *podSchedulingContext + // schedulingDuration is a pod scheduling duration used for metrics recording. + schedulingDuration time.Duration + // requiresPreemption determines whether this pod requires a preemption to proceed or not. + requiresPreemption bool + // status is a scheduling algorithm status. + status *fwk.Status + // permitStatus is a status of the permit check. + // This is only set when the `status` is success or the `requiresPreemption` is true. + permitStatus *fwk.Status +} + +// podGroupPostFilterMode defines how the pod group algorithm should run post filters plugins. +type podGroupPostFilterMode int + +const ( + // The pod group algorithm should try to run all post filters in pod-by-pod cycle. + runAllPostFilters podGroupPostFilterMode = iota + // The pod group algorithm should not try post filter at all. This can be used + // by workload aware preemption that tries to check if after removing some + // pods the pod group can be scheduled. + runWithoutPostFilters +) + +func (ar *algorithmResult) GetPod() *v1.Pod { + return ar.pod +} + +func (ar *algorithmResult) GetNodeName() string { + return ar.scheduleResult.SuggestedHost +} + +// podGroupAlgorithmResult stores the scheduling pod scheduling results for a pod group +// and any information needed to act on these results. +type podGroupAlgorithmResult struct { + // podResults is the list of scheduling results for each pod in the group. + // Only in the case of a pod group-wide Unschedulable or Error status can it contain fewer pods. + podResults []algorithmResult + // status is the final status of the pod group algorithm. + // + // Success code indicates that the pod group is schedulable and does not require any preemption. + // Its feasible pods should be moved to the binding cycle. + // This should only be set when the pod group is feasible and `waitingOnPreemption` is false. + // + // Unschedulable code indicates that the pod group is unschedulable, + // and all its pods should be moved back to the scheduling queue as unschedulable. + // Result with `waitingOnPreemption` set to true should have the Unschedulable status. + // + // Error code means that pod group scheduling failed due to an unexpected error, + // and no pods will be scheduled this attempt. + status *fwk.Status + // waitingOnPreemption indicates whether this pod group requires or is waiting for preemption to complete. + // This can only be set to true when the status is Unschedulable. + waitingOnPreemption bool +} + +// podGroupSchedulingDefaultAlgorithm runs the default algorithm for scheduling a pod group. +// It tries to schedule each pod using standard filtering and scoring logic in a fixed order. +// If a pod requires preemption to be schedulable, subsequent pods in the algorithm +// treat that pod as already scheduled on that node with victims being already removed in memory. +func (sched *Scheduler) podGroupSchedulingDefaultAlgorithm(ctx context.Context, schedFwk framework.Framework, podGroupCycleState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo, postFilterMode podGroupPostFilterMode) podGroupAlgorithmResult { + result := podGroupAlgorithmResult{ + podResults: make([]algorithmResult, 0, len(podGroupInfo.QueuedPodInfos)), + status: fwk.NewStatus(fwk.Unschedulable).WithError(errPodGroupUnschedulable), + waitingOnPreemption: false, + } + + logger := klog.FromContext(ctx) + logger.V(5).Info("Running a pod group scheduling algorithm", "podGroup", klog.KObj(podGroupInfo), "unscheduledPodsCount", len(podGroupInfo.QueuedPodInfos)) + + requiresPreemption := false + for _, podInfo := range podGroupInfo.QueuedPodInfos { + podResult, revertFn := sched.podGroupPodSchedulingAlgorithm(ctx, schedFwk, podGroupCycleState, podGroupInfo, podInfo, postFilterMode) + result.podResults = append(result.podResults, podResult) + if !podResult.status.IsSuccess() && !podResult.requiresPreemption { + // When a pod is not feasible and doesn't require preemption, it means that it failed scheduling. + if podResult.status.IsRejected() { + // If the pod is rejected, the pod group can still be schedulable as long as the permit check can succeed. + continue + } + // When the algorithm returns error or unexpected status, stop evaluating the rest of the pods. + result.status = fwk.AsStatus(fmt.Errorf("failed to schedule other pod from a pod group: %w", podResult.status.AsError())) + // Clear the waiting on preemption flag that could have been set by previous pods. + result.waitingOnPreemption = false + break + } + // At this point, the pod has passed the scheduling algorithm with the Permit status being either Success or Wait. + // We unreserve the pod at the end of the whole algorithm (via defer) because it should be ultimately returned to the queue, + // without binding it yet. We only assumed the pod to check feasibility of subsequent pods in the group. + defer revertFn() + + requiresPreemption = requiresPreemption || podResult.requiresPreemption + if podResult.permitStatus.IsSuccess() { + // When the permit returns success for any pod, the pod group is schedulable. + if requiresPreemption { + // If any preemption is required, the whole pod group requires it to be feasible. + result.status = fwk.NewStatus(fwk.Unschedulable, "pod group is waiting for preemption to complete").WithError(errPodGroupUnschedulable) + // Set the waitingOnPreemption to true iff the pod group is feasible (Permit returned Success) and requires preemption. + result.waitingOnPreemption = true + } else { + result.status = nil // Success + } + } + } + + return result +} + +// podGroupPodSchedulingAlgorithm runs a scheduling algorithm for individual pod from a pod group. +// It returns the algorithm result and, if successful or the preemption is required, the permit status together with the revert function. +func (sched *Scheduler) podGroupPodSchedulingAlgorithm(ctx context.Context, schedFwk framework.Framework, podGroupCycleState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo, podInfo *framework.QueuedPodInfo, postFilterMode podGroupPostFilterMode) (algorithmResult, func()) { + pod := podInfo.Pod + podCtx := initPodSchedulingContext(ctx, pod, podGroupCycleState, postFilterMode) + logger := podCtx.logger + ctx = klog.NewContext(ctx, logger) + start := time.Now() + + logger.V(4).Info("Attempting to schedule a pod belonging to a pod group", "podGroup", klog.KObj(podGroupInfo), "pod", klog.KObj(pod)) + + requiresPreemption := false + scheduleResult, status := sched.schedulingAlgorithm(ctx, podCtx.state, schedFwk, podInfo, start) + if !status.IsSuccess() { + if scheduleResult.nominatingInfo != nil && scheduleResult.nominatingInfo.NominatedNodeName != "" { + // If the NominatedNodeName is set, the preemption is required. + // Continue with assuming and reserving, because the subsequent pods from this group + // have to see this one as already scheduled on its nominated place. + // Set SuggestedHost to NominatedNodeName to handle the pod similarly to one that is feasible. + scheduleResult.SuggestedHost = scheduleResult.nominatingInfo.NominatedNodeName + requiresPreemption = true + } else { + // In case of pod being just unschedulable or having an error, just return now. + return algorithmResult{ + pod: pod, + scheduleResult: scheduleResult, + podCtx: podCtx, + schedulingDuration: time.Since(start), + status: status, + }, nil + } + } + assumedPodInfo, assumeStatus := sched.assumeAndReserve(ctx, podCtx.state, schedFwk, podInfo, scheduleResult) + if !assumeStatus.IsSuccess() { + return algorithmResult{ + pod: pod, + scheduleResult: ScheduleResult{nominatingInfo: clearNominatedNode}, + podCtx: podCtx, + schedulingDuration: time.Since(start), + status: assumeStatus, + }, nil + } + + revertFn := func() { + err := sched.unreserveAndForget(ctx, podCtx.state, schedFwk, assumedPodInfo, scheduleResult.SuggestedHost) + if err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "ForgetPod failed") + } + } + + _, permitStatus := schedFwk.RunPermitPlugins(ctx, podCtx.state, assumedPodInfo.Pod, scheduleResult.SuggestedHost) + if !permitStatus.IsWait() && !permitStatus.IsSuccess() { + revertFn() + if permitStatus.IsRejected() { + fitErr := &framework.FitError{ + NumAllNodes: 1, + Pod: assumedPodInfo.Pod, + Diagnosis: framework.Diagnosis{ + NodeToStatus: framework.NewDefaultNodeToStatus(), + }, + } + fitErr.Diagnosis.NodeToStatus.Set(scheduleResult.SuggestedHost, permitStatus) + fitErr.Diagnosis.AddPluginStatus(permitStatus) + permitStatus = fwk.NewStatus(permitStatus.Code()).WithError(fitErr) + } + return algorithmResult{ + pod: pod, + scheduleResult: ScheduleResult{nominatingInfo: clearNominatedNode}, + podCtx: podCtx, + schedulingDuration: time.Since(start), + status: permitStatus, + }, nil + } + + return algorithmResult{ + pod: pod, + scheduleResult: scheduleResult, + podCtx: podCtx, + schedulingDuration: time.Since(start), + status: status, + permitStatus: permitStatus, + requiresPreemption: requiresPreemption, + }, revertFn +} + +// submitPodGroupAlgorithmResult submits the result of the pod group scheduling algorithm. +// If that algorithm succedeed, the schedulable pods proceed to the binding cycle. +// Unschedulable pods are moved back to the scheduling queue and need to wait +// for the next pod group scheduling cycle. +// If the preemption is required for this pod group, all pods are moved back to the scheduling queue +// and require the next pod group scheduling cycle to verify the preemption outcome. +func (sched *Scheduler) submitPodGroupAlgorithmResult(ctx context.Context, schedFwk framework.Framework, podGroupState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo, podGroupResult podGroupAlgorithmResult, start time.Time) { + logger := klog.FromContext(ctx) + + var scheduledPods, unschedulablePods int + for i, pInfo := range podGroupInfo.QueuedPodInfos { + var podResult algorithmResult + if len(podGroupResult.podResults) > i { + podResult = podGroupResult.podResults[i] + } else { + // In pod group-level unschedulable or error cases, podResult may not be defined. + // Initialize it now to handle pod failure correctly. + podResult = algorithmResult{ + podCtx: initPodSchedulingContext(ctx, pInfo.Pod, podGroupState, runAllPostFilters), + status: podGroupResult.status.Clone(), + } + } + podCtx := podResult.podCtx + ctx := klog.NewContext(ctx, podCtx.logger) + // To be consistent with pod-by-pod scheduling, construct pod scheduling start time as `now - scheduling duration`. + podSchedulingStart := time.Now().Add(-podResult.schedulingDuration) + + if podGroupResult.status.IsError() { + if podResult.status.IsError() { + // If this exact pod failed with an error, use its status instead. + sched.FailureHandler(ctx, schedFwk, pInfo, podResult.status, clearNominatedNode, podSchedulingStart) + continue + } + // Pod group failed with an error. Reject all pods with its status. + sched.FailureHandler(ctx, schedFwk, pInfo, podGroupResult.status, clearNominatedNode, podSchedulingStart) + continue + } + if podResult.status.IsSuccess() { + nominatingInfo := &fwk.NominatingInfo{ + NominatingMode: fwk.ModeOverride, + NominatedNodeName: podResult.scheduleResult.SuggestedHost, + } + switch { + case podGroupResult.status.IsSuccess(): + // Disable pod group scheduling in cycle state before binding. + podCtx.state.SetPodGroupSchedulingCycle(nil) + // Schedule result is applied for pod and its binding cycle executes. + assumedPodInfo, status := sched.prepareForBindingCycle(ctx, podCtx.state, schedFwk, pInfo, podCtx.podsToActivate, podResult.scheduleResult) + if !status.IsSuccess() { + // In such unlikely situation just reject this pod. + sched.FailureHandler(ctx, schedFwk, pInfo, status, clearNominatedNode, podSchedulingStart) + unschedulablePods++ + continue + } + go sched.runBindingCycle(ctx, podCtx.state, schedFwk, podResult.scheduleResult, assumedPodInfo, podSchedulingStart, podCtx.podsToActivate) + scheduledPods++ + case podGroupResult.status.IsRejected(): + if podGroupResult.waitingOnPreemption { + // Pod has to come back to the scheduling queue as unschedulable, waiting for preemption to complete. + sched.FailureHandler(ctx, schedFwk, pInfo, podGroupResult.status, nominatingInfo, podSchedulingStart) + } else { + // Pod group is unschedulable, so the pod has to be marked as unschedulable. + // Its rejection status is set to its permit status message. + status := fwk.NewStatus(fwk.Unschedulable, podResult.permitStatus.Message()).WithError(errPodGroupUnschedulable) + sched.FailureHandler(ctx, schedFwk, pInfo, status, clearNominatedNode, podSchedulingStart) + } + unschedulablePods++ + default: + err := fmt.Errorf("received unexpected pod group scheduling algorithm status code: %s", podGroupResult.status.Code()) + sched.FailureHandler(ctx, schedFwk, pInfo, fwk.AsStatus(err), clearNominatedNode, podSchedulingStart) + unschedulablePods++ + } + } else { + // TBD: Add a message to status if the pod used features for which finding a placement cannot be guaranteed, + // such as heterogeneous pod group or using inter-pod dependencies. + if podResult.requiresPreemption && !podGroupResult.waitingOnPreemption { + // Pod group is unschedulable, so the pod has to be marked as unschedulable, even if it just required preemption. + // Its rejection status is set to its permit status message, as the preemption message is no longer relevant. + status := fwk.NewStatus(fwk.Unschedulable, podResult.permitStatus.Message()).WithError(errPodGroupUnschedulable) + sched.FailureHandler(ctx, schedFwk, pInfo, status, clearNominatedNode, podSchedulingStart) + } else { + // When a pod is unschedulable or preemption is required, just call the FailureHandler. + sched.FailureHandler(ctx, schedFwk, pInfo, podResult.status, podResult.scheduleResult.nominatingInfo, podSchedulingStart) + } + unschedulablePods++ + } + } + + var condition *metav1.Condition + switch { + case podGroupResult.status.IsSuccess(): + condition = &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: podGroupResult.status.Message(), + } + logger.V(2).Info("Successfully scheduled a pod group", "podGroup", klog.KObj(podGroupInfo), "scheduledPods", scheduledPods, "unschedulablePods", unschedulablePods) + metrics.PodGroupScheduled(schedFwk.ProfileName(), metrics.SinceInSeconds(start)) + + case podGroupResult.status.IsRejected(): + condition = &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: podGroupResult.status.Message(), + } + if podGroupResult.waitingOnPreemption { + logger.V(2).Info("Pod group is waiting for preemption", "podGroup", klog.KObj(podGroupInfo), "unschedulablePods", unschedulablePods) + metrics.PodGroupWaitingOnPreemption(schedFwk.ProfileName(), metrics.SinceInSeconds(start)) + } else { + logger.V(2).Info("Unable to schedule a pod group", "podGroup", klog.KObj(podGroupInfo), "unschedulablePods", unschedulablePods) + metrics.PodGroupUnschedulable(schedFwk.ProfileName(), metrics.SinceInSeconds(start)) + } + + default: + condition = &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: podGroupResult.status.AsError().Error(), + } + utilruntime.HandleErrorWithContext(ctx, podGroupResult.status.AsError(), "Error scheduling pod group", "podGroup", klog.KObj(podGroupInfo), "errorPods", len(podGroupInfo.QueuedPodInfos)) + metrics.PodGroupScheduleError(schedFwk.ProfileName(), metrics.SinceInSeconds(start)) + } + sched.updatePodGroupCondition(ctx, podGroupInfo, condition) +} + +// updatePodGroupCondition patches the given condition on a PodGroup. +func (sched *Scheduler) updatePodGroupCondition(ctx context.Context, + podGroupInfo *framework.QueuedPodGroupInfo, condition *metav1.Condition) { + logger := klog.FromContext(ctx) + + // If the PodGroup was already successfully scheduled, don't regress the + // condition back to False on a subsequent cycle for extra pods. + pg, err := sched.podGroupLister.PodGroups(podGroupInfo.Namespace).Get(podGroupInfo.Name) + if err != nil { + utilruntime.HandleErrorWithLogger(logger, err, "Failed to get PodGroup for status update", "podGroup", klog.KObj(podGroupInfo)) + return + } + + existing := apimeta.FindStatusCondition(pg.Status.Conditions, condition.Type) + if existing != nil && existing.Status == metav1.ConditionTrue && condition.Status != metav1.ConditionTrue { + return + } + + condition.ObservedGeneration = pg.Generation + newStatus := pg.Status.DeepCopy() + if !apimeta.SetStatusCondition(&newStatus.Conditions, *condition) { + return + } + + if err := util.PatchPodGroupStatus(ctx, sched.client, podGroupInfo.Name, podGroupInfo.Namespace, &pg.Status, newStatus); err != nil { + utilruntime.HandleErrorWithLogger(logger, err, "Failed to update PodGroup status", "podGroup", klog.KObj(podGroupInfo)) + } +} + +// podGroupSchedulingPlacementAlgorithm tries several different combinations for scheduling the pod group and selects the best one. +// First it runs placement generator plugins to create a list of placements. +// Placement is a set of nodes that will be considered when scheduling a pod group. +// Then for each placement it tries to schedule the pod group through podGroupSchedulingDefaultAlgorithm. +// Finally, it runs placement scorer plugins to select the best placement. +func (sched *Scheduler) podGroupSchedulingPlacementAlgorithm(ctx context.Context, schedFwk framework.Framework, podGroupCycleState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo, postFilterMode podGroupPostFilterMode) podGroupAlgorithmResult { + logger := klog.FromContext(ctx) + allNodes, err := sched.nodeInfoSnapshot.NodeInfos().List() + if err != nil { + return podGroupAlgorithmResult{ + status: fwk.AsStatus(fmt.Errorf("failed to list node infos: %w", err)), + } + } + + // For now, always record plugin metrics until we understand its impact on performance. + podGroupCycleState.SetRecordPluginMetrics(true) + placements, status := schedFwk.RunPlacementGeneratePlugins(ctx, podGroupCycleState, podGroupInfo.PodGroupInfo, allNodes) + if !status.IsSuccess() { + return podGroupAlgorithmResult{ + status: status, + } + } + + var anyResult *podGroupAlgorithmResult + successfulResults := make(map[*fwk.Placement]*podGroupAlgorithmResult) + + for _, placement := range placements { + logger.V(4).Info("Assuming placement in snapshot", "placement", placement.Name) + err := sched.nodeInfoSnapshot.AssumePlacement(placement) + if err != nil { + return podGroupAlgorithmResult{ + status: fwk.AsStatus(fmt.Errorf("failed to assume pod group placement: %w", err)), + } + } + result := sched.podGroupSchedulingDefaultAlgorithm(ctx, schedFwk, podGroupCycleState, podGroupInfo, postFilterMode) + sched.nodeInfoSnapshot.ForgetPlacement() + if result.status.IsError() { + return result + } + + if anyResult == nil { + anyResult = &result + } + + if result.status.IsSuccess() || result.waitingOnPreemption { + successfulResults[placement] = &result + } + } + + if len(successfulResults) == 0 { + // We need to send events and set the status for pods in case all simulations were infeasible. + // The selection of which simulation we report is arbitrary for now, but may change in the future. + anyResult.status = fwk.NewStatus(fwk.Unschedulable, fmt.Sprintf("0/%d placements are available, first placement status: %v", len(placements), anyResult.status.AsError())) + return *anyResult + } + + if len(successfulResults) == 1 { + for _, result := range successfulResults { + return *result + } + } + + bestPlacement, status := sched.findBestPlacement(ctx, schedFwk, podGroupCycleState, podGroupInfo, successfulResults) + if !status.IsSuccess() { + return podGroupAlgorithmResult{ + status: status, + } + } + + return *successfulResults[bestPlacement] +} + +// findBestPlacement uses PlacementScore plugins to determine the best placement based on the scheduling results. +func (sched *Scheduler) findBestPlacement(ctx context.Context, schedFwk framework.Framework, podGroupCycleState fwk.PodGroupCycleState, podGroupInfo *framework.QueuedPodGroupInfo, successfulResults map[*fwk.Placement]*podGroupAlgorithmResult) (*fwk.Placement, *fwk.Status) { + placementPodGroupAssignments := makePodGroupAssignments(successfulResults) + + scores, status := schedFwk.RunPlacementScorePlugins(ctx, podGroupCycleState, podGroupInfo, placementPodGroupAssignments) + if !status.IsSuccess() { + return nil, status + } + + for i := range scores { + scores[i].Randomizer = rand.Int() + } + + loggerVTen := klog.FromContext(ctx).V(10) + if loggerVTen.Enabled() { + for _, score := range scores { + for _, pluginScore := range score.Scores { + loggerVTen.Info("Plugin scored placement for podGroup", "podGroup", klog.KObj(podGroupInfo), "plugin", pluginScore.Name, "placement", score.Placement.Name, "score", pluginScore.Score) + } + loggerVTen.Info("Calculated placement's final score for podGroup", "podGroup", klog.KObj(podGroupInfo), "placement", score.Placement.Name, "score", score.TotalScore) + } + } + + bestScore := &scores[0] + for _, score := range scores[1:] { + if score.TotalScore > bestScore.TotalScore || + score.TotalScore == bestScore.TotalScore && + score.Randomizer > bestScore.Randomizer { + bestScore = &score + } + } + return bestScore.Placement, nil +} + +func makePodGroupAssignments(successfulResults map[*fwk.Placement]*podGroupAlgorithmResult) []*fwk.PodGroupAssignments { + placementPodGroupAssignments := make([]*fwk.PodGroupAssignments, 0, len(successfulResults)) + for placement, result := range successfulResults { + proposedAssignments := make([]fwk.ProposedAssignment, 0) + for _, algorithmResult := range result.podResults { + if algorithmResult.GetNodeName() != "" { + proposedAssignments = append(proposedAssignments, &algorithmResult) + } + } + placementPodGroupAssignments = append(placementPodGroupAssignments, &fwk.PodGroupAssignments{ + Placement: placement, + ProposedAssignments: proposedAssignments, + }) + } + return placementPodGroupAssignments +} + +// podGroupSchedulingAlgorithm attempts to schedule pods in the pod group according to the policy and constraints and returns the scheduling result for each pod in the pod group. +func (sched *Scheduler) podGroupSchedulingAlgorithm(ctx context.Context, schedFwk framework.Framework, podGroupCycleState *framework.CycleState, podGroupInfo *framework.QueuedPodGroupInfo, postFilterMode podGroupPostFilterMode) podGroupAlgorithmResult { + podGroupCycleCtx, cancel := context.WithCancel(ctx) + defer cancel() + + if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareWorkloadScheduling) { + return sched.podGroupSchedulingPlacementAlgorithm(podGroupCycleCtx, schedFwk, podGroupCycleState, podGroupInfo, postFilterMode) + } else { + return sched.podGroupSchedulingDefaultAlgorithm(podGroupCycleCtx, schedFwk, podGroupCycleState, podGroupInfo, postFilterMode) + } +} diff --git a/pkg/scheduler/schedule_one_podgroup_test.go b/pkg/scheduler/schedule_one_podgroup_test.go new file mode 100644 index 0000000000000..9a0347dc6cae7 --- /dev/null +++ b/pkg/scheduler/schedule_one_podgroup_test.go @@ -0,0 +1,2711 @@ +/* +Copyright 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 scheduler + +import ( + "context" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + clientsetfake "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/events" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + fwk "k8s.io/kube-scheduler/framework" + schedulingapi "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/scheduler/apis/config" + internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" + fakecache "k8s.io/kubernetes/pkg/scheduler/backend/cache/fake" + internalqueue "k8s.io/kubernetes/pkg/scheduler/backend/queue" + "k8s.io/kubernetes/pkg/scheduler/framework" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort" + frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime" + "k8s.io/kubernetes/pkg/scheduler/profile" + st "k8s.io/kubernetes/pkg/scheduler/testing" + tf "k8s.io/kubernetes/pkg/scheduler/testing/framework" + "k8s.io/utils/ptr" +) + +// fakePodGroupPlugin simulates Filter, PostFilter, Permit and PodGroupPostFilter behaviors for PodGroup scheduling testing. +type fakePodGroupPlugin struct { + filterStatus map[string]*fwk.Status + postFilterResult map[string]*fwk.PostFilterResult + postFilterStatus map[string]*fwk.Status + postFilterCalled bool + permitStatus map[string]*fwk.Status + podGroupPostFilterStatus *fwk.Status + podGroupPostFilterCalled bool +} + +var _ fwk.FilterPlugin = &fakePodGroupPlugin{} +var _ fwk.PostFilterPlugin = &fakePodGroupPlugin{} +var _ fwk.PermitPlugin = &fakePodGroupPlugin{} +var _ framework.PodGroupPostFilterPlugin = &fakePodGroupPlugin{} + +func (mp *fakePodGroupPlugin) Name() string { return "FakePodGroupPlugin" } + +func (mp *fakePodGroupPlugin) Filter(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status { + if status, ok := mp.filterStatus[pod.Name]; ok { + return status + } + return fwk.NewStatus(fwk.Unschedulable, "default fake filter failure") +} + +func (mp *fakePodGroupPlugin) PostFilter(ctx context.Context, state fwk.CycleState, pod *v1.Pod, filteredNodeStatusMap fwk.NodeToStatusReader) (*fwk.PostFilterResult, *fwk.Status) { + mp.postFilterCalled = true + if status, ok := mp.postFilterStatus[pod.Name]; ok { + return mp.postFilterResult[pod.Name], status + } + return &fwk.PostFilterResult{NominatingInfo: clearNominatedNode}, fwk.NewStatus(fwk.Unschedulable, "default fake postfilter failure") +} + +func (mp *fakePodGroupPlugin) Permit(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (*fwk.Status, time.Duration) { + if status, ok := mp.permitStatus[pod.Name]; ok { + return status, 0 + } + return fwk.NewStatus(fwk.Unschedulable, "default fake permit failure"), 0 +} + +func (mp *fakePodGroupPlugin) PodGroupPostFilter(ctx context.Context, pg *schedulingv1alpha2.PodGroup, pods []*v1.Pod, pgSchedulingFunc func(ctx context.Context) *fwk.Status) *fwk.Status { + mp.podGroupPostFilterCalled = true + if mp.podGroupPostFilterStatus != nil { + return mp.podGroupPostFilterStatus + } + return fwk.NewStatus(fwk.Unschedulable, "default fake podgroup postfilter failure") +} + +func TestPodGroupInfoForPod(t *testing.T) { + groupName := "pg" + p1 := st.MakePod().Name("p1").Namespace("ns1").UID("p1").PodGroupName(groupName).Priority(100).Obj() + p2 := st.MakePod().Name("p2").Namespace("ns1").UID("p2").PodGroupName(groupName).Priority(200).Obj() + p3 := st.MakePod().Name("p3").Namespace("ns1").UID("p3").PodGroupName(groupName).Priority(150).Obj() + p4 := st.MakePod().Name("p4").Namespace("ns1").UID("p4").PodGroupName(groupName).Priority(150).Obj() + + pInfo1, _ := framework.NewPodInfo(p1) + pInfo2, _ := framework.NewPodInfo(p2) + pInfo3, _ := framework.NewPodInfo(p3) + pInfo4, _ := framework.NewPodInfo(p4) + + now := time.Now() + qInfo1 := &framework.QueuedPodInfo{PodInfo: pInfo1, InitialAttemptTimestamp: ptr.To(now)} + qInfo2 := &framework.QueuedPodInfo{PodInfo: pInfo2, InitialAttemptTimestamp: ptr.To(now.Add(time.Second))} + qInfo3 := &framework.QueuedPodInfo{PodInfo: pInfo3, InitialAttemptTimestamp: ptr.To(now.Add(2 * time.Second))} + qInfo4 := &framework.QueuedPodInfo{PodInfo: pInfo4, InitialAttemptTimestamp: ptr.To(now.Add(time.Second))} + + tests := []struct { + name string + pInfo *framework.QueuedPodInfo + unscheduledPods map[string]*v1.Pod + queuePods map[types.UID]*framework.QueuedPodInfo + expectedPods []string + }{ + { + name: "Success with multiple pods, sorted by priority and timestamp", + pInfo: qInfo1, + unscheduledPods: map[string]*v1.Pod{ + "p2": p2, + "p3": p3, + "p4": p4, + }, + queuePods: map[types.UID]*framework.QueuedPodInfo{ + "p2": qInfo2, + "p3": qInfo3, + "p4": qInfo4, + }, + expectedPods: []string{"p2", "p4", "p3", "p1"}, + }, + { + name: "Pods not in state or not in queue are skipped", + pInfo: qInfo1, + unscheduledPods: map[string]*v1.Pod{ + "p2": p2, + "p3": p3, + // p4 missing from unscheduled pods + }, + queuePods: map[types.UID]*framework.QueuedPodInfo{ + "p2": qInfo2, + // p3 missing from queue + "p4": qInfo4, + }, + expectedPods: []string{"p2", "p1"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + cache := internalcache.New(ctx, nil, true) + + cache.AddPodGroupMember(tt.pInfo.Pod) + for _, pod := range tt.unscheduledPods { + cache.AddPodGroupMember(pod) + } + + q := internalqueue.NewTestQueue(ctx, nil) + for _, pInfo := range tt.queuePods { + err := q.AddUnschedulableIfNotPresent(logger, pInfo, 0) + if err != nil { + t.Fatalf("Failed to add unschedulable pod: %v", err) + } + } + sched := &Scheduler{ + Cache: cache, + SchedulingQueue: q, + } + + result, err := sched.podGroupInfoForPod(ctx, tt.pInfo) + if err != nil { + t.Fatalf("Failed to get pod group info: %v", err) + } + + if diff := cmp.Diff(groupName, result.PodGroupInfo.Name); diff != "" { + t.Errorf("Unexpected pod group name (-want,+got):\n%s", diff) + } + + var gotQueuedPods []string + for _, pInfo := range result.QueuedPodInfos { + gotQueuedPods = append(gotQueuedPods, pInfo.Pod.Name) + } + if diff := cmp.Diff(tt.expectedPods, gotQueuedPods); diff != "" { + t.Errorf("Unexpected pod order in QueuedPodInfos (-want,+got):\n%s", diff) + } + + var gotUnscheduledPods []string + for _, pod := range result.UnscheduledPods { + gotUnscheduledPods = append(gotUnscheduledPods, pod.Name) + } + if diff := cmp.Diff(tt.expectedPods, gotUnscheduledPods); diff != "" { + t.Errorf("Unexpected pod order in UnscheduledPods (-want,+got):\n%s", diff) + } + }) + } +} + +func TestFrameworkForPodGroup(t *testing.T) { + p1 := st.MakePod().Name("p1").PodGroupName("pg").SchedulerName("sched1").Obj() + p2 := st.MakePod().Name("p2").PodGroupName("pg").SchedulerName("sched1").Obj() + p3 := st.MakePod().Name("p3").PodGroupName("pg").SchedulerName("sched2").Obj() + + qInfo1 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p1}} + qInfo2 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p2}} + qInfo3 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p3}} + + tests := []struct { + name string + pods []*framework.QueuedPodInfo + profiles profile.Map + expectError bool + }{ + { + name: "success for same scheduler name", + pods: []*framework.QueuedPodInfo{qInfo1, qInfo2}, + profiles: profile.Map{ + "sched1": nil, + }, + expectError: false, + }, + { + name: "failure for different scheduler names", + pods: []*framework.QueuedPodInfo{qInfo1, qInfo3}, + profiles: profile.Map{ + "sched1": nil, + "sched2": nil, + }, + expectError: true, + }, + { + name: "failure when profile not found", + pods: []*framework.QueuedPodInfo{qInfo1}, + profiles: profile.Map{ + "other": nil, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sched := &Scheduler{Profiles: tt.profiles} + podGroupInfo := &framework.QueuedPodGroupInfo{QueuedPodInfos: tt.pods} + _, err := sched.frameworkForPodGroup(podGroupInfo) + if tt.expectError { + if err == nil { + t.Errorf("Expected error, but got nil") + } + } else { + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + } + }) + } +} + +func TestSkipPodGroupPodSchedule(t *testing.T) { + p1 := st.MakePod().Name("p1").UID("p1").PodGroupName("pg").Obj() + p2 := st.MakePod().Name("p2").UID("p2").PodGroupName("pg").Terminating().Obj() + p3 := st.MakePod().Name("p3").UID("p3").PodGroupName("pg").Obj() + + qInfo1 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p1}} + qInfo2 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p2}} + qInfo3 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p3}} + + podGroupInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{qInfo1, qInfo2, qInfo3}, + PodGroupInfo: &framework.PodGroupInfo{ + UnscheduledPods: []*v1.Pod{p1, p2, p3}, + }, + } + + logger, ctx := ktesting.NewTestContext(t) + + cache := internalcache.New(ctx, nil, true) + registry := frameworkruntime.Registry{ + queuesort.Name: queuesort.New, + defaultbinder.Name: defaultbinder.New, + } + profileCfg := config.KubeSchedulerProfile{ + SchedulerName: "default-scheduler", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: queuesort.Name}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: defaultbinder.Name}}, + }, + }, + } + schedFwk, err := frameworkruntime.NewFramework(ctx, registry, &profileCfg, + frameworkruntime.WithEventRecorder(events.NewFakeRecorder(100)), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + sched := &Scheduler{ + SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), + Cache: cache, + } + + // Assume pod p3 + err = cache.AssumePod(logger, p3) + if err != nil { + t.Fatalf("Failed to assume pod p3: %v", err) + } + + sched.skipPodGroupPodSchedule(ctx, schedFwk, podGroupInfo) + + if len(podGroupInfo.QueuedPodInfos) != 1 { + t.Errorf("Expected 1 queued pod left, got %d", len(podGroupInfo.QueuedPodInfos)) + } + if podGroupInfo.QueuedPodInfos[0].Pod.Name != "p1" { + t.Errorf("Expected p1 to be left in queued pods, got %s", podGroupInfo.QueuedPodInfos[0].Pod.Name) + } + if len(podGroupInfo.UnscheduledPods) != 1 { + t.Errorf("Expected 1 unscheduled pod left, got %d", len(podGroupInfo.UnscheduledPods)) + } + if podGroupInfo.UnscheduledPods[0].Name != "p1" { + t.Errorf("Expected p1 to be left in unscheduled pods, got %s", podGroupInfo.UnscheduledPods[0].Name) + } +} + +func TestPodGroupCycle_UpdateSnapshotError(t *testing.T) { + p1 := st.MakePod().Name("p1").UID("p1").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + p2 := st.MakePod().Name("p2").UID("p2").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + qInfo1 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p1}} + qInfo2 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p2}} + + testPodGroup := &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: "default"}, + } + + podGroupInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{qInfo1, qInfo2}, + PodGroupInfo: &framework.PodGroupInfo{ + Name: "pg", + Namespace: "default", + UnscheduledPods: []*v1.Pod{p1, p2}, + }, + } + + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + registry := frameworkruntime.Registry{ + queuesort.Name: queuesort.New, + defaultbinder.Name: defaultbinder.New, + } + profileCfg := config.KubeSchedulerProfile{ + SchedulerName: "test-scheduler", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: queuesort.Name}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: defaultbinder.Name}}, + }, + }, + } + schedFwk, err := frameworkruntime.NewFramework(ctx, registry, &profileCfg, + frameworkruntime.WithEventRecorder(events.NewFakeRecorder(100)), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + + // Create fake cache that returns error on UpdateSnapshot + updateSnapshotErr := fmt.Errorf("update snapshot error") + cache := &fakecache.Cache{ + Cache: internalcache.New(ctx, nil, true), + UpdateSnapshotFunc: func(nodeSnapshot *internalcache.Snapshot) error { + return updateSnapshotErr + }, + } + + client := clientsetfake.NewClientset(testPodGroup) + informerFactory := informers.NewSharedInformerFactory(client, 0) + podGroupLister := informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + + var failureHandlerCalled bool + sched := &Scheduler{ + Profiles: profile.Map{"test-scheduler": schedFwk}, + SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), + Cache: cache, + client: client, + podGroupLister: podGroupLister, + FailureHandler: func(ctx context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, ni *fwk.NominatingInfo, start time.Time) { + failureHandlerCalled = true + if updateSnapshotErr.Error() != status.AsError().Error() { + t.Errorf("Expected status error %q, got %q", updateSnapshotErr, status.AsError()) + } + }, + } + + sched.podGroupCycle(ctx, schedFwk, framework.NewCycleState(), podGroupInfo) + + if !failureHandlerCalled { + t.Errorf("Expected FailureHandler to be called after UpdateSnapshot failed") + } +} + +func TestPodGroupCycle_PodGroupPostFilter(t *testing.T) { + tests := []struct { + name string + wapFeatureGateEnabled bool + postFilterPlugin string + expectedPodGroupPostFilterCalled bool + }{ + { + name: "runs pod group post filter when WAP is enabled and DefaultPreemption is registered", + wapFeatureGateEnabled: true, + postFilterPlugin: "DefaultPreemption", + expectedPodGroupPostFilterCalled: true, + }, + { + name: "disables pod group post filter when WAP feature gate is disabled", + wapFeatureGateEnabled: false, + postFilterPlugin: "DefaultPreemption", + expectedPodGroupPostFilterCalled: false, + }, + { + name: "disables pod group post filter when DefaultPreemption is not registered", + wapFeatureGateEnabled: true, + postFilterPlugin: "FakePodGroupPlugin", + expectedPodGroupPostFilterCalled: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Enable feature gates + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.WorkloadAwarePreemption: tt.wapFeatureGateEnabled, + features.GangScheduling: true, + }) + + testPodGroup := st.MakePodGroup().Name("pg").Namespace("default").Obj() + p1 := st.MakePod().Name("p1").UID("p1").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + p2 := st.MakePod().Name("p2").UID("p2").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + testNode := st.MakeNode().Name("node1").UID("node1").Obj() + + qInfo1 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p1}} + qInfo2 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p2}} + + podGroupInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{qInfo1, qInfo2}, + PodGroupInfo: &framework.PodGroupInfo{ + Name: "pg", + Namespace: "default", + UnscheduledPods: []*v1.Pod{p1, p2}, + }, + } + + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + fakePlugin := &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable, "always fail p1"), + "p2": fwk.NewStatus(fwk.Unschedulable, "always fail p2"), + }, + podGroupPostFilterStatus: nil, + } + + registry := frameworkruntime.Registry{ + queuesort.Name: queuesort.New, + defaultbinder.Name: defaultbinder.New, + "FakePodGroupPlugin": func(ctx context.Context, obj runtime.Object, handle fwk.Handle) (fwk.Plugin, error) { + return fakePlugin, nil + }, + } + + if tt.postFilterPlugin == "DefaultPreemption" { + // Register the same plugin as DefaultPreemption to fulfill runWorkloadAwarePreemption requirements. + registry["DefaultPreemption"] = func(ctx context.Context, obj runtime.Object, handle fwk.Handle) (fwk.Plugin, error) { + return &fakeDefaultPreemption{fakePodGroupPlugin: fakePlugin}, nil + } + } + + profileCfg := config.KubeSchedulerProfile{ + SchedulerName: "test-scheduler", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: queuesort.Name}}, + }, + Filter: config.PluginSet{ + Enabled: []config.Plugin{{Name: "FakePodGroupPlugin"}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: defaultbinder.Name}}, + }, + PostFilter: config.PluginSet{ + Enabled: []config.Plugin{{Name: tt.postFilterPlugin}}, + }, + }, + } + + client := clientsetfake.NewSimpleClientset(testPodGroup, testNode) + informerFactory := informers.NewSharedInformerFactory(client, 0) + podGroupLister := informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory) + snapshot := internalcache.NewEmptySnapshot() + + schedFwk, err := frameworkruntime.NewFramework(ctx, registry, &profileCfg, + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithSnapshotSharedLister(snapshot), + frameworkruntime.WithPodNominator(queue), + frameworkruntime.WithClientSet(client), + frameworkruntime.WithEventRecorder(events.NewFakeRecorder(100)), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + + cache := internalcache.New(ctx, nil, true) + logger, ctx := ktesting.NewTestContext(t) + cache.AddNode(logger, testNode) + + sched := &Scheduler{ + Profiles: profile.Map{"test-scheduler": schedFwk}, + SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), + Cache: cache, + client: client, + podGroupLister: podGroupLister, + nodeInfoSnapshot: internalcache.NewEmptySnapshot(), + workloadAwarePreemptionEnabled: tt.wapFeatureGateEnabled, + FailureHandler: func(ctx context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, ni *fwk.NominatingInfo, start time.Time) { + }, + } + + sched.SchedulePod = sched.schedulePod + sched.podGroupCycle(ctx, schedFwk, framework.NewCycleState(), podGroupInfo) + + if fakePlugin.podGroupPostFilterCalled != tt.expectedPodGroupPostFilterCalled { + t.Errorf("Expected workload aware preemption (PodGroupPostFilter) to be %v, but got %v", tt.expectedPodGroupPostFilterCalled, fakePlugin.podGroupPostFilterCalled) + } + }) + } +} + +func TestPodGroupSchedulingAlgorithm(t *testing.T) { + testNode := st.MakeNode().Name("node1").UID("node1").Obj() + + p1 := st.MakePod().Name("p1").UID("p1").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + p2 := st.MakePod().Name("p2").UID("p2").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + p3 := st.MakePod().Name("p3").UID("p3").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + + qInfo1 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p1}} + qInfo2 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p2}} + qInfo3 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p3}} + + podGroupInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{qInfo1, qInfo2, qInfo3}, + PodGroupInfo: &framework.PodGroupInfo{ + Name: "pg", + UnscheduledPods: []*v1.Pod{p1, p2, p3}, + }, + } + + tests := []struct { + name string + plugin *fakePodGroupPlugin + expectedGroupStatusCode fwk.Code + expectedGroupWaitingOnPreemption bool + expectedPodStatus map[string]*fwk.Status + expectedPreemption map[string]bool + postFilterMode podGroupPostFilterMode + skipForTAS bool + }{ + { + name: "All pods feasible", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Success, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + { + name: "All pods feasible, two waiting", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Wait), + "p2": fwk.NewStatus(fwk.Wait), + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Success, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + { + name: "All pods feasible, but all waiting", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Wait), + "p2": fwk.NewStatus(fwk.Wait), + "p3": fwk.NewStatus(fwk.Wait), + }, + }, + expectedGroupStatusCode: fwk.Unschedulable, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + { + name: "All pods feasible, but last waiting", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": fwk.NewStatus(fwk.Wait), + }, + }, + expectedGroupStatusCode: fwk.Success, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + { + name: "All pods feasible, one waiting, one unschedulable", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Wait), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Success, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": nil, + }, + }, + { + name: "All pods require preemption", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": fwk.NewStatus(fwk.Unschedulable), + }, + postFilterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + postFilterResult: map[string]*fwk.PostFilterResult{ + "p1": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + "p2": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + "p3": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Unschedulable, + expectedGroupWaitingOnPreemption: true, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": fwk.NewStatus(fwk.Unschedulable), + }, + expectedPreemption: map[string]bool{ + "p1": true, + "p2": true, + "p3": true, + }, + skipForTAS: true, + }, + { + name: "All pods require preemption, but waiting", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": fwk.NewStatus(fwk.Unschedulable), + }, + postFilterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + postFilterResult: map[string]*fwk.PostFilterResult{ + "p1": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + "p2": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + "p3": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + }, + permitStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Wait), + "p2": fwk.NewStatus(fwk.Wait), + "p3": fwk.NewStatus(fwk.Wait), + }, + }, + expectedGroupStatusCode: fwk.Unschedulable, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": fwk.NewStatus(fwk.Unschedulable), + }, + expectedPreemption: map[string]bool{ + "p1": true, + "p2": true, + "p3": true, + }, + // preemption is not yet implemented for TAS + skipForTAS: true, + }, + { + name: "One pod requires preemption, but waiting, two are feasible", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": nil, + "p3": nil, + }, + postFilterStatus: map[string]*fwk.Status{ + "p1": nil, + }, + postFilterResult: map[string]*fwk.PostFilterResult{ + "p1": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + }, + permitStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Wait), + "p2": fwk.NewStatus(fwk.Wait), + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Unschedulable, + expectedGroupWaitingOnPreemption: true, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": nil, + "p3": nil, + }, + expectedPreemption: map[string]bool{ + "p1": true, + "p2": false, + "p3": false, + }, + // preemption is not yet implemented for TAS + skipForTAS: true, + }, + { + name: "One pod unschedulable, one requires preemption, one feasible", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": nil, + }, + postFilterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Unschedulable), + }, + postFilterResult: map[string]*fwk.PostFilterResult{ + "p1": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + "p2": {NominatingInfo: clearNominatedNode}, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Unschedulable, + expectedGroupWaitingOnPreemption: true, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": nil, + }, + expectedPreemption: map[string]bool{ + "p1": true, + "p2": false, + "p3": false, + }, + // preemption is not yet implemented for TAS + skipForTAS: true, + }, + { + name: "All pods unschedulable", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.UnschedulableAndUnresolvable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": fwk.NewStatus(fwk.Unschedulable), + }, + }, + expectedGroupStatusCode: fwk.Unschedulable, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Unschedulable), + "p3": fwk.NewStatus(fwk.Unschedulable), + }, + }, + { + name: "Any filter returned Error", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Error), + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Error, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Error), + // The algorithm stopped evaluating the pods after an error occurred, so a "p3" status is not expected. + }, + }, + { + name: "Any permit returned Error", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Error), + "p3": nil, + }, + }, + expectedGroupStatusCode: fwk.Error, + expectedPodStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Error), + // The algorithm stopped evaluating the pods after an error occurred, so a "p3" status is not expected. + }, + }, + { + name: "Any filter returned Error while waiting on preemption", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Error), + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": nil, + "p3": nil, + }, + postFilterResult: map[string]*fwk.PostFilterResult{ + "p1": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + }, + }, + expectedGroupStatusCode: fwk.Error, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Error), + // The algorithm stopped evaluating the pods after an error occurred, so a "p3" status is not expected. + }, + }, + { + name: "Any permit returned Error while waiting on preemption", + plugin: &fakePodGroupPlugin{ + filterStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": nil, + "p3": nil, + }, + permitStatus: map[string]*fwk.Status{ + "p1": nil, + "p2": fwk.NewStatus(fwk.Error), + "p3": nil, + }, + postFilterResult: map[string]*fwk.PostFilterResult{ + "p1": {NominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}}, + }, + }, + expectedGroupStatusCode: fwk.Error, + expectedPodStatus: map[string]*fwk.Status{ + "p1": fwk.NewStatus(fwk.Unschedulable), + "p2": fwk.NewStatus(fwk.Error), + // The algorithm stopped evaluating the pods after an error occurred, so a "p3" status is not expected. + }, + }, + } + + for _, tasEnabled := range []bool{true, false} { + for _, tt := range tests { + if tasEnabled && tt.skipForTAS { + continue + } + name := fmt.Sprintf("%s (TopologyAwareWorkloadScheduling=%v)", tt.name, tasEnabled) + t.Run(name, func(t *testing.T) { + if tasEnabled { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.TopologyAwareWorkloadScheduling: true, + features.GenericWorkload: true, + }) + } + + logger, ctx := ktesting.NewTestContext(t) + + client := clientsetfake.NewClientset(testNode) + informerFactory := informers.NewSharedInformerFactory(client, 0) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory) + snapshot := internalcache.NewEmptySnapshot() + + registry := []tf.RegisterPluginFunc{ + tf.RegisterFilterPlugin(tt.plugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return tt.plugin, nil + }), + tf.RegisterPostFilterPlugin(tt.plugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return tt.plugin, nil + }), + tf.RegisterPermitPlugin(tt.plugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return tt.plugin, nil + }), + } + schedFwk, err := tf.NewFramework(ctx, + append(registry, + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ), + "test-scheduler", + frameworkruntime.WithClientSet(client), + frameworkruntime.WithEventRecorder(events.NewFakeRecorder(100)), + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithSnapshotSharedLister(snapshot), + frameworkruntime.WithPodNominator(queue), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + + cache := internalcache.New(ctx, nil, true) + cache.AddNode(logger, testNode) + + sched := &Scheduler{ + Cache: cache, + nodeInfoSnapshot: snapshot, + SchedulingQueue: queue, + Profiles: profile.Map{"test-scheduler": schedFwk}, + } + sched.SchedulePod = sched.schedulePod + + if err := sched.Cache.UpdateSnapshot(logger, sched.nodeInfoSnapshot); err != nil { + t.Fatalf("Failed to update snapshot: %v", err) + } + + result := sched.podGroupSchedulingAlgorithm(ctx, schedFwk, framework.NewCycleState(), podGroupInfo, runAllPostFilters) + + if result.status.Code() != tt.expectedGroupStatusCode { + t.Errorf("Expected group status code: %v, got: %v", tt.expectedGroupStatusCode, result.status.Code()) + } + if result.waitingOnPreemption != tt.expectedGroupWaitingOnPreemption { + t.Errorf("Expected group waiting on preemption: %v, got: %v", tt.expectedGroupWaitingOnPreemption, result.waitingOnPreemption) + } + if len(tt.expectedPodStatus) != len(result.podResults) { + t.Errorf("Expected %d pod results, got %d", len(tt.expectedPodStatus), len(result.podResults)) + } + for i, podResult := range result.podResults { + podName := podGroupInfo.QueuedPodInfos[i].Pod.Name + if expected, ok := tt.expectedPodStatus[podName]; ok { + if podResult.status.Code() != expected.Code() { + t.Errorf("Expected pod %s status code: %v, got: %v", podName, expected.Code(), podResult.status.Code()) + } + } else { + t.Errorf("Got result for unexpected pod %s: %v", podName, podResult.status.Code()) + } + if podResult.status.IsSuccess() || podResult.requiresPreemption { + if podResult.scheduleResult.SuggestedHost != "node1" { + t.Errorf("Expected pod %s suggested host: node1, got: %v", podName, podResult.scheduleResult.SuggestedHost) + } + if expected, ok := tt.plugin.permitStatus[podName]; ok { + if podResult.permitStatus.Code() != expected.Code() { + t.Errorf("Expected pod %s permit status code: %v, got: %v", podName, expected.Code(), podResult.permitStatus.Code()) + } + } + } else { + if podResult.scheduleResult.SuggestedHost != "" { + t.Errorf("Expected pod %s empty suggested host, got: %v", podName, podResult.scheduleResult.SuggestedHost) + } + if podResult.permitStatus != nil { + t.Errorf("Expected pod %s nil permit status, got: %v", podName, podResult.permitStatus) + } + } + if expected, ok := tt.expectedPreemption[podName]; ok { + if podResult.requiresPreemption != expected { + t.Errorf("Expected pod %s requiresPreemption: %v, got: %v", podName, expected, podResult.requiresPreemption) + } + } + } + }) + } + } +} + +func TestSubmitPodGroupAlgorithmResult(t *testing.T) { + testNode := st.MakeNode().Name("node1").UID("node1").Obj() + + p1 := st.MakePod().Name("p1").UID("p1").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + p2 := st.MakePod().Name("p2").UID("p2").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + p3 := st.MakePod().Name("p3").UID("p3").PodGroupName("pg").SchedulerName("test-scheduler").Obj() + + qInfo1 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p1}} + qInfo2 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p2}} + qInfo3 := &framework.QueuedPodInfo{PodInfo: &framework.PodInfo{Pod: p3}} + + testPodGroup := &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: "default"}, + } + + podGroupInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{qInfo1, qInfo2, qInfo3}, + PodGroupInfo: &framework.PodGroupInfo{ + Name: "pg", + Namespace: "default", + UnscheduledPods: []*v1.Pod{p1, p2, p3}, + }, + } + + tests := []struct { + name string + existingPodGroup *schedulingv1alpha2.PodGroup + algorithmResult podGroupAlgorithmResult + expectBound sets.Set[string] + expectPreempting sets.Set[string] + expectFailed sets.Set[string] + expectCondition *metav1.Condition + }{ + { + name: "All pods feasible", + algorithmResult: podGroupAlgorithmResult{ + status: nil, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }}, + }, + expectBound: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + }, + }, + { + name: "All pods feasible, but all waiting", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "not enough capacity for the gang"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: fwk.NewStatus(fwk.Wait), + }}, + }, + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity for the gang", + }, + }, + { + name: "All pods feasible, but last waiting", + algorithmResult: podGroupAlgorithmResult{ + status: nil, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: fwk.NewStatus(fwk.Wait), + }}, + }, + expectBound: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + }, + }, + { + name: "All pods feasible, one waiting, one unschedulable", + algorithmResult: podGroupAlgorithmResult{ + status: nil, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }}, + }, + expectBound: sets.New("p1", "p3"), + expectFailed: sets.New("p2"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + }, + }, + { + name: "All pods require preemption", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "waiting for preemption to complete"), + waitingOnPreemption: true, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: nil, + }}, + }, + expectPreempting: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "waiting for preemption to complete", + }, + }, + { + name: "All pods require preemption, but waiting", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "preemption required but not feasible"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: fwk.NewStatus(fwk.Wait), + }}, + }, + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "preemption required but not feasible", + }, + }, + { + name: "One pod requires preemption, but waiting, two are feasible", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "waiting for preemption to complete"), + waitingOnPreemption: true, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: fwk.NewStatus(fwk.Wait), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }}, + }, + expectPreempting: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "waiting for preemption to complete", + }, + }, + { + name: "One pod unschedulable, one requires preemption, one feasible", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "waiting for preemption to complete"), + waitingOnPreemption: true, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + }}, + }, + expectPreempting: sets.New("p1", "p3"), + expectFailed: sets.New("p2"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "waiting for preemption to complete", + }, + }, + { + name: "All pods unschedulable", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "0/3 nodes are available: insufficient cpu"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }}, + }, + expectBound: sets.New[string](), + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "0/3 nodes are available: insufficient cpu", + }, + }, + { + name: "All pods unschedulable with nil nominatingInfo", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "0/3 nodes are available: insufficient cpu"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: nil}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: nil}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: nil}, + status: fwk.NewStatus(fwk.Unschedulable), + }}, + }, + expectBound: sets.New[string](), + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "0/3 nodes are available: insufficient cpu", + }, + }, + { + name: "Unschedulable for the entire pod group", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "node affinity mismatch"), + podResults: []algorithmResult{}, + }, + expectBound: sets.New[string](), + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "node affinity mismatch", + }, + }, + { + name: "Error for one pod", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Error, "plugin returned error"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Error, "plugin returned error"), + }}, + }, + expectBound: sets.New[string](), + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "plugin returned error", + }, + }, + { + name: "Error for one pod while waiting on preemption", + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Error, "internal failure"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{ + SuggestedHost: "node1", + nominatingInfo: &fwk.NominatingInfo{NominatedNodeName: "node1", NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable), + requiresPreemption: true, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Error, "internal failure"), + }}, + }, + expectBound: sets.New[string](), + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "internal failure", + }, + }, + { + name: "Already Scheduled, successful cycle keeps condition", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: "default"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + LastTransitionTime: metav1.Now(), + }}, + }, + }, + algorithmResult: podGroupAlgorithmResult{ + status: nil, + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }}, + }, + expectBound: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + }, + }, + { + name: "Already Scheduled, rejected cycle does not regress condition", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: "default"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + LastTransitionTime: metav1.Now(), + }}, + }, + }, + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "extra pods could not be placed"), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Unschedulable), + }}, + }, + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + }, + }, + { + name: "Already Scheduled, error cycle does not regress condition", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: "default"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + LastTransitionTime: metav1.Now(), + }}, + }, + }, + algorithmResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Error), + podResults: []algorithmResult{{ + scheduleResult: ScheduleResult{SuggestedHost: "node1"}, + status: nil, + permitStatus: nil, + }, { + scheduleResult: ScheduleResult{SuggestedHost: "", nominatingInfo: clearNominatedNode}, + status: fwk.NewStatus(fwk.Error), + }}, + }, + expectBound: sets.New[string](), + expectFailed: sets.New("p1", "p2", "p3"), + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + var lock sync.Mutex + boundPods := sets.New[string]() + preemptingPods := sets.New[string]() + failedPods := sets.New[string]() + + pg := testPodGroup + if tt.existingPodGroup != nil { + pg = tt.existingPodGroup + } + client := clientsetfake.NewClientset(testNode, pg) + client.PrependReactor("create", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + if action.GetSubresource() != "binding" { + return false, nil, nil + } + lock.Lock() + binding := action.(clienttesting.CreateAction).GetObject().(*v1.Binding) + boundPods.Insert(binding.Name) + lock.Unlock() + return true, binding, nil + }) + + registry := frameworkruntime.Registry{ + queuesort.Name: queuesort.New, + defaultbinder.Name: defaultbinder.New, + } + profileCfg := config.KubeSchedulerProfile{ + SchedulerName: "test-scheduler", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: queuesort.Name}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: defaultbinder.Name}}, + }, + }, + } + waitingPods := frameworkruntime.NewWaitingPodsMap() + podInPreBind := frameworkruntime.NewPodsInPreBindMap() + schedFwk, err := frameworkruntime.NewFramework(ctx, registry, &profileCfg, + frameworkruntime.WithClientSet(client), + frameworkruntime.WithEventRecorder(events.NewFakeRecorder(100)), + frameworkruntime.WithWaitingPods(waitingPods), + frameworkruntime.WithPodsInPreBind(podInPreBind), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + + cache := internalcache.New(ctx, nil, true) + cache.AddNode(klog.FromContext(ctx), testNode) + + informerFactory := informers.NewSharedInformerFactory(client, 0) + podGroupLister := informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + + sched := &Scheduler{ + client: client, + podGroupLister: podGroupLister, + Cache: cache, + Profiles: profile.Map{"test-scheduler": schedFwk}, + SchedulingQueue: internalqueue.NewTestQueue(ctx, nil), + FailureHandler: func(ctx context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, ni *fwk.NominatingInfo, start time.Time) { + lock.Lock() + if ni != nil && ni.NominatedNodeName != "" { + preemptingPods.Insert(p.Pod.Name) + } else { + failedPods.Insert(p.Pod.Name) + } + lock.Unlock() + }, + } + + podGroupCycleState := framework.NewCycleState() + + for i := range tt.algorithmResult.podResults { + pod := podGroupInfo.QueuedPodInfos[i].Pod + podCtx := initPodSchedulingContext(ctx, pod, podGroupCycleState, runAllPostFilters) + tt.algorithmResult.podResults[i].podCtx = podCtx + } + + sched.submitPodGroupAlgorithmResult(ctx, schedFwk, podGroupCycleState, podGroupInfo, tt.algorithmResult, time.Now()) + + if err := wait.PollUntilContextTimeout(ctx, time.Millisecond*200, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) { + lock.Lock() + defer lock.Unlock() + return len(boundPods)+len(preemptingPods)+len(failedPods) == len(podGroupInfo.QueuedPodInfos), nil + }); err != nil { + t.Errorf("Failed waiting for all pods to be either bound or failed") + } + + if !tt.expectBound.Equal(boundPods) { + t.Errorf("Expected bound pods: %v, but got: %v", tt.expectBound, boundPods) + } + if !tt.expectPreempting.Equal(preemptingPods) { + t.Errorf("Expected preempting pods: %v, but got: %v", tt.expectPreempting, preemptingPods) + } + if !tt.expectFailed.Equal(failedPods) { + t.Errorf("Expected failed pods: %v, but got: %v", tt.expectFailed, failedPods) + } + + updatedPodGroup, err := client.SchedulingV1alpha2().PodGroups("default").Get(ctx, "pg", metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get PodGroup: %v", err) + } + cond := apimeta.FindStatusCondition(updatedPodGroup.Status.Conditions, schedulingapi.PodGroupScheduled) + if diff := cmp.Diff(tt.expectCondition, cond, cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime")); diff != "" { + t.Errorf("Unexpected PodGroupScheduled condition (-want +got):\n%s", diff) + } + }) + } +} + +func TestUpdatePodGroupCondition(t *testing.T) { + tests := []struct { + name string + existingPodGroup *schedulingv1alpha2.PodGroup + namespace string + podGroupName string + condition *metav1.Condition + expectCondition *metav1.Condition + // expectLastTransitionTimeUnchanged, when true, verifies that LastTransitionTime + // is preserved from the existing condition. + expectLastTransitionTimeUnchanged bool + }{ + { + name: "set Scheduled condition to True on empty status", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg1", Namespace: "ns1"}, + }, + namespace: "ns1", + podGroupName: "pg1", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "SomeReason", + Message: "All required pods have been successfully scheduled", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "SomeReason", + Message: "All required pods have been successfully scheduled", + }, + }, + { + name: "set Scheduled condition to False with Unschedulable reason", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg2", Namespace: "ns1"}, + }, + namespace: "ns1", + podGroupName: "pg2", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "0/3 nodes are available: insufficient cpu", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "0/3 nodes are available: insufficient cpu", + }, + }, + { + name: "set Scheduled condition to False with SchedulerError reason", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg3", Namespace: "ns1"}, + }, + namespace: "ns1", + podGroupName: "pg3", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "Internal scheduling error", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "Internal scheduling error", + }, + }, + { + name: "transition from Unschedulable to Scheduled", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg4", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "previously unschedulable", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg4", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All required pods have been successfully scheduled", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All required pods have been successfully scheduled", + }, + }, + { + name: "transition from SchedulerError to Scheduled", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-se-to-true", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "internal error", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg-se-to-true", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All required pods have been successfully scheduled", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All required pods have been successfully scheduled", + }, + }, + { + name: "do not regress Scheduled to Unschedulable", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-true-to-unsched", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg-true-to-unsched", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "extra pods could not be placed", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + }, + expectLastTransitionTimeUnchanged: true, + }, + { + name: "do not regress Scheduled to SchedulerError", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-true-to-se", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg-true-to-se", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "internal error", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + }, + expectLastTransitionTimeUnchanged: true, + }, + { + name: "transition from Unschedulable to SchedulerError preserves LastTransitionTime", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-unsched-to-se", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough resources", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg-unsched-to-se", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "internal error", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "internal error", + }, + expectLastTransitionTimeUnchanged: true, + }, + { + name: "transition from SchedulerError to Unschedulable preserves LastTransitionTime", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-se-to-unsched", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonSchedulerError, + Message: "internal error", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg-se-to-unsched", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough resources", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough resources", + }, + expectLastTransitionTimeUnchanged: true, + }, + { + name: "Scheduled to Scheduled preserves LastTransitionTime", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-true-to-true", Namespace: "ns1"}, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + LastTransitionTime: metav1.Now(), + }, + }, + }, + }, + namespace: "ns1", + podGroupName: "pg-true-to-true", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "New condition message", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "New condition message", + }, + expectLastTransitionTimeUnchanged: true, + }, + { + name: "PodGroup not found does not panic", + namespace: "ns1", + podGroupName: "nonexistent", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "SomeReason", + Message: "test", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "SomeReason", + Message: "test", + }, + }, + { + name: "ObservedGeneration is set from PodGroup generation", + existingPodGroup: &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg-gen", Namespace: "ns1", Generation: 7}, + }, + namespace: "ns1", + podGroupName: "pg-gen", + condition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + }, + expectCondition: &metav1.Condition{ + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionTrue, + Reason: "Scheduled", + Message: "All pods scheduled", + ObservedGeneration: 7, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + var objects []runtime.Object + if tt.existingPodGroup != nil { + objects = append(objects, tt.existingPodGroup) + } + client := clientsetfake.NewClientset(objects...) + informerFactory := informers.NewSharedInformerFactory(client, 0) + podGroupLister := informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + sched := &Scheduler{client: client, podGroupLister: podGroupLister} + + var existingLTT metav1.Time + if tt.existingPodGroup != nil { + if existing := apimeta.FindStatusCondition(tt.existingPodGroup.Status.Conditions, schedulingapi.PodGroupScheduled); existing != nil { + existingLTT = existing.LastTransitionTime + } + } + + podGroupInfo := &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{ + Namespace: tt.namespace, + Name: tt.podGroupName, + }, + } + sched.updatePodGroupCondition(ctx, podGroupInfo, tt.condition) + + updatedPodGroup, err := client.SchedulingV1alpha2().PodGroups(tt.namespace).Get(ctx, tt.podGroupName, metav1.GetOptions{}) + if tt.existingPodGroup == nil { + if err == nil { + t.Fatalf("Expected PodGroup to not be found, but got: %v", updatedPodGroup) + } + return + } + if err != nil { + t.Fatalf("Failed to get PodGroup: %v", err) + } + + cond := apimeta.FindStatusCondition(updatedPodGroup.Status.Conditions, tt.expectCondition.Type) + if diff := cmp.Diff(tt.expectCondition, cond, cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime")); diff != "" { + t.Errorf("Unexpected PodGroupScheduled condition (-want +got):\n%s", diff) + } + + if tt.expectLastTransitionTimeUnchanged { + if !cond.LastTransitionTime.Time.Truncate(time.Second).Equal(existingLTT.Time.Truncate(time.Second)) { + t.Errorf("Expected LastTransitionTime to be preserved as %v, got %v", existingLTT, cond.LastTransitionTime) + } + } + }) + } +} + +// fakePlacementPlugin simulates Filter, PlacementGenerate and PlacementScore behaviors for PodGroup placement scheduling testing. +type fakePlacementPlugin struct { + name string + filterStatus map[string]*fwk.Status + generatePlacementsResult map[string][]string + generatePlacementsStatus *fwk.Status + scorePlacementsResult map[string]int64 + scorePlacementsStatus map[string]*fwk.Status +} + +var _ fwk.FilterPlugin = &fakePlacementPlugin{} +var _ fwk.PlacementGeneratePlugin = &fakePlacementPlugin{} +var _ fwk.PlacementScorePlugin = &fakePlacementPlugin{} + +func (mp *fakePlacementPlugin) Name() string { return mp.name } + +func (mp *fakePlacementPlugin) Filter(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status { + return mp.filterStatus[nodeInfo.Node().Name] +} + +func (mp *fakePlacementPlugin) PlacementScoreExtensions() fwk.PlacementScoreExtensions { + return nil +} + +func (mp *fakePlacementPlugin) ScorePlacement(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, placement *fwk.PodGroupAssignments) (int64, *fwk.Status) { + return mp.scorePlacementsResult[placement.Name], mp.scorePlacementsStatus[placement.Name] +} + +func (mp *fakePlacementPlugin) GeneratePlacements(ctx context.Context, state fwk.PodGroupCycleState, podGroup fwk.PodGroupInfo, parentPlacement *fwk.Placement) (*fwk.GeneratePlacementsResult, *fwk.Status) { + parentNodes := map[string]fwk.NodeInfo{} + for _, node := range parentPlacement.Nodes { + parentNodes[node.Node().Name] = node + } + + placements := make([]*fwk.Placement, 0, len(mp.generatePlacementsResult)) + for placementName, nodeNames := range mp.generatePlacementsResult { + placement := &fwk.Placement{Name: placementName} + for _, nodeName := range nodeNames { + placement.Nodes = append(placement.Nodes, parentNodes[nodeName]) + } + placements = append(placements, placement) + } + return &fwk.GeneratePlacementsResult{Placements: placements}, mp.generatePlacementsStatus +} + +var statusCmpOpt = cmp.Comparer(func(s1 *fwk.Status, s2 *fwk.Status) bool { + if s1 == nil || s2 == nil { + return s1.IsSuccess() && s2.IsSuccess() + } + if s1.Code() == fwk.Error { + return s1.AsError().Error() == s2.AsError().Error() + } + return s1.Code() == s2.Code() && s1.Plugin() == s2.Plugin() && s1.Message() == s2.Message() +}) + +func TestPodGroupSchedulingPlacementAlgorithm(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.TopologyAwareWorkloadScheduling: true, + features.GenericWorkload: true, + }) + + nodes := []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + } + podGroupPod := st.MakePod().Name("foo").UID("foo").PodGroupName("pg").Obj() + + tests := map[string]struct { + placementPlugin fakePlacementPlugin + expectedResult podGroupAlgorithmResult + }{ + "respects higher score of placement1": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsResult: map[string][]string{ + "placement1": {nodes[0].Name}, + "placement2": {nodes[1].Name}, + }, + scorePlacementsResult: map[string]int64{ + "placement1": 2, + "placement2": 1, + }, + }, + expectedResult: podGroupAlgorithmResult{ + podResults: []algorithmResult{ + { + pod: podGroupPod, + scheduleResult: ScheduleResult{ + SuggestedHost: nodes[0].Name, + EvaluatedNodes: 1, + FeasibleNodes: 1, + }, + }, + }, + status: nil, + }, + }, + "respects higher score of placement2": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsResult: map[string][]string{ + "placement1": {nodes[0].Name}, + "placement2": {nodes[1].Name}, + }, + scorePlacementsResult: map[string]int64{ + "placement1": 1, + "placement2": 2, + }, + }, + expectedResult: podGroupAlgorithmResult{ + podResults: []algorithmResult{ + { + pod: podGroupPod, + scheduleResult: ScheduleResult{ + SuggestedHost: nodes[1].Name, + EvaluatedNodes: 1, + FeasibleNodes: 1, + }, + }, + }, + status: nil, + }, + }, + "when no placements are generated, returns unschedulable": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsResult: map[string][]string{}, + }, + expectedResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Unschedulable, "no feasible placements found").WithPlugin("FakePlacementPlugin"), + }, + }, + "when all placements are infeasible, returns unschedulable": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsResult: map[string][]string{ + "placement1": {nodes[0].Name}, + "placement2": {nodes[1].Name}, + }, + scorePlacementsResult: map[string]int64{ + "placement1": 1, + "placement2": 2, + }, + filterStatus: map[string]*fwk.Status{ + nodes[0].Name: fwk.NewStatus(fwk.Unschedulable), + nodes[1].Name: fwk.NewStatus(fwk.Unschedulable), + }, + }, + expectedResult: podGroupAlgorithmResult{ + podResults: []algorithmResult{ + { + pod: podGroupPod, + scheduleResult: ScheduleResult{ + EvaluatedNodes: 0, + FeasibleNodes: 0, + nominatingInfo: &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride}, + }, + status: fwk.NewStatus(fwk.Unschedulable, "0/1 nodes are available:"), + }, + }, + status: fwk.NewStatus(fwk.Unschedulable, "0/2 placements are available, first placement status: pod group is unschedulable"), + }, + }, + "filters out infeasible placements": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsResult: map[string][]string{ + "placement1": {nodes[0].Name}, + "placement2": {nodes[1].Name}, + }, + scorePlacementsResult: map[string]int64{ + "placement1": 1, + }, + filterStatus: map[string]*fwk.Status{ + nodes[1].Name: fwk.NewStatus(fwk.Unschedulable), + }, + }, + expectedResult: podGroupAlgorithmResult{ + podResults: []algorithmResult{ + { + pod: podGroupPod, + scheduleResult: ScheduleResult{ + SuggestedHost: nodes[0].Name, + EvaluatedNodes: 1, + FeasibleNodes: 1, + }, + }, + }, + status: nil, + }, + }, + "when generate plugin fails, returns error": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsStatus: fwk.NewStatus(fwk.Error, "error for test"), + }, + expectedResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Error, "error for test").WithPlugin("FakePlacementPlugin"), + }, + }, + "when score plugin fails, returns error": { + placementPlugin: fakePlacementPlugin{ + generatePlacementsResult: map[string][]string{ + "placement1": {nodes[0].Name}, + "placement2": {nodes[1].Name}, + }, + scorePlacementsResult: map[string]int64{ + "placement1": 1, + }, + scorePlacementsStatus: map[string]*fwk.Status{ + "placement2": fwk.NewStatus(fwk.Error, "error for test"), + }, + }, + expectedResult: podGroupAlgorithmResult{ + status: fwk.NewStatus(fwk.Error, "running PlacementScore plugins: plugin \"FakePlacementPlugin\" failed with: error for test").WithPlugin("FakePlacementPlugin"), + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + + informerFactory := informers.NewSharedInformerFactory(clientsetfake.NewClientset(), 0) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory) + + tt.placementPlugin.name = "FakePlacementPlugin" + + registry := []tf.RegisterPluginFunc{ + tf.RegisterPlacementGeneratePlugin(tt.placementPlugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &tt.placementPlugin, nil + }), + tf.RegisterPlacementScorePlugin(tt.placementPlugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &tt.placementPlugin, nil + }, 1), + tf.RegisterFilterPlugin(tt.placementPlugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &tt.placementPlugin, nil + }), + } + + snapshot := internalcache.NewEmptySnapshot() + + schedFwk, err := tf.NewFramework(ctx, + append(registry, + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ), + "test-scheduler", + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithSnapshotSharedLister(snapshot), + frameworkruntime.WithPodNominator(queue), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + + cache := internalcache.New(ctx, nil, true) + for _, node := range nodes { + cache.AddNode(logger, node) + } + + sched := &Scheduler{ + Cache: cache, + nodeInfoSnapshot: snapshot, + SchedulingQueue: queue, + Profiles: profile.Map{"test-scheduler": schedFwk}, + } + sched.SchedulePod = sched.schedulePod + + if err := sched.Cache.UpdateSnapshot(logger, sched.nodeInfoSnapshot); err != nil { + t.Fatalf("Failed to update snapshot: %v", err) + } + + pgInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{ + { + PodInfo: &framework.PodInfo{Pod: podGroupPod}, + }, + }, + PodGroupInfo: &framework.PodGroupInfo{ + UnscheduledPods: []*v1.Pod{podGroupPod}, + }, + } + + result := sched.podGroupSchedulingPlacementAlgorithm(ctx, schedFwk, framework.NewCycleState(), pgInfo, runAllPostFilters) + + opts := cmp.Options{ + cmp.AllowUnexported( + podGroupAlgorithmResult{}, + algorithmResult{}, + ScheduleResult{}, + fwk.Status{}), + cmpopts.IgnoreFields(algorithmResult{}, "podCtx", "schedulingDuration"), + statusCmpOpt, + } + + if diff := cmp.Diff(tt.expectedResult, result, opts...); diff != "" { + t.Fatalf("Unexpected algorithm result (-want,+got):\n%s", diff) + } + }) + } +} + +func TestPodGroupSchedulingPlacementAlgorithm_Scoring(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.TopologyAwareWorkloadScheduling: true, + features.GenericWorkload: true, + }) + + nodes := []*v1.Node{ + st.MakeNode().Name("node1").Obj(), + st.MakeNode().Name("node2").Obj(), + } + placements := map[string][]string{ + "placement1": {nodes[0].Name}, + "placement2": {nodes[1].Name}, + } + podGroupPod := st.MakePod().Name("foo").UID("foo").PodGroupName("pg").Obj() + + type pluginData struct { + weight int32 + scorePlacementResult map[string]int64 + scorePlacementStatus map[string]*fwk.Status + } + + tests := map[string]struct { + pluginData []pluginData + expectedPlacement string + }{ + "respects higher score of placement1": { + pluginData: []pluginData{ + { + weight: 1, + scorePlacementResult: map[string]int64{ + "placement1": 50, + "placement2": 75, + }, + }, + { + weight: 2, + scorePlacementResult: map[string]int64{ + "placement1": 25, + "placement2": 10, + }, + }, + }, + expectedPlacement: "placement1", + }, + "respects higher score of placement2": { + pluginData: []pluginData{ + { + weight: 1, + scorePlacementResult: map[string]int64{ + "placement1": 75, + "placement2": 50, + }, + }, + { + weight: 2, + scorePlacementResult: map[string]int64{ + "placement1": 10, + "placement2": 25, + }, + }, + }, + expectedPlacement: "placement2", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + + informerFactory := informers.NewSharedInformerFactory(clientsetfake.NewClientset(), 0) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory) + + placementPlugin := fakePlacementPlugin{ + name: "FakeGeneratorPlugin", + generatePlacementsResult: placements, + } + + registry := []tf.RegisterPluginFunc{ + tf.RegisterPlacementGeneratePlugin(placementPlugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &placementPlugin, nil + }), + tf.RegisterFilterPlugin(placementPlugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &placementPlugin, nil + }), + } + + for i, placementScorePluginData := range tt.pluginData { + plugin := fakePlacementPlugin{ + name: fmt.Sprintf("FakeScorePlugin[%d]", i), + scorePlacementsResult: placementScorePluginData.scorePlacementResult, + scorePlacementsStatus: placementScorePluginData.scorePlacementStatus, + } + + registry = append(registry, tf.RegisterPlacementScorePlugin(plugin.Name(), func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &plugin, nil + }, placementScorePluginData.weight)) + } + + snapshot := internalcache.NewEmptySnapshot() + + schedFwk, err := tf.NewFramework(ctx, + append(registry, + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ), + "test-scheduler", + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithSnapshotSharedLister(snapshot), + frameworkruntime.WithPodNominator(queue), + ) + if err != nil { + t.Fatalf("Failed to create new framework: %v", err) + } + + cache := internalcache.New(ctx, nil, true) + for _, node := range nodes { + cache.AddNode(logger, node) + } + + sched := &Scheduler{ + Cache: cache, + nodeInfoSnapshot: snapshot, + SchedulingQueue: queue, + Profiles: profile.Map{"test-scheduler": schedFwk}, + } + sched.SchedulePod = sched.schedulePod + + if err := sched.Cache.UpdateSnapshot(logger, sched.nodeInfoSnapshot); err != nil { + t.Fatalf("Failed to update snapshot: %v", err) + } + + pgInfo := &framework.QueuedPodGroupInfo{ + QueuedPodInfos: []*framework.QueuedPodInfo{ + { + PodInfo: &framework.PodInfo{Pod: podGroupPod}, + }, + }, + PodGroupInfo: &framework.PodGroupInfo{ + UnscheduledPods: []*v1.Pod{podGroupPod}, + }, + } + + result := sched.podGroupSchedulingPlacementAlgorithm(ctx, schedFwk, framework.NewCycleState(), pgInfo, runAllPostFilters) + + expectedHost := placements[tt.expectedPlacement][0] + actualHost := result.podResults[0].scheduleResult.SuggestedHost + if expectedHost != actualHost { + t.Fatalf("Unexpected algorithm result, expected placement %s with node %s, got node %s", tt.expectedPlacement, expectedHost, actualHost) + } + }) + } +} + +type fakeDefaultPreemption struct { + *fakePodGroupPlugin +} + +func (f *fakeDefaultPreemption) Name() string { + return names.DefaultPreemption +} + +func TestRunWorkloadAwarePreemption(t *testing.T) { + tests := []struct { + name string + podGroupInfo *framework.QueuedPodGroupInfo + existingPodGroups []*schedulingv1alpha2.PodGroup + pluginsRegistered bool + pluginReturnStatus *fwk.Status + expectedStatus *fwk.Status + }{ + { + name: "error when no PodGroupPostFilter plugin is registered", + podGroupInfo: &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{Namespace: "default", Name: "pg1"}, + }, + pluginsRegistered: false, + expectedStatus: fwk.NewStatus(fwk.Unschedulable, "default preemption plugin is not registered, workload aware preemption is disabled"), + }, + { + name: "error when pod group is not found in informer", + podGroupInfo: &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{Namespace: "default", Name: "missing-pg"}, + QueuedPodInfos: []*framework.QueuedPodInfo{ + {PodInfo: &framework.PodInfo{Pod: st.MakePod().Name("p1").Namespace("default").Obj()}}, + }, + }, + pluginsRegistered: true, + expectedStatus: fwk.AsStatus(fmt.Errorf("failed to get pod group object: podgroup.scheduling.k8s.io \"missing-pg\" not found")), + }, + { + name: "error when pod group has scheduling constraints", + podGroupInfo: &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{Namespace: "default", Name: "pg-with-constraints"}, + QueuedPodInfos: []*framework.QueuedPodInfo{ + {PodInfo: &framework.PodInfo{Pod: st.MakePod().Name("p1").Namespace("default").Obj()}}, + }, + }, + existingPodGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "pg-with-constraints"}, + Spec: schedulingv1alpha2.PodGroupSpec{ + SchedulingConstraints: &schedulingv1alpha2.PodGroupSchedulingConstraints{ + Topology: []schedulingv1alpha2.TopologyConstraint{{}}, // non-empty + }, + }, + }, + }, + pluginsRegistered: true, + expectedStatus: fwk.NewStatus(fwk.Unschedulable, "workload aware preemption is not supported for pod groups with scheduling constraints"), + }, + { + name: "success when plugin returns success status", + podGroupInfo: &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{Namespace: "default", Name: "pg-success"}, + QueuedPodInfos: []*framework.QueuedPodInfo{ + {PodInfo: &framework.PodInfo{Pod: st.MakePod().Name("p1").Namespace("default").Obj()}}, + }, + }, + existingPodGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "pg-success"}, + }, + }, + pluginsRegistered: true, + pluginReturnStatus: fwk.NewStatus(fwk.Success), + expectedStatus: fwk.NewStatus(fwk.Success), + }, + { + name: "failure when plugin returns unschedulable status", + podGroupInfo: &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{Namespace: "default", Name: "pg-unschedulable"}, + QueuedPodInfos: []*framework.QueuedPodInfo{ + {PodInfo: &framework.PodInfo{Pod: st.MakePod().Name("p1").Namespace("default").Obj()}}, + }, + }, + existingPodGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "pg-unschedulable"}, + }, + }, + pluginsRegistered: true, + pluginReturnStatus: fwk.NewStatus(fwk.Unschedulable, "not enough resources"), + expectedStatus: fwk.NewStatus(fwk.Unschedulable, "not enough resources"), + }, + { + name: "error when pod group does not exist", + podGroupInfo: &framework.QueuedPodGroupInfo{ + PodGroupInfo: &framework.PodGroupInfo{Namespace: "default", Name: "pg-unschedulable"}, + QueuedPodInfos: []*framework.QueuedPodInfo{ + {PodInfo: &framework.PodInfo{Pod: st.MakePod().Name("p1").Namespace("default").Obj()}}, + }, + }, + existingPodGroups: []*schedulingv1alpha2.PodGroup{}, + pluginsRegistered: true, + pluginReturnStatus: fwk.NewStatus(fwk.Success), + expectedStatus: fwk.AsStatus(fmt.Errorf("failed to get pod group object: %w", errors.New("podgroup.scheduling.k8s.io \"pg-unschedulable\" not found"))), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Enable feature gate before framework initialization + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.WorkloadAwarePreemption: tt.pluginsRegistered, + features.GangScheduling: true, + }) + + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + registry := frameworkruntime.Registry{ + queuesort.Name: queuesort.New, + defaultbinder.Name: defaultbinder.New, + } + + if tt.pluginsRegistered { + registry["DefaultPreemption"] = func(ctx context.Context, obj runtime.Object, handle fwk.Handle) (fwk.Plugin, error) { + return &fakeDefaultPreemption{ + fakePodGroupPlugin: &fakePodGroupPlugin{ + podGroupPostFilterStatus: tt.pluginReturnStatus, + }, + }, nil + } + } + + profileCfg := config.KubeSchedulerProfile{ + SchedulerName: "test-scheduler", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: queuesort.Name}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: defaultbinder.Name}}, + }, + PostFilter: config.PluginSet{ + Enabled: []config.Plugin{}, + }, + }, + } + if tt.pluginsRegistered { + profileCfg.Plugins.PostFilter.Enabled = append(profileCfg.Plugins.PostFilter.Enabled, config.Plugin{Name: "DefaultPreemption"}) + } + + objs := []runtime.Object{} + for _, pg := range tt.existingPodGroups { + objs = append(objs, pg) + } + client := clientsetfake.NewSimpleClientset(objs...) + informerFactory := informers.NewSharedInformerFactory(client, 0) + + schedFwk, err := frameworkruntime.NewFramework(ctx, registry, &profileCfg, + frameworkruntime.WithInformerFactory(informerFactory), + frameworkruntime.WithClientSet(client), + ) + if err != nil { + t.Fatalf("Failed to create framework: %v", err) + } + + podGroupLister := informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + + if tt.pluginsRegistered { + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + } + + cache := internalcache.New(ctx, nil, true) + sched := &Scheduler{ + Cache: cache, + nodeInfoSnapshot: internalcache.NewEmptySnapshot(), // Need empty snapshot to avoid nil pointer issues + podGroupLister: podGroupLister, + } + + // Just inject logger explicitly in context to avoid panic + ctx = klog.NewContext(ctx, logger) + + status := sched.runWorkloadAwarePreemption(ctx, schedFwk, framework.NewCycleState(), tt.podGroupInfo) + + if tt.expectedStatus.Code() != status.Code() || tt.expectedStatus.Message() != status.Message() { + t.Errorf("Unexpected status, want code %v message %q, got code %v message %q", + tt.expectedStatus.Code(), tt.expectedStatus.Message(), + status.Code(), status.Message()) + } + }) + } +} diff --git a/pkg/scheduler/schedule_one_test.go b/pkg/scheduler/schedule_one_test.go index 592ff01a84e99..0279d730f6191 100644 --- a/pkg/scheduler/schedule_one_test.go +++ b/pkg/scheduler/schedule_one_test.go @@ -34,9 +34,11 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" v1 "k8s.io/api/core/v1" eventsv1 "k8s.io/api/events/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -49,6 +51,7 @@ import ( clientset "k8s.io/client-go/kubernetes" clientsetfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" clienttesting "k8s.io/client-go/testing" clientcache "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/events" @@ -367,6 +370,30 @@ func newFakeNodeSelectorDependOnPodAnnotation(_ context.Context, _ runtime.Objec return &fakeNodeSelectorDependOnPodAnnotation{}, nil } +var blockingPreBindStartCh = make(chan struct{}, 1) + +var _ fwk.PreBindPlugin = &BlockingPreBindPlugin{} + +type BlockingPreBindPlugin struct { + handle fwk.Handle +} + +func (p *BlockingPreBindPlugin) Name() string { return "BlockingPreBindPlugin" } + +func (p *BlockingPreBindPlugin) PreBind(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) *fwk.Status { + go func() { blockingPreBindStartCh <- struct{}{} }() + <-ctx.Done() + return fwk.AsStatus(ctx.Err()) +} + +func (p *BlockingPreBindPlugin) PreBindPreFlight(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeName string) (*fwk.PreBindPreFlightResult, *fwk.Status) { + return &fwk.PreBindPreFlightResult{}, nil +} + +func NewBlockingPreBindPlugin(_ context.Context, _ runtime.Object, h fwk.Handle) (fwk.Plugin, error) { + return &BlockingPreBindPlugin{handle: h}, nil +} + type TestPlugin struct { name string } @@ -694,7 +721,7 @@ func TestSchedulerScheduleOne(t *testing.T) { pod types.UID } - table := []struct { + type testItem struct { name string sendPod *v1.Pod registerPluginFuncs []tf.RegisterPluginFunc @@ -715,7 +742,14 @@ func TestSchedulerScheduleOne(t *testing.T) { nominatedNodeNameForExpectationEnabled *bool // If nil, the test case is run with both enabled and disabled asyncAPICallsEnabled *bool - }{ + // If nil, the test case is run with both true and false + scheduleAsPodGroup *bool + // postSchedulingCycle is run after ScheduleOne function returns + // (synchronous part of scheduling is done and binding goroutine is launched) + // and before blocking execution on waiting for event with eventReason + postSchedulingCycle func(context.Context, *Scheduler) + } + table := []testItem{ { name: "schedule pod failed", sendPod: testPod, @@ -791,9 +825,11 @@ func TestSchedulerScheduleOne(t *testing.T) { podToAdmit: &podToAdmit{pluginName: "FakePermit", pod: testPod.UID}, mockScheduleResult: scheduleResultOk, nominatedNodeNameForExpectationEnabled: ptr.To(false), - expectAssumedPod: assignedTestPod, - expectBind: fakeBinding, - eventReason: "Scheduled", + // PodGroup scheduling cycle treats Wait status from Permit as unschedulable. + scheduleAsPodGroup: ptr.To(false), + expectAssumedPod: assignedTestPod, + expectBind: fakeBinding, + eventReason: "Scheduled", }, { name: "nominated node name is set, permit plugin is working in wait on permit phase", @@ -808,9 +844,11 @@ func TestSchedulerScheduleOne(t *testing.T) { // So, it's safe to run this test with asyncAPICallsEnabled = false only. asyncAPICallsEnabled: ptr.To(false), nominatedNodeNameForExpectationEnabled: ptr.To(true), - expectAssumedPod: assignedTestPod, - expectBind: fakeBinding, - eventReason: "Scheduled", + // PodGroup scheduling cycle treats Wait status from Permit as unschedulable. + scheduleAsPodGroup: ptr.To(false), + expectAssumedPod: assignedTestPod, + expectBind: fakeBinding, + eventReason: "Scheduled", }, { name: "prebindpreflight failed with status code error", @@ -890,6 +928,30 @@ func TestSchedulerScheduleOne(t *testing.T) { expectError: fmt.Errorf(`running PreBind plugin "FakePreBind": %w`, preBindErr), eventReason: "FailedScheduling", }, + { + name: "prebind pod cancelled during prebind (external preemption)", + sendPod: testPod, + registerPluginFuncs: []tf.RegisterPluginFunc{ + tf.RegisterPreBindPlugin("BlockingPreBindPlugin", NewBlockingPreBindPlugin), + }, + mockScheduleResult: scheduleResultOk, + postSchedulingCycle: func(ctx context.Context, sched *Scheduler) { + <-blockingPreBindStartCh // Wait for plugin to start + // Trigger preemption from "outside" + if bp := sched.Profiles[testSchedulerName].GetPodInPreBind(testPod.UID); bp != nil { + bp.CancelPod("context cancelled externally") + } + }, + asyncAPICallsEnabled: ptr.To(false), + nominatedNodeNameForExpectationEnabled: ptr.To(true), + expectAssumedPod: assignedTestPod, + expectErrorPod: assignedTestPod, + expectForgetPod: assignedTestPod, + expectNominatedNodeName: "", + expectPodInBackoffQ: testPod, + expectError: fmt.Errorf(`running PreBind plugin "BlockingPreBindPlugin": context cancelled externally`), + eventReason: "FailedScheduling", + }, { name: "binding failed", sendPod: testPod, @@ -939,13 +1001,278 @@ func TestSchedulerScheduleOne(t *testing.T) { }(), // Depending on the timing, if asyncAPICallsEnabled, the NNN update might not be sent yet while checking the expectNominatedNodeName. // So, asyncAPICallsEnabled is set to false. + // TODO(Argh4k): create a method on APIDispatcher to wait for all API calls to finish to ease testing. asyncAPICallsEnabled: ptr.To(false), expectNominatedNodeName: "", eventReason: "FailedScheduling", }, } - // Test with QueueingHints and NominatedNodeNameForExpectation feature gates + type schedFeatures struct { + asyncAPICallsEnabled bool + nominatedNodeNameForExpectationEnabled bool + } + + withSchedulingGroup := func(pod *v1.Pod, group *v1.PodSchedulingGroup) *v1.Pod { + if pod == nil { + return nil + } + newPod := pod.DeepCopy() + newPod.Spec.SchedulingGroup = group + return newPod + } + + testFunc := func(t *testing.T, item testItem, scheduleAsPodGroup bool, features schedFeatures) { + logger, ctx := ktesting.NewTestContext(t) + var gotError error + var gotPod *v1.Pod + var gotForgetPod *v1.Pod + var gotAssumedPod *v1.Pod + var gotBinding *v1.Binding + var gotNominatingInfo *fwk.NominatingInfo + + var podGroupLister schedulinglisters.PodGroupLister + var clientObjs []runtime.Object + if scheduleAsPodGroup { + group := &v1.PodSchedulingGroup{ + PodGroupName: new("pg"), + } + // When scheduling a pod as a pod group, set scheduling group to all relevant pods. + item.sendPod = withSchedulingGroup(item.sendPod, group) + item.expectErrorPod = withSchedulingGroup(item.expectErrorPod, group) + item.expectPodInBackoffQ = withSchedulingGroup(item.expectPodInBackoffQ, group) + if item.expectPodInUnschedulable != nil { + // Pods from a pod group skip unschedulablePods structure and land directly in the backoffQ. + item.expectPodInBackoffQ = withSchedulingGroup(item.expectPodInUnschedulable, group) + item.expectPodInUnschedulable = nil + } + + testPG := &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: item.sendPod.Namespace}, + } + clientObjs = []runtime.Object{item.sendPod, testPG} + } else { + clientObjs = []runtime.Object{item.sendPod} + } + client := clientsetfake.NewClientset(clientObjs...) + informerFactory := informers.NewSharedInformerFactory(client, 0) + client.PrependReactor("create", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + if action.GetSubresource() != "binding" { + return false, nil, nil + } + gotBinding = action.(clienttesting.CreateAction).GetObject().(*v1.Binding) + return true, gotBinding, item.injectBindError + }) + + var apiDispatcher *apidispatcher.APIDispatcher + if features.asyncAPICallsEnabled { + apiDispatcher = apidispatcher.New(client, 16, apicalls.Relevances) + apiDispatcher.Run(logger) + defer apiDispatcher.Close() + } + + internalCache := internalcache.New(ctx, apiDispatcher, scheduleAsPodGroup) + + if scheduleAsPodGroup { + podGroupLister = informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + internalCache.AddPodGroupMember(item.sendPod) + } + cache := &fakecache.Cache{ + Cache: internalCache, + ForgetFunc: func(pod *v1.Pod) { + gotForgetPod = pod + }, + AssumeFunc: func(pod *v1.Pod) { + gotAssumedPod = pod + }, + IsAssumedPodFunc: func(pod *v1.Pod) bool { + if pod == nil || gotAssumedPod == nil { + return false + } + return pod.UID == gotAssumedPod.UID + }, + } + mu := &sync.Mutex{} + updatedNominatedNodeName := item.sendPod.Status.NominatedNodeName + client.PrependReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { + if action.GetSubresource() != "status" { + return false, nil, nil + } + patchAction := action.(clienttesting.PatchAction) + patch := patchAction.GetPatch() + patchMap := map[string]map[string]json.RawMessage{} + if err := json.Unmarshal(patch, &patchMap); err != nil { + t.Fatalf("Failed to unmarshal patch %q: %v", patch, err) + } + statusMap, ok := patchMap["status"] + if !ok { + t.Fatalf("patch doesn't include status: %q", patch) + } + nnn, ok := statusMap["nominatedNodeName"] + if !ok { + return false, nil, nil + } + mu.Lock() + updatedNominatedNodeName = strings.Trim(string(nnn), "\"") + if updatedNominatedNodeName == "null" { + // NNN has to be cleared with this patch. + updatedNominatedNodeName = "" + } + mu.Unlock() + return false, nil, nil + }) + + schedFramework, err := tf.NewFramework(ctx, + append(item.registerPluginFuncs, + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + ), + testSchedulerName, + frameworkruntime.WithClientSet(client), + frameworkruntime.WithAPIDispatcher(apiDispatcher), + frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, testSchedulerName)), + frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), + frameworkruntime.WithInformerFactory(informerFactory), + ) + if err != nil { + t.Fatal(err) + } + + ar := metrics.NewMetricsAsyncRecorder(10, 1*time.Second, ctx.Done()) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory, internalqueue.WithMetricsRecorder(ar), internalqueue.WithAPIDispatcher(apiDispatcher)) + if features.asyncAPICallsEnabled { + schedFramework.SetAPICacher(apicache.New(queue, cache)) + } + + sched := &Scheduler{ + Cache: cache, + client: client, + NextPod: queue.Pop, + SchedulingQueue: queue, + Profiles: profile.Map{testSchedulerName: schedFramework}, + APIDispatcher: apiDispatcher, + podGroupLister: podGroupLister, + nominatedNodeNameForExpectationEnabled: features.nominatedNodeNameForExpectationEnabled, + } + queue.Add(ctx, item.sendPod) + + sched.SchedulePod = func(ctx context.Context, fwk framework.Framework, state fwk.CycleState, podInfo *framework.QueuedPodInfo) (ScheduleResult, error) { + return item.mockScheduleResult, item.injectSchedulingError + } + sched.FailureHandler = func(ctx context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, ni *fwk.NominatingInfo, start time.Time) { + gotPod = p.Pod + gotError = status.AsError() + gotNominatingInfo = ni + + sched.handleSchedulingFailure(ctx, fwk, p, status, ni, start) + } + called := make(chan struct{}) + stopFunc, err := eventBroadcaster.StartEventWatcher(func(obj runtime.Object) { + e, _ := obj.(*eventsv1.Event) + if e.Reason != item.eventReason { + t.Errorf("got event %v, want %v", e.Reason, item.eventReason) + } + close(called) + }) + if err != nil { + t.Fatal(err) + } + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + sched.nodeInfoSnapshot = internalcache.NewEmptySnapshot() + sched.ScheduleOne(ctx) + + if item.postSchedulingCycle != nil { + item.postSchedulingCycle(ctx, sched) + } + + if item.podToAdmit != nil { + for { + if waitingPod := sched.Profiles[testSchedulerName].GetWaitingPod(item.podToAdmit.pod); waitingPod != nil { + waitingPod.Allow(item.podToAdmit.pluginName) + break + } + } + } + + <-called + + // TODO(Argh4k): Re-enable this assumption for item.expectError != nil once solution to + // https://github.com/kubernetes/kubernetes/issues/137125 is agreed upon and implemented. + // Right now, handleSchedulingFailure does not guarantee that all updates + // to nominated nodes will be send out as patch calls as its: + // - refreshes pod with data from informer cache that can be not up to date + // - with async calls enabled the previous update to NNN can still be sitting in async queue + mu.Lock() + // Cases with expected error are tested below + if item.expectError == nil && item.expectNominatedNodeName != updatedNominatedNodeName { + t.Errorf("Expected nominated node name %q, got %q", item.expectNominatedNodeName, updatedNominatedNodeName) + } + mu.Unlock() + + // When a pod is scheduled as a pod group, it is assumed first in the snapshot, + // meaning that assumed and forgotten pods obtained from cache would be nil in some cases. + // It also sometimes differently sets nodeName on an error pod, + // which is irrelevant for the actual correctness. + if !scheduleAsPodGroup { + if diff := cmp.Diff(item.expectAssumedPod, gotAssumedPod); diff != "" { + t.Errorf("Unexpected assumed pod (-want,+got):\n%s", diff) + } + if diff := cmp.Diff(item.expectErrorPod, gotPod); diff != "" { + t.Errorf("Unexpected error pod (-want,+got):\n%s", diff) + } + if diff := cmp.Diff(item.expectForgetPod, gotForgetPod); diff != "" { + t.Errorf("Unexpected forget pod (-want,+got):\n%s", diff) + } + } + if item.expectError == nil || gotError == nil { + if !errors.Is(gotError, item.expectError) { + t.Errorf("Unexpected error. Wanted %v, got %v", item.expectError, gotError) + } + } else if item.expectError.Error() != gotError.Error() { + t.Errorf("Unexpected error. Wanted %v, got %v", item.expectError.Error(), gotError.Error()) + } + if item.expectError != nil { + expectedNominatingInfo := &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride, NominatedNodeName: ""} + if diff := cmp.Diff(expectedNominatingInfo, gotNominatingInfo); diff != "" { + t.Errorf("Unexpected nominatingInfo (-want,+got):\n%s", diff) + } + } + if diff := cmp.Diff(item.expectBind, gotBinding); diff != "" { + t.Errorf("Unexpected binding (-want,+got):\n%s", diff) + } + // We have to use wait here because the Pod goes to the binding cycle in some test cases + // and the inflight pods might not be empty immediately at this point in such case. + if err := wait.PollUntilContextTimeout(ctx, time.Millisecond*200, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) { + return len(queue.InFlightPods()) == 0, nil + }); err != nil { + t.Errorf("in-flight pods should be always empty after SchedulingOne. It has %v Pods", len(queue.InFlightPods())) + } + podsInBackoffQ := queue.PodsInBackoffQ() + if item.expectPodInBackoffQ != nil { + if !podListContainsPod(podsInBackoffQ, item.expectPodInBackoffQ) { + t.Errorf("Expected to find pod in backoffQ, but it's not there.\nWant: %v,\ngot: %v", item.expectPodInBackoffQ, podsInBackoffQ) + } + } else { + if len(podsInBackoffQ) > 0 { + t.Errorf("Expected backoffQ to be empty, but it's not.\nGot: %v", podsInBackoffQ) + } + } + unschedulablePods := queue.UnschedulablePods() + if item.expectPodInUnschedulable != nil { + if !podListContainsPod(unschedulablePods, item.expectPodInUnschedulable) { + t.Errorf("Expected to find pod in unschedulable, but it's not there.\nWant: %v,\ngot: %v", item.expectPodInUnschedulable, unschedulablePods) + } + } else { + if len(unschedulablePods) > 0 { + t.Errorf("Expected unschedulable pods to be empty, but it's not.\nGot: %v", unschedulablePods) + } + } + stopFunc() + } + + // Test with QueueingHints, AsyncAPICalls and NominatedNodeNameForExpectation feature gates for _, qHintEnabled := range []bool{true, false} { for _, item := range table { asyncAPICallsEnabled := []bool{true, false} @@ -958,220 +1285,109 @@ func TestSchedulerScheduleOne(t *testing.T) { nominatedNodeNameForExpectationEnabled = []bool{*item.nominatedNodeNameForExpectationEnabled} } for _, nominatedNodeNameForExpectationEnabled := range nominatedNodeNameForExpectationEnabled { - if (asyncAPICallsEnabled || nominatedNodeNameForExpectationEnabled) && !qHintEnabled { - // If the QHint feature gate is disabled, NominatedNodeNameForExpectation and SchedulerAsyncAPICalls cannot be enabled - // because that means users set the emulation version to 1.33 or later. - continue + scheduleAsPodGroup := []bool{true, false} + if item.scheduleAsPodGroup != nil { + scheduleAsPodGroup = []bool{*item.scheduleAsPodGroup} } - t.Run(fmt.Sprintf("%s (Queueing hints enabled: %v, Async API calls enabled: %v, NominatedNodeNameForExpectation enabled: %v)", item.name, qHintEnabled, asyncAPICallsEnabled, nominatedNodeNameForExpectationEnabled), func(t *testing.T) { - if !qHintEnabled { - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.33")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, false) + // Test scheduling scenarios treating the pod as individual or part of a pod group. + for _, scheduleAsPodGroup := range scheduleAsPodGroup { + if !qHintEnabled && (asyncAPICallsEnabled || nominatedNodeNameForExpectationEnabled || scheduleAsPodGroup) { + // If the QHint feature gate is disabled, NominatedNodeNameForExpectation, SchedulerAsyncAPICalls and GenericWorkload cannot be enabled + // because that means users set the emulation version to 1.33 or later. + continue } - logger, ctx := ktesting.NewTestContext(t) - var gotError error - var gotPod *v1.Pod - var gotForgetPod *v1.Pod - var gotAssumedPod *v1.Pod - var gotBinding *v1.Binding - var gotNominatingInfo *fwk.NominatingInfo - - client := clientsetfake.NewClientset(item.sendPod) - informerFactory := informers.NewSharedInformerFactory(client, 0) - client.PrependReactor("create", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { - if action.GetSubresource() != "binding" { - return false, nil, nil + t.Run(fmt.Sprintf("%s (QueueingHints: %v, AsyncAPICalls: %v, NominatedNodeNameForExpectation: %v, Schedule as PodGroup: %v)", item.name, + qHintEnabled, asyncAPICallsEnabled, nominatedNodeNameForExpectationEnabled, scheduleAsPodGroup), func(t *testing.T) { + if !qHintEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.33")) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, false) } - gotBinding = action.(clienttesting.CreateAction).GetObject().(*v1.Binding) - return true, gotBinding, item.injectBindError + if scheduleAsPodGroup { + // When scheduling pod as a pod group, GenericWorkload feature gate has to be enabled. + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, true) + } + testFunc(t, item, scheduleAsPodGroup, schedFeatures{ + asyncAPICallsEnabled: asyncAPICallsEnabled, + nominatedNodeNameForExpectationEnabled: nominatedNodeNameForExpectationEnabled, + }) }) + } + } + } + } + } +} - var apiDispatcher *apidispatcher.APIDispatcher - if asyncAPICallsEnabled { - apiDispatcher = apidispatcher.New(client, 16, apicalls.Relevances) - apiDispatcher.Run(logger) - defer apiDispatcher.Close() - } +func TestHandleSchedulingFailureSkipsRecreatedPod(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() - internalCache := internalcache.New(ctx, 30*time.Second, apiDispatcher) - cache := &fakecache.Cache{ - Cache: internalCache, - ForgetFunc: func(pod *v1.Pod) { - gotForgetPod = pod - }, - AssumeFunc: func(pod *v1.Pod) { - gotAssumedPod = pod - }, - IsAssumedPodFunc: func(pod *v1.Pod) bool { - if pod == nil || gotAssumedPod == nil { - return false - } - return pod.UID == gotAssumedPod.UID - }, - } - mu := &sync.Mutex{} - updatedNominatedNodeName := item.sendPod.Status.NominatedNodeName - client.PrependReactor("patch", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) { - if action.GetSubresource() != "status" { - return false, nil, nil - } - patchAction := action.(clienttesting.PatchAction) - patch := patchAction.GetPatch() - patchMap := map[string]map[string]json.RawMessage{} - if err := json.Unmarshal(patch, &patchMap); err != nil { - t.Fatalf("Failed to unmarshal patch %q: %v", patch, err) - } - statusMap, ok := patchMap["status"] - if !ok { - t.Fatalf("patch doesn't include status: %q", patch) - } - nnn, ok := statusMap["nominatedNodeName"] - if !ok { - return false, nil, nil - } - mu.Lock() - updatedNominatedNodeName = strings.Trim(string(nnn), "\"") - if updatedNominatedNodeName == "null" { - // NNN has to be cleared with this patch. - updatedNominatedNodeName = "" - } - mu.Unlock() - return false, nil, nil - }) + oldPod := st.MakePod().Name("foo").Namespace("ns").UID("old-uid").SchedulerName(testSchedulerName).Obj() + recreatedPod := oldPod.DeepCopy() + recreatedPod.UID = "new-uid" - schedFramework, err := tf.NewFramework(ctx, - append(item.registerPluginFuncs, - tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), - tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), - ), - testSchedulerName, - frameworkruntime.WithClientSet(client), - frameworkruntime.WithAPIDispatcher(apiDispatcher), - frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, testSchedulerName)), - frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), - frameworkruntime.WithInformerFactory(informerFactory), - ) - if err != nil { - t.Fatal(err) - } + client := clientsetfake.NewClientset(recreatedPod) + informerFactory := informers.NewSharedInformerFactory(client, 0) + eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()}) - ar := metrics.NewMetricsAsyncRecorder(10, 1*time.Second, ctx.Done()) - queue := internalqueue.NewSchedulingQueue(nil, informerFactory, internalqueue.WithMetricsRecorder(ar), internalqueue.WithAPIDispatcher(apiDispatcher)) - if asyncAPICallsEnabled { - schedFramework.SetAPICacher(apicache.New(queue, cache)) - } + schedFramework, err := tf.NewFramework(ctx, + []tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + }, + testSchedulerName, + frameworkruntime.WithClientSet(client), + frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, testSchedulerName)), + frameworkruntime.WithInformerFactory(informerFactory), + ) + if err != nil { + t.Fatal(err) + } - sched := &Scheduler{ - Cache: cache, - client: client, - NextPod: queue.Pop, - SchedulingQueue: queue, - Profiles: profile.Map{testSchedulerName: schedFramework}, - APIDispatcher: apiDispatcher, - nominatedNodeNameForExpectationEnabled: nominatedNodeNameForExpectationEnabled, - } - queue.Add(logger, item.sendPod) + ar := metrics.NewMetricsAsyncRecorder(10, time.Second, ctx.Done()) + queue := internalqueue.NewSchedulingQueue(nil, informerFactory, internalqueue.WithMetricsRecorder(ar)) + sched := &Scheduler{ + client: client, + SchedulingQueue: queue, + } - sched.SchedulePod = func(ctx context.Context, fwk framework.Framework, state fwk.CycleState, pod *v1.Pod) (ScheduleResult, error) { - return item.mockScheduleResult, item.injectSchedulingError - } - sched.FailureHandler = func(ctx context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, ni *fwk.NominatingInfo, start time.Time) { - gotPod = p.Pod - gotError = status.AsError() - gotNominatingInfo = ni + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) - sched.handleSchedulingFailure(ctx, fwk, p, status, ni, start) - } - called := make(chan struct{}) - stopFunc, err := eventBroadcaster.StartEventWatcher(func(obj runtime.Object) { - e, _ := obj.(*eventsv1.Event) - if e.Reason != item.eventReason { - t.Errorf("got event %v, want %v", e.Reason, item.eventReason) - } - close(called) - }) - if err != nil { - t.Fatal(err) - } - informerFactory.Start(ctx.Done()) - informerFactory.WaitForCacheSync(ctx.Done()) - sched.nodeInfoSnapshot = internalcache.NewEmptySnapshot() - sched.ScheduleOne(ctx) - - if item.podToAdmit != nil { - for { - if waitingPod := sched.Profiles[testSchedulerName].GetWaitingPod(item.podToAdmit.pod); waitingPod != nil { - waitingPod.Allow(item.podToAdmit.pluginName) - break - } - } - } + queue.Add(ctx, oldPod) + popped, err := queue.Pop(logger) + if err != nil { + t.Fatalf("Pop: %v", err) + } + if got := queue.InFlightPods(); !podListContainsPod(got, oldPod) { + t.Fatalf("expected popped pod to be in-flight before failure handling, got %v", got) + } - <-called + nominatingInfo := &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride, NominatedNodeName: "node1"} + sched.handleSchedulingFailure(ctx, schedFramework, popped, fwk.NewStatus(fwk.Unschedulable, "no fit"), nominatingInfo, time.Now()) - mu.Lock() - if item.expectNominatedNodeName != updatedNominatedNodeName { - t.Errorf("Expected nominated node name %q, got %q", item.expectNominatedNodeName, updatedNominatedNodeName) - } - mu.Unlock() + if err := wait.PollUntilContextTimeout(ctx, time.Millisecond, wait.ForeverTestTimeout, false, func(context.Context) (bool, error) { + return len(queue.InFlightPods()) == 0, nil + }); err != nil { + t.Fatalf("in-flight pod was not cleared: %v", queue.InFlightPods()) + } + if got := queue.PodsInBackoffQ(); len(got) != 0 { + t.Fatalf("expected recreated pod to stay out of backoffQ, got %v", got) + } + if got := queue.UnschedulablePods(); len(got) != 0 { + t.Fatalf("expected recreated pod to stay out of unschedulablePods, got %v", got) + } + if got := queue.NominatedPodsForNode("node1"); len(got) != 0 { + t.Fatalf("expected recreated pod to stay out of nominated pods, got %v", got) + } - if diff := cmp.Diff(item.expectAssumedPod, gotAssumedPod); diff != "" { - t.Errorf("Unexpected assumed pod (-want,+got):\n%s", diff) - } - if diff := cmp.Diff(item.expectErrorPod, gotPod); diff != "" { - t.Errorf("Unexpected error pod (-want,+got):\n%s", diff) - } - if diff := cmp.Diff(item.expectForgetPod, gotForgetPod); diff != "" { - t.Errorf("Unexpected forget pod (-want,+got):\n%s", diff) - } - if item.expectError == nil || gotError == nil { - if !errors.Is(gotError, item.expectError) { - t.Errorf("Unexpected error. Wanted %v, got %v", item.expectError, gotError) - } - } else if item.expectError.Error() != gotError.Error() { - t.Errorf("Unexpected error. Wanted %v, got %v", item.expectError.Error(), gotError.Error()) - } - if item.expectError != nil { - expectedNominatingInfo := &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride, NominatedNodeName: ""} - if diff := cmp.Diff(expectedNominatingInfo, gotNominatingInfo); diff != "" { - t.Errorf("Unexpected nominatingInfo (-want,+got):\n%s", diff) - } - } - if diff := cmp.Diff(item.expectBind, gotBinding); diff != "" { - t.Errorf("Unexpected binding (-want,+got):\n%s", diff) - } - // We have to use wait here because the Pod goes to the binding cycle in some test cases - // and the inflight pods might not be empty immediately at this point in such case. - if err := wait.PollUntilContextTimeout(ctx, time.Millisecond*200, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) { - return len(queue.InFlightPods()) == 0, nil - }); err != nil { - t.Errorf("in-flight pods should be always empty after SchedulingOne. It has %v Pods", len(queue.InFlightPods())) - } - podsInBackoffQ := queue.PodsInBackoffQ() - if item.expectPodInBackoffQ != nil { - if !podListContainsPod(podsInBackoffQ, item.expectPodInBackoffQ) { - t.Errorf("Expected to find pod in backoffQ, but it's not there.\nWant: %v,\ngot: %v", item.expectPodInBackoffQ, podsInBackoffQ) - } - } else { - if len(podsInBackoffQ) > 0 { - t.Errorf("Expected backoffQ to be empty, but it's not.\nGot: %v", podsInBackoffQ) - } - } - unschedulablePods := queue.UnschedulablePods() - if item.expectPodInUnschedulable != nil { - if !podListContainsPod(unschedulablePods, item.expectPodInUnschedulable) { - t.Errorf("Expected to find pod in unschedulable, but it's not there.\nWant: %v,\ngot: %v", item.expectPodInUnschedulable, unschedulablePods) - } - } else { - if len(unschedulablePods) > 0 { - t.Errorf("Expected unschedulable pods to be empty, but it's not.\nGot: %v", unschedulablePods) - } - } - stopFunc() - }) - } - } - } + updatedPod, err := client.CoreV1().Pods(recreatedPod.Namespace).Get(ctx, recreatedPod.Name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Get pod: %v", err) + } + if diff := cmp.Diff(recreatedPod.Status, updatedPod.Status); diff != "" { + t.Fatalf("expected recreated pod status to remain unchanged (-want,+got):\n%s", diff) } } @@ -1391,7 +1607,7 @@ func TestSignatures(t *testing.T) { if err != nil { t.Fatal(err) } - signature := schedFramework.SignPod(ctx, podWithID("foo", ""), true) + signature := schedFramework.SignPod(ctx, podWithID("foo", "")) if !bytes.Equal(signature, item.expectedSignature) { t.Fatal(fmt.Errorf("Test %s got signature %s, expected %s", item.name, signature, item.expectedSignature)) } @@ -1588,7 +1804,7 @@ func TestScheduleOneMarksPodAsProcessedBeforePreBind(t *testing.T) { defer apiDispatcher.Close() } - internalCache := internalcache.New(ctx, 30*time.Second, apiDispatcher) + internalCache := internalcache.New(ctx, apiDispatcher, false) cache := &fakecache.Cache{ Cache: internalCache, ForgetFunc: func(pod *v1.Pod) { @@ -1620,6 +1836,7 @@ func TestScheduleOneMarksPodAsProcessedBeforePreBind(t *testing.T) { frameworkruntime.WithAPIDispatcher(apiDispatcher), frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, testSchedulerName)), frameworkruntime.WithWaitingPods(frameworkruntime.NewWaitingPodsMap()), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), ) if err != nil { t.Fatal(err) @@ -1645,12 +1862,12 @@ func TestScheduleOneMarksPodAsProcessedBeforePreBind(t *testing.T) { Profiles: profile.Map{testSchedulerName: schedFramework}, APIDispatcher: apiDispatcher, } - queue.Add(logger, item.sendPod) + queue.Add(ctx, item.sendPod) - sched.SchedulePod = func(ctx context.Context, fwk framework.Framework, state fwk.CycleState, pod *v1.Pod) (ScheduleResult, error) { + sched.SchedulePod = func(ctx context.Context, fwk framework.Framework, state fwk.CycleState, podInfo *framework.QueuedPodInfo) (ScheduleResult, error) { return item.mockScheduleResult, item.injectSchedulingError } - sched.FailureHandler = func(_ context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, _ *fwk.NominatingInfo, _ time.Time) { + sched.FailureHandler = func(ctx context.Context, fwk framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, _ *fwk.NominatingInfo, _ time.Time) { gotCallsToFailureHandler++ gotPodIsInFlightAtFailureHandler = podListContainsPod(queue.InFlightPods(), p.Pod) @@ -1658,7 +1875,7 @@ func TestScheduleOneMarksPodAsProcessedBeforePreBind(t *testing.T) { gotError = status.AsError() msg := truncateMessage(gotError.Error()) - fwk.EventRecorder().Eventf(p.Pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", msg) + fwk.EventRecorder().WithLogger(klog.FromContext(ctx)).Eventf(p.Pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", msg) queue.Done(p.Pod.UID) } @@ -1759,88 +1976,6 @@ func podListContainsPod(list []*v1.Pod, pod *v1.Pod) bool { return false } -func TestSchedulerNoPhantomPodAfterExpire(t *testing.T) { - for _, asyncAPICallsEnabled := range []bool{true, false} { - t.Run(fmt.Sprintf("Async API calls enabled: %v", asyncAPICallsEnabled), func(t *testing.T) { - logger, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) - client := clientsetfake.NewClientset() - bindingChan := interruptOnBind(client) - - var apiDispatcher *apidispatcher.APIDispatcher - if asyncAPICallsEnabled { - apiDispatcher = apidispatcher.New(client, 16, apicalls.Relevances) - apiDispatcher.Run(logger) - defer apiDispatcher.Close() - } - - scache := internalcache.New(ctx, 100*time.Millisecond, apiDispatcher) - pod := podWithPort("pod.Name", "", 8080) - node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1", UID: types.UID("node1")}} - scache.AddNode(logger, &node) - - fns := []tf.RegisterPluginFunc{ - tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), - tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), - tf.RegisterPluginAsExtensions(nodeports.Name, frameworkruntime.FactoryAdapter(feature.Features{}, nodeports.New), "Filter", "PreFilter"), - } - scheduler, errChan := setupTestSchedulerWithOnePodOnNode(ctx, t, client, queuedPodStore, scache, apiDispatcher, pod, &node, bindingChan, fns...) - - waitPodExpireChan := make(chan struct{}) - timeout := make(chan struct{}) - go func() { - for { - select { - case <-timeout: - return - default: - } - pods, err := scache.PodCount() - if err != nil { - errChan <- fmt.Errorf("cache.List failed: %w", err) - return - } - if pods == 0 { - close(waitPodExpireChan) - return - } - time.Sleep(100 * time.Millisecond) - } - }() - // waiting for the assumed pod to expire - select { - case err := <-errChan: - t.Fatal(err) - case <-waitPodExpireChan: - case <-time.After(wait.ForeverTestTimeout): - close(timeout) - t.Fatalf("timeout timeout in waiting pod expire after %v", wait.ForeverTestTimeout) - } - - // We use conflicted pod ports to incur fit predicate failure if first pod not removed. - secondPod := podWithPort("bar", "", 8080) - if err := queuedPodStore.Add(secondPod); err != nil { - t.Fatal(err) - } - scheduler.ScheduleOne(ctx) - select { - case b := <-bindingChan: - expectBinding := &v1.Binding{ - ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: types.UID("bar")}, - Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, - } - if diff := cmp.Diff(expectBinding, b); diff != "" { - t.Errorf("Unexpected binding (-want,+got):\n%s", diff) - } - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("timeout in binding after %v", wait.ForeverTestTimeout) - } - }) - } -} - func TestSchedulerNoPhantomPodAfterDelete(t *testing.T) { for _, asyncAPICallsEnabled := range []bool{true, false} { t.Run(fmt.Sprintf("Async API calls enabled: %v", asyncAPICallsEnabled), func(t *testing.T) { @@ -1858,7 +1993,7 @@ func TestSchedulerNoPhantomPodAfterDelete(t *testing.T) { defer apiDispatcher.Close() } - scache := internalcache.New(ctx, 10*time.Minute, apiDispatcher) + scache := internalcache.New(ctx, apiDispatcher, false) firstPod := podWithPort("pod.Name", "", 8080) node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1", UID: types.UID("node1")}} scache.AddNode(logger, &node) @@ -1947,7 +2082,7 @@ func TestSchedulerFailedSchedulingReasons(t *testing.T) { defer apiDispatcher.Close() } - scache := internalcache.New(ctx, 10*time.Minute, apiDispatcher) + scache := internalcache.New(ctx, apiDispatcher, false) // Design the baseline for the pods, and we will make nodes that don't fit it later. var cpu = int64(4) @@ -2255,7 +2390,7 @@ func TestSchedulerBinding(t *testing.T) { if err != nil { t.Fatal(err) } - cache := internalcache.New(ctx, 100*time.Millisecond, apiDispatcher) + cache := internalcache.New(ctx, apiDispatcher, false) if asyncAPICallsEnabled { informerFactory := informers.NewSharedInformerFactory(client, 0) ar := metrics.NewMetricsAsyncRecorder(10, 1*time.Second, ctx.Done()) @@ -3578,7 +3713,7 @@ func TestSchedulerSchedulePod(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := internalcache.New(ctx, time.Duration(0), nil) + cache := internalcache.New(ctx, nil, false) for _, pod := range test.pods { cache.AddPod(logger, pod) } @@ -3624,7 +3759,8 @@ func TestSchedulerSchedulePod(t *testing.T) { informerFactory.Start(ctx.Done()) informerFactory.WaitForCacheSync(ctx.Done()) - result, err := sched.SchedulePod(ctx, schedFramework, framework.NewCycleState(), test.pod) + podInfo := queuedPodInfoForPod(test.pod) + result, err := sched.SchedulePod(ctx, schedFramework, framework.NewCycleState(), podInfo) if err != test.wErr { gotFitErr, gotOK := err.(*framework.FitError) wantFitErr, wantOK := test.wErr.(*framework.FitError) @@ -3677,7 +3813,8 @@ func TestFindFitAllError(t *testing.T) { t.Fatal(err) } - _, diagnosis, _, _, err := scheduler.findNodesThatFitPod(ctx, schedFramework, framework.NewCycleState(), &v1.Pod{}) + podInfo := queuedPodInfoForPod(&v1.Pod{}) + _, diagnosis, _, err := scheduler.findNodesThatFitPod(ctx, schedFramework, framework.NewCycleState(), podInfo) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -3723,7 +3860,8 @@ func TestFindFitSomeError(t *testing.T) { } pod := st.MakePod().Name("1").UID("1").Obj() - _, diagnosis, _, _, err := scheduler.findNodesThatFitPod(ctx, fwk, framework.NewCycleState(), pod) + podInfo := queuedPodInfoForPod(pod) + _, diagnosis, _, err := scheduler.findNodesThatFitPod(ctx, fwk, framework.NewCycleState(), podInfo) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -3818,7 +3956,8 @@ func TestFindFitPredicateCallCounts(t *testing.T) { } schedFramework.AddNominatedPod(logger, podinfo, &fwk.NominatingInfo{NominatingMode: fwk.ModeOverride, NominatedNodeName: "1"}) - _, _, _, _, err = scheduler.findNodesThatFitPod(ctx, schedFramework, framework.NewCycleState(), test.pod) + podInfo := queuedPodInfoForPod(test.pod) + _, _, _, err = scheduler.findNodesThatFitPod(ctx, schedFramework, framework.NewCycleState(), podInfo) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -3829,156 +3968,6 @@ func TestFindFitPredicateCallCounts(t *testing.T) { } } -// The point of this test is to show that you: -// - get the same priority for a zero-request pod as for a pod with the defaults requests, -// both when the zero-request pod is already on the node and when the zero-request pod -// is the one being scheduled. -// - don't get the same score no matter what we schedule. -func TestZeroRequest(t *testing.T) { - // A pod with no resources. We expect spreading to count it as having the default resources. - noResources := v1.PodSpec{ - Containers: []v1.Container{ - {}, - }, - } - noResources1 := noResources - noResources1.NodeName = "node1" - // A pod with the same resources as a 0-request pod gets by default as its resources (for spreading). - small := v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse( - strconv.FormatInt(schedutil.DefaultMilliCPURequest, 10) + "m"), - v1.ResourceMemory: resource.MustParse( - strconv.FormatInt(schedutil.DefaultMemoryRequest, 10)), - }, - }, - }, - }, - } - small2 := small - small2.NodeName = "node2" - // A larger pod. - large := v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse( - strconv.FormatInt(schedutil.DefaultMilliCPURequest*3, 10) + "m"), - v1.ResourceMemory: resource.MustParse( - strconv.FormatInt(schedutil.DefaultMemoryRequest*3, 10)), - }, - }, - }, - }, - } - large1 := large - large1.NodeName = "node1" - large2 := large - large2.NodeName = "node2" - tests := []struct { - pod *v1.Pod - pods []*v1.Pod - nodes []*v1.Node - name string - expectedScore int64 - }{ - // The point of these next two tests is to show you get the same priority for a zero-request pod - // as for a pod with the defaults requests, both when the zero-request pod is already on the node - // and when the zero-request pod is the one being scheduled. - { - pod: &v1.Pod{Spec: noResources}, - nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)}, - name: "test priority of zero-request pod with node with zero-request pod", - pods: []*v1.Pod{ - {Spec: large1}, {Spec: noResources1}, - {Spec: large2}, {Spec: small2}, - }, - expectedScore: 50, - }, - { - pod: &v1.Pod{Spec: small}, - nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)}, - name: "test priority of nonzero-request pod with node with zero-request pod", - pods: []*v1.Pod{ - {Spec: large1}, {Spec: noResources1}, - {Spec: large2}, {Spec: small2}, - }, - expectedScore: 150, - }, - // The point of this test is to verify that we're not just getting the same score no matter what we schedule. - { - pod: &v1.Pod{Spec: large}, - nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)}, - name: "test priority of larger pod with node with zero-request pod", - pods: []*v1.Pod{ - {Spec: large1}, {Spec: noResources1}, - {Spec: large2}, {Spec: small2}, - }, - expectedScore: 130, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - client := clientsetfake.NewClientset() - informerFactory := informers.NewSharedInformerFactory(client, 0) - - snapshot := internalcache.NewSnapshot(test.pods, test.nodes) - fts := feature.Features{} - pluginRegistrations := []tf.RegisterPluginFunc{ - tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), - tf.RegisterScorePlugin(noderesources.Name, frameworkruntime.FactoryAdapter(fts, noderesources.NewFit), 1), - tf.RegisterScorePlugin(noderesources.BalancedAllocationName, frameworkruntime.FactoryAdapter(fts, noderesources.NewBalancedAllocation), 1), - tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), - } - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - fwk, err := tf.NewFramework( - ctx, - pluginRegistrations, "", - frameworkruntime.WithInformerFactory(informerFactory), - frameworkruntime.WithSnapshotSharedLister(snapshot), - frameworkruntime.WithClientSet(client), - frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)), - ) - if err != nil { - t.Fatalf("error creating framework: %+v", err) - } - - sched := &Scheduler{ - nodeInfoSnapshot: snapshot, - percentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore, - } - sched.applyDefaultHandlers() - - state := framework.NewCycleState() - _, _, _, _, err = sched.findNodesThatFitPod(ctx, fwk, state, test.pod) - if err != nil { - t.Fatalf("error filtering nodes: %+v", err) - } - nodeInfos, err := snapshot.NodeInfos().List() - if err != nil { - t.Fatalf("failed to list node from snapshot: %v", err) - } - fwk.RunPreScorePlugins(ctx, state, test.pod, nodeInfos) - list, err := prioritizeNodes(ctx, nil, fwk, state, test.pod, nodeInfos) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - for _, hp := range list { - if hp.TotalScore != test.expectedScore { - t.Errorf("expected %d for all priorities, got list %#v", test.expectedScore, list) - } - } - }) - } -} - func Test_prioritizeNodes(t *testing.T) { imageStatus1 := []v1.ContainerImage{ { @@ -4338,7 +4327,7 @@ func Test_prioritizeNodes(t *testing.T) { _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() - cache := internalcache.New(ctx, time.Duration(0), nil) + cache := internalcache.New(ctx, nil, false) for _, node := range test.nodes { cache.AddNode(klog.FromContext(ctx), node) } @@ -4491,7 +4480,8 @@ func TestFairEvaluationForNodes(t *testing.T) { // Iterating over all nodes more than twice for i := 0; i < 2*(numAllNodes/nodesToFind+1); i++ { - nodesThatFit, _, _, _, err := sched.findNodesThatFitPod(ctx, fwk, framework.NewCycleState(), &v1.Pod{}) + podInfo := queuedPodInfoForPod(&v1.Pod{}) + nodesThatFit, _, _, err := sched.findNodesThatFitPod(ctx, fwk, framework.NewCycleState(), podInfo) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -4539,7 +4529,7 @@ func TestPreferNominatedNodeFilterCallCounts(t *testing.T) { nodes := makeNodeList([]string{"node1", "node2", "node3"}) client := clientsetfake.NewClientset(test.pod) informerFactory := informers.NewSharedInformerFactory(client, 0) - cache := internalcache.New(ctx, time.Duration(0), nil) + cache := internalcache.New(ctx, nil, false) for _, n := range nodes { cache.AddNode(logger, n) } @@ -4575,7 +4565,8 @@ func TestPreferNominatedNodeFilterCallCounts(t *testing.T) { } sched.applyDefaultHandlers() - _, _, _, _, err = sched.findNodesThatFitPod(ctx, fwk, framework.NewCycleState(), test.pod) + podInfo := queuedPodInfoForPod(test.pod) + _, _, _, err = sched.findNodesThatFitPod(ctx, fwk, framework.NewCycleState(), podInfo) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -4621,7 +4612,7 @@ func makeNodeList(nodeNames []string) []*v1.Node { // makeScheduler makes a simple Scheduler for testing. func makeScheduler(ctx context.Context, nodes []*v1.Node) *Scheduler { logger := klog.FromContext(ctx) - cache := internalcache.New(ctx, time.Duration(0), nil) + cache := internalcache.New(ctx, nil, false) for _, n := range nodes { cache.AddNode(logger, n) } @@ -4705,7 +4696,7 @@ func setupTestSchedulerWithOnePodOnNode(ctx context.Context, t *testing.T, clien func setupTestScheduler(ctx context.Context, t *testing.T, client clientset.Interface, queuedPodStore *clientcache.FIFO, cache internalcache.Cache, apiDispatcher *apidispatcher.APIDispatcher, informerFactory informers.SharedInformerFactory, broadcaster events.EventBroadcaster, fns ...tf.RegisterPluginFunc) (*Scheduler, chan error) { - var recorder events.EventRecorder + var recorder events.EventRecorderLogger if broadcaster != nil { recorder = broadcaster.NewRecorder(scheme.Scheme, testSchedulerName) } else { @@ -4729,6 +4720,7 @@ func setupTestScheduler(ctx context.Context, t *testing.T, client clientset.Inte frameworkruntime.WithInformerFactory(informerFactory), frameworkruntime.WithPodNominator(schedulingQueue), frameworkruntime.WithWaitingPods(waitingPods), + frameworkruntime.WithPodsInPreBind(frameworkruntime.NewPodsInPreBindMap()), frameworkruntime.WithSnapshotSharedLister(snapshot), ) if apiDispatcher != nil { @@ -4742,7 +4734,7 @@ func setupTestScheduler(ctx context.Context, t *testing.T, client clientset.Inte nodeInfoSnapshot: snapshot, percentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore, NextPod: func(logger klog.Logger) (*framework.QueuedPodInfo, error) { - return &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(t, clientcache.Pop(queuedPodStore).(*v1.Pod))}, nil + return &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(t, pop(queuedPodStore).(*v1.Pod))}, nil }, SchedulingQueue: schedulingQueue, APIDispatcher: apiDispatcher, @@ -4750,12 +4742,12 @@ func setupTestScheduler(ctx context.Context, t *testing.T, client clientset.Inte } sched.SchedulePod = sched.schedulePod - sched.FailureHandler = func(_ context.Context, _ framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, _ *fwk.NominatingInfo, _ time.Time) { + sched.FailureHandler = func(ctx context.Context, _ framework.Framework, p *framework.QueuedPodInfo, status *fwk.Status, _ *fwk.NominatingInfo, _ time.Time) { err := status.AsError() errChan <- err msg := truncateMessage(err.Error()) - schedFramework.EventRecorder().Eventf(p.Pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", msg) + schedFramework.EventRecorder().WithLogger(klog.FromContext(ctx)).Eventf(p.Pod, nil, v1.EventTypeWarning, "FailedScheduling", "Scheduling", msg) } return sched, errChan } @@ -4784,7 +4776,7 @@ func setupTestSchedulerWithVolumeBinding(ctx context.Context, t *testing.T, clie t.Cleanup(apiDispatcher.Close) } - scache := internalcache.New(ctx, 10*time.Minute, apiDispatcher) + scache := internalcache.New(ctx, apiDispatcher, false) scache.AddNode(logger, &testNode) informerFactory := informers.NewSharedInformerFactory(client, 0) pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() @@ -4825,3 +4817,106 @@ func mustNewPodInfo(t *testing.T, pod *v1.Pod) *framework.PodInfo { } return podInfo } + +func pop(queue clientcache.Queue) interface{} { + obj, err := queue.Pop(func(obj interface{}, isInInitialList bool) error { + return nil + }) + if err != nil { + return nil + } + return obj +} + +func queuedPodInfoForPod(pod *v1.Pod) *framework.QueuedPodInfo { + return &framework.QueuedPodInfo{ + PodInfo: &framework.PodInfo{ + Pod: pod, + }, + } +} + +func TestEvaluateNominatedNode(t *testing.T) { + tests := map[string]struct { + allNodes []*v1.Node + placementNodes []string + pod *v1.Pod + wantNodeList []string + wantError bool + }{ + "When NNN is present in both snapshot and placement, returns node": { + allNodes: []*v1.Node{ + st.MakeNode().Name("n1").Obj(), + st.MakeNode().Name("n2").Obj(), + }, + placementNodes: []string{"n1"}, + pod: st.MakePod().NominatedNodeName("n1").Obj(), + wantNodeList: []string{"n1"}, + }, + "When NNN is present in snapshot but not in placement, returns success": { + allNodes: []*v1.Node{ + st.MakeNode().Name("n1").Obj(), + st.MakeNode().Name("n2").Obj(), + }, + placementNodes: []string{"n1"}, + pod: st.MakePod().NominatedNodeName("n2").Obj(), + wantError: false, + }, + "When NNN is not present in snapshot, returns error": { + allNodes: []*v1.Node{ + st.MakeNode().Name("n1").Obj(), + st.MakeNode().Name("n2").Obj(), + }, + placementNodes: []string{"n1"}, + pod: st.MakePod().NominatedNodeName("n3").Obj(), + wantError: true, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + snapshot := internalcache.NewSnapshot(nil, tt.allNodes) + placement := &fwk.Placement{} + for _, nodeName := range tt.placementNodes { + node, err := snapshot.Get(nodeName) + if err != nil { + t.Fatalf("Error getting node %s: %v", nodeName, err) + } + placement.Nodes = append(placement.Nodes, node) + } + err := snapshot.AssumePlacement(placement) + if err != nil { + t.Fatalf("AssumePlacement failed: %v", err) + } + fw, err := tf.NewFramework( + ctx, + []tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + }, + "", + frameworkruntime.WithSnapshotSharedLister(snapshot), + ) + if err != nil { + t.Fatalf("NewFramework failed: %v", err) + } + sched := &Scheduler{ + nodeInfoSnapshot: snapshot, + } + + gotNodes, err := sched.evaluateNominatedNode(ctx, tt.pod, fw, framework.NewCycleState(), "", framework.Diagnosis{}) + + if (err != nil) != tt.wantError { + t.Errorf("Unexpected error, want error: %v, got: %v", tt.wantError, err) + } + gotNodeNames := make([]string, len(gotNodes)) + for i, n := range gotNodes { + gotNodeNames[i] = n.Node().Name + } + if diff := cmp.Diff(tt.wantNodeList, gotNodeNames, cmpopts.EquateEmpty()); diff != "" { + t.Errorf("Unexpected nodes (-want, +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go index 7c289c154144d..f001ef6ec1257 100644 --- a/pkg/scheduler/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -31,6 +31,7 @@ import ( "k8s.io/client-go/informers" coreinformers "k8s.io/client-go/informers/core/v1" clientset "k8s.io/client-go/kubernetes" + schedulinglisters "k8s.io/client-go/listers/scheduling/v1alpha2" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker" @@ -45,7 +46,6 @@ import ( internalcache "k8s.io/kubernetes/pkg/scheduler/backend/cache" cachedebugger "k8s.io/kubernetes/pkg/scheduler/backend/cache/debugger" internalqueue "k8s.io/kubernetes/pkg/scheduler/backend/queue" - internalworkloadmanager "k8s.io/kubernetes/pkg/scheduler/backend/workloadmanager" "k8s.io/kubernetes/pkg/scheduler/framework" apicalls "k8s.io/kubernetes/pkg/scheduler/framework/api_calls" "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" @@ -60,12 +60,6 @@ import ( "k8s.io/utils/clock" ) -const ( - // Duration the scheduler will wait before expiring an assumed pod. - // See issue #106361 for more details about this parameter and its value. - durationToExpireAssumedPod time.Duration = 0 -) - // ErrNoNodesAvailable is used to describe the error that no nodes available to schedule pods. var ErrNoNodesAvailable = fmt.Errorf("no nodes available to schedule pods") @@ -90,7 +84,7 @@ type Scheduler struct { // SchedulePod tries to schedule the given pod to one of the nodes in the node list. // Return a struct of ScheduleResult with the name of suggested host on success, // otherwise will return a FitError with reasons. - SchedulePod func(ctx context.Context, fwk framework.Framework, state fwk.CycleState, pod *v1.Pod) (ScheduleResult, error) + SchedulePod func(ctx context.Context, fwk framework.Framework, state fwk.CycleState, podInfo *framework.QueuedPodInfo) (ScheduleResult, error) // Close this to shut down the scheduler. StopEverything <-chan struct{} @@ -104,9 +98,6 @@ type Scheduler struct { // framework.APICache should be used instead. APIDispatcher *apidispatcher.APIDispatcher - // WorkloadManager can be used to provide workload-aware scheduling. - WorkloadManager internalworkloadmanager.WorkloadManager - // Profiles are the scheduling profiles. Profiles profile.Map @@ -123,10 +114,14 @@ type Scheduler struct { // panic. logger klog.Logger + podGroupLister schedulinglisters.PodGroupLister + // registeredHandlers contains the registrations of all handlers. It's used to check if all handlers have finished syncing before the scheduling cycles start. registeredHandlers []cache.ResourceEventHandlerRegistration nominatedNodeNameForExpectationEnabled bool + genericWorkloadEnabled bool + workloadAwarePreemptionEnabled bool } func (sched *Scheduler) applyDefaultHandlers() { @@ -318,12 +313,19 @@ func New(ctx context.Context, podLister := informerFactory.Core().V1().Pods().Lister() nodeLister := informerFactory.Core().V1().Nodes().Lister() + var podGroupLister schedulinglisters.PodGroupLister + if feature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + podGroupLister = informerFactory.Scheduling().V1alpha2().PodGroups().Lister() + } snapshot := internalcache.NewEmptySnapshot() metricsRecorder := metrics.NewMetricsAsyncRecorder(1000, time.Second, stopEverything) // waitingPods holds all the pods that are in the scheduler and waiting in the permit stage waitingPods := frameworkruntime.NewWaitingPodsMap() + // podsInPreBind holds all the pods that are in the scheduler in the preBind phase + podsInPreBind := frameworkruntime.NewPodsInPreBindMap() + var resourceClaimCache *assumecache.AssumeCache var resourceSliceTracker *resourceslicetracker.Tracker var draManager fwk.SharedDRAManager @@ -339,7 +341,7 @@ func New(ctx context.Context, // If device taint rules are disabled, the additional informers are not needed and // the tracker turns into a simple wrapper around the slice informer. if resourceSliceTrackerOpts.EnableDeviceTaintRules { - resourceSliceTrackerOpts.TaintInformer = informerFactory.Resource().V1alpha3().DeviceTaintRules() + resourceSliceTrackerOpts.TaintInformer = informerFactory.Resource().V1beta2().DeviceTaintRules() resourceSliceTrackerOpts.ClassInformer = informerFactory.Resource().V1().DeviceClasses() } resourceSliceTracker, err = resourceslicetracker.StartTracker(ctx, resourceSliceTrackerOpts) @@ -354,10 +356,8 @@ func New(ctx context.Context, if feature.DefaultFeatureGate.Enabled(features.SchedulerAsyncAPICalls) { apiDispatcher = apidispatcher.New(client, int(options.parallelism), apicalls.Relevances) } - var workloadManager internalworkloadmanager.WorkloadManager - if feature.DefaultFeatureGate.Enabled(features.GenericWorkload) { - workloadManager = internalworkloadmanager.New() - } + + schedulerCache := internalcache.New(ctx, apiDispatcher, feature.DefaultFeatureGate.Enabled(features.GenericWorkload)) profiles, err := profile.NewMap(ctx, options.profiles, registry, recorderFactory, frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion), @@ -371,9 +371,10 @@ func New(ctx context.Context, frameworkruntime.WithExtenders(extenders), frameworkruntime.WithMetricsRecorder(metricsRecorder), frameworkruntime.WithWaitingPods(waitingPods), + frameworkruntime.WithPodsInPreBind(podsInPreBind), frameworkruntime.WithAPIDispatcher(apiDispatcher), frameworkruntime.WithSharedCSIManager(sharedCSIManager), - frameworkruntime.WithWorkloadManager(workloadManager), + frameworkruntime.WithPodGroupManager(schedulerCache), ) if err != nil { return nil, fmt.Errorf("initializing profiles: %v", err) @@ -403,6 +404,12 @@ func New(ctx context.Context, return nil, returnErr } + // Extract pod signing functions from each profile + podSigners := make(map[string]internalqueue.PodSigner) + for profileName, profile := range profiles { + podSigners[profileName] = profile.SignPod + } + podQueue := internalqueue.NewSchedulingQueue( profiles[options.profiles[0].SchedulerName].QueueSortFunc(), informerFactory, @@ -416,10 +423,9 @@ func New(ctx context.Context, internalqueue.WithPluginMetricsSamplePercent(pluginMetricsSamplePercent), internalqueue.WithMetricsRecorder(metricsRecorder), internalqueue.WithAPIDispatcher(apiDispatcher), + internalqueue.WithPodSigners(podSigners), ) - schedulerCache := internalcache.New(ctx, durationToExpireAssumedPod, apiDispatcher) - var apiCache fwk.APICacher if apiDispatcher != nil { apiCache = apicache.New(podQueue, schedulerCache) @@ -447,7 +453,9 @@ func New(ctx context.Context, logger: logger, APIDispatcher: apiDispatcher, nominatedNodeNameForExpectationEnabled: feature.DefaultFeatureGate.Enabled(features.NominatedNodeNameForExpectation), - WorkloadManager: workloadManager, + podGroupLister: podGroupLister, + genericWorkloadEnabled: feature.DefaultFeatureGate.Enabled(features.GenericWorkload), + workloadAwarePreemptionEnabled: feature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption), } sched.NextPod = podQueue.Pop sched.applyDefaultHandlers() diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go index 519c6fc71a226..88db89f9a0bf1 100644 --- a/pkg/scheduler/scheduler_test.go +++ b/pkg/scheduler/scheduler_test.go @@ -29,6 +29,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/onsi/gomega" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" eventsv1 "k8s.io/api/events/v1" @@ -309,9 +310,9 @@ func TestFailureHandler(t *testing.T) { recorder := metrics.NewMetricsAsyncRecorder(3, 20*time.Microsecond, ctx.Done()) queue := internalqueue.NewPriorityQueue(nil, informerFactory, internalqueue.WithClock(testingclock.NewFakeClock(time.Now())), internalqueue.WithMetricsRecorder(recorder), internalqueue.WithAPIDispatcher(apiDispatcher)) - schedulerCache := internalcache.New(ctx, 30*time.Second, apiDispatcher) + schedulerCache := internalcache.New(ctx, apiDispatcher, false) - queue.Add(logger, testPod) + queue.Add(ctx, testPod) if _, err := queue.Pop(logger); err != nil { t.Fatalf("Pop failed: %v", err) @@ -321,7 +322,7 @@ func TestFailureHandler(t *testing.T) { if err := podInformer.Informer().GetStore().Update(testPodUpdated); err != nil { t.Fatal(err) } - queue.Update(logger, testPod, testPodUpdated) + queue.Update(ctx, testPod, testPodUpdated) } if tt.podDeletedDuringScheduling { if err := podInformer.Informer().GetStore().Delete(testPod); err != nil { @@ -383,7 +384,7 @@ func TestFailureHandler_PodAlreadyBound(t *testing.T) { } queue := internalqueue.NewPriorityQueue(nil, informerFactory, internalqueue.WithClock(testingclock.NewFakeClock(time.Now())), internalqueue.WithAPIDispatcher(apiDispatcher)) - schedulerCache := internalcache.New(ctx, 30*time.Second, apiDispatcher) + schedulerCache := internalcache.New(ctx, apiDispatcher, false) // Add node to schedulerCache no matter it's deleted in API server or not. schedulerCache.AddNode(logger, &nodeFoo) @@ -691,7 +692,7 @@ func Test_buildQueueingHintMap(t *testing.T) { {Resource: fwk.DeviceClass, ActionType: fwk.All}: { {PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn}, }, - {Resource: fwk.Workload, ActionType: fwk.All}: { + {Resource: fwk.PodGroup, ActionType: fwk.All}: { {PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn}, }, }, @@ -884,7 +885,7 @@ func Test_UnionedGVKs(t *testing.T) { fwk.StorageClass: fwk.All, fwk.ResourceClaim: fwk.All, fwk.DeviceClass: fwk.All, - fwk.Workload: fwk.All, + fwk.PodGroup: fwk.All, }, enableGangScheduling: true, enableInPlacePodVerticalScaling: true, @@ -1035,8 +1036,8 @@ func Test_UnionedGVKs(t *testing.T) { enableSchedulerQueueingHints: true, }, { - name: "plugins with default profile and NodeDeclaredFeatures", - plugins: schedulerapi.PluginSet{Enabled: append(defaults.PluginsV1.MultiPoint.Enabled, schedulerapi.Plugin{Name: names.NodeDeclaredFeatures})}, + name: "plugins with default profile (NodeDeclaredFeatures: enabled)", + plugins: defaults.PluginsV1.MultiPoint, want: map[fwk.EventResource]fwk.ActionType{ // NodeDeclaredFeatures adds fwk.Update fwk.Pod: fwk.Add | fwk.UpdatePodLabel | fwk.UpdatePodGeneratedResourceClaim | fwk.UpdatePodToleration | fwk.UpdatePodSchedulingGatesEliminated | fwk.Delete | fwk.Update, @@ -1074,7 +1075,7 @@ func Test_UnionedGVKs(t *testing.T) { fwk.DeviceClass: fwk.All - fwk.Delete, fwk.ResourceClaim: fwk.All - fwk.Delete, fwk.ResourceSlice: fwk.All - fwk.Delete, - fwk.Workload: fwk.Add, + fwk.PodGroup: fwk.Add, }, enableGangScheduling: true, enableInPlacePodVerticalScaling: true, @@ -1092,6 +1093,15 @@ func Test_UnionedGVKs(t *testing.T) { } else if !tt.enableInPlacePodVerticalScaling { // In place pod resize GA'd in 1.35. Set emulation version to 1.34 for tests that do not have the flag set featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.34")) + // DRADeviceBindingConditions is alpha in 1.34 (disabled by default). + // Strip BindingTimeout from DynamicResources args to avoid validation failure. + pluginConfig = slices.Clone(pluginConfig) + for i := range pluginConfig { + if pluginConfig[i].Name == "DynamicResources" { + pluginConfig[i].Args = &schedulerapi.DynamicResourcesArgs{} + break + } + } } else { featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.NodeDeclaredFeatures, tt.enableNodeDeclaredFeatures) featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ @@ -1301,7 +1311,7 @@ func TestFrameworkHandler_IterateOverWaitingPods(t *testing.T) { // Wait all pods in waitSchedulingPods to be scheduled. wg.Wait() - utiltesting.Eventually(tCtx, func(utiltesting.TContext) sets.Set[string] { + tCtx.Eventually(func(utiltesting.TContext) sets.Set[string] { // Ensure that all waitingPods in scheduler can be obtained from any profiles. actualPodNamesInWaitingPods := sets.New[string]() for _, schedFramework := range scheduler.Profiles { @@ -1432,10 +1442,10 @@ func (*emptyEventsToRegisterPlugin) EventsToRegister(_ context.Context) ([]fwk.C // fakePermitPlugin only implements PermitPlugin interface. type fakePermitPlugin struct { - eventRecorder events.EventRecorder + eventRecorder events.EventRecorderLogger } -func newFakePermitPlugin(eventRecorder events.EventRecorder) frameworkruntime.PluginFactory { +func newFakePermitPlugin(eventRecorder events.EventRecorderLogger) frameworkruntime.PluginFactory { return func(ctx context.Context, configuration runtime.Object, f fwk.Handle) (fwk.Plugin, error) { pl := &fakePermitPlugin{ eventRecorder: eventRecorder, @@ -1456,10 +1466,39 @@ const ( func (f fakePermitPlugin) Permit(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) (*fwk.Status, time.Duration) { defer func() { // Send event with podWaiting reason to broadcast this pod is already waiting in the permit stage. - f.eventRecorder.Eventf(p, nil, v1.EventTypeWarning, podWaitingReason, "", "") + f.eventRecorder.WithLogger(klog.FromContext(ctx)).Eventf(p, nil, v1.EventTypeWarning, podWaitingReason, "", "") }() return fwk.NewStatus(fwk.Wait), permitTimeout } var _ fwk.PermitPlugin = &fakePermitPlugin{} + +func TestNewInformerFactoryTrim(t *testing.T) { + cs := fake.NewClientset() + + pd := &v1.Pod{ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + ManagedFields: []metav1.ManagedFieldsEntry{ + { + Manager: "update", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + }, + }, + }} + require.NoError(t, cs.Tracker().Add(pd)) + + informerFactory := NewInformerFactory(cs, 0) + lister := informerFactory.Core().V1().Pods().Lister() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + informerFactory.Start(ctx.Done()) + informerFactory.WaitForCacheSync(ctx.Done()) + + p, err := lister.Pods("default").Get("test") + require.NoError(t, err) + require.Empty(t, p.GetManagedFields(), "expected managedFields to be trimmed by the transform") +} diff --git a/pkg/scheduler/testing/framework/fake_plugins.go b/pkg/scheduler/testing/framework/fake_plugins.go index 14caf01a5d4a4..3a15dd63f392c 100644 --- a/pkg/scheduler/testing/framework/fake_plugins.go +++ b/pkg/scheduler/testing/framework/fake_plugins.go @@ -196,6 +196,32 @@ func NewFakeReservePlugin(status *fwk.Status) frameworkruntime.PluginFactory { } } +// FakePostFilterPlugin is a test post filter plugin. +type FakePostFilterPlugin struct { + Result *fwk.PostFilterResult + Status *fwk.Status +} + +// Name returns name of the plugin. +func (pl *FakePostFilterPlugin) Name() string { + return "FakePostFilter" +} + +// PostFilter invoked at the PostFilter extension point. +func (pl *FakePostFilterPlugin) PostFilter(_ context.Context, _ fwk.CycleState, _ *v1.Pod, _ fwk.NodeToStatusReader) (*fwk.PostFilterResult, *fwk.Status) { + return pl.Result, pl.Status +} + +// NewFakePostFilterPlugin initializes a fakePostFilterPlugin and returns it. +func NewFakePostFilterPlugin(result *fwk.PostFilterResult, status *fwk.Status) frameworkruntime.PluginFactory { + return func(_ context.Context, _ runtime.Object, _ fwk.Handle) (fwk.Plugin, error) { + return &FakePostFilterPlugin{ + Result: result, + Status: status, + }, nil + } +} + // FakePreBindPlugin is a test prebind plugin. type FakePreBindPlugin struct { PreBindPreFlightStatus *fwk.Status @@ -208,8 +234,8 @@ func (pl *FakePreBindPlugin) Name() string { } // PreBindPreFlight invoked at the PreBind extension point. -func (pl *FakePreBindPlugin) PreBindPreFlight(_ context.Context, _ fwk.CycleState, _ *v1.Pod, _ string) *fwk.Status { - return pl.PreBindPreFlightStatus +func (pl *FakePreBindPlugin) PreBindPreFlight(_ context.Context, _ fwk.CycleState, _ *v1.Pod, _ string) (*fwk.PreBindPreFlightResult, *fwk.Status) { + return &fwk.PreBindPreFlightResult{AllowParallel: false}, pl.PreBindPreFlightStatus } // PreBind invoked at the PreBind extension point. diff --git a/pkg/scheduler/testing/framework/framework_helpers.go b/pkg/scheduler/testing/framework/framework_helpers.go index 9372fd64b4152..c91f13bb2efad 100644 --- a/pkg/scheduler/testing/framework/framework_helpers.go +++ b/pkg/scheduler/testing/framework/framework_helpers.go @@ -63,6 +63,11 @@ func RegisterFilterPlugin(pluginName string, pluginNewFunc runtime.PluginFactory return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Filter") } +// RegisterPostFilterPlugin returns a function to register a PostFilter Plugin to a given registry. +func RegisterPostFilterPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc { + return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PostFilter") +} + // RegisterReservePlugin returns a function to register a Reserve Plugin to a given registry. func RegisterReservePlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc { return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Reserve") @@ -93,6 +98,16 @@ func RegisterBindPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Bind") } +// RegisterPlacementGeneratePlugin returns a function to register a PlacementGenerate Plugin to a given registry. +func RegisterPlacementGeneratePlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc { + return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PlacementGenerate") +} + +// RegisterPlacementScorePlugin returns a function to register a PlacementScore Plugin to a given registry. +func RegisterPlacementScorePlugin(pluginName string, pluginNewFunc runtime.PluginFactory, weight int32) RegisterPluginFunc { + return RegisterPluginAsExtensionsWithWeight(pluginName, weight, pluginNewFunc, "PlacementScore") +} + // RegisterPluginAsExtensions returns a function to register a Plugin as given extensionPoints to a given registry. func RegisterPluginAsExtensions(pluginName string, pluginNewFunc runtime.PluginFactory, extensions ...string) RegisterPluginFunc { return RegisterPluginAsExtensionsWithWeight(pluginName, 1, pluginNewFunc, extensions...) @@ -127,6 +142,8 @@ func getPluginSetByExtension(plugins *schedulerapi.Plugins, extension string) *s return &plugins.QueueSort case "Filter": return &plugins.Filter + case "PostFilter": + return &plugins.PostFilter case "PreFilter": return &plugins.PreFilter case "PreScore": @@ -143,6 +160,10 @@ func getPluginSetByExtension(plugins *schedulerapi.Plugins, extension string) *s return &plugins.PreBind case "PostBind": return &plugins.PostBind + case "PlacementGenerate": + return &plugins.PlacementGenerate + case "PlacementScore": + return &plugins.PlacementScore default: return nil } diff --git a/pkg/scheduler/testing/wrappers.go b/pkg/scheduler/testing/wrappers.go index 224e6c1fe18a2..f8dcaee4e266f 100644 --- a/pkg/scheduler/testing/wrappers.go +++ b/pkg/scheduler/testing/wrappers.go @@ -24,7 +24,7 @@ import ( v1 "k8s.io/api/core/v1" policy "k8s.io/api/policy/v1" resourceapi "k8s.io/api/resource/v1" - schedulingapi "k8s.io/api/scheduling/v1alpha1" + schedulingapi "k8s.io/api/scheduling/v1alpha2" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -866,9 +866,11 @@ func (p *PodWrapper) Overhead(rl v1.ResourceList) *PodWrapper { return p } -// WorkloadRef sets workloadRef of the inner pod. -func (p *PodWrapper) WorkloadRef(workloadRef *v1.WorkloadReference) *PodWrapper { - p.Spec.WorkloadRef = workloadRef +// PodGroupName sets `name` as the PodGroupName of the inner pod. +func (p *PodWrapper) PodGroupName(name string) *PodWrapper { + p.Spec.SchedulingGroup = &v1.PodSchedulingGroup{ + PodGroupName: new(name), + } return p } @@ -1322,6 +1324,11 @@ func (wrapper *ResourceClaimWrapper) ReservedForPod(podName string, podUID types return wrapper.ReservedFor(resourceapi.ResourceClaimConsumerReference{Resource: "pods", Name: podName, UID: podUID}) } +// ReservedForPodGroup sets that field of the inner object given information about one podgroup. +func (wrapper *ResourceClaimWrapper) ReservedForPodGroup(podGroupName string, podGroupUID types.UID) *ResourceClaimWrapper { + return wrapper.ReservedFor(resourceapi.ResourceClaimConsumerReference{APIGroup: schedulingapi.GroupName, Resource: "podgroups", Name: podGroupName, UID: podGroupUID}) +} + type ResourceSliceWrapper struct { resourceapi.ResourceSlice } @@ -1336,6 +1343,16 @@ func MakeResourceSlice(nodeName, driverName string) *ResourceSliceWrapper { return wrapper } +func MakeResourceSliceWithPerDeviceNodeSelection(namePrefix, driverName string) *ResourceSliceWrapper { + wrapper := new(ResourceSliceWrapper) + wrapper.Name = namePrefix + "-" + driverName + wrapper.Spec.PerDeviceNodeSelection = ptr.To(true) + wrapper.Spec.Pool.Name = namePrefix + wrapper.Spec.Pool.ResourceSliceCount = 1 + wrapper.Spec.Driver = driverName + return wrapper +} + // FromResourceSlice creates a ResourceSlice wrapper from some existing object. func FromResourceSlice(other *resourceapi.ResourceSlice) *ResourceSliceWrapper { return &ResourceSliceWrapper{*other.DeepCopy()} @@ -1353,6 +1370,8 @@ func (wrapper *ResourceSliceWrapper) Devices(names ...string) *ResourceSliceWrap return wrapper } +type NodeName string + // Device extends the devices field of the inner object. // The device must have a name and may have arbitrary additional fields. func (wrapper *ResourceSliceWrapper) Device(name string, otherFields ...any) *ResourceSliceWrapper { @@ -1365,6 +1384,10 @@ func (wrapper *ResourceSliceWrapper) Device(name string, otherFields ...any) *Re device.Capacity = typedField case resourceapi.DeviceTaint: device.Taints = append(device.Taints, typedField) + case NodeName: + device.NodeName = (*string)(&typedField) + case *v1.NodeSelector: + device.NodeSelector = typedField default: panic(fmt.Sprintf("expected a type which matches a field in BasicDevice, got %T", field)) } @@ -1550,6 +1573,99 @@ func (c *VolumeAttachmentWrapper) Attached(attached bool) *VolumeAttachmentWrapp return c } +// PodGroupWrapper wraps a PodGroup inside. +type PodGroupWrapper struct{ schedulingapi.PodGroup } + +// MakePodGroup creates a PodGroup wrapper. +func MakePodGroup() *PodGroupWrapper { + return &PodGroupWrapper{} +} + +// Name sets `name` as the name of the inner PodGroup. +func (wrapper *PodGroupWrapper) Name(name string) *PodGroupWrapper { + wrapper.PodGroup.Name = name + return wrapper +} + +// Namespace sets `namespace` as the namespace of the inner PodGroup. +func (wrapper *PodGroupWrapper) Namespace(namespace string) *PodGroupWrapper { + wrapper.PodGroup.Namespace = namespace + return wrapper +} + +// UID sets `uid` as the UID of the inner PodGroup. +func (wrapper *PodGroupWrapper) UID(uid types.UID) *PodGroupWrapper { + wrapper.PodGroup.UID = uid + return wrapper +} + +// Obj returns the inner PodGroup. +func (wrapper *PodGroupWrapper) Obj() *schedulingapi.PodGroup { + return &wrapper.PodGroup +} + +// MinCount sets the MinCount for the Gang scheduling policy. +func (wrapper *PodGroupWrapper) MinCount(count int32) *PodGroupWrapper { + if wrapper.PodGroup.Spec.SchedulingPolicy.Gang == nil { + wrapper.PodGroup.Spec.SchedulingPolicy.Gang = &schedulingapi.GangSchedulingPolicy{} + } + wrapper.PodGroup.Spec.SchedulingPolicy.Gang.MinCount = count + return wrapper +} + +// BasicPolicy sets the PodGroup policy to Basic. +func (wrapper *PodGroupWrapper) BasicPolicy() *PodGroupWrapper { + wrapper.PodGroup.Spec.SchedulingPolicy.Basic = &schedulingapi.BasicSchedulingPolicy{} + return wrapper +} + +// TemplateRef sets appropriate PodGroupTemplateRef field of the inner PodGroup. +func (wrapper *PodGroupWrapper) TemplateRef(templateName, workloadName string) *PodGroupWrapper { + wrapper.PodGroup.Spec.PodGroupTemplateRef = &schedulingapi.PodGroupTemplateReference{ + Workload: &schedulingapi.WorkloadPodGroupTemplateReference{ + PodGroupTemplateName: templateName, + WorkloadName: workloadName, + }, + } + return wrapper +} + +// TopologyKey sets appropriate TopologyKey field in the SchedulingConstraints of the inner PodGroup. +func (wrapper *PodGroupWrapper) TopologyKey(topologyKey string) *PodGroupWrapper { + wrapper.PodGroup.Spec.SchedulingConstraints = &schedulingapi.PodGroupSchedulingConstraints{ + Topology: []schedulingapi.TopologyConstraint{ + { + Key: topologyKey, + }, + }, + } + return wrapper +} + +// ResourceClaims adds resource claims to the inner PodGroup. +func (wrapper *PodGroupWrapper) ResourceClaims(claims ...schedulingapi.PodGroupResourceClaim) *PodGroupWrapper { + wrapper.Spec.ResourceClaims = append(wrapper.Spec.ResourceClaims, claims...) + return wrapper +} + +// ResourceClaimStatuses adds resource claim statuses to the inner PodGroup. +func (wrapper *PodGroupWrapper) ResourceClaimStatuses(statuses ...schedulingapi.PodGroupResourceClaimStatus) *PodGroupWrapper { + wrapper.Status.ResourceClaimStatuses = append(wrapper.Status.ResourceClaimStatuses, statuses...) + return wrapper +} + +// DisruptionMode sets the disruption mode of the inner PodGroup. +func (wrapper *PodGroupWrapper) DisruptionMode(mode schedulingapi.DisruptionMode) *PodGroupWrapper { + wrapper.PodGroup.Spec.DisruptionMode = &mode + return wrapper +} + +// Priority sets the priority of the inner PodGroup. +func (wrapper *PodGroupWrapper) Priority(priority int32) *PodGroupWrapper { + wrapper.PodGroup.Spec.Priority = &priority + return wrapper +} + // WorkloadWrapper wraps a Workload inside. type WorkloadWrapper struct{ schedulingapi.Workload } @@ -1565,52 +1681,52 @@ func (wrapper *WorkloadWrapper) Obj() *schedulingapi.Workload { // Name sets `name` as the name of the inner Workload. func (wrapper *WorkloadWrapper) Name(name string) *WorkloadWrapper { - wrapper.SetName(name) + wrapper.Workload.Name = name return wrapper } // Namespace sets `namespace` as the namespace of the inner Workload. func (wrapper *WorkloadWrapper) Namespace(namespace string) *WorkloadWrapper { - wrapper.SetNamespace(namespace) + wrapper.Workload.Namespace = namespace return wrapper } -// PodGroup injects the pod group into the inner Workload. -func (wrapper *WorkloadWrapper) PodGroup(pg *schedulingapi.PodGroup) *WorkloadWrapper { - wrapper.Spec.PodGroups = append(wrapper.Spec.PodGroups, *pg) +// PodGroupTemplate appends the given PodGroupTemplate to the Workload spec. +func (wrapper *WorkloadWrapper) PodGroupTemplate(t schedulingapi.PodGroupTemplate) *WorkloadWrapper { + wrapper.Workload.Spec.PodGroupTemplates = append(wrapper.Workload.Spec.PodGroupTemplates, t) return wrapper } -// PodGroupWrapper wraps a PodGroup inside. -type PodGroupWrapper struct{ schedulingapi.PodGroup } +// PodGroupTemplateWrapper wraps a PodGroupTemplate inside. +type PodGroupTemplateWrapper struct{ schedulingapi.PodGroupTemplate } -// MakePodGroup creates a PodGroup wrapper. -func MakePodGroup() *PodGroupWrapper { - return &PodGroupWrapper{} +// MakePodGroupTemplate creates a PodGroupTemplate wrapper. +func MakePodGroupTemplate() *PodGroupTemplateWrapper { + return &PodGroupTemplateWrapper{} } -// Obj returns the inner PodGroup. -func (wrapper *PodGroupWrapper) Obj() *schedulingapi.PodGroup { - return &wrapper.PodGroup +// Obj returns the inner PodGroupTemplate. +func (wrapper *PodGroupTemplateWrapper) Obj() schedulingapi.PodGroupTemplate { + return wrapper.PodGroupTemplate } -// Name sets `name` as the name of the inner PodGroup. -func (wrapper *PodGroupWrapper) Name(name string) *PodGroupWrapper { - wrapper.PodGroup.Name = name +// Name sets `name` as the name of the inner PodGroupTemplate. +func (wrapper *PodGroupTemplateWrapper) Name(name string) *PodGroupTemplateWrapper { + wrapper.PodGroupTemplate.Name = name return wrapper } // MinCount sets the MinCount for the Gang scheduling policy. -func (wrapper *PodGroupWrapper) MinCount(count int32) *PodGroupWrapper { - if wrapper.Policy.Gang == nil { - wrapper.Policy.Gang = &schedulingapi.GangSchedulingPolicy{} +func (wrapper *PodGroupTemplateWrapper) MinCount(count int32) *PodGroupTemplateWrapper { + if wrapper.SchedulingPolicy.Gang == nil { + wrapper.SchedulingPolicy.Gang = &schedulingapi.GangSchedulingPolicy{} } - wrapper.Policy.Gang.MinCount = count + wrapper.SchedulingPolicy.Gang.MinCount = count return wrapper } // BasicPolicy sets the PodGroup policy to Basic. -func (wrapper *PodGroupWrapper) BasicPolicy() *PodGroupWrapper { - wrapper.Policy.Basic = &schedulingapi.BasicSchedulingPolicy{} +func (wrapper *PodGroupTemplateWrapper) BasicPolicy() *PodGroupTemplateWrapper { + wrapper.SchedulingPolicy.Basic = &schedulingapi.BasicSchedulingPolicy{} return wrapper } diff --git a/pkg/scheduler/util/assumecache/assume_cache.go b/pkg/scheduler/util/assumecache/assume_cache.go index fae3230bc3afe..0e664babc5254 100644 --- a/pkg/scheduler/util/assumecache/assume_cache.go +++ b/pkg/scheduler/util/assumecache/assume_cache.go @@ -314,7 +314,6 @@ func (c *AssumeCache) delete(obj interface{}) { // An update has both as non-nil. func (c *AssumeCache) pushEvent(oldObj, newObj interface{}) { for _, handler := range c.eventHandlers { - handler := handler if oldObj == nil { c.eventQueue.WriteOne(func() { handler.OnAdd(newObj, false) @@ -463,8 +462,13 @@ func (c *AssumeCache) Assume(obj interface{}) error { c.pushEvent(objInfo.latestObj, obj) // Only update the cached object + oldObj := objInfo.latestObj objInfo.latestObj = obj - c.logger.V(4).Info("Assumed object", "description", c.description, "cacheKey", name, "version", newVersion) + if loggerV := c.logger.V(6); loggerV.Enabled() { + loggerV.Info("Assumed object", "description", c.description, "cacheKey", name, "version", newVersion, "oldObj", klog.Format(oldObj), "newObj", klog.Format(obj)) + } else { + c.logger.V(5).Info("Assumed object", "description", c.description, "cacheKey", name, "version", newVersion) + } return nil } @@ -483,7 +487,7 @@ func (c *AssumeCache) Restore(objName string) { c.pushEvent(objInfo.latestObj, objInfo.apiObj) objInfo.latestObj = objInfo.apiObj } - c.logger.V(4).Info("Restored object", "description", c.description, "cacheKey", objName) + c.logger.V(5).Info("Restored object", "description", c.description, "cacheKey", objName) } } @@ -508,7 +512,11 @@ func (c *AssumeCache) AddEventHandler(handler cache.ResourceEventHandler) cache. if c.handlerRegistration == nil { // No informer, so immediately synced. - return syncedHandlerRegistration{} + s := syncedHandlerRegistration{ + synced: make(chan struct{}), + } + close(s.synced) + return s } return c.handlerRegistration @@ -557,6 +565,14 @@ func (c *AssumeCache) emitEvents() { // syncedHandlerRegistration is an implementation of ResourceEventHandlerRegistration // which always returns true. -type syncedHandlerRegistration struct{} +type syncedHandlerRegistration struct { + synced chan struct{} +} func (syncedHandlerRegistration) HasSynced() bool { return true } + +func (s syncedHandlerRegistration) HasSyncedChecker() cache.DoneChecker { return s } + +func (s syncedHandlerRegistration) Name() string { return "AssumeCache" } + +func (s syncedHandlerRegistration) Done() <-chan struct{} { return s.synced } diff --git a/pkg/scheduler/util/assumecache/assume_cache_test.go b/pkg/scheduler/util/assumecache/assume_cache_test.go index 8024b20987d75..dcaeef0b6a7b3 100644 --- a/pkg/scheduler/util/assumecache/assume_cache_test.go +++ b/pkg/scheduler/util/assumecache/assume_cache_test.go @@ -274,7 +274,7 @@ func testAssumeRace(tCtx ktesting.TContext) { var informer testInformer testObj := makeObj("pvc1", "1", "") ac := NewAssumeCache(tCtx.Logger(), &informer, "TestObject", "", nil) - blockEvent := ktesting.WithCancel(tCtx) + blockEvent := tCtx.WithCancel() defer blockEvent.Cancel("test done") eventBlocked := false eventDone := false @@ -301,9 +301,7 @@ func testAssumeRace(tCtx ktesting.TContext) { assumeDone := false go func() { err := ac.Assume(testObj) - if err != nil { - tCtx.Errorf("Assume failed: %v", err) - } + tCtx.AssertNoError(err, "Assume failed") assumeDone = true }() @@ -337,27 +335,27 @@ func TestRestore(t *testing.T) { newObj := makeObj("pvc1", "5", "") // Restore object that doesn't exist - ktesting.Step(tCtx, "empty cache", func(tCtx ktesting.TContext) { + tCtx.Step("empty cache", func(tCtx ktesting.TContext) { cache.Restore("nothing") events.verifyAndFlush(tCtx, nil) }) // Add old object to cache. - ktesting.Step(tCtx, "initial update", func(tCtx ktesting.TContext) { + tCtx.Step("initial update", func(tCtx ktesting.TContext) { informer.add(oldObj) verify(tCtx, cache, oldObj.GetName(), oldObj, oldObj) events.verifyAndFlush(tCtx, []event{{What: "add", Obj: oldObj}}) }) // Restore the same object. - ktesting.Step(tCtx, "initial Restore", func(tCtx ktesting.TContext) { + tCtx.Step("initial Restore", func(tCtx ktesting.TContext) { cache.Restore(oldObj.GetName()) verify(tCtx, cache, oldObj.GetName(), oldObj, oldObj) events.verifyAndFlush(tCtx, nil) }) // Assume new object. - ktesting.Step(tCtx, "Assume", func(tCtx ktesting.TContext) { + tCtx.Step("Assume", func(tCtx ktesting.TContext) { if err := cache.Assume(newObj); err != nil { tCtx.Fatalf("Assume() returned error %v", err) } @@ -366,7 +364,7 @@ func TestRestore(t *testing.T) { }) // Restore the same object. - ktesting.Step(tCtx, "second Restore", func(tCtx ktesting.TContext) { + tCtx.Step("second Restore", func(tCtx ktesting.TContext) { cache.Restore(oldObj.GetName()) verify(tCtx, cache, oldObj.GetName(), oldObj, oldObj) events.verifyAndFlush(tCtx, []event{{What: "update", OldObj: newObj, Obj: oldObj}}) @@ -382,49 +380,49 @@ func TestEvents(t *testing.T) { // Add old object to cache. informer.add(oldObj) - verify(ktesting.WithStep(tCtx, "after initial update"), cache, key, oldObj, oldObj) + verify(tCtx.WithStep("after initial update"), cache, key, oldObj, oldObj) // Receive initial list. var events mockEventHandler cache.AddEventHandler(&events) - events.verifyAndFlush(ktesting.WithStep(tCtx, "initial list"), []event{{What: "add", Obj: oldObj, InitialList: true}}) + events.verifyAndFlush(tCtx.WithStep("initial list"), []event{{What: "add", Obj: oldObj, InitialList: true}}) // Update object. - ktesting.Step(tCtx, "initial update", func(tCtx ktesting.TContext) { + tCtx.Step("initial update", func(tCtx ktesting.TContext) { informer.update(newObj) verify(tCtx, cache, key, newObj, newObj) events.verifyAndFlush(tCtx, []event{{What: "update", OldObj: oldObj, Obj: newObj}}) }) // Some error cases (don't occur in practice). - ktesting.Step(tCtx, "nop add", func(tCtx ktesting.TContext) { + tCtx.Step("nop add", func(tCtx ktesting.TContext) { informer.add(1) verify(tCtx, cache, key, newObj, newObj) events.verifyAndFlush(tCtx, nil) }) - ktesting.Step(tCtx, "nil add", func(tCtx ktesting.TContext) { + tCtx.Step("nil add", func(tCtx ktesting.TContext) { informer.add(nil) verify(tCtx, cache, key, newObj, newObj) events.verifyAndFlush(tCtx, nil) }) - ktesting.Step(tCtx, "nop update", func(tCtx ktesting.TContext) { + tCtx.Step("nop update", func(tCtx ktesting.TContext) { informer.update(oldObj) events.verifyAndFlush(tCtx, nil) verify(tCtx, cache, key, newObj, newObj) }) - ktesting.Step(tCtx, "nil update", func(tCtx ktesting.TContext) { + tCtx.Step("nil update", func(tCtx ktesting.TContext) { informer.update(nil) verify(tCtx, cache, key, newObj, newObj) events.verifyAndFlush(tCtx, nil) }) - ktesting.Step(tCtx, "nop delete", func(tCtx ktesting.TContext) { + tCtx.Step("nop delete", func(tCtx ktesting.TContext) { informer.delete(nil) verify(tCtx, cache, key, newObj, newObj) events.verifyAndFlush(tCtx, nil) }) // Delete object. - ktesting.Step(tCtx, "delete", func(tCtx ktesting.TContext) { + tCtx.Step("delete", func(tCtx ktesting.TContext) { informer.delete(oldObj) events.verifyAndFlush(tCtx, []event{{What: "delete", Obj: newObj}}) _, err := cache.Get(key) @@ -506,7 +504,7 @@ func TestEventHandlerConcurrency(t *testing.T) { handlers[0].cache = cache // Each add blocks until this gets cancelled. - tCancelCtx := ktesting.WithCancel(tCtx) + tCancelCtx := tCtx.WithCancel() var wg sync.WaitGroup for i := range handlers { @@ -555,7 +553,7 @@ func TestListNoIndexer(t *testing.T) { } // List them - verifyList(ktesting.WithStep(tCtx, "after add"), cache, objs, "") + verifyList(tCtx.WithStep("after add"), cache, objs, "") // Update an object. updatedObj := makeObj("test-pvc3", "2", "") @@ -563,7 +561,7 @@ func TestListNoIndexer(t *testing.T) { informer.update(updatedObj) // List them - verifyList(ktesting.WithStep(tCtx, "after update"), cache, objs, "") + verifyList(tCtx.WithStep("after update"), cache, objs, "") // Delete a PV deletedObj := objs[7] @@ -571,7 +569,7 @@ func TestListNoIndexer(t *testing.T) { informer.delete(deletedObj) // List them - verifyList(ktesting.WithStep(tCtx, "after delete"), cache, objs, "") + verifyList(tCtx.WithStep("after delete"), cache, objs, "") } func TestListWithIndexer(t *testing.T) { @@ -600,7 +598,7 @@ func TestListWithIndexer(t *testing.T) { } // List them - verifyList(ktesting.WithStep(tCtx, "after add"), cache, objs, objs[0]) + verifyList(tCtx.WithStep("after add"), cache, objs, objs[0]) // Update an object. updatedObj := makeObj("test-pvc3", "2", ns) @@ -608,7 +606,7 @@ func TestListWithIndexer(t *testing.T) { informer.update(updatedObj) // List them - verifyList(ktesting.WithStep(tCtx, "after update"), cache, objs, objs[0]) + verifyList(tCtx.WithStep("after update"), cache, objs, objs[0]) // Delete a PV deletedObj := objs[7] @@ -616,5 +614,5 @@ func TestListWithIndexer(t *testing.T) { informer.delete(deletedObj) // List them - verifyList(ktesting.WithStep(tCtx, "after delete"), cache, objs, objs[0]) + verifyList(tCtx.WithStep("after delete"), cache, objs, objs[0]) } diff --git a/pkg/scheduler/util/utils.go b/pkg/scheduler/util/utils.go index 06ba395030dbd..8605c60359d34 100644 --- a/pkg/scheduler/util/utils.go +++ b/pkg/scheduler/util/utils.go @@ -25,6 +25,7 @@ import ( v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -97,7 +98,7 @@ func MoreImportantPod(pod1, pod2 *v1.Pod) bool { // Retriable defines the retriable errors during a scheduling cycle. func Retriable(err error) bool { return apierrors.IsInternalError(err) || apierrors.IsServiceUnavailable(err) || - net.IsConnectionRefused(err) + apierrors.IsConflict(err) || net.IsConnectionRefused(err) } // PatchPodStatus calculates the delta bytes change from to , @@ -107,6 +108,10 @@ func PatchPodStatus(ctx context.Context, cs kubernetes.Interface, name string, n return nil } + if oldStatus == nil { + oldStatus = &v1.PodStatus{} + } + oldData, err := json.Marshal(v1.Pod{Status: *oldStatus}) if err != nil { return err @@ -133,6 +138,45 @@ func PatchPodStatus(ctx context.Context, cs kubernetes.Interface, name string, n return retry.OnError(retry.DefaultBackoff, Retriable, patchFn) } +// PatchPodGroupStatus calculates the delta bytes change from to , +// and then submits a request to API server to patch the PodGroup status changes. +func PatchPodGroupStatus(ctx context.Context, cs kubernetes.Interface, name string, + namespace string, oldStatus *schedulingv1alpha2.PodGroupStatus, + newStatus *schedulingv1alpha2.PodGroupStatus) error { + if newStatus == nil { + return nil + } + + if oldStatus == nil { + oldStatus = &schedulingv1alpha2.PodGroupStatus{} + } + + oldData, err := json.Marshal(schedulingv1alpha2.PodGroup{Status: *oldStatus}) + if err != nil { + return err + } + + newData, err := json.Marshal(schedulingv1alpha2.PodGroup{Status: *newStatus}) + if err != nil { + return err + } + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, &schedulingv1alpha2.PodGroup{}) + if err != nil { + return fmt.Errorf("failed to create merge patch for podgroup %q/%q: %w", namespace, name, err) + } + + if string(patchBytes) == "{}" { + return nil + } + + patchFn := func() error { + _, err := cs.SchedulingV1alpha2().PodGroups(namespace).Patch(ctx, name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status") + return err + } + + return retry.OnError(retry.DefaultBackoff, Retriable, patchFn) +} + // DeletePod deletes the given from API server func DeletePod(ctx context.Context, cs kubernetes.Interface, pod *v1.Pod) error { return cs.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) @@ -213,3 +257,15 @@ func GetHostPorts(pod *v1.Pod) []v1.ContainerPort { } return ports } + +// PodGroupPriority returns priority of a given pod group. +func PodGroupPriority(pg *schedulingv1alpha2.PodGroup) int32 { + if pg.Spec.Priority != nil { + return *pg.Spec.Priority + } + // When priority of a pod group is nil, it means it was created at a time + // that there was no global default priority class and the priority class + // name of the pod group was empty (or when the WorkloadAwarePreemption + // feature gate was disabled). So, we resolve to the static default priority. + return 0 +} diff --git a/pkg/scheduler/util/utils_test.go b/pkg/scheduler/util/utils_test.go index 59c0f3d1a19d4..04bffd66242c4 100644 --- a/pkg/scheduler/util/utils_test.go +++ b/pkg/scheduler/util/utils_test.go @@ -27,14 +27,18 @@ import ( "github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/net" clientsetfake "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" extenderv1 "k8s.io/kube-scheduler/extender/v1" + schedulingapi "k8s.io/kubernetes/pkg/apis/scheduling" st "k8s.io/kubernetes/pkg/scheduler/testing" ) @@ -170,6 +174,7 @@ func TestPatchPodStatus(t *testing.T) { // (true means error is expected one.) validateErr func(goterr error) bool statusToUpdate v1.PodStatus + nilOldStatus bool }{ { name: "Should update pod conditions successfully", @@ -296,20 +301,44 @@ func TestPatchPodStatus(t *testing.T) { }, }, }, + { + name: "nil oldStatus patches successfully", + client: clientsetfake.NewClientset(), + pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pod1", + }, + }, + statusToUpdate: v1.PodStatus{ + Conditions: []v1.PodCondition{ + { + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + }, + }, + }, + nilOldStatus: true, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + client := tc.client _, err := client.CoreV1().Pods(tc.pod.Namespace).Create(context.TODO(), &tc.pod, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } - _, ctx := ktesting.NewTestContext(t) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - err = PatchPodStatus(ctx, client, tc.pod.Name, tc.pod.Namespace, &tc.pod.Status, &tc.statusToUpdate) + oldStatus := &tc.pod.Status + if tc.nilOldStatus { + oldStatus = nil + } + err = PatchPodStatus(ctx, client, tc.pod.Name, tc.pod.Namespace, oldStatus, &tc.statusToUpdate) if err != nil && tc.validateErr == nil { // shouldn't be error t.Fatal(err) @@ -333,6 +362,262 @@ func TestPatchPodStatus(t *testing.T) { } } +func TestPatchPodGroupStatus(t *testing.T) { + now := metav1.NewTime(time.Now().Truncate(time.Second)) + + tests := []struct { + name string + podGroup schedulingv1alpha2.PodGroup + client *clientsetfake.Clientset + // validateErr checks if error returned from PatchPodGroupStatus is expected one or not. + // (true means error is expected one.) + validateErr func(goterr error) bool + statusToUpdate *schedulingv1alpha2.PodGroupStatus + nilOldStatus bool + }{ + { + name: "Should update podgroup conditions successfully", + client: clientsetfake.NewClientset(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + }, + statusToUpdate: &schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity for the gang", + LastTransitionTime: now, + }, + }, + }, + }, + { + name: "no-op when status is unchanged", + client: clientsetfake.NewClientset(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + Status: schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity", + LastTransitionTime: now, + }, + }, + }, + }, + statusToUpdate: &schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity", + LastTransitionTime: now, + }, + }, + }, + }, + { + name: "nil newStatus returns nil", + client: clientsetfake.NewClientset(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + }, + statusToUpdate: nil, + }, + { + name: "retry patch request when a 'connection refused' error is returned", + client: func() *clientsetfake.Clientset { + client := clientsetfake.NewClientset() + + reqcount := 0 + client.PrependReactor("patch", "podgroups", func(action clienttesting.Action) (bool, runtime.Object, error) { + defer func() { reqcount++ }() + if reqcount == 0 { + return true, &schedulingv1alpha2.PodGroup{}, fmt.Errorf("connection refused: %w", syscall.ECONNREFUSED) + } + if reqcount == 1 { + return false, &schedulingv1alpha2.PodGroup{}, nil + } + return true, nil, errors.New("requests comes in more than three times.") + }) + + return client + }(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + }, + statusToUpdate: &schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity for the gang", + LastTransitionTime: now, + }, + }, + }, + }, + { + name: "only 4 retries at most", + client: func() *clientsetfake.Clientset { + client := clientsetfake.NewClientset() + + reqcount := 0 + client.PrependReactor("patch", "podgroups", func(action clienttesting.Action) (bool, runtime.Object, error) { + defer func() { reqcount++ }() + if reqcount >= 4 { + return true, nil, errors.New("requests comes in more than four times.") + } + return true, &schedulingv1alpha2.PodGroup{}, fmt.Errorf("connection refused: %w", syscall.ECONNREFUSED) + }) + + return client + }(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + }, + validateErr: net.IsConnectionRefused, + statusToUpdate: &schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity for the gang", + LastTransitionTime: now, + }, + }, + }, + }, + { + name: "retry patch request when a conflict error is returned", + client: func() *clientsetfake.Clientset { + client := clientsetfake.NewClientset() + + reqcount := 0 + client.PrependReactor("patch", "podgroups", func(action clienttesting.Action) (bool, runtime.Object, error) { + defer func() { reqcount++ }() + if reqcount == 0 { + return true, &schedulingv1alpha2.PodGroup{}, + apierrors.NewConflict(schema.GroupResource{ + Resource: "podgroups"}, "pg1", + errors.New("the object has been modified")) + } + if reqcount == 1 { + return false, &schedulingv1alpha2.PodGroup{}, nil + } + return true, nil, errors.New("requests comes in more than three times.") + }) + + return client + }(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + }, + statusToUpdate: &schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity for the gang", + LastTransitionTime: now, + }, + }, + }, + }, + { + name: "nil oldStatus patches successfully", + client: clientsetfake.NewClientset(), + podGroup: schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "pg1", + }, + }, + statusToUpdate: &schedulingv1alpha2.PodGroupStatus{ + Conditions: []metav1.Condition{ + { + Type: schedulingapi.PodGroupScheduled, + Status: metav1.ConditionFalse, + Reason: schedulingapi.PodGroupReasonUnschedulable, + Message: "not enough capacity for the gang", + LastTransitionTime: now, + }, + }, + }, + nilOldStatus: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + client := tc.client + _, err := client.SchedulingV1alpha2().PodGroups(tc.podGroup.Namespace).Create(ctx, &tc.podGroup, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + oldStatus := &tc.podGroup.Status + if tc.nilOldStatus { + oldStatus = nil + } + err = PatchPodGroupStatus(ctx, client, tc.podGroup.Name, tc.podGroup.Namespace, oldStatus, tc.statusToUpdate) + if err != nil && tc.validateErr == nil { + t.Fatal(err) + } + if tc.validateErr != nil { + if !tc.validateErr(err) { + t.Fatalf("Returned unexpected error: %v", err) + } + return + } + + retrievedPG, err := client.SchedulingV1alpha2().PodGroups(tc.podGroup.Namespace).Get(ctx, tc.podGroup.Name, metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + wantStatus := tc.podGroup.Status + if tc.statusToUpdate != nil { + wantStatus = *tc.statusToUpdate + } + if diff := cmp.Diff(wantStatus, retrievedPG.Status); diff != "" { + t.Errorf("unexpected podgroup status (-want,+got):\n%s", diff) + } + }) + } +} + // Test_As tests the As function with Pod. func Test_As_Pod(t *testing.T) { tests := []struct { diff --git a/pkg/security/apparmor/validate_disabled.go b/pkg/security/apparmor/validate_disabled.go index 91a82c2ef82dc..af10414ac19cc 100644 --- a/pkg/security/apparmor/validate_disabled.go +++ b/pkg/security/apparmor/validate_disabled.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/serviceaccount/claims.go b/pkg/serviceaccount/claims.go index 2893599cf6cdb..d0aada59b6a1e 100644 --- a/pkg/serviceaccount/claims.go +++ b/pkg/serviceaccount/claims.go @@ -177,7 +177,7 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims, noderef := private.Kubernetes.Node secref := private.Kubernetes.Secret // Make sure service account still exists (name and UID) - serviceAccount, err := v.getter.GetServiceAccount(namespace, saref.Name) + serviceAccount, err := v.getter.GetServiceAccount(ctx, namespace, saref.Name) if err != nil { klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err) return nil, err @@ -194,7 +194,7 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims, if secref != nil { // Make sure token hasn't been invalidated by deletion of the secret - secret, err := v.getter.GetSecret(namespace, secref.Name) + secret, err := v.getter.GetSecret(ctx, namespace, secref.Name) if err != nil { klog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, secref.Name, namespace, saref.Name, err) return nil, errors.New("service account token has been invalidated") @@ -212,7 +212,7 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims, var podName, podUID string if podref != nil { // Make sure token hasn't been invalidated by deletion of the pod - pod, err := v.getter.GetPod(namespace, podref.Name) + pod, err := v.getter.GetPod(ctx, namespace, podref.Name) if err != nil { klog.V(4).Infof("Could not retrieve bound pod %s/%s for service account %s/%s: %v", namespace, podref.Name, namespace, saref.Name, err) return nil, errors.New("service account token has been invalidated") @@ -244,7 +244,7 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims, return nil, fmt.Errorf("token is bound to a Node object but the %s feature gate is disabled", features.ServiceAccountTokenNodeBindingValidation) } - node, err := v.getter.GetNode(noderef.Name) + node, err := v.getter.GetNode(ctx, noderef.Name) if err != nil { klog.V(4).Infof("Could not retrieve node object %q for service account %s/%s: %v", noderef.Name, namespace, saref.Name, err) return nil, errors.New("service account token has been invalidated") diff --git a/pkg/serviceaccount/claims_test.go b/pkg/serviceaccount/claims_test.go index a39665bc245e3..9d9aa1cf8a136 100644 --- a/pkg/serviceaccount/claims_test.go +++ b/pkg/serviceaccount/claims_test.go @@ -504,25 +504,25 @@ type fakeGetter struct { node *v1.Node } -func (f fakeGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) { +func (f fakeGetter) GetServiceAccount(ctx context.Context, namespace, name string) (*v1.ServiceAccount, error) { if f.serviceAccount == nil { return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "serviceaccounts"}, name) } return f.serviceAccount, nil } -func (f fakeGetter) GetPod(namespace, name string) (*v1.Pod, error) { +func (f fakeGetter) GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) { if f.pod == nil { return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, name) } return f.pod, nil } -func (f fakeGetter) GetSecret(namespace, name string) (*v1.Secret, error) { +func (f fakeGetter) GetSecret(ctx context.Context, namespace, name string) (*v1.Secret, error) { if f.secret == nil { return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "secrets"}, name) } return f.secret, nil } -func (f fakeGetter) GetNode(name string) (*v1.Node, error) { +func (f fakeGetter) GetNode(ctx context.Context, name string) (*v1.Node, error) { if f.node == nil { return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "nodes"}, name) } diff --git a/pkg/serviceaccount/jwt.go b/pkg/serviceaccount/jwt.go index 71ac5c90959e3..6c06968428e20 100644 --- a/pkg/serviceaccount/jwt.go +++ b/pkg/serviceaccount/jwt.go @@ -40,10 +40,10 @@ import ( // ServiceAccountTokenGetter defines functions to retrieve a named service account and secret type ServiceAccountTokenGetter interface { - GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) - GetPod(namespace, name string) (*v1.Pod, error) - GetSecret(namespace, name string) (*v1.Secret, error) - GetNode(name string) (*v1.Node, error) + GetServiceAccount(ctx context.Context, namespace, name string) (*v1.ServiceAccount, error) + GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) + GetSecret(ctx context.Context, namespace, name string) (*v1.Secret, error) + GetNode(ctx context.Context, name string) (*v1.Node, error) } type TokenGenerator interface { diff --git a/pkg/serviceaccount/legacy.go b/pkg/serviceaccount/legacy.go index 7eae7f69d1dea..4489ff626da67 100644 --- a/pkg/serviceaccount/legacy.go +++ b/pkg/serviceaccount/legacy.go @@ -113,7 +113,7 @@ func (v *legacyValidator) Validate(ctx context.Context, tokenData string, public if v.lookup { // Make sure token hasn't been invalidated by deletion of the secret - secret, err := v.getter.GetSecret(namespace, secretName) + secret, err := v.getter.GetSecret(ctx, namespace, secretName) if err != nil { klog.V(4).Infof("Could not retrieve token %s/%s for service account %s/%s: %v", namespace, secretName, namespace, serviceAccountName, err) return nil, errors.New("Token has been invalidated") @@ -128,7 +128,7 @@ func (v *legacyValidator) Validate(ctx context.Context, tokenData string, public } // Make sure service account still exists (name and UID) - serviceAccount, err := v.getter.GetServiceAccount(namespace, serviceAccountName) + serviceAccount, err := v.getter.GetServiceAccount(ctx, namespace, serviceAccountName) if err != nil { klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, serviceAccountName, err) return nil, err diff --git a/pkg/util/coverage/coverage.go b/pkg/util/coverage/coverage.go index f8e3891708923..7d27119596475 100644 --- a/pkg/util/coverage/coverage.go +++ b/pkg/util/coverage/coverage.go @@ -1,5 +1,4 @@ //go:build coverage -// +build coverage /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/util/coverage/coverage_disabled.go b/pkg/util/coverage/coverage_disabled.go index f1c89a4d544ac..d95ea5867816e 100644 --- a/pkg/util/coverage/coverage_disabled.go +++ b/pkg/util/coverage/coverage_disabled.go @@ -1,5 +1,4 @@ //go:build !coverage -// +build !coverage /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/util/filesystem/util_unix.go b/pkg/util/filesystem/util_unix.go index d27d2640f33e2..351f9ffff9a9c 100644 --- a/pkg/util/filesystem/util_unix.go +++ b/pkg/util/filesystem/util_unix.go @@ -1,5 +1,4 @@ //go:build freebsd || linux || darwin -// +build freebsd linux darwin /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/util/filesystem/util_windows.go b/pkg/util/filesystem/util_windows.go index 95616d34f6c61..0d0799909a42c 100644 --- a/pkg/util/filesystem/util_windows.go +++ b/pkg/util/filesystem/util_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/util/filesystem/util_windows_test.go b/pkg/util/filesystem/util_windows_test.go index d3cb238c0c7ac..0bb622a47acfc 100644 --- a/pkg/util/filesystem/util_windows_test.go +++ b/pkg/util/filesystem/util_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/util/filesystem/watcher.go b/pkg/util/filesystem/watcher.go index cbbc83985de0a..7103e38ecb2ac 100644 --- a/pkg/util/filesystem/watcher.go +++ b/pkg/util/filesystem/watcher.go @@ -18,8 +18,6 @@ package filesystem import ( "context" - "fmt" - "time" "github.com/fsnotify/fsnotify" ) @@ -32,7 +30,8 @@ type FSWatcher interface { // Starts listening for events and errors. // When an event or error occurs, the corresponding handler is called. - Run() + // The watcher stops and releases resources when ctx is canceled. + Run(ctx context.Context) // Add a filesystem path to watch AddWatch(path string) error @@ -74,11 +73,13 @@ func (w *fsnotifyWatcher) Init(eventHandler FSEventHandler, errorHandler FSError return nil } -func (w *fsnotifyWatcher) Run() { +func (w *fsnotifyWatcher) Run(ctx context.Context) { go func() { defer w.watcher.Close() for { select { + case <-ctx.Done(): + return case event := <-w.watcher.Events: if w.eventHandler != nil { w.eventHandler(event) @@ -91,126 +92,3 @@ func (w *fsnotifyWatcher) Run() { } }() } - -type watchAddRemover interface { - Add(path string) error - Remove(path string) error -} -type noopWatcher struct{} - -func (noopWatcher) Add(path string) error { return nil } -func (noopWatcher) Remove(path string) error { return nil } - -// WatchUntil watches the specified path for changes and blocks until ctx is canceled. -// eventHandler() must be non-nil, and pollInterval must be greater than 0. -// eventHandler() is invoked whenever a change event is observed or pollInterval elapses. -// errorHandler() is invoked (if non-nil) whenever an error occurs initializing or watching the specified path. -// -// If path is a directory, only the directory and immediate children are watched. -// -// If path does not exist or cannot be watched, an error is passed to errorHandler() and eventHandler() is called at pollInterval. -// -// Multiple observed events may collapse to a single invocation of eventHandler(). -// -// eventHandler() is invoked immediately after successful initialization of the filesystem watch, -// in case the path changed concurrent with calling WatchUntil(). -func WatchUntil(ctx context.Context, pollInterval time.Duration, path string, eventHandler func(), errorHandler func(err error)) { - if pollInterval <= 0 { - panic(fmt.Errorf("pollInterval must be > 0")) - } - if eventHandler == nil { - panic(fmt.Errorf("eventHandler must be non-nil")) - } - if errorHandler == nil { - errorHandler = func(err error) {} - } - - // Initialize watcher, fall back to no-op - var ( - eventsCh chan fsnotify.Event - errorCh chan error - watcher watchAddRemover - ) - if w, err := fsnotify.NewWatcher(); err != nil { - errorHandler(fmt.Errorf("error creating file watcher, falling back to poll at interval %s: %w", pollInterval, err)) - watcher = noopWatcher{} - } else { - watcher = w - eventsCh = w.Events - errorCh = w.Errors - defer func() { - _ = w.Close() - }() - } - - // Initialize background poll - t := time.NewTicker(pollInterval) - defer t.Stop() - - attemptPeriodicRewatch := false - - // Start watching the path - if err := watcher.Add(path); err != nil { - errorHandler(err) - attemptPeriodicRewatch = true - } else { - // Invoke handle() at least once after successfully registering the listener, - // in case the file changed concurrent with calling WatchUntil. - eventHandler() - } - - for { - select { - case <-ctx.Done(): - return - - case <-t.C: - // Prioritize exiting if context is canceled - if ctx.Err() != nil { - return - } - - // Try to re-establish the watcher if we previously got a watch error - if attemptPeriodicRewatch { - _ = watcher.Remove(path) - if err := watcher.Add(path); err != nil { - errorHandler(err) - } else { - attemptPeriodicRewatch = false - } - } - - // Handle - eventHandler() - - case e := <-eventsCh: - // Prioritize exiting if context is canceled - if ctx.Err() != nil { - return - } - - // Try to re-establish the watcher for events which dropped the existing watch - if e.Name == path && (e.Has(fsnotify.Remove) || e.Has(fsnotify.Rename)) { - _ = watcher.Remove(path) - if err := watcher.Add(path); err != nil { - errorHandler(err) - attemptPeriodicRewatch = true - } - } - - // Handle - eventHandler() - - case err := <-errorCh: - // Prioritize exiting if context is canceled - if ctx.Err() != nil { - return - } - - // If the error occurs in response to calling watcher.Add, re-adding here could hot-loop. - // The periodic poll will attempt to re-establish the watch. - errorHandler(err) - attemptPeriodicRewatch = true - } - } -} diff --git a/pkg/util/flock/flock_other.go b/pkg/util/flock/flock_other.go index 33c7a0799ce83..1def472197a3b 100644 --- a/pkg/util/flock/flock_other.go +++ b/pkg/util/flock/flock_other.go @@ -1,5 +1,4 @@ //go:build !linux && !darwin && !freebsd && !openbsd && !netbsd && !dragonfly -// +build !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/util/flock/flock_unix.go b/pkg/util/flock/flock_unix.go index d44c1a8d56958..747b9e766cec9 100644 --- a/pkg/util/flock/flock_unix.go +++ b/pkg/util/flock/flock_unix.go @@ -1,5 +1,4 @@ //go:build linux || darwin || freebsd || openbsd || netbsd || dragonfly -// +build linux darwin freebsd openbsd netbsd dragonfly /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/util/hash/hash.go b/pkg/util/hash/hash.go index 0962e5cfb5b33..0971a8bb094f5 100644 --- a/pkg/util/hash/hash.go +++ b/pkg/util/hash/hash.go @@ -20,7 +20,7 @@ import ( "fmt" "hash" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) // DeepHashObject writes specified object to hash using the spew library diff --git a/pkg/util/hash/hash_test.go b/pkg/util/hash/hash_test.go index 695775c5a0e3a..323aecd5b5ecd 100644 --- a/pkg/util/hash/hash_test.go +++ b/pkg/util/hash/hash_test.go @@ -21,7 +21,7 @@ import ( "hash/adler32" "testing" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) type A struct { diff --git a/pkg/util/iptables/iptables.go b/pkg/util/iptables/iptables.go index 8476c1ca78d1b..52b81f1ec82ae 100644 --- a/pkg/util/iptables/iptables.go +++ b/pkg/util/iptables/iptables.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/util/iptables/iptables_linux.go b/pkg/util/iptables/iptables_linux.go index e307917307563..b038791ff6254 100644 --- a/pkg/util/iptables/iptables_linux.go +++ b/pkg/util/iptables/iptables_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/util/iptables/iptables_test.go b/pkg/util/iptables/iptables_test.go index e98abff487aff..0e89fd4fd1959 100644 --- a/pkg/util/iptables/iptables_test.go +++ b/pkg/util/iptables/iptables_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/util/iptables/monitor_test.go b/pkg/util/iptables/monitor_test.go index f0aa9187eb148..52fc1e5fea5e1 100644 --- a/pkg/util/iptables/monitor_test.go +++ b/pkg/util/iptables/monitor_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2019 The Kubernetes Authors. diff --git a/pkg/util/iptables/save_restore.go b/pkg/util/iptables/save_restore.go index d507adde73789..d56deed2cc0ef 100644 --- a/pkg/util/iptables/save_restore.go +++ b/pkg/util/iptables/save_restore.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/util/iptables/save_restore_test.go b/pkg/util/iptables/save_restore_test.go index 26da4bb3e1cff..10f6b741e89b5 100644 --- a/pkg/util/iptables/save_restore_test.go +++ b/pkg/util/iptables/save_restore_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/util/iptables/testing/fake.go b/pkg/util/iptables/testing/fake.go index 4658d73263d42..78c0c2070ec21 100644 --- a/pkg/util/iptables/testing/fake.go +++ b/pkg/util/iptables/testing/fake.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/util/iptables/testing/fake_test.go b/pkg/util/iptables/testing/fake_test.go index b2edaa5954927..de6c6a51265f2 100644 --- a/pkg/util/iptables/testing/fake_test.go +++ b/pkg/util/iptables/testing/fake_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/util/iptables/testing/parse.go b/pkg/util/iptables/testing/parse.go index d7957e8c76029..76b17455d541c 100644 --- a/pkg/util/iptables/testing/parse.go +++ b/pkg/util/iptables/testing/parse.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/util/iptables/testing/parse_test.go b/pkg/util/iptables/testing/parse_test.go index 5d320018b4d86..1afcb210975fd 100644 --- a/pkg/util/iptables/testing/parse_test.go +++ b/pkg/util/iptables/testing/parse_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2022 The Kubernetes Authors. diff --git a/pkg/util/kernel/constants.go b/pkg/util/kernel/constants.go index 1467f6c229d9b..b178110ea9103 100644 --- a/pkg/util/kernel/constants.go +++ b/pkg/util/kernel/constants.go @@ -58,3 +58,9 @@ const TCPReceiveMemoryNamespacedKernelVersion = "4.15" // TCPTransmitMemoryNamespacedKernelVersion is the kernel version in which net.ipv4.tcp_wmem was namespaced(netns). // (ref: https://github.com/torvalds/linux/commit/356d1833b638bd465672aefeb71def3ab93fc17d) const TCPTransmitMemoryNamespacedKernelVersion = "4.15" + +// MemoryQoSMinKernelVersion is the minimum kernel version for safe memory.high throttling. +// Kernels older than 5.9 have a livelock bug where processes can get stuck in an infinite +// reclaim loop at the memory.high boundary. +// (ref: https://github.com/torvalds/linux/commit/b3ff92916af3156df27716bb080a407e4caf9085) +const MemoryQoSMinKernelVersion = "5.9" diff --git a/pkg/util/oom/oom_linux.go b/pkg/util/oom/oom_linux.go index 380967f9c00d7..fcfb37cd1b444 100644 --- a/pkg/util/oom/oom_linux.go +++ b/pkg/util/oom/oom_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/util/oom/oom_linux_test.go b/pkg/util/oom/oom_linux_test.go index e724da7e90f52..60b271b13e38a 100644 --- a/pkg/util/oom/oom_linux_test.go +++ b/pkg/util/oom/oom_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/util/oom/oom_unsupported.go b/pkg/util/oom/oom_unsupported.go index 1e4b926e24e70..0e16bd7477e6b 100644 --- a/pkg/util/oom/oom_unsupported.go +++ b/pkg/util/oom/oom_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/util/procfs/procfs_linux.go b/pkg/util/procfs/procfs_linux.go index 041291bce9e31..8c9a98191bb0b 100644 --- a/pkg/util/procfs/procfs_linux.go +++ b/pkg/util/procfs/procfs_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/util/procfs/procfs_linux_test.go b/pkg/util/procfs/procfs_linux_test.go index b021afd9c2d7b..759e5fd2fa3e9 100644 --- a/pkg/util/procfs/procfs_linux_test.go +++ b/pkg/util/procfs/procfs_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/util/procfs/procfs_unsupported.go b/pkg/util/procfs/procfs_unsupported.go index 7ff7a49fa0b00..f8e3d36d7a761 100644 --- a/pkg/util/procfs/procfs_unsupported.go +++ b/pkg/util/procfs/procfs_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/util/rlimit/rlimit_linux.go b/pkg/util/rlimit/rlimit_linux.go index 3a42a14ff9081..158379bc88416 100644 --- a/pkg/util/rlimit/rlimit_linux.go +++ b/pkg/util/rlimit/rlimit_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/util/rlimit/rlimit_unsupported.go b/pkg/util/rlimit/rlimit_unsupported.go index 96eb9904278cc..6025ab3fb312d 100644 --- a/pkg/util/rlimit/rlimit_unsupported.go +++ b/pkg/util/rlimit/rlimit_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/util/slice/slice.go b/pkg/util/slice/slice.go index 872fbdcad6504..df90a91acc833 100644 --- a/pkg/util/slice/slice.go +++ b/pkg/util/slice/slice.go @@ -18,37 +18,38 @@ limitations under the License. package slice import ( - "sort" + "slices" ) // CopyStrings copies the contents of the specified string slice // into a new slice. +// +// Deprecated: Use slices.Clone instead. +// +//go:fix inline func CopyStrings(s []string) []string { - if s == nil { - return nil - } - c := make([]string, len(s)) - copy(c, s) - return c + return slices.Clone(s) } // SortStrings sorts the specified string slice in place. It returns the same // slice that was provided in order to facilitate method chaining. +// +// Deprecated: Use slices.Sort instead. func SortStrings(s []string) []string { - sort.Strings(s) + slices.Sort(s) return s } // ContainsString checks if a given slice of strings contains the provided string. // If a modifier func is provided, it is called with the slice item before the comparation. +// +// Deprecated: Use slices.ContainsFunc or slices.Contains instead. func ContainsString(slice []string, s string, modifier func(s string) string) bool { - for _, item := range slice { - if item == s { - return true - } - if modifier != nil && modifier(item) == s { - return true - } + if slices.Contains(slice, s) { + return true + } + if modifier != nil { + return slices.ContainsFunc(slice, func(item string) bool { return modifier(item) == s }) } return false } diff --git a/pkg/util/tolerations/tolerations_test.go b/pkg/util/tolerations/tolerations_test.go index bbfd5b29f43dc..175803e2114f5 100644 --- a/pkg/util/tolerations/tolerations_test.go +++ b/pkg/util/tolerations/tolerations_test.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "math/rand" + "slices" "strings" "testing" @@ -176,19 +177,11 @@ func TestIsSuperset(t *testing.T) { assert.False(t, isSuperset(tolerations[super], tolerations[sub]), "%s should NOT be a superset of %s", super, sub) } - contains := func(ss []string, s string) bool { - for _, str := range ss { - if str == s { - return true - } - } - return false - } for _, test := range tests { t.Run(test.toleration, func(t *testing.T) { for name := range tolerations { - if name == test.toleration || contains(test.ss, name) { + if name == test.toleration || slices.Contains(test.ss, name) { assertSuperset(t, test.toleration, name) } else { assertNotSuperset(t, test.toleration, name) diff --git a/pkg/volume/csi/csi_client.go b/pkg/volume/csi/csi_client.go index 797319280331d..068a7ccc350ba 100644 --- a/pkg/volume/csi/csi_client.go +++ b/pkg/volume/csi/csi_client.go @@ -703,6 +703,9 @@ func (c *csiDriverClient) nodeGetCapabilities(ctx context.Context) ([]*csipbv1.N req := &csipbv1.NodeGetCapabilitiesRequest{} resp, err := nodeClient.NodeGetCapabilities(ctx, req) if err != nil { + if !isFinalError(err) { + return []*csipbv1.NodeServiceCapability{}, volumetypes.NewUncertainProgressError(err.Error()) + } return []*csipbv1.NodeServiceCapability{}, err } return resp.GetCapabilities(), nil diff --git a/pkg/volume/csi/csi_client_test.go b/pkg/volume/csi/csi_client_test.go index cd86f0505e512..0b753779f1fdd 100644 --- a/pkg/volume/csi/csi_client_test.go +++ b/pkg/volume/csi/csi_client_test.go @@ -28,6 +28,8 @@ import ( csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" api "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -698,6 +700,27 @@ func TestClientNodeUnstageVolume(t *testing.T) { } } +func TestNodeSupportsStageUnstageReturnNewUncertainProgressError(t *testing.T) { + fakeCloser := fake.NewCloser(t) + client := &csiDriverClient{ + driverName: "Fake Driver Name", + nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) { + nodeClient := fake.NewNodeClient(true) + nodeClient.SetGetCapabilitiesErr(status.Errorf(codes.Unavailable, "connection error")) + return nodeClient, fakeCloser, nil + }, + } + + _, err := client.NodeSupportsStageUnstage(context.Background()) + + uncertainProgressError := volumetypes.IsUncertainProgressError(err) + if !uncertainProgressError { + t.Errorf("Expected IsUncertainProgressError to be true, got %v", uncertainProgressError) + } + + fakeCloser.Check() +} + func TestClientNodeSupportsStageUnstage(t *testing.T) { testClientNodeSupportsCapabilities(t, func(client *csiDriverClient) (bool, error) { diff --git a/pkg/volume/csi/csi_mounter.go b/pkg/volume/csi/csi_mounter.go index f8760280dcb0e..f915179e7868f 100644 --- a/pkg/volume/csi/csi_mounter.go +++ b/pkg/volume/csi/csi_mounter.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "slices" authenticationv1 "k8s.io/api/authentication/v1" api "k8s.io/api/core/v1" @@ -314,7 +315,11 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error if csiRPCError != nil { // If operation finished with error then we can remove the mount directory. - if volumetypes.IsOperationFinishedError(csiRPCError) { + // Skip on remount (e.g. CSIDriver.spec.requiresRepublish=true): the volume + // was already published and the pod is observing the existing bind mount, + // so removing the mount dir here would leave the pod with stale contents + // that a subsequent successful republish cannot repair (#121271). + if volumetypes.IsOperationFinishedError(csiRPCError) && !mounterArgs.ReconstructedVolume && !mounterArgs.IsRemount { if removeMountDirErr := removeMountDir(c.plugin, dir); removeMountDirErr != nil { klog.Error(log("mounter.SetupAt failed to remove mount dir after a NodePublish() error [%s]: %v", dir, removeMountDirErr)) } @@ -453,7 +458,7 @@ func (c *csiMountMgr) TearDownAt(dir string) error { // to the spec. // // Kubelet should only be responsible for removal of json data files it - // creates and parent directories. + // creates and their parent directories. // // However, some CSI plugins maybe buggy and don't adhere to the standard, // so we still need to remove the target_path here if it's unmounted and @@ -560,12 +565,7 @@ func (c *csiMountMgr) supportsVolumeLifecycleMode() error { // containsVolumeMode checks whether the given volume mode is listed. func containsVolumeMode(modes []storage.VolumeLifecycleMode, mode storage.VolumeLifecycleMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false + return slices.Contains(modes, mode) } // isDirMounted returns the !notMounted result from IsLikelyNotMountPoint check diff --git a/pkg/volume/csi/csi_mounter_test.go b/pkg/volume/csi/csi_mounter_test.go index 62aecb809f0b1..8af454a1bbccf 100644 --- a/pkg/volume/csi/csi_mounter_test.go +++ b/pkg/volume/csi/csi_mounter_test.go @@ -28,6 +28,8 @@ import ( "time" "github.com/google/go-cmp/cmp" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" authenticationv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" storage "k8s.io/api/storage/v1" @@ -35,6 +37,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" fakeclient "k8s.io/client-go/kubernetes/fake" clitesting "k8s.io/client-go/testing" @@ -216,6 +219,9 @@ func TestMounterSetUp(t *testing.T) { currentPodInfoMount := true for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if !test.enableSELinuxFeatureGate { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.enableSELinuxFeatureGate) modes := []storage.VolumeLifecycleMode{ @@ -1169,6 +1175,113 @@ func TestMounterSetUpWithFSGroup(t *testing.T) { } } +func TestMounterSetUpFWithNodePublishFinalError(t *testing.T) { + testCases := []struct { + name string + podUID types.UID + options []string + spec func(string, []string) *volume.Spec + reconstructedVolume bool + isRemount bool + expectDataFileExists bool + }{ + { + name: "setup with reconstructed volume", + podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())), + spec: func(fsType string, options []string) *volume.Spec { + pvSrc := makeTestPV("pv1", 20, testDriver, "vol1") + pvSrc.Spec.CSI.FSType = fsType + pvSrc.Spec.MountOptions = options + return volume.NewSpecFromPersistentVolume(pvSrc, false) + }, + reconstructedVolume: true, + expectDataFileExists: true, + }, + { + name: "setup with new volume", + podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())), + spec: func(fsType string, options []string) *volume.Spec { + pvSrc := makeTestPV("pv1", 20, testDriver, "vol1") + pvSrc.Spec.CSI.FSType = fsType + pvSrc.Spec.MountOptions = options + return volume.NewSpecFromPersistentVolume(pvSrc, false) + }, + reconstructedVolume: false, + expectDataFileExists: false, + }, + { + // Regression test for https://github.com/kubernetes/kubernetes/issues/121271: + // when NodePublishVolume fails on a remount (e.g. a republish + // triggered by CSIDriver.spec.requiresRepublish=true), the + // mount directory and vol_data.json must be preserved so the + // pod continues to see the previously-published contents and + // a subsequent successful republish can refresh them in place. + name: "setup with remount preserves mount dir on final error", + podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())), + spec: func(fsType string, options []string) *volume.Spec { + pvSrc := makeTestPV("pv1", 20, testDriver, "vol1") + pvSrc.Spec.CSI.FSType = fsType + pvSrc.Spec.MountOptions = options + return volume.NewSpecFromPersistentVolume(pvSrc, false) + }, + reconstructedVolume: false, + isRemount: true, + expectDataFileExists: true, + }, + } + + for _, tc := range testCases { + volumeLifecycleModes := []storage.VolumeLifecycleMode{ + storage.VolumeLifecyclePersistent, + } + driver := getTestCSIDriver(testDriver, nil, nil, volumeLifecycleModes) + fakeClient := fakeclient.NewClientset(driver) + plug, tmpDir := newTestPlugin(t, fakeClient) + defer func() { + _ = os.RemoveAll(tmpDir) + }() + registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t) + t.Run(tc.name, func(t *testing.T) { + mounter, err := plug.NewMounter( + tc.spec("zfs", tc.options), + &corev1.Pod{ObjectMeta: meta.ObjectMeta{UID: tc.podUID, Namespace: testns}}, + ) + if mounter == nil || err != nil { + t.Fatal("failed to create CSI mounter") + } + + csiMounter := mounter.(*csiMountMgr) + csiMounter.csiClient = setupClient(t, true) + + attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName())) + attachment := makeTestAttachment(attachID, "test-node", csiMounter.spec.Name()) + _, err = csiMounter.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, meta.CreateOptions{}) + if err != nil { + t.Fatalf("failed to setup VolumeAttachment: %v", err) + } + + csiMounter.csiClient.(*fakeCsiDriverClient).nodeClient.SetNextError(status.Errorf(codes.InvalidArgument, "mount failed")) + + // Mounter.SetUp() + if err := csiMounter.SetUp(volume.MounterArgs{ + ReconstructedVolume: tc.reconstructedVolume, + IsRemount: tc.isRemount, + }); err == nil { + t.Fatalf("mounter.Setup expected err but succeed") + } + + mountPath := csiMounter.GetPath() + volPath := filepath.Dir(mountPath) + dataFile := filepath.Join(volPath, volDataFileName) + _, statErr := os.Stat(dataFile) + exists := statErr == nil + if exists != tc.expectDataFileExists { + t.Errorf("volume file [%s]: exists=%v, want=%v (statErr=%v)", dataFile, exists, tc.expectDataFileExists, statErr) + } + }) + } +} + func TestUnmounterTeardown(t *testing.T) { plug, tmpDir := newTestPlugin(t, nil) defer os.RemoveAll(tmpDir) diff --git a/pkg/volume/csi/csi_plugin.go b/pkg/volume/csi/csi_plugin.go index 3d2699a0d3a44..b77f1c185694a 100644 --- a/pkg/volume/csi/csi_plugin.go +++ b/pkg/volume/csi/csi_plugin.go @@ -342,7 +342,7 @@ func (p *csiPlugin) Init(host volume.VolumeHost) error { return true }, csitranslationplugins.PortworxVolumePluginName: func() bool { - return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) + return true }, } diff --git a/pkg/volume/csi/csi_plugin_test.go b/pkg/volume/csi/csi_plugin_test.go index 5fb0cc177620a..16fb2505d02eb 100644 --- a/pkg/volume/csi/csi_plugin_test.go +++ b/pkg/volume/csi/csi_plugin_test.go @@ -371,6 +371,9 @@ func TestPluginConstructVolumeSpec(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + if !tc.seLinuxMountEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, utilversion.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, tc.seLinuxMountEnabled) mounter, err := plug.NewMounter( diff --git a/pkg/volume/csi/fake/fake_client.go b/pkg/volume/csi/fake/fake_client.go index 28a284ff1ef7c..ba87e8b964119 100644 --- a/pkg/volume/csi/fake/fake_client.go +++ b/pkg/volume/csi/fake/fake_client.go @@ -89,6 +89,7 @@ type NodeClient struct { nodeVolumeStatsResp *csipb.NodeGetVolumeStatsResponse FakeNodeExpansionRequest *csipb.NodeExpandVolumeRequest nextErr error + getCapabilitiesErr error } // NewNodeClient returns fake node client @@ -150,6 +151,10 @@ func (f *NodeClient) SetNextError(err error) { f.nextErr = err } +func (f *NodeClient) SetGetCapabilitiesErr(err error) { + f.getCapabilitiesErr = err +} + func (f *NodeClient) SetNodeGetInfoResp(resp *csipb.NodeGetInfoResponse) { f.nodeGetInfoResp = resp } @@ -354,6 +359,10 @@ func (f *NodeClient) NodeGetCapabilities(ctx context.Context, in *csipb.NodeGetC resp := &csipb.NodeGetCapabilitiesResponse{ Capabilities: []*csipb.NodeServiceCapability{}, } + if f.getCapabilitiesErr != nil { + return resp, f.getCapabilitiesErr + } + if f.stageUnstageSet { resp.Capabilities = append(resp.Capabilities, &csipb.NodeServiceCapability{ Type: &csipb.NodeServiceCapability_Rpc{ diff --git a/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go b/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go index ba1bde76a2dda..839c28ed328e1 100644 --- a/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go +++ b/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go @@ -41,7 +41,6 @@ import ( nodeutil "k8s.io/component-helpers/node/util" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/pkg/volume/util" ) const ( @@ -116,7 +115,6 @@ func (nim *nodeInfoManager) InstallCSIDriver(driverName string, driverNodeID str } nodeUpdateFuncs := []nodeUpdateFunc{ - removeMaxAttachLimit(driverName), // remove in 1.35 due to the version skew policy, we have to keep it for 3 releases updateNodeIDInNode(driverName, driverNodeID), updateTopologyLabels(topology), } @@ -154,7 +152,6 @@ func (nim *nodeInfoManager) UninstallCSIDriver(driverName string) error { } err = nim.updateNode( - removeMaxAttachLimit(driverName), // remove it when this function is removed from nodeUpdateFuncs removeNodeIDFromNode(driverName), ) if err != nil { @@ -713,35 +710,3 @@ func (nim *nodeInfoManager) tryUninstallDriverFromCSINode( return err // do not wrap error } - -func removeMaxAttachLimit(driverName string) nodeUpdateFunc { - return func(node *v1.Node) (*v1.Node, bool, error) { - limitKey := v1.ResourceName(util.GetCSIAttachLimitKey(driverName)) - - capacityExists := false - if node.Status.Capacity != nil { - _, capacityExists = node.Status.Capacity[limitKey] - } - - allocatableExists := false - if node.Status.Allocatable != nil { - _, allocatableExists = node.Status.Allocatable[limitKey] - } - - if !capacityExists && !allocatableExists { - return node, false, nil - } - - delete(node.Status.Capacity, limitKey) - if len(node.Status.Capacity) == 0 { - node.Status.Capacity = nil - } - - delete(node.Status.Allocatable, limitKey) - if len(node.Status.Allocatable) == 0 { - node.Status.Allocatable = nil - } - - return node, true, nil - } -} diff --git a/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go b/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go index 8e909d7e29cb1..df620177ac824 100644 --- a/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go +++ b/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go @@ -40,7 +40,6 @@ import ( utiltesting "k8s.io/client-go/util/testing" "k8s.io/kubernetes/pkg/apis/core/helper" volumetest "k8s.io/kubernetes/pkg/volume/testing" - "k8s.io/kubernetes/pkg/volume/util" "k8s.io/utils/ptr" ) @@ -812,39 +811,6 @@ func TestUninstallCSIDriver(t *testing.T) { Spec: storage.CSINodeSpec{}, }, }, - { - name: "new node with valid max limit", - driverName: "com.example.csi.driver1", - existingNode: generateNode( - nil, /*nodeIDs*/ - nil, /*labels*/ - map[v1.ResourceName]resource.Quantity{ - v1.ResourceCPU: *resource.NewScaledQuantity(4, -3), - v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI), - }, - ), - expectedNode: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - v1.ResourceCPU: *resource.NewScaledQuantity(4, -3), - v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI), - }, - Allocatable: v1.ResourceList{ - v1.ResourceCPU: *resource.NewScaledQuantity(4, -3), - v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI), - }, - }, - }, - expectedCSINode: &storage.CSINode{ - ObjectMeta: getCSINodeObjectMeta(), - Spec: storage.CSINodeSpec{}, - }, - inputTopology: nil, - inputNodeID: "com.example.csi/csi-node1", - }, } test(t, false /* addNodeInfo */, testcases) diff --git a/pkg/volume/csimigration/plugin_manager.go b/pkg/volume/csimigration/plugin_manager.go index b69352c2a23a4..93718216123e6 100644 --- a/pkg/volume/csimigration/plugin_manager.go +++ b/pkg/volume/csimigration/plugin_manager.go @@ -21,10 +21,8 @@ import ( "fmt" v1 "k8s.io/api/core/v1" - "k8s.io/component-base/featuregate" csilibplugins "k8s.io/csi-translation-lib/plugins" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" ) @@ -38,14 +36,12 @@ type PluginNameMapper interface { // PluginManager keeps track of migrated state of in-tree plugins type PluginManager struct { PluginNameMapper - featureGate featuregate.FeatureGate } // NewPluginManager returns a new PluginManager instance -func NewPluginManager(m PluginNameMapper, featureGate featuregate.FeatureGate) PluginManager { +func NewPluginManager(m PluginNameMapper) PluginManager { return PluginManager{ PluginNameMapper: m, - featureGate: featureGate, } } @@ -74,7 +70,7 @@ func (pm PluginManager) IsMigrationCompleteForPlugin(pluginName string) bool { case csilibplugins.VSphereInTreePluginName: return true case csilibplugins.PortworxVolumePluginName: - return pm.featureGate.Enabled(features.InTreePluginPortworxUnregister) + return true default: return false } @@ -100,7 +96,7 @@ func (pm PluginManager) IsMigrationEnabledForPlugin(pluginName string) bool { case csilibplugins.VSphereInTreePluginName: return true case csilibplugins.PortworxVolumePluginName: - return pm.featureGate.Enabled(features.CSIMigrationPortworx) + return true default: return false } @@ -152,15 +148,3 @@ func TranslateInTreeSpecToCSI(logger klog.Logger, spec *volume.Spec, podNamespac InlineVolumeSpecForCSIMigration: inlineVolume, }, nil } - -// CheckMigrationFeatureFlags checks the configuration of feature flags related -// to CSI Migration is valid. It will return whether the migration is complete -// by looking up the pluginUnregister flag -func CheckMigrationFeatureFlags(f featuregate.FeatureGate, pluginMigration, - pluginUnregister featuregate.Feature) (migrationComplete bool, err error) { - // This is for in-tree plugin that get migration finished - if f.Enabled(pluginMigration) && f.Enabled(pluginUnregister) { - return true, nil - } - return false, nil -} diff --git a/pkg/volume/csimigration/plugin_manager_test.go b/pkg/volume/csimigration/plugin_manager_test.go deleted file mode 100644 index ee7ca9eaf05e7..0000000000000 --- a/pkg/volume/csimigration/plugin_manager_test.go +++ /dev/null @@ -1,170 +0,0 @@ -/* -Copyright 2019 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 csimigration - -import ( - "fmt" - "testing" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/version" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/component-base/featuregate" - featuregatetesting "k8s.io/component-base/featuregate/testing" - csitrans "k8s.io/csi-translation-lib" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/volume" -) - -func TestIsMigratable(t *testing.T) { - testCases := []struct { - name string - pluginFeature featuregate.Feature - pluginFeatureEnabled bool - csiMigrationEnabled bool - isMigratable bool - spec *volume.Spec - }{ - { - name: "Portworx PV source with CSIMigrationPortworx enabled", - pluginFeature: features.CSIMigrationPortworx, - pluginFeatureEnabled: true, - isMigratable: true, - csiMigrationEnabled: true, - spec: &volume.Spec{ - PersistentVolume: &v1.PersistentVolume{ - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeSource: v1.PersistentVolumeSource{ - PortworxVolume: &v1.PortworxVolumeSource{ - VolumeID: "test-volume", - }, - }, - }, - }, - }, - }, - { - name: "Portworx PD PV Source with CSIMigrationPortworx disabled", - pluginFeature: features.CSIMigrationPortworx, - pluginFeatureEnabled: false, - isMigratable: false, - csiMigrationEnabled: true, - spec: &volume.Spec{ - PersistentVolume: &v1.PersistentVolume{ - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeSource: v1.PersistentVolumeSource{ - PortworxVolume: &v1.PortworxVolumeSource{ - VolumeID: "test-volume", - }, - }, - }, - }, - }, - }, - } - csiTranslator := csitrans.New() - for _, test := range testCases { - pm := NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) - t.Run(fmt.Sprintf("Testing %v", test.name), func(t *testing.T) { - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, test.pluginFeature, test.pluginFeatureEnabled) - migratable, err := pm.IsMigratable(test.spec) - if migratable != test.isMigratable { - t.Errorf("Expected migratability of spec: %v does not match obtained migratability: %v", test.isMigratable, migratable) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - }) - } -} - -func TestMigrationFeatureFlagStatus(t *testing.T) { - testCases := []struct { - name string - pluginName string - pluginFeature featuregate.Feature - pluginFeatureEnabled bool - inTreePluginUnregister featuregate.Feature - inTreePluginUnregisterEnabled bool - csiMigrationResult bool - csiMigrationCompleteResult bool - }{ - { - name: "portworx-volume migration flag disabled and migration-complete flag disabled with CSI migration flag", - pluginName: "kubernetes.io/portworx-volume", - pluginFeature: features.CSIMigrationPortworx, - pluginFeatureEnabled: false, - inTreePluginUnregister: features.InTreePluginPortworxUnregister, - inTreePluginUnregisterEnabled: false, - csiMigrationResult: false, - csiMigrationCompleteResult: false, - }, - { - name: "portworx-volume migration flag disabled and migration-complete flag enabled with CSI migration flag", - pluginName: "kubernetes.io/portworx-volume", - pluginFeature: features.CSIMigrationPortworx, - pluginFeatureEnabled: false, - inTreePluginUnregister: features.InTreePluginPortworxUnregister, - inTreePluginUnregisterEnabled: true, - csiMigrationResult: false, - csiMigrationCompleteResult: false, - }, - { - name: "portworx-volume migration flag enabled and migration-complete flag disabled with CSI migration flag", - pluginName: "kubernetes.io/portworx-volume", - pluginFeature: features.CSIMigrationPortworx, - pluginFeatureEnabled: true, - inTreePluginUnregister: features.InTreePluginPortworxUnregister, - inTreePluginUnregisterEnabled: false, - csiMigrationResult: true, - csiMigrationCompleteResult: false, - }, - { - name: "portworx-volume migration flag enabled and migration-complete flag enabled with CSI migration flag", - pluginName: "kubernetes.io/portworx-volume", - pluginFeature: features.CSIMigrationPortworx, - pluginFeatureEnabled: true, - inTreePluginUnregister: features.InTreePluginPortworxUnregister, - inTreePluginUnregisterEnabled: true, - csiMigrationResult: true, - csiMigrationCompleteResult: true, - }, - } - csiTranslator := csitrans.New() - for _, test := range testCases { - pm := NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) - t.Run(fmt.Sprintf("Testing %v", test.name), func(t *testing.T) { - // TODO: this will be removed in 1.36 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.32")) - if len(test.pluginFeature) > 0 { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, test.pluginFeature, test.pluginFeatureEnabled) - } - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, test.inTreePluginUnregister, test.inTreePluginUnregisterEnabled) - - csiMigrationResult := pm.IsMigrationEnabledForPlugin(test.pluginName) - if csiMigrationResult != test.csiMigrationResult { - t.Errorf("Expected migratability of plugin %v: %v does not match obtained migratability: %v", test.pluginName, test.csiMigrationResult, csiMigrationResult) - } - csiMigrationCompleteResult := pm.IsMigrationCompleteForPlugin(test.pluginName) - if csiMigrationCompleteResult != test.csiMigrationCompleteResult { - t.Errorf("Expected migration complete status of plugin: %v: %v does not match obtained migratability: %v", test.pluginName, test.csiMigrationCompleteResult, csiMigrationResult) - } - }) - } -} diff --git a/pkg/volume/emptydir/empty_dir.go b/pkg/volume/emptydir/empty_dir.go index d5c29b182fed5..622704495d724 100644 --- a/pkg/volume/emptydir/empty_dir.go +++ b/pkg/volume/emptydir/empty_dir.go @@ -35,6 +35,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" resourcehelper "k8s.io/component-helpers/resource" v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/pkg/kubelet/cm" usernamespacefeature "k8s.io/kubernetes/pkg/kubelet/userns" "k8s.io/kubernetes/pkg/volume" @@ -119,7 +120,7 @@ func calculateEmptyDirMemorySize(nodeAllocatableMemory *resource.Quantity, spec // determine pod resource allocation // we use the same function for pod cgroup assignment to maintain consistent behavior // NOTE: this could be nil on systems that do not support pod memory containment (i.e. windows) - podResourceConfig := cm.ResourceConfigForPod(pod, false, uint64(100000), false) + podResourceConfig := cm.ResourceConfigForPod(pod, false, uint64(100000), false, config.NoneMemoryReservationPolicy) if podResourceConfig != nil && podResourceConfig.Memory != nil { podMemoryLimit := resource.NewQuantity(*(podResourceConfig.Memory), resource.BinarySI) // ensure 0 < value < size diff --git a/pkg/volume/emptydir/empty_dir_linux.go b/pkg/volume/emptydir/empty_dir_linux.go index 381ad4bf6665f..179b117e044c6 100644 --- a/pkg/volume/emptydir/empty_dir_linux.go +++ b/pkg/volume/emptydir/empty_dir_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2015 The Kubernetes Authors. @@ -31,12 +30,6 @@ import ( "k8s.io/api/core/v1" ) -// Defined by Linux - the type number for tmpfs mounts. -const ( - linuxTmpfsMagic = 0x01021994 - linuxHugetlbfsMagic = 0x958458f6 -) - // realMountDetector implements mountDetector in terms of syscalls. type realMountDetector struct { mounter mount.Interface @@ -98,9 +91,9 @@ func (m *realMountDetector) GetMountMedium(path string, requestedMedium v1.Stora klog.V(3).Infof("Statfs_t of %v: %+v", path, buf) - if buf.Type == linuxTmpfsMagic { + if buf.Type == unix.TMPFS_MAGIC { return v1.StorageMediumMemory, !notMnt, nil, nil - } else if int64(buf.Type) == linuxHugetlbfsMagic { + } else if int64(buf.Type) == unix.HUGETLBFS_MAGIC { // Skip page size detection if requested medium doesn't have size specified if requestedMedium == v1.StorageMediumHugePages { return v1.StorageMediumHugePages, !notMnt, nil, nil diff --git a/pkg/volume/emptydir/empty_dir_test.go b/pkg/volume/emptydir/empty_dir_test.go index 73826145871d2..86dca71ac55a5 100644 --- a/pkg/volume/emptydir/empty_dir_test.go +++ b/pkg/volume/emptydir/empty_dir_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/emptydir/empty_dir_unsupported.go b/pkg/volume/emptydir/empty_dir_unsupported.go index 106d296d99256..dc710e8265f4f 100644 --- a/pkg/volume/emptydir/empty_dir_unsupported.go +++ b/pkg/volume/emptydir/empty_dir_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/volume/fc/fc_util_linux_test.go b/pkg/volume/fc/fc_util_linux_test.go index eebfce74c2eb9..0aa0d617b6b4f 100644 --- a/pkg/volume/fc/fc_util_linux_test.go +++ b/pkg/volume/fc/fc_util_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. diff --git a/pkg/volume/flexvolume/fake_watcher.go b/pkg/volume/flexvolume/fake_watcher.go index ce834043fbabc..924b6ae5c142a 100644 --- a/pkg/volume/flexvolume/fake_watcher.go +++ b/pkg/volume/flexvolume/fake_watcher.go @@ -17,6 +17,8 @@ limitations under the License. package flexvolume import ( + "context" + "github.com/fsnotify/fsnotify" utilfs "k8s.io/kubernetes/pkg/util/filesystem" ) @@ -40,7 +42,7 @@ func (w *fakeWatcher) Init(eventHandler utilfs.FSEventHandler, _ utilfs.FSErrorH return nil } -func (w *fakeWatcher) Run() { /* no-op */ } +func (w *fakeWatcher) Run(_ context.Context) { /* no-op */ } func (w *fakeWatcher) AddWatch(path string) error { w.watches = append(w.watches, path) diff --git a/pkg/volume/flexvolume/plugin.go b/pkg/volume/flexvolume/plugin.go index f8d3c08cd0f2d..17e352cb53f48 100644 --- a/pkg/volume/flexvolume/plugin.go +++ b/pkg/volume/flexvolume/plugin.go @@ -20,6 +20,7 @@ import ( "fmt" "path/filepath" "runtime" + "slices" "strings" "sync" @@ -297,12 +298,7 @@ func (plugin *flexVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) ( func (plugin *flexVolumePlugin) isUnsupported(command string) bool { plugin.Lock() defer plugin.Unlock() - for _, unsupportedCommand := range plugin.unsupportedCommands { - if command == unsupportedCommand { - return true - } - } - return false + return slices.Contains(plugin.unsupportedCommands, command) } func (plugin *flexVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { diff --git a/pkg/volume/flexvolume/probe.go b/pkg/volume/flexvolume/probe.go index 2c4c5d0f98cfe..e6f875ab2e313 100644 --- a/pkg/volume/flexvolume/probe.go +++ b/pkg/volume/flexvolume/probe.go @@ -17,6 +17,7 @@ limitations under the License. package flexvolume import ( + "context" "fmt" "os" "path/filepath" @@ -39,18 +40,20 @@ type flexVolumeProber struct { watcher utilfs.FSWatcher factory PluginFactory fs utilfs.Filesystem + ctx context.Context probeAllNeeded bool eventsMap map[string]volume.ProbeOperation // the key is the driver directory path, the value is the corresponding operation } // GetDynamicPluginProber creates dynamic plugin prober -func GetDynamicPluginProber(pluginDir string, runner exec.Interface) volume.DynamicPluginProber { +func GetDynamicPluginProber(ctx context.Context, pluginDir string, runner exec.Interface) volume.DynamicPluginProber { return &flexVolumeProber{ pluginDir: pluginDir, watcher: utilfs.NewFsnotifyWatcher(), factory: pluginFactory{}, runner: runner, fs: &utilfs.DefaultFs{}, + ctx: ctx, } } @@ -261,7 +264,7 @@ func (prober *flexVolumeProber) initWatcher() error { return fmt.Errorf("error adding watch on Flexvolume directory: %s", err) } - prober.watcher.Run() + prober.watcher.Run(prober.ctx) return nil } diff --git a/pkg/volume/git_repo/git_repo_test.go b/pkg/volume/git_repo/git_repo_test.go index bba41d32171ed..75e98d02d4cec 100644 --- a/pkg/volume/git_repo/git_repo_test.go +++ b/pkg/volume/git_repo/git_repo_test.go @@ -29,6 +29,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/features" @@ -428,6 +429,11 @@ func TestPlugin(t *testing.T) { for _, sc := range scenarios { t.Run(fmt.Sprintf("%s/gitRepoPluginDisabled:%v", sc.name, sc.gitRepoPluginDisabled), func(t *testing.T) { + + if !sc.gitRepoPluginDisabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.33")) + } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GitRepoVolumeDriver, !sc.gitRepoPluginDisabled) allErrs := doTestPlugin(t, sc) if len(allErrs) == 0 && sc.isExpectedFailure { @@ -542,7 +548,6 @@ func doTestSetUp(sc scenario, mounter volume.Mounter) []error { var fakeOutputs []fakeexec.FakeAction var fcmd fakeexec.FakeCmd for _, expected := range expecteds { - expected := expected if expected.cmd[1] == "clone" { // Calculate the subdirectory clone would create (if any) // git clone -- https://github.com/kubernetes/kubernetes.git target_dir --> target_dir diff --git a/pkg/volume/iscsi/iscsi.go b/pkg/volume/iscsi/iscsi.go index 1d43db1857eed..39daacb390189 100644 --- a/pkg/volume/iscsi/iscsi.go +++ b/pkg/volume/iscsi/iscsi.go @@ -631,7 +631,7 @@ func createSecretMap(spec *volume.Spec, plugin *iscsiPlugin, namespace string) ( } if len(secretName) > 0 && len(secretNamespace) > 0 { - // if secret is provideded, retrieve it + // if secret is provided, retrieve it kubeClient := plugin.host.GetKubeClient() if kubeClient == nil { return nil, fmt.Errorf("cannot get kube client") diff --git a/pkg/volume/iscsi/iscsi_util_test.go b/pkg/volume/iscsi/iscsi_util_test.go index 4d3906b509faf..689f56eaa0223 100644 --- a/pkg/volume/iscsi/iscsi_util_test.go +++ b/pkg/volume/iscsi/iscsi_util_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/volume/local/local_linux_test.go b/pkg/volume/local/local_linux_test.go index 08daebf04e201..d02698bf04114 100644 --- a/pkg/volume/local/local_linux_test.go +++ b/pkg/volume/local/local_linux_test.go @@ -1,5 +1,4 @@ //go:build linux || darwin -// +build linux darwin /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/volume/local/local_test.go b/pkg/volume/local/local_test.go index 7777997f3b696..39d4795ed6f10 100644 --- a/pkg/volume/local/local_test.go +++ b/pkg/volume/local/local_test.go @@ -1,5 +1,4 @@ //go:build linux || darwin || windows -// +build linux darwin windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/volume/metrics_block_linux_test.go b/pkg/volume/metrics_block_linux_test.go index d5e6951142e9a..76062452f0a8d 100644 --- a/pkg/volume/metrics_block_linux_test.go +++ b/pkg/volume/metrics_block_linux_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2021 The Kubernetes Authors. diff --git a/pkg/volume/metrics_du_unix_test.go b/pkg/volume/metrics_du_unix_test.go index ea35b669170f6..a09afe8815e95 100644 --- a/pkg/volume/metrics_du_unix_test.go +++ b/pkg/volume/metrics_du_unix_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/volume/metrics_du_windows_test.go b/pkg/volume/metrics_du_windows_test.go index 42217e2b6e4b0..4418a5e5a4382 100644 --- a/pkg/volume/metrics_du_windows_test.go +++ b/pkg/volume/metrics_du_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2023 The Kubernetes Authors. diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index 24b661493fdaa..e80603a52c7f0 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -390,9 +390,6 @@ type VolumeHost interface { // Get mounter interface. GetMounter() mount.Interface - // Returns the hostname of the host kubelet is running on - GetHostName() string - // Returns node allocatable. GetNodeAllocatable() (v1.ResourceList, error) diff --git a/pkg/volume/portworx/OWNERS b/pkg/volume/portworx/OWNERS deleted file mode 100644 index a38b2de0f8b67..0000000000000 --- a/pkg/volume/portworx/OWNERS +++ /dev/null @@ -1,9 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - saad-ali -reviewers: - - saad-ali -emeritus_approvers: - - lpabon - - rootfs diff --git a/pkg/volume/portworx/doc.go b/pkg/volume/portworx/doc.go deleted file mode 100644 index 9ba9ad256eca5..0000000000000 --- a/pkg/volume/portworx/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -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. -*/ - -// Package portworx contains the internal representation of Portworx -// Block Device volumes. -package portworx diff --git a/pkg/volume/portworx/portworx.go b/pkg/volume/portworx/portworx.go deleted file mode 100644 index f8d4c348c4f56..0000000000000 --- a/pkg/volume/portworx/portworx.go +++ /dev/null @@ -1,464 +0,0 @@ -/* -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. -*/ - -package portworx - -import ( - "fmt" - "net" - "os" - "strconv" - - "k8s.io/klog/v2" - "k8s.io/mount-utils" - "k8s.io/utils/exec" - utilstrings "k8s.io/utils/strings" - - volumeclient "github.com/libopenstorage/openstorage/api/client/volume" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/pkg/volume/util" -) - -const ( - attachContextKey = "context" - attachHostKey = "host" -) - -// ProbeVolumePlugins is the primary entrypoint for volume plugins. -func ProbeVolumePlugins() []volume.VolumePlugin { - return []volume.VolumePlugin{&portworxVolumePlugin{nil, nil}} -} - -type portworxVolumePlugin struct { - host volume.VolumeHost - util *portworxVolumeUtil -} - -var _ volume.VolumePlugin = &portworxVolumePlugin{} -var _ volume.PersistentVolumePlugin = &portworxVolumePlugin{} -var _ volume.DeletableVolumePlugin = &portworxVolumePlugin{} -var _ volume.ProvisionableVolumePlugin = &portworxVolumePlugin{} -var _ volume.ExpandableVolumePlugin = &portworxVolumePlugin{} - -const ( - portworxVolumePluginName = "kubernetes.io/portworx-volume" -) - -func getPath(uid types.UID, volName string, host volume.VolumeHost) string { - return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(portworxVolumePluginName), volName) -} - -func (plugin *portworxVolumePlugin) IsMigratedToCSI() bool { - return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) -} - -func (plugin *portworxVolumePlugin) Init(host volume.VolumeHost) error { - client, err := volumeclient.NewDriverClient( - fmt.Sprintf("http://%s", net.JoinHostPort(host.GetHostName(), strconv.Itoa(osdMgmtDefaultPort))), - pxdDriverName, osdDriverVersion, pxDriverName) - if err != nil { - return err - } - - plugin.host = host - plugin.util = &portworxVolumeUtil{ - portworxClient: client, - } - - return nil -} - -func (plugin *portworxVolumePlugin) GetPluginName() string { - return portworxVolumePluginName -} - -func (plugin *portworxVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { - volumeSource, _, err := getVolumeSource(spec) - if err != nil { - return "", err - } - - return volumeSource.VolumeID, nil -} - -func (plugin *portworxVolumePlugin) CanSupport(spec *volume.Spec) bool { - return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PortworxVolume != nil) || - (spec.Volume != nil && spec.Volume.PortworxVolume != nil) -} - -func (plugin *portworxVolumePlugin) RequiresRemount(spec *volume.Spec) bool { - return false -} - -func (plugin *portworxVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { - return []v1.PersistentVolumeAccessMode{ - v1.ReadWriteOnce, - v1.ReadWriteMany, - } -} - -func (plugin *portworxVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod) (volume.Mounter, error) { - return plugin.newMounterInternal(spec, pod.UID, plugin.util, plugin.host.GetMounter()) -} - -func (plugin *portworxVolumePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager portworxManager, mounter mount.Interface) (volume.Mounter, error) { - pwx, readOnly, err := getVolumeSource(spec) - if err != nil { - return nil, err - } - - volumeID := pwx.VolumeID - fsType := pwx.FSType - - return &portworxVolumeMounter{ - portworxVolume: &portworxVolume{ - podUID: podUID, - volName: spec.Name(), - volumeID: volumeID, - manager: manager, - mounter: mounter, - plugin: plugin, - MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)), - }, - fsType: fsType, - readOnly: readOnly, - diskMounter: mount.NewSafeFormatAndMount(plugin.host.GetMounter(), exec.New())}, nil -} - -func (plugin *portworxVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { - return plugin.newUnmounterInternal(volName, podUID, plugin.util, plugin.host.GetMounter()) -} - -func (plugin *portworxVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, manager portworxManager, - mounter mount.Interface) (volume.Unmounter, error) { - return &portworxVolumeUnmounter{ - &portworxVolume{ - podUID: podUID, - volName: volName, - manager: manager, - mounter: mounter, - plugin: plugin, - MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), - }}, nil -} - -func (plugin *portworxVolumePlugin) NewDeleter(logger klog.Logger, spec *volume.Spec) (volume.Deleter, error) { - return plugin.newDeleterInternal(spec, plugin.util) -} - -func (plugin *portworxVolumePlugin) newDeleterInternal(spec *volume.Spec, manager portworxManager) (volume.Deleter, error) { - if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PortworxVolume == nil { - return nil, fmt.Errorf("spec.PersistentVolumeSource.PortworxVolume is nil") - } - - return &portworxVolumeDeleter{ - portworxVolume: &portworxVolume{ - volName: spec.Name(), - volumeID: spec.PersistentVolume.Spec.PortworxVolume.VolumeID, - manager: manager, - plugin: plugin, - }}, nil -} - -func (plugin *portworxVolumePlugin) NewProvisioner(logger klog.Logger, options volume.VolumeOptions) (volume.Provisioner, error) { - return plugin.newProvisionerInternal(options, plugin.util) -} - -func (plugin *portworxVolumePlugin) newProvisionerInternal(options volume.VolumeOptions, manager portworxManager) (volume.Provisioner, error) { - return &portworxVolumeProvisioner{ - portworxVolume: &portworxVolume{ - manager: manager, - plugin: plugin, - }, - options: options, - }, nil -} - -func (plugin *portworxVolumePlugin) RequiresFSResize() bool { - return false -} - -func (plugin *portworxVolumePlugin) ExpandVolumeDevice( - spec *volume.Spec, - newSize resource.Quantity, - oldSize resource.Quantity) (resource.Quantity, error) { - klog.V(4).Infof("Expanding: %s from %v to %v", spec.Name(), oldSize, newSize) - err := plugin.util.ResizeVolume(spec, newSize, plugin.host) - if err != nil { - return oldSize, err - } - - klog.V(4).Infof("Successfully resized %s to %v", spec.Name(), newSize) - return newSize, nil -} - -func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { - portworxVolume := &v1.Volume{ - Name: volumeName, - VolumeSource: v1.VolumeSource{ - PortworxVolume: &v1.PortworxVolumeSource{ - VolumeID: volumeName, - }, - }, - } - return volume.ReconstructedVolume{ - Spec: volume.NewSpecFromVolume(portworxVolume), - }, nil -} - -func (plugin *portworxVolumePlugin) SupportsMountOption() bool { - return false -} - -func (plugin *portworxVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { - return false, nil -} - -func getVolumeSource( - spec *volume.Spec) (*v1.PortworxVolumeSource, bool, error) { - if spec.Volume != nil && spec.Volume.PortworxVolume != nil { - return spec.Volume.PortworxVolume, spec.Volume.PortworxVolume.ReadOnly, nil - } else if spec.PersistentVolume != nil && - spec.PersistentVolume.Spec.PortworxVolume != nil { - return spec.PersistentVolume.Spec.PortworxVolume, spec.ReadOnly, nil - } - - return nil, false, fmt.Errorf("Spec does not reference a Portworx Volume type") -} - -// Abstract interface to PD operations. -type portworxManager interface { - // Creates a volume - CreateVolume(provisioner *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error) - // Deletes a volume - DeleteVolume(deleter *portworxVolumeDeleter) error - // Attach a volume - AttachVolume(mounter *portworxVolumeMounter, attachOptions map[string]string) (string, error) - // Detach a volume - DetachVolume(unmounter *portworxVolumeUnmounter) error - // Mount a volume - MountVolume(mounter *portworxVolumeMounter, mountDir string) error - // Unmount a volume - UnmountVolume(unmounter *portworxVolumeUnmounter, mountDir string) error - // Resize a volume - ResizeVolume(spec *volume.Spec, newSize resource.Quantity, host volume.VolumeHost) error -} - -// portworxVolume volumes are portworx block devices -// that are attached to the kubelet's host machine and exposed to the pod. -type portworxVolume struct { - volName string - podUID types.UID - // Unique id of the PD, used to find the disk resource in the provider. - volumeID string - // Utility interface that provides API calls to the provider to attach/detach disks. - manager portworxManager - // Mounter interface that provides system calls to mount the global path to the pod local path. - mounter mount.Interface - plugin *portworxVolumePlugin - volume.MetricsProvider -} - -type portworxVolumeMounter struct { - *portworxVolume - // Filesystem type, optional. - fsType string - // Specifies whether the disk will be attached as read-only. - readOnly bool - // diskMounter provides the interface that is used to mount the actual block device. - diskMounter *mount.SafeFormatAndMount -} - -var _ volume.Mounter = &portworxVolumeMounter{} - -func (b *portworxVolumeMounter) GetAttributes() volume.Attributes { - return volume.Attributes{ - ReadOnly: b.readOnly, - Managed: !b.readOnly, - SELinuxRelabel: false, - } -} - -// SetUp attaches the disk and bind mounts to the volume path. -func (b *portworxVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { - return b.SetUpAt(b.GetPath(), mounterArgs) -} - -// SetUpAt attaches the disk and bind mounts to the volume path. -func (b *portworxVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { - notMnt, err := b.mounter.IsLikelyNotMountPoint(dir) - klog.Infof("Portworx Volume set up. Dir: %s %v %v", dir, !notMnt, err) - if err != nil && !os.IsNotExist(err) { - // don't log error details from client calls in events - klog.V(4).Infof("Cannot validate mountpoint %s: %v", dir, err) - return fmt.Errorf("failed to validate mountpoint: see kube-controller-manager.log for details") - } - if !notMnt { - return nil - } - - attachOptions := make(map[string]string) - attachOptions[attachContextKey] = dir - attachOptions[attachHostKey] = b.plugin.host.GetHostName() - if _, err := b.manager.AttachVolume(b, attachOptions); err != nil { - // don't log error details from client calls in events - klog.V(4).Infof("Failed to attach volume %s: %v", b.volumeID, err) - return fmt.Errorf("failed to attach volume: see kube-controller-manager.log for details") - } - - klog.V(4).Infof("Portworx Volume %s attached", b.volumeID) - - if err := os.MkdirAll(dir, 0750); err != nil { - return err - } - - if err := b.manager.MountVolume(b, dir); err != nil { - // don't log error details from client calls in events - klog.V(4).Infof("Failed to mount volume %s: %v", b.volumeID, err) - return fmt.Errorf("failed to mount volume: see kube-controller-manager.log for details") - } - if !b.readOnly { - // Since portworxVolume is in process of being removed from in-tree, we avoid larger refactor to add progress tracking for ownership operation - ownershipChanger := volume.NewVolumeOwnership(b, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil)) - _ = ownershipChanger.ChangePermissions() - } - klog.Infof("Portworx Volume %s setup at %s", b.volumeID, dir) - return nil -} - -func (pwx *portworxVolume) GetPath() string { - return getPath(pwx.podUID, pwx.volName, pwx.plugin.host) -} - -type portworxVolumeUnmounter struct { - *portworxVolume -} - -var _ volume.Unmounter = &portworxVolumeUnmounter{} - -// Unmounts the bind mount, and detaches the disk only if the PD -// resource was the last reference to that disk on the kubelet. -func (c *portworxVolumeUnmounter) TearDown() error { - return c.TearDownAt(c.GetPath()) -} - -// Unmounts the bind mount, and detaches the disk only if the PD -// resource was the last reference to that disk on the kubelet. -func (c *portworxVolumeUnmounter) TearDownAt(dir string) error { - klog.Infof("Portworx Volume TearDown of %s", dir) - - if err := c.manager.UnmountVolume(c, dir); err != nil { - // don't log error details from client calls in events - klog.V(4).Infof("Failed to unmount volume %s: %v", c.volumeID, err) - return fmt.Errorf("failed to unmount volume: see kube-controller-manager.log for details") - } - - // Call Portworx Detach Volume. - if err := c.manager.DetachVolume(c); err != nil { - // don't log error details from client calls in events - klog.V(4).Infof("Failed to detach volume %s: %v", c.volumeID, err) - return fmt.Errorf("failed to detach volume: see kube-controller-manager.log for details") - } - - return nil -} - -type portworxVolumeDeleter struct { - *portworxVolume -} - -var _ volume.Deleter = &portworxVolumeDeleter{} - -func (d *portworxVolumeDeleter) GetPath() string { - return getPath(d.podUID, d.volName, d.plugin.host) -} - -func (d *portworxVolumeDeleter) Delete() error { - err := d.manager.DeleteVolume(d) - if err != nil { - // don't log error details from client calls in events - klog.V(4).Infof("Failed to delete volume %s: %v", d.volumeID, err) - return fmt.Errorf("failed to delete volume: see kube-controller-manager.log for details") - } - return nil -} - -type portworxVolumeProvisioner struct { - *portworxVolume - options volume.VolumeOptions -} - -var _ volume.Provisioner = &portworxVolumeProvisioner{} - -func (c *portworxVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { - if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) { - return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) - } - - if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { - return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) - } - - volumeID, sizeGiB, labels, err := c.manager.CreateVolume(c) - if err != nil { - // don't log error details from client calls in events - klog.V(4).Infof("Failed to create volume: %v", err) - return nil, fmt.Errorf("failed to create volume: see kube-controller-manager.log for details") - } - - pv := &v1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: c.options.PVName, - Labels: map[string]string{}, - Annotations: map[string]string{ - util.VolumeDynamicallyCreatedByKey: "portworx-volume-dynamic-provisioner", - }, - }, - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy, - AccessModes: c.options.PVC.Spec.AccessModes, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGiB)), - }, - PersistentVolumeSource: v1.PersistentVolumeSource{ - PortworxVolume: &v1.PortworxVolumeSource{ - VolumeID: volumeID, - }, - }, - }, - } - - if len(labels) != 0 { - if pv.Labels == nil { - pv.Labels = make(map[string]string) - } - for k, v := range labels { - pv.Labels[k] = v - } - } - - if len(c.options.PVC.Spec.AccessModes) == 0 { - pv.Spec.AccessModes = c.plugin.GetAccessModes() - } - - return pv, nil -} diff --git a/pkg/volume/portworx/portworx_test.go b/pkg/volume/portworx/portworx_test.go deleted file mode 100644 index a175a7c8847f1..0000000000000 --- a/pkg/volume/portworx/portworx_test.go +++ /dev/null @@ -1,238 +0,0 @@ -/* -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. -*/ - -package portworx - -import ( - "fmt" - "os" - "path/filepath" - "testing" - - "k8s.io/mount-utils" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/types" - utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/volume" - volumetest "k8s.io/kubernetes/pkg/volume/testing" -) - -const ( - PortworxTestVolume = "portworx-test-vol" -) - -func TestCanSupport(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) - - plug, err := plugMgr.FindPluginByName("kubernetes.io/portworx-volume") - if err != nil { - t.Fatal("Can't find the plugin by name") - } - if plug.GetPluginName() != "kubernetes.io/portworx-volume" { - t.Errorf("Wrong name: %s", plug.GetPluginName()) - } - if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{PortworxVolume: &v1.PortworxVolumeSource{}}}}) { - t.Errorf("Expected true") - } - if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{PortworxVolume: &v1.PortworxVolumeSource{}}}}}) { - t.Errorf("Expected true") - } -} - -func TestGetAccessModes(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) - - plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/portworx-volume") - if err != nil { - t.Errorf("Can't find the plugin by name") - } - - if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) { - t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteOnce) - } - if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) { - t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteMany) - } - if volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { - t.Errorf("Expected not to support AccessModeTypes: %s", v1.ReadOnlyMany) - } -} - -type fakePortworxManager struct { - attachCalled bool - mountCalled bool -} - -func (fake *fakePortworxManager) AttachVolume(b *portworxVolumeMounter, attachOptions map[string]string) (string, error) { - fake.attachCalled = true - return "", nil -} - -func (fake *fakePortworxManager) DetachVolume(c *portworxVolumeUnmounter) error { - return nil -} - -func (fake *fakePortworxManager) MountVolume(b *portworxVolumeMounter, mountPath string) error { - fake.mountCalled = true - return nil -} - -func (fake *fakePortworxManager) UnmountVolume(c *portworxVolumeUnmounter, mountPath string) error { - return nil -} - -func (fake *fakePortworxManager) CreateVolume(c *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error) { - labels = make(map[string]string) - labels["fakeportworxmanager"] = "yes" - return PortworxTestVolume, 100, labels, nil -} - -func (fake *fakePortworxManager) DeleteVolume(cd *portworxVolumeDeleter) error { - if cd.volumeID != PortworxTestVolume { - return fmt.Errorf("Deleter got unexpected volume name: %s", cd.volumeID) - } - return nil -} - -func (fake *fakePortworxManager) ResizeVolume(spec *volume.Spec, newSize resource.Quantity, volumeHost volume.VolumeHost) error { - return nil -} - -func TestPlugin(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) - - plug, err := plugMgr.FindPluginByName("kubernetes.io/portworx-volume") - if err != nil { - t.Errorf("Can't find the plugin by name") - } - spec := &v1.Volume{ - Name: "vol1", - VolumeSource: v1.VolumeSource{ - PortworxVolume: &v1.PortworxVolumeSource{ - VolumeID: PortworxTestVolume, - FSType: "ext4", - }, - }, - } - fakeManager := &fakePortworxManager{} - // Test Mounter - fakeMounter := mount.NewFakeMounter(nil) - mounter, err := plug.(*portworxVolumePlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter) - if err != nil { - t.Errorf("Failed to make a new Mounter: %v", err) - } - if mounter == nil { - t.Errorf("Got a nil Mounter") - } - - volPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~portworx-volume/vol1") - path := mounter.GetPath() - if path != volPath { - t.Errorf("Got unexpected path: %s", path) - } - - if err := mounter.SetUp(volume.MounterArgs{}); err != nil { - t.Errorf("Expected success, got: %v", err) - } - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - t.Errorf("SetUp() failed, volume path not created: %s", path) - } else { - t.Errorf("SetUp() failed: %v", err) - } - } - if !fakeManager.attachCalled { - t.Errorf("Attach watch not called") - } - if !fakeManager.mountCalled { - t.Errorf("Mount watch not called") - } - - // Test Unmounter - fakeManager = &fakePortworxManager{} - unmounter, err := plug.(*portworxVolumePlugin).newUnmounterInternal("vol1", types.UID("poduid"), fakeManager, fakeMounter) - if err != nil { - t.Errorf("Failed to make a new Unmounter: %v", err) - } - if unmounter == nil { - t.Errorf("Got a nil Unmounter") - } - - if err := unmounter.TearDown(); err != nil { - t.Errorf("Expected success, got: %v", err) - } - - // Test Provisioner - options := volume.VolumeOptions{ - PVC: volumetest.CreateTestPVC("100Gi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}), - PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete, - } - - provisioner, err := plug.(*portworxVolumePlugin).newProvisionerInternal(options, &fakePortworxManager{}) - if err != nil { - t.Errorf("Error creating a new provisioner:%v", err) - } - persistentSpec, err := provisioner.Provision(nil, nil) - if err != nil { - t.Errorf("Provision() failed: %v", err) - } - - if persistentSpec.Spec.PersistentVolumeSource.PortworxVolume.VolumeID != PortworxTestVolume { - t.Errorf("Provision() returned unexpected volume ID: %s", persistentSpec.Spec.PersistentVolumeSource.PortworxVolume.VolumeID) - } - cap := persistentSpec.Spec.Capacity[v1.ResourceStorage] - size := cap.Value() - if size != 100*1024*1024*1024 { - t.Errorf("Provision() returned unexpected volume size: %v", size) - } - - if persistentSpec.Labels["fakeportworxmanager"] != "yes" { - t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels) - } - - // Test Deleter - volSpec := &volume.Spec{ - PersistentVolume: persistentSpec, - } - deleter, err := plug.(*portworxVolumePlugin).newDeleterInternal(volSpec, &fakePortworxManager{}) - if err != nil { - t.Errorf("Error creating a new Deleter:%v", err) - } - err = deleter.Delete() - if err != nil { - t.Errorf("Deleter() failed: %v", err) - } -} diff --git a/pkg/volume/portworx/portworx_util.go b/pkg/volume/portworx/portworx_util.go deleted file mode 100644 index 1d201bcf4a1c0..0000000000000 --- a/pkg/volume/portworx/portworx_util.go +++ /dev/null @@ -1,387 +0,0 @@ -/* -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. -*/ - -package portworx - -import ( - "context" - "fmt" - "net" - "strconv" - - osdapi "github.com/libopenstorage/openstorage/api" - osdclient "github.com/libopenstorage/openstorage/api/client" - volumeclient "github.com/libopenstorage/openstorage/api/client/volume" - osdspec "github.com/libopenstorage/openstorage/api/spec" - volumeapi "github.com/libopenstorage/openstorage/volume" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - volumehelpers "k8s.io/cloud-provider/volume/helpers" - "k8s.io/klog/v2" - - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/volume" -) - -const ( - osdMgmtDefaultPort = 9001 - osdDriverVersion = "v1" - pxdDriverName = "pxd" - pvcClaimLabel = "pvc" - pvcNamespaceLabel = "namespace" - pxServiceName = "portworx-service" - pxDriverName = "pxd-sched" -) - -type portworxVolumeUtil struct { - portworxClient *osdclient.Client -} - -// CreateVolume creates a Portworx volume. -func (util *portworxVolumeUtil) CreateVolume(p *portworxVolumeProvisioner) (string, int64, map[string]string, error) { - driver, err := util.getPortworxDriver(p.plugin.host) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return "", 0, nil, err - } - - klog.Infof("Creating Portworx volume for PVC: %v", p.options.PVC.Name) - - capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] - // Portworx Volumes are specified in GiB - requestGiB, err := volumehelpers.RoundUpToGiB(capacity) - if err != nil { - return "", 0, nil, err - } - - // Perform a best-effort parsing of parameters. Portworx 1.2.9 and later parses volume parameters from - // spec.VolumeLabels. So even if below SpecFromOpts() fails to parse certain parameters or - // doesn't support new parameters, the server-side processing will parse it correctly. - // We still need to call SpecFromOpts() here to handle cases where someone is running Portworx 1.2.8 and lower. - specHandler := osdspec.NewSpecHandler() - spec, locator, source, _ := specHandler.SpecFromOpts(p.options.Parameters) - if spec == nil { - spec = specHandler.DefaultSpec() - } - - // Pass all parameters as volume labels for Portworx server-side processing - if spec.VolumeLabels == nil { - spec.VolumeLabels = make(map[string]string, 0) - } - - for k, v := range p.options.Parameters { - spec.VolumeLabels[k] = v - } - - // Update the requested size in the spec - spec.Size = uint64(requestGiB * volumehelpers.GiB) - - // Change the Portworx Volume name to PV name - if locator == nil { - locator = &osdapi.VolumeLocator{ - VolumeLabels: make(map[string]string), - } - } - locator.Name = p.options.PVName - - // Add claim Name as a part of Portworx Volume Labels - locator.VolumeLabels[pvcClaimLabel] = p.options.PVC.Name - locator.VolumeLabels[pvcNamespaceLabel] = p.options.PVC.Namespace - - for k, v := range p.options.PVC.Annotations { - if _, present := spec.VolumeLabels[k]; present { - klog.Warningf("not saving annotation: %s=%s in spec labels due to an existing key", k, v) - continue - } - spec.VolumeLabels[k] = v - } - - volumeID, err := driver.Create(locator, source, spec) - if err != nil { - klog.Errorf("Error creating Portworx Volume : %v", err) - return "", 0, nil, err - } - - klog.Infof("Successfully created Portworx volume for PVC: %v", p.options.PVC.Name) - return volumeID, requestGiB, nil, err -} - -// DeleteVolume deletes a Portworx volume -func (util *portworxVolumeUtil) DeleteVolume(d *portworxVolumeDeleter) error { - driver, err := util.getPortworxDriver(d.plugin.host) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return err - } - - err = driver.Delete(d.volumeID) - if err != nil { - klog.Errorf("Error deleting Portworx Volume (%v): %v", d.volName, err) - return err - } - return nil -} - -// AttachVolume attaches a Portworx Volume -func (util *portworxVolumeUtil) AttachVolume(m *portworxVolumeMounter, attachOptions map[string]string) (string, error) { - driver, err := util.getLocalPortworxDriver(m.plugin.host) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return "", err - } - - devicePath, err := driver.Attach(m.volName, attachOptions) - if err != nil { - klog.Errorf("Error attaching Portworx Volume (%v): %v", m.volName, err) - return "", err - } - return devicePath, nil -} - -// DetachVolume detaches a Portworx Volume -func (util *portworxVolumeUtil) DetachVolume(u *portworxVolumeUnmounter) error { - driver, err := util.getLocalPortworxDriver(u.plugin.host) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return err - } - - err = driver.Detach(u.volName, false /*doNotForceDetach*/) - if err != nil { - klog.Errorf("Error detaching Portworx Volume (%v): %v", u.volName, err) - return err - } - return nil -} - -// MountVolume mounts a Portworx Volume on the specified mountPath -func (util *portworxVolumeUtil) MountVolume(m *portworxVolumeMounter, mountPath string) error { - driver, err := util.getLocalPortworxDriver(m.plugin.host) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return err - } - - err = driver.Mount(m.volName, mountPath) - if err != nil { - klog.Errorf("Error mounting Portworx Volume (%v) on Path (%v): %v", m.volName, mountPath, err) - return err - } - return nil -} - -// UnmountVolume unmounts a Portworx Volume -func (util *portworxVolumeUtil) UnmountVolume(u *portworxVolumeUnmounter, mountPath string) error { - driver, err := util.getLocalPortworxDriver(u.plugin.host) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return err - } - - err = driver.Unmount(u.volName, mountPath) - if err != nil { - klog.Errorf("Error unmounting Portworx Volume (%v) on Path (%v): %v", u.volName, mountPath, err) - return err - } - return nil -} - -func (util *portworxVolumeUtil) ResizeVolume(spec *volume.Spec, newSize resource.Quantity, volumeHost volume.VolumeHost) error { - driver, err := util.getPortworxDriver(volumeHost) - if err != nil || driver == nil { - klog.Errorf("Failed to get portworx driver. Err: %v", err) - return err - } - - vols, err := driver.Inspect([]string{spec.Name()}) - if err != nil { - return err - } - - if len(vols) != 1 { - return fmt.Errorf("failed to inspect Portworx volume: %s. Found: %d volumes", spec.Name(), len(vols)) - } - - vol := vols[0] - tBytes, err := volumehelpers.RoundUpToB(newSize) - if err != nil { - return err - } - newSizeInBytes := uint64(tBytes) - if vol.Spec.Size >= newSizeInBytes { - klog.Infof("Portworx volume: %s already at size: %d greater than or equal to new "+ - "requested size: %d. Skipping resize.", spec.Name(), vol.Spec.Size, newSizeInBytes) - return nil - } - - vol.Spec.Size = newSizeInBytes - err = driver.Set(spec.Name(), vol.Locator, vol.Spec) - if err != nil { - return err - } - - // check if the volume's size actually got updated - vols, err = driver.Inspect([]string{spec.Name()}) - if err != nil { - return err - } - - if len(vols) != 1 { - return fmt.Errorf("failed to inspect resized Portworx volume: %s. Found: %d volumes", spec.Name(), len(vols)) - } - - updatedVol := vols[0] - if updatedVol.Spec.Size < vol.Spec.Size { - return fmt.Errorf("Portworx volume: %s doesn't match expected size after resize. expected:%v actual:%v", - spec.Name(), vol.Spec.Size, updatedVol.Spec.Size) - } - - return nil -} - -func isClientValid(client *osdclient.Client) (bool, error) { - if client == nil { - return false, nil - } - - _, err := client.Versions(osdapi.OsdVolumePath) - if err != nil { - klog.Errorf("portworx client failed driver versions check. Err: %v", err) - return false, err - } - - return true, nil -} - -func createDriverClient(hostname string, port int32) (*osdclient.Client, error) { - client, err := volumeclient.NewDriverClient(fmt.Sprintf("http://%s", net.JoinHostPort(hostname, strconv.Itoa(int(port)))), - pxdDriverName, osdDriverVersion, pxDriverName) - if err != nil { - return nil, err - } - - isValid, err := isClientValid(client) - if isValid { - return client, nil - } - return nil, err -} - -// getPortworxDriver returns a Portworx volume driver which can be used for cluster wide operations. -// -// Operations like create and delete volume don't need to be restricted to local volume host since -// any node in the Portworx cluster can coordinate the create/delete request and forward the operations to -// the Portworx node that will own/owns the data. -func (util *portworxVolumeUtil) getPortworxDriver(volumeHost volume.VolumeHost) (volumeapi.VolumeDriver, error) { - // check if existing saved client is valid - if isValid, _ := isClientValid(util.portworxClient); isValid { - return volumeclient.VolumeDriver(util.portworxClient), nil - } - - // create new client - var err error - util.portworxClient, err = createDriverClient(volumeHost.GetHostName(), osdMgmtDefaultPort) // for backward compatibility - if err != nil || util.portworxClient == nil { - // Create client from portworx k8s service. - svc, err := getPortworxService(volumeHost) - if err != nil { - return nil, err - } - - // The port here is always the default one since it's the service port - util.portworxClient, err = createDriverClient(svc.Spec.ClusterIP, osdMgmtDefaultPort) - if err != nil || util.portworxClient == nil { - klog.Errorf("Failed to connect to portworx service. Err: %v", err) - return nil, err - } - - klog.Infof("Using portworx cluster service at: %v:%d as api endpoint", - svc.Spec.ClusterIP, osdMgmtDefaultPort) - } else { - klog.Infof("Using portworx service at: %v:%d as api endpoint", - volumeHost.GetHostName(), osdMgmtDefaultPort) - } - - return volumeclient.VolumeDriver(util.portworxClient), nil -} - -// getLocalPortworxDriver returns driver connected to Portworx API server on volume host. -// -// This is required to force certain operations (mount, unmount, detach, attach) to -// go to the volume host instead of the k8s service which might route it to any host. This pertains to how -// Portworx mounts and attaches a volume to the running container. The node getting these requests needs to -// see the pod container mounts (specifically /var/lib/kubelet/pods/) -func (util *portworxVolumeUtil) getLocalPortworxDriver(volumeHost volume.VolumeHost) (volumeapi.VolumeDriver, error) { - if util.portworxClient != nil { - // check if existing saved client is valid - if isValid, _ := isClientValid(util.portworxClient); isValid { - return volumeclient.VolumeDriver(util.portworxClient), nil - } - } - - // Lookup port - svc, err := getPortworxService(volumeHost) - if err != nil { - return nil, err - } - - osgMgmtPort := lookupPXAPIPortFromService(svc) - util.portworxClient, err = createDriverClient(volumeHost.GetHostName(), osgMgmtPort) - if err != nil { - return nil, err - } - - klog.Infof("Using portworx local service at: %v:%d as api endpoint", - volumeHost.GetHostName(), osgMgmtPort) - return volumeclient.VolumeDriver(util.portworxClient), nil -} - -// lookupPXAPIPortFromService goes over all the ports in the given service and returns the target -// port for osdMgmtDefaultPort -func lookupPXAPIPortFromService(svc *v1.Service) int32 { - for _, p := range svc.Spec.Ports { - if p.Port == osdMgmtDefaultPort { - return p.TargetPort.IntVal - } - } - return osdMgmtDefaultPort // default -} - -// getPortworxService returns the portworx cluster service from the API server -func getPortworxService(host volume.VolumeHost) (*v1.Service, error) { - kubeClient := host.GetKubeClient() - if kubeClient == nil { - err := fmt.Errorf("failed to get kubeclient when creating portworx client") - klog.Error(err.Error()) - return nil, err - } - - opts := metav1.GetOptions{} - svc, err := kubeClient.CoreV1().Services(api.NamespaceSystem).Get(context.TODO(), pxServiceName, opts) - if err != nil { - klog.Errorf("Failed to get service. Err: %v", err) - return nil, err - } - - if svc == nil { - err = fmt.Errorf("service: %v not found. Consult Portworx docs to deploy it", pxServiceName) - klog.Error(err.Error()) - return nil, err - } - - return svc, nil -} diff --git a/pkg/volume/testing/testing.go b/pkg/volume/testing/testing.go index e96df4eb0f9d2..aad6f58e61f44 100644 --- a/pkg/volume/testing/testing.go +++ b/pkg/volume/testing/testing.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" goruntime "runtime" + "slices" "strings" "sync" "testing" @@ -199,6 +200,10 @@ type FakeVolumePlugin struct { // Add callbacks as needed WaitForAttachHook func(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) UnmountDeviceHook func(globalMountPath string) error + SetUpHook func(plugin volume.VolumePlugin, mounterArgs volume.MounterArgs) error + + // Inject error to NewUnmounterError + NewUnmounterError error Mounters []*FakeVolume Unmounters []*FakeVolume @@ -226,12 +231,14 @@ func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume { defer volume.Unlock() volume.WaitForAttachHook = plugin.WaitForAttachHook volume.UnmountDeviceHook = plugin.UnmountDeviceHook + volume.SetUpHook = plugin.SetUpHook return volume } } volume := &FakeVolume{ WaitForAttachHook: plugin.WaitForAttachHook, UnmountDeviceHook: plugin.UnmountDeviceHook, + SetUpHook: plugin.SetUpHook, } volume.VolumesAttached = make(map[string]sets.Set[string]) volume.DeviceMountState = make(map[string]string) @@ -321,6 +328,9 @@ func (plugin *FakeVolumePlugin) GetMounters() (Mounters []*FakeVolume) { func (plugin *FakeVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { plugin.Lock() defer plugin.Unlock() + if plugin.NewUnmounterError != nil { + return nil, plugin.NewUnmounterError + } fakeVolume := plugin.getFakeVolume(&plugin.Unmounters) fakeVolume.Lock() defer fakeVolume.Unlock() @@ -689,6 +699,7 @@ type FakeVolume struct { // Add callbacks as needed WaitForAttachHook func(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) UnmountDeviceHook func(globalMountPath string) error + SetUpHook func(plugin volume.VolumePlugin, mounterArgs volume.MounterArgs) error SetUpCallCount int TearDownCallCount int @@ -738,6 +749,9 @@ func (fv *FakeVolume) SetUp(mounterArgs volume.MounterArgs) error { defer fv.Unlock() err := fv.setupInternal(mounterArgs) fv.SetUpCallCount++ + if fv.SetUpHook != nil { + return fv.SetUpHook(fv.Plugin, mounterArgs) + } return err } @@ -1735,10 +1749,5 @@ func MetricsEqualIgnoreTimestamp(a *volume.Metrics, b *volume.Metrics) bool { } func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false + return slices.Contains(modes, mode) } diff --git a/pkg/volume/testing/volume_host.go b/pkg/volume/testing/volume_host.go index 77a096b6c9825..52f91b0001520 100644 --- a/pkg/volume/testing/volume_host.go +++ b/pkg/volume/testing/volume_host.go @@ -173,11 +173,6 @@ func (f *fakeVolumeHost) NewWrapperUnmounter(volName string, spec Spec, podUID t return plug.NewUnmounter(spec.Name(), podUID) } -// Returns the hostname of the host kubelet is running on -func (f *fakeVolumeHost) GetHostName() string { - return "fakeHostName" -} - func (f *fakeVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) { return v1.ResourceList{}, nil } diff --git a/pkg/volume/util/atomic_writer.go b/pkg/volume/util/atomic_writer.go index 0466dffacdf49..cd61baabc1646 100644 --- a/pkg/volume/util/atomic_writer.go +++ b/pkg/volume/util/atomic_writer.go @@ -445,7 +445,7 @@ func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir continue } - if err := w.chown(fullPath, int(*fileProjection.FsUser), -1); err != nil { + if err := w.lchown(fullPath, int(*fileProjection.FsUser), -1); err != nil { klog.Errorf("%s: unable to change file %s with owner %v: %v", w.logContext, fullPath, int(*fileProjection.FsUser), err) return err } @@ -465,7 +465,7 @@ func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir // foo -> ..data/foo // baz -> ..data/baz func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) error { - for userVisiblePath := range payload { + for userVisiblePath, fileProjection := range payload { slashpos := strings.Index(userVisiblePath, string(os.PathSeparator)) if slashpos == -1 { slashpos = len(userVisiblePath) @@ -481,6 +481,15 @@ func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) if err != nil { return err } + + if fileProjection.FsUser == nil { + continue + } + + if err := w.lchown(visibleFile, int(*fileProjection.FsUser), -1); err != nil { + klog.Errorf("%s: unable to change file %s with owner %v: %v", w.logContext, visibleFile, int(*fileProjection.FsUser), err) + return err + } } } return nil diff --git a/pkg/volume/util/atomic_writer_linux.go b/pkg/volume/util/atomic_writer_linux.go index c12a0f4cd317e..ba3b64c55cc3e 100644 --- a/pkg/volume/util/atomic_writer_linux.go +++ b/pkg/volume/util/atomic_writer_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2024 The Kubernetes Authors. @@ -21,7 +20,8 @@ package util import "os" -// chown changes the numeric uid and gid of the named file. -func (w *AtomicWriter) chown(name string, uid, gid int) error { - return os.Chown(name, uid, gid) +// lchown changes the numeric uid and gid of the named file. +// If the file is a symbolic link, it changes the uid and gid of the link itself. +func (w *AtomicWriter) lchown(name string, uid, gid int) error { + return os.Lchown(name, uid, gid) } diff --git a/pkg/volume/util/atomic_writer_test.go b/pkg/volume/util/atomic_writer_test.go index dfe01d92f5b65..da1f1cb25b437 100644 --- a/pkg/volume/util/atomic_writer_test.go +++ b/pkg/volume/util/atomic_writer_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. @@ -1061,7 +1060,6 @@ func TestWriteAgainAfterUnexpectedExit(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { targetDir, err := utiltesting.MkTmpdir("atomic-write") if err != nil { diff --git a/pkg/volume/util/atomic_writer_unsupported.go b/pkg/volume/util/atomic_writer_unsupported.go index cdfb83e639725..a77ef9414e994 100644 --- a/pkg/volume/util/atomic_writer_unsupported.go +++ b/pkg/volume/util/atomic_writer_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2024 The Kubernetes Authors. @@ -25,9 +24,10 @@ import ( "k8s.io/klog/v2" ) -// chown changes the numeric uid and gid of the named file. +// lchown changes the numeric uid and gid of the named file. +// If the file is a symbolic link, it changes the uid and gid of the link itself. // This is a no-op on unsupported platforms. -func (w *AtomicWriter) chown(name string, uid, _ /* gid */ int) error { +func (w *AtomicWriter) lchown(name string, uid, _ /* gid */ int) error { klog.Warningf("%s: skipping change of Linux owner %v for file %s; unsupported on %s", w.logContext, uid, name, runtime.GOOS) return nil } diff --git a/pkg/volume/util/attach_limit.go b/pkg/volume/util/attach_limit.go deleted file mode 100644 index 4249a6be4f8f4..0000000000000 --- a/pkg/volume/util/attach_limit.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2018 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 util - -import ( - "crypto/sha1" - "encoding/hex" -) - -// This file is a common place holder for volume limit utility constants -// shared between volume package and scheduler - -const ( - // CSIAttachLimitPrefix defines prefix used for CSI volumes - CSIAttachLimitPrefix = "attachable-volumes-csi-" - - // ResourceNameLengthLimit stores maximum allowed Length for a ResourceName - ResourceNameLengthLimit = 63 -) - -// GetCSIAttachLimitKey returns limit key used for CSI volumes -func GetCSIAttachLimitKey(driverName string) string { - csiPrefixLength := len(CSIAttachLimitPrefix) - totalkeyLength := csiPrefixLength + len(driverName) - if totalkeyLength >= ResourceNameLengthLimit { - charsFromDriverName := driverName[:23] - hash := sha1.New() - hash.Write([]byte(driverName)) - hashed := hex.EncodeToString(hash.Sum(nil)) - hashed = hashed[:16] - return CSIAttachLimitPrefix + charsFromDriverName + hashed - } - return CSIAttachLimitPrefix + driverName -} diff --git a/pkg/volume/util/attach_limit_test.go b/pkg/volume/util/attach_limit_test.go deleted file mode 100644 index e298f197bf488..0000000000000 --- a/pkg/volume/util/attach_limit_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2018 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 util - -import ( - "crypto/sha1" - "encoding/hex" - "testing" - - "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" -) - -func TestGetCSIAttachLimitKey(t *testing.T) { - // When driverName is less than 39 characters - csiLimitKey := GetCSIAttachLimitKey("com.amazon.ebs") - if csiLimitKey != "attachable-volumes-csi-com.amazon.ebs" { - t.Errorf("Expected com.amazon.ebs got %s", csiLimitKey) - } - - // When driver is longer than 39 chars - longDriverName := "com.amazon.kubernetes.eks.ec2.ebs/csi-driver" - csiLimitKeyLonger := GetCSIAttachLimitKey(longDriverName) - if !v1helper.IsAttachableVolumeResourceName(v1.ResourceName(csiLimitKeyLonger)) { - t.Errorf("Expected %s to have attachable prefix", csiLimitKeyLonger) - } - - expectedCSIKey := getDriverHash(longDriverName) - if csiLimitKeyLonger != expectedCSIKey { - t.Errorf("Expected limit to be %s got %s", expectedCSIKey, csiLimitKeyLonger) - } -} - -func getDriverHash(driverName string) string { - charsFromDriverName := driverName[:23] - hash := sha1.New() - hash.Write([]byte(driverName)) - hashed := hex.EncodeToString(hash.Sum(nil)) - hashed = hashed[:16] - return CSIAttachLimitPrefix + charsFromDriverName + hashed -} diff --git a/pkg/volume/util/device_util_linux.go b/pkg/volume/util/device_util_linux.go index 18cbec072f420..b9e5be201f5d7 100644 --- a/pkg/volume/util/device_util_linux.go +++ b/pkg/volume/util/device_util_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/volume/util/device_util_linux_test.go b/pkg/volume/util/device_util_linux_test.go index bb3d113f1a550..982b6b06acc0e 100644 --- a/pkg/volume/util/device_util_linux_test.go +++ b/pkg/volume/util/device_util_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/volume/util/device_util_unsupported.go b/pkg/volume/util/device_util_unsupported.go index ea4786aff6f43..cbbf035e85b51 100644 --- a/pkg/volume/util/device_util_unsupported.go +++ b/pkg/volume/util/device_util_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/volume/util/fs/fs.go b/pkg/volume/util/fs/fs.go index 64301fd5de16a..fea83301b4c53 100644 --- a/pkg/volume/util/fs/fs.go +++ b/pkg/volume/util/fs/fs.go @@ -1,5 +1,4 @@ //go:build linux || darwin -// +build linux darwin /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/fs/fs_unsupported.go b/pkg/volume/util/fs/fs_unsupported.go index f66df0d0666f7..82065e316018a 100644 --- a/pkg/volume/util/fs/fs_unsupported.go +++ b/pkg/volume/util/fs/fs_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !darwin && !windows -// +build !linux,!darwin,!windows /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/fs/fs_windows.go b/pkg/volume/util/fs/fs_windows.go index 3e654d3fd3d3a..9afe97e579060 100644 --- a/pkg/volume/util/fs/fs_windows.go +++ b/pkg/volume/util/fs/fs_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/fsquota/common/quota_common_linux.go b/pkg/volume/util/fsquota/common/quota_common_linux.go index 77f845837b7dd..d0ef4b26a2a36 100644 --- a/pkg/volume/util/fsquota/common/quota_common_linux.go +++ b/pkg/volume/util/fsquota/common/quota_common_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/fsquota/common/quota_common_linux_impl.go b/pkg/volume/util/fsquota/common/quota_common_linux_impl.go index a995966239309..f50c2777c0771 100644 --- a/pkg/volume/util/fsquota/common/quota_common_linux_impl.go +++ b/pkg/volume/util/fsquota/common/quota_common_linux_impl.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/fsquota/project.go b/pkg/volume/util/fsquota/project.go index 16d25d6c3f57c..e1b2f0a96ae98 100644 --- a/pkg/volume/util/fsquota/project.go +++ b/pkg/volume/util/fsquota/project.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/fsquota/quota_linux.go b/pkg/volume/util/fsquota/quota_linux.go index 6ddf4d4d50faa..da93b5d2e9a5e 100644 --- a/pkg/volume/util/fsquota/quota_linux.go +++ b/pkg/volume/util/fsquota/quota_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/fsquota/quota_linux_test.go b/pkg/volume/util/fsquota/quota_linux_test.go index 30c8ede4d2389..5989b1428a1b7 100644 --- a/pkg/volume/util/fsquota/quota_linux_test.go +++ b/pkg/volume/util/fsquota/quota_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/fsquota/quota_unsupported.go b/pkg/volume/util/fsquota/quota_unsupported.go index 31d3e4f655e49..dcebb4faa4d06 100644 --- a/pkg/volume/util/fsquota/quota_unsupported.go +++ b/pkg/volume/util/fsquota/quota_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/hostutil/hostutil_linux.go b/pkg/volume/util/hostutil/hostutil_linux.go index 19bcc5f14e109..5444257f8130f 100644 --- a/pkg/volume/util/hostutil/hostutil_linux.go +++ b/pkg/volume/util/hostutil/hostutil_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. @@ -24,6 +23,7 @@ import ( "os" "path" "path/filepath" + "slices" "strings" "syscall" @@ -245,17 +245,7 @@ func GetSELinux(path string, mountInfoFilename string, selinuxEnabled seLinuxEna } // "seclabel" can be both in mount options and super options. - for _, opt := range info.SuperOptions { - if opt == "seclabel" { - return true, nil - } - } - for _, opt := range info.MountOptions { - if opt == "seclabel" { - return true, nil - } - } - return false, nil + return slices.Contains(info.SuperOptions, "seclabel") || slices.Contains(info.MountOptions, "seclabel"), nil } // GetSELinuxSupport returns true if given path is on a mount that supports diff --git a/pkg/volume/util/hostutil/hostutil_linux_test.go b/pkg/volume/util/hostutil/hostutil_linux_test.go index c720f32900d87..ab9a6736fdb81 100644 --- a/pkg/volume/util/hostutil/hostutil_linux_test.go +++ b/pkg/volume/util/hostutil/hostutil_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/hostutil/hostutil_unsupported.go b/pkg/volume/util/hostutil/hostutil_unsupported.go index c5ff9c0b5e1e4..2fd765938a25f 100644 --- a/pkg/volume/util/hostutil/hostutil_unsupported.go +++ b/pkg/volume/util/hostutil/hostutil_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/hostutil/hostutil_windows.go b/pkg/volume/util/hostutil/hostutil_windows.go index b702d1e7e9bce..fcaebdf7d151a 100644 --- a/pkg/volume/util/hostutil/hostutil_windows.go +++ b/pkg/volume/util/hostutil/hostutil_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/volume/util/operationexecutor/node_expander.go b/pkg/volume/util/operationexecutor/node_expander.go index 780f5563f3ba6..3e29c95448ad6 100644 --- a/pkg/volume/util/operationexecutor/node_expander.go +++ b/pkg/volume/util/operationexecutor/node_expander.go @@ -209,8 +209,8 @@ func (ne *NodeExpander) expandOnPlugin() (bool, resource.Quantity, error) { return false, ne.pluginResizeOpts.OldSize, resizeErr } simpleMsg, detailedMsg := ne.vmt.GenerateMsg("MountVolume.NodeExpandVolume succeeded", nodeName) - ne.recorder.Eventf(ne.vmt.Pod, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, simpleMsg) - ne.recorder.Eventf(ne.pvc, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, simpleMsg) + ne.recorder.Eventf(ne.vmt.Pod, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, "%s", simpleMsg) + ne.recorder.Eventf(ne.pvc, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, "%s", simpleMsg) klog.InfoS(detailedMsg, "pod", klog.KObj(ne.vmt.Pod)) ne.testStatus = testResponseData{true /*resizeCalledOnPlugin */, true /* assumeResizeFinished */} diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index 9d5c937d95372..cf16f99688f2a 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "time" @@ -290,29 +291,29 @@ func (og *operationGenerator) GenerateAttachVolumeFunc( return volumetypes.NewOperationContext(eventErr, detailedErr, migrated) } - // Successful attach event is useful for user debugging - simpleMsg, _ := volumeToAttach.GenerateMsg("AttachVolume.Attach succeeded", "") - for _, pod := range volumeToAttach.ScheduledPods { - og.recorder.Eventf(pod, v1.EventTypeNormal, kevents.SuccessfulAttachVolume, simpleMsg) - } - klog.Info(volumeToAttach.GenerateMsgDetailed("AttachVolume.Attach succeeded", "")) - // Update actual state of world addVolumeNodeErr := actualStateOfWorld.MarkVolumeAsAttached( - logger, v1.UniqueVolumeName(""), volumeToAttach.VolumeSpec, volumeToAttach.NodeName, devicePath) + logger, volumeToAttach.VolumeName, volumeToAttach.VolumeSpec, volumeToAttach.NodeName, devicePath) if addVolumeNodeErr != nil { // On failure, return error. Caller will log and retry. eventErr, detailedErr := volumeToAttach.GenerateError("AttachVolume.MarkVolumeAsAttached failed", addVolumeNodeErr) return volumetypes.NewOperationContext(eventErr, detailedErr, migrated) } + // Successful attach event is useful for user debugging + simpleMsg, _ := volumeToAttach.GenerateMsg("AttachVolume.Attach succeeded", "") + for _, pod := range volumeToAttach.ScheduledPods { + og.recorder.Eventf(pod, v1.EventTypeNormal, kevents.SuccessfulAttachVolume, "%s", simpleMsg) + } + klog.Info(volumeToAttach.GenerateMsgDetailed("AttachVolume.Attach succeeded", "")) + return volumetypes.NewOperationContext(nil, nil, migrated) } eventRecorderFunc := func(err *error) { if *err != nil { for _, pod := range volumeToAttach.ScheduledPods { - og.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, (*err).Error()) + og.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, "%s", (*err).Error()) } } } @@ -586,6 +587,8 @@ func (og *operationGenerator) GenerateMountVolumeFunc( FSGroupChangePolicy: fsGroupChangePolicy, Recorder: og.recorder, SELinuxLabel: volumeToMount.SELinuxLabel, + ReconstructedVolume: actualStateOfWorld.IsVolumeReconstructed(volumeToMount.VolumeName, volumeToMount.PodName), + IsRemount: isRemount, }) // Update actual state of world markOpts := MarkVolumeOpts{ @@ -646,7 +649,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( eventRecorderFunc := func(err *error) { if *err != nil { - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, (*err).Error()) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, "%s", (*err).Error()) } } @@ -666,7 +669,7 @@ func (og *operationGenerator) checkForFailedMount(volumeToMount VolumeToMount, m if volumetypes.IsFilesystemMismatchError(mountError) { simpleMsg, _ := volumeToMount.GenerateMsg("MountVolume failed", mountError.Error()) - og.recorder.Eventf(pv, v1.EventTypeWarning, kevents.FailedMountOnFilesystemMismatch, simpleMsg) + og.recorder.Eventf(pv, v1.EventTypeWarning, kevents.FailedMountOnFilesystemMismatch, "%s", simpleMsg) } } @@ -939,7 +942,7 @@ func (og *operationGenerator) GenerateMapVolumeFunc( affinityErr := checkNodeAffinity(og, volumeToMount) if affinityErr != nil { eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.NodeAffinity check failed", affinityErr) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, "%s", eventErr.Error()) return volumetypes.GeneratedOperations{}, detailedErr } blockVolumeMapper, newMapperErr := blockVolumePlugin.NewBlockVolumeMapper( @@ -947,7 +950,7 @@ func (og *operationGenerator) GenerateMapVolumeFunc( volumeToMount.Pod) if newMapperErr != nil { eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.NewBlockVolumeMapper initialization failed", newMapperErr) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMapVolume, eventErr.Error()) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMapVolume, "%s", eventErr.Error()) return volumetypes.GeneratedOperations{}, detailedErr } @@ -1102,13 +1105,13 @@ func (og *operationGenerator) GenerateMapVolumeFunc( // Device mapping for global map path succeeded simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MapVolume.MapPodDevice succeeded", fmt.Sprintf("globalMapPath %q", globalMapPath)) verbosity := klog.Level(4) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, simpleMsg) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, "%s", simpleMsg) klog.V(verbosity).InfoS(detailedMsg, "pod", klog.KObj(volumeToMount.Pod)) // Device mapping for pod device map path succeeded simpleMsg, detailedMsg = volumeToMount.GenerateMsg("MapVolume.MapPodDevice succeeded", fmt.Sprintf("volumeMapPath %q", volumeMapPath)) verbosity = klog.Level(1) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, simpleMsg) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, "%s", simpleMsg) klog.V(verbosity).InfoS(detailedMsg, "pod", klog.KObj(volumeToMount.Pod)) resizeOptions := volume.NodeResizeOptions{ @@ -1142,7 +1145,7 @@ func (og *operationGenerator) GenerateMapVolumeFunc( eventRecorderFunc := func(err *error) { if *err != nil { - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMapVolume, (*err).Error()) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMapVolume, "%s", (*err).Error()) } } @@ -1499,12 +1502,10 @@ func (og *operationGenerator) verifyVolumeIsSafeToDetach( return volumeToDetach.GenerateErrorDetailed("DetachVolume failed fetching node from API server", fetchErr) } - for _, inUseVolume := range node.Status.VolumesInUse { - if inUseVolume == volumeToDetach.VolumeName { - return volumeToDetach.GenerateErrorDetailed( - "DetachVolume failed", - fmt.Errorf("volume is still in use by node, according to Node status")) - } + if slices.Contains(node.Status.VolumesInUse, volumeToDetach.VolumeName) { + return volumeToDetach.GenerateErrorDetailed( + "DetachVolume failed", + fmt.Errorf("volume is still in use by node, according to Node status")) } // Volume is not marked as in use by node @@ -1570,7 +1571,7 @@ func (og *operationGenerator) GenerateExpandVolumeFunc( return volumetypes.NewOperationContext(detailedErr, detailedErr, migrated) } successMsg := fmt.Sprintf("ExpandVolume succeeded for volume %s", util.GetPersistentVolumeClaimQualifiedName(pvc)) - og.recorder.Eventf(pvc, v1.EventTypeNormal, kevents.VolumeResizeSuccess, successMsg) + og.recorder.Eventf(pvc, v1.EventTypeNormal, kevents.VolumeResizeSuccess, "%s", successMsg) } else { _, err := util.MarkForFSResize(pvc, og.kubeClient) if err != nil { @@ -1592,7 +1593,7 @@ func (og *operationGenerator) GenerateExpandVolumeFunc( eventRecorderFunc := func(err *error) { if *err != nil { - og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.VolumeResizeFailed, (*err).Error()) + og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.VolumeResizeFailed, "%s", (*err).Error()) } } @@ -1637,7 +1638,7 @@ func (og *operationGenerator) GenerateExpandAndRecoverVolumeFunc( eventRecorderFunc := func(err *error) { if *err != nil { - og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.VolumeResizeFailed, (*err).Error()) + og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.VolumeResizeFailed, "%s", (*err).Error()) } } @@ -1779,7 +1780,7 @@ func (og *operationGenerator) expandAndRecoverFunction(resizeOpts inTreeResizeOp } resizeResponse.pvc = pvc successMsg := fmt.Sprintf("ExpandVolume succeeded for volume %s", util.GetPersistentVolumeClaimQualifiedName(pvc)) - og.recorder.Eventf(pvc, v1.EventTypeNormal, kevents.VolumeResizeSuccess, successMsg) + og.recorder.Eventf(pvc, v1.EventTypeNormal, kevents.VolumeResizeSuccess, "%s", successMsg) } else { pvc, err = og.markForPendingNodeExpansion(pvc, pv) resizeResponse.pvc = pvc @@ -1911,7 +1912,7 @@ func (og *operationGenerator) GenerateExpandInUseVolumeFunc( eventRecorderFunc := func(err *error) { if *err != nil { - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.VolumeResizeFailed, (*err).Error()) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.VolumeResizeFailed, "%s", (*err).Error()) } } @@ -1962,8 +1963,8 @@ func (og *operationGenerator) expandVolumeDuringMount(volumeToMount VolumeToMoun if volumeToMount.VolumeSpec.ReadOnly { simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.NodeExpandVolume failed", "requested read-only file system") klog.Warning(detailedMsg) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FileSystemResizeFailed, simpleMsg) - og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.FileSystemResizeFailed, simpleMsg) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FileSystemResizeFailed, "%s", simpleMsg) + og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.FileSystemResizeFailed, "%s", simpleMsg) return true, nil } @@ -2032,8 +2033,8 @@ func (og *operationGenerator) nodeExpandVolume( if volumeToMount.VolumeSpec.ReadOnly { simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.NodeExpandVolume failed", "requested read-only file system") klog.Warning(detailedMsg) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FileSystemResizeFailed, simpleMsg) - og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.FileSystemResizeFailed, simpleMsg) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FileSystemResizeFailed, "%s", simpleMsg) + og.recorder.Eventf(pvc, v1.EventTypeWarning, kevents.FileSystemResizeFailed, "%s", simpleMsg) return true, *currentSize, nil } resizeOp := nodeResizeOperationOpts{ @@ -2130,8 +2131,8 @@ func (og *operationGenerator) legacyCallNodeExpandOnPlugin(resizeOp nodeResizeOp } simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.NodeExpandVolume succeeded", nodeName) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, simpleMsg) - og.recorder.Eventf(pvc, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, simpleMsg) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, "%s", simpleMsg) + og.recorder.Eventf(pvc, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, "%s", simpleMsg) klog.InfoS(detailedMsg, "pod", klog.KObj(volumeToMount.Pod)) // if PVC already has new size, there is no need to update it. diff --git a/pkg/volume/util/selinux_test.go b/pkg/volume/util/selinux_test.go index 6e744e8a62abb..c036f29c9903c 100644 --- a/pkg/volume/util/selinux_test.go +++ b/pkg/volume/util/selinux_test.go @@ -21,6 +21,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -301,10 +302,13 @@ func TestGetMountSELinuxLabel(t *testing.T) { }, } + // Unlock GA feature gates (SELinuxChangePolicy, SELinuxMount) so they can be disabled in tests + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Arrange - // Set feature gates for the test. *Disable* those that are not in tt.featureGates. + // Set feature gates for the test. allGates := []featuregate.Feature{features.SELinuxChangePolicy, features.SELinuxMount} enabledGates := sets.New(tt.featureGates...) for _, fg := range allGates { diff --git a/pkg/volume/util/subpath/subpath_linux.go b/pkg/volume/util/subpath/subpath_linux.go index f1958cd6050d0..0ed994d126622 100644 --- a/pkg/volume/util/subpath/subpath_linux.go +++ b/pkg/volume/util/subpath/subpath_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/subpath/subpath_linux_test.go b/pkg/volume/util/subpath/subpath_linux_test.go index 987d6967d6620..f07aba319c91a 100644 --- a/pkg/volume/util/subpath/subpath_linux_test.go +++ b/pkg/volume/util/subpath/subpath_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/subpath/subpath_unsupported.go b/pkg/volume/util/subpath/subpath_unsupported.go index 1771bc16b8e24..b294ffdfd6d56 100644 --- a/pkg/volume/util/subpath/subpath_unsupported.go +++ b/pkg/volume/util/subpath/subpath_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux && !windows -// +build !linux,!windows /* Copyright 2014 The Kubernetes Authors. diff --git a/pkg/volume/util/subpath/subpath_windows.go b/pkg/volume/util/subpath/subpath_windows.go index 14b52a365fcd6..06ff952d99c06 100644 --- a/pkg/volume/util/subpath/subpath_windows.go +++ b/pkg/volume/util/subpath/subpath_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/volume/util/subpath/subpath_windows_test.go b/pkg/volume/util/subpath/subpath_windows_test.go index 682fcd4f59da4..050143d96e774 100644 --- a/pkg/volume/util/subpath/subpath_windows_test.go +++ b/pkg/volume/util/subpath/subpath_windows_test.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index 29cc74923549e..f9c07f8c670fc 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -22,6 +22,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "strings" "time" @@ -220,12 +221,7 @@ func JoinMountOptions(userOptions []string, systemOptions []string) []string { // ContainsAccessMode returns whether the requested mode is contained by modes func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false + return slices.Contains(modes, mode) } // ContainsAllAccessModes returns whether all of the requested modes are contained by modes diff --git a/pkg/volume/util/util_test.go b/pkg/volume/util/util_test.go index e0c1a153da378..6a9ebb0efd1ba 100644 --- a/pkg/volume/util/util_test.go +++ b/pkg/volume/util/util_test.go @@ -20,6 +20,7 @@ import ( "os" "reflect" "runtime" + "slices" "testing" v1 "k8s.io/api/core/v1" @@ -30,7 +31,6 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/util/slice" "k8s.io/kubernetes/pkg/volume" "k8s.io/utils/ptr" ) @@ -386,7 +386,9 @@ func TestMountOptionFromSpec(t *testing.T) { for name, scenario := range scenarios { mountOptions := MountOptionFromSpec(scenario.volume, scenario.systemOptions...) - if !reflect.DeepEqual(slice.SortStrings(mountOptions), slice.SortStrings(scenario.expectedMountList)) { + slices.Sort(mountOptions) + slices.Sort(scenario.expectedMountList) + if !reflect.DeepEqual(mountOptions, scenario.expectedMountList) { t.Errorf("for %s expected mount options : %v got %v", name, scenario.expectedMountList, mountOptions) } } diff --git a/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go b/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go index 541d0b65d354f..b1256a2152c1d 100644 --- a/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go +++ b/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/util/volumepathhandler/volume_path_handler_unsupported.go b/pkg/volume/util/volumepathhandler/volume_path_handler_unsupported.go index 66fb2fe1f42aa..ec78ef8da10c3 100644 --- a/pkg/volume/util/volumepathhandler/volume_path_handler_unsupported.go +++ b/pkg/volume/util/volumepathhandler/volume_path_handler_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2018 The Kubernetes Authors. diff --git a/pkg/volume/validation/pv_validation.go b/pkg/volume/validation/pv_validation.go index 23fa51fdac98d..8c0391cc49b64 100644 --- a/pkg/volume/validation/pv_validation.go +++ b/pkg/volume/validation/pv_validation.go @@ -19,6 +19,7 @@ package validation import ( "errors" "path/filepath" + "slices" "strings" "k8s.io/apimachinery/pkg/util/validation/field" @@ -61,10 +62,8 @@ func checkMountOption(pv *api.PersistentVolume) field.ErrorList { // ValidatePathNoBacksteps will make sure the targetPath does not have any element which is ".." func ValidatePathNoBacksteps(targetPath string) error { parts := strings.Split(filepath.ToSlash(targetPath), "/") - for _, item := range parts { - if item == ".." { - return errors.New("must not contain '..'") - } + if slices.Contains(parts, "..") { + return errors.New("must not contain '..'") } return nil diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 4a6fc55e29bb7..4a166f000f21f 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -138,6 +138,17 @@ type MounterArgs struct { // Optional interface that will be used to change the ownership of the volume, if specified. // mainly used by unit tests VolumeOwnershipApplicator VolumeOwnershipChanger + ReconstructedVolume bool + + // IsRemount is true when SetUp is being invoked on a volume that the + // reconciler considers already mounted to the pod, e.g. a periodic + // republish triggered by CSIDriver.spec.requiresRepublish=true. Volume + // plugins should use this to avoid destroying state (e.g. mount + // directories, volume metadata files) that the pod is currently + // observing through an existing bind mount, since teardown on a + // failed remount cannot be repaired by a subsequent successful + // remount and would leave the pod with stale contents. + IsRemount bool } type VolumeOwnershipChanger interface { diff --git a/pkg/volume/volume_linux.go b/pkg/volume/volume_linux.go index c3caf8c66c6d5..48e6a8f385e28 100644 --- a/pkg/volume/volume_linux.go +++ b/pkg/volume/volume_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/volume/volume_linux_test.go b/pkg/volume/volume_linux_test.go index c133b69bcc1bb..bf1d29041140b 100644 --- a/pkg/volume/volume_linux_test.go +++ b/pkg/volume/volume_linux_test.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux /* Copyright 2020 The Kubernetes Authors. diff --git a/pkg/volume/volume_unsupported.go b/pkg/volume/volume_unsupported.go index ef46dac618dd7..e73bb4a14aace 100644 --- a/pkg/volume/volume_unsupported.go +++ b/pkg/volume/volume_unsupported.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/windows/service/service.go b/pkg/windows/service/service.go index 15e004a1fae5f..818a736a0bc7d 100644 --- a/pkg/windows/service/service.go +++ b/pkg/windows/service/service.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2018 The Kubernetes Authors. diff --git a/plugin/pkg/admission/job/admission.go b/plugin/pkg/admission/job/admission.go new file mode 100644 index 0000000000000..ed93099b1e6d4 --- /dev/null +++ b/plugin/pkg/admission/job/admission.go @@ -0,0 +1,162 @@ +/* +Copyright 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 job + +import ( + "context" + "fmt" + "io" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apiserver/pkg/admission" + genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/client-go/informers" + schedulingv1alpha2listers "k8s.io/client-go/listers/scheduling/v1alpha2" + "k8s.io/component-base/featuregate" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/features" + "k8s.io/utils/ptr" +) + +const ( + // PluginName is the name of the admission plugin. + PluginName = "JobValidation" +) + +// Register registers the plugin. +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return NewPlugin(), nil + }) +} + +var _ admission.Interface = &Plugin{} +var _ admission.ValidationInterface = &Plugin{} +var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{}) +var _ genericadmissioninitializer.WantsFeatures = &Plugin{} + +// Plugin is an admission controller that validates Job updates +// against gang-scheduled PodGroups. +type Plugin struct { + *admission.Handler + genericWorkloadEnabled bool + workloadWithJobEnabled bool + inspectedFeatureGates bool + pgLister schedulingv1alpha2listers.PodGroupLister +} + +// NewPlugin creates a new JobValidation admission plugin. +func NewPlugin() *Plugin { + return &Plugin{ + Handler: admission.NewHandler(admission.Update), + } +} + +func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { + p.genericWorkloadEnabled = featureGates.Enabled(features.GenericWorkload) + p.workloadWithJobEnabled = featureGates.Enabled(features.WorkloadWithJob) + p.inspectedFeatureGates = true +} + +func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { + if !p.genericWorkloadEnabled { + return + } + pgInformer := f.Scheduling().V1alpha2().PodGroups() + p.pgLister = pgInformer.Lister() + p.SetReadyFunc(pgInformer.Informer().HasSynced) +} + +// ValidateInitialization ensures the lister is set when the feature gate is enabled. +func (p *Plugin) ValidateInitialization() error { + if !p.inspectedFeatureGates { + return fmt.Errorf("%s has not inspected feature gates", PluginName) + } + if p.genericWorkloadEnabled && p.pgLister == nil { + return fmt.Errorf("missing PodGroup lister") + } + return nil +} + +// Validate performs admission checks on Job updates that require +// cross-referencing other API objects. +func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + if a.GetResource().GroupResource() != batch.Resource("jobs") { + return nil + } + if a.GetSubresource() != "" { + return nil + } + + job, ok := a.GetObject().(*batch.Job) + if !ok { + return nil + } + oldJob, ok := a.GetOldObject().(*batch.Job) + if !ok { + return nil + } + + if err := p.validateParallelismChange(a, job, oldJob); err != nil { + return err + } + + return nil +} + +// validateParallelismChange rejects parallelism changes on Jobs whose +// PodGroup uses gang scheduling. +func (p *Plugin) validateParallelismChange(a admission.Attributes, job, oldJob *batch.Job) error { + if !p.genericWorkloadEnabled && !p.workloadWithJobEnabled { + return nil + } + if ptr.Equal(job.Spec.Parallelism, oldJob.Spec.Parallelism) { + return nil + } + + // When SchedulingGroup is set in the template, look up that PodGroup directly. + sg := oldJob.Spec.Template.Spec.SchedulingGroup + if sg != nil && sg.PodGroupName != nil { + pg, err := p.pgLister.PodGroups(oldJob.Namespace).Get(*sg.PodGroupName) + if err != nil { + return nil + } + if pg.Spec.SchedulingPolicy.Gang != nil { + return admission.NewForbidden(a, fmt.Errorf( + "cannot change parallelism for a Job referencing gang-scheduled PodGroup %q", pg.Name)) + } + return nil + } + + // When SchedulingGroup is not in the template, scan PodGroups in the namespace owned by this Job. + pgs, err := p.pgLister.PodGroups(oldJob.Namespace).List(labels.Everything()) + if err != nil { + return nil + } + for _, pg := range pgs { + if !metav1.IsControlledBy(pg, oldJob) { + continue + } + if pg.Spec.SchedulingPolicy.Gang != nil { + return admission.NewForbidden(a, fmt.Errorf( + "cannot change parallelism for a Job referencing gang-scheduled PodGroup %q", pg.Name)) + } + } + + return nil +} diff --git a/plugin/pkg/admission/job/admission_test.go b/plugin/pkg/admission/job/admission_test.go new file mode 100644 index 0000000000000..9c93f00cb82c5 --- /dev/null +++ b/plugin/pkg/admission/job/admission_test.go @@ -0,0 +1,282 @@ +/* +Copyright 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 job + +import ( + "testing" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/admission" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/klog/v2/ktesting" + "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/features" + "k8s.io/utils/ptr" +) + +func TestGangSchedulingParallelism(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + + gangPG := &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gang-pg", + Namespace: metav1.NamespaceDefault, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{MinCount: 4}, + }, + }, + } + + basicPG := &schedulingv1alpha2.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic-pg", + Namespace: metav1.NamespaceDefault, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Basic: &schedulingv1alpha2.BasicSchedulingPolicy{}, + }, + }, + } + + indexedMode := batch.IndexedCompletion + + baseJob := func(sgName *string) *batch.Job { + j := &batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: metav1.NamespaceDefault, + UID: types.UID("test-job-uid"), + }, + Spec: batch.JobSpec{ + CompletionMode: &indexedMode, + Completions: ptr.To[int32](4), + Parallelism: ptr.To[int32](4), + }, + } + if sgName != nil { + j.Spec.Template.Spec.SchedulingGroup = &api.PodSchedulingGroup{ + PodGroupName: sgName, + } + } + return j + } + + cases := map[string]struct { + enableFeatureGate bool + oldJob *batch.Job + newJob *batch.Job + podGroups []*schedulingv1alpha2.PodGroup + wantErr bool + }{ + "feature gate disabled: allows parallelism change": { + oldJob: baseJob(ptr.To("gang-pg")), + newJob: func() *batch.Job { + j := baseJob(ptr.To("gang-pg")) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + podGroups: []*schedulingv1alpha2.PodGroup{gangPG}, + }, + "no schedulingGroup: skips check (handled by validation)": { + enableFeatureGate: true, + oldJob: baseJob(nil), + newJob: func() *batch.Job { + j := baseJob(nil) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + }, + "parallelism unchanged: allows": { + enableFeatureGate: true, + oldJob: baseJob(ptr.To("gang-pg")), + newJob: baseJob(ptr.To("gang-pg")), + podGroups: []*schedulingv1alpha2.PodGroup{gangPG}, + }, + "gang PodGroup: rejects parallelism change": { + enableFeatureGate: true, + oldJob: baseJob(ptr.To("gang-pg")), + newJob: func() *batch.Job { + j := baseJob(ptr.To("gang-pg")) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + podGroups: []*schedulingv1alpha2.PodGroup{gangPG}, + wantErr: true, + }, + "basic PodGroup: allows parallelism change": { + enableFeatureGate: true, + oldJob: baseJob(ptr.To("basic-pg")), + newJob: func() *batch.Job { + j := baseJob(ptr.To("basic-pg")) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + podGroups: []*schedulingv1alpha2.PodGroup{basicPG}, + }, + "PodGroup not found: allows parallelism change": { + enableFeatureGate: true, + oldJob: baseJob(ptr.To("missing-pg")), + newJob: func() *batch.Job { + j := baseJob(ptr.To("missing-pg")) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + }, + "controller-created gang PodGroup - rejects parallelism change": { + enableFeatureGate: true, + oldJob: baseJob(nil), + newJob: func() *batch.Job { + j := baseJob(nil) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + podGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "controller-created-pg", + Namespace: metav1.NamespaceDefault, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "batch/v1", + Kind: "Job", + Name: "test-job", + UID: types.UID("test-job-uid"), + Controller: ptr.To(true), + }, + }, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{MinCount: 4}, + }, + }, + }, + }, + wantErr: true, + }, + "controller-created basic PodGroup - allows parallelism change": { + enableFeatureGate: true, + oldJob: baseJob(nil), + newJob: func() *batch.Job { + j := baseJob(nil) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + podGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "controller-created-pg", + Namespace: metav1.NamespaceDefault, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "batch/v1", + Kind: "Job", + Name: "test-job", + UID: types.UID("test-job-uid"), + Controller: ptr.To(true), + }, + }, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Basic: &schedulingv1alpha2.BasicSchedulingPolicy{}, + }, + }, + }, + }, + }, + "gang PodGroup owned by different Job - allows parallelism change": { + enableFeatureGate: true, + oldJob: baseJob(nil), + newJob: func() *batch.Job { + j := baseJob(nil) + j.Spec.Parallelism = ptr.To[int32](2) + return j + }(), + podGroups: []*schedulingv1alpha2.PodGroup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "other-gang-pg", + Namespace: metav1.NamespaceDefault, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "batch/v1", + Kind: "Job", + Name: "other-job", + UID: types.UID("other-job-uid"), + Controller: ptr.To(true), + }, + }, + }, + Spec: schedulingv1alpha2.PodGroupSpec{ + SchedulingPolicy: schedulingv1alpha2.PodGroupSchedulingPolicy{ + Gang: &schedulingv1alpha2.GangSchedulingPolicy{MinCount: 4}, + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, + utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: tc.enableFeatureGate, + features.WorkloadWithJob: tc.enableFeatureGate, + }) + + p := NewPlugin() + p.InspectFeatureGates(utilfeature.DefaultFeatureGate) + + informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) + p.SetExternalKubeInformerFactory(informerFactory) + + for _, pg := range tc.podGroups { + if err := informerFactory.Scheduling().V1alpha2().PodGroups().Informer().GetStore().Add(pg); err != nil { + t.Fatalf("failed to add PodGroup: %v", err) + } + } + + attrs := admission.NewAttributesRecord( + tc.newJob, tc.oldJob, + schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"}, + metav1.NamespaceDefault, "test-job", + batch.Resource("jobs").WithVersion("v1"), + "", admission.Update, &metav1.UpdateOptions{}, false, nil, + ) + + err := p.Validate(ctx, attrs, nil) + if tc.wantErr && err == nil { + t.Error("expected error, got nil") + } + if !tc.wantErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} diff --git a/plugin/pkg/admission/nodedeclaredfeatures/OWNERS b/plugin/pkg/admission/nodedeclaredfeatures/OWNERS new file mode 100644 index 0000000000000..65e322510c3fc --- /dev/null +++ b/plugin/pkg/admission/nodedeclaredfeatures/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - sig-node-approvers +reviewers: + - sig-node-reviewers +labels: + - sig/node diff --git a/plugin/pkg/admission/nodedeclaredfeatures/admission.go b/plugin/pkg/admission/nodedeclaredfeatures/admission.go index a05a67e6f06dd..884307bab10a7 100644 --- a/plugin/pkg/admission/nodedeclaredfeatures/admission.go +++ b/plugin/pkg/admission/nodedeclaredfeatures/admission.go @@ -32,7 +32,6 @@ import ( "k8s.io/component-base/featuregate" "k8s.io/component-base/version" ndf "k8s.io/component-helpers/nodedeclaredfeatures" - ndffeatures "k8s.io/component-helpers/nodedeclaredfeatures/features" "k8s.io/kubernetes/pkg/apis/core" v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/features" @@ -65,18 +64,13 @@ var _ genericadmissioninitializer.WantsFeatures = &Plugin{} // New creates a new Plugin admission plugin func NewPlugin() (*Plugin, error) { - framework, err := ndf.New(ndffeatures.AllFeatures) - if err != nil { - return nil, fmt.Errorf("failed to create node declared features helper: %w", err) - } - ver, err := versionutil.Parse(version.Get().String()) if err != nil { return nil, fmt.Errorf("failed to parse version: %w", err) } return &Plugin{ Handler: admission.NewHandler(admission.Update), - nodeDeclaredFeatureFramework: framework, + nodeDeclaredFeatureFramework: ndf.DefaultFramework, version: ver, }, nil } @@ -100,11 +94,7 @@ func (p *Plugin) ValidateInitialization() error { } if p.nodeDeclaredFeatureFramework == nil { - framework, err := ndf.New(ndffeatures.AllFeatures) - if err != nil { - return fmt.Errorf("failed to create node feature helper for %s: %w", PluginName, err) - } - p.nodeDeclaredFeatureFramework = framework + p.nodeDeclaredFeatureFramework = ndf.DefaultFramework } return nil } @@ -173,7 +163,7 @@ func (p *Plugin) validatePodUpdate(pod, oldPod *core.Pod, a admission.Attributes return admission.NewForbidden(a, fmt.Errorf("failed to infer pod capability requirements: %w", err)) } // If there are no specific feature requirements for this update, we're done. - if reqs.Len() == 0 { + if reqs.IsEmpty() { return nil } node, err := p.nodeLister.Get(pod.Spec.NodeName) @@ -183,7 +173,7 @@ func (p *Plugin) validatePodUpdate(pod, oldPod *core.Pod, a admission.Attributes } return admission.NewForbidden(a, fmt.Errorf("failed to get node %q: %w", pod.Spec.NodeName, err)) } - result, err := ndf.MatchNode(reqs, node) + result, err := p.nodeDeclaredFeatureFramework.MatchNode(reqs, node) if err != nil { return admission.NewForbidden(a, fmt.Errorf("failed to match pod requirements against node %q: %w", node.Name, err)) } diff --git a/plugin/pkg/admission/nodedeclaredfeatures/admission_test.go b/plugin/pkg/admission/nodedeclaredfeatures/admission_test.go index be4fe78f8e51b..f2eb5732fdd10 100644 --- a/plugin/pkg/admission/nodedeclaredfeatures/admission_test.go +++ b/plugin/pkg/admission/nodedeclaredfeatures/admission_test.go @@ -21,7 +21,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" @@ -85,13 +84,12 @@ func TestAdmission(t *testing.T) { createMockFeature := func(t *testing.T, name string, inferForUpdate bool, maxVersionStr string) *ndftesting.MockFeature { m := ndftesting.NewMockFeature(t) - m.EXPECT().Name().Return(name).Maybe() - m.EXPECT().InferForUpdate(mock.Anything, mock.Anything).Return(inferForUpdate).Maybe() + m.SetName(name) + m.SetInferForUpdate(func(_, _ *ndf.PodInfo) bool { return inferForUpdate }) if maxVersionStr != "" { - minVersion := version.MustParseSemantic(maxVersionStr) - m.EXPECT().MaxVersion().Return(minVersion).Maybe() + m.SetMaxVersion(version.MustParseSemantic(maxVersionStr)) } else { - m.EXPECT().MaxVersion().Return(nil).Maybe() + m.SetMaxVersion(nil) } return m } @@ -244,8 +242,7 @@ func TestAdmission(t *testing.T) { require.NoError(t, err) if tc.featureGateEnabled { - framework, err := ndf.New(tc.registeredFeatures) - require.NoError(t, err) + framework := ndf.New(tc.registeredFeatures) target.nodeDeclaredFeatureFramework = framework target.version = version.MustParseSemantic(tc.componentVersion) } diff --git a/plugin/pkg/admission/podgroup/admission.go b/plugin/pkg/admission/podgroup/admission.go new file mode 100644 index 0000000000000..47c38656de0c5 --- /dev/null +++ b/plugin/pkg/admission/podgroup/admission.go @@ -0,0 +1,161 @@ +/* +Copyright 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 podgroup + +import ( + "context" + "fmt" + "io" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + schedulingv1alpha2listers "k8s.io/client-go/listers/scheduling/v1alpha2" + "k8s.io/component-base/featuregate" + scheduling "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" +) + +// PluginName indicates name of admission plugin. +const PluginName = "PodGroupWorkloadExists" + +// Register registers a plugin. +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return NewPodGroupWorkloadExists(), nil + }) +} + +var _ admission.ValidationInterface = &PodGroupWorkloadExists{} +var _ genericadmissioninitializer.WantsExternalKubeInformerFactory = &PodGroupWorkloadExists{} +var _ genericadmissioninitializer.WantsExternalKubeClientSet = &PodGroupWorkloadExists{} +var _ genericadmissioninitializer.WantsFeatures = &PodGroupWorkloadExists{} + +// PodGroupWorkloadExists is an admission plugin that validates a PodGroup's +// referenced Workload exists and contains the referenced PodGroupTemplate. +type PodGroupWorkloadExists struct { + *admission.Handler + workloadLister schedulingv1alpha2listers.WorkloadLister + client kubernetes.Interface + inspectedFeatureGates bool + genericWorkloadEnabled bool +} + +// NewPodGroupWorkloadExists creates a new PodGroupWorkloadExists admission plugin. +func NewPodGroupWorkloadExists() *PodGroupWorkloadExists { + return &PodGroupWorkloadExists{ + Handler: admission.NewHandler(admission.Create), + } +} + +// InspectFeatureGates captures the feature gate states. +func (p *PodGroupWorkloadExists) InspectFeatureGates(featureGates featuregate.FeatureGate) { + p.genericWorkloadEnabled = featureGates.Enabled(features.GenericWorkload) + p.inspectedFeatureGates = true +} + +// SetExternalKubeClientSet sets the client for the plugin. +func (p *PodGroupWorkloadExists) SetExternalKubeClientSet(client kubernetes.Interface) { + p.client = client +} + +// SetExternalKubeInformerFactory sets the informer factory for the plugin. +func (p *PodGroupWorkloadExists) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { + if !p.genericWorkloadEnabled { + return + } + workloadInformer := f.Scheduling().V1alpha2().Workloads() + p.SetReadyFunc(workloadInformer.Informer().HasSynced) + p.workloadLister = workloadInformer.Lister() +} + +// ValidateInitialization ensures the plugin is properly initialized. +func (p *PodGroupWorkloadExists) ValidateInitialization() error { + if !p.inspectedFeatureGates { + return fmt.Errorf("%s has not inspected feature gates", PluginName) + } + if !p.genericWorkloadEnabled { + return nil + } + if p.workloadLister == nil { + return fmt.Errorf("missing Workload lister") + } + if p.client == nil { + return fmt.Errorf("missing client") + } + return nil +} + +// Validate rejects PodGroup creation if the referenced Workload does not exist +// or does not contain the referenced PodGroupTemplate. +func (p *PodGroupWorkloadExists) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + if !p.genericWorkloadEnabled { + return nil + } + + if a.GetResource().GroupResource() != scheduling.Resource("podgroups") { + return nil + } + if a.GetSubresource() != "" { + return nil + } + + podGroup, ok := a.GetObject().(*scheduling.PodGroup) + if !ok { + return apierrors.NewBadRequest("resource was marked with kind PodGroup but could not be converted") + } + + if podGroup.Spec.PodGroupTemplateRef == nil || podGroup.Spec.PodGroupTemplateRef.Workload == nil { + return nil + } + + if !p.WaitForReady() { + return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) + } + + workloadRef := podGroup.Spec.PodGroupTemplateRef.Workload + workload, err := p.workloadLister.Workloads(a.GetNamespace()).Get(workloadRef.WorkloadName) + if apierrors.IsNotFound(err) { + workload, err = p.client.SchedulingV1alpha2().Workloads( + a.GetNamespace()).Get(ctx, workloadRef.WorkloadName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return admission.NewForbidden(a, + fmt.Errorf("PodGroup rejected: Workload %q not found", workloadRef.WorkloadName)) + } + } + if err != nil { + return apierrors.NewInternalError(err) + } + + if workload.DeletionTimestamp != nil { + return admission.NewForbidden(a, fmt.Errorf("PodGroup rejected: Workload %q is being deleted", workloadRef.WorkloadName)) + } + + for _, tmpl := range workload.Spec.PodGroupTemplates { + if tmpl.Name == workloadRef.PodGroupTemplateName { + return nil + } + } + + return admission.NewForbidden(a, fmt.Errorf( + "PodGroup rejected: PodGroupTemplate %q not found in Workload %q", + workloadRef.PodGroupTemplateName, workloadRef.WorkloadName, + )) +} diff --git a/plugin/pkg/admission/podgroup/admission_test.go b/plugin/pkg/admission/podgroup/admission_test.go new file mode 100644 index 0000000000000..6631bfe1a727e --- /dev/null +++ b/plugin/pkg/admission/podgroup/admission_test.go @@ -0,0 +1,201 @@ +/* +Copyright 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 podgroup + +import ( + "testing" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/user" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/klog/v2/ktesting" + scheduling "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/features" +) + +const ( + testNamespace = "test" + testWorkloadName = "my-workload" + testTemplateName = "worker" +) + +func newPodGroup(mutators ...func(*scheduling.PodGroup)) admission.Attributes { + pg := &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: testNamespace}, + Spec: scheduling.PodGroupSpec{ + PodGroupTemplateRef: &scheduling.PodGroupTemplateReference{ + Workload: &scheduling.WorkloadPodGroupTemplateReference{ + WorkloadName: testWorkloadName, + PodGroupTemplateName: testTemplateName, + }, + }, + }, + } + for _, m := range mutators { + m(pg) + } + return admission.NewAttributesRecord( + pg, nil, + scheduling.Kind("PodGroup").WithVersion("version"), + testNamespace, "pg", + scheduling.Resource("podgroups").WithVersion("version"), + "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{}, + ) +} + +func withWorkloadRef(workloadName, templateName string) func(*scheduling.PodGroup) { + return func(pg *scheduling.PodGroup) { + pg.Spec.PodGroupTemplateRef.Workload.WorkloadName = workloadName + pg.Spec.PodGroupTemplateRef.Workload.PodGroupTemplateName = templateName + } +} + +func newWorkload(name string, templateNames ...string) *schedulingv1alpha2.Workload { + w := &schedulingv1alpha2.Workload{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: testNamespace}, + Spec: schedulingv1alpha2.WorkloadSpec{ + PodGroupTemplates: make([]schedulingv1alpha2.PodGroupTemplate, 0, len(templateNames)), + }, + } + for _, tn := range templateNames { + w.Spec.PodGroupTemplates = append(w.Spec.PodGroupTemplates, schedulingv1alpha2.PodGroupTemplate{Name: tn}) + } + return w +} + +func TestHandles(t *testing.T) { + plugin := NewPodGroupWorkloadExists() + for op, shouldHandle := range map[admission.Operation]bool{ + admission.Create: true, + admission.Update: false, + admission.Delete: false, + admission.Connect: false, + } { + if e, a := shouldHandle, plugin.Handles(op); e != a { + t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a) + } + } +} + +func TestValidate(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + now := metav1.Now() + + deletingWorkload := newWorkload("deleting-workload", testTemplateName) + deletingWorkload.DeletionTimestamp = &now + + workload := newWorkload(testWorkloadName, testTemplateName, "driver") + + cases := map[string]struct { + enableFeatureGate bool + workloads []*schedulingv1alpha2.Workload + attrs admission.Attributes + wantErr bool + }{ + "feature gate disabled, always allow": { + attrs: newPodGroup(withWorkloadRef("non-existent", "worker")), + }, + "no podGroupTemplateRef, allow": { + enableFeatureGate: true, + attrs: newPodGroup(func(pg *scheduling.PodGroup) { + pg.Spec.PodGroupTemplateRef = nil + }), + }, + "nil workload ref, allow": { + enableFeatureGate: true, + attrs: newPodGroup(func(pg *scheduling.PodGroup) { + pg.Spec.PodGroupTemplateRef = &scheduling.PodGroupTemplateReference{} + }), + }, + "workload found, template exists": { + enableFeatureGate: true, + workloads: []*schedulingv1alpha2.Workload{workload}, + attrs: newPodGroup(), + }, + "workload not found": { + enableFeatureGate: true, + attrs: newPodGroup(withWorkloadRef("non-existent", testTemplateName)), + wantErr: true, + }, + "workload being deleted": { + enableFeatureGate: true, + workloads: []*schedulingv1alpha2.Workload{deletingWorkload}, + attrs: newPodGroup(withWorkloadRef("deleting-workload", testTemplateName)), + wantErr: true, + }, + "workload found but template name does not match": { + enableFeatureGate: true, + workloads: []*schedulingv1alpha2.Workload{workload}, + attrs: newPodGroup(withWorkloadRef(testWorkloadName, "non-existent-template")), + wantErr: true, + }, + "ignores non-PodGroup resources": { + enableFeatureGate: true, + attrs: admission.NewAttributesRecord( + nil, nil, + scheduling.Kind("Workload").WithVersion("version"), + testNamespace, "w1", + scheduling.Resource("workloads").WithVersion("version"), + "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{}, + ), + }, + "ignores subresources": { + enableFeatureGate: true, + attrs: admission.NewAttributesRecord( + &scheduling.PodGroup{ObjectMeta: metav1.ObjectMeta{Name: "pg", Namespace: testNamespace}}, nil, + scheduling.Kind("PodGroup").WithVersion("version"), + testNamespace, "pg", + scheduling.Resource("podgroups").WithVersion("version"), + "status", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{}, + ), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, + utilfeature.DefaultFeatureGate, features.GenericWorkload, tc.enableFeatureGate) + + plugin := NewPodGroupWorkloadExists() + plugin.InspectFeatureGates(utilfeature.DefaultFeatureGate) + + informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) + plugin.SetExternalKubeInformerFactory(informerFactory) + for _, w := range tc.workloads { + if err := informerFactory.Scheduling().V1alpha2().Workloads().Informer().GetStore().Add(w); err != nil { + t.Fatalf("failed to add Workload: %v", err) + } + } + plugin.SetReadyFunc(func() bool { return true }) + plugin.SetExternalKubeClientSet(fake.NewSimpleClientset()) + + err := plugin.Validate(ctx, tc.attrs, nil) + if tc.wantErr && err == nil { + t.Error("expected error, got nil") + } + if !tc.wantErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} diff --git a/plugin/pkg/admission/podresize/OWNERS b/plugin/pkg/admission/podresize/OWNERS new file mode 100644 index 0000000000000..65e322510c3fc --- /dev/null +++ b/plugin/pkg/admission/podresize/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - sig-node-approvers +reviewers: + - sig-node-reviewers +labels: + - sig/node diff --git a/plugin/pkg/admission/podresize/admission.go b/plugin/pkg/admission/podresize/admission.go new file mode 100644 index 0000000000000..c9f4a0de042cb --- /dev/null +++ b/plugin/pkg/admission/podresize/admission.go @@ -0,0 +1,215 @@ +/* +Copyright 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 podresize + +import ( + "context" + stderrors "errors" + "fmt" + "io" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/client-go/informers" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/component-base/featuregate" + "k8s.io/component-helpers/resource" + "k8s.io/kubernetes/pkg/apis/core" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/features" +) + +const ( + // PluginName is the name of this admission controller plugin. + PluginName = "PodResizeValidator" + + ReasonNodeCapacity metav1.CauseType = "NodeCapacity" + ReasonUnsupportedPlatform metav1.CauseType = "UnsupportedPlatform" +) + +// Register registers a plugin. +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return NewPlugin() + }) +} + +// Plugin is an admission controller that validates pod resize requests against the node's capabilities. +type Plugin struct { + *admission.Handler + nodeLister corev1listers.NodeLister + inPlacePodVerticalScalingEnabled bool +} + +var _ admission.ValidationInterface = &Plugin{} +var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{}) + +// New creates a new ResizeValidator admission plugin +func NewPlugin() (*Plugin, error) { + return &Plugin{ + Handler: admission.NewHandler(admission.Update), + }, nil +} + +// SetExternalKubeInformerFactory sets the informer factory for the plugin. +func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { + nodeInformer := f.Core().V1().Nodes() + p.nodeLister = nodeInformer.Lister() + p.SetReadyFunc(nodeInformer.Informer().HasSynced) +} + +// InspectFeatureGates sets the feature gates for the plugin. +func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { + p.inPlacePodVerticalScalingEnabled = featureGates.Enabled(features.InPlacePodVerticalScaling) +} + +// ValidateInitialization ensures that the plugin is properly initialized. +func (p *Plugin) ValidateInitialization() error { + if p.nodeLister == nil { + return fmt.Errorf("missing node lister for %s", PluginName) + } + + return nil +} + +// Validate is the core of the admission controller logic. +func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + // Ignore if the feature gate is not enabled. + if !p.inPlacePodVerticalScalingEnabled { + return nil + } + + if a.GetOperation() != admission.Update { + return nil + } + + // We only care about Pod updates. + if a.GetResource().GroupResource() != core.Resource("pods") { + return nil + } + + // Only validate updates to the custom "resize" subresource. + if a.GetSubresource() != "resize" { + return nil + } + pod, ok := a.GetObject().(*core.Pod) + if !ok { + return errors.NewBadRequest(fmt.Sprintf("expected a pod but got a %T", a.GetObject())) + } + + // We only care about pods that are already bound to a node. + if len(pod.Spec.NodeName) == 0 { + return nil + } + + oldPod, ok := a.GetOldObject().(*core.Pod) + if !ok { + return errors.NewBadRequest(fmt.Sprintf("expected an old pod but got a %T", a.GetOldObject())) + } + + // Since generation is only incremented when the spec changes, we can skip validation if it doesnt. + if oldPod.Generation == pod.Generation { + return nil + } + + if !p.WaitForReady() { + return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) + } + + return p.validatePodResize(pod, a) +} + +func (p *Plugin) validatePodResize(pod *core.Pod, a admission.Attributes) error { + node, err := p.nodeLister.Get(pod.Spec.NodeName) + if err != nil { + if errors.IsNotFound(err) { + return admission.NewForbidden(a, fmt.Errorf("node %q not found", pod.Spec.NodeName)) + } + return admission.NewForbidden(a, fmt.Errorf("failed to get node %q: %w", pod.Spec.NodeName, err)) + } + + // If the node is not a linux node, reject the update. + if err := validateLinuxNode(node); err != nil { + return resizeValidationError(a, err, ReasonUnsupportedPlatform) + } + + // If the new requests are larger than the node allocatable, reject the update. + if err := validateNodeAllocatable(pod, node); err != nil { + return resizeValidationError(a, err, ReasonNodeCapacity) + } + + return nil +} + +// Returns nil if the pod's resource requests fit within the node's allocatable, err otherwise. +func validateNodeAllocatable(pod *core.Pod, node *corev1.Node) error { + // Convert internal to external pods for the helper library. + podV1 := &corev1.Pod{} + if err := v1.Convert_core_Pod_To_v1_Pod(pod, podV1, nil); err != nil { + return errors.NewBadRequest(fmt.Sprintf("failed to convert pod: %v", err)) + } + return validateRequestsWithinAllocatable(podV1, node.Status.Allocatable) +} + +// Returns nil if the node is a linux node, err otherwise. +func validateLinuxNode(node *corev1.Node) error { + // This label should always be populated. + val, ok := node.Labels[corev1.LabelOSStable] + if !ok || val == "linux" { + return nil + } + return fmt.Errorf("pod resize is only supported on linux nodes, node %q is %q", node.Name, val) +} + +func resizeValidationError(a admission.Attributes, err error, reason metav1.CauseType) error { + forbiddenErr := admission.NewForbidden(a, err) + var statusErr *errors.StatusError + if stderrors.As(forbiddenErr, &statusErr) { + statusErr.ErrStatus.Details.Causes = append(statusErr.ErrStatus.Details.Causes, metav1.StatusCause{ + Type: reason, + }) + return statusErr + } + return forbiddenErr +} + +// validateRequestsWithinAllocatable returns an error if the pod requests exceed the allocatable +// capacity. +func validateRequestsWithinAllocatable(pod *corev1.Pod, allocatable corev1.ResourceList) error { + cpuAllocatable := allocatable.Cpu() + memAllocatable := allocatable.Memory() + + requests := resource.PodRequests(pod, resource.PodResourcesOptions{}) + cpuRequests := requests[corev1.ResourceCPU] + memRequests := requests[corev1.ResourceMemory] + + var msg []string + if cpuRequests.Cmp(*cpuAllocatable) > 0 { + msg = append(msg, fmt.Sprintf("cpu, requested: %d, allocatable: %d", cpuRequests.MilliValue(), cpuAllocatable.MilliValue())) + } + if memRequests.Cmp(*memAllocatable) > 0 { + msg = append(msg, fmt.Sprintf("memory, requested: %d, allocatable: %d", memRequests.Value(), memAllocatable.Value())) + } + if len(msg) > 0 { + return fmt.Errorf("node didn't have enough allocatable resources: %s", strings.Join(msg, "; ")) + } + return nil +} diff --git a/plugin/pkg/admission/podresize/admission_test.go b/plugin/pkg/admission/podresize/admission_test.go new file mode 100644 index 0000000000000..4d357ba0ef729 --- /dev/null +++ b/plugin/pkg/admission/podresize/admission_test.go @@ -0,0 +1,437 @@ +/* +Copyright 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 podresize + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/kubernetes/pkg/apis/core" +) + +func TestResizeAdmission(t *testing.T) { + linuxNode := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "linux-node", + Labels: map[string]string{ + corev1.LabelOSStable: "linux", + }, + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("4"), + corev1.ResourceMemory: resource.MustParse("8Gi"), + }, + }, + } + + windowsNode := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "windows-node", + Labels: map[string]string{ + corev1.LabelOSStable: "windows", + }, + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("4"), + corev1.ResourceMemory: resource.MustParse("8Gi"), + }, + }, + } + + testPod := &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + Generation: 1, + }, + Spec: core.PodSpec{ + NodeName: "linux-node", + Containers: []core.Container{ + { + Name: "main", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceCPU: resource.MustParse("1"), + core.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + }, + }, + } + + testCases := []struct { + name string + subresource string + nodeName string + oldPod *core.Pod + newPod func() *core.Pod + expectErr bool + errContains string + }{ + { + name: "Successful cpu resize on Linux node", + subresource: "resize", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("2") + return p + }, + expectErr: false, + }, + { + name: "Successful memory resize on Linux node", + subresource: "resize", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceMemory] = resource.MustParse("2Gi") + return p + }, + expectErr: false, + }, + { + name: "Skip validation if not resize subresource", + subresource: "", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("10") // Would fail if checked + return p + }, + expectErr: false, + }, + { + name: "Skip validation if generation is unchanged", + subresource: "", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 1 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("10") // Would fail if checked + return p + }, + expectErr: false, + }, + { + name: "Skip validation if pod not bound to a node", + subresource: "resize", + nodeName: "", + oldPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Spec.NodeName = "" + return p + }(), + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Spec.NodeName = "" + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("10") + return p + }, + expectErr: false, + }, + { + name: "Reject resize exceeding CPU allocatable", + subresource: "resize", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("10") + return p + }, + expectErr: true, + errContains: "node didn't have enough allocatable resources: cpu", + }, + { + name: "Reject resize exceeding memory allocatable", + subresource: "resize", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceMemory] = resource.MustParse("10Gi") + return p + }, + expectErr: true, + errContains: "node didn't have enough allocatable resources: memory", + }, + { + name: "Reject resize exceeding both CPU and memory allocatable", + subresource: "resize", + nodeName: "linux-node", + oldPod: testPod, + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("10") + p.Spec.Containers[0].Resources.Requests[core.ResourceMemory] = resource.MustParse("10Gi") + return p + }, + expectErr: true, + errContains: "node didn't have enough allocatable resources", + }, + { + name: "Reject resize on non-Linux node", + subresource: "resize", + nodeName: "windows-node", + oldPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Spec.NodeName = "windows-node" + return p + }(), + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Spec.NodeName = "windows-node" + p.Generation = 2 + p.Spec.Containers[0].Resources.Requests[core.ResourceCPU] = resource.MustParse("2") + return p + }, + expectErr: true, + errContains: "pod resize is only supported on linux nodes", + }, + { + name: "Reject if node not found", + subresource: "resize", + nodeName: "missing-node", + oldPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Spec.NodeName = "missing-node" + return p + }(), + newPod: func() *core.Pod { + p := testPod.DeepCopy() + p.Spec.NodeName = "missing-node" + p.Generation = 2 + return p + }, + expectErr: true, + errContains: "node \"missing-node\" not found", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + client := fake.NewClientset(linuxNode, windowsNode) + informerFactory := informers.NewSharedInformerFactory(client, 0) + + plugin, err := NewPlugin() + require.NoError(t, err) + plugin.SetExternalKubeInformerFactory(informerFactory) + plugin.inPlacePodVerticalScalingEnabled = true + + stopCh := make(chan struct{}) + defer close(stopCh) + informerFactory.Start(stopCh) + informerFactory.WaitForCacheSync(stopCh) + + newPodObj := tc.newPod() + attrs := admission.NewAttributesRecord( + newPodObj, + tc.oldPod, + core.Kind("Pod").WithVersion("v1"), + newPodObj.Namespace, + newPodObj.Name, + core.Resource("pods").WithVersion("v1"), + tc.subresource, + admission.Update, + &metav1.UpdateOptions{}, + false, + nil, + ) + + err = plugin.Validate(context.Background(), attrs, nil) + + if tc.expectErr { + require.Error(t, err) + if tc.errContains != "" { + assert.Contains(t, err.Error(), tc.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateRequestsWithinAllocatable(t *testing.T) { + testCases := []struct { + name string + pod *corev1.Pod + allocatable corev1.ResourceList + expectedErr bool + expectedMsg string + }{ + { + name: "cpu and mem requests within allocatable", + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + corev1.ResourceMemory: resource.MustParse("200Mi"), + }, + expectedErr: false, + }, + { + name: "cpu exceeds allocatable", + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + }, + }, + }, + }, + }, + }, + allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + }, + expectedErr: true, + expectedMsg: "cpu, requested: 500, allocatable: 200", + }, + { + name: "mem exceeds allocatable", + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("500Mi"), + }, + }, + }, + }, + }, + }, + allocatable: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + expectedErr: true, + // 500Mi = 524288000 bytes, 256Mi = 268435456 bytes + expectedMsg: "memory, requested: 524288000, allocatable: 268435456", + }, + { + name: "cpu and mem alone are within allocatable, but overhead causes failure", + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Overhead: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("100Mi"), + }, + Containers: []corev1.Container{ + { + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("150m"), + corev1.ResourceMemory: resource.MustParse("150Mi"), + }, + }, + }, + }, + }, + }, + allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + corev1.ResourceMemory: resource.MustParse("200Mi"), + }, + expectedErr: true, + // CPU: 150m + 100m = 250m. Mem: 150Mi + 100Mi = 250Mi (262144000 bytes) + expectedMsg: "cpu, requested: 250, allocatable: 200", + }, + { + name: "pod level resources override container requests and fit", + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + }, + }, + Containers: []corev1.Container{ + { + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + }, + }, + }, + }, + allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + }, + expectedErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := validateRequestsWithinAllocatable(tc.pod, tc.allocatable) + + if (err != nil) != tc.expectedErr { + t.Fatalf("ValidateRequestsWithinAllocatable() error = %v, expectedErr %v", err, tc.expectedErr) + } + + if tc.expectedErr && err != nil { + if !strings.Contains(err.Error(), tc.expectedMsg) { + t.Errorf("ValidateRequestsWithinAllocatable() error message = %q, expected to contain %q", err.Error(), tc.expectedMsg) + } + } + }) + } +} diff --git a/plugin/pkg/admission/priority/admission.go b/plugin/pkg/admission/priority/admission.go index 866fbf64516b8..91101f4f53383 100644 --- a/plugin/pkg/admission/priority/admission.go +++ b/plugin/pkg/admission/priority/admission.go @@ -27,11 +27,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/admission" genericadmissioninitializers "k8s.io/apiserver/pkg/admission/initializer" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" schedulingv1listers "k8s.io/client-go/listers/scheduling/v1" "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" ) const ( @@ -90,11 +92,11 @@ func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactor var ( podResource = core.Resource("pods") + podGroupResource = scheduling.Resource("podgroups") priorityClassResource = scheduling.Resource("priorityclasses") ) -// Admit checks Pods and admits or rejects them. It also resolves the priority of pods based on their PriorityClass. -// Note that pod validation mechanism prevents update of a pod priority. +// Admit checks Pods and PodGroups and admits or rejects them. It also resolves the priority of pods and pod groups based on their PriorityClass. func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { operation := a.GetOperation() // Ignore all calls to subresources @@ -107,7 +109,11 @@ func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission. return p.admitPod(a) } return nil - + case podGroupResource: + if operation == admission.Create { + return p.admitPodGroup(a) + } + return nil default: return nil } @@ -162,28 +168,11 @@ func (p *Plugin) admitPod(a admission.Attributes) error { if operation == admission.Create { var priority int32 var preemptionPolicy *apiv1.PreemptionPolicy - if len(pod.Spec.PriorityClassName) == 0 { - var err error - var pcName string - pcName, priority, preemptionPolicy, err = p.getDefaultPriority() - if err != nil { - return fmt.Errorf("failed to get default priority class: %v", err) - } - pod.Spec.PriorityClassName = pcName - } else { - // Try resolving the priority class name. - pc, err := p.lister.Get(pod.Spec.PriorityClassName) - if err != nil { - if errors.IsNotFound(err) { - return admission.NewForbidden(a, fmt.Errorf("no PriorityClass with name %v was found", pod.Spec.PriorityClassName)) - } - - return fmt.Errorf("failed to get PriorityClass with name %s: %v", pod.Spec.PriorityClassName, err) - } - - priority = pc.Value - preemptionPolicy = pc.PreemptionPolicy + pcName, priority, preemptionPolicy, err := p.establishPriority(a, &pod.Spec.PriorityClassName) + if err != nil { + return err } + pod.Spec.PriorityClassName = pcName // if the pod contained a priority that differs from the one computed from the priority class, error if pod.Spec.Priority != nil && *pod.Spec.Priority != priority { return admission.NewForbidden(a, fmt.Errorf("the integer value of priority (%d) must not be provided in pod spec; priority admission controller computed %d from the given PriorityClass name", *pod.Spec.Priority, priority)) @@ -202,6 +191,31 @@ func (p *Plugin) admitPod(a admission.Attributes) error { return nil } +// admitPodGroup makes sure a new pod group does not set spec.Priority field. It also makes sure that +// the PriorityClassName exists if it is provided and resolves the pod group priority from the PriorityClassName. +func (p *Plugin) admitPodGroup(attributes admission.Attributes) error { + if !utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) { + return nil + } + + pg, ok := attributes.GetObject().(*scheduling.PodGroup) + if !ok { + return errors.NewBadRequest("resource was marked with kind PodGroup but was unable to be converted") + } + + priorityClassName, priority, _, err := p.establishPriority(attributes, &pg.Spec.PriorityClassName) + if err != nil { + return err + } + // Reject if the pod group already contained a priority that differs from the one computed from the priority class. + if pg.Spec.Priority != nil && *pg.Spec.Priority != priority { + return admission.NewForbidden(attributes, fmt.Errorf("priority set in the pod group (%d) must match the priority computed (%d) based on the priority class set in the spec", *pg.Spec.Priority, priority)) + } + pg.Spec.Priority = &priority + pg.Spec.PriorityClassName = priorityClassName + return nil +} + // validatePriorityClass ensures that the value field is not larger than the highest user definable priority. If the GlobalDefault is set, it ensures that there is no other PriorityClass whose GlobalDefault is set. func (p *Plugin) validatePriorityClass(a admission.Attributes) error { operation := a.GetOperation() @@ -225,6 +239,28 @@ func (p *Plugin) validatePriorityClass(a admission.Attributes) error { return nil } +// establishPriority is an auxiliary method for calculating the priority-specific fields +// based on the provided priority class name. +// If the provided name is empty, we fall back to getting the default priority class and +// returning information contained there. +// If the provided name is not empty, we get the priority class with such name and return +// the information contained in that class. +func (p *Plugin) establishPriority(attributes admission.Attributes, priorityClassName *string) (string, int32, *apiv1.PreemptionPolicy, error) { + if priorityClassName == nil || *priorityClassName == "" { + pcName, priority, preemptionPolicy, err := p.getDefaultPriority() + if err != nil { + return "", 0, nil, fmt.Errorf("error occurred while retrieving default priority class: %w", err) + } + return pcName, priority, preemptionPolicy, nil + } + // Try resolving the priority class name. + pc, err := p.resolvePriorityClass(attributes, *priorityClassName) + if err != nil { + return "", 0, nil, err + } + return *priorityClassName, pc.Value, pc.PreemptionPolicy, nil +} + func (p *Plugin) getDefaultPriorityClass() (*schedulingv1.PriorityClass, error) { list, err := p.lister.List(labels.Everything()) if err != nil { @@ -254,3 +290,14 @@ func (p *Plugin) getDefaultPriority() (string, int32, *apiv1.PreemptionPolicy, e preemptLowerPriority := apiv1.PreemptLowerPriority return "", int32(scheduling.DefaultPriorityWhenNoDefaultClassExists), &preemptLowerPriority, nil } + +func (p *Plugin) resolvePriorityClass(attributes admission.Attributes, priorityClassName string) (*schedulingv1.PriorityClass, error) { + priorityClass, err := p.lister.Get(priorityClassName) + if err != nil { + if errors.IsNotFound(err) { + return nil, admission.NewForbidden(attributes, fmt.Errorf("no PriorityClass with name %v was found", priorityClassName)) + } + return nil, fmt.Errorf("failed to resolve PriorityClass with name %s: %w", priorityClassName, err) + } + return priorityClass, nil +} diff --git a/plugin/pkg/admission/priority/admission_test.go b/plugin/pkg/admission/priority/admission_test.go index 1e66a26f7c687..887fa9c32daea 100644 --- a/plugin/pkg/admission/priority/admission_test.go +++ b/plugin/pkg/admission/priority/admission_test.go @@ -20,18 +20,24 @@ import ( "context" "testing" + "github.com/google/go-cmp/cmp" "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" schedulingv1 "k8s.io/api/scheduling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/admission" admissiontesting "k8s.io/apiserver/pkg/admission/testing" "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" + featuregatetesting "k8s.io/component-base/featuregate/testing" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/scheduling" v1 "k8s.io/kubernetes/pkg/apis/scheduling/v1" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/features" ) func addPriorityClasses(ctrl *Plugin, priorityClasses []*scheduling.PriorityClass) error { @@ -760,3 +766,160 @@ func TestPodAdmission(t *testing.T) { } } } + +func TestAdmitPodGroup(t *testing.T) { + podGroup := func(priorityClassName string) *scheduling.PodGroup { + return &scheduling.PodGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-podgroup", + Namespace: metav1.NamespaceDefault, + }, + Spec: scheduling.PodGroupSpec{ + SchedulingPolicy: scheduling.PodGroupSchedulingPolicy{ + Basic: &scheduling.BasicSchedulingPolicy{}, + }, + PriorityClassName: priorityClassName, + }, + } + } + + podGroupWithPriority := func(priorityClassName string, priority int32) *scheduling.PodGroup { + pg := podGroup(priorityClassName) + pg.Spec.Priority = new(priority) + return pg + } + + attributes := func(podGroup *scheduling.PodGroup, operation admission.Operation) admission.Attributes { + var oldPodGroup runtime.Object + var options runtime.Object = &metav1.CreateOptions{} + if operation == admission.Update { + oldPodGroup = podGroup.DeepCopy() + options = &metav1.UpdateOptions{} + } + return admission.NewAttributesRecord( + podGroup, + oldPodGroup, + scheduling.Kind("PodGroup").WithVersion("v1alpha2"), + podGroup.ObjectMeta.Namespace, + "", + scheduling.Resource("podgroups").WithVersion("v1alpha2"), + "", + operation, + options, + false, + nil, + ) + } + + testCases := []struct { + name string + priorityClasses []*scheduling.PriorityClass + preparePodGroup *scheduling.PodGroup + operation admission.Operation + expectedPriorityClass string + expectedPriority int32 + enableWorkloadAwarePreemption bool + expectError bool + }{ + { + name: "pod group with empty priorityClassName, accepted and set to global default", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroup("" /* empty priorityClassName */), + operation: admission.Create, + expectedPriorityClass: "default1", + expectedPriority: defaultClass1.Value, + enableWorkloadAwarePreemption: true, + }, + { + name: "pod group with explicit priorityClassName, accepted", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroup("nondefault1"), + operation: admission.Create, + expectedPriorityClass: "nondefault1", + expectedPriority: nondefaultClass1.Value, + enableWorkloadAwarePreemption: true, + }, + { + name: "pod group with non-existent priorityClassName, rejected", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroup("non-existent"), + operation: admission.Create, + enableWorkloadAwarePreemption: true, + expectError: true, + }, + { + name: "pod group with any priorityClassName but feature gate disabled, skips validation", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroup("non-existent"), + operation: admission.Create, + }, + { + name: "pod group with no priorityClassName and no global default, accepted and priority should be zero", + priorityClasses: []*scheduling.PriorityClass{nondefaultClass1}, + preparePodGroup: podGroup("" /* empty priorityClassName */), + operation: admission.Create, + expectedPriorityClass: "", + expectedPriority: 0, + enableWorkloadAwarePreemption: true, + }, + { + name: "pod group create with pre-set Priority matching computed value, accepted", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroupWithPriority("nondefault1", nondefaultClass1.Value), + operation: admission.Create, + expectedPriorityClass: "nondefault1", + expectedPriority: nondefaultClass1.Value, + enableWorkloadAwarePreemption: true, + }, + { + name: "pod group create with pre-set Priority not matching computed value, rejected", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroupWithPriority("nondefault1", int32(9999)), + operation: admission.Create, + enableWorkloadAwarePreemption: true, + expectError: true, + }, + { + name: "update operation is a no-op, admission does not mutate pod group on update", + priorityClasses: []*scheduling.PriorityClass{defaultClass1, nondefaultClass1}, + preparePodGroup: podGroup("non-existent"), + operation: admission.Update, + enableWorkloadAwarePreemption: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: true, + features.GangScheduling: true, + features.WorkloadAwarePreemption: tt.enableWorkloadAwarePreemption, + }) + + admissionPlugin := NewPlugin() + if err := addPriorityClasses(admissionPlugin, tt.priorityClasses); err != nil { + t.Fatalf("unable to configure priority classes: %v", err) + } + + _, ctx := ktesting.NewTestContext(t) + podGroupCopy := tt.preparePodGroup.DeepCopy() + err := admissionPlugin.Admit(ctx, attributes(tt.preparePodGroup, tt.operation), nil) + if (err != nil) != tt.expectError { + t.Errorf("PodGroup Admit(), error = %v, want = %v", err, tt.expectError) + } + if !tt.expectError && tt.operation == admission.Create && tt.enableWorkloadAwarePreemption && tt.preparePodGroup.Spec.PodGroupTemplateRef == nil { + if tt.preparePodGroup.Spec.PriorityClassName != tt.expectedPriorityClass { + t.Errorf("PodGroup Admit(), priorityClassName = %v, want = %v", tt.preparePodGroup.Spec.PriorityClassName, tt.expectedPriorityClass) + } + if *tt.preparePodGroup.Spec.Priority != tt.expectedPriority { + t.Errorf("PodGroup Admit(), Priority = %v, want = %v", *tt.preparePodGroup.Spec.Priority, tt.expectedPriority) + } + } + if tt.operation != admission.Create { + if diff := cmp.Diff(tt.preparePodGroup, podGroupCopy); len(diff) > 0 { + t.Errorf("PodGroup Admit() should not modify the PodGroup (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/plugin/pkg/admission/resourcequota/admission_test.go b/plugin/pkg/admission/resourcequota/admission_test.go index 607f6036697e3..87b0689f75743 100644 --- a/plugin/pkg/admission/resourcequota/admission_test.go +++ b/plugin/pkg/admission/resourcequota/admission_test.go @@ -32,16 +32,13 @@ import ( genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/resourcequota" resourcequotaapi "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" testcore "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" - featuregatetesting "k8s.io/component-base/featuregate/testing" api "k8s.io/kubernetes/pkg/apis/core" controlplaneadmission "k8s.io/kubernetes/pkg/controlplane/apiserver/admission" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/quota/v1/install" ) @@ -110,7 +107,10 @@ func createHandlerWithConfig(kubeClient kubernetes.Interface, informerFactory in if config == nil { config = &resourcequotaapi.Configuration{} } - quotaConfiguration := install.NewQuotaConfigurationForAdmission(nil) + quotaConfiguration, err := install.NewQuotaConfigurationForAdmission(informerFactory, nil) + if err != nil { + return nil, err + } handler, err := resourcequota.NewResourceQuota(config, 5) if err != nil { @@ -990,8 +990,6 @@ func TestAdmitBelowTerminatingQuotaLimitWhenPodScopeUpdated(t *testing.T) { // It creates a glod and silver quota, and creates a pvc with the glod class. // It ensures that the glod quota is incremented, and the silver quota is not. func TestAdmitBelowVolumeAttributesClassQuotaLimit(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true) - classGold := "gold" classSilver := "silver" @@ -1124,8 +1122,6 @@ func TestAdmitBelowVolumeAttributesClassQuotaLimit(t *testing.T) { // use to lower / free the reserved quota. We need always overcount in the admission plugin if something later causes // the request to be rejected, so you can not reduce quota with requests that aren't completed. func TestAdmitBelowVolumeAttributesClassQuotaLimitWhenPVCScopeUpdated(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true) - classGold := "gold" classSilver := "silver" classCopper := "copper" @@ -1421,7 +1417,6 @@ func TestAdmitBelowVolumeAttributesClassQuotaLimitWhenPVCScopeUpdated(t *testing } for _, testCase := range testCases { - testCase := testCase t.Run(testCase.desc, func(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) diff --git a/plugin/pkg/admission/scheduling/podgroupprotection/admission.go b/plugin/pkg/admission/scheduling/podgroupprotection/admission.go new file mode 100644 index 0000000000000..bd7a905753603 --- /dev/null +++ b/plugin/pkg/admission/scheduling/podgroupprotection/admission.go @@ -0,0 +1,101 @@ +/* +Copyright 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 podgroupprotection + +import ( + "context" + "fmt" + "io" + "slices" + + "k8s.io/apiserver/pkg/admission" + apiserveradmission "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/component-base/featuregate" + "k8s.io/klog/v2" + schedulingapi "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" +) + +const ( + PluginName = "PodGroupProtection" +) + +// Register registers the plugin. +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return newPlugin(), nil + }) +} + +type podGroupProtectionPlugin struct { + *admission.Handler + enabled bool + inspectedFeatureGates bool +} + +var _ admission.MutationInterface = &podGroupProtectionPlugin{} +var _ apiserveradmission.WantsFeatures = &podGroupProtectionPlugin{} + +func newPlugin() *podGroupProtectionPlugin { + return &podGroupProtectionPlugin{ + Handler: admission.NewHandler(admission.Create), + } +} + +func (p *podGroupProtectionPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { + p.enabled = featureGates.Enabled(features.GenericWorkload) + p.inspectedFeatureGates = true +} + +func (p *podGroupProtectionPlugin) ValidateInitialization() error { + if !p.inspectedFeatureGates { + return fmt.Errorf("feature gates not inspected") + } + return nil +} + +var podGroupResource = schedulingapi.Resource("podgroups") + +// Admit stamps the PodGroupProtectionFinalizer on every newly created PodGroup +// so that it cannot be deleted while pods still reference it. +// The finalizer is removed by the PodGroupProtection controller when the +// PodGroup is no longer in use. +func (p *podGroupProtectionPlugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + if !p.enabled { + return nil + } + + if a.GetResource().GroupResource() != podGroupResource { + return nil + } + if len(a.GetSubresource()) != 0 { + return nil + } + + pg, ok := a.GetObject().(*schedulingapi.PodGroup) + if !ok { + return nil + } + + if slices.Contains(pg.Finalizers, schedulingapi.PodGroupProtectionFinalizer) { + return nil + } + + klog.V(4).InfoS("Adding protection finalizer to PodGroup", "podGroup", klog.KObj(pg)) + pg.Finalizers = append(pg.Finalizers, schedulingapi.PodGroupProtectionFinalizer) + return nil +} diff --git a/plugin/pkg/admission/scheduling/podgroupprotection/admission_test.go b/plugin/pkg/admission/scheduling/podgroupprotection/admission_test.go new file mode 100644 index 0000000000000..2837a1e0af4b3 --- /dev/null +++ b/plugin/pkg/admission/scheduling/podgroupprotection/admission_test.go @@ -0,0 +1,111 @@ +/* +Copyright 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 podgroupprotection + +import ( + "context" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + schedulingapi "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" + "k8s.io/utils/dump" +) + +func TestAdmit(t *testing.T) { + pg := &schedulingapi.PodGroup{ + TypeMeta: metav1.TypeMeta{Kind: "PodGroup"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-podgroup", + Namespace: "default", + }, + } + + pgWithFinalizer := pg.DeepCopy() + pgWithFinalizer.Finalizers = []string{schedulingapi.PodGroupProtectionFinalizer} + + tests := []struct { + name string + enabled bool + resource schema.GroupVersionResource + object runtime.Object + expectedObject runtime.Object + namespace string + }{ + { + name: "podgroup create with plugin enabled, add finalizer", + enabled: true, + resource: schedulingapi.SchemeGroupVersion.WithResource("podgroups"), + object: pg, + expectedObject: pgWithFinalizer, + namespace: pg.Namespace, + }, + { + name: "podgroup finalizer already exists, no new finalizer", + enabled: true, + resource: schedulingapi.SchemeGroupVersion.WithResource("podgroups"), + object: pgWithFinalizer, + expectedObject: pgWithFinalizer, + namespace: pgWithFinalizer.Namespace, + }, + { + name: "podgroup create with plugin disabled, no finalizer added", + enabled: false, + resource: schedulingapi.SchemeGroupVersion.WithResource("podgroups"), + object: pg, + expectedObject: pg, + namespace: pg.Namespace, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, test.enabled) + + ctrl := newPlugin() + ctrl.InspectFeatureGates(utilfeature.DefaultFeatureGate) + + obj := test.object.DeepCopyObject() + attrs := admission.NewAttributesRecord( + obj, + obj.DeepCopyObject(), + schema.GroupVersionKind{}, + test.namespace, + "foo", + test.resource, + "", + admission.Create, + &metav1.CreateOptions{}, + false, + nil, + ) + + if err := ctrl.Admit(context.TODO(), attrs, nil); err != nil { + t.Errorf("got unexpected error: %v", err) + } + if !reflect.DeepEqual(test.expectedObject, obj) { + t.Errorf("Expected object:\n%s\ngot:\n%s", dump.Pretty(test.expectedObject), dump.Pretty(obj)) + } + }) + } +} diff --git a/plugin/pkg/admission/storage/storageobjectinuseprotection/admission_test.go b/plugin/pkg/admission/storage/storageobjectinuseprotection/admission_test.go index 77c3d0c2318e3..fad305d1b96f2 100644 --- a/plugin/pkg/admission/storage/storageobjectinuseprotection/admission_test.go +++ b/plugin/pkg/admission/storage/storageobjectinuseprotection/admission_test.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/apiserver/pkg/admission" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -32,6 +32,7 @@ import ( storageapi "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/features" volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/utils/dump" ) func TestAdmit(t *testing.T) { @@ -150,6 +151,9 @@ func TestAdmit(t *testing.T) { t.Run(test.name, func(t *testing.T) { ctrl := newPlugin() + if !test.enableVacFeatureGate { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, test.enableVacFeatureGate) obj := test.object.DeepCopyObject() diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index 3abb5e0d3009c..994e716a288ea 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -189,9 +189,6 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie(), rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(), eventsRule(), - - // volume plugin - portworx - rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("services").RuleOrDie(), }, }) @@ -206,16 +203,28 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) }) if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) { + rules := []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(), + rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("pods/finalizers").RuleOrDie(), + rbacv1helpers.NewRule("get", "list", "watch", "create", "delete").Groups(resourceGroup).Resources("resourceclaims").RuleOrDie(), + rbacv1helpers.NewRule("update", "patch").Groups(resourceGroup).Resources("resourceclaims", "resourceclaims/status").RuleOrDie(), + rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("pods/status").RuleOrDie(), + eventsRule(), + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAWorkloadResourceClaims) { + rules = append(rules, + rbacv1helpers.NewRule("get", "list", "watch").Groups(schedulingGroup).Resources("podgroups").RuleOrDie(), + rbacv1helpers.NewRule("update", "patch").Groups(schedulingGroup).Resources("podgroups/status").RuleOrDie(), + ) + } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAResourceClaimGranularStatusAuthorization) { + rules = append(rules, + rbacv1helpers.NewRule("update", "patch").Groups(resourceGroup).Resources("resourceclaims/binding").RuleOrDie(), + ) + } addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "resource-claim-controller"}, - Rules: []rbacv1.PolicyRule{ - rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(), - rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("pods/finalizers").RuleOrDie(), - rbacv1helpers.NewRule("get", "list", "watch", "create", "delete").Groups(resourceGroup).Resources("resourceclaims").RuleOrDie(), - rbacv1helpers.NewRule("update", "patch").Groups(resourceGroup).Resources("resourceclaims", "resourceclaims/status").RuleOrDie(), - rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("pods/status").RuleOrDie(), - eventsRule(), - }, + Rules: rules, }) if utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaints) { rules := []rbacv1.PolicyRule{ @@ -245,6 +254,23 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) Rules: rules, }) } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAResourcePoolStatus) { + addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ + // Same name as in k8s.io/kubernetes/cmd/kube-controller-manager/names. + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "resourcepoolstatusrequest-controller"}, + Rules: []rbacv1.PolicyRule{ + // Read and delete ResourcePoolStatusRequests (delete needed for TTL cleanup) + rbacv1helpers.NewRule("get", "list", "watch", "delete").Groups(resourceGroup).Resources("resourcepoolstatusrequests").RuleOrDie(), + // Update status after processing + rbacv1helpers.NewRule("update", "patch").Groups(resourceGroup).Resources("resourcepoolstatusrequests/status").RuleOrDie(), + // Read ResourceSlices to calculate pool status + rbacv1helpers.NewRule("get", "list", "watch").Groups(resourceGroup).Resources("resourceslices").RuleOrDie(), + // Read ResourceClaims to calculate allocation counts + rbacv1helpers.NewRule("get", "list", "watch").Groups(resourceGroup).Resources("resourceclaims").RuleOrDie(), + eventsRule(), + }, + }) + } } addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ @@ -269,15 +295,21 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) eventsRule(), }, }) + jobControllerRules := []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("get", "list", "watch", "update", "patch").Groups(batchGroup).Resources("jobs").RuleOrDie(), + rbacv1helpers.NewRule("update").Groups(batchGroup).Resources("jobs/status").RuleOrDie(), + rbacv1helpers.NewRule("update").Groups(batchGroup).Resources("jobs/finalizers").RuleOrDie(), + rbacv1helpers.NewRule("list", "watch", "create", "delete", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(), + eventsRule(), + } + if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadWithJob) { + jobControllerRules = append(jobControllerRules, + rbacv1helpers.NewRule("get", "list", "watch", "create").Groups(schedulingGroup).Resources("workloads", "podgroups").RuleOrDie(), + ) + } addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "job-controller"}, - Rules: []rbacv1.PolicyRule{ - rbacv1helpers.NewRule("get", "list", "watch", "update", "patch").Groups(batchGroup).Resources("jobs").RuleOrDie(), - rbacv1helpers.NewRule("update").Groups(batchGroup).Resources("jobs/status").RuleOrDie(), - rbacv1helpers.NewRule("update").Groups(batchGroup).Resources("jobs/finalizers").RuleOrDie(), - rbacv1helpers.NewRule("list", "watch", "create", "delete", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(), - eventsRule(), - }, + Rules: jobControllerRules, }) addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "namespace-controller"}, @@ -315,9 +347,6 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) // recyclerClient.WatchPod rbacv1helpers.NewRule("watch").Groups(legacyGroup).Resources("events").RuleOrDie(), - - // volume plugin - portworx - rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("services").RuleOrDie(), }, }) addControllerRole(&controllerRoles, &controllerRoleBindings, func() rbacv1.ClusterRole { @@ -451,6 +480,7 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "pvc-protection-controller"}, Rules: []rbacv1.PolicyRule{ rbacv1helpers.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(), + rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie(), rbacv1helpers.NewRule("list", "watch", "get").Groups(legacyGroup).Resources("pods").RuleOrDie(), eventsRule(), }, @@ -474,6 +504,16 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) }) } + if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "podgroup-protection-controller"}, + Rules: []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("get", "list", "watch", "update").Groups(schedulingGroup).Resources("podgroups").RuleOrDie(), + rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(), + }, + }) + } + addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ttl-after-finished-controller"}, Rules: []rbacv1.PolicyRule{ @@ -550,6 +590,8 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) // need patch for SSA of any resource // need create because SSA of a deleted resource will be interpreted as a create request, these always fail with a conflict error because UID is set rbacv1helpers.NewRule("list", "create", "patch").Groups("*").Resources("*").RuleOrDie(), + rbacv1helpers.NewRule("get").Groups("apiextensions.k8s.io").Resources("customresourcedefinitions").RuleOrDie(), + rbacv1helpers.NewRule("patch").Groups("apiextensions.k8s.io").Resources("customresourcedefinitions/status").RuleOrDie(), rbacv1helpers.NewRule("update").Groups(storageVersionMigrationGroup).Resources("storageversionmigrations/status").RuleOrDie(), }, }) diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go index 21417d18d9024..a89337abb04e3 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go @@ -21,8 +21,12 @@ import ( "slices" "testing" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" ) // rolesWithAllowStar are the controller roles which are allowed to contain a *. These are @@ -38,7 +42,7 @@ var rolesWithAllowStar = sets.NewString( ) // TestNoStarsForControllers confirms that no controller role has star verbs, groups, -// or resources. There are three known exceptions: namespace lifecycle and GC which have to +// or resources. There are three known exceptions: namespace lifecycle and GC which have to // delete anything, and HPA, which has the power to read metrics associated // with any object. func TestNoStarsForControllers(t *testing.T) { @@ -93,6 +97,80 @@ func TestControllerRoleLabel(t *testing.T) { } } +func TestPodGroupProtectionControllerRBAC(t *testing.T) { + roleName := saRolePrefix + "podgroup-protection-controller" + + tests := []struct { + name string + enableFeatureGate bool + expectRole bool + }{ + { + name: "role and binding absent when GenericWorkload is disabled", + enableFeatureGate: false, + expectRole: false, + }, + { + name: "role and binding present when GenericWorkload is enabled", + enableFeatureGate: true, + expectRole: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericWorkload, test.enableFeatureGate) + + var foundRole *rbacv1.ClusterRole + for i, role := range ControllerRoles() { + if role.Name == roleName { + foundRole = &ControllerRoles()[i] + break + } + } + + if !test.expectRole { + if foundRole != nil { + t.Fatalf("role %q should not exist when GenericWorkload is disabled", roleName) + } + return + } + + if foundRole == nil { + t.Fatalf("role %q not found when GenericWorkload is enabled", roleName) + } + + wantRules := []rbacv1.PolicyRule{ + {Verbs: []string{"get", "list", "update", "watch"}, APIGroups: []string{"scheduling.k8s.io"}, Resources: []string{"podgroups"}}, + {Verbs: []string{"get", "list", "watch"}, APIGroups: []string{""}, Resources: []string{"pods"}}, + } + if !reflect.DeepEqual(foundRole.Rules, wantRules) { + t.Errorf("unexpected rules:\ngot: %+v\nwant: %+v", foundRole.Rules, wantRules) + } + + var foundBinding *rbacv1.ClusterRoleBinding + for i, binding := range ControllerRoleBindings() { + if binding.RoleRef.Name == roleName { + foundBinding = &ControllerRoleBindings()[i] + break + } + } + if foundBinding == nil { + t.Fatalf("binding for %q not found", roleName) + } + if len(foundBinding.Subjects) != 1 { + t.Fatalf("expected 1 subject, got %d", len(foundBinding.Subjects)) + } + if got, want := foundBinding.Subjects[0].Name, "podgroup-protection-controller"; got != want { + t.Errorf("subject name = %q, want %q", got, want) + } + if got, want := foundBinding.Subjects[0].Namespace, "kube-system"; got != want { + t.Errorf("subject namespace = %q, want %q", got, want) + } + }) + } +} + func TestControllerRoleVerbsConsistency(t *testing.T) { roles := ControllerRoles() for _, role := range roles { @@ -104,3 +182,51 @@ func TestControllerRoleVerbsConsistency(t *testing.T) { } } } + +func TestJobControllerSchedulingRules(t *testing.T) { + cases := []struct { + name string + workloadWithJob bool + wantSchedulingRules bool + }{ + { + name: "feature gate disabled", + workloadWithJob: false, + wantSchedulingRules: false, + }, + { + name: "feature gate enabled", + workloadWithJob: true, + wantSchedulingRules: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ + features.GenericWorkload: tc.workloadWithJob, + features.WorkloadWithJob: tc.workloadWithJob, + }) + got := hasSchedulingRules(ControllerRoles()) + if got != tc.wantSchedulingRules { + t.Errorf("hasSchedulingRules() = %v, want %v", got, tc.wantSchedulingRules) + } + }) + } +} + +func hasSchedulingRules(roles []rbacv1.ClusterRole) bool { + for _, role := range roles { + if role.Name != saRolePrefix+"job-controller" { + continue + } + for _, rule := range role.Rules { + if slices.Contains(rule.APIGroups, "scheduling.k8s.io") && + slices.Contains(rule.Resources, "workloads") && + slices.Contains(rule.Resources, "podgroups") { + return true + } + } + } + return false +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index 31f059a54b846..ef1dafc39191f 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -146,6 +146,9 @@ func viewRules() []rbacv1.PolicyRule { if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) { rules = append(rules, rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("resourceclaims", "resourceclaims/status", "resourceclaimtemplates").RuleOrDie()) } + if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + rules = append(rules, rbacv1helpers.NewRule(Read...).Groups(schedulingGroup).Resources("workloads", "podgroups", "podgroups/status").RuleOrDie()) + } return rules } @@ -187,6 +190,9 @@ func editRules() []rbacv1.PolicyRule { if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) { rules = append(rules, rbacv1helpers.NewRule(Write...).Groups(resourceGroup).Resources("resourceclaims", "resourceclaimtemplates").RuleOrDie()) } + if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { + rules = append(rules, rbacv1helpers.NewRule(Write...).Groups(schedulingGroup).Resources("workloads", "podgroups").RuleOrDie()) + } return rules } @@ -287,23 +293,25 @@ func NodeRules() []rbacv1.PolicyRule { // clusterRoles returns the cluster roles to bootstrap an API server with func clusterRoles() []rbacv1.ClusterRole { - monitoringRules := []rbacv1.PolicyRule{ - rbacv1helpers.NewRule("get").URLs( - "/metrics", "/metrics/slis", - "/livez", "/readyz", "/healthz", - "/livez/*", "/readyz/*", "/healthz/*", - ).RuleOrDie(), - - // Needed for kubelet metrics - rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("nodes/metrics").RuleOrDie(), + monitoringURLs := []string{ + "/metrics", "/metrics/slis", + "/livez", "/readyz", "/healthz", + "/livez/*", "/readyz/*", "/healthz/*", } if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentFlagz) { - monitoringRules = append(monitoringRules, rbacv1helpers.NewRule("get").URLs("/flagz").RuleOrDie()) + monitoringURLs = append(monitoringURLs, "/flagz") } if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) { - monitoringRules = append(monitoringRules, rbacv1helpers.NewRule("get").URLs("/statusz").RuleOrDie()) + monitoringURLs = append(monitoringURLs, "/statusz") + } + + monitoringRules := []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("get").URLs(monitoringURLs...).RuleOrDie(), + + // Needed for kubelet metrics + rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("nodes/metrics").RuleOrDie(), } roles := []rbacv1.ClusterRole{ @@ -658,9 +666,15 @@ func clusterRoles() []rbacv1.ClusterRole { if utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaintRules) { kubeSchedulerRules = append(kubeSchedulerRules, rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("devicetaintrules").RuleOrDie()) } + if utilfeature.DefaultFeatureGate.Enabled(features.DRAResourceClaimGranularStatusAuthorization) { + kubeSchedulerRules = append(kubeSchedulerRules, + rbacv1helpers.NewRule("update", "patch").Groups(resourceGroup).Resources("resourceclaims/binding").RuleOrDie(), + ) + } } if utilfeature.DefaultFeatureGate.Enabled(features.GenericWorkload) { - kubeSchedulerRules = append(kubeSchedulerRules, rbacv1helpers.NewRule(Read...).Groups(schedulingGroup).Resources("workloads").RuleOrDie()) + kubeSchedulerRules = append(kubeSchedulerRules, rbacv1helpers.NewRule(Read...).Groups(schedulingGroup).Resources("podgroups").RuleOrDie()) + kubeSchedulerRules = append(kubeSchedulerRules, rbacv1helpers.NewRule("patch", "update").Groups(schedulingGroup).Resources("podgroups/status").RuleOrDie()) } roles = append(roles, rbacv1.ClusterRole{ // a role to use for the kube-scheduler diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles-featuregates.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles-featuregates.yaml index 96e9efe0aefd6..c81f65af51aaa 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles-featuregates.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles-featuregates.yaml @@ -260,6 +260,17 @@ items: - deletecollection - patch - update + - apiGroups: + - scheduling.k8s.io + resources: + - podgroups + - workloads + verbs: + - create + - delete + - deletecollection + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -414,6 +425,16 @@ items: - get - list - watch + - apiGroups: + - scheduling.k8s.io + resources: + - podgroups + - podgroups/status + - workloads + verbs: + - get + - list + - watch - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -976,14 +997,28 @@ items: - get - list - watch + - apiGroups: + - resource.k8s.io + resources: + - resourceclaims/binding + verbs: + - patch + - update - apiGroups: - scheduling.k8s.io resources: - - workloads + - podgroups verbs: - get - list - watch + - apiGroups: + - scheduling.k8s.io + resources: + - podgroups/status + verbs: + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -1034,6 +1069,7 @@ items: name: system:monitoring rules: - nonResourceURLs: + - /flagz - /healthz - /healthz/* - /livez @@ -1042,6 +1078,7 @@ items: - /metrics/slis - /readyz - /readyz/* + - /statusz verbs: - get - apiGroups: @@ -1050,14 +1087,6 @@ items: - nodes/metrics verbs: - get - - nonResourceURLs: - - /flagz - verbs: - - get - - nonResourceURLs: - - /statusz - verbs: - - get - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml index e9ca1398e5c04..ef690cf1b187e 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml @@ -944,6 +944,20 @@ items: - get - list - watch + - apiGroups: + - resource.k8s.io + resources: + - resourceclaims + verbs: + - create + - delete + - apiGroups: + - resource.k8s.io + resources: + - resourceclaims/binding + verbs: + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -994,6 +1008,7 @@ items: name: system:monitoring rules: - nonResourceURLs: + - /flagz - /healthz - /healthz/* - /livez @@ -1002,6 +1017,7 @@ items: - /metrics/slis - /readyz - /readyz/* + - /statusz verbs: - get - apiGroups: diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml index c37504db9890e..6a6c50df8d488 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml @@ -96,6 +96,22 @@ items: - kind: ServiceAccount name: deployment-controller namespace: kube-system +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:device-taint-eviction-controller + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:device-taint-eviction-controller + subjects: + - kind: ServiceAccount + name: device-taint-eviction-controller + namespace: kube-system - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml index a99e86274f8a9..88459b2652dcd 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml @@ -352,6 +352,64 @@ items: - create - patch - update +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:device-taint-eviction-controller + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - delete + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods/status + verbs: + - patch + - update + - apiGroups: + - resource.k8s.io + resources: + - resourceclaims + verbs: + - get + - list + - watch + - apiGroups: + - resource.k8s.io + resources: + - resourceslices + verbs: + - get + - list + - watch + - apiGroups: + - resource.k8s.io + resources: + - deviceclasses + verbs: + - get + - list + - watch + - apiGroups: + - "" + - events.k8s.io + resources: + - events + verbs: + - create + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -666,12 +724,6 @@ items: - create - patch - update - - apiGroups: - - "" - resources: - - services - verbs: - - get - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -1012,12 +1064,6 @@ items: - events verbs: - watch - - apiGroups: - - "" - resources: - - services - verbs: - - get - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -1094,6 +1140,12 @@ items: - list - update - watch + - apiGroups: + - "" + resources: + - persistentvolumeclaims/status + verbs: + - update - apiGroups: - "" resources: @@ -1269,6 +1321,13 @@ items: - create - patch - update + - apiGroups: + - resource.k8s.io + resources: + - resourceclaims/binding + verbs: + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/staging/README.md b/staging/README.md index 30972648c4eee..f4046cfed3c03 100644 --- a/staging/README.md +++ b/staging/README.md @@ -20,6 +20,7 @@ Repositories currently staged here: - [`k8s.io/controller-manager`](https://github.com/kubernetes/controller-manager) - [`k8s.io/cri-api`](https://github.com/kubernetes/cri-api) - [`k8s.io/cri-client`](https://github.com/kubernetes/cri-client) +- [`k8s.io/cri-streaming`](https://github.com/kubernetes/cri-streaming) - [`k8s.io/csi-translation-lib`](https://github.com/kubernetes/csi-translation-lib) - [`k8s.io/dynamic-resource-allocation`](https://github.com/kubernetes/dynamic-resource-allocation) - [`k8s.io/endpointslice`](https://github.com/kubernetes/endpointslice) @@ -62,7 +63,7 @@ import ( ### Adding the staging repository in `kubernetes/kubernetes`: 1. Send an email to the SIG Architecture [mailing - list](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture) + list](https://groups.google.com/a/kubernetes.io/g/sig-architecture) and the mailing list of the SIG which would own the repo requesting approval for creating the staging repository. diff --git a/staging/publishing/OWNERS b/staging/publishing/OWNERS index 9770ff5e9dde0..c9fa3002a5017 100644 --- a/staging/publishing/OWNERS +++ b/staging/publishing/OWNERS @@ -6,6 +6,7 @@ approvers: - dims - justaugustus reviewers: + - aojea - sttts - cpanato - dims diff --git a/staging/publishing/import-restrictions.yaml b/staging/publishing/import-restrictions.yaml index 2c7ea26f2e29e..a61a5da7e3416 100644 --- a/staging/publishing/import-restrictions.yaml +++ b/staging/publishing/import-restrictions.yaml @@ -38,7 +38,9 @@ allowedImports: - k8s.io/apimachinery - k8s.io/kube-openapi + - k8s.io/streaming - k8s.io/utils/clock + - k8s.io/utils/dump - k8s.io/utils/net - k8s.io/utils/strings - k8s.io/klog @@ -60,6 +62,7 @@ - k8s.io/code-generator - k8s.io/kube-openapi - k8s.io/klog + - k8s.io/utils/dump - k8s.io/utils/ptr - baseImportPath: "./staging/src/k8s.io/component-base" @@ -78,6 +81,7 @@ - k8s.io/client-go - k8s.io/klog - k8s.io/kube-openapi + - k8s.io/streaming - k8s.io/utils # prevent core machinery from taking explicit v1 references unless @@ -114,6 +118,7 @@ - k8s.io/client-go - k8s.io/component-base - k8s.io/kube-openapi + - k8s.io/streaming - k8s.io/utils - k8s.io/klog - k8s.io/kms @@ -137,6 +142,7 @@ - k8s.io/component-base - k8s.io/kube-aggregator - k8s.io/kube-openapi + - k8s.io/streaming - k8s.io/klog - k8s.io/utils @@ -151,6 +157,7 @@ - k8s.io/kubectl - k8s.io/kube-openapi - k8s.io/metrics + - k8s.io/streaming - k8s.io/utils - k8s.io/klog @@ -276,6 +283,8 @@ - k8s.io/klog - k8s.io/csi-translation-lib +# To prevent dependency leaking into downstream components and to maintain a +# minimal footprint, do NOT add any new dependencies to this module - baseImportPath: "./staging/src/k8s.io/component-helpers" allowedImports: - k8s.io/api @@ -323,6 +332,20 @@ - k8s.io/klog/v2 - k8s.io/utils +- baseImportPath: "./staging/src/k8s.io/cri-streaming" + allowedImports: + - k8s.io/cri-api + - k8s.io/cri-streaming + - k8s.io/klog/v2 + - k8s.io/streaming + - k8s.io/utils + +- baseImportPath: "./staging/src/k8s.io/streaming" + allowedImports: + - k8s.io/klog/v2 + - k8s.io/streaming + - k8s.io/utils + - baseImportPath: "./staging/src/k8s.io/externaljwt" allowedImports: - k8s.io/externaljwt diff --git a/staging/publishing/rules.yaml b/staging/publishing/rules.yaml index eed4896957679..e9b091f325178 100644 --- a/staging/publishing/rules.yaml +++ b/staging/publishing/rules.yaml @@ -1,35 +1,55 @@ rules: +- destination: streaming + branches: + - name: master + source: + branch: master + dirs: + - staging/src/k8s.io/streaming + - name: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/streaming + library: true - destination: apimachinery branches: - name: master + dependencies: + - repository: streaming + branch: master source: branch: master dirs: - staging/src/k8s.io/apimachinery - name: release-1.32 - go: 1.24.11 source: branch: release-1.32 dirs: - staging/src/k8s.io/apimachinery - name: release-1.33 - go: 1.24.11 source: branch: release-1.33 dirs: - staging/src/k8s.io/apimachinery - name: release-1.34 - go: 1.24.11 source: branch: release-1.34 dirs: - staging/src/k8s.io/apimachinery - name: release-1.35 - go: 1.25.7 source: branch: release-1.35 dirs: - staging/src/k8s.io/apimachinery + - name: release-1.36 + dependencies: + - repository: streaming + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/apimachinery library: true - destination: api branches: @@ -37,12 +57,13 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master source: branch: master dirs: - staging/src/k8s.io/api - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -51,7 +72,6 @@ rules: dirs: - staging/src/k8s.io/api - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -60,7 +80,6 @@ rules: dirs: - staging/src/k8s.io/api - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -69,7 +88,6 @@ rules: dirs: - staging/src/k8s.io/api - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -77,6 +95,16 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/api + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/api library: true - destination: client-go branches: @@ -84,6 +112,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master source: @@ -95,7 +125,6 @@ rules: go build -mod=mod ./... go test -mod=mod ./... - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -110,7 +139,6 @@ rules: go build -mod=mod ./... go test -mod=mod ./... - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -125,7 +153,6 @@ rules: go build -mod=mod ./... go test -mod=mod ./... - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -140,7 +167,6 @@ rules: go build -mod=mod ./... go test -mod=mod ./... - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -154,6 +180,22 @@ rules: # assumes GO111MODULE=on go build -mod=mod ./... go test -mod=mod ./... + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/client-go + smoke-test: | + # assumes GO111MODULE=on + go build -mod=mod ./... + go test -mod=mod ./... library: true - destination: code-generator branches: @@ -161,12 +203,13 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master source: branch: master dirs: - staging/src/k8s.io/code-generator - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -175,7 +218,6 @@ rules: dirs: - staging/src/k8s.io/code-generator - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -184,7 +226,6 @@ rules: dirs: - staging/src/k8s.io/code-generator - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -193,7 +234,6 @@ rules: dirs: - staging/src/k8s.io/code-generator - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -201,12 +241,24 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/code-generator + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/code-generator - destination: component-base branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -216,7 +268,6 @@ rules: dirs: - staging/src/k8s.io/component-base - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -229,7 +280,6 @@ rules: dirs: - staging/src/k8s.io/component-base - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -242,7 +292,6 @@ rules: dirs: - staging/src/k8s.io/component-base - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -255,7 +304,6 @@ rules: dirs: - staging/src/k8s.io/component-base - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -267,6 +315,20 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/component-base + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/component-base library: true - destination: component-helpers branches: @@ -274,6 +336,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -283,7 +347,6 @@ rules: dirs: - staging/src/k8s.io/component-helpers - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -296,7 +359,6 @@ rules: dirs: - staging/src/k8s.io/component-helpers - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -309,7 +371,6 @@ rules: dirs: - staging/src/k8s.io/component-helpers - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -322,7 +383,6 @@ rules: dirs: - staging/src/k8s.io/component-helpers - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -334,6 +394,20 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/component-helpers + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/component-helpers library: true - destination: kms branches: @@ -346,7 +420,6 @@ rules: dirs: - staging/src/k8s.io/kms - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -355,7 +428,6 @@ rules: dirs: - staging/src/k8s.io/kms - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -364,7 +436,6 @@ rules: dirs: - staging/src/k8s.io/kms - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -373,7 +444,6 @@ rules: dirs: - staging/src/k8s.io/kms - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -381,6 +451,14 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kms + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kms library: true - destination: apiserver branches: @@ -388,6 +466,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -401,7 +481,6 @@ rules: dirs: - staging/src/k8s.io/apiserver - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -418,7 +497,6 @@ rules: dirs: - staging/src/k8s.io/apiserver - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -435,7 +513,6 @@ rules: dirs: - staging/src/k8s.io/apiserver - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -452,7 +529,6 @@ rules: dirs: - staging/src/k8s.io/apiserver - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -468,6 +544,24 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/apiserver + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: kms + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/apiserver library: true - destination: kube-aggregator branches: @@ -475,6 +569,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -492,7 +588,6 @@ rules: dirs: - staging/src/k8s.io/kube-aggregator - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -513,7 +608,6 @@ rules: dirs: - staging/src/k8s.io/kube-aggregator - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -534,7 +628,6 @@ rules: dirs: - staging/src/k8s.io/kube-aggregator - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -555,7 +648,6 @@ rules: dirs: - staging/src/k8s.io/kube-aggregator - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -575,12 +667,36 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kube-aggregator + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: kms + branch: release-1.36 + - repository: code-generator + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kube-aggregator - destination: sample-apiserver branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -603,7 +719,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -629,7 +744,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -655,7 +769,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -681,7 +794,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -706,12 +818,41 @@ rules: smoke-test: | # assumes GO111MODULE=on go build -mod=mod . + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: code-generator + branch: release-1.36 + - repository: kms + branch: release-1.36 + - repository: component-base + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/sample-apiserver + required-packages: + - k8s.io/code-generator + smoke-test: | + # assumes GO111MODULE=on + go build -mod=mod . - destination: sample-controller branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -728,7 +869,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -748,7 +888,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -768,7 +907,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -788,7 +926,6 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -807,12 +944,35 @@ rules: smoke-test: | # assumes GO111MODULE=on go build -mod=mod . + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: code-generator + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/sample-controller + required-packages: + - k8s.io/code-generator + smoke-test: | + # assumes GO111MODULE=on + go build -mod=mod . - destination: apiextensions-apiserver branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -832,7 +992,6 @@ rules: required-packages: - k8s.io/code-generator - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -855,7 +1014,6 @@ rules: required-packages: - k8s.io/code-generator - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -878,7 +1036,6 @@ rules: required-packages: - k8s.io/code-generator - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -901,7 +1058,6 @@ rules: required-packages: - k8s.io/code-generator - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -923,12 +1079,38 @@ rules: - staging/src/k8s.io/apiextensions-apiserver required-packages: - k8s.io/code-generator + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: code-generator + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: kms + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/apiextensions-apiserver + required-packages: + - k8s.io/code-generator - destination: metrics branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -940,7 +1122,6 @@ rules: dirs: - staging/src/k8s.io/metrics - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -955,7 +1136,6 @@ rules: dirs: - staging/src/k8s.io/metrics - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -970,7 +1150,6 @@ rules: dirs: - staging/src/k8s.io/metrics - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -985,7 +1164,6 @@ rules: dirs: - staging/src/k8s.io/metrics - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -999,6 +1177,22 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/metrics + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: code-generator + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/metrics library: true - destination: cli-runtime branches: @@ -1008,6 +1202,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: client-go branch: master source: @@ -1015,7 +1211,6 @@ rules: dirs: - staging/src/k8s.io/cli-runtime - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1028,7 +1223,6 @@ rules: dirs: - staging/src/k8s.io/cli-runtime - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -1041,7 +1235,6 @@ rules: dirs: - staging/src/k8s.io/cli-runtime - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -1054,7 +1247,6 @@ rules: dirs: - staging/src/k8s.io/cli-runtime - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -1066,6 +1258,20 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/cli-runtime + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: client-go + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/cli-runtime library: true - destination: sample-cli-plugin branches: @@ -1075,6 +1281,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: cli-runtime branch: master - repository: client-go @@ -1084,7 +1292,6 @@ rules: dirs: - staging/src/k8s.io/sample-cli-plugin - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1099,7 +1306,6 @@ rules: dirs: - staging/src/k8s.io/sample-cli-plugin - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -1114,7 +1320,6 @@ rules: dirs: - staging/src/k8s.io/sample-cli-plugin - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -1129,7 +1334,6 @@ rules: dirs: - staging/src/k8s.io/sample-cli-plugin - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -1143,12 +1347,30 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/sample-cli-plugin + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: cli-runtime + branch: release-1.36 + - repository: client-go + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/sample-cli-plugin - destination: kube-proxy branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: component-base branch: master - repository: api @@ -1160,7 +1382,6 @@ rules: dirs: - staging/src/k8s.io/kube-proxy - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -1175,7 +1396,6 @@ rules: dirs: - staging/src/k8s.io/kube-proxy - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -1190,7 +1410,6 @@ rules: dirs: - staging/src/k8s.io/kube-proxy - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -1205,7 +1424,6 @@ rules: dirs: - staging/src/k8s.io/kube-proxy - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -1219,6 +1437,22 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kube-proxy + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kube-proxy library: true - destination: cri-api branches: @@ -1228,29 +1462,30 @@ rules: dirs: - staging/src/k8s.io/cri-api - name: release-1.32 - go: 1.24.11 source: branch: release-1.32 dirs: - staging/src/k8s.io/cri-api - name: release-1.33 - go: 1.24.11 source: branch: release-1.33 dirs: - staging/src/k8s.io/cri-api - name: release-1.34 - go: 1.24.11 source: branch: release-1.34 dirs: - staging/src/k8s.io/cri-api - name: release-1.35 - go: 1.25.7 source: branch: release-1.35 dirs: - staging/src/k8s.io/cri-api + - name: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/cri-api library: true - destination: cri-client branches: @@ -1271,7 +1506,6 @@ rules: dirs: - staging/src/k8s.io/cri-client - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1288,7 +1522,6 @@ rules: dirs: - staging/src/k8s.io/cri-client - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -1305,7 +1538,6 @@ rules: dirs: - staging/src/k8s.io/cri-client - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -1322,7 +1554,6 @@ rules: dirs: - staging/src/k8s.io/cri-client - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -1338,6 +1569,45 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/cri-client + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: cri-api + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/cri-client + library: true +- destination: cri-streaming + branches: + - name: master + dependencies: + - repository: cri-api + branch: master + - repository: streaming + branch: master + source: + branch: master + dirs: + - staging/src/k8s.io/cri-streaming + - name: release-1.36 + dependencies: + - repository: cri-api + branch: release-1.36 + - repository: streaming + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/cri-streaming library: true - destination: kubelet branches: @@ -1345,24 +1615,19 @@ rules: dependencies: - repository: apimachinery branch: master - - repository: apiserver + - repository: streaming branch: master - repository: api branch: master - repository: client-go branch: master - - repository: cri-api - branch: master - repository: component-base branch: master - - repository: kms - branch: master source: branch: master dirs: - staging/src/k8s.io/kubelet - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -1383,7 +1648,6 @@ rules: dirs: - staging/src/k8s.io/kubelet - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -1404,7 +1668,6 @@ rules: dirs: - staging/src/k8s.io/kubelet - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -1425,7 +1688,6 @@ rules: dirs: - staging/src/k8s.io/kubelet - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -1445,6 +1707,22 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kubelet + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kubelet library: true - destination: controller-manager branches: @@ -1454,6 +1732,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: client-go branch: master - repository: component-base @@ -1467,7 +1747,6 @@ rules: dirs: - staging/src/k8s.io/controller-manager - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1486,7 +1765,6 @@ rules: dirs: - staging/src/k8s.io/controller-manager - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -1505,7 +1783,6 @@ rules: dirs: - staging/src/k8s.io/controller-manager - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -1524,7 +1801,6 @@ rules: dirs: - staging/src/k8s.io/controller-manager - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -1542,6 +1818,26 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/controller-manager + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: kms + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/controller-manager library: true - destination: cloud-provider branches: @@ -1551,6 +1847,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: apiserver branch: master - repository: client-go @@ -1568,7 +1866,6 @@ rules: dirs: - staging/src/k8s.io/cloud-provider - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1591,7 +1888,6 @@ rules: dirs: - staging/src/k8s.io/cloud-provider - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -1614,7 +1910,6 @@ rules: dirs: - staging/src/k8s.io/cloud-provider - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -1637,7 +1932,6 @@ rules: dirs: - staging/src/k8s.io/cloud-provider - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -1659,6 +1953,30 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/cloud-provider + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: controller-manager + branch: release-1.36 + - repository: component-helpers + branch: release-1.36 + - repository: kms + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/cloud-provider library: true - destination: kube-controller-manager branches: @@ -1666,6 +1984,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: apiserver branch: master - repository: component-base @@ -1687,7 +2007,6 @@ rules: dirs: - staging/src/k8s.io/kube-controller-manager - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -1712,7 +2031,6 @@ rules: dirs: - staging/src/k8s.io/kube-controller-manager - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -1737,7 +2055,6 @@ rules: dirs: - staging/src/k8s.io/kube-controller-manager - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -1762,7 +2079,6 @@ rules: dirs: - staging/src/k8s.io/kube-controller-manager - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -1786,6 +2102,32 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kube-controller-manager + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: controller-manager + branch: release-1.36 + - repository: cloud-provider + branch: release-1.36 + - repository: component-helpers + branch: release-1.36 + - repository: kms + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kube-controller-manager library: true - destination: cluster-bootstrap branches: @@ -1793,6 +2135,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master source: @@ -1800,7 +2144,6 @@ rules: dirs: - staging/src/k8s.io/cluster-bootstrap - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -1811,7 +2154,6 @@ rules: dirs: - staging/src/k8s.io/cluster-bootstrap - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -1822,7 +2164,6 @@ rules: dirs: - staging/src/k8s.io/cluster-bootstrap - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -1833,7 +2174,6 @@ rules: dirs: - staging/src/k8s.io/cluster-bootstrap - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -1843,6 +2183,18 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/cluster-bootstrap + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: api + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/cluster-bootstrap library: true - destination: csi-translation-lib branches: @@ -1852,12 +2204,13 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master source: branch: master dirs: - staging/src/k8s.io/csi-translation-lib - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1868,7 +2221,6 @@ rules: dirs: - staging/src/k8s.io/csi-translation-lib - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -1879,7 +2231,6 @@ rules: dirs: - staging/src/k8s.io/csi-translation-lib - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -1890,7 +2241,6 @@ rules: dirs: - staging/src/k8s.io/csi-translation-lib - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -1900,6 +2250,18 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/csi-translation-lib + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/csi-translation-lib library: true - destination: mount-utils branches: @@ -1909,29 +2271,30 @@ rules: dirs: - staging/src/k8s.io/mount-utils - name: release-1.32 - go: 1.24.11 source: branch: release-1.32 dirs: - staging/src/k8s.io/mount-utils - name: release-1.33 - go: 1.24.11 source: branch: release-1.33 dirs: - staging/src/k8s.io/mount-utils - name: release-1.34 - go: 1.24.11 source: branch: release-1.34 dirs: - staging/src/k8s.io/mount-utils - name: release-1.35 - go: 1.25.7 source: branch: release-1.35 dirs: - staging/src/k8s.io/mount-utils + - name: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/mount-utils library: true - destination: kubectl branches: @@ -1941,6 +2304,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: cli-runtime branch: master - repository: client-go @@ -1958,7 +2323,6 @@ rules: dirs: - staging/src/k8s.io/kubectl - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -1981,7 +2345,6 @@ rules: dirs: - staging/src/k8s.io/kubectl - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -2004,7 +2367,6 @@ rules: dirs: - staging/src/k8s.io/kubectl - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -2027,7 +2389,6 @@ rules: dirs: - staging/src/k8s.io/kubectl - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -2049,6 +2410,30 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kubectl + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: cli-runtime + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: code-generator + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: component-helpers + branch: release-1.36 + - repository: metrics + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kubectl library: true - destination: pod-security-admission branches: @@ -2058,6 +2443,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: apiserver branch: master - repository: client-go @@ -2071,7 +2458,6 @@ rules: dirs: - staging/src/k8s.io/pod-security-admission - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -2090,7 +2476,6 @@ rules: dirs: - staging/src/k8s.io/pod-security-admission - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -2109,7 +2494,6 @@ rules: dirs: - staging/src/k8s.io/pod-security-admission - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -2128,7 +2512,6 @@ rules: dirs: - staging/src/k8s.io/pod-security-admission - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -2146,6 +2529,26 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/pod-security-admission + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: kms + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/pod-security-admission library: true - destination: dynamic-resource-allocation branches: @@ -2153,14 +2556,14 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: apiserver branch: master - repository: api branch: master - repository: client-go branch: master - - repository: cri-api - branch: master - repository: component-base branch: master - repository: component-helpers @@ -2174,7 +2577,6 @@ rules: dirs: - staging/src/k8s.io/dynamic-resource-allocation - name: release-1.32 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -2199,7 +2601,6 @@ rules: dirs: - staging/src/k8s.io/dynamic-resource-allocation - name: release-1.33 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.33 @@ -2224,7 +2625,6 @@ rules: dirs: - staging/src/k8s.io/dynamic-resource-allocation - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -2249,7 +2649,6 @@ rules: dirs: - staging/src/k8s.io/dynamic-resource-allocation - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -2273,12 +2672,38 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/dynamic-resource-allocation + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: component-helpers + branch: release-1.36 + - repository: kms + branch: release-1.36 + - repository: kubelet + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/dynamic-resource-allocation - destination: kube-scheduler branches: - name: master dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: component-base branch: master - repository: api @@ -2300,7 +2725,6 @@ rules: dirs: - staging/src/k8s.io/kube-scheduler - name: release-1.32 - go: 1.23.11 dependencies: - repository: apimachinery branch: release-1.32 @@ -2315,7 +2739,6 @@ rules: dirs: - staging/src/k8s.io/kube-scheduler - name: release-1.33 - go: 1.24.5 dependencies: - repository: apimachinery branch: release-1.33 @@ -2340,7 +2763,6 @@ rules: dirs: - staging/src/k8s.io/kube-scheduler - name: release-1.34 - go: 1.24.11 dependencies: - repository: apimachinery branch: release-1.34 @@ -2355,7 +2777,6 @@ rules: dirs: - staging/src/k8s.io/kube-scheduler - name: release-1.35 - go: 1.25.7 dependencies: - repository: apimachinery branch: release-1.35 @@ -2379,6 +2800,32 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/kube-scheduler + - name: release-1.36 + dependencies: + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: component-base + branch: release-1.36 + - repository: api + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: apiserver + branch: release-1.36 + - repository: component-helpers + branch: release-1.36 + - repository: dynamic-resource-allocation + branch: release-1.36 + - repository: kms + branch: release-1.36 + - repository: kubelet + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/kube-scheduler library: true - destination: endpointslice branches: @@ -2388,6 +2835,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: client-go branch: master - repository: component-base @@ -2397,7 +2846,6 @@ rules: dirs: - staging/src/k8s.io/endpointslice - name: release-1.32 - go: 1.24.11 dependencies: - repository: api branch: release-1.32 @@ -2412,7 +2860,6 @@ rules: dirs: - staging/src/k8s.io/endpointslice - name: release-1.33 - go: 1.24.11 dependencies: - repository: api branch: release-1.33 @@ -2427,7 +2874,6 @@ rules: dirs: - staging/src/k8s.io/endpointslice - name: release-1.34 - go: 1.24.11 dependencies: - repository: api branch: release-1.34 @@ -2442,7 +2888,6 @@ rules: dirs: - staging/src/k8s.io/endpointslice - name: release-1.35 - go: 1.25.7 dependencies: - repository: api branch: release-1.35 @@ -2456,6 +2901,22 @@ rules: branch: release-1.35 dirs: - staging/src/k8s.io/endpointslice + - name: release-1.36 + dependencies: + - repository: api + branch: release-1.36 + - repository: apimachinery + branch: release-1.36 + - repository: streaming + branch: release-1.36 + - repository: client-go + branch: release-1.36 + - repository: component-base + branch: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/endpointslice - destination: externaljwt branches: - name: master @@ -2464,29 +2925,34 @@ rules: dirs: - staging/src/k8s.io/externaljwt - name: release-1.32 - go: 1.24.11 source: branch: release-1.32 dirs: - staging/src/k8s.io/externaljwt - name: release-1.33 - go: 1.24.11 source: branch: release-1.33 dirs: - staging/src/k8s.io/externaljwt - name: release-1.34 - go: 1.24.11 source: branch: release-1.34 dirs: - staging/src/k8s.io/externaljwt - name: release-1.35 - go: 1.25.7 source: branch: release-1.35 dirs: - staging/src/k8s.io/externaljwt + - name: release-1.36 + source: + branch: release-1.36 + dirs: + - staging/src/k8s.io/externaljwt recursive-delete-patterns: - '*/.gitattributes' -default-go-version: 1.25.7 +# minimum version with GOTOOLCHAIN support +# should be older than the version used by any of our modules in any +# current branch, that way GOTOOLCHAIN logic will select the module's minimum +# version when working with that module +default-go-version: 1.21.0 diff --git a/staging/src/k8s.io/api/admission/v1/generated.proto b/staging/src/k8s.io/api/admission/v1/generated.proto index cd5c88badc8cc..38a0fcea60d57 100644 --- a/staging/src/k8s.io/api/admission/v1/generated.proto +++ b/staging/src/k8s.io/api/admission/v1/generated.proto @@ -35,12 +35,15 @@ message AdmissionRequest { // otherwise identical (parallel requests, requests when earlier requests did not modify etc) // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + // +optional optional string uid = 1; // kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) + // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 2; // resource is the fully-qualified resource being requested (for example, v1.pods) + // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 3; // subResource is the subresource being requested, if any (for example, "status" or "scale") @@ -90,9 +93,11 @@ message AdmissionRequest { // operation is the operation being performed. This may be different than the operation // requested. e.g. a patch can result in either a CREATE or UPDATE Operation. + // +optional optional string operation = 7; // userInfo is information about the requesting user + // +optional optional .k8s.io.api.authentication.v1.UserInfo userInfo = 8; // object is the object from the incoming request. @@ -121,9 +126,11 @@ message AdmissionRequest { message AdmissionResponse { // uid is an identifier for the individual request/response. // This must be copied over from the corresponding AdmissionRequest. + // +optional optional string uid = 1; // allowed indicates whether or not the admission request was permitted. + // +optional optional bool allowed = 2; // status is the result contains extra details into why an admission request was denied. diff --git a/staging/src/k8s.io/api/admission/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/admission/v1/generated.protomessage.pb.go deleted file mode 100644 index 4e1ec547d6121..0000000000000 --- a/staging/src/k8s.io/api/admission/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*AdmissionRequest) ProtoMessage() {} - -func (*AdmissionResponse) ProtoMessage() {} - -func (*AdmissionReview) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/admission/v1/types.go b/staging/src/k8s.io/api/admission/v1/types.go index 395672c390c17..34cbf2f59bd3b 100644 --- a/staging/src/k8s.io/api/admission/v1/types.go +++ b/staging/src/k8s.io/api/admission/v1/types.go @@ -43,10 +43,13 @@ type AdmissionRequest struct { // otherwise identical (parallel requests, requests when earlier requests did not modify etc) // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + // +optional UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` // kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) + // +optional Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"` // resource is the fully-qualified resource being requested (for example, v1.pods) + // +optional Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"` // subResource is the subresource being requested, if any (for example, "status" or "scale") // +optional @@ -91,8 +94,10 @@ type AdmissionRequest struct { Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"` // operation is the operation being performed. This may be different than the operation // requested. e.g. a patch can result in either a CREATE or UPDATE Operation. + // +optional Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"` // userInfo is information about the requesting user + // +optional UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"` // object is the object from the incoming request. // +optional @@ -117,9 +122,11 @@ type AdmissionRequest struct { type AdmissionResponse struct { // uid is an identifier for the individual request/response. // This must be copied over from the corresponding AdmissionRequest. + // +optional UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` // allowed indicates whether or not the admission request was permitted. + // +optional Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"` // status is the result contains extra details into why an admission request was denied. diff --git a/staging/src/k8s.io/api/admission/v1beta1/generated.proto b/staging/src/k8s.io/api/admission/v1beta1/generated.proto index 5af234993a403..9514719baf287 100644 --- a/staging/src/k8s.io/api/admission/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/admission/v1beta1/generated.proto @@ -35,12 +35,15 @@ message AdmissionRequest { // otherwise identical (parallel requests, requests when earlier requests did not modify etc) // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + // +optional optional string uid = 1; // kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) + // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 2; // resource is the fully-qualified resource being requested (for example, v1.pods) + // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 3; // subResource is the subresource being requested, if any (for example, "status" or "scale") @@ -90,9 +93,11 @@ message AdmissionRequest { // operation is the operation being performed. This may be different than the operation // requested. e.g. a patch can result in either a CREATE or UPDATE Operation. + // +optional optional string operation = 7; // userInfo is information about the requesting user + // +optional optional .k8s.io.api.authentication.v1.UserInfo userInfo = 8; // object is the object from the incoming request. @@ -121,9 +126,11 @@ message AdmissionRequest { message AdmissionResponse { // uid is an identifier for the individual request/response. // This should be copied over from the corresponding AdmissionRequest. + // +optional optional string uid = 1; // allowed indicates whether or not the admission request was permitted. + // +optional optional bool allowed = 2; // status is the result contains extra details into why an admission request was denied. diff --git a/staging/src/k8s.io/api/admission/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/admission/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 95c7022932260..0000000000000 --- a/staging/src/k8s.io/api/admission/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*AdmissionRequest) ProtoMessage() {} - -func (*AdmissionResponse) ProtoMessage() {} - -func (*AdmissionReview) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/admission/v1beta1/types.go b/staging/src/k8s.io/api/admission/v1beta1/types.go index 81941eb325e60..015dc8ba3ebe5 100644 --- a/staging/src/k8s.io/api/admission/v1beta1/types.go +++ b/staging/src/k8s.io/api/admission/v1beta1/types.go @@ -47,10 +47,13 @@ type AdmissionRequest struct { // otherwise identical (parallel requests, requests when earlier requests did not modify etc) // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + // +optional UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` // kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) + // +optional Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"` // resource is the fully-qualified resource being requested (for example, v1.pods) + // +optional Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"` // subResource is the subresource being requested, if any (for example, "status" or "scale") // +optional @@ -95,8 +98,10 @@ type AdmissionRequest struct { Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"` // operation is the operation being performed. This may be different than the operation // requested. e.g. a patch can result in either a CREATE or UPDATE Operation. + // +optional Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"` // userInfo is information about the requesting user + // +optional UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"` // object is the object from the incoming request. // +optional @@ -121,9 +126,11 @@ type AdmissionRequest struct { type AdmissionResponse struct { // uid is an identifier for the individual request/response. // This should be copied over from the corresponding AdmissionRequest. + // +optional UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` // allowed indicates whether or not the admission request was permitted. + // +optional Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"` // status is the result contains extra details into why an admission request was denied. diff --git a/staging/src/k8s.io/api/admissionregistration/v1/generated.pb.go b/staging/src/k8s.io/api/admissionregistration/v1/generated.pb.go index 91b2f1cbaee2f..e2def5040ce92 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/generated.pb.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/generated.pb.go @@ -32,20 +32,38 @@ import ( strings "strings" ) +func (m *ApplyConfiguration) Reset() { *m = ApplyConfiguration{} } + func (m *AuditAnnotation) Reset() { *m = AuditAnnotation{} } func (m *ExpressionWarning) Reset() { *m = ExpressionWarning{} } +func (m *JSONPatch) Reset() { *m = JSONPatch{} } + func (m *MatchCondition) Reset() { *m = MatchCondition{} } func (m *MatchResources) Reset() { *m = MatchResources{} } +func (m *MutatingAdmissionPolicy) Reset() { *m = MutatingAdmissionPolicy{} } + +func (m *MutatingAdmissionPolicyBinding) Reset() { *m = MutatingAdmissionPolicyBinding{} } + +func (m *MutatingAdmissionPolicyBindingList) Reset() { *m = MutatingAdmissionPolicyBindingList{} } + +func (m *MutatingAdmissionPolicyBindingSpec) Reset() { *m = MutatingAdmissionPolicyBindingSpec{} } + +func (m *MutatingAdmissionPolicyList) Reset() { *m = MutatingAdmissionPolicyList{} } + +func (m *MutatingAdmissionPolicySpec) Reset() { *m = MutatingAdmissionPolicySpec{} } + func (m *MutatingWebhook) Reset() { *m = MutatingWebhook{} } func (m *MutatingWebhookConfiguration) Reset() { *m = MutatingWebhookConfiguration{} } func (m *MutatingWebhookConfigurationList) Reset() { *m = MutatingWebhookConfigurationList{} } +func (m *Mutation) Reset() { *m = Mutation{} } + func (m *NamedRuleWithOperations) Reset() { *m = NamedRuleWithOperations{} } func (m *ParamKind) Reset() { *m = ParamKind{} } @@ -86,6 +104,34 @@ func (m *Variable) Reset() { *m = Variable{} } func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} } +func (m *ApplyConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ApplyConfiguration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ApplyConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Expression) + copy(dAtA[i:], m.Expression) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *AuditAnnotation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -152,6 +198,34 @@ func (m *ExpressionWarning) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *JSONPatch) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *JSONPatch) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *JSONPatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Expression) + copy(dAtA[i:], m.Expression) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *MatchCondition) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -267,7 +341,7 @@ func (m *MatchResources) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MutatingWebhook) Marshal() (dAtA []byte, err error) { +func (m *MutatingAdmissionPolicy) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -277,112 +351,18 @@ func (m *MutatingWebhook) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MutatingWebhook) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicy) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MutatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.MatchConditions) > 0 { - for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x62 - } - } - if m.ObjectSelector != nil { - { - size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x5a - } - if m.ReinvocationPolicy != nil { - i -= len(*m.ReinvocationPolicy) - copy(dAtA[i:], *m.ReinvocationPolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ReinvocationPolicy))) - i-- - dAtA[i] = 0x52 - } - if m.MatchPolicy != nil { - i -= len(*m.MatchPolicy) - copy(dAtA[i:], *m.MatchPolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.MatchPolicy))) - i-- - dAtA[i] = 0x4a - } - if len(m.AdmissionReviewVersions) > 0 { - for iNdEx := len(m.AdmissionReviewVersions) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.AdmissionReviewVersions[iNdEx]) - copy(dAtA[i:], m.AdmissionReviewVersions[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.AdmissionReviewVersions[iNdEx]))) - i-- - dAtA[i] = 0x42 - } - } - if m.TimeoutSeconds != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) - i-- - dAtA[i] = 0x38 - } - if m.SideEffects != nil { - i -= len(*m.SideEffects) - copy(dAtA[i:], *m.SideEffects) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.SideEffects))) - i-- - dAtA[i] = 0x32 - } - if m.NamespaceSelector != nil { - { - size, err := m.NamespaceSelector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.FailurePolicy != nil { - i -= len(*m.FailurePolicy) - copy(dAtA[i:], *m.FailurePolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) - i-- - dAtA[i] = 0x22 - } - if len(m.Rules) > 0 { - for iNdEx := len(m.Rules) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Rules[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - } { - size, err := m.ClientConfig.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -391,15 +371,20 @@ func (m *MutatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *MutatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { +func (m *MutatingAdmissionPolicyBinding) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -409,30 +394,26 @@ func (m *MutatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MutatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyBinding) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MutatingWebhookConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyBinding) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Webhooks) > 0 { - for iNdEx := len(m.Webhooks) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Webhooks[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 { size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -446,7 +427,7 @@ func (m *MutatingWebhookConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } -func (m *MutatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { +func (m *MutatingAdmissionPolicyBindingList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -456,12 +437,12 @@ func (m *MutatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MutatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyBindingList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MutatingWebhookConfigurationList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyBindingList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -493,7 +474,7 @@ func (m *MutatingWebhookConfigurationList) MarshalToSizedBuffer(dAtA []byte) (in return len(dAtA) - i, nil } -func (m *NamedRuleWithOperations) Marshal() (dAtA []byte, err error) { +func (m *MutatingAdmissionPolicyBindingSpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -503,39 +484,49 @@ func (m *NamedRuleWithOperations) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *NamedRuleWithOperations) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyBindingSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *NamedRuleWithOperations) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyBindingSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.RuleWithOperations.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.MatchResources != nil { + { + size, err := m.MatchResources.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a } - i-- - dAtA[i] = 0x12 - if len(m.ResourceNames) > 0 { - for iNdEx := len(m.ResourceNames) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.ResourceNames[iNdEx]) - copy(dAtA[i:], m.ResourceNames[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceNames[iNdEx]))) - i-- - dAtA[i] = 0xa + if m.ParamRef != nil { + { + size, err := m.ParamRef.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 } + i -= len(m.PolicyName) + copy(dAtA[i:], m.PolicyName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PolicyName))) + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ParamKind) Marshal() (dAtA []byte, err error) { +func (m *MutatingAdmissionPolicyList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -545,31 +536,45 @@ func (m *ParamKind) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ParamKind) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ParamKind) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicyList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i -= len(m.Kind) - copy(dAtA[i:], m.Kind) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) - i-- - dAtA[i] = 0x12 - i -= len(m.APIVersion) - copy(dAtA[i:], m.APIVersion) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ParamRef) Marshal() (dAtA []byte, err error) { - size := m.Size() + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MutatingAdmissionPolicySpec) Marshal() (dAtA []byte, err error) { + size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { @@ -578,26 +583,73 @@ func (m *ParamRef) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ParamRef) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicySpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ParamRef) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingAdmissionPolicySpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.ParameterNotFoundAction != nil { - i -= len(*m.ParameterNotFoundAction) - copy(dAtA[i:], *m.ParameterNotFoundAction) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ParameterNotFoundAction))) + i -= len(m.ReinvocationPolicy) + copy(dAtA[i:], m.ReinvocationPolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ReinvocationPolicy))) + i-- + dAtA[i] = 0x3a + if len(m.MatchConditions) > 0 { + for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if m.FailurePolicy != nil { + i -= len(*m.FailurePolicy) + copy(dAtA[i:], *m.FailurePolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a } - if m.Selector != nil { + if len(m.Mutations) > 0 { + for iNdEx := len(m.Mutations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Mutations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.Variables) > 0 { + for iNdEx := len(m.Variables) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Variables[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if m.MatchConstraints != nil { { - size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.MatchConstraints.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -605,22 +657,24 @@ func (m *ParamRef) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x12 + } + if m.ParamKind != nil { + { + size, err := m.ParamKind.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa } - i -= len(m.Namespace) - copy(dAtA[i:], m.Namespace) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *Rule) Marshal() (dAtA []byte, err error) { +func (m *MutatingWebhook) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -630,54 +684,129 @@ func (m *Rule) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Rule) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingWebhook) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Rule) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Scope != nil { - i -= len(*m.Scope) - copy(dAtA[i:], *m.Scope) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Scope))) + if len(m.MatchConditions) > 0 { + for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x62 + } + } + if m.ObjectSelector != nil { + { + size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x5a } - if len(m.Resources) > 0 { - for iNdEx := len(m.Resources) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Resources[iNdEx]) - copy(dAtA[i:], m.Resources[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resources[iNdEx]))) + if m.ReinvocationPolicy != nil { + i -= len(*m.ReinvocationPolicy) + copy(dAtA[i:], *m.ReinvocationPolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ReinvocationPolicy))) + i-- + dAtA[i] = 0x52 + } + if m.MatchPolicy != nil { + i -= len(*m.MatchPolicy) + copy(dAtA[i:], *m.MatchPolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.MatchPolicy))) + i-- + dAtA[i] = 0x4a + } + if len(m.AdmissionReviewVersions) > 0 { + for iNdEx := len(m.AdmissionReviewVersions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AdmissionReviewVersions[iNdEx]) + copy(dAtA[i:], m.AdmissionReviewVersions[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.AdmissionReviewVersions[iNdEx]))) i-- - dAtA[i] = 0x1a + dAtA[i] = 0x42 } } - if len(m.APIVersions) > 0 { - for iNdEx := len(m.APIVersions) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.APIVersions[iNdEx]) - copy(dAtA[i:], m.APIVersions[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersions[iNdEx]))) - i-- - dAtA[i] = 0x12 + if m.TimeoutSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) + i-- + dAtA[i] = 0x38 + } + if m.SideEffects != nil { + i -= len(*m.SideEffects) + copy(dAtA[i:], *m.SideEffects) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.SideEffects))) + i-- + dAtA[i] = 0x32 + } + if m.NamespaceSelector != nil { + { + size, err := m.NamespaceSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x2a } - if len(m.APIGroups) > 0 { - for iNdEx := len(m.APIGroups) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.APIGroups[iNdEx]) - copy(dAtA[i:], m.APIGroups[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIGroups[iNdEx]))) + if m.FailurePolicy != nil { + i -= len(*m.FailurePolicy) + copy(dAtA[i:], *m.FailurePolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) + i-- + dAtA[i] = 0x22 + } + if len(m.Rules) > 0 { + for iNdEx := len(m.Rules) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Rules[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0xa + dAtA[i] = 0x1a + } + } + { + size, err := m.ClientConfig.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *RuleWithOperations) Marshal() (dAtA []byte, err error) { +func (m *MutatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -687,84 +816,44 @@ func (m *RuleWithOperations) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *RuleWithOperations) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *RuleWithOperations) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingWebhookConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Rule.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Operations) > 0 { - for iNdEx := len(m.Operations) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Operations[iNdEx]) - copy(dAtA[i:], m.Operations[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operations[iNdEx]))) + if len(m.Webhooks) > 0 { + for iNdEx := len(m.Webhooks) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Webhooks[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0xa + dAtA[i] = 0x12 } } - return len(dAtA) - i, nil -} - -func (m *ServiceReference) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ServiceReference) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ServiceReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Port != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.Port)) - i-- - dAtA[i] = 0x20 - } - if m.Path != nil { - i -= len(*m.Path) - copy(dAtA[i:], *m.Path) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Path))) - i-- - dAtA[i] = 0x1a + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x12 - i -= len(m.Namespace) - copy(dAtA[i:], m.Namespace) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *TypeChecking) Marshal() (dAtA []byte, err error) { +func (m *MutatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -774,20 +863,20 @@ func (m *TypeChecking) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *TypeChecking) MarshalTo(dAtA []byte) (int, error) { +func (m *MutatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *TypeChecking) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MutatingWebhookConfigurationList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.ExpressionWarnings) > 0 { - for iNdEx := len(m.ExpressionWarnings) - 1; iNdEx >= 0; iNdEx-- { + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.ExpressionWarnings[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -795,13 +884,23 @@ func (m *TypeChecking) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0xa + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicy) Marshal() (dAtA []byte, err error) { +func (m *Mutation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -811,50 +910,49 @@ func (m *ValidatingAdmissionPolicy) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicy) MarshalTo(dAtA []byte) (int, error) { +func (m *Mutation) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *Mutation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.JSONPatch != nil { + { + size, err := m.JSONPatch.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 } - i-- - dAtA[i] = 0x1a - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.ApplyConfiguration != nil { + { + size, err := m.ApplyConfiguration.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a } + i -= len(m.PatchType) + copy(dAtA[i:], m.PatchType) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PatchType))) i-- dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicyBinding) Marshal() (dAtA []byte, err error) { +func (m *NamedRuleWithOperations) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -864,18 +962,18 @@ func (m *ValidatingAdmissionPolicyBinding) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicyBinding) MarshalTo(dAtA []byte) (int, error) { +func (m *NamedRuleWithOperations) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicyBinding) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *NamedRuleWithOperations) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.RuleWithOperations.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -884,20 +982,19 @@ func (m *ValidatingAdmissionPolicyBinding) MarshalToSizedBuffer(dAtA []byte) (in } i-- dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if len(m.ResourceNames) > 0 { + for iNdEx := len(m.ResourceNames) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ResourceNames[iNdEx]) + copy(dAtA[i:], m.ResourceNames[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceNames[iNdEx]))) + i-- + dAtA[i] = 0xa } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i-- - dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicyBindingList) Marshal() (dAtA []byte, err error) { +func (m *ParamKind) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -907,44 +1004,30 @@ func (m *ValidatingAdmissionPolicyBindingList) Marshal() (dAtA []byte, err error return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicyBindingList) MarshalTo(dAtA []byte) (int, error) { +func (m *ParamKind) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicyBindingList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ParamKind) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x12 + i -= len(m.APIVersion) + copy(dAtA[i:], m.APIVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicyBindingSpec) Marshal() (dAtA []byte, err error) { +func (m *ParamRef) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -954,40 +1037,26 @@ func (m *ValidatingAdmissionPolicyBindingSpec) Marshal() (dAtA []byte, err error return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicyBindingSpec) MarshalTo(dAtA []byte) (int, error) { +func (m *ParamRef) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicyBindingSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ParamRef) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.ValidationActions) > 0 { - for iNdEx := len(m.ValidationActions) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.ValidationActions[iNdEx]) - copy(dAtA[i:], m.ValidationActions[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.ValidationActions[iNdEx]))) - i-- - dAtA[i] = 0x22 - } - } - if m.MatchResources != nil { - { - size, err := m.MatchResources.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + if m.ParameterNotFoundAction != nil { + i -= len(*m.ParameterNotFoundAction) + copy(dAtA[i:], *m.ParameterNotFoundAction) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ParameterNotFoundAction))) i-- - dAtA[i] = 0x1a + dAtA[i] = 0x22 } - if m.ParamRef != nil { + if m.Selector != nil { { - size, err := m.ParamRef.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -995,17 +1064,22 @@ func (m *ValidatingAdmissionPolicyBindingSpec) MarshalToSizedBuffer(dAtA []byte) i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0x1a } - i -= len(m.PolicyName) - copy(dAtA[i:], m.PolicyName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.PolicyName))) + i -= len(m.Namespace) + copy(dAtA[i:], m.Namespace) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicyList) Marshal() (dAtA []byte, err error) { +func (m *Rule) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1015,44 +1089,54 @@ func (m *ValidatingAdmissionPolicyList) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicyList) MarshalTo(dAtA []byte) (int, error) { +func (m *Rule) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicyList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *Rule) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.Scope != nil { + i -= len(*m.Scope) + copy(dAtA[i:], *m.Scope) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Scope))) + i-- + dAtA[i] = 0x22 + } + if len(m.Resources) > 0 { + for iNdEx := len(m.Resources) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Resources[iNdEx]) + copy(dAtA[i:], m.Resources[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resources[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.APIVersions) > 0 { + for iNdEx := len(m.APIVersions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.APIVersions[iNdEx]) + copy(dAtA[i:], m.APIVersions[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersions[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.APIGroups) > 0 { + for iNdEx := len(m.APIGroups) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.APIGroups[iNdEx]) + copy(dAtA[i:], m.APIGroups[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIGroups[iNdEx]))) + i-- + dAtA[i] = 0xa } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i-- - dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicySpec) Marshal() (dAtA []byte, err error) { +func (m *RuleWithOperations) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1062,107 +1146,84 @@ func (m *ValidatingAdmissionPolicySpec) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicySpec) MarshalTo(dAtA []byte) (int, error) { +func (m *RuleWithOperations) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicySpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *RuleWithOperations) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Variables) > 0 { - for iNdEx := len(m.Variables) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Variables[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } - } - if len(m.MatchConditions) > 0 { - for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x32 + { + size, err := m.Rule.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - if len(m.AuditAnnotations) > 0 { - for iNdEx := len(m.AuditAnnotations) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.AuditAnnotations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i-- + dAtA[i] = 0x12 + if len(m.Operations) > 0 { + for iNdEx := len(m.Operations) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Operations[iNdEx]) + copy(dAtA[i:], m.Operations[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operations[iNdEx]))) i-- - dAtA[i] = 0x2a + dAtA[i] = 0xa } } - if m.FailurePolicy != nil { - i -= len(*m.FailurePolicy) - copy(dAtA[i:], *m.FailurePolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) - i-- - dAtA[i] = 0x22 - } - if len(m.Validations) > 0 { - for iNdEx := len(m.Validations) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Validations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } + return len(dAtA) - i, nil +} + +func (m *ServiceReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - if m.MatchConstraints != nil { - { - size, err := m.MatchConstraints.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + return dAtA[:n], nil +} + +func (m *ServiceReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServiceReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Port != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Port)) i-- - dAtA[i] = 0x12 + dAtA[i] = 0x20 } - if m.ParamKind != nil { - { - size, err := m.ParamKind.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + if m.Path != nil { + i -= len(*m.Path) + copy(dAtA[i:], *m.Path) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Path))) i-- - dAtA[i] = 0xa + dAtA[i] = 0x1a } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x12 + i -= len(m.Namespace) + copy(dAtA[i:], m.Namespace) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingAdmissionPolicyStatus) Marshal() (dAtA []byte, err error) { +func (m *TypeChecking) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1172,20 +1233,20 @@ func (m *ValidatingAdmissionPolicyStatus) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingAdmissionPolicyStatus) MarshalTo(dAtA []byte) (int, error) { +func (m *TypeChecking) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingAdmissionPolicyStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *TypeChecking) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Conditions) > 0 { - for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + if len(m.ExpressionWarnings) > 0 { + for iNdEx := len(m.ExpressionWarnings) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ExpressionWarnings[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1193,28 +1254,13 @@ func (m *ValidatingAdmissionPolicyStatus) MarshalToSizedBuffer(dAtA []byte) (int i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a - } - } - if m.TypeChecking != nil { - { - size, err := m.TypeChecking.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + dAtA[i] = 0xa } - i-- - dAtA[i] = 0x12 } - i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) - i-- - dAtA[i] = 0x8 return len(dAtA) - i, nil } -func (m *ValidatingWebhook) Marshal() (dAtA []byte, err error) { +func (m *ValidatingAdmissionPolicy) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1224,105 +1270,38 @@ func (m *ValidatingWebhook) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingWebhook) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicy) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.MatchConditions) > 0 { - for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x5a + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - if m.ObjectSelector != nil { - { - size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x52 - } - if m.MatchPolicy != nil { - i -= len(*m.MatchPolicy) - copy(dAtA[i:], *m.MatchPolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.MatchPolicy))) - i-- - dAtA[i] = 0x4a - } - if len(m.AdmissionReviewVersions) > 0 { - for iNdEx := len(m.AdmissionReviewVersions) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.AdmissionReviewVersions[iNdEx]) - copy(dAtA[i:], m.AdmissionReviewVersions[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.AdmissionReviewVersions[iNdEx]))) - i-- - dAtA[i] = 0x42 - } - } - if m.TimeoutSeconds != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) - i-- - dAtA[i] = 0x38 - } - if m.SideEffects != nil { - i -= len(*m.SideEffects) - copy(dAtA[i:], *m.SideEffects) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.SideEffects))) - i-- - dAtA[i] = 0x32 - } - if m.NamespaceSelector != nil { - { - size, err := m.NamespaceSelector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.FailurePolicy != nil { - i -= len(*m.FailurePolicy) - copy(dAtA[i:], *m.FailurePolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) - i-- - dAtA[i] = 0x22 - } - if len(m.Rules) > 0 { - for iNdEx := len(m.Rules) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Rules[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 { - size, err := m.ClientConfig.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1330,16 +1309,11 @@ func (m *ValidatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { +func (m *ValidatingAdmissionPolicyBinding) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1349,30 +1323,26 @@ func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyBinding) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingWebhookConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyBinding) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Webhooks) > 0 { - for iNdEx := len(m.Webhooks) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Webhooks[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 { size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -1386,7 +1356,7 @@ func (m *ValidatingWebhookConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } -func (m *ValidatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { +func (m *ValidatingAdmissionPolicyBindingList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1396,12 +1366,12 @@ func (m *ValidatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *ValidatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyBindingList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ValidatingWebhookConfigurationList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyBindingList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1433,7 +1403,7 @@ func (m *ValidatingWebhookConfigurationList) MarshalToSizedBuffer(dAtA []byte) ( return len(dAtA) - i, nil } -func (m *Validation) Marshal() (dAtA []byte, err error) { +func (m *ValidatingAdmissionPolicyBindingSpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1443,42 +1413,58 @@ func (m *Validation) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Validation) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyBindingSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Validation) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyBindingSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i -= len(m.MessageExpression) - copy(dAtA[i:], m.MessageExpression) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MessageExpression))) - i-- - dAtA[i] = 0x22 - if m.Reason != nil { - i -= len(*m.Reason) - copy(dAtA[i:], *m.Reason) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Reason))) + if len(m.ValidationActions) > 0 { + for iNdEx := len(m.ValidationActions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ValidationActions[iNdEx]) + copy(dAtA[i:], m.ValidationActions[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ValidationActions[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + if m.MatchResources != nil { + { + size, err := m.MatchResources.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0x1a } - i -= len(m.Message) - copy(dAtA[i:], m.Message) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) - i-- - dAtA[i] = 0x12 - i -= len(m.Expression) - copy(dAtA[i:], m.Expression) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression))) + if m.ParamRef != nil { + { + size, err := m.ParamRef.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + i -= len(m.PolicyName) + copy(dAtA[i:], m.PolicyName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PolicyName))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *Variable) Marshal() (dAtA []byte, err error) { +func (m *ValidatingAdmissionPolicyList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1488,30 +1474,44 @@ func (m *Variable) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Variable) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Variable) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicyList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i -= len(m.Expression) - copy(dAtA[i:], m.Expression) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression))) - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *WebhookClientConfig) Marshal() (dAtA []byte, err error) { +func (m *ValidatingAdmissionPolicySpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1521,355 +1521,610 @@ func (m *WebhookClientConfig) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *WebhookClientConfig) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicySpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *WebhookClientConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ValidatingAdmissionPolicySpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.URL != nil { - i -= len(*m.URL) - copy(dAtA[i:], *m.URL) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.URL))) - i-- - dAtA[i] = 0x1a + if len(m.Variables) > 0 { + for iNdEx := len(m.Variables) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Variables[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } } - if m.CABundle != nil { - i -= len(m.CABundle) - copy(dAtA[i:], m.CABundle) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.CABundle))) - i-- - dAtA[i] = 0x12 + if len(m.MatchConditions) > 0 { + for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } } - if m.Service != nil { - { - size, err := m.Service.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if len(m.AuditAnnotations) > 0 { + for iNdEx := len(m.AuditAnnotations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AuditAnnotations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a } + } + if m.FailurePolicy != nil { + i -= len(*m.FailurePolicy) + copy(dAtA[i:], *m.FailurePolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) i-- - dAtA[i] = 0xa + dAtA[i] = 0x22 } - return len(dAtA) - i, nil -} - -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - offset -= sovGenerated(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ + if len(m.Validations) > 0 { + for iNdEx := len(m.Validations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Validations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } } - dAtA[offset] = uint8(v) - return base -} -func (m *AuditAnnotation) Size() (n int) { - if m == nil { - return 0 + if m.MatchConstraints != nil { + { + size, err := m.MatchConstraints.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } - var l int - _ = l - l = len(m.Key) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.ValueExpression) - n += 1 + l + sovGenerated(uint64(l)) - return n + if m.ParamKind != nil { + { + size, err := m.ParamKind.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } -func (m *ExpressionWarning) Size() (n int) { - if m == nil { - return 0 +func (m *ValidatingAdmissionPolicyStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - l = len(m.FieldRef) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Warning) - n += 1 + l + sovGenerated(uint64(l)) - return n + return dAtA[:n], nil } -func (m *MatchCondition) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Expression) - n += 1 + l + sovGenerated(uint64(l)) - return n +func (m *ValidatingAdmissionPolicyStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MatchResources) Size() (n int) { - if m == nil { - return 0 - } +func (m *ValidatingAdmissionPolicyStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if m.NamespaceSelector != nil { - l = m.NamespaceSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ObjectSelector != nil { - l = m.ObjectSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if len(m.ResourceRules) > 0 { - for _, e := range m.ResourceRules { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Conditions) > 0 { + for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a } } - if len(m.ExcludeResourceRules) > 0 { - for _, e := range m.ExcludeResourceRules { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if m.TypeChecking != nil { + { + size, err := m.TypeChecking.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 } - if m.MatchPolicy != nil { - l = len(*m.MatchPolicy) - n += 1 + l + sovGenerated(uint64(l)) - } - return n + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil } -func (m *MutatingWebhook) Size() (n int) { - if m == nil { - return 0 +func (m *ValidatingWebhook) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } + return dAtA[:n], nil +} + +func (m *ValidatingWebhook) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ValidatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = m.ClientConfig.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Rules) > 0 { - for _, e := range m.Rules { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.MatchConditions) > 0 { + for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a } } - if m.FailurePolicy != nil { - l = len(*m.FailurePolicy) - n += 1 + l + sovGenerated(uint64(l)) - } - if m.NamespaceSelector != nil { - l = m.NamespaceSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.SideEffects != nil { - l = len(*m.SideEffects) - n += 1 + l + sovGenerated(uint64(l)) + if m.ObjectSelector != nil { + { + size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 } - if m.TimeoutSeconds != nil { - n += 1 + sovGenerated(uint64(*m.TimeoutSeconds)) + if m.MatchPolicy != nil { + i -= len(*m.MatchPolicy) + copy(dAtA[i:], *m.MatchPolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.MatchPolicy))) + i-- + dAtA[i] = 0x4a } if len(m.AdmissionReviewVersions) > 0 { - for _, s := range m.AdmissionReviewVersions { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) + for iNdEx := len(m.AdmissionReviewVersions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AdmissionReviewVersions[iNdEx]) + copy(dAtA[i:], m.AdmissionReviewVersions[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.AdmissionReviewVersions[iNdEx]))) + i-- + dAtA[i] = 0x42 } } - if m.MatchPolicy != nil { - l = len(*m.MatchPolicy) - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ReinvocationPolicy != nil { - l = len(*m.ReinvocationPolicy) - n += 1 + l + sovGenerated(uint64(l)) + if m.TimeoutSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) + i-- + dAtA[i] = 0x38 } - if m.ObjectSelector != nil { - l = m.ObjectSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) + if m.SideEffects != nil { + i -= len(*m.SideEffects) + copy(dAtA[i:], *m.SideEffects) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.SideEffects))) + i-- + dAtA[i] = 0x32 } - if len(m.MatchConditions) > 0 { - for _, e := range m.MatchConditions { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if m.NamespaceSelector != nil { + { + size, err := m.NamespaceSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x2a } - return n -} - -func (m *MutatingWebhookConfiguration) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Webhooks) > 0 { - for _, e := range m.Webhooks { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } + if m.FailurePolicy != nil { + i -= len(*m.FailurePolicy) + copy(dAtA[i:], *m.FailurePolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) + i-- + dAtA[i] = 0x22 } - return n -} - -func (m *MutatingWebhookConfigurationList) Size() (n int) { - if m == nil { - return 0 + if len(m.Rules) > 0 { + for iNdEx := len(m.Rules) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Rules[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } } - var l int - _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.ClientConfig.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *NamedRuleWithOperations) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.ResourceNames) > 0 { - for _, s := range m.ResourceNames { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } +func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - l = m.RuleWithOperations.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n + return dAtA[:n], nil } -func (m *ParamKind) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.APIVersion) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Kind) - n += 1 + l + sovGenerated(uint64(l)) - return n +func (m *ValidatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ParamRef) Size() (n int) { - if m == nil { - return 0 - } +func (m *ValidatingWebhookConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Namespace) - n += 1 + l + sovGenerated(uint64(l)) - if m.Selector != nil { - l = m.Selector.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Webhooks) > 0 { + for iNdEx := len(m.Webhooks) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Webhooks[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } } - if m.ParameterNotFoundAction != nil { - l = len(*m.ParameterNotFoundAction) - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *Rule) Size() (n int) { - if m == nil { - return 0 +func (m *ValidatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } + return dAtA[:n], nil +} + +func (m *ValidatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ValidatingWebhookConfigurationList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.APIGroups) > 0 { - for _, s := range m.APIGroups { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } - if len(m.APIVersions) > 0 { - for _, s := range m.APIVersions { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } } - if len(m.Resources) > 0 { - for _, s := range m.Resources { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - if m.Scope != nil { - l = len(*m.Scope) - n += 1 + l + sovGenerated(uint64(l)) - } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *RuleWithOperations) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Operations) > 0 { - for _, s := range m.Operations { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } +func (m *Validation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - l = m.Rule.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n + return dAtA[:n], nil } -func (m *ServiceReference) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Namespace) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.Path != nil { - l = len(*m.Path) - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Port != nil { - n += 1 + sovGenerated(uint64(*m.Port)) - } - return n +func (m *Validation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *TypeChecking) Size() (n int) { - if m == nil { - return 0 - } +func (m *Validation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.ExpressionWarnings) > 0 { - for _, e := range m.ExpressionWarnings { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } + i -= len(m.MessageExpression) + copy(dAtA[i:], m.MessageExpression) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.MessageExpression))) + i-- + dAtA[i] = 0x22 + if m.Reason != nil { + i -= len(*m.Reason) + copy(dAtA[i:], *m.Reason) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Reason))) + i-- + dAtA[i] = 0x1a + } + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x12 + i -= len(m.Expression) + copy(dAtA[i:], m.Expression) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Variable) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Variable) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Variable) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Expression) + copy(dAtA[i:], m.Expression) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression))) + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *WebhookClientConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WebhookClientConfig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WebhookClientConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.URL != nil { + i -= len(*m.URL) + copy(dAtA[i:], *m.URL) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.URL))) + i-- + dAtA[i] = 0x1a + } + if m.CABundle != nil { + i -= len(m.CABundle) + copy(dAtA[i:], m.CABundle) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CABundle))) + i-- + dAtA[i] = 0x12 + } + if m.Service != nil { + { + size, err := m.Service.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ApplyConfiguration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Expression) + n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *ValidatingAdmissionPolicy) Size() (n int) { +func (m *AuditAnnotation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ValueExpression) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ExpressionWarning) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.FieldRef) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Warning) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *JSONPatch) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Expression) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *MatchCondition) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Expression) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *MatchResources) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NamespaceSelector != nil { + l = m.NamespaceSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ObjectSelector != nil { + l = m.ObjectSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.ResourceRules) > 0 { + for _, e := range m.ResourceRules { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.ExcludeResourceRules) > 0 { + for _, e := range m.ExcludeResourceRules { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.MatchPolicy != nil { + l = len(*m.MatchPolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *MutatingAdmissionPolicy) Size() (n int) { if m == nil { return 0 } @@ -1879,12 +2134,10 @@ func (m *ValidatingAdmissionPolicy) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = m.Spec.Size() n += 1 + l + sovGenerated(uint64(l)) - l = m.Status.Size() - n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *ValidatingAdmissionPolicyBinding) Size() (n int) { +func (m *MutatingAdmissionPolicyBinding) Size() (n int) { if m == nil { return 0 } @@ -1897,7 +2150,7 @@ func (m *ValidatingAdmissionPolicyBinding) Size() (n int) { return n } -func (m *ValidatingAdmissionPolicyBindingList) Size() (n int) { +func (m *MutatingAdmissionPolicyBindingList) Size() (n int) { if m == nil { return 0 } @@ -1914,7 +2167,7 @@ func (m *ValidatingAdmissionPolicyBindingList) Size() (n int) { return n } -func (m *ValidatingAdmissionPolicyBindingSpec) Size() (n int) { +func (m *MutatingAdmissionPolicyBindingSpec) Size() (n int) { if m == nil { return 0 } @@ -1930,16 +2183,10 @@ func (m *ValidatingAdmissionPolicyBindingSpec) Size() (n int) { l = m.MatchResources.Size() n += 1 + l + sovGenerated(uint64(l)) } - if len(m.ValidationActions) > 0 { - for _, s := range m.ValidationActions { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } return n } -func (m *ValidatingAdmissionPolicyList) Size() (n int) { +func (m *MutatingAdmissionPolicyList) Size() (n int) { if m == nil { return 0 } @@ -1956,7 +2203,7 @@ func (m *ValidatingAdmissionPolicyList) Size() (n int) { return n } -func (m *ValidatingAdmissionPolicySpec) Size() (n int) { +func (m *MutatingAdmissionPolicySpec) Size() (n int) { if m == nil { return 0 } @@ -1970,58 +2217,34 @@ func (m *ValidatingAdmissionPolicySpec) Size() (n int) { l = m.MatchConstraints.Size() n += 1 + l + sovGenerated(uint64(l)) } - if len(m.Validations) > 0 { - for _, e := range m.Validations { + if len(m.Variables) > 0 { + for _, e := range m.Variables { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } - if m.FailurePolicy != nil { - l = len(*m.FailurePolicy) - n += 1 + l + sovGenerated(uint64(l)) - } - if len(m.AuditAnnotations) > 0 { - for _, e := range m.AuditAnnotations { + if len(m.Mutations) > 0 { + for _, e := range m.Mutations { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } + if m.FailurePolicy != nil { + l = len(*m.FailurePolicy) + n += 1 + l + sovGenerated(uint64(l)) + } if len(m.MatchConditions) > 0 { for _, e := range m.MatchConditions { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } - if len(m.Variables) > 0 { - for _, e := range m.Variables { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } + l = len(m.ReinvocationPolicy) + n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *ValidatingAdmissionPolicyStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - n += 1 + sovGenerated(uint64(m.ObservedGeneration)) - if m.TypeChecking != nil { - l = m.TypeChecking.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if len(m.Conditions) > 0 { - for _, e := range m.Conditions { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *ValidatingWebhook) Size() (n int) { +func (m *MutatingWebhook) Size() (n int) { if m == nil { return 0 } @@ -2062,6 +2285,10 @@ func (m *ValidatingWebhook) Size() (n int) { l = len(*m.MatchPolicy) n += 1 + l + sovGenerated(uint64(l)) } + if m.ReinvocationPolicy != nil { + l = len(*m.ReinvocationPolicy) + n += 1 + l + sovGenerated(uint64(l)) + } if m.ObjectSelector != nil { l = m.ObjectSelector.Size() n += 1 + l + sovGenerated(uint64(l)) @@ -2075,7 +2302,7 @@ func (m *ValidatingWebhook) Size() (n int) { return n } -func (m *ValidatingWebhookConfiguration) Size() (n int) { +func (m *MutatingWebhookConfiguration) Size() (n int) { if m == nil { return 0 } @@ -2092,7 +2319,7 @@ func (m *ValidatingWebhookConfiguration) Size() (n int) { return n } -func (m *ValidatingWebhookConfigurationList) Size() (n int) { +func (m *MutatingWebhookConfigurationList) Size() (n int) { if m == nil { return 0 } @@ -2109,500 +2336,1983 @@ func (m *ValidatingWebhookConfigurationList) Size() (n int) { return n } -func (m *Validation) Size() (n int) { +func (m *Mutation) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Expression) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Message) + l = len(m.PatchType) n += 1 + l + sovGenerated(uint64(l)) - if m.Reason != nil { - l = len(*m.Reason) + if m.ApplyConfiguration != nil { + l = m.ApplyConfiguration.Size() n += 1 + l + sovGenerated(uint64(l)) } - l = len(m.MessageExpression) + if m.JSONPatch != nil { + l = m.JSONPatch.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *NamedRuleWithOperations) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ResourceNames) > 0 { + for _, s := range m.ResourceNames { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = m.RuleWithOperations.Size() n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *Variable) Size() (n int) { +func (m *ParamKind) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Name) + l = len(m.APIVersion) n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Expression) + l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *WebhookClientConfig) Size() (n int) { +func (m *ParamRef) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.Service != nil { - l = m.Service.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.CABundle != nil { - l = len(m.CABundle) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Namespace) + n += 1 + l + sovGenerated(uint64(l)) + if m.Selector != nil { + l = m.Selector.Size() n += 1 + l + sovGenerated(uint64(l)) } - if m.URL != nil { - l = len(*m.URL) + if m.ParameterNotFoundAction != nil { + l = len(*m.ParameterNotFoundAction) n += 1 + l + sovGenerated(uint64(l)) } return n } -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *AuditAnnotation) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&AuditAnnotation{`, - `Key:` + fmt.Sprintf("%v", this.Key) + `,`, - `ValueExpression:` + fmt.Sprintf("%v", this.ValueExpression) + `,`, - `}`, - }, "") - return s -} -func (this *ExpressionWarning) String() string { - if this == nil { - return "nil" +func (m *Rule) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&ExpressionWarning{`, - `FieldRef:` + fmt.Sprintf("%v", this.FieldRef) + `,`, - `Warning:` + fmt.Sprintf("%v", this.Warning) + `,`, - `}`, - }, "") - return s -} -func (this *MatchCondition) String() string { - if this == nil { - return "nil" + var l int + _ = l + if len(m.APIGroups) > 0 { + for _, s := range m.APIGroups { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } } - s := strings.Join([]string{`&MatchCondition{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, - `}`, - }, "") - return s -} -func (this *MatchResources) String() string { - if this == nil { - return "nil" + if len(m.APIVersions) > 0 { + for _, s := range m.APIVersions { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForResourceRules := "[]NamedRuleWithOperations{" - for _, f := range this.ResourceRules { - repeatedStringForResourceRules += strings.Replace(strings.Replace(f.String(), "NamedRuleWithOperations", "NamedRuleWithOperations", 1), `&`, ``, 1) + "," + if len(m.Resources) > 0 { + for _, s := range m.Resources { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForResourceRules += "}" - repeatedStringForExcludeResourceRules := "[]NamedRuleWithOperations{" - for _, f := range this.ExcludeResourceRules { - repeatedStringForExcludeResourceRules += strings.Replace(strings.Replace(f.String(), "NamedRuleWithOperations", "NamedRuleWithOperations", 1), `&`, ``, 1) + "," + if m.Scope != nil { + l = len(*m.Scope) + n += 1 + l + sovGenerated(uint64(l)) } - repeatedStringForExcludeResourceRules += "}" - s := strings.Join([]string{`&MatchResources{`, - `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `ResourceRules:` + repeatedStringForResourceRules + `,`, - `ExcludeResourceRules:` + repeatedStringForExcludeResourceRules + `,`, - `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, - `}`, - }, "") - return s + return n } -func (this *MutatingWebhook) String() string { - if this == nil { - return "nil" - } - repeatedStringForRules := "[]RuleWithOperations{" - for _, f := range this.Rules { - repeatedStringForRules += strings.Replace(strings.Replace(f.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + "," + +func (m *RuleWithOperations) Size() (n int) { + if m == nil { + return 0 } - repeatedStringForRules += "}" - repeatedStringForMatchConditions := "[]MatchCondition{" - for _, f := range this.MatchConditions { - repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," + var l int + _ = l + if len(m.Operations) > 0 { + for _, s := range m.Operations { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForMatchConditions += "}" - s := strings.Join([]string{`&MutatingWebhook{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, - `Rules:` + repeatedStringForRules + `,`, - `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, - `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, - `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, - `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, - `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, - `ReinvocationPolicy:` + valueToStringGenerated(this.ReinvocationPolicy) + `,`, - `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `MatchConditions:` + repeatedStringForMatchConditions + `,`, - `}`, - }, "") - return s + l = m.Rule.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n } -func (this *MutatingWebhookConfiguration) String() string { - if this == nil { - return "nil" + +func (m *ServiceReference) Size() (n int) { + if m == nil { + return 0 } - repeatedStringForWebhooks := "[]MutatingWebhook{" - for _, f := range this.Webhooks { - repeatedStringForWebhooks += strings.Replace(strings.Replace(f.String(), "MutatingWebhook", "MutatingWebhook", 1), `&`, ``, 1) + "," + var l int + _ = l + l = len(m.Namespace) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if m.Path != nil { + l = len(*m.Path) + n += 1 + l + sovGenerated(uint64(l)) } - repeatedStringForWebhooks += "}" - s := strings.Join([]string{`&MutatingWebhookConfiguration{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Webhooks:` + repeatedStringForWebhooks + `,`, - `}`, - }, "") - return s + if m.Port != nil { + n += 1 + sovGenerated(uint64(*m.Port)) + } + return n } -func (this *MutatingWebhookConfigurationList) String() string { - if this == nil { - return "nil" + +func (m *TypeChecking) Size() (n int) { + if m == nil { + return 0 } - repeatedStringForItems := "[]MutatingWebhookConfiguration{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "MutatingWebhookConfiguration", "MutatingWebhookConfiguration", 1), `&`, ``, 1) + "," + var l int + _ = l + if len(m.ExpressionWarnings) > 0 { + for _, e := range m.ExpressionWarnings { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForItems += "}" - s := strings.Join([]string{`&MutatingWebhookConfigurationList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s + return n } -func (this *NamedRuleWithOperations) String() string { - if this == nil { - return "nil" + +func (m *ValidatingAdmissionPolicy) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&NamedRuleWithOperations{`, - `ResourceNames:` + fmt.Sprintf("%v", this.ResourceNames) + `,`, - `RuleWithOperations:` + strings.Replace(strings.Replace(this.RuleWithOperations.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n } -func (this *ParamKind) String() string { - if this == nil { - return "nil" + +func (m *ValidatingAdmissionPolicyBinding) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&ParamKind{`, - `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, - `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, - `}`, - }, "") - return s + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n } -func (this *ParamRef) String() string { - if this == nil { - return "nil" + +func (m *ValidatingAdmissionPolicyBindingList) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&ParamRef{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, - `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `ParameterNotFoundAction:` + valueToStringGenerated(this.ParameterNotFoundAction) + `,`, - `}`, - }, "") - return s -} -func (this *Rule) String() string { - if this == nil { - return "nil" + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - s := strings.Join([]string{`&Rule{`, - `APIGroups:` + fmt.Sprintf("%v", this.APIGroups) + `,`, - `APIVersions:` + fmt.Sprintf("%v", this.APIVersions) + `,`, - `Resources:` + fmt.Sprintf("%v", this.Resources) + `,`, - `Scope:` + valueToStringGenerated(this.Scope) + `,`, - `}`, - }, "") - return s + return n } -func (this *RuleWithOperations) String() string { - if this == nil { - return "nil" + +func (m *ValidatingAdmissionPolicyBindingSpec) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&RuleWithOperations{`, - `Operations:` + fmt.Sprintf("%v", this.Operations) + `,`, - `Rule:` + strings.Replace(strings.Replace(this.Rule.String(), "Rule", "Rule", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ServiceReference) String() string { - if this == nil { - return "nil" + var l int + _ = l + l = len(m.PolicyName) + n += 1 + l + sovGenerated(uint64(l)) + if m.ParamRef != nil { + l = m.ParamRef.Size() + n += 1 + l + sovGenerated(uint64(l)) } - s := strings.Join([]string{`&ServiceReference{`, - `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Path:` + valueToStringGenerated(this.Path) + `,`, - `Port:` + valueToStringGenerated(this.Port) + `,`, - `}`, - }, "") - return s -} -func (this *TypeChecking) String() string { - if this == nil { - return "nil" + if m.MatchResources != nil { + l = m.MatchResources.Size() + n += 1 + l + sovGenerated(uint64(l)) } - repeatedStringForExpressionWarnings := "[]ExpressionWarning{" - for _, f := range this.ExpressionWarnings { - repeatedStringForExpressionWarnings += strings.Replace(strings.Replace(f.String(), "ExpressionWarning", "ExpressionWarning", 1), `&`, ``, 1) + "," + if len(m.ValidationActions) > 0 { + for _, s := range m.ValidationActions { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForExpressionWarnings += "}" - s := strings.Join([]string{`&TypeChecking{`, - `ExpressionWarnings:` + repeatedStringForExpressionWarnings + `,`, - `}`, - }, "") - return s + return n } -func (this *ValidatingAdmissionPolicy) String() string { - if this == nil { - return "nil" + +func (m *ValidatingAdmissionPolicyList) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&ValidatingAdmissionPolicy{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ValidatingAdmissionPolicySpec", "ValidatingAdmissionPolicySpec", 1), `&`, ``, 1) + `,`, - `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ValidatingAdmissionPolicyStatus", "ValidatingAdmissionPolicyStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ValidatingAdmissionPolicyBinding) String() string { - if this == nil { - return "nil" + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - s := strings.Join([]string{`&ValidatingAdmissionPolicyBinding{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ValidatingAdmissionPolicyBindingSpec", "ValidatingAdmissionPolicyBindingSpec", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s + return n } -func (this *ValidatingAdmissionPolicyBindingList) String() string { - if this == nil { - return "nil" - } - repeatedStringForItems := "[]ValidatingAdmissionPolicyBinding{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ValidatingAdmissionPolicyBinding", "ValidatingAdmissionPolicyBinding", 1), `&`, ``, 1) + "," + +func (m *ValidatingAdmissionPolicySpec) Size() (n int) { + if m == nil { + return 0 } - repeatedStringForItems += "}" - s := strings.Join([]string{`&ValidatingAdmissionPolicyBindingList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s -} -func (this *ValidatingAdmissionPolicyBindingSpec) String() string { - if this == nil { - return "nil" + var l int + _ = l + if m.ParamKind != nil { + l = m.ParamKind.Size() + n += 1 + l + sovGenerated(uint64(l)) } - s := strings.Join([]string{`&ValidatingAdmissionPolicyBindingSpec{`, - `PolicyName:` + fmt.Sprintf("%v", this.PolicyName) + `,`, - `ParamRef:` + strings.Replace(this.ParamRef.String(), "ParamRef", "ParamRef", 1) + `,`, - `MatchResources:` + strings.Replace(this.MatchResources.String(), "MatchResources", "MatchResources", 1) + `,`, - `ValidationActions:` + fmt.Sprintf("%v", this.ValidationActions) + `,`, - `}`, - }, "") - return s -} -func (this *ValidatingAdmissionPolicyList) String() string { - if this == nil { - return "nil" + if m.MatchConstraints != nil { + l = m.MatchConstraints.Size() + n += 1 + l + sovGenerated(uint64(l)) } - repeatedStringForItems := "[]ValidatingAdmissionPolicy{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ValidatingAdmissionPolicy", "ValidatingAdmissionPolicy", 1), `&`, ``, 1) + "," + if len(m.Validations) > 0 { + for _, e := range m.Validations { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForItems += "}" - s := strings.Join([]string{`&ValidatingAdmissionPolicyList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s -} -func (this *ValidatingAdmissionPolicySpec) String() string { - if this == nil { - return "nil" + if m.FailurePolicy != nil { + l = len(*m.FailurePolicy) + n += 1 + l + sovGenerated(uint64(l)) } - repeatedStringForValidations := "[]Validation{" - for _, f := range this.Validations { - repeatedStringForValidations += strings.Replace(strings.Replace(f.String(), "Validation", "Validation", 1), `&`, ``, 1) + "," + if len(m.AuditAnnotations) > 0 { + for _, e := range m.AuditAnnotations { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForValidations += "}" - repeatedStringForAuditAnnotations := "[]AuditAnnotation{" - for _, f := range this.AuditAnnotations { - repeatedStringForAuditAnnotations += strings.Replace(strings.Replace(f.String(), "AuditAnnotation", "AuditAnnotation", 1), `&`, ``, 1) + "," + if len(m.MatchConditions) > 0 { + for _, e := range m.MatchConditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForAuditAnnotations += "}" - repeatedStringForMatchConditions := "[]MatchCondition{" - for _, f := range this.MatchConditions { - repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," + if len(m.Variables) > 0 { + for _, e := range m.Variables { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForMatchConditions += "}" - repeatedStringForVariables := "[]Variable{" - for _, f := range this.Variables { - repeatedStringForVariables += strings.Replace(strings.Replace(f.String(), "Variable", "Variable", 1), `&`, ``, 1) + "," + return n +} + +func (m *ValidatingAdmissionPolicyStatus) Size() (n int) { + if m == nil { + return 0 } - repeatedStringForVariables += "}" - s := strings.Join([]string{`&ValidatingAdmissionPolicySpec{`, - `ParamKind:` + strings.Replace(this.ParamKind.String(), "ParamKind", "ParamKind", 1) + `,`, - `MatchConstraints:` + strings.Replace(this.MatchConstraints.String(), "MatchResources", "MatchResources", 1) + `,`, - `Validations:` + repeatedStringForValidations + `,`, - `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, - `AuditAnnotations:` + repeatedStringForAuditAnnotations + `,`, - `MatchConditions:` + repeatedStringForMatchConditions + `,`, - `Variables:` + repeatedStringForVariables + `,`, - `}`, - }, "") - return s + var l int + _ = l + n += 1 + sovGenerated(uint64(m.ObservedGeneration)) + if m.TypeChecking != nil { + l = m.TypeChecking.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n } -func (this *ValidatingAdmissionPolicyStatus) String() string { + +func (m *ValidatingWebhook) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = m.ClientConfig.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Rules) > 0 { + for _, e := range m.Rules { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.FailurePolicy != nil { + l = len(*m.FailurePolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.NamespaceSelector != nil { + l = m.NamespaceSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.SideEffects != nil { + l = len(*m.SideEffects) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.TimeoutSeconds != nil { + n += 1 + sovGenerated(uint64(*m.TimeoutSeconds)) + } + if len(m.AdmissionReviewVersions) > 0 { + for _, s := range m.AdmissionReviewVersions { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.MatchPolicy != nil { + l = len(*m.MatchPolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ObjectSelector != nil { + l = m.ObjectSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.MatchConditions) > 0 { + for _, e := range m.MatchConditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ValidatingWebhookConfiguration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Webhooks) > 0 { + for _, e := range m.Webhooks { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ValidatingWebhookConfigurationList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *Validation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Expression) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + if m.Reason != nil { + l = len(*m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.MessageExpression) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Variable) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Expression) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *WebhookClientConfig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Service != nil { + l = m.Service.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.CABundle != nil { + l = len(m.CABundle) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.URL != nil { + l = len(*m.URL) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *ApplyConfiguration) String() string { if this == nil { return "nil" } - repeatedStringForConditions := "[]Condition{" - for _, f := range this.Conditions { - repeatedStringForConditions += fmt.Sprintf("%v", f) + "," - } - repeatedStringForConditions += "}" - s := strings.Join([]string{`&ValidatingAdmissionPolicyStatus{`, - `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, - `TypeChecking:` + strings.Replace(this.TypeChecking.String(), "TypeChecking", "TypeChecking", 1) + `,`, - `Conditions:` + repeatedStringForConditions + `,`, + s := strings.Join([]string{`&ApplyConfiguration{`, + `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, `}`, }, "") return s } -func (this *ValidatingWebhook) String() string { +func (this *AuditAnnotation) String() string { if this == nil { return "nil" } - repeatedStringForRules := "[]RuleWithOperations{" - for _, f := range this.Rules { - repeatedStringForRules += strings.Replace(strings.Replace(f.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + "," - } - repeatedStringForRules += "}" - repeatedStringForMatchConditions := "[]MatchCondition{" - for _, f := range this.MatchConditions { - repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," - } - repeatedStringForMatchConditions += "}" - s := strings.Join([]string{`&ValidatingWebhook{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, - `Rules:` + repeatedStringForRules + `,`, - `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, - `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, - `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, - `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, - `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, - `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `MatchConditions:` + repeatedStringForMatchConditions + `,`, + s := strings.Join([]string{`&AuditAnnotation{`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `ValueExpression:` + fmt.Sprintf("%v", this.ValueExpression) + `,`, `}`, }, "") return s } -func (this *ValidatingWebhookConfiguration) String() string { +func (this *ExpressionWarning) String() string { if this == nil { return "nil" } - repeatedStringForWebhooks := "[]ValidatingWebhook{" - for _, f := range this.Webhooks { - repeatedStringForWebhooks += strings.Replace(strings.Replace(f.String(), "ValidatingWebhook", "ValidatingWebhook", 1), `&`, ``, 1) + "," - } - repeatedStringForWebhooks += "}" - s := strings.Join([]string{`&ValidatingWebhookConfiguration{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Webhooks:` + repeatedStringForWebhooks + `,`, + s := strings.Join([]string{`&ExpressionWarning{`, + `FieldRef:` + fmt.Sprintf("%v", this.FieldRef) + `,`, + `Warning:` + fmt.Sprintf("%v", this.Warning) + `,`, `}`, }, "") return s } -func (this *ValidatingWebhookConfigurationList) String() string { +func (this *JSONPatch) String() string { if this == nil { return "nil" } - repeatedStringForItems := "[]ValidatingWebhookConfiguration{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ValidatingWebhookConfiguration", "ValidatingWebhookConfiguration", 1), `&`, ``, 1) + "," - } - repeatedStringForItems += "}" - s := strings.Join([]string{`&ValidatingWebhookConfigurationList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, + s := strings.Join([]string{`&JSONPatch{`, + `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, `}`, }, "") return s } -func (this *Validation) String() string { +func (this *MatchCondition) String() string { if this == nil { return "nil" } - s := strings.Join([]string{`&Validation{`, + s := strings.Join([]string{`&MatchCondition{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, - `Message:` + fmt.Sprintf("%v", this.Message) + `,`, - `Reason:` + valueToStringGenerated(this.Reason) + `,`, - `MessageExpression:` + fmt.Sprintf("%v", this.MessageExpression) + `,`, `}`, }, "") return s } -func (this *Variable) String() string { +func (this *MatchResources) String() string { if this == nil { return "nil" } - s := strings.Join([]string{`&Variable{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, - `}`, - }, "") - return s -} -func (this *WebhookClientConfig) String() string { - if this == nil { - return "nil" + repeatedStringForResourceRules := "[]NamedRuleWithOperations{" + for _, f := range this.ResourceRules { + repeatedStringForResourceRules += strings.Replace(strings.Replace(f.String(), "NamedRuleWithOperations", "NamedRuleWithOperations", 1), `&`, ``, 1) + "," + } + repeatedStringForResourceRules += "}" + repeatedStringForExcludeResourceRules := "[]NamedRuleWithOperations{" + for _, f := range this.ExcludeResourceRules { + repeatedStringForExcludeResourceRules += strings.Replace(strings.Replace(f.String(), "NamedRuleWithOperations", "NamedRuleWithOperations", 1), `&`, ``, 1) + "," + } + repeatedStringForExcludeResourceRules += "}" + s := strings.Join([]string{`&MatchResources{`, + `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `ResourceRules:` + repeatedStringForResourceRules + `,`, + `ExcludeResourceRules:` + repeatedStringForExcludeResourceRules + `,`, + `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, + `}`, + }, "") + return s +} +func (this *MutatingAdmissionPolicy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MutatingAdmissionPolicy{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "MutatingAdmissionPolicySpec", "MutatingAdmissionPolicySpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *MutatingAdmissionPolicyBinding) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MutatingAdmissionPolicyBinding{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "MutatingAdmissionPolicyBindingSpec", "MutatingAdmissionPolicyBindingSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *MutatingAdmissionPolicyBindingList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]MutatingAdmissionPolicyBinding{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "MutatingAdmissionPolicyBinding", "MutatingAdmissionPolicyBinding", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&MutatingAdmissionPolicyBindingList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *MutatingAdmissionPolicyBindingSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MutatingAdmissionPolicyBindingSpec{`, + `PolicyName:` + fmt.Sprintf("%v", this.PolicyName) + `,`, + `ParamRef:` + strings.Replace(this.ParamRef.String(), "ParamRef", "ParamRef", 1) + `,`, + `MatchResources:` + strings.Replace(this.MatchResources.String(), "MatchResources", "MatchResources", 1) + `,`, + `}`, + }, "") + return s +} +func (this *MutatingAdmissionPolicyList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]MutatingAdmissionPolicy{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "MutatingAdmissionPolicy", "MutatingAdmissionPolicy", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&MutatingAdmissionPolicyList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *MutatingAdmissionPolicySpec) String() string { + if this == nil { + return "nil" + } + repeatedStringForVariables := "[]Variable{" + for _, f := range this.Variables { + repeatedStringForVariables += strings.Replace(strings.Replace(f.String(), "Variable", "Variable", 1), `&`, ``, 1) + "," + } + repeatedStringForVariables += "}" + repeatedStringForMutations := "[]Mutation{" + for _, f := range this.Mutations { + repeatedStringForMutations += strings.Replace(strings.Replace(f.String(), "Mutation", "Mutation", 1), `&`, ``, 1) + "," + } + repeatedStringForMutations += "}" + repeatedStringForMatchConditions := "[]MatchCondition{" + for _, f := range this.MatchConditions { + repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," + } + repeatedStringForMatchConditions += "}" + s := strings.Join([]string{`&MutatingAdmissionPolicySpec{`, + `ParamKind:` + strings.Replace(this.ParamKind.String(), "ParamKind", "ParamKind", 1) + `,`, + `MatchConstraints:` + strings.Replace(this.MatchConstraints.String(), "MatchResources", "MatchResources", 1) + `,`, + `Variables:` + repeatedStringForVariables + `,`, + `Mutations:` + repeatedStringForMutations + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `MatchConditions:` + repeatedStringForMatchConditions + `,`, + `ReinvocationPolicy:` + fmt.Sprintf("%v", this.ReinvocationPolicy) + `,`, + `}`, + }, "") + return s +} +func (this *MutatingWebhook) String() string { + if this == nil { + return "nil" + } + repeatedStringForRules := "[]RuleWithOperations{" + for _, f := range this.Rules { + repeatedStringForRules += strings.Replace(strings.Replace(f.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + "," + } + repeatedStringForRules += "}" + repeatedStringForMatchConditions := "[]MatchCondition{" + for _, f := range this.MatchConditions { + repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," + } + repeatedStringForMatchConditions += "}" + s := strings.Join([]string{`&MutatingWebhook{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, + `Rules:` + repeatedStringForRules + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, + `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, + `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, + `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, + `ReinvocationPolicy:` + valueToStringGenerated(this.ReinvocationPolicy) + `,`, + `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `MatchConditions:` + repeatedStringForMatchConditions + `,`, + `}`, + }, "") + return s +} +func (this *MutatingWebhookConfiguration) String() string { + if this == nil { + return "nil" + } + repeatedStringForWebhooks := "[]MutatingWebhook{" + for _, f := range this.Webhooks { + repeatedStringForWebhooks += strings.Replace(strings.Replace(f.String(), "MutatingWebhook", "MutatingWebhook", 1), `&`, ``, 1) + "," + } + repeatedStringForWebhooks += "}" + s := strings.Join([]string{`&MutatingWebhookConfiguration{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Webhooks:` + repeatedStringForWebhooks + `,`, + `}`, + }, "") + return s +} +func (this *MutatingWebhookConfigurationList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]MutatingWebhookConfiguration{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "MutatingWebhookConfiguration", "MutatingWebhookConfiguration", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&MutatingWebhookConfigurationList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *Mutation) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Mutation{`, + `PatchType:` + fmt.Sprintf("%v", this.PatchType) + `,`, + `ApplyConfiguration:` + strings.Replace(this.ApplyConfiguration.String(), "ApplyConfiguration", "ApplyConfiguration", 1) + `,`, + `JSONPatch:` + strings.Replace(this.JSONPatch.String(), "JSONPatch", "JSONPatch", 1) + `,`, + `}`, + }, "") + return s +} +func (this *NamedRuleWithOperations) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&NamedRuleWithOperations{`, + `ResourceNames:` + fmt.Sprintf("%v", this.ResourceNames) + `,`, + `RuleWithOperations:` + strings.Replace(strings.Replace(this.RuleWithOperations.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ParamKind) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ParamKind{`, + `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `}`, + }, "") + return s +} +func (this *ParamRef) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ParamRef{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `ParameterNotFoundAction:` + valueToStringGenerated(this.ParameterNotFoundAction) + `,`, + `}`, + }, "") + return s +} +func (this *Rule) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Rule{`, + `APIGroups:` + fmt.Sprintf("%v", this.APIGroups) + `,`, + `APIVersions:` + fmt.Sprintf("%v", this.APIVersions) + `,`, + `Resources:` + fmt.Sprintf("%v", this.Resources) + `,`, + `Scope:` + valueToStringGenerated(this.Scope) + `,`, + `}`, + }, "") + return s +} +func (this *RuleWithOperations) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RuleWithOperations{`, + `Operations:` + fmt.Sprintf("%v", this.Operations) + `,`, + `Rule:` + strings.Replace(strings.Replace(this.Rule.String(), "Rule", "Rule", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ServiceReference) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ServiceReference{`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Path:` + valueToStringGenerated(this.Path) + `,`, + `Port:` + valueToStringGenerated(this.Port) + `,`, + `}`, + }, "") + return s +} +func (this *TypeChecking) String() string { + if this == nil { + return "nil" + } + repeatedStringForExpressionWarnings := "[]ExpressionWarning{" + for _, f := range this.ExpressionWarnings { + repeatedStringForExpressionWarnings += strings.Replace(strings.Replace(f.String(), "ExpressionWarning", "ExpressionWarning", 1), `&`, ``, 1) + "," + } + repeatedStringForExpressionWarnings += "}" + s := strings.Join([]string{`&TypeChecking{`, + `ExpressionWarnings:` + repeatedStringForExpressionWarnings + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ValidatingAdmissionPolicy{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ValidatingAdmissionPolicySpec", "ValidatingAdmissionPolicySpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ValidatingAdmissionPolicyStatus", "ValidatingAdmissionPolicyStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicyBinding) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ValidatingAdmissionPolicyBinding{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ValidatingAdmissionPolicyBindingSpec", "ValidatingAdmissionPolicyBindingSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicyBindingList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]ValidatingAdmissionPolicyBinding{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ValidatingAdmissionPolicyBinding", "ValidatingAdmissionPolicyBinding", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&ValidatingAdmissionPolicyBindingList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicyBindingSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ValidatingAdmissionPolicyBindingSpec{`, + `PolicyName:` + fmt.Sprintf("%v", this.PolicyName) + `,`, + `ParamRef:` + strings.Replace(this.ParamRef.String(), "ParamRef", "ParamRef", 1) + `,`, + `MatchResources:` + strings.Replace(this.MatchResources.String(), "MatchResources", "MatchResources", 1) + `,`, + `ValidationActions:` + fmt.Sprintf("%v", this.ValidationActions) + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicyList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]ValidatingAdmissionPolicy{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ValidatingAdmissionPolicy", "ValidatingAdmissionPolicy", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&ValidatingAdmissionPolicyList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicySpec) String() string { + if this == nil { + return "nil" + } + repeatedStringForValidations := "[]Validation{" + for _, f := range this.Validations { + repeatedStringForValidations += strings.Replace(strings.Replace(f.String(), "Validation", "Validation", 1), `&`, ``, 1) + "," + } + repeatedStringForValidations += "}" + repeatedStringForAuditAnnotations := "[]AuditAnnotation{" + for _, f := range this.AuditAnnotations { + repeatedStringForAuditAnnotations += strings.Replace(strings.Replace(f.String(), "AuditAnnotation", "AuditAnnotation", 1), `&`, ``, 1) + "," + } + repeatedStringForAuditAnnotations += "}" + repeatedStringForMatchConditions := "[]MatchCondition{" + for _, f := range this.MatchConditions { + repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," + } + repeatedStringForMatchConditions += "}" + repeatedStringForVariables := "[]Variable{" + for _, f := range this.Variables { + repeatedStringForVariables += strings.Replace(strings.Replace(f.String(), "Variable", "Variable", 1), `&`, ``, 1) + "," + } + repeatedStringForVariables += "}" + s := strings.Join([]string{`&ValidatingAdmissionPolicySpec{`, + `ParamKind:` + strings.Replace(this.ParamKind.String(), "ParamKind", "ParamKind", 1) + `,`, + `MatchConstraints:` + strings.Replace(this.MatchConstraints.String(), "MatchResources", "MatchResources", 1) + `,`, + `Validations:` + repeatedStringForValidations + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `AuditAnnotations:` + repeatedStringForAuditAnnotations + `,`, + `MatchConditions:` + repeatedStringForMatchConditions + `,`, + `Variables:` + repeatedStringForVariables + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingAdmissionPolicyStatus) String() string { + if this == nil { + return "nil" + } + repeatedStringForConditions := "[]Condition{" + for _, f := range this.Conditions { + repeatedStringForConditions += fmt.Sprintf("%v", f) + "," + } + repeatedStringForConditions += "}" + s := strings.Join([]string{`&ValidatingAdmissionPolicyStatus{`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, + `TypeChecking:` + strings.Replace(this.TypeChecking.String(), "TypeChecking", "TypeChecking", 1) + `,`, + `Conditions:` + repeatedStringForConditions + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingWebhook) String() string { + if this == nil { + return "nil" + } + repeatedStringForRules := "[]RuleWithOperations{" + for _, f := range this.Rules { + repeatedStringForRules += strings.Replace(strings.Replace(f.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + "," + } + repeatedStringForRules += "}" + repeatedStringForMatchConditions := "[]MatchCondition{" + for _, f := range this.MatchConditions { + repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + "," + } + repeatedStringForMatchConditions += "}" + s := strings.Join([]string{`&ValidatingWebhook{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, + `Rules:` + repeatedStringForRules + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, + `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, + `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, + `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, + `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `MatchConditions:` + repeatedStringForMatchConditions + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingWebhookConfiguration) String() string { + if this == nil { + return "nil" + } + repeatedStringForWebhooks := "[]ValidatingWebhook{" + for _, f := range this.Webhooks { + repeatedStringForWebhooks += strings.Replace(strings.Replace(f.String(), "ValidatingWebhook", "ValidatingWebhook", 1), `&`, ``, 1) + "," + } + repeatedStringForWebhooks += "}" + s := strings.Join([]string{`&ValidatingWebhookConfiguration{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Webhooks:` + repeatedStringForWebhooks + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingWebhookConfigurationList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]ValidatingWebhookConfiguration{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ValidatingWebhookConfiguration", "ValidatingWebhookConfiguration", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&ValidatingWebhookConfigurationList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *Validation) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Validation{`, + `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `Reason:` + valueToStringGenerated(this.Reason) + `,`, + `MessageExpression:` + fmt.Sprintf("%v", this.MessageExpression) + `,`, + `}`, + }, "") + return s +} +func (this *Variable) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Variable{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, + `}`, + }, "") + return s +} +func (this *WebhookClientConfig) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&WebhookClientConfig{`, + `Service:` + strings.Replace(this.Service.String(), "ServiceReference", "ServiceReference", 1) + `,`, + `CABundle:` + valueToStringGenerated(this.CABundle) + `,`, + `URL:` + valueToStringGenerated(this.URL) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *ApplyConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplyConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplyConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Expression = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AuditAnnotation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AuditAnnotation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AuditAnnotation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValueExpression", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValueExpression = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExpressionWarning) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExpressionWarning: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExpressionWarning: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldRef", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldRef = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Warning", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Warning = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JSONPatch) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JSONPatch: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JSONPatch: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Expression = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MatchCondition) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MatchCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MatchCondition: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Expression = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MatchResources) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MatchResources: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MatchResources: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NamespaceSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NamespaceSelector == nil { + m.NamespaceSelector = &v1.LabelSelector{} + } + if err := m.NamespaceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ObjectSelector == nil { + m.ObjectSelector = &v1.LabelSelector{} + } + if err := m.ObjectSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceRules = append(m.ResourceRules, NamedRuleWithOperations{}) + if err := m.ResourceRules[len(m.ResourceRules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExcludeResourceRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExcludeResourceRules = append(m.ExcludeResourceRules, NamedRuleWithOperations{}) + if err := m.ExcludeResourceRules[len(m.ExcludeResourceRules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MatchPolicy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := MatchPolicyType(dAtA[iNdEx:postIndex]) + m.MatchPolicy = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MutatingAdmissionPolicy) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MutatingAdmissionPolicy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MutatingAdmissionPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF } - s := strings.Join([]string{`&WebhookClientConfig{`, - `Service:` + strings.Replace(this.Service.String(), "ServiceReference", "ServiceReference", 1) + `,`, - `CABundle:` + valueToStringGenerated(this.CABundle) + `,`, - `URL:` + valueToStringGenerated(this.URL) + `,`, - `}`, - }, "") - return s + return nil } -func valueToStringGenerated(v interface{}) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { - return "nil" +func (m *MutatingAdmissionPolicyBinding) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MutatingAdmissionPolicyBinding: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MutatingAdmissionPolicyBinding: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("*%v", pv) + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil } -func (m *AuditAnnotation) Unmarshal(dAtA []byte) error { +func (m *MutatingAdmissionPolicyBindingList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2625,17 +4335,17 @@ func (m *AuditAnnotation) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: AuditAnnotation: wiretype end group for non-group") + return fmt.Errorf("proto: MutatingAdmissionPolicyBindingList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: AuditAnnotation: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MutatingAdmissionPolicyBindingList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -2645,29 +4355,30 @@ func (m *AuditAnnotation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Key = string(dAtA[iNdEx:postIndex]) + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValueExpression", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -2677,23 +4388,25 @@ func (m *AuditAnnotation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.ValueExpression = string(dAtA[iNdEx:postIndex]) + m.Items = append(m.Items, MutatingAdmissionPolicyBinding{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -2716,7 +4429,7 @@ func (m *AuditAnnotation) Unmarshal(dAtA []byte) error { } return nil } -func (m *ExpressionWarning) Unmarshal(dAtA []byte) error { +func (m *MutatingAdmissionPolicyBindingSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2739,15 +4452,15 @@ func (m *ExpressionWarning) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ExpressionWarning: wiretype end group for non-group") + return fmt.Errorf("proto: MutatingAdmissionPolicyBindingSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ExpressionWarning: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MutatingAdmissionPolicyBindingSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 2: + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FieldRef", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field PolicyName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2775,13 +4488,49 @@ func (m *ExpressionWarning) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.FieldRef = string(dAtA[iNdEx:postIndex]) + m.PolicyName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ParamRef", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ParamRef == nil { + m.ParamRef = &ParamRef{} + } + if err := m.ParamRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Warning", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MatchResources", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -2791,23 +4540,27 @@ func (m *ExpressionWarning) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Warning = string(dAtA[iNdEx:postIndex]) + if m.MatchResources == nil { + m.MatchResources = &MatchResources{} + } + if err := m.MatchResources.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -2830,7 +4583,7 @@ func (m *ExpressionWarning) Unmarshal(dAtA []byte) error { } return nil } -func (m *MatchCondition) Unmarshal(dAtA []byte) error { +func (m *MutatingAdmissionPolicyList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2853,17 +4606,17 @@ func (m *MatchCondition) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MatchCondition: wiretype end group for non-group") + return fmt.Errorf("proto: MutatingAdmissionPolicyList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MatchCondition: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MutatingAdmissionPolicyList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -2873,29 +4626,30 @@ func (m *MatchCondition) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -2905,23 +4659,25 @@ func (m *MatchCondition) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Expression = string(dAtA[iNdEx:postIndex]) + m.Items = append(m.Items, MutatingAdmissionPolicy{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -2944,7 +4700,7 @@ func (m *MatchCondition) Unmarshal(dAtA []byte) error { } return nil } -func (m *MatchResources) Unmarshal(dAtA []byte) error { +func (m *MutatingAdmissionPolicySpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2967,15 +4723,15 @@ func (m *MatchResources) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MatchResources: wiretype end group for non-group") + return fmt.Errorf("proto: MutatingAdmissionPolicySpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MatchResources: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MutatingAdmissionPolicySpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NamespaceSelector", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ParamKind", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3002,16 +4758,16 @@ func (m *MatchResources) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.NamespaceSelector == nil { - m.NamespaceSelector = &v1.LabelSelector{} + if m.ParamKind == nil { + m.ParamKind = &ParamKind{} } - if err := m.NamespaceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ParamKind.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectSelector", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MatchConstraints", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3038,16 +4794,16 @@ func (m *MatchResources) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ObjectSelector == nil { - m.ObjectSelector = &v1.LabelSelector{} + if m.MatchConstraints == nil { + m.MatchConstraints = &MatchResources{} } - if err := m.ObjectSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.MatchConstraints.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ResourceRules", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Variables", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3074,14 +4830,14 @@ func (m *MatchResources) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ResourceRules = append(m.ResourceRules, NamedRuleWithOperations{}) - if err := m.ResourceRules[len(m.ResourceRules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Variables = append(m.Variables, Variable{}) + if err := m.Variables[len(m.Variables)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExcludeResourceRules", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Mutations", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3108,14 +4864,81 @@ func (m *MatchResources) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ExcludeResourceRules = append(m.ExcludeResourceRules, NamedRuleWithOperations{}) - if err := m.ExcludeResourceRules[len(m.ExcludeResourceRules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Mutations = append(m.Mutations, Mutation{}) + if err := m.Mutations[len(m.Mutations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FailurePolicy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := FailurePolicyType(dAtA[iNdEx:postIndex]) + m.FailurePolicy = &s + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MatchConditions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MatchConditions = append(m.MatchConditions, MatchCondition{}) + if err := m.MatchConditions[len(m.MatchConditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MatchPolicy", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ReinvocationPolicy", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3143,8 +4966,7 @@ func (m *MatchResources) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - s := MatchPolicyType(dAtA[iNdEx:postIndex]) - m.MatchPolicy = &s + m.ReinvocationPolicy = ReinvocationPolicyType(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -3840,6 +5662,160 @@ func (m *MutatingWebhookConfigurationList) Unmarshal(dAtA []byte) error { } return nil } +func (m *Mutation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Mutation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Mutation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PatchType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PatchType = PatchType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ApplyConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ApplyConfiguration == nil { + m.ApplyConfiguration = &ApplyConfiguration{} + } + if err := m.ApplyConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JSONPatch", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.JSONPatch == nil { + m.JSONPatch = &JSONPatch{} + } + if err := m.JSONPatch.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *NamedRuleWithOperations) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/admissionregistration/v1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1/generated.proto index e856e9eaf2fbd..89c1475b2e0b7 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1/generated.proto @@ -28,6 +28,51 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; // Package-wide variables from generator "generated". option go_package = "k8s.io/api/admissionregistration/v1"; +// ApplyConfiguration defines the desired configuration values of an object. +message ApplyConfiguration { + // expression will be evaluated by CEL to create an apply configuration. + // ref: https://github.com/google/cel-spec + // + // Apply configurations are declared in CEL using object initialization. For example, this CEL expression + // returns an apply configuration to set a single field: + // + // Object{ + // spec: Object.spec{ + // serviceAccountName: "example" + // } + // } + // + // Apply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of + // values not included in the apply configuration. + // + // CEL expressions have access to the object types needed to create apply configurations: + // + // - 'Object' - CEL type of the resource object. + // - 'Object.' - CEL type of object field (such as 'Object.spec') + // - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers') + // + // CEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. The value is null for DELETE requests. + // - 'oldObject' - The existing object. The value is null for CREATE requests. + // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). + // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. + // - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. + // - 'variables' - Map of composited variables, from its name to its lazily evaluated value. + // For example, a variable named 'foo' can be accessed as 'variables.foo'. + // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. + // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz + // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the + // request resource. + // + // The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the + // object. No other metadata properties are accessible. + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Required. + optional string expression = 1; +} + // AuditAnnotation describes how to produce an audit annotation for an API request. message AuditAnnotation { // key specifies the audit annotation key. The audit annotation keys of @@ -67,20 +112,89 @@ message AuditAnnotation { // ExpressionWarning is a warning information that targets a specific expression. message ExpressionWarning { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" optional string fieldRef = 2; - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. optional string warning = 3; } +// JSONPatch defines a JSON Patch. +message JSONPatch { + // expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). + // ref: https://github.com/google/cel-spec + // + // expression must return an array of JSONPatch values. + // + // For example, this CEL expression returns a JSON patch to conditionally modify a value: + // + // [ + // JSONPatch{op: "test", path: "/spec/example", value: "Red"}, + // JSONPatch{op: "replace", path: "/spec/example", value: "Green"} + // ] + // + // To define an object for the patch value, use Object types. For example: + // + // [ + // JSONPatch{ + // op: "add", + // path: "/spec/selector", + // value: Object.spec.selector{matchLabels: {"environment": "test"}} + // } + // ] + // + // To use strings containing '/' and '~' as JSONPatch path keys, use "jsonpatch.escapeKey". For example: + // + // [ + // JSONPatch{ + // op: "add", + // path: "/metadata/labels/" + jsonpatch.escapeKey("example.com/environment"), + // value: "test" + // }, + // ] + // + // CEL expressions have access to the types needed to create JSON patches and objects: + // + // - 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'. + // See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string, + // integer, array, map or object. If set, the 'path' and 'from' fields must be set to a + // [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL + // function may be used to escape path keys containing '/' and '~'. + // - 'Object' - CEL type of the resource object. + // - 'Object.' - CEL type of object field (such as 'Object.spec') + // - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers') + // + // CEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. The value is null for DELETE requests. + // - 'oldObject' - The existing object. The value is null for CREATE requests. + // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). + // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. + // - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. + // - 'variables' - Map of composited variables, from its name to its lazily evaluated value. + // For example, a variable named 'foo' can be accessed as 'variables.foo'. + // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. + // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz + // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the + // request resource. + // + // CEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) + // as well as: + // + // - 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively). + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Required. + optional string expression = 1; +} + // MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook. message MatchCondition { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -91,7 +205,7 @@ message MatchCondition { // Required. optional string name = 1; - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. @@ -112,7 +226,7 @@ message MatchCondition { // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +structType=atomic message MatchResources { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -158,7 +272,7 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 1; - // ObjectSelector decides whether to run the validation based on if the + // objectSelector decides whether to run the validation based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the cel validation, and // is considered to match if either object matches the selector. A null @@ -172,13 +286,13 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 2; - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional repeated NamedRuleWithOperations resourceRules = 3; - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -202,20 +316,187 @@ message MatchResources { optional string matchPolicy = 7; } +// MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. +message MutatingAdmissionPolicy { + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // spec defines the desired behavior of the MutatingAdmissionPolicy. + optional MutatingAdmissionPolicySpec spec = 2; +} + +// MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. +// MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators +// configure policies for clusters. +// +// For a given admission request, each binding will cause its policy to be +// evaluated N times, where N is 1 for policies/bindings that don't use +// params, otherwise N is the number of parameters selected by the binding. +// Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget). +// +// Adding/removing policies, bindings, or params can not affect whether a +// given (policy, binding, param) combination is within its own CEL budget. +message MutatingAdmissionPolicyBinding { + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. + optional MutatingAdmissionPolicyBindingSpec spec = 2; +} + +// MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding. +message MutatingAdmissionPolicyBindingList { + // metadata is the standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of PolicyBinding. + repeated MutatingAdmissionPolicyBinding items = 2; +} + +// MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding. +message MutatingAdmissionPolicyBindingSpec { + // policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. + // If the referenced resource does not exist, this binding is considered invalid and will be ignored + // Required. + optional string policyName = 1; + + // paramRef specifies the parameter resource used to configure the admission control policy. + // It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. + // If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. + // If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param. + // +optional + optional ParamRef paramRef = 2; + + // matchResources limits what resources match this binding and may be mutated by it. + // Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and + // matchConditions before the resource may be mutated. + // When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints + // and matchConditions must match for the resource to be mutated. + // Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. + // Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. + // The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. + // '*' matches CREATE, UPDATE and CONNECT. + // +optional + optional MatchResources matchResources = 3; +} + +// MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy. +message MutatingAdmissionPolicyList { + // metadata is the standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of ValidatingAdmissionPolicy. + repeated MutatingAdmissionPolicy items = 2; +} + +// MutatingAdmissionPolicySpec defines the desired behavior of the admission policy. +message MutatingAdmissionPolicySpec { + // paramKind specifies the kind of resources used to parameterize this policy. + // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. + // If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. + // If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null. + // +optional + optional ParamKind paramKind = 1; + + // matchConstraints specifies what resources this policy is designed to validate. + // The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. + // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API + // MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. + // The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. + // '*' matches CREATE, UPDATE and CONNECT. + // Required. + optional MatchResources matchConstraints = 2; + + // variables contain definitions of variables that can be used in composition of other expressions. + // Each variable is defined as a named CEL expression. + // The variables defined here will be available under `variables` in other expressions of the policy + // except matchConditions because matchConditions are evaluated before the rest of the policy. + // + // The expression of a variable can refer to other variables defined earlier in the list but not those after. + // Thus, variables must be sorted by the order of first appearance and acyclic. + // +listType=atomic + // +optional + repeated Variable variables = 3; + + // mutations contain operations to perform on matching objects. + // mutations may not be empty; a minimum of one mutation is required. + // mutations are evaluated in order, and are reinvoked according to + // the reinvocationPolicy. + // The mutations of a policy are invoked for each binding of this policy + // and reinvocation of mutations occurs on a per binding basis. + // + // +listType=atomic + // +optional + repeated Mutation mutations = 4; + + // failurePolicy defines how to handle failures for the admission policy. Failures can + // occur from CEL expression parse errors, type check errors, runtime errors and invalid + // or mis-configured policy definitions or bindings. + // + // A policy is invalid if paramKind refers to a non-existent Kind. + // A binding is invalid if paramRef.name refers to a non-existent resource. + // + // failurePolicy does not define how validations that evaluate to false are handled. + // + // Allowed values are Ignore or Fail. Defaults to Fail. + // +optional + optional string failurePolicy = 5; + + // matchConditions is a list of conditions that must be met for a request to be validated. + // Match conditions filter requests that have already been matched by the matchConstraints. + // An empty list of matchConditions matches all requests. + // There are a maximum of 64 match conditions allowed. + // + // If a parameter object is provided, it can be accessed via the `params` handle in the same + // manner as validation expressions. + // + // The exact matching logic is (in order): + // 1. If ANY matchCondition evaluates to FALSE, the policy is skipped. + // 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated. + // 3. If any matchCondition evaluates to an error (but none are FALSE): + // - If failurePolicy=Fail, reject the request + // - If failurePolicy=Ignore, the policy is skipped + // + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + repeated MatchCondition matchConditions = 6; + + // reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding + // as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + // + // Never: These mutations will not be called more than once per binding in a single admission evaluation. + // + // IfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of + // order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only + // reinvoked when mutations change the object after this mutation is invoked. + // Required. + optional string reinvocationPolicy = 7; +} + // MutatingWebhook describes an admission webhook and the resources and operations it applies to. message MutatingWebhook { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. optional string name = 1; - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required optional WebhookClientConfig clientConfig = 2; - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -224,7 +505,7 @@ message MutatingWebhook { // +listType=atomic repeated RuleWithOperations rules = 3; - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Fail. // +optional optional string failurePolicy = 4; @@ -246,7 +527,7 @@ message MutatingWebhook { // +optional optional string matchPolicy = 9; - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -292,7 +573,7 @@ message MutatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -306,7 +587,7 @@ message MutatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 11; - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -314,7 +595,7 @@ message MutatingWebhook { // sideEffects == Unknown or Some. optional string sideEffects = 6; - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -322,7 +603,7 @@ message MutatingWebhook { // +optional optional int32 timeoutSeconds = 7; - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -350,7 +631,7 @@ message MutatingWebhook { // +optional optional string reinvocationPolicy = 10; - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -372,11 +653,11 @@ message MutatingWebhook { // MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. message MutatingWebhookConfiguration { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -387,7 +668,7 @@ message MutatingWebhookConfiguration { // MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. message MutatingWebhookConfigurationList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -396,10 +677,30 @@ message MutatingWebhookConfigurationList { repeated MutatingWebhookConfiguration items = 2; } +// Mutation specifies the CEL expression which is used to apply the Mutation. +message Mutation { + // patchType indicates the patch strategy used. + // Allowed values are "ApplyConfiguration" and "JSONPatch". + // Required. + // + // +unionDiscriminator + optional string patchType = 2; + + // applyConfiguration defines the desired configuration values of an object. + // The configuration is applied to the admission object using + // [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). + // A CEL expression is used to create apply configuration. + optional ApplyConfiguration applyConfiguration = 3; + + // jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. + // A CEL expression is used to create the JSON patch. + optional JSONPatch jsonPatch = 4; +} + // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. // +structType=atomic message NamedRuleWithOperations { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. // +listType=atomic // +optional repeated string resourceNames = 1; @@ -411,12 +712,12 @@ message NamedRuleWithOperations { // ParamKind is a tuple of Group Kind and Version. // +structType=atomic message ParamKind { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. optional string apiVersion = 1; - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. optional string kind = 2; } @@ -465,7 +766,7 @@ message ParamRef { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 3; - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. @@ -481,19 +782,19 @@ message ParamRef { // Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended // to make sure that all the tuple expansions are valid. message Rule { - // APIGroups is the API groups the resources belong to. '*' is all groups. + // apiGroups is the API groups the resources belong to. '*' is all groups. // If '*' is present, the length of the slice must be one. // Required. // +listType=atomic repeated string apiGroups = 1; - // APIVersions is the API versions the resources belong to. '*' is all versions. + // apiVersions is the API versions the resources belong to. '*' is all versions. // If '*' is present, the length of the slice must be one. // Required. // +listType=atomic repeated string apiVersions = 2; - // Resources is a list of resources this rule applies to. + // resources is a list of resources this rule applies to. // // For example: // 'pods' means pods. @@ -527,7 +828,7 @@ message Rule { // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // sure that all the tuple expansions are valid. message RuleWithOperations { - // Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * + // operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * // for all of those operations and any future admission operations that are added. // If '*' is present, the length of the slice must be one. // Required. @@ -541,20 +842,20 @@ message RuleWithOperations { // ServiceReference holds a reference to Service.legacy.k8s.io message ServiceReference { - // `namespace` is the namespace of the service. + // namespace is the namespace of the service. // Required optional string namespace = 1; - // `name` is the name of the service. + // name is the name of the service. // Required optional string name = 2; - // `path` is an optional URL path which will be sent in any request to + // path is an optional URL path which will be sent in any request to // this service. // +optional optional string path = 3; - // If specified, the port on the service that hosting webhook. + // port is the port on the service that hosts the webhook. // Default to 443 for backward compatibility. // `port` should be a valid port number (1-65535, inclusive). // +optional @@ -564,7 +865,7 @@ message ServiceReference { // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy message TypeChecking { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. // +optional // +listType=atomic repeated ExpressionWarning expressionWarnings = 1; @@ -572,14 +873,14 @@ message TypeChecking { // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. message ValidatingAdmissionPolicy { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. optional ValidatingAdmissionPolicySpec spec = 2; - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. @@ -599,17 +900,18 @@ message ValidatingAdmissionPolicy { // Adding/removing policies, bindings, or params can not affect whether a // given (policy, binding, param) combination is within its own CEL budget. message ValidatingAdmissionPolicyBinding { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. + // +required optional ValidatingAdmissionPolicyBindingSpec spec = 2; } // ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding. message ValidatingAdmissionPolicyBindingList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -620,9 +922,11 @@ message ValidatingAdmissionPolicyBindingList { // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. message ValidatingAdmissionPolicyBindingSpec { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string policyName = 1; // paramRef specifies the parameter resource used to configure the admission control policy. @@ -632,7 +936,7 @@ message ValidatingAdmissionPolicyBindingSpec { // +optional optional ParamRef paramRef = 2; - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. @@ -680,12 +984,14 @@ message ValidatingAdmissionPolicyBindingSpec { // // Required. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required repeated string validationActions = 4; } // ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy. message ValidatingAdmissionPolicyList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -696,21 +1002,21 @@ message ValidatingAdmissionPolicyList { // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. message ValidatingAdmissionPolicySpec { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. // +optional optional ParamKind paramKind = 1; - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. optional MatchResources matchConstraints = 2; - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. // +listType=atomic @@ -741,7 +1047,7 @@ message ValidatingAdmissionPolicySpec { // +optional repeated AuditAnnotation auditAnnotations = 5; - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -763,7 +1069,7 @@ message ValidatingAdmissionPolicySpec { // +optional repeated MatchCondition matchConditions = 6; - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. @@ -780,16 +1086,16 @@ message ValidatingAdmissionPolicySpec { // ValidatingAdmissionPolicyStatus represents the status of an admission validation policy. message ValidatingAdmissionPolicyStatus { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. // +optional optional int64 observedGeneration = 1; - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. // +optional optional TypeChecking typeChecking = 2; - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. // +optional // +listType=map // +listMapKey=type @@ -798,18 +1104,18 @@ message ValidatingAdmissionPolicyStatus { // ValidatingWebhook describes an admission webhook and the resources and operations it applies to. message ValidatingWebhook { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. optional string name = 1; - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required optional WebhookClientConfig clientConfig = 2; - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -818,7 +1124,7 @@ message ValidatingWebhook { // +listType=atomic repeated RuleWithOperations rules = 3; - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Fail. // +optional optional string failurePolicy = 4; @@ -840,7 +1146,7 @@ message ValidatingWebhook { // +optional optional string matchPolicy = 9; - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -886,7 +1192,7 @@ message ValidatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -900,7 +1206,7 @@ message ValidatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 10; - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -908,7 +1214,7 @@ message ValidatingWebhook { // sideEffects == Unknown or Some. optional string sideEffects = 6; - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -916,7 +1222,7 @@ message ValidatingWebhook { // +optional optional int32 timeoutSeconds = 7; - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -926,7 +1232,7 @@ message ValidatingWebhook { // +listType=atomic repeated string admissionReviewVersions = 8; - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -948,11 +1254,11 @@ message ValidatingWebhook { // ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. message ValidatingWebhookConfiguration { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -963,7 +1269,7 @@ message ValidatingWebhookConfiguration { // ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. message ValidatingWebhookConfigurationList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -974,7 +1280,7 @@ message ValidatingWebhookConfigurationList { // Validation specifies the CEL expression which is used to apply the validation. message Validation { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -1017,7 +1323,7 @@ message Validation { // Required. optional string Expression = 1; - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -1027,7 +1333,7 @@ message Validation { // +optional optional string message = 2; - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. @@ -1053,12 +1359,12 @@ message Validation { // Variable is the definition of a variable that is used for composition. A variable is defined as a named expression. // +structType=atomic message Variable { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` optional string Name = 1; - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. optional string Expression = 2; } @@ -1066,7 +1372,7 @@ message Variable { // WebhookClientConfig contains the information to make a TLS // connection with the webhook message WebhookClientConfig { - // `url` gives the location of the webhook, in standard URL form + // url gives the location of the webhook, in standard URL form // (`scheme://host:port/path`). Exactly one of `url` or `service` // must be specified. // @@ -1095,7 +1401,7 @@ message WebhookClientConfig { // +optional optional string url = 3; - // `service` is a reference to the service for this webhook. Either + // service is a reference to the service for this webhook. Either // `service` or `url` must be specified. // // If the webhook is running within the cluster, then you should use `service`. @@ -1103,7 +1409,7 @@ message WebhookClientConfig { // +optional optional ServiceReference service = 1; - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. // If unspecified, system trust roots on the apiserver are used. // +optional optional bytes caBundle = 2; diff --git a/staging/src/k8s.io/api/admissionregistration/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/admissionregistration/v1/generated.protomessage.pb.go deleted file mode 100644 index 04a23c597ca44..0000000000000 --- a/staging/src/k8s.io/api/admissionregistration/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,76 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*AuditAnnotation) ProtoMessage() {} - -func (*ExpressionWarning) ProtoMessage() {} - -func (*MatchCondition) ProtoMessage() {} - -func (*MatchResources) ProtoMessage() {} - -func (*MutatingWebhook) ProtoMessage() {} - -func (*MutatingWebhookConfiguration) ProtoMessage() {} - -func (*MutatingWebhookConfigurationList) ProtoMessage() {} - -func (*NamedRuleWithOperations) ProtoMessage() {} - -func (*ParamKind) ProtoMessage() {} - -func (*ParamRef) ProtoMessage() {} - -func (*Rule) ProtoMessage() {} - -func (*RuleWithOperations) ProtoMessage() {} - -func (*ServiceReference) ProtoMessage() {} - -func (*TypeChecking) ProtoMessage() {} - -func (*ValidatingAdmissionPolicy) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBinding) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBindingList) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBindingSpec) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyList) ProtoMessage() {} - -func (*ValidatingAdmissionPolicySpec) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyStatus) ProtoMessage() {} - -func (*ValidatingWebhook) ProtoMessage() {} - -func (*ValidatingWebhookConfiguration) ProtoMessage() {} - -func (*ValidatingWebhookConfigurationList) ProtoMessage() {} - -func (*Validation) ProtoMessage() {} - -func (*Variable) ProtoMessage() {} - -func (*WebhookClientConfig) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/admissionregistration/v1/register.go b/staging/src/k8s.io/api/admissionregistration/v1/register.go index da74379ce2ed4..c1137d2bd4302 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/register.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/register.go @@ -54,6 +54,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ValidatingAdmissionPolicyList{}, &ValidatingAdmissionPolicyBinding{}, &ValidatingAdmissionPolicyBindingList{}, + &MutatingAdmissionPolicy{}, + &MutatingAdmissionPolicyList{}, + &MutatingAdmissionPolicyBinding{}, + &MutatingAdmissionPolicyBindingList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/staging/src/k8s.io/api/admissionregistration/v1/types.go b/staging/src/k8s.io/api/admissionregistration/v1/types.go index 311c05c0ffa76..f7a07b6453454 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/types.go @@ -23,19 +23,19 @@ import ( // Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended // to make sure that all the tuple expansions are valid. type Rule struct { - // APIGroups is the API groups the resources belong to. '*' is all groups. + // apiGroups is the API groups the resources belong to. '*' is all groups. // If '*' is present, the length of the slice must be one. // Required. // +listType=atomic APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,1,rep,name=apiGroups"` - // APIVersions is the API versions the resources belong to. '*' is all versions. + // apiVersions is the API versions the resources belong to. '*' is all versions. // If '*' is present, the length of the slice must be one. // Required. // +listType=atomic APIVersions []string `json:"apiVersions,omitempty" protobuf:"bytes,2,rep,name=apiVersions"` - // Resources is a list of resources this rule applies to. + // resources is a list of resources this rule applies to. // // For example: // 'pods' means pods. @@ -141,12 +141,12 @@ const ( // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. type ValidatingAdmissionPolicy struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. Spec ValidatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. @@ -156,14 +156,14 @@ type ValidatingAdmissionPolicy struct { // ValidatingAdmissionPolicyStatus represents the status of an admission validation policy. type ValidatingAdmissionPolicyStatus struct { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. // +optional TypeChecking *TypeChecking `json:"typeChecking,omitempty" protobuf:"bytes,2,opt,name=typeChecking"` - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. // +optional // +listType=map // +listMapKey=type @@ -176,7 +176,7 @@ type ValidatingAdmissionPolicyConditionType string // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy type TypeChecking struct { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. // +optional // +listType=atomic ExpressionWarnings []ExpressionWarning `json:"expressionWarnings,omitempty" protobuf:"bytes,1,rep,name=expressionWarnings"` @@ -184,11 +184,11 @@ type TypeChecking struct { // ExpressionWarning is a warning information that targets a specific expression. type ExpressionWarning struct { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" FieldRef string `json:"fieldRef" protobuf:"bytes,2,opt,name=fieldRef"` - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. Warning string `json:"warning" protobuf:"bytes,3,opt,name=warning"` @@ -200,7 +200,7 @@ type ExpressionWarning struct { // ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy. type ValidatingAdmissionPolicyList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -210,21 +210,21 @@ type ValidatingAdmissionPolicyList struct { // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. type ValidatingAdmissionPolicySpec struct { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. // +optional ParamKind *ParamKind `json:"paramKind,omitempty" protobuf:"bytes,1,rep,name=paramKind"` - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. MatchConstraints *MatchResources `json:"matchConstraints,omitempty" protobuf:"bytes,2,rep,name=matchConstraints"` - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. // +listType=atomic @@ -255,7 +255,7 @@ type ValidatingAdmissionPolicySpec struct { // +optional AuditAnnotations []AuditAnnotation `json:"auditAnnotations,omitempty" protobuf:"bytes,5,rep,name=auditAnnotations"` - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -277,7 +277,7 @@ type ValidatingAdmissionPolicySpec struct { // +optional MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,6,rep,name=matchConditions"` - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. @@ -295,19 +295,19 @@ type ValidatingAdmissionPolicySpec struct { // ParamKind is a tuple of Group Kind and Version. // +structType=atomic type ParamKind struct { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,1,rep,name=apiVersion"` - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. Kind string `json:"kind,omitempty" protobuf:"bytes,2,rep,name=kind"` } // Validation specifies the CEL expression which is used to apply the validation. type Validation struct { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -349,7 +349,7 @@ type Validation struct { // non-intersecting keys are appended, retaining their partial order. // Required. Expression string `json:"expression" protobuf:"bytes,1,opt,name=Expression"` - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -358,7 +358,7 @@ type Validation struct { // If unset, the message is "failed Expression: {Expression}". // +optional Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. @@ -383,12 +383,12 @@ type Validation struct { // Variable is the definition of a variable that is used for composition. A variable is defined as a named expression. // +structType=atomic type Variable struct { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` Name string `json:"name" protobuf:"bytes,1,opt,name=Name"` - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. Expression string `json:"expression" protobuf:"bytes,2,opt,name=Expression"` } @@ -448,10 +448,11 @@ type AuditAnnotation struct { // given (policy, binding, param) combination is within its own CEL budget. type ValidatingAdmissionPolicyBinding struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. + // +required Spec ValidatingAdmissionPolicyBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -461,7 +462,7 @@ type ValidatingAdmissionPolicyBinding struct { // ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -471,9 +472,11 @@ type ValidatingAdmissionPolicyBindingList struct { // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingSpec struct { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required PolicyName string `json:"policyName,omitempty" protobuf:"bytes,1,rep,name=policyName"` // paramRef specifies the parameter resource used to configure the admission control policy. @@ -483,7 +486,7 @@ type ValidatingAdmissionPolicyBindingSpec struct { // +optional ParamRef *ParamRef `json:"paramRef,omitempty" protobuf:"bytes,2,rep,name=paramRef"` - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. @@ -531,6 +534,8 @@ type ValidatingAdmissionPolicyBindingSpec struct { // // Required. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"` } @@ -579,7 +584,7 @@ type ParamRef struct { // +optional Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,rep,name=selector"` - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. @@ -597,7 +602,7 @@ type ParamRef struct { // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +structType=atomic type MatchResources struct { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -642,7 +647,7 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,1,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the validation based on if the + // objectSelector decides whether to run the validation based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the cel validation, and // is considered to match if either object matches the selector. A null @@ -655,12 +660,12 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,2,opt,name=objectSelector"` - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional ResourceRules []NamedRuleWithOperations `json:"resourceRules,omitempty" protobuf:"bytes,3,rep,name=resourceRules"` - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -704,7 +709,7 @@ const ( // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. // +structType=atomic type NamedRuleWithOperations struct { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. // +listType=atomic // +optional ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,1,rep,name=resourceNames"` @@ -720,10 +725,10 @@ type NamedRuleWithOperations struct { // ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. type ValidatingWebhookConfiguration struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -738,7 +743,7 @@ type ValidatingWebhookConfiguration struct { // ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. type ValidatingWebhookConfigurationList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -754,10 +759,10 @@ type ValidatingWebhookConfigurationList struct { // MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. type MutatingWebhookConfiguration struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -772,7 +777,7 @@ type MutatingWebhookConfiguration struct { // MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. type MutatingWebhookConfigurationList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -782,18 +787,18 @@ type MutatingWebhookConfigurationList struct { // ValidatingWebhook describes an admission webhook and the resources and operations it applies to. type ValidatingWebhook struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -802,7 +807,7 @@ type ValidatingWebhook struct { // +listType=atomic Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Fail. // +optional FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"` @@ -824,7 +829,7 @@ type ValidatingWebhook struct { // +optional MatchPolicy *MatchPolicyType `json:"matchPolicy,omitempty" protobuf:"bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -870,7 +875,7 @@ type ValidatingWebhook struct { // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -884,7 +889,7 @@ type ValidatingWebhook struct { // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,10,opt,name=objectSelector"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -892,7 +897,7 @@ type ValidatingWebhook struct { // sideEffects == Unknown or Some. SideEffects *SideEffectClass `json:"sideEffects" protobuf:"bytes,6,opt,name=sideEffects,casttype=SideEffectClass"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -900,7 +905,7 @@ type ValidatingWebhook struct { // +optional TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -910,7 +915,7 @@ type ValidatingWebhook struct { // +listType=atomic AdmissionReviewVersions []string `json:"admissionReviewVersions" protobuf:"bytes,8,rep,name=admissionReviewVersions"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -932,18 +937,18 @@ type ValidatingWebhook struct { // MutatingWebhook describes an admission webhook and the resources and operations it applies to. type MutatingWebhook struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -952,7 +957,7 @@ type MutatingWebhook struct { // +listType=atomic Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Fail. // +optional FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"` @@ -974,7 +979,7 @@ type MutatingWebhook struct { // +optional MatchPolicy *MatchPolicyType `json:"matchPolicy,omitempty" protobuf:"bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -1020,7 +1025,7 @@ type MutatingWebhook struct { // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -1034,7 +1039,7 @@ type MutatingWebhook struct { // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,11,opt,name=objectSelector"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -1042,7 +1047,7 @@ type MutatingWebhook struct { // sideEffects == Unknown or Some. SideEffects *SideEffectClass `json:"sideEffects" protobuf:"bytes,6,opt,name=sideEffects,casttype=SideEffectClass"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -1050,7 +1055,7 @@ type MutatingWebhook struct { // +optional TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -1078,7 +1083,7 @@ type MutatingWebhook struct { // +optional ReinvocationPolicy *ReinvocationPolicyType `json:"reinvocationPolicy,omitempty" protobuf:"bytes,10,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -1098,6 +1103,335 @@ type MutatingWebhook struct { MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,12,opt,name=matchConditions"` } +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. +type MutatingAdmissionPolicy struct { + metav1.TypeMeta `json:",inline"` + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // spec defines the desired behavior of the MutatingAdmissionPolicy. + Spec MutatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy. +type MutatingAdmissionPolicyList struct { + metav1.TypeMeta `json:",inline"` + // metadata is the standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // List of ValidatingAdmissionPolicy. + Items []MutatingAdmissionPolicy `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// MutatingAdmissionPolicySpec defines the desired behavior of the admission policy. +type MutatingAdmissionPolicySpec struct { + // paramKind specifies the kind of resources used to parameterize this policy. + // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. + // If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. + // If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null. + // +optional + ParamKind *ParamKind `json:"paramKind,omitempty" protobuf:"bytes,1,rep,name=paramKind"` + + // matchConstraints specifies what resources this policy is designed to validate. + // The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. + // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API + // MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. + // The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. + // '*' matches CREATE, UPDATE and CONNECT. + // Required. + MatchConstraints *MatchResources `json:"matchConstraints,omitempty" protobuf:"bytes,2,rep,name=matchConstraints"` + + // variables contain definitions of variables that can be used in composition of other expressions. + // Each variable is defined as a named CEL expression. + // The variables defined here will be available under `variables` in other expressions of the policy + // except matchConditions because matchConditions are evaluated before the rest of the policy. + // + // The expression of a variable can refer to other variables defined earlier in the list but not those after. + // Thus, variables must be sorted by the order of first appearance and acyclic. + // +listType=atomic + // +optional + Variables []Variable `json:"variables,omitempty" protobuf:"bytes,3,rep,name=variables"` + + // mutations contain operations to perform on matching objects. + // mutations may not be empty; a minimum of one mutation is required. + // mutations are evaluated in order, and are reinvoked according to + // the reinvocationPolicy. + // The mutations of a policy are invoked for each binding of this policy + // and reinvocation of mutations occurs on a per binding basis. + // + // +listType=atomic + // +optional + Mutations []Mutation `json:"mutations,omitempty" protobuf:"bytes,4,rep,name=mutations"` + + // failurePolicy defines how to handle failures for the admission policy. Failures can + // occur from CEL expression parse errors, type check errors, runtime errors and invalid + // or mis-configured policy definitions or bindings. + // + // A policy is invalid if paramKind refers to a non-existent Kind. + // A binding is invalid if paramRef.name refers to a non-existent resource. + // + // failurePolicy does not define how validations that evaluate to false are handled. + // + // Allowed values are Ignore or Fail. Defaults to Fail. + // +optional + FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,5,opt,name=failurePolicy,casttype=FailurePolicyType"` + + // matchConditions is a list of conditions that must be met for a request to be validated. + // Match conditions filter requests that have already been matched by the matchConstraints. + // An empty list of matchConditions matches all requests. + // There are a maximum of 64 match conditions allowed. + // + // If a parameter object is provided, it can be accessed via the `params` handle in the same + // manner as validation expressions. + // + // The exact matching logic is (in order): + // 1. If ANY matchCondition evaluates to FALSE, the policy is skipped. + // 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated. + // 3. If any matchCondition evaluates to an error (but none are FALSE): + // - If failurePolicy=Fail, reject the request + // - If failurePolicy=Ignore, the policy is skipped + // + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,6,rep,name=matchConditions"` + + // reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding + // as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + // + // Never: These mutations will not be called more than once per binding in a single admission evaluation. + // + // IfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of + // order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only + // reinvoked when mutations change the object after this mutation is invoked. + // Required. + ReinvocationPolicy ReinvocationPolicyType `json:"reinvocationPolicy,omitempty" protobuf:"bytes,7,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"` +} + +// Mutation specifies the CEL expression which is used to apply the Mutation. +type Mutation struct { + // patchType indicates the patch strategy used. + // Allowed values are "ApplyConfiguration" and "JSONPatch". + // Required. + // + // +unionDiscriminator + PatchType PatchType `json:"patchType" protobuf:"bytes,2,opt,name=patchType,casttype=PatchType"` + + // applyConfiguration defines the desired configuration values of an object. + // The configuration is applied to the admission object using + // [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). + // A CEL expression is used to create apply configuration. + ApplyConfiguration *ApplyConfiguration `json:"applyConfiguration,omitempty" protobuf:"bytes,3,opt,name=applyConfiguration"` + + // jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. + // A CEL expression is used to create the JSON patch. + JSONPatch *JSONPatch `json:"jsonPatch,omitempty" protobuf:"bytes,4,opt,name=jsonPatch"` +} + +// PatchType specifies the type of patch operation for a mutation. +// +enum +type PatchType string + +const ( + // ApplyConfiguration indicates that the mutation is using apply configuration to mutate the object. + PatchTypeApplyConfiguration PatchType = "ApplyConfiguration" + // JSONPatch indicates that the object is mutated through JSON Patch. + PatchTypeJSONPatch PatchType = "JSONPatch" +) + +// ApplyConfiguration defines the desired configuration values of an object. +type ApplyConfiguration struct { + // expression will be evaluated by CEL to create an apply configuration. + // ref: https://github.com/google/cel-spec + // + // Apply configurations are declared in CEL using object initialization. For example, this CEL expression + // returns an apply configuration to set a single field: + // + // Object{ + // spec: Object.spec{ + // serviceAccountName: "example" + // } + // } + // + // Apply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of + // values not included in the apply configuration. + // + // CEL expressions have access to the object types needed to create apply configurations: + // + // - 'Object' - CEL type of the resource object. + // - 'Object.' - CEL type of object field (such as 'Object.spec') + // - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers') + // + // CEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. The value is null for DELETE requests. + // - 'oldObject' - The existing object. The value is null for CREATE requests. + // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). + // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. + // - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. + // - 'variables' - Map of composited variables, from its name to its lazily evaluated value. + // For example, a variable named 'foo' can be accessed as 'variables.foo'. + // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. + // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz + // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the + // request resource. + // + // The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the + // object. No other metadata properties are accessible. + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Required. + Expression string `json:"expression,omitempty" protobuf:"bytes,1,opt,name=expression"` +} + +// JSONPatch defines a JSON Patch. +type JSONPatch struct { + // expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). + // ref: https://github.com/google/cel-spec + // + // expression must return an array of JSONPatch values. + // + // For example, this CEL expression returns a JSON patch to conditionally modify a value: + // + // [ + // JSONPatch{op: "test", path: "/spec/example", value: "Red"}, + // JSONPatch{op: "replace", path: "/spec/example", value: "Green"} + // ] + // + // To define an object for the patch value, use Object types. For example: + // + // [ + // JSONPatch{ + // op: "add", + // path: "/spec/selector", + // value: Object.spec.selector{matchLabels: {"environment": "test"}} + // } + // ] + // + // To use strings containing '/' and '~' as JSONPatch path keys, use "jsonpatch.escapeKey". For example: + // + // [ + // JSONPatch{ + // op: "add", + // path: "/metadata/labels/" + jsonpatch.escapeKey("example.com/environment"), + // value: "test" + // }, + // ] + // + // CEL expressions have access to the types needed to create JSON patches and objects: + // + // - 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'. + // See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string, + // integer, array, map or object. If set, the 'path' and 'from' fields must be set to a + // [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL + // function may be used to escape path keys containing '/' and '~'. + // - 'Object' - CEL type of the resource object. + // - 'Object.' - CEL type of object field (such as 'Object.spec') + // - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers') + // + // CEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. The value is null for DELETE requests. + // - 'oldObject' - The existing object. The value is null for CREATE requests. + // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). + // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. + // - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. + // - 'variables' - Map of composited variables, from its name to its lazily evaluated value. + // For example, a variable named 'foo' can be accessed as 'variables.foo'. + // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. + // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz + // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the + // request resource. + // + // CEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) + // as well as: + // + // - 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively). + // + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Required. + Expression string `json:"expression,omitempty" protobuf:"bytes,1,opt,name=expression"` +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. +// MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators +// configure policies for clusters. +// +// For a given admission request, each binding will cause its policy to be +// evaluated N times, where N is 1 for policies/bindings that don't use +// params, otherwise N is the number of parameters selected by the binding. +// Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget). +// +// Adding/removing policies, bindings, or params can not affect whether a +// given (policy, binding, param) combination is within its own CEL budget. +type MutatingAdmissionPolicyBinding struct { + metav1.TypeMeta `json:",inline"` + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. + Spec MutatingAdmissionPolicyBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding. +type MutatingAdmissionPolicyBindingList struct { + metav1.TypeMeta `json:",inline"` + // metadata is the standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // List of PolicyBinding. + Items []MutatingAdmissionPolicyBinding `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding. +type MutatingAdmissionPolicyBindingSpec struct { + // policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. + // If the referenced resource does not exist, this binding is considered invalid and will be ignored + // Required. + PolicyName string `json:"policyName,omitempty" protobuf:"bytes,1,rep,name=policyName"` + + // paramRef specifies the parameter resource used to configure the admission control policy. + // It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. + // If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. + // If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param. + // +optional + ParamRef *ParamRef `json:"paramRef,omitempty" protobuf:"bytes,2,rep,name=paramRef"` + + // matchResources limits what resources match this binding and may be mutated by it. + // Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and + // matchConditions before the resource may be mutated. + // When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints + // and matchConditions must match for the resource to be mutated. + // Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. + // Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. + // The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. + // '*' matches CREATE, UPDATE and CONNECT. + // +optional + MatchResources *MatchResources `json:"matchResources,omitempty" protobuf:"bytes,3,rep,name=matchResources"` +} + // ReinvocationPolicyType specifies what type of policy is used when other admission plugins also perform // modifications. // +enum @@ -1116,7 +1450,7 @@ const ( // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // sure that all the tuple expansions are valid. type RuleWithOperations struct { - // Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * + // operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * // for all of those operations and any future admission operations that are added. // If '*' is present, the length of the slice must be one. // Required. @@ -1143,7 +1477,7 @@ const ( // WebhookClientConfig contains the information to make a TLS // connection with the webhook type WebhookClientConfig struct { - // `url` gives the location of the webhook, in standard URL form + // url gives the location of the webhook, in standard URL form // (`scheme://host:port/path`). Exactly one of `url` or `service` // must be specified. // @@ -1172,7 +1506,7 @@ type WebhookClientConfig struct { // +optional URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"` - // `service` is a reference to the service for this webhook. Either + // service is a reference to the service for this webhook. Either // `service` or `url` must be specified. // // If the webhook is running within the cluster, then you should use `service`. @@ -1180,7 +1514,7 @@ type WebhookClientConfig struct { // +optional Service *ServiceReference `json:"service,omitempty" protobuf:"bytes,1,opt,name=service"` - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. // If unspecified, system trust roots on the apiserver are used. // +optional CABundle []byte `json:"caBundle,omitempty" protobuf:"bytes,2,opt,name=caBundle"` @@ -1188,19 +1522,19 @@ type WebhookClientConfig struct { // ServiceReference holds a reference to Service.legacy.k8s.io type ServiceReference struct { - // `namespace` is the namespace of the service. + // namespace is the namespace of the service. // Required Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"` - // `name` is the name of the service. + // name is the name of the service. // Required Name string `json:"name" protobuf:"bytes,2,opt,name=name"` - // `path` is an optional URL path which will be sent in any request to + // path is an optional URL path which will be sent in any request to // this service. // +optional Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` - // If specified, the port on the service that hosting webhook. + // port is the port on the service that hosts the webhook. // Default to 443 for backward compatibility. // `port` should be a valid port number (1-65535, inclusive). // +optional @@ -1209,7 +1543,7 @@ type ServiceReference struct { // MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook. type MatchCondition struct { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -1220,7 +1554,7 @@ type MatchCondition struct { // Required. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. diff --git a/staging/src/k8s.io/api/admissionregistration/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admissionregistration/v1/types_swagger_doc_generated.go index f43139505d151..182f8ad0f01a4 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/types_swagger_doc_generated.go @@ -27,6 +27,15 @@ package v1 // Those methods can be generated by using hack/update-codegen.sh // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. +var map_ApplyConfiguration = map[string]string{ + "": "ApplyConfiguration defines the desired configuration values of an object.", + "expression": "expression will be evaluated by CEL to create an apply configuration. ref: https://github.com/google/cel-spec\n\nApply configurations are declared in CEL using object initialization. For example, this CEL expression returns an apply configuration to set a single field:\n\n\tObject{\n\t spec: Object.spec{\n\t serviceAccountName: \"example\"\n\t }\n\t}\n\nApply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of values not included in the apply configuration.\n\nCEL expressions have access to the object types needed to create apply configurations:\n\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", +} + +func (ApplyConfiguration) SwaggerDoc() map[string]string { + return map_ApplyConfiguration +} + var map_AuditAnnotation = map[string]string{ "": "AuditAnnotation describes how to produce an audit annotation for an API request.", "key": "key specifies the audit annotation key. The audit annotation keys of a ValidatingAdmissionPolicy must be unique. The key must be a qualified name ([A-Za-z0-9][-A-Za-z0-9_.]*) no more than 63 bytes in length.\n\nThe key is combined with the resource name of the ValidatingAdmissionPolicy to construct an audit annotation key: \"{ValidatingAdmissionPolicy name}/{key}\".\n\nIf an admission webhook uses the same resource name as this ValidatingAdmissionPolicy and the same audit annotation key, the annotation key will be identical. In this case, the first annotation written with the key will be included in the audit event and all subsequent annotations with the same key will be discarded.\n\nRequired.", @@ -39,18 +48,27 @@ func (AuditAnnotation) SwaggerDoc() map[string]string { var map_ExpressionWarning = map[string]string{ "": "ExpressionWarning is a warning information that targets a specific expression.", - "fieldRef": "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", - "warning": "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + "fieldRef": "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + "warning": "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", } func (ExpressionWarning) SwaggerDoc() map[string]string { return map_ExpressionWarning } +var map_JSONPatch = map[string]string{ + "": "JSONPatch defines a JSON Patch.", + "expression": "expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). ref: https://github.com/google/cel-spec\n\nexpression must return an array of JSONPatch values.\n\nFor example, this CEL expression returns a JSON patch to conditionally modify a value:\n\n\t [\n\t JSONPatch{op: \"test\", path: \"/spec/example\", value: \"Red\"},\n\t JSONPatch{op: \"replace\", path: \"/spec/example\", value: \"Green\"}\n\t ]\n\nTo define an object for the patch value, use Object types. For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/spec/selector\",\n\t value: Object.spec.selector{matchLabels: {\"environment\": \"test\"}}\n\t }\n\t ]\n\nTo use strings containing '/' and '~' as JSONPatch path keys, use \"jsonpatch.escapeKey\". For example:\n\n\t [\n\t JSONPatch{\n\t op: \"add\",\n\t path: \"/metadata/labels/\" + jsonpatch.escapeKey(\"example.com/environment\"),\n\t value: \"test\"\n\t },\n\t ]\n\nCEL expressions have access to the types needed to create JSON patches and objects:\n\n- 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'.\n See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string,\n integer, array, map or object. If set, the 'path' and 'from' fields must be set to a\n [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL\n function may be used to escape path keys containing '/' and '~'.\n- 'Object' - CEL type of the resource object. - 'Object.' - CEL type of object field (such as 'Object.spec') - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers')\n\nCEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nCEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) as well as:\n\n- 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively).\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Required.", +} + +func (JSONPatch) SwaggerDoc() map[string]string { + return map_JSONPatch +} + var map_MatchCondition = map[string]string{ "": "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.", - "name": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", - "expression": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "name": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "expression": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", } func (MatchCondition) SwaggerDoc() map[string]string { @@ -59,10 +77,10 @@ func (MatchCondition) SwaggerDoc() map[string]string { var map_MatchResources = map[string]string{ "": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", - "namespaceSelector": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "resourceRules": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", - "excludeResourceRules": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "namespaceSelector": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "resourceRules": "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + "excludeResourceRules": "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "matchPolicy": "matchPolicy defines how the \"MatchResources\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the ValidatingAdmissionPolicy.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the ValidatingAdmissionPolicy.\n\nDefaults to \"Equivalent\"", } @@ -70,20 +88,86 @@ func (MatchResources) SwaggerDoc() map[string]string { return map_MatchResources } +var map_MutatingAdmissionPolicy = map[string]string{ + "": "MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the MutatingAdmissionPolicy.", +} + +func (MutatingAdmissionPolicy) SwaggerDoc() map[string]string { + return map_MutatingAdmissionPolicy +} + +var map_MutatingAdmissionPolicyBinding = map[string]string{ + "": "MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding. Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget).\n\nAdding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding.", +} + +func (MutatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { + return map_MutatingAdmissionPolicyBinding +} + +var map_MutatingAdmissionPolicyBindingList = map[string]string{ + "": "MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding.", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "items": "List of PolicyBinding.", +} + +func (MutatingAdmissionPolicyBindingList) SwaggerDoc() map[string]string { + return map_MutatingAdmissionPolicyBindingList +} + +var map_MutatingAdmissionPolicyBindingSpec = map[string]string{ + "": "MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding.", + "policyName": "policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "paramRef": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.", + "matchResources": "matchResources limits what resources match this binding and may be mutated by it. Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and matchConditions before the resource may be mutated. When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints and matchConditions must match for the resource to be mutated. Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT.", +} + +func (MutatingAdmissionPolicyBindingSpec) SwaggerDoc() map[string]string { + return map_MutatingAdmissionPolicyBindingSpec +} + +var map_MutatingAdmissionPolicyList = map[string]string{ + "": "MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy.", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "items": "List of ValidatingAdmissionPolicy.", +} + +func (MutatingAdmissionPolicyList) SwaggerDoc() map[string]string { + return map_MutatingAdmissionPolicyList +} + +var map_MutatingAdmissionPolicySpec = map[string]string{ + "": "MutatingAdmissionPolicySpec defines the desired behavior of the admission policy.", + "paramKind": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null.", + "matchConstraints": "matchConstraints specifies what resources this policy is designed to validate. The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. '*' matches CREATE, UPDATE and CONNECT. Required.", + "variables": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except matchConditions because matchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, variables must be sorted by the order of first appearance and acyclic.", + "mutations": "mutations contain operations to perform on matching objects. mutations may not be empty; a minimum of one mutation is required. mutations are evaluated in order, and are reinvoked according to the reinvocationPolicy. The mutations of a policy are invoked for each binding of this policy and reinvocation of mutations occurs on a per binding basis.", + "failurePolicy": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if paramKind refers to a non-existent Kind. A binding is invalid if paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the matchConstraints. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "reinvocationPolicy": "reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: These mutations will not be called more than once per binding in a single admission evaluation.\n\nIfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only reinvoked when mutations change the object after this mutation is invoked. Required.", +} + +func (MutatingAdmissionPolicySpec) SwaggerDoc() map[string]string { + return map_MutatingAdmissionPolicySpec +} + var map_MutatingWebhook = map[string]string{ "": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.", - "name": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", - "clientConfig": "ClientConfig defines how to communicate with the hook. Required", - "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", - "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", + "name": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "clientConfig": "clientConfig defines how to communicate with the hook. Required", + "rules": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "failurePolicy": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", "matchPolicy": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Equivalent\"", - "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "sideEffects": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", - "timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", - "admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + "namespaceSelector": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "sideEffects": "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", + "timeoutSeconds": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + "admissionReviewVersions": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", "reinvocationPolicy": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: the webhook will not be called more than once in a single admission evaluation.\n\nIfNeeded: the webhook will be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call. Webhooks that specify this option *must* be idempotent, able to process objects they previously admitted. Note: * the number of additional invocations is not guaranteed to be exactly one. * if additional invocations result in further modifications to the object, webhooks are not guaranteed to be invoked again. * webhooks that use this option may be reordered to minimize the number of additional invocations. * to validate an object after all mutations are guaranteed complete, use a validating admission webhook instead.\n\nDefaults to \"Never\".", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", } func (MutatingWebhook) SwaggerDoc() map[string]string { @@ -92,8 +176,8 @@ func (MutatingWebhook) SwaggerDoc() map[string]string { var map_MutatingWebhookConfiguration = map[string]string{ "": "MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "webhooks": "Webhooks is a list of webhooks and the affected resources and operations.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "webhooks": "webhooks is a list of webhooks and the affected resources and operations.", } func (MutatingWebhookConfiguration) SwaggerDoc() map[string]string { @@ -102,7 +186,7 @@ func (MutatingWebhookConfiguration) SwaggerDoc() map[string]string { var map_MutatingWebhookConfigurationList = map[string]string{ "": "MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of MutatingWebhookConfiguration.", } @@ -110,9 +194,20 @@ func (MutatingWebhookConfigurationList) SwaggerDoc() map[string]string { return map_MutatingWebhookConfigurationList } +var map_Mutation = map[string]string{ + "": "Mutation specifies the CEL expression which is used to apply the Mutation.", + "patchType": "patchType indicates the patch strategy used. Allowed values are \"ApplyConfiguration\" and \"JSONPatch\". Required.", + "applyConfiguration": "applyConfiguration defines the desired configuration values of an object. The configuration is applied to the admission object using [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). A CEL expression is used to create apply configuration.", + "jsonPatch": "jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. A CEL expression is used to create the JSON patch.", +} + +func (Mutation) SwaggerDoc() map[string]string { + return map_Mutation +} + var map_NamedRuleWithOperations = map[string]string{ "": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", - "resourceNames": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "resourceNames": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", } func (NamedRuleWithOperations) SwaggerDoc() map[string]string { @@ -121,8 +216,8 @@ func (NamedRuleWithOperations) SwaggerDoc() map[string]string { var map_ParamKind = map[string]string{ "": "ParamKind is a tuple of Group Kind and Version.", - "apiVersion": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", - "kind": "Kind is the API kind the resources belong to. Required.", + "apiVersion": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "kind": "kind is the API kind the resources belong to. Required.", } func (ParamKind) SwaggerDoc() map[string]string { @@ -134,7 +229,7 @@ var map_ParamRef = map[string]string{ "name": "name is the name of the resource being referenced.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.\n\nA single parameter used for all admission requests can be configured by setting the `name` field, leaving `selector` blank, and setting namespace if `paramKind` is namespace-scoped.", "namespace": "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.", "selector": "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", - "parameterNotFoundAction": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + "parameterNotFoundAction": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", } func (ParamRef) SwaggerDoc() map[string]string { @@ -143,9 +238,9 @@ func (ParamRef) SwaggerDoc() map[string]string { var map_Rule = map[string]string{ "": "Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended to make sure that all the tuple expansions are valid.", - "apiGroups": "APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", - "apiVersions": "APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", - "resources": "Resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", + "apiGroups": "apiGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required.", + "apiVersions": "apiVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required.", + "resources": "resources is a list of resources this rule applies to.\n\nFor example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources.\n\nIf wildcard is present, the validation rule will ensure resources do not overlap with each other.\n\nDepending on the enclosing object, subresources might not be allowed. Required.", "scope": "scope specifies the scope of this rule. Valid values are \"Cluster\", \"Namespaced\", and \"*\" \"Cluster\" means that only cluster-scoped resources will match this rule. Namespace API objects are cluster-scoped. \"Namespaced\" means that only namespaced resources will match this rule. \"*\" means that there are no scope restrictions. Subresources match the scope of their parent resource. Default is \"*\".", } @@ -155,7 +250,7 @@ func (Rule) SwaggerDoc() map[string]string { var map_RuleWithOperations = map[string]string{ "": "RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.", - "operations": "Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", + "operations": "operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required.", } func (RuleWithOperations) SwaggerDoc() map[string]string { @@ -164,10 +259,10 @@ func (RuleWithOperations) SwaggerDoc() map[string]string { var map_ServiceReference = map[string]string{ "": "ServiceReference holds a reference to Service.legacy.k8s.io", - "namespace": "`namespace` is the namespace of the service. Required", - "name": "`name` is the name of the service. Required", - "path": "`path` is an optional URL path which will be sent in any request to this service.", - "port": "If specified, the port on the service that hosting webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", + "namespace": "namespace is the namespace of the service. Required", + "name": "name is the name of the service. Required", + "path": "path is an optional URL path which will be sent in any request to this service.", + "port": "port is the port on the service that hosts the webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", } func (ServiceReference) SwaggerDoc() map[string]string { @@ -176,7 +271,7 @@ func (ServiceReference) SwaggerDoc() map[string]string { var map_TypeChecking = map[string]string{ "": "TypeChecking contains results of type checking the expressions in the ValidatingAdmissionPolicy", - "expressionWarnings": "The type checking warnings for each expression.", + "expressionWarnings": "expressionWarnings contains the type checking warnings for each expression.", } func (TypeChecking) SwaggerDoc() map[string]string { @@ -185,9 +280,9 @@ func (TypeChecking) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicy = map[string]string{ "": "ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the ValidatingAdmissionPolicy.", - "status": "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the ValidatingAdmissionPolicy.", + "status": "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", } func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string { @@ -196,8 +291,8 @@ func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBinding = map[string]string{ "": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding.", } func (ValidatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { @@ -206,7 +301,7 @@ func (ValidatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBindingList = map[string]string{ "": "ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of PolicyBinding.", } @@ -216,9 +311,9 @@ func (ValidatingAdmissionPolicyBindingList) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBindingSpec = map[string]string{ "": "ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding.", - "policyName": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "policyName": "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", "paramRef": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.", - "matchResources": "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", + "matchResources": "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", "validationActions": "validationActions declares how Validations of the referenced ValidatingAdmissionPolicy are enforced. If a validation evaluates to false it is always enforced according to these actions.\n\nFailures defined by the ValidatingAdmissionPolicy's FailurePolicy are enforced according to these actions only if the FailurePolicy is set to Fail, otherwise the failures are ignored. This includes compilation errors, runtime errors and misconfigurations of the policy.\n\nvalidationActions is declared as a set of action values. Order does not matter. validationActions may not contain duplicates of the same action.\n\nThe supported actions values are:\n\n\"Deny\" specifies that a validation failure results in a denied request.\n\n\"Warn\" specifies that a validation failure is reported to the request client in HTTP Warning headers, with a warning code of 299. Warnings can be sent both for allowed or denied admission responses.\n\n\"Audit\" specifies that a validation failure is included in the published audit event for the request. The audit event will contain a `validation.policy.admission.k8s.io/validation_failure` audit annotation with a value containing the details of the validation failures, formatted as a JSON list of objects, each with the following fields: - message: The validation failure message string - policy: The resource name of the ValidatingAdmissionPolicy - binding: The resource name of the ValidatingAdmissionPolicyBinding - expressionIndex: The index of the failed validations in the ValidatingAdmissionPolicy - validationActions: The enforcement actions enacted for the validation failure Example audit annotation: `\"validation.policy.admission.k8s.io/validation_failure\": \"[{\"message\": \"Invalid value\", {\"policy\": \"policy.example.com\", {\"binding\": \"policybinding.example.com\", {\"expressionIndex\": \"1\", {\"validationActions\": [\"Audit\"]}]\"`\n\nClients should expect to handle additional values by ignoring any values not recognized.\n\n\"Deny\" and \"Warn\" may not be used together since this combination needlessly duplicates the validation failure both in the API response body and the HTTP warning headers.\n\nRequired.", } @@ -228,7 +323,7 @@ func (ValidatingAdmissionPolicyBindingSpec) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyList = map[string]string{ "": "ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingAdmissionPolicy.", } @@ -238,13 +333,13 @@ func (ValidatingAdmissionPolicyList) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicySpec = map[string]string{ "": "ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy.", - "paramKind": "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", - "matchConstraints": "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", - "validations": "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + "paramKind": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", + "matchConstraints": "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", + "validations": "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", "failurePolicy": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if spec.paramKind refers to a non-existent Kind. A binding is invalid if spec.paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nWhen failurePolicy is set to Fail, ValidatingAdmissionPolicyBinding validationActions define how failures are enforced.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", "auditAnnotations": "auditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request. validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is required.", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", - "variables": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "variables": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", } func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { @@ -253,9 +348,9 @@ func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyStatus = map[string]string{ "": "ValidatingAdmissionPolicyStatus represents the status of an admission validation policy.", - "observedGeneration": "The generation observed by the controller.", - "typeChecking": "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.", - "conditions": "The conditions represent the latest available observations of a policy's current state.", + "observedGeneration": "observedGeneration is the generation observed by the controller.", + "typeChecking": "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking.", + "conditions": "conditions represent the latest available observations of a policy's current state.", } func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string { @@ -264,17 +359,17 @@ func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string { var map_ValidatingWebhook = map[string]string{ "": "ValidatingWebhook describes an admission webhook and the resources and operations it applies to.", - "name": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", - "clientConfig": "ClientConfig defines how to communicate with the hook. Required", - "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", - "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", + "name": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "clientConfig": "clientConfig defines how to communicate with the hook. Required", + "rules": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "failurePolicy": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.", "matchPolicy": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Equivalent\"", - "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "sideEffects": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", - "timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", - "admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "namespaceSelector": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "sideEffects": "sideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.", + "timeoutSeconds": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.", + "admissionReviewVersions": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", } func (ValidatingWebhook) SwaggerDoc() map[string]string { @@ -283,8 +378,8 @@ func (ValidatingWebhook) SwaggerDoc() map[string]string { var map_ValidatingWebhookConfiguration = map[string]string{ "": "ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "webhooks": "Webhooks is a list of webhooks and the affected resources and operations.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "webhooks": "webhooks is a list of webhooks and the affected resources and operations.", } func (ValidatingWebhookConfiguration) SwaggerDoc() map[string]string { @@ -293,7 +388,7 @@ func (ValidatingWebhookConfiguration) SwaggerDoc() map[string]string { var map_ValidatingWebhookConfigurationList = map[string]string{ "": "ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingWebhookConfiguration.", } @@ -303,9 +398,9 @@ func (ValidatingWebhookConfigurationList) SwaggerDoc() map[string]string { var map_Validation = map[string]string{ "": "Validation specifies the CEL expression which is used to apply the validation.", - "expression": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", - "message": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", - "reason": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + "expression": "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + "message": "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + "reason": "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", "messageExpression": "messageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a validation, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the `expression` except for 'authorizer' and 'authorizer.requestResource'. Example: \"object.x must be less than max (\"+string(params.max)+\")\"", } @@ -315,8 +410,8 @@ func (Validation) SwaggerDoc() map[string]string { var map_Variable = map[string]string{ "": "Variable is the definition of a variable that is used for composition. A variable is defined as a named expression.", - "name": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", - "expression": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "name": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "expression": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", } func (Variable) SwaggerDoc() map[string]string { @@ -325,9 +420,9 @@ func (Variable) SwaggerDoc() map[string]string { var map_WebhookClientConfig = map[string]string{ "": "WebhookClientConfig contains the information to make a TLS connection with the webhook", - "url": "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", - "service": "`service` is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", - "caBundle": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + "url": "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + "service": "service is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", + "caBundle": "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", } func (WebhookClientConfig) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.deepcopy.go index bfe599c1d3275..afdbfd914399a 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.deepcopy.go @@ -26,6 +26,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplyConfiguration) DeepCopyInto(out *ApplyConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplyConfiguration. +func (in *ApplyConfiguration) DeepCopy() *ApplyConfiguration { + if in == nil { + return nil + } + out := new(ApplyConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuditAnnotation) DeepCopyInto(out *AuditAnnotation) { *out = *in @@ -58,6 +74,22 @@ func (in *ExpressionWarning) DeepCopy() *ExpressionWarning { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONPatch) DeepCopyInto(out *JSONPatch) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatch. +func (in *JSONPatch) DeepCopy() *JSONPatch { + if in == nil { + return nil + } + out := new(JSONPatch) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MatchCondition) DeepCopyInto(out *MatchCondition) { *out = *in @@ -119,6 +151,200 @@ func (in *MatchResources) DeepCopy() *MatchResources { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicy) DeepCopyInto(out *MutatingAdmissionPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicy. +func (in *MutatingAdmissionPolicy) DeepCopy() *MutatingAdmissionPolicy { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingAdmissionPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicyBinding) DeepCopyInto(out *MutatingAdmissionPolicyBinding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicyBinding. +func (in *MutatingAdmissionPolicyBinding) DeepCopy() *MutatingAdmissionPolicyBinding { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicyBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingAdmissionPolicyBinding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicyBindingList) DeepCopyInto(out *MutatingAdmissionPolicyBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MutatingAdmissionPolicyBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicyBindingList. +func (in *MutatingAdmissionPolicyBindingList) DeepCopy() *MutatingAdmissionPolicyBindingList { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicyBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingAdmissionPolicyBindingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicyBindingSpec) DeepCopyInto(out *MutatingAdmissionPolicyBindingSpec) { + *out = *in + if in.ParamRef != nil { + in, out := &in.ParamRef, &out.ParamRef + *out = new(ParamRef) + (*in).DeepCopyInto(*out) + } + if in.MatchResources != nil { + in, out := &in.MatchResources, &out.MatchResources + *out = new(MatchResources) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicyBindingSpec. +func (in *MutatingAdmissionPolicyBindingSpec) DeepCopy() *MutatingAdmissionPolicyBindingSpec { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicyBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicyList) DeepCopyInto(out *MutatingAdmissionPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MutatingAdmissionPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicyList. +func (in *MutatingAdmissionPolicyList) DeepCopy() *MutatingAdmissionPolicyList { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingAdmissionPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicySpec) DeepCopyInto(out *MutatingAdmissionPolicySpec) { + *out = *in + if in.ParamKind != nil { + in, out := &in.ParamKind, &out.ParamKind + *out = new(ParamKind) + **out = **in + } + if in.MatchConstraints != nil { + in, out := &in.MatchConstraints, &out.MatchConstraints + *out = new(MatchResources) + (*in).DeepCopyInto(*out) + } + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make([]Variable, len(*in)) + copy(*out, *in) + } + if in.Mutations != nil { + in, out := &in.Mutations, &out.Mutations + *out = make([]Mutation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(FailurePolicyType) + **out = **in + } + if in.MatchConditions != nil { + in, out := &in.MatchConditions, &out.MatchConditions + *out = make([]MatchCondition, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicySpec. +func (in *MutatingAdmissionPolicySpec) DeepCopy() *MutatingAdmissionPolicySpec { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) { *out = *in @@ -254,6 +480,32 @@ func (in *MutatingWebhookConfigurationList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Mutation) DeepCopyInto(out *Mutation) { + *out = *in + if in.ApplyConfiguration != nil { + in, out := &in.ApplyConfiguration, &out.ApplyConfiguration + *out = new(ApplyConfiguration) + **out = **in + } + if in.JSONPatch != nil { + in, out := &in.JSONPatch, &out.JSONPatch + *out = new(JSONPatch) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mutation. +func (in *Mutation) DeepCopy() *Mutation { + if in == nil { + return nil + } + out := new(Mutation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamedRuleWithOperations) DeepCopyInto(out *NamedRuleWithOperations) { *out = *in diff --git a/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.model_name.go b/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.model_name.go index 3264285cd072f..b7baac8d1ca07 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.model_name.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.model_name.go @@ -21,6 +21,11 @@ limitations under the License. package v1 +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ApplyConfiguration) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.ApplyConfiguration" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in AuditAnnotation) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.AuditAnnotation" @@ -31,6 +36,11 @@ func (in ExpressionWarning) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.ExpressionWarning" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in JSONPatch) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.JSONPatch" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in MatchCondition) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.MatchCondition" @@ -41,6 +51,36 @@ func (in MatchResources) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.MatchResources" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MutatingAdmissionPolicy) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MutatingAdmissionPolicyBinding) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MutatingAdmissionPolicyBindingList) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MutatingAdmissionPolicyBindingSpec) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MutatingAdmissionPolicyList) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MutatingAdmissionPolicySpec) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in MutatingWebhook) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.MutatingWebhook" @@ -56,6 +96,11 @@ func (in MutatingWebhookConfigurationList) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.MutatingWebhookConfigurationList" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Mutation) OpenAPIModelName() string { + return "io.k8s.api.admissionregistration.v1.Mutation" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in NamedRuleWithOperations) OpenAPIModelName() string { return "io.k8s.api.admissionregistration.v1.NamedRuleWithOperations" diff --git a/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.prerelease-lifecycle.go index 0862bb1f2d39c..5fde55f151bd0 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.prerelease-lifecycle.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/zz_generated.prerelease-lifecycle.go @@ -21,6 +21,30 @@ limitations under the License. package v1 +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *MutatingAdmissionPolicy) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *MutatingAdmissionPolicyBinding) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *MutatingAdmissionPolicyBindingList) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *MutatingAdmissionPolicyList) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + // APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. func (in *MutatingWebhookConfiguration) APILifecycleIntroduced() (major, minor int) { diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto index d23f21cc84e79..57c7cd2b1db88 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto @@ -113,12 +113,12 @@ message AuditAnnotation { // ExpressionWarning is a warning information that targets a specific expression. message ExpressionWarning { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" optional string fieldRef = 2; - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. optional string warning = 3; @@ -194,7 +194,7 @@ message JSONPatch { } message MatchCondition { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -205,7 +205,7 @@ message MatchCondition { // Required. optional string name = 1; - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. @@ -226,7 +226,7 @@ message MatchCondition { // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +structType=atomic message MatchResources { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -272,7 +272,7 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 1; - // ObjectSelector decides whether to run the policy based on if the + // objectSelector decides whether to run the policy based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the policy's expression (CEL), and // is considered to match if either object matches the selector. A null @@ -286,13 +286,13 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 2; - // ResourceRules describes what operations on what resources/subresources the admission policy matches. + // resourceRules describes what operations on what resources/subresources the admission policy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional repeated NamedRuleWithOperations resourceRules = 3; - // ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the policy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -319,11 +319,11 @@ message MatchResources { // MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. message MutatingAdmissionPolicy { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the MutatingAdmissionPolicy. + // spec defines the desired behavior of the MutatingAdmissionPolicy. optional MutatingAdmissionPolicySpec spec = 2; } @@ -339,17 +339,17 @@ message MutatingAdmissionPolicy { // Adding/removing policies, bindings, or params can not affect whether a // given (policy, binding, param) combination is within its own CEL budget. message MutatingAdmissionPolicyBinding { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the MutatingAdmissionPolicyBinding. + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. optional MutatingAdmissionPolicyBindingSpec spec = 2; } // MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding. message MutatingAdmissionPolicyBindingList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -387,7 +387,7 @@ message MutatingAdmissionPolicyBindingSpec { // MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy. message MutatingAdmissionPolicyList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -507,7 +507,7 @@ message Mutation { // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. // +structType=atomic message NamedRuleWithOperations { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. // +listType=atomic // +optional repeated string resourceNames = 1; @@ -519,12 +519,12 @@ message NamedRuleWithOperations { // ParamKind is a tuple of Group Kind and Version. // +structType=atomic message ParamKind { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. optional string apiVersion = 1; - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. optional string kind = 2; } @@ -533,7 +533,7 @@ message ParamKind { // expressions of rules applied by a policy binding. // +structType=atomic message ParamRef { - // `name` is the name of the resource being referenced. + // name is the name of the resource being referenced. // // `name` and `selector` are mutually exclusive properties. If one is set, // the other must be unset. @@ -571,7 +571,7 @@ message ParamRef { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 3; - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. @@ -587,7 +587,7 @@ message ParamRef { // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy message TypeChecking { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. // +optional // +listType=atomic repeated ExpressionWarning expressionWarnings = 1; @@ -595,14 +595,14 @@ message TypeChecking { // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. message ValidatingAdmissionPolicy { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. optional ValidatingAdmissionPolicySpec spec = 2; - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. @@ -622,17 +622,18 @@ message ValidatingAdmissionPolicy { // Adding/removing policies, bindings, or params can not affect whether a // given (policy, binding, param) combination is within its own CEL budget. message ValidatingAdmissionPolicyBinding { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. + // +required optional ValidatingAdmissionPolicyBindingSpec spec = 2; } // ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding. message ValidatingAdmissionPolicyBindingList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -643,9 +644,11 @@ message ValidatingAdmissionPolicyBindingList { // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. message ValidatingAdmissionPolicyBindingSpec { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string policyName = 1; // paramRef specifies the parameter resource used to configure the admission control policy. @@ -655,7 +658,7 @@ message ValidatingAdmissionPolicyBindingSpec { // +optional optional ParamRef paramRef = 2; - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. @@ -703,12 +706,14 @@ message ValidatingAdmissionPolicyBindingSpec { // // Required. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required repeated string validationActions = 4; } // ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy. message ValidatingAdmissionPolicyList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -719,21 +724,21 @@ message ValidatingAdmissionPolicyList { // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. message ValidatingAdmissionPolicySpec { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. // +optional optional ParamKind paramKind = 1; - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. optional MatchResources matchConstraints = 2; - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. // +listType=atomic @@ -764,7 +769,7 @@ message ValidatingAdmissionPolicySpec { // +optional repeated AuditAnnotation auditAnnotations = 5; - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -786,7 +791,7 @@ message ValidatingAdmissionPolicySpec { // +optional repeated MatchCondition matchConditions = 6; - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. @@ -803,16 +808,16 @@ message ValidatingAdmissionPolicySpec { // ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy. message ValidatingAdmissionPolicyStatus { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. // +optional optional int64 observedGeneration = 1; - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. // +optional optional TypeChecking typeChecking = 2; - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. // +optional // +listType=map // +listMapKey=type @@ -821,7 +826,7 @@ message ValidatingAdmissionPolicyStatus { // Validation specifies the CEL expression which is used to apply the validation. message Validation { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -864,7 +869,7 @@ message Validation { // Required. optional string Expression = 1; - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -874,7 +879,7 @@ message Validation { // +optional optional string message = 2; - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. @@ -899,12 +904,12 @@ message Validation { // Variable is the definition of a variable that is used for composition. message Variable { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` optional string Name = 1; - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. optional string Expression = 2; } diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index 651a01f0b395f..0000000000000 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*ApplyConfiguration) ProtoMessage() {} - -func (*AuditAnnotation) ProtoMessage() {} - -func (*ExpressionWarning) ProtoMessage() {} - -func (*JSONPatch) ProtoMessage() {} - -func (*MatchCondition) ProtoMessage() {} - -func (*MatchResources) ProtoMessage() {} - -func (*MutatingAdmissionPolicy) ProtoMessage() {} - -func (*MutatingAdmissionPolicyBinding) ProtoMessage() {} - -func (*MutatingAdmissionPolicyBindingList) ProtoMessage() {} - -func (*MutatingAdmissionPolicyBindingSpec) ProtoMessage() {} - -func (*MutatingAdmissionPolicyList) ProtoMessage() {} - -func (*MutatingAdmissionPolicySpec) ProtoMessage() {} - -func (*Mutation) ProtoMessage() {} - -func (*NamedRuleWithOperations) ProtoMessage() {} - -func (*ParamKind) ProtoMessage() {} - -func (*ParamRef) ProtoMessage() {} - -func (*TypeChecking) ProtoMessage() {} - -func (*ValidatingAdmissionPolicy) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBinding) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBindingList) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBindingSpec) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyList) ProtoMessage() {} - -func (*ValidatingAdmissionPolicySpec) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyStatus) ProtoMessage() {} - -func (*Validation) ProtoMessage() {} - -func (*Variable) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go index 459f7944c7b2c..6a789b2d7f714 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go @@ -83,12 +83,12 @@ const ( // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. type ValidatingAdmissionPolicy struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. Spec ValidatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. @@ -98,14 +98,14 @@ type ValidatingAdmissionPolicy struct { // ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy. type ValidatingAdmissionPolicyStatus struct { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. // +optional TypeChecking *TypeChecking `json:"typeChecking,omitempty" protobuf:"bytes,2,opt,name=typeChecking"` - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. // +optional // +listType=map // +listMapKey=type @@ -115,7 +115,7 @@ type ValidatingAdmissionPolicyStatus struct { // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy type TypeChecking struct { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. // +optional // +listType=atomic ExpressionWarnings []ExpressionWarning `json:"expressionWarnings,omitempty" protobuf:"bytes,1,rep,name=expressionWarnings"` @@ -123,11 +123,11 @@ type TypeChecking struct { // ExpressionWarning is a warning information that targets a specific expression. type ExpressionWarning struct { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" FieldRef string `json:"fieldRef" protobuf:"bytes,2,opt,name=fieldRef"` - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. Warning string `json:"warning" protobuf:"bytes,3,opt,name=warning"` @@ -139,7 +139,7 @@ type ExpressionWarning struct { // ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy. type ValidatingAdmissionPolicyList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -149,21 +149,21 @@ type ValidatingAdmissionPolicyList struct { // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. type ValidatingAdmissionPolicySpec struct { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. // +optional ParamKind *ParamKind `json:"paramKind,omitempty" protobuf:"bytes,1,rep,name=paramKind"` - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. MatchConstraints *MatchResources `json:"matchConstraints,omitempty" protobuf:"bytes,2,rep,name=matchConstraints"` - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. // +listType=atomic @@ -194,7 +194,7 @@ type ValidatingAdmissionPolicySpec struct { // +optional AuditAnnotations []AuditAnnotation `json:"auditAnnotations,omitempty" protobuf:"bytes,5,rep,name=auditAnnotations"` - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -216,7 +216,7 @@ type ValidatingAdmissionPolicySpec struct { // +optional MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,6,rep,name=matchConditions"` - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. @@ -236,19 +236,19 @@ type MatchCondition v1.MatchCondition // ParamKind is a tuple of Group Kind and Version. // +structType=atomic type ParamKind struct { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,1,rep,name=apiVersion"` - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. Kind string `json:"kind,omitempty" protobuf:"bytes,2,rep,name=kind"` } // Validation specifies the CEL expression which is used to apply the validation. type Validation struct { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -290,7 +290,7 @@ type Validation struct { // non-intersecting keys are appended, retaining their partial order. // Required. Expression string `json:"expression" protobuf:"bytes,1,opt,name=Expression"` - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -299,7 +299,7 @@ type Validation struct { // If unset, the message is "failed Expression: {Expression}". // +optional Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. @@ -323,12 +323,12 @@ type Validation struct { // Variable is the definition of a variable that is used for composition. type Variable struct { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` Name string `json:"name" protobuf:"bytes,1,opt,name=Name"` - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. Expression string `json:"expression" protobuf:"bytes,2,opt,name=Expression"` } @@ -388,10 +388,11 @@ type AuditAnnotation struct { // given (policy, binding, param) combination is within its own CEL budget. type ValidatingAdmissionPolicyBinding struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. + // +required Spec ValidatingAdmissionPolicyBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -401,7 +402,7 @@ type ValidatingAdmissionPolicyBinding struct { // ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -411,9 +412,11 @@ type ValidatingAdmissionPolicyBindingList struct { // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingSpec struct { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required PolicyName string `json:"policyName,omitempty" protobuf:"bytes,1,rep,name=policyName"` // paramRef specifies the parameter resource used to configure the admission control policy. @@ -423,7 +426,7 @@ type ValidatingAdmissionPolicyBindingSpec struct { // +optional ParamRef *ParamRef `json:"paramRef,omitempty" protobuf:"bytes,2,rep,name=paramRef"` - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. @@ -471,6 +474,8 @@ type ValidatingAdmissionPolicyBindingSpec struct { // // Required. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"` } @@ -478,7 +483,7 @@ type ValidatingAdmissionPolicyBindingSpec struct { // expressions of rules applied by a policy binding. // +structType=atomic type ParamRef struct { - // `name` is the name of the resource being referenced. + // name is the name of the resource being referenced. // // `name` and `selector` are mutually exclusive properties. If one is set, // the other must be unset. @@ -516,7 +521,7 @@ type ParamRef struct { // +optional Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,rep,name=selector"` - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. @@ -534,7 +539,7 @@ type ParamRef struct { // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +structType=atomic type MatchResources struct { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -579,7 +584,7 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,1,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the policy based on if the + // objectSelector decides whether to run the policy based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the policy's expression (CEL), and // is considered to match if either object matches the selector. A null @@ -592,12 +597,12 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,2,opt,name=objectSelector"` - // ResourceRules describes what operations on what resources/subresources the admission policy matches. + // resourceRules describes what operations on what resources/subresources the admission policy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional ResourceRules []NamedRuleWithOperations `json:"resourceRules,omitempty" protobuf:"bytes,3,rep,name=resourceRules"` - // ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the policy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -642,7 +647,7 @@ const ( // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. // +structType=atomic type NamedRuleWithOperations struct { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. // +listType=atomic // +optional ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,1,rep,name=resourceNames"` @@ -675,10 +680,10 @@ const ( // MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. type MutatingAdmissionPolicy struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the MutatingAdmissionPolicy. + // spec defines the desired behavior of the MutatingAdmissionPolicy. Spec MutatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -688,7 +693,7 @@ type MutatingAdmissionPolicy struct { // MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy. type MutatingAdmissionPolicyList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -963,10 +968,10 @@ const ( // given (policy, binding, param) combination is within its own CEL budget. type MutatingAdmissionPolicyBinding struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the MutatingAdmissionPolicyBinding. + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. Spec MutatingAdmissionPolicyBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -976,7 +981,7 @@ type MutatingAdmissionPolicyBinding struct { // MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding. type MutatingAdmissionPolicyBindingList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go index 116e56e06568e..4b2b13bc4a646 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go @@ -48,8 +48,8 @@ func (AuditAnnotation) SwaggerDoc() map[string]string { var map_ExpressionWarning = map[string]string{ "": "ExpressionWarning is a warning information that targets a specific expression.", - "fieldRef": "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", - "warning": "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + "fieldRef": "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + "warning": "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", } func (ExpressionWarning) SwaggerDoc() map[string]string { @@ -67,10 +67,10 @@ func (JSONPatch) SwaggerDoc() map[string]string { var map_MatchResources = map[string]string{ "": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", - "namespaceSelector": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "resourceRules": "ResourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", - "excludeResourceRules": "ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "namespaceSelector": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "resourceRules": "resourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", + "excludeResourceRules": "excludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "matchPolicy": "matchPolicy defines how the \"MatchResources\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, the admission policy does not consider requests to apps/v1beta1 or extensions/v1beta1 API groups.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, the admission policy **does** consider requests made to apps/v1beta1 or extensions/v1beta1 API groups. The API server translates the request to a matched resource API if necessary.\n\nDefaults to \"Equivalent\"", } @@ -80,8 +80,8 @@ func (MatchResources) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicy = map[string]string{ "": "MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the MutatingAdmissionPolicy.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the MutatingAdmissionPolicy.", } func (MutatingAdmissionPolicy) SwaggerDoc() map[string]string { @@ -90,8 +90,8 @@ func (MutatingAdmissionPolicy) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicyBinding = map[string]string{ "": "MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding. Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget).\n\nAdding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the MutatingAdmissionPolicyBinding.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding.", } func (MutatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { @@ -100,7 +100,7 @@ func (MutatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicyBindingList = map[string]string{ "": "MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of PolicyBinding.", } @@ -121,7 +121,7 @@ func (MutatingAdmissionPolicyBindingSpec) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicyList = map[string]string{ "": "MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingAdmissionPolicy.", } @@ -157,7 +157,7 @@ func (Mutation) SwaggerDoc() map[string]string { var map_NamedRuleWithOperations = map[string]string{ "": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", - "resourceNames": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "resourceNames": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", } func (NamedRuleWithOperations) SwaggerDoc() map[string]string { @@ -166,8 +166,8 @@ func (NamedRuleWithOperations) SwaggerDoc() map[string]string { var map_ParamKind = map[string]string{ "": "ParamKind is a tuple of Group Kind and Version.", - "apiVersion": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", - "kind": "Kind is the API kind the resources belong to. Required.", + "apiVersion": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "kind": "kind is the API kind the resources belong to. Required.", } func (ParamKind) SwaggerDoc() map[string]string { @@ -176,10 +176,10 @@ func (ParamKind) SwaggerDoc() map[string]string { var map_ParamRef = map[string]string{ "": "ParamRef describes how to locate the params to be used as input to expressions of rules applied by a policy binding.", - "name": "`name` is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", + "name": "name is the name of the resource being referenced.\n\n`name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", "namespace": "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.", "selector": "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", - "parameterNotFoundAction": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`", + "parameterNotFoundAction": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny` Default to `Deny`", } func (ParamRef) SwaggerDoc() map[string]string { @@ -188,7 +188,7 @@ func (ParamRef) SwaggerDoc() map[string]string { var map_TypeChecking = map[string]string{ "": "TypeChecking contains results of type checking the expressions in the ValidatingAdmissionPolicy", - "expressionWarnings": "The type checking warnings for each expression.", + "expressionWarnings": "expressionWarnings contains the type checking warnings for each expression.", } func (TypeChecking) SwaggerDoc() map[string]string { @@ -197,9 +197,9 @@ func (TypeChecking) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicy = map[string]string{ "": "ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the ValidatingAdmissionPolicy.", - "status": "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the ValidatingAdmissionPolicy.", + "status": "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", } func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string { @@ -208,8 +208,8 @@ func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBinding = map[string]string{ "": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding.", } func (ValidatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { @@ -218,7 +218,7 @@ func (ValidatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBindingList = map[string]string{ "": "ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of PolicyBinding.", } @@ -228,9 +228,9 @@ func (ValidatingAdmissionPolicyBindingList) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBindingSpec = map[string]string{ "": "ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding.", - "policyName": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "policyName": "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", "paramRef": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.", - "matchResources": "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", + "matchResources": "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", "validationActions": "validationActions declares how Validations of the referenced ValidatingAdmissionPolicy are enforced. If a validation evaluates to false it is always enforced according to these actions.\n\nFailures defined by the ValidatingAdmissionPolicy's FailurePolicy are enforced according to these actions only if the FailurePolicy is set to Fail, otherwise the failures are ignored. This includes compilation errors, runtime errors and misconfigurations of the policy.\n\nvalidationActions is declared as a set of action values. Order does not matter. validationActions may not contain duplicates of the same action.\n\nThe supported actions values are:\n\n\"Deny\" specifies that a validation failure results in a denied request.\n\n\"Warn\" specifies that a validation failure is reported to the request client in HTTP Warning headers, with a warning code of 299. Warnings can be sent both for allowed or denied admission responses.\n\n\"Audit\" specifies that a validation failure is included in the published audit event for the request. The audit event will contain a `validation.policy.admission.k8s.io/validation_failure` audit annotation with a value containing the details of the validation failures, formatted as a JSON list of objects, each with the following fields: - message: The validation failure message string - policy: The resource name of the ValidatingAdmissionPolicy - binding: The resource name of the ValidatingAdmissionPolicyBinding - expressionIndex: The index of the failed validations in the ValidatingAdmissionPolicy - validationActions: The enforcement actions enacted for the validation failure Example audit annotation: `\"validation.policy.admission.k8s.io/validation_failure\": \"[{\"message\": \"Invalid value\", {\"policy\": \"policy.example.com\", {\"binding\": \"policybinding.example.com\", {\"expressionIndex\": \"1\", {\"validationActions\": [\"Audit\"]}]\"`\n\nClients should expect to handle additional values by ignoring any values not recognized.\n\n\"Deny\" and \"Warn\" may not be used together since this combination needlessly duplicates the validation failure both in the API response body and the HTTP warning headers.\n\nRequired.", } @@ -240,7 +240,7 @@ func (ValidatingAdmissionPolicyBindingSpec) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyList = map[string]string{ "": "ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingAdmissionPolicy.", } @@ -250,13 +250,13 @@ func (ValidatingAdmissionPolicyList) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicySpec = map[string]string{ "": "ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy.", - "paramKind": "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", - "matchConstraints": "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", - "validations": "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + "paramKind": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", + "matchConstraints": "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", + "validations": "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", "failurePolicy": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if spec.paramKind refers to a non-existent Kind. A binding is invalid if spec.paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nWhen failurePolicy is set to Fail, ValidatingAdmissionPolicyBinding validationActions define how failures are enforced.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", "auditAnnotations": "auditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request. validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is required.", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", - "variables": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "variables": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", } func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { @@ -265,9 +265,9 @@ func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyStatus = map[string]string{ "": "ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy.", - "observedGeneration": "The generation observed by the controller.", - "typeChecking": "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.", - "conditions": "The conditions represent the latest available observations of a policy's current state.", + "observedGeneration": "observedGeneration is the generation observed by the controller.", + "typeChecking": "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking.", + "conditions": "conditions represent the latest available observations of a policy's current state.", } func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string { @@ -276,9 +276,9 @@ func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string { var map_Validation = map[string]string{ "": "Validation specifies the CEL expression which is used to apply the validation.", - "expression": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", - "message": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", - "reason": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + "expression": "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + "message": "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + "reason": "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", "messageExpression": "messageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a validation, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the `expression` except for 'authorizer' and 'authorizer.requestResource'. Example: \"object.x must be less than max (\"+string(params.max)+\")\"", } @@ -288,8 +288,8 @@ func (Validation) SwaggerDoc() map[string]string { var map_Variable = map[string]string{ "": "Variable is the definition of a variable that is used for composition.", - "name": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", - "expression": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "name": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "expression": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", } func (Variable) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto index d184664e56334..5fcb74f8baeed 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto @@ -113,12 +113,12 @@ message AuditAnnotation { // ExpressionWarning is a warning information that targets a specific expression. message ExpressionWarning { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" optional string fieldRef = 2; - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. optional string warning = 3; @@ -195,7 +195,7 @@ message JSONPatch { // MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook. message MatchCondition { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -206,7 +206,7 @@ message MatchCondition { // Required. optional string name = 1; - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. @@ -227,7 +227,7 @@ message MatchCondition { // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +structType=atomic message MatchResources { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -273,7 +273,7 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 1; - // ObjectSelector decides whether to run the validation based on if the + // objectSelector decides whether to run the validation based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the cel validation, and // is considered to match if either object matches the selector. A null @@ -287,13 +287,13 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 2; - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional repeated NamedRuleWithOperations resourceRules = 3; - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -319,11 +319,11 @@ message MatchResources { // MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. message MutatingAdmissionPolicy { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the MutatingAdmissionPolicy. + // spec defines the desired behavior of the MutatingAdmissionPolicy. optional MutatingAdmissionPolicySpec spec = 2; } @@ -339,17 +339,17 @@ message MutatingAdmissionPolicy { // Adding/removing policies, bindings, or params can not affect whether a // given (policy, binding, param) combination is within its own CEL budget. message MutatingAdmissionPolicyBinding { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the MutatingAdmissionPolicyBinding. + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. optional MutatingAdmissionPolicyBindingSpec spec = 2; } // MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding. message MutatingAdmissionPolicyBindingList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -387,7 +387,7 @@ message MutatingAdmissionPolicyBindingSpec { // MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy. message MutatingAdmissionPolicyList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -486,18 +486,18 @@ message MutatingAdmissionPolicySpec { // MutatingWebhook describes an admission webhook and the resources and operations it applies to. message MutatingWebhook { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. optional string name = 1; - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required optional WebhookClientConfig clientConfig = 2; - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -506,7 +506,7 @@ message MutatingWebhook { // +listType=atomic repeated .k8s.io.api.admissionregistration.v1.RuleWithOperations rules = 3; - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. // +optional optional string failurePolicy = 4; @@ -528,7 +528,7 @@ message MutatingWebhook { // +optional optional string matchPolicy = 9; - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -574,7 +574,7 @@ message MutatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -588,7 +588,7 @@ message MutatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 11; - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -597,7 +597,7 @@ message MutatingWebhook { // +optional optional string sideEffects = 6; - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -605,7 +605,7 @@ message MutatingWebhook { // +optional optional int32 timeoutSeconds = 7; - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -635,7 +635,7 @@ message MutatingWebhook { // +optional optional string reinvocationPolicy = 10; - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -658,11 +658,11 @@ message MutatingWebhook { // MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. // Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration instead. message MutatingWebhookConfiguration { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -673,7 +673,7 @@ message MutatingWebhookConfiguration { // MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. message MutatingWebhookConfigurationList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -705,7 +705,7 @@ message Mutation { // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. // +structType=atomic message NamedRuleWithOperations { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. // +listType=atomic // +optional repeated string resourceNames = 1; @@ -717,12 +717,12 @@ message NamedRuleWithOperations { // ParamKind is a tuple of Group Kind and Version. // +structType=atomic message ParamKind { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. optional string apiVersion = 1; - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. optional string kind = 2; } @@ -771,7 +771,7 @@ message ParamRef { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 3; - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. @@ -786,22 +786,22 @@ message ParamRef { // ServiceReference holds a reference to Service.legacy.k8s.io message ServiceReference { - // `namespace` is the namespace of the service. + // namespace is the namespace of the service. // Required optional string namespace = 1; - // `name` is the name of the service. + // name is the name of the service. // Required optional string name = 2; - // `path` is an optional URL path which will be sent in any request to + // path is an optional URL path which will be sent in any request to // this service. // +optional optional string path = 3; - // If specified, the port on the service that hosting webhook. + // port is the port on the service that hosts the webhook. // Default to 443 for backward compatibility. - // `port` should be a valid port number (1-65535, inclusive). + // port should be a valid port number (1-65535, inclusive). // +optional optional int32 port = 4; } @@ -809,7 +809,7 @@ message ServiceReference { // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy message TypeChecking { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. // +optional // +listType=atomic repeated ExpressionWarning expressionWarnings = 1; @@ -821,14 +821,14 @@ message TypeChecking { // +k8s:prerelease-lifecycle-gen:introduced=1.28 // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. message ValidatingAdmissionPolicy { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. optional ValidatingAdmissionPolicySpec spec = 2; - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. @@ -848,17 +848,18 @@ message ValidatingAdmissionPolicy { // Adding/removing policies, bindings, or params can not affect whether a // given (policy, binding, param) combination is within its own CEL budget. message ValidatingAdmissionPolicyBinding { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. + // +required optional ValidatingAdmissionPolicyBindingSpec spec = 2; } // ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding. message ValidatingAdmissionPolicyBindingList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -869,9 +870,11 @@ message ValidatingAdmissionPolicyBindingList { // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. message ValidatingAdmissionPolicyBindingSpec { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string policyName = 1; // paramRef specifies the parameter resource used to configure the admission control policy. @@ -881,7 +884,7 @@ message ValidatingAdmissionPolicyBindingSpec { // +optional optional ParamRef paramRef = 2; - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. @@ -929,6 +932,8 @@ message ValidatingAdmissionPolicyBindingSpec { // // Required. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required repeated string validationActions = 4; } @@ -936,7 +941,7 @@ message ValidatingAdmissionPolicyBindingSpec { // +k8s:prerelease-lifecycle-gen:introduced=1.28 // ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy. message ValidatingAdmissionPolicyList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -947,21 +952,21 @@ message ValidatingAdmissionPolicyList { // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. message ValidatingAdmissionPolicySpec { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. // +optional optional ParamKind paramKind = 1; - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. optional MatchResources matchConstraints = 2; - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. // +listType=atomic @@ -992,7 +997,7 @@ message ValidatingAdmissionPolicySpec { // +optional repeated AuditAnnotation auditAnnotations = 5; - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -1014,7 +1019,7 @@ message ValidatingAdmissionPolicySpec { // +optional repeated MatchCondition matchConditions = 6; - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. @@ -1031,16 +1036,16 @@ message ValidatingAdmissionPolicySpec { // ValidatingAdmissionPolicyStatus represents the status of an admission validation policy. message ValidatingAdmissionPolicyStatus { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. // +optional optional int64 observedGeneration = 1; - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. // +optional optional TypeChecking typeChecking = 2; - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. // +optional // +listType=map // +listMapKey=type @@ -1049,18 +1054,18 @@ message ValidatingAdmissionPolicyStatus { // ValidatingWebhook describes an admission webhook and the resources and operations it applies to. message ValidatingWebhook { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. optional string name = 1; - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required optional WebhookClientConfig clientConfig = 2; - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -1069,7 +1074,7 @@ message ValidatingWebhook { // +listType=atomic repeated .k8s.io.api.admissionregistration.v1.RuleWithOperations rules = 3; - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. // +optional optional string failurePolicy = 4; @@ -1091,7 +1096,7 @@ message ValidatingWebhook { // +optional optional string matchPolicy = 9; - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -1137,7 +1142,7 @@ message ValidatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -1151,7 +1156,7 @@ message ValidatingWebhook { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 10; - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -1161,7 +1166,7 @@ message ValidatingWebhook { // +listType=atomic optional string sideEffects = 6; - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -1169,7 +1174,7 @@ message ValidatingWebhook { // +optional optional int32 timeoutSeconds = 7; - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -1181,7 +1186,7 @@ message ValidatingWebhook { // +listType=atomic repeated string admissionReviewVersions = 8; - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -1204,11 +1209,11 @@ message ValidatingWebhook { // ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. // Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration instead. message ValidatingWebhookConfiguration { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -1219,7 +1224,7 @@ message ValidatingWebhookConfiguration { // ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. message ValidatingWebhookConfigurationList { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; @@ -1230,7 +1235,7 @@ message ValidatingWebhookConfigurationList { // Validation specifies the CEL expression which is used to apply the validation. message Validation { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -1273,7 +1278,7 @@ message Validation { // Required. optional string Expression = 1; - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -1283,7 +1288,7 @@ message Validation { // +optional optional string message = 2; - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. @@ -1309,12 +1314,12 @@ message Validation { // Variable is the definition of a variable that is used for composition. A variable is defined as a named expression. // +structType=atomic message Variable { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` optional string Name = 1; - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. optional string Expression = 2; } @@ -1322,7 +1327,7 @@ message Variable { // WebhookClientConfig contains the information to make a TLS // connection with the webhook message WebhookClientConfig { - // `url` gives the location of the webhook, in standard URL form + // url gives the location of the webhook, in standard URL form // (`scheme://host:port/path`). Exactly one of `url` or `service` // must be specified. // @@ -1351,7 +1356,7 @@ message WebhookClientConfig { // +optional optional string url = 3; - // `service` is a reference to the service for this webhook. Either + // service is a reference to the service for this webhook. Either // `service` or `url` must be specified. // // If the webhook is running within the cluster, then you should use `service`. @@ -1359,7 +1364,7 @@ message WebhookClientConfig { // +optional optional ServiceReference service = 1; - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. // If unspecified, system trust roots on the apiserver are used. // +optional optional bytes caBundle = 2; diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 67b85ac629cb6..0000000000000 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,90 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*ApplyConfiguration) ProtoMessage() {} - -func (*AuditAnnotation) ProtoMessage() {} - -func (*ExpressionWarning) ProtoMessage() {} - -func (*JSONPatch) ProtoMessage() {} - -func (*MatchCondition) ProtoMessage() {} - -func (*MatchResources) ProtoMessage() {} - -func (*MutatingAdmissionPolicy) ProtoMessage() {} - -func (*MutatingAdmissionPolicyBinding) ProtoMessage() {} - -func (*MutatingAdmissionPolicyBindingList) ProtoMessage() {} - -func (*MutatingAdmissionPolicyBindingSpec) ProtoMessage() {} - -func (*MutatingAdmissionPolicyList) ProtoMessage() {} - -func (*MutatingAdmissionPolicySpec) ProtoMessage() {} - -func (*MutatingWebhook) ProtoMessage() {} - -func (*MutatingWebhookConfiguration) ProtoMessage() {} - -func (*MutatingWebhookConfigurationList) ProtoMessage() {} - -func (*Mutation) ProtoMessage() {} - -func (*NamedRuleWithOperations) ProtoMessage() {} - -func (*ParamKind) ProtoMessage() {} - -func (*ParamRef) ProtoMessage() {} - -func (*ServiceReference) ProtoMessage() {} - -func (*TypeChecking) ProtoMessage() {} - -func (*ValidatingAdmissionPolicy) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBinding) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBindingList) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyBindingSpec) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyList) ProtoMessage() {} - -func (*ValidatingAdmissionPolicySpec) ProtoMessage() {} - -func (*ValidatingAdmissionPolicyStatus) ProtoMessage() {} - -func (*ValidatingWebhook) ProtoMessage() {} - -func (*ValidatingWebhookConfiguration) ProtoMessage() {} - -func (*ValidatingWebhookConfigurationList) ProtoMessage() {} - -func (*Validation) ProtoMessage() {} - -func (*Variable) ProtoMessage() {} - -func (*WebhookClientConfig) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go index c7259d3d3e988..734a606f4b0b2 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go @@ -95,12 +95,12 @@ const ( // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. type ValidatingAdmissionPolicy struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. Spec ValidatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. @@ -110,14 +110,14 @@ type ValidatingAdmissionPolicy struct { // ValidatingAdmissionPolicyStatus represents the status of an admission validation policy. type ValidatingAdmissionPolicyStatus struct { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. // +optional TypeChecking *TypeChecking `json:"typeChecking,omitempty" protobuf:"bytes,2,opt,name=typeChecking"` - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. // +optional // +listType=map // +listMapKey=type @@ -130,7 +130,7 @@ type ValidatingAdmissionPolicyConditionType string // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy type TypeChecking struct { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. // +optional // +listType=atomic ExpressionWarnings []ExpressionWarning `json:"expressionWarnings,omitempty" protobuf:"bytes,1,rep,name=expressionWarnings"` @@ -138,11 +138,11 @@ type TypeChecking struct { // ExpressionWarning is a warning information that targets a specific expression. type ExpressionWarning struct { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" FieldRef string `json:"fieldRef" protobuf:"bytes,2,opt,name=fieldRef"` - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. Warning string `json:"warning" protobuf:"bytes,3,opt,name=warning"` @@ -153,7 +153,7 @@ type ExpressionWarning struct { // ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy. type ValidatingAdmissionPolicyList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -163,21 +163,21 @@ type ValidatingAdmissionPolicyList struct { // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. type ValidatingAdmissionPolicySpec struct { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. // +optional ParamKind *ParamKind `json:"paramKind,omitempty" protobuf:"bytes,1,rep,name=paramKind"` - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. MatchConstraints *MatchResources `json:"matchConstraints,omitempty" protobuf:"bytes,2,rep,name=matchConstraints"` - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. // +listType=atomic @@ -208,7 +208,7 @@ type ValidatingAdmissionPolicySpec struct { // +optional AuditAnnotations []AuditAnnotation `json:"auditAnnotations,omitempty" protobuf:"bytes,5,rep,name=auditAnnotations"` - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -230,7 +230,7 @@ type ValidatingAdmissionPolicySpec struct { // +optional MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,6,rep,name=matchConditions"` - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. @@ -248,19 +248,19 @@ type ValidatingAdmissionPolicySpec struct { // ParamKind is a tuple of Group Kind and Version. // +structType=atomic type ParamKind struct { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,1,rep,name=apiVersion"` - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. Kind string `json:"kind,omitempty" protobuf:"bytes,2,rep,name=kind"` } // Validation specifies the CEL expression which is used to apply the validation. type Validation struct { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -302,7 +302,7 @@ type Validation struct { // non-intersecting keys are appended, retaining their partial order. // Required. Expression string `json:"expression" protobuf:"bytes,1,opt,name=Expression"` - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -311,7 +311,7 @@ type Validation struct { // If unset, the message is "failed Expression: {Expression}". // +optional Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. @@ -336,12 +336,12 @@ type Validation struct { // Variable is the definition of a variable that is used for composition. A variable is defined as a named expression. // +structType=atomic type Variable struct { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` Name string `json:"name" protobuf:"bytes,1,opt,name=Name"` - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. Expression string `json:"expression" protobuf:"bytes,2,opt,name=Expression"` } @@ -401,10 +401,11 @@ type AuditAnnotation struct { // given (policy, binding, param) combination is within its own CEL budget. type ValidatingAdmissionPolicyBinding struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. + // +required Spec ValidatingAdmissionPolicyBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -414,7 +415,7 @@ type ValidatingAdmissionPolicyBinding struct { // ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -424,9 +425,11 @@ type ValidatingAdmissionPolicyBindingList struct { // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingSpec struct { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required PolicyName string `json:"policyName,omitempty" protobuf:"bytes,1,rep,name=policyName"` // paramRef specifies the parameter resource used to configure the admission control policy. @@ -436,7 +439,7 @@ type ValidatingAdmissionPolicyBindingSpec struct { // +optional ParamRef *ParamRef `json:"paramRef,omitempty" protobuf:"bytes,2,rep,name=paramRef"` - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. @@ -484,6 +487,8 @@ type ValidatingAdmissionPolicyBindingSpec struct { // // Required. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"` } @@ -532,7 +537,7 @@ type ParamRef struct { // +optional Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,rep,name=selector"` - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. @@ -550,7 +555,7 @@ type ParamRef struct { // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +structType=atomic type MatchResources struct { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -595,7 +600,7 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,1,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the validation based on if the + // objectSelector decides whether to run the validation based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the cel validation, and // is considered to match if either object matches the selector. A null @@ -608,12 +613,12 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,2,opt,name=objectSelector"` - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional ResourceRules []NamedRuleWithOperations `json:"resourceRules,omitempty" protobuf:"bytes,3,rep,name=resourceRules"` - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -657,7 +662,7 @@ const ( // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. // +structType=atomic type NamedRuleWithOperations struct { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. // +listType=atomic // +optional ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,1,rep,name=resourceNames"` @@ -677,10 +682,10 @@ type NamedRuleWithOperations struct { // Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration instead. type ValidatingWebhookConfiguration struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -698,7 +703,7 @@ type ValidatingWebhookConfiguration struct { // ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. type ValidatingWebhookConfigurationList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -718,10 +723,10 @@ type ValidatingWebhookConfigurationList struct { // Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration instead. type MutatingWebhookConfiguration struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. // +optional // +patchMergeKey=name // +patchStrategy=merge @@ -739,7 +744,7 @@ type MutatingWebhookConfiguration struct { // MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. type MutatingWebhookConfigurationList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -749,18 +754,18 @@ type MutatingWebhookConfigurationList struct { // ValidatingWebhook describes an admission webhook and the resources and operations it applies to. type ValidatingWebhook struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -769,7 +774,7 @@ type ValidatingWebhook struct { // +listType=atomic Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. // +optional FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"` @@ -791,7 +796,7 @@ type ValidatingWebhook struct { // +optional MatchPolicy *MatchPolicyType `json:"matchPolicy,omitempty" protobuf:"bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -837,7 +842,7 @@ type ValidatingWebhook struct { // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -851,7 +856,7 @@ type ValidatingWebhook struct { // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,10,opt,name=objectSelector"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -861,7 +866,7 @@ type ValidatingWebhook struct { // +listType=atomic SideEffects *SideEffectClass `json:"sideEffects,omitempty" protobuf:"bytes,6,opt,name=sideEffects,casttype=SideEffectClass"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -869,7 +874,7 @@ type ValidatingWebhook struct { // +optional TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -881,7 +886,7 @@ type ValidatingWebhook struct { // +listType=atomic AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -903,18 +908,18 @@ type ValidatingWebhook struct { // MutatingWebhook describes an admission webhook and the resources and operations it applies to. type MutatingWebhook struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely @@ -923,7 +928,7 @@ type MutatingWebhook struct { // +listType=atomic Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. // +optional FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"` @@ -945,7 +950,7 @@ type MutatingWebhook struct { // +optional MatchPolicy *MatchPolicyType `json:"matchPolicy,omitempty" protobuf:"bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -991,7 +996,7 @@ type MutatingWebhook struct { // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -1005,7 +1010,7 @@ type MutatingWebhook struct { // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,11,opt,name=objectSelector"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. @@ -1014,7 +1019,7 @@ type MutatingWebhook struct { // +optional SideEffects *SideEffectClass `json:"sideEffects,omitempty" protobuf:"bytes,6,opt,name=sideEffects,casttype=SideEffectClass"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. @@ -1022,7 +1027,7 @@ type MutatingWebhook struct { // +optional TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -1052,7 +1057,7 @@ type MutatingWebhook struct { // +optional ReinvocationPolicy *ReinvocationPolicyType `json:"reinvocationPolicy,omitempty" protobuf:"bytes,10,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -1107,7 +1112,7 @@ const ( // WebhookClientConfig contains the information to make a TLS // connection with the webhook type WebhookClientConfig struct { - // `url` gives the location of the webhook, in standard URL form + // url gives the location of the webhook, in standard URL form // (`scheme://host:port/path`). Exactly one of `url` or `service` // must be specified. // @@ -1136,7 +1141,7 @@ type WebhookClientConfig struct { // +optional URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"` - // `service` is a reference to the service for this webhook. Either + // service is a reference to the service for this webhook. Either // `service` or `url` must be specified. // // If the webhook is running within the cluster, then you should use `service`. @@ -1144,7 +1149,7 @@ type WebhookClientConfig struct { // +optional Service *ServiceReference `json:"service,omitempty" protobuf:"bytes,1,opt,name=service"` - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. // If unspecified, system trust roots on the apiserver are used. // +optional CABundle []byte `json:"caBundle,omitempty" protobuf:"bytes,2,opt,name=caBundle"` @@ -1152,28 +1157,28 @@ type WebhookClientConfig struct { // ServiceReference holds a reference to Service.legacy.k8s.io type ServiceReference struct { - // `namespace` is the namespace of the service. + // namespace is the namespace of the service. // Required Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"` - // `name` is the name of the service. + // name is the name of the service. // Required Name string `json:"name" protobuf:"bytes,2,opt,name=name"` - // `path` is an optional URL path which will be sent in any request to + // path is an optional URL path which will be sent in any request to // this service. // +optional Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` - // If specified, the port on the service that hosting webhook. + // port is the port on the service that hosts the webhook. // Default to 443 for backward compatibility. - // `port` should be a valid port number (1-65535, inclusive). + // port should be a valid port number (1-65535, inclusive). // +optional Port *int32 `json:"port,omitempty" protobuf:"varint,4,opt,name=port"` } // MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook. type MatchCondition struct { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -1184,7 +1189,7 @@ type MatchCondition struct { // Required. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. @@ -1204,24 +1209,26 @@ type MatchCondition struct { // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.34 +// +k8s:prerelease-lifecycle-gen:replacement=admissionregistration.k8s.io,v1,MutatingAdmissionPolicy // MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. type MutatingAdmissionPolicy struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the MutatingAdmissionPolicy. + // spec defines the desired behavior of the MutatingAdmissionPolicy. Spec MutatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.34 +// +k8s:prerelease-lifecycle-gen:replacement=admissionregistration.k8s.io,v1,MutatingAdmissionPolicyList // MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy. type MutatingAdmissionPolicyList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` @@ -1467,6 +1474,7 @@ type JSONPatch struct { // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.34 +// +k8s:prerelease-lifecycle-gen:replacement=admissionregistration.k8s.io,v1,MutatingAdmissionPolicyBinding // MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. // MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators @@ -1481,20 +1489,21 @@ type JSONPatch struct { // given (policy, binding, param) combination is within its own CEL budget. type MutatingAdmissionPolicyBinding struct { metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the desired behavior of the MutatingAdmissionPolicyBinding. + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. Spec MutatingAdmissionPolicyBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.34 +// +k8s:prerelease-lifecycle-gen:replacement=admissionregistration.k8s.io,v1,MutatingAdmissionPolicyBindingList // MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding. type MutatingAdmissionPolicyBindingList struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go index 1a97c94729f31..0fcf37aa1326e 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go @@ -48,8 +48,8 @@ func (AuditAnnotation) SwaggerDoc() map[string]string { var map_ExpressionWarning = map[string]string{ "": "ExpressionWarning is a warning information that targets a specific expression.", - "fieldRef": "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", - "warning": "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", + "fieldRef": "fieldRef is the path to the field that refers to the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"", + "warning": "warning contains the content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.", } func (ExpressionWarning) SwaggerDoc() map[string]string { @@ -67,8 +67,8 @@ func (JSONPatch) SwaggerDoc() map[string]string { var map_MatchCondition = map[string]string{ "": "MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook.", - "name": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", - "expression": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", + "name": "name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.", + "expression": "expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.", } func (MatchCondition) SwaggerDoc() map[string]string { @@ -77,10 +77,10 @@ func (MatchCondition) SwaggerDoc() map[string]string { var map_MatchResources = map[string]string{ "": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", - "namespaceSelector": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "resourceRules": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", - "excludeResourceRules": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "namespaceSelector": "namespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "resourceRules": "resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", + "excludeResourceRules": "excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "matchPolicy": "matchPolicy defines how the \"MatchResources\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the ValidatingAdmissionPolicy.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the ValidatingAdmissionPolicy.\n\nDefaults to \"Equivalent\"", } @@ -90,8 +90,8 @@ func (MatchResources) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicy = map[string]string{ "": "MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the MutatingAdmissionPolicy.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the MutatingAdmissionPolicy.", } func (MutatingAdmissionPolicy) SwaggerDoc() map[string]string { @@ -100,8 +100,8 @@ func (MutatingAdmissionPolicy) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicyBinding = map[string]string{ "": "MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding. Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget).\n\nAdding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the MutatingAdmissionPolicyBinding.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the MutatingAdmissionPolicyBinding.", } func (MutatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { @@ -110,7 +110,7 @@ func (MutatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicyBindingList = map[string]string{ "": "MutatingAdmissionPolicyBindingList is a list of MutatingAdmissionPolicyBinding.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of PolicyBinding.", } @@ -131,7 +131,7 @@ func (MutatingAdmissionPolicyBindingSpec) SwaggerDoc() map[string]string { var map_MutatingAdmissionPolicyList = map[string]string{ "": "MutatingAdmissionPolicyList is a list of MutatingAdmissionPolicy.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingAdmissionPolicy.", } @@ -156,18 +156,18 @@ func (MutatingAdmissionPolicySpec) SwaggerDoc() map[string]string { var map_MutatingWebhook = map[string]string{ "": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.", - "name": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", - "clientConfig": "ClientConfig defines how to communicate with the hook. Required", - "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", - "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "name": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "clientConfig": "clientConfig defines how to communicate with the hook. Required", + "rules": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "failurePolicy": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", "matchPolicy": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Exact\"", - "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "sideEffects": "SideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", - "timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", - "admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", + "namespaceSelector": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "sideEffects": "sideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", + "timeoutSeconds": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", + "admissionReviewVersions": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", "reinvocationPolicy": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: the webhook will not be called more than once in a single admission evaluation.\n\nIfNeeded: the webhook will be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call. Webhooks that specify this option *must* be idempotent, able to process objects they previously admitted. Note: * the number of additional invocations is not guaranteed to be exactly one. * if additional invocations result in further modifications to the object, webhooks are not guaranteed to be invoked again. * webhooks that use this option may be reordered to minimize the number of additional invocations. * to validate an object after all mutations are guaranteed complete, use a validating admission webhook instead.\n\nDefaults to \"Never\".", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", } func (MutatingWebhook) SwaggerDoc() map[string]string { @@ -176,8 +176,8 @@ func (MutatingWebhook) SwaggerDoc() map[string]string { var map_MutatingWebhookConfiguration = map[string]string{ "": "MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration instead.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "webhooks": "Webhooks is a list of webhooks and the affected resources and operations.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "webhooks": "webhooks is a list of webhooks and the affected resources and operations.", } func (MutatingWebhookConfiguration) SwaggerDoc() map[string]string { @@ -186,7 +186,7 @@ func (MutatingWebhookConfiguration) SwaggerDoc() map[string]string { var map_MutatingWebhookConfigurationList = map[string]string{ "": "MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of MutatingWebhookConfiguration.", } @@ -207,7 +207,7 @@ func (Mutation) SwaggerDoc() map[string]string { var map_NamedRuleWithOperations = map[string]string{ "": "NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames.", - "resourceNames": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + "resourceNames": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", } func (NamedRuleWithOperations) SwaggerDoc() map[string]string { @@ -216,8 +216,8 @@ func (NamedRuleWithOperations) SwaggerDoc() map[string]string { var map_ParamKind = map[string]string{ "": "ParamKind is a tuple of Group Kind and Version.", - "apiVersion": "APIVersion is the API group version the resources belong to. In format of \"group/version\". Required.", - "kind": "Kind is the API kind the resources belong to. Required.", + "apiVersion": "apiVersion is the API group version the resources belong to. In format of \"group/version\". Required.", + "kind": "kind is the API kind the resources belong to. Required.", } func (ParamKind) SwaggerDoc() map[string]string { @@ -229,7 +229,7 @@ var map_ParamRef = map[string]string{ "name": "name is the name of the resource being referenced.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.\n\nA single parameter used for all admission requests can be configured by setting the `name` field, leaving `selector` blank, and setting namespace if `paramKind` is namespace-scoped.", "namespace": "namespace is the namespace of the referenced resource. Allows limiting the search for params to a specific namespace. Applies to both `name` and `selector` fields.\n\nA per-namespace parameter may be used by specifying a namespace-scoped `paramKind` in the policy and leaving this field empty.\n\n- If `paramKind` is cluster-scoped, this field MUST be unset. Setting this field results in a configuration error.\n\n- If `paramKind` is namespace-scoped, the namespace of the object being evaluated for admission will be used when this field is left unset. Take care that if this is left empty the binding must not match any cluster-scoped resources, which will result in an error.", "selector": "selector can be used to match multiple param objects based on their labels. Supply selector: {} to match all resources of the ParamKind.\n\nIf multiple params are found, they are all evaluated with the policy expressions and the results are ANDed together.\n\nOne of `name` or `selector` must be set, but `name` and `selector` are mutually exclusive properties. If one is set, the other must be unset.", - "parameterNotFoundAction": "`parameterNotFoundAction` controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", + "parameterNotFoundAction": "parameterNotFoundAction controls the behavior of the binding when the resource exists, and name or selector is valid, but there are no parameters matched by the binding. If the value is set to `Allow`, then no matched parameters will be treated as successful validation by the binding. If set to `Deny`, then no matched parameters will be subject to the `failurePolicy` of the policy.\n\nAllowed values are `Allow` or `Deny`\n\nRequired", } func (ParamRef) SwaggerDoc() map[string]string { @@ -238,10 +238,10 @@ func (ParamRef) SwaggerDoc() map[string]string { var map_ServiceReference = map[string]string{ "": "ServiceReference holds a reference to Service.legacy.k8s.io", - "namespace": "`namespace` is the namespace of the service. Required", - "name": "`name` is the name of the service. Required", - "path": "`path` is an optional URL path which will be sent in any request to this service.", - "port": "If specified, the port on the service that hosting webhook. Default to 443 for backward compatibility. `port` should be a valid port number (1-65535, inclusive).", + "namespace": "namespace is the namespace of the service. Required", + "name": "name is the name of the service. Required", + "path": "path is an optional URL path which will be sent in any request to this service.", + "port": "port is the port on the service that hosts the webhook. Default to 443 for backward compatibility. port should be a valid port number (1-65535, inclusive).", } func (ServiceReference) SwaggerDoc() map[string]string { @@ -250,7 +250,7 @@ func (ServiceReference) SwaggerDoc() map[string]string { var map_TypeChecking = map[string]string{ "": "TypeChecking contains results of type checking the expressions in the ValidatingAdmissionPolicy", - "expressionWarnings": "The type checking warnings for each expression.", + "expressionWarnings": "expressionWarnings contains the type checking warnings for each expression.", } func (TypeChecking) SwaggerDoc() map[string]string { @@ -259,9 +259,9 @@ func (TypeChecking) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicy = map[string]string{ "": "ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the ValidatingAdmissionPolicy.", - "status": "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the ValidatingAdmissionPolicy.", + "status": "status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.", } func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string { @@ -270,8 +270,8 @@ func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBinding = map[string]string{ "": "ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources. ValidatingAdmissionPolicyBinding and parameter CRDs together define how cluster administrators configure policies for clusters.\n\nFor a given admission request, each binding will cause its policy to be evaluated N times, where N is 1 for policies/bindings that don't use params, otherwise N is the number of parameters selected by the binding.\n\nThe CEL expressions of a policy must have a computed CEL cost below the maximum CEL budget. Each evaluation of the policy is given an independent CEL cost budget. Adding/removing policies, bindings, or params can not affect whether a given (policy, binding, param) combination is within its own CEL budget.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "spec": "Specification of the desired behavior of the ValidatingAdmissionPolicyBinding.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "spec": "spec defines the desired behavior of the ValidatingAdmissionPolicyBinding.", } func (ValidatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { @@ -280,7 +280,7 @@ func (ValidatingAdmissionPolicyBinding) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBindingList = map[string]string{ "": "ValidatingAdmissionPolicyBindingList is a list of ValidatingAdmissionPolicyBinding.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of PolicyBinding.", } @@ -290,9 +290,9 @@ func (ValidatingAdmissionPolicyBindingList) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyBindingSpec = map[string]string{ "": "ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding.", - "policyName": "PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", + "policyName": "policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. If the referenced resource does not exist, this binding is considered invalid and will be ignored Required.", "paramRef": "paramRef specifies the parameter resource used to configure the admission control policy. It should point to a resource of the type specified in ParamKind of the bound ValidatingAdmissionPolicy. If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param.", - "matchResources": "MatchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", + "matchResources": "matchResources declares what resources match this binding and will be validated by it. Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. If this is unset, all resources matched by the policy are validated by this binding When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. Note that this is differs from ValidatingAdmissionPolicy matchConstraints, where resourceRules are required.", "validationActions": "validationActions declares how Validations of the referenced ValidatingAdmissionPolicy are enforced. If a validation evaluates to false it is always enforced according to these actions.\n\nFailures defined by the ValidatingAdmissionPolicy's FailurePolicy are enforced according to these actions only if the FailurePolicy is set to Fail, otherwise the failures are ignored. This includes compilation errors, runtime errors and misconfigurations of the policy.\n\nvalidationActions is declared as a set of action values. Order does not matter. validationActions may not contain duplicates of the same action.\n\nThe supported actions values are:\n\n\"Deny\" specifies that a validation failure results in a denied request.\n\n\"Warn\" specifies that a validation failure is reported to the request client in HTTP Warning headers, with a warning code of 299. Warnings can be sent both for allowed or denied admission responses.\n\n\"Audit\" specifies that a validation failure is included in the published audit event for the request. The audit event will contain a `validation.policy.admission.k8s.io/validation_failure` audit annotation with a value containing the details of the validation failures, formatted as a JSON list of objects, each with the following fields: - message: The validation failure message string - policy: The resource name of the ValidatingAdmissionPolicy - binding: The resource name of the ValidatingAdmissionPolicyBinding - expressionIndex: The index of the failed validations in the ValidatingAdmissionPolicy - validationActions: The enforcement actions enacted for the validation failure Example audit annotation: `\"validation.policy.admission.k8s.io/validation_failure\": \"[{\"message\": \"Invalid value\", {\"policy\": \"policy.example.com\", {\"binding\": \"policybinding.example.com\", {\"expressionIndex\": \"1\", {\"validationActions\": [\"Audit\"]}]\"`\n\nClients should expect to handle additional values by ignoring any values not recognized.\n\n\"Deny\" and \"Warn\" may not be used together since this combination needlessly duplicates the validation failure both in the API response body and the HTTP warning headers.\n\nRequired.", } @@ -302,7 +302,7 @@ func (ValidatingAdmissionPolicyBindingSpec) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyList = map[string]string{ "": "ValidatingAdmissionPolicyList is a list of ValidatingAdmissionPolicy.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingAdmissionPolicy.", } @@ -312,13 +312,13 @@ func (ValidatingAdmissionPolicyList) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicySpec = map[string]string{ "": "ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy.", - "paramKind": "ParamKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", - "matchConstraints": "MatchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", - "validations": "Validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", + "paramKind": "paramKind specifies the kind of resources used to parameterize this policy. If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null.", + "matchConstraints": "matchConstraints specifies what resources this policy is designed to validate. The AdmissionPolicy cares about a request if it matches _all_ Constraints. However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Required.", + "validations": "validations contain CEL expressions which is used to apply the validation. Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is required.", "failurePolicy": "failurePolicy defines how to handle failures for the admission policy. Failures can occur from CEL expression parse errors, type check errors, runtime errors and invalid or mis-configured policy definitions or bindings.\n\nA policy is invalid if spec.paramKind refers to a non-existent Kind. A binding is invalid if spec.paramRef.name refers to a non-existent resource.\n\nfailurePolicy does not define how validations that evaluate to false are handled.\n\nWhen failurePolicy is set to Fail, ValidatingAdmissionPolicyBinding validationActions define how failures are enforced.\n\nAllowed values are Ignore or Fail. Defaults to Fail.", "auditAnnotations": "auditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request. validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is required.", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", - "variables": "Variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be validated. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nIf a parameter object is provided, it can be accessed via the `params` handle in the same manner as validation expressions.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the policy is skipped", + "variables": "variables contain definitions of variables that can be used in composition of other expressions. Each variable is defined as a named CEL expression. The variables defined here will be available under `variables` in other expressions of the policy except MatchConditions because MatchConditions are evaluated before the rest of the policy.\n\nThe expression of a variable can refer to other variables defined earlier in the list but not those after. Thus, Variables must be sorted by the order of first appearance and acyclic.", } func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { @@ -327,9 +327,9 @@ func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string { var map_ValidatingAdmissionPolicyStatus = map[string]string{ "": "ValidatingAdmissionPolicyStatus represents the status of an admission validation policy.", - "observedGeneration": "The generation observed by the controller.", - "typeChecking": "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.", - "conditions": "The conditions represent the latest available observations of a policy's current state.", + "observedGeneration": "observedGeneration is the generation observed by the controller.", + "typeChecking": "typeChecking contains the results of type checking for each expression. Presence of this field indicates the completion of the type checking.", + "conditions": "conditions represent the latest available observations of a policy's current state.", } func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string { @@ -338,17 +338,17 @@ func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string { var map_ValidatingWebhook = map[string]string{ "": "ValidatingWebhook describes an admission webhook and the resources and operations it applies to.", - "name": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", - "clientConfig": "ClientConfig defines how to communicate with the hook. Required", - "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", - "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "name": "name is the name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.", + "clientConfig": "clientConfig defines how to communicate with the hook. Required", + "rules": "rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "failurePolicy": "failurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", "matchPolicy": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Exact\"", - "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "sideEffects": "SideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", - "timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", - "admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", - "matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", + "namespaceSelector": "namespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "objectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "sideEffects": "sideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", + "timeoutSeconds": "timeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", + "admissionReviewVersions": "admissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.", + "matchConditions": "matchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped", } func (ValidatingWebhook) SwaggerDoc() map[string]string { @@ -357,8 +357,8 @@ func (ValidatingWebhook) SwaggerDoc() map[string]string { var map_ValidatingWebhookConfiguration = map[string]string{ "": "ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration instead.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", - "webhooks": "Webhooks is a list of webhooks and the affected resources and operations.", + "metadata": "metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.", + "webhooks": "webhooks is a list of webhooks and the affected resources and operations.", } func (ValidatingWebhookConfiguration) SwaggerDoc() map[string]string { @@ -367,7 +367,7 @@ func (ValidatingWebhookConfiguration) SwaggerDoc() map[string]string { var map_ValidatingWebhookConfigurationList = map[string]string{ "": "ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of ValidatingWebhookConfiguration.", } @@ -377,9 +377,9 @@ func (ValidatingWebhookConfigurationList) SwaggerDoc() map[string]string { var map_Validation = map[string]string{ "": "Validation specifies the CEL expression which is used to apply the validation.", - "expression": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", - "message": "Message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", - "reason": "Reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", + "expression": "expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. - 'variables' - Map of composited variables, from its name to its lazily evaluated value.\n For example, a variable named 'foo' can be accessed as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.", + "message": "message represents the message displayed when validation fails. The message is required if the Expression contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\" If the Expression contains line breaks. Message is required. The message must not contain line breaks. If unset, the message is \"failed Expression: {Expression}\".", + "reason": "reason represents a machine-readable description of why this validation failed. If this is the first validation in the list to fail, this reason, as well as the corresponding HTTP response code, are used in the HTTP response to the client. The currently supported reasons are: \"Unauthorized\", \"Forbidden\", \"Invalid\", \"RequestEntityTooLarge\". If not set, StatusReasonInvalid is used in the response to the client.", "messageExpression": "messageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a validation, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the `expression` except for 'authorizer' and 'authorizer.requestResource'. Example: \"object.x must be less than max (\"+string(params.max)+\")\"", } @@ -389,8 +389,8 @@ func (Validation) SwaggerDoc() map[string]string { var map_Variable = map[string]string{ "": "Variable is the definition of a variable that is used for composition. A variable is defined as a named expression.", - "name": "Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", - "expression": "Expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", + "name": "name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. The variable can be accessed in other expressions through `variables` For example, if name is \"foo\", the variable will be available as `variables.foo`", + "expression": "expression is the expression that will be evaluated as the value of the variable. The CEL expression has access to the same identifiers as the CEL expressions in Validation.", } func (Variable) SwaggerDoc() map[string]string { @@ -399,9 +399,9 @@ func (Variable) SwaggerDoc() map[string]string { var map_WebhookClientConfig = map[string]string{ "": "WebhookClientConfig contains the information to make a TLS connection with the webhook", - "url": "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", - "service": "`service` is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", - "caBundle": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + "url": "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + "service": "service is a reference to the service for this webhook. Either `service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", + "caBundle": "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", } func (WebhookClientConfig) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.prerelease-lifecycle.go index 4fc0596b345b5..80064734ee625 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.prerelease-lifecycle.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.prerelease-lifecycle.go @@ -37,6 +37,12 @@ func (in *MutatingAdmissionPolicy) APILifecycleDeprecated() (major, minor int) { return 1, 37 } +// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. +// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. +func (in *MutatingAdmissionPolicy) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicy"} +} + // APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. func (in *MutatingAdmissionPolicy) APILifecycleRemoved() (major, minor int) { @@ -55,6 +61,12 @@ func (in *MutatingAdmissionPolicyBinding) APILifecycleDeprecated() (major, minor return 1, 37 } +// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. +// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. +func (in *MutatingAdmissionPolicyBinding) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicyBinding"} +} + // APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. func (in *MutatingAdmissionPolicyBinding) APILifecycleRemoved() (major, minor int) { @@ -73,6 +85,12 @@ func (in *MutatingAdmissionPolicyBindingList) APILifecycleDeprecated() (major, m return 1, 37 } +// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. +// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. +func (in *MutatingAdmissionPolicyBindingList) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicyBindingList"} +} + // APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. func (in *MutatingAdmissionPolicyBindingList) APILifecycleRemoved() (major, minor int) { @@ -91,6 +109,12 @@ func (in *MutatingAdmissionPolicyList) APILifecycleDeprecated() (major, minor in return 1, 37 } +// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. +// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. +func (in *MutatingAdmissionPolicyList) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingAdmissionPolicyList"} +} + // APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. func (in *MutatingAdmissionPolicyList) APILifecycleRemoved() (major, minor int) { diff --git a/staging/src/k8s.io/api/apidiscovery/v2/generated.proto b/staging/src/k8s.io/api/apidiscovery/v2/generated.proto index 62f2d7f2c1198..7f58048e17a03 100644 --- a/staging/src/k8s.io/api/apidiscovery/v2/generated.proto +++ b/staging/src/k8s.io/api/apidiscovery/v2/generated.proto @@ -32,7 +32,7 @@ option go_package = "k8s.io/api/apidiscovery/v2"; // It contains a list of APIVersionDiscovery that holds a list of APIResourceDiscovery types served for a version. // Versions are in descending order of preference, with the first version being the preferred entry. message APIGroupDiscovery { - // Standard object's metadata. + // metadata is standard object's metadata. // The only field completed will be name. For instance, resourceVersion will be empty. // name is the name of the API group whose discovery information is presented here. // name is allowed to be "" to represent the legacy, ungroupified resources. diff --git a/staging/src/k8s.io/api/apidiscovery/v2/generated.protomessage.pb.go b/staging/src/k8s.io/api/apidiscovery/v2/generated.protomessage.pb.go deleted file mode 100644 index 35fe0d2a8ad18..0000000000000 --- a/staging/src/k8s.io/api/apidiscovery/v2/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v2 - -func (*APIGroupDiscovery) ProtoMessage() {} - -func (*APIGroupDiscoveryList) ProtoMessage() {} - -func (*APIResourceDiscovery) ProtoMessage() {} - -func (*APISubresourceDiscovery) ProtoMessage() {} - -func (*APIVersionDiscovery) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/apidiscovery/v2/types.go b/staging/src/k8s.io/api/apidiscovery/v2/types.go index 449679b61d0c3..761bb6990be7b 100644 --- a/staging/src/k8s.io/api/apidiscovery/v2/types.go +++ b/staging/src/k8s.io/api/apidiscovery/v2/types.go @@ -45,7 +45,7 @@ type APIGroupDiscoveryList struct { // Versions are in descending order of preference, with the first version being the preferred entry. type APIGroupDiscovery struct { v1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is standard object's metadata. // The only field completed will be name. For instance, resourceVersion will be empty. // name is the name of the API group whose discovery information is presented here. // name is allowed to be "" to represent the legacy, ungroupified resources. diff --git a/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.proto b/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.proto index e9ae88072a5c3..f81449e6a9d63 100644 --- a/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.proto +++ b/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.proto @@ -32,7 +32,7 @@ option go_package = "k8s.io/api/apidiscovery/v2beta1"; // It contains a list of APIVersionDiscovery that holds a list of APIResourceDiscovery types served for a version. // Versions are in descending order of preference, with the first version being the preferred entry. message APIGroupDiscovery { - // Standard object's metadata. + // metadata is standard object's metadata. // The only field completed will be name. For instance, resourceVersion will be empty. // name is the name of the API group whose discovery information is presented here. // name is allowed to be "" to represent the legacy, ungroupified resources. diff --git a/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.protomessage.pb.go deleted file mode 100644 index 0998c461bb8d1..0000000000000 --- a/staging/src/k8s.io/api/apidiscovery/v2beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v2beta1 - -func (*APIGroupDiscovery) ProtoMessage() {} - -func (*APIGroupDiscoveryList) ProtoMessage() {} - -func (*APIResourceDiscovery) ProtoMessage() {} - -func (*APISubresourceDiscovery) ProtoMessage() {} - -func (*APIVersionDiscovery) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/apidiscovery/v2beta1/types.go b/staging/src/k8s.io/api/apidiscovery/v2beta1/types.go index 8342937730471..306264da4db8e 100644 --- a/staging/src/k8s.io/api/apidiscovery/v2beta1/types.go +++ b/staging/src/k8s.io/api/apidiscovery/v2beta1/types.go @@ -51,7 +51,7 @@ type APIGroupDiscoveryList struct { // Versions are in descending order of preference, with the first version being the preferred entry. type APIGroupDiscovery struct { v1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is standard object's metadata. // The only field completed will be name. For instance, resourceVersion will be empty. // name is the name of the API group whose discovery information is presented here. // name is allowed to be "" to represent the legacy, ungroupified resources. diff --git a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.proto b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.proto index 8a77860720f02..e8f8c339df93a 100644 --- a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.proto @@ -31,59 +31,69 @@ option go_package = "k8s.io/api/apiserverinternal/v1alpha1"; // An API server instance reports the version it can decode and the version it // encodes objects to when persisting objects in the backend. message ServerStorageVersion { - // The ID of the reporting API server. + // apiServerID is the ID of the reporting API server. + // +required optional string apiServerID = 1; - // The API server encodes the object to this version when persisting it in + // encodingVersion the API server encodes the object to when persisting it in // the backend (e.g., etcd). + // +required optional string encodingVersion = 2; + // decodableVersions are the encoding versions the API server can handle to decode. // The API server can decode objects encoded in these versions. // The encodingVersion must be included in the decodableVersions. // +listType=set + // +required repeated string decodableVersions = 3; - // The API server can serve these versions. + // servedVersions lists all versions the API server can serve. // DecodableVersions must include all ServedVersions. // +listType=set + // +optional repeated string servedVersions = 4; } // Storage version of a specific resource. message StorageVersion { + // metadata is the standard object metadata. // The name is .. + // +required optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec is an empty spec. It is here to comply with Kubernetes API style. + // spec is an empty spec. It is here to comply with Kubernetes API style. + // +optional optional StorageVersionSpec spec = 2; - // API server instances report the version they can decode and the version they + // status on the version the API server instance can decode from and // encode objects to when persisting objects in the backend. + // +optional optional StorageVersionStatus status = 3; } // Describes the state of the storageVersion at a certain point. message StorageVersionCondition { - // Type of the condition. + // type of the condition. // +required optional string type = 1; - // Status of the condition, one of True, False, Unknown. + // status of the condition, one of True, False, Unknown. // +required optional string status = 2; - // If set, this represents the .metadata.generation that the condition was set based upon. + // observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set. // +optional optional int64 observedGeneration = 3; - // Last time the condition transitioned from one status to another. + // lastTransitionTime is the last time the condition transitioned from one status to another. + // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4; - // The reason for the condition's last transition. + // reason for the condition's last transition. // +required optional string reason = 5; - // A human readable message indicating details about the transition. + // message is a human readable string indicating details about the transition. // +required optional string message = 6; } @@ -106,20 +116,21 @@ message StorageVersionSpec { // API server instances report the versions they can decode and the version they // encode objects to when persisting objects in the backend. message StorageVersionStatus { - // The reported versions per API server instance. + // storageVersions lists the reported versions per API server instance. // +optional // +listType=map // +listMapKey=apiServerID repeated ServerStorageVersion storageVersions = 1; - // If all API server instances agree on the same encoding storage version, - // then this field is set to that version. Otherwise this field is left empty. + // commonEncodingVersion is set to an encoding storage version if all API server + // instances share that same version. If they don't share one storage version, this + // field is left empty. // API servers should finish updating its storageVersionStatus entry before // serving write operations, so that this field will be in sync with the reality. // +optional optional string commonEncodingVersion = 2; - // The latest available observations of the storageVersion's state. + // conditions lists the latest available observations of the storageVersion's state. // +optional // +listType=map // +listMapKey=type diff --git a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index b0839952b2aa1..0000000000000 --- a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*ServerStorageVersion) ProtoMessage() {} - -func (*StorageVersion) ProtoMessage() {} - -func (*StorageVersionCondition) ProtoMessage() {} - -func (*StorageVersionList) ProtoMessage() {} - -func (*StorageVersionSpec) ProtoMessage() {} - -func (*StorageVersionStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types.go b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types.go index 31a419abf130d..69c21fd3c45fc 100644 --- a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types.go +++ b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types.go @@ -27,14 +27,18 @@ import ( // Storage version of a specific resource. type StorageVersion struct { metav1.TypeMeta `json:",inline"` + // metadata is the standard object metadata. // The name is .. + // +required metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec is an empty spec. It is here to comply with Kubernetes API style. + // spec is an empty spec. It is here to comply with Kubernetes API style. + // +optional Spec StorageVersionSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // API server instances report the version they can decode and the version they + // status on the version the API server instance can decode from and // encode objects to when persisting objects in the backend. + // +optional Status StorageVersionStatus `json:"status" protobuf:"bytes,3,opt,name=status"` } @@ -44,19 +48,20 @@ type StorageVersionSpec struct{} // API server instances report the versions they can decode and the version they // encode objects to when persisting objects in the backend. type StorageVersionStatus struct { - // The reported versions per API server instance. + // storageVersions lists the reported versions per API server instance. // +optional // +listType=map // +listMapKey=apiServerID StorageVersions []ServerStorageVersion `json:"storageVersions,omitempty" protobuf:"bytes,1,opt,name=storageVersions"` - // If all API server instances agree on the same encoding storage version, - // then this field is set to that version. Otherwise this field is left empty. + // commonEncodingVersion is set to an encoding storage version if all API server + // instances share that same version. If they don't share one storage version, this + // field is left empty. // API servers should finish updating its storageVersionStatus entry before // serving write operations, so that this field will be in sync with the reality. // +optional CommonEncodingVersion *string `json:"commonEncodingVersion,omitempty" protobuf:"bytes,2,opt,name=commonEncodingVersion"` - // The latest available observations of the storageVersion's state. + // conditions lists the latest available observations of the storageVersion's state. // +optional // +listType=map // +listMapKey=type @@ -66,21 +71,26 @@ type StorageVersionStatus struct { // An API server instance reports the version it can decode and the version it // encodes objects to when persisting objects in the backend. type ServerStorageVersion struct { - // The ID of the reporting API server. + // apiServerID is the ID of the reporting API server. + // +required APIServerID string `json:"apiServerID,omitempty" protobuf:"bytes,1,opt,name=apiServerID"` - // The API server encodes the object to this version when persisting it in + // encodingVersion the API server encodes the object to when persisting it in // the backend (e.g., etcd). + // +required EncodingVersion string `json:"encodingVersion,omitempty" protobuf:"bytes,2,opt,name=encodingVersion"` + // decodableVersions are the encoding versions the API server can handle to decode. // The API server can decode objects encoded in these versions. // The encodingVersion must be included in the decodableVersions. // +listType=set + // +required DecodableVersions []string `json:"decodableVersions,omitempty" protobuf:"bytes,3,opt,name=decodableVersions"` - // The API server can serve these versions. + // servedVersions lists all versions the API server can serve. // DecodableVersions must include all ServedVersions. // +listType=set + // +optional ServedVersions []string `json:"servedVersions,omitempty" protobuf:"bytes,4,opt,name=servedVersions"` } @@ -101,21 +111,22 @@ const ( // Describes the state of the storageVersion at a certain point. type StorageVersionCondition struct { - // Type of the condition. + // type of the condition. // +required Type StorageVersionConditionType `json:"type" protobuf:"bytes,1,opt,name=type"` - // Status of the condition, one of True, False, Unknown. + // status of the condition, one of True, False, Unknown. // +required Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"` - // If set, this represents the .metadata.generation that the condition was set based upon. + // observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` - // Last time the condition transitioned from one status to another. + // lastTransitionTime is the last time the condition transitioned from one status to another. + // +optional LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` - // The reason for the condition's last transition. + // reason for the condition's last transition. // +required Reason string `json:"reason" protobuf:"bytes,5,opt,name=reason"` - // A human readable message indicating details about the transition. + // message is a human readable string indicating details about the transition. // +required Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` } diff --git a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types_swagger_doc_generated.go index 6fd1c3ebe8a99..b74035e5003bc 100644 --- a/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/apiserverinternal/v1alpha1/types_swagger_doc_generated.go @@ -29,10 +29,10 @@ package v1alpha1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_ServerStorageVersion = map[string]string{ "": "An API server instance reports the version it can decode and the version it encodes objects to when persisting objects in the backend.", - "apiServerID": "The ID of the reporting API server.", - "encodingVersion": "The API server encodes the object to this version when persisting it in the backend (e.g., etcd).", - "decodableVersions": "The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", - "servedVersions": "The API server can serve these versions. DecodableVersions must include all ServedVersions.", + "apiServerID": "apiServerID is the ID of the reporting API server.", + "encodingVersion": "encodingVersion the API server encodes the object to when persisting it in the backend (e.g., etcd).", + "decodableVersions": "decodableVersions are the encoding versions the API server can handle to decode. The API server can decode objects encoded in these versions. The encodingVersion must be included in the decodableVersions.", + "servedVersions": "servedVersions lists all versions the API server can serve. DecodableVersions must include all ServedVersions.", } func (ServerStorageVersion) SwaggerDoc() map[string]string { @@ -41,9 +41,9 @@ func (ServerStorageVersion) SwaggerDoc() map[string]string { var map_StorageVersion = map[string]string{ "": "Storage version of a specific resource.", - "metadata": "The name is ..", - "spec": "Spec is an empty spec. It is here to comply with Kubernetes API style.", - "status": "API server instances report the version they can decode and the version they encode objects to when persisting objects in the backend.", + "metadata": "metadata is the standard object metadata. The name is ..", + "spec": "spec is an empty spec. It is here to comply with Kubernetes API style.", + "status": "status on the version the API server instance can decode from and encode objects to when persisting objects in the backend.", } func (StorageVersion) SwaggerDoc() map[string]string { @@ -52,12 +52,12 @@ func (StorageVersion) SwaggerDoc() map[string]string { var map_StorageVersionCondition = map[string]string{ "": "Describes the state of the storageVersion at a certain point.", - "type": "Type of the condition.", - "status": "Status of the condition, one of True, False, Unknown.", - "observedGeneration": "If set, this represents the .metadata.generation that the condition was set based upon.", - "lastTransitionTime": "Last time the condition transitioned from one status to another.", - "reason": "The reason for the condition's last transition.", - "message": "A human readable message indicating details about the transition.", + "type": "type of the condition.", + "status": "status of the condition, one of True, False, Unknown.", + "observedGeneration": "observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set.", + "lastTransitionTime": "lastTransitionTime is the last time the condition transitioned from one status to another.", + "reason": "reason for the condition's last transition.", + "message": "message is a human readable string indicating details about the transition.", } func (StorageVersionCondition) SwaggerDoc() map[string]string { @@ -84,9 +84,9 @@ func (StorageVersionSpec) SwaggerDoc() map[string]string { var map_StorageVersionStatus = map[string]string{ "": "API server instances report the versions they can decode and the version they encode objects to when persisting objects in the backend.", - "storageVersions": "The reported versions per API server instance.", - "commonEncodingVersion": "If all API server instances agree on the same encoding storage version, then this field is set to that version. Otherwise this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", - "conditions": "The latest available observations of the storageVersion's state.", + "storageVersions": "storageVersions lists the reported versions per API server instance.", + "commonEncodingVersion": "commonEncodingVersion is set to an encoding storage version if all API server instances share that same version. If they don't share one storage version, this field is left empty. API servers should finish updating its storageVersionStatus entry before serving write operations, so that this field will be in sync with the reality.", + "conditions": "conditions lists the latest available observations of the storageVersion's state.", } func (StorageVersionStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/apps/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/apps/v1/generated.protomessage.pb.go deleted file mode 100644 index f9faa4e13a0d9..0000000000000 --- a/staging/src/k8s.io/api/apps/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*ControllerRevision) ProtoMessage() {} - -func (*ControllerRevisionList) ProtoMessage() {} - -func (*DaemonSet) ProtoMessage() {} - -func (*DaemonSetCondition) ProtoMessage() {} - -func (*DaemonSetList) ProtoMessage() {} - -func (*DaemonSetSpec) ProtoMessage() {} - -func (*DaemonSetStatus) ProtoMessage() {} - -func (*DaemonSetUpdateStrategy) ProtoMessage() {} - -func (*Deployment) ProtoMessage() {} - -func (*DeploymentCondition) ProtoMessage() {} - -func (*DeploymentList) ProtoMessage() {} - -func (*DeploymentSpec) ProtoMessage() {} - -func (*DeploymentStatus) ProtoMessage() {} - -func (*DeploymentStrategy) ProtoMessage() {} - -func (*ReplicaSet) ProtoMessage() {} - -func (*ReplicaSetCondition) ProtoMessage() {} - -func (*ReplicaSetList) ProtoMessage() {} - -func (*ReplicaSetSpec) ProtoMessage() {} - -func (*ReplicaSetStatus) ProtoMessage() {} - -func (*RollingUpdateDaemonSet) ProtoMessage() {} - -func (*RollingUpdateDeployment) ProtoMessage() {} - -func (*RollingUpdateStatefulSetStrategy) ProtoMessage() {} - -func (*StatefulSet) ProtoMessage() {} - -func (*StatefulSetCondition) ProtoMessage() {} - -func (*StatefulSetList) ProtoMessage() {} - -func (*StatefulSetOrdinals) ProtoMessage() {} - -func (*StatefulSetPersistentVolumeClaimRetentionPolicy) ProtoMessage() {} - -func (*StatefulSetSpec) ProtoMessage() {} - -func (*StatefulSetStatus) ProtoMessage() {} - -func (*StatefulSetUpdateStrategy) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/apps/v1beta1/generated.proto b/staging/src/k8s.io/api/apps/v1beta1/generated.proto index b47d61e209259..c6f0628e0c0cb 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/apps/v1beta1/generated.proto @@ -318,9 +318,9 @@ message Scale { message ScaleSpec { // replicas is the number of observed instances of the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 optional int32 replicas = 1; } diff --git a/staging/src/k8s.io/api/apps/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/apps/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 3127ea3caeca7..0000000000000 --- a/staging/src/k8s.io/api/apps/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*ControllerRevision) ProtoMessage() {} - -func (*ControllerRevisionList) ProtoMessage() {} - -func (*Deployment) ProtoMessage() {} - -func (*DeploymentCondition) ProtoMessage() {} - -func (*DeploymentList) ProtoMessage() {} - -func (*DeploymentRollback) ProtoMessage() {} - -func (*DeploymentSpec) ProtoMessage() {} - -func (*DeploymentStatus) ProtoMessage() {} - -func (*DeploymentStrategy) ProtoMessage() {} - -func (*RollbackConfig) ProtoMessage() {} - -func (*RollingUpdateDeployment) ProtoMessage() {} - -func (*RollingUpdateStatefulSetStrategy) ProtoMessage() {} - -func (*Scale) ProtoMessage() {} - -func (*ScaleSpec) ProtoMessage() {} - -func (*ScaleStatus) ProtoMessage() {} - -func (*StatefulSet) ProtoMessage() {} - -func (*StatefulSetCondition) ProtoMessage() {} - -func (*StatefulSetList) ProtoMessage() {} - -func (*StatefulSetOrdinals) ProtoMessage() {} - -func (*StatefulSetPersistentVolumeClaimRetentionPolicy) ProtoMessage() {} - -func (*StatefulSetSpec) ProtoMessage() {} - -func (*StatefulSetStatus) ProtoMessage() {} - -func (*StatefulSetUpdateStrategy) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/apps/v1beta1/types.go b/staging/src/k8s.io/api/apps/v1beta1/types.go index b1e6b33683e3a..4e67338e36ab1 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/types.go +++ b/staging/src/k8s.io/api/apps/v1beta1/types.go @@ -33,9 +33,9 @@ const ( type ScaleSpec struct { // replicas is the number of observed instances of the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 Replicas int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` } @@ -63,7 +63,7 @@ type ScaleStatus struct { // +k8s:prerelease-lifecycle-gen:deprecated=1.8 // +k8s:prerelease-lifecycle-gen:removed=1.16 // +k8s:prerelease-lifecycle-gen:replacement=autoscaling,v1,Scale -// +k8s:isSubresource=/scale +// +k8s:isSubresource="/scale" // Scale represents a scaling request for a resource. type Scale struct { diff --git a/staging/src/k8s.io/api/apps/v1beta2/generated.proto b/staging/src/k8s.io/api/apps/v1beta2/generated.proto index 1cdd0a43f7adb..d680c9663e056 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/apps/v1beta2/generated.proto @@ -616,9 +616,9 @@ message Scale { message ScaleSpec { // desired number of instances for the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 optional int32 replicas = 1; } diff --git a/staging/src/k8s.io/api/apps/v1beta2/generated.protomessage.pb.go b/staging/src/k8s.io/api/apps/v1beta2/generated.protomessage.pb.go deleted file mode 100644 index 18582bf290de7..0000000000000 --- a/staging/src/k8s.io/api/apps/v1beta2/generated.protomessage.pb.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta2 - -func (*ControllerRevision) ProtoMessage() {} - -func (*ControllerRevisionList) ProtoMessage() {} - -func (*DaemonSet) ProtoMessage() {} - -func (*DaemonSetCondition) ProtoMessage() {} - -func (*DaemonSetList) ProtoMessage() {} - -func (*DaemonSetSpec) ProtoMessage() {} - -func (*DaemonSetStatus) ProtoMessage() {} - -func (*DaemonSetUpdateStrategy) ProtoMessage() {} - -func (*Deployment) ProtoMessage() {} - -func (*DeploymentCondition) ProtoMessage() {} - -func (*DeploymentList) ProtoMessage() {} - -func (*DeploymentSpec) ProtoMessage() {} - -func (*DeploymentStatus) ProtoMessage() {} - -func (*DeploymentStrategy) ProtoMessage() {} - -func (*ReplicaSet) ProtoMessage() {} - -func (*ReplicaSetCondition) ProtoMessage() {} - -func (*ReplicaSetList) ProtoMessage() {} - -func (*ReplicaSetSpec) ProtoMessage() {} - -func (*ReplicaSetStatus) ProtoMessage() {} - -func (*RollingUpdateDaemonSet) ProtoMessage() {} - -func (*RollingUpdateDeployment) ProtoMessage() {} - -func (*RollingUpdateStatefulSetStrategy) ProtoMessage() {} - -func (*Scale) ProtoMessage() {} - -func (*ScaleSpec) ProtoMessage() {} - -func (*ScaleStatus) ProtoMessage() {} - -func (*StatefulSet) ProtoMessage() {} - -func (*StatefulSetCondition) ProtoMessage() {} - -func (*StatefulSetList) ProtoMessage() {} - -func (*StatefulSetOrdinals) ProtoMessage() {} - -func (*StatefulSetPersistentVolumeClaimRetentionPolicy) ProtoMessage() {} - -func (*StatefulSetSpec) ProtoMessage() {} - -func (*StatefulSetStatus) ProtoMessage() {} - -func (*StatefulSetUpdateStrategy) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/apps/v1beta2/types.go b/staging/src/k8s.io/api/apps/v1beta2/types.go index 18c74a4aa6312..d4d4a7e0dc4cc 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/types.go +++ b/staging/src/k8s.io/api/apps/v1beta2/types.go @@ -35,9 +35,9 @@ const ( type ScaleSpec struct { // desired number of instances for the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 Replicas int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` } @@ -66,7 +66,7 @@ type ScaleStatus struct { // +k8s:prerelease-lifecycle-gen:deprecated=1.9 // +k8s:prerelease-lifecycle-gen:removed=1.16 // +k8s:prerelease-lifecycle-gen:replacement=autoscaling,v1,Scale -// +k8s:isSubresource=/scale +// +k8s:isSubresource="/scale" // Scale represents a scaling request for a resource. type Scale struct { diff --git a/staging/src/k8s.io/api/authentication/v1/generated.proto b/staging/src/k8s.io/api/authentication/v1/generated.proto index ae9763576cac1..82ff7c3c51f09 100644 --- a/staging/src/k8s.io/api/authentication/v1/generated.proto +++ b/staging/src/k8s.io/api/authentication/v1/generated.proto @@ -30,19 +30,19 @@ option go_package = "k8s.io/api/authentication/v1"; // BoundObjectReference is a reference to an object that a token is bound to. message BoundObjectReference { - // Kind of the referent. Valid kinds are 'Pod' and 'Secret'. + // kind of the referent. Valid kinds are 'Pod' and 'Secret'. // +optional optional string kind = 1; - // API version of the referent. + // apiVersion is API version of the referent. // +optional optional string apiVersion = 2; - // Name of the referent. + // name of the referent. // +optional optional string name = 3; - // UID of the referent. + // uid of the referent. // +optional optional string uID = 4; } @@ -60,55 +60,58 @@ message ExtraValue { // When using impersonation, users will receive the user info of the user being impersonated. If impersonation or // request header authentication is used, any extra keys will have their case ignored and returned as lowercase. message SelfSubjectReview { - // Standard object's metadata. + // metadata is standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Status is filled in by the server with the user attributes. + // status is filled in by the server with the user attributes. + // +optional optional SelfSubjectReviewStatus status = 2; } // SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user. message SelfSubjectReviewStatus { - // User attributes of the user making this request. + // userInfo is a set of attributes belonging to the user making this request. // +optional optional UserInfo userInfo = 1; } // TokenRequest requests a token for a given service account. message TokenRequest { - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated + // +optional optional TokenRequestSpec spec = 2; - // Status is filled in by the server and indicates whether the token can be authenticated. + // status is filled in by the server and indicates whether the token can be authenticated. // +optional optional TokenRequestStatus status = 3; } // TokenRequestSpec contains client provided parameters of a token request. message TokenRequestSpec { - // Audiences are the intendend audiences of the token. A recipient of a + // audiences are the intendend audiences of the token. A recipient of a // token must identify themself with an identifier in the list of // audiences of the token, and otherwise should reject the token. A // token issued for multiple audiences may be used to authenticate // against any of the audiences listed but implies a high degree of // trust between the target audiences. + // +optional // +listType=atomic repeated string audiences = 1; - // ExpirationSeconds is the requested duration of validity of the request. The + // expirationSeconds is the requested duration of validity of the request. The // token issuer may return a token with a different validity duration so a // client needs to check the 'expiration' field in a response. // +optional optional int64 expirationSeconds = 4; - // BoundObjectRef is a reference to an object that the token will be bound to. + // boundObjectRef is a reference to an object that the token will be bound to. // The token will only be valid for as long as the bound object exists. // NOTE: The API server's TokenReview endpoint will validate the // BoundObjectRef, but other audiences may not. Keep ExpirationSeconds @@ -119,10 +122,12 @@ message TokenRequestSpec { // TokenRequestStatus is the result of a token request. message TokenRequestStatus { - // Token is the opaque bearer token. + // token is the opaque bearer token. + // +optional optional string token = 1; - // ExpirationTimestamp is the time of expiration of the returned token. + // expirationTimestamp is the time of expiration of the returned token. + // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time expirationTimestamp = 2; } @@ -130,26 +135,27 @@ message TokenRequestStatus { // Note: TokenReview requests may be cached by the webhook token authenticator // plugin in the kube-apiserver. message TokenReview { - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated + // +required optional TokenReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request can be authenticated. + // status is filled in by the server and indicates whether the request can be authenticated. // +optional optional TokenReviewStatus status = 3; } // TokenReviewSpec is a description of the token authentication request. message TokenReviewSpec { - // Token is the opaque bearer token. - // +optional + // token is the opaque bearer token. + // +required optional string token = 1; - // Audiences is a list of the identifiers that the resource server presented + // audiences is a list of the identifiers that the resource server presented // with the token identifies as. Audience-aware token authenticators will // verify that the token was intended for at least one of the audiences in // this list. If no audiences are provided, the audience will default to the @@ -161,15 +167,15 @@ message TokenReviewSpec { // TokenReviewStatus is the result of the token authentication request. message TokenReviewStatus { - // Authenticated indicates that the token was associated with a known user. + // authenticated indicates that the token was associated with a known user. // +optional optional bool authenticated = 1; - // User is the UserInfo associated with the provided token. + // user is the UserInfo associated with the provided token. // +optional optional UserInfo user = 2; - // Audiences are audience identifiers chosen by the authenticator that are + // audiences are audience identifiers chosen by the authenticator that are // compatible with both the TokenReview and token. An identifier is any // identifier in the intersection of the TokenReviewSpec audiences and the // token's audiences. A client of the TokenReview API that sets the @@ -182,7 +188,7 @@ message TokenReviewStatus { // +listType=atomic repeated string audiences = 4; - // Error indicates that the token couldn't be checked + // error indicates that the token couldn't be checked // +optional optional string error = 3; } @@ -190,22 +196,22 @@ message TokenReviewStatus { // UserInfo holds the information about the user needed to implement the // user.Info interface. message UserInfo { - // The name that uniquely identifies this user among all active users. + // username is the name that uniquely identifies this user among all active users. // +optional optional string username = 1; - // A unique value that identifies this user across time. If this user is + // uid is a unique value that identifies this user across time. If this user is // deleted and another user by the same name is added, they will have // different UIDs. // +optional optional string uid = 2; - // The names of groups this user is a part of. + // groups is the names of groups this user is a part of. // +optional // +listType=atomic repeated string groups = 3; - // Any additional information provided by the authenticator. + // extra is any additional information provided by the authenticator. // +optional map extra = 4; } diff --git a/staging/src/k8s.io/api/authentication/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/authentication/v1/generated.protomessage.pb.go deleted file mode 100644 index 7003a8084351a..0000000000000 --- a/staging/src/k8s.io/api/authentication/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*BoundObjectReference) ProtoMessage() {} - -func (*ExtraValue) ProtoMessage() {} - -func (*SelfSubjectReview) ProtoMessage() {} - -func (*SelfSubjectReviewStatus) ProtoMessage() {} - -func (*TokenRequest) ProtoMessage() {} - -func (*TokenRequestSpec) ProtoMessage() {} - -func (*TokenRequestStatus) ProtoMessage() {} - -func (*TokenReview) ProtoMessage() {} - -func (*TokenReviewSpec) ProtoMessage() {} - -func (*TokenReviewStatus) ProtoMessage() {} - -func (*UserInfo) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/authentication/v1/types.go b/staging/src/k8s.io/api/authentication/v1/types.go index 2dc0707c4fae8..c28bded50a781 100644 --- a/staging/src/k8s.io/api/authentication/v1/types.go +++ b/staging/src/k8s.io/api/authentication/v1/types.go @@ -52,25 +52,26 @@ const ( // plugin in the kube-apiserver. type TokenReview struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated + // +required Spec TokenReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request can be authenticated. + // status is filled in by the server and indicates whether the request can be authenticated. // +optional Status TokenReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // TokenReviewSpec is a description of the token authentication request. type TokenReviewSpec struct { - // Token is the opaque bearer token. - // +optional + // token is the opaque bearer token. + // +required Token string `json:"token,omitempty" protobuf:"bytes,1,opt,name=token"` - // Audiences is a list of the identifiers that the resource server presented + // audiences is a list of the identifiers that the resource server presented // with the token identifies as. Audience-aware token authenticators will // verify that the token was intended for at least one of the audiences in // this list. If no audiences are provided, the audience will default to the @@ -82,13 +83,13 @@ type TokenReviewSpec struct { // TokenReviewStatus is the result of the token authentication request. type TokenReviewStatus struct { - // Authenticated indicates that the token was associated with a known user. + // authenticated indicates that the token was associated with a known user. // +optional Authenticated bool `json:"authenticated,omitempty" protobuf:"varint,1,opt,name=authenticated"` - // User is the UserInfo associated with the provided token. + // user is the UserInfo associated with the provided token. // +optional User UserInfo `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"` - // Audiences are audience identifiers chosen by the authenticator that are + // audiences are audience identifiers chosen by the authenticator that are // compatible with both the TokenReview and token. An identifier is any // identifier in the intersection of the TokenReviewSpec audiences and the // token's audiences. A client of the TokenReview API that sets the @@ -100,7 +101,7 @@ type TokenReviewStatus struct { // +optional // +listType=atomic Audiences []string `json:"audiences,omitempty" protobuf:"bytes,4,rep,name=audiences"` - // Error indicates that the token couldn't be checked + // error indicates that the token couldn't be checked // +optional Error string `json:"error,omitempty" protobuf:"bytes,3,opt,name=error"` } @@ -108,19 +109,19 @@ type TokenReviewStatus struct { // UserInfo holds the information about the user needed to implement the // user.Info interface. type UserInfo struct { - // The name that uniquely identifies this user among all active users. + // username is the name that uniquely identifies this user among all active users. // +optional Username string `json:"username,omitempty" protobuf:"bytes,1,opt,name=username"` - // A unique value that identifies this user across time. If this user is + // uid is a unique value that identifies this user across time. If this user is // deleted and another user by the same name is added, they will have // different UIDs. // +optional UID string `json:"uid,omitempty" protobuf:"bytes,2,opt,name=uid"` - // The names of groups this user is a part of. + // groups is the names of groups this user is a part of. // +optional // +listType=atomic Groups []string `json:"groups,omitempty" protobuf:"bytes,3,rep,name=groups"` - // Any additional information provided by the authenticator. + // extra is any additional information provided by the authenticator. // +optional Extra map[string]ExtraValue `json:"extra,omitempty" protobuf:"bytes,4,rep,name=extra"` } @@ -140,37 +141,39 @@ func (t ExtraValue) String() string { // TokenRequest requests a token for a given service account. type TokenRequest struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated + // +optional Spec TokenRequestSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the token can be authenticated. + // status is filled in by the server and indicates whether the token can be authenticated. // +optional Status TokenRequestStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // TokenRequestSpec contains client provided parameters of a token request. type TokenRequestSpec struct { - // Audiences are the intendend audiences of the token. A recipient of a + // audiences are the intendend audiences of the token. A recipient of a // token must identify themself with an identifier in the list of // audiences of the token, and otherwise should reject the token. A // token issued for multiple audiences may be used to authenticate // against any of the audiences listed but implies a high degree of // trust between the target audiences. + // +optional // +listType=atomic Audiences []string `json:"audiences" protobuf:"bytes,1,rep,name=audiences"` - // ExpirationSeconds is the requested duration of validity of the request. The + // expirationSeconds is the requested duration of validity of the request. The // token issuer may return a token with a different validity duration so a // client needs to check the 'expiration' field in a response. // +optional ExpirationSeconds *int64 `json:"expirationSeconds" protobuf:"varint,4,opt,name=expirationSeconds"` - // BoundObjectRef is a reference to an object that the token will be bound to. + // boundObjectRef is a reference to an object that the token will be bound to. // The token will only be valid for as long as the bound object exists. // NOTE: The API server's TokenReview endpoint will validate the // BoundObjectRef, but other audiences may not. Keep ExpirationSeconds @@ -181,25 +184,27 @@ type TokenRequestSpec struct { // TokenRequestStatus is the result of a token request. type TokenRequestStatus struct { - // Token is the opaque bearer token. + // token is the opaque bearer token. + // +optional Token string `json:"token" protobuf:"bytes,1,opt,name=token"` - // ExpirationTimestamp is the time of expiration of the returned token. + // expirationTimestamp is the time of expiration of the returned token. + // +optional ExpirationTimestamp metav1.Time `json:"expirationTimestamp" protobuf:"bytes,2,opt,name=expirationTimestamp"` } // BoundObjectReference is a reference to an object that a token is bound to. type BoundObjectReference struct { - // Kind of the referent. Valid kinds are 'Pod' and 'Secret'. + // kind of the referent. Valid kinds are 'Pod' and 'Secret'. // +optional Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` - // API version of the referent. + // apiVersion is API version of the referent. // +optional APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` - // Name of the referent. + // name of the referent. // +optional Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"` - // UID of the referent. + // uid of the referent. // +optional UID types.UID `json:"uid,omitempty" protobuf:"bytes,4,opt,name=uID,casttype=k8s.io/apimachinery/pkg/types.UID"` } @@ -215,17 +220,18 @@ type BoundObjectReference struct { // request header authentication is used, any extra keys will have their case ignored and returned as lowercase. type SelfSubjectReview struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Status is filled in by the server with the user attributes. + // status is filled in by the server with the user attributes. + // +optional Status SelfSubjectReviewStatus `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` } // SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user. type SelfSubjectReviewStatus struct { - // User attributes of the user making this request. + // userInfo is a set of attributes belonging to the user making this request. // +optional UserInfo UserInfo `json:"userInfo,omitempty" protobuf:"bytes,1,opt,name=userInfo"` } diff --git a/staging/src/k8s.io/api/authentication/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/authentication/v1/types_swagger_doc_generated.go index ebfd4852c058f..ea3acbb681cf9 100644 --- a/staging/src/k8s.io/api/authentication/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/authentication/v1/types_swagger_doc_generated.go @@ -29,10 +29,10 @@ package v1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_BoundObjectReference = map[string]string{ "": "BoundObjectReference is a reference to an object that a token is bound to.", - "kind": "Kind of the referent. Valid kinds are 'Pod' and 'Secret'.", - "apiVersion": "API version of the referent.", - "name": "Name of the referent.", - "uid": "UID of the referent.", + "kind": "kind of the referent. Valid kinds are 'Pod' and 'Secret'.", + "apiVersion": "apiVersion is API version of the referent.", + "name": "name of the referent.", + "uid": "uid of the referent.", } func (BoundObjectReference) SwaggerDoc() map[string]string { @@ -41,8 +41,8 @@ func (BoundObjectReference) SwaggerDoc() map[string]string { var map_SelfSubjectReview = map[string]string{ "": "SelfSubjectReview contains the user information that the kube-apiserver has about the user making this request. When using impersonation, users will receive the user info of the user being impersonated. If impersonation or request header authentication is used, any extra keys will have their case ignored and returned as lowercase.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "status": "Status is filled in by the server with the user attributes.", + "metadata": "metadata is standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "status": "status is filled in by the server with the user attributes.", } func (SelfSubjectReview) SwaggerDoc() map[string]string { @@ -51,7 +51,7 @@ func (SelfSubjectReview) SwaggerDoc() map[string]string { var map_SelfSubjectReviewStatus = map[string]string{ "": "SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user.", - "userInfo": "User attributes of the user making this request.", + "userInfo": "userInfo is a set of attributes belonging to the user making this request.", } func (SelfSubjectReviewStatus) SwaggerDoc() map[string]string { @@ -60,9 +60,9 @@ func (SelfSubjectReviewStatus) SwaggerDoc() map[string]string { var map_TokenRequest = map[string]string{ "": "TokenRequest requests a token for a given service account.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated", - "status": "Status is filled in by the server and indicates whether the token can be authenticated.", + "metadata": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated", + "status": "status is filled in by the server and indicates whether the token can be authenticated.", } func (TokenRequest) SwaggerDoc() map[string]string { @@ -71,9 +71,9 @@ func (TokenRequest) SwaggerDoc() map[string]string { var map_TokenRequestSpec = map[string]string{ "": "TokenRequestSpec contains client provided parameters of a token request.", - "audiences": "Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", - "expirationSeconds": "ExpirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", - "boundObjectRef": "BoundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation.", + "audiences": "audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.", + "expirationSeconds": "expirationSeconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response.", + "boundObjectRef": "boundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation.", } func (TokenRequestSpec) SwaggerDoc() map[string]string { @@ -82,8 +82,8 @@ func (TokenRequestSpec) SwaggerDoc() map[string]string { var map_TokenRequestStatus = map[string]string{ "": "TokenRequestStatus is the result of a token request.", - "token": "Token is the opaque bearer token.", - "expirationTimestamp": "ExpirationTimestamp is the time of expiration of the returned token.", + "token": "token is the opaque bearer token.", + "expirationTimestamp": "expirationTimestamp is the time of expiration of the returned token.", } func (TokenRequestStatus) SwaggerDoc() map[string]string { @@ -92,9 +92,9 @@ func (TokenRequestStatus) SwaggerDoc() map[string]string { var map_TokenReview = map[string]string{ "": "TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated", - "status": "Status is filled in by the server and indicates whether the request can be authenticated.", + "metadata": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated", + "status": "status is filled in by the server and indicates whether the request can be authenticated.", } func (TokenReview) SwaggerDoc() map[string]string { @@ -103,8 +103,8 @@ func (TokenReview) SwaggerDoc() map[string]string { var map_TokenReviewSpec = map[string]string{ "": "TokenReviewSpec is a description of the token authentication request.", - "token": "Token is the opaque bearer token.", - "audiences": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", + "token": "token is the opaque bearer token.", + "audiences": "audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", } func (TokenReviewSpec) SwaggerDoc() map[string]string { @@ -113,10 +113,10 @@ func (TokenReviewSpec) SwaggerDoc() map[string]string { var map_TokenReviewStatus = map[string]string{ "": "TokenReviewStatus is the result of the token authentication request.", - "authenticated": "Authenticated indicates that the token was associated with a known user.", - "user": "User is the UserInfo associated with the provided token.", - "audiences": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", - "error": "Error indicates that the token couldn't be checked", + "authenticated": "authenticated indicates that the token was associated with a known user.", + "user": "user is the UserInfo associated with the provided token.", + "audiences": "audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", + "error": "error indicates that the token couldn't be checked", } func (TokenReviewStatus) SwaggerDoc() map[string]string { @@ -125,10 +125,10 @@ func (TokenReviewStatus) SwaggerDoc() map[string]string { var map_UserInfo = map[string]string{ "": "UserInfo holds the information about the user needed to implement the user.Info interface.", - "username": "The name that uniquely identifies this user among all active users.", - "uid": "A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", - "groups": "The names of groups this user is a part of.", - "extra": "Any additional information provided by the authenticator.", + "username": "username is the name that uniquely identifies this user among all active users.", + "uid": "uid is a unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", + "groups": "groups is the names of groups this user is a part of.", + "extra": "extra is any additional information provided by the authenticator.", } func (UserInfo) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/authentication/v1alpha1/generated.proto b/staging/src/k8s.io/api/authentication/v1alpha1/generated.proto index 4585e5cdd3253..2bf836d730a1a 100644 --- a/staging/src/k8s.io/api/authentication/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/authentication/v1alpha1/generated.proto @@ -33,18 +33,19 @@ option go_package = "k8s.io/api/authentication/v1alpha1"; // When using impersonation, users will receive the user info of the user being impersonated. If impersonation or // request header authentication is used, any extra keys will have their case ignored and returned as lowercase. message SelfSubjectReview { - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Status is filled in by the server with the user attributes. + // status is filled in by the server with the user attributes. + // +optional optional SelfSubjectReviewStatus status = 2; } // SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user. message SelfSubjectReviewStatus { - // User attributes of the user making this request. + // userInfo is a set of attributes belonging to the user making this request. // +optional optional .k8s.io.api.authentication.v1.UserInfo userInfo = 1; } diff --git a/staging/src/k8s.io/api/authentication/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/authentication/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index d0e9c4e564dc9..0000000000000 --- a/staging/src/k8s.io/api/authentication/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*SelfSubjectReview) ProtoMessage() {} - -func (*SelfSubjectReviewStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/authentication/v1alpha1/types.go b/staging/src/k8s.io/api/authentication/v1alpha1/types.go index 1ee3612fbc481..7e02470c96ae5 100644 --- a/staging/src/k8s.io/api/authentication/v1alpha1/types.go +++ b/staging/src/k8s.io/api/authentication/v1alpha1/types.go @@ -32,17 +32,18 @@ import ( // request header authentication is used, any extra keys will have their case ignored and returned as lowercase. type SelfSubjectReview struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Status is filled in by the server with the user attributes. + // status is filled in by the server with the user attributes. + // +optional Status SelfSubjectReviewStatus `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` } // SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user. type SelfSubjectReviewStatus struct { - // User attributes of the user making this request. + // userInfo is a set of attributes belonging to the user making this request. // +optional UserInfo v1.UserInfo `json:"userInfo,omitempty" protobuf:"bytes,1,opt,name=userInfo"` } diff --git a/staging/src/k8s.io/api/authentication/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/authentication/v1alpha1/types_swagger_doc_generated.go index 1ffcc99e7d36e..95ce0287cf6f2 100644 --- a/staging/src/k8s.io/api/authentication/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/authentication/v1alpha1/types_swagger_doc_generated.go @@ -29,8 +29,8 @@ package v1alpha1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_SelfSubjectReview = map[string]string{ "": "SelfSubjectReview contains the user information that the kube-apiserver has about the user making this request. When using impersonation, users will receive the user info of the user being impersonated. If impersonation or request header authentication is used, any extra keys will have their case ignored and returned as lowercase.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "status": "Status is filled in by the server with the user attributes.", + "metadata": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "status": "status is filled in by the server with the user attributes.", } func (SelfSubjectReview) SwaggerDoc() map[string]string { @@ -39,7 +39,7 @@ func (SelfSubjectReview) SwaggerDoc() map[string]string { var map_SelfSubjectReviewStatus = map[string]string{ "": "SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user.", - "userInfo": "User attributes of the user making this request.", + "userInfo": "userInfo is a set of attributes belonging to the user making this request.", } func (SelfSubjectReviewStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/authentication/v1beta1/generated.proto b/staging/src/k8s.io/api/authentication/v1beta1/generated.proto index d0f6fe4402d8f..999c65156de54 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/authentication/v1beta1/generated.proto @@ -42,18 +42,19 @@ message ExtraValue { // When using impersonation, users will receive the user info of the user being impersonated. If impersonation or // request header authentication is used, any extra keys will have their case ignored and returned as lowercase. message SelfSubjectReview { - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Status is filled in by the server with the user attributes. + // status is filled in by the server with the user attributes. + // +optional optional SelfSubjectReviewStatus status = 2; } // SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user. message SelfSubjectReviewStatus { - // User attributes of the user making this request. + // userInfo is a set of attributes belonging to the user making this request. // +optional optional .k8s.io.api.authentication.v1.UserInfo userInfo = 1; } @@ -62,26 +63,27 @@ message SelfSubjectReviewStatus { // Note: TokenReview requests may be cached by the webhook token authenticator // plugin in the kube-apiserver. message TokenReview { - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated + // +required optional TokenReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the token can be authenticated. + // status is filled in by the server and indicates whether the token can be authenticated. // +optional optional TokenReviewStatus status = 3; } // TokenReviewSpec is a description of the token authentication request. message TokenReviewSpec { - // Token is the opaque bearer token. - // +optional + // token is the opaque bearer token. + // +required optional string token = 1; - // Audiences is a list of the identifiers that the resource server presented + // audiences is a list of the identifiers that the resource server presented // with the token identifies as. Audience-aware token authenticators will // verify that the token was intended for at least one of the audiences in // this list. If no audiences are provided, the audience will default to the @@ -93,15 +95,15 @@ message TokenReviewSpec { // TokenReviewStatus is the result of the token authentication request. message TokenReviewStatus { - // Authenticated indicates that the token was associated with a known user. + // authenticated indicates that the token was associated with a known user. // +optional optional bool authenticated = 1; - // User is the UserInfo associated with the provided token. + // user is the UserInfo associated with the provided token. // +optional optional UserInfo user = 2; - // Audiences are audience identifiers chosen by the authenticator that are + // audiences are audience identifiers chosen by the authenticator that are // compatible with both the TokenReview and token. An identifier is any // identifier in the intersection of the TokenReviewSpec audiences and the // token's audiences. A client of the TokenReview API that sets the @@ -114,7 +116,7 @@ message TokenReviewStatus { // +listType=atomic repeated string audiences = 4; - // Error indicates that the token couldn't be checked + // error indicates that the token couldn't be checked // +optional optional string error = 3; } @@ -122,22 +124,22 @@ message TokenReviewStatus { // UserInfo holds the information about the user needed to implement the // user.Info interface. message UserInfo { - // The name that uniquely identifies this user among all active users. + // username is the name that uniquely identifies this user among all active users. // +optional optional string username = 1; - // A unique value that identifies this user across time. If this user is + // uid is a unique value that identifies this user across time. If this user is // deleted and another user by the same name is added, they will have // different UIDs. // +optional optional string uid = 2; - // The names of groups this user is a part of. + // groups is the names of groups this user is a part of. // +optional // +listType=atomic repeated string groups = 3; - // Any additional information provided by the authenticator. + // extra is any additional information provided by the authenticator. // +optional map extra = 4; } diff --git a/staging/src/k8s.io/api/authentication/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/authentication/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index a55034da99046..0000000000000 --- a/staging/src/k8s.io/api/authentication/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*ExtraValue) ProtoMessage() {} - -func (*SelfSubjectReview) ProtoMessage() {} - -func (*SelfSubjectReviewStatus) ProtoMessage() {} - -func (*TokenReview) ProtoMessage() {} - -func (*TokenReviewSpec) ProtoMessage() {} - -func (*TokenReviewStatus) ProtoMessage() {} - -func (*UserInfo) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/authentication/v1beta1/types.go b/staging/src/k8s.io/api/authentication/v1beta1/types.go index 8038ef7d346cf..33dacdb723080 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/types.go +++ b/staging/src/k8s.io/api/authentication/v1beta1/types.go @@ -36,25 +36,26 @@ import ( // plugin in the kube-apiserver. type TokenReview struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated + // +required Spec TokenReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the token can be authenticated. + // status is filled in by the server and indicates whether the token can be authenticated. // +optional Status TokenReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // TokenReviewSpec is a description of the token authentication request. type TokenReviewSpec struct { - // Token is the opaque bearer token. - // +optional + // token is the opaque bearer token. + // +required Token string `json:"token,omitempty" protobuf:"bytes,1,opt,name=token"` - // Audiences is a list of the identifiers that the resource server presented + // audiences is a list of the identifiers that the resource server presented // with the token identifies as. Audience-aware token authenticators will // verify that the token was intended for at least one of the audiences in // this list. If no audiences are provided, the audience will default to the @@ -66,13 +67,13 @@ type TokenReviewSpec struct { // TokenReviewStatus is the result of the token authentication request. type TokenReviewStatus struct { - // Authenticated indicates that the token was associated with a known user. + // authenticated indicates that the token was associated with a known user. // +optional Authenticated bool `json:"authenticated,omitempty" protobuf:"varint,1,opt,name=authenticated"` - // User is the UserInfo associated with the provided token. + // user is the UserInfo associated with the provided token. // +optional User UserInfo `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"` - // Audiences are audience identifiers chosen by the authenticator that are + // audiences are audience identifiers chosen by the authenticator that are // compatible with both the TokenReview and token. An identifier is any // identifier in the intersection of the TokenReviewSpec audiences and the // token's audiences. A client of the TokenReview API that sets the @@ -84,7 +85,7 @@ type TokenReviewStatus struct { // +optional // +listType=atomic Audiences []string `json:"audiences,omitempty" protobuf:"bytes,4,rep,name=audiences"` - // Error indicates that the token couldn't be checked + // error indicates that the token couldn't be checked // +optional Error string `json:"error,omitempty" protobuf:"bytes,3,opt,name=error"` } @@ -92,19 +93,19 @@ type TokenReviewStatus struct { // UserInfo holds the information about the user needed to implement the // user.Info interface. type UserInfo struct { - // The name that uniquely identifies this user among all active users. + // username is the name that uniquely identifies this user among all active users. // +optional Username string `json:"username,omitempty" protobuf:"bytes,1,opt,name=username"` - // A unique value that identifies this user across time. If this user is + // uid is a unique value that identifies this user across time. If this user is // deleted and another user by the same name is added, they will have // different UIDs. // +optional UID string `json:"uid,omitempty" protobuf:"bytes,2,opt,name=uid"` - // The names of groups this user is a part of. + // groups is the names of groups this user is a part of. // +optional // +listType=atomic Groups []string `json:"groups,omitempty" protobuf:"bytes,3,rep,name=groups"` - // Any additional information provided by the authenticator. + // extra is any additional information provided by the authenticator. // +optional Extra map[string]ExtraValue `json:"extra,omitempty" protobuf:"bytes,4,rep,name=extra"` } @@ -129,17 +130,18 @@ func (t ExtraValue) String() string { // request header authentication is used, any extra keys will have their case ignored and returned as lowercase. type SelfSubjectReview struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. + // metadata is the standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Status is filled in by the server with the user attributes. + // status is filled in by the server with the user attributes. + // +optional Status SelfSubjectReviewStatus `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` } // SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user. type SelfSubjectReviewStatus struct { - // User attributes of the user making this request. + // userInfo is a set of attributes belonging to the user making this request. // +optional UserInfo v1.UserInfo `json:"userInfo,omitempty" protobuf:"bytes,1,opt,name=userInfo"` } diff --git a/staging/src/k8s.io/api/authentication/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/authentication/v1beta1/types_swagger_doc_generated.go index d6644f2cf9d18..31dadf968e4d4 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/authentication/v1beta1/types_swagger_doc_generated.go @@ -29,8 +29,8 @@ package v1beta1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_SelfSubjectReview = map[string]string{ "": "SelfSubjectReview contains the user information that the kube-apiserver has about the user making this request. When using impersonation, users will receive the user info of the user being impersonated. If impersonation or request header authentication is used, any extra keys will have their case ignored and returned as lowercase.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "status": "Status is filled in by the server with the user attributes.", + "metadata": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "status": "status is filled in by the server with the user attributes.", } func (SelfSubjectReview) SwaggerDoc() map[string]string { @@ -39,7 +39,7 @@ func (SelfSubjectReview) SwaggerDoc() map[string]string { var map_SelfSubjectReviewStatus = map[string]string{ "": "SelfSubjectReviewStatus is filled by the kube-apiserver and sent back to a user.", - "userInfo": "User attributes of the user making this request.", + "userInfo": "userInfo is a set of attributes belonging to the user making this request.", } func (SelfSubjectReviewStatus) SwaggerDoc() map[string]string { @@ -48,9 +48,9 @@ func (SelfSubjectReviewStatus) SwaggerDoc() map[string]string { var map_TokenReview = map[string]string{ "": "TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated", - "status": "Status is filled in by the server and indicates whether the token can be authenticated.", + "metadata": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated", + "status": "status is filled in by the server and indicates whether the token can be authenticated.", } func (TokenReview) SwaggerDoc() map[string]string { @@ -59,8 +59,8 @@ func (TokenReview) SwaggerDoc() map[string]string { var map_TokenReviewSpec = map[string]string{ "": "TokenReviewSpec is a description of the token authentication request.", - "token": "Token is the opaque bearer token.", - "audiences": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", + "token": "token is the opaque bearer token.", + "audiences": "audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.", } func (TokenReviewSpec) SwaggerDoc() map[string]string { @@ -69,10 +69,10 @@ func (TokenReviewSpec) SwaggerDoc() map[string]string { var map_TokenReviewStatus = map[string]string{ "": "TokenReviewStatus is the result of the token authentication request.", - "authenticated": "Authenticated indicates that the token was associated with a known user.", - "user": "User is the UserInfo associated with the provided token.", - "audiences": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", - "error": "Error indicates that the token couldn't be checked", + "authenticated": "authenticated indicates that the token was associated with a known user.", + "user": "user is the UserInfo associated with the provided token.", + "audiences": "audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.", + "error": "error indicates that the token couldn't be checked", } func (TokenReviewStatus) SwaggerDoc() map[string]string { @@ -81,10 +81,10 @@ func (TokenReviewStatus) SwaggerDoc() map[string]string { var map_UserInfo = map[string]string{ "": "UserInfo holds the information about the user needed to implement the user.Info interface.", - "username": "The name that uniquely identifies this user among all active users.", - "uid": "A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", - "groups": "The names of groups this user is a part of.", - "extra": "Any additional information provided by the authenticator.", + "username": "username is the name that uniquely identifies this user among all active users.", + "uid": "uid is a unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.", + "groups": "groups is the names of groups this user is a part of.", + "extra": "extra is any additional information provided by the authenticator.", } func (UserInfo) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/authorization/v1/generated.proto b/staging/src/k8s.io/api/authorization/v1/generated.proto index ff529c969e4ac..c47449f911b7c 100644 --- a/staging/src/k8s.io/api/authorization/v1/generated.proto +++ b/staging/src/k8s.io/api/authorization/v1/generated.proto @@ -95,38 +95,38 @@ message LabelSelectorAttributes { // Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions // checking. message LocalSubjectAccessReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace + // spec holds information about the request being evaluated. spec.namespace must be equal to the namespace // you made the request against. If empty, it is defaulted. optional SubjectAccessReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional optional SubjectAccessReviewStatus status = 3; } // NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface message NonResourceAttributes { - // Path is the URL path of the request + // path is the URL path of the request // +optional optional string path = 1; - // Verb is the standard HTTP verb + // verb is the standard HTTP verb // +optional optional string verb = 2; } // NonResourceRule holds information that describes a rule for the non-resource message NonResourceRule { - // Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. + // verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. // +listType=atomic repeated string verbs = 1; - // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, + // nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, // final step in the path. "*" means all. // +optional // +listType=atomic @@ -135,34 +135,34 @@ message NonResourceRule { // ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface message ResourceAttributes { - // Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces + // namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces // "" (empty) is defaulted for LocalSubjectAccessReviews // "" (empty) is empty for cluster-scoped resources // "" (empty) means "all" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview // +optional optional string namespace = 1; - // Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. // +optional optional string verb = 2; - // Group is the API Group of the Resource. "*" means all. + // group is the API Group of the Resource. "*" means all. // +optional optional string group = 3; - // Version is the API Version of the Resource. "*" means all. + // version is the API Version of the Resource. "*" means all. // +optional optional string version = 4; - // Resource is one of the existing resource types. "*" means all. + // resource is one of the existing resource types. "*" means all. // +optional optional string resource = 5; - // Subresource is one of the existing resource types. "" means none. + // subresource is one of the existing resource types. "" means none. // +optional optional string subresource = 6; - // Name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. + // name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. // +optional optional string name = 7; @@ -178,23 +178,23 @@ message ResourceAttributes { // ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, // may contain duplicates, and possibly be incomplete. message ResourceRule { - // Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. // +listType=atomic repeated string verbs = 1; - // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of // the enumerated resources in any API group will be allowed. "*" means all. // +optional // +listType=atomic repeated string apiGroups = 2; - // Resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. + // resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. // "*/foo" represents the subresource 'foo' for all resources in the specified apiGroups. // +optional // +listType=atomic repeated string resources = 3; - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. // +optional // +listType=atomic repeated string resourceNames = 4; @@ -204,27 +204,27 @@ message ResourceRule { // spec.namespace means "in all namespaces". Self is a special case, because users should always be able // to check whether they can perform an action message SelfSubjectAccessReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated. user and groups must be empty + // spec holds information about the request being evaluated. user and groups must be empty optional SelfSubjectAccessReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional optional SubjectAccessReviewStatus status = 3; } -// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set message SelfSubjectAccessReviewSpec { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional optional ResourceAttributes resourceAttributes = 1; - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional optional NonResourceAttributes nonResourceAttributes = 2; } @@ -236,88 +236,88 @@ message SelfSubjectAccessReviewSpec { // drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns. // SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server. message SelfSubjectRulesReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated. + // spec holds information about the request being evaluated. optional SelfSubjectRulesReviewSpec spec = 2; - // Status is filled in by the server and indicates the set of actions a user can perform. + // status is filled in by the server and indicates the set of actions a user can perform. // +optional optional SubjectRulesReviewStatus status = 3; } // SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview. message SelfSubjectRulesReviewSpec { - // Namespace to evaluate rules for. Required. + // namespace to evaluate rules for. Required. optional string namespace = 1; } // SubjectAccessReview checks whether or not a user or group can perform an action. message SubjectAccessReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated optional SubjectAccessReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional optional SubjectAccessReviewStatus status = 3; } -// SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set message SubjectAccessReviewSpec { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional optional ResourceAttributes resourceAttributes = 1; - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional optional NonResourceAttributes nonResourceAttributes = 2; - // User is the user you're testing for. + // user is the user you're testing for. // If you specify "User" but not "Groups", then is it interpreted as "What if User were not a member of any groups // +optional optional string user = 3; - // Groups is the groups you're testing for. + // groups is the groups you're testing for. // +optional // +listType=atomic repeated string groups = 4; - // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer + // extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // it needs a reflection here. // +optional map extra = 5; - // UID information about the requesting user. + // uid information about the requesting user. // +optional optional string uid = 6; } // SubjectAccessReviewStatus message SubjectAccessReviewStatus { - // Allowed is required. True if the action would be allowed, false otherwise. + // allowed is required. True if the action would be allowed, false otherwise. optional bool allowed = 1; - // Denied is optional. True if the action would be denied, otherwise + // denied is optional. True if the action would be denied, otherwise // false. If both allowed is false and denied is false, then the // authorizer has no opinion on whether to authorize the action. Denied // may not be true if Allowed is true. // +optional optional bool denied = 4; - // Reason is optional. It indicates why a request was allowed or denied. + // reason is optional. It indicates why a request was allowed or denied. // +optional optional string reason = 2; - // EvaluationError is an indication that some error occurred during the authorization check. + // evaluationError is an indication that some error occurred during the authorization check. // It is entirely possible to get an error and be able to continue determine authorization status in spite of it. // For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request. // +optional @@ -329,21 +329,21 @@ message SubjectAccessReviewStatus { // Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, // even if that list is incomplete. message SubjectRulesReviewStatus { - // ResourceRules is the list of actions the subject is allowed to perform on resources. + // resourceRules is the list of actions the subject is allowed to perform on resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic repeated ResourceRule resourceRules = 1; - // NonResourceRules is the list of actions the subject is allowed to perform on non-resources. + // nonResourceRules is the list of actions the subject is allowed to perform on non-resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic repeated NonResourceRule nonResourceRules = 2; - // Incomplete is true when the rules returned by this call are incomplete. This is most commonly + // incomplete is true when the rules returned by this call are incomplete. This is most commonly // encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation. optional bool incomplete = 3; - // EvaluationError can appear in combination with Rules. It indicates an error occurred during + // evaluationError can appear in combination with Rules. It indicates an error occurred during // rule evaluation, such as an authorizer that doesn't support rule evaluation, and that // ResourceRules and/or NonResourceRules may be incomplete. // +optional diff --git a/staging/src/k8s.io/api/authorization/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/authorization/v1/generated.protomessage.pb.go deleted file mode 100644 index dc1e1028c00c8..0000000000000 --- a/staging/src/k8s.io/api/authorization/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*ExtraValue) ProtoMessage() {} - -func (*FieldSelectorAttributes) ProtoMessage() {} - -func (*LabelSelectorAttributes) ProtoMessage() {} - -func (*LocalSubjectAccessReview) ProtoMessage() {} - -func (*NonResourceAttributes) ProtoMessage() {} - -func (*NonResourceRule) ProtoMessage() {} - -func (*ResourceAttributes) ProtoMessage() {} - -func (*ResourceRule) ProtoMessage() {} - -func (*SelfSubjectAccessReview) ProtoMessage() {} - -func (*SelfSubjectAccessReviewSpec) ProtoMessage() {} - -func (*SelfSubjectRulesReview) ProtoMessage() {} - -func (*SelfSubjectRulesReviewSpec) ProtoMessage() {} - -func (*SubjectAccessReview) ProtoMessage() {} - -func (*SubjectAccessReviewSpec) ProtoMessage() {} - -func (*SubjectAccessReviewStatus) ProtoMessage() {} - -func (*SubjectRulesReviewStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/authorization/v1/types.go b/staging/src/k8s.io/api/authorization/v1/types.go index 251e776b024b5..dcc9b0f853b6c 100644 --- a/staging/src/k8s.io/api/authorization/v1/types.go +++ b/staging/src/k8s.io/api/authorization/v1/types.go @@ -31,15 +31,15 @@ import ( // SubjectAccessReview checks whether or not a user or group can perform an action. type SubjectAccessReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated Spec SubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } @@ -55,15 +55,15 @@ type SubjectAccessReview struct { // to check whether they can perform an action type SelfSubjectAccessReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated. user and groups must be empty + // spec holds information about the request being evaluated. user and groups must be empty Spec SelfSubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } @@ -78,44 +78,44 @@ type SelfSubjectAccessReview struct { // checking. type LocalSubjectAccessReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace + // spec holds information about the request being evaluated. spec.namespace must be equal to the namespace // you made the request against. If empty, it is defaulted. Spec SubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface type ResourceAttributes struct { - // Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces + // namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces // "" (empty) is defaulted for LocalSubjectAccessReviews // "" (empty) is empty for cluster-scoped resources // "" (empty) means "all" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview // +optional Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"` - // Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. // +optional Verb string `json:"verb,omitempty" protobuf:"bytes,2,opt,name=verb"` - // Group is the API Group of the Resource. "*" means all. + // group is the API Group of the Resource. "*" means all. // +optional Group string `json:"group,omitempty" protobuf:"bytes,3,opt,name=group"` - // Version is the API Version of the Resource. "*" means all. + // version is the API Version of the Resource. "*" means all. // +optional Version string `json:"version,omitempty" protobuf:"bytes,4,opt,name=version"` - // Resource is one of the existing resource types. "*" means all. + // resource is one of the existing resource types. "*" means all. // +optional Resource string `json:"resource,omitempty" protobuf:"bytes,5,opt,name=resource"` - // Subresource is one of the existing resource types. "" means none. + // subresource is one of the existing resource types. "" means none. // +optional Subresource string `json:"subresource,omitempty" protobuf:"bytes,6,opt,name=subresource"` - // Name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. + // name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. // +optional Name string `json:"name,omitempty" protobuf:"bytes,7,opt,name=name"` // fieldSelector describes the limitation on access based on field. It can only limit access, not broaden it. @@ -182,37 +182,37 @@ type FieldSelectorAttributes struct { // NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface type NonResourceAttributes struct { - // Path is the URL path of the request + // path is the URL path of the request // +optional Path string `json:"path,omitempty" protobuf:"bytes,1,opt,name=path"` - // Verb is the standard HTTP verb + // verb is the standard HTTP verb // +optional Verb string `json:"verb,omitempty" protobuf:"bytes,2,opt,name=verb"` } -// SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set type SubjectAccessReviewSpec struct { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty" protobuf:"bytes,1,opt,name=resourceAttributes"` - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty" protobuf:"bytes,2,opt,name=nonResourceAttributes"` - // User is the user you're testing for. + // user is the user you're testing for. // If you specify "User" but not "Groups", then is it interpreted as "What if User were not a member of any groups // +optional User string `json:"user,omitempty" protobuf:"bytes,3,opt,name=user"` - // Groups is the groups you're testing for. + // groups is the groups you're testing for. // +optional // +listType=atomic Groups []string `json:"groups,omitempty" protobuf:"bytes,4,rep,name=groups"` - // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer + // extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // it needs a reflection here. // +optional Extra map[string]ExtraValue `json:"extra,omitempty" protobuf:"bytes,5,rep,name=extra"` - // UID information about the requesting user. + // uid information about the requesting user. // +optional UID string `json:"uid,omitempty" protobuf:"bytes,6,opt,name=uid"` } @@ -226,31 +226,31 @@ func (t ExtraValue) String() string { return fmt.Sprintf("%v", []string(t)) } -// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set type SelfSubjectAccessReviewSpec struct { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty" protobuf:"bytes,1,opt,name=resourceAttributes"` - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty" protobuf:"bytes,2,opt,name=nonResourceAttributes"` } // SubjectAccessReviewStatus type SubjectAccessReviewStatus struct { - // Allowed is required. True if the action would be allowed, false otherwise. + // allowed is required. True if the action would be allowed, false otherwise. Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` - // Denied is optional. True if the action would be denied, otherwise + // denied is optional. True if the action would be denied, otherwise // false. If both allowed is false and denied is false, then the // authorizer has no opinion on whether to authorize the action. Denied // may not be true if Allowed is true. // +optional Denied bool `json:"denied,omitempty" protobuf:"varint,4,opt,name=denied"` - // Reason is optional. It indicates why a request was allowed or denied. + // reason is optional. It indicates why a request was allowed or denied. // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` - // EvaluationError is an indication that some error occurred during the authorization check. + // evaluationError is an indication that some error occurred during the authorization check. // It is entirely possible to get an error and be able to continue determine authorization status in spite of it. // For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request. // +optional @@ -271,22 +271,22 @@ type SubjectAccessReviewStatus struct { // SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server. type SelfSubjectRulesReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated. + // spec holds information about the request being evaluated. Spec SelfSubjectRulesReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates the set of actions a user can perform. + // status is filled in by the server and indicates the set of actions a user can perform. // +optional Status SubjectRulesReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview. type SelfSubjectRulesReviewSpec struct { - // Namespace to evaluate rules for. Required. + // namespace to evaluate rules for. Required. Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"` } @@ -295,18 +295,18 @@ type SelfSubjectRulesReviewSpec struct { // Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, // even if that list is incomplete. type SubjectRulesReviewStatus struct { - // ResourceRules is the list of actions the subject is allowed to perform on resources. + // resourceRules is the list of actions the subject is allowed to perform on resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic ResourceRules []ResourceRule `json:"resourceRules" protobuf:"bytes,1,rep,name=resourceRules"` - // NonResourceRules is the list of actions the subject is allowed to perform on non-resources. + // nonResourceRules is the list of actions the subject is allowed to perform on non-resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic NonResourceRules []NonResourceRule `json:"nonResourceRules" protobuf:"bytes,2,rep,name=nonResourceRules"` - // Incomplete is true when the rules returned by this call are incomplete. This is most commonly + // incomplete is true when the rules returned by this call are incomplete. This is most commonly // encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation. Incomplete bool `json:"incomplete" protobuf:"bytes,3,rep,name=incomplete"` - // EvaluationError can appear in combination with Rules. It indicates an error occurred during + // evaluationError can appear in combination with Rules. It indicates an error occurred during // rule evaluation, such as an authorizer that doesn't support rule evaluation, and that // ResourceRules and/or NonResourceRules may be incomplete. // +optional @@ -316,21 +316,21 @@ type SubjectRulesReviewStatus struct { // ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, // may contain duplicates, and possibly be incomplete. type ResourceRule struct { - // Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. // +listType=atomic Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` - // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of // the enumerated resources in any API group will be allowed. "*" means all. // +optional // +listType=atomic APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"` - // Resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. + // resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. // "*/foo" represents the subresource 'foo' for all resources in the specified apiGroups. // +optional // +listType=atomic Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"` - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. // +optional // +listType=atomic ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"` @@ -338,11 +338,11 @@ type ResourceRule struct { // NonResourceRule holds information that describes a rule for the non-resource type NonResourceRule struct { - // Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. + // verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. // +listType=atomic Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` - // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, + // nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, // final step in the path. "*" means all. // +optional // +listType=atomic diff --git a/staging/src/k8s.io/api/authorization/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/authorization/v1/types_swagger_doc_generated.go index 29d0aa8463d21..0b3ae19a59999 100644 --- a/staging/src/k8s.io/api/authorization/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/authorization/v1/types_swagger_doc_generated.go @@ -49,9 +49,9 @@ func (LabelSelectorAttributes) SwaggerDoc() map[string]string { var map_LocalSubjectAccessReview = map[string]string{ "": "LocalSubjectAccessReview checks whether or not a user or group can perform an action in a given namespace. Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions checking.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", - "status": "Status is filled in by the server and indicates whether the request is allowed or not", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", + "status": "status is filled in by the server and indicates whether the request is allowed or not", } func (LocalSubjectAccessReview) SwaggerDoc() map[string]string { @@ -60,8 +60,8 @@ func (LocalSubjectAccessReview) SwaggerDoc() map[string]string { var map_NonResourceAttributes = map[string]string{ "": "NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface", - "path": "Path is the URL path of the request", - "verb": "Verb is the standard HTTP verb", + "path": "path is the URL path of the request", + "verb": "verb is the standard HTTP verb", } func (NonResourceAttributes) SwaggerDoc() map[string]string { @@ -70,8 +70,8 @@ func (NonResourceAttributes) SwaggerDoc() map[string]string { var map_NonResourceRule = map[string]string{ "": "NonResourceRule holds information that describes a rule for the non-resource", - "verbs": "Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", - "nonResourceURLs": "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", + "verbs": "verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", + "nonResourceURLs": "nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", } func (NonResourceRule) SwaggerDoc() map[string]string { @@ -80,13 +80,13 @@ func (NonResourceRule) SwaggerDoc() map[string]string { var map_ResourceAttributes = map[string]string{ "": "ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface", - "namespace": "Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", - "verb": "Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", - "group": "Group is the API Group of the Resource. \"*\" means all.", - "version": "Version is the API Version of the Resource. \"*\" means all.", - "resource": "Resource is one of the existing resource types. \"*\" means all.", - "subresource": "Subresource is one of the existing resource types. \"\" means none.", - "name": "Name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", + "namespace": "namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", + "verb": "verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "group": "group is the API Group of the Resource. \"*\" means all.", + "version": "version is the API Version of the Resource. \"*\" means all.", + "resource": "resource is one of the existing resource types. \"*\" means all.", + "subresource": "subresource is one of the existing resource types. \"\" means none.", + "name": "name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", "fieldSelector": "fieldSelector describes the limitation on access based on field. It can only limit access, not broaden it.", "labelSelector": "labelSelector describes the limitation on access based on labels. It can only limit access, not broaden it.", } @@ -97,10 +97,10 @@ func (ResourceAttributes) SwaggerDoc() map[string]string { var map_ResourceRule = map[string]string{ "": "ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", - "verbs": "Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", - "apiGroups": "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", - "resources": "Resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", - "resourceNames": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", + "verbs": "verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "apiGroups": "apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", + "resources": "resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", + "resourceNames": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", } func (ResourceRule) SwaggerDoc() map[string]string { @@ -109,9 +109,9 @@ func (ResourceRule) SwaggerDoc() map[string]string { var map_SelfSubjectAccessReview = map[string]string{ "": "SelfSubjectAccessReview checks whether or the current user can perform an action. Not filling in a spec.namespace means \"in all namespaces\". Self is a special case, because users should always be able to check whether they can perform an action", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated. user and groups must be empty", - "status": "Status is filled in by the server and indicates whether the request is allowed or not", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated. user and groups must be empty", + "status": "status is filled in by the server and indicates whether the request is allowed or not", } func (SelfSubjectAccessReview) SwaggerDoc() map[string]string { @@ -119,9 +119,9 @@ func (SelfSubjectAccessReview) SwaggerDoc() map[string]string { } var map_SelfSubjectAccessReviewSpec = map[string]string{ - "": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", - "resourceAttributes": "ResourceAuthorizationAttributes describes information for a resource access request", - "nonResourceAttributes": "NonResourceAttributes describes information for a non-resource access request", + "": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", + "resourceAttributes": "resourceAttributes describes information for a resource access request", + "nonResourceAttributes": "nonResourceAttributes describes information for a non-resource access request", } func (SelfSubjectAccessReviewSpec) SwaggerDoc() map[string]string { @@ -130,9 +130,9 @@ func (SelfSubjectAccessReviewSpec) SwaggerDoc() map[string]string { var map_SelfSubjectRulesReview = map[string]string{ "": "SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace. The returned list of actions may be incomplete depending on the server's authorization mode, and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions, or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns. SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated.", - "status": "Status is filled in by the server and indicates the set of actions a user can perform.", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated.", + "status": "status is filled in by the server and indicates the set of actions a user can perform.", } func (SelfSubjectRulesReview) SwaggerDoc() map[string]string { @@ -141,7 +141,7 @@ func (SelfSubjectRulesReview) SwaggerDoc() map[string]string { var map_SelfSubjectRulesReviewSpec = map[string]string{ "": "SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview.", - "namespace": "Namespace to evaluate rules for. Required.", + "namespace": "namespace to evaluate rules for. Required.", } func (SelfSubjectRulesReviewSpec) SwaggerDoc() map[string]string { @@ -150,9 +150,9 @@ func (SelfSubjectRulesReviewSpec) SwaggerDoc() map[string]string { var map_SubjectAccessReview = map[string]string{ "": "SubjectAccessReview checks whether or not a user or group can perform an action.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated", - "status": "Status is filled in by the server and indicates whether the request is allowed or not", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated", + "status": "status is filled in by the server and indicates whether the request is allowed or not", } func (SubjectAccessReview) SwaggerDoc() map[string]string { @@ -160,13 +160,13 @@ func (SubjectAccessReview) SwaggerDoc() map[string]string { } var map_SubjectAccessReviewSpec = map[string]string{ - "": "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", - "resourceAttributes": "ResourceAuthorizationAttributes describes information for a resource access request", - "nonResourceAttributes": "NonResourceAttributes describes information for a non-resource access request", - "user": "User is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", - "groups": "Groups is the groups you're testing for.", - "extra": "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", - "uid": "UID information about the requesting user.", + "": "SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", + "resourceAttributes": "resourceAttributes describes information for a resource access request", + "nonResourceAttributes": "nonResourceAttributes describes information for a non-resource access request", + "user": "user is the user you're testing for. If you specify \"User\" but not \"Groups\", then is it interpreted as \"What if User were not a member of any groups", + "groups": "groups is the groups you're testing for.", + "extra": "extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", + "uid": "uid information about the requesting user.", } func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string { @@ -175,10 +175,10 @@ func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string { var map_SubjectAccessReviewStatus = map[string]string{ "": "SubjectAccessReviewStatus", - "allowed": "Allowed is required. True if the action would be allowed, false otherwise.", - "denied": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", - "reason": "Reason is optional. It indicates why a request was allowed or denied.", - "evaluationError": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", + "allowed": "allowed is required. True if the action would be allowed, false otherwise.", + "denied": "denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", + "reason": "reason is optional. It indicates why a request was allowed or denied.", + "evaluationError": "evaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", } func (SubjectAccessReviewStatus) SwaggerDoc() map[string]string { @@ -187,10 +187,10 @@ func (SubjectAccessReviewStatus) SwaggerDoc() map[string]string { var map_SubjectRulesReviewStatus = map[string]string{ "": "SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on the set of authorizers the server is configured with and any errors experienced during evaluation. Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, even if that list is incomplete.", - "resourceRules": "ResourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", - "nonResourceRules": "NonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", - "incomplete": "Incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", - "evaluationError": "EvaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", + "resourceRules": "resourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "nonResourceRules": "nonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "incomplete": "incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", + "evaluationError": "evaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", } func (SubjectRulesReviewStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/authorization/v1beta1/generated.proto b/staging/src/k8s.io/api/authorization/v1beta1/generated.proto index 8738768b89077..8095ab4dbbe25 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/authorization/v1beta1/generated.proto @@ -42,38 +42,38 @@ message ExtraValue { // Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions // checking. message LocalSubjectAccessReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace + // spec holds information about the request being evaluated. spec.namespace must be equal to the namespace // you made the request against. If empty, it is defaulted. optional SubjectAccessReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional optional SubjectAccessReviewStatus status = 3; } // NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface message NonResourceAttributes { - // Path is the URL path of the request + // path is the URL path of the request // +optional optional string path = 1; - // Verb is the standard HTTP verb + // verb is the standard HTTP verb // +optional optional string verb = 2; } // NonResourceRule holds information that describes a rule for the non-resource message NonResourceRule { - // Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. + // verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. // +listType=atomic repeated string verbs = 1; - // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, + // nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, // final step in the path. "*" means all. // +optional // +listType=atomic @@ -82,34 +82,34 @@ message NonResourceRule { // ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface message ResourceAttributes { - // Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces + // namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces // "" (empty) is defaulted for LocalSubjectAccessReviews // "" (empty) is empty for cluster-scoped resources // "" (empty) means "all" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview // +optional optional string namespace = 1; - // Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. // +optional optional string verb = 2; - // Group is the API Group of the Resource. "*" means all. + // group is the API Group of the Resource. "*" means all. // +optional optional string group = 3; - // Version is the API Version of the Resource. "*" means all. + // version is the API Version of the Resource. "*" means all. // +optional optional string version = 4; - // Resource is one of the existing resource types. "*" means all. + // resource is one of the existing resource types. "*" means all. // +optional optional string resource = 5; - // Subresource is one of the existing resource types. "" means none. + // subresource is one of the existing resource types. "" means none. // +optional optional string subresource = 6; - // Name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. + // name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. // +optional optional string name = 7; @@ -125,23 +125,23 @@ message ResourceAttributes { // ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, // may contain duplicates, and possibly be incomplete. message ResourceRule { - // Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. // +listType=atomic repeated string verbs = 1; - // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of // the enumerated resources in any API group will be allowed. "*" means all. // +optional // +listType=atomic repeated string apiGroups = 2; - // Resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. + // resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. // "*/foo" represents the subresource 'foo' for all resources in the specified apiGroups. // +optional // +listType=atomic repeated string resources = 3; - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. // +optional // +listType=atomic repeated string resourceNames = 4; @@ -151,27 +151,27 @@ message ResourceRule { // spec.namespace means "in all namespaces". Self is a special case, because users should always be able // to check whether they can perform an action message SelfSubjectAccessReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated. user and groups must be empty + // spec holds information about the request being evaluated. user and groups must be empty optional SelfSubjectAccessReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional optional SubjectAccessReviewStatus status = 3; } -// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set message SelfSubjectAccessReviewSpec { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional optional ResourceAttributes resourceAttributes = 1; - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional optional NonResourceAttributes nonResourceAttributes = 2; } @@ -183,88 +183,88 @@ message SelfSubjectAccessReviewSpec { // drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns. // SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server. message SelfSubjectRulesReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated. + // spec holds information about the request being evaluated. optional SelfSubjectRulesReviewSpec spec = 2; - // Status is filled in by the server and indicates the set of actions a user can perform. + // status is filled in by the server and indicates the set of actions a user can perform. // +optional optional SubjectRulesReviewStatus status = 3; } // SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview. message SelfSubjectRulesReviewSpec { - // Namespace to evaluate rules for. Required. + // namespace to evaluate rules for. Required. optional string namespace = 1; } // SubjectAccessReview checks whether or not a user or group can perform an action. message SubjectAccessReview { - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated optional SubjectAccessReviewSpec spec = 2; - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional optional SubjectAccessReviewStatus status = 3; } -// SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set message SubjectAccessReviewSpec { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional optional ResourceAttributes resourceAttributes = 1; - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional optional NonResourceAttributes nonResourceAttributes = 2; - // User is the user you're testing for. + // user is the user you're testing for. // If you specify "User" but not "Group", then is it interpreted as "What if User were not a member of any groups // +optional optional string user = 3; - // Groups is the groups you're testing for. + // group is the groups you're testing for. // +optional // +listType=atomic repeated string group = 4; - // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer + // extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // it needs a reflection here. // +optional map extra = 5; - // UID information about the requesting user. + // uid information about the requesting user. // +optional optional string uid = 6; } // SubjectAccessReviewStatus message SubjectAccessReviewStatus { - // Allowed is required. True if the action would be allowed, false otherwise. + // allowed is required. True if the action would be allowed, false otherwise. optional bool allowed = 1; - // Denied is optional. True if the action would be denied, otherwise + // denied is optional. True if the action would be denied, otherwise // false. If both allowed is false and denied is false, then the // authorizer has no opinion on whether to authorize the action. Denied // may not be true if Allowed is true. // +optional optional bool denied = 4; - // Reason is optional. It indicates why a request was allowed or denied. + // reason is optional. It indicates why a request was allowed or denied. // +optional optional string reason = 2; - // EvaluationError is an indication that some error occurred during the authorization check. + // evaluationError is an indication that some error occurred during the authorization check. // It is entirely possible to get an error and be able to continue determine authorization status in spite of it. // For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request. // +optional @@ -276,21 +276,21 @@ message SubjectAccessReviewStatus { // Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, // even if that list is incomplete. message SubjectRulesReviewStatus { - // ResourceRules is the list of actions the subject is allowed to perform on resources. + // resourceRules is the list of actions the subject is allowed to perform on resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic repeated ResourceRule resourceRules = 1; - // NonResourceRules is the list of actions the subject is allowed to perform on non-resources. + // nonResourceRules is the list of actions the subject is allowed to perform on non-resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic repeated NonResourceRule nonResourceRules = 2; - // Incomplete is true when the rules returned by this call are incomplete. This is most commonly + // incomplete is true when the rules returned by this call are incomplete. This is most commonly // encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation. optional bool incomplete = 3; - // EvaluationError can appear in combination with Rules. It indicates an error occurred during + // evaluationError can appear in combination with Rules. It indicates an error occurred during // rule evaluation, such as an authorizer that doesn't support rule evaluation, and that // ResourceRules and/or NonResourceRules may be incomplete. // +optional diff --git a/staging/src/k8s.io/api/authorization/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/authorization/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 69af1b6c93050..0000000000000 --- a/staging/src/k8s.io/api/authorization/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,50 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*ExtraValue) ProtoMessage() {} - -func (*LocalSubjectAccessReview) ProtoMessage() {} - -func (*NonResourceAttributes) ProtoMessage() {} - -func (*NonResourceRule) ProtoMessage() {} - -func (*ResourceAttributes) ProtoMessage() {} - -func (*ResourceRule) ProtoMessage() {} - -func (*SelfSubjectAccessReview) ProtoMessage() {} - -func (*SelfSubjectAccessReviewSpec) ProtoMessage() {} - -func (*SelfSubjectRulesReview) ProtoMessage() {} - -func (*SelfSubjectRulesReviewSpec) ProtoMessage() {} - -func (*SubjectAccessReview) ProtoMessage() {} - -func (*SubjectAccessReviewSpec) ProtoMessage() {} - -func (*SubjectAccessReviewStatus) ProtoMessage() {} - -func (*SubjectRulesReviewStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/authorization/v1beta1/types.go b/staging/src/k8s.io/api/authorization/v1beta1/types.go index 8b8e5a9867804..204559fd20552 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/types.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/types.go @@ -34,15 +34,15 @@ import ( // SubjectAccessReview checks whether or not a user or group can perform an action. type SubjectAccessReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated + // spec holds information about the request being evaluated Spec SubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } @@ -60,15 +60,15 @@ type SubjectAccessReview struct { // to check whether they can perform an action type SelfSubjectAccessReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated. user and groups must be empty + // spec holds information about the request being evaluated. user and groups must be empty Spec SelfSubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } @@ -85,44 +85,44 @@ type SelfSubjectAccessReview struct { // checking. type LocalSubjectAccessReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace + // spec holds information about the request being evaluated. spec.namespace must be equal to the namespace // you made the request against. If empty, it is defaulted. Spec SubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates whether the request is allowed or not + // status is filled in by the server and indicates whether the request is allowed or not // +optional Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface type ResourceAttributes struct { - // Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces + // namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces // "" (empty) is defaulted for LocalSubjectAccessReviews // "" (empty) is empty for cluster-scoped resources // "" (empty) means "all" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview // +optional Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"` - // Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. // +optional Verb string `json:"verb,omitempty" protobuf:"bytes,2,opt,name=verb"` - // Group is the API Group of the Resource. "*" means all. + // group is the API Group of the Resource. "*" means all. // +optional Group string `json:"group,omitempty" protobuf:"bytes,3,opt,name=group"` - // Version is the API Version of the Resource. "*" means all. + // version is the API Version of the Resource. "*" means all. // +optional Version string `json:"version,omitempty" protobuf:"bytes,4,opt,name=version"` - // Resource is one of the existing resource types. "*" means all. + // resource is one of the existing resource types. "*" means all. // +optional Resource string `json:"resource,omitempty" protobuf:"bytes,5,opt,name=resource"` - // Subresource is one of the existing resource types. "" means none. + // subresource is one of the existing resource types. "" means none. // +optional Subresource string `json:"subresource,omitempty" protobuf:"bytes,6,opt,name=subresource"` - // Name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. + // name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. // +optional Name string `json:"name,omitempty" protobuf:"bytes,7,opt,name=name"` // fieldSelector describes the limitation on access based on field. It can only limit access, not broaden it. @@ -135,37 +135,37 @@ type ResourceAttributes struct { // NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface type NonResourceAttributes struct { - // Path is the URL path of the request + // path is the URL path of the request // +optional Path string `json:"path,omitempty" protobuf:"bytes,1,opt,name=path"` - // Verb is the standard HTTP verb + // verb is the standard HTTP verb // +optional Verb string `json:"verb,omitempty" protobuf:"bytes,2,opt,name=verb"` } -// SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set type SubjectAccessReviewSpec struct { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty" protobuf:"bytes,1,opt,name=resourceAttributes"` - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty" protobuf:"bytes,2,opt,name=nonResourceAttributes"` - // User is the user you're testing for. + // user is the user you're testing for. // If you specify "User" but not "Group", then is it interpreted as "What if User were not a member of any groups // +optional User string `json:"user,omitempty" protobuf:"bytes,3,opt,name=user"` - // Groups is the groups you're testing for. + // group is the groups you're testing for. // +optional // +listType=atomic Groups []string `json:"group,omitempty" protobuf:"bytes,4,rep,name=group"` - // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer + // extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // it needs a reflection here. // +optional Extra map[string]ExtraValue `json:"extra,omitempty" protobuf:"bytes,5,rep,name=extra"` - // UID information about the requesting user. + // uid information about the requesting user. // +optional UID string `json:"uid,omitempty" protobuf:"bytes,6,opt,name=uid"` } @@ -179,31 +179,31 @@ func (t ExtraValue) String() string { return fmt.Sprintf("%v", []string(t)) } -// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes -// and NonResourceAuthorizationAttributes must be set +// SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes +// and nonResourceAttributes must be set type SelfSubjectAccessReviewSpec struct { - // ResourceAuthorizationAttributes describes information for a resource access request + // resourceAttributes describes information for a resource access request // +optional ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty" protobuf:"bytes,1,opt,name=resourceAttributes"` - // NonResourceAttributes describes information for a non-resource access request + // nonResourceAttributes describes information for a non-resource access request // +optional NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty" protobuf:"bytes,2,opt,name=nonResourceAttributes"` } // SubjectAccessReviewStatus type SubjectAccessReviewStatus struct { - // Allowed is required. True if the action would be allowed, false otherwise. + // allowed is required. True if the action would be allowed, false otherwise. Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` - // Denied is optional. True if the action would be denied, otherwise + // denied is optional. True if the action would be denied, otherwise // false. If both allowed is false and denied is false, then the // authorizer has no opinion on whether to authorize the action. Denied // may not be true if Allowed is true. // +optional Denied bool `json:"denied,omitempty" protobuf:"varint,4,opt,name=denied"` - // Reason is optional. It indicates why a request was allowed or denied. + // reason is optional. It indicates why a request was allowed or denied. // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` - // EvaluationError is an indication that some error occurred during the authorization check. + // evaluationError is an indication that some error occurred during the authorization check. // It is entirely possible to get an error and be able to continue determine authorization status in spite of it. // For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request. // +optional @@ -226,22 +226,22 @@ type SubjectAccessReviewStatus struct { // SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server. type SelfSubjectRulesReview struct { metav1.TypeMeta `json:",inline"` - // Standard list metadata. + // metadata is the standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Spec holds information about the request being evaluated. + // spec holds information about the request being evaluated. Spec SelfSubjectRulesReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - // Status is filled in by the server and indicates the set of actions a user can perform. + // status is filled in by the server and indicates the set of actions a user can perform. // +optional Status SubjectRulesReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview. type SelfSubjectRulesReviewSpec struct { - // Namespace to evaluate rules for. Required. + // namespace to evaluate rules for. Required. Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"` } @@ -250,18 +250,18 @@ type SelfSubjectRulesReviewSpec struct { // Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, // even if that list is incomplete. type SubjectRulesReviewStatus struct { - // ResourceRules is the list of actions the subject is allowed to perform on resources. + // resourceRules is the list of actions the subject is allowed to perform on resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic ResourceRules []ResourceRule `json:"resourceRules" protobuf:"bytes,1,rep,name=resourceRules"` - // NonResourceRules is the list of actions the subject is allowed to perform on non-resources. + // nonResourceRules is the list of actions the subject is allowed to perform on non-resources. // The list ordering isn't significant, may contain duplicates, and possibly be incomplete. // +listType=atomic NonResourceRules []NonResourceRule `json:"nonResourceRules" protobuf:"bytes,2,rep,name=nonResourceRules"` - // Incomplete is true when the rules returned by this call are incomplete. This is most commonly + // incomplete is true when the rules returned by this call are incomplete. This is most commonly // encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation. Incomplete bool `json:"incomplete" protobuf:"bytes,3,rep,name=incomplete"` - // EvaluationError can appear in combination with Rules. It indicates an error occurred during + // evaluationError can appear in combination with Rules. It indicates an error occurred during // rule evaluation, such as an authorizer that doesn't support rule evaluation, and that // ResourceRules and/or NonResourceRules may be incomplete. // +optional @@ -271,21 +271,21 @@ type SubjectRulesReviewStatus struct { // ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, // may contain duplicates, and possibly be incomplete. type ResourceRule struct { - // Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. + // verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all. // +listType=atomic Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` - // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of // the enumerated resources in any API group will be allowed. "*" means all. // +optional // +listType=atomic APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"` - // Resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. + // resources is a list of resources this rule applies to. "*" means all in the specified apiGroups. // "*/foo" represents the subresource 'foo' for all resources in the specified apiGroups. // +optional // +listType=atomic Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"` - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. // +optional // +listType=atomic ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"` @@ -293,11 +293,11 @@ type ResourceRule struct { // NonResourceRule holds information that describes a rule for the non-resource type NonResourceRule struct { - // Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. + // verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all. // +listType=atomic Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` - // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, + // nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, // final step in the path. "*" means all. // +optional // +listType=atomic diff --git a/staging/src/k8s.io/api/authorization/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/authorization/v1beta1/types_swagger_doc_generated.go index bb1352a2d9d39..5667187c9dabe 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/types_swagger_doc_generated.go @@ -29,9 +29,9 @@ package v1beta1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_LocalSubjectAccessReview = map[string]string{ "": "LocalSubjectAccessReview checks whether or not a user or group can perform an action in a given namespace. Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions checking.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", - "status": "Status is filled in by the server and indicates whether the request is allowed or not", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted.", + "status": "status is filled in by the server and indicates whether the request is allowed or not", } func (LocalSubjectAccessReview) SwaggerDoc() map[string]string { @@ -40,8 +40,8 @@ func (LocalSubjectAccessReview) SwaggerDoc() map[string]string { var map_NonResourceAttributes = map[string]string{ "": "NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface", - "path": "Path is the URL path of the request", - "verb": "Verb is the standard HTTP verb", + "path": "path is the URL path of the request", + "verb": "verb is the standard HTTP verb", } func (NonResourceAttributes) SwaggerDoc() map[string]string { @@ -50,8 +50,8 @@ func (NonResourceAttributes) SwaggerDoc() map[string]string { var map_NonResourceRule = map[string]string{ "": "NonResourceRule holds information that describes a rule for the non-resource", - "verbs": "Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", - "nonResourceURLs": "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", + "verbs": "verbs is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. \"*\" means all.", + "nonResourceURLs": "nonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path. \"*\" means all.", } func (NonResourceRule) SwaggerDoc() map[string]string { @@ -60,13 +60,13 @@ func (NonResourceRule) SwaggerDoc() map[string]string { var map_ResourceAttributes = map[string]string{ "": "ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface", - "namespace": "Namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", - "verb": "Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", - "group": "Group is the API Group of the Resource. \"*\" means all.", - "version": "Version is the API Version of the Resource. \"*\" means all.", - "resource": "Resource is one of the existing resource types. \"*\" means all.", - "subresource": "Subresource is one of the existing resource types. \"\" means none.", - "name": "Name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", + "namespace": "namespace is the namespace of the action being requested. Currently, there is no distinction between no namespace and all namespaces \"\" (empty) is defaulted for LocalSubjectAccessReviews \"\" (empty) is empty for cluster-scoped resources \"\" (empty) means \"all\" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview", + "verb": "verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "group": "group is the API Group of the Resource. \"*\" means all.", + "version": "version is the API Version of the Resource. \"*\" means all.", + "resource": "resource is one of the existing resource types. \"*\" means all.", + "subresource": "subresource is one of the existing resource types. \"\" means none.", + "name": "name is the name of the resource being requested for a \"get\" or deleted for a \"delete\". \"\" (empty) means all.", "fieldSelector": "fieldSelector describes the limitation on access based on field. It can only limit access, not broaden it.", "labelSelector": "labelSelector describes the limitation on access based on labels. It can only limit access, not broaden it.", } @@ -77,10 +77,10 @@ func (ResourceAttributes) SwaggerDoc() map[string]string { var map_ResourceRule = map[string]string{ "": "ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", - "verbs": "Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", - "apiGroups": "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", - "resources": "Resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", - "resourceNames": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", + "verbs": "verbs is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. \"*\" means all.", + "apiGroups": "apiGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. \"*\" means all.", + "resources": "resources is a list of resources this rule applies to. \"*\" means all in the specified apiGroups.\n \"*/foo\" represents the subresource 'foo' for all resources in the specified apiGroups.", + "resourceNames": "resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. \"*\" means all.", } func (ResourceRule) SwaggerDoc() map[string]string { @@ -89,9 +89,9 @@ func (ResourceRule) SwaggerDoc() map[string]string { var map_SelfSubjectAccessReview = map[string]string{ "": "SelfSubjectAccessReview checks whether or the current user can perform an action. Not filling in a spec.namespace means \"in all namespaces\". Self is a special case, because users should always be able to check whether they can perform an action", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated. user and groups must be empty", - "status": "Status is filled in by the server and indicates whether the request is allowed or not", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated. user and groups must be empty", + "status": "status is filled in by the server and indicates whether the request is allowed or not", } func (SelfSubjectAccessReview) SwaggerDoc() map[string]string { @@ -99,9 +99,9 @@ func (SelfSubjectAccessReview) SwaggerDoc() map[string]string { } var map_SelfSubjectAccessReviewSpec = map[string]string{ - "": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", - "resourceAttributes": "ResourceAuthorizationAttributes describes information for a resource access request", - "nonResourceAttributes": "NonResourceAttributes describes information for a non-resource access request", + "": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", + "resourceAttributes": "resourceAttributes describes information for a resource access request", + "nonResourceAttributes": "nonResourceAttributes describes information for a non-resource access request", } func (SelfSubjectAccessReviewSpec) SwaggerDoc() map[string]string { @@ -110,9 +110,9 @@ func (SelfSubjectAccessReviewSpec) SwaggerDoc() map[string]string { var map_SelfSubjectRulesReview = map[string]string{ "": "SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace. The returned list of actions may be incomplete depending on the server's authorization mode, and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions, or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns. SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated.", - "status": "Status is filled in by the server and indicates the set of actions a user can perform.", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated.", + "status": "status is filled in by the server and indicates the set of actions a user can perform.", } func (SelfSubjectRulesReview) SwaggerDoc() map[string]string { @@ -121,7 +121,7 @@ func (SelfSubjectRulesReview) SwaggerDoc() map[string]string { var map_SelfSubjectRulesReviewSpec = map[string]string{ "": "SelfSubjectRulesReviewSpec defines the specification for SelfSubjectRulesReview.", - "namespace": "Namespace to evaluate rules for. Required.", + "namespace": "namespace to evaluate rules for. Required.", } func (SelfSubjectRulesReviewSpec) SwaggerDoc() map[string]string { @@ -130,9 +130,9 @@ func (SelfSubjectRulesReviewSpec) SwaggerDoc() map[string]string { var map_SubjectAccessReview = map[string]string{ "": "SubjectAccessReview checks whether or not a user or group can perform an action.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "Spec holds information about the request being evaluated", - "status": "Status is filled in by the server and indicates whether the request is allowed or not", + "metadata": "metadata is the standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec holds information about the request being evaluated", + "status": "status is filled in by the server and indicates whether the request is allowed or not", } func (SubjectAccessReview) SwaggerDoc() map[string]string { @@ -140,13 +140,13 @@ func (SubjectAccessReview) SwaggerDoc() map[string]string { } var map_SubjectAccessReviewSpec = map[string]string{ - "": "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", - "resourceAttributes": "ResourceAuthorizationAttributes describes information for a resource access request", - "nonResourceAttributes": "NonResourceAttributes describes information for a non-resource access request", - "user": "User is the user you're testing for. If you specify \"User\" but not \"Group\", then is it interpreted as \"What if User were not a member of any groups", - "group": "Groups is the groups you're testing for.", - "extra": "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", - "uid": "UID information about the requesting user.", + "": "SubjectAccessReviewSpec is a description of the access request. Exactly one of resourceAttributes and nonResourceAttributes must be set", + "resourceAttributes": "resourceAttributes describes information for a resource access request", + "nonResourceAttributes": "nonResourceAttributes describes information for a non-resource access request", + "user": "user is the user you're testing for. If you specify \"User\" but not \"Group\", then is it interpreted as \"What if User were not a member of any groups", + "group": "group is the groups you're testing for.", + "extra": "extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here.", + "uid": "uid information about the requesting user.", } func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string { @@ -155,10 +155,10 @@ func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string { var map_SubjectAccessReviewStatus = map[string]string{ "": "SubjectAccessReviewStatus", - "allowed": "Allowed is required. True if the action would be allowed, false otherwise.", - "denied": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", - "reason": "Reason is optional. It indicates why a request was allowed or denied.", - "evaluationError": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", + "allowed": "allowed is required. True if the action would be allowed, false otherwise.", + "denied": "denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.", + "reason": "reason is optional. It indicates why a request was allowed or denied.", + "evaluationError": "evaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", } func (SubjectAccessReviewStatus) SwaggerDoc() map[string]string { @@ -167,10 +167,10 @@ func (SubjectAccessReviewStatus) SwaggerDoc() map[string]string { var map_SubjectRulesReviewStatus = map[string]string{ "": "SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on the set of authorizers the server is configured with and any errors experienced during evaluation. Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission, even if that list is incomplete.", - "resourceRules": "ResourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", - "nonResourceRules": "NonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", - "incomplete": "Incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", - "evaluationError": "EvaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", + "resourceRules": "resourceRules is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "nonResourceRules": "nonResourceRules is the list of actions the subject is allowed to perform on non-resources. The list ordering isn't significant, may contain duplicates, and possibly be incomplete.", + "incomplete": "incomplete is true when the rules returned by this call are incomplete. This is most commonly encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.", + "evaluationError": "evaluationError can appear in combination with Rules. It indicates an error occurred during rule evaluation, such as an authorizer that doesn't support rule evaluation, and that ResourceRules and/or NonResourceRules may be incomplete.", } func (SubjectRulesReviewStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/autoscaling/v1/generated.proto b/staging/src/k8s.io/api/autoscaling/v1/generated.proto index a17d7989db520..6ef1facb6ae30 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/generated.proto +++ b/staging/src/k8s.io/api/autoscaling/v1/generated.proto @@ -148,7 +148,7 @@ message HorizontalPodAutoscaler { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // spec defines the behaviour of autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional + // +required optional HorizontalPodAutoscalerSpec spec = 2; // status is the current information about the autoscaler. @@ -202,9 +202,15 @@ message HorizontalPodAutoscalerSpec { // metric is configured. Scaling is active as long as at least one metric value is // available. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1 optional int32 minReplicas = 2; // maxReplicas is the upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:minimum=1 optional int32 maxReplicas = 3; // targetCPUUtilizationPercentage is the target average CPU utilization (represented as a percentage of requested CPU) over all the pods; @@ -472,9 +478,9 @@ message Scale { message ScaleSpec { // replicas is the desired number of instances for the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 optional int32 replicas = 1; } diff --git a/staging/src/k8s.io/api/autoscaling/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/autoscaling/v1/generated.protomessage.pb.go deleted file mode 100644 index 7b073f92c3ea8..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*ContainerResourceMetricSource) ProtoMessage() {} - -func (*ContainerResourceMetricStatus) ProtoMessage() {} - -func (*CrossVersionObjectReference) ProtoMessage() {} - -func (*ExternalMetricSource) ProtoMessage() {} - -func (*ExternalMetricStatus) ProtoMessage() {} - -func (*HorizontalPodAutoscaler) ProtoMessage() {} - -func (*HorizontalPodAutoscalerCondition) ProtoMessage() {} - -func (*HorizontalPodAutoscalerList) ProtoMessage() {} - -func (*HorizontalPodAutoscalerSpec) ProtoMessage() {} - -func (*HorizontalPodAutoscalerStatus) ProtoMessage() {} - -func (*MetricSpec) ProtoMessage() {} - -func (*MetricStatus) ProtoMessage() {} - -func (*ObjectMetricSource) ProtoMessage() {} - -func (*ObjectMetricStatus) ProtoMessage() {} - -func (*PodsMetricSource) ProtoMessage() {} - -func (*PodsMetricStatus) ProtoMessage() {} - -func (*ResourceMetricSource) ProtoMessage() {} - -func (*ResourceMetricStatus) ProtoMessage() {} - -func (*Scale) ProtoMessage() {} - -func (*ScaleSpec) ProtoMessage() {} - -func (*ScaleStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/autoscaling/v1/types.go b/staging/src/k8s.io/api/autoscaling/v1/types.go index e1e8809fe9ffa..97222afc0802a 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/types.go +++ b/staging/src/k8s.io/api/autoscaling/v1/types.go @@ -47,9 +47,15 @@ type HorizontalPodAutoscalerSpec struct { // metric is configured. Scaling is active as long as at least one metric value is // available. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1 MinReplicas *int32 `json:"minReplicas,omitempty" protobuf:"varint,2,opt,name=minReplicas"` // maxReplicas is the upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:minimum=1 MaxReplicas int32 `json:"maxReplicas" protobuf:"varint,3,opt,name=maxReplicas"` // targetCPUUtilizationPercentage is the target average CPU utilization (represented as a percentage of requested CPU) over all the pods; @@ -93,7 +99,7 @@ type HorizontalPodAutoscaler struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // spec defines the behaviour of autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional + // +required Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // status is the current information about the autoscaler. @@ -117,7 +123,7 @@ type HorizontalPodAutoscalerList struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.2 -// +k8s:isSubresource=/scale +// +k8s:isSubresource="/scale" // Scale represents a scaling request for a resource. type Scale struct { @@ -139,9 +145,9 @@ type Scale struct { type ScaleSpec struct { // replicas is the desired number of instances for the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 Replicas int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` } @@ -408,6 +414,8 @@ const ( // ScalingLimited indicates that the calculated scale based on metrics would be above or // below the range for the HPA, and has thus been capped. ScalingLimited HorizontalPodAutoscalerConditionType = "ScalingLimited" + // ScaledToZero indicates that the HPA controller scaled the workload to zero. + ScaledToZero HorizontalPodAutoscalerConditionType = "ScaledToZero" ) // HorizontalPodAutoscalerCondition describes the state of diff --git a/staging/src/k8s.io/api/autoscaling/v2/generated.proto b/staging/src/k8s.io/api/autoscaling/v2/generated.proto index a007676a7d541..f56b8c749d589 100644 --- a/staging/src/k8s.io/api/autoscaling/v2/generated.proto +++ b/staging/src/k8s.io/api/autoscaling/v2/generated.proto @@ -175,7 +175,7 @@ message HorizontalPodAutoscaler { // spec is the specification for the behaviour of the autoscaler. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional + // +required optional HorizontalPodAutoscalerSpec spec = 2; // status is the current information about the autoscaler. @@ -248,10 +248,16 @@ message HorizontalPodAutoscalerSpec { // metric is configured. Scaling is active as long as at least one metric value is // available. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1 optional int32 minReplicas = 2; // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. // It cannot be less that minReplicas. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:minimum=1 optional int32 maxReplicas = 3; // metrics contains the specifications for which to use to calculate the diff --git a/staging/src/k8s.io/api/autoscaling/v2/generated.protomessage.pb.go b/staging/src/k8s.io/api/autoscaling/v2/generated.protomessage.pb.go deleted file mode 100644 index 0a73d526bc297..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2/generated.protomessage.pb.go +++ /dev/null @@ -1,70 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v2 - -func (*ContainerResourceMetricSource) ProtoMessage() {} - -func (*ContainerResourceMetricStatus) ProtoMessage() {} - -func (*CrossVersionObjectReference) ProtoMessage() {} - -func (*ExternalMetricSource) ProtoMessage() {} - -func (*ExternalMetricStatus) ProtoMessage() {} - -func (*HPAScalingPolicy) ProtoMessage() {} - -func (*HPAScalingRules) ProtoMessage() {} - -func (*HorizontalPodAutoscaler) ProtoMessage() {} - -func (*HorizontalPodAutoscalerBehavior) ProtoMessage() {} - -func (*HorizontalPodAutoscalerCondition) ProtoMessage() {} - -func (*HorizontalPodAutoscalerList) ProtoMessage() {} - -func (*HorizontalPodAutoscalerSpec) ProtoMessage() {} - -func (*HorizontalPodAutoscalerStatus) ProtoMessage() {} - -func (*MetricIdentifier) ProtoMessage() {} - -func (*MetricSpec) ProtoMessage() {} - -func (*MetricStatus) ProtoMessage() {} - -func (*MetricTarget) ProtoMessage() {} - -func (*MetricValueStatus) ProtoMessage() {} - -func (*ObjectMetricSource) ProtoMessage() {} - -func (*ObjectMetricStatus) ProtoMessage() {} - -func (*PodsMetricSource) ProtoMessage() {} - -func (*PodsMetricStatus) ProtoMessage() {} - -func (*ResourceMetricSource) ProtoMessage() {} - -func (*ResourceMetricStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/autoscaling/v2/types.go b/staging/src/k8s.io/api/autoscaling/v2/types.go index 03a06dc8b41f0..ad10169a01c9b 100644 --- a/staging/src/k8s.io/api/autoscaling/v2/types.go +++ b/staging/src/k8s.io/api/autoscaling/v2/types.go @@ -40,7 +40,7 @@ type HorizontalPodAutoscaler struct { // spec is the specification for the behaviour of the autoscaler. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional + // +required Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // status is the current information about the autoscaler. @@ -59,10 +59,16 @@ type HorizontalPodAutoscalerSpec struct { // metric is configured. Scaling is active as long as at least one metric value is // available. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1 MinReplicas *int32 `json:"minReplicas,omitempty" protobuf:"varint,2,opt,name=minReplicas"` // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. // It cannot be less that minReplicas. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:minimum=1 MaxReplicas int32 `json:"maxReplicas" protobuf:"varint,3,opt,name=maxReplicas"` // metrics contains the specifications for which to use to calculate the @@ -444,6 +450,8 @@ const ( // ScalingLimited indicates that the calculated scale based on metrics would be above or // below the range for the HPA, and has thus been capped. ScalingLimited HorizontalPodAutoscalerConditionType = "ScalingLimited" + // ScaledToZero indicates that the HPA controller scaled the workload to zero. + ScaledToZero HorizontalPodAutoscalerConditionType = "ScaledToZero" ) // HorizontalPodAutoscalerCondition describes the state of diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go b/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go deleted file mode 100644 index 58cc1f89dc7ca..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2016 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. -*/ - -// +k8s:deepcopy-gen=package -// +k8s:protobuf-gen=package -// +k8s:openapi-gen=true -// +k8s:prerelease-lifecycle-gen=true -// +k8s:openapi-model-package=io.k8s.api.autoscaling.v2beta1 - -package v2beta1 diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.pb.go b/staging/src/k8s.io/api/autoscaling/v2beta1/generated.pb.go deleted file mode 100644 index b3b535adc718b..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.pb.go +++ /dev/null @@ -1,5106 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: k8s.io/api/autoscaling/v2beta1/generated.proto - -package v2beta1 - -import ( - fmt "fmt" - - io "io" - - k8s_io_api_core_v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - math_bits "math/bits" - reflect "reflect" - strings "strings" -) - -func (m *ContainerResourceMetricSource) Reset() { *m = ContainerResourceMetricSource{} } - -func (m *ContainerResourceMetricStatus) Reset() { *m = ContainerResourceMetricStatus{} } - -func (m *CrossVersionObjectReference) Reset() { *m = CrossVersionObjectReference{} } - -func (m *ExternalMetricSource) Reset() { *m = ExternalMetricSource{} } - -func (m *ExternalMetricStatus) Reset() { *m = ExternalMetricStatus{} } - -func (m *HorizontalPodAutoscaler) Reset() { *m = HorizontalPodAutoscaler{} } - -func (m *HorizontalPodAutoscalerCondition) Reset() { *m = HorizontalPodAutoscalerCondition{} } - -func (m *HorizontalPodAutoscalerList) Reset() { *m = HorizontalPodAutoscalerList{} } - -func (m *HorizontalPodAutoscalerSpec) Reset() { *m = HorizontalPodAutoscalerSpec{} } - -func (m *HorizontalPodAutoscalerStatus) Reset() { *m = HorizontalPodAutoscalerStatus{} } - -func (m *MetricSpec) Reset() { *m = MetricSpec{} } - -func (m *MetricStatus) Reset() { *m = MetricStatus{} } - -func (m *ObjectMetricSource) Reset() { *m = ObjectMetricSource{} } - -func (m *ObjectMetricStatus) Reset() { *m = ObjectMetricStatus{} } - -func (m *PodsMetricSource) Reset() { *m = PodsMetricSource{} } - -func (m *PodsMetricStatus) Reset() { *m = PodsMetricStatus{} } - -func (m *ResourceMetricSource) Reset() { *m = ResourceMetricSource{} } - -func (m *ResourceMetricStatus) Reset() { *m = ResourceMetricStatus{} } - -func (m *ContainerResourceMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ContainerResourceMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ContainerResourceMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Container) - copy(dAtA[i:], m.Container) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Container))) - i-- - dAtA[i] = 0x22 - if m.TargetAverageValue != nil { - { - size, err := m.TargetAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.TargetAverageUtilization != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.TargetAverageUtilization)) - i-- - dAtA[i] = 0x10 - } - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ContainerResourceMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ContainerResourceMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ContainerResourceMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Container) - copy(dAtA[i:], m.Container) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Container))) - i-- - dAtA[i] = 0x22 - { - size, err := m.CurrentAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - if m.CurrentAverageUtilization != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.CurrentAverageUtilization)) - i-- - dAtA[i] = 0x10 - } - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *CrossVersionObjectReference) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *CrossVersionObjectReference) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *CrossVersionObjectReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.APIVersion) - copy(dAtA[i:], m.APIVersion) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) - i-- - dAtA[i] = 0x1a - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x12 - i -= len(m.Kind) - copy(dAtA[i:], m.Kind) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ExternalMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ExternalMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.TargetAverageValue != nil { - { - size, err := m.TargetAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if m.TargetValue != nil { - { - size, err := m.TargetValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.MetricSelector != nil { - { - size, err := m.MetricSelector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.MetricName) - copy(dAtA[i:], m.MetricName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MetricName))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ExternalMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ExternalMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.CurrentAverageValue != nil { - { - size, err := m.CurrentAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - { - size, err := m.CurrentValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - if m.MetricSelector != nil { - { - size, err := m.MetricSelector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.MetricName) - copy(dAtA[i:], m.MetricName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MetricName))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscaler) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscaler) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscaler) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerCondition) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerCondition) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Message) - copy(dAtA[i:], m.Message) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) - i-- - dAtA[i] = 0x2a - i -= len(m.Reason) - copy(dAtA[i:], m.Reason) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) - i-- - dAtA[i] = 0x22 - { - size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - i -= len(m.Status) - copy(dAtA[i:], m.Status) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) - i-- - dAtA[i] = 0x12 - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerList) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerList) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerSpec) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Metrics) > 0 { - for iNdEx := len(m.Metrics) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Metrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - } - i = encodeVarintGenerated(dAtA, i, uint64(m.MaxReplicas)) - i-- - dAtA[i] = 0x18 - if m.MinReplicas != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.MinReplicas)) - i-- - dAtA[i] = 0x10 - } - { - size, err := m.ScaleTargetRef.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Conditions) > 0 { - for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x32 - } - } - if len(m.CurrentMetrics) > 0 { - for iNdEx := len(m.CurrentMetrics) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.CurrentMetrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - } - i = encodeVarintGenerated(dAtA, i, uint64(m.DesiredReplicas)) - i-- - dAtA[i] = 0x20 - i = encodeVarintGenerated(dAtA, i, uint64(m.CurrentReplicas)) - i-- - dAtA[i] = 0x18 - if m.LastScaleTime != nil { - { - size, err := m.LastScaleTime.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if m.ObservedGeneration != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.ObservedGeneration)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - -func (m *MetricSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricSpec) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.ContainerResource != nil { - { - size, err := m.ContainerResource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } - if m.External != nil { - { - size, err := m.External.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Resource != nil { - { - size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if m.Pods != nil { - { - size, err := m.Pods.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.Object != nil { - { - size, err := m.Object.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *MetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.ContainerResource != nil { - { - size, err := m.ContainerResource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } - if m.External != nil { - { - size, err := m.External.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Resource != nil { - { - size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if m.Pods != nil { - { - size, err := m.Pods.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.Object != nil { - { - size, err := m.Object.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ObjectMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ObjectMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ObjectMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.AverageValue != nil { - { - size, err := m.AverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Selector != nil { - { - size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - { - size, err := m.TargetValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - i -= len(m.MetricName) - copy(dAtA[i:], m.MetricName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MetricName))) - i-- - dAtA[i] = 0x12 - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ObjectMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ObjectMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ObjectMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.AverageValue != nil { - { - size, err := m.AverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Selector != nil { - { - size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - { - size, err := m.CurrentValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - i -= len(m.MetricName) - copy(dAtA[i:], m.MetricName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MetricName))) - i-- - dAtA[i] = 0x12 - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *PodsMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PodsMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PodsMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Selector != nil { - { - size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - { - size, err := m.TargetAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - i -= len(m.MetricName) - copy(dAtA[i:], m.MetricName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MetricName))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *PodsMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PodsMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PodsMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Selector != nil { - { - size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - { - size, err := m.CurrentAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - i -= len(m.MetricName) - copy(dAtA[i:], m.MetricName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.MetricName))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ResourceMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ResourceMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ResourceMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.TargetAverageValue != nil { - { - size, err := m.TargetAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.TargetAverageUtilization != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.TargetAverageUtilization)) - i-- - dAtA[i] = 0x10 - } - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ResourceMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ResourceMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ResourceMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.CurrentAverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - if m.CurrentAverageUtilization != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.CurrentAverageUtilization)) - i-- - dAtA[i] = 0x10 - } - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - offset -= sovGenerated(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *ContainerResourceMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.TargetAverageUtilization != nil { - n += 1 + sovGenerated(uint64(*m.TargetAverageUtilization)) - } - if m.TargetAverageValue != nil { - l = m.TargetAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - l = len(m.Container) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ContainerResourceMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.CurrentAverageUtilization != nil { - n += 1 + sovGenerated(uint64(*m.CurrentAverageUtilization)) - } - l = m.CurrentAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Container) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *CrossVersionObjectReference) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Kind) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.APIVersion) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ExternalMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.MetricName) - n += 1 + l + sovGenerated(uint64(l)) - if m.MetricSelector != nil { - l = m.MetricSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.TargetValue != nil { - l = m.TargetValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.TargetAverageValue != nil { - l = m.TargetAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ExternalMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.MetricName) - n += 1 + l + sovGenerated(uint64(l)) - if m.MetricSelector != nil { - l = m.MetricSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - l = m.CurrentValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.CurrentAverageValue != nil { - l = m.CurrentAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *HorizontalPodAutoscaler) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Spec.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Status.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *HorizontalPodAutoscalerCondition) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Status) - n += 1 + l + sovGenerated(uint64(l)) - l = m.LastTransitionTime.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Reason) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Message) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *HorizontalPodAutoscalerList) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *HorizontalPodAutoscalerSpec) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ScaleTargetRef.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.MinReplicas != nil { - n += 1 + sovGenerated(uint64(*m.MinReplicas)) - } - n += 1 + sovGenerated(uint64(m.MaxReplicas)) - if len(m.Metrics) > 0 { - for _, e := range m.Metrics { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *HorizontalPodAutoscalerStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ObservedGeneration != nil { - n += 1 + sovGenerated(uint64(*m.ObservedGeneration)) - } - if m.LastScaleTime != nil { - l = m.LastScaleTime.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - n += 1 + sovGenerated(uint64(m.CurrentReplicas)) - n += 1 + sovGenerated(uint64(m.DesiredReplicas)) - if len(m.CurrentMetrics) > 0 { - for _, e := range m.CurrentMetrics { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - if len(m.Conditions) > 0 { - for _, e := range m.Conditions { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *MetricSpec) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - if m.Object != nil { - l = m.Object.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Pods != nil { - l = m.Pods.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Resource != nil { - l = m.Resource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.External != nil { - l = m.External.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ContainerResource != nil { - l = m.ContainerResource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *MetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - if m.Object != nil { - l = m.Object.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Pods != nil { - l = m.Pods.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Resource != nil { - l = m.Resource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.External != nil { - l = m.External.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ContainerResource != nil { - l = m.ContainerResource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ObjectMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.MetricName) - n += 1 + l + sovGenerated(uint64(l)) - l = m.TargetValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.Selector != nil { - l = m.Selector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.AverageValue != nil { - l = m.AverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ObjectMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.MetricName) - n += 1 + l + sovGenerated(uint64(l)) - l = m.CurrentValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.Selector != nil { - l = m.Selector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.AverageValue != nil { - l = m.AverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *PodsMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.MetricName) - n += 1 + l + sovGenerated(uint64(l)) - l = m.TargetAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.Selector != nil { - l = m.Selector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *PodsMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.MetricName) - n += 1 + l + sovGenerated(uint64(l)) - l = m.CurrentAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.Selector != nil { - l = m.Selector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ResourceMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.TargetAverageUtilization != nil { - n += 1 + sovGenerated(uint64(*m.TargetAverageUtilization)) - } - if m.TargetAverageValue != nil { - l = m.TargetAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ResourceMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.CurrentAverageUtilization != nil { - n += 1 + sovGenerated(uint64(*m.CurrentAverageUtilization)) - } - l = m.CurrentAverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *ContainerResourceMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ContainerResourceMetricSource{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `TargetAverageUtilization:` + valueToStringGenerated(this.TargetAverageUtilization) + `,`, - `TargetAverageValue:` + strings.Replace(fmt.Sprintf("%v", this.TargetAverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `Container:` + fmt.Sprintf("%v", this.Container) + `,`, - `}`, - }, "") - return s -} -func (this *ContainerResourceMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ContainerResourceMetricStatus{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `CurrentAverageUtilization:` + valueToStringGenerated(this.CurrentAverageUtilization) + `,`, - `CurrentAverageValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CurrentAverageValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `Container:` + fmt.Sprintf("%v", this.Container) + `,`, - `}`, - }, "") - return s -} -func (this *CrossVersionObjectReference) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&CrossVersionObjectReference{`, - `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalMetricSource{`, - `MetricName:` + fmt.Sprintf("%v", this.MetricName) + `,`, - `MetricSelector:` + strings.Replace(fmt.Sprintf("%v", this.MetricSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `TargetValue:` + strings.Replace(fmt.Sprintf("%v", this.TargetValue), "Quantity", "resource.Quantity", 1) + `,`, - `TargetAverageValue:` + strings.Replace(fmt.Sprintf("%v", this.TargetAverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalMetricStatus{`, - `MetricName:` + fmt.Sprintf("%v", this.MetricName) + `,`, - `MetricSelector:` + strings.Replace(fmt.Sprintf("%v", this.MetricSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `CurrentValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CurrentValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `CurrentAverageValue:` + strings.Replace(fmt.Sprintf("%v", this.CurrentAverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscaler) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&HorizontalPodAutoscaler{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "HorizontalPodAutoscalerSpec", "HorizontalPodAutoscalerSpec", 1), `&`, ``, 1) + `,`, - `Status:` + strings.Replace(strings.Replace(this.Status.String(), "HorizontalPodAutoscalerStatus", "HorizontalPodAutoscalerStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerCondition) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&HorizontalPodAutoscalerCondition{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Status:` + fmt.Sprintf("%v", this.Status) + `,`, - `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, - `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, - `Message:` + fmt.Sprintf("%v", this.Message) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerList) String() string { - if this == nil { - return "nil" - } - repeatedStringForItems := "[]HorizontalPodAutoscaler{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "HorizontalPodAutoscaler", "HorizontalPodAutoscaler", 1), `&`, ``, 1) + "," - } - repeatedStringForItems += "}" - s := strings.Join([]string{`&HorizontalPodAutoscalerList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerSpec) String() string { - if this == nil { - return "nil" - } - repeatedStringForMetrics := "[]MetricSpec{" - for _, f := range this.Metrics { - repeatedStringForMetrics += strings.Replace(strings.Replace(f.String(), "MetricSpec", "MetricSpec", 1), `&`, ``, 1) + "," - } - repeatedStringForMetrics += "}" - s := strings.Join([]string{`&HorizontalPodAutoscalerSpec{`, - `ScaleTargetRef:` + strings.Replace(strings.Replace(this.ScaleTargetRef.String(), "CrossVersionObjectReference", "CrossVersionObjectReference", 1), `&`, ``, 1) + `,`, - `MinReplicas:` + valueToStringGenerated(this.MinReplicas) + `,`, - `MaxReplicas:` + fmt.Sprintf("%v", this.MaxReplicas) + `,`, - `Metrics:` + repeatedStringForMetrics + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerStatus) String() string { - if this == nil { - return "nil" - } - repeatedStringForCurrentMetrics := "[]MetricStatus{" - for _, f := range this.CurrentMetrics { - repeatedStringForCurrentMetrics += strings.Replace(strings.Replace(f.String(), "MetricStatus", "MetricStatus", 1), `&`, ``, 1) + "," - } - repeatedStringForCurrentMetrics += "}" - repeatedStringForConditions := "[]HorizontalPodAutoscalerCondition{" - for _, f := range this.Conditions { - repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "HorizontalPodAutoscalerCondition", "HorizontalPodAutoscalerCondition", 1), `&`, ``, 1) + "," - } - repeatedStringForConditions += "}" - s := strings.Join([]string{`&HorizontalPodAutoscalerStatus{`, - `ObservedGeneration:` + valueToStringGenerated(this.ObservedGeneration) + `,`, - `LastScaleTime:` + strings.Replace(fmt.Sprintf("%v", this.LastScaleTime), "Time", "v1.Time", 1) + `,`, - `CurrentReplicas:` + fmt.Sprintf("%v", this.CurrentReplicas) + `,`, - `DesiredReplicas:` + fmt.Sprintf("%v", this.DesiredReplicas) + `,`, - `CurrentMetrics:` + repeatedStringForCurrentMetrics + `,`, - `Conditions:` + repeatedStringForConditions + `,`, - `}`, - }, "") - return s -} -func (this *MetricSpec) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricSpec{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Object:` + strings.Replace(this.Object.String(), "ObjectMetricSource", "ObjectMetricSource", 1) + `,`, - `Pods:` + strings.Replace(this.Pods.String(), "PodsMetricSource", "PodsMetricSource", 1) + `,`, - `Resource:` + strings.Replace(this.Resource.String(), "ResourceMetricSource", "ResourceMetricSource", 1) + `,`, - `External:` + strings.Replace(this.External.String(), "ExternalMetricSource", "ExternalMetricSource", 1) + `,`, - `ContainerResource:` + strings.Replace(this.ContainerResource.String(), "ContainerResourceMetricSource", "ContainerResourceMetricSource", 1) + `,`, - `}`, - }, "") - return s -} -func (this *MetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricStatus{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Object:` + strings.Replace(this.Object.String(), "ObjectMetricStatus", "ObjectMetricStatus", 1) + `,`, - `Pods:` + strings.Replace(this.Pods.String(), "PodsMetricStatus", "PodsMetricStatus", 1) + `,`, - `Resource:` + strings.Replace(this.Resource.String(), "ResourceMetricStatus", "ResourceMetricStatus", 1) + `,`, - `External:` + strings.Replace(this.External.String(), "ExternalMetricStatus", "ExternalMetricStatus", 1) + `,`, - `ContainerResource:` + strings.Replace(this.ContainerResource.String(), "ContainerResourceMetricStatus", "ContainerResourceMetricStatus", 1) + `,`, - `}`, - }, "") - return s -} -func (this *ObjectMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ObjectMetricSource{`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "CrossVersionObjectReference", "CrossVersionObjectReference", 1), `&`, ``, 1) + `,`, - `MetricName:` + fmt.Sprintf("%v", this.MetricName) + `,`, - `TargetValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.TargetValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `AverageValue:` + strings.Replace(fmt.Sprintf("%v", this.AverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `}`, - }, "") - return s -} -func (this *ObjectMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ObjectMetricStatus{`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "CrossVersionObjectReference", "CrossVersionObjectReference", 1), `&`, ``, 1) + `,`, - `MetricName:` + fmt.Sprintf("%v", this.MetricName) + `,`, - `CurrentValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CurrentValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `AverageValue:` + strings.Replace(fmt.Sprintf("%v", this.AverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `}`, - }, "") - return s -} -func (this *PodsMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PodsMetricSource{`, - `MetricName:` + fmt.Sprintf("%v", this.MetricName) + `,`, - `TargetAverageValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.TargetAverageValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `}`, - }, "") - return s -} -func (this *PodsMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PodsMetricStatus{`, - `MetricName:` + fmt.Sprintf("%v", this.MetricName) + `,`, - `CurrentAverageValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CurrentAverageValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `}`, - }, "") - return s -} -func (this *ResourceMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ResourceMetricSource{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `TargetAverageUtilization:` + valueToStringGenerated(this.TargetAverageUtilization) + `,`, - `TargetAverageValue:` + strings.Replace(fmt.Sprintf("%v", this.TargetAverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `}`, - }, "") - return s -} -func (this *ResourceMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ResourceMetricStatus{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `CurrentAverageUtilization:` + valueToStringGenerated(this.CurrentAverageUtilization) + `,`, - `CurrentAverageValue:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CurrentAverageValue), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func valueToStringGenerated(v interface{}) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { - return "nil" - } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("*%v", pv) -} -func (m *ContainerResourceMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ContainerResourceMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ContainerResourceMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetAverageUtilization", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.TargetAverageUtilization = &v - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.TargetAverageValue == nil { - m.TargetAverageValue = &resource.Quantity{} - } - if err := m.TargetAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Container", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Container = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ContainerResourceMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ContainerResourceMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ContainerResourceMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentAverageUtilization", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.CurrentAverageUtilization = &v - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.CurrentAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Container", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Container = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *CrossVersionObjectReference) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: CrossVersionObjectReference: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: CrossVersionObjectReference: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Kind = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.APIVersion = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ExternalMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MetricName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricSelector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.MetricSelector == nil { - m.MetricSelector = &v1.LabelSelector{} - } - if err := m.MetricSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.TargetValue == nil { - m.TargetValue = &resource.Quantity{} - } - if err := m.TargetValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.TargetAverageValue == nil { - m.TargetAverageValue = &resource.Quantity{} - } - if err := m.TargetAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ExternalMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MetricName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricSelector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.MetricSelector == nil { - m.MetricSelector = &v1.LabelSelector{} - } - if err := m.MetricSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.CurrentValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.CurrentAverageValue == nil { - m.CurrentAverageValue = &resource.Quantity{} - } - if err := m.CurrentAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscaler) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscaler: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscaler: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerCondition) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerCondition: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerCondition: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = HorizontalPodAutoscalerConditionType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Status = k8s_io_api_core_v1.ConditionStatus(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Reason = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Message = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerList) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerList: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, HorizontalPodAutoscaler{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerSpec) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ScaleTargetRef", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ScaleTargetRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinReplicas", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.MinReplicas = &v - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxReplicas", wireType) - } - m.MaxReplicas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.MaxReplicas |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metrics", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Metrics = append(m.Metrics, MetricSpec{}) - if err := m.Metrics[len(m.Metrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) - } - var v int64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.ObservedGeneration = &v - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastScaleTime", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.LastScaleTime == nil { - m.LastScaleTime = &v1.Time{} - } - if err := m.LastScaleTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentReplicas", wireType) - } - m.CurrentReplicas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.CurrentReplicas |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DesiredReplicas", wireType) - } - m.DesiredReplicas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.DesiredReplicas |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentMetrics", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CurrentMetrics = append(m.CurrentMetrics, MetricStatus{}) - if err := m.CurrentMetrics[len(m.CurrentMetrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Conditions = append(m.Conditions, HorizontalPodAutoscalerCondition{}) - if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricSpec) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = MetricSourceType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Object", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Object == nil { - m.Object = &ObjectMetricSource{} - } - if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pods", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pods == nil { - m.Pods = &PodsMetricSource{} - } - if err := m.Pods.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Resource == nil { - m.Resource = &ResourceMetricSource{} - } - if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field External", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.External == nil { - m.External = &ExternalMetricSource{} - } - if err := m.External.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContainerResource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ContainerResource == nil { - m.ContainerResource = &ContainerResourceMetricSource{} - } - if err := m.ContainerResource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = MetricSourceType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Object", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Object == nil { - m.Object = &ObjectMetricStatus{} - } - if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pods", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pods == nil { - m.Pods = &PodsMetricStatus{} - } - if err := m.Pods.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Resource == nil { - m.Resource = &ResourceMetricStatus{} - } - if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field External", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.External == nil { - m.External = &ExternalMetricStatus{} - } - if err := m.External.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContainerResource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ContainerResource == nil { - m.ContainerResource = &ContainerResourceMetricStatus{} - } - if err := m.ContainerResource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ObjectMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ObjectMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ObjectMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MetricName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.TargetValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Selector == nil { - m.Selector = &v1.LabelSelector{} - } - if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AverageValue == nil { - m.AverageValue = &resource.Quantity{} - } - if err := m.AverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ObjectMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ObjectMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ObjectMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MetricName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.CurrentValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Selector == nil { - m.Selector = &v1.LabelSelector{} - } - if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AverageValue == nil { - m.AverageValue = &resource.Quantity{} - } - if err := m.AverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PodsMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PodsMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PodsMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MetricName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.TargetAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Selector == nil { - m.Selector = &v1.LabelSelector{} - } - if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PodsMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PodsMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PodsMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MetricName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MetricName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.CurrentAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Selector == nil { - m.Selector = &v1.LabelSelector{} - } - if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResourceMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResourceMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResourceMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetAverageUtilization", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.TargetAverageUtilization = &v - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.TargetAverageValue == nil { - m.TargetAverageValue = &resource.Quantity{} - } - if err := m.TargetAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResourceMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResourceMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResourceMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentAverageUtilization", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.CurrentAverageUtilization = &v - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentAverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.CurrentAverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipGenerated(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthGenerated - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupGenerated - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthGenerated - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") -) diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto b/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto deleted file mode 100644 index 4b71732ab9e44..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto +++ /dev/null @@ -1,474 +0,0 @@ -/* -Copyright 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. -*/ - - -// This file was autogenerated by go-to-protobuf. Do not edit it manually! - -syntax = "proto2"; - -package k8s.io.api.autoscaling.v2beta1; - -import "k8s.io/api/core/v1/generated.proto"; -import "k8s.io/apimachinery/pkg/api/resource/generated.proto"; -import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; -import "k8s.io/apimachinery/pkg/runtime/generated.proto"; -import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; - -// Package-wide variables from generator "generated". -option go_package = "k8s.io/api/autoscaling/v2beta1"; - -// ContainerResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -message ContainerResourceMetricSource { - // name is the name of the resource in question. - optional string name = 1; - - // targetAverageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // +optional - optional int32 targetAverageUtilization = 2; - - // targetAverageValue is the target value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity targetAverageValue = 3; - - // container is the name of the container in the pods of the scaling target - optional string container = 4; -} - -// ContainerResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing a single container in each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -message ContainerResourceMetricStatus { - // name is the name of the resource in question. - optional string name = 1; - - // currentAverageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. It will only be - // present if `targetAverageValue` was set in the corresponding metric - // specification. - // +optional - optional int32 currentAverageUtilization = 2; - - // currentAverageValue is the current value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // It will always be set, regardless of the corresponding metric specification. - optional .k8s.io.apimachinery.pkg.api.resource.Quantity currentAverageValue = 3; - - // container is the name of the container in the pods of the scaling target - optional string container = 4; -} - -// CrossVersionObjectReference contains enough information to let you identify the referred resource. -message CrossVersionObjectReference { - // Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - optional string kind = 1; - - // Name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - optional string name = 2; - - // API version of the referent - // +optional - optional string apiVersion = 3; -} - -// ExternalMetricSource indicates how to scale on a metric not associated with -// any Kubernetes object (for example length of queue in cloud -// messaging service, or QPS from loadbalancer running outside of cluster). -// Exactly one "target" type should be set. -message ExternalMetricSource { - // metricName is the name of the metric in question. - optional string metricName = 1; - - // metricSelector is used to identify a specific time series - // within a given metric. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector metricSelector = 2; - - // targetValue is the target value of the metric (as a quantity). - // Mutually exclusive with TargetAverageValue. - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity targetValue = 3; - - // targetAverageValue is the target per-pod value of global metric (as a quantity). - // Mutually exclusive with TargetValue. - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity targetAverageValue = 4; -} - -// ExternalMetricStatus indicates the current value of a global metric -// not associated with any Kubernetes object. -message ExternalMetricStatus { - // metricName is the name of a metric used for autoscaling in - // metric system. - optional string metricName = 1; - - // metricSelector is used to identify a specific time series - // within a given metric. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector metricSelector = 2; - - // currentValue is the current value of the metric (as a quantity) - optional .k8s.io.apimachinery.pkg.api.resource.Quantity currentValue = 3; - - // currentAverageValue is the current value of metric averaged over autoscaled pods. - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity currentAverageValue = 4; -} - -// HorizontalPodAutoscaler is the configuration for a horizontal pod -// autoscaler, which automatically manages the replica count of any resource -// implementing the scale subresource based on the metrics specified. -message HorizontalPodAutoscaler { - // metadata is the standard object metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // spec is the specification for the behaviour of the autoscaler. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional - optional HorizontalPodAutoscalerSpec spec = 2; - - // status is the current information about the autoscaler. - // +optional - optional HorizontalPodAutoscalerStatus status = 3; -} - -// HorizontalPodAutoscalerCondition describes the state of -// a HorizontalPodAutoscaler at a certain point. -message HorizontalPodAutoscalerCondition { - // type describes the current condition - optional string type = 1; - - // status is the status of the condition (True, False, Unknown) - optional string status = 2; - - // lastTransitionTime is the last time the condition transitioned from - // one status to another - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; - - // reason is the reason for the condition's last transition. - // +optional - optional string reason = 4; - - // message is a human-readable explanation containing details about - // the transition - // +optional - optional string message = 5; -} - -// HorizontalPodAutoscaler is a list of horizontal pod autoscaler objects. -message HorizontalPodAutoscalerList { - // metadata is the standard list metadata. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // items is the list of horizontal pod autoscaler objects. - repeated HorizontalPodAutoscaler items = 2; -} - -// HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. -message HorizontalPodAutoscalerSpec { - // scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - // should be collected, as well as to actually change the replica count. - optional CrossVersionObjectReference scaleTargetRef = 1; - - // minReplicas is the lower limit for the number of replicas to which the autoscaler - // can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - // alpha feature gate HPAScaleToZero is enabled and at least one Object or External - // metric is configured. Scaling is active as long as at least one metric value is - // available. - // +optional - optional int32 minReplicas = 2; - - // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - // It cannot be less that minReplicas. - optional int32 maxReplicas = 3; - - // metrics contains the specifications for which to use to calculate the - // desired replica count (the maximum replica count across all metrics will - // be used). The desired replica count is calculated multiplying the - // ratio between the target value and the current value by the current - // number of pods. Ergo, metrics used must decrease as the pod count is - // increased, and vice-versa. See the individual metric source types for - // more information about how each type of metric must respond. - // +optional - // +listType=atomic - repeated MetricSpec metrics = 4; -} - -// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler. -message HorizontalPodAutoscalerStatus { - // observedGeneration is the most recent generation observed by this autoscaler. - // +optional - optional int64 observedGeneration = 1; - - // lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, - // used by the autoscaler to control how often the number of pods is changed. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastScaleTime = 2; - - // currentReplicas is current number of replicas of pods managed by this autoscaler, - // as last seen by the autoscaler. - optional int32 currentReplicas = 3; - - // desiredReplicas is the desired number of replicas of pods managed by this autoscaler, - // as last calculated by the autoscaler. - optional int32 desiredReplicas = 4; - - // currentMetrics is the last read state of the metrics used by this autoscaler. - // +optional - // +listType=atomic - repeated MetricStatus currentMetrics = 5; - - // conditions is the set of conditions required for this autoscaler to scale its target, - // and indicates whether or not those conditions are met. - // +optional - // +listType=atomic - repeated HorizontalPodAutoscalerCondition conditions = 6; -} - -// MetricSpec specifies how to scale based on a single metric -// (only `type` and one other matching field should be set at once). -message MetricSpec { - // type is the type of metric source. It should be one of "ContainerResource", - // "External", "Object", "Pods" or "Resource", each mapping to a matching field in the object. - optional string type = 1; - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - optional ObjectMetricSource object = 2; - - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - optional PodsMetricSource pods = 3; - - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - optional ResourceMetricSource resource = 4; - - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in - // each pod of the current scale target (e.g. CPU or memory). Such metrics are - // built in to Kubernetes, and have special scaling options on top of those - // available to normal per-pod metrics using the "pods" source. - // +optional - optional ContainerResourceMetricSource containerResource = 7; - - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - optional ExternalMetricSource external = 5; -} - -// MetricStatus describes the last-read state of a single metric. -message MetricStatus { - // type is the type of metric source. It will be one of "ContainerResource", - // "External", "Object", "Pods" or "Resource", each corresponds to a matching field in the object. - optional string type = 1; - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - optional ObjectMetricStatus object = 2; - - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - optional PodsMetricStatus pods = 3; - - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - optional ResourceMetricStatus resource = 4; - - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - optional ContainerResourceMetricStatus containerResource = 7; - - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - optional ExternalMetricStatus external = 5; -} - -// ObjectMetricSource indicates how to scale on a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -message ObjectMetricSource { - // target is the described Kubernetes object. - optional CrossVersionObjectReference target = 1; - - // metricName is the name of the metric in question. - optional string metricName = 2; - - // targetValue is the target value of the metric (as a quantity). - optional .k8s.io.apimachinery.pkg.api.resource.Quantity targetValue = 3; - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping - // When unset, just the metricName will be used to gather metrics. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 4; - - // averageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity averageValue = 5; -} - -// ObjectMetricStatus indicates the current value of a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -message ObjectMetricStatus { - // target is the described Kubernetes object. - optional CrossVersionObjectReference target = 1; - - // metricName is the name of the metric in question. - optional string metricName = 2; - - // currentValue is the current value of the metric (as a quantity). - optional .k8s.io.apimachinery.pkg.api.resource.Quantity currentValue = 3; - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set in the ObjectMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 4; - - // averageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity averageValue = 5; -} - -// PodsMetricSource indicates how to scale on a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -// The values will be averaged together before being compared to the target -// value. -message PodsMetricSource { - // metricName is the name of the metric in question - optional string metricName = 1; - - // targetAverageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - optional .k8s.io.apimachinery.pkg.api.resource.Quantity targetAverageValue = 2; - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping - // When unset, just the metricName will be used to gather metrics. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 3; -} - -// PodsMetricStatus indicates the current value of a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -message PodsMetricStatus { - // metricName is the name of the metric in question - optional string metricName = 1; - - // currentAverageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - optional .k8s.io.apimachinery.pkg.api.resource.Quantity currentAverageValue = 2; - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set in the PodsMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 3; -} - -// ResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -message ResourceMetricSource { - // name is the name of the resource in question. - optional string name = 1; - - // targetAverageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // +optional - optional int32 targetAverageUtilization = 2; - - // targetAverageValue is the target value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity targetAverageValue = 3; -} - -// ResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -message ResourceMetricStatus { - // name is the name of the resource in question. - optional string name = 1; - - // currentAverageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. It will only be - // present if `targetAverageValue` was set in the corresponding metric - // specification. - // +optional - optional int32 currentAverageUtilization = 2; - - // currentAverageValue is the current value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // It will always be set, regardless of the corresponding metric specification. - optional .k8s.io.apimachinery.pkg.api.resource.Quantity currentAverageValue = 3; -} - diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/autoscaling/v2beta1/generated.protomessage.pb.go deleted file mode 100644 index ab1fe8c8b2b83..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v2beta1 - -func (*ContainerResourceMetricSource) ProtoMessage() {} - -func (*ContainerResourceMetricStatus) ProtoMessage() {} - -func (*CrossVersionObjectReference) ProtoMessage() {} - -func (*ExternalMetricSource) ProtoMessage() {} - -func (*ExternalMetricStatus) ProtoMessage() {} - -func (*HorizontalPodAutoscaler) ProtoMessage() {} - -func (*HorizontalPodAutoscalerCondition) ProtoMessage() {} - -func (*HorizontalPodAutoscalerList) ProtoMessage() {} - -func (*HorizontalPodAutoscalerSpec) ProtoMessage() {} - -func (*HorizontalPodAutoscalerStatus) ProtoMessage() {} - -func (*MetricSpec) ProtoMessage() {} - -func (*MetricStatus) ProtoMessage() {} - -func (*ObjectMetricSource) ProtoMessage() {} - -func (*ObjectMetricStatus) ProtoMessage() {} - -func (*PodsMetricSource) ProtoMessage() {} - -func (*PodsMetricStatus) ProtoMessage() {} - -func (*ResourceMetricSource) ProtoMessage() {} - -func (*ResourceMetricStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/register.go b/staging/src/k8s.io/api/autoscaling/v2beta1/register.go deleted file mode 100644 index 12d697f0130d6..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/register.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 2016 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 v2beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "autoscaling" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v2beta1"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. - // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -// Adds the list of known types to the given scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &HorizontalPodAutoscaler{}, - &HorizontalPodAutoscalerList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/types.go b/staging/src/k8s.io/api/autoscaling/v2beta1/types.go deleted file mode 100644 index c3abdd9bd9051..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/types.go +++ /dev/null @@ -1,486 +0,0 @@ -/* -Copyright 2016 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 v2beta1 - -import ( - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// CrossVersionObjectReference contains enough information to let you identify the referred resource. -type CrossVersionObjectReference struct { - // Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` - // Name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - Name string `json:"name" protobuf:"bytes,2,opt,name=name"` - // API version of the referent - // +optional - APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,3,opt,name=apiVersion"` -} - -// HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. -type HorizontalPodAutoscalerSpec struct { - // scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - // should be collected, as well as to actually change the replica count. - ScaleTargetRef CrossVersionObjectReference `json:"scaleTargetRef" protobuf:"bytes,1,opt,name=scaleTargetRef"` - // minReplicas is the lower limit for the number of replicas to which the autoscaler - // can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - // alpha feature gate HPAScaleToZero is enabled and at least one Object or External - // metric is configured. Scaling is active as long as at least one metric value is - // available. - // +optional - MinReplicas *int32 `json:"minReplicas,omitempty" protobuf:"varint,2,opt,name=minReplicas"` - // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - // It cannot be less that minReplicas. - MaxReplicas int32 `json:"maxReplicas" protobuf:"varint,3,opt,name=maxReplicas"` - // metrics contains the specifications for which to use to calculate the - // desired replica count (the maximum replica count across all metrics will - // be used). The desired replica count is calculated multiplying the - // ratio between the target value and the current value by the current - // number of pods. Ergo, metrics used must decrease as the pod count is - // increased, and vice-versa. See the individual metric source types for - // more information about how each type of metric must respond. - // +optional - // +listType=atomic - Metrics []MetricSpec `json:"metrics,omitempty" protobuf:"bytes,4,rep,name=metrics"` -} - -// MetricSourceType indicates the type of metric. -type MetricSourceType string - -const ( - // ObjectMetricSourceType is a metric describing a kubernetes object - // (for example, hits-per-second on an Ingress object). - ObjectMetricSourceType MetricSourceType = "Object" - // PodsMetricSourceType is a metric describing each pod in the current scale - // target (for example, transactions-processed-per-second). The values - // will be averaged together before being compared to the target value. - PodsMetricSourceType MetricSourceType = "Pods" - // ResourceMetricSourceType is a resource metric known to Kubernetes, as - // specified in requests and limits, describing each pod in the current - // scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics (the "pods" source). - ResourceMetricSourceType MetricSourceType = "Resource" - // ContainerResourceMetricSourceType is a resource metric known to Kubernetes, as - // specified in requests and limits, describing a single container in each pod in the current - // scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics (the "pods" source). - ContainerResourceMetricSourceType MetricSourceType = "ContainerResource" - // ExternalMetricSourceType is a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - ExternalMetricSourceType MetricSourceType = "External" -) - -// MetricSpec specifies how to scale based on a single metric -// (only `type` and one other matching field should be set at once). -type MetricSpec struct { - // type is the type of metric source. It should be one of "ContainerResource", - // "External", "Object", "Pods" or "Resource", each mapping to a matching field in the object. - Type MetricSourceType `json:"type" protobuf:"bytes,1,name=type"` - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - Object *ObjectMetricSource `json:"object,omitempty" protobuf:"bytes,2,opt,name=object"` - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - Pods *PodsMetricSource `json:"pods,omitempty" protobuf:"bytes,3,opt,name=pods"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - Resource *ResourceMetricSource `json:"resource,omitempty" protobuf:"bytes,4,opt,name=resource"` - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in - // each pod of the current scale target (e.g. CPU or memory). Such metrics are - // built in to Kubernetes, and have special scaling options on top of those - // available to normal per-pod metrics using the "pods" source. - // +optional - ContainerResource *ContainerResourceMetricSource `json:"containerResource,omitempty" protobuf:"bytes,7,opt,name=containerResource"` - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - External *ExternalMetricSource `json:"external,omitempty" protobuf:"bytes,5,opt,name=external"` -} - -// ObjectMetricSource indicates how to scale on a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricSource struct { - // target is the described Kubernetes object. - Target CrossVersionObjectReference `json:"target" protobuf:"bytes,1,name=target"` - - // metricName is the name of the metric in question. - MetricName string `json:"metricName" protobuf:"bytes,2,name=metricName"` - // targetValue is the target value of the metric (as a quantity). - TargetValue resource.Quantity `json:"targetValue" protobuf:"bytes,3,name=targetValue"` - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping - // When unset, just the metricName will be used to gather metrics. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,4,name=selector"` - // averageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - AverageValue *resource.Quantity `json:"averageValue,omitempty" protobuf:"bytes,5,name=averageValue"` -} - -// PodsMetricSource indicates how to scale on a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -// The values will be averaged together before being compared to the target -// value. -type PodsMetricSource struct { - // metricName is the name of the metric in question - MetricName string `json:"metricName" protobuf:"bytes,1,name=metricName"` - // targetAverageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - TargetAverageValue resource.Quantity `json:"targetAverageValue" protobuf:"bytes,2,name=targetAverageValue"` - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping - // When unset, just the metricName will be used to gather metrics. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,name=selector"` -} - -// ResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ResourceMetricSource struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - // targetAverageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // +optional - TargetAverageUtilization *int32 `json:"targetAverageUtilization,omitempty" protobuf:"varint,2,opt,name=targetAverageUtilization"` - // targetAverageValue is the target value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // +optional - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty" protobuf:"bytes,3,opt,name=targetAverageValue"` -} - -// ContainerResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ContainerResourceMetricSource struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - // targetAverageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // +optional - TargetAverageUtilization *int32 `json:"targetAverageUtilization,omitempty" protobuf:"varint,2,opt,name=targetAverageUtilization"` - // targetAverageValue is the target value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // +optional - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty" protobuf:"bytes,3,opt,name=targetAverageValue"` - // container is the name of the container in the pods of the scaling target - Container string `json:"container" protobuf:"bytes,4,opt,name=container"` -} - -// ExternalMetricSource indicates how to scale on a metric not associated with -// any Kubernetes object (for example length of queue in cloud -// messaging service, or QPS from loadbalancer running outside of cluster). -// Exactly one "target" type should be set. -type ExternalMetricSource struct { - // metricName is the name of the metric in question. - MetricName string `json:"metricName" protobuf:"bytes,1,name=metricName"` - // metricSelector is used to identify a specific time series - // within a given metric. - // +optional - MetricSelector *metav1.LabelSelector `json:"metricSelector,omitempty" protobuf:"bytes,2,opt,name=metricSelector"` - // targetValue is the target value of the metric (as a quantity). - // Mutually exclusive with TargetAverageValue. - // +optional - TargetValue *resource.Quantity `json:"targetValue,omitempty" protobuf:"bytes,3,opt,name=targetValue"` - // targetAverageValue is the target per-pod value of global metric (as a quantity). - // Mutually exclusive with TargetValue. - // +optional - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty" protobuf:"bytes,4,opt,name=targetAverageValue"` -} - -// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler. -type HorizontalPodAutoscalerStatus struct { - // observedGeneration is the most recent generation observed by this autoscaler. - // +optional - ObservedGeneration *int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - - // lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, - // used by the autoscaler to control how often the number of pods is changed. - // +optional - LastScaleTime *metav1.Time `json:"lastScaleTime,omitempty" protobuf:"bytes,2,opt,name=lastScaleTime"` - - // currentReplicas is current number of replicas of pods managed by this autoscaler, - // as last seen by the autoscaler. - CurrentReplicas int32 `json:"currentReplicas" protobuf:"varint,3,opt,name=currentReplicas"` - - // desiredReplicas is the desired number of replicas of pods managed by this autoscaler, - // as last calculated by the autoscaler. - DesiredReplicas int32 `json:"desiredReplicas" protobuf:"varint,4,opt,name=desiredReplicas"` - - // currentMetrics is the last read state of the metrics used by this autoscaler. - // +optional - // +listType=atomic - CurrentMetrics []MetricStatus `json:"currentMetrics" protobuf:"bytes,5,rep,name=currentMetrics"` - - // conditions is the set of conditions required for this autoscaler to scale its target, - // and indicates whether or not those conditions are met. - // +optional - // +listType=atomic - Conditions []HorizontalPodAutoscalerCondition `json:"conditions" protobuf:"bytes,6,rep,name=conditions"` -} - -// HorizontalPodAutoscalerConditionType are the valid conditions of -// a HorizontalPodAutoscaler. -type HorizontalPodAutoscalerConditionType string - -const ( - // ScalingActive indicates that the HPA controller is able to scale if necessary: - // it's correctly configured, can fetch the desired metrics, and isn't disabled. - ScalingActive HorizontalPodAutoscalerConditionType = "ScalingActive" - // AbleToScale indicates a lack of transient issues which prevent scaling from occurring, - // such as being in a backoff window, or being unable to access/update the target scale. - AbleToScale HorizontalPodAutoscalerConditionType = "AbleToScale" - // ScalingLimited indicates that the calculated scale based on metrics would be above or - // below the range for the HPA, and has thus been capped. - ScalingLimited HorizontalPodAutoscalerConditionType = "ScalingLimited" -) - -// HorizontalPodAutoscalerCondition describes the state of -// a HorizontalPodAutoscaler at a certain point. -type HorizontalPodAutoscalerCondition struct { - // type describes the current condition - Type HorizontalPodAutoscalerConditionType `json:"type" protobuf:"bytes,1,name=type"` - // status is the status of the condition (True, False, Unknown) - Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,name=status"` - // lastTransitionTime is the last time the condition transitioned from - // one status to another - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` - // reason is the reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` - // message is a human-readable explanation containing details about - // the transition - // +optional - Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` -} - -// MetricStatus describes the last-read state of a single metric. -type MetricStatus struct { - // type is the type of metric source. It will be one of "ContainerResource", - // "External", "Object", "Pods" or "Resource", each corresponds to a matching field in the object. - Type MetricSourceType `json:"type" protobuf:"bytes,1,name=type"` - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - Object *ObjectMetricStatus `json:"object,omitempty" protobuf:"bytes,2,opt,name=object"` - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - Pods *PodsMetricStatus `json:"pods,omitempty" protobuf:"bytes,3,opt,name=pods"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - Resource *ResourceMetricStatus `json:"resource,omitempty" protobuf:"bytes,4,opt,name=resource"` - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - ContainerResource *ContainerResourceMetricStatus `json:"containerResource,omitempty" protobuf:"bytes,7,opt,name=containerResource"` - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - External *ExternalMetricStatus `json:"external,omitempty" protobuf:"bytes,5,opt,name=external"` -} - -// ObjectMetricStatus indicates the current value of a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricStatus struct { - // target is the described Kubernetes object. - Target CrossVersionObjectReference `json:"target" protobuf:"bytes,1,name=target"` - - // metricName is the name of the metric in question. - MetricName string `json:"metricName" protobuf:"bytes,2,name=metricName"` - // currentValue is the current value of the metric (as a quantity). - CurrentValue resource.Quantity `json:"currentValue" protobuf:"bytes,3,name=currentValue"` - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set in the ObjectMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,4,name=selector"` - // averageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - AverageValue *resource.Quantity `json:"averageValue,omitempty" protobuf:"bytes,5,name=averageValue"` -} - -// PodsMetricStatus indicates the current value of a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -type PodsMetricStatus struct { - // metricName is the name of the metric in question - MetricName string `json:"metricName" protobuf:"bytes,1,name=metricName"` - // currentAverageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - CurrentAverageValue resource.Quantity `json:"currentAverageValue" protobuf:"bytes,2,name=currentAverageValue"` - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set in the PodsMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,3,name=selector"` -} - -// ResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ResourceMetricStatus struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - // currentAverageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. It will only be - // present if `targetAverageValue` was set in the corresponding metric - // specification. - // +optional - CurrentAverageUtilization *int32 `json:"currentAverageUtilization,omitempty" protobuf:"bytes,2,opt,name=currentAverageUtilization"` - // currentAverageValue is the current value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // It will always be set, regardless of the corresponding metric specification. - CurrentAverageValue resource.Quantity `json:"currentAverageValue" protobuf:"bytes,3,name=currentAverageValue"` -} - -// ContainerResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing a single container in each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ContainerResourceMetricStatus struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - // currentAverageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. It will only be - // present if `targetAverageValue` was set in the corresponding metric - // specification. - // +optional - CurrentAverageUtilization *int32 `json:"currentAverageUtilization,omitempty" protobuf:"bytes,2,opt,name=currentAverageUtilization"` - // currentAverageValue is the current value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // It will always be set, regardless of the corresponding metric specification. - CurrentAverageValue resource.Quantity `json:"currentAverageValue" protobuf:"bytes,3,name=currentAverageValue"` - // container is the name of the container in the pods of the scaling target - Container string `json:"container" protobuf:"bytes,4,opt,name=container"` -} - -// ExternalMetricStatus indicates the current value of a global metric -// not associated with any Kubernetes object. -type ExternalMetricStatus struct { - // metricName is the name of a metric used for autoscaling in - // metric system. - MetricName string `json:"metricName" protobuf:"bytes,1,name=metricName"` - // metricSelector is used to identify a specific time series - // within a given metric. - // +optional - MetricSelector *metav1.LabelSelector `json:"metricSelector,omitempty" protobuf:"bytes,2,opt,name=metricSelector"` - // currentValue is the current value of the metric (as a quantity) - CurrentValue resource.Quantity `json:"currentValue" protobuf:"bytes,3,name=currentValue"` - // currentAverageValue is the current value of metric averaged over autoscaled pods. - // +optional - CurrentAverageValue *resource.Quantity `json:"currentAverageValue,omitempty" protobuf:"bytes,4,opt,name=currentAverageValue"` -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:prerelease-lifecycle-gen:introduced=1.8 -// +k8s:prerelease-lifecycle-gen:deprecated=1.22 -// +k8s:prerelease-lifecycle-gen:replacement=autoscaling,v2,HorizontalPodAutoscaler - -// HorizontalPodAutoscaler is the configuration for a horizontal pod -// autoscaler, which automatically manages the replica count of any resource -// implementing the scale subresource based on the metrics specified. -type HorizontalPodAutoscaler struct { - metav1.TypeMeta `json:",inline"` - // metadata is the standard object metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // spec is the specification for the behaviour of the autoscaler. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional - Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - - // status is the current information about the autoscaler. - // +optional - Status HorizontalPodAutoscalerStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:prerelease-lifecycle-gen:introduced=1.8 -// +k8s:prerelease-lifecycle-gen:deprecated=1.22 -// +k8s:prerelease-lifecycle-gen:replacement=autoscaling,v2beta2,HorizontalPodAutoscalerList - -// HorizontalPodAutoscaler is a list of horizontal pod autoscaler objects. -type HorizontalPodAutoscalerList struct { - metav1.TypeMeta `json:",inline"` - // metadata is the standard list metadata. - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // items is the list of horizontal pod autoscaler objects. - Items []HorizontalPodAutoscaler `json:"items" protobuf:"bytes,2,rep,name=items"` -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/autoscaling/v2beta1/types_swagger_doc_generated.go deleted file mode 100644 index c7c72bf35687f..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/types_swagger_doc_generated.go +++ /dev/null @@ -1,247 +0,0 @@ -/* -Copyright 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 v2beta1 - -// This file contains a collection of methods that can be used from go-restful to -// generate Swagger API documentation for its models. Please read this PR for more -// information on the implementation: https://github.com/emicklei/go-restful/pull/215 -// -// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if -// they are on one line! For multiple line or blocks that you want to ignore use ---. -// Any context after a --- is ignored. -// -// Those methods can be generated by using hack/update-codegen.sh - -// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. -var map_ContainerResourceMetricSource = map[string]string{ - "": "ContainerResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - "name": "name is the name of the resource in question.", - "targetAverageUtilization": "targetAverageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", - "targetAverageValue": "targetAverageValue is the target value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type.", - "container": "container is the name of the container in the pods of the scaling target", -} - -func (ContainerResourceMetricSource) SwaggerDoc() map[string]string { - return map_ContainerResourceMetricSource -} - -var map_ContainerResourceMetricStatus = map[string]string{ - "": "ContainerResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "name": "name is the name of the resource in question.", - "currentAverageUtilization": "currentAverageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. It will only be present if `targetAverageValue` was set in the corresponding metric specification.", - "currentAverageValue": "currentAverageValue is the current value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type. It will always be set, regardless of the corresponding metric specification.", - "container": "container is the name of the container in the pods of the scaling target", -} - -func (ContainerResourceMetricStatus) SwaggerDoc() map[string]string { - return map_ContainerResourceMetricStatus -} - -var map_CrossVersionObjectReference = map[string]string{ - "": "CrossVersionObjectReference contains enough information to let you identify the referred resource.", - "kind": "Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "name": "Name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "apiVersion": "API version of the referent", -} - -func (CrossVersionObjectReference) SwaggerDoc() map[string]string { - return map_CrossVersionObjectReference -} - -var map_ExternalMetricSource = map[string]string{ - "": "ExternalMetricSource indicates how to scale on a metric not associated with any Kubernetes object (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster). Exactly one \"target\" type should be set.", - "metricName": "metricName is the name of the metric in question.", - "metricSelector": "metricSelector is used to identify a specific time series within a given metric.", - "targetValue": "targetValue is the target value of the metric (as a quantity). Mutually exclusive with TargetAverageValue.", - "targetAverageValue": "targetAverageValue is the target per-pod value of global metric (as a quantity). Mutually exclusive with TargetValue.", -} - -func (ExternalMetricSource) SwaggerDoc() map[string]string { - return map_ExternalMetricSource -} - -var map_ExternalMetricStatus = map[string]string{ - "": "ExternalMetricStatus indicates the current value of a global metric not associated with any Kubernetes object.", - "metricName": "metricName is the name of a metric used for autoscaling in metric system.", - "metricSelector": "metricSelector is used to identify a specific time series within a given metric.", - "currentValue": "currentValue is the current value of the metric (as a quantity)", - "currentAverageValue": "currentAverageValue is the current value of metric averaged over autoscaled pods.", -} - -func (ExternalMetricStatus) SwaggerDoc() map[string]string { - return map_ExternalMetricStatus -} - -var map_HorizontalPodAutoscaler = map[string]string{ - "": "HorizontalPodAutoscaler is the configuration for a horizontal pod autoscaler, which automatically manages the replica count of any resource implementing the scale subresource based on the metrics specified.", - "metadata": "metadata is the standard object metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "spec is the specification for the behaviour of the autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", - "status": "status is the current information about the autoscaler.", -} - -func (HorizontalPodAutoscaler) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscaler -} - -var map_HorizontalPodAutoscalerCondition = map[string]string{ - "": "HorizontalPodAutoscalerCondition describes the state of a HorizontalPodAutoscaler at a certain point.", - "type": "type describes the current condition", - "status": "status is the status of the condition (True, False, Unknown)", - "lastTransitionTime": "lastTransitionTime is the last time the condition transitioned from one status to another", - "reason": "reason is the reason for the condition's last transition.", - "message": "message is a human-readable explanation containing details about the transition", -} - -func (HorizontalPodAutoscalerCondition) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerCondition -} - -var map_HorizontalPodAutoscalerList = map[string]string{ - "": "HorizontalPodAutoscaler is a list of horizontal pod autoscaler objects.", - "metadata": "metadata is the standard list metadata.", - "items": "items is the list of horizontal pod autoscaler objects.", -} - -func (HorizontalPodAutoscalerList) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerList -} - -var map_HorizontalPodAutoscalerSpec = map[string]string{ - "": "HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler.", - "scaleTargetRef": "scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics should be collected, as well as to actually change the replica count.", - "minReplicas": "minReplicas is the lower limit for the number of replicas to which the autoscaler can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the alpha feature gate HPAScaleToZero is enabled and at least one Object or External metric is configured. Scaling is active as long as at least one metric value is available.", - "maxReplicas": "maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. It cannot be less that minReplicas.", - "metrics": "metrics contains the specifications for which to use to calculate the desired replica count (the maximum replica count across all metrics will be used). The desired replica count is calculated multiplying the ratio between the target value and the current value by the current number of pods. Ergo, metrics used must decrease as the pod count is increased, and vice-versa. See the individual metric source types for more information about how each type of metric must respond.", -} - -func (HorizontalPodAutoscalerSpec) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerSpec -} - -var map_HorizontalPodAutoscalerStatus = map[string]string{ - "": "HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler.", - "observedGeneration": "observedGeneration is the most recent generation observed by this autoscaler.", - "lastScaleTime": "lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, used by the autoscaler to control how often the number of pods is changed.", - "currentReplicas": "currentReplicas is current number of replicas of pods managed by this autoscaler, as last seen by the autoscaler.", - "desiredReplicas": "desiredReplicas is the desired number of replicas of pods managed by this autoscaler, as last calculated by the autoscaler.", - "currentMetrics": "currentMetrics is the last read state of the metrics used by this autoscaler.", - "conditions": "conditions is the set of conditions required for this autoscaler to scale its target, and indicates whether or not those conditions are met.", -} - -func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerStatus -} - -var map_MetricSpec = map[string]string{ - "": "MetricSpec specifies how to scale based on a single metric (only `type` and one other matching field should be set at once).", - "type": "type is the type of metric source. It should be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.", - "object": "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - "pods": "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - "resource": "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "containerResource": "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "external": "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", -} - -func (MetricSpec) SwaggerDoc() map[string]string { - return map_MetricSpec -} - -var map_MetricStatus = map[string]string{ - "": "MetricStatus describes the last-read state of a single metric.", - "type": "type is the type of metric source. It will be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.", - "object": "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - "pods": "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - "resource": "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "containerResource": "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "external": "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", -} - -func (MetricStatus) SwaggerDoc() map[string]string { - return map_MetricStatus -} - -var map_ObjectMetricSource = map[string]string{ - "": "ObjectMetricSource indicates how to scale on a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - "target": "target is the described Kubernetes object.", - "metricName": "metricName is the name of the metric in question.", - "targetValue": "targetValue is the target value of the metric (as a quantity).", - "selector": "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping When unset, just the metricName will be used to gather metrics.", - "averageValue": "averageValue is the target value of the average of the metric across all relevant pods (as a quantity)", -} - -func (ObjectMetricSource) SwaggerDoc() map[string]string { - return map_ObjectMetricSource -} - -var map_ObjectMetricStatus = map[string]string{ - "": "ObjectMetricStatus indicates the current value of a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - "target": "target is the described Kubernetes object.", - "metricName": "metricName is the name of the metric in question.", - "currentValue": "currentValue is the current value of the metric (as a quantity).", - "selector": "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set in the ObjectMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. When unset, just the metricName will be used to gather metrics.", - "averageValue": "averageValue is the current value of the average of the metric across all relevant pods (as a quantity)", -} - -func (ObjectMetricStatus) SwaggerDoc() map[string]string { - return map_ObjectMetricStatus -} - -var map_PodsMetricSource = map[string]string{ - "": "PodsMetricSource indicates how to scale on a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - "metricName": "metricName is the name of the metric in question", - "targetAverageValue": "targetAverageValue is the target value of the average of the metric across all relevant pods (as a quantity)", - "selector": "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping When unset, just the metricName will be used to gather metrics.", -} - -func (PodsMetricSource) SwaggerDoc() map[string]string { - return map_PodsMetricSource -} - -var map_PodsMetricStatus = map[string]string{ - "": "PodsMetricStatus indicates the current value of a metric describing each pod in the current scale target (for example, transactions-processed-per-second).", - "metricName": "metricName is the name of the metric in question", - "currentAverageValue": "currentAverageValue is the current value of the average of the metric across all relevant pods (as a quantity)", - "selector": "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set in the PodsMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. When unset, just the metricName will be used to gather metrics.", -} - -func (PodsMetricStatus) SwaggerDoc() map[string]string { - return map_PodsMetricStatus -} - -var map_ResourceMetricSource = map[string]string{ - "": "ResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - "name": "name is the name of the resource in question.", - "targetAverageUtilization": "targetAverageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", - "targetAverageValue": "targetAverageValue is the target value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type.", -} - -func (ResourceMetricSource) SwaggerDoc() map[string]string { - return map_ResourceMetricSource -} - -var map_ResourceMetricStatus = map[string]string{ - "": "ResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "name": "name is the name of the resource in question.", - "currentAverageUtilization": "currentAverageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. It will only be present if `targetAverageValue` was set in the corresponding metric specification.", - "currentAverageValue": "currentAverageValue is the current value of the average of the resource metric across all relevant pods, as a raw value (instead of as a percentage of the request), similar to the \"pods\" metric source type. It will always be set, regardless of the corresponding metric specification.", -} - -func (ResourceMetricStatus) SwaggerDoc() map[string]string { - return map_ResourceMetricStatus -} - -// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 610e81f81a7cc..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,525 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v2beta1 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerResourceMetricSource) DeepCopyInto(out *ContainerResourceMetricSource) { - *out = *in - if in.TargetAverageUtilization != nil { - in, out := &in.TargetAverageUtilization, &out.TargetAverageUtilization - *out = new(int32) - **out = **in - } - if in.TargetAverageValue != nil { - in, out := &in.TargetAverageValue, &out.TargetAverageValue - x := (*in).DeepCopy() - *out = &x - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerResourceMetricSource. -func (in *ContainerResourceMetricSource) DeepCopy() *ContainerResourceMetricSource { - if in == nil { - return nil - } - out := new(ContainerResourceMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerResourceMetricStatus) DeepCopyInto(out *ContainerResourceMetricStatus) { - *out = *in - if in.CurrentAverageUtilization != nil { - in, out := &in.CurrentAverageUtilization, &out.CurrentAverageUtilization - *out = new(int32) - **out = **in - } - out.CurrentAverageValue = in.CurrentAverageValue.DeepCopy() - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerResourceMetricStatus. -func (in *ContainerResourceMetricStatus) DeepCopy() *ContainerResourceMetricStatus { - if in == nil { - return nil - } - out := new(ContainerResourceMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CrossVersionObjectReference) DeepCopyInto(out *CrossVersionObjectReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossVersionObjectReference. -func (in *CrossVersionObjectReference) DeepCopy() *CrossVersionObjectReference { - if in == nil { - return nil - } - out := new(CrossVersionObjectReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalMetricSource) DeepCopyInto(out *ExternalMetricSource) { - *out = *in - if in.MetricSelector != nil { - in, out := &in.MetricSelector, &out.MetricSelector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.TargetValue != nil { - in, out := &in.TargetValue, &out.TargetValue - x := (*in).DeepCopy() - *out = &x - } - if in.TargetAverageValue != nil { - in, out := &in.TargetAverageValue, &out.TargetAverageValue - x := (*in).DeepCopy() - *out = &x - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalMetricSource. -func (in *ExternalMetricSource) DeepCopy() *ExternalMetricSource { - if in == nil { - return nil - } - out := new(ExternalMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalMetricStatus) DeepCopyInto(out *ExternalMetricStatus) { - *out = *in - if in.MetricSelector != nil { - in, out := &in.MetricSelector, &out.MetricSelector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - out.CurrentValue = in.CurrentValue.DeepCopy() - if in.CurrentAverageValue != nil { - in, out := &in.CurrentAverageValue, &out.CurrentAverageValue - x := (*in).DeepCopy() - *out = &x - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalMetricStatus. -func (in *ExternalMetricStatus) DeepCopy() *ExternalMetricStatus { - if in == nil { - return nil - } - out := new(ExternalMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscaler) DeepCopyInto(out *HorizontalPodAutoscaler) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscaler. -func (in *HorizontalPodAutoscaler) DeepCopy() *HorizontalPodAutoscaler { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscaler) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HorizontalPodAutoscaler) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerCondition) DeepCopyInto(out *HorizontalPodAutoscalerCondition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerCondition. -func (in *HorizontalPodAutoscalerCondition) DeepCopy() *HorizontalPodAutoscalerCondition { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerList) DeepCopyInto(out *HorizontalPodAutoscalerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]HorizontalPodAutoscaler, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerList. -func (in *HorizontalPodAutoscalerList) DeepCopy() *HorizontalPodAutoscalerList { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HorizontalPodAutoscalerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerSpec) DeepCopyInto(out *HorizontalPodAutoscalerSpec) { - *out = *in - out.ScaleTargetRef = in.ScaleTargetRef - if in.MinReplicas != nil { - in, out := &in.MinReplicas, &out.MinReplicas - *out = new(int32) - **out = **in - } - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics - *out = make([]MetricSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerSpec. -func (in *HorizontalPodAutoscalerSpec) DeepCopy() *HorizontalPodAutoscalerSpec { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerStatus) DeepCopyInto(out *HorizontalPodAutoscalerStatus) { - *out = *in - if in.ObservedGeneration != nil { - in, out := &in.ObservedGeneration, &out.ObservedGeneration - *out = new(int64) - **out = **in - } - if in.LastScaleTime != nil { - in, out := &in.LastScaleTime, &out.LastScaleTime - *out = (*in).DeepCopy() - } - if in.CurrentMetrics != nil { - in, out := &in.CurrentMetrics, &out.CurrentMetrics - *out = make([]MetricStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]HorizontalPodAutoscalerCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerStatus. -func (in *HorizontalPodAutoscalerStatus) DeepCopy() *HorizontalPodAutoscalerStatus { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricSpec) DeepCopyInto(out *MetricSpec) { - *out = *in - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(ObjectMetricSource) - (*in).DeepCopyInto(*out) - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(PodsMetricSource) - (*in).DeepCopyInto(*out) - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(ResourceMetricSource) - (*in).DeepCopyInto(*out) - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(ContainerResourceMetricSource) - (*in).DeepCopyInto(*out) - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(ExternalMetricSource) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricSpec. -func (in *MetricSpec) DeepCopy() *MetricSpec { - if in == nil { - return nil - } - out := new(MetricSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricStatus) DeepCopyInto(out *MetricStatus) { - *out = *in - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(ObjectMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(PodsMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(ResourceMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(ContainerResourceMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(ExternalMetricStatus) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricStatus. -func (in *MetricStatus) DeepCopy() *MetricStatus { - if in == nil { - return nil - } - out := new(MetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectMetricSource) DeepCopyInto(out *ObjectMetricSource) { - *out = *in - out.Target = in.Target - out.TargetValue = in.TargetValue.DeepCopy() - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.AverageValue != nil { - in, out := &in.AverageValue, &out.AverageValue - x := (*in).DeepCopy() - *out = &x - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMetricSource. -func (in *ObjectMetricSource) DeepCopy() *ObjectMetricSource { - if in == nil { - return nil - } - out := new(ObjectMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectMetricStatus) DeepCopyInto(out *ObjectMetricStatus) { - *out = *in - out.Target = in.Target - out.CurrentValue = in.CurrentValue.DeepCopy() - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.AverageValue != nil { - in, out := &in.AverageValue, &out.AverageValue - x := (*in).DeepCopy() - *out = &x - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMetricStatus. -func (in *ObjectMetricStatus) DeepCopy() *ObjectMetricStatus { - if in == nil { - return nil - } - out := new(ObjectMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodsMetricSource) DeepCopyInto(out *PodsMetricSource) { - *out = *in - out.TargetAverageValue = in.TargetAverageValue.DeepCopy() - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsMetricSource. -func (in *PodsMetricSource) DeepCopy() *PodsMetricSource { - if in == nil { - return nil - } - out := new(PodsMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodsMetricStatus) DeepCopyInto(out *PodsMetricStatus) { - *out = *in - out.CurrentAverageValue = in.CurrentAverageValue.DeepCopy() - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsMetricStatus. -func (in *PodsMetricStatus) DeepCopy() *PodsMetricStatus { - if in == nil { - return nil - } - out := new(PodsMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceMetricSource) DeepCopyInto(out *ResourceMetricSource) { - *out = *in - if in.TargetAverageUtilization != nil { - in, out := &in.TargetAverageUtilization, &out.TargetAverageUtilization - *out = new(int32) - **out = **in - } - if in.TargetAverageValue != nil { - in, out := &in.TargetAverageValue, &out.TargetAverageValue - x := (*in).DeepCopy() - *out = &x - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMetricSource. -func (in *ResourceMetricSource) DeepCopy() *ResourceMetricSource { - if in == nil { - return nil - } - out := new(ResourceMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceMetricStatus) DeepCopyInto(out *ResourceMetricStatus) { - *out = *in - if in.CurrentAverageUtilization != nil { - in, out := &in.CurrentAverageUtilization, &out.CurrentAverageUtilization - *out = new(int32) - **out = **in - } - out.CurrentAverageValue = in.CurrentAverageValue.DeepCopy() - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMetricStatus. -func (in *ResourceMetricStatus) DeepCopy() *ResourceMetricStatus { - if in == nil { - return nil - } - out := new(ResourceMetricStatus) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.model_name.go b/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.model_name.go deleted file mode 100644 index 2b8674e651620..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.model_name.go +++ /dev/null @@ -1,112 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by openapi-gen. DO NOT EDIT. - -package v2beta1 - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ContainerResourceMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ContainerResourceMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ContainerResourceMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ContainerResourceMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in CrossVersionObjectReference) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.CrossVersionObjectReference" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ExternalMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ExternalMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ExternalMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscaler) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscaler" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerCondition) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerCondition" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerList) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerList" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerSpec) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerSpec" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricSpec) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.MetricSpec" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.MetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ObjectMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ObjectMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ObjectMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ObjectMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PodsMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.PodsMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PodsMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.PodsMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ResourceMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ResourceMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ResourceMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta1.ResourceMetricStatus" -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.prerelease-lifecycle.go deleted file mode 100644 index 3437454ee3efe..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.prerelease-lifecycle.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by prerelease-lifecycle-gen. DO NOT EDIT. - -package v2beta1 - -import ( - schema "k8s.io/apimachinery/pkg/runtime/schema" -) - -// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. -func (in *HorizontalPodAutoscaler) APILifecycleIntroduced() (major, minor int) { - return 1, 8 -} - -// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. -func (in *HorizontalPodAutoscaler) APILifecycleDeprecated() (major, minor int) { - return 1, 22 -} - -// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. -// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. -func (in *HorizontalPodAutoscaler) APILifecycleReplacement() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "autoscaling", Version: "v2", Kind: "HorizontalPodAutoscaler"} -} - -// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. -func (in *HorizontalPodAutoscaler) APILifecycleRemoved() (major, minor int) { - return 1, 25 -} - -// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. -func (in *HorizontalPodAutoscalerList) APILifecycleIntroduced() (major, minor int) { - return 1, 8 -} - -// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. -func (in *HorizontalPodAutoscalerList) APILifecycleDeprecated() (major, minor int) { - return 1, 22 -} - -// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. -// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. -func (in *HorizontalPodAutoscalerList) APILifecycleReplacement() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "autoscaling", Version: "v2beta2", Kind: "HorizontalPodAutoscalerList"} -} - -// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. -func (in *HorizontalPodAutoscalerList) APILifecycleRemoved() (major, minor int) { - return 1, 25 -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/doc.go b/staging/src/k8s.io/api/autoscaling/v2beta2/doc.go deleted file mode 100644 index 5ea9edb68ad87..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2018 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. -*/ - -// +k8s:deepcopy-gen=package -// +k8s:protobuf-gen=package -// +k8s:openapi-gen=true -// +k8s:prerelease-lifecycle-gen=true -// +k8s:openapi-model-package=io.k8s.api.autoscaling.v2beta2 - -package v2beta2 diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/generated.pb.go b/staging/src/k8s.io/api/autoscaling/v2beta2/generated.pb.go deleted file mode 100644 index 87c8738678d4f..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/generated.pb.go +++ /dev/null @@ -1,5817 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: k8s.io/api/autoscaling/v2beta2/generated.proto - -package v2beta2 - -import ( - fmt "fmt" - - io "io" - - k8s_io_api_core_v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - math_bits "math/bits" - reflect "reflect" - strings "strings" -) - -func (m *ContainerResourceMetricSource) Reset() { *m = ContainerResourceMetricSource{} } - -func (m *ContainerResourceMetricStatus) Reset() { *m = ContainerResourceMetricStatus{} } - -func (m *CrossVersionObjectReference) Reset() { *m = CrossVersionObjectReference{} } - -func (m *ExternalMetricSource) Reset() { *m = ExternalMetricSource{} } - -func (m *ExternalMetricStatus) Reset() { *m = ExternalMetricStatus{} } - -func (m *HPAScalingPolicy) Reset() { *m = HPAScalingPolicy{} } - -func (m *HPAScalingRules) Reset() { *m = HPAScalingRules{} } - -func (m *HorizontalPodAutoscaler) Reset() { *m = HorizontalPodAutoscaler{} } - -func (m *HorizontalPodAutoscalerBehavior) Reset() { *m = HorizontalPodAutoscalerBehavior{} } - -func (m *HorizontalPodAutoscalerCondition) Reset() { *m = HorizontalPodAutoscalerCondition{} } - -func (m *HorizontalPodAutoscalerList) Reset() { *m = HorizontalPodAutoscalerList{} } - -func (m *HorizontalPodAutoscalerSpec) Reset() { *m = HorizontalPodAutoscalerSpec{} } - -func (m *HorizontalPodAutoscalerStatus) Reset() { *m = HorizontalPodAutoscalerStatus{} } - -func (m *MetricIdentifier) Reset() { *m = MetricIdentifier{} } - -func (m *MetricSpec) Reset() { *m = MetricSpec{} } - -func (m *MetricStatus) Reset() { *m = MetricStatus{} } - -func (m *MetricTarget) Reset() { *m = MetricTarget{} } - -func (m *MetricValueStatus) Reset() { *m = MetricValueStatus{} } - -func (m *ObjectMetricSource) Reset() { *m = ObjectMetricSource{} } - -func (m *ObjectMetricStatus) Reset() { *m = ObjectMetricStatus{} } - -func (m *PodsMetricSource) Reset() { *m = PodsMetricSource{} } - -func (m *PodsMetricStatus) Reset() { *m = PodsMetricStatus{} } - -func (m *ResourceMetricSource) Reset() { *m = ResourceMetricSource{} } - -func (m *ResourceMetricStatus) Reset() { *m = ResourceMetricStatus{} } - -func (m *ContainerResourceMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ContainerResourceMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ContainerResourceMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Container) - copy(dAtA[i:], m.Container) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Container))) - i-- - dAtA[i] = 0x1a - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ContainerResourceMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ContainerResourceMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ContainerResourceMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Container) - copy(dAtA[i:], m.Container) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Container))) - i-- - dAtA[i] = 0x1a - { - size, err := m.Current.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *CrossVersionObjectReference) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *CrossVersionObjectReference) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *CrossVersionObjectReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.APIVersion) - copy(dAtA[i:], m.APIVersion) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) - i-- - dAtA[i] = 0x1a - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x12 - i -= len(m.Kind) - copy(dAtA[i:], m.Kind) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ExternalMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ExternalMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.Metric.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ExternalMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ExternalMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Current.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.Metric.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HPAScalingPolicy) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HPAScalingPolicy) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HPAScalingPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i = encodeVarintGenerated(dAtA, i, uint64(m.PeriodSeconds)) - i-- - dAtA[i] = 0x18 - i = encodeVarintGenerated(dAtA, i, uint64(m.Value)) - i-- - dAtA[i] = 0x10 - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HPAScalingRules) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HPAScalingRules) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HPAScalingRules) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.StabilizationWindowSeconds != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.StabilizationWindowSeconds)) - i-- - dAtA[i] = 0x18 - } - if len(m.Policies) > 0 { - for iNdEx := len(m.Policies) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Policies[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - if m.SelectPolicy != nil { - i -= len(*m.SelectPolicy) - copy(dAtA[i:], *m.SelectPolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.SelectPolicy))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscaler) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscaler) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscaler) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerBehavior) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerBehavior) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerBehavior) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.ScaleDown != nil { - { - size, err := m.ScaleDown.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if m.ScaleUp != nil { - { - size, err := m.ScaleUp.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerCondition) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerCondition) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Message) - copy(dAtA[i:], m.Message) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) - i-- - dAtA[i] = 0x2a - i -= len(m.Reason) - copy(dAtA[i:], m.Reason) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) - i-- - dAtA[i] = 0x22 - { - size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - i -= len(m.Status) - copy(dAtA[i:], m.Status) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) - i-- - dAtA[i] = 0x12 - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerList) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerList) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerSpec) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Behavior != nil { - { - size, err := m.Behavior.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if len(m.Metrics) > 0 { - for iNdEx := len(m.Metrics) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Metrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - } - i = encodeVarintGenerated(dAtA, i, uint64(m.MaxReplicas)) - i-- - dAtA[i] = 0x18 - if m.MinReplicas != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.MinReplicas)) - i-- - dAtA[i] = 0x10 - } - { - size, err := m.ScaleTargetRef.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *HorizontalPodAutoscalerStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HorizontalPodAutoscalerStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HorizontalPodAutoscalerStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Conditions) > 0 { - for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x32 - } - } - if len(m.CurrentMetrics) > 0 { - for iNdEx := len(m.CurrentMetrics) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.CurrentMetrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - } - i = encodeVarintGenerated(dAtA, i, uint64(m.DesiredReplicas)) - i-- - dAtA[i] = 0x20 - i = encodeVarintGenerated(dAtA, i, uint64(m.CurrentReplicas)) - i-- - dAtA[i] = 0x18 - if m.LastScaleTime != nil { - { - size, err := m.LastScaleTime.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if m.ObservedGeneration != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.ObservedGeneration)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - -func (m *MetricIdentifier) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricIdentifier) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricIdentifier) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Selector != nil { - { - size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *MetricSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricSpec) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.ContainerResource != nil { - { - size, err := m.ContainerResource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } - if m.External != nil { - { - size, err := m.External.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Resource != nil { - { - size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if m.Pods != nil { - { - size, err := m.Pods.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.Object != nil { - { - size, err := m.Object.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *MetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.ContainerResource != nil { - { - size, err := m.ContainerResource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } - if m.External != nil { - { - size, err := m.External.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Resource != nil { - { - size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if m.Pods != nil { - { - size, err := m.Pods.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.Object != nil { - { - size, err := m.Object.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *MetricTarget) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricTarget) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricTarget) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.AverageUtilization != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.AverageUtilization)) - i-- - dAtA[i] = 0x20 - } - if m.AverageValue != nil { - { - size, err := m.AverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.Value != nil { - { - size, err := m.Value.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *MetricValueStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MetricValueStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MetricValueStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.AverageUtilization != nil { - i = encodeVarintGenerated(dAtA, i, uint64(*m.AverageUtilization)) - i-- - dAtA[i] = 0x18 - } - if m.AverageValue != nil { - { - size, err := m.AverageValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if m.Value != nil { - { - size, err := m.Value.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *ObjectMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ObjectMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ObjectMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Metric.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.DescribedObject.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ObjectMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ObjectMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ObjectMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.DescribedObject.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - { - size, err := m.Current.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.Metric.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *PodsMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PodsMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PodsMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.Metric.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *PodsMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PodsMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PodsMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Current.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.Metric.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ResourceMetricSource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ResourceMetricSource) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ResourceMetricSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Target.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *ResourceMetricStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ResourceMetricStatus) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ResourceMetricStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Current.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - offset -= sovGenerated(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *ContainerResourceMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Container) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ContainerResourceMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Current.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Container) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *CrossVersionObjectReference) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Kind) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.APIVersion) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ExternalMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Metric.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ExternalMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Metric.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Current.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *HPAScalingPolicy) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - n += 1 + sovGenerated(uint64(m.Value)) - n += 1 + sovGenerated(uint64(m.PeriodSeconds)) - return n -} - -func (m *HPAScalingRules) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.SelectPolicy != nil { - l = len(*m.SelectPolicy) - n += 1 + l + sovGenerated(uint64(l)) - } - if len(m.Policies) > 0 { - for _, e := range m.Policies { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - if m.StabilizationWindowSeconds != nil { - n += 1 + sovGenerated(uint64(*m.StabilizationWindowSeconds)) - } - return n -} - -func (m *HorizontalPodAutoscaler) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Spec.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Status.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *HorizontalPodAutoscalerBehavior) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ScaleUp != nil { - l = m.ScaleUp.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ScaleDown != nil { - l = m.ScaleDown.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *HorizontalPodAutoscalerCondition) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Status) - n += 1 + l + sovGenerated(uint64(l)) - l = m.LastTransitionTime.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Reason) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Message) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *HorizontalPodAutoscalerList) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *HorizontalPodAutoscalerSpec) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ScaleTargetRef.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.MinReplicas != nil { - n += 1 + sovGenerated(uint64(*m.MinReplicas)) - } - n += 1 + sovGenerated(uint64(m.MaxReplicas)) - if len(m.Metrics) > 0 { - for _, e := range m.Metrics { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - if m.Behavior != nil { - l = m.Behavior.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *HorizontalPodAutoscalerStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ObservedGeneration != nil { - n += 1 + sovGenerated(uint64(*m.ObservedGeneration)) - } - if m.LastScaleTime != nil { - l = m.LastScaleTime.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - n += 1 + sovGenerated(uint64(m.CurrentReplicas)) - n += 1 + sovGenerated(uint64(m.DesiredReplicas)) - if len(m.CurrentMetrics) > 0 { - for _, e := range m.CurrentMetrics { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - if len(m.Conditions) > 0 { - for _, e := range m.Conditions { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *MetricIdentifier) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.Selector != nil { - l = m.Selector.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *MetricSpec) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - if m.Object != nil { - l = m.Object.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Pods != nil { - l = m.Pods.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Resource != nil { - l = m.Resource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.External != nil { - l = m.External.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ContainerResource != nil { - l = m.ContainerResource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *MetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - if m.Object != nil { - l = m.Object.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Pods != nil { - l = m.Pods.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Resource != nil { - l = m.Resource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.External != nil { - l = m.External.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.ContainerResource != nil { - l = m.ContainerResource.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *MetricTarget) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - n += 1 + l + sovGenerated(uint64(l)) - if m.Value != nil { - l = m.Value.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.AverageValue != nil { - l = m.AverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.AverageUtilization != nil { - n += 1 + sovGenerated(uint64(*m.AverageUtilization)) - } - return n -} - -func (m *MetricValueStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Value != nil { - l = m.Value.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.AverageValue != nil { - l = m.AverageValue.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.AverageUtilization != nil { - n += 1 + sovGenerated(uint64(*m.AverageUtilization)) - } - return n -} - -func (m *ObjectMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.DescribedObject.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Metric.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ObjectMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Metric.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Current.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.DescribedObject.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *PodsMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Metric.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *PodsMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Metric.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Current.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ResourceMetricSource) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Target.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ResourceMetricStatus) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Current.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *ContainerResourceMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ContainerResourceMetricSource{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "MetricTarget", "MetricTarget", 1), `&`, ``, 1) + `,`, - `Container:` + fmt.Sprintf("%v", this.Container) + `,`, - `}`, - }, "") - return s -} -func (this *ContainerResourceMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ContainerResourceMetricStatus{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Current:` + strings.Replace(strings.Replace(this.Current.String(), "MetricValueStatus", "MetricValueStatus", 1), `&`, ``, 1) + `,`, - `Container:` + fmt.Sprintf("%v", this.Container) + `,`, - `}`, - }, "") - return s -} -func (this *CrossVersionObjectReference) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&CrossVersionObjectReference{`, - `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalMetricSource{`, - `Metric:` + strings.Replace(strings.Replace(this.Metric.String(), "MetricIdentifier", "MetricIdentifier", 1), `&`, ``, 1) + `,`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "MetricTarget", "MetricTarget", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalMetricStatus{`, - `Metric:` + strings.Replace(strings.Replace(this.Metric.String(), "MetricIdentifier", "MetricIdentifier", 1), `&`, ``, 1) + `,`, - `Current:` + strings.Replace(strings.Replace(this.Current.String(), "MetricValueStatus", "MetricValueStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *HPAScalingPolicy) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&HPAScalingPolicy{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Value:` + fmt.Sprintf("%v", this.Value) + `,`, - `PeriodSeconds:` + fmt.Sprintf("%v", this.PeriodSeconds) + `,`, - `}`, - }, "") - return s -} -func (this *HPAScalingRules) String() string { - if this == nil { - return "nil" - } - repeatedStringForPolicies := "[]HPAScalingPolicy{" - for _, f := range this.Policies { - repeatedStringForPolicies += strings.Replace(strings.Replace(f.String(), "HPAScalingPolicy", "HPAScalingPolicy", 1), `&`, ``, 1) + "," - } - repeatedStringForPolicies += "}" - s := strings.Join([]string{`&HPAScalingRules{`, - `SelectPolicy:` + valueToStringGenerated(this.SelectPolicy) + `,`, - `Policies:` + repeatedStringForPolicies + `,`, - `StabilizationWindowSeconds:` + valueToStringGenerated(this.StabilizationWindowSeconds) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscaler) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&HorizontalPodAutoscaler{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "HorizontalPodAutoscalerSpec", "HorizontalPodAutoscalerSpec", 1), `&`, ``, 1) + `,`, - `Status:` + strings.Replace(strings.Replace(this.Status.String(), "HorizontalPodAutoscalerStatus", "HorizontalPodAutoscalerStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerBehavior) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&HorizontalPodAutoscalerBehavior{`, - `ScaleUp:` + strings.Replace(this.ScaleUp.String(), "HPAScalingRules", "HPAScalingRules", 1) + `,`, - `ScaleDown:` + strings.Replace(this.ScaleDown.String(), "HPAScalingRules", "HPAScalingRules", 1) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerCondition) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&HorizontalPodAutoscalerCondition{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Status:` + fmt.Sprintf("%v", this.Status) + `,`, - `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, - `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, - `Message:` + fmt.Sprintf("%v", this.Message) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerList) String() string { - if this == nil { - return "nil" - } - repeatedStringForItems := "[]HorizontalPodAutoscaler{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "HorizontalPodAutoscaler", "HorizontalPodAutoscaler", 1), `&`, ``, 1) + "," - } - repeatedStringForItems += "}" - s := strings.Join([]string{`&HorizontalPodAutoscalerList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerSpec) String() string { - if this == nil { - return "nil" - } - repeatedStringForMetrics := "[]MetricSpec{" - for _, f := range this.Metrics { - repeatedStringForMetrics += strings.Replace(strings.Replace(f.String(), "MetricSpec", "MetricSpec", 1), `&`, ``, 1) + "," - } - repeatedStringForMetrics += "}" - s := strings.Join([]string{`&HorizontalPodAutoscalerSpec{`, - `ScaleTargetRef:` + strings.Replace(strings.Replace(this.ScaleTargetRef.String(), "CrossVersionObjectReference", "CrossVersionObjectReference", 1), `&`, ``, 1) + `,`, - `MinReplicas:` + valueToStringGenerated(this.MinReplicas) + `,`, - `MaxReplicas:` + fmt.Sprintf("%v", this.MaxReplicas) + `,`, - `Metrics:` + repeatedStringForMetrics + `,`, - `Behavior:` + strings.Replace(this.Behavior.String(), "HorizontalPodAutoscalerBehavior", "HorizontalPodAutoscalerBehavior", 1) + `,`, - `}`, - }, "") - return s -} -func (this *HorizontalPodAutoscalerStatus) String() string { - if this == nil { - return "nil" - } - repeatedStringForCurrentMetrics := "[]MetricStatus{" - for _, f := range this.CurrentMetrics { - repeatedStringForCurrentMetrics += strings.Replace(strings.Replace(f.String(), "MetricStatus", "MetricStatus", 1), `&`, ``, 1) + "," - } - repeatedStringForCurrentMetrics += "}" - repeatedStringForConditions := "[]HorizontalPodAutoscalerCondition{" - for _, f := range this.Conditions { - repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "HorizontalPodAutoscalerCondition", "HorizontalPodAutoscalerCondition", 1), `&`, ``, 1) + "," - } - repeatedStringForConditions += "}" - s := strings.Join([]string{`&HorizontalPodAutoscalerStatus{`, - `ObservedGeneration:` + valueToStringGenerated(this.ObservedGeneration) + `,`, - `LastScaleTime:` + strings.Replace(fmt.Sprintf("%v", this.LastScaleTime), "Time", "v1.Time", 1) + `,`, - `CurrentReplicas:` + fmt.Sprintf("%v", this.CurrentReplicas) + `,`, - `DesiredReplicas:` + fmt.Sprintf("%v", this.DesiredReplicas) + `,`, - `CurrentMetrics:` + repeatedStringForCurrentMetrics + `,`, - `Conditions:` + repeatedStringForConditions + `,`, - `}`, - }, "") - return s -} -func (this *MetricIdentifier) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricIdentifier{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, - `}`, - }, "") - return s -} -func (this *MetricSpec) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricSpec{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Object:` + strings.Replace(this.Object.String(), "ObjectMetricSource", "ObjectMetricSource", 1) + `,`, - `Pods:` + strings.Replace(this.Pods.String(), "PodsMetricSource", "PodsMetricSource", 1) + `,`, - `Resource:` + strings.Replace(this.Resource.String(), "ResourceMetricSource", "ResourceMetricSource", 1) + `,`, - `External:` + strings.Replace(this.External.String(), "ExternalMetricSource", "ExternalMetricSource", 1) + `,`, - `ContainerResource:` + strings.Replace(this.ContainerResource.String(), "ContainerResourceMetricSource", "ContainerResourceMetricSource", 1) + `,`, - `}`, - }, "") - return s -} -func (this *MetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricStatus{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Object:` + strings.Replace(this.Object.String(), "ObjectMetricStatus", "ObjectMetricStatus", 1) + `,`, - `Pods:` + strings.Replace(this.Pods.String(), "PodsMetricStatus", "PodsMetricStatus", 1) + `,`, - `Resource:` + strings.Replace(this.Resource.String(), "ResourceMetricStatus", "ResourceMetricStatus", 1) + `,`, - `External:` + strings.Replace(this.External.String(), "ExternalMetricStatus", "ExternalMetricStatus", 1) + `,`, - `ContainerResource:` + strings.Replace(this.ContainerResource.String(), "ContainerResourceMetricStatus", "ContainerResourceMetricStatus", 1) + `,`, - `}`, - }, "") - return s -} -func (this *MetricTarget) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricTarget{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Value:` + strings.Replace(fmt.Sprintf("%v", this.Value), "Quantity", "resource.Quantity", 1) + `,`, - `AverageValue:` + strings.Replace(fmt.Sprintf("%v", this.AverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `AverageUtilization:` + valueToStringGenerated(this.AverageUtilization) + `,`, - `}`, - }, "") - return s -} -func (this *MetricValueStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&MetricValueStatus{`, - `Value:` + strings.Replace(fmt.Sprintf("%v", this.Value), "Quantity", "resource.Quantity", 1) + `,`, - `AverageValue:` + strings.Replace(fmt.Sprintf("%v", this.AverageValue), "Quantity", "resource.Quantity", 1) + `,`, - `AverageUtilization:` + valueToStringGenerated(this.AverageUtilization) + `,`, - `}`, - }, "") - return s -} -func (this *ObjectMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ObjectMetricSource{`, - `DescribedObject:` + strings.Replace(strings.Replace(this.DescribedObject.String(), "CrossVersionObjectReference", "CrossVersionObjectReference", 1), `&`, ``, 1) + `,`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "MetricTarget", "MetricTarget", 1), `&`, ``, 1) + `,`, - `Metric:` + strings.Replace(strings.Replace(this.Metric.String(), "MetricIdentifier", "MetricIdentifier", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ObjectMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ObjectMetricStatus{`, - `Metric:` + strings.Replace(strings.Replace(this.Metric.String(), "MetricIdentifier", "MetricIdentifier", 1), `&`, ``, 1) + `,`, - `Current:` + strings.Replace(strings.Replace(this.Current.String(), "MetricValueStatus", "MetricValueStatus", 1), `&`, ``, 1) + `,`, - `DescribedObject:` + strings.Replace(strings.Replace(this.DescribedObject.String(), "CrossVersionObjectReference", "CrossVersionObjectReference", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *PodsMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PodsMetricSource{`, - `Metric:` + strings.Replace(strings.Replace(this.Metric.String(), "MetricIdentifier", "MetricIdentifier", 1), `&`, ``, 1) + `,`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "MetricTarget", "MetricTarget", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *PodsMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PodsMetricStatus{`, - `Metric:` + strings.Replace(strings.Replace(this.Metric.String(), "MetricIdentifier", "MetricIdentifier", 1), `&`, ``, 1) + `,`, - `Current:` + strings.Replace(strings.Replace(this.Current.String(), "MetricValueStatus", "MetricValueStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ResourceMetricSource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ResourceMetricSource{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Target:` + strings.Replace(strings.Replace(this.Target.String(), "MetricTarget", "MetricTarget", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ResourceMetricStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ResourceMetricStatus{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Current:` + strings.Replace(strings.Replace(this.Current.String(), "MetricValueStatus", "MetricValueStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func valueToStringGenerated(v interface{}) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { - return "nil" - } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("*%v", pv) -} -func (m *ContainerResourceMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ContainerResourceMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ContainerResourceMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Container", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Container = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ContainerResourceMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ContainerResourceMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ContainerResourceMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Current", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Current.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Container", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Container = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *CrossVersionObjectReference) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: CrossVersionObjectReference: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: CrossVersionObjectReference: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Kind = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.APIVersion = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ExternalMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metric.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ExternalMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metric.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Current", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Current.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HPAScalingPolicy) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HPAScalingPolicy: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HPAScalingPolicy: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = HPAScalingPolicyType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - m.Value = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Value |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field PeriodSeconds", wireType) - } - m.PeriodSeconds = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.PeriodSeconds |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HPAScalingRules) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HPAScalingRules: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HPAScalingRules: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SelectPolicy", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := ScalingPolicySelect(dAtA[iNdEx:postIndex]) - m.SelectPolicy = &s - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Policies", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Policies = append(m.Policies, HPAScalingPolicy{}) - if err := m.Policies[len(m.Policies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field StabilizationWindowSeconds", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.StabilizationWindowSeconds = &v - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscaler) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscaler: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscaler: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerBehavior) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerBehavior: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerBehavior: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ScaleUp", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ScaleUp == nil { - m.ScaleUp = &HPAScalingRules{} - } - if err := m.ScaleUp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ScaleDown", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ScaleDown == nil { - m.ScaleDown = &HPAScalingRules{} - } - if err := m.ScaleDown.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerCondition) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerCondition: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerCondition: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = HorizontalPodAutoscalerConditionType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Status = k8s_io_api_core_v1.ConditionStatus(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Reason = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Message = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerList) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerList: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, HorizontalPodAutoscaler{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerSpec) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ScaleTargetRef", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ScaleTargetRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinReplicas", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.MinReplicas = &v - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxReplicas", wireType) - } - m.MaxReplicas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.MaxReplicas |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metrics", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Metrics = append(m.Metrics, MetricSpec{}) - if err := m.Metrics[len(m.Metrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Behavior", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Behavior == nil { - m.Behavior = &HorizontalPodAutoscalerBehavior{} - } - if err := m.Behavior.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HorizontalPodAutoscalerStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HorizontalPodAutoscalerStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HorizontalPodAutoscalerStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) - } - var v int64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.ObservedGeneration = &v - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastScaleTime", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.LastScaleTime == nil { - m.LastScaleTime = &v1.Time{} - } - if err := m.LastScaleTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentReplicas", wireType) - } - m.CurrentReplicas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.CurrentReplicas |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DesiredReplicas", wireType) - } - m.DesiredReplicas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.DesiredReplicas |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentMetrics", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CurrentMetrics = append(m.CurrentMetrics, MetricStatus{}) - if err := m.CurrentMetrics[len(m.CurrentMetrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Conditions = append(m.Conditions, HorizontalPodAutoscalerCondition{}) - if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricIdentifier) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricIdentifier: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Selector == nil { - m.Selector = &v1.LabelSelector{} - } - if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricSpec) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = MetricSourceType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Object", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Object == nil { - m.Object = &ObjectMetricSource{} - } - if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pods", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pods == nil { - m.Pods = &PodsMetricSource{} - } - if err := m.Pods.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Resource == nil { - m.Resource = &ResourceMetricSource{} - } - if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field External", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.External == nil { - m.External = &ExternalMetricSource{} - } - if err := m.External.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContainerResource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ContainerResource == nil { - m.ContainerResource = &ContainerResourceMetricSource{} - } - if err := m.ContainerResource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = MetricSourceType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Object", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Object == nil { - m.Object = &ObjectMetricStatus{} - } - if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pods", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pods == nil { - m.Pods = &PodsMetricStatus{} - } - if err := m.Pods.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Resource == nil { - m.Resource = &ResourceMetricStatus{} - } - if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field External", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.External == nil { - m.External = &ExternalMetricStatus{} - } - if err := m.External.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContainerResource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ContainerResource == nil { - m.ContainerResource = &ContainerResourceMetricStatus{} - } - if err := m.ContainerResource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricTarget) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricTarget: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricTarget: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = MetricTargetType(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Value == nil { - m.Value = &resource.Quantity{} - } - if err := m.Value.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AverageValue == nil { - m.AverageValue = &resource.Quantity{} - } - if err := m.AverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AverageUtilization", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.AverageUtilization = &v - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MetricValueStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MetricValueStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MetricValueStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Value == nil { - m.Value = &resource.Quantity{} - } - if err := m.Value.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AverageValue", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AverageValue == nil { - m.AverageValue = &resource.Quantity{} - } - if err := m.AverageValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AverageUtilization", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.AverageUtilization = &v - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ObjectMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ObjectMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ObjectMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DescribedObject", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.DescribedObject.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metric.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ObjectMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ObjectMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ObjectMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metric.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Current", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Current.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DescribedObject", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.DescribedObject.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PodsMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PodsMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PodsMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metric.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PodsMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PodsMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PodsMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metric.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Current", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Current.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResourceMetricSource) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResourceMetricSource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResourceMetricSource: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Target.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResourceMetricStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResourceMetricStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResourceMetricStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Current", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Current.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipGenerated(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthGenerated - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupGenerated - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthGenerated - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") -) diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/generated.proto b/staging/src/k8s.io/api/autoscaling/v2beta2/generated.proto deleted file mode 100644 index 941d9752ae150..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/generated.proto +++ /dev/null @@ -1,493 +0,0 @@ -/* -Copyright 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. -*/ - - -// This file was autogenerated by go-to-protobuf. Do not edit it manually! - -syntax = "proto2"; - -package k8s.io.api.autoscaling.v2beta2; - -import "k8s.io/api/core/v1/generated.proto"; -import "k8s.io/apimachinery/pkg/api/resource/generated.proto"; -import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; -import "k8s.io/apimachinery/pkg/runtime/generated.proto"; -import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; - -// Package-wide variables from generator "generated". -option go_package = "k8s.io/api/autoscaling/v2beta2"; - -// ContainerResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -message ContainerResourceMetricSource { - // name is the name of the resource in question. - optional string name = 1; - - // target specifies the target value for the given metric - optional MetricTarget target = 2; - - // container is the name of the container in the pods of the scaling target - optional string container = 3; -} - -// ContainerResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing a single container in each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -message ContainerResourceMetricStatus { - // name is the name of the resource in question. - optional string name = 1; - - // current contains the current value for the given metric - optional MetricValueStatus current = 2; - - // container is the name of the container in the pods of the scaling target - optional string container = 3; -} - -// CrossVersionObjectReference contains enough information to let you identify the referred resource. -message CrossVersionObjectReference { - // kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - optional string kind = 1; - - // name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - optional string name = 2; - - // apiVersion is the API version of the referent - // +optional - optional string apiVersion = 3; -} - -// ExternalMetricSource indicates how to scale on a metric not associated with -// any Kubernetes object (for example length of queue in cloud -// messaging service, or QPS from loadbalancer running outside of cluster). -message ExternalMetricSource { - // metric identifies the target metric by name and selector - optional MetricIdentifier metric = 1; - - // target specifies the target value for the given metric - optional MetricTarget target = 2; -} - -// ExternalMetricStatus indicates the current value of a global metric -// not associated with any Kubernetes object. -message ExternalMetricStatus { - // metric identifies the target metric by name and selector - optional MetricIdentifier metric = 1; - - // current contains the current value for the given metric - optional MetricValueStatus current = 2; -} - -// HPAScalingPolicy is a single policy which must hold true for a specified past interval. -message HPAScalingPolicy { - // type is used to specify the scaling policy. - optional string type = 1; - - // value contains the amount of change which is permitted by the policy. - // It must be greater than zero - optional int32 value = 2; - - // periodSeconds specifies the window of time for which the policy should hold true. - // PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). - optional int32 periodSeconds = 3; -} - -// HPAScalingRules configures the scaling behavior for one direction. -// These Rules are applied after calculating DesiredReplicas from metrics for the HPA. -// They can limit the scaling velocity by specifying scaling policies. -// They can prevent flapping by specifying the stabilization window, so that the -// number of replicas is not set instantly, instead, the safest value from the stabilization -// window is chosen. -message HPAScalingRules { - // stabilizationWindowSeconds is the number of seconds for which past recommendations should be - // considered while scaling up or scaling down. - // StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). - // If not set, use the default values: - // - For scale up: 0 (i.e. no stabilization is done). - // - For scale down: 300 (i.e. the stabilization window is 300 seconds long). - // +optional - optional int32 stabilizationWindowSeconds = 3; - - // selectPolicy is used to specify which policy should be used. - // If not set, the default value MaxPolicySelect is used. - // +optional - optional string selectPolicy = 1; - - // policies is a list of potential scaling polices which can be used during scaling. - // At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid - // +optional - // +listType=atomic - repeated HPAScalingPolicy policies = 2; -} - -// HorizontalPodAutoscaler is the configuration for a horizontal pod -// autoscaler, which automatically manages the replica count of any resource -// implementing the scale subresource based on the metrics specified. -message HorizontalPodAutoscaler { - // metadata is the standard object metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // spec is the specification for the behaviour of the autoscaler. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional - optional HorizontalPodAutoscalerSpec spec = 2; - - // status is the current information about the autoscaler. - // +optional - optional HorizontalPodAutoscalerStatus status = 3; -} - -// HorizontalPodAutoscalerBehavior configures the scaling behavior of the target -// in both Up and Down directions (scaleUp and scaleDown fields respectively). -message HorizontalPodAutoscalerBehavior { - // scaleUp is scaling policy for scaling Up. - // If not set, the default value is the higher of: - // * increase no more than 4 pods per 60 seconds - // * double the number of pods per 60 seconds - // No stabilization is used. - // +optional - optional HPAScalingRules scaleUp = 1; - - // scaleDown is scaling policy for scaling Down. - // If not set, the default value is to allow to scale down to minReplicas pods, with a - // 300 second stabilization window (i.e., the highest recommendation for - // the last 300sec is used). - // +optional - optional HPAScalingRules scaleDown = 2; -} - -// HorizontalPodAutoscalerCondition describes the state of -// a HorizontalPodAutoscaler at a certain point. -message HorizontalPodAutoscalerCondition { - // type describes the current condition - optional string type = 1; - - // status is the status of the condition (True, False, Unknown) - optional string status = 2; - - // lastTransitionTime is the last time the condition transitioned from - // one status to another - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; - - // reason is the reason for the condition's last transition. - // +optional - optional string reason = 4; - - // message is a human-readable explanation containing details about - // the transition - // +optional - optional string message = 5; -} - -// HorizontalPodAutoscalerList is a list of horizontal pod autoscaler objects. -message HorizontalPodAutoscalerList { - // metadata is the standard list metadata. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // items is the list of horizontal pod autoscaler objects. - repeated HorizontalPodAutoscaler items = 2; -} - -// HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. -message HorizontalPodAutoscalerSpec { - // scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - // should be collected, as well as to actually change the replica count. - optional CrossVersionObjectReference scaleTargetRef = 1; - - // minReplicas is the lower limit for the number of replicas to which the autoscaler - // can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - // alpha feature gate HPAScaleToZero is enabled and at least one Object or External - // metric is configured. Scaling is active as long as at least one metric value is - // available. - // +optional - optional int32 minReplicas = 2; - - // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - // It cannot be less that minReplicas. - optional int32 maxReplicas = 3; - - // metrics contains the specifications for which to use to calculate the - // desired replica count (the maximum replica count across all metrics will - // be used). The desired replica count is calculated multiplying the - // ratio between the target value and the current value by the current - // number of pods. Ergo, metrics used must decrease as the pod count is - // increased, and vice-versa. See the individual metric source types for - // more information about how each type of metric must respond. - // If not set, the default metric will be set to 80% average CPU utilization. - // +optional - // +listType=atomic - repeated MetricSpec metrics = 4; - - // behavior configures the scaling behavior of the target - // in both Up and Down directions (scaleUp and scaleDown fields respectively). - // If not set, the default HPAScalingRules for scale up and scale down are used. - // +optional - optional HorizontalPodAutoscalerBehavior behavior = 5; -} - -// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler. -message HorizontalPodAutoscalerStatus { - // observedGeneration is the most recent generation observed by this autoscaler. - // +optional - optional int64 observedGeneration = 1; - - // lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, - // used by the autoscaler to control how often the number of pods is changed. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastScaleTime = 2; - - // currentReplicas is current number of replicas of pods managed by this autoscaler, - // as last seen by the autoscaler. - optional int32 currentReplicas = 3; - - // desiredReplicas is the desired number of replicas of pods managed by this autoscaler, - // as last calculated by the autoscaler. - optional int32 desiredReplicas = 4; - - // currentMetrics is the last read state of the metrics used by this autoscaler. - // +optional - // +listType=atomic - repeated MetricStatus currentMetrics = 5; - - // conditions is the set of conditions required for this autoscaler to scale its target, - // and indicates whether or not those conditions are met. - // +optional - // +listType=atomic - repeated HorizontalPodAutoscalerCondition conditions = 6; -} - -// MetricIdentifier defines the name and optionally selector for a metric -message MetricIdentifier { - // name is the name of the given metric - optional string name = 1; - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; -} - -// MetricSpec specifies how to scale based on a single metric -// (only `type` and one other matching field should be set at once). -message MetricSpec { - // type is the type of metric source. It should be one of "ContainerResource", "External", - // "Object", "Pods" or "Resource", each mapping to a matching field in the object. - optional string type = 1; - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - optional ObjectMetricSource object = 2; - - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - optional PodsMetricSource pods = 3; - - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - optional ResourceMetricSource resource = 4; - - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in - // each pod of the current scale target (e.g. CPU or memory). Such metrics are - // built in to Kubernetes, and have special scaling options on top of those - // available to normal per-pod metrics using the "pods" source. - // +optional - optional ContainerResourceMetricSource containerResource = 7; - - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - optional ExternalMetricSource external = 5; -} - -// MetricStatus describes the last-read state of a single metric. -message MetricStatus { - // type is the type of metric source. It will be one of "ContainerResource", "External", - // "Object", "Pods" or "Resource", each corresponds to a matching field in the object. - optional string type = 1; - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - optional ObjectMetricStatus object = 2; - - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - optional PodsMetricStatus pods = 3; - - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - optional ResourceMetricStatus resource = 4; - - // containerResource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - optional ContainerResourceMetricStatus containerResource = 7; - - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - optional ExternalMetricStatus external = 5; -} - -// MetricTarget defines the target value, average value, or average utilization of a specific metric -message MetricTarget { - // type represents whether the metric type is Utilization, Value, or AverageValue - optional string type = 1; - - // value is the target value of the metric (as a quantity). - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity value = 2; - - // averageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity averageValue = 3; - - // averageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // Currently only valid for Resource metric source type - // +optional - optional int32 averageUtilization = 4; -} - -// MetricValueStatus holds the current value for a metric -message MetricValueStatus { - // value is the current value of the metric (as a quantity). - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity value = 1; - - // averageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - optional .k8s.io.apimachinery.pkg.api.resource.Quantity averageValue = 2; - - // averageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // +optional - optional int32 averageUtilization = 3; -} - -// ObjectMetricSource indicates how to scale on a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -message ObjectMetricSource { - optional CrossVersionObjectReference describedObject = 1; - - // target specifies the target value for the given metric - optional MetricTarget target = 2; - - // metric identifies the target metric by name and selector - optional MetricIdentifier metric = 3; -} - -// ObjectMetricStatus indicates the current value of a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -message ObjectMetricStatus { - // metric identifies the target metric by name and selector - optional MetricIdentifier metric = 1; - - // current contains the current value for the given metric - optional MetricValueStatus current = 2; - - optional CrossVersionObjectReference describedObject = 3; -} - -// PodsMetricSource indicates how to scale on a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -// The values will be averaged together before being compared to the target -// value. -message PodsMetricSource { - // metric identifies the target metric by name and selector - optional MetricIdentifier metric = 1; - - // target specifies the target value for the given metric - optional MetricTarget target = 2; -} - -// PodsMetricStatus indicates the current value of a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -message PodsMetricStatus { - // metric identifies the target metric by name and selector - optional MetricIdentifier metric = 1; - - // current contains the current value for the given metric - optional MetricValueStatus current = 2; -} - -// ResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -message ResourceMetricSource { - // name is the name of the resource in question. - optional string name = 1; - - // target specifies the target value for the given metric - optional MetricTarget target = 2; -} - -// ResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -message ResourceMetricStatus { - // name is the name of the resource in question. - optional string name = 1; - - // current contains the current value for the given metric - optional MetricValueStatus current = 2; -} - diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/generated.protomessage.pb.go b/staging/src/k8s.io/api/autoscaling/v2beta2/generated.protomessage.pb.go deleted file mode 100644 index 6064b1eb0265d..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/generated.protomessage.pb.go +++ /dev/null @@ -1,70 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v2beta2 - -func (*ContainerResourceMetricSource) ProtoMessage() {} - -func (*ContainerResourceMetricStatus) ProtoMessage() {} - -func (*CrossVersionObjectReference) ProtoMessage() {} - -func (*ExternalMetricSource) ProtoMessage() {} - -func (*ExternalMetricStatus) ProtoMessage() {} - -func (*HPAScalingPolicy) ProtoMessage() {} - -func (*HPAScalingRules) ProtoMessage() {} - -func (*HorizontalPodAutoscaler) ProtoMessage() {} - -func (*HorizontalPodAutoscalerBehavior) ProtoMessage() {} - -func (*HorizontalPodAutoscalerCondition) ProtoMessage() {} - -func (*HorizontalPodAutoscalerList) ProtoMessage() {} - -func (*HorizontalPodAutoscalerSpec) ProtoMessage() {} - -func (*HorizontalPodAutoscalerStatus) ProtoMessage() {} - -func (*MetricIdentifier) ProtoMessage() {} - -func (*MetricSpec) ProtoMessage() {} - -func (*MetricStatus) ProtoMessage() {} - -func (*MetricTarget) ProtoMessage() {} - -func (*MetricValueStatus) ProtoMessage() {} - -func (*ObjectMetricSource) ProtoMessage() {} - -func (*ObjectMetricStatus) ProtoMessage() {} - -func (*PodsMetricSource) ProtoMessage() {} - -func (*PodsMetricStatus) ProtoMessage() {} - -func (*ResourceMetricSource) ProtoMessage() {} - -func (*ResourceMetricStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/register.go b/staging/src/k8s.io/api/autoscaling/v2beta2/register.go deleted file mode 100644 index eb1265c168c79..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/register.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2018 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 v2beta2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "autoscaling" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v2beta2"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -// Adds the list of known types to the given scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &HorizontalPodAutoscaler{}, - &HorizontalPodAutoscalerList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/types.go b/staging/src/k8s.io/api/autoscaling/v2beta2/types.go deleted file mode 100644 index bc9677b147e0a..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/types.go +++ /dev/null @@ -1,578 +0,0 @@ -/* -Copyright 2018 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. -*/ - -// +k8s:openapi-gen=true - -package v2beta2 - -import ( - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:prerelease-lifecycle-gen:introduced=1.12 -// +k8s:prerelease-lifecycle-gen:deprecated=1.23 -// +k8s:prerelease-lifecycle-gen:replacement=autoscaling,v2,HorizontalPodAutoscaler - -// HorizontalPodAutoscaler is the configuration for a horizontal pod -// autoscaler, which automatically manages the replica count of any resource -// implementing the scale subresource based on the metrics specified. -type HorizontalPodAutoscaler struct { - metav1.TypeMeta `json:",inline"` - // metadata is the standard object metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // spec is the specification for the behaviour of the autoscaler. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - // +optional - Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - - // status is the current information about the autoscaler. - // +optional - Status HorizontalPodAutoscalerStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. -type HorizontalPodAutoscalerSpec struct { - // scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - // should be collected, as well as to actually change the replica count. - ScaleTargetRef CrossVersionObjectReference `json:"scaleTargetRef" protobuf:"bytes,1,opt,name=scaleTargetRef"` - // minReplicas is the lower limit for the number of replicas to which the autoscaler - // can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - // alpha feature gate HPAScaleToZero is enabled and at least one Object or External - // metric is configured. Scaling is active as long as at least one metric value is - // available. - // +optional - MinReplicas *int32 `json:"minReplicas,omitempty" protobuf:"varint,2,opt,name=minReplicas"` - - // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - // It cannot be less that minReplicas. - MaxReplicas int32 `json:"maxReplicas" protobuf:"varint,3,opt,name=maxReplicas"` - - // metrics contains the specifications for which to use to calculate the - // desired replica count (the maximum replica count across all metrics will - // be used). The desired replica count is calculated multiplying the - // ratio between the target value and the current value by the current - // number of pods. Ergo, metrics used must decrease as the pod count is - // increased, and vice-versa. See the individual metric source types for - // more information about how each type of metric must respond. - // If not set, the default metric will be set to 80% average CPU utilization. - // +optional - // +listType=atomic - Metrics []MetricSpec `json:"metrics,omitempty" protobuf:"bytes,4,rep,name=metrics"` - - // behavior configures the scaling behavior of the target - // in both Up and Down directions (scaleUp and scaleDown fields respectively). - // If not set, the default HPAScalingRules for scale up and scale down are used. - // +optional - Behavior *HorizontalPodAutoscalerBehavior `json:"behavior,omitempty" protobuf:"bytes,5,opt,name=behavior"` -} - -// CrossVersionObjectReference contains enough information to let you identify the referred resource. -type CrossVersionObjectReference struct { - // kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` - - // name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - Name string `json:"name" protobuf:"bytes,2,opt,name=name"` - - // apiVersion is the API version of the referent - // +optional - APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,3,opt,name=apiVersion"` -} - -// MetricSpec specifies how to scale based on a single metric -// (only `type` and one other matching field should be set at once). -type MetricSpec struct { - // type is the type of metric source. It should be one of "ContainerResource", "External", - // "Object", "Pods" or "Resource", each mapping to a matching field in the object. - Type MetricSourceType `json:"type" protobuf:"bytes,1,name=type"` - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - Object *ObjectMetricSource `json:"object,omitempty" protobuf:"bytes,2,opt,name=object"` - - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - Pods *PodsMetricSource `json:"pods,omitempty" protobuf:"bytes,3,opt,name=pods"` - - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - Resource *ResourceMetricSource `json:"resource,omitempty" protobuf:"bytes,4,opt,name=resource"` - - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in - // each pod of the current scale target (e.g. CPU or memory). Such metrics are - // built in to Kubernetes, and have special scaling options on top of those - // available to normal per-pod metrics using the "pods" source. - // +optional - ContainerResource *ContainerResourceMetricSource `json:"containerResource,omitempty" protobuf:"bytes,7,opt,name=containerResource"` - - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - External *ExternalMetricSource `json:"external,omitempty" protobuf:"bytes,5,opt,name=external"` -} - -// HorizontalPodAutoscalerBehavior configures the scaling behavior of the target -// in both Up and Down directions (scaleUp and scaleDown fields respectively). -type HorizontalPodAutoscalerBehavior struct { - // scaleUp is scaling policy for scaling Up. - // If not set, the default value is the higher of: - // * increase no more than 4 pods per 60 seconds - // * double the number of pods per 60 seconds - // No stabilization is used. - // +optional - ScaleUp *HPAScalingRules `json:"scaleUp,omitempty" protobuf:"bytes,1,opt,name=scaleUp"` - - // scaleDown is scaling policy for scaling Down. - // If not set, the default value is to allow to scale down to minReplicas pods, with a - // 300 second stabilization window (i.e., the highest recommendation for - // the last 300sec is used). - // +optional - ScaleDown *HPAScalingRules `json:"scaleDown,omitempty" protobuf:"bytes,2,opt,name=scaleDown"` -} - -// ScalingPolicySelect is used to specify which policy should be used while scaling in a certain direction -type ScalingPolicySelect string - -const ( - // MaxPolicySelect selects the policy with the highest possible change. - MaxPolicySelect ScalingPolicySelect = "Max" - // MinPolicySelect selects the policy with the lowest possible change. - MinPolicySelect ScalingPolicySelect = "Min" - // DisabledPolicySelect disables the scaling in this direction. - DisabledPolicySelect ScalingPolicySelect = "Disabled" -) - -// HPAScalingRules configures the scaling behavior for one direction. -// These Rules are applied after calculating DesiredReplicas from metrics for the HPA. -// They can limit the scaling velocity by specifying scaling policies. -// They can prevent flapping by specifying the stabilization window, so that the -// number of replicas is not set instantly, instead, the safest value from the stabilization -// window is chosen. -type HPAScalingRules struct { - // stabilizationWindowSeconds is the number of seconds for which past recommendations should be - // considered while scaling up or scaling down. - // StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). - // If not set, use the default values: - // - For scale up: 0 (i.e. no stabilization is done). - // - For scale down: 300 (i.e. the stabilization window is 300 seconds long). - // +optional - StabilizationWindowSeconds *int32 `json:"stabilizationWindowSeconds,omitempty" protobuf:"varint,3,opt,name=stabilizationWindowSeconds"` - - // selectPolicy is used to specify which policy should be used. - // If not set, the default value MaxPolicySelect is used. - // +optional - SelectPolicy *ScalingPolicySelect `json:"selectPolicy,omitempty" protobuf:"bytes,1,opt,name=selectPolicy"` - - // policies is a list of potential scaling polices which can be used during scaling. - // At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid - // +optional - // +listType=atomic - Policies []HPAScalingPolicy `json:"policies,omitempty" protobuf:"bytes,2,rep,name=policies"` -} - -// HPAScalingPolicyType is the type of the policy which could be used while making scaling decisions. -type HPAScalingPolicyType string - -const ( - // PodsScalingPolicy is a policy used to specify a change in absolute number of pods. - PodsScalingPolicy HPAScalingPolicyType = "Pods" - // PercentScalingPolicy is a policy used to specify a relative amount of change with respect to - // the current number of pods. - PercentScalingPolicy HPAScalingPolicyType = "Percent" -) - -// HPAScalingPolicy is a single policy which must hold true for a specified past interval. -type HPAScalingPolicy struct { - // type is used to specify the scaling policy. - Type HPAScalingPolicyType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=HPAScalingPolicyType"` - - // value contains the amount of change which is permitted by the policy. - // It must be greater than zero - Value int32 `json:"value" protobuf:"varint,2,opt,name=value"` - - // periodSeconds specifies the window of time for which the policy should hold true. - // PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). - PeriodSeconds int32 `json:"periodSeconds" protobuf:"varint,3,opt,name=periodSeconds"` -} - -// MetricSourceType indicates the type of metric. -type MetricSourceType string - -const ( - // ObjectMetricSourceType is a metric describing a kubernetes object - // (for example, hits-per-second on an Ingress object). - ObjectMetricSourceType MetricSourceType = "Object" - // PodsMetricSourceType is a metric describing each pod in the current scale - // target (for example, transactions-processed-per-second). The values - // will be averaged together before being compared to the target value. - PodsMetricSourceType MetricSourceType = "Pods" - // ResourceMetricSourceType is a resource metric known to Kubernetes, as - // specified in requests and limits, describing each pod in the current - // scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics (the "pods" source). - ResourceMetricSourceType MetricSourceType = "Resource" - // ContainerResourceMetricSourceType is a resource metric known to Kubernetes, as - // specified in requests and limits, describing a single container in each pod in the current - // scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics (the "pods" source). - ContainerResourceMetricSourceType MetricSourceType = "ContainerResource" - // ExternalMetricSourceType is a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - ExternalMetricSourceType MetricSourceType = "External" -) - -// ObjectMetricSource indicates how to scale on a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricSource struct { - DescribedObject CrossVersionObjectReference `json:"describedObject" protobuf:"bytes,1,name=describedObject"` - // target specifies the target value for the given metric - Target MetricTarget `json:"target" protobuf:"bytes,2,name=target"` - - // metric identifies the target metric by name and selector - Metric MetricIdentifier `json:"metric" protobuf:"bytes,3,name=metric"` -} - -// PodsMetricSource indicates how to scale on a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -// The values will be averaged together before being compared to the target -// value. -type PodsMetricSource struct { - // metric identifies the target metric by name and selector - Metric MetricIdentifier `json:"metric" protobuf:"bytes,1,name=metric"` - - // target specifies the target value for the given metric - Target MetricTarget `json:"target" protobuf:"bytes,2,name=target"` -} - -// ResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ResourceMetricSource struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - - // target specifies the target value for the given metric - Target MetricTarget `json:"target" protobuf:"bytes,2,name=target"` -} - -// ContainerResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ContainerResourceMetricSource struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - - // target specifies the target value for the given metric - Target MetricTarget `json:"target" protobuf:"bytes,2,name=target"` - - // container is the name of the container in the pods of the scaling target - Container string `json:"container" protobuf:"bytes,3,opt,name=container"` -} - -// ExternalMetricSource indicates how to scale on a metric not associated with -// any Kubernetes object (for example length of queue in cloud -// messaging service, or QPS from loadbalancer running outside of cluster). -type ExternalMetricSource struct { - // metric identifies the target metric by name and selector - Metric MetricIdentifier `json:"metric" protobuf:"bytes,1,name=metric"` - - // target specifies the target value for the given metric - Target MetricTarget `json:"target" protobuf:"bytes,2,name=target"` -} - -// MetricIdentifier defines the name and optionally selector for a metric -type MetricIdentifier struct { - // name is the name of the given metric - Name string `json:"name" protobuf:"bytes,1,name=name"` - - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,name=selector"` -} - -// MetricTarget defines the target value, average value, or average utilization of a specific metric -type MetricTarget struct { - // type represents whether the metric type is Utilization, Value, or AverageValue - Type MetricTargetType `json:"type" protobuf:"bytes,1,name=type"` - - // value is the target value of the metric (as a quantity). - // +optional - Value *resource.Quantity `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"` - - // averageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - AverageValue *resource.Quantity `json:"averageValue,omitempty" protobuf:"bytes,3,opt,name=averageValue"` - - // averageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // Currently only valid for Resource metric source type - // +optional - AverageUtilization *int32 `json:"averageUtilization,omitempty" protobuf:"bytes,4,opt,name=averageUtilization"` -} - -// MetricTargetType specifies the type of metric being targeted, and should be either -// "Value", "AverageValue", or "Utilization" -type MetricTargetType string - -const ( - // UtilizationMetricType declares a MetricTarget is an AverageUtilization value - UtilizationMetricType MetricTargetType = "Utilization" - // ValueMetricType declares a MetricTarget is a raw value - ValueMetricType MetricTargetType = "Value" - // AverageValueMetricType declares a MetricTarget is an - AverageValueMetricType MetricTargetType = "AverageValue" -) - -// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler. -type HorizontalPodAutoscalerStatus struct { - // observedGeneration is the most recent generation observed by this autoscaler. - // +optional - ObservedGeneration *int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - - // lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, - // used by the autoscaler to control how often the number of pods is changed. - // +optional - LastScaleTime *metav1.Time `json:"lastScaleTime,omitempty" protobuf:"bytes,2,opt,name=lastScaleTime"` - - // currentReplicas is current number of replicas of pods managed by this autoscaler, - // as last seen by the autoscaler. - CurrentReplicas int32 `json:"currentReplicas" protobuf:"varint,3,opt,name=currentReplicas"` - - // desiredReplicas is the desired number of replicas of pods managed by this autoscaler, - // as last calculated by the autoscaler. - DesiredReplicas int32 `json:"desiredReplicas" protobuf:"varint,4,opt,name=desiredReplicas"` - - // currentMetrics is the last read state of the metrics used by this autoscaler. - // +optional - // +listType=atomic - CurrentMetrics []MetricStatus `json:"currentMetrics" protobuf:"bytes,5,rep,name=currentMetrics"` - - // conditions is the set of conditions required for this autoscaler to scale its target, - // and indicates whether or not those conditions are met. - // +optional - // +listType=atomic - Conditions []HorizontalPodAutoscalerCondition `json:"conditions" protobuf:"bytes,6,rep,name=conditions"` -} - -// HorizontalPodAutoscalerConditionType are the valid conditions of -// a HorizontalPodAutoscaler. -type HorizontalPodAutoscalerConditionType string - -const ( - // ScalingActive indicates that the HPA controller is able to scale if necessary: - // it's correctly configured, can fetch the desired metrics, and isn't disabled. - ScalingActive HorizontalPodAutoscalerConditionType = "ScalingActive" - // AbleToScale indicates a lack of transient issues which prevent scaling from occurring, - // such as being in a backoff window, or being unable to access/update the target scale. - AbleToScale HorizontalPodAutoscalerConditionType = "AbleToScale" - // ScalingLimited indicates that the calculated scale based on metrics would be above or - // below the range for the HPA, and has thus been capped. - ScalingLimited HorizontalPodAutoscalerConditionType = "ScalingLimited" -) - -// HorizontalPodAutoscalerCondition describes the state of -// a HorizontalPodAutoscaler at a certain point. -type HorizontalPodAutoscalerCondition struct { - // type describes the current condition - Type HorizontalPodAutoscalerConditionType `json:"type" protobuf:"bytes,1,name=type"` - - // status is the status of the condition (True, False, Unknown) - Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,name=status"` - - // lastTransitionTime is the last time the condition transitioned from - // one status to another - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` - - // reason is the reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` - - // message is a human-readable explanation containing details about - // the transition - // +optional - Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` -} - -// MetricStatus describes the last-read state of a single metric. -type MetricStatus struct { - // type is the type of metric source. It will be one of "ContainerResource", "External", - // "Object", "Pods" or "Resource", each corresponds to a matching field in the object. - Type MetricSourceType `json:"type" protobuf:"bytes,1,name=type"` - - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - // +optional - Object *ObjectMetricStatus `json:"object,omitempty" protobuf:"bytes,2,opt,name=object"` - - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - // +optional - Pods *PodsMetricStatus `json:"pods,omitempty" protobuf:"bytes,3,opt,name=pods"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - Resource *ResourceMetricStatus `json:"resource,omitempty" protobuf:"bytes,4,opt,name=resource"` - - // containerResource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - // +optional - ContainerResource *ContainerResourceMetricStatus `json:"containerResource,omitempty" protobuf:"bytes,7,opt,name=containerResource"` - - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - // +optional - External *ExternalMetricStatus `json:"external,omitempty" protobuf:"bytes,5,opt,name=external"` -} - -// ObjectMetricStatus indicates the current value of a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricStatus struct { - // metric identifies the target metric by name and selector - Metric MetricIdentifier `json:"metric" protobuf:"bytes,1,name=metric"` - - // current contains the current value for the given metric - Current MetricValueStatus `json:"current" protobuf:"bytes,2,name=current"` - - DescribedObject CrossVersionObjectReference `json:"describedObject" protobuf:"bytes,3,name=describedObject"` -} - -// PodsMetricStatus indicates the current value of a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -type PodsMetricStatus struct { - // metric identifies the target metric by name and selector - Metric MetricIdentifier `json:"metric" protobuf:"bytes,1,name=metric"` - - // current contains the current value for the given metric - Current MetricValueStatus `json:"current" protobuf:"bytes,2,name=current"` -} - -// ResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ResourceMetricStatus struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - - // current contains the current value for the given metric - Current MetricValueStatus `json:"current" protobuf:"bytes,2,name=current"` -} - -// ContainerResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing a single container in each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ContainerResourceMetricStatus struct { - // name is the name of the resource in question. - Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"` - - // current contains the current value for the given metric - Current MetricValueStatus `json:"current" protobuf:"bytes,2,name=current"` - - // container is the name of the container in the pods of the scaling target - Container string `json:"container" protobuf:"bytes,3,opt,name=container"` -} - -// ExternalMetricStatus indicates the current value of a global metric -// not associated with any Kubernetes object. -type ExternalMetricStatus struct { - // metric identifies the target metric by name and selector - Metric MetricIdentifier `json:"metric" protobuf:"bytes,1,name=metric"` - - // current contains the current value for the given metric - Current MetricValueStatus `json:"current" protobuf:"bytes,2,name=current"` -} - -// MetricValueStatus holds the current value for a metric -type MetricValueStatus struct { - // value is the current value of the metric (as a quantity). - // +optional - Value *resource.Quantity `json:"value,omitempty" protobuf:"bytes,1,opt,name=value"` - - // averageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - // +optional - AverageValue *resource.Quantity `json:"averageValue,omitempty" protobuf:"bytes,2,opt,name=averageValue"` - - // averageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // +optional - AverageUtilization *int32 `json:"averageUtilization,omitempty" protobuf:"bytes,3,opt,name=averageUtilization"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:prerelease-lifecycle-gen:introduced=1.12 -// +k8s:prerelease-lifecycle-gen:deprecated=1.22 - -// HorizontalPodAutoscalerList is a list of horizontal pod autoscaler objects. -type HorizontalPodAutoscalerList struct { - metav1.TypeMeta `json:",inline"` - // metadata is the standard list metadata. - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // items is the list of horizontal pod autoscaler objects. - Items []HorizontalPodAutoscaler `json:"items" protobuf:"bytes,2,rep,name=items"` -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/types_swagger_doc_generated.go b/staging/src/k8s.io/api/autoscaling/v2beta2/types_swagger_doc_generated.go deleted file mode 100644 index 5d4bb86b8344c..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/types_swagger_doc_generated.go +++ /dev/null @@ -1,297 +0,0 @@ -/* -Copyright 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 v2beta2 - -// This file contains a collection of methods that can be used from go-restful to -// generate Swagger API documentation for its models. Please read this PR for more -// information on the implementation: https://github.com/emicklei/go-restful/pull/215 -// -// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if -// they are on one line! For multiple line or blocks that you want to ignore use ---. -// Any context after a --- is ignored. -// -// Those methods can be generated by using hack/update-codegen.sh - -// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. -var map_ContainerResourceMetricSource = map[string]string{ - "": "ContainerResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - "name": "name is the name of the resource in question.", - "target": "target specifies the target value for the given metric", - "container": "container is the name of the container in the pods of the scaling target", -} - -func (ContainerResourceMetricSource) SwaggerDoc() map[string]string { - return map_ContainerResourceMetricSource -} - -var map_ContainerResourceMetricStatus = map[string]string{ - "": "ContainerResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "name": "name is the name of the resource in question.", - "current": "current contains the current value for the given metric", - "container": "container is the name of the container in the pods of the scaling target", -} - -func (ContainerResourceMetricStatus) SwaggerDoc() map[string]string { - return map_ContainerResourceMetricStatus -} - -var map_CrossVersionObjectReference = map[string]string{ - "": "CrossVersionObjectReference contains enough information to let you identify the referred resource.", - "kind": "kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "name": "name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "apiVersion": "apiVersion is the API version of the referent", -} - -func (CrossVersionObjectReference) SwaggerDoc() map[string]string { - return map_CrossVersionObjectReference -} - -var map_ExternalMetricSource = map[string]string{ - "": "ExternalMetricSource indicates how to scale on a metric not associated with any Kubernetes object (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", - "metric": "metric identifies the target metric by name and selector", - "target": "target specifies the target value for the given metric", -} - -func (ExternalMetricSource) SwaggerDoc() map[string]string { - return map_ExternalMetricSource -} - -var map_ExternalMetricStatus = map[string]string{ - "": "ExternalMetricStatus indicates the current value of a global metric not associated with any Kubernetes object.", - "metric": "metric identifies the target metric by name and selector", - "current": "current contains the current value for the given metric", -} - -func (ExternalMetricStatus) SwaggerDoc() map[string]string { - return map_ExternalMetricStatus -} - -var map_HPAScalingPolicy = map[string]string{ - "": "HPAScalingPolicy is a single policy which must hold true for a specified past interval.", - "type": "type is used to specify the scaling policy.", - "value": "value contains the amount of change which is permitted by the policy. It must be greater than zero", - "periodSeconds": "periodSeconds specifies the window of time for which the policy should hold true. PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).", -} - -func (HPAScalingPolicy) SwaggerDoc() map[string]string { - return map_HPAScalingPolicy -} - -var map_HPAScalingRules = map[string]string{ - "": "HPAScalingRules configures the scaling behavior for one direction. These Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.", - "stabilizationWindowSeconds": "stabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).", - "selectPolicy": "selectPolicy is used to specify which policy should be used. If not set, the default value MaxPolicySelect is used.", - "policies": "policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid", -} - -func (HPAScalingRules) SwaggerDoc() map[string]string { - return map_HPAScalingRules -} - -var map_HorizontalPodAutoscaler = map[string]string{ - "": "HorizontalPodAutoscaler is the configuration for a horizontal pod autoscaler, which automatically manages the replica count of any resource implementing the scale subresource based on the metrics specified.", - "metadata": "metadata is the standard object metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "spec": "spec is the specification for the behaviour of the autoscaler. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.", - "status": "status is the current information about the autoscaler.", -} - -func (HorizontalPodAutoscaler) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscaler -} - -var map_HorizontalPodAutoscalerBehavior = map[string]string{ - "": "HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively).", - "scaleUp": "scaleUp is scaling policy for scaling Up. If not set, the default value is the higher of:\n * increase no more than 4 pods per 60 seconds\n * double the number of pods per 60 seconds\nNo stabilization is used.", - "scaleDown": "scaleDown is scaling policy for scaling Down. If not set, the default value is to allow to scale down to minReplicas pods, with a 300 second stabilization window (i.e., the highest recommendation for the last 300sec is used).", -} - -func (HorizontalPodAutoscalerBehavior) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerBehavior -} - -var map_HorizontalPodAutoscalerCondition = map[string]string{ - "": "HorizontalPodAutoscalerCondition describes the state of a HorizontalPodAutoscaler at a certain point.", - "type": "type describes the current condition", - "status": "status is the status of the condition (True, False, Unknown)", - "lastTransitionTime": "lastTransitionTime is the last time the condition transitioned from one status to another", - "reason": "reason is the reason for the condition's last transition.", - "message": "message is a human-readable explanation containing details about the transition", -} - -func (HorizontalPodAutoscalerCondition) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerCondition -} - -var map_HorizontalPodAutoscalerList = map[string]string{ - "": "HorizontalPodAutoscalerList is a list of horizontal pod autoscaler objects.", - "metadata": "metadata is the standard list metadata.", - "items": "items is the list of horizontal pod autoscaler objects.", -} - -func (HorizontalPodAutoscalerList) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerList -} - -var map_HorizontalPodAutoscalerSpec = map[string]string{ - "": "HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler.", - "scaleTargetRef": "scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics should be collected, as well as to actually change the replica count.", - "minReplicas": "minReplicas is the lower limit for the number of replicas to which the autoscaler can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the alpha feature gate HPAScaleToZero is enabled and at least one Object or External metric is configured. Scaling is active as long as at least one metric value is available.", - "maxReplicas": "maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. It cannot be less that minReplicas.", - "metrics": "metrics contains the specifications for which to use to calculate the desired replica count (the maximum replica count across all metrics will be used). The desired replica count is calculated multiplying the ratio between the target value and the current value by the current number of pods. Ergo, metrics used must decrease as the pod count is increased, and vice-versa. See the individual metric source types for more information about how each type of metric must respond. If not set, the default metric will be set to 80% average CPU utilization.", - "behavior": "behavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). If not set, the default HPAScalingRules for scale up and scale down are used.", -} - -func (HorizontalPodAutoscalerSpec) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerSpec -} - -var map_HorizontalPodAutoscalerStatus = map[string]string{ - "": "HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler.", - "observedGeneration": "observedGeneration is the most recent generation observed by this autoscaler.", - "lastScaleTime": "lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, used by the autoscaler to control how often the number of pods is changed.", - "currentReplicas": "currentReplicas is current number of replicas of pods managed by this autoscaler, as last seen by the autoscaler.", - "desiredReplicas": "desiredReplicas is the desired number of replicas of pods managed by this autoscaler, as last calculated by the autoscaler.", - "currentMetrics": "currentMetrics is the last read state of the metrics used by this autoscaler.", - "conditions": "conditions is the set of conditions required for this autoscaler to scale its target, and indicates whether or not those conditions are met.", -} - -func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string { - return map_HorizontalPodAutoscalerStatus -} - -var map_MetricIdentifier = map[string]string{ - "": "MetricIdentifier defines the name and optionally selector for a metric", - "name": "name is the name of the given metric", - "selector": "selector is the string-encoded form of a standard kubernetes label selector for the given metric When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. When unset, just the metricName will be used to gather metrics.", -} - -func (MetricIdentifier) SwaggerDoc() map[string]string { - return map_MetricIdentifier -} - -var map_MetricSpec = map[string]string{ - "": "MetricSpec specifies how to scale based on a single metric (only `type` and one other matching field should be set at once).", - "type": "type is the type of metric source. It should be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.", - "object": "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - "pods": "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - "resource": "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "containerResource": "container resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod of the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "external": "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", -} - -func (MetricSpec) SwaggerDoc() map[string]string { - return map_MetricSpec -} - -var map_MetricStatus = map[string]string{ - "": "MetricStatus describes the last-read state of a single metric.", - "type": "type is the type of metric source. It will be one of \"ContainerResource\", \"External\", \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.", - "object": "object refers to a metric describing a single kubernetes object (for example, hits-per-second on an Ingress object).", - "pods": "pods refers to a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - "resource": "resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "containerResource": "containerResource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing a single container in each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "external": "external refers to a global metric that is not associated with any Kubernetes object. It allows autoscaling based on information coming from components running outside of cluster (for example length of queue in cloud messaging service, or QPS from loadbalancer running outside of cluster).", -} - -func (MetricStatus) SwaggerDoc() map[string]string { - return map_MetricStatus -} - -var map_MetricTarget = map[string]string{ - "": "MetricTarget defines the target value, average value, or average utilization of a specific metric", - "type": "type represents whether the metric type is Utilization, Value, or AverageValue", - "value": "value is the target value of the metric (as a quantity).", - "averageValue": "averageValue is the target value of the average of the metric across all relevant pods (as a quantity)", - "averageUtilization": "averageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. Currently only valid for Resource metric source type", -} - -func (MetricTarget) SwaggerDoc() map[string]string { - return map_MetricTarget -} - -var map_MetricValueStatus = map[string]string{ - "": "MetricValueStatus holds the current value for a metric", - "value": "value is the current value of the metric (as a quantity).", - "averageValue": "averageValue is the current value of the average of the metric across all relevant pods (as a quantity)", - "averageUtilization": "averageUtilization is the current value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods.", -} - -func (MetricValueStatus) SwaggerDoc() map[string]string { - return map_MetricValueStatus -} - -var map_ObjectMetricSource = map[string]string{ - "": "ObjectMetricSource indicates how to scale on a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - "target": "target specifies the target value for the given metric", - "metric": "metric identifies the target metric by name and selector", -} - -func (ObjectMetricSource) SwaggerDoc() map[string]string { - return map_ObjectMetricSource -} - -var map_ObjectMetricStatus = map[string]string{ - "": "ObjectMetricStatus indicates the current value of a metric describing a kubernetes object (for example, hits-per-second on an Ingress object).", - "metric": "metric identifies the target metric by name and selector", - "current": "current contains the current value for the given metric", -} - -func (ObjectMetricStatus) SwaggerDoc() map[string]string { - return map_ObjectMetricStatus -} - -var map_PodsMetricSource = map[string]string{ - "": "PodsMetricSource indicates how to scale on a metric describing each pod in the current scale target (for example, transactions-processed-per-second). The values will be averaged together before being compared to the target value.", - "metric": "metric identifies the target metric by name and selector", - "target": "target specifies the target value for the given metric", -} - -func (PodsMetricSource) SwaggerDoc() map[string]string { - return map_PodsMetricSource -} - -var map_PodsMetricStatus = map[string]string{ - "": "PodsMetricStatus indicates the current value of a metric describing each pod in the current scale target (for example, transactions-processed-per-second).", - "metric": "metric identifies the target metric by name and selector", - "current": "current contains the current value for the given metric", -} - -func (PodsMetricStatus) SwaggerDoc() map[string]string { - return map_PodsMetricStatus -} - -var map_ResourceMetricSource = map[string]string{ - "": "ResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source. Only one \"target\" type should be set.", - "name": "name is the name of the resource in question.", - "target": "target specifies the target value for the given metric", -} - -func (ResourceMetricSource) SwaggerDoc() map[string]string { - return map_ResourceMetricSource -} - -var map_ResourceMetricStatus = map[string]string{ - "": "ResourceMetricStatus indicates the current value of a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the \"pods\" source.", - "name": "name is the name of the resource in question.", - "current": "current contains the current value for the given metric", -} - -func (ResourceMetricStatus) SwaggerDoc() map[string]string { - return map_ResourceMetricStatus -} - -// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.deepcopy.go b/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.deepcopy.go deleted file mode 100644 index 49838843c0619..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.deepcopy.go +++ /dev/null @@ -1,610 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerResourceMetricSource) DeepCopyInto(out *ContainerResourceMetricSource) { - *out = *in - in.Target.DeepCopyInto(&out.Target) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerResourceMetricSource. -func (in *ContainerResourceMetricSource) DeepCopy() *ContainerResourceMetricSource { - if in == nil { - return nil - } - out := new(ContainerResourceMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerResourceMetricStatus) DeepCopyInto(out *ContainerResourceMetricStatus) { - *out = *in - in.Current.DeepCopyInto(&out.Current) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerResourceMetricStatus. -func (in *ContainerResourceMetricStatus) DeepCopy() *ContainerResourceMetricStatus { - if in == nil { - return nil - } - out := new(ContainerResourceMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CrossVersionObjectReference) DeepCopyInto(out *CrossVersionObjectReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossVersionObjectReference. -func (in *CrossVersionObjectReference) DeepCopy() *CrossVersionObjectReference { - if in == nil { - return nil - } - out := new(CrossVersionObjectReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalMetricSource) DeepCopyInto(out *ExternalMetricSource) { - *out = *in - in.Metric.DeepCopyInto(&out.Metric) - in.Target.DeepCopyInto(&out.Target) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalMetricSource. -func (in *ExternalMetricSource) DeepCopy() *ExternalMetricSource { - if in == nil { - return nil - } - out := new(ExternalMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalMetricStatus) DeepCopyInto(out *ExternalMetricStatus) { - *out = *in - in.Metric.DeepCopyInto(&out.Metric) - in.Current.DeepCopyInto(&out.Current) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalMetricStatus. -func (in *ExternalMetricStatus) DeepCopy() *ExternalMetricStatus { - if in == nil { - return nil - } - out := new(ExternalMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HPAScalingPolicy) DeepCopyInto(out *HPAScalingPolicy) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPAScalingPolicy. -func (in *HPAScalingPolicy) DeepCopy() *HPAScalingPolicy { - if in == nil { - return nil - } - out := new(HPAScalingPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HPAScalingRules) DeepCopyInto(out *HPAScalingRules) { - *out = *in - if in.StabilizationWindowSeconds != nil { - in, out := &in.StabilizationWindowSeconds, &out.StabilizationWindowSeconds - *out = new(int32) - **out = **in - } - if in.SelectPolicy != nil { - in, out := &in.SelectPolicy, &out.SelectPolicy - *out = new(ScalingPolicySelect) - **out = **in - } - if in.Policies != nil { - in, out := &in.Policies, &out.Policies - *out = make([]HPAScalingPolicy, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPAScalingRules. -func (in *HPAScalingRules) DeepCopy() *HPAScalingRules { - if in == nil { - return nil - } - out := new(HPAScalingRules) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscaler) DeepCopyInto(out *HorizontalPodAutoscaler) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscaler. -func (in *HorizontalPodAutoscaler) DeepCopy() *HorizontalPodAutoscaler { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscaler) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HorizontalPodAutoscaler) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerBehavior) DeepCopyInto(out *HorizontalPodAutoscalerBehavior) { - *out = *in - if in.ScaleUp != nil { - in, out := &in.ScaleUp, &out.ScaleUp - *out = new(HPAScalingRules) - (*in).DeepCopyInto(*out) - } - if in.ScaleDown != nil { - in, out := &in.ScaleDown, &out.ScaleDown - *out = new(HPAScalingRules) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerBehavior. -func (in *HorizontalPodAutoscalerBehavior) DeepCopy() *HorizontalPodAutoscalerBehavior { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerBehavior) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerCondition) DeepCopyInto(out *HorizontalPodAutoscalerCondition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerCondition. -func (in *HorizontalPodAutoscalerCondition) DeepCopy() *HorizontalPodAutoscalerCondition { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerList) DeepCopyInto(out *HorizontalPodAutoscalerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]HorizontalPodAutoscaler, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerList. -func (in *HorizontalPodAutoscalerList) DeepCopy() *HorizontalPodAutoscalerList { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HorizontalPodAutoscalerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerSpec) DeepCopyInto(out *HorizontalPodAutoscalerSpec) { - *out = *in - out.ScaleTargetRef = in.ScaleTargetRef - if in.MinReplicas != nil { - in, out := &in.MinReplicas, &out.MinReplicas - *out = new(int32) - **out = **in - } - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics - *out = make([]MetricSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Behavior != nil { - in, out := &in.Behavior, &out.Behavior - *out = new(HorizontalPodAutoscalerBehavior) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerSpec. -func (in *HorizontalPodAutoscalerSpec) DeepCopy() *HorizontalPodAutoscalerSpec { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HorizontalPodAutoscalerStatus) DeepCopyInto(out *HorizontalPodAutoscalerStatus) { - *out = *in - if in.ObservedGeneration != nil { - in, out := &in.ObservedGeneration, &out.ObservedGeneration - *out = new(int64) - **out = **in - } - if in.LastScaleTime != nil { - in, out := &in.LastScaleTime, &out.LastScaleTime - *out = (*in).DeepCopy() - } - if in.CurrentMetrics != nil { - in, out := &in.CurrentMetrics, &out.CurrentMetrics - *out = make([]MetricStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]HorizontalPodAutoscalerCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerStatus. -func (in *HorizontalPodAutoscalerStatus) DeepCopy() *HorizontalPodAutoscalerStatus { - if in == nil { - return nil - } - out := new(HorizontalPodAutoscalerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricIdentifier) DeepCopyInto(out *MetricIdentifier) { - *out = *in - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricIdentifier. -func (in *MetricIdentifier) DeepCopy() *MetricIdentifier { - if in == nil { - return nil - } - out := new(MetricIdentifier) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricSpec) DeepCopyInto(out *MetricSpec) { - *out = *in - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(ObjectMetricSource) - (*in).DeepCopyInto(*out) - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(PodsMetricSource) - (*in).DeepCopyInto(*out) - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(ResourceMetricSource) - (*in).DeepCopyInto(*out) - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(ContainerResourceMetricSource) - (*in).DeepCopyInto(*out) - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(ExternalMetricSource) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricSpec. -func (in *MetricSpec) DeepCopy() *MetricSpec { - if in == nil { - return nil - } - out := new(MetricSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricStatus) DeepCopyInto(out *MetricStatus) { - *out = *in - if in.Object != nil { - in, out := &in.Object, &out.Object - *out = new(ObjectMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.Pods != nil { - in, out := &in.Pods, &out.Pods - *out = new(PodsMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource - *out = new(ResourceMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.ContainerResource != nil { - in, out := &in.ContainerResource, &out.ContainerResource - *out = new(ContainerResourceMetricStatus) - (*in).DeepCopyInto(*out) - } - if in.External != nil { - in, out := &in.External, &out.External - *out = new(ExternalMetricStatus) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricStatus. -func (in *MetricStatus) DeepCopy() *MetricStatus { - if in == nil { - return nil - } - out := new(MetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricTarget) DeepCopyInto(out *MetricTarget) { - *out = *in - if in.Value != nil { - in, out := &in.Value, &out.Value - x := (*in).DeepCopy() - *out = &x - } - if in.AverageValue != nil { - in, out := &in.AverageValue, &out.AverageValue - x := (*in).DeepCopy() - *out = &x - } - if in.AverageUtilization != nil { - in, out := &in.AverageUtilization, &out.AverageUtilization - *out = new(int32) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTarget. -func (in *MetricTarget) DeepCopy() *MetricTarget { - if in == nil { - return nil - } - out := new(MetricTarget) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetricValueStatus) DeepCopyInto(out *MetricValueStatus) { - *out = *in - if in.Value != nil { - in, out := &in.Value, &out.Value - x := (*in).DeepCopy() - *out = &x - } - if in.AverageValue != nil { - in, out := &in.AverageValue, &out.AverageValue - x := (*in).DeepCopy() - *out = &x - } - if in.AverageUtilization != nil { - in, out := &in.AverageUtilization, &out.AverageUtilization - *out = new(int32) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricValueStatus. -func (in *MetricValueStatus) DeepCopy() *MetricValueStatus { - if in == nil { - return nil - } - out := new(MetricValueStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectMetricSource) DeepCopyInto(out *ObjectMetricSource) { - *out = *in - out.DescribedObject = in.DescribedObject - in.Target.DeepCopyInto(&out.Target) - in.Metric.DeepCopyInto(&out.Metric) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMetricSource. -func (in *ObjectMetricSource) DeepCopy() *ObjectMetricSource { - if in == nil { - return nil - } - out := new(ObjectMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectMetricStatus) DeepCopyInto(out *ObjectMetricStatus) { - *out = *in - in.Metric.DeepCopyInto(&out.Metric) - in.Current.DeepCopyInto(&out.Current) - out.DescribedObject = in.DescribedObject - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMetricStatus. -func (in *ObjectMetricStatus) DeepCopy() *ObjectMetricStatus { - if in == nil { - return nil - } - out := new(ObjectMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodsMetricSource) DeepCopyInto(out *PodsMetricSource) { - *out = *in - in.Metric.DeepCopyInto(&out.Metric) - in.Target.DeepCopyInto(&out.Target) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsMetricSource. -func (in *PodsMetricSource) DeepCopy() *PodsMetricSource { - if in == nil { - return nil - } - out := new(PodsMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodsMetricStatus) DeepCopyInto(out *PodsMetricStatus) { - *out = *in - in.Metric.DeepCopyInto(&out.Metric) - in.Current.DeepCopyInto(&out.Current) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsMetricStatus. -func (in *PodsMetricStatus) DeepCopy() *PodsMetricStatus { - if in == nil { - return nil - } - out := new(PodsMetricStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceMetricSource) DeepCopyInto(out *ResourceMetricSource) { - *out = *in - in.Target.DeepCopyInto(&out.Target) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMetricSource. -func (in *ResourceMetricSource) DeepCopy() *ResourceMetricSource { - if in == nil { - return nil - } - out := new(ResourceMetricSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceMetricStatus) DeepCopyInto(out *ResourceMetricStatus) { - *out = *in - in.Current.DeepCopyInto(&out.Current) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMetricStatus. -func (in *ResourceMetricStatus) DeepCopy() *ResourceMetricStatus { - if in == nil { - return nil - } - out := new(ResourceMetricStatus) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.model_name.go b/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.model_name.go deleted file mode 100644 index 20e8a854c674e..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.model_name.go +++ /dev/null @@ -1,142 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by openapi-gen. DO NOT EDIT. - -package v2beta2 - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ContainerResourceMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ContainerResourceMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ContainerResourceMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ContainerResourceMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in CrossVersionObjectReference) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.CrossVersionObjectReference" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ExternalMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ExternalMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ExternalMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ExternalMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HPAScalingPolicy) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HPAScalingPolicy" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HPAScalingRules) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HPAScalingRules" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscaler) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscaler" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerBehavior) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerBehavior" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerCondition) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerCondition" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerList) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerList" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerSpec) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerSpec" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in HorizontalPodAutoscalerStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricIdentifier) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.MetricIdentifier" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricSpec) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.MetricSpec" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.MetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricTarget) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.MetricTarget" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in MetricValueStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.MetricValueStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ObjectMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ObjectMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ObjectMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ObjectMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PodsMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.PodsMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PodsMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.PodsMetricStatus" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ResourceMetricSource) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ResourceMetricSource" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in ResourceMetricStatus) OpenAPIModelName() string { - return "io.k8s.api.autoscaling.v2beta2.ResourceMetricStatus" -} diff --git a/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.prerelease-lifecycle.go deleted file mode 100644 index 6d1c2504abf77..0000000000000 --- a/staging/src/k8s.io/api/autoscaling/v2beta2/zz_generated.prerelease-lifecycle.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by prerelease-lifecycle-gen. DO NOT EDIT. - -package v2beta2 - -import ( - schema "k8s.io/apimachinery/pkg/runtime/schema" -) - -// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. -func (in *HorizontalPodAutoscaler) APILifecycleIntroduced() (major, minor int) { - return 1, 12 -} - -// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. -func (in *HorizontalPodAutoscaler) APILifecycleDeprecated() (major, minor int) { - return 1, 23 -} - -// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type. -// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=,," tags in types.go. -func (in *HorizontalPodAutoscaler) APILifecycleReplacement() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "autoscaling", Version: "v2", Kind: "HorizontalPodAutoscaler"} -} - -// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. -func (in *HorizontalPodAutoscaler) APILifecycleRemoved() (major, minor int) { - return 1, 26 -} - -// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. -func (in *HorizontalPodAutoscalerList) APILifecycleIntroduced() (major, minor int) { - return 1, 12 -} - -// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. -func (in *HorizontalPodAutoscalerList) APILifecycleDeprecated() (major, minor int) { - return 1, 22 -} - -// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. -// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. -func (in *HorizontalPodAutoscalerList) APILifecycleRemoved() (major, minor int) { - return 1, 25 -} diff --git a/staging/src/k8s.io/api/batch/v1/generated.proto b/staging/src/k8s.io/api/batch/v1/generated.proto index ca8248ff5b6cd..5c35012852e7b 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1/generated.proto @@ -38,7 +38,7 @@ message CronJob { // Specification of the desired behavior of a cron job, including the schedule. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required optional CronJobSpec spec = 2; // Current status of a cron job. @@ -61,6 +61,8 @@ message CronJobList { // CronJobSpec describes how the job execution will look like and when it will actually run. message CronJobSpec { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string schedule = 1; // The time zone name for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. diff --git a/staging/src/k8s.io/api/batch/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/batch/v1/generated.protomessage.pb.go deleted file mode 100644 index 82928e07d1ede..0000000000000 --- a/staging/src/k8s.io/api/batch/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,56 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*CronJob) ProtoMessage() {} - -func (*CronJobList) ProtoMessage() {} - -func (*CronJobSpec) ProtoMessage() {} - -func (*CronJobStatus) ProtoMessage() {} - -func (*Job) ProtoMessage() {} - -func (*JobCondition) ProtoMessage() {} - -func (*JobList) ProtoMessage() {} - -func (*JobSpec) ProtoMessage() {} - -func (*JobStatus) ProtoMessage() {} - -func (*JobTemplateSpec) ProtoMessage() {} - -func (*PodFailurePolicy) ProtoMessage() {} - -func (*PodFailurePolicyOnExitCodesRequirement) ProtoMessage() {} - -func (*PodFailurePolicyOnPodConditionsPattern) ProtoMessage() {} - -func (*PodFailurePolicyRule) ProtoMessage() {} - -func (*SuccessPolicy) ProtoMessage() {} - -func (*SuccessPolicyRule) ProtoMessage() {} - -func (*UncountedTerminatedPods) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/batch/v1/types.go b/staging/src/k8s.io/api/batch/v1/types.go index 26d0ee6dc4a89..d4894e2ef62af 100644 --- a/staging/src/k8s.io/api/batch/v1/types.go +++ b/staging/src/k8s.io/api/batch/v1/types.go @@ -686,7 +686,7 @@ type CronJob struct { // Specification of the desired behavior of a cron job, including the schedule. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required Spec CronJobSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // Current status of a cron job. @@ -715,6 +715,8 @@ type CronJobList struct { type CronJobSpec struct { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Schedule string `json:"schedule" protobuf:"bytes,1,opt,name=schedule"` // The time zone name for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. diff --git a/staging/src/k8s.io/api/batch/v1beta1/generated.proto b/staging/src/k8s.io/api/batch/v1beta1/generated.proto index 6dd322128dc04..47000470c64e7 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1beta1/generated.proto @@ -39,7 +39,7 @@ message CronJob { // Specification of the desired behavior of a cron job, including the schedule. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required optional CronJobSpec spec = 2; // Current status of a cron job. @@ -62,6 +62,8 @@ message CronJobList { // CronJobSpec describes how the job execution will look like and when it will actually run. message CronJobSpec { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string schedule = 1; // The time zone name for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. diff --git a/staging/src/k8s.io/api/batch/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/batch/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 57520d7aff6db..0000000000000 --- a/staging/src/k8s.io/api/batch/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*CronJob) ProtoMessage() {} - -func (*CronJobList) ProtoMessage() {} - -func (*CronJobSpec) ProtoMessage() {} - -func (*CronJobStatus) ProtoMessage() {} - -func (*JobTemplateSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/batch/v1beta1/types.go b/staging/src/k8s.io/api/batch/v1beta1/types.go index 976752a926f36..9e1a1b4704b82 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/types.go +++ b/staging/src/k8s.io/api/batch/v1beta1/types.go @@ -52,7 +52,7 @@ type CronJob struct { // Specification of the desired behavior of a cron job, including the schedule. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required Spec CronJobSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // Current status of a cron job. @@ -84,6 +84,8 @@ type CronJobList struct { type CronJobSpec struct { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Schedule string `json:"schedule" protobuf:"bytes,1,opt,name=schedule"` // The time zone name for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. diff --git a/staging/src/k8s.io/api/certificates/v1/generated.proto b/staging/src/k8s.io/api/certificates/v1/generated.proto index a689f3e8920eb..a334db4e182c5 100644 --- a/staging/src/k8s.io/api/certificates/v1/generated.proto +++ b/staging/src/k8s.io/api/certificates/v1/generated.proto @@ -39,8 +39,8 @@ option go_package = "k8s.io/api/certificates/v1"; // This API can be used to request client certificates to authenticate to kube-apiserver // (with the "kubernetes.io/kube-apiserver-client" signerName), // or to obtain certificates from custom non-Kubernetes signers. -// +k8s:supportsSubresource=/status -// +k8s:supportsSubresource=/approval +// +k8s:supportsSubresource="/status" +// +k8s:supportsSubresource="/approval" message CertificateSigningRequest { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; @@ -204,12 +204,12 @@ message CertificateSigningRequestStatus { // +listType=map // +listMapKey=type // +optional - // +k8s:listType=map - // +k8s:listMapKey=type - // +k8s:customUnique - // +k8s:optional - // +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember - // +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=type + // +k8s:alpha(since: "1.36")=+k8s:customUnique + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember repeated CertificateSigningRequestCondition conditions = 1; // certificate is populated with an issued certificate by the signer after an Approved condition is present. diff --git a/staging/src/k8s.io/api/certificates/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/certificates/v1/generated.protomessage.pb.go deleted file mode 100644 index 527630582c77a..0000000000000 --- a/staging/src/k8s.io/api/certificates/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*CertificateSigningRequest) ProtoMessage() {} - -func (*CertificateSigningRequestCondition) ProtoMessage() {} - -func (*CertificateSigningRequestList) ProtoMessage() {} - -func (*CertificateSigningRequestSpec) ProtoMessage() {} - -func (*CertificateSigningRequestStatus) ProtoMessage() {} - -func (*ExtraValue) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/certificates/v1/types.go b/staging/src/k8s.io/api/certificates/v1/types.go index 8cd56e6db7e1d..9a86af13a8079 100644 --- a/staging/src/k8s.io/api/certificates/v1/types.go +++ b/staging/src/k8s.io/api/certificates/v1/types.go @@ -39,8 +39,8 @@ import ( // This API can be used to request client certificates to authenticate to kube-apiserver // (with the "kubernetes.io/kube-apiserver-client" signerName), // or to obtain certificates from custom non-Kubernetes signers. -// +k8s:supportsSubresource=/status -// +k8s:supportsSubresource=/approval +// +k8s:supportsSubresource="/status" +// +k8s:supportsSubresource="/approval" type CertificateSigningRequest struct { metav1.TypeMeta `json:",inline"` // +optional @@ -179,12 +179,12 @@ type CertificateSigningRequestStatus struct { // +listType=map // +listMapKey=type // +optional - // +k8s:listType=map - // +k8s:listMapKey=type - // +k8s:customUnique - // +k8s:optional - // +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember - // +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=type + // +k8s:alpha(since: "1.36")=+k8s:customUnique + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember Conditions []CertificateSigningRequestCondition `json:"conditions,omitempty" protobuf:"bytes,1,rep,name=conditions"` // certificate is populated with an issued certificate by the signer after an Approved condition is present. diff --git a/staging/src/k8s.io/api/certificates/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/certificates/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index 81dd80e28a926..0000000000000 --- a/staging/src/k8s.io/api/certificates/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*ClusterTrustBundle) ProtoMessage() {} - -func (*ClusterTrustBundleList) ProtoMessage() {} - -func (*ClusterTrustBundleSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/certificates/v1beta1/generated.pb.go b/staging/src/k8s.io/api/certificates/v1beta1/generated.pb.go index 5a8b8f2d13b5f..f375246a4b32a 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/generated.pb.go @@ -642,6 +642,13 @@ func (m *PodCertificateRequestSpec) MarshalToSizedBuffer(dAtA []byte) (int, erro _ = i var l int _ = l + if m.StubPKCS10Request != nil { + i -= len(m.StubPKCS10Request) + copy(dAtA[i:], m.StubPKCS10Request) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StubPKCS10Request))) + i-- + dAtA[i] = 0x62 + } if len(m.UnverifiedUserAnnotations) > 0 { keysForUnverifiedUserAnnotations := make([]string, 0, len(m.UnverifiedUserAnnotations)) for k := range m.UnverifiedUserAnnotations { @@ -1058,6 +1065,10 @@ func (m *PodCertificateRequestSpec) Size() (n int) { n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } + if m.StubPKCS10Request != nil { + l = len(m.StubPKCS10Request) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -1274,6 +1285,7 @@ func (this *PodCertificateRequestSpec) String() string { `PKIXPublicKey:` + valueToStringGenerated(this.PKIXPublicKey) + `,`, `ProofOfPossession:` + valueToStringGenerated(this.ProofOfPossession) + `,`, `UnverifiedUserAnnotations:` + mapStringForUnverifiedUserAnnotations + `,`, + `StubPKCS10Request:` + valueToStringGenerated(this.StubPKCS10Request) + `,`, `}`, }, "") return s @@ -3490,6 +3502,40 @@ func (m *PodCertificateRequestSpec) Unmarshal(dAtA []byte) error { } m.UnverifiedUserAnnotations[mapkey] = mapvalue iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StubPKCS10Request", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StubPKCS10Request = append(m.StubPKCS10Request[:0], dAtA[iNdEx:postIndex]...) + if m.StubPKCS10Request == nil { + m.StubPKCS10Request = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/certificates/v1beta1/generated.proto b/staging/src/k8s.io/api/certificates/v1beta1/generated.proto index a8ffad4727f54..cc46dee16d1d9 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/certificates/v1beta1/generated.proto @@ -30,8 +30,8 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; option go_package = "k8s.io/api/certificates/v1beta1"; // Describes a certificate signing request -// +k8s:supportsSubresource=/status -// +k8s:supportsSubresource=/approval +// +k8s:supportsSubresource="/status" +// +k8s:supportsSubresource="/approval" message CertificateSigningRequest { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; @@ -183,12 +183,12 @@ message CertificateSigningRequestStatus { // +listType=map // +listMapKey=type // +optional - // +k8s:listType=map - // +k8s:listMapKey=type - // +k8s:customUnique - // +k8s:optional - // +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember - // +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=type + // +k8s:alpha(since: "1.36")=+k8s:customUnique + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember repeated CertificateSigningRequestCondition conditions = 1; // If request was approved, the controller will place the issued certificate here. @@ -289,6 +289,7 @@ message PodCertificateRequest { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // spec contains the details about the certificate being requested. + // +required optional PodCertificateRequestSpec spec = 2; // status contains the issued certificate, and a standard set of conditions. @@ -369,8 +370,7 @@ message PodCertificateRequestSpec { // +default=86400 optional int32 maxExpirationSeconds = 8; - // pkixPublicKey is the PKIX-serialized public key the signer will issue the - // certificate to. + // The PKIX-serialized public key the signer will issue the certificate to. // // The key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, // or ED25519. Note that this list may be expanded in the future. @@ -382,11 +382,16 @@ message PodCertificateRequestSpec { // "UnsupportedKeyType". It may also suggest a key type that it does support // in the message field. // - // +required + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. Signer + // implementations should extract the public key from the StubPKCS10Request + // field. + // + // +optional optional bytes pkixPublicKey = 9; - // proofOfPossession proves that the requesting kubelet holds the private - // key corresponding to pkixPublicKey. + // A proof that the requesting kubelet holds the private key corresponding + // to pkixPublicKey. // // It is contructed by signing the ASCII bytes of the pod's UID using // `pkixPublicKey`. @@ -403,12 +408,35 @@ message PodCertificateRequestSpec { // golang library function crypto/ecdsa.SignASN1) // // If the key is an ED25519 key, the the signature is as described by the - // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by - // the golang library crypto/ed25519.Sign). + // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the + // golang library crypto/ed25519.Sign). // - // +required + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. + // + // +optional optional bytes proofOfPossession = 10; + // A PKCS#10 certificate signing request (DER-serialized) generated by + // Kubelet using the subject private key. + // + // Most signer implementations will ignore the contents of the CSR except to + // extract the subject public key. The API server automatically verifies the + // CSR signature during admission, so the signer does not need to repeat the + // verification. CSRs generated by kubelet are completely empty. + // + // The subject public key must be one of RSA3072, RSA4096, ECDSAP256, + // ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in + // the future. + // + // Signer implementations do not need to support all key types supported by + // kube-apiserver and kubelet. If a signer does not support the key type + // used for a given PodCertificateRequest, it must deny the request by + // setting a status.conditions entry with a type of "Denied" and a reason of + // "UnsupportedKeyType". It may also suggest a key type that it does support + // in the message field. + optional bytes stubPKCS10Request = 12; + // unverifiedUserAnnotations allow pod authors to pass additional information to // the signer implementation. Kubernetes does not restrict or validate this // metadata in any way. diff --git a/staging/src/k8s.io/api/certificates/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/certificates/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 00ba7faaeb6e6..0000000000000 --- a/staging/src/k8s.io/api/certificates/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*CertificateSigningRequest) ProtoMessage() {} - -func (*CertificateSigningRequestCondition) ProtoMessage() {} - -func (*CertificateSigningRequestList) ProtoMessage() {} - -func (*CertificateSigningRequestSpec) ProtoMessage() {} - -func (*CertificateSigningRequestStatus) ProtoMessage() {} - -func (*ClusterTrustBundle) ProtoMessage() {} - -func (*ClusterTrustBundleList) ProtoMessage() {} - -func (*ClusterTrustBundleSpec) ProtoMessage() {} - -func (*ExtraValue) ProtoMessage() {} - -func (*PodCertificateRequest) ProtoMessage() {} - -func (*PodCertificateRequestList) ProtoMessage() {} - -func (*PodCertificateRequestSpec) ProtoMessage() {} - -func (*PodCertificateRequestStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/certificates/v1beta1/types.go b/staging/src/k8s.io/api/certificates/v1beta1/types.go index acfabbfe9fd65..e8ffa41e6d28f 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/types.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/types.go @@ -32,8 +32,8 @@ import ( // +k8s:prerelease-lifecycle-gen:replacement=certificates.k8s.io,v1,CertificateSigningRequest // Describes a certificate signing request -// +k8s:supportsSubresource=/status -// +k8s:supportsSubresource=/approval +// +k8s:supportsSubresource="/status" +// +k8s:supportsSubresource="/approval" type CertificateSigningRequest struct { metav1.TypeMeta `json:",inline"` // +optional @@ -177,12 +177,12 @@ type CertificateSigningRequestStatus struct { // +listType=map // +listMapKey=type // +optional - // +k8s:listType=map - // +k8s:listMapKey=type - // +k8s:customUnique - // +k8s:optional - // +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember - // +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=type + // +k8s:alpha(since: "1.36")=+k8s:customUnique + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember + // +k8s:alpha(since: "1.36")=+k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember Conditions []CertificateSigningRequestCondition `json:"conditions,omitempty" protobuf:"bytes,1,rep,name=conditions"` // If request was approved, the controller will place the issued certificate here. @@ -273,6 +273,7 @@ const ( // +genclient // +genclient:nonNamespaced // +k8s:prerelease-lifecycle-gen:introduced=1.33 +// +k8s:prerelease-lifecycle-gen:deprecated=1.37 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ClusterTrustBundle is a cluster-scoped container for X.509 trust anchors @@ -340,6 +341,7 @@ type ClusterTrustBundleSpec struct { } // +k8s:prerelease-lifecycle-gen:introduced=1.33 +// +k8s:prerelease-lifecycle-gen:deprecated=1.37 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ClusterTrustBundleList is a collection of ClusterTrustBundle objects @@ -372,6 +374,7 @@ type PodCertificateRequest struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // spec contains the details about the certificate being requested. + // +required Spec PodCertificateRequestSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // status contains the issued certificate, and a standard set of conditions. @@ -438,8 +441,7 @@ type PodCertificateRequestSpec struct { // +default=86400 MaxExpirationSeconds *int32 `json:"maxExpirationSeconds,omitempty" protobuf:"varint,8,opt,name=maxExpirationSeconds"` - // pkixPublicKey is the PKIX-serialized public key the signer will issue the - // certificate to. + // The PKIX-serialized public key the signer will issue the certificate to. // // The key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, // or ED25519. Note that this list may be expanded in the future. @@ -451,11 +453,16 @@ type PodCertificateRequestSpec struct { // "UnsupportedKeyType". It may also suggest a key type that it does support // in the message field. // - // +required + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. Signer + // implementations should extract the public key from the StubPKCS10Request + // field. + // + // +optional PKIXPublicKey []byte `json:"pkixPublicKey" protobuf:"bytes,9,opt,name=pkixPublicKey"` - // proofOfPossession proves that the requesting kubelet holds the private - // key corresponding to pkixPublicKey. + // A proof that the requesting kubelet holds the private key corresponding + // to pkixPublicKey. // // It is contructed by signing the ASCII bytes of the pod's UID using // `pkixPublicKey`. @@ -472,12 +479,35 @@ type PodCertificateRequestSpec struct { // golang library function crypto/ecdsa.SignASN1) // // If the key is an ED25519 key, the the signature is as described by the - // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by - // the golang library crypto/ed25519.Sign). + // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the + // golang library crypto/ed25519.Sign). // - // +required + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. + // + // +optional ProofOfPossession []byte `json:"proofOfPossession" protobuf:"bytes,10,opt,name=proofOfPossession"` + // A PKCS#10 certificate signing request (DER-serialized) generated by + // Kubelet using the subject private key. + // + // Most signer implementations will ignore the contents of the CSR except to + // extract the subject public key. The API server automatically verifies the + // CSR signature during admission, so the signer does not need to repeat the + // verification. CSRs generated by kubelet are completely empty. + // + // The subject public key must be one of RSA3072, RSA4096, ECDSAP256, + // ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in + // the future. + // + // Signer implementations do not need to support all key types supported by + // kube-apiserver and kubelet. If a signer does not support the key type + // used for a given PodCertificateRequest, it must deny the request by + // setting a status.conditions entry with a type of "Denied" and a reason of + // "UnsupportedKeyType". It may also suggest a key type that it does support + // in the message field. + StubPKCS10Request []byte `json:"stubPKCS10Request" protobuf:"bytes,12,opt,name=stubPKCS10Request"` + // unverifiedUserAnnotations allow pod authors to pass additional information to // the signer implementation. Kubernetes does not restrict or validate this // metadata in any way. diff --git a/staging/src/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go index bd829fb216bb8..cad8768a59a35 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go @@ -136,8 +136,9 @@ var map_PodCertificateRequestSpec = map[string]string{ "nodeName": "nodeName is the name of the node the pod is assigned to.", "nodeUID": "nodeUID is the UID of the node the pod is assigned to.", "maxExpirationSeconds": "maxExpirationSeconds is the maximum lifetime permitted for the certificate.\n\nIf omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver will reject values shorter than 3600 (1 hour). The maximum allowable value is 7862400 (91 days).\n\nThe signer implementation is then free to issue a certificate with any lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 seconds (1 hour). This constraint is enforced by kube-apiserver. `kubernetes.io` signers will never issue certificates with a lifetime longer than 24 hours.", - "pkixPublicKey": "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", - "proofOfPossession": "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).", + "pkixPublicKey": "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty. Signer implementations should extract the public key from the StubPKCS10Request field.", + "proofOfPossession": "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf the key is an ED25519 key, the the signature is as described by the [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the golang library crypto/ed25519.Sign).\n\nDeprecated: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.", + "stubPKCS10Request": "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost signer implementations will ignore the contents of the CSR except to extract the subject public key. The API server automatically verifies the CSR signature during admission, so the signer does not need to repeat the verification. CSRs generated by kubelet are completely empty.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner implementations do not need to support all key types supported by kube-apiserver and kubelet. If a signer does not support the key type used for a given PodCertificateRequest, it must deny the request by setting a status.conditions entry with a type of \"Denied\" and a reason of \"UnsupportedKeyType\". It may also suggest a key type that it does support in the message field.", "unverifiedUserAnnotations": "unverifiedUserAnnotations allow pod authors to pass additional information to the signer implementation. Kubernetes does not restrict or validate this metadata in any way.\n\nEntries are subject to the same validation as object metadata annotations, with the addition that all keys must be domain-prefixed. No restrictions are placed on values, except an overall size limitation on the entire field.\n\nSigners should document the keys and values they support. Signers should deny requests that contain keys they do not recognize.", } diff --git a/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go index 20b5c2a24262b..417b8a3e655cd 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go @@ -364,6 +364,11 @@ func (in *PodCertificateRequestSpec) DeepCopyInto(out *PodCertificateRequestSpec *out = make([]byte, len(*in)) copy(*out, *in) } + if in.StubPKCS10Request != nil { + in, out := &in.StubPKCS10Request, &out.StubPKCS10Request + *out = make([]byte, len(*in)) + copy(*out, *in) + } if in.UnverifiedUserAnnotations != nil { in, out := &in.UnverifiedUserAnnotations, &out.UnverifiedUserAnnotations *out = make(map[string]string, len(*in)) diff --git a/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go index f95e329e2dbc2..d541ffb0055e6 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go @@ -82,13 +82,13 @@ func (in *ClusterTrustBundle) APILifecycleIntroduced() (major, minor int) { // APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. func (in *ClusterTrustBundle) APILifecycleDeprecated() (major, minor int) { - return 1, 36 + return 1, 37 } // APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. func (in *ClusterTrustBundle) APILifecycleRemoved() (major, minor int) { - return 1, 39 + return 1, 40 } // APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. @@ -100,13 +100,13 @@ func (in *ClusterTrustBundleList) APILifecycleIntroduced() (major, minor int) { // APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. func (in *ClusterTrustBundleList) APILifecycleDeprecated() (major, minor int) { - return 1, 36 + return 1, 37 } // APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. func (in *ClusterTrustBundleList) APILifecycleRemoved() (major, minor int) { - return 1, 39 + return 1, 40 } // APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. diff --git a/staging/src/k8s.io/api/coordination/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/coordination/v1/generated.protomessage.pb.go deleted file mode 100644 index f9210fce6a5fa..0000000000000 --- a/staging/src/k8s.io/api/coordination/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Lease) ProtoMessage() {} - -func (*LeaseList) ProtoMessage() {} - -func (*LeaseSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/coordination/v1alpha2/generated.proto b/staging/src/k8s.io/api/coordination/v1alpha2/generated.proto index 250c6113ec1f1..379fcf0d5d0cc 100644 --- a/staging/src/k8s.io/api/coordination/v1alpha2/generated.proto +++ b/staging/src/k8s.io/api/coordination/v1alpha2/generated.proto @@ -38,7 +38,7 @@ message LeaseCandidate { // spec contains the specification of the Lease. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required optional LeaseCandidateSpec spec = 2; } diff --git a/staging/src/k8s.io/api/coordination/v1alpha2/generated.protomessage.pb.go b/staging/src/k8s.io/api/coordination/v1alpha2/generated.protomessage.pb.go deleted file mode 100644 index 93743262291a9..0000000000000 --- a/staging/src/k8s.io/api/coordination/v1alpha2/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha2 - -func (*LeaseCandidate) ProtoMessage() {} - -func (*LeaseCandidateList) ProtoMessage() {} - -func (*LeaseCandidateSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/coordination/v1alpha2/types.go b/staging/src/k8s.io/api/coordination/v1alpha2/types.go index 13e1deb067d3b..f8a6d33af1131 100644 --- a/staging/src/k8s.io/api/coordination/v1alpha2/types.go +++ b/staging/src/k8s.io/api/coordination/v1alpha2/types.go @@ -35,7 +35,7 @@ type LeaseCandidate struct { // spec contains the specification of the Lease. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required Spec LeaseCandidateSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } diff --git a/staging/src/k8s.io/api/coordination/v1beta1/generated.proto b/staging/src/k8s.io/api/coordination/v1beta1/generated.proto index 7ca043f5288e4..74df40c64feab 100644 --- a/staging/src/k8s.io/api/coordination/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/coordination/v1beta1/generated.proto @@ -50,7 +50,7 @@ message LeaseCandidate { // spec contains the specification of the Lease. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required optional LeaseCandidateSpec spec = 2; } diff --git a/staging/src/k8s.io/api/coordination/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/coordination/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index a1a88e55ff019..0000000000000 --- a/staging/src/k8s.io/api/coordination/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*Lease) ProtoMessage() {} - -func (*LeaseCandidate) ProtoMessage() {} - -func (*LeaseCandidateList) ProtoMessage() {} - -func (*LeaseCandidateSpec) ProtoMessage() {} - -func (*LeaseList) ProtoMessage() {} - -func (*LeaseSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/coordination/v1beta1/types.go b/staging/src/k8s.io/api/coordination/v1beta1/types.go index 781d29efce175..ba6d6aa095ab9 100644 --- a/staging/src/k8s.io/api/coordination/v1beta1/types.go +++ b/staging/src/k8s.io/api/coordination/v1beta1/types.go @@ -106,7 +106,7 @@ type LeaseCandidate struct { // spec contains the specification of the Lease. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required Spec LeaseCandidateSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } diff --git a/staging/src/k8s.io/api/core/v1/generated.pb.go b/staging/src/k8s.io/api/core/v1/generated.pb.go index b7de1bea54d37..0fcbd5edf7b2f 100644 --- a/staging/src/k8s.io/api/core/v1/generated.pb.go +++ b/staging/src/k8s.io/api/core/v1/generated.pb.go @@ -196,6 +196,8 @@ func (m *ISCSIVolumeSource) Reset() { *m = ISCSIVolumeSource{} } func (m *ImageVolumeSource) Reset() { *m = ImageVolumeSource{} } +func (m *ImageVolumeStatus) Reset() { *m = ImageVolumeStatus{} } + func (m *KeyToPath) Reset() { *m = KeyToPath{} } func (m *Lifecycle) Reset() { *m = Lifecycle{} } @@ -242,6 +244,8 @@ func (m *NodeAddress) Reset() { *m = NodeAddress{} } func (m *NodeAffinity) Reset() { *m = NodeAffinity{} } +func (m *NodeAllocatableResourceClaimStatus) Reset() { *m = NodeAllocatableResourceClaimStatus{} } + func (m *NodeCondition) Reset() { *m = NodeCondition{} } func (m *NodeConfigSource) Reset() { *m = NodeConfigSource{} } @@ -346,6 +350,8 @@ func (m *PodResourceClaimStatus) Reset() { *m = PodResourceClaimStatus{} } func (m *PodSchedulingGate) Reset() { *m = PodSchedulingGate{} } +func (m *PodSchedulingGroup) Reset() { *m = PodSchedulingGroup{} } + func (m *PodSecurityContext) Reset() { *m = PodSecurityContext{} } func (m *PodSignature) Reset() { *m = PodSignature{} } @@ -504,14 +510,14 @@ func (m *VolumeResourceRequirements) Reset() { *m = VolumeResourceRequirements{} func (m *VolumeSource) Reset() { *m = VolumeSource{} } +func (m *VolumeStatus) Reset() { *m = VolumeStatus{} } + func (m *VsphereVirtualDiskVolumeSource) Reset() { *m = VsphereVirtualDiskVolumeSource{} } func (m *WeightedPodAffinityTerm) Reset() { *m = WeightedPodAffinityTerm{} } func (m *WindowsSecurityContextOptions) Reset() { *m = WindowsSecurityContextOptions{} } -func (m *WorkloadReference) Reset() { *m = WorkloadReference{} } - func (m *AWSElasticBlockStoreVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -5155,6 +5161,34 @@ func (m *ImageVolumeSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ImageVolumeStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ImageVolumeStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ImageVolumeStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.ImageRef) + copy(dAtA[i:], m.ImageRef) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ImageRef))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *KeyToPath) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6291,6 +6325,72 @@ func (m *NodeAffinity) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *NodeAllocatableResourceClaimStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NodeAllocatableResourceClaimStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *NodeAllocatableResourceClaimStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Resources) > 0 { + keysForResources := make([]string, 0, len(m.Resources)) + for k := range m.Resources { + keysForResources = append(keysForResources, string(k)) + } + sort.Strings(keysForResources) + for iNdEx := len(keysForResources) - 1; iNdEx >= 0; iNdEx-- { + v := m.Resources[ResourceName(keysForResources[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForResources[iNdEx]) + copy(dAtA[i:], keysForResources[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForResources[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Containers) > 0 { + for iNdEx := len(m.Containers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Containers[iNdEx]) + copy(dAtA[i:], m.Containers[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Containers[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.ResourceClaimName) + copy(dAtA[i:], m.ResourceClaimName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceClaimName))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *NodeCondition) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -9446,6 +9546,36 @@ func (m *PodSchedulingGate) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PodSchedulingGroup) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodSchedulingGroup) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodSchedulingGroup) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.PodGroupName != nil { + i -= len(*m.PodGroupName) + copy(dAtA[i:], *m.PodGroupName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PodGroupName))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *PodSecurityContext) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -9639,9 +9769,9 @@ func (m *PodSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.WorkloadRef != nil { + if m.SchedulingGroup != nil { { - size, err := m.WorkloadRef.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.SchedulingGroup.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -9651,7 +9781,7 @@ func (m *PodSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2 i-- - dAtA[i] = 0xd2 + dAtA[i] = 0xda } if m.HostnameOverride != nil { i -= len(*m.HostnameOverride) @@ -10150,6 +10280,22 @@ func (m *PodStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.NodeAllocatableResourceClaimStatuses) > 0 { + for iNdEx := len(m.NodeAllocatableResourceClaimStatuses) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.NodeAllocatableResourceClaimStatuses[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xaa + } + } if m.Resources != nil { { size, err := m.Resources.MarshalToSizedBuffer(dAtA[:i]) @@ -11532,6 +11678,13 @@ func (m *ResourceHealth) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Message != nil { + i -= len(*m.Message) + copy(dAtA[i:], *m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Message))) + i-- + dAtA[i] = 0x32 + } i -= len(m.Health) copy(dAtA[i:], m.Health) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Health))) @@ -14149,6 +14302,18 @@ func (m *VolumeMountStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.VolumeStatus != nil { + { + size, err := m.VolumeStatus.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } if m.RecursiveReadOnly != nil { i -= len(*m.RecursiveReadOnly) copy(dAtA[i:], *m.RecursiveReadOnly) @@ -14801,6 +14966,41 @@ func (m *VolumeSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *VolumeStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VolumeStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Image != nil { + { + size, err := m.Image.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *VsphereVirtualDiskVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14934,44 +15134,6 @@ func (m *WindowsSecurityContextOptions) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } -func (m *WorkloadReference) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *WorkloadReference) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *WorkloadReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.PodGroupReplicaKey) - copy(dAtA[i:], m.PodGroupReplicaKey) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.PodGroupReplicaKey))) - i-- - dAtA[i] = 0x1a - i -= len(m.PodGroup) - copy(dAtA[i:], m.PodGroup) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.PodGroup))) - i-- - dAtA[i] = 0x12 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset @@ -16706,6 +16868,17 @@ func (m *ImageVolumeSource) Size() (n int) { return n } +func (m *ImageVolumeStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ImageRef) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *KeyToPath) Size() (n int) { if m == nil { return 0 @@ -17123,6 +17296,32 @@ func (m *NodeAffinity) Size() (n int) { return n } +func (m *NodeAllocatableResourceClaimStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ResourceClaimName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Containers) > 0 { + for _, s := range m.Containers { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Resources) > 0 { + for k, v := range m.Resources { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + return n +} + func (m *NodeCondition) Size() (n int) { if m == nil { return 0 @@ -18296,6 +18495,19 @@ func (m *PodSchedulingGate) Size() (n int) { return n } +func (m *PodSchedulingGroup) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PodGroupName != nil { + l = len(*m.PodGroupName) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *PodSecurityContext) Size() (n int) { if m == nil { return 0 @@ -18535,8 +18747,8 @@ func (m *PodSpec) Size() (n int) { l = len(*m.HostnameOverride) n += 2 + l + sovGenerated(uint64(l)) } - if m.WorkloadRef != nil { - l = m.WorkloadRef.Size() + if m.SchedulingGroup != nil { + l = m.SchedulingGroup.Size() n += 2 + l + sovGenerated(uint64(l)) } return n @@ -18628,6 +18840,12 @@ func (m *PodStatus) Size() (n int) { l = m.Resources.Size() n += 2 + l + sovGenerated(uint64(l)) } + if len(m.NodeAllocatableResourceClaimStatuses) > 0 { + for _, e := range m.NodeAllocatableResourceClaimStatuses { + l = e.Size() + n += 2 + l + sovGenerated(uint64(l)) + } + } return n } @@ -19052,6 +19270,10 @@ func (m *ResourceHealth) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.Health) n += 1 + l + sovGenerated(uint64(l)) + if m.Message != nil { + l = len(*m.Message) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -20041,6 +20263,10 @@ func (m *VolumeMountStatus) Size() (n int) { l = len(*m.RecursiveReadOnly) n += 1 + l + sovGenerated(uint64(l)) } + if m.VolumeStatus != nil { + l = m.VolumeStatus.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -20246,6 +20472,19 @@ func (m *VolumeSource) Size() (n int) { return n } +func (m *VolumeStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Image != nil { + l = m.Image.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *VsphereVirtualDiskVolumeSource) Size() (n int) { if m == nil { return 0 @@ -20299,21 +20538,6 @@ func (m *WindowsSecurityContextOptions) Size() (n int) { return n } -func (m *WorkloadReference) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.PodGroup) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.PodGroupReplicaKey) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -21595,6 +21819,16 @@ func (this *ImageVolumeSource) String() string { }, "") return s } +func (this *ImageVolumeStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ImageVolumeStatus{`, + `ImageRef:` + fmt.Sprintf("%v", this.ImageRef) + `,`, + `}`, + }, "") + return s +} func (this *KeyToPath) String() string { if this == nil { return "nil" @@ -21951,6 +22185,28 @@ func (this *NodeAffinity) String() string { }, "") return s } +func (this *NodeAllocatableResourceClaimStatus) String() string { + if this == nil { + return "nil" + } + keysForResources := make([]string, 0, len(this.Resources)) + for k := range this.Resources { + keysForResources = append(keysForResources, string(k)) + } + sort.Strings(keysForResources) + mapStringForResources := "map[ResourceName]resource.Quantity{" + for _, k := range keysForResources { + mapStringForResources += fmt.Sprintf("%v: %v,", k, this.Resources[ResourceName(k)]) + } + mapStringForResources += "}" + s := strings.Join([]string{`&NodeAllocatableResourceClaimStatus{`, + `ResourceClaimName:` + fmt.Sprintf("%v", this.ResourceClaimName) + `,`, + `Containers:` + fmt.Sprintf("%v", this.Containers) + `,`, + `Resources:` + mapStringForResources + `,`, + `}`, + }, "") + return s +} func (this *NodeCondition) String() string { if this == nil { return "nil" @@ -22800,6 +23056,16 @@ func (this *PodSchedulingGate) String() string { }, "") return s } +func (this *PodSchedulingGroup) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodSchedulingGroup{`, + `PodGroupName:` + valueToStringGenerated(this.PodGroupName) + `,`, + `}`, + }, "") + return s +} func (this *PodSecurityContext) String() string { if this == nil { return "nil" @@ -22958,7 +23224,7 @@ func (this *PodSpec) String() string { `ResourceClaims:` + repeatedStringForResourceClaims + `,`, `Resources:` + strings.Replace(this.Resources.String(), "ResourceRequirements", "ResourceRequirements", 1) + `,`, `HostnameOverride:` + valueToStringGenerated(this.HostnameOverride) + `,`, - `WorkloadRef:` + strings.Replace(this.WorkloadRef.String(), "WorkloadReference", "WorkloadReference", 1) + `,`, + `SchedulingGroup:` + strings.Replace(this.SchedulingGroup.String(), "PodSchedulingGroup", "PodSchedulingGroup", 1) + `,`, `}`, }, "") return s @@ -23002,6 +23268,11 @@ func (this *PodStatus) String() string { repeatedStringForHostIPs += strings.Replace(strings.Replace(f.String(), "HostIP", "HostIP", 1), `&`, ``, 1) + "," } repeatedStringForHostIPs += "}" + repeatedStringForNodeAllocatableResourceClaimStatuses := "[]NodeAllocatableResourceClaimStatus{" + for _, f := range this.NodeAllocatableResourceClaimStatuses { + repeatedStringForNodeAllocatableResourceClaimStatuses += strings.Replace(strings.Replace(f.String(), "NodeAllocatableResourceClaimStatus", "NodeAllocatableResourceClaimStatus", 1), `&`, ``, 1) + "," + } + repeatedStringForNodeAllocatableResourceClaimStatuses += "}" keysForAllocatedResources := make([]string, 0, len(this.AllocatedResources)) for k := range this.AllocatedResources { keysForAllocatedResources = append(keysForAllocatedResources, string(k)) @@ -23033,6 +23304,7 @@ func (this *PodStatus) String() string { `ExtendedResourceClaimStatus:` + strings.Replace(this.ExtendedResourceClaimStatus.String(), "PodExtendedResourceClaimStatus", "PodExtendedResourceClaimStatus", 1) + `,`, `AllocatedResources:` + mapStringForAllocatedResources + `,`, `Resources:` + strings.Replace(this.Resources.String(), "ResourceRequirements", "ResourceRequirements", 1) + `,`, + `NodeAllocatableResourceClaimStatuses:` + repeatedStringForNodeAllocatableResourceClaimStatuses + `,`, `}`, }, "") return s @@ -23365,6 +23637,7 @@ func (this *ResourceHealth) String() string { s := strings.Join([]string{`&ResourceHealth{`, `ResourceID:` + fmt.Sprintf("%v", this.ResourceID) + `,`, `Health:` + fmt.Sprintf("%v", this.Health) + `,`, + `Message:` + valueToStringGenerated(this.Message) + `,`, `}`, }, "") return s @@ -24118,6 +24391,7 @@ func (this *VolumeMountStatus) String() string { `MountPath:` + fmt.Sprintf("%v", this.MountPath) + `,`, `ReadOnly:` + fmt.Sprintf("%v", this.ReadOnly) + `,`, `RecursiveReadOnly:` + valueToStringGenerated(this.RecursiveReadOnly) + `,`, + `VolumeStatus:` + strings.Replace(this.VolumeStatus.String(), "VolumeStatus", "VolumeStatus", 1) + `,`, `}`, }, "") return s @@ -24217,6 +24491,16 @@ func (this *VolumeSource) String() string { }, "") return s } +func (this *VolumeStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeStatus{`, + `Image:` + strings.Replace(this.Image.String(), "ImageVolumeStatus", "ImageVolumeStatus", 1) + `,`, + `}`, + }, "") + return s +} func (this *VsphereVirtualDiskVolumeSource) String() string { if this == nil { return "nil" @@ -24254,18 +24538,6 @@ func (this *WindowsSecurityContextOptions) String() string { }, "") return s } -func (this *WorkloadReference) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&WorkloadReference{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `PodGroup:` + fmt.Sprintf("%v", this.PodGroup) + `,`, - `PodGroupReplicaKey:` + fmt.Sprintf("%v", this.PodGroupReplicaKey) + `,`, - `}`, - }, "") - return s -} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -39166,6 +39438,88 @@ func (m *ImageVolumeSource) Unmarshal(dAtA []byte) error { } return nil } +func (m *ImageVolumeStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ImageVolumeStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ImageVolumeStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ImageRef", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ImageRef = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *KeyToPath) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -42742,6 +43096,249 @@ func (m *NodeAffinity) Unmarshal(dAtA []byte) error { } return nil } +func (m *NodeAllocatableResourceClaimStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeAllocatableResourceClaimStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeAllocatableResourceClaimStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceClaimName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Containers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Containers = append(m.Containers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resources", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Resources == nil { + m.Resources = make(map[ResourceName]resource.Quantity) + } + var mapkey ResourceName + mapvalue := &resource.Quantity{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = ResourceName(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &resource.Quantity{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Resources[ResourceName(mapkey)] = *mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *NodeCondition) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -52496,7 +53093,155 @@ func (m *PodResourceClaim) Unmarshal(dAtA []byte) error { } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.ResourceClaimName = &s + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimTemplateName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.ResourceClaimTemplateName = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodResourceClaimStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodResourceClaimStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodResourceClaimStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimName", wireType) } @@ -52529,39 +53274,6 @@ func (m *PodResourceClaim) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.ResourceClaimName = &s iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimTemplateName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := string(dAtA[iNdEx:postIndex]) - m.ResourceClaimTemplateName = &s - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -52583,7 +53295,7 @@ func (m *PodResourceClaim) Unmarshal(dAtA []byte) error { } return nil } -func (m *PodResourceClaimStatus) Unmarshal(dAtA []byte) error { +func (m *PodSchedulingGate) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -52606,10 +53318,10 @@ func (m *PodResourceClaimStatus) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: PodResourceClaimStatus: wiretype end group for non-group") + return fmt.Errorf("proto: PodSchedulingGate: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: PodResourceClaimStatus: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: PodSchedulingGate: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -52644,39 +53356,6 @@ func (m *PodResourceClaimStatus) Unmarshal(dAtA []byte) error { } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := string(dAtA[iNdEx:postIndex]) - m.ResourceClaimName = &s - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -52698,7 +53377,7 @@ func (m *PodResourceClaimStatus) Unmarshal(dAtA []byte) error { } return nil } -func (m *PodSchedulingGate) Unmarshal(dAtA []byte) error { +func (m *PodSchedulingGroup) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -52721,15 +53400,15 @@ func (m *PodSchedulingGate) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: PodSchedulingGate: wiretype end group for non-group") + return fmt.Errorf("proto: PodSchedulingGroup: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: PodSchedulingGate: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: PodSchedulingGroup: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field PodGroupName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -52757,7 +53436,8 @@ func (m *PodSchedulingGate) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.PodGroupName = &s iNdEx = postIndex default: iNdEx = preIndex @@ -54801,9 +55481,9 @@ func (m *PodSpec) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.HostnameOverride = &s iNdEx = postIndex - case 42: + case 43: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WorkloadRef", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SchedulingGroup", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -54830,10 +55510,10 @@ func (m *PodSpec) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.WorkloadRef == nil { - m.WorkloadRef = &WorkloadReference{} + if m.SchedulingGroup == nil { + m.SchedulingGroup = &PodSchedulingGroup{} } - if err := m.WorkloadRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.SchedulingGroup.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -55637,6 +56317,40 @@ func (m *PodStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeAllocatableResourceClaimStatuses", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NodeAllocatableResourceClaimStatuses = append(m.NodeAllocatableResourceClaimStatuses, NodeAllocatableResourceClaimStatus{}) + if err := m.NodeAllocatableResourceClaimStatuses[len(m.NodeAllocatableResourceClaimStatuses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -59194,13 +59908,159 @@ func (m *ResourceClaim) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Request", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Request = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ResourceFieldSelector) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResourceFieldSelector: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResourceFieldSelector: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContainerName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContainerName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Resource = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Request", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Divisor", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -59210,23 +60070,24 @@ func (m *ResourceClaim) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Request = string(dAtA[iNdEx:postIndex]) + if err := m.Divisor.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -59249,7 +60110,7 @@ func (m *ResourceClaim) Unmarshal(dAtA []byte) error { } return nil } -func (m *ResourceFieldSelector) Unmarshal(dAtA []byte) error { +func (m *ResourceHealth) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -59272,15 +60133,15 @@ func (m *ResourceFieldSelector) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ResourceFieldSelector: wiretype end group for non-group") + return fmt.Errorf("proto: ResourceHealth: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ResourceFieldSelector: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ResourceHealth: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContainerName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ResourceID", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -59308,126 +60169,11 @@ func (m *ResourceFieldSelector) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ContainerName = string(dAtA[iNdEx:postIndex]) + m.ResourceID = ResourceID(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Resource = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Divisor", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Divisor.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResourceHealth) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResourceHealth: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResourceHealth: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ResourceID", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Health", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -59455,11 +60201,11 @@ func (m *ResourceHealth) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ResourceID = ResourceID(dAtA[iNdEx:postIndex]) + m.Health = ResourceHealthStatus(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Health", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -59487,7 +60233,8 @@ func (m *ResourceHealth) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Health = ResourceHealthStatus(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.Message = &s iNdEx = postIndex default: iNdEx = preIndex @@ -68123,6 +68870,42 @@ func (m *VolumeMountStatus) Unmarshal(dAtA []byte) error { s := RecursiveReadOnlyMode(dAtA[iNdEx:postIndex]) m.RecursiveReadOnly = &s iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeStatus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VolumeStatus == nil { + m.VolumeStatus = &VolumeStatus{} + } + if err := m.VolumeStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -69690,16 +70473,88 @@ func (m *VolumeSource) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.PortworxVolume == nil { - m.PortworxVolume = &PortworxVolumeSource{} + if m.PortworxVolume == nil { + m.PortworxVolume = &PortworxVolumeSource{} + } + if err := m.PortworxVolume.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 25: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ScaleIO", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ScaleIO == nil { + m.ScaleIO = &ScaleIOVolumeSource{} + } + if err := m.ScaleIO.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 26: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Projected", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Projected == nil { + m.Projected = &ProjectedVolumeSource{} } - if err := m.PortworxVolume.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Projected.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 25: + case 27: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ScaleIO", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StorageOS", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -69726,16 +70581,16 @@ func (m *VolumeSource) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ScaleIO == nil { - m.ScaleIO = &ScaleIOVolumeSource{} + if m.StorageOS == nil { + m.StorageOS = &StorageOSVolumeSource{} } - if err := m.ScaleIO.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.StorageOS.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 26: + case 28: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Projected", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CSI", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -69762,16 +70617,16 @@ func (m *VolumeSource) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Projected == nil { - m.Projected = &ProjectedVolumeSource{} + if m.CSI == nil { + m.CSI = &CSIVolumeSource{} } - if err := m.Projected.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.CSI.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 27: + case 29: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StorageOS", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Ephemeral", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -69798,16 +70653,16 @@ func (m *VolumeSource) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.StorageOS == nil { - m.StorageOS = &StorageOSVolumeSource{} + if m.Ephemeral == nil { + m.Ephemeral = &EphemeralVolumeSource{} } - if err := m.StorageOS.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Ephemeral.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 28: + case 30: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CSI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Image", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -69834,50 +70689,64 @@ func (m *VolumeSource) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.CSI == nil { - m.CSI = &CSIVolumeSource{} + if m.Image == nil { + m.Image = &ImageVolumeSource{} } - if err := m.CSI.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Image.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 29: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ephemeral", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err } - postIndex := iNdEx + msglen - if postIndex < 0 { + if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } - if postIndex > l { + if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } - if m.Ephemeral == nil { - m.Ephemeral = &EphemeralVolumeSource{} + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VolumeStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated } - if err := m.Ephemeral.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + if iNdEx >= l { + return io.ErrUnexpectedEOF } - iNdEx = postIndex - case 30: + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VolumeStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Image", wireType) } @@ -69907,7 +70776,7 @@ func (m *VolumeSource) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.Image == nil { - m.Image = &ImageVolumeSource{} + m.Image = &ImageVolumeStatus{} } if err := m.Image.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -70384,152 +71253,6 @@ func (m *WindowsSecurityContextOptions) Unmarshal(dAtA []byte) error { } return nil } -func (m *WorkloadReference) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: WorkloadReference: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: WorkloadReference: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PodGroup", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PodGroup = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PodGroupReplicaKey", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PodGroupReplicaKey = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/core/v1/generated.proto b/staging/src/k8s.io/api/core/v1/generated.proto index 570b4d3438928..ba4f80e5ec348 100644 --- a/staging/src/k8s.io/api/core/v1/generated.proto +++ b/staging/src/k8s.io/api/core/v1/generated.proto @@ -1132,7 +1132,6 @@ message ContainerStatus { // AllocatedResources represents the compute resources allocated for this container by the // node. Kubelet sets this value to Container.Resources.Requests upon successful pod admission // and after successfully admitting desired pod resize. - // +featureGate=InPlacePodVerticalScalingAllocatedStatus // +optional map allocatedResources = 10; @@ -2275,6 +2274,16 @@ message ImageVolumeSource { optional string pullPolicy = 2; } +// ImageVolumeStatus represents the image-based volume status. +message ImageVolumeStatus { + // ImageRef is the digest of the image used for this volume. + // It should have a value that's similar to the pod's status.containerStatuses[i].imageID. + // The ImageRef length should not exceed 256 characters. + // +kubebuilder:validation:MaxLength=256 + // +required + optional string imageRef = 1; +} + // Maps a string key to a path within a volume. message KeyToPath { // key is the key to project. @@ -2677,6 +2686,22 @@ message NodeAffinity { repeated PreferredSchedulingTerm preferredDuringSchedulingIgnoredDuringExecution = 2; } +// NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA. +message NodeAllocatableResourceClaimStatus { + // ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation. + // +required + optional string resourceClaimName = 1; + + // Containers lists the names of all containers in this pod that reference the claim. + // +optional + // +listType=set + repeated string containers = 2; + + // Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim. + // +required + map resources = 3; +} + // NodeCondition contains condition information for a node. message NodeCondition { // Type of node condition. @@ -2812,7 +2837,6 @@ message NodeRuntimeHandlerFeatures { optional bool recursiveReadOnlyMounts = 1; // UserNamespaces is set to true if the runtime handler supports UserNamespaces, including for volumes. - // +featureGate=UserNamespacesSupport // +optional optional bool userNamespaces = 2; } @@ -2974,7 +2998,6 @@ message NodeStatus { optional NodeConfigStatus config = 11; // The available runtime handlers. - // +featureGate=UserNamespacesSupport // +optional // +listType=atomic repeated NodeRuntimeHandler runtimeHandlers = 12; @@ -3528,8 +3551,7 @@ message PersistentVolumeSource { // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. // +optional optional PortworxVolumeSource portworxVolume = 18; @@ -3918,8 +3940,6 @@ message PodCondition { optional string type = 1; // If set, this represents the .metadata.generation that the pod condition was set based upon. - // The PodObservedGenerationTracking feature gate must be enabled to use this field. - // +featureGate=PodObservedGenerationTracking // +optional optional int64 observedGeneration = 7; @@ -4153,6 +4173,14 @@ message PodReadinessGate { // // It adds a name to it that uniquely identifies the ResourceClaim inside the Pod. // Containers that need access to the ResourceClaim reference it with this name. +// +// When the DRAWorkloadResourceClaims feature gate is enabled and this Pod +// belongs to a PodGroup, a PodResourceClaim is matched to a +// PodGroupResourceClaim if all of their fields are equal (Name, +// ResourceClaimName, and ResourceClaimTemplateName). A matched claim references +// a single ResourceClaim shared across all Pods in the PodGroup, reserved for +// the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual +// Pods. message PodResourceClaim { // Name uniquely identifies this resource claim inside the pod. // This must be a DNS_LABEL. @@ -4174,6 +4202,16 @@ message PodResourceClaim { // generated component, will be used to form a unique name for the // ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses. // + // When the DRAWorkloadResourceClaims feature gate is enabled and the pod + // belongs to a PodGroup that defines a PodGroupResourceClaim with the same + // Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the + // ResourceClaim generated for the PodGroup. All pods in the group that + // define an equivalent PodResourceClaim matching the + // PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same + // generated ResourceClaim. ResourceClaims generated for a PodGroup are + // owned by the PodGroup and their lifecycles are tied to the PodGroup + // instead of any individual pod. + // // This field is immutable and no changes will be made to the // corresponding ResourceClaim by the control plane after creating the // ResourceClaim. @@ -4193,9 +4231,16 @@ message PodResourceClaimStatus { optional string name = 1; // ResourceClaimName is the name of the ResourceClaim that was - // generated for the Pod in the namespace of the Pod. If this is - // unset, then generating a ResourceClaim was not necessary. The - // pod.spec.resourceClaims entry can be ignored in this case. + // generated for the Pod in the namespace of the Pod. + // + // When the DRAWorkloadResourceClaims feature is enabled and the + // corresponding PodResourceClaim matches a PodGroupResourceClaim + // made by the Pod's PodGroup, then this is the name of the + // ResourceClaim generated and reserved for the PodGroup. + // + // If this is unset, then generating a ResourceClaim was not + // necessary. The pod.spec.resourceClaims entry can be ignored in + // this case. // // +optional optional string resourceClaimName = 2; @@ -4208,6 +4253,20 @@ message PodSchedulingGate { optional string name = 1; } +// PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. +// The scheduler uses this information to apply workload-aware scheduling semantics. +// Exactly one field must be specified. +// +union +message PodSchedulingGroup { + // PodGroupName specifies the name of the standalone PodGroup object + // that represents the runtime instance of this group. + // Must be a DNS subdomain. + // + // +optional + // +oneOf=GroupSelection + optional string podGroupName = 1; +} + // PodSecurityContext holds pod-level security attributes and common container settings. // Some fields are also present in container.securityContext. Field values of // container.securityContext take precedence over field values of PodSecurityContext. @@ -4669,7 +4728,6 @@ message PodSpec { // When set to false, a new userns is created for the pod. Setting false is useful for // mitigating container breakout vulnerabilities even allowing users to run their // containers as root without actually having root privileges on the host. - // This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature. // +k8s:conversion-gen=false // +optional optional bool hostUsers = 37; @@ -4735,17 +4793,22 @@ message PodSpec { // +optional optional string hostnameOverride = 41; - // WorkloadRef provides a reference to the Workload object that this Pod belongs to. - // This field is used by the scheduler to identify the PodGroup and apply the - // correct group scheduling policies. The Workload object referenced - // by this field may not exist at the time the Pod is created. - // This field is immutable, but a Workload object with the same name - // may be recreated with different policies. Doing this during pod scheduling + // SchedulingGroup provides a reference to the immediate scheduling runtime + // grouping object that this Pod belongs to. + // This field is used by the scheduler to identify the group and apply the + // correct group scheduling policies. The association with a group also + // impacts other lifecycle aspects of a Pod that are relevant in a wider context + // of scheduling like preemption, resource attachment, etc. If not specified, + // the Pod is treated as a single unit in all of these aspects. + // The group object referenced by this field may not exist at the time the + // Pod is created. + // This field is immutable, but a group object with the same name may be + // recreated with different policies. Doing this during pod scheduling // may result in the placement not conforming to the expected policies. // // +featureGate=GenericWorkload // +optional - optional WorkloadReference workloadRef = 42; + optional PodSchedulingGroup schedulingGroup = 43; } // PodStatus represents information about the status of a pod. Status may trail the actual @@ -4754,7 +4817,6 @@ message PodSpec { message PodStatus { // If set, this represents the .metadata.generation that the pod status was set based upon. // The PodObservedGenerationTracking feature gate must be enabled to use this field. - // +featureGate=PodObservedGenerationTracking // +optional optional int64 observedGeneration = 17; @@ -4923,6 +4985,16 @@ message PodStatus { // +featureGate=InPlacePodLevelResourcesVerticalScaling // +optional optional ResourceRequirements resources = 20; + + // NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources + // that were allocated for this pod through DRA claims. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // +featureGate=DRANodeAllocatableResources + // +optional + // +listType=atomic + repeated NodeAllocatableResourceClaimStatus nodeAllocatableResourceClaimStatuses = 21; } // PodStatusResult is a wrapper for PodStatus returned by kubelet that can be encode/decoded @@ -5300,8 +5372,8 @@ message ReplicationController { // be the same as the Pod(s) that the replication controller manages. // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // Spec defines the specification of the desired behavior of the replication controller. @@ -5358,18 +5430,18 @@ message ReplicationControllerSpec { // Defaults to 1. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=1 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 optional int32 replicas = 1; // Minimum number of seconds for which a newly created pod should be ready // without any of its container crashing, for it to be considered available. // Defaults to 0 (pod will be considered available as soon as it is ready) // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 optional int32 minReadySeconds = 4; // Selector is a label query over pods that should match the Replicas count. @@ -5468,6 +5540,12 @@ message ResourceHealth { // // In future we may want to introduce the PermanentlyUnhealthy Status. optional string health = 2; + + // Message provides human-readable context for Health (e.g. "ECC error count exceeded threshold"). + // This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise. + // +featureGate=ResourceHealthStatusMessage + // +optional + optional string message = 6; } // ResourceQuota sets aggregate quota restrictions enforced per namespace @@ -5970,7 +6048,6 @@ message SecurityContext { // procMount denotes the type of proc mount to use for the containers. // The default value is Default which uses the container runtime defaults for // readonly paths and masked paths. - // This requires the ProcMountType feature flag to be enabled. // Note that this field cannot be set when spec.os.name is windows. // +optional optional string procMount = 9; @@ -6885,6 +6962,11 @@ message VolumeMountStatus { // depending on the mount result. // +optional optional string recursiveReadOnly = 4; + + // volumeStatus represents volume-type-specific status about the mounted + // volume. + // +optional + optional VolumeStatus volumeStatus = 5; } // VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from. @@ -7126,8 +7208,7 @@ message VolumeSource { // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. // +optional optional PortworxVolumeSource portworxVolume = 24; @@ -7184,14 +7265,22 @@ message VolumeSource { // A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. // The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. // The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - // The volume will be mounted read-only (ro) and non-executable files (noexec). + // The volume will be mounted read-only (ro). // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. - // +featureGate=ImageVolume // +optional optional ImageVolumeSource image = 30; } +// VolumeStatus represents the status of a mounted volume. +// At most one of its members must be specified. +message VolumeStatus { + // image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + // +featureGate=ImageVolumeWithDigest + // +optional + optional ImageVolumeStatus image = 1; +} + // Represents a vSphere volume resource. message VsphereVirtualDiskVolumeSource { // volumePath is the path that identifies vSphere volume vmdk @@ -7249,33 +7338,3 @@ message WindowsSecurityContextOptions { optional bool hostProcess = 4; } -// WorkloadReference identifies the Workload object and PodGroup membership -// that a Pod belongs to. The scheduler uses this information to apply -// workload-aware scheduling semantics. -message WorkloadReference { - // Name defines the name of the Workload object this Pod belongs to. - // Workload must be in the same namespace as the Pod. - // If it doesn't match any existing Workload, the Pod will remain unschedulable - // until a Workload object is created and observed by the kube-scheduler. - // It must be a DNS subdomain. - // - // +required - optional string name = 1; - - // PodGroup is the name of the PodGroup within the Workload that this Pod - // belongs to. If it doesn't match any existing PodGroup within the Workload, - // the Pod will remain unschedulable until the Workload object is recreated - // and observed by the kube-scheduler. It must be a DNS label. - // - // +required - optional string podGroup = 2; - - // PodGroupReplicaKey specifies the replica key of the PodGroup to which this - // Pod belongs. It is used to distinguish pods belonging to different replicas - // of the same pod group. The pod group policy is applied separately to each replica. - // When set, it must be a DNS label. - // - // +optional - optional string podGroupReplicaKey = 3; -} - diff --git a/staging/src/k8s.io/api/core/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/core/v1/generated.protomessage.pb.go deleted file mode 100644 index cf74ffa0885e2..0000000000000 --- a/staging/src/k8s.io/api/core/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,498 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*AWSElasticBlockStoreVolumeSource) ProtoMessage() {} - -func (*Affinity) ProtoMessage() {} - -func (*AppArmorProfile) ProtoMessage() {} - -func (*AttachedVolume) ProtoMessage() {} - -func (*AvoidPods) ProtoMessage() {} - -func (*AzureDiskVolumeSource) ProtoMessage() {} - -func (*AzureFilePersistentVolumeSource) ProtoMessage() {} - -func (*AzureFileVolumeSource) ProtoMessage() {} - -func (*Binding) ProtoMessage() {} - -func (*CSIPersistentVolumeSource) ProtoMessage() {} - -func (*CSIVolumeSource) ProtoMessage() {} - -func (*Capabilities) ProtoMessage() {} - -func (*CephFSPersistentVolumeSource) ProtoMessage() {} - -func (*CephFSVolumeSource) ProtoMessage() {} - -func (*CinderPersistentVolumeSource) ProtoMessage() {} - -func (*CinderVolumeSource) ProtoMessage() {} - -func (*ClientIPConfig) ProtoMessage() {} - -func (*ClusterTrustBundleProjection) ProtoMessage() {} - -func (*ComponentCondition) ProtoMessage() {} - -func (*ComponentStatus) ProtoMessage() {} - -func (*ComponentStatusList) ProtoMessage() {} - -func (*ConfigMap) ProtoMessage() {} - -func (*ConfigMapEnvSource) ProtoMessage() {} - -func (*ConfigMapKeySelector) ProtoMessage() {} - -func (*ConfigMapList) ProtoMessage() {} - -func (*ConfigMapNodeConfigSource) ProtoMessage() {} - -func (*ConfigMapProjection) ProtoMessage() {} - -func (*ConfigMapVolumeSource) ProtoMessage() {} - -func (*Container) ProtoMessage() {} - -func (*ContainerExtendedResourceRequest) ProtoMessage() {} - -func (*ContainerImage) ProtoMessage() {} - -func (*ContainerPort) ProtoMessage() {} - -func (*ContainerResizePolicy) ProtoMessage() {} - -func (*ContainerRestartRule) ProtoMessage() {} - -func (*ContainerRestartRuleOnExitCodes) ProtoMessage() {} - -func (*ContainerState) ProtoMessage() {} - -func (*ContainerStateRunning) ProtoMessage() {} - -func (*ContainerStateTerminated) ProtoMessage() {} - -func (*ContainerStateWaiting) ProtoMessage() {} - -func (*ContainerStatus) ProtoMessage() {} - -func (*ContainerUser) ProtoMessage() {} - -func (*DaemonEndpoint) ProtoMessage() {} - -func (*DownwardAPIProjection) ProtoMessage() {} - -func (*DownwardAPIVolumeFile) ProtoMessage() {} - -func (*DownwardAPIVolumeSource) ProtoMessage() {} - -func (*EmptyDirVolumeSource) ProtoMessage() {} - -func (*EndpointAddress) ProtoMessage() {} - -func (*EndpointPort) ProtoMessage() {} - -func (*EndpointSubset) ProtoMessage() {} - -func (*Endpoints) ProtoMessage() {} - -func (*EndpointsList) ProtoMessage() {} - -func (*EnvFromSource) ProtoMessage() {} - -func (*EnvVar) ProtoMessage() {} - -func (*EnvVarSource) ProtoMessage() {} - -func (*EphemeralContainer) ProtoMessage() {} - -func (*EphemeralContainerCommon) ProtoMessage() {} - -func (*EphemeralVolumeSource) ProtoMessage() {} - -func (*Event) ProtoMessage() {} - -func (*EventList) ProtoMessage() {} - -func (*EventSeries) ProtoMessage() {} - -func (*EventSource) ProtoMessage() {} - -func (*ExecAction) ProtoMessage() {} - -func (*FCVolumeSource) ProtoMessage() {} - -func (*FileKeySelector) ProtoMessage() {} - -func (*FlexPersistentVolumeSource) ProtoMessage() {} - -func (*FlexVolumeSource) ProtoMessage() {} - -func (*FlockerVolumeSource) ProtoMessage() {} - -func (*GCEPersistentDiskVolumeSource) ProtoMessage() {} - -func (*GRPCAction) ProtoMessage() {} - -func (*GitRepoVolumeSource) ProtoMessage() {} - -func (*GlusterfsPersistentVolumeSource) ProtoMessage() {} - -func (*GlusterfsVolumeSource) ProtoMessage() {} - -func (*HTTPGetAction) ProtoMessage() {} - -func (*HTTPHeader) ProtoMessage() {} - -func (*HostAlias) ProtoMessage() {} - -func (*HostIP) ProtoMessage() {} - -func (*HostPathVolumeSource) ProtoMessage() {} - -func (*ISCSIPersistentVolumeSource) ProtoMessage() {} - -func (*ISCSIVolumeSource) ProtoMessage() {} - -func (*ImageVolumeSource) ProtoMessage() {} - -func (*KeyToPath) ProtoMessage() {} - -func (*Lifecycle) ProtoMessage() {} - -func (*LifecycleHandler) ProtoMessage() {} - -func (*LimitRange) ProtoMessage() {} - -func (*LimitRangeItem) ProtoMessage() {} - -func (*LimitRangeList) ProtoMessage() {} - -func (*LimitRangeSpec) ProtoMessage() {} - -func (*LinuxContainerUser) ProtoMessage() {} - -func (*List) ProtoMessage() {} - -func (*LoadBalancerIngress) ProtoMessage() {} - -func (*LoadBalancerStatus) ProtoMessage() {} - -func (*LocalObjectReference) ProtoMessage() {} - -func (*LocalVolumeSource) ProtoMessage() {} - -func (*ModifyVolumeStatus) ProtoMessage() {} - -func (*NFSVolumeSource) ProtoMessage() {} - -func (*Namespace) ProtoMessage() {} - -func (*NamespaceCondition) ProtoMessage() {} - -func (*NamespaceList) ProtoMessage() {} - -func (*NamespaceSpec) ProtoMessage() {} - -func (*NamespaceStatus) ProtoMessage() {} - -func (*Node) ProtoMessage() {} - -func (*NodeAddress) ProtoMessage() {} - -func (*NodeAffinity) ProtoMessage() {} - -func (*NodeCondition) ProtoMessage() {} - -func (*NodeConfigSource) ProtoMessage() {} - -func (*NodeConfigStatus) ProtoMessage() {} - -func (*NodeDaemonEndpoints) ProtoMessage() {} - -func (*NodeFeatures) ProtoMessage() {} - -func (*NodeList) ProtoMessage() {} - -func (*NodeProxyOptions) ProtoMessage() {} - -func (*NodeRuntimeHandler) ProtoMessage() {} - -func (*NodeRuntimeHandlerFeatures) ProtoMessage() {} - -func (*NodeSelector) ProtoMessage() {} - -func (*NodeSelectorRequirement) ProtoMessage() {} - -func (*NodeSelectorTerm) ProtoMessage() {} - -func (*NodeSpec) ProtoMessage() {} - -func (*NodeStatus) ProtoMessage() {} - -func (*NodeSwapStatus) ProtoMessage() {} - -func (*NodeSystemInfo) ProtoMessage() {} - -func (*ObjectFieldSelector) ProtoMessage() {} - -func (*ObjectReference) ProtoMessage() {} - -func (*PersistentVolume) ProtoMessage() {} - -func (*PersistentVolumeClaim) ProtoMessage() {} - -func (*PersistentVolumeClaimCondition) ProtoMessage() {} - -func (*PersistentVolumeClaimList) ProtoMessage() {} - -func (*PersistentVolumeClaimSpec) ProtoMessage() {} - -func (*PersistentVolumeClaimStatus) ProtoMessage() {} - -func (*PersistentVolumeClaimTemplate) ProtoMessage() {} - -func (*PersistentVolumeClaimVolumeSource) ProtoMessage() {} - -func (*PersistentVolumeList) ProtoMessage() {} - -func (*PersistentVolumeSource) ProtoMessage() {} - -func (*PersistentVolumeSpec) ProtoMessage() {} - -func (*PersistentVolumeStatus) ProtoMessage() {} - -func (*PhotonPersistentDiskVolumeSource) ProtoMessage() {} - -func (*Pod) ProtoMessage() {} - -func (*PodAffinity) ProtoMessage() {} - -func (*PodAffinityTerm) ProtoMessage() {} - -func (*PodAntiAffinity) ProtoMessage() {} - -func (*PodAttachOptions) ProtoMessage() {} - -func (*PodCertificateProjection) ProtoMessage() {} - -func (*PodCondition) ProtoMessage() {} - -func (*PodDNSConfig) ProtoMessage() {} - -func (*PodDNSConfigOption) ProtoMessage() {} - -func (*PodExecOptions) ProtoMessage() {} - -func (*PodExtendedResourceClaimStatus) ProtoMessage() {} - -func (*PodIP) ProtoMessage() {} - -func (*PodList) ProtoMessage() {} - -func (*PodLogOptions) ProtoMessage() {} - -func (*PodOS) ProtoMessage() {} - -func (*PodPortForwardOptions) ProtoMessage() {} - -func (*PodProxyOptions) ProtoMessage() {} - -func (*PodReadinessGate) ProtoMessage() {} - -func (*PodResourceClaim) ProtoMessage() {} - -func (*PodResourceClaimStatus) ProtoMessage() {} - -func (*PodSchedulingGate) ProtoMessage() {} - -func (*PodSecurityContext) ProtoMessage() {} - -func (*PodSignature) ProtoMessage() {} - -func (*PodSpec) ProtoMessage() {} - -func (*PodStatus) ProtoMessage() {} - -func (*PodStatusResult) ProtoMessage() {} - -func (*PodTemplate) ProtoMessage() {} - -func (*PodTemplateList) ProtoMessage() {} - -func (*PodTemplateSpec) ProtoMessage() {} - -func (*PortStatus) ProtoMessage() {} - -func (*PortworxVolumeSource) ProtoMessage() {} - -func (*Preconditions) ProtoMessage() {} - -func (*PreferAvoidPodsEntry) ProtoMessage() {} - -func (*PreferredSchedulingTerm) ProtoMessage() {} - -func (*Probe) ProtoMessage() {} - -func (*ProbeHandler) ProtoMessage() {} - -func (*ProjectedVolumeSource) ProtoMessage() {} - -func (*QuobyteVolumeSource) ProtoMessage() {} - -func (*RBDPersistentVolumeSource) ProtoMessage() {} - -func (*RBDVolumeSource) ProtoMessage() {} - -func (*RangeAllocation) ProtoMessage() {} - -func (*ReplicationController) ProtoMessage() {} - -func (*ReplicationControllerCondition) ProtoMessage() {} - -func (*ReplicationControllerList) ProtoMessage() {} - -func (*ReplicationControllerSpec) ProtoMessage() {} - -func (*ReplicationControllerStatus) ProtoMessage() {} - -func (*ResourceClaim) ProtoMessage() {} - -func (*ResourceFieldSelector) ProtoMessage() {} - -func (*ResourceHealth) ProtoMessage() {} - -func (*ResourceQuota) ProtoMessage() {} - -func (*ResourceQuotaList) ProtoMessage() {} - -func (*ResourceQuotaSpec) ProtoMessage() {} - -func (*ResourceQuotaStatus) ProtoMessage() {} - -func (*ResourceRequirements) ProtoMessage() {} - -func (*ResourceStatus) ProtoMessage() {} - -func (*SELinuxOptions) ProtoMessage() {} - -func (*ScaleIOPersistentVolumeSource) ProtoMessage() {} - -func (*ScaleIOVolumeSource) ProtoMessage() {} - -func (*ScopeSelector) ProtoMessage() {} - -func (*ScopedResourceSelectorRequirement) ProtoMessage() {} - -func (*SeccompProfile) ProtoMessage() {} - -func (*Secret) ProtoMessage() {} - -func (*SecretEnvSource) ProtoMessage() {} - -func (*SecretKeySelector) ProtoMessage() {} - -func (*SecretList) ProtoMessage() {} - -func (*SecretProjection) ProtoMessage() {} - -func (*SecretReference) ProtoMessage() {} - -func (*SecretVolumeSource) ProtoMessage() {} - -func (*SecurityContext) ProtoMessage() {} - -func (*SerializedReference) ProtoMessage() {} - -func (*Service) ProtoMessage() {} - -func (*ServiceAccount) ProtoMessage() {} - -func (*ServiceAccountList) ProtoMessage() {} - -func (*ServiceAccountTokenProjection) ProtoMessage() {} - -func (*ServiceList) ProtoMessage() {} - -func (*ServicePort) ProtoMessage() {} - -func (*ServiceProxyOptions) ProtoMessage() {} - -func (*ServiceSpec) ProtoMessage() {} - -func (*ServiceStatus) ProtoMessage() {} - -func (*SessionAffinityConfig) ProtoMessage() {} - -func (*SleepAction) ProtoMessage() {} - -func (*StorageOSPersistentVolumeSource) ProtoMessage() {} - -func (*StorageOSVolumeSource) ProtoMessage() {} - -func (*Sysctl) ProtoMessage() {} - -func (*TCPSocketAction) ProtoMessage() {} - -func (*Taint) ProtoMessage() {} - -func (*Toleration) ProtoMessage() {} - -func (*TopologySelectorLabelRequirement) ProtoMessage() {} - -func (*TopologySelectorTerm) ProtoMessage() {} - -func (*TopologySpreadConstraint) ProtoMessage() {} - -func (*TypedLocalObjectReference) ProtoMessage() {} - -func (*TypedObjectReference) ProtoMessage() {} - -func (*Volume) ProtoMessage() {} - -func (*VolumeDevice) ProtoMessage() {} - -func (*VolumeMount) ProtoMessage() {} - -func (*VolumeMountStatus) ProtoMessage() {} - -func (*VolumeNodeAffinity) ProtoMessage() {} - -func (*VolumeProjection) ProtoMessage() {} - -func (*VolumeResourceRequirements) ProtoMessage() {} - -func (*VolumeSource) ProtoMessage() {} - -func (*VsphereVirtualDiskVolumeSource) ProtoMessage() {} - -func (*WeightedPodAffinityTerm) ProtoMessage() {} - -func (*WindowsSecurityContextOptions) ProtoMessage() {} - -func (*WorkloadReference) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index 705c82083cc50..ac0494f3c5147 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -161,8 +161,7 @@ type VolumeSource struct { Projected *ProjectedVolumeSource `json:"projected,omitempty" protobuf:"bytes,26,opt,name=projected"` // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. // +optional PortworxVolume *PortworxVolumeSource `json:"portworxVolume,omitempty" protobuf:"bytes,24,opt,name=portworxVolume"` // scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. @@ -214,10 +213,9 @@ type VolumeSource struct { // A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. // The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. // The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - // The volume will be mounted read-only (ro) and non-executable files (noexec). + // The volume will be mounted read-only (ro). // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. - // +featureGate=ImageVolume // +optional Image *ImageVolumeSource `json:"image,omitempty" protobuf:"bytes,30,opt,name=image"` } @@ -325,8 +323,7 @@ type PersistentVolumeSource struct { PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,17,opt,name=photonPersistentDisk"` // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. // +optional PortworxVolume *PortworxVolumeSource `json:"portworxVolume,omitempty" protobuf:"bytes,18,opt,name=portworxVolume"` // scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. @@ -672,6 +669,17 @@ const ( PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError" // Volume is being modified PersistentVolumeClaimVolumeModifyingVolume PersistentVolumeClaimConditionType = "ModifyingVolume" + + // PersistentVolumeClaimUnused indicates whether the PVC is currently not in use by any Pod. + // When status is True, the PVC is not referenced by any non-terminal Pod. + // The lastTransitionTime indicates when the PVC last transitioned to being unused. + // + // Both in-use time and unused time duration indicated by this condition may be shorter or + // slightly longer than actual in-use time or unused time because of processing delays or + // when this feature was enabled in the cluster. + // + // Requires PersistentVolumeClaimUnusedSinceTime alpha featuregate + PersistentVolumeClaimUnused PersistentVolumeClaimConditionType = "Unused" ) // +enum @@ -3363,7 +3371,6 @@ type ContainerStatus struct { // AllocatedResources represents the compute resources allocated for this container by the // node. Kubelet sets this value to Container.Resources.Requests upon successful pod admission // and after successfully admitting desired pod resize. - // +featureGate=InPlacePodVerticalScalingAllocatedStatus // +optional AllocatedResources ResourceList `json:"allocatedResources,omitempty" protobuf:"bytes,10,rep,name=allocatedResources,casttype=ResourceList,castkey=ResourceName"` // Resources represents the compute resource requests and limits that have been successfully @@ -3421,6 +3428,10 @@ const ( ResourceHealthStatusUnknown ResourceHealthStatus = "Unknown" ) +// ResourceHealthMessageMaxLength is the maximum length for ResourceHealth.Message field. +// Messages longer than this will be truncated with "..." appended. +const ResourceHealthMessageMaxLength = 1024 + // ResourceID is calculated based on the source of this resource health information. // For DevicePlugin: // @@ -3448,6 +3459,11 @@ type ResourceHealth struct { // // In future we may want to introduce the PermanentlyUnhealthy Status. Health ResourceHealthStatus `json:"health,omitempty" protobuf:"bytes,2,name=health"` + // Message provides human-readable context for Health (e.g. "ECC error count exceeded threshold"). + // This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise. + // +featureGate=ResourceHealthStatusMessage + // +optional + Message *string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` } // ContainerUser represents user identity information @@ -3575,8 +3591,6 @@ type PodCondition struct { // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions Type PodConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=PodConditionType"` // If set, this represents the .metadata.generation that the pod condition was set based upon. - // The PodObservedGenerationTracking feature gate must be enabled to use this field. - // +featureGate=PodObservedGenerationTracking // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,7,opt,name=observedGeneration"` // Status is the status of the condition. @@ -3623,6 +3637,29 @@ type VolumeMountStatus struct { // depending on the mount result. // +optional RecursiveReadOnly *RecursiveReadOnlyMode `json:"recursiveReadOnly,omitempty" protobuf:"bytes,4,opt,name=recursiveReadOnly,casttype=RecursiveReadOnlyMode"` + // volumeStatus represents volume-type-specific status about the mounted + // volume. + // +optional + VolumeStatus *VolumeStatus `json:"volumeStatus,omitempty" protobuf:"bytes,5,opt,name=volumeStatus"` +} + +// VolumeStatus represents the status of a mounted volume. +// At most one of its members must be specified. +type VolumeStatus struct { + // image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + // +featureGate=ImageVolumeWithDigest + // +optional + Image *ImageVolumeStatus `json:"image,omitempty" protobuf:"bytes,1,opt,name=image"` +} + +// ImageVolumeStatus represents the image-based volume status. +type ImageVolumeStatus struct { + // ImageRef is the digest of the image used for this volume. + // It should have a value that's similar to the pod's status.containerStatuses[i].imageID. + // The ImageRef length should not exceed 256 characters. + // +kubebuilder:validation:MaxLength=256 + // +required + ImageRef string `json:"imageRef,omitempty" protobuf:"bytes,1,opt,name=imageRef"` } // RestartPolicy describes how the container should be restarted. @@ -4378,7 +4415,6 @@ type PodSpec struct { // When set to false, a new userns is created for the pod. Setting false is useful for // mitigating container breakout vulnerabilities even allowing users to run their // containers as root without actually having root privileges on the host. - // This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature. // +k8s:conversion-gen=false // +optional HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"` @@ -4440,17 +4476,27 @@ type PodSpec struct { // +featureGate=HostnameOverride // +optional HostnameOverride *string `json:"hostnameOverride,omitempty" protobuf:"bytes,41,opt,name=hostnameOverride"` - // WorkloadRef provides a reference to the Workload object that this Pod belongs to. - // This field is used by the scheduler to identify the PodGroup and apply the - // correct group scheduling policies. The Workload object referenced - // by this field may not exist at the time the Pod is created. - // This field is immutable, but a Workload object with the same name - // may be recreated with different policies. Doing this during pod scheduling + + // WorkloadRef is tombstoned to show why 42 is a reserved protobuf tag. + // This field got replaced with the SchedulingGroup field in Kubernetes 1.36. + // WorkloadRef *WorkloadReference `json:"workloadRef,omitempty" protobuf:"bytes,42,opt,name=workloadRef"` + + // SchedulingGroup provides a reference to the immediate scheduling runtime + // grouping object that this Pod belongs to. + // This field is used by the scheduler to identify the group and apply the + // correct group scheduling policies. The association with a group also + // impacts other lifecycle aspects of a Pod that are relevant in a wider context + // of scheduling like preemption, resource attachment, etc. If not specified, + // the Pod is treated as a single unit in all of these aspects. + // The group object referenced by this field may not exist at the time the + // Pod is created. + // This field is immutable, but a group object with the same name may be + // recreated with different policies. Doing this during pod scheduling // may result in the placement not conforming to the expected policies. // // +featureGate=GenericWorkload // +optional - WorkloadRef *WorkloadReference `json:"workloadRef,omitempty" protobuf:"bytes,42,opt,name=workloadRef"` + SchedulingGroup *PodSchedulingGroup `json:"schedulingGroup,omitempty" protobuf:"bytes,43,opt,name=schedulingGroup"` } // PodResourceClaim references exactly one ResourceClaim, either directly @@ -4459,6 +4505,14 @@ type PodSpec struct { // // It adds a name to it that uniquely identifies the ResourceClaim inside the Pod. // Containers that need access to the ResourceClaim reference it with this name. +// +// When the DRAWorkloadResourceClaims feature gate is enabled and this Pod +// belongs to a PodGroup, a PodResourceClaim is matched to a +// PodGroupResourceClaim if all of their fields are equal (Name, +// ResourceClaimName, and ResourceClaimTemplateName). A matched claim references +// a single ResourceClaim shared across all Pods in the PodGroup, reserved for +// the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual +// Pods. type PodResourceClaim struct { // Name uniquely identifies this resource claim inside the pod. // This must be a DNS_LABEL. @@ -4485,6 +4539,16 @@ type PodResourceClaim struct { // generated component, will be used to form a unique name for the // ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses. // + // When the DRAWorkloadResourceClaims feature gate is enabled and the pod + // belongs to a PodGroup that defines a PodGroupResourceClaim with the same + // Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the + // ResourceClaim generated for the PodGroup. All pods in the group that + // define an equivalent PodResourceClaim matching the + // PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same + // generated ResourceClaim. ResourceClaims generated for a PodGroup are + // owned by the PodGroup and their lifecycles are tied to the PodGroup + // instead of any individual pod. + // // This field is immutable and no changes will be made to the // corresponding ResourceClaim by the control plane after creating the // ResourceClaim. @@ -4504,9 +4568,16 @@ type PodResourceClaimStatus struct { Name string `json:"name" protobuf:"bytes,1,name=name"` // ResourceClaimName is the name of the ResourceClaim that was - // generated for the Pod in the namespace of the Pod. If this is - // unset, then generating a ResourceClaim was not necessary. The - // pod.spec.resourceClaims entry can be ignored in this case. + // generated for the Pod in the namespace of the Pod. + // + // When the DRAWorkloadResourceClaims feature is enabled and the + // corresponding PodResourceClaim matches a PodGroupResourceClaim + // made by the Pod's PodGroup, then this is the name of the + // ResourceClaim generated and reserved for the PodGroup. + // + // If this is unset, then generating a ResourceClaim was not + // necessary. The pod.spec.resourceClaims entry can be ignored in + // this case. // // +optional ResourceClaimName *string `json:"resourceClaimName,omitempty" protobuf:"bytes,2,opt,name=resourceClaimName"` @@ -4562,34 +4633,18 @@ type PodSchedulingGate struct { Name string `json:"name" protobuf:"bytes,1,opt,name=name"` } -// WorkloadReference identifies the Workload object and PodGroup membership -// that a Pod belongs to. The scheduler uses this information to apply -// workload-aware scheduling semantics. -type WorkloadReference struct { - // Name defines the name of the Workload object this Pod belongs to. - // Workload must be in the same namespace as the Pod. - // If it doesn't match any existing Workload, the Pod will remain unschedulable - // until a Workload object is created and observed by the kube-scheduler. - // It must be a DNS subdomain. - // - // +required - Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - - // PodGroup is the name of the PodGroup within the Workload that this Pod - // belongs to. If it doesn't match any existing PodGroup within the Workload, - // the Pod will remain unschedulable until the Workload object is recreated - // and observed by the kube-scheduler. It must be a DNS label. - // - // +required - PodGroup string `json:"podGroup" protobuf:"bytes,2,opt,name=podGroup"` - - // PodGroupReplicaKey specifies the replica key of the PodGroup to which this - // Pod belongs. It is used to distinguish pods belonging to different replicas - // of the same pod group. The pod group policy is applied separately to each replica. - // When set, it must be a DNS label. +// PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. +// The scheduler uses this information to apply workload-aware scheduling semantics. +// Exactly one field must be specified. +// +union +type PodSchedulingGroup struct { + // PodGroupName specifies the name of the standalone PodGroup object + // that represents the runtime instance of this group. + // Must be a DNS subdomain. // // +optional - PodGroupReplicaKey string `json:"podGroupReplicaKey,omitempty" protobuf:"bytes,3,opt,name=podGroupReplicaKey"` + // +oneOf=GroupSelection + PodGroupName *string `json:"podGroupName,omitempty" protobuf:"bytes,1,opt,name=podGroupName"` } // +enum @@ -5251,7 +5306,6 @@ type EphemeralContainer struct { type PodStatus struct { // If set, this represents the .metadata.generation that the pod status was set based upon. // The PodObservedGenerationTracking feature gate must be enabled to use this field. - // +featureGate=PodObservedGenerationTracking // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,17,opt,name=observedGeneration"` // The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. @@ -5414,6 +5468,16 @@ type PodStatus struct { // +featureGate=InPlacePodLevelResourcesVerticalScaling // +optional Resources *ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,20,opt,name=resources"` + + // NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources + // that were allocated for this pod through DRA claims. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // +featureGate=DRANodeAllocatableResources + // +optional + // +listType=atomic + NodeAllocatableResourceClaimStatuses []NodeAllocatableResourceClaimStatus `json:"nodeAllocatableResourceClaimStatuses,omitempty" protobuf:"bytes,21,rep,name=nodeAllocatableResourceClaimStatuses"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -5533,18 +5597,18 @@ type ReplicationControllerSpec struct { // Defaults to 1. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=1 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` // Minimum number of seconds for which a newly created pod should be ready // without any of its container crashing, for it to be considered available. // Defaults to 0 (pod will be considered available as soon as it is ready) // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` // Selector is a label query over pods that should match the Replicas count. @@ -5634,7 +5698,7 @@ type ReplicationControllerCondition struct { // +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.0 -// +k8s:supportsSubresource=/scale +// +k8s:supportsSubresource="/scale" // ReplicationController represents the configuration of a replication controller. type ReplicationController struct { @@ -5644,8 +5708,8 @@ type ReplicationController struct { // be the same as the Pod(s) that the replication controller manages. // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec defines the specification of the desired behavior of the replication controller. @@ -6585,7 +6649,6 @@ type NodeRuntimeHandlerFeatures struct { // +optional RecursiveReadOnlyMounts *bool `json:"recursiveReadOnlyMounts,omitempty" protobuf:"varint,1,opt,name=recursiveReadOnlyMounts"` // UserNamespaces is set to true if the runtime handler supports UserNamespaces, including for volumes. - // +featureGate=UserNamespacesSupport // +optional UserNamespaces *bool `json:"userNamespaces,omitempty" protobuf:"varint,2,opt,name=userNamespaces"` } @@ -6756,7 +6819,6 @@ type NodeStatus struct { // +optional Config *NodeConfigStatus `json:"config,omitempty" protobuf:"bytes,11,opt,name=config"` // The available runtime handlers. - // +featureGate=UserNamespacesSupport // +optional // +listType=atomic RuntimeHandlers []NodeRuntimeHandler `json:"runtimeHandlers,omitempty" protobuf:"bytes,12,rep,name=runtimeHandlers"` @@ -8247,7 +8309,6 @@ type SecurityContext struct { // procMount denotes the type of proc mount to use for the containers. // The default value is Default which uses the container runtime defaults for // readonly paths and masked paths. - // This requires the ProcMountType feature flag to be enabled. // Note that this field cannot be set when spec.os.name is windows. // +optional ProcMount *ProcMountType `json:"procMount,omitempty" protobuf:"bytes,9,opt,name=procMount"` @@ -8452,3 +8513,17 @@ type ImageVolumeSource struct { // +optional PullPolicy PullPolicy `json:"pullPolicy,omitempty" protobuf:"bytes,2,opt,name=pullPolicy,casttype=PullPolicy"` } + +// NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA. +type NodeAllocatableResourceClaimStatus struct { + // ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation. + // +required + ResourceClaimName string `json:"resourceClaimName" protobuf:"bytes,1,opt,name=resourceClaimName"` + // Containers lists the names of all containers in this pod that reference the claim. + // +optional + // +listType=set + Containers []string `json:"containers,omitempty" protobuf:"bytes,2,rep,name=containers"` + // Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim. + // +required + Resources map[ResourceName]resource.Quantity `json:"resources" protobuf:"bytes,3,rep,name=resources"` +} diff --git a/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go index 0f5e44e917a41..97eb5ceec3a54 100644 --- a/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go @@ -992,6 +992,15 @@ func (ImageVolumeSource) SwaggerDoc() map[string]string { return map_ImageVolumeSource } +var map_ImageVolumeStatus = map[string]string{ + "": "ImageVolumeStatus represents the image-based volume status.", + "imageRef": "ImageRef is the digest of the image used for this volume. It should have a value that's similar to the pod's status.containerStatuses[i].imageID. The ImageRef length should not exceed 256 characters.", +} + +func (ImageVolumeStatus) SwaggerDoc() map[string]string { + return map_ImageVolumeStatus +} + var map_KeyToPath = map[string]string{ "": "Maps a string key to a path within a volume.", "key": "key is the key to project.", @@ -1225,6 +1234,17 @@ func (NodeAffinity) SwaggerDoc() map[string]string { return map_NodeAffinity } +var map_NodeAllocatableResourceClaimStatus = map[string]string{ + "": "NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA.", + "resourceClaimName": "ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation.", + "containers": "Containers lists the names of all containers in this pod that reference the claim.", + "resources": "Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim.", +} + +func (NodeAllocatableResourceClaimStatus) SwaggerDoc() map[string]string { + return map_NodeAllocatableResourceClaimStatus +} + var map_NodeCondition = map[string]string{ "": "NodeCondition contains condition information for a node.", "type": "Type of node condition.", @@ -1565,7 +1585,7 @@ var map_PersistentVolumeSource = map[string]string{ "quobyte": "quobyte represents a Quobyte mount on the host that shares a pod's lifetime. Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported.", "azureDisk": "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type are redirected to the disk.csi.azure.com CSI driver.", "photonPersistentDisk": "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.", - "portworxVolume": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + "portworxVolume": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver.", "scaleIO": "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.", "local": "local represents directly-attached storage with node affinity", "storageos": "storageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. More info: https://examples.k8s.io/volumes/storageos/README.md", @@ -1691,7 +1711,7 @@ func (PodCertificateProjection) SwaggerDoc() map[string]string { var map_PodCondition = map[string]string{ "": "PodCondition contains details for the current condition of this pod.", "type": "Type is the type of the condition. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", - "observedGeneration": "If set, this represents the .metadata.generation that the pod condition was set based upon. The PodObservedGenerationTracking feature gate must be enabled to use this field.", + "observedGeneration": "If set, this represents the .metadata.generation that the pod condition was set based upon.", "status": "Status is the status of the condition. Can be True, False, Unknown. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", "lastProbeTime": "Last time we probed the condition.", "lastTransitionTime": "Last time the condition transitioned from one status to another.", @@ -1822,10 +1842,10 @@ func (PodReadinessGate) SwaggerDoc() map[string]string { } var map_PodResourceClaim = map[string]string{ - "": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + "": "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and this Pod belongs to a PodGroup, a PodResourceClaim is matched to a PodGroupResourceClaim if all of their fields are equal (Name, ResourceClaimName, and ResourceClaimTemplateName). A matched claim references a single ResourceClaim shared across all Pods in the PodGroup, reserved for the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual Pods.", "name": "Name uniquely identifies this resource claim inside the pod. This must be a DNS_LABEL.", "resourceClaimName": "ResourceClaimName is the name of a ResourceClaim object in the same namespace as this pod.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", - "resourceClaimTemplateName": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "resourceClaimTemplateName": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nWhen the DRAWorkloadResourceClaims feature gate is enabled and the pod belongs to a PodGroup that defines a PodGroupResourceClaim with the same Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the ResourceClaim generated for the PodGroup. All pods in the group that define an equivalent PodResourceClaim matching the PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same generated ResourceClaim. ResourceClaims generated for a PodGroup are owned by the PodGroup and their lifecycles are tied to the PodGroup instead of any individual pod.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", } func (PodResourceClaim) SwaggerDoc() map[string]string { @@ -1835,7 +1855,7 @@ func (PodResourceClaim) SwaggerDoc() map[string]string { var map_PodResourceClaimStatus = map[string]string{ "": "PodResourceClaimStatus is stored in the PodStatus for each PodResourceClaim which references a ResourceClaimTemplate. It stores the generated name for the corresponding ResourceClaim.", "name": "Name uniquely identifies this resource claim inside the pod. This must match the name of an entry in pod.spec.resourceClaims, which implies that the string must be a DNS_LABEL.", - "resourceClaimName": "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod. If this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", + "resourceClaimName": "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod.\n\nWhen the DRAWorkloadResourceClaims feature is enabled and the corresponding PodResourceClaim matches a PodGroupResourceClaim made by the Pod's PodGroup, then this is the name of the ResourceClaim generated and reserved for the PodGroup.\n\nIf this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", } func (PodResourceClaimStatus) SwaggerDoc() map[string]string { @@ -1851,6 +1871,15 @@ func (PodSchedulingGate) SwaggerDoc() map[string]string { return map_PodSchedulingGate } +var map_PodSchedulingGroup = map[string]string{ + "": "PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics. Exactly one field must be specified.", + "podGroupName": "PodGroupName specifies the name of the standalone PodGroup object that represents the runtime instance of this group. Must be a DNS subdomain.", +} + +func (PodSchedulingGroup) SwaggerDoc() map[string]string { + return map_PodSchedulingGroup +} + var map_PodSecurityContext = map[string]string{ "": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", "seLinuxOptions": "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", @@ -1919,12 +1948,12 @@ var map_PodSpec = map[string]string{ "topologySpreadConstraints": "TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed.", "setHostnameAsFQDN": "If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false.", "os": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.resources - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.securityContext.supplementalGroupsPolicy - spec.containers[*].securityContext.appArmorProfile - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup", - "hostUsers": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "hostUsers": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "schedulingGates": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.", "resourceClaims": "ResourceClaims defines which ResourceClaims must be allocated and reserved before the Pod is allowed to start. The resources will be made available to those containers which consume them by name.\n\nThis is a stable field but requires that the DynamicResourceAllocation feature gate is enabled.\n\nThis field is immutable.", "resources": "Resources is the total amount of CPU and Memory resources required by all containers in the pod. It supports specifying Requests and Limits for \"cpu\", \"memory\" and \"hugepages-\" resource names only. ResourceClaims are not supported.\n\nThis field enables fine-grained control over resource allocation for the entire pod, allowing resource sharing among containers in a pod.\n\nThis is an alpha field and requires enabling the PodLevelResources feature gate.", "hostnameOverride": "HostnameOverride specifies an explicit override for the pod's hostname as perceived by the pod. This field only specifies the pod's hostname and does not affect its DNS records. When this field is set to a non-empty string: - It takes precedence over the values set in `hostname` and `subdomain`. - The Pod's hostname will be set to this value. - `setHostnameAsFQDN` must be nil or set to false. - `hostNetwork` must be set to false.\n\nThis field must be a valid DNS subdomain as defined in RFC 1123 and contain at most 64 characters. Requires the HostnameOverride feature gate to be enabled.", - "workloadRef": "WorkloadRef provides a reference to the Workload object that this Pod belongs to. This field is used by the scheduler to identify the PodGroup and apply the correct group scheduling policies. The Workload object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a Workload object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies.", + "schedulingGroup": "SchedulingGroup provides a reference to the immediate scheduling runtime grouping object that this Pod belongs to. This field is used by the scheduler to identify the group and apply the correct group scheduling policies. The association with a group also impacts other lifecycle aspects of a Pod that are relevant in a wider context of scheduling like preemption, resource attachment, etc. If not specified, the Pod is treated as a single unit in all of these aspects. The group object referenced by this field may not exist at the time the Pod is created. This field is immutable, but a group object with the same name may be recreated with different policies. Doing this during pod scheduling may result in the placement not conforming to the expected policies.", } func (PodSpec) SwaggerDoc() map[string]string { @@ -1932,27 +1961,28 @@ func (PodSpec) SwaggerDoc() map[string]string { } var map_PodStatus = map[string]string{ - "": "PodStatus represents information about the status of a pod. Status may trail the actual state of a system, especially if the node that hosts the pod cannot contact the control plane.", - "observedGeneration": "If set, this represents the .metadata.generation that the pod status was set based upon. The PodObservedGenerationTracking feature gate must be enabled to use this field.", - "phase": "The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. The conditions array, the reason and message fields, and the individual container status arrays contain more detail about the pod's status. There are five possible phase values:\n\nPending: The pod has been accepted by the Kubernetes system, but one or more of the container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Running: The pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. Succeeded: All containers in the pod have terminated in success, and will not be restarted. Failed: All containers in the pod have terminated, and at least one container has terminated in failure. The container either exited with non-zero status or was terminated by the system. Unknown: For some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod.\n\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase", - "conditions": "Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", - "message": "A human readable message indicating details about why the pod is in this condition.", - "reason": "A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted'", - "nominatedNodeName": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", - "hostIP": "hostIP holds the IP address of the host to which the pod is assigned. Empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns mean that HostIP will not be updated even if there is a node is assigned to pod", - "hostIPs": "hostIPs holds the IP addresses allocated to the host. If this field is specified, the first entry must match the hostIP field. This list is empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns means that HostIPs will not be updated even if there is a node is assigned to this pod.", - "podIP": "podIP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.", - "podIPs": "podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list is empty if no IPs have been allocated yet.", - "startTime": "RFC 3339 date and time at which the object was acknowledged by the Kubelet. This is before the Kubelet pulled the container image(s) for the pod.", - "initContainerStatuses": "Statuses of init containers in this pod. The most recent successful non-restartable init container will have ready = true, the most recently started container will have startTime set. Each init container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-and-container-status", - "containerStatuses": "Statuses of containers in this pod. Each container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", - "qosClass": "The Quality of Service (QOS) classification assigned to the pod based on resource requirements See PodQOSClass type for available QOS classes More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes", - "ephemeralContainerStatuses": "Statuses for any ephemeral containers that have run in this pod. Each ephemeral container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", - "resize": "Status of resources resize desired for pod's containers. It is empty if no resources resize is pending. Any changes to container resources will automatically set this to \"Proposed\" Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress. PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources. PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.", - "resourceClaimStatuses": "Status of resource claims.", - "extendedResourceClaimStatus": "Status of extended resource claim backed by DRA.", - "allocatedResources": "AllocatedResources is the total requests allocated for this pod by the node. If pod-level requests are not set, this will be the total requests aggregated across containers in the pod.", - "resources": "Resources represents the compute resource requests and limits that have been applied at the pod level if pod-level requests or limits are set in PodSpec.Resources", + "": "PodStatus represents information about the status of a pod. Status may trail the actual state of a system, especially if the node that hosts the pod cannot contact the control plane.", + "observedGeneration": "If set, this represents the .metadata.generation that the pod status was set based upon. The PodObservedGenerationTracking feature gate must be enabled to use this field.", + "phase": "The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. The conditions array, the reason and message fields, and the individual container status arrays contain more detail about the pod's status. There are five possible phase values:\n\nPending: The pod has been accepted by the Kubernetes system, but one or more of the container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Running: The pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. Succeeded: All containers in the pod have terminated in success, and will not be restarted. Failed: All containers in the pod have terminated, and at least one container has terminated in failure. The container either exited with non-zero status or was terminated by the system. Unknown: For some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod.\n\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase", + "conditions": "Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + "message": "A human readable message indicating details about why the pod is in this condition.", + "reason": "A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted'", + "nominatedNodeName": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", + "hostIP": "hostIP holds the IP address of the host to which the pod is assigned. Empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns mean that HostIP will not be updated even if there is a node is assigned to pod", + "hostIPs": "hostIPs holds the IP addresses allocated to the host. If this field is specified, the first entry must match the hostIP field. This list is empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns means that HostIPs will not be updated even if there is a node is assigned to this pod.", + "podIP": "podIP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.", + "podIPs": "podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list is empty if no IPs have been allocated yet.", + "startTime": "RFC 3339 date and time at which the object was acknowledged by the Kubelet. This is before the Kubelet pulled the container image(s) for the pod.", + "initContainerStatuses": "Statuses of init containers in this pod. The most recent successful non-restartable init container will have ready = true, the most recently started container will have startTime set. Each init container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-and-container-status", + "containerStatuses": "Statuses of containers in this pod. Each container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", + "qosClass": "The Quality of Service (QOS) classification assigned to the pod based on resource requirements See PodQOSClass type for available QOS classes More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes", + "ephemeralContainerStatuses": "Statuses for any ephemeral containers that have run in this pod. Each ephemeral container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", + "resize": "Status of resources resize desired for pod's containers. It is empty if no resources resize is pending. Any changes to container resources will automatically set this to \"Proposed\" Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress. PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources. PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.", + "resourceClaimStatuses": "Status of resource claims.", + "extendedResourceClaimStatus": "Status of extended resource claim backed by DRA.", + "allocatedResources": "AllocatedResources is the total requests allocated for this pod by the node. If pod-level requests are not set, this will be the total requests aggregated across containers in the pod.", + "resources": "Resources represents the compute resource requests and limits that have been applied at the pod level if pod-level requests or limits are set in PodSpec.Resources", + "nodeAllocatableResourceClaimStatuses": "NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources that were allocated for this pod through DRA claims. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages.", } func (PodStatus) SwaggerDoc() map[string]string { @@ -2230,6 +2260,7 @@ var map_ResourceHealth = map[string]string{ "": "ResourceHealth represents the health of a resource. It has the latest device health information. This is a part of KEP https://kep.k8s.io/4680.", "resourceID": "ResourceID is the unique identifier of the resource. See the ResourceID type for more information.", "health": "Health of the resource. can be one of:\n - Healthy: operates as normal\n - Unhealthy: reported unhealthy. We consider this a temporary health issue\n since we do not have a mechanism today to distinguish\n temporary and permanent issues.\n - Unknown: The status cannot be determined.\n For example, Device Plugin got unregistered and hasn't been re-registered since.\n\nIn future we may want to introduce the PermanentlyUnhealthy Status.", + "message": "Message provides human-readable context for Health (e.g. \"ECC error count exceeded threshold\"). This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise.", } func (ResourceHealth) SwaggerDoc() map[string]string { @@ -2462,7 +2493,7 @@ var map_SecurityContext = map[string]string{ "runAsNonRoot": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", "readOnlyRootFilesystem": "Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows.", "allowPrivilegeEscalation": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.", - "procMount": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "procMount": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. Note that this field cannot be set when spec.os.name is windows.", "seccompProfile": "The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows.", "appArmorProfile": "appArmorProfile is the AppArmor options to use by this container. If set, this profile overrides the pod's appArmorProfile. Note that this field cannot be set when spec.os.name is windows.", } @@ -2782,6 +2813,7 @@ var map_VolumeMountStatus = map[string]string{ "mountPath": "MountPath corresponds to the original VolumeMount.", "readOnly": "ReadOnly corresponds to the original VolumeMount.", "recursiveReadOnly": "RecursiveReadOnly must be set to Disabled, Enabled, or unspecified (for non-readonly mounts). An IfPossible value in the original VolumeMount must be translated to Disabled or Enabled, depending on the mount result.", + "volumeStatus": "volumeStatus represents volume-type-specific status about the mounted volume.", } func (VolumeMountStatus) SwaggerDoc() map[string]string { @@ -2847,18 +2879,27 @@ var map_VolumeSource = map[string]string{ "azureDisk": "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type are redirected to the disk.csi.azure.com CSI driver.", "photonPersistentDisk": "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.", "projected": "projected items for all in one resources secrets, configmaps, and downward API", - "portworxVolume": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + "portworxVolume": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver.", "scaleIO": "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.", "storageos": "storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported.", "csi": "csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.", "ephemeral": "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time.", - "image": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", + "image": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", } func (VolumeSource) SwaggerDoc() map[string]string { return map_VolumeSource } +var map_VolumeStatus = map[string]string{ + "": "VolumeStatus represents the status of a mounted volume. At most one of its members must be specified.", + "image": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine.", +} + +func (VolumeStatus) SwaggerDoc() map[string]string { + return map_VolumeStatus +} + var map_VsphereVirtualDiskVolumeSource = map[string]string{ "": "Represents a vSphere volume resource.", "volumePath": "volumePath is the path that identifies vSphere volume vmdk", @@ -2893,15 +2934,4 @@ func (WindowsSecurityContextOptions) SwaggerDoc() map[string]string { return map_WindowsSecurityContextOptions } -var map_WorkloadReference = map[string]string{ - "": "WorkloadReference identifies the Workload object and PodGroup membership that a Pod belongs to. The scheduler uses this information to apply workload-aware scheduling semantics.", - "name": "Name defines the name of the Workload object this Pod belongs to. Workload must be in the same namespace as the Pod. If it doesn't match any existing Workload, the Pod will remain unschedulable until a Workload object is created and observed by the kube-scheduler. It must be a DNS subdomain.", - "podGroup": "PodGroup is the name of the PodGroup within the Workload that this Pod belongs to. If it doesn't match any existing PodGroup within the Workload, the Pod will remain unschedulable until the Workload object is recreated and observed by the kube-scheduler. It must be a DNS label.", - "podGroupReplicaKey": "PodGroupReplicaKey specifies the replica key of the PodGroup to which this Pod belongs. It is used to distinguish pods belonging to different replicas of the same pod group. The pod group policy is applied separately to each replica. When set, it must be a DNS label.", -} - -func (WorkloadReference) SwaggerDoc() map[string]string { - return map_WorkloadReference -} - // AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/core/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/core/v1/zz_generated.deepcopy.go index 15bc2ee0331e0..10af6c17d2c72 100644 --- a/staging/src/k8s.io/api/core/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/core/v1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package v1 import ( + resource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" @@ -2170,6 +2171,22 @@ func (in *ImageVolumeSource) DeepCopy() *ImageVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageVolumeStatus) DeepCopyInto(out *ImageVolumeStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageVolumeStatus. +func (in *ImageVolumeStatus) DeepCopy() *ImageVolumeStatus { + if in == nil { + return nil + } + out := new(ImageVolumeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KeyToPath) DeepCopyInto(out *KeyToPath) { *out = *in @@ -2760,6 +2777,34 @@ func (in *NodeAffinity) DeepCopy() *NodeAffinity { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAllocatableResourceClaimStatus) DeepCopyInto(out *NodeAllocatableResourceClaimStatus) { + *out = *in + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(map[ResourceName]resource.Quantity, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAllocatableResourceClaimStatus. +func (in *NodeAllocatableResourceClaimStatus) DeepCopy() *NodeAllocatableResourceClaimStatus { + if in == nil { + return nil + } + out := new(NodeAllocatableResourceClaimStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeCondition) DeepCopyInto(out *NodeCondition) { *out = *in @@ -4299,6 +4344,27 @@ func (in *PodSchedulingGate) DeepCopy() *PodSchedulingGate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSchedulingGroup) DeepCopyInto(out *PodSchedulingGroup) { + *out = *in + if in.PodGroupName != nil { + in, out := &in.PodGroupName, &out.PodGroupName + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSchedulingGroup. +func (in *PodSchedulingGroup) DeepCopy() *PodSchedulingGroup { + if in == nil { + return nil + } + out := new(PodSchedulingGroup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) { *out = *in @@ -4569,10 +4635,10 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { *out = new(string) **out = **in } - if in.WorkloadRef != nil { - in, out := &in.WorkloadRef, &out.WorkloadRef - *out = new(WorkloadReference) - **out = **in + if in.SchedulingGroup != nil { + in, out := &in.SchedulingGroup, &out.SchedulingGroup + *out = new(PodSchedulingGroup) + (*in).DeepCopyInto(*out) } return } @@ -4656,6 +4722,13 @@ func (in *PodStatus) DeepCopyInto(out *PodStatus) { *out = new(ResourceRequirements) (*in).DeepCopyInto(*out) } + if in.NodeAllocatableResourceClaimStatuses != nil { + in, out := &in.NodeAllocatableResourceClaimStatuses, &out.NodeAllocatableResourceClaimStatuses + *out = make([]NodeAllocatableResourceClaimStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -5222,6 +5295,11 @@ func (in *ResourceFieldSelector) DeepCopy() *ResourceFieldSelector { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceHealth) DeepCopyInto(out *ResourceHealth) { *out = *in + if in.Message != nil { + in, out := &in.Message, &out.Message + *out = new(string) + **out = **in + } return } @@ -5422,7 +5500,9 @@ func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { if in.Resources != nil { in, out := &in.Resources, &out.Resources *out = make([]ResourceHealth, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } return } @@ -6529,6 +6609,11 @@ func (in *VolumeMountStatus) DeepCopyInto(out *VolumeMountStatus) { *out = new(RecursiveReadOnlyMode) **out = **in } + if in.VolumeStatus != nil { + in, out := &in.VolumeStatus, &out.VolumeStatus + *out = new(VolumeStatus) + (*in).DeepCopyInto(*out) + } return } @@ -6805,6 +6890,27 @@ func (in *VolumeSource) DeepCopy() *VolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeStatus) DeepCopyInto(out *VolumeStatus) { + *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ImageVolumeStatus) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeStatus. +func (in *VolumeStatus) DeepCopy() *VolumeStatus { + if in == nil { + return nil + } + out := new(VolumeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VsphereVirtualDiskVolumeSource) DeepCopyInto(out *VsphereVirtualDiskVolumeSource) { *out = *in @@ -6873,19 +6979,3 @@ func (in *WindowsSecurityContextOptions) DeepCopy() *WindowsSecurityContextOptio in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkloadReference) DeepCopyInto(out *WorkloadReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadReference. -func (in *WorkloadReference) DeepCopy() *WorkloadReference { - if in == nil { - return nil - } - out := new(WorkloadReference) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/api/core/v1/zz_generated.model_name.go b/staging/src/k8s.io/api/core/v1/zz_generated.model_name.go index 523bb3a85290b..8e79ad909ed54 100644 --- a/staging/src/k8s.io/api/core/v1/zz_generated.model_name.go +++ b/staging/src/k8s.io/api/core/v1/zz_generated.model_name.go @@ -421,6 +421,11 @@ func (in ImageVolumeSource) OpenAPIModelName() string { return "io.k8s.api.core.v1.ImageVolumeSource" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ImageVolumeStatus) OpenAPIModelName() string { + return "io.k8s.api.core.v1.ImageVolumeStatus" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in KeyToPath) OpenAPIModelName() string { return "io.k8s.api.core.v1.KeyToPath" @@ -536,6 +541,11 @@ func (in NodeAffinity) OpenAPIModelName() string { return "io.k8s.api.core.v1.NodeAffinity" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NodeAllocatableResourceClaimStatus) OpenAPIModelName() string { + return "io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in NodeCondition) OpenAPIModelName() string { return "io.k8s.api.core.v1.NodeCondition" @@ -796,6 +806,11 @@ func (in PodSchedulingGate) OpenAPIModelName() string { return "io.k8s.api.core.v1.PodSchedulingGate" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodSchedulingGroup) OpenAPIModelName() string { + return "io.k8s.api.core.v1.PodSchedulingGroup" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in PodSecurityContext) OpenAPIModelName() string { return "io.k8s.api.core.v1.PodSecurityContext" @@ -1191,6 +1206,11 @@ func (in VolumeSource) OpenAPIModelName() string { return "io.k8s.api.core.v1.VolumeSource" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in VolumeStatus) OpenAPIModelName() string { + return "io.k8s.api.core.v1.VolumeStatus" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in VsphereVirtualDiskVolumeSource) OpenAPIModelName() string { return "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource" @@ -1205,8 +1225,3 @@ func (in WeightedPodAffinityTerm) OpenAPIModelName() string { func (in WindowsSecurityContextOptions) OpenAPIModelName() string { return "io.k8s.api.core.v1.WindowsSecurityContextOptions" } - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in WorkloadReference) OpenAPIModelName() string { - return "io.k8s.api.core.v1.WorkloadReference" -} diff --git a/staging/src/k8s.io/api/discovery/v1/generated.proto b/staging/src/k8s.io/api/discovery/v1/generated.proto index 97582a1b2a021..edbc4304b169d 100644 --- a/staging/src/k8s.io/api/discovery/v1/generated.proto +++ b/staging/src/k8s.io/api/discovery/v1/generated.proto @@ -38,6 +38,9 @@ message Endpoint { // controller will always have exactly 1 address. No semantics are defined for // additional addresses beyond the first, and kube-proxy does not look at them. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxItems=100 repeated string addresses = 1; // conditions contains information about the current status of the endpoint. @@ -181,11 +184,16 @@ message EndpointSlice { // The EndpointSlice controller only generates, and kube-proxy only processes, // slices of addressType "IPv4" and "IPv6". No semantics are defined for // the "FQDN" type. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable optional string addressType = 4; // endpoints is a list of unique endpoints in this slice. Each slice may // include a maximum of 1000 endpoints. + // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Endpoint endpoints = 2; // ports specifies the list of network ports exposed by each endpoint in diff --git a/staging/src/k8s.io/api/discovery/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/discovery/v1/generated.protomessage.pb.go deleted file mode 100644 index 9e246b87b8bf3..0000000000000 --- a/staging/src/k8s.io/api/discovery/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Endpoint) ProtoMessage() {} - -func (*EndpointConditions) ProtoMessage() {} - -func (*EndpointHints) ProtoMessage() {} - -func (*EndpointPort) ProtoMessage() {} - -func (*EndpointSlice) ProtoMessage() {} - -func (*EndpointSliceList) ProtoMessage() {} - -func (*ForNode) ProtoMessage() {} - -func (*ForZone) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/discovery/v1/types.go b/staging/src/k8s.io/api/discovery/v1/types.go index ca78ce386963b..c291ed85ddb19 100644 --- a/staging/src/k8s.io/api/discovery/v1/types.go +++ b/staging/src/k8s.io/api/discovery/v1/types.go @@ -48,11 +48,16 @@ type EndpointSlice struct { // The EndpointSlice controller only generates, and kube-proxy only processes, // slices of addressType "IPv4" and "IPv6". No semantics are defined for // the "FQDN" type. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` // endpoints is a list of unique endpoints in this slice. Each slice may // include a maximum of 1000 endpoints. + // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Endpoints []Endpoint `json:"endpoints" protobuf:"bytes,2,rep,name=endpoints"` // ports specifies the list of network ports exposed by each endpoint in @@ -68,6 +73,7 @@ type EndpointSlice struct { // AddressType represents the type of address referred to by an endpoint. // +enum +// +k8s:alpha(since: "1.36")=+k8s:enum type AddressType string const ( @@ -90,6 +96,9 @@ type Endpoint struct { // controller will always have exactly 1 address. No semantics are defined for // additional addresses beyond the first, and kube-proxy does not look at them. // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxItems=100 Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"` // conditions contains information about the current status of the endpoint. diff --git a/staging/src/k8s.io/api/discovery/v1beta1/generated.proto b/staging/src/k8s.io/api/discovery/v1beta1/generated.proto index 7b9d983b831a8..9b6edb14793fd 100644 --- a/staging/src/k8s.io/api/discovery/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/discovery/v1beta1/generated.proto @@ -38,6 +38,9 @@ message Endpoint { // 100. These are all assumed to be fungible and clients may choose to only // use the first element. Refer to: https://issue.k8s.io/106267 // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxItems=100 repeated string addresses = 1; // conditions contains information about the current status of the endpoint. @@ -167,11 +170,15 @@ message EndpointSlice { // * IPv4: Represents an IPv4 Address. // * IPv6: Represents an IPv6 Address. // * FQDN: Represents a Fully Qualified Domain Name. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable optional string addressType = 4; // endpoints is a list of unique endpoints in this slice. Each slice may // include a maximum of 1000 endpoints. // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Endpoint endpoints = 2; // ports specifies the list of network ports exposed by each endpoint in diff --git a/staging/src/k8s.io/api/discovery/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/discovery/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 6e60bb139e7f1..0000000000000 --- a/staging/src/k8s.io/api/discovery/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*Endpoint) ProtoMessage() {} - -func (*EndpointConditions) ProtoMessage() {} - -func (*EndpointHints) ProtoMessage() {} - -func (*EndpointPort) ProtoMessage() {} - -func (*EndpointSlice) ProtoMessage() {} - -func (*EndpointSliceList) ProtoMessage() {} - -func (*ForNode) ProtoMessage() {} - -func (*ForZone) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/discovery/v1beta1/types.go b/staging/src/k8s.io/api/discovery/v1beta1/types.go index 11ec1b3968a88..1334194d2aa7d 100644 --- a/staging/src/k8s.io/api/discovery/v1beta1/types.go +++ b/staging/src/k8s.io/api/discovery/v1beta1/types.go @@ -45,11 +45,15 @@ type EndpointSlice struct { // * IPv4: Represents an IPv4 Address. // * IPv6: Represents an IPv6 Address. // * FQDN: Represents a Fully Qualified Domain Name. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` // endpoints is a list of unique endpoints in this slice. Each slice may // include a maximum of 1000 endpoints. // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Endpoints []Endpoint `json:"endpoints" protobuf:"bytes,2,rep,name=endpoints"` // ports specifies the list of network ports exposed by each endpoint in @@ -63,6 +67,8 @@ type EndpointSlice struct { } // AddressType represents the type of address referred to by an endpoint. +// +enum +// +k8s:alpha(since: "1.36")=+k8s:enum type AddressType string const ( @@ -85,6 +91,9 @@ type Endpoint struct { // 100. These are all assumed to be fungible and clients may choose to only // use the first element. Refer to: https://issue.k8s.io/106267 // +listType=set + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxItems=100 Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"` // conditions contains information about the current status of the endpoint. diff --git a/staging/src/k8s.io/api/events/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/events/v1/generated.protomessage.pb.go deleted file mode 100644 index 09ff231dac6d1..0000000000000 --- a/staging/src/k8s.io/api/events/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Event) ProtoMessage() {} - -func (*EventList) ProtoMessage() {} - -func (*EventSeries) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/events/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/events/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index a70924a20dcd1..0000000000000 --- a/staging/src/k8s.io/api/events/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*Event) ProtoMessage() {} - -func (*EventList) ProtoMessage() {} - -func (*EventSeries) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/extensions/v1beta1/generated.proto b/staging/src/k8s.io/api/extensions/v1beta1/generated.proto index c664c71bd2689..f090306686a26 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/extensions/v1beta1/generated.proto @@ -428,6 +428,8 @@ message HTTPIngressRuleValue { message IPBlock { // CIDR is a string representing the IP Block // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string cidr = 1; // Except is a slice of CIDRs that should not be included within an IP Block @@ -686,6 +688,7 @@ message NetworkPolicyEgressRule { // allows traffic only if the traffic matches at least one item in the to list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyPeer to = 2; } @@ -708,6 +711,7 @@ message NetworkPolicyIngressRule { // traffic matches at least one item in the from list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyPeer from = 2; } @@ -746,6 +750,7 @@ message NetworkPolicyPeer { // IPBlock defines policy on a particular IPBlock. If this field is set then // neither of the other fields can be. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional IPBlock ipBlock = 3; } @@ -789,6 +794,7 @@ message NetworkPolicySpec { // (and serves solely to ensure that the pods it selects are isolated by default). // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyIngressRule ingress = 2; // List of egress rules to be applied to the selected pods. Outgoing traffic is @@ -800,6 +806,7 @@ message NetworkPolicySpec { // This field is beta-level in 1.8 // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyEgressRule egress = 3; // List of rule types that the NetworkPolicy relates to. @@ -1039,9 +1046,9 @@ message Scale { message ScaleSpec { // desired number of instances for the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 optional int32 replicas = 1; } diff --git a/staging/src/k8s.io/api/extensions/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/extensions/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index ef18ae29964f5..0000000000000 --- a/staging/src/k8s.io/api/extensions/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,112 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*DaemonSet) ProtoMessage() {} - -func (*DaemonSetCondition) ProtoMessage() {} - -func (*DaemonSetList) ProtoMessage() {} - -func (*DaemonSetSpec) ProtoMessage() {} - -func (*DaemonSetStatus) ProtoMessage() {} - -func (*DaemonSetUpdateStrategy) ProtoMessage() {} - -func (*Deployment) ProtoMessage() {} - -func (*DeploymentCondition) ProtoMessage() {} - -func (*DeploymentList) ProtoMessage() {} - -func (*DeploymentRollback) ProtoMessage() {} - -func (*DeploymentSpec) ProtoMessage() {} - -func (*DeploymentStatus) ProtoMessage() {} - -func (*DeploymentStrategy) ProtoMessage() {} - -func (*HTTPIngressPath) ProtoMessage() {} - -func (*HTTPIngressRuleValue) ProtoMessage() {} - -func (*IPBlock) ProtoMessage() {} - -func (*Ingress) ProtoMessage() {} - -func (*IngressBackend) ProtoMessage() {} - -func (*IngressList) ProtoMessage() {} - -func (*IngressLoadBalancerIngress) ProtoMessage() {} - -func (*IngressLoadBalancerStatus) ProtoMessage() {} - -func (*IngressPortStatus) ProtoMessage() {} - -func (*IngressRule) ProtoMessage() {} - -func (*IngressRuleValue) ProtoMessage() {} - -func (*IngressSpec) ProtoMessage() {} - -func (*IngressStatus) ProtoMessage() {} - -func (*IngressTLS) ProtoMessage() {} - -func (*NetworkPolicy) ProtoMessage() {} - -func (*NetworkPolicyEgressRule) ProtoMessage() {} - -func (*NetworkPolicyIngressRule) ProtoMessage() {} - -func (*NetworkPolicyList) ProtoMessage() {} - -func (*NetworkPolicyPeer) ProtoMessage() {} - -func (*NetworkPolicyPort) ProtoMessage() {} - -func (*NetworkPolicySpec) ProtoMessage() {} - -func (*ReplicaSet) ProtoMessage() {} - -func (*ReplicaSetCondition) ProtoMessage() {} - -func (*ReplicaSetList) ProtoMessage() {} - -func (*ReplicaSetSpec) ProtoMessage() {} - -func (*ReplicaSetStatus) ProtoMessage() {} - -func (*RollbackConfig) ProtoMessage() {} - -func (*RollingUpdateDaemonSet) ProtoMessage() {} - -func (*RollingUpdateDeployment) ProtoMessage() {} - -func (*Scale) ProtoMessage() {} - -func (*ScaleSpec) ProtoMessage() {} - -func (*ScaleStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types.go b/staging/src/k8s.io/api/extensions/v1beta1/types.go index c0d8b4f9592d2..09d143ea60404 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types.go @@ -27,9 +27,9 @@ import ( type ScaleSpec struct { // desired number of instances for the scaled object. // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +default=0 - // +k8s:minimum=0 + // +k8s:alpha(since: "1.36")=+k8s:minimum=0 Replicas int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` } @@ -57,7 +57,7 @@ type ScaleStatus struct { // +k8s:prerelease-lifecycle-gen:introduced=1.1 // +k8s:prerelease-lifecycle-gen:deprecated=1.2 // +k8s:prerelease-lifecycle-gen:removed=1.16 -// +k8s:isSubresource=/scale +// +k8s:isSubresource="/scale" // represents a scaling request for a resource. type Scale struct { @@ -1106,6 +1106,7 @@ type NetworkPolicySpec struct { // (and serves solely to ensure that the pods it selects are isolated by default). // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Ingress []NetworkPolicyIngressRule `json:"ingress,omitempty" protobuf:"bytes,2,rep,name=ingress"` // List of egress rules to be applied to the selected pods. Outgoing traffic is @@ -1117,6 +1118,7 @@ type NetworkPolicySpec struct { // This field is beta-level in 1.8 // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Egress []NetworkPolicyEgressRule `json:"egress,omitempty" protobuf:"bytes,3,rep,name=egress"` // List of rule types that the NetworkPolicy relates to. @@ -1153,6 +1155,7 @@ type NetworkPolicyIngressRule struct { // traffic matches at least one item in the from list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional From []NetworkPolicyPeer `json:"from,omitempty" protobuf:"bytes,2,rep,name=from"` } @@ -1177,6 +1180,7 @@ type NetworkPolicyEgressRule struct { // allows traffic only if the traffic matches at least one item in the to list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional To []NetworkPolicyPeer `json:"to,omitempty" protobuf:"bytes,2,rep,name=to"` } @@ -1209,6 +1213,8 @@ type NetworkPolicyPort struct { type IPBlock struct { // CIDR is a string representing the IP Block // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + // +required + // +k8s:alpha(since: "1.36")=+k8s:required CIDR string `json:"cidr" protobuf:"bytes,1,name=cidr"` // Except is a slice of CIDRs that should not be included within an IP Block // Valid examples are "192.168.1.0/24" or "2001:db8::/64" @@ -1241,6 +1247,7 @@ type NetworkPolicyPeer struct { // IPBlock defines policy on a particular IPBlock. If this field is set then // neither of the other fields can be. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional IPBlock *IPBlock `json:"ipBlock,omitempty" protobuf:"bytes,3,rep,name=ipBlock"` } diff --git a/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.validations.go b/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.validations.go index f4f0e317a763d..d504d5452e8cc 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.validations.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.validations.go @@ -25,6 +25,7 @@ import ( context "context" fmt "fmt" + equality "k8s.io/apimachinery/pkg/api/equality" operation "k8s.io/apimachinery/pkg/api/operation" safe "k8s.io/apimachinery/pkg/api/safe" validate "k8s.io/apimachinery/pkg/api/validate" @@ -37,6 +38,14 @@ func init() { localSchemeBuilder.Register(RegisterValidations) } // RegisterValidations adds validation functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterValidations(scheme *runtime.Scheme) error { + // type NetworkPolicy + scheme.AddValidationFunc((*NetworkPolicy)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_NetworkPolicy(ctx, op, nil /* fldPath */, obj.(*NetworkPolicy), safe.Cast[*NetworkPolicy](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type Scale scheme.AddValidationFunc((*Scale)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -48,6 +57,187 @@ func RegisterValidations(scheme *runtime.Scheme) error { return nil } +// Validate_IPBlock validates an instance of IPBlock according +// to declarative validation rules in the API schema. +func Validate_IPBlock(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *IPBlock) (errs field.ErrorList) { + // field IPBlock.CIDR + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("cidr"), &obj.CIDR, safe.Field(oldObj, func(oldObj *IPBlock) *string { return &oldObj.CIDR }), oldObj != nil)...) + + // field IPBlock.Except has no validation + return errs +} + +// Validate_NetworkPolicy validates an instance of NetworkPolicy according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicy(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NetworkPolicy) (errs field.ErrorList) { + // field NetworkPolicy.TypeMeta has no validation + // field NetworkPolicy.ObjectMeta has no validation + + // field NetworkPolicy.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *NetworkPolicySpec, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_NetworkPolicySpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *NetworkPolicy) *NetworkPolicySpec { return &oldObj.Spec }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicyEgressRule validates an instance of NetworkPolicyEgressRule according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicyEgressRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NetworkPolicyEgressRule) (errs field.ErrorList) { + // field NetworkPolicyEgressRule.Ports has no validation + + // field NetworkPolicyEgressRule.To + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []NetworkPolicyPeer, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyPeer)...) + return + }(fldPath.Child("to"), obj.To, safe.Field(oldObj, func(oldObj *NetworkPolicyEgressRule) []NetworkPolicyPeer { return oldObj.To }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicyIngressRule validates an instance of NetworkPolicyIngressRule according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicyIngressRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NetworkPolicyIngressRule) (errs field.ErrorList) { + // field NetworkPolicyIngressRule.Ports has no validation + + // field NetworkPolicyIngressRule.From + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []NetworkPolicyPeer, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyPeer)...) + return + }(fldPath.Child("from"), obj.From, safe.Field(oldObj, func(oldObj *NetworkPolicyIngressRule) []NetworkPolicyPeer { return oldObj.From }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicyPeer validates an instance of NetworkPolicyPeer according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicyPeer(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NetworkPolicyPeer) (errs field.ErrorList) { + // field NetworkPolicyPeer.PodSelector has no validation + // field NetworkPolicyPeer.NamespaceSelector has no validation + + // field NetworkPolicyPeer.IPBlock + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IPBlock, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IPBlock(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("ipBlock"), obj.IPBlock, safe.Field(oldObj, func(oldObj *NetworkPolicyPeer) *IPBlock { return oldObj.IPBlock }), oldObj != nil)...) + + return errs +} + +// Validate_NetworkPolicySpec validates an instance of NetworkPolicySpec according +// to declarative validation rules in the API schema. +func Validate_NetworkPolicySpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NetworkPolicySpec) (errs field.ErrorList) { + // field NetworkPolicySpec.PodSelector has no validation + + // field NetworkPolicySpec.Ingress + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []NetworkPolicyIngressRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyIngressRule)...) + return + }(fldPath.Child("ingress"), obj.Ingress, safe.Field(oldObj, func(oldObj *NetworkPolicySpec) []NetworkPolicyIngressRule { return oldObj.Ingress }), oldObj != nil)...) + + // field NetworkPolicySpec.Egress + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []NetworkPolicyEgressRule, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NetworkPolicyEgressRule)...) + return + }(fldPath.Child("egress"), obj.Egress, safe.Field(oldObj, func(oldObj *NetworkPolicySpec) []NetworkPolicyEgressRule { return oldObj.Egress }), oldObj != nil)...) + + // field NetworkPolicySpec.PolicyTypes has no validation + return errs +} + // Validate_Scale validates an instance of Scale according // to declarative validation rules in the API schema. func Validate_Scale(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Scale) (errs field.ErrorList) { @@ -76,13 +266,12 @@ func Validate_ScaleSpec(ctx context.Context, op operation.Operation, fldPath *fi // field ScaleSpec.Replicas errs = append(errs, func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { - // optional value-type fields with zero-value defaults are purely documentation // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { return nil } // call field-attached validations - errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha()...) return }(fldPath.Child("replicas"), &obj.Replicas, safe.Field(oldObj, func(oldObj *ScaleSpec) *int32 { return &oldObj.Replicas }), oldObj != nil)...) diff --git a/staging/src/k8s.io/api/flowcontrol/v1/generated.proto b/staging/src/k8s.io/api/flowcontrol/v1/generated.proto index 33a135889e517..eb9186d013afe 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1/generated.proto +++ b/staging/src/k8s.io/api/flowcontrol/v1/generated.proto @@ -179,11 +179,16 @@ message LimitResponse { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden optional QueuingConfiguration queuing = 2; } @@ -367,11 +372,16 @@ message PriorityLevelConfigurationSpec { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden optional LimitedPriorityLevelConfiguration limited = 2; // `exempt` specifies how requests are handled for an exempt priority level. @@ -380,6 +390,9 @@ message PriorityLevelConfigurationSpec { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden optional ExemptPriorityLevelConfiguration exempt = 3; } diff --git a/staging/src/k8s.io/api/flowcontrol/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/flowcontrol/v1/generated.protomessage.pb.go deleted file mode 100644 index 51612dee82634..0000000000000 --- a/staging/src/k8s.io/api/flowcontrol/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*ExemptPriorityLevelConfiguration) ProtoMessage() {} - -func (*FlowDistinguisherMethod) ProtoMessage() {} - -func (*FlowSchema) ProtoMessage() {} - -func (*FlowSchemaCondition) ProtoMessage() {} - -func (*FlowSchemaList) ProtoMessage() {} - -func (*FlowSchemaSpec) ProtoMessage() {} - -func (*FlowSchemaStatus) ProtoMessage() {} - -func (*GroupSubject) ProtoMessage() {} - -func (*LimitResponse) ProtoMessage() {} - -func (*LimitedPriorityLevelConfiguration) ProtoMessage() {} - -func (*NonResourcePolicyRule) ProtoMessage() {} - -func (*PolicyRulesWithSubjects) ProtoMessage() {} - -func (*PriorityLevelConfiguration) ProtoMessage() {} - -func (*PriorityLevelConfigurationCondition) ProtoMessage() {} - -func (*PriorityLevelConfigurationList) ProtoMessage() {} - -func (*PriorityLevelConfigurationReference) ProtoMessage() {} - -func (*PriorityLevelConfigurationSpec) ProtoMessage() {} - -func (*PriorityLevelConfigurationStatus) ProtoMessage() {} - -func (*QueuingConfiguration) ProtoMessage() {} - -func (*ResourcePolicyRule) ProtoMessage() {} - -func (*ServiceAccountSubject) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} - -func (*UserSubject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/flowcontrol/v1/types.go b/staging/src/k8s.io/api/flowcontrol/v1/types.go index ad72bcee2285f..4a40bdca42e52 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1/types.go +++ b/staging/src/k8s.io/api/flowcontrol/v1/types.go @@ -429,11 +429,16 @@ type PriorityLevelConfigurationSpec struct { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type PriorityLevelEnablement `json:"type" protobuf:"bytes,1,opt,name=type"` // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden Limited *LimitedPriorityLevelConfiguration `json:"limited,omitempty" protobuf:"bytes,2,opt,name=limited"` // `exempt` specifies how requests are handled for an exempt priority level. @@ -442,6 +447,9 @@ type PriorityLevelConfigurationSpec struct { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden Exempt *ExemptPriorityLevelConfiguration `json:"exempt,omitempty" protobuf:"bytes,3,opt,name=exempt"` } @@ -566,11 +574,16 @@ type LimitResponse struct { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type LimitResponseType `json:"type" protobuf:"bytes,1,opt,name=type"` // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden Queuing *QueuingConfiguration `json:"queuing,omitempty" protobuf:"bytes,2,opt,name=queuing"` } diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.proto b/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.proto index 61ed3833aeec5..8a701a61003da 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.proto @@ -177,11 +177,16 @@ message LimitResponse { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden optional QueuingConfiguration queuing = 2; } @@ -360,11 +365,16 @@ message PriorityLevelConfigurationSpec { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden optional LimitedPriorityLevelConfiguration limited = 2; // `exempt` specifies how requests are handled for an exempt priority level. @@ -373,6 +383,9 @@ message PriorityLevelConfigurationSpec { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden optional ExemptPriorityLevelConfiguration exempt = 3; } diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 9d2059597a69d..0000000000000 --- a/staging/src/k8s.io/api/flowcontrol/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*ExemptPriorityLevelConfiguration) ProtoMessage() {} - -func (*FlowDistinguisherMethod) ProtoMessage() {} - -func (*FlowSchema) ProtoMessage() {} - -func (*FlowSchemaCondition) ProtoMessage() {} - -func (*FlowSchemaList) ProtoMessage() {} - -func (*FlowSchemaSpec) ProtoMessage() {} - -func (*FlowSchemaStatus) ProtoMessage() {} - -func (*GroupSubject) ProtoMessage() {} - -func (*LimitResponse) ProtoMessage() {} - -func (*LimitedPriorityLevelConfiguration) ProtoMessage() {} - -func (*NonResourcePolicyRule) ProtoMessage() {} - -func (*PolicyRulesWithSubjects) ProtoMessage() {} - -func (*PriorityLevelConfiguration) ProtoMessage() {} - -func (*PriorityLevelConfigurationCondition) ProtoMessage() {} - -func (*PriorityLevelConfigurationList) ProtoMessage() {} - -func (*PriorityLevelConfigurationReference) ProtoMessage() {} - -func (*PriorityLevelConfigurationSpec) ProtoMessage() {} - -func (*PriorityLevelConfigurationStatus) ProtoMessage() {} - -func (*QueuingConfiguration) ProtoMessage() {} - -func (*ResourcePolicyRule) ProtoMessage() {} - -func (*ServiceAccountSubject) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} - -func (*UserSubject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta1/types.go b/staging/src/k8s.io/api/flowcontrol/v1beta1/types.go index abc3e420096f5..0de6ea940ea54 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1beta1/types.go +++ b/staging/src/k8s.io/api/flowcontrol/v1beta1/types.go @@ -431,11 +431,16 @@ type PriorityLevelConfigurationSpec struct { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type PriorityLevelEnablement `json:"type" protobuf:"bytes,1,opt,name=type"` // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden Limited *LimitedPriorityLevelConfiguration `json:"limited,omitempty" protobuf:"bytes,2,opt,name=limited"` // `exempt` specifies how requests are handled for an exempt priority level. @@ -444,6 +449,9 @@ type PriorityLevelConfigurationSpec struct { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden Exempt *ExemptPriorityLevelConfiguration `json:"exempt,omitempty" protobuf:"bytes,3,opt,name=exempt"` } @@ -563,11 +571,16 @@ type LimitResponse struct { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type LimitResponseType `json:"type" protobuf:"bytes,1,opt,name=type"` // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden Queuing *QueuingConfiguration `json:"queuing,omitempty" protobuf:"bytes,2,opt,name=queuing"` } diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.proto b/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.proto index d6073fc9259c2..371b90a5cd940 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.proto @@ -177,11 +177,16 @@ message LimitResponse { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden optional QueuingConfiguration queuing = 2; } @@ -360,11 +365,16 @@ message PriorityLevelConfigurationSpec { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden optional LimitedPriorityLevelConfiguration limited = 2; // `exempt` specifies how requests are handled for an exempt priority level. @@ -373,6 +383,9 @@ message PriorityLevelConfigurationSpec { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden optional ExemptPriorityLevelConfiguration exempt = 3; } diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.protomessage.pb.go b/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.protomessage.pb.go deleted file mode 100644 index 672834e9fa6ae..0000000000000 --- a/staging/src/k8s.io/api/flowcontrol/v1beta2/generated.protomessage.pb.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta2 - -func (*ExemptPriorityLevelConfiguration) ProtoMessage() {} - -func (*FlowDistinguisherMethod) ProtoMessage() {} - -func (*FlowSchema) ProtoMessage() {} - -func (*FlowSchemaCondition) ProtoMessage() {} - -func (*FlowSchemaList) ProtoMessage() {} - -func (*FlowSchemaSpec) ProtoMessage() {} - -func (*FlowSchemaStatus) ProtoMessage() {} - -func (*GroupSubject) ProtoMessage() {} - -func (*LimitResponse) ProtoMessage() {} - -func (*LimitedPriorityLevelConfiguration) ProtoMessage() {} - -func (*NonResourcePolicyRule) ProtoMessage() {} - -func (*PolicyRulesWithSubjects) ProtoMessage() {} - -func (*PriorityLevelConfiguration) ProtoMessage() {} - -func (*PriorityLevelConfigurationCondition) ProtoMessage() {} - -func (*PriorityLevelConfigurationList) ProtoMessage() {} - -func (*PriorityLevelConfigurationReference) ProtoMessage() {} - -func (*PriorityLevelConfigurationSpec) ProtoMessage() {} - -func (*PriorityLevelConfigurationStatus) ProtoMessage() {} - -func (*QueuingConfiguration) ProtoMessage() {} - -func (*ResourcePolicyRule) ProtoMessage() {} - -func (*ServiceAccountSubject) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} - -func (*UserSubject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta2/types.go b/staging/src/k8s.io/api/flowcontrol/v1beta2/types.go index c66cb173f4a2c..2f82a6823b439 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1beta2/types.go +++ b/staging/src/k8s.io/api/flowcontrol/v1beta2/types.go @@ -431,11 +431,16 @@ type PriorityLevelConfigurationSpec struct { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type PriorityLevelEnablement `json:"type" protobuf:"bytes,1,opt,name=type"` // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden Limited *LimitedPriorityLevelConfiguration `json:"limited,omitempty" protobuf:"bytes,2,opt,name=limited"` // `exempt` specifies how requests are handled for an exempt priority level. @@ -444,6 +449,9 @@ type PriorityLevelConfigurationSpec struct { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden Exempt *ExemptPriorityLevelConfiguration `json:"exempt,omitempty" protobuf:"bytes,3,opt,name=exempt"` } @@ -563,11 +571,16 @@ type LimitResponse struct { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type LimitResponseType `json:"type" protobuf:"bytes,1,opt,name=type"` // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden Queuing *QueuingConfiguration `json:"queuing,omitempty" protobuf:"bytes,2,opt,name=queuing"` } diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.proto b/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.proto index c6504d4353f09..7e11de2a28d6f 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.proto +++ b/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.proto @@ -179,11 +179,16 @@ message LimitResponse { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden optional QueuingConfiguration queuing = 2; } @@ -362,11 +367,16 @@ message PriorityLevelConfigurationSpec { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator optional string type = 1; // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden optional LimitedPriorityLevelConfiguration limited = 2; // `exempt` specifies how requests are handled for an exempt priority level. @@ -375,6 +385,9 @@ message PriorityLevelConfigurationSpec { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden optional ExemptPriorityLevelConfiguration exempt = 3; } diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.protomessage.pb.go b/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.protomessage.pb.go deleted file mode 100644 index 20407c045c02b..0000000000000 --- a/staging/src/k8s.io/api/flowcontrol/v1beta3/generated.protomessage.pb.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta3 - -func (*ExemptPriorityLevelConfiguration) ProtoMessage() {} - -func (*FlowDistinguisherMethod) ProtoMessage() {} - -func (*FlowSchema) ProtoMessage() {} - -func (*FlowSchemaCondition) ProtoMessage() {} - -func (*FlowSchemaList) ProtoMessage() {} - -func (*FlowSchemaSpec) ProtoMessage() {} - -func (*FlowSchemaStatus) ProtoMessage() {} - -func (*GroupSubject) ProtoMessage() {} - -func (*LimitResponse) ProtoMessage() {} - -func (*LimitedPriorityLevelConfiguration) ProtoMessage() {} - -func (*NonResourcePolicyRule) ProtoMessage() {} - -func (*PolicyRulesWithSubjects) ProtoMessage() {} - -func (*PriorityLevelConfiguration) ProtoMessage() {} - -func (*PriorityLevelConfigurationCondition) ProtoMessage() {} - -func (*PriorityLevelConfigurationList) ProtoMessage() {} - -func (*PriorityLevelConfigurationReference) ProtoMessage() {} - -func (*PriorityLevelConfigurationSpec) ProtoMessage() {} - -func (*PriorityLevelConfigurationStatus) ProtoMessage() {} - -func (*QueuingConfiguration) ProtoMessage() {} - -func (*ResourcePolicyRule) ProtoMessage() {} - -func (*ServiceAccountSubject) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} - -func (*UserSubject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/flowcontrol/v1beta3/types.go b/staging/src/k8s.io/api/flowcontrol/v1beta3/types.go index 0ffc22a236566..26b67629cef1a 100644 --- a/staging/src/k8s.io/api/flowcontrol/v1beta3/types.go +++ b/staging/src/k8s.io/api/flowcontrol/v1beta3/types.go @@ -447,11 +447,16 @@ type PriorityLevelConfigurationSpec struct { // capacity is made available exclusively to this priority level. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type PriorityLevelEnablement `json:"type" protobuf:"bytes,1,opt,name=type"` // `limited` specifies how requests are handled for a Limited priority level. // This field must be non-empty if and only if `type` is `"Limited"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:forbidden Limited *LimitedPriorityLevelConfiguration `json:"limited,omitempty" protobuf:"bytes,2,opt,name=limited"` // `exempt` specifies how requests are handled for an exempt priority level. @@ -460,6 +465,9 @@ type PriorityLevelConfigurationSpec struct { // If empty and `type` is `"Exempt"` then the default values // for `ExemptPriorityLevelConfiguration` apply. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Exempt")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Limited")=+k8s:forbidden Exempt *ExemptPriorityLevelConfiguration `json:"exempt,omitempty" protobuf:"bytes,3,opt,name=exempt"` } @@ -579,11 +587,16 @@ type LimitResponse struct { // are rejected. // Required. // +unionDiscriminator + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:discriminator Type LimitResponseType `json:"type" protobuf:"bytes,1,opt,name=type"` // `queuing` holds the configuration parameters for queuing. // This field may be non-empty only if `type` is `"Queue"`. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:member("Queue")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:member("Reject")=+k8s:forbidden Queuing *QueuingConfiguration `json:"queuing,omitempty" protobuf:"bytes,2,opt,name=queuing"` } diff --git a/staging/src/k8s.io/api/go.mod b/staging/src/k8s.io/api/go.mod index cbeb95b5a92f7..b017d007fe3f0 100644 --- a/staging/src/k8s.io/api/go.mod +++ b/staging/src/k8s.io/api/go.mod @@ -2,13 +2,13 @@ module k8s.io/api -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( k8s.io/apimachinery v0.0.0 - k8s.io/klog/v2 v2.130.1 + k8s.io/klog/v2 v2.140.0 ) require ( @@ -23,19 +23,19 @@ require ( github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 k8s.io/apimachinery => ../apimachinery + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/api/go.sum b/staging/src/k8s.io/api/go.sum index efd188ea1a7aa..eab20f29d9034 100644 --- a/staging/src/k8s.io/api/go.sum +++ b/staging/src/k8s.io/api/go.sum @@ -1,5 +1,4 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,12 +12,10 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -31,7 +28,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -40,8 +37,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -61,20 +56,20 @@ 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= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -84,17 +79,17 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= 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/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto index 5ea5c0ec8e8aa..03b1ac45c026b 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto @@ -36,6 +36,7 @@ message ImageReview { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // Spec holds information about the pod being evaluated + // +optional optional ImageReviewSpec spec = 2; // Status is filled in by the backend and indicates whether the pod should be allowed. @@ -71,6 +72,7 @@ message ImageReviewSpec { // ImageReviewStatus is the result of the review for the pod creation request. message ImageReviewStatus { // Allowed indicates that all images were allowed to be run. + // +optional optional bool allowed = 1; // Reason should be empty unless Allowed is false in which case it diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index 215f52a377347..0000000000000 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*ImageReview) ProtoMessage() {} - -func (*ImageReviewContainerSpec) ProtoMessage() {} - -func (*ImageReviewSpec) ProtoMessage() {} - -func (*ImageReviewStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/types.go b/staging/src/k8s.io/api/imagepolicy/v1alpha1/types.go index 19ac2b536f94e..cc8f4cf89f210 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/types.go +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/types.go @@ -34,6 +34,7 @@ type ImageReview struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec holds information about the pod being evaluated + // +optional Spec ImageReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // Status is filled in by the backend and indicates whether the pod should be allowed. @@ -68,6 +69,7 @@ type ImageReviewContainerSpec struct { // ImageReviewStatus is the result of the review for the pod creation request. type ImageReviewStatus struct { // Allowed indicates that all images were allowed to be run. + // +optional Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` // Reason should be empty unless Allowed is false in which case it // may contain a short description of what is wrong. Kubernetes diff --git a/staging/src/k8s.io/api/networking/v1/generated.proto b/staging/src/k8s.io/api/networking/v1/generated.proto index 16a2792aa6411..26dcfdfcc3244 100644 --- a/staging/src/k8s.io/api/networking/v1/generated.proto +++ b/staging/src/k8s.io/api/networking/v1/generated.proto @@ -87,7 +87,7 @@ message IPAddress { // spec is the desired state of the IPAddress. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required optional IPAddressSpec spec = 2; } @@ -107,6 +107,8 @@ message IPAddressSpec { // ParentRef references the resource that an IPAddress is attached to. // An IPAddress must reference a parent object. // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable optional ParentReference parentRef = 1; } @@ -116,6 +118,8 @@ message IPAddressSpec { message IPBlock { // cidr is a string representing the IPBlock // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string cidr = 1; // except is a slice of CIDRs that should not be included within an IPBlock @@ -199,9 +203,13 @@ message IngressClassParametersReference { optional string aPIGroup = 1; // kind is the type of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string kind = 2; // name is the name of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; // scope represents if this refers to a cluster or namespace scoped resource. @@ -230,6 +238,7 @@ message IngressClassSpec { // configuration for the controller. This is optional if the controller does // not require extra parameters. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional IngressClassParametersReference parameters = 2; } @@ -446,6 +455,7 @@ message NetworkPolicyEgressRule { // allows traffic only if the traffic matches at least one item in the to list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyPeer to = 2; } @@ -468,6 +478,7 @@ message NetworkPolicyIngressRule { // allows traffic only if the traffic matches at least one item in the from list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyPeer from = 2; } @@ -506,6 +517,7 @@ message NetworkPolicyPeer { // ipBlock defines policy on a particular IPBlock. If this field is set then // neither of the other fields can be. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional IPBlock ipBlock = 3; } @@ -551,6 +563,7 @@ message NetworkPolicySpec { // solely to ensure that the pods it selects are isolated by default) // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyIngressRule ingress = 2; // egress is a list of egress rules to be applied to the selected pods. Outgoing traffic @@ -562,6 +575,7 @@ message NetworkPolicySpec { // This field is beta-level in 1.8 // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated NetworkPolicyEgressRule egress = 3; // policyTypes is a list of rule types that the NetworkPolicy relates to. @@ -587,6 +601,7 @@ message ParentReference { // Resource is the resource of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string resource = 2; // Namespace is the namespace of the object being referenced. @@ -595,6 +610,7 @@ message ParentReference { // Name is the name of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 4; } diff --git a/staging/src/k8s.io/api/networking/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/networking/v1/generated.protomessage.pb.go deleted file mode 100644 index 54918d47e709b..0000000000000 --- a/staging/src/k8s.io/api/networking/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*HTTPIngressPath) ProtoMessage() {} - -func (*HTTPIngressRuleValue) ProtoMessage() {} - -func (*IPAddress) ProtoMessage() {} - -func (*IPAddressList) ProtoMessage() {} - -func (*IPAddressSpec) ProtoMessage() {} - -func (*IPBlock) ProtoMessage() {} - -func (*Ingress) ProtoMessage() {} - -func (*IngressBackend) ProtoMessage() {} - -func (*IngressClass) ProtoMessage() {} - -func (*IngressClassList) ProtoMessage() {} - -func (*IngressClassParametersReference) ProtoMessage() {} - -func (*IngressClassSpec) ProtoMessage() {} - -func (*IngressList) ProtoMessage() {} - -func (*IngressLoadBalancerIngress) ProtoMessage() {} - -func (*IngressLoadBalancerStatus) ProtoMessage() {} - -func (*IngressPortStatus) ProtoMessage() {} - -func (*IngressRule) ProtoMessage() {} - -func (*IngressRuleValue) ProtoMessage() {} - -func (*IngressServiceBackend) ProtoMessage() {} - -func (*IngressSpec) ProtoMessage() {} - -func (*IngressStatus) ProtoMessage() {} - -func (*IngressTLS) ProtoMessage() {} - -func (*NetworkPolicy) ProtoMessage() {} - -func (*NetworkPolicyEgressRule) ProtoMessage() {} - -func (*NetworkPolicyIngressRule) ProtoMessage() {} - -func (*NetworkPolicyList) ProtoMessage() {} - -func (*NetworkPolicyPeer) ProtoMessage() {} - -func (*NetworkPolicyPort) ProtoMessage() {} - -func (*NetworkPolicySpec) ProtoMessage() {} - -func (*ParentReference) ProtoMessage() {} - -func (*ServiceBackendPort) ProtoMessage() {} - -func (*ServiceCIDR) ProtoMessage() {} - -func (*ServiceCIDRList) ProtoMessage() {} - -func (*ServiceCIDRSpec) ProtoMessage() {} - -func (*ServiceCIDRStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/networking/v1/types.go b/staging/src/k8s.io/api/networking/v1/types.go index 7d9a4fc94cb0d..a0e737ee15ba2 100644 --- a/staging/src/k8s.io/api/networking/v1/types.go +++ b/staging/src/k8s.io/api/networking/v1/types.go @@ -77,6 +77,7 @@ type NetworkPolicySpec struct { // solely to ensure that the pods it selects are isolated by default) // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Ingress []NetworkPolicyIngressRule `json:"ingress,omitempty" protobuf:"bytes,2,rep,name=ingress"` // egress is a list of egress rules to be applied to the selected pods. Outgoing traffic @@ -88,6 +89,7 @@ type NetworkPolicySpec struct { // This field is beta-level in 1.8 // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Egress []NetworkPolicyEgressRule `json:"egress,omitempty" protobuf:"bytes,3,rep,name=egress"` // policyTypes is a list of rule types that the NetworkPolicy relates to. @@ -124,6 +126,7 @@ type NetworkPolicyIngressRule struct { // allows traffic only if the traffic matches at least one item in the from list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional From []NetworkPolicyPeer `json:"from,omitempty" protobuf:"bytes,2,rep,name=from"` } @@ -147,6 +150,7 @@ type NetworkPolicyEgressRule struct { // allows traffic only if the traffic matches at least one item in the to list. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional To []NetworkPolicyPeer `json:"to,omitempty" protobuf:"bytes,2,rep,name=to"` } @@ -178,6 +182,8 @@ type NetworkPolicyPort struct { type IPBlock struct { // cidr is a string representing the IPBlock // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + // +required + // +k8s:alpha(since: "1.36")=+k8s:required CIDR string `json:"cidr" protobuf:"bytes,1,name=cidr"` // except is a slice of CIDRs that should not be included within an IPBlock @@ -212,6 +218,7 @@ type NetworkPolicyPeer struct { // ipBlock defines policy on a particular IPBlock. If this field is set then // neither of the other fields can be. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional IPBlock *IPBlock `json:"ipBlock,omitempty" protobuf:"bytes,3,rep,name=ipBlock"` } @@ -583,6 +590,7 @@ type IngressClassSpec struct { // configuration for the controller. This is optional if the controller does // not require extra parameters. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Parameters *IngressClassParametersReference `json:"parameters,omitempty" protobuf:"bytes,2,opt,name=parameters"` } @@ -605,9 +613,13 @@ type IngressClassParametersReference struct { APIGroup *string `json:"apiGroup,omitempty" protobuf:"bytes,1,opt,name=aPIGroup"` // kind is the type of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` // name is the name of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` // scope represents if this refers to a cluster or namespace scoped resource. @@ -657,7 +669,7 @@ type IPAddress struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // spec is the desired state of the IPAddress. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required Spec IPAddressSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -666,6 +678,8 @@ type IPAddressSpec struct { // ParentRef references the resource that an IPAddress is attached to. // An IPAddress must reference a parent object. // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable ParentRef *ParentReference `json:"parentRef,omitempty" protobuf:"bytes,1,opt,name=parentRef"` } @@ -676,12 +690,14 @@ type ParentReference struct { Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"` // Resource is the resource of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required Resource string `json:"resource,omitempty" protobuf:"bytes,2,opt,name=resource"` // Namespace is the namespace of the object being referenced. // +optional Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` // Name is the name of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name,omitempty" protobuf:"bytes,4,opt,name=name"` } diff --git a/staging/src/k8s.io/api/networking/v1beta1/generated.proto b/staging/src/k8s.io/api/networking/v1beta1/generated.proto index 3368dcaec3f58..a6944d6034624 100644 --- a/staging/src/k8s.io/api/networking/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/networking/v1beta1/generated.proto @@ -88,7 +88,7 @@ message IPAddress { // spec is the desired state of the IPAddress. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required optional IPAddressSpec spec = 2; } @@ -108,6 +108,8 @@ message IPAddressSpec { // ParentRef references the resource that an IPAddress is attached to. // An IPAddress must reference a parent object. // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable optional ParentReference parentRef = 1; } @@ -186,9 +188,13 @@ message IngressClassParametersReference { optional string aPIGroup = 1; // kind is the type of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string kind = 2; // name is the name of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; // scope represents if this refers to a cluster or namespace scoped resource. @@ -216,6 +222,7 @@ message IngressClassSpec { // configuration for the controller. This is optional if the controller does // not require extra parameters. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional IngressClassParametersReference parameters = 2; } @@ -397,6 +404,7 @@ message ParentReference { // Resource is the resource of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string resource = 2; // Namespace is the namespace of the object being referenced. @@ -405,6 +413,7 @@ message ParentReference { // Name is the name of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 4; } diff --git a/staging/src/k8s.io/api/networking/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/networking/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index abd29cba981b3..0000000000000 --- a/staging/src/k8s.io/api/networking/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,72 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*HTTPIngressPath) ProtoMessage() {} - -func (*HTTPIngressRuleValue) ProtoMessage() {} - -func (*IPAddress) ProtoMessage() {} - -func (*IPAddressList) ProtoMessage() {} - -func (*IPAddressSpec) ProtoMessage() {} - -func (*Ingress) ProtoMessage() {} - -func (*IngressBackend) ProtoMessage() {} - -func (*IngressClass) ProtoMessage() {} - -func (*IngressClassList) ProtoMessage() {} - -func (*IngressClassParametersReference) ProtoMessage() {} - -func (*IngressClassSpec) ProtoMessage() {} - -func (*IngressList) ProtoMessage() {} - -func (*IngressLoadBalancerIngress) ProtoMessage() {} - -func (*IngressLoadBalancerStatus) ProtoMessage() {} - -func (*IngressPortStatus) ProtoMessage() {} - -func (*IngressRule) ProtoMessage() {} - -func (*IngressRuleValue) ProtoMessage() {} - -func (*IngressSpec) ProtoMessage() {} - -func (*IngressStatus) ProtoMessage() {} - -func (*IngressTLS) ProtoMessage() {} - -func (*ParentReference) ProtoMessage() {} - -func (*ServiceCIDR) ProtoMessage() {} - -func (*ServiceCIDRList) ProtoMessage() {} - -func (*ServiceCIDRSpec) ProtoMessage() {} - -func (*ServiceCIDRStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/networking/v1beta1/types.go b/staging/src/k8s.io/api/networking/v1beta1/types.go index cd7126a5a8e8a..7cd9b2b58b61a 100644 --- a/staging/src/k8s.io/api/networking/v1beta1/types.go +++ b/staging/src/k8s.io/api/networking/v1beta1/types.go @@ -368,6 +368,7 @@ type IngressClassSpec struct { // configuration for the controller. This is optional if the controller does // not require extra parameters. // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Parameters *IngressClassParametersReference `json:"parameters,omitempty" protobuf:"bytes,2,opt,name=parameters"` } @@ -390,9 +391,13 @@ type IngressClassParametersReference struct { APIGroup *string `json:"apiGroup,omitempty" protobuf:"bytes,1,opt,name=aPIGroup"` // kind is the type of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` // name is the name of resource being referenced. + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` // scope represents if this refers to a cluster or namespace scoped resource. @@ -442,7 +447,7 @@ type IPAddress struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // spec is the desired state of the IPAddress. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status - // +optional + // +required Spec IPAddressSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } @@ -451,6 +456,8 @@ type IPAddressSpec struct { // ParentRef references the resource that an IPAddress is attached to. // An IPAddress must reference a parent object. // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable ParentRef *ParentReference `json:"parentRef,omitempty" protobuf:"bytes,1,opt,name=parentRef"` } @@ -461,12 +468,14 @@ type ParentReference struct { Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"` // Resource is the resource of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required Resource string `json:"resource,omitempty" protobuf:"bytes,2,opt,name=resource"` // Namespace is the namespace of the object being referenced. // +optional Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` // Name is the name of the object being referenced. // +required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name,omitempty" protobuf:"bytes,4,opt,name=name"` } diff --git a/staging/src/k8s.io/api/node/v1/generated.proto b/staging/src/k8s.io/api/node/v1/generated.proto index e6b8852ec1047..4d77c9b7810db 100644 --- a/staging/src/k8s.io/api/node/v1/generated.proto +++ b/staging/src/k8s.io/api/node/v1/generated.proto @@ -59,6 +59,10 @@ message RuntimeClass { // in a pod. // The Handler must be lowercase, conform to the DNS Label (RFC 1123) requirements, // and is immutable. + // +required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-short-name" + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:required optional string handler = 2; // overhead represents the resource overhead associated with running a pod for a diff --git a/staging/src/k8s.io/api/node/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/node/v1/generated.protomessage.pb.go deleted file mode 100644 index 97072dfa1eb5e..0000000000000 --- a/staging/src/k8s.io/api/node/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Overhead) ProtoMessage() {} - -func (*RuntimeClass) ProtoMessage() {} - -func (*RuntimeClassList) ProtoMessage() {} - -func (*Scheduling) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/node/v1/types.go b/staging/src/k8s.io/api/node/v1/types.go index 169862ea9408f..c2ea739dc8b31 100644 --- a/staging/src/k8s.io/api/node/v1/types.go +++ b/staging/src/k8s.io/api/node/v1/types.go @@ -50,6 +50,10 @@ type RuntimeClass struct { // in a pod. // The Handler must be lowercase, conform to the DNS Label (RFC 1123) requirements, // and is immutable. + // +required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-short-name" + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:required Handler string `json:"handler" protobuf:"bytes,2,opt,name=handler"` // overhead represents the resource overhead associated with running a pod for a diff --git a/staging/src/k8s.io/api/node/v1alpha1/generated.proto b/staging/src/k8s.io/api/node/v1alpha1/generated.proto index bc68718d90c59..c50b639065ed9 100644 --- a/staging/src/k8s.io/api/node/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/node/v1alpha1/generated.proto @@ -51,6 +51,7 @@ message RuntimeClass { // spec represents specification of the RuntimeClass // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +required optional RuntimeClassSpec spec = 2; } @@ -80,6 +81,10 @@ message RuntimeClassSpec { // in a pod. // The runtimeHandler must be lowercase, conform to the DNS Label (RFC 1123) // requirements, and is immutable. + // +required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-short-name" + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:required optional string runtimeHandler = 1; // overhead represents the resource overhead associated with running a pod for a diff --git a/staging/src/k8s.io/api/node/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/node/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index acd3b604ee370..0000000000000 --- a/staging/src/k8s.io/api/node/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*Overhead) ProtoMessage() {} - -func (*RuntimeClass) ProtoMessage() {} - -func (*RuntimeClassList) ProtoMessage() {} - -func (*RuntimeClassSpec) ProtoMessage() {} - -func (*Scheduling) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/node/v1alpha1/types.go b/staging/src/k8s.io/api/node/v1alpha1/types.go index bf9e284bf7320..3521335b07d9a 100644 --- a/staging/src/k8s.io/api/node/v1alpha1/types.go +++ b/staging/src/k8s.io/api/node/v1alpha1/types.go @@ -41,6 +41,7 @@ type RuntimeClass struct { // spec represents specification of the RuntimeClass // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +required Spec RuntimeClassSpec `json:"spec" protobuf:"bytes,2,name=spec"` } @@ -59,6 +60,10 @@ type RuntimeClassSpec struct { // in a pod. // The runtimeHandler must be lowercase, conform to the DNS Label (RFC 1123) // requirements, and is immutable. + // +required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-short-name" + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:required RuntimeHandler string `json:"runtimeHandler" protobuf:"bytes,1,opt,name=runtimeHandler"` // overhead represents the resource overhead associated with running a pod for a diff --git a/staging/src/k8s.io/api/node/v1beta1/generated.proto b/staging/src/k8s.io/api/node/v1beta1/generated.proto index 497027e033af6..073a185e2c4d9 100644 --- a/staging/src/k8s.io/api/node/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/node/v1beta1/generated.proto @@ -59,6 +59,10 @@ message RuntimeClass { // in a pod. // The handler must be lowercase, conform to the DNS Label (RFC 1123) requirements, // and is immutable. + // +required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-short-name" + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:required optional string handler = 2; // overhead represents the resource overhead associated with running a pod for a diff --git a/staging/src/k8s.io/api/node/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/node/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index dfe8e29d95492..0000000000000 --- a/staging/src/k8s.io/api/node/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*Overhead) ProtoMessage() {} - -func (*RuntimeClass) ProtoMessage() {} - -func (*RuntimeClassList) ProtoMessage() {} - -func (*Scheduling) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/node/v1beta1/types.go b/staging/src/k8s.io/api/node/v1beta1/types.go index 74ecca26ad4ce..145e1386634d9 100644 --- a/staging/src/k8s.io/api/node/v1beta1/types.go +++ b/staging/src/k8s.io/api/node/v1beta1/types.go @@ -51,6 +51,10 @@ type RuntimeClass struct { // in a pod. // The handler must be lowercase, conform to the DNS Label (RFC 1123) requirements, // and is immutable. + // +required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-short-name" + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:required Handler string `json:"handler" protobuf:"bytes,2,opt,name=handler"` // overhead represents the resource overhead associated with running a pod for a diff --git a/staging/src/k8s.io/api/policy/v1/generated.proto b/staging/src/k8s.io/api/policy/v1/generated.proto index 95348907234fb..704cc10b7dbeb 100644 --- a/staging/src/k8s.io/api/policy/v1/generated.proto +++ b/staging/src/k8s.io/api/policy/v1/generated.proto @@ -142,15 +142,19 @@ message PodDisruptionBudgetStatus { map disruptedPods = 2; // Number of pod disruptions that are currently allowed. + // +optional optional int32 disruptionsAllowed = 3; // current number of healthy pods + // +optional optional int32 currentHealthy = 4; // minimum desired number of healthy pods + // +optional optional int32 desiredHealthy = 5; // total number of pods counted by this disruption budget + // +optional optional int32 expectedPods = 6; // Conditions contain conditions for PDB. The disruption controller sets the diff --git a/staging/src/k8s.io/api/policy/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/policy/v1/generated.protomessage.pb.go deleted file mode 100644 index 2ede21763990b..0000000000000 --- a/staging/src/k8s.io/api/policy/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Eviction) ProtoMessage() {} - -func (*PodDisruptionBudget) ProtoMessage() {} - -func (*PodDisruptionBudgetList) ProtoMessage() {} - -func (*PodDisruptionBudgetSpec) ProtoMessage() {} - -func (*PodDisruptionBudgetStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/policy/v1/types.go b/staging/src/k8s.io/api/policy/v1/types.go index 4e74367894a02..ebf349e317704 100644 --- a/staging/src/k8s.io/api/policy/v1/types.go +++ b/staging/src/k8s.io/api/policy/v1/types.go @@ -117,15 +117,19 @@ type PodDisruptionBudgetStatus struct { DisruptedPods map[string]metav1.Time `json:"disruptedPods,omitempty" protobuf:"bytes,2,rep,name=disruptedPods"` // Number of pod disruptions that are currently allowed. + // +optional DisruptionsAllowed int32 `json:"disruptionsAllowed" protobuf:"varint,3,opt,name=disruptionsAllowed"` // current number of healthy pods + // +optional CurrentHealthy int32 `json:"currentHealthy" protobuf:"varint,4,opt,name=currentHealthy"` // minimum desired number of healthy pods + // +optional DesiredHealthy int32 `json:"desiredHealthy" protobuf:"varint,5,opt,name=desiredHealthy"` // total number of pods counted by this disruption budget + // +optional ExpectedPods int32 `json:"expectedPods" protobuf:"varint,6,opt,name=expectedPods"` // Conditions contain conditions for PDB. The disruption controller sets the diff --git a/staging/src/k8s.io/api/policy/v1beta1/generated.proto b/staging/src/k8s.io/api/policy/v1beta1/generated.proto index e0cbe00f1ca6d..a8200a2d78d5c 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/policy/v1beta1/generated.proto @@ -142,15 +142,19 @@ message PodDisruptionBudgetStatus { map disruptedPods = 2; // Number of pod disruptions that are currently allowed. + // +optional optional int32 disruptionsAllowed = 3; // current number of healthy pods + // +optional optional int32 currentHealthy = 4; // minimum desired number of healthy pods + // +optional optional int32 desiredHealthy = 5; // total number of pods counted by this disruption budget + // +optional optional int32 expectedPods = 6; // Conditions contain conditions for PDB. The disruption controller sets the diff --git a/staging/src/k8s.io/api/policy/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/policy/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 01e0847fceab2..0000000000000 --- a/staging/src/k8s.io/api/policy/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*Eviction) ProtoMessage() {} - -func (*PodDisruptionBudget) ProtoMessage() {} - -func (*PodDisruptionBudgetList) ProtoMessage() {} - -func (*PodDisruptionBudgetSpec) ProtoMessage() {} - -func (*PodDisruptionBudgetStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/policy/v1beta1/types.go b/staging/src/k8s.io/api/policy/v1beta1/types.go index 9bba454f9490c..354c22a3d09ae 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/types.go +++ b/staging/src/k8s.io/api/policy/v1beta1/types.go @@ -114,15 +114,19 @@ type PodDisruptionBudgetStatus struct { DisruptedPods map[string]metav1.Time `json:"disruptedPods,omitempty" protobuf:"bytes,2,rep,name=disruptedPods"` // Number of pod disruptions that are currently allowed. + // +optional DisruptionsAllowed int32 `json:"disruptionsAllowed" protobuf:"varint,3,opt,name=disruptionsAllowed"` // current number of healthy pods + // +optional CurrentHealthy int32 `json:"currentHealthy" protobuf:"varint,4,opt,name=currentHealthy"` // minimum desired number of healthy pods + // +optional DesiredHealthy int32 `json:"desiredHealthy" protobuf:"varint,5,opt,name=desiredHealthy"` // total number of pods counted by this disruption budget + // +optional ExpectedPods int32 `json:"expectedPods" protobuf:"varint,6,opt,name=expectedPods"` // Conditions contain conditions for PDB. The disruption controller sets the diff --git a/staging/src/k8s.io/api/rbac/v1/generated.proto b/staging/src/k8s.io/api/rbac/v1/generated.proto index 56869814ec487..7b4ec2afc1652 100644 --- a/staging/src/k8s.io/api/rbac/v1/generated.proto +++ b/staging/src/k8s.io/api/rbac/v1/generated.proto @@ -46,6 +46,7 @@ message ClusterRole { // Rules holds all the PolicyRules for this ClusterRole // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated PolicyRule rules = 2; // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. @@ -65,11 +66,13 @@ message ClusterRoleBinding { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Subject subjects = 2; // RoleRef can only reference a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. // This field is immutable. + // +required optional RoleRef roleRef = 3; } @@ -98,6 +101,8 @@ message ClusterRoleList { message PolicyRule { // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. // +listType=atomic + // +required + // +k8s:alpha(since: "1.36")=+k8s:required repeated string verbs = 1; // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of @@ -133,6 +138,7 @@ message Role { // Rules holds all the PolicyRules for this Role // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated PolicyRule rules = 2; } @@ -147,11 +153,13 @@ message RoleBinding { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Subject subjects = 2; // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. // This field is immutable. + // +required optional RoleRef roleRef = 3; } @@ -179,14 +187,16 @@ message RoleList { // +structType=atomic message RoleRef { // APIGroup is the group for the resource being referenced + // +optional optional string apiGroup = 1; // Kind is the type of resource being referenced + // +required optional string kind = 2; // Name is the name of resource being referenced // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; } @@ -196,6 +206,7 @@ message RoleRef { message Subject { // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + // +required optional string kind = 1; // APIGroup holds the API group of the referenced subject. @@ -206,7 +217,7 @@ message Subject { // Name of the object being referenced. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty diff --git a/staging/src/k8s.io/api/rbac/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/rbac/v1/generated.protomessage.pb.go deleted file mode 100644 index 6e29e012fa59a..0000000000000 --- a/staging/src/k8s.io/api/rbac/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*AggregationRule) ProtoMessage() {} - -func (*ClusterRole) ProtoMessage() {} - -func (*ClusterRoleBinding) ProtoMessage() {} - -func (*ClusterRoleBindingList) ProtoMessage() {} - -func (*ClusterRoleList) ProtoMessage() {} - -func (*PolicyRule) ProtoMessage() {} - -func (*Role) ProtoMessage() {} - -func (*RoleBinding) ProtoMessage() {} - -func (*RoleBindingList) ProtoMessage() {} - -func (*RoleList) ProtoMessage() {} - -func (*RoleRef) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/rbac/v1/types.go b/staging/src/k8s.io/api/rbac/v1/types.go index 2fde11b30640f..d3bb6d1a9cd0f 100644 --- a/staging/src/k8s.io/api/rbac/v1/types.go +++ b/staging/src/k8s.io/api/rbac/v1/types.go @@ -49,6 +49,8 @@ const ( type PolicyRule struct { // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. // +listType=atomic + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of @@ -79,6 +81,7 @@ type PolicyRule struct { type Subject struct { // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + // +required Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` // APIGroup holds the API group of the referenced subject. // Defaults to "" for ServiceAccount subjects. @@ -87,7 +90,7 @@ type Subject struct { APIGroup string `json:"apiGroup,omitempty" protobuf:"bytes,2,opt,name=apiGroup"` // Name of the object being referenced. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty // the Authorizer should report an error. @@ -99,12 +102,14 @@ type Subject struct { // +structType=atomic type RoleRef struct { // APIGroup is the group for the resource being referenced + // +optional APIGroup string `json:"apiGroup" protobuf:"bytes,1,opt,name=apiGroup"` // Kind is the type of resource being referenced + // +required Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` // Name is the name of resource being referenced // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` } @@ -122,6 +127,7 @@ type Role struct { // Rules holds all the PolicyRules for this Role // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` } @@ -141,11 +147,13 @@ type RoleBinding struct { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Subjects []Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"` // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. // This field is immutable. + // +required RoleRef RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"` } @@ -192,6 +200,7 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. @@ -226,11 +235,13 @@ type ClusterRoleBinding struct { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Subjects []Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"` // RoleRef can only reference a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. // This field is immutable. + // +required RoleRef RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"` } diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto b/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto index f787f3976e7c3..9551d1192163a 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto @@ -47,6 +47,7 @@ message ClusterRole { // Rules holds all the PolicyRules for this ClusterRole // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated PolicyRule rules = 2; // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. @@ -67,10 +68,12 @@ message ClusterRoleBinding { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Subject subjects = 2; // RoleRef can only reference a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required optional RoleRef roleRef = 3; } @@ -101,6 +104,8 @@ message ClusterRoleList { message PolicyRule { // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. // +listType=atomic + // +required + // +k8s:alpha(since: "1.36")=+k8s:required repeated string verbs = 1; // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of @@ -137,6 +142,7 @@ message Role { // Rules holds all the PolicyRules for this Role // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated PolicyRule rules = 2; } @@ -152,10 +158,12 @@ message RoleBinding { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Subject subjects = 2; // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required optional RoleRef roleRef = 3; } @@ -184,14 +192,16 @@ message RoleList { // RoleRef contains information that points to the role being used message RoleRef { // APIGroup is the group for the resource being referenced + // +optional optional string apiGroup = 1; // Kind is the type of resource being referenced + // +required optional string kind = 2; // Name is the name of resource being referenced // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; } @@ -200,6 +210,7 @@ message RoleRef { message Subject { // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + // +required optional string kind = 1; // APIVersion holds the API group and version of the referenced subject. @@ -211,7 +222,7 @@ message Subject { // Name of the object being referenced. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/rbac/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index 20f8d575ea1ea..0000000000000 --- a/staging/src/k8s.io/api/rbac/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*AggregationRule) ProtoMessage() {} - -func (*ClusterRole) ProtoMessage() {} - -func (*ClusterRoleBinding) ProtoMessage() {} - -func (*ClusterRoleBindingList) ProtoMessage() {} - -func (*ClusterRoleList) ProtoMessage() {} - -func (*PolicyRule) ProtoMessage() {} - -func (*Role) ProtoMessage() {} - -func (*RoleBinding) ProtoMessage() {} - -func (*RoleBindingList) ProtoMessage() {} - -func (*RoleList) ProtoMessage() {} - -func (*RoleRef) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/types.go b/staging/src/k8s.io/api/rbac/v1alpha1/types.go index a0d52ee4fc7d8..4ff4c060156c1 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/types.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/types.go @@ -49,6 +49,8 @@ const ( type PolicyRule struct { // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. // +listType=atomic + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of @@ -78,6 +80,7 @@ type PolicyRule struct { type Subject struct { // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + // +required Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` // APIVersion holds the API group and version of the referenced subject. // Defaults to "v1" for ServiceAccount subjects. @@ -87,7 +90,7 @@ type Subject struct { APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` // Name of the object being referenced. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty // the Authorizer should report an error. @@ -98,12 +101,14 @@ type Subject struct { // RoleRef contains information that points to the role being used type RoleRef struct { // APIGroup is the group for the resource being referenced + // +optional APIGroup string `json:"apiGroup" protobuf:"bytes,1,opt,name=apiGroup"` // Kind is the type of resource being referenced + // +required Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` // Name is the name of resource being referenced // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` } @@ -121,6 +126,7 @@ type Role struct { // Rules holds all the PolicyRules for this Role // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` } @@ -140,10 +146,12 @@ type RoleBinding struct { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Subjects []Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"` // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required RoleRef RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"` } @@ -190,6 +198,7 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. @@ -224,10 +233,12 @@ type ClusterRoleBinding struct { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Subjects []Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"` // RoleRef can only reference a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required RoleRef RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"` } diff --git a/staging/src/k8s.io/api/rbac/v1beta1/generated.proto b/staging/src/k8s.io/api/rbac/v1beta1/generated.proto index cac7b413e87da..326f312ec48ab 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/rbac/v1beta1/generated.proto @@ -47,6 +47,7 @@ message ClusterRole { // Rules holds all the PolicyRules for this ClusterRole // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated PolicyRule rules = 2; // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. @@ -67,10 +68,12 @@ message ClusterRoleBinding { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Subject subjects = 2; // RoleRef can only reference a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required optional RoleRef roleRef = 3; } @@ -101,6 +104,8 @@ message ClusterRoleList { message PolicyRule { // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. // +listType=atomic + // +required + // +k8s:alpha(since: "1.36")=+k8s:required repeated string verbs = 1; // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of @@ -138,6 +143,7 @@ message Role { // Rules holds all the PolicyRules for this Role // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated PolicyRule rules = 2; } @@ -153,10 +159,12 @@ message RoleBinding { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional repeated Subject subjects = 2; // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required optional RoleRef roleRef = 3; } @@ -185,14 +193,16 @@ message RoleList { // RoleRef contains information that points to the role being used message RoleRef { // APIGroup is the group for the resource being referenced + // +optional optional string apiGroup = 1; // Kind is the type of resource being referenced + // +required optional string kind = 2; // Name is the name of resource being referenced // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; } @@ -201,6 +211,7 @@ message RoleRef { message Subject { // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + // +required optional string kind = 1; // APIGroup holds the API group of the referenced subject. @@ -211,7 +222,7 @@ message Subject { // Name of the object being referenced. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string name = 3; // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty diff --git a/staging/src/k8s.io/api/rbac/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/rbac/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index b29896836740c..0000000000000 --- a/staging/src/k8s.io/api/rbac/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*AggregationRule) ProtoMessage() {} - -func (*ClusterRole) ProtoMessage() {} - -func (*ClusterRoleBinding) ProtoMessage() {} - -func (*ClusterRoleBindingList) ProtoMessage() {} - -func (*ClusterRoleList) ProtoMessage() {} - -func (*PolicyRule) ProtoMessage() {} - -func (*Role) ProtoMessage() {} - -func (*RoleBinding) ProtoMessage() {} - -func (*RoleBindingList) ProtoMessage() {} - -func (*RoleList) ProtoMessage() {} - -func (*RoleRef) ProtoMessage() {} - -func (*Subject) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/rbac/v1beta1/types.go b/staging/src/k8s.io/api/rbac/v1beta1/types.go index 861e33c975586..c653d071fe43c 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/types.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/types.go @@ -49,6 +49,8 @@ const ( type PolicyRule struct { // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. // +listType=atomic + // +required + // +k8s:alpha(since: "1.36")=+k8s:required Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of @@ -79,6 +81,7 @@ type PolicyRule struct { type Subject struct { // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + // +required Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` // APIGroup holds the API group of the referenced subject. // Defaults to "" for ServiceAccount subjects. @@ -87,7 +90,7 @@ type Subject struct { APIGroup string `json:"apiGroup,omitempty" protobuf:"bytes,2,opt,name=apiGroup"` // Name of the object being referenced. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty // the Authorizer should report an error. @@ -98,12 +101,14 @@ type Subject struct { // RoleRef contains information that points to the role being used type RoleRef struct { // APIGroup is the group for the resource being referenced + // +optional APIGroup string `json:"apiGroup" protobuf:"bytes,1,opt,name=apiGroup"` // Kind is the type of resource being referenced + // +required Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` // Name is the name of resource being referenced // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Name string `json:"name" protobuf:"bytes,3,opt,name=name"` } @@ -125,6 +130,7 @@ type Role struct { // Rules holds all the PolicyRules for this Role // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` } @@ -148,10 +154,12 @@ type RoleBinding struct { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Subjects []Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"` // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required RoleRef RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"` } @@ -210,6 +218,7 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be @@ -247,10 +256,12 @@ type ClusterRoleBinding struct { // Subjects holds references to the objects the role applies to. // +optional // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional Subjects []Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"` // RoleRef can only reference a ClusterRole in the global namespace. // If the RoleRef cannot be resolved, the Authorizer must return an error. + // +required RoleRef RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"` } diff --git a/staging/src/k8s.io/api/resource/v1/generated.pb.go b/staging/src/k8s.io/api/resource/v1/generated.pb.go index e690204905ae8..f59e829057afc 100644 --- a/staging/src/k8s.io/api/resource/v1/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1/generated.pb.go @@ -25,6 +25,7 @@ import ( io "io" "sort" + k8s_io_api_core_v1 "k8s.io/api/core/v1" v11 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -97,6 +98,8 @@ func (m *ExactDeviceRequest) Reset() { *m = ExactDeviceRequest{} } func (m *NetworkDeviceData) Reset() { *m = NetworkDeviceData{} } +func (m *NodeAllocatableResourceMapping) Reset() { *m = NodeAllocatableResourceMapping{} } + func (m *OpaqueDeviceConfiguration) Reset() { *m = OpaqueDeviceConfiguration{} } func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } @@ -573,6 +576,35 @@ func (m *Device) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.NodeAllocatableResourceMappings) > 0 { + keysForNodeAllocatableResourceMappings := make([]string, 0, len(m.NodeAllocatableResourceMappings)) + for k := range m.NodeAllocatableResourceMappings { + keysForNodeAllocatableResourceMappings = append(keysForNodeAllocatableResourceMappings, string(k)) + } + sort.Strings(keysForNodeAllocatableResourceMappings) + for iNdEx := len(keysForNodeAllocatableResourceMappings) - 1; iNdEx >= 0; iNdEx-- { + v := m.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(keysForNodeAllocatableResourceMappings[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForNodeAllocatableResourceMappings[iNdEx]) + copy(dAtA[i:], keysForNodeAllocatableResourceMappings[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForNodeAllocatableResourceMappings[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x6a + } + } if m.AllowMultipleAllocations != nil { i-- if *m.AllowMultipleAllocations { @@ -852,6 +884,43 @@ func (m *DeviceAttribute) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.VersionValues) > 0 { + for iNdEx := len(m.VersionValues) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.VersionValues[iNdEx]) + copy(dAtA[i:], m.VersionValues[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.VersionValues[iNdEx]))) + i-- + dAtA[i] = 0x4a + } + } + if len(m.StringValues) > 0 { + for iNdEx := len(m.StringValues) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.StringValues[iNdEx]) + copy(dAtA[i:], m.StringValues[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StringValues[iNdEx]))) + i-- + dAtA[i] = 0x42 + } + } + if len(m.BoolValues) > 0 { + for iNdEx := len(m.BoolValues) - 1; iNdEx >= 0; iNdEx-- { + i-- + if m.BoolValues[iNdEx] { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + } + if len(m.IntValues) > 0 { + for iNdEx := len(m.IntValues) - 1; iNdEx >= 0; iNdEx-- { + i = encodeVarintGenerated(dAtA, i, uint64(m.IntValues[iNdEx])) + i-- + dAtA[i] = 0x30 + } + } if m.VersionValue != nil { i -= len(*m.VersionValue) copy(dAtA[i:], *m.VersionValue) @@ -1872,6 +1941,48 @@ func (m *NetworkDeviceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *NodeAllocatableResourceMapping) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NodeAllocatableResourceMapping) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *NodeAllocatableResourceMapping) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AllocationMultiplier != nil { + { + size, err := m.AllocationMultiplier.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.CapacityKey != nil { + i -= len(*m.CapacityKey) + copy(dAtA[i:], *m.CapacityKey) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.CapacityKey))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2745,6 +2856,15 @@ func (m *Device) Size() (n int) { if m.AllowMultipleAllocations != nil { n += 2 } + if len(m.NodeAllocatableResourceMappings) > 0 { + for k, v := range m.NodeAllocatableResourceMappings { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } return n } @@ -2808,6 +2928,26 @@ func (m *DeviceAttribute) Size() (n int) { l = len(*m.VersionValue) n += 1 + l + sovGenerated(uint64(l)) } + if len(m.IntValues) > 0 { + for _, e := range m.IntValues { + n += 1 + sovGenerated(uint64(e)) + } + } + if len(m.BoolValues) > 0 { + n += 2 * len(m.BoolValues) + } + if len(m.StringValues) > 0 { + for _, s := range m.StringValues { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.VersionValues) > 0 { + for _, s := range m.VersionValues { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3200,6 +3340,23 @@ func (m *NetworkDeviceData) Size() (n int) { return n } +func (m *NodeAllocatableResourceMapping) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CapacityKey != nil { + l = len(*m.CapacityKey) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.AllocationMultiplier != nil { + l = m.AllocationMultiplier.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *OpaqueDeviceConfiguration) Size() (n int) { if m == nil { return 0 @@ -3586,6 +3743,16 @@ func (this *Device) String() string { mapStringForCapacity += fmt.Sprintf("%v: %v,", k, this.Capacity[QualifiedName(k)]) } mapStringForCapacity += "}" + keysForNodeAllocatableResourceMappings := make([]string, 0, len(this.NodeAllocatableResourceMappings)) + for k := range this.NodeAllocatableResourceMappings { + keysForNodeAllocatableResourceMappings = append(keysForNodeAllocatableResourceMappings, string(k)) + } + sort.Strings(keysForNodeAllocatableResourceMappings) + mapStringForNodeAllocatableResourceMappings := "map[k8s_io_api_core_v1.ResourceName]NodeAllocatableResourceMapping{" + for _, k := range keysForNodeAllocatableResourceMappings { + mapStringForNodeAllocatableResourceMappings += fmt.Sprintf("%v: %v,", k, this.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(k)]) + } + mapStringForNodeAllocatableResourceMappings += "}" s := strings.Join([]string{`&Device{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Attributes:` + mapStringForAttributes + `,`, @@ -3599,6 +3766,7 @@ func (this *Device) String() string { `BindingConditions:` + fmt.Sprintf("%v", this.BindingConditions) + `,`, `BindingFailureConditions:` + fmt.Sprintf("%v", this.BindingFailureConditions) + `,`, `AllowMultipleAllocations:` + valueToStringGenerated(this.AllowMultipleAllocations) + `,`, + `NodeAllocatableResourceMappings:` + mapStringForNodeAllocatableResourceMappings + `,`, `}`, }, "") return s @@ -3645,6 +3813,10 @@ func (this *DeviceAttribute) String() string { `BoolValue:` + valueToStringGenerated(this.BoolValue) + `,`, `StringValue:` + valueToStringGenerated(this.StringValue) + `,`, `VersionValue:` + valueToStringGenerated(this.VersionValue) + `,`, + `IntValues:` + fmt.Sprintf("%v", this.IntValues) + `,`, + `BoolValues:` + fmt.Sprintf("%v", this.BoolValues) + `,`, + `StringValues:` + fmt.Sprintf("%v", this.StringValues) + `,`, + `VersionValues:` + fmt.Sprintf("%v", this.VersionValues) + `,`, `}`, }, "") return s @@ -3939,6 +4111,17 @@ func (this *NetworkDeviceData) String() string { }, "") return s } +func (this *NodeAllocatableResourceMapping) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&NodeAllocatableResourceMapping{`, + `CapacityKey:` + valueToStringGenerated(this.CapacityKey) + `,`, + `AllocationMultiplier:` + strings.Replace(fmt.Sprintf("%v", this.AllocationMultiplier), "Quantity", "resource.Quantity", 1) + `,`, + `}`, + }, "") + return s +} func (this *OpaqueDeviceConfiguration) String() string { if this == nil { return "nil" @@ -6027,6 +6210,135 @@ func (m *Device) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AllowMultipleAllocations = &b + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeAllocatableResourceMappings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NodeAllocatableResourceMappings == nil { + m.NodeAllocatableResourceMappings = make(map[k8s_io_api_core_v1.ResourceName]NodeAllocatableResourceMapping) + } + var mapkey k8s_io_api_core_v1.ResourceName + mapvalue := &NodeAllocatableResourceMapping{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &NodeAllocatableResourceMapping{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(mapkey)] = *mapvalue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6449,6 +6761,216 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.VersionValue = &s iNdEx = postIndex + case 6: + if wireType == 0 { + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IntValues = append(m.IntValues, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.IntValues) == 0 { + m.IntValues = make([]int64, 0, elementCount) + } + for iNdEx < postIndex { + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IntValues = append(m.IntValues, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field IntValues", wireType) + } + case 7: + if wireType == 0 { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BoolValues = append(m.BoolValues, bool(v != 0)) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen + if elementCount != 0 && len(m.BoolValues) == 0 { + m.BoolValues = make([]bool, 0, elementCount) + } + for iNdEx < postIndex { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BoolValues = append(m.BoolValues, bool(v != 0)) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field BoolValues", wireType) + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StringValues", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StringValues = append(m.StringValues, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionValues", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VersionValues = append(m.VersionValues, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -9518,6 +10040,125 @@ func (m *NetworkDeviceData) Unmarshal(dAtA []byte) error { } return nil } +func (m *NodeAllocatableResourceMapping) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeAllocatableResourceMapping: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeAllocatableResourceMapping: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CapacityKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := QualifiedName(dAtA[iNdEx:postIndex]) + m.CapacityKey = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMultiplier", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AllocationMultiplier == nil { + m.AllocationMultiplier = &resource.Quantity{} + } + if err := m.AllocationMultiplier.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *OpaqueDeviceConfiguration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/resource/v1/generated.proto b/staging/src/k8s.io/api/resource/v1/generated.proto index c254137c43bd7..8bacfd846f737 100644 --- a/staging/src/k8s.io/api/resource/v1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1/generated.proto @@ -65,8 +65,8 @@ message AllocatedDeviceStatus { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid optional string shareID = 7; // Conditions contains the latest observation of the device's state. @@ -90,7 +90,7 @@ message AllocatedDeviceStatus { // NetworkData contains network-related information specific to the device. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional NetworkDeviceData networkData = 6; } @@ -110,7 +110,7 @@ message AllocationResult { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -169,6 +169,14 @@ message CELDeviceSelector { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -307,8 +315,8 @@ message CounterSet { // It must be a DNS label. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name optional string name = 1; // Counters defines the set of counters for this CounterSet @@ -317,6 +325,8 @@ message CounterSet { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name map counters = 2; } @@ -335,6 +345,7 @@ message Device { // The maximum number of attributes and capacities combined is 32. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional map attributes = 2; // Capacity defines the set of capacities for this device. @@ -355,13 +366,13 @@ message Device { // device is 2. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=counterSet + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=counterSet // +featureGate=DRAPartitionableDevices - // +k8s:maxItems=2 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=2 repeated DeviceCounterConsumption consumesCounters = 4; // NodeName identifies the node where the device is available. @@ -402,12 +413,13 @@ message Device { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceTaint taints = 8; // BindsToNode indicates if the usage of an allocation involving this device @@ -415,7 +427,7 @@ message Device { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -431,14 +443,14 @@ message Device { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingConditions = 10; // BindingFailureConditions defines the conditions for binding failure. @@ -449,14 +461,14 @@ message Device { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingFailureConditions = 11; // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -467,6 +479,21 @@ message Device { // +optional // +featureGate=DRAConsumableCapacity optional bool allowMultipleAllocations = 12; + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + map nodeAllocatableResourceMappings = 13; } // DeviceAllocationConfiguration gets embedded in an AllocationResult. @@ -476,7 +503,7 @@ message DeviceAllocationConfiguration { // or from a claim. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string source = 1; // Requests lists the names of requests where the configuration applies. @@ -488,10 +515,10 @@ message DeviceAllocationConfiguration { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 2; optional DeviceConfiguration deviceConfiguration = 3; @@ -503,8 +530,8 @@ message DeviceAllocationResult { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceRequestAllocationResult results = 1; // This field is a combination of all the claim and class configuration parameters. @@ -517,8 +544,8 @@ message DeviceAllocationResult { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=64 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=64 repeated DeviceAllocationConfiguration config = 2; } @@ -527,31 +554,79 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional string version = 5; + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated int64 ints = 6; + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated bool bools = 7; + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated string strings = 8; + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated string versions = 9; } // DeviceCapacity describes a quantity associated with a device. @@ -587,11 +662,11 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceRequest requests = 1; // These constraints must be satisfied by the set of devices that get @@ -599,8 +674,8 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceConstraint constraints = 2; // This field holds configuration for multiple potential drivers which @@ -609,8 +684,8 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceClaimConfiguration config = 3; } @@ -625,10 +700,10 @@ message DeviceClaimConfiguration { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 1; optional DeviceConfiguration deviceConfiguration = 2; @@ -638,14 +713,11 @@ message DeviceClaimConfiguration { // device configuration and selectors. It can be referenced in // the device requests of a claim to apply these presets. // Cluster scoped. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. message DeviceClass { // Standard object metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // Spec defines what can be allocated and how to configure it. @@ -681,8 +753,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 1; // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -693,8 +765,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceClassConfiguration config = 2; // ExtendedResourceName is the extended resource name for the devices of this class. @@ -706,11 +778,11 @@ message DeviceClassSpec { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. // +optional // +featureGate=DRAExtendedResource - // +k8s:optional - // +k8s:format=k8s-extended-resource-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-extended-resource-name optional string extendedResourceName = 4; } @@ -722,7 +794,7 @@ message DeviceConfiguration { // // +optional // +oneOf=ConfigurationType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional OpaqueDeviceConfiguration opaque = 1; } @@ -740,10 +812,10 @@ message DeviceConstraint { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 1; // MatchAttribute requires that all devices in question have this @@ -757,17 +829,27 @@ message DeviceConstraint { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as single-element lists for backward compatibility. + // // Must include the domain qualifier. // // +optional // +oneOf=ConstraintType - // +k8s:optional - // +k8s:format=k8s-resource-fully-qualified-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-fully-qualified-name optional string matchAttribute = 2; // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -789,8 +871,8 @@ message DeviceCounterConsumption { // counters defined will be consumed. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name optional string counterSet = 1; // Counters defines the counters that will be consumed by the device. @@ -798,6 +880,8 @@ message DeviceCounterConsumption { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name map counters = 2; } @@ -827,7 +911,7 @@ message DeviceRequest { // // +optional // +oneOf=deviceRequestType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional ExactDeviceRequest exactly = 2; // FirstAvailable contains subrequests, of which exactly one will be @@ -848,11 +932,11 @@ message DeviceRequest { // +oneOf=deviceRequestType // +listType=atomic // +featureGate=DRAPrioritizedList - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 repeated DeviceSubRequest firstAvailable = 3; } @@ -877,8 +961,9 @@ message DeviceRequestAllocationResult { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:format=k8s-long-name-caseless - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 optional string driver = 2; // This name together with the driver name and the device name field @@ -888,8 +973,8 @@ message DeviceRequestAllocationResult { // DNS sub-domains separated by slashes. // // +required - // +k8s:required - // +k8s:format=k8s-resource-pool-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-pool-name optional string pool = 3; // Device references one device instance via its name in the driver's @@ -902,8 +987,7 @@ message DeviceRequestAllocationResult { // administrative access. See the corresponding request field // for a definition of mode. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. // // +optional @@ -915,38 +999,39 @@ message DeviceRequestAllocationResult { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 6; // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingConditions = 7; // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingFailureConditions = 8; // ShareID uniquely identifies an individual allocation share of the device, @@ -956,8 +1041,8 @@ message DeviceRequestAllocationResult { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid optional string shareID = 9; // ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. @@ -1014,8 +1099,8 @@ message DeviceSubRequest { // to reference. // // +required - // +k8s:required - // +k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -1025,8 +1110,8 @@ message DeviceSubRequest { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 3; // AllocationMode and its related fields define how devices are allocated @@ -1048,6 +1133,7 @@ message DeviceSubRequest { // requests with unknown modes. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string allocationMode = 4; // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1072,12 +1158,13 @@ message DeviceSubRequest { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 6; // Capacity define resource requirements against each capacity. @@ -1124,12 +1211,21 @@ message DeviceTaint { // Consumers must treat unknown effects like None. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string effect = 3; - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; } @@ -1142,8 +1238,8 @@ message DeviceToleration { // Must be a label name. // // +optional - // +k8s:optional - // +k8s:format=k8s-label-key + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-label-key optional string key = 1; // Operator represents a key's relationship to the value. @@ -1153,6 +1249,7 @@ message DeviceToleration { // // +optional // +default="Equal" + // +k8s:alpha(since: "1.36")=+k8s:optional optional string operator = 2; // Value is the taint value the toleration matches to. @@ -1166,6 +1263,7 @@ message DeviceToleration { // When specified, allowed values are NoSchedule and NoExecute. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string effect = 4; // TolerationSeconds represents the period of time the toleration (which must be @@ -1203,8 +1301,8 @@ message ExactDeviceRequest { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 2; // AllocationMode and its related fields define how devices are allocated @@ -1227,7 +1325,7 @@ message ExactDeviceRequest { // requests with unknown modes. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string allocationMode = 3; // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1243,8 +1341,7 @@ message ExactDeviceRequest { // all ordinary claims to the device with respect to access modes and // any resource allocations. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. // // +optional @@ -1266,12 +1363,13 @@ message ExactDeviceRequest { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 6; // Capacity define resource requirements against each capacity. @@ -1300,11 +1398,11 @@ message NetworkDeviceData { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=256 optional string interfaceName = 1; // IPs lists the network addresses assigned to the device's network interface. @@ -1315,22 +1413,60 @@ message NetworkDeviceData { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 repeated string ips = 2; // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=128 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=128 optional string hardwareAddress = 3; } +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +message NodeAllocatableResourceMapping { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + optional string capacityKey = 1; + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + optional .k8s.io.apimachinery.pkg.api.resource.Quantity allocationMultiplier = 2; +} + // OpaqueDeviceConfiguration contains configuration parameters for a driver // in a format defined by the driver vendor. message OpaqueDeviceConfiguration { @@ -1344,8 +1480,9 @@ message OpaqueDeviceConfiguration { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:required - // +k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 optional string driver = 1; // Parameters can contain arbitrary data. It is the responsibility of @@ -1364,9 +1501,6 @@ message OpaqueDeviceConfiguration { // with specific properties, this is how that request is expressed. The status // stanza tracks whether this claim has been satisfied and what specific // resources have been allocated. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. message ResourceClaim { // Standard object metadata // +optional @@ -1374,7 +1508,7 @@ message ResourceClaim { // Spec describes what is being requested and how to configure it. // The spec is immutable. - // +k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:immutable optional ResourceClaimSpec spec = 2; // Status describes whether the claim is ready to use and what has been allocated. @@ -1429,8 +1563,8 @@ message ResourceClaimStatus { // Allocation is set once the claim has been allocated successfully. // // +optional - // +k8s:optional - // +k8s:update=NoModify + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:update=NoModify optional AllocationResult allocation = 1; // ReservedFor indicates which entities are currently allowed to use @@ -1458,10 +1592,10 @@ message ResourceClaimStatus { // +listMapKey=uid // +patchStrategy=merge // +patchMergeKey=uid - // +k8s:optional - // +k8s:listType=map - // +k8s:listMapKey=uid - // +k8s:maxItems=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=uid + // +k8s:alpha(since: "1.36")=+k8s:maxItems=256 repeated ResourceClaimConsumerReference reservedFor = 2; // Devices contains the status of each device allocated for this @@ -1469,25 +1603,22 @@ message ResourceClaimStatus { // information. Entries are owned by their respective drivers. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=map // +listMapKey=driver // +listMapKey=device // +listMapKey=pool // +listMapKey=shareID // +featureGate=DRAResourceClaimDeviceStatus - // +k8s:listType=map - // +k8s:listMapKey=driver - // +k8s:listMapKey=device - // +k8s:listMapKey=pool - // +k8s:listMapKey=shareID + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=driver + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=device + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=pool + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=shareID repeated AllocatedDeviceStatus devices = 4; } // ResourceClaimTemplate is used to produce ResourceClaim objects. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. message ResourceClaimTemplate { // Standard object metadata // +optional @@ -1583,9 +1714,6 @@ message ResourcePool { // // For resources that are not local to a node, the node name is not set. Instead, // the driver may use a node selector to specify where the devices are available. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. message ResourceSlice { // Standard object metadata // +optional @@ -1594,6 +1722,7 @@ message ResourceSlice { // Contains the information published by the driver. // // Changing the spec automatically increments the metadata.generation number. + // +required optional ResourceSliceSpec spec = 2; } @@ -1668,7 +1797,7 @@ message ResourceSliceSpec { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +zeroOrOneOf=ResourceSliceType repeated Device devices = 6; @@ -1694,14 +1823,14 @@ message ResourceSliceSpec { // The maximum number of counter sets is 8. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name // +featureGate=DRAPartitionableDevices // +zeroOrOneOf=ResourceSliceType - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 repeated CounterSet sharedCounters = 8; } diff --git a/staging/src/k8s.io/api/resource/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/resource/v1/generated.protomessage.pb.go deleted file mode 100644 index 97035a9360571..0000000000000 --- a/staging/src/k8s.io/api/resource/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,108 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*AllocatedDeviceStatus) ProtoMessage() {} - -func (*AllocationResult) ProtoMessage() {} - -func (*CELDeviceSelector) ProtoMessage() {} - -func (*CapacityRequestPolicy) ProtoMessage() {} - -func (*CapacityRequestPolicyRange) ProtoMessage() {} - -func (*CapacityRequirements) ProtoMessage() {} - -func (*Counter) ProtoMessage() {} - -func (*CounterSet) ProtoMessage() {} - -func (*Device) ProtoMessage() {} - -func (*DeviceAllocationConfiguration) ProtoMessage() {} - -func (*DeviceAllocationResult) ProtoMessage() {} - -func (*DeviceAttribute) ProtoMessage() {} - -func (*DeviceCapacity) ProtoMessage() {} - -func (*DeviceClaim) ProtoMessage() {} - -func (*DeviceClaimConfiguration) ProtoMessage() {} - -func (*DeviceClass) ProtoMessage() {} - -func (*DeviceClassConfiguration) ProtoMessage() {} - -func (*DeviceClassList) ProtoMessage() {} - -func (*DeviceClassSpec) ProtoMessage() {} - -func (*DeviceConfiguration) ProtoMessage() {} - -func (*DeviceConstraint) ProtoMessage() {} - -func (*DeviceCounterConsumption) ProtoMessage() {} - -func (*DeviceRequest) ProtoMessage() {} - -func (*DeviceRequestAllocationResult) ProtoMessage() {} - -func (*DeviceSelector) ProtoMessage() {} - -func (*DeviceSubRequest) ProtoMessage() {} - -func (*DeviceTaint) ProtoMessage() {} - -func (*DeviceToleration) ProtoMessage() {} - -func (*ExactDeviceRequest) ProtoMessage() {} - -func (*NetworkDeviceData) ProtoMessage() {} - -func (*OpaqueDeviceConfiguration) ProtoMessage() {} - -func (*ResourceClaim) ProtoMessage() {} - -func (*ResourceClaimConsumerReference) ProtoMessage() {} - -func (*ResourceClaimList) ProtoMessage() {} - -func (*ResourceClaimSpec) ProtoMessage() {} - -func (*ResourceClaimStatus) ProtoMessage() {} - -func (*ResourceClaimTemplate) ProtoMessage() {} - -func (*ResourceClaimTemplateList) ProtoMessage() {} - -func (*ResourceClaimTemplateSpec) ProtoMessage() {} - -func (*ResourcePool) ProtoMessage() {} - -func (*ResourceSlice) ProtoMessage() {} - -func (*ResourceSliceList) ProtoMessage() {} - -func (*ResourceSliceSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/resource/v1/types.go b/staging/src/k8s.io/api/resource/v1/types.go index 29b4a5fbaccbf..27d5eff29c25f 100644 --- a/staging/src/k8s.io/api/resource/v1/types.go +++ b/staging/src/k8s.io/api/resource/v1/types.go @@ -35,6 +35,11 @@ const ( // special ResourceClaim. Its single valid value is "true". // This is used only inside the scheduler. ExtendedResourceClaimAnnotation = "resource.kubernetes.io/extended-resource-claim" + // PodResourceClaimAnnotation is the annotation set on template-generated + // ResourceClaims by the ResourceClaim controller. Its value is the + // spec.resourceClaims[].name of the Pod or PodGroup for which the claim was + // generated. The Pod and its PodGroup are guaranteed to use the same name. + PodResourceClaimAnnotation = "resource.kubernetes.io/pod-claim-name" // Resource device class prefix is for generating implicit extended resource // name for a device class when its ExtendedResourceName field is not // specified. The generated name is this prefix + the device class name. @@ -42,6 +47,23 @@ const ( // in pod.Spec.Resources.Requests, in that case, a valid name has to be specified // explicitly in device class. ResourceDeviceClassPrefix string = "deviceclass.resource.kubernetes.io/" + + // The constants below are all related to synthetic authorization checks for resourceclaims.status writes. + + // SubresourceBinding is the synthetic subresource used for authorization + // of updates to status.allocation and status.reservedFor. + SubresourceBinding = "binding" + // SubresourceDriver is the synthetic subresource used for per-driver + // authorization of updates to status.devices. + SubresourceDriver = "driver" + // VerbPrefixAssociatedNode is the verb prefix for requests from a service account + // on the same node as the claim's allocation. The full verb is + // "associated-node:", e.g. "associated-node:update". + VerbPrefixAssociatedNode = "associated-node:" + // VerbPrefixArbitraryNode is the verb prefix for requests not associated + // with a specific node (controllers, etc.). The full verb is + // "arbitrary-node:", e.g. "arbitrary-node:update". + VerbPrefixArbitraryNode = "arbitrary-node:" ) // +genclient @@ -70,9 +92,6 @@ const ( // // For resources that are not local to a node, the node name is not set. Instead, // the driver may use a node selector to specify where the devices are available. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type ResourceSlice struct { metav1.TypeMeta `json:",inline"` // Standard object metadata @@ -82,6 +101,7 @@ type ResourceSlice struct { // Contains the information published by the driver. // // Changing the spec automatically increments the metadata.generation number. + // +required Spec ResourceSliceSpec `json:"spec" protobuf:"bytes,2,name=spec"` } @@ -155,7 +175,7 @@ type ResourceSliceSpec struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +zeroOrOneOf=ResourceSliceType Devices []Device `json:"devices,omitempty" protobuf:"bytes,6,name=devices"` @@ -181,14 +201,14 @@ type ResourceSliceSpec struct { // The maximum number of counter sets is 8. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name // +featureGate=DRAPartitionableDevices // +zeroOrOneOf=ResourceSliceType - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 SharedCounters []CounterSet `json:"sharedCounters,omitempty" protobuf:"bytes,8,name=sharedCounters"` } @@ -205,8 +225,8 @@ type CounterSet struct { // It must be a DNS label. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name Name string `json:"name" protobuf:"bytes,1,name=name"` // Counters defines the set of counters for this CounterSet @@ -215,6 +235,8 @@ type CounterSet struct { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"` } @@ -261,7 +283,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 @@ -298,6 +327,7 @@ type Device struct { // The maximum number of attributes and capacities combined is 32. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Attributes map[QualifiedName]DeviceAttribute `json:"attributes,omitempty" protobuf:"bytes,2,rep,name=attributes"` // Capacity defines the set of capacities for this device. @@ -318,13 +348,13 @@ type Device struct { // device is 2. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=counterSet + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=counterSet // +featureGate=DRAPartitionableDevices - // +k8s:maxItems=2 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=2 ConsumesCounters []DeviceCounterConsumption `json:"consumesCounters,omitempty" protobuf:"bytes,4,rep,name=consumesCounters"` // NodeName identifies the node where the device is available. @@ -365,12 +395,13 @@ type Device struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Taints []DeviceTaint `json:"taints,omitempty" protobuf:"bytes,8,rep,name=taints"` // BindsToNode indicates if the usage of an allocation involving this device @@ -378,7 +409,7 @@ type Device struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -394,14 +425,14 @@ type Device struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingConditions []string `json:"bindingConditions,omitempty" protobuf:"bytes,10,rep,name=bindingConditions"` // BindingFailureConditions defines the conditions for binding failure. @@ -412,14 +443,14 @@ type Device struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingFailureConditions []string `json:"bindingFailureConditions,omitempty" protobuf:"bytes,11,rep,name=bindingFailureConditions"` // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -430,6 +461,59 @@ type Device struct { // +optional // +featureGate=DRAConsumableCapacity AllowMultipleAllocations *bool `json:"allowMultipleAllocations,omitempty" protobuf:"bytes,12,opt,name=allowMultipleAllocations"` + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + NodeAllocatableResourceMappings map[v1.ResourceName]NodeAllocatableResourceMapping `json:"nodeAllocatableResourceMappings,omitempty" protobuf:"bytes,13,opt,name=nodeAllocatableResourceMappings"` +} + +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +type NodeAllocatableResourceMapping struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + CapacityKey *QualifiedName `json:"capacityKey,omitempty" protobuf:"bytes,1,opt,name=capacityKey"` + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + AllocationMultiplier *resource.Quantity `json:"allocationMultiplier,omitempty" protobuf:"bytes,2,opt,name=allocationMultiplier"` } // DeviceCounterConsumption defines a set of counters that @@ -439,8 +523,8 @@ type DeviceCounterConsumption struct { // counters defined will be consumed. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name CounterSet string `json:"counterSet" protobuf:"bytes,1,opt,name=counterSet"` // Counters defines the counters that will be consumed by the device. @@ -448,6 +532,8 @@ type DeviceCounterConsumption struct { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"` } @@ -568,6 +654,10 @@ type CapacityRequestPolicyRange struct { // Limit for the sum of the number of entries in both attributes and capacity. const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32 +// Limit per device for the total number of string, version, bool or int values +// in list and non-list attributes. +const ResourceSliceMaxAttributeValuesPerDevice = 48 + // QualifiedName is the name of a device attribute or capacity. // // Attributes and capacities are defined either by the owner of the specific @@ -607,31 +697,79 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + IntValues []int64 `json:"ints,omitempty" protobuf:"varint,6,opt,name=ints"` + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + BoolValues []bool `json:"bools,omitempty" protobuf:"varint,7,opt,name=bools"` + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + StringValues []string `json:"strings,omitempty" protobuf:"bytes,8,opt,name=strings"` + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + VersionValues []string `json:"versions,omitempty" protobuf:"bytes,9,opt,name=versions"` } // DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value. @@ -666,7 +804,7 @@ type DeviceTaint struct { // Consumers must treat unknown effects like None. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"` // ^^^^ @@ -682,9 +820,18 @@ type DeviceTaint struct { // which will enable adding new enums within a single release without // ratcheting. - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"` @@ -696,7 +843,7 @@ type DeviceTaint struct { } // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceTaintEffect string const ( @@ -729,16 +876,13 @@ type ResourceSliceList struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.34 -// +k8s:supportsSubresource=/status +// +k8s:supportsSubresource="/status" // ResourceClaim describes a request for access to resources in the cluster, // for use by workloads. For example, if a workload needs an accelerator device // with specific properties, this is how that request is expressed. The status // stanza tracks whether this claim has been satisfied and what specific // resources have been allocated. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type ResourceClaim struct { metav1.TypeMeta `json:",inline"` // Standard object metadata @@ -747,7 +891,7 @@ type ResourceClaim struct { // Spec describes what is being requested and how to configure it. // The spec is immutable. - // +k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:immutable Spec ResourceClaimSpec `json:"spec" protobuf:"bytes,2,name=spec"` // Status describes whether the claim is ready to use and what has been allocated. @@ -775,11 +919,11 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []DeviceRequest `json:"requests" protobuf:"bytes,1,name=requests"` // These constraints must be satisfied by the set of devices that get @@ -787,8 +931,8 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Constraints []DeviceConstraint `json:"constraints,omitempty" protobuf:"bytes,2,opt,name=constraints"` // This field holds configuration for multiple potential drivers which @@ -797,8 +941,8 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Config []DeviceClaimConfiguration `json:"config,omitempty" protobuf:"bytes,3,opt,name=config"` // Potential future extension, ignored by older schedulers. This is @@ -849,7 +993,7 @@ type DeviceRequest struct { // // +optional // +oneOf=deviceRequestType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional Exactly *ExactDeviceRequest `json:"exactly,omitempty" protobuf:"bytes,2,name=exactly"` // FirstAvailable contains subrequests, of which exactly one will be @@ -870,11 +1014,11 @@ type DeviceRequest struct { // +oneOf=deviceRequestType // +listType=atomic // +featureGate=DRAPrioritizedList - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,3,name=firstAvailable"` } @@ -902,8 +1046,8 @@ type ExactDeviceRequest struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,2,name=selectors"` // AllocationMode and its related fields define how devices are allocated @@ -926,7 +1070,7 @@ type ExactDeviceRequest struct { // requests with unknown modes. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,3,opt,name=allocationMode"` // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -942,8 +1086,7 @@ type ExactDeviceRequest struct { // all ordinary claims to the device with respect to access modes and // any resource allocations. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. // // +optional @@ -965,12 +1108,13 @@ type ExactDeviceRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // Capacity define resource requirements against each capacity. @@ -1022,8 +1166,8 @@ type DeviceSubRequest struct { // to reference. // // +required - // +k8s:required - // +k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` // Selectors define criteria which must be satisfied by a specific @@ -1033,8 +1177,8 @@ type DeviceSubRequest struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` // AllocationMode and its related fields define how devices are allocated @@ -1056,6 +1200,7 @@ type DeviceSubRequest struct { // requests with unknown modes. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1080,12 +1225,13 @@ type DeviceSubRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // Capacity define resource requirements against each capacity. @@ -1142,7 +1288,7 @@ const ( ) // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceAllocationMode string // Valid [DeviceRequest.CountMode] values. @@ -1211,6 +1357,14 @@ type CELDeviceSelector struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -1261,10 +1415,10 @@ type DeviceConstraint struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` // MatchAttribute requires that all devices in question have this @@ -1278,12 +1432,17 @@ type DeviceConstraint struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as single-element lists for backward compatibility. + // // Must include the domain qualifier. // // +optional // +oneOf=ConstraintType - // +k8s:optional - // +k8s:format=k8s-resource-fully-qualified-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-fully-qualified-name MatchAttribute *FullyQualifiedName `json:"matchAttribute,omitempty" protobuf:"bytes,2,opt,name=matchAttribute"` // Potential future extension, not part of the current design: @@ -1299,6 +1458,11 @@ type DeviceConstraint struct { // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -1324,10 +1488,10 @@ type DeviceClaimConfiguration struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"` @@ -1341,7 +1505,7 @@ type DeviceConfiguration struct { // // +optional // +oneOf=ConfigurationType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional Opaque *OpaqueDeviceConfiguration `json:"opaque,omitempty" protobuf:"bytes,1,opt,name=opaque"` } @@ -1358,8 +1522,9 @@ type OpaqueDeviceConfiguration struct { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:required - // +k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 Driver string `json:"driver" protobuf:"bytes,1,name=driver"` // Parameters can contain arbitrary data. It is the responsibility of @@ -1385,8 +1550,8 @@ type DeviceToleration struct { // Must be a label name. // // +optional - // +k8s:optional - // +k8s:format=k8s-label-key + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-label-key Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"` // Operator represents a key's relationship to the value. @@ -1396,6 +1561,7 @@ type DeviceToleration struct { // // +optional // +default="Equal" + // +k8s:alpha(since: "1.36")=+k8s:optional Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"` // Value is the taint value the toleration matches to. @@ -1409,6 +1575,7 @@ type DeviceToleration struct { // When specified, allowed values are NoSchedule and NoExecute. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"` // TolerationSeconds represents the period of time the toleration (which must be @@ -1425,7 +1592,7 @@ type DeviceToleration struct { // A toleration operator is the set of operators that can be used in a toleration. // // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceTolerationOperator string const ( @@ -1439,8 +1606,8 @@ type ResourceClaimStatus struct { // Allocation is set once the claim has been allocated successfully. // // +optional - // +k8s:optional - // +k8s:update=NoModify + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:update=NoModify Allocation *AllocationResult `json:"allocation,omitempty" protobuf:"bytes,1,opt,name=allocation"` // ReservedFor indicates which entities are currently allowed to use @@ -1468,10 +1635,10 @@ type ResourceClaimStatus struct { // +listMapKey=uid // +patchStrategy=merge // +patchMergeKey=uid - // +k8s:optional - // +k8s:listType=map - // +k8s:listMapKey=uid - // +k8s:maxItems=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=uid + // +k8s:alpha(since: "1.36")=+k8s:maxItems=256 ReservedFor []ResourceClaimConsumerReference `json:"reservedFor,omitempty" protobuf:"bytes,2,opt,name=reservedFor" patchStrategy:"merge" patchMergeKey:"uid"` // DeallocationRequested is tombstoned since Kubernetes 1.32 where @@ -1484,18 +1651,18 @@ type ResourceClaimStatus struct { // information. Entries are owned by their respective drivers. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=map // +listMapKey=driver // +listMapKey=device // +listMapKey=pool // +listMapKey=shareID // +featureGate=DRAResourceClaimDeviceStatus - // +k8s:listType=map - // +k8s:listMapKey=driver - // +k8s:listMapKey=device - // +k8s:listMapKey=pool - // +k8s:listMapKey=shareID + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=driver + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=device + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=pool + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=shareID Devices []AllocatedDeviceStatus `json:"devices,omitempty" protobuf:"bytes,4,opt,name=devices"` } @@ -1544,7 +1711,7 @@ type AllocationResult struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -1558,8 +1725,8 @@ type DeviceAllocationResult struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Results []DeviceRequestAllocationResult `json:"results,omitempty" protobuf:"bytes,1,opt,name=results"` // This field is a combination of all the claim and class configuration parameters. @@ -1572,8 +1739,8 @@ type DeviceAllocationResult struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=64 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=64 Config []DeviceAllocationConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` } @@ -1602,8 +1769,9 @@ type DeviceRequestAllocationResult struct { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:format=k8s-long-name-caseless - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 Driver string `json:"driver" protobuf:"bytes,2,name=driver"` // This name together with the driver name and the device name field @@ -1613,8 +1781,8 @@ type DeviceRequestAllocationResult struct { // DNS sub-domains separated by slashes. // // +required - // +k8s:required - // +k8s:format=k8s-resource-pool-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-pool-name Pool string `json:"pool" protobuf:"bytes,3,name=pool"` // Device references one device instance via its name in the driver's @@ -1627,8 +1795,7 @@ type DeviceRequestAllocationResult struct { // administrative access. See the corresponding request field // for a definition of mode. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. // // +optional @@ -1640,38 +1807,39 @@ type DeviceRequestAllocationResult struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingConditions []string `json:"bindingConditions,omitempty" protobuf:"bytes,7,rep,name=bindingConditions"` // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingFailureConditions []string `json:"bindingFailureConditions,omitempty" protobuf:"bytes,8,rep,name=bindingFailureConditions"` // ShareID uniquely identifies an individual allocation share of the device, @@ -1681,8 +1849,8 @@ type DeviceRequestAllocationResult struct { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid ShareID *types.UID `json:"shareID,omitempty" protobuf:"bytes,9,opt,name=shareID"` // ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. @@ -1706,7 +1874,7 @@ type DeviceAllocationConfiguration struct { // or from a claim. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Source AllocationConfigSource `json:"source" protobuf:"bytes,1,name=source"` // Requests lists the names of requests where the configuration applies. @@ -1718,17 +1886,17 @@ type DeviceAllocationConfiguration struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` DeviceConfiguration `json:",inline" protobuf:"bytes,3,name=deviceConfiguration"` } // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type AllocationConfigSource string // Valid [DeviceAllocationConfiguration.Source] values. @@ -1760,15 +1928,12 @@ type ResourceClaimList struct { // device configuration and selectors. It can be referenced in // the device requests of a claim to apply these presets. // Cluster scoped. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type DeviceClass struct { metav1.TypeMeta `json:",inline"` // Standard object metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec defines what can be allocated and how to configure it. @@ -1789,8 +1954,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,1,opt,name=selectors"` // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -1801,8 +1966,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Config []DeviceClassConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` // SuitableNodes is tombstoned since Kubernetes 1.32 where @@ -1819,11 +1984,11 @@ type DeviceClassSpec struct { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. // +optional // +featureGate=DRAExtendedResource - // +k8s:optional - // +k8s:format=k8s-extended-resource-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-extended-resource-name ExtendedResourceName *string `json:"extendedResourceName,omitempty" protobuf:"bytes,4,opt,name=extendedResourceName"` } @@ -1851,9 +2016,6 @@ type DeviceClassList struct { // +k8s:prerelease-lifecycle-gen:introduced=1.34 // ResourceClaimTemplate is used to produce ResourceClaim objects. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type ResourceClaimTemplate struct { metav1.TypeMeta `json:",inline"` // Standard object metadata @@ -1949,8 +2111,8 @@ type AllocatedDeviceStatus struct { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid ShareID *string `json:"shareID,omitempty" protobuf:"bytes,7,opt,name=shareID"` // Conditions contains the latest observation of the device's state. @@ -1974,7 +2136,7 @@ type AllocatedDeviceStatus struct { // NetworkData contains network-related information specific to the device. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional NetworkData *NetworkDeviceData `json:"networkData,omitempty" protobuf:"bytes,6,opt,name=networkData"` } @@ -1986,11 +2148,11 @@ type NetworkDeviceData struct { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=256 InterfaceName string `json:"interfaceName,omitempty" protobuf:"bytes,1,opt,name=interfaceName"` // IPs lists the network addresses assigned to the device's network interface. @@ -2001,18 +2163,18 @@ type NetworkDeviceData struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 IPs []string `json:"ips,omitempty" protobuf:"bytes,2,opt,name=ips"` // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=128 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=128 HardwareAddress string `json:"hardwareAddress,omitempty" protobuf:"bytes,3,opt,name=hardwareAddress"` } diff --git a/staging/src/k8s.io/api/resource/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/resource/v1/types_swagger_doc_generated.go index 6ba5f598c6885..51915d3e20ec5 100644 --- a/staging/src/k8s.io/api/resource/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/resource/v1/types_swagger_doc_generated.go @@ -46,7 +46,7 @@ var map_AllocationResult = map[string]string{ "": "AllocationResult contains attributes of an allocated resource.", "devices": "Devices is the result of allocating devices.", "nodeSelector": "NodeSelector defines where the allocated resources are available. If unset, they are available everywhere.", - "allocationTimestamp": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", + "allocationTimestamp": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", } func (AllocationResult) SwaggerDoc() map[string]string { @@ -55,7 +55,7 @@ func (AllocationResult) SwaggerDoc() map[string]string { var map_CELDeviceSelector = map[string]string{ "": "CELDeviceSelector contains a CEL expression for selecting a device.", - "expression": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "expression": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", } func (CELDeviceSelector) SwaggerDoc() map[string]string { @@ -113,19 +113,20 @@ func (CounterSet) SwaggerDoc() map[string]string { } var map_Device = map[string]string{ - "": "Device represents one individual hardware instance that can be selected based on its attributes. Besides the name, exactly one field must be set.", - "name": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", - "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe maximum number of device counter consumptions per device is 2.", - "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", - "bindsToNode": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingConditions": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingFailureConditions": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "allowMultipleAllocations": "AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests.\n\nIf AllowMultipleAllocations is set to true, the device can be allocated more than once, and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not.", + "": "Device represents one individual hardware instance that can be selected based on its attributes. Besides the name, exactly one field must be set.", + "name": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", + "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe maximum number of device counter consumptions per device is 2.", + "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", + "bindsToNode": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingConditions": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingFailureConditions": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "allowMultipleAllocations": "AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests.\n\nIf AllowMultipleAllocations is set to true, the device can be allocated more than once, and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not.", + "nodeAllocatableResourceMappings": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", } func (Device) SwaggerDoc() map[string]string { @@ -153,11 +154,15 @@ func (DeviceAllocationResult) SwaggerDoc() map[string]string { } var map_DeviceAttribute = map[string]string{ - "": "DeviceAttribute must have exactly one field set.", - "int": "IntValue is a number.", - "bool": "BoolValue is a true/false value.", - "string": "StringValue is a string. Must not be longer than 64 characters.", - "version": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", + "": "DeviceAttribute must have exactly one field set.", + "int": "IntValue is a number.", + "bool": "BoolValue is a true/false value.", + "string": "StringValue is a string. Must not be longer than 64 characters.", + "version": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", + "ints": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "bools": "BoolValues is a non-empty list of true/false values.", + "strings": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "versions": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", } func (DeviceAttribute) SwaggerDoc() map[string]string { @@ -195,7 +200,7 @@ func (DeviceClaimConfiguration) SwaggerDoc() map[string]string { } var map_DeviceClass = map[string]string{ - "": "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "": "DeviceClass is a vendor- or admin-provided resource that contains device configuration and selectors. It can be referenced in the device requests of a claim to apply these presets. Cluster scoped.", "metadata": "Standard object metadata", "spec": "Spec defines what can be allocated and how to configure it.\n\nThis is mutable. Consumers have to be prepared for classes changing at any time, either because they get updated or replaced. Claim allocations are done once based on whatever was set in classes at the time of allocation.\n\nChanging the spec automatically increments the metadata.generation number.", } @@ -226,7 +231,7 @@ var map_DeviceClassSpec = map[string]string{ "": "DeviceClassSpec is used in a [DeviceClass] to define what can be allocated and how to configure it.", "selectors": "Each selector must be satisfied by a device which is claimed via this class.", "config": "Config defines configuration parameters that apply to each device that is claimed via this class. Some classses may potentially be satisfied by multiple drivers, so each instance of a vendor configuration applies to exactly one driver.\n\nThey are passed to the driver, but are not considered while allocating the claim.", - "extendedResourceName": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "extendedResourceName": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", } func (DeviceClassSpec) SwaggerDoc() map[string]string { @@ -245,8 +250,8 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", - "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", - "distinctAttribute": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as single-element lists for backward compatibility.\n\nMust include the domain qualifier.", + "distinctAttribute": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", } func (DeviceConstraint) SwaggerDoc() map[string]string { @@ -280,10 +285,10 @@ var map_DeviceRequestAllocationResult = map[string]string{ "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver. It should use only lower case characters.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", - "adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", - "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", - "bindingConditions": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingFailureConditions": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", + "bindingConditions": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingFailureConditions": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "shareID": "ShareID uniquely identifies an individual allocation share of the device, used when the device supports multiple simultaneous allocations. It serves as an additional map key to differentiate concurrent shares of the same device.", "consumedCapacity": "ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. The consumed amount may differ from the requested amount: it is rounded up to the nearest valid value based on the device’s requestPolicy if applicable (i.e., may not be less than the requested amount).\n\nThe total consumed capacity for each device must not exceed the DeviceCapacity's Value.\n\nThis field is populated only for devices that allow multiple allocations. All capacity entries are included, even if the consumed amount is zero.", } @@ -308,7 +313,7 @@ var map_DeviceSubRequest = map[string]string{ "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "capacity": "Capacity define resource requirements against each capacity.\n\nIf this field is unset and the device supports multiple allocations, the default value will be applied to each capacity according to requestPolicy. For the capacity that has no requestPolicy, default is the full capacity value.\n\nApplies to each device allocation. If Count > 1, the request fails if there aren't enough devices that meet the requirements. If AllocationMode is set to All, the request fails if there are devices that otherwise match the request, and have this capacity, with a value >= the requested amount, but which cannot be allocated to this request.", } @@ -321,7 +326,7 @@ var map_DeviceTaint = map[string]string{ "key": "The taint key to be applied to a device. Must be a label name.", "value": "The taint value corresponding to the taint key. Must be a label value.", "effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them.\n\nValid effects are None, NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here. More effects may get added in the future. Consumers must treat unknown effects like None.", - "timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + "timeAdded": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", } func (DeviceTaint) SwaggerDoc() map[string]string { @@ -347,8 +352,8 @@ var map_ExactDeviceRequest = map[string]string{ "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", - "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nAdmin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "capacity": "Capacity define resource requirements against each capacity.\n\nIf this field is unset and the device supports multiple allocations, the default value will be applied to each capacity according to requestPolicy. For the capacity that has no requestPolicy, default is the full capacity value.\n\nApplies to each device allocation. If Count > 1, the request fails if there aren't enough devices that meet the requirements. If AllocationMode is set to All, the request fails if there are devices that otherwise match the request, and have this capacity, with a value >= the requested amount, but which cannot be allocated to this request.", } @@ -358,15 +363,25 @@ func (ExactDeviceRequest) SwaggerDoc() map[string]string { var map_NetworkDeviceData = map[string]string{ "": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", - "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "ips": "IPs lists the network addresses assigned to the device's network interface. This can include both IPv4 and IPv6 addresses. The IPs are in the CIDR notation, which includes both the address and the associated subnet mask. e.g.: \"192.0.2.5/24\" for IPv4 and \"2001:db8::5/64\" for IPv6.", - "hardwareAddress": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "hardwareAddress": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", } func (NetworkDeviceData) SwaggerDoc() map[string]string { return map_NetworkDeviceData } +var map_NodeAllocatableResourceMapping = map[string]string{ + "": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "capacityKey": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "allocationMultiplier": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16.", +} + +func (NodeAllocatableResourceMapping) SwaggerDoc() map[string]string { + return map_NodeAllocatableResourceMapping +} + var map_OpaqueDeviceConfiguration = map[string]string{ "": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "driver": "Driver is used to determine which kubelet plugin needs to be passed these configuration parameters.\n\nAn admission policy provided by the driver developer could use this to decide whether it needs to validate them.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver. It should use only lower case characters.", @@ -378,7 +393,7 @@ func (OpaqueDeviceConfiguration) SwaggerDoc() map[string]string { } var map_ResourceClaim = map[string]string{ - "": "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "": "ResourceClaim describes a request for access to resources in the cluster, for use by workloads. For example, if a workload needs an accelerator device with specific properties, this is how that request is expressed. The status stanza tracks whether this claim has been satisfied and what specific resources have been allocated.", "metadata": "Standard object metadata", "spec": "Spec describes what is being requested and how to configure it. The spec is immutable.", "status": "Status describes whether the claim is ready to use and what has been allocated.", @@ -431,7 +446,7 @@ func (ResourceClaimStatus) SwaggerDoc() map[string]string { } var map_ResourceClaimTemplate = map[string]string{ - "": "ResourceClaimTemplate is used to produce ResourceClaim objects.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "": "ResourceClaimTemplate is used to produce ResourceClaim objects.", "metadata": "Standard object metadata", "spec": "Describes the ResourceClaim that is to be generated.\n\nThis field is immutable. A ResourceClaim will get created by the control plane for a Pod when needed and then not get updated anymore.", } @@ -472,7 +487,7 @@ func (ResourcePool) SwaggerDoc() map[string]string { } var map_ResourceSlice = map[string]string{ - "": "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.\n\nThis is an alpha type and requires enabling the DynamicResourceAllocation feature gate.", + "": "ResourceSlice represents one or more resources in a pool of similar resources, managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many ResourceSlices comprise a pool is determined by the driver.\n\nAt the moment, the only supported resources are devices with attributes and capacities. Each device in a given pool, regardless of how many ResourceSlices, must have a unique name. The ResourceSlice in which a device gets published may change over time. The unique identifier for a device is the tuple , , .\n\nWhenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number and updates all ResourceSlices with that new number and new resource definitions. A consumer must only use ResourceSlices with the highest generation number and ignore all others.\n\nWhen allocating all resources in a pool matching certain criteria or when looking for the best solution among several different alternatives, a consumer should check the number of ResourceSlices in a pool (included in each ResourceSlice) to determine whether its view of a pool is complete and if not, should wait until the driver has completed updating the pool.\n\nFor resources that are not local to a node, the node name is not set. Instead, the driver may use a node selector to specify where the devices are available.", "metadata": "Standard object metadata", "spec": "Contains the information published by the driver.\n\nChanging the spec automatically increments the metadata.generation number.", } diff --git a/staging/src/k8s.io/api/resource/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1/zz_generated.deepcopy.go index 580ba64875f70..0c488a05ae0c3 100644 --- a/staging/src/k8s.io/api/resource/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1/zz_generated.deepcopy.go @@ -302,6 +302,13 @@ func (in *Device) DeepCopyInto(out *Device) { *out = new(bool) **out = **in } + if in.NodeAllocatableResourceMappings != nil { + in, out := &in.NodeAllocatableResourceMappings, &out.NodeAllocatableResourceMappings + *out = make(map[corev1.ResourceName]NodeAllocatableResourceMapping, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } return } @@ -390,6 +397,26 @@ func (in *DeviceAttribute) DeepCopyInto(out *DeviceAttribute) { *out = new(string) **out = **in } + if in.IntValues != nil { + in, out := &in.IntValues, &out.IntValues + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.BoolValues != nil { + in, out := &in.BoolValues, &out.BoolValues + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.StringValues != nil { + in, out := &in.StringValues, &out.StringValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.VersionValues != nil { + in, out := &in.VersionValues, &out.VersionValues + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -907,6 +934,32 @@ func (in *NetworkDeviceData) DeepCopy() *NetworkDeviceData { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAllocatableResourceMapping) DeepCopyInto(out *NodeAllocatableResourceMapping) { + *out = *in + if in.CapacityKey != nil { + in, out := &in.CapacityKey, &out.CapacityKey + *out = new(QualifiedName) + **out = **in + } + if in.AllocationMultiplier != nil { + in, out := &in.AllocationMultiplier, &out.AllocationMultiplier + x := (*in).DeepCopy() + *out = &x + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAllocatableResourceMapping. +func (in *NodeAllocatableResourceMapping) DeepCopy() *NodeAllocatableResourceMapping { + if in == nil { + return nil + } + out := new(NodeAllocatableResourceMapping) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpaqueDeviceConfiguration) DeepCopyInto(out *OpaqueDeviceConfiguration) { *out = *in diff --git a/staging/src/k8s.io/api/resource/v1/zz_generated.model_name.go b/staging/src/k8s.io/api/resource/v1/zz_generated.model_name.go index c0de4f9e9db2e..f163c61ed189f 100644 --- a/staging/src/k8s.io/api/resource/v1/zz_generated.model_name.go +++ b/staging/src/k8s.io/api/resource/v1/zz_generated.model_name.go @@ -171,6 +171,11 @@ func (in NetworkDeviceData) OpenAPIModelName() string { return "io.k8s.api.resource.v1.NetworkDeviceData" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NodeAllocatableResourceMapping) OpenAPIModelName() string { + return "io.k8s.api.resource.v1.NodeAllocatableResourceMapping" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in OpaqueDeviceConfiguration) OpenAPIModelName() string { return "io.k8s.api.resource.v1.OpaqueDeviceConfiguration" diff --git a/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go b/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go index 66b9f8f6203dc..fa1f5f47100a6 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go @@ -47,6 +47,16 @@ func (m *DeviceTaintRuleStatus) Reset() { *m = DeviceTaintRuleStatus{} } func (m *DeviceTaintSelector) Reset() { *m = DeviceTaintSelector{} } +func (m *PoolStatus) Reset() { *m = PoolStatus{} } + +func (m *ResourcePoolStatusRequest) Reset() { *m = ResourcePoolStatusRequest{} } + +func (m *ResourcePoolStatusRequestList) Reset() { *m = ResourcePoolStatusRequestList{} } + +func (m *ResourcePoolStatusRequestSpec) Reset() { *m = ResourcePoolStatusRequestSpec{} } + +func (m *ResourcePoolStatusRequestStatus) Reset() { *m = ResourcePoolStatusRequestStatus{} } + func (m *CELDeviceSelector) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -386,6 +396,279 @@ func (m *DeviceTaintSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PoolStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PoolStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PoolStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ValidationError != nil { + i -= len(*m.ValidationError) + copy(dAtA[i:], *m.ValidationError) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ValidationError))) + i-- + dAtA[i] = 0x52 + } + i = encodeVarintGenerated(dAtA, i, uint64(m.Generation)) + i-- + dAtA[i] = 0x48 + if m.ResourceSliceCount != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.ResourceSliceCount)) + i-- + dAtA[i] = 0x40 + } + if m.UnavailableDevices != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.UnavailableDevices)) + i-- + dAtA[i] = 0x38 + } + if m.AvailableDevices != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.AvailableDevices)) + i-- + dAtA[i] = 0x30 + } + if m.AllocatedDevices != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.AllocatedDevices)) + i-- + dAtA[i] = 0x28 + } + if m.TotalDevices != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TotalDevices)) + i-- + dAtA[i] = 0x20 + } + if m.NodeName != nil { + i -= len(*m.NodeName) + copy(dAtA[i:], *m.NodeName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.NodeName))) + i-- + dAtA[i] = 0x1a + } + i -= len(m.PoolName) + copy(dAtA[i:], m.PoolName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PoolName))) + i-- + dAtA[i] = 0x12 + i -= len(m.Driver) + copy(dAtA[i:], m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ResourcePoolStatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourcePoolStatusRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourcePoolStatusRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Status != nil { + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ResourcePoolStatusRequestList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourcePoolStatusRequestList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourcePoolStatusRequestList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ResourcePoolStatusRequestSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourcePoolStatusRequestSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourcePoolStatusRequestSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Limit != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Limit)) + i-- + dAtA[i] = 0x18 + } + if m.PoolName != nil { + i -= len(*m.PoolName) + copy(dAtA[i:], *m.PoolName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PoolName))) + i-- + dAtA[i] = 0x12 + } + i -= len(m.Driver) + copy(dAtA[i:], m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ResourcePoolStatusRequestStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourcePoolStatusRequestStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourcePoolStatusRequestStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.PoolCount != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.PoolCount)) + i-- + dAtA[i] = 0x30 + } + if len(m.Conditions) > 0 { + for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Pools) > 0 { + for iNdEx := len(m.Pools) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Pools[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + return len(dAtA) - i, nil +} + func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset @@ -523,35 +806,148 @@ func (m *DeviceTaintSelector) Size() (n int) { return n } -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *CELDeviceSelector) String() string { - if this == nil { - return "nil" +func (m *PoolStatus) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&CELDeviceSelector{`, - `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, - `}`, - }, "") - return s -} -func (this *DeviceSelector) String() string { - if this == nil { - return "nil" + var l int + _ = l + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.PoolName) + n += 1 + l + sovGenerated(uint64(l)) + if m.NodeName != nil { + l = len(*m.NodeName) + n += 1 + l + sovGenerated(uint64(l)) } - s := strings.Join([]string{`&DeviceSelector{`, - `CEL:` + strings.Replace(this.CEL.String(), "CELDeviceSelector", "CELDeviceSelector", 1) + `,`, - `}`, - }, "") - return s + if m.TotalDevices != nil { + n += 1 + sovGenerated(uint64(*m.TotalDevices)) + } + if m.AllocatedDevices != nil { + n += 1 + sovGenerated(uint64(*m.AllocatedDevices)) + } + if m.AvailableDevices != nil { + n += 1 + sovGenerated(uint64(*m.AvailableDevices)) + } + if m.UnavailableDevices != nil { + n += 1 + sovGenerated(uint64(*m.UnavailableDevices)) + } + if m.ResourceSliceCount != nil { + n += 1 + sovGenerated(uint64(*m.ResourceSliceCount)) + } + n += 1 + sovGenerated(uint64(m.Generation)) + if m.ValidationError != nil { + l = len(*m.ValidationError) + n += 1 + l + sovGenerated(uint64(l)) + } + return n } -func (this *DeviceTaintRule) String() string { - if this == nil { - return "nil" + +func (m *ResourcePoolStatusRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.Status != nil { + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *ResourcePoolStatusRequestList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ResourcePoolStatusRequestSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + if m.PoolName != nil { + l = len(*m.PoolName) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Limit != nil { + n += 1 + sovGenerated(uint64(*m.Limit)) + } + return n +} + +func (m *ResourcePoolStatusRequestStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Pools) > 0 { + for _, e := range m.Pools { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.PoolCount != nil { + n += 1 + sovGenerated(uint64(*m.PoolCount)) + } + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *CELDeviceSelector) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CELDeviceSelector{`, + `Expression:` + fmt.Sprintf("%v", this.Expression) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceSelector) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceSelector{`, + `CEL:` + strings.Replace(this.CEL.String(), "CELDeviceSelector", "CELDeviceSelector", 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRule) String() string { + if this == nil { + return "nil" } s := strings.Join([]string{`&DeviceTaintRule{`, `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, @@ -615,6 +1011,87 @@ func (this *DeviceTaintSelector) String() string { }, "") return s } +func (this *PoolStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PoolStatus{`, + `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, + `PoolName:` + fmt.Sprintf("%v", this.PoolName) + `,`, + `NodeName:` + valueToStringGenerated(this.NodeName) + `,`, + `TotalDevices:` + valueToStringGenerated(this.TotalDevices) + `,`, + `AllocatedDevices:` + valueToStringGenerated(this.AllocatedDevices) + `,`, + `AvailableDevices:` + valueToStringGenerated(this.AvailableDevices) + `,`, + `UnavailableDevices:` + valueToStringGenerated(this.UnavailableDevices) + `,`, + `ResourceSliceCount:` + valueToStringGenerated(this.ResourceSliceCount) + `,`, + `Generation:` + fmt.Sprintf("%v", this.Generation) + `,`, + `ValidationError:` + valueToStringGenerated(this.ValidationError) + `,`, + `}`, + }, "") + return s +} +func (this *ResourcePoolStatusRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ResourcePoolStatusRequest{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ResourcePoolStatusRequestSpec", "ResourcePoolStatusRequestSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(this.Status.String(), "ResourcePoolStatusRequestStatus", "ResourcePoolStatusRequestStatus", 1) + `,`, + `}`, + }, "") + return s +} +func (this *ResourcePoolStatusRequestList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]ResourcePoolStatusRequest{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ResourcePoolStatusRequest", "ResourcePoolStatusRequest", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&ResourcePoolStatusRequestList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *ResourcePoolStatusRequestSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ResourcePoolStatusRequestSpec{`, + `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, + `PoolName:` + valueToStringGenerated(this.PoolName) + `,`, + `Limit:` + valueToStringGenerated(this.Limit) + `,`, + `}`, + }, "") + return s +} +func (this *ResourcePoolStatusRequestStatus) String() string { + if this == nil { + return "nil" + } + repeatedStringForPools := "[]PoolStatus{" + for _, f := range this.Pools { + repeatedStringForPools += strings.Replace(strings.Replace(f.String(), "PoolStatus", "PoolStatus", 1), `&`, ``, 1) + "," + } + repeatedStringForPools += "}" + repeatedStringForConditions := "[]Condition{" + for _, f := range this.Conditions { + repeatedStringForConditions += fmt.Sprintf("%v", f) + "," + } + repeatedStringForConditions += "}" + s := strings.Join([]string{`&ResourcePoolStatusRequestStatus{`, + `Pools:` + repeatedStringForPools + `,`, + `Conditions:` + repeatedStringForConditions + `,`, + `PoolCount:` + valueToStringGenerated(this.PoolCount) + `,`, + `}`, + }, "") + return s +} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -824,7 +1301,924 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) } - var stringLen uint64 + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Effect = DeviceTaintEffect(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeAdded", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TimeAdded == nil { + m.TimeAdded = &v1.Time{} + } + if err := m.TimeAdded.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRuleList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRuleList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, DeviceTaintRule{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRuleSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRuleSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DeviceSelector == nil { + m.DeviceSelector = &DeviceTaintSelector{} + } + if err := m.DeviceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Taint", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Taint.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRuleStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRuleStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRuleStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, v1.Condition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintSelector: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintSelector: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Driver = &s + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Pool = &s + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Device = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PoolStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PoolStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PoolStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Driver = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PoolName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PoolName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.NodeName = &s + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalDevices", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TotalDevices = &v + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocatedDevices", wireType) + } + var v int32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -834,29 +2228,17 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int32(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Key = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + m.AllocatedDevices = &v + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AvailableDevices", wireType) } - var stringLen uint64 + var v int32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -866,29 +2248,37 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int32(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated + m.AvailableDevices = &v + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnavailableDevices", wireType) } - if postIndex > l { - return io.ErrUnexpectedEOF + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } } - m.Value = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) + m.UnavailableDevices = &v + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceSliceCount", wireType) } - var stringLen uint64 + var v int32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -898,29 +2288,36 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int32(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated + m.ResourceSliceCount = &v + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) } - if postIndex > l { - return io.ErrUnexpectedEOF + m.Generation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Generation |= int64(b&0x7F) << shift + if b < 0x80 { + break + } } - m.Effect = DeviceTaintEffect(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: + case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TimeAdded", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValidationError", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -930,27 +2327,24 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.TimeAdded == nil { - m.TimeAdded = &v1.Time{} - } - if err := m.TimeAdded.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + s := string(dAtA[iNdEx:postIndex]) + m.ValidationError = &s iNdEx = postIndex default: iNdEx = preIndex @@ -973,7 +2367,7 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { +func (m *ResourcePoolStatusRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -996,10 +2390,10 @@ func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceTaintRule: wiretype end group for non-group") + return fmt.Errorf("proto: ResourcePoolStatusRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceTaintRule: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ResourcePoolStatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1097,6 +2491,9 @@ func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } + if m.Status == nil { + m.Status = &ResourcePoolStatusRequestStatus{} + } if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1122,7 +2519,7 @@ func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { +func (m *ResourcePoolStatusRequestList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1145,10 +2542,10 @@ func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceTaintRuleList: wiretype end group for non-group") + return fmt.Errorf("proto: ResourcePoolStatusRequestList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceTaintRuleList: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ResourcePoolStatusRequestList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1213,7 +2610,7 @@ func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Items = append(m.Items, DeviceTaintRule{}) + m.Items = append(m.Items, ResourcePoolStatusRequest{}) if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1239,7 +2636,7 @@ func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { +func (m *ResourcePoolStatusRequestSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1262,17 +2659,17 @@ func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceTaintRuleSpec: wiretype end group for non-group") + return fmt.Errorf("proto: ResourcePoolStatusRequestSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceTaintRuleSpec: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ResourcePoolStatusRequestSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceSelector", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -1282,33 +2679,29 @@ func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.DeviceSelector == nil { - m.DeviceSelector = &DeviceTaintSelector{} - } - if err := m.DeviceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Driver = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Taint", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field PoolName", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -1318,80 +2711,30 @@ func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Taint.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + s := string(dAtA[iNdEx:postIndex]) + m.PoolName = &s iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceTaintRuleStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceTaintRuleStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceTaintRuleStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) } - var msglen int + var v int32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -1401,26 +2744,12 @@ func (m *DeviceTaintRuleStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= int32(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Conditions = append(m.Conditions, v1.Condition{}) - if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex + m.Limit = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -1442,7 +2771,7 @@ func (m *DeviceTaintRuleStatus) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { +func (m *ResourcePoolStatusRequestStatus) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1465,17 +2794,17 @@ func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceTaintSelector: wiretype end group for non-group") + return fmt.Errorf("proto: ResourcePoolStatusRequestStatus: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceTaintSelector: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ResourcePoolStatusRequestStatus: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pools", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -1485,30 +2814,31 @@ func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - s := string(dAtA[iNdEx:postIndex]) - m.Driver = &s + m.Pools = append(m.Pools, PoolStatus{}) + if err := m.Pools[len(m.Pools)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -1518,30 +2848,31 @@ func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - s := string(dAtA[iNdEx:postIndex]) - m.Pool = &s + m.Conditions = append(m.Conditions, v1.Condition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PoolCount", wireType) } - var stringLen uint64 + var v int32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -1551,25 +2882,12 @@ func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int32(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := string(dAtA[iNdEx:postIndex]) - m.Device = &s - iNdEx = postIndex + m.PoolCount = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/resource/v1alpha3/generated.proto b/staging/src/k8s.io/api/resource/v1alpha3/generated.proto index 6414216db2967..d6ee310806836 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/generated.proto +++ b/staging/src/k8s.io/api/resource/v1alpha3/generated.proto @@ -120,11 +120,21 @@ message DeviceTaint { // Consumers must treat unknown effects like None. // // +required + // +k8s:required optional string effect = 3; - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; } @@ -140,6 +150,7 @@ message DeviceTaintRule { // Spec specifies the selector and one taint. // // Changing the spec automatically increments the metadata.generation number. + // +required optional DeviceTaintRuleSpec spec = 2; // Status provides information about what was requested in the spec. @@ -237,3 +248,210 @@ message DeviceTaintSelector { optional string device = 4; } +// PoolStatus contains status information for a single resource pool. +message PoolStatus { + // Driver is the DRA driver name for this pool. + // Must be a DNS subdomain (e.g., "gpu.example.com"). + // + // +required + // +k8s:required + // +k8s:format=k8s-long-name-caseless + optional string driver = 1; + + // PoolName is the name of the pool. + // Must be a valid resource pool name (DNS subdomains separated by "/"). + // + // +required + // +k8s:required + // +k8s:format=k8s-resource-pool-name + optional string poolName = 2; + + // Generation is the pool generation observed across all ResourceSlices + // in this pool. Only the latest generation is reported. During a generation + // rollout, if not all slices at the latest generation have been published, + // the pool is included with a validationError and device counts unset. + // + // +required + // +k8s:required + // +k8s:minimum=0 + optional int64 generation = 9; + + // ResourceSliceCount is the number of ResourceSlices that make up this pool. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=1 + optional int32 resourceSliceCount = 8; + + // TotalDevices is the total number of devices in the pool across all slices. + // A value of 0 means the pool has no devices. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + optional int32 totalDevices = 4; + + // AllocatedDevices is the number of devices currently allocated to claims. + // A value of 0 means no devices are allocated. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + optional int32 allocatedDevices = 5; + + // AvailableDevices is the number of devices available for allocation. + // This equals TotalDevices - AllocatedDevices - UnavailableDevices. + // A value of 0 means no devices are currently available. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + optional int32 availableDevices = 6; + + // UnavailableDevices is the number of devices that are not available + // due to taints or other conditions, but are not allocated. + // A value of 0 means all unallocated devices are available. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + optional int32 unavailableDevices = 7; + + // NodeName is the node this pool is associated with. + // When omitted, the pool is not associated with a specific node. + // Must be a valid DNS subdomain name (RFC1123). + // + // +optional + // +k8s:optional + // +k8s:format=k8s-long-name + optional string nodeName = 3; + + // ValidationError is set when the pool's data could not be fully + // validated (e.g., incomplete slice publication). When set, device + // count fields and ResourceSliceCount may be unset. + // + // +optional + // +k8s:optional + // +k8s:maxBytes=256 + optional string validationError = 10; +} + +// ResourcePoolStatusRequest triggers a one-time calculation of resource pool status +// based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. +// Users should delete and recreate requests to get updated information. +message ResourcePoolStatusRequest { + // Standard object metadata + // +required + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the filters for which pools to include in the status. + // The spec is immutable once created. + // + // +required + // +k8s:immutable + optional ResourcePoolStatusRequestSpec spec = 2; + + // Status is populated by the controller with the calculated pool status. + // When status is non-nil, the request is considered complete and the + // entire object becomes immutable. + // + // +optional + // +k8s:optional + optional ResourcePoolStatusRequestStatus status = 3; +} + +// ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests. +message ResourcePoolStatusRequestList { + // Standard list metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of ResourcePoolStatusRequests. + repeated ResourcePoolStatusRequest items = 2; +} + +// ResourcePoolStatusRequestSpec defines the filters for the pool status request. +message ResourcePoolStatusRequestSpec { + // Driver specifies the DRA driver name to filter pools. + // Only pools from ResourceSlices with this driver will be included. + // Must be a DNS subdomain (e.g., "gpu.example.com"). + // + // +required + // +k8s:required + // +k8s:format=k8s-long-name-caseless + optional string driver = 1; + + // PoolName optionally filters to a specific pool name. + // If not specified, all pools from the specified driver are included. + // When specified, must be a non-empty valid resource pool name + // (DNS subdomains separated by "/"). + // + // +optional + // +k8s:optional + // +k8s:format=k8s-resource-pool-name + optional string poolName = 2; + + // Limit optionally specifies the maximum number of pools to return in the status. + // If more pools match the filter criteria, the response will be truncated + // (i.e., len(status.pools) < status.poolCount). + // + // Default: 100 + // Minimum: 1 + // Maximum: 1000 + // + // +optional + // +k8s:optional + // +default=100 + // +k8s:minimum=1 + // +k8s:maximum=1000 + optional int32 limit = 3; +} + +// ResourcePoolStatusRequestStatus contains the calculated pool status information. +message ResourcePoolStatusRequestStatus { + // PoolCount is the total number of pools that matched the filter criteria, + // regardless of truncation. This helps users understand how many pools exist + // even when the response is truncated. A value of 0 means no pools matched + // the filter criteria. + // + // +required + // +k8s:required + // +k8s:minimum=0 + optional int32 poolCount = 6; + + // Pools contains the first `spec.limit` matching pools, sorted by driver + // then pool name. If `len(pools) < poolCount`, the list was truncated. + // When omitted, no pools matched the request filters. + // + // +optional + // +k8s:optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:maxItems=1000 + repeated PoolStatus pools = 2; + + // Conditions provide information about the state of the request. + // A condition with type=Complete or type=Failed will always be set + // when the status is populated. + // + // Known condition types: + // - "Complete": True when the request has been processed successfully + // - "Failed": True when the request could not be processed + // + // +optional + // +k8s:optional + // +listType=map + // +k8s:listType=map + // +listMapKey=type + // +k8s:listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + // +k8s:maxItems=10 + repeated .k8s.io.apimachinery.pkg.apis.meta.v1.Condition conditions = 3; +} + diff --git a/staging/src/k8s.io/api/resource/v1alpha3/generated.protomessage.pb.go b/staging/src/k8s.io/api/resource/v1alpha3/generated.protomessage.pb.go deleted file mode 100644 index aba6231f3d333..0000000000000 --- a/staging/src/k8s.io/api/resource/v1alpha3/generated.protomessage.pb.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha3 - -func (*CELDeviceSelector) ProtoMessage() {} - -func (*DeviceSelector) ProtoMessage() {} - -func (*DeviceTaint) ProtoMessage() {} - -func (*DeviceTaintRule) ProtoMessage() {} - -func (*DeviceTaintRuleList) ProtoMessage() {} - -func (*DeviceTaintRuleSpec) ProtoMessage() {} - -func (*DeviceTaintRuleStatus) ProtoMessage() {} - -func (*DeviceTaintSelector) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/resource/v1alpha3/register.go b/staging/src/k8s.io/api/resource/v1alpha3/register.go index dd79ec341e618..ab20bd05fb81b 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/register.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/register.go @@ -46,6 +46,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &DeviceTaintRule{}, &DeviceTaintRuleList{}, + &ResourcePoolStatusRequest{}, + &ResourcePoolStatusRequestList{}, ) // Add the watch version that applies diff --git a/staging/src/k8s.io/api/resource/v1alpha3/types.go b/staging/src/k8s.io/api/resource/v1alpha3/types.go index ba02edabaa622..1757c86150cb8 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/types.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/types.go @@ -140,6 +140,7 @@ type DeviceTaint struct { // Consumers must treat unknown effects like None. // // +required + // +k8s:required Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"` // ^^^^ @@ -155,9 +156,18 @@ type DeviceTaint struct { // which will enable adding new enums within a single release without // ratcheting. - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"` @@ -169,6 +179,7 @@ type DeviceTaint struct { } // +enum +// +k8s:enum type DeviceTaintEffect string const ( @@ -201,6 +212,7 @@ type DeviceTaintRule struct { // Spec specifies the selector and one taint. // // Changing the spec automatically increments the metadata.generation number. + // +required Spec DeviceTaintRuleSpec `json:"spec" protobuf:"bytes,2,name=spec"` // Status provides information about what was requested in the spec. @@ -332,3 +344,233 @@ type DeviceTaintRuleList struct { // Items is the list of DeviceTaintRules. Items []DeviceTaintRule `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 +// +k8s:supportsSubresource="/status" + +// ResourcePoolStatusRequest triggers a one-time calculation of resource pool status +// based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. +// Users should delete and recreate requests to get updated information. +type ResourcePoolStatusRequest struct { + metav1.TypeMeta `json:",inline"` + // Standard object metadata + // +required + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the filters for which pools to include in the status. + // The spec is immutable once created. + // + // +required + // +k8s:immutable + Spec ResourcePoolStatusRequestSpec `json:"spec" protobuf:"bytes,2,name=spec"` + + // Status is populated by the controller with the calculated pool status. + // When status is non-nil, the request is considered complete and the + // entire object becomes immutable. + // + // +optional + // +k8s:optional + Status *ResourcePoolStatusRequestStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// ResourcePoolStatusRequestSpec defines the filters for the pool status request. +type ResourcePoolStatusRequestSpec struct { + // Driver specifies the DRA driver name to filter pools. + // Only pools from ResourceSlices with this driver will be included. + // Must be a DNS subdomain (e.g., "gpu.example.com"). + // + // +required + // +k8s:required + // +k8s:format=k8s-long-name-caseless + Driver string `json:"driver" protobuf:"bytes,1,name=driver"` + + // PoolName optionally filters to a specific pool name. + // If not specified, all pools from the specified driver are included. + // When specified, must be a non-empty valid resource pool name + // (DNS subdomains separated by "/"). + // + // +optional + // +k8s:optional + // +k8s:format=k8s-resource-pool-name + PoolName *string `json:"poolName,omitempty" protobuf:"bytes,2,opt,name=poolName"` + + // Limit optionally specifies the maximum number of pools to return in the status. + // If more pools match the filter criteria, the response will be truncated + // (i.e., len(status.pools) < status.poolCount). + // + // Default: 100 + // Minimum: 1 + // Maximum: 1000 + // + // +optional + // +k8s:optional + // +default=100 + // +k8s:minimum=1 + // +k8s:maximum=1000 + Limit *int32 `json:"limit,omitempty" protobuf:"varint,3,opt,name=limit"` +} + +// ResourcePoolStatusRequestLimitDefault is the default value for spec.limit. +const ResourcePoolStatusRequestLimitDefault int32 = 100 + +// ResourcePoolStatusRequestLimitMax is the maximum allowed value for spec.limit. +const ResourcePoolStatusRequestLimitMax int32 = 1000 + +// ResourcePoolStatusRequestStatus contains the calculated pool status information. +type ResourcePoolStatusRequestStatus struct { + // PoolCount is the total number of pools that matched the filter criteria, + // regardless of truncation. This helps users understand how many pools exist + // even when the response is truncated. A value of 0 means no pools matched + // the filter criteria. + // + // +required + // +k8s:required + // +k8s:minimum=0 + PoolCount *int32 `json:"poolCount,omitempty" protobuf:"varint,6,opt,name=poolCount"` + + // Pools contains the first `spec.limit` matching pools, sorted by driver + // then pool name. If `len(pools) < poolCount`, the list was truncated. + // When omitted, no pools matched the request filters. + // + // +optional + // +k8s:optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:maxItems=1000 + Pools []PoolStatus `json:"pools,omitempty" protobuf:"bytes,2,rep,name=pools"` + + // Conditions provide information about the state of the request. + // A condition with type=Complete or type=Failed will always be set + // when the status is populated. + // + // Known condition types: + // - "Complete": True when the request has been processed successfully + // - "Failed": True when the request could not be processed + // + // +optional + // +k8s:optional + // +listType=map + // +k8s:listType=map + // +listMapKey=type + // +k8s:listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + // +k8s:maxItems=10 + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,3,rep,name=conditions"` +} + +// PoolStatus contains status information for a single resource pool. +type PoolStatus struct { + // Driver is the DRA driver name for this pool. + // Must be a DNS subdomain (e.g., "gpu.example.com"). + // + // +required + // +k8s:required + // +k8s:format=k8s-long-name-caseless + Driver string `json:"driver,omitempty" protobuf:"bytes,1,name=driver"` + + // PoolName is the name of the pool. + // Must be a valid resource pool name (DNS subdomains separated by "/"). + // + // +required + // +k8s:required + // +k8s:format=k8s-resource-pool-name + PoolName string `json:"poolName,omitempty" protobuf:"bytes,2,name=poolName"` + + // Generation is the pool generation observed across all ResourceSlices + // in this pool. Only the latest generation is reported. During a generation + // rollout, if not all slices at the latest generation have been published, + // the pool is included with a validationError and device counts unset. + // + // +required + // +k8s:required + // +k8s:minimum=0 + Generation int64 `json:"generation" protobuf:"varint,9,opt,name=generation"` + + // ResourceSliceCount is the number of ResourceSlices that make up this pool. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=1 + ResourceSliceCount *int32 `json:"resourceSliceCount,omitempty" protobuf:"varint,8,opt,name=resourceSliceCount"` + + // TotalDevices is the total number of devices in the pool across all slices. + // A value of 0 means the pool has no devices. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + TotalDevices *int32 `json:"totalDevices,omitempty" protobuf:"varint,4,opt,name=totalDevices"` + + // AllocatedDevices is the number of devices currently allocated to claims. + // A value of 0 means no devices are allocated. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + AllocatedDevices *int32 `json:"allocatedDevices,omitempty" protobuf:"varint,5,opt,name=allocatedDevices"` + + // AvailableDevices is the number of devices available for allocation. + // This equals TotalDevices - AllocatedDevices - UnavailableDevices. + // A value of 0 means no devices are currently available. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + AvailableDevices *int32 `json:"availableDevices,omitempty" protobuf:"varint,6,opt,name=availableDevices"` + + // UnavailableDevices is the number of devices that are not available + // due to taints or other conditions, but are not allocated. + // A value of 0 means all unallocated devices are available. + // May be unset when validationError is set. + // + // +optional + // +k8s:optional + // +k8s:minimum=0 + UnavailableDevices *int32 `json:"unavailableDevices,omitempty" protobuf:"varint,7,opt,name=unavailableDevices"` + + // NodeName is the node this pool is associated with. + // When omitted, the pool is not associated with a specific node. + // Must be a valid DNS subdomain name (RFC1123). + // + // +optional + // +k8s:optional + // +k8s:format=k8s-long-name + NodeName *string `json:"nodeName,omitempty" protobuf:"bytes,3,opt,name=nodeName"` + + // ValidationError is set when the pool's data could not be fully + // validated (e.g., incomplete slice publication). When set, device + // count fields and ResourceSliceCount may be unset. + // + // +optional + // +k8s:optional + // +k8s:maxBytes=256 + ValidationError *string `json:"validationError,omitempty" protobuf:"bytes,10,opt,name=validationError"` +} + +// ResourcePoolStatusRequestConditionComplete is the condition type for completed requests. +const ResourcePoolStatusRequestConditionComplete = "Complete" + +// ResourcePoolStatusRequestConditionFailed is the condition type for failed requests. +const ResourcePoolStatusRequestConditionFailed = "Failed" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests. +type ResourcePoolStatusRequestList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of ResourcePoolStatusRequests. + Items []ResourcePoolStatusRequest `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go b/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go index 30981bd7e600e..49ff642fea90d 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go @@ -50,7 +50,7 @@ var map_DeviceTaint = map[string]string{ "key": "The taint key to be applied to a device. Must be a label name.", "value": "The taint value corresponding to the taint key. Must be a label value.", "effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them.\n\nValid effects are None, NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here. More effects may get added in the future. Consumers must treat unknown effects like None.", - "timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + "timeAdded": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", } func (DeviceTaint) SwaggerDoc() map[string]string { @@ -108,4 +108,65 @@ func (DeviceTaintSelector) SwaggerDoc() map[string]string { return map_DeviceTaintSelector } +var map_PoolStatus = map[string]string{ + "": "PoolStatus contains status information for a single resource pool.", + "driver": "Driver is the DRA driver name for this pool. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + "poolName": "PoolName is the name of the pool. Must be a valid resource pool name (DNS subdomains separated by \"/\").", + "generation": "Generation is the pool generation observed across all ResourceSlices in this pool. Only the latest generation is reported. During a generation rollout, if not all slices at the latest generation have been published, the pool is included with a validationError and device counts unset.", + "resourceSliceCount": "ResourceSliceCount is the number of ResourceSlices that make up this pool. May be unset when validationError is set.", + "totalDevices": "TotalDevices is the total number of devices in the pool across all slices. A value of 0 means the pool has no devices. May be unset when validationError is set.", + "allocatedDevices": "AllocatedDevices is the number of devices currently allocated to claims. A value of 0 means no devices are allocated. May be unset when validationError is set.", + "availableDevices": "AvailableDevices is the number of devices available for allocation. This equals TotalDevices - AllocatedDevices - UnavailableDevices. A value of 0 means no devices are currently available. May be unset when validationError is set.", + "unavailableDevices": "UnavailableDevices is the number of devices that are not available due to taints or other conditions, but are not allocated. A value of 0 means all unallocated devices are available. May be unset when validationError is set.", + "nodeName": "NodeName is the node this pool is associated with. When omitted, the pool is not associated with a specific node. Must be a valid DNS subdomain name (RFC1123).", + "validationError": "ValidationError is set when the pool's data could not be fully validated (e.g., incomplete slice publication). When set, device count fields and ResourceSliceCount may be unset.", +} + +func (PoolStatus) SwaggerDoc() map[string]string { + return map_PoolStatus +} + +var map_ResourcePoolStatusRequest = map[string]string{ + "": "ResourcePoolStatusRequest triggers a one-time calculation of resource pool status based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. Users should delete and recreate requests to get updated information.", + "metadata": "Standard object metadata", + "spec": "Spec defines the filters for which pools to include in the status. The spec is immutable once created.", + "status": "Status is populated by the controller with the calculated pool status. When status is non-nil, the request is considered complete and the entire object becomes immutable.", +} + +func (ResourcePoolStatusRequest) SwaggerDoc() map[string]string { + return map_ResourcePoolStatusRequest +} + +var map_ResourcePoolStatusRequestList = map[string]string{ + "": "ResourcePoolStatusRequestList is a collection of ResourcePoolStatusRequests.", + "metadata": "Standard list metadata", + "items": "Items is the list of ResourcePoolStatusRequests.", +} + +func (ResourcePoolStatusRequestList) SwaggerDoc() map[string]string { + return map_ResourcePoolStatusRequestList +} + +var map_ResourcePoolStatusRequestSpec = map[string]string{ + "": "ResourcePoolStatusRequestSpec defines the filters for the pool status request.", + "driver": "Driver specifies the DRA driver name to filter pools. Only pools from ResourceSlices with this driver will be included. Must be a DNS subdomain (e.g., \"gpu.example.com\").", + "poolName": "PoolName optionally filters to a specific pool name. If not specified, all pools from the specified driver are included. When specified, must be a non-empty valid resource pool name (DNS subdomains separated by \"/\").", + "limit": "Limit optionally specifies the maximum number of pools to return in the status. If more pools match the filter criteria, the response will be truncated (i.e., len(status.pools) < status.poolCount).\n\nDefault: 100 Minimum: 1 Maximum: 1000", +} + +func (ResourcePoolStatusRequestSpec) SwaggerDoc() map[string]string { + return map_ResourcePoolStatusRequestSpec +} + +var map_ResourcePoolStatusRequestStatus = map[string]string{ + "": "ResourcePoolStatusRequestStatus contains the calculated pool status information.", + "poolCount": "PoolCount is the total number of pools that matched the filter criteria, regardless of truncation. This helps users understand how many pools exist even when the response is truncated. A value of 0 means no pools matched the filter criteria.", + "pools": "Pools contains the first `spec.limit` matching pools, sorted by driver then pool name. If `len(pools) < poolCount`, the list was truncated. When omitted, no pools matched the request filters.", + "conditions": "Conditions provide information about the state of the request. A condition with type=Complete or type=Failed will always be set when the status is populated.\n\nKnown condition types: - \"Complete\": True when the request has been processed successfully - \"Failed\": True when the request could not be processed", +} + +func (ResourcePoolStatusRequestStatus) SwaggerDoc() map[string]string { + return map_ResourcePoolStatusRequestStatus +} + // AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go index 6813ab043c6df..32ec2ad8e2e8b 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go @@ -219,3 +219,180 @@ func (in *DeviceTaintSelector) DeepCopy() *DeviceTaintSelector { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PoolStatus) DeepCopyInto(out *PoolStatus) { + *out = *in + if in.ResourceSliceCount != nil { + in, out := &in.ResourceSliceCount, &out.ResourceSliceCount + *out = new(int32) + **out = **in + } + if in.TotalDevices != nil { + in, out := &in.TotalDevices, &out.TotalDevices + *out = new(int32) + **out = **in + } + if in.AllocatedDevices != nil { + in, out := &in.AllocatedDevices, &out.AllocatedDevices + *out = new(int32) + **out = **in + } + if in.AvailableDevices != nil { + in, out := &in.AvailableDevices, &out.AvailableDevices + *out = new(int32) + **out = **in + } + if in.UnavailableDevices != nil { + in, out := &in.UnavailableDevices, &out.UnavailableDevices + *out = new(int32) + **out = **in + } + if in.NodeName != nil { + in, out := &in.NodeName, &out.NodeName + *out = new(string) + **out = **in + } + if in.ValidationError != nil { + in, out := &in.ValidationError, &out.ValidationError + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PoolStatus. +func (in *PoolStatus) DeepCopy() *PoolStatus { + if in == nil { + return nil + } + out := new(PoolStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequest) DeepCopyInto(out *ResourcePoolStatusRequest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(ResourcePoolStatusRequestStatus) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequest. +func (in *ResourcePoolStatusRequest) DeepCopy() *ResourcePoolStatusRequest { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourcePoolStatusRequest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequestList) DeepCopyInto(out *ResourcePoolStatusRequestList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ResourcePoolStatusRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequestList. +func (in *ResourcePoolStatusRequestList) DeepCopy() *ResourcePoolStatusRequestList { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequestList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourcePoolStatusRequestList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequestSpec) DeepCopyInto(out *ResourcePoolStatusRequestSpec) { + *out = *in + if in.PoolName != nil { + in, out := &in.PoolName, &out.PoolName + *out = new(string) + **out = **in + } + if in.Limit != nil { + in, out := &in.Limit, &out.Limit + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequestSpec. +func (in *ResourcePoolStatusRequestSpec) DeepCopy() *ResourcePoolStatusRequestSpec { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequestSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePoolStatusRequestStatus) DeepCopyInto(out *ResourcePoolStatusRequestStatus) { + *out = *in + if in.PoolCount != nil { + in, out := &in.PoolCount, &out.PoolCount + *out = new(int32) + **out = **in + } + if in.Pools != nil { + in, out := &in.Pools, &out.Pools + *out = make([]PoolStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePoolStatusRequestStatus. +func (in *ResourcePoolStatusRequestStatus) DeepCopy() *ResourcePoolStatusRequestStatus { + if in == nil { + return nil + } + out := new(ResourcePoolStatusRequestStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.model_name.go b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.model_name.go index 1c1672b4d61db..3ef712c223cb7 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.model_name.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.model_name.go @@ -60,3 +60,28 @@ func (in DeviceTaintRuleStatus) OpenAPIModelName() string { func (in DeviceTaintSelector) OpenAPIModelName() string { return "io.k8s.api.resource.v1alpha3.DeviceTaintSelector" } + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PoolStatus) OpenAPIModelName() string { + return "io.k8s.api.resource.v1alpha3.PoolStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ResourcePoolStatusRequest) OpenAPIModelName() string { + return "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ResourcePoolStatusRequestList) OpenAPIModelName() string { + return "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ResourcePoolStatusRequestSpec) OpenAPIModelName() string { + return "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ResourcePoolStatusRequestStatus) OpenAPIModelName() string { + return "io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus" +} diff --git a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.prerelease-lifecycle.go index 08e8b6040f389..5b74f407360be 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.prerelease-lifecycle.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.prerelease-lifecycle.go @@ -56,3 +56,39 @@ func (in *DeviceTaintRuleList) APILifecycleDeprecated() (major, minor int) { func (in *DeviceTaintRuleList) APILifecycleRemoved() (major, minor int) { return 1, 39 } + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *ResourcePoolStatusRequest) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *ResourcePoolStatusRequest) APILifecycleDeprecated() (major, minor int) { + return 1, 39 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *ResourcePoolStatusRequest) APILifecycleRemoved() (major, minor int) { + return 1, 42 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *ResourcePoolStatusRequestList) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *ResourcePoolStatusRequestList) APILifecycleDeprecated() (major, minor int) { + return 1, 39 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *ResourcePoolStatusRequestList) APILifecycleRemoved() (major, minor int) { + return 1, 42 +} diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go b/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go index 1ac7de8a8fcf7..4170f7780269f 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go @@ -25,6 +25,7 @@ import ( io "io" "sort" + k8s_io_api_core_v1 "k8s.io/api/core/v1" v11 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -97,6 +98,8 @@ func (m *DeviceToleration) Reset() { *m = DeviceToleration{} } func (m *NetworkDeviceData) Reset() { *m = NetworkDeviceData{} } +func (m *NodeAllocatableResourceMapping) Reset() { *m = NodeAllocatableResourceMapping{} } + func (m *OpaqueDeviceConfiguration) Reset() { *m = OpaqueDeviceConfiguration{} } func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } @@ -283,6 +286,35 @@ func (m *BasicDevice) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.NodeAllocatableResourceMappings) > 0 { + keysForNodeAllocatableResourceMappings := make([]string, 0, len(m.NodeAllocatableResourceMappings)) + for k := range m.NodeAllocatableResourceMappings { + keysForNodeAllocatableResourceMappings = append(keysForNodeAllocatableResourceMappings, string(k)) + } + sort.Strings(keysForNodeAllocatableResourceMappings) + for iNdEx := len(keysForNodeAllocatableResourceMappings) - 1; iNdEx >= 0; iNdEx-- { + v := m.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(keysForNodeAllocatableResourceMappings[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForNodeAllocatableResourceMappings[iNdEx]) + copy(dAtA[i:], keysForNodeAllocatableResourceMappings[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForNodeAllocatableResourceMappings[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x62 + } + } if m.AllowMultipleAllocations != nil { i-- if *m.AllowMultipleAllocations { @@ -887,6 +919,43 @@ func (m *DeviceAttribute) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.VersionValues) > 0 { + for iNdEx := len(m.VersionValues) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.VersionValues[iNdEx]) + copy(dAtA[i:], m.VersionValues[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.VersionValues[iNdEx]))) + i-- + dAtA[i] = 0x4a + } + } + if len(m.StringValues) > 0 { + for iNdEx := len(m.StringValues) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.StringValues[iNdEx]) + copy(dAtA[i:], m.StringValues[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StringValues[iNdEx]))) + i-- + dAtA[i] = 0x42 + } + } + if len(m.BoolValues) > 0 { + for iNdEx := len(m.BoolValues) - 1; iNdEx >= 0; iNdEx-- { + i-- + if m.BoolValues[iNdEx] { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + } + if len(m.IntValues) > 0 { + for iNdEx := len(m.IntValues) - 1; iNdEx >= 0; iNdEx-- { + i = encodeVarintGenerated(dAtA, i, uint64(m.IntValues[iNdEx])) + i-- + dAtA[i] = 0x30 + } + } if m.VersionValue != nil { i -= len(*m.VersionValue) copy(dAtA[i:], *m.VersionValue) @@ -1872,6 +1941,48 @@ func (m *NetworkDeviceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *NodeAllocatableResourceMapping) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NodeAllocatableResourceMapping) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *NodeAllocatableResourceMapping) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AllocationMultiplier != nil { + { + size, err := m.AllocationMultiplier.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.CapacityKey != nil { + i -= len(*m.CapacityKey) + copy(dAtA[i:], *m.CapacityKey) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.CapacityKey))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2635,6 +2746,15 @@ func (m *BasicDevice) Size() (n int) { if m.AllowMultipleAllocations != nil { n += 2 } + if len(m.NodeAllocatableResourceMappings) > 0 { + for k, v := range m.NodeAllocatableResourceMappings { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } return n } @@ -2817,6 +2937,26 @@ func (m *DeviceAttribute) Size() (n int) { l = len(*m.VersionValue) n += 1 + l + sovGenerated(uint64(l)) } + if len(m.IntValues) > 0 { + for _, e := range m.IntValues { + n += 1 + sovGenerated(uint64(e)) + } + } + if len(m.BoolValues) > 0 { + n += 2 * len(m.BoolValues) + } + if len(m.StringValues) > 0 { + for _, s := range m.StringValues { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.VersionValues) > 0 { + for _, s := range m.VersionValues { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3196,6 +3336,23 @@ func (m *NetworkDeviceData) Size() (n int) { return n } +func (m *NodeAllocatableResourceMapping) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CapacityKey != nil { + l = len(*m.CapacityKey) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.AllocationMultiplier != nil { + l = m.AllocationMultiplier.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *OpaqueDeviceConfiguration) Size() (n int) { if m == nil { return 0 @@ -3488,6 +3645,16 @@ func (this *BasicDevice) String() string { mapStringForCapacity += fmt.Sprintf("%v: %v,", k, this.Capacity[QualifiedName(k)]) } mapStringForCapacity += "}" + keysForNodeAllocatableResourceMappings := make([]string, 0, len(this.NodeAllocatableResourceMappings)) + for k := range this.NodeAllocatableResourceMappings { + keysForNodeAllocatableResourceMappings = append(keysForNodeAllocatableResourceMappings, string(k)) + } + sort.Strings(keysForNodeAllocatableResourceMappings) + mapStringForNodeAllocatableResourceMappings := "map[k8s_io_api_core_v1.ResourceName]NodeAllocatableResourceMapping{" + for _, k := range keysForNodeAllocatableResourceMappings { + mapStringForNodeAllocatableResourceMappings += fmt.Sprintf("%v: %v,", k, this.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(k)]) + } + mapStringForNodeAllocatableResourceMappings += "}" s := strings.Join([]string{`&BasicDevice{`, `Attributes:` + mapStringForAttributes + `,`, `Capacity:` + mapStringForCapacity + `,`, @@ -3500,6 +3667,7 @@ func (this *BasicDevice) String() string { `BindingConditions:` + fmt.Sprintf("%v", this.BindingConditions) + `,`, `BindingFailureConditions:` + fmt.Sprintf("%v", this.BindingFailureConditions) + `,`, `AllowMultipleAllocations:` + valueToStringGenerated(this.AllowMultipleAllocations) + `,`, + `NodeAllocatableResourceMappings:` + mapStringForNodeAllocatableResourceMappings + `,`, `}`, }, "") return s @@ -3647,6 +3815,10 @@ func (this *DeviceAttribute) String() string { `BoolValue:` + valueToStringGenerated(this.BoolValue) + `,`, `StringValue:` + valueToStringGenerated(this.StringValue) + `,`, `VersionValue:` + valueToStringGenerated(this.VersionValue) + `,`, + `IntValues:` + fmt.Sprintf("%v", this.IntValues) + `,`, + `BoolValues:` + fmt.Sprintf("%v", this.BoolValues) + `,`, + `StringValues:` + fmt.Sprintf("%v", this.StringValues) + `,`, + `VersionValues:` + fmt.Sprintf("%v", this.VersionValues) + `,`, `}`, }, "") return s @@ -3931,6 +4103,17 @@ func (this *NetworkDeviceData) String() string { }, "") return s } +func (this *NodeAllocatableResourceMapping) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&NodeAllocatableResourceMapping{`, + `CapacityKey:` + valueToStringGenerated(this.CapacityKey) + `,`, + `AllocationMultiplier:` + strings.Replace(fmt.Sprintf("%v", this.AllocationMultiplier), "Quantity", "resource.Quantity", 1) + `,`, + `}`, + }, "") + return s +} func (this *OpaqueDeviceConfiguration) String() string { if this == nil { return "nil" @@ -5118,6 +5301,135 @@ func (m *BasicDevice) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AllowMultipleAllocations = &b + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeAllocatableResourceMappings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NodeAllocatableResourceMappings == nil { + m.NodeAllocatableResourceMappings = make(map[k8s_io_api_core_v1.ResourceName]NodeAllocatableResourceMapping) + } + var mapkey k8s_io_api_core_v1.ResourceName + mapvalue := &NodeAllocatableResourceMapping{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &NodeAllocatableResourceMapping{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(mapkey)] = *mapvalue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6527,6 +6839,216 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.VersionValue = &s iNdEx = postIndex + case 6: + if wireType == 0 { + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IntValues = append(m.IntValues, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.IntValues) == 0 { + m.IntValues = make([]int64, 0, elementCount) + } + for iNdEx < postIndex { + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IntValues = append(m.IntValues, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field IntValues", wireType) + } + case 7: + if wireType == 0 { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BoolValues = append(m.BoolValues, bool(v != 0)) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen + if elementCount != 0 && len(m.BoolValues) == 0 { + m.BoolValues = make([]bool, 0, elementCount) + } + for iNdEx < postIndex { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BoolValues = append(m.BoolValues, bool(v != 0)) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field BoolValues", wireType) + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StringValues", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StringValues = append(m.StringValues, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionValues", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VersionValues = append(m.VersionValues, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -9510,6 +10032,125 @@ func (m *NetworkDeviceData) Unmarshal(dAtA []byte) error { } return nil } +func (m *NodeAllocatableResourceMapping) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeAllocatableResourceMapping: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeAllocatableResourceMapping: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CapacityKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := QualifiedName(dAtA[iNdEx:postIndex]) + m.CapacityKey = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMultiplier", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AllocationMultiplier == nil { + m.AllocationMultiplier = &resource.Quantity{} + } + if err := m.AllocationMultiplier.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *OpaqueDeviceConfiguration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.proto b/staging/src/k8s.io/api/resource/v1beta1/generated.proto index fe2397a8795c8..79a51ee2c8be5 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.proto @@ -65,8 +65,8 @@ message AllocatedDeviceStatus { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid optional string shareID = 7; // Conditions contains the latest observation of the device's state. @@ -90,7 +90,7 @@ message AllocatedDeviceStatus { // NetworkData contains network-related information specific to the device. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional NetworkDeviceData networkData = 6; } @@ -110,7 +110,7 @@ message AllocationResult { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -126,6 +126,7 @@ message BasicDevice { // The maximum number of attributes and capacities combined is 32. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional map attributes = 1; // Capacity defines the set of capacities for this device. @@ -146,13 +147,13 @@ message BasicDevice { // device is 2. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=counterSet + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=counterSet // +featureGate=DRAPartitionableDevices - // +k8s:maxItems=2 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=2 repeated DeviceCounterConsumption consumesCounters = 3; // NodeName identifies the node where the device is available. @@ -192,12 +193,13 @@ message BasicDevice { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceTaint taints = 7; // BindsToNode indicates if the usage of an allocation involving this device @@ -205,7 +207,7 @@ message BasicDevice { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -221,14 +223,14 @@ message BasicDevice { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingConditions = 9; // BindingFailureConditions defines the conditions for binding failure. @@ -239,14 +241,14 @@ message BasicDevice { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingFailureConditions = 10; // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -257,6 +259,21 @@ message BasicDevice { // +optional // +featureGate=DRAConsumableCapacity optional bool allowMultipleAllocations = 11; + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + map nodeAllocatableResourceMappings = 12; } // CELDeviceSelector contains a CEL expression for selecting a device. @@ -310,6 +327,14 @@ message CELDeviceSelector { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -448,8 +473,8 @@ message CounterSet { // It must be a DNS label. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name optional string name = 1; // Counters defines the set of counters for this CounterSet @@ -458,6 +483,8 @@ message CounterSet { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name map counters = 2; } @@ -474,6 +501,7 @@ message Device { // // +optional // +oneOf=deviceType + // +k8s:alpha(since: "1.36")=+k8s:optional optional BasicDevice basic = 2; } @@ -484,7 +512,7 @@ message DeviceAllocationConfiguration { // or from a claim. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string source = 1; // Requests lists the names of requests where the configuration applies. @@ -496,10 +524,10 @@ message DeviceAllocationConfiguration { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 2; optional DeviceConfiguration deviceConfiguration = 3; @@ -511,8 +539,8 @@ message DeviceAllocationResult { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceRequestAllocationResult results = 1; // This field is a combination of all the claim and class configuration parameters. @@ -525,8 +553,8 @@ message DeviceAllocationResult { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=64 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=64 repeated DeviceAllocationConfiguration config = 2; } @@ -535,31 +563,75 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional string version = 5; + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated int64 ints = 6; + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated bool bools = 7; + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated string strings = 8; + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated string versions = 9; } // DeviceCapacity describes a quantity associated with a device. @@ -595,11 +667,11 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceRequest requests = 1; // These constraints must be satisfied by the set of devices that get @@ -607,8 +679,8 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceConstraint constraints = 2; // This field holds configuration for multiple potential drivers which @@ -617,8 +689,8 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceClaimConfiguration config = 3; } @@ -633,10 +705,10 @@ message DeviceClaimConfiguration { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 1; optional DeviceConfiguration deviceConfiguration = 2; @@ -652,8 +724,8 @@ message DeviceClaimConfiguration { message DeviceClass { // Standard object metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // Spec defines what can be allocated and how to configure it. @@ -689,8 +761,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 1; // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -701,8 +773,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceClassConfiguration config = 2; // ExtendedResourceName is the extended resource name for the devices of this class. @@ -714,11 +786,11 @@ message DeviceClassSpec { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. // +optional // +featureGate=DRAExtendedResource - // +k8s:optional - // +k8s:format=k8s-extended-resource-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-extended-resource-name optional string extendedResourceName = 4; } @@ -730,7 +802,7 @@ message DeviceConfiguration { // // +optional // +oneOf=ConfigurationType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional OpaqueDeviceConfiguration opaque = 1; } @@ -748,10 +820,10 @@ message DeviceConstraint { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 1; // MatchAttribute requires that all devices in question have this @@ -765,17 +837,27 @@ message DeviceConstraint { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. // // +optional // +oneOf=ConstraintType - // +k8s:optional - // +k8s:format=k8s-resource-fully-qualified-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-fully-qualified-name optional string matchAttribute = 2; // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -797,8 +879,8 @@ message DeviceCounterConsumption { // counters defined will be consumed. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name optional string counterSet = 1; // Counters defines the counters that will be consumed by the device. @@ -806,6 +888,8 @@ message DeviceCounterConsumption { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name map counters = 2; } @@ -851,8 +935,8 @@ message DeviceRequest { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 3; // AllocationMode and its related fields define how devices are allocated @@ -878,7 +962,7 @@ message DeviceRequest { // requests with unknown modes. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string allocationMode = 4; // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -928,11 +1012,11 @@ message DeviceRequest { // +oneOf=deviceRequestType // +listType=atomic // +featureGate=DRAPrioritizedList - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 repeated DeviceSubRequest firstAvailable = 7; // If specified, the request's tolerations. @@ -953,12 +1037,13 @@ message DeviceRequest { // This field can only be set when deviceClassName is set and no subrequests // are specified in the firstAvailable list. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 8; // Capacity define resource requirements against each capacity. @@ -1000,8 +1085,9 @@ message DeviceRequestAllocationResult { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:format=k8s-long-name-caseless - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 optional string driver = 2; // This name together with the driver name and the device name field @@ -1011,8 +1097,8 @@ message DeviceRequestAllocationResult { // DNS sub-domains separated by slashes. // // +required - // +k8s:required - // +k8s:format=k8s-resource-pool-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-pool-name optional string pool = 3; // Device references one device instance via its name in the driver's @@ -1038,40 +1124,40 @@ message DeviceRequestAllocationResult { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints - // +k8s:optional - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 repeated DeviceToleration tolerations = 6; // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingConditions = 7; // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingFailureConditions = 8; // ShareID uniquely identifies an individual allocation share of the device, @@ -1081,8 +1167,8 @@ message DeviceRequestAllocationResult { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid optional string shareID = 9; // ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. @@ -1140,8 +1226,8 @@ message DeviceSubRequest { // to reference. // // +required - // +k8s:required - // +k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -1151,7 +1237,8 @@ message DeviceSubRequest { // // +optional // +listType=atomic - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceSelector selectors = 3; // AllocationMode and its related fields define how devices are allocated @@ -1173,6 +1260,7 @@ message DeviceSubRequest { // requests with unknown modes. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string allocationMode = 4; // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1197,12 +1285,13 @@ message DeviceSubRequest { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 7; // Capacity define resource requirements against each capacity. @@ -1249,12 +1338,21 @@ message DeviceTaint { // Consumers must treat unknown effects like None. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string effect = 3; - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; } @@ -1267,8 +1365,8 @@ message DeviceToleration { // Must be a label name. // // +optional - // +k8s:optional - // +k8s:format=k8s-label-key + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-label-key optional string key = 1; // Operator represents a key's relationship to the value. @@ -1278,6 +1376,7 @@ message DeviceToleration { // // +optional // +default="Equal" + // +k8s:alpha(since: "1.36")=+k8s:optional optional string operator = 2; // Value is the taint value the toleration matches to. @@ -1291,6 +1390,7 @@ message DeviceToleration { // When specified, allowed values are NoSchedule and NoExecute. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string effect = 4; // TolerationSeconds represents the period of time the toleration (which must be @@ -1312,11 +1412,11 @@ message NetworkDeviceData { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=256 optional string interfaceName = 1; // IPs lists the network addresses assigned to the device's network interface. @@ -1329,22 +1429,60 @@ message NetworkDeviceData { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 repeated string ips = 2; // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=128 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=128 optional string hardwareAddress = 3; } +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +message NodeAllocatableResourceMapping { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + optional string capacityKey = 1; + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + optional .k8s.io.apimachinery.pkg.api.resource.Quantity allocationMultiplier = 2; +} + // OpaqueDeviceConfiguration contains configuration parameters for a driver // in a format defined by the driver vendor. message OpaqueDeviceConfiguration { @@ -1358,8 +1496,9 @@ message OpaqueDeviceConfiguration { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:required - // +k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless optional string driver = 1; // Parameters can contain arbitrary data. It is the responsibility of @@ -1388,7 +1527,7 @@ message ResourceClaim { // Spec describes what is being requested and how to configure it. // The spec is immutable. - // +k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:immutable optional ResourceClaimSpec spec = 2; // Status describes whether the claim is ready to use and what has been allocated. @@ -1443,8 +1582,8 @@ message ResourceClaimStatus { // Allocation is set once the claim has been allocated successfully. // // +optional - // +k8s:optional - // +k8s:update=NoModify + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:update=NoModify optional AllocationResult allocation = 1; // ReservedFor indicates which entities are currently allowed to use @@ -1472,10 +1611,10 @@ message ResourceClaimStatus { // +listMapKey=uid // +patchStrategy=merge // +patchMergeKey=uid - // +k8s:optional - // +k8s:listType=map - // +k8s:listMapKey=uid - // +k8s:maxItems=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=uid + // +k8s:alpha(since: "1.36")=+k8s:maxItems=256 repeated ResourceClaimConsumerReference reservedFor = 2; // Devices contains the status of each device allocated for this @@ -1483,18 +1622,18 @@ message ResourceClaimStatus { // information. Entries are owned by their respective drivers. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=map // +listMapKey=driver // +listMapKey=device // +listMapKey=pool // +listMapKey=shareID // +featureGate=DRAResourceClaimDeviceStatus - // +k8s:listType=map - // +k8s:listMapKey=driver - // +k8s:listMapKey=device - // +k8s:listMapKey=pool - // +k8s:listMapKey=shareID + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=driver + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=device + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=pool + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=shareID repeated AllocatedDeviceStatus devices = 4; } @@ -1608,6 +1747,7 @@ message ResourceSlice { // Contains the information published by the driver. // // Changing the spec automatically increments the metadata.generation number. + // +required optional ResourceSliceSpec spec = 2; } @@ -1682,7 +1822,7 @@ message ResourceSliceSpec { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +zeroOrOneOf=ResourceSliceType repeated Device devices = 6; @@ -1708,14 +1848,14 @@ message ResourceSliceSpec { // The maximum number of counter sets is 8. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name // +featureGate=DRAPartitionableDevices // +zeroOrOneOf=ResourceSliceType - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 repeated CounterSet sharedCounters = 8; } diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/resource/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index e47160fb83872..0000000000000 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,108 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*AllocatedDeviceStatus) ProtoMessage() {} - -func (*AllocationResult) ProtoMessage() {} - -func (*BasicDevice) ProtoMessage() {} - -func (*CELDeviceSelector) ProtoMessage() {} - -func (*CapacityRequestPolicy) ProtoMessage() {} - -func (*CapacityRequestPolicyRange) ProtoMessage() {} - -func (*CapacityRequirements) ProtoMessage() {} - -func (*Counter) ProtoMessage() {} - -func (*CounterSet) ProtoMessage() {} - -func (*Device) ProtoMessage() {} - -func (*DeviceAllocationConfiguration) ProtoMessage() {} - -func (*DeviceAllocationResult) ProtoMessage() {} - -func (*DeviceAttribute) ProtoMessage() {} - -func (*DeviceCapacity) ProtoMessage() {} - -func (*DeviceClaim) ProtoMessage() {} - -func (*DeviceClaimConfiguration) ProtoMessage() {} - -func (*DeviceClass) ProtoMessage() {} - -func (*DeviceClassConfiguration) ProtoMessage() {} - -func (*DeviceClassList) ProtoMessage() {} - -func (*DeviceClassSpec) ProtoMessage() {} - -func (*DeviceConfiguration) ProtoMessage() {} - -func (*DeviceConstraint) ProtoMessage() {} - -func (*DeviceCounterConsumption) ProtoMessage() {} - -func (*DeviceRequest) ProtoMessage() {} - -func (*DeviceRequestAllocationResult) ProtoMessage() {} - -func (*DeviceSelector) ProtoMessage() {} - -func (*DeviceSubRequest) ProtoMessage() {} - -func (*DeviceTaint) ProtoMessage() {} - -func (*DeviceToleration) ProtoMessage() {} - -func (*NetworkDeviceData) ProtoMessage() {} - -func (*OpaqueDeviceConfiguration) ProtoMessage() {} - -func (*ResourceClaim) ProtoMessage() {} - -func (*ResourceClaimConsumerReference) ProtoMessage() {} - -func (*ResourceClaimList) ProtoMessage() {} - -func (*ResourceClaimSpec) ProtoMessage() {} - -func (*ResourceClaimStatus) ProtoMessage() {} - -func (*ResourceClaimTemplate) ProtoMessage() {} - -func (*ResourceClaimTemplateList) ProtoMessage() {} - -func (*ResourceClaimTemplateSpec) ProtoMessage() {} - -func (*ResourcePool) ProtoMessage() {} - -func (*ResourceSlice) ProtoMessage() {} - -func (*ResourceSliceList) ProtoMessage() {} - -func (*ResourceSliceSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go index c55e2e92c931a..0d35ac9895ea6 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types.go @@ -35,6 +35,10 @@ const ( // special ResourceClaim. Its single valid value is "true". // This is used only inside the scheduler. ExtendedResourceClaimAnnotation = "resource.kubernetes.io/extended-resource-claim" + // PodResourceClaimAnnotation is the annotation set on template-generated + // ResourceClaims by the ResourceClaim controller. Its value is the + // pod.spec.resourceClaims[].name for which the claim was generated. + PodResourceClaimAnnotation = "resource.kubernetes.io/pod-claim-name" // Resource device class prefix is for generating implicit extended resource // name for a device class when its ExtendedResourceName field is not // specified. The generated name is this prefix + the device class name. @@ -82,6 +86,7 @@ type ResourceSlice struct { // Contains the information published by the driver. // // Changing the spec automatically increments the metadata.generation number. + // +required Spec ResourceSliceSpec `json:"spec" protobuf:"bytes,2,name=spec"` } @@ -155,7 +160,7 @@ type ResourceSliceSpec struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +zeroOrOneOf=ResourceSliceType Devices []Device `json:"devices,omitempty" protobuf:"bytes,6,name=devices"` @@ -181,14 +186,14 @@ type ResourceSliceSpec struct { // The maximum number of counter sets is 8. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name // +featureGate=DRAPartitionableDevices // +zeroOrOneOf=ResourceSliceType - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 SharedCounters []CounterSet `json:"sharedCounters,omitempty" protobuf:"bytes,8,name=sharedCounters"` } @@ -205,8 +210,8 @@ type CounterSet struct { // It must be a DNS label. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name Name string `json:"name" protobuf:"bytes,1,name=name"` // Counters defines the set of counters for this CounterSet @@ -215,6 +220,8 @@ type CounterSet struct { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"` } @@ -269,7 +276,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 @@ -304,6 +318,7 @@ type Device struct { // // +optional // +oneOf=deviceType + // +k8s:alpha(since: "1.36")=+k8s:optional Basic *BasicDevice `json:"basic,omitempty" protobuf:"bytes,2,opt,name=basic"` } @@ -315,6 +330,7 @@ type BasicDevice struct { // The maximum number of attributes and capacities combined is 32. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Attributes map[QualifiedName]DeviceAttribute `json:"attributes,omitempty" protobuf:"bytes,1,rep,name=attributes"` // Capacity defines the set of capacities for this device. @@ -335,13 +351,13 @@ type BasicDevice struct { // device is 2. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=counterSet + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=counterSet // +featureGate=DRAPartitionableDevices - // +k8s:maxItems=2 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=2 ConsumesCounters []DeviceCounterConsumption `json:"consumesCounters,omitempty" protobuf:"bytes,3,rep,name=consumesCounters"` // NodeName identifies the node where the device is available. @@ -381,12 +397,13 @@ type BasicDevice struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Taints []DeviceTaint `json:"taints,omitempty" protobuf:"bytes,7,rep,name=taints"` // BindsToNode indicates if the usage of an allocation involving this device @@ -394,7 +411,7 @@ type BasicDevice struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -410,14 +427,14 @@ type BasicDevice struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingConditions []string `json:"bindingConditions,omitempty" protobuf:"bytes,9,rep,name=bindingConditions"` // BindingFailureConditions defines the conditions for binding failure. @@ -428,14 +445,14 @@ type BasicDevice struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingFailureConditions []string `json:"bindingFailureConditions,omitempty" protobuf:"bytes,10,rep,name=bindingFailureConditions"` // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -446,6 +463,59 @@ type BasicDevice struct { // +optional // +featureGate=DRAConsumableCapacity AllowMultipleAllocations *bool `json:"allowMultipleAllocations,omitempty" protobuf:"bytes,11,opt,name=allowMultipleAllocations"` + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + NodeAllocatableResourceMappings map[v1.ResourceName]NodeAllocatableResourceMapping `json:"nodeAllocatableResourceMappings,omitempty" protobuf:"bytes,12,opt,name=nodeAllocatableResourceMappings"` +} + +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +type NodeAllocatableResourceMapping struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + CapacityKey *QualifiedName `json:"capacityKey,omitempty" protobuf:"bytes,1,opt,name=capacityKey"` + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + AllocationMultiplier *resource.Quantity `json:"allocationMultiplier,omitempty" protobuf:"bytes,2,opt,name=allocationMultiplier"` } // DeviceCounterConsumption defines a set of counters that @@ -455,8 +525,8 @@ type DeviceCounterConsumption struct { // counters defined will be consumed. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name CounterSet string `json:"counterSet" protobuf:"bytes,1,opt,name=counterSet"` // Counters defines the counters that will be consumed by the device. @@ -464,6 +534,8 @@ type DeviceCounterConsumption struct { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"` } @@ -576,6 +648,10 @@ type CapacityRequestPolicyRange struct { // Limit for the sum of the number of entries in both attributes and capacity. const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32 +// Limit per device for the total number of string, version, bool or int values +// in list and non-list attributes. +const ResourceSliceMaxAttributeValuesPerDevice = 48 + // QualifiedName is the name of a device attribute or capacity. // // Attributes and capacities are defined either by the owner of the specific @@ -615,31 +691,75 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + IntValues []int64 `json:"ints,omitempty" protobuf:"varint,6,opt,name=ints"` + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + BoolValues []bool `json:"bools,omitempty" protobuf:"varint,7,opt,name=bools"` + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + StringValues []string `json:"strings,omitempty" protobuf:"bytes,8,opt,name=strings"` + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + VersionValues []string `json:"versions,omitempty" protobuf:"bytes,9,opt,name=versions"` } // DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value. @@ -674,7 +794,7 @@ type DeviceTaint struct { // Consumers must treat unknown effects like None. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"` // ^^^^ @@ -690,9 +810,18 @@ type DeviceTaint struct { // which will enable adding new enums within a single release without // ratcheting. - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"` @@ -704,7 +833,7 @@ type DeviceTaint struct { } // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceTaintEffect string const ( @@ -737,7 +866,7 @@ type ResourceSliceList struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.32 -// +k8s:supportsSubresource=/status +// +k8s:supportsSubresource="/status" // ResourceClaim describes a request for access to resources in the cluster, // for use by workloads. For example, if a workload needs an accelerator device @@ -755,7 +884,7 @@ type ResourceClaim struct { // Spec describes what is being requested and how to configure it. // The spec is immutable. - // +k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:immutable Spec ResourceClaimSpec `json:"spec" protobuf:"bytes,2,name=spec"` // Status describes whether the claim is ready to use and what has been allocated. @@ -783,11 +912,11 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []DeviceRequest `json:"requests" protobuf:"bytes,1,name=requests"` // These constraints must be satisfied by the set of devices that get @@ -795,8 +924,8 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Constraints []DeviceConstraint `json:"constraints,omitempty" protobuf:"bytes,2,opt,name=constraints"` // This field holds configuration for multiple potential drivers which @@ -805,8 +934,8 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Config []DeviceClaimConfiguration `json:"config,omitempty" protobuf:"bytes,3,opt,name=config"` // Potential future extension, ignored by older schedulers. This is @@ -873,8 +1002,8 @@ type DeviceRequest struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` // AllocationMode and its related fields define how devices are allocated @@ -900,7 +1029,7 @@ type DeviceRequest struct { // requests with unknown modes. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -950,11 +1079,11 @@ type DeviceRequest struct { // +oneOf=deviceRequestType // +listType=atomic // +featureGate=DRAPrioritizedList - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,7,name=firstAvailable"` // If specified, the request's tolerations. @@ -975,12 +1104,13 @@ type DeviceRequest struct { // This field can only be set when deviceClassName is set and no subrequests // are specified in the firstAvailable list. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,8,opt,name=tolerations"` // Capacity define resource requirements against each capacity. @@ -1033,8 +1163,8 @@ type DeviceSubRequest struct { // to reference. // // +required - // +k8s:required - // +k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` // Selectors define criteria which must be satisfied by a specific @@ -1044,7 +1174,8 @@ type DeviceSubRequest struct { // // +optional // +listType=atomic - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` // AllocationMode and its related fields define how devices are allocated @@ -1066,6 +1197,7 @@ type DeviceSubRequest struct { // requests with unknown modes. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1090,12 +1222,13 @@ type DeviceSubRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,7,opt,name=tolerations"` // Capacity define resource requirements against each capacity. @@ -1152,7 +1285,7 @@ const ( ) // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceAllocationMode string // Valid [DeviceRequest.CountMode] values. @@ -1221,6 +1354,14 @@ type CELDeviceSelector struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -1271,10 +1412,10 @@ type DeviceConstraint struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` // MatchAttribute requires that all devices in question have this @@ -1288,12 +1429,17 @@ type DeviceConstraint struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. // // +optional // +oneOf=ConstraintType - // +k8s:optional - // +k8s:format=k8s-resource-fully-qualified-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-fully-qualified-name MatchAttribute *FullyQualifiedName `json:"matchAttribute,omitempty" protobuf:"bytes,2,opt,name=matchAttribute"` // Potential future extension, not part of the current design: @@ -1309,6 +1455,11 @@ type DeviceConstraint struct { // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -1334,10 +1485,10 @@ type DeviceClaimConfiguration struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"` @@ -1351,7 +1502,7 @@ type DeviceConfiguration struct { // // +optional // +oneOf=ConfigurationType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional Opaque *OpaqueDeviceConfiguration `json:"opaque,omitempty" protobuf:"bytes,1,opt,name=opaque"` } @@ -1368,8 +1519,9 @@ type OpaqueDeviceConfiguration struct { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:required - // +k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless Driver string `json:"driver" protobuf:"bytes,1,name=driver"` // Parameters can contain arbitrary data. It is the responsibility of @@ -1395,8 +1547,8 @@ type DeviceToleration struct { // Must be a label name. // // +optional - // +k8s:optional - // +k8s:format=k8s-label-key + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-label-key Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"` // Operator represents a key's relationship to the value. @@ -1406,6 +1558,7 @@ type DeviceToleration struct { // // +optional // +default="Equal" + // +k8s:alpha(since: "1.36")=+k8s:optional Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"` // Value is the taint value the toleration matches to. @@ -1419,6 +1572,7 @@ type DeviceToleration struct { // When specified, allowed values are NoSchedule and NoExecute. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"` // TolerationSeconds represents the period of time the toleration (which must be @@ -1435,7 +1589,7 @@ type DeviceToleration struct { // A toleration operator is the set of operators that can be used in a toleration. // // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceTolerationOperator string const ( @@ -1449,8 +1603,8 @@ type ResourceClaimStatus struct { // Allocation is set once the claim has been allocated successfully. // // +optional - // +k8s:optional - // +k8s:update=NoModify + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:update=NoModify Allocation *AllocationResult `json:"allocation,omitempty" protobuf:"bytes,1,opt,name=allocation"` // ReservedFor indicates which entities are currently allowed to use @@ -1478,10 +1632,10 @@ type ResourceClaimStatus struct { // +listMapKey=uid // +patchStrategy=merge // +patchMergeKey=uid - // +k8s:optional - // +k8s:listType=map - // +k8s:listMapKey=uid - // +k8s:maxItems=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=uid + // +k8s:alpha(since: "1.36")=+k8s:maxItems=256 ReservedFor []ResourceClaimConsumerReference `json:"reservedFor,omitempty" protobuf:"bytes,2,opt,name=reservedFor" patchStrategy:"merge" patchMergeKey:"uid"` // DeallocationRequested is tombstoned since Kubernetes 1.32 where @@ -1494,18 +1648,18 @@ type ResourceClaimStatus struct { // information. Entries are owned by their respective drivers. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=map // +listMapKey=driver // +listMapKey=device // +listMapKey=pool // +listMapKey=shareID // +featureGate=DRAResourceClaimDeviceStatus - // +k8s:listType=map - // +k8s:listMapKey=driver - // +k8s:listMapKey=device - // +k8s:listMapKey=pool - // +k8s:listMapKey=shareID + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=driver + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=device + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=pool + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=shareID Devices []AllocatedDeviceStatus `json:"devices,omitempty" protobuf:"bytes,4,opt,name=devices"` } @@ -1554,7 +1708,7 @@ type AllocationResult struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -1568,8 +1722,8 @@ type DeviceAllocationResult struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Results []DeviceRequestAllocationResult `json:"results,omitempty" protobuf:"bytes,1,opt,name=results"` // This field is a combination of all the claim and class configuration parameters. @@ -1582,8 +1736,8 @@ type DeviceAllocationResult struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=64 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=64 Config []DeviceAllocationConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` } @@ -1612,8 +1766,9 @@ type DeviceRequestAllocationResult struct { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:format=k8s-long-name-caseless - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 Driver string `json:"driver" protobuf:"bytes,2,name=driver"` // This name together with the driver name and the device name field @@ -1623,8 +1778,8 @@ type DeviceRequestAllocationResult struct { // DNS sub-domains separated by slashes. // // +required - // +k8s:required - // +k8s:format=k8s-resource-pool-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-pool-name Pool string `json:"pool" protobuf:"bytes,3,name=pool"` // Device references one device instance via its name in the driver's @@ -1650,40 +1805,40 @@ type DeviceRequestAllocationResult struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints - // +k8s:optional - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingConditions []string `json:"bindingConditions,omitempty" protobuf:"bytes,7,rep,name=bindingConditions"` // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingFailureConditions []string `json:"bindingFailureConditions,omitempty" protobuf:"bytes,8,rep,name=bindingFailureConditions"` // ShareID uniquely identifies an individual allocation share of the device, @@ -1693,8 +1848,8 @@ type DeviceRequestAllocationResult struct { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid ShareID *types.UID `json:"shareID,omitempty" protobuf:"bytes,9,opt,name=shareID"` // ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. @@ -1718,7 +1873,7 @@ type DeviceAllocationConfiguration struct { // or from a claim. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Source AllocationConfigSource `json:"source" protobuf:"bytes,1,name=source"` // Requests lists the names of requests where the configuration applies. @@ -1730,17 +1885,17 @@ type DeviceAllocationConfiguration struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` DeviceConfiguration `json:",inline" protobuf:"bytes,3,name=deviceConfiguration"` } // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type AllocationConfigSource string // Valid [DeviceAllocationConfiguration.Source] values. @@ -1779,8 +1934,8 @@ type DeviceClass struct { metav1.TypeMeta `json:",inline"` // Standard object metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec defines what can be allocated and how to configure it. @@ -1801,8 +1956,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,1,opt,name=selectors"` // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -1813,8 +1968,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Config []DeviceClassConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` // SuitableNodes is tombstoned since Kubernetes 1.32 where @@ -1831,11 +1986,11 @@ type DeviceClassSpec struct { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. // +optional // +featureGate=DRAExtendedResource - // +k8s:optional - // +k8s:format=k8s-extended-resource-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-extended-resource-name ExtendedResourceName *string `json:"extendedResourceName,omitempty" protobuf:"bytes,4,opt,name=extendedResourceName"` } @@ -1961,8 +2116,8 @@ type AllocatedDeviceStatus struct { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid ShareID *string `json:"shareID,omitempty" protobuf:"bytes,7,opt,name=shareID"` // Conditions contains the latest observation of the device's state. @@ -1986,7 +2141,7 @@ type AllocatedDeviceStatus struct { // NetworkData contains network-related information specific to the device. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional NetworkData *NetworkDeviceData `json:"networkData,omitempty" protobuf:"bytes,6,opt,name=networkData"` } @@ -1998,11 +2153,11 @@ type NetworkDeviceData struct { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=256 InterfaceName string `json:"interfaceName,omitempty" protobuf:"bytes,1,opt,name=interfaceName"` // IPs lists the network addresses assigned to the device's network interface. @@ -2015,18 +2170,18 @@ type NetworkDeviceData struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 IPs []string `json:"ips,omitempty" protobuf:"bytes,2,opt,name=ips"` // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=128 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=128 HardwareAddress string `json:"hardwareAddress,omitempty" protobuf:"bytes,3,opt,name=hardwareAddress"` } diff --git a/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go index 070536e67f79d..4b147a3f3e5e7 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go @@ -46,7 +46,7 @@ var map_AllocationResult = map[string]string{ "": "AllocationResult contains attributes of an allocated resource.", "devices": "Devices is the result of allocating devices.", "nodeSelector": "NodeSelector defines where the allocated resources are available. If unset, they are available everywhere.", - "allocationTimestamp": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", + "allocationTimestamp": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", } func (AllocationResult) SwaggerDoc() map[string]string { @@ -54,18 +54,19 @@ func (AllocationResult) SwaggerDoc() map[string]string { } var map_BasicDevice = map[string]string{ - "": "BasicDevice defines one device instance.", - "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe maximum number of device counter consumptions per device is 2.", - "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", - "bindsToNode": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingConditions": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingFailureConditions": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "allowMultipleAllocations": "AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests.\n\nIf AllowMultipleAllocations is set to true, the device can be allocated more than once, and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not.", + "": "BasicDevice defines one device instance.", + "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe maximum number of device counter consumptions per device is 2.", + "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", + "bindsToNode": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingConditions": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingFailureConditions": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is true, a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "allowMultipleAllocations": "AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests.\n\nIf AllowMultipleAllocations is set to true, the device can be allocated more than once, and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not.", + "nodeAllocatableResourceMappings": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", } func (BasicDevice) SwaggerDoc() map[string]string { @@ -74,7 +75,7 @@ func (BasicDevice) SwaggerDoc() map[string]string { var map_CELDeviceSelector = map[string]string{ "": "CELDeviceSelector contains a CEL expression for selecting a device.", - "expression": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "expression": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", } func (CELDeviceSelector) SwaggerDoc() map[string]string { @@ -162,11 +163,15 @@ func (DeviceAllocationResult) SwaggerDoc() map[string]string { } var map_DeviceAttribute = map[string]string{ - "": "DeviceAttribute must have exactly one field set.", - "int": "IntValue is a number.", - "bool": "BoolValue is a true/false value.", - "string": "StringValue is a string. Must not be longer than 64 characters.", - "version": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", + "": "DeviceAttribute must have exactly one field set.", + "int": "IntValue is a number.", + "bool": "BoolValue is a true/false value.", + "string": "StringValue is a string. Must not be longer than 64 characters.", + "version": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", + "ints": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "bools": "BoolValues is a non-empty list of true/false values.", + "strings": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "versions": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", } func (DeviceAttribute) SwaggerDoc() map[string]string { @@ -235,7 +240,7 @@ var map_DeviceClassSpec = map[string]string{ "": "DeviceClassSpec is used in a [DeviceClass] to define what can be allocated and how to configure it.", "selectors": "Each selector must be satisfied by a device which is claimed via this class.", "config": "Config defines configuration parameters that apply to each device that is claimed via this class. Some classses may potentially be satisfied by multiple drivers, so each instance of a vendor configuration applies to exactly one driver.\n\nThey are passed to the driver, but are not considered while allocating the claim.", - "extendedResourceName": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "extendedResourceName": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", } func (DeviceClassSpec) SwaggerDoc() map[string]string { @@ -254,8 +259,8 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", - "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", - "distinctAttribute": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", + "distinctAttribute": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", } func (DeviceConstraint) SwaggerDoc() map[string]string { @@ -281,7 +286,7 @@ var map_DeviceRequest = map[string]string{ "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", "firstAvailable": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", - "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "capacity": "Capacity define resource requirements against each capacity.\n\nIf this field is unset and the device supports multiple allocations, the default value will be applied to each capacity according to requestPolicy. For the capacity that has no requestPolicy, default is the full capacity value.\n\nApplies to each device allocation. If Count > 1, the request fails if there aren't enough devices that meet the requirements. If AllocationMode is set to All, the request fails if there are devices that otherwise match the request, and have this capacity, with a value >= the requested amount, but which cannot be allocated to this request.", } @@ -296,9 +301,9 @@ var map_DeviceRequestAllocationResult = map[string]string{ "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", "adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", - "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", - "bindingConditions": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingFailureConditions": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", + "bindingConditions": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingFailureConditions": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "shareID": "ShareID uniquely identifies an individual allocation share of the device, used when the device supports multiple simultaneous allocations. It serves as an additional map key to differentiate concurrent shares of the same device.", "consumedCapacity": "ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. The consumed amount may differ from the requested amount: it is rounded up to the nearest valid value based on the device’s requestPolicy if applicable (i.e., may not be less than the requested amount).\n\nThe total consumed capacity for each device must not exceed the DeviceCapacity's Value.\n\nThis field is populated only for devices that allow multiple allocations. All capacity entries are included, even if the consumed amount is zero.", } @@ -323,7 +328,7 @@ var map_DeviceSubRequest = map[string]string{ "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "capacity": "Capacity define resource requirements against each capacity.\n\nIf this field is unset and the device supports multiple allocations, the default value will be applied to each capacity according to requestPolicy. For the capacity that has no requestPolicy, default is the full capacity value.\n\nApplies to each device allocation. If Count > 1, the request fails if there aren't enough devices that meet the requirements. If AllocationMode is set to All, the request fails if there are devices that otherwise match the request, and have this capacity, with a value >= the requested amount, but which cannot be allocated to this request.", } @@ -336,7 +341,7 @@ var map_DeviceTaint = map[string]string{ "key": "The taint key to be applied to a device. Must be a label name.", "value": "The taint value corresponding to the taint key. Must be a label value.", "effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them.\n\nValid effects are None, NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here. More effects may get added in the future. Consumers must treat unknown effects like None.", - "timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + "timeAdded": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", } func (DeviceTaint) SwaggerDoc() map[string]string { @@ -358,15 +363,25 @@ func (DeviceToleration) SwaggerDoc() map[string]string { var map_NetworkDeviceData = map[string]string{ "": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", - "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "ips": "IPs lists the network addresses assigned to the device's network interface. This can include both IPv4 and IPv6 addresses. The IPs are in the CIDR notation, which includes both the address and the associated subnet mask. e.g.: \"192.0.2.5/24\" for IPv4 and \"2001:db8::5/64\" for IPv6.\n\nMust not contain more than 16 entries.", - "hardwareAddress": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "hardwareAddress": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", } func (NetworkDeviceData) SwaggerDoc() map[string]string { return map_NetworkDeviceData } +var map_NodeAllocatableResourceMapping = map[string]string{ + "": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "capacityKey": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "allocationMultiplier": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16.", +} + +func (NodeAllocatableResourceMapping) SwaggerDoc() map[string]string { + return map_NodeAllocatableResourceMapping +} + var map_OpaqueDeviceConfiguration = map[string]string{ "": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "driver": "Driver is used to determine which kubelet plugin needs to be passed these configuration parameters.\n\nAn admission policy provided by the driver developer could use this to decide whether it needs to validate them.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver. It should use only lower case characters.", diff --git a/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go index 156fea79ba8a4..78ec7b0d5801a 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go @@ -159,6 +159,13 @@ func (in *BasicDevice) DeepCopyInto(out *BasicDevice) { *out = new(bool) **out = **in } + if in.NodeAllocatableResourceMappings != nil { + in, out := &in.NodeAllocatableResourceMappings, &out.NodeAllocatableResourceMappings + *out = make(map[corev1.ResourceName]NodeAllocatableResourceMapping, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } return } @@ -411,6 +418,26 @@ func (in *DeviceAttribute) DeepCopyInto(out *DeviceAttribute) { *out = new(string) **out = **in } + if in.IntValues != nil { + in, out := &in.IntValues, &out.IntValues + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.BoolValues != nil { + in, out := &in.BoolValues, &out.BoolValues + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.StringValues != nil { + in, out := &in.StringValues, &out.StringValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.VersionValues != nil { + in, out := &in.VersionValues, &out.VersionValues + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -907,6 +934,32 @@ func (in *NetworkDeviceData) DeepCopy() *NetworkDeviceData { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAllocatableResourceMapping) DeepCopyInto(out *NodeAllocatableResourceMapping) { + *out = *in + if in.CapacityKey != nil { + in, out := &in.CapacityKey, &out.CapacityKey + *out = new(QualifiedName) + **out = **in + } + if in.AllocationMultiplier != nil { + in, out := &in.AllocationMultiplier, &out.AllocationMultiplier + x := (*in).DeepCopy() + *out = &x + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAllocatableResourceMapping. +func (in *NodeAllocatableResourceMapping) DeepCopy() *NodeAllocatableResourceMapping { + if in == nil { + return nil + } + out := new(NodeAllocatableResourceMapping) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpaqueDeviceConfiguration) DeepCopyInto(out *OpaqueDeviceConfiguration) { *out = *in diff --git a/staging/src/k8s.io/api/resource/v1beta1/zz_generated.model_name.go b/staging/src/k8s.io/api/resource/v1beta1/zz_generated.model_name.go index 0d4983d34e016..a2887e753436f 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/zz_generated.model_name.go +++ b/staging/src/k8s.io/api/resource/v1beta1/zz_generated.model_name.go @@ -171,6 +171,11 @@ func (in NetworkDeviceData) OpenAPIModelName() string { return "io.k8s.api.resource.v1beta1.NetworkDeviceData" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NodeAllocatableResourceMapping) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in OpaqueDeviceConfiguration) OpenAPIModelName() string { return "io.k8s.api.resource.v1beta1.OpaqueDeviceConfiguration" diff --git a/staging/src/k8s.io/api/resource/v1beta2/generated.pb.go b/staging/src/k8s.io/api/resource/v1beta2/generated.pb.go index 5a36e6d2b5dfc..6b5909880714a 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1beta2/generated.pb.go @@ -25,6 +25,7 @@ import ( io "io" "sort" + k8s_io_api_core_v1 "k8s.io/api/core/v1" v11 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -91,12 +92,24 @@ func (m *DeviceSubRequest) Reset() { *m = DeviceSubRequest{} } func (m *DeviceTaint) Reset() { *m = DeviceTaint{} } +func (m *DeviceTaintRule) Reset() { *m = DeviceTaintRule{} } + +func (m *DeviceTaintRuleList) Reset() { *m = DeviceTaintRuleList{} } + +func (m *DeviceTaintRuleSpec) Reset() { *m = DeviceTaintRuleSpec{} } + +func (m *DeviceTaintRuleStatus) Reset() { *m = DeviceTaintRuleStatus{} } + +func (m *DeviceTaintSelector) Reset() { *m = DeviceTaintSelector{} } + func (m *DeviceToleration) Reset() { *m = DeviceToleration{} } func (m *ExactDeviceRequest) Reset() { *m = ExactDeviceRequest{} } func (m *NetworkDeviceData) Reset() { *m = NetworkDeviceData{} } +func (m *NodeAllocatableResourceMapping) Reset() { *m = NodeAllocatableResourceMapping{} } + func (m *OpaqueDeviceConfiguration) Reset() { *m = OpaqueDeviceConfiguration{} } func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } @@ -573,6 +586,35 @@ func (m *Device) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.NodeAllocatableResourceMappings) > 0 { + keysForNodeAllocatableResourceMappings := make([]string, 0, len(m.NodeAllocatableResourceMappings)) + for k := range m.NodeAllocatableResourceMappings { + keysForNodeAllocatableResourceMappings = append(keysForNodeAllocatableResourceMappings, string(k)) + } + sort.Strings(keysForNodeAllocatableResourceMappings) + for iNdEx := len(keysForNodeAllocatableResourceMappings) - 1; iNdEx >= 0; iNdEx-- { + v := m.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(keysForNodeAllocatableResourceMappings[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForNodeAllocatableResourceMappings[iNdEx]) + copy(dAtA[i:], keysForNodeAllocatableResourceMappings[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForNodeAllocatableResourceMappings[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x6a + } + } if m.AllowMultipleAllocations != nil { i-- if *m.AllowMultipleAllocations { @@ -852,6 +894,43 @@ func (m *DeviceAttribute) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.VersionValues) > 0 { + for iNdEx := len(m.VersionValues) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.VersionValues[iNdEx]) + copy(dAtA[i:], m.VersionValues[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.VersionValues[iNdEx]))) + i-- + dAtA[i] = 0x4a + } + } + if len(m.StringValues) > 0 { + for iNdEx := len(m.StringValues) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.StringValues[iNdEx]) + copy(dAtA[i:], m.StringValues[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StringValues[iNdEx]))) + i-- + dAtA[i] = 0x42 + } + } + if len(m.BoolValues) > 0 { + for iNdEx := len(m.BoolValues) - 1; iNdEx >= 0; iNdEx-- { + i-- + if m.BoolValues[iNdEx] { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + } + if len(m.IntValues) > 0 { + for iNdEx := len(m.IntValues) - 1; iNdEx >= 0; iNdEx-- { + i = encodeVarintGenerated(dAtA, i, uint64(m.IntValues[iNdEx])) + i-- + dAtA[i] = 0x30 + } + } if m.VersionValue != nil { i -= len(*m.VersionValue) copy(dAtA[i:], *m.VersionValue) @@ -1696,6 +1775,232 @@ func (m *DeviceTaint) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *DeviceTaintRule) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceTaintRule) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceTaintRule) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *DeviceTaintRuleList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceTaintRuleList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceTaintRuleList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *DeviceTaintRuleSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceTaintRuleSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceTaintRuleSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Taint.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.DeviceSelector != nil { + { + size, err := m.DeviceSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DeviceTaintRuleStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceTaintRuleStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceTaintRuleStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Conditions) > 0 { + for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DeviceTaintSelector) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceTaintSelector) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceTaintSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Device != nil { + i -= len(*m.Device) + copy(dAtA[i:], *m.Device) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Device))) + i-- + dAtA[i] = 0x22 + } + if m.Pool != nil { + i -= len(*m.Pool) + copy(dAtA[i:], *m.Pool) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Pool))) + i-- + dAtA[i] = 0x1a + } + if m.Driver != nil { + i -= len(*m.Driver) + copy(dAtA[i:], *m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Driver))) + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} + func (m *DeviceToleration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1872,7 +2177,7 @@ func (m *NetworkDeviceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { +func (m *NodeAllocatableResourceMapping) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1882,29 +2187,71 @@ func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *OpaqueDeviceConfiguration) MarshalTo(dAtA []byte) (int, error) { +func (m *NodeAllocatableResourceMapping) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *OpaqueDeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *NodeAllocatableResourceMapping) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Parameters.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.AllocationMultiplier != nil { + { + size, err := m.AllocationMultiplier.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 - i -= len(m.Driver) - copy(dAtA[i:], m.Driver) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + if m.CapacityKey != nil { + i -= len(*m.CapacityKey) + copy(dAtA[i:], *m.CapacityKey) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.CapacityKey))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *OpaqueDeviceConfiguration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *OpaqueDeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Parameters.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(m.Driver) + copy(dAtA[i:], m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil @@ -2745,6 +3092,15 @@ func (m *Device) Size() (n int) { if m.AllowMultipleAllocations != nil { n += 2 } + if len(m.NodeAllocatableResourceMappings) > 0 { + for k, v := range m.NodeAllocatableResourceMappings { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } return n } @@ -2808,6 +3164,26 @@ func (m *DeviceAttribute) Size() (n int) { l = len(*m.VersionValue) n += 1 + l + sovGenerated(uint64(l)) } + if len(m.IntValues) > 0 { + for _, e := range m.IntValues { + n += 1 + sovGenerated(uint64(e)) + } + } + if len(m.BoolValues) > 0 { + n += 2 * len(m.BoolValues) + } + if len(m.StringValues) > 0 { + for _, s := range m.StringValues { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.VersionValues) > 0 { + for _, s := range m.VersionValues { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3128,6 +3504,89 @@ func (m *DeviceTaint) Size() (n int) { return n } +func (m *DeviceTaintRule) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *DeviceTaintRuleList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *DeviceTaintRuleSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DeviceSelector != nil { + l = m.DeviceSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = m.Taint.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *DeviceTaintRuleStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *DeviceTaintSelector) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Driver != nil { + l = len(*m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Pool != nil { + l = len(*m.Pool) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Device != nil { + l = len(*m.Device) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *DeviceToleration) Size() (n int) { if m == nil { return 0 @@ -3200,6 +3659,23 @@ func (m *NetworkDeviceData) Size() (n int) { return n } +func (m *NodeAllocatableResourceMapping) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CapacityKey != nil { + l = len(*m.CapacityKey) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.AllocationMultiplier != nil { + l = m.AllocationMultiplier.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *OpaqueDeviceConfiguration) Size() (n int) { if m == nil { return 0 @@ -3586,6 +4062,16 @@ func (this *Device) String() string { mapStringForCapacity += fmt.Sprintf("%v: %v,", k, this.Capacity[QualifiedName(k)]) } mapStringForCapacity += "}" + keysForNodeAllocatableResourceMappings := make([]string, 0, len(this.NodeAllocatableResourceMappings)) + for k := range this.NodeAllocatableResourceMappings { + keysForNodeAllocatableResourceMappings = append(keysForNodeAllocatableResourceMappings, string(k)) + } + sort.Strings(keysForNodeAllocatableResourceMappings) + mapStringForNodeAllocatableResourceMappings := "map[k8s_io_api_core_v1.ResourceName]NodeAllocatableResourceMapping{" + for _, k := range keysForNodeAllocatableResourceMappings { + mapStringForNodeAllocatableResourceMappings += fmt.Sprintf("%v: %v,", k, this.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(k)]) + } + mapStringForNodeAllocatableResourceMappings += "}" s := strings.Join([]string{`&Device{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Attributes:` + mapStringForAttributes + `,`, @@ -3599,6 +4085,7 @@ func (this *Device) String() string { `BindingConditions:` + fmt.Sprintf("%v", this.BindingConditions) + `,`, `BindingFailureConditions:` + fmt.Sprintf("%v", this.BindingFailureConditions) + `,`, `AllowMultipleAllocations:` + valueToStringGenerated(this.AllowMultipleAllocations) + `,`, + `NodeAllocatableResourceMappings:` + mapStringForNodeAllocatableResourceMappings + `,`, `}`, }, "") return s @@ -3645,6 +4132,10 @@ func (this *DeviceAttribute) String() string { `BoolValue:` + valueToStringGenerated(this.BoolValue) + `,`, `StringValue:` + valueToStringGenerated(this.StringValue) + `,`, `VersionValue:` + valueToStringGenerated(this.VersionValue) + `,`, + `IntValues:` + fmt.Sprintf("%v", this.IntValues) + `,`, + `BoolValues:` + fmt.Sprintf("%v", this.BoolValues) + `,`, + `StringValues:` + fmt.Sprintf("%v", this.StringValues) + `,`, + `VersionValues:` + fmt.Sprintf("%v", this.VersionValues) + `,`, `}`, }, "") return s @@ -3887,6 +4378,72 @@ func (this *DeviceSubRequest) String() string { }, "") return s } +func (this *DeviceTaintRule) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceTaintRule{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "DeviceTaintRuleSpec", "DeviceTaintRuleSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "DeviceTaintRuleStatus", "DeviceTaintRuleStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRuleList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]DeviceTaintRule{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "DeviceTaintRule", "DeviceTaintRule", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&DeviceTaintRuleList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRuleSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceTaintRuleSpec{`, + `DeviceSelector:` + strings.Replace(this.DeviceSelector.String(), "DeviceTaintSelector", "DeviceTaintSelector", 1) + `,`, + `Taint:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Taint), "DeviceTaint", "DeviceTaint", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRuleStatus) String() string { + if this == nil { + return "nil" + } + repeatedStringForConditions := "[]Condition{" + for _, f := range this.Conditions { + repeatedStringForConditions += fmt.Sprintf("%v", f) + "," + } + repeatedStringForConditions += "}" + s := strings.Join([]string{`&DeviceTaintRuleStatus{`, + `Conditions:` + repeatedStringForConditions + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintSelector) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceTaintSelector{`, + `Driver:` + valueToStringGenerated(this.Driver) + `,`, + `Pool:` + valueToStringGenerated(this.Pool) + `,`, + `Device:` + valueToStringGenerated(this.Device) + `,`, + `}`, + }, "") + return s +} func (this *DeviceToleration) String() string { if this == nil { return "nil" @@ -3939,6 +4496,17 @@ func (this *NetworkDeviceData) String() string { }, "") return s } +func (this *NodeAllocatableResourceMapping) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&NodeAllocatableResourceMapping{`, + `CapacityKey:` + valueToStringGenerated(this.CapacityKey) + `,`, + `AllocationMultiplier:` + strings.Replace(fmt.Sprintf("%v", this.AllocationMultiplier), "Quantity", "resource.Quantity", 1) + `,`, + `}`, + }, "") + return s +} func (this *OpaqueDeviceConfiguration) String() string { if this == nil { return "nil" @@ -6027,61 +6595,11 @@ func (m *Device) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AllowMultipleAllocations = &b - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceAllocationConfiguration: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceAllocationConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 13: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Source", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NodeAllocatableResourceMappings", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -6091,23 +6609,202 @@ func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Source = AllocationConfigSource(dAtA[iNdEx:postIndex]) + if m.NodeAllocatableResourceMappings == nil { + m.NodeAllocatableResourceMappings = make(map[k8s_io_api_core_v1.ResourceName]NodeAllocatableResourceMapping) + } + var mapkey k8s_io_api_core_v1.ResourceName + mapvalue := &NodeAllocatableResourceMapping{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &NodeAllocatableResourceMapping{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.NodeAllocatableResourceMappings[k8s_io_api_core_v1.ResourceName(mapkey)] = *mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceAllocationConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceAllocationConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Source", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Source = AllocationConfigSource(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -6449,76 +7146,286 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.VersionValue = &s iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceCapacity) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceCapacity: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceCapacity: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break + case 6: + if wireType == 0 { + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } } - } - if msglen < 0 { + m.IntValues = append(m.IntValues, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.IntValues) == 0 { + m.IntValues = make([]int64, 0, elementCount) + } + for iNdEx < postIndex { + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IntValues = append(m.IntValues, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field IntValues", wireType) + } + case 7: + if wireType == 0 { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BoolValues = append(m.BoolValues, bool(v != 0)) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen + if elementCount != 0 && len(m.BoolValues) == 0 { + m.BoolValues = make([]bool, 0, elementCount) + } + for iNdEx < postIndex { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BoolValues = append(m.BoolValues, bool(v != 0)) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field BoolValues", wireType) + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StringValues", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StringValues = append(m.StringValues, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionValues", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VersionValues = append(m.VersionValues, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceCapacity) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceCapacity: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceCapacity: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + msglen @@ -8619,33 +9526,601 @@ func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { if postIndex < 0 { return ErrInvalidLengthGenerated } - if postIndex > l { + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Count |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tolerations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tolerations = append(m.Tolerations, DeviceToleration{}) + if err := m.Tolerations[len(m.Tolerations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Capacity", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Capacity == nil { + m.Capacity = &CapacityRequirements{} + } + if err := m.Capacity.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Effect = DeviceTaintEffect(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeAdded", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TimeAdded == nil { + m.TimeAdded = &v1.Time{} + } + if err := m.TimeAdded.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRuleList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRuleList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, DeviceTaintRule{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { return io.ErrUnexpectedEOF } - m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) - } - m.Count = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Count |= int64(b&0x7F) << shift - if b < 0x80 { - break - } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break } - case 6: + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintRuleSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintRuleSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Tolerations", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DeviceSelector", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -8672,14 +10147,16 @@ func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Tolerations = append(m.Tolerations, DeviceToleration{}) - if err := m.Tolerations[len(m.Tolerations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.DeviceSelector == nil { + m.DeviceSelector = &DeviceTaintSelector{} + } + if err := m.DeviceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 7: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Capacity", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Taint", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -8706,10 +10183,7 @@ func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Capacity == nil { - m.Capacity = &CapacityRequirements{} - } - if err := m.Capacity.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Taint.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -8734,7 +10208,7 @@ func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceTaint) Unmarshal(dAtA []byte) error { +func (m *DeviceTaintRuleStatus) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -8757,17 +10231,17 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceTaint: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceTaintRuleStatus: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceTaint: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceTaintRuleStatus: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -8777,27 +10251,79 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Key = string(dAtA[iNdEx:postIndex]) + m.Conditions = append(m.Conditions, v1.Condition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceTaintSelector: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceTaintSelector: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -8825,11 +10351,12 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Value = string(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.Driver = &s iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -8857,13 +10384,14 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Effect = DeviceTaintEffect(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.Pool = &s iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TimeAdded", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -8873,27 +10401,24 @@ func (m *DeviceTaint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.TimeAdded == nil { - m.TimeAdded = &v1.Time{} - } - if err := m.TimeAdded.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + s := string(dAtA[iNdEx:postIndex]) + m.Device = &s iNdEx = postIndex default: iNdEx = preIndex @@ -9518,6 +11043,125 @@ func (m *NetworkDeviceData) Unmarshal(dAtA []byte) error { } return nil } +func (m *NodeAllocatableResourceMapping) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeAllocatableResourceMapping: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeAllocatableResourceMapping: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CapacityKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := QualifiedName(dAtA[iNdEx:postIndex]) + m.CapacityKey = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMultiplier", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AllocationMultiplier == nil { + m.AllocationMultiplier = &resource.Quantity{} + } + if err := m.AllocationMultiplier.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *OpaqueDeviceConfiguration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/resource/v1beta2/generated.proto b/staging/src/k8s.io/api/resource/v1beta2/generated.proto index 76af5aa4e205d..82bb033b3efd5 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta2/generated.proto @@ -65,8 +65,8 @@ message AllocatedDeviceStatus { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid optional string shareID = 7; // Conditions contains the latest observation of the device's state. @@ -90,7 +90,7 @@ message AllocatedDeviceStatus { // NetworkData contains network-related information specific to the device. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional NetworkDeviceData networkData = 6; } @@ -110,7 +110,7 @@ message AllocationResult { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -169,6 +169,14 @@ message CELDeviceSelector { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -307,8 +315,8 @@ message CounterSet { // It must be a DNS label. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name optional string name = 1; // Counters defines the set of counters for this CounterSet @@ -317,6 +325,8 @@ message CounterSet { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name map counters = 2; } @@ -335,6 +345,7 @@ message Device { // The maximum number of attributes and capacities combined is 32. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional map attributes = 2; // Capacity defines the set of capacities for this device. @@ -355,13 +366,13 @@ message Device { // device is 2. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=counterSet + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=counterSet // +featureGate=DRAPartitionableDevices - // +k8s:maxItems=2 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=2 repeated DeviceCounterConsumption consumesCounters = 4; // NodeName identifies the node where the device is available. @@ -402,12 +413,13 @@ message Device { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceTaint taints = 8; // BindsToNode indicates if the usage of an allocation involving this device @@ -415,7 +427,7 @@ message Device { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -431,14 +443,14 @@ message Device { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingConditions = 10; // BindingFailureConditions defines the conditions for binding failure. @@ -449,14 +461,14 @@ message Device { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingFailureConditions = 11; // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -467,6 +479,21 @@ message Device { // +optional // +featureGate=DRAConsumableCapacity optional bool allowMultipleAllocations = 12; + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + map nodeAllocatableResourceMappings = 13; } // DeviceAllocationConfiguration gets embedded in an AllocationResult. @@ -476,7 +503,7 @@ message DeviceAllocationConfiguration { // or from a claim. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string source = 1; // Requests lists the names of requests where the configuration applies. @@ -488,10 +515,10 @@ message DeviceAllocationConfiguration { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 2; optional DeviceConfiguration deviceConfiguration = 3; @@ -503,8 +530,8 @@ message DeviceAllocationResult { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceRequestAllocationResult results = 1; // This field is a combination of all the claim and class configuration parameters. @@ -517,8 +544,8 @@ message DeviceAllocationResult { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=64 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=64 repeated DeviceAllocationConfiguration config = 2; } @@ -527,31 +554,79 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember optional string version = 5; + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated int64 ints = 6; + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated bool bools = 7; + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated string strings = 8; + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + repeated string versions = 9; } // DeviceCapacity describes a quantity associated with a device. @@ -587,11 +662,11 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceRequest requests = 1; // These constraints must be satisfied by the set of devices that get @@ -599,8 +674,8 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceConstraint constraints = 2; // This field holds configuration for multiple potential drivers which @@ -609,8 +684,8 @@ message DeviceClaim { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceClaimConfiguration config = 3; } @@ -625,10 +700,10 @@ message DeviceClaimConfiguration { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 1; optional DeviceConfiguration deviceConfiguration = 2; @@ -644,8 +719,8 @@ message DeviceClaimConfiguration { message DeviceClass { // Standard object metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // Spec defines what can be allocated and how to configure it. @@ -681,8 +756,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 1; // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -693,8 +768,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceClassConfiguration config = 2; // ExtendedResourceName is the extended resource name for the devices of this class. @@ -706,11 +781,11 @@ message DeviceClassSpec { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. // +optional // +featureGate=DRAExtendedResource - // +k8s:optional - // +k8s:format=k8s-extended-resource-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-extended-resource-name optional string extendedResourceName = 4; } @@ -722,7 +797,7 @@ message DeviceConfiguration { // // +optional // +oneOf=ConfigurationType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional OpaqueDeviceConfiguration opaque = 1; } @@ -740,10 +815,10 @@ message DeviceConstraint { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated string requests = 1; // MatchAttribute requires that all devices in question have this @@ -757,17 +832,27 @@ message DeviceConstraint { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. // // +optional // +oneOf=ConstraintType - // +k8s:optional - // +k8s:format=k8s-resource-fully-qualified-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-fully-qualified-name optional string matchAttribute = 2; // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -789,8 +874,8 @@ message DeviceCounterConsumption { // counters defined will be consumed. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name optional string counterSet = 1; // Counters defines the counters that will be consumed by the device. @@ -798,6 +883,8 @@ message DeviceCounterConsumption { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name map counters = 2; } @@ -827,7 +914,7 @@ message DeviceRequest { // // +optional // +oneOf=deviceRequestType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional ExactDeviceRequest exactly = 2; // FirstAvailable contains subrequests, of which exactly one will be @@ -848,11 +935,11 @@ message DeviceRequest { // +oneOf=deviceRequestType // +listType=atomic // +featureGate=DRAPrioritizedList - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 repeated DeviceSubRequest firstAvailable = 3; } @@ -877,8 +964,9 @@ message DeviceRequestAllocationResult { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:format=k8s-long-name-caseless - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 + // +k8s:alpha(since: "1.36")=+k8s:required optional string driver = 2; // This name together with the driver name and the device name field @@ -888,8 +976,8 @@ message DeviceRequestAllocationResult { // DNS sub-domains separated by slashes. // // +required - // +k8s:required - // +k8s:format=k8s-resource-pool-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-pool-name optional string pool = 3; // Device references one device instance via its name in the driver's @@ -915,38 +1003,39 @@ message DeviceRequestAllocationResult { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 6; // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingConditions = 7; // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 repeated string bindingFailureConditions = 8; // ShareID uniquely identifies an individual allocation share of the device, @@ -956,8 +1045,8 @@ message DeviceRequestAllocationResult { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid optional string shareID = 9; // ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. @@ -1014,8 +1103,8 @@ message DeviceSubRequest { // to reference. // // +required - // +k8s:required - // +k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -1025,8 +1114,8 @@ message DeviceSubRequest { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 3; // AllocationMode and its related fields define how devices are allocated @@ -1048,6 +1137,7 @@ message DeviceSubRequest { // requests with unknown modes. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string allocationMode = 4; // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1072,12 +1162,13 @@ message DeviceSubRequest { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 6; // Capacity define resource requirements against each capacity. @@ -1124,16 +1215,134 @@ message DeviceTaint { // Consumers must treat unknown effects like None. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required optional string effect = 3; - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; } +// DeviceTaintRule adds one taint to all devices which match the selector. +// This has the same effect as if the taint was specified directly +// in the ResourceSlice by the DRA driver. +message DeviceTaintRule { + // Standard object metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec specifies the selector and one taint. + // + // Changing the spec automatically increments the metadata.generation number. + // +required + optional DeviceTaintRuleSpec spec = 2; + + // Status provides information about what was requested in the spec. + // + // +optional + optional DeviceTaintRuleStatus status = 3; +} + +// DeviceTaintRuleList is a collection of DeviceTaintRules. +message DeviceTaintRuleList { + // Standard list metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of DeviceTaintRules. + repeated DeviceTaintRule items = 2; +} + +// DeviceTaintRuleSpec specifies the selector and one taint. +message DeviceTaintRuleSpec { + // DeviceSelector defines which device(s) the taint is applied to. + // All selector criteria must be satisfied for a device to + // match. The empty selector matches all devices. Without + // a selector, no devices are matches. + // + // +optional + optional DeviceTaintSelector deviceSelector = 1; + + // The taint that gets applied to matching devices. + // + // +required + optional DeviceTaint taint = 2; +} + +// DeviceTaintRuleStatus provides information about an on-going pod eviction. +message DeviceTaintRuleStatus { + // Conditions provide information about the state of the DeviceTaintRule + // and the cluster at some point in time, + // in a machine-readable and human-readable format. + // + // The following condition is currently defined as part of this API, more may + // get added: + // - Type: EvictionInProgress + // - Status: True if there are currently pods which need to be evicted, False otherwise + // (includes the effects which don't cause eviction). + // - Reason: not specified, may change + // - Message: includes information about number of pending pods and already evicted pods + // in a human-readable format, updated periodically, may change + // + // For `effect: None`, the condition above gets set once for each change to + // the spec, with the message containing information about what would happen + // if the effect was `NoExecute`. This feedback can be used to decide whether + // changing the effect to `NoExecute` will work as intended. It only gets + // set once to avoid having to constantly update the status. + // + // Must have 8 or fewer entries. + // + // +optional + // +listType=map + // +listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + repeated .k8s.io.apimachinery.pkg.apis.meta.v1.Condition conditions = 1; +} + +// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. +// The empty selector matches all devices. Without a selector, no devices +// are matched. +message DeviceTaintSelector { + // If driver is set, only devices from that driver are selected. + // This fields corresponds to slice.spec.driver. + // + // +optional + optional string driver = 2; + + // If pool is set, only devices in that pool are selected. + // + // Also setting the driver name may be useful to avoid + // ambiguity when different drivers use the same pool name, + // but this is not required because selecting pools from + // different drivers may also be useful, for example when + // drivers with node-local devices use the node name as + // their pool name. + // + // +optional + optional string pool = 3; + + // If device is set, only devices with that name are selected. + // This field corresponds to slice.spec.devices[].name. + // + // Setting also driver and pool may be required to avoid ambiguity, + // but is not required. + // + // +optional + optional string device = 4; +} + // The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches // the triple using the matching operator . message DeviceToleration { @@ -1142,8 +1351,8 @@ message DeviceToleration { // Must be a label name. // // +optional - // +k8s:optional - // +k8s:format=k8s-label-key + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-label-key optional string key = 1; // Operator represents a key's relationship to the value. @@ -1153,6 +1362,7 @@ message DeviceToleration { // // +optional // +default="Equal" + // +k8s:alpha(since: "1.36")=+k8s:optional optional string operator = 2; // Value is the taint value the toleration matches to. @@ -1166,6 +1376,7 @@ message DeviceToleration { // When specified, allowed values are NoSchedule and NoExecute. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string effect = 4; // TolerationSeconds represents the period of time the toleration (which must be @@ -1203,8 +1414,8 @@ message ExactDeviceRequest { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 repeated DeviceSelector selectors = 2; // AllocationMode and its related fields define how devices are allocated @@ -1227,7 +1438,7 @@ message ExactDeviceRequest { // requests with unknown modes. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional optional string allocationMode = 3; // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1266,12 +1477,13 @@ message ExactDeviceRequest { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional repeated DeviceToleration tolerations = 6; // Capacity define resource requirements against each capacity. @@ -1300,11 +1512,11 @@ message NetworkDeviceData { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=256 optional string interfaceName = 1; // IPs lists the network addresses assigned to the device's network interface. @@ -1315,22 +1527,60 @@ message NetworkDeviceData { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 repeated string ips = 2; // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=128 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=128 optional string hardwareAddress = 3; } +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +message NodeAllocatableResourceMapping { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + optional string capacityKey = 1; + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + optional .k8s.io.apimachinery.pkg.api.resource.Quantity allocationMultiplier = 2; +} + // OpaqueDeviceConfiguration contains configuration parameters for a driver // in a format defined by the driver vendor. message OpaqueDeviceConfiguration { @@ -1344,8 +1594,9 @@ message OpaqueDeviceConfiguration { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:required - // +k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless optional string driver = 1; // Parameters can contain arbitrary data. It is the responsibility of @@ -1374,7 +1625,7 @@ message ResourceClaim { // Spec describes what is being requested and how to configure it. // The spec is immutable. - // +k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:immutable optional ResourceClaimSpec spec = 2; // Status describes whether the claim is ready to use and what has been allocated. @@ -1429,8 +1680,8 @@ message ResourceClaimStatus { // Allocation is set once the claim has been allocated successfully. // // +optional - // +k8s:optional - // +k8s:update=NoModify + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:update=NoModify optional AllocationResult allocation = 1; // ReservedFor indicates which entities are currently allowed to use @@ -1458,10 +1709,10 @@ message ResourceClaimStatus { // +listMapKey=uid // +patchStrategy=merge // +patchMergeKey=uid - // +k8s:optional - // +k8s:listType=map - // +k8s:listMapKey=uid - // +k8s:maxItems=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=uid + // +k8s:alpha(since: "1.36")=+k8s:maxItems=256 repeated ResourceClaimConsumerReference reservedFor = 2; // Devices contains the status of each device allocated for this @@ -1469,18 +1720,18 @@ message ResourceClaimStatus { // information. Entries are owned by their respective drivers. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=map // +listMapKey=driver // +listMapKey=device // +listMapKey=pool // +listMapKey=shareID // +featureGate=DRAResourceClaimDeviceStatus - // +k8s:listType=map - // +k8s:listMapKey=driver - // +k8s:listMapKey=device - // +k8s:listMapKey=pool - // +k8s:listMapKey=shareID + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=driver + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=device + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=pool + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=shareID repeated AllocatedDeviceStatus devices = 4; } @@ -1594,6 +1845,7 @@ message ResourceSlice { // Contains the information published by the driver. // // Changing the spec automatically increments the metadata.generation number. + // +required optional ResourceSliceSpec spec = 2; } @@ -1668,7 +1920,7 @@ message ResourceSliceSpec { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +zeroOrOneOf=ResourceSliceType repeated Device devices = 6; @@ -1694,14 +1946,14 @@ message ResourceSliceSpec { // The maximum number of counter sets is 8. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name // +featureGate=DRAPartitionableDevices // +zeroOrOneOf=ResourceSliceType - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 repeated CounterSet sharedCounters = 8; } diff --git a/staging/src/k8s.io/api/resource/v1beta2/generated.protomessage.pb.go b/staging/src/k8s.io/api/resource/v1beta2/generated.protomessage.pb.go deleted file mode 100644 index b6417d18c240f..0000000000000 --- a/staging/src/k8s.io/api/resource/v1beta2/generated.protomessage.pb.go +++ /dev/null @@ -1,108 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta2 - -func (*AllocatedDeviceStatus) ProtoMessage() {} - -func (*AllocationResult) ProtoMessage() {} - -func (*CELDeviceSelector) ProtoMessage() {} - -func (*CapacityRequestPolicy) ProtoMessage() {} - -func (*CapacityRequestPolicyRange) ProtoMessage() {} - -func (*CapacityRequirements) ProtoMessage() {} - -func (*Counter) ProtoMessage() {} - -func (*CounterSet) ProtoMessage() {} - -func (*Device) ProtoMessage() {} - -func (*DeviceAllocationConfiguration) ProtoMessage() {} - -func (*DeviceAllocationResult) ProtoMessage() {} - -func (*DeviceAttribute) ProtoMessage() {} - -func (*DeviceCapacity) ProtoMessage() {} - -func (*DeviceClaim) ProtoMessage() {} - -func (*DeviceClaimConfiguration) ProtoMessage() {} - -func (*DeviceClass) ProtoMessage() {} - -func (*DeviceClassConfiguration) ProtoMessage() {} - -func (*DeviceClassList) ProtoMessage() {} - -func (*DeviceClassSpec) ProtoMessage() {} - -func (*DeviceConfiguration) ProtoMessage() {} - -func (*DeviceConstraint) ProtoMessage() {} - -func (*DeviceCounterConsumption) ProtoMessage() {} - -func (*DeviceRequest) ProtoMessage() {} - -func (*DeviceRequestAllocationResult) ProtoMessage() {} - -func (*DeviceSelector) ProtoMessage() {} - -func (*DeviceSubRequest) ProtoMessage() {} - -func (*DeviceTaint) ProtoMessage() {} - -func (*DeviceToleration) ProtoMessage() {} - -func (*ExactDeviceRequest) ProtoMessage() {} - -func (*NetworkDeviceData) ProtoMessage() {} - -func (*OpaqueDeviceConfiguration) ProtoMessage() {} - -func (*ResourceClaim) ProtoMessage() {} - -func (*ResourceClaimConsumerReference) ProtoMessage() {} - -func (*ResourceClaimList) ProtoMessage() {} - -func (*ResourceClaimSpec) ProtoMessage() {} - -func (*ResourceClaimStatus) ProtoMessage() {} - -func (*ResourceClaimTemplate) ProtoMessage() {} - -func (*ResourceClaimTemplateList) ProtoMessage() {} - -func (*ResourceClaimTemplateSpec) ProtoMessage() {} - -func (*ResourcePool) ProtoMessage() {} - -func (*ResourceSlice) ProtoMessage() {} - -func (*ResourceSliceList) ProtoMessage() {} - -func (*ResourceSliceSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/resource/v1beta2/register.go b/staging/src/k8s.io/api/resource/v1beta2/register.go index 5e676a0549702..d08534c9e00ee 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/register.go +++ b/staging/src/k8s.io/api/resource/v1beta2/register.go @@ -46,6 +46,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &DeviceClass{}, &DeviceClassList{}, + &DeviceTaintRule{}, + &DeviceTaintRuleList{}, &ResourceClaim{}, &ResourceClaimList{}, &ResourceClaimTemplate{}, diff --git a/staging/src/k8s.io/api/resource/v1beta2/types.go b/staging/src/k8s.io/api/resource/v1beta2/types.go index 49534348843ce..47a46355f0ee0 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/types.go +++ b/staging/src/k8s.io/api/resource/v1beta2/types.go @@ -35,6 +35,10 @@ const ( // special ResourceClaim. Its single valid value is "true". // This is used only inside the scheduler. ExtendedResourceClaimAnnotation = "resource.kubernetes.io/extended-resource-claim" + // PodResourceClaimAnnotation is the annotation set on template-generated + // ResourceClaims by the ResourceClaim controller. Its value is the + // pod.spec.resourceClaims[].name for which the claim was generated. + PodResourceClaimAnnotation = "resource.kubernetes.io/pod-claim-name" // Resource device class prefix is for generating implicit extended resource // name for a device class when its ExtendedResourceName field is not // specified. The generated name is this prefix + the device class name. @@ -82,6 +86,7 @@ type ResourceSlice struct { // Contains the information published by the driver. // // Changing the spec automatically increments the metadata.generation number. + // +required Spec ResourceSliceSpec `json:"spec" protobuf:"bytes,2,name=spec"` } @@ -155,7 +160,7 @@ type ResourceSliceSpec struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +zeroOrOneOf=ResourceSliceType Devices []Device `json:"devices,omitempty" protobuf:"bytes,6,name=devices"` @@ -181,14 +186,14 @@ type ResourceSliceSpec struct { // The maximum number of counter sets is 8. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name // +featureGate=DRAPartitionableDevices // +zeroOrOneOf=ResourceSliceType - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 SharedCounters []CounterSet `json:"sharedCounters,omitempty" protobuf:"bytes,8,name=sharedCounters"` } @@ -205,8 +210,8 @@ type CounterSet struct { // It must be a DNS label. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name Name string `json:"name" protobuf:"bytes,1,name=name"` // Counters defines the set of counters for this CounterSet @@ -215,6 +220,8 @@ type CounterSet struct { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"` } @@ -261,7 +268,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 @@ -298,6 +312,7 @@ type Device struct { // The maximum number of attributes and capacities combined is 32. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Attributes map[QualifiedName]DeviceAttribute `json:"attributes,omitempty" protobuf:"bytes,2,rep,name=attributes"` // Capacity defines the set of capacities for this device. @@ -318,13 +333,13 @@ type Device struct { // device is 2. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=atomic // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=counterSet + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=counterSet // +featureGate=DRAPartitionableDevices - // +k8s:maxItems=2 + // +k8s:alpha(since: "1.36")=+k8s:maxItems=2 ConsumesCounters []DeviceCounterConsumption `json:"consumesCounters,omitempty" protobuf:"bytes,4,rep,name=consumesCounters"` // NodeName identifies the node where the device is available. @@ -365,12 +380,13 @@ type Device struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Taints []DeviceTaint `json:"taints,omitempty" protobuf:"bytes,8,rep,name=taints"` // BindsToNode indicates if the usage of an allocation involving this device @@ -378,7 +394,7 @@ type Device struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional @@ -394,14 +410,14 @@ type Device struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingConditions []string `json:"bindingConditions,omitempty" protobuf:"bytes,10,rep,name=bindingConditions"` // BindingFailureConditions defines the conditions for binding failure. @@ -412,14 +428,14 @@ type Device struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingFailureConditions []string `json:"bindingFailureConditions,omitempty" protobuf:"bytes,11,rep,name=bindingFailureConditions"` // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -430,6 +446,59 @@ type Device struct { // +optional // +featureGate=DRAConsumableCapacity AllowMultipleAllocations *bool `json:"allowMultipleAllocations,omitempty" protobuf:"bytes,12,opt,name=allowMultipleAllocations"` + + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + // +optional + // +featureGate=DRANodeAllocatableResources + NodeAllocatableResourceMappings map[v1.ResourceName]NodeAllocatableResourceMapping `json:"nodeAllocatableResourceMappings,omitempty" protobuf:"bytes,13,opt,name=nodeAllocatableResourceMappings"` +} + +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +type NodeAllocatableResourceMapping struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + // +optional + CapacityKey *QualifiedName `json:"capacityKey,omitempty" protobuf:"bytes,1,opt,name=capacityKey"` + + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + // +optional + AllocationMultiplier *resource.Quantity `json:"allocationMultiplier,omitempty" protobuf:"bytes,2,opt,name=allocationMultiplier"` } // DeviceCounterConsumption defines a set of counters that @@ -439,8 +508,8 @@ type DeviceCounterConsumption struct { // counters defined will be consumed. // // +required - // +k8s:required - // +k8s:format=k8s-short-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-short-name CounterSet string `json:"counterSet" protobuf:"bytes,1,opt,name=counterSet"` // Counters defines the counters that will be consumed by the device. @@ -448,6 +517,8 @@ type DeviceCounterConsumption struct { // The maximum number of counters is 32. // // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:eachKey=+k8s:format=k8s-short-name Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"` } @@ -568,6 +639,10 @@ type CapacityRequestPolicyRange struct { // Limit for the sum of the number of entries in both attributes and capacity. const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32 +// Limit per device for the total number of string, version, bool or int values +// in list and non-list attributes. +const ResourceSliceMaxAttributeValuesPerDevice = 48 + // QualifiedName is the name of a device attribute or capacity. // // Attributes and capacities are defined either by the owner of the specific @@ -607,31 +682,79 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +k8s:optional - // +k8s:unionMember + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` + + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + IntValues []int64 `json:"ints,omitempty" protobuf:"varint,6,opt,name=ints"` + + // BoolValues is a non-empty list of true/false values. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + BoolValues []bool `json:"bools,omitempty" protobuf:"varint,7,opt,name=bools"` + + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + StringValues []string `json:"strings,omitempty" protobuf:"bytes,8,opt,name=strings"` + + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + // + // +optional + // +listType=atomic + // +k8s:listType=atomic + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:unionMember + // +featureGate=DRAListTypeAttributes + VersionValues []string `json:"versions,omitempty" protobuf:"bytes,9,opt,name=versions"` } // DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value. @@ -666,7 +789,7 @@ type DeviceTaint struct { // Consumers must treat unknown effects like None. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"` // ^^^^ @@ -682,9 +805,18 @@ type DeviceTaint struct { // which will enable adding new enums within a single release without // ratcheting. - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). + // // +optional TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"` @@ -696,7 +828,7 @@ type DeviceTaint struct { } // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceTaintEffect string const ( @@ -712,6 +844,156 @@ const ( DeviceTaintEffectNoExecute DeviceTaintEffect = "NoExecute" ) +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// DeviceTaintRule adds one taint to all devices which match the selector. +// This has the same effect as if the taint was specified directly +// in the ResourceSlice by the DRA driver. +type DeviceTaintRule struct { + metav1.TypeMeta `json:",inline"` + // Standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec specifies the selector and one taint. + // + // Changing the spec automatically increments the metadata.generation number. + // +required + Spec DeviceTaintRuleSpec `json:"spec" protobuf:"bytes,2,name=spec"` + + // Status provides information about what was requested in the spec. + // + // +optional + Status DeviceTaintRuleStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// DeviceTaintRuleSpec specifies the selector and one taint. +type DeviceTaintRuleSpec struct { + // DeviceSelector defines which device(s) the taint is applied to. + // All selector criteria must be satisfied for a device to + // match. The empty selector matches all devices. Without + // a selector, no devices are matches. + // + // +optional + DeviceSelector *DeviceTaintSelector `json:"deviceSelector,omitempty" protobuf:"bytes,1,opt,name=deviceSelector"` + + // The taint that gets applied to matching devices. + // + // +required + Taint DeviceTaint `json:"taint,omitempty" protobuf:"bytes,2,rep,name=taint"` +} + +// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. +// The empty selector matches all devices. Without a selector, no devices +// are matched. +type DeviceTaintSelector struct { + // If DeviceClassName is set, the selectors defined there must be + // satisfied by a device to be selected. This field corresponds + // to class.metadata.name. + // + // +optional + // + // Tombstoned since 1.35 because it turned out that supporting this in all cases + // would depend on copying the device attributes into the ResourceClaim allocation + // result. Without that the eviction controller cannot evaluate these CEL expressions. + // + // DeviceClassName *string `json:"deviceClassName,omitempty" protobuf:"bytes,1,opt,name=deviceClassName"` + + // If driver is set, only devices from that driver are selected. + // This fields corresponds to slice.spec.driver. + // + // +optional + Driver *string `json:"driver,omitempty" protobuf:"bytes,2,opt,name=driver"` + + // If pool is set, only devices in that pool are selected. + // + // Also setting the driver name may be useful to avoid + // ambiguity when different drivers use the same pool name, + // but this is not required because selecting pools from + // different drivers may also be useful, for example when + // drivers with node-local devices use the node name as + // their pool name. + // + // +optional + Pool *string `json:"pool,omitempty" protobuf:"bytes,3,opt,name=pool"` + + // If device is set, only devices with that name are selected. + // This field corresponds to slice.spec.devices[].name. + // + // Setting also driver and pool may be required to avoid ambiguity, + // but is not required. + // + // +optional + Device *string `json:"device,omitempty" protobuf:"bytes,4,opt,name=device"` + + // Selectors contains the same selection criteria as a ResourceClaim. + // Currently, CEL expressions are supported. All of these selectors + // must be satisfied. + // + // +optional + // +listType=atomic + // + // Tombstoned since 1.35 because it turned out that supporting this in all cases + // would depend on copying the device attributes into the ResourceClaim allocation + // result. Without that the eviction controller cannot evaluate these CEL expressions. + // + // Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,5,rep,name=selectors"` +} + +// DeviceTaintRuleStatus provides information about an on-going pod eviction. +type DeviceTaintRuleStatus struct { + // Conditions provide information about the state of the DeviceTaintRule + // and the cluster at some point in time, + // in a machine-readable and human-readable format. + // + // The following condition is currently defined as part of this API, more may + // get added: + // - Type: EvictionInProgress + // - Status: True if there are currently pods which need to be evicted, False otherwise + // (includes the effects which don't cause eviction). + // - Reason: not specified, may change + // - Message: includes information about number of pending pods and already evicted pods + // in a human-readable format, updated periodically, may change + // + // For `effect: None`, the condition above gets set once for each change to + // the spec, with the message containing information about what would happen + // if the effect was `NoExecute`. This feedback can be used to decide whether + // changing the effect to `NoExecute` will work as intended. It only gets + // set once to avoid having to constantly update the status. + // + // Must have 8 or fewer entries. + // + // +optional + // +listType=map + // +listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +// DeviceTaintRuleStatusMaxConditions is the maximum number of conditions in DeviceTaintRuleStatus. +const DeviceTaintRuleStatusMaxConditions = 8 + +// DeviceTaintConditionEvictionInProgress is the publicly documented condition type for the DeviceTaintRuleStatus. +const DeviceTaintConditionEvictionInProgress = "EvictionInProgress" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.36 + +// DeviceTaintRuleList is a collection of DeviceTaintRules. +type DeviceTaintRuleList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of DeviceTaintRules. + Items []DeviceTaintRule `json:"items" protobuf:"bytes,2,rep,name=items"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.33 @@ -729,7 +1011,7 @@ type ResourceSliceList struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.33 -// +k8s:supportsSubresource=/status +// +k8s:supportsSubresource="/status" // ResourceClaim describes a request for access to resources in the cluster, // for use by workloads. For example, if a workload needs an accelerator device @@ -747,7 +1029,7 @@ type ResourceClaim struct { // Spec describes what is being requested and how to configure it. // The spec is immutable. - // +k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:immutable Spec ResourceClaimSpec `json:"spec" protobuf:"bytes,2,name=spec"` // Status describes whether the claim is ready to use and what has been allocated. @@ -775,11 +1057,11 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []DeviceRequest `json:"requests" protobuf:"bytes,1,name=requests"` // These constraints must be satisfied by the set of devices that get @@ -787,8 +1069,8 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Constraints []DeviceConstraint `json:"constraints,omitempty" protobuf:"bytes,2,opt,name=constraints"` // This field holds configuration for multiple potential drivers which @@ -797,8 +1079,8 @@ type DeviceClaim struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Config []DeviceClaimConfiguration `json:"config,omitempty" protobuf:"bytes,3,opt,name=config"` // Potential future extension, ignored by older schedulers. This is @@ -849,7 +1131,7 @@ type DeviceRequest struct { // // +optional // +oneOf=deviceRequestType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional Exactly *ExactDeviceRequest `json:"exactly,omitempty" protobuf:"bytes,2,name=exactly"` // FirstAvailable contains subrequests, of which exactly one will be @@ -870,11 +1152,11 @@ type DeviceRequest struct { // +oneOf=deviceRequestType // +listType=atomic // +featureGate=DRAPrioritizedList - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=map - // +k8s:listMapKey=name - // +k8s:maxItems=8 + // +k8s:alpha(since: "1.36")=+k8s:unique=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=name + // +k8s:alpha(since: "1.36")=+k8s:maxItems=8 FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,3,name=firstAvailable"` } @@ -902,8 +1184,8 @@ type ExactDeviceRequest struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,2,name=selectors"` // AllocationMode and its related fields define how devices are allocated @@ -926,7 +1208,7 @@ type ExactDeviceRequest struct { // requests with unknown modes. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,3,opt,name=allocationMode"` // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -965,12 +1247,13 @@ type ExactDeviceRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // Capacity define resource requirements against each capacity. @@ -1022,8 +1305,8 @@ type DeviceSubRequest struct { // to reference. // // +required - // +k8s:required - // +k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` // Selectors define criteria which must be satisfied by a specific @@ -1033,8 +1316,8 @@ type DeviceSubRequest struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` // AllocationMode and its related fields define how devices are allocated @@ -1056,6 +1339,7 @@ type DeviceSubRequest struct { // requests with unknown modes. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` // Count is used only when the count mode is "ExactCount". Must be greater than zero. @@ -1080,12 +1364,13 @@ type DeviceSubRequest struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // Capacity define resource requirements against each capacity. @@ -1142,7 +1427,7 @@ const ( ) // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceAllocationMode string // Valid [DeviceRequest.CountMode] values. @@ -1211,6 +1496,14 @@ type CELDeviceSelector struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. @@ -1261,10 +1554,10 @@ type DeviceConstraint struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` // MatchAttribute requires that all devices in question have this @@ -1278,12 +1571,17 @@ type DeviceConstraint struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. // // +optional // +oneOf=ConstraintType - // +k8s:optional - // +k8s:format=k8s-resource-fully-qualified-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-fully-qualified-name MatchAttribute *FullyQualifiedName `json:"matchAttribute,omitempty" protobuf:"bytes,2,opt,name=matchAttribute"` // Potential future extension, not part of the current design: @@ -1299,6 +1597,11 @@ type DeviceConstraint struct { // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device @@ -1324,10 +1627,10 @@ type DeviceClaimConfiguration struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"` @@ -1341,7 +1644,7 @@ type DeviceConfiguration struct { // // +optional // +oneOf=ConfigurationType - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional Opaque *OpaqueDeviceConfiguration `json:"opaque,omitempty" protobuf:"bytes,1,opt,name=opaque"` } @@ -1358,8 +1661,9 @@ type OpaqueDeviceConfiguration struct { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:required - // +k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless Driver string `json:"driver" protobuf:"bytes,1,name=driver"` // Parameters can contain arbitrary data. It is the responsibility of @@ -1385,8 +1689,8 @@ type DeviceToleration struct { // Must be a label name. // // +optional - // +k8s:optional - // +k8s:format=k8s-label-key + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-label-key Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"` // Operator represents a key's relationship to the value. @@ -1396,6 +1700,7 @@ type DeviceToleration struct { // // +optional // +default="Equal" + // +k8s:alpha(since: "1.36")=+k8s:optional Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"` // Value is the taint value the toleration matches to. @@ -1409,6 +1714,7 @@ type DeviceToleration struct { // When specified, allowed values are NoSchedule and NoExecute. // // +optional + // +k8s:alpha(since: "1.36")=+k8s:optional Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"` // TolerationSeconds represents the period of time the toleration (which must be @@ -1425,7 +1731,7 @@ type DeviceToleration struct { // A toleration operator is the set of operators that can be used in a toleration. // // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type DeviceTolerationOperator string const ( @@ -1439,8 +1745,8 @@ type ResourceClaimStatus struct { // Allocation is set once the claim has been allocated successfully. // // +optional - // +k8s:optional - // +k8s:update=NoModify + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:update=NoModify Allocation *AllocationResult `json:"allocation,omitempty" protobuf:"bytes,1,opt,name=allocation"` // ReservedFor indicates which entities are currently allowed to use @@ -1468,10 +1774,10 @@ type ResourceClaimStatus struct { // +listMapKey=uid // +patchStrategy=merge // +patchMergeKey=uid - // +k8s:optional - // +k8s:listType=map - // +k8s:listMapKey=uid - // +k8s:maxItems=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=uid + // +k8s:alpha(since: "1.36")=+k8s:maxItems=256 ReservedFor []ResourceClaimConsumerReference `json:"reservedFor,omitempty" protobuf:"bytes,2,opt,name=reservedFor" patchStrategy:"merge" patchMergeKey:"uid"` // DeallocationRequested is tombstoned since Kubernetes 1.32 where @@ -1484,18 +1790,18 @@ type ResourceClaimStatus struct { // information. Entries are owned by their respective drivers. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +listType=map // +listMapKey=driver // +listMapKey=device // +listMapKey=pool // +listMapKey=shareID // +featureGate=DRAResourceClaimDeviceStatus - // +k8s:listType=map - // +k8s:listMapKey=driver - // +k8s:listMapKey=device - // +k8s:listMapKey=pool - // +k8s:listMapKey=shareID + // +k8s:alpha(since: "1.36")=+k8s:listType=map + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=driver + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=device + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=pool + // +k8s:alpha(since: "1.36")=+k8s:listMapKey=shareID Devices []AllocatedDeviceStatus `json:"devices,omitempty" protobuf:"bytes,4,opt,name=devices"` } @@ -1544,7 +1850,7 @@ type AllocationResult struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. // // +optional @@ -1558,8 +1864,8 @@ type DeviceAllocationResult struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Results []DeviceRequestAllocationResult `json:"results,omitempty" protobuf:"bytes,1,opt,name=results"` // This field is a combination of all the claim and class configuration parameters. @@ -1572,8 +1878,8 @@ type DeviceAllocationResult struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=64 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=64 Config []DeviceAllocationConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` } @@ -1602,8 +1908,9 @@ type DeviceRequestAllocationResult struct { // vendor of the driver. It should use only lower case characters. // // +required - // +k8s:format=k8s-long-name-caseless - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-long-name-caseless + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 + // +k8s:alpha(since: "1.36")=+k8s:required Driver string `json:"driver" protobuf:"bytes,2,name=driver"` // This name together with the driver name and the device name field @@ -1613,8 +1920,8 @@ type DeviceRequestAllocationResult struct { // DNS sub-domains separated by slashes. // // +required - // +k8s:required - // +k8s:format=k8s-resource-pool-name + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-resource-pool-name Pool string `json:"pool" protobuf:"bytes,3,name=pool"` // Device references one device instance via its name in the driver's @@ -1640,38 +1947,39 @@ type DeviceRequestAllocationResult struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. // // +optional // +listType=atomic // +featureGate=DRADeviceTaints + // +k8s:alpha(since: "1.36")=+k8s:optional Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingConditions []string `json:"bindingConditions,omitempty" protobuf:"bytes,7,rep,name=bindingConditions"` // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. // // +optional // +listType=atomic // +featureGate=DRADeviceBindingConditions,DRAResourceClaimDeviceStatus - // +k8s:optional - // +k8s:maxItems=4 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=4 BindingFailureConditions []string `json:"bindingFailureConditions,omitempty" protobuf:"bytes,8,rep,name=bindingFailureConditions"` // ShareID uniquely identifies an individual allocation share of the device, @@ -1681,8 +1989,8 @@ type DeviceRequestAllocationResult struct { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid ShareID *types.UID `json:"shareID,omitempty" protobuf:"bytes,9,opt,name=shareID"` // ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. @@ -1706,7 +2014,7 @@ type DeviceAllocationConfiguration struct { // or from a claim. // // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required Source AllocationConfigSource `json:"source" protobuf:"bytes,1,name=source"` // Requests lists the names of requests where the configuration applies. @@ -1718,17 +2026,17 @@ type DeviceAllocationConfiguration struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` DeviceConfiguration `json:",inline" protobuf:"bytes,3,name=deviceConfiguration"` } // +enum -// +k8s:enum +// +k8s:alpha(since: "1.36")=+k8s:enum type AllocationConfigSource string // Valid [DeviceAllocationConfiguration.Source] values. @@ -1767,8 +2075,8 @@ type DeviceClass struct { metav1.TypeMeta `json:",inline"` // Standard object metadata // +optional - // +k8s:subfield(name)=+k8s:optional - // +k8s:subfield(name)=+k8s:format=k8s-long-name + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:subfield(name)=+k8s:format=k8s-long-name metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec defines what can be allocated and how to configure it. @@ -1789,8 +2097,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,1,opt,name=selectors"` // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -1801,8 +2109,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic - // +k8s:optional - // +k8s:maxItems=32 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxItems=32 Config []DeviceClassConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` // SuitableNodes is tombstoned since Kubernetes 1.32 where @@ -1819,11 +2127,11 @@ type DeviceClassSpec struct { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. // +optional // +featureGate=DRAExtendedResource - // +k8s:optional - // +k8s:format=k8s-extended-resource-name + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-extended-resource-name ExtendedResourceName *string `json:"extendedResourceName,omitempty" protobuf:"bytes,4,opt,name=extendedResourceName"` } @@ -1949,8 +2257,8 @@ type AllocatedDeviceStatus struct { // // +optional // +featureGate=DRAConsumableCapacity - // +k8s:optional - // +k8s:format=k8s-uuid + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:format=k8s-uuid ShareID *string `json:"shareID,omitempty" protobuf:"bytes,7,opt,name=shareID"` // Conditions contains the latest observation of the device's state. @@ -1974,7 +2282,7 @@ type AllocatedDeviceStatus struct { // NetworkData contains network-related information specific to the device. // // +optional - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional NetworkData *NetworkDeviceData `json:"networkData,omitempty" protobuf:"bytes,6,opt,name=networkData"` } @@ -1986,11 +2294,11 @@ type NetworkDeviceData struct { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=256 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=256 InterfaceName string `json:"interfaceName,omitempty" protobuf:"bytes,1,opt,name=interfaceName"` // IPs lists the network addresses assigned to the device's network interface. @@ -2001,18 +2309,18 @@ type NetworkDeviceData struct { // // +optional // +listType=atomic - // +k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:optional // +k8s:listType=atomic - // +k8s:unique=set - // +k8s:maxItems=16 + // +k8s:alpha(since: "1.36")=+k8s:unique=set + // +k8s:alpha(since: "1.36")=+k8s:maxItems=16 IPs []string `json:"ips,omitempty" protobuf:"bytes,2,opt,name=ips"` // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. // // +optional - // +k8s:optional - // +k8s:maxLength=128 + // +k8s:alpha(since: "1.36")=+k8s:optional + // +k8s:alpha(since: "1.36")=+k8s:maxBytes=128 HardwareAddress string `json:"hardwareAddress,omitempty" protobuf:"bytes,3,opt,name=hardwareAddress"` } diff --git a/staging/src/k8s.io/api/resource/v1beta2/types_swagger_doc_generated.go b/staging/src/k8s.io/api/resource/v1beta2/types_swagger_doc_generated.go index a086f9bf23ee0..122170ec25165 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/resource/v1beta2/types_swagger_doc_generated.go @@ -46,7 +46,7 @@ var map_AllocationResult = map[string]string{ "": "AllocationResult contains attributes of an allocated resource.", "devices": "Devices is the result of allocating devices.", "nodeSelector": "NodeSelector defines where the allocated resources are available. If unset, they are available everywhere.", - "allocationTimestamp": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", + "allocationTimestamp": "AllocationTimestamp stores the time when the resources were allocated. This field is not guaranteed to be set, in which case that time is unknown.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gate.", } func (AllocationResult) SwaggerDoc() map[string]string { @@ -55,7 +55,7 @@ func (AllocationResult) SwaggerDoc() map[string]string { var map_CELDeviceSelector = map[string]string{ "": "CELDeviceSelector contains a CEL expression for selecting a device.", - "expression": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", + "expression": "Expression is a CEL expression which evaluates a single device. It must evaluate to true when the device under consideration satisfies the desired criteria, and false when it does not. Any other result is an error and causes allocation of devices to abort.\n\nThe expression's input is an object named \"device\", which carries the following properties:\n - driver (string): the name of the driver which defines this device.\n - attributes (map[string]object): the device's attributes, grouped by prefix\n (e.g. device.attributes[\"dra.example.com\"] evaluates to an object with all\n of the attributes which were prefixed by \"dra.example.com\".\n - capacity (map[string]object): the device's capacities, grouped by prefix.\n - allowMultipleAllocations (bool): the allowMultipleAllocations property of the device\n (v1.34+ with the DRAConsumableCapacity feature enabled).\n\nExample: Consider a device with driver=\"dra.example.com\", which exposes two attributes named \"model\" and \"ext.example.com/family\" and which exposes one capacity named \"modules\". This input to this expression would have the following fields:\n\n device.driver\n device.attributes[\"dra.example.com\"].model\n device.attributes[\"ext.example.com\"].family\n device.capacity[\"dra.example.com\"].modules\n\nThe device.driver field can be used to check for a specific driver, either as a high-level precondition (i.e. you only want to consider devices from this driver) or as part of a multi-clause expression that is meant to consider devices from different drivers.\n\nThe value type of each attribute is defined by the device definition, and users who write these expressions must consult the documentation for their specific drivers. The value type of each capacity is Quantity.\n\nIf an unknown prefix is used as a lookup in either device.attributes or device.capacity, an empty map will be returned. Any reference to an unknown field will cause an evaluation error and allocation to abort.\n\nA robust expression should check for the existence of attributes before referencing them.\n\nFor ease of use, the cel.bind() function is enabled, and can be used to simplify expressions that access multiple attributes with the same domain. For example:\n\n cel.bind(dra, device.attributes[\"dra.example.com\"], dra.someBool && dra.anotherBool)\n\nWhen the DRAListTypeAttributes feature gate is enabled, the includes() helper is available and it can work for both scalar and list-type attributes. It was introduced to support smooth migration from scalar attributes to list-type attributes while keeping CEL expressions simple. For example:\n\n device.attributes[\"dra.example.com\"].models.includes(\"some-model\")\n\nThe length of the expression must be smaller or equal to 10 Ki. The cost of evaluating it is also limited based on the estimated number of logical steps.", } func (CELDeviceSelector) SwaggerDoc() map[string]string { @@ -113,19 +113,20 @@ func (CounterSet) SwaggerDoc() map[string]string { } var map_Device = map[string]string{ - "": "Device represents one individual hardware instance that can be selected based on its attributes. Besides the name, exactly one field must be set.", - "name": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", - "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe maximum number of device counter consumptions per device is 2.", - "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", - "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", - "bindsToNode": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingConditions": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingFailureConditions": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "allowMultipleAllocations": "AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests.\n\nIf AllowMultipleAllocations is set to true, the device can be allocated more than once, and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not.", + "": "Device represents one individual hardware instance that can be selected based on its attributes. Besides the name, exactly one field must be set.", + "name": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", + "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe maximum number of device counter consumptions per device is 2.", + "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 16. If taints are set for any device in a ResourceSlice, then the maximum number of allowed devices per ResourceSlice is 64 instead of 128.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", + "bindsToNode": "BindsToNode indicates if the usage of an allocation involving this device has to be limited to exactly the node that was chosen when allocating the claim. If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector to match the node where the allocation was made.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingConditions": "BindingConditions defines the conditions for proceeding with binding. All of these conditions must be set in the per-device status conditions with a value of True to proceed with binding the pod to the node while scheduling the pod.\n\nThe maximum number of binding conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingFailureConditions": "BindingFailureConditions defines the conditions for binding failure. They may be set in the per-device status conditions. If any is set to \"True\", a binding failure occurred.\n\nThe maximum number of binding failure conditions is 4.\n\nThe conditions must be a valid condition type string.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "allowMultipleAllocations": "AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests.\n\nIf AllowMultipleAllocations is set to true, the device can be allocated more than once, and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not.", + "nodeAllocatableResourceMappings": "NodeAllocatableResourceMappings defines the mapping of node resources that are managed by the DRA driver exposing this device. This includes resources currently reported in v1.Node `status.allocatable` that are not extended resources (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). Examples include \"cpu\", \"memory\", \"ephemeral-storage\", and hugepages. In addition to standard requests made through the Pod `spec`, these resources can also be requested through claims and allocated by the DRA driver. For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory dependencies of an accelerator device. The keys of this map are the node-allocatable resource names (e.g., \"cpu\", \"memory\"). Extended resource names are not permitted as keys.", } func (Device) SwaggerDoc() map[string]string { @@ -153,11 +154,15 @@ func (DeviceAllocationResult) SwaggerDoc() map[string]string { } var map_DeviceAttribute = map[string]string{ - "": "DeviceAttribute must have exactly one field set.", - "int": "IntValue is a number.", - "bool": "BoolValue is a true/false value.", - "string": "StringValue is a string. Must not be longer than 64 characters.", - "version": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", + "": "DeviceAttribute must have exactly one field set.", + "int": "IntValue is a number.", + "bool": "BoolValue is a true/false value.", + "string": "StringValue is a string. Must not be longer than 64 characters.", + "version": "VersionValue is a semantic version according to semver.org spec 2.0.0. Must not be longer than 64 characters.", + "ints": "IntValues is a non-empty list of numbers.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "bools": "BoolValues is a non-empty list of true/false values.", + "strings": "StringValues is a non-empty list of strings. Each string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", + "versions": "VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. Each version string must not be longer than 64 characters.\n\nThis is an alpha field and requires enabling the DRAListTypeAttributes feature gate.", } func (DeviceAttribute) SwaggerDoc() map[string]string { @@ -226,7 +231,7 @@ var map_DeviceClassSpec = map[string]string{ "": "DeviceClassSpec is used in a [DeviceClass] to define what can be allocated and how to configure it.", "selectors": "Each selector must be satisfied by a device which is claimed via this class.", "config": "Config defines configuration parameters that apply to each device that is claimed via this class. Some classses may potentially be satisfied by multiple drivers, so each instance of a vendor configuration applies to exactly one driver.\n\nThey are passed to the driver, but are not considered while allocating the claim.", - "extendedResourceName": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is an alpha field.", + "extendedResourceName": "ExtendedResourceName is the extended resource name for the devices of this class. The devices of this class can be used to satisfy a pod's extended resource requests. It has the same format as the name of a pod's extended resource. It should be unique among all the device classes in a cluster. If two device classes have the same name, then the class created later is picked to satisfy a pod's extended resource requests. If two classes are created at the same time, then the name of the class lexicographically sorted first is picked.\n\nThis is a beta field.", } func (DeviceClassSpec) SwaggerDoc() map[string]string { @@ -245,8 +250,8 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", - "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", - "distinctAttribute": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", + "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics(i.e., element order and duplicates are ignored): list-valued attributes match when the intersection across all devices is non-empty. Scalar values are treated as singleton sets for backward compatibility.\n\nMust include the domain qualifier.", + "distinctAttribute": "DistinctAttribute requires that all devices in question have this attribute and that its type and value are unique across those devices.\n\nWhen the DRAListTypeAttributes feature gate is enabled, comparison uses set semantics (i.e., element order and duplicates are ignored): list-valued attributes must be pairwise disjoint across devices. Scalar values are treated as singleton sets for backward compatibility.\n\nThis acts as the inverse of MatchAttribute.\n\nThis constraint is used to avoid allocating multiple requests to the same device by ensuring attribute-level differentiation.\n\nThis is useful for scenarios where resource requests must be fulfilled by separate physical devices. For example, a container requests two network interfaces that must be allocated from two different physical NICs.", } func (DeviceConstraint) SwaggerDoc() map[string]string { @@ -281,9 +286,9 @@ var map_DeviceRequestAllocationResult = map[string]string{ "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", "adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", - "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", - "bindingConditions": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", - "bindingFailureConditions": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", + "bindingConditions": "BindingConditions contains a copy of the BindingConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", + "bindingFailureConditions": "BindingFailureConditions contains a copy of the BindingFailureConditions from the corresponding ResourceSlice at the time of allocation.\n\nThis is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus feature gates.", "shareID": "ShareID uniquely identifies an individual allocation share of the device, used when the device supports multiple simultaneous allocations. It serves as an additional map key to differentiate concurrent shares of the same device.", "consumedCapacity": "ConsumedCapacity tracks the amount of capacity consumed per device as part of the claim request. The consumed amount may differ from the requested amount: it is rounded up to the nearest valid value based on the device’s requestPolicy if applicable (i.e., may not be less than the requested amount).\n\nThe total consumed capacity for each device must not exceed the DeviceCapacity's Value.\n\nThis field is populated only for devices that allow multiple allocations. All capacity entries are included, even if the consumed amount is zero.", } @@ -308,7 +313,7 @@ var map_DeviceSubRequest = map[string]string{ "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "capacity": "Capacity define resource requirements against each capacity.\n\nIf this field is unset and the device supports multiple allocations, the default value will be applied to each capacity according to requestPolicy. For the capacity that has no requestPolicy, default is the full capacity value.\n\nApplies to each device allocation. If Count > 1, the request fails if there aren't enough devices that meet the requirements. If AllocationMode is set to All, the request fails if there are devices that otherwise match the request, and have this capacity, with a value >= the requested amount, but which cannot be allocated to this request.", } @@ -321,13 +326,64 @@ var map_DeviceTaint = map[string]string{ "key": "The taint key to be applied to a device. Must be a label name.", "value": "The taint value corresponding to the taint key. Must be a label value.", "effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them.\n\nValid effects are None, NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here. More effects may get added in the future. Consumers must treat unknown effects like None.", - "timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", + "timeAdded": "TimeAdded represents the time at which the taint was added or (only in a DeviceTaintRule) the effect was modified. Added automatically during create or update if not set.\n\nIn addition, in a DeviceTaintRule a value provided during an update gets replaced with the current time if the provided value is the same as the old one and the new effect is different. Changing the key and/or value while keeping the effect unchanged is possible and does not update the time stamp because the eviction which uses it is either already started (NoExecute) or not started yet (NoEffect, NoSchedule).", } func (DeviceTaint) SwaggerDoc() map[string]string { return map_DeviceTaint } +var map_DeviceTaintRule = map[string]string{ + "": "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.", + "metadata": "Standard object metadata", + "spec": "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number.", + "status": "Status provides information about what was requested in the spec.", +} + +func (DeviceTaintRule) SwaggerDoc() map[string]string { + return map_DeviceTaintRule +} + +var map_DeviceTaintRuleList = map[string]string{ + "": "DeviceTaintRuleList is a collection of DeviceTaintRules.", + "metadata": "Standard list metadata", + "items": "Items is the list of DeviceTaintRules.", +} + +func (DeviceTaintRuleList) SwaggerDoc() map[string]string { + return map_DeviceTaintRuleList +} + +var map_DeviceTaintRuleSpec = map[string]string{ + "": "DeviceTaintRuleSpec specifies the selector and one taint.", + "deviceSelector": "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satisfied for a device to match. The empty selector matches all devices. Without a selector, no devices are matches.", + "taint": "The taint that gets applied to matching devices.", +} + +func (DeviceTaintRuleSpec) SwaggerDoc() map[string]string { + return map_DeviceTaintRuleSpec +} + +var map_DeviceTaintRuleStatus = map[string]string{ + "": "DeviceTaintRuleStatus provides information about an on-going pod eviction.", + "conditions": "Conditions provide information about the state of the DeviceTaintRule and the cluster at some point in time, in a machine-readable and human-readable format.\n\nThe following condition is currently defined as part of this API, more may get added: - Type: EvictionInProgress - Status: True if there are currently pods which need to be evicted, False otherwise\n (includes the effects which don't cause eviction).\n- Reason: not specified, may change - Message: includes information about number of pending pods and already evicted pods\n in a human-readable format, updated periodically, may change\n\nFor `effect: None`, the condition above gets set once for each change to the spec, with the message containing information about what would happen if the effect was `NoExecute`. This feedback can be used to decide whether changing the effect to `NoExecute` will work as intended. It only gets set once to avoid having to constantly update the status.\n\nMust have 8 or fewer entries.", +} + +func (DeviceTaintRuleStatus) SwaggerDoc() map[string]string { + return map_DeviceTaintRuleStatus +} + +var map_DeviceTaintSelector = map[string]string{ + "": "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.", + "driver": "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.", + "pool": "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.", + "device": "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.", +} + +func (DeviceTaintSelector) SwaggerDoc() map[string]string { + return map_DeviceTaintSelector +} + var map_DeviceToleration = map[string]string{ "": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple using the matching operator .", "key": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.", @@ -348,7 +404,7 @@ var map_ExactDeviceRequest = map[string]string{ "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", - "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is a beta field and requires enabling the DRADeviceTaints feature gate.", "capacity": "Capacity define resource requirements against each capacity.\n\nIf this field is unset and the device supports multiple allocations, the default value will be applied to each capacity according to requestPolicy. For the capacity that has no requestPolicy, default is the full capacity value.\n\nApplies to each device allocation. If Count > 1, the request fails if there aren't enough devices that meet the requirements. If AllocationMode is set to All, the request fails if there are devices that otherwise match the request, and have this capacity, with a value >= the requested amount, but which cannot be allocated to this request.", } @@ -358,15 +414,25 @@ func (ExactDeviceRequest) SwaggerDoc() map[string]string { var map_NetworkDeviceData = map[string]string{ "": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", - "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", + "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 bytes.", "ips": "IPs lists the network addresses assigned to the device's network interface. This can include both IPv4 and IPv6 addresses. The IPs are in the CIDR notation, which includes both the address and the associated subnet mask. e.g.: \"192.0.2.5/24\" for IPv4 and \"2001:db8::5/64\" for IPv6.", - "hardwareAddress": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 characters.", + "hardwareAddress": "HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface.\n\nMust not be longer than 128 bytes.", } func (NetworkDeviceData) SwaggerDoc() map[string]string { return map_NetworkDeviceData } +var map_NodeAllocatableResourceMapping = map[string]string{ + "": "NodeAllocatableResourceMapping defines the translation between the DRA device/capacity units requested to the corresponding quantity of the node allocatable resource.", + "capacityKey": "CapacityKey references a capacity name defined as a key in the `spec.devices[*].capacity` map. When this field is set, the value associated with this key in the `status.allocation.devices.results[*].consumedCapacity` map (for a specific claim allocation) determines the base quantity for the node allocatable resource. If `allocationMultiplier` is also set, it is multiplied with the base quantity. For example, if `spec.devices[*].capacity` has an entry \"dra.example.com/memory\": \"128Gi\", and this field is set to \"dra.example.com/memory\", then for a claim allocation that consumes { \"dra.example.com/memory\": \"4Gi\" } the base quantity for the node allocatable resource mapping will be \"4Gi\", and `allocationMultiplier` should be omitted or set to \"1\".", + "allocationMultiplier": "AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim.\n\t a. A DRA driver representing each CPU core as a device would have\n {ResourceName: \"cpu\", allocationMultiplier: \"2\"} in its\n `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim,\n\t\t 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity.\n b. A GPU device that needs additional node memory per GPU allocation would\n have {ResourceName: \"memory\", allocationMultiplier: \"2Gi\"}. Each allocated\n\t\t GPU device instance of this type will account for 2Gi of memory.\n\n2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed.\n\t The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`.\n For example, if a Device's capacity \"dra.example.com/cores\" is consumed,\n and each \"core\" provides 2 \"cpu\"s, the mapping would be:\n {ResourceName: \"cpu\", capacityKey: \"dra.example.com/cores\", allocationMultiplier: \"2\"}.\n If a claim consumes 8 \"dra.example.com/cores\", the CPU footprint is 8 * 2 = 16.", +} + +func (NodeAllocatableResourceMapping) SwaggerDoc() map[string]string { + return map_NodeAllocatableResourceMapping +} + var map_OpaqueDeviceConfiguration = map[string]string{ "": "OpaqueDeviceConfiguration contains configuration parameters for a driver in a format defined by the driver vendor.", "driver": "Driver is used to determine which kubelet plugin needs to be passed these configuration parameters.\n\nAn admission policy provided by the driver developer could use this to decide whether it needs to validate them.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver. It should use only lower case characters.", diff --git a/staging/src/k8s.io/api/resource/v1beta2/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1beta2/zz_generated.deepcopy.go index 3af5c43c78a48..820ad2af61b37 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1beta2/zz_generated.deepcopy.go @@ -302,6 +302,13 @@ func (in *Device) DeepCopyInto(out *Device) { *out = new(bool) **out = **in } + if in.NodeAllocatableResourceMappings != nil { + in, out := &in.NodeAllocatableResourceMappings, &out.NodeAllocatableResourceMappings + *out = make(map[corev1.ResourceName]NodeAllocatableResourceMapping, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } return } @@ -390,6 +397,26 @@ func (in *DeviceAttribute) DeepCopyInto(out *DeviceAttribute) { *out = new(string) **out = **in } + if in.IntValues != nil { + in, out := &in.IntValues, &out.IntValues + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.BoolValues != nil { + in, out := &in.BoolValues, &out.BoolValues + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.StringValues != nil { + in, out := &in.StringValues, &out.StringValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.VersionValues != nil { + in, out := &in.VersionValues, &out.VersionValues + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -825,6 +852,143 @@ func (in *DeviceTaint) DeepCopy() *DeviceTaint { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceTaintRule) DeepCopyInto(out *DeviceTaintRule) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRule. +func (in *DeviceTaintRule) DeepCopy() *DeviceTaintRule { + if in == nil { + return nil + } + out := new(DeviceTaintRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DeviceTaintRule) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceTaintRuleList) DeepCopyInto(out *DeviceTaintRuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DeviceTaintRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleList. +func (in *DeviceTaintRuleList) DeepCopy() *DeviceTaintRuleList { + if in == nil { + return nil + } + out := new(DeviceTaintRuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DeviceTaintRuleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceTaintRuleSpec) DeepCopyInto(out *DeviceTaintRuleSpec) { + *out = *in + if in.DeviceSelector != nil { + in, out := &in.DeviceSelector, &out.DeviceSelector + *out = new(DeviceTaintSelector) + (*in).DeepCopyInto(*out) + } + in.Taint.DeepCopyInto(&out.Taint) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleSpec. +func (in *DeviceTaintRuleSpec) DeepCopy() *DeviceTaintRuleSpec { + if in == nil { + return nil + } + out := new(DeviceTaintRuleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceTaintRuleStatus) DeepCopyInto(out *DeviceTaintRuleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleStatus. +func (in *DeviceTaintRuleStatus) DeepCopy() *DeviceTaintRuleStatus { + if in == nil { + return nil + } + out := new(DeviceTaintRuleStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceTaintSelector) DeepCopyInto(out *DeviceTaintSelector) { + *out = *in + if in.Driver != nil { + in, out := &in.Driver, &out.Driver + *out = new(string) + **out = **in + } + if in.Pool != nil { + in, out := &in.Pool, &out.Pool + *out = new(string) + **out = **in + } + if in.Device != nil { + in, out := &in.Device, &out.Device + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintSelector. +func (in *DeviceTaintSelector) DeepCopy() *DeviceTaintSelector { + if in == nil { + return nil + } + out := new(DeviceTaintSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeviceToleration) DeepCopyInto(out *DeviceToleration) { *out = *in @@ -907,6 +1071,32 @@ func (in *NetworkDeviceData) DeepCopy() *NetworkDeviceData { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAllocatableResourceMapping) DeepCopyInto(out *NodeAllocatableResourceMapping) { + *out = *in + if in.CapacityKey != nil { + in, out := &in.CapacityKey, &out.CapacityKey + *out = new(QualifiedName) + **out = **in + } + if in.AllocationMultiplier != nil { + in, out := &in.AllocationMultiplier, &out.AllocationMultiplier + x := (*in).DeepCopy() + *out = &x + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAllocatableResourceMapping. +func (in *NodeAllocatableResourceMapping) DeepCopy() *NodeAllocatableResourceMapping { + if in == nil { + return nil + } + out := new(NodeAllocatableResourceMapping) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpaqueDeviceConfiguration) DeepCopyInto(out *OpaqueDeviceConfiguration) { *out = *in diff --git a/staging/src/k8s.io/api/resource/v1beta2/zz_generated.model_name.go b/staging/src/k8s.io/api/resource/v1beta2/zz_generated.model_name.go index 0518ff73ce919..ce388ffd2eabc 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/zz_generated.model_name.go +++ b/staging/src/k8s.io/api/resource/v1beta2/zz_generated.model_name.go @@ -156,6 +156,31 @@ func (in DeviceTaint) OpenAPIModelName() string { return "io.k8s.api.resource.v1beta2.DeviceTaint" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DeviceTaintRule) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta2.DeviceTaintRule" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DeviceTaintRuleList) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta2.DeviceTaintRuleList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DeviceTaintRuleSpec) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DeviceTaintRuleStatus) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DeviceTaintSelector) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta2.DeviceTaintSelector" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in DeviceToleration) OpenAPIModelName() string { return "io.k8s.api.resource.v1beta2.DeviceToleration" @@ -171,6 +196,11 @@ func (in NetworkDeviceData) OpenAPIModelName() string { return "io.k8s.api.resource.v1beta2.NetworkDeviceData" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NodeAllocatableResourceMapping) OpenAPIModelName() string { + return "io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in OpaqueDeviceConfiguration) OpenAPIModelName() string { return "io.k8s.api.resource.v1beta2.OpaqueDeviceConfiguration" diff --git a/staging/src/k8s.io/api/resource/v1beta2/zz_generated.prerelease-lifecycle.go b/staging/src/k8s.io/api/resource/v1beta2/zz_generated.prerelease-lifecycle.go index 898b1aed356b0..c28687b870635 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/zz_generated.prerelease-lifecycle.go +++ b/staging/src/k8s.io/api/resource/v1beta2/zz_generated.prerelease-lifecycle.go @@ -57,6 +57,42 @@ func (in *DeviceClassList) APILifecycleRemoved() (major, minor int) { return 1, 39 } +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *DeviceTaintRule) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *DeviceTaintRule) APILifecycleDeprecated() (major, minor int) { + return 1, 39 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *DeviceTaintRule) APILifecycleRemoved() (major, minor int) { + return 1, 42 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *DeviceTaintRuleList) APILifecycleIntroduced() (major, minor int) { + return 1, 36 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *DeviceTaintRuleList) APILifecycleDeprecated() (major, minor int) { + return 1, 39 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *DeviceTaintRuleList) APILifecycleRemoved() (major, minor int) { + return 1, 42 +} + // APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. func (in *ResourceClaim) APILifecycleIntroduced() (major, minor int) { diff --git a/staging/src/k8s.io/api/roundtrip_test.go b/staging/src/k8s.io/api/roundtrip_test.go index e8aac998c0dbe..d113dde058bb8 100644 --- a/staging/src/k8s.io/api/roundtrip_test.go +++ b/staging/src/k8s.io/api/roundtrip_test.go @@ -37,8 +37,6 @@ import ( authorizationv1beta1 "k8s.io/api/authorization/v1beta1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1 "k8s.io/api/certificates/v1" @@ -73,7 +71,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingv1 "k8s.io/api/scheduling/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" storagev1 "k8s.io/api/storage/v1" storagev1alpha1 "k8s.io/api/storage/v1alpha1" @@ -105,8 +103,6 @@ var groups = []runtime.SchemeBuilder{ authorizationv1.SchemeBuilder, autoscalingv1.SchemeBuilder, autoscalingv2.SchemeBuilder, - autoscalingv2beta1.SchemeBuilder, - autoscalingv2beta2.SchemeBuilder, batchv1beta1.SchemeBuilder, batchv1.SchemeBuilder, certificatesv1.SchemeBuilder, @@ -140,7 +136,7 @@ var groups = []runtime.SchemeBuilder{ resourcev1beta1.SchemeBuilder, resourcev1beta2.SchemeBuilder, resourcev1.SchemeBuilder, - schedulingv1alpha1.SchemeBuilder, + schedulingv1alpha2.SchemeBuilder, schedulingv1beta1.SchemeBuilder, schedulingv1.SchemeBuilder, storagev1alpha1.SchemeBuilder, diff --git a/staging/src/k8s.io/api/scheduling/v1/generated.proto b/staging/src/k8s.io/api/scheduling/v1/generated.proto index 374e68238b5e2..6be7204de148c 100644 --- a/staging/src/k8s.io/api/scheduling/v1/generated.proto +++ b/staging/src/k8s.io/api/scheduling/v1/generated.proto @@ -39,6 +39,7 @@ message PriorityClass { // value represents the integer value of this priority class. This is the actual priority that pods // receive when they have the name of this class in their pod spec. + // +optional optional int32 value = 2; // globalDefault specifies whether this PriorityClass should be considered as diff --git a/staging/src/k8s.io/api/scheduling/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/scheduling/v1/generated.protomessage.pb.go deleted file mode 100644 index 261d4903e7b25..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*PriorityClass) ProtoMessage() {} - -func (*PriorityClassList) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/scheduling/v1/types.go b/staging/src/k8s.io/api/scheduling/v1/types.go index 019dbcd00e170..655b5eb159f3d 100644 --- a/staging/src/k8s.io/api/scheduling/v1/types.go +++ b/staging/src/k8s.io/api/scheduling/v1/types.go @@ -37,6 +37,7 @@ type PriorityClass struct { // value represents the integer value of this priority class. This is the actual priority that pods // receive when they have the name of this class in their pod spec. + // +optional Value int32 `json:"value" protobuf:"bytes,2,opt,name=value"` // globalDefault specifies whether this PriorityClass should be considered as diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go b/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go deleted file mode 100644 index 2b6182e86488f..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -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. -*/ - -// +k8s:deepcopy-gen=package -// +k8s:protobuf-gen=package -// +k8s:openapi-gen=true -// +k8s:openapi-model-package=io.k8s.api.scheduling.v1alpha1 - -// +groupName=scheduling.k8s.io - -package v1alpha1 diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go deleted file mode 100644 index 64c39b4c35d1a..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go +++ /dev/null @@ -1,2010 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: k8s.io/api/scheduling/v1alpha1/generated.proto - -package v1alpha1 - -import ( - fmt "fmt" - - io "io" - - k8s_io_api_core_v1 "k8s.io/api/core/v1" - - math_bits "math/bits" - reflect "reflect" - strings "strings" -) - -func (m *BasicSchedulingPolicy) Reset() { *m = BasicSchedulingPolicy{} } - -func (m *GangSchedulingPolicy) Reset() { *m = GangSchedulingPolicy{} } - -func (m *PodGroup) Reset() { *m = PodGroup{} } - -func (m *PodGroupPolicy) Reset() { *m = PodGroupPolicy{} } - -func (m *PriorityClass) Reset() { *m = PriorityClass{} } - -func (m *PriorityClassList) Reset() { *m = PriorityClassList{} } - -func (m *TypedLocalObjectReference) Reset() { *m = TypedLocalObjectReference{} } - -func (m *Workload) Reset() { *m = Workload{} } - -func (m *WorkloadList) Reset() { *m = WorkloadList{} } - -func (m *WorkloadSpec) Reset() { *m = WorkloadSpec{} } - -func (m *BasicSchedulingPolicy) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *BasicSchedulingPolicy) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *BasicSchedulingPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *GangSchedulingPolicy) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GangSchedulingPolicy) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *GangSchedulingPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i = encodeVarintGenerated(dAtA, i, uint64(m.MinCount)) - i-- - dAtA[i] = 0x8 - return len(dAtA) - i, nil -} - -func (m *PodGroup) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PodGroup) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PodGroup) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Policy.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *PodGroupPolicy) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PodGroupPolicy) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PodGroupPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Gang != nil { - { - size, err := m.Gang.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.Basic != nil { - { - size, err := m.Basic.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - return len(dAtA) - i, nil -} - -func (m *PriorityClass) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PriorityClass) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PriorityClass) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.PreemptionPolicy != nil { - i -= len(*m.PreemptionPolicy) - copy(dAtA[i:], *m.PreemptionPolicy) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PreemptionPolicy))) - i-- - dAtA[i] = 0x2a - } - i -= len(m.Description) - copy(dAtA[i:], m.Description) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Description))) - i-- - dAtA[i] = 0x22 - i-- - if m.GlobalDefault { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x18 - i = encodeVarintGenerated(dAtA, i, uint64(m.Value)) - i-- - dAtA[i] = 0x10 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *PriorityClassList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PriorityClassList) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PriorityClassList) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *TypedLocalObjectReference) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *TypedLocalObjectReference) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *TypedLocalObjectReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x1a - i -= len(m.Kind) - copy(dAtA[i:], m.Kind) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) - i-- - dAtA[i] = 0x12 - i -= len(m.APIGroup) - copy(dAtA[i:], m.APIGroup) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIGroup))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *Workload) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Workload) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Workload) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *WorkloadList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *WorkloadList) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *WorkloadList) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *WorkloadSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *WorkloadSpec) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *WorkloadSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.PodGroups) > 0 { - for iNdEx := len(m.PodGroups) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.PodGroups[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - if m.ControllerRef != nil { - { - size, err := m.ControllerRef.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - offset -= sovGenerated(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *BasicSchedulingPolicy) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *GangSchedulingPolicy) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - n += 1 + sovGenerated(uint64(m.MinCount)) - return n -} - -func (m *PodGroup) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Policy.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *PodGroupPolicy) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Basic != nil { - l = m.Basic.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if m.Gang != nil { - l = m.Gang.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *PriorityClass) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - n += 1 + sovGenerated(uint64(m.Value)) - n += 2 - l = len(m.Description) - n += 1 + l + sovGenerated(uint64(l)) - if m.PreemptionPolicy != nil { - l = len(*m.PreemptionPolicy) - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *PriorityClassList) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *TypedLocalObjectReference) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.APIGroup) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Kind) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *Workload) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Spec.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *WorkloadList) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *WorkloadSpec) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ControllerRef != nil { - l = m.ControllerRef.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - if len(m.PodGroups) > 0 { - for _, e := range m.PodGroups { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *BasicSchedulingPolicy) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&BasicSchedulingPolicy{`, - `}`, - }, "") - return s -} -func (this *GangSchedulingPolicy) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&GangSchedulingPolicy{`, - `MinCount:` + fmt.Sprintf("%v", this.MinCount) + `,`, - `}`, - }, "") - return s -} -func (this *PodGroup) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PodGroup{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Policy:` + strings.Replace(strings.Replace(this.Policy.String(), "PodGroupPolicy", "PodGroupPolicy", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *PodGroupPolicy) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PodGroupPolicy{`, - `Basic:` + strings.Replace(this.Basic.String(), "BasicSchedulingPolicy", "BasicSchedulingPolicy", 1) + `,`, - `Gang:` + strings.Replace(this.Gang.String(), "GangSchedulingPolicy", "GangSchedulingPolicy", 1) + `,`, - `}`, - }, "") - return s -} -func (this *PriorityClass) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&PriorityClass{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Value:` + fmt.Sprintf("%v", this.Value) + `,`, - `GlobalDefault:` + fmt.Sprintf("%v", this.GlobalDefault) + `,`, - `Description:` + fmt.Sprintf("%v", this.Description) + `,`, - `PreemptionPolicy:` + valueToStringGenerated(this.PreemptionPolicy) + `,`, - `}`, - }, "") - return s -} -func (this *PriorityClassList) String() string { - if this == nil { - return "nil" - } - repeatedStringForItems := "[]PriorityClass{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "PriorityClass", "PriorityClass", 1), `&`, ``, 1) + "," - } - repeatedStringForItems += "}" - s := strings.Join([]string{`&PriorityClassList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s -} -func (this *TypedLocalObjectReference) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&TypedLocalObjectReference{`, - `APIGroup:` + fmt.Sprintf("%v", this.APIGroup) + `,`, - `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `}`, - }, "") - return s -} -func (this *Workload) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&Workload{`, - `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "WorkloadSpec", "WorkloadSpec", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *WorkloadList) String() string { - if this == nil { - return "nil" - } - repeatedStringForItems := "[]Workload{" - for _, f := range this.Items { - repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "Workload", "Workload", 1), `&`, ``, 1) + "," - } - repeatedStringForItems += "}" - s := strings.Join([]string{`&WorkloadList{`, - `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + repeatedStringForItems + `,`, - `}`, - }, "") - return s -} -func (this *WorkloadSpec) String() string { - if this == nil { - return "nil" - } - repeatedStringForPodGroups := "[]PodGroup{" - for _, f := range this.PodGroups { - repeatedStringForPodGroups += strings.Replace(strings.Replace(f.String(), "PodGroup", "PodGroup", 1), `&`, ``, 1) + "," - } - repeatedStringForPodGroups += "}" - s := strings.Join([]string{`&WorkloadSpec{`, - `ControllerRef:` + strings.Replace(this.ControllerRef.String(), "TypedLocalObjectReference", "TypedLocalObjectReference", 1) + `,`, - `PodGroups:` + repeatedStringForPodGroups + `,`, - `}`, - }, "") - return s -} -func valueToStringGenerated(v interface{}) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { - return "nil" - } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("*%v", pv) -} -func (m *BasicSchedulingPolicy) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BasicSchedulingPolicy: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BasicSchedulingPolicy: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GangSchedulingPolicy) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GangSchedulingPolicy: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GangSchedulingPolicy: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinCount", wireType) - } - m.MinCount = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.MinCount |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PodGroup) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PodGroup: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PodGroup: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Policy", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Policy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PodGroupPolicy) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PodGroupPolicy: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PodGroupPolicy: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Basic", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Basic == nil { - m.Basic = &BasicSchedulingPolicy{} - } - if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Gang", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Gang == nil { - m.Gang = &GangSchedulingPolicy{} - } - if err := m.Gang.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PriorityClass) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PriorityClass: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PriorityClass: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - m.Value = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Value |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field GlobalDefault", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.GlobalDefault = bool(v != 0) - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Description = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PreemptionPolicy", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := k8s_io_api_core_v1.PreemptionPolicy(dAtA[iNdEx:postIndex]) - m.PreemptionPolicy = &s - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PriorityClassList) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PriorityClassList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PriorityClassList: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, PriorityClass{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *TypedLocalObjectReference) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: TypedLocalObjectReference: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: TypedLocalObjectReference: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field APIGroup", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.APIGroup = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Kind = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Workload) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Workload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Workload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *WorkloadList) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: WorkloadList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: WorkloadList: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, Workload{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *WorkloadSpec) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: WorkloadSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: WorkloadSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ControllerRef", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ControllerRef == nil { - m.ControllerRef = &TypedLocalObjectReference{} - } - if err := m.ControllerRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PodGroups", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PodGroups = append(m.PodGroups, PodGroup{}) - if err := m.PodGroups[len(m.PodGroups)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipGenerated(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthGenerated - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupGenerated - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthGenerated - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") -) diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto deleted file mode 100644 index 6014f60e20c85..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright 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. -*/ - - -// This file was autogenerated by go-to-protobuf. Do not edit it manually! - -syntax = "proto2"; - -package k8s.io.api.scheduling.v1alpha1; - -import "k8s.io/api/core/v1/generated.proto"; -import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; -import "k8s.io/apimachinery/pkg/runtime/generated.proto"; -import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; - -// Package-wide variables from generator "generated". -option go_package = "k8s.io/api/scheduling/v1alpha1"; - -// BasicSchedulingPolicy indicates that standard Kubernetes -// scheduling behavior should be used. -message BasicSchedulingPolicy { -} - -// GangSchedulingPolicy defines the parameters for gang scheduling. -message GangSchedulingPolicy { - // MinCount is the minimum number of pods that must be schedulable or scheduled - // at the same time for the scheduler to admit the entire group. - // It must be a positive integer. - // - // +required - optional int32 minCount = 1; -} - -// PodGroup represents a set of pods with a common scheduling policy. -message PodGroup { - // Name is a unique identifier for the PodGroup within the Workload. - // It must be a DNS label. This field is immutable. - // - // +required - optional string name = 1; - - // Policy defines the scheduling policy for this PodGroup. - // - // +required - optional PodGroupPolicy policy = 3; -} - -// PodGroupPolicy defines the scheduling configuration for a PodGroup. -message PodGroupPolicy { - // Basic specifies that the pods in this group should be scheduled using - // standard Kubernetes scheduling behavior. - // - // +optional - // +oneOf=PolicySelection - optional BasicSchedulingPolicy basic = 2; - - // Gang specifies that the pods in this group should be scheduled using - // all-or-nothing semantics. - // - // +optional - // +oneOf=PolicySelection - optional GangSchedulingPolicy gang = 3; -} - -// DEPRECATED - This group version of PriorityClass is deprecated by scheduling.k8s.io/v1/PriorityClass. -// PriorityClass defines mapping from a priority class name to the priority -// integer value. The value can be any valid integer. -message PriorityClass { - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // value represents the integer value of this priority class. This is the actual priority that pods - // receive when they have the name of this class in their pod spec. - optional int32 value = 2; - - // globalDefault specifies whether this PriorityClass should be considered as - // the default priority for pods that do not have any priority class. - // Only one PriorityClass can be marked as `globalDefault`. However, if more than - // one PriorityClasses exists with their `globalDefault` field set to true, - // the smallest value of such global default PriorityClasses will be used as the default priority. - // +optional - optional bool globalDefault = 3; - - // description is an arbitrary string that usually provides guidelines on - // when this priority class should be used. - // +optional - optional string description = 4; - - // preemptionPolicy is the Policy for preempting pods with lower priority. - // One of Never, PreemptLowerPriority. - // Defaults to PreemptLowerPriority if unset. - // +optional - optional string preemptionPolicy = 5; -} - -// PriorityClassList is a collection of priority classes. -message PriorityClassList { - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // items is the list of PriorityClasses - repeated PriorityClass items = 2; -} - -// TypedLocalObjectReference allows to reference typed object inside the same namespace. -message TypedLocalObjectReference { - // APIGroup is the group for the resource being referenced. - // If APIGroup is empty, the specified Kind must be in the core API group. - // For any other third-party types, setting APIGroup is required. - // It must be a DNS subdomain. - // - // +optional - optional string apiGroup = 1; - - // Kind is the type of resource being referenced. - // It must be a path segment name. - // - // +required - optional string kind = 2; - - // Name is the name of resource being referenced. - // It must be a path segment name. - // - // +required - optional string name = 3; -} - -// Workload allows for expressing scheduling constraints that should be used -// when managing lifecycle of workloads from scheduling perspective, -// including scheduling, preemption, eviction and other phases. -message Workload { - // Standard object's metadata. - // Name must be a DNS subdomain. - // - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // Spec defines the desired behavior of a Workload. - // - // +required - optional WorkloadSpec spec = 2; -} - -// WorkloadList contains a list of Workload resources. -message WorkloadList { - // Standard list metadata. - // - // +optional - optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // Items is the list of Workloads. - repeated Workload items = 2; -} - -// WorkloadSpec defines the desired state of a Workload. -message WorkloadSpec { - // ControllerRef is an optional reference to the controlling object, such as a - // Deployment or Job. This field is intended for use by tools like CLIs - // to provide a link back to the original workload definition. - // When set, it cannot be changed. - // - // +optional - optional TypedLocalObjectReference controllerRef = 1; - - // PodGroups is the list of pod groups that make up the Workload. - // The maximum number of pod groups is 8. This field is immutable. - // - // +required - // +listType=map - // +listMapKey=name - repeated PodGroup podGroups = 2; -} - diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index a92e57b1e76a8..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,42 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*BasicSchedulingPolicy) ProtoMessage() {} - -func (*GangSchedulingPolicy) ProtoMessage() {} - -func (*PodGroup) ProtoMessage() {} - -func (*PodGroupPolicy) ProtoMessage() {} - -func (*PriorityClass) ProtoMessage() {} - -func (*PriorityClassList) ProtoMessage() {} - -func (*TypedLocalObjectReference) ProtoMessage() {} - -func (*Workload) ProtoMessage() {} - -func (*WorkloadList) ProtoMessage() {} - -func (*WorkloadSpec) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/register.go b/staging/src/k8s.io/api/scheduling/v1alpha1/register.go deleted file mode 100644 index 25de55d308dc6..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/register.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -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. -*/ - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "scheduling.k8s.io" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. - // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -// Adds the list of known types to the given scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &PriorityClass{}, - &PriorityClassList{}, - &Workload{}, - &WorkloadList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/types.go b/staging/src/k8s.io/api/scheduling/v1alpha1/types.go deleted file mode 100644 index 480b53da015e5..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/types.go +++ /dev/null @@ -1,201 +0,0 @@ -/* -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. -*/ - -package v1alpha1 - -import ( - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// DEPRECATED - This group version of PriorityClass is deprecated by scheduling.k8s.io/v1/PriorityClass. -// PriorityClass defines mapping from a priority class name to the priority -// integer value. The value can be any valid integer. -type PriorityClass struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // value represents the integer value of this priority class. This is the actual priority that pods - // receive when they have the name of this class in their pod spec. - Value int32 `json:"value" protobuf:"bytes,2,opt,name=value"` - - // globalDefault specifies whether this PriorityClass should be considered as - // the default priority for pods that do not have any priority class. - // Only one PriorityClass can be marked as `globalDefault`. However, if more than - // one PriorityClasses exists with their `globalDefault` field set to true, - // the smallest value of such global default PriorityClasses will be used as the default priority. - // +optional - GlobalDefault bool `json:"globalDefault,omitempty" protobuf:"bytes,3,opt,name=globalDefault"` - - // description is an arbitrary string that usually provides guidelines on - // when this priority class should be used. - // +optional - Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"` - - // preemptionPolicy is the Policy for preempting pods with lower priority. - // One of Never, PreemptLowerPriority. - // Defaults to PreemptLowerPriority if unset. - // +optional - PreemptionPolicy *apiv1.PreemptionPolicy `json:"preemptionPolicy,omitempty" protobuf:"bytes,5,opt,name=preemptionPolicy"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PriorityClassList is a collection of priority classes. -type PriorityClassList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // items is the list of PriorityClasses - Items []PriorityClass `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Workload allows for expressing scheduling constraints that should be used -// when managing lifecycle of workloads from scheduling perspective, -// including scheduling, preemption, eviction and other phases. -type Workload struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // Name must be a DNS subdomain. - // - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Spec defines the desired behavior of a Workload. - // - // +required - Spec WorkloadSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// WorkloadList contains a list of Workload resources. -type WorkloadList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata. - // - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Items is the list of Workloads. - Items []Workload `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// WorkloadMaxPodGroups is the maximum number of pod groups per Workload. -const WorkloadMaxPodGroups = 8 - -// WorkloadSpec defines the desired state of a Workload. -type WorkloadSpec struct { - // ControllerRef is an optional reference to the controlling object, such as a - // Deployment or Job. This field is intended for use by tools like CLIs - // to provide a link back to the original workload definition. - // When set, it cannot be changed. - // - // +optional - ControllerRef *TypedLocalObjectReference `json:"controllerRef,omitempty" protobuf:"bytes,1,opt,name=controllerRef"` - - // PodGroups is the list of pod groups that make up the Workload. - // The maximum number of pod groups is 8. This field is immutable. - // - // +required - // +listType=map - // +listMapKey=name - PodGroups []PodGroup `json:"podGroups" protobuf:"bytes,2,rep,name=podGroups"` -} - -// TypedLocalObjectReference allows to reference typed object inside the same namespace. -type TypedLocalObjectReference struct { - // APIGroup is the group for the resource being referenced. - // If APIGroup is empty, the specified Kind must be in the core API group. - // For any other third-party types, setting APIGroup is required. - // It must be a DNS subdomain. - // - // +optional - APIGroup string `json:"apiGroup,omitempty" protobuf:"bytes,1,opt,name=apiGroup"` - // Kind is the type of resource being referenced. - // It must be a path segment name. - // - // +required - Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` - // Name is the name of resource being referenced. - // It must be a path segment name. - // - // +required - Name string `json:"name" protobuf:"bytes,3,opt,name=name"` -} - -// PodGroup represents a set of pods with a common scheduling policy. -type PodGroup struct { - // Name is a unique identifier for the PodGroup within the Workload. - // It must be a DNS label. This field is immutable. - // - // +required - Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - - // Policy defines the scheduling policy for this PodGroup. - // - // +required - Policy PodGroupPolicy `json:"policy" protobuf:"bytes,3,opt,name=policy"` -} - -// PodGroupPolicy defines the scheduling configuration for a PodGroup. -type PodGroupPolicy struct { - // Basic specifies that the pods in this group should be scheduled using - // standard Kubernetes scheduling behavior. - // - // +optional - // +oneOf=PolicySelection - Basic *BasicSchedulingPolicy `json:"basic,omitempty" protobuf:"bytes,2,opt,name=basic"` - - // Gang specifies that the pods in this group should be scheduled using - // all-or-nothing semantics. - // - // +optional - // +oneOf=PolicySelection - Gang *GangSchedulingPolicy `json:"gang,omitempty" protobuf:"bytes,3,opt,name=gang"` -} - -// BasicSchedulingPolicy indicates that standard Kubernetes -// scheduling behavior should be used. -type BasicSchedulingPolicy struct { - // This is intentionally empty. Its presence indicates that the basic - // scheduling policy should be applied. In the future, new fields may appear, - // describing such constraints on a pod group level without "all or nothing" - // (gang) scheduling. -} - -// GangSchedulingPolicy defines the parameters for gang scheduling. -type GangSchedulingPolicy struct { - // MinCount is the minimum number of pods that must be schedulable or scheduled - // at the same time for the scheduler to admit the entire group. - // It must be a positive integer. - // - // +required - MinCount int32 `json:"minCount" protobuf:"varint,1,opt,name=minCount"` -} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/scheduling/v1alpha1/types_swagger_doc_generated.go deleted file mode 100644 index a2915bff44bae..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/types_swagger_doc_generated.go +++ /dev/null @@ -1,131 +0,0 @@ -/* -Copyright 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 v1alpha1 - -// This file contains a collection of methods that can be used from go-restful to -// generate Swagger API documentation for its models. Please read this PR for more -// information on the implementation: https://github.com/emicklei/go-restful/pull/215 -// -// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if -// they are on one line! For multiple line or blocks that you want to ignore use ---. -// Any context after a --- is ignored. -// -// Those methods can be generated by using hack/update-codegen.sh - -// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. -var map_BasicSchedulingPolicy = map[string]string{ - "": "BasicSchedulingPolicy indicates that standard Kubernetes scheduling behavior should be used.", -} - -func (BasicSchedulingPolicy) SwaggerDoc() map[string]string { - return map_BasicSchedulingPolicy -} - -var map_GangSchedulingPolicy = map[string]string{ - "": "GangSchedulingPolicy defines the parameters for gang scheduling.", - "minCount": "MinCount is the minimum number of pods that must be schedulable or scheduled at the same time for the scheduler to admit the entire group. It must be a positive integer.", -} - -func (GangSchedulingPolicy) SwaggerDoc() map[string]string { - return map_GangSchedulingPolicy -} - -var map_PodGroup = map[string]string{ - "": "PodGroup represents a set of pods with a common scheduling policy.", - "name": "Name is a unique identifier for the PodGroup within the Workload. It must be a DNS label. This field is immutable.", - "policy": "Policy defines the scheduling policy for this PodGroup.", -} - -func (PodGroup) SwaggerDoc() map[string]string { - return map_PodGroup -} - -var map_PodGroupPolicy = map[string]string{ - "": "PodGroupPolicy defines the scheduling configuration for a PodGroup.", - "basic": "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior.", - "gang": "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics.", -} - -func (PodGroupPolicy) SwaggerDoc() map[string]string { - return map_PodGroupPolicy -} - -var map_PriorityClass = map[string]string{ - "": "DEPRECATED - This group version of PriorityClass is deprecated by scheduling.k8s.io/v1/PriorityClass. PriorityClass defines mapping from a priority class name to the priority integer value. The value can be any valid integer.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "value": "value represents the integer value of this priority class. This is the actual priority that pods receive when they have the name of this class in their pod spec.", - "globalDefault": "globalDefault specifies whether this PriorityClass should be considered as the default priority for pods that do not have any priority class. Only one PriorityClass can be marked as `globalDefault`. However, if more than one PriorityClasses exists with their `globalDefault` field set to true, the smallest value of such global default PriorityClasses will be used as the default priority.", - "description": "description is an arbitrary string that usually provides guidelines on when this priority class should be used.", - "preemptionPolicy": "preemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset.", -} - -func (PriorityClass) SwaggerDoc() map[string]string { - return map_PriorityClass -} - -var map_PriorityClassList = map[string]string{ - "": "PriorityClassList is a collection of priority classes.", - "metadata": "Standard list metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - "items": "items is the list of PriorityClasses", -} - -func (PriorityClassList) SwaggerDoc() map[string]string { - return map_PriorityClassList -} - -var map_TypedLocalObjectReference = map[string]string{ - "": "TypedLocalObjectReference allows to reference typed object inside the same namespace.", - "apiGroup": "APIGroup is the group for the resource being referenced. If APIGroup is empty, the specified Kind must be in the core API group. For any other third-party types, setting APIGroup is required. It must be a DNS subdomain.", - "kind": "Kind is the type of resource being referenced. It must be a path segment name.", - "name": "Name is the name of resource being referenced. It must be a path segment name.", -} - -func (TypedLocalObjectReference) SwaggerDoc() map[string]string { - return map_TypedLocalObjectReference -} - -var map_Workload = map[string]string{ - "": "Workload allows for expressing scheduling constraints that should be used when managing lifecycle of workloads from scheduling perspective, including scheduling, preemption, eviction and other phases.", - "metadata": "Standard object's metadata. Name must be a DNS subdomain.", - "spec": "Spec defines the desired behavior of a Workload.", -} - -func (Workload) SwaggerDoc() map[string]string { - return map_Workload -} - -var map_WorkloadList = map[string]string{ - "": "WorkloadList contains a list of Workload resources.", - "metadata": "Standard list metadata.", - "items": "Items is the list of Workloads.", -} - -func (WorkloadList) SwaggerDoc() map[string]string { - return map_WorkloadList -} - -var map_WorkloadSpec = map[string]string{ - "": "WorkloadSpec defines the desired state of a Workload.", - "controllerRef": "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. When set, it cannot be changed.", - "podGroups": "PodGroups is the list of pod groups that make up the Workload. The maximum number of pod groups is 8. This field is immutable.", -} - -func (WorkloadSpec) SwaggerDoc() map[string]string { - return map_WorkloadSpec -} - -// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index ccaf45d72ccf2..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,270 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - v1 "k8s.io/api/core/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BasicSchedulingPolicy) DeepCopyInto(out *BasicSchedulingPolicy) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicSchedulingPolicy. -func (in *BasicSchedulingPolicy) DeepCopy() *BasicSchedulingPolicy { - if in == nil { - return nil - } - out := new(BasicSchedulingPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GangSchedulingPolicy) DeepCopyInto(out *GangSchedulingPolicy) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GangSchedulingPolicy. -func (in *GangSchedulingPolicy) DeepCopy() *GangSchedulingPolicy { - if in == nil { - return nil - } - out := new(GangSchedulingPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodGroup) DeepCopyInto(out *PodGroup) { - *out = *in - in.Policy.DeepCopyInto(&out.Policy) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroup. -func (in *PodGroup) DeepCopy() *PodGroup { - if in == nil { - return nil - } - out := new(PodGroup) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodGroupPolicy) DeepCopyInto(out *PodGroupPolicy) { - *out = *in - if in.Basic != nil { - in, out := &in.Basic, &out.Basic - *out = new(BasicSchedulingPolicy) - **out = **in - } - if in.Gang != nil { - in, out := &in.Gang, &out.Gang - *out = new(GangSchedulingPolicy) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupPolicy. -func (in *PodGroupPolicy) DeepCopy() *PodGroupPolicy { - if in == nil { - return nil - } - out := new(PodGroupPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PriorityClass) DeepCopyInto(out *PriorityClass) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.PreemptionPolicy != nil { - in, out := &in.PreemptionPolicy, &out.PreemptionPolicy - *out = new(v1.PreemptionPolicy) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityClass. -func (in *PriorityClass) DeepCopy() *PriorityClass { - if in == nil { - return nil - } - out := new(PriorityClass) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PriorityClass) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PriorityClassList) DeepCopyInto(out *PriorityClassList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PriorityClass, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityClassList. -func (in *PriorityClassList) DeepCopy() *PriorityClassList { - if in == nil { - return nil - } - out := new(PriorityClassList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PriorityClassList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TypedLocalObjectReference) DeepCopyInto(out *TypedLocalObjectReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedLocalObjectReference. -func (in *TypedLocalObjectReference) DeepCopy() *TypedLocalObjectReference { - if in == nil { - return nil - } - out := new(TypedLocalObjectReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Workload) DeepCopyInto(out *Workload) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workload. -func (in *Workload) DeepCopy() *Workload { - if in == nil { - return nil - } - out := new(Workload) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Workload) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkloadList) DeepCopyInto(out *WorkloadList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Workload, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadList. -func (in *WorkloadList) DeepCopy() *WorkloadList { - if in == nil { - return nil - } - out := new(WorkloadList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *WorkloadList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) { - *out = *in - if in.ControllerRef != nil { - in, out := &in.ControllerRef, &out.ControllerRef - *out = new(TypedLocalObjectReference) - **out = **in - } - if in.PodGroups != nil { - in, out := &in.PodGroups, &out.PodGroups - *out = make([]PodGroup, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadSpec. -func (in *WorkloadSpec) DeepCopy() *WorkloadSpec { - if in == nil { - return nil - } - out := new(WorkloadSpec) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.model_name.go b/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.model_name.go deleted file mode 100644 index 25844f419a826..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.model_name.go +++ /dev/null @@ -1,72 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by openapi-gen. DO NOT EDIT. - -package v1alpha1 - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in BasicSchedulingPolicy) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in GangSchedulingPolicy) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PodGroup) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.PodGroup" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PodGroupPolicy) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.PodGroupPolicy" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PriorityClass) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.PriorityClass" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in PriorityClassList) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.PriorityClassList" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in TypedLocalObjectReference) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in Workload) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.Workload" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in WorkloadList) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.WorkloadList" -} - -// OpenAPIModelName returns the OpenAPI model name for this type. -func (in WorkloadSpec) OpenAPIModelName() string { - return "io.k8s.api.scheduling.v1alpha1.WorkloadSpec" -} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/doc.go b/staging/src/k8s.io/api/scheduling/v1alpha2/doc.go new file mode 100644 index 0000000000000..24ba23e72c681 --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:protobuf-gen=package +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.api.scheduling.v1alpha2 + +// +groupName=scheduling.k8s.io + +package v1alpha2 diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/generated.pb.go b/staging/src/k8s.io/api/scheduling/v1alpha2/generated.pb.go new file mode 100644 index 0000000000000..12402df3d27d6 --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/generated.pb.go @@ -0,0 +1,3828 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/api/scheduling/v1alpha2/generated.proto + +package v1alpha2 + +import ( + fmt "fmt" + + io "io" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + math_bits "math/bits" + reflect "reflect" + strings "strings" +) + +func (m *BasicSchedulingPolicy) Reset() { *m = BasicSchedulingPolicy{} } + +func (m *GangSchedulingPolicy) Reset() { *m = GangSchedulingPolicy{} } + +func (m *PodGroup) Reset() { *m = PodGroup{} } + +func (m *PodGroupList) Reset() { *m = PodGroupList{} } + +func (m *PodGroupResourceClaim) Reset() { *m = PodGroupResourceClaim{} } + +func (m *PodGroupResourceClaimStatus) Reset() { *m = PodGroupResourceClaimStatus{} } + +func (m *PodGroupSchedulingConstraints) Reset() { *m = PodGroupSchedulingConstraints{} } + +func (m *PodGroupSchedulingPolicy) Reset() { *m = PodGroupSchedulingPolicy{} } + +func (m *PodGroupSpec) Reset() { *m = PodGroupSpec{} } + +func (m *PodGroupStatus) Reset() { *m = PodGroupStatus{} } + +func (m *PodGroupTemplate) Reset() { *m = PodGroupTemplate{} } + +func (m *PodGroupTemplateReference) Reset() { *m = PodGroupTemplateReference{} } + +func (m *TopologyConstraint) Reset() { *m = TopologyConstraint{} } + +func (m *TypedLocalObjectReference) Reset() { *m = TypedLocalObjectReference{} } + +func (m *Workload) Reset() { *m = Workload{} } + +func (m *WorkloadList) Reset() { *m = WorkloadList{} } + +func (m *WorkloadPodGroupTemplateReference) Reset() { *m = WorkloadPodGroupTemplateReference{} } + +func (m *WorkloadSpec) Reset() { *m = WorkloadSpec{} } + +func (m *BasicSchedulingPolicy) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BasicSchedulingPolicy) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BasicSchedulingPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *GangSchedulingPolicy) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GangSchedulingPolicy) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GangSchedulingPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.MinCount)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *PodGroup) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroup) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroup) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PodGroupList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PodGroupResourceClaim) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupResourceClaim) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupResourceClaim) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ResourceClaimTemplateName != nil { + i -= len(*m.ResourceClaimTemplateName) + copy(dAtA[i:], *m.ResourceClaimTemplateName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ResourceClaimTemplateName))) + i-- + dAtA[i] = 0x1a + } + if m.ResourceClaimName != nil { + i -= len(*m.ResourceClaimName) + copy(dAtA[i:], *m.ResourceClaimName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ResourceClaimName))) + i-- + dAtA[i] = 0x12 + } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PodGroupResourceClaimStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupResourceClaimStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupResourceClaimStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ResourceClaimName != nil { + i -= len(*m.ResourceClaimName) + copy(dAtA[i:], *m.ResourceClaimName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ResourceClaimName))) + i-- + dAtA[i] = 0x12 + } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PodGroupSchedulingConstraints) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupSchedulingConstraints) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupSchedulingConstraints) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Topology) > 0 { + for iNdEx := len(m.Topology) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Topology[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *PodGroupSchedulingPolicy) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupSchedulingPolicy) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupSchedulingPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Gang != nil { + { + size, err := m.Gang.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Basic != nil { + { + size, err := m.Basic.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PodGroupSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Priority != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Priority)) + i-- + dAtA[i] = 0x38 + } + i -= len(m.PriorityClassName) + copy(dAtA[i:], m.PriorityClassName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PriorityClassName))) + i-- + dAtA[i] = 0x32 + if m.DisruptionMode != nil { + i -= len(*m.DisruptionMode) + copy(dAtA[i:], *m.DisruptionMode) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.DisruptionMode))) + i-- + dAtA[i] = 0x2a + } + if len(m.ResourceClaims) > 0 { + for iNdEx := len(m.ResourceClaims) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ResourceClaims[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if m.SchedulingConstraints != nil { + { + size, err := m.SchedulingConstraints.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + { + size, err := m.SchedulingPolicy.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.PodGroupTemplateRef != nil { + { + size, err := m.PodGroupTemplateRef.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PodGroupStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ResourceClaimStatuses) > 0 { + for iNdEx := len(m.ResourceClaimStatuses) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ResourceClaimStatuses[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Conditions) > 0 { + for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *PodGroupTemplate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupTemplate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupTemplate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Priority != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Priority)) + i-- + dAtA[i] = 0x38 + } + i -= len(m.PriorityClassName) + copy(dAtA[i:], m.PriorityClassName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PriorityClassName))) + i-- + dAtA[i] = 0x32 + if m.DisruptionMode != nil { + i -= len(*m.DisruptionMode) + copy(dAtA[i:], *m.DisruptionMode) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.DisruptionMode))) + i-- + dAtA[i] = 0x2a + } + if len(m.ResourceClaims) > 0 { + for iNdEx := len(m.ResourceClaims) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ResourceClaims[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if m.SchedulingConstraints != nil { + { + size, err := m.SchedulingConstraints.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + { + size, err := m.SchedulingPolicy.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PodGroupTemplateReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodGroupTemplateReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PodGroupTemplateReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Workload != nil { + { + size, err := m.Workload.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TopologyConstraint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TopologyConstraint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TopologyConstraint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *TypedLocalObjectReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TypedLocalObjectReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TypedLocalObjectReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x1a + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x12 + i -= len(m.APIGroup) + copy(dAtA[i:], m.APIGroup) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIGroup))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Workload) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Workload) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Workload) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *WorkloadList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WorkloadList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WorkloadList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *WorkloadPodGroupTemplateReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WorkloadPodGroupTemplateReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WorkloadPodGroupTemplateReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.PodGroupTemplateName) + copy(dAtA[i:], m.PodGroupTemplateName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PodGroupTemplateName))) + i-- + dAtA[i] = 0x12 + i -= len(m.WorkloadName) + copy(dAtA[i:], m.WorkloadName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.WorkloadName))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *WorkloadSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WorkloadSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WorkloadSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PodGroupTemplates) > 0 { + for iNdEx := len(m.PodGroupTemplates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PodGroupTemplates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.ControllerRef != nil { + { + size, err := m.ControllerRef.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *BasicSchedulingPolicy) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *GangSchedulingPolicy) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovGenerated(uint64(m.MinCount)) + return n +} + +func (m *PodGroup) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *PodGroupList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *PodGroupResourceClaim) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if m.ResourceClaimName != nil { + l = len(*m.ResourceClaimName) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ResourceClaimTemplateName != nil { + l = len(*m.ResourceClaimTemplateName) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *PodGroupResourceClaimStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if m.ResourceClaimName != nil { + l = len(*m.ResourceClaimName) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *PodGroupSchedulingConstraints) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Topology) > 0 { + for _, e := range m.Topology { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *PodGroupSchedulingPolicy) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Basic != nil { + l = m.Basic.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Gang != nil { + l = m.Gang.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *PodGroupSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PodGroupTemplateRef != nil { + l = m.PodGroupTemplateRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = m.SchedulingPolicy.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.SchedulingConstraints != nil { + l = m.SchedulingConstraints.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.ResourceClaims) > 0 { + for _, e := range m.ResourceClaims { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.DisruptionMode != nil { + l = len(*m.DisruptionMode) + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.PriorityClassName) + n += 1 + l + sovGenerated(uint64(l)) + if m.Priority != nil { + n += 1 + sovGenerated(uint64(*m.Priority)) + } + return n +} + +func (m *PodGroupStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.ResourceClaimStatuses) > 0 { + for _, e := range m.ResourceClaimStatuses { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *PodGroupTemplate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = m.SchedulingPolicy.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.SchedulingConstraints != nil { + l = m.SchedulingConstraints.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.ResourceClaims) > 0 { + for _, e := range m.ResourceClaims { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.DisruptionMode != nil { + l = len(*m.DisruptionMode) + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.PriorityClassName) + n += 1 + l + sovGenerated(uint64(l)) + if m.Priority != nil { + n += 1 + sovGenerated(uint64(*m.Priority)) + } + return n +} + +func (m *PodGroupTemplateReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Workload != nil { + l = m.Workload.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *TopologyConstraint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *TypedLocalObjectReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.APIGroup) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Workload) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *WorkloadList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *WorkloadPodGroupTemplateReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.WorkloadName) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.PodGroupTemplateName) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *WorkloadSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ControllerRef != nil { + l = m.ControllerRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.PodGroupTemplates) > 0 { + for _, e := range m.PodGroupTemplates { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *BasicSchedulingPolicy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&BasicSchedulingPolicy{`, + `}`, + }, "") + return s +} +func (this *GangSchedulingPolicy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&GangSchedulingPolicy{`, + `MinCount:` + fmt.Sprintf("%v", this.MinCount) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroup) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodGroup{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "PodGroupSpec", "PodGroupSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "PodGroupStatus", "PodGroupStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]PodGroup{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "PodGroup", "PodGroup", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&PodGroupList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupResourceClaim) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodGroupResourceClaim{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ResourceClaimName:` + valueToStringGenerated(this.ResourceClaimName) + `,`, + `ResourceClaimTemplateName:` + valueToStringGenerated(this.ResourceClaimTemplateName) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupResourceClaimStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodGroupResourceClaimStatus{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ResourceClaimName:` + valueToStringGenerated(this.ResourceClaimName) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupSchedulingConstraints) String() string { + if this == nil { + return "nil" + } + repeatedStringForTopology := "[]TopologyConstraint{" + for _, f := range this.Topology { + repeatedStringForTopology += strings.Replace(strings.Replace(f.String(), "TopologyConstraint", "TopologyConstraint", 1), `&`, ``, 1) + "," + } + repeatedStringForTopology += "}" + s := strings.Join([]string{`&PodGroupSchedulingConstraints{`, + `Topology:` + repeatedStringForTopology + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupSchedulingPolicy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodGroupSchedulingPolicy{`, + `Basic:` + strings.Replace(this.Basic.String(), "BasicSchedulingPolicy", "BasicSchedulingPolicy", 1) + `,`, + `Gang:` + strings.Replace(this.Gang.String(), "GangSchedulingPolicy", "GangSchedulingPolicy", 1) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupSpec) String() string { + if this == nil { + return "nil" + } + repeatedStringForResourceClaims := "[]PodGroupResourceClaim{" + for _, f := range this.ResourceClaims { + repeatedStringForResourceClaims += strings.Replace(strings.Replace(f.String(), "PodGroupResourceClaim", "PodGroupResourceClaim", 1), `&`, ``, 1) + "," + } + repeatedStringForResourceClaims += "}" + s := strings.Join([]string{`&PodGroupSpec{`, + `PodGroupTemplateRef:` + strings.Replace(this.PodGroupTemplateRef.String(), "PodGroupTemplateReference", "PodGroupTemplateReference", 1) + `,`, + `SchedulingPolicy:` + strings.Replace(strings.Replace(this.SchedulingPolicy.String(), "PodGroupSchedulingPolicy", "PodGroupSchedulingPolicy", 1), `&`, ``, 1) + `,`, + `SchedulingConstraints:` + strings.Replace(this.SchedulingConstraints.String(), "PodGroupSchedulingConstraints", "PodGroupSchedulingConstraints", 1) + `,`, + `ResourceClaims:` + repeatedStringForResourceClaims + `,`, + `DisruptionMode:` + valueToStringGenerated(this.DisruptionMode) + `,`, + `PriorityClassName:` + fmt.Sprintf("%v", this.PriorityClassName) + `,`, + `Priority:` + valueToStringGenerated(this.Priority) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupStatus) String() string { + if this == nil { + return "nil" + } + repeatedStringForConditions := "[]Condition{" + for _, f := range this.Conditions { + repeatedStringForConditions += fmt.Sprintf("%v", f) + "," + } + repeatedStringForConditions += "}" + repeatedStringForResourceClaimStatuses := "[]PodGroupResourceClaimStatus{" + for _, f := range this.ResourceClaimStatuses { + repeatedStringForResourceClaimStatuses += strings.Replace(strings.Replace(f.String(), "PodGroupResourceClaimStatus", "PodGroupResourceClaimStatus", 1), `&`, ``, 1) + "," + } + repeatedStringForResourceClaimStatuses += "}" + s := strings.Join([]string{`&PodGroupStatus{`, + `Conditions:` + repeatedStringForConditions + `,`, + `ResourceClaimStatuses:` + repeatedStringForResourceClaimStatuses + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupTemplate) String() string { + if this == nil { + return "nil" + } + repeatedStringForResourceClaims := "[]PodGroupResourceClaim{" + for _, f := range this.ResourceClaims { + repeatedStringForResourceClaims += strings.Replace(strings.Replace(f.String(), "PodGroupResourceClaim", "PodGroupResourceClaim", 1), `&`, ``, 1) + "," + } + repeatedStringForResourceClaims += "}" + s := strings.Join([]string{`&PodGroupTemplate{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `SchedulingPolicy:` + strings.Replace(strings.Replace(this.SchedulingPolicy.String(), "PodGroupSchedulingPolicy", "PodGroupSchedulingPolicy", 1), `&`, ``, 1) + `,`, + `SchedulingConstraints:` + strings.Replace(this.SchedulingConstraints.String(), "PodGroupSchedulingConstraints", "PodGroupSchedulingConstraints", 1) + `,`, + `ResourceClaims:` + repeatedStringForResourceClaims + `,`, + `DisruptionMode:` + valueToStringGenerated(this.DisruptionMode) + `,`, + `PriorityClassName:` + fmt.Sprintf("%v", this.PriorityClassName) + `,`, + `Priority:` + valueToStringGenerated(this.Priority) + `,`, + `}`, + }, "") + return s +} +func (this *PodGroupTemplateReference) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodGroupTemplateReference{`, + `Workload:` + strings.Replace(this.Workload.String(), "WorkloadPodGroupTemplateReference", "WorkloadPodGroupTemplateReference", 1) + `,`, + `}`, + }, "") + return s +} +func (this *TopologyConstraint) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&TopologyConstraint{`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `}`, + }, "") + return s +} +func (this *TypedLocalObjectReference) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&TypedLocalObjectReference{`, + `APIGroup:` + fmt.Sprintf("%v", this.APIGroup) + `,`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `}`, + }, "") + return s +} +func (this *Workload) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Workload{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "WorkloadSpec", "WorkloadSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *WorkloadList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]Workload{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "Workload", "Workload", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&WorkloadList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *WorkloadPodGroupTemplateReference) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&WorkloadPodGroupTemplateReference{`, + `WorkloadName:` + fmt.Sprintf("%v", this.WorkloadName) + `,`, + `PodGroupTemplateName:` + fmt.Sprintf("%v", this.PodGroupTemplateName) + `,`, + `}`, + }, "") + return s +} +func (this *WorkloadSpec) String() string { + if this == nil { + return "nil" + } + repeatedStringForPodGroupTemplates := "[]PodGroupTemplate{" + for _, f := range this.PodGroupTemplates { + repeatedStringForPodGroupTemplates += strings.Replace(strings.Replace(f.String(), "PodGroupTemplate", "PodGroupTemplate", 1), `&`, ``, 1) + "," + } + repeatedStringForPodGroupTemplates += "}" + s := strings.Join([]string{`&WorkloadSpec{`, + `ControllerRef:` + strings.Replace(this.ControllerRef.String(), "TypedLocalObjectReference", "TypedLocalObjectReference", 1) + `,`, + `PodGroupTemplates:` + repeatedStringForPodGroupTemplates + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *BasicSchedulingPolicy) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BasicSchedulingPolicy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BasicSchedulingPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GangSchedulingPolicy) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GangSchedulingPolicy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GangSchedulingPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinCount", wireType) + } + m.MinCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinCount |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroup) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroup: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroup: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, PodGroup{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupResourceClaim) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupResourceClaim: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupResourceClaim: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.ResourceClaimName = &s + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimTemplateName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.ResourceClaimTemplateName = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupResourceClaimStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupResourceClaimStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupResourceClaimStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.ResourceClaimName = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupSchedulingConstraints) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupSchedulingConstraints: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupSchedulingConstraints: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topology", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Topology = append(m.Topology, TopologyConstraint{}) + if err := m.Topology[len(m.Topology)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupSchedulingPolicy) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupSchedulingPolicy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupSchedulingPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Basic", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Basic == nil { + m.Basic = &BasicSchedulingPolicy{} + } + if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gang", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gang == nil { + m.Gang = &GangSchedulingPolicy{} + } + if err := m.Gang.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PodGroupTemplateRef", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PodGroupTemplateRef == nil { + m.PodGroupTemplateRef = &PodGroupTemplateReference{} + } + if err := m.PodGroupTemplateRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchedulingPolicy", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SchedulingPolicy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchedulingConstraints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SchedulingConstraints == nil { + m.SchedulingConstraints = &PodGroupSchedulingConstraints{} + } + if err := m.SchedulingConstraints.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaims", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceClaims = append(m.ResourceClaims, PodGroupResourceClaim{}) + if err := m.ResourceClaims[len(m.ResourceClaims)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DisruptionMode", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := DisruptionMode(dAtA[iNdEx:postIndex]) + m.DisruptionMode = &s + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PriorityClassName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PriorityClassName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Priority", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Priority = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, v1.Condition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaimStatuses", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceClaimStatuses = append(m.ResourceClaimStatuses, PodGroupResourceClaimStatus{}) + if err := m.ResourceClaimStatuses[len(m.ResourceClaimStatuses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupTemplate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupTemplate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupTemplate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchedulingPolicy", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SchedulingPolicy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchedulingConstraints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SchedulingConstraints == nil { + m.SchedulingConstraints = &PodGroupSchedulingConstraints{} + } + if err := m.SchedulingConstraints.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceClaims", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceClaims = append(m.ResourceClaims, PodGroupResourceClaim{}) + if err := m.ResourceClaims[len(m.ResourceClaims)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DisruptionMode", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := DisruptionMode(dAtA[iNdEx:postIndex]) + m.DisruptionMode = &s + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PriorityClassName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PriorityClassName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Priority", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Priority = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodGroupTemplateReference) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodGroupTemplateReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodGroupTemplateReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Workload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Workload == nil { + m.Workload = &WorkloadPodGroupTemplateReference{} + } + if err := m.Workload.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TopologyConstraint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TopologyConstraint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TopologyConstraint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TypedLocalObjectReference) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TypedLocalObjectReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TypedLocalObjectReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIGroup", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIGroup = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Workload) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Workload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Workload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WorkloadList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WorkloadList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WorkloadList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, Workload{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WorkloadPodGroupTemplateReference) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WorkloadPodGroupTemplateReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WorkloadPodGroupTemplateReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WorkloadName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WorkloadName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PodGroupTemplateName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PodGroupTemplateName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WorkloadSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WorkloadSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WorkloadSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ControllerRef", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ControllerRef == nil { + m.ControllerRef = &TypedLocalObjectReference{} + } + if err := m.ControllerRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PodGroupTemplates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PodGroupTemplates = append(m.PodGroupTemplates, PodGroupTemplate{}) + if err := m.PodGroupTemplates[len(m.PodGroupTemplates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/generated.proto b/staging/src/k8s.io/api/scheduling/v1alpha2/generated.proto new file mode 100644 index 0000000000000..73b3308e38b2f --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/generated.proto @@ -0,0 +1,550 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.api.scheduling.v1alpha2; + +import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/api/scheduling/v1alpha2"; + +// BasicSchedulingPolicy indicates that standard Kubernetes +// scheduling behavior should be used. +message BasicSchedulingPolicy { +} + +// GangSchedulingPolicy defines the parameters for gang scheduling. +message GangSchedulingPolicy { + // MinCount is the minimum number of pods that must be schedulable or scheduled + // at the same time for the scheduler to admit the entire group. + // It must be a positive integer. + // + // +required + // +k8s:required + // +k8s:minimum=1 + optional int32 minCount = 1; +} + +// PodGroup represents a runtime instance of pods grouped together. +// PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from +// Workload.podGroupTemplates. +// PodGroup API enablement is toggled by the GenericWorkload feature gate. +message PodGroup { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the desired state of the PodGroup. + // + // +required + optional PodGroupSpec spec = 2; + + // Status represents the current observed state of the PodGroup. + // + // +optional + optional PodGroupStatus status = 3; +} + +// PodGroupList contains a list of PodGroup resources. +message PodGroupList { + // Standard list metadata. + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of PodGroups. + repeated PodGroup items = 2; +} + +// PodGroupResourceClaim references exactly one ResourceClaim, either directly +// or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim +// for the PodGroup. +// +// It adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. +// Pods that need access to the ResourceClaim define a matching reference in its +// own Spec.ResourceClaims. The Pod's claim must match all fields of the +// PodGroup's claim exactly. +message PodGroupResourceClaim { + // Name uniquely identifies this resource claim inside the PodGroup. + // This must be a DNS_LABEL. + // + // +required + // +k8s:required + // +k8s:format=k8s-short-name + optional string name = 1; + + // ResourceClaimName is the name of a ResourceClaim object in the same + // namespace as this PodGroup. The ResourceClaim will be reserved for the + // PodGroup instead of its individual pods. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + // + // +optional + // +k8s:optional + // +k8s:unionMember + // +k8s:format=k8s-long-name + optional string resourceClaimName = 2; + + // ResourceClaimTemplateName is the name of a ResourceClaimTemplate + // object in the same namespace as this PodGroup. + // + // The template will be used to create a new ResourceClaim, which will + // be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim + // will also be deleted. The PodGroup name and resource name, along with a + // generated component, will be used to form a unique name for the + // ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses. + // + // This field is immutable and no changes will be made to the + // corresponding ResourceClaim by the control plane after creating the + // ResourceClaim. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + // + // +optional + // +k8s:optional + // +k8s:unionMember + // +k8s:format=k8s-long-name + optional string resourceClaimTemplateName = 3; +} + +// PodGroupResourceClaimStatus is stored in the PodGroupStatus for each +// PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the +// generated name for the corresponding ResourceClaim. +message PodGroupResourceClaimStatus { + // Name uniquely identifies this resource claim inside the PodGroup. This + // must match the name of an entry in podgroup.spec.resourceClaims, which + // implies that the string must be a DNS_LABEL. + // + // +required + optional string name = 1; + + // ResourceClaimName is the name of the ResourceClaim that was generated for + // the PodGroup in the namespace of the PodGroup. If this is unset, then + // generating a ResourceClaim was not necessary. The + // podgroup.spec.resourceClaims entry can be ignored in this case. + // + // +optional + // +k8s:optional + // +k8s:format=k8s-long-name + optional string resourceClaimName = 2; +} + +// PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup. +message PodGroupSchedulingConstraints { + // Topology defines the topology constraints for the pod group. + // Currently only a single topology constraint can be specified. This may change in the future. + // + // +optional + // +k8s:optional + // +k8s:maxItems=1 + // +listType=atomic + // +k8s:listType=atomic + repeated TopologyConstraint topology = 1; +} + +// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. +// Exactly one policy must be set. +// +union +message PodGroupSchedulingPolicy { + // Basic specifies that the pods in this group should be scheduled using + // standard Kubernetes scheduling behavior. + // + // +optional + // +k8s:optional + // +k8s:unionMember + optional BasicSchedulingPolicy basic = 1; + + // Gang specifies that the pods in this group should be scheduled using + // all-or-nothing semantics. + // + // +optional + // +k8s:optional + // +k8s:unionMember + optional GangSchedulingPolicy gang = 2; +} + +// PodGroupSpec defines the desired state of a PodGroup. +message PodGroupSpec { + // PodGroupTemplateRef references an optional PodGroup template within other object + // (e.g. Workload) that was used to create the PodGroup. This field is immutable. + // + // +optional + // +k8s:optional + // +k8s:immutable + optional PodGroupTemplateReference podGroupTemplateRef = 1; + + // SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // + // +required + // +k8s:immutable + optional PodGroupSchedulingPolicy schedulingPolicy = 2; + + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + // + // +featureGate=TopologyAwareWorkloadScheduling + // +optional + // +k8s:ifDisabled(TopologyAwareWorkloadScheduling)=+k8s:forbidden + // +k8s:ifEnabled(TopologyAwareWorkloadScheduling)=+k8s:optional + // +k8s:ifEnabled(TopologyAwareWorkloadScheduling)=+k8s:immutable + optional PodGroupSchedulingConstraints schedulingConstraints = 3; + + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + // + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=4 + // +k8s:immutable + // +featureGate=DRAWorkloadResourceClaims + repeated PodGroupResourceClaim resourceClaims = 4; + + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // One of Pod, PodGroup. Defaults to Pod if unset. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable + // +default="Pod" + optional string disruptionMode = 5; + + // PriorityClassName defines the priority that should be considered when scheduling this pod group. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate + // (i.e. if no priority class is specified, admission control can set this to the global default + // priority class if it exists. Otherwise, the pod group's priority will be zero). + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:format=k8s-long-name + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable + optional string priorityClassName = 6; + + // Priority is the value of priority of this pod group. Various system components + // use this field to find the priority of the pod group. When Priority Admission + // Controller is enabled, it prevents users from setting this field. The admission + // controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:minimum=-2147483648 + optional int32 priority = 7; +} + +// PodGroupStatus represents information about the status of a pod group. +message PodGroupStatus { + // Conditions represent the latest observations of the PodGroup's state. + // + // Known condition types: + // - "PodGroupScheduled": Indicates whether the scheduling requirement has been satisfied. + // - "DisruptionTarget": Indicates whether the PodGroup is about to be terminated + // due to disruption such as preemption. + // + // Known reasons for the PodGroupScheduled condition: + // - "Unschedulable": The PodGroup cannot be scheduled due to resource constraints, + // affinity/anti-affinity rules, or insufficient capacity for the gang. + // - "SchedulerError": The PodGroup cannot be scheduled due to some internal error + // that happened during scheduling, for example due to nodeAffinity parsing errors. + // + // Known reasons for the DisruptionTarget condition: + // - "PreemptionByScheduler": The PodGroup was preempted by the scheduler to make room for + // higher-priority PodGroups or Pods. + // + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + repeated .k8s.io.apimachinery.pkg.apis.meta.v1.Condition conditions = 1; + + // Status of resource claims. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=4 + // +featureGate=DRAWorkloadResourceClaims + repeated PodGroupResourceClaimStatus resourceClaimStatuses = 2; +} + +// PodGroupTemplate represents a template for a set of pods with a scheduling policy. +message PodGroupTemplate { + // Name is a unique identifier for the PodGroupTemplate within the Workload. + // It must be a DNS label. This field is immutable. + // + // +required + // +k8s:required + // +k8s:format=k8s-short-name + optional string name = 1; + + // SchedulingPolicy defines the scheduling policy for this PodGroupTemplate. + // + // +required + optional PodGroupSchedulingPolicy schedulingPolicy = 2; + + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + // + // +featureGate=TopologyAwareWorkloadScheduling + // +optional + // +k8s:ifDisabled(TopologyAwareWorkloadScheduling)=+k8s:forbidden + // +k8s:ifEnabled(TopologyAwareWorkloadScheduling)=+k8s:optional + optional PodGroupSchedulingConstraints schedulingConstraints = 3; + + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + // + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=4 + // +k8s:immutable + // +featureGate=DRAWorkloadResourceClaims + repeated PodGroupResourceClaim resourceClaims = 4; + + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // One of Pod, PodGroup. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + optional string disruptionMode = 5; + + // PriorityClassName indicates the priority that should be considered when scheduling + // a pod group created from this template. If no priority class is specified, admission + // control can set this to the global default priority class if it exists. Otherwise, + // pod groups created from this template will have the priority set to zero. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:format=k8s-long-name + optional string priorityClassName = 6; + + // Priority is the value of priority of pod groups created from this template. Various + // system components use this field to find the priority of the pod group. When + // Priority Admission Controller is enabled, it prevents users from setting this field. + // The admission controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:minimum=-2147483648 + optional int32 priority = 7; +} + +// PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). +// Exactly one reference must be set. +// +union +message PodGroupTemplateReference { + // Workload references the PodGroupTemplate within the Workload object that was used to create + // the PodGroup. + // + // +optional + // +k8s:optional + // +k8s:unionMember + optional WorkloadPodGroupTemplateReference workload = 1; +} + +// TopologyConstraint defines a topology constraint for a PodGroup. +message TopologyConstraint { + // Key specifies the key of the node label representing the topology domain. + // All pods within the PodGroup must be colocated within the same domain instance. + // Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. + // Examples: "topology.kubernetes.io/rack" + // + // +required + // +k8s:required + // +k8s:format=k8s-label-key + optional string key = 1; +} + +// TypedLocalObjectReference allows to reference typed object inside the same namespace. +message TypedLocalObjectReference { + // APIGroup is the group for the resource being referenced. + // If APIGroup is empty, the specified Kind must be in the core API group. + // For any other third-party types, setting APIGroup is required. + // It must be a DNS subdomain. + // + // +optional + // +k8s:optional + // +k8s:format=k8s-long-name + optional string apiGroup = 1; + + // Kind is the type of resource being referenced. + // It must be a path segment name. + // + // +required + // +k8s:required + // +k8s:format=k8s-path-segment-name + optional string kind = 2; + + // Name is the name of resource being referenced. + // It must be a path segment name. + // + // +required + // +k8s:required + // +k8s:format=k8s-path-segment-name + optional string name = 3; +} + +// Workload allows for expressing scheduling constraints that should be used +// when managing the lifecycle of workloads from the scheduling perspective, +// including scheduling, preemption, eviction and other phases. +// Workload API enablement is toggled by the GenericWorkload feature gate. +message Workload { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the desired behavior of a Workload. + // + // +required + optional WorkloadSpec spec = 2; +} + +// WorkloadList contains a list of Workload resources. +message WorkloadList { + // Standard list metadata. + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of Workloads. + repeated Workload items = 2; +} + +// WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object. +message WorkloadPodGroupTemplateReference { + // WorkloadName defines the name of the Workload object. + // + // +required + // +k8s:required + // +k8s:format=k8s-long-name + optional string workloadName = 1; + + // PodGroupTemplateName defines the PodGroupTemplate name within the Workload object. + // + // +required + // +k8s:required + // +k8s:format=k8s-short-name + optional string podGroupTemplateName = 2; +} + +// WorkloadSpec defines the desired state of a Workload. +message WorkloadSpec { + // ControllerRef is an optional reference to the controlling object, such as a + // Deployment or Job. This field is intended for use by tools like CLIs + // to provide a link back to the original workload definition. + // This field is immutable. + // + // +optional + // +k8s:optional + // +k8s:immutable + optional TypedLocalObjectReference controllerRef = 1; + + // PodGroupTemplates is the list of templates that make up the Workload. + // The maximum number of templates is 8. This field is immutable. + // + // +required + // +listType=map + // +listMapKey=name + // +k8s:required + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=8 + // +k8s:immutable + repeated PodGroupTemplate podGroupTemplates = 2; +} + diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/register.go b/staging/src/k8s.io/api/scheduling/v1alpha2/register.go new file mode 100644 index 0000000000000..fc03ae5ec9a50 --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/register.go @@ -0,0 +1,55 @@ +/* +Copyright 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 v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "scheduling.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes( + SchemeGroupVersion, + &Workload{}, + &WorkloadList{}, + &PodGroup{}, + &PodGroupList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/types.go b/staging/src/k8s.io/api/scheduling/v1alpha2/types.go new file mode 100644 index 0000000000000..1f84f2c43d3d5 --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/types.go @@ -0,0 +1,601 @@ +/* +Copyright 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 v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Workload allows for expressing scheduling constraints that should be used +// when managing the lifecycle of workloads from the scheduling perspective, +// including scheduling, preemption, eviction and other phases. +// Workload API enablement is toggled by the GenericWorkload feature gate. +type Workload struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the desired behavior of a Workload. + // + // +required + Spec WorkloadSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// WorkloadList contains a list of Workload resources. +type WorkloadList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of Workloads. + Items []Workload `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// WorkloadMaxPodGroupTemplates is the maximum number of pod group templates per Workload. +const WorkloadMaxPodGroupTemplates = 8 + +// WorkloadSpec defines the desired state of a Workload. +type WorkloadSpec struct { + // ControllerRef is an optional reference to the controlling object, such as a + // Deployment or Job. This field is intended for use by tools like CLIs + // to provide a link back to the original workload definition. + // This field is immutable. + // + // +optional + // +k8s:optional + // +k8s:immutable + ControllerRef *TypedLocalObjectReference `json:"controllerRef,omitempty" protobuf:"bytes,1,opt,name=controllerRef"` + + // PodGroupTemplates is the list of templates that make up the Workload. + // The maximum number of templates is 8. This field is immutable. + // + // +required + // +listType=map + // +listMapKey=name + // +k8s:required + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=8 + // +k8s:immutable + PodGroupTemplates []PodGroupTemplate `json:"podGroupTemplates" protobuf:"bytes,2,rep,name=podGroupTemplates"` +} + +// TypedLocalObjectReference allows to reference typed object inside the same namespace. +type TypedLocalObjectReference struct { + // APIGroup is the group for the resource being referenced. + // If APIGroup is empty, the specified Kind must be in the core API group. + // For any other third-party types, setting APIGroup is required. + // It must be a DNS subdomain. + // + // +optional + // +k8s:optional + // +k8s:format=k8s-long-name + APIGroup string `json:"apiGroup,omitempty" protobuf:"bytes,1,opt,name=apiGroup"` + // Kind is the type of resource being referenced. + // It must be a path segment name. + // + // +required + // +k8s:required + // +k8s:format=k8s-path-segment-name + Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` + // Name is the name of resource being referenced. + // It must be a path segment name. + // + // +required + // +k8s:required + // +k8s:format=k8s-path-segment-name + Name string `json:"name" protobuf:"bytes,3,opt,name=name"` +} + +// MaxPodGroupResourceClaims is the maximum number of resource claims for a +// PodGroup or a Workload's PodGroupTemplate. +const MaxPodGroupResourceClaims = 4 + +// PodGroupTemplate represents a template for a set of pods with a scheduling policy. +type PodGroupTemplate struct { + // Name is a unique identifier for the PodGroupTemplate within the Workload. + // It must be a DNS label. This field is immutable. + // + // +required + // +k8s:required + // +k8s:format=k8s-short-name + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + + // SchedulingPolicy defines the scheduling policy for this PodGroupTemplate. + // + // +required + SchedulingPolicy PodGroupSchedulingPolicy `json:"schedulingPolicy" protobuf:"bytes,2,opt,name=schedulingPolicy"` + + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + // + // +featureGate=TopologyAwareWorkloadScheduling + // +optional + // +k8s:ifDisabled(TopologyAwareWorkloadScheduling)=+k8s:forbidden + // +k8s:ifEnabled(TopologyAwareWorkloadScheduling)=+k8s:optional + SchedulingConstraints *PodGroupSchedulingConstraints `json:"schedulingConstraints" protobuf:"bytes,3,opt,name=schedulingConstraints"` + + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + // + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=4 + // +k8s:immutable + // +featureGate=DRAWorkloadResourceClaims + ResourceClaims []PodGroupResourceClaim `json:"resourceClaims,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,4,rep,name=resourceClaims"` + + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // One of Pod, PodGroup. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + DisruptionMode *DisruptionMode `json:"disruptionMode,omitempty" protobuf:"bytes,5,opt,name=disruptionMode,casttype=DisruptionMode"` + + // PriorityClassName indicates the priority that should be considered when scheduling + // a pod group created from this template. If no priority class is specified, admission + // control can set this to the global default priority class if it exists. Otherwise, + // pod groups created from this template will have the priority set to zero. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:format=k8s-long-name + PriorityClassName string `json:"priorityClassName,omitempty" protobuf:"bytes,6,opt,name=priorityClassName"` + + // Priority is the value of priority of pod groups created from this template. Various + // system components use this field to find the priority of the pod group. When + // Priority Admission Controller is enabled, it prevents users from setting this field. + // The admission controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:minimum=-2147483648 + Priority *int32 `json:"priority,omitempty" protobuf:"varint,7,opt,name=priority"` +} + +// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. +// Exactly one policy must be set. +// +union +type PodGroupSchedulingPolicy struct { + // Basic specifies that the pods in this group should be scheduled using + // standard Kubernetes scheduling behavior. + // + // +optional + // +k8s:optional + // +k8s:unionMember + Basic *BasicSchedulingPolicy `json:"basic,omitempty" protobuf:"bytes,1,opt,name=basic"` + + // Gang specifies that the pods in this group should be scheduled using + // all-or-nothing semantics. + // + // +optional + // +k8s:optional + // +k8s:unionMember + Gang *GangSchedulingPolicy `json:"gang,omitempty" protobuf:"bytes,2,opt,name=gang"` +} + +// BasicSchedulingPolicy indicates that standard Kubernetes +// scheduling behavior should be used. +type BasicSchedulingPolicy struct { + // This is intentionally empty. Its presence indicates that the basic + // scheduling policy should be applied. In the future, new fields may appear, + // describing such constraints on a pod group level without "all or nothing" + // (gang) scheduling. +} + +// GangSchedulingPolicy defines the parameters for gang scheduling. +type GangSchedulingPolicy struct { + // MinCount is the minimum number of pods that must be schedulable or scheduled + // at the same time for the scheduler to admit the entire group. + // It must be a positive integer. + // + // +required + // +k8s:required + // +k8s:minimum=1 + MinCount int32 `json:"minCount" protobuf:"varint,1,opt,name=minCount"` +} + +// PodGroupResourceClaim references exactly one ResourceClaim, either directly +// or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim +// for the PodGroup. +// +// It adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. +// Pods that need access to the ResourceClaim define a matching reference in its +// own Spec.ResourceClaims. The Pod's claim must match all fields of the +// PodGroup's claim exactly. +type PodGroupResourceClaim struct { + // Name uniquely identifies this resource claim inside the PodGroup. + // This must be a DNS_LABEL. + // + // +required + // +k8s:required + // +k8s:format=k8s-short-name + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + + // ResourceClaimName is the name of a ResourceClaim object in the same + // namespace as this PodGroup. The ResourceClaim will be reserved for the + // PodGroup instead of its individual pods. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + // + // +optional + // +k8s:optional + // +k8s:unionMember + // +k8s:format=k8s-long-name + ResourceClaimName *string `json:"resourceClaimName,omitempty" protobuf:"bytes,2,opt,name=resourceClaimName"` + + // ResourceClaimTemplateName is the name of a ResourceClaimTemplate + // object in the same namespace as this PodGroup. + // + // The template will be used to create a new ResourceClaim, which will + // be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim + // will also be deleted. The PodGroup name and resource name, along with a + // generated component, will be used to form a unique name for the + // ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses. + // + // This field is immutable and no changes will be made to the + // corresponding ResourceClaim by the control plane after creating the + // ResourceClaim. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + // + // +optional + // +k8s:optional + // +k8s:unionMember + // +k8s:format=k8s-long-name + ResourceClaimTemplateName *string `json:"resourceClaimTemplateName,omitempty" protobuf:"bytes,3,opt,name=resourceClaimTemplateName"` +} + +// DisruptionMode describes the mode in which a PodGroup can be disrupted (e.g. preempted). +// +enum +// +k8s:enum +type DisruptionMode string + +const ( + // DisruptionModePod means that individual pods can be disrupted or preempted independently. + // It doesn't depend on exact set of pods currently running in this PodGroup. + DisruptionModePod DisruptionMode = "Pod" + // DisruptionModePodGroup means that the whole PodGroup needs to be disrupted + // or preempted together. + DisruptionModePodGroup DisruptionMode = "PodGroup" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:supportsSubresource="/status" + +// PodGroup represents a runtime instance of pods grouped together. +// PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from +// Workload.podGroupTemplates. +// PodGroup API enablement is toggled by the GenericWorkload feature gate. +type PodGroup struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the desired state of the PodGroup. + // + // +required + Spec PodGroupSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // Status represents the current observed state of the PodGroup. + // + // +optional + Status PodGroupStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodGroupList contains a list of PodGroup resources. +type PodGroupList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of PodGroups. + Items []PodGroup `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// PodGroupSpec defines the desired state of a PodGroup. +type PodGroupSpec struct { + // PodGroupTemplateRef references an optional PodGroup template within other object + // (e.g. Workload) that was used to create the PodGroup. This field is immutable. + // + // +optional + // +k8s:optional + // +k8s:immutable + PodGroupTemplateRef *PodGroupTemplateReference `json:"podGroupTemplateRef" protobuf:"bytes,1,opt,name=podGroupTemplateRef"` + + // SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // + // +required + // +k8s:immutable + SchedulingPolicy PodGroupSchedulingPolicy `json:"schedulingPolicy" protobuf:"bytes,2,opt,name=schedulingPolicy"` + + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + // + // +featureGate=TopologyAwareWorkloadScheduling + // +optional + // +k8s:ifDisabled(TopologyAwareWorkloadScheduling)=+k8s:forbidden + // +k8s:ifEnabled(TopologyAwareWorkloadScheduling)=+k8s:optional + // +k8s:ifEnabled(TopologyAwareWorkloadScheduling)=+k8s:immutable + SchedulingConstraints *PodGroupSchedulingConstraints `json:"schedulingConstraints,omitempty" protobuf:"bytes,3,opt,name=schedulingConstraints"` + + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + // + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=4 + // +k8s:immutable + // +featureGate=DRAWorkloadResourceClaims + ResourceClaims []PodGroupResourceClaim `json:"resourceClaims,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,4,rep,name=resourceClaims"` + + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // One of Pod, PodGroup. Defaults to Pod if unset. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable + // +default="Pod" + DisruptionMode *DisruptionMode `json:"disruptionMode,omitempty" protobuf:"bytes,5,opt,name=disruptionMode,casttype=DisruptionMode"` + + // PriorityClassName defines the priority that should be considered when scheduling this pod group. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate + // (i.e. if no priority class is specified, admission control can set this to the global default + // priority class if it exists. Otherwise, the pod group's priority will be zero). + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:format=k8s-long-name + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable + PriorityClassName string `json:"priorityClassName,omitempty" protobuf:"bytes,6,opt,name=priorityClassName"` + + // Priority is the value of priority of this pod group. Various system components + // use this field to find the priority of the pod group. When Priority Admission + // Controller is enabled, it prevents users from setting this field. The admission + // controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + // + // +featureGate=WorkloadAwarePreemption + // +optional + // +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority + // +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:minimum=-2147483648 + Priority *int32 `json:"priority,omitempty" protobuf:"varint,7,opt,name=priority"` +} + +// PodGroupStatus represents information about the status of a pod group. +type PodGroupStatus struct { + // Conditions represent the latest observations of the PodGroup's state. + // + // Known condition types: + // - "PodGroupScheduled": Indicates whether the scheduling requirement has been satisfied. + // - "DisruptionTarget": Indicates whether the PodGroup is about to be terminated + // due to disruption such as preemption. + // + // Known reasons for the PodGroupScheduled condition: + // - "Unschedulable": The PodGroup cannot be scheduled due to resource constraints, + // affinity/anti-affinity rules, or insufficient capacity for the gang. + // - "SchedulerError": The PodGroup cannot be scheduled due to some internal error + // that happened during scheduling, for example due to nodeAffinity parsing errors. + // + // Known reasons for the DisruptionTarget condition: + // - "PreemptionByScheduler": The PodGroup was preempted by the scheduler to make room for + // higher-priority PodGroups or Pods. + // + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + + // Status of resource claims. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:maxItems=4 + // +featureGate=DRAWorkloadResourceClaims + ResourceClaimStatuses []PodGroupResourceClaimStatus `json:"resourceClaimStatuses,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,2,rep,name=resourceClaimStatuses"` +} + +// Well-known condition types for PodGroups. +const ( + // PodGroupScheduled represents status of the scheduling process for this PodGroup. + PodGroupScheduled string = "PodGroupScheduled" + // DisruptionTarget indicates the PodGroup is about to be terminated due to disruption + // such as preemption. + DisruptionTarget string = "DisruptionTarget" +) + +// Well-known condition reasons for PodGroups. +const ( + // Unschedulable reason in the PodGroupScheduled condition indicates that the PodGroup cannot be scheduled + // due to resource constraints, affinity/anti-affinity rules, or insufficient capacity for the PodGroup. + PodGroupReasonUnschedulable string = "Unschedulable" + // SchedulerError reason in the PodGroupScheduled condition means that some internal error happens + // during scheduling, for example due to nodeAffinity parsing errors. + PodGroupReasonSchedulerError string = "SchedulerError" + // PreemptionByScheduler reason in the DisruptionTarget condition indicates the PodGroup was preempted + // to make room for higher-priority PodGroups or Pods. + PodGroupReasonPreemptionByScheduler string = "PreemptionByScheduler" +) + +// PodGroupResourceClaimStatus is stored in the PodGroupStatus for each +// PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the +// generated name for the corresponding ResourceClaim. +type PodGroupResourceClaimStatus struct { + // Name uniquely identifies this resource claim inside the PodGroup. This + // must match the name of an entry in podgroup.spec.resourceClaims, which + // implies that the string must be a DNS_LABEL. + // + // +required + Name string `json:"name" protobuf:"bytes,1,name=name"` + + // ResourceClaimName is the name of the ResourceClaim that was generated for + // the PodGroup in the namespace of the PodGroup. If this is unset, then + // generating a ResourceClaim was not necessary. The + // podgroup.spec.resourceClaims entry can be ignored in this case. + // + // +optional + // +k8s:optional + // +k8s:format=k8s-long-name + ResourceClaimName *string `json:"resourceClaimName,omitempty" protobuf:"bytes,2,opt,name=resourceClaimName"` +} + +// PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). +// Exactly one reference must be set. +// +union +type PodGroupTemplateReference struct { + // Workload references the PodGroupTemplate within the Workload object that was used to create + // the PodGroup. + // + // +optional + // +k8s:optional + // +k8s:unionMember + Workload *WorkloadPodGroupTemplateReference `json:"workload" protobuf:"bytes,1,opt,name=workload"` +} + +// WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object. +type WorkloadPodGroupTemplateReference struct { + // WorkloadName defines the name of the Workload object. + // + // +required + // +k8s:required + // +k8s:format=k8s-long-name + WorkloadName string `json:"workloadName" protobuf:"bytes,1,opt,name=workloadName"` + + // PodGroupTemplateName defines the PodGroupTemplate name within the Workload object. + // + // +required + // +k8s:required + // +k8s:format=k8s-short-name + PodGroupTemplateName string `json:"podGroupTemplateName" protobuf:"bytes,2,opt,name=podGroupTemplateName"` +} + +// PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup. +type PodGroupSchedulingConstraints struct { + // Topology defines the topology constraints for the pod group. + // Currently only a single topology constraint can be specified. This may change in the future. + // + // +optional + // +k8s:optional + // +k8s:maxItems=1 + // +listType=atomic + // +k8s:listType=atomic + Topology []TopologyConstraint `json:"topology,omitempty" protobuf:"bytes,1,rep,name=topology"` +} + +// TopologyConstraint defines a topology constraint for a PodGroup. +type TopologyConstraint struct { + // Key specifies the key of the node label representing the topology domain. + // All pods within the PodGroup must be colocated within the same domain instance. + // Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. + // Examples: "topology.kubernetes.io/rack" + // + // +required + // +k8s:required + // +k8s:format=k8s-label-key + Key string `json:"key" protobuf:"bytes,1,opt,name=key"` +} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/types_swagger_doc_generated.go b/staging/src/k8s.io/api/scheduling/v1alpha2/types_swagger_doc_generated.go new file mode 100644 index 0000000000000..2dc06b1c6f77f --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/types_swagger_doc_generated.go @@ -0,0 +1,217 @@ +/* +Copyright 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 v1alpha2 + +// This file contains a collection of methods that can be used from go-restful to +// generate Swagger API documentation for its models. Please read this PR for more +// information on the implementation: https://github.com/emicklei/go-restful/pull/215 +// +// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if +// they are on one line! For multiple line or blocks that you want to ignore use ---. +// Any context after a --- is ignored. +// +// Those methods can be generated by using hack/update-codegen.sh + +// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. +var map_BasicSchedulingPolicy = map[string]string{ + "": "BasicSchedulingPolicy indicates that standard Kubernetes scheduling behavior should be used.", +} + +func (BasicSchedulingPolicy) SwaggerDoc() map[string]string { + return map_BasicSchedulingPolicy +} + +var map_GangSchedulingPolicy = map[string]string{ + "": "GangSchedulingPolicy defines the parameters for gang scheduling.", + "minCount": "MinCount is the minimum number of pods that must be schedulable or scheduled at the same time for the scheduler to admit the entire group. It must be a positive integer.", +} + +func (GangSchedulingPolicy) SwaggerDoc() map[string]string { + return map_GangSchedulingPolicy +} + +var map_PodGroup = map[string]string{ + "": "PodGroup represents a runtime instance of pods grouped together. PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from Workload.podGroupTemplates. PodGroup API enablement is toggled by the GenericWorkload feature gate.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "Spec defines the desired state of the PodGroup.", + "status": "Status represents the current observed state of the PodGroup.", +} + +func (PodGroup) SwaggerDoc() map[string]string { + return map_PodGroup +} + +var map_PodGroupList = map[string]string{ + "": "PodGroupList contains a list of PodGroup resources.", + "metadata": "Standard list metadata.", + "items": "Items is the list of PodGroups.", +} + +func (PodGroupList) SwaggerDoc() map[string]string { + return map_PodGroupList +} + +var map_PodGroupResourceClaim = map[string]string{ + "": "PodGroupResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the PodGroup.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. Pods that need access to the ResourceClaim define a matching reference in its own Spec.ResourceClaims. The Pod's claim must match all fields of the PodGroup's claim exactly.", + "name": "Name uniquely identifies this resource claim inside the PodGroup. This must be a DNS_LABEL.", + "resourceClaimName": "ResourceClaimName is the name of a ResourceClaim object in the same namespace as this PodGroup. The ResourceClaim will be reserved for the PodGroup instead of its individual pods.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + "resourceClaimTemplateName": "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this PodGroup.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim will also be deleted. The PodGroup name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", +} + +func (PodGroupResourceClaim) SwaggerDoc() map[string]string { + return map_PodGroupResourceClaim +} + +var map_PodGroupResourceClaimStatus = map[string]string{ + "": "PodGroupResourceClaimStatus is stored in the PodGroupStatus for each PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the generated name for the corresponding ResourceClaim.", + "name": "Name uniquely identifies this resource claim inside the PodGroup. This must match the name of an entry in podgroup.spec.resourceClaims, which implies that the string must be a DNS_LABEL.", + "resourceClaimName": "ResourceClaimName is the name of the ResourceClaim that was generated for the PodGroup in the namespace of the PodGroup. If this is unset, then generating a ResourceClaim was not necessary. The podgroup.spec.resourceClaims entry can be ignored in this case.", +} + +func (PodGroupResourceClaimStatus) SwaggerDoc() map[string]string { + return map_PodGroupResourceClaimStatus +} + +var map_PodGroupSchedulingConstraints = map[string]string{ + "": "PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup.", + "topology": "Topology defines the topology constraints for the pod group. Currently only a single topology constraint can be specified. This may change in the future.", +} + +func (PodGroupSchedulingConstraints) SwaggerDoc() map[string]string { + return map_PodGroupSchedulingConstraints +} + +var map_PodGroupSchedulingPolicy = map[string]string{ + "": "PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. Exactly one policy must be set.", + "basic": "Basic specifies that the pods in this group should be scheduled using standard Kubernetes scheduling behavior.", + "gang": "Gang specifies that the pods in this group should be scheduled using all-or-nothing semantics.", +} + +func (PodGroupSchedulingPolicy) SwaggerDoc() map[string]string { + return map_PodGroupSchedulingPolicy +} + +var map_PodGroupSpec = map[string]string{ + "": "PodGroupSpec defines the desired state of a PodGroup.", + "podGroupTemplateRef": "PodGroupTemplateRef references an optional PodGroup template within other object (e.g. Workload) that was used to create the PodGroup. This field is immutable.", + "schedulingPolicy": "SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable.", + "schedulingConstraints": "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. Controllers are expected to fill this field by copying it from a PodGroupTemplate. This field is immutable. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled.", + "resourceClaims": "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + "disruptionMode": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. Controllers are expected to fill this field by copying it from a PodGroupTemplate. One of Pod, PodGroup. Defaults to Pod if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "priorityClassName": "PriorityClassName defines the priority that should be considered when scheduling this pod group. Controllers are expected to fill this field by copying it from a PodGroupTemplate. Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate (i.e. if no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, the pod group's priority will be zero). This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "priority": "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", +} + +func (PodGroupSpec) SwaggerDoc() map[string]string { + return map_PodGroupSpec +} + +var map_PodGroupStatus = map[string]string{ + "": "PodGroupStatus represents information about the status of a pod group.", + "conditions": "Conditions represent the latest observations of the PodGroup's state.\n\nKnown condition types: - \"PodGroupScheduled\": Indicates whether the scheduling requirement has been satisfied. - \"DisruptionTarget\": Indicates whether the PodGroup is about to be terminated\n due to disruption such as preemption.\n\nKnown reasons for the PodGroupScheduled condition: - \"Unschedulable\": The PodGroup cannot be scheduled due to resource constraints,\n affinity/anti-affinity rules, or insufficient capacity for the gang.\n- \"SchedulerError\": The PodGroup cannot be scheduled due to some internal error\n that happened during scheduling, for example due to nodeAffinity parsing errors.\n\nKnown reasons for the DisruptionTarget condition: - \"PreemptionByScheduler\": The PodGroup was preempted by the scheduler to make room for\n higher-priority PodGroups or Pods.", + "resourceClaimStatuses": "Status of resource claims.", +} + +func (PodGroupStatus) SwaggerDoc() map[string]string { + return map_PodGroupStatus +} + +var map_PodGroupTemplate = map[string]string{ + "": "PodGroupTemplate represents a template for a set of pods with a scheduling policy.", + "name": "Name is a unique identifier for the PodGroupTemplate within the Workload. It must be a DNS label. This field is immutable.", + "schedulingPolicy": "SchedulingPolicy defines the scheduling policy for this PodGroupTemplate.", + "schedulingConstraints": "SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled.", + "resourceClaims": "ResourceClaims defines which ResourceClaims may be shared among Pods in the group. Pods consume the devices allocated to a PodGroup's claim by defining a claim in its own Spec.ResourceClaims that matches the PodGroup's claim exactly. The claim must have the same name and refer to the same ResourceClaim or ResourceClaimTemplate.\n\nThis is an alpha-level field and requires that the DRAWorkloadResourceClaims feature gate is enabled.\n\nThis field is immutable.", + "disruptionMode": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. One of Pod, PodGroup. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "priorityClassName": "PriorityClassName indicates the priority that should be considered when scheduling a pod group created from this template. If no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, pod groups created from this template will have the priority set to zero. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", + "priority": "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.", +} + +func (PodGroupTemplate) SwaggerDoc() map[string]string { + return map_PodGroupTemplate +} + +var map_PodGroupTemplateReference = map[string]string{ + "": "PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). Exactly one reference must be set.", + "workload": "Workload references the PodGroupTemplate within the Workload object that was used to create the PodGroup.", +} + +func (PodGroupTemplateReference) SwaggerDoc() map[string]string { + return map_PodGroupTemplateReference +} + +var map_TopologyConstraint = map[string]string{ + "": "TopologyConstraint defines a topology constraint for a PodGroup.", + "key": "Key specifies the key of the node label representing the topology domain. All pods within the PodGroup must be colocated within the same domain instance. Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. Examples: \"topology.kubernetes.io/rack\"", +} + +func (TopologyConstraint) SwaggerDoc() map[string]string { + return map_TopologyConstraint +} + +var map_TypedLocalObjectReference = map[string]string{ + "": "TypedLocalObjectReference allows to reference typed object inside the same namespace.", + "apiGroup": "APIGroup is the group for the resource being referenced. If APIGroup is empty, the specified Kind must be in the core API group. For any other third-party types, setting APIGroup is required. It must be a DNS subdomain.", + "kind": "Kind is the type of resource being referenced. It must be a path segment name.", + "name": "Name is the name of resource being referenced. It must be a path segment name.", +} + +func (TypedLocalObjectReference) SwaggerDoc() map[string]string { + return map_TypedLocalObjectReference +} + +var map_Workload = map[string]string{ + "": "Workload allows for expressing scheduling constraints that should be used when managing the lifecycle of workloads from the scheduling perspective, including scheduling, preemption, eviction and other phases. Workload API enablement is toggled by the GenericWorkload feature gate.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "Spec defines the desired behavior of a Workload.", +} + +func (Workload) SwaggerDoc() map[string]string { + return map_Workload +} + +var map_WorkloadList = map[string]string{ + "": "WorkloadList contains a list of Workload resources.", + "metadata": "Standard list metadata.", + "items": "Items is the list of Workloads.", +} + +func (WorkloadList) SwaggerDoc() map[string]string { + return map_WorkloadList +} + +var map_WorkloadPodGroupTemplateReference = map[string]string{ + "": "WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object.", + "workloadName": "WorkloadName defines the name of the Workload object.", + "podGroupTemplateName": "PodGroupTemplateName defines the PodGroupTemplate name within the Workload object.", +} + +func (WorkloadPodGroupTemplateReference) SwaggerDoc() map[string]string { + return map_WorkloadPodGroupTemplateReference +} + +var map_WorkloadSpec = map[string]string{ + "": "WorkloadSpec defines the desired state of a Workload.", + "controllerRef": "ControllerRef is an optional reference to the controlling object, such as a Deployment or Job. This field is intended for use by tools like CLIs to provide a link back to the original workload definition. This field is immutable.", + "podGroupTemplates": "PodGroupTemplates is the list of templates that make up the Workload. The maximum number of templates is 8. This field is immutable.", +} + +func (WorkloadSpec) SwaggerDoc() map[string]string { + return map_WorkloadSpec +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/zz_generated.deepcopy.go b/staging/src/k8s.io/api/scheduling/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..901bb0a3ad981 --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,484 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BasicSchedulingPolicy) DeepCopyInto(out *BasicSchedulingPolicy) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicSchedulingPolicy. +func (in *BasicSchedulingPolicy) DeepCopy() *BasicSchedulingPolicy { + if in == nil { + return nil + } + out := new(BasicSchedulingPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GangSchedulingPolicy) DeepCopyInto(out *GangSchedulingPolicy) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GangSchedulingPolicy. +func (in *GangSchedulingPolicy) DeepCopy() *GangSchedulingPolicy { + if in == nil { + return nil + } + out := new(GangSchedulingPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroup) DeepCopyInto(out *PodGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroup. +func (in *PodGroup) DeepCopy() *PodGroup { + if in == nil { + return nil + } + out := new(PodGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupList) DeepCopyInto(out *PodGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PodGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupList. +func (in *PodGroupList) DeepCopy() *PodGroupList { + if in == nil { + return nil + } + out := new(PodGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupResourceClaim) DeepCopyInto(out *PodGroupResourceClaim) { + *out = *in + if in.ResourceClaimName != nil { + in, out := &in.ResourceClaimName, &out.ResourceClaimName + *out = new(string) + **out = **in + } + if in.ResourceClaimTemplateName != nil { + in, out := &in.ResourceClaimTemplateName, &out.ResourceClaimTemplateName + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupResourceClaim. +func (in *PodGroupResourceClaim) DeepCopy() *PodGroupResourceClaim { + if in == nil { + return nil + } + out := new(PodGroupResourceClaim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupResourceClaimStatus) DeepCopyInto(out *PodGroupResourceClaimStatus) { + *out = *in + if in.ResourceClaimName != nil { + in, out := &in.ResourceClaimName, &out.ResourceClaimName + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupResourceClaimStatus. +func (in *PodGroupResourceClaimStatus) DeepCopy() *PodGroupResourceClaimStatus { + if in == nil { + return nil + } + out := new(PodGroupResourceClaimStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupSchedulingConstraints) DeepCopyInto(out *PodGroupSchedulingConstraints) { + *out = *in + if in.Topology != nil { + in, out := &in.Topology, &out.Topology + *out = make([]TopologyConstraint, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupSchedulingConstraints. +func (in *PodGroupSchedulingConstraints) DeepCopy() *PodGroupSchedulingConstraints { + if in == nil { + return nil + } + out := new(PodGroupSchedulingConstraints) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupSchedulingPolicy) DeepCopyInto(out *PodGroupSchedulingPolicy) { + *out = *in + if in.Basic != nil { + in, out := &in.Basic, &out.Basic + *out = new(BasicSchedulingPolicy) + **out = **in + } + if in.Gang != nil { + in, out := &in.Gang, &out.Gang + *out = new(GangSchedulingPolicy) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupSchedulingPolicy. +func (in *PodGroupSchedulingPolicy) DeepCopy() *PodGroupSchedulingPolicy { + if in == nil { + return nil + } + out := new(PodGroupSchedulingPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupSpec) DeepCopyInto(out *PodGroupSpec) { + *out = *in + if in.PodGroupTemplateRef != nil { + in, out := &in.PodGroupTemplateRef, &out.PodGroupTemplateRef + *out = new(PodGroupTemplateReference) + (*in).DeepCopyInto(*out) + } + in.SchedulingPolicy.DeepCopyInto(&out.SchedulingPolicy) + if in.SchedulingConstraints != nil { + in, out := &in.SchedulingConstraints, &out.SchedulingConstraints + *out = new(PodGroupSchedulingConstraints) + (*in).DeepCopyInto(*out) + } + if in.ResourceClaims != nil { + in, out := &in.ResourceClaims, &out.ResourceClaims + *out = make([]PodGroupResourceClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DisruptionMode != nil { + in, out := &in.DisruptionMode, &out.DisruptionMode + *out = new(DisruptionMode) + **out = **in + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupSpec. +func (in *PodGroupSpec) DeepCopy() *PodGroupSpec { + if in == nil { + return nil + } + out := new(PodGroupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupStatus) DeepCopyInto(out *PodGroupStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ResourceClaimStatuses != nil { + in, out := &in.ResourceClaimStatuses, &out.ResourceClaimStatuses + *out = make([]PodGroupResourceClaimStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupStatus. +func (in *PodGroupStatus) DeepCopy() *PodGroupStatus { + if in == nil { + return nil + } + out := new(PodGroupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupTemplate) DeepCopyInto(out *PodGroupTemplate) { + *out = *in + in.SchedulingPolicy.DeepCopyInto(&out.SchedulingPolicy) + if in.SchedulingConstraints != nil { + in, out := &in.SchedulingConstraints, &out.SchedulingConstraints + *out = new(PodGroupSchedulingConstraints) + (*in).DeepCopyInto(*out) + } + if in.ResourceClaims != nil { + in, out := &in.ResourceClaims, &out.ResourceClaims + *out = make([]PodGroupResourceClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DisruptionMode != nil { + in, out := &in.DisruptionMode, &out.DisruptionMode + *out = new(DisruptionMode) + **out = **in + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupTemplate. +func (in *PodGroupTemplate) DeepCopy() *PodGroupTemplate { + if in == nil { + return nil + } + out := new(PodGroupTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGroupTemplateReference) DeepCopyInto(out *PodGroupTemplateReference) { + *out = *in + if in.Workload != nil { + in, out := &in.Workload, &out.Workload + *out = new(WorkloadPodGroupTemplateReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGroupTemplateReference. +func (in *PodGroupTemplateReference) DeepCopy() *PodGroupTemplateReference { + if in == nil { + return nil + } + out := new(PodGroupTemplateReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TopologyConstraint) DeepCopyInto(out *TopologyConstraint) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TopologyConstraint. +func (in *TopologyConstraint) DeepCopy() *TopologyConstraint { + if in == nil { + return nil + } + out := new(TopologyConstraint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypedLocalObjectReference) DeepCopyInto(out *TypedLocalObjectReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedLocalObjectReference. +func (in *TypedLocalObjectReference) DeepCopy() *TypedLocalObjectReference { + if in == nil { + return nil + } + out := new(TypedLocalObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Workload) DeepCopyInto(out *Workload) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workload. +func (in *Workload) DeepCopy() *Workload { + if in == nil { + return nil + } + out := new(Workload) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Workload) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadList) DeepCopyInto(out *WorkloadList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Workload, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadList. +func (in *WorkloadList) DeepCopy() *WorkloadList { + if in == nil { + return nil + } + out := new(WorkloadList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkloadList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadPodGroupTemplateReference) DeepCopyInto(out *WorkloadPodGroupTemplateReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadPodGroupTemplateReference. +func (in *WorkloadPodGroupTemplateReference) DeepCopy() *WorkloadPodGroupTemplateReference { + if in == nil { + return nil + } + out := new(WorkloadPodGroupTemplateReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) { + *out = *in + if in.ControllerRef != nil { + in, out := &in.ControllerRef, &out.ControllerRef + *out = new(TypedLocalObjectReference) + **out = **in + } + if in.PodGroupTemplates != nil { + in, out := &in.PodGroupTemplates, &out.PodGroupTemplates + *out = make([]PodGroupTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadSpec. +func (in *WorkloadSpec) DeepCopy() *WorkloadSpec { + if in == nil { + return nil + } + out := new(WorkloadSpec) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/api/scheduling/v1alpha2/zz_generated.model_name.go b/staging/src/k8s.io/api/scheduling/v1alpha2/zz_generated.model_name.go new file mode 100644 index 0000000000000..1b7effea965ce --- /dev/null +++ b/staging/src/k8s.io/api/scheduling/v1alpha2/zz_generated.model_name.go @@ -0,0 +1,112 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1alpha2 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in BasicSchedulingPolicy) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GangSchedulingPolicy) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroup) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroup" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupList) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupResourceClaim) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupResourceClaimStatus) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupSchedulingConstraints) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupSchedulingPolicy) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupSpec) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupStatus) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupTemplate) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupTemplate" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PodGroupTemplateReference) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TopologyConstraint) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.TopologyConstraint" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TypedLocalObjectReference) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Workload) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.Workload" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WorkloadList) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.WorkloadList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WorkloadPodGroupTemplateReference) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WorkloadSpec) OpenAPIModelName() string { + return "io.k8s.api.scheduling.v1alpha2.WorkloadSpec" +} diff --git a/staging/src/k8s.io/api/scheduling/v1beta1/generated.proto b/staging/src/k8s.io/api/scheduling/v1beta1/generated.proto index 7f77b01753f57..0e701be5495f3 100644 --- a/staging/src/k8s.io/api/scheduling/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/scheduling/v1beta1/generated.proto @@ -40,6 +40,7 @@ message PriorityClass { // value represents the integer value of this priority class. This is the actual priority that pods // receive when they have the name of this class in their pod spec. + // +optional optional int32 value = 2; // globalDefault specifies whether this PriorityClass should be considered as diff --git a/staging/src/k8s.io/api/scheduling/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/scheduling/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 0c7339ae79ea1..0000000000000 --- a/staging/src/k8s.io/api/scheduling/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*PriorityClass) ProtoMessage() {} - -func (*PriorityClassList) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/scheduling/v1beta1/types.go b/staging/src/k8s.io/api/scheduling/v1beta1/types.go index 6f88592cf20a6..8a7a2058f0b91 100644 --- a/staging/src/k8s.io/api/scheduling/v1beta1/types.go +++ b/staging/src/k8s.io/api/scheduling/v1beta1/types.go @@ -41,6 +41,7 @@ type PriorityClass struct { // value represents the integer value of this priority class. This is the actual priority that pods // receive when they have the name of this class in their pod spec. + // +optional Value int32 `json:"value" protobuf:"bytes,2,opt,name=value"` // globalDefault specifies whether this PriorityClass should be considered as diff --git a/staging/src/k8s.io/api/storage/v1/generated.pb.go b/staging/src/k8s.io/api/storage/v1/generated.pb.go index 52003c313298d..5fc7d55a5b3c9 100644 --- a/staging/src/k8s.io/api/storage/v1/generated.pb.go +++ b/staging/src/k8s.io/api/storage/v1/generated.pb.go @@ -187,6 +187,16 @@ func (m *CSIDriverSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.PreventPodSchedulingIfMissing != nil { + i-- + if *m.PreventPodSchedulingIfMissing { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + } if m.ServiceAccountTokenInSecrets != nil { i-- if *m.ServiceAccountTokenInSecrets { @@ -1304,6 +1314,9 @@ func (m *CSIDriverSpec) Size() (n int) { if m.ServiceAccountTokenInSecrets != nil { n += 2 } + if m.PreventPodSchedulingIfMissing != nil { + n += 2 + } return n } @@ -1701,6 +1714,7 @@ func (this *CSIDriverSpec) String() string { `SELinuxMount:` + valueToStringGenerated(this.SELinuxMount) + `,`, `NodeAllocatableUpdatePeriodSeconds:` + valueToStringGenerated(this.NodeAllocatableUpdatePeriodSeconds) + `,`, `ServiceAccountTokenInSecrets:` + valueToStringGenerated(this.ServiceAccountTokenInSecrets) + `,`, + `PreventPodSchedulingIfMissing:` + valueToStringGenerated(this.PreventPodSchedulingIfMissing) + `,`, `}`, }, "") return s @@ -2498,6 +2512,27 @@ func (m *CSIDriverSpec) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.ServiceAccountTokenInSecrets = &b + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PreventPodSchedulingIfMissing", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.PreventPodSchedulingIfMissing = &b default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/storage/v1/generated.proto b/staging/src/k8s.io/api/storage/v1/generated.proto index d77bea9cc0852..d6bc969bb3ab7 100644 --- a/staging/src/k8s.io/api/storage/v1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1/generated.proto @@ -218,7 +218,7 @@ message CSIDriverSpec { // occur (neither periodic nor upon detecting capacity-related failures), and the // allocatable.count remains static. The minimum allowed value for this field is 10 seconds. // - // This is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled. + // This feature requires the MutableCSINodeAllocatableCount feature gate to be enabled. // // This field is mutable. // @@ -249,6 +249,27 @@ message CSIDriverSpec { // +featureGate=CSIServiceAccountTokenSecrets // +optional optional bool serviceAccountTokenInSecrets = 10; + + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + // +featureGate=VolumeLimitScaling + // +optional + optional bool preventPodSchedulingIfMissing = 11; } // CSINode holds information about all CSI drivers installed on a node. @@ -434,17 +455,22 @@ message StorageClass { // provisioner indicates the type of the provisioner. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable optional string provisioner = 2; // parameters holds the parameters for the provisioner that should // create volumes of this storage class. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional map parameters = 3; // reclaimPolicy controls the reclaimPolicy for dynamically provisioned PersistentVolumes of this storage class. // Defaults to Delete. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional optional string reclaimPolicy = 4; // mountOptions controls the mountOptions for dynamically provisioned PersistentVolumes of this storage class. @@ -462,6 +488,8 @@ message StorageClass { // provisioned and bound. When unset, VolumeBindingImmediate is used. // This field is only honored by servers that enable the VolumeScheduling feature. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional optional string volumeBindingMode = 7; // allowedTopologies restrict the node topologies where volumes can be dynamically provisioned. @@ -509,6 +537,8 @@ message VolumeAttachment { // spec represents specification of the desired attach/detach volume behavior. // Populated by the Kubernetes system. + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +required optional VolumeAttachmentSpec spec = 2; // status represents status of the VolumeAttachment request. @@ -552,6 +582,10 @@ message VolumeAttachmentSource { message VolumeAttachmentSpec { // attacher indicates the name of the volume driver that MUST handle this // request. This is the name returned by GetPluginName(). + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-long-name-caseless" + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 optional string attacher = 1; // source represents the volume that should be attached. @@ -643,7 +677,7 @@ message VolumeError { // errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations. // - // This is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. + // This field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. // // +featureGate=MutableCSINodeAllocatableCount // +optional diff --git a/staging/src/k8s.io/api/storage/v1/generated.protomessage.pb.go b/staging/src/k8s.io/api/storage/v1/generated.protomessage.pb.go deleted file mode 100644 index 370206042254e..0000000000000 --- a/staging/src/k8s.io/api/storage/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*CSIDriver) ProtoMessage() {} - -func (*CSIDriverList) ProtoMessage() {} - -func (*CSIDriverSpec) ProtoMessage() {} - -func (*CSINode) ProtoMessage() {} - -func (*CSINodeDriver) ProtoMessage() {} - -func (*CSINodeList) ProtoMessage() {} - -func (*CSINodeSpec) ProtoMessage() {} - -func (*CSIStorageCapacity) ProtoMessage() {} - -func (*CSIStorageCapacityList) ProtoMessage() {} - -func (*StorageClass) ProtoMessage() {} - -func (*StorageClassList) ProtoMessage() {} - -func (*TokenRequest) ProtoMessage() {} - -func (*VolumeAttachment) ProtoMessage() {} - -func (*VolumeAttachmentList) ProtoMessage() {} - -func (*VolumeAttachmentSource) ProtoMessage() {} - -func (*VolumeAttachmentSpec) ProtoMessage() {} - -func (*VolumeAttachmentStatus) ProtoMessage() {} - -func (*VolumeAttributesClass) ProtoMessage() {} - -func (*VolumeAttributesClassList) ProtoMessage() {} - -func (*VolumeError) ProtoMessage() {} - -func (*VolumeNodeResources) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/storage/v1/types.go b/staging/src/k8s.io/api/storage/v1/types.go index b198cb7138a77..c65ebed4ecb46 100644 --- a/staging/src/k8s.io/api/storage/v1/types.go +++ b/staging/src/k8s.io/api/storage/v1/types.go @@ -42,17 +42,22 @@ type StorageClass struct { // provisioner indicates the type of the provisioner. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable Provisioner string `json:"provisioner" protobuf:"bytes,2,opt,name=provisioner"` // parameters holds the parameters for the provisioner that should // create volumes of this storage class. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional Parameters map[string]string `json:"parameters,omitempty" protobuf:"bytes,3,rep,name=parameters"` // reclaimPolicy controls the reclaimPolicy for dynamically provisioned PersistentVolumes of this storage class. // Defaults to Delete. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional ReclaimPolicy *v1.PersistentVolumeReclaimPolicy `json:"reclaimPolicy,omitempty" protobuf:"bytes,4,opt,name=reclaimPolicy,casttype=k8s.io/api/core/v1.PersistentVolumeReclaimPolicy"` // mountOptions controls the mountOptions for dynamically provisioned PersistentVolumes of this storage class. @@ -70,6 +75,8 @@ type StorageClass struct { // provisioned and bound. When unset, VolumeBindingImmediate is used. // This field is only honored by servers that enable the VolumeScheduling feature. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional VolumeBindingMode *VolumeBindingMode `json:"volumeBindingMode,omitempty" protobuf:"bytes,7,opt,name=volumeBindingMode"` // allowedTopologies restrict the node topologies where volumes can be dynamically provisioned. @@ -117,6 +124,7 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.13 +// +k8s:supportsSubresource="/status" // VolumeAttachment captures the intent to attach or detach the specified volume // to/from the specified node. @@ -132,6 +140,8 @@ type VolumeAttachment struct { // spec represents specification of the desired attach/detach volume behavior. // Populated by the Kubernetes system. + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +required Spec VolumeAttachmentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // status represents status of the VolumeAttachment request. @@ -161,6 +171,10 @@ type VolumeAttachmentList struct { type VolumeAttachmentSpec struct { // attacher indicates the name of the volume driver that MUST handle this // request. This is the name returned by GetPluginName(). + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-long-name-caseless" + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 Attacher string `json:"attacher" protobuf:"bytes,1,opt,name=attacher"` // source represents the volume that should be attached. @@ -231,7 +245,7 @@ type VolumeError struct { // errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations. // - // This is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. + // This field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. // // +featureGate=MutableCSINodeAllocatableCount // +optional @@ -438,7 +452,7 @@ type CSIDriverSpec struct { // occur (neither periodic nor upon detecting capacity-related failures), and the // allocatable.count remains static. The minimum allowed value for this field is 10 seconds. // - // This is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled. + // This feature requires the MutableCSINodeAllocatableCount feature gate to be enabled. // // This field is mutable. // @@ -469,6 +483,27 @@ type CSIDriverSpec struct { // +featureGate=CSIServiceAccountTokenSecrets // +optional ServiceAccountTokenInSecrets *bool `json:"serviceAccountTokenInSecrets,omitempty" protobuf:"varint,10,opt,name=serviceAccountTokenInSecrets"` + + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + // +featureGate=VolumeLimitScaling + // +optional + PreventPodSchedulingIfMissing *bool `json:"preventPodSchedulingIfMissing,omitempty" protobuf:"varint,11,opt,name=preventPodSchedulingIfMissing"` } // FSGroupPolicy specifies if a CSI Driver supports modifying diff --git a/staging/src/k8s.io/api/storage/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/storage/v1/types_swagger_doc_generated.go index 7f06c2744f9cb..47a7e92492e54 100644 --- a/staging/src/k8s.io/api/storage/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/storage/v1/types_swagger_doc_generated.go @@ -57,8 +57,9 @@ var map_CSIDriverSpec = map[string]string{ "tokenRequests": "tokenRequests indicates the CSI driver needs pods' service account tokens it is mounting volume for to do necessary authentication. Kubelet will pass the tokens in VolumeContext in the CSI NodePublishVolume calls. The CSI driver should parse and validate the following VolumeContext: \"csi.storage.k8s.io/serviceAccount.tokens\": {\n \"\": {\n \"token\": ,\n \"expirationTimestamp\": ,\n },\n ...\n}\n\nNote: Audience in each TokenRequest should be different and at most one token is empty string. To receive a new token after expiry, RequiresRepublish can be used to trigger NodePublishVolume periodically.", "requiresRepublish": "requiresRepublish indicates the CSI driver wants `NodePublishVolume` being periodically called to reflect any possible change in the mounted volume. This field defaults to false.\n\nNote: After a successful initial NodePublishVolume call, subsequent calls to NodePublishVolume should only update the contents of the volume. New mount points will not be seen by a running container.", "seLinuxMount": "seLinuxMount specifies if the CSI driver supports \"-o context\" mount option.\n\nWhen \"true\", the CSI driver must ensure that all volumes provided by this CSI driver can be mounted separately with different `-o context` options. This is typical for storage backends that provide volumes as filesystems on block devices or as independent shared volumes. Kubernetes will call NodeStage / NodePublish with \"-o context=xyz\" mount option when mounting a ReadWriteOncePod volume used in Pod that has explicitly set SELinux context. In the future, it may be expanded to other volume AccessModes. In any case, Kubernetes will ensure that the volume is mounted only with a single SELinux context.\n\nWhen \"false\", Kubernetes won't pass any special SELinux mount options to the driver. This is typical for volumes that represent subdirectories of a bigger shared filesystem.\n\nDefault is \"false\".", - "nodeAllocatableUpdatePeriodSeconds": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", + "nodeAllocatableUpdatePeriodSeconds": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis feature requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", "serviceAccountTokenInSecrets": "serviceAccountTokenInSecrets is an opt-in for CSI drivers to indicate that service account tokens should be passed via the Secrets field in NodePublishVolumeRequest instead of the VolumeContext field. The CSI specification provides a dedicated Secrets field for sensitive information like tokens, which is the appropriate mechanism for handling credentials. This addresses security concerns where sensitive tokens were being logged as part of volume context.\n\nWhen \"true\", kubelet will pass the tokens only in the Secrets field with the key \"csi.storage.k8s.io/serviceAccount.tokens\". The CSI driver must be updated to read tokens from the Secrets field instead of VolumeContext.\n\nWhen \"false\" or not set, kubelet will pass the tokens in VolumeContext with the key \"csi.storage.k8s.io/serviceAccount.tokens\" (existing behavior). This maintains backward compatibility with existing CSI drivers.\n\nThis field can only be set when TokenRequests is configured. The API server will reject CSIDriver specs that set this field without TokenRequests.\n\nDefault behavior if unset is to pass tokens in the VolumeContext field.", + "preventPodSchedulingIfMissing": "PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod scheduling if the CSI driver on the node is missing.\n\nEnabling this option will prevent the scheduler (or any other component which embeds default scheduler such as cluster-autoscaler) from scheduling pods to nodes where CSI driver is not installed.\n\nFor components(such as cluster-autoscaler) that embed the scheduler and run pod placement simulations using scheduler plugins, they MUST be aware of CSI driver registration information via CSINode object. They must create simulated CSINode objects in addition to Node objects during scheduling simulation, otherwise if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any newly created node may be rejected by the scheduler because of missing CSI driver information from the node.\n\nThis is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. Default is \"false\".", } func (CSIDriverSpec) SwaggerDoc() map[string]string { @@ -243,7 +244,7 @@ var map_VolumeError = map[string]string{ "": "VolumeError captures an error encountered during a volume operation.", "time": "time represents the time the error was encountered.", "message": "message represents the error encountered during Attach or Detach operation. This string may be logged, so it should not contain sensitive information.", - "errorCode": "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", + "errorCode": "errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations.\n\nThis field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set.", } func (VolumeError) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/storage/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/storage/v1/zz_generated.deepcopy.go index b9e0a7d91253b..aa3ffd06861f3 100644 --- a/staging/src/k8s.io/api/storage/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/storage/v1/zz_generated.deepcopy.go @@ -142,6 +142,11 @@ func (in *CSIDriverSpec) DeepCopyInto(out *CSIDriverSpec) { *out = new(bool) **out = **in } + if in.PreventPodSchedulingIfMissing != nil { + in, out := &in.PreventPodSchedulingIfMissing, &out.PreventPodSchedulingIfMissing + *out = new(bool) + **out = **in + } return } diff --git a/staging/src/k8s.io/api/storage/v1alpha1/generated.proto b/staging/src/k8s.io/api/storage/v1alpha1/generated.proto index c1abc3fb36a2e..837d011640ba6 100644 --- a/staging/src/k8s.io/api/storage/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1alpha1/generated.proto @@ -134,6 +134,8 @@ message VolumeAttachment { // spec represents specification of the desired attach/detach volume behavior. // Populated by the Kubernetes system. + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +required optional VolumeAttachmentSpec spec = 2; // status represents status of the VolumeAttachment request. @@ -177,6 +179,10 @@ message VolumeAttachmentSource { message VolumeAttachmentSpec { // attacher indicates the name of the volume driver that MUST handle this // request. This is the name returned by GetPluginName(). + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-long-name-caseless" + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 optional string attacher = 1; // source represents the volume that should be attached. diff --git a/staging/src/k8s.io/api/storage/v1alpha1/generated.protomessage.pb.go b/staging/src/k8s.io/api/storage/v1alpha1/generated.protomessage.pb.go deleted file mode 100644 index d58dcac3c243f..0000000000000 --- a/staging/src/k8s.io/api/storage/v1alpha1/generated.protomessage.pb.go +++ /dev/null @@ -1,42 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1alpha1 - -func (*CSIStorageCapacity) ProtoMessage() {} - -func (*CSIStorageCapacityList) ProtoMessage() {} - -func (*VolumeAttachment) ProtoMessage() {} - -func (*VolumeAttachmentList) ProtoMessage() {} - -func (*VolumeAttachmentSource) ProtoMessage() {} - -func (*VolumeAttachmentSpec) ProtoMessage() {} - -func (*VolumeAttachmentStatus) ProtoMessage() {} - -func (*VolumeAttributesClass) ProtoMessage() {} - -func (*VolumeAttributesClassList) ProtoMessage() {} - -func (*VolumeError) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/storage/v1alpha1/types.go b/staging/src/k8s.io/api/storage/v1alpha1/types.go index 2785b01d4a2b6..286ac42aaa09a 100644 --- a/staging/src/k8s.io/api/storage/v1alpha1/types.go +++ b/staging/src/k8s.io/api/storage/v1alpha1/types.go @@ -28,6 +28,7 @@ import ( // +k8s:prerelease-lifecycle-gen:introduced=1.9 // +k8s:prerelease-lifecycle-gen:deprecated=1.21 // +k8s:prerelease-lifecycle-gen:replacement=storage.k8s.io,v1,VolumeAttachment +// +k8s:supportsSubresource="/status" // VolumeAttachment captures the intent to attach or detach the specified volume // to/from the specified node. @@ -43,6 +44,8 @@ type VolumeAttachment struct { // spec represents specification of the desired attach/detach volume behavior. // Populated by the Kubernetes system. + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +required Spec VolumeAttachmentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // status represents status of the VolumeAttachment request. @@ -74,6 +77,10 @@ type VolumeAttachmentList struct { type VolumeAttachmentSpec struct { // attacher indicates the name of the volume driver that MUST handle this // request. This is the name returned by GetPluginName(). + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-long-name-caseless" + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 Attacher string `json:"attacher" protobuf:"bytes,1,opt,name=attacher"` // source represents the volume that should be attached. diff --git a/staging/src/k8s.io/api/storage/v1beta1/generated.pb.go b/staging/src/k8s.io/api/storage/v1beta1/generated.pb.go index ed03dd5059faf..7fd08783aa5b3 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/storage/v1beta1/generated.pb.go @@ -187,6 +187,16 @@ func (m *CSIDriverSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.PreventPodSchedulingIfMissing != nil { + i-- + if *m.PreventPodSchedulingIfMissing { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + } if m.ServiceAccountTokenInSecrets != nil { i-- if *m.ServiceAccountTokenInSecrets { @@ -1304,6 +1314,9 @@ func (m *CSIDriverSpec) Size() (n int) { if m.ServiceAccountTokenInSecrets != nil { n += 2 } + if m.PreventPodSchedulingIfMissing != nil { + n += 2 + } return n } @@ -1701,6 +1714,7 @@ func (this *CSIDriverSpec) String() string { `SELinuxMount:` + valueToStringGenerated(this.SELinuxMount) + `,`, `NodeAllocatableUpdatePeriodSeconds:` + valueToStringGenerated(this.NodeAllocatableUpdatePeriodSeconds) + `,`, `ServiceAccountTokenInSecrets:` + valueToStringGenerated(this.ServiceAccountTokenInSecrets) + `,`, + `PreventPodSchedulingIfMissing:` + valueToStringGenerated(this.PreventPodSchedulingIfMissing) + `,`, `}`, }, "") return s @@ -2498,6 +2512,27 @@ func (m *CSIDriverSpec) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.ServiceAccountTokenInSecrets = &b + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PreventPodSchedulingIfMissing", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.PreventPodSchedulingIfMissing = &b default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/storage/v1beta1/generated.proto b/staging/src/k8s.io/api/storage/v1beta1/generated.proto index 33b904ee6d940..3cae755fb4f38 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1beta1/generated.proto @@ -251,6 +251,27 @@ message CSIDriverSpec { // +featureGate=CSIServiceAccountTokenSecrets // +optional optional bool serviceAccountTokenInSecrets = 10; + + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + // +featureGate=VolumeLimitScaling + // +optional + optional bool preventPodSchedulingIfMissing = 11; } // DEPRECATED - This group version of CSINode is deprecated by storage/v1/CSINode. @@ -436,17 +457,22 @@ message StorageClass { // provisioner indicates the type of the provisioner. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable optional string provisioner = 2; // parameters holds the parameters for the provisioner that should // create volumes of this storage class. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional map parameters = 3; // reclaimPolicy controls the reclaimPolicy for dynamically provisioned PersistentVolumes of this storage class. // Defaults to Delete. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional optional string reclaimPolicy = 4; // mountOptions controls the mountOptions for dynamically provisioned PersistentVolumes of this storage class. @@ -464,6 +490,8 @@ message StorageClass { // provisioned and bound. When unset, VolumeBindingImmediate is used. // This field is only honored by servers that enable the VolumeScheduling feature. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional optional string volumeBindingMode = 7; // allowedTopologies restrict the node topologies where volumes can be dynamically provisioned. @@ -511,6 +539,8 @@ message VolumeAttachment { // spec represents specification of the desired attach/detach volume behavior. // Populated by the Kubernetes system. + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +required optional VolumeAttachmentSpec spec = 2; // status represents status of the VolumeAttachment request. @@ -554,6 +584,10 @@ message VolumeAttachmentSource { message VolumeAttachmentSpec { // attacher indicates the name of the volume driver that MUST handle this // request. This is the name returned by GetPluginName(). + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-long-name-caseless" + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 optional string attacher = 1; // source represents the volume that should be attached. diff --git a/staging/src/k8s.io/api/storage/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/storage/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index a288b7fb6a0a2..0000000000000 --- a/staging/src/k8s.io/api/storage/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*CSIDriver) ProtoMessage() {} - -func (*CSIDriverList) ProtoMessage() {} - -func (*CSIDriverSpec) ProtoMessage() {} - -func (*CSINode) ProtoMessage() {} - -func (*CSINodeDriver) ProtoMessage() {} - -func (*CSINodeList) ProtoMessage() {} - -func (*CSINodeSpec) ProtoMessage() {} - -func (*CSIStorageCapacity) ProtoMessage() {} - -func (*CSIStorageCapacityList) ProtoMessage() {} - -func (*StorageClass) ProtoMessage() {} - -func (*StorageClassList) ProtoMessage() {} - -func (*TokenRequest) ProtoMessage() {} - -func (*VolumeAttachment) ProtoMessage() {} - -func (*VolumeAttachmentList) ProtoMessage() {} - -func (*VolumeAttachmentSource) ProtoMessage() {} - -func (*VolumeAttachmentSpec) ProtoMessage() {} - -func (*VolumeAttachmentStatus) ProtoMessage() {} - -func (*VolumeAttributesClass) ProtoMessage() {} - -func (*VolumeAttributesClassList) ProtoMessage() {} - -func (*VolumeError) ProtoMessage() {} - -func (*VolumeNodeResources) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/storage/v1beta1/types.go b/staging/src/k8s.io/api/storage/v1beta1/types.go index b5dde72ad9519..0c33e218e7768 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/types.go +++ b/staging/src/k8s.io/api/storage/v1beta1/types.go @@ -44,17 +44,22 @@ type StorageClass struct { // provisioner indicates the type of the provisioner. // +required - // +k8s:required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:immutable Provisioner string `json:"provisioner" protobuf:"bytes,2,opt,name=provisioner"` // parameters holds the parameters for the provisioner that should // create volumes of this storage class. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional Parameters map[string]string `json:"parameters,omitempty" protobuf:"bytes,3,rep,name=parameters"` // reclaimPolicy controls the reclaimPolicy for dynamically provisioned PersistentVolumes of this storage class. // Defaults to Delete. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional ReclaimPolicy *v1.PersistentVolumeReclaimPolicy `json:"reclaimPolicy,omitempty" protobuf:"bytes,4,opt,name=reclaimPolicy,casttype=k8s.io/api/core/v1.PersistentVolumeReclaimPolicy"` // mountOptions controls the mountOptions for dynamically provisioned PersistentVolumes of this storage class. @@ -72,6 +77,8 @@ type StorageClass struct { // provisioned and bound. When unset, VolumeBindingImmediate is used. // This field is only honored by servers that enable the VolumeScheduling feature. // +optional + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +k8s:alpha(since: "1.36")=+k8s:optional VolumeBindingMode *VolumeBindingMode `json:"volumeBindingMode,omitempty" protobuf:"bytes,7,opt,name=volumeBindingMode"` // allowedTopologies restrict the node topologies where volumes can be dynamically provisioned. @@ -122,6 +129,7 @@ const ( // +k8s:prerelease-lifecycle-gen:introduced=1.10 // +k8s:prerelease-lifecycle-gen:deprecated=1.19 // +k8s:prerelease-lifecycle-gen:replacement=storage.k8s.io,v1,VolumeAttachment +// +k8s:supportsSubresource="/status" // VolumeAttachment captures the intent to attach or detach the specified volume // to/from the specified node. @@ -137,6 +145,8 @@ type VolumeAttachment struct { // spec represents specification of the desired attach/detach volume behavior. // Populated by the Kubernetes system. + // +k8s:alpha(since: "1.36")=+k8s:immutable + // +required Spec VolumeAttachmentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // status represents status of the VolumeAttachment request. @@ -168,6 +178,10 @@ type VolumeAttachmentList struct { type VolumeAttachmentSpec struct { // attacher indicates the name of the volume driver that MUST handle this // request. This is the name returned by GetPluginName(). + // +required + // +k8s:alpha(since: "1.36")=+k8s:required + // +k8s:alpha(since: "1.36")=+k8s:format="k8s-long-name-caseless" + // +k8s:alpha(since: "1.36")=+k8s:maxLength=63 Attacher string `json:"attacher" protobuf:"bytes,1,opt,name=attacher"` // source represents the volume that should be attached. @@ -482,6 +496,27 @@ type CSIDriverSpec struct { // +featureGate=CSIServiceAccountTokenSecrets // +optional ServiceAccountTokenInSecrets *bool `json:"serviceAccountTokenInSecrets,omitempty" protobuf:"varint,10,opt,name=serviceAccountTokenInSecrets"` + + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + // +featureGate=VolumeLimitScaling + // +optional + PreventPodSchedulingIfMissing *bool `json:"preventPodSchedulingIfMissing,omitempty" protobuf:"varint,11,opt,name=preventPodSchedulingIfMissing"` } // FSGroupPolicy specifies if a CSI Driver supports modifying diff --git a/staging/src/k8s.io/api/storage/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/storage/v1beta1/types_swagger_doc_generated.go index 78da9266e09f8..80b68839b4ebe 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/storage/v1beta1/types_swagger_doc_generated.go @@ -59,6 +59,7 @@ var map_CSIDriverSpec = map[string]string{ "seLinuxMount": "seLinuxMount specifies if the CSI driver supports \"-o context\" mount option.\n\nWhen \"true\", the CSI driver must ensure that all volumes provided by this CSI driver can be mounted separately with different `-o context` options. This is typical for storage backends that provide volumes as filesystems on block devices or as independent shared volumes. Kubernetes will call NodeStage / NodePublish with \"-o context=xyz\" mount option when mounting a ReadWriteOncePod volume used in Pod that has explicitly set SELinux context. In the future, it may be expanded to other volume AccessModes. In any case, Kubernetes will ensure that the volume is mounted only with a single SELinux context.\n\nWhen \"false\", Kubernetes won't pass any special SELinux mount options to the driver. This is typical for volumes that represent subdirectories of a bigger shared filesystem.\n\nDefault is \"false\".", "nodeAllocatableUpdatePeriodSeconds": "nodeAllocatableUpdatePeriodSeconds specifies the interval between periodic updates of the CSINode allocatable capacity for this driver. When set, both periodic updates and updates triggered by capacity-related failures are enabled. If not set, no updates occur (neither periodic nor upon detecting capacity-related failures), and the allocatable.count remains static. The minimum allowed value for this field is 10 seconds.\n\nThis is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled.\n\nThis field is mutable.", "serviceAccountTokenInSecrets": "serviceAccountTokenInSecrets is an opt-in for CSI drivers to indicate that service account tokens should be passed via the Secrets field in NodePublishVolumeRequest instead of the VolumeContext field. The CSI specification provides a dedicated Secrets field for sensitive information like tokens, which is the appropriate mechanism for handling credentials. This addresses security concerns where sensitive tokens were being logged as part of volume context.\n\nWhen \"true\", kubelet will pass the tokens only in the Secrets field with the key \"csi.storage.k8s.io/serviceAccount.tokens\". The CSI driver must be updated to read tokens from the Secrets field instead of VolumeContext.\n\nWhen \"false\" or not set, kubelet will pass the tokens in VolumeContext with the key \"csi.storage.k8s.io/serviceAccount.tokens\" (existing behavior). This maintains backward compatibility with existing CSI drivers.\n\nThis field can only be set when TokenRequests is configured. The API server will reject CSIDriver specs that set this field without TokenRequests.\n\nDefault behavior if unset is to pass tokens in the VolumeContext field.", + "preventPodSchedulingIfMissing": "PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod scheduling if the CSI driver on the node is missing.\n\nEnabling this option will prevent the scheduler (or any other component which embeds default scheduler such as cluster-autoscaler) from scheduling pods to nodes where CSI driver is not installed.\n\nFor components(such as cluster-autoscaler) that embed the scheduler and run pod placement simulations using scheduler plugins, they MUST be aware of CSI driver registration information via CSINode object. They must create simulated CSINode objects in addition to Node objects during scheduling simulation, otherwise if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any newly created node may be rejected by the scheduler because of missing CSI driver information from the node.\n\nThis is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. Default is \"false\".", } func (CSIDriverSpec) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/storage/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/storage/v1beta1/zz_generated.deepcopy.go index 94cc629da649a..ca28e0340c5f7 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/storage/v1beta1/zz_generated.deepcopy.go @@ -142,6 +142,11 @@ func (in *CSIDriverSpec) DeepCopyInto(out *CSIDriverSpec) { *out = new(bool) **out = **in } + if in.PreventPodSchedulingIfMissing != nil { + in, out := &in.PreventPodSchedulingIfMissing, &out.PreventPodSchedulingIfMissing + *out = new(bool) + **out = **in + } return } diff --git a/staging/src/k8s.io/api/storagemigration/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/api/storagemigration/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 39cafd8a9e747..0000000000000 --- a/staging/src/k8s.io/api/storagemigration/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*StorageVersionMigration) ProtoMessage() {} - -func (*StorageVersionMigrationList) ProtoMessage() {} - -func (*StorageVersionMigrationSpec) ProtoMessage() {} - -func (*StorageVersionMigrationStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.json b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.json index c34bd5e439792..06d14bfefe570 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.json +++ b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.json @@ -80,7 +80,10 @@ "selfLink": "selfLinkValue", "resourceVersion": "resourceVersionValue", "continue": "continueValue", - "remainingItemCount": 4 + "remainingItemCount": 4, + "shardInfo": { + "selector": "selectorValue" + } }, "status": "statusValue", "message": "messageValue", diff --git a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.pb b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.pb index 73532668dba3d..dcedba8a7a7a3 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.pb and b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.yaml b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.yaml index c278ba71e73eb..ffa013b5490f5 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.yaml @@ -77,6 +77,8 @@ response: remainingItemCount: 4 resourceVersion: resourceVersionValue selfLink: selfLinkValue + shardInfo: + selector: selectorValue reason: reasonValue status: statusValue uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.json b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.json index ee068e50c6826..0a9590f4ad98e 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.json +++ b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.json @@ -80,7 +80,10 @@ "selfLink": "selfLinkValue", "resourceVersion": "resourceVersionValue", "continue": "continueValue", - "remainingItemCount": 4 + "remainingItemCount": 4, + "shardInfo": { + "selector": "selectorValue" + } }, "status": "statusValue", "message": "messageValue", diff --git a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.pb b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.pb index a5b36e718dd7c..0b7562c9af08c 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.pb and b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.yaml b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.yaml index d6c7bfd77b39a..6d59ef2d1ba6f 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1beta1.AdmissionReview.yaml @@ -77,6 +77,8 @@ response: remainingItemCount: 4 resourceVersion: resourceVersionValue selfLink: selfLinkValue + shardInfo: + selector: selectorValue reason: reasonValue status: statusValue uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.json b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.json new file mode 100644 index 0000000000000..d2e197e1f8702 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.json @@ -0,0 +1,148 @@ +{ + "kind": "MutatingAdmissionPolicy", + "apiVersion": "admissionregistration.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "paramKind": { + "apiVersion": "apiVersionValue", + "kind": "kindValue" + }, + "matchConstraints": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "variables": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "mutations": [ + { + "patchType": "patchTypeValue", + "applyConfiguration": { + "expression": "expressionValue" + }, + "jsonPatch": { + "expression": "expressionValue" + } + } + ], + "failurePolicy": "failurePolicyValue", + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "reinvocationPolicy": "reinvocationPolicyValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.pb b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.pb new file mode 100644 index 0000000000000..652aec03b03de Binary files /dev/null and b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.yaml b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.yaml new file mode 100644 index 0000000000000..cb2f5d21dcfcc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicy.yaml @@ -0,0 +1,94 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchConstraints: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + mutations: + - applyConfiguration: + expression: expressionValue + jsonPatch: + expression: expressionValue + patchType: patchTypeValue + paramKind: + apiVersion: apiVersionValue + kind: kindValue + reinvocationPolicy: reinvocationPolicyValue + variables: + - expression: expressionValue + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.json b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.json new file mode 100644 index 0000000000000..3b6f00923a28d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.json @@ -0,0 +1,139 @@ +{ + "kind": "MutatingAdmissionPolicyBinding", + "apiVersion": "admissionregistration.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "policyName": "policyNameValue", + "paramRef": { + "name": "nameValue", + "namespace": "namespaceValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "parameterNotFoundAction": "parameterNotFoundActionValue" + }, + "matchResources": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.pb b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.pb new file mode 100644 index 0000000000000..ce7ef517c3e61 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.yaml b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.yaml new file mode 100644 index 0000000000000..4f73500804961 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/admissionregistration.k8s.io.v1.MutatingAdmissionPolicyBinding.yaml @@ -0,0 +1,90 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingAdmissionPolicyBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + matchResources: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramRef: + name: nameValue + namespace: namespaceValue + parameterNotFoundAction: parameterNotFoundActionValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyName: policyNameValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.json index 48b204ffd730e..3b2f25995688f 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.json @@ -1847,10 +1847,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.pb index 86fae4aca19c7..f9fe67eb0f19a 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.yaml index e57a716d19c38..a1b2246797c2d 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.DaemonSet.yaml @@ -914,6 +914,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1269,10 +1271,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue updateStrategy: rollingUpdate: maxSurge: maxSurgeValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.json index 5168ada9be23f..11b08b24841ff 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.pb index bff40b2ef4e60..b3b253f448b6f 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.yaml index e730e6296ddb6..fb8012d2cefd6 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.Deployment.yaml @@ -922,6 +922,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1277,10 +1279,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 4 collisionCount: 8 diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.json index 77dbe6c40b1e2..7deae728eed5b 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.json @@ -1849,10 +1849,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } } diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.pb index b19b378ed0057..5389e7194af35 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.yaml index 7ba70c61ad092..466df3c668a43 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.ReplicaSet.yaml @@ -914,6 +914,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1269,10 +1271,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 5 conditions: diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.json index 66c5c1bbf8e2a..07bd137224cb9 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.pb index eaad0ed30100a..1d584a5ce4c16 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.yaml index 593d4a368d45e..cf3c5ece6de94 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1.StatefulSet.yaml @@ -922,6 +922,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1277,10 +1279,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue updateStrategy: rollingUpdate: maxUnavailable: maxUnavailableValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.json index 1a2a057e02f9e..f7a0c7cf0dee2 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.pb index 596d4ed64af91..7b270c7fa2537 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.yaml index d04c97bfc7c7a..b0be3d94da17e 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.Deployment.yaml @@ -924,6 +924,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1279,10 +1281,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 4 collisionCount: 8 diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.json index 064d41d805f09..7a8a85bc8ca6a 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.pb index 6975bae4a37e5..3bb0553568d2f 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.yaml index fb99d0e602cab..4d658891ef0e0 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta1.StatefulSet.yaml @@ -922,6 +922,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1277,10 +1279,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue updateStrategy: rollingUpdate: maxUnavailable: maxUnavailableValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.json index d6eee9ea6e9d0..5d67c185cf3a3 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.json @@ -1847,10 +1847,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.pb index d2fa55813e098..143a10cbd7ffe 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.yaml index 6c5827f42d10b..b79d05cccf0cb 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.DaemonSet.yaml @@ -914,6 +914,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1269,10 +1271,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue updateStrategy: rollingUpdate: maxSurge: maxSurgeValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.json index 62cd655717b2c..70d57ac98dd89 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.pb index bf681bb8d865e..ad45f955a3b7b 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.yaml index 9dc11ada486c0..9e56e44f88fc0 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.Deployment.yaml @@ -922,6 +922,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1277,10 +1279,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 4 collisionCount: 8 diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.json index d2e880c58f0d5..774221915d813 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.json @@ -1849,10 +1849,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } } diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.pb index 15275cd57a1eb..e5f630cebeba8 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.yaml index ec1574bc98b76..4058074efbddb 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.ReplicaSet.yaml @@ -914,6 +914,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1269,10 +1271,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 5 conditions: diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.json b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.json index 28a32b44dcd41..e03d667df0406 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.pb b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.pb index 2a7abed471fff..b477aec1e2691 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.yaml index 1a799675be072..5f2a435d6db69 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/apps.v1beta2.StatefulSet.yaml @@ -922,6 +922,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1277,10 +1279,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue updateStrategy: rollingUpdate: maxUnavailable: maxUnavailableValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json index 1f5b8df2388be..dfb0f08849bd9 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json @@ -1931,10 +1931,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.pb b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.pb index 775ebf95206f0..4c0637715a989 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.pb and b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml index b08dfe35206ff..04f7aab5cbdbe 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml @@ -974,6 +974,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1329,10 +1331,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue ttlSecondsAfterFinished: 8 schedule: scheduleValue startingDeadlineSeconds: 2 diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json index cae93ab95027e..9d576fe102cab 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json @@ -1882,10 +1882,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb index 8f767838b8817..8452949e9e810 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb and b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml index 3d25c120ee32f..bde4815ab3340 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml @@ -938,6 +938,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1293,10 +1295,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue ttlSecondsAfterFinished: 8 status: active: 4 diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json index 5436d0cf4e83d..be06de982cb55 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json @@ -1931,10 +1931,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.pb b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.pb index 6bfb4158e7f31..c52e60e5d2ae9 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.pb and b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml index 21d9f01c16bd4..67a66b4e97ad2 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml @@ -974,6 +974,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1329,10 +1331,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue ttlSecondsAfterFinished: 8 schedule: scheduleValue startingDeadlineSeconds: 2 diff --git a/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.json b/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.json index 93a2f04448c19..36e41223b9d9e 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.json +++ b/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.json @@ -54,6 +54,7 @@ "maxExpirationSeconds": 8, "pkixPublicKey": "CQ==", "proofOfPossession": "Cg==", + "stubPKCS10Request": "DA==", "unverifiedUserAnnotations": { "unverifiedUserAnnotationsKey": "unverifiedUserAnnotationsValue" } diff --git a/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.pb b/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.pb index 3b2e1ebfecdf8..c7dd6b1172be4 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.pb and b/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml b/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml index f8cec35717984..b0a2d9a303721 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml @@ -43,6 +43,7 @@ spec: serviceAccountName: serviceAccountNameValue serviceAccountUID: serviceAccountUIDValue signerName: signerNameValue + stubPKCS10Request: DA== unverifiedUserAnnotations: unverifiedUserAnnotationsKey: unverifiedUserAnnotationsValue status: diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.json b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.json index 066b25b45faeb..72f072a09d78b 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.json +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.json @@ -10,5 +10,6 @@ "timeoutSeconds": 5, "limit": 7, "continue": "continueValue", - "sendInitialEvents": true + "sendInitialEvents": true, + "shardSelector": "shardSelectorValue" } \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.pb b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.pb index ea28506449565..7327bafb71c84 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.pb and b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.yaml b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.yaml index 7dac63dd343a8..e40a5a24804b3 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ListOptions.yaml @@ -8,5 +8,6 @@ limit: 7 resourceVersion: resourceVersionValue resourceVersionMatch: resourceVersionMatchValue sendInitialEvents: true +shardSelector: shardSelectorValue timeoutSeconds: 5 watch: true diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.json b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.json index 8316bb1654163..9b21412d3979b 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.json +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.json @@ -1789,10 +1789,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } }, "status": { @@ -1892,7 +1890,12 @@ "name": "nameValue", "mountPath": "mountPathValue", "readOnly": true, - "recursiveReadOnly": "recursiveReadOnlyValue" + "recursiveReadOnly": "recursiveReadOnlyValue", + "volumeStatus": { + "image": { + "imageRef": "imageRefValue" + } + } } ], "user": { @@ -1910,7 +1913,8 @@ "resources": [ { "resourceID": "resourceIDValue", - "health": "healthValue" + "health": "healthValue", + "message": "messageValue" } ] } @@ -1985,7 +1989,12 @@ "name": "nameValue", "mountPath": "mountPathValue", "readOnly": true, - "recursiveReadOnly": "recursiveReadOnlyValue" + "recursiveReadOnly": "recursiveReadOnlyValue", + "volumeStatus": { + "image": { + "imageRef": "imageRefValue" + } + } } ], "user": { @@ -2003,7 +2012,8 @@ "resources": [ { "resourceID": "resourceIDValue", - "health": "healthValue" + "health": "healthValue", + "message": "messageValue" } ] } @@ -2079,7 +2089,12 @@ "name": "nameValue", "mountPath": "mountPathValue", "readOnly": true, - "recursiveReadOnly": "recursiveReadOnlyValue" + "recursiveReadOnly": "recursiveReadOnlyValue", + "volumeStatus": { + "image": { + "imageRef": "imageRefValue" + } + } } ], "user": { @@ -2097,7 +2112,8 @@ "resources": [ { "resourceID": "resourceIDValue", - "health": "healthValue" + "health": "healthValue", + "message": "messageValue" } ] } @@ -2138,6 +2154,17 @@ "request": "requestValue" } ] - } + }, + "nodeAllocatableResourceClaimStatuses": [ + { + "resourceClaimName": "resourceClaimNameValue", + "containers": [ + "containersValue" + ], + "resources": { + "resourcesKey": "0" + } + } + ] } } \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb index 5edf767085d42..9e570fd6e7a89 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb and b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.yaml b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.yaml index 1335003613c70..98eafd087e7dd 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.yaml @@ -870,6 +870,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1225,10 +1227,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: allocatedResources: allocatedResourcesKey: "0" @@ -1247,6 +1245,7 @@ status: - name: nameValue resources: - health: healthValue + message: messageValue resourceID: resourceIDValue containerID: containerIDValue image: imageValue @@ -1303,6 +1302,9 @@ status: name: nameValue readOnly: true recursiveReadOnly: recursiveReadOnlyValue + volumeStatus: + image: + imageRef: imageRefValue ephemeralContainerStatuses: - allocatedResources: allocatedResourcesKey: "0" @@ -1310,6 +1312,7 @@ status: - name: nameValue resources: - health: healthValue + message: messageValue resourceID: resourceIDValue containerID: containerIDValue image: imageValue @@ -1366,6 +1369,9 @@ status: name: nameValue readOnly: true recursiveReadOnly: recursiveReadOnlyValue + volumeStatus: + image: + imageRef: imageRefValue extendedResourceClaimStatus: requestMappings: - containerName: containerNameValue @@ -1382,6 +1388,7 @@ status: - name: nameValue resources: - health: healthValue + message: messageValue resourceID: resourceIDValue containerID: containerIDValue image: imageValue @@ -1438,7 +1445,16 @@ status: name: nameValue readOnly: true recursiveReadOnly: recursiveReadOnlyValue + volumeStatus: + image: + imageRef: imageRefValue message: messageValue + nodeAllocatableResourceClaimStatuses: + - containers: + - containersValue + resourceClaimName: resourceClaimNameValue + resources: + resourcesKey: "0" nominatedNodeName: nominatedNodeNameValue observedGeneration: 17 phase: phaseValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.json b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.json index eb59a472428c5..84113d84d4669 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.json +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.json @@ -140,7 +140,12 @@ "name": "nameValue", "mountPath": "mountPathValue", "readOnly": true, - "recursiveReadOnly": "recursiveReadOnlyValue" + "recursiveReadOnly": "recursiveReadOnlyValue", + "volumeStatus": { + "image": { + "imageRef": "imageRefValue" + } + } } ], "user": { @@ -158,7 +163,8 @@ "resources": [ { "resourceID": "resourceIDValue", - "health": "healthValue" + "health": "healthValue", + "message": "messageValue" } ] } @@ -233,7 +239,12 @@ "name": "nameValue", "mountPath": "mountPathValue", "readOnly": true, - "recursiveReadOnly": "recursiveReadOnlyValue" + "recursiveReadOnly": "recursiveReadOnlyValue", + "volumeStatus": { + "image": { + "imageRef": "imageRefValue" + } + } } ], "user": { @@ -251,7 +262,8 @@ "resources": [ { "resourceID": "resourceIDValue", - "health": "healthValue" + "health": "healthValue", + "message": "messageValue" } ] } @@ -327,7 +339,12 @@ "name": "nameValue", "mountPath": "mountPathValue", "readOnly": true, - "recursiveReadOnly": "recursiveReadOnlyValue" + "recursiveReadOnly": "recursiveReadOnlyValue", + "volumeStatus": { + "image": { + "imageRef": "imageRefValue" + } + } } ], "user": { @@ -345,7 +362,8 @@ "resources": [ { "resourceID": "resourceIDValue", - "health": "healthValue" + "health": "healthValue", + "message": "messageValue" } ] } @@ -386,6 +404,17 @@ "request": "requestValue" } ] - } + }, + "nodeAllocatableResourceClaimStatuses": [ + { + "resourceClaimName": "resourceClaimNameValue", + "containers": [ + "containersValue" + ], + "resources": { + "resourcesKey": "0" + } + } + ] } } \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.pb b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.pb index a06ad0fe01372..5b2c55c26faa9 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.pb and b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.yaml b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.yaml index 561d88be58d30..8d4de8bca0694 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodStatusResult.yaml @@ -50,6 +50,7 @@ status: - name: nameValue resources: - health: healthValue + message: messageValue resourceID: resourceIDValue containerID: containerIDValue image: imageValue @@ -106,6 +107,9 @@ status: name: nameValue readOnly: true recursiveReadOnly: recursiveReadOnlyValue + volumeStatus: + image: + imageRef: imageRefValue ephemeralContainerStatuses: - allocatedResources: allocatedResourcesKey: "0" @@ -113,6 +117,7 @@ status: - name: nameValue resources: - health: healthValue + message: messageValue resourceID: resourceIDValue containerID: containerIDValue image: imageValue @@ -169,6 +174,9 @@ status: name: nameValue readOnly: true recursiveReadOnly: recursiveReadOnlyValue + volumeStatus: + image: + imageRef: imageRefValue extendedResourceClaimStatus: requestMappings: - containerName: containerNameValue @@ -185,6 +193,7 @@ status: - name: nameValue resources: - health: healthValue + message: messageValue resourceID: resourceIDValue containerID: containerIDValue image: imageValue @@ -241,7 +250,16 @@ status: name: nameValue readOnly: true recursiveReadOnly: recursiveReadOnlyValue + volumeStatus: + image: + imageRef: imageRefValue message: messageValue + nodeAllocatableResourceClaimStatuses: + - containers: + - containersValue + resourceClaimName: resourceClaimNameValue + resources: + resourcesKey: "0" nominatedNodeName: nominatedNodeNameValue observedGeneration: 17 phase: phaseValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.json b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.json index 52e8ad2c7d6d6..8c3c67632e55f 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.json +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.json @@ -1832,10 +1832,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } } diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.pb b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.pb index c8a6030194004..b88e8737313ec 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.pb and b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.yaml b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.yaml index 4080ec597c8b8..0b3f2c3ff18c4 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.PodTemplate.yaml @@ -903,6 +903,8 @@ template: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1258,7 +1260,3 @@ template: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.json b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.json index f4db5d62148a6..d734c1eb69a78 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.json +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.json @@ -1838,10 +1838,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } } diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.pb b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.pb index 799572d89be5d..a5a45ae7e0335 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.pb and b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.yaml b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.yaml index 7bee55311ba73..672abd6b0930e 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.ReplicationController.yaml @@ -908,6 +908,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1263,10 +1265,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 5 conditions: diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.json b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.json index 075b2b50704a7..c9ea171167a24 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.json +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.json @@ -5,7 +5,10 @@ "selfLink": "selfLinkValue", "resourceVersion": "resourceVersionValue", "continue": "continueValue", - "remainingItemCount": 4 + "remainingItemCount": 4, + "shardInfo": { + "selector": "selectorValue" + } }, "status": "statusValue", "message": "messageValue", diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.pb b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.pb index a9e1126abcbd2..1ba2a784933af 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.pb and b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.yaml b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.yaml index 6fa05d9a6d3bf..a852d2961e5f9 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/core.v1.Status.yaml @@ -17,5 +17,7 @@ metadata: remainingItemCount: 4 resourceVersion: resourceVersionValue selfLink: selfLinkValue + shardInfo: + selector: selectorValue reason: reasonValue status: statusValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.json b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.json index dbde12274e6af..ba6df7bcb43b1 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.json @@ -1847,10 +1847,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.pb b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.pb index 161b76999178e..cd23a55cbe0cf 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.yaml index 6512376d9748c..af50c7a5f80f4 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.DaemonSet.yaml @@ -914,6 +914,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1269,10 +1271,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue templateGeneration: 5 updateStrategy: rollingUpdate: diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.json b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.json index be0cd57a8cf09..61b8ba0bed493 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.json +++ b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.json @@ -1848,10 +1848,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } }, diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.pb b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.pb index c2c612c2efb0e..353ee9ae5bb02 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.pb and b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.yaml b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.yaml index b47e20398aebb..cf1b07f625001 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.Deployment.yaml @@ -924,6 +924,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1279,10 +1281,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 4 collisionCount: 8 diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.json b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.json index 760674be7cd34..400f0e7fdfd88 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.json +++ b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.json @@ -1849,10 +1849,8 @@ ] }, "hostnameOverride": "hostnameOverrideValue", - "workloadRef": { - "name": "nameValue", - "podGroup": "podGroupValue", - "podGroupReplicaKey": "podGroupReplicaKeyValue" + "schedulingGroup": { + "podGroupName": "podGroupNameValue" } } } diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.pb b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.pb index b61e355108b7b..55d1bfac1a56e 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.pb and b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.yaml b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.yaml index d983d21a3fbb1..e1ad58c8f5811 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/extensions.v1beta1.ReplicaSet.yaml @@ -914,6 +914,8 @@ spec: schedulerName: schedulerNameValue schedulingGates: - name: nameValue + schedulingGroup: + podGroupName: podGroupNameValue securityContext: appArmorProfile: localhostProfile: localhostProfileValue @@ -1269,10 +1271,6 @@ spec: storagePolicyID: storagePolicyIDValue storagePolicyName: storagePolicyNameValue volumePath: volumePathValue - workloadRef: - name: nameValue - podGroup: podGroupValue - podGroupReplicaKey: podGroupReplicaKeyValue status: availableReplicas: 5 conditions: diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.json index 71a2a34646581..71863f549718c 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.json @@ -84,7 +84,19 @@ "int": 2, "bool": true, "string": "stringValue", - "version": "versionValue" + "version": "versionValue", + "ints": [ + 6 + ], + "bools": [ + true + ], + "strings": [ + "stringsValue" + ], + "versions": [ + "versionsValue" + ] } }, "capacity": { @@ -154,7 +166,13 @@ "bindingFailureConditions": [ "bindingFailureConditionsValue" ], - "allowMultipleAllocations": true + "allowMultipleAllocations": true, + "nodeAllocatableResourceMappings": { + "nodeAllocatableResourceMappingsKey": { + "capacityKey": "capacityKeyValue", + "allocationMultiplier": "0" + } + } } ], "perDeviceNodeSelection": true, diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.pb index 3dcb6b97c1e2c..18b65a0a1e589 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.yaml index f39b9ce2a3055..1b044c8c4beec 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1.ResourceSlice.yaml @@ -40,9 +40,17 @@ spec: attributes: attributesKey: bool: true + bools: + - true int: 2 + ints: + - 6 string: stringValue + strings: + - stringsValue version: versionValue + versions: + - versionsValue bindingConditions: - bindingConditionsValue bindingFailureConditions: @@ -65,6 +73,10 @@ spec: countersKey: value: "0" name: nameValue + nodeAllocatableResourceMappings: + nodeAllocatableResourceMappingsKey: + allocationMultiplier: "0" + capacityKey: capacityKeyValue nodeName: nodeNameValue nodeSelector: nodeSelectorTerms: diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.json new file mode 100644 index 0000000000000..973edc17bcf55 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.json @@ -0,0 +1,78 @@ +{ + "kind": "ResourcePoolStatusRequest", + "apiVersion": "resource.k8s.io/v1alpha3", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "driver": "driverValue", + "poolName": "poolNameValue", + "limit": 3 + }, + "status": { + "poolCount": 6, + "pools": [ + { + "driver": "driverValue", + "poolName": "poolNameValue", + "generation": 9, + "resourceSliceCount": 8, + "totalDevices": 4, + "allocatedDevices": 5, + "availableDevices": 6, + "unavailableDevices": 7, + "nodeName": "nodeNameValue", + "validationError": "validationErrorValue" + } + ], + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.pb new file mode 100644 index 0000000000000..58e3313746b43 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.yaml new file mode 100644 index 0000000000000..abfaacbf50486 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourcePoolStatusRequest.yaml @@ -0,0 +1,58 @@ +apiVersion: resource.k8s.io/v1alpha3 +kind: ResourcePoolStatusRequest +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + driver: driverValue + limit: 3 + poolName: poolNameValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + poolCount: 6 + pools: + - allocatedDevices: 5 + availableDevices: 6 + driver: driverValue + generation: 9 + nodeName: nodeNameValue + poolName: poolNameValue + resourceSliceCount: 8 + totalDevices: 4 + unavailableDevices: 7 + validationError: validationErrorValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.json index eb2879fa0a4d2..4ba36bcd1b3e7 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.json @@ -85,7 +85,19 @@ "int": 2, "bool": true, "string": "stringValue", - "version": "versionValue" + "version": "versionValue", + "ints": [ + 6 + ], + "bools": [ + true + ], + "strings": [ + "stringsValue" + ], + "versions": [ + "versionsValue" + ] } }, "capacity": { @@ -155,7 +167,13 @@ "bindingFailureConditions": [ "bindingFailureConditionsValue" ], - "allowMultipleAllocations": true + "allowMultipleAllocations": true, + "nodeAllocatableResourceMappings": { + "nodeAllocatableResourceMappingsKey": { + "capacityKey": "capacityKeyValue", + "allocationMultiplier": "0" + } + } } } ], diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.pb index 248a5d59e7555..44795b5f30b58 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.yaml index dea39fec6203a..d6db4cf787c98 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceSlice.yaml @@ -41,9 +41,17 @@ spec: attributes: attributesKey: bool: true + bools: + - true int: 2 + ints: + - 6 string: stringValue + strings: + - stringsValue version: versionValue + versions: + - versionsValue bindingConditions: - bindingConditionsValue bindingFailureConditions: @@ -65,6 +73,10 @@ spec: counters: countersKey: value: "0" + nodeAllocatableResourceMappings: + nodeAllocatableResourceMappingsKey: + allocationMultiplier: "0" + capacityKey: capacityKeyValue nodeName: nodeNameValue nodeSelector: nodeSelectorTerms: diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.json new file mode 100644 index 0000000000000..98fce16c04327 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.json @@ -0,0 +1,71 @@ +{ + "kind": "DeviceTaintRule", + "apiVersion": "resource.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "deviceSelector": { + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue" + }, + "taint": { + "key": "keyValue", + "value": "valueValue", + "effect": "effectValue", + "timeAdded": "2004-01-01T01:01:01Z" + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.pb new file mode 100644 index 0000000000000..491f2596a1f62 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.yaml new file mode 100644 index 0000000000000..118727a09332a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.DeviceTaintRule.yaml @@ -0,0 +1,52 @@ +apiVersion: resource.k8s.io/v1beta2 +kind: DeviceTaintRule +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + deviceSelector: + device: deviceValue + driver: driverValue + pool: poolValue + taint: + effect: effectValue + key: keyValue + timeAdded: "2004-01-01T01:01:01Z" + value: valueValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.json index 4d80e7cfe726a..59c0446a7a8d3 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.json @@ -84,7 +84,19 @@ "int": 2, "bool": true, "string": "stringValue", - "version": "versionValue" + "version": "versionValue", + "ints": [ + 6 + ], + "bools": [ + true + ], + "strings": [ + "stringsValue" + ], + "versions": [ + "versionsValue" + ] } }, "capacity": { @@ -154,7 +166,13 @@ "bindingFailureConditions": [ "bindingFailureConditionsValue" ], - "allowMultipleAllocations": true + "allowMultipleAllocations": true, + "nodeAllocatableResourceMappings": { + "nodeAllocatableResourceMappingsKey": { + "capacityKey": "capacityKeyValue", + "allocationMultiplier": "0" + } + } } ], "perDeviceNodeSelection": true, diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.pb index a81ae25142726..c0777d17c9ad7 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.yaml index 73881c40d3cb4..30c2efac64e4c 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta2.ResourceSlice.yaml @@ -40,9 +40,17 @@ spec: attributes: attributesKey: bool: true + bools: + - true int: 2 + ints: + - 6 string: stringValue + strings: + - stringsValue version: versionValue + versions: + - versionsValue bindingConditions: - bindingConditionsValue bindingFailureConditions: @@ -65,6 +73,10 @@ spec: countersKey: value: "0" name: nameValue + nodeAllocatableResourceMappings: + nodeAllocatableResourceMappingsKey: + allocationMultiplier: "0" + capacityKey: capacityKeyValue nodeName: nodeNameValue nodeSelector: nodeSelectorTerms: diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.json b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.json new file mode 100644 index 0000000000000..bab4a230a3a30 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.json @@ -0,0 +1,95 @@ +{ + "kind": "PodGroup", + "apiVersion": "scheduling.k8s.io/v1alpha2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "podGroupTemplateRef": { + "workload": { + "workloadName": "workloadNameValue", + "podGroupTemplateName": "podGroupTemplateNameValue" + } + }, + "schedulingPolicy": { + "basic": {}, + "gang": { + "minCount": 1 + } + }, + "schedulingConstraints": { + "topology": [ + { + "key": "keyValue" + } + ] + }, + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "disruptionMode": "disruptionModeValue", + "priorityClassName": "priorityClassNameValue", + "priority": 7 + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "resourceClaimStatuses": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.pb b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.pb new file mode 100644 index 0000000000000..4813dba502602 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.yaml b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.yaml new file mode 100644 index 0000000000000..169b493f20b11 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.PodGroup.yaml @@ -0,0 +1,64 @@ +apiVersion: scheduling.k8s.io/v1alpha2 +kind: PodGroup +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + disruptionMode: disruptionModeValue + podGroupTemplateRef: + workload: + podGroupTemplateName: podGroupTemplateNameValue + workloadName: workloadNameValue + priority: 7 + priorityClassName: priorityClassNameValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + schedulingConstraints: + topology: + - key: keyValue + schedulingPolicy: + basic: {} + gang: + minCount: 1 +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + resourceClaimStatuses: + - name: nameValue + resourceClaimName: resourceClaimNameValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.json b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.json new file mode 100644 index 0000000000000..672365d0ad89a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.json @@ -0,0 +1,81 @@ +{ + "kind": "Workload", + "apiVersion": "scheduling.k8s.io/v1alpha2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "controllerRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "podGroupTemplates": [ + { + "name": "nameValue", + "schedulingPolicy": { + "basic": {}, + "gang": { + "minCount": 1 + } + }, + "schedulingConstraints": { + "topology": [ + { + "key": "keyValue" + } + ] + }, + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "disruptionMode": "disruptionModeValue", + "priorityClassName": "priorityClassNameValue", + "priority": 7 + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.pb b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.pb new file mode 100644 index 0000000000000..d26486caaa9c5 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.yaml b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.yaml new file mode 100644 index 0000000000000..b145b727bf7e7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha2.Workload.yaml @@ -0,0 +1,55 @@ +apiVersion: scheduling.k8s.io/v1alpha2 +kind: Workload +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + controllerRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + podGroupTemplates: + - disruptionMode: disruptionModeValue + name: nameValue + priority: 7 + priorityClassName: priorityClassNameValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + schedulingConstraints: + topology: + - key: keyValue + schedulingPolicy: + basic: {} + gang: + minCount: 1 diff --git a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.json b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.json index addc685f37eb0..c7178a3ab0da4 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.json +++ b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.json @@ -60,6 +60,7 @@ "requiresRepublish": true, "seLinuxMount": true, "nodeAllocatableUpdatePeriodSeconds": 9, - "serviceAccountTokenInSecrets": true + "serviceAccountTokenInSecrets": true, + "preventPodSchedulingIfMissing": true } } \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.pb b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.pb index c5e60ad070b16..b45b9d60b7dba 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.pb and b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.yaml b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.yaml index 9ede049a06fec..085c4d6711e89 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1.CSIDriver.yaml @@ -37,6 +37,7 @@ spec: fsGroupPolicy: fsGroupPolicyValue nodeAllocatableUpdatePeriodSeconds: 9 podInfoOnMount: true + preventPodSchedulingIfMissing: true requiresRepublish: true seLinuxMount: true serviceAccountTokenInSecrets: true diff --git a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.json b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.json index 687d508ad41e4..955833354f562 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.json +++ b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.json @@ -60,6 +60,7 @@ "requiresRepublish": true, "seLinuxMount": true, "nodeAllocatableUpdatePeriodSeconds": 9, - "serviceAccountTokenInSecrets": true + "serviceAccountTokenInSecrets": true, + "preventPodSchedulingIfMissing": true } } \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.pb b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.pb index 7976348b86bcb..caec447fe4d25 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.pb and b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.yaml b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.yaml index 7eb84a3419203..c5e1ac293a804 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/storage.k8s.io.v1beta1.CSIDriver.yaml @@ -37,6 +37,7 @@ spec: fsGroupPolicy: fsGroupPolicyValue nodeAllocatableUpdatePeriodSeconds: 9 podInfoOnMount: true + preventPodSchedulingIfMissing: true requiresRepublish: true seLinuxMount: true serviceAccountTokenInSecrets: true diff --git a/staging/src/k8s.io/api/testdata/README.md b/staging/src/k8s.io/api/testdata/README.md index 8989ed7ca9c56..549408faeec4e 100644 --- a/staging/src/k8s.io/api/testdata/README.md +++ b/staging/src/k8s.io/api/testdata/README.md @@ -17,6 +17,11 @@ git add . git commit -m "Add ${VERSION} API testdata" ``` +You can then test with: +```sh +go test -count 1 k8s.io/api +``` + ## Current version The `HEAD` subdirectory contains serialized API objects generated from the current commit: diff --git a/staging/src/k8s.io/api/testdata/v1.33.0/core.v1.ListOptions.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.33.0/core.v1.ListOptions.after_roundtrip.pb new file mode 100644 index 0000000000000..376328ccf8e76 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.33.0/core.v1.ListOptions.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.34.0/core.v1.ListOptions.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.34.0/core.v1.ListOptions.after_roundtrip.pb new file mode 100644 index 0000000000000..376328ccf8e76 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.34.0/core.v1.ListOptions.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.json new file mode 100644 index 0000000000000..c34bd5e439792 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.json @@ -0,0 +1,113 @@ +{ + "kind": "AdmissionReview", + "apiVersion": "admission.k8s.io/v1", + "request": { + "uid": "uidValue", + "kind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "resource": { + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue" + }, + "subResource": "subResourceValue", + "requestKind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "requestResource": { + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue" + }, + "requestSubResource": "requestSubResourceValue", + "name": "nameValue", + "namespace": "namespaceValue", + "operation": "operationValue", + "userInfo": { + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + }, + "object": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "oldObject": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "dryRun": true, + "options": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + }, + "response": { + "uid": "uidValue", + "allowed": true, + "status": { + "metadata": { + "selfLink": "selfLinkValue", + "resourceVersion": "resourceVersionValue", + "continue": "continueValue", + "remainingItemCount": 4 + }, + "status": "statusValue", + "message": "messageValue", + "reason": "reasonValue", + "details": { + "name": "nameValue", + "group": "groupValue", + "kind": "kindValue", + "uid": "uidValue", + "causes": [ + { + "reason": "reasonValue", + "message": "messageValue", + "field": "fieldValue" + } + ], + "retryAfterSeconds": 5 + }, + "code": 6 + }, + "patch": "BA==", + "patchType": "patchTypeValue", + "auditAnnotations": { + "auditAnnotationsKey": "auditAnnotationsValue" + }, + "warnings": [ + "warningsValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.pb new file mode 100644 index 0000000000000..73532668dba3d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.yaml new file mode 100644 index 0000000000000..c278ba71e73eb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1.AdmissionReview.yaml @@ -0,0 +1,84 @@ +apiVersion: admission.k8s.io/v1 +kind: AdmissionReview +request: + dryRun: true + kind: + group: groupValue + kind: kindValue + version: versionValue + name: nameValue + namespace: namespaceValue + object: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + oldObject: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + operation: operationValue + options: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requestKind: + group: groupValue + kind: kindValue + version: versionValue + requestResource: + group: groupValue + resource: resourceValue + version: versionValue + requestSubResource: requestSubResourceValue + resource: + group: groupValue + resource: resourceValue + version: versionValue + subResource: subResourceValue + uid: uidValue + userInfo: + extra: + extraKey: + - extraValue + groups: + - groupsValue + uid: uidValue + username: usernameValue +response: + allowed: true + auditAnnotations: + auditAnnotationsKey: auditAnnotationsValue + patch: BA== + patchType: patchTypeValue + status: + code: 6 + details: + causes: + - field: fieldValue + message: messageValue + reason: reasonValue + group: groupValue + kind: kindValue + name: nameValue + retryAfterSeconds: 5 + uid: uidValue + message: messageValue + metadata: + continue: continueValue + remainingItemCount: 4 + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + reason: reasonValue + status: statusValue + uid: uidValue + warnings: + - warningsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.json new file mode 100644 index 0000000000000..ee068e50c6826 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.json @@ -0,0 +1,113 @@ +{ + "kind": "AdmissionReview", + "apiVersion": "admission.k8s.io/v1beta1", + "request": { + "uid": "uidValue", + "kind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "resource": { + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue" + }, + "subResource": "subResourceValue", + "requestKind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "requestResource": { + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue" + }, + "requestSubResource": "requestSubResourceValue", + "name": "nameValue", + "namespace": "namespaceValue", + "operation": "operationValue", + "userInfo": { + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + }, + "object": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "oldObject": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "dryRun": true, + "options": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + }, + "response": { + "uid": "uidValue", + "allowed": true, + "status": { + "metadata": { + "selfLink": "selfLinkValue", + "resourceVersion": "resourceVersionValue", + "continue": "continueValue", + "remainingItemCount": 4 + }, + "status": "statusValue", + "message": "messageValue", + "reason": "reasonValue", + "details": { + "name": "nameValue", + "group": "groupValue", + "kind": "kindValue", + "uid": "uidValue", + "causes": [ + { + "reason": "reasonValue", + "message": "messageValue", + "field": "fieldValue" + } + ], + "retryAfterSeconds": 5 + }, + "code": 6 + }, + "patch": "BA==", + "patchType": "patchTypeValue", + "auditAnnotations": { + "auditAnnotationsKey": "auditAnnotationsValue" + }, + "warnings": [ + "warningsValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.pb new file mode 100644 index 0000000000000..a5b36e718dd7c Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.yaml new file mode 100644 index 0000000000000..d6c7bfd77b39a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admission.k8s.io.v1beta1.AdmissionReview.yaml @@ -0,0 +1,84 @@ +apiVersion: admission.k8s.io/v1beta1 +kind: AdmissionReview +request: + dryRun: true + kind: + group: groupValue + kind: kindValue + version: versionValue + name: nameValue + namespace: namespaceValue + object: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + oldObject: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + operation: operationValue + options: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requestKind: + group: groupValue + kind: kindValue + version: versionValue + requestResource: + group: groupValue + resource: resourceValue + version: versionValue + requestSubResource: requestSubResourceValue + resource: + group: groupValue + resource: resourceValue + version: versionValue + subResource: subResourceValue + uid: uidValue + userInfo: + extra: + extraKey: + - extraValue + groups: + - groupsValue + uid: uidValue + username: usernameValue +response: + allowed: true + auditAnnotations: + auditAnnotationsKey: auditAnnotationsValue + patch: BA== + patchType: patchTypeValue + status: + code: 6 + details: + causes: + - field: fieldValue + message: messageValue + reason: reasonValue + group: groupValue + kind: kindValue + name: nameValue + retryAfterSeconds: 5 + uid: uidValue + message: messageValue + metadata: + continue: continueValue + remainingItemCount: 4 + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + reason: reasonValue + status: statusValue + uid: uidValue + warnings: + - warningsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.json new file mode 100644 index 0000000000000..520763fcd4733 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.json @@ -0,0 +1,120 @@ +{ + "kind": "MutatingWebhookConfiguration", + "apiVersion": "admissionregistration.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "webhooks": [ + { + "name": "nameValue", + "clientConfig": { + "url": "urlValue", + "service": { + "namespace": "namespaceValue", + "name": "nameValue", + "path": "pathValue", + "port": 4 + }, + "caBundle": "Ag==" + }, + "rules": [ + { + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "failurePolicy": "failurePolicyValue", + "matchPolicy": "matchPolicyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "sideEffects": "sideEffectsValue", + "timeoutSeconds": 7, + "admissionReviewVersions": [ + "admissionReviewVersionsValue" + ], + "reinvocationPolicy": "reinvocationPolicyValue", + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.pb new file mode 100644 index 0000000000000..d9cd866cf19ca Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.yaml new file mode 100644 index 0000000000000..34b84f217914d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.MutatingWebhookConfiguration.yaml @@ -0,0 +1,80 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +webhooks: +- admissionReviewVersions: + - admissionReviewVersionsValue + clientConfig: + caBundle: Ag== + service: + name: nameValue + namespace: namespaceValue + path: pathValue + port: 4 + url: urlValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchPolicy: matchPolicyValue + name: nameValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + reinvocationPolicy: reinvocationPolicyValue + rules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resources: + - resourcesValue + scope: scopeValue + sideEffects: sideEffectsValue + timeoutSeconds: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.json new file mode 100644 index 0000000000000..f5e514c9066c6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.json @@ -0,0 +1,171 @@ +{ + "kind": "ValidatingAdmissionPolicy", + "apiVersion": "admissionregistration.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "paramKind": { + "apiVersion": "apiVersionValue", + "kind": "kindValue" + }, + "matchConstraints": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "validations": [ + { + "expression": "expressionValue", + "message": "messageValue", + "reason": "reasonValue", + "messageExpression": "messageExpressionValue" + } + ], + "failurePolicy": "failurePolicyValue", + "auditAnnotations": [ + { + "key": "keyValue", + "valueExpression": "valueExpressionValue" + } + ], + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "variables": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + }, + "status": { + "observedGeneration": 1, + "typeChecking": { + "expressionWarnings": [ + { + "fieldRef": "fieldRefValue", + "warning": "warningValue" + } + ] + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.pb new file mode 100644 index 0000000000000..0c5cc4d84d517 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.yaml new file mode 100644 index 0000000000000..a7fd3ece57023 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicy.yaml @@ -0,0 +1,108 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + auditAnnotations: + - key: keyValue + valueExpression: valueExpressionValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchConstraints: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramKind: + apiVersion: apiVersionValue + kind: kindValue + validations: + - expression: expressionValue + message: messageValue + messageExpression: messageExpressionValue + reason: reasonValue + variables: + - expression: expressionValue + name: nameValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + typeChecking: + expressionWarnings: + - fieldRef: fieldRefValue + warning: warningValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.json new file mode 100644 index 0000000000000..f49ef8a364289 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.json @@ -0,0 +1,142 @@ +{ + "kind": "ValidatingAdmissionPolicyBinding", + "apiVersion": "admissionregistration.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "policyName": "policyNameValue", + "paramRef": { + "name": "nameValue", + "namespace": "namespaceValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "parameterNotFoundAction": "parameterNotFoundActionValue" + }, + "matchResources": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "validationActions": [ + "validationActionsValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.pb new file mode 100644 index 0000000000000..fde6678f201f8 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.yaml new file mode 100644 index 0000000000000..9cd5310554640 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingAdmissionPolicyBinding.yaml @@ -0,0 +1,92 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + matchResources: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramRef: + name: nameValue + namespace: namespaceValue + parameterNotFoundAction: parameterNotFoundActionValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyName: policyNameValue + validationActions: + - validationActionsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.json new file mode 100644 index 0000000000000..a61187e4493f0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.json @@ -0,0 +1,119 @@ +{ + "kind": "ValidatingWebhookConfiguration", + "apiVersion": "admissionregistration.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "webhooks": [ + { + "name": "nameValue", + "clientConfig": { + "url": "urlValue", + "service": { + "namespace": "namespaceValue", + "name": "nameValue", + "path": "pathValue", + "port": 4 + }, + "caBundle": "Ag==" + }, + "rules": [ + { + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "failurePolicy": "failurePolicyValue", + "matchPolicy": "matchPolicyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "sideEffects": "sideEffectsValue", + "timeoutSeconds": 7, + "admissionReviewVersions": [ + "admissionReviewVersionsValue" + ], + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.pb new file mode 100644 index 0000000000000..6ece9204a7d5a Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.yaml new file mode 100644 index 0000000000000..58da8319171c7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1.ValidatingWebhookConfiguration.yaml @@ -0,0 +1,79 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +webhooks: +- admissionReviewVersions: + - admissionReviewVersionsValue + clientConfig: + caBundle: Ag== + service: + name: nameValue + namespace: namespaceValue + path: pathValue + port: 4 + url: urlValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchPolicy: matchPolicyValue + name: nameValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + rules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resources: + - resourcesValue + scope: scopeValue + sideEffects: sideEffectsValue + timeoutSeconds: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.json new file mode 100644 index 0000000000000..985735ab110f8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.json @@ -0,0 +1,148 @@ +{ + "kind": "MutatingAdmissionPolicy", + "apiVersion": "admissionregistration.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "paramKind": { + "apiVersion": "apiVersionValue", + "kind": "kindValue" + }, + "matchConstraints": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "variables": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "mutations": [ + { + "patchType": "patchTypeValue", + "applyConfiguration": { + "expression": "expressionValue" + }, + "jsonPatch": { + "expression": "expressionValue" + } + } + ], + "failurePolicy": "failurePolicyValue", + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "reinvocationPolicy": "reinvocationPolicyValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.pb new file mode 100644 index 0000000000000..fb9d898bc43fa Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.yaml new file mode 100644 index 0000000000000..3de9686c34e1d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicy.yaml @@ -0,0 +1,94 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: MutatingAdmissionPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchConstraints: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + mutations: + - applyConfiguration: + expression: expressionValue + jsonPatch: + expression: expressionValue + patchType: patchTypeValue + paramKind: + apiVersion: apiVersionValue + kind: kindValue + reinvocationPolicy: reinvocationPolicyValue + variables: + - expression: expressionValue + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.json new file mode 100644 index 0000000000000..f14b6dc6d6b35 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.json @@ -0,0 +1,139 @@ +{ + "kind": "MutatingAdmissionPolicyBinding", + "apiVersion": "admissionregistration.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "policyName": "policyNameValue", + "paramRef": { + "name": "nameValue", + "namespace": "namespaceValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "parameterNotFoundAction": "parameterNotFoundActionValue" + }, + "matchResources": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.pb new file mode 100644 index 0000000000000..aca4185bffff3 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.yaml new file mode 100644 index 0000000000000..251870e8e674e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.MutatingAdmissionPolicyBinding.yaml @@ -0,0 +1,90 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: MutatingAdmissionPolicyBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + matchResources: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramRef: + name: nameValue + namespace: namespaceValue + parameterNotFoundAction: parameterNotFoundActionValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyName: policyNameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.json new file mode 100644 index 0000000000000..d8160896548f9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.json @@ -0,0 +1,171 @@ +{ + "kind": "ValidatingAdmissionPolicy", + "apiVersion": "admissionregistration.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "paramKind": { + "apiVersion": "apiVersionValue", + "kind": "kindValue" + }, + "matchConstraints": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "validations": [ + { + "expression": "expressionValue", + "message": "messageValue", + "reason": "reasonValue", + "messageExpression": "messageExpressionValue" + } + ], + "failurePolicy": "failurePolicyValue", + "auditAnnotations": [ + { + "key": "keyValue", + "valueExpression": "valueExpressionValue" + } + ], + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "variables": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + }, + "status": { + "observedGeneration": 1, + "typeChecking": { + "expressionWarnings": [ + { + "fieldRef": "fieldRefValue", + "warning": "warningValue" + } + ] + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.pb new file mode 100644 index 0000000000000..cd92e73bd3900 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.yaml new file mode 100644 index 0000000000000..8ca6b38bc5f51 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicy.yaml @@ -0,0 +1,108 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + auditAnnotations: + - key: keyValue + valueExpression: valueExpressionValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchConstraints: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramKind: + apiVersion: apiVersionValue + kind: kindValue + validations: + - expression: expressionValue + message: messageValue + messageExpression: messageExpressionValue + reason: reasonValue + variables: + - expression: expressionValue + name: nameValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + typeChecking: + expressionWarnings: + - fieldRef: fieldRefValue + warning: warningValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.json new file mode 100644 index 0000000000000..65c73c03948da --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.json @@ -0,0 +1,142 @@ +{ + "kind": "ValidatingAdmissionPolicyBinding", + "apiVersion": "admissionregistration.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "policyName": "policyNameValue", + "paramRef": { + "name": "nameValue", + "namespace": "namespaceValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "parameterNotFoundAction": "parameterNotFoundActionValue" + }, + "matchResources": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "validationActions": [ + "validationActionsValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.pb new file mode 100644 index 0000000000000..222018318bd57 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.yaml new file mode 100644 index 0000000000000..ff44f4d2e2449 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1alpha1.ValidatingAdmissionPolicyBinding.yaml @@ -0,0 +1,92 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicyBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + matchResources: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramRef: + name: nameValue + namespace: namespaceValue + parameterNotFoundAction: parameterNotFoundActionValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyName: policyNameValue + validationActions: + - validationActionsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.json new file mode 100644 index 0000000000000..937fa114f5ffa --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.json @@ -0,0 +1,148 @@ +{ + "kind": "MutatingAdmissionPolicy", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "paramKind": { + "apiVersion": "apiVersionValue", + "kind": "kindValue" + }, + "matchConstraints": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "variables": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "mutations": [ + { + "patchType": "patchTypeValue", + "applyConfiguration": { + "expression": "expressionValue" + }, + "jsonPatch": { + "expression": "expressionValue" + } + } + ], + "failurePolicy": "failurePolicyValue", + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "reinvocationPolicy": "reinvocationPolicyValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.pb new file mode 100644 index 0000000000000..34793ae99580c Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.yaml new file mode 100644 index 0000000000000..c32d9a29a5f11 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicy.yaml @@ -0,0 +1,94 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchConstraints: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + mutations: + - applyConfiguration: + expression: expressionValue + jsonPatch: + expression: expressionValue + patchType: patchTypeValue + paramKind: + apiVersion: apiVersionValue + kind: kindValue + reinvocationPolicy: reinvocationPolicyValue + variables: + - expression: expressionValue + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.json new file mode 100644 index 0000000000000..70fb8eb402dcd --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.json @@ -0,0 +1,139 @@ +{ + "kind": "MutatingAdmissionPolicyBinding", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "policyName": "policyNameValue", + "paramRef": { + "name": "nameValue", + "namespace": "namespaceValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "parameterNotFoundAction": "parameterNotFoundActionValue" + }, + "matchResources": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.pb new file mode 100644 index 0000000000000..2297f5ce744dd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.yaml new file mode 100644 index 0000000000000..e14c99958938b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingAdmissionPolicyBinding.yaml @@ -0,0 +1,90 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicyBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + matchResources: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramRef: + name: nameValue + namespace: namespaceValue + parameterNotFoundAction: parameterNotFoundActionValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyName: policyNameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.json new file mode 100644 index 0000000000000..40e3405e02ae9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.json @@ -0,0 +1,120 @@ +{ + "kind": "MutatingWebhookConfiguration", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "webhooks": [ + { + "name": "nameValue", + "clientConfig": { + "url": "urlValue", + "service": { + "namespace": "namespaceValue", + "name": "nameValue", + "path": "pathValue", + "port": 4 + }, + "caBundle": "Ag==" + }, + "rules": [ + { + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "failurePolicy": "failurePolicyValue", + "matchPolicy": "matchPolicyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "sideEffects": "sideEffectsValue", + "timeoutSeconds": 7, + "admissionReviewVersions": [ + "admissionReviewVersionsValue" + ], + "reinvocationPolicy": "reinvocationPolicyValue", + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.pb new file mode 100644 index 0000000000000..65940c58ae109 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.yaml new file mode 100644 index 0000000000000..14fea5b332217 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.MutatingWebhookConfiguration.yaml @@ -0,0 +1,80 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingWebhookConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +webhooks: +- admissionReviewVersions: + - admissionReviewVersionsValue + clientConfig: + caBundle: Ag== + service: + name: nameValue + namespace: namespaceValue + path: pathValue + port: 4 + url: urlValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchPolicy: matchPolicyValue + name: nameValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + reinvocationPolicy: reinvocationPolicyValue + rules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resources: + - resourcesValue + scope: scopeValue + sideEffects: sideEffectsValue + timeoutSeconds: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.json new file mode 100644 index 0000000000000..0106a1a27f687 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.json @@ -0,0 +1,171 @@ +{ + "kind": "ValidatingAdmissionPolicy", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "paramKind": { + "apiVersion": "apiVersionValue", + "kind": "kindValue" + }, + "matchConstraints": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "validations": [ + { + "expression": "expressionValue", + "message": "messageValue", + "reason": "reasonValue", + "messageExpression": "messageExpressionValue" + } + ], + "failurePolicy": "failurePolicyValue", + "auditAnnotations": [ + { + "key": "keyValue", + "valueExpression": "valueExpressionValue" + } + ], + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ], + "variables": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + }, + "status": { + "observedGeneration": 1, + "typeChecking": { + "expressionWarnings": [ + { + "fieldRef": "fieldRefValue", + "warning": "warningValue" + } + ] + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.pb new file mode 100644 index 0000000000000..68fc6c6f9b7a7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.yaml new file mode 100644 index 0000000000000..187f29dc34e2d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicy.yaml @@ -0,0 +1,108 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingAdmissionPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + auditAnnotations: + - key: keyValue + valueExpression: valueExpressionValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchConstraints: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramKind: + apiVersion: apiVersionValue + kind: kindValue + validations: + - expression: expressionValue + message: messageValue + messageExpression: messageExpressionValue + reason: reasonValue + variables: + - expression: expressionValue + name: nameValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + typeChecking: + expressionWarnings: + - fieldRef: fieldRefValue + warning: warningValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.json new file mode 100644 index 0000000000000..e371aadf1cbf4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.json @@ -0,0 +1,142 @@ +{ + "kind": "ValidatingAdmissionPolicyBinding", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "policyName": "policyNameValue", + "paramRef": { + "name": "nameValue", + "namespace": "namespaceValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "parameterNotFoundAction": "parameterNotFoundActionValue" + }, + "matchResources": { + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "excludeResourceRules": [ + { + "resourceNames": [ + "resourceNamesValue" + ], + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "matchPolicy": "matchPolicyValue" + }, + "validationActions": [ + "validationActionsValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.pb new file mode 100644 index 0000000000000..dae25655a4d31 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.yaml new file mode 100644 index 0000000000000..2c8de3d330a59 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingAdmissionPolicyBinding.yaml @@ -0,0 +1,92 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingAdmissionPolicyBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + matchResources: + excludeResourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + matchPolicy: matchPolicyValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resourceRules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + scope: scopeValue + paramRef: + name: nameValue + namespace: namespaceValue + parameterNotFoundAction: parameterNotFoundActionValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyName: policyNameValue + validationActions: + - validationActionsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.json new file mode 100644 index 0000000000000..601d6486deaa4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.json @@ -0,0 +1,119 @@ +{ + "kind": "ValidatingWebhookConfiguration", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "webhooks": [ + { + "name": "nameValue", + "clientConfig": { + "url": "urlValue", + "service": { + "namespace": "namespaceValue", + "name": "nameValue", + "path": "pathValue", + "port": 4 + }, + "caBundle": "Ag==" + }, + "rules": [ + { + "operations": [ + "operationsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "apiVersions": [ + "apiVersionsValue" + ], + "resources": [ + "resourcesValue" + ], + "scope": "scopeValue" + } + ], + "failurePolicy": "failurePolicyValue", + "matchPolicy": "matchPolicyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "objectSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "sideEffects": "sideEffectsValue", + "timeoutSeconds": 7, + "admissionReviewVersions": [ + "admissionReviewVersionsValue" + ], + "matchConditions": [ + { + "name": "nameValue", + "expression": "expressionValue" + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.pb new file mode 100644 index 0000000000000..cbc3fe417be24 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.yaml new file mode 100644 index 0000000000000..014160ba2d1b9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/admissionregistration.k8s.io.v1beta1.ValidatingWebhookConfiguration.yaml @@ -0,0 +1,79 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingWebhookConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +webhooks: +- admissionReviewVersions: + - admissionReviewVersionsValue + clientConfig: + caBundle: Ag== + service: + name: nameValue + namespace: namespaceValue + path: pathValue + port: 4 + url: urlValue + failurePolicy: failurePolicyValue + matchConditions: + - expression: expressionValue + name: nameValue + matchPolicy: matchPolicyValue + name: nameValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + objectSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + rules: + - apiGroups: + - apiGroupsValue + apiVersions: + - apiVersionsValue + operations: + - operationsValue + resources: + - resourcesValue + scope: scopeValue + sideEffects: sideEffectsValue + timeoutSeconds: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.json b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.json new file mode 100644 index 0000000000000..f73a2fe10d7fa --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.json @@ -0,0 +1,93 @@ +{ + "kind": "APIGroupDiscovery", + "apiVersion": "apidiscovery.k8s.io/v2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "versions": [ + { + "version": "versionValue", + "resources": [ + { + "resource": "resourceValue", + "responseKind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "scope": "scopeValue", + "singularResource": "singularResourceValue", + "verbs": [ + "verbsValue" + ], + "shortNames": [ + "shortNamesValue" + ], + "categories": [ + "categoriesValue" + ], + "subresources": [ + { + "subresource": "subresourceValue", + "responseKind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "acceptedTypes": [ + { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + } + ], + "verbs": [ + "verbsValue" + ] + } + ] + } + ], + "freshness": "freshnessValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.pb new file mode 100644 index 0000000000000..c53450ce956d3 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.yaml new file mode 100644 index 0000000000000..d244a5af91e64 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2.APIGroupDiscovery.yaml @@ -0,0 +1,63 @@ +apiVersion: apidiscovery.k8s.io/v2 +kind: APIGroupDiscovery +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +versions: +- freshness: freshnessValue + resources: + - categories: + - categoriesValue + resource: resourceValue + responseKind: + group: groupValue + kind: kindValue + version: versionValue + scope: scopeValue + shortNames: + - shortNamesValue + singularResource: singularResourceValue + subresources: + - acceptedTypes: + - group: groupValue + kind: kindValue + version: versionValue + responseKind: + group: groupValue + kind: kindValue + version: versionValue + subresource: subresourceValue + verbs: + - verbsValue + verbs: + - verbsValue + version: versionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.json b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.json new file mode 100644 index 0000000000000..699ca16525ab1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.json @@ -0,0 +1,93 @@ +{ + "kind": "APIGroupDiscovery", + "apiVersion": "apidiscovery.k8s.io/v2beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "versions": [ + { + "version": "versionValue", + "resources": [ + { + "resource": "resourceValue", + "responseKind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "scope": "scopeValue", + "singularResource": "singularResourceValue", + "verbs": [ + "verbsValue" + ], + "shortNames": [ + "shortNamesValue" + ], + "categories": [ + "categoriesValue" + ], + "subresources": [ + { + "subresource": "subresourceValue", + "responseKind": { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + }, + "acceptedTypes": [ + { + "group": "groupValue", + "version": "versionValue", + "kind": "kindValue" + } + ], + "verbs": [ + "verbsValue" + ] + } + ] + } + ], + "freshness": "freshnessValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.pb new file mode 100644 index 0000000000000..83c2857fd8a87 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.yaml new file mode 100644 index 0000000000000..41d1feb4ce300 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apidiscovery.k8s.io.v2beta1.APIGroupDiscovery.yaml @@ -0,0 +1,63 @@ +apiVersion: apidiscovery.k8s.io/v2beta1 +kind: APIGroupDiscovery +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +versions: +- freshness: freshnessValue + resources: + - categories: + - categoriesValue + resource: resourceValue + responseKind: + group: groupValue + kind: kindValue + version: versionValue + scope: scopeValue + shortNames: + - shortNamesValue + singularResource: singularResourceValue + subresources: + - acceptedTypes: + - group: groupValue + kind: kindValue + version: versionValue + responseKind: + group: groupValue + kind: kindValue + version: versionValue + subresource: subresourceValue + verbs: + - verbsValue + verbs: + - verbsValue + version: versionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.json new file mode 100644 index 0000000000000..033ce01e8004c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.json @@ -0,0 +1,57 @@ +{ + "kind": "ControllerRevision", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "data": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "revision": 3 +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.pb new file mode 100644 index 0000000000000..f1221277de4b8 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.yaml new file mode 100644 index 0000000000000..052b19c6346ce --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ControllerRevision.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +data: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 +kind: ControllerRevision +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +revision: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.json new file mode 100644 index 0000000000000..fb8a6da193677 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.json @@ -0,0 +1,1882 @@ +{ + "kind": "DaemonSet", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 4, + "revisionHistoryLimit": 6 + }, + "status": { + "currentNumberScheduled": 1, + "numberMisscheduled": 2, + "desiredNumberScheduled": 3, + "numberReady": 4, + "observedGeneration": 5, + "updatedNumberScheduled": 6, + "numberAvailable": 7, + "numberUnavailable": 8, + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.pb new file mode 100644 index 0000000000000..023be7ad437bc Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..964c256f67776 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.after_roundtrip.yaml @@ -0,0 +1,1292 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + updateStrategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue +status: + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentNumberScheduled: 1 + desiredNumberScheduled: 3 + numberAvailable: 7 + numberMisscheduled: 2 + numberReady: 4 + numberUnavailable: 8 + observedGeneration: 5 + updatedNumberScheduled: 6 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.json new file mode 100644 index 0000000000000..48b204ffd730e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.json @@ -0,0 +1,1887 @@ +{ + "kind": "DaemonSet", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 4, + "revisionHistoryLimit": 6 + }, + "status": { + "currentNumberScheduled": 1, + "numberMisscheduled": 2, + "desiredNumberScheduled": 3, + "numberReady": 4, + "observedGeneration": 5, + "updatedNumberScheduled": 6, + "numberAvailable": 7, + "numberUnavailable": 8, + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.pb new file mode 100644 index 0000000000000..86fae4aca19c7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.yaml new file mode 100644 index 0000000000000..e57a716d19c38 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.DaemonSet.yaml @@ -0,0 +1,1296 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + updateStrategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue +status: + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentNumberScheduled: 1 + desiredNumberScheduled: 3 + numberAvailable: 7 + numberMisscheduled: 2 + numberReady: 4 + numberUnavailable: 8 + observedGeneration: 5 + updatedNumberScheduled: 6 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.json new file mode 100644 index 0000000000000..b5110ecdb0584 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.json @@ -0,0 +1,1885 @@ +{ + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.pb new file mode 100644 index 0000000000000..0f20eab42cbf1 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.yaml new file mode 100644 index 0000000000000..510dcfae0b75a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.after_roundtrip.yaml @@ -0,0 +1,1295 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.json new file mode 100644 index 0000000000000..5168ada9be23f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.json @@ -0,0 +1,1890 @@ +{ + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.pb new file mode 100644 index 0000000000000..bff40b2ef4e60 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.yaml new file mode 100644 index 0000000000000..e730e6296ddb6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.Deployment.yaml @@ -0,0 +1,1299 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.json new file mode 100644 index 0000000000000..1076c46a2a081 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.json @@ -0,0 +1,1872 @@ +{ + "kind": "ReplicaSet", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "terminatingReplicas": 7, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.pb new file mode 100644 index 0000000000000..7133bc057622a Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..10abdd14194e9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.after_roundtrip.yaml @@ -0,0 +1,1284 @@ +apiVersion: apps/v1 +kind: ReplicaSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 + terminatingReplicas: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.json new file mode 100644 index 0000000000000..77dbe6c40b1e2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.json @@ -0,0 +1,1877 @@ +{ + "kind": "ReplicaSet", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "terminatingReplicas": 7, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.pb new file mode 100644 index 0000000000000..b19b378ed0057 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.yaml new file mode 100644 index 0000000000000..7ba70c61ad092 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.ReplicaSet.yaml @@ -0,0 +1,1288 @@ +apiVersion: apps/v1 +kind: ReplicaSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 + terminatingReplicas: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.json new file mode 100644 index 0000000000000..241308edbabbf --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.json @@ -0,0 +1,2010 @@ +{ + "kind": "StatefulSet", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } + } + ], + "serviceName": "serviceNameValue", + "podManagementPolicy": "podManagementPolicyValue", + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "partition": 1, + "maxUnavailable": "maxUnavailableValue" + } + }, + "revisionHistoryLimit": 8, + "minReadySeconds": 9, + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "whenDeletedValue", + "whenScaled": "whenScaledValue" + }, + "ordinals": { + "start": 1 + } + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "readyReplicas": 3, + "currentReplicas": 4, + "updatedReplicas": 5, + "currentRevision": "currentRevisionValue", + "updateRevision": "updateRevisionValue", + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "availableReplicas": 11 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.pb new file mode 100644 index 0000000000000..74271d14bfb9f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..e25dc68e2bc4d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.after_roundtrip.yaml @@ -0,0 +1,1383 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 9 + ordinals: + start: 1 + persistentVolumeClaimRetentionPolicy: + whenDeleted: whenDeletedValue + whenScaled: whenScaledValue + podManagementPolicy: podManagementPolicyValue + replicas: 1 + revisionHistoryLimit: 8 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + serviceName: serviceNameValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + updateStrategy: + rollingUpdate: + maxUnavailable: maxUnavailableValue + partition: 1 + type: typeValue + volumeClaimTemplates: + - metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue +status: + availableReplicas: 11 + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentReplicas: 4 + currentRevision: currentRevisionValue + observedGeneration: 1 + readyReplicas: 3 + replicas: 2 + updateRevision: updateRevisionValue + updatedReplicas: 5 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.json new file mode 100644 index 0000000000000..66c5c1bbf8e2a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.json @@ -0,0 +1,2015 @@ +{ + "kind": "StatefulSet", + "apiVersion": "apps/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } + } + ], + "serviceName": "serviceNameValue", + "podManagementPolicy": "podManagementPolicyValue", + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "partition": 1, + "maxUnavailable": "maxUnavailableValue" + } + }, + "revisionHistoryLimit": 8, + "minReadySeconds": 9, + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "whenDeletedValue", + "whenScaled": "whenScaledValue" + }, + "ordinals": { + "start": 1 + } + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "readyReplicas": 3, + "currentReplicas": 4, + "updatedReplicas": 5, + "currentRevision": "currentRevisionValue", + "updateRevision": "updateRevisionValue", + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "availableReplicas": 11 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.pb new file mode 100644 index 0000000000000..eaad0ed30100a Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.yaml new file mode 100644 index 0000000000000..593d4a368d45e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1.StatefulSet.yaml @@ -0,0 +1,1387 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 9 + ordinals: + start: 1 + persistentVolumeClaimRetentionPolicy: + whenDeleted: whenDeletedValue + whenScaled: whenScaledValue + podManagementPolicy: podManagementPolicyValue + replicas: 1 + revisionHistoryLimit: 8 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + serviceName: serviceNameValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + updateStrategy: + rollingUpdate: + maxUnavailable: maxUnavailableValue + partition: 1 + type: typeValue + volumeClaimTemplates: + - metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue +status: + availableReplicas: 11 + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentReplicas: 4 + currentRevision: currentRevisionValue + observedGeneration: 1 + readyReplicas: 3 + replicas: 2 + updateRevision: updateRevisionValue + updatedReplicas: 5 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.json new file mode 100644 index 0000000000000..2b4ec8589e644 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.json @@ -0,0 +1,57 @@ +{ + "kind": "ControllerRevision", + "apiVersion": "apps/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "data": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "revision": 3 +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.pb new file mode 100644 index 0000000000000..59c57ae9a561f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.yaml new file mode 100644 index 0000000000000..b592efec3c1e7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.ControllerRevision.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1beta1 +data: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 +kind: ControllerRevision +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +revision: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.json new file mode 100644 index 0000000000000..6c52e92531e01 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.json @@ -0,0 +1,1888 @@ +{ + "kind": "Deployment", + "apiVersion": "apps/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "rollbackTo": { + "revision": 1 + }, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.pb new file mode 100644 index 0000000000000..456ddc5149a54 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.yaml new file mode 100644 index 0000000000000..115797da9d6fb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.after_roundtrip.yaml @@ -0,0 +1,1297 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + rollbackTo: + revision: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.json new file mode 100644 index 0000000000000..1a2a057e02f9e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.json @@ -0,0 +1,1893 @@ +{ + "kind": "Deployment", + "apiVersion": "apps/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "rollbackTo": { + "revision": 1 + }, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.pb new file mode 100644 index 0000000000000..596d4ed64af91 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.yaml new file mode 100644 index 0000000000000..d04c97bfc7c7a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Deployment.yaml @@ -0,0 +1,1301 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + rollbackTo: + revision: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.json new file mode 100644 index 0000000000000..b2c53b6afdd86 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.json @@ -0,0 +1,11 @@ +{ + "kind": "DeploymentRollback", + "apiVersion": "apps/v1beta1", + "name": "nameValue", + "updatedAnnotations": { + "updatedAnnotationsKey": "updatedAnnotationsValue" + }, + "rollbackTo": { + "revision": 1 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.pb new file mode 100644 index 0000000000000..acf1d1c08d6f3 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.yaml new file mode 100644 index 0000000000000..8faf658719003 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.DeploymentRollback.yaml @@ -0,0 +1,7 @@ +apiVersion: apps/v1beta1 +kind: DeploymentRollback +name: nameValue +rollbackTo: + revision: 1 +updatedAnnotations: + updatedAnnotationsKey: updatedAnnotationsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.json new file mode 100644 index 0000000000000..370e54aa4fa84 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.json @@ -0,0 +1,56 @@ +{ + "kind": "Scale", + "apiVersion": "apps/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1 + }, + "status": { + "replicas": 1, + "selector": { + "selectorKey": "selectorValue" + }, + "targetSelector": "targetSelectorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.pb new file mode 100644 index 0000000000000..422ad4267aeef Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.yaml new file mode 100644 index 0000000000000..e821a7fe35034 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.Scale.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1beta1 +kind: Scale +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + replicas: 1 +status: + replicas: 1 + selector: + selectorKey: selectorValue + targetSelector: targetSelectorValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.json new file mode 100644 index 0000000000000..8b397a5f5282c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.json @@ -0,0 +1,2010 @@ +{ + "kind": "StatefulSet", + "apiVersion": "apps/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } + } + ], + "serviceName": "serviceNameValue", + "podManagementPolicy": "podManagementPolicyValue", + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "partition": 1, + "maxUnavailable": "maxUnavailableValue" + } + }, + "revisionHistoryLimit": 8, + "minReadySeconds": 9, + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "whenDeletedValue", + "whenScaled": "whenScaledValue" + }, + "ordinals": { + "start": 1 + } + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "readyReplicas": 3, + "currentReplicas": 4, + "updatedReplicas": 5, + "currentRevision": "currentRevisionValue", + "updateRevision": "updateRevisionValue", + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "availableReplicas": 11 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.pb new file mode 100644 index 0000000000000..8524e86b5f09b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..f08ae4e71198a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.after_roundtrip.yaml @@ -0,0 +1,1383 @@ +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 9 + ordinals: + start: 1 + persistentVolumeClaimRetentionPolicy: + whenDeleted: whenDeletedValue + whenScaled: whenScaledValue + podManagementPolicy: podManagementPolicyValue + replicas: 1 + revisionHistoryLimit: 8 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + serviceName: serviceNameValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + updateStrategy: + rollingUpdate: + maxUnavailable: maxUnavailableValue + partition: 1 + type: typeValue + volumeClaimTemplates: + - metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue +status: + availableReplicas: 11 + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentReplicas: 4 + currentRevision: currentRevisionValue + observedGeneration: 1 + readyReplicas: 3 + replicas: 2 + updateRevision: updateRevisionValue + updatedReplicas: 5 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.json new file mode 100644 index 0000000000000..064d41d805f09 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.json @@ -0,0 +1,2015 @@ +{ + "kind": "StatefulSet", + "apiVersion": "apps/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } + } + ], + "serviceName": "serviceNameValue", + "podManagementPolicy": "podManagementPolicyValue", + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "partition": 1, + "maxUnavailable": "maxUnavailableValue" + } + }, + "revisionHistoryLimit": 8, + "minReadySeconds": 9, + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "whenDeletedValue", + "whenScaled": "whenScaledValue" + }, + "ordinals": { + "start": 1 + } + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "readyReplicas": 3, + "currentReplicas": 4, + "updatedReplicas": 5, + "currentRevision": "currentRevisionValue", + "updateRevision": "updateRevisionValue", + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "availableReplicas": 11 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.pb new file mode 100644 index 0000000000000..6975bae4a37e5 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.yaml new file mode 100644 index 0000000000000..fb99d0e602cab --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta1.StatefulSet.yaml @@ -0,0 +1,1387 @@ +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 9 + ordinals: + start: 1 + persistentVolumeClaimRetentionPolicy: + whenDeleted: whenDeletedValue + whenScaled: whenScaledValue + podManagementPolicy: podManagementPolicyValue + replicas: 1 + revisionHistoryLimit: 8 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + serviceName: serviceNameValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + updateStrategy: + rollingUpdate: + maxUnavailable: maxUnavailableValue + partition: 1 + type: typeValue + volumeClaimTemplates: + - metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue +status: + availableReplicas: 11 + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentReplicas: 4 + currentRevision: currentRevisionValue + observedGeneration: 1 + readyReplicas: 3 + replicas: 2 + updateRevision: updateRevisionValue + updatedReplicas: 5 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.json new file mode 100644 index 0000000000000..60c5f5767eca1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.json @@ -0,0 +1,57 @@ +{ + "kind": "ControllerRevision", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "data": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "revision": 3 +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.pb new file mode 100644 index 0000000000000..869de5ce53ffe Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.yaml new file mode 100644 index 0000000000000..136b0cd03b09e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ControllerRevision.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1beta2 +data: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 +kind: ControllerRevision +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +revision: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.json new file mode 100644 index 0000000000000..b8e1f6ff24d7d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.json @@ -0,0 +1,1882 @@ +{ + "kind": "DaemonSet", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 4, + "revisionHistoryLimit": 6 + }, + "status": { + "currentNumberScheduled": 1, + "numberMisscheduled": 2, + "desiredNumberScheduled": 3, + "numberReady": 4, + "observedGeneration": 5, + "updatedNumberScheduled": 6, + "numberAvailable": 7, + "numberUnavailable": 8, + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.pb new file mode 100644 index 0000000000000..1ef6506147387 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..e19e9642410cc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.after_roundtrip.yaml @@ -0,0 +1,1292 @@ +apiVersion: apps/v1beta2 +kind: DaemonSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + updateStrategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue +status: + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentNumberScheduled: 1 + desiredNumberScheduled: 3 + numberAvailable: 7 + numberMisscheduled: 2 + numberReady: 4 + numberUnavailable: 8 + observedGeneration: 5 + updatedNumberScheduled: 6 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.json new file mode 100644 index 0000000000000..d6eee9ea6e9d0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.json @@ -0,0 +1,1887 @@ +{ + "kind": "DaemonSet", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 4, + "revisionHistoryLimit": 6 + }, + "status": { + "currentNumberScheduled": 1, + "numberMisscheduled": 2, + "desiredNumberScheduled": 3, + "numberReady": 4, + "observedGeneration": 5, + "updatedNumberScheduled": 6, + "numberAvailable": 7, + "numberUnavailable": 8, + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.pb new file mode 100644 index 0000000000000..d2fa55813e098 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.yaml new file mode 100644 index 0000000000000..6c5827f42d10b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.DaemonSet.yaml @@ -0,0 +1,1296 @@ +apiVersion: apps/v1beta2 +kind: DaemonSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + updateStrategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue +status: + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentNumberScheduled: 1 + desiredNumberScheduled: 3 + numberAvailable: 7 + numberMisscheduled: 2 + numberReady: 4 + numberUnavailable: 8 + observedGeneration: 5 + updatedNumberScheduled: 6 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.json new file mode 100644 index 0000000000000..4253567389da0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.json @@ -0,0 +1,1885 @@ +{ + "kind": "Deployment", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.pb new file mode 100644 index 0000000000000..b8e2038d0fb52 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.yaml new file mode 100644 index 0000000000000..13161c957f04d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.after_roundtrip.yaml @@ -0,0 +1,1295 @@ +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.json new file mode 100644 index 0000000000000..62cd655717b2c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.json @@ -0,0 +1,1890 @@ +{ + "kind": "Deployment", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.pb new file mode 100644 index 0000000000000..bf681bb8d865e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.yaml new file mode 100644 index 0000000000000..9dc11ada486c0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Deployment.yaml @@ -0,0 +1,1299 @@ +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.json new file mode 100644 index 0000000000000..8f405718087f7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.json @@ -0,0 +1,1872 @@ +{ + "kind": "ReplicaSet", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "terminatingReplicas": 7, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.pb new file mode 100644 index 0000000000000..466e8b7ae92a1 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..e89e13b45b860 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.after_roundtrip.yaml @@ -0,0 +1,1284 @@ +apiVersion: apps/v1beta2 +kind: ReplicaSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 + terminatingReplicas: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.json new file mode 100644 index 0000000000000..d2e880c58f0d5 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.json @@ -0,0 +1,1877 @@ +{ + "kind": "ReplicaSet", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "terminatingReplicas": 7, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.pb new file mode 100644 index 0000000000000..15275cd57a1eb Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.yaml new file mode 100644 index 0000000000000..ec1574bc98b76 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.ReplicaSet.yaml @@ -0,0 +1,1288 @@ +apiVersion: apps/v1beta2 +kind: ReplicaSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 + terminatingReplicas: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.json new file mode 100644 index 0000000000000..ab0a7ad374e90 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.json @@ -0,0 +1,56 @@ +{ + "kind": "Scale", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1 + }, + "status": { + "replicas": 1, + "selector": { + "selectorKey": "selectorValue" + }, + "targetSelector": "targetSelectorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.pb new file mode 100644 index 0000000000000..d62f25ff1dad9 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.yaml new file mode 100644 index 0000000000000..bb916412c2e73 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.Scale.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1beta2 +kind: Scale +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + replicas: 1 +status: + replicas: 1 + selector: + selectorKey: selectorValue + targetSelector: targetSelectorValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.json new file mode 100644 index 0000000000000..ce356c81d694d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.json @@ -0,0 +1,2010 @@ +{ + "kind": "StatefulSet", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } + } + ], + "serviceName": "serviceNameValue", + "podManagementPolicy": "podManagementPolicyValue", + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "partition": 1, + "maxUnavailable": "maxUnavailableValue" + } + }, + "revisionHistoryLimit": 8, + "minReadySeconds": 9, + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "whenDeletedValue", + "whenScaled": "whenScaledValue" + }, + "ordinals": { + "start": 1 + } + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "readyReplicas": 3, + "currentReplicas": 4, + "updatedReplicas": 5, + "currentRevision": "currentRevisionValue", + "updateRevision": "updateRevisionValue", + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "availableReplicas": 11 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.pb new file mode 100644 index 0000000000000..1eecfa8381db9 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..ff2412f8b5c23 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.after_roundtrip.yaml @@ -0,0 +1,1383 @@ +apiVersion: apps/v1beta2 +kind: StatefulSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 9 + ordinals: + start: 1 + persistentVolumeClaimRetentionPolicy: + whenDeleted: whenDeletedValue + whenScaled: whenScaledValue + podManagementPolicy: podManagementPolicyValue + replicas: 1 + revisionHistoryLimit: 8 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + serviceName: serviceNameValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + updateStrategy: + rollingUpdate: + maxUnavailable: maxUnavailableValue + partition: 1 + type: typeValue + volumeClaimTemplates: + - metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue +status: + availableReplicas: 11 + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentReplicas: 4 + currentRevision: currentRevisionValue + observedGeneration: 1 + readyReplicas: 3 + replicas: 2 + updateRevision: updateRevisionValue + updatedReplicas: 5 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.json new file mode 100644 index 0000000000000..28a32b44dcd41 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.json @@ -0,0 +1,2015 @@ +{ + "kind": "StatefulSet", + "apiVersion": "apps/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } + } + ], + "serviceName": "serviceNameValue", + "podManagementPolicy": "podManagementPolicyValue", + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "partition": 1, + "maxUnavailable": "maxUnavailableValue" + } + }, + "revisionHistoryLimit": 8, + "minReadySeconds": 9, + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "whenDeletedValue", + "whenScaled": "whenScaledValue" + }, + "ordinals": { + "start": 1 + } + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "readyReplicas": 3, + "currentReplicas": 4, + "updatedReplicas": 5, + "currentRevision": "currentRevisionValue", + "updateRevision": "updateRevisionValue", + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "availableReplicas": 11 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.pb new file mode 100644 index 0000000000000..2a7abed471fff Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.yaml new file mode 100644 index 0000000000000..1a799675be072 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/apps.v1beta2.StatefulSet.yaml @@ -0,0 +1,1387 @@ +apiVersion: apps/v1beta2 +kind: StatefulSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 9 + ordinals: + start: 1 + persistentVolumeClaimRetentionPolicy: + whenDeleted: whenDeletedValue + whenScaled: whenScaledValue + podManagementPolicy: podManagementPolicyValue + replicas: 1 + revisionHistoryLimit: 8 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + serviceName: serviceNameValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + updateStrategy: + rollingUpdate: + maxUnavailable: maxUnavailableValue + partition: 1 + type: typeValue + volumeClaimTemplates: + - metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue +status: + availableReplicas: 11 + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentReplicas: 4 + currentRevision: currentRevisionValue + observedGeneration: 1 + readyReplicas: 3 + replicas: 2 + updateRevision: updateRevisionValue + updatedReplicas: 5 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.json new file mode 100644 index 0000000000000..471629438430a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.json @@ -0,0 +1,60 @@ +{ + "kind": "SelfSubjectReview", + "apiVersion": "authentication.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "status": { + "userInfo": { + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.pb new file mode 100644 index 0000000000000..fc4df485686e6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.yaml new file mode 100644 index 0000000000000..4468f4062b329 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.SelfSubjectReview.yaml @@ -0,0 +1,43 @@ +apiVersion: authentication.k8s.io/v1 +kind: SelfSubjectReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +status: + userInfo: + extra: + extraKey: + - extraValue + groups: + - groupsValue + uid: uidValue + username: usernameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.json b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.json new file mode 100644 index 0000000000000..8b54b2b543462 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.json @@ -0,0 +1,62 @@ +{ + "kind": "TokenRequest", + "apiVersion": "authentication.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "audiences": [ + "audiencesValue" + ], + "expirationSeconds": 4, + "boundObjectRef": { + "kind": "kindValue", + "apiVersion": "apiVersionValue", + "name": "nameValue", + "uid": "uidValue" + } + }, + "status": { + "token": "tokenValue", + "expirationTimestamp": "2002-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.pb new file mode 100644 index 0000000000000..b7b414d852fbd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.yaml new file mode 100644 index 0000000000000..1c7b4638e0eb6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenRequest.yaml @@ -0,0 +1,46 @@ +apiVersion: authentication.k8s.io/v1 +kind: TokenRequest +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + audiences: + - audiencesValue + boundObjectRef: + apiVersion: apiVersionValue + kind: kindValue + name: nameValue + uid: uidValue + expirationSeconds: 4 +status: + expirationTimestamp: "2002-01-01T01:01:01Z" + token: tokenValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.json new file mode 100644 index 0000000000000..18faf990e85ef --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.json @@ -0,0 +1,71 @@ +{ + "kind": "TokenReview", + "apiVersion": "authentication.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "token": "tokenValue", + "audiences": [ + "audiencesValue" + ] + }, + "status": { + "authenticated": true, + "user": { + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + }, + "audiences": [ + "audiencesValue" + ], + "error": "errorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.pb new file mode 100644 index 0000000000000..808cdb50d6b12 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.yaml new file mode 100644 index 0000000000000..0bf995578705e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1.TokenReview.yaml @@ -0,0 +1,51 @@ +apiVersion: authentication.k8s.io/v1 +kind: TokenReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + audiences: + - audiencesValue + token: tokenValue +status: + audiences: + - audiencesValue + authenticated: true + error: errorValue + user: + extra: + extraKey: + - extraValue + groups: + - groupsValue + uid: uidValue + username: usernameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.json new file mode 100644 index 0000000000000..5dc2c9967f96d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.json @@ -0,0 +1,60 @@ +{ + "kind": "SelfSubjectReview", + "apiVersion": "authentication.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "status": { + "userInfo": { + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.pb new file mode 100644 index 0000000000000..8b12151619dc6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.yaml new file mode 100644 index 0000000000000..d03790d11a401 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.SelfSubjectReview.yaml @@ -0,0 +1,43 @@ +apiVersion: authentication.k8s.io/v1beta1 +kind: SelfSubjectReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +status: + userInfo: + extra: + extraKey: + - extraValue + groups: + - groupsValue + uid: uidValue + username: usernameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.json new file mode 100644 index 0000000000000..0ce24e21895a5 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.json @@ -0,0 +1,71 @@ +{ + "kind": "TokenReview", + "apiVersion": "authentication.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "token": "tokenValue", + "audiences": [ + "audiencesValue" + ] + }, + "status": { + "authenticated": true, + "user": { + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + }, + "audiences": [ + "audiencesValue" + ], + "error": "errorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.pb new file mode 100644 index 0000000000000..93315878f1df7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.yaml new file mode 100644 index 0000000000000..0f7c240d447f7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authentication.k8s.io.v1beta1.TokenReview.yaml @@ -0,0 +1,51 @@ +apiVersion: authentication.k8s.io/v1beta1 +kind: TokenReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + audiences: + - audiencesValue + token: tokenValue +status: + audiences: + - audiencesValue + authenticated: true + error: errorValue + user: + extra: + extraKey: + - extraValue + groups: + - groupsValue + uid: uidValue + username: usernameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.json new file mode 100644 index 0000000000000..9d6b138a15ea9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.json @@ -0,0 +1,101 @@ +{ + "kind": "LocalSubjectAccessReview", + "apiVersion": "authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resourceAttributes": { + "namespace": "namespaceValue", + "verb": "verbValue", + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue", + "subresource": "subresourceValue", + "name": "nameValue", + "fieldSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "labelSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "nonResourceAttributes": { + "path": "pathValue", + "verb": "verbValue" + }, + "user": "userValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + }, + "uid": "uidValue" + }, + "status": { + "allowed": true, + "denied": true, + "reason": "reasonValue", + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.pb new file mode 100644 index 0000000000000..fe26fea6f5c48 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.yaml new file mode 100644 index 0000000000000..bb343117e26c0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.LocalSubjectAccessReview.yaml @@ -0,0 +1,72 @@ +apiVersion: authorization.k8s.io/v1 +kind: LocalSubjectAccessReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + extra: + extraKey: + - extraValue + groups: + - groupsValue + nonResourceAttributes: + path: pathValue + verb: verbValue + resourceAttributes: + fieldSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + group: groupValue + labelSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + name: nameValue + namespace: namespaceValue + resource: resourceValue + subresource: subresourceValue + verb: verbValue + version: versionValue + uid: uidValue + user: userValue +status: + allowed: true + denied: true + evaluationError: evaluationErrorValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.json new file mode 100644 index 0000000000000..c713d9ba3c8ed --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.json @@ -0,0 +1,91 @@ +{ + "kind": "SelfSubjectAccessReview", + "apiVersion": "authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resourceAttributes": { + "namespace": "namespaceValue", + "verb": "verbValue", + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue", + "subresource": "subresourceValue", + "name": "nameValue", + "fieldSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "labelSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "nonResourceAttributes": { + "path": "pathValue", + "verb": "verbValue" + } + }, + "status": { + "allowed": true, + "denied": true, + "reason": "reasonValue", + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.pb new file mode 100644 index 0000000000000..127df8fe0743c Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.yaml new file mode 100644 index 0000000000000..069bc76884905 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectAccessReview.yaml @@ -0,0 +1,65 @@ +apiVersion: authorization.k8s.io/v1 +kind: SelfSubjectAccessReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + nonResourceAttributes: + path: pathValue + verb: verbValue + resourceAttributes: + fieldSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + group: groupValue + labelSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + name: nameValue + namespace: namespaceValue + resource: resourceValue + subresource: subresourceValue + verb: verbValue + version: versionValue +status: + allowed: true + denied: true + evaluationError: evaluationErrorValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.json new file mode 100644 index 0000000000000..e6b7d2dff6025 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.json @@ -0,0 +1,79 @@ +{ + "kind": "SelfSubjectRulesReview", + "apiVersion": "authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "namespace": "namespaceValue" + }, + "status": { + "resourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ] + } + ], + "nonResourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ], + "incomplete": true, + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.pb new file mode 100644 index 0000000000000..ff981d386cc7d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.yaml new file mode 100644 index 0000000000000..835154ef29c96 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SelfSubjectRulesReview.yaml @@ -0,0 +1,53 @@ +apiVersion: authorization.k8s.io/v1 +kind: SelfSubjectRulesReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + namespace: namespaceValue +status: + evaluationError: evaluationErrorValue + incomplete: true + nonResourceRules: + - nonResourceURLs: + - nonResourceURLsValue + verbs: + - verbsValue + resourceRules: + - apiGroups: + - apiGroupsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.json new file mode 100644 index 0000000000000..ee23994da7434 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.json @@ -0,0 +1,101 @@ +{ + "kind": "SubjectAccessReview", + "apiVersion": "authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resourceAttributes": { + "namespace": "namespaceValue", + "verb": "verbValue", + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue", + "subresource": "subresourceValue", + "name": "nameValue", + "fieldSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "labelSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "nonResourceAttributes": { + "path": "pathValue", + "verb": "verbValue" + }, + "user": "userValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + }, + "uid": "uidValue" + }, + "status": { + "allowed": true, + "denied": true, + "reason": "reasonValue", + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.pb new file mode 100644 index 0000000000000..00e714385aca5 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.yaml new file mode 100644 index 0000000000000..49b85947191eb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1.SubjectAccessReview.yaml @@ -0,0 +1,72 @@ +apiVersion: authorization.k8s.io/v1 +kind: SubjectAccessReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + extra: + extraKey: + - extraValue + groups: + - groupsValue + nonResourceAttributes: + path: pathValue + verb: verbValue + resourceAttributes: + fieldSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + group: groupValue + labelSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + name: nameValue + namespace: namespaceValue + resource: resourceValue + subresource: subresourceValue + verb: verbValue + version: versionValue + uid: uidValue + user: userValue +status: + allowed: true + denied: true + evaluationError: evaluationErrorValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.json new file mode 100644 index 0000000000000..a13c9713a10fa --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.json @@ -0,0 +1,101 @@ +{ + "kind": "LocalSubjectAccessReview", + "apiVersion": "authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resourceAttributes": { + "namespace": "namespaceValue", + "verb": "verbValue", + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue", + "subresource": "subresourceValue", + "name": "nameValue", + "fieldSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "labelSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "nonResourceAttributes": { + "path": "pathValue", + "verb": "verbValue" + }, + "user": "userValue", + "group": [ + "groupValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + }, + "uid": "uidValue" + }, + "status": { + "allowed": true, + "denied": true, + "reason": "reasonValue", + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.pb new file mode 100644 index 0000000000000..9c6438d5d017e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.yaml new file mode 100644 index 0000000000000..8bdc6b65326f2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.LocalSubjectAccessReview.yaml @@ -0,0 +1,72 @@ +apiVersion: authorization.k8s.io/v1beta1 +kind: LocalSubjectAccessReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + extra: + extraKey: + - extraValue + group: + - groupValue + nonResourceAttributes: + path: pathValue + verb: verbValue + resourceAttributes: + fieldSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + group: groupValue + labelSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + name: nameValue + namespace: namespaceValue + resource: resourceValue + subresource: subresourceValue + verb: verbValue + version: versionValue + uid: uidValue + user: userValue +status: + allowed: true + denied: true + evaluationError: evaluationErrorValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.json new file mode 100644 index 0000000000000..a2c2f0ae67c2e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.json @@ -0,0 +1,91 @@ +{ + "kind": "SelfSubjectAccessReview", + "apiVersion": "authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resourceAttributes": { + "namespace": "namespaceValue", + "verb": "verbValue", + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue", + "subresource": "subresourceValue", + "name": "nameValue", + "fieldSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "labelSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "nonResourceAttributes": { + "path": "pathValue", + "verb": "verbValue" + } + }, + "status": { + "allowed": true, + "denied": true, + "reason": "reasonValue", + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.pb new file mode 100644 index 0000000000000..c17a8e5f9b589 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.yaml new file mode 100644 index 0000000000000..1a548c4a76cfa --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectAccessReview.yaml @@ -0,0 +1,65 @@ +apiVersion: authorization.k8s.io/v1beta1 +kind: SelfSubjectAccessReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + nonResourceAttributes: + path: pathValue + verb: verbValue + resourceAttributes: + fieldSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + group: groupValue + labelSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + name: nameValue + namespace: namespaceValue + resource: resourceValue + subresource: subresourceValue + verb: verbValue + version: versionValue +status: + allowed: true + denied: true + evaluationError: evaluationErrorValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.json new file mode 100644 index 0000000000000..0b6e883761bfc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.json @@ -0,0 +1,79 @@ +{ + "kind": "SelfSubjectRulesReview", + "apiVersion": "authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "namespace": "namespaceValue" + }, + "status": { + "resourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ] + } + ], + "nonResourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ], + "incomplete": true, + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.pb new file mode 100644 index 0000000000000..b3c29b4d204cd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.yaml new file mode 100644 index 0000000000000..83f896be64d31 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SelfSubjectRulesReview.yaml @@ -0,0 +1,53 @@ +apiVersion: authorization.k8s.io/v1beta1 +kind: SelfSubjectRulesReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + namespace: namespaceValue +status: + evaluationError: evaluationErrorValue + incomplete: true + nonResourceRules: + - nonResourceURLs: + - nonResourceURLsValue + verbs: + - verbsValue + resourceRules: + - apiGroups: + - apiGroupsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.json new file mode 100644 index 0000000000000..5a2472c6e9b06 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.json @@ -0,0 +1,101 @@ +{ + "kind": "SubjectAccessReview", + "apiVersion": "authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resourceAttributes": { + "namespace": "namespaceValue", + "verb": "verbValue", + "group": "groupValue", + "version": "versionValue", + "resource": "resourceValue", + "subresource": "subresourceValue", + "name": "nameValue", + "fieldSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "labelSelector": { + "rawSelector": "rawSelectorValue", + "requirements": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "nonResourceAttributes": { + "path": "pathValue", + "verb": "verbValue" + }, + "user": "userValue", + "group": [ + "groupValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + }, + "uid": "uidValue" + }, + "status": { + "allowed": true, + "denied": true, + "reason": "reasonValue", + "evaluationError": "evaluationErrorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.pb new file mode 100644 index 0000000000000..3d82e2bac7fb0 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.yaml new file mode 100644 index 0000000000000..5d3647e4982d4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/authorization.k8s.io.v1beta1.SubjectAccessReview.yaml @@ -0,0 +1,72 @@ +apiVersion: authorization.k8s.io/v1beta1 +kind: SubjectAccessReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + extra: + extraKey: + - extraValue + group: + - groupValue + nonResourceAttributes: + path: pathValue + verb: verbValue + resourceAttributes: + fieldSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + group: groupValue + labelSelector: + rawSelector: rawSelectorValue + requirements: + - key: keyValue + operator: operatorValue + values: + - valuesValue + name: nameValue + namespace: namespaceValue + resource: resourceValue + subresource: subresourceValue + verb: verbValue + version: versionValue + uid: uidValue + user: userValue +status: + allowed: true + denied: true + evaluationError: evaluationErrorValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.json b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.json new file mode 100644 index 0000000000000..c4af108f9d5d3 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.json @@ -0,0 +1,63 @@ +{ + "kind": "HorizontalPodAutoscaler", + "apiVersion": "autoscaling/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "scaleTargetRef": { + "kind": "kindValue", + "name": "nameValue", + "apiVersion": "apiVersionValue" + }, + "minReplicas": 2, + "maxReplicas": 3, + "targetCPUUtilizationPercentage": 4 + }, + "status": { + "observedGeneration": 1, + "lastScaleTime": "2002-01-01T01:01:01Z", + "currentReplicas": 3, + "desiredReplicas": 4, + "currentCPUUtilizationPercentage": 5 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.pb b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.pb new file mode 100644 index 0000000000000..89d9b6eeb9100 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.yaml new file mode 100644 index 0000000000000..4d76ce4accdf5 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.HorizontalPodAutoscaler.yaml @@ -0,0 +1,48 @@ +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + maxReplicas: 3 + minReplicas: 2 + scaleTargetRef: + apiVersion: apiVersionValue + kind: kindValue + name: nameValue + targetCPUUtilizationPercentage: 4 +status: + currentCPUUtilizationPercentage: 5 + currentReplicas: 3 + desiredReplicas: 4 + lastScaleTime: "2002-01-01T01:01:01Z" + observedGeneration: 1 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.json b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.json new file mode 100644 index 0000000000000..f5f9a463d27b7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.json @@ -0,0 +1,53 @@ +{ + "kind": "Scale", + "apiVersion": "autoscaling/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1 + }, + "status": { + "replicas": 1, + "selector": "selectorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.pb b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.pb new file mode 100644 index 0000000000000..9bcf37e969796 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.yaml new file mode 100644 index 0000000000000..098815e83e632 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v1.Scale.yaml @@ -0,0 +1,39 @@ +apiVersion: autoscaling/v1 +kind: Scale +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + replicas: 1 +status: + replicas: 1 + selector: selectorValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.json b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.json new file mode 100644 index 0000000000000..4bc9fceaf70fd --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.json @@ -0,0 +1,299 @@ +{ + "kind": "HorizontalPodAutoscaler", + "apiVersion": "autoscaling/v2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "scaleTargetRef": { + "kind": "kindValue", + "name": "nameValue", + "apiVersion": "apiVersionValue" + }, + "minReplicas": 2, + "maxReplicas": 3, + "metrics": [ + { + "type": "typeValue", + "object": { + "describedObject": { + "kind": "kindValue", + "name": "nameValue", + "apiVersion": "apiVersionValue" + }, + "target": { + "type": "typeValue", + "value": "0", + "averageValue": "0", + "averageUtilization": 4 + }, + "metric": { + "name": "nameValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + }, + "pods": { + "metric": { + "name": "nameValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "target": { + "type": "typeValue", + "value": "0", + "averageValue": "0", + "averageUtilization": 4 + } + }, + "resource": { + "name": "nameValue", + "target": { + "type": "typeValue", + "value": "0", + "averageValue": "0", + "averageUtilization": 4 + } + }, + "containerResource": { + "name": "nameValue", + "target": { + "type": "typeValue", + "value": "0", + "averageValue": "0", + "averageUtilization": 4 + }, + "container": "containerValue" + }, + "external": { + "metric": { + "name": "nameValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "target": { + "type": "typeValue", + "value": "0", + "averageValue": "0", + "averageUtilization": 4 + } + } + } + ], + "behavior": { + "scaleUp": { + "stabilizationWindowSeconds": 3, + "selectPolicy": "selectPolicyValue", + "policies": [ + { + "type": "typeValue", + "value": 2, + "periodSeconds": 3 + } + ], + "tolerance": "0" + }, + "scaleDown": { + "stabilizationWindowSeconds": 3, + "selectPolicy": "selectPolicyValue", + "policies": [ + { + "type": "typeValue", + "value": 2, + "periodSeconds": 3 + } + ], + "tolerance": "0" + } + } + }, + "status": { + "observedGeneration": 1, + "lastScaleTime": "2002-01-01T01:01:01Z", + "currentReplicas": 3, + "desiredReplicas": 4, + "currentMetrics": [ + { + "type": "typeValue", + "object": { + "metric": { + "name": "nameValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "current": { + "value": "0", + "averageValue": "0", + "averageUtilization": 3 + }, + "describedObject": { + "kind": "kindValue", + "name": "nameValue", + "apiVersion": "apiVersionValue" + } + }, + "pods": { + "metric": { + "name": "nameValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "current": { + "value": "0", + "averageValue": "0", + "averageUtilization": 3 + } + }, + "resource": { + "name": "nameValue", + "current": { + "value": "0", + "averageValue": "0", + "averageUtilization": 3 + } + }, + "containerResource": { + "name": "nameValue", + "current": { + "value": "0", + "averageValue": "0", + "averageUtilization": 3 + }, + "container": "containerValue" + }, + "external": { + "metric": { + "name": "nameValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "current": { + "value": "0", + "averageValue": "0", + "averageUtilization": 3 + } + } + } + ], + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.pb b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.pb new file mode 100644 index 0000000000000..0d57f89ae3c65 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.yaml new file mode 100644 index 0000000000000..463936eeba013 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2.HorizontalPodAutoscaler.yaml @@ -0,0 +1,202 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 3 + type: typeValue + value: 2 + selectPolicy: selectPolicyValue + stabilizationWindowSeconds: 3 + tolerance: "0" + scaleUp: + policies: + - periodSeconds: 3 + type: typeValue + value: 2 + selectPolicy: selectPolicyValue + stabilizationWindowSeconds: 3 + tolerance: "0" + maxReplicas: 3 + metrics: + - containerResource: + container: containerValue + name: nameValue + target: + averageUtilization: 4 + averageValue: "0" + type: typeValue + value: "0" + external: + metric: + name: nameValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + target: + averageUtilization: 4 + averageValue: "0" + type: typeValue + value: "0" + object: + describedObject: + apiVersion: apiVersionValue + kind: kindValue + name: nameValue + metric: + name: nameValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + target: + averageUtilization: 4 + averageValue: "0" + type: typeValue + value: "0" + pods: + metric: + name: nameValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + target: + averageUtilization: 4 + averageValue: "0" + type: typeValue + value: "0" + resource: + name: nameValue + target: + averageUtilization: 4 + averageValue: "0" + type: typeValue + value: "0" + type: typeValue + minReplicas: 2 + scaleTargetRef: + apiVersion: apiVersionValue + kind: kindValue + name: nameValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentMetrics: + - containerResource: + container: containerValue + current: + averageUtilization: 3 + averageValue: "0" + value: "0" + name: nameValue + external: + current: + averageUtilization: 3 + averageValue: "0" + value: "0" + metric: + name: nameValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + object: + current: + averageUtilization: 3 + averageValue: "0" + value: "0" + describedObject: + apiVersion: apiVersionValue + kind: kindValue + name: nameValue + metric: + name: nameValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + pods: + current: + averageUtilization: 3 + averageValue: "0" + value: "0" + metric: + name: nameValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + resource: + current: + averageUtilization: 3 + averageValue: "0" + value: "0" + name: nameValue + type: typeValue + currentReplicas: 3 + desiredReplicas: 4 + lastScaleTime: "2002-01-01T01:01:01Z" + observedGeneration: 1 diff --git a/staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta1.HorizontalPodAutoscaler.json b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta1.HorizontalPodAutoscaler.json similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta1.HorizontalPodAutoscaler.json rename to staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta1.HorizontalPodAutoscaler.json diff --git a/staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta1.HorizontalPodAutoscaler.pb b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta1.HorizontalPodAutoscaler.pb similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta1.HorizontalPodAutoscaler.pb rename to staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta1.HorizontalPodAutoscaler.pb diff --git a/staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta1.HorizontalPodAutoscaler.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta1.HorizontalPodAutoscaler.yaml similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta1.HorizontalPodAutoscaler.yaml rename to staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta1.HorizontalPodAutoscaler.yaml diff --git a/staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta2.HorizontalPodAutoscaler.json b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta2.HorizontalPodAutoscaler.json similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta2.HorizontalPodAutoscaler.json rename to staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta2.HorizontalPodAutoscaler.json diff --git a/staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta2.HorizontalPodAutoscaler.pb b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta2.HorizontalPodAutoscaler.pb similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta2.HorizontalPodAutoscaler.pb rename to staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta2.HorizontalPodAutoscaler.pb diff --git a/staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta2.HorizontalPodAutoscaler.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta2.HorizontalPodAutoscaler.yaml similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/autoscaling.v2beta2.HorizontalPodAutoscaler.yaml rename to staging/src/k8s.io/api/testdata/v1.35.0/autoscaling.v2beta2.HorizontalPodAutoscaler.yaml diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.json new file mode 100644 index 0000000000000..be330443fc92a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.json @@ -0,0 +1,1961 @@ +{ + "kind": "CronJob", + "apiVersion": "batch/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "schedule": "scheduleValue", + "timeZone": "timeZoneValue", + "startingDeadlineSeconds": 2, + "concurrencyPolicy": "concurrencyPolicyValue", + "suspend": true, + "jobTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 2, + "activeDeadlineSeconds": 3, + "podFailurePolicy": { + "rules": [ + { + "action": "actionValue", + "onExitCodes": { + "containerName": "containerNameValue", + "operator": "operatorValue", + "values": [ + 3 + ] + }, + "onPodConditions": [ + { + "type": "typeValue", + "status": "statusValue" + } + ] + } + ] + }, + "successPolicy": { + "rules": [ + { + "succeededIndexes": "succeededIndexesValue", + "succeededCount": 2 + } + ] + }, + "backoffLimit": 7, + "backoffLimitPerIndex": 12, + "maxFailedIndexes": 13, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "manualSelector": true, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "ttlSecondsAfterFinished": 8, + "completionMode": "completionModeValue", + "suspend": true, + "podReplacementPolicy": "podReplacementPolicyValue", + "managedBy": "managedByValue" + } + }, + "successfulJobsHistoryLimit": 6, + "failedJobsHistoryLimit": 7 + }, + "status": { + "active": [ + { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + ], + "lastScheduleTime": "2004-01-01T01:01:01Z", + "lastSuccessfulTime": "2005-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.pb new file mode 100644 index 0000000000000..939476f36e213 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.yaml new file mode 100644 index 0000000000000..f7fa21feb395b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.after_roundtrip.yaml @@ -0,0 +1,1348 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + concurrencyPolicy: concurrencyPolicyValue + failedJobsHistoryLimit: 7 + jobTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 3 + backoffLimit: 7 + backoffLimitPerIndex: 12 + completionMode: completionModeValue + completions: 2 + managedBy: managedByValue + manualSelector: true + maxFailedIndexes: 13 + parallelism: 1 + podFailurePolicy: + rules: + - action: actionValue + onExitCodes: + containerName: containerNameValue + operator: operatorValue + values: + - 3 + onPodConditions: + - status: statusValue + type: typeValue + podReplacementPolicy: podReplacementPolicyValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + successPolicy: + rules: + - succeededCount: 2 + succeededIndexes: succeededIndexesValue + suspend: true + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + ttlSecondsAfterFinished: 8 + schedule: scheduleValue + startingDeadlineSeconds: 2 + successfulJobsHistoryLimit: 6 + suspend: true + timeZone: timeZoneValue +status: + active: + - apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + lastScheduleTime: "2004-01-01T01:01:01Z" + lastSuccessfulTime: "2005-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.json b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.json new file mode 100644 index 0000000000000..1f5b8df2388be --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.json @@ -0,0 +1,1966 @@ +{ + "kind": "CronJob", + "apiVersion": "batch/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "schedule": "scheduleValue", + "timeZone": "timeZoneValue", + "startingDeadlineSeconds": 2, + "concurrencyPolicy": "concurrencyPolicyValue", + "suspend": true, + "jobTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 2, + "activeDeadlineSeconds": 3, + "podFailurePolicy": { + "rules": [ + { + "action": "actionValue", + "onExitCodes": { + "containerName": "containerNameValue", + "operator": "operatorValue", + "values": [ + 3 + ] + }, + "onPodConditions": [ + { + "type": "typeValue", + "status": "statusValue" + } + ] + } + ] + }, + "successPolicy": { + "rules": [ + { + "succeededIndexes": "succeededIndexesValue", + "succeededCount": 2 + } + ] + }, + "backoffLimit": 7, + "backoffLimitPerIndex": 12, + "maxFailedIndexes": 13, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "manualSelector": true, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "ttlSecondsAfterFinished": 8, + "completionMode": "completionModeValue", + "suspend": true, + "podReplacementPolicy": "podReplacementPolicyValue", + "managedBy": "managedByValue" + } + }, + "successfulJobsHistoryLimit": 6, + "failedJobsHistoryLimit": 7 + }, + "status": { + "active": [ + { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + ], + "lastScheduleTime": "2004-01-01T01:01:01Z", + "lastSuccessfulTime": "2005-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.pb b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.pb new file mode 100644 index 0000000000000..775ebf95206f0 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.yaml new file mode 100644 index 0000000000000..b08dfe35206ff --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.CronJob.yaml @@ -0,0 +1,1352 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + concurrencyPolicy: concurrencyPolicyValue + failedJobsHistoryLimit: 7 + jobTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 3 + backoffLimit: 7 + backoffLimitPerIndex: 12 + completionMode: completionModeValue + completions: 2 + managedBy: managedByValue + manualSelector: true + maxFailedIndexes: 13 + parallelism: 1 + podFailurePolicy: + rules: + - action: actionValue + onExitCodes: + containerName: containerNameValue + operator: operatorValue + values: + - 3 + onPodConditions: + - status: statusValue + type: typeValue + podReplacementPolicy: podReplacementPolicyValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + successPolicy: + rules: + - succeededCount: 2 + succeededIndexes: succeededIndexesValue + suspend: true + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + ttlSecondsAfterFinished: 8 + schedule: scheduleValue + startingDeadlineSeconds: 2 + successfulJobsHistoryLimit: 6 + suspend: true + timeZone: timeZoneValue +status: + active: + - apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + lastScheduleTime: "2004-01-01T01:01:01Z" + lastSuccessfulTime: "2005-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.json new file mode 100644 index 0000000000000..dbf36796b289e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.json @@ -0,0 +1,1922 @@ +{ + "kind": "Job", + "apiVersion": "batch/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 2, + "activeDeadlineSeconds": 3, + "podFailurePolicy": { + "rules": [ + { + "action": "actionValue", + "onExitCodes": { + "containerName": "containerNameValue", + "operator": "operatorValue", + "values": [ + 3 + ] + }, + "onPodConditions": [ + { + "type": "typeValue", + "status": "statusValue" + } + ] + } + ] + }, + "successPolicy": { + "rules": [ + { + "succeededIndexes": "succeededIndexesValue", + "succeededCount": 2 + } + ] + }, + "backoffLimit": 7, + "backoffLimitPerIndex": 12, + "maxFailedIndexes": 13, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "manualSelector": true, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "ttlSecondsAfterFinished": 8, + "completionMode": "completionModeValue", + "suspend": true, + "podReplacementPolicy": "podReplacementPolicyValue", + "managedBy": "managedByValue" + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "startTime": "2002-01-01T01:01:01Z", + "completionTime": "2003-01-01T01:01:01Z", + "active": 4, + "succeeded": 5, + "failed": 6, + "terminating": 11, + "completedIndexes": "completedIndexesValue", + "failedIndexes": "failedIndexesValue", + "uncountedTerminatedPods": { + "succeeded": [ + "succeededValue" + ], + "failed": [ + "failedValue" + ] + }, + "ready": 9 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.pb new file mode 100644 index 0000000000000..effd13b60c652 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.yaml new file mode 100644 index 0000000000000..5941071a9ac47 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.after_roundtrip.yaml @@ -0,0 +1,1318 @@ +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + activeDeadlineSeconds: 3 + backoffLimit: 7 + backoffLimitPerIndex: 12 + completionMode: completionModeValue + completions: 2 + managedBy: managedByValue + manualSelector: true + maxFailedIndexes: 13 + parallelism: 1 + podFailurePolicy: + rules: + - action: actionValue + onExitCodes: + containerName: containerNameValue + operator: operatorValue + values: + - 3 + onPodConditions: + - status: statusValue + type: typeValue + podReplacementPolicy: podReplacementPolicyValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + successPolicy: + rules: + - succeededCount: 2 + succeededIndexes: succeededIndexesValue + suspend: true + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + ttlSecondsAfterFinished: 8 +status: + active: 4 + completedIndexes: completedIndexesValue + completionTime: "2003-01-01T01:01:01Z" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + failed: 6 + failedIndexes: failedIndexesValue + ready: 9 + startTime: "2002-01-01T01:01:01Z" + succeeded: 5 + terminating: 11 + uncountedTerminatedPods: + failed: + - failedValue + succeeded: + - succeededValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.json b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.json new file mode 100644 index 0000000000000..cae93ab95027e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.json @@ -0,0 +1,1927 @@ +{ + "kind": "Job", + "apiVersion": "batch/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 2, + "activeDeadlineSeconds": 3, + "podFailurePolicy": { + "rules": [ + { + "action": "actionValue", + "onExitCodes": { + "containerName": "containerNameValue", + "operator": "operatorValue", + "values": [ + 3 + ] + }, + "onPodConditions": [ + { + "type": "typeValue", + "status": "statusValue" + } + ] + } + ] + }, + "successPolicy": { + "rules": [ + { + "succeededIndexes": "succeededIndexesValue", + "succeededCount": 2 + } + ] + }, + "backoffLimit": 7, + "backoffLimitPerIndex": 12, + "maxFailedIndexes": 13, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "manualSelector": true, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "ttlSecondsAfterFinished": 8, + "completionMode": "completionModeValue", + "suspend": true, + "podReplacementPolicy": "podReplacementPolicyValue", + "managedBy": "managedByValue" + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "startTime": "2002-01-01T01:01:01Z", + "completionTime": "2003-01-01T01:01:01Z", + "active": 4, + "succeeded": 5, + "failed": 6, + "terminating": 11, + "completedIndexes": "completedIndexesValue", + "failedIndexes": "failedIndexesValue", + "uncountedTerminatedPods": { + "succeeded": [ + "succeededValue" + ], + "failed": [ + "failedValue" + ] + }, + "ready": 9 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.pb b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.pb new file mode 100644 index 0000000000000..8f767838b8817 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.yaml new file mode 100644 index 0000000000000..3d25c120ee32f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1.Job.yaml @@ -0,0 +1,1322 @@ +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + activeDeadlineSeconds: 3 + backoffLimit: 7 + backoffLimitPerIndex: 12 + completionMode: completionModeValue + completions: 2 + managedBy: managedByValue + manualSelector: true + maxFailedIndexes: 13 + parallelism: 1 + podFailurePolicy: + rules: + - action: actionValue + onExitCodes: + containerName: containerNameValue + operator: operatorValue + values: + - 3 + onPodConditions: + - status: statusValue + type: typeValue + podReplacementPolicy: podReplacementPolicyValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + successPolicy: + rules: + - succeededCount: 2 + succeededIndexes: succeededIndexesValue + suspend: true + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + ttlSecondsAfterFinished: 8 +status: + active: 4 + completedIndexes: completedIndexesValue + completionTime: "2003-01-01T01:01:01Z" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + failed: 6 + failedIndexes: failedIndexesValue + ready: 9 + startTime: "2002-01-01T01:01:01Z" + succeeded: 5 + terminating: 11 + uncountedTerminatedPods: + failed: + - failedValue + succeeded: + - succeededValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.json new file mode 100644 index 0000000000000..9b6467188e4b7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.json @@ -0,0 +1,1961 @@ +{ + "kind": "CronJob", + "apiVersion": "batch/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "schedule": "scheduleValue", + "timeZone": "timeZoneValue", + "startingDeadlineSeconds": 2, + "concurrencyPolicy": "concurrencyPolicyValue", + "suspend": true, + "jobTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 2, + "activeDeadlineSeconds": 3, + "podFailurePolicy": { + "rules": [ + { + "action": "actionValue", + "onExitCodes": { + "containerName": "containerNameValue", + "operator": "operatorValue", + "values": [ + 3 + ] + }, + "onPodConditions": [ + { + "type": "typeValue", + "status": "statusValue" + } + ] + } + ] + }, + "successPolicy": { + "rules": [ + { + "succeededIndexes": "succeededIndexesValue", + "succeededCount": 2 + } + ] + }, + "backoffLimit": 7, + "backoffLimitPerIndex": 12, + "maxFailedIndexes": 13, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "manualSelector": true, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "ttlSecondsAfterFinished": 8, + "completionMode": "completionModeValue", + "suspend": true, + "podReplacementPolicy": "podReplacementPolicyValue", + "managedBy": "managedByValue" + } + }, + "successfulJobsHistoryLimit": 6, + "failedJobsHistoryLimit": 7 + }, + "status": { + "active": [ + { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + ], + "lastScheduleTime": "2004-01-01T01:01:01Z", + "lastSuccessfulTime": "2005-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.pb new file mode 100644 index 0000000000000..c4a234030d0f9 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.yaml new file mode 100644 index 0000000000000..edcef666052b3 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.after_roundtrip.yaml @@ -0,0 +1,1348 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + concurrencyPolicy: concurrencyPolicyValue + failedJobsHistoryLimit: 7 + jobTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 3 + backoffLimit: 7 + backoffLimitPerIndex: 12 + completionMode: completionModeValue + completions: 2 + managedBy: managedByValue + manualSelector: true + maxFailedIndexes: 13 + parallelism: 1 + podFailurePolicy: + rules: + - action: actionValue + onExitCodes: + containerName: containerNameValue + operator: operatorValue + values: + - 3 + onPodConditions: + - status: statusValue + type: typeValue + podReplacementPolicy: podReplacementPolicyValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + successPolicy: + rules: + - succeededCount: 2 + succeededIndexes: succeededIndexesValue + suspend: true + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + ttlSecondsAfterFinished: 8 + schedule: scheduleValue + startingDeadlineSeconds: 2 + successfulJobsHistoryLimit: 6 + suspend: true + timeZone: timeZoneValue +status: + active: + - apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + lastScheduleTime: "2004-01-01T01:01:01Z" + lastSuccessfulTime: "2005-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.json b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.json new file mode 100644 index 0000000000000..5436d0cf4e83d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.json @@ -0,0 +1,1966 @@ +{ + "kind": "CronJob", + "apiVersion": "batch/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "schedule": "scheduleValue", + "timeZone": "timeZoneValue", + "startingDeadlineSeconds": 2, + "concurrencyPolicy": "concurrencyPolicyValue", + "suspend": true, + "jobTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 2, + "activeDeadlineSeconds": 3, + "podFailurePolicy": { + "rules": [ + { + "action": "actionValue", + "onExitCodes": { + "containerName": "containerNameValue", + "operator": "operatorValue", + "values": [ + 3 + ] + }, + "onPodConditions": [ + { + "type": "typeValue", + "status": "statusValue" + } + ] + } + ] + }, + "successPolicy": { + "rules": [ + { + "succeededIndexes": "succeededIndexesValue", + "succeededCount": 2 + } + ] + }, + "backoffLimit": 7, + "backoffLimitPerIndex": 12, + "maxFailedIndexes": 13, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "manualSelector": true, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "ttlSecondsAfterFinished": 8, + "completionMode": "completionModeValue", + "suspend": true, + "podReplacementPolicy": "podReplacementPolicyValue", + "managedBy": "managedByValue" + } + }, + "successfulJobsHistoryLimit": 6, + "failedJobsHistoryLimit": 7 + }, + "status": { + "active": [ + { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + ], + "lastScheduleTime": "2004-01-01T01:01:01Z", + "lastSuccessfulTime": "2005-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.pb b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.pb new file mode 100644 index 0000000000000..6bfb4158e7f31 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.yaml new file mode 100644 index 0000000000000..21d9f01c16bd4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/batch.v1beta1.CronJob.yaml @@ -0,0 +1,1352 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + concurrencyPolicy: concurrencyPolicyValue + failedJobsHistoryLimit: 7 + jobTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 3 + backoffLimit: 7 + backoffLimitPerIndex: 12 + completionMode: completionModeValue + completions: 2 + managedBy: managedByValue + manualSelector: true + maxFailedIndexes: 13 + parallelism: 1 + podFailurePolicy: + rules: + - action: actionValue + onExitCodes: + containerName: containerNameValue + operator: operatorValue + values: + - 3 + onPodConditions: + - status: statusValue + type: typeValue + podReplacementPolicy: podReplacementPolicyValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + successPolicy: + rules: + - succeededCount: 2 + succeededIndexes: succeededIndexesValue + suspend: true + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + ttlSecondsAfterFinished: 8 + schedule: scheduleValue + startingDeadlineSeconds: 2 + successfulJobsHistoryLimit: 6 + suspend: true + timeZone: timeZoneValue +status: + active: + - apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + lastScheduleTime: "2004-01-01T01:01:01Z" + lastSuccessfulTime: "2005-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.json b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.json new file mode 100644 index 0000000000000..6c7f5be0a6bbc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.json @@ -0,0 +1,77 @@ +{ + "kind": "CertificateSigningRequest", + "apiVersion": "certificates.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "request": "AQ==", + "signerName": "signerNameValue", + "expirationSeconds": 8, + "usages": [ + "usagesValue" + ], + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "reason": "reasonValue", + "message": "messageValue", + "lastUpdateTime": "2004-01-01T01:01:01Z", + "lastTransitionTime": "2005-01-01T01:01:01Z" + } + ], + "certificate": "Ag==" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.pb b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.pb new file mode 100644 index 0000000000000..65b963f5c0996 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.yaml new file mode 100644 index 0000000000000..c66e9ad661276 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1.CertificateSigningRequest.yaml @@ -0,0 +1,56 @@ +apiVersion: certificates.k8s.io/v1 +kind: CertificateSigningRequest +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + expirationSeconds: 8 + extra: + extraKey: + - extraValue + groups: + - groupsValue + request: AQ== + signerName: signerNameValue + uid: uidValue + usages: + - usagesValue + username: usernameValue +status: + certificate: Ag== + conditions: + - lastTransitionTime: "2005-01-01T01:01:01Z" + lastUpdateTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.json b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.json new file mode 100644 index 0000000000000..abd68b875d03e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.json @@ -0,0 +1,50 @@ +{ + "kind": "ClusterTrustBundle", + "apiVersion": "certificates.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "signerName": "signerNameValue", + "trustBundle": "trustBundleValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.pb b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.pb new file mode 100644 index 0000000000000..638772cc8a973 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.yaml new file mode 100644 index 0000000000000..52b063a8d76e2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1alpha1.ClusterTrustBundle.yaml @@ -0,0 +1,37 @@ +apiVersion: certificates.k8s.io/v1alpha1 +kind: ClusterTrustBundle +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + signerName: signerNameValue + trustBundle: trustBundleValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.json b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.json new file mode 100644 index 0000000000000..7307d013e8f5f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.json @@ -0,0 +1,77 @@ +{ + "kind": "CertificateSigningRequest", + "apiVersion": "certificates.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "request": "AQ==", + "signerName": "signerNameValue", + "expirationSeconds": 8, + "usages": [ + "usagesValue" + ], + "username": "usernameValue", + "uid": "uidValue", + "groups": [ + "groupsValue" + ], + "extra": { + "extraKey": [ + "extraValue" + ] + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "reason": "reasonValue", + "message": "messageValue", + "lastUpdateTime": "2004-01-01T01:01:01Z", + "lastTransitionTime": "2005-01-01T01:01:01Z" + } + ], + "certificate": "Ag==" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.pb b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.pb new file mode 100644 index 0000000000000..224d8838bb7c1 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.yaml new file mode 100644 index 0000000000000..9cb463c635f4e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.CertificateSigningRequest.yaml @@ -0,0 +1,56 @@ +apiVersion: certificates.k8s.io/v1beta1 +kind: CertificateSigningRequest +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + expirationSeconds: 8 + extra: + extraKey: + - extraValue + groups: + - groupsValue + request: AQ== + signerName: signerNameValue + uid: uidValue + usages: + - usagesValue + username: usernameValue +status: + certificate: Ag== + conditions: + - lastTransitionTime: "2005-01-01T01:01:01Z" + lastUpdateTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.json b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.json new file mode 100644 index 0000000000000..d578d21e89adb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.json @@ -0,0 +1,50 @@ +{ + "kind": "ClusterTrustBundle", + "apiVersion": "certificates.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "signerName": "signerNameValue", + "trustBundle": "trustBundleValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.pb b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.pb new file mode 100644 index 0000000000000..1b405ba9d8824 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.yaml new file mode 100644 index 0000000000000..8102689219020 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.ClusterTrustBundle.yaml @@ -0,0 +1,37 @@ +apiVersion: certificates.k8s.io/v1beta1 +kind: ClusterTrustBundle +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + signerName: signerNameValue + trustBundle: trustBundleValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.after_roundtrip.json new file mode 100644 index 0000000000000..85593546e8b12 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.after_roundtrip.json @@ -0,0 +1,78 @@ +{ + "kind": "PodCertificateRequest", + "apiVersion": "certificates.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "signerName": "signerNameValue", + "podName": "podNameValue", + "podUID": "podUIDValue", + "serviceAccountName": "serviceAccountNameValue", + "serviceAccountUID": "serviceAccountUIDValue", + "nodeName": "nodeNameValue", + "nodeUID": "nodeUIDValue", + "maxExpirationSeconds": 8, + "pkixPublicKey": "CQ==", + "proofOfPossession": "Cg==", + "stubPKCS10Request": null, + "unverifiedUserAnnotations": { + "unverifiedUserAnnotationsKey": "unverifiedUserAnnotationsValue" + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "certificateChain": "certificateChainValue", + "notBefore": "2004-01-01T01:01:01Z", + "beginRefreshAt": "2005-01-01T01:01:01Z", + "notAfter": "2006-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.after_roundtrip.yaml new file mode 100644 index 0000000000000..3260ee694f26c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.after_roundtrip.yaml @@ -0,0 +1,60 @@ +apiVersion: certificates.k8s.io/v1beta1 +kind: PodCertificateRequest +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + maxExpirationSeconds: 8 + nodeName: nodeNameValue + nodeUID: nodeUIDValue + pkixPublicKey: CQ== + podName: podNameValue + podUID: podUIDValue + proofOfPossession: Cg== + serviceAccountName: serviceAccountNameValue + serviceAccountUID: serviceAccountUIDValue + signerName: signerNameValue + stubPKCS10Request: null + unverifiedUserAnnotations: + unverifiedUserAnnotationsKey: unverifiedUserAnnotationsValue +status: + beginRefreshAt: "2005-01-01T01:01:01Z" + certificateChain: certificateChainValue + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + notAfter: "2006-01-01T01:01:01Z" + notBefore: "2004-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.json b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.json new file mode 100644 index 0000000000000..93a2f04448c19 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.json @@ -0,0 +1,77 @@ +{ + "kind": "PodCertificateRequest", + "apiVersion": "certificates.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "signerName": "signerNameValue", + "podName": "podNameValue", + "podUID": "podUIDValue", + "serviceAccountName": "serviceAccountNameValue", + "serviceAccountUID": "serviceAccountUIDValue", + "nodeName": "nodeNameValue", + "nodeUID": "nodeUIDValue", + "maxExpirationSeconds": 8, + "pkixPublicKey": "CQ==", + "proofOfPossession": "Cg==", + "unverifiedUserAnnotations": { + "unverifiedUserAnnotationsKey": "unverifiedUserAnnotationsValue" + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "certificateChain": "certificateChainValue", + "notBefore": "2004-01-01T01:01:01Z", + "beginRefreshAt": "2005-01-01T01:01:01Z", + "notAfter": "2006-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.pb b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.pb new file mode 100644 index 0000000000000..3b2e1ebfecdf8 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml new file mode 100644 index 0000000000000..f8cec35717984 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/certificates.k8s.io.v1beta1.PodCertificateRequest.yaml @@ -0,0 +1,59 @@ +apiVersion: certificates.k8s.io/v1beta1 +kind: PodCertificateRequest +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + maxExpirationSeconds: 8 + nodeName: nodeNameValue + nodeUID: nodeUIDValue + pkixPublicKey: CQ== + podName: podNameValue + podUID: podUIDValue + proofOfPossession: Cg== + serviceAccountName: serviceAccountNameValue + serviceAccountUID: serviceAccountUIDValue + signerName: signerNameValue + unverifiedUserAnnotations: + unverifiedUserAnnotationsKey: unverifiedUserAnnotationsValue +status: + beginRefreshAt: "2005-01-01T01:01:01Z" + certificateChain: certificateChainValue + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + notAfter: "2006-01-01T01:01:01Z" + notBefore: "2004-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.json b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.json new file mode 100644 index 0000000000000..72da0adfe5593 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.json @@ -0,0 +1,55 @@ +{ + "kind": "Lease", + "apiVersion": "coordination.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "holderIdentity": "holderIdentityValue", + "leaseDurationSeconds": 2, + "acquireTime": "2003-01-01T01:01:01.000003Z", + "renewTime": "2004-01-01T01:01:01.000004Z", + "leaseTransitions": 5, + "strategy": "strategyValue", + "preferredHolder": "preferredHolderValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.pb b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.pb new file mode 100644 index 0000000000000..9a8f59bfd5af5 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.yaml new file mode 100644 index 0000000000000..b9093c67cdb03 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1.Lease.yaml @@ -0,0 +1,42 @@ +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + acquireTime: "2003-01-01T01:01:01.000003Z" + holderIdentity: holderIdentityValue + leaseDurationSeconds: 2 + leaseTransitions: 5 + preferredHolder: preferredHolderValue + renewTime: "2004-01-01T01:01:01.000004Z" + strategy: strategyValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.json b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.json new file mode 100644 index 0000000000000..22ac6bfb9151e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.json @@ -0,0 +1,54 @@ +{ + "kind": "LeaseCandidate", + "apiVersion": "coordination.k8s.io/v1alpha2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "leaseName": "leaseNameValue", + "pingTime": "2002-01-01T01:01:01.000002Z", + "renewTime": "2003-01-01T01:01:01.000003Z", + "binaryVersion": "binaryVersionValue", + "emulationVersion": "emulationVersionValue", + "strategy": "strategyValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.pb b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.pb new file mode 100644 index 0000000000000..f092766664d69 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.yaml new file mode 100644 index 0000000000000..214791807d655 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1alpha2.LeaseCandidate.yaml @@ -0,0 +1,41 @@ +apiVersion: coordination.k8s.io/v1alpha2 +kind: LeaseCandidate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + binaryVersion: binaryVersionValue + emulationVersion: emulationVersionValue + leaseName: leaseNameValue + pingTime: "2002-01-01T01:01:01.000002Z" + renewTime: "2003-01-01T01:01:01.000003Z" + strategy: strategyValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.json b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.json new file mode 100644 index 0000000000000..ae9769c11f6ec --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.json @@ -0,0 +1,55 @@ +{ + "kind": "Lease", + "apiVersion": "coordination.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "holderIdentity": "holderIdentityValue", + "leaseDurationSeconds": 2, + "acquireTime": "2003-01-01T01:01:01.000003Z", + "renewTime": "2004-01-01T01:01:01.000004Z", + "leaseTransitions": 5, + "strategy": "strategyValue", + "preferredHolder": "preferredHolderValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.pb b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.pb new file mode 100644 index 0000000000000..bce198d2cffd7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.yaml new file mode 100644 index 0000000000000..a8405f66cc58d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.Lease.yaml @@ -0,0 +1,42 @@ +apiVersion: coordination.k8s.io/v1beta1 +kind: Lease +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + acquireTime: "2003-01-01T01:01:01.000003Z" + holderIdentity: holderIdentityValue + leaseDurationSeconds: 2 + leaseTransitions: 5 + preferredHolder: preferredHolderValue + renewTime: "2004-01-01T01:01:01.000004Z" + strategy: strategyValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.json b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.json new file mode 100644 index 0000000000000..42baac728a09c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.json @@ -0,0 +1,54 @@ +{ + "kind": "LeaseCandidate", + "apiVersion": "coordination.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "leaseName": "leaseNameValue", + "pingTime": "2002-01-01T01:01:01.000002Z", + "renewTime": "2003-01-01T01:01:01.000003Z", + "binaryVersion": "binaryVersionValue", + "emulationVersion": "emulationVersionValue", + "strategy": "strategyValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.pb b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.pb new file mode 100644 index 0000000000000..a72e9476af735 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.yaml new file mode 100644 index 0000000000000..bc2e58690dcee --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/coordination.k8s.io.v1beta1.LeaseCandidate.yaml @@ -0,0 +1,41 @@ +apiVersion: coordination.k8s.io/v1beta1 +kind: LeaseCandidate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + binaryVersion: binaryVersionValue + emulationVersion: emulationVersionValue + leaseName: leaseNameValue + pingTime: "2002-01-01T01:01:01.000002Z" + renewTime: "2003-01-01T01:01:01.000003Z" + strategy: strategyValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.json new file mode 100644 index 0000000000000..d65e20c0fedf7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.json @@ -0,0 +1,21 @@ +{ + "kind": "APIGroup", + "apiVersion": "v1", + "name": "nameValue", + "versions": [ + { + "groupVersion": "groupVersionValue", + "version": "versionValue" + } + ], + "preferredVersion": { + "groupVersion": "groupVersionValue", + "version": "versionValue" + }, + "serverAddressByClientCIDRs": [ + { + "clientCIDR": "clientCIDRValue", + "serverAddress": "serverAddressValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.pb new file mode 100644 index 0000000000000..dc4fc4b38158f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.yaml new file mode 100644 index 0000000000000..9c82b82ba4070 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIGroup.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: APIGroup +name: nameValue +preferredVersion: + groupVersion: groupVersionValue + version: versionValue +serverAddressByClientCIDRs: +- clientCIDR: clientCIDRValue + serverAddress: serverAddressValue +versions: +- groupVersion: groupVersionValue + version: versionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.json new file mode 100644 index 0000000000000..df5a16a0acab0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.json @@ -0,0 +1,13 @@ +{ + "kind": "APIVersions", + "apiVersion": "v1", + "versions": [ + "versionsValue" + ], + "serverAddressByClientCIDRs": [ + { + "clientCIDR": "clientCIDRValue", + "serverAddress": "serverAddressValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.pb new file mode 100644 index 0000000000000..7704c9bc02e27 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.yaml new file mode 100644 index 0000000000000..68a0670803fda --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.APIVersions.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: APIVersions +serverAddressByClientCIDRs: +- clientCIDR: clientCIDRValue + serverAddress: serverAddressValue +versions: +- versionsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.json new file mode 100644 index 0000000000000..4741bab46631b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.json @@ -0,0 +1,55 @@ +{ + "kind": "Binding", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "target": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.pb new file mode 100644 index 0000000000000..5f44fe6deda6f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.yaml new file mode 100644 index 0000000000000..8246c2ce3ea7c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Binding.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Binding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +target: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.json new file mode 100644 index 0000000000000..dfef6b138774f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.json @@ -0,0 +1,54 @@ +{ + "kind": "ComponentStatus", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "message": "messageValue", + "error": "errorValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.pb new file mode 100644 index 0000000000000..1c6f1fe2e928b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.yaml new file mode 100644 index 0000000000000..1e75b6f620974 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ComponentStatus.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +conditions: +- error: errorValue + message: messageValue + status: statusValue + type: typeValue +kind: ComponentStatus +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.json new file mode 100644 index 0000000000000..4362bb277cc5c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.json @@ -0,0 +1,53 @@ +{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "immutable": true, + "data": { + "dataKey": "dataValue" + }, + "binaryData": { + "binaryDataKey": "Aw==" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.pb new file mode 100644 index 0000000000000..10e9c13b6c8c9 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.yaml new file mode 100644 index 0000000000000..ecb3a3f7d5379 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ConfigMap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +binaryData: + binaryDataKey: Aw== +data: + dataKey: dataValue +immutable: true +kind: ConfigMap +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.json new file mode 100644 index 0000000000000..afcbef2f66507 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.json @@ -0,0 +1,9 @@ +{ + "kind": "CreateOptions", + "apiVersion": "v1", + "dryRun": [ + "dryRunValue" + ], + "fieldManager": "fieldManagerValue", + "fieldValidation": "fieldValidationValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.pb new file mode 100644 index 0000000000000..32fe071eac6a6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.yaml new file mode 100644 index 0000000000000..a696c6407ef62 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.CreateOptions.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +dryRun: +- dryRunValue +fieldManager: fieldManagerValue +fieldValidation: fieldValidationValue +kind: CreateOptions diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.json new file mode 100644 index 0000000000000..efe609bb3f898 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.json @@ -0,0 +1,15 @@ +{ + "kind": "DeleteOptions", + "apiVersion": "v1", + "gracePeriodSeconds": 1, + "preconditions": { + "uid": "uidValue", + "resourceVersion": "resourceVersionValue" + }, + "orphanDependents": true, + "propagationPolicy": "propagationPolicyValue", + "dryRun": [ + "dryRunValue" + ], + "ignoreStoreReadErrorWithClusterBreakingPotential": true +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.pb new file mode 100644 index 0000000000000..833a9b976a1e8 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.yaml new file mode 100644 index 0000000000000..3c15f8e6bff0d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.DeleteOptions.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +dryRun: +- dryRunValue +gracePeriodSeconds: 1 +ignoreStoreReadErrorWithClusterBreakingPotential: true +kind: DeleteOptions +orphanDependents: true +preconditions: + resourceVersion: resourceVersionValue + uid: uidValue +propagationPolicy: propagationPolicyValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.json new file mode 100644 index 0000000000000..cf3995cec82ca --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.json @@ -0,0 +1,90 @@ +{ + "kind": "Endpoints", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subsets": [ + { + "addresses": [ + { + "ip": "ipValue", + "hostname": "hostnameValue", + "nodeName": "nodeNameValue", + "targetRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + } + ], + "notReadyAddresses": [ + { + "ip": "ipValue", + "hostname": "hostnameValue", + "nodeName": "nodeNameValue", + "targetRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + } + ], + "ports": [ + { + "name": "nameValue", + "port": 2, + "protocol": "protocolValue", + "appProtocol": "appProtocolValue" + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.pb new file mode 100644 index 0000000000000..41551b7ff1d86 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.yaml new file mode 100644 index 0000000000000..e41c409995133 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Endpoints.yaml @@ -0,0 +1,64 @@ +apiVersion: v1 +kind: Endpoints +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +subsets: +- addresses: + - hostname: hostnameValue + ip: ipValue + nodeName: nodeNameValue + targetRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + notReadyAddresses: + - hostname: hostnameValue + ip: ipValue + nodeName: nodeNameValue + targetRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + ports: + - appProtocol: appProtocolValue + name: nameValue + port: 2 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.json new file mode 100644 index 0000000000000..84f731fc0a323 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.json @@ -0,0 +1,82 @@ +{ + "kind": "Event", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "involvedObject": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "reason": "reasonValue", + "message": "messageValue", + "source": { + "component": "componentValue", + "host": "hostValue" + }, + "firstTimestamp": "2006-01-01T01:01:01Z", + "lastTimestamp": "2007-01-01T01:01:01Z", + "count": 8, + "type": "typeValue", + "eventTime": "2010-01-01T01:01:01.000010Z", + "series": { + "count": 1, + "lastObservedTime": "2002-01-01T01:01:01.000002Z" + }, + "action": "actionValue", + "related": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "reportingComponent": "reportingComponentValue", + "reportingInstance": "reportingInstanceValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.pb new file mode 100644 index 0000000000000..2c034a995ee2f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.yaml new file mode 100644 index 0000000000000..79851ea30a03f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Event.yaml @@ -0,0 +1,66 @@ +action: actionValue +apiVersion: v1 +count: 8 +eventTime: "2010-01-01T01:01:01.000010Z" +firstTimestamp: "2006-01-01T01:01:01Z" +involvedObject: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue +kind: Event +lastTimestamp: "2007-01-01T01:01:01Z" +message: messageValue +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +reason: reasonValue +related: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue +reportingComponent: reportingComponentValue +reportingInstance: reportingInstanceValue +series: + count: 1 + lastObservedTime: "2002-01-01T01:01:01.000002Z" +source: + component: componentValue + host: hostValue +type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.json new file mode 100644 index 0000000000000..1cb1f261caf5b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.json @@ -0,0 +1,5 @@ +{ + "kind": "GetOptions", + "apiVersion": "v1", + "resourceVersion": "resourceVersionValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.pb new file mode 100644 index 0000000000000..5588495db353f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.yaml new file mode 100644 index 0000000000000..e84bdbdc49d7c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.GetOptions.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +kind: GetOptions +resourceVersion: resourceVersionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.json new file mode 100644 index 0000000000000..36f5267df9331 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.json @@ -0,0 +1,68 @@ +{ + "kind": "LimitRange", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "limits": [ + { + "type": "typeValue", + "max": { + "maxKey": "0" + }, + "min": { + "minKey": "0" + }, + "default": { + "defaultKey": "0" + }, + "defaultRequest": { + "defaultRequestKey": "0" + }, + "maxLimitRequestRatio": { + "maxLimitRequestRatioKey": "0" + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.pb new file mode 100644 index 0000000000000..f0d7e9badb656 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.yaml new file mode 100644 index 0000000000000..982a09ecb6532 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.LimitRange.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: LimitRange +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + limits: + - default: + defaultKey: "0" + defaultRequest: + defaultRequestKey: "0" + max: + maxKey: "0" + maxLimitRequestRatio: + maxLimitRequestRatioKey: "0" + min: + minKey: "0" + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.after_roundtrip.pb new file mode 100644 index 0000000000000..376328ccf8e76 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.json new file mode 100644 index 0000000000000..066b25b45faeb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.json @@ -0,0 +1,14 @@ +{ + "kind": "ListOptions", + "apiVersion": "v1", + "labelSelector": "labelSelectorValue", + "fieldSelector": "fieldSelectorValue", + "watch": true, + "allowWatchBookmarks": true, + "resourceVersion": "resourceVersionValue", + "resourceVersionMatch": "resourceVersionMatchValue", + "timeoutSeconds": 5, + "limit": 7, + "continue": "continueValue", + "sendInitialEvents": true +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.pb new file mode 100644 index 0000000000000..ea28506449565 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.yaml new file mode 100644 index 0000000000000..7dac63dd343a8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ListOptions.yaml @@ -0,0 +1,12 @@ +allowWatchBookmarks: true +apiVersion: v1 +continue: continueValue +fieldSelector: fieldSelectorValue +kind: ListOptions +labelSelector: labelSelectorValue +limit: 7 +resourceVersion: resourceVersionValue +resourceVersionMatch: resourceVersionMatchValue +sendInitialEvents: true +timeoutSeconds: 5 +watch: true diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.json new file mode 100644 index 0000000000000..d04cbb9fe982f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.json @@ -0,0 +1,63 @@ +{ + "kind": "Namespace", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "finalizers": [ + "finalizersValue" + ] + }, + "status": { + "phase": "phaseValue", + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.pb new file mode 100644 index 0000000000000..665796b15032e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.yaml new file mode 100644 index 0000000000000..d262f3fae2b0f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Namespace.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Namespace +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + finalizers: + - finalizersValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + phase: phaseValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.json new file mode 100644 index 0000000000000..e207e4c6a5420 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.json @@ -0,0 +1,179 @@ +{ + "kind": "Node", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "podCIDR": "podCIDRValue", + "podCIDRs": [ + "podCIDRsValue" + ], + "providerID": "providerIDValue", + "unschedulable": true, + "taints": [ + { + "key": "keyValue", + "value": "valueValue", + "effect": "effectValue", + "timeAdded": "2004-01-01T01:01:01Z" + } + ], + "configSource": { + "configMap": { + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "kubeletConfigKey": "kubeletConfigKeyValue" + } + }, + "externalID": "externalIDValue" + }, + "status": { + "capacity": { + "capacityKey": "0" + }, + "allocatable": { + "allocatableKey": "0" + }, + "phase": "phaseValue", + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastHeartbeatTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "addresses": [ + { + "type": "typeValue", + "address": "addressValue" + } + ], + "daemonEndpoints": { + "kubeletEndpoint": { + "Port": 1 + } + }, + "nodeInfo": { + "machineID": "machineIDValue", + "systemUUID": "systemUUIDValue", + "bootID": "bootIDValue", + "kernelVersion": "kernelVersionValue", + "osImage": "osImageValue", + "containerRuntimeVersion": "containerRuntimeVersionValue", + "kubeletVersion": "kubeletVersionValue", + "kubeProxyVersion": "kubeProxyVersionValue", + "operatingSystem": "operatingSystemValue", + "architecture": "architectureValue", + "swap": { + "capacity": 1 + } + }, + "images": [ + { + "names": [ + "namesValue" + ], + "sizeBytes": 2 + } + ], + "volumesInUse": [ + "volumesInUseValue" + ], + "volumesAttached": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "config": { + "assigned": { + "configMap": { + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "kubeletConfigKey": "kubeletConfigKeyValue" + } + }, + "active": { + "configMap": { + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "kubeletConfigKey": "kubeletConfigKeyValue" + } + }, + "lastKnownGood": { + "configMap": { + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "kubeletConfigKey": "kubeletConfigKeyValue" + } + }, + "error": "errorValue" + }, + "runtimeHandlers": [ + { + "name": "nameValue", + "features": { + "recursiveReadOnlyMounts": true, + "userNamespaces": true + } + } + ], + "features": { + "supplementalGroupsPolicy": true + }, + "declaredFeatures": [ + "declaredFeaturesValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.pb new file mode 100644 index 0000000000000..05c5adcfb9b47 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.yaml new file mode 100644 index 0000000000000..e5fdd6b9c35ff --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Node.yaml @@ -0,0 +1,126 @@ +apiVersion: v1 +kind: Node +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + configSource: + configMap: + kubeletConfigKey: kubeletConfigKeyValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + externalID: externalIDValue + podCIDR: podCIDRValue + podCIDRs: + - podCIDRsValue + providerID: providerIDValue + taints: + - effect: effectValue + key: keyValue + timeAdded: "2004-01-01T01:01:01Z" + value: valueValue + unschedulable: true +status: + addresses: + - address: addressValue + type: typeValue + allocatable: + allocatableKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastHeartbeatTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + config: + active: + configMap: + kubeletConfigKey: kubeletConfigKeyValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + assigned: + configMap: + kubeletConfigKey: kubeletConfigKeyValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + error: errorValue + lastKnownGood: + configMap: + kubeletConfigKey: kubeletConfigKeyValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + daemonEndpoints: + kubeletEndpoint: + Port: 1 + declaredFeatures: + - declaredFeaturesValue + features: + supplementalGroupsPolicy: true + images: + - names: + - namesValue + sizeBytes: 2 + nodeInfo: + architecture: architectureValue + bootID: bootIDValue + containerRuntimeVersion: containerRuntimeVersionValue + kernelVersion: kernelVersionValue + kubeProxyVersion: kubeProxyVersionValue + kubeletVersion: kubeletVersionValue + machineID: machineIDValue + operatingSystem: operatingSystemValue + osImage: osImageValue + swap: + capacity: 1 + systemUUID: systemUUIDValue + phase: phaseValue + runtimeHandlers: + - features: + recursiveReadOnlyMounts: true + userNamespaces: true + name: nameValue + volumesAttached: + - devicePath: devicePathValue + name: nameValue + volumesInUse: + - volumesInUseValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.json new file mode 100644 index 0000000000000..c6449453146c4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.json @@ -0,0 +1,5 @@ +{ + "kind": "NodeProxyOptions", + "apiVersion": "v1", + "path": "pathValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.pb new file mode 100644 index 0000000000000..25eb5071a4269 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.yaml new file mode 100644 index 0000000000000..cb9c68d849cfa --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.NodeProxyOptions.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +kind: NodeProxyOptions +path: pathValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.json new file mode 100644 index 0000000000000..32bdd8369b66e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.json @@ -0,0 +1,10 @@ +{ + "kind": "PatchOptions", + "apiVersion": "v1", + "dryRun": [ + "dryRunValue" + ], + "force": true, + "fieldManager": "fieldManagerValue", + "fieldValidation": "fieldValidationValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.pb new file mode 100644 index 0000000000000..e2dc7f3d2c42b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.yaml new file mode 100644 index 0000000000000..cc7ae8afcf07a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PatchOptions.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +dryRun: +- dryRunValue +fieldManager: fieldManagerValue +fieldValidation: fieldValidationValue +force: true +kind: PatchOptions diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.json new file mode 100644 index 0000000000000..ff283f72527d0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.json @@ -0,0 +1,311 @@ +{ + "kind": "PersistentVolume", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "capacity": { + "capacityKey": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true, + "endpointsNamespace": "endpointsNamespaceValue" + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "initiatorName": "initiatorNameValue" + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true, + "secretNamespace": "secretNamespaceValue" + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "local": { + "path": "pathValue", + "fsType": "fsTypeValue" + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + }, + "csi": { + "driver": "driverValue", + "volumeHandle": "volumeHandleValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "controllerPublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeStageSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodePublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "controllerExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "accessModes": [ + "accessModesValue" + ], + "claimRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "persistentVolumeReclaimPolicy": "persistentVolumeReclaimPolicyValue", + "storageClassName": "storageClassNameValue", + "mountOptions": [ + "mountOptionsValue" + ], + "volumeMode": "volumeModeValue", + "nodeAffinity": { + "required": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "message": "messageValue", + "reason": "reasonValue", + "lastPhaseTransitionTime": "2004-01-01T01:01:01Z" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.pb new file mode 100644 index 0000000000000..474cf3b733277 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.yaml new file mode 100644 index 0000000000000..1c2ff4fcea68b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolume.yaml @@ -0,0 +1,239 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + accessModes: + - accessModesValue + awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + secretNamespace: secretNamespaceValue + shareName: shareNameValue + capacity: + capacityKey: "0" + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + volumeID: volumeIDValue + claimRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + csi: + controllerExpandSecretRef: + name: nameValue + namespace: namespaceValue + controllerPublishSecretRef: + name: nameValue + namespace: namespaceValue + driver: driverValue + fsType: fsTypeValue + nodeExpandSecretRef: + name: nameValue + namespace: namespaceValue + nodePublishSecretRef: + name: nameValue + namespace: namespaceValue + nodeStageSecretRef: + name: nameValue + namespace: namespaceValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + volumeHandle: volumeHandleValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + glusterfs: + endpoints: endpointsValue + endpointsNamespace: endpointsNamespaceValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + targetPortal: targetPortalValue + local: + fsType: fsTypeValue + path: pathValue + mountOptions: + - mountOptionsValue + nfs: + path: pathValue + readOnly: true + server: serverValue + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + persistentVolumeReclaimPolicy: persistentVolumeReclaimPolicyValue + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + storageClassName: storageClassNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + lastPhaseTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + phase: phaseValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.json new file mode 100644 index 0000000000000..e40dfb6ece06a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.json @@ -0,0 +1,118 @@ +{ + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + }, + "status": { + "phase": "phaseValue", + "accessModes": [ + "accessModesValue" + ], + "capacity": { + "capacityKey": "0" + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "allocatedResourceStatuses": { + "allocatedResourceStatusesKey": "allocatedResourceStatusesValue" + }, + "currentVolumeAttributesClassName": "currentVolumeAttributesClassNameValue", + "modifyVolumeStatus": { + "targetVolumeAttributesClassName": "targetVolumeAttributesClassNameValue", + "status": "statusValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.pb new file mode 100644 index 0000000000000..1bd3103f05abd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.yaml new file mode 100644 index 0000000000000..e20cc4629fb2e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PersistentVolumeClaim.yaml @@ -0,0 +1,84 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue +status: + accessModes: + - accessModesValue + allocatedResourceStatuses: + allocatedResourceStatusesKey: allocatedResourceStatusesValue + allocatedResources: + allocatedResourcesKey: "0" + capacity: + capacityKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentVolumeAttributesClassName: currentVolumeAttributesClassNameValue + modifyVolumeStatus: + status: statusValue + targetVolumeAttributesClassName: targetVolumeAttributesClassNameValue + phase: phaseValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.json new file mode 100644 index 0000000000000..52eccb6fb9da9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.json @@ -0,0 +1,2138 @@ +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + }, + "status": { + "observedGeneration": 17, + "phase": "phaseValue", + "conditions": [ + { + "type": "typeValue", + "observedGeneration": 7, + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "message": "messageValue", + "reason": "reasonValue", + "nominatedNodeName": "nominatedNodeNameValue", + "hostIP": "hostIPValue", + "hostIPs": [ + { + "ip": "ipValue" + } + ], + "podIP": "podIPValue", + "podIPs": [ + { + "ip": "ipValue" + } + ], + "startTime": "2007-01-01T01:01:01Z", + "initContainerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "containerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "qosClass": "qosClassValue", + "ephemeralContainerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "resize": "resizeValue", + "resourceClaimStatuses": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue" + } + ], + "extendedResourceClaimStatus": { + "requestMappings": [ + { + "containerName": "containerNameValue", + "resourceName": "resourceNameValue", + "requestName": "requestNameValue" + } + ], + "resourceClaimName": "resourceClaimNameValue" + }, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.pb new file mode 100644 index 0000000000000..09d621801edbf Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.yaml new file mode 100644 index 0000000000000..0dc091a716e3f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.after_roundtrip.yaml @@ -0,0 +1,1458 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + allocatedResources: + allocatedResourcesKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 7 + reason: reasonValue + status: statusValue + type: typeValue + containerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + ephemeralContainerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + extendedResourceClaimStatus: + requestMappings: + - containerName: containerNameValue + requestName: requestNameValue + resourceName: resourceNameValue + resourceClaimName: resourceClaimNameValue + hostIP: hostIPValue + hostIPs: + - ip: ipValue + initContainerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + message: messageValue + nominatedNodeName: nominatedNodeNameValue + observedGeneration: 17 + phase: phaseValue + podIP: podIPValue + podIPs: + - ip: ipValue + qosClass: qosClassValue + reason: reasonValue + resize: resizeValue + resourceClaimStatuses: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + startTime: "2007-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.json new file mode 100644 index 0000000000000..8316bb1654163 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.json @@ -0,0 +1,2143 @@ +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + }, + "status": { + "observedGeneration": 17, + "phase": "phaseValue", + "conditions": [ + { + "type": "typeValue", + "observedGeneration": 7, + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "message": "messageValue", + "reason": "reasonValue", + "nominatedNodeName": "nominatedNodeNameValue", + "hostIP": "hostIPValue", + "hostIPs": [ + { + "ip": "ipValue" + } + ], + "podIP": "podIPValue", + "podIPs": [ + { + "ip": "ipValue" + } + ], + "startTime": "2007-01-01T01:01:01Z", + "initContainerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "containerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "qosClass": "qosClassValue", + "ephemeralContainerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "resize": "resizeValue", + "resourceClaimStatuses": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue" + } + ], + "extendedResourceClaimStatus": { + "requestMappings": [ + { + "containerName": "containerNameValue", + "resourceName": "resourceNameValue", + "requestName": "requestNameValue" + } + ], + "resourceClaimName": "resourceClaimNameValue" + }, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.pb new file mode 100644 index 0000000000000..5edf767085d42 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.yaml new file mode 100644 index 0000000000000..1335003613c70 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Pod.yaml @@ -0,0 +1,1462 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + allocatedResources: + allocatedResourcesKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 7 + reason: reasonValue + status: statusValue + type: typeValue + containerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + ephemeralContainerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + extendedResourceClaimStatus: + requestMappings: + - containerName: containerNameValue + requestName: requestNameValue + resourceName: resourceNameValue + resourceClaimName: resourceClaimNameValue + hostIP: hostIPValue + hostIPs: + - ip: ipValue + initContainerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + message: messageValue + nominatedNodeName: nominatedNodeNameValue + observedGeneration: 17 + phase: phaseValue + podIP: podIPValue + podIPs: + - ip: ipValue + qosClass: qosClassValue + reason: reasonValue + resize: resizeValue + resourceClaimStatuses: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + startTime: "2007-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.json new file mode 100644 index 0000000000000..836e03d1c1658 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.json @@ -0,0 +1,9 @@ +{ + "kind": "PodAttachOptions", + "apiVersion": "v1", + "stdin": true, + "stdout": true, + "stderr": true, + "tty": true, + "container": "containerValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.pb new file mode 100644 index 0000000000000..c1a2cba6071b6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.yaml new file mode 100644 index 0000000000000..04c9804b84ef8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodAttachOptions.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +container: containerValue +kind: PodAttachOptions +stderr: true +stdin: true +stdout: true +tty: true diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.json new file mode 100644 index 0000000000000..1db7b818716db --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.json @@ -0,0 +1,12 @@ +{ + "kind": "PodExecOptions", + "apiVersion": "v1", + "stdin": true, + "stdout": true, + "stderr": true, + "tty": true, + "container": "containerValue", + "command": [ + "commandValue" + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.pb new file mode 100644 index 0000000000000..7fe7509a415fd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.yaml new file mode 100644 index 0000000000000..70e003dc992e1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodExecOptions.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +command: +- commandValue +container: containerValue +kind: PodExecOptions +stderr: true +stdin: true +stdout: true +tty: true diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.json new file mode 100644 index 0000000000000..a219c377e3ad2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.json @@ -0,0 +1,14 @@ +{ + "kind": "PodLogOptions", + "apiVersion": "v1", + "container": "containerValue", + "follow": true, + "previous": true, + "sinceSeconds": 4, + "sinceTime": "2005-01-01T01:01:01Z", + "timestamps": true, + "tailLines": 7, + "limitBytes": 8, + "insecureSkipTLSVerifyBackend": true, + "stream": "streamValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.pb new file mode 100644 index 0000000000000..389fb4458bd5b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.yaml new file mode 100644 index 0000000000000..e92f94c140daf --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodLogOptions.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +container: containerValue +follow: true +insecureSkipTLSVerifyBackend: true +kind: PodLogOptions +limitBytes: 8 +previous: true +sinceSeconds: 4 +sinceTime: "2005-01-01T01:01:01Z" +stream: streamValue +tailLines: 7 +timestamps: true diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.json new file mode 100644 index 0000000000000..e977fc0dfcd58 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.json @@ -0,0 +1,7 @@ +{ + "kind": "PodPortForwardOptions", + "apiVersion": "v1", + "ports": [ + 1 + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.pb new file mode 100644 index 0000000000000..25eceb4a1b1dd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.yaml new file mode 100644 index 0000000000000..326dfd43f1c2a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodPortForwardOptions.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: PodPortForwardOptions +ports: +- 1 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.json new file mode 100644 index 0000000000000..5a195a6b5d046 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.json @@ -0,0 +1,5 @@ +{ + "kind": "PodProxyOptions", + "apiVersion": "v1", + "path": "pathValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.pb new file mode 100644 index 0000000000000..c17c1cd559c38 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.yaml new file mode 100644 index 0000000000000..bc3db8ce4d582 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodProxyOptions.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +kind: PodProxyOptions +path: pathValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.json new file mode 100644 index 0000000000000..eb59a472428c5 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.json @@ -0,0 +1,391 @@ +{ + "kind": "PodStatusResult", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "status": { + "observedGeneration": 17, + "phase": "phaseValue", + "conditions": [ + { + "type": "typeValue", + "observedGeneration": 7, + "status": "statusValue", + "lastProbeTime": "2003-01-01T01:01:01Z", + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "message": "messageValue", + "reason": "reasonValue", + "nominatedNodeName": "nominatedNodeNameValue", + "hostIP": "hostIPValue", + "hostIPs": [ + { + "ip": "ipValue" + } + ], + "podIP": "podIPValue", + "podIPs": [ + { + "ip": "ipValue" + } + ], + "startTime": "2007-01-01T01:01:01Z", + "initContainerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "containerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "qosClass": "qosClassValue", + "ephemeralContainerStatuses": [ + { + "name": "nameValue", + "state": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "lastState": { + "waiting": { + "reason": "reasonValue", + "message": "messageValue" + }, + "running": { + "startedAt": "2001-01-01T01:01:01Z" + }, + "terminated": { + "exitCode": 1, + "signal": 2, + "reason": "reasonValue", + "message": "messageValue", + "startedAt": "2005-01-01T01:01:01Z", + "finishedAt": "2006-01-01T01:01:01Z", + "containerID": "containerIDValue" + } + }, + "ready": true, + "restartCount": 5, + "image": "imageValue", + "imageID": "imageIDValue", + "containerID": "containerIDValue", + "started": true, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "volumeMounts": [ + { + "name": "nameValue", + "mountPath": "mountPathValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue" + } + ], + "user": { + "linux": { + "uid": 1, + "gid": 2, + "supplementalGroups": [ + 3 + ] + } + }, + "allocatedResourcesStatus": [ + { + "name": "nameValue", + "resources": [ + { + "resourceID": "resourceIDValue", + "health": "healthValue" + } + ] + } + ], + "stopSignal": "stopSignalValue" + } + ], + "resize": "resizeValue", + "resourceClaimStatuses": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue" + } + ], + "extendedResourceClaimStatus": { + "requestMappings": [ + { + "containerName": "containerNameValue", + "resourceName": "resourceNameValue", + "requestName": "requestNameValue" + } + ], + "resourceClaimName": "resourceClaimNameValue" + }, + "allocatedResources": { + "allocatedResourcesKey": "0" + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.pb new file mode 100644 index 0000000000000..a06ad0fe01372 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.yaml new file mode 100644 index 0000000000000..561d88be58d30 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodStatusResult.yaml @@ -0,0 +1,265 @@ +apiVersion: v1 +kind: PodStatusResult +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +status: + allocatedResources: + allocatedResourcesKey: "0" + conditions: + - lastProbeTime: "2003-01-01T01:01:01Z" + lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 7 + reason: reasonValue + status: statusValue + type: typeValue + containerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + ephemeralContainerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + extendedResourceClaimStatus: + requestMappings: + - containerName: containerNameValue + requestName: requestNameValue + resourceName: resourceNameValue + resourceClaimName: resourceClaimNameValue + hostIP: hostIPValue + hostIPs: + - ip: ipValue + initContainerStatuses: + - allocatedResources: + allocatedResourcesKey: "0" + allocatedResourcesStatus: + - name: nameValue + resources: + - health: healthValue + resourceID: resourceIDValue + containerID: containerIDValue + image: imageValue + imageID: imageIDValue + lastState: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + name: nameValue + ready: true + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartCount: 5 + started: true + state: + running: + startedAt: "2001-01-01T01:01:01Z" + terminated: + containerID: containerIDValue + exitCode: 1 + finishedAt: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + signal: 2 + startedAt: "2005-01-01T01:01:01Z" + waiting: + message: messageValue + reason: reasonValue + stopSignal: stopSignalValue + user: + linux: + gid: 2 + supplementalGroups: + - 3 + uid: 1 + volumeMounts: + - mountPath: mountPathValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + message: messageValue + nominatedNodeName: nominatedNodeNameValue + observedGeneration: 17 + phase: phaseValue + podIP: podIPValue + podIPs: + - ip: ipValue + qosClass: qosClassValue + reason: reasonValue + resize: resizeValue + resourceClaimStatuses: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + startTime: "2007-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.json new file mode 100644 index 0000000000000..a79675cbe7aad --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.json @@ -0,0 +1,1837 @@ +{ + "kind": "PodTemplate", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.pb new file mode 100644 index 0000000000000..a6ebc08bfda6d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.yaml new file mode 100644 index 0000000000000..0a08c4c915075 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.after_roundtrip.yaml @@ -0,0 +1,1260 @@ +apiVersion: v1 +kind: PodTemplate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.json new file mode 100644 index 0000000000000..52e8ad2c7d6d6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.json @@ -0,0 +1,1842 @@ +{ + "kind": "PodTemplate", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.pb new file mode 100644 index 0000000000000..c8a6030194004 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.yaml new file mode 100644 index 0000000000000..4080ec597c8b8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.PodTemplate.yaml @@ -0,0 +1,1264 @@ +apiVersion: v1 +kind: PodTemplate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.json new file mode 100644 index 0000000000000..da56554374fb9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.json @@ -0,0 +1,48 @@ +{ + "kind": "RangeAllocation", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "range": "rangeValue", + "data": "Aw==" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.pb new file mode 100644 index 0000000000000..ebcaef091a975 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.yaml new file mode 100644 index 0000000000000..845df3e0d0d96 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.RangeAllocation.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +data: Aw== +kind: RangeAllocation +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +range: rangeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.json new file mode 100644 index 0000000000000..6ed964059b23c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.json @@ -0,0 +1,1860 @@ +{ + "kind": "ReplicationController", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "selectorKey": "selectorValue" + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.pb new file mode 100644 index 0000000000000..5c86d0acb3d4f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.yaml new file mode 100644 index 0000000000000..e38aeca75fe2e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.after_roundtrip.yaml @@ -0,0 +1,1277 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + selectorKey: selectorValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.json new file mode 100644 index 0000000000000..f4db5d62148a6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.json @@ -0,0 +1,1865 @@ +{ + "kind": "ReplicationController", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "selectorKey": "selectorValue" + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.pb new file mode 100644 index 0000000000000..799572d89be5d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.yaml new file mode 100644 index 0000000000000..7bee55311ba73 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ReplicationController.yaml @@ -0,0 +1,1281 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + selectorKey: selectorValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.json new file mode 100644 index 0000000000000..7dca13cebc31f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.json @@ -0,0 +1,73 @@ +{ + "kind": "ResourceQuota", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "hard": { + "hardKey": "0" + }, + "scopes": [ + "scopesValue" + ], + "scopeSelector": { + "matchExpressions": [ + { + "scopeName": "scopeNameValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + }, + "status": { + "hard": { + "hardKey": "0" + }, + "used": { + "usedKey": "0" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.pb new file mode 100644 index 0000000000000..200c4e27d658c Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.yaml new file mode 100644 index 0000000000000..d7400f084760c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ResourceQuota.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + hard: + hardKey: "0" + scopeSelector: + matchExpressions: + - operator: operatorValue + scopeName: scopeNameValue + values: + - valuesValue + scopes: + - scopesValue +status: + hard: + hardKey: "0" + used: + usedKey: "0" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.json new file mode 100644 index 0000000000000..3370980c79b0f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.json @@ -0,0 +1,54 @@ +{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "immutable": true, + "data": { + "dataKey": "Ag==" + }, + "stringData": { + "stringDataKey": "stringDataValue" + }, + "type": "typeValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.pb new file mode 100644 index 0000000000000..ca841c63ff5e4 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.yaml new file mode 100644 index 0000000000000..71e1f3f80b06f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Secret.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +data: + dataKey: Ag== +immutable: true +kind: Secret +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +stringData: + stringDataKey: stringDataValue +type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.json new file mode 100644 index 0000000000000..2113db689a052 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.json @@ -0,0 +1,13 @@ +{ + "kind": "SerializedReference", + "apiVersion": "v1", + "reference": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.pb new file mode 100644 index 0000000000000..a6155965e9551 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.yaml new file mode 100644 index 0000000000000..9b2d0c07d4e62 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.SerializedReference.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: SerializedReference +reference: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.json new file mode 100644 index 0000000000000..978887a307b6f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.json @@ -0,0 +1,119 @@ +{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "ports": [ + { + "name": "nameValue", + "protocol": "protocolValue", + "appProtocol": "appProtocolValue", + "port": 3, + "targetPort": "targetPortValue", + "nodePort": 5 + } + ], + "selector": { + "selectorKey": "selectorValue" + }, + "clusterIP": "clusterIPValue", + "clusterIPs": [ + "clusterIPsValue" + ], + "type": "typeValue", + "externalIPs": [ + "externalIPsValue" + ], + "sessionAffinity": "sessionAffinityValue", + "loadBalancerIP": "loadBalancerIPValue", + "loadBalancerSourceRanges": [ + "loadBalancerSourceRangesValue" + ], + "externalName": "externalNameValue", + "externalTrafficPolicy": "externalTrafficPolicyValue", + "healthCheckNodePort": 12, + "publishNotReadyAddresses": true, + "sessionAffinityConfig": { + "clientIP": { + "timeoutSeconds": 1 + } + }, + "ipFamilies": [ + "ipFamiliesValue" + ], + "ipFamilyPolicy": "ipFamilyPolicyValue", + "allocateLoadBalancerNodePorts": true, + "loadBalancerClass": "loadBalancerClassValue", + "internalTrafficPolicy": "internalTrafficPolicyValue", + "trafficDistribution": "trafficDistributionValue" + }, + "status": { + "loadBalancer": { + "ingress": [ + { + "ip": "ipValue", + "hostname": "hostnameValue", + "ipMode": "ipModeValue", + "ports": [ + { + "port": 1, + "protocol": "protocolValue", + "error": "errorValue" + } + ] + } + ] + }, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.pb new file mode 100644 index 0000000000000..e70163900c392 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.yaml new file mode 100644 index 0000000000000..cde30006265ce --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Service.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + allocateLoadBalancerNodePorts: true + clusterIP: clusterIPValue + clusterIPs: + - clusterIPsValue + externalIPs: + - externalIPsValue + externalName: externalNameValue + externalTrafficPolicy: externalTrafficPolicyValue + healthCheckNodePort: 12 + internalTrafficPolicy: internalTrafficPolicyValue + ipFamilies: + - ipFamiliesValue + ipFamilyPolicy: ipFamilyPolicyValue + loadBalancerClass: loadBalancerClassValue + loadBalancerIP: loadBalancerIPValue + loadBalancerSourceRanges: + - loadBalancerSourceRangesValue + ports: + - appProtocol: appProtocolValue + name: nameValue + nodePort: 5 + port: 3 + protocol: protocolValue + targetPort: targetPortValue + publishNotReadyAddresses: true + selector: + selectorKey: selectorValue + sessionAffinity: sessionAffinityValue + sessionAffinityConfig: + clientIP: + timeoutSeconds: 1 + trafficDistribution: trafficDistributionValue + type: typeValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + loadBalancer: + ingress: + - hostname: hostnameValue + ip: ipValue + ipMode: ipModeValue + ports: + - error: errorValue + port: 1 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.json new file mode 100644 index 0000000000000..540385147c0c7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.json @@ -0,0 +1,63 @@ +{ + "kind": "ServiceAccount", + "apiVersion": "v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "secrets": [ + { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + ], + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "automountServiceAccountToken": true +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.pb new file mode 100644 index 0000000000000..9e3d69cf63a1d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.yaml new file mode 100644 index 0000000000000..876293f9d68cc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceAccount.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +automountServiceAccountToken: true +imagePullSecrets: +- name: nameValue +kind: ServiceAccount +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +secrets: +- apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.json new file mode 100644 index 0000000000000..358429d4429f8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.json @@ -0,0 +1,5 @@ +{ + "kind": "ServiceProxyOptions", + "apiVersion": "v1", + "path": "pathValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.pb new file mode 100644 index 0000000000000..26b157fb32d98 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.yaml new file mode 100644 index 0000000000000..fadf55212e658 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.ServiceProxyOptions.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +kind: ServiceProxyOptions +path: pathValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.json new file mode 100644 index 0000000000000..075b2b50704a7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.json @@ -0,0 +1,28 @@ +{ + "kind": "Status", + "apiVersion": "v1", + "metadata": { + "selfLink": "selfLinkValue", + "resourceVersion": "resourceVersionValue", + "continue": "continueValue", + "remainingItemCount": 4 + }, + "status": "statusValue", + "message": "messageValue", + "reason": "reasonValue", + "details": { + "name": "nameValue", + "group": "groupValue", + "kind": "kindValue", + "uid": "uidValue", + "causes": [ + { + "reason": "reasonValue", + "message": "messageValue", + "field": "fieldValue" + } + ], + "retryAfterSeconds": 5 + }, + "code": 6 +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.pb new file mode 100644 index 0000000000000..a9e1126abcbd2 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.yaml new file mode 100644 index 0000000000000..6fa05d9a6d3bf --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.Status.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +code: 6 +details: + causes: + - field: fieldValue + message: messageValue + reason: reasonValue + group: groupValue + kind: kindValue + name: nameValue + retryAfterSeconds: 5 + uid: uidValue +kind: Status +message: messageValue +metadata: + continue: continueValue + remainingItemCount: 4 + resourceVersion: resourceVersionValue + selfLink: selfLinkValue +reason: reasonValue +status: statusValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.json new file mode 100644 index 0000000000000..3cf86270714ac --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.json @@ -0,0 +1,9 @@ +{ + "kind": "UpdateOptions", + "apiVersion": "v1", + "dryRun": [ + "dryRunValue" + ], + "fieldManager": "fieldManagerValue", + "fieldValidation": "fieldValidationValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.pb new file mode 100644 index 0000000000000..0534a05eeec99 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.yaml new file mode 100644 index 0000000000000..1d2d9943eff07 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.UpdateOptions.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +dryRun: +- dryRunValue +fieldManager: fieldManagerValue +fieldValidation: fieldValidationValue +kind: UpdateOptions diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.json b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.json new file mode 100644 index 0000000000000..64a45ac66b337 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.json @@ -0,0 +1,13 @@ +{ + "type": "typeValue", + "object": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.pb b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.pb new file mode 100644 index 0000000000000..6d7c78307c4ae Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.yaml new file mode 100644 index 0000000000000..6b24f40117a7e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/core.v1.WatchEvent.yaml @@ -0,0 +1,8 @@ +object: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 +type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.json b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.json new file mode 100644 index 0000000000000..640c6842a9df1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.json @@ -0,0 +1,94 @@ +{ + "kind": "EndpointSlice", + "apiVersion": "discovery.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "addressType": "addressTypeValue", + "endpoints": [ + { + "addresses": [ + "addressesValue" + ], + "conditions": { + "ready": true, + "serving": true, + "terminating": true + }, + "hostname": "hostnameValue", + "targetRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "deprecatedTopology": { + "deprecatedTopologyKey": "deprecatedTopologyValue" + }, + "nodeName": "nodeNameValue", + "zone": "zoneValue", + "hints": { + "forZones": [ + { + "name": "nameValue" + } + ], + "forNodes": [ + { + "name": "nameValue" + } + ] + } + } + ], + "ports": [ + { + "name": "nameValue", + "protocol": "protocolValue", + "port": 3, + "appProtocol": "appProtocolValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.pb b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.pb new file mode 100644 index 0000000000000..a4ff78a321415 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.yaml new file mode 100644 index 0000000000000..db95f61cad5a6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1.EndpointSlice.yaml @@ -0,0 +1,65 @@ +addressType: addressTypeValue +apiVersion: discovery.k8s.io/v1 +endpoints: +- addresses: + - addressesValue + conditions: + ready: true + serving: true + terminating: true + deprecatedTopology: + deprecatedTopologyKey: deprecatedTopologyValue + hints: + forNodes: + - name: nameValue + forZones: + - name: nameValue + hostname: hostnameValue + nodeName: nodeNameValue + targetRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + zone: zoneValue +kind: EndpointSlice +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +ports: +- appProtocol: appProtocolValue + name: nameValue + port: 3 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.json b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.json new file mode 100644 index 0000000000000..87dac1aa951d6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.json @@ -0,0 +1,93 @@ +{ + "kind": "EndpointSlice", + "apiVersion": "discovery.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "addressType": "addressTypeValue", + "endpoints": [ + { + "addresses": [ + "addressesValue" + ], + "conditions": { + "ready": true, + "serving": true, + "terminating": true + }, + "hostname": "hostnameValue", + "targetRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "topology": { + "topologyKey": "topologyValue" + }, + "nodeName": "nodeNameValue", + "hints": { + "forZones": [ + { + "name": "nameValue" + } + ], + "forNodes": [ + { + "name": "nameValue" + } + ] + } + } + ], + "ports": [ + { + "name": "nameValue", + "protocol": "protocolValue", + "port": 3, + "appProtocol": "appProtocolValue" + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.pb b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.pb new file mode 100644 index 0000000000000..0bb8fa81af94f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.yaml new file mode 100644 index 0000000000000..f5901ad8e4811 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/discovery.k8s.io.v1beta1.EndpointSlice.yaml @@ -0,0 +1,64 @@ +addressType: addressTypeValue +apiVersion: discovery.k8s.io/v1beta1 +endpoints: +- addresses: + - addressesValue + conditions: + ready: true + serving: true + terminating: true + hints: + forNodes: + - name: nameValue + forZones: + - name: nameValue + hostname: hostnameValue + nodeName: nodeNameValue + targetRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + topology: + topologyKey: topologyValue +kind: EndpointSlice +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +ports: +- appProtocol: appProtocolValue + name: nameValue + port: 3 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.json b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.json new file mode 100644 index 0000000000000..e7bb7a4c067fb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.json @@ -0,0 +1,82 @@ +{ + "kind": "Event", + "apiVersion": "events.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "eventTime": "2002-01-01T01:01:01.000002Z", + "series": { + "count": 1, + "lastObservedTime": "2002-01-01T01:01:01.000002Z" + }, + "reportingController": "reportingControllerValue", + "reportingInstance": "reportingInstanceValue", + "action": "actionValue", + "reason": "reasonValue", + "regarding": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "related": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "note": "noteValue", + "type": "typeValue", + "deprecatedSource": { + "component": "componentValue", + "host": "hostValue" + }, + "deprecatedFirstTimestamp": "2013-01-01T01:01:01Z", + "deprecatedLastTimestamp": "2014-01-01T01:01:01Z", + "deprecatedCount": 15 +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.pb b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.pb new file mode 100644 index 0000000000000..9ea014ffe5324 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.yaml new file mode 100644 index 0000000000000..a3e4cf56176e0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1.Event.yaml @@ -0,0 +1,66 @@ +action: actionValue +apiVersion: events.k8s.io/v1 +deprecatedCount: 15 +deprecatedFirstTimestamp: "2013-01-01T01:01:01Z" +deprecatedLastTimestamp: "2014-01-01T01:01:01Z" +deprecatedSource: + component: componentValue + host: hostValue +eventTime: "2002-01-01T01:01:01.000002Z" +kind: Event +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +note: noteValue +reason: reasonValue +regarding: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue +related: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue +reportingController: reportingControllerValue +reportingInstance: reportingInstanceValue +series: + count: 1 + lastObservedTime: "2002-01-01T01:01:01.000002Z" +type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.json b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.json new file mode 100644 index 0000000000000..a4659adf84a19 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.json @@ -0,0 +1,82 @@ +{ + "kind": "Event", + "apiVersion": "events.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "eventTime": "2002-01-01T01:01:01.000002Z", + "series": { + "count": 1, + "lastObservedTime": "2002-01-01T01:01:01.000002Z" + }, + "reportingController": "reportingControllerValue", + "reportingInstance": "reportingInstanceValue", + "action": "actionValue", + "reason": "reasonValue", + "regarding": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "related": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "note": "noteValue", + "type": "typeValue", + "deprecatedSource": { + "component": "componentValue", + "host": "hostValue" + }, + "deprecatedFirstTimestamp": "2013-01-01T01:01:01Z", + "deprecatedLastTimestamp": "2014-01-01T01:01:01Z", + "deprecatedCount": 15 +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.pb b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.pb new file mode 100644 index 0000000000000..fa18d6ad27527 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.yaml new file mode 100644 index 0000000000000..0ca757e9c6c0f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/events.k8s.io.v1beta1.Event.yaml @@ -0,0 +1,66 @@ +action: actionValue +apiVersion: events.k8s.io/v1beta1 +deprecatedCount: 15 +deprecatedFirstTimestamp: "2013-01-01T01:01:01Z" +deprecatedLastTimestamp: "2014-01-01T01:01:01Z" +deprecatedSource: + component: componentValue + host: hostValue +eventTime: "2002-01-01T01:01:01.000002Z" +kind: Event +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +note: noteValue +reason: reasonValue +regarding: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue +related: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue +reportingController: reportingControllerValue +reportingInstance: reportingInstanceValue +series: + count: 1 + lastObservedTime: "2002-01-01T01:01:01.000002Z" +type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.json new file mode 100644 index 0000000000000..2199956e9fefa --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.json @@ -0,0 +1,1883 @@ +{ + "kind": "DaemonSet", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 4, + "templateGeneration": 5, + "revisionHistoryLimit": 6 + }, + "status": { + "currentNumberScheduled": 1, + "numberMisscheduled": 2, + "desiredNumberScheduled": 3, + "numberReady": 4, + "observedGeneration": 5, + "updatedNumberScheduled": 6, + "numberAvailable": 7, + "numberUnavailable": 8, + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.pb new file mode 100644 index 0000000000000..da9be5df6b90e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..67a9b895ab0d1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.after_roundtrip.yaml @@ -0,0 +1,1293 @@ +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + templateGeneration: 5 + updateStrategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue +status: + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentNumberScheduled: 1 + desiredNumberScheduled: 3 + numberAvailable: 7 + numberMisscheduled: 2 + numberReady: 4 + numberUnavailable: 8 + observedGeneration: 5 + updatedNumberScheduled: 6 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.json new file mode 100644 index 0000000000000..dbde12274e6af --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.json @@ -0,0 +1,1888 @@ +{ + "kind": "DaemonSet", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "updateStrategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 4, + "templateGeneration": 5, + "revisionHistoryLimit": 6 + }, + "status": { + "currentNumberScheduled": 1, + "numberMisscheduled": 2, + "desiredNumberScheduled": 3, + "numberReady": 4, + "observedGeneration": 5, + "updatedNumberScheduled": 6, + "numberAvailable": 7, + "numberUnavailable": 8, + "collisionCount": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.pb new file mode 100644 index 0000000000000..161b76999178e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.yaml new file mode 100644 index 0000000000000..6512376d9748c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DaemonSet.yaml @@ -0,0 +1,1297 @@ +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + revisionHistoryLimit: 6 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue + templateGeneration: 5 + updateStrategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue +status: + collisionCount: 9 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + currentNumberScheduled: 1 + desiredNumberScheduled: 3 + numberAvailable: 7 + numberMisscheduled: 2 + numberReady: 4 + numberUnavailable: 8 + observedGeneration: 5 + updatedNumberScheduled: 6 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.json new file mode 100644 index 0000000000000..661f20f9fb12b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.json @@ -0,0 +1,1888 @@ +{ + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "rollbackTo": { + "revision": 1 + }, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.pb new file mode 100644 index 0000000000000..80067dff3ade9 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.yaml new file mode 100644 index 0000000000000..d753c0d9d4d17 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.after_roundtrip.yaml @@ -0,0 +1,1297 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + rollbackTo: + revision: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.json new file mode 100644 index 0000000000000..be0cd57a8cf09 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.json @@ -0,0 +1,1893 @@ +{ + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + }, + "strategy": { + "type": "typeValue", + "rollingUpdate": { + "maxUnavailable": "maxUnavailableValue", + "maxSurge": "maxSurgeValue" + } + }, + "minReadySeconds": 5, + "revisionHistoryLimit": 6, + "paused": true, + "rollbackTo": { + "revision": 1 + }, + "progressDeadlineSeconds": 9 + }, + "status": { + "observedGeneration": 1, + "replicas": 2, + "updatedReplicas": 3, + "readyReplicas": 7, + "availableReplicas": 4, + "unavailableReplicas": 5, + "terminatingReplicas": 9, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastUpdateTime": "2006-01-01T01:01:01Z", + "lastTransitionTime": "2007-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "collisionCount": 8 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.pb new file mode 100644 index 0000000000000..c2c612c2efb0e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.yaml new file mode 100644 index 0000000000000..b47e20398aebb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Deployment.yaml @@ -0,0 +1,1301 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 5 + paused: true + progressDeadlineSeconds: 9 + replicas: 1 + revisionHistoryLimit: 6 + rollbackTo: + revision: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + strategy: + rollingUpdate: + maxSurge: maxSurgeValue + maxUnavailable: maxUnavailableValue + type: typeValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 4 + collisionCount: 8 + conditions: + - lastTransitionTime: "2007-01-01T01:01:01Z" + lastUpdateTime: "2006-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + observedGeneration: 1 + readyReplicas: 7 + replicas: 2 + terminatingReplicas: 9 + unavailableReplicas: 5 + updatedReplicas: 3 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.json new file mode 100644 index 0000000000000..c31203a8899d7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.json @@ -0,0 +1,11 @@ +{ + "kind": "DeploymentRollback", + "apiVersion": "extensions/v1beta1", + "name": "nameValue", + "updatedAnnotations": { + "updatedAnnotationsKey": "updatedAnnotationsValue" + }, + "rollbackTo": { + "revision": 1 + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.pb new file mode 100644 index 0000000000000..036b003748eb3 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.yaml new file mode 100644 index 0000000000000..67ed04692a00d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.DeploymentRollback.yaml @@ -0,0 +1,7 @@ +apiVersion: extensions/v1beta1 +kind: DeploymentRollback +name: nameValue +rollbackTo: + revision: 1 +updatedAnnotations: + updatedAnnotationsKey: updatedAnnotationsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.json new file mode 100644 index 0000000000000..aeb03519515c1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.json @@ -0,0 +1,105 @@ +{ + "kind": "Ingress", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "ingressClassName": "ingressClassNameValue", + "backend": { + "serviceName": "serviceNameValue", + "servicePort": "servicePortValue", + "resource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } + }, + "tls": [ + { + "hosts": [ + "hostsValue" + ], + "secretName": "secretNameValue" + } + ], + "rules": [ + { + "host": "hostValue", + "http": { + "paths": [ + { + "path": "pathValue", + "pathType": "pathTypeValue", + "backend": { + "serviceName": "serviceNameValue", + "servicePort": "servicePortValue", + "resource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": { + "ingress": [ + { + "ip": "ipValue", + "hostname": "hostnameValue", + "ports": [ + { + "port": 1, + "protocol": "protocolValue", + "error": "errorValue" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.pb new file mode 100644 index 0000000000000..d1203f1177d4b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.yaml new file mode 100644 index 0000000000000..13cedd0d4cc4c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Ingress.yaml @@ -0,0 +1,69 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + backend: + resource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + serviceName: serviceNameValue + servicePort: servicePortValue + ingressClassName: ingressClassNameValue + rules: + - host: hostValue + http: + paths: + - backend: + resource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + serviceName: serviceNameValue + servicePort: servicePortValue + path: pathValue + pathType: pathTypeValue + tls: + - hosts: + - hostsValue + secretName: secretNameValue +status: + loadBalancer: + ingress: + - hostname: hostnameValue + ip: ipValue + ports: + - error: errorValue + port: 1 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.json new file mode 100644 index 0000000000000..41d542a9bbddc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.json @@ -0,0 +1,163 @@ +{ + "kind": "NetworkPolicy", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "podSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "ingress": [ + { + "ports": [ + { + "protocol": "protocolValue", + "port": "portValue", + "endPort": 3 + } + ], + "from": [ + { + "podSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "ipBlock": { + "cidr": "cidrValue", + "except": [ + "exceptValue" + ] + } + } + ] + } + ], + "egress": [ + { + "ports": [ + { + "protocol": "protocolValue", + "port": "portValue", + "endPort": 3 + } + ], + "to": [ + { + "podSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "ipBlock": { + "cidr": "cidrValue", + "except": [ + "exceptValue" + ] + } + } + ] + } + ], + "policyTypes": [ + "policyTypesValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.pb new file mode 100644 index 0000000000000..4c7ce5ac9cbe0 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.yaml new file mode 100644 index 0000000000000..176cbe3313c72 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.NetworkPolicy.yaml @@ -0,0 +1,97 @@ +apiVersion: extensions/v1beta1 +kind: NetworkPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + egress: + - ports: + - endPort: 3 + port: portValue + protocol: protocolValue + to: + - ipBlock: + cidr: cidrValue + except: + - exceptValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + podSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + ingress: + - from: + - ipBlock: + cidr: cidrValue + except: + - exceptValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + podSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + ports: + - endPort: 3 + port: portValue + protocol: protocolValue + podSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyTypes: + - policyTypesValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.json new file mode 100644 index 0000000000000..d1cbc39ce4802 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.json @@ -0,0 +1,1872 @@ +{ + "kind": "ReplicaSet", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue" + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "terminatingReplicas": 7, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.pb new file mode 100644 index 0000000000000..c013521ae4c6d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.yaml new file mode 100644 index 0000000000000..9928b3e47c6c6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.after_roundtrip.yaml @@ -0,0 +1,1284 @@ +apiVersion: extensions/v1beta1 +kind: ReplicaSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 + terminatingReplicas: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.json new file mode 100644 index 0000000000000..760674be7cd34 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.json @@ -0,0 +1,1877 @@ +{ + "kind": "ReplicaSet", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1, + "minReadySeconds": 4, + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "template": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "nameValue", + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "emptyDir": { + "medium": "mediumValue", + "sizeLimit": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "gitRepo": { + "repository": "repositoryValue", + "revision": "revisionValue", + "directory": "directoryValue" + }, + "secret": { + "secretName": "secretNameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue" + }, + "initiatorName": "initiatorNameValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true + }, + "persistentVolumeClaim": { + "claimName": "claimNameValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue" + }, + "readOnly": true + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ], + "defaultMode": 2 + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "defaultMode": 3, + "optional": true + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "projected": { + "sources": [ + { + "secret": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "downwardAPI": { + "items": [ + { + "path": "pathValue", + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "mode": 4 + } + ] + }, + "configMap": { + "name": "nameValue", + "items": [ + { + "key": "keyValue", + "path": "pathValue", + "mode": 3 + } + ], + "optional": true + }, + "serviceAccountToken": { + "audience": "audienceValue", + "expirationSeconds": 2, + "path": "pathValue" + }, + "clusterTrustBundle": { + "name": "nameValue", + "signerName": "signerNameValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "optional": true, + "path": "pathValue" + }, + "podCertificate": { + "signerName": "signerNameValue", + "keyType": "keyTypeValue", + "maxExpirationSeconds": 3, + "credentialBundlePath": "credentialBundlePathValue", + "keyPath": "keyPathValue", + "certificateChainPath": "certificateChainPathValue", + "userAnnotations": { + "userAnnotationsKey": "userAnnotationsValue" + } + } + } + ], + "defaultMode": 2 + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue" + } + }, + "csi": { + "driver": "driverValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "nodePublishSecretRef": { + "name": "nameValue" + } + }, + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "accessModes": [ + "accessModesValue" + ], + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + } + }, + "volumeName": "volumeNameValue", + "storageClassName": "storageClassNameValue", + "volumeMode": "volumeModeValue", + "dataSource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + }, + "dataSourceRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "namespace": "namespaceValue" + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + } + }, + "image": { + "reference": "referenceValue", + "pullPolicy": "pullPolicyValue" + } + } + ], + "initContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "containers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true + } + ], + "ephemeralContainers": [ + { + "name": "nameValue", + "image": "imageValue", + "command": [ + "commandValue" + ], + "args": [ + "argsValue" + ], + "workingDir": "workingDirValue", + "ports": [ + { + "name": "nameValue", + "hostPort": 2, + "containerPort": 3, + "protocol": "protocolValue", + "hostIP": "hostIPValue" + } + ], + "envFrom": [ + { + "prefix": "prefixValue", + "configMapRef": { + "name": "nameValue", + "optional": true + }, + "secretRef": { + "name": "nameValue", + "optional": true + } + } + ], + "env": [ + { + "name": "nameValue", + "value": "valueValue", + "valueFrom": { + "fieldRef": { + "apiVersion": "apiVersionValue", + "fieldPath": "fieldPathValue" + }, + "resourceFieldRef": { + "containerName": "containerNameValue", + "resource": "resourceValue", + "divisor": "0" + }, + "configMapKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "secretKeyRef": { + "name": "nameValue", + "key": "keyValue", + "optional": true + }, + "fileKeyRef": { + "volumeName": "volumeNameValue", + "path": "pathValue", + "key": "keyValue", + "optional": true + } + } + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "resizePolicy": [ + { + "resourceName": "resourceNameValue", + "restartPolicy": "restartPolicyValue" + } + ], + "restartPolicy": "restartPolicyValue", + "restartPolicyRules": [ + { + "action": "actionValue", + "exitCodes": { + "operator": "operatorValue", + "values": [ + 2 + ] + } + } + ], + "volumeMounts": [ + { + "name": "nameValue", + "readOnly": true, + "recursiveReadOnly": "recursiveReadOnlyValue", + "mountPath": "mountPathValue", + "subPath": "subPathValue", + "mountPropagation": "mountPropagationValue", + "subPathExpr": "subPathExprValue" + } + ], + "volumeDevices": [ + { + "name": "nameValue", + "devicePath": "devicePathValue" + } + ], + "livenessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "readinessProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "startupProbe": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "grpc": { + "port": 1, + "service": "serviceValue" + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 3, + "periodSeconds": 4, + "successThreshold": 5, + "failureThreshold": 6, + "terminationGracePeriodSeconds": 7 + }, + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "preStop": { + "exec": { + "command": [ + "commandValue" + ] + }, + "httpGet": { + "path": "pathValue", + "port": "portValue", + "host": "hostValue", + "scheme": "schemeValue", + "httpHeaders": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "tcpSocket": { + "port": "portValue", + "host": "hostValue" + }, + "sleep": { + "seconds": 1 + } + }, + "stopSignal": "stopSignalValue" + }, + "terminationMessagePath": "terminationMessagePathValue", + "terminationMessagePolicy": "terminationMessagePolicyValue", + "imagePullPolicy": "imagePullPolicyValue", + "securityContext": { + "capabilities": { + "add": [ + "addValue" + ], + "drop": [ + "dropValue" + ] + }, + "privileged": true, + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 4, + "runAsGroup": 8, + "runAsNonRoot": true, + "readOnlyRootFilesystem": true, + "allowPrivilegeEscalation": true, + "procMount": "procMountValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + } + }, + "stdin": true, + "stdinOnce": true, + "tty": true, + "targetContainerName": "targetContainerNameValue" + } + ], + "restartPolicy": "restartPolicyValue", + "terminationGracePeriodSeconds": 4, + "activeDeadlineSeconds": 5, + "dnsPolicy": "dnsPolicyValue", + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "serviceAccountName": "serviceAccountNameValue", + "serviceAccount": "serviceAccountValue", + "automountServiceAccountToken": true, + "nodeName": "nodeNameValue", + "hostNetwork": true, + "hostPID": true, + "hostIPC": true, + "shareProcessNamespace": true, + "securityContext": { + "seLinuxOptions": { + "user": "userValue", + "role": "roleValue", + "type": "typeValue", + "level": "levelValue" + }, + "windowsOptions": { + "gmsaCredentialSpecName": "gmsaCredentialSpecNameValue", + "gmsaCredentialSpec": "gmsaCredentialSpecValue", + "runAsUserName": "runAsUserNameValue", + "hostProcess": true + }, + "runAsUser": 2, + "runAsGroup": 6, + "runAsNonRoot": true, + "supplementalGroups": [ + 4 + ], + "supplementalGroupsPolicy": "supplementalGroupsPolicyValue", + "fsGroup": 5, + "sysctls": [ + { + "name": "nameValue", + "value": "valueValue" + } + ], + "fsGroupChangePolicy": "fsGroupChangePolicyValue", + "seccompProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "appArmorProfile": { + "type": "typeValue", + "localhostProfile": "localhostProfileValue" + }, + "seLinuxChangePolicy": "seLinuxChangePolicyValue" + }, + "imagePullSecrets": [ + { + "name": "nameValue" + } + ], + "hostname": "hostnameValue", + "subdomain": "subdomainValue", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "preference": { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + }, + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + ], + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 1, + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaces": [ + "namespacesValue" + ], + "topologyKey": "topologyKeyValue", + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "matchLabelKeys": [ + "matchLabelKeysValue" + ], + "mismatchLabelKeys": [ + "mismatchLabelKeysValue" + ] + } + } + ] + } + }, + "schedulerName": "schedulerNameValue", + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "hostAliases": [ + { + "ip": "ipValue", + "hostnames": [ + "hostnamesValue" + ] + } + ], + "priorityClassName": "priorityClassNameValue", + "priority": 25, + "dnsConfig": { + "nameservers": [ + "nameserversValue" + ], + "searches": [ + "searchesValue" + ], + "options": [ + { + "name": "nameValue", + "value": "valueValue" + } + ] + }, + "readinessGates": [ + { + "conditionType": "conditionTypeValue" + } + ], + "runtimeClassName": "runtimeClassNameValue", + "enableServiceLinks": true, + "preemptionPolicy": "preemptionPolicyValue", + "overhead": { + "overheadKey": "0" + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "topologyKeyValue", + "whenUnsatisfiable": "whenUnsatisfiableValue", + "labelSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "minDomains": 5, + "nodeAffinityPolicy": "nodeAffinityPolicyValue", + "nodeTaintsPolicy": "nodeTaintsPolicyValue", + "matchLabelKeys": [ + "matchLabelKeysValue" + ] + } + ], + "setHostnameAsFQDN": true, + "os": { + "name": "nameValue" + }, + "hostUsers": true, + "schedulingGates": [ + { + "name": "nameValue" + } + ], + "resourceClaims": [ + { + "name": "nameValue", + "resourceClaimName": "resourceClaimNameValue", + "resourceClaimTemplateName": "resourceClaimTemplateNameValue" + } + ], + "resources": { + "limits": { + "limitsKey": "0" + }, + "requests": { + "requestsKey": "0" + }, + "claims": [ + { + "name": "nameValue", + "request": "requestValue" + } + ] + }, + "hostnameOverride": "hostnameOverrideValue", + "workloadRef": { + "name": "nameValue", + "podGroup": "podGroupValue", + "podGroupReplicaKey": "podGroupReplicaKeyValue" + } + } + } + }, + "status": { + "replicas": 1, + "fullyLabeledReplicas": 2, + "readyReplicas": 4, + "availableReplicas": 5, + "terminatingReplicas": 7, + "observedGeneration": 3, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.pb new file mode 100644 index 0000000000000..b61e355108b7b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.yaml new file mode 100644 index 0000000000000..d983d21a3fbb1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.ReplicaSet.yaml @@ -0,0 +1,1288 @@ +apiVersion: extensions/v1beta1 +kind: ReplicaSet +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + minReadySeconds: 4 + replicas: 1 + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + template: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + activeDeadlineSeconds: 5 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + weight: 1 + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + mismatchLabelKeys: + - mismatchLabelKeysValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + namespaces: + - namespacesValue + topologyKey: topologyKeyValue + automountServiceAccountToken: true + containers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + dnsConfig: + nameservers: + - nameserversValue + options: + - name: nameValue + value: valueValue + searches: + - searchesValue + dnsPolicy: dnsPolicyValue + enableServiceLinks: true + ephemeralContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + targetContainerName: targetContainerNameValue + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + hostAliases: + - hostnames: + - hostnamesValue + ip: ipValue + hostIPC: true + hostNetwork: true + hostPID: true + hostUsers: true + hostname: hostnameValue + hostnameOverride: hostnameOverrideValue + imagePullSecrets: + - name: nameValue + initContainers: + - args: + - argsValue + command: + - commandValue + env: + - name: nameValue + value: valueValue + valueFrom: + configMapKeyRef: + key: keyValue + name: nameValue + optional: true + fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + fileKeyRef: + key: keyValue + optional: true + path: pathValue + volumeName: volumeNameValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + secretKeyRef: + key: keyValue + name: nameValue + optional: true + envFrom: + - configMapRef: + name: nameValue + optional: true + prefix: prefixValue + secretRef: + name: nameValue + optional: true + image: imageValue + imagePullPolicy: imagePullPolicyValue + lifecycle: + postStart: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + preStop: + exec: + command: + - commandValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + sleep: + seconds: 1 + tcpSocket: + host: hostValue + port: portValue + stopSignal: stopSignalValue + livenessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + name: nameValue + ports: + - containerPort: 3 + hostIP: hostIPValue + hostPort: 2 + name: nameValue + protocol: protocolValue + readinessProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + resizePolicy: + - resourceName: resourceNameValue + restartPolicy: restartPolicyValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + restartPolicyRules: + - action: actionValue + exitCodes: + operator: operatorValue + values: + - 2 + securityContext: + allowPrivilegeEscalation: true + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + capabilities: + add: + - addValue + drop: + - dropValue + privileged: true + procMount: procMountValue + readOnlyRootFilesystem: true + runAsGroup: 8 + runAsNonRoot: true + runAsUser: 4 + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + startupProbe: + exec: + command: + - commandValue + failureThreshold: 6 + grpc: + port: 1 + service: serviceValue + httpGet: + host: hostValue + httpHeaders: + - name: nameValue + value: valueValue + path: pathValue + port: portValue + scheme: schemeValue + initialDelaySeconds: 2 + periodSeconds: 4 + successThreshold: 5 + tcpSocket: + host: hostValue + port: portValue + terminationGracePeriodSeconds: 7 + timeoutSeconds: 3 + stdin: true + stdinOnce: true + terminationMessagePath: terminationMessagePathValue + terminationMessagePolicy: terminationMessagePolicyValue + tty: true + volumeDevices: + - devicePath: devicePathValue + name: nameValue + volumeMounts: + - mountPath: mountPathValue + mountPropagation: mountPropagationValue + name: nameValue + readOnly: true + recursiveReadOnly: recursiveReadOnlyValue + subPath: subPathValue + subPathExpr: subPathExprValue + workingDir: workingDirValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorKey: nodeSelectorValue + os: + name: nameValue + overhead: + overheadKey: "0" + preemptionPolicy: preemptionPolicyValue + priority: 25 + priorityClassName: priorityClassNameValue + readinessGates: + - conditionType: conditionTypeValue + resourceClaims: + - name: nameValue + resourceClaimName: resourceClaimNameValue + resourceClaimTemplateName: resourceClaimTemplateNameValue + resources: + claims: + - name: nameValue + request: requestValue + limits: + limitsKey: "0" + requests: + requestsKey: "0" + restartPolicy: restartPolicyValue + runtimeClassName: runtimeClassNameValue + schedulerName: schedulerNameValue + schedulingGates: + - name: nameValue + securityContext: + appArmorProfile: + localhostProfile: localhostProfileValue + type: typeValue + fsGroup: 5 + fsGroupChangePolicy: fsGroupChangePolicyValue + runAsGroup: 6 + runAsNonRoot: true + runAsUser: 2 + seLinuxChangePolicy: seLinuxChangePolicyValue + seLinuxOptions: + level: levelValue + role: roleValue + type: typeValue + user: userValue + seccompProfile: + localhostProfile: localhostProfileValue + type: typeValue + supplementalGroups: + - 4 + supplementalGroupsPolicy: supplementalGroupsPolicyValue + sysctls: + - name: nameValue + value: valueValue + windowsOptions: + gmsaCredentialSpec: gmsaCredentialSpecValue + gmsaCredentialSpecName: gmsaCredentialSpecNameValue + hostProcess: true + runAsUserName: runAsUserNameValue + serviceAccount: serviceAccountValue + serviceAccountName: serviceAccountNameValue + setHostnameAsFQDN: true + shareProcessNamespace: true + subdomain: subdomainValue + terminationGracePeriodSeconds: 4 + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + topologySpreadConstraints: + - labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + matchLabelKeys: + - matchLabelKeysValue + maxSkew: 1 + minDomains: 5 + nodeAffinityPolicy: nodeAffinityPolicyValue + nodeTaintsPolicy: nodeTaintsPolicyValue + topologyKey: topologyKeyValue + whenUnsatisfiable: whenUnsatisfiableValue + volumes: + - awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + shareName: shareNameValue + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeID: volumeIDValue + configMap: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + csi: + driver: driverValue + fsType: fsTypeValue + nodePublishSecretRef: + name: nameValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + downwardAPI: + defaultMode: 2 + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + emptyDir: + medium: mediumValue + sizeLimit: "0" + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + accessModes: + - accessModesValue + dataSource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + dataSourceRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resources: + limits: + limitsKey: "0" + requests: + requestsKey: "0" + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + storageClassName: storageClassNameValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + volumeName: volumeNameValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + gitRepo: + directory: directoryValue + repository: repositoryValue + revision: revisionValue + glusterfs: + endpoints: endpointsValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + image: + pullPolicy: pullPolicyValue + reference: referenceValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + targetPortal: targetPortalValue + name: nameValue + nfs: + path: pathValue + readOnly: true + server: serverValue + persistentVolumeClaim: + claimName: claimNameValue + readOnly: true + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + projected: + defaultMode: 2 + sources: + - clusterTrustBundle: + labelSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + name: nameValue + optional: true + path: pathValue + signerName: signerNameValue + configMap: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + downwardAPI: + items: + - fieldRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + mode: 4 + path: pathValue + resourceFieldRef: + containerName: containerNameValue + divisor: "0" + resource: resourceValue + podCertificate: + certificateChainPath: certificateChainPathValue + credentialBundlePath: credentialBundlePathValue + keyPath: keyPathValue + keyType: keyTypeValue + maxExpirationSeconds: 3 + signerName: signerNameValue + userAnnotations: + userAnnotationsKey: userAnnotationsValue + secret: + items: + - key: keyValue + mode: 3 + path: pathValue + name: nameValue + optional: true + serviceAccountToken: + audience: audienceValue + expirationSeconds: 2 + path: pathValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + secret: + defaultMode: 3 + items: + - key: keyValue + mode: 3 + path: pathValue + optional: true + secretName: secretNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + workloadRef: + name: nameValue + podGroup: podGroupValue + podGroupReplicaKey: podGroupReplicaKeyValue +status: + availableReplicas: 5 + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue + fullyLabeledReplicas: 2 + observedGeneration: 3 + readyReplicas: 4 + replicas: 1 + terminatingReplicas: 7 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.json b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.json new file mode 100644 index 0000000000000..74603e1ccb75a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.json @@ -0,0 +1,56 @@ +{ + "kind": "Scale", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "replicas": 1 + }, + "status": { + "replicas": 1, + "selector": { + "selectorKey": "selectorValue" + }, + "targetSelector": "targetSelectorValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.pb b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.pb new file mode 100644 index 0000000000000..394e86eef4d5e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.yaml new file mode 100644 index 0000000000000..4d8465601b2fc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/extensions.v1beta1.Scale.yaml @@ -0,0 +1,41 @@ +apiVersion: extensions/v1beta1 +kind: Scale +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + replicas: 1 +status: + replicas: 1 + selector: + selectorKey: selectorValue + targetSelector: targetSelectorValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.json new file mode 100644 index 0000000000000..2afc3cf008650 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.json @@ -0,0 +1,112 @@ +{ + "kind": "FlowSchema", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "priorityLevelConfiguration": { + "name": "nameValue" + }, + "matchingPrecedence": 2, + "distinguisherMethod": { + "type": "typeValue" + }, + "rules": [ + { + "subjects": [ + { + "kind": "kindValue", + "user": { + "name": "nameValue" + }, + "group": { + "name": "nameValue" + }, + "serviceAccount": { + "namespace": "namespaceValue", + "name": "nameValue" + } + } + ], + "resourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "clusterScope": true, + "namespaces": [ + "namespacesValue" + ] + } + ], + "nonResourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] + } + ] + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.pb new file mode 100644 index 0000000000000..e146421bd86ac Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.yaml new file mode 100644 index 0000000000000..b052de9d9547b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.FlowSchema.yaml @@ -0,0 +1,72 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1 +kind: FlowSchema +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + distinguisherMethod: + type: typeValue + matchingPrecedence: 2 + priorityLevelConfiguration: + name: nameValue + rules: + - nonResourceRules: + - nonResourceURLs: + - nonResourceURLsValue + verbs: + - verbsValue + resourceRules: + - apiGroups: + - apiGroupsValue + clusterScope: true + namespaces: + - namespacesValue + resources: + - resourcesValue + verbs: + - verbsValue + subjects: + - group: + name: nameValue + kind: kindValue + serviceAccount: + name: nameValue + namespace: namespaceValue + user: + name: nameValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.json new file mode 100644 index 0000000000000..43e99395b7a0a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.json @@ -0,0 +1,77 @@ +{ + "kind": "PriorityLevelConfiguration", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "type": "typeValue", + "limited": { + "nominalConcurrencyShares": 1, + "limitResponse": { + "type": "typeValue", + "queuing": { + "queues": 1, + "handSize": 2, + "queueLengthLimit": 3 + } + }, + "lendablePercent": 3, + "borrowingLimitPercent": 4 + }, + "exempt": { + "nominalConcurrencyShares": 1, + "lendablePercent": 2 + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.pb new file mode 100644 index 0000000000000..d2c4190dbe6fa Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.yaml new file mode 100644 index 0000000000000..852cb8fdf885f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1.PriorityLevelConfiguration.yaml @@ -0,0 +1,56 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1 +kind: PriorityLevelConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + exempt: + lendablePercent: 2 + nominalConcurrencyShares: 1 + limited: + borrowingLimitPercent: 4 + lendablePercent: 3 + limitResponse: + queuing: + handSize: 2 + queueLengthLimit: 3 + queues: 1 + type: typeValue + nominalConcurrencyShares: 1 + type: typeValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.json new file mode 100644 index 0000000000000..80cd266459438 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.json @@ -0,0 +1,112 @@ +{ + "kind": "FlowSchema", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "priorityLevelConfiguration": { + "name": "nameValue" + }, + "matchingPrecedence": 2, + "distinguisherMethod": { + "type": "typeValue" + }, + "rules": [ + { + "subjects": [ + { + "kind": "kindValue", + "user": { + "name": "nameValue" + }, + "group": { + "name": "nameValue" + }, + "serviceAccount": { + "namespace": "namespaceValue", + "name": "nameValue" + } + } + ], + "resourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "clusterScope": true, + "namespaces": [ + "namespacesValue" + ] + } + ], + "nonResourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] + } + ] + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.pb new file mode 100644 index 0000000000000..961f572d6b403 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.yaml new file mode 100644 index 0000000000000..c845947973288 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.FlowSchema.yaml @@ -0,0 +1,72 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1beta1 +kind: FlowSchema +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + distinguisherMethod: + type: typeValue + matchingPrecedence: 2 + priorityLevelConfiguration: + name: nameValue + rules: + - nonResourceRules: + - nonResourceURLs: + - nonResourceURLsValue + verbs: + - verbsValue + resourceRules: + - apiGroups: + - apiGroupsValue + clusterScope: true + namespaces: + - namespacesValue + resources: + - resourcesValue + verbs: + - verbsValue + subjects: + - group: + name: nameValue + kind: kindValue + serviceAccount: + name: nameValue + namespace: namespaceValue + user: + name: nameValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.json new file mode 100644 index 0000000000000..9a0c8089313ab --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.json @@ -0,0 +1,77 @@ +{ + "kind": "PriorityLevelConfiguration", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "type": "typeValue", + "limited": { + "assuredConcurrencyShares": 1, + "limitResponse": { + "type": "typeValue", + "queuing": { + "queues": 1, + "handSize": 2, + "queueLengthLimit": 3 + } + }, + "lendablePercent": 3, + "borrowingLimitPercent": 4 + }, + "exempt": { + "nominalConcurrencyShares": 1, + "lendablePercent": 2 + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.pb new file mode 100644 index 0000000000000..6a9a9a7ae5577 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.yaml new file mode 100644 index 0000000000000..f185e6dbce8ce --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta1.PriorityLevelConfiguration.yaml @@ -0,0 +1,56 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1beta1 +kind: PriorityLevelConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + exempt: + lendablePercent: 2 + nominalConcurrencyShares: 1 + limited: + assuredConcurrencyShares: 1 + borrowingLimitPercent: 4 + lendablePercent: 3 + limitResponse: + queuing: + handSize: 2 + queueLengthLimit: 3 + queues: 1 + type: typeValue + type: typeValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.json new file mode 100644 index 0000000000000..9270c20610531 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.json @@ -0,0 +1,112 @@ +{ + "kind": "FlowSchema", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "priorityLevelConfiguration": { + "name": "nameValue" + }, + "matchingPrecedence": 2, + "distinguisherMethod": { + "type": "typeValue" + }, + "rules": [ + { + "subjects": [ + { + "kind": "kindValue", + "user": { + "name": "nameValue" + }, + "group": { + "name": "nameValue" + }, + "serviceAccount": { + "namespace": "namespaceValue", + "name": "nameValue" + } + } + ], + "resourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "clusterScope": true, + "namespaces": [ + "namespacesValue" + ] + } + ], + "nonResourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] + } + ] + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.pb new file mode 100644 index 0000000000000..e62f711aebf5d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.yaml new file mode 100644 index 0000000000000..de79db8f01796 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.FlowSchema.yaml @@ -0,0 +1,72 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1beta2 +kind: FlowSchema +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + distinguisherMethod: + type: typeValue + matchingPrecedence: 2 + priorityLevelConfiguration: + name: nameValue + rules: + - nonResourceRules: + - nonResourceURLs: + - nonResourceURLsValue + verbs: + - verbsValue + resourceRules: + - apiGroups: + - apiGroupsValue + clusterScope: true + namespaces: + - namespacesValue + resources: + - resourcesValue + verbs: + - verbsValue + subjects: + - group: + name: nameValue + kind: kindValue + serviceAccount: + name: nameValue + namespace: namespaceValue + user: + name: nameValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.json new file mode 100644 index 0000000000000..1e965e3b31074 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.json @@ -0,0 +1,77 @@ +{ + "kind": "PriorityLevelConfiguration", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "type": "typeValue", + "limited": { + "assuredConcurrencyShares": 1, + "limitResponse": { + "type": "typeValue", + "queuing": { + "queues": 1, + "handSize": 2, + "queueLengthLimit": 3 + } + }, + "lendablePercent": 3, + "borrowingLimitPercent": 4 + }, + "exempt": { + "nominalConcurrencyShares": 1, + "lendablePercent": 2 + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.pb new file mode 100644 index 0000000000000..133ec29b96c01 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.yaml new file mode 100644 index 0000000000000..47c8303dd2048 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta2.PriorityLevelConfiguration.yaml @@ -0,0 +1,56 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1beta2 +kind: PriorityLevelConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + exempt: + lendablePercent: 2 + nominalConcurrencyShares: 1 + limited: + assuredConcurrencyShares: 1 + borrowingLimitPercent: 4 + lendablePercent: 3 + limitResponse: + queuing: + handSize: 2 + queueLengthLimit: 3 + queues: 1 + type: typeValue + type: typeValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.json new file mode 100644 index 0000000000000..6ccdcb3c722f2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.json @@ -0,0 +1,112 @@ +{ + "kind": "FlowSchema", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1beta3", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "priorityLevelConfiguration": { + "name": "nameValue" + }, + "matchingPrecedence": 2, + "distinguisherMethod": { + "type": "typeValue" + }, + "rules": [ + { + "subjects": [ + { + "kind": "kindValue", + "user": { + "name": "nameValue" + }, + "group": { + "name": "nameValue" + }, + "serviceAccount": { + "namespace": "namespaceValue", + "name": "nameValue" + } + } + ], + "resourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "clusterScope": true, + "namespaces": [ + "namespacesValue" + ] + } + ], + "nonResourceRules": [ + { + "verbs": [ + "verbsValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] + } + ] + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.pb new file mode 100644 index 0000000000000..29d93b140f283 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.yaml new file mode 100644 index 0000000000000..7e5c4329c0fc0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.FlowSchema.yaml @@ -0,0 +1,72 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1beta3 +kind: FlowSchema +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + distinguisherMethod: + type: typeValue + matchingPrecedence: 2 + priorityLevelConfiguration: + name: nameValue + rules: + - nonResourceRules: + - nonResourceURLs: + - nonResourceURLsValue + verbs: + - verbsValue + resourceRules: + - apiGroups: + - apiGroupsValue + clusterScope: true + namespaces: + - namespacesValue + resources: + - resourcesValue + verbs: + - verbsValue + subjects: + - group: + name: nameValue + kind: kindValue + serviceAccount: + name: nameValue + namespace: namespaceValue + user: + name: nameValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.json b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.json new file mode 100644 index 0000000000000..9bcd0b5ab255f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.json @@ -0,0 +1,77 @@ +{ + "kind": "PriorityLevelConfiguration", + "apiVersion": "flowcontrol.apiserver.k8s.io/v1beta3", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "type": "typeValue", + "limited": { + "nominalConcurrencyShares": 1, + "limitResponse": { + "type": "typeValue", + "queuing": { + "queues": 1, + "handSize": 2, + "queueLengthLimit": 3 + } + }, + "lendablePercent": 3, + "borrowingLimitPercent": 4 + }, + "exempt": { + "nominalConcurrencyShares": 1, + "lendablePercent": 2 + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "lastTransitionTime": "2003-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.pb new file mode 100644 index 0000000000000..f7d8d446bbcb5 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.yaml new file mode 100644 index 0000000000000..2bb7ded37f877 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/flowcontrol.apiserver.k8s.io.v1beta3.PriorityLevelConfiguration.yaml @@ -0,0 +1,56 @@ +apiVersion: flowcontrol.apiserver.k8s.io/v1beta3 +kind: PriorityLevelConfiguration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + exempt: + lendablePercent: 2 + nominalConcurrencyShares: 1 + limited: + borrowingLimitPercent: 4 + lendablePercent: 3 + limitResponse: + queuing: + handSize: 2 + queueLengthLimit: 3 + queues: 1 + type: typeValue + nominalConcurrencyShares: 1 + type: typeValue +status: + conditions: + - lastTransitionTime: "2003-01-01T01:01:01Z" + message: messageValue + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.json b/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.json new file mode 100644 index 0000000000000..c05070e53756d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.json @@ -0,0 +1,64 @@ +{ + "kind": "ImageReview", + "apiVersion": "imagepolicy.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "containers": [ + { + "image": "imageValue" + } + ], + "annotations": { + "annotationsKey": "annotationsValue" + }, + "namespace": "namespaceValue" + }, + "status": { + "allowed": true, + "reason": "reasonValue", + "auditAnnotations": { + "auditAnnotationsKey": "auditAnnotationsValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.pb b/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.pb new file mode 100644 index 0000000000000..b71c3c9ceaad4 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.yaml new file mode 100644 index 0000000000000..416218ff21417 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/imagepolicy.k8s.io.v1alpha1.ImageReview.yaml @@ -0,0 +1,45 @@ +apiVersion: imagepolicy.k8s.io/v1alpha1 +kind: ImageReview +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + annotations: + annotationsKey: annotationsValue + containers: + - image: imageValue + namespace: namespaceValue +status: + allowed: true + auditAnnotations: + auditAnnotationsKey: auditAnnotationsValue + reason: reasonValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.json b/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.json new file mode 100644 index 0000000000000..795cd8b939f99 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.json @@ -0,0 +1,72 @@ +{ + "kind": "StorageVersion", + "apiVersion": "internal.apiserver.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": {}, + "status": { + "storageVersions": [ + { + "apiServerID": "apiServerIDValue", + "encodingVersion": "encodingVersionValue", + "decodableVersions": [ + "decodableVersionsValue" + ], + "servedVersions": [ + "servedVersionsValue" + ] + } + ], + "commonEncodingVersion": "commonEncodingVersionValue", + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.pb b/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.pb new file mode 100644 index 0000000000000..fca7ad3f3dfed Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.yaml new file mode 100644 index 0000000000000..1ca02d69f0b1b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/internal.apiserver.k8s.io.v1alpha1.StorageVersion.yaml @@ -0,0 +1,51 @@ +apiVersion: internal.apiserver.k8s.io/v1alpha1 +kind: StorageVersion +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: {} +status: + commonEncodingVersion: commonEncodingVersionValue + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + storageVersions: + - apiServerID: apiServerIDValue + decodableVersions: + - decodableVersionsValue + encodingVersion: encodingVersionValue + servedVersions: + - servedVersionsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.json new file mode 100644 index 0000000000000..f1c898e3afbf4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.json @@ -0,0 +1,54 @@ +{ + "kind": "IPAddress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parentRef": { + "group": "groupValue", + "resource": "resourceValue", + "namespace": "namespaceValue", + "name": "nameValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.pb new file mode 100644 index 0000000000000..786612311229d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.yaml new file mode 100644 index 0000000000000..29c5e6908803b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IPAddress.yaml @@ -0,0 +1,40 @@ +apiVersion: networking.k8s.io/v1 +kind: IPAddress +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + parentRef: + group: groupValue + name: nameValue + namespace: namespaceValue + resource: resourceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.json new file mode 100644 index 0000000000000..7f84f0c6c9a0a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.json @@ -0,0 +1,115 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "ingressClassName": "ingressClassNameValue", + "defaultBackend": { + "service": { + "name": "nameValue", + "port": { + "name": "nameValue", + "number": 2 + } + }, + "resource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } + }, + "tls": [ + { + "hosts": [ + "hostsValue" + ], + "secretName": "secretNameValue" + } + ], + "rules": [ + { + "host": "hostValue", + "http": { + "paths": [ + { + "path": "pathValue", + "pathType": "pathTypeValue", + "backend": { + "service": { + "name": "nameValue", + "port": { + "name": "nameValue", + "number": 2 + } + }, + "resource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": { + "ingress": [ + { + "ip": "ipValue", + "hostname": "hostnameValue", + "ports": [ + { + "port": 1, + "protocol": "protocolValue", + "error": "errorValue" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.pb new file mode 100644 index 0000000000000..d7b9a162970cf Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.yaml new file mode 100644 index 0000000000000..b01d1b3445d5f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.Ingress.yaml @@ -0,0 +1,75 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + defaultBackend: + resource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + service: + name: nameValue + port: + name: nameValue + number: 2 + ingressClassName: ingressClassNameValue + rules: + - host: hostValue + http: + paths: + - backend: + resource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + service: + name: nameValue + port: + name: nameValue + number: 2 + path: pathValue + pathType: pathTypeValue + tls: + - hosts: + - hostsValue + secretName: secretNameValue +status: + loadBalancer: + ingress: + - hostname: hostnameValue + ip: ipValue + ports: + - error: errorValue + port: 1 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.json new file mode 100644 index 0000000000000..99065b04ecec6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.json @@ -0,0 +1,56 @@ +{ + "kind": "IngressClass", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "controller": "controllerValue", + "parameters": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "scope": "scopeValue", + "namespace": "namespaceValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.pb new file mode 100644 index 0000000000000..1f883673983ee Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.yaml new file mode 100644 index 0000000000000..229e24aaf39e1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.IngressClass.yaml @@ -0,0 +1,42 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + controller: controllerValue + parameters: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + scope: scopeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.json new file mode 100644 index 0000000000000..e61daad40ed50 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.json @@ -0,0 +1,163 @@ +{ + "kind": "NetworkPolicy", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "podSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "ingress": [ + { + "ports": [ + { + "protocol": "protocolValue", + "port": "portValue", + "endPort": 3 + } + ], + "from": [ + { + "podSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "ipBlock": { + "cidr": "cidrValue", + "except": [ + "exceptValue" + ] + } + } + ] + } + ], + "egress": [ + { + "ports": [ + { + "protocol": "protocolValue", + "port": "portValue", + "endPort": 3 + } + ], + "to": [ + { + "podSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "namespaceSelector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "ipBlock": { + "cidr": "cidrValue", + "except": [ + "exceptValue" + ] + } + } + ] + } + ], + "policyTypes": [ + "policyTypesValue" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.pb new file mode 100644 index 0000000000000..90efd44a0496b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.yaml new file mode 100644 index 0000000000000..09bf909047361 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.NetworkPolicy.yaml @@ -0,0 +1,97 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + egress: + - ports: + - endPort: 3 + port: portValue + protocol: protocolValue + to: + - ipBlock: + cidr: cidrValue + except: + - exceptValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + podSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + ingress: + - from: + - ipBlock: + cidr: cidrValue + except: + - exceptValue + namespaceSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + podSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + ports: + - endPort: 3 + port: portValue + protocol: protocolValue + podSelector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + policyTypes: + - policyTypesValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.json new file mode 100644 index 0000000000000..929b8afb43e5f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.json @@ -0,0 +1,63 @@ +{ + "kind": "ServiceCIDR", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "cidrs": [ + "cidrsValue" + ] + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.pb new file mode 100644 index 0000000000000..c221a66e3ec36 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.yaml new file mode 100644 index 0000000000000..347f5763d2167 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1.ServiceCIDR.yaml @@ -0,0 +1,45 @@ +apiVersion: networking.k8s.io/v1 +kind: ServiceCIDR +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + cidrs: + - cidrsValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.json new file mode 100644 index 0000000000000..68b4d2e1bac22 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.json @@ -0,0 +1,54 @@ +{ + "kind": "IPAddress", + "apiVersion": "networking.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "parentRef": { + "group": "groupValue", + "resource": "resourceValue", + "namespace": "namespaceValue", + "name": "nameValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.pb new file mode 100644 index 0000000000000..2f1f28ce8668a Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.yaml new file mode 100644 index 0000000000000..b16eff5d7f985 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IPAddress.yaml @@ -0,0 +1,40 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IPAddress +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + parentRef: + group: groupValue + name: nameValue + namespace: namespaceValue + resource: resourceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.json new file mode 100644 index 0000000000000..7a95be4a58b12 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.json @@ -0,0 +1,105 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "ingressClassName": "ingressClassNameValue", + "backend": { + "serviceName": "serviceNameValue", + "servicePort": "servicePortValue", + "resource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } + }, + "tls": [ + { + "hosts": [ + "hostsValue" + ], + "secretName": "secretNameValue" + } + ], + "rules": [ + { + "host": "hostValue", + "http": { + "paths": [ + { + "path": "pathValue", + "pathType": "pathTypeValue", + "backend": { + "serviceName": "serviceNameValue", + "servicePort": "servicePortValue", + "resource": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": { + "ingress": [ + { + "ip": "ipValue", + "hostname": "hostnameValue", + "ports": [ + { + "port": 1, + "protocol": "protocolValue", + "error": "errorValue" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.pb new file mode 100644 index 0000000000000..1c582df7cf5f6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.yaml new file mode 100644 index 0000000000000..8a8237b25adc3 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.Ingress.yaml @@ -0,0 +1,69 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + backend: + resource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + serviceName: serviceNameValue + servicePort: servicePortValue + ingressClassName: ingressClassNameValue + rules: + - host: hostValue + http: + paths: + - backend: + resource: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + serviceName: serviceNameValue + servicePort: servicePortValue + path: pathValue + pathType: pathTypeValue + tls: + - hosts: + - hostsValue + secretName: secretNameValue +status: + loadBalancer: + ingress: + - hostname: hostnameValue + ip: ipValue + ports: + - error: errorValue + port: 1 + protocol: protocolValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.json new file mode 100644 index 0000000000000..8fd98672b1f7b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.json @@ -0,0 +1,56 @@ +{ + "kind": "IngressClass", + "apiVersion": "networking.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "controller": "controllerValue", + "parameters": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue", + "scope": "scopeValue", + "namespace": "namespaceValue" + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.pb new file mode 100644 index 0000000000000..72f1d49042648 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.yaml new file mode 100644 index 0000000000000..a8fd20df752c4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.IngressClass.yaml @@ -0,0 +1,42 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + controller: controllerValue + parameters: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue + scope: scopeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.json b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.json new file mode 100644 index 0000000000000..2fdd547009cf7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.json @@ -0,0 +1,63 @@ +{ + "kind": "ServiceCIDR", + "apiVersion": "networking.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "cidrs": [ + "cidrsValue" + ] + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.pb b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.pb new file mode 100644 index 0000000000000..5b45721bd2713 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.yaml new file mode 100644 index 0000000000000..07cd34d3201eb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/networking.k8s.io.v1beta1.ServiceCIDR.yaml @@ -0,0 +1,45 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: ServiceCIDR +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + cidrs: + - cidrsValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.json new file mode 100644 index 0000000000000..0612e706352fd --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.json @@ -0,0 +1,66 @@ +{ + "kind": "RuntimeClass", + "apiVersion": "node.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "handler": "handlerValue", + "overhead": { + "podFixed": { + "podFixedKey": "0" + } + }, + "scheduling": { + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.pb new file mode 100644 index 0000000000000..9a0dbadb1b2b1 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.yaml new file mode 100644 index 0000000000000..0e00081d2825d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1.RuntimeClass.yaml @@ -0,0 +1,47 @@ +apiVersion: node.k8s.io/v1 +handler: handlerValue +kind: RuntimeClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +overhead: + podFixed: + podFixedKey: "0" +scheduling: + nodeSelector: + nodeSelectorKey: nodeSelectorValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.json new file mode 100644 index 0000000000000..c83df3574ea3b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.json @@ -0,0 +1,68 @@ +{ + "kind": "RuntimeClass", + "apiVersion": "node.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "runtimeHandler": "runtimeHandlerValue", + "overhead": { + "podFixed": { + "podFixedKey": "0" + } + }, + "scheduling": { + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ] + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.pb new file mode 100644 index 0000000000000..2f13b3cf6fcaf Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.yaml new file mode 100644 index 0000000000000..5d760398980dd --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1alpha1.RuntimeClass.yaml @@ -0,0 +1,48 @@ +apiVersion: node.k8s.io/v1alpha1 +kind: RuntimeClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + overhead: + podFixed: + podFixedKey: "0" + runtimeHandler: runtimeHandlerValue + scheduling: + nodeSelector: + nodeSelectorKey: nodeSelectorValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.json new file mode 100644 index 0000000000000..720d8c5eb9816 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.json @@ -0,0 +1,66 @@ +{ + "kind": "RuntimeClass", + "apiVersion": "node.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "handler": "handlerValue", + "overhead": { + "podFixed": { + "podFixedKey": "0" + } + }, + "scheduling": { + "nodeSelector": { + "nodeSelectorKey": "nodeSelectorValue" + }, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.pb new file mode 100644 index 0000000000000..1233b744b6cbc Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.yaml new file mode 100644 index 0000000000000..1d7f3b481d04a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/node.k8s.io.v1beta1.RuntimeClass.yaml @@ -0,0 +1,47 @@ +apiVersion: node.k8s.io/v1beta1 +handler: handlerValue +kind: RuntimeClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +overhead: + podFixed: + podFixedKey: "0" +scheduling: + nodeSelector: + nodeSelectorKey: nodeSelectorValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.json b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.json new file mode 100644 index 0000000000000..5037089fad439 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.json @@ -0,0 +1,59 @@ +{ + "kind": "Eviction", + "apiVersion": "policy/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "deleteOptions": { + "gracePeriodSeconds": 1, + "preconditions": { + "uid": "uidValue", + "resourceVersion": "resourceVersionValue" + }, + "orphanDependents": true, + "propagationPolicy": "propagationPolicyValue", + "dryRun": [ + "dryRunValue" + ], + "ignoreStoreReadErrorWithClusterBreakingPotential": true + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.pb b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.pb new file mode 100644 index 0000000000000..767c0a5761a0e Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.yaml new file mode 100644 index 0000000000000..243516131249b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.Eviction.yaml @@ -0,0 +1,44 @@ +apiVersion: policy/v1 +deleteOptions: + dryRun: + - dryRunValue + gracePeriodSeconds: 1 + ignoreStoreReadErrorWithClusterBreakingPotential: true + orphanDependents: true + preconditions: + resourceVersion: resourceVersionValue + uid: uidValue + propagationPolicy: propagationPolicyValue +kind: Eviction +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.json b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.json new file mode 100644 index 0000000000000..0fb410fd8e22e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.json @@ -0,0 +1,85 @@ +{ + "kind": "PodDisruptionBudget", + "apiVersion": "policy/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "minAvailable": "minAvailableValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "maxUnavailable": "maxUnavailableValue", + "unhealthyPodEvictionPolicy": "unhealthyPodEvictionPolicyValue" + }, + "status": { + "observedGeneration": 1, + "disruptedPods": { + "disruptedPodsKey": null + }, + "disruptionsAllowed": 3, + "currentHealthy": 4, + "desiredHealthy": 5, + "expectedPods": 6, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.pb b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.pb new file mode 100644 index 0000000000000..b54b69b357031 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.yaml new file mode 100644 index 0000000000000..e51af35c18223 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1.PodDisruptionBudget.yaml @@ -0,0 +1,61 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + maxUnavailable: maxUnavailableValue + minAvailable: minAvailableValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + unhealthyPodEvictionPolicy: unhealthyPodEvictionPolicyValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + currentHealthy: 4 + desiredHealthy: 5 + disruptedPods: + disruptedPodsKey: null + disruptionsAllowed: 3 + expectedPods: 6 + observedGeneration: 1 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.json b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.json new file mode 100644 index 0000000000000..0e6e890407e7d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.json @@ -0,0 +1,59 @@ +{ + "kind": "Eviction", + "apiVersion": "policy/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "deleteOptions": { + "gracePeriodSeconds": 1, + "preconditions": { + "uid": "uidValue", + "resourceVersion": "resourceVersionValue" + }, + "orphanDependents": true, + "propagationPolicy": "propagationPolicyValue", + "dryRun": [ + "dryRunValue" + ], + "ignoreStoreReadErrorWithClusterBreakingPotential": true + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.pb b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.pb new file mode 100644 index 0000000000000..c5399ef982f16 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.yaml new file mode 100644 index 0000000000000..d48b01a4d779e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.Eviction.yaml @@ -0,0 +1,44 @@ +apiVersion: policy/v1beta1 +deleteOptions: + dryRun: + - dryRunValue + gracePeriodSeconds: 1 + ignoreStoreReadErrorWithClusterBreakingPotential: true + orphanDependents: true + preconditions: + resourceVersion: resourceVersionValue + uid: uidValue + propagationPolicy: propagationPolicyValue +kind: Eviction +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.json b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.json new file mode 100644 index 0000000000000..d7ef20e576cc4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.json @@ -0,0 +1,85 @@ +{ + "kind": "PodDisruptionBudget", + "apiVersion": "policy/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "minAvailable": "minAvailableValue", + "selector": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "maxUnavailable": "maxUnavailableValue", + "unhealthyPodEvictionPolicy": "unhealthyPodEvictionPolicyValue" + }, + "status": { + "observedGeneration": 1, + "disruptedPods": { + "disruptedPodsKey": null + }, + "disruptionsAllowed": 3, + "currentHealthy": 4, + "desiredHealthy": 5, + "expectedPods": 6, + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.pb b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.pb new file mode 100644 index 0000000000000..dadce0b4f029c Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.yaml new file mode 100644 index 0000000000000..5cc8d4558767e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/policy.v1beta1.PodDisruptionBudget.yaml @@ -0,0 +1,61 @@ +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + maxUnavailable: maxUnavailableValue + minAvailable: minAvailableValue + selector: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue + unhealthyPodEvictionPolicy: unhealthyPodEvictionPolicyValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + currentHealthy: 4 + desiredHealthy: 5 + disruptedPods: + disruptedPodsKey: null + disruptionsAllowed: 3 + expectedPods: 6 + observedGeneration: 1 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.json new file mode 100644 index 0000000000000..c18c88e41b0a6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.json @@ -0,0 +1,83 @@ +{ + "kind": "ClusterRole", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "rules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ], + "aggregationRule": { + "clusterRoleSelectors": [ + { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.pb new file mode 100644 index 0000000000000..7ab4355c85204 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.yaml new file mode 100644 index 0000000000000..6467543328e9f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRole.yaml @@ -0,0 +1,54 @@ +aggregationRule: + clusterRoleSelectors: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +rules: +- apiGroups: + - apiGroupsValue + nonResourceURLs: + - nonResourceURLsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.json new file mode 100644 index 0000000000000..c7c30cc731d0d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.json @@ -0,0 +1,59 @@ +{ + "kind": "ClusterRoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subjects": [ + { + "kind": "kindValue", + "apiGroup": "apiGroupValue", + "name": "nameValue", + "namespace": "namespaceValue" + } + ], + "roleRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.pb new file mode 100644 index 0000000000000..fef76c0d062fc Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.yaml new file mode 100644 index 0000000000000..137f25d66daa8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.ClusterRoleBinding.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +roleRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue +subjects: +- apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.json new file mode 100644 index 0000000000000..937ba8d4414cb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.json @@ -0,0 +1,65 @@ +{ + "kind": "Role", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "rules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.pb new file mode 100644 index 0000000000000..5aaea0025d63d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.yaml new file mode 100644 index 0000000000000..38b545cd2c79b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.Role.yaml @@ -0,0 +1,45 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +rules: +- apiGroups: + - apiGroupsValue + nonResourceURLs: + - nonResourceURLsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.json new file mode 100644 index 0000000000000..44f5d01efe6a1 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.json @@ -0,0 +1,59 @@ +{ + "kind": "RoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subjects": [ + { + "kind": "kindValue", + "apiGroup": "apiGroupValue", + "name": "nameValue", + "namespace": "namespaceValue" + } + ], + "roleRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.pb new file mode 100644 index 0000000000000..078c69e8ee11b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.yaml new file mode 100644 index 0000000000000..c672dfb335395 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1.RoleBinding.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +roleRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue +subjects: +- apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.json new file mode 100644 index 0000000000000..5f67d09d75445 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.json @@ -0,0 +1,83 @@ +{ + "kind": "ClusterRole", + "apiVersion": "rbac.authorization.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "rules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ], + "aggregationRule": { + "clusterRoleSelectors": [ + { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.pb new file mode 100644 index 0000000000000..521869d3d8113 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.yaml new file mode 100644 index 0000000000000..7f0858f61a6cd --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRole.yaml @@ -0,0 +1,54 @@ +aggregationRule: + clusterRoleSelectors: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue +apiVersion: rbac.authorization.k8s.io/v1alpha1 +kind: ClusterRole +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +rules: +- apiGroups: + - apiGroupsValue + nonResourceURLs: + - nonResourceURLsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.json new file mode 100644 index 0000000000000..36534c7ad0f60 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.json @@ -0,0 +1,59 @@ +{ + "kind": "ClusterRoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subjects": [ + { + "kind": "kindValue", + "apiVersion": "apiVersionValue", + "name": "nameValue", + "namespace": "namespaceValue" + } + ], + "roleRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.pb new file mode 100644 index 0000000000000..b70e6566de645 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.yaml new file mode 100644 index 0000000000000..6cdb234124d4f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.ClusterRoleBinding.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1alpha1 +kind: ClusterRoleBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +roleRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue +subjects: +- apiVersion: apiVersionValue + kind: kindValue + name: nameValue + namespace: namespaceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.json new file mode 100644 index 0000000000000..ea4a6c314a16f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.json @@ -0,0 +1,65 @@ +{ + "kind": "Role", + "apiVersion": "rbac.authorization.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "rules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.pb new file mode 100644 index 0000000000000..31edb183861a4 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.yaml new file mode 100644 index 0000000000000..e8a6e87b7448a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.Role.yaml @@ -0,0 +1,45 @@ +apiVersion: rbac.authorization.k8s.io/v1alpha1 +kind: Role +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +rules: +- apiGroups: + - apiGroupsValue + nonResourceURLs: + - nonResourceURLsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.json new file mode 100644 index 0000000000000..92096ea97e0ff --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.json @@ -0,0 +1,59 @@ +{ + "kind": "RoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subjects": [ + { + "kind": "kindValue", + "apiVersion": "apiVersionValue", + "name": "nameValue", + "namespace": "namespaceValue" + } + ], + "roleRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.pb new file mode 100644 index 0000000000000..1f631d33398a7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.yaml new file mode 100644 index 0000000000000..2436f0f78cae0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1alpha1.RoleBinding.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1alpha1 +kind: RoleBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +roleRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue +subjects: +- apiVersion: apiVersionValue + kind: kindValue + name: nameValue + namespace: namespaceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.json new file mode 100644 index 0000000000000..73ac5c28ba1a3 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.json @@ -0,0 +1,83 @@ +{ + "kind": "ClusterRole", + "apiVersion": "rbac.authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "rules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ], + "aggregationRule": { + "clusterRoleSelectors": [ + { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.pb new file mode 100644 index 0000000000000..40d272dc5eff3 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.yaml new file mode 100644 index 0000000000000..4e15eec53230b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRole.yaml @@ -0,0 +1,54 @@ +aggregationRule: + clusterRoleSelectors: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +rules: +- apiGroups: + - apiGroupsValue + nonResourceURLs: + - nonResourceURLsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.json new file mode 100644 index 0000000000000..8e0aa4ae0c9a7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.json @@ -0,0 +1,59 @@ +{ + "kind": "ClusterRoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subjects": [ + { + "kind": "kindValue", + "apiGroup": "apiGroupValue", + "name": "nameValue", + "namespace": "namespaceValue" + } + ], + "roleRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.pb new file mode 100644 index 0000000000000..f846a1b87a212 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.yaml new file mode 100644 index 0000000000000..5e788349972d8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.ClusterRoleBinding.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +roleRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue +subjects: +- apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.json new file mode 100644 index 0000000000000..6d7db5102bbc6 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.json @@ -0,0 +1,65 @@ +{ + "kind": "Role", + "apiVersion": "rbac.authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "rules": [ + { + "verbs": [ + "verbsValue" + ], + "apiGroups": [ + "apiGroupsValue" + ], + "resources": [ + "resourcesValue" + ], + "resourceNames": [ + "resourceNamesValue" + ], + "nonResourceURLs": [ + "nonResourceURLsValue" + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.pb new file mode 100644 index 0000000000000..e50a66e58fb1a Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.yaml new file mode 100644 index 0000000000000..c5ff6d5ecaabe --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.Role.yaml @@ -0,0 +1,45 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +rules: +- apiGroups: + - apiGroupsValue + nonResourceURLs: + - nonResourceURLsValue + resourceNames: + - resourceNamesValue + resources: + - resourcesValue + verbs: + - verbsValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.json b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.json new file mode 100644 index 0000000000000..42b6dc21a5aff --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.json @@ -0,0 +1,59 @@ +{ + "kind": "RoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "subjects": [ + { + "kind": "kindValue", + "apiGroup": "apiGroupValue", + "name": "nameValue", + "namespace": "namespaceValue" + } + ], + "roleRef": { + "apiGroup": "apiGroupValue", + "kind": "kindValue", + "name": "nameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.pb b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.pb new file mode 100644 index 0000000000000..120f92e51c9f3 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.yaml new file mode 100644 index 0000000000000..1e8620b5fca38 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/rbac.authorization.k8s.io.v1beta1.RoleBinding.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +roleRef: + apiGroup: apiGroupValue + kind: kindValue + name: nameValue +subjects: +- apiGroup: apiGroupValue + kind: kindValue + name: nameValue + namespace: namespaceValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.json new file mode 100644 index 0000000000000..8fd86ddef7314 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.json @@ -0,0 +1,73 @@ +{ + "kind": "DeviceClass", + "apiVersion": "resource.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "config": [ + { + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ], + "extendedResourceName": "extendedResourceNameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.pb new file mode 100644 index 0000000000000..1b1c76ceaf6c1 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.yaml new file mode 100644 index 0000000000000..36f958f33d22c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.DeviceClass.yaml @@ -0,0 +1,49 @@ +apiVersion: resource.k8s.io/v1 +kind: DeviceClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + extendedResourceName: extendedResourceNameValue + selectors: + - cel: + expression: expressionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.json new file mode 100644 index 0000000000000..a67a53104130e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.json @@ -0,0 +1,263 @@ +{ + "kind": "ResourceClaim", + "apiVersion": "resource.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "devices": { + "requests": [ + { + "name": "nameValue", + "exactly": { + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 4, + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + }, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ] + } + ], + "constraints": [ + { + "requests": [ + "requestsValue" + ], + "matchAttribute": "matchAttributeValue", + "distinctAttribute": "distinctAttributeValue" + } + ], + "config": [ + { + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + } + }, + "status": { + "allocation": { + "devices": { + "results": [ + { + "request": "requestValue", + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue", + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "bindingConditions": [ + "bindingConditionsValue" + ], + "bindingFailureConditions": [ + "bindingFailureConditionsValue" + ], + "shareID": "shareIDValue", + "consumedCapacity": { + "consumedCapacityKey": "0" + } + } + ], + "config": [ + { + "source": "sourceValue", + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + }, + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allocationTimestamp": "2005-01-01T01:01:01Z" + }, + "reservedFor": [ + { + "apiGroup": "apiGroupValue", + "resource": "resourceValue", + "name": "nameValue", + "uid": "uidValue" + } + ], + "devices": [ + { + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue", + "shareID": "shareIDValue", + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "data": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "networkData": { + "interfaceName": "interfaceNameValue", + "ips": [ + "ipsValue" + ], + "hardwareAddress": "hardwareAddressValue" + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.pb new file mode 100644 index 0000000000000..4a5f34d71c5cd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.yaml new file mode 100644 index 0000000000000..db448e861fdeb --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaim.yaml @@ -0,0 +1,166 @@ +apiVersion: resource.k8s.io/v1 +kind: ResourceClaim +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + constraints: + - distinctAttribute: distinctAttributeValue + matchAttribute: matchAttributeValue + requests: + - requestsValue + requests: + - exactly: + adminAccess: true + allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 4 + deviceClassName: deviceClassNameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + firstAvailable: + - allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + name: nameValue +status: + allocation: + allocationTimestamp: "2005-01-01T01:01:01Z" + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + source: sourceValue + results: + - adminAccess: true + bindingConditions: + - bindingConditionsValue + bindingFailureConditions: + - bindingFailureConditionsValue + consumedCapacity: + consumedCapacityKey: "0" + device: deviceValue + driver: driverValue + pool: poolValue + request: requestValue + shareID: shareIDValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + devices: + - conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + data: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + device: deviceValue + driver: driverValue + networkData: + hardwareAddress: hardwareAddressValue + interfaceName: interfaceNameValue + ips: + - ipsValue + pool: poolValue + shareID: shareIDValue + reservedFor: + - apiGroup: apiGroupValue + name: nameValue + resource: resourceValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.json new file mode 100644 index 0000000000000..c7f2c88974e2c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.json @@ -0,0 +1,184 @@ +{ + "kind": "ResourceClaimTemplate", + "apiVersion": "resource.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "devices": { + "requests": [ + { + "name": "nameValue", + "exactly": { + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 4, + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + }, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ] + } + ], + "constraints": [ + { + "requests": [ + "requestsValue" + ], + "matchAttribute": "matchAttributeValue", + "distinctAttribute": "distinctAttributeValue" + } + ], + "config": [ + { + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.pb new file mode 100644 index 0000000000000..db1f0ee449c48 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.yaml new file mode 100644 index 0000000000000..a29821f1c1c08 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceClaimTemplate.yaml @@ -0,0 +1,122 @@ +apiVersion: resource.k8s.io/v1 +kind: ResourceClaimTemplate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + constraints: + - distinctAttribute: distinctAttributeValue + matchAttribute: matchAttributeValue + requests: + - requestsValue + requests: + - exactly: + adminAccess: true + allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 4 + deviceClassName: deviceClassNameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + firstAvailable: + - allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.json new file mode 100644 index 0000000000000..71a2a34646581 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.json @@ -0,0 +1,172 @@ +{ + "kind": "ResourceSlice", + "apiVersion": "resource.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "driver": "driverValue", + "pool": { + "name": "nameValue", + "generation": 2, + "resourceSliceCount": 3 + }, + "nodeName": "nodeNameValue", + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allNodes": true, + "devices": [ + { + "name": "nameValue", + "attributes": { + "attributesKey": { + "int": 2, + "bool": true, + "string": "stringValue", + "version": "versionValue" + } + }, + "capacity": { + "capacityKey": { + "value": "0", + "requestPolicy": { + "default": "0", + "validValues": [ + "0" + ], + "validRange": { + "min": "0", + "max": "0", + "step": "0" + } + } + } + }, + "consumesCounters": [ + { + "counterSet": "counterSetValue", + "counters": { + "countersKey": { + "value": "0" + } + } + } + ], + "nodeName": "nodeNameValue", + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allNodes": true, + "taints": [ + { + "key": "keyValue", + "value": "valueValue", + "effect": "effectValue", + "timeAdded": "2004-01-01T01:01:01Z" + } + ], + "bindsToNode": true, + "bindingConditions": [ + "bindingConditionsValue" + ], + "bindingFailureConditions": [ + "bindingFailureConditionsValue" + ], + "allowMultipleAllocations": true + } + ], + "perDeviceNodeSelection": true, + "sharedCounters": [ + { + "name": "nameValue", + "counters": { + "countersKey": { + "value": "0" + } + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.pb new file mode 100644 index 0000000000000..3dcb6b97c1e2c Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.yaml new file mode 100644 index 0000000000000..f39b9ce2a3055 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1.ResourceSlice.yaml @@ -0,0 +1,109 @@ +apiVersion: resource.k8s.io/v1 +kind: ResourceSlice +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + allNodes: true + devices: + - allNodes: true + allowMultipleAllocations: true + attributes: + attributesKey: + bool: true + int: 2 + string: stringValue + version: versionValue + bindingConditions: + - bindingConditionsValue + bindingFailureConditions: + - bindingFailureConditionsValue + bindsToNode: true + capacity: + capacityKey: + requestPolicy: + default: "0" + validRange: + max: "0" + min: "0" + step: "0" + validValues: + - "0" + value: "0" + consumesCounters: + - counterSet: counterSetValue + counters: + countersKey: + value: "0" + name: nameValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + taints: + - effect: effectValue + key: keyValue + timeAdded: "2004-01-01T01:01:01Z" + value: valueValue + driver: driverValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + perDeviceNodeSelection: true + pool: + generation: 2 + name: nameValue + resourceSliceCount: 3 + sharedCounters: + - counters: + countersKey: + value: "0" + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.json new file mode 100644 index 0000000000000..c574f84cb2e14 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.json @@ -0,0 +1,71 @@ +{ + "kind": "DeviceTaintRule", + "apiVersion": "resource.k8s.io/v1alpha3", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "deviceSelector": { + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue" + }, + "taint": { + "key": "keyValue", + "value": "valueValue", + "effect": "effectValue", + "timeAdded": "2004-01-01T01:01:01Z" + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.pb new file mode 100644 index 0000000000000..feaa316ef8d42 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.yaml new file mode 100644 index 0000000000000..8c2bc6cfd3dbc --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1alpha3.DeviceTaintRule.yaml @@ -0,0 +1,52 @@ +apiVersion: resource.k8s.io/v1alpha3 +kind: DeviceTaintRule +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + deviceSelector: + device: deviceValue + driver: driverValue + pool: poolValue + taint: + effect: effectValue + key: keyValue + timeAdded: "2004-01-01T01:01:01Z" + value: valueValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.json new file mode 100644 index 0000000000000..82ed74a32c393 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.json @@ -0,0 +1,73 @@ +{ + "kind": "DeviceClass", + "apiVersion": "resource.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "config": [ + { + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ], + "extendedResourceName": "extendedResourceNameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.pb new file mode 100644 index 0000000000000..a60344d077565 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.yaml new file mode 100644 index 0000000000000..9390e676b428b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.DeviceClass.yaml @@ -0,0 +1,49 @@ +apiVersion: resource.k8s.io/v1beta1 +kind: DeviceClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + extendedResourceName: extendedResourceNameValue + selectors: + - cel: + expression: expressionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.json new file mode 100644 index 0000000000000..74e5b53ce8a6c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.json @@ -0,0 +1,261 @@ +{ + "kind": "ResourceClaim", + "apiVersion": "resource.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "devices": { + "requests": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "adminAccess": true, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ], + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ], + "constraints": [ + { + "requests": [ + "requestsValue" + ], + "matchAttribute": "matchAttributeValue", + "distinctAttribute": "distinctAttributeValue" + } + ], + "config": [ + { + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + } + }, + "status": { + "allocation": { + "devices": { + "results": [ + { + "request": "requestValue", + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue", + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "bindingConditions": [ + "bindingConditionsValue" + ], + "bindingFailureConditions": [ + "bindingFailureConditionsValue" + ], + "shareID": "shareIDValue", + "consumedCapacity": { + "consumedCapacityKey": "0" + } + } + ], + "config": [ + { + "source": "sourceValue", + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + }, + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allocationTimestamp": "2005-01-01T01:01:01Z" + }, + "reservedFor": [ + { + "apiGroup": "apiGroupValue", + "resource": "resourceValue", + "name": "nameValue", + "uid": "uidValue" + } + ], + "devices": [ + { + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue", + "shareID": "shareIDValue", + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "data": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "networkData": { + "interfaceName": "interfaceNameValue", + "ips": [ + "ipsValue" + ], + "hardwareAddress": "hardwareAddressValue" + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.pb new file mode 100644 index 0000000000000..8a0697d2ab34a Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.yaml new file mode 100644 index 0000000000000..ccb2b63c3622d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaim.yaml @@ -0,0 +1,165 @@ +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceClaim +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + constraints: + - distinctAttribute: distinctAttributeValue + matchAttribute: matchAttributeValue + requests: + - requestsValue + requests: + - adminAccess: true + allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + firstAvailable: + - allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue +status: + allocation: + allocationTimestamp: "2005-01-01T01:01:01Z" + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + source: sourceValue + results: + - adminAccess: true + bindingConditions: + - bindingConditionsValue + bindingFailureConditions: + - bindingFailureConditionsValue + consumedCapacity: + consumedCapacityKey: "0" + device: deviceValue + driver: driverValue + pool: poolValue + request: requestValue + shareID: shareIDValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + devices: + - conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + data: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + device: deviceValue + driver: driverValue + networkData: + hardwareAddress: hardwareAddressValue + interfaceName: interfaceNameValue + ips: + - ipsValue + pool: poolValue + shareID: shareIDValue + reservedFor: + - apiGroup: apiGroupValue + name: nameValue + resource: resourceValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.json new file mode 100644 index 0000000000000..9f40b834f072d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.json @@ -0,0 +1,182 @@ +{ + "kind": "ResourceClaimTemplate", + "apiVersion": "resource.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "devices": { + "requests": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "adminAccess": true, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ], + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ], + "constraints": [ + { + "requests": [ + "requestsValue" + ], + "matchAttribute": "matchAttributeValue", + "distinctAttribute": "distinctAttributeValue" + } + ], + "config": [ + { + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb new file mode 100644 index 0000000000000..344bd3899f378 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml new file mode 100644 index 0000000000000..ca60241548004 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml @@ -0,0 +1,121 @@ +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceClaimTemplate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + constraints: + - distinctAttribute: distinctAttributeValue + matchAttribute: matchAttributeValue + requests: + - requestsValue + requests: + - adminAccess: true + allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + firstAvailable: + - allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.json new file mode 100644 index 0000000000000..eb2879fa0a4d2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.json @@ -0,0 +1,174 @@ +{ + "kind": "ResourceSlice", + "apiVersion": "resource.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "driver": "driverValue", + "pool": { + "name": "nameValue", + "generation": 2, + "resourceSliceCount": 3 + }, + "nodeName": "nodeNameValue", + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allNodes": true, + "devices": [ + { + "name": "nameValue", + "basic": { + "attributes": { + "attributesKey": { + "int": 2, + "bool": true, + "string": "stringValue", + "version": "versionValue" + } + }, + "capacity": { + "capacityKey": { + "value": "0", + "requestPolicy": { + "default": "0", + "validValues": [ + "0" + ], + "validRange": { + "min": "0", + "max": "0", + "step": "0" + } + } + } + }, + "consumesCounters": [ + { + "counterSet": "counterSetValue", + "counters": { + "countersKey": { + "value": "0" + } + } + } + ], + "nodeName": "nodeNameValue", + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allNodes": true, + "taints": [ + { + "key": "keyValue", + "value": "valueValue", + "effect": "effectValue", + "timeAdded": "2004-01-01T01:01:01Z" + } + ], + "bindsToNode": true, + "bindingConditions": [ + "bindingConditionsValue" + ], + "bindingFailureConditions": [ + "bindingFailureConditionsValue" + ], + "allowMultipleAllocations": true + } + } + ], + "perDeviceNodeSelection": true, + "sharedCounters": [ + { + "name": "nameValue", + "counters": { + "countersKey": { + "value": "0" + } + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.pb new file mode 100644 index 0000000000000..248a5d59e7555 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.yaml new file mode 100644 index 0000000000000..dea39fec6203a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta1.ResourceSlice.yaml @@ -0,0 +1,110 @@ +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceSlice +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + allNodes: true + devices: + - basic: + allNodes: true + allowMultipleAllocations: true + attributes: + attributesKey: + bool: true + int: 2 + string: stringValue + version: versionValue + bindingConditions: + - bindingConditionsValue + bindingFailureConditions: + - bindingFailureConditionsValue + bindsToNode: true + capacity: + capacityKey: + requestPolicy: + default: "0" + validRange: + max: "0" + min: "0" + step: "0" + validValues: + - "0" + value: "0" + consumesCounters: + - counterSet: counterSetValue + counters: + countersKey: + value: "0" + nodeName: nodeNameValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + taints: + - effect: effectValue + key: keyValue + timeAdded: "2004-01-01T01:01:01Z" + value: valueValue + name: nameValue + driver: driverValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + perDeviceNodeSelection: true + pool: + generation: 2 + name: nameValue + resourceSliceCount: 3 + sharedCounters: + - counters: + countersKey: + value: "0" + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.json new file mode 100644 index 0000000000000..2cc62c50c4a3e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.json @@ -0,0 +1,73 @@ +{ + "kind": "DeviceClass", + "apiVersion": "resource.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "config": [ + { + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ], + "extendedResourceName": "extendedResourceNameValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.pb new file mode 100644 index 0000000000000..0ca74192a08e8 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.yaml new file mode 100644 index 0000000000000..c307342cacc09 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.DeviceClass.yaml @@ -0,0 +1,49 @@ +apiVersion: resource.k8s.io/v1beta2 +kind: DeviceClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + extendedResourceName: extendedResourceNameValue + selectors: + - cel: + expression: expressionValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.json new file mode 100644 index 0000000000000..4036029512f2f --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.json @@ -0,0 +1,263 @@ +{ + "kind": "ResourceClaim", + "apiVersion": "resource.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "devices": { + "requests": [ + { + "name": "nameValue", + "exactly": { + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 4, + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + }, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ] + } + ], + "constraints": [ + { + "requests": [ + "requestsValue" + ], + "matchAttribute": "matchAttributeValue", + "distinctAttribute": "distinctAttributeValue" + } + ], + "config": [ + { + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + } + }, + "status": { + "allocation": { + "devices": { + "results": [ + { + "request": "requestValue", + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue", + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "bindingConditions": [ + "bindingConditionsValue" + ], + "bindingFailureConditions": [ + "bindingFailureConditionsValue" + ], + "shareID": "shareIDValue", + "consumedCapacity": { + "consumedCapacityKey": "0" + } + } + ], + "config": [ + { + "source": "sourceValue", + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + }, + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allocationTimestamp": "2005-01-01T01:01:01Z" + }, + "reservedFor": [ + { + "apiGroup": "apiGroupValue", + "resource": "resourceValue", + "name": "nameValue", + "uid": "uidValue" + } + ], + "devices": [ + { + "driver": "driverValue", + "pool": "poolValue", + "device": "deviceValue", + "shareID": "shareIDValue", + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "data": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + }, + "networkData": { + "interfaceName": "interfaceNameValue", + "ips": [ + "ipsValue" + ], + "hardwareAddress": "hardwareAddressValue" + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.pb new file mode 100644 index 0000000000000..6dcae3edcf99b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.yaml new file mode 100644 index 0000000000000..3dc7772bfb3f2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaim.yaml @@ -0,0 +1,166 @@ +apiVersion: resource.k8s.io/v1beta2 +kind: ResourceClaim +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + constraints: + - distinctAttribute: distinctAttributeValue + matchAttribute: matchAttributeValue + requests: + - requestsValue + requests: + - exactly: + adminAccess: true + allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 4 + deviceClassName: deviceClassNameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + firstAvailable: + - allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + name: nameValue +status: + allocation: + allocationTimestamp: "2005-01-01T01:01:01Z" + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + source: sourceValue + results: + - adminAccess: true + bindingConditions: + - bindingConditionsValue + bindingFailureConditions: + - bindingFailureConditionsValue + consumedCapacity: + consumedCapacityKey: "0" + device: deviceValue + driver: driverValue + pool: poolValue + request: requestValue + shareID: shareIDValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + devices: + - conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + data: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + device: deviceValue + driver: driverValue + networkData: + hardwareAddress: hardwareAddressValue + interfaceName: interfaceNameValue + ips: + - ipsValue + pool: poolValue + shareID: shareIDValue + reservedFor: + - apiGroup: apiGroupValue + name: nameValue + resource: resourceValue + uid: uidValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.json new file mode 100644 index 0000000000000..9633ebb2c7d32 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.json @@ -0,0 +1,184 @@ +{ + "kind": "ResourceClaimTemplate", + "apiVersion": "resource.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "devices": { + "requests": [ + { + "name": "nameValue", + "exactly": { + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 4, + "adminAccess": true, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + }, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5, + "tolerations": [ + { + "key": "keyValue", + "operator": "operatorValue", + "value": "valueValue", + "effect": "effectValue", + "tolerationSeconds": 5 + } + ], + "capacity": { + "requests": { + "requestsKey": "0" + } + } + } + ] + } + ], + "constraints": [ + { + "requests": [ + "requestsValue" + ], + "matchAttribute": "matchAttributeValue", + "distinctAttribute": "distinctAttributeValue" + } + ], + "config": [ + { + "requests": [ + "requestsValue" + ], + "opaque": { + "driver": "driverValue", + "parameters": { + "apiVersion": "example.com/v1", + "kind": "CustomType", + "spec": { + "replicas": 1 + }, + "status": { + "available": 1 + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.pb new file mode 100644 index 0000000000000..1fff455fdeb5d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.yaml new file mode 100644 index 0000000000000..1cc17c3612370 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceClaimTemplate.yaml @@ -0,0 +1,122 @@ +apiVersion: resource.k8s.io/v1beta2 +kind: ResourceClaimTemplate +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue + spec: + devices: + config: + - opaque: + driver: driverValue + parameters: + apiVersion: example.com/v1 + kind: CustomType + spec: + replicas: 1 + status: + available: 1 + requests: + - requestsValue + constraints: + - distinctAttribute: distinctAttributeValue + matchAttribute: matchAttributeValue + requests: + - requestsValue + requests: + - exactly: + adminAccess: true + allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 4 + deviceClassName: deviceClassNameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + firstAvailable: + - allocationMode: allocationModeValue + capacity: + requests: + requestsKey: "0" + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue + tolerations: + - effect: effectValue + key: keyValue + operator: operatorValue + tolerationSeconds: 5 + value: valueValue + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.json b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.json new file mode 100644 index 0000000000000..4d80e7cfe726a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.json @@ -0,0 +1,172 @@ +{ + "kind": "ResourceSlice", + "apiVersion": "resource.k8s.io/v1beta2", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "driver": "driverValue", + "pool": { + "name": "nameValue", + "generation": 2, + "resourceSliceCount": 3 + }, + "nodeName": "nodeNameValue", + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allNodes": true, + "devices": [ + { + "name": "nameValue", + "attributes": { + "attributesKey": { + "int": 2, + "bool": true, + "string": "stringValue", + "version": "versionValue" + } + }, + "capacity": { + "capacityKey": { + "value": "0", + "requestPolicy": { + "default": "0", + "validValues": [ + "0" + ], + "validRange": { + "min": "0", + "max": "0", + "step": "0" + } + } + } + }, + "consumesCounters": [ + { + "counterSet": "counterSetValue", + "counters": { + "countersKey": { + "value": "0" + } + } + } + ], + "nodeName": "nodeNameValue", + "nodeSelector": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + }, + "allNodes": true, + "taints": [ + { + "key": "keyValue", + "value": "valueValue", + "effect": "effectValue", + "timeAdded": "2004-01-01T01:01:01Z" + } + ], + "bindsToNode": true, + "bindingConditions": [ + "bindingConditionsValue" + ], + "bindingFailureConditions": [ + "bindingFailureConditionsValue" + ], + "allowMultipleAllocations": true + } + ], + "perDeviceNodeSelection": true, + "sharedCounters": [ + { + "name": "nameValue", + "counters": { + "countersKey": { + "value": "0" + } + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.pb b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.pb new file mode 100644 index 0000000000000..a81ae25142726 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.yaml new file mode 100644 index 0000000000000..73881c40d3cb4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/resource.k8s.io.v1beta2.ResourceSlice.yaml @@ -0,0 +1,109 @@ +apiVersion: resource.k8s.io/v1beta2 +kind: ResourceSlice +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + allNodes: true + devices: + - allNodes: true + allowMultipleAllocations: true + attributes: + attributesKey: + bool: true + int: 2 + string: stringValue + version: versionValue + bindingConditions: + - bindingConditionsValue + bindingFailureConditions: + - bindingFailureConditionsValue + bindsToNode: true + capacity: + capacityKey: + requestPolicy: + default: "0" + validRange: + max: "0" + min: "0" + step: "0" + validValues: + - "0" + value: "0" + consumesCounters: + - counterSet: counterSetValue + counters: + countersKey: + value: "0" + name: nameValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + taints: + - effect: effectValue + key: keyValue + timeAdded: "2004-01-01T01:01:01Z" + value: valueValue + driver: driverValue + nodeName: nodeNameValue + nodeSelector: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + perDeviceNodeSelection: true + pool: + generation: 2 + name: nameValue + resourceSliceCount: 3 + sharedCounters: + - counters: + countersKey: + value: "0" + name: nameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.json new file mode 100644 index 0000000000000..fb75f776b82c5 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.json @@ -0,0 +1,50 @@ +{ + "kind": "PriorityClass", + "apiVersion": "scheduling.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "value": 2, + "globalDefault": true, + "description": "descriptionValue", + "preemptionPolicy": "preemptionPolicyValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.pb new file mode 100644 index 0000000000000..499f3653208a4 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.yaml new file mode 100644 index 0000000000000..275260df3a08c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1.PriorityClass.yaml @@ -0,0 +1,38 @@ +apiVersion: scheduling.k8s.io/v1 +description: descriptionValue +globalDefault: true +kind: PriorityClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +preemptionPolicy: preemptionPolicyValue +value: 2 diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.PriorityClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.PriorityClass.json similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.PriorityClass.json rename to staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.PriorityClass.json diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.PriorityClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.PriorityClass.pb similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.PriorityClass.pb rename to staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.PriorityClass.pb diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.PriorityClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.PriorityClass.yaml similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.PriorityClass.yaml rename to staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.PriorityClass.yaml diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.Workload.json b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.Workload.json similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.Workload.json rename to staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.Workload.json diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.Workload.pb b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.Workload.pb similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.Workload.pb rename to staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.Workload.pb diff --git a/staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.Workload.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.Workload.yaml similarity index 100% rename from staging/src/k8s.io/api/testdata/HEAD/scheduling.k8s.io.v1alpha1.Workload.yaml rename to staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1alpha1.Workload.yaml diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.json new file mode 100644 index 0000000000000..44cb32f4609e7 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.json @@ -0,0 +1,50 @@ +{ + "kind": "PriorityClass", + "apiVersion": "scheduling.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "value": 2, + "globalDefault": true, + "description": "descriptionValue", + "preemptionPolicy": "preemptionPolicyValue" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.pb new file mode 100644 index 0000000000000..a61387a182eea Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.yaml new file mode 100644 index 0000000000000..046a8a448d90b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/scheduling.k8s.io.v1beta1.PriorityClass.yaml @@ -0,0 +1,38 @@ +apiVersion: scheduling.k8s.io/v1beta1 +description: descriptionValue +globalDefault: true +kind: PriorityClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +preemptionPolicy: preemptionPolicyValue +value: 2 diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.json new file mode 100644 index 0000000000000..addc685f37eb0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.json @@ -0,0 +1,65 @@ +{ + "kind": "CSIDriver", + "apiVersion": "storage.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "attachRequired": true, + "podInfoOnMount": true, + "volumeLifecycleModes": [ + "volumeLifecycleModesValue" + ], + "storageCapacity": true, + "fsGroupPolicy": "fsGroupPolicyValue", + "tokenRequests": [ + { + "audience": "audienceValue", + "expirationSeconds": 2 + } + ], + "requiresRepublish": true, + "seLinuxMount": true, + "nodeAllocatableUpdatePeriodSeconds": 9, + "serviceAccountTokenInSecrets": true + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.pb new file mode 100644 index 0000000000000..c5e60ad070b16 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.yaml new file mode 100644 index 0000000000000..9ede049a06fec --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIDriver.yaml @@ -0,0 +1,48 @@ +apiVersion: storage.k8s.io/v1 +kind: CSIDriver +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + attachRequired: true + fsGroupPolicy: fsGroupPolicyValue + nodeAllocatableUpdatePeriodSeconds: 9 + podInfoOnMount: true + requiresRepublish: true + seLinuxMount: true + serviceAccountTokenInSecrets: true + storageCapacity: true + tokenRequests: + - audience: audienceValue + expirationSeconds: 2 + volumeLifecycleModes: + - volumeLifecycleModesValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.json new file mode 100644 index 0000000000000..0bfe5b71b208e --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.json @@ -0,0 +1,60 @@ +{ + "kind": "CSINode", + "apiVersion": "storage.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "drivers": [ + { + "name": "nameValue", + "nodeID": "nodeIDValue", + "topologyKeys": [ + "topologyKeysValue" + ], + "allocatable": { + "count": 1 + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.pb new file mode 100644 index 0000000000000..f67125d6a2bad Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.yaml new file mode 100644 index 0000000000000..4f780e15c6590 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSINode.yaml @@ -0,0 +1,42 @@ +apiVersion: storage.k8s.io/v1 +kind: CSINode +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + drivers: + - allocatable: + count: 1 + name: nameValue + nodeID: nodeIDValue + topologyKeys: + - topologyKeysValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.json new file mode 100644 index 0000000000000..38c75991ec27c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.json @@ -0,0 +1,63 @@ +{ + "kind": "CSIStorageCapacity", + "apiVersion": "storage.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "nodeTopology": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "storageClassName": "storageClassNameValue", + "capacity": "0", + "maximumVolumeSize": "0" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.pb new file mode 100644 index 0000000000000..29c136467f1ae Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.yaml new file mode 100644 index 0000000000000..4781557d73272 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.CSIStorageCapacity.yaml @@ -0,0 +1,45 @@ +apiVersion: storage.k8s.io/v1 +capacity: "0" +kind: CSIStorageCapacity +maximumVolumeSize: "0" +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +nodeTopology: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue +storageClassName: storageClassNameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.json new file mode 100644 index 0000000000000..82d3874817a23 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.json @@ -0,0 +1,68 @@ +{ + "kind": "StorageClass", + "apiVersion": "storage.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "provisioner": "provisionerValue", + "parameters": { + "parametersKey": "parametersValue" + }, + "reclaimPolicy": "reclaimPolicyValue", + "mountOptions": [ + "mountOptionsValue" + ], + "allowVolumeExpansion": true, + "volumeBindingMode": "volumeBindingModeValue", + "allowedTopologies": [ + { + "matchLabelExpressions": [ + { + "key": "keyValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.pb new file mode 100644 index 0000000000000..b1e32d4c891dd Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.yaml new file mode 100644 index 0000000000000..943b34f21db6a --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.StorageClass.yaml @@ -0,0 +1,47 @@ +allowVolumeExpansion: true +allowedTopologies: +- matchLabelExpressions: + - key: keyValue + values: + - valuesValue +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +mountOptions: +- mountOptionsValue +parameters: + parametersKey: parametersValue +provisioner: provisionerValue +reclaimPolicy: reclaimPolicyValue +volumeBindingMode: volumeBindingModeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.json new file mode 100644 index 0000000000000..2ae5ba4606e64 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.json @@ -0,0 +1,328 @@ +{ + "kind": "VolumeAttachment", + "apiVersion": "storage.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "attacher": "attacherValue", + "source": { + "persistentVolumeName": "persistentVolumeNameValue", + "inlineVolumeSpec": { + "capacity": { + "capacityKey": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true, + "endpointsNamespace": "endpointsNamespaceValue" + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "initiatorName": "initiatorNameValue" + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true, + "secretNamespace": "secretNamespaceValue" + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "local": { + "path": "pathValue", + "fsType": "fsTypeValue" + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + }, + "csi": { + "driver": "driverValue", + "volumeHandle": "volumeHandleValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "controllerPublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeStageSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodePublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "controllerExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "accessModes": [ + "accessModesValue" + ], + "claimRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "persistentVolumeReclaimPolicy": "persistentVolumeReclaimPolicyValue", + "storageClassName": "storageClassNameValue", + "mountOptions": [ + "mountOptionsValue" + ], + "volumeMode": "volumeModeValue", + "nodeAffinity": { + "required": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + }, + "nodeName": "nodeNameValue" + }, + "status": { + "attached": true, + "attachmentMetadata": { + "attachmentMetadataKey": "attachmentMetadataValue" + }, + "attachError": { + "time": "2001-01-01T01:01:01Z", + "message": "messageValue", + "errorCode": 3 + }, + "detachError": { + "time": "2001-01-01T01:01:01Z", + "message": "messageValue", + "errorCode": 3 + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.pb new file mode 100644 index 0000000000000..3bd058574449f Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.yaml new file mode 100644 index 0000000000000..8e6f6f227aa61 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttachment.yaml @@ -0,0 +1,251 @@ +apiVersion: storage.k8s.io/v1 +kind: VolumeAttachment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + attacher: attacherValue + nodeName: nodeNameValue + source: + inlineVolumeSpec: + accessModes: + - accessModesValue + awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + secretNamespace: secretNamespaceValue + shareName: shareNameValue + capacity: + capacityKey: "0" + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + volumeID: volumeIDValue + claimRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + csi: + controllerExpandSecretRef: + name: nameValue + namespace: namespaceValue + controllerPublishSecretRef: + name: nameValue + namespace: namespaceValue + driver: driverValue + fsType: fsTypeValue + nodeExpandSecretRef: + name: nameValue + namespace: namespaceValue + nodePublishSecretRef: + name: nameValue + namespace: namespaceValue + nodeStageSecretRef: + name: nameValue + namespace: namespaceValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + volumeHandle: volumeHandleValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + glusterfs: + endpoints: endpointsValue + endpointsNamespace: endpointsNamespaceValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + targetPortal: targetPortalValue + local: + fsType: fsTypeValue + path: pathValue + mountOptions: + - mountOptionsValue + nfs: + path: pathValue + readOnly: true + server: serverValue + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + persistentVolumeReclaimPolicy: persistentVolumeReclaimPolicyValue + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + storageClassName: storageClassNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + persistentVolumeName: persistentVolumeNameValue +status: + attachError: + errorCode: 3 + message: messageValue + time: "2001-01-01T01:01:01Z" + attached: true + attachmentMetadata: + attachmentMetadataKey: attachmentMetadataValue + detachError: + errorCode: 3 + message: messageValue + time: "2001-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.json new file mode 100644 index 0000000000000..b2ac8688bd4f2 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.json @@ -0,0 +1,50 @@ +{ + "kind": "VolumeAttributesClass", + "apiVersion": "storage.k8s.io/v1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "driverName": "driverNameValue", + "parameters": { + "parametersKey": "parametersValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.pb new file mode 100644 index 0000000000000..8e8ab76b9d9a6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.yaml new file mode 100644 index 0000000000000..95e68ff5861e9 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1.VolumeAttributesClass.yaml @@ -0,0 +1,37 @@ +apiVersion: storage.k8s.io/v1 +driverName: driverNameValue +kind: VolumeAttributesClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +parameters: + parametersKey: parametersValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.json new file mode 100644 index 0000000000000..e6dd47126ebea --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.json @@ -0,0 +1,63 @@ +{ + "kind": "CSIStorageCapacity", + "apiVersion": "storage.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "nodeTopology": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "storageClassName": "storageClassNameValue", + "capacity": "0", + "maximumVolumeSize": "0" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.pb new file mode 100644 index 0000000000000..374561deb0727 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.yaml new file mode 100644 index 0000000000000..2ca4299b4b9b0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.CSIStorageCapacity.yaml @@ -0,0 +1,45 @@ +apiVersion: storage.k8s.io/v1alpha1 +capacity: "0" +kind: CSIStorageCapacity +maximumVolumeSize: "0" +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +nodeTopology: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue +storageClassName: storageClassNameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.json new file mode 100644 index 0000000000000..473d4f31a3419 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.json @@ -0,0 +1,328 @@ +{ + "kind": "VolumeAttachment", + "apiVersion": "storage.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "attacher": "attacherValue", + "source": { + "persistentVolumeName": "persistentVolumeNameValue", + "inlineVolumeSpec": { + "capacity": { + "capacityKey": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true, + "endpointsNamespace": "endpointsNamespaceValue" + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "initiatorName": "initiatorNameValue" + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true, + "secretNamespace": "secretNamespaceValue" + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "local": { + "path": "pathValue", + "fsType": "fsTypeValue" + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + }, + "csi": { + "driver": "driverValue", + "volumeHandle": "volumeHandleValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "controllerPublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeStageSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodePublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "controllerExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "accessModes": [ + "accessModesValue" + ], + "claimRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "persistentVolumeReclaimPolicy": "persistentVolumeReclaimPolicyValue", + "storageClassName": "storageClassNameValue", + "mountOptions": [ + "mountOptionsValue" + ], + "volumeMode": "volumeModeValue", + "nodeAffinity": { + "required": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + }, + "nodeName": "nodeNameValue" + }, + "status": { + "attached": true, + "attachmentMetadata": { + "attachmentMetadataKey": "attachmentMetadataValue" + }, + "attachError": { + "time": "2001-01-01T01:01:01Z", + "message": "messageValue", + "errorCode": 3 + }, + "detachError": { + "time": "2001-01-01T01:01:01Z", + "message": "messageValue", + "errorCode": 3 + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.pb new file mode 100644 index 0000000000000..2dac24f387414 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.yaml new file mode 100644 index 0000000000000..22a9ab208d9e8 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttachment.yaml @@ -0,0 +1,251 @@ +apiVersion: storage.k8s.io/v1alpha1 +kind: VolumeAttachment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + attacher: attacherValue + nodeName: nodeNameValue + source: + inlineVolumeSpec: + accessModes: + - accessModesValue + awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + secretNamespace: secretNamespaceValue + shareName: shareNameValue + capacity: + capacityKey: "0" + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + volumeID: volumeIDValue + claimRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + csi: + controllerExpandSecretRef: + name: nameValue + namespace: namespaceValue + controllerPublishSecretRef: + name: nameValue + namespace: namespaceValue + driver: driverValue + fsType: fsTypeValue + nodeExpandSecretRef: + name: nameValue + namespace: namespaceValue + nodePublishSecretRef: + name: nameValue + namespace: namespaceValue + nodeStageSecretRef: + name: nameValue + namespace: namespaceValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + volumeHandle: volumeHandleValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + glusterfs: + endpoints: endpointsValue + endpointsNamespace: endpointsNamespaceValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + targetPortal: targetPortalValue + local: + fsType: fsTypeValue + path: pathValue + mountOptions: + - mountOptionsValue + nfs: + path: pathValue + readOnly: true + server: serverValue + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + persistentVolumeReclaimPolicy: persistentVolumeReclaimPolicyValue + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + storageClassName: storageClassNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + persistentVolumeName: persistentVolumeNameValue +status: + attachError: + errorCode: 3 + message: messageValue + time: "2001-01-01T01:01:01Z" + attached: true + attachmentMetadata: + attachmentMetadataKey: attachmentMetadataValue + detachError: + errorCode: 3 + message: messageValue + time: "2001-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.json new file mode 100644 index 0000000000000..bffbd2f81e21d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.json @@ -0,0 +1,50 @@ +{ + "kind": "VolumeAttributesClass", + "apiVersion": "storage.k8s.io/v1alpha1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "driverName": "driverNameValue", + "parameters": { + "parametersKey": "parametersValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.pb new file mode 100644 index 0000000000000..dc8e82be1fd2d Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.yaml new file mode 100644 index 0000000000000..3d339ccfbf5ce --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1alpha1.VolumeAttributesClass.yaml @@ -0,0 +1,37 @@ +apiVersion: storage.k8s.io/v1alpha1 +driverName: driverNameValue +kind: VolumeAttributesClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +parameters: + parametersKey: parametersValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.json new file mode 100644 index 0000000000000..687d508ad41e4 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.json @@ -0,0 +1,65 @@ +{ + "kind": "CSIDriver", + "apiVersion": "storage.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "attachRequired": true, + "podInfoOnMount": true, + "volumeLifecycleModes": [ + "volumeLifecycleModesValue" + ], + "storageCapacity": true, + "fsGroupPolicy": "fsGroupPolicyValue", + "tokenRequests": [ + { + "audience": "audienceValue", + "expirationSeconds": 2 + } + ], + "requiresRepublish": true, + "seLinuxMount": true, + "nodeAllocatableUpdatePeriodSeconds": 9, + "serviceAccountTokenInSecrets": true + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.pb new file mode 100644 index 0000000000000..7976348b86bcb Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.yaml new file mode 100644 index 0000000000000..7eb84a3419203 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIDriver.yaml @@ -0,0 +1,48 @@ +apiVersion: storage.k8s.io/v1beta1 +kind: CSIDriver +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + attachRequired: true + fsGroupPolicy: fsGroupPolicyValue + nodeAllocatableUpdatePeriodSeconds: 9 + podInfoOnMount: true + requiresRepublish: true + seLinuxMount: true + serviceAccountTokenInSecrets: true + storageCapacity: true + tokenRequests: + - audience: audienceValue + expirationSeconds: 2 + volumeLifecycleModes: + - volumeLifecycleModesValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.json new file mode 100644 index 0000000000000..1a46a629e669b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.json @@ -0,0 +1,60 @@ +{ + "kind": "CSINode", + "apiVersion": "storage.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "drivers": [ + { + "name": "nameValue", + "nodeID": "nodeIDValue", + "topologyKeys": [ + "topologyKeysValue" + ], + "allocatable": { + "count": 1 + } + } + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.pb new file mode 100644 index 0000000000000..ad7b167aeb4ac Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.yaml new file mode 100644 index 0000000000000..f6b1789d1ce28 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSINode.yaml @@ -0,0 +1,42 @@ +apiVersion: storage.k8s.io/v1beta1 +kind: CSINode +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + drivers: + - allocatable: + count: 1 + name: nameValue + nodeID: nodeIDValue + topologyKeys: + - topologyKeysValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.json new file mode 100644 index 0000000000000..059bd01659fde --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.json @@ -0,0 +1,63 @@ +{ + "kind": "CSIStorageCapacity", + "apiVersion": "storage.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "nodeTopology": { + "matchLabels": { + "matchLabelsKey": "matchLabelsValue" + }, + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + }, + "storageClassName": "storageClassNameValue", + "capacity": "0", + "maximumVolumeSize": "0" +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.pb new file mode 100644 index 0000000000000..a61d0d13094ff Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.yaml new file mode 100644 index 0000000000000..2e4a1c86e9d0b --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.CSIStorageCapacity.yaml @@ -0,0 +1,45 @@ +apiVersion: storage.k8s.io/v1beta1 +capacity: "0" +kind: CSIStorageCapacity +maximumVolumeSize: "0" +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +nodeTopology: + matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchLabels: + matchLabelsKey: matchLabelsValue +storageClassName: storageClassNameValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.json new file mode 100644 index 0000000000000..cc9825942c9e3 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.json @@ -0,0 +1,68 @@ +{ + "kind": "StorageClass", + "apiVersion": "storage.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "provisioner": "provisionerValue", + "parameters": { + "parametersKey": "parametersValue" + }, + "reclaimPolicy": "reclaimPolicyValue", + "mountOptions": [ + "mountOptionsValue" + ], + "allowVolumeExpansion": true, + "volumeBindingMode": "volumeBindingModeValue", + "allowedTopologies": [ + { + "matchLabelExpressions": [ + { + "key": "keyValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.pb new file mode 100644 index 0000000000000..2eab651cb75f6 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.yaml new file mode 100644 index 0000000000000..e0b0cf020760c --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.StorageClass.yaml @@ -0,0 +1,47 @@ +allowVolumeExpansion: true +allowedTopologies: +- matchLabelExpressions: + - key: keyValue + values: + - valuesValue +apiVersion: storage.k8s.io/v1beta1 +kind: StorageClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +mountOptions: +- mountOptionsValue +parameters: + parametersKey: parametersValue +provisioner: provisionerValue +reclaimPolicy: reclaimPolicyValue +volumeBindingMode: volumeBindingModeValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.json new file mode 100644 index 0000000000000..b153d0b643784 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.json @@ -0,0 +1,328 @@ +{ + "kind": "VolumeAttachment", + "apiVersion": "storage.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "attacher": "attacherValue", + "source": { + "persistentVolumeName": "persistentVolumeNameValue", + "inlineVolumeSpec": { + "capacity": { + "capacityKey": "0" + }, + "gcePersistentDisk": { + "pdName": "pdNameValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "awsElasticBlockStore": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "partition": 3, + "readOnly": true + }, + "hostPath": { + "path": "pathValue", + "type": "typeValue" + }, + "glusterfs": { + "endpoints": "endpointsValue", + "path": "pathValue", + "readOnly": true, + "endpointsNamespace": "endpointsNamespaceValue" + }, + "nfs": { + "server": "serverValue", + "path": "pathValue", + "readOnly": true + }, + "rbd": { + "monitors": [ + "monitorsValue" + ], + "image": "imageValue", + "fsType": "fsTypeValue", + "pool": "poolValue", + "user": "userValue", + "keyring": "keyringValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "iscsi": { + "targetPortal": "targetPortalValue", + "iqn": "iqnValue", + "lun": 3, + "iscsiInterface": "iscsiInterfaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "portals": [ + "portalsValue" + ], + "chapAuthDiscovery": true, + "chapAuthSession": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "initiatorName": "initiatorNameValue" + }, + "cinder": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "cephfs": { + "monitors": [ + "monitorsValue" + ], + "path": "pathValue", + "user": "userValue", + "secretFile": "secretFileValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true + }, + "fc": { + "targetWWNs": [ + "targetWWNsValue" + ], + "lun": 2, + "fsType": "fsTypeValue", + "readOnly": true, + "wwids": [ + "wwidsValue" + ] + }, + "flocker": { + "datasetName": "datasetNameValue", + "datasetUUID": "datasetUUIDValue" + }, + "flexVolume": { + "driver": "driverValue", + "fsType": "fsTypeValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "readOnly": true, + "options": { + "optionsKey": "optionsValue" + } + }, + "azureFile": { + "secretName": "secretNameValue", + "shareName": "shareNameValue", + "readOnly": true, + "secretNamespace": "secretNamespaceValue" + }, + "vsphereVolume": { + "volumePath": "volumePathValue", + "fsType": "fsTypeValue", + "storagePolicyName": "storagePolicyNameValue", + "storagePolicyID": "storagePolicyIDValue" + }, + "quobyte": { + "registry": "registryValue", + "volume": "volumeValue", + "readOnly": true, + "user": "userValue", + "group": "groupValue", + "tenant": "tenantValue" + }, + "azureDisk": { + "diskName": "diskNameValue", + "diskURI": "diskURIValue", + "cachingMode": "cachingModeValue", + "fsType": "fsTypeValue", + "readOnly": true, + "kind": "kindValue" + }, + "photonPersistentDisk": { + "pdID": "pdIDValue", + "fsType": "fsTypeValue" + }, + "portworxVolume": { + "volumeID": "volumeIDValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "scaleIO": { + "gateway": "gatewayValue", + "system": "systemValue", + "secretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "sslEnabled": true, + "protectionDomain": "protectionDomainValue", + "storagePool": "storagePoolValue", + "storageMode": "storageModeValue", + "volumeName": "volumeNameValue", + "fsType": "fsTypeValue", + "readOnly": true + }, + "local": { + "path": "pathValue", + "fsType": "fsTypeValue" + }, + "storageos": { + "volumeName": "volumeNameValue", + "volumeNamespace": "volumeNamespaceValue", + "fsType": "fsTypeValue", + "readOnly": true, + "secretRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + } + }, + "csi": { + "driver": "driverValue", + "volumeHandle": "volumeHandleValue", + "readOnly": true, + "fsType": "fsTypeValue", + "volumeAttributes": { + "volumeAttributesKey": "volumeAttributesValue" + }, + "controllerPublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeStageSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodePublishSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "controllerExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + }, + "nodeExpandSecretRef": { + "name": "nameValue", + "namespace": "namespaceValue" + } + }, + "accessModes": [ + "accessModesValue" + ], + "claimRef": { + "kind": "kindValue", + "namespace": "namespaceValue", + "name": "nameValue", + "uid": "uidValue", + "apiVersion": "apiVersionValue", + "resourceVersion": "resourceVersionValue", + "fieldPath": "fieldPathValue" + }, + "persistentVolumeReclaimPolicy": "persistentVolumeReclaimPolicyValue", + "storageClassName": "storageClassNameValue", + "mountOptions": [ + "mountOptionsValue" + ], + "volumeMode": "volumeModeValue", + "nodeAffinity": { + "required": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ], + "matchFields": [ + { + "key": "keyValue", + "operator": "operatorValue", + "values": [ + "valuesValue" + ] + } + ] + } + ] + } + }, + "volumeAttributesClassName": "volumeAttributesClassNameValue" + } + }, + "nodeName": "nodeNameValue" + }, + "status": { + "attached": true, + "attachmentMetadata": { + "attachmentMetadataKey": "attachmentMetadataValue" + }, + "attachError": { + "time": "2001-01-01T01:01:01Z", + "message": "messageValue", + "errorCode": 3 + }, + "detachError": { + "time": "2001-01-01T01:01:01Z", + "message": "messageValue", + "errorCode": 3 + } + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.pb new file mode 100644 index 0000000000000..2f900531c59a7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.yaml new file mode 100644 index 0000000000000..f4cf507f6e4c0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttachment.yaml @@ -0,0 +1,251 @@ +apiVersion: storage.k8s.io/v1beta1 +kind: VolumeAttachment +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + attacher: attacherValue + nodeName: nodeNameValue + source: + inlineVolumeSpec: + accessModes: + - accessModesValue + awsElasticBlockStore: + fsType: fsTypeValue + partition: 3 + readOnly: true + volumeID: volumeIDValue + azureDisk: + cachingMode: cachingModeValue + diskName: diskNameValue + diskURI: diskURIValue + fsType: fsTypeValue + kind: kindValue + readOnly: true + azureFile: + readOnly: true + secretName: secretNameValue + secretNamespace: secretNamespaceValue + shareName: shareNameValue + capacity: + capacityKey: "0" + cephfs: + monitors: + - monitorsValue + path: pathValue + readOnly: true + secretFile: secretFileValue + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + cinder: + fsType: fsTypeValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + volumeID: volumeIDValue + claimRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + csi: + controllerExpandSecretRef: + name: nameValue + namespace: namespaceValue + controllerPublishSecretRef: + name: nameValue + namespace: namespaceValue + driver: driverValue + fsType: fsTypeValue + nodeExpandSecretRef: + name: nameValue + namespace: namespaceValue + nodePublishSecretRef: + name: nameValue + namespace: namespaceValue + nodeStageSecretRef: + name: nameValue + namespace: namespaceValue + readOnly: true + volumeAttributes: + volumeAttributesKey: volumeAttributesValue + volumeHandle: volumeHandleValue + fc: + fsType: fsTypeValue + lun: 2 + readOnly: true + targetWWNs: + - targetWWNsValue + wwids: + - wwidsValue + flexVolume: + driver: driverValue + fsType: fsTypeValue + options: + optionsKey: optionsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + flocker: + datasetName: datasetNameValue + datasetUUID: datasetUUIDValue + gcePersistentDisk: + fsType: fsTypeValue + partition: 3 + pdName: pdNameValue + readOnly: true + glusterfs: + endpoints: endpointsValue + endpointsNamespace: endpointsNamespaceValue + path: pathValue + readOnly: true + hostPath: + path: pathValue + type: typeValue + iscsi: + chapAuthDiscovery: true + chapAuthSession: true + fsType: fsTypeValue + initiatorName: initiatorNameValue + iqn: iqnValue + iscsiInterface: iscsiInterfaceValue + lun: 3 + portals: + - portalsValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + targetPortal: targetPortalValue + local: + fsType: fsTypeValue + path: pathValue + mountOptions: + - mountOptionsValue + nfs: + path: pathValue + readOnly: true + server: serverValue + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: keyValue + operator: operatorValue + values: + - valuesValue + matchFields: + - key: keyValue + operator: operatorValue + values: + - valuesValue + persistentVolumeReclaimPolicy: persistentVolumeReclaimPolicyValue + photonPersistentDisk: + fsType: fsTypeValue + pdID: pdIDValue + portworxVolume: + fsType: fsTypeValue + readOnly: true + volumeID: volumeIDValue + quobyte: + group: groupValue + readOnly: true + registry: registryValue + tenant: tenantValue + user: userValue + volume: volumeValue + rbd: + fsType: fsTypeValue + image: imageValue + keyring: keyringValue + monitors: + - monitorsValue + pool: poolValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + user: userValue + scaleIO: + fsType: fsTypeValue + gateway: gatewayValue + protectionDomain: protectionDomainValue + readOnly: true + secretRef: + name: nameValue + namespace: namespaceValue + sslEnabled: true + storageMode: storageModeValue + storagePool: storagePoolValue + system: systemValue + volumeName: volumeNameValue + storageClassName: storageClassNameValue + storageos: + fsType: fsTypeValue + readOnly: true + secretRef: + apiVersion: apiVersionValue + fieldPath: fieldPathValue + kind: kindValue + name: nameValue + namespace: namespaceValue + resourceVersion: resourceVersionValue + uid: uidValue + volumeName: volumeNameValue + volumeNamespace: volumeNamespaceValue + volumeAttributesClassName: volumeAttributesClassNameValue + volumeMode: volumeModeValue + vsphereVolume: + fsType: fsTypeValue + storagePolicyID: storagePolicyIDValue + storagePolicyName: storagePolicyNameValue + volumePath: volumePathValue + persistentVolumeName: persistentVolumeNameValue +status: + attachError: + errorCode: 3 + message: messageValue + time: "2001-01-01T01:01:01Z" + attached: true + attachmentMetadata: + attachmentMetadataKey: attachmentMetadataValue + detachError: + errorCode: 3 + message: messageValue + time: "2001-01-01T01:01:01Z" diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.json b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.json new file mode 100644 index 0000000000000..f5c0d3435fd66 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.json @@ -0,0 +1,50 @@ +{ + "kind": "VolumeAttributesClass", + "apiVersion": "storage.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "driverName": "driverNameValue", + "parameters": { + "parametersKey": "parametersValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.pb new file mode 100644 index 0000000000000..f2990f48e21e7 Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.yaml new file mode 100644 index 0000000000000..9636204e341a0 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storage.k8s.io.v1beta1.VolumeAttributesClass.yaml @@ -0,0 +1,37 @@ +apiVersion: storage.k8s.io/v1beta1 +driverName: driverNameValue +kind: VolumeAttributesClass +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +parameters: + parametersKey: parametersValue diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.json b/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.json new file mode 100644 index 0000000000000..4b805fe541d68 --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.json @@ -0,0 +1,65 @@ +{ + "kind": "StorageVersionMigration", + "apiVersion": "storagemigration.k8s.io/v1beta1", + "metadata": { + "name": "nameValue", + "generateName": "generateNameValue", + "namespace": "namespaceValue", + "selfLink": "selfLinkValue", + "uid": "uidValue", + "resourceVersion": "resourceVersionValue", + "generation": 7, + "creationTimestamp": "2008-01-01T01:01:01Z", + "deletionTimestamp": "2009-01-01T01:01:01Z", + "deletionGracePeriodSeconds": 10, + "labels": { + "labelsKey": "labelsValue" + }, + "annotations": { + "annotationsKey": "annotationsValue" + }, + "ownerReferences": [ + { + "apiVersion": "apiVersionValue", + "kind": "kindValue", + "name": "nameValue", + "uid": "uidValue", + "controller": true, + "blockOwnerDeletion": true + } + ], + "finalizers": [ + "finalizersValue" + ], + "managedFields": [ + { + "manager": "managerValue", + "operation": "operationValue", + "apiVersion": "apiVersionValue", + "time": "2004-01-01T01:01:01Z", + "fieldsType": "fieldsTypeValue", + "fieldsV1": {}, + "subresource": "subresourceValue" + } + ] + }, + "spec": { + "resource": { + "group": "groupValue", + "resource": "resourceValue" + } + }, + "status": { + "conditions": [ + { + "type": "typeValue", + "status": "statusValue", + "observedGeneration": 3, + "lastTransitionTime": "2004-01-01T01:01:01Z", + "reason": "reasonValue", + "message": "messageValue" + } + ], + "resourceVersion": "resourceVersionValue" + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.pb b/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.pb new file mode 100644 index 0000000000000..dc4895e52c15b Binary files /dev/null and b/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.pb differ diff --git a/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.yaml b/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.yaml new file mode 100644 index 0000000000000..8c34b8f4a9f0d --- /dev/null +++ b/staging/src/k8s.io/api/testdata/v1.35.0/storagemigration.k8s.io.v1beta1.StorageVersionMigration.yaml @@ -0,0 +1,47 @@ +apiVersion: storagemigration.k8s.io/v1beta1 +kind: StorageVersionMigration +metadata: + annotations: + annotationsKey: annotationsValue + creationTimestamp: "2008-01-01T01:01:01Z" + deletionGracePeriodSeconds: 10 + deletionTimestamp: "2009-01-01T01:01:01Z" + finalizers: + - finalizersValue + generateName: generateNameValue + generation: 7 + labels: + labelsKey: labelsValue + managedFields: + - apiVersion: apiVersionValue + fieldsType: fieldsTypeValue + fieldsV1: {} + manager: managerValue + operation: operationValue + subresource: subresourceValue + time: "2004-01-01T01:01:01Z" + name: nameValue + namespace: namespaceValue + ownerReferences: + - apiVersion: apiVersionValue + blockOwnerDeletion: true + controller: true + kind: kindValue + name: nameValue + uid: uidValue + resourceVersion: resourceVersionValue + selfLink: selfLinkValue + uid: uidValue +spec: + resource: + group: groupValue + resource: resourceValue +status: + conditions: + - lastTransitionTime: "2004-01-01T01:01:01Z" + message: messageValue + observedGeneration: 3 + reason: reasonValue + status: statusValue + type: typeValue + resourceVersion: resourceVersionValue diff --git a/staging/src/k8s.io/apiextensions-apiserver/OWNERS b/staging/src/k8s.io/apiextensions-apiserver/OWNERS index e02ae9ca8b176..f1c1486ecc554 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/OWNERS +++ b/staging/src/k8s.io/apiextensions-apiserver/OWNERS @@ -8,7 +8,6 @@ reviewers: - deads2k - sttts - yue9944882 - - logicalhan - jpbetz - roycaihw - alexzielenski diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/tools.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/tools.go index 73ab6ef11b396..3e2ab980786ca 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/tools.go +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/tools.go @@ -1,5 +1,4 @@ //go:build tools -// +build tools /* Copyright 2019 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/clientset_generated.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/clientset_generated.go index 07601ef8e67ae..e4d010f750f06 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -80,7 +80,7 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } -// IsWatchListSemanticsSupported informs the reflector that this client +// IsWatchListSemanticsUnSupported informs the reflector that this client // doesn't support WatchList semantics. // // This is a synthetic method whose sole purpose is to satisfy the optional diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/example.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/example.go index b3c461ecfb9d1..1b33cf63506c1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/example.go +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/example.go @@ -28,6 +28,7 @@ import ( crv1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" ) @@ -49,48 +50,61 @@ type exampleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewExampleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredExampleInformer(client, namespace, resyncPeriod, indexers, nil) + return NewExampleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredExampleInformer constructs a new informer for Example type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredExampleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewExampleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewExampleInformerWithOptions constructs a new informer for Example type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewExampleInformerWithOptions(client versioned.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "cr.example.apiextensions.k8s.io", Version: "v1", Resource: "examples"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CrV1().Examples(namespace).List(context.Background(), options) + return client.CrV1().Examples(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CrV1().Examples(namespace).Watch(context.Background(), options) + return client.CrV1().Examples(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CrV1().Examples(namespace).List(ctx, options) + return client.CrV1().Examples(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CrV1().Examples(namespace).Watch(ctx, options) + return client.CrV1().Examples(namespace).Watch(ctx, opts) }, }, client), &apiscrv1.Example{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *exampleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredExampleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewExampleInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *exampleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/factory.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/factory.go index 1fe528901b30a..1d9136ad6bb21 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/factory.go +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/factory.go @@ -19,6 +19,7 @@ limitations under the License. package externalversions import ( + context "context" reflect "reflect" sync "sync" time "time" @@ -29,6 +30,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" + wait "k8s.io/apimachinery/pkg/util/wait" cache "k8s.io/client-go/tools/cache" ) @@ -43,6 +45,7 @@ type sharedInformerFactory struct { defaultResync time.Duration customResync map[reflect.Type]time.Duration transform cache.TransformFunc + informerName *cache.InformerName informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -89,6 +92,21 @@ func WithTransform(transform cache.TransformFunc) SharedInformerOption { } } +// WithInformerName sets the InformerName for informer identity used in metrics. +// The InformerName must be created via cache.NewInformerName() at startup, +// which validates global uniqueness. Each informer type will register its +// GVR under this name. +func WithInformerName(informerName *cache.InformerName) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.informerName = informerName + return factory + } +} + +func (f *sharedInformerFactory) InformerName() *cache.InformerName { + return f.informerName +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -123,6 +141,10 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy } func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext(wait.ContextForChannel(stopCh)) +} + +func (f *sharedInformerFactory) StartWithContext(ctx context.Context) { f.lock.Lock() defer f.lock.Unlock() @@ -132,15 +154,9 @@ func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + f.wg.Go(func() { + informer.RunWithContext(ctx) + }) f.startedInformers[informerType] = true } } @@ -153,9 +169,15 @@ func (f *sharedInformerFactory) Shutdown() { // Will return immediately if there is nothing to wait for. f.wg.Wait() + f.informerName.Release() } func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + result := f.WaitForCacheSyncWithContext(wait.ContextForChannel(stopCh)) + return result.Synced +} + +func (f *sharedInformerFactory) WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() defer f.lock.Unlock() @@ -169,10 +191,31 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return informers }() - res := map[reflect.Type]bool{} + // Wait for informers to sync, without polling. + cacheSyncs := make([]cache.DoneChecker, 0, len(informers)) + for _, informer := range informers { + cacheSyncs = append(cacheSyncs, informer.HasSyncedChecker()) + } + cache.WaitFor(ctx, "" /* no logging */, cacheSyncs...) + + res := cache.SyncResult{ + Synced: make(map[reflect.Type]bool, len(informers)), + } + failed := false for informType, informer := range informers { - res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + hasSynced := informer.HasSynced() + if !hasSynced { + failed = true + } + res.Synced[informType] = hasSynced } + if failed { + // context.Cause is more informative than ctx.Err(). + // This must be non-nil, otherwise WaitFor wouldn't have stopped + // prematurely. + res.Err = context.Cause(ctx) + } + return res } @@ -194,7 +237,9 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) - informer.SetTransform(f.transform) + if f.transform != nil { + informer.SetTransform(f.transform) + } f.informers[informerType] = informer return informer @@ -211,27 +256,46 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // defer factory.WaitForStop() // Returns immediately if nothing was started. // genericInformer := factory.ForResource(resource) // typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } +// handle, err := typeInformer.Informer().AddEventHandler(...) +// if err != nil { +// return fmt.Errorf("register event handler: %v", err) +// } +// defer typeInformer.Informer().RemoveEventHandler(handle) // Avoids leaking goroutines. +// factory.StartWithContext(ctx) // Start processing these informers. +// synced := factory.WaitForCacheSyncWithContext(ctx) +// if err := synced.AsError(); err != nil { +// return err +// } +// for v := range synced { +// // Only if desired log some information similar to this. +// fmt.Fprintf(os.Stdout, "cache synced: %s", v) +// } +// +// // Also make sure that all of the initial cache events have been delivered. +// if !WaitFor(ctx, "event handler sync", handle.HasSyncedChecker()) { +// // Must have failed because of context. +// return fmt.Errorf("sync event handler: %w", context.Cause(ctx)) // } // // // Creating informers can also be created after Start, but then // // Start must be called again: // anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) +// factory.StartWithContext(ctx) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + // + // Contextual logging: StartWithContext should be used instead of Start in code which supports contextual logging. Start(stopCh <-chan struct{}) + // StartWithContext initializes all requested informers. They are handled in goroutines + // which run until the context gets canceled. + // Warning: StartWithContext does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + StartWithContext(ctx context.Context) + // Shutdown marks a factory as shutting down. At that point no new // informers can be started anymore and Start will return without // doing anything. @@ -246,8 +310,14 @@ type SharedInformerFactory interface { // WaitForCacheSync blocks until all started informers' caches were synced // or the stop channel gets closed. + // + // Contextual logging: WaitForCacheSync should be used instead of WaitForCacheSync in code which supports contextual logging. It also returns a more useful result. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // WaitForCacheSyncWithContext blocks until all started informers' caches were synced + // or the context gets canceled. + WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult + // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go index c39e812f050e5..a7d4b87338a4f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -34,7 +34,26 @@ type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexI type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer + InformerName() *cache.InformerName } // TweakListOptionsFunc is a function that transforms a v1.ListOptions. type TweakListOptionsFunc func(*v1.ListOptions) + +// InformerOptions holds the options for creating an informer. +type InformerOptions struct { + // ResyncPeriod is the resync period for this informer. + // If not set, defaults to 0 (no resync). + ResyncPeriod time.Duration + + // Indexers are the indexers for this informer. + Indexers cache.Indexers + + // InformerName is used to uniquely identify this informer for metrics. + // If not set, metrics will not be published for this informer. + // Use cache.NewInformerName() to create an InformerName at startup. + InformerName *cache.InformerName + + // TweakListOptions is an optional function to modify the list options. + TweakListOptions TweakListOptionsFunc +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.mod b/staging/src/k8s.io/apiextensions-apiserver/go.mod index c11def68122fb..4bbcdeac31ac1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.mod +++ b/staging/src/k8s.io/apiextensions-apiserver/go.mod @@ -2,56 +2,56 @@ module k8s.io/apiextensions-apiserver -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( - github.com/emicklei/go-restful/v3 v3.12.2 + github.com/emicklei/go-restful/v3 v3.13.0 github.com/fxamacker/cbor/v2 v2.9.0 github.com/google/cel-go v0.26.0 github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9 - github.com/spf13/cobra v1.10.0 + github.com/openshift/api v0.0.0-20260612223652-30cac1b00eaf + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/client/pkg/v3 v3.6.5 - go.etcd.io/etcd/client/v3 v3.6.5 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 + go.etcd.io/etcd/client/pkg/v3 v3.6.8 + go.etcd.io/etcd/client/v3 v3.6.8 + go.opentelemetry.io/otel v1.41.0 + go.opentelemetry.io/otel/trace v1.41.0 go.yaml.in/yaml/v2 v2.4.3 - golang.org/x/sync v0.18.0 - golang.org/x/text v0.31.0 - google.golang.org/grpc v1.72.2 - google.golang.org/protobuf v1.36.8 + golang.org/x/sync v0.19.0 + golang.org/x/text v0.33.0 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/evanphx/json-patch.v4 v4.13.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/api v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/apiserver v0.36.2 + k8s.io/client-go v0.36.2 k8s.io/code-generator v0.0.0 - k8s.io/component-base v0.35.1 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/component-base v0.36.2 + k8s.io/klog/v2 v2.140.0 + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 sigs.k8s.io/randfill v1.0.0 - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 sigs.k8s.io/yaml v1.6.0 ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -62,31 +62,30 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // 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.27.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern 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/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 // indirect + github.com/openshift/library-go v0.0.0-20260612230159-06432ea1b022 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect @@ -94,43 +93,46 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/api/v3 v3.6.5 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.5 // indirect - go.etcd.io/etcd/server/v3 v3.6.5 // indirect + go.etcd.io/etcd/api/v3 v3.6.8 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.8 // indirect + go.etcd.io/etcd/server/v3 v3.6.8 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // 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/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // 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/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.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/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.38.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.41.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 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/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect - k8s.io/kms v0.35.1 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + k8s.io/kms v0.36.2 // indirect + k8s.io/streaming v0.36.2 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 + github.com/openshift/api => github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf + github.com/openshift/client-go => github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27 + github.com/openshift/library-go => github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery k8s.io/apiserver => ../apiserver @@ -139,4 +141,5 @@ replace ( k8s.io/component-base => ../component-base k8s.io/kms => ../kms k8s.io/kube-aggregator => ../kube-aggregator + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.sum b/staging/src/k8s.io/apiextensions-apiserver/go.sum index c8a6d6e0a660b..a5f95ef977cef 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.sum +++ b/staging/src/k8s.io/apiextensions-apiserver/go.sum @@ -1,18 +1,17 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1/go.mod h1:novQBstnxcGpfKf8qGRATqn1anQKwMJIbH5Q581jibU= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= -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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2/go.mod h1:XyjUkMA8GN+tOOPXvnbi3XuRxWFvTJntqvTFnjmhzbk= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 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= @@ -22,19 +21,19 @@ 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= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 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/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -48,12 +47,12 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +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.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -63,7 +62,7 @@ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui72 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-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -80,14 +79,12 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0/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.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +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 v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -106,7 +103,6 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -114,16 +110,13 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS 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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +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.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -135,6 +128,11 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf h1:ZNPdLNPryuQVSUzna4We8hoCmzEduyxIbPa2tQgk0a0= +github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf/go.mod h1:FS918VnrERibSebUVMka1NP+eyrKGi5ry4nl/BXAKbs= +github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27/go.mod h1:bOkl2x9t+UlXxD8riuhEi0MbjjYnXuYCSzuDXTIA4wI= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 h1:Pk6nmvZxrSQ9IlpsOC9FQiV45idIDLTN3hft7dzxdSA= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022/go.mod h1:iGCkgqyOiqzvkTHEyxVf+RmxzvNNfEFR/AJLoFSxeG0= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -152,8 +150,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -167,19 +165,11 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= -github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9 h1:yb8ul1HPFYhO04yp0D8T/qSySZnKv210f4nE//i/Bdg= -github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= -github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a/go.mod h1:V933kvY/cb/Un7UCEOhXHUySNX327u7Epe8g9KNqg2Q= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 h1:AHjJETxL4n2ocMHBY7Epqex574puUSJjKIUkbasYAKI= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3/go.mod h1:gKG9lctU0yEftSoT3DUyeIWz1oAgF0EHUpwI4pnCo4o= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= @@ -193,10 +183,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -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/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.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -207,12 +197,11 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= -github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= -github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= 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= @@ -238,53 +227,52 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -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.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +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/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +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.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +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.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -292,29 +280,29 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -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/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +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/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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -323,25 +311,25 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= 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= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -350,14 +338,16 @@ 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= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +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/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -375,20 +365,20 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -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/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +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-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.protomessage.pb.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.protomessage.pb.go deleted file mode 100644 index 7dc6ed2bc584b..0000000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,76 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*ConversionRequest) ProtoMessage() {} - -func (*ConversionResponse) ProtoMessage() {} - -func (*ConversionReview) ProtoMessage() {} - -func (*CustomResourceColumnDefinition) ProtoMessage() {} - -func (*CustomResourceConversion) ProtoMessage() {} - -func (*CustomResourceDefinition) ProtoMessage() {} - -func (*CustomResourceDefinitionCondition) ProtoMessage() {} - -func (*CustomResourceDefinitionList) ProtoMessage() {} - -func (*CustomResourceDefinitionNames) ProtoMessage() {} - -func (*CustomResourceDefinitionSpec) ProtoMessage() {} - -func (*CustomResourceDefinitionStatus) ProtoMessage() {} - -func (*CustomResourceDefinitionVersion) ProtoMessage() {} - -func (*CustomResourceSubresourceScale) ProtoMessage() {} - -func (*CustomResourceSubresourceStatus) ProtoMessage() {} - -func (*CustomResourceSubresources) ProtoMessage() {} - -func (*CustomResourceValidation) ProtoMessage() {} - -func (*ExternalDocumentation) ProtoMessage() {} - -func (*JSON) ProtoMessage() {} - -func (*JSONSchemaProps) ProtoMessage() {} - -func (*JSONSchemaPropsOrArray) ProtoMessage() {} - -func (*JSONSchemaPropsOrBool) ProtoMessage() {} - -func (*JSONSchemaPropsOrStringArray) ProtoMessage() {} - -func (*SelectableField) ProtoMessage() {} - -func (*ServiceReference) ProtoMessage() {} - -func (*ValidationRule) ProtoMessage() {} - -func (*WebhookClientConfig) ProtoMessage() {} - -func (*WebhookConversion) ProtoMessage() {} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go index 764726b6e9a6b..b3a8ce7ef4610 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go @@ -332,6 +332,9 @@ const ( // be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false. // See https://github.com/kubernetes/enhancements/pull/1111 for more details. KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant" + // StorageMigrating indicates that the underlying storage version of the CRD + // is undergoing migration. + StorageMigrating CustomResourceDefinitionConditionType = "StorageMigrating" ) // CustomResourceDefinitionCondition contains details for the current condition of this pod. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index 997d32f31e1e4..0000000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*ConversionRequest) ProtoMessage() {} - -func (*ConversionResponse) ProtoMessage() {} - -func (*ConversionReview) ProtoMessage() {} - -func (*CustomResourceColumnDefinition) ProtoMessage() {} - -func (*CustomResourceConversion) ProtoMessage() {} - -func (*CustomResourceDefinition) ProtoMessage() {} - -func (*CustomResourceDefinitionCondition) ProtoMessage() {} - -func (*CustomResourceDefinitionList) ProtoMessage() {} - -func (*CustomResourceDefinitionNames) ProtoMessage() {} - -func (*CustomResourceDefinitionSpec) ProtoMessage() {} - -func (*CustomResourceDefinitionStatus) ProtoMessage() {} - -func (*CustomResourceDefinitionVersion) ProtoMessage() {} - -func (*CustomResourceSubresourceScale) ProtoMessage() {} - -func (*CustomResourceSubresourceStatus) ProtoMessage() {} - -func (*CustomResourceSubresources) ProtoMessage() {} - -func (*CustomResourceValidation) ProtoMessage() {} - -func (*ExternalDocumentation) ProtoMessage() {} - -func (*JSON) ProtoMessage() {} - -func (*JSONSchemaProps) ProtoMessage() {} - -func (*JSONSchemaPropsOrArray) ProtoMessage() {} - -func (*JSONSchemaPropsOrBool) ProtoMessage() {} - -func (*JSONSchemaPropsOrStringArray) ProtoMessage() {} - -func (*SelectableField) ProtoMessage() {} - -func (*ServiceReference) ProtoMessage() {} - -func (*ValidationRule) ProtoMessage() {} - -func (*WebhookClientConfig) ProtoMessage() {} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.json b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.json index 8943464460547..6a2bd32c586c6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.json +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.json @@ -36,7 +36,10 @@ "selfLink": "selfLinkValue", "resourceVersion": "resourceVersionValue", "continue": "continueValue", - "remainingItemCount": 4 + "remainingItemCount": 4, + "shardInfo": { + "selector": "selectorValue" + } }, "status": "statusValue", "message": "messageValue", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.pb b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.pb index 0f2e34ade0b36..3a83da0579ec6 100644 Binary files a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.pb and b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.pb differ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.yaml b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.yaml index 5e2bf422a3c49..3325e1e251085 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.yaml +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1.ConversionReview.yaml @@ -36,6 +36,8 @@ response: remainingItemCount: 4 resourceVersion: resourceVersionValue selfLink: selfLinkValue + shardInfo: + selector: selectorValue reason: reasonValue status: statusValue uid: uidValue diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.json b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.json index 2b129c4e550ea..b01e6dc41ba2c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.json +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.json @@ -36,7 +36,10 @@ "selfLink": "selfLinkValue", "resourceVersion": "resourceVersionValue", "continue": "continueValue", - "remainingItemCount": 4 + "remainingItemCount": 4, + "shardInfo": { + "selector": "selectorValue" + } }, "status": "statusValue", "message": "messageValue", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.pb b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.pb index e7acab7bf0e7a..068b4fcc58308 100644 Binary files a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.pb and b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.pb differ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.yaml b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.yaml index 6352966430621..0d5db346ac5c0 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.yaml +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/testdata/HEAD/apiextensions.k8s.io.v1beta1.ConversionReview.yaml @@ -36,6 +36,8 @@ response: remainingItemCount: 4 resourceVersion: resourceVersionValue selfLink: selfLinkValue + shardInfo: + selector: selectorValue reason: reasonValue status: statusValue uid: uidValue diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go index e27495de54e2a..de3fddf316a9f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go @@ -246,7 +246,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) go establishingController.RunWithContext(hookContext) go nonStructuralSchemaController.RunWithContext(5, hookContext) go apiApprovalController.RunWithContext(5, hookContext) - go finalizingController.Run(5, hookContext.Done()) + go finalizingController.RunWithContext(5, hookContext) discoverySyncedCh := make(chan struct{}) go discoveryController.Run(hookContext.Done(), discoverySyncedCh) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/celcoststability_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/celcoststability_test.go index dc15b6159ad67..3aed98c1d88d7 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/celcoststability_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/celcoststability_test.go @@ -1235,12 +1235,9 @@ func TestCelCostStability(t *testing.T) { } for _, tt := range cases { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() for validRule, expectedCost := range tt.expectCost { - validRule := validRule - expectedCost := expectedCost testName := validRule if len(testName) > 127 { testName = testName[:127] @@ -2093,12 +2090,9 @@ func TestCelEstimatedCostStability(t *testing.T) { } for _, tt := range cases { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() for validRule, expectedCost := range tt.expectCost { - validRule := validRule - expectedCost := expectedCost testName := validRule if len(testName) > 127 { testName = testName[:127] diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/adaptor.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/adaptor.go index a66ab42937c80..04cb0f3fe869d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/adaptor.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/adaptor.go @@ -229,7 +229,6 @@ func (s *Structural) XListMapKeys() []string { func (s *Structural) AllOf() []common.Schema { var res []common.Schema for _, subSchema := range s.Structural.ValueValidation.AllOf { - subSchema := subSchema res = append(res, nestedValueValidationToStructural(&subSchema)) } return res @@ -238,7 +237,6 @@ func (s *Structural) AllOf() []common.Schema { func (s *Structural) AnyOf() []common.Schema { var res []common.Schema for _, subSchema := range s.Structural.ValueValidation.AnyOf { - subSchema := subSchema res = append(res, nestedValueValidationToStructural(&subSchema)) } return res @@ -247,7 +245,6 @@ func (s *Structural) AnyOf() []common.Schema { func (s *Structural) OneOf() []common.Schema { var res []common.Schema for _, subSchema := range s.Structural.ValueValidation.OneOf { - subSchema := subSchema res = append(res, nestedValueValidationToStructural(&subSchema)) } return res diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go index 006db6f149ef9..9f9fecab47898 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go @@ -2196,7 +2196,6 @@ func TestValidationExpressions(t *testing.T) { } for i := range tests { - i := i t.Run(tests[i].name, func(t *testing.T) { t.Parallel() tt := tests[i] @@ -2918,7 +2917,6 @@ func TestValidationExpressionsAtSchemaLevels(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx := context.TODO() @@ -4593,7 +4591,6 @@ func TestOptionalOldSelf(t *testing.T) { } for _, tt := range tests { - tt := tt tp := true for i := range tt.schema.XValidations { tt.schema.XValidations[i].OptionalOldSelf = &tp @@ -4749,7 +4746,6 @@ func TestOptionalOldSelfCheckForNull(t *testing.T) { } for _, tt := range tests { - tt := tt tp := true for i := range tt.schema.XValidations { tt.schema.XValidations[i].OptionalOldSelf = &tp diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go index 78350c135fceb..a4a35354088ab 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go @@ -35,10 +35,6 @@ import ( // - dependencies // - additionalItems // - definitions. -// -// The follow fields are not preserved: -// - externalDocs -// - example. func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) { if s == nil { return nil, nil @@ -126,6 +122,15 @@ func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) { if s.Default != nil { g.Default = JSON{interface{}(*s.Default)} } + if s.ExternalDocs != nil { + g.ExternalDocs = &ExternalDocumentation{ + Description: s.ExternalDocs.Description, + URL: s.ExternalDocs.URL, + } + } + if s.Example != nil { + g.Example = JSON{interface{}(*s.Example)} + } return g, nil } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert_test.go index 5eb7fba2727a2..e16f0f2cad54b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert_test.go @@ -70,10 +70,6 @@ func TestStructuralRoundtripOrError(t *testing.T) { origSchema := &apiextensions.JSONSchemaProps{} x := reflect.ValueOf(origSchema).Elem() n := rand.Intn(x.NumField()) - if name := x.Type().Field(n).Name; name == "Example" || name == "ExternalDocs" { - // we drop these intentionally - continue - } f.Fill(x.Field(n).Addr().Interface()) // it roundtrips or NewStructural errors out. We should never drop anything diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi.go index df78ba77e614a..a8b29a1e61dcf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi.go @@ -62,6 +62,13 @@ func (g *Generic) toKubeOpenAPI(ret *spec.Schema) { ret.Description = g.Description ret.Title = g.Title ret.Default = g.Default.Object + if g.ExternalDocs != nil { + ret.ExternalDocs = &spec.ExternalDocumentation{ + Description: g.ExternalDocs.Description, + URL: g.ExternalDocs.URL, + } + } + ret.Example = g.Example.Object } func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi_test.go index b327e52d5612a..1ffc89e1f664e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi_test.go @@ -30,6 +30,23 @@ import ( "k8s.io/apimachinery/pkg/util/json" ) +// isJSONEmptyForOmitempty returns true if the JSON value would be dropped by the +// kube-openapi v2 JSON encoder's omitempty tag. Unlike omitzero (used for Default), +// the Example field uses omitempty which considers empty strings, empty slices, +// and empty maps as empty. +func isJSONEmptyForOmitempty(j JSON) bool { + switch v := j.Object.(type) { + case string: + return v == "" + case []interface{}: + return len(v) == 0 + case map[string]interface{}: + return len(v) == 0 + default: + return j.Object == nil + } +} + func TestStructuralKubeOpenAPIRoundtrip(t *testing.T) { f := randfill.New() seed := time.Now().UnixNano() @@ -64,13 +81,21 @@ func TestStructuralKubeOpenAPIRoundtrip(t *testing.T) { f.Fill(orig) // normalize Structural.ValueValidation to zero values if it was nil before + // and normalize Example values that are considered empty by omitempty + // (the kube-openapi Example field uses omitempty, not omitzero, so + // empty strings/slices/maps get dropped during JSON roundtrip) normalizer := Visitor{ Structural: func(s *Structural) bool { + changed := false if s.ValueValidation == nil { s.ValueValidation = &ValueValidation{} - return true + changed = true + } + if isJSONEmptyForOmitempty(s.Generic.Example) { + s.Generic.Example = JSON{} + changed = true } - return false + return changed }, } normalizer.Visit(orig) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce_test.go index 620983e06accd..62881ae2cc410 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/equality" @@ -372,7 +373,7 @@ func TestGetObjectMetaNils(t *testing.T) { t.Errorf("expected null json generateName value to be read as \"\" string, but got: %q", o.GenerateName) } if o.Generation != 0 { - t.Errorf("expected null json generation value to be read as zero, but got: %q", o.Generation) + t.Errorf("expected null json generation value to be read as zero, but got: %d", o.Generation) } if got, expected := o.Labels, map[string]string{"foo": ""}; !reflect.DeepEqual(got, expected) { t.Errorf("unexpected labels, expected=%#v, got=%#v", expected, got) @@ -392,7 +393,7 @@ func TestGetObjectMetaNils(t *testing.T) { t.Errorf("expected generatedName to be %q, got %q", expected, got) } if got, expected := o.Generation, pod.ObjectMeta.Generation; got != expected { - t.Errorf("expected generation to be %q, got %q", expected, got) + t.Errorf("expected generation to be %d, got %d", expected, got) } if got, expected := o.Labels, pod.ObjectMeta.Labels; !reflect.DeepEqual(got, expected) { t.Errorf("expected labels to be %v, got %v", expected, got) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation.go index 569c1f257a71d..ce4a6036d3ff4 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation.go @@ -20,8 +20,8 @@ import ( "strings" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" + "k8s.io/apimachinery/pkg/api/validate/content" metavalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/runtime/schema" utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -75,6 +75,13 @@ func validate(pth *field.Path, x interface{}, s *structuralschema.Structural) fi return allErrs } +func validatePathSegment(name string, prefix bool) []string { + if prefix { + return content.IsPathSegmentPrefix(name) + } + return content.IsPathSegmentName(name) +} + func validateEmbeddedResource(pth *field.Path, x map[string]interface{}, s *structuralschema.Structural) field.ErrorList { var allErrs field.ErrorList @@ -112,7 +119,7 @@ func validateEmbeddedResource(pth *field.Path, x map[string]interface{}, s *stru if len(meta.Name) == 0 { meta.Name = "fakename" // we have to set something to avoid an error } - allErrs = append(allErrs, metavalidation.ValidateObjectMeta(meta, len(meta.Namespace) > 0, path.ValidatePathSegmentName, pth.Child("metadata"))...) + allErrs = append(allErrs, metavalidation.ValidateObjectMeta(meta, len(meta.Namespace) > 0, validatePathSegment, pth.Child("metadata"))...) } } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/structural.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/structural.go index 5688c2ac40b89..9965718b01409 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/structural.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/structural.go @@ -46,6 +46,14 @@ type StructuralOrBool struct { // +k8s:deepcopy-gen=true +// ExternalDocumentation allows referencing an external resource for extended documentation. +type ExternalDocumentation struct { + Description string + URL string +} + +// +k8s:deepcopy-gen=true + // Generic contains the generic schema fields not allowed in value validation. type Generic struct { Description string @@ -53,10 +61,12 @@ type Generic struct { // It can be object, array, number, integer, boolean, string. // It is optional only if x-kubernetes-preserve-unknown-fields // or x-kubernetes-int-or-string is true. - Type string - Title string - Default JSON - Nullable bool + Type string + Title string + Default JSON + Nullable bool + ExternalDocs *ExternalDocumentation + Example JSON } // +k8s:deepcopy-gen=true diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation_test.go index a83e50ff9582b..734cec1325f3c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation_test.go @@ -402,6 +402,9 @@ func TestValidateNestedValueValidationComplete(t *testing.T) { // check that we didn't forget to check any forbidden generic field tt := reflect.TypeOf(Generic{}) for i := 0; i < tt.NumField(); i++ { + if name := tt.Field(i).Name; name == "ExternalDocs" || name == "Example" { + continue // allowed in nested value validations + } vv := &NestedValueValidation{} x := reflect.ValueOf(&vv.ForbiddenGenerics).Elem() fuzzer.Fill(x.Field(i).Addr().Interface()) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/zz_generated.deepcopy.go index 9cec0262b53a8..c2154a05fd53b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/zz_generated.deepcopy.go @@ -56,10 +56,32 @@ func (in *Extensions) DeepCopy() *Extensions { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation. +func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation { + if in == nil { + return nil + } + out := new(ExternalDocumentation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Generic) DeepCopyInto(out *Generic) { *out = *in out.Default = in.Default.DeepCopy() + if in.ExternalDocs != nil { + in, out := &in.ExternalDocs, &out.ExternalDocs + *out = new(ExternalDocumentation) + **out = **in + } + out.Example = in.Example.DeepCopy() return } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats.go index 5c0397909b656..a02908c64d499 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats.go @@ -96,8 +96,26 @@ func StripUnsupportedFormatsPostProcessorForVersion(compatibilityVersion *versio return nil } - normalized := strings.ReplaceAll(s.Format, "-", "") // go-openapi default format name normalization - if !supportedFormatsAtVersion(compatibilityVersion).supported.Has(normalized) { + schemaType := "" + if len(s.Type) == 1 { + schemaType = s.Type[0] + } + switch schemaType { + case "", "string": + normalized := strings.ReplaceAll(s.Format, "-", "") // go-openapi default format name normalization + if !supportedFormatsAtVersion(compatibilityVersion).supported.Has(normalized) { + s.Format = "" + } + case "integer": + if s.Format != "int32" && s.Format != "int64" { + s.Format = "" + } + case "number": + if s.Format != "float" && s.Format != "double" { + s.Format = "" + } + default: + // Format not supported on other types s.Format = "" } @@ -113,11 +131,27 @@ func GetUnrecognizedFormats(schema *spec.Schema, compatibilityVersion *version.V return unrecognizedFormats } - if len(schema.Type) == 1 && schema.Type[0] == "string" { + schemaType := "" + if len(schema.Type) == 1 { + schemaType = schema.Type[0] + } + switch schemaType { + case "", "string": normalized := strings.ReplaceAll(schema.Format, "-", "") // go-openapi default format name normalization if !supportedFormatsAtVersion(compatibilityVersion).supported.Has(normalized) { unrecognizedFormats = append(unrecognizedFormats, schema.Format) } + case "integer": + if schema.Format != "int32" && schema.Format != "int64" { + unrecognizedFormats = append(unrecognizedFormats, schema.Format) + } + case "number": + if schema.Format != "float" && schema.Format != "double" { + unrecognizedFormats = append(unrecognizedFormats, schema.Format) + } + default: + // Format not supported on other types + unrecognizedFormats = append(unrecognizedFormats, schema.Format) } return unrecognizedFormats diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats_test.go index b4a0e08d7412f..36b58e3e677e1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/formats_test.go @@ -201,22 +201,22 @@ func TestGetUnrecognizedFormats(t *testing.T) { expectedFormats: []string{}, }, { - name: "unrecognized format for integer type is not reported", + name: "unrecognized format for integer type is reported", schema: &spec.Schema{SchemaProps: spec.SchemaProps{Format: "unknown-format", Type: []string{"integer"}}}, compatibilityVersion: version.MajorMinor(1, 0), - expectedFormats: []string{}, + expectedFormats: []string{"unknown-format"}, }, { - name: "unrecognized format for string,null type is not reported", + name: "unrecognized format for string,null type is reported", schema: &spec.Schema{SchemaProps: spec.SchemaProps{Format: "unknown-format", Type: []string{"string", "null"}}}, compatibilityVersion: version.MajorMinor(1, 0), - expectedFormats: []string{}, + expectedFormats: []string{"unknown-format"}, }, { - name: "unrecognized format for no type is not reported", + name: "unrecognized format for no type is reported", schema: &spec.Schema{SchemaProps: spec.SchemaProps{Format: "unknown-format"}}, compatibilityVersion: version.MajorMinor(1, 0), - expectedFormats: []string{}, + expectedFormats: []string{"unknown-format"}, }, } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting_test.go index e33f6c13b5320..ae4e43a6d2e74 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting_test.go @@ -17,6 +17,7 @@ limitations under the License. package validation_test import ( + "math" "testing" "github.com/stretchr/testify/assert" @@ -131,3 +132,49 @@ func TestObjectObjectFieldsRatcheting(t *testing.T) { "small": 501, }}, validation.WithRatcheting(nil)).IsValid()) } + +var numericFormatSchema *spec.Schema = &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"object"}, + Properties: map[string]spec.Schema{ + "intThirtyTwo": { + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"integer"}, + Format: "int32", + }, + }, + "unrelated": { + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"string"}, + }, + }, + }, + }, +} + +func TestNumericFormatRatcheting(t *testing.T) { + validator := validation.NewRatchetingSchemaValidator(numericFormatSchema, nil, "", strfmt.Default) + + // Ratcheting should allow existing invalid value if unchanged + assert.True(t, validator.ValidateUpdate(map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32 + 1), + }, map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32 + 1), + }, validation.WithRatcheting(nil)).IsValid()) + + // Ratcheting should allow existing invalid value if other fields change + assert.True(t, validator.ValidateUpdate(map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32 + 1), + "unrelated": "changed", + }, map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32 + 1), + "unrelated": "original", + }, validation.WithRatcheting(nil)).IsValid()) + + // Should fail if value changes to another invalid value + assert.False(t, validator.ValidateUpdate(map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32 + 2), + }, map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32 + 1), + }, validation.WithRatcheting(nil)).IsValid()) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go index ff324c270533b..586551af7bb0c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go @@ -18,6 +18,7 @@ package validation import ( "context" + "math" "math/rand" "os" "strconv" @@ -308,6 +309,7 @@ func TestValidateCustomResource(t *testing.T) { `: Invalid value: "": "field" must validate at least one schema (anyOf)`, `field: Invalid value: "number": field in body must be of type integer,string: "number"`, `field: Invalid value: "number": field in body must be of type integer: "number"`, + `: Invalid value: "": Checked value must be of type integer (default format) in field`, }}, {object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{ `: Invalid value: "": "field" must validate at least one schema (anyOf)`, @@ -357,6 +359,7 @@ func TestValidateCustomResource(t *testing.T) { `: Invalid value: "": "field" must validate at least one schema (anyOf)`, `field: Invalid value: "number": field in body must be of type integer,string: "number"`, `field: Invalid value: "number": field in body must be of type integer: "number"`, + `: Invalid value: "": Checked value must be of type integer (default format) in field`, }}, {object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{ `: Invalid value: "": "field" must validate all the schemas (allOf). None validated`, @@ -636,6 +639,93 @@ func TestValidateCustomResource(t *testing.T) { }}, }, }, + {name: "numeric formats valid", + schema: apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "intThirtyTwo": {Type: "integer", Format: "int32"}, + "intSixtyFour": {Type: "integer", Format: "int64"}, + "floatThreeTwo": {Type: "number", Format: "float"}, + "floatSixFour": {Type: "number", Format: "double"}, + }, + }, + objects: []interface{}{ + map[string]interface{}{ + "intThirtyTwo": int64(math.MinInt32), + "intSixtyFour": int64(math.MinInt64), + "floatThreeTwo": float64(-math.MaxFloat32), + "floatSixFour": float64(-math.MaxFloat64), + }, + map[string]interface{}{ + "intThirtyTwo": int64(0), + "intSixtyFour": int64(0), + "floatThreeTwo": float64(0), + "floatSixFour": float64(0), + }, + map[string]interface{}{ + "intThirtyTwo": int64(math.MaxInt32), + "intSixtyFour": int64(math.MaxInt64), + "floatThreeTwo": float64(math.MaxFloat32), + "floatSixFour": float64(math.MaxFloat64), + }, + }, + }, + {name: "numeric formats invalid", + schema: apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "intThirtyTwo": {Type: "integer", Format: "int32"}, + "intSixtyFour": {Type: "integer", Format: "int64"}, + "floatThreeTwo": {Type: "number", Format: "float"}, + "floatSixFour": {Type: "number", Format: "double"}, + }, + }, + failingObjects: []failingObject{ + { + object: map[string]interface{}{"intThirtyTwo": int64(math.MaxInt32 + 1)}, + expectErrs: []string{ + `: Invalid value: "": Checked value must be of type integer with format int32 in intThirtyTwo`, + }, + }, + { + object: map[string]interface{}{"intThirtyTwo": int64(math.MinInt32 - 1)}, + expectErrs: []string{ + `: Invalid value: "": Checked value must be of type integer with format int32 in intThirtyTwo`, + }, + }, + // int64 overflow is not possible with int64 input, but we can test it with float64 + { + object: map[string]interface{}{"intSixtyFour": float64(math.MaxInt64) * 1.1}, + expectErrs: []string{ + `intSixtyFour: Invalid value: "float64": intSixtyFour in body must be of type int64: "float64"`, + `: Invalid value: "": Checked value must be of type integer with format int64 in intSixtyFour`, + }, + }, + { + object: map[string]interface{}{"intSixtyFour": float64(math.MinInt64) * 1.1}, + expectErrs: []string{ + `intSixtyFour: Invalid value: "float64": intSixtyFour in body must be of type int64: "float64"`, + `: Invalid value: "": Checked value must be of type integer with format int64 in intSixtyFour`, + }, + }, + { + object: map[string]interface{}{"floatThreeTwo": float64(math.MaxFloat32 * 1.1)}, + expectErrs: []string{ + `: Invalid value: "": Checked value must be of type number with format float in floatThreeTwo`, + }, + }, + { + object: map[string]interface{}{"floatThreeTwo": float64(-math.MaxFloat32 * 1.1)}, + expectErrs: []string{ + `: Invalid value: "": Checked value must be of type number with format float in floatThreeTwo`, + }, + }, + // double overflow (float64) is handled by JSON parsing, but we can try to pass a value that might trigger it if not parsed as such. + // However, standard JSON unmarshalling usually caps at float64 limits or errors out. + // The validator itself checks ranges. Since Go's float64 matches double, true overflow is hard to represent without a custom numeric type. + // We will skip explicit double overflow tests here as they often result in +Inf/-Inf which might be handled differently or parse errors. + }, + }, } for _, tt := range tests { compatVersion := tt.compatVersion diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/clientset_generated.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/clientset_generated.go index 88f7f242b64c1..052739600a634 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/clientset_generated.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/clientset_generated.go @@ -82,7 +82,7 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } -// IsWatchListSemanticsSupported informs the reflector that this client +// IsWatchListSemanticsUnSupported informs the reflector that this client // doesn't support WatchList semantics. // // This is a synthetic method whose sole purpose is to satisfy the optional diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1/customresourcedefinition.go index 7f9c9137e9823..36b9c3b71a9dc 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1/customresourcedefinition.go @@ -28,6 +28,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" ) @@ -48,48 +49,61 @@ type customResourceDefinitionInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCustomResourceDefinitionInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, indexers, nil) + return NewCustomResourceDefinitionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCustomResourceDefinitionInformer constructs a new informer for CustomResourceDefinition type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCustomResourceDefinitionInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCustomResourceDefinitionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCustomResourceDefinitionInformerWithOptions constructs a new informer for CustomResourceDefinition type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCustomResourceDefinitionInformerWithOptions(client clientset.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1().CustomResourceDefinitions().List(context.Background(), options) + return client.ApiextensionsV1().CustomResourceDefinitions().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1().CustomResourceDefinitions().Watch(context.Background(), options) + return client.ApiextensionsV1().CustomResourceDefinitions().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1().CustomResourceDefinitions().List(ctx, options) + return client.ApiextensionsV1().CustomResourceDefinitions().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1().CustomResourceDefinitions().Watch(ctx, options) + return client.ApiextensionsV1().CustomResourceDefinitions().Watch(ctx, opts) }, }, client), &apisapiextensionsv1.CustomResourceDefinition{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *customResourceDefinitionInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCustomResourceDefinitionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *customResourceDefinitionInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/customresourcedefinition.go index 140998152c28f..04a5ee217f628 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/customresourcedefinition.go @@ -28,6 +28,7 @@ import ( apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" ) @@ -48,48 +49,61 @@ type customResourceDefinitionInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCustomResourceDefinitionInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, indexers, nil) + return NewCustomResourceDefinitionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCustomResourceDefinitionInformer constructs a new informer for CustomResourceDefinition type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCustomResourceDefinitionInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCustomResourceDefinitionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCustomResourceDefinitionInformerWithOptions constructs a new informer for CustomResourceDefinition type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCustomResourceDefinitionInformerWithOptions(client clientset.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1beta1().CustomResourceDefinitions().List(context.Background(), options) + return client.ApiextensionsV1beta1().CustomResourceDefinitions().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1beta1().CustomResourceDefinitions().Watch(context.Background(), options) + return client.ApiextensionsV1beta1().CustomResourceDefinitions().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1beta1().CustomResourceDefinitions().List(ctx, options) + return client.ApiextensionsV1beta1().CustomResourceDefinitions().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ApiextensionsV1beta1().CustomResourceDefinitions().Watch(ctx, options) + return client.ApiextensionsV1beta1().CustomResourceDefinitions().Watch(ctx, opts) }, }, client), &apisapiextensionsv1beta1.CustomResourceDefinition{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *customResourceDefinitionInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCustomResourceDefinitionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *customResourceDefinitionInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/factory.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/factory.go index e8d6e5b2644f2..c0a30f6e99f08 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/factory.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/factory.go @@ -19,6 +19,7 @@ limitations under the License. package externalversions import ( + context "context" reflect "reflect" sync "sync" time "time" @@ -29,6 +30,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" + wait "k8s.io/apimachinery/pkg/util/wait" cache "k8s.io/client-go/tools/cache" ) @@ -43,6 +45,7 @@ type sharedInformerFactory struct { defaultResync time.Duration customResync map[reflect.Type]time.Duration transform cache.TransformFunc + informerName *cache.InformerName informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -89,6 +92,21 @@ func WithTransform(transform cache.TransformFunc) SharedInformerOption { } } +// WithInformerName sets the InformerName for informer identity used in metrics. +// The InformerName must be created via cache.NewInformerName() at startup, +// which validates global uniqueness. Each informer type will register its +// GVR under this name. +func WithInformerName(informerName *cache.InformerName) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.informerName = informerName + return factory + } +} + +func (f *sharedInformerFactory) InformerName() *cache.InformerName { + return f.informerName +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client clientset.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -123,6 +141,10 @@ func NewSharedInformerFactoryWithOptions(client clientset.Interface, defaultResy } func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext(wait.ContextForChannel(stopCh)) +} + +func (f *sharedInformerFactory) StartWithContext(ctx context.Context) { f.lock.Lock() defer f.lock.Unlock() @@ -132,15 +154,9 @@ func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + f.wg.Go(func() { + informer.RunWithContext(ctx) + }) f.startedInformers[informerType] = true } } @@ -153,9 +169,15 @@ func (f *sharedInformerFactory) Shutdown() { // Will return immediately if there is nothing to wait for. f.wg.Wait() + f.informerName.Release() } func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + result := f.WaitForCacheSyncWithContext(wait.ContextForChannel(stopCh)) + return result.Synced +} + +func (f *sharedInformerFactory) WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() defer f.lock.Unlock() @@ -169,10 +191,31 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return informers }() - res := map[reflect.Type]bool{} + // Wait for informers to sync, without polling. + cacheSyncs := make([]cache.DoneChecker, 0, len(informers)) + for _, informer := range informers { + cacheSyncs = append(cacheSyncs, informer.HasSyncedChecker()) + } + cache.WaitFor(ctx, "" /* no logging */, cacheSyncs...) + + res := cache.SyncResult{ + Synced: make(map[reflect.Type]bool, len(informers)), + } + failed := false for informType, informer := range informers { - res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + hasSynced := informer.HasSynced() + if !hasSynced { + failed = true + } + res.Synced[informType] = hasSynced } + if failed { + // context.Cause is more informative than ctx.Err(). + // This must be non-nil, otherwise WaitFor wouldn't have stopped + // prematurely. + res.Err = context.Cause(ctx) + } + return res } @@ -194,7 +237,9 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) - informer.SetTransform(f.transform) + if f.transform != nil { + informer.SetTransform(f.transform) + } f.informers[informerType] = informer return informer @@ -211,27 +256,46 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // defer factory.WaitForStop() // Returns immediately if nothing was started. // genericInformer := factory.ForResource(resource) // typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } +// handle, err := typeInformer.Informer().AddEventHandler(...) +// if err != nil { +// return fmt.Errorf("register event handler: %v", err) +// } +// defer typeInformer.Informer().RemoveEventHandler(handle) // Avoids leaking goroutines. +// factory.StartWithContext(ctx) // Start processing these informers. +// synced := factory.WaitForCacheSyncWithContext(ctx) +// if err := synced.AsError(); err != nil { +// return err +// } +// for v := range synced { +// // Only if desired log some information similar to this. +// fmt.Fprintf(os.Stdout, "cache synced: %s", v) +// } +// +// // Also make sure that all of the initial cache events have been delivered. +// if !WaitFor(ctx, "event handler sync", handle.HasSyncedChecker()) { +// // Must have failed because of context. +// return fmt.Errorf("sync event handler: %w", context.Cause(ctx)) // } // // // Creating informers can also be created after Start, but then // // Start must be called again: // anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) +// factory.StartWithContext(ctx) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + // + // Contextual logging: StartWithContext should be used instead of Start in code which supports contextual logging. Start(stopCh <-chan struct{}) + // StartWithContext initializes all requested informers. They are handled in goroutines + // which run until the context gets canceled. + // Warning: StartWithContext does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + StartWithContext(ctx context.Context) + // Shutdown marks a factory as shutting down. At that point no new // informers can be started anymore and Start will return without // doing anything. @@ -246,8 +310,14 @@ type SharedInformerFactory interface { // WaitForCacheSync blocks until all started informers' caches were synced // or the stop channel gets closed. + // + // Contextual logging: WaitForCacheSync should be used instead of WaitForCacheSync in code which supports contextual logging. It also returns a more useful result. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // WaitForCacheSyncWithContext blocks until all started informers' caches were synced + // or the context gets canceled. + WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult + // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go index da6eadaa7f952..4c60257a41ff5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -34,7 +34,26 @@ type NewInformerFunc func(clientset.Interface, time.Duration) cache.SharedIndexI type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer + InformerName() *cache.InformerName } // TweakListOptionsFunc is a function that transforms a v1.ListOptions. type TweakListOptionsFunc func(*v1.ListOptions) + +// InformerOptions holds the options for creating an informer. +type InformerOptions struct { + // ResyncPeriod is the resync period for this informer. + // If not set, defaults to 0 (no resync). + ResyncPeriod time.Duration + + // Indexers are the indexers for this informer. + Indexers cache.Indexers + + // InformerName is used to uniquely identify this informer for metrics. + // If not set, metrics will not be published for this informer. + // Use cache.NewInformerName() to create an InformerName at startup. + InformerName *cache.InformerName + + // TweakListOptions is an optional function to modify the list options. + TweakListOptions TweakListOptionsFunc +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go index e3354f336a7c7..f9123915cec40 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go @@ -242,7 +242,7 @@ func (c *CRDFinalizer) deleteInstances(ctx context.Context, crd *apiextensionsv1 if len(listObj.(*unstructured.UnstructuredList).Items) == 0 { return true, nil } - klog.V(2).Infof("%s.%s waiting for %d items to be removed", crd.Status.AcceptedNames.Plural, crd.Spec.Group, len(listObj.(*unstructured.UnstructuredList).Items)) + klog.FromContext(ctx).V(2).Info("Waiting for items to be removed", "resource", crd.Status.AcceptedNames.Plural, "group", crd.Spec.Group, "count", len(listObj.(*unstructured.UnstructuredList).Items)) return false, nil }) if err != nil { @@ -261,17 +261,20 @@ func (c *CRDFinalizer) deleteInstances(ctx context.Context, crd *apiextensionsv1 }, nil } +// Run is a legacy wrapper that starts the controller. +// +//logcheck:context // RunWithContext should be used instead of Run in code which supports contextual logging. func (c *CRDFinalizer) Run(workers int, stopCh <-chan struct{}) { c.RunWithContext(workers, wait.ContextForChannel(stopCh)) } -//logcheck:context // RunWithContext should be used instead of Run in code which supports contextual logging. func (c *CRDFinalizer) RunWithContext(workers int, ctx context.Context) { defer utilruntime.HandleCrash() defer c.queue.ShutDown() - klog.Info("Starting CRDFinalizer") - defer klog.Info("Shutting down CRDFinalizer") + logger := klog.FromContext(ctx) + logger.Info("Starting CRDFinalizer") + defer logger.Info("Shutting down CRDFinalizer") if !cache.WaitForCacheSync(ctx.Done(), c.crdSynced) { return diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go index 76363d945b44d..3f3759562171e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go @@ -632,7 +632,6 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) { }, expected: new(spec.Schema). WithExternalDocs(testStr, testStr2), - expectDiff: true, }, { name: "example", @@ -641,7 +640,6 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) { }, expected: new(spec.Schema). WithExample(testStr), - expectDiff: true, }, { name: "preserve-unknown-fields in arrays", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go index e59d6213f6199..13ede31c1bdec 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go @@ -65,6 +65,7 @@ func init() { var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{ CRDObservedGenerationTracking: { {Version: version.MustParse("1.35"), PreRelease: featuregate.Beta, Default: false}, + {Version: version.MustParse("1.36"), PreRelease: featuregate.Beta, Default: true}, }, CRDValidationRatcheting: { {Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Alpha}, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/generated/openapi/zz_generated.openapi.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/generated/openapi/zz_generated.openapi.go index 065019b5ebe11..224416c922677 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/generated/openapi/zz_generated.openapi.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/generated/openapi/zz_generated.openapi.go @@ -146,6 +146,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA metav1.Preconditions{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Preconditions(ref), metav1.RootPaths{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_RootPaths(ref), metav1.ServerAddressByClientCIDR{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref), + metav1.ShardInfo{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_ShardInfo(ref), metav1.Status{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_Status(ref), metav1.StatusCause{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_StatusCause(ref), metav1.StatusDetails{}.OpenAPIModelName(): schema_pkg_apis_meta_v1_StatusDetails(ref), @@ -425,6 +426,7 @@ func schema_k8sio_api_autoscaling_v1_HorizontalPodAutoscaler(ref common.Referenc }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -5309,9 +5311,17 @@ func schema_pkg_apis_meta_v1_ListMeta(ref common.ReferenceCallback) common.OpenA Format: "int64", }, }, + "shardInfo": { + SchemaProps: spec.SchemaProps{ + Description: "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + Ref: ref(metav1.ShardInfo{}.OpenAPIModelName()), + }, + }, }, }, }, + Dependencies: []string{ + metav1.ShardInfo{}.OpenAPIModelName()}, } } @@ -5406,6 +5416,13 @@ func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.Op Format: "", }, }, + "shardSelector": { + SchemaProps: spec.SchemaProps{ + Description: "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -5987,6 +6004,28 @@ func schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref common.ReferenceCallb } } +func schema_pkg_apis_meta_v1_ShardInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"selector"}, + }, + }, + } +} + func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go index b89e42db167fb..53ab282625255 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go @@ -56,6 +56,7 @@ func (a statusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set // we might need a mechanism that is the inverse of resetFields where // you specify only the fields to be kept rather than the fields to be wiped // that way you could wipe everything but the status in this case. + fieldpath.MakePathOrDie("metadata"), fieldpath.MakePathOrDie("spec"), ), } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go index 8713b8ec5b688..91d7929afea18 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go @@ -252,11 +252,17 @@ func (statusStrategy) NamespaceScoped() bool { func (statusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { fields := map[fieldpath.APIVersion]*fieldpath.Set{ "apiextensions.k8s.io/v1": fieldpath.NewSet( - fieldpath.MakePathOrDie("metadata"), + // Disabled to match PrepareForUpdate, which do not wipe metadata + // https://github.com/kubernetes/kubernetes/issues/137681 + // fieldpath.MakePathOrDie("metadata"), + fieldpath.MakePathOrDie("spec"), ), "apiextensions.k8s.io/v1beta1": fieldpath.NewSet( - fieldpath.MakePathOrDie("metadata"), + // Disabled to match PrepareForUpdate, which do not wipe metadata + // https://github.com/kubernetes/kubernetes/issues/137681 + // fieldpath.MakePathOrDie("metadata"), + fieldpath.MakePathOrDie("spec"), ), } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/test/cel.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/test/cel.go index f88d2a636638e..69914d93acb8b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/test/cel.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/test/cel.go @@ -116,7 +116,6 @@ func findCEL(t *testing.T, s *schema.Structural, root bool, pth *field.Path) (ma } for k, v := range s.Properties { - v := v sub, err := findCEL(t, &v, false, pth.Child("properties").Child(k)) if err != nil { return nil, err diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/test/pattern.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/test/pattern.go index 2c4da5f79bfa0..633aabe2fcb61 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/test/pattern.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/test/pattern.go @@ -69,7 +69,6 @@ func findPattern(t *testing.T, s *schema.Structural, pth *field.Path) (map[strin } for k, v := range s.Properties { - v := v sub, err := findPattern(t, &v, pth.Child("properties").Child(k)) if err != nil { return nil, err diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go index f2abe7bd30e8d..317bb2ff97a36 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/conversion/conversion_test.go @@ -1541,11 +1541,10 @@ func servedVersions(versions []apiextensionsv1.CustomResourceDefinitionVersion) func TestWebhookConversion_WhitespaceCABundleEtcdBypass(t *testing.T) { // Setup server and clients - tearDown, config, options, err := fixtures.StartDefaultServer(t) + apiServerTearDown, config, options, err := fixtures.StartDefaultServer(t) if err != nil { t.Fatal(err) } - defer tearDown() apiExtensionsClient, err := clientset.NewForConfig(config) if err != nil { @@ -1591,11 +1590,17 @@ func TestWebhookConversion_WhitespaceCABundleEtcdBypass(t *testing.T) { verifyMultiVersionObject(t, "v1beta1", obj) // Set up webhook conversion - tearDown, webhookClientConfig, err := StartConversionWebhookServer(NewObjectConverterWebhookHandler(t, noopConverter)) + webhookTearDown, webhookClientConfig, err := StartConversionWebhookServer(NewObjectConverterWebhookHandler(t, noopConverter)) if err != nil { t.Fatal(err) } - defer tearDown() + + t.Cleanup(func() { + // Ensure conversion webhook remains available until the apiserver + // and its etcd watches are fully shut down. + apiServerTearDown() + webhookTearDown() + }) crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{ Strategy: apiextensionsv1.WebhookConverter, diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures/server.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures/server.go index 60a98f335a9b1..766944e47e814 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures/server.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures/server.go @@ -90,7 +90,7 @@ users: "--kubeconfig", fakeKubeConfig.Name(), // disable admission and filters that require talking to kube-apiserver "--enable-priority-and-fairness=false", - "--disable-admission-plugins", "NamespaceLifecycle,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ValidatingAdmissionPolicy"}, + "--disable-admission-plugins", "NamespaceLifecycle,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ValidatingAdmissionPolicy,MutatingAdmissionPolicy"}, flags..., ), nil) if err != nil { diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go index ad588a1f4bce8..635dea720b056 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go @@ -450,11 +450,11 @@ func TestApplyScaleSubresource(t *testing.T) { t.Fatalf("Expected 3 managed fields, got %v: %v", len(managedFields), obj.GetManagedFields()) } specEntry := managedFields[0] - if specEntry.Manager != "applier" || specEntry.APIVersion != "mygroup.example.com/v1" || specEntry.Operation != "Apply" || string(specEntry.FieldsV1.Raw) != `{"f:spec":{}}` || specEntry.Subresource != "" { + if specEntry.Manager != "applier" || specEntry.APIVersion != "mygroup.example.com/v1" || specEntry.Operation != "Apply" || specEntry.FieldsV1.GetRawString() != `{"f:spec":{}}` || specEntry.Subresource != "" { t.Fatalf("Unexpected entry: %v", specEntry) } scaleEntry := managedFields[1] - if scaleEntry.Manager != "scaler" || scaleEntry.APIVersion != "mygroup.example.com/v1" || scaleEntry.Operation != "Update" || string(scaleEntry.FieldsV1.Raw) != `{"f:spec":{"f:replicas":{}}}` || scaleEntry.Subresource != "scale" { + if scaleEntry.Manager != "scaler" || scaleEntry.APIVersion != "mygroup.example.com/v1" || scaleEntry.Operation != "Update" || scaleEntry.FieldsV1.GetRawString() != `{"f:spec":{"f:replicas":{}}}` || scaleEntry.Subresource != "scale" { t.Fatalf("Unexpected entry: %v", scaleEntry) } restEntry := managedFields[2] diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go index 9e9f7cd67a0a4..0a33aaad318e5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go @@ -252,7 +252,7 @@ func TestTableGet(t *testing.T) { // Validate extra column for v1 if i == 1 { if got, expected := tbl.Rows[0].Cells[6], int64(5); got != expected { - t.Errorf("expected cell[6] to equal %q, got %q", expected, got) + t.Errorf("expected cell[6] to equal %v, got %v", expected, got) } } } @@ -350,7 +350,7 @@ func TestTableGet(t *testing.T) { // Validate extra column for v1 if i == 1 { if got, expected := tbl.Rows[0].Cells[6], int64(5); got != expected { - t.Errorf("expected cell[6] to equal %q, got %q", expected, got) + t.Errorf("expected cell[6] to equal %v, got %v", expected, got) } } } diff --git a/staging/src/k8s.io/apimachinery/OWNERS b/staging/src/k8s.io/apimachinery/OWNERS index 2dc066cd7996f..08b30b3ebc392 100644 --- a/staging/src/k8s.io/apimachinery/OWNERS +++ b/staging/src/k8s.io/apimachinery/OWNERS @@ -19,7 +19,6 @@ reviewers: - mikedanese - liggitt - sttts - - logicalhan - jpbetz labels: - sig/api-machinery diff --git a/staging/src/k8s.io/apimachinery/go.mod b/staging/src/k8s.io/apimachinery/go.mod index cfdee0381b755..fd8861090f41d 100644 --- a/staging/src/k8s.io/apimachinery/go.mod +++ b/staging/src/k8s.io/apimachinery/go.mod @@ -2,36 +2,35 @@ module k8s.io/apimachinery -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( - github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/fxamacker/cbor/v2 v2.9.0 github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/moby/spdystream v0.5.0 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.47.0 - golang.org/x/time v0.9.0 + golang.org/x/net v0.49.0 + golang.org/x/time v0.14.0 gopkg.in/evanphx/json-patch.v4 v4.13.0 gopkg.in/inf.v0 v0.9.1 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/klog/v2 v2.140.0 + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a + k8s.io/streaming v0.0.0 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 sigs.k8s.io/randfill v1.0.0 - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 sigs.k8s.io/yaml v1.6.0 ) require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -39,17 +38,16 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/spdystream v0.5.1 // 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/onsi/ginkgo/v2 v2.25.1 // indirect - github.com/onsi/gomega v1.38.2 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/text v0.31.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 +replace k8s.io/streaming => ../streaming diff --git a/staging/src/k8s.io/apimachinery/go.sum b/staging/src/k8s.io/apimachinery/go.sum index b096b58bf2d2e..53a98cb1526c5 100644 --- a/staging/src/k8s.io/apimachinery/go.sum +++ b/staging/src/k8s.io/apimachinery/go.sum @@ -1,5 +1,3 @@ -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/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -11,9 +9,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= -github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -24,22 +19,16 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-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/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -51,10 +40,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= -github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -64,10 +51,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -85,38 +68,28 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 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= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -128,17 +101,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= 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/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go index 4ca7f6c5a0b96..694dece2954d5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go @@ -43,7 +43,7 @@ func defaultFillFuncs() map[reflect.Type]FillFunc { obj.(*metav1.TypeMeta).Kind = "" } funcs[reflect.TypeOf(&metav1.FieldsV1{})] = func(s string, i int, obj interface{}) { - obj.(*metav1.FieldsV1).Raw = []byte(`{}`) + obj.(*metav1.FieldsV1).SetRawString(`{}`) } funcs[reflect.TypeOf(&metav1.Time{})] = func(s string, i int, obj interface{}) { // use the integer as an offset from the year diff --git a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go index c655b3ab71e78..23057619b16b2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go @@ -1,5 +1,4 @@ //go:build !race -// +build !race /* Copyright 2021 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go index fa252960f0103..f7ebb8c170d08 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go @@ -1,5 +1,4 @@ //go:build race -// +build race /* Copyright 2021 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go index d9d177c46b079..7a6cb93f5e39e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go @@ -38,8 +38,8 @@ import ( runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/dump" ) type InstallFunc func(scheme *runtime.Scheme) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/equality/semantic.go b/staging/src/k8s.io/apimachinery/pkg/api/equality/semantic.go index f02fa8e4340e0..cd78c384161b5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/equality/semantic.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/equality/semantic.go @@ -40,6 +40,9 @@ var Semantic = conversion.EqualitiesOrDie( func(a, b metav1.Time) bool { return a.UTC() == b.UTC() }, + func(a, b metav1.FieldsV1) bool { + return a.Equal(b) + }, func(a, b labels.Selector) bool { return a.String() == b.String() }, diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.protomessage.pb.go deleted file mode 100644 index 712e155c3e5ed..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.protomessage.pb.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package resource - -func (*Quantity) ProtoMessage() {} - -func (*QuantityValue) ProtoMessage() {} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/content/errors.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/content/errors.go index a4a1b5574c6ef..13eeced1a06cf 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/content/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/content/errors.go @@ -29,6 +29,12 @@ func MinError[T constraints.Integer](min T) string { return fmt.Sprintf("must be greater than or equal to %d", min) } +// MaxError returns a string explanation of a "must be less than or equal" +// validation failure. +func MaxError[T constraints.Integer](max T) string { + return fmt.Sprintf("must be less than or equal to %d", max) +} + // MaxLenError returns a string explanation of a "string too long" validation // failure. func MaxLenError(length int) string { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/content/path.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/content/path.go new file mode 100644 index 0000000000000..c41b1d47312b8 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/content/path.go @@ -0,0 +1,63 @@ +/* +Copyright 2015 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 content + +import ( + "fmt" + "strings" +) + +// Strings that cannot be used as names specified as path segments (like the +// REST API or etcd store). +var pathSegmentNameMayNotBe = []string{".", ".."} + +// Substrings that cannot be used in names specified as path segments (like the +// REST API or etcd store). +var pathSegmentNameMayNotContain = []string{"/", "%"} + +// IsPathSegmentName validates the name can be safely encoded as a path +// segment. +// +// Note that, for historical reason, this function does not check for +// empty strings or impose a limit on the length of the name. +func IsPathSegmentName(name string) []string { + for _, illegalName := range pathSegmentNameMayNotBe { + if name == illegalName { + return []string{fmt.Sprintf(`may not be '%s'`, illegalName)} + } + } + + return IsPathSegmentPrefix(name) +} + +// IsPathSegmentPrefix validates the name can be used as a prefix for a +// name which will be encoded as a path segment It does not check for exact +// matches with disallowed names, since an arbitrary suffix might make the name +// valid. +// +// Note that, for historical reason, this function does not check for +// empty strings or impose a limit on the length of the name. +func IsPathSegmentPrefix(name string) []string { + var errors []string + for _, illegalContent := range pathSegmentNameMayNotContain { + if strings.Contains(name, illegalContent) { + errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) + } + } + + return errors +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/content/path_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/content/path_test.go new file mode 100644 index 0000000000000..16a1119e82025 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/content/path_test.go @@ -0,0 +1,158 @@ +/* +Copyright 2015 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 content + +import ( + "strings" + "testing" +) + +func TestIsPathSegmentPrefix(t *testing.T) { + testcases := map[string]struct { + Name string + ExpectedMsg string + }{ + "empty": { + Name: "", + ExpectedMsg: "", // NOTE: this probably should fail + }, + // NOTE: no validation of max length + "valid short": { + Name: "foo", + ExpectedMsg: "", + }, + "valid long": { + Name: "foo.bar.baz", + ExpectedMsg: "", + }, + // Make sure mixed case, non DNS subdomain characters are tolerated + "valid complex": { + Name: "sha256:ABCDEF012345@ABCDEF012345", + ExpectedMsg: "", + }, + // Make sure non-ascii characters are tolerated + "valid extended charset": { + Name: "Iñtërnâtiônàlizætiøn", + ExpectedMsg: "", + }, + "dot": { + Name: ".", + ExpectedMsg: "", + }, + "dot leading": { + Name: ".test", + ExpectedMsg: "", + }, + "dot dot": { + Name: "..", + ExpectedMsg: "", + }, + "dot dot leading": { + Name: "..test", + ExpectedMsg: "", + }, + "slash": { + Name: "foo/bar", + ExpectedMsg: "/", + }, + "percent": { + Name: "foo%bar", + ExpectedMsg: "%", + }, + } + + for k, tc := range testcases { + msgs := IsPathSegmentPrefix(tc.Name) + if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { + t.Errorf("%s: expected no error, got %v", k, msgs) + } + if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { + t.Errorf("%s: expected error, got none", k) + } + if len(tc.ExpectedMsg) > 0 && !strings.Contains(msgs[0], tc.ExpectedMsg) { + t.Errorf("%s: expected error containing %q, got %v", k, tc.ExpectedMsg, msgs[0]) + } + } +} + +func TestIsPathSegmentName(t *testing.T) { + testcases := map[string]struct { + Name string + ExpectedMsg string + }{ + "empty": { + Name: "", + ExpectedMsg: "", // NOTE: this probably should fail + }, + // NOTE: no validation of max length + "valid short": { + Name: "foo", + ExpectedMsg: "", + }, + "valid long": { + Name: "foo.bar.baz", + ExpectedMsg: "", + }, + // Make sure mixed case, non DNS subdomain characters are tolerated + "valid complex": { + Name: "sha256:ABCDEF012345@ABCDEF012345", + ExpectedMsg: "", + }, + // Make sure non-ascii characters are tolerated + "valid extended charset": { + Name: "Iñtërnâtiônàlizætiøn", + ExpectedMsg: "", + }, + "dot": { + Name: ".", + ExpectedMsg: ".", + }, + "dot leading": { + Name: ".test", + ExpectedMsg: "", + }, + "dot dot": { + Name: "..", + ExpectedMsg: "..", + }, + "dot dot leading": { + Name: "..test", + ExpectedMsg: "", + }, + "slash": { + Name: "foo/bar", + ExpectedMsg: "/", + }, + "percent": { + Name: "foo%bar", + ExpectedMsg: "%", + }, + } + + for k, tc := range testcases { + msgs := IsPathSegmentName(tc.Name) + if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { + t.Errorf("%s: expected no error, got %v", k, msgs) + } + if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { + t.Errorf("%s: expected error, got none", k) + } + if len(tc.ExpectedMsg) > 0 && !strings.Contains(msgs[0], tc.ExpectedMsg) { + t.Errorf("%s: expected error containing %q, got %v", k, tc.ExpectedMsg, msgs[0]) + } + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/discriminator.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/discriminator.go new file mode 100644 index 0000000000000..787ae6b0b7f5e --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/discriminator.go @@ -0,0 +1,71 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// DiscriminatedRule defines a validation to apply for a specific discriminator value. +type DiscriminatedRule[Tfield any, Tdisc comparable] struct { + Value Tdisc + Validation ValidateFunc[Tfield] +} + +// Discriminated validates a member field based on a discriminator value. +// It iterates through the rules and applies the first one that matches the discriminator. +// If no rule matches, it applies the defaultValidation if provided. +// +// It performs ratcheting: if the operation is an Update, and neither the discriminator +// nor the value (checked via equiv) have changed, validation is skipped. +func Discriminated[Tfield any, Tdisc comparable, Tstruct any](ctx context.Context, op operation.Operation, structPath *field.Path, + obj, oldObj *Tstruct, fieldName string, getMemberValue func(*Tstruct) Tfield, getDiscriminator func(*Tstruct) Tdisc, + equiv MatchFunc[Tfield], defaultValidation ValidateFunc[Tfield], rules []DiscriminatedRule[Tfield, Tdisc], +) field.ErrorList { + value := getMemberValue(obj) + discriminator := getDiscriminator(obj) + var oldValue Tfield + var oldDiscriminator Tdisc + + if oldObj != nil { + oldValue = getMemberValue(oldObj) + oldDiscriminator = getDiscriminator(oldObj) + } + + if op.Type == operation.Update && oldObj != nil && discriminator == oldDiscriminator && equiv(value, oldValue) { + return nil + } + + fldPath := structPath.Child(fieldName) + for _, rule := range rules { + if rule.Value == discriminator { + if rule.Validation == nil { + return nil + } + return rule.Validation(ctx, op, fldPath, value, oldValue) + } + } + + if defaultValidation != nil { + return defaultValidation(ctx, op, fldPath, value, oldValue) + } + + return nil +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/discriminator_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/discriminator_test.go new file mode 100644 index 0000000000000..ae9621b2a9eab --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/discriminator_test.go @@ -0,0 +1,265 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestDiscriminated(t *testing.T) { + errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")} + errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")} + + mockValid := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return nil + } + mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errMatch + } + mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errDefault + } + + // mockEqual compares pointer values by dereferencing, not by pointer identity. + mockEqual := func(a, b *string) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b + } + + testCases := []struct { + name string + opType operation.Type + discriminator string + oldDiscriminator string + value *string + oldValue *string + rules []DiscriminatedRule[*string, string] + defaultValidation ValidateFunc[*string] + expected field.ErrorList + }{ + { + name: "matches rule, returns valid", + opType: operation.Create, + discriminator: "A", + oldDiscriminator: "A", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "matches rule, returns error", + opType: operation.Create, + discriminator: "B", + oldDiscriminator: "B", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errMatch, + }, + { + name: "ratcheting: update, unchanged, skips validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "B", // unchanged + value: nil, + oldValue: nil, // unchanged + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, // would fail if run + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "ratcheting: update, same value different pointers, skips validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "B", + value: strPtr("same"), + oldValue: strPtr("same"), // different pointer, same value + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, // would fail if run + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "ratcheting: update, discriminator changed, runs validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "A", // changed + value: nil, + oldValue: nil, + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errMatch, + }, + { + name: "ratcheting: update, value changed, discriminator unchanged, runs validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "B", // unchanged + value: strPtr("new"), + oldValue: strPtr("old"), // changed + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errMatch, + }, + { + name: "matches rule with nil validation, returns valid", + opType: operation.Create, + discriminator: "A", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: nil}, + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "no match, runs default", + opType: operation.Create, + discriminator: "C", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errDefault, + }, + { + name: "no match, nil default, returns valid", + opType: operation.Create, + discriminator: "C", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + }, + defaultValidation: nil, + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + oldDisc := tc.oldDiscriminator + if oldDisc == "" { + oldDisc = tc.discriminator + } + + type StringP struct { + Val *string + Disc string + } + newObj := &StringP{Val: tc.value, Disc: tc.discriminator} + var oldObj *StringP + if tc.opType == operation.Update { + oldObj = &StringP{Val: tc.oldValue, Disc: oldDisc} + } + getVal := func(p *StringP) *string { return p.Val } + getDisc := func(p *StringP) string { return p.Disc } + got := Discriminated[*string, string, StringP](context.Background(), operation.Operation{Type: tc.opType}, field.NewPath("root"), newObj, oldObj, "field", getVal, getDisc, mockEqual, tc.defaultValidation, tc.rules) + + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} + +func TestDiscriminatedIntDiscriminator(t *testing.T) { + errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")} + errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")} + + mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errMatch + } + mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errDefault + } + + mockEqual := func(a, b *string) bool { + return a == b + } + + type IntP struct { + Val *string + Disc int + } + newObj := &IntP{Val: nil, Disc: 1} + getVal := func(p *IntP) *string { return p.Val } + getDisc := func(p *IntP) int { return p.Disc } + + got := Discriminated[*string, int, IntP](context.Background(), operation.Operation{Type: operation.Create}, field.NewPath("root"), newObj, nil, "field", getVal, getDisc, mockEqual, mockErrorDefault, []DiscriminatedRule[*string, int]{ + {Value: 1, Validation: mockErrorMatch}, + }) + if !reflect.DeepEqual(got, errMatch) { + t.Errorf("int discriminator: got %v want %v", got, errMatch) + } +} + +func TestDiscriminatedBoolDiscriminator(t *testing.T) { + errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")} + errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")} + + mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errMatch + } + mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errDefault + } + + mockEqual := func(a, b *string) bool { + return a == b + } + + type BoolP struct { + Val *string + Disc bool + } + newObj := &BoolP{Val: nil, Disc: true} + getVal := func(p *BoolP) *string { return p.Val } + getDisc := func(p *BoolP) bool { return p.Disc } + + got := Discriminated[*string, bool, BoolP](context.Background(), operation.Operation{Type: operation.Create}, field.NewPath("root"), newObj, nil, "field", getVal, getDisc, mockEqual, mockErrorDefault, []DiscriminatedRule[*string, bool]{ + {Value: true, Validation: mockErrorMatch}, + }) + if !reflect.DeepEqual(got, errMatch) { + t.Errorf("bool discriminator: got %v want %v", got, errMatch) + } +} + +// strPtr returns a new pointer to a copy of s, guaranteeing a distinct allocation. +func strPtr(s string) *string { + return &s +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/limits.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/limits.go index b6db5e08cbe6a..dcc7f518d81b7 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/limits.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/limits.go @@ -18,6 +18,8 @@ package validate import ( "context" + "math" + "unicode/utf8" "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/api/validate/constraints" @@ -31,9 +33,40 @@ func MaxLength[T ~string](_ context.Context, _ operation.Operation, fldPath *fie if value == nil { return nil } + + // if the length of the value in bytes is less + // than the maximum size then we can confidently + // say that this value is within the bounds + // enforced by the maximum value regardless + // of the actual makeup of characters in the value + byteLength := len(*value) + if byteLength <= max { + return nil + } + + // because runes are up to 4 byte characters, if we assume all characters + // in the input are runes, the minimum number of characters that + // are specified is len(value)/4. If the minimum multi-byte + // character count is greater than our enforced maximum, we + // can confidently say that the value is invalid without having + // to actually perform the more expensive rune counting step + minimum := int(math.Ceil(float64(byteLength) / 4.0)) + if minimum > max || utf8.RuneCountInString(string(*value)) > max { + return field.ErrorList{field.TooLongCharacters(fldPath, *value, max).WithOrigin("maxLength")} + } + return nil +} + +// MaxBytes verifies that the specified value is not longer than max bytes. +func MaxBytes[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max int) field.ErrorList { + if value == nil { + return nil + } + if len(*value) > max { - return field.ErrorList{field.TooLong(fldPath, *value, max).WithOrigin("maxLength")} + return field.ErrorList{field.TooLong(fldPath, *value, max).WithOrigin("maxBytes")} } + return nil } @@ -45,6 +78,14 @@ func MaxItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Pa return nil } +// MinItems verifies that the specified slice is not shorter than min items. +func MinItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T, min int) field.ErrorList { + if len(value) < min { + return field.ErrorList{field.TooFew(fldPath, len(value), min).WithOrigin("minItems")} + } + return nil +} + // Minimum verifies that the specified value is greater than or equal to min. func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min T) field.ErrorList { if value == nil { @@ -55,3 +96,45 @@ func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fl } return nil } + +// Maximum verifies that the specified value is less than or equal to max. +func Maximum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max T) field.ErrorList { + if value == nil { + return nil + } + if *value > max { + return field.ErrorList{field.Invalid(fldPath, *value, content.MaxError(max)).WithOrigin("maximum")} + } + return nil +} + +// MinLength verifies that the specified value is at least min characters, if non-nil. +func MinLength[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min int) field.ErrorList { + if value == nil { + return nil + } + + byteLength := len(*value) + + // because runes are up to 4 byte characters, if we assume all characters + // in the input are 4 byte runes, the minimum number of characters that + // are specified is len(value)/4. If the minimum multi-byte + // character count is greater than or equal to our enforced minimum, we + // can confidently say that the value is valid without having + // to actually perform the more expensive rune counting step + if int(math.Ceil(float64(byteLength)/4.0)) >= min { + return nil + } + + // if the length of the value in bytes is less + // than the minimum size then we can confidently + // say that this value is not within the bounds + // enforced by the maximum value regardless + // of the actual makeup of characters in the value. + // Otherwise, perform a rune count to determine if the + // number of characters is less than the minimum. + if byteLength < min || utf8.RuneCountInString(string(*value)) < min { + return field.ErrorList{field.TooShort(fldPath, *value, min).WithOrigin("minLength")} + } + return nil +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go index f17937c691306..ff1cc60f2702d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go @@ -18,7 +18,7 @@ package validate import ( "context" - "regexp" + "fmt" "testing" "k8s.io/apimachinery/pkg/api/operation" @@ -31,7 +31,7 @@ func TestMaxLength(t *testing.T) { name string value string max int - wantErrs field.ErrorList // regex + wantErrs field.ErrorList }{{ name: "empty string", value: "", @@ -42,7 +42,7 @@ func TestMaxLength(t *testing.T) { value: "0", max: 0, wantErrs: field.ErrorList{ - field.TooLong(field.NewPath("fldpath"), nil, 0).WithOrigin("maxLength"), + field.TooLongCharacters(field.NewPath("fldpath"), "", 0).WithOrigin("maxLength"), }, }, { name: "one character", @@ -54,14 +54,55 @@ func TestMaxLength(t *testing.T) { value: "01", max: 1, wantErrs: field.ErrorList{ - field.TooLong(field.NewPath("fldpath"), nil, 1).WithOrigin("maxLength"), + field.TooLongCharacters(field.NewPath("fldpath"), "", 1).WithOrigin("maxLength"), }, }, { value: "", max: -1, wantErrs: field.ErrorList{ - field.TooLong(field.NewPath("fldpath"), nil, -1).WithOrigin("maxLength"), + field.TooLongCharacters(field.NewPath("fldpath"), "", -1).WithOrigin("maxLength"), + }, + }, { + name: "ascii-only characters, less characters than max (n-1)", + value: "abcdefghi", + max: 10, + wantErrs: nil, + }, { + name: "multi-byte characters, less characters than max (n-1)", + value: "©®©®©®©®©", + max: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, more characters than max (n+1)", + value: "abcdefghijkl", + max: 10, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 10).WithOrigin("maxLength"), }, + }, { + name: "multi-byte characters, more characters than max (n+1)", + value: "©®©®©®©®©®©", + max: 10, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 10).WithOrigin("maxLength"), + }, + }, { + name: "mixture of characters, minimum possible size of input is less than max, rune count exceed maximum", + value: "©abc®defghi", + max: 10, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 10).WithOrigin("maxLength"), + }, + }, { + name: "multi-byte characters, exact characters as max (n)", + value: "©®©®©®©®©®", + max: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, exact characters as max (n)", + value: "abcdefghij", + max: 10, + wantErrs: nil, }} matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() @@ -74,12 +115,60 @@ func TestMaxLength(t *testing.T) { } } +func TestMinItems(t *testing.T) { + cases := []struct { + name string + items int + min int + wantErrs field.ErrorList + }{{ + name: "0 items, min 0", + items: 0, + min: 0, + }, { + name: "1 item, min 0", + items: 1, + min: 0, + }, { + name: "1 item, min 1", + items: 1, + min: 1, + }, { + name: "0 items, min 1", + items: 0, + min: 1, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 0, 1).WithOrigin("minItems"), + }, + }, { + name: "1 item, min 2", + items: 1, + min: 2, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 1, 2).WithOrigin("minItems"), + }, + }, { + name: "0 items, min -1", + items: 0, + min: -1, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + value := make([]bool, tc.items) + gotErrs := MinItems(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + func TestMaxItems(t *testing.T) { cases := []struct { - name string - items int - max int - err string // regex + name string + items int + max int + wantErrs field.ErrorList }{{ name: "0 items, max 0", items: 0, @@ -88,7 +177,9 @@ func TestMaxItems(t *testing.T) { name: "1 item, max 0", items: 1, max: 0, - err: "fldpath: Too many.*must have at most", + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 1, 0).WithOrigin("maxItems"), + }, }, { name: "1 item, max 1", items: 1, @@ -97,35 +188,24 @@ func TestMaxItems(t *testing.T) { name: "2 items, max 1", items: 2, max: 1, - err: "fldpath: Too many.*must have at most", + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 2, 1).WithOrigin("maxItems"), + }, }, { name: "0 items, max -1", items: 0, max: -1, - err: "fldpath: Too many.*too many items", + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 0, -1).WithOrigin("maxItems"), + }, }} + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { value := make([]bool, tc.items) - result := MaxItems(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.max) - if len(result) > 0 && tc.err == "" { - t.Errorf("unexpected failure: %v", fmtErrs(result)) - return - } - if len(result) == 0 && tc.err != "" { - t.Errorf("unexpected success: expected %q", tc.err) - return - } - if len(result) > 0 { - if len(result) > 1 { - t.Errorf("unexepected multi-error: %v", fmtErrs(result)) - return - } - if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { - t.Errorf("wrong error\nexpected: %q\n got: %v", tc.err, fmtErrs(result)) - } - } + gotErrs := MaxItems(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) }) } } @@ -150,27 +230,31 @@ func TestMinimum(t *testing.T) { } type minimumTestCase[T constraints.Integer] struct { - value T - min T - err string // regex + min T + value T + wantErrs field.ErrorList } func testMinimumPositive[T constraints.Integer](t *testing.T) { t.Helper() cases := []minimumTestCase[T]{{ - value: 0, min: 0, - }, { value: 0, - min: 1, - err: "fldpath: Invalid value.*must be greater than or equal to", }, { - value: 1, min: 1, + value: 0, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be greater than or equal to").WithOrigin("minimum"), + }, }, { + min: 1, value: 1, + }, { min: 2, - err: "fldpath: Invalid value.*must be greater than or equal to", + value: 1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be greater than or equal to").WithOrigin("minimum"), + }, }} doTestMinimum[T](t, cases) } @@ -178,12 +262,14 @@ func testMinimumPositive[T constraints.Integer](t *testing.T) { func testMinimumNegative[T constraints.Signed](t *testing.T) { t.Helper() cases := []minimumTestCase[T]{{ - value: -1, min: -1, + value: -1, }, { - value: -2, min: -1, - err: "fldpath: Invalid value.*must be greater than or equal to", + value: -2, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be greater than or equal to").WithOrigin("minimum"), + }, }} doTestMinimum[T](t, cases) @@ -191,25 +277,268 @@ func testMinimumNegative[T constraints.Signed](t *testing.T) { func doTestMinimum[T constraints.Integer](t *testing.T, cases []minimumTestCase[T]) { t.Helper() - for i, tc := range cases { - v := tc.value - result := Minimum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.min) - if len(result) > 0 && tc.err == "" { - t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) - continue - } - if len(result) == 0 && tc.err != "" { - t.Errorf("case %d: unexpected success: expected %q", i, tc.err) - continue - } - if len(result) > 0 { - if len(result) > 1 { - t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) - continue - } - if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { - t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) - } - } + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + name := fmt.Sprintf("%T (%v >= %v)", tc.value, tc.value, tc.min) + t.Run(name, func(t *testing.T) { + v := tc.value + gotErrs := Minimum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMaximum(t *testing.T) { + testMaximumPositive[int](t) + testMaximumNegative[int](t) + testMaximumPositive[int8](t) + testMaximumNegative[int8](t) + testMaximumPositive[int16](t) + testMaximumNegative[int16](t) + testMaximumPositive[int32](t) + testMaximumNegative[int32](t) + testMaximumPositive[int64](t) + testMaximumNegative[int64](t) + + testMaximumPositive[uint](t) + testMaximumPositive[uint8](t) + testMaximumPositive[uint16](t) + testMaximumPositive[uint32](t) + testMaximumPositive[uint64](t) +} + +type maximumTestCase[T constraints.Integer] struct { + max T + value T + wantErrs field.ErrorList +} + +func testMaximumPositive[T constraints.Integer](t *testing.T) { + t.Helper() + cases := []maximumTestCase[T]{{ + max: 0, + value: 0, + }, { + max: 0, + value: 1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be less than or equal to").WithOrigin("maximum"), + }, + }, { + max: 1, + value: 1, + }, { + max: 1, + value: 2, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be less than or equal to").WithOrigin("maximum"), + }, + }} + doTestMaximum[T](t, cases) +} + +func testMaximumNegative[T constraints.Signed](t *testing.T) { + t.Helper() + cases := []maximumTestCase[T]{{ + max: -1, + value: -1, + }, { + max: -2, + value: -1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be less than or equal to").WithOrigin("maximum"), + }, + }} + + doTestMaximum[T](t, cases) +} + +func doTestMaximum[T constraints.Integer](t *testing.T, cases []maximumTestCase[T]) { + t.Helper() + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + name := fmt.Sprintf("%T (%v <= %v)", tc.value, tc.value, tc.max) + t.Run(name, func(t *testing.T) { + v := tc.value + gotErrs := Maximum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMaxBytes(t *testing.T) { + cases := []struct { + name string + value string + max int + wantErrs field.ErrorList + }{{ + name: "empty string", + value: "", + max: 0, + wantErrs: nil, + }, { + name: "zero length", + value: "0", + max: 0, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 0).WithOrigin("maxBytes"), + }, + }, { + name: "one character", + value: "0", + max: 1, + wantErrs: nil, + }, { + name: "two characters", + value: "01", + max: 1, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 1).WithOrigin("maxBytes"), + }, + }, { + value: "", + max: -1, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", -1).WithOrigin("maxBytes"), + }, + }, { + name: "ascii-only characters, less bytes than max", + value: "abcdefghi", + max: 10, + wantErrs: nil, + }, { + name: "multi-byte characters, less bytes than max", + value: "©®©®", + max: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, more bytes than max", + value: "abcdefghijkl", + max: 10, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 10).WithOrigin("maxBytes"), + }, + }, { + name: "multi-byte characters, more bytes than max", + value: "©®©®©©", + max: 10, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 10).WithOrigin("maxBytes"), + }, + }, { + name: "mixture of characters, less bytes than max", + value: "©abc®®", + max: 10, + wantErrs: nil, + }, { + name: "mixture of characters, more bytes than max", + value: "©abc®®abc", + max: 10, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 10).WithOrigin("maxBytes"), + }, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + v := tc.value + gotErrs := MaxBytes(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMinLength(t *testing.T) { + cases := []struct { + name string + value string + min int + wantErrs field.ErrorList // regex + }{{ + name: "empty string allowed", + value: "", + min: 0, + wantErrs: nil, + }, { + name: "minimum length of one, empty string", + value: "", + min: 1, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "", 1).WithOrigin("minLength"), + }, + }, { + name: "minimum length of one, non-empty string", + value: "test", + min: 1, + wantErrs: nil, + }, { + name: "minimum length of 10, 9 character string", + value: "012345678", + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "012345678", 10).WithOrigin("minLength"), + }, + }, { + name: "minimum length of 10, 10 character string", + value: "0123456789", + min: 10, + wantErrs: nil, + }, { + name: "negative minimum value", + value: "", + min: -1, + wantErrs: nil, + }, { + name: "ascii-only characters, less characters than min (n-1)", + value: "abcdefghi", + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "abcdefghi", 10).WithOrigin("minLength"), + }, + }, { + name: "multi-byte characters, less characters than min (n-1)", + value: "©®©®©®©®©", + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "©®©®©®©®©", 10).WithOrigin("minLength"), + }, + }, { + name: "ascii-only characters, more characters than min (n+1)", + value: "abcdefghijkl", + min: 10, + wantErrs: nil, + }, { + name: "multi-byte characters, more characters than min (n+1)", + value: "©®©®©®©®©®©", + min: 10, + wantErrs: nil, + }, { + name: "mixture of characters, maximum size in bytes of input is greater than min, rune count less than min", + value: "©®©®©®", // 12 bytes, but 6 characters + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "©®©®©®", 10).WithOrigin("minLength"), + }, + }, { + name: "multi-byte characters, exact characters as min (n)", + value: "©®©®©®©®©®", + min: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, exact characters as min (n)", + value: "abcdefghij", + min: 10, + wantErrs: nil, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + v := tc.value + gotErrs := MinLength(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) } } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt.go index 9a21473055fda..190fa8dbe947b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt.go @@ -133,6 +133,23 @@ func LabelValue[T ~string](_ context.Context, op operation.Operation, fldPath *f return allErrs } +// PathSegmentName verifies that the specified value is a valid path segment name. +// A path segment name can be safely encoded as a path segment in URLs and file paths. +// - must not be exactly "." or ".." +// - must not contain "/" (forward slash) +// - must not contain "%" (percent sign) +// - can contain any other characters including mixed case, numbers, dots, hyphens, underscores, and non-ASCII characters +func PathSegmentName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsPathSegmentName((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-path-segment-name")) + } + return allErrs +} + // UUID verifies that the specified value is a valid UUID (RFC 4122). // - must be 36 characters long // - must be in the normalized form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go index a3c209265304a..f2e84ffdbec0d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go @@ -412,6 +412,101 @@ func TestLabelValue(t *testing.T) { } } +func TestPathSegmentName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + // This format validator does not check for empty strings or max length, + // only checks path-segment-unsafe characters. Those constraints are + // handled by separate validators (Required, maxLength) in the schema. + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid simple name", + input: "valid-name", + wantErrs: nil, + }, { + name: "valid with dots", + input: "foo.bar.baz", + wantErrs: nil, + }, { + name: "valid with mixed case", + input: "MyResource", + wantErrs: nil, + }, { + name: "valid with numbers", + input: "resource123", + wantErrs: nil, + }, { + name: "valid with underscores", + input: "my_resource", + wantErrs: nil, + }, { + name: "valid complex identifier", + input: "sha256:ABCDEF012345@ABCDEF012345", + wantErrs: nil, + }, { + name: "valid with non-ASCII characters", + input: "Iñtërnâtiônàlizætiøn", + wantErrs: nil, + }, { + name: "valid with leading dot", + input: ".test", + wantErrs: nil, + }, { + name: "valid with leading double dot", + input: "..test", + wantErrs: nil, + }, { + name: "valid empty string", + input: "", + wantErrs: nil, + }, { + name: "invalid: exactly dot", + input: ".", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, ".", "may not be '.'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: exactly double dot", + input: "..", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "..", "may not be '..'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: contains slash", + input: "foo/bar", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "foo/bar", "may not contain '/'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: contains percent", + input: "foo%bar", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "foo%bar", "may not contain '%'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: contains both slash and percent", + input: "foo/bar%baz", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "may not contain '/'").WithOrigin("format=k8s-path-segment-name"), + field.Invalid(fldPath, nil, "may not contain '%'").WithOrigin("format=k8s-path-segment-name"), + }, + }} + + exactMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := PathSegmentName(ctx, operation.Operation{}, fldPath, &value, nil) + + exactMatcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + func TestLongNameCaseless(t *testing.T) { ctx := context.Background() fldPath := field.NewPath("test") diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/union.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/union.go index 03f45f8661dea..753a4a9ee12f4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/union.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/union.go @@ -19,6 +19,7 @@ package validate import ( "context" "fmt" + "reflect" "strings" "k8s.io/apimachinery/pkg/api/operation" @@ -60,6 +61,10 @@ type UnionValidationOptions struct { // )...) // return errs // } +// +// Note that T is "any", rather than "comparable", because union-members can be +// slices, meaning T might be a struct with a slice, meaning it is not +// comparable. func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList { options := UnionValidationOptions{ ErrorForEmpty: func(fldPath *field.Path, allFields []string) *field.Error { @@ -72,7 +77,7 @@ func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path }, } - return unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...) + return unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...).WithOrigin("union") } // DiscriminatedUnion verifies specified union member matches the discriminator. @@ -98,6 +103,10 @@ func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path // // It is not an error for the discriminatorValue to be unknown. That must be // validated on its own. +// +// Note that T is "any", rather than "comparable", because union-members can be +// slices, meaning T might be a struct with a slice, meaning it is not +// comparable. func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, discriminatorExtractor ExtractorFn[T, D], isSetFns ...ExtractorFn[T, bool]) (errs field.ErrorList) { if len(union.members) != len(isSetFns) { return field.ErrorList{ @@ -106,6 +115,7 @@ func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operat len(isSetFns), len(union.members))), } } + hasOldValue := !reflect.ValueOf(oldObj).IsZero() // because T is any, rather than comparable var changed bool discriminatorValue := discriminatorExtractor(obj) if op.Type == operation.Update { @@ -131,10 +141,10 @@ func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operat } // If the union discriminator and membership is unchanged, we don't need to // re-validate. - if op.Type == operation.Update && !changed { + if op.Type == operation.Update && hasOldValue && !changed { return nil } - return errs + return errs.WithOrigin("union") } // UnionMember represents a member of a union. @@ -195,6 +205,7 @@ func unionValidate[T any](op operation.Operation, fldPath *field.Path, } } + hasOldValue := !reflect.ValueOf(oldObj).IsZero() // because T is any, rather than comparable var specifiedFields []string var changed bool for i, fieldIsSet := range isSetFns { @@ -209,7 +220,7 @@ func unionValidate[T any](op operation.Operation, fldPath *field.Path, } // If the union membership is unchanged, we don't need to re-validate. - if op.Type == operation.Update && !changed { + if op.Type == operation.Update && hasOldValue && !changed { return nil } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/union_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/union_test.go index db9fb7231e42b..4afc3f759d7bb 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/union_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/union_test.go @@ -44,19 +44,19 @@ func TestUnion(t *testing.T) { name: "two members set", fields: []string{"a", "b", "c", "d"}, fieldValues: []bool{false, true, false, true}, - expected: field.ErrorList{field.Invalid(nil, "{b, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")}, + expected: field.ErrorList{field.Invalid(nil, "{b, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")}.WithOrigin("union"), }, { name: "all members set", fields: []string{"a", "b", "c", "d"}, fieldValues: []bool{true, true, true, true}, - expected: field.ErrorList{field.Invalid(nil, "{a, b, c, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")}, + expected: field.ErrorList{field.Invalid(nil, "{a, b, c, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")}.WithOrigin("union"), }, { name: "no member set", fields: []string{"a", "b", "c", "d"}, fieldValues: []bool{false, false, false, false}, - expected: field.ErrorList{field.Invalid(nil, "", "must specify one of: `a`, `b`, `c`, `d`")}, + expected: field.ErrorList{field.Invalid(nil, "", "must specify one of: `a`, `b`, `c`, `d`")}.WithOrigin("union"), }, } @@ -67,18 +67,34 @@ func TestUnion(t *testing.T) { members = append(members, NewUnionMember(f)) } - // Create mock extractors that return predefined values instead of - // actually extracting from the object. - extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) - for i, val := range tc.fieldValues { - extractors[i] = func(_ *testMember) bool { return val } - } + t.Run("pointer", func(t *testing.T) { + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ *testMember) bool { return val } + } - got := Union(context.Background(), operation.Operation{}, nil, &testMember{}, nil, - NewUnionMembership(members...), extractors...) - if !reflect.DeepEqual(got, tc.expected) { - t.Errorf("got %v want %v", got, tc.expected) - } + got := Union(context.Background(), operation.Operation{}, nil, &testMember{}, nil, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + t.Run("value", func(t *testing.T) { + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ testMember) bool { return val } + } + + got := Union(context.Background(), operation.Operation{}, nil, testMember{}, testMember{}, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) }) } } @@ -115,7 +131,7 @@ func TestDiscriminatedUnion(t *testing.T) { expected: field.ErrorList{ field.Invalid(field.NewPath("b"), "", "may only be specified when `type` is \"B\""), field.Invalid(field.NewPath("c"), "", "must be specified when `type` is \"C\""), - }, + }.WithOrigin("union"), }, { name: "invalid, discriminator correct, multiple members set", @@ -126,38 +142,58 @@ func TestDiscriminatedUnion(t *testing.T) { expected: field.ErrorList{ field.Invalid(field.NewPath("b"), "", "may only be specified when `type` is \"B\""), field.Invalid(field.NewPath("d"), "", "may only be specified when `type` is \"D\""), - }, + }.WithOrigin("union"), }, } for _, tc := range testCases { + members := []UnionMember{} + for _, f := range tc.fields { + members = append(members, NewDiscriminatedUnionMember(f[0], f[1])) + } + t.Run(tc.name, func(t *testing.T) { - members := []UnionMember{} - for _, f := range tc.fields { - members = append(members, NewDiscriminatedUnionMember(f[0], f[1])) - } + t.Run("pointer", func(t *testing.T) { + discriminatorExtractor := func(_ *testMember) string { return tc.discriminatorValue } - discriminatorExtractor := func(_ *testMember) string { return tc.discriminatorValue } + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ *testMember) bool { return val } + } - // Create mock extractors that return predefined values instead of - // actually extracting from the object. - extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) - for i, val := range tc.fieldValues { - extractors[i] = func(_ *testMember) bool { return val } - } + got := DiscriminatedUnion(context.Background(), operation.Operation{}, nil, &testMember{}, nil, + NewDiscriminatedUnionMembership(tc.discriminatorField, members...), discriminatorExtractor, extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got.ToAggregate(), tc.expected.ToAggregate()) + } + }) + t.Run("value", func(t *testing.T) { + discriminatorExtractor := func(_ testMember) string { return tc.discriminatorValue } - got := DiscriminatedUnion(context.Background(), operation.Operation{}, nil, &testMember{}, nil, - NewDiscriminatedUnionMembership(tc.discriminatorField, members...), discriminatorExtractor, extractors...) - if !reflect.DeepEqual(got, tc.expected) { - t.Errorf("got %v want %v", got.ToAggregate(), tc.expected.ToAggregate()) - } + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ testMember) bool { return val } + } + + got := DiscriminatedUnion(context.Background(), operation.Operation{}, nil, testMember{}, testMember{}, + NewDiscriminatedUnionMembership(tc.discriminatorField, members...), discriminatorExtractor, extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got.ToAggregate(), tc.expected.ToAggregate()) + } + }) }) } } type testStruct struct { - M1 *m1 `json:"m1"` - M2 *m2 `json:"m2"` + M1 *m1 `json:"m1"` + M2 *m2 `json:"m2"` + M3 []string `json:"m3"` + M4 map[string]string `json:"m4"` } type m1 struct{} @@ -176,6 +212,18 @@ var extractors = []ExtractorFn[*testStruct, bool]{ } return s.M2 != nil }, + func(s *testStruct) bool { + if s == nil { + return false + } + return len(s.M3) != 0 + }, + func(s *testStruct) bool { + if s == nil { + return false + } + return len(s.M4) != 0 + }, } func TestUnionRatcheting(t *testing.T) { @@ -186,9 +234,12 @@ func TestUnionRatcheting(t *testing.T) { expected field.ErrorList }{ { - name: "both nil", + name: "old nil - no ratcheting", oldStruct: nil, - newStruct: nil, + newStruct: &testStruct{}, + expected: field.ErrorList{ + field.Invalid(nil, "", "must specify one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), }, { name: "both empty struct", @@ -216,14 +267,40 @@ func TestUnionRatcheting(t *testing.T) { M2: &m2{}, }, expected: field.ErrorList{ - field.Invalid(nil, "{m1, m2}", "must specify exactly one of: `m1`, `m2`"), - }, + field.Invalid(nil, "{m1, m2}", "must specify exactly one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), + }, + { + name: "slice member ratcheting: unchanged membership", + oldStruct: &testStruct{M3: []string{"a"}}, + newStruct: &testStruct{M3: []string{"b"}}, + }, + { + name: "map member ratcheting: unchanged membership", + oldStruct: &testStruct{M4: map[string]string{"k": "v1"}}, + newStruct: &testStruct{M4: map[string]string{"k": "v2"}}, + }, + { + name: "empty slice is not set", + oldStruct: nil, + newStruct: &testStruct{M3: []string{}}, + expected: field.ErrorList{ + field.Invalid(nil, "", "must specify one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), + }, + { + name: "empty map is not set", + oldStruct: nil, + newStruct: &testStruct{M4: map[string]string{}}, + expected: field.ErrorList{ + field.Invalid(nil, "", "must specify one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - members := []UnionMember{NewUnionMember("m1"), NewUnionMember("m2")} + members := []UnionMember{NewUnionMember("m1"), NewUnionMember("m2"), NewUnionMember("m3"), NewUnionMember("m4")} got := Union(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, NewUnionMembership(members...), extractors...) if !reflect.DeepEqual(got, tc.expected) { @@ -234,9 +311,11 @@ func TestUnionRatcheting(t *testing.T) { } type testDiscriminatedStruct struct { - D string `json:"d"` - M1 *m1 `json:"m1"` - M2 *m2 `json:"m2"` + D string `json:"d"` + M1 *m1 `json:"m1"` + M2 *m2 `json:"m2"` + M3 []string `json:"m3"` + M4 map[string]string `json:"m4"` } var testDiscriminatorExtractor = func(s *testDiscriminatedStruct) string { @@ -258,6 +337,18 @@ var testDiscriminatedExtractors = []ExtractorFn[*testDiscriminatedStruct, bool]{ } return s.M2 != nil }, + func(s *testDiscriminatedStruct) bool { + if s == nil { + return false + } + return len(s.M3) != 0 + }, + func(s *testDiscriminatedStruct) bool { + if s == nil { + return false + } + return len(s.M4) != 0 + }, } func TestDiscriminatedUnionRatcheting(t *testing.T) { @@ -312,7 +403,7 @@ func TestDiscriminatedUnionRatcheting(t *testing.T) { }, expected: field.ErrorList{ field.Invalid(field.NewPath("m2"), "", "may only be specified when `d` is \"m2\""), - }, + }.WithOrigin("union"), }, { name: "fail on changing the discriminator", @@ -327,13 +418,63 @@ func TestDiscriminatedUnionRatcheting(t *testing.T) { expected: field.ErrorList{ field.Invalid(field.NewPath("m1"), "", "may only be specified when `d` is \"m1\""), field.Invalid(field.NewPath("m2"), "", "must be specified when `d` is \"m2\""), + }.WithOrigin("union"), + }, + { + name: "slice member ratcheting: unchanged membership", + oldStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{"a"}, }, + newStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{"b"}, + }, + }, + { + name: "map member ratcheting: unchanged membership", + oldStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{"k": "v1"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{"k": "v2"}, + }, + }, + { + name: "empty slice is not set", + oldStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{"a"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{}, + }, + expected: field.ErrorList{ + field.Invalid(field.NewPath("m3"), "", "must be specified when `d` is \"m3\""), + }.WithOrigin("union"), + }, + { + name: "empty map is not set", + oldStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{"k": "v"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{}, + }, + expected: field.ErrorList{ + field.Invalid(field.NewPath("m4"), "", "must be specified when `d` is \"m4\""), + }.WithOrigin("union"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - members := []UnionMember{NewDiscriminatedUnionMember("m1", "m1"), NewDiscriminatedUnionMember("m2", "m2")} + members := []UnionMember{NewDiscriminatedUnionMember("m1", "m1"), NewDiscriminatedUnionMember("m2", "m2"), NewDiscriminatedUnionMember("m3", "m3"), NewDiscriminatedUnionMember("m4", "m4")} got := DiscriminatedUnion(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, NewDiscriminatedUnionMembership("d", members...), testDiscriminatorExtractor, testDiscriminatedExtractors...) if !reflect.DeepEqual(got, tc.expected) { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go index 7222c5cd7a2e4..686a1c11a640c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go @@ -119,7 +119,7 @@ func TestZeroOrOneOfUnionRatcheting(t *testing.T) { M2: &m2{}, }, expected: field.ErrorList{ - field.Invalid(nil, "{m1, m2}", "must specify at most one of: `m1`, `m2`").WithOrigin("zeroOrOneOf"), + field.Invalid(nil, "{m1, m2}", "must specify at most one of: `m1`, `m2`, `m3`, `m4`").WithOrigin("zeroOrOneOf"), }, }, { @@ -142,7 +142,7 @@ func TestZeroOrOneOfUnionRatcheting(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - members := []UnionMember{NewUnionMember("m1"), NewUnionMember("m2")} + members := []UnionMember{NewUnionMember("m1"), NewUnionMember("m2"), NewUnionMember("m3"), NewUnionMember("m4")} got := ZeroOrOneOfUnion(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, NewUnionMembership(members...), extractors...) if !reflect.DeepEqual(got, tc.expected) { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go index 839fcbc2c17b0..9a4f378483a6a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go @@ -46,7 +46,7 @@ func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) fie for k := range annotations { // The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking. for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { - allErrs = append(allErrs, field.Invalid(fldPath, k, msg)).WithOrigin("format=k8s-label-key") + allErrs = append(allErrs, field.Invalid(fldPath, k, msg).WithOrigin("format=k8s-label-key")) } } if err := ValidateAnnotationsSize(annotations); err != nil { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name.go index ffb9f56d800ed..c0dee8108a292 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name.go @@ -17,52 +17,26 @@ limitations under the License. package path import ( - "fmt" - "strings" + "k8s.io/apimachinery/pkg/api/validate/content" ) -// NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store) -var NameMayNotBe = []string{".", ".."} - -// NameMayNotContain specifies substrings that cannot be used in names specified as path segments (like the REST API or etcd store) -var NameMayNotContain = []string{"/", "%"} - // IsValidPathSegmentName validates the name can be safely encoded as a path segment -func IsValidPathSegmentName(name string) []string { - for _, illegalName := range NameMayNotBe { - if name == illegalName { - return []string{fmt.Sprintf(`may not be '%s'`, illegalName)} - } - } - - var errors []string - for _, illegalContent := range NameMayNotContain { - if strings.Contains(name, illegalContent) { - errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) - } - } - - return errors -} +// +// Deprecated: use content.IsPathSegmentName directly. +var IsValidPathSegmentName = content.IsPathSegmentName // IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment // It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid -func IsValidPathSegmentPrefix(name string) []string { - var errors []string - for _, illegalContent := range NameMayNotContain { - if strings.Contains(name, illegalContent) { - errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) - } - } - - return errors -} +// +// Deprecated: use content.IsPathSegmentPrefix directly. +var IsValidPathSegmentPrefix = content.IsPathSegmentPrefix // ValidatePathSegmentName validates the name can be safely encoded as a path segment +// +// Deprecated: use a locally defined function. func ValidatePathSegmentName(name string, prefix bool) []string { if prefix { return IsValidPathSegmentPrefix(name) } - return IsValidPathSegmentName(name) } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name_test.go deleted file mode 100644 index 4289052738878..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/path/name_test.go +++ /dev/null @@ -1,168 +0,0 @@ -/* -Copyright 2015 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 path - -import ( - "strings" - "testing" -) - -func TestValidatePathSegmentName(t *testing.T) { - testcases := map[string]struct { - Name string - Prefix bool - ExpectedMsg string - }{ - "empty": { - Name: "", - Prefix: false, - ExpectedMsg: "", - }, - "empty,prefix": { - Name: "", - Prefix: true, - ExpectedMsg: "", - }, - - "valid": { - Name: "foo.bar.baz", - Prefix: false, - ExpectedMsg: "", - }, - "valid,prefix": { - Name: "foo.bar.baz", - Prefix: true, - ExpectedMsg: "", - }, - - // Make sure mixed case, non DNS subdomain characters are tolerated - "valid complex": { - Name: "sha256:ABCDEF012345@ABCDEF012345", - Prefix: false, - ExpectedMsg: "", - }, - // Make sure non-ascii characters are tolerated - "valid extended charset": { - Name: "Iñtërnâtiônàlizætiøn", - Prefix: false, - ExpectedMsg: "", - }, - - "dot": { - Name: ".", - Prefix: false, - ExpectedMsg: ".", - }, - "dot leading": { - Name: ".test", - Prefix: false, - ExpectedMsg: "", - }, - "dot,prefix": { - Name: ".", - Prefix: true, - ExpectedMsg: "", - }, - - "dot dot": { - Name: "..", - Prefix: false, - ExpectedMsg: "..", - }, - "dot dot leading": { - Name: "..test", - Prefix: false, - ExpectedMsg: "", - }, - "dot dot,prefix": { - Name: "..", - Prefix: true, - ExpectedMsg: "", - }, - - "slash": { - Name: "foo/bar", - Prefix: false, - ExpectedMsg: "/", - }, - "slash,prefix": { - Name: "foo/bar", - Prefix: true, - ExpectedMsg: "/", - }, - - "percent": { - Name: "foo%bar", - Prefix: false, - ExpectedMsg: "%", - }, - "percent,prefix": { - Name: "foo%bar", - Prefix: true, - ExpectedMsg: "%", - }, - } - - for k, tc := range testcases { - msgs := ValidatePathSegmentName(tc.Name, tc.Prefix) - if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { - t.Errorf("%s: expected no message, got %v", k, msgs) - } - if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { - t.Errorf("%s: expected error message, got none", k) - } - if len(tc.ExpectedMsg) > 0 && !strings.Contains(msgs[0], tc.ExpectedMsg) { - t.Errorf("%s: expected message containing %q, got %v", k, tc.ExpectedMsg, msgs[0]) - } - } -} - -func TestValidateWithMultiErrors(t *testing.T) { - testcases := map[string]struct { - Name string - Prefix bool - ExpectedMsg []string - }{ - "slash,percent": { - Name: "foo//bar%", - Prefix: false, - ExpectedMsg: []string{"may not contain '/'", "may not contain '%'"}, - }, - "slash,percent,prefix": { - Name: "foo//bar%", - Prefix: true, - ExpectedMsg: []string{"may not contain '/'", "may not contain '%'"}, - }, - } - - for k, tc := range testcases { - msgs := ValidatePathSegmentName(tc.Name, tc.Prefix) - if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { - t.Errorf("%s: expected no message, got %v", k, msgs) - } - if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { - t.Errorf("%s: expected error message, got none", k) - } - if len(tc.ExpectedMsg) > 0 { - for i := 0; i < len(tc.ExpectedMsg); i++ { - if msgs[i] != tc.ExpectedMsg[i] { - t.Errorf("%s: expected message containing %q, got %v", k, tc.ExpectedMsg[i], msgs[i]) - } - } - } - } -} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go new file mode 100644 index 0000000000000..f431157ec4ee7 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go @@ -0,0 +1,30 @@ +/* +Copyright 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 internalversion + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" +) + +func Convert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { + return autoConvert_v1_ListOptions_To_internalversion_ListOptions(in, out, s) +} + +func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error { + return autoConvert_internalversion_ListOptions_To_v1_ListOptions(in, out, s) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go index 8c60e7d2a814d..a34a11c3e301c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go @@ -89,6 +89,11 @@ type ListOptions struct { // Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward // compatibility reasons) and to false otherwise. SendInitialEvents *bool + + // ShardSelector is the raw shard selector string from the request. + // Parsing is deferred to the apiserver storage layer where the CEL + // parser dependency is available. + ShardSelector string } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go index a6552c276eeaf..f321ad2345148 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go @@ -46,12 +46,12 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*ListOptions)(nil), (*v1.ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + if err := s.AddConversionFunc((*ListOptions)(nil), (*v1.ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_internalversion_ListOptions_To_v1_ListOptions(a.(*ListOptions), b.(*v1.ListOptions), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ListOptions)(nil), (*ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + if err := s.AddConversionFunc((*v1.ListOptions)(nil), (*ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_ListOptions_To_internalversion_ListOptions(a.(*v1.ListOptions), b.(*ListOptions), scope) }); err != nil { return err @@ -116,14 +116,10 @@ func autoConvert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out.Limit = in.Limit out.Continue = in.Continue out.SendInitialEvents = (*bool)(unsafe.Pointer(in.SendInitialEvents)) + out.ShardSelector = in.ShardSelector return nil } -// Convert_internalversion_ListOptions_To_v1_ListOptions is an autogenerated conversion function. -func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error { - return autoConvert_internalversion_ListOptions_To_v1_ListOptions(in, out, s) -} - func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { if err := v1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { return err @@ -139,10 +135,6 @@ func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOption out.Limit = in.Limit out.Continue = in.Continue out.SendInitialEvents = (*bool)(unsafe.Pointer(in.SendInitialEvents)) + out.ShardSelector = in.ShardSelector return nil } - -// Convert_v1_ListOptions_To_internalversion_ListOptions is an autogenerated conversion function. -func Convert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { - return autoConvert_v1_ListOptions_To_internalversion_ListOptions(in, out, s) -} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1.go new file mode 100644 index 0000000000000..81aca59facb60 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1.go @@ -0,0 +1,170 @@ +/* +Copyright 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 v1 + +import ( + "fmt" + "io" +) + +func (FieldsV1) SwaggerDoc() map[string]string { + return map[string]string{ + "": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + } +} + +type FieldsV1Reader interface { + io.Reader + io.ReaderAt + // Size returns the original byte length of the underlying data. Size is the number of bytes available for reading via ReadAt. + Size() int64 +} + +func (f *FieldsV1) DeepCopy() *FieldsV1 { + if f == nil { + return nil + } + out := new(FieldsV1) + f.DeepCopyInto(out) + return out +} + +func (f *FieldsV1) Marshal() (dAtA []byte, err error) { + size := f.Size() + dAtA = make([]byte, size) + n, err := f.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (f *FieldsV1) MarshalTo(dAtA []byte) (int, error) { + size := f.Size() + return f.MarshalToSizedBuffer(dAtA[:size]) +} + +func (f *FieldsV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + rawBytes := f.GetRawBytes() + if len(rawBytes) > 0 { + i -= len(rawBytes) + copy(dAtA[i:], rawBytes) + i = encodeVarintGenerated(dAtA, i, uint64(len(rawBytes))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (f *FieldsV1) Size() (n int) { + if f == nil { + return 0 + } + var l int + _ = l + if l := int(f.GetRawReader().Size()); l > 0 { + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (f *FieldsV1) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FieldsV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FieldsV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + + f.SetRawBytes(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_benchmark_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_benchmark_test.go new file mode 100644 index 0000000000000..812c181627c5e --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_benchmark_test.go @@ -0,0 +1,118 @@ +/* +Copyright 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 v1_test + +import ( + "encoding/json" + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var benchmarkPayloads = []string{ + // Small managed fields payload + `{"f:metadata":{"f:labels":{"f:app":{}},"f:annotations":{"f:revision":{}}},"f:spec":{"f:replicas":{},"f:template":{"f:metadata":{"f:labels":{"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"nginx\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`, + // Larger, deeply nested valid JSON payload (representing complex managedFields) + `{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".":{},"f:app.kubernetes.io/name":{}}},"f:spec":{"f:replicas":{},"f:selector":{},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app.kubernetes.io/name":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"app\"}":{".":{},"f:image":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8080,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{".":{},"f:limits":{".":{},"f:cpu":{},"f:memory":{}},"f:requests":{".":{},"f:cpu":{},"f:memory":{}}}}}}}}}`, +} + +// BenchmarkDecodeDuplicate measures the allocation and speed of unmarshaling +// exactly identical payloads repeatedly, which is the most common case for +// heavily replicated resources (DaemonSets, ReplicaSets) in the API server. +func BenchmarkDecodeDuplicate(b *testing.B) { + for _, payload := range benchmarkPayloads { + b.Run(fmt.Sprintf("Size%d", len(payload)), func(b *testing.B) { + rawJSON := []byte(payload) + b.ResetTimer() + b.ReportAllocs() + var retained []metav1.FieldsV1 + for j := 0; j < b.N; j++ { + var f metav1.FieldsV1 + if err := json.Unmarshal(rawJSON, &f); err != nil { + b.Fatal(err) + } + retained = append(retained, f) + } + _ = retained + }) + } +} + +// BenchmarkDecodeUnique measures the allocation and speed of unmarshaling +// completely unique payloads to measure the worst-case interning overhead. +func BenchmarkDecodeUnique(b *testing.B) { + for _, payload := range benchmarkPayloads { + b.Run(fmt.Sprintf("Size%d", len(payload)), func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + var retained []metav1.FieldsV1 + for j := 0; j < b.N; j++ { + // Inject the iteration counter to guarantee the string is unique + novelPayload := fmt.Appendf(nil, `{"f:iter":%d,%s`, j, payload[1:]) + var f metav1.FieldsV1 + if err := json.Unmarshal(novelPayload, &f); err != nil { + b.Fatal(err) + } + retained = append(retained, f) + } + _ = retained + }) + } +} + +// BenchmarkParallelDecode tests parallel deserialization of duplicate strings +// to verify lock contention behavior in highly concurrent environments. +func BenchmarkParallelDecode(b *testing.B) { + for _, payload := range benchmarkPayloads { + b.Run(fmt.Sprintf("Size%d", len(payload)), func(b *testing.B) { + rawJSON := []byte(payload) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var f metav1.FieldsV1 + if err := json.Unmarshal(rawJSON, &f); err != nil { + b.Fatal(err) + } + } + }) + }) + } +} + +// BenchmarkEqual_Same measures the fast-path equality check for identical structs. +func BenchmarkEqual_Same(b *testing.B) { + f1 := metav1.NewFieldsV1(benchmarkPayloads[1]) + f2 := metav1.NewFieldsV1(benchmarkPayloads[1]) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = f1.Equal(*f2) + } +} + +// BenchmarkEqual_Different measures the fallback-path equality check for differing structs. +func BenchmarkEqual_Different(b *testing.B) { + f1 := metav1.NewFieldsV1(benchmarkPayloads[1]) + f2 := metav1.NewFieldsV1(benchmarkPayloads[0]) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = f1.Equal(*f2) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_byte.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_byte.go new file mode 100644 index 0000000000000..66f28e1d9ffe6 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_byte.go @@ -0,0 +1,105 @@ +//go:build !fieldsv1string + +/* +Copyright 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 v1 + +import ( + "bytes" +) + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of a item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +k8s:deepcopy-gen=false +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +type FieldsV1 struct { + // Raw is the underlying serialization of this object. + // + // Deprecated: Direct access to this field is deprecated. Use GetRawBytes, GetRawString, SetRawBytes, SetRawString, GetRawReader, NewFieldsV1 instead. + Raw []byte `json:"-" protobuf:"bytes,1,opt,name=Raw"` +} + +func (f FieldsV1) String() string { + return string(f.Raw) +} + +func (f FieldsV1) Equal(f2 FieldsV1) bool { + return bytes.Equal(f.Raw, f2.Raw) +} + +func (f *FieldsV1) GetRawReader() FieldsV1Reader { + if f == nil || len(f.Raw) == 0 { + return bytes.NewReader(nil) + } + return bytes.NewReader(f.Raw) +} + +// GetRawBytes returns the raw bytes. +// These may or may not be a copy of the underlying bytes. +// If mutating the underlying bytes is desired, the returned bytes may be mutated and then passed to SetRawBytes(). +// If mutating the underlying bytes is not desired, make a copy of the returned bytes. +func (f *FieldsV1) GetRawBytes() []byte { + if f == nil { + return nil + } + return f.Raw +} + +// GetRawString returns the raw data as a string. +func (f *FieldsV1) GetRawString() string { + if f == nil { + return "" + } + return string(f.Raw) +} + +// SetRawBytes sets the raw bytes. It does not retain the passed-in byte slice. +func (f *FieldsV1) SetRawBytes(b []byte) { + if f != nil { + f.Raw = bytes.Clone(b) + } +} + +// SetRawString sets the raw data from a string. +func (f *FieldsV1) SetRawString(s string) { + if f != nil { + f.Raw = []byte(s) + } +} + +func NewFieldsV1(raw string) *FieldsV1 { + return &FieldsV1{Raw: []byte(raw)} +} + +func (f *FieldsV1) DeepCopyInto(out *FieldsV1) { + *out = *f + if f.Raw != nil { + in, out := &f.Raw, &out.Raw + *out = make([]byte, len(*in)) + copy(*out, *in) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string.go new file mode 100644 index 0000000000000..679452ae17dd0 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string.go @@ -0,0 +1,113 @@ +//go:build fieldsv1string + +/* +Copyright 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 v1 + +import ( + "strings" + "unique" +) + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of a item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +k8s:deepcopy-gen=false +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +type FieldsV1 struct { + // The zero value of a unique.Handle[string] has an uninitialized underlying pointer. + // Calling .Value() on it panics. We must explicitly check for this uninitialized + // state (f.handle == unique.Handle[string]{}) across accessors to safely support + // uninitialized metav1.FieldsV1{} objects. + // See ongoing golang discussion related to this here: https://github.com/golang/go/issues/73344 + handle unique.Handle[string] +} + +func (f FieldsV1) String() string { + if f.handle == (unique.Handle[string]{}) { + return "" + } + return f.handle.Value() +} + +func (f FieldsV1) Equal(f2 FieldsV1) bool { + if f.handle == f2.handle { + return true + } + // An uninitialized FieldsV1 compared to an explicitly empty + // FieldsV1 (unique.Make("") will fail the handle check above. + // Evaluate string contents directly as well to maintain parity with legacy + // bytes.Equal(nil, []byte{}) == true behavior. + return f.GetRawString() == f2.GetRawString() +} + +func (f *FieldsV1) GetRawReader() FieldsV1Reader { + if f == nil || f.handle == (unique.Handle[string]{}) { + return strings.NewReader("") + } + return strings.NewReader(f.handle.Value()) +} + +// GetRawBytes returns the raw bytes. +// These may or may not be a copy of the underlying bytes. +// If mutating the underlying bytes is desired, the returned bytes may be mutated and then passed to SetRawBytes(). +// If mutating the underlying bytes is not desired, make a copy of the returned bytes. +func (f *FieldsV1) GetRawBytes() []byte { + if f == nil || f.handle == (unique.Handle[string]{}) { + return nil + } + return []byte(f.handle.Value()) +} + +// GetRawString returns the raw data as a string. +func (f *FieldsV1) GetRawString() string { + if f == nil || f.handle == (unique.Handle[string]{}) { + return "" + } + return f.handle.Value() +} + +// SetRawBytes sets the raw bytes. It does not retain the passed-in byte slice. +func (f *FieldsV1) SetRawBytes(b []byte) { + if f != nil { + f.handle = unique.Make(string(b)) + } +} + +// SetRawString sets the raw data from a string. +func (f *FieldsV1) SetRawString(s string) { + if f != nil { + f.handle = unique.Make(s) + } +} + +func NewFieldsV1(raw string) *FieldsV1 { + return &FieldsV1{handle: unique.Make(raw)} +} + +func (f *FieldsV1) DeepCopyInto(out *FieldsV1) { + *out = *f +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string_test.go new file mode 100644 index 0000000000000..384951673aaa2 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string_test.go @@ -0,0 +1,362 @@ +//go:build fieldsv1string + +/* +Copyright 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 v1_test + +import ( + "bytes" + "encoding/json" + "io" + "testing" + "unsafe" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// requireInterned fails the test if the two strings do not point to the exact same memory address. +func requireInterned(t *testing.T, a, b string) { + t.Helper() + ptrA := unsafe.StringData(a) + ptrB := unsafe.StringData(b) + if ptrA != ptrB { + t.Fatalf("Expected strings to be interned (same memory address) but pointers differ: %p != %p", ptrA, ptrB) + } +} + +func TestFieldsV1_String(t *testing.T) { + for _, tc := range []struct { + name string + f metav1.FieldsV1 + expected string + }{ + { + name: "zero value handle", + f: metav1.FieldsV1{}, + expected: "", + }, + { + name: "initialized empty handle", + f: *metav1.NewFieldsV1(""), + expected: "", + }, + { + name: "valid payload", + f: *metav1.NewFieldsV1(`{"f:app":{}}`), + expected: `{"f:app":{}}`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.f.String(); got != tc.expected { + t.Errorf("expected %q, got %q", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_Equal(t *testing.T) { + valid := *metav1.NewFieldsV1(`{"f:app":{}}`) + validClone := *metav1.NewFieldsV1(`{"f:app":{}}`) + different := *metav1.NewFieldsV1(`{"f:other":{}}`) + + for _, tc := range []struct { + name string + a metav1.FieldsV1 + b metav1.FieldsV1 + expected bool + }{ + {"both zero value", metav1.FieldsV1{}, metav1.FieldsV1{}, true}, + {"zero value and initialized empty handle", metav1.FieldsV1{}, *metav1.NewFieldsV1(""), true}, + {"identical valid payloads", valid, validClone, true}, + {"different payloads", valid, different, false}, + {"zero value and valid payload", metav1.FieldsV1{}, valid, false}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.a.Equal(tc.b); got != tc.expected { + t.Errorf("expected Equal() to be %v, got %v", tc.expected, got) + } + // Commutative property + if got := tc.b.Equal(tc.a); got != tc.expected { + t.Errorf("expected Equal() commutative to be %v, got %v", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_GetRawReader(t *testing.T) { + for _, tc := range []struct { + name string + f *metav1.FieldsV1 + expected []byte + }{ + {"nil receiver", nil, []byte("")}, + {"zero value handle", &metav1.FieldsV1{}, []byte("")}, + {"initialized empty handle", metav1.NewFieldsV1(""), []byte("")}, + {"valid payload", metav1.NewFieldsV1(`{"f:app":{}}`), []byte(`{"f:app":{}}`)}, + } { + t.Run(tc.name, func(t *testing.T) { + reader := tc.f.GetRawReader() + got, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("unexpected error reading from RawReader: %v", err) + } + if !bytes.Equal(got, tc.expected) { + t.Errorf("expected %q, got %q", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_GetRawBytes(t *testing.T) { + for _, tc := range []struct { + name string + f *metav1.FieldsV1 + expected []byte + }{ + {"nil receiver", nil, nil}, + {"zero value handle", &metav1.FieldsV1{}, nil}, + {"initialized empty handle", metav1.NewFieldsV1(""), []byte{}}, + {"valid payload", metav1.NewFieldsV1(`{"f:app":{}}`), []byte(`{"f:app":{}}`)}, + } { + t.Run(tc.name, func(t *testing.T) { + got := tc.f.GetRawBytes() + if !bytes.Equal(got, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, got) + } + // Explicitly check for nil + if tc.expected == nil && got != nil { + t.Errorf("expected strict nil, got %v", got) + } + }) + } + + t.Run("mutation safety", func(t *testing.T) { + f := metav1.NewFieldsV1(`{"f:app":{}}`) + b := f.GetRawBytes() + + // Mutate the returned bytes + b[2] = 'X' + + // The original interned string must remain unchanged + if got := f.GetRawString(); got != `{"f:app":{}}` { + t.Errorf("GetRawBytes returned an unisolated slice! Mutating it corrupted the handle. Got: %s", got) + } + }) +} + +func TestFieldsV1_GetRawString(t *testing.T) { + for _, tc := range []struct { + name string + f *metav1.FieldsV1 + expected string + }{ + {"nil receiver", nil, ""}, + {"zero value handle", &metav1.FieldsV1{}, ""}, + {"initialized empty handle", metav1.NewFieldsV1(""), ""}, + {"valid payload", metav1.NewFieldsV1(`{"f:app":{}}`), `{"f:app":{}}`}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.f.GetRawString(); got != tc.expected { + t.Errorf("expected %q, got %q", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_SetRawBytes(t *testing.T) { + t.Run("nil receiver", func(t *testing.T) { + var f *metav1.FieldsV1 // nil + f.SetRawBytes([]byte("test")) // Should not panic + }) + + t.Run("empty slice input", func(t *testing.T) { + f := &metav1.FieldsV1{} + f.SetRawBytes([]byte{}) + if f.GetRawString() != "" { + t.Errorf("Expected empty string, got %q", f.GetRawString()) + } + }) + + t.Run("nil slice input", func(t *testing.T) { + f := &metav1.FieldsV1{} + f.SetRawBytes(nil) + if f.GetRawString() != "" { + t.Errorf("Expected empty string, got %q", f.GetRawString()) + } + }) + + t.Run("interning behavior", func(t *testing.T) { + payload := []byte(`{"f:app":{}}`) + + // Two distinct slices with identical content + b1 := append([]byte(nil), payload...) + b2 := append([]byte(nil), payload...) + + f1 := &metav1.FieldsV1{} + f1.SetRawBytes(b1) + + f2 := &metav1.FieldsV1{} + f2.SetRawBytes(b2) + + if f1.GetRawString() != string(payload) { + t.Errorf("Expected GetRawString to match payload, got %q", f1.GetRawString()) + } + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) +} + +func TestFieldsV1_SetRawString(t *testing.T) { + t.Run("nil receiver", func(t *testing.T) { + var f *metav1.FieldsV1 // nil + f.SetRawString("test") // Should not panic + }) + + t.Run("interning behavior", func(t *testing.T) { + payload := `{"f:app":{}}` + + // Force string copy to avoid compiler static string optimization if possible + s1 := string(append([]byte(nil), payload...)) + s2 := string(append([]byte(nil), payload...)) + + f1 := &metav1.FieldsV1{} + f1.SetRawString(s1) + + f2 := &metav1.FieldsV1{} + f2.SetRawString(s2) + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) +} + +func TestFieldsV1_NewFieldsV1(t *testing.T) { + t.Run("interning behavior", func(t *testing.T) { + payload := `{"f:app":{}}` + s1 := string(append([]byte(nil), payload...)) + s2 := string(append([]byte(nil), payload...)) + + f1 := metav1.NewFieldsV1(s1) + f2 := metav1.NewFieldsV1(s2) + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) +} + +func TestFieldsV1_DeepCopyInto(t *testing.T) { + t.Run("preserves interning", func(t *testing.T) { + orig := metav1.NewFieldsV1(`{"f:app":{}}`) + var clone metav1.FieldsV1 + orig.DeepCopyInto(&clone) + + if orig.GetRawString() != clone.GetRawString() { + t.Fatalf("Expected strings to match after DeepCopyInto, got %q and %q", orig.GetRawString(), clone.GetRawString()) + } + + requireInterned(t, orig.GetRawString(), clone.GetRawString()) + }) + + t.Run("zero value deep copy", func(t *testing.T) { + var orig metav1.FieldsV1 + var clone metav1.FieldsV1 + orig.DeepCopyInto(&clone) + + if clone.GetRawString() != "" { + t.Errorf("Expected empty string from cloned zero-value, got %q", clone.GetRawString()) + } + if !orig.Equal(clone) { + t.Errorf("Expected original zero-value and its clone to be equal") + } + }) + + t.Run("independence after deep copy", func(t *testing.T) { + orig := metav1.NewFieldsV1(`{"f:app":{}}`) + var clone metav1.FieldsV1 + orig.DeepCopyInto(&clone) + + // Modify the clone + clone.SetRawString(`{"f:new":{}}`) + + // The original should be completely untouched + if got := orig.GetRawString(); got != `{"f:app":{}}` { + t.Errorf("DeepCopyInto failed to isolate clone! Modifying clone corrupted original. Got: %s", got) + } + }) +} +func TestFieldsV1_UnmarshalInterning(t *testing.T) { + jsonPayload := []byte(`{"f:metadata":{"f:labels":{"f:app":{}}}}`) + orig := metav1.NewFieldsV1(string(jsonPayload)) + + protoPayload, err := orig.Marshal() + if err != nil { + t.Fatalf("Failed to marshal protobuf payload during setup: %v", err) + } + + cborPayload, err := orig.MarshalCBOR() + if err != nil { + t.Fatalf("Failed to marshal CBOR payload during setup: %v", err) + } + + tests := []struct { + name string + payload []byte + unmarshal func(t *testing.T, f *metav1.FieldsV1, payload []byte) + }{ + { + name: "json unmarshal", + payload: jsonPayload, + unmarshal: func(t *testing.T, f *metav1.FieldsV1, payload []byte) { + if err := json.Unmarshal(payload, f); err != nil { + t.Fatalf("JSON Unmarshal failed: %v", err) + } + }, + }, + { + name: "protobuf unmarshal", + payload: protoPayload, + unmarshal: func(t *testing.T, f *metav1.FieldsV1, payload []byte) { + if err := f.Unmarshal(payload); err != nil { + t.Fatalf("Protobuf Unmarshal failed: %v", err) + } + }, + }, + { + name: "cbor unmarshal", + payload: cborPayload, + unmarshal: func(t *testing.T, f *metav1.FieldsV1, payload []byte) { + if err := f.UnmarshalCBOR(payload); err != nil { + t.Fatalf("CBOR Unmarshal failed: %v", err) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Ensure independent slice memory + b1 := append([]byte(nil), tc.payload...) + b2 := append([]byte(nil), tc.payload...) + + var f1 metav1.FieldsV1 + var f2 metav1.FieldsV1 + + tc.unmarshal(t, &f1, b1) + tc.unmarshal(t, &f2, b2) + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go index f6b1a6a4e06bb..293b0223a731f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go @@ -105,6 +105,8 @@ func (m *RootPaths) Reset() { *m = RootPaths{} } func (m *ServerAddressByClientCIDR) Reset() { *m = ServerAddressByClientCIDR{} } +func (m *ShardInfo) Reset() { *m = ShardInfo{} } + func (m *Status) Reset() { *m = Status{} } func (m *StatusCause) Reset() { *m = StatusCause{} } @@ -694,36 +696,6 @@ func (m *FieldSelectorRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } -func (m *FieldsV1) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *FieldsV1) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *FieldsV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Raw != nil { - i -= len(m.Raw) - copy(dAtA[i:], m.Raw) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Raw))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *GetOptions) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1130,6 +1102,18 @@ func (m *ListMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ShardInfo != nil { + { + size, err := m.ShardInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } if m.RemainingItemCount != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.RemainingItemCount)) i-- @@ -1173,6 +1157,11 @@ func (m *ListOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i -= len(m.ShardSelector) + copy(dAtA[i:], m.ShardSelector) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ShardSelector))) + i-- + dAtA[i] = 0x7a if m.SendInitialEvents != nil { i-- if *m.SendInitialEvents { @@ -1797,6 +1786,34 @@ func (m *ServerAddressByClientCIDR) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *ShardInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ShardInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Selector) + copy(dAtA[i:], m.Selector) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Selector))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *Status) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2405,19 +2422,6 @@ func (m *FieldSelectorRequirement) Size() (n int) { return n } -func (m *FieldsV1) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Raw != nil { - l = len(m.Raw) - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - func (m *GetOptions) Size() (n int) { if m == nil { return 0 @@ -2585,6 +2589,10 @@ func (m *ListMeta) Size() (n int) { if m.RemainingItemCount != nil { n += 1 + sovGenerated(uint64(*m.RemainingItemCount)) } + if m.ShardInfo != nil { + l = m.ShardInfo.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -2613,6 +2621,8 @@ func (m *ListOptions) Size() (n int) { if m.SendInitialEvents != nil { n += 2 } + l = len(m.ShardSelector) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -2835,6 +2845,17 @@ func (m *ServerAddressByClientCIDR) Size() (n int) { return n } +func (m *ShardInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Selector) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *Status) Size() (n int) { if m == nil { return 0 @@ -3217,6 +3238,7 @@ func (this *ListMeta) String() string { `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, `Continue:` + fmt.Sprintf("%v", this.Continue) + `,`, `RemainingItemCount:` + valueToStringGenerated(this.RemainingItemCount) + `,`, + `ShardInfo:` + strings.Replace(this.ShardInfo.String(), "ShardInfo", "ShardInfo", 1) + `,`, `}`, }, "") return s @@ -3236,6 +3258,7 @@ func (this *ListOptions) String() string { `AllowWatchBookmarks:` + fmt.Sprintf("%v", this.AllowWatchBookmarks) + `,`, `ResourceVersionMatch:` + fmt.Sprintf("%v", this.ResourceVersionMatch) + `,`, `SendInitialEvents:` + valueToStringGenerated(this.SendInitialEvents) + `,`, + `ShardSelector:` + fmt.Sprintf("%v", this.ShardSelector) + `,`, `}`, }, "") return s @@ -3405,6 +3428,16 @@ func (this *ServerAddressByClientCIDR) String() string { }, "") return s } +func (this *ShardInfo) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ShardInfo{`, + `Selector:` + fmt.Sprintf("%v", this.Selector) + `,`, + `}`, + }, "") + return s +} func (this *Status) String() string { if this == nil { return "nil" @@ -5314,90 +5347,6 @@ func (m *FieldSelectorRequirement) Unmarshal(dAtA []byte) error { } return nil } -func (m *FieldsV1) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: FieldsV1: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: FieldsV1: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...) - if m.Raw == nil { - m.Raw = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *GetOptions) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -6847,6 +6796,42 @@ func (m *ListMeta) Unmarshal(dAtA []byte) error { } } m.RemainingItemCount = &v + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ShardInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ShardInfo == nil { + m.ShardInfo = &ShardInfo{} + } + if err := m.ShardInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -7157,6 +7142,38 @@ func (m *ListOptions) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.SendInitialEvents = &b + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ShardSelector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ShardSelector = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -9113,6 +9130,88 @@ func (m *ServerAddressByClientCIDR) Unmarshal(dAtA []byte) error { } return nil } +func (m *ShardInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ShardInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ShardInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Selector = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Status) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto index fb21b72368683..d03a733141149 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto @@ -369,9 +369,13 @@ message FieldSelectorRequirement { // If a key maps to an empty Fields value, the field that key represents is part of the set. // // The exact format is defined in sigs.k8s.io/structured-merge-diff +// +k8s:deepcopy-gen=false +// +protobuf.options.marshal=false // +protobuf.options.(gogoproto.goproto_stringer)=false message FieldsV1 { // Raw is the underlying serialization of this object. + // + // Deprecated: Direct access to this field is deprecated. Use GetRawBytes, GetRawString, SetRawBytes, SetRawString, GetRawReader, NewFieldsV1 instead. optional bytes Raw = 1; } @@ -533,6 +537,17 @@ message ListMeta { // should not rely on the remainingItemCount to be set or to be exact. // +optional optional int64 remainingItemCount = 4; + + // shardInfo is set when the list is a filtered subset of the full collection, + // as selected by a shard selector on the request. It echoes back the selector + // so clients can verify which shard they received and merge sharded responses. + // Clients should not cache sharded list responses as a full representation + // of the collection. + // + // This is an alpha field and requires enabling the ShardedListAndWatch feature gate. + // +featureGate=ShardedListAndWatch + // +optional + optional ShardInfo shardInfo = 5; } // ListOptions is the query options to a standard REST list call. @@ -643,6 +658,38 @@ message ListOptions { // compatibility reasons) and to false otherwise. // +optional optional bool sendInitialEvents = 11; + + // shardSelector restricts the list of returned objects using a CEL-based + // shard selector expression. The format uses the shardRange() function + // combined with || (logical OR) to specify one or more hash ranges: + // + // shardRange(object.metadata.uid, '0x0', '0x8000000000000000') + // shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000') + // + // Field paths use CEL-style object-rooted syntax (e.g. "object.metadata.uid"), + // NOT the fieldSelector format ("metadata.uid"). Currently supported paths: + // - object.metadata.uid + // - object.metadata.namespace + // + // hexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, + // defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a + // hash space. The full range is [0x0, 0x10000000000000000), where the exclusive + // upper bound equals 2^64. + // + // Examples: + // 2-shard split: + // shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000') + // shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000') + // 4-shard split: + // shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000') + // shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000') + // shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000') + // shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000') + // + // This is an alpha field and requires enabling the ShardedListAndWatch feature gate. + // +featureGate=ShardedListAndWatch + // +optional + optional string shardSelector = 15; } // ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource @@ -1009,6 +1056,15 @@ message ServerAddressByClientCIDR { optional string serverAddress = 2; } +// ShardInfo describes the shard selector that was applied to produce a list response. +// Its presence on a list response indicates the list is a filtered subset. +message ShardInfo { + // selector is the shard selector string from the request, echoed back so clients + // can verify which shard they received and merge responses from multiple shards. + // +required + optional string selector = 1; +} + // Status is a return value for calls that don't return other objects. message Status { // Standard list metadata. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.protomessage.pb.go deleted file mode 100644 index 459ae1ad873d3..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,112 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*APIGroup) ProtoMessage() {} - -func (*APIGroupList) ProtoMessage() {} - -func (*APIResource) ProtoMessage() {} - -func (*APIResourceList) ProtoMessage() {} - -func (*APIVersions) ProtoMessage() {} - -func (*ApplyOptions) ProtoMessage() {} - -func (*Condition) ProtoMessage() {} - -func (*CreateOptions) ProtoMessage() {} - -func (*DeleteOptions) ProtoMessage() {} - -func (*Duration) ProtoMessage() {} - -func (*FieldSelectorRequirement) ProtoMessage() {} - -func (*FieldsV1) ProtoMessage() {} - -func (*GetOptions) ProtoMessage() {} - -func (*GroupKind) ProtoMessage() {} - -func (*GroupResource) ProtoMessage() {} - -func (*GroupVersion) ProtoMessage() {} - -func (*GroupVersionForDiscovery) ProtoMessage() {} - -func (*GroupVersionKind) ProtoMessage() {} - -func (*GroupVersionResource) ProtoMessage() {} - -func (*LabelSelector) ProtoMessage() {} - -func (*LabelSelectorRequirement) ProtoMessage() {} - -func (*List) ProtoMessage() {} - -func (*ListMeta) ProtoMessage() {} - -func (*ListOptions) ProtoMessage() {} - -func (*ManagedFieldsEntry) ProtoMessage() {} - -func (*MicroTime) ProtoMessage() {} - -func (*ObjectMeta) ProtoMessage() {} - -func (*OwnerReference) ProtoMessage() {} - -func (*PartialObjectMetadata) ProtoMessage() {} - -func (*PartialObjectMetadataList) ProtoMessage() {} - -func (*Patch) ProtoMessage() {} - -func (*PatchOptions) ProtoMessage() {} - -func (*Preconditions) ProtoMessage() {} - -func (*RootPaths) ProtoMessage() {} - -func (*ServerAddressByClientCIDR) ProtoMessage() {} - -func (*Status) ProtoMessage() {} - -func (*StatusCause) ProtoMessage() {} - -func (*StatusDetails) ProtoMessage() {} - -func (*TableOptions) ProtoMessage() {} - -func (*Time) ProtoMessage() {} - -func (*Timestamp) ProtoMessage() {} - -func (*TypeMeta) ProtoMessage() {} - -func (*UpdateOptions) ProtoMessage() {} - -func (*Verbs) ProtoMessage() {} - -func (*WatchEvent) ProtoMessage() {} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go index c748071ed7fbc..45ed1e9891ec7 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go @@ -279,17 +279,18 @@ func ResetObjectMetaForStatus(meta, existingMeta Object) { // MarshalJSON may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (f FieldsV1) MarshalJSON() ([]byte, error) { - if f.Raw == nil { + raw := f.GetRawBytes() + if len(raw) == 0 { return []byte("null"), nil } if f.getContentType() == fieldsV1InvalidOrValidCBORObject { var u map[string]interface{} - if err := cbor.Unmarshal(f.Raw, &u); err != nil { + if err := cbor.Unmarshal(raw, &u); err != nil { return nil, fmt.Errorf("metav1.FieldsV1 cbor invalid: %w", err) } return utiljson.Marshal(u) } - return f.Raw, nil + return raw, nil } // UnmarshalJSON implements json.Unmarshaler @@ -298,7 +299,7 @@ func (f *FieldsV1) UnmarshalJSON(b []byte) error { return errors.New("metav1.FieldsV1: UnmarshalJSON on nil pointer") } if !bytes.Equal(b, []byte("null")) { - f.Raw = append(f.Raw[0:0], b...) + f.SetRawBytes(b) } return nil } @@ -307,17 +308,18 @@ var _ json.Marshaler = FieldsV1{} var _ json.Unmarshaler = &FieldsV1{} func (f FieldsV1) MarshalCBOR() ([]byte, error) { - if f.Raw == nil { + raw := f.GetRawBytes() + if len(raw) == 0 { return cbor.Marshal(nil) } if f.getContentType() == fieldsV1InvalidOrValidJSONObject { var u map[string]interface{} - if err := utiljson.Unmarshal(f.Raw, &u); err != nil { + if err := utiljson.Unmarshal(raw, &u); err != nil { return nil, fmt.Errorf("metav1.FieldsV1 json invalid: %w", err) } return cbor.Marshal(u) } - return f.Raw, nil + return raw, nil } var cborNull = []byte{0xf6} @@ -327,7 +329,7 @@ func (f *FieldsV1) UnmarshalCBOR(b []byte) error { return errors.New("metav1.FieldsV1: UnmarshalCBOR on nil pointer") } if !bytes.Equal(b, cborNull) { - f.Raw = append(f.Raw[0:0], b...) + f.SetRawBytes(b) } return nil } @@ -362,8 +364,13 @@ const ( // or, if a tag-enclosed map, an initial byte with major type "tag" (0xf6, 0xa0...0xbf, or // 0xc6...0xdb). The two sets of valid initial bytes don't intersect. func (f FieldsV1) getContentType() int { - if len(f.Raw) > 0 { - p := f.Raw[0] + reader := f.GetRawReader() + if reader.Size() > 0 { + var buf [1]byte + if _, err := reader.Read(buf[:]); err != nil { + return fieldsV1InvalidOrEmpty + } + p := buf[0] switch p { case 'n', '{', '\t', '\r', '\n', ' ': return fieldsV1InvalidOrValidJSONObject diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go index 39e02cbdc6e2e..b529d9b0b10fb 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go @@ -258,43 +258,38 @@ func TestFieldsV1MarshalJSON(t *testing.T) { Error string }{ { - Name: "nil encodes as json null", + Name: "zero-value encodes as json null", FieldsV1: FieldsV1{}, Want: []byte(`null`), }, - { - Name: "empty invalid json is returned as-is", - FieldsV1: FieldsV1{Raw: []byte{}}, - Want: []byte{}, - }, { Name: "cbor null is transcoded to json null", - FieldsV1: FieldsV1{Raw: []byte{0xf6}}, // null + FieldsV1: *NewFieldsV1(string([]byte{0xf6})), // null Want: []byte(`null`), }, { Name: "valid non-map cbor and valid non-object json is returned as-is", - FieldsV1: FieldsV1{Raw: []byte{0x30}}, + FieldsV1: *NewFieldsV1(string([]byte{0x30})), Want: []byte{0x30}, // Valid CBOR encoding of -17 and JSON encoding of 0! }, { Name: "self-described cbor map is transcoded to json map", - FieldsV1: FieldsV1{Raw: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}}, // 55799({"foo":"bar"}) + FieldsV1: *NewFieldsV1(string([]byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'})), // 55799({"foo":"bar"}) Want: []byte(`{"foo":"bar"}`), }, { Name: "json object is returned as-is", - FieldsV1: FieldsV1{Raw: []byte(" \t\r\n{\"foo\":\"bar\"}")}, + FieldsV1: *NewFieldsV1(" \t\r\n{\"foo\":\"bar\"}"), Want: []byte(" \t\r\n{\"foo\":\"bar\"}"), }, { Name: "invalid json is returned as-is", - FieldsV1: FieldsV1{Raw: []byte(`{{`)}, + FieldsV1: *NewFieldsV1(`{{`), Want: []byte(`{{`), }, { Name: "invalid cbor fails to transcode to json", - FieldsV1: FieldsV1{Raw: []byte{0xa1}}, + FieldsV1: *NewFieldsV1(string([]byte{0xa1})), Error: "metav1.FieldsV1 cbor invalid: unexpected EOF", }, } { @@ -329,39 +324,34 @@ func TestFieldsV1MarshalCBOR(t *testing.T) { FieldsV1: FieldsV1{}, Want: []byte{0xf6}, // null }, - { - Name: "empty invalid cbor is returned as-is", - FieldsV1: FieldsV1{Raw: []byte{}}, - Want: []byte{}, - }, { Name: "json null is transcoded to cbor null", - FieldsV1: FieldsV1{Raw: []byte(`null`)}, + FieldsV1: *NewFieldsV1(string(`null`)), Want: []byte{0xf6}, // null }, { Name: "valid non-map cbor and valid non-object json is returned as-is", - FieldsV1: FieldsV1{Raw: []byte{0x30}}, + FieldsV1: *NewFieldsV1(string([]byte{0x30})), Want: []byte{0x30}, // Valid CBOR encoding of -17 and JSON encoding of 0! }, { Name: "json object is transcoded to cbor map", - FieldsV1: FieldsV1{Raw: []byte(" \t\r\n{\"foo\":\"bar\"}")}, + FieldsV1: *NewFieldsV1(" \t\r\n{\"foo\":\"bar\"}"), Want: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, }, { Name: "self-described cbor map is returned as-is", - FieldsV1: FieldsV1{Raw: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}}, // 55799({"foo":"bar"}) - Want: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, // 55799({"foo":"bar"}) + FieldsV1: *NewFieldsV1(string([]byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'})), // 55799({"foo":"bar"}) + Want: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, // 55799({"foo":"bar"}) }, { Name: "invalid json fails to transcode to cbor", - FieldsV1: FieldsV1{Raw: []byte(`{{`)}, + FieldsV1: *NewFieldsV1(`{{`), Error: "metav1.FieldsV1 json invalid: invalid character '{' looking for beginning of object key string", }, { Name: "invalid cbor is returned as-is", - FieldsV1: FieldsV1{Raw: []byte{0xa1}}, + FieldsV1: *NewFieldsV1(string([]byte{0xa1})), Want: []byte{0xa1}, }, } { @@ -401,20 +391,20 @@ func TestFieldsV1UnmarshalJSON(t *testing.T) { { Name: "json null does not modify receiver", // conventional for json.Unmarshaler JSON: []byte(`null`), - Into: &FieldsV1{Raw: []byte(`unmodified`)}, - Want: &FieldsV1{Raw: []byte(`unmodified`)}, + Into: NewFieldsV1(`unmodified`), + Want: NewFieldsV1(`unmodified`), }, { Name: "valid input is copied verbatim", JSON: []byte("{\"foo\":\"bar\"} \t\r\n"), Into: &FieldsV1{}, - Want: &FieldsV1{Raw: []byte("{\"foo\":\"bar\"} \t\r\n")}, + Want: NewFieldsV1("{\"foo\":\"bar\"} \t\r\n"), }, { Name: "invalid input is copied verbatim", JSON: []byte("{{"), Into: &FieldsV1{}, - Want: &FieldsV1{Raw: []byte("{{")}, + Want: NewFieldsV1("{{"), }, } { t.Run(tc.Name, func(t *testing.T) { @@ -455,20 +445,20 @@ func TestFieldsV1UnmarshalCBOR(t *testing.T) { { Name: "cbor null does not modify receiver", CBOR: []byte{0xf6}, - Into: &FieldsV1{Raw: []byte(`unmodified`)}, - Want: &FieldsV1{Raw: []byte(`unmodified`)}, + Into: NewFieldsV1(`unmodified`), + Want: NewFieldsV1(`unmodified`), }, { Name: "valid input is copied verbatim", CBOR: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, Into: &FieldsV1{}, - Want: &FieldsV1{Raw: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}}, + Want: NewFieldsV1(string([]byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'})), }, { Name: "invalid input is copied verbatim", CBOR: []byte{0xff}, // UnmarshalCBOR should never be called with malformed input, testing anyway. Into: &FieldsV1{}, - Want: &FieldsV1{Raw: []byte{0xff}}, + Want: NewFieldsV1(string([]byte{0xff})), }, } { t.Run(tc.Name, func(t *testing.T) { diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go index 92d3ed5e01231..6ae1b095fc55a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go @@ -94,6 +94,13 @@ type ListInterface interface { SetRemainingItemCount(c *int64) } +// ShardedListInterface can be implemented by list types to indicate that they +// represent a sharded subset of the full collection rather than the complete list. +type ShardedListInterface interface { + GetShardInfo() *ShardInfo + SetShardInfo(*ShardInfo) +} + // Type exposes the type and APIVersion of versioned or internal API objects. // TODO: move this, and TypeMeta and ListMeta, to a different package type Type interface { @@ -113,6 +120,8 @@ func (meta *ListMeta) GetContinue() string { return meta.Continue func (meta *ListMeta) SetContinue(c string) { meta.Continue = c } func (meta *ListMeta) GetRemainingItemCount() *int64 { return meta.RemainingItemCount } func (meta *ListMeta) SetRemainingItemCount(c *int64) { meta.RemainingItemCount = c } +func (meta *ListMeta) GetShardInfo() *ShardInfo { return meta.ShardInfo } +func (meta *ListMeta) SetShardInfo(s *ShardInfo) { meta.ShardInfo = s } func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go index a5f437b4b38ab..338ea9be72232 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go @@ -1,5 +1,4 @@ //go:build !notest -// +build !notest /* Copyright 2020 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go index 48fb978450e25..14ec5f058681e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go @@ -1,5 +1,4 @@ //go:build !notest -// +build !notest /* Copyright 2020 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go index 9970e877d096d..c8b87f20c8e18 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -92,6 +92,26 @@ type ListMeta struct { // should not rely on the remainingItemCount to be set or to be exact. // +optional RemainingItemCount *int64 `json:"remainingItemCount,omitempty" protobuf:"bytes,4,opt,name=remainingItemCount"` + + // shardInfo is set when the list is a filtered subset of the full collection, + // as selected by a shard selector on the request. It echoes back the selector + // so clients can verify which shard they received and merge sharded responses. + // Clients should not cache sharded list responses as a full representation + // of the collection. + // + // This is an alpha field and requires enabling the ShardedListAndWatch feature gate. + // +featureGate=ShardedListAndWatch + // +optional + ShardInfo *ShardInfo `json:"shardInfo,omitempty" protobuf:"bytes,5,opt,name=shardInfo"` +} + +// ShardInfo describes the shard selector that was applied to produce a list response. +// Its presence on a list response indicates the list is a filtered subset. +type ShardInfo struct { + // selector is the shard selector string from the request, echoed back so clients + // can verify which shard they received and merge responses from multiple shards. + // +required + Selector string `json:"selector" protobuf:"bytes,1,opt,name=selector"` } // Field path constants that are specific to the internal API @@ -430,6 +450,38 @@ type ListOptions struct { // compatibility reasons) and to false otherwise. // +optional SendInitialEvents *bool `json:"sendInitialEvents,omitempty" protobuf:"varint,11,opt,name=sendInitialEvents"` + + // shardSelector restricts the list of returned objects using a CEL-based + // shard selector expression. The format uses the shardRange() function + // combined with || (logical OR) to specify one or more hash ranges: + // + // shardRange(object.metadata.uid, '0x0', '0x8000000000000000') + // shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000') + // + // Field paths use CEL-style object-rooted syntax (e.g. "object.metadata.uid"), + // NOT the fieldSelector format ("metadata.uid"). Currently supported paths: + // - object.metadata.uid + // - object.metadata.namespace + // + // hexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, + // defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a + // hash space. The full range is [0x0, 0x10000000000000000), where the exclusive + // upper bound equals 2^64. + // + // Examples: + // 2-shard split: + // shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000') + // shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000') + // 4-shard split: + // shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000') + // shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000') + // shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000') + // shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000') + // + // This is an alpha field and requires enabling the ShardedListAndWatch feature gate. + // +featureGate=ShardedListAndWatch + // +optional + ShardSelector string `json:"shardSelector,omitempty" protobuf:"bytes,15,opt,name=shardSelector"` } const ( @@ -1384,27 +1436,6 @@ const ( ManagedFieldsOperationUpdate ManagedFieldsOperationType = "Update" ) -// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. -// -// Each key is either a '.' representing the field itself, and will always map to an empty set, -// or a string representing a sub-field or item. The string will follow one of these four formats: -// 'f:', where is the name of a field in a struct, or key in a map -// 'v:', where is the exact json formatted value of a list item -// 'i:', where is position of a item in a list -// 'k:', where is a map of a list item's key fields to their unique values -// If a key maps to an empty Fields value, the field that key represents is part of the set. -// -// The exact format is defined in sigs.k8s.io/structured-merge-diff -// +protobuf.options.(gogoproto.goproto_stringer)=false -type FieldsV1 struct { - // Raw is the underlying serialization of this object. - Raw []byte `json:"-" protobuf:"bytes,1,opt,name=Raw"` -} - -func (f FieldsV1) String() string { - return string(f.Raw) -} - // TODO: Table does not generate to protobuf because of the interface{} - fix protobuf // generation to support a meta type that can accept any valid JSON. This can be introduced // in a v1 because clients a) receive an error if they try to access proto today, and b) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go index 405496d3df569..c45c00f1aac61 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go @@ -147,14 +147,6 @@ func (FieldSelectorRequirement) SwaggerDoc() map[string]string { return map_FieldSelectorRequirement } -var map_FieldsV1 = map[string]string{ - "": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", -} - -func (FieldsV1) SwaggerDoc() map[string]string { - return map_FieldsV1 -} - var map_GetOptions = map[string]string{ "": "GetOptions is the standard query options to the standard REST get call.", "resourceVersion": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", @@ -211,6 +203,7 @@ var map_ListMeta = map[string]string{ "resourceVersion": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", "continue": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", "remainingItemCount": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "shardInfo": "shardInfo is set when the list is a filtered subset of the full collection, as selected by a shard selector on the request. It echoes back the selector so clients can verify which shard they received and merge sharded responses. Clients should not cache sharded list responses as a full representation of the collection.\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", } func (ListMeta) SwaggerDoc() map[string]string { @@ -229,6 +222,7 @@ var map_ListOptions = map[string]string{ "limit": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", "continue": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", "sendInitialEvents": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "shardSelector": "shardSelector restricts the list of returned objects using a CEL-based shard selector expression. The format uses the shardRange() function combined with || (logical OR) to specify one or more hash ranges:\n\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000')\n shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n\nField paths use CEL-style object-rooted syntax (e.g. \"object.metadata.uid\"), NOT the fieldSelector format (\"metadata.uid\"). Currently supported paths:\n - object.metadata.uid\n - object.metadata.namespace\n\nhexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a hash space. The full range is [0x0, 0x10000000000000000), where the exclusive upper bound equals 2^64.\n\nExamples:\n 2-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')\n 4-shard split:\n shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000')\n shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000')\n shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')\n shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000')\n\nThis is an alpha field and requires enabling the ShardedListAndWatch feature gate.", } func (ListOptions) SwaggerDoc() map[string]string { @@ -355,6 +349,15 @@ func (ServerAddressByClientCIDR) SwaggerDoc() map[string]string { return map_ServerAddressByClientCIDR } +var map_ShardInfo = map[string]string{ + "": "ShardInfo describes the shard selector that was applied to produce a list response. Its presence on a list response indicates the list is a filtered subset.", + "selector": "selector is the shard selector string from the request, echoed back so clients can verify which shard they received and merge responses from multiple shards.", +} + +func (ShardInfo) SwaggerDoc() map[string]string { + return map_ShardInfo +} + var map_Status = map[string]string{ "": "Status is a return value for calls that don't return other objects.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go index 82e2722404c57..735cbcb08f15a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go @@ -440,6 +440,13 @@ func autoConvert_url_Values_To_v1_ListOptions(in *url.Values, out *ListOptions, } else { out.SendInitialEvents = nil } + if values, ok := map[string][]string(*in)["shardSelector"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.ShardSelector, s); err != nil { + return err + } + } else { + out.ShardSelector = "" + } return nil } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go index 6b0d0dfee9608..eb7eb80d9ba76 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go @@ -353,27 +353,6 @@ func (in *FieldSelectorRequirement) DeepCopy() *FieldSelectorRequirement { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FieldsV1) DeepCopyInto(out *FieldsV1) { - *out = *in - if in.Raw != nil { - in, out := &in.Raw, &out.Raw - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldsV1. -func (in *FieldsV1) DeepCopy() *FieldsV1 { - if in == nil { - return nil - } - out := new(FieldsV1) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GetOptions) DeepCopyInto(out *GetOptions) { *out = *in @@ -606,6 +585,11 @@ func (in *ListMeta) DeepCopyInto(out *ListMeta) { *out = new(int64) **out = **in } + if in.ShardInfo != nil { + in, out := &in.ShardInfo, &out.ShardInfo + *out = new(ShardInfo) + **out = **in + } return } @@ -663,8 +647,7 @@ func (in *ManagedFieldsEntry) DeepCopyInto(out *ManagedFieldsEntry) { } if in.FieldsV1 != nil { in, out := &in.FieldsV1, &out.FieldsV1 - *out = new(FieldsV1) - (*in).DeepCopyInto(*out) + *out = (*in).DeepCopy() } return } @@ -947,6 +930,22 @@ func (in *ServerAddressByClientCIDR) DeepCopy() *ServerAddressByClientCIDR { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShardInfo) DeepCopyInto(out *ShardInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardInfo. +func (in *ShardInfo) DeepCopy() *ShardInfo { + if in == nil { + return nil + } + out := new(ShardInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Status) DeepCopyInto(out *Status) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go index fd6e876ece286..3df687707c6fc 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go @@ -201,6 +201,11 @@ func (in ServerAddressByClientCIDR) OpenAPIModelName() string { return "io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR" } +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ShardInfo) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" +} + // OpenAPIModelName returns the OpenAPI model name for this type. func (in Status) OpenAPIModelName() string { return "io.k8s.apimachinery.pkg.apis.meta.v1.Status" diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.protomessage.pb.go deleted file mode 100644 index a782b1d8f05c8..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.protomessage.pb.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1beta1 - -func (*PartialObjectMetadataList) ProtoMessage() {} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.protomessage.pb.go deleted file mode 100644 index 2c3fcb5e9555d..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Carp) ProtoMessage() {} - -func (*CarpCondition) ProtoMessage() {} - -func (*CarpInfo) ProtoMessage() {} - -func (*CarpList) ProtoMessage() {} - -func (*CarpSpec) ProtoMessage() {} - -func (*CarpStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go index fcb82900889a7..cceb26f958e81 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go @@ -964,7 +964,6 @@ func TestCustomToUnstructured(t *testing.T) { } for _, tc := range testcases { - tc := tc t.Run(tc.Data, func(t *testing.T) { t.Parallel() result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{ @@ -989,7 +988,6 @@ func TestCustomToUnstructuredTopLevel(t *testing.T) { } expected := map[string]interface{}{"a": int64(1)} for i, obj := range topLevelCases { - obj := obj t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj) diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/runtime/generated.protomessage.pb.go deleted file mode 100644 index 1716853ff17cb..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package runtime - -func (*RawExtension) ProtoMessage() {} - -func (*TypeMeta) ProtoMessage() {} - -func (*Unknown) ProtoMessage() {} diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.protomessage.pb.go deleted file mode 100644 index 047437377fb33..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.protomessage.pb.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package schema diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go index 2c118e285c999..8cdcecbcdb918 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go @@ -333,8 +333,8 @@ func TestDecode(t *testing.T) { typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, into: &structWithRawFields{}, expectedObj: &structWithRawFields{ - FieldsV1: metav1.FieldsV1{Raw: []byte(`{"a":1}`)}, - FieldsV1Pointer: &metav1.FieldsV1{Raw: []byte(`{"z":2}`)}, + FieldsV1: *metav1.NewFieldsV1(`{"a":1}`), + FieldsV1Pointer: metav1.NewFieldsV1(`{"z":2}`), RawExtension: runtime.RawExtension{Raw: []byte(`{"b":3}`)}, RawExtensionPointer: &runtime.RawExtension{Raw: []byte(`{"y":4}`)}, }, @@ -354,8 +354,8 @@ func TestDecode(t *testing.T) { typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, into: &structWithRawFields{}, expectedObj: &structWithRawFields{ - FieldsV1: metav1.FieldsV1{Raw: []byte{0xa1, 0x41, 'a', 0x01}}, - FieldsV1Pointer: &metav1.FieldsV1{Raw: []byte{0xa1, 0x41, 'z', 0x02}}, + FieldsV1: *metav1.NewFieldsV1(string([]byte{0xa1, 0x41, 'a', 0x01})), + FieldsV1Pointer: metav1.NewFieldsV1(string([]byte{0xa1, 0x41, 'z', 0x02})), // RawExtension's UnmarshalCBOR ensures the self-described CBOR tag // is present in the result so that there is never any ambiguity in // distinguishing CBOR from JSON or Protobuf. It is unnecessary for diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go index 09d1340f93248..6d4f283cae659 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go @@ -50,7 +50,7 @@ var rawTypeTranscodeFuncs = map[reflect.Type]func(reflect.Value) error{ return nil } fields := rv.Addr().Interface().(*metav1.FieldsV1) - if fields.Raw == nil { + if fields.GetRawReader().Size() == 0 { // When Raw is nil it encodes to null. Don't change nil Raw values during // transcoding, they would have unmarshalled from JSON as nil too. return nil @@ -59,7 +59,7 @@ var rawTypeTranscodeFuncs = map[reflect.Type]func(reflect.Value) error{ if err != nil { return fmt.Errorf("failed to transcode FieldsV1 to JSON: %w", err) } - fields.Raw = j + fields.SetRawBytes(j) return nil }, } diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go index e9af098bafddf..5f6b140e41b78 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go @@ -114,28 +114,28 @@ func TestTranscodeRawTypes(t *testing.T) { Out: &map[string][]runtime.RawExtension{"hello": {{Raw: []byte(`7`)}}}, }, { - In: &metav1.FieldsV1{Raw: []byte{0xa0}}, - Out: &metav1.FieldsV1{Raw: []byte(`{}`)}, + In: metav1.NewFieldsV1(string([]byte{0xa0})), + Out: metav1.NewFieldsV1(`{}`), }, { In: &metav1.FieldsV1{}, Out: &metav1.FieldsV1{}, }, { - In: metav1.FieldsV1{Raw: []byte{0xa0}}, - Out: metav1.FieldsV1{Raw: []byte{0xa0}}, // not addressable + In: *metav1.NewFieldsV1(string([]byte{0xa0})), + Out: *metav1.NewFieldsV1(string([]byte{0xa0})), // not addressable }, { - In: &[...]metav1.FieldsV1{{Raw: []byte{0xa0}}, {Raw: []byte{0xf6}}}, - Out: &[...]metav1.FieldsV1{{Raw: []byte(`{}`)}, {Raw: []byte(`null`)}}, + In: &[...]metav1.FieldsV1{*metav1.NewFieldsV1(string([]byte{0xa0})), *metav1.NewFieldsV1(string([]byte{0xf6}))}, + Out: &[...]metav1.FieldsV1{*metav1.NewFieldsV1(`{}`), *metav1.NewFieldsV1(`null`)}, }, { In: &[0]metav1.FieldsV1{}, Out: &[0]metav1.FieldsV1{}, }, { - In: &[]metav1.FieldsV1{{Raw: []byte{0xa0}}, {Raw: []byte{0xf6}}}, - Out: &[]metav1.FieldsV1{{Raw: []byte(`{}`)}, {Raw: []byte(`null`)}}, + In: &[]metav1.FieldsV1{*metav1.NewFieldsV1(string([]byte{0xa0})), *metav1.NewFieldsV1(string([]byte{0xf6}))}, + Out: &[]metav1.FieldsV1{*metav1.NewFieldsV1(`{}`), *metav1.NewFieldsV1(`null`)}, }, { In: &[]metav1.FieldsV1{}, @@ -146,34 +146,34 @@ func TestTranscodeRawTypes(t *testing.T) { Out: (*metav1.FieldsV1)(nil), }, { - In: &struct{ I fmt.Stringer }{I: &metav1.FieldsV1{Raw: []byte{0xa0}}}, - Out: &struct{ I fmt.Stringer }{I: &metav1.FieldsV1{Raw: []byte(`{}`)}}, + In: &struct{ I fmt.Stringer }{I: metav1.NewFieldsV1(string([]byte{0xa0}))}, + Out: &struct{ I fmt.Stringer }{I: metav1.NewFieldsV1(`{}`)}, }, { In: &struct { E metav1.FieldsV1 I int64 - }{E: metav1.FieldsV1{Raw: []byte{0xa0}}, I: 7}, + }{E: *metav1.NewFieldsV1(string([]byte{0xa0})), I: 7}, Out: &struct { E metav1.FieldsV1 I int64 - }{E: metav1.FieldsV1{Raw: []byte(`{}`)}, I: 7}, + }{E: *metav1.NewFieldsV1(`{}`), I: 7}, }, { In: &struct { metav1.FieldsV1 - }{FieldsV1: metav1.FieldsV1{Raw: []byte{0xa0}}}, + }{FieldsV1: *metav1.NewFieldsV1(string([]byte{0xa0}))}, Out: &struct { metav1.FieldsV1 - }{FieldsV1: metav1.FieldsV1{Raw: []byte(`{}`)}}, + }{FieldsV1: *metav1.NewFieldsV1(`{}`)}, }, { - In: &map[string]metav1.FieldsV1{"hello": {Raw: []byte{0xa0}}}, - Out: &map[string]metav1.FieldsV1{"hello": {Raw: []byte{0xa0}}}, // not addressable + In: &map[string]metav1.FieldsV1{"hello": *metav1.NewFieldsV1(string([]byte{0xa0}))}, + Out: &map[string]metav1.FieldsV1{"hello": *metav1.NewFieldsV1(string([]byte{0xa0}))}, // not addressable }, { - In: &map[string][]metav1.FieldsV1{"hello": {{Raw: []byte{0xa0}}}}, - Out: &map[string][]metav1.FieldsV1{"hello": {{Raw: []byte(`{}`)}}}, + In: &map[string][]metav1.FieldsV1{"hello": {*metav1.NewFieldsV1(string([]byte{0xa0}))}}, + Out: &map[string][]metav1.FieldsV1{"hello": {*metav1.NewFieldsV1(`{}`)}}, }, } { t.Run(fmt.Sprintf("%#v", tc.In), func(t *testing.T) { diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go index 8375d6ecfa1f1..f320ce229eb07 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go @@ -37,16 +37,16 @@ type VersionValidationRunner func(t *testing.T, gv string, versionValidationErro // errs = append(errs, versionValidationErrors...) // generated declarative validation // // Validate that the errors are what was expected for this test case. // }) -func RunValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, subresources ...string) { - runValidation(t, scheme, options, unversioned, fn, subresources...) +func RunValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + runValidation(t, scheme, options, unversioned, fn, ignoreConversionErrors, subresources...) } // RunUpdateValidationForEachVersion is like RunValidationForEachVersion but for update validation. -func RunUpdateValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned, unversionedOld runtime.Object, fn VersionValidationRunner, subresources ...string) { - runUpdateValidation(t, scheme, options, unversioned, unversionedOld, fn, subresources...) +func RunUpdateValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned, unversionedOld runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + runUpdateValidation(t, scheme, options, unversioned, unversionedOld, fn, ignoreConversionErrors, subresources...) } -func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, subresources ...string) { +func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { unversionedGVKs, _, err := scheme.ObjectKinds(unversioned) if err != nil { t.Fatal(err) @@ -66,7 +66,9 @@ func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unver t.Fatal(err) } err = scheme.Convert(unversioned, versioned, nil) - if err != nil { + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { t.Fatal(err) } fn(t, gv.String(), scheme.Validate(context.Background(), options, versioned, subresources...)) @@ -76,7 +78,7 @@ func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unver } } -func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversionedNew, unversionedOld runtime.Object, fn VersionValidationRunner, subresources ...string) { +func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversionedNew, unversionedOld runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { unversionedGVKs, _, err := scheme.ObjectKinds(unversionedNew) if err != nil { t.Fatal(err) @@ -96,10 +98,11 @@ func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, t.Fatal(err) } err = scheme.Convert(unversionedNew, versionedNew, nil) - if err != nil { + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { t.Fatal(err) } - var versionedOld runtime.Object if unversionedOld != nil { versionedOld, err = scheme.New(gvk) @@ -108,7 +111,9 @@ func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, } err = scheme.Convert(unversionedOld, versionedOld, nil) - if err != nil { + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { t.Fatal(err) } } diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/accessor.go b/staging/src/k8s.io/apimachinery/pkg/sharding/accessor.go new file mode 100644 index 0000000000000..fab98ffca4381 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/accessor.go @@ -0,0 +1,50 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" +) + +// ResolveFieldValue extracts a metadata field value from a runtime.Object +// based on the given field path. +// +// Field paths use CEL-style object-rooted syntax ("object.metadata."), +// which differs from the fieldSelector format ("metadata."). The +// "object." prefix anchors the path to the resource being filtered. +// +// Supported field paths: +// - "object.metadata.uid" +// - "object.metadata.namespace" +func ResolveFieldValue(obj runtime.Object, fieldPath string) (string, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return "", fmt.Errorf("failed to access object metadata: %w", err) + } + + switch fieldPath { + case "object.metadata.uid": + return string(accessor.GetUID()), nil + case "object.metadata.namespace": + return accessor.GetNamespace(), nil + default: + return "", fmt.Errorf("unsupported field path: %q", fieldPath) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/accessor_test.go b/staging/src/k8s.io/apimachinery/pkg/sharding/accessor_test.go new file mode 100644 index 0000000000000..bee6d3902599b --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/accessor_test.go @@ -0,0 +1,64 @@ +/* +Copyright 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 sharding + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestResolveFieldValue(t *testing.T) { + obj := &testObject{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("test-uid-123"), + Name: "test-name", + Namespace: "test-namespace", + }, + } + + tests := []struct { + fieldPath string + want string + wantErr bool + }{ + {"object.metadata.uid", "test-uid-123", false}, + {"object.metadata.namespace", "test-namespace", false}, + {"object.metadata.name", "", true}, + {"object.metadata.labels", "", true}, + {"invalid.path", "", true}, + } + + for _, tt := range tests { + t.Run(tt.fieldPath, func(t *testing.T) { + got, err := ResolveFieldValue(obj, tt.fieldPath) + if tt.wantErr { + if err == nil { + t.Errorf("expected error for fieldPath %q", tt.fieldPath) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("ResolveFieldValue(%q) = %q, want %q", tt.fieldPath, got, tt.want) + } + }) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/hash.go b/staging/src/k8s.io/apimachinery/pkg/sharding/hash.go new file mode 100644 index 0000000000000..ae4dcfe2f6a39 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/hash.go @@ -0,0 +1,30 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + "hash/fnv" +) + +// HashField computes a hash of value and returns it +// as a 16-character lowercase hex string (no "0x" prefix). +func HashField(value string) string { + h := fnv.New64a() + h.Write([]byte(value)) + return fmt.Sprintf("%016x", h.Sum64()) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/hash_test.go b/staging/src/k8s.io/apimachinery/pkg/sharding/hash_test.go new file mode 100644 index 0000000000000..750352eef1fef --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/hash_test.go @@ -0,0 +1,58 @@ +/* +Copyright 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 sharding + +import ( + "testing" +) + +func TestHashField(t *testing.T) { + tests := []struct { + input string + }{ + {""}, + {"abc"}, + {"test-uid-12345"}, + {"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}, + } + + for _, tt := range tests { + result := HashField(tt.input) + if len(result) != 16 { + t.Errorf("HashField(%q) returned %q (len %d), expected 16 hex chars", tt.input, result, len(result)) + } + // Verify all chars are hex + for _, c := range result { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') { + t.Errorf("HashField(%q) returned %q which contains non-hex char %q", tt.input, result, string(c)) + } + } + } + + // Determinism + h1 := HashField("test") + h2 := HashField("test") + if h1 != h2 { + t.Errorf("HashField is not deterministic: %q != %q", h1, h2) + } + + // Different inputs produce different outputs + h3 := HashField("different") + if h1 == h3 { + t.Errorf("HashField produced same hash for different inputs: %q", h1) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/selector.go b/staging/src/k8s.io/apimachinery/pkg/sharding/selector.go new file mode 100644 index 0000000000000..841388a0d7197 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/selector.go @@ -0,0 +1,137 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime" +) + +// Selector represents a shard selector that can match objects based on +// hash ranges of their metadata fields. It follows the labels.Selector +// pattern from the Kubernetes API. +type Selector interface { + // Matches returns true if the given object matches the shard selector. + Matches(obj runtime.Object) (bool, error) + + // Empty returns true if the selector matches everything (no filtering). + Empty() bool + + // String returns the wire-format string representation that can be + // round-tripped through Parse. + String() string + + // Requirements returns the list of shard range requirements. + Requirements() []ShardRangeRequirement + + // DeepCopySelector returns a deep copy of the selector. + DeepCopySelector() Selector +} + +// Everything returns a selector that matches all objects. +func Everything() Selector { + return &everythingSelector{} +} + +type everythingSelector struct{} + +func (s *everythingSelector) Matches(_ runtime.Object) (bool, error) { return true, nil } +func (s *everythingSelector) Empty() bool { return true } +func (s *everythingSelector) String() string { return "" } +func (s *everythingSelector) Requirements() []ShardRangeRequirement { return nil } +func (s *everythingSelector) DeepCopySelector() Selector { return &everythingSelector{} } + +// shardSelector implements Selector with one or more shard range requirements. +type shardSelector struct { + requirements []ShardRangeRequirement +} + +func (s *shardSelector) Matches(obj runtime.Object) (bool, error) { + if len(s.requirements) == 0 { + return true, nil + } + // All requirements must share the same key so we resolve the field value + // and compute the hash once. The parser enforces this, but we verify here + // to guard against selectors constructed through other means. + key := s.requirements[0].Key + for _, req := range s.requirements[1:] { + if req.Key != key { + return false, fmt.Errorf("inconsistent shard keys: %q vs %q", key, req.Key) + } + } + + value, err := ResolveFieldValue(obj, key) + if err != nil { + return false, err + } + hash := "0x" + HashField(value) + + for _, req := range s.requirements { + if !HexLess(hash, req.Start) && HexLess(hash, req.End) { + return true, nil + } + } + return false, nil +} + +// HexLess compares two 0x-prefixed lowercase hex strings numerically. +// Both values must be normalized to 16 hex digits (e.g. "0x0000000000000000"), +// except for the special upper bound "0x10000000000000000" (2^64) which has 17. +func HexLess(a, b string) bool { + if len(a) != len(b) { + return len(a) < len(b) + } + return a < b +} + +func (s *shardSelector) Empty() bool { + return len(s.requirements) == 0 +} + +func (s *shardSelector) String() string { + parts := make([]string, 0, len(s.requirements)) + for _, req := range s.requirements { + parts = append(parts, fmt.Sprintf("shardRange(%s, '%s', '%s')", req.Key, req.Start, req.End)) + } + return strings.Join(parts, " || ") +} + +func (s *shardSelector) Requirements() []ShardRangeRequirement { + result := make([]ShardRangeRequirement, len(s.requirements)) + copy(result, s.requirements) + return result +} + +func (s *shardSelector) DeepCopySelector() Selector { + reqs := make([]ShardRangeRequirement, len(s.requirements)) + copy(reqs, s.requirements) + return &shardSelector{requirements: reqs} +} + +// NewSelector creates a Selector from the given requirements. +// All requirements must use the same Key; this is validated at match time. +// If no requirements are provided, returns Everything(). +func NewSelector(reqs ...ShardRangeRequirement) Selector { + if len(reqs) == 0 { + return Everything() + } + return &shardSelector{ + requirements: reqs, + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/selector_test.go b/staging/src/k8s.io/apimachinery/pkg/sharding/selector_test.go new file mode 100644 index 0000000000000..afaaf4125c030 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/selector_test.go @@ -0,0 +1,141 @@ +/* +Copyright 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 sharding + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// testObject is a minimal runtime.Object for testing. +type testObject struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` +} + +func (t *testObject) DeepCopyObject() runtime.Object { + return &testObject{ + TypeMeta: t.TypeMeta, + ObjectMeta: *t.ObjectMeta.DeepCopy(), + } +} + +func TestSelectorMatches(t *testing.T) { + obj := &testObject{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("test-uid-123"), + Name: "test-name", + Namespace: "test-namespace", + }, + } + + hash := "0x" + HashField("test-uid-123") + + tests := []struct { + name string + selector Selector + wantMatch bool + }{ + { + name: "everything matches", + selector: Everything(), + wantMatch: true, + }, + { + name: "empty selector matches", + selector: NewSelector(), + wantMatch: true, + }, + { + name: "full range matches", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x0000000000000000", + End: "0x10000000000000000", + }), + wantMatch: true, + }, + { + name: "hash in specific range", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: hash, + End: hash + "f", // hash + "f" is always > hash + }), + wantMatch: true, + }, + { + name: "hash below start", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0xffffffffffffffff", + End: "0x10000000000000000", + }), + wantMatch: hash >= "0xffffffffffffffff", + }, + { + name: "hash at or above end", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x0000000000000000", + End: "0x0000000000000001", + }), + wantMatch: hash < "0x0000000000000001", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matched, err := tt.selector.Matches(obj) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if matched != tt.wantMatch { + t.Errorf("Matches() = %v, want %v (hash=%s)", matched, tt.wantMatch, hash) + } + }) + } +} + +func TestSelectorEmpty(t *testing.T) { + if !Everything().Empty() { + t.Error("Everything() should be empty") + } + if !NewSelector().Empty() { + t.Error("NewSelector() with no args should be empty") + } + + sel := NewSelector(ShardRangeRequirement{Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x8000000000000000"}) + if sel.Empty() { + t.Error("selector with requirement should not be empty") + } +} + +func TestSelectorString(t *testing.T) { + sel := NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x00", + End: "0x80", + }) + expected := "shardRange(object.metadata.uid, '0x00', '0x80')" + if sel.String() != expected { + t.Errorf("String() = %q, want %q", sel.String(), expected) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/sharding/types.go b/staging/src/k8s.io/apimachinery/pkg/sharding/types.go new file mode 100644 index 0000000000000..4684fc497fb24 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/sharding/types.go @@ -0,0 +1,32 @@ +/* +Copyright 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 sharding + +// ShardRangeRequirement represents a single shard range requirement. +// It specifies a field path to hash and a hex range [Start, End) for filtering. +// The hash space is FNV-1a 64-bit: [0x0000000000000000, 0x10000000000000000). +// Both Start and End must be specified (no empty/unbounded values). +type ShardRangeRequirement struct { + // Key is the field path, e.g. "object.metadata.uid" + Key string + // Start is the inclusive lower bound as a 0x-prefixed lowercase hex string. + // Minimum value is "0x0000000000000000". + Start string + // End is the exclusive upper bound as a 0x-prefixed lowercase hex string. + // Maximum value is "0x10000000000000000" (2^64). + End string +} diff --git a/staging/src/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go b/staging/src/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go index b1a50a41a25ae..6b487d3e40d2f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go @@ -370,7 +370,6 @@ func TestOwnerReferences(t *testing.T) { }, } for i, ref := range refs { - ref := ref t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() u1 := unstructured.Unstructured{ diff --git a/staging/src/k8s.io/apimachinery/pkg/util/diff/cmp.go b/staging/src/k8s.io/apimachinery/pkg/util/diff/cmp.go deleted file mode 100644 index 0a8100d06392f..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/util/diff/cmp.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build usegocmp -// +build usegocmp - -/* -Copyright 2025 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 diff - -import ( - "github.com/google/go-cmp/cmp" //nolint:depguard -) - -// Diff returns a string representation of the difference between two objects. -// When built with the usegocmp tag, it uses go-cmp/cmp to generate a diff -// between the objects. -func Diff(a, b any) string { - return cmp.Diff(a, b) -} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/diff/complex_test.go b/staging/src/k8s.io/apimachinery/pkg/util/diff/complex_test.go index fb31513f09010..18ac9336e2cc4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/diff/complex_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/diff/complex_test.go @@ -1,6 +1,3 @@ -//go:build !usegocmp -// +build !usegocmp - /* Copyright 2025 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/diff/diff.go b/staging/src/k8s.io/apimachinery/pkg/util/diff/diff.go index aed04524b659d..7bfa6088bc384 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/diff/diff.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/diff/diff.go @@ -1,6 +1,3 @@ -//go:build !usegocmp -// +build !usegocmp - /* Copyright 2025 The Kubernetes Authors. @@ -25,7 +22,7 @@ import ( "github.com/pmezard/go-difflib/difflib" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) // Diff returns a string representation of the difference between two objects. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go b/staging/src/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go index 4d32d36adb31f..9f0dc2f6ad8b4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go @@ -22,7 +22,7 @@ import ( "strings" "text/tabwriter" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) // ObjectGoPrintSideBySide prints a and b as textual dumps side by side, diff --git a/staging/src/k8s.io/apimachinery/pkg/util/dump/dump.go b/staging/src/k8s.io/apimachinery/pkg/util/dump/dump.go index cf61ef76aed32..5cf55473c3f64 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/dump/dump.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/dump/dump.go @@ -17,38 +17,26 @@ limitations under the License. package dump import ( - "github.com/davecgh/go-spew/spew" + "k8s.io/utils/dump" ) -var prettyPrintConfig = &spew.ConfigState{ - Indent: " ", - DisableMethods: true, - DisablePointerAddresses: true, - DisableCapacities: true, -} - -// The config MUST NOT be changed because that could change the result of a hash operation -var prettyPrintConfigForHash = &spew.ConfigState{ - Indent: " ", - SortKeys: true, - DisableMethods: true, - SpewKeys: true, - DisablePointerAddresses: true, - DisableCapacities: true, -} - -// Pretty wrap the spew.Sdump with Indent, and disabled methods like error() and String() -// The output may change over time, so for guaranteed output please take more direct control +// Deprecated: Use k8s.io/utils/dump.Pretty instead. +// +//go:fix inline func Pretty(a interface{}) string { - return prettyPrintConfig.Sdump(a) + return dump.Pretty(a) } -// ForHash keeps the original Spew.Sprintf format to ensure the same checksum +// Deprecated: Use k8s.io/utils/dump.ForHash instead. +// +//go:fix inline func ForHash(a interface{}) string { - return prettyPrintConfigForHash.Sprintf("%#v", a) + return dump.ForHash(a) } -// OneLine outputs the object in one line +// Deprecated: Use k8s.io/utils/dump.OneLine instead. +// +//go:fix inline func OneLine(a interface{}) string { - return prettyPrintConfig.Sprintf("%#v", a) + return dump.OneLine(a) } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/dump/dump_test.go b/staging/src/k8s.io/apimachinery/pkg/util/dump/dump_test.go deleted file mode 100644 index 6c641a3162982..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/util/dump/dump_test.go +++ /dev/null @@ -1,304 +0,0 @@ -/* -Copyright 2021 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 dump - -import ( - "fmt" - "testing" - - "k8s.io/utils/ptr" -) - -// custom type to test Stringer interface on non-pointer receiver. -type customString string - -// String implements the Stringer interface for testing invocation -func (s customString) String() string { - return "custom string " + string(s) -} - -// custom type to test error interface on non-pointer receiver. -type customError int - -// Error implements the error interface for testing invocation -func (e customError) Error() string { - return fmt.Sprintf("custom error: %d", int(e)) -} - -// custom type to test Stringer interface on pointer receiver. -type pCustomString string - -// String implements the Stringer interface for testing invocation -func (s *pCustomString) String() string { - return "custom string " + string(*s) -} - -// custom type to test error interface on pointer receiver. -type pCustomError int - -// Error implements the error interface for testing invocation -func (e *pCustomError) Error() string { - return fmt.Sprintf("custom error: %d", int(*e)) -} - -// embed is used to test embedded structures. -type embed struct { - s string -} - -// embedwrap is used to test embedded structures. -type embedwrap struct { - embed - e *embed -} - -func TestPretty(t *testing.T) { - tcs := customString("test") - tpcs := pCustomString("&test") - - tce := customError(0) - tpce := pCustomError(0) - - teb := embed{"test"} - tebw := embedwrap{teb, &teb} - - testCases := []struct { - a interface{} - want string - }{ - {int8(93), "(int8) 93\n"}, - {int16(93), "(int16) 93\n"}, - {int32(93), "(int32) 93\n"}, - {int64(93), "(int64) 93\n"}, - {int(-93), "(int) -93\n"}, - {int8(-93), "(int8) -93\n"}, - {int16(-93), "(int16) -93\n"}, - {int32(-93), "(int32) -93\n"}, - {int64(-93), "(int64) -93\n"}, - {uint(93), "(uint) 93\n"}, - {uint8(93), "(uint8) 93\n"}, - {uint16(93), "(uint16) 93\n"}, - {uint32(93), "(uint32) 93\n"}, - {uint64(93), "(uint64) 93\n"}, - {uintptr(93), "(uintptr) 0x5d\n"}, - {ptr.To(93), "(*int)(93)\n"}, - {float32(93.76), "(float32) 93.76\n"}, - {float64(93.76), "(float64) 93.76\n"}, - {complex64(93i), "(complex64) (0+93i)\n"}, - {complex128(93i), "(complex128) (0+93i)\n"}, - {bool(true), "(bool) true\n"}, - {bool(false), "(bool) false\n"}, - {string("test"), "(string) (len=4) \"test\"\n"}, - {ptr.To("test"), "(*string)((len=4) \"test\")\n"}, - {[1]string{"arr"}, "([1]string) (len=1) {\n (string) (len=3) \"arr\"\n}\n"}, - {[]string{"slice"}, "([]string) (len=1) {\n (string) (len=5) \"slice\"\n}\n"}, - {tcs, "(dump.customString) (len=4) \"test\"\n"}, - {&tcs, "(*dump.customString)((len=4) \"test\")\n"}, - {tpcs, "(dump.pCustomString) (len=5) \"&test\"\n"}, - {&tpcs, "(*dump.pCustomString)((len=5) \"&test\")\n"}, - {tce, "(dump.customError) 0\n"}, - {&tce, "(*dump.customError)(0)\n"}, - {tpce, "(dump.pCustomError) 0\n"}, - {&tpce, "(*dump.pCustomError)(0)\n"}, - { - struct { - arr [1]string - slice []string - m map[string]int - }{ - [1]string{"arr"}, - []string{"slice"}, - map[string]int{"one": 1}, - }, - "(struct { arr [1]string; slice []string; m map[string]int }) {\n arr: ([1]string) (len=1) {\n (string) (len=3) \"arr\"\n },\n slice: ([]string) (len=1) {\n (string) (len=5) \"slice\"\n },\n m: (map[string]int) (len=1) {\n (string) (len=3) \"one\": (int) 1\n }\n}\n", - }, - {teb, "(dump.embed) {\n s: (string) (len=4) \"test\"\n}\n"}, - {tebw, "(dump.embedwrap) {\n embed: (dump.embed) {\n s: (string) (len=4) \"test\"\n },\n e: (*dump.embed)({\n s: (string) (len=4) \"test\"\n })\n}\n"}, - {map[string]int{}, "(map[string]int) {\n}\n"}, - {map[string]int{"one": 1}, "(map[string]int) (len=1) {\n (string) (len=3) \"one\": (int) 1\n}\n"}, - {map[string]interface{}{"one": 1}, "(map[string]interface {}) (len=1) {\n (string) (len=3) \"one\": (int) 1\n}\n"}, - {map[string]customString{"key": tcs}, "(map[string]dump.customString) (len=1) {\n (string) (len=3) \"key\": (dump.customString) (len=4) \"test\"\n}\n"}, - {map[string]customError{"key": tce}, "(map[string]dump.customError) (len=1) {\n (string) (len=3) \"key\": (dump.customError) 0\n}\n"}, - {map[string]embed{"key": teb}, "(map[string]dump.embed) (len=1) {\n (string) (len=3) \"key\": (dump.embed) {\n s: (string) (len=4) \"test\"\n }\n}\n"}, - {map[string]embedwrap{"key": tebw}, "(map[string]dump.embedwrap) (len=1) {\n (string) (len=3) \"key\": (dump.embedwrap) {\n embed: (dump.embed) {\n s: (string) (len=4) \"test\"\n },\n e: (*dump.embed)({\n s: (string) (len=4) \"test\"\n })\n }\n}\n"}, - } - - for i, tc := range testCases { - s := Pretty(tc.a) - if tc.want != s { - t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, tc.want, s) - } - } -} - -func TestForHash(t *testing.T) { - tcs := customString("test") - tpcs := pCustomString("&test") - - tce := customError(0) - tpce := pCustomError(0) - - teb := embed{"test"} - tebw := embedwrap{teb, &teb} - - testCases := []struct { - a interface{} - want string - }{ - {int8(93), "(int8)93"}, - {int16(93), "(int16)93"}, - {int32(93), "(int32)93"}, - {int64(93), "(int64)93"}, - {int(-93), "(int)-93"}, - {int8(-93), "(int8)-93"}, - {int16(-93), "(int16)-93"}, - {int32(-93), "(int32)-93"}, - {int64(-93), "(int64)-93"}, - {uint(93), "(uint)93"}, - {uint8(93), "(uint8)93"}, - {uint16(93), "(uint16)93"}, - {uint32(93), "(uint32)93"}, - {uint64(93), "(uint64)93"}, - {uintptr(93), "(uintptr)0x5d"}, - {ptr.To(93), "(*int)93"}, - {float32(93.76), "(float32)93.76"}, - {float64(93.76), "(float64)93.76"}, - {complex64(93i), "(complex64)(0+93i)"}, - {complex128(93i), "(complex128)(0+93i)"}, - {bool(true), "(bool)true"}, - {bool(false), "(bool)false"}, - {string("test"), "(string)test"}, - {ptr.To("test"), "(*string)test"}, - {[1]string{"arr"}, "([1]string)[arr]"}, - {[]string{"slice"}, "([]string)[slice]"}, - {tcs, "(dump.customString)test"}, - {&tcs, "(*dump.customString)test"}, - {tpcs, "(dump.pCustomString)&test"}, - {&tpcs, "(*dump.pCustomString)&test"}, - {tce, "(dump.customError)0"}, - {&tce, "(*dump.customError)0"}, - {tpce, "(dump.pCustomError)0"}, - {&tpce, "(*dump.pCustomError)0"}, - { - struct { - arr [1]string - slice []string - m map[string]int - }{ - [1]string{"arr"}, - []string{"slice"}, - map[string]int{"one": 1}, - }, - "(struct { arr [1]string; slice []string; m map[string]int }){arr:([1]string)[arr] slice:([]string)[slice] m:(map[string]int)map[one:1]}", - }, - {teb, "(dump.embed){s:(string)test}"}, - {tebw, "(dump.embedwrap){embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}"}, - {map[string]int{}, "(map[string]int)map[]"}, - {map[string]int{"one": 1, "two": 2}, "(map[string]int)map[one:1 two:2]"}, - {map[string]interface{}{"one": 1}, "(map[string]interface {})map[one:(int)1]"}, - {map[string]customString{"key": tcs}, "(map[string]dump.customString)map[key:test]"}, - {map[string]customError{"key": tce}, "(map[string]dump.customError)map[key:0]"}, - {map[string]embed{"key": teb}, "(map[string]dump.embed)map[key:{s:(string)test}]"}, - {map[string]embedwrap{"key": tebw}, "(map[string]dump.embedwrap)map[key:{embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}]"}, - } - - for i, tc := range testCases { - s := ForHash(tc.a) - if tc.want != s { - t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, tc.want, s) - } - } -} - -func TestOneLine(t *testing.T) { - tcs := customString("test") - tpcs := pCustomString("&test") - - tce := customError(0) - tpce := pCustomError(0) - - teb := embed{"test"} - tebw := embedwrap{teb, &teb} - - testCases := []struct { - a interface{} - want string - }{ - {int8(93), "(int8)93"}, - {int16(93), "(int16)93"}, - {int32(93), "(int32)93"}, - {int64(93), "(int64)93"}, - {int(-93), "(int)-93"}, - {int8(-93), "(int8)-93"}, - {int16(-93), "(int16)-93"}, - {int32(-93), "(int32)-93"}, - {int64(-93), "(int64)-93"}, - {uint(93), "(uint)93"}, - {uint8(93), "(uint8)93"}, - {uint16(93), "(uint16)93"}, - {uint32(93), "(uint32)93"}, - {uint64(93), "(uint64)93"}, - {uintptr(93), "(uintptr)0x5d"}, - {ptr.To(93), "(*int)93"}, - {float32(93.76), "(float32)93.76"}, - {float64(93.76), "(float64)93.76"}, - {complex64(93i), "(complex64)(0+93i)"}, - {complex128(93i), "(complex128)(0+93i)"}, - {bool(true), "(bool)true"}, - {bool(false), "(bool)false"}, - {string("test"), "(string)test"}, - {ptr.To("test"), "(*string)test"}, - {[1]string{"arr"}, "([1]string)[arr]"}, - {[]string{"slice"}, "([]string)[slice]"}, - {tcs, "(dump.customString)test"}, - {&tcs, "(*dump.customString)test"}, - {tpcs, "(dump.pCustomString)&test"}, - {&tpcs, "(*dump.pCustomString)&test"}, - {tce, "(dump.customError)0"}, - {&tce, "(*dump.customError)0"}, - {tpce, "(dump.pCustomError)0"}, - {&tpce, "(*dump.pCustomError)0"}, - { - struct { - arr [1]string - slice []string - m map[string]int - }{ - [1]string{"arr"}, - []string{"slice"}, - map[string]int{"one": 1}, - }, - "(struct { arr [1]string; slice []string; m map[string]int }){arr:([1]string)[arr] slice:([]string)[slice] m:(map[string]int)map[one:1]}", - }, - {teb, "(dump.embed){s:(string)test}"}, - {tebw, "(dump.embedwrap){embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}"}, - {map[string]int{}, "(map[string]int)map[]"}, - {map[string]int{"one": 1}, "(map[string]int)map[one:1]"}, - {map[string]interface{}{"one": 1}, "(map[string]interface {})map[one:(int)1]"}, - {map[string]customString{"key": tcs}, "(map[string]dump.customString)map[key:test]"}, - {map[string]customError{"key": tce}, "(map[string]dump.customError)map[key:0]"}, - {map[string]embed{"key": teb}, "(map[string]dump.embed)map[key:{s:(string)test}]"}, - {map[string]embedwrap{"key": tebw}, "(map[string]dump.embedwrap)map[key:{embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}]"}, - } - - for i, tc := range testCases { - s := OneLine(tc.a) - if tc.want != s { - t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, tc.want, s) - } - } -} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go index 1da83f14b18c2..5fdc7955fa510 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package httpstream adds multiplexed streaming support to HTTP requests and -// responses via connection upgrades. +// Package httpstream contains compatibility wrappers for streaming transport APIs. +// +// Deprecated: use k8s.io/streaming/pkg/httpstream directly. package httpstream diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go new file mode 100644 index 0000000000000..d03acb0eed18a --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2015 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 spdy contains compatibility wrappers for the SPDY transport stack. +// +// Deprecated: use k8s.io/streaming/pkg/httpstream/spdy directly. +package spdy diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go deleted file mode 100644 index ed131d112f17e..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go +++ /dev/null @@ -1,399 +0,0 @@ -/* -Copyright 2015 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 spdy - -import ( - "bufio" - "context" - "crypto/tls" - "encoding/base64" - "errors" - "fmt" - "io" - "net" - "net/http" - "net/http/httputil" - "net/url" - "strings" - "time" - - "golang.org/x/net/proxy" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/util/httpstream" - utilnet "k8s.io/apimachinery/pkg/util/net" - apiproxy "k8s.io/apimachinery/pkg/util/proxy" - "k8s.io/apimachinery/third_party/forked/golang/netutil" -) - -// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports -// multiplexed streams. After RoundTrip() is invoked, Conn will be set -// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface. -type SpdyRoundTripper struct { - //tlsConfig holds the TLS configuration settings to use when connecting - //to the remote server. - tlsConfig *tls.Config - - /* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper - must be safe for use by multiple concurrent goroutines. If this is absolutely - necessary, we could keep a map from http.Request to net.Conn. In practice, - a client will create an http.Client, set the transport to a new insteace of - SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue. - */ - // conn is the underlying network connection to the remote server. - conn net.Conn - - // Dialer is the dialer used to connect. Used if non-nil. - Dialer *net.Dialer - - // proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment - // Used primarily for mocking the proxy discovery in tests. - proxier func(req *http.Request) (*url.URL, error) - - // pingPeriod is a period for sending Ping frames over established - // connections. - pingPeriod time.Duration - - // upgradeTransport is an optional substitute for dialing if present. This field is - // mutually exclusive with the "tlsConfig", "Dialer", and "proxier". - upgradeTransport http.RoundTripper -} - -var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{} -var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{} -var _ utilnet.Dialer = &SpdyRoundTripper{} - -// NewRoundTripper creates a new SpdyRoundTripper that will use the specified -// tlsConfig. -func NewRoundTripper(tlsConfig *tls.Config) (*SpdyRoundTripper, error) { - return NewRoundTripperWithConfig(RoundTripperConfig{ - TLS: tlsConfig, - UpgradeTransport: nil, - }) -} - -// NewRoundTripperWithProxy creates a new SpdyRoundTripper that will use the -// specified tlsConfig and proxy func. -func NewRoundTripperWithProxy(tlsConfig *tls.Config, proxier func(*http.Request) (*url.URL, error)) (*SpdyRoundTripper, error) { - return NewRoundTripperWithConfig(RoundTripperConfig{ - TLS: tlsConfig, - Proxier: proxier, - UpgradeTransport: nil, - }) -} - -// NewRoundTripperWithConfig creates a new SpdyRoundTripper with the specified -// configuration. Returns an error if the SpdyRoundTripper is misconfigured. -func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error) { - // Process UpgradeTransport, which is mutually exclusive to TLSConfig and Proxier. - if cfg.UpgradeTransport != nil { - if cfg.TLS != nil || cfg.Proxier != nil { - return nil, fmt.Errorf("SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier") - } - tlsConfig, err := utilnet.TLSClientConfig(cfg.UpgradeTransport) - if err != nil { - return nil, fmt.Errorf("SpdyRoundTripper: Unable to retrieve TLSConfig from UpgradeTransport: %v", err) - } - cfg.TLS = tlsConfig - } - if cfg.Proxier == nil { - cfg.Proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) - } - return &SpdyRoundTripper{ - tlsConfig: cfg.TLS, - proxier: cfg.Proxier, - pingPeriod: cfg.PingPeriod, - upgradeTransport: cfg.UpgradeTransport, - }, nil -} - -// RoundTripperConfig is a set of options for an SpdyRoundTripper. -type RoundTripperConfig struct { - // TLS configuration used by the round tripper if UpgradeTransport not present. - TLS *tls.Config - // Proxier is a proxy function invoked on each request. Optional. - Proxier func(*http.Request) (*url.URL, error) - // PingPeriod is a period for sending SPDY Pings on the connection. - // Optional. - PingPeriod time.Duration - // UpgradeTransport is a subtitute transport used for dialing. If set, - // this field will be used instead of "TLS" and "Proxier" for connection creation. - // Optional. - UpgradeTransport http.RoundTripper -} - -// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during -// proxying with a spdy roundtripper. -func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config { - return s.tlsConfig -} - -// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer. -func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { - var conn net.Conn - var err error - if s.upgradeTransport != nil { - conn, err = apiproxy.DialURL(req.Context(), req.URL, s.upgradeTransport) - } else { - conn, err = s.dial(req) - } - if err != nil { - return nil, err - } - - if err := req.Write(conn); err != nil { - conn.Close() - return nil, err - } - - return conn, nil -} - -// dial dials the host specified by req, using TLS if appropriate, optionally -// using a proxy server if one is configured via environment variables. -func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { - proxyURL, err := s.proxier(req) - if err != nil { - return nil, err - } - - if proxyURL == nil { - return s.dialWithoutProxy(req.Context(), req.URL) - } - - switch proxyURL.Scheme { - case "socks5": - return s.dialWithSocks5Proxy(req, proxyURL) - case "https", "http", "": - return s.dialWithHttpProxy(req, proxyURL) - } - - return nil, fmt.Errorf("proxy URL scheme not supported: %s", proxyURL.Scheme) -} - -// dialWithHttpProxy dials the host specified by url through an http or an https proxy. -func (s *SpdyRoundTripper) dialWithHttpProxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) { - // ensure we use a canonical host with proxyReq - targetHost := netutil.CanonicalAddr(req.URL) - - // proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support - proxyReq := http.Request{ - Method: http.MethodConnect, - URL: &url.URL{}, - Host: targetHost, - } - - proxyReq = *proxyReq.WithContext(req.Context()) - - if pa := s.proxyAuth(proxyURL); pa != "" { - proxyReq.Header = http.Header{} - proxyReq.Header.Set("Proxy-Authorization", pa) - } - - proxyDialConn, err := s.dialWithoutProxy(proxyReq.Context(), proxyURL) - if err != nil { - return nil, err - } - - //nolint:staticcheck // SA1019 ignore deprecated httputil.NewProxyClientConn - proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil) - response, err := proxyClientConn.Do(&proxyReq) - //nolint:staticcheck // SA1019 ignore deprecated httputil.ErrPersistEOF: it might be - // returned from the invocation of proxyClientConn.Do - if err != nil && err != httputil.ErrPersistEOF { - return nil, err - } - if response != nil && response.StatusCode >= 300 || response.StatusCode < 200 { - return nil, fmt.Errorf("CONNECT request to %s returned response: %s", proxyURL.Redacted(), response.Status) - } - - rwc, _ := proxyClientConn.Hijack() - - if req.URL.Scheme == "https" { - return s.tlsConn(proxyReq.Context(), rwc, targetHost) - } - return rwc, nil -} - -// dialWithSocks5Proxy dials the host specified by url through a socks5 proxy. -func (s *SpdyRoundTripper) dialWithSocks5Proxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) { - // ensure we use a canonical host with proxyReq - targetHost := netutil.CanonicalAddr(req.URL) - proxyDialAddr := netutil.CanonicalAddr(proxyURL) - - var auth *proxy.Auth - if proxyURL.User != nil { - pass, _ := proxyURL.User.Password() - auth = &proxy.Auth{ - User: proxyURL.User.Username(), - Password: pass, - } - } - - dialer := s.Dialer - if dialer == nil { - dialer = &net.Dialer{ - Timeout: 30 * time.Second, - } - } - - proxyDialer, err := proxy.SOCKS5("tcp", proxyDialAddr, auth, dialer) - if err != nil { - return nil, err - } - - // According to the implementation of proxy.SOCKS5, the type assertion will always succeed - contextDialer, ok := proxyDialer.(proxy.ContextDialer) - if !ok { - return nil, errors.New("SOCKS5 Dialer must implement ContextDialer") - } - - proxyDialConn, err := contextDialer.DialContext(req.Context(), "tcp", targetHost) - if err != nil { - return nil, err - } - - if req.URL.Scheme == "https" { - return s.tlsConn(req.Context(), proxyDialConn, targetHost) - } - return proxyDialConn, nil -} - -// tlsConn returns a TLS client side connection using rwc as the underlying transport. -func (s *SpdyRoundTripper) tlsConn(ctx context.Context, rwc net.Conn, targetHost string) (net.Conn, error) { - - host, _, err := net.SplitHostPort(targetHost) - if err != nil { - return nil, err - } - - tlsConfig := s.tlsConfig - switch { - case tlsConfig == nil: - tlsConfig = &tls.Config{ServerName: host} - case len(tlsConfig.ServerName) == 0: - tlsConfig = tlsConfig.Clone() - tlsConfig.ServerName = host - } - - tlsConn := tls.Client(rwc, tlsConfig) - - if err := tlsConn.HandshakeContext(ctx); err != nil { - tlsConn.Close() - return nil, err - } - - return tlsConn, nil -} - -// dialWithoutProxy dials the host specified by url, using TLS if appropriate. -func (s *SpdyRoundTripper) dialWithoutProxy(ctx context.Context, url *url.URL) (net.Conn, error) { - dialAddr := netutil.CanonicalAddr(url) - dialer := s.Dialer - if dialer == nil { - dialer = &net.Dialer{} - } - - if url.Scheme == "http" { - return dialer.DialContext(ctx, "tcp", dialAddr) - } - - tlsDialer := tls.Dialer{ - NetDialer: dialer, - Config: s.tlsConfig, - } - return tlsDialer.DialContext(ctx, "tcp", dialAddr) -} - -// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header -func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string { - if proxyURL == nil || proxyURL.User == nil { - return "" - } - username := proxyURL.User.Username() - password, _ := proxyURL.User.Password() - auth := username + ":" + password - return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) -} - -// RoundTrip executes the Request and upgrades it. After a successful upgrade, -// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded -// connection. -func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - req = utilnet.CloneRequest(req) - req.Header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade) - req.Header.Add(httpstream.HeaderUpgrade, HeaderSpdy31) - - conn, err := s.Dial(req) - if err != nil { - return nil, err - } - - responseReader := bufio.NewReader(conn) - - resp, err := http.ReadResponse(responseReader, nil) - if err != nil { - conn.Close() - return nil, err - } - - s.conn = conn - - return resp, nil -} - -// NewConnection validates the upgrade response, creating and returning a new -// httpstream.Connection if there were no errors. -func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) { - connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection)) - upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade)) - if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) { - defer resp.Body.Close() - responseError := "" - responseErrorBytes, err := io.ReadAll(resp.Body) - if err != nil { - responseError = "unable to read error from server response" - } else { - // TODO: I don't belong here, I should be abstracted from this class - if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil { - if status, ok := obj.(*metav1.Status); ok { - return nil, &apierrors.StatusError{ErrStatus: *status} - } - } - responseError = string(responseErrorBytes) - responseError = strings.TrimSpace(responseError) - } - - return nil, fmt.Errorf("unable to upgrade connection: %s", responseError) - } - - return NewClientConnectionWithPings(s.conn, s.pingPeriod) -} - -// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection -var statusScheme = runtime.NewScheme() - -// ParameterCodec knows about query parameters used with the meta v1 API spec. -var statusCodecs = serializer.NewCodecFactory(statusScheme) - -func init() { - statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion, - &metav1.Status{}, - ) -} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go new file mode 100644 index 0000000000000..37dfe81894cdb --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go @@ -0,0 +1,236 @@ +/* +Copyright 2015 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 spdy + +import ( + "crypto/tls" + "net" + "net/http" + "net/url" + "time" + + apihttpstream "k8s.io/apimachinery/pkg/util/httpstream" + streamhttp "k8s.io/streaming/pkg/httpstream" + streamspdy "k8s.io/streaming/pkg/httpstream/spdy" +) + +const HeaderSpdy31 = streamspdy.HeaderSpdy31 + +// SpdyRoundTripper is a compatibility wrapper around the streaming module's +// SPDY round tripper. +type SpdyRoundTripper struct { + delegate *streamspdy.SpdyRoundTripper +} + +func NewRoundTripper(tlsConfig *tls.Config) (*SpdyRoundTripper, error) { + delegate, err := streamspdy.NewRoundTripper(tlsConfig) + if err != nil { + return nil, err + } + return &SpdyRoundTripper{delegate: delegate}, nil +} + +func NewRoundTripperWithProxy(tlsConfig *tls.Config, proxier func(*http.Request) (*url.URL, error)) (*SpdyRoundTripper, error) { + delegate, err := streamspdy.NewRoundTripperWithProxy(tlsConfig, proxier) + if err != nil { + return nil, err + } + return &SpdyRoundTripper{delegate: delegate}, nil +} + +// RoundTripperConfig is a set of options for an SpdyRoundTripper. +type RoundTripperConfig struct { + // TLS configuration used by the round tripper if UpgradeTransport not present. + TLS *tls.Config + // Proxier is a proxy function invoked on each request. Optional. + Proxier func(*http.Request) (*url.URL, error) + // PingPeriod is a period for sending SPDY Pings on the connection. + // Optional. + PingPeriod time.Duration + // UpgradeTransport is a subtitute transport used for dialing. If set, + // this field will be used instead of "TLS" and "Proxier" for connection creation. + // Optional. + UpgradeTransport http.RoundTripper +} + +func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error) { + delegate, err := streamspdy.NewRoundTripperWithConfig(streamspdy.RoundTripperConfig{ + TLS: cfg.TLS, + Proxier: cfg.Proxier, + PingPeriod: cfg.PingPeriod, + UpgradeTransport: cfg.UpgradeTransport, + }) + if err != nil { + return nil, err + } + return &SpdyRoundTripper{delegate: delegate}, nil +} + +// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during +// proxying with a spdy roundtripper. +func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config { + return s.delegate.TLSClientConfig() +} + +// Dial opens a network connection for an upgrade request. +func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { + return s.delegate.Dial(req) +} + +// RoundTrip executes a request and upgrades the connection. +func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return s.delegate.RoundTrip(req) +} + +// NewConnection validates a server upgrade response and prepares the transport. +func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (apihttpstream.Connection, error) { + conn, err := s.delegate.NewConnection(resp) + if err != nil { + return nil, err + } + return wrapConnection(conn), nil +} + +type responseUpgraderAdapter struct { + delegate streamhttp.ResponseUpgrader +} + +func (r *responseUpgraderAdapter) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler apihttpstream.NewStreamHandler) apihttpstream.Connection { + conn := r.delegate.UpgradeResponse(w, req, wrapNewStreamHandler(newStreamHandler)) + return wrapConnection(conn) +} + +func NewResponseUpgrader() apihttpstream.ResponseUpgrader { + return &responseUpgraderAdapter{delegate: streamspdy.NewResponseUpgrader()} +} + +func NewResponseUpgraderWithPings(pingPeriod time.Duration) apihttpstream.ResponseUpgrader { + return &responseUpgraderAdapter{delegate: streamspdy.NewResponseUpgraderWithPings(pingPeriod)} +} + +func NewClientConnection(conn net.Conn) (apihttpstream.Connection, error) { + c, err := streamspdy.NewClientConnection(conn) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +func NewClientConnectionWithPings(conn net.Conn, pingPeriod time.Duration) (apihttpstream.Connection, error) { + c, err := streamspdy.NewClientConnectionWithPings(conn, pingPeriod) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +func NewServerConnection(conn net.Conn, newStreamHandler apihttpstream.NewStreamHandler) (apihttpstream.Connection, error) { + c, err := streamspdy.NewServerConnection(conn, wrapNewStreamHandler(newStreamHandler)) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +func NewServerConnectionWithPings(conn net.Conn, newStreamHandler apihttpstream.NewStreamHandler, pingPeriod time.Duration) (apihttpstream.Connection, error) { + c, err := streamspdy.NewServerConnectionWithPings(conn, wrapNewStreamHandler(newStreamHandler), pingPeriod) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +type streamAdapter struct { + delegate streamhttp.Stream +} + +func (s *streamAdapter) Read(p []byte) (int, error) { + return s.delegate.Read(p) +} + +func (s *streamAdapter) Write(p []byte) (int, error) { + return s.delegate.Write(p) +} + +func (s *streamAdapter) Close() error { + return s.delegate.Close() +} + +func (s *streamAdapter) Reset() error { + return s.delegate.Reset() +} + +func (s *streamAdapter) Headers() http.Header { + return s.delegate.Headers() +} + +func (s *streamAdapter) Identifier() uint32 { + return s.delegate.Identifier() +} + +type connectionAdapter struct { + delegate streamhttp.Connection +} + +func (c *connectionAdapter) CreateStream(headers http.Header) (apihttpstream.Stream, error) { + stream, err := c.delegate.CreateStream(headers) + if err != nil { + return nil, err + } + return &streamAdapter{delegate: stream}, nil +} + +func (c *connectionAdapter) Close() error { + return c.delegate.Close() +} + +func (c *connectionAdapter) CloseChan() <-chan bool { + return c.delegate.CloseChan() +} + +func (c *connectionAdapter) SetIdleTimeout(timeout time.Duration) { + c.delegate.SetIdleTimeout(timeout) +} + +func (c *connectionAdapter) RemoveStreams(streams ...apihttpstream.Stream) { + streamingStreams := make([]streamhttp.Stream, 0, len(streams)) + for _, stream := range streams { + if stream == nil { + continue + } + if s, ok := stream.(streamhttp.Stream); ok { + streamingStreams = append(streamingStreams, s) + } + } + c.delegate.RemoveStreams(streamingStreams...) +} + +func wrapConnection(conn streamhttp.Connection) apihttpstream.Connection { + if conn == nil { + return nil + } + return &connectionAdapter{delegate: conn} +} + +func wrapNewStreamHandler(newStreamHandler apihttpstream.NewStreamHandler) streamhttp.NewStreamHandler { + if newStreamHandler == nil { + return nil + } + return func(stream streamhttp.Stream, replySent <-chan struct{}) error { + return newStreamHandler(&streamAdapter{delegate: stream}, replySent) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go index a57e8df60a3a8..9b07bfbb137f2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go @@ -14,56 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package wsstream contains utilities for streaming content over WebSockets. -// The Conn type allows callers to multiplex multiple read/write channels over -// a single websocket. +// Package wsstream contains compatibility wrappers for websocket streaming. // -// "channel.k8s.io" -// -// The Websocket RemoteCommand subprotocol "channel.k8s.io" prepends each binary message with a -// byte indicating the channel number (zero indexed) the message was sent on. Messages in both -// directions should prefix their messages with this channel byte. Used for remote execution, -// the channel numbers are by convention defined to match the POSIX file-descriptors assigned -// to STDIN, STDOUT, and STDERR (0, 1, and 2). No other conversion is performed on the raw -// subprotocol - writes are sent as they are received by the server. -// -// Example client session: -// -// CONNECT http://server.com with subprotocol "channel.k8s.io" -// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN) -// READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT) -// CLOSE -// -// "v2.channel.k8s.io" -// -// The second Websocket subprotocol version "v2.channel.k8s.io" is the same as version 1, -// but it is the first "versioned" subprotocol. -// -// "v3.channel.k8s.io" -// -// The third version of the Websocket RemoteCommand subprotocol adds another channel -// for terminal resizing events. This channel is prepended with the byte '3', and it -// transmits two window sizes (encoding TerminalSize struct) with integers in the range -// (0,65536]. -// -// "v4.channel.k8s.io" -// -// The fourth version of the Websocket RemoteCommand subprotocol adds a channel for -// errors. This channel returns structured errors containing process exit codes. The -// error is "apierrors.StatusError{}". -// -// "v5.channel.k8s.io" -// -// The fifth version of the Websocket RemoteCommand subprotocol adds a CLOSE signal, -// which is sent as the first byte of the message. The second byte is the channel -// id. This CLOSE signal is handled by the websocket server by closing the stream, -// allowing the other streams to complete transmission if necessary, and gracefully -// shutdown the connection. -// -// Example client session: -// -// CONNECT http://server.com with subprotocol "v5.channel.k8s.io" -// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN) -// WRITE []byte{255, 0} # send CLOSE signal (STDIN) -// CLOSE +// Deprecated: use k8s.io/streaming/pkg/httpstream/wsstream directly. package wsstream diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go new file mode 100644 index 0000000000000..7384864f912d3 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go @@ -0,0 +1,91 @@ +/* +Copyright 2015 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 wsstream + +import ( + "io" + "net/http" + "time" + + "golang.org/x/net/websocket" + + "k8s.io/klog/v2" + streamws "k8s.io/streaming/pkg/httpstream/wsstream" +) + +const ( + WebSocketProtocolHeader = streamws.WebSocketProtocolHeader + ChannelWebSocketProtocol = streamws.ChannelWebSocketProtocol + Base64ChannelWebSocketProtocol = streamws.Base64ChannelWebSocketProtocol +) + +type ChannelType = streamws.ChannelType + +const ( + IgnoreChannel = streamws.IgnoreChannel + ReadChannel = streamws.ReadChannel + WriteChannel = streamws.WriteChannel + ReadWriteChannel = streamws.ReadWriteChannel +) + +func IsWebSocketRequest(req *http.Request) bool { + return streamws.IsWebSocketRequest(req) +} + +func IsWebSocketRequestWithStreamCloseProtocol(req *http.Request) bool { + return streamws.IsWebSocketRequestWithStreamCloseProtocol(req) +} + +func IsWebSocketRequestWithTunnelingProtocol(req *http.Request) bool { + return streamws.IsWebSocketRequestWithTunnelingProtocol(req) +} + +func IgnoreReceives(ws *websocket.Conn, timeout time.Duration) { + streamws.IgnoreReceives(ws, timeout) +} + +func IgnoreReceivesWithLogger(logger klog.Logger, ws *websocket.Conn, timeout time.Duration) { + streamws.IgnoreReceivesWithLogger(logger, ws, timeout) +} + +type ChannelProtocolConfig = streamws.ChannelProtocolConfig + +func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig { + return streamws.NewDefaultChannelProtocols(channels) +} + +type Conn = streamws.Conn + +func NewConn(protocols map[string]ChannelProtocolConfig) *Conn { + return streamws.NewConn(protocols) +} + +type ReaderProtocolConfig = streamws.ReaderProtocolConfig + +func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig { + return streamws.NewDefaultReaderProtocols() +} + +type Reader = streamws.Reader + +func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader { + return streamws.NewReader(r, ping, protocols) +} + +func NewReaderWithLogger(logger klog.Logger, r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader { + return streamws.NewReaderWithLogger(logger, r, ping, protocols) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.protomessage.pb.go b/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.protomessage.pb.go deleted file mode 100644 index 2853a01830047..0000000000000 --- a/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.protomessage.pb.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package intstr - -func (*IntOrString) ProtoMessage() {} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go b/staging/src/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go index 2d6f6a0ccca65..494325c1f3dc0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go @@ -1,5 +1,4 @@ //go:build !notest -// +build !notest /* Copyright 2020 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/json/json_test.go b/staging/src/k8s.io/apimachinery/pkg/util/json/json_test.go index 9464eb7f3f70a..8caaa274a7e14 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/json/json_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/json/json_test.go @@ -1,5 +1,4 @@ //go:build go1.8 -// +build go1.8 /* Copyright 2015 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go index f2c9c3cc103dd..1a9eb0f874fac 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go @@ -22,8 +22,8 @@ import ( "testing" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/json" + "k8s.io/utils/dump" "sigs.k8s.io/yaml" ) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go index aef858b26dfb1..ff7dda1a50187 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go @@ -17,7 +17,6 @@ limitations under the License. package managedfields import ( - "bytes" "fmt" "sigs.k8s.io/structured-merge-diff/v6/fieldpath" @@ -65,7 +64,7 @@ func ExtractInto(object runtime.Object, objectType typed.ParseableType, fieldMan return nil } fieldset := &fieldpath.Set{} - err = fieldset.FromJSON(bytes.NewReader(fieldsEntry.FieldsV1.Raw)) + err = fieldset.FromJSON(fieldsEntry.FieldsV1.GetRawReader()) if err != nil { return fmt.Errorf("error marshalling FieldsV1 to JSON: %w", err) } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go index b87c4f949c829..50d6273de08c2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go @@ -144,7 +144,7 @@ func applyFieldsEntry(fieldManager string, fieldsJSON string, subresource string Operation: metav1.ManagedFieldsOperationApply, APIVersion: "v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(fieldsJSON)}, + FieldsV1: metav1.NewFieldsV1(fieldsJSON), Subresource: subresource, } } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go index 7110f2656c751..6ba8dc56797a1 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go @@ -851,9 +851,9 @@ func TestUpdateViaSubresources(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{ - Raw: []byte(`{"f:metadata":{"f:labels":{"f:another_field":{}}}}`), - }, + FieldsV1: metav1.NewFieldsV1( + `{"f:metadata":{"f:labels":{"f:another_field":{}}}}`, + ), }, }) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go index 9b139f93e23dd..c7d0711f815b6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go @@ -129,7 +129,7 @@ func TestCapUpdateManagers(t *testing.T) { if err != nil { panic(fmt.Sprintf("error building ManagedFieldsEntry for test: %v", err)) } - return &metav1.FieldsV1{Raw: b} + return metav1.NewFieldsV1(string(b)) } entry := func(name string, version string, order int, fields *metav1.FieldsV1) metav1.ManagedFieldsEntry { @@ -272,7 +272,7 @@ func expectManagesField(t *testing.T, f managedfieldstest.TestFieldManager, m st for _, e := range f.ManagedFields() { if e.Manager == m { var s fieldpath.Set - err := s.FromJSON(bytes.NewReader(e.FieldsV1.Raw)) + err := s.FromJSON(bytes.NewReader(e.FieldsV1.GetRawBytes())) if err != nil { t.Fatalf("error parsing managedFields for %v: %v: %#v", m, err, f.ManagedFields()) } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go index 5c47b9c546640..549412b41db3a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go @@ -17,8 +17,6 @@ limitations under the License. package internal import ( - "bytes" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/structured-merge-diff/v6/fieldpath" @@ -36,12 +34,13 @@ var EmptyFields = func() metav1.FieldsV1 { // FieldsToSet creates a set paths from an input trie of fields func FieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) { - err = s.FromJSON(bytes.NewReader(f.Raw)) + err = s.FromJSON(f.GetRawReader()) return s, err } // SetToFields creates a trie of fields from an input set of paths func SetToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) { - f.Raw, err = s.ToJSON() + raw, err := s.ToJSON() + f.SetRawBytes(raw) return f, err } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go index 2b4acfb2a02f7..e397d2c6810f5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go @@ -29,9 +29,7 @@ import ( // TestFieldsRoundTrip tests that a fields trie can be round tripped as a path set func TestFieldsRoundTrip(t *testing.T) { tests := []metav1.FieldsV1{ - { - Raw: []byte(`{"f:metadata":{".":{},"f:name":{}}}`), - }, + *metav1.NewFieldsV1(`{"f:metadata":{".":{},"f:name":{}}}`), EmptyFields, } @@ -57,9 +55,9 @@ func TestFieldsToSetError(t *testing.T) { errString string }{ { - fields: metav1.FieldsV1{ - Raw: []byte(`{"k:{invalid json}":{"f:name":{},".":{}}}`), - }, + fields: *metav1.NewFieldsV1( + `{"k:{invalid json}":{"f:name":{},".":{}}}`, + ), errString: "ReadObjectCB", }, } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go index 090ab27ca455f..8b35974ed6f37 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go @@ -43,14 +43,14 @@ func TestTransformManagedFieldsToSubresource(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:another-field":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:another-field":{}}}`), }, { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Time: &managedFieldTime, }, }, @@ -60,7 +60,7 @@ func TestTransformManagedFieldsToSubresource(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Time: &managedFieldTime, }, }, @@ -73,21 +73,21 @@ func TestTransformManagedFieldsToSubresource(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, { Manager: "manager-3", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -97,21 +97,21 @@ func TestTransformManagedFieldsToSubresource(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, { Manager: "manager-3", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -124,7 +124,7 @@ func TestTransformManagedFieldsToSubresource(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v10", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, }, expected: nil, @@ -163,7 +163,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -172,7 +172,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -182,14 +182,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -202,7 +202,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -211,7 +211,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -221,7 +221,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, { @@ -229,7 +229,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, }, @@ -241,7 +241,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -250,7 +250,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -260,7 +260,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, { @@ -268,7 +268,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, }, @@ -280,7 +280,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -289,7 +289,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -299,7 +299,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, { @@ -307,7 +307,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, }, @@ -319,7 +319,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -328,7 +328,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -338,14 +338,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -358,7 +358,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -367,7 +367,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -377,14 +377,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -397,7 +397,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -406,7 +406,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -416,7 +416,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, { @@ -424,7 +424,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, }, @@ -436,7 +436,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -445,7 +445,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -455,14 +455,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -475,7 +475,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -484,7 +484,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:status":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:status":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -494,7 +494,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, }, @@ -506,7 +506,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -515,7 +515,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -525,7 +525,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -538,7 +538,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -547,14 +547,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -564,14 +564,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -584,14 +584,14 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, { Manager: "another-manager", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v10", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -600,7 +600,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, }, expected: []metav1.ManagedFieldsEntry{ @@ -609,7 +609,7 @@ func TestTransformingManagedFieldsToParent(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), }, }, }, @@ -655,14 +655,14 @@ func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`), }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v2", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:not-the-replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:not-the-replicas":{},"f:selector":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -671,7 +671,7 @@ func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -681,21 +681,21 @@ func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v2", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{}}}`), Subresource: "scale", }, }, @@ -713,14 +713,14 @@ func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`), }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v2", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:test-other":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:test-other":{}}}`), }, }, subresource: []metav1.ManagedFieldsEntry{ @@ -729,7 +729,7 @@ func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), Subresource: "scale", }, }, @@ -739,21 +739,21 @@ func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v2", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:test-other":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:test-other":{}}}`), }, { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "mygroup/v1", FieldsType: "FieldsV1", - FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{}}}`)}, + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{}}}`), Subresource: "scale", }, }, diff --git a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/util.go b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/util.go index 25626cf3af2a6..46e3bb75ddc5b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/util.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/util.go @@ -20,7 +20,7 @@ import ( "fmt" "reflect" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" "sigs.k8s.io/yaml" ) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/http.go b/staging/src/k8s.io/apimachinery/pkg/util/net/http.go index 8cc1810af1330..8912804c50e40 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/http.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/http.go @@ -132,9 +132,11 @@ func SetTransportDefaults(t *http.Transport) *http.Transport { t = SetOldTransportDefaults(t) // Allow clients to disable http2 if needed. if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 { + //nolint:logcheck // Should be rare, not worth converting. klog.Info("HTTP2 has been explicitly disabled") } else if allowsHTTP2(t) { if err := configureHTTP2Transport(t); err != nil { + //nolint:logcheck // Should be rare, not worth converting. klog.Warningf("Transport failed http2 configuration: %v", err) } } @@ -148,6 +150,7 @@ func readIdleTimeoutSeconds() int { if s := os.Getenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS"); len(s) > 0 { i, err := strconv.Atoi(s) if err != nil { + //nolint:logcheck // Should be rare, not worth converting. klog.Warningf("Illegal HTTP2_READ_IDLE_TIMEOUT_SECONDS(%q): %v."+ " Default value %d is used", s, err, ret) return ret @@ -162,6 +165,7 @@ func pingTimeoutSeconds() int { if s := os.Getenv("HTTP2_PING_TIMEOUT_SECONDS"); len(s) > 0 { i, err := strconv.Atoi(s) if err != nil { + //nolint:logcheck // Should be rare, not worth converting. klog.Warningf("Illegal HTTP2_PING_TIMEOUT_SECONDS(%q): %v."+ " Default value %d is used", s, err, ret) return ret @@ -256,6 +260,7 @@ func CloseIdleConnectionsFor(transport http.RoundTripper) { case RoundTripperWrapper: CloseIdleConnectionsFor(transport.WrappedRoundTripper()) default: + //nolint:logcheck // Should be rare, not worth converting. klog.Warningf("unknown transport type: %T", transport) } } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go b/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go index c6673b29cb6f0..8cced587d2cf7 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go @@ -1,5 +1,4 @@ //go:build go1.8 -// +build go1.8 /* Copyright 2016 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go b/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go index 01d028e727dcb..3ccf227af0219 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go @@ -201,12 +201,12 @@ func parseIP(str string, family AddressFamily) (net.IP, error) { return net.IP(bytes), nil } -func isInterfaceUp(intf *net.Interface) bool { +func isInterfaceUp(logger klog.Logger, intf *net.Interface) bool { if intf == nil { return false } if intf.Flags&net.FlagUp != 0 { - klog.V(4).Infof("Interface %v is up", intf.Name) + logger.V(4).Info("Interface is up", "interface", intf.Name) return true } return false @@ -218,23 +218,23 @@ func isLoopbackOrPointToPoint(intf *net.Interface) bool { // getMatchingGlobalIP returns the first valid global unicast address of the given // 'family' from the list of 'addrs'. -func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) { +func getMatchingGlobalIP(logger klog.Logger, addrs []net.Addr, family AddressFamily) (net.IP, error) { if len(addrs) > 0 { for i := range addrs { - klog.V(4).Infof("Checking addr %s.", addrs[i].String()) + logger.V(4).Info("Checking for matching global IP", "address", addrs[i]) ip, _, err := netutils.ParseCIDRSloppy(addrs[i].String()) if err != nil { return nil, err } if memberOf(ip, family) { if ip.IsGlobalUnicast() { - klog.V(4).Infof("IP found %v", ip) + logger.V(4).Info("IP found", "IP", ip) return ip, nil } else { - klog.V(4).Infof("Non-global unicast address found %v", ip) + logger.V(4).Info("Non-global unicast address found", "IP", ip) } } else { - klog.V(4).Infof("%v is not an IPv%d address", ip, int(family)) + logger.V(4).Info("IP address has wrong version", "IP", ip, "IPVersion", int(family)) } } @@ -244,23 +244,23 @@ func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) // getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The // interface must be up, the IP must in the family requested, and the IP must be a global unicast address. -func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { +func getIPFromInterface(logger klog.Logger, intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { intf, err := nw.InterfaceByName(intfName) if err != nil { return nil, err } - if isInterfaceUp(intf) { + if isInterfaceUp(logger, intf) { addrs, err := nw.Addrs(intf) if err != nil { return nil, err } - klog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs) - matchingIP, err := getMatchingGlobalIP(addrs, forFamily) + logger.V(4).Info("Found addresses for interface", "interface", intfName, "numAddresses", len(addrs), "addresses", addrs) + matchingIP, err := getMatchingGlobalIP(logger, addrs, forFamily) if err != nil { return nil, err } if matchingIP != nil { - klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName) + logger.V(4).Info("Found valid address", "IPVersion", int(forFamily), "IP", matchingIP, "interface", intfName) return matchingIP, nil } } @@ -269,13 +269,13 @@ func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInte // getIPFromLoopbackInterface gets the IPs on a loopback interface and returns a global unicast address, if any. // The loopback interface must be up, the IP must in the family requested, and the IP must be a global unicast address. -func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { +func getIPFromLoopbackInterface(logger klog.Logger, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { intfs, err := nw.Interfaces() if err != nil { return nil, err } for _, intf := range intfs { - if !isInterfaceUp(&intf) { + if !isInterfaceUp(logger, &intf) { continue } if intf.Flags&(net.FlagLoopback) != 0 { @@ -283,13 +283,13 @@ func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) ( if err != nil { return nil, err } - klog.V(4).Infof("Interface %q has %d addresses :%v.", intf.Name, len(addrs), addrs) - matchingIP, err := getMatchingGlobalIP(addrs, forFamily) + logger.V(4).Info("Found addresses for interface", "interface", intf.Name, "numAddresses", len(addrs), "addresses", addrs) + matchingIP, err := getMatchingGlobalIP(logger, addrs, forFamily) if err != nil { return nil, err } if matchingIP != nil { - klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intf.Name) + logger.V(4).Info("Found valid address", "IPVersion", int(forFamily), "IP", matchingIP, "interface", intf.Name) return matchingIP, nil } } @@ -309,7 +309,7 @@ func memberOf(ip net.IP, family AddressFamily) bool { // chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that // has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP. // addressFamilies determines whether it prefers IPv4 or IPv6 -func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { +func chooseIPFromHostInterfaces(logger klog.Logger, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { intfs, err := nw.Interfaces() if err != nil { return nil, err @@ -318,14 +318,14 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam return nil, fmt.Errorf("no interfaces found on host.") } for _, family := range addressFamilies { - klog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family)) + logger.V(4).Info("Looking for system interface with a global address", "IPVersion", uint(family)) for _, intf := range intfs { - if !isInterfaceUp(&intf) { - klog.V(4).Infof("Skipping: down interface %q", intf.Name) + if !isInterfaceUp(logger, &intf) { + logger.V(4).Info("Skipping: interface is down", "interface", intf.Name) continue } if isLoopbackOrPointToPoint(&intf) { - klog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name) + logger.V(4).Info("Skipping: is LB or P2P", "interface", intf.Name) continue } addrs, err := nw.Addrs(&intf) @@ -333,7 +333,7 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam return nil, err } if len(addrs) == 0 { - klog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name) + logger.V(4).Info("Skipping: no addresses", "interface", intf.Name) continue } for _, addr := range addrs { @@ -342,15 +342,15 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam return nil, fmt.Errorf("unable to parse CIDR for interface %q: %s", intf.Name, err) } if !memberOf(ip, family) { - klog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name) + logger.V(4).Info("Skipping: no address family match", "IP", ip, "interface", intf.Name) continue } // TODO: Decide if should open up to allow IPv6 LLAs in future. if !ip.IsGlobalUnicast() { - klog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name) + logger.V(4).Info("Skipping: non-global address", "IP", ip, "interface", intf.Name) continue } - klog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name) + logger.V(4).Info("Found global unicast address", "IP", ip, "interface", intf.Name) return ip, nil } } @@ -363,20 +363,31 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam // interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the // IP of the interface with a gateway on it (with priority given to IPv4). For a node // with no internet connection, it returns error. +// +//logcheck:context // [ChooseHostInterfaceWithLogger] should be used instead of ChooseHostInterface in code which supports contextual logging. func ChooseHostInterface() (net.IP, error) { - return chooseHostInterface(preferIPv4) + return ChooseHostInterfaceWithLogger(klog.Background()) } -func chooseHostInterface(addressFamilies AddressFamilyPreference) (net.IP, error) { +// ChooseHostInterfaceWithLogger is a method used fetch an IP for a daemon. +// If there is no routing info file, it will choose a global IP from the system +// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the +// IP of the interface with a gateway on it (with priority given to IPv4). For a node +// with no internet connection, it returns error. +func ChooseHostInterfaceWithLogger(logger klog.Logger) (net.IP, error) { + return chooseHostInterface(logger, preferIPv4) +} + +func chooseHostInterface(logger klog.Logger, addressFamilies AddressFamilyPreference) (net.IP, error) { var nw networkInterfacer = networkInterface{} if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) { - return chooseIPFromHostInterfaces(nw, addressFamilies) + return chooseIPFromHostInterfaces(logger, nw, addressFamilies) } routes, err := getAllDefaultRoutes() if err != nil { return nil, err } - return chooseHostInterfaceFromRoute(routes, nw, addressFamilies) + return chooseHostInterfaceFromRoute(logger, routes, nw, addressFamilies) } // networkInterfacer defines an interface for several net library functions. Production @@ -427,36 +438,36 @@ func getAllDefaultRoutes() ([]Route, error) { // global IP address from the interface for the route. If there are routes but no global // address is obtained from the interfaces, it checks if the loopback interface has a global address. // addressFamilies determines whether it prefers IPv4 or IPv6 -func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { +func chooseHostInterfaceFromRoute(logger klog.Logger, routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { for _, family := range addressFamilies { - klog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family)) + logger.V(4).Info("Looking for default routes with IP addresses", "IPVersion", uint(family)) for _, route := range routes { if route.Family != family { continue } - klog.V(4).Infof("Default route transits interface %q", route.Interface) - finalIP, err := getIPFromInterface(route.Interface, family, nw) + logger.V(4).Info("Default route transits interface", "interface", route.Interface) + finalIP, err := getIPFromInterface(logger, route.Interface, family, nw) if err != nil { return nil, err } if finalIP != nil { - klog.V(4).Infof("Found active IP %v ", finalIP) + logger.V(4).Info("Found active IP", "IP", finalIP) return finalIP, nil } // In case of network setups where default routes are present, but network // interfaces use only link-local addresses (e.g. as described in RFC5549). // the global IP is assigned to the loopback interface, and we should use it - loopbackIP, err := getIPFromLoopbackInterface(family, nw) + loopbackIP, err := getIPFromLoopbackInterface(logger, family, nw) if err != nil { return nil, err } if loopbackIP != nil { - klog.V(4).Infof("Found active IP %v on Loopback interface", loopbackIP) + logger.V(4).Info("Found active IP on Loopback interface", "IP", loopbackIP) return loopbackIP, nil } } } - klog.V(4).Infof("No active IP found by looking at default routes") + logger.V(4).Info("No active IP found by looking at default routes") return nil, fmt.Errorf("unable to select an IP from default routes.") } @@ -465,14 +476,25 @@ func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressF // If bindAddress is unspecified or loopback, it returns the default IP of the same // address family as bindAddress. // Otherwise, it just returns bindAddress. +// +//logcheck:context // [ResolveBindAddressWithLogger] should be used instead of ResolveBindAddress in code which supports contextual logging. func ResolveBindAddress(bindAddress net.IP) (net.IP, error) { + return ResolveBindAddressWithLogger(klog.Background(), bindAddress) +} + +// ResolveBindAddressWithLogger returns the IP address of a daemon, based on the given bindAddress: +// If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface(). +// If bindAddress is unspecified or loopback, it returns the default IP of the same +// address family as bindAddress. +// Otherwise, it just returns bindAddress. +func ResolveBindAddressWithLogger(logger klog.Logger, bindAddress net.IP) (net.IP, error) { addressFamilies := preferIPv4 if bindAddress != nil && memberOf(bindAddress, familyIPv6) { addressFamilies = preferIPv6 } if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() { - hostIP, err := chooseHostInterface(addressFamilies) + hostIP, err := chooseHostInterface(logger, addressFamilies) if err != nil { return nil, err } @@ -485,10 +507,20 @@ func ResolveBindAddress(bindAddress net.IP) (net.IP, error) { // This is required in case of network setups where default routes are present, but network // interfaces use only link-local addresses (e.g. as described in RFC5549). // e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface. +// +//logcheck:context // [ChooseBindAddressForInterfaceWithLogger] should be used instead of ChooseBindAddressForInterface in code which supports contextual logging. func ChooseBindAddressForInterface(intfName string) (net.IP, error) { + return ChooseBindAddressForInterfaceWithLogger(klog.Background(), intfName) +} + +// ChooseBindAddressForInterfaceWithLogger choose a global IP for a specific interface, with priority given to IPv4. +// This is required in case of network setups where default routes are present, but network +// interfaces use only link-local addresses (e.g. as described in RFC5549). +// e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface. +func ChooseBindAddressForInterfaceWithLogger(logger klog.Logger, intfName string) (net.IP, error) { var nw networkInterfacer = networkInterface{} for _, family := range preferIPv4 { - ip, err := getIPFromInterface(intfName, family, nw) + ip, err := getIPFromInterface(logger, intfName, family, nw) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go b/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go index 77ff70a19c55b..b791aa109019e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go @@ -23,6 +23,7 @@ import ( "strings" "testing" + "k8s.io/klog/v2/ktesting" netutils "k8s.io/utils/net" ) @@ -242,6 +243,7 @@ func TestParseIP(t *testing.T) { } func TestIsInterfaceUp(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testCases := []struct { tcase string intf *net.Interface @@ -252,7 +254,7 @@ func TestIsInterfaceUp(t *testing.T) { {"no interface", nil, false}, } for _, tc := range testCases { - it := isInterfaceUp(tc.intf) + it := isInterfaceUp(logger, tc.intf) if it != tc.expected { t.Errorf("case[%v]: expected %v, got %v .", tc.tcase, tc.expected, it) } @@ -269,6 +271,7 @@ func (a addrStruct) String() string { } func TestFinalIP(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testCases := []struct { tcase string addr []net.Addr @@ -289,7 +292,7 @@ func TestFinalIP(t *testing.T) { {"no addresses", []net.Addr{}, familyIPv4, nil}, } for _, tc := range testCases { - ip, err := getMatchingGlobalIP(tc.addr, tc.family) + ip, err := getMatchingGlobalIP(logger, tc.addr, tc.family) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, ip, err) } @@ -549,6 +552,7 @@ func (_ networkInterfaceWithInvalidAddr) Interfaces() ([]net.Interface, error) { } func TestGetIPFromInterface(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testCases := []struct { tcase string nwname string @@ -567,7 +571,7 @@ func TestGetIPFromInterface(t *testing.T) { {"bad addr", "eth3", familyIPv4, networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"}, } for _, tc := range testCases { - ip, err := getIPFromInterface(tc.nwname, tc.family, tc.nw) + ip, err := getIPFromInterface(logger, tc.nwname, tc.family, tc.nw) if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) @@ -581,6 +585,7 @@ func TestGetIPFromInterface(t *testing.T) { } func TestGetIPFromLoopbackInterface(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testCases := []struct { tcase string family AddressFamily @@ -594,7 +599,7 @@ func TestGetIPFromLoopbackInterface(t *testing.T) { {"no global ipv6", familyIPv6, loopbackNetworkInterface{}, nil, ""}, } for _, tc := range testCases { - ip, err := getIPFromLoopbackInterface(tc.family, tc.nw) + ip, err := getIPFromLoopbackInterface(logger, tc.family, tc.nw) if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) @@ -608,6 +613,7 @@ func TestGetIPFromLoopbackInterface(t *testing.T) { } func TestChooseHostInterfaceFromRoute(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testCases := []struct { tcase string routes []Route @@ -636,7 +642,7 @@ func TestChooseHostInterfaceFromRoute(t *testing.T) { {"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil}, } for _, tc := range testCases { - ip, err := chooseHostInterfaceFromRoute(tc.routes, tc.nw, tc.order) + ip, err := chooseHostInterfaceFromRoute(logger, tc.routes, tc.nw, tc.order) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) } @@ -663,6 +669,7 @@ func TestMemberOf(t *testing.T) { } func TestGetIPFromHostInterfaces(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) testCases := []struct { tcase string nw networkInterfacer @@ -688,7 +695,7 @@ func TestGetIPFromHostInterfaces(t *testing.T) { } for _, tc := range testCases { - ip, err := chooseIPFromHostInterfaces(tc.nw, tc.order) + ip, err := chooseIPFromHostInterfaces(logger, tc.nw, tc.order) if !ip.Equal(tc.expected) { t.Errorf("case[%s]: expected %+v, got %+v with err : %v", tc.tcase, tc.expected, ip, err) } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/dial.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/dial.go index e5196d1ee831a..d6ba23d416cb0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/dial.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/dial.go @@ -39,7 +39,7 @@ func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (ne dialer, err := utilnet.DialerFor(transport) if err != nil { - klog.V(5).Infof("Unable to unwrap transport %T to get dialer: %v", transport, err) + klog.FromContext(ctx).V(5).Info("Unable to unwrap transport to get dialer", "type", fmt.Sprintf("%T", transport), "err", err) } switch url.Scheme { @@ -53,7 +53,7 @@ func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (ne // Get the tls config from the transport if we recognize it tlsConfig, err := utilnet.TLSClientConfig(transport) if err != nil { - klog.V(5).Infof("Unable to unwrap transport %T to get at TLS config: %v", transport, err) + klog.FromContext(ctx).V(5).Info("Unable to unwrap transport to get at TLS config", "type", fmt.Sprintf("%T", transport), "err", err) } if dialer != nil { @@ -65,7 +65,7 @@ func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (ne } if tlsConfig == nil { // tls.Client requires non-nil config - klog.Warning("using custom dialer with no TLSClientConfig. Defaulting to InsecureSkipVerify") + klog.FromContext(ctx).Info("Warning: using custom dialer with no TLSClientConfig, defaulting to InsecureSkipVerify") // tls.Handshake() requires ServerName or InsecureSkipVerify tlsConfig = &tls.Config{ InsecureSkipVerify: true, diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go index 5a2dd6e14c879..1c17f53df033e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go @@ -249,7 +249,7 @@ func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*ht // This is fine default: // Some encoding we don't understand-- don't try to parse this - klog.Errorf("Proxy encountered encoding %v for text/html; can't understand this so not fixing links.", encoding) + klog.FromContext(req.Context()).Error(nil, "Proxy encountered unknown encoding for text/html, can't understand this so not fixing links", "encoding", encoding) return resp, nil } @@ -258,7 +258,7 @@ func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*ht } err := rewriteHTML(reader, writer, urlRewriter) if err != nil { - klog.Errorf("Failed to rewrite URLs: %v", err) + klog.FromContext(req.Context()).Error(err, "Failed to rewrite URLs") return resp, err } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go index 8c30a366de97a..56f62148347a2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go @@ -31,9 +31,9 @@ import ( "time" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/httpstream" utilnet "k8s.io/apimachinery/pkg/util/net" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/streaming/pkg/httpstream" "github.com/mxk/go-flowrate/flowrate" @@ -307,8 +307,9 @@ func (noSuppressPanicError) Write(p []byte) (n int, err error) { // tryUpgrade returns true if the request was handled. func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool { + logger := klog.FromContext(req.Context()) if !httpstream.IsUpgradeRequest(req) { - klog.V(6).Infof("Request was not an upgrade") + logger.V(6).Info("Request was not an upgrade") return false } @@ -332,15 +333,15 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques // Only append X-Forwarded-For in the upgrade path, since httputil.NewSingleHostReverseProxy // handles this in the non-upgrade path. utilnet.AppendForwardedForHeader(clone) - klog.V(6).Infof("Connecting to backend proxy (direct dial) %s\n Headers: %v", &location, clone.Header) + logger.V(6).Info("Connecting to backend proxy (direct dial)", "location", &location, "headers", clone.Header) if h.UseLocationHost { clone.Host = h.Location.Host } clone.URL = &location - klog.V(6).Infof("UpgradeAwareProxy: dialing for SPDY upgrade with headers: %v", clone.Header) + logger.V(6).Info("UpgradeAwareProxy: dialing for SPDY upgrade with headers", "headers", clone.Header) backendConn, err = h.DialForUpgrade(clone) if err != nil { - klog.V(6).Infof("Proxy connection error: %v", err) + logger.V(6).Info("Proxy connection error", "err", err) h.Responder.Error(w, req, err) return true } @@ -349,7 +350,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques // determine the http response code from the backend by reading from rawResponse+backendConn backendHTTPResponse, headerBytes, err := getResponse(io.MultiReader(bytes.NewReader(rawResponse), backendConn)) if err != nil { - klog.V(6).Infof("Proxy connection error: %v", err) + logger.V(6).Info("Proxy connection error", "err", err) h.Responder.Error(w, req, err) return true } @@ -363,7 +364,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques // return a generic error here. if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 { err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode) - klog.Errorf("Proxy upgrade error: %v", err) + logger.Error(err, "Proxy upgrade error") h.Responder.Error(w, req, err) return true } @@ -372,13 +373,13 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques // hijacking should be the last step in the upgrade. requestHijacker, ok := w.(http.Hijacker) if !ok { - klog.Errorf("Unable to hijack response writer: %T", w) + logger.Error(nil, "Unable to hijack response writer", "type", fmt.Sprintf("%T", w)) h.Responder.Error(w, req, fmt.Errorf("request connection cannot be hijacked: %T", w)) return true } requestHijackedConn, _, err := requestHijacker.Hijack() if err != nil { - klog.Errorf("Unable to hijack response: %v", err) + logger.Error(err, "Unable to hijack response") h.Responder.Error(w, req, fmt.Errorf("error hijacking connection: %v", err)) return true } @@ -386,7 +387,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols { // If the backend did not upgrade the request, echo the response from the backend to the client and return, closing the connection. - klog.V(6).Infof("Proxy upgrade error, status code %d", backendHTTPResponse.StatusCode) + logger.V(6).Info("Proxy upgrade error", "statusCode", backendHTTPResponse.StatusCode) // set read/write deadlines deadline := time.Now().Add(10 * time.Second) backendConn.SetReadDeadline(deadline) @@ -394,7 +395,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques // write the response to the client err := backendHTTPResponse.Write(requestHijackedConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - klog.Errorf("Error proxying data from backend to client: %v", err) + logger.Error(err, "Error proxying data from backend to client") } // Indicate we handled the request return true @@ -402,9 +403,9 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques // Forward raw response bytes back to client. if len(rawResponse) > 0 { - klog.V(6).Infof("Writing %d bytes to hijacked connection", len(rawResponse)) + logger.V(6).Info("Writing to hijacked connection", "length", len(rawResponse)) if _, err = requestHijackedConn.Write(rawResponse); err != nil { - utilruntime.HandleError(fmt.Errorf("Error proxying response from backend to client: %v", err)) + utilruntime.HandleErrorWithLogger(logger, err, "Error proxying response from backend to client") } } @@ -424,7 +425,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques } _, err := io.Copy(writer, requestHijackedConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - klog.Errorf("Error proxying data from client to backend: %v", err) + logger.Error(err, "Error proxying data from client to backend") } close(writerComplete) }() @@ -438,7 +439,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques } _, err := io.Copy(requestHijackedConn, reader) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - klog.Errorf("Error proxying data from backend to client: %v", err) + logger.Error(err, "Error proxying data from backend to client") } close(readerComplete) }() @@ -449,7 +450,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques case <-writerComplete: case <-readerComplete: } - klog.V(6).Infof("Disconnecting from backend proxy %s\n Headers: %v", &location, clone.Header) + logger.V(6).Info("Disconnecting from backend proxy", "location", &location, "headers", clone.Header) return true } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go index 33ad02e2012a1..4a0f821febda6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go @@ -42,8 +42,8 @@ import ( "golang.org/x/net/websocket" - "k8s.io/apimachinery/pkg/util/httpstream" utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/streaming/pkg/httpstream" ) const fakeStatusCode = 567 diff --git a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go index cc09bdbc43609..38c1f7b03c41b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go @@ -17,14 +17,17 @@ limitations under the License. package runtime import ( + "bytes" "context" "fmt" "net/http" "runtime" + "strings" "sync" "time" "k8s.io/klog/v2" + "k8s.io/klog/v2/textlogger" ) var ( @@ -55,7 +58,6 @@ func HandleCrash(additionalHandlers ...func(interface{})) { if r := recover(); r != nil { additionalHandlersWithContext := make([]func(context.Context, interface{}), len(additionalHandlers)) for i, handler := range additionalHandlers { - handler := handler // capture loop variable additionalHandlersWithContext[i] = func(_ context.Context, r interface{}) { handler(r) } @@ -155,8 +157,37 @@ var ErrorHandlers = []ErrorHandler{ backoffError(1 * time.Millisecond), } +// ErrorHandler is called indirectly through [HandleError], [HandleErrorWithContext] or [HandleErrorWithLogger]. +// It is passed the same parameters that a structured logging backend needs to log a problem. +// It follows the semantic described for [HandleErrorWithContext] and [logr.Logger.Error]: +// - err is optional and may be nil +// - msg is string that describes the problem +// - keysAndValues contains additional information that varies between different occurrences of the problem +// +// [ErrorToString] can be used to convert these parameters into a single string, using the klog text output. type ErrorHandler func(ctx context.Context, err error, msg string, keysAndValues ...interface{}) +// ErrorToString takes the parameters passed to [ErrorHandler] and +// formats them as a string using the klog text output. +// +// If any of the values is a multi-line string, then the resulting +// string also uses line breaks and indention for the sake of readability. +// Does not include a trailing newline. +// +// Use errors.New if an error instead of a string is needed. +func ErrorToString(err error, msg string, keysAndValues ...interface{}) string { + var buffer bytes.Buffer + config := textlogger.NewConfig( + textlogger.Output(&buffer), + textlogger.WithHeader(false), + ) + logger := textlogger.NewLogger(config) + logger.Error(err, msg, keysAndValues...) + result := buffer.String() + result = strings.TrimSpace(result) + return result +} + // HandlerError is a method to invoke when a non-user facing piece of code cannot // return an error and needs to indicate it has been ignored. Invoking this method // is preferable to logging the error - the default behavior is to log but the diff --git a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go index 09e7bc104a9e9..5af48a7a80604 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go @@ -311,3 +311,39 @@ func TestHandleError(t *testing.T) { }) } } + +func TestErrorToString(t *testing.T) { + for name, tt := range map[string]struct { + err error + msg string + kvs []any + expectString string + }{ + "simple": { + errors.New("some error"), + "Unhandled error", + nil, + `"Unhandled error" err="some error"`, + }, + "nil-error": { + nil, + "Some problem occurred", + nil, + `"Some problem occurred"`, + }, + "keys-and-values": { + errors.New("some error"), + "Some error occurred", + []any{"str", "foobar", "int", 1, "multiLine", "line 1\nline 2"}, + `"Some error occurred" err="some error" str="foobar" int=1 multiLine=< + line 1 + line 2 + >`, + }, + } { + t.Run(name, func(t *testing.T) { + actualString := ErrorToString(tt.err, tt.msg, tt.kvs...) + assert.Equal(t, tt.expectString, actualString) + }) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go index 6825a808e67dc..71f6b5e875c18 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go @@ -1827,9 +1827,7 @@ func (ss SortableSliceOfMaps) Less(i, j int) bool { } func (ss SortableSliceOfMaps) Swap(i, j int) { - tmp := ss.s[i] - ss.s[i] = ss.s[j] - ss.s[j] = tmp + ss.s[i], ss.s[j] = ss.s[j], ss.s[i] } func deduplicateAndSortScalars(s []interface{}) []interface{} { @@ -1875,9 +1873,7 @@ func (ss SortableSliceOfScalars) Less(i, j int) bool { } func (ss SortableSliceOfScalars) Swap(i, j int) { - tmp := ss.s[i] - ss.s[i] = ss.s[j] - ss.s[j] = tmp + ss.s[i], ss.s[j] = ss.s[j], ss.s[i] } // Returns the type of the elements of N slice(s). If the type is different, diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go index d18217fcafc05..fe88937c574af 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go @@ -26,11 +26,11 @@ import ( "sigs.k8s.io/yaml" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/mergepatch" "k8s.io/apimachinery/pkg/util/sets" sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing" + "k8s.io/utils/dump" ) var ( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go index f0264e50c7dff..1eeb71ff53e8a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go @@ -40,10 +40,12 @@ type ErrorMatcher struct { matchField bool // TODO(thockin): consider whether value could be assumed - if the // "want" error has a nil value, don't match on value. - matchValue bool - matchOrigin bool - matchDetail func(want, got string) bool - requireOriginWhenInvalid bool + matchValue bool + matchOrigin bool + matchDetail func(want, got string) bool + requireOriginWhenInvalid bool + matchValidationStabilityLevel bool + matchSource bool // normalizationRules holds the pre-compiled regex patterns for path normalization. normalizationRules []NormalizationRule } @@ -86,6 +88,14 @@ func (m ErrorMatcher) Matches(want, got *Error) bool { if m.matchDetail != nil && !m.matchDetail(want.Detail, got.Detail) { return false } + if m.matchValidationStabilityLevel && want.ValidationStabilityLevel != got.ValidationStabilityLevel { + return false + } + + if m.matchSource && want.FromImperative != got.FromImperative { + return false + } + return true } @@ -148,6 +158,14 @@ func (m ErrorMatcher) Render(e *Error) string { comma() buf.WriteString(fmt.Sprintf("Detail=%q", e.Detail)) } + if m.matchValidationStabilityLevel { + comma() + buf.WriteString(fmt.Sprintf("ValidationStabilityLevel=%s", e.ValidationStabilityLevel)) + } + if m.matchSource { + comma() + buf.WriteString(fmt.Sprintf("FromImperative=%t", e.FromImperative)) + } return "{" + buf.String() + "}" } @@ -224,6 +242,20 @@ func (m ErrorMatcher) RequireOriginWhenInvalid() ErrorMatcher { return m } +// BySource returns a derived ErrorMatcher which also matches by the error origination +// value of field errors. +func (m ErrorMatcher) BySource() ErrorMatcher { + m.matchSource = true + return m +} + +// ByValidationStabilityLevel returns a derived ErrorMatcher which also matches by the validation stability level +// value of field errors. +func (m ErrorMatcher) ByValidationStabilityLevel() ErrorMatcher { + m.matchValidationStabilityLevel = true + return m +} + // ByDetailExact returns a derived ErrorMatcher which also matches errors by // the exact detail string. func (m ErrorMatcher) ByDetailExact() ErrorMatcher { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go index 45a477ce2a24b..8597badf62821 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go @@ -315,6 +315,26 @@ func TestErrorMatcher_Matches(t *testing.T) { wantedErr: baseErr, actualErr: &Error{Type: ErrorTypeInvalid}, matches: false, + }, { + name: "BySource: match", + matcher: ErrorMatcher{}.BySource(), + wantedErr: func() *Error { + e := baseErr() + e.FromImperative = true + return e + }, + actualErr: &Error{FromImperative: true}, + matches: true, + }, { + name: "BySource: no match", + matcher: ErrorMatcher{}.BySource(), + wantedErr: func() *Error { + e := baseErr() + e.FromImperative = true + return e + }, + actualErr: &Error{FromImperative: false}, + matches: false, }} for _, tc := range testCases { @@ -406,6 +426,28 @@ func TestErrorMatcher_Test(t *testing.T) { want: ErrorList{Invalid(NewPath("f").Index(0).Child("x", "a"), nil, "")}, got: ErrorList{Invalid(NewPath("f").Index(1).Child("a"), "v", "d")}, expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }, { + name: "validation level: match", + matcher: ErrorMatcher{}.ByValidationStabilityLevel(), + want: ErrorList{{}}.MarkAlpha(), + got: ErrorList{{}}.MarkAlpha(), + }, { + name: "validation level: no match", + matcher: ErrorMatcher{}.ByValidationStabilityLevel(), + want: ErrorList{{}}.MarkAlpha(), + got: ErrorList{{}}.MarkBeta(), + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }, { + name: "by source: match", + matcher: ErrorMatcher{}.BySource(), + want: ErrorList{{FromImperative: true}}, + got: ErrorList{{FromImperative: true}}, + }, { + name: "by source: no match", + matcher: ErrorMatcher{}.BySource(), + want: ErrorList{{FromImperative: true}}, + got: ErrorList{{FromImperative: false}}, + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, }, { name: "with origin: single match", matcher: ErrorMatcher{}.ByField().ByOrigin(), @@ -544,6 +586,26 @@ func TestErrorMatcher_Render(t *testing.T) { err: Required(NewPath("field"), "detail"), expected: `{Type="Required value", Value=""}`, }, + { + name: "with from imperative", + matcher: ErrorMatcher{}.BySource(), + err: func() *Error { + e := Invalid(NewPath("field"), "value", "detail") + e.FromImperative = true + return e + }(), + expected: `{FromImperative=true}`, + }, + { + name: "all fields with from imperative", + matcher: ErrorMatcher{}.ByType().ByField().ByValue().ByOrigin().ByDetailExact().BySource(), + err: func() *Error { + e := Invalid(NewPath("field"), "value", "detail").WithOrigin("origin") + e.FromImperative = true + return e + }(), + expected: `{Type="Invalid value", Field="field", Value="value", Origin="origin", Detail="detail", FromImperative=true}`, + }, } for _, tc := range testCases { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go index 950d838682346..9357fb306cec0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go @@ -42,7 +42,7 @@ type Error struct { // The value should be either: // - A simple camelCase identifier (e.g., "maximum", "maxItems") // - A structured format using "format=" for validation errors related to specific formats - // (e.g., "format=dns-label", "format=qualified-name") + // (e.g. "format=k8s-short-name") // // If the Origin corresponds to an existing declarative validation tag or JSON Schema keyword, // use that same name for consistency. @@ -55,10 +55,46 @@ type Error struct { // validation. This field is to identify errors from imperative validation // that should also be caught by declarative validation. CoveredByDeclarative bool + + // FromImperative denotes these errors are originating from the hand written validations. + FromImperative bool + + // ValidationStabilityLevel denotes the validation stability level of the declarative validation from this error is returned. This should be used in the declarative validations only. + ValidationStabilityLevel ValidationStabilityLevel +} + +// ValidationStabilityLevel denotes the stability level of a validation. +type ValidationStabilityLevel int + +const ( + stabilityLevelUnknown ValidationStabilityLevel = iota + stabilityLevelAlpha + stabilityLevelBeta +) + +func (v ValidationStabilityLevel) String() string { + switch v { + case stabilityLevelAlpha: + return "alpha" + case stabilityLevelBeta: + return "beta" + default: + return "unknown" + } } var _ error = &Error{} +// IsAlpha returns true if the error is an alpha validation error. +func (e *Error) IsAlpha() bool { + return e.ValidationStabilityLevel == stabilityLevelAlpha +} + +// IsBeta returns true if the error is a beta validation error. +func (e *Error) IsBeta() bool { + return e.ValidationStabilityLevel == stabilityLevelBeta +} + // Error implements the error interface. func (e *Error) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.ErrorBody()) @@ -73,10 +109,10 @@ var omitValue = OmitValueType{} func (e *Error) ErrorBody() string { var s string switch e.Type { - case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal: + case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeTooShort, ErrorTypeInternal: s = e.Type.String() case ErrorTypeInvalid, ErrorTypeTypeInvalid, ErrorTypeNotSupported, - ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany: + ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany, ErrorTypeTooFew: if e.BadValue == omitValue { s = e.Type.String() break @@ -113,6 +149,7 @@ func (e *Error) ErrorBody() string { if len(e.Detail) != 0 { s += fmt.Sprintf(": %s", e.Detail) } + return s } @@ -164,11 +201,18 @@ const ( // report that a given list has too many items. This is similar to FieldValueTooLong, // but the error indicates quantity instead of length. ErrorTypeTooMany ErrorType = "FieldValueTooMany" + // ErrorTypeTooFew is used to report "too few". This is used to + // report that a given list has too few items. This is similar to FieldValueTooLong, + // but the error indicates quantity instead of length. + ErrorTypeTooFew ErrorType = "FieldValueTooFew" // ErrorTypeInternal is used to report other errors that are not related // to user input. See InternalError(). ErrorTypeInternal ErrorType = "InternalError" // ErrorTypeTypeInvalid is for the value did not match the schema type for that field ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid" + // ErrorTypeTooShort is used to report that the given value is too short. + // This is similar to ErrorTypeInvalid. See TooShort(). + ErrorTypeTooShort ErrorType = "FieldValueTooShort" ) // String converts a ErrorType into its corresponding canonical error message. @@ -190,10 +234,14 @@ func (t ErrorType) String() string { return "Too long" case ErrorTypeTooMany: return "Too many" + case ErrorTypeTooFew: + return "Too few" case ErrorTypeInternal: return "Internal error" case ErrorTypeTypeInvalid: return "Invalid value" + case ErrorTypeTooShort: + return "Too short" default: return fmt.Sprintf("", string(t)) } @@ -201,32 +249,56 @@ func (t ErrorType) String() string { // TypeInvalid returns a *Error indicating "type is invalid" func TypeInvalid(field *Path, value interface{}, detail string) *Error { - return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, "", false} + return &Error{ + Type: ErrorTypeTypeInvalid, + Field: field.String(), + BadValue: value, + Detail: detail, + } } // NotFound returns a *Error indicating "value not found". This is // used to report failure to find a requested value (e.g. looking up an ID). func NotFound(field *Path, value interface{}) *Error { - return &Error{ErrorTypeNotFound, field.String(), value, "", "", false} + return &Error{ + Type: ErrorTypeNotFound, + Field: field.String(), + BadValue: value, + } } // Required returns a *Error indicating "value required". This is used // to report required values that are not provided (e.g. empty strings, null // values, or empty arrays). func Required(field *Path, detail string) *Error { - return &Error{ErrorTypeRequired, field.String(), "", detail, "", false} + return &Error{ + Type: ErrorTypeRequired, + Field: field.String(), + Detail: detail, + BadValue: "", + } } // Duplicate returns a *Error indicating "duplicate value". This is // used to report collisions of values that must be unique (e.g. names or IDs). func Duplicate(field *Path, value interface{}) *Error { - return &Error{ErrorTypeDuplicate, field.String(), value, "", "", false} + return &Error{ + Type: ErrorTypeDuplicate, + Field: field.String(), + BadValue: value, + } } // Invalid returns a *Error indicating "invalid value". This is used // to report malformed values (e.g. failed regex match, too long, out of bounds). func Invalid(field *Path, value interface{}, detail string) *Error { - return &Error{ErrorTypeInvalid, field.String(), value, detail, "", false} + return &Error{ + Type: ErrorTypeInvalid, + Field: field.String(), + BadValue: value, + Detail: detail, + } + } // NotSupported returns a *Error indicating "unsupported value". @@ -241,7 +313,12 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E } detail = "supported values: " + strings.Join(quotedValues, ", ") } - return &Error{ErrorTypeNotSupported, field.String(), value, detail, "", false} + return &Error{ + Type: ErrorTypeNotSupported, + Field: field.String(), + BadValue: value, + Detail: detail, + } } // Forbidden returns a *Error indicating "forbidden". This is used to @@ -249,7 +326,12 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E // some conditions, but which are not permitted by current conditions (e.g. // security policy). func Forbidden(field *Path, detail string) *Error { - return &Error{ErrorTypeForbidden, field.String(), "", detail, "", false} + return &Error{ + Type: ErrorTypeForbidden, + Field: field.String(), + Detail: detail, + BadValue: "", + } } // TooLong returns a *Error indicating "too long". This is used to report that @@ -267,7 +349,35 @@ func TooLong(field *Path, _ interface{}, maxLength int) *Error { } else { msg = "value is too long" } - return &Error{ErrorTypeTooLong, field.String(), "", msg, "", false} + return &Error{ + Type: ErrorTypeTooLong, + Field: field.String(), + BadValue: "", + Detail: msg, + } +} + +// TooLongCharacters returns a *Error indicating "too long". This is used to report that +// the given value is too long in characters (including multi-byte characters). +// This is similar to Invalid, but the returned error will not include the too-long value. +// If maxLength is negative, it will be included in the message. The value argument is not used. +func TooLongCharacters[T ~string](field *Path, _ T, maxLength int) *Error { + var msg string + if maxLength >= 0 { + bs := "characters" + if maxLength == 1 { + bs = "character" + } + msg = fmt.Sprintf("may not be more than %d %s", maxLength, bs) + } else { + msg = "value is too long" + } + return &Error{ + Type: ErrorTypeTooLong, + Field: field.String(), + BadValue: "", + Detail: msg, + } } // TooLongMaxLength returns a *Error indicating "too long". @@ -299,14 +409,46 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error { actual = omitValue } - return &Error{ErrorTypeTooMany, field.String(), actual, msg, "", false} + return &Error{ + Type: ErrorTypeTooMany, + Field: field.String(), + BadValue: actual, + Detail: msg, + } } // InternalError returns a *Error indicating "internal error". This is used // to signal that an error was found that was not directly related to user // input. The err argument must be non-nil. func InternalError(field *Path, err error) *Error { - return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), "", false} + return &Error{ + Type: ErrorTypeInternal, + Field: field.String(), + BadValue: err, + Detail: err.Error(), + } +} + +// TooShort returns a *Error indicating "too short". This is used to report that +// the given value is too short in characters. This is similar to Invalid. +// If minLength is non-negative, it will be included in the message. +func TooShort[T ~string](field *Path, value T, minLength int) *Error { + var msg string + if minLength >= 0 { + bs := "characters" + if minLength == 1 { + bs = "character" + } + msg = fmt.Sprintf("must be at least %d %s", minLength, bs) + } else { + msg = "value is too short" + } + return &Error{ + Type: ErrorTypeTooShort, + Field: field.String(), + BadValue: value, + Detail: msg, + } } // ErrorList holds a set of Errors. It is plausible that we might one day have @@ -397,6 +539,46 @@ func (list ErrorList) ExtractCoveredByDeclarative() ErrorList { return newList } +// MarkAlpha marks the error as an alpha validation error. +func (e *Error) MarkAlpha() *Error { + e.ValidationStabilityLevel = stabilityLevelAlpha + return e +} + +// MarkAlpha marks the errors as alpha validation errors. +func (list ErrorList) MarkAlpha() ErrorList { + for _, err := range list { + err.ValidationStabilityLevel = stabilityLevelAlpha + } + return list +} + +// MarkBeta marks the error as a beta validation error. +func (e *Error) MarkBeta() *Error { + e.ValidationStabilityLevel = stabilityLevelBeta + return e +} + +// MarkBeta marks the errors as beta validation errors. +func (list ErrorList) MarkBeta() ErrorList { + for _, err := range list { + err.ValidationStabilityLevel = stabilityLevelBeta + } + return list +} + +func (e *Error) MarkFromImperative() *Error { + e.FromImperative = true + return e +} + +func (list ErrorList) MarkFromImperative() ErrorList { + for _, err := range list { + err.FromImperative = true + } + return list +} + // RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation. func (list ErrorList) RemoveCoveredByDeclarative() ErrorList { newList := ErrorList{} @@ -407,3 +589,27 @@ func (list ErrorList) RemoveCoveredByDeclarative() ErrorList { } return newList } + +// TooFew returns a *Error indicating "too few". This is used to +// report that a given list has too few items. This is similar to TooLong, +// but the returned error indicates quantity instead of length. +func TooFew(field *Path, actualQuantity, minQuantity int) *Error { + var msg string + + if minQuantity >= 0 { + is := "items" + if minQuantity == 1 { + is = "item" + } + msg = fmt.Sprintf("must have at least %d %s", minQuantity, is) + } else { + msg = "has too few items" + } + + return &Error{ + Type: ErrorTypeTooFew, + Field: field.String(), + BadValue: actualQuantity, + Detail: msg, + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go index a8c7052f9fb35..229595d900d78 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go @@ -200,6 +200,45 @@ func TestErrorListMarkDeclarative(t *testing.T) { } } +func TestErrorMarkFromImperative(t *testing.T) { + // Test for single Error + err := Invalid(NewPath("field"), "value", "detail") + if err.FromImperative { + t.Errorf("New error should not be from imperative by default") + } + + // Mark as FromImperative + err.MarkFromImperative() //nolint:errcheck // The "error" here is not an unexpected error from the function. + if !err.FromImperative { + t.Errorf("Error should be from imperative after marking") + } +} + +func TestErrorListMarkFromImperative(t *testing.T) { + // Test for ErrorList + list := ErrorList{ + Invalid(NewPath("field1"), "value1", "detail1"), + Invalid(NewPath("field2"), "value2", "detail2"), + } + + // Verify none are from imperative by default + for i, err := range list { + if err.FromImperative { + t.Errorf("Error %d should not be from imperative by default", i) + } + } + + // Mark list as from imperative + list.MarkFromImperative() + + // Verify all errors in the list are now from imperative + for i, err := range list { + if !err.FromImperative { + t.Errorf("Error %d should be from imperative after marking the list", i) + } + } +} + func TestErrorListExtractCoveredByDeclarative(t *testing.T) { testCases := []struct { list ErrorList diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/ip.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/ip.go index 6e947c74c9e14..869fb0aad08d2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/ip.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/ip.go @@ -115,7 +115,7 @@ func GetWarningsForIP(fldPath *field.Path, value string) []string { // ParseAddr() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way, // re-stringifying the net.IP value will give the preferred form. return []string{ - fmt.Sprintf("%s: non-standard IP address %q will be considered invalid in a future Kubernetes release: use %q", fldPath, value, ip.String()), + fmt.Sprintf("%s: non-standard IP address %q is invalid: use %q", fldPath, value, ip.String()), } } @@ -233,7 +233,7 @@ func GetWarningsForCIDR(fldPath *field.Path, value string) []string { // ParsePrefix() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way, // re-stringifying the net.IPNet value will give the preferred form. warnings = append(warnings, - fmt.Sprintf("%s: non-standard CIDR value %q will be considered invalid in a future Kubernetes release: use %q", fldPath, value, ipnet.String()), + fmt.Sprintf("%s: non-standard CIDR value %q is invalid: use %q", fldPath, value, ipnet.String()), ) } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/ip_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/ip_test.go index caf5cf1fe51c2..a96ced3cad447 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/ip_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/ip_test.go @@ -284,7 +284,7 @@ func TestGetWarningsForIP(t *testing.T) { address: "192.012.2.2", fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0), want: []string{ - `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, }, }, { @@ -292,7 +292,7 @@ func TestGetWarningsForIP(t *testing.T) { address: "::ffff:192.12.2.2", fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0), want: []string{ - `spec.clusterIPs[0]: non-standard IP address "::ffff:192.12.2.2" will be considered invalid in a future Kubernetes release: use "192.12.2.2"`, + `spec.clusterIPs[0]: non-standard IP address "::ffff:192.12.2.2" is invalid: use "192.12.2.2"`, }, }, { @@ -551,7 +551,7 @@ func TestGetWarningsForCIDR(t *testing.T) { cidr: "192.012.2.0/24", fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), want: []string{ - `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.012.2.0/24" will be considered invalid in a future Kubernetes release: use "192.12.2.0/24"`, + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.012.2.0/24" is invalid: use "192.12.2.0/24"`, }, }, { @@ -559,7 +559,7 @@ func TestGetWarningsForCIDR(t *testing.T) { cidr: "192.12.2.0/024", fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), want: []string{ - `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.12.2.0/024" will be considered invalid in a future Kubernetes release: use "192.12.2.0/24"`, + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.12.2.0/024" is invalid: use "192.12.2.0/24"`, }, }, { @@ -567,7 +567,7 @@ func TestGetWarningsForCIDR(t *testing.T) { cidr: "::ffff:192.12.2.0/120", fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), want: []string{ - `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "::ffff:192.12.2.0/120" will be considered invalid in a future Kubernetes release: use "192.12.2.0/24"`, + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "::ffff:192.12.2.0/120" is invalid: use "192.12.2.0/24"`, }, }, { @@ -584,7 +584,7 @@ func TestGetWarningsForCIDR(t *testing.T) { fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), want: []string{ `spec.loadBalancerSourceRanges[0]: CIDR value "192.012.2.8/24" is ambiguous in this context (should be "192.12.2.0/24" or "192.12.2.8/32"?)`, - `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.012.2.8/24" will be considered invalid in a future Kubernetes release: use "192.12.2.0/24"`, + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.012.2.8/24" is invalid: use "192.12.2.0/24"`, }, }, { diff --git a/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go b/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go index 511e625b63df3..bb0fa55f2124d 100644 --- a/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go +++ b/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go @@ -178,7 +178,7 @@ func (e Equalities) deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, } // Two lists that are both empty and both non nil are equal - if v1.Len() == 0 || v2.Len() == 0 { + if v1.Len() == 0 && v2.Len() == 0 { return true } } @@ -229,7 +229,7 @@ func (e Equalities) deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, } // Two maps that are both empty and both non nil are equal - if v1.Len() == 0 || v2.Len() == 0 { + if v1.Len() == 0 && v2.Len() == 0 { return true } } diff --git a/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go b/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go index 6222817615c9c..6e1e6d0d3834f 100644 --- a/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go +++ b/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go @@ -36,64 +36,64 @@ func TestEqualities(t *testing.T) { X int } - type Case struct { - a, b interface{} - equal bool - } - - table := []Case{ - {1, 2, true}, - {2, 1, false}, - {"foo", "fo", false}, - {"foo", "foo", true}, - {"foo", "foobar", false}, - {Foo{1}, Foo{2}, true}, - {Foo{2}, Foo{1}, false}, - {Bar{1}, Bar{10}, true}, - {&Bar{1}, &Bar{10}, true}, - {Baz{Bar{1}}, Baz{Bar{10}}, true}, - {[...]string{}, [...]string{"1", "2", "3"}, false}, - {[...]string{"1"}, [...]string{"1", "2", "3"}, false}, - {[...]string{"1", "2", "3"}, [...]string{}, false}, - {[...]string{"1", "2", "3"}, [...]string{"1", "2", "3"}, true}, - {map[string]int{"foo": 1}, map[string]int{}, false}, - {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true}, - {map[string]int{"foo": 2}, map[string]int{"foo": 1}, false}, - {map[string]int{"foo": 1}, map[string]int{"foo": 2, "bar": 6}, false}, - {map[string]int{"foo": 1, "bar": 6}, map[string]int{"foo": 2}, false}, - {map[string]int{}, map[string]int(nil), true}, - {[]string(nil), []string(nil), true}, - {[]string{}, []string(nil), true}, - {[]string(nil), []string{}, true}, - {[]string{"1"}, []string(nil), false}, - {[]string{}, []string{"1", "2", "3"}, false}, - {[]string{"1"}, []string{"1", "2", "3"}, false}, - {[]string{"1", "2", "3"}, []string{}, false}, + table := []struct { + a, b interface{} + deepEqual bool // Expected result for DeepEqual + deepEqualWithNilDifferentFromEmpty bool // Expected result for DeepEqualWithNilDifferentFromEmpty + }{ + // Custom equality functions + {1, 2, true, true}, + {2, 1, false, false}, + {"foo", "fo", false, false}, + {"foo", "foo", true, true}, + {"foo", "foobar", false, false}, + {Foo{1}, Foo{2}, true, true}, + {Foo{2}, Foo{1}, false, false}, + {Bar{1}, Bar{10}, true, true}, + {&Bar{1}, &Bar{10}, true, true}, + {Baz{Bar{1}}, Baz{Bar{10}}, true, true}, + // Arrays + {[...]string{}, [...]string{"1", "2", "3"}, false, false}, + {[...]string{"1"}, [...]string{"1", "2", "3"}, false, false}, + {[...]string{"1", "2", "3"}, [...]string{}, false, false}, + {[...]string{"1", "2", "3"}, [...]string{"1", "2", "3"}, true, true}, + // Maps with custom equality + {map[string]int{"foo": 1}, map[string]int{}, false, false}, + {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true, true}, + {map[string]int{"foo": 2}, map[string]int{"foo": 1}, false, false}, + {map[string]int{"foo": 1}, map[string]int{"foo": 2, "bar": 6}, false, false}, + {map[string]int{"foo": 1, "bar": 6}, map[string]int{"foo": 2}, false, false}, + // Nil vs empty (DeepEqual treats as equal, DeepEqualWithNilDifferentFromEmpty treats as different) + {map[string]int{}, map[string]int(nil), true, false}, + {[]string(nil), []string(nil), true, true}, + {[]string{}, []string(nil), true, false}, + {[]string(nil), []string{}, true, false}, + {[]int{}, []int(nil), true, false}, + // Nil vs filled (both functions should return false) + {[]string{"1"}, []string(nil), false, false}, + // Empty vs filled (both functions should return false) + {[]string{}, []string{"1"}, false, false}, + {[]string{}, []string{"1", "2", "3"}, false, false}, + {[]int{}, []int{1, 2, 3}, false, false}, + {map[string]int{}, map[string]int{"foo": 1}, false, false}, + // Filled vs empty (both functions should return false) + {[]string{"1"}, []string{}, false, false}, + {[]string{"1"}, []string{"1", "2", "3"}, false, false}, + {[]string{"1", "2", "3"}, []string{}, false, false}, + {[]int{1, 2, 3}, []int{}, false, false}, + {map[string]int{"foo": 1}, map[string]int{}, false, false}, + // Nested nil/empty (DeepEqual treats as equal, DeepEqualWithNilDifferentFromEmpty treats as different) + {map[string][]int{}, map[string][]int(nil), true, false}, + {map[string][]int{"foo": nil}, map[string][]int{"foo": {}}, true, false}, + {Zap{A: nil, B: map[string][]int{"foo": nil}}, Zap{A: []int{}, B: map[string][]int{"foo": {}}}, true, false}, } for _, item := range table { - if e, a := item.equal, e.DeepEqual(item.a, item.b); e != a { - t.Errorf("Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) + if e, a := item.deepEqual, e.DeepEqual(item.a, item.b); e != a { + t.Errorf("DeepEqual: Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) } - } - - // Cases which hinge upon implicit nil/empty map/slice equality - implicitTable := []Case{ - {map[string][]int{}, map[string][]int(nil), true}, - {[]int{}, []int(nil), true}, - {map[string][]int{"foo": nil}, map[string][]int{"foo": {}}, true}, - {Zap{A: nil, B: map[string][]int{"foo": nil}}, Zap{A: []int{}, B: map[string][]int{"foo": {}}}, true}, - } - - for _, item := range implicitTable { - if e, a := item.equal, e.DeepEqual(item.a, item.b); e != a { - t.Errorf("Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) - } - } - - for _, item := range implicitTable { - if e, a := !item.equal, e.DeepEqualWithNilDifferentFromEmpty(item.a, item.b); e != a { - t.Errorf("Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) + if e, a := item.deepEqualWithNilDifferentFromEmpty, e.DeepEqualWithNilDifferentFromEmpty(item.a, item.b); e != a { + t.Errorf("DeepEqualWithNilDifferentFromEmpty: Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) } } } diff --git a/staging/src/k8s.io/apiserver/go.mod b/staging/src/k8s.io/apiserver/go.mod index 181fe9986e668..12ecd229dd030 100644 --- a/staging/src/k8s.io/apiserver/go.mod +++ b/staging/src/k8s.io/apiserver/go.mod @@ -2,75 +2,76 @@ module k8s.io/apiserver -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/blang/semver/v4 v4.0.0 - github.com/coreos/go-oidc v2.3.0+incompatible - github.com/coreos/go-systemd/v22 v22.5.0 - github.com/emicklei/go-restful/v3 v3.12.2 + github.com/coreos/go-oidc v2.5.0+incompatible + github.com/coreos/go-systemd/v22 v22.7.0 + github.com/emicklei/go-restful/v3 v3.13.0 github.com/fsnotify/fsnotify v1.9.0 github.com/go-logr/logr v1.4.3 - github.com/google/btree v1.1.3 + github.com/go-openapi/jsonreference v0.20.2 github.com/google/cel-go v0.26.0 github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f - github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 + github.com/openshift/library-go v0.0.0-20260612230159-06432ea1b022 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/api/v3 v3.6.5 - go.etcd.io/etcd/client/pkg/v3 v3.6.5 - go.etcd.io/etcd/client/v3 v3.6.5 - go.etcd.io/etcd/server/v3 v3.6.5 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 - go.opentelemetry.io/otel/metric v1.36.0 - go.opentelemetry.io/otel/sdk v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 + go.etcd.io/etcd/api/v3 v3.6.8 + go.etcd.io/etcd/client/pkg/v3 v3.6.8 + go.etcd.io/etcd/client/v3 v3.6.8 + go.etcd.io/etcd/server/v3 v3.6.8 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 + go.opentelemetry.io/otel v1.41.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 + go.opentelemetry.io/otel/metric v1.41.0 + go.opentelemetry.io/otel/sdk v1.40.0 + go.opentelemetry.io/otel/trace v1.41.0 go.uber.org/atomic v1.11.0 - go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.45.0 - golang.org/x/net v0.47.0 - golang.org/x/sync v0.18.0 - golang.org/x/sys v0.38.0 - golang.org/x/text v0.31.0 - golang.org/x/time v0.9.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb - google.golang.org/grpc v1.72.2 - google.golang.org/protobuf v1.36.8 + go.uber.org/zap v1.27.1 + golang.org/x/crypto v0.47.0 + golang.org/x/net v0.49.0 + golang.org/x/sync v0.19.0 + golang.org/x/sys v0.40.0 + golang.org/x/text v0.33.0 + golang.org/x/time v0.14.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/evanphx/json-patch.v4 v4.13.0 gopkg.in/go-jose/go-jose.v2 v2.6.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 - k8s.io/api v0.35.1 - k8s.io/apimachinery v0.35.1 - k8s.io/client-go v0.35.1 - k8s.io/component-base v0.35.1 - k8s.io/klog/v2 v2.130.1 - k8s.io/kms v0.35.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 + k8s.io/api v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/client-go v0.36.2 + k8s.io/component-base v0.36.2 + k8s.io/klog/v2 v2.140.0 + k8s.io/kms v0.36.2 + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a + k8s.io/streaming v0.36.2 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 sigs.k8s.io/randfill v1.0.0 - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 sigs.k8s.io/yaml v1.6.0 ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -79,55 +80,56 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // 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/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern 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/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect - github.com/spf13/cobra v1.10.0 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.5 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.8 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/term v0.37.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/term v0.39.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 + github.com/openshift/api => github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf + github.com/openshift/client-go => github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27 + github.com/openshift/library-go => github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 k8s.io/api => ../api k8s.io/apiextensions-apiserver => ../apiextensions-apiserver k8s.io/apimachinery => ../apimachinery @@ -135,4 +137,5 @@ replace ( k8s.io/component-base => ../component-base k8s.io/kms => ../kms k8s.io/kube-aggregator => ../kube-aggregator + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/apiserver/go.sum b/staging/src/k8s.io/apiserver/go.sum index 8fb2fe6eb0987..2b96a4580b8f6 100644 --- a/staging/src/k8s.io/apiserver/go.sum +++ b/staging/src/k8s.io/apiserver/go.sum @@ -1,18 +1,17 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1/go.mod h1:novQBstnxcGpfKf8qGRATqn1anQKwMJIbH5Q581jibU= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= -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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2/go.mod h1:XyjUkMA8GN+tOOPXvnbi3XuRxWFvTJntqvTFnjmhzbk= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 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= @@ -22,20 +21,20 @@ 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= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= -github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc v2.5.0+incompatible h1:6W0vGJR3Tu0r0PwfmjOrRZSlfxeEln8dsejt3ZWIvwo= +github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 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/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -49,12 +48,12 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +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.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -64,7 +63,7 @@ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui72 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-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -81,14 +80,12 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0/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.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +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 v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -107,7 +104,6 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -115,16 +111,13 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS 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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +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.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -136,6 +129,10 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf/go.mod h1:FS918VnrERibSebUVMka1NP+eyrKGi5ry4nl/BXAKbs= +github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27/go.mod h1:bOkl2x9t+UlXxD8riuhEi0MbjjYnXuYCSzuDXTIA4wI= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 h1:Pk6nmvZxrSQ9IlpsOC9FQiV45idIDLTN3hft7dzxdSA= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022/go.mod h1:iGCkgqyOiqzvkTHEyxVf+RmxzvNNfEFR/AJLoFSxeG0= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -153,8 +150,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -168,18 +165,11 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= -github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= -github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a/go.mod h1:V933kvY/cb/Un7UCEOhXHUySNX327u7Epe8g9KNqg2Q= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 h1:AHjJETxL4n2ocMHBY7Epqex574puUSJjKIUkbasYAKI= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3/go.mod h1:gKG9lctU0yEftSoT3DUyeIWz1oAgF0EHUpwI4pnCo4o= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= @@ -194,10 +184,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -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/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.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -208,12 +198,11 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= -github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= -github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= 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= @@ -239,53 +228,52 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chq github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -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.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +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/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +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.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +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.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -293,29 +281,28 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -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/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +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/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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -324,38 +311,39 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +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/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -373,20 +361,20 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -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/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +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-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/apiserver/pkg/admission/config.go b/staging/src/k8s.io/apiserver/pkg/admission/config.go index 4bb5f27bfbbbf..1e64af447470e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/config.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/config.go @@ -108,7 +108,7 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, con // in order to preserve backwards compatibility, we set plugins that // previously read input from a non-versioned file configuration to the // current input file. - legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector") + legacyPluginsWithUnversionedConfig := sets.New[string]("ImagePolicyWebhook", "PodNodeSelector") externalConfig := &apiserverv1.AdmissionConfiguration{} for _, pluginName := range pluginNames { if legacyPluginsWithUnversionedConfig.Has(pluginName) { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go index 801c4ecaa351f..383d0084cd558 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go @@ -20,16 +20,17 @@ import ( "context" "reflect" "testing" - "time" + "testing/synctest" - "k8s.io/api/admissionregistration/v1" + v1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" ) -func TestGetMutatingWebhookConfig(t *testing.T) { +func TestGetMutatingWebhookConfig(t *testing.T) { synctest.Test(t, testGetMutatingWebhookConfig) } +func testGetMutatingWebhookConfig(t *testing.T) { // Build a test client that the admission plugin can use to look up the MutatingWebhookConfiguration client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, 0) @@ -40,6 +41,9 @@ func TestGetMutatingWebhookConfig(t *testing.T) { informerFactory.Start(stop) informerFactory.WaitForCacheSync(stop) + // Wait for background activity to settle. + synctest.Wait() + // no configurations if configurations := manager.Webhooks(); len(configurations) != 0 { t.Errorf("expected empty webhooks, but got %v", configurations) @@ -55,19 +59,10 @@ func TestGetMutatingWebhookConfig(t *testing.T) { MutatingWebhookConfigurations(). Create(context.TODO(), webhookConfiguration, metav1.CreateOptions{}) - // Wait up to 10s for the notification to be delivered. - // (on my system this takes < 2ms) - startTime := time.Now() + // Wait for the notification to be delivered. + synctest.Wait() configurations := manager.Webhooks() - for len(configurations) == 0 { - if time.Since(startTime) > 10*time.Second { - break - } - time.Sleep(time.Millisecond) - configurations = manager.Webhooks() - } - // verify presence if len(configurations) == 0 { t.Errorf("expected non empty webhooks") } @@ -212,72 +207,72 @@ func TestGetMutatingWebhookConfigSmartReload(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - client := fake.NewSimpleClientset() - informerFactory := informers.NewSharedInformerFactory(client, 0) - stop := make(chan struct{}) - defer close(stop) - manager := NewMutatingWebhookConfigurationManager(informerFactory) - managerStructPtr := manager.(*mutatingWebhookConfigurationManager) - fakeWebhookAccessorCreator := &mockCreateMutatingWebhookAccessor{} - managerStructPtr.createMutatingWebhookAccessor = fakeWebhookAccessorCreator.fn - informerFactory.Start(stop) - informerFactory.WaitForCacheSync(stop) + synctest.Test(t, func(t *testing.T) { + client := fake.NewSimpleClientset() + informerFactory := informers.NewSharedInformerFactory(client, 0) + stop := make(chan struct{}) + defer close(stop) + manager := NewMutatingWebhookConfigurationManager(informerFactory) + managerStructPtr := manager.(*mutatingWebhookConfigurationManager) + fakeWebhookAccessorCreator := &mockCreateMutatingWebhookAccessor{} + managerStructPtr.createMutatingWebhookAccessor = fakeWebhookAccessorCreator.fn + informerFactory.Start(stop) + informerFactory.WaitForCacheSync(stop) + + // Create webhooks + for _, configurations := range tt.args.createWebhookConfigurations { + client. + AdmissionregistrationV1(). + MutatingWebhookConfigurations(). + Create(context.TODO(), configurations, metav1.CreateOptions{}) + } + + synctest.Wait() + webhooks := manager.Webhooks() + if mutatingConfigurationTotalWebhooks(tt.args.createWebhookConfigurations) != len(webhooks) { + t.Errorf("Expected number of webhooks %d received %d", + mutatingConfigurationTotalWebhooks(tt.args.createWebhookConfigurations), + len(webhooks), + ) + } + // assert creations + if tt.numberOfCreations != fakeWebhookAccessorCreator.calledNTimes() { + t.Errorf( + "Expected number of creations %d received %d", + tt.numberOfCreations, fakeWebhookAccessorCreator.calledNTimes(), + ) + } - // Create webhooks - for _, configurations := range tt.args.createWebhookConfigurations { - client. - AdmissionregistrationV1(). - MutatingWebhookConfigurations(). - Create(context.TODO(), configurations, metav1.CreateOptions{}) - } - // TODO use channels to wait for manager.createMutatingWebhookAccessor - // to be called instead of using time.Sleep - time.Sleep(1 * time.Second) - webhooks := manager.Webhooks() - if mutatingConfigurationTotalWebhooks(tt.args.createWebhookConfigurations) != len(webhooks) { - t.Errorf("Expected number of webhooks %d received %d", - mutatingConfigurationTotalWebhooks(tt.args.createWebhookConfigurations), - len(webhooks), - ) - } - // assert creations - if tt.numberOfCreations != fakeWebhookAccessorCreator.calledNTimes() { - t.Errorf( - "Expected number of creations %d received %d", - tt.numberOfCreations, fakeWebhookAccessorCreator.calledNTimes(), - ) - } + // reset mock counter + fakeWebhookAccessorCreator.resetCounter() - // reset mock counter - fakeWebhookAccessorCreator.resetCounter() + // Update webhooks + for _, configurations := range tt.args.updateWebhookConfigurations { + client. + AdmissionregistrationV1(). + MutatingWebhookConfigurations(). + Update(context.TODO(), configurations, metav1.UpdateOptions{}) + } - // Update webhooks - for _, configurations := range tt.args.updateWebhookConfigurations { - client. - AdmissionregistrationV1(). - MutatingWebhookConfigurations(). - Update(context.TODO(), configurations, metav1.UpdateOptions{}) - } - // TODO use channels to wait for manager.createMutatingWebhookAccessor - // to be called instead of using time.Sleep - time.Sleep(1 * time.Second) - webhooks = manager.Webhooks() - if tt.finalNumberOfWebhookAccessors != len(webhooks) { - t.Errorf("Expected final number of webhooks %d received %d", - tt.finalNumberOfWebhookAccessors, - len(webhooks), - ) - } + synctest.Wait() + webhooks = manager.Webhooks() + if tt.finalNumberOfWebhookAccessors != len(webhooks) { + t.Errorf("Expected final number of webhooks %d received %d", + tt.finalNumberOfWebhookAccessors, + len(webhooks), + ) + } - // assert updates - if tt.numberOfRefreshes != fakeWebhookAccessorCreator.calledNTimes() { - t.Errorf( - "Expected number of refreshes %d received %d", - tt.numberOfRefreshes, fakeWebhookAccessorCreator.calledNTimes(), - ) - } - // reset mock counter for the next test cases - fakeWebhookAccessorCreator.resetCounter() + // assert updates + if tt.numberOfRefreshes != fakeWebhookAccessorCreator.calledNTimes() { + t.Errorf( + "Expected number of refreshes %d received %d", + tt.numberOfRefreshes, fakeWebhookAccessorCreator.calledNTimes(), + ) + } + // reset mock counter for the next test cases + fakeWebhookAccessorCreator.resetCounter() + }) }) } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go index 2f18f386bb435..9446f11b13e8d 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go @@ -20,16 +20,17 @@ import ( "context" "reflect" "testing" - "time" + "testing/synctest" - "k8s.io/api/admissionregistration/v1" + v1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" ) -func TestGetValidatingWebhookConfig(t *testing.T) { +func TestGetValidatingWebhookConfig(t *testing.T) { synctest.Test(t, testGetValidatingWebhookConfig) } +func testGetValidatingWebhookConfig(t *testing.T) { // Build a test client that the admission plugin can use to look up the ValidatingWebhookConfiguration client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, 0) @@ -41,6 +42,7 @@ func TestGetValidatingWebhookConfig(t *testing.T) { informerFactory.WaitForCacheSync(stop) // no configurations + synctest.Wait() if configurations := manager.Webhooks(); len(configurations) != 0 { t.Errorf("expected empty webhooks, but got %v", configurations) } @@ -55,17 +57,9 @@ func TestGetValidatingWebhookConfig(t *testing.T) { ValidatingWebhookConfigurations(). Create(context.TODO(), webhookConfiguration, metav1.CreateOptions{}) - // Wait up to 10s for the notification to be delivered. - // (on my system this takes < 2ms) - startTime := time.Now() + // Wait for the notification to be delivered. + synctest.Wait() configurations := manager.Webhooks() - for len(configurations) == 0 { - if time.Since(startTime) > 10*time.Second { - break - } - time.Sleep(time.Millisecond) - configurations = manager.Webhooks() - } // verify presence if len(configurations) == 0 { @@ -212,72 +206,71 @@ func TestGetValidatingWebhookConfigSmartReload(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - client := fake.NewSimpleClientset() - informerFactory := informers.NewSharedInformerFactory(client, 0) - stop := make(chan struct{}) - defer close(stop) - manager := NewValidatingWebhookConfigurationManager(informerFactory) - managerStructPtr := manager.(*validatingWebhookConfigurationManager) - fakeWebhookAccessorCreator := &mockCreateValidatingWebhookAccessor{} - managerStructPtr.createValidatingWebhookAccessor = fakeWebhookAccessorCreator.fn - informerFactory.Start(stop) - informerFactory.WaitForCacheSync(stop) + synctest.Test(t, func(t *testing.T) { + client := fake.NewSimpleClientset() + informerFactory := informers.NewSharedInformerFactory(client, 0) + stop := make(chan struct{}) + defer close(stop) + manager := NewValidatingWebhookConfigurationManager(informerFactory) + managerStructPtr := manager.(*validatingWebhookConfigurationManager) + fakeWebhookAccessorCreator := &mockCreateValidatingWebhookAccessor{} + managerStructPtr.createValidatingWebhookAccessor = fakeWebhookAccessorCreator.fn + informerFactory.Start(stop) + informerFactory.WaitForCacheSync(stop) + + // Create webhooks + for _, configurations := range tt.args.createWebhookConfigurations { + client. + AdmissionregistrationV1(). + ValidatingWebhookConfigurations(). + Create(context.TODO(), configurations, metav1.CreateOptions{}) + } - // Create webhooks - for _, configurations := range tt.args.createWebhookConfigurations { - client. - AdmissionregistrationV1(). - ValidatingWebhookConfigurations(). - Create(context.TODO(), configurations, metav1.CreateOptions{}) - } - // TODO use channels to wait for manager.createValidatingWebhookAccessor - // to be called instead of using time.Sleep - time.Sleep(1 * time.Second) - webhooks := manager.Webhooks() - if configurationTotalWebhooks(tt.args.createWebhookConfigurations) != len(webhooks) { - t.Errorf("Expected number of webhooks %d received %d", - configurationTotalWebhooks(tt.args.createWebhookConfigurations), - len(webhooks), - ) - } - // assert creations - if tt.numberOfCreations != fakeWebhookAccessorCreator.calledNTimes() { - t.Errorf( - "Expected number of creations %d received %d", - tt.numberOfCreations, fakeWebhookAccessorCreator.calledNTimes(), - ) - } + synctest.Wait() + webhooks := manager.Webhooks() + if configurationTotalWebhooks(tt.args.createWebhookConfigurations) != len(webhooks) { + t.Errorf("Expected number of webhooks %d received %d", + configurationTotalWebhooks(tt.args.createWebhookConfigurations), + len(webhooks), + ) + } + // assert creations + if tt.numberOfCreations != fakeWebhookAccessorCreator.calledNTimes() { + t.Errorf( + "Expected number of creations %d received %d", + tt.numberOfCreations, fakeWebhookAccessorCreator.calledNTimes(), + ) + } - // reset mock counter - fakeWebhookAccessorCreator.resetCounter() + // reset mock counter + fakeWebhookAccessorCreator.resetCounter() - // Update webhooks - for _, configurations := range tt.args.updateWebhookConfigurations { - client. - AdmissionregistrationV1(). - ValidatingWebhookConfigurations(). - Update(context.TODO(), configurations, metav1.UpdateOptions{}) - } - // TODO use channels to wait for manager.createValidatingWebhookAccessor - // to be called instead of using time.Sleep - time.Sleep(1 * time.Second) - webhooks = manager.Webhooks() - if tt.finalNumberOfWebhookAccessors != len(webhooks) { - t.Errorf("Expected final number of webhooks %d received %d", - tt.finalNumberOfWebhookAccessors, - len(webhooks), - ) - } + // Update webhooks + for _, configurations := range tt.args.updateWebhookConfigurations { + client. + AdmissionregistrationV1(). + ValidatingWebhookConfigurations(). + Update(context.TODO(), configurations, metav1.UpdateOptions{}) + } + synctest.Wait() + webhooks = manager.Webhooks() + if tt.finalNumberOfWebhookAccessors != len(webhooks) { + t.Errorf("Expected final number of webhooks %d received %d", + tt.finalNumberOfWebhookAccessors, + len(webhooks), + ) + } - // assert updates - if tt.numberOfRefreshes != fakeWebhookAccessorCreator.calledNTimes() { - t.Errorf( - "Expected number of refreshes %d received %d", - tt.numberOfRefreshes, fakeWebhookAccessorCreator.calledNTimes(), - ) - } - // reset mock counter for the next test cases - fakeWebhookAccessorCreator.resetCounter() + // assert updates + if tt.numberOfRefreshes != fakeWebhookAccessorCreator.calledNTimes() { + t.Errorf( + "Expected number of refreshes %d received %d", + tt.numberOfRefreshes, fakeWebhookAccessorCreator.calledNTimes(), + ) + } + // reset mock counter for the next test cases + fakeWebhookAccessorCreator.resetCounter() + }) }) } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/handler.go b/staging/src/k8s.io/apiserver/pkg/admission/handler.go index d2a9e7d4cf7c5..e498b78f6ba46 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/handler.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/handler.go @@ -34,7 +34,7 @@ type ReadyFunc func() bool // Handler is a base for admission control handlers that // support a predefined set of operations type Handler struct { - operations sets.String + operations sets.Set[string] readyFunc ReadyFunc } @@ -46,7 +46,7 @@ func (h *Handler) Handles(operation Operation) bool { // NewHandler creates a new base handler that handles the passed // in operations func NewHandler(ops ...Operation) *Handler { - operations := sets.NewString() + operations := sets.New[string]() for _, op := range ops { operations.Insert(string(op)) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/apiserver_id.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/apiserver_id.go new file mode 100644 index 0000000000000..b708d03ad8a69 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/apiserver_id.go @@ -0,0 +1,43 @@ +/* +Copyright 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 initializer + +import "k8s.io/apiserver/pkg/admission" + +// apiServerIDInitializer injects the API server identity into admission plugins +// that implement WantsAPIServerID. This is used for metrics labeling in HA setups +// where multiple API servers may be running with different manifest configurations. +type apiServerIDInitializer struct { + apiServerID string +} + +var _ admission.PluginInitializer = &apiServerIDInitializer{} + +// NewAPIServerIDInitializer returns a PluginInitializer that injects the given +// API server ID into admission plugins that implement WantsAPIServerID. +// This initializer must be placed before the generic initializer in the chain +// so that the API server ID is available when SetExternalKubeInformerFactory is called. +func NewAPIServerIDInitializer(apiServerID string) admission.PluginInitializer { + return &apiServerIDInitializer{apiServerID: apiServerID} +} + +// Initialize checks whether the plugin implements WantsAPIServerID and injects the ID. +func (i *apiServerIDInitializer) Initialize(plugin admission.Interface) { + if wants, ok := plugin.(WantsAPIServerID); ok { + wants.SetAPIServerID(i.apiServerID) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go index a66d62dff06ba..cd3da1a6195a0 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go @@ -17,6 +17,7 @@ limitations under the License. package initializer import ( + admissionregistrationv1api "k8s.io/api/admissionregistration/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" @@ -104,3 +105,41 @@ type WantsExcludedAdmissionResources interface { SetExcludedAdmissionResources(excludedAdmissionResources []schema.GroupResource) admission.InitializationValidator } + +// WantsAPIServerID defines a function which sets the API server ID for admission plugins +// that need it (e.g., for metrics labeling in HA setups). +type WantsAPIServerID interface { + SetAPIServerID(apiServerID string) + admission.InitializationValidator +} + +// ValidatingWebhookManifestLoadFunc loads ValidatingWebhookConfiguration manifests from a directory. +type ValidatingWebhookManifestLoadFunc func(dir string) ([]*admissionregistrationv1api.ValidatingWebhookConfiguration, string, error) + +// MutatingWebhookManifestLoadFunc loads MutatingWebhookConfiguration manifests from a directory. +type MutatingWebhookManifestLoadFunc func(dir string) ([]*admissionregistrationv1api.MutatingWebhookConfiguration, string, error) + +// ValidatingPolicyManifestLoadFunc loads ValidatingAdmissionPolicy manifests from a directory. +type ValidatingPolicyManifestLoadFunc func(dir string) ([]*admissionregistrationv1api.ValidatingAdmissionPolicy, []*admissionregistrationv1api.ValidatingAdmissionPolicyBinding, string, error) + +// MutatingPolicyManifestLoadFunc loads MutatingAdmissionPolicy manifests from a directory. +type MutatingPolicyManifestLoadFunc func(dir string) ([]*admissionregistrationv1api.MutatingAdmissionPolicy, []*admissionregistrationv1api.MutatingAdmissionPolicyBinding, string, error) + +// ManifestLoaders provides functions to load admission configurations from static manifest files +// with scheme-based defaulting and validation. +type ManifestLoaders struct { + // LoadValidatingWebhookManifests loads ValidatingWebhookConfiguration manifests. + LoadValidatingWebhookManifests ValidatingWebhookManifestLoadFunc + // LoadMutatingWebhookManifests loads MutatingWebhookConfiguration manifests. + LoadMutatingWebhookManifests MutatingWebhookManifestLoadFunc + // LoadValidatingPolicyManifests loads ValidatingAdmissionPolicy manifests. + LoadValidatingPolicyManifests ValidatingPolicyManifestLoadFunc + // LoadMutatingPolicyManifests loads MutatingAdmissionPolicy manifests. + LoadMutatingPolicyManifests MutatingPolicyManifestLoadFunc +} + +// WantsManifestLoaders is implemented by admission plugins that load configurations +// from static manifest files and need scheme-based defaulting and validation. +type WantsManifestLoaders interface { + SetManifestLoaders(loaders *ManifestLoaders) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go index bf8715a144218..d7456d090b3d8 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go @@ -34,7 +34,7 @@ import ( "k8s.io/apiserver/pkg/cel/lazy" ) -const VariablesTypeName = "kubernetes.variables" +const variablesTypeName = "kubernetes.variables" // CompositedCompiler compiles expressions with variable composition. type CompositedCompiler struct { @@ -42,7 +42,7 @@ type CompositedCompiler struct { ConditionCompiler MutatingCompiler - CompositionEnv *CompositionEnv + state *compositionState } // CompositedConditionEvaluator provides evaluation of a condition expression with variable composition. @@ -50,7 +50,7 @@ type CompositedCompiler struct { type CompositedConditionEvaluator struct { ConditionEvaluator - compositionEnv *CompositionEnv + state *compositionState } // CompositedEvaluator provides evaluation of a single expression with variable composition. @@ -58,32 +58,78 @@ type CompositedConditionEvaluator struct { type CompositedEvaluator struct { MutatingEvaluator - compositionEnv *CompositionEnv + state *compositionState } func NewCompositedCompiler(envSet *environment.EnvSet) (*CompositedCompiler, error) { - compositionContext, err := NewCompositionEnv(VariablesTypeName, envSet) + newMapType := apiservercel.NewObjectType(variablesTypeName, map[string]*apiservercel.DeclField{}) + + newEnvSet, err := envSet.Extend(environment.VersionedOptions{ + IntroducedVersion: version.MajorMinor(1, 0), + EnvOptions: []cel.EnvOption{ + cel.Variable("variables", newMapType.CelType()), + }, + DeclTypes: []*apiservercel.DeclType{ + newMapType, + }, + }) if err != nil { return nil, err } - return NewCompositedCompilerFromTemplate(compositionContext), nil -} -func NewCompositedCompilerFromTemplate(context *CompositionEnv) *CompositedCompiler { - context = &CompositionEnv{ - MapType: context.MapType, - EnvSet: context.EnvSet, - CompiledVariables: map[string]CompilationResult{}, + state := &compositionState{ + mapType: newMapType, + EnvSet: newEnvSet, + compiledVariables: map[string]CompilationResult{}, } - compiler := NewCompiler(context.EnvSet) + + compiler := NewCompiler(state.EnvSet) conditionCompiler := &conditionCompiler{compiler} mutation := &mutatingCompiler{compiler} return &CompositedCompiler{ Compiler: compiler, ConditionCompiler: conditionCompiler, MutatingCompiler: mutation, - CompositionEnv: context, + state: state, + }, nil +} + +// NewCompositedCompilerForTypeChecking creates a CompositedCompiler for type checking. +// It initializes the composition state but leaves the Compilers nil, as they are expected +// to be replaced by the caller (who is doing type checking). +func NewCompositedCompilerForTypeChecking(envSet *environment.EnvSet) (*CompositedCompiler, error) { + newMapType := apiservercel.NewObjectType(variablesTypeName, map[string]*apiservercel.DeclField{}) + + newEnvSet, err := envSet.Extend(environment.VersionedOptions{ + IntroducedVersion: version.MajorMinor(1, 0), + EnvOptions: []cel.EnvOption{ + cel.Variable("variables", newMapType.CelType()), + }, + DeclTypes: []*apiservercel.DeclType{ + newMapType, + }, + }) + if err != nil { + return nil, err } + + state := &compositionState{ + mapType: newMapType, + EnvSet: newEnvSet, + compiledVariables: map[string]CompilationResult{}, + } + return &CompositedCompiler{ + state: state, + }, nil +} + +// Env returns the CEL environment for the given mode. +func (c *CompositedCompiler) Env(mode environment.Type) (*cel.Env, error) { + return c.state.Env(mode) +} + +func (c *CompositedCompiler) CreateContext(parent context.Context) CompositionContext { + return c.state.CreateContext(parent) } func (c *CompositedCompiler) CompileAndStoreVariables(variables []NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) { @@ -94,8 +140,8 @@ func (c *CompositedCompiler) CompileAndStoreVariables(variables []NamedExpressio func (c *CompositedCompiler) CompileAndStoreVariable(variable NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult { result := c.Compiler.CompileCELExpression(variable, options, mode) - c.CompositionEnv.AddField(variable.GetName(), result.OutputType) - c.CompositionEnv.CompiledVariables[variable.GetName()] = result + c.state.AddField(variable.GetName(), result.OutputType) + c.state.compiledVariables[variable.GetName()] = result return result } @@ -103,7 +149,7 @@ func (c *CompositedCompiler) CompileCondition(expressions []ExpressionAccessor, condition := c.ConditionCompiler.CompileCondition(expressions, optionalDecls, envType) return &CompositedConditionEvaluator{ ConditionEvaluator: condition, - compositionEnv: c.CompositionEnv, + state: c.state, } } @@ -112,47 +158,25 @@ func (c *CompositedCompiler) CompileMutatingEvaluator(expression ExpressionAcces mutation := c.MutatingCompiler.CompileMutatingEvaluator(expression, optionalDecls, envType) return &CompositedEvaluator{ MutatingEvaluator: mutation, - compositionEnv: c.CompositionEnv, + state: c.state, } } -type CompositionEnv struct { +type compositionState struct { *environment.EnvSet - MapType *apiservercel.DeclType - CompiledVariables map[string]CompilationResult -} - -func (c *CompositionEnv) AddField(name string, celType *cel.Type) { - c.MapType.Fields[name] = apiservercel.NewDeclField(name, convertCelTypeToDeclType(celType), true, nil, nil) + mapType *apiservercel.DeclType + compiledVariables map[string]CompilationResult } -func NewCompositionEnv(typeName string, baseEnvSet *environment.EnvSet) (*CompositionEnv, error) { - declType := apiservercel.NewObjectType(typeName, map[string]*apiservercel.DeclField{}) - envSet, err := baseEnvSet.Extend(environment.VersionedOptions{ - // set to 1.0 because composition is one of the fundamental components - IntroducedVersion: version.MajorMinor(1, 0), - EnvOptions: []cel.EnvOption{ - cel.Variable("variables", declType.CelType()), - }, - DeclTypes: []*apiservercel.DeclType{ - declType, - }, - }) - if err != nil { - return nil, err - } - return &CompositionEnv{ - MapType: declType, - EnvSet: envSet, - CompiledVariables: map[string]CompilationResult{}, - }, nil +func (c *compositionState) AddField(name string, celType *cel.Type) { + c.mapType.Fields[name] = apiservercel.NewDeclField(name, convertCelTypeToDeclType(celType), true, nil, nil) } -func (c *CompositionEnv) CreateContext(parent context.Context) CompositionContext { +func (c *compositionState) CreateContext(parent context.Context) CompositionContext { return &compositionContext{ - Context: parent, - compositionEnv: c, + Context: parent, + state: c, } } @@ -165,13 +189,13 @@ type CompositionContext interface { type compositionContext struct { context.Context - compositionEnv *CompositionEnv + state *compositionState accumulatedCost int64 } func (c *compositionContext) Variables(activation any) ref.Val { - lazyMap := lazy.NewMapValue(c.compositionEnv.MapType) - for name, result := range c.compositionEnv.CompiledVariables { + lazyMap := lazy.NewMapValue(c.state.mapType) + for name, result := range c.state.compiledVariables { accessor := &variableAccessor{ name: name, result: result, @@ -184,7 +208,7 @@ func (c *compositionContext) Variables(activation any) ref.Val { } func (f *CompositedConditionEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) { - ctx = f.compositionEnv.CreateContext(ctx) + ctx = f.state.CreateContext(ctx) return f.ConditionEvaluator.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go index fb46f218503cb..45859f5c274fb 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/composition_test.go @@ -247,3 +247,46 @@ func TestCompositedPolicies(t *testing.T) { }) } } + +// TestCompilerIsolation verifies that each call to NewCompositedCompiler +// creates an isolated environment where variables from one compiler +// do not leak into another. +func TestCompilerIsolation(t *testing.T) { + baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()) + + // Create first compiler with variable "foo" + compiler1, err := NewCompositedCompiler(baseEnv) + if err != nil { + t.Fatal(err) + } + vars1 := []NamedExpressionAccessor{ + &testVariable{name: "foo", expression: "'bar'"}, + } + compiler1.CompileAndStoreVariables(vars1, OptionalVariableDeclarations{}, environment.StoredExpressions) + + // Create second compiler with variable "baz" + compiler2, err := NewCompositedCompiler(baseEnv) + if err != nil { + t.Fatal(err) + } + vars2 := []NamedExpressionAccessor{ + &testVariable{name: "baz", expression: "'qux'"}, + } + compiler2.CompileAndStoreVariables(vars2, OptionalVariableDeclarations{}, environment.StoredExpressions) + + // Verify isolation: compiler1 should not have "baz", compiler2 should not have "foo" + if _, ok := compiler1.state.mapType.Fields["baz"]; ok { + t.Error("compiler1 should not have variable 'baz' from compiler2") + } + if _, ok := compiler2.state.mapType.Fields["foo"]; ok { + t.Error("compiler2 should not have variable 'foo' from compiler1") + } + + // Verify each compiler has its own variable + if _, ok := compiler1.state.mapType.Fields["foo"]; !ok { + t.Error("compiler1 should have variable 'foo'") + } + if _, ok := compiler2.state.mapType.Fields["baz"]; !ok { + t.Error("compiler2 should have variable 'baz'") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/condition_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/condition_test.go index e9a851ad9e493..5cf898a90b650 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/condition_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/condition_test.go @@ -803,7 +803,7 @@ func TestCondition(t *testing.T) { attributes: newValidAttribute(nil, false), results: []EvaluationResult{ { - Error: errors.New(fmt.Sprintf("operation cancelled: actual cost limit exceeded")), + Error: fmt.Errorf("operation cancelled: actual cost limit exceeded"), }, }, hasParamKind: true, @@ -1573,6 +1573,8 @@ func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) panic(fmt.Sprintf("unsupported type: %T", a)) } + // Compare AttributesRecord structs - contains error fields but we're comparing the struct, not handling errors + //nolint:all if reflect.DeepEqual(f.match.match, *other) { return f.match.decision, f.match.reason, f.match.err } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/loader.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/loader.go new file mode 100644 index 0000000000000..bb160fc10bd96 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/loader.go @@ -0,0 +1,120 @@ +/* +Copyright 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 manifest provides shared utilities for loading admission configurations +// from static manifest files. +package manifest + +import ( + "bufio" + "bytes" + "crypto/sha256" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + utilyaml "k8s.io/apimachinery/pkg/util/yaml" +) + +// splitYAMLDocuments splits a multi-document YAML byte slice into individual documents. +// Empty documents are skipped. +func splitYAMLDocuments(data []byte) ([][]byte, error) { + reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(data))) + var docs [][]byte + for { + doc, err := reader.Read() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, err + } + doc = bytes.TrimSpace(doc) + if len(doc) == 0 { + continue + } + docs = append(docs, doc) + } + return docs, nil +} + +// FileDoc holds a decoded YAML document and the file it came from. +type FileDoc struct { + FilePath string + Doc []byte +} + +// LoadFiles reads all YAML/JSON files from dir, splits multi-document YAML, +// and returns individual documents with their source file paths plus a +// sha256-prefixed hash of the file contents for change detection. +// Files are processed in alphabetical order for deterministic behavior. +func LoadFiles(dir string) ([]FileDoc, string, error) { + if len(dir) == 0 { + return nil, "", fmt.Errorf("manifest directory path is empty") + } + + // os.ReadDir returns entries sorted by filename. + entries, err := os.ReadDir(dir) + if err != nil { + return nil, "", fmt.Errorf("failed to read manifest directory %q: %w", dir, err) + } + + var fileDocs []FileDoc + h := sha256.New() + hasData := false + + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + ext := strings.ToLower(filepath.Ext(name)) + if ext != ".yaml" && ext != ".yml" && ext != ".json" { + continue + } + + filePath := filepath.Join(dir, name) + data, err := os.ReadFile(filePath) + if err != nil { + return nil, "", fmt.Errorf("failed to read file %q: %w", filePath, err) + } + if len(data) == 0 { + continue + } + + h.Write(data) + h.Write([]byte{0}) + hasData = true + + docs, err := splitYAMLDocuments(data) + if err != nil { + return nil, "", fmt.Errorf("failed to split YAML documents in file %q: %w", filePath, err) + } + for _, doc := range docs { + fileDocs = append(fileDocs, FileDoc{FilePath: filePath, Doc: doc}) + } + } + + var hash string + if hasData { + hash = fmt.Sprintf("sha256:%x", h.Sum(nil)) + } + + return fileDocs, hash, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/loader_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/loader_test.go new file mode 100644 index 0000000000000..8c81724982d71 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/loader_test.go @@ -0,0 +1,281 @@ +/* +Copyright 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 manifest + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func Test_splitYAMLDocuments(t *testing.T) { + tests := []struct { + name string + input string + wantCount int + wantErr bool + // wantContains checks that each returned doc contains the expected substring + wantContains []string + }{ + { + name: "empty input", + input: "", + wantCount: 0, + }, + { + name: "only whitespace", + input: " \n\n \n", + wantCount: 0, + }, + { + name: "single YAML webhook configuration", + input: `apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test-webhook.static.k8s.io +webhooks: +- name: test.example.com + clientConfig: + url: "https://example.com/validate" +`, + wantCount: 1, + wantContains: []string{"ValidatingWebhookConfiguration"}, + }, + { + name: "multi-document YAML with policy and binding", + input: `apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: deny-privileged.static.k8s.io +spec: + failurePolicy: Fail +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: deny-privileged-binding.static.k8s.io +spec: + policyName: deny-privileged.static.k8s.io +`, + wantCount: 2, + wantContains: []string{"ValidatingAdmissionPolicy", "ValidatingAdmissionPolicyBinding"}, + }, + { + name: "single JSON document", + input: `{"apiVersion":"admissionregistration.k8s.io/v1","kind":"ValidatingWebhookConfiguration","metadata":{"name":"test.static.k8s.io"}}`, + wantCount: 1, + wantContains: []string{"ValidatingWebhookConfiguration"}, + }, + { + name: "empty document between separators is skipped", + input: `a: 1 +--- +--- +b: 2 +`, + wantCount: 2, + }, + { + name: "leading separator", + input: `--- +apiVersion: v1 +kind: ConfigMap +`, + wantCount: 1, + }, + { + name: "trailing separator", + input: `apiVersion: v1 +kind: ConfigMap +--- +`, + wantCount: 1, + }, + { + name: "three documents", + input: `kind: A +--- +kind: B +--- +kind: C +`, + wantCount: 3, + wantContains: []string{"A", "B", "C"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + docs, err := splitYAMLDocuments([]byte(tc.input)) + if (err != nil) != tc.wantErr { + t.Fatalf("splitYAMLDocuments() error = %v, wantErr %v", err, tc.wantErr) + } + if len(docs) != tc.wantCount { + t.Fatalf("splitYAMLDocuments() returned %d documents, want %d", len(docs), tc.wantCount) + } + // Verify no empty documents were returned + for i, doc := range docs { + trimmed := strings.TrimSpace(string(doc)) + if trimmed == "" { + t.Errorf("document %d is empty after trim", i) + } + } + // Verify expected content + for i, want := range tc.wantContains { + if i >= len(docs) { + break + } + if !strings.Contains(string(docs[i]), want) { + t.Errorf("document %d: expected to contain %q, got %q", i, want, string(docs[i])) + } + } + }) + } +} + +func TestLoadFiles(t *testing.T) { + t.Run("empty dir returns no docs", func(t *testing.T) { + dir := t.TempDir() + docs, hash, err := LoadFiles(dir) + if err != nil { + t.Fatal(err) + } + if len(docs) != 0 { + t.Errorf("expected 0 docs, got %d", len(docs)) + } + if hash != "" { + t.Errorf("expected empty hash for empty dir, got %q", hash) + } + }) + + t.Run("reads yaml and json files", func(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "a.yaml"), []byte("kind: A"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "b.json"), []byte(`{"kind":"B"}`), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "c.yml"), []byte("kind: C"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "d.txt"), []byte("ignored"), 0644); err != nil { + t.Fatal(err) + } + + docs, hash, err := LoadFiles(dir) + if err != nil { + t.Fatal(err) + } + if len(docs) != 3 { + t.Fatalf("expected 3 docs (.yaml, .json, .yml), got %d", len(docs)) + } + if len(hash) == 0 { + t.Error("expected non-empty hash") + } + }) + + t.Run("splits multi-document yaml", func(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "multi.yaml"), []byte("a: 1\n---\nb: 2\n"), 0644); err != nil { + t.Fatal(err) + } + + docs, _, err := LoadFiles(dir) + if err != nil { + t.Fatal(err) + } + if len(docs) != 2 { + t.Fatalf("expected 2 docs, got %d", len(docs)) + } + }) + + t.Run("alphabetical order", func(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "02-second.yaml"), []byte("name: second"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "01-first.yaml"), []byte("name: first"), 0644); err != nil { + t.Fatal(err) + } + + docs, _, err := LoadFiles(dir) + if err != nil { + t.Fatal(err) + } + if len(docs) != 2 { + t.Fatalf("expected 2 docs, got %d", len(docs)) + } + if !strings.Contains(string(docs[0].Doc), "first") { + t.Errorf("first doc should be from 01-first.yaml, got %q", string(docs[0].Doc)) + } + }) + + t.Run("empty path returns error", func(t *testing.T) { + _, _, err := LoadFiles("") + if err == nil { + t.Error("expected error for empty path") + } + }) + + t.Run("nonexistent dir returns error", func(t *testing.T) { + _, _, err := LoadFiles("/nonexistent/path") + if err == nil { + t.Error("expected error for nonexistent dir") + } + }) + + t.Run("skips subdirectories", func(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "a.yaml"), []byte("kind: A"), 0644); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(dir, "subdir"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "subdir", "b.yaml"), []byte("kind: B"), 0644); err != nil { + t.Fatal(err) + } + + docs, _, err := LoadFiles(dir) + if err != nil { + t.Fatal(err) + } + if len(docs) != 1 { + t.Fatalf("expected 1 doc (subdir skipped), got %d", len(docs)) + } + }) + + t.Run("skips empty files", func(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "empty.yaml"), []byte(""), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "valid.yaml"), []byte("kind: A"), 0644); err != nil { + t.Fatal(err) + } + + docs, _, err := LoadFiles(dir) + if err != nil { + t.Fatal(err) + } + if len(docs) != 1 { + t.Fatalf("expected 1 doc (empty skipped), got %d", len(docs)) + } + }) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/metrics/metrics.go new file mode 100644 index 0000000000000..efdf2fe21e58f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/metrics/metrics.go @@ -0,0 +1,161 @@ +/* +Copyright 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 metrics provides metrics for manifest-based admission configuration. +package metrics + +import ( + "crypto/sha256" + "fmt" + "sync" + + "k8s.io/apiserver/pkg/util/configmetrics" + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const ( + namespace = "apiserver" + subsystem = "manifest_admission_config_controller" +) + +var ( + admissionManifestAutomaticReloadsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "automatic_reloads_total", + Help: "Total number of automatic reloads of admission manifest configuration split by status, plugin, and apiserver identity.", + StabilityLevel: metrics.ALPHA, + }, + []string{"status", "plugin", "apiserver_id_hash"}, + ) + + admissionManifestAutomaticReloadLastTimestampSeconds = metrics.NewGaugeVec( + &metrics.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "automatic_reload_last_timestamp_seconds", + Help: "Timestamp of the last automatic reload of admission manifest configuration split by status, plugin, and apiserver identity.", + StabilityLevel: metrics.ALPHA, + }, + []string{"status", "plugin", "apiserver_id_hash"}, + ) + + admissionManifestLastConfigInfo = metrics.NewDesc( + metrics.BuildFQName(namespace, subsystem, "last_config_info"), + "Information about the last applied admission manifest configuration with hash as label, split by plugin and apiserver identity.", + []string{"plugin", "apiserver_id_hash", "hash"}, + nil, + metrics.ALPHA, + "", + ) +) + +// ManifestType represents the admission plugin name for which manifests are being tracked. +type ManifestType string + +const ( + // ValidatingWebhookManifestType represents validating webhook configurations. + ValidatingWebhookManifestType ManifestType = "ValidatingAdmissionWebhook" + // MutatingWebhookManifestType represents mutating webhook configurations. + MutatingWebhookManifestType ManifestType = "MutatingAdmissionWebhook" + // VAPManifestType represents validating admission policy configurations. + VAPManifestType ManifestType = "ValidatingAdmissionPolicy" + // MAPManifestType represents mutating admission policy configurations. + MAPManifestType ManifestType = "MutatingAdmissionPolicy" +) + +var registerMetrics sync.Once + +// configHashProviders stores providers per plugin for config info metrics. +// Each plugin has its own provider that stores [apiserver_id_hash, config_hash]. +var configHashProviders = map[ManifestType]*configmetrics.AtomicHashProvider{ + ValidatingWebhookManifestType: configmetrics.NewAtomicHashProvider(), + MutatingWebhookManifestType: configmetrics.NewAtomicHashProvider(), + VAPManifestType: configmetrics.NewAtomicHashProvider(), + MAPManifestType: configmetrics.NewAtomicHashProvider(), +} + +// multiTypeConfigInfoCollector emits config info metrics for multiple manifest types. +type multiTypeConfigInfoCollector struct { + metrics.BaseStableCollector + desc *metrics.Desc +} + +var _ metrics.StableCollector = &multiTypeConfigInfoCollector{} + +func (c *multiTypeConfigInfoCollector) DescribeWithStability(ch chan<- *metrics.Desc) { + ch <- c.desc +} + +func (c *multiTypeConfigInfoCollector) CollectWithStability(ch chan<- metrics.Metric) { + // Emit a metric for each plugin that has data + for manifestType, provider := range configHashProviders { + hashes := provider.GetCurrentHashes() + if len(hashes) >= 2 && len(hashes[0]) > 0 { + // hashes contains [apiserver_id_hash, config_hash] + // Prepend the plugin to match label order: ["plugin", "apiserver_id_hash", "hash"] + labelValues := append([]string{string(manifestType)}, hashes...) + ch <- metrics.NewLazyConstMetric(c.desc, metrics.GaugeValue, 1, labelValues...) + } + } +} + +func RegisterMetrics() { + registerMetrics.Do(func() { + legacyregistry.MustRegister(admissionManifestAutomaticReloadsTotal) + legacyregistry.MustRegister(admissionManifestAutomaticReloadLastTimestampSeconds) + // Use a custom collector that emits metrics for all manifest types + legacyregistry.CustomMustRegister(&multiTypeConfigInfoCollector{desc: admissionManifestLastConfigInfo}) + }) +} + +func ResetMetricsForTest() { + admissionManifestAutomaticReloadsTotal.Reset() + admissionManifestAutomaticReloadLastTimestampSeconds.Reset() +} + +// RecordAutomaticReloadFailure records a failed reload attempt for the given manifest type. +func RecordAutomaticReloadFailure(manifestType ManifestType, apiServerID string) { + apiServerIDHash := getHash(apiServerID) + admissionManifestAutomaticReloadsTotal.WithLabelValues("failure", string(manifestType), apiServerIDHash).Inc() + admissionManifestAutomaticReloadLastTimestampSeconds.WithLabelValues("failure", string(manifestType), apiServerIDHash).SetToCurrentTime() +} + +// RecordAutomaticReloadSuccess records a successful reload for the given manifest type. +func RecordAutomaticReloadSuccess(manifestType ManifestType, apiServerID, configHash string) { + apiServerIDHash := getHash(apiServerID) + admissionManifestAutomaticReloadsTotal.WithLabelValues("success", string(manifestType), apiServerIDHash).Inc() + admissionManifestAutomaticReloadLastTimestampSeconds.WithLabelValues("success", string(manifestType), apiServerIDHash).SetToCurrentTime() + + RecordLastConfigInfo(manifestType, apiServerID, configHash) +} + +// RecordLastConfigInfo records the hash of the last successfully loaded configuration. +func RecordLastConfigInfo(manifestType ManifestType, apiServerID, configHash string) { + if provider, ok := configHashProviders[manifestType]; ok { + // Store [apiserver_id_hash, config_hash] - the multiTypeConfigInfoCollector prepends the type + provider.SetHashes(getHash(apiServerID), configHash) + } +} + +func getHash(data string) string { + if len(data) == 0 { + return "" + } + return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(data))) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/metrics/metrics_test.go new file mode 100644 index 0000000000000..7dba8000bb98f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/metrics/metrics_test.go @@ -0,0 +1,247 @@ +/* +Copyright 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 metrics + +import ( + "strings" + "testing" + + "k8s.io/component-base/metrics" +) + +func TestGetHash(t *testing.T) { + tests := []struct { + name string + input string + want string // empty means expect empty return + }{ + { + name: "empty string returns empty", + input: "", + want: "", + }, + { + name: "non-empty string returns sha256 prefix", + input: "test-data", + }, + { + name: "different inputs produce different hashes", + input: "other-data", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := getHash(tc.input) + if tc.want == "" && tc.input == "" { + if result != "" { + t.Errorf("getHash(%q) = %q, want empty", tc.input, result) + } + return + } + if !strings.HasPrefix(result, "sha256:") { + t.Errorf("getHash(%q) = %q, want sha256: prefix", tc.input, result) + } + }) + } + + // Verify determinism + hash1 := getHash("same-input") + hash2 := getHash("same-input") + if hash1 != hash2 { + t.Errorf("getHash is not deterministic: %q != %q", hash1, hash2) + } + + // Verify different inputs produce different hashes + hashA := getHash("input-a") + hashB := getHash("input-b") + if hashA == hashB { + t.Errorf("different inputs produced same hash: %q", hashA) + } +} + +func TestRecordAutomaticReloadSuccess(t *testing.T) { + RegisterMetrics() + ResetMetricsForTest() + + RecordAutomaticReloadSuccess(ValidatingWebhookManifestType, "test-server-id", "test-config-data") + + // Verify config info was recorded + provider := configHashProviders[ValidatingWebhookManifestType] + hashes := provider.GetCurrentHashes() + if len(hashes) < 2 { + t.Fatalf("expected at least 2 hashes, got %d", len(hashes)) + } + if hashes[0] == "" { + t.Error("expected non-empty apiserver ID hash") + } + if hashes[1] == "" { + t.Error("expected non-empty config hash") + } +} + +func TestRecordAutomaticReloadFailure(t *testing.T) { + RegisterMetrics() + ResetMetricsForTest() + + // Should not panic + RecordAutomaticReloadFailure(MutatingWebhookManifestType, "test-server-id") +} + +func TestRecordLastConfigInfo(t *testing.T) { + RegisterMetrics() + + tests := []struct { + name string + manifestType ManifestType + apiServerID string + configData string + }{ + { + name: "validating webhook", + manifestType: ValidatingWebhookManifestType, + apiServerID: "server-1", + configData: "config-1", + }, + { + name: "mutating webhook", + manifestType: MutatingWebhookManifestType, + apiServerID: "server-2", + configData: "config-2", + }, + { + name: "VAP", + manifestType: VAPManifestType, + apiServerID: "server-3", + configData: "config-3", + }, + { + name: "MAP", + manifestType: MAPManifestType, + apiServerID: "server-4", + configData: "config-4", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + RecordLastConfigInfo(tc.manifestType, tc.apiServerID, tc.configData) + + provider := configHashProviders[tc.manifestType] + hashes := provider.GetCurrentHashes() + if len(hashes) < 2 { + t.Fatalf("expected at least 2 hashes, got %d", len(hashes)) + } + expectedAPIHash := getHash(tc.apiServerID) + if hashes[0] != expectedAPIHash { + t.Errorf("apiserver hash = %q, want %q", hashes[0], expectedAPIHash) + } + // configData is already a hash, stored directly + if hashes[1] != tc.configData { + t.Errorf("config hash = %q, want %q", hashes[1], tc.configData) + } + }) + } +} + +func TestRecordLastConfigInfoUnknownType(t *testing.T) { + // Should not panic for unknown manifest type + RecordLastConfigInfo(ManifestType("Unknown"), "server", "data") +} + +func TestMultiTypeConfigInfoCollectorDescribe(t *testing.T) { + collector := &multiTypeConfigInfoCollector{desc: admissionManifestLastConfigInfo} + ch := make(chan *metrics.Desc, 1) + collector.DescribeWithStability(ch) + close(ch) + + var descs []*metrics.Desc + for d := range ch { + descs = append(descs, d) + } + if len(descs) != 1 { + t.Fatalf("expected 1 descriptor, got %d", len(descs)) + } + if descs[0] != admissionManifestLastConfigInfo { + t.Error("descriptor does not match admissionManifestLastConfigInfo") + } +} + +func TestMultiTypeConfigInfoCollectorCollectEmpty(t *testing.T) { + // Reset all providers to empty state + for _, provider := range configHashProviders { + provider.SetHashes() + } + + collector := &multiTypeConfigInfoCollector{desc: admissionManifestLastConfigInfo} + ch := make(chan metrics.Metric, 10) + collector.CollectWithStability(ch) + close(ch) + + var collected []metrics.Metric + for m := range ch { + collected = append(collected, m) + } + if len(collected) != 0 { + t.Errorf("expected 0 metrics when no hashes are set, got %d", len(collected)) + } +} + +func TestMultiTypeConfigInfoCollectorCollectWithData(t *testing.T) { + // Reset all providers + for _, provider := range configHashProviders { + provider.SetHashes() + } + + // Set data for two types + RecordLastConfigInfo(ValidatingWebhookManifestType, "server-a", "config-a") + RecordLastConfigInfo(VAPManifestType, "server-b", "config-b") + + collector := &multiTypeConfigInfoCollector{desc: admissionManifestLastConfigInfo} + ch := make(chan metrics.Metric, 10) + collector.CollectWithStability(ch) + close(ch) + + var collected []metrics.Metric + for m := range ch { + collected = append(collected, m) + } + if len(collected) != 2 { + t.Fatalf("expected 2 metrics for 2 types with data, got %d", len(collected)) + } +} + +func TestMultiTypeConfigInfoCollectorCollectAllTypes(t *testing.T) { + // Set data for all four types + RecordLastConfigInfo(ValidatingWebhookManifestType, "s1", "c1") + RecordLastConfigInfo(MutatingWebhookManifestType, "s2", "c2") + RecordLastConfigInfo(VAPManifestType, "s3", "c3") + RecordLastConfigInfo(MAPManifestType, "s4", "c4") + + collector := &multiTypeConfigInfoCollector{desc: admissionManifestLastConfigInfo} + ch := make(chan metrics.Metric, 10) + collector.CollectWithStability(ch) + close(ch) + + var collected []metrics.Metric + for m := range ch { + collected = append(collected, m) + } + if len(collected) != 4 { + t.Fatalf("expected 4 metrics for all types, got %d", len(collected)) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation.go new file mode 100644 index 0000000000000..9effc544f143d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation.go @@ -0,0 +1,74 @@ +/* +Copyright 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 manifest provides shared utilities for loading admission configurations +// from static manifest files. +package manifest + +import ( + "fmt" + "os" + "path" + "strings" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" +) + +// StaticConfigSuffix is the reserved suffix for manifest-based admission configurations. +// Resources with names ending in this suffix can only be created via static manifest +// files loaded at API server startup, not through the REST API. +// NOTE: This constant is duplicated in pkg/apis/admissionregistration/validation/static_suffix.go +// because that package cannot import from staging. Keep both in sync. +const StaticConfigSuffix = ".static.k8s.io" + +// ValidateStaticManifestsDir validates the staticManifestsDir config field. +// It checks the feature gate is enabled, the path is absolute, exists, and is a directory. +func ValidateStaticManifestsDir(staticManifestsDir string) error { + if len(staticManifestsDir) > 0 { + if !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return field.Forbidden(field.NewPath("staticManifestsDir"), "staticManifestsDir requires the ManifestBasedAdmissionControlConfig feature gate to be enabled") + } + if !path.IsAbs(staticManifestsDir) { + return field.Invalid(field.NewPath("staticManifestsDir"), staticManifestsDir, "must be an absolute file path") + } + info, err := os.Stat(staticManifestsDir) + if err != nil { + return field.Invalid(field.NewPath("staticManifestsDir"), staticManifestsDir, fmt.Sprintf("unable to read: %v", err)) + } + if !info.IsDir() { + return field.Invalid(field.NewPath("staticManifestsDir"), staticManifestsDir, "must be a directory") + } + } + return nil +} + +// ValidateManifestName checks that the object name is non-empty, has the required +// .static.k8s.io suffix, and is unique within the manifest set. +func ValidateManifestName(name, filePath string, seenNames map[string]string) error { + if len(name) == 0 { + return fmt.Errorf("resource in file %q must have a name", filePath) + } + if !strings.HasSuffix(name, StaticConfigSuffix) { + return fmt.Errorf("%q in file %q must have a name ending with %q", name, filePath, StaticConfigSuffix) + } + if prevFile, ok := seenNames[name]; ok { + return fmt.Errorf("duplicate name %q found in file %q (previously seen in %q)", name, filePath, prevFile) + } + seenNames[name] = filePath + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation_test.go new file mode 100644 index 0000000000000..637690c3131d8 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/manifest/validation_test.go @@ -0,0 +1,156 @@ +/* +Copyright 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 manifest + +import ( + "os" + "path" + "strings" + "testing" + + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" +) + +func TestValidateStaticManifestsDir(t *testing.T) { + validDir := t.TempDir() + notADirFile := path.Join(t.TempDir(), "file.txt") + if err := os.WriteFile(notADirFile, []byte(""), 0644); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + dir string + enableFeatureGate bool + wantErr string + }{ + { + name: "empty dir is valid", + dir: "", + enableFeatureGate: true, + }, + { + name: "valid directory", + dir: validDir, + enableFeatureGate: true, + }, + { + name: "feature gate disabled", + dir: "/some/path", + enableFeatureGate: false, + wantErr: "Forbidden", + }, + { + name: "relative path", + dir: "relative/path", + enableFeatureGate: true, + wantErr: "must be an absolute file path", + }, + { + name: "nonexistent path", + dir: "/nonexistent/path", + enableFeatureGate: true, + wantErr: "unable to read", + }, + { + name: "not a directory", + dir: notADirFile, + enableFeatureGate: true, + wantErr: "must be a directory", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, tc.enableFeatureGate) + err := ValidateStaticManifestsDir(tc.dir) + if len(tc.wantErr) > 0 { + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("expected error containing %q, got: %v", tc.wantErr, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func TestValidateManifestName(t *testing.T) { + tests := []struct { + name string + objName string + filePath string + seenNames map[string]string + wantErr bool + errContains string + }{ + { + name: "valid name", + objName: "test.static.k8s.io", + filePath: "test.yaml", + seenNames: map[string]string{}, + }, + { + name: "empty name", + objName: "", + filePath: "test.yaml", + seenNames: map[string]string{}, + wantErr: true, + errContains: "must have a name", + }, + { + name: "missing suffix", + objName: "no-suffix", + filePath: "test.yaml", + seenNames: map[string]string{}, + wantErr: true, + errContains: "must have a name ending with", + }, + { + name: "duplicate name", + objName: "dup.static.k8s.io", + filePath: "second.yaml", + seenNames: map[string]string{"dup.static.k8s.io": "first.yaml"}, + wantErr: true, + errContains: "duplicate", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateManifestName(tt.objName, tt.filePath, tt.seenNames) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("error %q does not contain %q", err.Error(), tt.errContains) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go index cec6769c65dfc..19958c1137c3e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go @@ -54,7 +54,7 @@ const ( // Register registers a plugin func Register(plugins *admission.Plugins) { plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { - return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic, + return NewLifecycle(sets.New[string](metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic, // user specified configuration that cannot be rebuilt "openshift-config", // cluster generated configuration that cannot be rebuilt (etcd encryption keys) @@ -72,7 +72,7 @@ func Register(plugins *admission.Plugins) { type Lifecycle struct { *admission.Handler client kubernetes.Interface - immortalNamespaces sets.String + immortalNamespaces sets.Set[string] namespaceLister corelisters.NamespaceLister // forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache. // if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server. @@ -195,11 +195,11 @@ func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admissi } // NewLifecycle creates a new namespace Lifecycle admission control handler -func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) { +func NewLifecycle(immortalNamespaces sets.Set[string]) (*Lifecycle, error) { return newLifecycleWithClock(immortalNamespaces, clock.RealClock{}) } -func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) { +func newLifecycleWithClock(immortalNamespaces sets.Set[string], clock utilcache.Clock) (*Lifecycle, error) { forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock) return &Lifecycle{ Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete), diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go index ea6a10218d64d..debb61e2b83aa 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go @@ -50,7 +50,7 @@ func newHandlerForTest(c clientset.Interface) (*Lifecycle, informers.SharedInfor // newHandlerForTestWithClock returns a configured handler for testing. func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (*Lifecycle, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) - handler, err := newLifecycleWithClock(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem), cacheClock) + handler, err := newLifecycleWithClock(sets.New[string](metav1.NamespaceDefault, metav1.NamespaceSystem), cacheClock) if err != nil { return nil, f, err } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/doc.go new file mode 100644 index 0000000000000..03728eda4ee4e --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package + +// Package policyconfig defines the configuration for the validating and mutating admission policy plugins. +package policyconfig // import "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/install/install.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/install/install.go new file mode 100644 index 0000000000000..e0925752c1da0 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/install/install.go @@ -0,0 +1,33 @@ +/* +Copyright 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 install installs the policyconfig API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig" + v1 "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1" +) + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(policyconfig.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) + utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion)) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/register.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/register.go new file mode 100644 index 0000000000000..11976152cd516 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/register.go @@ -0,0 +1,51 @@ +/* +Copyright 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 policyconfig + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// GroupName is the group name use in this package +const GroupName = "apiserver.config.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ValidatingAdmissionPolicyConfiguration{}, + &MutatingAdmissionPolicyConfiguration{}, + ) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/types.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/types.go new file mode 100644 index 0000000000000..9af51012224e8 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/types.go @@ -0,0 +1,63 @@ +/* +Copyright 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 policyconfig + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ValidatingAdmissionPolicyConfiguration provides configuration for the validating admission policy controller. +type ValidatingAdmissionPolicyConfiguration struct { + metav1.TypeMeta + + // StaticManifestsDir is the path to a directory containing static + // ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding + // resources to be loaded at startup. Files with extensions .yaml, + // .yml, and .json are read. Only admissionregistration.k8s.io/v1 + // resources are supported. + // Using this field requires the ManifestBasedAdmissionControlConfig + // feature gate to be enabled. + // +optional + StaticManifestsDir string +} + +// GetStaticManifestsDir returns the static manifests directory path. +func (c *ValidatingAdmissionPolicyConfiguration) GetStaticManifestsDir() string { + return c.StaticManifestsDir +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MutatingAdmissionPolicyConfiguration provides configuration for the mutating admission policy controller. +type MutatingAdmissionPolicyConfiguration struct { + metav1.TypeMeta + + // StaticManifestsDir is the path to a directory containing static + // MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding + // resources to be loaded at startup. Files with extensions .yaml, + // .yml, and .json are read. Only admissionregistration.k8s.io/v1 + // resources are supported. + // Using this field requires the ManifestBasedAdmissionControlConfig + // feature gate to be enabled. + // +optional + StaticManifestsDir string +} + +// GetStaticManifestsDir returns the static manifests directory path. +func (c *MutatingAdmissionPolicyConfiguration) GetStaticManifestsDir() string { + return c.StaticManifestsDir +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/doc.go new file mode 100644 index 0000000000000..52837293d51fc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:conversion-gen=k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig + +// Package v1 is the v1 version of the API. +package v1 // import "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/register.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/register.go new file mode 100644 index 0000000000000..f9b3d1176b393 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/register.go @@ -0,0 +1,51 @@ +/* +Copyright 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "apiserver.config.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ValidatingAdmissionPolicyConfiguration{}, + &MutatingAdmissionPolicyConfiguration{}, + ) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/types.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/types.go new file mode 100644 index 0000000000000..967b6ca3f9674 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/types.go @@ -0,0 +1,53 @@ +/* +Copyright 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 v1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ValidatingAdmissionPolicyConfiguration provides configuration for the validating admission policy controller. +type ValidatingAdmissionPolicyConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // StaticManifestsDir is the path to a directory containing static + // ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding + // resources to be loaded at startup. Files with extensions .yaml, + // .yml, and .json are read. Only admissionregistration.k8s.io/v1 + // resources are supported. + // Using this field requires the ManifestBasedAdmissionControlConfig + // feature gate to be enabled. + // +optional + StaticManifestsDir string `json:"staticManifestsDir,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MutatingAdmissionPolicyConfiguration provides configuration for the mutating admission policy controller. +type MutatingAdmissionPolicyConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // StaticManifestsDir is the path to a directory containing static + // MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding + // resources to be loaded at startup. Files with extensions .yaml, + // .yml, and .json are read. Only admissionregistration.k8s.io/v1 + // resources are supported. + // Using this field requires the ManifestBasedAdmissionControlConfig + // feature gate to be enabled. + // +optional + StaticManifestsDir string `json:"staticManifestsDir,omitempty"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/zz_generated.conversion.go new file mode 100644 index 0000000000000..e65c4e52f19ff --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/zz_generated.conversion.go @@ -0,0 +1,98 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1 + +import ( + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + policyconfig "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*MutatingAdmissionPolicyConfiguration)(nil), (*policyconfig.MutatingAdmissionPolicyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MutatingAdmissionPolicyConfiguration_To_policyconfig_MutatingAdmissionPolicyConfiguration(a.(*MutatingAdmissionPolicyConfiguration), b.(*policyconfig.MutatingAdmissionPolicyConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*policyconfig.MutatingAdmissionPolicyConfiguration)(nil), (*MutatingAdmissionPolicyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_policyconfig_MutatingAdmissionPolicyConfiguration_To_v1_MutatingAdmissionPolicyConfiguration(a.(*policyconfig.MutatingAdmissionPolicyConfiguration), b.(*MutatingAdmissionPolicyConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ValidatingAdmissionPolicyConfiguration)(nil), (*policyconfig.ValidatingAdmissionPolicyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ValidatingAdmissionPolicyConfiguration_To_policyconfig_ValidatingAdmissionPolicyConfiguration(a.(*ValidatingAdmissionPolicyConfiguration), b.(*policyconfig.ValidatingAdmissionPolicyConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*policyconfig.ValidatingAdmissionPolicyConfiguration)(nil), (*ValidatingAdmissionPolicyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_policyconfig_ValidatingAdmissionPolicyConfiguration_To_v1_ValidatingAdmissionPolicyConfiguration(a.(*policyconfig.ValidatingAdmissionPolicyConfiguration), b.(*ValidatingAdmissionPolicyConfiguration), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1_MutatingAdmissionPolicyConfiguration_To_policyconfig_MutatingAdmissionPolicyConfiguration(in *MutatingAdmissionPolicyConfiguration, out *policyconfig.MutatingAdmissionPolicyConfiguration, s conversion.Scope) error { + out.StaticManifestsDir = in.StaticManifestsDir + return nil +} + +// Convert_v1_MutatingAdmissionPolicyConfiguration_To_policyconfig_MutatingAdmissionPolicyConfiguration is an autogenerated conversion function. +func Convert_v1_MutatingAdmissionPolicyConfiguration_To_policyconfig_MutatingAdmissionPolicyConfiguration(in *MutatingAdmissionPolicyConfiguration, out *policyconfig.MutatingAdmissionPolicyConfiguration, s conversion.Scope) error { + return autoConvert_v1_MutatingAdmissionPolicyConfiguration_To_policyconfig_MutatingAdmissionPolicyConfiguration(in, out, s) +} + +func autoConvert_policyconfig_MutatingAdmissionPolicyConfiguration_To_v1_MutatingAdmissionPolicyConfiguration(in *policyconfig.MutatingAdmissionPolicyConfiguration, out *MutatingAdmissionPolicyConfiguration, s conversion.Scope) error { + out.StaticManifestsDir = in.StaticManifestsDir + return nil +} + +// Convert_policyconfig_MutatingAdmissionPolicyConfiguration_To_v1_MutatingAdmissionPolicyConfiguration is an autogenerated conversion function. +func Convert_policyconfig_MutatingAdmissionPolicyConfiguration_To_v1_MutatingAdmissionPolicyConfiguration(in *policyconfig.MutatingAdmissionPolicyConfiguration, out *MutatingAdmissionPolicyConfiguration, s conversion.Scope) error { + return autoConvert_policyconfig_MutatingAdmissionPolicyConfiguration_To_v1_MutatingAdmissionPolicyConfiguration(in, out, s) +} + +func autoConvert_v1_ValidatingAdmissionPolicyConfiguration_To_policyconfig_ValidatingAdmissionPolicyConfiguration(in *ValidatingAdmissionPolicyConfiguration, out *policyconfig.ValidatingAdmissionPolicyConfiguration, s conversion.Scope) error { + out.StaticManifestsDir = in.StaticManifestsDir + return nil +} + +// Convert_v1_ValidatingAdmissionPolicyConfiguration_To_policyconfig_ValidatingAdmissionPolicyConfiguration is an autogenerated conversion function. +func Convert_v1_ValidatingAdmissionPolicyConfiguration_To_policyconfig_ValidatingAdmissionPolicyConfiguration(in *ValidatingAdmissionPolicyConfiguration, out *policyconfig.ValidatingAdmissionPolicyConfiguration, s conversion.Scope) error { + return autoConvert_v1_ValidatingAdmissionPolicyConfiguration_To_policyconfig_ValidatingAdmissionPolicyConfiguration(in, out, s) +} + +func autoConvert_policyconfig_ValidatingAdmissionPolicyConfiguration_To_v1_ValidatingAdmissionPolicyConfiguration(in *policyconfig.ValidatingAdmissionPolicyConfiguration, out *ValidatingAdmissionPolicyConfiguration, s conversion.Scope) error { + out.StaticManifestsDir = in.StaticManifestsDir + return nil +} + +// Convert_policyconfig_ValidatingAdmissionPolicyConfiguration_To_v1_ValidatingAdmissionPolicyConfiguration is an autogenerated conversion function. +func Convert_policyconfig_ValidatingAdmissionPolicyConfiguration_To_v1_ValidatingAdmissionPolicyConfiguration(in *policyconfig.ValidatingAdmissionPolicyConfiguration, out *ValidatingAdmissionPolicyConfiguration, s conversion.Scope) error { + return autoConvert_policyconfig_ValidatingAdmissionPolicyConfiguration_To_v1_ValidatingAdmissionPolicyConfiguration(in, out, s) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..f4382cd852bf9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1/zz_generated.deepcopy.go @@ -0,0 +1,76 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicyConfiguration) DeepCopyInto(out *MutatingAdmissionPolicyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicyConfiguration. +func (in *MutatingAdmissionPolicyConfiguration) DeepCopy() *MutatingAdmissionPolicyConfiguration { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingAdmissionPolicyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingAdmissionPolicyConfiguration) DeepCopyInto(out *ValidatingAdmissionPolicyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyConfiguration. +func (in *ValidatingAdmissionPolicyConfiguration) DeepCopy() *ValidatingAdmissionPolicyConfiguration { + if in == nil { + return nil + } + out := new(ValidatingAdmissionPolicyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValidatingAdmissionPolicyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..cfc56a61ae4d9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/zz_generated.deepcopy.go @@ -0,0 +1,76 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package policyconfig + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingAdmissionPolicyConfiguration) DeepCopyInto(out *MutatingAdmissionPolicyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingAdmissionPolicyConfiguration. +func (in *MutatingAdmissionPolicyConfiguration) DeepCopy() *MutatingAdmissionPolicyConfiguration { + if in == nil { + return nil + } + out := new(MutatingAdmissionPolicyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingAdmissionPolicyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingAdmissionPolicyConfiguration) DeepCopyInto(out *ValidatingAdmissionPolicyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyConfiguration. +func (in *ValidatingAdmissionPolicyConfiguration) DeepCopy() *ValidatingAdmissionPolicyConfiguration { + if in == nil { + return nil + } + out := new(ValidatingAdmissionPolicyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValidatingAdmissionPolicyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/config.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/config.go new file mode 100644 index 0000000000000..125b9d8b0b2f9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/config.go @@ -0,0 +1,97 @@ +/* +Copyright 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 config + +import ( + "fmt" + "io" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/admission/plugin/manifest" + "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig" + v1 "k8s.io/apiserver/pkg/admission/plugin/policy/config/apis/policyconfig/v1" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict) +) + +func init() { + utilruntime.Must(policyconfig.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) +} + +// PolicyConfig holds the configuration loaded from the config file. +type PolicyConfig struct { + // StaticManifestsDir is the path to the directory containing static policy manifests. + StaticManifestsDir string +} + +// staticManifestsDirAccessor is implemented by config types that have a StaticManifestsDir field. +type staticManifestsDirAccessor interface { + runtime.Object + GetStaticManifestsDir() string +} + +// LoadValidatingConfig extracts the validating admission policy configuration from configFile. +func LoadValidatingConfig(configFile io.Reader) (PolicyConfig, error) { + return loadConfig(configFile, func(obj runtime.Object) bool { + _, ok := obj.(*policyconfig.ValidatingAdmissionPolicyConfiguration) + return ok + }) +} + +// LoadMutatingConfig extracts the mutating admission policy configuration from configFile. +func LoadMutatingConfig(configFile io.Reader) (PolicyConfig, error) { + return loadConfig(configFile, func(obj runtime.Object) bool { + _, ok := obj.(*policyconfig.MutatingAdmissionPolicyConfiguration) + return ok + }) +} + +func loadConfig(configFile io.Reader, isExpectedType func(runtime.Object) bool) (PolicyConfig, error) { + var cfg PolicyConfig + if configFile == nil { + return cfg, nil + } + + data, err := io.ReadAll(configFile) + if err != nil { + return cfg, err + } + decoder := codecs.UniversalDecoder() + decodedObj, err := runtime.Decode(decoder, data) + if err != nil { + return cfg, err + } + if !isExpectedType(decodedObj) { + return cfg, fmt.Errorf("unexpected type: %T", decodedObj) + } + config, ok := decodedObj.(staticManifestsDirAccessor) + if !ok { + return cfg, fmt.Errorf("type %T does not implement staticManifestsDirAccessor", decodedObj) + } + + if err := manifest.ValidateStaticManifestsDir(config.GetStaticManifestsDir()); err != nil { + return cfg, err + } + cfg.StaticManifestsDir = config.GetStaticManifestsDir() + return cfg, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/config_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/config_test.go new file mode 100644 index 0000000000000..a50cd7c37d7cd --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/config/config_test.go @@ -0,0 +1,245 @@ +/* +Copyright 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 config + +import ( + "bytes" + "os" + "path" + "strings" + "testing" + + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" +) + +func TestLoadValidatingConfig(t *testing.T) { + validDir := t.TempDir() + notADirFile := path.Join(t.TempDir(), "file.txt") + if err := os.WriteFile(notADirFile, []byte(""), 0644); err != nil { + t.Fatal(err) + } + + testcases := []struct { + name string + input string + enableFeatureGate *bool + expectErr string + expectStaticManifestsDir string + }{ + { + name: "empty input", + input: "", + expectErr: `'Kind' is missing`, + }, + { + name: "unknown kind", + input: `{"kind":"Unknown","apiVersion":"v1"}`, + expectErr: `no kind "Unknown" is registered`, + }, + { + name: "valid config with staticManifestsDir", + input: ` +kind: ValidatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: ` + validDir + ` +`, + enableFeatureGate: new(true), + expectStaticManifestsDir: validDir, + }, + { + name: "valid config with empty staticManifestsDir", + input: ` +kind: ValidatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +`, + }, + { + name: "forbidden when feature gate disabled", + input: ` +kind: ValidatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: /etc/kubernetes/policies +`, + enableFeatureGate: new(false), + expectErr: "staticManifestsDir: Forbidden", + }, + { + name: "invalid relative path", + input: ` +kind: ValidatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: relative/path +`, + enableFeatureGate: new(true), + expectErr: "must be an absolute file path", + }, + { + name: "path does not exist", + input: ` +kind: ValidatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: /nonexistent/path +`, + enableFeatureGate: new(true), + expectErr: "unable to read", + }, + { + name: "path is not a directory", + input: ` +kind: ValidatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: ` + notADirFile + ` +`, + enableFeatureGate: new(true), + expectErr: "must be a directory", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if tc.enableFeatureGate != nil { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, *tc.enableFeatureGate) + } + cfg, err := LoadValidatingConfig(bytes.NewBufferString(tc.input)) + if len(tc.expectErr) > 0 { + if err == nil { + t.Fatal("expected err, got none") + } + if !strings.Contains(err.Error(), tc.expectErr) { + t.Fatalf("expected err containing %q, got %v", tc.expectErr, err) + } + return + } + if err != nil { + t.Fatal(err) + } + if cfg.StaticManifestsDir != tc.expectStaticManifestsDir { + t.Fatalf("expected StaticManifestsDir %q, got %q", tc.expectStaticManifestsDir, cfg.StaticManifestsDir) + } + }) + } +} + +func TestLoadMutatingConfig(t *testing.T) { + validDir := t.TempDir() + notADirFile := path.Join(t.TempDir(), "file.txt") + if err := os.WriteFile(notADirFile, []byte(""), 0644); err != nil { + t.Fatal(err) + } + + testcases := []struct { + name string + input string + enableFeatureGate *bool + expectErr string + expectStaticManifestsDir string + }{ + { + name: "empty input", + input: "", + expectErr: `'Kind' is missing`, + }, + { + name: "unknown kind", + input: `{"kind":"Unknown","apiVersion":"v1"}`, + expectErr: `no kind "Unknown" is registered`, + }, + { + name: "valid config with staticManifestsDir", + input: ` +kind: MutatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: ` + validDir + ` +`, + enableFeatureGate: new(true), + expectStaticManifestsDir: validDir, + }, + { + name: "valid config with empty staticManifestsDir", + input: ` +kind: MutatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +`, + }, + { + name: "forbidden when feature gate disabled", + input: ` +kind: MutatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: /etc/kubernetes/policies +`, + enableFeatureGate: new(false), + expectErr: "staticManifestsDir: Forbidden", + }, + { + name: "invalid relative path", + input: ` +kind: MutatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: relative/path +`, + enableFeatureGate: new(true), + expectErr: "must be an absolute file path", + }, + { + name: "path does not exist", + input: ` +kind: MutatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: /nonexistent/path +`, + enableFeatureGate: new(true), + expectErr: "unable to read", + }, + { + name: "path is not a directory", + input: ` +kind: MutatingAdmissionPolicyConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: ` + notADirFile + ` +`, + enableFeatureGate: new(true), + expectErr: "must be a directory", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if tc.enableFeatureGate != nil { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, *tc.enableFeatureGate) + } + cfg, err := LoadMutatingConfig(bytes.NewBufferString(tc.input)) + if len(tc.expectErr) > 0 { + if err == nil { + t.Fatal("expected err, got none") + } + if !strings.Contains(err.Error(), tc.expectErr) { + t.Fatalf("expected err containing %q, got %v", tc.expectErr, err) + } + return + } + if err != nil { + t.Fatal(err) + } + if cfg.StaticManifestsDir != tc.expectStaticManifestsDir { + t.Fatalf("expected StaticManifestsDir %q, got %q", tc.expectStaticManifestsDir, cfg.StaticManifestsDir) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/composite_policy_source.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/composite_policy_source.go new file mode 100644 index 0000000000000..f991674e53e01 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/composite_policy_source.go @@ -0,0 +1,119 @@ +/* +Copyright 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 generic + +import ( + "context" + "sync" +) + +// HookSource provides hooks and sync status. This is the minimal interface +// needed for static sources that don't have their own Run lifecycle. +type HookSource[H Hook] interface { + Hooks() []H + HasSynced() bool +} + +// compositePolicySource combines multiple policy sources into a single source. +// Static (manifest-based) policies are returned before API-based policies. +type compositePolicySource[H Hook] struct { + staticSource HookSource[H] + apiSource Source[H] + + mu sync.RWMutex + lastStatic []H + lastAPI []H + lastCombined []H +} + +var _ Source[Hook] = &compositePolicySource[Hook]{} + +// NewCompositePolicySource creates a policy source that combines static and API-based sources. +// Static policies are evaluated first, followed by API-based policies. +// If staticSource is nil, only apiSource policies are returned. +func NewCompositePolicySource[H Hook](staticSource HookSource[H], apiSource Source[H]) Source[H] { + if staticSource == nil { + return apiSource + } + return &compositePolicySource[H]{ + staticSource: staticSource, + apiSource: apiSource, + } +} + +// Hooks returns all policy hooks from both sources. +// Static policies come first, followed by API-based policies. +// The combined slice is cached and reused when the underlying slices haven't changed. +func (c *compositePolicySource[H]) Hooks() []H { + var staticHooks, apiHooks []H + + // Static policies first (platform policies take precedence) + if c.staticSource != nil { + staticHooks = c.staticSource.Hooks() + } + + // Then API-based policies + if c.apiSource != nil { + apiHooks = c.apiSource.Hooks() + } + + c.mu.RLock() + if slicesAreEqual(staticHooks, c.lastStatic) && slicesAreEqual(apiHooks, c.lastAPI) { + combined := c.lastCombined + c.mu.RUnlock() + return combined + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + // Re-check under write lock. + if slicesAreEqual(staticHooks, c.lastStatic) && slicesAreEqual(apiHooks, c.lastAPI) { + return c.lastCombined + } + + combined := make([]H, 0, len(staticHooks)+len(apiHooks)) + combined = append(combined, staticHooks...) + combined = append(combined, apiHooks...) + + c.lastStatic = staticHooks + c.lastAPI = apiHooks + c.lastCombined = combined + return combined +} + +// slicesAreEqual reports whether two slices share the same backing array and length. +func slicesAreEqual[T any](a, b []T) bool { + return len(a) == len(b) && (len(a) == 0 || &a[0] == &b[0]) +} + +// Run starts the API-based source. The static source is started separately +// by the plugin via its own goroutine (file watcher), so this method only +// needs to handle the API source lifecycle. +func (c *compositePolicySource[H]) Run(ctx context.Context) error { + if c.apiSource != nil { + return c.apiSource.Run(ctx) + } + return nil +} + +// HasSynced returns true only when both sources have synced. +func (c *compositePolicySource[H]) HasSynced() bool { + staticSynced := c.staticSource == nil || c.staticSource.HasSynced() + apiSynced := c.apiSource == nil || c.apiSource.HasSynced() + return staticSynced && apiSynced +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/composite_policy_source_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/composite_policy_source_test.go new file mode 100644 index 0000000000000..7dc547d7ae183 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/composite_policy_source_test.go @@ -0,0 +1,186 @@ +/* +Copyright 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 generic + +import ( + "context" + "testing" +) + +// testHook is a minimal Hook implementation for testing. +type testHook struct { + name string +} + +// mockPolicySource implements Source[testHook] for testing. +type mockPolicySource struct { + hooks []testHook + hasSynced bool +} + +func (m *mockPolicySource) Hooks() []testHook { + return m.hooks +} + +func (m *mockPolicySource) Run(_ context.Context) error { + return nil +} + +func (m *mockPolicySource) HasSynced() bool { + return m.hasSynced +} + +var _ Source[testHook] = &mockPolicySource{} + +func TestCompositePolicySource_Hooks(t *testing.T) { + staticHook := testHook{name: "static-1"} + apiHook := testHook{name: "api-1"} + + tests := []struct { + name string + staticSource Source[testHook] + apiSource Source[testHook] + wantNames []string + }{ + { + name: "only static source", + staticSource: &mockPolicySource{hooks: []testHook{staticHook}, hasSynced: true}, + apiSource: &mockPolicySource{hooks: nil, hasSynced: true}, + wantNames: []string{"static-1"}, + }, + { + name: "only api source", + staticSource: &mockPolicySource{hooks: nil, hasSynced: true}, + apiSource: &mockPolicySource{hooks: []testHook{apiHook}, hasSynced: true}, + wantNames: []string{"api-1"}, + }, + { + name: "both sources - static first", + staticSource: &mockPolicySource{hooks: []testHook{staticHook}, hasSynced: true}, + apiSource: &mockPolicySource{hooks: []testHook{apiHook}, hasSynced: true}, + wantNames: []string{"static-1", "api-1"}, + }, + { + name: "nil static source", + staticSource: nil, + apiSource: &mockPolicySource{hooks: []testHook{apiHook}, hasSynced: true}, + wantNames: []string{"api-1"}, + }, + { + name: "both empty", + staticSource: &mockPolicySource{hooks: nil, hasSynced: true}, + apiSource: &mockPolicySource{hooks: nil, hasSynced: true}, + wantNames: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + source := NewCompositePolicySource[testHook](tt.staticSource, tt.apiSource) + hooks := source.Hooks() + + if len(hooks) != len(tt.wantNames) { + t.Errorf("Hooks() returned %d hooks, want %d", len(hooks), len(tt.wantNames)) + return + } + + for i, h := range hooks { + if h.name != tt.wantNames[i] { + t.Errorf("Hooks()[%d].name = %s, want %s", i, h.name, tt.wantNames[i]) + } + } + }) + } +} + +func TestCompositePolicySource_Caching(t *testing.T) { + staticSource := &mockPolicySource{hooks: []testHook{{name: "static-1"}}, hasSynced: true} + apiSource := &mockPolicySource{hooks: []testHook{{name: "api-1"}}, hasSynced: true} + + source := NewCompositePolicySource[testHook](staticSource, apiSource) + + // First call should create the combined slice. + hooks1 := source.Hooks() + if len(hooks1) != 2 { + t.Fatalf("Expected 2 hooks, got %d", len(hooks1)) + } + + // Second call with same underlying slices should return the cached combined slice. + hooks2 := source.Hooks() + if &hooks1[0] != &hooks2[0] { + t.Error("Expected cached slice to be returned when underlying slices haven't changed") + } + + // Changing the underlying slice should produce a new combined slice. + staticSource.hooks = []testHook{{name: "static-2"}} + hooks3 := source.Hooks() + if hooks3[0].name != "static-2" { + t.Errorf("Expected first hook to be static-2 after update, got %s", hooks3[0].name) + } + if &hooks2[0] == &hooks3[0] { + t.Error("Expected new slice after underlying source changed") + } +} + +func TestCompositePolicySource_HasSynced(t *testing.T) { + tests := []struct { + name string + staticSource Source[testHook] + apiSource Source[testHook] + want bool + }{ + { + name: "both synced", + staticSource: &mockPolicySource{hasSynced: true}, + apiSource: &mockPolicySource{hasSynced: true}, + want: true, + }, + { + name: "static not synced", + staticSource: &mockPolicySource{hasSynced: false}, + apiSource: &mockPolicySource{hasSynced: true}, + want: false, + }, + { + name: "api not synced", + staticSource: &mockPolicySource{hasSynced: true}, + apiSource: &mockPolicySource{hasSynced: false}, + want: false, + }, + { + name: "neither synced", + staticSource: &mockPolicySource{hasSynced: false}, + apiSource: &mockPolicySource{hasSynced: false}, + want: false, + }, + { + name: "nil static source, api synced", + staticSource: nil, + apiSource: &mockPolicySource{hasSynced: true}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + source := NewCompositePolicySource[testHook](tt.staticSource, tt.apiSource) + if got := source.HasSynced(); got != tt.want { + t.Errorf("HasSynced() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go index 03aebdd58ac10..20771583ce066 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go @@ -30,6 +30,8 @@ import ( "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/policy/matching" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -37,9 +39,22 @@ import ( // H is the Hook type generated by the source and consumed by the dispatcher. // !TODO: Just pass in a Plugin[H] with accessors to all this information -type sourceFactory[H any] func(informers.SharedInformerFactory, kubernetes.Interface, dynamic.Interface, meta.RESTMapper) Source[H] +type apiSourceFactory[H any] func(informers.SharedInformerFactory, kubernetes.Interface, dynamic.Interface, meta.RESTMapper) Source[H] type dispatcherFactory[H any] func(authorizer.Authorizer, *matching.Matcher, kubernetes.Interface) Dispatcher[H] +// ReloadableSource extends HookSource with a method to run a reload loop +// that watches for configuration changes and blocks until the context is canceled. +type ReloadableSource[H any] interface { + HookSource[H] + // RunReloadLoop watches for configuration changes and reloads when detected. + // It blocks until ctx is canceled. + RunReloadLoop(ctx context.Context) +} + +// StaticSourceFactory creates a static policy source from a manifest directory. +// The returned Source should have LoadInitial() already called. +type StaticSourceFactory[H any] func(manifestsDir string) (ReloadableSource[H], error) + // admissionResources is the list of resources related to CEL-based admission // features. var admissionResources = []schema.GroupResource{ @@ -54,17 +69,37 @@ var admissionResources = []schema.GroupResource{ type Plugin[H any] struct { *admission.Handler - sourceFactory sourceFactory[H] - dispatcherFactory dispatcherFactory[H] + apiSourceFactory apiSourceFactory[H] + dispatcherFactory dispatcherFactory[H] + staticSourceFactory StaticSourceFactory[H] source Source[H] dispatcher Dispatcher[H] matcher *matching.Matcher - informerFactory informers.SharedInformerFactory - client kubernetes.Interface - restMapper meta.RESTMapper - dynamicClient dynamic.Interface + // staticSource holds a reference to only the static (manifest-based) policy source. + // This is used in Dispatch to run static hooks for admission config resources + // (e.g., VAP/MAP/VAPB/MAPB), allowing static policies to protect those resources + // without the circular dependency concern that applies to API-based policies. + staticSource HookSource[H] + + // staticManifestsDir is the path to a directory containing static policy manifests. + // When set and the feature gate is enabled, policies are also loaded from this directory. + staticManifestsDir string + + // apiServerID is the identity of this API server instance, used for metrics labeling. + apiServerID string + + informerFactory informers.SharedInformerFactory + client kubernetes.Interface + restMapper meta.RESTMapper + dynamicClient dynamic.Interface + // admissionConfigResources are admission configuration resources (VAP/MAP/VAPB/MAPB). + // API-based policies skip these to prevent circular dependencies, but static policies + // are safe to evaluate on them. + admissionConfigResources sets.Set[schema.GroupResource] + // excludedResources are non-persisted resources (auth/authz reviews) that must be + // excluded from ALL policy evaluation, including static policies. excludedResources sets.Set[schema.GroupResource] stopCh <-chan struct{} authorizer authorizer.Authorizer @@ -77,6 +112,7 @@ var ( _ initializer.WantsRESTMapper = &Plugin[any]{} _ initializer.WantsDynamicClient = &Plugin[any]{} _ initializer.WantsDrainedNotification = &Plugin[any]{} + _ initializer.WantsAPIServerID = &Plugin[any]{} _ initializer.WantsAuthorizer = &Plugin[any]{} _ initializer.WantsExcludedAdmissionResources = &Plugin[any]{} _ admission.InitializationValidator = &Plugin[any]{} @@ -84,16 +120,18 @@ var ( func NewPlugin[H any]( handler *admission.Handler, - sourceFactory sourceFactory[H], + apiSourceFactory apiSourceFactory[H], dispatcherFactory dispatcherFactory[H], ) *Plugin[H] { return &Plugin[H]{ Handler: handler, - sourceFactory: sourceFactory, + apiSourceFactory: apiSourceFactory, dispatcherFactory: dispatcherFactory, - // always exclude admission/mutating policies and bindings - excludedResources: sets.New(admissionResources...), + // admission config resources are skipped by API-based policies (circular dep) + // but evaluated by static policies + admissionConfigResources: sets.New(admissionResources...), + excludedResources: sets.New[schema.GroupResource](), } } @@ -133,6 +171,34 @@ func (c *Plugin[H]) SetExcludedAdmissionResources(excludedResources []schema.Gro c.excludedResources.Insert(excludedResources...) } +// SetStaticManifestsDir sets the directory containing static policy manifests. +func (c *Plugin[H]) SetStaticManifestsDir(dir string) { + c.staticManifestsDir = dir +} + +// GetStaticManifestsDir returns the directory containing static policy manifests. +func (c *Plugin[H]) GetStaticManifestsDir() string { + return c.staticManifestsDir +} + +// SetStaticSourceFactory sets the factory for creating static policy sources. +// This should be called before ValidateInitialization. +func (c *Plugin[H]) SetStaticSourceFactory(factory StaticSourceFactory[H]) { + c.staticSourceFactory = factory +} + +// SetAPIServerID implements the WantsAPIServerID interface. +// The API server ID is used for metrics labeling and must be set before +// ValidateInitialization is called. +func (c *Plugin[H]) SetAPIServerID(id string) { + c.apiServerID = id +} + +// GetAPIServerID returns the stored API server ID. +func (c *Plugin[H]) GetAPIServerID() string { + return c.apiServerID +} + // ValidateInitialization - once clientset and informer factory are provided, creates and starts the admission controller func (c *Plugin[H]) ValidateInitialization() error { // By default enabled is set to false. It is up to types which embed this @@ -162,38 +228,74 @@ func (c *Plugin[H]) ValidateInitialization() error { return errors.New("missing authorizer") } - // Use default matcher + // Guard: if static manifests dir is set but feature gate is off, return an error + if len(c.staticManifestsDir) > 0 && !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return fmt.Errorf("static policy manifests dir %q configured but %s feature gate is not enabled", c.staticManifestsDir, features.ManifestBasedAdmissionControlConfig) + } + namespaceInformer := c.informerFactory.Core().V1().Namespaces() - c.matcher = matching.NewMatcher(namespaceInformer.Lister(), c.client) + // Construct matcher once, avoiding overwriting if already set. + if c.matcher == nil { + c.matcher = matching.NewMatcher(namespaceInformer.Lister(), c.client) + } if err := c.matcher.ValidateInitialization(); err != nil { return err } - c.source = c.sourceFactory(c.informerFactory, c.client, c.dynamicClient, c.restMapper) - c.dispatcher = c.dispatcherFactory(c.authorizer, c.matcher, c.client) - + // Create a shared context tied to server shutdown for all background goroutines. pluginContext, pluginContextCancel := context.WithCancel(context.Background()) go func() { defer pluginContextCancel() <-c.stopCh }() - go func() { - err := c.source.Run(pluginContext) - if err != nil && !errors.Is(err, context.Canceled) { - utilruntime.HandleError(fmt.Errorf("policy source context unexpectedly closed: %w", err)) + // Construct source once, avoiding overwriting if already set. + if c.source == nil { + apiSource := c.apiSourceFactory(c.informerFactory, c.client, c.dynamicClient, c.restMapper) + + if len(c.staticManifestsDir) > 0 { + if c.staticSourceFactory == nil { + return fmt.Errorf("static policy manifests configured in %q but no static source factory is set", c.staticManifestsDir) + } + staticSource, err := c.staticSourceFactory(c.staticManifestsDir) + if err != nil { + return fmt.Errorf("failed to load static policy manifests from %q: %w", c.staticManifestsDir, err) + } + // Use composite source that combines static + API sources + c.source = NewCompositePolicySource(staticSource, apiSource) + // Keep a reference to the static source so Dispatch can run static hooks + // for resources that are normally excluded from policy evaluation. + c.staticSource = staticSource + // Start the file watcher in a background goroutine + go staticSource.RunReloadLoop(pluginContext) + } else { + // Use only API-based source + c.source = apiSource } - }() - err := c.dispatcher.Start(pluginContext) - if err != nil && !errors.Is(err, context.Canceled) { - utilruntime.HandleError(fmt.Errorf("policy dispatcher context unexpectedly closed: %w", err)) + go func() { + err := c.source.Run(pluginContext) + if err != nil && !errors.Is(err, context.Canceled) { + utilruntime.HandleError(fmt.Errorf("policy source context unexpectedly closed: %w", err)) + } + }() + + c.SetReadyFunc(func() bool { + return namespaceInformer.Informer().HasSynced() && c.source.HasSynced() + }) + } + + // Construct dispatcher once, avoiding overwriting if already set. + if c.dispatcher == nil { + c.dispatcher = c.dispatcherFactory(c.authorizer, c.matcher, c.client) + + err := c.dispatcher.Start(pluginContext) + if err != nil && !errors.Is(err, context.Canceled) { + utilruntime.HandleError(fmt.Errorf("policy dispatcher context unexpectedly closed: %w", err)) + } } - c.SetReadyFunc(func() bool { - return namespaceInformer.Informer().HasSynced() && c.source.HasSynced() - }) return nil } @@ -204,18 +306,42 @@ func (c *Plugin[H]) Dispatch( ) (err error) { if !c.enabled { return nil - } else if c.shouldIgnoreResource(a) { + } + + gr := a.GetResource().GroupResource() + + if c.isExcludedFromAllHooks(gr) { + // Non-persisted resources (auth/authz reviews) are excluded from all policy + // evaluation to avoid breaking the cluster. + return nil + } else if c.isExcludedFromAPIHooks(gr) { + // Admission config resources (VAP/MAP/VAPB/MAPB) are excluded from API-based + // policies to prevent circular dependencies. However, static (manifest-based) + // policies are safe to evaluate since they don't have self-referential concerns. + if c.staticSource != nil { + if !c.staticSource.HasSynced() { + return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) + } + return c.dispatcher.Dispatch(ctx, a, o, c.staticSource.Hooks()) + } return nil - } else if !c.WaitForReady() { + } + + if !c.WaitForReady() { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } return c.dispatcher.Dispatch(ctx, a, o, c.source.Hooks()) } -func (c *Plugin[H]) shouldIgnoreResource(attr admission.Attributes) bool { - gvr := attr.GetResource() - // exclusion decision ignores the version. - gr := gvr.GroupResource() +// isExcludedFromAllHooks returns true for non-persisted resources (auth/authz reviews) +// that must not be intercepted by any policy hook. +func (c *Plugin[H]) isExcludedFromAllHooks(gr schema.GroupResource) bool { return c.excludedResources.Has(gr) } + +// isExcludedFromAPIHooks returns true for admission config resources (VAP/MAP/VAPB/MAPB) +// that are excluded from API-based hooks but may be evaluated by static hooks. +func (c *Plugin[H]) isExcludedFromAPIHooks(gr schema.GroupResource) bool { + return c.admissionConfigResources.Has(gr) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go index b98c10e705036..5a989028642e4 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go @@ -164,7 +164,7 @@ func NewPolicyTestContext[P, B runtime.Object, E Evaluator]( return policiesAndBindingsTracker.List(fakePolicyGVR, fakePolicyGVK, "") }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return policiesAndBindingsTracker.Watch(fakePolicyGVR, "") + return policiesAndBindingsTracker.Watch(fakePolicyGVR, "", options) }, }, policiesAndBindingsTracker), Pexample, @@ -177,7 +177,7 @@ func NewPolicyTestContext[P, B runtime.Object, E Evaluator]( return policiesAndBindingsTracker.List(fakeBindingGVR, fakeBindingGVK, "") }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return policiesAndBindingsTracker.Watch(fakeBindingGVR, "") + return policiesAndBindingsTracker.Watch(fakeBindingGVR, "", options) }, }, policiesAndBindingsTracker), Bexample, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go index a94004c272dff..65ffee4e2f62f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "sync" - "sync/atomic" "time" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -48,10 +47,7 @@ type controller[T runtime.Object] struct { options ControllerOptions - // must hold a func() bool or nil - notificationsDelivered atomic.Value - - hasProcessed synctrack.AsyncTracker[string] + hasProcessed *synctrack.AsyncTracker[string] } type ControllerOptions struct { @@ -77,17 +73,11 @@ func NewController[T runtime.Object]( } c := &controller[T]{ - options: options, - informer: informer, - reconciler: reconciler, - queue: nil, - } - c.hasProcessed.UpstreamHasSynced = func() bool { - f := c.notificationsDelivered.Load() - if f == nil { - return false - } - return f.(func() bool)() + options: options, + informer: informer, + reconciler: reconciler, + queue: nil, + hasProcessed: synctrack.NewAsyncTracker[string](options.Name), } return c } @@ -159,12 +149,9 @@ func (c *controller[T]) Run(ctx context.Context) error { return err } - c.notificationsDelivered.Store(registration.HasSynced) - // Make sure event handler is removed from informer in case return early from // an error defer func() { - c.notificationsDelivered.Store(func() bool { return false }) // Remove event handler and Handle Error here. Error should only be raised // for improper usage of event handler API. if err := c.informer.RemoveEventHandler(registration); err != nil { @@ -174,7 +161,12 @@ func (c *controller[T]) Run(ctx context.Context) error { // Wait for initial cache list to complete before beginning to reconcile // objects. - if !cache.WaitForNamedCacheSyncWithContext(ctx, c.informer.HasSynced) { + if !cache.WaitFor(ctx, "caches", c.informer.HasSyncedChecker(), registration.HasSyncedChecker()) { + // TODO: should cache.WaitFor return an error? + // ctx.Err() or context.Cause(ctx)? + // Either of them would make dead code like the "if err == nil" + // below more obvious. + // ctx cancelled during cache sync. return early err := ctx.Err() if err == nil { @@ -184,6 +176,10 @@ func (c *controller[T]) Run(ctx context.Context) error { return err } + // c.informer *and* our handler have synced, which implies that our AddFunc(= enqueue) + // and thus c.hasProcessed.Start have been called for the initial list => upstream is done. + c.hasProcessed.UpstreamHasSynced() + waitGroup := sync.WaitGroup{} for i := uint(0); i < c.options.Workers; i++ { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller_test.go index 9ccaffad25d25..efe7a5e9d77f7 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller_test.go @@ -117,7 +117,7 @@ func setupTest(ctx context.Context, customReconciler func(string, string, runtim return tracker.List(fakeGVR, fakeGVK, "") }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return tracker.Watch(fakeGVR, "") + return tracker.Watch(fakeGVR, "", options) }, }, tracker), &unstructured.Unstructured{}, 30*time.Second, nil)} @@ -370,7 +370,7 @@ func TestIgnoredUpdate(t *testing.T) { // Shows that an object which fails reconciliation will retry func TestReconcileRetry(t *testing.T) { - testContext, testCancel := context.WithTimeout(context.Background(), 2*time.Second) + testContext, testCancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout) defer testCancel() calls := atomic.Uint64{} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader/loader.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader/loader.go new file mode 100644 index 0000000000000..430da1e3c7960 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader/loader.go @@ -0,0 +1,227 @@ +/* +Copyright 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 loader provides generic functionality to load policy and binding +// configurations from manifest files. It handles file reading, YAML/JSON +// decoding, manifest name validation (suffix + uniqueness), policy-specific +// manifest constraints, and binding-reference checking. Type-specific +// defaulting and validation (e.g., scheme-based defaulting via internal +// types) are injected by callers through the AcceptObjectFunc callbacks. +package loader + +import ( + "errors" + "fmt" + "sort" + "strings" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/admission/plugin/manifest" +) + +// ErrUnrecognizedType is returned by AcceptObjectFunc when the object type is +// not handled. This allows callers with multiple accept functions to try the next one. +var ErrUnrecognizedType = fmt.Errorf("unrecognized resource type") + +// AcceptObjectFunc extracts typed items from a decoded runtime.Object, applying +// defaulting and validation. Returns ErrUnrecognizedType if the object type is +// not handled by this function. +type AcceptObjectFunc[T metav1.Object] func(obj runtime.Object) ([]T, error) + +// LoadPolicyManifests is a generic helper that loads policy and binding manifests +// from a directory. It handles file I/O, decoding, v1.List unwrapping, manifest +// name validation, policy-specific manifest constraints, binding-reference checking, +// and deterministic sorting by name. The decoder should be created by the caller +// with whatever scheme is appropriate (e.g., with full internal type install for +// kube-apiserver, or a minimal v1-only scheme for other consumers). +func LoadPolicyManifests[P, B metav1.Object]( + dir string, + decoder runtime.Decoder, + acceptPolicy AcceptObjectFunc[P], + acceptBinding AcceptObjectFunc[B], + getBindingPolicyName func(B) string, +) ([]P, []B, string, error) { + fileDocs, hash, err := manifest.LoadFiles(dir) + if err != nil { + return nil, nil, "", err + } + + policies := make([]P, 0) + bindings := make([]B, 0) + seenPolicyNames := map[string]string{} + seenBindingNames := map[string]string{} + + for _, fd := range fileDocs { + obj, _, err := decoder.Decode(fd.Doc, nil, nil) + if err != nil { + return nil, nil, "", fmt.Errorf("error loading %s: %w", fd.FilePath, err) + } + + newPolicies, newBindings, err := acceptFileObject(obj, fd.FilePath, seenPolicyNames, seenBindingNames, decoder, acceptPolicy, acceptBinding) + if err != nil { + return nil, nil, "", fmt.Errorf("error loading %s: %w", fd.FilePath, err) + } + policies = append(policies, newPolicies...) + bindings = append(bindings, newBindings...) + } + + if err := validateBindingReferences(policies, bindings, getBindingPolicyName); err != nil { + return nil, nil, "", err + } + + sort.Slice(policies, func(i, j int) bool { + return policies[i].GetName() < policies[j].GetName() + }) + sort.Slice(bindings, func(i, j int) bool { + return bindings[i].GetName() < bindings[j].GetName() + }) + + return policies, bindings, hash, nil +} + +// acceptFileObject handles type dispatch for a decoded object, including +// generic v1.List unwrapping, manifest name validation, policy-specific +// manifest constraint validation, and the default unsupported-type error. +func acceptFileObject[P, B metav1.Object]( + obj runtime.Object, + filePath string, + seenPolicyNames, seenBindingNames map[string]string, + decoder runtime.Decoder, + acceptPolicy AcceptObjectFunc[P], + acceptBinding AcceptObjectFunc[B], +) ([]P, []B, error) { + // Handle generic v1.List by recursing into each item + if list, ok := obj.(*metav1.List); ok { + var allPolicies []P + var allBindings []B + for _, rawItem := range list.Items { + itemObj, _, err := decoder.Decode(rawItem.Raw, nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode list item: %w", err) + } + p, b, err := acceptFileObject(itemObj, filePath, seenPolicyNames, seenBindingNames, decoder, acceptPolicy, acceptBinding) + if err != nil { + return nil, nil, err + } + allPolicies = append(allPolicies, p...) + allBindings = append(allBindings, b...) + } + return allPolicies, allBindings, nil + } + + // Try policy + policies, pErr := acceptPolicy(obj) + if pErr != nil && !errors.Is(pErr, ErrUnrecognizedType) { + return nil, nil, pErr + } + if pErr == nil { + for _, item := range policies { + if err := validateAcceptedItem(item, filePath, seenPolicyNames); err != nil { + return nil, nil, err + } + } + return policies, nil, nil + } + + // Try binding + bindings, bErr := acceptBinding(obj) + if bErr != nil && !errors.Is(bErr, ErrUnrecognizedType) { + return nil, nil, bErr + } + if bErr == nil { + for _, item := range bindings { + if err := validateAcceptedItem(item, filePath, seenBindingNames); err != nil { + return nil, nil, err + } + } + return nil, bindings, nil + } + + // Neither recognized it + return nil, nil, fmt.Errorf("unsupported resource type %T", obj) +} + +// validateAcceptedItem runs manifest name validation and policy-specific +// manifest constraint validation. +func validateAcceptedItem[T metav1.Object](item T, filePath string, seenNames map[string]string) error { + name := item.GetName() + if err := manifest.ValidateManifestName(name, filePath, seenNames); err != nil { + return err + } + obj, ok := any(item).(runtime.Object) + if !ok { + return fmt.Errorf("type %T does not implement runtime.Object", item) + } + return validatePolicyManifestConstraints(obj, name) +} + +// validatePolicyManifestConstraints validates manifest-specific constraints +// for policy and binding types (paramKind, paramRef, policyName suffix). +func validatePolicyManifestConstraints(obj runtime.Object, name string) error { + switch c := obj.(type) { + case *admissionregistrationv1.ValidatingAdmissionPolicy: + if c.Spec.ParamKind != nil { + return fmt.Errorf("ValidatingAdmissionPolicy %q: spec.paramKind is not supported for static manifests", name) + } + case *admissionregistrationv1.ValidatingAdmissionPolicyBinding: + if err := validateBindingConstraints(name, c.Spec.PolicyName, c.Spec.ParamRef != nil); err != nil { + return err + } + case *admissionregistrationv1.MutatingAdmissionPolicy: + if c.Spec.ParamKind != nil { + return fmt.Errorf("MutatingAdmissionPolicy %q: spec.paramKind is not supported for static manifests", name) + } + case *admissionregistrationv1.MutatingAdmissionPolicyBinding: + if err := validateBindingConstraints(name, c.Spec.PolicyName, c.Spec.ParamRef != nil); err != nil { + return err + } + default: + return fmt.Errorf("unsupported policy type %T", obj) + } + return nil +} + +// validateBindingConstraints checks common binding manifest constraints: +// policyName must be non-empty, have the static suffix, and paramRef must be nil. +func validateBindingConstraints(bindingName, policyName string, hasParamRef bool) error { + if len(policyName) == 0 { + return fmt.Errorf("binding %q must reference a policy (spec.policyName)", bindingName) + } + if !strings.HasSuffix(policyName, manifest.StaticConfigSuffix) { + return fmt.Errorf("binding %q: spec.policyName %q must end with %q", bindingName, policyName, manifest.StaticConfigSuffix) + } + if hasParamRef { + return fmt.Errorf("binding %q: spec.paramRef is not supported for static manifests", bindingName) + } + return nil +} + +// validateBindingReferences ensures all bindings reference policies that exist in the manifest set. +func validateBindingReferences[P, B metav1.Object](policies []P, bindings []B, getBindingPolicyName func(B) string) error { + policyNames := sets.New[string]() + for _, policy := range policies { + policyNames.Insert(policy.GetName()) + } + for _, binding := range bindings { + if !policyNames.Has(getBindingPolicyName(binding)) { + return fmt.Errorf("binding %q references policy %q which does not exist in the manifest directory", binding.GetName(), getBindingPolicyName(binding)) + } + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader/loader_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader/loader_test.go new file mode 100644 index 0000000000000..b12decf5e56ba --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/loader/loader_test.go @@ -0,0 +1,363 @@ +/* +Copyright 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 loader + +import ( + "os" + "path/filepath" + "strings" + "testing" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var ( + testScheme = runtime.NewScheme() + testCodecs serializer.CodecFactory +) + +func init() { + utilruntime.Must(admissionregistrationv1.AddToScheme(testScheme)) + testScheme.AddUnversionedTypes(metav1.SchemeGroupVersion, &metav1.List{}, &metav1.Status{}) + testCodecs = serializer.NewCodecFactory(testScheme, serializer.EnableStrict) +} + +func acceptTestPolicy(obj runtime.Object) ([]*admissionregistrationv1.ValidatingAdmissionPolicy, error) { + switch p := obj.(type) { + case *admissionregistrationv1.ValidatingAdmissionPolicy: + return []*admissionregistrationv1.ValidatingAdmissionPolicy{p}, nil + case *admissionregistrationv1.ValidatingAdmissionPolicyList: + items := make([]*admissionregistrationv1.ValidatingAdmissionPolicy, len(p.Items)) + for i := range p.Items { + items[i] = &p.Items[i] + } + return items, nil + default: + return nil, ErrUnrecognizedType + } +} + +func acceptTestBinding(obj runtime.Object) ([]*admissionregistrationv1.ValidatingAdmissionPolicyBinding, error) { + switch b := obj.(type) { + case *admissionregistrationv1.ValidatingAdmissionPolicyBinding: + return []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{b}, nil + case *admissionregistrationv1.ValidatingAdmissionPolicyBindingList: + items := make([]*admissionregistrationv1.ValidatingAdmissionPolicyBinding, len(b.Items)) + for i := range b.Items { + items[i] = &b.Items[i] + } + return items, nil + default: + return nil, ErrUnrecognizedType + } +} + +func TestLoadPolicyManifests(t *testing.T) { + tests := []struct { + name string + files map[string]string + wantPolicies int + wantBindings int + wantErr bool + wantErrContain string + }{ + { + name: "load policy and binding", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: test-policy.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: test-binding.static.k8s.io +spec: + policyName: test-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantPolicies: 1, + wantBindings: 1, + }, + { + name: "empty directory", + files: map[string]string{}, + wantPolicies: 0, + wantBindings: 0, + }, + { + name: "binding references non-existent policy", + files: map[string]string{ + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: orphan.static.k8s.io +spec: + policyName: does-not-exist.static.k8s.io + validationActions: + - Deny +`, + }, + wantErr: true, + wantErrContain: "does not exist", + }, + { + name: "duplicate policy names", + files: map[string]string{ + "01-policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: dup.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + "02-policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: dup.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + }, + wantErr: true, + wantErrContain: "duplicate", + }, + { + name: "unsupported resource type", + files: map[string]string{ + "wrong.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: not-a-policy +`, + }, + wantErr: true, + wantErrContain: "error loading", + }, + { + name: "v1.List with policy and binding", + files: map[string]string{ + "list.yaml": ` +apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingAdmissionPolicy + metadata: + name: v1list-policy.static.k8s.io + spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingAdmissionPolicyBinding + metadata: + name: v1list-binding.static.k8s.io + spec: + policyName: v1list-policy.static.k8s.io + validationActions: + - Deny +`, + }, + wantPolicies: 1, + wantBindings: 1, + }, + { + name: "policy with paramKind rejected", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: param-policy.static.k8s.io +spec: + paramKind: + apiVersion: v1 + kind: ConfigMap + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + }, + wantErr: true, + wantErrContain: "paramKind is not supported", + }, + { + name: "binding with paramRef rejected", + files: map[string]string{ + "policy.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: ref-policy.static.k8s.io +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + validations: + - expression: "true" +`, + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: ref-binding.static.k8s.io +spec: + policyName: ref-policy.static.k8s.io + paramRef: + name: my-config + namespace: default + validationActions: + - Deny +`, + }, + wantErr: true, + wantErrContain: "paramRef", + }, + { + name: "binding policyName missing static suffix", + files: map[string]string{ + "binding.yaml": ` +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: bad-ref.static.k8s.io +spec: + policyName: some-policy-without-suffix + validationActions: + - Deny +`, + }, + wantErr: true, + wantErrContain: "must end with", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("failed to write file %s: %v", name, err) + } + } + + policies, bindings, _, err := LoadPolicyManifests( + dir, + testCodecs.UniversalDeserializer(), + acceptTestPolicy, + acceptTestBinding, + func(b *admissionregistrationv1.ValidatingAdmissionPolicyBinding) string { + return b.Spec.PolicyName + }, + ) + + if tt.wantErr { + if err == nil { + t.Error("expected error but got none") + } else if tt.wantErrContain != "" && !strings.Contains(err.Error(), tt.wantErrContain) { + t.Errorf("expected error containing %q, got %q", tt.wantErrContain, err.Error()) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(policies) != tt.wantPolicies { + t.Errorf("expected %d policies, got %d", tt.wantPolicies, len(policies)) + } + if len(bindings) != tt.wantBindings { + t.Errorf("expected %d bindings, got %d", tt.wantBindings, len(bindings)) + } + }) + } +} + +func TestValidateBindingReferences(t *testing.T) { + policies := []*admissionregistrationv1.ValidatingAdmissionPolicy{ + {ObjectMeta: metav1.ObjectMeta{Name: "policy-a"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "policy-b"}}, + } + goodBindings := []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{ + {ObjectMeta: metav1.ObjectMeta{Name: "b1"}, Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{PolicyName: "policy-a"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "b2"}, Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{PolicyName: "policy-b"}}, + } + badBindings := []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{ + {ObjectMeta: metav1.ObjectMeta{Name: "b1"}, Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{PolicyName: "policy-missing"}}, + } + + getBindPolicy := func(b *admissionregistrationv1.ValidatingAdmissionPolicyBinding) string { + return b.Spec.PolicyName + } + + if err := validateBindingReferences(policies, goodBindings, getBindPolicy); err != nil { + t.Errorf("unexpected error for valid bindings: %v", err) + } + + if err := validateBindingReferences(policies, badBindings, getBindPolicy); err == nil { + t.Error("expected error for binding referencing non-existent policy") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source/source.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source/source.go new file mode 100644 index 0000000000000..0e37d786450da --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source/source.go @@ -0,0 +1,192 @@ +/* +Copyright 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 source provides a Source implementation that loads policy configurations from manifest files. +package source + +import ( + "context" + "fmt" + "sync/atomic" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission/plugin/manifest/metrics" + "k8s.io/apiserver/pkg/admission/plugin/policy/generic" + "k8s.io/apiserver/pkg/util/filesystem" + "k8s.io/klog/v2" +) + +// defaultReloadInterval is the default interval at which the manifest directory is checked for changes. +var defaultReloadInterval = 1 * time.Minute + +// SetReloadIntervalForTests sets the reload interval for testing and returns a function to restore the original value. +func SetReloadIntervalForTests(interval time.Duration) func() { + original := defaultReloadInterval + defaultReloadInterval = interval + return func() { + defaultReloadInterval = original + } +} + +// PolicyLoadFunc loads policy and binding manifests from a directory. +type PolicyLoadFunc[P, B runtime.Object] func(dir string) ([]P, []B, string, error) + +// PolicyCompiler compiles a policy into an Evaluator. +type PolicyCompiler[P runtime.Object, E generic.Evaluator] func(P) (E, error) + +// BindingPolicyName extracts the policy name referenced by a binding. +type BindingPolicyName[B runtime.Object] func(B) string + +// StaticPolicySource provides policy configurations loaded from manifest files. +type StaticPolicySource[P, B runtime.Object, E generic.Evaluator] struct { + manifestsDir string + apiServerID string + reloadInterval time.Duration + compiler PolicyCompiler[P, E] + loadFunc PolicyLoadFunc[P, B] + getBindingPolicyName BindingPolicyName[B] + manifestType metrics.ManifestType + + current atomic.Pointer[[]generic.PolicyHook[P, B, E]] + lastReadHash atomic.Pointer[string] // hash of last file content read (for short-circuiting) + hasSynced atomic.Bool +} + +// NewStaticPolicySource creates a new static policy source that loads configurations from the specified directory. +func NewStaticPolicySource[P, B runtime.Object, E generic.Evaluator]( + manifestsDir, apiServerID string, + compiler PolicyCompiler[P, E], + loadFunc PolicyLoadFunc[P, B], + getBindingPolicyName BindingPolicyName[B], + manifestType metrics.ManifestType, +) *StaticPolicySource[P, B, E] { + metrics.RegisterMetrics() + return &StaticPolicySource[P, B, E]{ + manifestsDir: manifestsDir, + apiServerID: apiServerID, + reloadInterval: defaultReloadInterval, + compiler: compiler, + loadFunc: loadFunc, + getBindingPolicyName: getBindingPolicyName, + manifestType: manifestType, + } +} + +// LoadInitial performs the initial load of manifests. +// This should be called during API server startup and will fail if the manifests cannot be loaded. +func (s *StaticPolicySource[P, B, E]) LoadInitial() error { + policies, bindings, hash, err := s.loadFunc(s.manifestsDir) + if err != nil { + return err + } + + hooks, err := s.compile(policies, bindings) + if err != nil { + return err + } + s.current.Store(&hooks) + s.lastReadHash.Store(&hash) + s.hasSynced.Store(true) + + klog.InfoS("Loaded manifest-based admission policy configurations", "plugin", string(s.manifestType), "count", len(hooks)) + metrics.RecordAutomaticReloadSuccess(s.manifestType, s.apiServerID, hash) + return nil +} + +// RunReloadLoop watches for configuration changes and reloads when detected. +// It blocks until ctx is canceled. +func (s *StaticPolicySource[P, B, E]) RunReloadLoop(ctx context.Context) { + filesystem.WatchUntil( + ctx, + s.reloadInterval, + s.manifestsDir, + func() { + s.checkAndReload() + }, + func(err error) { + klog.ErrorS(err, "watching manifest directory", "plugin", string(s.manifestType), "dir", s.manifestsDir) + }, + ) +} + +func (s *StaticPolicySource[P, B, E]) compile(policies []P, bindings []B) ([]generic.PolicyHook[P, B, E], error) { + bindingsByPolicy := make(map[string][]B) + for _, binding := range bindings { + policyName := s.getBindingPolicyName(binding) + bindingsByPolicy[policyName] = append(bindingsByPolicy[policyName], binding) + } + + var hooks []generic.PolicyHook[P, B, E] + for _, policy := range policies { + nameGetter, ok := any(policy).(interface{ GetName() string }) + if !ok { + return nil, fmt.Errorf("policy type %T does not implement GetName()", policy) + } + evaluator, err := s.compiler(policy) + if err != nil { + return nil, fmt.Errorf("failed to compile policy %q: %w", nameGetter.GetName(), err) + } + hook := generic.PolicyHook[P, B, E]{ + Policy: policy, + Bindings: bindingsByPolicy[nameGetter.GetName()], + Evaluator: evaluator, + } + hooks = append(hooks, hook) + } + + return hooks, nil +} + +func (s *StaticPolicySource[P, B, E]) checkAndReload() { + policies, bindings, hash, err := s.loadFunc(s.manifestsDir) + if err != nil { + klog.ErrorS(err, "reloading manifest config", "plugin", string(s.manifestType), "dir", s.manifestsDir) + metrics.RecordAutomaticReloadFailure(s.manifestType, s.apiServerID) + return + } + + // Short-circuit if file content hasn't changed since last read. + if last := s.lastReadHash.Load(); last != nil && hash == *last { + return + } + s.lastReadHash.Store(&hash) + + hooks, err := s.compile(policies, bindings) + if err != nil { + klog.ErrorS(err, "compiling manifest config", "plugin", string(s.manifestType), "dir", s.manifestsDir) + metrics.RecordAutomaticReloadFailure(s.manifestType, s.apiServerID) + return + } + s.current.Store(&hooks) + + klog.InfoS("reloaded manifest config", "plugin", string(s.manifestType), "dir", s.manifestsDir) + metrics.RecordAutomaticReloadSuccess(s.manifestType, s.apiServerID, hash) +} + +// HasSynced returns true if the initial load has completed. +func (s *StaticPolicySource[P, B, E]) HasSynced() bool { + return s.hasSynced.Load() +} + +// Hooks returns the list of policy hooks. +func (s *StaticPolicySource[P, B, E]) Hooks() []generic.PolicyHook[P, B, E] { + current := s.current.Load() + if current == nil { + return nil + } + return *current +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source/source_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source/source_test.go new file mode 100644 index 0000000000000..cc1bf1902db48 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source/source_test.go @@ -0,0 +1,189 @@ +/* +Copyright 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 source + +import ( + "fmt" + "testing" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission/plugin/manifest/metrics" +) + +type mockEvaluator struct{} + +type mockPolicyLoadFunc struct { + policies []*admissionregistrationv1.ValidatingAdmissionPolicy + bindings []*admissionregistrationv1.ValidatingAdmissionPolicyBinding + hash string + err error + callCount int +} + +func (m *mockPolicyLoadFunc) load(dir string) ([]*admissionregistrationv1.ValidatingAdmissionPolicy, []*admissionregistrationv1.ValidatingAdmissionPolicyBinding, string, error) { + m.callCount++ + return m.policies, m.bindings, m.hash, m.err +} + +func validVAP(name string) *admissionregistrationv1.ValidatingAdmissionPolicy { + return &admissionregistrationv1.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + } +} + +func validVAPB(name, policyName string) *admissionregistrationv1.ValidatingAdmissionPolicyBinding { + return &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{PolicyName: policyName}, + } +} + +func mockCompiler(p *admissionregistrationv1.ValidatingAdmissionPolicy) (mockEvaluator, error) { + return mockEvaluator{}, nil +} + +func getBindingPolicyName(b *admissionregistrationv1.ValidatingAdmissionPolicyBinding) string { + return b.Spec.PolicyName +} + +func newTestSource(loadFunc PolicyLoadFunc[*admissionregistrationv1.ValidatingAdmissionPolicy, *admissionregistrationv1.ValidatingAdmissionPolicyBinding]) *StaticPolicySource[*admissionregistrationv1.ValidatingAdmissionPolicy, *admissionregistrationv1.ValidatingAdmissionPolicyBinding, mockEvaluator] { + return NewStaticPolicySource[*admissionregistrationv1.ValidatingAdmissionPolicy, *admissionregistrationv1.ValidatingAdmissionPolicyBinding, mockEvaluator]( + "/tmp/test-manifests", + "test-server", + mockCompiler, + loadFunc, + getBindingPolicyName, + metrics.VAPManifestType, + ) +} + +func TestStaticPolicySource_LoadInitial(t *testing.T) { + mock := &mockPolicyLoadFunc{ + policies: []*admissionregistrationv1.ValidatingAdmissionPolicy{validVAP("policy1")}, + bindings: []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{validVAPB("binding1", "policy1")}, + hash: "h1", + } + src := newTestSource(mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial() returned unexpected error: %v", err) + } + if !src.HasSynced() { + t.Error("HasSynced() = false, want true after LoadInitial") + } + + hooks := src.Hooks() + if len(hooks) != 1 { + t.Fatalf("Hooks() returned %d hooks, want 1", len(hooks)) + } + if hooks[0].Policy.Name != "policy1" { + t.Errorf("hook policy name = %q, want %q", hooks[0].Policy.Name, "policy1") + } + if len(hooks[0].Bindings) != 1 { + t.Fatalf("hook has %d bindings, want 1", len(hooks[0].Bindings)) + } + if hooks[0].Bindings[0].Name != "binding1" { + t.Errorf("hook binding name = %q, want %q", hooks[0].Bindings[0].Name, "binding1") + } + if mock.callCount != 1 { + t.Errorf("loadFunc called %d times, want 1", mock.callCount) + } +} + +func TestStaticPolicySource_HashSkipsReload(t *testing.T) { + mock := &mockPolicyLoadFunc{ + policies: []*admissionregistrationv1.ValidatingAdmissionPolicy{validVAP("policy1")}, + bindings: []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{validVAPB("binding1", "policy1")}, + hash: "h1", + } + src := newTestSource(mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial() returned unexpected error: %v", err) + } + hooksBefore := src.Hooks() + + // checkAndReload with the same hash should skip the update. + src.checkAndReload() + + if mock.callCount != 2 { + t.Errorf("loadFunc called %d times, want 2", mock.callCount) + } + hooksAfter := src.Hooks() + if len(hooksAfter) != len(hooksBefore) { + t.Errorf("Hooks() length changed from %d to %d after no-op reload", len(hooksBefore), len(hooksAfter)) + } + // Verify the pointer didn't change (same underlying slice). + if &hooksAfter[0] != &hooksBefore[0] { + t.Error("Hooks() returned a different slice after hash-matched reload; expected same slice") + } +} + +func TestStaticPolicySource_FilesDeletionClearsHooks(t *testing.T) { + mock := &mockPolicyLoadFunc{ + policies: []*admissionregistrationv1.ValidatingAdmissionPolicy{validVAP("policy1")}, + bindings: []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{validVAPB("binding1", "policy1")}, + hash: "h1", + } + src := newTestSource(mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial() returned unexpected error: %v", err) + } + if len(src.Hooks()) != 1 { + t.Fatalf("Hooks() returned %d hooks after initial load, want 1", len(src.Hooks())) + } + + // Simulate files being deleted: empty policies/bindings with a new hash. + mock.policies = nil + mock.bindings = nil + mock.hash = "h2" + + src.checkAndReload() + + hooks := src.Hooks() + if len(hooks) != 0 { + t.Errorf("Hooks() returned %d hooks after deletion reload, want 0", len(hooks)) + } +} + +func TestStaticPolicySource_ReloadKeepsPreviousOnError(t *testing.T) { + mock := &mockPolicyLoadFunc{ + policies: []*admissionregistrationv1.ValidatingAdmissionPolicy{validVAP("policy1")}, + bindings: []*admissionregistrationv1.ValidatingAdmissionPolicyBinding{validVAPB("binding1", "policy1")}, + hash: "h1", + } + src := newTestSource(mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial() returned unexpected error: %v", err) + } + + // Make loadFunc return an error on subsequent calls. + mock.err = fmt.Errorf("simulated load error") + + src.checkAndReload() + + hooks := src.Hooks() + if len(hooks) != 1 { + t.Fatalf("Hooks() returned %d hooks after error reload, want 1", len(hooks)) + } + if hooks[0].Policy.Name != "policy1" { + t.Errorf("hook policy name = %q, want %q", hooks[0].Policy.Name, "policy1") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go index ff342623a95dc..b20103c5747be 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go @@ -18,7 +18,6 @@ package mutating import ( v1 "k8s.io/api/admissionregistration/v1" - "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission/plugin/policy/generic" ) @@ -59,19 +58,11 @@ func (v *mutatingAdmissionPolicyAccessor) GetParamKind() *v1.ParamKind { } func (v *mutatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResources { - return convertV1alpha1ResourceRulesToV1(v.Spec.MatchConstraints) + return v.Spec.MatchConstraints } func (v *mutatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType { - return toV1FailurePolicy(v.Spec.FailurePolicy) -} - -func toV1FailurePolicy(failurePolicy *v1beta1.FailurePolicyType) *v1.FailurePolicyType { - if failurePolicy == nil { - return nil - } - fp := v1.FailurePolicyType(*failurePolicy) - return &fp + return v.Spec.FailurePolicy } type mutatingAdmissionPolicyBindingAccessor struct { @@ -94,7 +85,7 @@ func (v *mutatingAdmissionPolicyBindingAccessor) GetPolicyName() types.Namespace } func (v *mutatingAdmissionPolicyBindingAccessor) GetMatchResources() *v1.MatchResources { - return convertV1alpha1ResourceRulesToV1(v.Spec.MatchResources) + return v.Spec.MatchResources } func (v *mutatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef { @@ -115,30 +106,3 @@ func (v *mutatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef { ParameterNotFoundAction: nfa, } } - -func convertV1alpha1ResourceRulesToV1(mc *v1beta1.MatchResources) *v1.MatchResources { - if mc == nil { - return nil - } - - var res v1.MatchResources - res.NamespaceSelector = mc.NamespaceSelector - res.ObjectSelector = mc.ObjectSelector - for _, ex := range mc.ExcludeResourceRules { - res.ExcludeResourceRules = append(res.ExcludeResourceRules, v1.NamedRuleWithOperations{ - ResourceNames: ex.ResourceNames, - RuleWithOperations: ex.RuleWithOperations, - }) - } - for _, ex := range mc.ResourceRules { - res.ResourceRules = append(res.ResourceRules, v1.NamedRuleWithOperations{ - ResourceNames: ex.ResourceNames, - RuleWithOperations: ex.RuleWithOperations, - }) - } - if mc.MatchPolicy != nil { - mp := v1.MatchPolicyType(*mc.MatchPolicy) - res.MatchPolicy = &mp - } - return &res -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go index 121c3b4004f7e..a0772aab4415b 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go @@ -19,9 +19,10 @@ package mutating import ( "fmt" - "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/api/admissionregistration/v1" plugincel "k8s.io/apiserver/pkg/admission/plugin/cel" "k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch" + "k8s.io/apiserver/pkg/admission/plugin/policy/validating" "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" apiservercel "k8s.io/apiserver/pkg/cel" "k8s.io/apiserver/pkg/cel/environment" @@ -32,7 +33,7 @@ import ( // // Each individual mutation is compiled into MutationEvaluationFunc and // returned is a PolicyEvaluator in the same order as the mutations appeared in the policy. -func compilePolicy(policy *Policy) PolicyEvaluator { +func compilePolicy(policy *v1.MutatingAdmissionPolicy) PolicyEvaluator { opts := plugincel.OptionalVariableDeclarations{HasParams: policy.Spec.ParamKind != nil, HasAuthorizer: true} compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) if err != nil { @@ -43,7 +44,7 @@ func compilePolicy(policy *Policy) PolicyEvaluator { } // Compile and store variables - compiler.CompileAndStoreVariables(convertv1alpha1Variables(policy.Spec.Variables), opts, environment.StoredExpressions) + compiler.CompileAndStoreVariables(convertV1Variables(policy.Spec.Variables), opts, environment.StoredExpressions) // Compile matchers var matcher matchconditions.Matcher = nil @@ -53,7 +54,7 @@ func compilePolicy(policy *Policy) PolicyEvaluator { for i := range matchConditions { matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i]) } - matcher = matchconditions.NewMatcher(compiler.CompileCondition(matchExpressionAccessors, opts, environment.StoredExpressions), toV1FailurePolicy(policy.Spec.FailurePolicy), "policy", "validate", policy.Name) + matcher = matchconditions.NewMatcher(compiler.CompileCondition(matchExpressionAccessors, opts, environment.StoredExpressions), policy.Spec.FailurePolicy, "policy", "validate", policy.Name) } // Compiler patchers @@ -62,13 +63,13 @@ func compilePolicy(policy *Policy) PolicyEvaluator { patchOptions.HasPatchTypes = true for _, m := range policy.Spec.Mutations { switch m.PatchType { - case v1beta1.PatchTypeJSONPatch: + case v1.PatchTypeJSONPatch: if m.JSONPatch != nil { accessor := &patch.JSONPatchCondition{Expression: m.JSONPatch.Expression} compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions) patchers = append(patchers, patch.NewJSONPatcher(compileResult)) } - case v1beta1.PatchTypeApplyConfiguration: + case v1.PatchTypeApplyConfiguration: if m.ApplyConfiguration != nil { accessor := &patch.ApplyConfigurationCondition{Expression: m.ApplyConfiguration.Expression} compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions) @@ -77,5 +78,19 @@ func compilePolicy(policy *Policy) PolicyEvaluator { } } - return PolicyEvaluator{Matcher: matcher, Mutators: patchers, CompositionEnv: compiler.CompositionEnv} + return PolicyEvaluator{Matcher: matcher, Mutators: patchers, CompositedCompiler: compiler} +} + +func convertV1Variables(variables []v1.Variable) []plugincel.NamedExpressionAccessor { + if variables == nil { + return nil + } + res := make([]plugincel.NamedExpressionAccessor, len(variables)) + for i, v := range variables { + res[i] = &validating.Variable{ + Name: v.Name, + Expression: v.Expression, + } + } + return res } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation_test.go index c5be5befc16d4..296584b25938f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation_test.go @@ -18,12 +18,13 @@ package mutating import ( "context" - "github.com/google/go-cmp/cmp" "strings" "testing" "time" - "k8s.io/api/admissionregistration/v1beta1" + "github.com/google/go-cmp/cmp" + + v1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -49,7 +50,7 @@ func TestCompilation(t *testing.T) { deploymentGVK := schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"} testCases := []struct { name string - policy *Policy + policy *v1.MutatingAdmissionPolicy gvr schema.GroupVersionResource object runtime.Object oldObject runtime.Object @@ -60,9 +61,9 @@ func TestCompilation(t *testing.T) { }{ { name: "applyConfiguration then jsonPatch", - policy: mutations(policy("d1"), v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + policy: mutations(policy("d1"), v1.Mutation{ + PatchType: v1.PatchTypeApplyConfiguration, + ApplyConfiguration: &v1.ApplyConfiguration{ Expression: `Object{ spec: Object.spec{ replicas: object.spec.replicas + 100 @@ -70,9 +71,9 @@ func TestCompilation(t *testing.T) { }`, }, }, - v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeJSONPatch, - JSONPatch: &v1beta1.JSONPatch{ + v1.Mutation{ + PatchType: v1.PatchTypeJSONPatch, + JSONPatch: &v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: object.spec.replicas + 10} ]`, @@ -85,17 +86,17 @@ func TestCompilation(t *testing.T) { { name: "jsonPatch then applyConfiguration", policy: mutations(policy("d1"), - v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeJSONPatch, - JSONPatch: &v1beta1.JSONPatch{ + v1.Mutation{ + PatchType: v1.PatchTypeJSONPatch, + JSONPatch: &v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: object.spec.replicas + 10} ]`, }, }, - v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + v1.Mutation{ + PatchType: v1.PatchTypeApplyConfiguration, + ApplyConfiguration: &v1.ApplyConfiguration{ Expression: `Object{ spec: Object.spec{ replicas: object.spec.replicas + 100 @@ -109,7 +110,7 @@ func TestCompilation(t *testing.T) { }, { name: "jsonPatch with variable", - policy: jsonPatches(variables(policy("d1"), v1beta1.Variable{Name: "desired", Expression: "10"}), v1beta1.JSONPatch{ + policy: jsonPatches(variables(policy("d1"), v1.Variable{Name: "desired", Expression: "10"}), v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: variables.desired + 1}, ]`, @@ -120,7 +121,7 @@ func TestCompilation(t *testing.T) { }, { name: "apply configuration with variable", - policy: applyConfigurations(variables(policy("d1"), v1beta1.Variable{Name: "desired", Expression: "10"}), + policy: applyConfigurations(variables(policy("d1"), v1.Variable{Name: "desired", Expression: "10"}), `Object{ spec: Object.spec{ replicas: variables.desired + 1 @@ -137,7 +138,7 @@ func TestCompilation(t *testing.T) { spec: Object.spec{ replicas: int(params.data['k1']) } - }`), &v1beta1.ParamKind{Kind: "ConfigMap", APIVersion: "v1"}), + }`), &v1.ParamKind{Kind: "ConfigMap", APIVersion: "v1"}), params: &corev1.ConfigMap{Data: map[string]string{"k1": "100"}}, gvr: deploymentGVR, object: &appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: ptr.To[int32](1)}}, @@ -145,7 +146,7 @@ func TestCompilation(t *testing.T) { }, { name: "jsonPatch with excessive cost", - policy: jsonPatches(variables(policy("d1"), v1beta1.Variable{Name: "list", Expression: "[0,1,2,3,4,5,6,7,8,9]"}), v1beta1.JSONPatch{ + policy: jsonPatches(variables(policy("d1"), v1.Variable{Name: "list", Expression: "[0,1,2,3,4,5,6,7,8,9]"}), v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: variables.list.all(x1, variables.list.all(x2, variables.list.all(x3, variables.list.all(x4, variables.list.all(x5, variables.list.all(x5, "0123456789" == "0123456789"))))))? 1 : 0 @@ -163,14 +164,14 @@ func TestCompilation(t *testing.T) { spec: Object.spec{ replicas: variables.list.all(x1, variables.list.all(x2, variables.list.all(x3, variables.list.all(x4, variables.list.all(x5, variables.list.all(x5, "0123456789" == "0123456789"))))))? 1 : 0 } - }`), v1beta1.Variable{Name: "list", Expression: "[0,1,2,3,4,5,6,7,8,9]"}), + }`), v1.Variable{Name: "list", Expression: "[0,1,2,3,4,5,6,7,8,9]"}), gvr: deploymentGVR, object: &appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: ptr.To[int32](1)}}, expectedErr: "operation cancelled: actual cost limit exceeded", }, { name: "request variable", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: request.kind.group == 'apps' && request.kind.version == 'v1' && request.kind.kind == 'Deployment' ? 10 : 0 @@ -182,7 +183,7 @@ func TestCompilation(t *testing.T) { }, { name: "namespace request variable", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: namespaceObject.metadata.name == 'ns1' ? 10 : 0 @@ -195,7 +196,7 @@ func TestCompilation(t *testing.T) { }, { name: "authorizer check", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{op: "replace", path: "/spec/replicas", value: authorizer.group('').resource('endpoints').check('create').allowed() ? 10 : 0 @@ -208,7 +209,7 @@ func TestCompilation(t *testing.T) { }, { name: "object type has field access", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{ op: "add", path: "/metadata/labels", @@ -226,7 +227,7 @@ func TestCompilation(t *testing.T) { }, { name: "object type has field testing", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{ op: "add", path: "/metadata/labels", @@ -246,7 +247,7 @@ func TestCompilation(t *testing.T) { }, { name: "object type equality", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{ op: "add", path: "/metadata/labels", @@ -274,7 +275,7 @@ func TestCompilation(t *testing.T) { // recompile all expressions with fully bound types before evaluation and report // errors if invalid Object types like this are initialized. name: "object types are not fully type checked", - policy: jsonPatches(policy("d1"), v1beta1.JSONPatch{ + policy: jsonPatches(policy("d1"), v1.JSONPatch{ Expression: `[ JSONPatch{ op: "add", path: "/spec", @@ -326,8 +327,8 @@ func TestCompilation(t *testing.T) { } policyEvaluator := compilePolicy(tc.policy) - if policyEvaluator.CompositionEnv != nil { - ctx = policyEvaluator.CompositionEnv.CreateContext(ctx) + if policyEvaluator.CompositedCompiler != nil { + ctx = policyEvaluator.CompositedCompiler.CreateContext(ctx) } obj := tc.object @@ -400,52 +401,52 @@ func TestCompilation(t *testing.T) { } } -func policy(name string) *v1beta1.MutatingAdmissionPolicy { - return &v1beta1.MutatingAdmissionPolicy{ +func policy(name string) *v1.MutatingAdmissionPolicy { + return &v1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, - Spec: v1beta1.MutatingAdmissionPolicySpec{}, + Spec: v1.MutatingAdmissionPolicySpec{}, } } -func variables(policy *v1beta1.MutatingAdmissionPolicy, variables ...v1beta1.Variable) *v1beta1.MutatingAdmissionPolicy { +func variables(policy *v1.MutatingAdmissionPolicy, variables ...v1.Variable) *v1.MutatingAdmissionPolicy { policy.Spec.Variables = append(policy.Spec.Variables, variables...) return policy } -func jsonPatches(policy *v1beta1.MutatingAdmissionPolicy, jsonPatches ...v1beta1.JSONPatch) *v1beta1.MutatingAdmissionPolicy { +func jsonPatches(policy *v1.MutatingAdmissionPolicy, jsonPatches ...v1.JSONPatch) *v1.MutatingAdmissionPolicy { for _, jsonPatch := range jsonPatches { - policy.Spec.Mutations = append(policy.Spec.Mutations, v1beta1.Mutation{ + policy.Spec.Mutations = append(policy.Spec.Mutations, v1.Mutation{ JSONPatch: &jsonPatch, - PatchType: v1beta1.PatchTypeJSONPatch, + PatchType: v1.PatchTypeJSONPatch, }) } return policy } -func applyConfigurations(policy *v1beta1.MutatingAdmissionPolicy, expressions ...string) *v1beta1.MutatingAdmissionPolicy { +func applyConfigurations(policy *v1.MutatingAdmissionPolicy, expressions ...string) *v1.MutatingAdmissionPolicy { for _, expression := range expressions { - policy.Spec.Mutations = append(policy.Spec.Mutations, v1beta1.Mutation{ - ApplyConfiguration: &v1beta1.ApplyConfiguration{Expression: expression}, - PatchType: v1beta1.PatchTypeApplyConfiguration, + policy.Spec.Mutations = append(policy.Spec.Mutations, v1.Mutation{ + ApplyConfiguration: &v1.ApplyConfiguration{Expression: expression}, + PatchType: v1.PatchTypeApplyConfiguration, }) } return policy } -func paramKind(policy *v1beta1.MutatingAdmissionPolicy, paramKind *v1beta1.ParamKind) *v1beta1.MutatingAdmissionPolicy { +func paramKind(policy *v1.MutatingAdmissionPolicy, paramKind *v1.ParamKind) *v1.MutatingAdmissionPolicy { policy.Spec.ParamKind = paramKind return policy } -func mutations(policy *v1beta1.MutatingAdmissionPolicy, mutations ...v1beta1.Mutation) *v1beta1.MutatingAdmissionPolicy { +func mutations(policy *v1.MutatingAdmissionPolicy, mutations ...v1.Mutation) *v1.MutatingAdmissionPolicy { policy.Spec.Mutations = append(policy.Spec.Mutations, mutations...) return policy } -func matchConstraints(policy *v1beta1.MutatingAdmissionPolicy, matchConstraints *v1beta1.MatchResources) *v1beta1.MutatingAdmissionPolicy { +func matchConstraints(policy *v1.MutatingAdmissionPolicy, matchConstraints *v1.MatchResources) *v1.MutatingAdmissionPolicy { policy.Spec.MatchConstraints = matchConstraints return policy } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go index 53dbd6486f08a..e5b10c7f6d64f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go @@ -22,7 +22,7 @@ import ( "fmt" "time" - "k8s.io/api/admissionregistration/v1beta1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" v1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -137,8 +137,8 @@ func (d *dispatcher) dispatchInvocations( // Should loop through invocations, handling possible error and invoking // evaluator to apply patch, also should handle re-invocations for _, invocation := range invocations { - if invocation.Evaluator.CompositionEnv != nil { - ctx = invocation.Evaluator.CompositionEnv.CreateContext(ctx) + if invocation.Evaluator.CompositedCompiler != nil { + ctx = invocation.Evaluator.CompositedCompiler.CreateContext(ctx) } if len(invocation.Evaluator.Mutators) != len(invocation.Policy.Spec.Mutations) { // This would be a bug. The compiler should always return exactly as @@ -210,7 +210,7 @@ func (d *dispatcher) dispatchInvocations( policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins() reinvokeCtx.SetShouldReinvoke() } - if invocation.Policy.Spec.ReinvocationPolicy == v1beta1.IfNeededReinvocationPolicy { + if invocation.Policy.Spec.ReinvocationPolicy == admissionregistrationv1.IfNeededReinvocationPolicy { policyReinvokeCtx.AddReinvocablePolicyToPreviouslyInvoked(invocationKey) } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher_test.go index d4f01266c0623..7885d314408da 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher_test.go @@ -23,7 +23,6 @@ import ( "time" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - "k8s.io/api/admissionregistration/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -33,7 +32,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/admission/plugin/policy/generic" "k8s.io/apiserver/pkg/admission/plugin/policy/matching" "k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch" "k8s.io/client-go/informers" @@ -75,16 +73,16 @@ func TestDispatcher(t *testing.T) { }, }, }}, - policyHooks: []generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]{ + policyHooks: []PolicyHook{ { - Policy: mutations(matchConstraints(policy("policy1"), &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Policy: mutations(matchConstraints(policy("policy1"), &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -93,18 +91,18 @@ func TestDispatcher(t *testing.T) { }, }, }, - }), v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + }), admissionregistrationv1.Mutation{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ spec: Object.spec{ replicas: object.spec.replicas + 100 } }`, }}), - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy1", }, }}, @@ -166,16 +164,16 @@ func TestDispatcher(t *testing.T) { }, }, }, - policyHooks: []generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]{ + policyHooks: []PolicyHook{ { - Policy: paramKind(mutations(matchConstraints(policy("policy1"), &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Policy: paramKind(mutations(matchConstraints(policy("policy1"), &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -184,24 +182,24 @@ func TestDispatcher(t *testing.T) { }, }, }}), - v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + admissionregistrationv1.Mutation{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ spec: Object.spec{ replicas: object.spec.replicas + int(params.data['key']) } }`, }}), - &v1beta1.ParamKind{ + &admissionregistrationv1.ParamKind{ APIVersion: "v1", Kind: "ConfigMap", }), - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy1", - ParamRef: &v1beta1.ParamRef{Name: "cm1", Namespace: "default"}, + ParamRef: &admissionregistrationv1.ParamRef{Name: "cm1", Namespace: "default"}, }, }}, }, @@ -246,16 +244,16 @@ func TestDispatcher(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, }, }}, - policyHooks: []generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]{ + policyHooks: []PolicyHook{ { - Policy: mutations(matchConstraints(policy("policy1"), &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Policy: mutations(matchConstraints(policy("policy1"), &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -264,31 +262,31 @@ func TestDispatcher(t *testing.T) { }, }, }, - }), v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + }), admissionregistrationv1.Mutation{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ metadata: Object.metadata{ labels: {"policy1": string(int(object.?metadata.labels["count"].orValue("1")) + 1)} } }`, }}), - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy1", }, }}, }, { - Policy: mutations(matchConstraints(policy("policy2"), &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Policy: mutations(matchConstraints(policy("policy2"), &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -297,18 +295,18 @@ func TestDispatcher(t *testing.T) { }, }, }, - }), v1beta1.Mutation{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + }), admissionregistrationv1.Mutation{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ metadata: Object.metadata{ labels: {"policy2": string(int(object.?metadata.labels["count"].orValue("1")) + 1)} } }`, }}), - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy2", }, }}, @@ -357,21 +355,21 @@ func TestDispatcher(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, }, }}, - policyHooks: []generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]{ + policyHooks: []PolicyHook{ { - Policy: &v1beta1.MutatingAdmissionPolicy{ + Policy: &admissionregistrationv1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "policy1", }, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: admissionregistrationv1.MutatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -381,9 +379,9 @@ func TestDispatcher(t *testing.T) { }, }, }, - Mutations: []v1beta1.Mutation{{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + Mutations: []admissionregistrationv1.Mutation{{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ metadata: Object.metadata{ labels: {"environment": "production"} @@ -392,27 +390,27 @@ func TestDispatcher(t *testing.T) { }, }, }, - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy1", }, }}, }, { - Policy: &v1beta1.MutatingAdmissionPolicy{ + Policy: &admissionregistrationv1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "policy2", }, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: admissionregistrationv1.MutatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -422,15 +420,15 @@ func TestDispatcher(t *testing.T) { }, }, }, - MatchConditions: []v1beta1.MatchCondition{ + MatchConditions: []admissionregistrationv1.MatchCondition{ { Name: "prodonly", Expression: `object.?metadata.labels["environment"].orValue("") == "production"`, }, }, - Mutations: []v1beta1.Mutation{{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + Mutations: []admissionregistrationv1.Mutation{{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ metadata: Object.metadata{ labels: {"policy1invoked": "true"} @@ -439,9 +437,9 @@ func TestDispatcher(t *testing.T) { }, }, }, - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy2", }, }}, @@ -491,21 +489,21 @@ func TestDispatcher(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, }, }}, - policyHooks: []generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]{ + policyHooks: []PolicyHook{ { - Policy: &v1beta1.MutatingAdmissionPolicy{ + Policy: &admissionregistrationv1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "policy1", }, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: admissionregistrationv1.MutatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -515,15 +513,15 @@ func TestDispatcher(t *testing.T) { }, }, }, - MatchConditions: []v1beta1.MatchCondition{ + MatchConditions: []admissionregistrationv1.MatchCondition{ { Name: "prodonly", Expression: `object.?metadata.labels["environment"].orValue("") == "production"`, }, }, - Mutations: []v1beta1.Mutation{{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + Mutations: []admissionregistrationv1.Mutation{{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ metadata: Object.metadata{ labels: {"policy1invoked": "true"} @@ -532,27 +530,27 @@ func TestDispatcher(t *testing.T) { }, }, }, - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy1", }, }}, }, { - Policy: &v1beta1.MutatingAdmissionPolicy{ + Policy: &admissionregistrationv1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "policy2", }, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: admissionregistrationv1.MutatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistrationv1.MatchResources{ + MatchPolicy: ptr.To(admissionregistrationv1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, - ResourceRules: []v1beta1.NamedRuleWithOperations{ + ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{ { - RuleWithOperations: v1beta1.RuleWithOperations{ - Rule: v1beta1.Rule{ + RuleWithOperations: admissionregistrationv1.RuleWithOperations{ + Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, @@ -562,9 +560,9 @@ func TestDispatcher(t *testing.T) { }, }, }, - Mutations: []v1beta1.Mutation{{ - PatchType: v1beta1.PatchTypeApplyConfiguration, - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + Mutations: []admissionregistrationv1.Mutation{{ + PatchType: admissionregistrationv1.PatchTypeApplyConfiguration, + ApplyConfiguration: &admissionregistrationv1.ApplyConfiguration{ Expression: `Object{ metadata: Object.metadata{ labels: {"environment": "production"} @@ -573,9 +571,9 @@ func TestDispatcher(t *testing.T) { }, }, }, - Bindings: []*PolicyBinding{{ + Bindings: []*admissionregistrationv1.MutatingAdmissionPolicyBinding{{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: admissionregistrationv1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy2", }, }}, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go index 20e4d2d0c471c..01dd230dea2f8 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go @@ -18,18 +18,23 @@ package mutating import ( "context" - celgo "github.com/google/cel-go/cel" "io" - "k8s.io/api/admissionregistration/v1beta1" + celgo "github.com/google/cel-go/cel" + + v1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/cel" + "k8s.io/apiserver/pkg/admission/plugin/manifest/metrics" + "k8s.io/apiserver/pkg/admission/plugin/policy/config" "k8s.io/apiserver/pkg/admission/plugin/policy/generic" + "k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source" "k8s.io/apiserver/pkg/admission/plugin/policy/matching" "k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch" "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" @@ -49,13 +54,13 @@ const ( // Register registers a plugin func Register(plugins *admission.Plugins) { plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) { - return NewPlugin(configFile), nil + return NewPlugin(configFile) }) } -type Policy = v1beta1.MutatingAdmissionPolicy -type PolicyBinding = v1beta1.MutatingAdmissionPolicyBinding -type PolicyMutation = v1beta1.Mutation +type Policy = v1.MutatingAdmissionPolicy +type PolicyBinding = v1.MutatingAdmissionPolicyBinding + type PolicyHook = generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator] type Mutator struct { @@ -73,10 +78,10 @@ type MutationEvaluationFunc func( ) (runtime.Object, error) type PolicyEvaluator struct { - Matcher matchconditions.Matcher - Mutators []patch.Patcher - CompositionEnv *cel.CompositionEnv - Error error + Matcher matchconditions.Matcher + Mutators []patch.Patcher + CompositedCompiler *cel.CompositedCompiler + Error error } // Plugin is an implementation of admission.Interface. @@ -86,9 +91,43 @@ type Plugin struct { var _ admission.Interface = &Plugin{} var _ admission.MutationInterface = &Plugin{} +var _ initializer.WantsManifestLoaders = &Plugin{} + +// SetManifestLoaders provides the manifest load functions for scheme-based defaulting and validation. +func (a *Plugin) SetManifestLoaders(loaders *initializer.ManifestLoaders) { + if loaders == nil || loaders.LoadMutatingPolicyManifests == nil { + return + } + loadFunc := loaders.LoadMutatingPolicyManifests + a.SetStaticSourceFactory(func(manifestsDir string) (generic.ReloadableSource[PolicyHook], error) { + staticSource := source.NewStaticPolicySource(manifestsDir, a.GetAPIServerID(), + func(p *v1.MutatingAdmissionPolicy) (PolicyEvaluator, error) { + e := compilePolicy(p) + if e.Error != nil { + return PolicyEvaluator{}, e.Error + } + return e, nil + }, + func(dir string) ([]*v1.MutatingAdmissionPolicy, []*v1.MutatingAdmissionPolicyBinding, string, error) { + return loadFunc(dir) + }, + func(b *v1.MutatingAdmissionPolicyBinding) string { return b.Spec.PolicyName }, + metrics.MAPManifestType, + ) + if err := staticSource.LoadInitial(); err != nil { + return nil, err + } + return staticSource, nil + }) +} // NewPlugin returns a generic admission webhook plugin. -func NewPlugin(_ io.Reader) *Plugin { +func NewPlugin(configFile io.Reader) (*Plugin, error) { + cfg, err := config.LoadMutatingConfig(configFile) + if err != nil { + return nil, err + } + // There is no request body to mutate for DELETE, so this plugin never handles that operation. handler := admission.NewHandler(admission.Create, admission.Update, admission.Connect) res := &Plugin{} @@ -96,8 +135,8 @@ func NewPlugin(_ io.Reader) *Plugin { handler, func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] { return generic.NewPolicySource( - f.Admissionregistration().V1beta1().MutatingAdmissionPolicies().Informer(), - f.Admissionregistration().V1beta1().MutatingAdmissionPolicyBindings().Informer(), + f.Admissionregistration().V1().MutatingAdmissionPolicies().Informer(), + f.Admissionregistration().V1().MutatingAdmissionPolicyBindings().Informer(), NewMutatingAdmissionPolicyAccessor, NewMutatingAdmissionPolicyBindingAccessor, compilePolicy, @@ -112,7 +151,8 @@ func NewPlugin(_ io.Reader) *Plugin { return NewDispatcher(a, m, patch.NewTypeConverterManager(nil, client.Discovery().OpenAPIV3())) }, ) - return res + res.SetStaticManifestsDir(cfg.StaticManifestsDir) + return res, nil } // Admit makes an admission decision based on the request attributes. @@ -141,11 +181,3 @@ func (v *Variable) ReturnTypes() []*celgo.Type { func (v *Variable) GetName() string { return v.Name } - -func convertv1alpha1Variables(variables []v1beta1.Variable) []cel.NamedExpressionAccessor { - namedExpressions := make([]cel.NamedExpressionAccessor, len(variables)) - for i, variable := range variables { - namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression} - } - return namedExpressions -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin_test.go index e57f17ce8fd8d..2ab6cfd8f4ce4 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/require" - "k8s.io/api/admissionregistration/v1beta1" + v1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,10 +42,10 @@ import ( func setupTest( t *testing.T, - compiler func(*mutating.Policy) mutating.PolicyEvaluator, -) *generic.PolicyTestContext[*mutating.Policy, *mutating.PolicyBinding, mutating.PolicyEvaluator] { + compiler func(*v1.MutatingAdmissionPolicy) mutating.PolicyEvaluator, +) *generic.PolicyTestContext[*v1.MutatingAdmissionPolicy, *v1.MutatingAdmissionPolicyBinding, mutating.PolicyEvaluator] { - testContext, testCancel, err := generic.NewPolicyTestContext[*mutating.Policy, *mutating.PolicyBinding, mutating.PolicyEvaluator]( + testContext, testCancel, err := generic.NewPolicyTestContext[*v1.MutatingAdmissionPolicy, *v1.MutatingAdmissionPolicyBinding, mutating.PolicyEvaluator]( t, mutating.NewMutatingAdmissionPolicyAccessor, mutating.NewMutatingAdmissionPolicyBindingAccessor, @@ -81,33 +81,33 @@ func TestBasicPatch(t *testing.T) { expectedAnnotations := map[string]string{"foo": "bar"} // Treat all policies as setting foo annotation to bar - testContext := setupTest(t, func(p *mutating.Policy) mutating.PolicyEvaluator { + testContext := setupTest(t, func(p *v1.MutatingAdmissionPolicy) mutating.PolicyEvaluator { return mutating.PolicyEvaluator{Mutators: []patch.Patcher{annotationPatcher{expectedAnnotations}}} }) // Set up a policy and binding that match, no params require.NoError(t, testContext.UpdateAndWait( - &mutating.Policy{ + &v1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "policy"}, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: v1.MutatingAdmissionPolicySpec{ + MatchConstraints: &v1.MatchResources{ + MatchPolicy: ptr.To(v1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, }, - Mutations: []v1beta1.Mutation{ + Mutations: []v1.Mutation{ { - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + ApplyConfiguration: &v1.ApplyConfiguration{ Expression: "ignored, but required", }, - PatchType: v1beta1.PatchTypeApplyConfiguration, + PatchType: v1.PatchTypeApplyConfiguration, }, }, }, }, - &mutating.PolicyBinding{ + &v1.MutatingAdmissionPolicyBinding{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: v1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy", }, }, @@ -136,7 +136,7 @@ func TestJSONPatch(t *testing.T) { }, } - testContext := setupTest(t, func(p *mutating.Policy) mutating.PolicyEvaluator { + testContext := setupTest(t, func(p *v1.MutatingAdmissionPolicy) mutating.PolicyEvaluator { return mutating.PolicyEvaluator{ Mutators: []patch.Patcher{smdPatcher{patch: patchObj}}, } @@ -144,27 +144,27 @@ func TestJSONPatch(t *testing.T) { // Set up a policy and binding that match, no params require.NoError(t, testContext.UpdateAndWait( - &mutating.Policy{ + &v1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "policy"}, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: v1.MutatingAdmissionPolicySpec{ + MatchConstraints: &v1.MatchResources{ + MatchPolicy: ptr.To(v1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, }, - Mutations: []v1beta1.Mutation{ + Mutations: []v1.Mutation{ { - JSONPatch: &v1beta1.JSONPatch{ + JSONPatch: &v1.JSONPatch{ Expression: "ignored, but required", }, - PatchType: v1beta1.PatchTypeApplyConfiguration, + PatchType: v1.PatchTypeApplyConfiguration, }, }, }, }, - &mutating.PolicyBinding{ + &v1.MutatingAdmissionPolicyBinding{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: v1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy", }, }, @@ -198,7 +198,7 @@ func TestSSAPatch(t *testing.T) { }, } - testContext := setupTest(t, func(p *mutating.Policy) mutating.PolicyEvaluator { + testContext := setupTest(t, func(p *v1.MutatingAdmissionPolicy) mutating.PolicyEvaluator { return mutating.PolicyEvaluator{ Mutators: []patch.Patcher{smdPatcher{patch: patchObj}}, } @@ -206,27 +206,27 @@ func TestSSAPatch(t *testing.T) { // Set up a policy and binding that match, no params require.NoError(t, testContext.UpdateAndWait( - &mutating.Policy{ + &v1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "policy"}, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: v1.MutatingAdmissionPolicySpec{ + MatchConstraints: &v1.MatchResources{ + MatchPolicy: ptr.To(v1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, }, - Mutations: []v1beta1.Mutation{ + Mutations: []v1.Mutation{ { - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + ApplyConfiguration: &v1.ApplyConfiguration{ Expression: "ignored, but required", }, - PatchType: v1beta1.PatchTypeApplyConfiguration, + PatchType: v1.PatchTypeApplyConfiguration, }, }, }, }, - &mutating.PolicyBinding{ + &v1.MutatingAdmissionPolicyBinding{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: v1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy", }, }, @@ -265,7 +265,7 @@ func TestSSAMapList(t *testing.T) { }, } - testContext := setupTest(t, func(p *mutating.Policy) mutating.PolicyEvaluator { + testContext := setupTest(t, func(p *v1.MutatingAdmissionPolicy) mutating.PolicyEvaluator { return mutating.PolicyEvaluator{ Mutators: []patch.Patcher{smdPatcher{patch: patchObj}}, } @@ -273,27 +273,27 @@ func TestSSAMapList(t *testing.T) { // Set up a policy and binding that match, no params require.NoError(t, testContext.UpdateAndWait( - &mutating.Policy{ + &v1.MutatingAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "policy"}, - Spec: v1beta1.MutatingAdmissionPolicySpec{ - MatchConstraints: &v1beta1.MatchResources{ - MatchPolicy: ptr.To(v1beta1.Equivalent), + Spec: v1.MutatingAdmissionPolicySpec{ + MatchConstraints: &v1.MatchResources{ + MatchPolicy: ptr.To(v1.Equivalent), NamespaceSelector: &metav1.LabelSelector{}, ObjectSelector: &metav1.LabelSelector{}, }, - Mutations: []v1beta1.Mutation{ + Mutations: []v1.Mutation{ { - ApplyConfiguration: &v1beta1.ApplyConfiguration{ + ApplyConfiguration: &v1.ApplyConfiguration{ Expression: "ignored, but required", }, - PatchType: v1beta1.PatchTypeApplyConfiguration, + PatchType: v1.PatchTypeApplyConfiguration, }, }, }, }, - &mutating.PolicyBinding{ + &v1.MutatingAdmissionPolicyBinding{ ObjectMeta: metav1.ObjectMeta{Name: "binding"}, - Spec: v1beta1.MutatingAdmissionPolicyBindingSpec{ + Spec: v1.MutatingAdmissionPolicyBindingSpec{ PolicyName: "policy", }, }, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/admission_test.go index baad6a9d9f204..69ee612eca632 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/admission_test.go @@ -199,6 +199,8 @@ type validateFunc func( runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult +func (f validateFunc) CompileError() error { return nil } + type fakeCompiler struct { ValidateFuncs map[types.NamespacedName]validating.Validator diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/interface.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/interface.go index 97eeb95504a47..fd8015e7ce5d3 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/interface.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/interface.go @@ -92,4 +92,6 @@ type Validator interface { // Validate is used to take cel evaluations and convert into decisions // runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input. Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult + // CompileError returns any error from CEL compilation, or nil if compilation succeeded. + CompileError() error } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go index bdca01e691a73..348ecde2d0ea1 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go @@ -26,7 +26,10 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/cel" + "k8s.io/apiserver/pkg/admission/plugin/manifest/metrics" + "k8s.io/apiserver/pkg/admission/plugin/policy/config" "k8s.io/apiserver/pkg/admission/plugin/policy/generic" + "k8s.io/apiserver/pkg/admission/plugin/policy/manifest/source" "k8s.io/apiserver/pkg/admission/plugin/policy/matching" "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -43,16 +46,12 @@ const ( var ( lazyCompositionEnvTemplateWithStrictCostInit sync.Once - lazyCompositionEnvTemplateWithStrictCost *cel.CompositionEnv + lazyCompositionEnvTemplateWithStrictCost *environment.EnvSet ) -func getCompositionEnvTemplateWithStrictCost() *cel.CompositionEnv { +func getCompositionEnvTemplateWithStrictCost() *environment.EnvSet { lazyCompositionEnvTemplateWithStrictCostInit.Do(func() { - env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) - if err != nil { - panic(err) - } - lazyCompositionEnvTemplateWithStrictCost = env + lazyCompositionEnvTemplateWithStrictCost = environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()) }) return lazyCompositionEnvTemplateWithStrictCost } @@ -60,7 +59,7 @@ func getCompositionEnvTemplateWithStrictCost() *cel.CompositionEnv { // Register registers a plugin func Register(plugins *admission.Plugins) { plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) { - return NewPlugin(configFile), nil + return NewPlugin(configFile) }) } @@ -77,8 +76,42 @@ type Plugin struct { var _ admission.Interface = &Plugin{} var _ admission.ValidationInterface = &Plugin{} var _ initializer.WantsExcludedAdmissionResources = &Plugin{} +var _ initializer.WantsManifestLoaders = &Plugin{} + +// SetManifestLoaders provides the manifest load functions for scheme-based defaulting and validation. +func (a *Plugin) SetManifestLoaders(loaders *initializer.ManifestLoaders) { + if loaders == nil || loaders.LoadValidatingPolicyManifests == nil { + return + } + loadFunc := loaders.LoadValidatingPolicyManifests + a.SetStaticSourceFactory(func(manifestsDir string) (generic.ReloadableSource[PolicyHook], error) { + staticSource := source.NewStaticPolicySource(manifestsDir, a.GetAPIServerID(), + func(p *v1.ValidatingAdmissionPolicy) (Validator, error) { + v := compilePolicy(p) + if err := v.CompileError(); err != nil { + return nil, err + } + return v, nil + }, + func(dir string) ([]*v1.ValidatingAdmissionPolicy, []*v1.ValidatingAdmissionPolicyBinding, string, error) { + return loadFunc(dir) + }, + func(b *v1.ValidatingAdmissionPolicyBinding) string { return b.Spec.PolicyName }, + metrics.VAPManifestType, + ) + if err := staticSource.LoadInitial(); err != nil { + return nil, err + } + return staticSource, nil + }) +} + +func NewPlugin(configFile io.Reader) (*Plugin, error) { + cfg, err := config.LoadValidatingConfig(configFile) + if err != nil { + return nil, err + } -func NewPlugin(_ io.Reader) *Plugin { handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update) p := &Plugin{ @@ -102,7 +135,8 @@ func NewPlugin(_ io.Reader) *Plugin { ), } p.SetEnabled(true) - return p + p.SetStaticManifestsDir(cfg.StaticManifestsDir) + return p, nil } // Validate makes an admission decision based on the request attributes. @@ -120,9 +154,11 @@ func compilePolicy(policy *Policy) Validator { failurePolicy := policy.Spec.FailurePolicy var matcher matchconditions.Matcher = nil matchConditions := policy.Spec.MatchConditions - var compositionEnvTemplate *cel.CompositionEnv - compositionEnvTemplate = getCompositionEnvTemplateWithStrictCost() - filterCompiler := cel.NewCompositedCompilerFromTemplate(compositionEnvTemplate) + compositionEnvTemplate := getCompositionEnvTemplateWithStrictCost() + filterCompiler, err := cel.NewCompositedCompiler(compositionEnvTemplate) + if err != nil { + return NewValidator(nil, nil, nil, nil, failurePolicy, err) + } filterCompiler.CompileAndStoreVariables(convertv1beta1Variables(policy.Spec.Variables), optionalVars, environment.StoredExpressions) if len(matchConditions) > 0 { @@ -138,6 +174,7 @@ func compilePolicy(policy *Policy) Validator { filterCompiler.CompileCondition(convertv1AuditAnnotations(policy.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions), filterCompiler.CompileCondition(convertv1MessageExpressions(policy.Spec.Validations), expressionOptionalVars, environment.StoredExpressions), failurePolicy, + nil, ) return res diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking.go index 6a284e6de950c..c8b258f16357f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking.go @@ -177,20 +177,17 @@ func (c *TypeChecker) CreateContext(policy *v1.ValidatingAdmissionPolicy) *TypeC func (c *TypeChecker) compiler(ctx *TypeCheckingContext, typeOverwrite typeOverwrite) (*plugincel.CompositedCompiler, error) { envSet, err := buildEnvSet( - /* hasParams */ ctx.paramDeclType != nil, + /* hasParams */ !ctx.paramGVK.Empty(), /* hasAuthorizer */ true, typeOverwrite) if err != nil { return nil, err } - env, err := plugincel.NewCompositionEnv(plugincel.VariablesTypeName, envSet) + compiler, err := plugincel.NewCompositedCompilerForTypeChecking(envSet) if err != nil { return nil, err } - compiler := &plugincel.CompositedCompiler{ - Compiler: &typeCheckingCompiler{typeOverwrite: typeOverwrite, compositionEnv: env}, - CompositionEnv: env, - } + compiler.Compiler = &typeCheckingCompiler{typeOverwrite: typeOverwrite, compiler: compiler} return compiler, nil } @@ -208,7 +205,7 @@ func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression strin continue } options := plugincel.OptionalVariableDeclarations{ - HasParams: ctx.paramDeclType != nil, + HasParams: !ctx.paramGVK.Empty(), HasAuthorizer: true, } compiler.CompileAndStoreVariables(convertv1beta1Variables(ctx.variables), options, environment.StoredExpressions) @@ -247,7 +244,11 @@ func (c *TypeChecker) declType(gvk schema.GroupVersionKind) (*apiservercel.DeclT if err != nil { return nil, err } - return common.SchemaDeclType(&openapi.Schema{Schema: s}, true).MaybeAssignTypeName(generateUniqueTypeName(gvk.Kind)), nil + declType := common.SchemaDeclType(&openapi.Schema{Schema: s}, true) + if declType == nil { + return nil, nil + } + return declType.MaybeAssignTypeName(generateUniqueTypeName(gvk.Kind)), nil } func (c *TypeChecker) paramsGVK(policy *v1.ValidatingAdmissionPolicy) schema.GroupVersionKind { @@ -407,12 +408,16 @@ func buildEnvSet(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*envi varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...) // object and oldObject, same type, type(s) resolved from constraints - declTypes = append(declTypes, types.object) + if types.object != nil { + declTypes = append(declTypes, types.object) + } varOpts = append(varOpts, createVariableOpts(types.object, plugincel.ObjectVarName, plugincel.OldObjectVarName)...) // params, defined by ParamKind - if hasParams && types.params != nil { - declTypes = append(declTypes, types.params) + if hasParams { + if types.params != nil { + declTypes = append(declTypes, types.params) + } varOpts = append(varOpts, createVariableOpts(types.params, plugincel.ParamsVarName)...) } @@ -449,8 +454,8 @@ func createVariableOpts(declType *apiservercel.DeclType, variables ...string) [] } type typeCheckingCompiler struct { - compositionEnv *plugincel.CompositionEnv - typeOverwrite typeOverwrite + compiler *plugincel.CompositedCompiler + typeOverwrite typeOverwrite } // CompileCELExpression compiles the given expression. @@ -469,7 +474,7 @@ func (c *typeCheckingCompiler) CompileCELExpression(expressionAccessor plugincel ExpressionAccessor: expressionAccessor, } } - env, err := c.compositionEnv.Env(mode) + env, err := c.compiler.Env(mode) if err != nil { return resultError(fmt.Sprintf("fail to build env: %v", err), apiservercel.ErrorTypeInternal) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking_test.go index 2a78ac6284790..63243f431d06f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking_test.go @@ -214,6 +214,24 @@ func TestTypeCheck(t *testing.T) { }, }}, }} + noTypeSchemaPolicy := &v1.ValidatingAdmissionPolicy{Spec: v1.ValidatingAdmissionPolicySpec{ + Validations: []v1.Validation{ + { + Expression: "true", + }, + }, + MatchConstraints: &v1.MatchResources{ResourceRules: []v1.NamedRuleWithOperations{ + { + RuleWithOperations: v1.RuleWithOperations{ + Rule: v1.Rule{ + APIGroups: []string{"apps"}, + APIVersions: []string{"v1"}, + Resources: []string{"deployments"}, + }, + }, + }, + }}, + }} deploymentPolicyWithBadMessageExpression := deploymentPolicy.DeepCopy() deploymentPolicyWithBadMessageExpression.Spec.Validations[0].MessageExpression = "object.foo + 114514" // confusion @@ -395,6 +413,23 @@ func TestTypeCheck(t *testing.T) { toContain(`undefined field 'bar'`), }, }, + { + name: "params with untyped schema", + policy: &v1.ValidatingAdmissionPolicy{Spec: v1.ValidatingAdmissionPolicySpec{ + ParamKind: &v1.ParamKind{ + APIVersion: "v1", + Kind: "Config", + }, + Validations: []v1.Validation{ + { + Expression: "params != null", + }, + }, + MatchConstraints: deploymentPolicy.Spec.MatchConstraints, + }}, + schemaToReturn: &spec.Schema{}, + assertions: []assertionFunc{toBeEmpty}, + }, { name: "multiple expressions", policy: multiExpressionPolicy, @@ -490,6 +525,12 @@ func TestTypeCheck(t *testing.T) { }, assertions: []assertionFunc{toBeEmpty}, }, + { + name: "schema without type", + policy: noTypeSchemaPolicy, + schemaToReturn: &spec.Schema{}, + assertions: []assertionFunc{toBeEmpty}, + }, { name: "variables valid", policy: &v1.ValidatingAdmissionPolicy{Spec: v1.ValidatingAdmissionPolicySpec{ diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go index 4057e515ee253..2144795fd881e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go @@ -45,15 +45,19 @@ type validator struct { auditAnnotationFilter cel.ConditionEvaluator messageFilter cel.ConditionEvaluator failPolicy *v1.FailurePolicyType + // compileError holds any compilation error from the CEL expressions. + // If non-nil, the validator will return an error result based on the failPolicy. + compileError error } -func NewValidator(validationFilter cel.ConditionEvaluator, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.ConditionEvaluator, failPolicy *v1.FailurePolicyType) Validator { +func NewValidator(validationFilter cel.ConditionEvaluator, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.ConditionEvaluator, failPolicy *v1.FailurePolicyType, err error) Validator { return &validator{ celMatcher: celMatcher, validationFilter: validationFilter, auditAnnotationFilter: auditAnnotationFilter, messageFilter: messageFilter, failPolicy: failPolicy, + compileError: err, } } @@ -71,6 +75,10 @@ func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnota return AuditAnnotationActionError } +func (v *validator) CompileError() error { + return v.compileError +} + // Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions // runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input. @@ -81,6 +89,17 @@ func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVe } else { f = *v.failPolicy } + if v.compileError != nil { + return ValidateResult{ + Decisions: []PolicyDecision{ + { + Action: policyDecisionActionForError(f), + Evaluation: EvalError, + Message: v.compileError.Error(), + }, + }, + } + } if v.celMatcher != nil { matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams, authz) if matchResults.Error != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator_test.go index 5100d00777975..f6cc71d8e0ad3 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator_test.go @@ -53,7 +53,7 @@ func (f *fakeCelFilter) ForInput(ctx context.Context, versionedAttr *admission.V if costBudget <= 0 { // this filter will cost 1, so cost = 0 means fail. return nil, -1, &apiservercel.Error{ Type: apiservercel.ErrorTypeInvalid, - Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), + Detail: "validation failed due to running out of cost budget, no further validation rules will be run", Cause: apiservercel.ErrOutOfBudget, } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go index 5455b414eda66..883ac4c625d87 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go @@ -26,7 +26,6 @@ import ( "k8s.io/apiserver/pkg/admission" genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" resourcequotaapi "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota" - v1 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1" "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation" quota "k8s.io/apiserver/pkg/quota/v1" "k8s.io/apiserver/pkg/quota/v1/generic" @@ -38,7 +37,7 @@ import ( const PluginName = "ResourceQuota" var ( - namespaceGVK = v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() + namespaceGVK = corev1.SchemeGroupVersion.WithKind("Namespace").GroupKind() stopChUnconfiguredErr = fmt.Errorf("quota configuration configured between stop channel") ) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go index 0cc3d7be137c6..eac3a051cf86c 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go @@ -25,7 +25,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" - v1 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1" "k8s.io/apiserver/pkg/quota/v1/generic" ) @@ -149,7 +148,7 @@ func TestExcludedOperations(t *testing.T) { }, { "namespace creation", - admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace"), "namespace", "namespace", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil), + admission.NewAttributesRecord(nil, nil, corev1.SchemeGroupVersion.WithKind("Namespace"), "namespace", "namespace", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil), }, } for _, test := range testCases { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/controller.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/controller.go index 95c9c84f6f276..78022342db00e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/controller.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/controller.go @@ -65,7 +65,7 @@ type quotaEvaluator struct { workLock sync.Mutex work map[string][]*admissionWaiter dirtyWork map[string][]*admissionWaiter - inProgress sets.String + inProgress sets.Set[string] // controls the run method so that we can cleanly conform to the Evaluator interface workers int @@ -125,7 +125,7 @@ func NewQuotaEvaluator(quotaAccessor QuotaAccessor, ignoredResources map[schema. queue: workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[string]{Name: "admission_quota_controller"}), work: map[string][]*admissionWaiter{}, dirtyWork: map[string][]*admissionWaiter{}, - inProgress: sets.String{}, + inProgress: sets.Set[string]{}, workers: workers, stopCh: stopCh, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/install.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/install.go index b724c346afc9d..0dea394599b89 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/install.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/install.go @@ -23,13 +23,10 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission" v1 "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1" - "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1" ) // Install registers the API group and adds types to a scheme func Install(scheme *runtime.Scheme) { utilruntime.Must(webhookadmission.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) - utilruntime.Must(v1alpha1.AddToScheme(scheme)) - utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1alpha1.SchemeGroupVersion)) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go index 71ce47b1f42e9..f6ba94e7cd30a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go @@ -26,4 +26,14 @@ type WebhookAdmission struct { // KubeConfigFile is the path to the kubeconfig file. KubeConfigFile string + + // StaticManifestsDir is the path to a directory containing static webhook + // configurations to be loaded at startup. Files with extensions .yaml, + // .yml, and .json are read. Only admissionregistration.k8s.io/v1 + // ValidatingWebhookConfiguration and MutatingWebhookConfiguration + // resources are supported. + // Using this field requires the ManifestBasedAdmissionControlConfig + // feature gate to be enabled. + // +optional + StaticManifestsDir string } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/types.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/types.go index 632427d7df9f3..be0920534896f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/types.go @@ -26,4 +26,14 @@ type WebhookAdmission struct { // KubeConfigFile is the path to the kubeconfig file. KubeConfigFile string `json:"kubeConfigFile"` + + // StaticManifestsDir is the path to a directory containing static webhook + // configurations to be loaded at startup. Files with extensions .yaml, + // .yml, and .json are read. Only admissionregistration.k8s.io/v1 + // ValidatingWebhookConfiguration and MutatingWebhookConfiguration + // resources are supported. + // Using this field requires the ManifestBasedAdmissionControlConfig + // feature gate to be enabled. + // +optional + StaticManifestsDir string `json:"staticManifestsDir,omitempty"` } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.conversion.go index 4cf69291b3a7d..90235e1e9d6ca 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.conversion.go @@ -49,6 +49,7 @@ func RegisterConversions(s *runtime.Scheme) error { func autoConvert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error { out.KubeConfigFile = in.KubeConfigFile + out.StaticManifestsDir = in.StaticManifestsDir return nil } @@ -59,6 +60,7 @@ func Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *Webhoo func autoConvert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error { out.KubeConfigFile = in.KubeConfigFile + out.StaticManifestsDir = in.StaticManifestsDir return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/doc.go deleted file mode 100644 index 703f467f9fcc7..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -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. -*/ - -// +k8s:deepcopy-gen=package -// +k8s:conversion-gen=k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission -// +k8s:defaulter-gen=TypeMeta -// +groupName=apiserver.config.k8s.io - -// Package v1alpha1 is the v1alpha1 version of the API. -package v1alpha1 diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/register.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/register.go deleted file mode 100644 index 56489f7804cce..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/register.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -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. -*/ - -package v1alpha1 - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "apiserver.config.k8s.io" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} - -var ( - // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. - // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &WebhookAdmission{}, - ) - return nil -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/types.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/types.go deleted file mode 100644 index a49a6a813ea45..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/types.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -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. -*/ - -package v1alpha1 - -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// WebhookAdmission provides configuration for the webhook admission controller. -type WebhookAdmission struct { - metav1.TypeMeta `json:",inline"` - - // KubeConfigFile is the path to the kubeconfig file. - KubeConfigFile string `json:"kubeConfigFile"` -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.conversion.go deleted file mode 100644 index 66aaecbd86373..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.conversion.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - webhookadmission "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*WebhookAdmission)(nil), (*webhookadmission.WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(a.(*WebhookAdmission), b.(*webhookadmission.WebhookAdmission), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*webhookadmission.WebhookAdmission)(nil), (*WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(a.(*webhookadmission.WebhookAdmission), b.(*WebhookAdmission), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error { - out.KubeConfigFile = in.KubeConfigFile - return nil -} - -// Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission is an autogenerated conversion function. -func Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error { - return autoConvert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in, out, s) -} - -func autoConvert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error { - out.KubeConfigFile = in.KubeConfigFile - return nil -} - -// Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission is an autogenerated conversion function. -func Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error { - return autoConvert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(in, out, s) -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index f997f4abaf3e7..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebhookAdmission) DeepCopyInto(out *WebhookAdmission) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookAdmission. -func (in *WebhookAdmission) DeepCopy() *WebhookAdmission { - if in == nil { - return nil - } - out := new(WebhookAdmission) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *WebhookAdmission) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.defaults.go deleted file mode 100644 index 5070cb91b90f1..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.defaults.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go index 7b845f1d19ab4..1b751effa0936 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go @@ -25,46 +25,49 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission/plugin/manifest" "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission" - "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1" - "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1" + v1 "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1" ) var ( scheme = runtime.NewScheme() - codecs = serializer.NewCodecFactory(scheme) + codecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict) ) func init() { utilruntime.Must(webhookadmission.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) - utilruntime.Must(v1alpha1.AddToScheme(scheme)) } -// LoadConfig extract the KubeConfigFile from configFile -func LoadConfig(configFile io.Reader) (string, error) { - var kubeconfigFile string - if configFile != nil { - // we have a config so parse it. - data, err := io.ReadAll(configFile) - if err != nil { - return "", err - } - decoder := codecs.UniversalDecoder() - decodedObj, err := runtime.Decode(decoder, data) - if err != nil { - return "", err - } - config, ok := decodedObj.(*webhookadmission.WebhookAdmission) - if !ok { - return "", fmt.Errorf("unexpected type: %T", decodedObj) - } +// LoadConfig extracts the webhook admission configuration from configFile. +func LoadConfig(configFile io.Reader) (*webhookadmission.WebhookAdmission, error) { + if configFile == nil { + return &webhookadmission.WebhookAdmission{}, nil + } + // we have a config so parse it. + data, err := io.ReadAll(configFile) + if err != nil { + return nil, err + } + decoder := codecs.UniversalDecoder() + decodedObj, err := runtime.Decode(decoder, data) + if err != nil { + return nil, err + } + config, ok := decodedObj.(*webhookadmission.WebhookAdmission) + if !ok { + return nil, fmt.Errorf("unexpected type: %T", decodedObj) + } - if !path.IsAbs(config.KubeConfigFile) { - return "", field.Invalid(field.NewPath("kubeConfigFile"), config.KubeConfigFile, "must be an absolute file path") - } + // KubeConfigFile may be empty when only staticManifestsDir is configured. + if len(config.KubeConfigFile) > 0 && !path.IsAbs(config.KubeConfigFile) { + return nil, field.Invalid(field.NewPath("kubeConfigFile"), config.KubeConfigFile, "must be an absolute file path") + } - kubeconfigFile = config.KubeConfigFile + if err := manifest.ValidateStaticManifestsDir(config.StaticManifestsDir); err != nil { + return nil, err } - return kubeconfigFile, nil + + return config, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig_test.go index 101e686805352..c4d3c49c3e5ca 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig_test.go @@ -18,16 +18,32 @@ package config import ( "bytes" + "os" + "path" "strings" "testing" + + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" ) func TestLoadConfig(t *testing.T) { + // Create a temp directory for tests that need a valid directory path + validDir := t.TempDir() + // Create a temp file for "not a directory" test + notADirFile := path.Join(t.TempDir(), "file.txt") + if err := os.WriteFile(notADirFile, []byte(""), 0644); err != nil { + t.Fatal(err) + } + testcases := []struct { - name string - input string - expectErr string - expectKubeconfig string + name string + input string + enableFeatureGate *bool + expectErr string + expectKubeconfig string + expectStaticManifestsDir string }{ { name: "empty", @@ -40,28 +56,88 @@ func TestLoadConfig(t *testing.T) { expectErr: `no kind "Unknown" is registered for version "v1"`, }, { - name: "valid v1alpha1", + name: "valid v1", input: ` -kind: WebhookAdmission -apiVersion: apiserver.config.k8s.io/v1alpha1 +kind: WebhookAdmissionConfiguration +apiVersion: apiserver.config.k8s.io/v1 kubeConfigFile: /foo `, expectKubeconfig: "/foo", }, { - name: "valid v1", + name: "valid v1 with staticManifestsDir", + enableFeatureGate: new(true), input: ` kind: WebhookAdmissionConfiguration apiVersion: apiserver.config.k8s.io/v1 kubeConfigFile: /foo +staticManifestsDir: ` + validDir + ` `, - expectKubeconfig: "/foo", + expectKubeconfig: "/foo", + expectStaticManifestsDir: validDir, + }, + { + name: "invalid relative staticManifestsDir", + enableFeatureGate: new(true), + input: ` +kind: WebhookAdmissionConfiguration +apiVersion: apiserver.config.k8s.io/v1 +kubeConfigFile: /foo +staticManifestsDir: relative/path +`, + expectErr: `staticManifestsDir: Invalid value: "relative/path": must be an absolute file path`, + }, + { + name: "staticManifestsDir forbidden when feature gate disabled", + enableFeatureGate: new(false), + input: ` +kind: WebhookAdmissionConfiguration +apiVersion: apiserver.config.k8s.io/v1 +kubeConfigFile: /foo +staticManifestsDir: /etc/kubernetes/admission +`, + expectErr: "staticManifestsDir: Forbidden", + }, + { + name: "staticManifestsDir must be a directory", + enableFeatureGate: new(true), + input: ` +kind: WebhookAdmissionConfiguration +apiVersion: apiserver.config.k8s.io/v1 +kubeConfigFile: /foo +staticManifestsDir: ` + notADirFile + ` +`, + expectErr: "must be a directory", + }, + { + name: "staticManifestsDir must exist", + enableFeatureGate: new(true), + input: ` +kind: WebhookAdmissionConfiguration +apiVersion: apiserver.config.k8s.io/v1 +kubeConfigFile: /foo +staticManifestsDir: /nonexistent/path +`, + expectErr: "unable to read", + }, + { + name: "valid staticManifestsDir only (no kubeConfigFile)", + enableFeatureGate: new(true), + input: ` +kind: WebhookAdmissionConfiguration +apiVersion: apiserver.config.k8s.io/v1 +staticManifestsDir: ` + validDir + ` +`, + expectStaticManifestsDir: validDir, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - kubeconfig, err := LoadConfig(bytes.NewBufferString(tc.input)) + if tc.enableFeatureGate != nil { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ManifestBasedAdmissionControlConfig, *tc.enableFeatureGate) + } + cfg, err := LoadConfig(bytes.NewBufferString(tc.input)) if len(tc.expectErr) > 0 { if err == nil { t.Fatal("expected err, got none") @@ -74,8 +150,11 @@ kubeConfigFile: /foo if err != nil { t.Fatal(err) } - if kubeconfig != tc.expectKubeconfig { - t.Fatalf("expected %q, got %q", tc.expectKubeconfig, kubeconfig) + if cfg.KubeConfigFile != tc.expectKubeconfig { + t.Fatalf("expected KubeConfigFile %q, got %q", tc.expectKubeconfig, cfg.KubeConfigFile) + } + if cfg.StaticManifestsDir != tc.expectStaticManifestsDir { + t.Fatalf("expected StaticManifestsDir %q, got %q", tc.expectStaticManifestsDir, cfg.StaticManifestsDir) } }) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/composite_webhook_source.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/composite_webhook_source.go new file mode 100644 index 0000000000000..3b38c9fade1a5 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/composite_webhook_source.go @@ -0,0 +1,99 @@ +/* +Copyright 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 generic + +import ( + "sync" + + "k8s.io/apiserver/pkg/admission/plugin/webhook" +) + +// compositeWebhookSource combines multiple webhook sources into a single source. +// Static (manifest-based) webhooks are returned before API-based webhooks. +type compositeWebhookSource struct { + staticSource Source + apiSource Source + + mu sync.RWMutex + lastStaticSlice []webhook.WebhookAccessor + lastAPISlice []webhook.WebhookAccessor + lastCombined []webhook.WebhookAccessor +} + +var _ Source = &compositeWebhookSource{} + +// NewCompositeWebhookSource creates a webhook source that combines static and API-based sources. +// Static webhooks are evaluated first, followed by API-based webhooks. +// If staticSource is nil, only apiSource webhooks are returned. +func NewCompositeWebhookSource(staticSource, apiSource Source) Source { + if staticSource == nil { + return apiSource + } + return &compositeWebhookSource{ + staticSource: staticSource, + apiSource: apiSource, + } +} + +// Webhooks returns all webhook accessors from both sources. +// Static webhooks come first, followed by API-based webhooks. +// The combined slice is cached and reused when the underlying slices haven't changed. +func (c *compositeWebhookSource) Webhooks() []webhook.WebhookAccessor { + var staticSlice, apiSlice []webhook.WebhookAccessor + if c.staticSource != nil { + staticSlice = c.staticSource.Webhooks() + } + if c.apiSource != nil { + apiSlice = c.apiSource.Webhooks() + } + + c.mu.RLock() + if slicesAreEqual(c.lastStaticSlice, staticSlice) && slicesAreEqual(c.lastAPISlice, apiSlice) { + combined := c.lastCombined + c.mu.RUnlock() + return combined + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + // Re-check under write lock. + if slicesAreEqual(c.lastStaticSlice, staticSlice) && slicesAreEqual(c.lastAPISlice, apiSlice) { + return c.lastCombined + } + + combined := make([]webhook.WebhookAccessor, 0, len(staticSlice)+len(apiSlice)) + combined = append(combined, staticSlice...) + combined = append(combined, apiSlice...) + + c.lastStaticSlice = staticSlice + c.lastAPISlice = apiSlice + c.lastCombined = combined + return combined +} + +// slicesAreEqual reports whether two slices share the same backing array and length. +func slicesAreEqual[T any](a, b []T) bool { + return len(a) == len(b) && (len(a) == 0 || &a[0] == &b[0]) +} + +// HasSynced returns true only when both sources have synced. +func (c *compositeWebhookSource) HasSynced() bool { + staticSynced := c.staticSource == nil || c.staticSource.HasSynced() + apiSynced := c.apiSource == nil || c.apiSource.HasSynced() + return staticSynced && apiSynced +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/composite_webhook_source_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/composite_webhook_source_test.go new file mode 100644 index 0000000000000..56f2f3c7a06b5 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/composite_webhook_source_test.go @@ -0,0 +1,187 @@ +/* +Copyright 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 generic + +import ( + "testing" + + v1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apiserver/pkg/admission/plugin/webhook" +) + +// mockSource implements Source for testing. +type mockSource struct { + webhooks []webhook.WebhookAccessor + hasSynced bool +} + +func (m *mockSource) Webhooks() []webhook.WebhookAccessor { + return m.webhooks +} + +func (m *mockSource) HasSynced() bool { + return m.hasSynced +} + +var _ Source = &mockSource{} + +func createTestValidatingWebhook(uid, name string) webhook.WebhookAccessor { + return webhook.NewValidatingWebhookAccessor(uid, name, &v1.ValidatingWebhook{ + Name: "test.webhook.io", + ClientConfig: v1.WebhookClientConfig{ + URL: new("https://example.com"), + }, + AdmissionReviewVersions: []string{"v1"}, + }) +} + +func TestCompositeWebhookSource_Webhooks(t *testing.T) { + staticWebhook := createTestValidatingWebhook("static-1", "static-config") + apiWebhook := createTestValidatingWebhook("api-1", "api-config") + + tests := []struct { + name string + staticSource Source + apiSource Source + wantUIDs []string + }{ + { + name: "only static source", + staticSource: &mockSource{webhooks: []webhook.WebhookAccessor{staticWebhook}, hasSynced: true}, + apiSource: &mockSource{webhooks: nil, hasSynced: true}, + wantUIDs: []string{"static-1"}, + }, + { + name: "only api source", + staticSource: &mockSource{webhooks: nil, hasSynced: true}, + apiSource: &mockSource{webhooks: []webhook.WebhookAccessor{apiWebhook}, hasSynced: true}, + wantUIDs: []string{"api-1"}, + }, + { + name: "both sources - static first", + staticSource: &mockSource{webhooks: []webhook.WebhookAccessor{staticWebhook}, hasSynced: true}, + apiSource: &mockSource{webhooks: []webhook.WebhookAccessor{apiWebhook}, hasSynced: true}, + wantUIDs: []string{"static-1", "api-1"}, + }, + { + name: "nil static source", + staticSource: nil, + apiSource: &mockSource{webhooks: []webhook.WebhookAccessor{apiWebhook}, hasSynced: true}, + wantUIDs: []string{"api-1"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + source := NewCompositeWebhookSource(tt.staticSource, tt.apiSource) + webhooks := source.Webhooks() + + if len(webhooks) != len(tt.wantUIDs) { + t.Errorf("Webhooks() returned %d webhooks, want %d", len(webhooks), len(tt.wantUIDs)) + return + } + + for i, w := range webhooks { + if w.GetUID() != tt.wantUIDs[i] { + t.Errorf("Webhooks()[%d].GetUID() = %s, want %s", i, w.GetUID(), tt.wantUIDs[i]) + } + } + }) + } +} + +func TestCompositeWebhookSource_Caching(t *testing.T) { + staticWebhook := createTestValidatingWebhook("static-1", "static-config") + apiWebhook := createTestValidatingWebhook("api-1", "api-config") + + staticSource := &mockSource{webhooks: []webhook.WebhookAccessor{staticWebhook}, hasSynced: true} + apiSource := &mockSource{webhooks: []webhook.WebhookAccessor{apiWebhook}, hasSynced: true} + + source := NewCompositeWebhookSource(staticSource, apiSource) + + // First call should create the combined slice. + webhooks1 := source.Webhooks() + if len(webhooks1) != 2 { + t.Fatalf("Expected 2 webhooks, got %d", len(webhooks1)) + } + + // Second call with same underlying slices should return the cached combined slice. + webhooks2 := source.Webhooks() + if &webhooks1[0] != &webhooks2[0] { + t.Error("Expected cached slice to be returned when underlying slices haven't changed") + } + + // Changing the underlying slice should produce a new combined slice. + newStaticWebhook := createTestValidatingWebhook("static-2", "static-config-2") + staticSource.webhooks = []webhook.WebhookAccessor{newStaticWebhook} + webhooks3 := source.Webhooks() + if webhooks3[0].GetUID() != "static-2" { + t.Errorf("Expected first webhook UID to be static-2 after update, got %s", webhooks3[0].GetUID()) + } + if &webhooks2[0] == &webhooks3[0] { + t.Error("Expected new slice after underlying source changed") + } +} + +func TestCompositeWebhookSource_HasSynced(t *testing.T) { + tests := []struct { + name string + staticSource Source + apiSource Source + want bool + }{ + { + name: "both synced", + staticSource: &mockSource{hasSynced: true}, + apiSource: &mockSource{hasSynced: true}, + want: true, + }, + { + name: "static not synced", + staticSource: &mockSource{hasSynced: false}, + apiSource: &mockSource{hasSynced: true}, + want: false, + }, + { + name: "api not synced", + staticSource: &mockSource{hasSynced: true}, + apiSource: &mockSource{hasSynced: false}, + want: false, + }, + { + name: "neither synced", + staticSource: &mockSource{hasSynced: false}, + apiSource: &mockSource{hasSynced: false}, + want: false, + }, + { + name: "nil static source, api synced", + staticSource: nil, + apiSource: &mockSource{hasSynced: true}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + source := NewCompositeWebhookSource(tt.staticSource, tt.apiSource) + if got := source.HasSynced(); got != tt.want { + t.Errorf("HasSynced() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go index bf646d0080198..cef27b2330c8a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go @@ -22,6 +22,7 @@ import ( "io" "k8s.io/apiserver/pkg/cel/environment" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" admissionv1 "k8s.io/api/admission/v1" @@ -39,8 +40,10 @@ import ( "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/features" webhookutil "k8s.io/apiserver/pkg/util/webhook" "k8s.io/client-go/informers" + coreinformers "k8s.io/client-go/informers/core/v1" clientset "k8s.io/client-go/kubernetes" ) @@ -48,28 +51,63 @@ import ( type Webhook struct { *admission.Handler - sourceFactory sourceFactory + // Factories for creating webhook sources. + apiSourceFactory sourceFactory + staticSourceFactory StaticSourceFactory - hookSource Source - clientManager *webhookutil.ClientManager - namespaceMatcher *namespace.Matcher - objectMatcher *object.Matcher - dispatcher Dispatcher - filterCompiler cel.ConditionCompiler - authorizer authorizer.Authorizer + // Configuration provided via admission config file and initializers. + staticManifestsDir string // path to static webhook manifest directory (optional) + apiServerID string // identity of this API server instance, used for metrics + + // hookSource is the webhook source used at admission time. When staticManifestsDir + // is configured, this is a composite source combining staticSource (loaded from disk) + // with apiSource (from the API). Otherwise, it points directly to apiSource. + hookSource Source + // apiSource provides webhook configurations from the Kubernetes API (informer-based). + apiSource Source + // staticSource holds a reference to only the static (manifest-based) webhook source. + // This can be used to route requests for excluded resources to static hooks only. + staticSource ReloadableSource + + // Admission-time dependencies. + namespaceInformer coreinformers.NamespaceInformer + clientManager *webhookutil.ClientManager + namespaceMatcher *namespace.Matcher + objectMatcher *object.Matcher + dispatcher Dispatcher + filterCompiler cel.ConditionCompiler + authorizer authorizer.Authorizer + + // Lifecycle. + stopCh <-chan struct{} } var ( _ genericadmissioninit.WantsExternalKubeClientSet = &Webhook{} + _ genericadmissioninit.WantsDrainedNotification = &Webhook{} + _ genericadmissioninit.WantsAPIServerID = &Webhook{} _ admission.Interface = &Webhook{} ) type sourceFactory func(f informers.SharedInformerFactory) Source type dispatcherFactory func(cm *webhookutil.ClientManager) Dispatcher +// ReloadableSource extends Source with a method to run a reload loop +// that watches for configuration changes and blocks until the context is canceled. +type ReloadableSource interface { + Source + // RunReloadLoop watches for configuration changes and reloads when detected. + // It blocks until ctx is canceled. + RunReloadLoop(ctx context.Context) +} + +// StaticSourceFactory creates a static webhook source from a manifest directory. +// The returned Source should have LoadInitial() already called. +type StaticSourceFactory func(manifestsDir string) (ReloadableSource, error) + // NewWebhook creates a new generic admission webhook. func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) { - kubeconfigFile, err := config.LoadConfig(configFile) + cfg, err := config.LoadConfig(configFile) if err != nil { return nil, err } @@ -85,7 +123,7 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory if err != nil { return nil, err } - authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile) + authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(cfg.KubeConfigFile) if err != nil { return nil, err } @@ -94,13 +132,14 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver()) return &Webhook{ - Handler: handler, - sourceFactory: sourceFactory, - clientManager: &cm, - namespaceMatcher: &namespace.Matcher{}, - objectMatcher: &object.Matcher{}, - dispatcher: dispatcherFactory(&cm), - filterCompiler: cel.NewConditionCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())), + Handler: handler, + apiSourceFactory: sourceFactory, + staticManifestsDir: cfg.StaticManifestsDir, + clientManager: &cm, + namespaceMatcher: &namespace.Matcher{}, + objectMatcher: &object.Matcher{}, + dispatcher: dispatcherFactory(&cm), + filterCompiler: cel.NewConditionCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())), }, nil } @@ -117,6 +156,29 @@ func (a *Webhook) SetServiceResolver(sr webhookutil.ServiceResolver) { a.clientManager.SetServiceResolver(sr) } +// SetStaticSourceFactory sets the factory for creating static webhook sources. +// This should be called before SetExternalKubeInformerFactory. +func (a *Webhook) SetStaticSourceFactory(factory StaticSourceFactory) { + a.staticSourceFactory = factory +} + +// SetAPIServerID implements the WantsAPIServerID interface. +// The API server ID is used for metrics labeling and must be set before +// SetExternalKubeInformerFactory is called. +func (a *Webhook) SetAPIServerID(id string) { + a.apiServerID = id +} + +// GetAPIServerID returns the stored API server ID. +func (a *Webhook) GetAPIServerID() string { + return a.apiServerID +} + +// SetDrainedNotification implements the WantsDrainedNotification interface. +func (a *Webhook) SetDrainedNotification(stopCh <-chan struct{}) { + a.stopCh = stopCh +} + // SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface. // It sets external ClientSet for admission plugins that need it func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) { @@ -127,10 +189,10 @@ func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) { func (a *Webhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { namespaceInformer := f.Core().V1().Namespaces() a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister() - a.hookSource = a.sourceFactory(f) - a.SetReadyFunc(func() bool { - return namespaceInformer.Informer().HasSynced() && a.hookSource.HasSynced() - }) + a.namespaceInformer = namespaceInformer + + // Create the API-based source (stored for later use in ValidateInitialization) + a.apiSource = a.apiSourceFactory(f) } func (a *Webhook) SetAuthorizer(authorizer authorizer.Authorizer) { @@ -138,8 +200,10 @@ func (a *Webhook) SetAuthorizer(authorizer authorizer.Authorizer) { } // ValidateInitialization implements the InitializationValidator interface. +// Static source creation happens here (after all initializers have run) because +// SetManifestLoaders may be called after SetExternalKubeInformerFactory. func (a *Webhook) ValidateInitialization() error { - if a.hookSource == nil { + if a.apiSource == nil { return fmt.Errorf("kubernetes client is not properly setup") } if err := a.namespaceMatcher.Validate(); err != nil { @@ -148,6 +212,46 @@ func (a *Webhook) ValidateInitialization() error { if err := a.clientManager.Validate(); err != nil { return fmt.Errorf("clientManager is not properly setup: %v", err) } + + // Guard: if static manifests dir is set but feature gate is off, return an error + if len(a.staticManifestsDir) > 0 && !utilfeature.DefaultFeatureGate.Enabled(features.ManifestBasedAdmissionControlConfig) { + return fmt.Errorf("static webhook manifests dir %q configured but %s feature gate is not enabled", a.staticManifestsDir, features.ManifestBasedAdmissionControlConfig) + } + + // Construct hookSource. The static source path (which starts goroutines) + // is guarded to avoid duplicate construction. The API-only path is a cheap + // pointer assignment that must reflect the latest apiSource. + if len(a.staticManifestsDir) > 0 { + if a.hookSource == nil { + if a.staticSourceFactory == nil { + return fmt.Errorf("static webhook manifests configured in %q but no static source factory is set", a.staticManifestsDir) + } + if a.stopCh == nil { + return fmt.Errorf("stopCh not set: WantsDrainedNotification must be called before ValidateInitialization") + } + staticSource, err := a.staticSourceFactory(a.staticManifestsDir) + if err != nil { + return fmt.Errorf("failed to load static webhook manifests from %q: %w", a.staticManifestsDir, err) + } + a.staticSource = staticSource + // Start the file watcher in a background goroutine, tied to server shutdown + staticCtx, staticCancel := context.WithCancel(context.Background()) + go func() { + defer staticCancel() + <-a.stopCh + }() + go staticSource.RunReloadLoop(staticCtx) + // Use composite source that combines static + API sources + a.hookSource = NewCompositeWebhookSource(staticSource, a.apiSource) + } + } else { + a.hookSource = a.apiSource + } + + a.SetReadyFunc(func() bool { + return a.namespaceInformer.Informer().HasSynced() && a.hookSource.HasSynced() + }) + return nil } @@ -252,6 +356,16 @@ func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { r // Dispatch is called by the downstream Validate or Admit methods. func (a *Webhook) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { if rules.IsExemptAdmissionConfigurationResource(attr) { + // Admission config resources are excluded from API-based webhooks to + // prevent circular dependencies. However, static (manifest-based) webhooks + // are safe to evaluate since they don't have self-referential concerns. + if a.staticSource != nil { + if !a.staticSource.HasSynced() { + return admission.NewForbidden(attr, fmt.Errorf("not yet ready to handle request")) + } + hooks := a.staticSource.Webhooks() + return a.dispatcher.Dispatch(ctx, attr, o, hooks) + } return nil } if !a.WaitForReady() { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader/loader.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader/loader.go new file mode 100644 index 0000000000000..258e5b47d70c9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader/loader.go @@ -0,0 +1,220 @@ +/* +Copyright 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 loader provides generic functionality to load webhook configurations +// from manifest files. It handles file reading, YAML/JSON decoding, and generic +// manifest validation. Type-specific defaulting and validation (e.g., scheme-based +// defaulting via internal types) are injected by callers through the AcceptObjectFunc +// callback. +package loader + +import ( + "fmt" + "sort" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission/plugin/manifest" + "k8s.io/apiserver/pkg/admission/plugin/webhook" +) + +// AcceptObjectFunc extracts typed items from a decoded runtime.Object, applying +// defaulting and validation. Returns the extracted items, or an error if the +// object type is not recognized or processing fails. +type AcceptObjectFunc[T metav1.Object] func(obj runtime.Object) ([]T, error) + +// LoadManifests loads webhook configurations from manifest files in dir using +// the provided decoder and acceptObject callback. It handles file I/O, YAML +// splitting, decoding, v1.List unwrapping, manifest name validation, webhook +// client config validation, and deterministic sorting by name. +func LoadManifests[T metav1.Object]( + dir string, + decoder runtime.Decoder, + acceptObject AcceptObjectFunc[T], +) ([]T, string, error) { + fileDocs, hash, err := manifest.LoadFiles(dir) + if err != nil { + return nil, "", err + } + + configs := make([]T, 0) + seenNames := map[string]string{} + + for _, fd := range fileDocs { + obj, _, err := decoder.Decode(fd.Doc, nil, nil) + if err != nil { + return nil, "", fmt.Errorf("error loading %s: %w", fd.FilePath, err) + } + + items, err := acceptFileObject(obj, fd.FilePath, seenNames, decoder, acceptObject) + if err != nil { + return nil, "", fmt.Errorf("error loading %s: %w", fd.FilePath, err) + } + configs = append(configs, items...) + } + + sort.Slice(configs, func(i, j int) bool { + return configs[i].GetName() < configs[j].GetName() + }) + + return configs, hash, nil +} + +// acceptFileObject handles type dispatch for a decoded object, including +// generic v1.List unwrapping, manifest name validation, item validation, +// and the default unsupported-type error. +func acceptFileObject[T metav1.Object]( + obj runtime.Object, + filePath string, + seenNames map[string]string, + decoder runtime.Decoder, + acceptObject AcceptObjectFunc[T], +) ([]T, error) { + // Handle generic v1.List by recursing into each item + if list, ok := obj.(*metav1.List); ok { + var allItems []T + for _, rawItem := range list.Items { + itemObj, _, err := decoder.Decode(rawItem.Raw, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode list item: %w", err) + } + items, err := acceptFileObject(itemObj, filePath, seenNames, decoder, acceptObject) + if err != nil { + return nil, err + } + allItems = append(allItems, items...) + } + return allItems, nil + } + + items, err := acceptObject(obj) + if err != nil { + return nil, err + } + for _, item := range items { + if err := validateAcceptedItem(item, filePath, seenNames); err != nil { + return nil, err + } + } + return items, nil +} + +// validateAcceptedItem runs manifest name validation and webhook client config validation. +func validateAcceptedItem[T metav1.Object](item T, filePath string, seenNames map[string]string) error { + name := item.GetName() + if err := manifest.ValidateManifestName(name, filePath, seenNames); err != nil { + return err + } + // Validate webhook client configs for webhook configuration types. + obj, ok := any(item).(runtime.Object) + if !ok { + return fmt.Errorf("type %T does not implement runtime.Object", item) + } + return validateWebhookClientConfigs(obj, name, filePath) +} + +// validateWebhookClientConfigs validates that all webhooks in a configuration +// use URL-based client config (service references are not supported for manifests). +func validateWebhookClientConfigs(obj runtime.Object, configName, filePath string) error { + switch c := obj.(type) { + case *admissionregistrationv1.ValidatingWebhookConfiguration: + for _, wh := range c.Webhooks { + if err := ValidateWebhookClientConfig(wh.Name, configName, filePath, wh.ClientConfig); err != nil { + return err + } + } + case *admissionregistrationv1.MutatingWebhookConfiguration: + for _, wh := range c.Webhooks { + if err := ValidateWebhookClientConfig(wh.Name, configName, filePath, wh.ClientConfig); err != nil { + return err + } + } + default: + return fmt.Errorf("unsupported webhook configuration type %T in file %q", obj, filePath) + } + return nil +} + +// ValidateWebhookClientConfig checks that a webhook uses URL-based client config +// (service references are not supported for static manifests). +func ValidateWebhookClientConfig(webhookName, configName, filePath string, cc admissionregistrationv1.WebhookClientConfig) error { + if cc.Service != nil { + return fmt.Errorf("webhook %q in %q (file %q): clientConfig.service is not supported for static manifests; use clientConfig.url instead", webhookName, configName, filePath) + } + if cc.URL == nil || len(*cc.URL) == 0 { + return fmt.Errorf("webhook %q in %q (file %q): clientConfig.url is required for static manifests", webhookName, configName, filePath) + } + return nil +} + +// ValidatingLoadResult holds the validating webhook configurations loaded from manifest files. +type ValidatingLoadResult struct { + // Configurations is the list of loaded validating webhook configurations. + Configurations []*admissionregistrationv1.ValidatingWebhookConfiguration + // Hash is the sha256 hash of all loaded files, used for change detection. + Hash string +} + +// MutatingLoadResult holds the mutating webhook configurations loaded from manifest files. +type MutatingLoadResult struct { + // Configurations is the list of loaded mutating webhook configurations. + Configurations []*admissionregistrationv1.MutatingWebhookConfiguration + // Hash is the sha256 hash of all loaded files, used for change detection. + Hash string +} + +// BuildValidatingAccessors builds webhook accessors from validating webhook configurations. +func BuildValidatingAccessors(configs []*admissionregistrationv1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor { + var accessors []webhook.WebhookAccessor + for _, config := range configs { + names := map[string]int{} + for i := range config.Webhooks { + w := &config.Webhooks[i] + n := w.Name + uid := fmt.Sprintf("manifest/%s/%s/%d", config.Name, n, names[n]) + names[n]++ + accessors = append(accessors, webhook.NewValidatingWebhookAccessor(uid, config.Name, w)) + } + } + return accessors +} + +// BuildMutatingAccessors builds webhook accessors from mutating webhook configurations. +func BuildMutatingAccessors(configs []*admissionregistrationv1.MutatingWebhookConfiguration) []webhook.WebhookAccessor { + var accessors []webhook.WebhookAccessor + for _, config := range configs { + names := map[string]int{} + for i := range config.Webhooks { + w := &config.Webhooks[i] + n := w.Name + uid := fmt.Sprintf("manifest/%s/%s/%d", config.Name, n, names[n]) + names[n]++ + accessors = append(accessors, webhook.NewMutatingWebhookAccessor(uid, config.Name, w)) + } + } + return accessors +} + +// GetWebhookAccessors returns webhook accessors for all validating webhooks. +func (r *ValidatingLoadResult) GetWebhookAccessors() []webhook.WebhookAccessor { + return BuildValidatingAccessors(r.Configurations) +} + +// GetWebhookAccessors returns webhook accessors for all mutating webhooks. +func (r *MutatingLoadResult) GetWebhookAccessors() []webhook.WebhookAccessor { + return BuildMutatingAccessors(r.Configurations) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader/loader_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader/loader_test.go new file mode 100644 index 0000000000000..6bf4a6a8cb08d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader/loader_test.go @@ -0,0 +1,264 @@ +/* +Copyright 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 loader + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +func TestValidateWebhookClientConfig(t *testing.T) { + url := "https://example.com" + tests := []struct { + name string + cc admissionregistrationv1.WebhookClientConfig + wantErr bool + errContains string + }{ + { + name: "valid URL config", + cc: admissionregistrationv1.WebhookClientConfig{URL: &url}, + }, + { + name: "service ref", + cc: admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Name: "svc", Namespace: "ns"}}, + wantErr: true, + errContains: "clientConfig.service is not supported", + }, + { + name: "no URL", + cc: admissionregistrationv1.WebhookClientConfig{}, + wantErr: true, + errContains: "clientConfig.url is required", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateWebhookClientConfig("wh", "cfg", "test.yaml", tt.cc) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("error %q does not contain %q", err.Error(), tt.errContains) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func TestLoadManifests(t *testing.T) { + // Build a minimal decoder (admissionregistration types only, no full client-go scheme) + s := runtime.NewScheme() + utilruntime.Must(admissionregistrationv1.AddToScheme(s)) + s.AddUnversionedTypes(metav1.SchemeGroupVersion, &metav1.List{}, &metav1.Status{}) + decoder := serializer.NewCodecFactory(s).UniversalDeserializer() + + // Simple accept function that extracts the typed object (no kube-apiserver defaulting) + accept := func(obj runtime.Object) ([]*admissionregistrationv1.ValidatingWebhookConfiguration, error) { + vwc, ok := obj.(*admissionregistrationv1.ValidatingWebhookConfiguration) + if !ok { + return nil, fmt.Errorf("unsupported type %T", obj) + } + return []*admissionregistrationv1.ValidatingWebhookConfiguration{vwc}, nil + } + + tests := []struct { + name string + files map[string]string + wantConfigs int + wantErr bool + wantErrContain string + }{ + { + name: "single validating webhook", + files: map[string]string{ + "wh.yaml": `apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: test.static.k8s.io +webhooks: +- name: test.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + sideEffects: None +`, + }, + wantConfigs: 1, + }, + { + name: "empty directory", + files: map[string]string{}, + wantConfigs: 0, + }, + { + name: "unsupported resource type", + files: map[string]string{ + "wrong.yaml": `apiVersion: v1 +kind: ConfigMap +metadata: + name: not-a-webhook +`, + }, + wantErr: true, + wantErrContain: "error loading", + }, + { + name: "v1.List with validating webhook", + files: map[string]string{ + "list.yaml": `apiVersion: v1 +kind: List +items: +- apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: v1list-wh.static.k8s.io + webhooks: + - name: list.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + sideEffects: None +`, + }, + wantConfigs: 1, + }, + { + name: "duplicate webhook config names", + files: map[string]string{ + "01-wh.yaml": `apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: dup.static.k8s.io +webhooks: +- name: first.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + sideEffects: None +`, + "02-wh.yaml": `apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: dup.static.k8s.io +webhooks: +- name: second.webhook.io + admissionReviewVersions: ["v1"] + clientConfig: + url: "https://example.com" + sideEffects: None +`, + }, + wantErr: true, + wantErrContain: "duplicate", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for name, content := range tt.files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatalf("failed to write file %s: %v", name, err) + } + } + + configs, hash, err := LoadManifests(dir, decoder, accept) + + if tt.wantErr { + if err == nil { + t.Error("expected error but got none") + } else if tt.wantErrContain != "" && !strings.Contains(err.Error(), tt.wantErrContain) { + t.Errorf("expected error containing %q, got %q", tt.wantErrContain, err.Error()) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(configs) != tt.wantConfigs { + t.Errorf("expected %d configs, got %d", tt.wantConfigs, len(configs)) + } + if tt.wantConfigs > 0 && hash == "" { + t.Error("expected non-empty hash") + } + }) + } +} + +func TestValidatingLoadResult_GetWebhookAccessors(t *testing.T) { + url := "https://example.com" + result := &ValidatingLoadResult{ + Configurations: []*admissionregistrationv1.ValidatingWebhookConfiguration{ + { + ObjectMeta: metav1.ObjectMeta{Name: "test.static.k8s.io"}, + Webhooks: []admissionregistrationv1.ValidatingWebhook{ + { + Name: "first.webhook.io", + ClientConfig: admissionregistrationv1.WebhookClientConfig{URL: &url}, + AdmissionReviewVersions: []string{"v1"}, + }, + { + Name: "second.webhook.io", + ClientConfig: admissionregistrationv1.WebhookClientConfig{URL: &url}, + AdmissionReviewVersions: []string{"v1"}, + }, + }, + }, + }, + } + accessors := result.GetWebhookAccessors() + if len(accessors) != 2 { + t.Fatalf("expected 2 accessors, got %d", len(accessors)) + } +} + +func TestMutatingLoadResult_GetWebhookAccessors(t *testing.T) { + url := "https://example.com" + result := &MutatingLoadResult{ + Configurations: []*admissionregistrationv1.MutatingWebhookConfiguration{ + { + ObjectMeta: metav1.ObjectMeta{Name: "test.static.k8s.io"}, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + { + Name: "mutate.webhook.io", + ClientConfig: admissionregistrationv1.WebhookClientConfig{URL: &url}, + AdmissionReviewVersions: []string{"v1"}, + }, + }, + }, + }, + } + accessors := result.GetWebhookAccessors() + if len(accessors) != 1 { + t.Fatalf("expected 1 accessor, got %d", len(accessors)) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source/source.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source/source.go new file mode 100644 index 0000000000000..7ebd2bd5c8b02 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source/source.go @@ -0,0 +1,218 @@ +/* +Copyright 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 source provides a Source implementation that loads webhook configurations from manifest files. +package source + +import ( + "context" + "errors" + "fmt" + "sync/atomic" + "time" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission/plugin/manifest/metrics" + "k8s.io/apiserver/pkg/admission/plugin/webhook" + stagingloader "k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/loader" + "k8s.io/apiserver/pkg/util/filesystem" + "k8s.io/klog/v2" +) + +// defaultReloadInterval is the default interval at which the manifest directory is checked for changes. +var defaultReloadInterval = 1 * time.Minute + +// SetReloadIntervalForTests sets the reload interval for testing and returns a function to restore the original value. +func SetReloadIntervalForTests(interval time.Duration) func() { + original := defaultReloadInterval + defaultReloadInterval = interval + return func() { + defaultReloadInterval = original + } +} + +// ValidatingWebhookLoadFunc loads validating webhook configurations from a directory. +// Returns the configurations, a hash string for change detection, and any error. +type ValidatingWebhookLoadFunc func(dir string) ([]*admissionregistrationv1.ValidatingWebhookConfiguration, string, error) + +// MutatingWebhookLoadFunc loads mutating webhook configurations from a directory. +// Returns the configurations, a hash string for change detection, and any error. +type MutatingWebhookLoadFunc func(dir string) ([]*admissionregistrationv1.MutatingWebhookConfiguration, string, error) + +// accessorBuilder builds webhook accessors from configurations. +type accessorBuilder[T runtime.Object] func(configs []T) []webhook.WebhookAccessor + +// webhookData holds the currently loaded webhook configurations and pre-built accessors. +type webhookData[T runtime.Object] struct { + configurations []T + accessors []webhook.WebhookAccessor +} + +// staticWebhookSource provides webhook configurations loaded from manifest files. +type staticWebhookSource[T runtime.Object] struct { + manifestsDir string + apiServerID string + reloadInterval time.Duration + webhookType metrics.ManifestType + loadFunc func(dir string) ([]T, string, error) + buildAccessors accessorBuilder[T] + + current atomic.Pointer[webhookData[T]] + lastReadHash atomic.Pointer[string] // hash of last file content read (for short-circuiting) + hasSynced atomic.Bool +} + +// ValidatingSource provides validating webhook configurations loaded from manifest files. +type ValidatingSource struct { + *staticWebhookSource[*admissionregistrationv1.ValidatingWebhookConfiguration] +} + +// MutatingSource provides mutating webhook configurations loaded from manifest files. +type MutatingSource struct { + *staticWebhookSource[*admissionregistrationv1.MutatingWebhookConfiguration] +} + +// NewValidatingSource creates a new validating webhook source that loads configurations from the specified directory. +func NewValidatingSource(manifestsDir, apiServerID string, loadFunc ValidatingWebhookLoadFunc) *ValidatingSource { + metrics.RegisterMetrics() + return &ValidatingSource{ + staticWebhookSource: &staticWebhookSource[*admissionregistrationv1.ValidatingWebhookConfiguration]{ + manifestsDir: manifestsDir, + apiServerID: apiServerID, + reloadInterval: defaultReloadInterval, + webhookType: metrics.ValidatingWebhookManifestType, + loadFunc: loadFunc, + buildAccessors: stagingloader.BuildValidatingAccessors, + }, + } +} + +// NewMutatingSource creates a new mutating webhook source that loads configurations from the specified directory. +func NewMutatingSource(manifestsDir, apiServerID string, loadFunc MutatingWebhookLoadFunc) *MutatingSource { + metrics.RegisterMetrics() + return &MutatingSource{ + staticWebhookSource: &staticWebhookSource[*admissionregistrationv1.MutatingWebhookConfiguration]{ + manifestsDir: manifestsDir, + apiServerID: apiServerID, + reloadInterval: defaultReloadInterval, + webhookType: metrics.MutatingWebhookManifestType, + loadFunc: loadFunc, + buildAccessors: stagingloader.BuildMutatingAccessors, + }, + } +} + +// LoadInitial performs the initial load of webhook manifests. +func (s *staticWebhookSource[T]) LoadInitial() error { + configs, hash, err := s.loadFunc(s.manifestsDir) + if err != nil { + return err + } + + accessors := s.buildAccessors(configs) + + // Validate accessors eagerly to catch selector parse errors at startup. + if err := validateAccessors(accessors); err != nil { + return err + } + + s.current.Store(&webhookData[T]{configurations: configs, accessors: accessors}) + s.lastReadHash.Store(&hash) + s.hasSynced.Store(true) + + klog.InfoS("Loaded manifest-based webhook configurations", "plugin", string(s.webhookType), + "configurations", len(configs)) + metrics.RecordAutomaticReloadSuccess(s.webhookType, s.apiServerID, hash) + return nil +} + +// RunReloadLoop watches for configuration changes and reloads when detected. +// It blocks until ctx is canceled. +func (s *staticWebhookSource[T]) RunReloadLoop(ctx context.Context) { + filesystem.WatchUntil( + ctx, + s.reloadInterval, + s.manifestsDir, + func() { + s.checkAndReload() + }, + func(err error) { + klog.ErrorS(err, "watching manifest directory", "plugin", string(s.webhookType), "dir", s.manifestsDir) + }, + ) +} + +func (s *staticWebhookSource[T]) checkAndReload() { + configs, hash, err := s.loadFunc(s.manifestsDir) + if err != nil { + klog.ErrorS(err, "reloading admission manifest config", "plugin", string(s.webhookType), "dir", s.manifestsDir) + metrics.RecordAutomaticReloadFailure(s.webhookType, s.apiServerID) + return + } + + // Short-circuit if file content hasn't changed since last read. + if last := s.lastReadHash.Load(); last != nil && hash == *last { + return + } + s.lastReadHash.Store(&hash) + + // Build and validate accessors (configs may be empty if files deleted) + accessors := s.buildAccessors(configs) + if err := validateAccessors(accessors); err != nil { + klog.ErrorS(err, "manifest webhook accessor validation failed after reload", "plugin", string(s.webhookType), "dir", s.manifestsDir) + metrics.RecordAutomaticReloadFailure(s.webhookType, s.apiServerID) + return + } + + s.current.Store(&webhookData[T]{configurations: configs, accessors: accessors}) + klog.InfoS("reloaded admission manifest config", "plugin", string(s.webhookType), "dir", s.manifestsDir) + metrics.RecordAutomaticReloadSuccess(s.webhookType, s.apiServerID, hash) +} + +// HasSynced returns true if the initial load has completed. +func (s *staticWebhookSource[T]) HasSynced() bool { + return s.hasSynced.Load() +} + +// Webhooks returns the list of webhook accessors. +func (s *staticWebhookSource[T]) Webhooks() []webhook.WebhookAccessor { + current := s.current.Load() + if current == nil { + return nil + } + return current.accessors +} + +// validateAccessors exercises lazy getters on each accessor to surface latent +// errors (e.g. invalid label selectors) early. +// Note: We intentionally skip GetRESTClient validation here because it requires +// a ClientManager. API validation (applied during defaulting) already ensures +// CA bundles parse and URLs are present and valid, and the manifest loader +// rejects service references, which covers the inputs needed to construct a +// REST client at admission time. +func validateAccessors(accessors []webhook.WebhookAccessor) error { + var errs []error + for _, a := range accessors { + if _, err := a.GetParsedNamespaceSelector(); err != nil { + errs = append(errs, fmt.Errorf("webhook %q: invalid namespaceSelector: %w", a.GetName(), err)) + } + if _, err := a.GetParsedObjectSelector(); err != nil { + errs = append(errs, fmt.Errorf("webhook %q: invalid objectSelector: %w", a.GetName(), err)) + } + } + return errors.Join(errs...) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source/source_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source/source_test.go new file mode 100644 index 0000000000000..bca5440a02d01 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source/source_test.go @@ -0,0 +1,210 @@ +/* +Copyright 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 source + +import ( + "testing" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type mockLoadFunc struct { + configs []*admissionregistrationv1.ValidatingWebhookConfiguration + hash string + err error + callCount int +} + +func (m *mockLoadFunc) load(_ string) ([]*admissionregistrationv1.ValidatingWebhookConfiguration, string, error) { + m.callCount++ + return m.configs, m.hash, m.err +} + +func validVWC(name string) *admissionregistrationv1.ValidatingWebhookConfiguration { + return &admissionregistrationv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Webhooks: []admissionregistrationv1.ValidatingWebhook{{ + Name: "test.webhook.io", + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + URL: new("https://example.com"), + }, + AdmissionReviewVersions: []string{"v1"}, + SideEffects: func() *admissionregistrationv1.SideEffectClass { + s := admissionregistrationv1.SideEffectClassNone + return &s + }(), + }}, + } +} + +func invalidSelectorVWC(name string) *admissionregistrationv1.ValidatingWebhookConfiguration { + vwc := validVWC(name) + vwc.Webhooks[0].NamespaceSelector = &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: "InvalidOperator", + }}, + } + return vwc +} + +// TestValidatingSource_ValidateBeforeStore verifies that LoadInitial rejects +// configurations with invalid selectors and does not store them (Bug #1). +func TestValidatingSource_ValidateBeforeStore(t *testing.T) { + mock := &mockLoadFunc{ + configs: []*admissionregistrationv1.ValidatingWebhookConfiguration{invalidSelectorVWC("bad")}, + hash: "h1", + } + + src := NewValidatingSource("/tmp/test", "test-server", mock.load) + + if err := src.LoadInitial(); err == nil { + t.Fatal("LoadInitial should have returned an error for invalid selector") + } + + if webhooks := src.Webhooks(); webhooks != nil { + t.Errorf("Webhooks() = %v, want nil (bad config should not be stored)", webhooks) + } +} + +// TestValidatingSource_ReloadKeepsPreviousOnValidationFailure verifies that +// checkAndReload keeps the previous valid configuration when the new one fails +// validation (Bug #1 on reload path). +func TestValidatingSource_ReloadKeepsPreviousOnValidationFailure(t *testing.T) { + mock := &mockLoadFunc{ + configs: []*admissionregistrationv1.ValidatingWebhookConfiguration{validVWC("good")}, + hash: "h1", + } + + src := NewValidatingSource("/tmp/test", "test-server", mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial failed: %v", err) + } + + initialWebhooks := src.Webhooks() + if len(initialWebhooks) != 1 { + t.Fatalf("expected 1 webhook after LoadInitial, got %d", len(initialWebhooks)) + } + + // Switch to invalid config for reload + mock.configs = []*admissionregistrationv1.ValidatingWebhookConfiguration{invalidSelectorVWC("bad")} + mock.hash = "h2" + + src.checkAndReload() + + reloadedWebhooks := src.Webhooks() + if len(reloadedWebhooks) != 1 { + t.Fatalf("expected 1 webhook after failed reload, got %d", len(reloadedWebhooks)) + } + // Should still be the original accessor + if &initialWebhooks[0] != &reloadedWebhooks[0] { + t.Error("Webhooks() returned different slice after failed reload; previous config should be preserved") + } +} + +// TestValidatingSource_CachedAccessorSlice verifies that Webhooks() returns +// the same slice on repeated calls (Bug #2 — enables composite source caching). +func TestValidatingSource_CachedAccessorSlice(t *testing.T) { + mock := &mockLoadFunc{ + configs: []*admissionregistrationv1.ValidatingWebhookConfiguration{validVWC("cfg")}, + hash: "h1", + } + + src := NewValidatingSource("/tmp/test", "test-server", mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial failed: %v", err) + } + + result1 := src.Webhooks() + result2 := src.Webhooks() + + if len(result1) == 0 || len(result2) == 0 { + t.Fatal("expected non-empty webhook slices") + } + + if &result1[0] != &result2[0] { + t.Error("Webhooks() returned different slices; expected pointer-equal backing array for caching") + } +} + +// TestValidatingSource_PreviousHashSkipsReload verifies that when checkAndReload +// is called and the hash hasn't changed, the source keeps the original accessors +// (the source owns hash comparison). +func TestValidatingSource_PreviousHashSkipsReload(t *testing.T) { + mock := &mockLoadFunc{ + configs: []*admissionregistrationv1.ValidatingWebhookConfiguration{validVWC("cfg")}, + hash: "h1", + } + + src := NewValidatingSource("/tmp/test", "test-server", mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial failed: %v", err) + } + if mock.callCount != 1 { + t.Fatalf("expected 1 call after LoadInitial, got %d", mock.callCount) + } + + initialWebhooks := src.Webhooks() + + // loadFunc returns same hash — source should detect no change + src.checkAndReload() + + if mock.callCount != 2 { + t.Fatalf("expected 2 calls after checkAndReload, got %d", mock.callCount) + } + + afterReload := src.Webhooks() + if len(afterReload) != len(initialWebhooks) { + t.Fatalf("expected %d webhooks after no-op reload, got %d", len(initialWebhooks), len(afterReload)) + } + if &afterReload[0] != &initialWebhooks[0] { + t.Error("Webhooks() returned different slice after no-op reload; original should be preserved") + } +} + +// TestValidatingSource_FilesDeletionClearsConfigs verifies that when loadFunc +// returns empty configs with a new hash, stored configs are updated to empty. +func TestValidatingSource_FilesDeletionClearsConfigs(t *testing.T) { + mock := &mockLoadFunc{ + configs: []*admissionregistrationv1.ValidatingWebhookConfiguration{validVWC("cfg")}, + hash: "h1", + } + + src := NewValidatingSource("/tmp/test", "test-server", mock.load) + + if err := src.LoadInitial(); err != nil { + t.Fatalf("LoadInitial failed: %v", err) + } + if len(src.Webhooks()) != 1 { + t.Fatalf("expected 1 webhook after LoadInitial, got %d", len(src.Webhooks())) + } + + // Simulate files deleted: empty configs, new hash + mock.configs = []*admissionregistrationv1.ValidatingWebhookConfiguration{} + mock.hash = "h2" + + src.checkAndReload() + + webhooks := src.Webhooks() + if len(webhooks) != 0 { + t.Errorf("expected 0 webhooks after files deletion, got %d", len(webhooks)) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go index fb07a61c1b8f8..4ffd4c97d63f6 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go @@ -22,7 +22,9 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/configuration" + "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" + "k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source" ) const ( @@ -48,6 +50,22 @@ type Plugin struct { } var _ admission.MutationInterface = &Plugin{} +var _ initializer.WantsManifestLoaders = &Plugin{} + +// SetManifestLoaders provides the manifest load functions for scheme-based defaulting and validation. +func (a *Plugin) SetManifestLoaders(loaders *initializer.ManifestLoaders) { + if loaders == nil || loaders.LoadMutatingWebhookManifests == nil { + return + } + loadFunc := loaders.LoadMutatingWebhookManifests + a.Webhook.SetStaticSourceFactory(func(manifestsDir string) (generic.ReloadableSource, error) { + src := source.NewMutatingSource(manifestsDir, a.Webhook.GetAPIServerID(), source.MutatingWebhookLoadFunc(loadFunc)) + if err := src.LoadInitial(); err != nil { + return nil, err + } + return src, nil + }) +} // NewMutatingWebhook returns a generic admission webhook plugin. func NewMutatingWebhook(configFile io.Reader) (*Plugin, error) { @@ -58,7 +76,6 @@ func NewMutatingWebhook(configFile io.Reader) (*Plugin, error) { if err != nil { return nil, err } - return p, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go index de0af221e13bf..d7237d0518a6c 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go @@ -27,9 +27,9 @@ type webhookReinvokeContext struct { lastWebhookOutput runtime.Object // previouslyInvokedReinvocableWebhooks holds the set of webhooks that have been invoked and // should be reinvoked if a later mutation occurs - previouslyInvokedReinvocableWebhooks sets.String + previouslyInvokedReinvocableWebhooks sets.Set[string] // reinvokeWebhooks holds the set of webhooks that should be reinvoked - reinvokeWebhooks sets.String + reinvokeWebhooks sets.Set[string] } func (rc *webhookReinvokeContext) ShouldReinvokeWebhook(webhook string) bool { @@ -50,7 +50,7 @@ func (rc *webhookReinvokeContext) SetLastWebhookInvocationOutput(object runtime. func (rc *webhookReinvokeContext) AddReinvocableWebhookToPreviouslyInvoked(webhook string) { if rc.previouslyInvokedReinvocableWebhooks == nil { - rc.previouslyInvokedReinvocableWebhooks = sets.NewString() + rc.previouslyInvokedReinvocableWebhooks = sets.New[string]() } rc.previouslyInvokedReinvocableWebhooks.Insert(webhook) } @@ -58,11 +58,11 @@ func (rc *webhookReinvokeContext) AddReinvocableWebhookToPreviouslyInvoked(webho func (rc *webhookReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() { if len(rc.previouslyInvokedReinvocableWebhooks) > 0 { if rc.reinvokeWebhooks == nil { - rc.reinvokeWebhooks = sets.NewString() + rc.reinvokeWebhooks = sets.New[string]() } for s := range rc.previouslyInvokedReinvocableWebhooks { rc.reinvokeWebhooks.Insert(s) } - rc.previouslyInvokedReinvocableWebhooks = sets.NewString() + rc.previouslyInvokedReinvocableWebhooks = sets.New[string]() } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules/rules_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules/rules_test.go index a3604462b22bc..0142afbccbcf9 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules/rules_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules/rules_test.go @@ -396,11 +396,11 @@ func TestScope(t *testing.T) { noMatch: attrList(), }, } - keys := sets.NewString() + keys := sets.New[string]() for name := range table { keys.Insert(name) } - for _, name := range keys.List() { + for _, name := range sets.List(keys) { tt := table[name] for i, m := range tt.match { t.Run(fmt.Sprintf("%s_match_%d", name, i), func(t *testing.T) { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go index 6972877b185b5..da2096021005a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go @@ -22,7 +22,9 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/configuration" + "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" + "k8s.io/apiserver/pkg/admission/plugin/webhook/manifest/source" ) const ( @@ -48,6 +50,22 @@ type Plugin struct { } var _ admission.ValidationInterface = &Plugin{} +var _ initializer.WantsManifestLoaders = &Plugin{} + +// SetManifestLoaders provides the manifest load functions for scheme-based defaulting and validation. +func (a *Plugin) SetManifestLoaders(loaders *initializer.ManifestLoaders) { + if loaders == nil || loaders.LoadValidatingWebhookManifests == nil { + return + } + loadFunc := loaders.LoadValidatingWebhookManifests + a.Webhook.SetStaticSourceFactory(func(manifestsDir string) (generic.ReloadableSource, error) { + src := source.NewValidatingSource(manifestsDir, a.Webhook.GetAPIServerID(), source.ValidatingWebhookLoadFunc(loadFunc)) + if err := src.LoadInitial(); err != nil { + return nil, err + } + return src, nil + }) +} // NewValidatingAdmissionWebhook returns a generic admission webhook plugin. func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) { diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go index 596b1f140ac18..0c5e6b5485f00 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go @@ -146,6 +146,10 @@ type TLSConfig struct { // Must be configured if TCPTransport.URL is prefixed with https:// // +optional ClientCert string + + // tlsServerName is used to check server certificate. If tlsServerName is empty, the hostname used to contact the server is used. + // +optional + TLSServerName string } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go index 6e0f86da86dfb..1e20c697b55ab 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go @@ -147,6 +147,10 @@ type TLSConfig struct { // Must be configured if TCPTransport.URL is prefixed with https:// // +optional ClientCert string `json:"clientCert,omitempty"` + + // tlsServerName is used to check server certificate. If tlsServerName is empty, the hostname used to contact the server is used. + // +optional + TLSServerName string `json:"tlsServerName,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go index 9d7a8ce627968..4ea029789543c 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go @@ -817,6 +817,7 @@ func autoConvert_v1alpha1_TLSConfig_To_apiserver_TLSConfig(in *TLSConfig, out *a out.CABundle = in.CABundle out.ClientKey = in.ClientKey out.ClientCert = in.ClientCert + out.TLSServerName = in.TLSServerName return nil } @@ -829,6 +830,7 @@ func autoConvert_apiserver_TLSConfig_To_v1alpha1_TLSConfig(in *apiserver.TLSConf out.CABundle = in.CABundle out.ClientKey = in.ClientKey out.ClientCert = in.ClientCert + out.TLSServerName = in.TLSServerName return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/types.go index 16260efb66dcb..44bfbbd3c1f95 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/types.go @@ -118,6 +118,10 @@ type TLSConfig struct { // Must be configured if TCPTransport.URL is prefixed with https:// // +optional ClientCert string `json:"clientCert,omitempty"` + + // tlsServerName is used to check server certificate. If tlsServerName is empty, the hostname used to contact the server is used. + // +optional + TLSServerName string `json:"tlsServerName,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/zz_generated.conversion.go index 4f25bd60c2cbc..04323e2082e05 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1/zz_generated.conversion.go @@ -753,6 +753,7 @@ func autoConvert_v1beta1_TLSConfig_To_apiserver_TLSConfig(in *TLSConfig, out *ap out.CABundle = in.CABundle out.ClientKey = in.ClientKey out.ClientCert = in.ClientCert + out.TLSServerName = in.TLSServerName return nil } @@ -765,6 +766,7 @@ func autoConvert_apiserver_TLSConfig_To_v1beta1_TLSConfig(in *apiserver.TLSConfi out.CABundle = in.CABundle out.ClientKey = in.ClientKey out.ClientCert = in.ClientCert + out.TLSServerName = in.TLSServerName return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go index be85d4f3d714d..a5eeaef60f39d 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go @@ -763,7 +763,7 @@ func ValidateWebhookConfiguration(compiler authorizationcel.Compiler, fldPath *f allErrs = append(allErrs, field.NotSupported(fldPath.Child("connectionInfo", "type"), c.ConnectionInfo, []string{api.AuthorizationWebhookConnectionInfoTypeInCluster, api.AuthorizationWebhookConnectionInfoTypeKubeConfigFile})) } - _, errs := compileMatchConditions(compiler, c.MatchConditions, fldPath, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration)) + _, errs := compileMatchConditions(compiler, c.MatchConditions, fldPath) allErrs = append(allErrs, errs...) return allErrs @@ -772,15 +772,11 @@ func ValidateWebhookConfiguration(compiler authorizationcel.Compiler, fldPath *f // ValidateAndCompileMatchConditions validates a given webhook's matchConditions. // This is exported for use in authz package. func ValidateAndCompileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition) (*authorizationcel.CELMatcher, field.ErrorList) { - return compileMatchConditions(compiler, matchConditions, nil, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration)) + return compileMatchConditions(compiler, matchConditions, nil) } -func compileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition, fldPath *field.Path, structuredAuthzFeatureEnabled bool) (*authorizationcel.CELMatcher, field.ErrorList) { +func compileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition, fldPath *field.Path) (*authorizationcel.CELMatcher, field.ErrorList) { var allErrs field.ErrorList - // should fail when match conditions are used without feature enabled - if len(matchConditions) > 0 && !structuredAuthzFeatureEnabled { - allErrs = append(allErrs, field.Invalid(fldPath.Child("matchConditions"), "", "not supported when StructuredAuthorizationConfiguration feature gate is disabled")) - } if len(matchConditions) > 64 { allErrs = append(allErrs, field.TooMany(fldPath.Child("matchConditions"), len(matchConditions), 64)) return nil, allErrs diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/types.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/types.go index dfc9c4fd23c71..6218ae315cb72 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/types.go @@ -275,6 +275,7 @@ type PolicyRule struct { type GroupResources struct { // Group is the name of the API group that contains the resources. // The empty string represents the core API group. + // `*` matches all groups // +optional Group string // Resources is a list of resources this rule applies to. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.proto b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.proto index 632db99862e81..db0afa5e428b5 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.proto +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.proto @@ -135,6 +135,7 @@ message EventList { message GroupResources { // Group is the name of the API group that contains the resources. // The empty string represents the core API group. + // `*` matches all groups // +optional optional string group = 1; diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.protomessage.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.protomessage.pb.go deleted file mode 100644 index 93920227e1f25..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*AuthenticationMetadata) ProtoMessage() {} - -func (*Event) ProtoMessage() {} - -func (*EventList) ProtoMessage() {} - -func (*GroupResources) ProtoMessage() {} - -func (*ObjectReference) ProtoMessage() {} - -func (*Policy) ProtoMessage() {} - -func (*PolicyList) ProtoMessage() {} - -func (*PolicyRule) ProtoMessage() {} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/types.go index 28d3f2ace370d..8c6dc676afd2f 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1/types.go @@ -278,6 +278,7 @@ type PolicyRule struct { type GroupResources struct { // Group is the name of the API group that contains the resources. // The empty string represents the core API group. + // `*` matches all groups // +optional Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"` // Resources is a list of resources this rule applies to. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go index 0611c1ae5f199..3b0d077fd4728 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go @@ -98,7 +98,7 @@ func validateResources(groupResources []audit.GroupResources, fldPath *field.Pat var allErrs field.ErrorList for _, groupResource := range groupResources { // The empty string represents the core API group. - if len(groupResource.Group) != 0 { + if len(groupResource.Group) != 0 && groupResource.Group != "*" { // Group names must be lower case and be valid DNS subdomains. // reference: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md // an error is returned for group name like rbac.authorization.k8s.io/v1beta1 diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.protomessage.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.protomessage.pb.go deleted file mode 100644 index 199fc9b637a74..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*Pod) ProtoMessage() {} - -func (*PodCondition) ProtoMessage() {} - -func (*PodList) ProtoMessage() {} - -func (*PodSpec) ProtoMessage() {} - -func (*PodStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.protomessage.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.protomessage.pb.go deleted file mode 100644 index 95b07e12c76db..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.protomessage.pb.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build kubernetes_protomessage_one_more_release -// +build kubernetes_protomessage_one_more_release - -/* -Copyright 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. -*/ - -// Code generated by go-to-protobuf. DO NOT EDIT. - -package v1 - -func (*ReplicaSet) ProtoMessage() {} - -func (*ReplicaSetSpec) ProtoMessage() {} - -func (*ReplicaSetStatus) ProtoMessage() {} diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go b/staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go index cd6ec92bc8ac5..9c21e2348a7f3 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go @@ -191,7 +191,7 @@ func ruleMatchesResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool name := attrs.GetName() for _, gr := range r.Resources { - if gr.Group == apiGroup { + if gr.Group == apiGroup || gr.Group == "*" { if len(gr.Resources) == 0 { return true } diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/checker_test.go b/staging/src/k8s.io/apiserver/pkg/audit/policy/checker_test.go index 1e889c3c6786a..da747a0be9ade 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/checker_test.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/checker_test.go @@ -84,6 +84,17 @@ var ( ResourceRequest: true, Path: "/api/v1/namespaces/default/pods/busybox", }, + "ClusterRoleUpdate": &authorizer.AttributesRecord{ + User: tim, + Verb: "update", + APIGroup: "rbac.authorization.k8s.io", + APIVersion: "v1beta1", + Resource: "clusterroles", + Subresource: "status", + Name: "somerole", + ResourceRequest: true, + Path: "/apis/rbac.authorization.k8s.io/v1beta1/clusterroles/somerole", + }, } rules = map[string]audit.PolicyRule{ @@ -140,6 +151,14 @@ var ( }}, Namespaces: []string{""}, }, + "getGroupWildCardMatchingWithSubresourceWildcardMatching": { + Level: audit.LevelRequestResponse, + Verbs: []string{"update"}, + Resources: []audit.GroupResources{{ + Group: "*", + Resources: []string{"*/status"}, + }}, + }, "getLogs": { Level: audit.LevelRequestResponse, Verbs: []string{"get"}, @@ -242,6 +261,7 @@ func testAuditLevel(t *testing.T, stages []audit.Stage) { test(t, "Unauthorized", audit.LevelMetadata, stages, stages, "tims", "default") test(t, "Unauthorized", audit.LevelNone, stages, stages, "humans") test(t, "Unauthorized", audit.LevelMetadata, stages, stages, "humans", "default") + test(t, "ClusterRoleUpdate", audit.LevelRequestResponse, stages, stages, "getGroupWildCardMatchingWithSubresourceWildcardMatching") } func TestChecker(t *testing.T) { diff --git a/staging/src/k8s.io/apiserver/pkg/audit/union.go b/staging/src/k8s.io/apiserver/pkg/audit/union.go index 0766a9207b95f..39dd74f740e8c 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/union.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/union.go @@ -48,7 +48,6 @@ func (u union) ProcessEvents(events ...*auditinternal.Event) bool { func (u union) Run(stopCh <-chan struct{}) error { var funcs []func() error for _, backend := range u.backends { - backend := backend funcs = append(funcs, func() error { return backend.Run(stopCh) }) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go index a9e76fe011896..e5419d0cec9e4 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go @@ -174,7 +174,7 @@ func (c *RequestHeaderAuthRequestController) AllowedClientNames() []string { // Run starts RequestHeaderAuthRequestController controller and blocks until stopCh is closed. func (c *RequestHeaderAuthRequestController) Run(ctx context.Context, workers int) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer c.queue.ShutDown() klog.Infof("Starting %s", c.name) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go index ee8c89f5cedad..a3a7833156391 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go @@ -24,8 +24,8 @@ import ( "strings" "unicode/utf8" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/streaming/pkg/httpstream/wsstream" ) const bearerProtocolPrefix = "base64url.bearer.authorization.k8s.io." diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go b/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go index a638fd4ac190a..befc05d706bf2 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go @@ -82,24 +82,24 @@ import ( // // Examples: // -// cidr('192.168.0.0/24').containsIP(ip('192.168.0.1')) // returns true -// cidr('192.168.0.0/24').containsIP(ip('192.168.1.1')) // returns false -// cidr('192.168.0.0/24').containsIP('192.168.0.1') // returns true -// cidr('192.168.0.0/24').containsIP('192.168.1.1') // returns false -// cidr('192.168.0.0/16').containsCIDR(cidr('192.168.10.0/24')) // returns true -// cidr('192.168.1.0/24').containsCIDR(cidr('192.168.2.0/24')) // returns false -// cidr('192.168.0.0/16').containsCIDR('192.168.10.0/24') // returns true -// cidr('192.168.1.0/24').containsCIDR('192.168.2.0/24') // returns false -// cidr('192.168.0.1/24').ip() // returns ipAddr('192.168.0.1') -// cidr('192.168.0.1/24').ip().family() // returns '4' -// cidr('::1/128').ip() // returns ipAddr('::1') -// cidr('::1/128').ip().family() // returns '6' -// cidr('192.168.0.0/24').masked() // returns cidr('192.168.0.0/24') -// cidr('192.168.0.1/24').masked() // returns cidr('192.168.0.0/24') -// cidr('192.168.0.0/24') == cidr('192.168.0.0/24').masked() // returns true, CIDR was already in canonical format -// cidr('192.168.0.1/24') == cidr('192.168.0.1/24').masked() // returns false, CIDR was not in canonical format -// cidr('192.168.0.0/16').prefixLength() // returns 16 -// cidr('::1/128').prefixLength() // returns 128 +// cidr('192.168.0.0/24').containsIP(ip('192.168.0.1')) // returns true +// cidr('192.168.0.0/24').containsIP(ip('192.168.1.1')) // returns false +// cidr('192.168.0.0/24').containsIP('192.168.0.1') // returns true +// cidr('192.168.0.0/24').containsIP('192.168.1.1') // returns false +// cidr('192.168.0.0/16').containsCIDR(cidr('192.168.10.0/24')) // returns true +// cidr('192.168.1.0/24').containsCIDR(cidr('192.168.2.0/24')) // returns false +// cidr('192.168.0.0/16').containsCIDR('192.168.10.0/24') // returns true +// cidr('192.168.1.0/24').containsCIDR('192.168.2.0/24') // returns false +// cidr('192.168.0.1/24').ip() // returns ipAddr('192.168.0.1') +// cidr('192.168.0.1/24').ip().family() // returns '4' +// cidr('::1/128').ip() // returns ipAddr('::1') +// cidr('::1/128').ip().family() // returns '6' +// cidr('192.168.0.0/24').masked() // returns cidr('192.168.0.0/24') +// cidr('192.168.0.1/24').masked() // returns cidr('192.168.0.0/24') +// cidr('192.168.0.0/24') == cidr('192.168.0.0/24').masked() // returns true, CIDR was already in canonical format +// cidr('192.168.0.1/24') == cidr('192.168.0.1/24').masked() // returns false, CIDR was not in canonical format +// cidr('192.168.0.0/16').prefixLength() // returns 16 +// cidr('::1/128').prefixLength() // returns 128 func CIDR() cel.EnvOption { return cel.Lib(cidrsLib) } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/format.go b/staging/src/k8s.io/apiserver/pkg/cel/library/format.go index 2adc35c905d90..e8b2d07b557c9 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/format.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/format.go @@ -41,53 +41,53 @@ var ( // Format provides a CEL library exposing common named Kubernetes string // validations. Can be used in CRD ValidationRules messageExpression. // -// Example: +// Example: // -// rule: format.dns1123label.validate(object.metadata.name).hasValue() -// messageExpression: format.dns1123label.validate(object.metadata.name).value().join("\n") +// rule: format.dns1123label.validate(object.metadata.name).hasValue() +// messageExpression: format.dns1123label.validate(object.metadata.name).value().join("\n") // // format.named(name: string) -> ?Format // -// Returns the Format with the given name, if it exists. Otherwise, optional.none -// Allowed names are: -// - `dns1123Label` -// - `dns1123Subdomain` -// - `dns1035Label` -// - `qualifiedName` -// - `dns1123LabelPrefix` -// - `dns1123SubdomainPrefix` -// - `dns1035LabelPrefix` -// - `labelValue` -// - `uri` -// - `uuid` -// - `byte` -// - `date` -// - `datetime` +// Returns the Format with the given name, if it exists. Otherwise, optional.none +// Allowed names are: +// - `dns1123Label` +// - `dns1123Subdomain` +// - `dns1035Label` +// - `qualifiedName` +// - `dns1123LabelPrefix` +// - `dns1123SubdomainPrefix` +// - `dns1035LabelPrefix` +// - `labelValue` +// - `uri` +// - `uuid` +// - `byte` +// - `date` +// - `datetime` // // format.() -> Format // -// Convenience functions for all the named formats are also available +// Convenience functions for all the named formats are also available. // -// Examples: -// format.dns1123Label().validate("my-label-name") -// format.dns1123Subdomain().validate("apiextensions.k8s.io") -// format.dns1035Label().validate("my-label-name") -// format.qualifiedName().validate("apiextensions.k8s.io/v1beta1") -// format.dns1123LabelPrefix().validate("my-label-prefix-") -// format.dns1123SubdomainPrefix().validate("mysubdomain.prefix.-") -// format.dns1035LabelPrefix().validate("my-label-prefix-") -// format.uri().validate("http://example.com") -// Uses same pattern as isURL, but returns an error -// format.uuid().validate("123e4567-e89b-12d3-a456-426614174000") -// format.byte().validate("aGVsbG8=") -// format.date().validate("2021-01-01") -// format.datetime().validate("2021-01-01T00:00:00Z") +// Examples: +// +// format.dns1123Label().validate("my-label-name") +// format.dns1123Subdomain().validate("apiextensions.k8s.io") +// format.dns1035Label().validate("my-label-name") +// format.qualifiedName().validate("apiextensions.k8s.io/v1beta1") +// format.dns1123LabelPrefix().validate("my-label-prefix-") +// format.dns1123SubdomainPrefix().validate("mysubdomain.prefix.-") +// format.dns1035LabelPrefix().validate("my-label-prefix-") +// format.uri().validate("http://example.com") +// Uses same pattern as isURL, but returns an error +// format.uuid().validate("123e4567-e89b-12d3-a456-426614174000") +// format.byte().validate("aGVsbG8=") +// format.date().validate("2021-01-01") +// format.datetime().validate("2021-01-01T00:00:00Z") // - // .validate(str: string) -> ?list // -// Validates the given string against the given format. Returns optional.none -// if the string is valid, otherwise a list of validation error strings. +// Validates the given string against the given format. Returns optional.none +// if the string is valid, otherwise a list of validation error strings. func Format() cel.EnvOption { return cel.Lib(formatLib) } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go b/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go index 5a089ae1cdfe5..fa5e9f001dddc 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go @@ -99,30 +99,30 @@ import ( // // Examples: // -// ip('127.0.0.1').family() // returns '4” -// ip('::1').family() // returns '6' -// ip('127.0.0.1').family() == 4 // returns true -// ip('::1').family() == 6 // returns true -// ip('0.0.0.0').isUnspecified() // returns true -// ip('127.0.0.1').isUnspecified() // returns false -// ip('::').isUnspecified() // returns true -// ip('::1').isUnspecified() // returns false -// ip('127.0.0.1').isLoopback() // returns true -// ip('192.168.0.1').isLoopback() // returns false -// ip('::1').isLoopback() // returns true -// ip('2001:db8::abcd').isLoopback() // returns false -// ip('224.0.0.1').isLinkLocalMulticast() // returns true -// ip('224.0.1.1').isLinkLocalMulticast() // returns false -// ip('ff02::1').isLinkLocalMulticast() // returns true -// ip('fd00::1').isLinkLocalMulticast() // returns false -// ip('169.254.169.254').isLinkLocalUnicast() // returns true -// ip('192.168.0.1').isLinkLocalUnicast() // returns false -// ip('fe80::1').isLinkLocalUnicast() // returns true -// ip('fd80::1').isLinkLocalUnicast() // returns false -// ip('192.168.0.1').isGlobalUnicast() // returns true -// ip('255.255.255.255').isGlobalUnicast() // returns false -// ip('2001:db8::abcd').isGlobalUnicast() // returns true -// ip('ff00::1').isGlobalUnicast() // returns false +// ip('127.0.0.1').family() // returns '4” +// ip('::1').family() // returns '6' +// ip('127.0.0.1').family() == 4 // returns true +// ip('::1').family() == 6 // returns true +// ip('0.0.0.0').isUnspecified() // returns true +// ip('127.0.0.1').isUnspecified() // returns false +// ip('::').isUnspecified() // returns true +// ip('::1').isUnspecified() // returns false +// ip('127.0.0.1').isLoopback() // returns true +// ip('192.168.0.1').isLoopback() // returns false +// ip('::1').isLoopback() // returns true +// ip('2001:db8::abcd').isLoopback() // returns false +// ip('224.0.0.1').isLinkLocalMulticast() // returns true +// ip('224.0.1.1').isLinkLocalMulticast() // returns false +// ip('ff02::1').isLinkLocalMulticast() // returns true +// ip('fd00::1').isLinkLocalMulticast() // returns false +// ip('169.254.169.254').isLinkLocalUnicast() // returns true +// ip('192.168.0.1').isLinkLocalUnicast() // returns false +// ip('fe80::1').isLinkLocalUnicast() // returns true +// ip('fd80::1').isLinkLocalUnicast() // returns false +// ip('192.168.0.1').isGlobalUnicast() // returns true +// ip('255.255.255.255').isGlobalUnicast() // returns false +// ip('2001:db8::abcd').isGlobalUnicast() // returns true +// ip('ff00::1').isGlobalUnicast() // returns false func IP() cel.EnvOption { return cel.Lib(ipLib) } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/quantity.go b/staging/src/k8s.io/apiserver/pkg/cel/library/quantity.go index f9036a47c7990..f3fd8b1c50280 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/quantity.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/quantity.go @@ -82,12 +82,12 @@ import ( // // Examples: // -// quantity("50000000G").isInteger() // returns true -// quantity("50k").isInteger() // returns true -// quantity("9999999999999999999999999999999999999G").asInteger() // error: cannot convert value to integer -// quantity("9999999999999999999999999999999999999G").isInteger() // returns false -// quantity("50k").asInteger() == 50000 // returns true -// quantity("50k").sub(20000).asApproximateFloat() == 30000 // returns true +// quantity("50000000G").isInteger() // returns true +// quantity("50k").isInteger() // returns true +// quantity("9999999999999999999999999999999999999G").asInteger() // error: cannot convert value to integer +// quantity("9999999999999999999999999999999999999G").isInteger() // returns false +// quantity("50k").asInteger() == 50000 // returns true +// quantity("50k").sub(20000).asApproximateFloat() == 30000 // returns true // // Arithmetic // @@ -105,11 +105,11 @@ import ( // // Examples: // -// quantity("50k").add("20k") == quantity("70k") // returns true -// quantity("50k").add(20) == quantity("50020") // returns true -// quantity("50k").sub("20k") == quantity("30k") // returns true -// quantity("50k").sub(20000) == quantity("30k") // returns true -// quantity("50k").add(20).sub(quantity("100k")).sub(-50000) == quantity("20") // returns true +// quantity("50k").add("20k") == quantity("70k") // returns true +// quantity("50k").add(20) == quantity("50020") // returns true +// quantity("50k").sub("20k") == quantity("30k") // returns true +// quantity("50k").sub(20000) == quantity("30k") // returns true +// quantity("50k").add(20).sub(quantity("100k")).sub(-50000) == quantity("20") // returns true // // Comparisons // @@ -125,13 +125,13 @@ import ( // // Examples: // -// quantity("200M").compareTo(quantity("0.2G")) // returns 0 -// quantity("50M").compareTo(quantity("50Mi")) // returns -1 -// quantity("50Mi").compareTo(quantity("50M")) // returns 1 -// quantity("150Mi").isGreaterThan(quantity("100Mi")) // returns true -// quantity("50Mi").isGreaterThan(quantity("100Mi")) // returns false -// quantity("50M").isLessThan(quantity("100M")) // returns true -// quantity("100M").isLessThan(quantity("50M")) // returns false +// quantity("200M").compareTo(quantity("0.2G")) // returns 0 +// quantity("50M").compareTo(quantity("50Mi")) // returns -1 +// quantity("50Mi").compareTo(quantity("50M")) // returns 1 +// quantity("150Mi").isGreaterThan(quantity("100Mi")) // returns true +// quantity("50Mi").isGreaterThan(quantity("100Mi")) // returns false +// quantity("50M").isLessThan(quantity("100M")) // returns true +// quantity("100M").isLessThan(quantity("50M")) // returns false func Quantity() cel.EnvOption { return cel.Lib(quantityLib) } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/openapi/adaptor.go b/staging/src/k8s.io/apiserver/pkg/cel/openapi/adaptor.go index f95ec2a372234..a5bfac262063d 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/openapi/adaptor.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/openapi/adaptor.go @@ -156,7 +156,6 @@ func (s *Schema) Nullable() bool { func (s *Schema) AllOf() []common.Schema { var res []common.Schema for _, nestedSchema := range s.Schema.AllOf { - nestedSchema := nestedSchema res = append(res, &Schema{&nestedSchema}) } return res @@ -165,7 +164,6 @@ func (s *Schema) AllOf() []common.Schema { func (s *Schema) AnyOf() []common.Schema { var res []common.Schema for _, nestedSchema := range s.Schema.AnyOf { - nestedSchema := nestedSchema res = append(res, &Schema{&nestedSchema}) } return res @@ -174,7 +172,6 @@ func (s *Schema) AnyOf() []common.Schema { func (s *Schema) OneOf() []common.Schema { var res []common.Schema for _, nestedSchema := range s.Schema.OneOf { - nestedSchema := nestedSchema res = append(res, &Schema{&nestedSchema}) } return res diff --git a/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs.go b/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs.go index 56e2a4bbd3a0b..808d643415601 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs.go @@ -86,7 +86,9 @@ func populateRefs(schemaOf func(ref string) (*spec.Schema, bool), visited sets.S } if populated != result.AdditionalProperties.Schema { changed = true - result.AdditionalProperties.Schema = populated + newProps := *result.AdditionalProperties + newProps.Schema = populated + result.AdditionalProperties = &newProps } } // schema is a list, populate its items @@ -97,7 +99,9 @@ func populateRefs(schemaOf func(ref string) (*spec.Schema, bool), visited sets.S } if populated != result.Items.Schema { changed = true - result.Items.Schema = populated + newItems := *result.Items + newItems.Schema = populated + result.Items = &newItems } } if changed { diff --git a/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs_test.go b/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs_test.go new file mode 100644 index 0000000000000..c2f0fc5594f7f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/cel/openapi/resolver/refs_test.go @@ -0,0 +1,124 @@ +/* +Copyright 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 resolver + +import ( + "sync" + "testing" + + "github.com/go-openapi/jsonreference" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "k8s.io/kube-openapi/pkg/validation/spec" +) + +func schemaRef(ref string) *spec.Schema { + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: spec.MustCreateRef(ref), + }, + } +} + +func newSchema(ref string) *spec.Schema { + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + ID: ref, + }, + } +} + +// rootSchema returns a schema with references in all places where TestPopulateRefs +// resolves references. +func rootSchema() *spec.Schema { + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "a": *schemaRef("prop"), + }, + AdditionalProperties: &spec.SchemaOrBool{ + Schema: schemaRef("additional"), + }, + Items: &spec.SchemaOrArray{ + Schema: schemaRef("item"), + }, + }, + } +} + +func resolvedRootSchema() *spec.Schema { + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "a": *newSchema("prop"), + }, + AdditionalProperties: &spec.SchemaOrBool{ + Schema: newSchema("additional"), + }, + Items: &spec.SchemaOrArray{ + Schema: newSchema("item"), + }, + }, + } +} + +func TestPopulateRefs(t *testing.T) { + schemas := map[string]*spec.Schema{ + "root": rootSchema(), + } + + // Add one schema for each of the references above, with an ID that allows + // verifying that the right schema got resolved. + for _, ref := range []string{"prop", "additional", "item"} { + schemas[ref] = newSchema(ref) + } + + schemaOf := func(ref string) (*spec.Schema, bool) { + schema, ok := schemas[ref] + return schema, ok + } + + // Comparing the root schema below detects mutations where the data is semantically different, + // but it cannot detect undesired writes where the thing being written is the same (unlikely, + // but it could happen). Therefore we run two PopulateRefs calls and let the data race + // detector warn about concurrent, uncoordinated writes. + var wg sync.WaitGroup + var actualResolved *spec.Schema + wg.Go(func() { + schema, err := PopulateRefs(schemaOf, "root") + if err != nil { + t.Errorf("first PopulateRefs failed: %v", err) + } + actualResolved = schema + }) + wg.Go(func() { + _, err := PopulateRefs(schemaOf, "root") + if err != nil { + t.Errorf("second PopulateRefs failed: %v", err) + } + }) + wg.Wait() + + if diff := cmp.Diff(resolvedRootSchema(), actualResolved, cmpopts.IgnoreUnexported(jsonreference.Ref{})); diff != "" { + t.Errorf("unexpected resolved schema (- want, + got):\n%s", diff) + } + + if diff := cmp.Diff(rootSchema(), schemas["root"], cmpopts.IgnoreUnexported(jsonreference.Ref{})); diff != "" { + t.Errorf("read-only input schema got modified (- original, + modification):\n%s", diff) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go b/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go index 5e325ba447d1f..1b8e8c72e04fb 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go @@ -559,9 +559,13 @@ func writeDiscoveryResponse( return } var targetGV schema.GroupVersion - if mediaType.Convert == nil || - (mediaType.Convert.GroupVersion() != apidiscoveryv2.SchemeGroupVersion && - mediaType.Convert.GroupVersion() != apidiscoveryv2beta1.SchemeGroupVersion) { + if mediaType.Convert == nil { + utilruntime.HandleError(fmt.Errorf("expected aggregated discovery group version, got unknown group and version")) + w.WriteHeader(http.StatusInternalServerError) + return + } + if mediaType.Convert.GroupVersion() != apidiscoveryv2.SchemeGroupVersion && + mediaType.Convert.GroupVersion() != apidiscoveryv2beta1.SchemeGroupVersion { utilruntime.HandleError(fmt.Errorf("expected aggregated discovery group version, got group: %s, version %s", mediaType.Convert.Group, mediaType.Convert.Version)) w.WriteHeader(http.StatusInternalServerError) return diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/metrics_test.go index e5a420b51d092..93565eb0a35b4 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/metrics_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/metrics_test.go @@ -93,6 +93,7 @@ func TestMetricsModified(t *testing.T) { } func TestPeerAggregatedDiscoveryMetrics(t *testing.T) { + legacyregistry.Reset() featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.UnknownVersionInteroperabilityProxy, true) manager := discoveryendpoint.NewResourceManager("apis") localGroup := newAPIGroup("local.example.com", "v1", "local-resource") @@ -107,9 +108,9 @@ func TestPeerAggregatedDiscoveryMetrics(t *testing.T) { peerAggregatedDiscoveryManager := discoveryendpoint.NewPeerAggregatedDiscoveryHandler("test-server", manager, peerProvider, "apis") wrapped := discoveryendpoint.WrapAggregatedDiscoveryToHandler(manager, manager, peerAggregatedDiscoveryManager) - legacyregistry.MustRegister(discoveryendpoint.PeerAggregatedCacheHitsCounter) - legacyregistry.MustRegister(discoveryendpoint.PeerAggregatedCacheMissesCounter) - legacyregistry.MustRegister(discoveryendpoint.NoPeerDiscoveryRequestCounter) + _ = legacyregistry.Register(discoveryendpoint.PeerAggregatedCacheHitsCounter) + _ = legacyregistry.Register(discoveryendpoint.PeerAggregatedCacheMissesCounter) + _ = legacyregistry.Register(discoveryendpoint.NoPeerDiscoveryRequestCounter) // Make 3 peer-aggregated requests. fetchPath(wrapped, "application/json", "/apis", "") diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit.go index d25bf35ae3af0..83537bd215e51 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit.go @@ -149,7 +149,6 @@ func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRul // writeLatencyToAnnotation writes the latency incurred in different // layers of the apiserver to the annotations of the audit object. -// it should be invoked after ev.StageTimestamp has been set appropriately. func writeLatencyToAnnotation(ctx context.Context) { ac := audit.AuditContextFrom(ctx) // we will track latency in annotation only when the total latency @@ -157,7 +156,7 @@ func writeLatencyToAnnotation(ctx context.Context) { // traces in rest/handlers for create, delete, update, // get, list, and deletecollection. const threshold = 500 * time.Millisecond - latency := ac.GetEventStageTimestamp().Sub(ac.GetEventRequestReceivedTimestamp().Time) + latency := time.Since(ac.GetEventRequestReceivedTimestamp().Time) if latency <= threshold { return } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go index e2e77ac3742bd..03bac7a07b343 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go @@ -23,6 +23,7 @@ import ( "net/http/httptest" "net/url" "reflect" + "regexp" "sync" "testing" "time" @@ -224,7 +225,7 @@ func TestAudit(t *testing.T) { shortRunningPath := "/api/v1/namespaces/default/pods/foo" longRunningPath := "/api/v1/namespaces/default/pods?watch=true" - delay := 500 * time.Millisecond + delay := 501 * time.Millisecond for _, test := range []struct { desc string @@ -351,6 +352,10 @@ func TestAudit(t *testing.T) { Verb: "update", RequestURI: shortRunningPath, ResponseStatus: &metav1.Status{Code: 200}, + Annotations: map[string]string{ + "apiserver.latency.k8s.io/response-write": "^[0-9.]+[µnm]s$", + "apiserver.latency.k8s.io/total": "^[0-9.]+[µnm]s$", + }, }, }, true, @@ -713,6 +718,7 @@ func TestAudit(t *testing.T) { // simplified long-running check return ri.Verb == "watch" }) + handler = WithLatencyTrackers(handler) handler = WithAuditInit(handler) req, _ := http.NewRequestWithContext(ctx, test.verb, test.path, nil) @@ -772,6 +778,19 @@ func TestAudit(t *testing.T) { if (event.ResponseStatus != nil) && (event.ResponseStatus.Code != expect.ResponseStatus.Code) { t.Errorf("Unexpected status code : %d", event.ResponseStatus.Code) } + + for k, v := range expect.Annotations { + if actual, exists := event.Annotations[k]; !exists { + t.Errorf("Expect key %s in the annotations but it does not exist", k) + } else if matched, _ := regexp.MatchString(v, actual); !matched { + t.Errorf("Annotation %s value %q does not match regex %q", k, actual, v) + } + } + for k := range event.Annotations { + if _, exists := expect.Annotations[k]; !exists { + t.Errorf("Unexpected key %s in the annotations", k) + } + } } }) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/cache.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/cache.go index cf73107896a87..c0ef42c583a13 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/cache.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/cache.go @@ -18,11 +18,12 @@ package impersonation import ( "crypto/sha256" + "encoding/binary" + "encoding/hex" "fmt" "hash/fnv" "time" - - "golang.org/x/crypto/cryptobyte" + "unsafe" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -60,21 +61,19 @@ func modeIndexCacheKey(attributes authorizer.Attributes) string { key := attributes.GetUser().GetName() // hash the name so our cache size is predicable regardless of the size of usernames // collisions do not matter for this logic as it simply changes the ordering of the modes used - hash := fnvSum128a([]byte(key)) - return fmt.Sprintf("%x", hash) -} - -func fnvSum128a(data []byte) []byte { h := fnv.New128a() - h.Write(data) + h.Write([]byte(key)) var sum [16]byte - return h.Sum(sum[:0]) + h.Sum(sum[:0]) + buf := make([]byte, 32) // 16 bytes of hash -> 32 hex chars + hex.Encode(buf, sum[:]) + return toString(buf) // only safe because buf is heap allocated via make } func newModeIndexCache() *modeIndexCache { return &modeIndexCache{ - // each entry is roughly ~24 bytes (16 bytes for the hashed key, 8 bytes for value) - // thus at even 10k entries, we should use less than 1 MB memory + // each entry is roughly ~100 bytes (hashed string key + int value + LRU list/map overhead) + // thus at even 10k entries, we should use about 1 MB memory // this hardcoded size allows us to remember many users without leaking memory cache: lru.New(10_000), } @@ -246,7 +245,7 @@ func buildKey(wantedUser *user.DefaultInfo, attributes authorizer.Attributes) (s } }) - return b.build() + return b.build(), nil } func addUser(b *cacheKeyBuilder, u user.Info) { @@ -265,20 +264,21 @@ func addUser(b *cacheKeyBuilder, u user.Info) { }) } -// cacheKeyBuilder adds syntactic sugar on top of cryptobyte.Builder to make it easier to use for complex inputs. +// cacheKeyBuilder builds a binary key from structured inputs using uint32 length-prefixed encoding, then hashes +// it with SHA-256. All methods operate on a single shared buffer to avoid closure and child-builder allocations. type cacheKeyBuilder struct { namespace string // in the programming sense, not the Kubernetes concept - builder *cryptobyte.Builder + builder []byte } func newCacheKeyBuilder(namespace string) *cacheKeyBuilder { // start with a reasonable size to avoid too many allocations - return &cacheKeyBuilder{namespace: namespace, builder: cryptobyte.NewBuilder(make([]byte, 0, 384))} + return &cacheKeyBuilder{namespace: namespace, builder: make([]byte, 0, 384)} } func (c *cacheKeyBuilder) addString(value string) *cacheKeyBuilder { c.addLengthPrefixed(func(c *cacheKeyBuilder) { - c.builder.AddBytes([]byte(value)) + c.builder = append(c.builder, value...) }) return c } @@ -297,24 +297,36 @@ func (c *cacheKeyBuilder) addBool(value bool) *cacheKeyBuilder { if value { b = 1 } - c.builder.AddUint8(b) + c.builder = append(c.builder, b) return c } type builderContinuation func(child *cacheKeyBuilder) func (c *cacheKeyBuilder) addLengthPrefixed(f builderContinuation) { - c.builder.AddUint32LengthPrefixed(func(b *cryptobyte.Builder) { - c := &cacheKeyBuilder{namespace: c.namespace, builder: b} - f(c) - }) + offset := len(c.builder) + c.builder = append(c.builder, 0, 0, 0, 0) // placeholder for uint32 length + f(c) + binary.BigEndian.PutUint32(c.builder[offset:], uint32(len(c.builder)-offset-4)) } -func (c *cacheKeyBuilder) build() (string, error) { - key, err := c.builder.Bytes() - if err != nil { - return "", err +func (c *cacheKeyBuilder) build() string { + hashed := sha256.Sum256(c.builder) // reduce the size of the cache key to keep the overall cache size small + // sha256 = 32 bytes -> 64 hex chars + "/" + namespace + buf := make([]byte, 0, 64+1+len(c.namespace)) + buf = hex.AppendEncode(buf, hashed[:]) + buf = append(buf, '/') + buf = append(buf, c.namespace...) + return toString(buf) // only safe because buf is heap allocated via make +} + +// toString performs unholy acts to avoid allocations +func toString(b []byte) string { + // unsafe.SliceData relies on cap whereas we want to rely on len + if len(b) == 0 { + return "" } - hash := sha256.Sum256(key) // reduce the size of the cache key to keep the overall cache size small - return fmt.Sprintf("%x/%s", hash[:], c.namespace), nil + // Copied from go 1.20.1 strings.Builder.String + // https://github.com/golang/go/blob/202a1a57064127c3f19d96df57b9f9586145e21c/src/strings/builder.go#L48 + return unsafe.String(unsafe.SliceData(b), len(b)) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go index 861e6d69904d3..e2a6e953d5ebc 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" "strings" + "time" authenticationv1 "k8s.io/api/authentication/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -30,6 +31,7 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/filters" + "k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/server/httplog" @@ -42,10 +44,25 @@ import ( // expression of impersonation access. For example, a service account may be authorized to impersonate the // node that it is associated with but only when listing pods. See the linked KEP for further details. func WithConstrainedImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler { + metrics.RegisterMetrics() + + ma := &metricsAuthorizer{ + delegate: a, + recordAuthorizationCall: metrics.RecordImpersonationAuthorizationCall, + } + + recordAttempt := func(ctx context.Context, mode, decision string, duration time.Duration) { + metrics.RecordImpersonationAttempt(mode, decision, duration) + request.TrackImpersonationLatency(ctx, duration) + } + return &constrainedImpersonationHandler{ handler: handler, - tracker: newImpersonationModesTracker(a), + tracker: newImpersonationModesTracker(ma), s: s, + + recordAttempt: recordAttempt, + metricsAuthorizer: ma, } } @@ -53,6 +70,10 @@ type constrainedImpersonationHandler struct { handler http.Handler tracker *impersonationModesTracker s runtime.NegotiatedSerializer + + // to allow unit tests to override metrics recording + recordAttempt func(ctx context.Context, mode, decision string, duration time.Duration) + metricsAuthorizer *metricsAuthorizer } func (c *constrainedImpersonationHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -78,12 +99,16 @@ func (c *constrainedImpersonationHandler) ServeHTTP(w http.ResponseWriter, req * return } + start := time.Now() impersonatedUser, err := c.tracker.getImpersonatedUser(ctx, wantedUser, attributes) + duration := time.Since(start) if err != nil { + c.recordAttempt(ctx, "", "denied", duration) klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "err", err) responsewriters.RespondWithError(w, req, err, c.s) return } + c.recordAttempt(ctx, modeFromConstraint(impersonatedUser.constraint), "allowed", duration) req = req.WithContext(request.WithUser(ctx, impersonatedUser.user)) httplog.LogOf(req, w).Addf("%v is impersonating %v", userString(requestor), userString(impersonatedUser.user)) @@ -249,3 +274,49 @@ func (t *impersonationModesTracker) getImpersonatedUser(ctx context.Context, wan // this should not happen, but make sure we fail closed when no impersonation mode succeeded return nil, errors.New("all impersonation modes failed") } + +type metricsAuthorizer struct { + delegate authorizer.Authorizer + recordAuthorizationCall func(mode, decision string, duration time.Duration) +} + +func (m *metricsAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) { + start := time.Now() + decision, reason, err := m.delegate.Authorize(ctx, attributes) + duration := time.Since(start) + + m.recordAuthorizationCall(modeFromVerb(attributes.GetVerb()), decisionToLabel(decision), duration) + + return decision, reason, err +} + +func modeFromConstraint(constraint string) string { + if len(constraint) == 0 { + return "legacy" + } + return modeFromVerb(constraint) +} + +func modeFromVerb(verb string) string { + switch { + case verb == "impersonate": + return "legacy" + case verb == "impersonate:associated-node" || strings.HasPrefix(verb, "impersonate-on:associated-node:"): + return "associated-node" + case verb == "impersonate:arbitrary-node" || strings.HasPrefix(verb, "impersonate-on:arbitrary-node:"): + return "arbitrary-node" + case verb == "impersonate:serviceaccount" || strings.HasPrefix(verb, "impersonate-on:serviceaccount:"): + return "serviceaccount" + case verb == "impersonate:user-info" || strings.HasPrefix(verb, "impersonate-on:user-info:"): + return "user-info" + default: + return "unknown" + } +} + +func decisionToLabel(decision authorizer.Decision) string { + if decision == authorizer.DecisionAllow { + return "allowed" + } + return "denied" +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation_test.go index 361e662f67f41..80e771daefe69 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation_test.go @@ -18,14 +18,18 @@ package impersonation import ( "context" + "crypto/sha256" "encoding/json" "fmt" + "hash/fnv" "io" "net/http" "net/http/httptest" "strconv" "strings" + "sync" "testing" + "time" "github.com/stretchr/testify/require" @@ -33,6 +37,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" + auditinternal "k8s.io/apiserver/pkg/apis/audit" + "k8s.io/apiserver/pkg/audit/policy" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authentication/user" @@ -43,18 +49,45 @@ import ( ) type constrainedImpersonationTest struct { - t *testing.T + t testing.TB constrainedImpersonationHandler *constrainedImpersonationHandler - checkedAttrs []authorizer.Attributes + authzChecks []authzCheck echoCalled bool + auditEvents []*auditinternal.Event + + attemptMode string + attemptDecision string + authorizationMetrics map[string]int // "mode/decision" -> count + + // only used for parallelHandler + lock sync.Mutex +} + +type authzCheck struct { + attributes authorizer.AttributesRecord + decision authorizer.Decision } -func (c *constrainedImpersonationTest) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { - c.checkedAttrs = append(c.checkedAttrs, a) +func (c *constrainedImpersonationTest) ProcessEvents(evs ...*auditinternal.Event) bool { + for _, e := range evs { + c.auditEvents = append(c.auditEvents, e.DeepCopy()) // event is mutated in place so capture its current state + } + return true +} +func (c *constrainedImpersonationTest) Authorize(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { u := a.GetUser() + defer func() { + c.lock.Lock() + c.authzChecks = append(c.authzChecks, authzCheck{ + attributes: comparableAttributes(a), + decision: decision, + }) + c.lock.Unlock() + }() + if u.GetName() == "sa-impersonater" && a.GetVerb() == "impersonate:serviceaccount" && a.GetResource() == "serviceaccounts" { return authorizer.DecisionAllow, "", nil } @@ -75,6 +108,10 @@ func (c *constrainedImpersonationTest) Authorize(ctx context.Context, a authoriz return authorizer.DecisionAllow, "", nil } + if u.GetName() == "user-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "uids" { + return authorizer.DecisionAllow, "", nil + } + if len(u.GetGroups()) > 0 && u.GetGroups()[0] == "group-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "groups" { return authorizer.DecisionAllow, "", nil } @@ -93,12 +130,44 @@ func (c *constrainedImpersonationTest) Authorize(ctx context.Context, a authoriz return authorizer.DecisionAllow, "", nil } + // many-groups-impersonater: can impersonate users and any groups via wildcard + if u.GetName() == "many-groups-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "users" { + return authorizer.DecisionAllow, "", nil + } + if u.GetName() == "many-groups-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "groups" && a.GetName() == "*" { + return authorizer.DecisionAllow, "", nil + } + + // many-extras-impersonater: can impersonate users and any extras via wildcard + if u.GetName() == "many-extras-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "users" { + return authorizer.DecisionAllow, "", nil + } + if u.GetName() == "many-extras-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "userextras" && a.GetSubresource() == "*" && a.GetName() == "*" { + return authorizer.DecisionAllow, "", nil + } + + // mixed-impersonater: can impersonate users, specific groups, and specific extras (but NOT via wildcards) + if u.GetName() == "mixed-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "users" { + return authorizer.DecisionAllow, "", nil + } + // Allow specific group names, but NOT wildcard + if u.GetName() == "mixed-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "groups" && a.GetName() != "*" { + return authorizer.DecisionAllow, "", nil + } + // Allow specific extras for tags.io/env, but NOT wildcard + if u.GetName() == "mixed-impersonater" && a.GetVerb() == "impersonate:user-info" && a.GetResource() == "userextras" && + a.GetSubresource() == "tags.io/env" && a.GetName() != "*" { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionNoOpinion, "deny by default", nil } func (c *constrainedImpersonationTest) echoUserInfoHandler() http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { + c.lock.Lock() c.echoCalled = true + c.lock.Unlock() u, ok := request.UserFrom(req.Context()) if !ok { @@ -169,6 +238,44 @@ func (c *constrainedImpersonationTest) handler() http.Handler { addImpersonation := WithConstrainedImpersonation(c.echoUserInfoHandler(), c, serializer.NewCodecFactory(s)) c.constrainedImpersonationHandler = addImpersonation.(*constrainedImpersonationHandler) + recordAttempt := c.constrainedImpersonationHandler.recordAttempt + c.constrainedImpersonationHandler.recordAttempt = func(ctx context.Context, mode, decision string, duration time.Duration) { + c.attemptMode = mode + c.attemptDecision = decision + recordAttempt(ctx, mode, decision, duration) + } + c.constrainedImpersonationHandler.metricsAuthorizer.recordAuthorizationCall = func(mode, decision string, _ time.Duration) { + if c.authorizationMetrics == nil { + c.authorizationMetrics = map[string]int{} + } + c.authorizationMetrics[mode+"/"+decision]++ + } + + fakeRuleEvaluator := policy.NewFakePolicyRuleEvaluator(auditinternal.LevelRequestResponse, nil) + + // follow the same handler chain order as DefaultBuildHandlerChain + addAudit := filters.WithAudit(addImpersonation, c, fakeRuleEvaluator, nil) + addAuthentication := c.authenticationHandler(addAudit) + addLatencyTrackers := filters.WithLatencyTrackers(addAuthentication) + addRequestInfo := c.requestInfoHandler(addLatencyTrackers) + // set received timestamp >500ms in the past so audit annotations are emitted + addReceivedTimestamp := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + req = req.WithContext(request.WithReceivedTimestamp(req.Context(), time.Now().Add(-time.Hour))) + addRequestInfo.ServeHTTP(w, req) + }) + addAuditInit := filters.WithAuditInit(addReceivedTimestamp) + return addAuditInit +} + +func (c *constrainedImpersonationTest) parallelHandler() http.Handler { + s := runtime.NewScheme() + metav1.AddToGroupVersion(s, metav1.SchemeGroupVersion) + addImpersonation := WithConstrainedImpersonation(c.echoUserInfoHandler(), c, serializer.NewCodecFactory(s)) + c.constrainedImpersonationHandler = addImpersonation.(*constrainedImpersonationHandler) + + c.constrainedImpersonationHandler.recordAttempt = func(ctx context.Context, mode, decision string, _ time.Duration) {} + c.constrainedImpersonationHandler.metricsAuthorizer.recordAuthorizationCall = func(mode, decision string, _ time.Duration) {} + addAuthentication := c.authenticationHandler(addImpersonation) addRequestInfo := c.requestInfoHandler(addAuthentication) return addRequestInfo @@ -195,10 +302,10 @@ func (t *testRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { } func (c *constrainedImpersonationTest) assertAttributes(r testRequest) { - checkedAttrs := c.checkedAttrs - c.checkedAttrs = nil + authzChecks := c.authzChecks + c.authzChecks = nil - require.Equal(c.t, len(r.expectedAttributes), len(checkedAttrs)) + require.Len(c.t, authzChecks, len(r.expectedAuthzChecks)) // normally all authorization checks are done against the requestor // but in some cases such as associated-node, we check against a slightly different user @@ -207,9 +314,10 @@ func (c *constrainedImpersonationTest) assertAttributes(r testRequest) { expectedAttributesUser = r.requestor } - for i := range len(r.expectedAttributes) { - expectedAttributes := withUser(r.expectedAttributes[i], expectedAttributesUser) - require.Equal(c.t, expectedAttributes, comparableAttributes(checkedAttrs[i])) + for i := range len(r.expectedAuthzChecks) { + expectedAttributes := withUser(r.expectedAuthzChecks[i].attributes, expectedAttributesUser) + require.Equal(c.t, expectedAttributes, authzChecks[i].attributes, "authz check %d: attributes mismatch", i) + require.Equal(c.t, r.expectedAuthzChecks[i].decision, authzChecks[i].decision, "authz check %d: decision mismatch", i) } } @@ -234,6 +342,25 @@ func comparableAttributes(attributes authorizer.Attributes) authorizer.Attribute } } +func comparableAuditUser(u *authenticationv1.UserInfo) *user.DefaultInfo { + if u == nil { + return nil + } + var extra map[string][]string + if len(u.Extra) > 0 { + extra = make(map[string][]string, len(u.Extra)) + for k, v := range u.Extra { + extra[k] = v + } + } + return &user.DefaultInfo{ + Name: u.Username, + UID: u.UID, + Groups: u.Groups, + Extra: extra, + } +} + func comparableUser(u user.Info) *user.DefaultInfo { return &user.DefaultInfo{ Name: u.GetName(), @@ -249,6 +376,51 @@ func (c *constrainedImpersonationTest) assertEchoCalled(expectedCalled bool) { require.Equal(c.t, expectedCalled, called) } +func (c *constrainedImpersonationTest) assertMetrics(r testRequest) { + c.t.Helper() + + attemptMode := c.attemptMode + attemptDecision := c.attemptDecision + authorizationMetrics := c.authorizationMetrics + c.attemptMode = "" + c.attemptDecision = "" + c.authorizationMetrics = nil + + require.Equal(c.t, r.expectedAttemptMode, attemptMode, "unexpected attempt mode") + require.Equal(c.t, r.expectedAttemptDecision, attemptDecision, "unexpected attempt decision") + require.Equal(c.t, r.expectedAuthorizationMetrics, authorizationMetrics, "unexpected authorization metrics") +} + +func (c *constrainedImpersonationTest) assertAuditEvents(r testRequest) { + c.t.Helper() + + events := c.auditEvents + c.auditEvents = nil + + require.Len(c.t, events, 2) + + requestReceived := events[0] + responseComplete := events[1] + + require.Empty(c.t, requestReceived.Annotations["apiserver.latency.k8s.io/impersonation"]) + require.Regexp(c.t, "^[0-9.]+[µnm]s$", responseComplete.Annotations["apiserver.latency.k8s.io/impersonation"]) + + require.Nil(c.t, requestReceived.ImpersonatedUser) + require.Equal(c.t, r.expectedImpersonatedUser, comparableAuditUser(responseComplete.ImpersonatedUser)) + + require.Nil(c.t, requestReceived.ResponseStatus) + require.Equal(c.t, r.expectedMessage, responseComplete.ResponseStatus.Message) + + require.Nil(c.t, requestReceived.AuthenticationMetadata) + var expectedAuthenticationMetadata *auditinternal.AuthenticationMetadata + if r.expectedCode == http.StatusOK && r.expectedAttemptMode != "legacy" { + expectedAuthenticationMetadata = &auditinternal.AuthenticationMetadata{ + ImpersonationConstraint: "impersonate:" + r.expectedAttemptMode, + } + } + require.Equal(c.t, expectedAuthenticationMetadata, responseComplete.AuthenticationMetadata) +} + func (c *constrainedImpersonationTest) assertCache(r testRequest) { rr := require.New(c.t) @@ -284,12 +456,12 @@ func (c *constrainedImpersonationTest) assertCache(r testRequest) { } rr.Equal(len(expectedMode.outer), outer.cache.Len()) for expectedKey, expectedUser := range expectedMode.outer { - c.checkCacheEntry(outer, expectedKey.wantedUser, expectedKey.attributes, expectedUser, verb) + c.checkCacheEntry(outer, expectedKey.wantedUser, expectedKey.attributes, expectedKey.rawKey, expectedUser, verb) } rr.Equal(len(expectedMode.inner), inner.cache.Len()) for expectedKey, expectedUser := range expectedMode.inner { - c.checkCacheEntry(inner, expectedKey.wantedUser, authorizer.AttributesRecord{User: expectedKey.requestor}, expectedUser, verb) + c.checkCacheEntry(inner, expectedKey.wantedUser, authorizer.AttributesRecord{User: expectedKey.requestor}, expectedKey.rawKey, expectedUser, verb) } } } @@ -299,7 +471,14 @@ func usernameHash(username string) string { return fmt.Sprintf("%x", hash) } -func (c *constrainedImpersonationTest) checkCacheEntry(cache *impersonationCache, wantedUser *user.DefaultInfo, attributes authorizer.Attributes, expectedUser *user.DefaultInfo, expectedVerb string) { +func fnvSum128a(data []byte) []byte { + h := fnv.New128a() + h.Write(data) + var sum [16]byte + return h.Sum(sum[:0]) +} + +func (c *constrainedImpersonationTest) checkCacheEntry(cache *impersonationCache, wantedUser *user.DefaultInfo, attributes authorizer.Attributes, rawKey string, expectedUser *user.DefaultInfo, expectedVerb string) { rr := require.New(c.t) keyString, err := buildKey(wantedUser, attributes) rr.NoError(err) @@ -308,7 +487,12 @@ func (c *constrainedImpersonationTest) checkCacheEntry(cache *impersonationCache impersonatedUser := val.(*impersonatedUserInfo) rr.Equal(expectedVerb, impersonatedUser.constraint) rr.Equal(expectedUser, impersonatedUser.user) - rr.True(strings.HasSuffix(keyString, "/"+attributes.GetUser().GetName())) + rr.Equal(keyHash(rawKey)+"/"+attributes.GetUser().GetName(), keyString, "hash of %q does not match", rawKey) +} + +func keyHash(rawKey string) string { + hash := sha256.Sum256([]byte(rawKey)) + return fmt.Sprintf("%x", hash[:]) } func withConstrainedImpersonationAttributes(a authorizer.AttributesRecord, mode string) authorizer.AttributesRecord { @@ -346,11 +530,27 @@ func withUser(a authorizer.AttributesRecord, u *user.DefaultInfo) authorizer.Att return a } -func outerCacheKey(wantedUser, requestor *user.DefaultInfo, req *request.RequestInfo) impersonationCacheKey { +func expectAuthzCheck(attributes authorizer.AttributesRecord, decision authorizer.Decision) authzCheck { + return authzCheck{ + attributes: attributes, + decision: decision, + } +} + +func expectAllow(attributes authorizer.AttributesRecord) authzCheck { + return expectAuthzCheck(attributes, authorizer.DecisionAllow) +} + +func expectDeny(attributes authorizer.AttributesRecord) authzCheck { + return expectAuthzCheck(attributes, authorizer.DecisionNoOpinion) +} + +func outerCacheKey(wantedUser, requestor *user.DefaultInfo, req *request.RequestInfo, rawKey string) outerKey { attributes := withUser(requestInfoToAttributes(req), requestor) - return impersonationCacheKey{ + return outerKey{ wantedUser: wantedUser, attributes: &attributes, // use a *authorizer.AttributesRecord so that it can be used in a map key + rawKey: rawKey, } } @@ -360,12 +560,19 @@ type expectedCache struct { } type expectedModeCache struct { - outer map[impersonationCacheKey]*user.DefaultInfo + outer map[outerKey]*user.DefaultInfo inner map[innerKey]*user.DefaultInfo } +type outerKey struct { + wantedUser *user.DefaultInfo + attributes *authorizer.AttributesRecord + rawKey string +} + type innerKey struct { wantedUser, requestor *user.DefaultInfo + rawKey string } type testRequest struct { @@ -377,9 +584,13 @@ type testRequest struct { expectedMessage string expectedAttributesUser *user.DefaultInfo // nil means use requestor - expectedAttributes []authorizer.AttributesRecord + expectedAuthzChecks []authzCheck expectedCache *expectedCache expectedCode int + + expectedAttemptMode string + expectedAttemptDecision string + expectedAuthorizationMetrics map[string]int } func associatedNodeTestCase() []testRequest { @@ -434,13 +645,16 @@ func associatedNodeTestCase() []testRequest { }, modes: map[string]expectedModeCache{ "impersonate:associated-node": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(&user.DefaultInfo{Name: "system:node:*"}, saDefaultOnAnyNode, getSecretRequest): node1FullUserInfo, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{Name: "system:node:*"}, saDefaultOnAnyNode, getSecretRequest, + "\x00\x00\x00\x1d\x00\x00\x00\rsystem:node:*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb7\x00\x00\x00%system:serviceaccount:default:default\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x1bassociate-node-impersonater\x00\x00\x00c\x00\x00\x001authentication.kubernetes.io/associated-node-keys\x00\x00\x00*\x00\x00\x00&authentication.kubernetes.io/node-name\x00\x00\x004\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\asecrets\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): node1FullUserInfo, }, inner: map[innerKey]*user.DefaultInfo{ { wantedUser: &user.DefaultInfo{Name: "system:node:*"}, requestor: saDefaultOnAnyNode, + rawKey: "\x00\x00\x00\x1d\x00\x00\x00\rsystem:node:*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb7\x00\x00\x00%system:serviceaccount:default:default\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x1bassociate-node-impersonater\x00\x00\x00c\x00\x00\x001authentication.kubernetes.io/associated-node-keys\x00\x00\x00*\x00\x00\x00&authentication.kubernetes.io/node-name\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", }: node1FullUserInfo, }, }, @@ -450,10 +664,14 @@ func associatedNodeTestCase() []testRequest { modeIdx: cacheWithOnlyNode1Data.modeIdx, modes: map[string]expectedModeCache{ "impersonate:associated-node": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(&user.DefaultInfo{Name: "system:node:*"}, saDefaultOnAnyNode, getSecretRequest): node1FullUserInfo, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{Name: "system:node:*"}, saDefaultOnAnyNode, getSecretRequest, + "\x00\x00\x00\x1d\x00\x00\x00\rsystem:node:*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb7\x00\x00\x00%system:serviceaccount:default:default\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x1bassociate-node-impersonater\x00\x00\x00c\x00\x00\x001authentication.kubernetes.io/associated-node-keys\x00\x00\x00*\x00\x00\x00&authentication.kubernetes.io/node-name\x00\x00\x004\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\asecrets\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): node1FullUserInfo, // even though this request was made on node2, since the inner cache matched it returned a value for node1 - outerCacheKey(&user.DefaultInfo{Name: "system:node:*"}, saDefaultOnAnyNode, getPodRequest): node1FullUserInfo, + outerCacheKey(&user.DefaultInfo{Name: "system:node:*"}, saDefaultOnAnyNode, getPodRequest, + "\x00\x00\x00\x1d\x00\x00\x00\rsystem:node:*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb7\x00\x00\x00%system:serviceaccount:default:default\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x1bassociate-node-impersonater\x00\x00\x00c\x00\x00\x001authentication.kubernetes.io/associated-node-keys\x00\x00\x00*\x00\x00\x00&authentication.kubernetes.io/node-name\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): node1FullUserInfo, }, inner: cacheWithOnlyNode1Data.modes["impersonate:associated-node"].inner, }, @@ -466,22 +684,30 @@ func associatedNodeTestCase() []testRequest { impersonatedUser: &user.DefaultInfo{Name: "system:node:node1"}, // node matches expectedImpersonatedUser: node1FullUserInfo, expectedAttributesUser: saDefaultOnAnyNode, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getSecretRequest, "associated-node"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "*"}, "associated-node"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getSecretRequest, "associated-node")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "*"}, "associated-node")), + }, + expectedCache: cacheWithOnlyNode1Data, + expectedCode: http.StatusOK, + expectedAttemptMode: "associated-node", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "associated-node/allowed": 2, // impersonate-on + impersonate:associated-node }, - expectedCache: cacheWithOnlyNode1Data, - expectedCode: http.StatusOK, }, { - request: getSecretRequest, - requestor: saDefaultOnNode2, - impersonatedUser: &user.DefaultInfo{Name: "system:node:node2"}, // node matches - expectedImpersonatedUser: node2FullUserInfo, - expectedAttributesUser: saDefaultOnAnyNode, - expectedAttributes: nil, // no authz checks for the second request - expectedCache: cacheWithOnlyNode1Data, - expectedCode: http.StatusOK, + request: getSecretRequest, + requestor: saDefaultOnNode2, + impersonatedUser: &user.DefaultInfo{Name: "system:node:node2"}, // node matches + expectedImpersonatedUser: node2FullUserInfo, + expectedAttributesUser: saDefaultOnAnyNode, + expectedAuthzChecks: nil, // no authz checks for the second request + expectedCache: cacheWithOnlyNode1Data, + expectedCode: http.StatusOK, + expectedAttemptMode: "associated-node", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: nil, // full cache hit }, { request: getSecretRequest, @@ -489,13 +715,20 @@ func associatedNodeTestCase() []testRequest { impersonatedUser: &user.DefaultInfo{Name: "system:node:node1"}, // node does not match expectedMessage: `nodes.authentication.k8s.io "node1" is forbidden: User "system:serviceaccount:default:default" cannot impersonate:arbitrary-node resource "nodes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, expectedAttributesUser: nil, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getSecretRequest, "arbitrary-node"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"}), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getSecretRequest, "arbitrary-node")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"})), + }, + expectedCache: cacheWithOnlyNode1Data, + expectedCode: http.StatusForbidden, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "arbitrary-node/allowed": 1, // impersonate-on:arbitrary-node:get + "arbitrary-node/denied": 1, // impersonate:arbitrary-node nodes + "legacy/denied": 1, // impersonate users }, - expectedCache: cacheWithOnlyNode1Data, - expectedCode: http.StatusForbidden, }, { request: getPodRequest, @@ -503,26 +736,39 @@ func associatedNodeTestCase() []testRequest { impersonatedUser: &user.DefaultInfo{Name: "system:node:node2"}, // node matches expectedImpersonatedUser: node2FullUserInfo, expectedAttributesUser: saDefaultOnAnyNode, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "associated-node"), // one authz check because different request info + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "associated-node")), // one authz check because different request info + }, + expectedCache: cacheWithMultipleRequests, + expectedCode: http.StatusOK, + expectedAttemptMode: "associated-node", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "associated-node/allowed": 1, // impersonate-on only (inner cache hit) }, - expectedCache: cacheWithMultipleRequests, - expectedCode: http.StatusOK, }, { - request: getPodRequest, - requestor: saDefaultOnNode1, - impersonatedUser: &user.DefaultInfo{Name: "system:node:node1"}, // node matches - expectedImpersonatedUser: node1FullUserInfo, - expectedAttributesUser: nil, - expectedAttributes: nil, // no authz checks for pod request via node1 - expectedCache: cacheWithMultipleRequests, - expectedCode: http.StatusOK, + request: getPodRequest, + requestor: saDefaultOnNode1, + impersonatedUser: &user.DefaultInfo{Name: "system:node:node1"}, // node matches + expectedImpersonatedUser: node1FullUserInfo, + expectedAttributesUser: nil, + expectedAuthzChecks: nil, // no authz checks for pod request via node1 + expectedCache: cacheWithMultipleRequests, + expectedCode: http.StatusOK, + expectedAttemptMode: "associated-node", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: nil, // full cache hit }, } } -func TestConstrainedImpersonationFilter(t *testing.T) { +type impersonationTestCase struct { + name string + requests []testRequest +} + +func constrainedImpersonationTestCases() []impersonationTestCase { getPodRequest := &request.RequestInfo{ IsResourceRequest: true, Verb: "get", @@ -576,10 +822,7 @@ func TestConstrainedImpersonationFilter(t *testing.T) { saImpersonator := &user.DefaultInfo{Name: "sa-impersonater"} nodeImpersonator := &user.DefaultInfo{Name: "node-impersonater"} - testCases := []struct { - name string - requests []testRequest - }{ + testCases := []impersonationTestCase{ { name: "impersonating-error", requests: []testRequest{ @@ -587,14 +830,21 @@ func TestConstrainedImpersonationFilter(t *testing.T) { request: getPodRequest, requestor: &user.DefaultInfo{Name: "tester"}, impersonatedUser: anyone, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}), - }, - expectedCache: nil, - expectedCode: http.StatusForbidden, - expectedMessage: `users.authentication.k8s.io "anyone" is forbidden: User "tester" cannot impersonate:user-info resource "users" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"})), + }, + expectedCache: nil, + expectedCode: http.StatusForbidden, + expectedMessage: `users.authentication.k8s.io "anyone" is forbidden: User "tester" cannot impersonate:user-info resource "users" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 1, // impersonate-on:user-info:get + "user-info/denied": 1, // impersonate:user-info users + "legacy/denied": 1, // impersonate users + }, }, }, }, @@ -606,9 +856,9 @@ func TestConstrainedImpersonationFilter(t *testing.T) { requestor: userImpersonator, impersonatedUser: anyone, expectedImpersonatedUser: anyoneAuthenticated, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -616,24 +866,35 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:user-info": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(anyone, userImpersonator, getPodRequest): anyoneAuthenticated, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(anyone, userImpersonator, getPodRequest, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, }, inner: map[innerKey]*user.DefaultInfo{ - {wantedUser: anyone, requestor: userImpersonator}: anyoneAuthenticated, + { + wantedUser: anyone, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: anyoneAuthenticated, }, }, }, }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 2, // impersonate-on + impersonate:user-info + }, }, { request: getAnotherPodRequest, requestor: userImpersonator, impersonatedUser: anyone, expectedImpersonatedUser: anyoneAuthenticated, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getAnotherPodRequest, "user-info"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getAnotherPodRequest, "user-info")), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -641,25 +902,38 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:user-info": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(anyone, userImpersonator, getPodRequest): anyoneAuthenticated, - outerCacheKey(anyone, userImpersonator, getAnotherPodRequest): anyoneAuthenticated, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(anyone, userImpersonator, getPodRequest, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, + outerCacheKey(anyone, userImpersonator, getAnotherPodRequest, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\x00\x00\x00\x03get\x01\x00\x00\x00\x04bar1\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x04foo1\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, }, inner: map[innerKey]*user.DefaultInfo{ - {wantedUser: anyone, requestor: userImpersonator}: anyoneAuthenticated, + { + wantedUser: anyone, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: anyoneAuthenticated, }, }, }, }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 1, // impersonate-on (inner cache hit skips impersonate:user-info) + }, }, { request: createPodRequest, requestor: userImpersonator, impersonatedUser: anyone, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(createPodRequest, "user-info"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}), + expectedAuthzChecks: []authzCheck{ + expectDeny(withImpersonateOnAttributes(createPodRequest, "user-info")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"})), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -667,18 +941,32 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:user-info": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(anyone, userImpersonator, getPodRequest): anyoneAuthenticated, - outerCacheKey(anyone, userImpersonator, getAnotherPodRequest): anyoneAuthenticated, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(anyone, userImpersonator, getPodRequest, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, + outerCacheKey(anyone, userImpersonator, getAnotherPodRequest, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\x00\x00\x00\x03get\x01\x00\x00\x00\x04bar1\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x04foo1\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, }, inner: map[innerKey]*user.DefaultInfo{ - {wantedUser: anyone, requestor: userImpersonator}: anyoneAuthenticated, + { + wantedUser: anyone, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: anyoneAuthenticated, }, }, }, }, - expectedCode: http.StatusForbidden, - expectedMessage: `pods "foo" is forbidden: User "user-impersonater" cannot impersonate-on:user-info:create resource "pods" in API group "" in the namespace "bar": deny by default`, + expectedCode: http.StatusForbidden, + expectedMessage: `pods "foo" is forbidden: User "user-impersonater" cannot impersonate-on:user-info:create resource "pods" in API group "" in the namespace "bar": deny by default`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "user-info/denied": 1, // impersonate-on:user-info:create + "legacy/denied": 1, // impersonate users + }, }, }, }, @@ -690,9 +978,9 @@ func TestConstrainedImpersonationFilter(t *testing.T) { requestor: saImpersonator, impersonatedUser: saUser, expectedImpersonatedUser: saUserAuthenticated, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "serviceaccount"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "serviceaccounts", Namespace: "default", Name: "default"}, "serviceaccount"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "serviceaccount")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "serviceaccounts", Namespace: "default", Name: "default"}, "serviceaccount")), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -700,19 +988,27 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:serviceaccount": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(saUser, saImpersonator, getPodRequest): saUserAuthenticated, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(saUser, saImpersonator, getPodRequest, + "\x00\x00\x005\x00\x00\x00%system:serviceaccount:default:default\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x0fsa-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): saUserAuthenticated, }, inner: map[innerKey]*user.DefaultInfo{ { wantedUser: saUser, requestor: saImpersonator, + rawKey: "\x00\x00\x005\x00\x00\x00%system:serviceaccount:default:default\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x0fsa-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", }: saUserAuthenticated, }, }, }, }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "serviceaccount", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "serviceaccount/allowed": 2, // impersonate-on + impersonate:serviceaccount + }, }, }, }, @@ -723,14 +1019,21 @@ func TestConstrainedImpersonationFilter(t *testing.T) { request: getPodRequest, requestor: saImpersonator, impersonatedUser: nodeUser, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "arbitrary-node"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"}), - }, - expectedCache: nil, - expectedCode: http.StatusForbidden, - expectedMessage: `nodes.authentication.k8s.io "node1" is forbidden: User "sa-impersonater" cannot impersonate:arbitrary-node resource "nodes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "arbitrary-node")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"})), + }, + expectedCache: nil, + expectedCode: http.StatusForbidden, + expectedMessage: `nodes.authentication.k8s.io "node1" is forbidden: User "sa-impersonater" cannot impersonate:arbitrary-node resource "nodes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "arbitrary-node/allowed": 1, // impersonate-on:arbitrary-node:get + "arbitrary-node/denied": 1, // impersonate:arbitrary-node nodes + "legacy/denied": 1, // impersonate users + }, }, }, }, @@ -741,13 +1044,19 @@ func TestConstrainedImpersonationFilter(t *testing.T) { request: createPodRequest, requestor: nodeImpersonator, impersonatedUser: nodeUser, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(createPodRequest, "arbitrary-node"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"}), + expectedAuthzChecks: []authzCheck{ + expectDeny(withImpersonateOnAttributes(createPodRequest, "arbitrary-node")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"})), + }, + expectedCache: nil, + expectedCode: http.StatusForbidden, + expectedMessage: `pods "foo" is forbidden: User "node-impersonater" cannot impersonate-on:arbitrary-node:create resource "pods" in API group "" in the namespace "bar": deny by default`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "arbitrary-node/denied": 1, // impersonate-on:arbitrary-node:create + "legacy/denied": 1, // impersonate users }, - expectedCache: nil, - expectedCode: http.StatusForbidden, - expectedMessage: `pods "foo" is forbidden: User "node-impersonater" cannot impersonate-on:arbitrary-node:create resource "pods" in API group "" in the namespace "bar": deny by default`, }, }, }, @@ -759,9 +1068,9 @@ func TestConstrainedImpersonationFilter(t *testing.T) { requestor: nodeImpersonator, impersonatedUser: nodeUser, expectedImpersonatedUser: nodeUserAuthenticated, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "arbitrary-node"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "arbitrary-node")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node")), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -769,16 +1078,27 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:arbitrary-node": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(nodeUser, nodeImpersonator, getPodRequest): nodeUserAuthenticated, + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(nodeUser, nodeImpersonator, getPodRequest, + "\x00\x00\x00!\x00\x00\x00\x11system:node:node1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11node-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): nodeUserAuthenticated, }, inner: map[innerKey]*user.DefaultInfo{ - {wantedUser: nodeUser, requestor: nodeImpersonator}: nodeUserAuthenticated, + { + wantedUser: nodeUser, + requestor: nodeImpersonator, + rawKey: "\x00\x00\x00!\x00\x00\x00\x11system:node:node1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11node-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: nodeUserAuthenticated, }, }, }, }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "arbitrary-node", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "arbitrary-node/allowed": 2, // impersonate-on + impersonate:arbitrary-node + }, }, }, }, @@ -796,16 +1116,23 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Groups: []string{"extra-setter-scopes"}, Extra: map[string][]string{"pandas.io/scopes": {"scope-a", "scope-b"}}, }, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "extra-setter-scopes"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "scope-a"}, "user-info"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "extra-setter-scopes"}, "user-info")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "scope-a"}, "user-info")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"})), + }, + expectedCache: nil, + expectedCode: http.StatusForbidden, + expectedMessage: `userextras.authentication.k8s.io "scope-a" is forbidden: User "user-impersonater" cannot impersonate:user-info resource "userextras/pandas.io/scopes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 3, // impersonate-on:get + users + groups + "user-info/denied": 1, // userextras scope-a + "legacy/denied": 1, // impersonate users }, - expectedCache: nil, - expectedCode: http.StatusForbidden, - expectedMessage: `userextras.authentication.k8s.io "scope-a" is forbidden: User "user-impersonater" cannot impersonate:user-info resource "userextras/pandas.io/scopes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, }, }, }, @@ -827,17 +1154,17 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Groups: []string{"system:authenticated"}, Extra: map[string][]string{"pandas.io/scopes": {"scope-a", "scope-b", "5", "4", "3", "2", "1"}}, }, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "*", Name: "*"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "scope-a"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "scope-b"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "5"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "4"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "3"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "2"}, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "1"}, "user-info"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}, "user-info")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "*", Name: "*"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "scope-a"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "scope-b"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "5"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "4"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "3"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "2"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "pandas.io/scopes", Name: "1"}, "user-info")), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -845,7 +1172,7 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:user-info": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ + outer: map[outerKey]*user.DefaultInfo{ outerCacheKey( &user.DefaultInfo{ Name: "system:admin", @@ -855,7 +1182,7 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Name: "user-impersonater", Groups: []string{"extra-setter-scopes"}, }, - getPodRequest): { + getPodRequest, "\x00\x00\x00c\x00\x00\x00\fsystem:admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00G\x00\x00\x00\x10pandas.io/scopes\x00\x00\x00/\x00\x00\x00\ascope-a\x00\x00\x00\ascope-b\x00\x00\x00\x015\x00\x00\x00\x014\x00\x00\x00\x013\x00\x00\x00\x012\x00\x00\x00\x011\x00\x00\x008\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x13extra-setter-scopes\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"): { Name: "system:admin", Groups: []string{"system:authenticated"}, Extra: map[string][]string{"pandas.io/scopes": {"scope-a", "scope-b", "5", "4", "3", "2", "1"}}, @@ -871,6 +1198,7 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Name: "user-impersonater", Groups: []string{"extra-setter-scopes"}, }, + rawKey: "\x00\x00\x00c\x00\x00\x00\fsystem:admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00G\x00\x00\x00\x10pandas.io/scopes\x00\x00\x00/\x00\x00\x00\ascope-a\x00\x00\x00\ascope-b\x00\x00\x00\x015\x00\x00\x00\x014\x00\x00\x00\x013\x00\x00\x00\x012\x00\x00\x00\x011\x00\x00\x008\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x13extra-setter-scopes\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", }: { Name: "system:admin", Groups: []string{"system:authenticated"}, @@ -880,7 +1208,13 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, }, }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 9, // impersonate-on:get + users + 7 individual extra values + "user-info/denied": 1, // wildcard userextras check + }, }, }, }, @@ -901,14 +1235,21 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, }, impersonatedUser: &user.DefaultInfo{Name: "system:node:node1"}, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "arbitrary-node"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"}), - }, - expectedCache: nil, - expectedCode: http.StatusForbidden, - expectedMessage: `nodes.authentication.k8s.io "node1" is forbidden: User "user-impersonater" cannot impersonate:arbitrary-node resource "nodes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "arbitrary-node")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "nodes", Name: "node1"}, "arbitrary-node")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:node:node1"})), + }, + expectedCache: nil, + expectedCode: http.StatusForbidden, + expectedMessage: `nodes.authentication.k8s.io "node1" is forbidden: User "user-impersonater" cannot impersonate:arbitrary-node resource "nodes" in API group "authentication.k8s.io" at the cluster scope: deny by default`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "arbitrary-node/allowed": 1, // impersonate-on:arbitrary-node:get + "arbitrary-node/denied": 1, // impersonate:arbitrary-node nodes + "legacy/denied": 1, // impersonate users + }, }, }, }, @@ -923,10 +1264,10 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Name: "system:admin", Groups: []string{"system:authenticated"}, }, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getPodRequest, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}, "user-info"), - withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"}, "user-info")), + expectAllow(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "system:admin"})), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -934,7 +1275,294 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: nil, // legacy impersonation does not cache }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "legacy", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 1, // impersonate-on:user-info:get + "user-info/denied": 1, // impersonate:user-info users + "legacy/allowed": 1, // impersonate users + }, + }, + }, + }, + { + name: "system:masters-group-not-allowed", + requests: []testRequest{ + { + request: getPodRequest, + requestor: userImpersonator, + impersonatedUser: &user.DefaultInfo{ + Name: "admin-user", + Groups: []string{user.SystemPrivilegedGroup}, + }, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "admin-user"}, "user-info")), + expectDeny(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "admin-user"})), + }, + expectedCache: nil, + expectedCode: http.StatusForbidden, + expectedMessage: `groups.authentication.k8s.io "system:masters" is forbidden: User "user-impersonater" cannot impersonate:user-info resource "groups" in API group "authentication.k8s.io" at the cluster scope: impersonating the system:masters group is not allowed`, + expectedAttemptMode: "", + expectedAttemptDecision: "denied", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 2, // impersonate-on:user-info:get, impersonate:user-info users + "legacy/denied": 1, // impersonate users + }, + }, + }, + }, + { + name: "many-groups-wildcard-allowed", + requests: []testRequest{ + { + request: getPodRequest, + requestor: &user.DefaultInfo{ + Name: "many-groups-impersonater", + }, + impersonatedUser: &user.DefaultInfo{ + Name: "bob", + Groups: []string{"group1", "group2", "group3", "group4"}, + }, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "bob", + Groups: []string{"group1", "group2", "group3", "group4", "system:authenticated"}, + }, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "bob"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "*"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + "many-groups-impersonater": "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{ + Name: "bob", + Groups: []string{"group1", "group2", "group3", "group4"}, + }, &user.DefaultInfo{Name: "many-groups-impersonater"}, getPodRequest, + "\x00\x00\x00;\x00\x00\x00\x03bob\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x06group1\x00\x00\x00\x06group2\x00\x00\x00\x06group3\x00\x00\x00\x06group4\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x18many-groups-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): { + Name: "bob", + Groups: []string{"group1", "group2", "group3", "group4", "system:authenticated"}, + }, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: &user.DefaultInfo{ + Name: "bob", + Groups: []string{"group1", "group2", "group3", "group4"}, + }, + requestor: &user.DefaultInfo{Name: "many-groups-impersonater"}, + rawKey: "\x00\x00\x00;\x00\x00\x00\x03bob\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x06group1\x00\x00\x00\x06group2\x00\x00\x00\x06group3\x00\x00\x00\x06group4\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x18many-groups-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: { + Name: "bob", + Groups: []string{"group1", "group2", "group3", "group4", "system:authenticated"}, + }, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 3, // impersonate-on:user-info:get, impersonate:user-info users/groups + }, + }, + }, + }, + { + name: "many-extras-wildcard-allowed", + requests: []testRequest{ + { + request: getPodRequest, + requestor: &user.DefaultInfo{ + Name: "many-extras-impersonater", + }, + impersonatedUser: &user.DefaultInfo{ + Name: "alice", + Extra: map[string][]string{ + "scopes.example.com/key1": {"val1"}, + "scopes.example.com/key2": {"val2"}, + "scopes.example.com/key3": {"val3"}, + "scopes.example.com/key4": {"val4"}, + }, + }, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "alice", + Groups: []string{"system:authenticated"}, + Extra: map[string][]string{ + "scopes.example.com/key1": {"val1"}, + "scopes.example.com/key2": {"val2"}, + "scopes.example.com/key3": {"val3"}, + "scopes.example.com/key4": {"val4"}, + }, + }, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "alice"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "*", Name: "*"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + "many-extras-impersonater": "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{ + Name: "alice", + Extra: map[string][]string{ + "scopes.example.com/key1": {"val1"}, + "scopes.example.com/key2": {"val2"}, + "scopes.example.com/key3": {"val3"}, + "scopes.example.com/key4": {"val4"}, + }, + }, &user.DefaultInfo{Name: "many-extras-impersonater"}, getPodRequest, + "\x00\x00\x00\xb1\x00\x00\x00\x05alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9c\x00\x00\x00\x17scopes.example.com/key1\x00\x00\x00\b\x00\x00\x00\x04val1\x00\x00\x00\x17scopes.example.com/key2\x00\x00\x00\b\x00\x00\x00\x04val2\x00\x00\x00\x17scopes.example.com/key3\x00\x00\x00\b\x00\x00\x00\x04val3\x00\x00\x00\x17scopes.example.com/key4\x00\x00\x00\b\x00\x00\x00\x04val4\x00\x00\x00(\x00\x00\x00\x18many-extras-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): { + Name: "alice", + Groups: []string{"system:authenticated"}, + Extra: map[string][]string{ + "scopes.example.com/key1": {"val1"}, + "scopes.example.com/key2": {"val2"}, + "scopes.example.com/key3": {"val3"}, + "scopes.example.com/key4": {"val4"}, + }, + }, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: &user.DefaultInfo{ + Name: "alice", + Extra: map[string][]string{ + "scopes.example.com/key1": {"val1"}, + "scopes.example.com/key2": {"val2"}, + "scopes.example.com/key3": {"val3"}, + "scopes.example.com/key4": {"val4"}, + }, + }, + requestor: &user.DefaultInfo{Name: "many-extras-impersonater"}, + rawKey: "\x00\x00\x00\xb1\x00\x00\x00\x05alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9c\x00\x00\x00\x17scopes.example.com/key1\x00\x00\x00\b\x00\x00\x00\x04val1\x00\x00\x00\x17scopes.example.com/key2\x00\x00\x00\b\x00\x00\x00\x04val2\x00\x00\x00\x17scopes.example.com/key3\x00\x00\x00\b\x00\x00\x00\x04val3\x00\x00\x00\x17scopes.example.com/key4\x00\x00\x00\b\x00\x00\x00\x04val4\x00\x00\x00(\x00\x00\x00\x18many-extras-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: { + Name: "alice", + Groups: []string{"system:authenticated"}, + Extra: map[string][]string{ + "scopes.example.com/key1": {"val1"}, + "scopes.example.com/key2": {"val2"}, + "scopes.example.com/key3": {"val3"}, + "scopes.example.com/key4": {"val4"}, + }, + }, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 3, // impersonate-on:user-info:get, impersonate:user-info users/extras + }, + }, + }, + }, + { + name: "mixed-groups-extras-wildcard-denied-individuals-allowed", + requests: []testRequest{ + { + request: getPodRequest, + requestor: &user.DefaultInfo{ + Name: "mixed-impersonater", + }, + impersonatedUser: &user.DefaultInfo{ + Name: "frank", + Groups: []string{"dev", "ops", "security", "audit"}, + Extra: map[string][]string{ + "tags.io/env": {"prod", "staging", "dev"}, + }, + }, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "frank", + Groups: []string{"dev", "ops", "security", "audit", "system:authenticated"}, + Extra: map[string][]string{ + "tags.io/env": {"prod", "staging", "dev"}, + }, + }, + expectedAuthzChecks: []authzCheck{ + // impersonate-on check passes + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + // user check passes + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "frank"}, "user-info")), + // wildcard groups check FAILS (mixed-impersonater doesn't allow wildcard groups) + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "*"}, "user-info")), + // individual group checks PASS - demonstrating fallback after wildcard failure + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "dev"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "ops"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "security"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "groups", Name: "audit"}, "user-info")), + // individual extra checks PASS (wildcard not attempted for extras with single subresource) + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "tags.io/env", Name: "prod"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "tags.io/env", Name: "staging"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "userextras", Subresource: "tags.io/env", Name: "dev"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + "mixed-impersonater": "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{ + Name: "frank", + Groups: []string{"dev", "ops", "security", "audit"}, + Extra: map[string][]string{ + "tags.io/env": {"prod", "staging", "dev"}, + }, + }, &user.DefaultInfo{Name: "mixed-impersonater"}, getPodRequest, + "\x00\x00\x00e\x00\x00\x00\x05frank\x00\x00\x00\x00\x00\x00\x00#\x00\x00\x00\x03dev\x00\x00\x00\x03ops\x00\x00\x00\bsecurity\x00\x00\x00\x05audit\x00\x00\x00-\x00\x00\x00\vtags.io/env\x00\x00\x00\x1a\x00\x00\x00\x04prod\x00\x00\x00\astaging\x00\x00\x00\x03dev\x00\x00\x00\"\x00\x00\x00\x12mixed-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): { + Name: "frank", + Groups: []string{"dev", "ops", "security", "audit", "system:authenticated"}, + Extra: map[string][]string{ + "tags.io/env": {"prod", "staging", "dev"}, + }, + }, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: &user.DefaultInfo{ + Name: "frank", + Groups: []string{"dev", "ops", "security", "audit"}, + Extra: map[string][]string{ + "tags.io/env": {"prod", "staging", "dev"}, + }, + }, + requestor: &user.DefaultInfo{Name: "mixed-impersonater"}, + rawKey: "\x00\x00\x00e\x00\x00\x00\x05frank\x00\x00\x00\x00\x00\x00\x00#\x00\x00\x00\x03dev\x00\x00\x00\x03ops\x00\x00\x00\bsecurity\x00\x00\x00\x05audit\x00\x00\x00-\x00\x00\x00\vtags.io/env\x00\x00\x00\x1a\x00\x00\x00\x04prod\x00\x00\x00\astaging\x00\x00\x00\x03dev\x00\x00\x00\"\x00\x00\x00\x12mixed-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: { + Name: "frank", + Groups: []string{"dev", "ops", "security", "audit", "system:authenticated"}, + Extra: map[string][]string{ + "tags.io/env": {"prod", "staging", "dev"}, + }, + }, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 9, // impersonate-on:user-info:get; impersonate:user-info users; 4*impersonate:user-info groups; 3*impersonate:user-info extras + "user-info/denied": 1, // impersonate:user-info groups "*" + }, }, }, }, @@ -949,9 +1577,9 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Name: "bob", Groups: []string{"system:authenticated"}, }, - expectedAttributes: []authorizer.AttributesRecord{ - withImpersonateOnAttributes(getDeploymentRequest, "user-info"), - withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "bob"}, "user-info"), + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getDeploymentRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "bob"}, "user-info")), }, expectedCache: &expectedCache{ modeIdx: map[string]string{ @@ -959,14 +1587,20 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, modes: map[string]expectedModeCache{ "impersonate:user-info": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(&user.DefaultInfo{Name: "bob"}, userImpersonator, getDeploymentRequest): { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{Name: "bob"}, userImpersonator, getDeploymentRequest, + "\x00\x00\x00\x13\x00\x00\x00\x03bob\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\vdeployments\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x04apps\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): { Name: "bob", Groups: []string{"system:authenticated"}, }, }, inner: map[innerKey]*user.DefaultInfo{ - {wantedUser: &user.DefaultInfo{Name: "bob"}, requestor: userImpersonator}: { + { + wantedUser: &user.DefaultInfo{Name: "bob"}, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x13\x00\x00\x00\x03bob\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: { Name: "bob", Groups: []string{"system:authenticated"}, }, @@ -974,7 +1608,12 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, }, }, - expectedCode: http.StatusOK, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 2, // impersonate-on + impersonate:user-info + }, }, { request: getDeploymentRequest, @@ -984,21 +1623,28 @@ func TestConstrainedImpersonationFilter(t *testing.T) { Name: "bob", Groups: []string{"system:authenticated"}, }, - expectedCode: http.StatusOK, + expectedAuthzChecks: nil, // no authz checks for cached request + expectedCode: http.StatusOK, expectedCache: &expectedCache{ modeIdx: map[string]string{ userImpersonator.Name: "impersonate:user-info", }, modes: map[string]expectedModeCache{ "impersonate:user-info": { - outer: map[impersonationCacheKey]*user.DefaultInfo{ - outerCacheKey(&user.DefaultInfo{Name: "bob"}, userImpersonator, getDeploymentRequest): { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(&user.DefaultInfo{Name: "bob"}, userImpersonator, getDeploymentRequest, + "\x00\x00\x00\x13\x00\x00\x00\x03bob\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\vdeployments\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x04apps\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): { Name: "bob", Groups: []string{"system:authenticated"}, }, }, inner: map[innerKey]*user.DefaultInfo{ - {wantedUser: &user.DefaultInfo{Name: "bob"}, requestor: userImpersonator}: { + { + wantedUser: &user.DefaultInfo{Name: "bob"}, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x13\x00\x00\x00\x03bob\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: { Name: "bob", Groups: []string{"system:authenticated"}, }, @@ -1006,19 +1652,329 @@ func TestConstrainedImpersonationFilter(t *testing.T) { }, }, }, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: nil, // full cache hit }, }, }, - } - - var mux http.ServeMux - tests := make([]*constrainedImpersonationTest, len(testCases)) - handlers := make([]http.Handler, len(testCases)) - for i := range len(testCases) { - mux.Handle("/"+strconv.Itoa(i), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - handlers[i].ServeHTTP(w, req) - })) - } + { + name: "impersonating-user-with-uid", + requests: []testRequest{ + { + request: getPodRequest, + requestor: &user.DefaultInfo{Name: "user-impersonater", UID: "requestor-uid-123"}, + impersonatedUser: &user.DefaultInfo{ + Name: "anyone", + UID: "wanted-uid-456", + }, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "anyone", + UID: "wanted-uid-456", + Groups: []string{"system:authenticated"}, + }, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "uids", Name: "wanted-uid-456"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + "user-impersonater": "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey( + &user.DefaultInfo{Name: "anyone", UID: "wanted-uid-456"}, + &user.DefaultInfo{Name: "user-impersonater", UID: "requestor-uid-123"}, + getPodRequest, + "\x00\x00\x00$\x00\x00\x00\x06anyone\x00\x00\x00\x0ewanted-uid-456\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x11user-impersonater\x00\x00\x00\x11requestor-uid-123\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): { + Name: "anyone", + UID: "wanted-uid-456", + Groups: []string{"system:authenticated"}, + }, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: &user.DefaultInfo{Name: "anyone", UID: "wanted-uid-456"}, + requestor: &user.DefaultInfo{Name: "user-impersonater", UID: "requestor-uid-123"}, + rawKey: "\x00\x00\x00$\x00\x00\x00\x06anyone\x00\x00\x00\x0ewanted-uid-456\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x11user-impersonater\x00\x00\x00\x11requestor-uid-123\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: { + Name: "anyone", + UID: "wanted-uid-456", + Groups: []string{"system:authenticated"}, + }, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 3, // impersonate-on + impersonate:user-info users + uids + }, + }, + }, + }, + { + name: "impersonating-user-subresource-request", + requests: []testRequest{ + { + request: &request.RequestInfo{ + IsResourceRequest: true, + Verb: "get", + APIVersion: "v1", + Resource: "pods", + Subresource: "status", + Name: "foo", + Namespace: "bar", + }, + requestor: userImpersonator, + impersonatedUser: anyone, + expectedImpersonatedUser: anyoneAuthenticated, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(&request.RequestInfo{ + IsResourceRequest: true, + Verb: "get", + APIVersion: "v1", + Resource: "pods", + Subresource: "status", + Name: "foo", + Namespace: "bar", + }, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + userImpersonator.Name: "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(anyone, userImpersonator, &request.RequestInfo{ + IsResourceRequest: true, + Verb: "get", + APIVersion: "v1", + Resource: "pods", + Subresource: "status", + Name: "foo", + Namespace: "bar", + }, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x007\x00\x00\x00\x03get\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x06status\x00\x00\x00\x03foo\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: anyone, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: anyoneAuthenticated, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 2, // impersonate-on + impersonate:user-info + }, + }, + }, + }, + { + name: "impersonating-user-non-resource-request", + requests: []testRequest{ + { + request: &request.RequestInfo{ + IsResourceRequest: false, + Verb: "get", + Path: "/healthz", + }, + requestor: userImpersonator, + impersonatedUser: anyone, + expectedImpersonatedUser: anyoneAuthenticated, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(&request.RequestInfo{ + IsResourceRequest: false, + Verb: "get", + Path: "/healthz", + }, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + userImpersonator.Name: "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(anyone, userImpersonator, &request.RequestInfo{ + IsResourceRequest: false, + Verb: "get", + Path: "/healthz", + }, + "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00\x00\x00\x03get\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\b/healthz\x00\x00\x00\x00\x00\x00\x00\x00", + ): anyoneAuthenticated, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: anyone, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: anyoneAuthenticated, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 2, // impersonate-on + impersonate:user-info + }, + }, + }, + }, + { + name: "impersonating-user-with-field-and-label-selectors", + requests: []testRequest{ + { + request: &request.RequestInfo{ + IsResourceRequest: true, + Verb: "list", + APIVersion: "v1", + Resource: "pods", + Namespace: "bar", + FieldSelector: "spec.nodeName=node1", + LabelSelector: "app=web", + }, + requestor: userImpersonator, + impersonatedUser: anyone, + expectedImpersonatedUser: anyoneAuthenticated, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(&request.RequestInfo{ + IsResourceRequest: true, + Verb: "list", + APIVersion: "v1", + Resource: "pods", + Namespace: "bar", + FieldSelector: "spec.nodeName=node1", + LabelSelector: "app=web", + }, "user-info")), + expectAllow(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + userImpersonator.Name: "impersonate:user-info", + }, + modes: map[string]expectedModeCache{ + "impersonate:user-info": { + outer: map[outerKey]*user.DefaultInfo{ + outerCacheKey(anyone, userImpersonator, &request.RequestInfo{ + IsResourceRequest: true, + Verb: "list", + APIVersion: "v1", + Resource: "pods", + Namespace: "bar", + FieldSelector: "spec.nodeName=node1", + LabelSelector: "app=web", + }, "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00\x00\x00\x04list\x01\x00\x00\x00\x03bar\x00\x00\x00\x04pods\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02v1\x01\x00\x00\x00\x00\x00\x00\x00#\x00\x00\x00\x1f\x00\x00\x00\rspec.nodeName\x00\x00\x00\x01=\x00\x00\x00\x05node1\x00\x00\x00\v\x00\x00\x00\aapp=web"): anyoneAuthenticated, + }, + inner: map[innerKey]*user.DefaultInfo{ + { + wantedUser: anyone, + requestor: userImpersonator, + rawKey: "\x00\x00\x00\x16\x00\x00\x00\x06anyone\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x11user-impersonater\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + }: anyoneAuthenticated, + }, + }, + }, + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "user-info", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 2, // impersonate-on + impersonate:user-info + }, + }, + }, + }, + { + name: "legacy-fallback", + requests: []testRequest{ + { + request: getPodRequest, + requestor: &user.DefaultInfo{Name: "legacy-impersonater"}, + impersonatedUser: anyone, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "anyone", + Groups: []string{"system:authenticated"}, + }, + expectedAuthzChecks: []authzCheck{ + expectAllow(withImpersonateOnAttributes(getPodRequest, "user-info")), + expectDeny(withConstrainedImpersonationAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"}, "user-info")), + expectAllow(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"})), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + "legacy-impersonater": "impersonate", + }, + modes: nil, // legacy impersonation does not cache + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "legacy", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "user-info/allowed": 1, // impersonate-on:user-info:get + "user-info/denied": 1, // impersonate:user-info users + "legacy/allowed": 1, // impersonate users + }, + }, + { + request: getDeploymentRequest, + requestor: &user.DefaultInfo{Name: "legacy-impersonater"}, + impersonatedUser: anyone, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "anyone", + Groups: []string{"system:authenticated"}, + }, + expectedAuthzChecks: []authzCheck{ + expectAllow(withLegacyImpersonateAttributes(authorizer.AttributesRecord{Resource: "users", Name: "anyone"})), + }, + expectedCache: &expectedCache{ + modeIdx: map[string]string{ + "legacy-impersonater": "impersonate", + }, + modes: nil, // legacy impersonation does not cache + }, + expectedCode: http.StatusOK, + expectedAttemptMode: "legacy", + expectedAttemptDecision: "allowed", + expectedAuthorizationMetrics: map[string]int{ + "legacy/allowed": 1, // impersonate users (mode index cache hit skips constrained checks) + }, + }, + }, + }, + } + return testCases +} + +func TestConstrainedImpersonationFilter(t *testing.T) { + testCases := constrainedImpersonationTestCases() + + var mux http.ServeMux + tests := make([]*constrainedImpersonationTest, len(testCases)) + handlers := make([]http.Handler, len(testCases)) + for i := range len(testCases) { + mux.Handle("/"+strconv.Itoa(i), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + handlers[i].ServeHTTP(w, req) + })) + } server := httptest.NewServer(&mux) t.Cleanup(server.Close) @@ -1032,28 +1988,7 @@ func TestConstrainedImpersonationFilter(t *testing.T) { handlers[i] = test.handler() for _, r := range tc.requests { - client := &http.Client{ - Transport: &testRoundTripper{ - user: r.requestor, - requestInfo: r.request, - delegate: transport.NewImpersonatingRoundTripper( - transport.ImpersonationConfig{ - UserName: r.impersonatedUser.Name, - UID: r.impersonatedUser.UID, - Groups: r.impersonatedUser.Groups, - Extra: r.impersonatedUser.Extra, - }, - http.DefaultTransport, - ), - }, - } - - req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, server.URL+"/"+strconv.Itoa(i), nil) - require.NoError(t, err) - - resp, err := client.Do(req) - require.NoError(t, err) - require.Equal(t, r.expectedCode, resp.StatusCode) + resp := doImpersonationRequest(t, http.DefaultTransport, server.URL+"/"+strconv.Itoa(i), r, r.expectedCode) body, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -1082,7 +2017,390 @@ func TestConstrainedImpersonationFilter(t *testing.T) { test.assertAttributes(r) test.assertCache(r) + test.assertMetrics(r) + test.assertAuditEvents(r) + } + }) + } +} + +func TestConstrainedImpersonationParallel(t *testing.T) { + // This test verifies goroutine safety by sending concurrent requests. + // Each test case defines multiple requests that are sent in parallel, + // ensuring no data races or request interference occurs. + + // Common request infos + getPods := &request.RequestInfo{IsResourceRequest: true, Verb: "get", APIVersion: "v1", Resource: "pods"} + listServices := &request.RequestInfo{IsResourceRequest: true, Verb: "list", APIVersion: "v1", Resource: "services"} + createConfigMaps := &request.RequestInfo{IsResourceRequest: true, Verb: "create", APIVersion: "v1", Resource: "configmaps"} + createSecrets := &request.RequestInfo{IsResourceRequest: true, Verb: "create", APIVersion: "v1", Resource: "secrets"} + + type parallelRequest struct { + requestor *user.DefaultInfo + impersonatedUser string + requestInfo *request.RequestInfo + expectedStatusCode int + expectedImpersonatedUser *user.DefaultInfo + } + + testCases := []struct { + name string + requests []parallelRequest + }{ + { + name: "different-users-same-target", + requests: []parallelRequest{ + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:my-sa", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "node-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: getPods, + expectedStatusCode: http.StatusForbidden, + }, + { + requestor: &user.DefaultInfo{Name: "user-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: getPods, + expectedStatusCode: http.StatusForbidden, + }, + }, + }, + { + name: "same-user-same-targets", + requests: []parallelRequest{ + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:my-sa", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:my-sa", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + }, + }, + { + name: "same-user-different-targets", + requests: []parallelRequest{ + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:sa1", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:sa1", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:kube-system:sa2", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:kube-system:sa2", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:kube-system", "system:authenticated"}, + Extra: map[string][]string{}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:node:node-1", + requestInfo: getPods, + expectedStatusCode: http.StatusForbidden, + }, + }, + }, + { + name: "same-user-same-target-different-operations", + requests: []parallelRequest{ + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:my-sa", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: listServices, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:my-sa", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: createConfigMaps, + expectedStatusCode: http.StatusForbidden, + }, + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:my-sa", + requestInfo: createSecrets, + expectedStatusCode: http.StatusForbidden, + }, + }, + }, + { + name: "mixed-scenarios", + requests: []parallelRequest{ + { + requestor: &user.DefaultInfo{Name: "sa-impersonater"}, + impersonatedUser: "system:serviceaccount:default:sa1", + requestInfo: getPods, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:serviceaccount:default:sa1", + Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default", "system:authenticated"}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "node-impersonater"}, + impersonatedUser: "system:node:node-1", + requestInfo: listServices, + expectedStatusCode: http.StatusOK, + expectedImpersonatedUser: &user.DefaultInfo{ + Name: "system:node:node-1", + Groups: []string{"system:nodes", "system:authenticated"}, + Extra: map[string][]string{}, + }, + }, + { + requestor: &user.DefaultInfo{Name: "user-impersonater"}, + impersonatedUser: "alice", + requestInfo: createConfigMaps, + expectedStatusCode: http.StatusForbidden, + }, + { + requestor: &user.DefaultInfo{Name: "unauthorized-user"}, + impersonatedUser: "bob", + requestInfo: getPods, + expectedStatusCode: http.StatusForbidden, + }, + }, + }, + } + + type resultType struct { + requestIdx int + statusCode int + body []byte + err error + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + test := &constrainedImpersonationTest{t: t} + server := httptest.NewServer(test.parallelHandler()) + defer server.Close() + + var wg sync.WaitGroup + results := make(chan resultType, len(tc.requests)) + + // Send all requests concurrently + for idx, req := range tc.requests { + wg.Go(func() { + rt := &testRoundTripper{ + user: req.requestor, + requestInfo: req.requestInfo, + delegate: http.DefaultTransport, + } + client := &http.Client{Transport: rt} + + httpReq, err := http.NewRequest(http.MethodGet, server.URL, nil) + if err != nil { + results <- resultType{requestIdx: idx, err: err} + return + } + + if len(req.impersonatedUser) > 0 { + httpReq.Header.Add(authenticationv1.ImpersonateUserHeader, req.impersonatedUser) + } + + resp, err := client.Do(httpReq) + if err != nil { + results <- resultType{requestIdx: idx, err: err} + return + } + defer func() { + err := resp.Body.Close() + if err != nil { + results <- resultType{requestIdx: idx, err: err} + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + results <- resultType{requestIdx: idx, err: err} + return + } + + results <- resultType{ + requestIdx: idx, + statusCode: resp.StatusCode, + body: body, + } + }) + } + + // Wait for all requests to complete + wg.Wait() + close(results) + + // Collect results + resultMap := make(map[int]resultType) + for res := range results { + require.NoError(t, res.err, "request %d: unexpected error", res.requestIdx) + _, ok := resultMap[res.requestIdx] + require.False(t, ok, "request %d: should not duplicate in the result", res.requestIdx) + resultMap[res.requestIdx] = res + } + + // Verify each request's result + for idx, req := range tc.requests { + res, ok := resultMap[idx] + require.True(t, ok, "request %d: missing result", idx) + + require.Equal(t, req.expectedStatusCode, res.statusCode, + "request %d: status code mismatch (requestor: %s, target: %s, verb: %s, resource: %s)", + idx, req.requestor.Name, req.impersonatedUser, req.requestInfo.Verb, req.requestInfo.Resource) + + if req.expectedImpersonatedUser != nil { + // Success case - verify impersonated user + var actualUser user.DefaultInfo + err := json.Unmarshal(res.body, &actualUser) + require.NoError(t, err, "request %d: failed to unmarshal user", idx) + require.Equal(t, req.expectedImpersonatedUser.Name, actualUser.Name, + "request %d: user name mismatch", idx) + require.ElementsMatch(t, req.expectedImpersonatedUser.Groups, actualUser.Groups, + "request %d: groups mismatch", idx) + } } }) } } + +func (c *constrainedImpersonationTest) benchmarkHandler(withImpersonation func(http.Handler, authorizer.Authorizer, runtime.NegotiatedSerializer) http.Handler) http.Handler { + s := runtime.NewScheme() + metav1.AddToGroupVersion(s, metav1.SchemeGroupVersion) + addImpersonation := withImpersonation(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}), c, serializer.NewCodecFactory(s)) + h := addImpersonation.(*constrainedImpersonationHandler) + h.recordAttempt = func(ctx context.Context, mode, decision string, _ time.Duration) {} + h.metricsAuthorizer.recordAuthorizationCall = func(mode, decision string, _ time.Duration) {} + addAuthentication := c.authenticationHandler(addImpersonation) + addRequestInfo := c.requestInfoHandler(addAuthentication) + return addRequestInfo +} + +func BenchmarkImpersonation(b *testing.B) { + testCases := constrainedImpersonationTestCases() + + type impersonationHandler struct { + name string + fn func(http.Handler, authorizer.Authorizer, runtime.NegotiatedSerializer) http.Handler + isLegacy bool + } + + impersonators := []impersonationHandler{ + { + name: "constrained", + fn: WithConstrainedImpersonation, + }, + { + name: "legacy", + fn: WithImpersonation, + isLegacy: true, + }, + } + + for _, imp := range impersonators { + b.Run(imp.name, func(b *testing.B) { + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + test := &constrainedImpersonationTest{t: b} + handler := test.benchmarkHandler(imp.fn) + + server := httptest.NewServer(handler) + defer server.Close() + + baseTransport := &http.Transport{ + DisableKeepAlives: true, + } + + b.ResetTimer() + for b.Loop() { + for _, r := range tc.requests { + resp := doImpersonationRequest(b, baseTransport, server.URL, r, benchmarkExpectedCode(r, imp.isLegacy)) + _ = resp.Body.Close() + } + } + b.StopTimer() + }) + } + }) + } +} + +func benchmarkExpectedCode(r testRequest, isLegacy bool) int { + if isLegacy && r.expectedCode == http.StatusOK && r.requestor.Name != "legacy-impersonater" { + // legacy WithImpersonation denies any requestor that only has constrained impersonation verbs + return http.StatusForbidden + } + return r.expectedCode +} + +func doImpersonationRequest(tb testing.TB, baseTransport http.RoundTripper, url string, r testRequest, expectedCode int) *http.Response { + tb.Helper() + + client := &http.Client{ + Transport: &testRoundTripper{ + user: r.requestor, + requestInfo: r.request, + delegate: transport.NewImpersonatingRoundTripper( + transport.ImpersonationConfig{ + UserName: r.impersonatedUser.Name, + UID: r.impersonatedUser.UID, + Groups: r.impersonatedUser.Groups, + Extra: r.impersonatedUser.Extra, + }, + baseTransport, + ), + }, + } + + req, err := http.NewRequest(http.MethodGet, url, nil) + require.NoError(tb, err) + + resp, err := client.Do(req) + require.NoError(tb, err) + require.Equal(tb, expectedCode, resp.StatusCode) + + return resp +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics/metrics.go new file mode 100644 index 0000000000000..ac47de82a0f74 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics/metrics.go @@ -0,0 +1,99 @@ +/* +Copyright 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 metrics + +import ( + "sync" + "time" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const ( + namespace = "apiserver" + subsystem = "impersonation" +) + +var ( + impersonationAttemptsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "attempts_total", + Help: "Total number of impersonation attempts split by mode and decision.", + StabilityLevel: metrics.ALPHA, + }, + []string{"mode", "decision"}, + ) + + impersonationAttemptsDurationSeconds = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "attempts_duration_seconds", + Help: "Latency of impersonation attempts in seconds split by mode and decision.", + StabilityLevel: metrics.ALPHA, + Buckets: metrics.ExponentialBuckets(0.001, 2, 15), + }, + []string{"mode", "decision"}, + ) + + impersonationAuthorizationAttemptsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "authorization_attempts_total", + Help: "Total number of authorization checks made by the impersonation handler split by mode and decision.", + StabilityLevel: metrics.ALPHA, + }, + []string{"mode", "decision"}, + ) + + impersonationAuthorizationAttemptsDurationSeconds = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "authorization_attempts_duration_seconds", + Help: "Latency of authorization checks made by the impersonation handler in seconds split by mode and decision.", + StabilityLevel: metrics.ALPHA, + Buckets: metrics.ExponentialBuckets(0.001, 2, 15), + }, + []string{"mode", "decision"}, + ) +) + +var registerMetrics sync.Once + +func RegisterMetrics() { + registerMetrics.Do(func() { + legacyregistry.MustRegister(impersonationAttemptsTotal) + legacyregistry.MustRegister(impersonationAttemptsDurationSeconds) + legacyregistry.MustRegister(impersonationAuthorizationAttemptsTotal) + legacyregistry.MustRegister(impersonationAuthorizationAttemptsDurationSeconds) + }) +} + +func RecordImpersonationAttempt(mode, decision string, duration time.Duration) { + impersonationAttemptsTotal.WithLabelValues(mode, decision).Inc() + impersonationAttemptsDurationSeconds.WithLabelValues(mode, decision).Observe(duration.Seconds()) +} + +func RecordImpersonationAuthorizationCall(mode, decision string, duration time.Duration) { + impersonationAuthorizationAttemptsTotal.WithLabelValues(mode, decision).Inc() + impersonationAuthorizationAttemptsDurationSeconds.WithLabelValues(mode, decision).Observe(duration.Seconds()) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics/metrics_test.go new file mode 100644 index 0000000000000..6eb4d0c960e60 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/metrics/metrics_test.go @@ -0,0 +1,401 @@ +/* +Copyright 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 metrics + +import ( + "strings" + "testing" + "time" + + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" +) + +func TestRecordImpersonationAttempt(t *testing.T) { + RegisterMetrics() + + attemptMetrics := []string{ + namespace + "_" + subsystem + "_attempts_total", + namespace + "_" + subsystem + "_attempts_duration_seconds", + } + + testCases := []struct { + name string + mode string + decision string + expectedValue string + }{ + { + name: "allowed with user-info mode", + mode: "user-info", + decision: "allowed", + expectedValue: ` + # HELP apiserver_impersonation_attempts_total [ALPHA] Total number of impersonation attempts split by mode and decision. + # TYPE apiserver_impersonation_attempts_total counter + apiserver_impersonation_attempts_total{decision="allowed",mode="user-info"} 1 + # HELP apiserver_impersonation_attempts_duration_seconds [ALPHA] Latency of impersonation attempts in seconds split by mode and decision. + # TYPE apiserver_impersonation_attempts_duration_seconds histogram + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.001"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.002"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.004"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.008"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.016"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.032"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.064"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.128"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.256"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.512"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="1.024"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="2.048"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="4.096"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="8.192"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="16.384"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="+Inf"} 1 + apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="user-info"} 0.1 + apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="user-info"} 1 + `, + }, + { + name: "denied attempt", + mode: "", + decision: "denied", + expectedValue: ` + # HELP apiserver_impersonation_attempts_total [ALPHA] Total number of impersonation attempts split by mode and decision. + # TYPE apiserver_impersonation_attempts_total counter + apiserver_impersonation_attempts_total{decision="denied",mode=""} 1 + # HELP apiserver_impersonation_attempts_duration_seconds [ALPHA] Latency of impersonation attempts in seconds split by mode and decision. + # TYPE apiserver_impersonation_attempts_duration_seconds histogram + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.001"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.002"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.004"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.008"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.016"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.032"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.064"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.128"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.256"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.512"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="1.024"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="2.048"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="4.096"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="8.192"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="16.384"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="+Inf"} 1 + apiserver_impersonation_attempts_duration_seconds_sum{decision="denied",mode=""} 0.1 + apiserver_impersonation_attempts_duration_seconds_count{decision="denied",mode=""} 1 + `, + }, + { + name: "allowed with legacy mode", + mode: "legacy", + decision: "allowed", + expectedValue: ` + # HELP apiserver_impersonation_attempts_total [ALPHA] Total number of impersonation attempts split by mode and decision. + # TYPE apiserver_impersonation_attempts_total counter + apiserver_impersonation_attempts_total{decision="allowed",mode="legacy"} 1 + # HELP apiserver_impersonation_attempts_duration_seconds [ALPHA] Latency of impersonation attempts in seconds split by mode and decision. + # TYPE apiserver_impersonation_attempts_duration_seconds histogram + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.001"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.002"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.004"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.008"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.016"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.032"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.064"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.128"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.256"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.512"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="1.024"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="2.048"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="4.096"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="8.192"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="16.384"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="+Inf"} 1 + apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="legacy"} 0.1 + apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="legacy"} 1 + `, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resetMetricsForTest() + + RecordImpersonationAttempt(tc.mode, tc.decision, 100*time.Millisecond) + + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.expectedValue), attemptMetrics...); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestRecordImpersonationAuthorizationCall(t *testing.T) { + RegisterMetrics() + + authorizationMetrics := []string{ + namespace + "_" + subsystem + "_authorization_attempts_total", + namespace + "_" + subsystem + "_authorization_attempts_duration_seconds", + } + + testCases := []struct { + name string + mode string + decision string + expectedValue string + }{ + { + name: "user-info allowed", + mode: "user-info", + decision: "allowed", + expectedValue: ` + # HELP apiserver_impersonation_authorization_attempts_total [ALPHA] Total number of authorization checks made by the impersonation handler split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_total counter + apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="user-info"} 1 + # HELP apiserver_impersonation_authorization_attempts_duration_seconds [ALPHA] Latency of authorization checks made by the impersonation handler in seconds split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_duration_seconds histogram + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.001"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.002"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.004"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.008"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.016"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.032"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.064"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.128"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.256"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.512"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="1.024"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="2.048"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="4.096"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="8.192"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="16.384"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="+Inf"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="user-info"} 0.1 + apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="user-info"} 1 + `, + }, + { + name: "arbitrary-node denied", + mode: "arbitrary-node", + decision: "denied", + expectedValue: ` + # HELP apiserver_impersonation_authorization_attempts_total [ALPHA] Total number of authorization checks made by the impersonation handler split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_total counter + apiserver_impersonation_authorization_attempts_total{decision="denied",mode="arbitrary-node"} 1 + # HELP apiserver_impersonation_authorization_attempts_duration_seconds [ALPHA] Latency of authorization checks made by the impersonation handler in seconds split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_duration_seconds histogram + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.001"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.002"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.004"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.008"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.016"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.032"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.064"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.128"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.256"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="0.512"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="1.024"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="2.048"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="4.096"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="8.192"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="16.384"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="arbitrary-node",le="+Inf"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="arbitrary-node"} 0.1 + apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="arbitrary-node"} 1 + `, + }, + { + name: "legacy allowed", + mode: "legacy", + decision: "allowed", + expectedValue: ` + # HELP apiserver_impersonation_authorization_attempts_total [ALPHA] Total number of authorization checks made by the impersonation handler split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_total counter + apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="legacy"} 1 + # HELP apiserver_impersonation_authorization_attempts_duration_seconds [ALPHA] Latency of authorization checks made by the impersonation handler in seconds split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_duration_seconds histogram + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.001"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.002"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.004"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.008"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.016"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.032"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.064"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.128"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.256"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="0.512"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="1.024"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="2.048"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="4.096"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="8.192"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="16.384"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="legacy",le="+Inf"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="legacy"} 0.1 + apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="legacy"} 1 + `, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resetMetricsForTest() + + RecordImpersonationAuthorizationCall(tc.mode, tc.decision, 100*time.Millisecond) + + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.expectedValue), authorizationMetrics...); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestRecordImpersonationMetricsMultiple(t *testing.T) { + RegisterMetrics() + resetMetricsForTest() + + RecordImpersonationAttempt("user-info", "allowed", 100*time.Millisecond) + RecordImpersonationAttempt("", "denied", 50*time.Millisecond) + RecordImpersonationAttempt("", "denied", 50*time.Millisecond) + RecordImpersonationAuthorizationCall("user-info", "allowed", 100*time.Millisecond) + RecordImpersonationAuthorizationCall("user-info", "allowed", 100*time.Millisecond) + RecordImpersonationAuthorizationCall("user-info", "denied", 50*time.Millisecond) + RecordImpersonationAuthorizationCall("legacy", "denied", 50*time.Millisecond) + + expectedValue := ` + # HELP apiserver_impersonation_attempts_duration_seconds [ALPHA] Latency of impersonation attempts in seconds split by mode and decision. + # TYPE apiserver_impersonation_attempts_duration_seconds histogram + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.001"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.002"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.004"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.008"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.016"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.032"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.064"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.128"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.256"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.512"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="1.024"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="2.048"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="4.096"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="8.192"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="16.384"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="+Inf"} 1 + apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="user-info"} 0.1 + apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="user-info"} 1 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.001"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.002"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.004"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.008"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.016"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.032"} 0 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.064"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.128"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.256"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="0.512"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="1.024"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="2.048"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="4.096"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="8.192"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="16.384"} 2 + apiserver_impersonation_attempts_duration_seconds_bucket{decision="denied",mode="",le="+Inf"} 2 + apiserver_impersonation_attempts_duration_seconds_sum{decision="denied",mode=""} 0.1 + apiserver_impersonation_attempts_duration_seconds_count{decision="denied",mode=""} 2 + # HELP apiserver_impersonation_attempts_total [ALPHA] Total number of impersonation attempts split by mode and decision. + # TYPE apiserver_impersonation_attempts_total counter + apiserver_impersonation_attempts_total{decision="allowed",mode="user-info"} 1 + apiserver_impersonation_attempts_total{decision="denied",mode=""} 2 + # HELP apiserver_impersonation_authorization_attempts_duration_seconds [ALPHA] Latency of authorization checks made by the impersonation handler in seconds split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_duration_seconds histogram + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.001"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.002"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.004"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.008"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.016"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.032"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.064"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.128"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.256"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="0.512"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="1.024"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="2.048"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="4.096"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="8.192"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="16.384"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="allowed",mode="user-info",le="+Inf"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="user-info"} 0.2 + apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="user-info"} 2 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.001"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.002"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.004"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.008"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.016"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.032"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.064"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.128"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.256"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="0.512"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="1.024"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="2.048"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="4.096"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="8.192"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="16.384"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="legacy",le="+Inf"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="legacy"} 0.05 + apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="legacy"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.001"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.002"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.004"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.008"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.016"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.032"} 0 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.064"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.128"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.256"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="0.512"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="1.024"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="2.048"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="4.096"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="8.192"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="16.384"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_bucket{decision="denied",mode="user-info",le="+Inf"} 1 + apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="user-info"} 0.05 + apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="user-info"} 1 + # HELP apiserver_impersonation_authorization_attempts_total [ALPHA] Total number of authorization checks made by the impersonation handler split by mode and decision. + # TYPE apiserver_impersonation_authorization_attempts_total counter + apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="user-info"} 2 + apiserver_impersonation_authorization_attempts_total{decision="denied",mode="legacy"} 1 + apiserver_impersonation_authorization_attempts_total{decision="denied",mode="user-info"} 1 + ` + + allMetrics := []string{ + namespace + "_" + subsystem + "_attempts_duration_seconds", + namespace + "_" + subsystem + "_attempts_total", + namespace + "_" + subsystem + "_authorization_attempts_duration_seconds", + namespace + "_" + subsystem + "_authorization_attempts_total", + } + + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedValue), allMetrics...); err != nil { + t.Fatal(err) + } +} + +func resetMetricsForTest() { + impersonationAttemptsTotal.Reset() + impersonationAttemptsDurationSeconds.Reset() + impersonationAuthorizationAttemptsTotal.Reset() + impersonationAuthorizationAttemptsDurationSeconds.Reset() +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go index 2efd08d25a8ef..3d0800747a423 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go @@ -415,7 +415,7 @@ func authorizeUnsafeDelete(ctx context.Context, attr admission.Attributes, authz decision, reason, err := authz.Authorize(ctx, record) if err != nil { err = fmt.Errorf("error while checking permission for %q, %w", record.Verb, err) - klog.FromContext(ctx).V(1).Error(err, "failed to authorize") + klog.FromContext(ctx).V(1).Info("failed to authorize", "err", err) return admission.NewForbidden(attr, err) } if decision == authorizer.DecisionAllow { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete_test.go index 3fbeef7f63e59..d0d50fcd0d260 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete_test.go @@ -18,10 +18,12 @@ package handlers import ( "context" + "encoding/json" "fmt" "io" "net/http" "net/http/httptest" + "strconv" "strings" "sync/atomic" "testing" @@ -41,9 +43,14 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/utils/ptr" + + "github.com/google/go-cmp/cmp" ) type mockCodecs struct { @@ -657,3 +664,501 @@ type fakeAuthorizer struct { func (authorizer fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { return authorizer.decision, authorizer.reason, authorizer.err } + +func TestDeleteResourceWithUnsafeDeletionFlow(t *testing.T) { + success := metav1.Status{Status: "Success", Code: 200, Details: &metav1.StatusDetails{}} + newInternalError := func(err error) metav1.Status { + status := errors.NewInternalError(err).ErrStatus + status.Kind = "Status" + status.APIVersion = "v1" + return status + } + newForbiddenError := func() metav1.Status { + status := errors.NewForbidden(schema.GroupResource{}, "Unknown", + fmt.Errorf("not permitted to do %q, reason: %s", "unsafe-delete-ignore-read-errors", "not permitted")).ErrStatus + status.Kind = "Status" + status.APIVersion = "v1" + return status + } + + tests := []struct { + name string + featureEnabled bool + // whether the registry implements rest.CorruptObjectDeleterProvider, and provides an unsafe deleter: + // nil: it does not implement the rest.CorruptObjectDeleterProvider interface + // true: it implements CorruptObjectDeleterProvider, and returns a valid unsafe deleter + // false: it implements CorruptObjectDeleterProvider, but returns nil when asked for an unsafe deleter + registryHasUnsafeDeleter *bool + // what the user passes in the delete options for ignoreStoreReadErrorWithClusterBreakingPotential + ignoreReadErr *bool + authorizer authorizer.Authorizer + // want + normalFlowWant *deletionFlowTracker // what the normal deletion flow should observe + unsafeFlowWant *deletionFlowTracker // what the unsafe deletion flow should observe + unsafeAnnotationWant bool // whether the unsafe audit annotation should be added + status metav1.Status // status we expect from the HTTP response + }{ + { + name: "feature disabled, registry does not implement CorruptObjectDeleterProvider, ignore is false, should invoke the normal deletion flow", + featureEnabled: false, + registryHasUnsafeDeleter: nil, + ignoreReadErr: ptr.To(false), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature disabled, registry does not implement CorruptObjectDeleterProvider, ignore is true, should invoke the normal deletion flow", + featureEnabled: false, + registryHasUnsafeDeleter: nil, + ignoreReadErr: ptr.To(true), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature disabled, registry provides a nil unsafe deleter, ignore is false, should invoke the normal deletion flow", + featureEnabled: false, + registryHasUnsafeDeleter: ptr.To(false), + ignoreReadErr: ptr.To(false), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature disabled, registry provides a nil unsafe deleter, ignore is true, should invoke the normal deletion flow", + featureEnabled: false, + registryHasUnsafeDeleter: ptr.To(false), + ignoreReadErr: ptr.To(true), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature disabled, registry provides a valid unsafe deleter, ignore is false, should invoke the normal deletion flow", + featureEnabled: false, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: ptr.To(false), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature disabled, registry provides a valid unsafe deleter, ignore is true, should invoke the normal deletion flow", + featureEnabled: false, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: ptr.To(true), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + + // feature enabled + { + name: "feature enabled, registry does not implement CorruptObjectDeleterProvider, ignore not set, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: nil, + ignoreReadErr: nil, + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature enabled, registry does not implement CorruptObjectDeleterProvider, ignore is false, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: nil, + ignoreReadErr: ptr.To(false), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: ptr.To(false)}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature enabled, registry does not implement CorruptObjectDeleterProvider, ignore is true, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: nil, + ignoreReadErr: ptr.To(true), + normalFlowWant: &deletionFlowTracker{}, + unsafeFlowWant: &deletionFlowTracker{}, + unsafeAnnotationWant: true, + status: newInternalError(fmt.Errorf("no unsafe deleter provided, can not honor ignoreStoreReadErrorWithClusterBreakingPotential")), + }, + { + name: "feature enabled, registry provides a nil unsafe deleter, ignore not set, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(false), + ignoreReadErr: nil, + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature enabled, registry provides a nil unsafe deleter, ignore is false, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(false), + ignoreReadErr: ptr.To(false), + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: ptr.To(false)}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature enabled, registry provides a nil unsafe deleter, ignore is true, error expected", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(false), + ignoreReadErr: ptr.To(true), + normalFlowWant: &deletionFlowTracker{}, + unsafeFlowWant: &deletionFlowTracker{}, + unsafeAnnotationWant: true, + status: newInternalError(fmt.Errorf("no unsafe deleter provided, can not honor ignoreStoreReadErrorWithClusterBreakingPotential")), + }, + { + name: "feature enabled, registry provides a valid unsafe deleter, ignore is not set, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: nil, + authorizer: &fakeAuthorizer{decision: authorizer.DecisionAllow, reason: "permitted"}, + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: nil}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature enabled, registry provides a valid unsafe deleter, ignore is false, should invoke the normal deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: ptr.To(false), + authorizer: &fakeAuthorizer{decision: authorizer.DecisionAllow, reason: "permitted"}, + normalFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: ptr.To(false)}, + unsafeFlowWant: &deletionFlowTracker{}, + status: success, + }, + { + name: "feature enabled, registry provides a valid unsafe deleter, ignore is true, no authorizer, error expected", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: ptr.To(true), + normalFlowWant: &deletionFlowTracker{}, + unsafeFlowWant: &deletionFlowTracker{}, + unsafeAnnotationWant: true, + status: newInternalError(fmt.Errorf("no authorizer provided, unable to authorize unsafe delete")), + }, + { + name: "feature enabled, registry provides a valid unsafe deleter, ignore is true, not authorized, error expected", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: ptr.To(true), + authorizer: &fakeAuthorizer{decision: authorizer.DecisionDeny, reason: "not permitted"}, + normalFlowWant: &deletionFlowTracker{}, + unsafeFlowWant: &deletionFlowTracker{}, + unsafeAnnotationWant: true, + status: newForbiddenError(), + }, + { + name: "feature enabled, registry provides a valid unsafe deleter, ignore is true, authorized, should invoke the unsafe deletion flow", + featureEnabled: true, + registryHasUnsafeDeleter: ptr.To(true), + ignoreReadErr: ptr.To(true), + authorizer: &fakeAuthorizer{decision: authorizer.DecisionAllow, reason: "permitted"}, + normalFlowWant: &deletionFlowTracker{}, + unsafeFlowWant: &deletionFlowTracker{Invoked: 1, Ignore: ptr.To(true)}, + unsafeAnnotationWant: true, + status: success, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowUnsafeMalformedObjectDeletion, test.featureEnabled) + + scope := &RequestScope{ + Namer: &mockNamer{}, + Serializer: newMetav1Serializer(t), + Authorizer: test.authorizer, + MetaGroupVersion: metav1.SchemeGroupVersion, + } + + // create a fake deleter that will be used for the normal deletion + // flow, and it will record the normal deletion activities + normalFlowObserved, unsafeFlowObserved := &deletionFlowTracker{}, &deletionFlowTracker{} + var deleter rest.GracefulDeleter = &fakeRegistry{tracker: normalFlowObserved} + if provider := test.registryHasUnsafeDeleter; provider != nil { + r := &fakeRegistryWithCorruptObjDeleter{GracefulDeleter: deleter} + if *provider { + // create a fake deleter that will be used for the unsafe deletion + // flow, and it will record the unsafe deletion activities + r.unsafeDeleter = &fakeRegistry{tracker: unsafeFlowObserved} + } + deleter = r + } + + handler := DeleteResource(deleter, true, scope, &fakeAdmitter{}) + req := newRequest(t, test.ignoreReadErr) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + + // the status we have received in the response should match expectation + resp := recorder.Result() + status := toStatus(t, resp) + if want, got := test.status, status; !cmp.Equal(want, got) { + t.Errorf("expected the unsafe deletion flow observation to match, diff: %s", cmp.Diff(want, got)) + } + + // normal deletion flow + if want, got := test.normalFlowWant, normalFlowObserved; !cmp.Equal(want, got) { + t.Errorf("expected the normal deletion flow observation to match, diff: %s", cmp.Diff(want, got)) + } + // this is an invariant; if the feature is disabled the normal deletion + // flow should never see the ignore option set. + if got := normalFlowObserved; !test.featureEnabled && got.Invoked == 1 && got.Ignore != nil { + t.Errorf("IgnoreStoreReadErrorWithClusterBreakingPotential should always be nil when the feature is disabled, but got: %t", *got.Ignore) + } + + // unsafe deletion flow + if want, got := test.unsafeFlowWant, unsafeFlowObserved; !cmp.Equal(want, got) { + t.Errorf("expected the unsafe deletion flow observation to match, diff: %s", cmp.Diff(want, got)) + } + // this is an invariant; when invoked, the unsafe deletion flow should always see the option enabled + if got := ptr.Deref(unsafeFlowObserved.Ignore, false); unsafeFlowObserved.Invoked == 1 && !got { + t.Errorf("Invariant failed, IgnoreStoreReadErrorWithClusterBreakingPotential should always be set to %t for the unsafe deletion flow, but got: %t", true, got) + } + + // certain annotation should be added in unsafe delete + const keyWant = "apiserver.k8s.io/unsafe-delete-ignore-read-error" + ac := audit.AuditContextFrom(req.Context()) + switch test.unsafeAnnotationWant { + case true: + if value, ok := ac.GetEventAnnotation(keyWant); !ok || len(value) > 0 { + t.Errorf("expected annotation %q to exist with an empty value, but got exists: %t, value: %q", keyWant, ok, value) + } + default: + if value, ok := ac.GetEventAnnotation(keyWant); ok { + t.Errorf("did not expect annotation %q to exist, but got exists: %t, value: %q", keyWant, ok, value) + } + } + }) + } +} + +func TestDeleteCollectionWithUnsafeDeletionFlow(t *testing.T) { + success := metav1.Status{Status: "Success", Code: 200, Details: &metav1.StatusDetails{}} + + tests := []struct { + name string + featureEnabled bool + // what the user passes in the delete options for ignoreStoreReadErrorWithClusterBreakingPotential + ignoreReadErr *bool + // want + deleterInvoked int // how many times the deleter should be invoked + status metav1.Status // status we expect from the HTTP response + }{ + { + name: "feature disabled, ignore not set, should invoke the deleter", + featureEnabled: false, + ignoreReadErr: nil, + deleterInvoked: 1, + status: success, + }, + { + name: "feature disabled, ignore is false, should invoke the deleter", + featureEnabled: false, + ignoreReadErr: ptr.To(false), + deleterInvoked: 1, + status: success, + }, + { + name: "feature disabled, ignore is true, should invoke the deleter", + featureEnabled: false, + ignoreReadErr: ptr.To(true), + deleterInvoked: 1, + status: success, + }, + { + name: "feature enabled, ignore not set, should invoke deleter", + featureEnabled: true, + ignoreReadErr: nil, + deleterInvoked: 1, + status: success, + }, + { + name: "feature enabled, ignore is false, should invoke deleter", + featureEnabled: true, + ignoreReadErr: ptr.To(false), + deleterInvoked: 1, + status: success, + }, + { + name: "feature enabled, ignore is true, invalid error expected", + featureEnabled: true, + ignoreReadErr: ptr.To(true), + status: metav1.Status{ + TypeMeta: metav1.TypeMeta{Kind: "Status", APIVersion: "v1"}, + Status: "Failure", + Reason: "Invalid", + Code: 422, + Details: &metav1.StatusDetails{ + Group: "meta.k8s.io", + Kind: "DeleteOptions", + Causes: []metav1.StatusCause{ + { + Type: "FieldValueInvalid", + Field: "ignoreStoreReadErrorWithClusterBreakingPotential", + Message: "Invalid value: true: is not allowed with DELETECOLLECTION, try again after removing the option", + }, + }, + }, + Message: `DeleteOptions.meta.k8s.io "" is invalid: ignoreStoreReadErrorWithClusterBreakingPotential: Invalid value: true: is not allowed with DELETECOLLECTION, try again after removing the option`, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowUnsafeMalformedObjectDeletion, test.featureEnabled) + + scope := &RequestScope{ + Namer: &mockNamer{}, + Serializer: newMetav1Serializer(t), + ParameterCodec: runtime.NewParameterCodec(scheme), + MetaGroupVersion: metav1.SchemeGroupVersion, + } + + // the deleter should never see the ignore option enabled, + // initializing it to true, so we can verify that it is reset to nil + observed := &deletionFlowTracker{} + deleter := func(ctx context.Context, _ rest.ValidateObjectFunc, options *metav1.DeleteOptions, _ *metainternalversion.ListOptions) (runtime.Object, error) { + observed.Invoked++ + observed.Ignore = nil + if ignore := options.IgnoreStoreReadErrorWithClusterBreakingPotential; ignore != nil { + observed.Ignore = ptr.To(*ignore) + } + return nil, nil + } + + handler := DeleteCollection(fakeCollectionDeleterFunc(deleter), true, scope, &fakeAdmitter{}) + req := newRequest(t, test.ignoreReadErr) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + + // the status we have received in the response should match expectation + resp := recorder.Result() + status := toStatus(t, resp) + if want, got := test.status, status; !cmp.Equal(want, got) { + t.Errorf("expected the unsafe deletion flow observation to match, diff: %s", cmp.Diff(want, got)) + } + + if want, got := test.deleterInvoked, observed.Invoked; want != got { + t.Errorf("expected the deleter to be invoked %d times, but got: %d", want, got) + } + // if invoked, the deleter should never see the ignore option enabled + if got := ptr.Deref(observed.Ignore, false); test.deleterInvoked == 1 && got { + t.Errorf("expected IgnoreStoreReadErrorWithClusterBreakingPotential to be nil for the deleter, but got: %t", got) + } + // we never expect the annotation to be added + const keyNeverWant = "apiserver.k8s.io/unsafe-delete-ignore-read-error" + ac := audit.AuditContextFrom(req.Context()) + if value, ok := ac.GetEventAnnotation(keyNeverWant); ok || len(value) > 0 { + t.Errorf("did not expect annotation %q to exist, but got exists: %t, value: %q", keyNeverWant, ok, value) + } + }) + } +} + +func newRequest(t *testing.T, ignoreReadErr *bool) *http.Request { + t.Helper() + + req, err := http.NewRequest(http.MethodDelete, "/test", io.NopCloser(strings.NewReader(""))) + if err != nil { + t.Fatalf("unexpected error creating a new http.Request: %v", err) + } + + reqInfo := &request.RequestInfo{IsResourceRequest: true} + req = req.WithContext(request.WithRequestInfo(req.Context(), reqInfo)) + + ctx := audit.WithAuditContext(req.Context()) + ac := audit.AuditContextFrom(ctx) + if err := ac.Init(audit.RequestAuditConfig{Level: auditinternal.LevelMetadata}, nil); err != nil { + t.Fatal(err) + } + req = req.WithContext(ctx) + + if ignoreReadErr != nil { + q := req.URL.Query() + q.Add("ignoreStoreReadErrorWithClusterBreakingPotential", strconv.FormatBool(*ignoreReadErr)) + req.URL.RawQuery = q.Encode() + } + + req.Header.Set("Content-Type", "application/json") + return req +} + +func newMetav1Serializer(t *testing.T) runtime.NegotiatedSerializer { + t.Helper() + + scheme := runtime.NewScheme() + metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) + if err := metainternalversion.AddToScheme(scheme); err != nil { + t.Fatalf("metainternalversion.AddToScheme failed - err: %v", err) + } + codecs := serializer.NewCodecFactory(scheme) + info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) + if !ok { + t.Fatalf("failed to setup serializer") + } + return runtime.NewSimpleNegotiatedSerializer(info) +} + +func toStatus(t *testing.T, resp *http.Response) metav1.Status { + t.Helper() + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unexpected error reading response body: %v", err) + } + var status metav1.Status + if err := json.Unmarshal(body, &status); err != nil { + t.Fatalf("unexpected error unmarshaling to metav1.Status: %v", err) + } + return status +} + +// to keep track of what a deletion flow (GracefulDeleter) observes +type deletionFlowTracker struct { + Invoked int // how many times Delete has been invoked + Ignore *bool // the ignore option passed to Delete +} + +// implements a fake GracefulDeleter. +// TODO: this is not thread safe if concurrent Delete calls are made by a +// test, tests are expected to invoke Delete serially, if that is to chage +// we need to implement locking to keept it concurrency safe. +type fakeRegistry struct { + tracker *deletionFlowTracker +} + +func (f *fakeRegistry) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + f.tracker.Invoked++ + f.tracker.Ignore = nil + // the following is on the basis that invoking the deleter more than once is an + // error condition, so we are just storing the final ignore option provided + if ignore := options.IgnoreStoreReadErrorWithClusterBreakingPotential; ignore != nil { + f.tracker.Ignore = ptr.To(*ignore) + } + return nil, true, nil +} + +var _ rest.CorruptObjectDeleterProvider = &fakeRegistryWithCorruptObjDeleter{} + +// fake registry implementation with support of the unsafe deletion flow +type fakeRegistryWithCorruptObjDeleter struct { + // the default GracefulDeleter in use for the normal deletion flow + rest.GracefulDeleter + // used for the unsafe deletion flow + unsafeDeleter rest.GracefulDeleter +} + +// the delete handler switches to the unsafe deletion flow by using GetCorruptObjDeleter +func (p *fakeRegistryWithCorruptObjDeleter) GetCorruptObjDeleter() rest.GracefulDeleter { + return p.unsafeDeleter +} + +type fakeAdmitter struct{} + +func (f *fakeAdmitter) Handles(_ admission.Operation) bool { return false } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go index 7734aabf8ca4d..6c5df54502ada 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go @@ -36,11 +36,11 @@ func TestAdmission(t *testing.T) { now := metav1.Now() validFieldsV1 := metav1.FieldsV1{} - var err error - validFieldsV1.Raw, err = fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "labels", "test-label")).ToJSON() + raw, err := fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "labels", "test-label")).ToJSON() if err != nil { t.Fatal(err) } + validFieldsV1.SetRawBytes(raw) validManagedFieldsEntry := metav1.ManagedFieldsEntry{ APIVersion: "v1", Operation: metav1.ManagedFieldsOperationApply, @@ -64,7 +64,7 @@ func TestAdmission(t *testing.T) { return managedFields, true }, "invalid fieldsV1": func(managedFields metav1.ManagedFieldsEntry) (metav1.ManagedFieldsEntry, bool) { - managedFields.FieldsV1 = &metav1.FieldsV1{Raw: []byte("{invalid}")} + managedFields.FieldsV1 = metav1.NewFieldsV1("{invalid}") return managedFields, true }, "invalid manager": func(managedFields metav1.ManagedFieldsEntry) (metav1.ManagedFieldsEntry, bool) { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go index eb52baf194657..134e163eeb3d6 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go @@ -25,10 +25,7 @@ import ( "strings" "time" - "go.opentelemetry.io/otel/attribute" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation" @@ -36,6 +33,7 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/apiserver/pkg/endpoints/request" @@ -170,142 +168,163 @@ func getRequestOptions(req *http.Request, scope *RequestScope, into runtime.Obje func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() - // For performance tracking purposes. - ctx, span := tracing.Start(ctx, "List", traceFields(req)...) - req = req.WithContext(ctx) - namespace, err := scope.Namer.Namespace(req) if err != nil { scope.err(err, w, req) return } - - // Watches for single objects are routed to this function. - // Treat a name parameter the same as a field selector entry. - hasName := true - _, name, err := scope.Namer.Name(req) - if err != nil { - hasName = false - } ctx = request.WithNamespace(ctx, namespace) - opts := metainternalversion.ListOptions{} - if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { - err = errors.NewBadRequest(err.Error()) + outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) + if err != nil { scope.err(err, w, req) return } - - metainternalversion.SetListOptionsDefaults(&opts, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)) - if errs := metainternalversionvalidation.ValidateListOptions(&opts, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)); len(errs) > 0 { - err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs) + opts, err := listOpts(req, scope) + if err != nil { scope.err(err, w, req) return } - outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) + switch { + case opts.Watch || forceWatch: + err = handleWatch(ctx, rw, scope, req, w, opts, outputMediaType, minRequestTimeout) + default: + err = handleList(ctx, r, scope, req, w, opts, outputMediaType) + } if err != nil { scope.err(err, w, req) return } + } +} - // transform fields - // TODO: DecodeParametersInto should do this. - if opts.FieldSelector != nil { - fn := func(label, value string) (newLabel, newValue string, err error) { - return scope.Convertor.ConvertFieldLabel(scope.Kind, label, value) - } - if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { - // TODO: allow bad request to set field causes based on query parameters - err = errors.NewBadRequest(err.Error()) - scope.err(err, w, req) - return - } +func listOpts(req *http.Request, scope *RequestScope) (metainternalversion.ListOptions, error) { + // Watches for single objects are routed to this function. + // Treat a name parameter the same as a field selector entry. + hasName := true + _, name, err := scope.Namer.Name(req) + if err != nil { + hasName = false + } + + opts := metainternalversion.ListOptions{} + if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { + return opts, errors.NewBadRequest(err.Error()) + } + + metainternalversion.SetListOptionsDefaults(&opts, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)) + if errs := metainternalversionvalidation.ValidateListOptions(&opts, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)); len(errs) > 0 { + err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs) + return opts, err + } + + // transform fields + // TODO: DecodeParametersInto should do this. + if opts.FieldSelector != nil { + fn := func(label, value string) (newLabel, newValue string, err error) { + return scope.Convertor.ConvertFieldLabel(scope.Kind, label, value) } + if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { + // TODO: allow bad request to set field causes based on query parameters + return opts, errors.NewBadRequest(err.Error()) + } + } - if hasName { - // metadata.name is the canonical internal name. - // SelectionPredicate will notice that this is a request for - // a single object and optimize the storage query accordingly. - nameSelector := fields.OneTermEqualSelector("metadata.name", name) - - // Note that fieldSelector setting explicitly the "metadata.name" - // will result in reaching this branch (as the value of that field - // is propagated to requestInfo as the name parameter. - // That said, the allowed field selectors in this branch are: - // nil, fields.Everything and field selector matching metadata.name - // for our name. - if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { - selectedName, ok := opts.FieldSelector.RequiresExactMatch("metadata.name") - if !ok || name != selectedName { - scope.err(errors.NewBadRequest("fieldSelector metadata.name doesn't match requested name"), w, req) - return - } - } else { - opts.FieldSelector = nameSelector + if hasName { + // metadata.name is the canonical internal name. + // SelectionPredicate will notice that this is a request for + // a single object and optimize the storage query accordingly. + nameSelector := fields.OneTermEqualSelector("metadata.name", name) + + // Note that fieldSelector setting explicitly the "metadata.name" + // will result in reaching this branch (as the value of that field + // is propagated to requestInfo as the name parameter. + // That said, the allowed field selectors in this branch are: + // nil, fields.Everything and field selector matching metadata.name + // for our name. + if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { + selectedName, ok := opts.FieldSelector.RequiresExactMatch("metadata.name") + if !ok || name != selectedName { + return opts, errors.NewBadRequest("fieldSelector metadata.name doesn't match requested name") } + } else { + opts.FieldSelector = nameSelector } + } + return opts, nil +} - if opts.Watch || forceWatch { - if rw == nil { - scope.err(errors.NewMethodNotSupported(scope.Resource.GroupResource(), "watch"), w, req) - return - } - // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. - timeout := time.Duration(0) - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - if timeout == 0 && minRequestTimeout > 0 { - timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) - } +func handleWatch(ctx context.Context, rw rest.Watcher, scope *RequestScope, req *http.Request, w http.ResponseWriter, opts metainternalversion.ListOptions, outputMediaType negotiation.MediaTypeOptions, minRequestTimeout time.Duration) error { + var span *tracing.Span + var onWatchListComplete WatchListCompleteHook + if isListWatchRequest(opts) { + ctx, span = tracing.Start(ctx, "WatchList", traceFields(req)...) + onWatchListComplete = func() { span.End(500 * time.Millisecond) } + req = req.WithContext(ctx) + } - klog.V(3).InfoS("Starting watch", "path", req.URL.Path, "resourceVersion", opts.ResourceVersion, "labels", opts.LabelSelector, "fields", opts.FieldSelector, "timeout", timeout) - ctx, cancel := context.WithTimeout(ctx, timeout) - defer func() { cancel() }() - watcher, err := rw.Watch(ctx, &opts) - if err != nil { - scope.err(err, w, req) - return - } - handler, err := serveWatchHandler(watcher, scope, outputMediaType, req, w, timeout, metrics.CleanListScope(ctx, &opts)) - if err != nil { - scope.err(err, w, req) - return - } - // Invalidate cancel() to defer until serve() is complete. - deferredCancel := cancel - cancel = func() {} - - serve := func() { - defer deferredCancel() - requestInfo, _ := request.RequestInfoFrom(ctx) - metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() { - defer watcher.Stop() - handler.ServeHTTP(w, req) - }) - } + if rw == nil { + return errors.NewMethodNotSupported(scope.Resource.GroupResource(), "watch") + } + // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. + timeout := time.Duration(0) + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + if timeout == 0 && minRequestTimeout > 0 { + timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) + } - // Run watch serving in a separate goroutine to allow freeing current stack memory - t := routine.TaskFrom(req.Context()) - if t != nil { - t.Func = serve - } else { - serve() - } - return - } + klog.V(3).InfoS("Starting watch", "path", req.URL.Path, "resourceVersion", opts.ResourceVersion, "labels", opts.LabelSelector, "fields", opts.FieldSelector, "sendInitialEvents", opts.SendInitialEvents, "timeout", timeout, "audit-ID", audit.GetAuditIDTruncated(ctx)) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer func() { cancel() }() + watcher, err := rw.Watch(ctx, &opts) + if err != nil { + return err + } + handler, err := serveWatchHandler(watcher, scope, outputMediaType, req, w, timeout, metrics.CleanListScope(ctx, &opts), onWatchListComplete) + if err != nil { + return err + } + // Invalidate cancel() to defer until serve() is complete. + deferredCancel := cancel + cancel = func() {} - // Log only long List requests (ignore Watch). - defer span.End(500 * time.Millisecond) - span.AddEvent("About to List from storage") - result, err := r.List(ctx, &opts) - if err != nil { - scope.err(err, w, req) - return - } - span.AddEvent("Listing from storage done") - defer span.AddEvent("Writing http response done", attribute.Int("count", meta.LenList(result))) - transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result) + serve := func() { + defer deferredCancel() + requestInfo, _ := request.RequestInfoFrom(ctx) + metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() { + defer watcher.Stop() + handler.ServeHTTP(w, req) + }) } + + // Run watch serving in a separate goroutine to allow freeing current stack memory + t := routine.TaskFrom(req.Context()) + if t != nil { + t.Func = serve + } else { + serve() + } + return nil +} + +func handleList(ctx context.Context, r rest.Lister, scope *RequestScope, req *http.Request, w http.ResponseWriter, opts metainternalversion.ListOptions, outputMediaType negotiation.MediaTypeOptions) error { + // For performance tracking purposes. + ctx, span := tracing.Start(ctx, "List", traceFields(req)...) + defer span.End(500 * time.Millisecond) + req = req.WithContext(ctx) + + result, err := r.List(ctx, &opts) + if err != nil { + return err + } + transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result) + return nil +} + +// isListWatchRequest is mirrored in staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go +func isListWatchRequest(opts metainternalversion.ListOptions) bool { + return opts.SendInitialEvents != nil && *opts.SendInitialEvents && opts.AllowWatchBookmarks } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/metrics/OWNERS b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/metrics/OWNERS index 433e84aa3e43e..04bd3fa653a75 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/metrics/OWNERS +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/metrics/OWNERS @@ -1,4 +1,5 @@ # See the OWNERS docs at https://go.k8s.io/owners -approvers: +approvers: [] +emeritus_approvers: - logicalhan diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go index 55f8b657a3c40..cb782f76a3eb4 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go @@ -34,7 +34,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" @@ -44,6 +43,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/flushwriter" "k8s.io/component-base/tracing" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response. diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go index 822e56288940e..9121ec3fadfff 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go @@ -24,21 +24,26 @@ import ( "sync/atomic" "time" + "go.opentelemetry.io/otel/attribute" "golang.org/x/net/websocket" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/metrics" apirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/server/httplog" "k8s.io/apiserver/pkg/storage" utilfeature "k8s.io/apiserver/pkg/util/feature" compbasemetrics "k8s.io/component-base/metrics" + "k8s.io/component-base/tracing" + "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // timeoutFactory abstracts watch timeout logic for testing @@ -64,7 +69,7 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) { // serveWatchHandler returns a handle to serve a watch response. // TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled. -func serveWatchHandler(watcher watch.Interface, scope *RequestScope, mediaTypeOptions negotiation.MediaTypeOptions, req *http.Request, w http.ResponseWriter, timeout time.Duration, metricsScope string) (http.Handler, error) { +func serveWatchHandler(watcher watch.Interface, scope *RequestScope, mediaTypeOptions negotiation.MediaTypeOptions, req *http.Request, w http.ResponseWriter, timeout time.Duration, metricsScope string, completeHook WatchListCompleteHook) (http.Handler, error) { options, err := optionsForTransform(mediaTypeOptions, req) if err != nil { return nil, err @@ -171,7 +176,8 @@ func serveWatchHandler(watcher watch.Interface, scope *RequestScope, mediaTypeOp TimeoutFactory: &realTimeoutFactory{timeout}, ServerShuttingDownCh: serverShuttingDownCh, - metricsScope: metricsScope, + metricsScope: metricsScope, + watchListCompleteHook: completeHook, } if wsstream.IsWebSocketRequest(req) { @@ -201,9 +207,12 @@ type WatchServer struct { TimeoutFactory TimeoutFactory ServerShuttingDownCh <-chan struct{} - metricsScope string + metricsScope string + watchListCompleteHook WatchListCompleteHook } +type WatchListCompleteHook func() + // watchEventMetricsRecorder allows the caller to count bytes written and report the size of the event. // It is thread-safe, as long as underlying io.Writer is thread-safe. // Once all Write calls for a given watch event have finished, RecordEvent must be called. @@ -230,6 +239,15 @@ func (c *watchEventMetricsRecorder) RecordEvent() { // HandleHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked. // or over a websocket connection. func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + ctx, span := tracing.Start(ctx, "WatchServer.HandleHTTP", + attribute.String("audit-id", audit.GetAuditIDTruncated(ctx)), + attribute.String("method", req.Method), + attribute.String("url", req.URL.Path), + attribute.String("protocol", req.Proto), + attribute.String("mediaType", s.MediaType), + attribute.String("encoder", string(s.Encoder.Identifier()))) + req = req.WithContext(ctx) defer func() { if s.MemoryAllocator != nil { runtime.AllocatorPool.Put(s.MemoryAllocator) @@ -239,7 +257,7 @@ func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { flusher, ok := w.(http.Flusher) if !ok { err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w) - utilruntime.HandleError(err) + utilruntime.HandleErrorWithContext(req.Context(), err, "Unable to start watch") s.Scope.err(errors.NewInternalError(err), w, req) return } @@ -248,7 +266,7 @@ func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { if framer == nil { // programmer error err := fmt.Errorf("no stream framing support is available for media type %q", s.MediaType) - utilruntime.HandleError(err) + utilruntime.HandleErrorWithContext(req.Context(), err, "No stream framing support available") s.Scope.err(errors.NewBadRequest(err.Error()), w, req) return } @@ -275,6 +293,7 @@ func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { ch := s.Watching.ResultChan() done := req.Context().Done() + span.AddEvent("About to start writing response") for { select { case <-s.ServerShuttingDownCh: @@ -295,10 +314,10 @@ func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { // End of results. return } - isWatchListLatencyRecordingRequired := shouldRecordWatchListLatency(event) + isWatchListLatencyRecordingRequired := shouldRecordWatchListLatency(req.Context(), event) if err := watchEncoder.Encode(event); err != nil { - utilruntime.HandleError(err) + utilruntime.HandleErrorWithContext(req.Context(), err, "Failed to encode watch event") // client disconnect. return } @@ -308,7 +327,20 @@ func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { flusher.Flush() } if isWatchListLatencyRecordingRequired { - metrics.RecordWatchListLatency(req.Context(), s.Scope.Resource, s.metricsScope) + // Record completion of initial listing phase for WatchList + receivedTimestamp, ok := apirequest.ReceivedTimestampFrom(req.Context()) + if !ok { + utilruntime.HandleErrorWithContext(req.Context(), nil, "Unable to measure watchlist latency, no received timestamp found in the context", "gvr", s.Scope.Resource) + } else { + initLatency := time.Since(receivedTimestamp) + metrics.RecordWatchListLatency(req.Context(), s.Scope.Resource, s.metricsScope, initLatency) + auditID := audit.GetAuditIDTruncated(req.Context()) + klog.V(3).InfoS("WatchList initial events sent", "path", req.URL.Path, "auditID", auditID, "initLatency", initLatency) + httplog.AddKeyValue(req.Context(), "watchlist_init_latency", initLatency) + span.AddEvent("Writing initial events done") + span.End(5 * time.Second) + s.watchListCompleteHook() + } } } } @@ -316,6 +348,9 @@ func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { // HandleWS serves a series of encoded events over a websocket connection. func (s *WatchServer) HandleWS(ws *websocket.Conn) { + ctx := ws.Request().Context() + logger := klog.FromContext(ctx) + defer func() { if s.MemoryAllocator != nil { runtime.AllocatorPool.Put(s.MemoryAllocator) @@ -329,10 +364,10 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) { defer cleanup() go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithLogger(logger) // This blocks until the connection is closed. // Client should not send anything. - wsstream.IgnoreReceives(ws, 0) + wsstream.IgnoreReceivesWithLogger(logger, ws, 0) // Once the client closes, we should also close close(done) }() @@ -340,7 +375,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) { framer := newWebsocketFramer(ws, s.UseTextFraming) gvr := s.Scope.Resource - watchEncoder := newWatchEncoder(context.TODO(), gvr, s.EmbeddedEncoder, s.Encoder, framer) + watchEncoder := newWatchEncoder(ctx, gvr, s.EmbeddedEncoder, s.Encoder, framer) ch := s.Watching.ResultChan() for { @@ -356,7 +391,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) { } if err := watchEncoder.Encode(event); err != nil { - utilruntime.HandleError(err) + utilruntime.HandleErrorWithLogger(logger, err, "Failed to encode watch event") // client disconnect. return } @@ -393,7 +428,7 @@ func (w *websocketFramer) Write(p []byte) (int, error) { var _ io.Writer = &websocketFramer{} -func shouldRecordWatchListLatency(event watch.Event) bool { +func shouldRecordWatchListLatency(ctx context.Context, event watch.Event) bool { if event.Type != watch.Bookmark || !utilfeature.DefaultFeatureGate.Enabled(features.WatchList) { return false } @@ -403,7 +438,7 @@ func shouldRecordWatchListLatency(event watch.Event) bool { // for more please read https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3157-watch-list hasAnnotation, err := storage.HasInitialEventsEndBookmarkAnnotation(event.Object) if err != nil { - utilruntime.HandleError(fmt.Errorf("unable to determine if the obj has the required annotation for measuring watchlist latency, obj %T: %v", event.Object, err)) + utilruntime.HandleErrorWithContext(ctx, err, "Unable to determine if the object has the required annotation for measuring watchlist latency", "object", fmt.Sprintf("%T", event.Object)) return false } return hasAnnotation diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS b/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS index 43fbd53336413..50b8726a7d23a 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS @@ -2,5 +2,6 @@ reviewers: - wojtek-t -approvers: +approvers: [] +emeritus_approvers: - logicalhan diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go index c0fe2bd411dfe..3773c07f2b0c2 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go @@ -18,7 +18,6 @@ package metrics import ( "context" - "fmt" "net/http" "net/url" "strconv" @@ -32,7 +31,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilsets "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/endpoints/request" @@ -290,7 +288,7 @@ var ( Name: "watch_list_duration_seconds", Help: "Response latency distribution in seconds for watch list requests broken by group, version, resource and scope.", Buckets: []float64{0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 2, 4, 6, 8, 10, 15, 20, 30, 45, 60}, - StabilityLevel: compbasemetrics.ALPHA, + StabilityLevel: compbasemetrics.BETA, }, []string{"group", "version", "resource", "scope"}, ) @@ -573,15 +571,8 @@ func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, comp } // RecordWatchListLatency simply records response latency for watch list requests. -func RecordWatchListLatency(ctx context.Context, gvr schema.GroupVersionResource, metricsScope string) { - requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(ctx) - if !ok { - utilruntime.HandleError(fmt.Errorf("unable to measure watchlist latency because no received ts found in the ctx, gvr: %s", gvr)) - return - } - elapsedSeconds := time.Since(requestReceivedTimestamp).Seconds() - - watchListLatencies.WithContext(ctx).WithLabelValues(gvr.Group, gvr.Version, gvr.Resource, metricsScope).Observe(elapsedSeconds) +func RecordWatchListLatency(ctx context.Context, gvr schema.GroupVersionResource, metricsScope string, elapsed time.Duration) { + watchListLatencies.WithContext(ctx).WithLabelValues(gvr.Group, gvr.Version, gvr.Resource, metricsScope).Observe(elapsed.Seconds()) } // MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go b/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go index d6bf062a19b71..1cf14d892fdcc 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go @@ -22,7 +22,7 @@ import ( "net/http" "strings" - "k8s.io/apimachinery/pkg/api/validation/path" + "k8s.io/apimachinery/pkg/api/validate/content" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -246,7 +246,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er if opts.FieldSelector != nil { if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok { - if len(path.IsValidPathSegmentName(name)) == 0 { + if len(content.IsPathSegmentName(name)) == 0 { requestInfo.Name = name } } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/request/webhook_duration.go b/staging/src/k8s.io/apiserver/pkg/endpoints/request/webhook_duration.go index bda43b617b7e2..df0ff77d8931c 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/request/webhook_duration.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/request/webhook_duration.go @@ -165,6 +165,10 @@ type LatencyTrackers struct { // When called multiple times, the latency incurred inside to // decode func each time will be summed up. DecodeTracker DurationTracker + + // ImpersonationTracker tracks the latency incurred in resolving impersonation. + // This includes mode selection, authorization checks, and cache lookups. + ImpersonationTracker DurationTracker } type latencyTrackersKeyType int @@ -193,6 +197,7 @@ func WithLatencyTrackersAndCustomClock(parent context.Context, c clock.Clock) co SerializationTracker: newSumLatencyTracker(c), ResponseWriteTracker: newSumLatencyTracker(c), DecodeTracker: newSumLatencyTracker(c), + ImpersonationTracker: newSumLatencyTracker(c), }) } @@ -286,6 +291,14 @@ func TrackDecodeLatency(ctx context.Context, d time.Duration) { } } +// TrackImpersonationLatency is used to track latency incurred +// in resolving impersonation for the request. +func TrackImpersonationLatency(ctx context.Context, d time.Duration) { + if tracker, ok := LatencyTrackersFrom(ctx); ok { + tracker.ImpersonationTracker.TrackDuration(d) + } +} + // AuditAnnotationsFromLatencyTrackers will inspect each latency tracker // associated with the request context and return a set of audit // annotations that can be added to the API audit entry. @@ -301,6 +314,7 @@ func AuditAnnotationsFromLatencyTrackers(ctx context.Context) map[string]string apfQueueWaitLatencyKey = "apiserver.latency.k8s.io/apf-queue-wait" authenticationLatencyKey = "apiserver.latency.k8s.io/authentication" authorizationLatencyKey = "apiserver.latency.k8s.io/authorization" + impersonationLatencyKey = "apiserver.latency.k8s.io/impersonation" ) tracker, ok := LatencyTrackersFrom(ctx) @@ -339,5 +353,8 @@ func AuditAnnotationsFromLatencyTrackers(ctx context.Context) map[string]string if latency := tracker.AuthorizationTracker.GetLatency(); latency != 0 { annotations[authorizationLatencyKey] = latency.String() } + if latency := tracker.ImpersonationTracker.GetLatency(); latency != 0 { + annotations[impersonationLatencyKey] = latency.String() + } return annotations } diff --git a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go index 3c9a799a58706..dbe776e8af490 100644 --- a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go @@ -128,13 +128,32 @@ const ( // Enables running declarative validation of APIs, where declared. When enabled, APIs with // declarative validation rules will validate objects using the generated // declarative validation code and compare the results to the regular imperative validation. - // See DeclarativeValidationTakeover for more. + // See DeclarativeValidationBeta for more. DeclarativeValidation featuregate.Feature = "DeclarativeValidation" + // owner: @jpbetz @aaron-prindle @yongruilin + // kep: http://kep.k8s.io/5073 + // beta: v1.36 + // + // This feature gate acts as the Global Safety Switch for Beta-stage validation rules (+k8s:beta). + // It allows cluster admins to disable enforcement for validations in the Beta stage if + // regressions are found, forcing them back to Shadow mode. + // In Shadow mode, declarative validation is executed and mismatches against handwritten + // validation are logged as metrics, but failures do not reject requests. + // Handwritten validation remains authoritative and enforced. + // Enforcement logic for resources using WithDeclarativeEnforcement(): + // - Standard tags (no prefix): Always Enforced (Bypasses this gate). + // - Beta tags (+k8s:beta): Enforced when this gate is enabled (default), otherwise Shadowed. + // - Alpha tags (+k8s:alpha): Always Shadowed. + // This gate has no effect if the master DeclarativeValidation feature gate is disabled. + DeclarativeValidationBeta featuregate.Feature = "DeclarativeValidationBeta" + // owner: @jpbetz @aaron-prindle @yongruilin // kep: http://kep.k8s.io/5073 // beta: v1.33 // + // Deprecated: in favor of DeclarativeValidationBeta. + // // When enabled, declarative validation errors are returned directly to the caller, // replacing hand-written validation errors for rules that have declarative implementations. // When disabled, hand-written validation errors are always returned, effectively putting @@ -162,6 +181,15 @@ const ( // Enables generating snapshots of watch cache store and using them to serve LIST requests. ListFromCacheSnapshot featuregate.Feature = "ListFromCacheSnapshot" + // owner: @aramase @BenTheElder + // kep: https://kep.k8s.io/5793 + // + // Enables manifest-based admission control configuration for webhooks and policies. + // When enabled, admission webhooks and policies can be loaded from + // manifest files on disk at API server startup, providing bootstrap-time enforcement + // and protection against API-based modification. + ManifestBasedAdmissionControlConfig featuregate.Feature = "ManifestBasedAdmissionControlConfig" + // owner: @alexzielenski, @cici37, @jiahuif, @jpbetz // kep: https://kep.k8s.io/3962 // @@ -199,6 +227,14 @@ const ( // This prevents watch cache from being starved by other watches. SeparateCacheWatchRPC featuregate.Feature = "SeparateCacheWatchRPC" + // owner: @jefftree + // kep: https://kep.k8s.io/5866 + // + // Enables the shard selector parameter on List/Watch requests, + // allowing clients to receive a filtered subset of objects based + // on hash ranges of metadata fields (e.g. UID). + ShardedListAndWatch featuregate.Feature = "ShardedListAndWatch" + // owner: @serathius // // Enables APF to use size of objects for estimating request cost. @@ -241,12 +277,6 @@ const ( // Enables JWKs metrics for Structured Authentication Configuration StructuredAuthenticationConfigurationJWKSMetrics featuregate.Feature = "StructuredAuthenticationConfigurationJWKSMetrics" - // owner: @palnabarun - // kep: https://kep.k8s.io/3221 - // - // Enables Structured Authorization Configuration - StructuredAuthorizationConfiguration featuregate.Feature = "StructuredAuthorizationConfiguration" - // owner: @aramase // // Enables validation of service account UID in TokenRequest API. @@ -284,11 +314,6 @@ const ( // Enables post-start-hook for storage readiness WatchCacheInitializationPostStartHook featuregate.Feature = "WatchCacheInitializationPostStartHook" - // owner: @serathius - // Enables watches without resourceVersion to be served from storage. - // Used to prevent https://github.com/kubernetes/kubernetes/issues/123072 until etcd fixes the issue. - WatchFromStorageWithoutResourceVersion featuregate.Feature = "WatchFromStorageWithoutResourceVersion" - // owner: @p0lyn0mial // // Allow the API server to stream individual items instead of chunking @@ -374,6 +399,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate ConstrainedImpersonation: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, CoordinatedLeaderElection: { @@ -383,10 +409,16 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate DeclarativeValidation: { {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA and LockToDefault in 1.36, remove in 1.39 + }, + + DeclarativeValidationBeta: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, DeclarativeValidationTakeover: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Deprecated}, }, DetectCacheInconsistency: { @@ -404,9 +436,14 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, + ManifestBasedAdmissionControlConfig: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, + }, + MutatingAdmissionPolicy: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA}, }, OpenAPIEnums: { @@ -433,6 +470,11 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate SeparateCacheWatchRPC: { {Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated}, + {Version: version.MustParse("1.36"), Default: false, LockToDefault: true, PreRelease: featuregate.Deprecated}, + }, + + ShardedListAndWatch: { + {Version: version.MustParse("1.36"), Default: false, PreRelease: featuregate.Alpha}, }, SizeBasedListCostEstimate: { @@ -472,12 +514,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, }, - StructuredAuthorizationConfiguration: { - {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, - }, - TokenRequestServiceAccountUIDValidation: { {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, @@ -489,15 +525,12 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate UnknownVersionInteroperabilityProxy: { {Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, WatchCacheInitializationPostStartHook: { {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Beta}, - }, - - WatchFromStorageWithoutResourceVersion: { - {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Deprecated, LockToDefault: true}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta}, }, WatchList: { diff --git a/staging/src/k8s.io/apiserver/pkg/quota/v1/resources.go b/staging/src/k8s.io/apiserver/pkg/quota/v1/resources.go index b66471920993d..6fa01fc61379a 100644 --- a/staging/src/k8s.io/apiserver/pkg/quota/v1/resources.go +++ b/staging/src/k8s.io/apiserver/pkg/quota/v1/resources.go @@ -17,7 +17,7 @@ limitations under the License. package v1 import ( - "sort" + "slices" "strings" corev1 "k8s.io/api/core/v1" @@ -198,7 +198,7 @@ func Intersection(a []corev1.ResourceName, b []corev1.ResourceName) []corev1.Res } result = append(result, item) } - sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) + slices.Sort(result) return result } @@ -211,7 +211,7 @@ func Difference(a []corev1.ResourceName, b []corev1.ResourceName) []corev1.Resou } result = append(result, item) } - sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) + slices.Sort(result) return result } diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go index 722a312270a3e..4fef3be397553 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go @@ -27,8 +27,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/validate/content" "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/api/validation/path" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -45,6 +45,7 @@ import ( "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/sharding" "k8s.io/apiserver/pkg/storage" storeerr "k8s.io/apiserver/pkg/storage/errors" "k8s.io/apiserver/pkg/storage/etcd3/metrics" @@ -286,7 +287,7 @@ func NamespaceKeyFunc(ctx context.Context, prefix string, name string) (string, if len(name) == 0 { return "", apierrors.NewBadRequest("Name parameter required.") } - if msgs := path.IsValidPathSegmentName(name); len(msgs) != 0 { + if msgs := content.IsPathSegmentName(name); len(msgs) != 0 { return "", apierrors.NewBadRequest(fmt.Sprintf("Name parameter invalid: %q: %s", name, strings.Join(msgs, ";"))) } key = key + "/" + name @@ -299,7 +300,7 @@ func NoNamespaceKeyFunc(ctx context.Context, prefix string, name string) (string if len(name) == 0 { return "", apierrors.NewBadRequest("Name parameter required.") } - if msgs := path.IsValidPathSegmentName(name); len(msgs) != 0 { + if msgs := content.IsPathSegmentName(name); len(msgs) != 0 { return "", apierrors.NewBadRequest(fmt.Sprintf("Name parameter invalid: %q: %s", name, strings.Join(msgs, ";"))) } key := prefix + "/" + name @@ -393,6 +394,13 @@ func (e *Store) ListPredicate(ctx context.Context, p storage.SelectionPredicate, } p.Limit = options.Limit p.Continue = options.Continue + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) && options.ShardSelector != "" { + sel, err := sharding.Parse(options.ShardSelector) + if err != nil { + return nil, fmt.Errorf("invalid shard selector: %w", err) + } + p.ShardSelector = sel + } list := e.NewListFunc() qualifiedResource := e.qualifiedResourceFromContext(ctx) storageOpts := storage.ListOptions{ @@ -1263,7 +1271,7 @@ func (e *Store) DeleteCollection(ctx context.Context, deleteValidation rest.Vali for i := 0; i < workersNumber; i++ { go func() { // panics don't cross goroutine boundaries - defer utilruntime.HandleCrash(func(panicReason interface{}) { + defer utilruntime.HandleCrashWithContext(ctx, func(_ context.Context, panicReason interface{}) { errs <- fmt.Errorf("DeleteCollection goroutine panicked: %v", panicReason) }) defer wg.Done() @@ -1288,7 +1296,7 @@ func (e *Store) DeleteCollection(ctx context.Context, deleteValidation rest.Vali } // In case of all workers exit, notify distributor. go func() { - defer utilruntime.HandleCrash(func(panicReason interface{}) { + defer utilruntime.HandleCrashWithContext(ctx, func(_ context.Context, panicReason interface{}) { errs <- fmt.Errorf("DeleteCollection workers closer panicked: %v", panicReason) }) wg.Wait() @@ -1429,13 +1437,25 @@ func (e *Store) Watch(ctx context.Context, options *metainternalversion.ListOpti if options != nil { resourceVersion = options.ResourceVersion predicate.AllowWatchBookmarks = options.AllowWatchBookmarks + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) && options.ShardSelector != "" { + sel, err := sharding.Parse(options.ShardSelector) + if err != nil { + return nil, fmt.Errorf("invalid shard selector: %w", err) + } + predicate.ShardSelector = sel + } } return e.WatchPredicate(ctx, predicate, resourceVersion, options.SendInitialEvents) } // WatchPredicate starts a watch for the items that matches. func (e *Store) WatchPredicate(ctx context.Context, p storage.SelectionPredicate, resourceVersion string, sendInitialEvents *bool) (watch.Interface, error) { - storageOpts := storage.ListOptions{ResourceVersion: resourceVersion, Predicate: p, Recursive: true, SendInitialEvents: sendInitialEvents} + storageOpts := storage.ListOptions{ + ResourceVersion: resourceVersion, + Predicate: p, + Recursive: true, + SendInitialEvents: sendInitialEvents, + } // if we're not already namespace-scoped, see if the field selector narrows the scope of the watch if requestNamespace, _ := genericapirequest.NamespaceFrom(ctx); len(requestNamespace) == 0 { diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go index dc2172eded85a..14bcac4194c93 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/user" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest/resttest" @@ -80,6 +81,11 @@ func (t *Tester) ReturnDeletedObject() *Tester { return t } +func (t *Tester) SetRequestInfo(requestInfo *genericapirequest.RequestInfo) *Tester { + t.tester.SetRequestInfo(requestInfo) + return t +} + func (t *Tester) TestCreate(valid runtime.Object, invalid ...runtime.Object) { t.tester.TestCreate( valid, diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go index b3e57d0a45724..87b2c9d81bfdb 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go @@ -22,8 +22,8 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/validate/content" genericvalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/api/validation/path" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -89,6 +89,13 @@ type RESTCreateStrategy interface { Canonicalize(obj runtime.Object) } +func validatePathSegment(name string, prefix bool) []string { + if prefix { + return content.IsPathSegmentPrefix(name) + } + return content.IsPathSegmentName(name) +} + // BeforeCreate ensures that common operations for all resources are performed on creation. It only returns // errors that can be converted to api.Status. It invokes PrepareForCreate, then Validate. // It returns nil if the object should be created. @@ -126,7 +133,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime. // Custom validation (including name validation) passed // Now run common validation on object meta // Do this *after* custom validation so that specific error messages are shown whenever possible - if errs := genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 { + if errs := genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), validatePathSegment, field.NewPath("metadata")); len(errs) > 0 { return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) } diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go index 000bb52497a5f..e003cfe1316db 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go @@ -28,7 +28,6 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/api/validation/path" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" @@ -54,6 +53,7 @@ type Tester struct { returnDeletedObject bool namer func(int) string userInfo user.Info + requestInfo *genericapirequest.RequestInfo } func New(t *testing.T, storage rest.Storage) *Tester { @@ -95,6 +95,12 @@ func (t *Tester) ReturnDeletedObject() *Tester { return t } +// SetRequestInfo sets the RequestInfo that should be present in the context when the +// storage operation is called. +func (t *Tester) SetRequestInfo(requestInfo *genericapirequest.RequestInfo) { + t.requestInfo = requestInfo +} + // TestNamespace returns the namespace that will be used when creating contexts. // Returns NamespaceNone for cluster-scoped objects. func (t *Tester) TestNamespace() string { @@ -118,6 +124,9 @@ func (t *Tester) TestContext() context.Context { if t.userInfo != nil { ctx = genericapirequest.WithUser(ctx, t.userInfo) } + if t.requestInfo != nil { + ctx = genericapirequest.WithRequestInfo(ctx, t.requestInfo) + } return ctx } @@ -416,6 +425,9 @@ func (t *Tester) testCreateHasMetadata(valid runtime.Object) { func (t *Tester) testCreateIgnoresContextNamespace(valid runtime.Object, opts metav1.CreateOptions) { // Ignore non-empty namespace in context ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2") + if t.requestInfo != nil { + ctx = genericapirequest.WithRequestInfo(ctx, t.requestInfo) + } // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), rest.ValidateAllObjectFunc, &opts) @@ -435,6 +447,9 @@ func (t *Tester) testCreateIgnoresMismatchedNamespace(valid runtime.Object, opts // Ignore non-empty namespace in object meta objectMeta.SetNamespace("not-default") ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2") + if t.requestInfo != nil { + ctx = genericapirequest.WithRequestInfo(ctx, t.requestInfo) + } // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), rest.ValidateAllObjectFunc, &opts) @@ -449,7 +464,8 @@ func (t *Tester) testCreateIgnoresMismatchedNamespace(valid runtime.Object, opts } func (t *Tester) testCreateValidatesNames(valid runtime.Object, opts metav1.CreateOptions) { - for _, invalidName := range path.NameMayNotBe { + invalidPathElements := []string{".", ".."} + for _, invalidName := range invalidPathElements { objCopy := valid.DeepCopyObject() objCopyMeta := t.getObjectMetaOrFail(objCopy) objCopyMeta.SetName(invalidName) @@ -461,7 +477,8 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object, opts metav1.Crea } } - for _, invalidSuffix := range path.NameMayNotContain { + invalidInPath := []string{"/", "%"} + for _, invalidSuffix := range invalidInPath { objCopy := valid.DeepCopyObject() objCopyMeta := t.getObjectMetaOrFail(objCopy) objCopyMeta.SetName(objCopyMeta.GetName() + invalidSuffix) @@ -1178,6 +1195,9 @@ func (t *Tester) testGetDifferentNamespace(obj runtime.Object) { objMeta.SetName(t.namer(5)) ctx1 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar3") + if t.requestInfo != nil { + ctx1 = genericapirequest.WithRequestInfo(ctx1, t.requestInfo) + } objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1)) _, err := t.storage.(rest.Creater).Create(ctx1, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) if err != nil { @@ -1185,6 +1205,9 @@ func (t *Tester) testGetDifferentNamespace(obj runtime.Object) { } ctx2 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar4") + if t.requestInfo != nil { + ctx2 = genericapirequest.WithRequestInfo(ctx2, t.requestInfo) + } objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx2)) _, err = t.storage.(rest.Creater).Create(ctx2, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) if err != nil { @@ -1239,7 +1262,13 @@ func (t *Tester) testGetFound(obj runtime.Object) { func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) { ctx1 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar1") + if t.requestInfo != nil { + ctx1 = genericapirequest.WithRequestInfo(ctx1, t.requestInfo) + } ctx2 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar2") + if t.requestInfo != nil { + ctx2 = genericapirequest.WithRequestInfo(ctx2, t.requestInfo) + } objMeta := t.getObjectMetaOrFail(obj) objMeta.SetName(t.namer(4)) objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1)) diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go index 00f42cfe7c7a2..32de351aa7ae9 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go @@ -23,7 +23,6 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" genericvalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/api/validation/path" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" @@ -94,7 +93,7 @@ func validateCommonFields(obj, old runtime.Object, strategy RESTUpdateStrategy) if err != nil { return nil, fmt.Errorf("failed to get old object metadata: %v", err) } - allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata"))...) + allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), validatePathSegment, field.NewPath("metadata"))...) allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessorUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...) return allErrs, nil diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/validate.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/validate.go index 65ffdd4f16df6..f0e355e54f920 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/validate.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/validate.go @@ -76,13 +76,37 @@ func WithNormalizationRules(rules []field.NormalizationRule) ValidationConfig { } } +// WithDeclarativeEnforcement marks the validation configuration to indicate that it includes +// declarative validations that should follow the fine-grained Validation Lifecycle. +// When set, declarative validation is always executed regardless of feature gates. +// Authority is determined by individual tag prefixes (+k8s:alpha, +k8s:beta) and the +// DeclarativeValidationBeta safety switch. +func WithDeclarativeEnforcement() ValidationConfig { + return func(config *validationConfigOption) { + config.declarativeEnforcement = true + } +} + +type allDeclarativeEnforcedKeyType struct{} + +var allDeclarativeEnforcedKey = allDeclarativeEnforcedKeyType{} + +// WithAllDeclarativeEnforcedForTest returns a copy of parent context with allDeclarativeEnforcedKey set to true. +// This is used for testing to expose all declarative validation errors and filter all handwritten validation errors +// that are covered by declarative validation, regardless of the feature gate or maturity level. +// +// NOTE: This function is intended for testing purposes only and should not be used in production code. +func WithAllDeclarativeEnforcedForTest(ctx context.Context) context.Context { + return context.WithValue(ctx, allDeclarativeEnforcedKey, true) +} + type validationConfigOption struct { - opType operation.Type - options []string - takeover bool - subresourceGVKMapper GroupVersionKindProvider - validationIdentifier string - normalizationRules []field.NormalizationRule + opType operation.Type + options []string + subresourceGVKMapper GroupVersionKindProvider + validationIdentifier string + normalizationRules []field.NormalizationRule + declarativeEnforcement bool } // validateDeclaratively validates obj and oldObj against declarative @@ -155,9 +179,9 @@ func parseSubresourcePath(subresourcePath string) ([]string, error) { // compareDeclarativeErrorsAndEmitMismatches checks for mismatches between imperative and declarative validation // and logs + emits metrics when inconsistencies are found -func compareDeclarativeErrorsAndEmitMismatches(ctx context.Context, imperativeErrs, declarativeErrs field.ErrorList, takeover bool, validationIdentifier string, normalizationRules []field.NormalizationRule) { +func compareDeclarativeErrorsAndEmitMismatches(ctx context.Context, imperativeErrs, declarativeErrs field.ErrorList, enforced bool, validationIdentifier string, normalizationRules []field.NormalizationRule) { logger := klog.FromContext(ctx) - mismatchDetails := gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs, takeover, normalizationRules) + mismatchDetails := gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs, enforced, normalizationRules) for _, detail := range mismatchDetails { // Log information about the mismatch using contextual logger logger.Error(nil, detail) @@ -169,29 +193,34 @@ func compareDeclarativeErrorsAndEmitMismatches(ctx context.Context, imperativeEr // gatherDeclarativeValidationMismatches compares imperative and declarative validation errors // and returns detailed information about any mismatches found. Errors are compared via type, field, and origin -func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field.ErrorList, takeover bool, normalizationRules []field.NormalizationRule) []string { +func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field.ErrorList, enforced bool, normalizationRules []field.NormalizationRule) []string { var mismatchDetails []string // short circuit here to minimize allocs for usual case of 0 validation errors if len(imperativeErrs) == 0 && len(declarativeErrs) == 0 { return mismatchDetails } - // recommendation based on takeover status - recommendation := "This difference should not affect system operation since hand written validation is authoritative." - if takeover { - recommendation = "Consider disabling the DeclarativeValidationTakeover feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes." + // default recommendation based on enforcement status + const ( + authoritativeMsg = "This difference should not affect system operation since hand written validation is authoritative." + disableBetaMsg = "Consider disabling the DeclarativeValidationBeta feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes." + ) + + defaultRecommendation := authoritativeMsg + if enforced { + defaultRecommendation = disableBetaMsg } + fuzzyMatcher := field.ErrorMatcher{}.ByType().ByOrigin().RequireOriginWhenInvalid().ByFieldNormalized(normalizationRules) - exactMatcher := field.ErrorMatcher{}.Exactly() - // Dedupe imperative errors of exact error matches as they are - // not intended and come from (buggy) duplicate validation calls + // Dedupe imperative errors using the fuzzy matcher (type, field, and origin) as they are + // not intended and come from (buggy) duplicate validation calls. // This is necessary as without deduping we could get unmatched - // imperative errors for cases that are correct (matching) + // imperative errors for cases that are correct (matching). dedupedImperativeErrs := field.ErrorList{} for _, err := range imperativeErrs { found := false for _, existingErr := range dedupedImperativeErrs { - if exactMatcher.Matches(existingErr, err) { + if fuzzyMatcher.Matches(existingErr, err) { found = true break } @@ -233,12 +262,18 @@ func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field } if matchCount == 0 { + rec := defaultRecommendation + // If the imperative error is explicitly Alpha, it is never enforced, so HV is authoritative. + if iErr.IsAlpha() { + rec = authoritativeMsg + } + mismatchDetails = append(mismatchDetails, fmt.Sprintf( "Unexpected difference between hand written validation and declarative validation error results, unmatched error(s) found %s. "+ "This indicates an issue with declarative validation. %s", fuzzyMatcher.Render(iErr), - recommendation, + rec, ), ) } @@ -248,12 +283,18 @@ func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field // Any remaining unmatched declarative errors are considered "extra" for _, dErr := range remaining { + rec := defaultRecommendation + // If the declarative error is Alpha, it is never enforced (shadowed), so HV is authoritative. + if dErr.IsAlpha() { + rec = authoritativeMsg + } + mismatchDetails = append(mismatchDetails, fmt.Sprintf( "Unexpected difference between hand written validation and declarative validation error results, extra error(s) found %s. "+ "This indicates an issue with declarative validation. %s", fuzzyMatcher.Render(dErr), - recommendation, + rec, ), ) } @@ -262,8 +303,8 @@ func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field } // createDeclarativeValidationPanicHandler returns a function with panic recovery logic -// that will increment the panic metric and either log or append errors based on the takeover parameter. -func createDeclarativeValidationPanicHandler(ctx context.Context, errs *field.ErrorList, takeover bool, validationIdentifier string) func() { +// that will increment the panic metric and either log or append errors based on the shouldFail parameter. +func createDeclarativeValidationPanicHandler(ctx context.Context, errs *field.ErrorList, shouldFail bool, validationIdentifier string) func() { logger := klog.FromContext(ctx) return func() { if r := recover(); r != nil { @@ -271,11 +312,11 @@ func createDeclarativeValidationPanicHandler(ctx context.Context, errs *field.Er validationmetrics.Metrics.IncDeclarativeValidationPanicMetric(validationIdentifier) const errorFmt = "panic during declarative validation: %v" - if takeover { - // If takeover is enabled, output as a validation error as authoritative validator panicked and validation should error + if shouldFail { + // If shouldFail is enabled, output as a validation error as authoritative validator panicked and validation should error *errs = append(*errs, field.InternalError(nil, fmt.Errorf(errorFmt, r))) } else { - // if takeover not enabled, log the panic as an error message + // if shouldFail not enabled, log the panic as an error message logger.Error(nil, fmt.Sprintf(errorFmt, r)) } } @@ -285,15 +326,15 @@ func createDeclarativeValidationPanicHandler(ctx context.Context, errs *field.Er // panicSafeValidateFunc wraps an validation function with panic recovery logic. // The returned function will execute the wrapped function and handle any panics by // incrementing the panic metric, and logging an error message -// if takeover=false, and adding a validation error if takeover=true. +// if shouldFail=false, and adding a validation error if shouldFail=true. func panicSafeValidateFunc( - validateUpdateFunc func(ctx context.Context, scheme *runtime.Scheme, obj, oldObj runtime.Object, o *validationConfigOption) field.ErrorList, - takeover bool, validationIdentifier string, + validateFunc func(ctx context.Context, scheme *runtime.Scheme, obj, oldObj runtime.Object, o *validationConfigOption) field.ErrorList, + shouldFail bool, validationIdentifier string, ) func(ctx context.Context, scheme *runtime.Scheme, obj, oldObj runtime.Object, o *validationConfigOption) field.ErrorList { return func(ctx context.Context, scheme *runtime.Scheme, obj, oldObj runtime.Object, o *validationConfigOption) (errs field.ErrorList) { - defer createDeclarativeValidationPanicHandler(ctx, &errs, takeover, validationIdentifier)() + defer createDeclarativeValidationPanicHandler(ctx, &errs, shouldFail, validationIdentifier)() - return validateUpdateFunc(ctx, scheme, obj, oldObj, o) + return validateFunc(ctx, scheme, obj, oldObj, o) } } @@ -334,16 +375,25 @@ func metricIdentifier(ctx context.Context, scheme *runtime.Scheme, obj runtime.O return identifier, errs } -// ValidateDeclarativelyWithMigrationChecks is a helper function that encapsulates the logic for running declarative validation. -// It checks if the DeclarativeValidation feature gate is enabled, generates a validation identifier, -// runs declarative validation, compares the results with imperative validation, and merges the errors if takeover is enabled. +// ValidateDeclarativelyWithMigrationChecks executes declarative validation and implements the Validation Lifecycle strategy. +// It manages the transition from handwritten (HV) to declarative (DV) validation by controlling enforcement: +// - Standard: Enforced if declarativeEnforcement is set. HV counterparts are expected to be deleted from source. +// - Beta: Enforced if declarativeEnforcement is set AND DeclarativeValidationBeta feature gate is enabled. +// When enforced, corresponding HV errors are filtered out. Otherwise, DV is shadowed. +// - Alpha: Always shadowed; HV remains authoritative. +// +// Mismatches between HV and DV are logged if the DeclarativeValidation gate is enabled. +// Mismatch checking is limited to Alpha and Beta stages when explicit enforcement is active. +// +// For testing purposes, WithAllDeclarativeEnforcedForTest can be used to enforce all declarative validations +// regardless of feature gates and filter all covered handwritten validations. func ValidateDeclarativelyWithMigrationChecks(ctx context.Context, scheme *runtime.Scheme, obj, oldObj runtime.Object, errs field.ErrorList, opType operation.Type, configOpts ...ValidationConfig) field.ErrorList { - if !utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) { - return errs - } - - takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover) - + declarativeValidationEnabled := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) + betaEnabled := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationBeta) + // allDeclarativeEnforced indicates that we should check all declarative errors for testing purposes. + allDeclarativeEnforced := ctx.Value(allDeclarativeEnforcedKey) == true + // These errors must be errors returned by the handwritten validation. + errs = errs.MarkFromImperative() validationIdentifier, err := metricIdentifier(ctx, scheme, obj, opType) if err != nil { // Log the error, but continue with the best-effort identifier. @@ -353,24 +403,92 @@ func ValidateDeclarativelyWithMigrationChecks(ctx context.Context, scheme *runti // Directly create the config and call the core validation logic. cfg := &validationConfigOption{ opType: opType, - takeover: takeover, validationIdentifier: validationIdentifier, } for _, opt := range configOpts { opt(cfg) } + // Short-circuit if neither DeclarativeValidation is enabled nor the object is explicitly configured for declarative enforcement. + if !declarativeValidationEnabled && !cfg.declarativeEnforcement && !allDeclarativeEnforced { + return errs + } + // Call the panic-safe wrapper with the real validation function. - declarativeErrs := panicSafeValidateFunc(validateDeclaratively, cfg.takeover, cfg.validationIdentifier)(ctx, scheme, obj, oldObj, cfg) + // We should fail if validation is enforced. + declarativeErrs := panicSafeValidateFunc(validateDeclaratively, cfg.declarativeEnforcement, cfg.validationIdentifier)(ctx, scheme, obj, oldObj, cfg) + + if declarativeValidationEnabled { + // Log mismatches. + // When explicit strategy is used (declarativeEnforcement), Standard errors are authoritative + // and may not have handwritten counterparts (e.g., in new APIs). + // We only mismatch check Alpha and Beta errors in this mode. + mismatchCandidateErrs := declarativeErrs + if cfg.declarativeEnforcement { + mismatchCandidateErrs = nil + for _, err := range declarativeErrs { + if err.IsAlpha() || err.IsBeta() { + mismatchCandidateErrs = append(mismatchCandidateErrs, err) + } + } + } - compareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier, cfg.normalizationRules) - if takeover { - errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...) + // We pass betaEnabled (and enforcement) as the takeover flag to avoid changing logic elsewhere for now. + compareDeclarativeErrorsAndEmitMismatches(ctx, errs, mismatchCandidateErrs, cfg.declarativeEnforcement && betaEnabled, validationIdentifier, cfg.normalizationRules) + } + + if !cfg.declarativeEnforcement && !allDeclarativeEnforced { + // If enforcement is not enabled, we shadow declarative errors with hand-written ones, so we return early here. + return errs + } + + // Filter HV errors + errs = filterHandwrittenErrors(errs, allDeclarativeEnforced, betaEnabled) + + // Append Enforced DV errors + for _, dvErr := range declarativeErrs { + if allDeclarativeEnforced { + errs = append(errs, dvErr) + continue + } + switch { + case dvErr.Type == field.ErrorTypeInternal: + errs = append(errs, dvErr) + case dvErr.IsBeta(): + if betaEnabled { + errs = append(errs, dvErr) + } + case !dvErr.IsAlpha(): + errs = append(errs, dvErr) // Standard + } } return errs } +func filterHandwrittenErrors(errs field.ErrorList, allDeclarativeEnforced, betaEnabled bool) field.ErrorList { + // We remove HV errors that are covered by declarative validation AND are enforced. + return errs.Filter(func(e error) bool { + var fe *field.Error + if !errors.As(e, &fe) || !fe.CoveredByDeclarative { + return false + } + + if allDeclarativeEnforced { + return true + } + + // Explicit Strategy + if fe.IsBeta() { + // Beta validations are enforced only if the Beta feature gate is enabled. + return betaEnabled + } + // For Standard validations, we keep the handwritten error for now to avoid losing coverage + // before it is deleted from source. Alpha validations are always shadowed (kept). + return false + }) +} + // RecordDuplicateValidationErrors increments a metric and log the error when duplicate validation errors are found. func RecordDuplicateValidationErrors(ctx context.Context, qualifiedKind schema.GroupKind, errs field.ErrorList) { logger := klog.FromContext(ctx) diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/validate_test.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/validate_test.go index b1218e0191879..51f5295145633 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/validate_test.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/validate_test.go @@ -33,8 +33,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/version" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/validation" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/testutil" "k8s.io/klog/v2" @@ -171,7 +175,8 @@ func TestValidateDeclaratively(t *testing.T) { } else { cfg.opType = operation.Update } - results := panicSafeValidateFunc(validateDeclaratively, cfg.takeover, cfg.validationIdentifier)(ctx, scheme, tc.object, tc.oldObject, cfg) + // takeover is not used here, passing false for shouldFail + results := panicSafeValidateFunc(validateDeclaratively, false, cfg.validationIdentifier)(ctx, scheme, tc.object, tc.oldObject, cfg) matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() matcher.Test(t, tc.expected, results) }) @@ -205,184 +210,147 @@ func (p Pod) DeepCopyObject() runtime.Object { // scenarios across imperative and declarative errors for // the gatherDeclarativeValidationMismatches function func TestGatherDeclarativeValidationMismatches(t *testing.T) { - replicasPath := field.NewPath("spec").Child("replicas") - minReadySecondsPath := field.NewPath("spec").Child("minReadySeconds") - selectorPath := field.NewPath("spec").Child("selector") + pathStandard := field.NewPath("spec", "standard") + pathBeta := field.NewPath("spec", "beta") + pathAlpha := field.NewPath("spec", "alpha") + pathOther := field.NewPath("spec", "other") - errA := field.Invalid(replicasPath, nil, "regular error A") - errB := field.Invalid(minReadySecondsPath, -1, "covered error B").WithOrigin("minimum") - coveredErrB := field.Invalid(minReadySecondsPath, -1, "covered error B").WithOrigin("minimum") - errBWithDiffDetail := field.Invalid(minReadySecondsPath, -1, "covered error B - different detail").WithOrigin("minimum") - errBWithDiffPath := field.Invalid(field.NewPath("spec").Child("fakeminReadySeconds"), -1, "covered error B").WithOrigin("minimum") - coveredErrB.CoveredByDeclarative = true - errC := field.Invalid(replicasPath, nil, "covered error C").WithOrigin("minimum") - coveredErrC := field.Invalid(replicasPath, nil, "covered error C").WithOrigin("minimum") - coveredErrC.CoveredByDeclarative = true - errCWithDiffOrigin := field.Invalid(replicasPath, nil, "covered error C").WithOrigin("maximum") - errD := field.Invalid(selectorPath, nil, "regular error D") + // Standard Errors + errHVStandard := field.Invalid(pathStandard, "val", "impStandard").MarkCoveredByDeclarative().WithOrigin("min") + errDVStandard := field.Invalid(pathStandard, "val", "decStandard").WithOrigin("min") + + // Beta Errors + errHVBeta := field.Invalid(pathBeta, "val", "impBeta").MarkCoveredByDeclarative().MarkBeta().WithOrigin("min") + + // Alpha Errors + errDVAlpha := field.Invalid(pathAlpha, "val", "decAlpha").MarkAlpha().WithOrigin("min") + // Note: No HV Alpha by convention, so if it exists it would be a mismatch if DV is missing. + // But we usually don't mark HV as covered for Alpha rules. + + // Normalization / Fuzzy match helpers + errDVStandardDiffDetail := field.Invalid(pathStandard, "val", "decStandardDiffDetail").WithOrigin("min") + errDVStandardDiffPath := field.Invalid(field.NewPath("spec", "standardAliased"), "val", "decStandard").WithOrigin("min") + + const ( + authoritativeMsg = "This difference should not affect system operation since hand written validation is authoritative." + disableBetaMsg = "Consider disabling the DeclarativeValidationBeta feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes." + ) testCases := []struct { name string imperativeErrors field.ErrorList declarativeErrors field.ErrorList - takeover bool + enforced bool expectMismatches bool expectDetailsContaining []string normalizedRules []field.NormalizationRule }{ { - name: "Declarative and imperative return 0 errors - no mismatch", - imperativeErrors: field.ErrorList{}, - declarativeErrors: field.ErrorList{}, - takeover: false, - expectMismatches: false, - expectDetailsContaining: []string{}, + name: "No errors - no mismatch", + imperativeErrors: field.ErrorList{}, + declarativeErrors: field.ErrorList{}, + expectMismatches: false, }, { - name: "Declarative returns multiple errors with different origins, errors match - no mismatch", - imperativeErrors: field.ErrorList{ - errA, - coveredErrB, - coveredErrC, - errD, - }, - declarativeErrors: field.ErrorList{ - errB, - errC, - }, - takeover: false, - expectMismatches: false, - expectDetailsContaining: []string{}, + name: "Clean match - no mismatch", + imperativeErrors: field.ErrorList{errHVStandard}, + declarativeErrors: field.ErrorList{errDVStandard}, + expectMismatches: false, }, { - name: "Declarative returns multiple errors with different origins, errors don't match - mismatch case", - imperativeErrors: field.ErrorList{ - errA, - coveredErrB, - coveredErrC, - }, - declarativeErrors: field.ErrorList{ - errB, - errCWithDiffOrigin, - }, - takeover: true, - expectMismatches: true, + name: "Mismatch: Missing DV (Standard) + Enforced -> Disable Beta Gate", + imperativeErrors: field.ErrorList{errHVStandard}, + declarativeErrors: field.ErrorList{}, + enforced: true, + expectMismatches: true, expectDetailsContaining: []string{ - "Unexpected difference between hand written validation and declarative validation error results", "unmatched error(s) found", - "extra error(s) found", - "replicas", - "Consider disabling the DeclarativeValidationTakeover feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes", - }, - }, - { - name: "Declarative and imperative return exactly 1 error, errors match - no mismatch", - imperativeErrors: field.ErrorList{ - coveredErrB, - }, - declarativeErrors: field.ErrorList{ - errB, + "spec.standard", + disableBetaMsg, }, - takeover: false, - expectMismatches: false, - expectDetailsContaining: []string{}, }, { - name: "Declarative and imperative exactly 1 error, errors don't match - mismatch", - imperativeErrors: field.ErrorList{ - coveredErrB, - }, - declarativeErrors: field.ErrorList{ - errC, - }, - takeover: false, - expectMismatches: true, + name: "Mismatch: Extra DV (Standard) + Not Enforced -> HV Authoritative", + imperativeErrors: field.ErrorList{}, + declarativeErrors: field.ErrorList{errDVStandard}, + enforced: false, + expectMismatches: true, expectDetailsContaining: []string{ - "Unexpected difference between hand written validation and declarative validation error results", - "unmatched error(s) found", - "minReadySeconds", "extra error(s) found", - "replicas", - "This difference should not affect system operation since hand written validation is authoritative", + "spec.standard", + authoritativeMsg, }, }, { - name: "Declarative returns 0 errors, imperative returns 1 covered error - mismatch", - imperativeErrors: field.ErrorList{ - coveredErrB, - }, + name: "Mismatch: Missing DV (Beta) + Enforced -> Disable Beta Gate", + imperativeErrors: field.ErrorList{errHVBeta}, declarativeErrors: field.ErrorList{}, - takeover: true, + enforced: true, expectMismatches: true, expectDetailsContaining: []string{ - "Unexpected difference between hand written validation and declarative validation error results", "unmatched error(s) found", - "minReadySeconds", - "Consider disabling the DeclarativeValidationTakeover feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes", + "spec.beta", + disableBetaMsg, }, }, { - name: "Declarative returns 0 errors, imperative returns 1 uncovered error - no mismatch", - imperativeErrors: field.ErrorList{ - errB, - }, - declarativeErrors: field.ErrorList{}, - takeover: false, - expectMismatches: false, - expectDetailsContaining: []string{}, - }, - { - name: "Declarative returns 1 error, imperative returns 0 error - mismatch", - imperativeErrors: field.ErrorList{}, - declarativeErrors: field.ErrorList{ - errB, - }, - takeover: false, - expectMismatches: true, + name: "Mismatch: Extra DV (Alpha) + Enforced -> HV Authoritative (Override)", + imperativeErrors: field.ErrorList{}, + declarativeErrors: field.ErrorList{errDVAlpha}, + enforced: true, + expectMismatches: true, expectDetailsContaining: []string{ - "Unexpected difference between hand written validation and declarative validation error results", "extra error(s) found", - "minReadySeconds", - "This difference should not affect system operation since hand written validation is authoritative", + "spec.alpha", + authoritativeMsg, }, }, { - name: "Declarative returns 1 error, imperative returns 3 matching errors - no mismatch", - imperativeErrors: field.ErrorList{ - coveredErrB, - }, - declarativeErrors: field.ErrorList{ - errB, - errB, - errBWithDiffDetail, - }, - takeover: false, - expectMismatches: false, - expectDetailsContaining: []string{}, + name: "Fuzzy matching (different detail) - no mismatch", + imperativeErrors: field.ErrorList{errHVStandard}, + declarativeErrors: field.ErrorList{errDVStandardDiffDetail}, + expectMismatches: false, }, { - name: "Field normalization, errors don't match - mismatch", - imperativeErrors: field.ErrorList{ - coveredErrB, - }, - declarativeErrors: field.ErrorList{ - errBWithDiffPath, - }, + name: "Field normalization - no mismatch", + imperativeErrors: field.ErrorList{errHVStandard}, + declarativeErrors: field.ErrorList{errDVStandardDiffPath}, normalizedRules: []field.NormalizationRule{ { - Regexp: regexp.MustCompile(`spec.fakeminReadySeconds`), - Replacement: "spec.minReadySeconds", + Regexp: regexp.MustCompile(`spec.standardAliased`), + Replacement: "spec.standard", }, }, - takeover: false, - expectMismatches: false, - expectDetailsContaining: []string{}, + expectMismatches: false, + }, + { + name: "Multiple mismatches - combined log info", + imperativeErrors: field.ErrorList{errHVBeta}, + declarativeErrors: field.ErrorList{errDVAlpha}, + enforced: true, + expectMismatches: true, + expectDetailsContaining: []string{ + "unmatched error(s) found", + "spec.beta", + disableBetaMsg, + "extra error(s) found", + "spec.alpha", + authoritativeMsg, + }, + }, + { + name: "Uncovered HV error - no mismatch (ignored)", + imperativeErrors: field.ErrorList{ + field.Invalid(pathOther, "val", "other").WithOrigin("min"), + }, + declarativeErrors: field.ErrorList{}, + expectMismatches: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - details := gatherDeclarativeValidationMismatches(tc.imperativeErrors, tc.declarativeErrors, tc.takeover, tc.normalizedRules) + details := gatherDeclarativeValidationMismatches(tc.imperativeErrors, tc.declarativeErrors, tc.enforced, tc.normalizedRules) // Check if mismatches were found if expected if tc.expectMismatches && len(details) == 0 { t.Errorf("Expected mismatches but got none") @@ -418,7 +386,7 @@ func TestCompareDeclarativeErrorsAndEmitMismatches(t *testing.T) { name string imperativeErrs field.ErrorList declarativeErrs field.ErrorList - takeover bool + enforced bool expectLogs bool expectedRegex string }{ @@ -426,16 +394,16 @@ func TestCompareDeclarativeErrorsAndEmitMismatches(t *testing.T) { name: "mismatched errors, log info", imperativeErrs: field.ErrorList{coveredErrB}, declarativeErrs: field.ErrorList{errA}, - takeover: true, + enforced: true, expectLogs: true, // logs have a prefix of the form - E0309 21:05:33.865030 1926106 validate.go:199] - expectedRegex: "E.*Unexpected difference between hand written validation and declarative validation error results.*Consider disabling the DeclarativeValidationTakeover feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes", + expectedRegex: "E.*Unexpected difference between hand written validation and declarative validation error results.*Consider disabling the DeclarativeValidationBeta feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes", }, { name: "matching errors, don't log info", imperativeErrs: field.ErrorList{coveredErrB}, declarativeErrs: field.ErrorList{errB}, - takeover: true, + enforced: true, expectLogs: false, expectedRegex: "", }, @@ -449,7 +417,7 @@ func TestCompareDeclarativeErrorsAndEmitMismatches(t *testing.T) { defer klog.LogToStderr(true) ctx := context.Background() - compareDeclarativeErrorsAndEmitMismatches(ctx, tc.imperativeErrs, tc.declarativeErrs, tc.takeover, "test_validationIdentifier", nil) + compareDeclarativeErrorsAndEmitMismatches(ctx, tc.imperativeErrs, tc.declarativeErrs, tc.enforced, "test_validationIdentifier", nil) klog.Flush() logOutput := buf.String() @@ -476,11 +444,11 @@ func TestWithRecover(t *testing.T) { obj := &runtime.Unknown{} testCases := []struct { - name string - validateFn func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList - takeoverEnabled bool - wantErrs field.ErrorList - expectLogRegex string + name string + validateFn func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList + enforcementEnabled bool + wantErrs field.ErrorList + expectLogRegex string }{ { name: "no panic", @@ -489,28 +457,28 @@ func TestWithRecover(t *testing.T) { field.Invalid(field.NewPath("field"), "value", "reason"), } }, - takeoverEnabled: false, + enforcementEnabled: false, wantErrs: field.ErrorList{ field.Invalid(field.NewPath("field"), "value", "reason"), }, expectLogRegex: "", }, { - name: "panic with takeover disabled", + name: "panic with enforcement disabled", validateFn: func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList { panic("test panic") }, - takeoverEnabled: false, - wantErrs: nil, + enforcementEnabled: false, + wantErrs: nil, // logs have a prefix of the form - E0309 21:05:33.865030 1926106 validate.go:199] expectLogRegex: "E.*panic during declarative validation: test panic", }, { - name: "panic with takeover enabled", + name: "panic with enforcement enabled", validateFn: func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList { panic("test panic") }, - takeoverEnabled: true, + enforcementEnabled: true, wantErrs: field.ErrorList{ field.InternalError(nil, fmt.Errorf("panic during declarative validation: test panic")), }, @@ -521,9 +489,9 @@ func TestWithRecover(t *testing.T) { validateFn: func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList { return nil }, - takeoverEnabled: false, - wantErrs: nil, - expectLogRegex: "", + enforcementEnabled: false, + wantErrs: nil, + expectLogRegex: "", }, } @@ -534,9 +502,9 @@ func TestWithRecover(t *testing.T) { klog.LogToStderr(false) defer klog.LogToStderr(true) - // Pass the takeover flag to panicSafeValidateFunc instead of relying on the feature gate - wrapped := panicSafeValidateFunc(tc.validateFn, tc.takeoverEnabled, "test_validationIdentifier") - gotErrs := wrapped(ctx, scheme, obj, nil, &validationConfigOption{opType: operation.Create, options: options, takeover: tc.takeoverEnabled}) + // Pass the enforcement flag to panicSafeValidateFunc + wrapped := panicSafeValidateFunc(tc.validateFn, tc.enforcementEnabled, "test_validationIdentifier") + gotErrs := wrapped(ctx, scheme, obj, nil, &validationConfigOption{opType: operation.Create, options: options}) klog.Flush() logOutput := buf.String() @@ -570,11 +538,11 @@ func TestWithRecoverUpdate(t *testing.T) { oldObj := &runtime.Unknown{} testCases := []struct { - name string - validateFn func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList - takeoverEnabled bool - wantErrs field.ErrorList - expectLogRegex string + name string + validateFn func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList + enforcementEnabled bool + wantErrs field.ErrorList + expectLogRegex string }{ { name: "no panic", @@ -583,28 +551,28 @@ func TestWithRecoverUpdate(t *testing.T) { field.Invalid(field.NewPath("field"), "value", "reason"), } }, - takeoverEnabled: false, + enforcementEnabled: false, wantErrs: field.ErrorList{ field.Invalid(field.NewPath("field"), "value", "reason"), }, expectLogRegex: "", }, { - name: "panic with takeover disabled", + name: "panic with enforcement disabled", validateFn: func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList { panic("test update panic") }, - takeoverEnabled: false, - wantErrs: nil, + enforcementEnabled: false, + wantErrs: nil, // logs have a prefix of the form - E0309 21:05:33.865030 1926106 validate.go:199] expectLogRegex: "E.*panic during declarative validation: test update panic", }, { - name: "panic with takeover enabled", + name: "panic with enforcement enabled", validateFn: func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList { panic("test update panic") }, - takeoverEnabled: true, + enforcementEnabled: true, wantErrs: field.ErrorList{ field.InternalError(nil, fmt.Errorf("panic during declarative validation: test update panic")), }, @@ -615,9 +583,9 @@ func TestWithRecoverUpdate(t *testing.T) { validateFn: func(context.Context, *runtime.Scheme, runtime.Object, runtime.Object, *validationConfigOption) field.ErrorList { return nil }, - takeoverEnabled: false, - wantErrs: nil, - expectLogRegex: "", + enforcementEnabled: false, + wantErrs: nil, + expectLogRegex: "", }, } @@ -628,9 +596,9 @@ func TestWithRecoverUpdate(t *testing.T) { klog.LogToStderr(false) defer klog.LogToStderr(true) - // Pass the takeover flag to panicSafeValidateUpdateFunc instead of relying on the feature gate - wrapped := panicSafeValidateFunc(tc.validateFn, tc.takeoverEnabled, "test_validationIdentifier") - gotErrs := wrapped(ctx, scheme, obj, oldObj, &validationConfigOption{opType: operation.Update, options: options, takeover: tc.takeoverEnabled}) + // Pass the enforcement flag to panicSafeValidateUpdateFunc + wrapped := panicSafeValidateFunc(tc.validateFn, tc.enforcementEnabled, "test_validationIdentifier") + gotErrs := wrapped(ctx, scheme, obj, oldObj, &validationConfigOption{opType: operation.Update, options: options}) klog.Flush() logOutput := buf.String() @@ -823,3 +791,157 @@ func TestMetricIdentifier(t *testing.T) { }) } } + +func TestValidateDeclarativelyWithMigrationChecks(t *testing.T) { + // Standard Lifecycle (Enforced by default in explicit strategy) + // Standard HV error marked as covered. In a fully migrated state, this should be deleted from source. + // We include it here to verify it persists (duplicate) if not deleted, rather than being implicitly filtered. + errHVStandardCovered := field.Forbidden(field.NewPath("spec", "standard"), "imperative standard").MarkCoveredByDeclarative() + errDVStandard := field.Forbidden(field.NewPath("spec", "standard"), "decStandard") + + // Additional Declarative (No HV counterpart) + errDVAdditional := field.Invalid(field.NewPath("spec", "additional"), "decAdditional", "declarative additional") + + // Beta Lifecycle (Gated by DeclarativeValidationBeta) + errHVBetaCovered := field.Forbidden(field.NewPath("spec", "beta"), "imperative beta").MarkCoveredByDeclarative().MarkBeta() + errDVBeta := field.Invalid(field.NewPath("spec", "beta"), "decBeta", "declarative beta").MarkBeta() + + // Alpha Lifecycle (Shadowed) + // Alpha rules should NOT mark HV as covered, so HV remains authoritative. + errHVAlpha := field.Forbidden(field.NewPath("spec", "alpha"), "imperative alpha") + errDVAlpha := field.Invalid(field.NewPath("spec", "alpha"), "decAlpha", "declarative alpha").MarkAlpha() + + testCases := []struct { + name string + dvFeatureEnabled bool + declarativeEnforcement bool + betaGateEnabled bool + imperativeErrors field.ErrorList + declarativeErrors field.ErrorList + expectedErrors field.ErrorList + shouldPanic bool + }{ + { + name: "Feature Disabled, Not Enforced -> Skips declarative, Returns HV", + imperativeErrors: field.ErrorList{errHVStandardCovered}, + declarativeErrors: field.ErrorList{errDVStandard}, + expectedErrors: field.ErrorList{errHVStandardCovered}, + }, + { + name: "Feature Disabled, Enforced -> Enforces Standard (HV kept+DV returned, duplicate expected if HV not deleted)", + declarativeEnforcement: true, + imperativeErrors: field.ErrorList{errHVStandardCovered}, + declarativeErrors: field.ErrorList{errDVStandard, errDVAdditional}, + expectedErrors: field.ErrorList{errHVStandardCovered, errDVStandard, errDVAdditional}, + }, + { + name: "Feature Enabled, Not Enforced -> Returns imperative (Shadow Mode)", + dvFeatureEnabled: true, + imperativeErrors: field.ErrorList{errHVStandardCovered}, + declarativeErrors: field.ErrorList{errDVStandard}, + expectedErrors: field.ErrorList{errHVStandardCovered}, + }, + { + name: "Feature Enabled, Enforced -> Enforces Standard (HV kept+DV returned, duplicate expected if HV not deleted)", + dvFeatureEnabled: true, + declarativeEnforcement: true, + imperativeErrors: field.ErrorList{errHVStandardCovered}, + declarativeErrors: field.ErrorList{errDVStandard, errDVAdditional}, + expectedErrors: field.ErrorList{errHVStandardCovered, errDVStandard, errDVAdditional}, + }, + { + name: "Feature Disabled, Enforced, Panics -> Returns InternalError", + declarativeEnforcement: true, + imperativeErrors: field.ErrorList{errHVStandardCovered}, + shouldPanic: true, + // Standard HV is kept. Panic error appended. + expectedErrors: append(field.ErrorList{errHVStandardCovered}, field.InternalError(nil, fmt.Errorf("panic during declarative validation: test panic"))), + }, + { + name: "Feature Enabled, Enforced, InternalError -> Returns InternalError", + dvFeatureEnabled: true, + declarativeEnforcement: true, + imperativeErrors: field.ErrorList{errHVStandardCovered}, + declarativeErrors: field.ErrorList{field.InternalError(nil, fmt.Errorf("internal error"))}, + // Standard HV kept. Internal error appended. + expectedErrors: field.ErrorList{errHVStandardCovered, field.InternalError(nil, fmt.Errorf("internal error"))}, + }, + { + name: "Enforced, Beta Gate Enabled -> Enforces Beta (HV removed, DV returned)", + dvFeatureEnabled: true, + declarativeEnforcement: true, + betaGateEnabled: true, + imperativeErrors: field.ErrorList{errHVBetaCovered}, + declarativeErrors: field.ErrorList{errDVBeta}, + expectedErrors: field.ErrorList{errDVBeta}, + }, + { + name: "Enforced, Beta Gate Disabled -> Shadows Beta (HV kept, DV hidden)", + dvFeatureEnabled: true, + declarativeEnforcement: true, + betaGateEnabled: false, + imperativeErrors: field.ErrorList{errHVBetaCovered}, + declarativeErrors: field.ErrorList{errDVBeta}, + expectedErrors: field.ErrorList{errHVBetaCovered}, + }, + { + name: "Enforced, Alpha -> Shadows Alpha (HV kept, DV hidden)", + dvFeatureEnabled: true, + declarativeEnforcement: true, + betaGateEnabled: true, + imperativeErrors: field.ErrorList{errHVAlpha}, + declarativeErrors: field.ErrorList{errDVAlpha}, + expectedErrors: field.ErrorList{errHVAlpha}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Set feature gate + if !tc.dvFeatureEnabled { + featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.35")) + } else { + // Only set Beta gate if we are not emulating an older version where it doesn't exist + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidationBeta, tc.betaGateEnabled) + } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidation, tc.dvFeatureEnabled) + + // Setup scheme for this run + localScheme := runtime.NewScheme() + localScheme.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &v1.Pod{}) + localScheme.AddValidationFunc(&v1.Pod{}, func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList { + if tc.shouldPanic { + panic("test panic") + } + return tc.declarativeErrors + }) + + // Setup context + ctx := genericapirequest.WithRequestInfo(context.Background(), &genericapirequest.RequestInfo{ + APIGroup: "", + APIVersion: "v1", + Resource: "pods", + }) + + obj := &v1.Pod{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, + ObjectMeta: metav1.ObjectMeta{Name: "test-pod"}, + } + + // Copy imperative errors because they might be modified/appended to + inputErrs := make(field.ErrorList, len(tc.imperativeErrors)) + copy(inputErrs, tc.imperativeErrors) + + opts := []ValidationConfig{} + if tc.declarativeEnforcement { + opts = append(opts, WithDeclarativeEnforcement()) + } + + gotErrs := ValidateDeclarativelyWithMigrationChecks(ctx, localScheme, obj, nil, inputErrs, operation.Create, opts...) + + if !equalErrorLists(gotErrs, tc.expectedErrors) { + t.Errorf("Expected errors: %v, got: %v", tc.expectedErrors, gotErrs) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index efe10e81a696f..c09051a1bb065 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -19,6 +19,7 @@ package server import ( "context" "crypto/sha256" + "crypto/tls" "encoding/base32" "fmt" "net" @@ -382,6 +383,13 @@ type SecureServingInfo struct { // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). CipherSuites []uint16 + // CurvePreferences optionally specifies the set of allowed key exchange mechanisms for the server. + // The order of the list is ignored, and key exchange mechanisms + // are chosen by Go from this list using an internal preference order. + // If empty, the default Go curves will be used. + // Values are from the Go crypto/tls CurveID constants (https://golang.org/pkg/crypto/tls/#CurveID). + CurvePreferences []tls.CurveID + // HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client. // A value of zero means to use the default provided by golang's HTTP/2 support. HTTP2MaxStreamsPerConnection int @@ -590,6 +598,18 @@ func (c *Config) AddHealthChecks(healthChecks ...healthz.HealthChecker) { c.AddReadyzChecks(healthChecks...) } +// AddHealthzChecks adds the provided health checks to our config to be exposed by the +// healthz endpoint of our configured apiserver. +func (c *Config) AddHealthzChecks(healthChecks ...healthz.HealthChecker) { + c.HealthzChecks = append(c.HealthzChecks, healthChecks...) +} + +// AddLivezChecks adds the provided health checks to our config to be exposed by the +// livez endpoint of our configured apiserver. +func (c *Config) AddLivezChecks(healthChecks ...healthz.HealthChecker) { + c.LivezChecks = append(c.LivezChecks, healthChecks...) +} + // AddReadyzChecks adds a health check to our config to be exposed by the readyz endpoint // of our configured apiserver. func (c *Config) AddReadyzChecks(healthChecks ...healthz.HealthChecker) { @@ -874,6 +894,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G UnprotectedDebugSocket: debugSocket, listedPathProvider: apiServerHandler, + Flagz: c.Flagz, minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, ShutdownTimeout: c.RequestTimeout, diff --git a/staging/src/k8s.io/apiserver/pkg/server/config_test.go b/staging/src/k8s.io/apiserver/pkg/server/config_test.go index 577b99fc22b4d..f497a9b2509de 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config_test.go @@ -223,6 +223,7 @@ func TestNewWithDelegate(t *testing.T) { "/readyz/poststarthook/storage-object-count-tracker-hook", "/readyz/poststarthook/wrapping-post-start-hook", "/readyz/shutdown", + "/statusz", } checkExpectedPathsAtRoot(server.URL, expectedPaths, t) diff --git a/staging/src/k8s.io/apiserver/pkg/server/deleted_kinds_test.go b/staging/src/k8s.io/apiserver/pkg/server/deleted_kinds_test.go index 20f38a98c95b6..c6a5accdd72e2 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/deleted_kinds_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/deleted_kinds_test.go @@ -23,12 +23,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/sets" apimachineryversion "k8s.io/apimachinery/pkg/util/version" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/server/resourceconfig" serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/utils/dump" "github.com/stretchr/testify/require" ) diff --git a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/configmap_cafile_content.go b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/configmap_cafile_content.go index dbe9e375f26c8..5af3dbe866de7 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/configmap_cafile_content.go +++ b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/configmap_cafile_content.go @@ -199,7 +199,7 @@ func (c *ConfigMapCAController) RunOnce(ctx context.Context) error { // Run starts the kube-apiserver and blocks until stopCh is closed. func (c *ConfigMapCAController) Run(ctx context.Context, workers int) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer c.queue.ShutDown() klog.InfoS("Starting controller", "name", c.name) diff --git a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_cafile_content.go b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_cafile_content.go index 0fcf82bd0dbd3..2f21951349ffc 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_cafile_content.go +++ b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_cafile_content.go @@ -155,7 +155,7 @@ func (c *DynamicFileCAContent) RunOnce(ctx context.Context) error { // Run starts the controller and blocks until stopCh is closed. func (c *DynamicFileCAContent) Run(ctx context.Context, workers int) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer c.queue.ShutDown() klog.InfoS("Starting controller", "name", c.name) diff --git a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_serving_content.go b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_serving_content.go index e0dd0474b1cc7..268fe70b0d5fc 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_serving_content.go +++ b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_serving_content.go @@ -129,7 +129,7 @@ func (c *DynamicCertKeyPairContent) RunOnce(ctx context.Context) error { // Run starts the controller and blocks until context is killed. func (c *DynamicCertKeyPairContent) Run(ctx context.Context, workers int) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer c.queue.ShutDown() klog.InfoS("Starting controller", "name", c.name) diff --git a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/tlsconfig_test.go b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/tlsconfig_test.go index 322cd7141824a..0fe37628fb2e1 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/tlsconfig_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates/tlsconfig_test.go @@ -20,7 +20,7 @@ import ( "reflect" "testing" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY----- diff --git a/staging/src/k8s.io/apiserver/pkg/server/egressselector/config_test.go b/staging/src/k8s.io/apiserver/pkg/server/egressselector/config_test.go index 3eb72683ff05f..441bc3224dd53 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/egressselector/config_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/egressselector/config_test.go @@ -302,6 +302,52 @@ spec: expectedResult: nil, expectedError: ptr.To("invalid service configuration object \"DaemonSet\""), }, + { + name: "v1beta1 with tlsServerName", + createFile: true, + contents: ` +apiVersion: apiserver.k8s.io/v1beta1 +kind: EgressSelectorConfiguration +egressSelections: +- name: "cluster" + connection: + proxyProtocol: "HTTPConnect" + transport: + tcp: + url: "https://proxy.kube-system.svc.cluster.local:8443" + tlsConfig: + caBundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt" + clientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key" + clientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt" + tlsServerName: "konnectivity-server.example.com" +`, + expectedResult: &apiserver.EgressSelectorConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "", + APIVersion: "", + }, + EgressSelections: []apiserver.EgressSelection{ + { + Name: "cluster", + Connection: apiserver.Connection{ + ProxyProtocol: "HTTPConnect", + Transport: &apiserver.Transport{ + TCP: &apiserver.TCPTransport{ + URL: "https://proxy.kube-system.svc.cluster.local:8443", + TLSConfig: &apiserver.TLSConfig{ + CABundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt", + ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key", + ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt", + TLSServerName: "konnectivity-server.example.com", + }, + }, + }, + }, + }, + }, + }, + expectedError: nil, + }, } for _, tc := range testcases { diff --git a/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector.go b/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector.go index 27408fe15825d..b898ff3270c0d 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector.go +++ b/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector.go @@ -300,6 +300,7 @@ func getTLSConfig(t *apiserver.TLSConfig) (*tls.Config, error) { return &tls.Config{ Certificates: []tls.Certificate{clientCerts}, RootCAs: certPool, + ServerName: t.TLSServerName, }, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector_test.go b/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector_test.go index 896b2446183f6..875cdacd8c867 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/egressselector/egress_selector_test.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" "net" + "os" + "path/filepath" "strings" "testing" "time" @@ -29,6 +31,7 @@ import ( utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apiserver/pkg/apis/apiserver" "k8s.io/apiserver/pkg/server/egressselector/metrics" + certutil "k8s.io/client-go/util/cert" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/testutil" testingclock "k8s.io/utils/clock/testing" @@ -343,3 +346,61 @@ konnectivity_network_proxy_client_client_connections{status="dialing"} 1 }) } } + +func TestGetTLSConfig(t *testing.T) { + tempDir := t.TempDir() + + certPEM, keyPEM, err := certutil.GenerateSelfSignedCertKey("localhost", nil, nil) + if err != nil { + t.Fatalf("Failed to generate test certificates: %v", err) + } + + certPath := filepath.Join(tempDir, "cert.crt") + keyPath := filepath.Join(tempDir, "cert.key") + if err := os.WriteFile(certPath, certPEM, 0600); err != nil { + t.Fatalf("Failed to write cert file: %v", err) + } + if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil { + t.Fatalf("Failed to write key file: %v", err) + } + + testcases := []struct { + name string + tlsConfig *apiserver.TLSConfig + expectedServerName string + }{ + { + name: "with TLSServerName set", + tlsConfig: &apiserver.TLSConfig{ + CABundle: certPath, + ClientCert: certPath, + ClientKey: keyPath, + TLSServerName: "custom-server.example.com", + }, + expectedServerName: "custom-server.example.com", + }, + { + name: "without TLSServerName (empty)", + tlsConfig: &apiserver.TLSConfig{ + CABundle: certPath, + ClientCert: certPath, + ClientKey: keyPath, + TLSServerName: "", + }, + expectedServerName: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + tlsConfig, err := getTLSConfig(tc.tlsConfig) + if err != nil { + t.Fatalf("getTLSConfig returned unexpected error: %v", err) + } + + if tlsConfig.ServerName != tc.expectedServerName { + t.Errorf("expected ServerName %q, got %q", tc.expectedServerName, tlsConfig.ServerName) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/filters/priority-and-fairness_test.go b/staging/src/k8s.io/apiserver/pkg/server/filters/priority-and-fairness_test.go index 18d99292ccd89..c218a57e1f0d1 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/filters/priority-and-fairness_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/filters/priority-and-fairness_test.go @@ -712,7 +712,7 @@ func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) { firstRequestPathPanic, secondRequestPathShouldWork := "/request/panic-as-designed", "/request/should-succeed-as-expected" firstHandlerDoneCh, secondHandlerDoneCh := make(chan struct{}), make(chan struct{}) requestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - headerMatcher.inspect(t, w, fsName, plName) + headerMatcher.inspect(t, w, r.Context(), fsName, plName) switch { case r.URL.Path == firstRequestPathPanic: close(firstHandlerDoneCh) @@ -785,7 +785,7 @@ func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) { rquestTimesOutPath := "/request/time-out-as-designed" reqHandlerCompletedCh, callerRoundTripDoneCh := make(chan struct{}), make(chan struct{}) requestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - headerMatcher.inspect(t, w, fsName, plName) + headerMatcher.inspect(t, w, r.Context(), fsName, plName) if r.URL.Path == rquestTimesOutPath { defer close(reqHandlerCompletedCh) @@ -858,7 +858,7 @@ func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) { reqHandlerErrCh, callerRoundTripDoneCh := make(chan error, 1), make(chan struct{}) rquestTimesOutPath := "/request/time-out-as-designed" requestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - headerMatcher.inspect(t, w, fsName, plName) + headerMatcher.inspect(t, w, r.Context(), fsName, plName) if r.URL.Path == rquestTimesOutPath { <-callerRoundTripDoneCh @@ -937,7 +937,7 @@ func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) { rquestTimesOutPath := "/request/time-out-as-designed" reqHandlerErrCh, callerRoundTripDoneCh := make(chan error, 1), make(chan struct{}) requestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - headerMatcher.inspect(t, w, fsName, plName) + headerMatcher.inspect(t, w, r.Context(), fsName, plName) if r.URL.Path == rquestTimesOutPath { @@ -1016,7 +1016,7 @@ func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) { firstReqHandlerErrCh, firstReqInProgressCh := make(chan error, 1), make(chan struct{}) firstReqRoundTripDoneCh, secondReqRoundTripDoneCh := make(chan struct{}), make(chan struct{}) requestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - headerMatcher.inspect(t, w, fsName, plName) + headerMatcher.inspect(t, w, r.Context(), fsName, plName) switch { case r.URL.Path == firstRequestTimesOutPath: close(firstReqInProgressCh) @@ -1188,28 +1188,42 @@ func newHTTP2ServerWithClient(handler http.Handler, clientTimeout time.Duration) type headerMatcher struct{} // verifies that the expected flow schema and priority level UIDs are attached to the header. -func (m *headerMatcher) inspect(t *testing.T, w http.ResponseWriter, expectedFS, expectedPL string) { +func (m *headerMatcher) inspect(t *testing.T, w http.ResponseWriter, ctx context.Context, expectedFS, expectedPL string) { t.Helper() - err := func() error { - if w == nil { - return fmt.Errorf("expected a non nil HTTP response") - } - key := flowcontrol.ResponseHeaderMatchedFlowSchemaUID - if value := w.Header().Get(key); expectedFS != value { - return fmt.Errorf("expected HTTP header %s to have value %q, but got: %q", key, expectedFS, value) + if w == nil { + t.Errorf("expected a non nil HTTP response") + return + } + + checkHeader := func(key, expected string) { + actual := w.Header().Get(key) + if actual == expected { + return } - key = flowcontrol.ResponseHeaderMatchedPriorityLevelConfigurationUID - if value := w.Header().Get(key); expectedPL != value { - return fmt.Errorf("expected HTTP header %s to have value %q, but got %q", key, expectedPL, value) + // Header writes are best-effort once the request context is canceled. + if actual == "" { + select { + case <-ctx.Done(): + t.Logf("Skipping APF header assertion for %s: request context already done", key) + return + default: + } + + // The timeout filter may have already committed the response + // before APF had a chance to attach its headers. + if ctx.Err() != nil { + t.Logf("Skipping APF header assertion for %s: response committed after timeout", key) + return + } } - return nil - }() - if err == nil { - return + + t.Errorf("expected HTTP header %s to have value %q, but got: %q", key, expected, actual) } - t.Errorf("Expected APF headers to match, but got: %v", err) + + checkHeader(flowcontrol.ResponseHeaderMatchedFlowSchemaUID, expectedFS) + checkHeader(flowcontrol.ResponseHeaderMatchedPriorityLevelConfigurationUID, expectedPL) } // when a request panics, http2 resets the stream with an INTERNAL_ERROR message diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1alpha1/doc.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1alpha1/doc.go index ab7a48c210653..42d5fcef53697 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1alpha1/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1alpha1/doc.go @@ -18,5 +18,5 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:openapi-model-package=io.k8s.apiserver.pkg.server.flagz.api.v1alpha1 -// Package v1alpha1 contains API Schema definitions for the zpages v1alpha1 API group +// Package v1alpha1 contains API Schema definitions for the flagz v1alpha1 API group package v1alpha1 diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/doc.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/doc.go new file mode 100644 index 0000000000000..bc8cffc0a0e10 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apiserver.pkg.server.flagz.api.v1beta1 + +// Package v1beta1 contains API Schema definitions for the flagz v1beta1 API group +package v1beta1 diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/register.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/register.go new file mode 100644 index 0000000000000..f66acc7dd6c69 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/register.go @@ -0,0 +1,47 @@ +/* +Copyright 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 v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +const ( + GroupName = "config.k8s.io" + Version = "v1beta1" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version} + +var ( + // SchemeBuilder initializes a scheme builder + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme is a global function that adds this group's types to a scheme + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Flagz{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/types.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/types.go new file mode 100644 index 0000000000000..403436fdcf3b4 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/types.go @@ -0,0 +1,37 @@ +/* +Copyright 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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// Flagz is the structured response for the /flagz endpoint. +type Flagz struct { + // TypeMeta is the type metadata for the object. + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Flags contains the command-line flags and their values. + // The keys are the flag names and the values are the flag values, + // possibly with confidential values redacted. + // +optional + Flags map[string]string `json:"flags,omitempty"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..1d5e52605ccba --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,59 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Flagz) DeepCopyInto(out *Flagz) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Flags != nil { + in, out := &in.Flags, &out.Flags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Flagz. +func (in *Flagz) DeepCopy() *Flagz { + if in == nil { + return nil + } + out := new(Flagz) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Flagz) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.model_name.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.model_name.go new file mode 100644 index 0000000000000..d475a0aa64a58 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.model_name.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1beta1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Flagz) OpenAPIModelName() string { + return "io.k8s.apiserver.pkg.server.flagz.api.v1beta1.Flagz" +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go index de5dfe79fee56..cb6df7b5d6433 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go @@ -20,25 +20,44 @@ import ( "fmt" "net/http" "strings" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" - v1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" + "k8s.io/apiserver/pkg/endpoints/metrics" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/endpoints/responsewriter" + "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" + "k8s.io/apiserver/pkg/server/flagz/api/v1beta1" "k8s.io/apiserver/pkg/server/flagz/negotiate" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) -const ( - DefaultFlagzPath = "/flagz" - Kind = "Flagz" - GroupName = "config.k8s.io" - Version = "v1alpha1" +var ( + v1alpha1FlagzKind = v1alpha1.SchemeGroupVersion.WithKind("Flagz") + v1beta1FlagzKind = v1beta1.SchemeGroupVersion.WithKind("Flagz") + recognizedStructuredKinds = map[schema.GroupVersionKind]bool{ + v1alpha1FlagzKind: true, + v1beta1FlagzKind: true, + } ) +const DefaultFlagzPath = "/flagz" + +// flagzCodecFactory wraps a CodecFactory to filter out unsupported media types (like protobuf) +// from the supported media types list, so error messages only show actually supported types. +type flagzCodecFactory struct { + serializer.CodecFactory + supportedMediaTypes []runtime.SerializerInfo +} + type mux interface { Handle(path string, handler http.Handler) } @@ -47,7 +66,7 @@ type mux interface { func Install(m mux, componentName string, flagReader Reader, opts ...Option) { reg := ®istry{ reader: flagReader, - deprecatedVersionsMap: map[string]bool{}, + deprecatedVersionsMap: map[string]bool{"v1alpha1": true}, } for _, opt := range opts { opt(reg) @@ -55,10 +74,23 @@ func Install(m mux, componentName string, flagReader Reader, opts ...Option) { scheme := runtime.NewScheme() utilruntime.Must(v1alpha1.AddToScheme(scheme)) - codecFactory := serializer.NewCodecFactory( - scheme, + utilruntime.Must(v1beta1.AddToScheme(scheme)) + filteredCodecFactory, err := newFlagzCodecFactory(scheme, componentName, reg.reader) + if err != nil { + utilruntime.HandleError(err) + } + restrictions := negotiate.FlagzEndpointRestrictions{ + RecognizedStructuredKinds: recognizedStructuredKinds, + } + m.Handle(DefaultFlagzPath, handleFlagz(componentName, reg, filteredCodecFactory, restrictions)) +} + +// newFlagzCodecFactory creates a codec factory with the standard serializers for flagz, +// filtering out unsupported media types (e.g., protobuf). +func newFlagzCodecFactory(scheme *runtime.Scheme, componentName string, flagReader Reader) (*flagzCodecFactory, error) { + codecFactoryOpts := []serializer.CodecFactoryOptionsMutator{ serializer.WithSerializer(func(_ runtime.ObjectCreater, _ runtime.ObjectTyper) runtime.SerializerInfo { - textSerializer := flagzTextSerializer{componentName, reg.reader} + textSerializer := flagzTextSerializer{componentName, flagReader} return runtime.SerializerInfo{ MediaType: "text/plain", MediaTypeType: "text", @@ -68,16 +100,75 @@ func Install(m mux, componentName string, flagReader Reader, opts ...Option) { PrettySerializer: textSerializer, } }), - ) - m.Handle(DefaultFlagzPath, handleFlagz(componentName, reg, codecFactory, negotiate.FlagzEndpointRestrictions{})) + } + // TODO: remove this explicit check when https://github.com/kubernetes/enhancements/pull/5740 is implemented. + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { + codecFactoryOpts = append(codecFactoryOpts, serializer.WithSerializer(cbor.NewSerializerInfo)) + } + + codecFactory := serializer.NewCodecFactory(scheme, codecFactoryOpts...) + allTypes := codecFactory.SupportedMediaTypes() + filtered := make([]runtime.SerializerInfo, 0, len(allTypes)) + + var unknownTypes []string + for _, info := range allTypes { + switch info.MediaType { + // Supported media types + case "text/plain", runtime.ContentTypeJSON, runtime.ContentTypeYAML, runtime.ContentTypeCBOR: + filtered = append(filtered, info) + // Unsupported media types + case runtime.ContentTypeProtobuf: + continue + default: + unknownTypes = append(unknownTypes, info.MediaType) + } + } + + var err error + if len(unknownTypes) > 0 { + err = fmt.Errorf("flagz: unknown media type(s) %v, excluding from supported types", unknownTypes) + } + + return &flagzCodecFactory{ + CodecFactory: codecFactory, + supportedMediaTypes: filtered, + }, err +} + +func (f *flagzCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { + return f.supportedMediaTypes } func handleFlagz(componentName string, reg *registry, serializer runtime.NegotiatedSerializer, restrictions negotiate.FlagzEndpointRestrictions) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - obj := flagz(componentName, reg.reader) + requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(r.Context()) + if !ok { + requestReceivedTimestamp = time.Now() + } + delegate := &metrics.ResponseWriterDelegator{ResponseWriter: w} + w = responsewriter.WrapForHTTP1Or2(delegate) + + // Use MonitorRequest instead of InstrumentHandlerFunc because the group, + // version, and deprecated status depend on per-request content negotiation. + // For text/plain requests, group and version remain empty. For structured + // responses (JSON/YAML/CBOR), they are set to the negotiated API group and + // version (e.g., config.k8s.io/v1alpha1). + var group, version string + var deprecated bool + defer func() { + metrics.MonitorRequest(r, "GET", group, version, + "flagz", // resource + "", // subresource + "", // scope + componentName, // component + deprecated, + "", // removedRelease + delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp)) + }() + acceptHeader := r.Header.Get("Accept") if strings.TrimSpace(acceptHeader) == "" { - writePlainTextResponse(obj, serializer, w, reg) + writePlainTextResponse(v1beta1Flagz(componentName, reg.reader), serializer, w, reg) return } @@ -94,11 +185,10 @@ func handleFlagz(componentName string, reg *registry, serializer runtime.Negotia return } - var targetGV schema.GroupVersion switch serializerInfo.MediaType { - case "application/json": + case "application/json", "application/yaml", "application/cbor": if mediaType.Convert == nil { - err := fmt.Errorf("content negotiation failed: mediaType.Convert is nil for application/json") + err := fmt.Errorf("content negotiation failed: mediaType.Convert is nil for %s", serializerInfo.MediaType) utilruntime.HandleError(err) responsewriters.ErrorNegotiated( err, @@ -109,15 +199,17 @@ func handleFlagz(componentName string, reg *registry, serializer runtime.Negotia ) return } - targetGV = mediaType.Convert.GroupVersion() - if reg.deprecatedVersions()[targetGV.Version] { + group = mediaType.Convert.Group + version = mediaType.Convert.Version + deprecated = reg.deprecatedVersions()[version] + if deprecated { w.Header().Set("Warning", `299 - "This version of the flagz endpoint is deprecated. Please use a newer version."`) } + handleStructuredResponse(w, r, componentName, reg, serializer, restrictions, mediaType) case "text/plain": - writePlainTextResponse(obj, serializer, w, reg) - return + writePlainTextResponse(v1beta1Flagz(componentName, reg.reader), serializer, w, reg) default: - err = fmt.Errorf("content negotiation failed: unsupported media type '%s'", serializerInfo.MediaType) + err := fmt.Errorf("unsupported media type: %s/%s", serializerInfo.MediaType, serializerInfo.MediaTypeSubType) utilruntime.HandleError(err) responsewriters.ErrorNegotiated( err, @@ -126,10 +218,7 @@ func handleFlagz(componentName string, reg *registry, serializer runtime.Negotia w, r, ) - return } - - writeResponse(obj, serializer, targetGV, restrictions, w, r) } } @@ -170,7 +259,7 @@ func writePlainTextResponse(obj runtime.Object, serializer runtime.NegotiatedSer } } -func writeResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, targetGV schema.GroupVersion, restrictions negotiate.FlagzEndpointRestrictions, w http.ResponseWriter, r *http.Request) { +func writeStructuredResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, targetGV schema.GroupVersion, restrictions negotiate.FlagzEndpointRestrictions, w http.ResponseWriter, r *http.Request) { responsewriters.WriteObjectNegotiated( serializer, restrictions, @@ -183,12 +272,45 @@ func writeResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, ) } -func flagz(componentName string, flagReader Reader) *v1alpha1.Flagz { +func handleStructuredResponse(w http.ResponseWriter, r *http.Request, componentName string, reg *registry, serializer runtime.NegotiatedSerializer, restrictions negotiate.FlagzEndpointRestrictions, mediaType negotiation.MediaTypeOptions) { + switch *mediaType.Convert { + case v1alpha1FlagzKind: + writeStructuredResponse(v1alpha1Flagz(componentName, reg.reader), serializer, v1alpha1FlagzKind.GroupVersion(), restrictions, w, r) + case v1beta1FlagzKind: + writeStructuredResponse(v1beta1Flagz(componentName, reg.reader), serializer, v1beta1FlagzKind.GroupVersion(), restrictions, w, r) + default: + err := fmt.Errorf("unsupported media type: %s", mediaType.Convert.String()) + utilruntime.HandleError(err) + responsewriters.ErrorNegotiated( + err, + serializer, + schema.GroupVersion{}, + w, + r, + ) + } +} + +func v1alpha1Flagz(componentName string, flagReader Reader) *v1alpha1.Flagz { flags := flagReader.GetFlagz() return &v1alpha1.Flagz{ TypeMeta: metav1.TypeMeta{ - Kind: Kind, - APIVersion: fmt.Sprintf("%s/%s", GroupName, Version), + Kind: v1alpha1FlagzKind.Kind, + APIVersion: v1alpha1FlagzKind.GroupVersion().String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: componentName, + }, + Flags: flags, + } +} + +func v1beta1Flagz(componentName string, flagReader Reader) *v1beta1.Flagz { + flags := flagReader.GetFlagz() + return &v1beta1.Flagz{ + TypeMeta: metav1.TypeMeta{ + Kind: v1beta1FlagzKind.Kind, + APIVersion: v1beta1FlagzKind.GroupVersion().String(), }, ObjectMeta: metav1.ObjectMeta{ Name: componentName, diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go index 4f6e61484921c..5caeb45fd171c 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go @@ -27,8 +27,19 @@ import ( "github.com/google/go-cmp/cmp" "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + cbordirect "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/endpoints/metrics" + "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" + "k8s.io/apiserver/pkg/server/flagz/api/v1beta1" + utilfeature "k8s.io/apiserver/pkg/util/feature" cliflag "k8s.io/component-base/cli/flag" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" + "sigs.k8s.io/yaml" ) const wantTmpl = ` @@ -37,6 +48,8 @@ Warning: This endpoint is not meant to be machine parseable, has no formatting c ` func TestHandleFlagz(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) + fakeFlagName := "test-flag" fakeFlagValue := "test-value" fs := pflag.NewFlagSet("test", pflag.ContinueOnError) @@ -50,14 +63,14 @@ func TestHandleFlagz(t *testing.T) { } tests := []struct { - name string - acceptHeader string - componentName string - registry *registry - wantStatusCode int - wantBody string - wantJSONBody *v1alpha1.Flagz - wantWarning bool + name string + acceptHeader string + componentName string + registry *registry + wantStatusCode int + wantBody string + wantStructuredBody interface{} + wantWarning bool }{ { name: "valid request for text/plain", @@ -74,18 +87,18 @@ func TestHandleFlagz(t *testing.T) { ), }, { - name: "valid request for v1alpha1", - acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz", + name: "valid request for application/json", + acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz", componentName: "test-server", registry: ®istry{ reader: fakeReader, deprecatedVersionsMap: map[string]bool{}, }, wantStatusCode: http.StatusOK, - wantJSONBody: &v1alpha1.Flagz{ + wantStructuredBody: &v1beta1.Flagz{ TypeMeta: metav1.TypeMeta{ - Kind: Kind, - APIVersion: fmt.Sprintf("%s/%s", GroupName, Version), + Kind: "Flagz", + APIVersion: "config.k8s.io/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "test-server", @@ -104,10 +117,10 @@ func TestHandleFlagz(t *testing.T) { deprecatedVersionsMap: map[string]bool{"v1alpha1": true}, }, wantStatusCode: http.StatusOK, - wantJSONBody: &v1alpha1.Flagz{ + wantStructuredBody: &v1alpha1.Flagz{ TypeMeta: metav1.TypeMeta{ - Kind: Kind, - APIVersion: fmt.Sprintf("%s/%s", GroupName, Version), + Kind: "Flagz", + APIVersion: "config.k8s.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "test-server", @@ -118,6 +131,50 @@ func TestHandleFlagz(t *testing.T) { }, wantWarning: true, }, + { + name: "valid request for application/yaml", + acceptHeader: "application/yaml;v=v1beta1;g=config.k8s.io;as=Flagz", + componentName: "test-server", + registry: ®istry{ + reader: fakeReader, + deprecatedVersionsMap: map[string]bool{}, + }, + wantStatusCode: http.StatusOK, + wantStructuredBody: &v1beta1.Flagz{ + TypeMeta: metav1.TypeMeta{ + Kind: "Flagz", + APIVersion: "config.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-server", + }, + Flags: map[string]string{ + fakeFlagName: fakeFlagValue, + }, + }, + }, + { + name: "valid request for application/cbor", + acceptHeader: "application/cbor;v=v1beta1;g=config.k8s.io;as=Flagz", + componentName: "test-server", + registry: ®istry{ + reader: fakeReader, + deprecatedVersionsMap: map[string]bool{}, + }, + wantStatusCode: http.StatusOK, + wantStructuredBody: &v1beta1.Flagz{ + TypeMeta: metav1.TypeMeta{ + Kind: "Flagz", + APIVersion: "config.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-server", + }, + Flags: map[string]string{ + fakeFlagName: fakeFlagValue, + }, + }, + }, { name: "no accept header falls back to text/plain", acceptHeader: "", @@ -182,7 +239,7 @@ func TestHandleFlagz(t *testing.T) { }, { name: "unsupported application/json with missing params", - acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io", + acceptHeader: "application/json;v=v1beta1;g=config.k8s.io", componentName: "test-server", registry: ®istry{ reader: fakeReader, @@ -224,12 +281,19 @@ func TestHandleFlagz(t *testing.T) { } if tt.wantStatusCode == http.StatusOK { - if tt.wantJSONBody != nil { - var got v1alpha1.Flagz - if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil { - t.Fatalf("unexpected error while unmarshalling response: %v", err) + if tt.wantStructuredBody != nil { + var got interface{} + switch tt.wantStructuredBody.(type) { + case *v1alpha1.Flagz: + got = &v1alpha1.Flagz{} + case *v1beta1.Flagz: + got = &v1beta1.Flagz{} + default: + t.Fatalf("unexpected type for wantStructuredBody: %T", tt.wantStructuredBody) } - if diff := cmp.Diff(*tt.wantJSONBody, got); diff != "" { + unmarshalResponse(t, w.Header().Get("Content-Type"), w.Body.Bytes(), got) + + if diff := cmp.Diff(tt.wantStructuredBody, got); diff != "" { t.Errorf("Unexpected diff on response (-want,+got):\n%s", diff) } if tt.wantWarning { @@ -245,6 +309,26 @@ func TestHandleFlagz(t *testing.T) { } } +func unmarshalResponse(t *testing.T, contentType string, body []byte, got interface{}) { + t.Helper() + switch { + case strings.Contains(contentType, "application/json"): + if err := json.Unmarshal(body, got); err != nil { + t.Fatalf("unexpected error while unmarshalling JSON response: %v", err) + } + case strings.Contains(contentType, "application/yaml"): + if err := yaml.Unmarshal(body, got); err != nil { + t.Fatalf("unexpected error while unmarshalling YAML response: %v", err) + } + case strings.Contains(contentType, "application/cbor"): + if err := cbordirect.Unmarshal(body, got); err != nil { + t.Fatalf("unexpected error while unmarshalling CBOR response: %v", err) + } + default: + t.Fatalf("unexpected content type: %s", contentType) + } +} + func TestCache(t *testing.T) { fakeFlagName := "test-flag" fakeFlagValue := "test-value" @@ -291,3 +375,61 @@ func TestCache(t *testing.T) { t.Errorf("Unexpected diff on cached response (-want,+got):\n%s", diff) } } + +// TestNewFlagzCodecFactory ensures all media types in the codec factory +// are explicitly handled. If this test fails, a new media type was added +// to the codec factory and needs to be explicitly added to the supported +// or unsupported list in newFlagzCodecFactory. +func TestNewFlagzCodecFactory(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) + scheme := runtime.NewScheme() + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) + + _, err := newFlagzCodecFactory(scheme, "", nil) + if err != nil { + t.Fatalf("unknown media type(s) detected - update newFlagzCodecFactory to explicitly handle them: %v", err) + } +} + +func TestMetrics(t *testing.T) { + fs := pflag.NewFlagSet("test", pflag.ContinueOnError) + fs.String("test-flag", "test-value", "usage") + fakeReader := NamedFlagSetsReader{ + FlagSets: cliflag.NamedFlagSets{ + FlagSets: map[string]*pflag.FlagSet{ + "test": fs, + }, + }, + } + + mux := http.NewServeMux() + Install(mux, "test-server", fakeReader) + metrics.Register() + metrics.Reset() + + // text/plain request: group and version should be empty + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com%s", DefaultFlagzPath), nil) + if err != nil { + t.Errorf("%v", err) + } + mux.ServeHTTP(httptest.NewRecorder(), req) + + // structured request: group and version should reflect the negotiated version + req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com%s", DefaultFlagzPath), nil) + if err != nil { + t.Errorf("%v", err) + } + req.Header.Set("Accept", "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz") + mux.ServeHTTP(httptest.NewRecorder(), req) + + expected := strings.NewReader(` + # HELP apiserver_request_total [STABLE] Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code. + # TYPE apiserver_request_total counter + apiserver_request_total{code="200",component="test-server",dry_run="",group="",resource="flagz",scope="",subresource="",system_client="",verb="GET",version=""} 1 + apiserver_request_total{code="200",component="test-server",dry_run="",group="config.k8s.io",resource="flagz",scope="",subresource="",system_client="",verb="GET",version="v1alpha1"} 1 +`) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, expected, "apiserver_request_total"); err != nil { + t.Error(err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/negotiate/negotiation.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/negotiate/negotiation.go index f3edc8f3c1ada..ec2d28e67c14c 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/flagz/negotiate/negotiation.go +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/negotiate/negotiation.go @@ -20,16 +20,21 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -// FlagzEndpointRestrictions implements content negotiation restrictions for the z-pages. +// FlagzEndpointRestrictions implements content negotiation restrictions for the flagz endpoint. // It is used to validate and restrict which GroupVersionKinds are allowed for structured responses. -type FlagzEndpointRestrictions struct{} +type FlagzEndpointRestrictions struct { + RecognizedStructuredKinds map[schema.GroupVersionKind]bool +} -// AllowsMediaTypeTransform checks if the provided GVK is supported for structured z-page responses. -func (FlagzEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool { +// AllowsMediaTypeTransform checks if the provided GVK is supported for structured flagz responses. +func (f FlagzEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool { if mimeType == "text" && mimeSubType == "plain" { return gvk == nil } - return isStructured(gvk) + if gvk != nil { + return f.RecognizedStructuredKinds[*gvk] + } + return false } func (FlagzEndpointRestrictions) AllowsServerVersion(string) bool { @@ -39,15 +44,3 @@ func (FlagzEndpointRestrictions) AllowsServerVersion(string) bool { func (FlagzEndpointRestrictions) AllowsStreamSchema(s string) bool { return false } - -func isStructured(gvk *schema.GroupVersionKind) bool { - if gvk != nil { - if gvk.Group == "config.k8s.io" && gvk.Version == "v1alpha1" { - if gvk.Kind == "Flagz" { - return true - } - } - } - - return false -} diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/testing/testing.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/testing/testing.go new file mode 100644 index 0000000000000..8b3edd65b3259 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/testing/testing.go @@ -0,0 +1,120 @@ +/* +Copyright 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 testing + +import ( + "encoding/json" + "strings" + "testing" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" + "k8s.io/apiserver/pkg/server/flagz/api/v1beta1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func VerifyStructuredResponse(t *testing.T, acceptHeader string, body []byte, warnings []string, want interface{}, wantDeprecationHeader bool) { + t.Helper() + + unmarshal := unmarshalFunc(t, acceptHeader) + wantTypeMeta, wantName, wantFlags := wantFields(t, want) + gotTypeMeta, gotName, gotFlags := gotFields(t, unmarshal, body, wantTypeMeta.APIVersion) + + if gotName != wantName { + t.Errorf("name mismatch: got %q, want %q", gotName, wantName) + } + if gotTypeMeta != wantTypeMeta { + t.Errorf("type meta mismatch: got %v, want %v", gotTypeMeta, wantTypeMeta) + } + for k, v := range wantFlags { + gotV, ok := gotFlags[k] + if !ok { + t.Errorf("missing flag %q", k) + continue + } + if gotV != v { + t.Errorf("flag %q match: got %q, want %q", k, gotV, v) + } + } + + foundWarning := false + for _, w := range warnings { + if strings.Contains(w, "deprecated") { + foundWarning = true + break + } + } + if foundWarning != wantDeprecationHeader { + t.Errorf("deprecation header mismatch: got %v, want %v", foundWarning, wantDeprecationHeader) + } +} + +func unmarshalFunc(t *testing.T, acceptHeader string) func([]byte, interface{}) error { + switch { + case strings.Contains(acceptHeader, "application/json"): + return json.Unmarshal + case strings.Contains(acceptHeader, "application/yaml"): + return yaml.Unmarshal + case strings.Contains(acceptHeader, "application/cbor"): + return cbor.Unmarshal + default: + t.Fatalf("unexpected Accept header: %q", acceptHeader) + } + return nil +} + +func wantFields(t *testing.T, want interface{}) (metav1.TypeMeta, string, map[string]string) { + t.Helper() + switch w := want.(type) { + case *v1alpha1.Flagz: + return w.TypeMeta, w.Name, w.Flags + case *v1beta1.Flagz: + return w.TypeMeta, w.Name, w.Flags + default: + t.Fatalf("unexpected type for want: %T", want) + return metav1.TypeMeta{}, "", nil + } +} + +func gotFields(t *testing.T, unmarshal func([]byte, interface{}) error, body []byte, apiVersion string) (metav1.TypeMeta, string, map[string]string) { + var gotName string + var gotTypeMeta metav1.TypeMeta + var gotFlags map[string]string + switch apiVersion { + case "config.k8s.io/v1alpha1": + var got v1alpha1.Flagz + if err := unmarshal(body, &got); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + gotName = got.Name + gotTypeMeta = got.TypeMeta + gotFlags = got.Flags + case "config.k8s.io/v1beta1": + var got v1beta1.Flagz + if err := unmarshal(body, &got); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + gotName = got.Name + gotTypeMeta = got.TypeMeta + gotFlags = got.Flags + default: + t.Fatalf("unexpected API version: %q", apiVersion) + } + return gotTypeMeta, gotName, gotFlags +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index 0cb1007495741..c821dbfa52cc6 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -50,6 +50,7 @@ import ( discoveryendpoint "k8s.io/apiserver/pkg/endpoints/discovery/aggregated" "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/server/flagz" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/routes" "k8s.io/apiserver/pkg/server/statusz" @@ -114,6 +115,9 @@ type GenericAPIServer struct { // LoopbackClientConfig is a config for a privileged loopback connection to the API server LoopbackClientConfig *restclient.Config + // Flagz is used to set up flagz endpoint. + Flagz flagz.Reader + // minRequestTimeout is how short the request timeout can be. This is used to build the RESTHandler minRequestTimeout time.Duration @@ -467,8 +471,15 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer { } s.installReadyz() + componentName := "apiserver" + if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentFlagz) { + if s.Flagz != nil { + flagz.Install(s.Handler.NonGoRestfulMux, componentName, s.Flagz) + } + } + // statusz is installed last so that it can list all the paths that have been registered if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) { - statusz.Install(s.Handler.NonGoRestfulMux, "apiserver", statusz.NewRegistry(s.EffectiveVersion, statusz.WithListedPaths(s.ListedPaths()))) + statusz.Install(s.Handler.NonGoRestfulMux, componentName, statusz.NewRegistry(s.EffectiveVersion, statusz.WithListedPaths(s.ListedPaths()))) } return preparedGenericAPIServer{s} @@ -537,7 +548,7 @@ func (s preparedGenericAPIServer) RunWithContext(ctx context.Context) error { // If UDS profiling is enabled, start a local http server listening on that socket if s.UnprotectedDebugSocket != nil { go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) klog.Error(s.UnprotectedDebugSocket.RunWithContext(ctx)) }() } @@ -593,7 +604,7 @@ func (s preparedGenericAPIServer) RunWithContext(ctx context.Context) error { Listener: s.SecureServingInfo.Listener, lateStopCh: lateStopCh, } - unexpectedRequestsEventf.Store(s.Eventf) + unexpectedRequestsEventf.Store(eventfFunc(s.Eventf)) // close socket after delayed stopCh shutdownTimeout := s.ShutdownTimeout @@ -763,6 +774,11 @@ func (s preparedGenericAPIServer) RunWithContext(ctx context.Context) error { klog.V(1).Info("[graceful-termination] apiserver is exiting") s.Eventf(corev1.EventTypeNormal, "TerminationGracefulTerminationFinished", "All pending requests processed") + // Release the global reference to s.Eventf so the server object graph can + // be garbage collected. The TLS transport cache relies on GC to cancel cert + // rotation goroutines via runtime.AddCleanup (ClientsAllowTLSCacheGC). + unexpectedRequestsEventf.Store(eventfFunc(nil)) + return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/server/hooks.go b/staging/src/k8s.io/apiserver/pkg/server/hooks.go index 150b40b4755ad..3a828061e8966 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/hooks.go +++ b/staging/src/k8s.io/apiserver/pkg/server/hooks.go @@ -195,8 +195,7 @@ func (s *GenericAPIServer) isPostStartHookRegistered(name string) bool { func runPostStartHook(name string, entry postStartHookEntry, context PostStartHookContext) { var err error func() { - // don't let the hook *accidentally* panic and kill the server - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(context) err = entry.hook(context) }() // if the hook intentionally wants to kill server, let it. diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/admission.go b/staging/src/k8s.io/apiserver/pkg/server/options/admission.go index b41f97ec968ab..ec09f0d564002 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/admission.go @@ -157,7 +157,7 @@ func (a *AdmissionOptions) ApplyTo( discoveryRESTMapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) genericInitializer := initializer.New(kubeClient, dynamicClient, informers, c.Authorization.Authorizer, features, effectiveVersion, c.DrainedNotify(), NewAdmissionRESTMapper(discoveryRESTMapper)) - initializersChain := admission.PluginInitializers{genericInitializer} + initializersChain := admission.PluginInitializers{initializer.NewAPIServerIDInitializer(c.APIServerID), genericInitializer} initializersChain = append(initializersChain, pluginInitializers...) admissionPostStartHook := func(hookContext server.PostStartHookContext) error { diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go index 81e4899f0850d..6698c73a2d3c0 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/resourceconfig" serverstore "k8s.io/apiserver/pkg/server/storage" @@ -70,20 +71,22 @@ func (s *APIEnablementOptions) Validate(registries ...GroupRegistry) []error { errors := []error{} if s.RuntimeConfig[resourceconfig.APIAll] == "false" && len(s.RuntimeConfig) == 1 { // Do not allow only set api/all=false, in such case apiserver startup has no meaning. - return append(errors, fmt.Errorf("invalid key with only %v=false", resourceconfig.APIAll)) + return append(errors, fmt.Errorf("invalid runtime-config with only %v=false", resourceconfig.APIAll)) } - groups, err := resourceconfig.ParseGroups(s.RuntimeConfig) + groupVersions, err := resourceconfig.ParseGroupVersions(s.RuntimeConfig) if err != nil { return append(errors, err) } - for _, registry := range registries { - // filter out known groups - groups = unknownGroups(groups, registry) + unknownGroupVersions := sets.New[string]() + for _, groupVersion := range groupVersions { + if !isGroupRegistered(groupVersion.Group, registries) { + unknownGroupVersions.Insert(groupVersion.String()) + } } - if len(groups) != 0 { - errors = append(errors, fmt.Errorf("unknown api groups %s", strings.Join(groups, ","))) + if len(unknownGroupVersions) != 0 { + errors = append(errors, fmt.Errorf("unknown api groups %s", strings.Join(sets.List(unknownGroupVersions), ","))) } return errors @@ -129,18 +132,17 @@ func (s *APIEnablementOptions) ApplyTo(c *server.Config, defaultResourceConfig * return err } -func unknownGroups(groups []string, registry GroupRegistry) []string { - unknownGroups := []string{} - for _, group := range groups { - if !registry.IsGroupRegistered(group) { - unknownGroups = append(unknownGroups, group) - } - } - return unknownGroups -} - // GroupRegistry provides a method to check whether given group is registered. type GroupRegistry interface { - // IsRegistered returns true if given group is registered. + // IsGroupRegistered returns true if given group is registered. IsGroupRegistered(group string) bool } + +func isGroupRegistered(group string, registries []GroupRegistry) bool { + for _, registry := range registries { + if registry.IsGroupRegistered(group) { + return true + } + } + return false +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go index b93771633f1ae..06978f9ff2143 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go @@ -48,9 +48,9 @@ func TestAPIEnablementOptionsValidate(t *testing.T) { name: "test when options is nil", }, { - name: "test when invalid key with only api/all=false", + name: "test when invalid runtime-config with only api/all=false", runtimeConfig: cliflag.ConfigurationMap{"api/all": "false"}, - expectErr: "invalid key with only api/all=false", + expectErr: "invalid runtime-config with only api/all=false", }, { name: "test when ConfigurationMap key is invalid", @@ -59,13 +59,17 @@ func TestAPIEnablementOptionsValidate(t *testing.T) { }, { name: "test when unknown api groups", - runtimeConfig: cliflag.ConfigurationMap{"api/v1": "true"}, - expectErr: "unknown api groups", + runtimeConfig: cliflag.ConfigurationMap{"api/v1": "true", "api/v1beta2": "true"}, + expectErr: "unknown api groups api/v1,api/v1beta2", }, { name: "test when valid api groups", runtimeConfig: cliflag.ConfigurationMap{"apiregistration.k8s.io/v1beta1": "true"}, }, + { + name: "test when invalid api groups", + runtimeConfig: cliflag.ConfigurationMap{"apiregistration.k8s.io/v1beta1": "true"}, + }, } testGroupRegistry := fakeGroupRegistry{} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/audit.go b/staging/src/k8s.io/apiserver/pkg/server/options/audit.go index af5b06a5bd75a..74e2e71971b12 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/audit.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/audit.go @@ -154,6 +154,10 @@ func NewAuditOptions() *AuditOptions { GroupVersionString: "audit.k8s.io/v1", }, LogOptions: AuditLogOptions{ + MaxSize: 100, + MaxAge: 366, + MaxBackups: 100, + Format: pluginlog.FormatJson, BatchOptions: AuditBatchOptions{ Mode: ModeBlocking, @@ -436,11 +440,11 @@ func (o *AuditLogOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.Path, "audit-log-path", o.Path, "If set, all requests coming to the apiserver will be logged to this file. '-' means standard out.") fs.IntVar(&o.MaxAge, "audit-log-maxage", o.MaxAge, - "The maximum number of days to retain old audit log files based on the timestamp encoded in their filename.") + "The maximum number of days to retain old audit log files based on the timestamp encoded in their filename. Setting a value of 0 means old audit log files are not removed based on age.") fs.IntVar(&o.MaxBackups, "audit-log-maxbackup", o.MaxBackups, "The maximum number of old audit log files to retain. Setting a value of 0 will mean there's no restriction on the number of files.") fs.IntVar(&o.MaxSize, "audit-log-maxsize", o.MaxSize, - "The maximum size in megabytes of the audit log file before it gets rotated.") + "The maximum size in megabytes of the audit log file before it gets rotated. Setting to 0 disables rotation (not recommended).") fs.StringVar(&o.Format, "audit-log-format", o.Format, "Format of saved audits. \"legacy\" indicates 1-line text format for each event."+ " \"json\" indicates structured json format. Known formats are "+ @@ -506,6 +510,10 @@ func (o *AuditLogOptions) getWriter() (io.Writer, error) { return nil, fmt.Errorf("ensureLogFile: %w", err) } + if o.MaxSize == 0 { + return os.OpenFile(o.Path, os.O_APPEND|os.O_WRONLY, 0644) + } + return &lumberjack.Logger{ Filename: o.Path, MaxAge: o.MaxAge, diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/audit_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/audit_test.go index 78fdc72105503..16425d88b576b 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/audit_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/audit_test.go @@ -38,10 +38,10 @@ func TestAuditValidOptions(t *testing.T) { auditPath := filepath.Join(tmpDir, "audit") webhookConfig := makeTmpWebhookConfig(t) - defer os.Remove(webhookConfig) + defer func() { _ = os.Remove(webhookConfig) }() policy := makeTmpPolicy(t) - defer os.Remove(policy) + defer func() { _ = os.Remove(policy) }() testCases := []struct { name string @@ -177,14 +177,46 @@ func TestAuditValidOptions(t *testing.T) { if options.LogOptions.Path == "-" { assert.Equal(t, os.Stdout, w) assert.NoFileExists(t, options.LogOptions.Path) + } else if options.LogOptions.MaxSize == 0 { + // When MaxSize is 0, we return a raw file writer (os.File) for unlimited size + file, ok := w.(*os.File) + assert.True(t, ok, "Writer should be of type *os.File when MaxSize is 0") + assert.FileExists(t, options.LogOptions.Path) + assert.NoError(t, file.Close()) } else { - assert.IsType(t, (*lumberjack.Logger)(nil), w) + logger, ok := w.(*lumberjack.Logger) + assert.True(t, ok, "Writer should be of type *lumberjack.Logger") assert.FileExists(t, options.LogOptions.Path) + assert.Equal(t, options.LogOptions.MaxSize, logger.MaxSize) } }) } } +func TestAuditLogMaxSizeZero(t *testing.T) { + tmpDir := t.TempDir() + auditPath := filepath.Join(tmpDir, "audit") + policy := makeTmpPolicy(t) + defer func() { _ = os.Remove(policy) }() + + o := NewAuditOptions() + o.LogOptions.Path = auditPath + o.PolicyFile = policy + o.LogOptions.MaxSize = 0 + + w, err := o.LogOptions.getWriter() + require.NoError(t, err) + require.NotNil(t, w) + + // When MaxSize is 0, we return a raw file writer (os.File) for unlimited size + file, ok := w.(*os.File) + require.True(t, ok, "Should be an os.File when MaxSize is 0") + + // Verify that the file was opened correctly + require.NotNil(t, file) + require.NoError(t, file.Close()) +} + func TestAuditInvalidOptions(t *testing.T) { tmpDir := t.TempDir() auditPath := filepath.Join(tmpDir, "audit") diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go index e340c9d2ce562..2d481bc6f7b62 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go @@ -282,8 +282,6 @@ func getTransformerOverridesAndKMSPluginProbes(ctx context.Context, config *apis // For each entry in the configuration for _, resourceConfig := range config.Resources { - resourceConfig := resourceConfig - transformers, p, used, err := prefixTransformersAndProbes(ctx, resourceConfig, apiServerID) if err != nil { return nil, nil, nil, err @@ -292,7 +290,6 @@ func getTransformerOverridesAndKMSPluginProbes(ctx context.Context, config *apis // For each resource, create a list of providers to use for _, resource := range resourceConfig.Resources { - resource := resource gr := schema.ParseGroupResource(resource) // check if resource is masked by *.group rule @@ -317,8 +314,6 @@ func getTransformerOverridesAndKMSPluginProbes(ctx context.Context, config *apis transformers := make(map[schema.GroupResource]storagevalue.Transformer, len(resourceToPrefixTransformer)) for gr, transList := range resourceToPrefixTransformer { - gr := gr - transList := transList transformers[gr] = storagevalue.NewPrefixTransformers(fmt.Errorf("no matching prefix found"), transList...) } @@ -567,7 +562,6 @@ func prefixTransformersAndProbes(ctx context.Context, config apiserver.ResourceC var kmsUsed kmsState for _, provider := range config.Providers { - provider := provider var ( transformer storagevalue.PrefixTransformer transformerErr error @@ -624,7 +618,6 @@ func aesPrefixTransformer(config *apiserver.AESConfiguration, fn blockTransforme return result, fmt.Errorf("aes provider has no valid keys") } for _, key := range config.Keys { - key := key if key.Name == "" { return result, fmt.Errorf("key with invalid name provided") } @@ -636,7 +629,6 @@ func aesPrefixTransformer(config *apiserver.AESConfiguration, fn blockTransforme keyTransformers := []storagevalue.PrefixTransformer{} for _, keyData := range config.Keys { - keyData := keyData key, err := base64.StdEncoding.DecodeString(keyData.Secret) if err != nil { return result, fmt.Errorf("could not obtain secret for named key %s: %w", keyData.Name, err) @@ -677,7 +669,6 @@ func secretboxPrefixTransformer(config *apiserver.SecretboxConfiguration) (stora return result, fmt.Errorf("secretbox provider has no valid keys") } for _, key := range config.Keys { - key := key if key.Name == "" { return result, fmt.Errorf("key with invalid name provided") } @@ -689,7 +680,6 @@ func secretboxPrefixTransformer(config *apiserver.SecretboxConfiguration) (stora keyTransformers := []storagevalue.PrefixTransformer{} for _, keyData := range config.Keys { - keyData := keyData key, err := base64.StdEncoding.DecodeString(keyData.Secret) if err != nil { return result, fmt.Errorf("could not obtain secret for named key %s: %s", keyData.Name, err) diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config_test.go index f509b989660c9..f95edbc5eff40 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config_test.go @@ -2229,7 +2229,6 @@ func TestGetEncryptionConfigHash(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/controller/controller.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/controller/controller.go index 5c249bcc08bfd..9314d6d125e77 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/controller/controller.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/controller/controller.go @@ -92,7 +92,7 @@ func NewDynamicEncryptionConfiguration( // Run starts the controller and blocks until ctx is canceled. func (d *DynamicEncryptionConfigContent) Run(ctx context.Context) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) klog.InfoS("Starting controller", "name", d.name) defer klog.InfoS("Shutting down controller", "name", d.name) @@ -101,7 +101,7 @@ func (d *DynamicEncryptionConfigContent) Run(ctx context.Context) { wg.Add(1) go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer wg.Done() defer d.queue.ShutDown() <-ctx.Done() @@ -109,7 +109,7 @@ func (d *DynamicEncryptionConfigContent) Run(ctx context.Context) { wg.Add(1) go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer wg.Done() d.runWorker(ctx) }() diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go b/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go index a80c3f9ed42f9..9721797787a58 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go @@ -105,7 +105,7 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error { if err := o.Traces.ApplyTo(config.Config.EgressSelector, &config.Config); err != nil { return err } - if err := o.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil { + if err := o.SecureServing.ApplyToConfig(&config.Config); err != nil { return err } if err := o.Authentication.ApplyTo(&config.Config.Authentication, config.SecureServing, config.OpenAPIConfig); err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving.go index 21a2736e10739..fbcb0724701e9 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving.go @@ -64,6 +64,14 @@ type SecureServingOptions struct { // CipherSuites is the list of allowed cipher suites for the server. // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). CipherSuites []string + // CurvePreferences is the set of allowed key exchange mechanisms for the server, + // specified as numeric Go crypto/tls CurveID values. + // 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 empty, the default Go curves will be used. + CurvePreferences []int32 // MinTLSVersion is the minimum TLS version supported. // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). MinTLSVersion string @@ -189,6 +197,15 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) { "Preferred values: "+strings.Join(tlsCipherPreferredValues, ", ")+". \n"+ "Insecure values: "+strings.Join(tlsCipherInsecureValues, ", ")+".") + fs.Int32SliceVar(&s.CurvePreferences, "tls-curve-preferences", s.CurvePreferences, + "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.") + tlsPossibleVersions := cliflag.TLSPossibleVersions() fs.StringVar(&s.MinTLSVersion, "tls-min-version", s.MinTLSVersion, "Minimum TLS version supported. "+ @@ -317,6 +334,14 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error c.CipherSuites = cipherSuites } + if len(s.CurvePreferences) != 0 { + curvePreferences, err := cliflag.TLSCurvePreferences(s.CurvePreferences) + if err != nil { + return err + } + c.CurvePreferences = curvePreferences + } + var err error c.MinTLSVersion, err = cliflag.TLSVersion(s.MinTLSVersion) if err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix.go index 483eac79b235a..6f484ac3764d2 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2020 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix_test.go index bfc4815329f49..db23538ebae65 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving_unix_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2020 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving_windows.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving_windows.go index 844de618833ac..f8fb141098779 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving_windows.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows /* Copyright 2020 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback.go index 980ddc61a48a7..2dea7d2db19a5 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback.go @@ -18,26 +18,48 @@ package options import ( "fmt" + "net/http" "time" "github.com/google/uuid" "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/dynamiccertificates" + "k8s.io/apiserver/pkg/server/healthz" "k8s.io/client-go/rest" certutil "k8s.io/client-go/util/cert" + "k8s.io/utils/clock" ) type SecureServingOptionsWithLoopback struct { *SecureServingOptions + clock clock.PassiveClock } func (o *SecureServingOptions) WithLoopback() *SecureServingOptionsWithLoopback { - return &SecureServingOptionsWithLoopback{o} + return &SecureServingOptionsWithLoopback{ + SecureServingOptions: o, + clock: clock.RealClock{}, + } } +// Set a validity period of approximately 3 years for the loopback certificate +// to avoid kube-apiserver disruptions due to certificate expiration. +// When this certificate expires, restarting kube-apiserver will automatically +// regenerate a new certificate with fresh validity dates. +const maxAge = (3*365 + 1) * 24 * time.Hour + // ApplyTo fills up serving information in the server configuration. func (s *SecureServingOptionsWithLoopback) ApplyTo(secureServingInfo **server.SecureServingInfo, loopbackClientConfig **rest.Config) error { + return s.applyTo(secureServingInfo, loopbackClientConfig, nil) +} + +type HealthzLivezHealthChecksAdder interface { + AddHealthzChecks(checks ...healthz.HealthChecker) + AddLivezChecks(checks ...healthz.HealthChecker) +} + +func (s *SecureServingOptionsWithLoopback) applyTo(secureServingInfo **server.SecureServingInfo, loopbackClientConfig **rest.Config, healthCheckAdder HealthzLivezHealthChecksAdder) error { if s == nil || s.SecureServingOptions == nil || secureServingInfo == nil { return nil } @@ -50,12 +72,6 @@ func (s *SecureServingOptionsWithLoopback) ApplyTo(secureServingInfo **server.Se return nil } - // Set a validity period of approximately 3 years for the loopback certificate - // to avoid kube-apiserver disruptions due to certificate expiration. - // When this certificate expires, restarting kube-apiserver will automatically - // regenerate a new certificate with fresh validity dates. - maxAge := (3*365 + 1) * 24 * time.Hour - // create self-signed cert+key with the fake server.LoopbackClientServerNameOverride and // let the server return it when the loopback client connects. certPem, keyPem, err := certutil.GenerateSelfSignedCertKeyWithOptions(certutil.SelfSignedCertKeyOptions{ @@ -85,7 +101,37 @@ func (s *SecureServingOptionsWithLoopback) ApplyTo(secureServingInfo **server.Se default: *loopbackClientConfig = secureLoopbackClientConfig + if healthCheckAdder != nil { + s.addLoopbackServingCertificateHealthCheck(healthCheckAdder) + } } return nil } + +func (s *SecureServingOptionsWithLoopback) ApplyToConfig(cfg *server.Config) error { + return s.applyTo(&cfg.SecureServing, &cfg.LoopbackClientConfig, cfg) +} + +// addLoopbackServingCertificateHealthCheck adds a health check called `loopback-certificate-expiry` to the +// server that fails when the loopback client certificate has expired, enabling +// liveness probes to be used to automatically restart the apiserver. +func (s *SecureServingOptionsWithLoopback) addLoopbackServingCertificateHealthCheck(healthCheckAdder HealthzLivezHealthChecksAdder) { + expirationDate := s.clock.Now().Add(maxAge) + check := healthz.NamedCheck("loopback-serving-certificate", func(r *http.Request) error { + if s.clock.Now().After(expirationDate) { + return LoopbackCertificateExpiredError{} + } + + return nil + }) + + healthCheckAdder.AddHealthzChecks(check) + healthCheckAdder.AddLivezChecks(check) +} + +type LoopbackCertificateExpiredError struct{} + +func (lcee LoopbackCertificateExpiredError) Error() string { + return "loopback serving certificate is expired" +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback_test.go index c4b0c57b568c2..95bb11229743e 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving_with_loopback_test.go @@ -17,11 +17,17 @@ limitations under the License. package options import ( + "errors" "net" "testing" + "time" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/server/healthz" "k8s.io/client-go/rest" + clocktesting "k8s.io/utils/clock/testing" netutils "k8s.io/utils/net" ) @@ -50,3 +56,81 @@ func TestEmptyMainCert(t *testing.T) { t.Errorf("expected %d SNICert, got %d", e, a) } } + +func TestApplyToConfig(t *testing.T) { + type testcase struct { + name string + expired bool + } + + testcases := []testcase{ + { + name: "current time not after certificate expiry", + }, + { + name: "current time after certificate expiry", + expired: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cfg := server.NewConfig(serializer.NewCodecFactory(runtime.NewScheme())) + + ssowlb := (&SecureServingOptions{ + BindAddress: netutils.ParseIPSloppy("127.0.0.1"), + }).WithLoopback() + now := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.Local) + fakeClock := clocktesting.NewFakeClock(now) + ssowlb.clock = fakeClock + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to listen on 127.0.0.1:0") + } + defer ln.Close() //nolint:errcheck + ssowlb.Listener = ln + ssowlb.BindPort = ln.Addr().(*net.TCPAddr).Port + + if err := ssowlb.ApplyToConfig(cfg); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if tc.expired { + fakeClock.Step(maxAge + 1) + } + + checkSet := map[string][]healthz.HealthChecker{ + "/livez": cfg.LivezChecks, + "/healthz": cfg.HealthzChecks, + } + + for endpoint, checks := range checkSet { + var foundChecker healthz.HealthChecker + + for _, check := range checks { + if check.Name() == "loopback-serving-certificate" { + foundChecker = check + break + } + } + + if foundChecker == nil { + t.Fatalf("loopback-serving-certificate health checker not found for %s endpoint", endpoint) + } + + err = foundChecker.Check(nil) + if tc.expired { + if !errors.Is(err, LoopbackCertificateExpiredError{}) { + t.Errorf("%s endpoint check expected error and received error do not match. expected: %v , received: %v", endpoint, LoopbackCertificateExpiredError{}, err) + } + return + } + + if err != nil { + t.Errorf("%s endpoint check received an unexpected error: %v", endpoint, err) + } + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go index c546112ec9ce6..d99d8b8b2a800 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go +++ b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go @@ -221,24 +221,37 @@ func getRuntimeConfigValue(overrides cliflag.ConfigurationMap, apiKey string, de // ParseGroups takes in resourceConfig and returns parsed groups. func ParseGroups(resourceConfig cliflag.ConfigurationMap) ([]string, error) { + groupVersions, err := ParseGroupVersions(resourceConfig) + if err != nil { + return nil, err + } groups := []string{} + for _, gv := range groupVersions { + groups = append(groups, gv.Group) + } + return groups, nil +} + +// ParseGroupVersions takes in resourceConfig and returns parsed group versions. +func ParseGroupVersions(resourceConfig cliflag.ConfigurationMap) ([]schema.GroupVersion, error) { + groupVersions := []schema.GroupVersion{} for key := range resourceConfig { if _, ok := groupVersionMatchers[key]; ok { continue } tokens := strings.Split(key, "/") if len(tokens) != 2 && len(tokens) != 3 { - return groups, fmt.Errorf("runtime-config invalid key %s", key) + return nil, fmt.Errorf("runtime-config invalid key %s", key) } groupVersionString := tokens[0] + "/" + tokens[1] groupVersion, err := schema.ParseGroupVersion(groupVersionString) if err != nil { return nil, fmt.Errorf("runtime-config invalid key %s", key) } - groups = append(groups, groupVersion.Group) + groupVersions = append(groupVersions, groupVersion) } - return groups, nil + return groupVersions, nil } // EmulationForwardCompatibleResourceConfig creates a new ResourceConfig that besides all the enabled resources in resourceConfig, diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/metrics.go b/staging/src/k8s.io/apiserver/pkg/server/routes/metrics.go index 8fd4d5599994e..644020049a02b 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/metrics.go @@ -24,6 +24,7 @@ import ( etcd3metrics "k8s.io/apiserver/pkg/storage/etcd3/metrics" flowcontrolmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics" peerproxymetrics "k8s.io/apiserver/pkg/util/peerproxy/metrics" + proxymetrics "k8s.io/apiserver/pkg/util/proxy/metrics" "k8s.io/component-base/metrics/legacyregistry" ) @@ -54,4 +55,5 @@ func register() { flowcontrolmetrics.Register() peerproxymetrics.Register() handlersmetrics.Register() + proxymetrics.Register() } diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go index 13982b7b4cef4..459d73777629d 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go @@ -29,6 +29,7 @@ import ( "k8s.io/kube-openapi/pkg/common/restfuladapter" "k8s.io/kube-openapi/pkg/handler" "k8s.io/kube-openapi/pkg/handler3" + "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -76,7 +77,9 @@ func (oa OpenAPI) InstallV2(c *restful.Container, mux *mux.PathRecorderMux) (*ha return openAPIVersionedService, spec } -// InstallV3 adds the static group/versions defined in the RegisteredWebServices to the OpenAPI v3 spec +// InstallV3 adds the static group/versions defined in the RegisteredWebServices to the OpenAPI v3 spec. +// This only covers built-in resources served via go-restful; CRDs and aggregated APIs publish +// their OpenAPI v3 specs through separate code paths. func (oa OpenAPI) InstallV3(c *restful.Container, mux *mux.PathRecorderMux) *handler3.OpenAPIService { openAPIVersionedService := handler3.NewOpenAPIService() err := openAPIVersionedService.RegisterOpenAPIV3VersionedService("/openapi/v3", mux) @@ -96,9 +99,111 @@ func (oa OpenAPI) InstallV3(c *restful.Container, mux *mux.PathRecorderMux) *han spec, err := builder3.BuildOpenAPISpecFromRoutes(restfuladapter.AdaptWebServices(ws), oa.V3Config) if err != nil { klog.Errorf("Failed to build OpenAPI v3 for group %s, %q", gv, err) - + continue + } + if group, version, ok := groupVersionFromPath(gv); ok { + filterScopedGVKs(spec, group, version) } openAPIVersionedService.UpdateGroupVersion(gv, spec) } return openAPIVersionedService } + +// groupVersionFromPath extracts the API group and version from a root path like "apis/apps/v1" or "api/v1". +func groupVersionFromPath(path string) (group, version string, ok bool) { + // "api/v1" → ("", "v1", true) + // "apis/apps/v1" → ("apps", "v1", true) + // "apis/networking.k8s.io/v1" → ("networking.k8s.io", "v1", true) + parts := strings.SplitN(path, "/", 4) + switch { + case len(parts) < 2: + return "", "", false + case parts[0] == "api" && len(parts) == 2: + return "", parts[1], true + case parts[0] == "apis" && len(parts) == 3: + return parts[1], parts[2], true + default: + return "", "", false + } +} + +// crossRegisteredKinds lists the kinds that AddToGroupVersion (in +// k8s.io/apimachinery/pkg/apis/meta/v1) registers into every API group. +// Only these types get their x-kubernetes-group-version-kind list filtered +// in per-GV v3 specs. +var crossRegisteredKinds = map[string]bool{ + "WatchEvent": true, + "DeleteOptions": true, +} + +// filterScopedGVKs narrows x-kubernetes-group-version-kind on the meta types +// that AddToGroupVersion cross-registers into every API group (WatchEvent and +// DeleteOptions). Without filtering, each per-GV v3 spec carries ~60 GVKs for +// these types. This filter keeps only the entry matching this spec's group/version +// plus the canonical core/v1 entry. +// +// Only the meta types listed in crossRegisteredKinds are filtered. Other types +// that are intentionally registered into specific groups (like autoscaling/v1 +// Scale into apps/v1 and core/v1) are left unchanged. +// +// This only affects built-in resource specs generated by InstallV3 above. +// CRDs and aggregated APIs are not affected as they publish specs through separate paths. +func filterScopedGVKs(s *spec3.OpenAPI, group, version string) { + if s == nil || s.Components == nil { + return + } + for _, schema := range s.Components.Schemas { + if schema == nil { + continue + } + ext, ok := schema.Extensions["x-kubernetes-group-version-kind"] + if !ok { + continue + } + gvks, ok := ext.([]interface{}) + if !ok || len(gvks) <= 1 { + continue + } + if !isCrossRegisteredKind(gvks) { + continue + } + var filtered []interface{} + for _, item := range gvks { + m, ok := item.(map[string]interface{}) + if !ok { + continue + } + g, ok := m["group"].(string) + if !ok { + continue + } + v, ok := m["version"].(string) + if !ok { + continue + } + if (g == group && v == version) || (g == "" && v == "v1") { + filtered = append(filtered, item) + } + } + if len(filtered) > 0 { + schema.Extensions["x-kubernetes-group-version-kind"] = filtered + } else { + klog.Warningf("Unexpected: filtering x-kubernetes-group-version-kind for %s/%s produced no matches", group, version) + delete(schema.Extensions, "x-kubernetes-group-version-kind") + } + } +} + +// isCrossRegisteredKind checks whether a GVK list represents one of the +// meta types from crossRegisteredKinds by inspecting the first entry's kind. +func isCrossRegisteredKind(gvks []interface{}) bool { + if len(gvks) == 0 { + return false + } + m, ok := gvks[0].(map[string]interface{}) + if !ok { + return false + } + kind, _ := m["kind"].(string) + return crossRegisteredKinds[kind] +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi_test.go b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi_test.go new file mode 100644 index 0000000000000..6f0f93b29e411 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi_test.go @@ -0,0 +1,177 @@ +/* +Copyright 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 routes + +import ( + "reflect" + "testing" + + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +func TestGroupVersionFromPath(t *testing.T) { + tests := []struct { + path string + wantGroup string + wantVersion string + wantOK bool + }{ + {"api/v1", "", "v1", true}, + {"apis/apps/v1", "apps", "v1", true}, + {"apis/networking.k8s.io/v1", "networking.k8s.io", "v1", true}, + {"apis/batch/v1", "batch", "v1", true}, + {"api", "", "", false}, + {"apis/apps", "", "", false}, + {"api/v1/extra", "", "", false}, + {"apis/apps/v1/extra", "", "", false}, + {"", "", "", false}, + } + for _, tt := range tests { + group, version, ok := groupVersionFromPath(tt.path) + if group != tt.wantGroup || version != tt.wantVersion || ok != tt.wantOK { + t.Errorf("groupVersionFromPath(%q) = (%q, %q, %v), want (%q, %q, %v)", tt.path, group, version, ok, tt.wantGroup, tt.wantVersion, tt.wantOK) + } + } +} + +func gvk(group, version, kind string) map[string]interface{} { + return map[string]interface{}{"group": group, "version": version, "kind": kind} +} + +func TestFilterScopedGVKs(t *testing.T) { + tests := []struct { + name string + gvks []interface{} + group string + version string + wantGVKs []interface{} + }{ + { + name: "DeleteOptions keeps local and core/v1", + gvks: []interface{}{ + gvk("", "v1", "DeleteOptions"), + gvk("apps", "v1", "DeleteOptions"), + gvk("batch", "v1", "DeleteOptions"), + gvk("autoscaling", "v1", "DeleteOptions"), + }, + group: "apps", + version: "v1", + wantGVKs: []interface{}{ + gvk("", "v1", "DeleteOptions"), + gvk("apps", "v1", "DeleteOptions"), + }, + }, + { + name: "WatchEvent keeps local and core/v1", + gvks: []interface{}{ + gvk("", "v1", "WatchEvent"), + gvk("apps", "v1", "WatchEvent"), + gvk("batch", "v1", "WatchEvent"), + }, + group: "batch", + version: "v1", + wantGVKs: []interface{}{ + gvk("", "v1", "WatchEvent"), + gvk("batch", "v1", "WatchEvent"), + }, + }, + { + name: "DeleteOptions with core group keeps only core/v1", + gvks: []interface{}{ + gvk("", "v1", "DeleteOptions"), + gvk("apps", "v1", "DeleteOptions"), + gvk("batch", "v1", "DeleteOptions"), + }, + group: "", + version: "v1", + wantGVKs: []interface{}{gvk("", "v1", "DeleteOptions")}, + }, + { + name: "single GVK unchanged", + gvks: []interface{}{ + gvk("apps", "v1", "Deployment"), + }, + group: "apps", + version: "v1", + wantGVKs: []interface{}{gvk("apps", "v1", "Deployment")}, + }, + { + name: "non-meta cross-group type unchanged", + gvks: []interface{}{ + gvk("autoscaling", "v1", "Scale"), + gvk("apps", "v1", "Scale"), + }, + group: "apps", + version: "v1", + wantGVKs: []interface{}{ + gvk("autoscaling", "v1", "Scale"), + gvk("apps", "v1", "Scale"), + }, + }, + { + name: "DeleteOptions no match removes extension", + gvks: []interface{}{ + gvk("apps", "v1", "DeleteOptions"), + gvk("batch", "v1", "DeleteOptions"), + }, + group: "networking.k8s.io", + version: "v1", + wantGVKs: nil, + }, + { + name: "multi-version same group unchanged", + gvks: []interface{}{ + gvk("autoscaling", "v1", "HorizontalPodAutoscaler"), + gvk("autoscaling", "v2", "HorizontalPodAutoscaler"), + }, + group: "autoscaling", + version: "v2", + wantGVKs: []interface{}{ + gvk("autoscaling", "v1", "HorizontalPodAutoscaler"), + gvk("autoscaling", "v2", "HorizontalPodAutoscaler"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &spec3.OpenAPI{ + Components: &spec3.Components{ + Schemas: map[string]*spec.Schema{ + "TestType": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-group-version-kind": tt.gvks, + }, + }, + }, + }, + }, + } + filterScopedGVKs(s, tt.group, tt.version) + got, exists := s.Components.Schemas["TestType"].Extensions["x-kubernetes-group-version-kind"] + if tt.wantGVKs == nil { + if exists { + t.Errorf("expected extension to be removed, got %v", got) + } + } else if !reflect.DeepEqual(got, tt.wantGVKs) { + t.Errorf("got %v, want %v", got, tt.wantGVKs) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go b/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go index 867c7efac2c4f..0c14893a74aad 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go +++ b/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go @@ -71,6 +71,9 @@ func (s *SecureServingInfo) tlsConfig(stopCh <-chan struct{}) (*tls.Config, erro } } } + if len(s.CurvePreferences) > 0 { + tlsConfig.CurvePreferences = s.CurvePreferences + } if s.ClientCA != nil { // Populate PeerCertificates in requests, but don't reject connections without certificates diff --git a/staging/src/k8s.io/apiserver/pkg/server/signal_posix.go b/staging/src/k8s.io/apiserver/pkg/server/signal_posix.go index 7acb2038a1b36..fc0ab2d6716be 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/signal_posix.go +++ b/staging/src/k8s.io/apiserver/pkg/server/signal_posix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1alpha1/doc.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1alpha1/doc.go index aa0fdaed0b622..9a542b0970b01 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1alpha1/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1alpha1/doc.go @@ -18,5 +18,5 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:openapi-model-package=io.k8s.apiserver.pkg.server.statusz.api.v1alpha1 -// Package v1alpha1 contains API Schema definitions for the zpages v1alpha1 API group +// Package v1alpha1 contains API Schema definitions for the statusz v1alpha1 API group package v1alpha1 diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/doc.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/doc.go new file mode 100644 index 0000000000000..8237e4776f5d2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apiserver.pkg.server.statusz.api.v1beta1 + +// Package v1beta1 contains API Schema definitions for the statusz v1beta1 API group +package v1beta1 diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/register.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/register.go new file mode 100644 index 0000000000000..c41806b69dff0 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/register.go @@ -0,0 +1,47 @@ +/* +Copyright 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 v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +const ( + GroupName = "config.k8s.io" + Version = "v1beta1" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version} + +var ( + // SchemeBuilder initializes a scheme builder + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme is a global function that adds this group's types to a scheme + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Statusz{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/types.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/types.go new file mode 100644 index 0000000000000..a8898826bbe3f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/types.go @@ -0,0 +1,50 @@ +/* +Copyright 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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// Statusz is a struct used for versioned statusz endpoint. +type Statusz struct { + // TypeMeta is the type metadata for the object. + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + // StartTime is the time the component process was initiated. + StartTime metav1.Time `json:"startTime"` + // UptimeSeconds is the duration in seconds for which the component has been running continuously. + UptimeSeconds int64 `json:"uptimeSeconds"` + // GoVersion is the version of the Go programming language used to build the binary. + // The format is not guaranteed to be consistent across different Go builds. + // +optional + GoVersion string `json:"goVersion"` + // BinaryVersion is the version of the component's binary. + // The format is not guaranteed to be semantic versioning and may be an arbitrary string. + BinaryVersion string `json:"binaryVersion"` + // EmulationVersion is the Kubernetes API version which this component is emulating. + // if present, formatted as "." + // +optional + EmulationVersion string `json:"emulationVersion,omitempty"` + // Paths contains relative URLs to other essential read-only endpoints for debugging and troubleshooting. + // +optional + // +listType=set + Paths []string `json:"paths"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000..e879af624c312 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,58 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Statusz) DeepCopyInto(out *Statusz) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.StartTime.DeepCopyInto(&out.StartTime) + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Statusz. +func (in *Statusz) DeepCopy() *Statusz { + if in == nil { + return nil + } + out := new(Statusz) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Statusz) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/zz_generated.model_name.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/zz_generated.model_name.go new file mode 100644 index 0000000000000..8a4134750b1b2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/api/v1beta1/zz_generated.model_name.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1beta1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Statusz) OpenAPIModelName() string { + return "io.k8s.apiserver.pkg.server.statusz.api.v1beta1.Statusz" +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/negotiate/negotiation.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/negotiate/negotiation.go index 04c6943774faa..cd314e4aea39b 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/negotiate/negotiation.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/negotiate/negotiation.go @@ -20,34 +20,29 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -// StatuszEndpointRestrictions implements content negotiation restrictions for the z-pages. +// StatuszEndpointRestrictions implements content negotiation restrictions for statusz. // It is used to validate and restrict which GroupVersionKinds are allowed for structured responses. -type StatuszEndpointRestrictions struct{} +type StatuszEndpointRestrictions struct { + RecognizedStructuredKinds map[schema.GroupVersionKind]bool +} -// AllowsMediaTypeTransform checks if the provided GVK is supported for structured z-page responses. -func (StatuszEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool { +// AllowsMediaTypeTransform checks if the provided GVK is supported for structured statusz responses. +func (s StatuszEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool { if mimeType == "text" && mimeSubType == "plain" { return gvk == nil } - return isStructured(gvk) -} -func (StatuszEndpointRestrictions) AllowsServerVersion(string) bool { + if gvk != nil { + return s.RecognizedStructuredKinds[*gvk] + } + return false } -func (StatuszEndpointRestrictions) AllowsStreamSchema(s string) bool { +func (StatuszEndpointRestrictions) AllowsServerVersion(s string) bool { return false } -func isStructured(gvk *schema.GroupVersionKind) bool { - if gvk != nil { - if gvk.Group == "config.k8s.io" && gvk.Version == "v1alpha1" { - if gvk.Kind == "Statusz" { - return true - } - } - } - +func (StatuszEndpointRestrictions) AllowsStreamSchema(s string) bool { return false } diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go index f9f24dfbd7f8e..1b334b16992cd 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go @@ -26,15 +26,22 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor" "k8s.io/component-base/compatibility" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + "k8s.io/apiserver/pkg/endpoints/metrics" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/endpoints/responsewriter" + "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1" + "k8s.io/apiserver/pkg/server/statusz/api/v1beta1" "k8s.io/apiserver/pkg/server/statusz/negotiate" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - v1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) var ( @@ -46,21 +53,27 @@ var ( "/openapi": true, "/.well-known": true, } + v1alpha1StatuszKind = v1alpha1.SchemeGroupVersion.WithKind("Statusz") + v1beta1StatuszKind = v1beta1.SchemeGroupVersion.WithKind("Statusz") + recognizedStructuredKinds = map[schema.GroupVersionKind]bool{ + v1alpha1StatuszKind: true, + v1beta1StatuszKind: true, + } ) -const ( - DefaultStatuszPath = "/statusz" - Kind = "Statusz" - GroupName = "config.k8s.io" - Version = "v1alpha1" -) +const DefaultStatuszPath = "/statusz" const headerFmt = ` %s statusz Warning: This endpoint is not meant to be machine parseable, has no formatting compatibility guarantees and is for debugging purposes only. ` -var schemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version} +// statuszCodecFactory wraps a CodecFactory to filter out unsupported media types (like protobuf) +// from the supported media types list, so error messages only show actually supported types. +type statuszCodecFactory struct { + serializer.CodecFactory + supportedMediaTypes []runtime.SerializerInfo +} type mux interface { Handle(path string, handler http.Handler) @@ -70,7 +83,8 @@ type ListedPathsOption []string func NewRegistry(effectiveVersion compatibility.EffectiveVersion, opts ...Option) statuszRegistry { r := ®istry{ - effectiveVersion: effectiveVersion, + effectiveVersion: effectiveVersion, + deprecatedVersionsMap: map[string]bool{"v1alpha1": true}, } for _, opt := range opts { opt(r) @@ -82,8 +96,21 @@ func NewRegistry(effectiveVersion compatibility.EffectiveVersion, opts ...Option func Install(m mux, componentName string, reg statuszRegistry) { scheme := runtime.NewScheme() utilruntime.Must(v1alpha1.AddToScheme(scheme)) - codecFactory := serializer.NewCodecFactory( - scheme, + utilruntime.Must(v1beta1.AddToScheme(scheme)) + filteredCodecFactory, err := newStatuszCodecFactory(scheme, componentName, reg) + if err != nil { + utilruntime.HandleError(err) + } + restrictions := negotiate.StatuszEndpointRestrictions{ + RecognizedStructuredKinds: recognizedStructuredKinds, + } + m.Handle(DefaultStatuszPath, handleStatusz(componentName, reg, filteredCodecFactory, restrictions)) +} + +// newStatuszCodecFactory creates a codec factory with the standard serializers for statusz, +// filtering out unsupported media types (e.g., protobuf). +func newStatuszCodecFactory(scheme *runtime.Scheme, componentName string, reg statuszRegistry) (*statuszCodecFactory, error) { + codecFactoryOpts := []serializer.CodecFactoryOptionsMutator{ serializer.WithSerializer(func(_ runtime.ObjectCreater, _ runtime.ObjectTyper) runtime.SerializerInfo { textSerializer := statuszTextSerializer{componentName, reg} return runtime.SerializerInfo{ @@ -95,16 +122,75 @@ func Install(m mux, componentName string, reg statuszRegistry) { PrettySerializer: textSerializer, } }), - ) - m.Handle(DefaultStatuszPath, handleStatusz(componentName, reg, codecFactory, negotiate.StatuszEndpointRestrictions{})) + } + // TODO: remove this explicit check when https://github.com/kubernetes/enhancements/pull/5740 is implemented. + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { + codecFactoryOpts = append(codecFactoryOpts, serializer.WithSerializer(cbor.NewSerializerInfo)) + } + + codecFactory := serializer.NewCodecFactory(scheme, codecFactoryOpts...) + allTypes := codecFactory.SupportedMediaTypes() + filtered := make([]runtime.SerializerInfo, 0, len(allTypes)) + + var unknownTypes []string + for _, info := range allTypes { + switch info.MediaType { + // Supported media types + case "text/plain", runtime.ContentTypeJSON, runtime.ContentTypeYAML, runtime.ContentTypeCBOR: + filtered = append(filtered, info) + // Unsupported media types + case runtime.ContentTypeProtobuf: + continue + default: + unknownTypes = append(unknownTypes, info.MediaType) + } + } + + var err error + if len(unknownTypes) > 0 { + err = fmt.Errorf("statusz: unknown media type(s) %v, excluding from supported types", unknownTypes) + } + + return &statuszCodecFactory{ + CodecFactory: codecFactory, + supportedMediaTypes: filtered, + }, err +} + +func (f *statuszCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { + return f.supportedMediaTypes } func handleStatusz(componentName string, reg statuszRegistry, serializer runtime.NegotiatedSerializer, restrictions negotiate.StatuszEndpointRestrictions) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - obj := statusz(componentName, reg) + requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(r.Context()) + if !ok { + requestReceivedTimestamp = time.Now() + } + delegate := &metrics.ResponseWriterDelegator{ResponseWriter: w} + w = responsewriter.WrapForHTTP1Or2(delegate) + + // Use MonitorRequest instead of InstrumentHandlerFunc because the group, + // version, and deprecated status depend on per-request content negotiation. + // For text/plain requests, group and version remain empty. For structured + // responses (JSON/YAML/CBOR), they are set to the negotiated API group and + // version (e.g., config.k8s.io/v1alpha1). + var group, version string + var deprecated bool + defer func() { + metrics.MonitorRequest(r, "GET", group, version, + "statusz", // resource + "", // subresource + "", // scope + componentName, // component + deprecated, + "", // removedRelease + delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp)) + }() + acceptHeader := r.Header.Get("Accept") if strings.TrimSpace(acceptHeader) == "" { - writePlainTextResponse(obj, serializer, w) + writePlainTextResponse(v1beta1Statusz(componentName, reg), serializer, w) return } @@ -121,11 +207,10 @@ func handleStatusz(componentName string, reg statuszRegistry, serializer runtime return } - var targetGV schema.GroupVersion switch serializerInfo.MediaType { - case "application/json": + case "application/json", "application/yaml", "application/cbor": if mediaType.Convert == nil { - err := fmt.Errorf("content negotiation failed: mediaType.Convert is nil for application/json") + err := fmt.Errorf("content negotiation failed: mediaType.Convert is nil for %s", serializerInfo.MediaType) utilruntime.HandleError(err) responsewriters.ErrorNegotiated( err, @@ -136,18 +221,19 @@ func handleStatusz(componentName string, reg statuszRegistry, serializer runtime ) return } - targetGV = mediaType.Convert.GroupVersion() - deprecated := reg.deprecatedVersions()[targetGV.Version] + // Set group, version, and deprecated from the negotiated target so + // the deferred MonitorRequest records the actual requested API version. + group = mediaType.Convert.Group + version = mediaType.Convert.Version + deprecated = reg.deprecatedVersions()[version] if deprecated { w.Header().Set("Warning", `299 - "This version of the statusz endpoint is deprecated. Please use a newer version."`) } + handleStructuredResponse(w, r, componentName, reg, serializer, restrictions, mediaType) case "text/plain": - // Even though text/plain serialization does not use the group/version, - // the serialization machinery expects a non-zero schema.GroupVersion to be passed. - // Passing the zero value can cause errors or unexpected behavior in the negotiation logic. - targetGV = schemeGroupVersion + writePlainTextResponse(v1beta1Statusz(componentName, reg), serializer, w) default: - err = fmt.Errorf("content negotiation failed: unsupported media type '%s'", serializerInfo.MediaType) + err := fmt.Errorf("unsupported media type: %s/%s", serializerInfo.MediaType, serializerInfo.MediaTypeSubType) utilruntime.HandleError(err) responsewriters.ErrorNegotiated( err, @@ -156,14 +242,29 @@ func handleStatusz(componentName string, reg statuszRegistry, serializer runtime w, r, ) - return } + } +} - writeResponse(obj, serializer, targetGV, restrictions, w, r) +func handleStructuredResponse(w http.ResponseWriter, r *http.Request, componentName string, reg statuszRegistry, serializer runtime.NegotiatedSerializer, restrictions negotiate.StatuszEndpointRestrictions, mediaType negotiation.MediaTypeOptions) { + switch *mediaType.Convert { + case v1alpha1StatuszKind: + writeStructuredResponse(v1alpha1Statusz(componentName, reg), serializer, mediaType.Convert.GroupVersion(), restrictions, w, r) + case v1beta1StatuszKind: + writeStructuredResponse(v1beta1Statusz(componentName, reg), serializer, mediaType.Convert.GroupVersion(), restrictions, w, r) + default: + err := fmt.Errorf("unsupported media type: %s", mediaType.Convert.String()) + utilruntime.HandleError(err) + responsewriters.ErrorNegotiated( + err, + serializer, + schema.GroupVersion{}, + w, + r, + ) } } -// writePlainTextResponse writes the statusz response as text/plain using the registered serializer. func writePlainTextResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, w http.ResponseWriter) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") // Find the text/plain serializer @@ -185,7 +286,7 @@ func writePlainTextResponse(obj runtime.Object, serializer runtime.NegotiatedSer } } -func writeResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, targetGV schema.GroupVersion, restrictions negotiate.StatuszEndpointRestrictions, w http.ResponseWriter, r *http.Request) { +func writeStructuredResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, targetGV schema.GroupVersion, restrictions negotiate.StatuszEndpointRestrictions, w http.ResponseWriter, r *http.Request) { responsewriters.WriteObjectNegotiated( serializer, restrictions, @@ -198,7 +299,7 @@ func writeResponse(obj runtime.Object, serializer runtime.NegotiatedSerializer, ) } -func statusz(componentName string, reg statuszRegistry) *v1alpha1.Statusz { +func v1alpha1Statusz(componentName string, reg statuszRegistry) *v1alpha1.Statusz { startTime := reg.processStartTime() upTimeSeconds := max(0, int64(time.Since(startTime).Seconds())) goVersion := reg.goVersion() @@ -211,8 +312,38 @@ func statusz(componentName string, reg statuszRegistry) *v1alpha1.Statusz { paths := aggregatePaths(reg.paths()) data := &v1alpha1.Statusz{ TypeMeta: metav1.TypeMeta{ - Kind: Kind, - APIVersion: fmt.Sprintf("%s/%s", GroupName, Version), + Kind: v1alpha1StatuszKind.Kind, + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: componentName, + }, + StartTime: metav1.Time{Time: startTime}, + UptimeSeconds: upTimeSeconds, + GoVersion: goVersion, + BinaryVersion: binaryVersion, + EmulationVersion: emulationVersion, + Paths: paths, + } + + return data +} + +func v1beta1Statusz(componentName string, reg statuszRegistry) *v1beta1.Statusz { + startTime := reg.processStartTime() + upTimeSeconds := max(0, int64(time.Since(startTime).Seconds())) + goVersion := reg.goVersion() + binaryVersion := reg.binaryVersion().String() + var emulationVersion string + if reg.emulationVersion() != nil { + emulationVersion = reg.emulationVersion().String() + } + + paths := aggregatePaths(reg.paths()) + data := &v1beta1.Statusz{ + TypeMeta: metav1.TypeMeta{ + Kind: v1beta1StatuszKind.Kind, + APIVersion: v1beta1.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: componentName, diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go index cd0b2a17d458c..26e6a83f2f387 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go @@ -27,10 +27,21 @@ import ( "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/runtime" + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/version" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apiserver/pkg/endpoints/metrics" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1" + v1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1" ) const wantTmpl = ` @@ -58,6 +69,9 @@ Paths: /livez /readyz ` func TestHandleStatusz(t *testing.T) { + // Enable CBOR feature gate for CBOR test case + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) + delimiters = []string{":"} fakeStartTime := time.Now() fakeUptime := uptime(fakeStartTime) @@ -68,14 +82,14 @@ func TestHandleStatusz(t *testing.T) { fakeEmulationVersion := parseVersion(t, fakeEvStr) fakeListedPaths := []string{"/livez/poststarthook/peer-discovery-cache-sync", "/livez/post", "/readyz/informer-sync", "/readyz/log", "/readyz/ping"} tests := []struct { - name string - acceptHeader string - componentName string - registry fakeRegistry - wantStatusCode int - wantBody string - wantJSONBody *v1alpha1.Statusz - wantWarning bool + name string + acceptHeader string + componentName string + registry fakeRegistry + wantStatusCode int + wantBody string + wantStructuredBody interface{} + wantWarning bool }{ { name: "valid request for text/plain", @@ -100,8 +114,8 @@ func TestHandleStatusz(t *testing.T) { ), }, { - name: "valid request for v1alpha1", - acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Statusz", + name: "valid request for application/json", + acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Statusz", componentName: "test-server", registry: fakeRegistry{ startTime: fakeStartTime, @@ -112,10 +126,10 @@ func TestHandleStatusz(t *testing.T) { deprecated: map[string]bool{}, }, wantStatusCode: http.StatusOK, - wantJSONBody: &v1alpha1.Statusz{ + wantStructuredBody: &v1beta1.Statusz{ TypeMeta: metav1.TypeMeta{ - Kind: Kind, - APIVersion: fmt.Sprintf("%s/%s", GroupName, Version), + Kind: "Statusz", + APIVersion: "config.k8s.io/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "test-server", @@ -185,7 +199,7 @@ func TestHandleStatusz(t *testing.T) { }, { name: "application/json with missing as", - acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io", + acceptHeader: "application/json;v=v1beta1;g=config.k8s.io", componentName: "test-server", wantStatusCode: http.StatusNotAcceptable, }, @@ -234,7 +248,7 @@ func TestHandleStatusz(t *testing.T) { ), }, { - name: "deprecated version request", + name: "deprecated v1alpha1 request", acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Statusz", componentName: "test-server", registry: fakeRegistry{ @@ -243,15 +257,13 @@ func TestHandleStatusz(t *testing.T) { binaryVer: fakeBinaryVersion, emulationVer: fakeEmulationVersion, listedPaths: fakeListedPaths, - deprecated: map[string]bool{ - "v1alpha1": true, - }, + deprecated: map[string]bool{"v1alpha1": true}, }, wantStatusCode: http.StatusOK, - wantJSONBody: &v1alpha1.Statusz{ + wantStructuredBody: &v1alpha1.Statusz{ TypeMeta: metav1.TypeMeta{ - Kind: Kind, - APIVersion: fmt.Sprintf("%s/%s", GroupName, Version), + Kind: "Statusz", + APIVersion: "config.k8s.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "test-server", @@ -265,6 +277,64 @@ func TestHandleStatusz(t *testing.T) { }, wantWarning: true, }, + { + name: "valid request for application/yaml", + acceptHeader: "application/yaml;v=v1beta1;g=config.k8s.io;as=Statusz", + componentName: "test-server", + registry: fakeRegistry{ + startTime: fakeStartTime, + goVer: fakeGoVersion, + binaryVer: fakeBinaryVersion, + emulationVer: fakeEmulationVersion, + listedPaths: fakeListedPaths, + deprecated: map[string]bool{}, + }, + wantStatusCode: http.StatusOK, + wantStructuredBody: &v1beta1.Statusz{ + TypeMeta: metav1.TypeMeta{ + Kind: "Statusz", + APIVersion: "config.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-server", + }, + StartTime: metav1.Time{Time: fakeStartTime}, + UptimeSeconds: int64(time.Since(fakeStartTime).Seconds()), + GoVersion: fakeGoVersion, + BinaryVersion: fakeBvStr, + EmulationVersion: fakeEvStr, + Paths: []string{"/livez", "/readyz"}, + }, + }, + { + name: "valid request for application/cbor", + acceptHeader: "application/cbor;v=v1beta1;g=config.k8s.io;as=Statusz", + componentName: "test-server", + registry: fakeRegistry{ + startTime: fakeStartTime, + goVer: fakeGoVersion, + binaryVer: fakeBinaryVersion, + emulationVer: fakeEmulationVersion, + listedPaths: fakeListedPaths, + deprecated: map[string]bool{}, + }, + wantStatusCode: http.StatusOK, + wantStructuredBody: &v1beta1.Statusz{ + TypeMeta: metav1.TypeMeta{ + Kind: "Statusz", + APIVersion: "config.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-server", + }, + StartTime: metav1.Time{Time: fakeStartTime}, + UptimeSeconds: int64(time.Since(fakeStartTime).Seconds()), + GoVersion: fakeGoVersion, + BinaryVersion: fakeBvStr, + EmulationVersion: fakeEvStr, + Paths: []string{"/livez", "/readyz"}, + }, + }, } for _, tt := range tests { @@ -289,12 +359,18 @@ func TestHandleStatusz(t *testing.T) { } if tt.wantStatusCode == http.StatusOK { - if tt.wantJSONBody != nil { - var got v1alpha1.Statusz - if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil { - t.Fatalf("unexpected error while unmarshalling response: %v", err) + if tt.wantStructuredBody != nil { + var got interface{} + switch tt.wantStructuredBody.(type) { + case *v1alpha1.Statusz: + got = &v1alpha1.Statusz{} + case *v1beta1.Statusz: + got = &v1beta1.Statusz{} + default: + t.Fatalf("unexpected type for wantStructuredBody: %T", tt.wantStructuredBody) } - if diff := cmp.Diff(*tt.wantJSONBody, got, timeEqual()); diff != "" { + unmarshalResponse(t, w.Header().Get("Content-Type"), w.Body.Bytes(), got) + if diff := cmp.Diff(tt.wantStructuredBody, got, timeEqual()); diff != "" { t.Errorf("Unexpected diff on response (-want,+got):\n%s", diff) } if tt.wantWarning { @@ -303,8 +379,8 @@ func TestHandleStatusz(t *testing.T) { } } } else { - if diff := cmp.Diff(tt.wantBody, string(w.Body.String())); diff != "" { - t.Errorf("Unexpected diff on response (-want,+got):\n%s", diff) + if !strings.Contains(string(w.Body.String()), tt.wantBody) { + t.Errorf("Unexpected response body:\n- want to contain: %s\n- got: %s", tt.wantBody, string(w.Body.String())) } } } @@ -312,6 +388,26 @@ func TestHandleStatusz(t *testing.T) { } } +func unmarshalResponse(t *testing.T, contentType string, body []byte, got interface{}) { + t.Helper() + switch { + case strings.Contains(contentType, "application/json"): + if err := json.Unmarshal(body, got); err != nil { + t.Fatalf("unexpected error while unmarshalling JSON response: %v", err) + } + case strings.Contains(contentType, "application/cbor"): + if err := cbor.Unmarshal(body, got); err != nil { + t.Fatalf("unexpected error while unmarshalling CBOR response: %v", err) + } + case strings.Contains(contentType, "application/yaml"): + if err := yaml.Unmarshal(body, got); err != nil { + t.Fatalf("unexpected error while unmarshalling YAML response: %v", err) + } + default: + t.Fatalf("unexpected content type: %s", contentType) + } +} + func parseVersion(t *testing.T, v string) *version.Version { parsed, err := version.ParseMajorMinor(v) if err != nil { @@ -359,3 +455,64 @@ func timeEqual() cmp.Option { return expectedTime.Truncate(time.Second).Equal(actualTime.Truncate(time.Second)) }) } + +// TestNewStatuszCodecFactory ensures all media types in the codec factory +// are explicitly handled. If this test fails, a new media type was added +// to the codec factory and needs to be explicitly added to the supported +// or unsupported list in newStatuszCodecFactory. +func TestNewStatuszCodecFactory(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) + scheme := runtime.NewScheme() + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) + + _, err := newStatuszCodecFactory(scheme, "", nil) + if err != nil { + t.Fatalf("unknown media type(s) detected - update newStatuszCodecFactory to explicitly handle them: %v", err) + } +} + +func TestMetrics(t *testing.T) { + fakeStartTime := time.Now() + fakeBinaryVersion := parseVersion(t, "1.31") + fakeEmulationVersion := parseVersion(t, "1.30") + fakeListedPaths := []string{"/livez/ping", "/readyz/ping"} + + reg := fakeRegistry{ + startTime: fakeStartTime, + goVer: "1.21", + binaryVer: fakeBinaryVersion, + emulationVer: fakeEmulationVersion, + listedPaths: fakeListedPaths, + } + + mux := http.NewServeMux() + Install(mux, "test-server", reg) + metrics.Register() + metrics.Reset() + + // text/plain request: group and version should be empty + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com%s", DefaultStatuszPath), nil) + if err != nil { + t.Errorf("%v", err) + } + mux.ServeHTTP(httptest.NewRecorder(), req) + + // structured request: group and version should reflect the negotiated version + req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com%s", DefaultStatuszPath), nil) + if err != nil { + t.Errorf("%v", err) + } + req.Header.Set("Accept", "application/json;v=v1alpha1;g=config.k8s.io;as=Statusz") + mux.ServeHTTP(httptest.NewRecorder(), req) + + expected := strings.NewReader(` + # HELP apiserver_request_total [STABLE] Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code. + # TYPE apiserver_request_total counter + apiserver_request_total{code="200",component="test-server",dry_run="",group="",resource="statusz",scope="",subresource="",system_client="",verb="GET",version=""} 1 + apiserver_request_total{code="200",component="test-server",dry_run="",group="config.k8s.io",resource="statusz",scope="",subresource="",system_client="",verb="GET",version="v1alpha1"} 1 +`) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, expected, "apiserver_request_total"); err != nil { + t.Error(err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/testing/testing.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/testing/testing.go new file mode 100644 index 0000000000000..75d9782a3a631 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/testing/testing.go @@ -0,0 +1,116 @@ +/* +Copyright 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 testing + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1" + "k8s.io/apiserver/pkg/server/statusz/api/v1beta1" +) + +// VerifyStructuredResponse verifies that the response body matches the expected static fields +// and checks for the presence/absence of warnings. +func VerifyStructuredResponse(t *testing.T, acceptHeader string, body []byte, warnings []string, want interface{}, wantDeprecationHeader bool) { + t.Helper() + + unmarshal := unmarshalFunc(t, acceptHeader) + wantTypeMeta, wantName, wantPaths := wantFields(t, want) + gotTypeMeta, gotName, gotPaths := gotFields(t, unmarshal, body, wantTypeMeta.APIVersion) + + if gotName != wantName { + t.Errorf("ObjectMeta.Name mismatch: want %q, got %q", wantName, gotName) + } + if gotTypeMeta != wantTypeMeta { + t.Errorf("TypeMeta mismatch: want %+v, got %+v", wantTypeMeta, gotTypeMeta) + } + if diff := cmp.Diff(wantPaths, gotPaths); diff != "" { + t.Errorf("Paths mismatch (-want,+got):\n%s", diff) + } + + foundWarning := false + for _, w := range warnings { + if strings.Contains(w, "deprecated") { + foundWarning = true + break + } + } + + if wantDeprecationHeader { + if !foundWarning { + t.Errorf("want warning 'deprecated', got none or different: %v", warnings) + } + } else { + if foundWarning { + t.Errorf("want no warning 'deprecated', got one: %v", warnings) + } + } +} + +func unmarshalFunc(t *testing.T, acceptHeader string) func([]byte, interface{}) error { + switch { + case strings.Contains(acceptHeader, "application/json"): + return json.Unmarshal + case strings.Contains(acceptHeader, "application/yaml"): + return yaml.Unmarshal + case strings.Contains(acceptHeader, "application/cbor"): + return cbor.Unmarshal + default: + t.Fatalf("unexpected Accept header: %q", acceptHeader) + } + return nil +} + +func wantFields(t *testing.T, want interface{}) (metav1.TypeMeta, string, []string) { + t.Helper() + switch w := want.(type) { + case *v1alpha1.Statusz: + return w.TypeMeta, w.Name, w.Paths + case *v1beta1.Statusz: + return w.TypeMeta, w.Name, w.Paths + default: + t.Fatalf("unexpected type: %T", want) + } + return metav1.TypeMeta{}, "", nil +} + +func gotFields(t *testing.T, unmarshal func([]byte, interface{}) error, body []byte, apiVersion string) (metav1.TypeMeta, string, []string) { + t.Helper() + switch apiVersion { + case "config.k8s.io/v1alpha1": + var got v1alpha1.Statusz + if err := unmarshal(body, &got); err != nil { + t.Fatalf("error unmarshalling %s: %v", apiVersion, err) + } + return got.TypeMeta, got.Name, got.Paths + case "config.k8s.io/v1beta1": + var got v1beta1.Statusz + if err := unmarshal(body, &got); err != nil { + t.Fatalf("error unmarshalling %s: %v", apiVersion, err) + } + return got.TypeMeta, got.Name, got.Paths + default: + t.Fatalf("unexpected APIVersion: %q", apiVersion) + } + return metav1.TypeMeta{}, "", nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/textserializer.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/textserializer.go index c8bdc2c72a135..5c97923a0b8d3 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/textserializer.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/textserializer.go @@ -27,7 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - v1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1" + v1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1" ) // statuszTextSerializer implements runtime.Serializer for text/plain output. @@ -45,9 +45,9 @@ func (s statuszTextSerializer) Encode(obj runtime.Object, w io.Writer) error { randomIndex := rand.Intn(len(delimiters)) delim := html.EscapeString(delimiters[randomIndex]) - statuszObj, ok := obj.(*v1alpha1.Statusz) + statuszObj, ok := obj.(*v1beta1.Statusz) if !ok { - return fmt.Errorf("expected *v1alpha1.Statusz, got %T", obj) + return fmt.Errorf("expected *v1beta1.Statusz, got %T", obj) } startTime := html.EscapeString(statuszObj.StartTime.Time.Format(time.UnixDate)) diff --git a/staging/src/k8s.io/apiserver/pkg/sharding/parser.go b/staging/src/k8s.io/apiserver/pkg/sharding/parser.go new file mode 100644 index 0000000000000..fe08eac0c9b98 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/sharding/parser.go @@ -0,0 +1,217 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + "strings" + + celparser "github.com/google/cel-go/parser" + + "github.com/google/cel-go/common" + "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/operators" + + apisharding "k8s.io/apimachinery/pkg/sharding" +) + +// Parse parses a CEL-based shard selector expression into a Selector. +// +// The expression format is: +// +// shardRange(object.metadata.uid, '0x0', '0x8000000000000000') +// shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(...) +// +// Only the shardRange() function and || operator are permitted. The CEL +// expression is parsed but never evaluated — the AST is walked to extract +// shard range requirements. +func Parse(expr string) (apisharding.Selector, error) { + expr = strings.TrimSpace(expr) + if expr == "" { + return nil, fmt.Errorf("empty shard selector is not allowed; omit the parameter for unfiltered lists") + } + + p, err := celparser.NewParser(celparser.Macros( /* no macros */ )) + if err != nil { + return nil, fmt.Errorf("failed to create CEL parser: %w", err) + } + + parsed, errs := p.Parse(common.NewTextSource(expr)) + if errs != nil && len(errs.GetErrors()) > 0 { + return nil, fmt.Errorf("CEL parse error: %s", errs.GetErrors()[0].Message) + } + + reqs, err := walkExpr(parsed.Expr()) + if err != nil { + return nil, err + } + + // Validate that all requirements use the same field key. + for i := 1; i < len(reqs); i++ { + if reqs[i].Key != reqs[0].Key { + return nil, fmt.Errorf("all shard ranges must use the same field, got %q and %q", reqs[0].Key, reqs[i].Key) + } + } + + return apisharding.NewSelector(reqs...), nil +} + +// walkExpr recursively walks the CEL AST and extracts ShardRangeRequirement values. +// The root expression must be either a shardRange() call or a chain of || operators +// combining shardRange() calls. +func walkExpr(e ast.Expr) ([]apisharding.ShardRangeRequirement, error) { + if e.Kind() == ast.CallKind { + call := e.AsCall() + fn := call.FunctionName() + + if fn == operators.LogicalOr { + // _||_ operator: recurse into both sides + args := call.Args() + if len(args) != 2 { + return nil, fmt.Errorf("|| operator requires exactly 2 arguments") + } + left, err := walkExpr(args[0]) + if err != nil { + return nil, err + } + right, err := walkExpr(args[1]) + if err != nil { + return nil, err + } + return append(left, right...), nil + } + + if fn == "shardRange" { + req, err := parseShardRangeCall(call) + if err != nil { + return nil, err + } + return []apisharding.ShardRangeRequirement{req}, nil + } + + return nil, fmt.Errorf("unsupported function %q; only shardRange() and || are allowed", fn) + } + + return nil, fmt.Errorf("unexpected expression kind %v; expected shardRange() call or || operator", e.Kind()) +} + +// parseShardRangeCall extracts a ShardRangeRequirement from a shardRange(field, start, end) call. +func parseShardRangeCall(call ast.CallExpr) (apisharding.ShardRangeRequirement, error) { + args := call.Args() + if len(args) != 3 { + return apisharding.ShardRangeRequirement{}, fmt.Errorf("shardRange() requires exactly 3 arguments, got %d", len(args)) + } + + // Arg 0: field path (select chain like object.metadata.uid) + fieldPath, err := extractFieldPath(args[0]) + if err != nil { + return apisharding.ShardRangeRequirement{}, fmt.Errorf("shardRange() first argument: %w", err) + } + + // Validate field path + switch fieldPath { + case "object.metadata.uid", "object.metadata.namespace": + // ok + default: + return apisharding.ShardRangeRequirement{}, fmt.Errorf("unsupported field path %q; supported: object.metadata.uid, object.metadata.namespace", fieldPath) + } + + // Arg 1: hex start (string literal) + hexStart, err := extractHexLiteral(args[1], "hexStart") + if err != nil { + return apisharding.ShardRangeRequirement{}, err + } + + // Arg 2: hex end (string literal) + hexEnd, err := extractHexLiteral(args[2], "hexEnd") + if err != nil { + return apisharding.ShardRangeRequirement{}, err + } + + // Validate start < end using the canonical hex comparison. + if !apisharding.HexLess(hexStart, hexEnd) { + return apisharding.ShardRangeRequirement{}, fmt.Errorf("shard range start %s must be less than end %s", hexStart, hexEnd) + } + + return apisharding.ShardRangeRequirement{ + Key: fieldPath, + Start: hexStart, + End: hexEnd, + }, nil +} + +// extractFieldPath walks a select chain (object.metadata.uid) and returns the dot-joined path. +func extractFieldPath(e ast.Expr) (string, error) { + switch e.Kind() { + case ast.IdentKind: + return e.AsIdent(), nil + case ast.SelectKind: + sel := e.AsSelect() + operand, err := extractFieldPath(sel.Operand()) + if err != nil { + return "", err + } + return operand + "." + sel.FieldName(), nil + default: + return "", fmt.Errorf("expected field path (e.g. object.metadata.uid), got expression kind %v", e.Kind()) + } +} + +// extractHexLiteral extracts a hex string from a CEL string literal. +// The literal must be a single-quoted string like '0xff' with a 0x prefix. +func extractHexLiteral(e ast.Expr, name string) (string, error) { + if e.Kind() != ast.LiteralKind { + return "", fmt.Errorf("%s must be a string literal (e.g. '0xff'), got expression kind %v", name, e.Kind()) + } + + val := e.AsLiteral() + s, ok := val.Value().(string) + if !ok { + return "", fmt.Errorf("%s must be a string literal, got %T", name, val.Value()) + } + + if !strings.HasPrefix(s, "0x") { + return "", fmt.Errorf("%s must have '0x' prefix, got %q", name, s) + } + + hex := s[2:] + if hex == "" { + return "", fmt.Errorf("%s: hex value is required after '0x'", name) + } + if len(hex) > 17 { + return "", fmt.Errorf("%s: hex value too long (%d chars, max 17): %q", name, len(hex), hex) + } + + for _, c := range hex { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') { + return "", fmt.Errorf("%s: invalid hex character %q in %q", name, string(c), s) + } + } + + // Require exactly 16 hex digits, except for the special case + // "0x10000000000000000" (2^64) which is the exclusive upper bound. + if len(hex) == 17 { + if s != "0x10000000000000000" { + return "", fmt.Errorf("%s: 17-digit hex value must be '0x10000000000000000' (2^64), got %q", name, s) + } + } else if len(hex) != 16 { + padded := "0x" + strings.Repeat("0", 16-len(hex)) + hex + return "", fmt.Errorf("%s must be a 0x-prefixed 16-digit hex value, got %q (did you mean %q?)", name, s, padded) + } + + return s, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/sharding/parser_test.go b/staging/src/k8s.io/apiserver/pkg/sharding/parser_test.go new file mode 100644 index 0000000000000..bf412a45bc193 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/sharding/parser_test.go @@ -0,0 +1,237 @@ +/* +Copyright 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 sharding + +import ( + "testing" + + apisharding "k8s.io/apimachinery/pkg/sharding" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + input string + wantErr bool + wantReq []apisharding.ShardRangeRequirement + }{ + { + name: "empty string", + input: "", + wantErr: true, + }, + { + name: "single requirement with uid", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x8000000000000000"}, + }, + }, + { + name: "full range with 2^64 end", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '0x10000000000000000')", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x10000000000000000"}, + }, + }, + { + name: "namespace field", + input: "shardRange(object.metadata.namespace, '0x00000000000000aa', '0x00000000000000ff')", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.namespace", Start: "0x00000000000000aa", End: "0x00000000000000ff"}, + }, + }, + { + name: "two requirements with ||", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x8000000000000000"}, + {Key: "object.metadata.uid", Start: "0x8000000000000000", End: "0x10000000000000000"}, + }, + }, + { + name: "three requirements with ||", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '0x0000000000000005') || shardRange(object.metadata.uid, '0x0000000000000005', '0x000000000000000a') || shardRange(object.metadata.uid, '0x000000000000000a', '0x000000000000000f')", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x0000000000000005"}, + {Key: "object.metadata.uid", Start: "0x0000000000000005", End: "0x000000000000000a"}, + {Key: "object.metadata.uid", Start: "0x000000000000000a", End: "0x000000000000000f"}, + }, + }, + { + name: "no spaces", + input: "shardRange(object.metadata.uid,'0x0000000000000000','0x8000000000000000')", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x8000000000000000"}, + }, + }, + { + name: "extra whitespace", + input: " shardRange( object.metadata.uid , '0x0000000000000000' , '0x8000000000000000' ) ", + wantReq: []apisharding.ShardRangeRequirement{ + {Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x8000000000000000"}, + }, + }, + // Error cases + { + name: "missing 0x prefix on start", + input: "shardRange(object.metadata.uid, '0000000000000000', '0x8000000000000000')", + wantErr: true, + }, + { + name: "missing 0x prefix on end", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '8000000000000000')", + wantErr: true, + }, + { + name: "empty after 0x prefix", + input: "shardRange(object.metadata.uid, '0x', '0x8000000000000000')", + wantErr: true, + }, + { + name: "name field unsupported", + input: "shardRange(object.metadata.name, '0x00', '0x80')", + wantErr: true, + }, + { + name: "unsupported field", + input: "shardRange(object.metadata.labels, '0x00', '0x80')", + wantErr: true, + }, + { + name: "unknown function", + input: "invalidFunc(object.metadata.uid, '0x00', '0x80')", + wantErr: true, + }, + { + name: "hex too long (18 chars)", + input: "shardRange(object.metadata.uid, '0x000000000000000000', '0x80')", + wantErr: true, + }, + { + name: "invalid hex char", + input: "shardRange(object.metadata.uid, '0x0g', '0x80')", + wantErr: true, + }, + { + name: "&& operator not allowed", + input: "shardRange(object.metadata.uid, '0x0', '0x8') && shardRange(object.metadata.uid, '0x8', '0xf')", + wantErr: true, + }, + { + name: "integer literal instead of string", + input: "shardRange(object.metadata.uid, 0, 8)", + wantErr: true, + }, + { + name: "comparison operator", + input: "object.metadata.uid > '0x0'", + wantErr: true, + }, + { + name: "wrong number of arguments", + input: "shardRange(object.metadata.uid, '0x0')", + wantErr: true, + }, + { + name: "nested function call", + input: "shardRange(object.metadata.uid, hex(0), '0x8')", + wantErr: true, + }, + { + name: "mixed field keys", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000') || shardRange(object.metadata.namespace, '0x8000000000000000', '0x10000000000000000')", + wantErr: true, + }, + { + name: "short hex rejected", + input: "shardRange(object.metadata.uid, '0x0', '0x8000000000000000')", + wantErr: true, + }, + { + name: "short hex rejected on end", + input: "shardRange(object.metadata.uid, '0x0000000000000000', '0xff')", + wantErr: true, + }, + { + name: "start equals end", + input: "shardRange(object.metadata.uid, '0x8000000000000000', '0x8000000000000000')", + wantErr: true, + }, + { + name: "start greater than end", + input: "shardRange(object.metadata.uid, '0xffffffffffffffff', '0x0000000000000000')", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sel, err := Parse(tt.input) + if tt.wantErr { + if err == nil { + t.Errorf("Parse(%q) expected error, got nil", tt.input) + } + return + } + if err != nil { + t.Fatalf("Parse(%q) unexpected error: %v", tt.input, err) + } + + reqs := sel.Requirements() + if len(reqs) != len(tt.wantReq) { + t.Fatalf("Parse(%q) got %d requirements, want %d", tt.input, len(reqs), len(tt.wantReq)) + } + for i, req := range reqs { + if req != tt.wantReq[i] { + t.Errorf("Parse(%q) requirement[%d] = %+v, want %+v", tt.input, i, req, tt.wantReq[i]) + } + } + }) + } +} + +func TestParseRoundTrip(t *testing.T) { + tests := []string{ + "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')", + "shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')", + "shardRange(object.metadata.uid, '0x0000000000000000', '0x10000000000000000')", + "shardRange(object.metadata.namespace, '0x00000000000000aa', '0x00000000000000ff')", + "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')", + } + + for _, input := range tests { + t.Run(input, func(t *testing.T) { + sel, err := Parse(input) + if err != nil { + t.Fatalf("Parse(%q) error: %v", input, err) + } + output := sel.String() + if output != input { + t.Errorf("Parse(%q).String() = %q, want %q", input, output, input) + } + // Round-trip — parse(string(parse(input))) should be stable. + sel2, err := Parse(output) + if err != nil { + t.Fatalf("Parse(%q) (round-trip) error: %v", output, err) + } + if sel.String() != sel2.String() { + t.Errorf("round-trip unstable: %q -> %q -> %q", input, output, sel2.String()) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/OWNERS b/staging/src/k8s.io/apiserver/pkg/storage/OWNERS index b68cef1fe531d..39822f47defbd 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/OWNERS +++ b/staging/src/k8s.io/apiserver/pkg/storage/OWNERS @@ -3,6 +3,7 @@ approvers: - liggitt - wojtek-t + - serathius reviewers: - smarterclayton - wojtek-t @@ -14,6 +15,7 @@ reviewers: - enj - stevekuznetsov - MadhavJivrajani + - serathius emeritus_approvers: - xiang90 - timothysc diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher.go index a74cf8d4a3e23..8711596a3cf75 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher.go @@ -371,10 +371,10 @@ func (c *cacheWatcher) convertToWatchEvent(event *watchCacheEvent) *watch.Event return e } - curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields) + curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields, event.Object) oldObjPasses := false if event.PrevObject != nil { - oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields) + oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields, event.PrevObject) } if !curObjPasses && !oldObjPasses { // Watcher is not interested in that object. @@ -434,7 +434,7 @@ func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) { } func (c *cacheWatcher) processInterval(ctx context.Context, cacheInterval *watchCacheInterval, resourceVersion uint64) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer close(c.result) defer c.Stop() diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher_test.go index 80d1b59657edc..7d68dfbc61e6f 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cache_watcher_test.go @@ -29,13 +29,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/cacher/store" utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol" "k8s.io/client-go/tools/cache" testingclock "k8s.io/utils/clock/testing" + + cachertesting "k8s.io/apiserver/pkg/storage/cacher/testing" ) // verifies the cacheWatcher.process goroutine is properly cleaned up even if @@ -44,7 +48,7 @@ func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) { var lock sync.RWMutex var w *cacheWatcher count := 0 - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, runtime.Object) bool { return true } forget := func(drainWatcher bool) { lock.Lock() defer lock.Unlock() @@ -74,7 +78,7 @@ func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) { } func TestCacheWatcherHandlesFiltering(t *testing.T) { - filter := func(_ string, _ labels.Set, field fields.Set) bool { + filter := func(_ string, _ labels.Set, field fields.Set, _ runtime.Object) bool { return field["spec.nodeName"] == "host" } forget := func(bool) {} @@ -206,7 +210,7 @@ TestCase: func TestCacheWatcherStoppedInAnotherGoroutine(t *testing.T) { var w *cacheWatcher done := make(chan struct{}) - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, runtime.Object) bool { return true } forget := func(drainWatcher bool) { w.setDrainInputBufferLocked(drainWatcher) w.stopLocked() @@ -246,7 +250,7 @@ func TestCacheWatcherStoppedInAnotherGoroutine(t *testing.T) { } func TestCacheWatcherStoppedOnDestroy(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -288,7 +292,7 @@ func TestCacheWatcherStoppedOnDestroy(t *testing.T) { func TestResourceVersionAfterInitEvents(t *testing.T) { const numObjects = 10 - store := cache.NewIndexer(storeElementKey, storeElementIndexers(nil)) + store := cache.NewIndexer(store.ElementKey, store.ElementIndexers(nil)) for i := 0; i < numObjects; i++ { elem := makeTestStoreElement(makeTestPod(fmt.Sprintf("pod-%d", i), uint64(i))) @@ -300,7 +304,7 @@ func TestResourceVersionAfterInitEvents(t *testing.T) { t.Fatal(err) } - filter := func(_ string, _ labels.Set, _ fields.Set) bool { return true } + filter := func(_ string, _ labels.Set, _ fields.Set, _ runtime.Object) bool { return true } forget := func(_ bool) {} deadline := time.Now().Add(time.Minute) w := newCacheWatcher(numObjects+1, filter, forget, storage.APIObjectVersioner{}, deadline, true, schema.GroupResource{Resource: "pods"}, "") @@ -340,7 +344,7 @@ func TestResourceVersionAfterInitEvents(t *testing.T) { } func TestTimeBucketWatchersBasic(t *testing.T) { - filter := func(_ string, _ labels.Set, _ fields.Set) bool { + filter := func(_ string, _ labels.Set, _ fields.Set, _ runtime.Object) bool { return true } forget := func(bool) {} @@ -401,7 +405,7 @@ func TestCacheWatcherDraining(t *testing.T) { var lock sync.RWMutex var w *cacheWatcher count := 0 - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, runtime.Object) bool { return true } forget := func(drainWatcher bool) { lock.Lock() defer lock.Unlock() @@ -442,7 +446,7 @@ func TestCacheWatcherDrainingRequestedButNotDrained(t *testing.T) { var lock sync.RWMutex var w *cacheWatcher count := 0 - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, runtime.Object) bool { return true } forget := func(drainWatcher bool) { lock.Lock() defer lock.Unlock() @@ -476,7 +480,7 @@ func TestCacheWatcherDrainingNoBookmarkAfterResourceVersionReceived(t *testing.T var lock sync.RWMutex var w *cacheWatcher count := 0 - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, runtime.Object) bool { return true } forget := func(drainWatcher bool) { lock.Lock() defer lock.Unlock() @@ -540,7 +544,7 @@ func TestCacheWatcherDrainingNoBookmarkAfterResourceVersionSent(t *testing.T) { watchInitializationSignal := utilflowcontrol.NewInitializationSignal() ctx := utilflowcontrol.WithInitializationSignal(context.Background(), watchInitializationSignal) count := 0 - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, runtime.Object) bool { return true } forget := func(drainWatcher bool) { lock.Lock() defer lock.Unlock() @@ -603,7 +607,7 @@ func TestCacheWatcherDrainingNoBookmarkAfterResourceVersionSent(t *testing.T) { func TestBookmarkAfterResourceVersionWatchers(t *testing.T) { newWatcher := func(id string, deadline time.Time) *cacheWatcher { - w := newCacheWatcher(0, func(_ string, _ labels.Set, _ fields.Set) bool { return true }, func(bool) {}, storage.APIObjectVersioner{}, deadline, true, schema.GroupResource{Resource: "pods"}, id) + w := newCacheWatcher(0, func(_ string, _ labels.Set, _ fields.Set, _ runtime.Object) bool { return true }, func(bool) {}, storage.APIObjectVersioner{}, deadline, true, schema.GroupResource{Resource: "pods"}, id) w.setBookmarkAfterResourceVersion(10) return w } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go index a5f7ab6d692ad..326ed27c80d16 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go @@ -36,6 +36,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/audit" @@ -45,6 +46,7 @@ import ( "k8s.io/apiserver/pkg/storage/cacher/delegator" "k8s.io/apiserver/pkg/storage/cacher/metrics" "k8s.io/apiserver/pkg/storage/cacher/progress" + "k8s.io/apiserver/pkg/storage/cacher/store" etcdfeature "k8s.io/apiserver/pkg/storage/feature" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/cache" @@ -246,7 +248,7 @@ func (t *watcherBookmarkTimeBuckets) popExpiredWatchersThreadUnsafe() [][]*cache return expiredWatchers } -type filterWithAttrsFunc func(key string, l labels.Set, f fields.Set) bool +type filterWithAttrsFunc func(key string, l labels.Set, f fields.Set, obj runtime.Object) bool type indexedTriggerFunc struct { indexName string @@ -505,6 +507,10 @@ type namespacedName struct { } func (c *Cacher) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) { + ctx, span := tracing.Start(ctx, "cacher.Watch", + attribute.String("audit-id", audit.GetAuditIDTruncated(ctx)), + attribute.Stringer("type", c.groupResource)) + defer span.End(500 * time.Millisecond) key, err := c.prepareKey(key, opts.Recursive) if err != nil { return nil, err @@ -597,7 +603,7 @@ func (c *Cacher) Watch(ctx context.Context, key string, opts storage.ListOptions // to compute watcher.forget function (which has to happen under lock). watcher := newCacheWatcher( chanSize, - filterWithAttrsAndPrefixFunction(key, pred), + filterWithAttrsAndPrefixFunction(key, pred, c.groupResource), emptyFunc, c.versioner, deadline, @@ -671,6 +677,15 @@ func (c *Cacher) Watch(ctx context.Context, key string, opts storage.ListOptions return newImmediateCloseWatcher(), nil } + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) && pred.ShardSelector != nil && !pred.ShardSelector.Empty() { + metrics.RecordShardedWatchStarted(c.groupResource) + originalForget := watcher.forget + watcher.forget = func(drainWatcher bool) { + metrics.RecordShardedWatchStopped(c.groupResource) + originalForget(drainWatcher) + } + } + go watcher.processInterval(ctx, cacheInterval, requiredResourceVersion) return watcher, nil } @@ -696,9 +711,9 @@ func (c *Cacher) Get(ctx context.Context, key string, opts storage.GetOptions, o } if exists { - elem, ok := obj.(*storeElement) + elem, ok := obj.(*store.Element) if !ok { - return fmt.Errorf("non *storeElement returned from storage: %v", obj) + return fmt.Errorf("non *store.Element returned from storage: %v", obj) } objVal.Set(reflect.ValueOf(elem.Object).Elem()) } else { @@ -785,11 +800,19 @@ func (c *Cacher) GetList(ctx context.Context, key string, opts storage.ListOptio var hasMoreListItems bool limit := computeListLimit(opts) for i, obj := range resp.Items { - elem, ok := obj.(*storeElement) + elem, ok := obj.(*store.Element) if !ok { - return fmt.Errorf("non *storeElement returned from storage: %v", obj) + return fmt.Errorf("non *store.Element returned from storage: %v", obj) } - if opts.Predicate.MatchesObjectAttributes(elem.Labels, elem.Fields) { + shardMatch := true + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + var err error + shardMatch, err = opts.Predicate.MatchesSharding(elem.Object) + if err != nil { + return fmt.Errorf("shard matching failed: %w", err) + } + } + if shardMatch && opts.Predicate.MatchesObjectAttributes(elem.Labels, elem.Fields) { selectedObjects = append(selectedObjects, elem.Object) lastSelectedObjectKey = elem.Key } @@ -820,6 +843,9 @@ func (c *Cacher) GetList(ctx context.Context, key string, opts storage.ListOptio return err } } + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + opts.Predicate.SetShardInfoOnList(listObj) + } metrics.RecordListCacheMetrics(c.groupResource, indexUsed, len(resp.Items), listVal.Len()) return nil } @@ -1198,11 +1224,23 @@ func forgetWatcher(c *Cacher, w *cacheWatcher, index int, scope namespacedName, } } -func filterWithAttrsAndPrefixFunction(key string, p storage.SelectionPredicate) filterWithAttrsFunc { - filterFunc := func(objKey string, label labels.Set, field fields.Set) bool { +func filterWithAttrsAndPrefixFunction(key string, p storage.SelectionPredicate, groupResource schema.GroupResource) filterWithAttrsFunc { + isSharded := utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) && p.ShardSelector != nil && !p.ShardSelector.Empty() + filterFunc := func(objKey string, label labels.Set, field fields.Set, obj runtime.Object) bool { if !hasPathPrefix(objKey, key) { return false } + if isSharded { + matches, err := p.MatchesSharding(obj) + if err != nil { + utilruntime.HandleError(fmt.Errorf("shard matching failed for %v: %w", groupResource, err)) + return false + } + if !matches { + metrics.RecordWatchFilteredEvent(groupResource) + return false + } + } return p.MatchesObjectAttributes(label, field) } return filterFunc @@ -1238,6 +1276,7 @@ func (c *Cacher) getBookmarkAfterResourceVersionLockedFunc(parsedResourceVersion } } +// isListWatchRequest is mirrored in staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go func isListWatchRequest(opts storage.ListOptions) bool { return opts.SendInitialEvents != nil && *opts.SendInitialEvents && opts.Predicate.AllowWatchBookmarks } @@ -1258,7 +1297,7 @@ func (c *Cacher) getWatchCacheResourceVersion(ctx context.Context, parsedWatchRe return parsedWatchResourceVersion, nil } // legacy case - if !utilfeature.DefaultFeatureGate.Enabled(features.WatchFromStorageWithoutResourceVersion) && opts.SendInitialEvents == nil && opts.ResourceVersion == "" { + if opts.SendInitialEvents == nil && opts.ResourceVersion == "" { return 0, nil } rv, err := c.storage.GetCurrentResourceVersion(ctx) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher_whitebox_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher_whitebox_test.go index 215fc13ac6038..8005bf1ae7086 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher_whitebox_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher_whitebox_test.go @@ -41,6 +41,8 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/sharding" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" @@ -50,6 +52,7 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/cacher/delegator" "k8s.io/apiserver/pkg/storage/cacher/metrics" + "k8s.io/apiserver/pkg/storage/cacher/store" etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" etcdfeature "k8s.io/apiserver/pkg/storage/feature" storagetesting "k8s.io/apiserver/pkg/storage/testing" @@ -62,6 +65,8 @@ import ( "k8s.io/utils/clock" testingclock "k8s.io/utils/clock/testing" "k8s.io/utils/ptr" + + cachertesting "k8s.io/apiserver/pkg/storage/cacher/testing" ) func newTestCacherWithoutSyncing(s storage.Interface, c clock.WithTicker) (*Cacher, storage.Versioner, error) { @@ -112,130 +117,6 @@ func newTestCacher(s storage.Interface) (*Cacher, storage.Versioner, error) { return cacher, versioner, nil } -type dummyStorage struct { - sync.RWMutex - getlistErr error - watchErr error - getListFn func(_ context.Context, _ string, _ storage.ListOptions, listObj runtime.Object) error - getRVFn func(_ context.Context) (uint64, error) - watchFn func(_ context.Context, _ string, _ storage.ListOptions) (watch.Interface, error) - - // use getRequestWatchProgressCounter when reading - // the value of the counter - requestWatchProgressCounter int -} - -func (d *dummyStorage) RequestWatchProgress(ctx context.Context) error { - d.Lock() - defer d.Unlock() - d.requestWatchProgressCounter++ - return nil -} - -func (d *dummyStorage) getRequestWatchProgressCounter() int { - d.RLock() - defer d.RUnlock() - return d.requestWatchProgressCounter -} - -func (d *dummyStorage) CompactRevision() int64 { - return 0 -} - -func (d *dummyStorage) IsWatchListSemanticsUnSupported() bool { - return true -} - -type dummyWatch struct { - ch chan watch.Event -} - -func (w *dummyWatch) ResultChan() <-chan watch.Event { - return w.ch -} - -func (w *dummyWatch) Stop() { - close(w.ch) -} - -func newDummyWatch() watch.Interface { - return &dummyWatch{ - ch: make(chan watch.Event), - } -} - -func (d *dummyStorage) Versioner() storage.Versioner { return nil } -func (d *dummyStorage) Create(_ context.Context, _ string, _, _ runtime.Object, _ uint64) error { - return fmt.Errorf("unimplemented") -} -func (d *dummyStorage) Delete(_ context.Context, _ string, _ runtime.Object, _ *storage.Preconditions, _ storage.ValidateObjectFunc, _ runtime.Object, _ storage.DeleteOptions) error { - return fmt.Errorf("unimplemented") -} -func (d *dummyStorage) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) { - if d.watchFn != nil { - return d.watchFn(ctx, key, opts) - } - d.RLock() - defer d.RUnlock() - - return newDummyWatch(), d.watchErr -} -func (d *dummyStorage) Get(_ context.Context, _ string, _ storage.GetOptions, _ runtime.Object) error { - d.RLock() - defer d.RUnlock() - return d.getlistErr -} -func (d *dummyStorage) GetList(ctx context.Context, resPrefix string, opts storage.ListOptions, listObj runtime.Object) error { - if d.getListFn != nil { - return d.getListFn(ctx, resPrefix, opts, listObj) - } - d.RLock() - defer d.RUnlock() - podList := listObj.(*example.PodList) - podList.ListMeta = metav1.ListMeta{ResourceVersion: "100"} - return d.getlistErr -} -func (d *dummyStorage) GuaranteedUpdate(_ context.Context, _ string, _ runtime.Object, _ bool, _ *storage.Preconditions, _ storage.UpdateFunc, _ runtime.Object) error { - return fmt.Errorf("unimplemented") -} -func (d *dummyStorage) Stats(_ context.Context) (storage.Stats, error) { - return storage.Stats{}, fmt.Errorf("unimplemented") -} -func (d *dummyStorage) EnableResourceSizeEstimation(storage.KeysFunc) error { - return nil -} -func (d *dummyStorage) ReadinessCheck() error { - return nil -} -func (d *dummyStorage) injectGetListError(err error) { - d.Lock() - defer d.Unlock() - - d.getlistErr = err -} - -func (d *dummyStorage) GetCurrentResourceVersion(ctx context.Context) (uint64, error) { - if d.getRVFn != nil { - return d.getRVFn(ctx) - } - return 100, nil -} - -type dummyCacher struct { - dummyStorage - ready bool - - consistent bool -} - -func (d *dummyCacher) Ready() bool { - return d.ready -} - -func (d *dummyCacher) MarkConsistent(consistent bool) { - d.consistent = consistent -} - func TestShouldDelegateList(t *testing.T) { type opts struct { Recursive bool @@ -371,8 +252,8 @@ func TestShouldDelegateList(t *testing.T) { expectBypass = bypass } } - backingStorage := &dummyStorage{} - backingStorage.getListFn = func(_ context.Context, _ string, _ storage.ListOptions, listObj runtime.Object) error { + backingStorage := &cachertesting.MockStorage{} + backingStorage.GetListFn = func(_ context.Context, _ string, _ storage.ListOptions, listObj runtime.Object) error { podList := listObj.(*example.PodList) podList.ListMeta = metav1.ListMeta{ResourceVersion: cacheRV} return nil @@ -567,8 +448,8 @@ apiserver_watch_cache_consistent_read_total{fallback="true", group="", resource= if err := registry.Register(metrics.ConsistentReadTotal); err != nil { t.Errorf("unexpected error: %v", err) } - backingStorage := &dummyStorage{} - backingStorage.getListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + backingStorage := &cachertesting.MockStorage{} + backingStorage.GetListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { podList := listObj.(*example.PodList) podList.ResourceVersion = tc.watchCacheRV return nil @@ -589,7 +470,7 @@ apiserver_watch_cache_consistent_read_total{fallback="true", group="", resource= t.Fatalf("Expected watch cache RV to equal watchCacheRV, got: %d, want: %s", cacher.watchCache.resourceVersion, tc.watchCacheRV) } requestToStorageCount := 0 - backingStorage.getListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + backingStorage.GetListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { requestToStorageCount += 1 podList := listObj.(*example.PodList) if key == cacher.resourcePrefix { @@ -602,7 +483,7 @@ apiserver_watch_cache_consistent_read_total{fallback="true", group="", resource= podList.ResourceVersion = tc.storageRV return nil } - backingStorage.getRVFn = func(_ context.Context) (uint64, error) { + backingStorage.GetRVFn = func(_ context.Context) (uint64, error) { requestToStorageCount += 1 rv, err := strconv.Atoi(tc.storageRV) if err != nil { @@ -685,9 +566,9 @@ func TestMatchExactResourceVersionFallback(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ListFromCacheSnapshot, true) - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} expectStoreRequests := 0 - backingStorage.getListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + backingStorage.GetListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { expectStoreRequests++ podList := listObj.(*example.PodList) switch opts.ResourceVersionMatch { @@ -706,7 +587,7 @@ func TestMatchExactResourceVersionFallback(t *testing.T) { snapshotRequestCount := 0 cacher.watchCache.RWMutex.Lock() cacher.watchCache.snapshots = &fakeSnapshotter{ - getLessOrEqual: func(rv uint64) (orderedLister, bool) { + getLessOrEqual: func(rv uint64) (store.OrderedLister, bool) { snapshotAvailable := tc.snapshotsAvailable[snapshotRequestCount] snapshotRequestCount++ if snapshotAvailable { @@ -744,7 +625,7 @@ func TestMatchExactResourceVersionFallback(t *testing.T) { func TestGetListNonRecursiveCacheBypass(t *testing.T) { featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.33")) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentListFromCache, false) - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -765,7 +646,7 @@ func TestGetListNonRecursiveCacheBypass(t *testing.T) { } // Inject error to underlying layer and check if cacher is not bypassed. - backingStorage.injectGetListError(errDummy) + backingStorage.InjectGetListError(errDummy) err = delegator.GetList(context.TODO(), "/pods/ns", storage.ListOptions{ ResourceVersion: "0", Predicate: pred, @@ -812,9 +693,9 @@ func TestGetListNonRecursiveCacheWithConsistentListFromCache(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var getListCount, getCurrentRVCount int - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} - backingStorage.getListFn = func(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + backingStorage.GetListFn = func(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { getListCount++ if tc.injectRVError { return errDummy @@ -824,7 +705,7 @@ func TestGetListNonRecursiveCacheWithConsistentListFromCache(t *testing.T) { return nil } - backingStorage.getRVFn = func(ctx context.Context) (uint64, error) { + backingStorage.GetRVFn = func(ctx context.Context) (uint64, error) { getCurrentRVCount++ rv := uint64(100) err := error(nil) @@ -905,7 +786,7 @@ func TestGetListNonRecursiveCacheWithConsistentListFromCache(t *testing.T) { } } func TestGetCacheBypass(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -923,7 +804,7 @@ func TestGetCacheBypass(t *testing.T) { } // Inject error to underlying layer and check if cacher is not bypassed. - backingStorage.injectGetListError(errDummy) + backingStorage.InjectGetListError(errDummy) err = delegator.Get(context.TODO(), "/pods/ns/pod-0", storage.GetOptions{ IgnoreNotFound: true, ResourceVersion: "0", @@ -942,7 +823,7 @@ func TestGetCacheBypass(t *testing.T) { } func TestWatchCacheBypass(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -981,7 +862,7 @@ func TestTooManyRequestsNotReturned(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ResilientWatchCacheInitialization, false) dummyErr := fmt.Errorf("dummy") - backingStorage := &dummyStorage{getlistErr: dummyErr} + backingStorage := &cachertesting.MockStorage{GetListErr: dummyErr} cacher, _, err := newTestCacherWithoutSyncing(backingStorage, clock.RealClock{}) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1109,7 +990,7 @@ func TestWatchNotHangingOnStartupFailure(t *testing.T) { // Configure cacher so that it can't initialize, because of // constantly failing lists to the underlying storage. dummyErr := fmt.Errorf("dummy") - backingStorage := &dummyStorage{watchErr: dummyErr} + backingStorage := &cachertesting.MockStorage{WatchErr: dummyErr} cacher, _, err := newTestCacherWithoutSyncing(backingStorage, testingclock.NewFakeClock(time.Now())) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1140,7 +1021,7 @@ func TestWatchNotHangingOnStartupFailure(t *testing.T) { } func TestWatcherNotGoingBackInTime(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, v, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1228,7 +1109,7 @@ func TestWatcherNotGoingBackInTime(t *testing.T) { } func TestCacherDontAcceptRequestsStopped(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1319,8 +1200,8 @@ func TestCacherDontMissEventsOnReinitialization(t *testing.T) { } listCalls, watchCalls := 0, 0 - backingStorage := &dummyStorage{ - getListFn: func(_ context.Context, _ string, _ storage.ListOptions, listObj runtime.Object) error { + backingStorage := &cachertesting.MockStorage{ + GetListFn: func(_ context.Context, _ string, _ storage.ListOptions, listObj runtime.Object) error { podList := listObj.(*example.PodList) var err error switch listCalls { @@ -1335,7 +1216,7 @@ func TestCacherDontMissEventsOnReinitialization(t *testing.T) { listCalls++ return err }, - watchFn: func(_ context.Context, _ string, _ storage.ListOptions) (watch.Interface, error) { + WatchFn: func(_ context.Context, _ string, _ storage.ListOptions) (watch.Interface, error) { var w *watch.FakeWatcher var err error switch watchCalls { @@ -1417,7 +1298,7 @@ func TestCacherDontMissEventsOnReinitialization(t *testing.T) { } func TestCacherNoLeakWithMultipleWatchers(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1498,7 +1379,7 @@ func TestCacherNoLeakWithMultipleWatchers(t *testing.T) { } func testCacherSendBookmarkEvents(t *testing.T, allowWatchBookmarks, expectedBookmarks bool) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1598,7 +1479,7 @@ func TestInitialEventsEndBookmark(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true) forceRequestWatchProgressSupport(t) - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1695,7 +1576,7 @@ func TestInitialEventsEndBookmark(t *testing.T) { } func TestCacherSendsMultipleWatchBookmarks(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1770,7 +1651,7 @@ func TestCacherSendsMultipleWatchBookmarks(t *testing.T) { } func TestDispatchingBookmarkEventsWithConcurrentStop(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1846,7 +1727,7 @@ func TestDispatchingBookmarkEventsWithConcurrentStop(t *testing.T) { } func TestBookmarksOnResourceVersionUpdates(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -1928,7 +1809,7 @@ func (f *fakeTimeBudget) takeAvailable() time.Duration { func (f *fakeTimeBudget) returnUnused(_ time.Duration) {} func TestStartingResourceVersion(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -2009,7 +1890,7 @@ func TestStartingResourceVersion(t *testing.T) { } func TestDispatchEventWillNotBeBlockedByTimedOutWatcher(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -2152,7 +2033,7 @@ func verifyEvents(t *testing.T, w watch.Interface, events []watch.Event, strictO } func TestCachingDeleteEvents(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -2235,7 +2116,7 @@ func TestCachingDeleteEvents(t *testing.T) { } func testCachingObjects(t *testing.T, watchersCount int) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -2332,7 +2213,7 @@ func TestCachingObjects(t *testing.T) { } func TestCacheIntervalInvalidationStopsWatch(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) if err != nil { t.Fatalf("Couldn't create cacher: %v", err) @@ -2427,8 +2308,8 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { scenarios := []struct { name string opts storage.ListOptions - backingStorage *dummyStorage - verifyBackingStore func(t *testing.T, s *dummyStorage) + backingStorage *cachertesting.MockStorage + verifyBackingStore func(t *testing.T, s *cachertesting.MockStorage) }{ { name: "allowWatchBookmarks=true, sendInitialEvents=true, RV=105", @@ -2441,8 +2322,8 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { SendInitialEvents: ptr.To(true), ResourceVersion: "105", }, - verifyBackingStore: func(t *testing.T, s *dummyStorage) { - require.NotEqual(t, 0, s.getRequestWatchProgressCounter(), "expected store.RequestWatchProgressCounter to be > 0. It looks like watch progress wasn't requested!") + verifyBackingStore: func(t *testing.T, s *cachertesting.MockStorage) { + require.NotEqual(t, 0, s.GetRequestWatchProgressCounter(), "expected store.RequestWatchProgressCounter to be > 0. It looks like watch progress wasn't requested!") }, }, @@ -2456,10 +2337,10 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { }(), SendInitialEvents: ptr.To(true), }, - backingStorage: func() *dummyStorage { + backingStorage: func() *cachertesting.MockStorage { hasBeenPrimed := false - s := &dummyStorage{} - s.getListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + s := &cachertesting.MockStorage{} + s.GetListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { listAccessor, err := meta.ListAccessor(listObj) if err != nil { return err @@ -2474,7 +2355,7 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { listAccessor.SetResourceVersion("105") return nil } - s.getRVFn = func(_ context.Context) (uint64, error) { + s.GetRVFn = func(_ context.Context) (uint64, error) { // the first call to this function // primes the cacher if !hasBeenPrimed { @@ -2485,8 +2366,8 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { } return s }(), - verifyBackingStore: func(t *testing.T, s *dummyStorage) { - require.NotEqual(t, 0, s.getRequestWatchProgressCounter(), "expected store.RequestWatchProgressCounter to be > 0. It looks like watch progress wasn't requested!") + verifyBackingStore: func(t *testing.T, s *cachertesting.MockStorage) { + require.NotEqual(t, 0, s.GetRequestWatchProgressCounter(), "expected store.RequestWatchProgressCounter to be > 0. It looks like watch progress wasn't requested!") }, }, @@ -2500,10 +2381,10 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { }(), SendInitialEvents: ptr.To(true), }, - backingStorage: func() *dummyStorage { + backingStorage: func() *cachertesting.MockStorage { hasBeenPrimed := false - s := &dummyStorage{} - s.getListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + s := &cachertesting.MockStorage{} + s.GetListFn = func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { listAccessor, err := meta.ListAccessor(listObj) if err != nil { return err @@ -2518,7 +2399,7 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { listAccessor.SetResourceVersion("105") return nil } - s.getRVFn = func(_ context.Context) (uint64, error) { + s.GetRVFn = func(_ context.Context) (uint64, error) { // the first call to this function // primes the cacher if !hasBeenPrimed { @@ -2529,8 +2410,8 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { } return s }(), - verifyBackingStore: func(t *testing.T, s *dummyStorage) { - require.NotEqual(t, 0, s.getRequestWatchProgressCounter(), "expected store.RequestWatchProgressCounter to be > 0. It looks like watch progress wasn't requested!") + verifyBackingStore: func(t *testing.T, s *cachertesting.MockStorage) { + require.NotEqual(t, 0, s.GetRequestWatchProgressCounter(), "expected store.RequestWatchProgressCounter to be > 0. It looks like watch progress wasn't requested!") }, }, } @@ -2538,11 +2419,11 @@ func TestWaitUntilWatchCacheFreshAndForceAllEvents(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { t.Parallel() - var backingStorage *dummyStorage + var backingStorage *cachertesting.MockStorage if scenario.backingStorage != nil { backingStorage = scenario.backingStorage } else { - backingStorage = &dummyStorage{} + backingStorage = &cachertesting.MockStorage{} } cacher, _, err := newTestCacher(backingStorage) if err != nil { @@ -2616,7 +2497,7 @@ func (m fakeStorage) GetList(ctx context.Context, key string, opts storage.ListO return nil } func (m fakeStorage) Watch(_ context.Context, _ string, _ storage.ListOptions) (watch.Interface, error) { - return newDummyWatch(), nil + return cachertesting.NewMockWatch(), nil } func BenchmarkCacher_GetList(b *testing.B) { @@ -2709,7 +2590,7 @@ func BenchmarkCacher_GetList(b *testing.B) { // a bookmark event will be delivered even if the cacher has not received an event. func TestWatchListIsSynchronisedWhenNoEventsFromStoreReceived(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true) - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) require.NoError(t, err, "failed to create cacher") defer cacher.Stop() @@ -2741,7 +2622,7 @@ func TestWatchListIsSynchronisedWhenNoEventsFromStoreReceived(t *testing.T) { } func TestForgetWatcher(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) require.NoError(t, err) defer cacher.Stop() @@ -2769,7 +2650,7 @@ func TestForgetWatcher(t *testing.T) { } w := newCacheWatcher( 0, - func(_ string, _ labels.Set, _ fields.Set) bool { return true }, + func(_ string, _ labels.Set, _ fields.Set, _ runtime.Object) bool { return true }, nil, storage.APIObjectVersioner{}, testingclock.NewFakeClock(time.Now()).Now().Add(2*time.Minute), @@ -2940,7 +2821,7 @@ func TestGetWatchCacheResourceVersion(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) require.NoError(t, err, "couldn't create cacher") defer cacher.Stop() @@ -3134,7 +3015,7 @@ func TestGetBookmarkAfterResourceVersionLockedFunc(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} cacher, _, err := newTestCacher(backingStorage) require.NoError(t, err, "couldn't create cacher") @@ -3389,7 +3270,7 @@ func TestRetryAfterForUnreadyCache(t *testing.T) { if !utilfeature.DefaultFeatureGate.Enabled(features.ResilientWatchCacheInitialization) { t.Skipf("the test requires %v to be enabled", features.ResilientWatchCacheInitialization) } - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} clock := testingclock.NewFakeClock(time.Now()) cacher, _, err := newTestCacherWithoutSyncing(backingStorage, clock) if err != nil { @@ -3433,7 +3314,7 @@ func TestWatchListSemanticsSimple(t *testing.T) { // The dummyStore doesn’t support WatchList semantics, // so we don’t need to prepare a response. - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} clock := testingclock.NewFakeClock(time.Now()) cacher, _, err := newTestCacherWithoutSyncing(backingStorage, clock) if err != nil { @@ -3446,3 +3327,268 @@ func TestWatchListSemanticsSimple(t *testing.T) { t.Fatalf("error waiting for the cache to be ready, err: %v", err) } } + +type fakeOrderedLister struct { +} + +func (f fakeOrderedLister) Add(obj interface{}) error { return nil } +func (f fakeOrderedLister) Update(obj interface{}) error { return nil } +func (f fakeOrderedLister) Delete(obj interface{}) error { return nil } +func (f fakeOrderedLister) Clone() store.OrderedLister { return f } +func (f fakeOrderedLister) ListPrefix(prefixKey, continueKey string) []interface{} { + return nil +} +func (f fakeOrderedLister) Count(prefixKey, continueKey string) int { return 0 } + +type fakeSnapshotter struct { + getLessOrEqual func(rv uint64) (store.OrderedLister, bool) +} + +var _ store.Snapshotter = (*fakeSnapshotter)(nil) + +func (f *fakeSnapshotter) Reset() {} +func (f *fakeSnapshotter) GetLessOrEqual(rv uint64) (store.OrderedLister, bool) { + if f.getLessOrEqual == nil { + return nil, false + } + return f.getLessOrEqual(rv) +} +func (f *fakeSnapshotter) Add(rv uint64, indexer store.OrderedLister) {} +func (f *fakeSnapshotter) RemoveLess(rv uint64) {} +func (f *fakeSnapshotter) Len() int { + return 0 +} + +// --- Sharding unit tests for filterWithAttrsAndPrefixFunction --- + +// selectorIncludingUID builds a shard selector whose range contains the hash of uid. +func selectorIncludingUID(uid string) sharding.Selector { + hash := "0x" + sharding.HashField(uid) + return sharding.NewSelector(sharding.ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: hash, + End: incrementHex(hash), + }) +} + +// selectorExcludingUID builds a shard selector whose range does NOT contain the hash of uid. +func selectorExcludingUID(uid string) sharding.Selector { + hash := "0x" + sharding.HashField(uid) + return sharding.NewSelector(sharding.ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x0000000000000000", + End: hash, + }) +} + +func incrementHex(hex string) string { + b := []byte(hex) + for i := len(b) - 1; i >= 2; i-- { + if b[i] < '9' { + b[i]++ + return string(b) + } else if b[i] == '9' { + b[i] = 'a' + return string(b) + } else if b[i] < 'f' { + b[i]++ + return string(b) + } + b[i] = '0' + } + return "0x10000000000000000" +} + +func makeExamplePod(name, namespace, uid string, podLabels map[string]string) *example.Pod { + return &example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: types.UID(uid), + Labels: podLabels, + }, + } +} + +func TestFilterWithAttrsAndPrefixFunction_ShardMatchAndLabels(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + uid := "test-uid-1" + pod := makeExamplePod("pod1", "default", uid, map[string]string{"app": "web"}) + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.SelectorFromSet(labels.Set{"app": "web"}), + Field: fields.Everything(), + ShardSelector: selectorIncludingUID(uid), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if !filter("/pods/default/pod1", labels.Set{"app": "web"}, fields.Set{"metadata.name": "pod1", "metadata.namespace": "default"}, pod) { + t.Error("expected filter to match: shard matches and labels match") + } +} + +func TestFilterWithAttrsAndPrefixFunction_ShardMatchButLabelMismatch(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + uid := "test-uid-2" + pod := makeExamplePod("pod2", "default", uid, map[string]string{"app": "api"}) + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.SelectorFromSet(labels.Set{"app": "web"}), + Field: fields.Everything(), + ShardSelector: selectorIncludingUID(uid), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if filter("/pods/default/pod2", labels.Set{"app": "api"}, fields.Set{"metadata.name": "pod2", "metadata.namespace": "default"}, pod) { + t.Error("expected filter to reject: shard matches but labels don't") + } +} + +func TestFilterWithAttrsAndPrefixFunction_ShardMismatch(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + uid := "test-uid-3" + pod := makeExamplePod("pod3", "default", uid, map[string]string{"app": "web"}) + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + ShardSelector: selectorExcludingUID(uid), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if filter("/pods/default/pod3", labels.Set{"app": "web"}, fields.Set{"metadata.name": "pod3", "metadata.namespace": "default"}, pod) { + t.Error("expected filter to reject: shard does not match") + } +} + +func TestFilterWithAttrsAndPrefixFunction_NilShardSelector(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + pod := makeExamplePod("pod4", "default", "uid-4", map[string]string{"app": "web"}) + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.SelectorFromSet(labels.Set{"app": "web"}), + Field: fields.Everything(), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if !filter("/pods/default/pod4", labels.Set{"app": "web"}, fields.Set{"metadata.name": "pod4", "metadata.namespace": "default"}, pod) { + t.Error("expected filter to match: nil ShardSelector should pass everything through") + } +} + +func TestFilterWithAttrsAndPrefixFunction_EverythingSelector(t *testing.T) { + pod := makeExamplePod("pod5", "default", "uid-5", nil) + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + ShardSelector: sharding.Everything(), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if !filter("/pods/default/pod5", labels.Set{}, fields.Set{"metadata.name": "pod5", "metadata.namespace": "default"}, pod) { + t.Error("expected filter to match: Everything() selector should pass all objects") + } +} + +func TestFilterWithAttrsAndPrefixFunction_WrongPrefix(t *testing.T) { + uid := "test-uid-6" + pod := makeExamplePod("pod6", "default", uid, nil) + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + ShardSelector: selectorIncludingUID(uid), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if filter("/configmaps/default/cm1", nil, nil, pod) { + t.Error("expected filter to reject: key prefix doesn't match") + } +} + +func TestFilterWithAttrsAndPrefixFunction_ShardErrorDropsEvent(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + obj := &runtime.Unknown{} + gr := schema.GroupResource{Resource: "pods"} + + pred := storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + ShardSelector: sharding.NewSelector(sharding.ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x0000000000000000", + End: "0x10000000000000000", + }), + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if filter("/pods/default/pod-err", nil, nil, obj) { + t.Error("expected filter to reject: shard matching should fail on unstructured object") + } +} + +func TestFilterWithAttrsAndPrefixFunction_NamespaceSharding(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + ns := "my-namespace" + pod := makeExamplePod("pod-ns", ns, "some-uid", nil) + + nsHash := "0x" + sharding.HashField(ns) + sel := sharding.NewSelector(sharding.ShardRangeRequirement{ + Key: "object.metadata.namespace", + Start: nsHash, + End: incrementHex(nsHash), + }) + + gr := schema.GroupResource{Resource: "pods"} + pred := storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + ShardSelector: sel, + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if !filter("/pods/my-namespace/pod-ns", labels.Set{}, fields.Set{"metadata.name": "pod-ns", "metadata.namespace": ns}, pod) { + t.Error("expected filter to match: namespace-based shard selector should match") + } +} + +func TestFilterWithAttrsAndPrefixFunction_NamespaceShardingMismatch(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ShardedListAndWatch, true) + ns := "other-namespace" + pod := makeExamplePod("pod-ns2", ns, "some-uid-2", nil) + + targetHash := "0x" + sharding.HashField("my-namespace") + sel := sharding.NewSelector(sharding.ShardRangeRequirement{ + Key: "object.metadata.namespace", + Start: targetHash, + End: incrementHex(targetHash), + }) + + gr := schema.GroupResource{Resource: "pods"} + pred := storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + ShardSelector: sel, + GetAttrs: storage.DefaultNamespaceScopedAttr, + } + filter := filterWithAttrsAndPrefixFunction("/pods/", pred, gr) + + if filter("/pods/other-namespace/pod-ns2", labels.Set{}, fields.Set{"metadata.name": "pod-ns2", "metadata.namespace": ns}, pod) { + t.Error("expected filter to reject: namespace hash doesn't fall in shard range") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator.go index b08c1fd5a4edd..4e1fabfaba82d 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator.go @@ -39,6 +39,7 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/cacher/delegator" "k8s.io/apiserver/pkg/storage/cacher/metrics" + "k8s.io/apiserver/pkg/storage/cacher/store" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/tracing" "k8s.io/klog/v2" @@ -111,7 +112,7 @@ func (c *CacheDelegator) Delete(ctx context.Context, key string, out runtime.Obj } else if exists { // DeepCopy the object since we modify resource version when serializing the // current object. - currObj := elem.(*storeElement).Object.DeepCopyObject() + currObj := elem.(*store.Element).Object.DeepCopyObject() return c.storage.Delete(ctx, key, out, preconditions, validateDeletion, currObj, opts) } // If we couldn't get the object, fallback to no-suggestion. @@ -129,10 +130,6 @@ func (c *CacheDelegator) Watch(ctx context.Context, key string, opts storage.Lis if !utilfeature.DefaultFeatureGate.Enabled(features.WatchList) && opts.SendInitialEvents != nil { opts.SendInitialEvents = nil } - // TODO: we should eventually get rid of this legacy case - if utilfeature.DefaultFeatureGate.Enabled(features.WatchFromStorageWithoutResourceVersion) && opts.SendInitialEvents == nil && opts.ResourceVersion == "" { - return c.storage.Watch(ctx, key, opts) - } return c.cacher.Watch(ctx, key, opts) } @@ -254,7 +251,7 @@ func (c *CacheDelegator) GuaranteedUpdate(ctx context.Context, key string, desti } else if exists { // DeepCopy the object since we modify resource version when serializing the // current object. - currObj := elem.(*storeElement).Object.DeepCopyObject() + currObj := elem.(*store.Element).Object.DeepCopyObject() return c.storage.GuaranteedUpdate(ctx, key, destination, ignoreNotFound, preconditions, tryUpdate, currObj) } // If we couldn't get the object, fallback to no-suggestion. @@ -344,10 +341,10 @@ func (c *consistencyChecker) check(ctx context.Context) { c.cacher.MarkConsistent(true) return } - klog.ErrorS(nil, "Cache consistency check failed", "group", c.groupResource.Group, "resource", c.groupResource.Resource, "resourceVersion", digests.ResourceVersion, "etcdDigest", digests.EtcdDigest, "cacheDigest", digests.CacheDigest) + klog.ErrorS(nil, "Cache consistency check failed", "group", c.groupResource.Group, "resource", c.groupResource.Resource, "resourceVersion", digests.ResourceVersion, "etcdDigest", digests.EtcdDigest, "cacheDigest", digests.CacheDigest, "diffDetail", digests.DiffDetail) metrics.StorageConsistencyCheckTotal.WithLabelValues(c.groupResource.Group, c.groupResource.Resource, "failure").Inc() if panicOnCacheInconsistency { - panic(fmt.Sprintf("Cache consistency check failed, group: %q, resource: %q, resourceVersion: %q, etcdDigest: %q, cacheDigest: %q", c.groupResource.Group, c.groupResource.Resource, digests.ResourceVersion, digests.EtcdDigest, digests.CacheDigest)) + panic(fmt.Sprintf("Cache consistency check failed, group: %q, resource: %q, resourceVersion: %q, etcdDigest: %q, cacheDigest: %q, diffDetail: %v", c.groupResource.Group, c.groupResource.Resource, digests.ResourceVersion, digests.EtcdDigest, digests.CacheDigest, digests.DiffDetail)) } c.cacher.MarkConsistent(false) } @@ -356,14 +353,43 @@ func (c *consistencyChecker) calculateDigests(ctx context.Context) (*storageDige if !c.cacher.Ready() { return nil, fmt.Errorf("cache is not ready") } - cacheDigest, cacheResourceVersion, err := c.calculateStoreDigest(ctx, c.cacher, "0", 0) + // variables for tracking the diff in consistency checker + cacheItems := []namespaceNameRV{} + var foundDiff *diffDetail + var etcdIndex int + + cacheDigest, cacheResourceVersion, err := c.calculateStoreDigest(ctx, c.cacher, "0", 0, func(obj metav1.Object) { + // collect items from cacher's list + cacheItems = append(cacheItems, namespaceNameRV{Namespace: obj.GetNamespace(), Name: obj.GetName(), RV: obj.GetResourceVersion()}) + }) if err != nil { return nil, fmt.Errorf("failed calculating cache digest: %w", err) } - etcdDigest, etcdResourceVersion, err := c.calculateStoreDigest(ctx, c.etcd, cacheResourceVersion, storageWatchListPageSize) + etcdDigest, etcdResourceVersion, err := c.calculateStoreDigest(ctx, c.etcd, cacheResourceVersion, storageWatchListPageSize, func(obj metav1.Object) { + // compare the item in etcd's list against cacheItems which is cacher's list + if foundDiff != nil { + return + } + etcdItem := namespaceNameRV{Namespace: obj.GetNamespace(), Name: obj.GetName(), RV: obj.GetResourceVersion()} + if len(cacheItems) <= etcdIndex { + foundDiff = &diffDetail{Index: etcdIndex, EtcdItem: &etcdItem} + cacheItems = nil // don't need it any more and nil it to allow GC to collect it + return + } + if cacheItem := cacheItems[etcdIndex]; cacheItem != etcdItem { + foundDiff = &diffDetail{Index: etcdIndex, EtcdItem: &etcdItem, CacheItem: &cacheItem} + cacheItems = nil // don't need it any more and nil it to allow GC to collect it + return + } + etcdIndex += 1 + }) if err != nil { return nil, fmt.Errorf("failed calculating etcd digest: %w", err) } + if len(cacheItems) > etcdIndex { + cacheItem := cacheItems[etcdIndex] + foundDiff = &diffDetail{Index: etcdIndex, CacheItem: &cacheItem} + } if cacheResourceVersion != etcdResourceVersion { return nil, fmt.Errorf("etcd returned different resource version then expected, cache: %q, etcd: %q", cacheResourceVersion, etcdResourceVersion) } @@ -371,16 +397,30 @@ func (c *consistencyChecker) calculateDigests(ctx context.Context) (*storageDige ResourceVersion: cacheResourceVersion, CacheDigest: cacheDigest, EtcdDigest: etcdDigest, + DiffDetail: foundDiff, }, nil } +type namespaceNameRV struct { + Namespace string + Name string + RV string +} + +type diffDetail struct { + Index int + CacheItem *namespaceNameRV + EtcdItem *namespaceNameRV +} + type storageDigest struct { ResourceVersion string CacheDigest string EtcdDigest string + DiffDetail *diffDetail } -func (c *consistencyChecker) calculateStoreDigest(ctx context.Context, store getLister, resourceVersion string, limit int64) (digest, rv string, err error) { +func (c *consistencyChecker) calculateStoreDigest(ctx context.Context, store getLister, resourceVersion string, limit int64, metaVisitor func(objectMeta metav1.Object)) (digest, rv string, err error) { opts := storage.ListOptions{ Recursive: true, Predicate: storage.Everything, @@ -399,7 +439,7 @@ func (c *consistencyChecker) calculateStoreDigest(ctx context.Context, store get if err != nil { return "", "", err } - err = addListToDigest(h, resp) + err = addListToDigest(h, resp, metaVisitor) if err != nil { return "", "", err } @@ -420,7 +460,7 @@ func (c *consistencyChecker) calculateStoreDigest(ctx context.Context, store get } } -func addListToDigest(h hash.Hash64, list runtime.Object) error { +func addListToDigest(h hash.Hash64, list runtime.Object, metaVisitor func(objectMeta metav1.Object)) error { return meta.EachListItem(list, func(obj runtime.Object) error { objectMeta, err := meta.Accessor(obj) if err != nil { @@ -430,6 +470,7 @@ func addListToDigest(h hash.Hash64, list runtime.Object) error { if err != nil { return err } + metaVisitor(objectMeta) return nil }) } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator_test.go index b10aab4ddeaaa..a3fc672a34223 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/delegator_test.go @@ -19,6 +19,7 @@ package cacher import ( "context" "fmt" + "reflect" "testing" "github.com/google/go-cmp/cmp" @@ -29,6 +30,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/apis/example" "k8s.io/apiserver/pkg/storage" + + cachertesting "k8s.io/apiserver/pkg/storage/cacher/testing" ) func TestConsistencyCheckerDigest(t *testing.T) { @@ -94,6 +97,11 @@ func TestConsistencyCheckerDigest(t *testing.T) { ResourceVersion: "2", CacheDigest: "4ae4e750bd825b17", EtcdDigest: "f940a60af965b03", + DiffDetail: &diffDetail{ + Index: 0, + CacheItem: &namespaceNameRV{Namespace: "kube-system", Name: "pod", RV: "2"}, + EtcdItem: &namespaceNameRV{Namespace: "kube-public", Name: "pod", RV: "2"}, + }, }, expectConsistent: false, }, @@ -111,6 +119,11 @@ func TestConsistencyCheckerDigest(t *testing.T) { ResourceVersion: "2", CacheDigest: "c9120494e4c1897d", EtcdDigest: "c9156494e4c46274", + DiffDetail: &diffDetail{ + Index: 0, + CacheItem: &namespaceNameRV{Namespace: "default", Name: "pod2", RV: "2"}, + EtcdItem: &namespaceNameRV{Namespace: "default", Name: "pod3", RV: "2"}, + }, }, expectConsistent: false, }, @@ -128,6 +141,11 @@ func TestConsistencyCheckerDigest(t *testing.T) { ResourceVersion: "4", CacheDigest: "86bf3a5e80d1c5ca", EtcdDigest: "86bf3a5e80d1c5cd", + DiffDetail: &diffDetail{ + Index: 0, + CacheItem: &namespaceNameRV{Namespace: "default", Name: "pod", RV: "3"}, + EtcdItem: &namespaceNameRV{Namespace: "default", Name: "pod", RV: "4"}, + }, }, expectConsistent: false, }, @@ -146,6 +164,34 @@ func TestConsistencyCheckerDigest(t *testing.T) { ResourceVersion: "3", CacheDigest: "1859bac707c2cb2b", EtcdDigest: "11d147fc800df0e0", + DiffDetail: &diffDetail{ + Index: 1, + CacheItem: nil, + EtcdItem: &namespaceNameRV{Namespace: "Default", Name: "pod", RV: "3"}, + }, + }, + expectConsistent: false, + }, + { + desc: "watch missed delete event", + resourceVersion: "3", + cacherReady: true, + cacherItems: []example.Pod{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "Default", Name: "pod", ResourceVersion: "2"}}, + {ObjectMeta: metav1.ObjectMeta{Namespace: "Default", Name: "pod", ResourceVersion: "3"}}, + }, + etcdItems: []example.Pod{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "Default", Name: "pod", ResourceVersion: "2"}}, + }, + expectDigest: storageDigest{ + ResourceVersion: "3", + CacheDigest: "11d147fc800df0e0", + EtcdDigest: "1859bac707c2cb2b", + DiffDetail: &diffDetail{ + Index: 1, + CacheItem: &namespaceNameRV{Namespace: "Default", Name: "pod", RV: "3"}, + EtcdItem: nil, + }, }, expectConsistent: false, }, @@ -153,8 +199,8 @@ func TestConsistencyCheckerDigest(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - etcd := &dummyStorage{ - getListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + etcd := &cachertesting.MockStorage{ + GetListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { if key != tc.expectListKey { t.Fatalf("Expect GetList key %q, got %q", tc.expectListKey, key) } @@ -170,11 +216,11 @@ func TestConsistencyCheckerDigest(t *testing.T) { return nil }, } - cacher := &dummyCacher{ - ready: tc.cacherReady, - consistent: true, - dummyStorage: dummyStorage{ - getListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + cacher := &cachertesting.MockCacher{ + IsReady: tc.cacherReady, + Consistent: true, + MockStorage: cachertesting.MockStorage{ + GetListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { if key != tc.expectListKey { t.Fatalf("Expect GetList key %q, got %q", tc.expectListKey, key) } @@ -199,13 +245,13 @@ func TestConsistencyCheckerDigest(t *testing.T) { if err != nil { return } - if *digest != tc.expectDigest { + if !reflect.DeepEqual(*digest, tc.expectDigest) { t.Errorf("Expect: %+v Got: %+v", &tc.expectDigest, *digest) } checker.check(context.Background()) - if cacher.consistent != tc.expectConsistent { - t.Errorf("Expect: %+v Got: %+v", tc.expectConsistent, cacher.consistent) + if cacher.Consistent != tc.expectConsistent { + t.Errorf("Expect: %+v Got: %+v", tc.expectConsistent, cacher.Consistent) } }) } @@ -216,8 +262,8 @@ func TestConsistencyCheckerListOpts(t *testing.T) { resourceVersion := "50" etcdOpts := []storage.ListOptions{} - etcd := &dummyStorage{ - getListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + etcd := &cachertesting.MockStorage{ + GetListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { etcdOpts = append(etcdOpts, opts) podList := listObj.(*example.PodList) podList.ResourceVersion = resourceVersion @@ -225,10 +271,10 @@ func TestConsistencyCheckerListOpts(t *testing.T) { }, } cacherOpts := []storage.ListOptions{} - cacher := &dummyCacher{ - ready: true, - dummyStorage: dummyStorage{ - getListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + cacher := &cachertesting.MockCacher{ + IsReady: true, + MockStorage: cachertesting.MockStorage{ + GetListFn: func(_ context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { cacherOpts = append(cacherOpts, opts) podList := listObj.(*example.PodList) podList.ResourceVersion = resourceVersion diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/lister_watcher_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/lister_watcher_test.go index ec4317a254771..1ec786e7c803d 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/lister_watcher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/lister_watcher_test.go @@ -32,12 +32,14 @@ import ( "k8s.io/client-go/util/watchlist" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/utils/ptr" + + cachertesting "k8s.io/apiserver/pkg/storage/cacher/testing" ) func TestDoesClientSupportWatchListSemanticsForKubeClient(t *testing.T) { - target1 := &dummyStorage{} + target1 := &cachertesting.MockStorage{} if !watchlist.DoesClientNotSupportWatchListSemantics(target1) { - t.Fatalf("dummyStorage should NOT support WatchList semantics") + t.Fatalf("cachertesting.MockStorage should NOT support WatchList semantics") } server, target2 := newEtcdTestStorage(t, "/pods/") @@ -274,9 +276,9 @@ func TestListerWatcherListResourceVersionPropagation(t *testing.T) { t.Run(scenario.name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, scenario.watchListEnabled) - backingStorage := &dummyStorage{} + backingStorage := &cachertesting.MockStorage{} var capturedOpts storage.ListOptions - backingStorage.getListFn = func(ctx context.Context, resPrefix string, opts storage.ListOptions, listObj runtime.Object) error { + backingStorage.GetListFn = func(ctx context.Context, resPrefix string, opts storage.ListOptions, listObj runtime.Object) error { capturedOpts = opts return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/metrics/metrics.go index 0f507cf121352..0981d45739f8c 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/metrics/metrics.go @@ -20,6 +20,8 @@ import ( "sync" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" compbasemetrics "k8s.io/component-base/metrics" "k8s.io/component-base/metrics/legacyregistry" ) @@ -112,7 +114,7 @@ var ( Namespace: namespace, Subsystem: subsystem, Name: "resource_version", - Help: "Current resource version of watch cache broken by resource type.", + Help: "Current resource version of watch cache broken by resource type. This is truncated to the 15 least significant digits.", StabilityLevel: compbasemetrics.ALPHA, }, []string{"group", "resource"}, @@ -185,6 +187,26 @@ var ( Help: "Counter for status of consistency checks between etcd and watch cache", StabilityLevel: compbasemetrics.ALPHA, }, []string{"group", "resource", "status"}) + + WatchShardsTotal = compbasemetrics.NewGaugeVec( + &compbasemetrics.GaugeOpts{ + Namespace: namespace, + Name: "watch_shards_total", + Help: "Number of active sharded watch connections broken by resource type.", + StabilityLevel: compbasemetrics.ALPHA, + }, + []string{"group", "resource"}, + ) + + WatchFilteredEventsTotal = compbasemetrics.NewCounterVec( + &compbasemetrics.CounterOpts{ + Namespace: namespace, + Name: "watch_filtered_events_total", + Help: "Counter of events filtered out by shard selector during watch dispatch, broken by resource type.", + StabilityLevel: compbasemetrics.ALPHA, + }, + []string{"group", "resource"}, + ) ) var registerMetrics sync.Once @@ -208,6 +230,10 @@ func Register() { legacyregistry.MustRegister(WatchCacheReadWait) legacyregistry.MustRegister(ConsistentReadTotal) legacyregistry.MustRegister(StorageConsistencyCheckTotal) + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + legacyregistry.MustRegister(WatchShardsTotal) + legacyregistry.MustRegister(WatchFilteredEventsTotal) + } }) } @@ -219,11 +245,28 @@ func RecordListCacheMetrics(groupResource schema.GroupResource, indexName string } // RecordResourceVersion sets the current resource version for a given resource type. +// The resource version is truncated to the 15 least significant digits to prevent +// the metric from growing indefinitely and losing precision when it exceeds 2^53-1. func RecordResourceVersion(groupResource schema.GroupResource, resourceVersion uint64) { - watchCacheResourceVersion.WithLabelValues(groupResource.Group, groupResource.Resource).Set(float64(resourceVersion)) + watchCacheResourceVersion.WithLabelValues(groupResource.Group, groupResource.Resource).Set(float64(resourceVersion % 1000000000000000)) +} + +// RecordShardedWatchStarted increments the active sharded watch gauge for the given resource. +func RecordShardedWatchStarted(groupResource schema.GroupResource) { + WatchShardsTotal.WithLabelValues(groupResource.Group, groupResource.Resource).Inc() +} + +// RecordShardedWatchStopped decrements the active sharded watch gauge for the given resource. +func RecordShardedWatchStopped(groupResource schema.GroupResource) { + WatchShardsTotal.WithLabelValues(groupResource.Group, groupResource.Resource).Dec() +} + +// RecordWatchFilteredEvent increments the counter for events filtered by shard selector. +func RecordWatchFilteredEvent(groupResource schema.GroupResource) { + WatchFilteredEventsTotal.WithLabelValues(groupResource.Group, groupResource.Resource).Inc() } -// RecordsWatchCacheCapacityChange record watchCache capacity resize(increase or decrease) operations. +// RecordsWatchCacheCapacityChange records watchCache capacity resize(increase or decrease) operations. func RecordsWatchCacheCapacityChange(groupResource schema.GroupResource, old, new int) { WatchCacheCapacity.WithLabelValues(groupResource.Group, groupResource.Resource).Set(float64(new)) if old < new { diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/progress/watch_progress.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/progress/watch_progress.go index 76fe73f13657d..9880fc6c36a79 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/progress/watch_progress.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/progress/watch_progress.go @@ -71,7 +71,7 @@ func (pr *ConditionalProgressRequester) Run(stopCh <-chan struct{}) { ctx = metadata.NewOutgoingContext(ctx, pr.contextMetadata) } go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) <-stopCh pr.mux.Lock() defer pr.mux.Unlock() diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store.go similarity index 92% rename from staging/src/k8s.io/apiserver/pkg/storage/cacher/store.go rename to staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store.go index 9ec654e7d1a9a..00dbc2129879a 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cacher +package store import ( "fmt" @@ -60,7 +60,7 @@ const ( btreeDegree = 16 ) -type storeIndexer interface { +type Indexer interface { Add(obj interface{}) error Update(obj interface{}) error Delete(obj interface{}) error @@ -72,17 +72,17 @@ type storeIndexer interface { ByIndex(indexName, indexedValue string) ([]interface{}, error) } -type orderedLister interface { +type OrderedLister interface { ListPrefix(prefix, continueKey string) []interface{} Count(prefix, continueKey string) (count int) - Clone() orderedLister + Clone() OrderedLister } -func newStoreIndexer(indexers *cache.Indexers) storeIndexer { +func NewIndexer(indexers *cache.Indexers) Indexer { if utilfeature.DefaultFeatureGate.Enabled(features.BtreeWatchCache) { - return newThreadedBtreeStoreIndexer(storeElementIndexers(indexers), btreeDegree) + return newThreadedBtreeStoreIndexer(ElementIndexers(indexers), btreeDegree) } - return cache.NewIndexer(storeElementKey, storeElementIndexers(indexers)) + return cache.NewIndexer(ElementKey, ElementIndexers(indexers)) } // Computing a key of an object is generally non-trivial (it performs @@ -90,32 +90,32 @@ func newStoreIndexer(indexers *cache.Indexers) storeIndexer { // labels. To avoid computing them multiple times (to serve the event // in different List/Watch requests), in the underlying store we are // keeping structs (key, object, labels, fields). -type storeElement struct { +type Element struct { Key string Object runtime.Object Labels labels.Set Fields fields.Set } -func storeElementKey(obj interface{}) (string, error) { - elem, ok := obj.(*storeElement) +func ElementKey(obj interface{}) (string, error) { + elem, ok := obj.(*Element) if !ok { return "", fmt.Errorf("not a storeElement: %v", obj) } return elem.Key, nil } -func storeElementObject(obj interface{}) (runtime.Object, error) { - elem, ok := obj.(*storeElement) +func ElementObject(obj interface{}) (runtime.Object, error) { + elem, ok := obj.(*Element) if !ok { return nil, fmt.Errorf("not a storeElement: %v", obj) } return elem.Object, nil } -func storeElementIndexFunc(objIndexFunc cache.IndexFunc) cache.IndexFunc { +func ElementIndexFunc(objIndexFunc cache.IndexFunc) cache.IndexFunc { return func(obj interface{}) (strings []string, e error) { - seo, err := storeElementObject(obj) + seo, err := ElementObject(obj) if err != nil { return nil, err } @@ -123,13 +123,13 @@ func storeElementIndexFunc(objIndexFunc cache.IndexFunc) cache.IndexFunc { } } -func storeElementIndexers(indexers *cache.Indexers) cache.Indexers { +func ElementIndexers(indexers *cache.Indexers) cache.Indexers { if indexers == nil { return cache.Indexers{} } ret := cache.Indexers{} for indexName, indexFunc := range *indexers { - ret[indexName] = storeElementIndexFunc(indexFunc) + ret[indexName] = ElementIndexFunc(indexFunc) } return ret } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store_btree.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_btree.go similarity index 86% rename from staging/src/k8s.io/apiserver/pkg/storage/cacher/store_btree.go rename to staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_btree.go index 9908e2f64b8e1..33e8e4f1024fa 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store_btree.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_btree.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cacher +package store import ( "fmt" "strings" "sync" - "github.com/google/btree" "k8s.io/client-go/tools/cache" + "k8s.io/utils/third_party/forked/golang/btree" ) // newThreadedBtreeStoreIndexer returns a storage for cacher by adding locking over the two 2 data structures: @@ -43,7 +43,7 @@ type threadedStoreIndexer struct { indexer indexer } -var _ orderedLister = (*threadedStoreIndexer)(nil) +var _ OrderedLister = (*threadedStoreIndexer)(nil) func (si *threadedStoreIndexer) Count(prefix, continueKey string) (count int) { si.lock.RLock() @@ -51,7 +51,7 @@ func (si *threadedStoreIndexer) Count(prefix, continueKey string) (count int) { return si.store.Count(prefix, continueKey) } -func (si *threadedStoreIndexer) Clone() orderedLister { +func (si *threadedStoreIndexer) Clone() OrderedLister { si.lock.RLock() defer si.lock.RUnlock() return si.store.Clone() @@ -69,7 +69,7 @@ func (si *threadedStoreIndexer) addOrUpdate(obj interface{}) error { if obj == nil { return fmt.Errorf("obj cannot be nil") } - newElem, ok := obj.(*storeElement) + newElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -80,7 +80,7 @@ func (si *threadedStoreIndexer) addOrUpdate(obj interface{}) error { } func (si *threadedStoreIndexer) Delete(obj interface{}) error { - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -141,17 +141,17 @@ func (si *threadedStoreIndexer) ByIndex(indexName, indexValue string) ([]interfa func newBtreeStore(degree int) btreeStore { return btreeStore{ - tree: btree.NewG(degree, func(a, b *storeElement) bool { + tree: btree.New(degree, func(a, b *Element) bool { return a.Key < b.Key }), } } type btreeStore struct { - tree *btree.BTreeG[*storeElement] + tree *btree.BTree[*Element] } -func (s *btreeStore) Clone() orderedLister { +func (s *btreeStore) Clone() OrderedLister { return &btreeStore{ tree: s.tree.Clone(), } @@ -161,7 +161,7 @@ func (s *btreeStore) Add(obj interface{}) error { if obj == nil { return fmt.Errorf("obj cannot be nil") } - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -173,7 +173,7 @@ func (s *btreeStore) Update(obj interface{}) error { if obj == nil { return fmt.Errorf("obj cannot be nil") } - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -185,7 +185,7 @@ func (s *btreeStore) Delete(obj interface{}) error { if obj == nil { return fmt.Errorf("obj cannot be nil") } - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -193,13 +193,13 @@ func (s *btreeStore) Delete(obj interface{}) error { return nil } -func (s *btreeStore) deleteElem(storeElem *storeElement) (*storeElement, bool) { +func (s *btreeStore) deleteElem(storeElem *Element) (*Element, bool) { return s.tree.Delete(storeElem) } func (s *btreeStore) List() []interface{} { items := make([]interface{}, 0, s.tree.Len()) - s.tree.Ascend(func(item *storeElement) bool { + s.tree.Ascend(func(item *Element) bool { items = append(items, item) return true }) @@ -208,7 +208,7 @@ func (s *btreeStore) List() []interface{} { func (s *btreeStore) ListKeys() []string { items := make([]string, 0, s.tree.Len()) - s.tree.Ascend(func(item *storeElement) bool { + s.tree.Ascend(func(item *Element) bool { items = append(items, item.Key) return true }) @@ -216,7 +216,7 @@ func (s *btreeStore) ListKeys() []string { } func (s *btreeStore) Get(obj interface{}) (item interface{}, exists bool, err error) { - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return nil, false, fmt.Errorf("obj is not a storeElement") } @@ -231,7 +231,7 @@ func (s *btreeStore) GetByKey(key string) (item interface{}, exists bool, err er func (s *btreeStore) Replace(objs []interface{}, _ string) error { s.tree.Clear(false) for _, obj := range objs { - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -242,13 +242,13 @@ func (s *btreeStore) Replace(objs []interface{}, _ string) error { // addOrUpdateLocked assumes a lock is held and is used for Add // and Update operations. -func (s *btreeStore) addOrUpdateElem(storeElem *storeElement) *storeElement { +func (s *btreeStore) addOrUpdateElem(storeElem *Element) *Element { oldObj, _ := s.tree.ReplaceOrInsert(storeElem) return oldObj } func (s *btreeStore) getByKey(key string) (item interface{}, exists bool, err error) { - keyElement := &storeElement{Key: key} + keyElement := &Element{Key: key} item, exists = s.tree.Get(keyElement) return item, exists, nil } @@ -258,7 +258,7 @@ func (s *btreeStore) ListPrefix(prefix, continueKey string) []interface{} { continueKey = prefix } var result []interface{} - s.tree.AscendGreaterOrEqual(&storeElement{Key: continueKey}, func(item *storeElement) bool { + s.tree.AscendGreaterOrEqual(&Element{Key: continueKey}, func(item *Element) bool { if !strings.HasPrefix(item.Key, prefix) { return false } @@ -272,7 +272,7 @@ func (s *btreeStore) Count(prefix, continueKey string) (count int) { if continueKey == "" { continueKey = prefix } - s.tree.AscendGreaterOrEqual(&storeElement{Key: continueKey}, func(item *storeElement) bool { + s.tree.AscendGreaterOrEqual(&Element{Key: continueKey}, func(item *Element) bool { if !strings.HasPrefix(item.Key, prefix) { return false } @@ -292,13 +292,13 @@ func (s *btreeStore) Count(prefix, continueKey string) (count int) { // Difference in mutability of stored values is used for optimizing some operations in client-go Indexer. func newIndexer(indexers cache.Indexers) indexer { return indexer{ - indices: map[string]map[string]map[string]*storeElement{}, + indices: map[string]map[string]map[string]*Element{}, indexers: indexers, } } type indexer struct { - indices map[string]map[string]map[string]*storeElement + indices map[string]map[string]map[string]*Element indexers cache.Indexers } @@ -317,9 +317,9 @@ func (i *indexer) ByIndex(indexName, indexValue string) ([]interface{}, error) { } func (i *indexer) Replace(objs []interface{}, resourceVersion string) error { - i.indices = map[string]map[string]map[string]*storeElement{} + i.indices = map[string]map[string]map[string]*Element{} for _, obj := range objs { - storeElem, ok := obj.(*storeElement) + storeElem, ok := obj.(*Element) if !ok { return fmt.Errorf("obj not a storeElement: %#v", obj) } @@ -331,7 +331,7 @@ func (i *indexer) Replace(objs []interface{}, resourceVersion string) error { return nil } -func (i *indexer) updateElem(key string, oldObj, newObj *storeElement) (err error) { +func (i *indexer) updateElem(key string, oldObj, newObj *Element) (err error) { var oldIndexValues, indexValues []string for name, indexFunc := range i.indexers { if oldObj != nil { @@ -352,7 +352,7 @@ func (i *indexer) updateElem(key string, oldObj, newObj *storeElement) (err erro } index := i.indices[name] if index == nil { - index = map[string]map[string]*storeElement{} + index = map[string]map[string]*Element{} i.indices[name] = index } if len(indexValues) == 1 && len(oldIndexValues) == 1 && indexValues[0] == oldIndexValues[0] { @@ -370,16 +370,16 @@ func (i *indexer) updateElem(key string, oldObj, newObj *storeElement) (err erro return nil } -func (i *indexer) add(key, value string, obj *storeElement, index map[string]map[string]*storeElement) { +func (i *indexer) add(key, value string, obj *Element, index map[string]map[string]*Element) { set := index[value] if set == nil { - set = map[string]*storeElement{} + set = map[string]*Element{} index[value] = set } set[key] = obj } -func (i *indexer) delete(key, value string, index map[string]map[string]*storeElement) { +func (i *indexer) delete(key, value string, index map[string]map[string]*Element) { set := index[value] if set == nil { return @@ -426,9 +426,9 @@ func (i *indexer) delete(key, value string, index map[string]map[string]*storeEl // However, this solution is more complex and is deferred for future implementation. // // TODO: Rewrite to use a cyclic buffer -func newStoreSnapshotter() *storeSnapshotter { +func NewSnapshotter() Snapshotter { s := &storeSnapshotter{ - snapshots: btree.NewG[rvSnapshot](btreeDegree, func(a, b rvSnapshot) bool { + snapshots: btree.New(btreeDegree, func(a, b rvSnapshot) bool { return a.resourceVersion < b.resourceVersion }), } @@ -439,20 +439,20 @@ var _ Snapshotter = (*storeSnapshotter)(nil) type Snapshotter interface { Reset() - GetLessOrEqual(rv uint64) (orderedLister, bool) - Add(rv uint64, indexer orderedLister) + GetLessOrEqual(rv uint64) (OrderedLister, bool) + Add(rv uint64, indexer OrderedLister) RemoveLess(rv uint64) Len() int } type storeSnapshotter struct { mux sync.RWMutex - snapshots *btree.BTreeG[rvSnapshot] + snapshots *btree.BTree[rvSnapshot] } type rvSnapshot struct { resourceVersion uint64 - snapshot orderedLister + snapshot OrderedLister } func (s *storeSnapshotter) Reset() { @@ -461,7 +461,7 @@ func (s *storeSnapshotter) Reset() { s.snapshots.Clear(false) } -func (s *storeSnapshotter) GetLessOrEqual(rv uint64) (orderedLister, bool) { +func (s *storeSnapshotter) GetLessOrEqual(rv uint64) (OrderedLister, bool) { s.mux.RLock() defer s.mux.RUnlock() @@ -476,7 +476,7 @@ func (s *storeSnapshotter) GetLessOrEqual(rv uint64) (orderedLister, bool) { return result.snapshot, true } -func (s *storeSnapshotter) Add(rv uint64, indexer orderedLister) { +func (s *storeSnapshotter) Add(rv uint64, indexer OrderedLister) { s.mux.Lock() defer s.mux.Unlock() s.snapshots.ReplaceOrInsert(rvSnapshot{resourceVersion: rv, snapshot: indexer.Clone()}) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store_btree_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_btree_test.go similarity index 91% rename from staging/src/k8s.io/apiserver/pkg/storage/cacher/store_btree_test.go rename to staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_btree_test.go index b461fe3b1b724..2ea923d6441fa 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store_btree_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_btree_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cacher +package store import ( "testing" @@ -71,12 +71,12 @@ func TestStoreListPrefix(t *testing.T) { } func TestStoreSnapshotter(t *testing.T) { - cache := newStoreSnapshotter() + cache := NewSnapshotter() cache.Add(10, fakeOrderedLister{rv: 10}) cache.Add(20, fakeOrderedLister{rv: 20}) cache.Add(30, fakeOrderedLister{rv: 30}) cache.Add(40, fakeOrderedLister{rv: 40}) - assert.Equal(t, 4, cache.snapshots.Len()) + assert.Equal(t, 4, cache.Len()) t.Log("No snapshot from before first RV") _, found := cache.GetLessOrEqual(9) @@ -105,7 +105,7 @@ func TestStoreSnapshotter(t *testing.T) { t.Log("Remove snapshot less than 30") cache.RemoveLess(30) - assert.Equal(t, 2, cache.snapshots.Len()) + assert.Equal(t, 2, cache.Len()) _, found = cache.GetLessOrEqual(10) assert.False(t, found) @@ -118,7 +118,7 @@ func TestStoreSnapshotter(t *testing.T) { t.Log("Remove removing all RVs") cache.Reset() - assert.Equal(t, 0, cache.snapshots.Len()) + assert.Equal(t, 0, cache.Len()) _, found = cache.GetLessOrEqual(30) assert.False(t, found) _, found = cache.GetLessOrEqual(40) @@ -132,26 +132,26 @@ type fakeOrderedLister struct { func (f fakeOrderedLister) Add(obj interface{}) error { return nil } func (f fakeOrderedLister) Update(obj interface{}) error { return nil } func (f fakeOrderedLister) Delete(obj interface{}) error { return nil } -func (f fakeOrderedLister) Clone() orderedLister { return f } +func (f fakeOrderedLister) Clone() OrderedLister { return f } func (f fakeOrderedLister) ListPrefix(prefixKey, continueKey string) []interface{} { return nil } func (f fakeOrderedLister) Count(prefixKey, continueKey string) int { return 0 } type fakeSnapshotter struct { - getLessOrEqual func(rv uint64) (orderedLister, bool) + getLessOrEqual func(rv uint64) (OrderedLister, bool) } var _ Snapshotter = (*fakeSnapshotter)(nil) func (f *fakeSnapshotter) Reset() {} -func (f *fakeSnapshotter) GetLessOrEqual(rv uint64) (orderedLister, bool) { +func (f *fakeSnapshotter) GetLessOrEqual(rv uint64) (OrderedLister, bool) { if f.getLessOrEqual == nil { return nil, false } return f.getLessOrEqual(rv) } -func (f *fakeSnapshotter) Add(rv uint64, indexer orderedLister) {} +func (f *fakeSnapshotter) Add(rv uint64, indexer OrderedLister) {} func (f *fakeSnapshotter) RemoveLess(rv uint64) {} func (f *fakeSnapshotter) Len() int { return 0 diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_test.go similarity index 83% rename from staging/src/k8s.io/apiserver/pkg/storage/cacher/store_test.go rename to staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_test.go index 7018bc334e66b..aeca893fc21b7 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/store/store_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cacher +package store import ( "testing" @@ -29,16 +29,16 @@ import ( func TestStoreSingleKey(t *testing.T) { t.Run("cache.Indexer", func(t *testing.T) { - store := newStoreIndexer(testStoreIndexers()) + store := NewIndexer(testStoreIndexers()) testStoreSingleKey(t, store) }) t.Run("btree", func(t *testing.T) { - store := newThreadedBtreeStoreIndexer(storeElementIndexers(testStoreIndexers()), btreeDegree) + store := newThreadedBtreeStoreIndexer(ElementIndexers(testStoreIndexers()), btreeDegree) testStoreSingleKey(t, store) }) } -func testStoreSingleKey(t *testing.T, store storeIndexer) { +func testStoreSingleKey(t *testing.T, store Indexer) { assertStoreEmpty(t, store, "foo") require.NoError(t, store.Add(testStorageElement("foo", "bar", 1))) @@ -61,16 +61,16 @@ func testStoreSingleKey(t *testing.T, store storeIndexer) { func TestStoreIndexerSingleKey(t *testing.T) { t.Run("cache.Indexer", func(t *testing.T) { - store := newStoreIndexer(testStoreIndexers()) + store := NewIndexer(testStoreIndexers()) testStoreIndexerSingleKey(t, store) }) t.Run("btree", func(t *testing.T) { - store := newThreadedBtreeStoreIndexer(storeElementIndexers(testStoreIndexers()), btreeDegree) + store := newThreadedBtreeStoreIndexer(ElementIndexers(testStoreIndexers()), btreeDegree) testStoreIndexerSingleKey(t, store) }) } -func testStoreIndexerSingleKey(t *testing.T, store storeIndexer) { +func testStoreIndexerSingleKey(t *testing.T, store Indexer) { items, err := store.ByIndex("by_val", "bar") require.NoError(t, err) assert.Empty(t, items) @@ -122,7 +122,7 @@ func testStoreIndexerSingleKey(t *testing.T, store storeIndexer) { require.NoError(t, store.Delete(testStorageElement("foo", "", 0))) } -func assertStoreEmpty(t *testing.T, store storeIndexer, nonExistingKey string) { +func assertStoreEmpty(t *testing.T, store Indexer, nonExistingKey string) { item, ok, err := store.Get(testStorageElement(nonExistingKey, "", 0)) require.NoError(t, err) assert.False(t, ok) @@ -137,23 +137,23 @@ func assertStoreEmpty(t *testing.T, store storeIndexer, nonExistingKey string) { assert.Empty(t, items) } -func assertStoreSingleKey(t *testing.T, store storeIndexer, expectKey, expectValue string, expectRV int) { +func assertStoreSingleKey(t *testing.T, store Indexer, expectKey, expectValue string, expectRV int) { item, ok, err := store.Get(testStorageElement(expectKey, "", expectRV)) require.NoError(t, err) assert.True(t, ok) - assert.Equal(t, expectValue, item.(*storeElement).Object.(fakeObj).value) + assert.Equal(t, expectValue, item.(*Element).Object.(fakeObj).value) item, ok, err = store.GetByKey(expectKey) require.NoError(t, err) assert.True(t, ok) - assert.Equal(t, expectValue, item.(*storeElement).Object.(fakeObj).value) + assert.Equal(t, expectValue, item.(*Element).Object.(fakeObj).value) items := store.List() assert.Equal(t, []interface{}{testStorageElement(expectKey, expectValue, expectRV)}, items) } -func testStorageElement(key, value string, rv int) *storeElement { - return &storeElement{Key: key, Object: fakeObj{value: value, rv: rv}} +func testStorageElement(key, value string, rv int) *Element { + return &Element{Key: key, Object: fakeObj{value: value, rv: rv}} } type fakeObj struct { diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/testing/mock.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/testing/mock.go new file mode 100644 index 0000000000000..f9f445cebd47f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/testing/mock.go @@ -0,0 +1,153 @@ +/* +Copyright 2025 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 testing + +import ( + "context" + "fmt" + "sync" + _ "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/apis/example" + "k8s.io/apiserver/pkg/storage" +) + +type MockStorage struct { + sync.RWMutex + GetListErr error + WatchErr error + GetListFn func(_ context.Context, _ string, _ storage.ListOptions, listObj runtime.Object) error + GetRVFn func(_ context.Context) (uint64, error) + WatchFn func(_ context.Context, _ string, _ storage.ListOptions) (watch.Interface, error) + + // use GetRequestWatchProgressCounter when reading + // the value of the counter + RequestWatchProgressCounter int +} + +func (d *MockStorage) RequestWatchProgress(ctx context.Context) error { + d.Lock() + defer d.Unlock() + d.RequestWatchProgressCounter++ + return nil +} + +func (d *MockStorage) GetRequestWatchProgressCounter() int { + d.RLock() + defer d.RUnlock() + return d.RequestWatchProgressCounter +} + +func (d *MockStorage) CompactRevision() int64 { + return 0 +} + +func (d *MockStorage) IsWatchListSemanticsUnSupported() bool { + return true +} + +type MockWatch struct { + ch chan watch.Event +} + +func (w *MockWatch) ResultChan() <-chan watch.Event { + return w.ch +} + +func (w *MockWatch) Stop() { + close(w.ch) +} + +func NewMockWatch() watch.Interface { + return &MockWatch{ + ch: make(chan watch.Event), + } +} + +func (d *MockStorage) Versioner() storage.Versioner { return nil } +func (d *MockStorage) Create(_ context.Context, _ string, _, _ runtime.Object, _ uint64) error { + return fmt.Errorf("unimplemented") +} +func (d *MockStorage) Delete(_ context.Context, _ string, _ runtime.Object, _ *storage.Preconditions, _ storage.ValidateObjectFunc, _ runtime.Object, _ storage.DeleteOptions) error { + return fmt.Errorf("unimplemented") +} +func (d *MockStorage) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) { + if d.WatchFn != nil { + return d.WatchFn(ctx, key, opts) + } + d.RLock() + defer d.RUnlock() + + return NewMockWatch(), d.WatchErr +} +func (d *MockStorage) Get(_ context.Context, _ string, _ storage.GetOptions, _ runtime.Object) error { + d.RLock() + defer d.RUnlock() + return d.GetListErr +} +func (d *MockStorage) GetList(ctx context.Context, resPrefix string, opts storage.ListOptions, listObj runtime.Object) error { + if d.GetListFn != nil { + return d.GetListFn(ctx, resPrefix, opts, listObj) + } + d.RLock() + defer d.RUnlock() + podList := listObj.(*example.PodList) + podList.ListMeta = metav1.ListMeta{ResourceVersion: "100"} + return d.GetListErr +} +func (d *MockStorage) GuaranteedUpdate(_ context.Context, _ string, _ runtime.Object, _ bool, _ *storage.Preconditions, _ storage.UpdateFunc, _ runtime.Object) error { + return fmt.Errorf("unimplemented") +} +func (d *MockStorage) Stats(_ context.Context) (storage.Stats, error) { + return storage.Stats{}, fmt.Errorf("unimplemented") +} +func (d *MockStorage) EnableResourceSizeEstimation(storage.KeysFunc) error { + return nil +} +func (d *MockStorage) ReadinessCheck() error { + return nil +} +func (d *MockStorage) InjectGetListError(err error) { + d.Lock() + defer d.Unlock() + + d.GetListErr = err +} + +func (d *MockStorage) GetCurrentResourceVersion(ctx context.Context) (uint64, error) { + if d.GetRVFn != nil { + return d.GetRVFn(ctx) + } + return 100, nil +} + +type MockCacher struct { + MockStorage + IsReady bool + Consistent bool +} + +func (d *MockCacher) Ready() bool { + return d.IsReady +} + +func (d *MockCacher) MarkConsistent(consistent bool) { + d.Consistent = consistent +} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go index 620b0f36bd20b..fc73708a8ce6e 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go @@ -37,6 +37,7 @@ import ( "k8s.io/apiserver/pkg/storage/cacher/delegator" "k8s.io/apiserver/pkg/storage/cacher/metrics" "k8s.io/apiserver/pkg/storage/cacher/progress" + "k8s.io/apiserver/pkg/storage/cacher/store" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/cache" "k8s.io/component-base/tracing" @@ -121,7 +122,7 @@ type watchCache struct { // history" i.e. from the moment just after the newest cached watched event. // It is necessary to effectively allow clients to start watching at now. // NOTE: We assume that is thread-safe. - store storeIndexer + store store.Indexer // ResourceVersion up to which the watchCache is propagated. resourceVersion uint64 @@ -156,7 +157,7 @@ type watchCache struct { waitingUntilFresh *progress.ConditionalProgressRequester // Stores previous snapshots of orderedLister to allow serving requests from previous revisions. - snapshots Snapshotter + snapshots store.Snapshotter snapshottingEnabled atomic.Bool getCurrentRV func(context.Context) (uint64, error) @@ -183,7 +184,7 @@ func newWatchCache( upperBoundCapacity: capacityUpperBound(eventFreshDuration), startIndex: 0, endIndex: 0, - store: newStoreIndexer(indexers), + store: store.NewIndexer(indexers), resourceVersion: 0, listResourceVersion: 0, eventHandler: eventHandler, @@ -196,7 +197,7 @@ func newWatchCache( } if utilfeature.DefaultFeatureGate.Enabled(features.ListFromCacheSnapshot) { wc.snapshottingEnabled.Store(true) - wc.snapshots = newStoreSnapshotter() + wc.snapshots = store.NewSnapshotter() } metrics.WatchCacheCapacity.WithLabelValues(groupResource.Group, groupResource.Resource).Set(float64(wc.capacity)) wc.cond = sync.NewCond(wc.RLocker()) @@ -237,7 +238,7 @@ func (w *watchCache) Add(obj interface{}) error { } event := watch.Event{Type: watch.Added, Object: object} - f := func(elem *storeElement) error { return w.store.Add(elem) } + f := func(elem *store.Element) error { return w.store.Add(elem) } return w.processEvent(event, resourceVersion, f) } @@ -249,7 +250,7 @@ func (w *watchCache) Update(obj interface{}) error { } event := watch.Event{Type: watch.Modified, Object: object} - f := func(elem *storeElement) error { return w.store.Update(elem) } + f := func(elem *store.Element) error { return w.store.Update(elem) } return w.processEvent(event, resourceVersion, f) } @@ -261,7 +262,7 @@ func (w *watchCache) Delete(obj interface{}) error { } event := watch.Event{Type: watch.Deleted, Object: object} - f := func(elem *storeElement) error { return w.store.Delete(elem) } + f := func(elem *store.Element) error { return w.store.Delete(elem) } return w.processEvent(event, resourceVersion, f) } @@ -279,14 +280,14 @@ func (w *watchCache) objectToVersionedRuntimeObject(obj interface{}) (runtime.Ob // processEvent is safe as long as there is at most one call to it in flight // at any point in time. -func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, updateFunc func(*storeElement) error) error { +func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, updateFunc func(*store.Element) error) error { metrics.EventsReceivedCounter.WithLabelValues(w.groupResource.Group, w.groupResource.Resource).Inc() key, err := w.keyFunc(event.Object) if err != nil { return fmt.Errorf("couldn't compute key: %v", err) } - elem := &storeElement{Key: key, Object: event.Object} + elem := &store.Element{Key: key, Object: event.Object} elem.Labels, elem.Fields, err = w.getAttrsFunc(event.Object) if err != nil { return err @@ -312,7 +313,7 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd return err } if exists { - previousElem := previous.(*storeElement) + previousElem := previous.(*store.Element) wcEvent.PrevObject = previousElem.Object wcEvent.PrevObjLabels = previousElem.Labels wcEvent.PrevObjFields = previousElem.Fields @@ -331,7 +332,7 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd return err } if w.snapshots != nil && w.snapshottingEnabled.Load() { - if orderedLister, ordered := w.store.(orderedLister); ordered { + if orderedLister, ordered := w.store.(store.OrderedLister); ordered { if w.isCacheFullLocked() { oldestRV := w.cache[w.startIndex%w.capacity].ResourceVersion w.snapshots.RemoveLess(oldestRV) @@ -437,7 +438,7 @@ func (w *watchCache) UpdateResourceVersion(resourceVersion string) { metrics.RecordResourceVersion(w.groupResource, rv) } -// List returns list of pointers to objects. +// List returns list of pointers to objects. func (w *watchCache) List() []interface{} { return w.store.List() } @@ -495,7 +496,7 @@ func (s sortableStoreElements) Len() int { } func (s sortableStoreElements) Less(i, j int) bool { - return s[i].(*storeElement).Key < s[j].(*storeElement).Key + return s[i].(*store.Element).Key < s[j].(*store.Element).Key } func (s sortableStoreElements) Swap(i, j int) { @@ -652,7 +653,7 @@ func (w *watchCache) listLatestRV(key, continueKey string, matchValues []storage }, matchValue.IndexName, err } } - if store, ok := w.store.(orderedLister); ok { + if store, ok := w.store.(store.OrderedLister); ok { result := store.ListPrefix(key, continueKey) return listResp{ Items: result, @@ -670,9 +671,9 @@ func (w *watchCache) listLatestRV(key, continueKey string, matchValues []storage func filterPrefixAndOrder(prefix string, items []interface{}) ([]interface{}, error) { var result []interface{} for _, item := range items { - elem, ok := item.(*storeElement) + elem, ok := item.(*store.Element) if !ok { - return nil, fmt.Errorf("non *storeElement returned from storage: %v", item) + return nil, fmt.Errorf("non *store.Element returned from storage: %v", item) } if !hasPathPrefix(elem.Key, prefix) { continue @@ -723,7 +724,7 @@ func (w *watchCache) Get(obj interface{}) (interface{}, bool, error) { return nil, false, fmt.Errorf("couldn't compute key: %v", err) } - return w.store.Get(&storeElement{Key: key, Object: object}) + return w.store.Get(&store.Element{Key: key, Object: object}) } // GetByKey returns pointer to . @@ -752,7 +753,7 @@ func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error { if err != nil { return err } - toReplace = append(toReplace, &storeElement{ + toReplace = append(toReplace, &store.Element{ Key: key, Object: object, Labels: objLabels, @@ -777,7 +778,7 @@ func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error { } if w.snapshots != nil { w.snapshots.Reset() - if orderedLister, ordered := w.store.(orderedLister); ordered && w.snapshottingEnabled.Load() { + if orderedLister, ordered := w.store.(store.OrderedLister); ordered && w.snapshottingEnabled.Load() { w.snapshots.Add(version, orderedLister) } } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval.go index b74b3ca9178dd..8cdcac370f1ff 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval.go @@ -22,6 +22,7 @@ import ( "sync" "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/storage/cacher/store" ) // watchCacheInterval serves as an abstraction over a source @@ -136,11 +137,11 @@ func (s sortableWatchCacheEvents) Swap(i, j int) { // returned by Next() need to be events from a List() done on the underlying store of // the watch cache. // The items returned in the interval will be sorted by Key. -func newCacheIntervalFromStore(resourceVersion uint64, store storeIndexer, key string, matchesSingle bool) (*watchCacheInterval, error) { +func newCacheIntervalFromStore(resourceVersion uint64, indexer store.Indexer, key string, matchesSingle bool) (*watchCacheInterval, error) { buffer := &watchCacheIntervalBuffer{} var allItems []interface{} if matchesSingle { - item, exists, err := store.GetByKey(key) + item, exists, err := indexer.GetByKey(key) if err != nil { return nil, err } @@ -149,11 +150,11 @@ func newCacheIntervalFromStore(resourceVersion uint64, store storeIndexer, key s allItems = append(allItems, item) } } else { - allItems = store.List() + allItems = indexer.List() } buffer.buffer = make([]*watchCacheEvent, len(allItems)) for i, item := range allItems { - elem, ok := item.(*storeElement) + elem, ok := item.(*store.Element) if !ok { return nil, fmt.Errorf("not a storeElement: %v", elem) } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval_test.go index 21dec3500cd7c..f201f06a3b00c 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_interval_test.go @@ -29,6 +29,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/cache" + + "k8s.io/apiserver/pkg/storage/cacher/store" ) func intervalFromEvents(events []*watchCacheEvent) *watchCacheInterval { @@ -371,7 +373,7 @@ func TestCacheIntervalNextFromStore(t *testing.T) { return labels.Set(pod.Labels), fields.Set{"spec.nodeName": pod.Spec.NodeName}, nil } const numEvents = 50 - store := cache.NewIndexer(storeElementKey, storeElementIndexers(nil)) + store := cache.NewIndexer(store.ElementKey, store.ElementIndexers(nil)) events := make(map[string]*watchCacheEvent) var rv uint64 = 1 // arbitrary number; rv till which the watch cache has progressed. diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_test.go index 1c090269c55af..548bf0f94fe36 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache_test.go @@ -42,6 +42,7 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/cacher/metrics" "k8s.io/apiserver/pkg/storage/cacher/progress" + "k8s.io/apiserver/pkg/storage/cacher/store" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/cache" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -69,8 +70,8 @@ func makeTestPodDetails(name string, resourceVersion uint64, nodeName string, la } } -func makeTestStoreElement(pod *v1.Pod) *storeElement { - return &storeElement{ +func makeTestStoreElement(pod *v1.Pod) *store.Element { + return &store.Element{ Key: "/prefix/ns/" + pod.Name, Object: pod, Labels: labels.Set(pod.Labels), @@ -202,15 +203,15 @@ func (w *testWatchCache) Stop() { } func TestWatchCacheBasic(t *testing.T) { - store := newTestWatchCache(2, DefaultEventFreshDuration, &cache.Indexers{}) - defer store.Stop() + s := newTestWatchCache(2, DefaultEventFreshDuration, &cache.Indexers{}) + defer s.Stop() // Test Add/Update/Delete. pod1 := makeTestPod("pod", 1) - if err := store.Add(pod1); err != nil { + if err := s.Add(pod1); err != nil { t.Errorf("unexpected error: %v", err) } - if item, ok, _ := store.Get(pod1); !ok { + if item, ok, _ := s.Get(pod1); !ok { t.Errorf("didn't find pod") } else { expected := makeTestStoreElement(makeTestPod("pod", 1)) @@ -219,10 +220,10 @@ func TestWatchCacheBasic(t *testing.T) { } } pod2 := makeTestPod("pod", 2) - if err := store.Update(pod2); err != nil { + if err := s.Update(pod2); err != nil { t.Errorf("unexpected error: %v", err) } - if item, ok, _ := store.Get(pod2); !ok { + if item, ok, _ := s.Get(pod2); !ok { t.Errorf("didn't find pod") } else { expected := makeTestStoreElement(makeTestPod("pod", 2)) @@ -231,26 +232,26 @@ func TestWatchCacheBasic(t *testing.T) { } } pod3 := makeTestPod("pod", 3) - if err := store.Delete(pod3); err != nil { + if err := s.Delete(pod3); err != nil { t.Errorf("unexpected error: %v", err) } - if _, ok, _ := store.Get(pod3); ok { + if _, ok, _ := s.Get(pod3); ok { t.Errorf("found pod") } // Test List. - store.Add(makeTestPod("pod1", 4)) - store.Add(makeTestPod("pod2", 5)) - store.Add(makeTestPod("pod3", 6)) + s.Add(makeTestPod("pod1", 4)) + s.Add(makeTestPod("pod2", 5)) + s.Add(makeTestPod("pod3", 6)) { - expected := map[string]storeElement{ + expected := map[string]store.Element{ "/prefix/ns/pod1": *makeTestStoreElement(makeTestPod("pod1", 4)), "/prefix/ns/pod2": *makeTestStoreElement(makeTestPod("pod2", 5)), "/prefix/ns/pod3": *makeTestStoreElement(makeTestPod("pod3", 6)), } - items := make(map[string]storeElement) - for _, item := range store.List() { - elem := item.(*storeElement) + items := make(map[string]store.Element) + for _, item := range s.List() { + elem := item.(*store.Element) items[elem.Key] = *elem } if !apiequality.Semantic.DeepEqual(expected, items) { @@ -259,18 +260,18 @@ func TestWatchCacheBasic(t *testing.T) { } // Test Replace. - store.Replace([]interface{}{ + s.Replace([]interface{}{ makeTestPod("pod4", 7), makeTestPod("pod5", 8), }, "8") { - expected := map[string]storeElement{ + expected := map[string]store.Element{ "/prefix/ns/pod4": *makeTestStoreElement(makeTestPod("pod4", 7)), "/prefix/ns/pod5": *makeTestStoreElement(makeTestPod("pod5", 8)), } - items := make(map[string]storeElement) - for _, item := range store.List() { - elem := item.(*storeElement) + items := make(map[string]store.Element) + for _, item := range s.List() { + elem := item.(*store.Element) items[elem.Key] = *elem } if !apiequality.Semantic.DeepEqual(expected, items) { @@ -1316,97 +1317,97 @@ func TestHistogramCacheReadWait(t *testing.T) { func TestCacheSnapshots(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ListFromCacheSnapshot, true) - store := newTestWatchCache(3, DefaultEventFreshDuration, &cache.Indexers{}) - defer store.Stop() - store.upperBoundCapacity = 3 - store.lowerBoundCapacity = 1 - clock := store.clock.(*testingclock.FakeClock) + s := newTestWatchCache(3, DefaultEventFreshDuration, &cache.Indexers{}) + defer s.Stop() + s.upperBoundCapacity = 3 + s.lowerBoundCapacity = 1 + clock := s.clock.(*testingclock.FakeClock) - _, found := store.snapshots.GetLessOrEqual(100) + _, found := s.snapshots.GetLessOrEqual(100) assert.False(t, found, "Expected empty cache to not include any snapshots") t.Log("Test cache on rev 100") - require.NoError(t, store.Add(makeTestPod("foo", 100))) - require.NoError(t, store.Update(makeTestPod("foo", 200))) + require.NoError(t, s.Add(makeTestPod("foo", 100))) + require.NoError(t, s.Update(makeTestPod("foo", 200))) clock.Step(time.Second) - require.NoError(t, store.Delete(makeTestPod("foo", 300))) + require.NoError(t, s.Delete(makeTestPod("foo", 300))) t.Log("Test cache on rev 100") - _, found = store.snapshots.GetLessOrEqual(99) + _, found = s.snapshots.GetLessOrEqual(99) assert.False(t, found, "Expected store to not include rev 99") - lister, found := store.snapshots.GetLessOrEqual(100) + lister, found := s.snapshots.GetLessOrEqual(100) assert.True(t, found, "Expected store to not include rev 100") elements := lister.ListPrefix("", "") assert.Len(t, elements, 1) - assert.Equal(t, makeTestPod("foo", 100), elements[0].(*storeElement).Object) + assert.Equal(t, makeTestPod("foo", 100), elements[0].(*store.Element).Object) t.Log("Overflow cache to remove rev 100") - require.NoError(t, store.Add(makeTestPod("foo", 400))) - _, found = store.snapshots.GetLessOrEqual(100) + require.NoError(t, s.Add(makeTestPod("foo", 400))) + _, found = s.snapshots.GetLessOrEqual(100) assert.False(t, found, "Expected overfilled cache to delete oldest rev 100") t.Log("Test cache on rev 200") - lister, found = store.snapshots.GetLessOrEqual(200) + lister, found = s.snapshots.GetLessOrEqual(200) assert.True(t, found, "Expected store to still keep rev 200") elements = lister.ListPrefix("", "") assert.Len(t, elements, 1) - assert.Equal(t, makeTestPod("foo", 200), elements[0].(*storeElement).Object) + assert.Equal(t, makeTestPod("foo", 200), elements[0].(*store.Element).Object) t.Log("Test cache on rev 300") - lister, found = store.snapshots.GetLessOrEqual(300) + lister, found = s.snapshots.GetLessOrEqual(300) assert.True(t, found, "Expected store to still keep rev 300") elements = lister.ListPrefix("", "") assert.Empty(t, elements) t.Log("Test cache on rev 400") - lister, found = store.snapshots.GetLessOrEqual(400) + lister, found = s.snapshots.GetLessOrEqual(400) assert.True(t, found, "Expected store to still keep rev 400") elements = lister.ListPrefix("", "") assert.Len(t, elements, 1) - assert.Equal(t, makeTestPod("foo", 400), elements[0].(*storeElement).Object) + assert.Equal(t, makeTestPod("foo", 400), elements[0].(*store.Element).Object) t.Log("Add event outside the event fresh window to force cache capacity downsize") - assert.Equal(t, 3, store.capacity) + assert.Equal(t, 3, s.capacity) clock.Step(DefaultEventFreshDuration + 1) - require.NoError(t, store.Update(makeTestPod("foo", 500))) - assert.Equal(t, 1, store.capacity) - assert.Equal(t, 1, store.snapshots.Len()) - _, found = store.snapshots.GetLessOrEqual(499) + require.NoError(t, s.Update(makeTestPod("foo", 500))) + assert.Equal(t, 1, s.capacity) + assert.Equal(t, 1, s.snapshots.Len()) + _, found = s.snapshots.GetLessOrEqual(499) assert.False(t, found, "Expected overfilled cache to delete events below 500") t.Log("Test cache on rev 500") - lister, found = store.snapshots.GetLessOrEqual(500) + lister, found = s.snapshots.GetLessOrEqual(500) assert.True(t, found, "Expected store to still keep rev 500") elements = lister.ListPrefix("", "") assert.Len(t, elements, 1) - assert.Equal(t, makeTestPod("foo", 500), elements[0].(*storeElement).Object) + assert.Equal(t, makeTestPod("foo", 500), elements[0].(*store.Element).Object) t.Log("Add event to force capacity upsize") - require.NoError(t, store.Update(makeTestPod("foo", 600))) - assert.Equal(t, 2, store.capacity) - assert.Equal(t, 2, store.snapshots.Len()) + require.NoError(t, s.Update(makeTestPod("foo", 600))) + assert.Equal(t, 2, s.capacity) + assert.Equal(t, 2, s.snapshots.Len()) t.Log("Test cache on rev 600") - lister, found = store.snapshots.GetLessOrEqual(600) + lister, found = s.snapshots.GetLessOrEqual(600) assert.True(t, found, "Expected replace to be snapshotted") elements = lister.ListPrefix("", "") assert.Len(t, elements, 1) - assert.Equal(t, makeTestPod("foo", 600), elements[0].(*storeElement).Object) + assert.Equal(t, makeTestPod("foo", 600), elements[0].(*store.Element).Object) t.Log("Replace cache to remove history") - _, found = store.snapshots.GetLessOrEqual(500) + _, found = s.snapshots.GetLessOrEqual(500) assert.True(t, found, "Confirm that cache stores history before replace") - err := store.Replace([]interface{}{makeTestPod("foo", 600)}, "700") + err := s.Replace([]interface{}{makeTestPod("foo", 600)}, "700") require.NoError(t, err) - _, found = store.snapshots.GetLessOrEqual(500) + _, found = s.snapshots.GetLessOrEqual(500) assert.False(t, found, "Expected replace to remove history") - _, found = store.snapshots.GetLessOrEqual(600) + _, found = s.snapshots.GetLessOrEqual(600) assert.False(t, found, "Expected replace to remove history") t.Log("Test cache on rev 700") - lister, found = store.snapshots.GetLessOrEqual(700) + lister, found = s.snapshots.GetLessOrEqual(700) assert.True(t, found, "Expected replace to be snapshotted") elements = lister.ListPrefix("", "") assert.Len(t, elements, 1) - assert.Equal(t, makeTestPod("foo", 600), elements[0].(*storeElement).Object) + assert.Equal(t, makeTestPod("foo", 600), elements[0].(*store.Element).Object) } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go index f77eee9576d04..b3a63781397ff 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go @@ -112,13 +112,22 @@ var ( Subsystem: "apiserver", Name: "storage_events_received_total", Help: "Number of etcd events received split by kind.", - StabilityLevel: compbasemetrics.ALPHA, + StabilityLevel: compbasemetrics.BETA, }, []string{"group", "resource"}, ) etcdBookmarkCounts = compbasemetrics.NewGaugeVec( &compbasemetrics.GaugeOpts{ - Name: "etcd_bookmark_counts", + Name: "etcd_bookmark_counts", + Help: "Number of etcd bookmarks (progress notify events) split by kind.", + StabilityLevel: compbasemetrics.ALPHA, + DeprecatedVersion: "1.36.0", + }, + []string{"group", "resource"}, + ) + etcdBookmarkTotal = compbasemetrics.NewCounterVec( + &compbasemetrics.CounterOpts{ + Name: "etcd_bookmark_total", Help: "Number of etcd bookmarks (progress notify events) split by kind.", StabilityLevel: compbasemetrics.ALPHA, }, @@ -200,6 +209,7 @@ func Register() { legacyregistry.CustomMustRegister(storageMonitor) legacyregistry.MustRegister(etcdEventsReceivedCounts) legacyregistry.MustRegister(etcdBookmarkCounts) + legacyregistry.MustRegister(etcdBookmarkTotal) legacyregistry.MustRegister(etcdLeaseObjectCounts) legacyregistry.MustRegister(listStorageCount) legacyregistry.MustRegister(listStorageNumFetched) @@ -255,9 +265,10 @@ func RecordEtcdEvent(groupResource schema.GroupResource) { etcdEventsReceivedCounts.WithLabelValues(groupResource.Group, groupResource.Resource).Inc() } -// RecordEtcdBookmark updates the etcd_bookmark_counts metric. +// RecordEtcdBookmark updates the etcd_bookmark_total metric. func RecordEtcdBookmark(groupResource schema.GroupResource) { etcdBookmarkCounts.WithLabelValues(groupResource.Group, groupResource.Resource).Inc() + etcdBookmarkTotal.WithLabelValues(groupResource.Group, groupResource.Resource).Inc() } // RecordDecodeError sets the storage_decode_errors metrics. @@ -300,7 +311,7 @@ func UpdateEtcdRequestRetry(errorCode string) { etcdRequestRetry.WithLabelValues(errorCode).Inc() } -// RecordListEtcd3Metrics notes various metrics of the cost to serve a LIST request +// RecordStorageListMetrics notes various metrics of the cost to serve a LIST request func RecordStorageListMetrics(groupResource schema.GroupResource, numFetched, numEvald, numReturned int) { listStorageCount.WithLabelValues(groupResource.Group, groupResource.Resource).Inc() listStorageNumFetched.WithLabelValues(groupResource.Group, groupResource.Resource).Add(float64(numFetched)) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go index 3c8a33d49c58a..ea551745b229f 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go @@ -60,6 +60,69 @@ func TestRecordDecodeError(t *testing.T) { } } +func TestRecordEtcdEvent(t *testing.T) { + registry := metrics.NewKubeRegistry() + defer registry.Reset() + registry.MustRegister(etcdEventsReceivedCounts) + testedMetrics := "apiserver_storage_events_received_total" + testCases := []struct { + desc string + resource schema.GroupResource + want string + }{ + { + desc: "record single event", + resource: schema.GroupResource{Group: "apps", Resource: "deployments"}, + want: `# HELP apiserver_storage_events_received_total [BETA] Number of etcd events received split by kind. +# TYPE apiserver_storage_events_received_total counter +apiserver_storage_events_received_total{group="apps",resource="deployments"} 1 +`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + RecordEtcdEvent(test.resource) + if err := testutil.GatherAndCompare(registry, strings.NewReader(test.want), testedMetrics); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestRecordEtcdBookmark(t *testing.T) { + registry := metrics.NewKubeRegistry() + registry.MustRegister(etcdBookmarkTotal) + + testCases := []struct { + desc string + groupResource schema.GroupResource + callCount int + want string + }{ + { + desc: "test success", + groupResource: schema.GroupResource{Group: "apps", Resource: "deployments"}, + callCount: 1, + want: `# HELP etcd_bookmark_total [ALPHA] Number of etcd bookmarks (progress notify events) split by kind. +# TYPE etcd_bookmark_total counter +etcd_bookmark_total{group="apps",resource="deployments"} 1 +`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + for i := 0; i < test.callCount; i++ { + RecordEtcdBookmark(test.groupResource) + } + if err := testutil.GatherAndCompare(registry, strings.NewReader(test.want), "etcd_bookmark_total"); err != nil { + t.Fatal(err) + } + }) + } +} + func TestRecordEtcdRequest(t *testing.T) { registry := metrics.NewKubeRegistry() diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go index 798567d1cd16c..8384832335f3d 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go @@ -895,7 +895,13 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption if err != nil { return err } - return s.versioner.UpdateList(listObj, uint64(withRev), continueValue, remainingItemCount) + if err := s.versioner.UpdateList(listObj, uint64(withRev), continueValue, remainingItemCount); err != nil { + return err + } + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + opts.Predicate.SetShardInfoOnList(listObj) + } + return nil } func (s *store) getList(ctx context.Context, keyPrefix string, recursive bool, options kubernetes.ListOptions) (resp kubernetes.ListResponse, err error) { diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go index cf16606bc512a..0559383f8daa1 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" clientv3 "go.etcd.io/etcd/client/v3" @@ -55,17 +56,17 @@ const ( var defaultWatcherMaxLimit int64 = maxLimit // fatalOnDecodeError is used during testing to panic the server if watcher encounters a decoding error -var fatalOnDecodeError = false +var fatalOnDecodeError atomic.Bool func init() { // check to see if we are running in a test environment - TestOnlySetFatalOnDecodeError(true) - fatalOnDecodeError, _ = strconv.ParseBool(os.Getenv("KUBE_PANIC_WATCH_DECODE_ERROR")) + b, _ := strconv.ParseBool(os.Getenv("KUBE_PANIC_WATCH_DECODE_ERROR")) + TestOnlySetFatalOnDecodeError(b) } // TestOnlySetFatalOnDecodeError should only be used for cases where decode errors are expected and need to be tested. e.g. conversion webhooks. func TestOnlySetFatalOnDecodeError(b bool) { - fatalOnDecodeError = b + fatalOnDecodeError.Store(b) } type watcher struct { @@ -758,7 +759,7 @@ func (w *watcher) transformIfCorruptObjectError(e *event, err error) error { func decodeObj(codec runtime.Codec, versioner storage.Versioner, data []byte, rev int64) (_ runtime.Object, err error) { obj, err := runtime.Decode(codec, []byte(data)) if err != nil { - if fatalOnDecodeError { + if fatalOnDecodeError.Load() { // we are running in a test environment and thus an // error here is due to a coder mistake if the defer // does not catch it diff --git a/staging/src/k8s.io/apiserver/pkg/storage/feature/feature_support_checker.go b/staging/src/k8s.io/apiserver/pkg/storage/feature/feature_support_checker.go index 77f5ab05c9c2b..e664d22a6467b 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/feature/feature_support_checker.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/feature/feature_support_checker.go @@ -109,7 +109,7 @@ func (f *defaultFeatureSupportChecker) checkClient(ctx context.Context, c client } f.checkingEndpoint[ep] = struct{}{} go func(ep string) { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithContext(ctx) err := delayFunc.Until(ctx, true, true, func(ctx context.Context) (done bool, err error) { internalErr := f.clientSupportsRequestWatchProgress(ctx, c, ep) return internalErr == nil, nil diff --git a/staging/src/k8s.io/apiserver/pkg/storage/interfaces_test.go b/staging/src/k8s.io/apiserver/pkg/storage/interfaces_test.go index 8f4826694a544..3826c1ceaa0d6 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/interfaces_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/interfaces_test.go @@ -173,7 +173,6 @@ func TestValidateListOptions(t *testing.T) { }, } for _, tt := range testCases { - tt := tt t.Run(tt.name, func(t *testing.T) { withRev, continueKey, err := ValidateListOptions("", APIObjectVersioner{}, tt.opts) if len(tt.expectedError) > 0 { diff --git a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go index 480b5a89342cf..37d470b25479f 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go @@ -20,10 +20,14 @@ import ( "context" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/sharding" "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) // AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match. @@ -82,12 +86,19 @@ type SelectionPredicate struct { Limit int64 Continue string AllowWatchBookmarks bool + // ShardSelector is the parsed shard selector for filtering objects by hash range. + ShardSelector sharding.Selector } // Matches returns true if the given object's labels and fields (as // returned by s.GetAttrs) match s.Label and s.Field. An error is // returned if s.GetAttrs fails. func (s *SelectionPredicate) Matches(obj runtime.Object) (bool, error) { + if utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + if matched, err := s.MatchesSharding(obj); err != nil || !matched { + return matched, err + } + } if s.Empty() { return true, nil } @@ -168,6 +179,30 @@ func (s *SelectionPredicate) MatcherIndex(ctx context.Context) []MatchValue { return result } +// MatchesSharding returns true if the given object matches the sharding configuration. +// If ShardSelector is set and non-empty, it delegates to ShardSelector.Matches(). +func (s *SelectionPredicate) MatchesSharding(obj runtime.Object) (bool, error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + return true, nil + } + if s.ShardSelector != nil && !s.ShardSelector.Empty() { + return s.ShardSelector.Matches(obj) + } + return true, nil +} + +// SetShardInfoOnList sets shard metadata on the list response if sharding is active. +func (s *SelectionPredicate) SetShardInfoOnList(listObj runtime.Object) { + if !utilfeature.DefaultFeatureGate.Enabled(features.ShardedListAndWatch) { + return + } + if s.ShardSelector != nil && !s.ShardSelector.Empty() { + if setter, ok := listObj.(metav1.ShardedListInterface); ok { + setter.SetShardInfo(&metav1.ShardInfo{Selector: s.ShardSelector.String()}) + } + } +} + func isNamespaceScopedRequest(ctx context.Context) (string, bool) { re, _ := request.RequestInfoFrom(ctx) if re == nil || len(re.Namespace) == 0 { diff --git a/staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go b/staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go index c6598d7cf237e..49607fe9367d9 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go @@ -29,7 +29,7 @@ import ( "sync" "time" - grpcprom "github.com/grpc-ecosystem/go-grpc-prometheus" + grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" "go.etcd.io/etcd/client/pkg/v3/logutil" "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" @@ -80,12 +80,15 @@ const ( // https://github.com/kubernetes/kubernetes/issues/111476 for more. var etcd3ClientLogger *zap.Logger +// grpcpromClientMetrics is a singleton instance of grpc client prometheus metrics. +var grpcpromClientMetrics = grpcprom.NewClientMetrics() + func init() { - // grpcprom auto-registers (via an init function) their client metrics, since we are opting out of - // using the global prometheus registry and using our own wrapped global registry, - // we need to explicitly register these metrics to our global registry here. + // Since we are opting out of using the global prometheus registry and using + // our own wrapped global registry, we need to explicitly register the client + // metrics to our global registry here. // For reference: https://github.com/kubernetes/kubernetes/pull/81387 - legacyregistry.RawMustRegister(grpcprom.DefaultClientMetrics) + legacyregistry.RawMustRegister(grpcpromClientMetrics) dbMetricsMonitors = make(map[string]struct{}) l, err := logutil.CreateDefaultZapLogger(etcdClientDebugLevel()) @@ -314,8 +317,8 @@ var newETCD3Client = func(c storagebackend.TransportConfig) (*kubernetes.Client, // // these optional interceptors will be placed after the default ones. // which seems to be what we want as the metrics will be collected on each attempt (retry) - grpc.WithChainUnaryInterceptor(grpcprom.UnaryClientInterceptor), - grpc.WithChainStreamInterceptor(grpcprom.StreamClientInterceptor), + grpc.WithChainUnaryInterceptor(grpcpromClientMetrics.UnaryClientInterceptor()), + grpc.WithChainStreamInterceptor(grpcpromClientMetrics.StreamClientInterceptor()), } if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) { tracingOpts := []otelgrpc.Option{ diff --git a/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go b/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go index 23e30eb45d491..669af09d73cb4 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.etcd.io/etcd/client/v3/kubernetes" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -205,7 +206,6 @@ func RunTestGet(ctx context.Context, t *testing.T, store storage.Interface) { }} for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { // For some asynchronous implementations of storage interface (in particular watchcache), // certain requests may impact result of further requests. As an example, if we first @@ -2262,7 +2262,6 @@ func RunTestList(ctx context.Context, t *testing.T, store storage.Interface, com } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { // For some asynchronous implementations of storage interface (in particular watchcache), // certain requests may impact result of further requests. As an example, if we first @@ -2688,7 +2687,6 @@ func RunTestGetListNonRecursive(ctx context.Context, t *testing.T, increaseRV In }} for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { // For some asynchronous implementations of storage interface (in particular watchcache), // certain requests may impact result of further requests. As an example, if we first @@ -2816,10 +2814,8 @@ func RunTestGetListRecursivePrefix(ctx context.Context, t *testing.T, store stor } for _, listType := range listTypes { - listType := listType t.Run(listType.name, func(t *testing.T) { for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { out := &example.PodList{} storageOpts := storage.ListOptions{ @@ -3717,7 +3713,7 @@ func RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx context.Context, t *te t.Fatalf("unexpected error: %v", err) } if updatedPod2.Generation != 3 { - t.Errorf("unexpected pod generation: %q", updatedPod2.Generation) + t.Errorf("unexpected pod generation: %d", updatedPod2.Generation) } // Third, update using a current version as the suggestion. diff --git a/staging/src/k8s.io/apiserver/pkg/storage/util.go b/staging/src/k8s.io/apiserver/pkg/storage/util.go index 3ca9acf3bd235..941422ef5e212 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/util.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/util.go @@ -22,7 +22,7 @@ import ( "sync/atomic" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/api/validation/path" + "k8s.io/apimachinery/pkg/api/validate/content" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -46,7 +46,7 @@ func NamespaceKeyFunc(prefix string, obj runtime.Object) (string, error) { return "", err } name := meta.GetName() - if msgs := path.IsValidPathSegmentName(name); len(msgs) != 0 { + if msgs := content.IsPathSegmentName(name); len(msgs) != 0 { return "", fmt.Errorf("invalid name: %v", msgs) } return prefix + meta.GetNamespace() + "/" + name, nil @@ -61,7 +61,7 @@ func NoNamespaceKeyFunc(prefix string, obj runtime.Object) (string, error) { return "", err } name := meta.GetName() - if msgs := path.IsValidPathSegmentName(name); len(msgs) != 0 { + if msgs := content.IsPathSegmentName(name); len(msgs) != 0 { return "", fmt.Errorf("invalid name: %v", msgs) } return prefix + name, nil diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes_test.go index a2e5a9278926e..6e0332ebe2098 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes_test.go @@ -502,7 +502,6 @@ func BenchmarkGCMRead(b *testing.B) { name := fmt.Sprintf("%vKeyLength/%vValueLength/%vExpectStale", t.keyLength, t.valueLength, t.expectStale) b.Run(name, func(b *testing.B) { for _, n := range gcmBenchmarks { - n := n if t.keyLength == 16 && n.name == "gcm-extended-nonce" { continue // gcm-extended-nonce requires 32 byte keys } @@ -528,7 +527,6 @@ func BenchmarkGCMWrite(b *testing.B) { name := fmt.Sprintf("%vKeyLength/%vValueLength", t.keyLength, t.valueLength) b.Run(name, func(b *testing.B) { for _, n := range gcmBenchmarks { - n := n if t.keyLength == 16 && n.name == "gcm-extended-nonce" { continue // gcm-extended-nonce requires 32 byte keys } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/cache_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/cache_test.go index c8ba9582966d3..7180642403cbf 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/cache_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/cache_test.go @@ -110,7 +110,6 @@ func Test_simpleCache(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clock := clocktesting.NewFakeClock(time.Now()) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service.go index b2a5fd145cd19..a5987c7893fc5 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service.go @@ -86,7 +86,7 @@ func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Durat s.kmsClient = kmsapi.NewKeyManagementServiceClient(s.connection) go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) <-ctx.Done() _ = s.connection.Close() diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service_unix_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service_unix_test.go index ede17d23e8488..96365df0a82f7 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service_unix_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service_unix_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. @@ -112,7 +111,6 @@ func TestTimeouts(t *testing.T) { } for _, tt := range testCases { - tt := tt t.Run(tt.desc, func(t *testing.T) { t.Parallel() var ( diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go index 4549257a0340f..01947eb053676 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go @@ -523,7 +523,6 @@ func generateCacheKey(encryptedDEKSourceType kmstypes.EncryptedDEKSourceType, en // Sort the annotations by key. keys := make([]string, 0, len(annotations)) for k := range annotations { - k := k keys = append(keys, k) } sort.Strings(keys) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope_test.go index 59dae5b19a240..a70c09e54653d 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. @@ -315,7 +314,6 @@ func TestEnvelopeTransformerStaleness(t *testing.T) { } for _, tt := range testCases { - tt := tt t.Run(tt.desc, func(t *testing.T) { t.Parallel() @@ -513,7 +511,6 @@ func TestTransformToStorageError(t *testing.T) { } for _, tt := range testCases { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -659,7 +656,6 @@ func TestValidateAnnotations(t *testing.T) { } t.Run("success", func(t *testing.T) { for i := range successCases { - i := i t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() if err := validateAnnotations(successCases[i]); err != nil { @@ -697,7 +693,6 @@ func TestValidateAnnotations(t *testing.T) { t.Run("name error", func(t *testing.T) { for i := range annotationsNameErrorCases { - i := i t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() err := validateAnnotations(annotationsNameErrorCases[i].annotations) @@ -725,7 +720,6 @@ func TestValidateAnnotations(t *testing.T) { } t.Run("size error", func(t *testing.T) { for i := range annotationsSizeErrorCases { - i := i t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() err := validateAnnotations(annotationsSizeErrorCases[i].annotations) @@ -770,7 +764,6 @@ func TestValidateKeyID(t *testing.T) { } for _, tt := range testCases { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() errCode, err := ValidateKeyID(tt.keyID) @@ -823,7 +816,6 @@ func TestValidateEncryptedDEKSource(t *testing.T) { } for _, tt := range testCases { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() err := validateEncryptedDEKSource(tt.encryptedDEKSource) @@ -1029,7 +1021,6 @@ func TestEnvelopeLogging(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.desc, func(t *testing.T) { var buf bytes.Buffer klog.SetOutput(&buf) @@ -1171,9 +1162,7 @@ func TestGenerateCacheKey(t *testing.T) { } for _, tc := range testCases { - tc := tc for _, tc2 := range testCases { - tc2 := tc2 t.Run(fmt.Sprintf("%+v-%+v", tc, tc2), func(t *testing.T) { key1, err1 := generateCacheKey(tc.encryptedDEKSourceType, tc.encryptedDEKSource, tc.keyID, tc.annotations) key2, err2 := generateCacheKey(tc2.encryptedDEKSourceType, tc2.encryptedDEKSource, tc2.keyID, tc2.annotations) @@ -1241,7 +1230,6 @@ func TestGenerateTransformer(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service.go index 78cc02e57a6c6..7cd7d1ce2b7f1 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service.go @@ -83,7 +83,7 @@ func NewGRPCService(ctx context.Context, endpoint, providerName string, callTime s.kmsClient = kmsapi.NewKeyManagementServiceClient(s.connection) go func() { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) <-ctx.Done() _ = s.connection.Close() diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service_unix_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service_unix_test.go index c0a78d9937dab..6826b18e96490 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service_unix_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service_unix_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. @@ -118,7 +117,6 @@ func TestTimeouts(t *testing.T) { } for _, tt := range testCases { - tt := tt t.Run(tt.desc, func(t *testing.T) { t.Parallel() var ( diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v1beta1/kms_plugin_mock.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v1beta1/kms_plugin_mock.go index 595107a899005..445bb6a536d52 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v1beta1/kms_plugin_mock.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v1beta1/kms_plugin_mock.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2017 The Kubernetes Authors. @@ -69,6 +68,8 @@ func NewBase64Plugin(t *testing.T, socketPath string) *Base64Plugin { socketPath: socketPath, } + // Make sure we don't have a leftover socket. + _ = os.Remove(socketPath) kmsapi.RegisterKeyManagementServiceServer(server, result) if err := result.start(); err != nil { t.Fatalf("failed to start KMS plugin, err: %v", err) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v2/kms_plugin_mock.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v2/kms_plugin_mock.go index d5da68f9a10dd..823805c76daab 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v2/kms_plugin_mock.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v2/kms_plugin_mock.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* Copyright 2022 The Kubernetes Authors. diff --git a/staging/src/k8s.io/apiserver/pkg/util/filesystem/watchuntil.go b/staging/src/k8s.io/apiserver/pkg/util/filesystem/watchuntil.go new file mode 100644 index 0000000000000..fe594e4bfb1ca --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/filesystem/watchuntil.go @@ -0,0 +1,149 @@ +/* +Copyright 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 filesystem + +import ( + "context" + "fmt" + "time" + + "github.com/fsnotify/fsnotify" +) + +type watchAddRemover interface { + Add(path string) error + Remove(path string) error +} + +type noopWatcher struct{} + +func (noopWatcher) Add(path string) error { return nil } +func (noopWatcher) Remove(path string) error { return nil } + +// WatchUntil watches the specified path for changes and blocks until ctx is canceled. +// eventHandler() must be non-nil, and pollInterval must be greater than 0. +// eventHandler() is invoked whenever a change event is observed or pollInterval elapses. +// errorHandler() is invoked (if non-nil) whenever an error occurs initializing or watching the specified path. +// +// If path is a directory, only the directory and immediate children are watched. +// +// If path does not exist or cannot be watched, an error is passed to errorHandler() and eventHandler() is called at pollInterval. +// +// Multiple observed events may collapse to a single invocation of eventHandler(). +// +// eventHandler() is invoked immediately after successful initialization of the filesystem watch, +// in case the path changed concurrent with calling WatchUntil(). +func WatchUntil(ctx context.Context, pollInterval time.Duration, path string, eventHandler func(), errorHandler func(err error)) { + if pollInterval <= 0 { + panic(fmt.Errorf("pollInterval must be > 0")) + } + if eventHandler == nil { + panic(fmt.Errorf("eventHandler must be non-nil")) + } + if errorHandler == nil { + errorHandler = func(err error) {} + } + + // Initialize watcher, fall back to no-op + var ( + eventsCh chan fsnotify.Event + errorCh chan error + watcher watchAddRemover + ) + if w, err := fsnotify.NewWatcher(); err != nil { + errorHandler(fmt.Errorf("error creating file watcher, falling back to poll at interval %s: %w", pollInterval, err)) + watcher = noopWatcher{} + } else { + watcher = w + eventsCh = w.Events + errorCh = w.Errors + defer func() { + _ = w.Close() + }() + } + + // Initialize background poll + t := time.NewTicker(pollInterval) + defer t.Stop() + + attemptPeriodicRewatch := false + + // Start watching the path + if err := watcher.Add(path); err != nil { + errorHandler(err) + attemptPeriodicRewatch = true + } else { + // Invoke handle() at least once after successfully registering the listener, + // in case the file changed concurrent with calling WatchUntil. + eventHandler() + } + + for { + select { + case <-ctx.Done(): + return + + case <-t.C: + // Prioritize exiting if context is canceled + if ctx.Err() != nil { + return + } + + // Try to re-establish the watcher if we previously got a watch error + if attemptPeriodicRewatch { + _ = watcher.Remove(path) + if err := watcher.Add(path); err != nil { + errorHandler(err) + } else { + attemptPeriodicRewatch = false + } + } + + // Handle + eventHandler() + + case e := <-eventsCh: + // Prioritize exiting if context is canceled + if ctx.Err() != nil { + return + } + + // Try to re-establish the watcher for events which dropped the existing watch + if e.Name == path && (e.Has(fsnotify.Remove) || e.Has(fsnotify.Rename)) { + _ = watcher.Remove(path) + if err := watcher.Add(path); err != nil { + errorHandler(err) + attemptPeriodicRewatch = true + } + } + + // Handle + eventHandler() + + case err := <-errorCh: + // Prioritize exiting if context is canceled + if ctx.Err() != nil { + return + } + + // If the error occurs in response to calling watcher.Add, re-adding here could hot-loop. + // The periodic poll will attempt to re-establish the watch. + errorHandler(err) + attemptPeriodicRewatch = true + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/filesystem/watchuntil_test.go b/staging/src/k8s.io/apiserver/pkg/util/filesystem/watchuntil_test.go new file mode 100644 index 0000000000000..fe53af67270a7 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/filesystem/watchuntil_test.go @@ -0,0 +1,257 @@ +/* +Copyright 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 filesystem + +import ( + "context" + "os" + "path/filepath" + "sync/atomic" + "testing" + "time" +) + +func TestWatchUntilPanicsOnInvalidArgs(t *testing.T) { + t.Run("zero pollInterval panics", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("expected panic for zero pollInterval") + } + }() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + WatchUntil(ctx, 0, "/tmp", func() {}, nil) + }) + + t.Run("negative pollInterval panics", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("expected panic for negative pollInterval") + } + }() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + WatchUntil(ctx, -1*time.Second, "/tmp", func() {}, nil) + }) + + t.Run("nil eventHandler panics", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("expected panic for nil eventHandler") + } + }() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + WatchUntil(ctx, time.Second, "/tmp", nil, nil) + }) +} + +func TestWatchUntilContextCancellation(t *testing.T) { + dir := t.TempDir() + + ctx, cancel := context.WithCancel(context.Background()) + var callCount atomic.Int32 + + done := make(chan struct{}) + go func() { + defer close(done) + WatchUntil(ctx, 50*time.Millisecond, dir, func() { + callCount.Add(1) + }, nil) + }() + + // Wait for at least one handler invocation + waitForCondition(t, 2*time.Second, func() bool { + return callCount.Load() > 0 + }) + + cancel() + + select { + case <-done: + // WatchUntil returned after context cancellation + case <-time.After(5 * time.Second): + t.Fatal("WatchUntil did not return after context cancellation") + } +} + +func TestWatchUntilCallsHandlerOnPoll(t *testing.T) { + dir := t.TempDir() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var callCount atomic.Int32 + + go func() { + WatchUntil(ctx, 50*time.Millisecond, dir, func() { + callCount.Add(1) + }, nil) + }() + + // Should get called multiple times via polling + waitForCondition(t, 2*time.Second, func() bool { + return callCount.Load() >= 3 + }) +} + +func TestWatchUntilCallsHandlerOnFileChange(t *testing.T) { + dir := t.TempDir() + testFile := filepath.Join(dir, "test.yaml") + if err := os.WriteFile(testFile, []byte("initial"), 0644); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var callCount atomic.Int32 + + go func() { + WatchUntil(ctx, 10*time.Minute, dir, func() { + callCount.Add(1) + }, nil) + }() + + // Wait for initial handler call + waitForCondition(t, 2*time.Second, func() bool { + return callCount.Load() >= 1 + }) + + initialCount := callCount.Load() + + // Modify the file to trigger a filesystem event + if err := os.WriteFile(testFile, []byte("modified"), 0644); err != nil { + t.Fatal(err) + } + + // Should get called again due to file change + waitForCondition(t, 5*time.Second, func() bool { + return callCount.Load() > initialCount + }) +} + +func TestWatchUntilNonExistentPathCallsErrorHandler(t *testing.T) { + nonExistent := filepath.Join(t.TempDir(), "does-not-exist") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var errorCount atomic.Int32 + var eventCount atomic.Int32 + + go func() { + WatchUntil(ctx, 50*time.Millisecond, nonExistent, func() { + eventCount.Add(1) + }, func(err error) { + errorCount.Add(1) + }) + }() + + // Error handler should be called for the non-existent path + waitForCondition(t, 2*time.Second, func() bool { + return errorCount.Load() > 0 + }) + + // Event handler should still be called via polling + waitForCondition(t, 2*time.Second, func() bool { + return eventCount.Load() > 0 + }) +} + +func TestWatchUntilNilErrorHandler(t *testing.T) { + nonExistent := filepath.Join(t.TempDir(), "does-not-exist") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var eventCount atomic.Int32 + + // nil errorHandler should not panic + done := make(chan struct{}) + go func() { + defer close(done) + WatchUntil(ctx, 50*time.Millisecond, nonExistent, func() { + eventCount.Add(1) + }, nil) + }() + + // Should still work with polling + waitForCondition(t, 2*time.Second, func() bool { + return eventCount.Load() > 0 + }) + + cancel() + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("WatchUntil did not return") + } +} + +func TestWatchUntilDetectsAtomicRename(t *testing.T) { + dir := t.TempDir() + testFile := filepath.Join(dir, "config.yaml") + if err := os.WriteFile(testFile, []byte("version: 1"), 0644); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var callCount atomic.Int32 + + go func() { + // Use a long poll interval so we rely on fsnotify, not polling + WatchUntil(ctx, 10*time.Minute, dir, func() { + callCount.Add(1) + }, nil) + }() + + // Wait for initial handler call + waitForCondition(t, 2*time.Second, func() bool { + return callCount.Load() >= 1 + }) + + countBefore := callCount.Load() + + // Atomic file replacement: write temp, then rename (recommended KEP pattern) + tmpFile := filepath.Join(dir, ".config.yaml.tmp") + if err := os.WriteFile(tmpFile, []byte("version: 2"), 0644); err != nil { + t.Fatal(err) + } + if err := os.Rename(tmpFile, testFile); err != nil { + t.Fatal(err) + } + + // Should detect the rename event + waitForCondition(t, 5*time.Second, func() bool { + return callCount.Load() > countBefore + }) +} + +func waitForCondition(t *testing.T, timeout time.Duration, condition func() bool) { + t.Helper() + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + if condition() { + return + } + time.Sleep(10 * time.Millisecond) + } + t.Fatalf("condition not met within %v", timeout) +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/exclusion_filter.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/exclusion_filter.go deleted file mode 100644 index a808d06157a04..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/exclusion_filter.go +++ /dev/null @@ -1,368 +0,0 @@ -/* -Copyright 2025 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 peerproxy - -import ( - "context" - "time" - - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/tools/cache" - "k8s.io/klog/v2" - - apidiscoveryv2 "k8s.io/api/apidiscovery/v2" -) - -// GVExtractor is a function that extracts group-versions from an object. -// It returns a slice of GroupVersions belonging to CRDs or aggregated APIs that -// should be excluded from peer-discovery to avoid advertising stale CRDs/aggregated APIs -// in peer-aggregated discovery that were deleted but still appear in a peer's discovery. -type GVExtractor func(obj interface{}) []schema.GroupVersion - -// RegisterCRDInformerHandlers registers event handlers for CRD informer using a custom extractor. -// The extractor function is responsible for extracting GroupVersions from CRD objects. -func (h *peerProxyHandler) RegisterCRDInformerHandlers(crdInformer cache.SharedIndexInformer, extractor GVExtractor) error { - h.crdInformer = crdInformer - h.crdExtractor = extractor - _, err := h.crdInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - gvs := extractor(obj) - if gvs == nil { - return - } - h.addExcludedGVs(gvs) - }, - UpdateFunc: func(oldObj, newObj interface{}) { - gvs := extractor(newObj) - if gvs == nil { - return - } - h.addExcludedGVs(gvs) - }, - DeleteFunc: func(obj interface{}) { - // Handle tombstone objects - if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { - obj = tombstone.Obj - } - gvs := extractor(obj) - if gvs == nil { - return - } - for _, gv := range gvs { - h.gvDeletionQueue.Add(gv.String()) - } - }, - }) - return err -} - -// RegisterAPIServiceInformerHandlers registers event handlers for APIService informer using a custom extractor. -// The extractor function is responsible for extracting GroupVersions from APIService objects -// and determining if they represent aggregated APIs. -func (h *peerProxyHandler) RegisterAPIServiceInformerHandlers(apiServiceInformer cache.SharedIndexInformer, extractor GVExtractor) error { - h.apiServiceInformer = apiServiceInformer - h.apiServiceExtractor = extractor - _, err := h.apiServiceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - gvs := extractor(obj) - if gvs == nil { - return - } - h.addExcludedGVs(gvs) - }, - UpdateFunc: func(oldObj, newObj interface{}) { - gvs := extractor(newObj) - if gvs == nil { - return - } - h.addExcludedGVs(gvs) - }, - DeleteFunc: func(obj interface{}) { - // Handle tombstone objects - if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { - obj = tombstone.Obj - } - - gvs := extractor(obj) - if gvs == nil { - return - } - for _, gv := range gvs { - h.gvDeletionQueue.Add(gv.String()) - } - }, - }) - return err -} - -// addExcludedGVs adds group-versions to the exclusion set, filters them from the peer discovery cache, -// and triggers invalidation callbacks if the cache changed. -func (h *peerProxyHandler) addExcludedGVs(gvs []schema.GroupVersion) { - if len(gvs) == 0 { - return - } - - h.excludedGVsMu.Lock() - for _, gv := range gvs { - h.excludedGVs[gv] = nil - } - h.excludedGVsMu.Unlock() - - // Load current peer cache and filter it - cache := h.peerDiscoveryInfoCache.Load() - if cache == nil { - return - } - - cacheMap, ok := cache.(map[string]PeerDiscoveryCacheEntry) - if !ok { - klog.Warning("Invalid cache type in peerDiscoveryInfoCache") - return - } - - // Always trigger filter and callbacks, to ensure late-appearing GVs are filtered. - if peerDiscovery, peerDiscoveryChanged := h.filterPeerDiscoveryCache(cacheMap); peerDiscoveryChanged { - h.storePeerDiscoveryCacheAndInvalidate(peerDiscovery) - } -} - -// filterPeerDiscoveryCache filters the provided peer discovery cache, -// excluding GVs in h.excludedGVs. -func (h *peerProxyHandler) filterPeerDiscoveryCache(cacheMap map[string]PeerDiscoveryCacheEntry) (map[string]PeerDiscoveryCacheEntry, bool) { - h.excludedGVsMu.RLock() - if len(h.excludedGVs) == 0 { - // No exclusions, no filtering needed. - h.excludedGVsMu.RUnlock() - return cacheMap, false - } - - excludedCloned := make(map[schema.GroupVersion]struct{}, len(h.excludedGVs)) - for gv := range h.excludedGVs { - excludedCloned[gv] = struct{}{} - } - h.excludedGVsMu.RUnlock() - - filtered := make(map[string]PeerDiscoveryCacheEntry, len(cacheMap)) - peerDiscoveryChanged := false - for peerID, entry := range cacheMap { - newEntry, peerChanged := h.filterPeerCacheEntry(entry, excludedCloned) - filtered[peerID] = newEntry - if peerChanged { - peerDiscoveryChanged = true - } - } - - return filtered, peerDiscoveryChanged -} - -// filterPeerCacheEntry filters a single peer's cache entry for excluded groups. -// Returns the filtered entry and whether any excluded group was present. -func (h *peerProxyHandler) filterPeerCacheEntry(entry PeerDiscoveryCacheEntry, excluded map[schema.GroupVersion]struct{}) (PeerDiscoveryCacheEntry, bool) { - filteredGVRs := make(map[schema.GroupVersionResource]bool, len(entry.GVRs)) - peerDiscoveryChanged := false - - for existingGVR, v := range entry.GVRs { - gv := schema.GroupVersion{Group: existingGVR.Group, Version: existingGVR.Version} - if _, skip := excluded[gv]; skip { - peerDiscoveryChanged = true - continue - } - filteredGVRs[existingGVR] = v - } - - if !peerDiscoveryChanged { - return entry, false - } - - // Filter the group discovery list - filteredGroups := h.filterGroupDiscovery(entry.GroupDiscovery, excluded) - return PeerDiscoveryCacheEntry{ - GVRs: filteredGVRs, - GroupDiscovery: filteredGroups, - }, true -} - -func (h *peerProxyHandler) filterGroupDiscovery(groupDiscoveries []apidiscoveryv2.APIGroupDiscovery, excluded map[schema.GroupVersion]struct{}) []apidiscoveryv2.APIGroupDiscovery { - var filteredDiscovery []apidiscoveryv2.APIGroupDiscovery - for _, groupDiscovery := range groupDiscoveries { - filteredGroup := apidiscoveryv2.APIGroupDiscovery{ - ObjectMeta: groupDiscovery.ObjectMeta, - } - - for _, version := range groupDiscovery.Versions { - gv := schema.GroupVersion{Group: groupDiscovery.Name, Version: version.Version} - if _, found := excluded[gv]; found { - // This version is excluded, skip it - continue - } - filteredGroup.Versions = append(filteredGroup.Versions, version) - } - - // Only add the group to the final list if it still has any versions left - if len(filteredGroup.Versions) > 0 { - filteredDiscovery = append(filteredDiscovery, filteredGroup) - } - } - return filteredDiscovery -} - -// RunGVDeletionWorkers starts workers to process GroupVersions from deleted CRDs and APIServices. -// When a GV is processed, it is checked for usage by other resources. If no longer in use, -// it is marked with a deletion timestamp, initiating a grace period. After this period, -// the RunExcludedGVsReaper is responsible for removing the GV from the exclusion set. -func (h *peerProxyHandler) RunGVDeletionWorkers(ctx context.Context, workers int) { - defer func() { - klog.Info("Shutting down GV deletion workers") - h.gvDeletionQueue.ShutDown() - }() - klog.Infof("Starting %d GV deletion worker(s)", workers) - for i := 0; i < workers; i++ { - go func(workerID int) { - for h.processNextGVDeletion() { - select { - case <-ctx.Done(): - klog.Infof("GV deletion worker %d shutting down", workerID) - return - default: - } - } - }(i) - } - - <-ctx.Done() // Wait till context is done to call deferred shutdown function -} - -// processNextGVDeletion processes a single item from the GV deletion queue. -func (h *peerProxyHandler) processNextGVDeletion() bool { - gvString, shutdown := h.gvDeletionQueue.Get() - if shutdown { - return false - } - defer h.gvDeletionQueue.Done(gvString) - - gv, err := schema.ParseGroupVersion(gvString) - if err != nil { - klog.Errorf("Failed to parse GroupVersion %q: %v", gvString, err) - return true - } - - h.markGVForDeletionIfUnused(gv) - return true -} - -// markGVForDeletionIfUnused checks if a group-version is still in use. -// If not, it marks it for deletion by setting a timestamp. -func (h *peerProxyHandler) markGVForDeletionIfUnused(gv schema.GroupVersion) { - // Best-effort check if GV is still in use. This is racy - a new CRD/APIService could be - // created after this check completes and returns false. - if h.isGVUsed(gv) { - return - } - - h.excludedGVsMu.Lock() - defer h.excludedGVsMu.Unlock() - - // Only mark if it's currently active (nil timestamp) - if ts, exists := h.excludedGVs[gv]; exists && ts == nil { - now := time.Now() - h.excludedGVs[gv] = &now - klog.V(4).Infof("Marking group-version %q for deletion, grace period started", gv) - } -} - -// isGVUsed checks the informer stores to see if any CRD or APIService -// is still using the given group-version. -// -// This is necessary because multiple CRDs or aggregated APIs can share the same group-version, -// but define different resources (kinds) or versions. Deleting one CRD or APIService -// for a group-version does not mean the group-version is entirely gone—other CRDs or APIs in that group-version -// may still exist. We must only remove a group-version from the exclusion set when there are -// no remaining CRDs or APIService objects for that group-version, to ensure we do not prematurely -// allow peer-aggregated discovery or proxying for APIs that are still served locally. -func (h *peerProxyHandler) isGVUsed(gv schema.GroupVersion) bool { - // Check CRD informer store for the specific group-version - if h.crdInformer != nil && h.crdExtractor != nil { - crdList := h.crdInformer.GetStore().List() - for _, item := range crdList { - gvs := h.crdExtractor(item) - if gvs == nil { - continue - } - for _, extractedGV := range gvs { - if extractedGV == gv { - return true - } - } - } - } - - // Check APIService informer store for the specific group-version - if h.apiServiceInformer != nil && h.apiServiceExtractor != nil { - apiSvcList := h.apiServiceInformer.GetStore().List() - for _, item := range apiSvcList { - gvs := h.apiServiceExtractor(item) - if gvs == nil { - continue - } - for _, extractedGV := range gvs { - if extractedGV == gv { - return true - } - } - } - } - - return false -} - -// RunExcludedGVsReaper starts a goroutine that periodically cleans up -// excluded group-versions that have passed their grace period. -func (h *peerProxyHandler) RunExcludedGVsReaper(stopCh <-chan struct{}) { - klog.Infof("Starting excluded group-versions reaper with %s interval", h.reaperCheckInterval) - ticker := time.NewTicker(h.reaperCheckInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - h.reapExcludedGVs() - case <-stopCh: - klog.Info("Shutting down excluded group-versions reaper") - return - } - } -} - -// reapExcludedGVs is the garbage collector function. -// It removes group-versions from excludedGVs if their grace period has expired. -func (h *peerProxyHandler) reapExcludedGVs() { - h.excludedGVsMu.Lock() - defer h.excludedGVsMu.Unlock() - - now := time.Now() - for gv, deletionTime := range h.excludedGVs { - if deletionTime == nil { - // Still actively excluded - continue - } - - if now.Sub(*deletionTime) > h.exclusionGracePeriod { - klog.V(4).Infof("Reaping excluded group-version %q (grace period expired)", gv) - delete(h.excludedGVs, gv) - } - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/exclusion_filter_test.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/exclusion_filter_test.go deleted file mode 100644 index bafa19341bad7..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/exclusion_filter_test.go +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright 2025 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 peerproxy - -import ( - "reflect" - "testing" - - "k8s.io/apimachinery/pkg/runtime/schema" - - apidiscoveryv2 "k8s.io/api/apidiscovery/v2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestFilteringLogic(t *testing.T) { - gvr := func(g, v, r string) schema.GroupVersionResource { - return schema.GroupVersionResource{Group: g, Version: v, Resource: r} - } - gv := func(g, v string) schema.GroupVersion { - return schema.GroupVersion{Group: g, Version: v} - } - - testCases := []struct { - name string - initialPeerCache PeerDiscoveryCacheEntry - excludedGVs map[schema.GroupVersion]struct{} - wantFilteredGVRs map[schema.GroupVersionResource]bool - wantFilteredGroups []apidiscoveryv2.APIGroupDiscovery - wantChange bool - }{ - { - name: "no exclusions", - initialPeerCache: PeerDiscoveryCacheEntry{ - GVRs: map[schema.GroupVersionResource]bool{gvr("apps", "v1", "deployments"): true}, - GroupDiscovery: []apidiscoveryv2.APIGroupDiscovery{{ - ObjectMeta: metav1.ObjectMeta{Name: "apps"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }}, - }, - excludedGVs: map[schema.GroupVersion]struct{}{}, - wantFilteredGVRs: map[schema.GroupVersionResource]bool{gvr("apps", "v1", "deployments"): true}, - wantFilteredGroups: []apidiscoveryv2.APIGroupDiscovery{{ - ObjectMeta: metav1.ObjectMeta{Name: "apps"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }}, - wantChange: false, - }, - { - name: "exclude one GV that exists", - initialPeerCache: PeerDiscoveryCacheEntry{ - GVRs: map[schema.GroupVersionResource]bool{ - gvr("apps", "v1", "deployments"): true, - gvr("apps", "v1", "statefulsets"): true, - gvr("batch", "v1", "jobs"): true, - }, - GroupDiscovery: []apidiscoveryv2.APIGroupDiscovery{ - { - ObjectMeta: metav1.ObjectMeta{Name: "apps"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "batch"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }, - }, - }, - excludedGVs: map[schema.GroupVersion]struct{}{ - gv("apps", "v1"): {}, - }, - wantFilteredGVRs: map[schema.GroupVersionResource]bool{gvr("batch", "v1", "jobs"): true}, - wantFilteredGroups: []apidiscoveryv2.APIGroupDiscovery{ - { - ObjectMeta: metav1.ObjectMeta{Name: "batch"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }, - }, - wantChange: true, - }, - { - name: "exclude a GV that does not exist", - initialPeerCache: PeerDiscoveryCacheEntry{ - GVRs: map[schema.GroupVersionResource]bool{gvr("apps", "v1", "deployments"): true}, - GroupDiscovery: []apidiscoveryv2.APIGroupDiscovery{{ - ObjectMeta: metav1.ObjectMeta{Name: "apps"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }}, - }, - excludedGVs: map[schema.GroupVersion]struct{}{ - gv("foo", "v1"): {}, - }, - wantFilteredGVRs: map[schema.GroupVersionResource]bool{gvr("apps", "v1", "deployments"): true}, - wantFilteredGroups: []apidiscoveryv2.APIGroupDiscovery{{ - ObjectMeta: metav1.ObjectMeta{Name: "apps"}, - Versions: []apidiscoveryv2.APIVersionDiscovery{{Version: "v1"}}, - }}, - wantChange: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - h := &peerProxyHandler{} - filteredEntry, changed := h.filterPeerCacheEntry(tc.initialPeerCache, tc.excludedGVs) - - if changed != tc.wantChange { - t.Errorf("want change to be %v, got %v", tc.wantChange, changed) - } - - if !reflect.DeepEqual(filteredEntry.GVRs, tc.wantFilteredGVRs) { - t.Errorf("filtered GVRs mismatch: got %v, want %v", filteredEntry.GVRs, tc.wantFilteredGVRs) - } - - if !reflect.DeepEqual(filteredEntry.GroupDiscovery, tc.wantFilteredGroups) { - t.Errorf("filtered GroupDiscovery mismatch: got %v, want %v", filteredEntry.GroupDiscovery, tc.wantFilteredGroups) - } - }) - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/gv_exclusion_manager.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/gv_exclusion_manager.go new file mode 100644 index 0000000000000..e7fb00710310c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/gv_exclusion_manager.go @@ -0,0 +1,492 @@ +/* +Copyright 2025 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 peerproxy + +import ( + "context" + "sync/atomic" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + + apidiscoveryv2 "k8s.io/api/apidiscovery/v2" +) + +// GVExtractor is a function that extracts group-versions from an object. +// It returns a slice of GroupVersions belonging to CRDs or aggregated APIs that +// should be excluded from peer-discovery to avoid advertising stale CRDs/aggregated APIs +// in peer-aggregated discovery that were deleted but still appear in a peer's discovery. +type GVExtractor func(obj interface{}) []schema.GroupVersion + +// GVExclusionManager manages the exclusion of group-versions from peer discovery. +// It maintains two atomic maps: +// - currentlyActiveGVs: All GVs currently served by CRDs and aggregated APIServices +// - recentlyDeletedGVs: GVs belonging to CRDs and aggregated APIServices that were recently deleted, +// tracked with deletion timestamp for grace period +// +// It runs two workers: +// 1. Active GV Tracker: Triggered on CRD/APIService events, rebuilds active GVs +// and reaps expired deleted GVs. When a GV is deleted, a delayed sync is scheduled +// after the grace period to reap it. +// 2. Peer Discovery Re-filter: Rate-limited worker that filters peer cache +type GVExclusionManager struct { + // Atomic maps for lock-free access + currentlyActiveGVs atomic.Value // map[schema.GroupVersion]struct{} + recentlyDeletedGVs atomic.Value // map[schema.GroupVersion]time.Time + + // Informers for fetching active GVs + crdInformer cache.SharedIndexInformer + crdExtractor GVExtractor + apiServiceInformer cache.SharedIndexInformer + apiServiceExtractor GVExtractor + + // Worker 1: triggered by CRD/APIService events or delayed reap scheduling + activeGVQueue workqueue.TypedRateLimitingInterface[string] + // Grace period before reaping deleted GVs from the exclusion set + exclusionGracePeriod time.Duration + // Worker 2: triggered by Active/Deleted GV changes + refilterQueue workqueue.TypedRateLimitingInterface[string] + + // rawPeerDiscoveryCache is written only by peerLeaseQueue worker + // when peer leases change + rawPeerDiscoveryCache *atomic.Value // map[string]PeerDiscoveryCacheEntry + // filteredPeerDiscoveryCache is written only by the refilter worker + // when raw cache or exclusion set changes. + filteredPeerDiscoveryCache atomic.Value // map[string]PeerDiscoveryCacheEntry + invalidationCallback *atomic.Pointer[func()] +} + +// NewGVExclusionManager creates a new GV exclusion manager. +func NewGVExclusionManager( + exclusionGracePeriod time.Duration, + rawPeerDiscoveryCache *atomic.Value, + invalidationCallback *atomic.Pointer[func()], +) *GVExclusionManager { + mgr := &GVExclusionManager{ + exclusionGracePeriod: exclusionGracePeriod, + rawPeerDiscoveryCache: rawPeerDiscoveryCache, + invalidationCallback: invalidationCallback, + activeGVQueue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[string](), + workqueue.TypedRateLimitingQueueConfig[string]{ + Name: "active-gv-tracker", + }), + refilterQueue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[string](), + workqueue.TypedRateLimitingQueueConfig[string]{ + Name: "peer-discovery-refilter", + }), + } + + mgr.currentlyActiveGVs.Store(map[schema.GroupVersion]struct{}{}) + mgr.recentlyDeletedGVs.Store(map[schema.GroupVersion]time.Time{}) + mgr.filteredPeerDiscoveryCache.Store(map[string]PeerDiscoveryCacheEntry{}) + + return mgr +} + +// GetFilteredPeerDiscoveryCache returns the filtered peer discovery cache. +func (m *GVExclusionManager) GetFilteredPeerDiscoveryCache() map[string]PeerDiscoveryCacheEntry { + if cache := m.filteredPeerDiscoveryCache.Load(); cache != nil { + if cacheMap, ok := cache.(map[string]PeerDiscoveryCacheEntry); ok { + return cacheMap + } + } + return map[string]PeerDiscoveryCacheEntry{} +} + +// RegisterCRDInformerHandlers registers event handlers for CRD informer using a custom extractor. +// The extractor function is responsible for extracting GroupVersions from CRD objects. +func (m *GVExclusionManager) RegisterCRDInformerHandlers(crdInformer cache.SharedIndexInformer, extractor GVExtractor) error { + m.crdInformer = crdInformer + m.crdExtractor = extractor + _, err := m.crdInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + m.handleGVUpdate() + }, + UpdateFunc: func(oldObj, newObj interface{}) { + m.handleGVUpdate() + }, + DeleteFunc: func(obj interface{}) { + m.handleGVUpdate() + }, + }) + return err +} + +// RegisterAPIServiceInformerHandlers registers event handlers for APIService informer using a custom extractor. +// The extractor function is responsible for extracting GroupVersions from APIService objects +// and determining if they represent aggregated APIs. +func (m *GVExclusionManager) RegisterAPIServiceInformerHandlers(apiServiceInformer cache.SharedIndexInformer, extractor GVExtractor) error { + m.apiServiceInformer = apiServiceInformer + m.apiServiceExtractor = extractor + _, err := m.apiServiceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + m.handleGVUpdate() + }, + UpdateFunc: func(oldObj, newObj interface{}) { + m.handleGVUpdate() + }, + DeleteFunc: func(obj interface{}) { + m.handleGVUpdate() + }, + }) + return err +} + +// WaitForCacheSync waits for the informer caches to sync. +func (m *GVExclusionManager) WaitForCacheSync(stopCh <-chan struct{}) bool { + synced := []cache.InformerSynced{} + if m.crdInformer != nil { + synced = append(synced, m.crdInformer.HasSynced) + } + if m.apiServiceInformer != nil { + synced = append(synced, m.apiServiceInformer.HasSynced) + } + if len(synced) == 0 { + return true + } + return cache.WaitForNamedCacheSync("gv-exclusion-manager", stopCh, synced...) +} + +// getExclusionSet returns the combined exclusion set i.e., union(currentlyActiveGVs, recentlyDeletedGVs) +func (m *GVExclusionManager) getExclusionSet() map[schema.GroupVersion]struct{} { + activeMap := m.loadCurrentlyActiveGVs() + deletedMap := m.loadRecentlyDeletedGVs() + + exclusionSet := make(map[schema.GroupVersion]struct{}, len(activeMap)+len(deletedMap)) + for gv := range activeMap { + exclusionSet[gv] = struct{}{} + } + for gv := range deletedMap { + exclusionSet[gv] = struct{}{} + } + + return exclusionSet +} + +// handleGVUpdate is called when a CRD or APIService event occurs. +// This triggers reconciliation which rebuilds the active GV set +// and also reaps expired GVs if indicated so. +func (m *GVExclusionManager) handleGVUpdate() { + m.activeGVQueue.Add("sync") +} + +// RunPeerDiscoveryActiveGVTracker runs the Active GV Tracker worker. +// This worker is triggered by CRD/APIService events and +// rebuilds the active GV set and reaps expired GVs. +func (m *GVExclusionManager) RunPeerDiscoveryActiveGVTracker(ctx context.Context) { + defer m.activeGVQueue.ShutDown() + + klog.Info("Starting Active GV Tracker worker") + go wait.UntilWithContext(ctx, m.runActiveGVTrackerWorker, time.Second) + + <-ctx.Done() + klog.Info("Active GV Tracker workers stopped") +} + +func (m *GVExclusionManager) runActiveGVTrackerWorker(ctx context.Context) { + for m.processNextActiveGV(ctx) { + } +} + +func (m *GVExclusionManager) processNextActiveGV(ctx context.Context) bool { + key, shutdown := m.activeGVQueue.Get() + if shutdown { + return false + } + defer m.activeGVQueue.Done(key) + + select { + case <-ctx.Done(): + return false + default: + } + + m.reconcileActiveGVs() + return true +} + +// reconcileActiveGVs does the following +// 1. fetches all GVs from CRD and APIService informers +// 2. detects diffs with the previous state +// 3. adds deleted GVs to recentlyDeletedGVs +// 4. reaps expired GVs, and queues Worker 2 if changes detected. +func (m *GVExclusionManager) reconcileActiveGVs() { + freshGVs := make(map[schema.GroupVersion]struct{}) + + // Fetch GVs from CRD informer + if m.crdInformer != nil && m.crdExtractor != nil { + crdList := m.crdInformer.GetStore().List() + for _, item := range crdList { + gvs := m.crdExtractor(item) + for _, gv := range gvs { + freshGVs[gv] = struct{}{} + } + } + } + + // Fetch GVs from APIService informer + if m.apiServiceInformer != nil && m.apiServiceExtractor != nil { + apiSvcList := m.apiServiceInformer.GetStore().List() + for _, item := range apiSvcList { + gvs := m.apiServiceExtractor(item) + for _, gv := range gvs { + freshGVs[gv] = struct{}{} + } + } + } + + // Load previous active GVs and detect diff + previousGVs := m.loadCurrentlyActiveGVs() + deletedGVs, activeGVsChanged := diffGVs(previousGVs, freshGVs) + + // Update recentlyDeletedGVs: add newly deleted GVs and reap expired ones + recentlyDeletedChanged := m.updateRecentlyDeletedGVs(deletedGVs) + + if activeGVsChanged || recentlyDeletedChanged { + if activeGVsChanged { + m.currentlyActiveGVs.Store(freshGVs) + klog.V(4).Infof("Active GVs updated: %d GVs now active", len(freshGVs)) + } + m.TriggerRefilter() + } else { + klog.V(4).Infof("No changes detected in active or recently deleted GVs") + } +} + +// updateRecentlyDeletedGVs adds newly deleted GVs to recentlyDeletedGVs +// and reaps expired ones. Returns true if any changes were made. +func (m *GVExclusionManager) updateRecentlyDeletedGVs(deletedGVs []schema.GroupVersion) bool { + deletedMap := m.loadRecentlyDeletedGVs() + // Early return if nothing to do + if len(deletedGVs) == 0 && len(deletedMap) == 0 { + return false + } + + now := time.Now() + newDeletedMap := make(map[schema.GroupVersion]time.Time, len(deletedMap)+len(deletedGVs)) + changed := false + + // Copy existing entries, reaping expired ones + for gv, deletionTime := range deletedMap { + if now.Sub(deletionTime) > m.exclusionGracePeriod { + klog.V(4).Infof("Reaping GV %s (grace period expired)", gv.String()) + changed = true + // Don't add to new map (effectively removing it) + } else { + newDeletedMap[gv] = deletionTime + } + } + + // Schedule a delayed sync to reap expired GVs after the grace period. + if len(deletedGVs) > 0 { + m.activeGVQueue.AddAfter("sync", m.exclusionGracePeriod) + } + + for _, gv := range deletedGVs { + newDeletedMap[gv] = now + klog.V(4).Infof("GV %s deleted: moved to recentlyDeletedGVs", gv.String()) + changed = true + } + + if changed { + m.recentlyDeletedGVs.Store(newDeletedMap) + } + + return changed +} + +// diffGVs compares old and new GV maps, returns GVs that were deleted (in old but not new) +// and a boolean indicating if there were any changes (additions or deletions). +func diffGVs(old, new map[schema.GroupVersion]struct{}) ([]schema.GroupVersion, bool) { + var deletedGVs []schema.GroupVersion + hasChanges := len(old) != len(new) + + // Find deleted GVs (in old but not in new) + for gv := range old { + if _, exists := new[gv]; !exists { + deletedGVs = append(deletedGVs, gv) + hasChanges = true + } + } + + return deletedGVs, hasChanges +} + +// RunPeerDiscoveryRefilter runs the Peer Discovery Re-filter worker. +// This worker filters the peer discovery cache using the exclusion set. +func (m *GVExclusionManager) RunPeerDiscoveryRefilter(ctx context.Context) { + defer m.refilterQueue.ShutDown() + + klog.Info("Starting Peer Discovery Re-filter worker") + go wait.UntilWithContext(ctx, m.runRefilterWorker, time.Second) + + <-ctx.Done() + klog.Info("Peer Discovery Re-filter workers stopped") +} + +func (m *GVExclusionManager) runRefilterWorker(ctx context.Context) { + for m.processNextRefilter(ctx) { + } +} + +func (m *GVExclusionManager) processNextRefilter(ctx context.Context) bool { + key, shutdown := m.refilterQueue.Get() + if shutdown { + return false + } + defer m.refilterQueue.Done(key) + + select { + case <-ctx.Done(): + return false + default: + } + + m.refilterPeerDiscoveryCache() + return true +} + +// refilterPeerDiscoveryCache reads the raw peer discovery cache, +// applies exclusion filtering, and stores the result to the filtered cache. +func (m *GVExclusionManager) refilterPeerDiscoveryCache() { + var cacheMap map[string]PeerDiscoveryCacheEntry + if m.rawPeerDiscoveryCache != nil { + if rawCache := m.rawPeerDiscoveryCache.Load(); rawCache != nil { + cacheMap, _ = rawCache.(map[string]PeerDiscoveryCacheEntry) + } + } + + if len(cacheMap) == 0 { + m.filteredPeerDiscoveryCache.Store(map[string]PeerDiscoveryCacheEntry{}) + klog.V(4).Infof("Raw peer discovery cache is empty or unavailable") + return + } + + filteredCache := m.filterPeerDiscoveryCache(cacheMap) + m.filteredPeerDiscoveryCache.Store(filteredCache) + + if m.invalidationCallback != nil { + if callback := m.invalidationCallback.Load(); callback != nil { + (*callback)() + } + } + + klog.V(4).Infof("Peer discovery cache re-filtered, %d GVs in exclusion set", len(m.getExclusionSet())) + +} + +// filterPeerDiscoveryCache applies the current exclusion set to the provided peer cache. +func (m *GVExclusionManager) filterPeerDiscoveryCache(cacheMap map[string]PeerDiscoveryCacheEntry) map[string]PeerDiscoveryCacheEntry { + exclusionSet := m.getExclusionSet() + if len(exclusionSet) == 0 { + return cacheMap + } + + filtered := make(map[string]PeerDiscoveryCacheEntry, len(cacheMap)) + for peerID, entry := range cacheMap { + filtered[peerID] = m.filterPeerCacheEntry(entry, exclusionSet) + } + + return filtered +} + +// filterPeerCacheEntry filters a single peer's cache entry for excluded GVs. +func (m *GVExclusionManager) filterPeerCacheEntry( + entry PeerDiscoveryCacheEntry, + exclusionSet map[schema.GroupVersion]struct{}, +) PeerDiscoveryCacheEntry { + filteredGVRs := make(map[schema.GroupVersionResource]bool, len(entry.GVRs)) + anyExcluded := false + for existingGVR, v := range entry.GVRs { + gv := schema.GroupVersion{Group: existingGVR.Group, Version: existingGVR.Version} + if _, excluded := exclusionSet[gv]; excluded { + anyExcluded = true + continue + } + filteredGVRs[existingGVR] = v + } + + // If no GVRs were excluded, the exclusion set doesn't intersect with this peer's GVs, + // so we can return the entry unchanged without filtering GroupDiscovery. + if !anyExcluded { + return entry + } + filteredGroups := m.filterGroupDiscovery(entry.GroupDiscovery, exclusionSet) + + return PeerDiscoveryCacheEntry{ + GVRs: filteredGVRs, + GroupDiscovery: filteredGroups, + } +} + +// filterGroupDiscovery filters group discovery entries, removing excluded GVs. +func (m *GVExclusionManager) filterGroupDiscovery( + groupDiscoveries []apidiscoveryv2.APIGroupDiscovery, + exclusionSet map[schema.GroupVersion]struct{}, +) []apidiscoveryv2.APIGroupDiscovery { + var filteredDiscovery []apidiscoveryv2.APIGroupDiscovery + for _, groupDiscovery := range groupDiscoveries { + filteredGroup := apidiscoveryv2.APIGroupDiscovery{ + ObjectMeta: groupDiscovery.ObjectMeta, + } + + for _, version := range groupDiscovery.Versions { + gv := schema.GroupVersion{Group: groupDiscovery.Name, Version: version.Version} + if _, found := exclusionSet[gv]; found { + // This version is excluded, skip it + continue + } + filteredGroup.Versions = append(filteredGroup.Versions, version) + } + + // Only add the group to the final list if it still has any versions left + if len(filteredGroup.Versions) > 0 { + filteredDiscovery = append(filteredDiscovery, filteredGroup) + } + } + return filteredDiscovery +} + +func (m *GVExclusionManager) loadCurrentlyActiveGVs() map[schema.GroupVersion]struct{} { + if val := m.currentlyActiveGVs.Load(); val != nil { + if gvMap, ok := val.(map[schema.GroupVersion]struct{}); ok { + return gvMap + } + } + return map[schema.GroupVersion]struct{}{} +} + +func (m *GVExclusionManager) loadRecentlyDeletedGVs() map[schema.GroupVersion]time.Time { + if val := m.recentlyDeletedGVs.Load(); val != nil { + if gvMap, ok := val.(map[schema.GroupVersion]time.Time); ok { + return gvMap + } + } + return map[schema.GroupVersion]time.Time{} +} + +// TriggerRefilter triggers the refilter worker to apply exclusions to the filtered cache. +// This should be called by peerLeaseQueue after updating the raw peer discovery cache. +func (m *GVExclusionManager) TriggerRefilter() { + m.refilterQueue.Add("refilter") +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/gv_exclusion_manager_test.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/gv_exclusion_manager_test.go new file mode 100644 index 0000000000000..bc8125bf20ef6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/gv_exclusion_manager_test.go @@ -0,0 +1,589 @@ +/* +Copyright 2025 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 peerproxy + +import ( + "slices" + "sync/atomic" + "testing" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + + apidiscoveryv2 "k8s.io/api/apidiscovery/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetExclusionSet(t *testing.T) { + tests := []struct { + name string + activeGVs map[schema.GroupVersion]struct{} + deletedGVs map[schema.GroupVersion]time.Time + wantGVs []schema.GroupVersion + }{ + { + name: "empty state", + activeGVs: nil, + deletedGVs: nil, + wantGVs: []schema.GroupVersion{}, + }, + { + name: "only active GVs", + activeGVs: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + {Group: "batch", Version: "v1"}: {}, + {Group: "custom", Version: "v1alpha1"}: {}, + }, + wantGVs: []schema.GroupVersion{ + {Group: "apps", Version: "v1"}, + {Group: "batch", Version: "v1"}, + {Group: "custom", Version: "v1alpha1"}, + }, + }, + { + name: "only deleted GVs", + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "deprecated", Version: "v1beta1"}: time.Now(), + {Group: "legacy", Version: "v1alpha1"}: time.Now(), + }, + // Reaper hasnt removed deleted GVs yet. + wantGVs: []schema.GroupVersion{ + {Group: "deprecated", Version: "v1beta1"}, + {Group: "legacy", Version: "v1alpha1"}, + }, + }, + { + name: "different active and deleted GVs", + activeGVs: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + {Group: "batch", Version: "v1"}: {}, + }, + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "old", Version: "v1"}: time.Now(), + }, + // Include both active GVs and recently deleted GVs. + // Deleted GVs remain in the exclusion set until the reaper removes them after the grace period. + wantGVs: []schema.GroupVersion{ + {Group: "apps", Version: "v1"}, + {Group: "batch", Version: "v1"}, + {Group: "old", Version: "v1"}, + }, + }, + { + // A GV can appear in both active and deleted sets if: + // 1. CRD was deleted (moved from active to deleted) + // 2. CRD was recreated (added back to active) + // 3. Reaper hasn't cleaned up the deleted entry yet + name: "same GV in both active and deleted", + activeGVs: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "apps", Version: "v1"}: time.Now(), + }, + wantGVs: []schema.GroupVersion{ + {Group: "apps", Version: "v1"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mgr := NewGVExclusionManager(5*time.Minute, &atomic.Value{}, &atomic.Pointer[func()]{}) + + if tt.activeGVs != nil { + mgr.currentlyActiveGVs.Store(tt.activeGVs) + } + if tt.deletedGVs != nil { + mgr.recentlyDeletedGVs.Store(tt.deletedGVs) + } + + exclusionSet := mgr.getExclusionSet() + if len(exclusionSet) != len(tt.wantGVs) { + t.Errorf("Want exclusion set size %d, got %d", len(tt.wantGVs), len(exclusionSet)) + } + + for _, gv := range tt.wantGVs { + if _, found := exclusionSet[gv]; !found { + t.Errorf("Want GV %v in exclusion set, but not found", gv) + } + } + }) + } +} + +func TestReapExpiredGVs(t *testing.T) { + tests := []struct { + name string + gracePeriod time.Duration + deletedGVs map[schema.GroupVersion]time.Time + wantReaped []schema.GroupVersion + }{ + { + name: "empty state", + gracePeriod: 100 * time.Millisecond, + deletedGVs: map[schema.GroupVersion]time.Time{}, + wantReaped: []schema.GroupVersion{}, + }, + { + name: "reap old GV keep recent", + gracePeriod: 100 * time.Millisecond, + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "old", Version: "v1"}: time.Now().Add(-200 * time.Millisecond), + {Group: "recent", Version: "v1"}: time.Now().Add(-50 * time.Millisecond), + {Group: "new", Version: "v1"}: time.Now(), + }, + wantReaped: []schema.GroupVersion{ + {Group: "old", Version: "v1"}, + }, + }, + { + name: "all expired", + gracePeriod: 50 * time.Millisecond, + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "old1", Version: "v1"}: time.Now().Add(-100 * time.Millisecond), + {Group: "old2", Version: "v1"}: time.Now().Add(-200 * time.Millisecond), + }, + wantReaped: []schema.GroupVersion{ + {Group: "old1", Version: "v1"}, + {Group: "old2", Version: "v1"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mgr := NewGVExclusionManager(tt.gracePeriod, &atomic.Value{}, &atomic.Pointer[func()]{}) + mgr.recentlyDeletedGVs.Store(tt.deletedGVs) + mgr.updateRecentlyDeletedGVs(nil) + result := mgr.loadRecentlyDeletedGVs() + + for _, gv := range tt.wantReaped { + if _, found := result[gv]; found { + t.Errorf("GV %v should have been reaped but still exists", gv) + } + } + }) + } +} + +func TestDetectDiff(t *testing.T) { + _ = NewGVExclusionManager(5*time.Minute, &atomic.Value{}, &atomic.Pointer[func()]{}) + + tests := []struct { + name string + old map[schema.GroupVersion]struct{} + new map[schema.GroupVersion]struct{} + wantChanged bool + wantDeleted []schema.GroupVersion + }{ + { + name: "both empty", + old: map[schema.GroupVersion]struct{}{}, + new: map[schema.GroupVersion]struct{}{}, + wantChanged: false, + wantDeleted: nil, + }, + { + name: "identical", + old: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + new: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + wantChanged: false, + wantDeleted: nil, + }, + { + name: "added GV", + old: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + new: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + {Group: "batch", Version: "v1"}: {}, + }, + wantChanged: true, + wantDeleted: nil, // No deletions, only addition + }, + { + name: "removed GV", + old: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + {Group: "batch", Version: "v1"}: {}, + }, + new: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + wantChanged: true, + wantDeleted: []schema.GroupVersion{ + {Group: "batch", Version: "v1"}, + }, + }, + { + name: "different GVs same size", + old: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + new: map[schema.GroupVersion]struct{}{ + {Group: "batch", Version: "v1"}: {}, + }, + wantChanged: true, + wantDeleted: []schema.GroupVersion{ + {Group: "apps", Version: "v1"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + deletedGVs, changed := diffGVs(tt.old, tt.new) + if changed != tt.wantChanged { + t.Errorf("diffGVs() changed = %v, want %v", changed, tt.wantChanged) + } + if len(deletedGVs) != len(tt.wantDeleted) { + t.Errorf("diffGVs() deleted count = %d, want %d", len(deletedGVs), len(tt.wantDeleted)) + } + for _, wantGV := range tt.wantDeleted { + if !slices.Contains(deletedGVs, wantGV) { + t.Errorf("diffGVs() deleted missing GV %v", wantGV) + } + } + }) + } +} + +func TestFilterPeerDiscoveryCache(t *testing.T) { + tests := []struct { + name string + activeGVs map[schema.GroupVersion]struct{} + deletedGVs map[schema.GroupVersion]time.Time + cacheMap map[string]PeerDiscoveryCacheEntry + wantPeerGVRs map[string]int // peer name -> GVR count + }{ + { + name: "empty exclusion no changes", + activeGVs: nil, + cacheMap: map[string]PeerDiscoveryCacheEntry{ + "peer1": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "apps", Version: "v1", Resource: "deployments"}: true, + }, + }, + "peer2": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "batch", Version: "v1", Resource: "jobs"}: true, + }, + }, + }, + wantPeerGVRs: map[string]int{ + "peer1": 1, + "peer2": 1, + }, + }, + { + name: "filter active GVs", + activeGVs: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + cacheMap: map[string]PeerDiscoveryCacheEntry{ + "peer1": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "apps", Version: "v1", Resource: "deployments"}: true, + }, + }, + "peer2": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "batch", Version: "v1", Resource: "jobs"}: true, + }, + }, + }, + wantPeerGVRs: map[string]int{ + "peer1": 0, // apps/v1 filtered out + "peer2": 1, // unchanged + }, + }, + { + // Recently deleted GVs are still filtered from peer discovery during the + // grace period (before the reaper cleans them up) to avoid routing requests + // to peers for GVs that were just deleted locally. + name: "filter deleted GVs", + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "custom", Version: "v1alpha1"}: time.Now(), + }, + cacheMap: map[string]PeerDiscoveryCacheEntry{ + "peer1": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "custom", Version: "v1alpha1", Resource: "myresources"}: true, + }, + }, + "peer2": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "apps", Version: "v1", Resource: "deployments"}: true, + }, + }, + }, + wantPeerGVRs: map[string]int{ + "peer1": 0, // custom/v1alpha1 filtered out + "peer2": 1, // unchanged + }, + }, + { + // Both active GVs and recently deleted GVs (within grace period, not yet + // cleaned up by the reaper) are filtered from peer discovery. + name: "filter both active and deleted GVs", + activeGVs: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + deletedGVs: map[schema.GroupVersion]time.Time{ + {Group: "custom", Version: "v1alpha1"}: time.Now(), + }, + cacheMap: map[string]PeerDiscoveryCacheEntry{ + "peer1": { + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "apps", Version: "v1", Resource: "deployments"}: true, + {Group: "custom", Version: "v1alpha1", Resource: "myresources"}: true, + {Group: "batch", Version: "v1", Resource: "jobs"}: true, + }, + }, + }, + wantPeerGVRs: map[string]int{ + "peer1": 1, // apps/v1 and custom/v1alpha1 filtered out, batch/v1 remains + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mgr := NewGVExclusionManager(5*time.Minute, &atomic.Value{}, &atomic.Pointer[func()]{}) + + if tt.activeGVs != nil { + mgr.currentlyActiveGVs.Store(tt.activeGVs) + } + if tt.deletedGVs != nil { + mgr.recentlyDeletedGVs.Store(tt.deletedGVs) + } + + filtered := mgr.filterPeerDiscoveryCache(tt.cacheMap) + + if len(filtered) != len(tt.cacheMap) { + t.Errorf("Want %d peers in filtered cache, got %d", len(tt.cacheMap), len(filtered)) + } + + for peerName, wantCount := range tt.wantPeerGVRs { + entry, found := filtered[peerName] + if !found { + t.Errorf("Peer %s not found in filtered cache", peerName) + continue + } + if len(entry.GVRs) != wantCount { + t.Errorf("Peer %s: want %d GVRs, got %d", peerName, wantCount, len(entry.GVRs)) + } + } + }) + } +} + +func TestFilterPeerCacheEntry(t *testing.T) { + tests := []struct { + name string + entry PeerDiscoveryCacheEntry + exclusionSet map[schema.GroupVersion]struct{} + wantGVRs []schema.GroupVersionResource + }{ + { + name: "empty exclusion set returns entry unchanged", + entry: PeerDiscoveryCacheEntry{ + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "apps", Version: "v1", Resource: "deployments"}: true, + {Group: "batch", Version: "v1", Resource: "jobs"}: true, + }, + GroupDiscovery: []apidiscoveryv2.APIGroupDiscovery{ + { + ObjectMeta: metav1.ObjectMeta{Name: "apps"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + }, + }, + }, + }, + exclusionSet: map[schema.GroupVersion]struct{}{}, + wantGVRs: []schema.GroupVersionResource{ + {Group: "apps", Version: "v1", Resource: "deployments"}, + {Group: "batch", Version: "v1", Resource: "jobs"}, + }, + }, + { + name: "filter GVRs and groups", + entry: PeerDiscoveryCacheEntry{ + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "apps", Version: "v1", Resource: "deployments"}: true, + {Group: "batch", Version: "v1", Resource: "jobs"}: true, + {Group: "custom", Version: "v1", Resource: "myresources"}: true, + }, + GroupDiscovery: []apidiscoveryv2.APIGroupDiscovery{ + { + ObjectMeta: metav1.ObjectMeta{Name: "apps"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "batch"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + }, + }, + }, + }, + exclusionSet: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + wantGVRs: []schema.GroupVersionResource{ + {Group: "batch", Version: "v1", Resource: "jobs"}, + {Group: "custom", Version: "v1", Resource: "myresources"}, + }, + }, + { + name: "no changes when exclusion doesn't match", + entry: PeerDiscoveryCacheEntry{ + GVRs: map[schema.GroupVersionResource]bool{ + {Group: "batch", Version: "v1", Resource: "jobs"}: true, + }, + }, + exclusionSet: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + wantGVRs: []schema.GroupVersionResource{ + {Group: "batch", Version: "v1", Resource: "jobs"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mgr := NewGVExclusionManager(5*time.Minute, &atomic.Value{}, &atomic.Pointer[func()]{}) + + filtered := mgr.filterPeerCacheEntry(tt.entry, tt.exclusionSet) + + for _, gvr := range tt.wantGVRs { + if _, found := filtered.GVRs[gvr]; !found { + t.Errorf("Want GVR %v to be present", gvr) + } + } + }) + } +} + +func TestFilterGroupDiscovery(t *testing.T) { + tests := []struct { + name string + groupDiscoveries []apidiscoveryv2.APIGroupDiscovery + exclusionSet map[schema.GroupVersion]struct{} + wantGroups map[string][]string // group name -> list of versions + }{ + { + name: "partial version exclusion", + groupDiscoveries: []apidiscoveryv2.APIGroupDiscovery{ + { + ObjectMeta: metav1.ObjectMeta{Name: "apps"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + {Version: "v1beta1"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "batch"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + }, + }, + }, + exclusionSet: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + wantGroups: map[string][]string{ + "apps": {"v1beta1"}, + "batch": {"v1"}, + }, + }, + { + name: "all versions excluded", + groupDiscoveries: []apidiscoveryv2.APIGroupDiscovery{ + { + ObjectMeta: metav1.ObjectMeta{Name: "apps"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "batch"}, + Versions: []apidiscoveryv2.APIVersionDiscovery{ + {Version: "v1"}, + }, + }, + }, + exclusionSet: map[schema.GroupVersion]struct{}{ + {Group: "apps", Version: "v1"}: {}, + }, + wantGroups: map[string][]string{ + "batch": {"v1"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mgr := NewGVExclusionManager(5*time.Minute, &atomic.Value{}, &atomic.Pointer[func()]{}) + + filtered := mgr.filterGroupDiscovery(tt.groupDiscoveries, tt.exclusionSet) + for groupName, wantVersionStrs := range tt.wantGroups { + var group *apidiscoveryv2.APIGroupDiscovery + for i := range filtered { + if filtered[i].Name == groupName { + group = &filtered[i] + break + } + } + + if group == nil { + t.Errorf("Want group %s not found in filtered results", groupName) + continue + } + + if len(group.Versions) != len(wantVersionStrs) { + t.Errorf("Group %s: want %d versions, got %d", groupName, len(wantVersionStrs), len(group.Versions)) + continue + } + + for _, wantVer := range wantVersionStrs { + found := false + for _, ver := range group.Versions { + if ver.Version == wantVer { + found = true + break + } + } + if !found { + t.Errorf("Group %s: want version %s not found", groupName, wantVer) + } + } + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/metrics/metrics.go index 9b7aee4eaa146..9c7c5e0410944 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/metrics/metrics.go @@ -27,6 +27,22 @@ import ( const ( subsystem = "apiserver" statuscode = "code" + group = "group" + version = "version" + resource = "resource" + errorType = "type" + + // ProxyErrorEndpointResolution indicates a failure to resolve the network address of a peer apiserver. + ProxyErrorEndpointResolution = "endpoint_resolution" + // ProxyErrorTransport indicates a failure to build the proxy transport for the request. + ProxyErrorTransport = "proxy_transport" + + // DiscoveryErrorLeaseList indicates a failure to list apiserver identity leases. + DiscoveryErrorLeaseList = "lease_list" + // DiscoveryErrorHostPortResolution indicates a failure to resolve host/port from an identity lease. + DiscoveryErrorHostPortResolution = "hostport_resolution" + // DiscoveryErrorFetch indicates a failure to fetch discovery document from a peer. + DiscoveryErrorFetch = "fetch_discovery" ) var registerMetricsOnce sync.Once @@ -37,16 +53,40 @@ var ( &metrics.CounterOpts{ Subsystem: subsystem, Name: "rerouted_request_total", - Help: "Total number of requests that were proxied to a peer kube apiserver because the local apiserver was not capable of serving it", + Help: `Total number of requests that were proxied to a peer kube-apiserver because the local apiserver was not capable of serving it, broken down by 'group', 'version', and 'resource' indicating the GVR of the request. If all three are empty (""), the request is a discovery request.`, + StabilityLevel: metrics.ALPHA, + }, + []string{statuscode, group, version, resource}, + ) + + // peerProxyErrorsTotal counts the number of errors encountered while proxying requests to a peer kube-apiserver. + peerProxyErrorsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: subsystem, + Name: "peer_proxy_errors_total", + Help: "Total number of errors encountered while proxying requests to a peer kube apiserver", + StabilityLevel: metrics.ALPHA, + }, + []string{errorType, group, version, resource}, + ) + + // peerDiscoverySyncErrorsTotal counts the number of errors encountered while syncing discovery information from a peer kube-apiserver. + peerDiscoverySyncErrorsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: subsystem, + Name: "peer_discovery_sync_errors_total", + Help: "Total number of errors encountered while syncing discovery information from a peer kube-apiserver", StabilityLevel: metrics.ALPHA, }, - []string{statuscode}, + []string{errorType}, ) ) func Register() { registerMetricsOnce.Do(func() { legacyregistry.MustRegister(peerProxiedRequestsTotal) + legacyregistry.MustRegister(peerProxyErrorsTotal) + legacyregistry.MustRegister(peerDiscoverySyncErrorsTotal) }) } @@ -56,6 +96,16 @@ func Reset() { } // IncPeerProxiedRequest increments the # of proxied requests to peer kube-apiserver -func IncPeerProxiedRequest(ctx context.Context, status string) { - peerProxiedRequestsTotal.WithContext(ctx).WithLabelValues(status).Add(1) +func IncPeerProxiedRequest(ctx context.Context, status, g, v, r string) { + peerProxiedRequestsTotal.WithContext(ctx).WithLabelValues(status, g, v, r).Add(1) +} + +// IncPeerProxyError increments the # of errors encountered during peer proxying +func IncPeerProxyError(ctx context.Context, e, g, v, r string) { + peerProxyErrorsTotal.WithContext(ctx).WithLabelValues(e, g, v, r).Add(1) +} + +// IncPeerDiscoverySyncError increments the # of errors encountered during peer discovery sync +func IncPeerDiscoverySyncError(ctx context.Context, e string) { + peerDiscoverySyncErrorsTotal.WithContext(ctx).WithLabelValues(e).Add(1) } diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery.go index ae55f90a147ed..693d5faca2307 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery.go +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/authentication/user" + peerproxymetrics "k8s.io/apiserver/pkg/util/peerproxy/metrics" "k8s.io/client-go/discovery" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" @@ -51,7 +52,7 @@ const ( ) func (h *peerProxyHandler) RunPeerDiscoveryCacheSync(ctx context.Context, workers int) { - defer utilruntime.HandleCrash() + defer utilruntime.HandleCrashWithContext(ctx) defer h.peerLeaseQueue.ShutDown() defer func() { err := h.apiserverIdentityInformer.Informer().RemoveEventHandler(h.leaseRegistration) @@ -94,6 +95,7 @@ func (h *peerProxyHandler) syncPeerDiscoveryCache(ctx context.Context) error { // Rebuild the peer discovery cache from available leases. leases, err := h.apiserverIdentityInformer.Lister().List(h.identityLeaseLabelSelector) if err != nil { + peerproxymetrics.IncPeerDiscoverySyncError(ctx, peerproxymetrics.DiscoveryErrorLeaseList) utilruntime.HandleError(err) return err } @@ -115,28 +117,17 @@ func (h *peerProxyHandler) syncPeerDiscoveryCache(ctx context.Context) error { } } - // Apply exclusion filter to the cache. - if len(newCache) != 0 { - if filteredCache, peerDiscoveryChanged := h.filterPeerDiscoveryCache(newCache); peerDiscoveryChanged { - newCache = filteredCache - } - } - - h.storePeerDiscoveryCacheAndInvalidate(newCache) + // Store unfiltered data to raw cache and trigger refilter. + // The refilter worker (single writer to filtered cache) will apply exclusions. + h.rawPeerDiscoveryCache.Store(newCache) + h.gvExclusionManager.TriggerRefilter() return fetchDiscoveryErr } -// storePeerDiscoveryCacheAndInvalidate stores the new peer discovery cache and always calls the invalidation callback if set. -func (h *peerProxyHandler) storePeerDiscoveryCacheAndInvalidate(newCache map[string]PeerDiscoveryCacheEntry) { - h.peerDiscoveryInfoCache.Store(newCache) - if callback := h.cacheInvalidationCallback.Load(); callback != nil { - (*callback)() - } -} - func (h *peerProxyHandler) fetchNewDiscoveryFor(ctx context.Context, serverID string) (PeerDiscoveryCacheEntry, error) { hostport, err := h.hostportInfo(serverID) if err != nil { + peerproxymetrics.IncPeerDiscoverySyncError(ctx, peerproxymetrics.DiscoveryErrorHostPortResolution) return PeerDiscoveryCacheEntry{}, fmt.Errorf("failed to get host port info from identity lease for server %s: %w", serverID, err) } @@ -154,6 +145,7 @@ func (h *peerProxyHandler) fetchNewDiscoveryFor(ctx context.Context, serverID st for _, path := range discoveryPaths { discoveryResponse, discoveryErr = h.aggregateDiscovery(ctx, path, hostport) if discoveryErr != nil { + peerproxymetrics.IncPeerDiscoverySyncError(ctx, peerproxymetrics.DiscoveryErrorFetch) klog.ErrorS(discoveryErr, "error querying discovery endpoint for serverID", "path", path, "serverID", serverID) continue } @@ -208,7 +200,7 @@ func (h *peerProxyHandler) aggregateDiscovery(ctx context.Context, path string, req.Header.Add("Accept", discovery.AcceptV2NoPeer+","+discovery.AcceptV2+","+discovery.AcceptV1) writer := responsewriterutil.NewInMemoryResponseWriter() - h.proxyRequestToDestinationAPIServer(req, writer, hostport) + h.proxyRequestToDestinationAPIServer(req, writer, hostport, schema.GroupVersionResource{}) if writer.RespCode() != http.StatusOK { return nil, fmt.Errorf("discovery request failed with status: %d", writer.RespCode()) } @@ -287,14 +279,8 @@ func (h *peerProxyHandler) isValidPeerIdentityLease(obj interface{}) (*v1.Lease, func (h *peerProxyHandler) findServiceableByPeerFromPeerDiscoveryCache(gvr schema.GroupVersionResource) []string { var serviceableByIDs []string - cache := h.peerDiscoveryInfoCache.Load() - if cache == nil { - return serviceableByIDs - } - - cacheMap, ok := cache.(map[string]PeerDiscoveryCacheEntry) - if !ok { - klog.Warning("Invalid cache type in peerDiscoveryInfoCache") + cacheMap := h.gvExclusionManager.GetFilteredPeerDiscoveryCache() + if len(cacheMap) == 0 { return serviceableByIDs } diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery_test.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery_test.go index e15e4c314b992..d1cfc7b016d46 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peer_discovery_test.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -36,16 +37,17 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" "k8s.io/client-go/transport" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" apidiscoveryv2 "k8s.io/api/apidiscovery/v2" v1 "k8s.io/api/coordination/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + peerproxymetrics "k8s.io/apiserver/pkg/util/peerproxy/metrics" ) func TestRunPeerDiscoveryCacheSync(t *testing.T) { - localServerID := "local-server" - testCases := []struct { desc string leases []*v1.Lease @@ -136,31 +138,7 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { - fakeClient := fake.NewSimpleClientset() - fakeInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0) - leaseInformer := fakeInformerFactory.Coordination().V1().Leases() - - fakeReconciler := newFakeReconciler() - - negotiatedSerializer := serializer.NewCodecFactory(runtime.NewScheme()) - loopbackConfig := &rest.Config{} - proxyConfig := &transport.Config{ - TLS: transport.TLSConfig{Insecure: true}, - } - - h, err := NewPeerProxyHandler( - localServerID, - tt.labelSelectorString, - leaseInformer, - fakeReconciler, - negotiatedSerializer, - loopbackConfig, - proxyConfig, - ) - if err != nil { - t.Fatalf("failed to create handler: %v", err) - } - + h, fakeReconciler, leaseInformer, fakeClient, fakeInformerFactory := setupPeerProxyHandler(t, tt.labelSelectorString) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -170,13 +148,13 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { if err != nil { t.Fatalf("failed to create lease: %v", err) } - if err = leaseInformer.Informer().GetIndexer().Add(lease); err != nil { + if err = leaseInformer.GetIndexer().Add(lease); err != nil { t.Fatalf("failed to create lease: %v", err) } } go fakeInformerFactory.Start(ctx.Done()) - cache.WaitForCacheSync(ctx.Done(), leaseInformer.Informer().HasSynced) + cache.WaitForCacheSync(ctx.Done(), leaseInformer.HasSynced) // Create test servers based on leases testServers := make(map[string]*httptest.Server) @@ -192,19 +170,20 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { } go h.RunPeerDiscoveryCacheSync(ctx, 1) + go h.RunPeerDiscoveryRefilter(ctx) // Wait for initial cache update. initialCache := map[string]PeerDiscoveryCacheEntry{} for _, lease := range tt.leases { initialCache[lease.Name] = makePeerDiscoveryCacheEntry("testgroup", "v1", "testresources") } - err = wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 5*time.Second, false, func(ctx context.Context) (bool, error) { + err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 5*time.Second, false, func(ctx context.Context) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } - gotCache := h.peerDiscoveryInfoCache.Load() + gotCache := h.gvExclusionManager.GetFilteredPeerDiscoveryCache() return assert.ObjectsAreEqual(initialCache, gotCache), nil }) if err != nil { @@ -218,7 +197,7 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { if err != nil { t.Fatalf("failed to update lease: %v", err) } - if err = leaseInformer.Informer().GetIndexer().Update(updatedLease); err != nil { + if err = leaseInformer.GetIndexer().Update(updatedLease); err != nil { t.Fatalf("failed to update lease: %v", err) } } @@ -226,7 +205,7 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { // Delete leases if indicated. if len(tt.deletedLeaseNames) > 0 { for _, leaseName := range tt.deletedLeaseNames { - lease, exists, err := leaseInformer.Informer().GetIndexer().GetByKey("default/" + leaseName) + lease, exists, err := leaseInformer.GetIndexer().GetByKey("default/" + leaseName) if err != nil { t.Fatalf("failed to get lease from indexer: %v", err) } @@ -238,7 +217,7 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { if err != nil { t.Fatalf("failed to delete lease: %v", err) } - if err = leaseInformer.Informer().GetIndexer().Delete(deletedLease); err != nil { + if err = leaseInformer.GetIndexer().Delete(deletedLease); err != nil { t.Fatalf("failed to delete lease: %v", err) } @@ -251,7 +230,7 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { return false, ctx.Err() default: } - gotCache := h.peerDiscoveryInfoCache.Load() + gotCache := h.gvExclusionManager.GetFilteredPeerDiscoveryCache() r := assert.ObjectsAreEqual(tt.wantCache, gotCache) return r, nil }) @@ -263,6 +242,118 @@ func TestRunPeerDiscoveryCacheSync(t *testing.T) { } } +func TestPeerDiscoveryMetrics(t *testing.T) { + testCases := []struct { + desc string + leases []*v1.Lease + peerServerConfig map[string]http.HandlerFunc + wantMetrics string + }{ + { + desc: "hostport resolution error", + leases: []*v1.Lease{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "remote-resolution-error", + Labels: map[string]string{"apiserver-identity": "testserver"}, + }, + Spec: v1.LeaseSpec{HolderIdentity: proto.String("holder-error")}, + }, + }, + // No peer server configured means no endpoint will be registered in the reconciler. + // This should cause GetEndpoint to fail, triggering the "hostport_resolution" error. + peerServerConfig: nil, + wantMetrics: ` + # HELP apiserver_peer_discovery_sync_errors_total [ALPHA] Total number of errors encountered while syncing discovery information from a peer kube-apiserver + # TYPE apiserver_peer_discovery_sync_errors_total counter + apiserver_peer_discovery_sync_errors_total{type="hostport_resolution"} 1 + `, + }, + { + desc: "fetch discovery error", + leases: []*v1.Lease{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "remote-fetch-error", + Labels: map[string]string{"apiserver-identity": "testserver"}, + }, + Spec: v1.LeaseSpec{HolderIdentity: proto.String("holder-fetch-error")}, + }, + }, + peerServerConfig: map[string]http.HandlerFunc{ + "remote-fetch-error": func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }, + }, + wantMetrics: ` + # HELP apiserver_peer_discovery_sync_errors_total [ALPHA] Total number of errors encountered while syncing discovery information from a peer kube-apiserver + # TYPE apiserver_peer_discovery_sync_errors_total counter + apiserver_peer_discovery_sync_errors_total{type="fetch_discovery"} 2 + `, + }, + } + + peerproxymetrics.Register() + for _, tt := range testCases { + t.Run(tt.desc, func(t *testing.T) { + defer peerproxymetrics.Reset() + h, fakeReconciler, leaseInformer, _, _ := setupPeerProxyHandler(t, "apiserver-identity=testserver") + + for _, lease := range tt.leases { + if err := leaseInformer.GetIndexer().Add(lease); err != nil { + t.Fatalf("failed to create lease: %v", err) + } + } + + for leaseName, handler := range tt.peerServerConfig { + if handler == nil { + handler = func(w http.ResponseWriter, r *http.Request) {} + } + ts := httptest.NewServer(handler) + defer ts.Close() + fakeReconciler.setEndpoint(leaseName, ts.URL[7:]) + } + + // Directly call syncPeerDiscoveryCache + // We don't care about the return error of syncPeerDiscoveryCache for this test, + // we only care that metrics are incremented. + _ = h.syncPeerDiscoveryCache(context.Background()) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tt.wantMetrics), "apiserver_peer_discovery_sync_errors_total"); err != nil { + t.Error(err) + } + }) + } +} + +func setupPeerProxyHandler(t *testing.T, labelSelector string) (*peerProxyHandler, *fakeReconciler, cache.SharedIndexInformer, *fake.Clientset, informers.SharedInformerFactory) { + localServerID := "local-server" + fakeClient := fake.NewSimpleClientset() + fakeInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + leaseInformer := fakeInformerFactory.Coordination().V1().Leases() + + fakeReconciler := newFakeReconciler() + negotiatedSerializer := serializer.NewCodecFactory(runtime.NewScheme()) + loopbackConfig := &rest.Config{} + proxyConfig := &transport.Config{ + TLS: transport.TLSConfig{Insecure: true}, + } + + h, err := NewPeerProxyHandler( + localServerID, + labelSelector, + leaseInformer, + fakeReconciler, + negotiatedSerializer, + loopbackConfig, + proxyConfig, + ) + if err != nil { + t.Fatalf("failed to create handler: %v", err) + } + + return h, fakeReconciler, leaseInformer.Informer(), fakeClient, fakeInformerFactory +} + // newTestTLSServer creates a new httptest.NewTLSServer that serves discovery endpoints. func newTestTLSServer(t *testing.T) *httptest.Server { return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy.go index e89eea6ad3565..9644be4cfd0ae 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy.go +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy.go @@ -49,15 +49,12 @@ const ( // available on this server. localDiscoveryRefreshInterval = 30 * time.Minute // defaultExclusionGracePeriod is the default duration to wait before - // removing a group from the exclusion set after it is deleted from + // removing a groupversion from the exclusion set after it is deleted from // CRDs and aggregated APIs. // This is to allow time for all peer API servers to also observe // the deleted CRD or aggregated API before this server stops excluding it // in peer-aggregated discovery and while proxying requests to peers. defaultExclusionGracePeriod = 5 * time.Minute - // defaultExclusionReaperInterval is the interval at which the we - // clean up deleted groups from the exclusion list. - defaultExclusionReaperInterval = 1 * time.Minute ) // Interface defines how the Mixed Version Proxy filter interacts with the underlying system. @@ -67,12 +64,49 @@ type Interface interface { HasFinishedSync() bool RunLocalDiscoveryCacheSync(stopCh <-chan struct{}) error RunPeerDiscoveryCacheSync(ctx context.Context, workers int) - RunExcludedGVsReaper(stopCh <-chan struct{}) - RunGVDeletionWorkers(ctx context.Context, workers int) GetPeerResources() map[string][]apidiscoveryv2.APIGroupDiscovery RegisterCacheInvalidationCallback(cb func()) + + // RegisterCRDInformerHandlers registers event handlers on the CRD informer to track + // which GroupVersions are served locally by CRDs. When a CRD is created or updated, + // its GV is added to the exclusion set. When deleted, the GV is marked for exclusion + // during a grace period to allow peers to observe the deletion. The extractor function + // extracts the GroupVersion from a CRD object. + // + // This exclusion is necessary because peer discovery is not refreshed when a local + // CRD is deleted. Without exclusion, the deleted GV might still appear in cached peer + // discovery data, causing requests to be incorrectly routed to a peer for a GV that + // no longer exists locally. Therefore, we intentionally exclude CRD GVs from peer + // discovery from the start and only rely on the local apiserver's view of the CRD + // to serve it in peer-aggregated discovery. RegisterCRDInformerHandlers(crdInformer cache.SharedIndexInformer, extractor GVExtractor) error + + // RegisterAPIServiceInformerHandlers registers event handlers on the APIService informer + // to track which GroupVersions are served locally by aggregated APIServices. When an + // APIService is created or updated, its GV is added to the exclusion set. When deleted, + // the GV is marked for exclusion during a grace period. + // + // This exclusion is necessary because peer discovery is not refreshed when a local + // aggregated APIService is deleted. Without exclusion, the deleted GV might still appear + // in cached peer discovery data, causing requests to be incorrectly routed to a peer. + // Therefore, we intentionally exclude aggregated APIService GVs from peer discovery + // from the start and only rely on the local apiserver's view to serve them in + // peer-aggregated discovery. RegisterAPIServiceInformerHandlers(apiServiceInformer cache.SharedIndexInformer, extractor GVExtractor) error + + // RunPeerDiscoveryActiveGVTracker starts a worker that processes CRD/APIService informer + // events to rebuild the set of actively served GroupVersions. This worker is triggered + // whenever a CRD or APIService is added or updated and updates the exclusion + // set accordingly. When a GV is deleted, a delayed sync is automatically scheduled + // after the grace period to reap expired GVs from the exclusion set. + RunPeerDiscoveryActiveGVTracker(ctx context.Context) + + // RunPeerDiscoveryRefilter starts a worker that re-applies exclusion filtering to the + // cached peer discovery data whenever the exclusion set changes. This ensures that + // already-cached peer discovery responses are immediately updated to exclude newly added + // or updated local GVs, rather than waiting for the next peer lease event to trigger a + // cache refresh of peer discovery data. + RunPeerDiscoveryRefilter(ctx context.Context) } // New creates a new instance to implement unknown version proxy @@ -95,23 +129,21 @@ func NewPeerProxyHandler( localDiscoveryInfoCache: atomic.Value{}, localDiscoveryCacheTicker: time.NewTicker(localDiscoveryRefreshInterval), localDiscoveryInfoCachePopulated: make(chan struct{}), - peerDiscoveryInfoCache: atomic.Value{}, + rawPeerDiscoveryCache: atomic.Value{}, peerLeaseQueue: workqueue.NewTypedRateLimitingQueueWithConfig( workqueue.DefaultTypedControllerRateLimiter[string](), workqueue.TypedRateLimitingQueueConfig[string]{ Name: peerDiscoveryControllerName, }), apiserverIdentityInformer: leaseInformer, - excludedGVs: make(map[schema.GroupVersion]*time.Time), - exclusionGracePeriod: defaultExclusionGracePeriod, - reaperCheckInterval: defaultExclusionReaperInterval, - gvDeletionQueue: workqueue.NewTypedRateLimitingQueueWithConfig( - workqueue.DefaultTypedControllerRateLimiter[string](), - workqueue.TypedRateLimitingQueueConfig[string]{ - Name: "gv-deletion", - }), } + h.gvExclusionManager = NewGVExclusionManager( + defaultExclusionGracePeriod, + &h.rawPeerDiscoveryCache, + &h.cacheInvalidationCallback, + ) + if parts := strings.Split(identityLeaseLabelSelector, "="); len(parts) != 2 { return nil, fmt.Errorf("invalid identityLeaseLabelSelector provided, must be of the form key=value, received: %v", identityLeaseLabelSelector) } @@ -134,7 +166,7 @@ func NewPeerProxyHandler( discoveryClient.NoPeerDiscovery = true h.discoveryClient = discoveryClient h.localDiscoveryInfoCache.Store(map[schema.GroupVersionResource]bool{}) - h.peerDiscoveryInfoCache.Store(map[string]PeerDiscoveryCacheEntry{}) + h.rawPeerDiscoveryCache.Store(map[string]PeerDiscoveryCacheEntry{}) proxyTransport, err := transport.New(proxyClientConfig) if err != nil { @@ -169,3 +201,33 @@ func NewPeerProxyHandler( h.leaseRegistration = peerDiscoveryRegistration return h, nil } + +// RegisterCRDInformerHandlers registers event handlers for CRD informer. +func (h *peerProxyHandler) RegisterCRDInformerHandlers(crdInformer cache.SharedIndexInformer, extractor GVExtractor) error { + if h.gvExclusionManager != nil { + return h.gvExclusionManager.RegisterCRDInformerHandlers(crdInformer, extractor) + } + return nil +} + +// RegisterAPIServiceInformerHandlers registers event handlers for APIService informer. +func (h *peerProxyHandler) RegisterAPIServiceInformerHandlers(apiServiceInformer cache.SharedIndexInformer, extractor GVExtractor) error { + if h.gvExclusionManager != nil { + return h.gvExclusionManager.RegisterAPIServiceInformerHandlers(apiServiceInformer, extractor) + } + return nil +} + +// RunPeerDiscoveryActiveGVTracker starts the worker that tracks active GVs from CRDs/APIServices. +func (h *peerProxyHandler) RunPeerDiscoveryActiveGVTracker(ctx context.Context) { + if h.gvExclusionManager != nil { + h.gvExclusionManager.RunPeerDiscoveryActiveGVTracker(ctx) + } +} + +// RunPeerDiscoveryRefilter starts the worker that refilters peer discovery cache. +func (h *peerProxyHandler) RunPeerDiscoveryRefilter(ctx context.Context) { + if h.gvExclusionManager != nil { + h.gvExclusionManager.RunPeerDiscoveryRefilter(ctx) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler.go index 7d12ee8488951..97c2a2e289669 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler.go +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler.go @@ -79,36 +79,17 @@ type peerProxyHandler struct { localDiscoveryCacheTicker *time.Ticker localDiscoveryInfoCachePopulated chan struct{} localDiscoveryInfoCachePopulatedOnce sync.Once - // Cache that stores resources and groups served by peer apiservers. - // The map is from string to PeerDiscoveryCacheEntry where the string - // is the serverID of the peer apiserver. - // Refreshed if a new apiserver identity lease is added, deleted or - // holderIndentity change is observed in the lease. - peerDiscoveryInfoCache atomic.Value // map[string]struct{GVRs map[schema.GroupVersionResource]bool; Groups []apidiscoveryv2.APIGroupDiscovery} - proxyTransport http.RoundTripper - // Worker queue that keeps the peerDiscoveryInfoCache up-to-date. + // rawPeerDiscoveryCache stores unfiltered resources and groups served by peer apiservers. + // The map is from string (serverID) to PeerDiscoveryCacheEntry. + // Written ONLY by peerLeaseQueue worker when peer leases change. + rawPeerDiscoveryCache atomic.Value // map[string]PeerDiscoveryCacheEntry + proxyTransport http.RoundTripper + // Worker queue that keeps the rawPeerDiscoveryCache up-to-date. peerLeaseQueue workqueue.TypedRateLimitingInterface[string] serializer runtime.NegotiatedSerializer cacheInvalidationCallback atomic.Pointer[func()] - // Exclusion set for groups that should not be included in peer proxying - // or peer-aggregated discovery (e.g., CRDs/APIServices) - // - // This map has three states for a group: - // - Not in map: Group is not excluded. - // - In map, value is nil: Group is actively excluded. - // - In map, value is non-nil: Group is pending deletion (grace period). - excludedGVs map[schema.GroupVersion]*time.Time - excludedGVsMu sync.RWMutex - crdInformer cache.SharedIndexInformer - crdExtractor GVExtractor - apiServiceInformer cache.SharedIndexInformer - apiServiceExtractor GVExtractor - - exclusionGracePeriod time.Duration - reaperCheckInterval time.Duration - - // Worker queue for processing GV deletions asynchronously - gvDeletionQueue workqueue.TypedRateLimitingInterface[string] + // Manager for GV exclusions (CRDs/APIServices) + gvExclusionManager *GVExclusionManager } // PeerDiscoveryCacheEntry holds the GVRs and group-level discovery info for a peer. @@ -141,12 +122,10 @@ func (h *peerProxyHandler) WaitForCacheSync(stopCh <-chan struct{}) error { return fmt.Errorf("error while waiting for peer-identity-lease event handler registration sync") } - if h.crdInformer != nil && !cache.WaitForNamedCacheSync("peer-discovery-crd-informer", stopCh, h.crdInformer.HasSynced) { - return fmt.Errorf("error while waiting for crd informer sync") - } - - if h.apiServiceInformer != nil && !cache.WaitForNamedCacheSync("peer-discovery-api-service-informer", stopCh, h.apiServiceInformer.HasSynced) { - return fmt.Errorf("error while waiting for apiservice informer sync") + if h.gvExclusionManager != nil { + if !h.gvExclusionManager.WaitForCacheSync(stopCh) { + return fmt.Errorf("error while waiting for gv exclusion manager cache sync") + } } // Wait for localDiscoveryInfoCache to be populated. @@ -203,22 +182,23 @@ func (h *peerProxyHandler) WrapHandler(handler http.Handler) http.Handler { // find servers that are capable of serving this request peerServerIDs := h.findServiceableByPeerFromPeerDiscoveryCache(gvr) if len(peerServerIDs) == 0 { - klog.Errorf("gvr %v is not served by anything in this cluster", gvr) + klog.V(3).Infof("gvr %v is not served by anything in this cluster", gvr) handler.ServeHTTP(w, r) return } peerEndpoints, err := h.resolveServingLocation(peerServerIDs) if err != nil { + metrics.IncPeerProxyError(ctx, metrics.ProxyErrorEndpointResolution, gvr.Group, gvr.Version, gvr.Resource) gv := schema.GroupVersion{Group: gvr.Group, Version: gvr.Version} klog.ErrorS(err, "error finding serviceable-by apiservers for the requested resource", "gvr", gvr) responsewriters.ErrorNegotiated(apierrors.NewServiceUnavailable("Error getting ip and port info of the remote server while proxying"), h.serializer, gv, w, r) return } - rand := rand.Intn(len(peerEndpoints)) - peerEndpoint := peerEndpoints[rand] - h.proxyRequestToDestinationAPIServer(r, w, peerEndpoint) + endpointIndex := rand.Intn(len(peerEndpoints)) + peerEndpoint := peerEndpoints[endpointIndex] + h.proxyRequestToDestinationAPIServer(r, w, peerEndpoint, gvr) }) } @@ -259,7 +239,7 @@ func (h *peerProxyHandler) hostportInfo(apiserverKey string) (string, error) { return hostPort, nil } -func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request, rw http.ResponseWriter, host string) { +func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request, rw http.ResponseWriter, host string, gvr schema.GroupVersionResource) { // write a new location based on the existing request pointed at the target service location := &url.URL{} location.Scheme = "https" @@ -273,6 +253,7 @@ func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request, proxyRoundTripper, err := h.buildProxyRoundtripper(req) if err != nil { + metrics.IncPeerProxyError(req.Context(), metrics.ProxyErrorTransport, gvr.Group, gvr.Version, gvr.Resource) klog.Errorf("failed to build proxy round tripper: %v", err) return } @@ -282,7 +263,7 @@ func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request, handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, false, &responder{w: w, ctx: req.Context()}) klog.Infof("Proxying request for %s from %s to %s", req.URL.Path, req.Host, location.Host) handler.ServeHTTP(w, newReq) - metrics.IncPeerProxiedRequest(req.Context(), strconv.Itoa(delegate.Status())) + metrics.IncPeerProxiedRequest(req.Context(), strconv.Itoa(delegate.Status()), gvr.Group, gvr.Version, gvr.Resource) } func (h *peerProxyHandler) buildProxyRoundtripper(req *http.Request) (http.RoundTripper, error) { @@ -303,16 +284,9 @@ func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) { // Returns a map of serverID -> []apidiscoveryv2.APIGroupDiscovery served by peer servers func (h *peerProxyHandler) GetPeerResources() map[string][]apidiscoveryv2.APIGroupDiscovery { result := make(map[string][]apidiscoveryv2.APIGroupDiscovery) - - peerCache := h.peerDiscoveryInfoCache.Load() - if peerCache == nil { - klog.V(4).Infof("GetPeerResources: peer cache is nil") - return result - } - - cacheMap, ok := peerCache.(map[string]PeerDiscoveryCacheEntry) - if !ok { - klog.Warning("Invalid cache type in peerDiscoveryGVRCache") + cacheMap := h.gvExclusionManager.GetFilteredPeerDiscoveryCache() + if len(cacheMap) == 0 { + klog.V(4).Infof("GetPeerResources: peer cache is empty") return result } diff --git a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler_test.go b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler_test.go index 9598370114221..01f90f07ffbf2 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/peerproxy/peerproxy_handler_test.go @@ -125,6 +125,11 @@ func TestPeerProxy(t *testing.T) { }, }, wantStatus: http.StatusServiceUnavailable, + wantMetricsData: ` + # HELP apiserver_peer_proxy_errors_total [ALPHA] Total number of errors encountered while proxying requests to a peer kube apiserver + # TYPE apiserver_peer_proxy_errors_total counter + apiserver_peer_proxy_errors_total{group="",resource="bar",type="endpoint_resolution",version="foo"} 1 + `, }, { desc: "503 unreachable peer bind address", @@ -148,10 +153,10 @@ func TestPeerProxy(t *testing.T) { }, wantStatus: http.StatusServiceUnavailable, wantMetricsData: ` - # HELP apiserver_rerouted_request_total [ALPHA] Total number of requests that were proxied to a peer kube apiserver because the local apiserver was not capable of serving it - # TYPE apiserver_rerouted_request_total counter - apiserver_rerouted_request_total{code="503"} 1 - `, + # HELP apiserver_rerouted_request_total [ALPHA] Total number of requests that were proxied to a peer kube-apiserver because the local apiserver was not capable of serving it, broken down by 'group', 'version', and 'resource' indicating the GVR of the request. If all three are empty (""), the request is a discovery request. + # TYPE apiserver_rerouted_request_total counter + apiserver_rerouted_request_total{code="503",group="",resource="bar",version="foo"} 1 + `, }, { desc: "503 if one apiserver's endpoint lease wasnt found but another valid (unreachable) apiserver was found", @@ -180,10 +185,10 @@ func TestPeerProxy(t *testing.T) { }, wantStatus: http.StatusServiceUnavailable, wantMetricsData: ` - # HELP apiserver_rerouted_request_total [ALPHA] Total number of requests that were proxied to a peer kube apiserver because the local apiserver was not capable of serving it - # TYPE apiserver_rerouted_request_total counter - apiserver_rerouted_request_total{code="503"} 1 - `, + # HELP apiserver_rerouted_request_total [ALPHA] Total number of requests that were proxied to a peer kube-apiserver because the local apiserver was not capable of serving it, broken down by 'group', 'version', and 'resource' indicating the GVR of the request. If all three are empty (""), the request is a discovery request. + # TYPE apiserver_rerouted_request_total counter + apiserver_rerouted_request_total{code="503",group="",resource="bar",version="foo"} 1 + `, }, } @@ -230,7 +235,7 @@ func TestPeerProxy(t *testing.T) { // compare metric if tt.wantMetricsData != "" { - if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tt.wantMetricsData), []string{"apiserver_rerouted_request_total"}...); err != nil { + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tt.wantMetricsData), []string{"apiserver_rerouted_request_total", "apiserver_peer_proxy_errors_total"}...); err != nil { t.Fatal(err) } } @@ -387,7 +392,7 @@ func newFakePeerProxyHandler(informerFinishedSync bool, return nil, err } ppH.localDiscoveryInfoCache.Store(localCache) - ppH.peerDiscoveryInfoCache.Store(peerCache) + ppH.gvExclusionManager.filteredPeerDiscoveryCache.Store(peerCache) ppH.finishedSync.Store(informerFinishedSync) return ppH, nil diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/metrics/metrics.go index e7e83c1718051..f09724716dd43 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/metrics/metrics.go @@ -54,18 +54,33 @@ var ( }, []string{statuscode}, ) + // websocketStreamingRequestsTotal counts WebSocket streaming requests (exec/attach/portforward) + // routed by the API server, labeled by subresource and proxy_type. + websocketStreamingRequestsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: subsystem, + Name: "websocket_streaming_requests_total", + Help: "Total number of WebSocket streaming requests (exec/attach/portforward) routed by the API server, " + + "labeled by subresource and proxy_type. proxy_type is proxied_to_kubelet when the kubelet " + + "handles the request directly; otherwise translated_at_apiserver.", + StabilityLevel: metrics.ALPHA, + }, + []string{"subresource", "proxy_type"}, + ) ) func Register() { registerMetricsOnce.Do(func() { legacyregistry.MustRegister(streamTranslatorRequestsTotal) legacyregistry.MustRegister(streamTunnelRequestsTotal) + legacyregistry.MustRegister(websocketStreamingRequestsTotal) }) } func ResetForTest() { streamTranslatorRequestsTotal.Reset() streamTunnelRequestsTotal.Reset() + websocketStreamingRequestsTotal.Reset() } // IncStreamTranslatorRequest increments the # of requests handled by the StreamTranslatorProxy. @@ -77,3 +92,10 @@ func IncStreamTranslatorRequest(ctx context.Context, status string) { func IncStreamTunnelRequest(ctx context.Context, status string) { streamTunnelRequestsTotal.WithContext(ctx).WithLabelValues(status).Add(1) } + +// IncWebSocketStreamingRequest increments the count of WebSocket streaming requests +// routed by the API server with the given subresource (exec/attach/portforward) and +// proxy_type (proxied_to_kubelet or translated_at_apiserver). +func IncWebSocketStreamingRequest(ctx context.Context, subresource, proxyType string) { + websocketStreamingRequestsTotal.WithContext(ctx).WithLabelValues(subresource, proxyType).Add(1) +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go index 33bbdc325260c..a85a5c5d0effe 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go @@ -26,11 +26,12 @@ import ( "github.com/mxk/go-flowrate/flowrate" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" constants "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apiserver/pkg/util/proxy/metrics" "k8s.io/client-go/tools/remotecommand" + clientspdy "k8s.io/client-go/transport/spdy" "k8s.io/client-go/util/exec" + "k8s.io/streaming/pkg/httpstream/spdy" ) // StreamTranslatorHandler is a handler which translates WebSocket stream data @@ -77,7 +78,12 @@ func (h *StreamTranslatorHandler) ServeHTTP(w http.ResponseWriter, req *http.Req websocketStreams.writeStatus(apierrors.NewInternalError(err)) //nolint:errcheck return } - spdyExecutor, err := remotecommand.NewSPDYExecutorRejectRedirects(spdyRoundTripper, spdyRoundTripper, "POST", h.Location) + spdyExecutor, err := remotecommand.NewSPDYExecutorRejectRedirects( + spdyRoundTripper, + clientspdy.NewUpgraderForStreaming(spdyRoundTripper), + "POST", + h.Location, + ) if err != nil { metrics.IncStreamTranslatorRequest(req.Context(), strconv.Itoa(http.StatusInternalServerError)) websocketStreams.writeStatus(apierrors.NewInternalError(err)) //nolint:errcheck diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go index c34522022366e..ffbd0f6359e64 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go @@ -28,6 +28,7 @@ import ( mrand "math/rand" "net/http" "net/http/httptest" + "net/http/httputil" "net/url" "reflect" "strings" @@ -37,8 +38,6 @@ import ( v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" rcconstants "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/util/proxy/metrics" @@ -47,6 +46,8 @@ import ( "k8s.io/client-go/transport" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/testutil" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/spdy" ) // TestStreamTranslator_LoopbackStdinToStdout returns random data sent on the client's @@ -61,7 +62,7 @@ func TestStreamTranslator_LoopbackStdinToStdout(t *testing.T) { t.Cleanup(metrics.ResetForTest) // Create upstream fake SPDY server which copies STDIN back onto STDOUT stream. spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx, err := createSPDYServerStreams(w, req, Options{ + ctx, err := createSPDYServerStreams(t, w, req, Options{ Stdin: true, Stdout: true, }) @@ -162,7 +163,7 @@ func TestStreamTranslator_LoopbackStdinToStderr(t *testing.T) { t.Cleanup(metrics.ResetForTest) // Create upstream fake SPDY server which copies STDIN back onto STDERR stream. spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx, err := createSPDYServerStreams(w, req, Options{ + ctx, err := createSPDYServerStreams(t, w, req, Options{ Stdin: true, Stderr: true, }) @@ -267,7 +268,7 @@ func TestStreamTranslator_ErrorStream(t *testing.T) { // Create upstream fake SPDY server, returning a non-zero exit code // on error stream within the structured error. spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx, err := createSPDYServerStreams(w, req, Options{ + ctx, err := createSPDYServerStreams(t, w, req, Options{ Stdout: true, }) if err != nil { @@ -373,7 +374,7 @@ func TestStreamTranslator_MultipleReadChannels(t *testing.T) { t.Cleanup(metrics.ResetForTest) // Create upstream fake SPDY server which copies STDIN back onto STDOUT and STDERR stream. spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx, err := createSPDYServerStreams(w, req, Options{ + ctx, err := createSPDYServerStreams(t, w, req, Options{ Stdin: true, Stdout: true, Stderr: true, @@ -478,7 +479,7 @@ apiserver_stream_translator_requests_total{code="200"} 1 func TestStreamTranslator_ThrottleReadChannels(t *testing.T) { // Create upstream fake SPDY server which copies STDIN back onto STDOUT and STDERR stream. spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx, err := createSPDYServerStreams(w, req, Options{ + ctx, err := createSPDYServerStreams(t, w, req, Options{ Stdin: true, Stdout: true, Stderr: true, @@ -621,7 +622,7 @@ func TestStreamTranslator_TTYResizeChannel(t *testing.T) { actualTerminalSizes := make([]remotecommand.TerminalSize, 0, numSizeQueue) // Create upstream fake SPDY server which copies STDIN back onto STDERR stream. spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx, err := createSPDYServerStreams(w, req, Options{ + ctx, err := createSPDYServerStreams(t, w, req, Options{ Tty: true, }) if err != nil { @@ -871,9 +872,23 @@ type streamAndReply struct { // connection with remote command streams defined in passed options. Returns a streamContext // structure containing the Reader/Writer streams to communicate through the SDPY connection. // Returns an error if unable to upgrade the HTTP connection to a SPDY connection. -func createSPDYServerStreams(w http.ResponseWriter, req *http.Request, opts Options) (*streamContext, error) { +func createSPDYServerStreams(t *testing.T, w http.ResponseWriter, req *http.Request, opts Options) (*streamContext, error) { _, err := httpstream.Handshake(req, w, []string{rcconstants.StreamProtocolV4Name}) if err != nil { + t.Log("------------------- [DEBUG] HANDSHAKE FAILED -------------------") + t.Logf("Method: %s", req.Method) + t.Logf("URL: %s", req.URL.String()) + t.Logf("Proto: %s", req.Proto) + t.Logf("Header Count: %d", len(req.Header)) + t.Logf("ContentLength: %d", req.ContentLength) // Should be zero for upgrade request + // Dump the request in its entirety. + dump, err := httputil.DumpRequest(req, true) + if err != nil { + t.Logf("DumpRequest failed: %v", err) + } else { + t.Logf("Full HTTP Request Dump:\n%s", dump) + } + return nil, err } @@ -956,20 +971,11 @@ func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) err } } -func fakeTransport() (*http.Transport, error) { +func fakeTransport() (http.RoundTripper, error) { cfg := &transport.Config{ TLS: transport.TLSConfig{ Insecure: true, - CAFile: "", }, } - rt, err := transport.New(cfg) - if err != nil { - return nil, err - } - t, ok := rt.(*http.Transport) - if !ok { - return nil, fmt.Errorf("unknown transport type: %T", rt) - } - return t, nil + return transport.New(cfg) } diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go index 7a7b92adaf77b..429ab78ebfa29 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go @@ -31,14 +31,14 @@ import ( gwebsocket "github.com/gorilla/websocket" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" utilnet "k8s.io/apimachinery/pkg/util/net" constants "k8s.io/apimachinery/pkg/util/portforward" "k8s.io/apiserver/pkg/util/proxy/metrics" "k8s.io/client-go/tools/portforward" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/spdy" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // TunnelingHandler is a handler which tunnels SPDY through WebSockets. diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go index 7cf57f2d3c652..c979ffbced324 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go @@ -33,8 +33,6 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" constants "k8s.io/apimachinery/pkg/util/portforward" "k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apimachinery/pkg/util/wait" @@ -44,6 +42,8 @@ import ( "k8s.io/client-go/tools/portforward" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/testutil" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/spdy" ) func TestTunnelingHandler_UpgradeStreamingAndTunneling(t *testing.T) { diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go index ee5a53ed88a39..c4aef1f70c7e1 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // fakeHandler implements http.Handler interface diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go index 798ce17672472..837c468108ba6 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go @@ -25,10 +25,10 @@ import ( "time" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" constants "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/remotecommand" + "k8s.io/streaming/pkg/httpstream/wsstream" ) const ( diff --git a/staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go b/staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go deleted file mode 100644 index 61b4dd4892726..0000000000000 --- a/staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2023 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. -*/ - -// Deprecated: This WebSockets package under apiserver is no longer in use. -// Please use the apimachinery version of the package at: -// -// k8s.io/apimachinery/pkg/util/httpstream/wsstream -package wsstream - -import apimachinerywsstream "k8s.io/apimachinery/pkg/util/httpstream/wsstream" - -// Aliases for all exported symbols previously in "conn.go" -const ( - ChannelWebSocketProtocol = apimachinerywsstream.ChannelWebSocketProtocol - Base64ChannelWebSocketProtocol = apimachinerywsstream.Base64ChannelWebSocketProtocol -) - -type ChannelType = apimachinerywsstream.ChannelType - -const ( - IgnoreChannel = apimachinerywsstream.IgnoreChannel - ReadChannel = apimachinerywsstream.ReadChannel - WriteChannel = apimachinerywsstream.WriteChannel - ReadWriteChannel = apimachinerywsstream.ReadWriteChannel -) - -type ChannelProtocolConfig = apimachinerywsstream.ChannelProtocolConfig - -var ( - IsWebSocketRequest = apimachinerywsstream.IsWebSocketRequest - IgnoreReceives = apimachinerywsstream.IgnoreReceives - NewDefaultChannelProtocols = apimachinerywsstream.NewDefaultChannelProtocols -) - -type Conn = apimachinerywsstream.Conn - -var NewConn = apimachinerywsstream.NewConn - -// Aliases for all exported symbols previously in "stream.go" -type ReaderProtocolConfig = apimachinerywsstream.ReaderProtocolConfig - -var NewDefaultReaderProtocols = apimachinerywsstream.NewDefaultReaderProtocols - -type Reader = apimachinerywsstream.Reader - -var NewReader = apimachinerywsstream.NewReader diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/truncate/truncate_test.go b/staging/src/k8s.io/apiserver/plugin/pkg/audit/truncate/truncate_test.go index a8ae1582d3498..644f81fde9f10 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/audit/truncate/truncate_test.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/truncate/truncate_test.go @@ -70,7 +70,6 @@ func TestTruncatingEvents(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() @@ -122,7 +121,6 @@ func TestSplittingBatches(t *testing.T) { }, } for _, tc := range testCases { - tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go index 3df8e580e4422..65bf08ee59adf 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go @@ -204,21 +204,18 @@ func (w *WebhookAuthorizer) Authorize(ctx context.Context, attr authorizer.Attri Verb: attr.GetVerb(), } } - // skipping match when feature is not enabled - if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) { - // Process Match Conditions before calling the webhook - matches, err := w.match(ctx, r) - // If at least one matchCondition evaluates to an error (but none are FALSE): - // If failurePolicy=Deny, then the webhook rejects the request - // If failurePolicy=NoOpinion, then the error is ignored and the webhook is skipped - if err != nil { - return w.decisionOnError, "", err - } - // If at least one matchCondition successfully evaluates to FALSE, - // then the webhook is skipped. - if !matches { - return authorizer.DecisionNoOpinion, "", nil - } + // Process Match Conditions before calling the webhook + matches, err := w.match(ctx, r) + // If at least one matchCondition evaluates to an error (but none are FALSE): + // If failurePolicy=Deny, then the webhook rejects the request + // If failurePolicy=NoOpinion, then the error is ignored and the webhook is skipped + if err != nil { + return w.decisionOnError, "", err + } + // If at least one matchCondition successfully evaluates to FALSE, + // then the webhook is skipped. + if !matches { + return authorizer.DecisionNoOpinion, "", nil } // If all evaluated successfully and ALL matchConditions evaluate to TRUE, // then the webhook is called. diff --git a/staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger-with-shared-parameters.json b/staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger-with-shared-parameters.json index b6422c534b940..3fdbaac405b87 100644 --- a/staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger-with-shared-parameters.json +++ b/staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger-with-shared-parameters.json @@ -8193,7 +8193,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { diff --git a/staging/src/k8s.io/cli-runtime/go.mod b/staging/src/k8s.io/cli-runtime/go.mod index fa6887f0ab457..7d11b8c9b52f3 100644 --- a/staging/src/k8s.io/cli-runtime/go.mod +++ b/staging/src/k8s.io/cli-runtime/go.mod @@ -2,9 +2,9 @@ module k8s.io/cli-runtime -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/google/gnostic-models v0.7.0 @@ -12,21 +12,21 @@ require ( github.com/google/uuid v1.6.0 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/moby/term v0.5.0 - github.com/spf13/cobra v1.10.0 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v2 v2.4.3 - golang.org/x/sync v0.18.0 - golang.org/x/text v0.31.0 + golang.org/x/sync v0.19.0 + golang.org/x/text v0.33.0 gopkg.in/evanphx/json-patch.v4 v4.13.0 k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 k8s.io/client-go v0.0.0 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 - sigs.k8s.io/kustomize/api v0.20.1 - sigs.k8s.io/kustomize/kyaml v0.20.1 + k8s.io/klog/v2 v2.140.0 + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 + sigs.k8s.io/kustomize/api v0.21.1 + sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) @@ -34,7 +34,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/blang/semver/v4 v4.0.0 // 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/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -42,7 +42,6 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -56,22 +55,22 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/time v0.9.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/cli-runtime/go.sum b/staging/src/k8s.io/cli-runtime/go.sum index b53889afc8eec..90ae35f8613ae 100644 --- a/staging/src/k8s.io/cli-runtime/go.sum +++ b/staging/src/k8s.io/cli-runtime/go.sum @@ -1,10 +1,7 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -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/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -15,8 +12,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/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-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -31,8 +28,6 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -41,13 +36,9 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -65,7 +56,7 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -79,24 +70,18 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -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= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= -github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= -github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -121,31 +106,28 @@ 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= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +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/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -157,21 +139,21 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= 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/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= +sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/kyaml.go b/staging/src/k8s.io/cli-runtime/pkg/printers/kyaml.go index 285da2037dc0c..8b1d69b45f5e9 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/printers/kyaml.go +++ b/staging/src/k8s.io/cli-runtime/pkg/printers/kyaml.go @@ -54,6 +54,8 @@ func (p *KYAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error { if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj.Object.Object)).Type().PkgPath()) { return errors.New(InternalObjectPrinterErr) } + return p.encoder.FromObject(obj, w) + case *runtime.Unknown: return p.encoder.FromYAML(bytes.NewReader(obj.Raw), w) } diff --git a/staging/src/k8s.io/cli-runtime/pkg/resource/builder_test.go b/staging/src/k8s.io/cli-runtime/pkg/resource/builder_test.go index e556e462da590..1e090d714dfd3 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/resource/builder_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/resource/builder_test.go @@ -39,13 +39,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/streaming" - "k8s.io/apimachinery/pkg/util/dump" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" restclientwatch "k8s.io/client-go/rest/watch" "k8s.io/client-go/restmapper" + "k8s.io/utils/dump" // TODO we need to remove this linkage and create our own scheme v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/scheme" diff --git a/staging/src/k8s.io/cli-runtime/pkg/resource/kustomizevisitor_test.go b/staging/src/k8s.io/cli-runtime/pkg/resource/kustomizevisitor_test.go index 52e6bd1cc3e1d..7acca2d188ad3 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/resource/kustomizevisitor_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/resource/kustomizevisitor_test.go @@ -19,7 +19,7 @@ package resource import ( "testing" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/kyaml/filesys" ) diff --git a/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go b/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go index 76cfbbda82323..fb37db367c086 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go +++ b/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go @@ -220,7 +220,6 @@ func (l ConcurrentVisitorList) Visit(fn VisitorFunc) error { g.SetLimit(concurrency) for i := range l.visitors { - i := i g.Go(func() error { return l.visitors[i].Visit(fn) }) diff --git a/staging/src/k8s.io/cli-runtime/pkg/resource/visitor_test.go b/staging/src/k8s.io/cli-runtime/pkg/resource/visitor_test.go index fd9b6ef81c169..c8df077c5287e 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/resource/visitor_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/resource/visitor_test.go @@ -29,7 +29,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/utils/dump" ) func TestVisitorHttpGet(t *testing.T) { diff --git a/staging/src/k8s.io/client-go/OWNERS b/staging/src/k8s.io/client-go/OWNERS index b0ddb05929373..ad5c014f2ef33 100644 --- a/staging/src/k8s.io/client-go/OWNERS +++ b/staging/src/k8s.io/client-go/OWNERS @@ -9,6 +9,7 @@ approvers: - sttts - yliaog - jpbetz + - enj reviewers: - aojea - apelisse @@ -22,6 +23,7 @@ reviewers: - yliaog - jpbetz - jefftree + - enj labels: - sig/api-machinery emeritus_approvers: diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/applyconfiguration.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/applyconfiguration.go new file mode 100644 index 0000000000000..63582a6365717 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/applyconfiguration.go @@ -0,0 +1,81 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ApplyConfigurationApplyConfiguration represents a declarative configuration of the ApplyConfiguration type for use +// with apply. +// +// ApplyConfiguration defines the desired configuration values of an object. +type ApplyConfigurationApplyConfiguration struct { + // expression will be evaluated by CEL to create an apply configuration. + // ref: https://github.com/google/cel-spec + // + // Apply configurations are declared in CEL using object initialization. For example, this CEL expression + // returns an apply configuration to set a single field: + // + // Object{ + // spec: Object.spec{ + // serviceAccountName: "example" + // } + // } + // + // Apply configurations may not modify atomic structs, maps or arrays due to the risk of accidental deletion of + // values not included in the apply configuration. + // + // CEL expressions have access to the object types needed to create apply configurations: + // + // - 'Object' - CEL type of the resource object. + // - 'Object.' - CEL type of object field (such as 'Object.spec') + // - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers') + // + // CEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. The value is null for DELETE requests. + // - 'oldObject' - The existing object. The value is null for CREATE requests. + // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). + // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. + // - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. + // - 'variables' - Map of composited variables, from its name to its lazily evaluated value. + // For example, a variable named 'foo' can be accessed as 'variables.foo'. + // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. + // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz + // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the + // request resource. + // + // The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the + // object. No other metadata properties are accessible. + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Required. + Expression *string `json:"expression,omitempty"` +} + +// ApplyConfigurationApplyConfiguration constructs a declarative configuration of the ApplyConfiguration type for use with +// apply. +func ApplyConfiguration() *ApplyConfigurationApplyConfiguration { + return &ApplyConfigurationApplyConfiguration{} +} + +// WithExpression sets the Expression field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Expression field is set to the value of the last call. +func (b *ApplyConfigurationApplyConfiguration) WithExpression(value string) *ApplyConfigurationApplyConfiguration { + b.Expression = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/expressionwarning.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/expressionwarning.go index 31219c7e80895..44105d016757a 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/expressionwarning.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/expressionwarning.go @@ -23,11 +23,11 @@ package v1 // // ExpressionWarning is a warning information that targets a specific expression. type ExpressionWarningApplyConfiguration struct { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" FieldRef *string `json:"fieldRef,omitempty"` - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. Warning *string `json:"warning,omitempty"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/jsonpatch.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/jsonpatch.go new file mode 100644 index 0000000000000..800a835a47fa4 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/jsonpatch.go @@ -0,0 +1,105 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// JSONPatchApplyConfiguration represents a declarative configuration of the JSONPatch type for use +// with apply. +// +// JSONPatch defines a JSON Patch. +type JSONPatchApplyConfiguration struct { + // expression will be evaluated by CEL to create a [JSON patch](https://jsonpatch.com/). + // ref: https://github.com/google/cel-spec + // + // expression must return an array of JSONPatch values. + // + // For example, this CEL expression returns a JSON patch to conditionally modify a value: + // + // [ + // JSONPatch{op: "test", path: "/spec/example", value: "Red"}, + // JSONPatch{op: "replace", path: "/spec/example", value: "Green"} + // ] + // + // To define an object for the patch value, use Object types. For example: + // + // [ + // JSONPatch{ + // op: "add", + // path: "/spec/selector", + // value: Object.spec.selector{matchLabels: {"environment": "test"}} + // } + // ] + // + // To use strings containing '/' and '~' as JSONPatch path keys, use "jsonpatch.escapeKey". For example: + // + // [ + // JSONPatch{ + // op: "add", + // path: "/metadata/labels/" + jsonpatch.escapeKey("example.com/environment"), + // value: "test" + // }, + // ] + // + // CEL expressions have access to the types needed to create JSON patches and objects: + // + // - 'JSONPatch' - CEL type of JSON Patch operations. JSONPatch has the fields 'op', 'from', 'path' and 'value'. + // See [JSON patch](https://jsonpatch.com/) for more details. The 'value' field may be set to any of: string, + // integer, array, map or object. If set, the 'path' and 'from' fields must be set to a + // [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901/) string, where the 'jsonpatch.escapeKey()' CEL + // function may be used to escape path keys containing '/' and '~'. + // - 'Object' - CEL type of the resource object. + // - 'Object.' - CEL type of object field (such as 'Object.spec') + // - 'Object.....` - CEL type of nested field (such as 'Object.spec.containers') + // + // CEL expressions have access to the contents of the API request, organized into CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. The value is null for DELETE requests. + // - 'oldObject' - The existing object. The value is null for CREATE requests. + // - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). + // - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. + // - 'namespaceObject' - The namespace object that the incoming object belongs to. The value is null for cluster-scoped resources. + // - 'variables' - Map of composited variables, from its name to its lazily evaluated value. + // For example, a variable named 'foo' can be accessed as 'variables.foo'. + // - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request. + // See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz + // - 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the + // request resource. + // + // CEL expressions have access to [Kubernetes CEL function libraries](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries) + // as well as: + // + // - 'jsonpatch.escapeKey' - Performs JSONPatch key escaping. '~' and '/' are escaped as '~0' and `~1' respectively). + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Required. + Expression *string `json:"expression,omitempty"` +} + +// JSONPatchApplyConfiguration constructs a declarative configuration of the JSONPatch type for use with +// apply. +func JSONPatch() *JSONPatchApplyConfiguration { + return &JSONPatchApplyConfiguration{} +} + +// WithExpression sets the Expression field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Expression field is set to the value of the last call. +func (b *JSONPatchApplyConfiguration) WithExpression(value string) *JSONPatchApplyConfiguration { + b.Expression = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchcondition.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchcondition.go index 1e149da4ec4d8..785fbbf023107 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchcondition.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchcondition.go @@ -23,7 +23,7 @@ package v1 // // MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook. type MatchConditionApplyConfiguration struct { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -33,7 +33,7 @@ type MatchConditionApplyConfiguration struct { // // Required. Name *string `json:"name,omitempty"` - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchresources.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchresources.go index cd1a3d5fcf4eb..29ee198b3a21c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchresources.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/matchresources.go @@ -30,7 +30,7 @@ import ( // on whether it meets the match criteria. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) type MatchResourcesApplyConfiguration struct { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -74,7 +74,7 @@ type MatchResourcesApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *metav1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the validation based on if the + // objectSelector decides whether to run the validation based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the cel validation, and // is considered to match if either object matches the selector. A null @@ -86,10 +86,10 @@ type MatchResourcesApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *metav1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. // The policy cares about an operation if it matches _any_ Rule. ResourceRules []NamedRuleWithOperationsApplyConfiguration `json:"resourceRules,omitempty"` - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) ExcludeResourceRules []NamedRuleWithOperationsApplyConfiguration `json:"excludeResourceRules,omitempty"` // matchPolicy defines how the "MatchResources" list is used to match incoming requests. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicy.go new file mode 100644 index 0000000000000..f99f055b3c777 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicy.go @@ -0,0 +1,274 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + internal "k8s.io/client-go/applyconfigurations/internal" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// MutatingAdmissionPolicyApplyConfiguration represents a declarative configuration of the MutatingAdmissionPolicy type for use +// with apply. +// +// MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. +type MutatingAdmissionPolicyApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // spec defines the desired behavior of the MutatingAdmissionPolicy. + Spec *MutatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"` +} + +// MutatingAdmissionPolicy constructs a declarative configuration of the MutatingAdmissionPolicy type for use with +// apply. +func MutatingAdmissionPolicy(name string) *MutatingAdmissionPolicyApplyConfiguration { + b := &MutatingAdmissionPolicyApplyConfiguration{} + b.WithName(name) + b.WithKind("MutatingAdmissionPolicy") + b.WithAPIVersion("admissionregistration.k8s.io/v1") + return b +} + +// ExtractMutatingAdmissionPolicyFrom extracts the applied configuration owned by fieldManager from +// mutatingAdmissionPolicy for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// mutatingAdmissionPolicy must be a unmodified MutatingAdmissionPolicy API object that was retrieved from the Kubernetes API. +// ExtractMutatingAdmissionPolicyFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractMutatingAdmissionPolicyFrom(mutatingAdmissionPolicy *admissionregistrationv1.MutatingAdmissionPolicy, fieldManager string, subresource string) (*MutatingAdmissionPolicyApplyConfiguration, error) { + b := &MutatingAdmissionPolicyApplyConfiguration{} + err := managedfields.ExtractInto(mutatingAdmissionPolicy, internal.Parser().Type("io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(mutatingAdmissionPolicy.Name) + + b.WithKind("MutatingAdmissionPolicy") + b.WithAPIVersion("admissionregistration.k8s.io/v1") + return b, nil +} + +// ExtractMutatingAdmissionPolicy extracts the applied configuration owned by fieldManager from +// mutatingAdmissionPolicy. If no managedFields are found in mutatingAdmissionPolicy for fieldManager, a +// MutatingAdmissionPolicyApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// mutatingAdmissionPolicy must be a unmodified MutatingAdmissionPolicy API object that was retrieved from the Kubernetes API. +// ExtractMutatingAdmissionPolicy provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractMutatingAdmissionPolicy(mutatingAdmissionPolicy *admissionregistrationv1.MutatingAdmissionPolicy, fieldManager string) (*MutatingAdmissionPolicyApplyConfiguration, error) { + return ExtractMutatingAdmissionPolicyFrom(mutatingAdmissionPolicy, fieldManager, "") +} + +func (b MutatingAdmissionPolicyApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithKind(value string) *MutatingAdmissionPolicyApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithAPIVersion(value string) *MutatingAdmissionPolicyApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithName(value string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithGenerateName(value string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithNamespace(value string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithUID(value types.UID) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithResourceVersion(value string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithGeneration(value int64) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithLabels(entries map[string]string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithAnnotations(entries map[string]string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithFinalizers(values ...string) *MutatingAdmissionPolicyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *MutatingAdmissionPolicyApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *MutatingAdmissionPolicyApplyConfiguration) WithSpec(value *MutatingAdmissionPolicySpecApplyConfiguration) *MutatingAdmissionPolicyApplyConfiguration { + b.Spec = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *MutatingAdmissionPolicyApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *MutatingAdmissionPolicyApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *MutatingAdmissionPolicyApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *MutatingAdmissionPolicyApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicybinding.go new file mode 100644 index 0000000000000..4bb30baa6f908 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicybinding.go @@ -0,0 +1,284 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + internal "k8s.io/client-go/applyconfigurations/internal" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// MutatingAdmissionPolicyBindingApplyConfiguration represents a declarative configuration of the MutatingAdmissionPolicyBinding type for use +// with apply. +// +// MutatingAdmissionPolicyBinding binds the MutatingAdmissionPolicy with parametrized resources. +// MutatingAdmissionPolicyBinding and the optional parameter resource together define how cluster administrators +// configure policies for clusters. +// +// For a given admission request, each binding will cause its policy to be +// evaluated N times, where N is 1 for policies/bindings that don't use +// params, otherwise N is the number of parameters selected by the binding. +// Each evaluation is constrained by a [runtime cost budget](https://kubernetes.io/docs/reference/using-api/cel/#runtime-cost-budget). +// +// Adding/removing policies, bindings, or params can not affect whether a +// given (policy, binding, param) combination is within its own CEL budget. +type MutatingAdmissionPolicyBindingApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. + Spec *MutatingAdmissionPolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` +} + +// MutatingAdmissionPolicyBinding constructs a declarative configuration of the MutatingAdmissionPolicyBinding type for use with +// apply. +func MutatingAdmissionPolicyBinding(name string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b := &MutatingAdmissionPolicyBindingApplyConfiguration{} + b.WithName(name) + b.WithKind("MutatingAdmissionPolicyBinding") + b.WithAPIVersion("admissionregistration.k8s.io/v1") + return b +} + +// ExtractMutatingAdmissionPolicyBindingFrom extracts the applied configuration owned by fieldManager from +// mutatingAdmissionPolicyBinding for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// mutatingAdmissionPolicyBinding must be a unmodified MutatingAdmissionPolicyBinding API object that was retrieved from the Kubernetes API. +// ExtractMutatingAdmissionPolicyBindingFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractMutatingAdmissionPolicyBindingFrom(mutatingAdmissionPolicyBinding *admissionregistrationv1.MutatingAdmissionPolicyBinding, fieldManager string, subresource string) (*MutatingAdmissionPolicyBindingApplyConfiguration, error) { + b := &MutatingAdmissionPolicyBindingApplyConfiguration{} + err := managedfields.ExtractInto(mutatingAdmissionPolicyBinding, internal.Parser().Type("io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(mutatingAdmissionPolicyBinding.Name) + + b.WithKind("MutatingAdmissionPolicyBinding") + b.WithAPIVersion("admissionregistration.k8s.io/v1") + return b, nil +} + +// ExtractMutatingAdmissionPolicyBinding extracts the applied configuration owned by fieldManager from +// mutatingAdmissionPolicyBinding. If no managedFields are found in mutatingAdmissionPolicyBinding for fieldManager, a +// MutatingAdmissionPolicyBindingApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// mutatingAdmissionPolicyBinding must be a unmodified MutatingAdmissionPolicyBinding API object that was retrieved from the Kubernetes API. +// ExtractMutatingAdmissionPolicyBinding provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractMutatingAdmissionPolicyBinding(mutatingAdmissionPolicyBinding *admissionregistrationv1.MutatingAdmissionPolicyBinding, fieldManager string) (*MutatingAdmissionPolicyBindingApplyConfiguration, error) { + return ExtractMutatingAdmissionPolicyBindingFrom(mutatingAdmissionPolicyBinding, fieldManager, "") +} + +func (b MutatingAdmissionPolicyBindingApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithKind(value string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithAPIVersion(value string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithName(value string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithGenerateName(value string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithNamespace(value string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithUID(value types.UID) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithResourceVersion(value string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithGeneration(value int64) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithLabels(entries map[string]string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithAnnotations(entries map[string]string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithFinalizers(values ...string) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) WithSpec(value *MutatingAdmissionPolicyBindingSpecApplyConfiguration) *MutatingAdmissionPolicyBindingApplyConfiguration { + b.Spec = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *MutatingAdmissionPolicyBindingApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicybindingspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicybindingspec.go new file mode 100644 index 0000000000000..a62e5dbb282e4 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicybindingspec.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// MutatingAdmissionPolicyBindingSpecApplyConfiguration represents a declarative configuration of the MutatingAdmissionPolicyBindingSpec type for use +// with apply. +// +// MutatingAdmissionPolicyBindingSpec defines the specification of the MutatingAdmissionPolicyBinding. +type MutatingAdmissionPolicyBindingSpecApplyConfiguration struct { + // policyName references a MutatingAdmissionPolicy name which the MutatingAdmissionPolicyBinding binds to. + // If the referenced resource does not exist, this binding is considered invalid and will be ignored + // Required. + PolicyName *string `json:"policyName,omitempty"` + // paramRef specifies the parameter resource used to configure the admission control policy. + // It should point to a resource of the type specified in spec.ParamKind of the bound MutatingAdmissionPolicy. + // If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the MutatingAdmissionPolicy applied. + // If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param. + ParamRef *ParamRefApplyConfiguration `json:"paramRef,omitempty"` + // matchResources limits what resources match this binding and may be mutated by it. + // Note that if matchResources matches a resource, the resource must also match a policy's matchConstraints and + // matchConditions before the resource may be mutated. + // When matchResources is unset, it does not constrain resource matching, and only the policy's matchConstraints + // and matchConditions must match for the resource to be mutated. + // Additionally, matchResources.resourceRules are optional and do not constraint matching when unset. + // Note that this is differs from MutatingAdmissionPolicy matchConstraints, where resourceRules are required. + // The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. + // '*' matches CREATE, UPDATE and CONNECT. + MatchResources *MatchResourcesApplyConfiguration `json:"matchResources,omitempty"` +} + +// MutatingAdmissionPolicyBindingSpecApplyConfiguration constructs a declarative configuration of the MutatingAdmissionPolicyBindingSpec type for use with +// apply. +func MutatingAdmissionPolicyBindingSpec() *MutatingAdmissionPolicyBindingSpecApplyConfiguration { + return &MutatingAdmissionPolicyBindingSpecApplyConfiguration{} +} + +// WithPolicyName sets the PolicyName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PolicyName field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingSpecApplyConfiguration) WithPolicyName(value string) *MutatingAdmissionPolicyBindingSpecApplyConfiguration { + b.PolicyName = &value + return b +} + +// WithParamRef sets the ParamRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ParamRef field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingSpecApplyConfiguration) WithParamRef(value *ParamRefApplyConfiguration) *MutatingAdmissionPolicyBindingSpecApplyConfiguration { + b.ParamRef = value + return b +} + +// WithMatchResources sets the MatchResources field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MatchResources field is set to the value of the last call. +func (b *MutatingAdmissionPolicyBindingSpecApplyConfiguration) WithMatchResources(value *MatchResourcesApplyConfiguration) *MutatingAdmissionPolicyBindingSpecApplyConfiguration { + b.MatchResources = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicyspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicyspec.go new file mode 100644 index 0000000000000..fc52a5595fdae --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingadmissionpolicyspec.go @@ -0,0 +1,172 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" +) + +// MutatingAdmissionPolicySpecApplyConfiguration represents a declarative configuration of the MutatingAdmissionPolicySpec type for use +// with apply. +// +// MutatingAdmissionPolicySpec defines the desired behavior of the admission policy. +type MutatingAdmissionPolicySpecApplyConfiguration struct { + // paramKind specifies the kind of resources used to parameterize this policy. + // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. + // If paramKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. + // If paramKind is specified but paramRef is unset in MutatingAdmissionPolicyBinding, the params variable will be null. + ParamKind *ParamKindApplyConfiguration `json:"paramKind,omitempty"` + // matchConstraints specifies what resources this policy is designed to validate. + // The MutatingAdmissionPolicy cares about a request if it matches _all_ Constraints. + // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API + // MutatingAdmissionPolicy cannot match MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding. + // The CREATE, UPDATE and CONNECT operations are allowed. The DELETE operation may not be matched. + // '*' matches CREATE, UPDATE and CONNECT. + // Required. + MatchConstraints *MatchResourcesApplyConfiguration `json:"matchConstraints,omitempty"` + // variables contain definitions of variables that can be used in composition of other expressions. + // Each variable is defined as a named CEL expression. + // The variables defined here will be available under `variables` in other expressions of the policy + // except matchConditions because matchConditions are evaluated before the rest of the policy. + // + // The expression of a variable can refer to other variables defined earlier in the list but not those after. + // Thus, variables must be sorted by the order of first appearance and acyclic. + Variables []VariableApplyConfiguration `json:"variables,omitempty"` + // mutations contain operations to perform on matching objects. + // mutations may not be empty; a minimum of one mutation is required. + // mutations are evaluated in order, and are reinvoked according to + // the reinvocationPolicy. + // The mutations of a policy are invoked for each binding of this policy + // and reinvocation of mutations occurs on a per binding basis. + Mutations []MutationApplyConfiguration `json:"mutations,omitempty"` + // failurePolicy defines how to handle failures for the admission policy. Failures can + // occur from CEL expression parse errors, type check errors, runtime errors and invalid + // or mis-configured policy definitions or bindings. + // + // A policy is invalid if paramKind refers to a non-existent Kind. + // A binding is invalid if paramRef.name refers to a non-existent resource. + // + // failurePolicy does not define how validations that evaluate to false are handled. + // + // Allowed values are Ignore or Fail. Defaults to Fail. + FailurePolicy *admissionregistrationv1.FailurePolicyType `json:"failurePolicy,omitempty"` + // matchConditions is a list of conditions that must be met for a request to be validated. + // Match conditions filter requests that have already been matched by the matchConstraints. + // An empty list of matchConditions matches all requests. + // There are a maximum of 64 match conditions allowed. + // + // If a parameter object is provided, it can be accessed via the `params` handle in the same + // manner as validation expressions. + // + // The exact matching logic is (in order): + // 1. If ANY matchCondition evaluates to FALSE, the policy is skipped. + // 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated. + // 3. If any matchCondition evaluates to an error (but none are FALSE): + // - If failurePolicy=Fail, reject the request + // - If failurePolicy=Ignore, the policy is skipped + MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"` + // reinvocationPolicy indicates whether mutations may be called multiple times per MutatingAdmissionPolicyBinding + // as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + // + // Never: These mutations will not be called more than once per binding in a single admission evaluation. + // + // IfNeeded: These mutations may be invoked more than once per binding for a single admission request and there is no guarantee of + // order with respect to other admission plugins, admission webhooks, bindings of this policy and admission policies. Mutations are only + // reinvoked when mutations change the object after this mutation is invoked. + // Required. + ReinvocationPolicy *admissionregistrationv1.ReinvocationPolicyType `json:"reinvocationPolicy,omitempty"` +} + +// MutatingAdmissionPolicySpecApplyConfiguration constructs a declarative configuration of the MutatingAdmissionPolicySpec type for use with +// apply. +func MutatingAdmissionPolicySpec() *MutatingAdmissionPolicySpecApplyConfiguration { + return &MutatingAdmissionPolicySpecApplyConfiguration{} +} + +// WithParamKind sets the ParamKind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ParamKind field is set to the value of the last call. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithParamKind(value *ParamKindApplyConfiguration) *MutatingAdmissionPolicySpecApplyConfiguration { + b.ParamKind = value + return b +} + +// WithMatchConstraints sets the MatchConstraints field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MatchConstraints field is set to the value of the last call. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithMatchConstraints(value *MatchResourcesApplyConfiguration) *MutatingAdmissionPolicySpecApplyConfiguration { + b.MatchConstraints = value + return b +} + +// WithVariables adds the given value to the Variables field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Variables field. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithVariables(values ...*VariableApplyConfiguration) *MutatingAdmissionPolicySpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithVariables") + } + b.Variables = append(b.Variables, *values[i]) + } + return b +} + +// WithMutations adds the given value to the Mutations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Mutations field. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithMutations(values ...*MutationApplyConfiguration) *MutatingAdmissionPolicySpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMutations") + } + b.Mutations = append(b.Mutations, *values[i]) + } + return b +} + +// WithFailurePolicy sets the FailurePolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FailurePolicy field is set to the value of the last call. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithFailurePolicy(value admissionregistrationv1.FailurePolicyType) *MutatingAdmissionPolicySpecApplyConfiguration { + b.FailurePolicy = &value + return b +} + +// WithMatchConditions adds the given value to the MatchConditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the MatchConditions field. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithMatchConditions(values ...*MatchConditionApplyConfiguration) *MutatingAdmissionPolicySpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMatchConditions") + } + b.MatchConditions = append(b.MatchConditions, *values[i]) + } + return b +} + +// WithReinvocationPolicy sets the ReinvocationPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ReinvocationPolicy field is set to the value of the last call. +func (b *MutatingAdmissionPolicySpecApplyConfiguration) WithReinvocationPolicy(value admissionregistrationv1.ReinvocationPolicyType) *MutatingAdmissionPolicySpecApplyConfiguration { + b.ReinvocationPolicy = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhook.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhook.go index 498611b5dd96c..47e691f12db6b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhook.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhook.go @@ -28,23 +28,23 @@ import ( // // MutatingWebhook describes an admission webhook and the resources and operations it applies to. type MutatingWebhookApplyConfiguration struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name *string `json:"name,omitempty"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig *WebhookClientConfigApplyConfiguration `json:"clientConfig,omitempty"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely // disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. Rules []RuleWithOperationsApplyConfiguration `json:"rules,omitempty"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Fail. FailurePolicy *admissionregistrationv1.FailurePolicyType `json:"failurePolicy,omitempty"` // matchPolicy defines how the "rules" list is used to match incoming requests. @@ -62,7 +62,7 @@ type MutatingWebhookApplyConfiguration struct { // // Defaults to "Equivalent" MatchPolicy *admissionregistrationv1.MatchPolicyType `json:"matchPolicy,omitempty"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -106,7 +106,7 @@ type MutatingWebhookApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *metav1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -118,20 +118,20 @@ type MutatingWebhookApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *metav1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. // Requests with the dryRun attribute will be auto-rejected if they match a webhook with // sideEffects == Unknown or Some. SideEffects *admissionregistrationv1.SideEffectClass `json:"sideEffects,omitempty"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. // Default to 10 seconds. TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -155,7 +155,7 @@ type MutatingWebhookApplyConfiguration struct { // // Defaults to "Never". ReinvocationPolicy *admissionregistrationv1.ReinvocationPolicyType `json:"reinvocationPolicy,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhookconfiguration.go index 87909f254bba4..dcb0b81cca3a8 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutatingwebhookconfiguration.go @@ -33,9 +33,9 @@ import ( // MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. type MutatingWebhookConfigurationApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. Webhooks []MutatingWebhookApplyConfiguration `json:"webhooks,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutation.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutation.go new file mode 100644 index 0000000000000..f47749d3cd310 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/mutation.go @@ -0,0 +1,72 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" +) + +// MutationApplyConfiguration represents a declarative configuration of the Mutation type for use +// with apply. +// +// Mutation specifies the CEL expression which is used to apply the Mutation. +type MutationApplyConfiguration struct { + // patchType indicates the patch strategy used. + // Allowed values are "ApplyConfiguration" and "JSONPatch". + // Required. + PatchType *admissionregistrationv1.PatchType `json:"patchType,omitempty"` + // applyConfiguration defines the desired configuration values of an object. + // The configuration is applied to the admission object using + // [structured merge diff](https://github.com/kubernetes-sigs/structured-merge-diff). + // A CEL expression is used to create apply configuration. + ApplyConfiguration *ApplyConfigurationApplyConfiguration `json:"applyConfiguration,omitempty"` + // jsonPatch defines a [JSON patch](https://jsonpatch.com/) operation to perform a mutation to the object. + // A CEL expression is used to create the JSON patch. + JSONPatch *JSONPatchApplyConfiguration `json:"jsonPatch,omitempty"` +} + +// MutationApplyConfiguration constructs a declarative configuration of the Mutation type for use with +// apply. +func Mutation() *MutationApplyConfiguration { + return &MutationApplyConfiguration{} +} + +// WithPatchType sets the PatchType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PatchType field is set to the value of the last call. +func (b *MutationApplyConfiguration) WithPatchType(value admissionregistrationv1.PatchType) *MutationApplyConfiguration { + b.PatchType = &value + return b +} + +// WithApplyConfiguration sets the ApplyConfiguration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ApplyConfiguration field is set to the value of the last call. +func (b *MutationApplyConfiguration) WithApplyConfiguration(value *ApplyConfigurationApplyConfiguration) *MutationApplyConfiguration { + b.ApplyConfiguration = value + return b +} + +// WithJSONPatch sets the JSONPatch field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the JSONPatch field is set to the value of the last call. +func (b *MutationApplyConfiguration) WithJSONPatch(value *JSONPatchApplyConfiguration) *MutationApplyConfiguration { + b.JSONPatch = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/namedrulewithoperations.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/namedrulewithoperations.go index fe7a6b8c0cd1e..326337c8cc7e6 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/namedrulewithoperations.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/namedrulewithoperations.go @@ -27,7 +27,7 @@ import ( // // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. type NamedRuleWithOperationsApplyConfiguration struct { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. ResourceNames []string `json:"resourceNames,omitempty"` // RuleWithOperations is a tuple of Operations and Resources. RuleWithOperationsApplyConfiguration `json:",inline"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramkind.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramkind.go index 5c6729fc6379c..7a140feddc58c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramkind.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramkind.go @@ -23,11 +23,11 @@ package v1 // // ParamKind is a tuple of Group Kind and Version. type ParamKindApplyConfiguration struct { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. APIVersion *string `json:"apiVersion,omitempty"` - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. Kind *string `json:"kind,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramref.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramref.go index eb9f6c47aaa9a..25690d06d1583 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramref.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/paramref.go @@ -62,7 +62,7 @@ type ParamRefApplyConfiguration struct { // One of `name` or `selector` must be set, but `name` and `selector` are // mutually exclusive properties. If one is set, the other must be unset. Selector *metav1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rule.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rule.go index 056e944fec741..aa1d948a99222 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rule.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rule.go @@ -28,15 +28,15 @@ import ( // Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended // to make sure that all the tuple expansions are valid. type RuleApplyConfiguration struct { - // APIGroups is the API groups the resources belong to. '*' is all groups. + // apiGroups is the API groups the resources belong to. '*' is all groups. // If '*' is present, the length of the slice must be one. // Required. APIGroups []string `json:"apiGroups,omitempty"` - // APIVersions is the API versions the resources belong to. '*' is all versions. + // apiVersions is the API versions the resources belong to. '*' is all versions. // If '*' is present, the length of the slice must be one. // Required. APIVersions []string `json:"apiVersions,omitempty"` - // Resources is a list of resources this rule applies to. + // resources is a list of resources this rule applies to. // // For example: // 'pods' means pods. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rulewithoperations.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rulewithoperations.go index a4a1643fec50a..575e1ab35c249 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rulewithoperations.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/rulewithoperations.go @@ -28,7 +28,7 @@ import ( // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // sure that all the tuple expansions are valid. type RuleWithOperationsApplyConfiguration struct { - // Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * + // operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * // for all of those operations and any future admission operations that are added. // If '*' is present, the length of the slice must be one. // Required. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/servicereference.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/servicereference.go index c93b61150faf7..e15728ae18ac5 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/servicereference.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/servicereference.go @@ -23,16 +23,16 @@ package v1 // // ServiceReference holds a reference to Service.legacy.k8s.io type ServiceReferenceApplyConfiguration struct { - // `namespace` is the namespace of the service. + // namespace is the namespace of the service. // Required Namespace *string `json:"namespace,omitempty"` - // `name` is the name of the service. + // name is the name of the service. // Required Name *string `json:"name,omitempty"` - // `path` is an optional URL path which will be sent in any request to + // path is an optional URL path which will be sent in any request to // this service. Path *string `json:"path,omitempty"` - // If specified, the port on the service that hosting webhook. + // port is the port on the service that hosts the webhook. // Default to 443 for backward compatibility. // `port` should be a valid port number (1-65535, inclusive). Port *int32 `json:"port,omitempty"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/typechecking.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/typechecking.go index f6077ee39e47b..5140aedb51312 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/typechecking.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/typechecking.go @@ -24,7 +24,7 @@ package v1 // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy type TypeCheckingApplyConfiguration struct { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. ExpressionWarnings []ExpressionWarningApplyConfiguration `json:"expressionWarnings,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicy.go index 22d965fa8132d..6e06ed3e5f09b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicy.go @@ -33,11 +33,11 @@ import ( // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. type ValidatingAdmissionPolicyApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. Spec *ValidatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"` - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybinding.go index 5a083a0568c02..590aabda8152d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybinding.go @@ -43,9 +43,9 @@ import ( // given (policy, binding, param) combination is within its own CEL budget. type ValidatingAdmissionPolicyBindingApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. Spec *ValidatingAdmissionPolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybindingspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybindingspec.go index 31d98bb9470bb..d783b4c41c807 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybindingspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicybindingspec.go @@ -27,7 +27,7 @@ import ( // // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingSpecApplyConfiguration struct { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. PolicyName *string `json:"policyName,omitempty"` @@ -36,7 +36,7 @@ type ValidatingAdmissionPolicyBindingSpecApplyConfiguration struct { // If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. // If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param. ParamRef *ParamRefApplyConfiguration `json:"paramRef,omitempty"` - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicyspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicyspec.go index b3f6989a72bbe..5f9336d4dd247 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicyspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicyspec.go @@ -27,18 +27,18 @@ import ( // // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. type ValidatingAdmissionPolicySpecApplyConfiguration struct { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. ParamKind *ParamKindApplyConfiguration `json:"paramKind,omitempty"` - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. MatchConstraints *MatchResourcesApplyConfiguration `json:"matchConstraints,omitempty"` - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. Validations []ValidationApplyConfiguration `json:"validations,omitempty"` @@ -61,7 +61,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct { // validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is // required. AuditAnnotations []AuditAnnotationApplyConfiguration `json:"auditAnnotations,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -76,7 +76,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct { // - If failurePolicy=Fail, reject the request // - If failurePolicy=Ignore, the policy is skipped MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"` - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicystatus.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicystatus.go index 0b3ea76124539..382ca9b260050 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicystatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingadmissionpolicystatus.go @@ -27,12 +27,12 @@ import ( // // ValidatingAdmissionPolicyStatus represents the status of an admission validation policy. type ValidatingAdmissionPolicyStatusApplyConfiguration struct { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. TypeChecking *TypeCheckingApplyConfiguration `json:"typeChecking,omitempty"` - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhook.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhook.go index 02298017cb7d1..3312b9d27d2a1 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhook.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhook.go @@ -28,23 +28,23 @@ import ( // // ValidatingWebhook describes an admission webhook and the resources and operations it applies to. type ValidatingWebhookApplyConfiguration struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name *string `json:"name,omitempty"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig *WebhookClientConfigApplyConfiguration `json:"clientConfig,omitempty"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely // disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. Rules []RuleWithOperationsApplyConfiguration `json:"rules,omitempty"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Fail. FailurePolicy *admissionregistrationv1.FailurePolicyType `json:"failurePolicy,omitempty"` // matchPolicy defines how the "rules" list is used to match incoming requests. @@ -62,7 +62,7 @@ type ValidatingWebhookApplyConfiguration struct { // // Defaults to "Equivalent" MatchPolicy *admissionregistrationv1.MatchPolicyType `json:"matchPolicy,omitempty"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -106,7 +106,7 @@ type ValidatingWebhookApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *metav1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -118,20 +118,20 @@ type ValidatingWebhookApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *metav1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. // Requests with the dryRun attribute will be auto-rejected if they match a webhook with // sideEffects == Unknown or Some. SideEffects *admissionregistrationv1.SideEffectClass `json:"sideEffects,omitempty"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. // Default to 10 seconds. TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -139,7 +139,7 @@ type ValidatingWebhookApplyConfiguration struct { // include any versions known to the API Server, calls to the webhook will fail // and be subject to the failure policy. AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhookconfiguration.go index fdf7923e9687f..c3ac1fafdd395 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validatingwebhookconfiguration.go @@ -33,9 +33,9 @@ import ( // ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. type ValidatingWebhookConfigurationApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. Webhooks []ValidatingWebhookApplyConfiguration `json:"webhooks,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validation.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validation.go index 2ad6756d019e8..32dc38391a65b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validation.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/validation.go @@ -27,7 +27,7 @@ import ( // // Validation specifies the CEL expression which is used to apply the validation. type ValidationApplyConfiguration struct { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -69,7 +69,7 @@ type ValidationApplyConfiguration struct { // non-intersecting keys are appended, retaining their partial order. // Required. Expression *string `json:"expression,omitempty"` - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -77,7 +77,7 @@ type ValidationApplyConfiguration struct { // The message must not contain line breaks. // If unset, the message is "failed Expression: {Expression}". Message *string `json:"message,omitempty"` - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/variable.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/variable.go index a99f837944dc2..eb89ec088f0f4 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/variable.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/variable.go @@ -23,11 +23,11 @@ package v1 // // Variable is the definition of a variable that is used for composition. A variable is defined as a named expression. type VariableApplyConfiguration struct { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` Name *string `json:"name,omitempty"` - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. Expression *string `json:"expression,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/webhookclientconfig.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/webhookclientconfig.go index 10f3d48de40a1..b379de8524d8b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/webhookclientconfig.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1/webhookclientconfig.go @@ -24,7 +24,7 @@ package v1 // WebhookClientConfig contains the information to make a TLS // connection with the webhook type WebhookClientConfigApplyConfiguration struct { - // `url` gives the location of the webhook, in standard URL form + // url gives the location of the webhook, in standard URL form // (`scheme://host:port/path`). Exactly one of `url` or `service` // must be specified. // @@ -50,12 +50,12 @@ type WebhookClientConfigApplyConfiguration struct { // allowed. Fragments ("#...") and query parameters ("?...") are not // allowed, either. URL *string `json:"url,omitempty"` - // `service` is a reference to the service for this webhook. Either + // service is a reference to the service for this webhook. Either // `service` or `url` must be specified. // // If the webhook is running within the cluster, then you should use `service`. Service *ServiceReferenceApplyConfiguration `json:"service,omitempty"` - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. // If unspecified, system trust roots on the apiserver are used. CABundle []byte `json:"caBundle,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/expressionwarning.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/expressionwarning.go index 220c5d2c935a5..c52e4d372aeaa 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/expressionwarning.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/expressionwarning.go @@ -23,11 +23,11 @@ package v1alpha1 // // ExpressionWarning is a warning information that targets a specific expression. type ExpressionWarningApplyConfiguration struct { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" FieldRef *string `json:"fieldRef,omitempty"` - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. Warning *string `json:"warning,omitempty"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchcondition.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchcondition.go index 7b758dbf4bd40..34fdee402cfae 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchcondition.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchcondition.go @@ -21,7 +21,7 @@ package v1alpha1 // MatchConditionApplyConfiguration represents a declarative configuration of the MatchCondition type for use // with apply. type MatchConditionApplyConfiguration struct { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -31,7 +31,7 @@ type MatchConditionApplyConfiguration struct { // // Required. Name *string `json:"name,omitempty"` - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchresources.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchresources.go index 5bdeab094b327..e6b5a8aceef0c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchresources.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/matchresources.go @@ -30,7 +30,7 @@ import ( // on whether it meets the match criteria. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) type MatchResourcesApplyConfiguration struct { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -74,7 +74,7 @@ type MatchResourcesApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *v1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the policy based on if the + // objectSelector decides whether to run the policy based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the policy's expression (CEL), and // is considered to match if either object matches the selector. A null @@ -86,10 +86,10 @@ type MatchResourcesApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *v1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // ResourceRules describes what operations on what resources/subresources the admission policy matches. + // resourceRules describes what operations on what resources/subresources the admission policy matches. // The policy cares about an operation if it matches _any_ Rule. ResourceRules []NamedRuleWithOperationsApplyConfiguration `json:"resourceRules,omitempty"` - // ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the policy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) ExcludeResourceRules []NamedRuleWithOperationsApplyConfiguration `json:"excludeResourceRules,omitempty"` // matchPolicy defines how the "MatchResources" list is used to match incoming requests. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicy.go index be5b79124736d..678e4d612a458 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicy.go @@ -33,9 +33,9 @@ import ( // MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. type MutatingAdmissionPolicyApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the MutatingAdmissionPolicy. + // spec defines the desired behavior of the MutatingAdmissionPolicy. Spec *MutatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go index 4964a3106d8c6..5286ce24c57cb 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go @@ -43,9 +43,9 @@ import ( // given (policy, binding, param) combination is within its own CEL budget. type MutatingAdmissionPolicyBindingApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the MutatingAdmissionPolicyBinding. + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. Spec *MutatingAdmissionPolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/namedrulewithoperations.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/namedrulewithoperations.go index b9e309edc2bc2..45c12f191dd83 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/namedrulewithoperations.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/namedrulewithoperations.go @@ -28,7 +28,7 @@ import ( // // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. type NamedRuleWithOperationsApplyConfiguration struct { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. ResourceNames []string `json:"resourceNames,omitempty"` // RuleWithOperations is a tuple of Operations and Resources. v1.RuleWithOperationsApplyConfiguration `json:",inline"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramkind.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramkind.go index 7e008e329c4e0..26227c10a15d8 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramkind.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramkind.go @@ -23,11 +23,11 @@ package v1alpha1 // // ParamKind is a tuple of Group Kind and Version. type ParamKindApplyConfiguration struct { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. APIVersion *string `json:"apiVersion,omitempty"` - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. Kind *string `json:"kind,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramref.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramref.go index 6b77cd0eae30d..ce2ecf1a00993 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramref.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/paramref.go @@ -29,7 +29,7 @@ import ( // ParamRef describes how to locate the params to be used as input to // expressions of rules applied by a policy binding. type ParamRefApplyConfiguration struct { - // `name` is the name of the resource being referenced. + // name is the name of the resource being referenced. // // `name` and `selector` are mutually exclusive properties. If one is set, // the other must be unset. @@ -58,7 +58,7 @@ type ParamRefApplyConfiguration struct { // One of `name` or `selector` must be set, but `name` and `selector` are // mutually exclusive properties. If one is set, the other must be unset. Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/typechecking.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/typechecking.go index 3b5bff0f3c7b8..c455b88165283 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/typechecking.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/typechecking.go @@ -24,7 +24,7 @@ package v1alpha1 // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy type TypeCheckingApplyConfiguration struct { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. ExpressionWarnings []ExpressionWarningApplyConfiguration `json:"expressionWarnings,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicy.go index 7f85ed8e2fe1c..9191eaab20647 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicy.go @@ -33,11 +33,11 @@ import ( // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. type ValidatingAdmissionPolicyApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. Spec *ValidatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"` - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go index 0760e92744979..ad61dad084664 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go @@ -43,9 +43,9 @@ import ( // given (policy, binding, param) combination is within its own CEL budget. type ValidatingAdmissionPolicyBindingApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. Spec *ValidatingAdmissionPolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybindingspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybindingspec.go index bbbd59c97a702..f157dc55d9471 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybindingspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicybindingspec.go @@ -27,7 +27,7 @@ import ( // // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingSpecApplyConfiguration struct { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. PolicyName *string `json:"policyName,omitempty"` @@ -36,7 +36,7 @@ type ValidatingAdmissionPolicyBindingSpecApplyConfiguration struct { // If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. // If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param. ParamRef *ParamRefApplyConfiguration `json:"paramRef,omitempty"` - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicyspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicyspec.go index ba20af8851c5a..969f71817852b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicyspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicyspec.go @@ -27,18 +27,18 @@ import ( // // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. type ValidatingAdmissionPolicySpecApplyConfiguration struct { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. ParamKind *ParamKindApplyConfiguration `json:"paramKind,omitempty"` - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. MatchConstraints *MatchResourcesApplyConfiguration `json:"matchConstraints,omitempty"` - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. Validations []ValidationApplyConfiguration `json:"validations,omitempty"` @@ -61,7 +61,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct { // validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is // required. AuditAnnotations []AuditAnnotationApplyConfiguration `json:"auditAnnotations,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -76,7 +76,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct { // - If failurePolicy=Fail, reject the request // - If failurePolicy=Ignore, the policy is skipped MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"` - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicystatus.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicystatus.go index bd33b855649cc..2158263448110 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicystatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validatingadmissionpolicystatus.go @@ -27,12 +27,12 @@ import ( // // ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy. type ValidatingAdmissionPolicyStatusApplyConfiguration struct { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. TypeChecking *TypeCheckingApplyConfiguration `json:"typeChecking,omitempty"` - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validation.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validation.go index 24416b210ec37..b21800a8a3578 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validation.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/validation.go @@ -27,7 +27,7 @@ import ( // // Validation specifies the CEL expression which is used to apply the validation. type ValidationApplyConfiguration struct { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -69,7 +69,7 @@ type ValidationApplyConfiguration struct { // non-intersecting keys are appended, retaining their partial order. // Required. Expression *string `json:"expression,omitempty"` - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -77,7 +77,7 @@ type ValidationApplyConfiguration struct { // The message must not contain line breaks. // If unset, the message is "failed Expression: {Expression}". Message *string `json:"message,omitempty"` - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/variable.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/variable.go index df7e1c9db6685..284234926b9a3 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/variable.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1/variable.go @@ -23,11 +23,11 @@ package v1alpha1 // // Variable is the definition of a variable that is used for composition. type VariableApplyConfiguration struct { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` Name *string `json:"name,omitempty"` - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. Expression *string `json:"expression,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/expressionwarning.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/expressionwarning.go index 0b0235f6f0e0c..39e6ca9d28e91 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/expressionwarning.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/expressionwarning.go @@ -23,11 +23,11 @@ package v1beta1 // // ExpressionWarning is a warning information that targets a specific expression. type ExpressionWarningApplyConfiguration struct { - // The path to the field that refers the expression. + // fieldRef is the path to the field that refers to the expression. // For example, the reference to the expression of the first item of // validations is "spec.validations[0].expression" FieldRef *string `json:"fieldRef,omitempty"` - // The content of type checking information in a human-readable form. + // warning contains the content of type checking information in a human-readable form. // Each line of the warning contains the type that the expression is checked // against, followed by the type check error from the compiler. Warning *string `json:"warning,omitempty"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchcondition.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchcondition.go index a5c4b433ed18e..93c1aeb07cc7b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchcondition.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchcondition.go @@ -23,7 +23,7 @@ package v1beta1 // // MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook. type MatchConditionApplyConfiguration struct { - // Name is an identifier for this match condition, used for strategic merging of MatchConditions, + // name is an identifier for this match condition, used for strategic merging of MatchConditions, // as well as providing an identifier for logging purposes. A good name should be descriptive of // the associated expression. // Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and @@ -33,7 +33,7 @@ type MatchConditionApplyConfiguration struct { // // Required. Name *string `json:"name,omitempty"` - // Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. // CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables: // // 'object' - The object from the incoming request. The value is null for DELETE requests. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchresources.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchresources.go index a74a65fd64ef9..836b0a12183b4 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchresources.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/matchresources.go @@ -30,7 +30,7 @@ import ( // on whether it meets the match criteria. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) type MatchResourcesApplyConfiguration struct { - // NamespaceSelector decides whether to run the admission control policy on an object based + // namespaceSelector decides whether to run the admission control policy on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -74,7 +74,7 @@ type MatchResourcesApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *v1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the validation based on if the + // objectSelector decides whether to run the validation based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the cel validation, and // is considered to match if either object matches the selector. A null @@ -86,10 +86,10 @@ type MatchResourcesApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *v1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // resourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. // The policy cares about an operation if it matches _any_ Rule. ResourceRules []NamedRuleWithOperationsApplyConfiguration `json:"resourceRules,omitempty"` - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // excludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) ExcludeResourceRules []NamedRuleWithOperationsApplyConfiguration `json:"excludeResourceRules,omitempty"` // matchPolicy defines how the "MatchResources" list is used to match incoming requests. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicy.go index 8812dee13070a..9dbdd7dbd9089 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicy.go @@ -33,9 +33,9 @@ import ( // MutatingAdmissionPolicy describes the definition of an admission mutation policy that mutates the object coming into admission chain. type MutatingAdmissionPolicyApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the MutatingAdmissionPolicy. + // spec defines the desired behavior of the MutatingAdmissionPolicy. Spec *MutatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go index 7ee4a731e13f8..560d1b1469487 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go @@ -43,9 +43,9 @@ import ( // given (policy, binding, param) combination is within its own CEL budget. type MutatingAdmissionPolicyBindingApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the MutatingAdmissionPolicyBinding. + // spec defines the desired behavior of the MutatingAdmissionPolicyBinding. Spec *MutatingAdmissionPolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhook.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhook.go index af93de8a951e3..d70d551d3e345 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhook.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhook.go @@ -30,23 +30,23 @@ import ( // // MutatingWebhook describes an admission webhook and the resources and operations it applies to. type MutatingWebhookApplyConfiguration struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name *string `json:"name,omitempty"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig *WebhookClientConfigApplyConfiguration `json:"clientConfig,omitempty"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely // disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. Rules []v1.RuleWithOperationsApplyConfiguration `json:"rules,omitempty"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. FailurePolicy *admissionregistrationv1beta1.FailurePolicyType `json:"failurePolicy,omitempty"` // matchPolicy defines how the "rules" list is used to match incoming requests. @@ -64,7 +64,7 @@ type MutatingWebhookApplyConfiguration struct { // // Defaults to "Exact" MatchPolicy *admissionregistrationv1beta1.MatchPolicyType `json:"matchPolicy,omitempty"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -108,7 +108,7 @@ type MutatingWebhookApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *metav1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -120,20 +120,20 @@ type MutatingWebhookApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *metav1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. // Requests with the dryRun attribute will be auto-rejected if they match a webhook with // sideEffects == Unknown or Some. Defaults to Unknown. SideEffects *admissionregistrationv1beta1.SideEffectClass `json:"sideEffects,omitempty"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. // Default to 30 seconds. TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -158,7 +158,7 @@ type MutatingWebhookApplyConfiguration struct { // // Defaults to "Never". ReinvocationPolicy *admissionregistrationv1.ReinvocationPolicyType `json:"reinvocationPolicy,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhookconfiguration.go index d51d71a328ff6..63bce15404d05 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/mutatingwebhookconfiguration.go @@ -34,9 +34,9 @@ import ( // Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration instead. type MutatingWebhookConfigurationApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. Webhooks []MutatingWebhookApplyConfiguration `json:"webhooks,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/namedrulewithoperations.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/namedrulewithoperations.go index 8b189dbb891ed..0da66790cc77c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/namedrulewithoperations.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/namedrulewithoperations.go @@ -28,7 +28,7 @@ import ( // // NamedRuleWithOperations is a tuple of Operations and Resources with ResourceNames. type NamedRuleWithOperationsApplyConfiguration struct { - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // resourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. ResourceNames []string `json:"resourceNames,omitempty"` // RuleWithOperations is a tuple of Operations and Resources. v1.RuleWithOperationsApplyConfiguration `json:",inline"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramkind.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramkind.go index dbbef1b6e0bea..2ee21d0fc96c4 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramkind.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramkind.go @@ -23,11 +23,11 @@ package v1beta1 // // ParamKind is a tuple of Group Kind and Version. type ParamKindApplyConfiguration struct { - // APIVersion is the API group version the resources belong to. + // apiVersion is the API group version the resources belong to. // In format of "group/version". // Required. APIVersion *string `json:"apiVersion,omitempty"` - // Kind is the API kind the resources belong to. + // kind is the API kind the resources belong to. // Required. Kind *string `json:"kind,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramref.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramref.go index 724cd1a0de548..3e598a06d9e60 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramref.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/paramref.go @@ -62,7 +62,7 @@ type ParamRefApplyConfiguration struct { // One of `name` or `selector` must be set, but `name` and `selector` are // mutually exclusive properties. If one is set, the other must be unset. Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` - // `parameterNotFoundAction` controls the behavior of the binding when the resource + // parameterNotFoundAction controls the behavior of the binding when the resource // exists, and name or selector is valid, but there are no parameters // matched by the binding. If the value is set to `Allow`, then no // matched parameters will be treated as successful validation by the binding. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/servicereference.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/servicereference.go index 9591d48a873d5..b1c44013a256f 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/servicereference.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/servicereference.go @@ -23,18 +23,18 @@ package v1beta1 // // ServiceReference holds a reference to Service.legacy.k8s.io type ServiceReferenceApplyConfiguration struct { - // `namespace` is the namespace of the service. + // namespace is the namespace of the service. // Required Namespace *string `json:"namespace,omitempty"` - // `name` is the name of the service. + // name is the name of the service. // Required Name *string `json:"name,omitempty"` - // `path` is an optional URL path which will be sent in any request to + // path is an optional URL path which will be sent in any request to // this service. Path *string `json:"path,omitempty"` - // If specified, the port on the service that hosting webhook. + // port is the port on the service that hosts the webhook. // Default to 443 for backward compatibility. - // `port` should be a valid port number (1-65535, inclusive). + // port should be a valid port number (1-65535, inclusive). Port *int32 `json:"port,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/typechecking.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/typechecking.go index b3b26edb83276..87a0ea3be6d9c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/typechecking.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/typechecking.go @@ -24,7 +24,7 @@ package v1beta1 // TypeChecking contains results of type checking the expressions in the // ValidatingAdmissionPolicy type TypeCheckingApplyConfiguration struct { - // The type checking warnings for each expression. + // expressionWarnings contains the type checking warnings for each expression. ExpressionWarnings []ExpressionWarningApplyConfiguration `json:"expressionWarnings,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicy.go index b8eba927d3a5d..bc1ad6473a92d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicy.go @@ -33,11 +33,11 @@ import ( // ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it. type ValidatingAdmissionPolicyApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the ValidatingAdmissionPolicy. + // spec defines the desired behavior of the ValidatingAdmissionPolicy. Spec *ValidatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"` - // The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy + // status represents the current status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy // behaves in the expected way. // Populated by the system. // Read-only. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybinding.go index db33d910c234e..fd658f9a459a5 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybinding.go @@ -43,9 +43,9 @@ import ( // given (policy, binding, param) combination is within its own CEL budget. type ValidatingAdmissionPolicyBindingApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Specification of the desired behavior of the ValidatingAdmissionPolicyBinding. + // spec defines the desired behavior of the ValidatingAdmissionPolicyBinding. Spec *ValidatingAdmissionPolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybindingspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybindingspec.go index 7b1c0af26ba88..874cb907b0704 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybindingspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicybindingspec.go @@ -27,7 +27,7 @@ import ( // // ValidatingAdmissionPolicyBindingSpec is the specification of the ValidatingAdmissionPolicyBinding. type ValidatingAdmissionPolicyBindingSpecApplyConfiguration struct { - // PolicyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. + // policyName references a ValidatingAdmissionPolicy name which the ValidatingAdmissionPolicyBinding binds to. // If the referenced resource does not exist, this binding is considered invalid and will be ignored // Required. PolicyName *string `json:"policyName,omitempty"` @@ -36,7 +36,7 @@ type ValidatingAdmissionPolicyBindingSpecApplyConfiguration struct { // If the policy specifies a ParamKind and the resource referred to by ParamRef does not exist, this binding is considered mis-configured and the FailurePolicy of the ValidatingAdmissionPolicy applied. // If the policy does not specify a ParamKind then this field is ignored, and the rules are evaluated without a param. ParamRef *ParamRefApplyConfiguration `json:"paramRef,omitempty"` - // MatchResources declares what resources match this binding and will be validated by it. + // matchResources declares what resources match this binding and will be validated by it. // Note that this is intersected with the policy's matchConstraints, so only requests that are matched by the policy can be selected by this. // If this is unset, all resources matched by the policy are validated by this binding // When resourceRules is unset, it does not constrain resource matching. If a resource is matched by the other fields of this object, it will be validated. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicyspec.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicyspec.go index 4600fb9da4ab2..1d599eabd4943 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicyspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicyspec.go @@ -27,18 +27,18 @@ import ( // // ValidatingAdmissionPolicySpec is the specification of the desired behavior of the AdmissionPolicy. type ValidatingAdmissionPolicySpecApplyConfiguration struct { - // ParamKind specifies the kind of resources used to parameterize this policy. + // paramKind specifies the kind of resources used to parameterize this policy. // If absent, there are no parameters for this policy and the param CEL variable will not be provided to validation expressions. // If ParamKind refers to a non-existent kind, this policy definition is mis-configured and the FailurePolicy is applied. // If paramKind is specified but paramRef is unset in ValidatingAdmissionPolicyBinding, the params variable will be null. ParamKind *ParamKindApplyConfiguration `json:"paramKind,omitempty"` - // MatchConstraints specifies what resources this policy is designed to validate. + // matchConstraints specifies what resources this policy is designed to validate. // The AdmissionPolicy cares about a request if it matches _all_ Constraints. // However, in order to prevent clusters from being put into an unstable state that cannot be recovered from via the API // ValidatingAdmissionPolicy cannot match ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. // Required. MatchConstraints *MatchResourcesApplyConfiguration `json:"matchConstraints,omitempty"` - // Validations contain CEL expressions which is used to apply the validation. + // validations contain CEL expressions which is used to apply the validation. // Validations and AuditAnnotations may not both be empty; a minimum of one Validations or AuditAnnotations is // required. Validations []ValidationApplyConfiguration `json:"validations,omitempty"` @@ -61,7 +61,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct { // validations and auditAnnotations may not both be empty; a least one of validations or auditAnnotations is // required. AuditAnnotations []AuditAnnotationApplyConfiguration `json:"auditAnnotations,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be validated. + // matchConditions is a list of conditions that must be met for a request to be validated. // Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. @@ -76,7 +76,7 @@ type ValidatingAdmissionPolicySpecApplyConfiguration struct { // - If failurePolicy=Fail, reject the request // - If failurePolicy=Ignore, the policy is skipped MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"` - // Variables contain definitions of variables that can be used in composition of other expressions. + // variables contain definitions of variables that can be used in composition of other expressions. // Each variable is defined as a named CEL expression. // The variables defined here will be available under `variables` in other expressions of the policy // except MatchConditions because MatchConditions are evaluated before the rest of the policy. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicystatus.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicystatus.go index 9e05da1395f36..573286c146af3 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicystatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingadmissionpolicystatus.go @@ -27,12 +27,12 @@ import ( // // ValidatingAdmissionPolicyStatus represents the status of an admission validation policy. type ValidatingAdmissionPolicyStatusApplyConfiguration struct { - // The generation observed by the controller. + // observedGeneration is the generation observed by the controller. ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - // The results of type checking for each expression. + // typeChecking contains the results of type checking for each expression. // Presence of this field indicates the completion of the type checking. TypeChecking *TypeCheckingApplyConfiguration `json:"typeChecking,omitempty"` - // The conditions represent the latest available observations of a policy's current state. + // conditions represent the latest available observations of a policy's current state. Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhook.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhook.go index c3df12b87b5fa..f50cb4668e08f 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhook.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhook.go @@ -29,23 +29,23 @@ import ( // // ValidatingWebhook describes an admission webhook and the resources and operations it applies to. type ValidatingWebhookApplyConfiguration struct { - // The name of the admission webhook. + // name is the name of the admission webhook. // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // "imagepolicy" is the name of the webhook, and kubernetes.io is the name // of the organization. // Required. Name *string `json:"name,omitempty"` - // ClientConfig defines how to communicate with the hook. + // clientConfig defines how to communicate with the hook. // Required ClientConfig *WebhookClientConfigApplyConfiguration `json:"clientConfig,omitempty"` - // Rules describes what operations on what resources/subresources the webhook cares about. + // rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks // from putting the cluster in a state which cannot be recovered from without completely // disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. Rules []v1.RuleWithOperationsApplyConfiguration `json:"rules,omitempty"` - // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // failurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. FailurePolicy *admissionregistrationv1beta1.FailurePolicyType `json:"failurePolicy,omitempty"` // matchPolicy defines how the "rules" list is used to match incoming requests. @@ -63,7 +63,7 @@ type ValidatingWebhookApplyConfiguration struct { // // Defaults to "Exact" MatchPolicy *admissionregistrationv1beta1.MatchPolicyType `json:"matchPolicy,omitempty"` - // NamespaceSelector decides whether to run the webhook on an object based + // namespaceSelector decides whether to run the webhook on an object based // on whether the namespace for that object matches the selector. If the // object itself is a namespace, the matching is performed on // object.metadata.labels. If the object is another cluster scoped resource, @@ -107,7 +107,7 @@ type ValidatingWebhookApplyConfiguration struct { // // Default to the empty LabelSelector, which matches everything. NamespaceSelector *metav1.LabelSelectorApplyConfiguration `json:"namespaceSelector,omitempty"` - // ObjectSelector decides whether to run the webhook based on if the + // objectSelector decides whether to run the webhook based on if the // object has matching labels. objectSelector is evaluated against both // the oldObject and newObject that would be sent to the webhook, and // is considered to match if either object matches the selector. A null @@ -119,20 +119,20 @@ type ValidatingWebhookApplyConfiguration struct { // users may skip the admission webhook by setting the labels. // Default to the empty LabelSelector, which matches everything. ObjectSelector *metav1.LabelSelectorApplyConfiguration `json:"objectSelector,omitempty"` - // SideEffects states whether this webhook has side effects. + // sideEffects states whether this webhook has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be // rejected by a future step in the admission chain and the side effects therefore need to be undone. // Requests with the dryRun attribute will be auto-rejected if they match a webhook with // sideEffects == Unknown or Some. Defaults to Unknown. SideEffects *admissionregistrationv1beta1.SideEffectClass `json:"sideEffects,omitempty"` - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, + // timeoutSeconds specifies the timeout for this webhook. After the timeout passes, // the webhook call will be ignored or the API call will fail based on the // failure policy. // The timeout value must be between 1 and 30 seconds. // Default to 30 seconds. TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // admissionReviewVersions is an ordered list of preferred `AdmissionReview` // versions the Webhook expects. API server will try to use first version in // the list which it supports. If none of the versions specified in this list // supported by API server, validation will fail for this object. @@ -141,7 +141,7 @@ type ValidatingWebhookApplyConfiguration struct { // and be subject to the failure policy. // Default to `['v1beta1']`. AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty"` - // MatchConditions is a list of conditions that must be met for a request to be sent to this + // matchConditions is a list of conditions that must be met for a request to be sent to this // webhook. Match conditions filter requests that have already been matched by the rules, // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. // There are a maximum of 64 match conditions allowed. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhookconfiguration.go index 66b74b31d8b00..dd53f3b334d9b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validatingwebhookconfiguration.go @@ -34,9 +34,9 @@ import ( // Deprecated in v1.16, planned for removal in v1.19. Use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration instead. type ValidatingWebhookConfigurationApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + // metadata is the standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Webhooks is a list of webhooks and the affected resources and operations. + // webhooks is a list of webhooks and the affected resources and operations. Webhooks []ValidatingWebhookApplyConfiguration `json:"webhooks,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validation.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validation.go index 6505b8b498349..47ccf0f3166c8 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validation.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/validation.go @@ -27,7 +27,7 @@ import ( // // Validation specifies the CEL expression which is used to apply the validation. type ValidationApplyConfiguration struct { - // Expression represents the expression which will be evaluated by CEL. + // expression represents the expression which will be evaluated by CEL. // ref: https://github.com/google/cel-spec // CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables: // @@ -69,7 +69,7 @@ type ValidationApplyConfiguration struct { // non-intersecting keys are appended, retaining their partial order. // Required. Expression *string `json:"expression,omitempty"` - // Message represents the message displayed when validation fails. The message is required if the Expression contains + // message represents the message displayed when validation fails. The message is required if the Expression contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" @@ -77,7 +77,7 @@ type ValidationApplyConfiguration struct { // The message must not contain line breaks. // If unset, the message is "failed Expression: {Expression}". Message *string `json:"message,omitempty"` - // Reason represents a machine-readable description of why this validation failed. + // reason represents a machine-readable description of why this validation failed. // If this is the first validation in the list to fail, this reason, as well as the // corresponding HTTP response code, are used in the // HTTP response to the client. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/variable.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/variable.go index db334f2265712..0419a986cae33 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/variable.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/variable.go @@ -23,11 +23,11 @@ package v1beta1 // // Variable is the definition of a variable that is used for composition. A variable is defined as a named expression. type VariableApplyConfiguration struct { - // Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + // name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. // The variable can be accessed in other expressions through `variables` // For example, if name is "foo", the variable will be available as `variables.foo` Name *string `json:"name,omitempty"` - // Expression is the expression that will be evaluated as the value of the variable. + // expression is the expression that will be evaluated as the value of the variable. // The CEL expression has access to the same identifiers as the CEL expressions in Validation. Expression *string `json:"expression,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/webhookclientconfig.go b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/webhookclientconfig.go index 593ff43ebfe6e..67109521569db 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/webhookclientconfig.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1/webhookclientconfig.go @@ -24,7 +24,7 @@ package v1beta1 // WebhookClientConfig contains the information to make a TLS // connection with the webhook type WebhookClientConfigApplyConfiguration struct { - // `url` gives the location of the webhook, in standard URL form + // url gives the location of the webhook, in standard URL form // (`scheme://host:port/path`). Exactly one of `url` or `service` // must be specified. // @@ -50,12 +50,12 @@ type WebhookClientConfigApplyConfiguration struct { // allowed. Fragments ("#...") and query parameters ("?...") are not // allowed, either. URL *string `json:"url,omitempty"` - // `service` is a reference to the service for this webhook. Either + // service is a reference to the service for this webhook. Either // `service` or `url` must be specified. // // If the webhook is running within the cluster, then you should use `service`. Service *ServiceReferenceApplyConfiguration `json:"service,omitempty"` - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. // If unspecified, system trust roots on the apiserver are used. CABundle []byte `json:"caBundle,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/serverstorageversion.go b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/serverstorageversion.go index 94379ddc5af46..5d6e1446fc22c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/serverstorageversion.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/serverstorageversion.go @@ -24,15 +24,16 @@ package v1alpha1 // An API server instance reports the version it can decode and the version it // encodes objects to when persisting objects in the backend. type ServerStorageVersionApplyConfiguration struct { - // The ID of the reporting API server. + // apiServerID is the ID of the reporting API server. APIServerID *string `json:"apiServerID,omitempty"` - // The API server encodes the object to this version when persisting it in + // encodingVersion the API server encodes the object to when persisting it in // the backend (e.g., etcd). EncodingVersion *string `json:"encodingVersion,omitempty"` + // decodableVersions are the encoding versions the API server can handle to decode. // The API server can decode objects encoded in these versions. // The encodingVersion must be included in the decodableVersions. DecodableVersions []string `json:"decodableVersions,omitempty"` - // The API server can serve these versions. + // servedVersions lists all versions the API server can serve. // DecodableVersions must include all ServedVersions. ServedVersions []string `json:"servedVersions,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversion.go b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversion.go index e9f41cc2773b5..46b2349b1db6a 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversion.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversion.go @@ -33,11 +33,12 @@ import ( // Storage version of a specific resource. type StorageVersionApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` + // metadata is the standard object metadata. // The name is .. *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Spec is an empty spec. It is here to comply with Kubernetes API style. + // spec is an empty spec. It is here to comply with Kubernetes API style. Spec *apiserverinternalv1alpha1.StorageVersionSpec `json:"spec,omitempty"` - // API server instances report the version they can decode and the version they + // status on the version the API server instance can decode from and // encode objects to when persisting objects in the backend. Status *StorageVersionStatusApplyConfiguration `json:"status,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversioncondition.go b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversioncondition.go index 84752a459969e..c78cd9affe818 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversioncondition.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversioncondition.go @@ -28,17 +28,17 @@ import ( // // Describes the state of the storageVersion at a certain point. type StorageVersionConditionApplyConfiguration struct { - // Type of the condition. + // type of the condition. Type *apiserverinternalv1alpha1.StorageVersionConditionType `json:"type,omitempty"` - // Status of the condition, one of True, False, Unknown. + // status of the condition, one of True, False, Unknown. Status *apiserverinternalv1alpha1.ConditionStatus `json:"status,omitempty"` - // If set, this represents the .metadata.generation that the condition was set based upon. + // observedGeneration represents the .metadata.generation that the condition was set based upon, if field is set. ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - // Last time the condition transitioned from one status to another. + // lastTransitionTime is the last time the condition transitioned from one status to another. LastTransitionTime *v1.Time `json:"lastTransitionTime,omitempty"` - // The reason for the condition's last transition. + // reason for the condition's last transition. Reason *string `json:"reason,omitempty"` - // A human readable message indicating details about the transition. + // message is a human readable string indicating details about the transition. Message *string `json:"message,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversionstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversionstatus.go index 00a71ad5a21f4..a24c66e24573f 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversionstatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1/storageversionstatus.go @@ -24,14 +24,15 @@ package v1alpha1 // API server instances report the versions they can decode and the version they // encode objects to when persisting objects in the backend. type StorageVersionStatusApplyConfiguration struct { - // The reported versions per API server instance. + // storageVersions lists the reported versions per API server instance. StorageVersions []ServerStorageVersionApplyConfiguration `json:"storageVersions,omitempty"` - // If all API server instances agree on the same encoding storage version, - // then this field is set to that version. Otherwise this field is left empty. + // commonEncodingVersion is set to an encoding storage version if all API server + // instances share that same version. If they don't share one storage version, this + // field is left empty. // API servers should finish updating its storageVersionStatus entry before // serving write operations, so that this field will be in sync with the reality. CommonEncodingVersion *string `json:"commonEncodingVersion,omitempty"` - // The latest available observations of the storageVersion's state. + // conditions lists the latest available observations of the storageVersion's state. Conditions []StorageVersionConditionApplyConfiguration `json:"conditions,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/containerresourcemetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/containerresourcemetricsource.go deleted file mode 100644 index 392e491cf9cba..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/containerresourcemetricsource.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" -) - -// ContainerResourceMetricSourceApplyConfiguration represents a declarative configuration of the ContainerResourceMetricSource type for use -// with apply. -// -// ContainerResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ContainerResourceMetricSourceApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // targetAverageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - TargetAverageUtilization *int32 `json:"targetAverageUtilization,omitempty"` - // targetAverageValue is the target value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty"` - // container is the name of the container in the pods of the scaling target - Container *string `json:"container,omitempty"` -} - -// ContainerResourceMetricSourceApplyConfiguration constructs a declarative configuration of the ContainerResourceMetricSource type for use with -// apply. -func ContainerResourceMetricSource() *ContainerResourceMetricSourceApplyConfiguration { - return &ContainerResourceMetricSourceApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithName(value v1.ResourceName) *ContainerResourceMetricSourceApplyConfiguration { - b.Name = &value - return b -} - -// WithTargetAverageUtilization sets the TargetAverageUtilization field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetAverageUtilization field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithTargetAverageUtilization(value int32) *ContainerResourceMetricSourceApplyConfiguration { - b.TargetAverageUtilization = &value - return b -} - -// WithTargetAverageValue sets the TargetAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetAverageValue field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithTargetAverageValue(value resource.Quantity) *ContainerResourceMetricSourceApplyConfiguration { - b.TargetAverageValue = &value - return b -} - -// WithContainer sets the Container field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Container field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithContainer(value string) *ContainerResourceMetricSourceApplyConfiguration { - b.Container = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/containerresourcemetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/containerresourcemetricstatus.go deleted file mode 100644 index e69d2bad8ac47..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/containerresourcemetricstatus.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" -) - -// ContainerResourceMetricStatusApplyConfiguration represents a declarative configuration of the ContainerResourceMetricStatus type for use -// with apply. -// -// ContainerResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing a single container in each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ContainerResourceMetricStatusApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // currentAverageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. It will only be - // present if `targetAverageValue` was set in the corresponding metric - // specification. - CurrentAverageUtilization *int32 `json:"currentAverageUtilization,omitempty"` - // currentAverageValue is the current value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // It will always be set, regardless of the corresponding metric specification. - CurrentAverageValue *resource.Quantity `json:"currentAverageValue,omitempty"` - // container is the name of the container in the pods of the scaling target - Container *string `json:"container,omitempty"` -} - -// ContainerResourceMetricStatusApplyConfiguration constructs a declarative configuration of the ContainerResourceMetricStatus type for use with -// apply. -func ContainerResourceMetricStatus() *ContainerResourceMetricStatusApplyConfiguration { - return &ContainerResourceMetricStatusApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithName(value v1.ResourceName) *ContainerResourceMetricStatusApplyConfiguration { - b.Name = &value - return b -} - -// WithCurrentAverageUtilization sets the CurrentAverageUtilization field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentAverageUtilization field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithCurrentAverageUtilization(value int32) *ContainerResourceMetricStatusApplyConfiguration { - b.CurrentAverageUtilization = &value - return b -} - -// WithCurrentAverageValue sets the CurrentAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentAverageValue field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithCurrentAverageValue(value resource.Quantity) *ContainerResourceMetricStatusApplyConfiguration { - b.CurrentAverageValue = &value - return b -} - -// WithContainer sets the Container field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Container field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithContainer(value string) *ContainerResourceMetricStatusApplyConfiguration { - b.Container = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/crossversionobjectreference.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/crossversionobjectreference.go deleted file mode 100644 index 40c8e450f58f1..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/crossversionobjectreference.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -// CrossVersionObjectReferenceApplyConfiguration represents a declarative configuration of the CrossVersionObjectReference type for use -// with apply. -// -// CrossVersionObjectReference contains enough information to let you identify the referred resource. -type CrossVersionObjectReferenceApplyConfiguration struct { - // Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - Kind *string `json:"kind,omitempty"` - // Name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - Name *string `json:"name,omitempty"` - // API version of the referent - APIVersion *string `json:"apiVersion,omitempty"` -} - -// CrossVersionObjectReferenceApplyConfiguration constructs a declarative configuration of the CrossVersionObjectReference type for use with -// apply. -func CrossVersionObjectReference() *CrossVersionObjectReferenceApplyConfiguration { - return &CrossVersionObjectReferenceApplyConfiguration{} -} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *CrossVersionObjectReferenceApplyConfiguration) WithKind(value string) *CrossVersionObjectReferenceApplyConfiguration { - b.Kind = &value - return b -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *CrossVersionObjectReferenceApplyConfiguration) WithName(value string) *CrossVersionObjectReferenceApplyConfiguration { - b.Name = &value - return b -} - -// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the APIVersion field is set to the value of the last call. -func (b *CrossVersionObjectReferenceApplyConfiguration) WithAPIVersion(value string) *CrossVersionObjectReferenceApplyConfiguration { - b.APIVersion = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/externalmetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/externalmetricsource.go deleted file mode 100644 index 3b072172574e5..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/externalmetricsource.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// ExternalMetricSourceApplyConfiguration represents a declarative configuration of the ExternalMetricSource type for use -// with apply. -// -// ExternalMetricSource indicates how to scale on a metric not associated with -// any Kubernetes object (for example length of queue in cloud -// messaging service, or QPS from loadbalancer running outside of cluster). -// Exactly one "target" type should be set. -type ExternalMetricSourceApplyConfiguration struct { - // metricName is the name of the metric in question. - MetricName *string `json:"metricName,omitempty"` - // metricSelector is used to identify a specific time series - // within a given metric. - MetricSelector *v1.LabelSelectorApplyConfiguration `json:"metricSelector,omitempty"` - // targetValue is the target value of the metric (as a quantity). - // Mutually exclusive with TargetAverageValue. - TargetValue *resource.Quantity `json:"targetValue,omitempty"` - // targetAverageValue is the target per-pod value of global metric (as a quantity). - // Mutually exclusive with TargetValue. - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty"` -} - -// ExternalMetricSourceApplyConfiguration constructs a declarative configuration of the ExternalMetricSource type for use with -// apply. -func ExternalMetricSource() *ExternalMetricSourceApplyConfiguration { - return &ExternalMetricSourceApplyConfiguration{} -} - -// WithMetricName sets the MetricName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricName field is set to the value of the last call. -func (b *ExternalMetricSourceApplyConfiguration) WithMetricName(value string) *ExternalMetricSourceApplyConfiguration { - b.MetricName = &value - return b -} - -// WithMetricSelector sets the MetricSelector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricSelector field is set to the value of the last call. -func (b *ExternalMetricSourceApplyConfiguration) WithMetricSelector(value *v1.LabelSelectorApplyConfiguration) *ExternalMetricSourceApplyConfiguration { - b.MetricSelector = value - return b -} - -// WithTargetValue sets the TargetValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetValue field is set to the value of the last call. -func (b *ExternalMetricSourceApplyConfiguration) WithTargetValue(value resource.Quantity) *ExternalMetricSourceApplyConfiguration { - b.TargetValue = &value - return b -} - -// WithTargetAverageValue sets the TargetAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetAverageValue field is set to the value of the last call. -func (b *ExternalMetricSourceApplyConfiguration) WithTargetAverageValue(value resource.Quantity) *ExternalMetricSourceApplyConfiguration { - b.TargetAverageValue = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/externalmetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/externalmetricstatus.go deleted file mode 100644 index c7e3629a9addf..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/externalmetricstatus.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// ExternalMetricStatusApplyConfiguration represents a declarative configuration of the ExternalMetricStatus type for use -// with apply. -// -// ExternalMetricStatus indicates the current value of a global metric -// not associated with any Kubernetes object. -type ExternalMetricStatusApplyConfiguration struct { - // metricName is the name of a metric used for autoscaling in - // metric system. - MetricName *string `json:"metricName,omitempty"` - // metricSelector is used to identify a specific time series - // within a given metric. - MetricSelector *v1.LabelSelectorApplyConfiguration `json:"metricSelector,omitempty"` - // currentValue is the current value of the metric (as a quantity) - CurrentValue *resource.Quantity `json:"currentValue,omitempty"` - // currentAverageValue is the current value of metric averaged over autoscaled pods. - CurrentAverageValue *resource.Quantity `json:"currentAverageValue,omitempty"` -} - -// ExternalMetricStatusApplyConfiguration constructs a declarative configuration of the ExternalMetricStatus type for use with -// apply. -func ExternalMetricStatus() *ExternalMetricStatusApplyConfiguration { - return &ExternalMetricStatusApplyConfiguration{} -} - -// WithMetricName sets the MetricName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricName field is set to the value of the last call. -func (b *ExternalMetricStatusApplyConfiguration) WithMetricName(value string) *ExternalMetricStatusApplyConfiguration { - b.MetricName = &value - return b -} - -// WithMetricSelector sets the MetricSelector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricSelector field is set to the value of the last call. -func (b *ExternalMetricStatusApplyConfiguration) WithMetricSelector(value *v1.LabelSelectorApplyConfiguration) *ExternalMetricStatusApplyConfiguration { - b.MetricSelector = value - return b -} - -// WithCurrentValue sets the CurrentValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentValue field is set to the value of the last call. -func (b *ExternalMetricStatusApplyConfiguration) WithCurrentValue(value resource.Quantity) *ExternalMetricStatusApplyConfiguration { - b.CurrentValue = &value - return b -} - -// WithCurrentAverageValue sets the CurrentAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentAverageValue field is set to the value of the last call. -func (b *ExternalMetricStatusApplyConfiguration) WithCurrentAverageValue(value resource.Quantity) *ExternalMetricStatusApplyConfiguration { - b.CurrentAverageValue = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscaler.go deleted file mode 100644 index 8a89398f9cb11..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscaler.go +++ /dev/null @@ -1,296 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - managedfields "k8s.io/apimachinery/pkg/util/managedfields" - internal "k8s.io/client-go/applyconfigurations/internal" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// HorizontalPodAutoscalerApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscaler type for use -// with apply. -// -// HorizontalPodAutoscaler is the configuration for a horizontal pod -// autoscaler, which automatically manages the replica count of any resource -// implementing the scale subresource based on the metrics specified. -type HorizontalPodAutoscalerApplyConfiguration struct { - v1.TypeMetaApplyConfiguration `json:",inline"` - // metadata is the standard object metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // spec is the specification for the behaviour of the autoscaler. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - Spec *HorizontalPodAutoscalerSpecApplyConfiguration `json:"spec,omitempty"` - // status is the current information about the autoscaler. - Status *HorizontalPodAutoscalerStatusApplyConfiguration `json:"status,omitempty"` -} - -// HorizontalPodAutoscaler constructs a declarative configuration of the HorizontalPodAutoscaler type for use with -// apply. -func HorizontalPodAutoscaler(name, namespace string) *HorizontalPodAutoscalerApplyConfiguration { - b := &HorizontalPodAutoscalerApplyConfiguration{} - b.WithName(name) - b.WithNamespace(namespace) - b.WithKind("HorizontalPodAutoscaler") - b.WithAPIVersion("autoscaling/v2beta1") - return b -} - -// ExtractHorizontalPodAutoscalerFrom extracts the applied configuration owned by fieldManager from -// horizontalPodAutoscaler for the specified subresource. Pass an empty string for subresource to extract -// the main resource. Common subresources include "status", "scale", etc. -// horizontalPodAutoscaler must be a unmodified HorizontalPodAutoscaler API object that was retrieved from the Kubernetes API. -// ExtractHorizontalPodAutoscalerFrom provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractHorizontalPodAutoscalerFrom(horizontalPodAutoscaler *autoscalingv2beta1.HorizontalPodAutoscaler, fieldManager string, subresource string) (*HorizontalPodAutoscalerApplyConfiguration, error) { - b := &HorizontalPodAutoscalerApplyConfiguration{} - err := managedfields.ExtractInto(horizontalPodAutoscaler, internal.Parser().Type("io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscaler"), fieldManager, b, subresource) - if err != nil { - return nil, err - } - b.WithName(horizontalPodAutoscaler.Name) - b.WithNamespace(horizontalPodAutoscaler.Namespace) - - b.WithKind("HorizontalPodAutoscaler") - b.WithAPIVersion("autoscaling/v2beta1") - return b, nil -} - -// ExtractHorizontalPodAutoscaler extracts the applied configuration owned by fieldManager from -// horizontalPodAutoscaler. If no managedFields are found in horizontalPodAutoscaler for fieldManager, a -// HorizontalPodAutoscalerApplyConfiguration is returned with only the Name, Namespace (if applicable), -// APIVersion and Kind populated. It is possible that no managed fields were found for because other -// field managers have taken ownership of all the fields previously owned by fieldManager, or because -// the fieldManager never owned fields any fields. -// horizontalPodAutoscaler must be a unmodified HorizontalPodAutoscaler API object that was retrieved from the Kubernetes API. -// ExtractHorizontalPodAutoscaler provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractHorizontalPodAutoscaler(horizontalPodAutoscaler *autoscalingv2beta1.HorizontalPodAutoscaler, fieldManager string) (*HorizontalPodAutoscalerApplyConfiguration, error) { - return ExtractHorizontalPodAutoscalerFrom(horizontalPodAutoscaler, fieldManager, "") -} - -// ExtractHorizontalPodAutoscalerStatus extracts the applied configuration owned by fieldManager from -// horizontalPodAutoscaler for the status subresource. -func ExtractHorizontalPodAutoscalerStatus(horizontalPodAutoscaler *autoscalingv2beta1.HorizontalPodAutoscaler, fieldManager string) (*HorizontalPodAutoscalerApplyConfiguration, error) { - return ExtractHorizontalPodAutoscalerFrom(horizontalPodAutoscaler, fieldManager, "status") -} - -func (b HorizontalPodAutoscalerApplyConfiguration) IsApplyConfiguration() {} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithKind(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.TypeMetaApplyConfiguration.Kind = &value - return b -} - -// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the APIVersion field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithAPIVersion(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.TypeMetaApplyConfiguration.APIVersion = &value - return b -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithName(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Name = &value - return b -} - -// WithGenerateName sets the GenerateName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the GenerateName field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithGenerateName(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.GenerateName = &value - return b -} - -// WithNamespace sets the Namespace field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Namespace field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithNamespace(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Namespace = &value - return b -} - -// WithUID sets the UID field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the UID field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithUID(value types.UID) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.UID = &value - return b -} - -// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ResourceVersion field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithResourceVersion(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.ResourceVersion = &value - return b -} - -// WithGeneration sets the Generation field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Generation field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithGeneration(value int64) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Generation = &value - return b -} - -// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CreationTimestamp field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.CreationTimestamp = &value - return b -} - -// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionTimestamp field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value - return b -} - -// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value - return b -} - -// WithLabels puts the entries into the Labels field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Labels field, -// overwriting an existing map entries in Labels field with the same key. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithLabels(entries map[string]string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Labels[k] = v - } - return b -} - -// WithAnnotations puts the entries into the Annotations field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Annotations field, -// overwriting an existing map entries in Annotations field with the same key. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithAnnotations(entries map[string]string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Annotations[k] = v - } - return b -} - -// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the OwnerReferences field. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - if values[i] == nil { - panic("nil value passed to WithOwnerReferences") - } - b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) - } - return b -} - -// WithFinalizers adds the given value to the Finalizers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Finalizers field. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithFinalizers(values ...string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) - } - return b -} - -func (b *HorizontalPodAutoscalerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { - if b.ObjectMetaApplyConfiguration == nil { - b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} - } -} - -// WithSpec sets the Spec field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Spec field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithSpec(value *HorizontalPodAutoscalerSpecApplyConfiguration) *HorizontalPodAutoscalerApplyConfiguration { - b.Spec = value - return b -} - -// WithStatus sets the Status field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Status field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithStatus(value *HorizontalPodAutoscalerStatusApplyConfiguration) *HorizontalPodAutoscalerApplyConfiguration { - b.Status = value - return b -} - -// GetKind retrieves the value of the Kind field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetKind() *string { - return b.TypeMetaApplyConfiguration.Kind -} - -// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetAPIVersion() *string { - return b.TypeMetaApplyConfiguration.APIVersion -} - -// GetName retrieves the value of the Name field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetName() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Name -} - -// GetNamespace retrieves the value of the Namespace field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetNamespace() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Namespace -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalercondition.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalercondition.go deleted file mode 100644 index 302df35a73439..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalercondition.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// HorizontalPodAutoscalerConditionApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerCondition type for use -// with apply. -// -// HorizontalPodAutoscalerCondition describes the state of -// a HorizontalPodAutoscaler at a certain point. -type HorizontalPodAutoscalerConditionApplyConfiguration struct { - // type describes the current condition - Type *autoscalingv2beta1.HorizontalPodAutoscalerConditionType `json:"type,omitempty"` - // status is the status of the condition (True, False, Unknown) - Status *v1.ConditionStatus `json:"status,omitempty"` - // lastTransitionTime is the last time the condition transitioned from - // one status to another - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - // reason is the reason for the condition's last transition. - Reason *string `json:"reason,omitempty"` - // message is a human-readable explanation containing details about - // the transition - Message *string `json:"message,omitempty"` -} - -// HorizontalPodAutoscalerConditionApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerCondition type for use with -// apply. -func HorizontalPodAutoscalerCondition() *HorizontalPodAutoscalerConditionApplyConfiguration { - return &HorizontalPodAutoscalerConditionApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithType(value autoscalingv2beta1.HorizontalPodAutoscalerConditionType) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Type = &value - return b -} - -// WithStatus sets the Status field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Status field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithStatus(value v1.ConditionStatus) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Status = &value - return b -} - -// WithLastTransitionTime sets the LastTransitionTime field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the LastTransitionTime field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithLastTransitionTime(value metav1.Time) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.LastTransitionTime = &value - return b -} - -// WithReason sets the Reason field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Reason field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithReason(value string) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Reason = &value - return b -} - -// WithMessage sets the Message field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Message field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithMessage(value string) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Message = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalerspec.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalerspec.go deleted file mode 100644 index bbe6a8febff5a..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalerspec.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -// HorizontalPodAutoscalerSpecApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerSpec type for use -// with apply. -// -// HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. -type HorizontalPodAutoscalerSpecApplyConfiguration struct { - // scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - // should be collected, as well as to actually change the replica count. - ScaleTargetRef *CrossVersionObjectReferenceApplyConfiguration `json:"scaleTargetRef,omitempty"` - // minReplicas is the lower limit for the number of replicas to which the autoscaler - // can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - // alpha feature gate HPAScaleToZero is enabled and at least one Object or External - // metric is configured. Scaling is active as long as at least one metric value is - // available. - MinReplicas *int32 `json:"minReplicas,omitempty"` - // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - // It cannot be less that minReplicas. - MaxReplicas *int32 `json:"maxReplicas,omitempty"` - // metrics contains the specifications for which to use to calculate the - // desired replica count (the maximum replica count across all metrics will - // be used). The desired replica count is calculated multiplying the - // ratio between the target value and the current value by the current - // number of pods. Ergo, metrics used must decrease as the pod count is - // increased, and vice-versa. See the individual metric source types for - // more information about how each type of metric must respond. - Metrics []MetricSpecApplyConfiguration `json:"metrics,omitempty"` -} - -// HorizontalPodAutoscalerSpecApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerSpec type for use with -// apply. -func HorizontalPodAutoscalerSpec() *HorizontalPodAutoscalerSpecApplyConfiguration { - return &HorizontalPodAutoscalerSpecApplyConfiguration{} -} - -// WithScaleTargetRef sets the ScaleTargetRef field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ScaleTargetRef field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithScaleTargetRef(value *CrossVersionObjectReferenceApplyConfiguration) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.ScaleTargetRef = value - return b -} - -// WithMinReplicas sets the MinReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MinReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithMinReplicas(value int32) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.MinReplicas = &value - return b -} - -// WithMaxReplicas sets the MaxReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithMaxReplicas(value int32) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.MaxReplicas = &value - return b -} - -// WithMetrics adds the given value to the Metrics field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Metrics field. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithMetrics(values ...*MetricSpecApplyConfiguration) *HorizontalPodAutoscalerSpecApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithMetrics") - } - b.Metrics = append(b.Metrics, *values[i]) - } - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalerstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalerstatus.go deleted file mode 100644 index 831ad5cf6f695..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/horizontalpodautoscalerstatus.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// HorizontalPodAutoscalerStatusApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerStatus type for use -// with apply. -// -// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler. -type HorizontalPodAutoscalerStatusApplyConfiguration struct { - // observedGeneration is the most recent generation observed by this autoscaler. - ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - // lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, - // used by the autoscaler to control how often the number of pods is changed. - LastScaleTime *v1.Time `json:"lastScaleTime,omitempty"` - // currentReplicas is current number of replicas of pods managed by this autoscaler, - // as last seen by the autoscaler. - CurrentReplicas *int32 `json:"currentReplicas,omitempty"` - // desiredReplicas is the desired number of replicas of pods managed by this autoscaler, - // as last calculated by the autoscaler. - DesiredReplicas *int32 `json:"desiredReplicas,omitempty"` - // currentMetrics is the last read state of the metrics used by this autoscaler. - CurrentMetrics []MetricStatusApplyConfiguration `json:"currentMetrics,omitempty"` - // conditions is the set of conditions required for this autoscaler to scale its target, - // and indicates whether or not those conditions are met. - Conditions []HorizontalPodAutoscalerConditionApplyConfiguration `json:"conditions,omitempty"` -} - -// HorizontalPodAutoscalerStatusApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerStatus type for use with -// apply. -func HorizontalPodAutoscalerStatus() *HorizontalPodAutoscalerStatusApplyConfiguration { - return &HorizontalPodAutoscalerStatusApplyConfiguration{} -} - -// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ObservedGeneration field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithObservedGeneration(value int64) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.ObservedGeneration = &value - return b -} - -// WithLastScaleTime sets the LastScaleTime field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the LastScaleTime field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithLastScaleTime(value v1.Time) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.LastScaleTime = &value - return b -} - -// WithCurrentReplicas sets the CurrentReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithCurrentReplicas(value int32) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.CurrentReplicas = &value - return b -} - -// WithDesiredReplicas sets the DesiredReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DesiredReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithDesiredReplicas(value int32) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.DesiredReplicas = &value - return b -} - -// WithCurrentMetrics adds the given value to the CurrentMetrics field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the CurrentMetrics field. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithCurrentMetrics(values ...*MetricStatusApplyConfiguration) *HorizontalPodAutoscalerStatusApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithCurrentMetrics") - } - b.CurrentMetrics = append(b.CurrentMetrics, *values[i]) - } - return b -} - -// WithConditions adds the given value to the Conditions field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Conditions field. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithConditions(values ...*HorizontalPodAutoscalerConditionApplyConfiguration) *HorizontalPodAutoscalerStatusApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithConditions") - } - b.Conditions = append(b.Conditions, *values[i]) - } - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/metricspec.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/metricspec.go deleted file mode 100644 index d48043c32234b..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/metricspec.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" -) - -// MetricSpecApplyConfiguration represents a declarative configuration of the MetricSpec type for use -// with apply. -// -// MetricSpec specifies how to scale based on a single metric -// (only `type` and one other matching field should be set at once). -type MetricSpecApplyConfiguration struct { - // type is the type of metric source. It should be one of "ContainerResource", - // "External", "Object", "Pods" or "Resource", each mapping to a matching field in the object. - Type *autoscalingv2beta1.MetricSourceType `json:"type,omitempty"` - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - Object *ObjectMetricSourceApplyConfiguration `json:"object,omitempty"` - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - Pods *PodsMetricSourceApplyConfiguration `json:"pods,omitempty"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - Resource *ResourceMetricSourceApplyConfiguration `json:"resource,omitempty"` - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in - // each pod of the current scale target (e.g. CPU or memory). Such metrics are - // built in to Kubernetes, and have special scaling options on top of those - // available to normal per-pod metrics using the "pods" source. - ContainerResource *ContainerResourceMetricSourceApplyConfiguration `json:"containerResource,omitempty"` - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - External *ExternalMetricSourceApplyConfiguration `json:"external,omitempty"` -} - -// MetricSpecApplyConfiguration constructs a declarative configuration of the MetricSpec type for use with -// apply. -func MetricSpec() *MetricSpecApplyConfiguration { - return &MetricSpecApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithType(value autoscalingv2beta1.MetricSourceType) *MetricSpecApplyConfiguration { - b.Type = &value - return b -} - -// WithObject sets the Object field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Object field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithObject(value *ObjectMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.Object = value - return b -} - -// WithPods sets the Pods field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Pods field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithPods(value *PodsMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.Pods = value - return b -} - -// WithResource sets the Resource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Resource field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithResource(value *ResourceMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.Resource = value - return b -} - -// WithContainerResource sets the ContainerResource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ContainerResource field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithContainerResource(value *ContainerResourceMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.ContainerResource = value - return b -} - -// WithExternal sets the External field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the External field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithExternal(value *ExternalMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.External = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/metricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/metricstatus.go deleted file mode 100644 index 7ec38582de84b..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/metricstatus.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" -) - -// MetricStatusApplyConfiguration represents a declarative configuration of the MetricStatus type for use -// with apply. -// -// MetricStatus describes the last-read state of a single metric. -type MetricStatusApplyConfiguration struct { - // type is the type of metric source. It will be one of "ContainerResource", - // "External", "Object", "Pods" or "Resource", each corresponds to a matching field in the object. - Type *autoscalingv2beta1.MetricSourceType `json:"type,omitempty"` - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - Object *ObjectMetricStatusApplyConfiguration `json:"object,omitempty"` - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - Pods *PodsMetricStatusApplyConfiguration `json:"pods,omitempty"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - Resource *ResourceMetricStatusApplyConfiguration `json:"resource,omitempty"` - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - ContainerResource *ContainerResourceMetricStatusApplyConfiguration `json:"containerResource,omitempty"` - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - External *ExternalMetricStatusApplyConfiguration `json:"external,omitempty"` -} - -// MetricStatusApplyConfiguration constructs a declarative configuration of the MetricStatus type for use with -// apply. -func MetricStatus() *MetricStatusApplyConfiguration { - return &MetricStatusApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithType(value autoscalingv2beta1.MetricSourceType) *MetricStatusApplyConfiguration { - b.Type = &value - return b -} - -// WithObject sets the Object field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Object field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithObject(value *ObjectMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.Object = value - return b -} - -// WithPods sets the Pods field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Pods field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithPods(value *PodsMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.Pods = value - return b -} - -// WithResource sets the Resource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Resource field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithResource(value *ResourceMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.Resource = value - return b -} - -// WithContainerResource sets the ContainerResource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ContainerResource field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithContainerResource(value *ContainerResourceMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.ContainerResource = value - return b -} - -// WithExternal sets the External field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the External field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithExternal(value *ExternalMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.External = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/objectmetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/objectmetricsource.go deleted file mode 100644 index 103280e26839c..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/objectmetricsource.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// ObjectMetricSourceApplyConfiguration represents a declarative configuration of the ObjectMetricSource type for use -// with apply. -// -// ObjectMetricSource indicates how to scale on a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricSourceApplyConfiguration struct { - // target is the described Kubernetes object. - Target *CrossVersionObjectReferenceApplyConfiguration `json:"target,omitempty"` - // metricName is the name of the metric in question. - MetricName *string `json:"metricName,omitempty"` - // targetValue is the target value of the metric (as a quantity). - TargetValue *resource.Quantity `json:"targetValue,omitempty"` - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping - // When unset, just the metricName will be used to gather metrics. - Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` - // averageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - AverageValue *resource.Quantity `json:"averageValue,omitempty"` -} - -// ObjectMetricSourceApplyConfiguration constructs a declarative configuration of the ObjectMetricSource type for use with -// apply. -func ObjectMetricSource() *ObjectMetricSourceApplyConfiguration { - return &ObjectMetricSourceApplyConfiguration{} -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithTarget(value *CrossVersionObjectReferenceApplyConfiguration) *ObjectMetricSourceApplyConfiguration { - b.Target = value - return b -} - -// WithMetricName sets the MetricName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricName field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithMetricName(value string) *ObjectMetricSourceApplyConfiguration { - b.MetricName = &value - return b -} - -// WithTargetValue sets the TargetValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetValue field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithTargetValue(value resource.Quantity) *ObjectMetricSourceApplyConfiguration { - b.TargetValue = &value - return b -} - -// WithSelector sets the Selector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Selector field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithSelector(value *v1.LabelSelectorApplyConfiguration) *ObjectMetricSourceApplyConfiguration { - b.Selector = value - return b -} - -// WithAverageValue sets the AverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AverageValue field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithAverageValue(value resource.Quantity) *ObjectMetricSourceApplyConfiguration { - b.AverageValue = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/objectmetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/objectmetricstatus.go deleted file mode 100644 index b58d0e28dbc19..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/objectmetricstatus.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// ObjectMetricStatusApplyConfiguration represents a declarative configuration of the ObjectMetricStatus type for use -// with apply. -// -// ObjectMetricStatus indicates the current value of a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricStatusApplyConfiguration struct { - // target is the described Kubernetes object. - Target *CrossVersionObjectReferenceApplyConfiguration `json:"target,omitempty"` - // metricName is the name of the metric in question. - MetricName *string `json:"metricName,omitempty"` - // currentValue is the current value of the metric (as a quantity). - CurrentValue *resource.Quantity `json:"currentValue,omitempty"` - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set in the ObjectMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` - // averageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - AverageValue *resource.Quantity `json:"averageValue,omitempty"` -} - -// ObjectMetricStatusApplyConfiguration constructs a declarative configuration of the ObjectMetricStatus type for use with -// apply. -func ObjectMetricStatus() *ObjectMetricStatusApplyConfiguration { - return &ObjectMetricStatusApplyConfiguration{} -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithTarget(value *CrossVersionObjectReferenceApplyConfiguration) *ObjectMetricStatusApplyConfiguration { - b.Target = value - return b -} - -// WithMetricName sets the MetricName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricName field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithMetricName(value string) *ObjectMetricStatusApplyConfiguration { - b.MetricName = &value - return b -} - -// WithCurrentValue sets the CurrentValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentValue field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithCurrentValue(value resource.Quantity) *ObjectMetricStatusApplyConfiguration { - b.CurrentValue = &value - return b -} - -// WithSelector sets the Selector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Selector field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithSelector(value *v1.LabelSelectorApplyConfiguration) *ObjectMetricStatusApplyConfiguration { - b.Selector = value - return b -} - -// WithAverageValue sets the AverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AverageValue field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithAverageValue(value resource.Quantity) *ObjectMetricStatusApplyConfiguration { - b.AverageValue = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/podsmetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/podsmetricsource.go deleted file mode 100644 index 47ceaee50b6a3..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/podsmetricsource.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// PodsMetricSourceApplyConfiguration represents a declarative configuration of the PodsMetricSource type for use -// with apply. -// -// PodsMetricSource indicates how to scale on a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -// The values will be averaged together before being compared to the target -// value. -type PodsMetricSourceApplyConfiguration struct { - // metricName is the name of the metric in question - MetricName *string `json:"metricName,omitempty"` - // targetAverageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty"` - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping - // When unset, just the metricName will be used to gather metrics. - Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` -} - -// PodsMetricSourceApplyConfiguration constructs a declarative configuration of the PodsMetricSource type for use with -// apply. -func PodsMetricSource() *PodsMetricSourceApplyConfiguration { - return &PodsMetricSourceApplyConfiguration{} -} - -// WithMetricName sets the MetricName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricName field is set to the value of the last call. -func (b *PodsMetricSourceApplyConfiguration) WithMetricName(value string) *PodsMetricSourceApplyConfiguration { - b.MetricName = &value - return b -} - -// WithTargetAverageValue sets the TargetAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetAverageValue field is set to the value of the last call. -func (b *PodsMetricSourceApplyConfiguration) WithTargetAverageValue(value resource.Quantity) *PodsMetricSourceApplyConfiguration { - b.TargetAverageValue = &value - return b -} - -// WithSelector sets the Selector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Selector field is set to the value of the last call. -func (b *PodsMetricSourceApplyConfiguration) WithSelector(value *v1.LabelSelectorApplyConfiguration) *PodsMetricSourceApplyConfiguration { - b.Selector = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/podsmetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/podsmetricstatus.go deleted file mode 100644 index 140f0a22925bd..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/podsmetricstatus.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// PodsMetricStatusApplyConfiguration represents a declarative configuration of the PodsMetricStatus type for use -// with apply. -// -// PodsMetricStatus indicates the current value of a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -type PodsMetricStatusApplyConfiguration struct { - // metricName is the name of the metric in question - MetricName *string `json:"metricName,omitempty"` - // currentAverageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - CurrentAverageValue *resource.Quantity `json:"currentAverageValue,omitempty"` - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set in the PodsMetricSource, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` -} - -// PodsMetricStatusApplyConfiguration constructs a declarative configuration of the PodsMetricStatus type for use with -// apply. -func PodsMetricStatus() *PodsMetricStatusApplyConfiguration { - return &PodsMetricStatusApplyConfiguration{} -} - -// WithMetricName sets the MetricName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MetricName field is set to the value of the last call. -func (b *PodsMetricStatusApplyConfiguration) WithMetricName(value string) *PodsMetricStatusApplyConfiguration { - b.MetricName = &value - return b -} - -// WithCurrentAverageValue sets the CurrentAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentAverageValue field is set to the value of the last call. -func (b *PodsMetricStatusApplyConfiguration) WithCurrentAverageValue(value resource.Quantity) *PodsMetricStatusApplyConfiguration { - b.CurrentAverageValue = &value - return b -} - -// WithSelector sets the Selector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Selector field is set to the value of the last call. -func (b *PodsMetricStatusApplyConfiguration) WithSelector(value *v1.LabelSelectorApplyConfiguration) *PodsMetricStatusApplyConfiguration { - b.Selector = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/resourcemetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/resourcemetricsource.go deleted file mode 100644 index 0c3c849e3c81d..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/resourcemetricsource.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" -) - -// ResourceMetricSourceApplyConfiguration represents a declarative configuration of the ResourceMetricSource type for use -// with apply. -// -// ResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ResourceMetricSourceApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // targetAverageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - TargetAverageUtilization *int32 `json:"targetAverageUtilization,omitempty"` - // targetAverageValue is the target value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - TargetAverageValue *resource.Quantity `json:"targetAverageValue,omitempty"` -} - -// ResourceMetricSourceApplyConfiguration constructs a declarative configuration of the ResourceMetricSource type for use with -// apply. -func ResourceMetricSource() *ResourceMetricSourceApplyConfiguration { - return &ResourceMetricSourceApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ResourceMetricSourceApplyConfiguration) WithName(value v1.ResourceName) *ResourceMetricSourceApplyConfiguration { - b.Name = &value - return b -} - -// WithTargetAverageUtilization sets the TargetAverageUtilization field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetAverageUtilization field is set to the value of the last call. -func (b *ResourceMetricSourceApplyConfiguration) WithTargetAverageUtilization(value int32) *ResourceMetricSourceApplyConfiguration { - b.TargetAverageUtilization = &value - return b -} - -// WithTargetAverageValue sets the TargetAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TargetAverageValue field is set to the value of the last call. -func (b *ResourceMetricSourceApplyConfiguration) WithTargetAverageValue(value resource.Quantity) *ResourceMetricSourceApplyConfiguration { - b.TargetAverageValue = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/resourcemetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/resourcemetricstatus.go deleted file mode 100644 index 61db2eae0dd87..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta1/resourcemetricstatus.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta1 - -import ( - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" -) - -// ResourceMetricStatusApplyConfiguration represents a declarative configuration of the ResourceMetricStatus type for use -// with apply. -// -// ResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ResourceMetricStatusApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // currentAverageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. It will only be - // present if `targetAverageValue` was set in the corresponding metric - // specification. - CurrentAverageUtilization *int32 `json:"currentAverageUtilization,omitempty"` - // currentAverageValue is the current value of the average of the - // resource metric across all relevant pods, as a raw value (instead of as - // a percentage of the request), similar to the "pods" metric source type. - // It will always be set, regardless of the corresponding metric specification. - CurrentAverageValue *resource.Quantity `json:"currentAverageValue,omitempty"` -} - -// ResourceMetricStatusApplyConfiguration constructs a declarative configuration of the ResourceMetricStatus type for use with -// apply. -func ResourceMetricStatus() *ResourceMetricStatusApplyConfiguration { - return &ResourceMetricStatusApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ResourceMetricStatusApplyConfiguration) WithName(value v1.ResourceName) *ResourceMetricStatusApplyConfiguration { - b.Name = &value - return b -} - -// WithCurrentAverageUtilization sets the CurrentAverageUtilization field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentAverageUtilization field is set to the value of the last call. -func (b *ResourceMetricStatusApplyConfiguration) WithCurrentAverageUtilization(value int32) *ResourceMetricStatusApplyConfiguration { - b.CurrentAverageUtilization = &value - return b -} - -// WithCurrentAverageValue sets the CurrentAverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentAverageValue field is set to the value of the last call. -func (b *ResourceMetricStatusApplyConfiguration) WithCurrentAverageValue(value resource.Quantity) *ResourceMetricStatusApplyConfiguration { - b.CurrentAverageValue = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/containerresourcemetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/containerresourcemetricsource.go deleted file mode 100644 index babb8e8bb0ab0..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/containerresourcemetricsource.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/api/core/v1" -) - -// ContainerResourceMetricSourceApplyConfiguration represents a declarative configuration of the ContainerResourceMetricSource type for use -// with apply. -// -// ContainerResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ContainerResourceMetricSourceApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // target specifies the target value for the given metric - Target *MetricTargetApplyConfiguration `json:"target,omitempty"` - // container is the name of the container in the pods of the scaling target - Container *string `json:"container,omitempty"` -} - -// ContainerResourceMetricSourceApplyConfiguration constructs a declarative configuration of the ContainerResourceMetricSource type for use with -// apply. -func ContainerResourceMetricSource() *ContainerResourceMetricSourceApplyConfiguration { - return &ContainerResourceMetricSourceApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithName(value v1.ResourceName) *ContainerResourceMetricSourceApplyConfiguration { - b.Name = &value - return b -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithTarget(value *MetricTargetApplyConfiguration) *ContainerResourceMetricSourceApplyConfiguration { - b.Target = value - return b -} - -// WithContainer sets the Container field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Container field is set to the value of the last call. -func (b *ContainerResourceMetricSourceApplyConfiguration) WithContainer(value string) *ContainerResourceMetricSourceApplyConfiguration { - b.Container = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/containerresourcemetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/containerresourcemetricstatus.go deleted file mode 100644 index 771e9fdfcf3f3..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/containerresourcemetricstatus.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/api/core/v1" -) - -// ContainerResourceMetricStatusApplyConfiguration represents a declarative configuration of the ContainerResourceMetricStatus type for use -// with apply. -// -// ContainerResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing a single container in each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ContainerResourceMetricStatusApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // current contains the current value for the given metric - Current *MetricValueStatusApplyConfiguration `json:"current,omitempty"` - // container is the name of the container in the pods of the scaling target - Container *string `json:"container,omitempty"` -} - -// ContainerResourceMetricStatusApplyConfiguration constructs a declarative configuration of the ContainerResourceMetricStatus type for use with -// apply. -func ContainerResourceMetricStatus() *ContainerResourceMetricStatusApplyConfiguration { - return &ContainerResourceMetricStatusApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithName(value v1.ResourceName) *ContainerResourceMetricStatusApplyConfiguration { - b.Name = &value - return b -} - -// WithCurrent sets the Current field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Current field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithCurrent(value *MetricValueStatusApplyConfiguration) *ContainerResourceMetricStatusApplyConfiguration { - b.Current = value - return b -} - -// WithContainer sets the Container field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Container field is set to the value of the last call. -func (b *ContainerResourceMetricStatusApplyConfiguration) WithContainer(value string) *ContainerResourceMetricStatusApplyConfiguration { - b.Container = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/crossversionobjectreference.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/crossversionobjectreference.go deleted file mode 100644 index df6708a19d416..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/crossversionobjectreference.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// CrossVersionObjectReferenceApplyConfiguration represents a declarative configuration of the CrossVersionObjectReference type for use -// with apply. -// -// CrossVersionObjectReference contains enough information to let you identify the referred resource. -type CrossVersionObjectReferenceApplyConfiguration struct { - // kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - Kind *string `json:"kind,omitempty"` - // name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - Name *string `json:"name,omitempty"` - // apiVersion is the API version of the referent - APIVersion *string `json:"apiVersion,omitempty"` -} - -// CrossVersionObjectReferenceApplyConfiguration constructs a declarative configuration of the CrossVersionObjectReference type for use with -// apply. -func CrossVersionObjectReference() *CrossVersionObjectReferenceApplyConfiguration { - return &CrossVersionObjectReferenceApplyConfiguration{} -} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *CrossVersionObjectReferenceApplyConfiguration) WithKind(value string) *CrossVersionObjectReferenceApplyConfiguration { - b.Kind = &value - return b -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *CrossVersionObjectReferenceApplyConfiguration) WithName(value string) *CrossVersionObjectReferenceApplyConfiguration { - b.Name = &value - return b -} - -// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the APIVersion field is set to the value of the last call. -func (b *CrossVersionObjectReferenceApplyConfiguration) WithAPIVersion(value string) *CrossVersionObjectReferenceApplyConfiguration { - b.APIVersion = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/externalmetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/externalmetricsource.go deleted file mode 100644 index 6baca7f05d75b..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/externalmetricsource.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// ExternalMetricSourceApplyConfiguration represents a declarative configuration of the ExternalMetricSource type for use -// with apply. -// -// ExternalMetricSource indicates how to scale on a metric not associated with -// any Kubernetes object (for example length of queue in cloud -// messaging service, or QPS from loadbalancer running outside of cluster). -type ExternalMetricSourceApplyConfiguration struct { - // metric identifies the target metric by name and selector - Metric *MetricIdentifierApplyConfiguration `json:"metric,omitempty"` - // target specifies the target value for the given metric - Target *MetricTargetApplyConfiguration `json:"target,omitempty"` -} - -// ExternalMetricSourceApplyConfiguration constructs a declarative configuration of the ExternalMetricSource type for use with -// apply. -func ExternalMetricSource() *ExternalMetricSourceApplyConfiguration { - return &ExternalMetricSourceApplyConfiguration{} -} - -// WithMetric sets the Metric field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Metric field is set to the value of the last call. -func (b *ExternalMetricSourceApplyConfiguration) WithMetric(value *MetricIdentifierApplyConfiguration) *ExternalMetricSourceApplyConfiguration { - b.Metric = value - return b -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *ExternalMetricSourceApplyConfiguration) WithTarget(value *MetricTargetApplyConfiguration) *ExternalMetricSourceApplyConfiguration { - b.Target = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/externalmetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/externalmetricstatus.go deleted file mode 100644 index 725f56354479b..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/externalmetricstatus.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// ExternalMetricStatusApplyConfiguration represents a declarative configuration of the ExternalMetricStatus type for use -// with apply. -// -// ExternalMetricStatus indicates the current value of a global metric -// not associated with any Kubernetes object. -type ExternalMetricStatusApplyConfiguration struct { - // metric identifies the target metric by name and selector - Metric *MetricIdentifierApplyConfiguration `json:"metric,omitempty"` - // current contains the current value for the given metric - Current *MetricValueStatusApplyConfiguration `json:"current,omitempty"` -} - -// ExternalMetricStatusApplyConfiguration constructs a declarative configuration of the ExternalMetricStatus type for use with -// apply. -func ExternalMetricStatus() *ExternalMetricStatusApplyConfiguration { - return &ExternalMetricStatusApplyConfiguration{} -} - -// WithMetric sets the Metric field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Metric field is set to the value of the last call. -func (b *ExternalMetricStatusApplyConfiguration) WithMetric(value *MetricIdentifierApplyConfiguration) *ExternalMetricStatusApplyConfiguration { - b.Metric = value - return b -} - -// WithCurrent sets the Current field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Current field is set to the value of the last call. -func (b *ExternalMetricStatusApplyConfiguration) WithCurrent(value *MetricValueStatusApplyConfiguration) *ExternalMetricStatusApplyConfiguration { - b.Current = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscaler.go deleted file mode 100644 index 9a4588bc8c8b6..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscaler.go +++ /dev/null @@ -1,296 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - managedfields "k8s.io/apimachinery/pkg/util/managedfields" - internal "k8s.io/client-go/applyconfigurations/internal" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// HorizontalPodAutoscalerApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscaler type for use -// with apply. -// -// HorizontalPodAutoscaler is the configuration for a horizontal pod -// autoscaler, which automatically manages the replica count of any resource -// implementing the scale subresource based on the metrics specified. -type HorizontalPodAutoscalerApplyConfiguration struct { - v1.TypeMetaApplyConfiguration `json:",inline"` - // metadata is the standard object metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // spec is the specification for the behaviour of the autoscaler. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status. - Spec *HorizontalPodAutoscalerSpecApplyConfiguration `json:"spec,omitempty"` - // status is the current information about the autoscaler. - Status *HorizontalPodAutoscalerStatusApplyConfiguration `json:"status,omitempty"` -} - -// HorizontalPodAutoscaler constructs a declarative configuration of the HorizontalPodAutoscaler type for use with -// apply. -func HorizontalPodAutoscaler(name, namespace string) *HorizontalPodAutoscalerApplyConfiguration { - b := &HorizontalPodAutoscalerApplyConfiguration{} - b.WithName(name) - b.WithNamespace(namespace) - b.WithKind("HorizontalPodAutoscaler") - b.WithAPIVersion("autoscaling/v2beta2") - return b -} - -// ExtractHorizontalPodAutoscalerFrom extracts the applied configuration owned by fieldManager from -// horizontalPodAutoscaler for the specified subresource. Pass an empty string for subresource to extract -// the main resource. Common subresources include "status", "scale", etc. -// horizontalPodAutoscaler must be a unmodified HorizontalPodAutoscaler API object that was retrieved from the Kubernetes API. -// ExtractHorizontalPodAutoscalerFrom provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractHorizontalPodAutoscalerFrom(horizontalPodAutoscaler *autoscalingv2beta2.HorizontalPodAutoscaler, fieldManager string, subresource string) (*HorizontalPodAutoscalerApplyConfiguration, error) { - b := &HorizontalPodAutoscalerApplyConfiguration{} - err := managedfields.ExtractInto(horizontalPodAutoscaler, internal.Parser().Type("io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscaler"), fieldManager, b, subresource) - if err != nil { - return nil, err - } - b.WithName(horizontalPodAutoscaler.Name) - b.WithNamespace(horizontalPodAutoscaler.Namespace) - - b.WithKind("HorizontalPodAutoscaler") - b.WithAPIVersion("autoscaling/v2beta2") - return b, nil -} - -// ExtractHorizontalPodAutoscaler extracts the applied configuration owned by fieldManager from -// horizontalPodAutoscaler. If no managedFields are found in horizontalPodAutoscaler for fieldManager, a -// HorizontalPodAutoscalerApplyConfiguration is returned with only the Name, Namespace (if applicable), -// APIVersion and Kind populated. It is possible that no managed fields were found for because other -// field managers have taken ownership of all the fields previously owned by fieldManager, or because -// the fieldManager never owned fields any fields. -// horizontalPodAutoscaler must be a unmodified HorizontalPodAutoscaler API object that was retrieved from the Kubernetes API. -// ExtractHorizontalPodAutoscaler provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractHorizontalPodAutoscaler(horizontalPodAutoscaler *autoscalingv2beta2.HorizontalPodAutoscaler, fieldManager string) (*HorizontalPodAutoscalerApplyConfiguration, error) { - return ExtractHorizontalPodAutoscalerFrom(horizontalPodAutoscaler, fieldManager, "") -} - -// ExtractHorizontalPodAutoscalerStatus extracts the applied configuration owned by fieldManager from -// horizontalPodAutoscaler for the status subresource. -func ExtractHorizontalPodAutoscalerStatus(horizontalPodAutoscaler *autoscalingv2beta2.HorizontalPodAutoscaler, fieldManager string) (*HorizontalPodAutoscalerApplyConfiguration, error) { - return ExtractHorizontalPodAutoscalerFrom(horizontalPodAutoscaler, fieldManager, "status") -} - -func (b HorizontalPodAutoscalerApplyConfiguration) IsApplyConfiguration() {} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithKind(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.TypeMetaApplyConfiguration.Kind = &value - return b -} - -// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the APIVersion field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithAPIVersion(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.TypeMetaApplyConfiguration.APIVersion = &value - return b -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithName(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Name = &value - return b -} - -// WithGenerateName sets the GenerateName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the GenerateName field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithGenerateName(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.GenerateName = &value - return b -} - -// WithNamespace sets the Namespace field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Namespace field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithNamespace(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Namespace = &value - return b -} - -// WithUID sets the UID field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the UID field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithUID(value types.UID) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.UID = &value - return b -} - -// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ResourceVersion field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithResourceVersion(value string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.ResourceVersion = &value - return b -} - -// WithGeneration sets the Generation field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Generation field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithGeneration(value int64) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Generation = &value - return b -} - -// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CreationTimestamp field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.CreationTimestamp = &value - return b -} - -// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionTimestamp field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value - return b -} - -// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value - return b -} - -// WithLabels puts the entries into the Labels field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Labels field, -// overwriting an existing map entries in Labels field with the same key. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithLabels(entries map[string]string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Labels[k] = v - } - return b -} - -// WithAnnotations puts the entries into the Annotations field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Annotations field, -// overwriting an existing map entries in Annotations field with the same key. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithAnnotations(entries map[string]string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Annotations[k] = v - } - return b -} - -// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the OwnerReferences field. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - if values[i] == nil { - panic("nil value passed to WithOwnerReferences") - } - b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) - } - return b -} - -// WithFinalizers adds the given value to the Finalizers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Finalizers field. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithFinalizers(values ...string) *HorizontalPodAutoscalerApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) - } - return b -} - -func (b *HorizontalPodAutoscalerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { - if b.ObjectMetaApplyConfiguration == nil { - b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} - } -} - -// WithSpec sets the Spec field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Spec field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithSpec(value *HorizontalPodAutoscalerSpecApplyConfiguration) *HorizontalPodAutoscalerApplyConfiguration { - b.Spec = value - return b -} - -// WithStatus sets the Status field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Status field is set to the value of the last call. -func (b *HorizontalPodAutoscalerApplyConfiguration) WithStatus(value *HorizontalPodAutoscalerStatusApplyConfiguration) *HorizontalPodAutoscalerApplyConfiguration { - b.Status = value - return b -} - -// GetKind retrieves the value of the Kind field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetKind() *string { - return b.TypeMetaApplyConfiguration.Kind -} - -// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetAPIVersion() *string { - return b.TypeMetaApplyConfiguration.APIVersion -} - -// GetName retrieves the value of the Name field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetName() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Name -} - -// GetNamespace retrieves the value of the Namespace field in the declarative configuration. -func (b *HorizontalPodAutoscalerApplyConfiguration) GetNamespace() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Namespace -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerbehavior.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerbehavior.go deleted file mode 100644 index 355e15ff52e6b..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerbehavior.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// HorizontalPodAutoscalerBehaviorApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerBehavior type for use -// with apply. -// -// HorizontalPodAutoscalerBehavior configures the scaling behavior of the target -// in both Up and Down directions (scaleUp and scaleDown fields respectively). -type HorizontalPodAutoscalerBehaviorApplyConfiguration struct { - // scaleUp is scaling policy for scaling Up. - // If not set, the default value is the higher of: - // * increase no more than 4 pods per 60 seconds - // * double the number of pods per 60 seconds - // No stabilization is used. - ScaleUp *HPAScalingRulesApplyConfiguration `json:"scaleUp,omitempty"` - // scaleDown is scaling policy for scaling Down. - // If not set, the default value is to allow to scale down to minReplicas pods, with a - // 300 second stabilization window (i.e., the highest recommendation for - // the last 300sec is used). - ScaleDown *HPAScalingRulesApplyConfiguration `json:"scaleDown,omitempty"` -} - -// HorizontalPodAutoscalerBehaviorApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerBehavior type for use with -// apply. -func HorizontalPodAutoscalerBehavior() *HorizontalPodAutoscalerBehaviorApplyConfiguration { - return &HorizontalPodAutoscalerBehaviorApplyConfiguration{} -} - -// WithScaleUp sets the ScaleUp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ScaleUp field is set to the value of the last call. -func (b *HorizontalPodAutoscalerBehaviorApplyConfiguration) WithScaleUp(value *HPAScalingRulesApplyConfiguration) *HorizontalPodAutoscalerBehaviorApplyConfiguration { - b.ScaleUp = value - return b -} - -// WithScaleDown sets the ScaleDown field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ScaleDown field is set to the value of the last call. -func (b *HorizontalPodAutoscalerBehaviorApplyConfiguration) WithScaleDown(value *HPAScalingRulesApplyConfiguration) *HorizontalPodAutoscalerBehaviorApplyConfiguration { - b.ScaleDown = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalercondition.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalercondition.go deleted file mode 100644 index a23e18b158efd..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalercondition.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// HorizontalPodAutoscalerConditionApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerCondition type for use -// with apply. -// -// HorizontalPodAutoscalerCondition describes the state of -// a HorizontalPodAutoscaler at a certain point. -type HorizontalPodAutoscalerConditionApplyConfiguration struct { - // type describes the current condition - Type *autoscalingv2beta2.HorizontalPodAutoscalerConditionType `json:"type,omitempty"` - // status is the status of the condition (True, False, Unknown) - Status *v1.ConditionStatus `json:"status,omitempty"` - // lastTransitionTime is the last time the condition transitioned from - // one status to another - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - // reason is the reason for the condition's last transition. - Reason *string `json:"reason,omitempty"` - // message is a human-readable explanation containing details about - // the transition - Message *string `json:"message,omitempty"` -} - -// HorizontalPodAutoscalerConditionApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerCondition type for use with -// apply. -func HorizontalPodAutoscalerCondition() *HorizontalPodAutoscalerConditionApplyConfiguration { - return &HorizontalPodAutoscalerConditionApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithType(value autoscalingv2beta2.HorizontalPodAutoscalerConditionType) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Type = &value - return b -} - -// WithStatus sets the Status field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Status field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithStatus(value v1.ConditionStatus) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Status = &value - return b -} - -// WithLastTransitionTime sets the LastTransitionTime field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the LastTransitionTime field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithLastTransitionTime(value metav1.Time) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.LastTransitionTime = &value - return b -} - -// WithReason sets the Reason field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Reason field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithReason(value string) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Reason = &value - return b -} - -// WithMessage sets the Message field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Message field is set to the value of the last call. -func (b *HorizontalPodAutoscalerConditionApplyConfiguration) WithMessage(value string) *HorizontalPodAutoscalerConditionApplyConfiguration { - b.Message = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerspec.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerspec.go deleted file mode 100644 index 50ee3fae7b10f..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerspec.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// HorizontalPodAutoscalerSpecApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerSpec type for use -// with apply. -// -// HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. -type HorizontalPodAutoscalerSpecApplyConfiguration struct { - // scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - // should be collected, as well as to actually change the replica count. - ScaleTargetRef *CrossVersionObjectReferenceApplyConfiguration `json:"scaleTargetRef,omitempty"` - // minReplicas is the lower limit for the number of replicas to which the autoscaler - // can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - // alpha feature gate HPAScaleToZero is enabled and at least one Object or External - // metric is configured. Scaling is active as long as at least one metric value is - // available. - MinReplicas *int32 `json:"minReplicas,omitempty"` - // maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - // It cannot be less that minReplicas. - MaxReplicas *int32 `json:"maxReplicas,omitempty"` - // metrics contains the specifications for which to use to calculate the - // desired replica count (the maximum replica count across all metrics will - // be used). The desired replica count is calculated multiplying the - // ratio between the target value and the current value by the current - // number of pods. Ergo, metrics used must decrease as the pod count is - // increased, and vice-versa. See the individual metric source types for - // more information about how each type of metric must respond. - // If not set, the default metric will be set to 80% average CPU utilization. - Metrics []MetricSpecApplyConfiguration `json:"metrics,omitempty"` - // behavior configures the scaling behavior of the target - // in both Up and Down directions (scaleUp and scaleDown fields respectively). - // If not set, the default HPAScalingRules for scale up and scale down are used. - Behavior *HorizontalPodAutoscalerBehaviorApplyConfiguration `json:"behavior,omitempty"` -} - -// HorizontalPodAutoscalerSpecApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerSpec type for use with -// apply. -func HorizontalPodAutoscalerSpec() *HorizontalPodAutoscalerSpecApplyConfiguration { - return &HorizontalPodAutoscalerSpecApplyConfiguration{} -} - -// WithScaleTargetRef sets the ScaleTargetRef field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ScaleTargetRef field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithScaleTargetRef(value *CrossVersionObjectReferenceApplyConfiguration) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.ScaleTargetRef = value - return b -} - -// WithMinReplicas sets the MinReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MinReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithMinReplicas(value int32) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.MinReplicas = &value - return b -} - -// WithMaxReplicas sets the MaxReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithMaxReplicas(value int32) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.MaxReplicas = &value - return b -} - -// WithMetrics adds the given value to the Metrics field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Metrics field. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithMetrics(values ...*MetricSpecApplyConfiguration) *HorizontalPodAutoscalerSpecApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithMetrics") - } - b.Metrics = append(b.Metrics, *values[i]) - } - return b -} - -// WithBehavior sets the Behavior field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Behavior field is set to the value of the last call. -func (b *HorizontalPodAutoscalerSpecApplyConfiguration) WithBehavior(value *HorizontalPodAutoscalerBehaviorApplyConfiguration) *HorizontalPodAutoscalerSpecApplyConfiguration { - b.Behavior = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerstatus.go deleted file mode 100644 index ffbdc1c32f34c..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/horizontalpodautoscalerstatus.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// HorizontalPodAutoscalerStatusApplyConfiguration represents a declarative configuration of the HorizontalPodAutoscalerStatus type for use -// with apply. -// -// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler. -type HorizontalPodAutoscalerStatusApplyConfiguration struct { - // observedGeneration is the most recent generation observed by this autoscaler. - ObservedGeneration *int64 `json:"observedGeneration,omitempty"` - // lastScaleTime is the last time the HorizontalPodAutoscaler scaled the number of pods, - // used by the autoscaler to control how often the number of pods is changed. - LastScaleTime *v1.Time `json:"lastScaleTime,omitempty"` - // currentReplicas is current number of replicas of pods managed by this autoscaler, - // as last seen by the autoscaler. - CurrentReplicas *int32 `json:"currentReplicas,omitempty"` - // desiredReplicas is the desired number of replicas of pods managed by this autoscaler, - // as last calculated by the autoscaler. - DesiredReplicas *int32 `json:"desiredReplicas,omitempty"` - // currentMetrics is the last read state of the metrics used by this autoscaler. - CurrentMetrics []MetricStatusApplyConfiguration `json:"currentMetrics,omitempty"` - // conditions is the set of conditions required for this autoscaler to scale its target, - // and indicates whether or not those conditions are met. - Conditions []HorizontalPodAutoscalerConditionApplyConfiguration `json:"conditions,omitempty"` -} - -// HorizontalPodAutoscalerStatusApplyConfiguration constructs a declarative configuration of the HorizontalPodAutoscalerStatus type for use with -// apply. -func HorizontalPodAutoscalerStatus() *HorizontalPodAutoscalerStatusApplyConfiguration { - return &HorizontalPodAutoscalerStatusApplyConfiguration{} -} - -// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ObservedGeneration field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithObservedGeneration(value int64) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.ObservedGeneration = &value - return b -} - -// WithLastScaleTime sets the LastScaleTime field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the LastScaleTime field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithLastScaleTime(value v1.Time) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.LastScaleTime = &value - return b -} - -// WithCurrentReplicas sets the CurrentReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CurrentReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithCurrentReplicas(value int32) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.CurrentReplicas = &value - return b -} - -// WithDesiredReplicas sets the DesiredReplicas field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DesiredReplicas field is set to the value of the last call. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithDesiredReplicas(value int32) *HorizontalPodAutoscalerStatusApplyConfiguration { - b.DesiredReplicas = &value - return b -} - -// WithCurrentMetrics adds the given value to the CurrentMetrics field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the CurrentMetrics field. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithCurrentMetrics(values ...*MetricStatusApplyConfiguration) *HorizontalPodAutoscalerStatusApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithCurrentMetrics") - } - b.CurrentMetrics = append(b.CurrentMetrics, *values[i]) - } - return b -} - -// WithConditions adds the given value to the Conditions field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Conditions field. -func (b *HorizontalPodAutoscalerStatusApplyConfiguration) WithConditions(values ...*HorizontalPodAutoscalerConditionApplyConfiguration) *HorizontalPodAutoscalerStatusApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithConditions") - } - b.Conditions = append(b.Conditions, *values[i]) - } - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/hpascalingpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/hpascalingpolicy.go deleted file mode 100644 index ca07910b907ff..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/hpascalingpolicy.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" -) - -// HPAScalingPolicyApplyConfiguration represents a declarative configuration of the HPAScalingPolicy type for use -// with apply. -// -// HPAScalingPolicy is a single policy which must hold true for a specified past interval. -type HPAScalingPolicyApplyConfiguration struct { - // type is used to specify the scaling policy. - Type *autoscalingv2beta2.HPAScalingPolicyType `json:"type,omitempty"` - // value contains the amount of change which is permitted by the policy. - // It must be greater than zero - Value *int32 `json:"value,omitempty"` - // periodSeconds specifies the window of time for which the policy should hold true. - // PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). - PeriodSeconds *int32 `json:"periodSeconds,omitempty"` -} - -// HPAScalingPolicyApplyConfiguration constructs a declarative configuration of the HPAScalingPolicy type for use with -// apply. -func HPAScalingPolicy() *HPAScalingPolicyApplyConfiguration { - return &HPAScalingPolicyApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *HPAScalingPolicyApplyConfiguration) WithType(value autoscalingv2beta2.HPAScalingPolicyType) *HPAScalingPolicyApplyConfiguration { - b.Type = &value - return b -} - -// WithValue sets the Value field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Value field is set to the value of the last call. -func (b *HPAScalingPolicyApplyConfiguration) WithValue(value int32) *HPAScalingPolicyApplyConfiguration { - b.Value = &value - return b -} - -// WithPeriodSeconds sets the PeriodSeconds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PeriodSeconds field is set to the value of the last call. -func (b *HPAScalingPolicyApplyConfiguration) WithPeriodSeconds(value int32) *HPAScalingPolicyApplyConfiguration { - b.PeriodSeconds = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/hpascalingrules.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/hpascalingrules.go deleted file mode 100644 index a87dc3d3c95e2..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/hpascalingrules.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" -) - -// HPAScalingRulesApplyConfiguration represents a declarative configuration of the HPAScalingRules type for use -// with apply. -// -// HPAScalingRules configures the scaling behavior for one direction. -// These Rules are applied after calculating DesiredReplicas from metrics for the HPA. -// They can limit the scaling velocity by specifying scaling policies. -// They can prevent flapping by specifying the stabilization window, so that the -// number of replicas is not set instantly, instead, the safest value from the stabilization -// window is chosen. -type HPAScalingRulesApplyConfiguration struct { - // stabilizationWindowSeconds is the number of seconds for which past recommendations should be - // considered while scaling up or scaling down. - // StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). - // If not set, use the default values: - // - For scale up: 0 (i.e. no stabilization is done). - // - For scale down: 300 (i.e. the stabilization window is 300 seconds long). - StabilizationWindowSeconds *int32 `json:"stabilizationWindowSeconds,omitempty"` - // selectPolicy is used to specify which policy should be used. - // If not set, the default value MaxPolicySelect is used. - SelectPolicy *autoscalingv2beta2.ScalingPolicySelect `json:"selectPolicy,omitempty"` - // policies is a list of potential scaling polices which can be used during scaling. - // At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid - Policies []HPAScalingPolicyApplyConfiguration `json:"policies,omitempty"` -} - -// HPAScalingRulesApplyConfiguration constructs a declarative configuration of the HPAScalingRules type for use with -// apply. -func HPAScalingRules() *HPAScalingRulesApplyConfiguration { - return &HPAScalingRulesApplyConfiguration{} -} - -// WithStabilizationWindowSeconds sets the StabilizationWindowSeconds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the StabilizationWindowSeconds field is set to the value of the last call. -func (b *HPAScalingRulesApplyConfiguration) WithStabilizationWindowSeconds(value int32) *HPAScalingRulesApplyConfiguration { - b.StabilizationWindowSeconds = &value - return b -} - -// WithSelectPolicy sets the SelectPolicy field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the SelectPolicy field is set to the value of the last call. -func (b *HPAScalingRulesApplyConfiguration) WithSelectPolicy(value autoscalingv2beta2.ScalingPolicySelect) *HPAScalingRulesApplyConfiguration { - b.SelectPolicy = &value - return b -} - -// WithPolicies adds the given value to the Policies field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Policies field. -func (b *HPAScalingRulesApplyConfiguration) WithPolicies(values ...*HPAScalingPolicyApplyConfiguration) *HPAScalingRulesApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithPolicies") - } - b.Policies = append(b.Policies, *values[i]) - } - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricidentifier.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricidentifier.go deleted file mode 100644 index 9fd064cb9cee7..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricidentifier.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// MetricIdentifierApplyConfiguration represents a declarative configuration of the MetricIdentifier type for use -// with apply. -// -// MetricIdentifier defines the name and optionally selector for a metric -type MetricIdentifierApplyConfiguration struct { - // name is the name of the given metric - Name *string `json:"name,omitempty"` - // selector is the string-encoded form of a standard kubernetes label selector for the given metric - // When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - // When unset, just the metricName will be used to gather metrics. - Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"` -} - -// MetricIdentifierApplyConfiguration constructs a declarative configuration of the MetricIdentifier type for use with -// apply. -func MetricIdentifier() *MetricIdentifierApplyConfiguration { - return &MetricIdentifierApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *MetricIdentifierApplyConfiguration) WithName(value string) *MetricIdentifierApplyConfiguration { - b.Name = &value - return b -} - -// WithSelector sets the Selector field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Selector field is set to the value of the last call. -func (b *MetricIdentifierApplyConfiguration) WithSelector(value *v1.LabelSelectorApplyConfiguration) *MetricIdentifierApplyConfiguration { - b.Selector = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricspec.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricspec.go deleted file mode 100644 index 92eb2fa644923..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricspec.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" -) - -// MetricSpecApplyConfiguration represents a declarative configuration of the MetricSpec type for use -// with apply. -// -// MetricSpec specifies how to scale based on a single metric -// (only `type` and one other matching field should be set at once). -type MetricSpecApplyConfiguration struct { - // type is the type of metric source. It should be one of "ContainerResource", "External", - // "Object", "Pods" or "Resource", each mapping to a matching field in the object. - Type *autoscalingv2beta2.MetricSourceType `json:"type,omitempty"` - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - Object *ObjectMetricSourceApplyConfiguration `json:"object,omitempty"` - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - Pods *PodsMetricSourceApplyConfiguration `json:"pods,omitempty"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - Resource *ResourceMetricSourceApplyConfiguration `json:"resource,omitempty"` - // container resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in - // each pod of the current scale target (e.g. CPU or memory). Such metrics are - // built in to Kubernetes, and have special scaling options on top of those - // available to normal per-pod metrics using the "pods" source. - ContainerResource *ContainerResourceMetricSourceApplyConfiguration `json:"containerResource,omitempty"` - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - External *ExternalMetricSourceApplyConfiguration `json:"external,omitempty"` -} - -// MetricSpecApplyConfiguration constructs a declarative configuration of the MetricSpec type for use with -// apply. -func MetricSpec() *MetricSpecApplyConfiguration { - return &MetricSpecApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithType(value autoscalingv2beta2.MetricSourceType) *MetricSpecApplyConfiguration { - b.Type = &value - return b -} - -// WithObject sets the Object field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Object field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithObject(value *ObjectMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.Object = value - return b -} - -// WithPods sets the Pods field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Pods field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithPods(value *PodsMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.Pods = value - return b -} - -// WithResource sets the Resource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Resource field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithResource(value *ResourceMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.Resource = value - return b -} - -// WithContainerResource sets the ContainerResource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ContainerResource field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithContainerResource(value *ContainerResourceMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.ContainerResource = value - return b -} - -// WithExternal sets the External field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the External field is set to the value of the last call. -func (b *MetricSpecApplyConfiguration) WithExternal(value *ExternalMetricSourceApplyConfiguration) *MetricSpecApplyConfiguration { - b.External = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricstatus.go deleted file mode 100644 index 16dc67814f787..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricstatus.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" -) - -// MetricStatusApplyConfiguration represents a declarative configuration of the MetricStatus type for use -// with apply. -// -// MetricStatus describes the last-read state of a single metric. -type MetricStatusApplyConfiguration struct { - // type is the type of metric source. It will be one of "ContainerResource", "External", - // "Object", "Pods" or "Resource", each corresponds to a matching field in the object. - Type *autoscalingv2beta2.MetricSourceType `json:"type,omitempty"` - // object refers to a metric describing a single kubernetes object - // (for example, hits-per-second on an Ingress object). - Object *ObjectMetricStatusApplyConfiguration `json:"object,omitempty"` - // pods refers to a metric describing each pod in the current scale target - // (for example, transactions-processed-per-second). The values will be - // averaged together before being compared to the target value. - Pods *PodsMetricStatusApplyConfiguration `json:"pods,omitempty"` - // resource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - Resource *ResourceMetricStatusApplyConfiguration `json:"resource,omitempty"` - // containerResource refers to a resource metric (such as those specified in - // requests and limits) known to Kubernetes describing a single container in each pod in the - // current scale target (e.g. CPU or memory). Such metrics are built in to - // Kubernetes, and have special scaling options on top of those available - // to normal per-pod metrics using the "pods" source. - ContainerResource *ContainerResourceMetricStatusApplyConfiguration `json:"containerResource,omitempty"` - // external refers to a global metric that is not associated - // with any Kubernetes object. It allows autoscaling based on information - // coming from components running outside of cluster - // (for example length of queue in cloud messaging service, or - // QPS from loadbalancer running outside of cluster). - External *ExternalMetricStatusApplyConfiguration `json:"external,omitempty"` -} - -// MetricStatusApplyConfiguration constructs a declarative configuration of the MetricStatus type for use with -// apply. -func MetricStatus() *MetricStatusApplyConfiguration { - return &MetricStatusApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithType(value autoscalingv2beta2.MetricSourceType) *MetricStatusApplyConfiguration { - b.Type = &value - return b -} - -// WithObject sets the Object field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Object field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithObject(value *ObjectMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.Object = value - return b -} - -// WithPods sets the Pods field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Pods field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithPods(value *PodsMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.Pods = value - return b -} - -// WithResource sets the Resource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Resource field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithResource(value *ResourceMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.Resource = value - return b -} - -// WithContainerResource sets the ContainerResource field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ContainerResource field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithContainerResource(value *ContainerResourceMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.ContainerResource = value - return b -} - -// WithExternal sets the External field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the External field is set to the value of the last call. -func (b *MetricStatusApplyConfiguration) WithExternal(value *ExternalMetricStatusApplyConfiguration) *MetricStatusApplyConfiguration { - b.External = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metrictarget.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metrictarget.go deleted file mode 100644 index eedcd59d105fd..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metrictarget.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - resource "k8s.io/apimachinery/pkg/api/resource" -) - -// MetricTargetApplyConfiguration represents a declarative configuration of the MetricTarget type for use -// with apply. -// -// MetricTarget defines the target value, average value, or average utilization of a specific metric -type MetricTargetApplyConfiguration struct { - // type represents whether the metric type is Utilization, Value, or AverageValue - Type *autoscalingv2beta2.MetricTargetType `json:"type,omitempty"` - // value is the target value of the metric (as a quantity). - Value *resource.Quantity `json:"value,omitempty"` - // averageValue is the target value of the average of the - // metric across all relevant pods (as a quantity) - AverageValue *resource.Quantity `json:"averageValue,omitempty"` - // averageUtilization is the target value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - // Currently only valid for Resource metric source type - AverageUtilization *int32 `json:"averageUtilization,omitempty"` -} - -// MetricTargetApplyConfiguration constructs a declarative configuration of the MetricTarget type for use with -// apply. -func MetricTarget() *MetricTargetApplyConfiguration { - return &MetricTargetApplyConfiguration{} -} - -// WithType sets the Type field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Type field is set to the value of the last call. -func (b *MetricTargetApplyConfiguration) WithType(value autoscalingv2beta2.MetricTargetType) *MetricTargetApplyConfiguration { - b.Type = &value - return b -} - -// WithValue sets the Value field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Value field is set to the value of the last call. -func (b *MetricTargetApplyConfiguration) WithValue(value resource.Quantity) *MetricTargetApplyConfiguration { - b.Value = &value - return b -} - -// WithAverageValue sets the AverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AverageValue field is set to the value of the last call. -func (b *MetricTargetApplyConfiguration) WithAverageValue(value resource.Quantity) *MetricTargetApplyConfiguration { - b.AverageValue = &value - return b -} - -// WithAverageUtilization sets the AverageUtilization field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AverageUtilization field is set to the value of the last call. -func (b *MetricTargetApplyConfiguration) WithAverageUtilization(value int32) *MetricTargetApplyConfiguration { - b.AverageUtilization = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricvaluestatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricvaluestatus.go deleted file mode 100644 index 49141abcc63d7..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/metricvaluestatus.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" -) - -// MetricValueStatusApplyConfiguration represents a declarative configuration of the MetricValueStatus type for use -// with apply. -// -// MetricValueStatus holds the current value for a metric -type MetricValueStatusApplyConfiguration struct { - // value is the current value of the metric (as a quantity). - Value *resource.Quantity `json:"value,omitempty"` - // averageValue is the current value of the average of the - // metric across all relevant pods (as a quantity) - AverageValue *resource.Quantity `json:"averageValue,omitempty"` - // averageUtilization is the current value of the average of the - // resource metric across all relevant pods, represented as a percentage of - // the requested value of the resource for the pods. - AverageUtilization *int32 `json:"averageUtilization,omitempty"` -} - -// MetricValueStatusApplyConfiguration constructs a declarative configuration of the MetricValueStatus type for use with -// apply. -func MetricValueStatus() *MetricValueStatusApplyConfiguration { - return &MetricValueStatusApplyConfiguration{} -} - -// WithValue sets the Value field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Value field is set to the value of the last call. -func (b *MetricValueStatusApplyConfiguration) WithValue(value resource.Quantity) *MetricValueStatusApplyConfiguration { - b.Value = &value - return b -} - -// WithAverageValue sets the AverageValue field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AverageValue field is set to the value of the last call. -func (b *MetricValueStatusApplyConfiguration) WithAverageValue(value resource.Quantity) *MetricValueStatusApplyConfiguration { - b.AverageValue = &value - return b -} - -// WithAverageUtilization sets the AverageUtilization field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AverageUtilization field is set to the value of the last call. -func (b *MetricValueStatusApplyConfiguration) WithAverageUtilization(value int32) *MetricValueStatusApplyConfiguration { - b.AverageUtilization = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/objectmetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/objectmetricsource.go deleted file mode 100644 index e99e081cea42f..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/objectmetricsource.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// ObjectMetricSourceApplyConfiguration represents a declarative configuration of the ObjectMetricSource type for use -// with apply. -// -// ObjectMetricSource indicates how to scale on a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricSourceApplyConfiguration struct { - DescribedObject *CrossVersionObjectReferenceApplyConfiguration `json:"describedObject,omitempty"` - // target specifies the target value for the given metric - Target *MetricTargetApplyConfiguration `json:"target,omitempty"` - // metric identifies the target metric by name and selector - Metric *MetricIdentifierApplyConfiguration `json:"metric,omitempty"` -} - -// ObjectMetricSourceApplyConfiguration constructs a declarative configuration of the ObjectMetricSource type for use with -// apply. -func ObjectMetricSource() *ObjectMetricSourceApplyConfiguration { - return &ObjectMetricSourceApplyConfiguration{} -} - -// WithDescribedObject sets the DescribedObject field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DescribedObject field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithDescribedObject(value *CrossVersionObjectReferenceApplyConfiguration) *ObjectMetricSourceApplyConfiguration { - b.DescribedObject = value - return b -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithTarget(value *MetricTargetApplyConfiguration) *ObjectMetricSourceApplyConfiguration { - b.Target = value - return b -} - -// WithMetric sets the Metric field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Metric field is set to the value of the last call. -func (b *ObjectMetricSourceApplyConfiguration) WithMetric(value *MetricIdentifierApplyConfiguration) *ObjectMetricSourceApplyConfiguration { - b.Metric = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/objectmetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/objectmetricstatus.go deleted file mode 100644 index 4d5d016791d7a..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/objectmetricstatus.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// ObjectMetricStatusApplyConfiguration represents a declarative configuration of the ObjectMetricStatus type for use -// with apply. -// -// ObjectMetricStatus indicates the current value of a metric describing a -// kubernetes object (for example, hits-per-second on an Ingress object). -type ObjectMetricStatusApplyConfiguration struct { - // metric identifies the target metric by name and selector - Metric *MetricIdentifierApplyConfiguration `json:"metric,omitempty"` - // current contains the current value for the given metric - Current *MetricValueStatusApplyConfiguration `json:"current,omitempty"` - DescribedObject *CrossVersionObjectReferenceApplyConfiguration `json:"describedObject,omitempty"` -} - -// ObjectMetricStatusApplyConfiguration constructs a declarative configuration of the ObjectMetricStatus type for use with -// apply. -func ObjectMetricStatus() *ObjectMetricStatusApplyConfiguration { - return &ObjectMetricStatusApplyConfiguration{} -} - -// WithMetric sets the Metric field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Metric field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithMetric(value *MetricIdentifierApplyConfiguration) *ObjectMetricStatusApplyConfiguration { - b.Metric = value - return b -} - -// WithCurrent sets the Current field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Current field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithCurrent(value *MetricValueStatusApplyConfiguration) *ObjectMetricStatusApplyConfiguration { - b.Current = value - return b -} - -// WithDescribedObject sets the DescribedObject field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DescribedObject field is set to the value of the last call. -func (b *ObjectMetricStatusApplyConfiguration) WithDescribedObject(value *CrossVersionObjectReferenceApplyConfiguration) *ObjectMetricStatusApplyConfiguration { - b.DescribedObject = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/podsmetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/podsmetricsource.go deleted file mode 100644 index 11dd2f6e9b736..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/podsmetricsource.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// PodsMetricSourceApplyConfiguration represents a declarative configuration of the PodsMetricSource type for use -// with apply. -// -// PodsMetricSource indicates how to scale on a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -// The values will be averaged together before being compared to the target -// value. -type PodsMetricSourceApplyConfiguration struct { - // metric identifies the target metric by name and selector - Metric *MetricIdentifierApplyConfiguration `json:"metric,omitempty"` - // target specifies the target value for the given metric - Target *MetricTargetApplyConfiguration `json:"target,omitempty"` -} - -// PodsMetricSourceApplyConfiguration constructs a declarative configuration of the PodsMetricSource type for use with -// apply. -func PodsMetricSource() *PodsMetricSourceApplyConfiguration { - return &PodsMetricSourceApplyConfiguration{} -} - -// WithMetric sets the Metric field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Metric field is set to the value of the last call. -func (b *PodsMetricSourceApplyConfiguration) WithMetric(value *MetricIdentifierApplyConfiguration) *PodsMetricSourceApplyConfiguration { - b.Metric = value - return b -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *PodsMetricSourceApplyConfiguration) WithTarget(value *MetricTargetApplyConfiguration) *PodsMetricSourceApplyConfiguration { - b.Target = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/podsmetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/podsmetricstatus.go deleted file mode 100644 index 929c02d4a41d1..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/podsmetricstatus.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -// PodsMetricStatusApplyConfiguration represents a declarative configuration of the PodsMetricStatus type for use -// with apply. -// -// PodsMetricStatus indicates the current value of a metric describing each pod in -// the current scale target (for example, transactions-processed-per-second). -type PodsMetricStatusApplyConfiguration struct { - // metric identifies the target metric by name and selector - Metric *MetricIdentifierApplyConfiguration `json:"metric,omitempty"` - // current contains the current value for the given metric - Current *MetricValueStatusApplyConfiguration `json:"current,omitempty"` -} - -// PodsMetricStatusApplyConfiguration constructs a declarative configuration of the PodsMetricStatus type for use with -// apply. -func PodsMetricStatus() *PodsMetricStatusApplyConfiguration { - return &PodsMetricStatusApplyConfiguration{} -} - -// WithMetric sets the Metric field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Metric field is set to the value of the last call. -func (b *PodsMetricStatusApplyConfiguration) WithMetric(value *MetricIdentifierApplyConfiguration) *PodsMetricStatusApplyConfiguration { - b.Metric = value - return b -} - -// WithCurrent sets the Current field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Current field is set to the value of the last call. -func (b *PodsMetricStatusApplyConfiguration) WithCurrent(value *MetricValueStatusApplyConfiguration) *PodsMetricStatusApplyConfiguration { - b.Current = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/resourcemetricsource.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/resourcemetricsource.go deleted file mode 100644 index ce7e7e5fe27d3..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/resourcemetricsource.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/api/core/v1" -) - -// ResourceMetricSourceApplyConfiguration represents a declarative configuration of the ResourceMetricSource type for use -// with apply. -// -// ResourceMetricSource indicates how to scale on a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). The values will be averaged -// together before being compared to the target. Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. Only one "target" type -// should be set. -type ResourceMetricSourceApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // target specifies the target value for the given metric - Target *MetricTargetApplyConfiguration `json:"target,omitempty"` -} - -// ResourceMetricSourceApplyConfiguration constructs a declarative configuration of the ResourceMetricSource type for use with -// apply. -func ResourceMetricSource() *ResourceMetricSourceApplyConfiguration { - return &ResourceMetricSourceApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ResourceMetricSourceApplyConfiguration) WithName(value v1.ResourceName) *ResourceMetricSourceApplyConfiguration { - b.Name = &value - return b -} - -// WithTarget sets the Target field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Target field is set to the value of the last call. -func (b *ResourceMetricSourceApplyConfiguration) WithTarget(value *MetricTargetApplyConfiguration) *ResourceMetricSourceApplyConfiguration { - b.Target = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/resourcemetricstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/resourcemetricstatus.go deleted file mode 100644 index 9a6f3919001db..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/autoscaling/v2beta2/resourcemetricstatus.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v2beta2 - -import ( - v1 "k8s.io/api/core/v1" -) - -// ResourceMetricStatusApplyConfiguration represents a declarative configuration of the ResourceMetricStatus type for use -// with apply. -// -// ResourceMetricStatus indicates the current value of a resource metric known to -// Kubernetes, as specified in requests and limits, describing each pod in the -// current scale target (e.g. CPU or memory). Such metrics are built in to -// Kubernetes, and have special scaling options on top of those available to -// normal per-pod metrics using the "pods" source. -type ResourceMetricStatusApplyConfiguration struct { - // name is the name of the resource in question. - Name *v1.ResourceName `json:"name,omitempty"` - // current contains the current value for the given metric - Current *MetricValueStatusApplyConfiguration `json:"current,omitempty"` -} - -// ResourceMetricStatusApplyConfiguration constructs a declarative configuration of the ResourceMetricStatus type for use with -// apply. -func ResourceMetricStatus() *ResourceMetricStatusApplyConfiguration { - return &ResourceMetricStatusApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *ResourceMetricStatusApplyConfiguration) WithName(value v1.ResourceName) *ResourceMetricStatusApplyConfiguration { - b.Name = &value - return b -} - -// WithCurrent sets the Current field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Current field is set to the value of the last call. -func (b *ResourceMetricStatusApplyConfiguration) WithCurrent(value *MetricValueStatusApplyConfiguration) *ResourceMetricStatusApplyConfiguration { - b.Current = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/certificates/v1beta1/podcertificaterequestspec.go b/staging/src/k8s.io/client-go/applyconfigurations/certificates/v1beta1/podcertificaterequestspec.go index 234420c40ab8d..6e195e38376f2 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/certificates/v1beta1/podcertificaterequestspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/certificates/v1beta1/podcertificaterequestspec.go @@ -62,8 +62,7 @@ type PodCertificateRequestSpecApplyConfiguration struct { // `kubernetes.io` signers will never issue certificates with a lifetime // longer than 24 hours. MaxExpirationSeconds *int32 `json:"maxExpirationSeconds,omitempty"` - // pkixPublicKey is the PKIX-serialized public key the signer will issue the - // certificate to. + // The PKIX-serialized public key the signer will issue the certificate to. // // The key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, // or ED25519. Note that this list may be expanded in the future. @@ -74,9 +73,14 @@ type PodCertificateRequestSpecApplyConfiguration struct { // setting a status.conditions entry with a type of "Denied" and a reason of // "UnsupportedKeyType". It may also suggest a key type that it does support // in the message field. + // + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. Signer + // implementations should extract the public key from the StubPKCS10Request + // field. PKIXPublicKey []byte `json:"pkixPublicKey,omitempty"` - // proofOfPossession proves that the requesting kubelet holds the private - // key corresponding to pkixPublicKey. + // A proof that the requesting kubelet holds the private key corresponding + // to pkixPublicKey. // // It is contructed by signing the ASCII bytes of the pod's UID using // `pkixPublicKey`. @@ -93,9 +97,31 @@ type PodCertificateRequestSpecApplyConfiguration struct { // golang library function crypto/ecdsa.SignASN1) // // If the key is an ED25519 key, the the signature is as described by the - // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by - // the golang library crypto/ed25519.Sign). + // [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the + // golang library crypto/ed25519.Sign). + // + // Deprecated: This field is replaced by StubPKCS10Request. If + // StubPKCS10Request is set, this field must be empty. ProofOfPossession []byte `json:"proofOfPossession,omitempty"` + // A PKCS#10 certificate signing request (DER-serialized) generated by + // Kubelet using the subject private key. + // + // Most signer implementations will ignore the contents of the CSR except to + // extract the subject public key. The API server automatically verifies the + // CSR signature during admission, so the signer does not need to repeat the + // verification. CSRs generated by kubelet are completely empty. + // + // The subject public key must be one of RSA3072, RSA4096, ECDSAP256, + // ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in + // the future. + // + // Signer implementations do not need to support all key types supported by + // kube-apiserver and kubelet. If a signer does not support the key type + // used for a given PodCertificateRequest, it must deny the request by + // setting a status.conditions entry with a type of "Denied" and a reason of + // "UnsupportedKeyType". It may also suggest a key type that it does support + // in the message field. + StubPKCS10Request []byte `json:"stubPKCS10Request,omitempty"` // unverifiedUserAnnotations allow pod authors to pass additional information to // the signer implementation. Kubernetes does not restrict or validate this // metadata in any way. @@ -199,6 +225,16 @@ func (b *PodCertificateRequestSpecApplyConfiguration) WithProofOfPossession(valu return b } +// WithStubPKCS10Request adds the given value to the StubPKCS10Request field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the StubPKCS10Request field. +func (b *PodCertificateRequestSpecApplyConfiguration) WithStubPKCS10Request(values ...byte) *PodCertificateRequestSpecApplyConfiguration { + for i := range values { + b.StubPKCS10Request = append(b.StubPKCS10Request, values[i]) + } + return b +} + // WithUnverifiedUserAnnotations puts the entries into the UnverifiedUserAnnotations field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, the entries provided by each call will be put on the UnverifiedUserAnnotations field, diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/imagevolumestatus.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/imagevolumestatus.go new file mode 100644 index 0000000000000..a16e44e508bf8 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/imagevolumestatus.go @@ -0,0 +1,44 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ImageVolumeStatusApplyConfiguration represents a declarative configuration of the ImageVolumeStatus type for use +// with apply. +// +// ImageVolumeStatus represents the image-based volume status. +type ImageVolumeStatusApplyConfiguration struct { + // ImageRef is the digest of the image used for this volume. + // It should have a value that's similar to the pod's status.containerStatuses[i].imageID. + // The ImageRef length should not exceed 256 characters. + ImageRef *string `json:"imageRef,omitempty"` +} + +// ImageVolumeStatusApplyConfiguration constructs a declarative configuration of the ImageVolumeStatus type for use with +// apply. +func ImageVolumeStatus() *ImageVolumeStatusApplyConfiguration { + return &ImageVolumeStatusApplyConfiguration{} +} + +// WithImageRef sets the ImageRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ImageRef field is set to the value of the last call. +func (b *ImageVolumeStatusApplyConfiguration) WithImageRef(value string) *ImageVolumeStatusApplyConfiguration { + b.ImageRef = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/nodeallocatableresourceclaimstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/nodeallocatableresourceclaimstatus.go new file mode 100644 index 0000000000000..6e384b002851c --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/nodeallocatableresourceclaimstatus.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + corev1 "k8s.io/api/core/v1" + resource "k8s.io/apimachinery/pkg/api/resource" +) + +// NodeAllocatableResourceClaimStatusApplyConfiguration represents a declarative configuration of the NodeAllocatableResourceClaimStatus type for use +// with apply. +// +// NodeAllocatableResourceClaimStatus describes the status of node allocatable resources allocated via DRA. +type NodeAllocatableResourceClaimStatusApplyConfiguration struct { + // ResourceClaimName is the resource claim referenced by the pod that resulted in this node allocatable resource allocation. + ResourceClaimName *string `json:"resourceClaimName,omitempty"` + // Containers lists the names of all containers in this pod that reference the claim. + Containers []string `json:"containers,omitempty"` + // Resources is a map of the node-allocatable resource name to the aggregate quantity allocated to the claim. + Resources map[corev1.ResourceName]resource.Quantity `json:"resources,omitempty"` +} + +// NodeAllocatableResourceClaimStatusApplyConfiguration constructs a declarative configuration of the NodeAllocatableResourceClaimStatus type for use with +// apply. +func NodeAllocatableResourceClaimStatus() *NodeAllocatableResourceClaimStatusApplyConfiguration { + return &NodeAllocatableResourceClaimStatusApplyConfiguration{} +} + +// WithResourceClaimName sets the ResourceClaimName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceClaimName field is set to the value of the last call. +func (b *NodeAllocatableResourceClaimStatusApplyConfiguration) WithResourceClaimName(value string) *NodeAllocatableResourceClaimStatusApplyConfiguration { + b.ResourceClaimName = &value + return b +} + +// WithContainers adds the given value to the Containers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Containers field. +func (b *NodeAllocatableResourceClaimStatusApplyConfiguration) WithContainers(values ...string) *NodeAllocatableResourceClaimStatusApplyConfiguration { + for i := range values { + b.Containers = append(b.Containers, values[i]) + } + return b +} + +// WithResources puts the entries into the Resources field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Resources field, +// overwriting an existing map entries in Resources field with the same key. +func (b *NodeAllocatableResourceClaimStatusApplyConfiguration) WithResources(entries map[corev1.ResourceName]resource.Quantity) *NodeAllocatableResourceClaimStatusApplyConfiguration { + if b.Resources == nil && len(entries) > 0 { + b.Resources = make(map[corev1.ResourceName]resource.Quantity, len(entries)) + } + for k, v := range entries { + b.Resources[k] = v + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumesource.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumesource.go index 8627148bfc00b..2d9d1dd5dafae 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumesource.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumesource.go @@ -94,8 +94,7 @@ type PersistentVolumeSourceApplyConfiguration struct { PhotonPersistentDisk *PhotonPersistentDiskVolumeSourceApplyConfiguration `json:"photonPersistentDisk,omitempty"` // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. PortworxVolume *PortworxVolumeSourceApplyConfiguration `json:"portworxVolume,omitempty"` // scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podcondition.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podcondition.go index 55e55dd0f2bce..dc527cdf2575a 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podcondition.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podcondition.go @@ -32,7 +32,6 @@ type PodConditionApplyConfiguration struct { // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions Type *corev1.PodConditionType `json:"type,omitempty"` // If set, this represents the .metadata.generation that the pod condition was set based upon. - // The PodObservedGenerationTracking feature gate must be enabled to use this field. ObservedGeneration *int64 `json:"observedGeneration,omitempty"` // Status is the status of the condition. // Can be True, False, Unknown. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaim.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaim.go index 93d2280f0dfa6..554cfd0ffc16a 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaim.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaim.go @@ -27,6 +27,14 @@ package v1 // // It adds a name to it that uniquely identifies the ResourceClaim inside the Pod. // Containers that need access to the ResourceClaim reference it with this name. +// +// When the DRAWorkloadResourceClaims feature gate is enabled and this Pod +// belongs to a PodGroup, a PodResourceClaim is matched to a +// PodGroupResourceClaim if all of their fields are equal (Name, +// ResourceClaimName, and ResourceClaimTemplateName). A matched claim references +// a single ResourceClaim shared across all Pods in the PodGroup, reserved for +// the PodGroup in ResourceClaimStatus.ReservedFor rather than for individual +// Pods. type PodResourceClaimApplyConfiguration struct { // Name uniquely identifies this resource claim inside the pod. // This must be a DNS_LABEL. @@ -46,6 +54,16 @@ type PodResourceClaimApplyConfiguration struct { // generated component, will be used to form a unique name for the // ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses. // + // When the DRAWorkloadResourceClaims feature gate is enabled and the pod + // belongs to a PodGroup that defines a PodGroupResourceClaim with the same + // Name and ResourceClaimTemplateName, this PodResourceClaim resolves to the + // ResourceClaim generated for the PodGroup. All pods in the group that + // define an equivalent PodResourceClaim matching the + // PodGroupResourceClaim's Name and ResourceClaimTemplateName share the same + // generated ResourceClaim. ResourceClaims generated for a PodGroup are + // owned by the PodGroup and their lifecycles are tied to the PodGroup + // instead of any individual pod. + // // This field is immutable and no changes will be made to the // corresponding ResourceClaim by the control plane after creating the // ResourceClaim. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaimstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaimstatus.go index f0673fa0f118c..094e2b8747002 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaimstatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podresourceclaimstatus.go @@ -30,9 +30,16 @@ type PodResourceClaimStatusApplyConfiguration struct { // which implies that the string must be a DNS_LABEL. Name *string `json:"name,omitempty"` // ResourceClaimName is the name of the ResourceClaim that was - // generated for the Pod in the namespace of the Pod. If this is - // unset, then generating a ResourceClaim was not necessary. The - // pod.spec.resourceClaims entry can be ignored in this case. + // generated for the Pod in the namespace of the Pod. + // + // When the DRAWorkloadResourceClaims feature is enabled and the + // corresponding PodResourceClaim matches a PodGroupResourceClaim + // made by the Pod's PodGroup, then this is the name of the + // ResourceClaim generated and reserved for the PodGroup. + // + // If this is unset, then generating a ResourceClaim was not + // necessary. The pod.spec.resourceClaims entry can be ignored in + // this case. ResourceClaimName *string `json:"resourceClaimName,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podschedulinggroup.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podschedulinggroup.go new file mode 100644 index 0000000000000..4dd3b23ba9b9b --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podschedulinggroup.go @@ -0,0 +1,46 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// PodSchedulingGroupApplyConfiguration represents a declarative configuration of the PodSchedulingGroup type for use +// with apply. +// +// PodSchedulingGroup identifies the runtime scheduling group instance that a Pod belongs to. +// The scheduler uses this information to apply workload-aware scheduling semantics. +// Exactly one field must be specified. +type PodSchedulingGroupApplyConfiguration struct { + // PodGroupName specifies the name of the standalone PodGroup object + // that represents the runtime instance of this group. + // Must be a DNS subdomain. + PodGroupName *string `json:"podGroupName,omitempty"` +} + +// PodSchedulingGroupApplyConfiguration constructs a declarative configuration of the PodSchedulingGroup type for use with +// apply. +func PodSchedulingGroup() *PodSchedulingGroupApplyConfiguration { + return &PodSchedulingGroupApplyConfiguration{} +} + +// WithPodGroupName sets the PodGroupName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PodGroupName field is set to the value of the last call. +func (b *PodSchedulingGroupApplyConfiguration) WithPodGroupName(value string) *PodSchedulingGroupApplyConfiguration { + b.PodGroupName = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podspec.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podspec.go index 1948c7131f9c8..e67c18c41ba07 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podspec.go @@ -233,7 +233,6 @@ type PodSpecApplyConfiguration struct { // When set to false, a new userns is created for the pod. Setting false is useful for // mitigating container breakout vulnerabilities even allowing users to run their // containers as root without actually having root privileges on the host. - // This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature. HostUsers *bool `json:"hostUsers,omitempty"` // SchedulingGates is an opaque list of values that if specified will block scheduling the pod. // If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the @@ -273,14 +272,19 @@ type PodSpecApplyConfiguration struct { // This field must be a valid DNS subdomain as defined in RFC 1123 and contain at most 64 characters. // Requires the HostnameOverride feature gate to be enabled. HostnameOverride *string `json:"hostnameOverride,omitempty"` - // WorkloadRef provides a reference to the Workload object that this Pod belongs to. - // This field is used by the scheduler to identify the PodGroup and apply the - // correct group scheduling policies. The Workload object referenced - // by this field may not exist at the time the Pod is created. - // This field is immutable, but a Workload object with the same name - // may be recreated with different policies. Doing this during pod scheduling + // SchedulingGroup provides a reference to the immediate scheduling runtime + // grouping object that this Pod belongs to. + // This field is used by the scheduler to identify the group and apply the + // correct group scheduling policies. The association with a group also + // impacts other lifecycle aspects of a Pod that are relevant in a wider context + // of scheduling like preemption, resource attachment, etc. If not specified, + // the Pod is treated as a single unit in all of these aspects. + // The group object referenced by this field may not exist at the time the + // Pod is created. + // This field is immutable, but a group object with the same name may be + // recreated with different policies. Doing this during pod scheduling // may result in the placement not conforming to the expected policies. - WorkloadRef *WorkloadReferenceApplyConfiguration `json:"workloadRef,omitempty"` + SchedulingGroup *PodSchedulingGroupApplyConfiguration `json:"schedulingGroup,omitempty"` } // PodSpecApplyConfiguration constructs a declarative configuration of the PodSpec type for use with @@ -678,10 +682,10 @@ func (b *PodSpecApplyConfiguration) WithHostnameOverride(value string) *PodSpecA return b } -// WithWorkloadRef sets the WorkloadRef field in the declarative configuration to the given value +// WithSchedulingGroup sets the SchedulingGroup field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the WorkloadRef field is set to the value of the last call. -func (b *PodSpecApplyConfiguration) WithWorkloadRef(value *WorkloadReferenceApplyConfiguration) *PodSpecApplyConfiguration { - b.WorkloadRef = value +// If called multiple times, the SchedulingGroup field is set to the value of the last call. +func (b *PodSpecApplyConfiguration) WithSchedulingGroup(value *PodSchedulingGroupApplyConfiguration) *PodSpecApplyConfiguration { + b.SchedulingGroup = value return b } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podstatus.go index e50ca9335e880..8328d9cdb1e54 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podstatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/podstatus.go @@ -139,6 +139,12 @@ type PodStatusApplyConfiguration struct { // applied at the pod level if pod-level requests or limits are set in // PodSpec.Resources Resources *ResourceRequirementsApplyConfiguration `json:"resources,omitempty"` + // NodeAllocatableResourceClaimStatuses contains the status of node-allocatable resources + // that were allocated for this pod through DRA claims. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + NodeAllocatableResourceClaimStatuses []NodeAllocatableResourceClaimStatusApplyConfiguration `json:"nodeAllocatableResourceClaimStatuses,omitempty"` } // PodStatusApplyConfiguration constructs a declarative configuration of the PodStatus type for use with @@ -341,3 +347,16 @@ func (b *PodStatusApplyConfiguration) WithResources(value *ResourceRequirementsA b.Resources = value return b } + +// WithNodeAllocatableResourceClaimStatuses adds the given value to the NodeAllocatableResourceClaimStatuses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the NodeAllocatableResourceClaimStatuses field. +func (b *PodStatusApplyConfiguration) WithNodeAllocatableResourceClaimStatuses(values ...*NodeAllocatableResourceClaimStatusApplyConfiguration) *PodStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithNodeAllocatableResourceClaimStatuses") + } + b.NodeAllocatableResourceClaimStatuses = append(b.NodeAllocatableResourceClaimStatuses, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/resourcehealth.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/resourcehealth.go index 684153c49b464..061535b8d9bae 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/resourcehealth.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/resourcehealth.go @@ -41,6 +41,9 @@ type ResourceHealthApplyConfiguration struct { // // In future we may want to introduce the PermanentlyUnhealthy Status. Health *corev1.ResourceHealthStatus `json:"health,omitempty"` + // Message provides human-readable context for Health (e.g. "ECC error count exceeded threshold"). + // This field is populated by the kubelet when ResourceHealthStatusMessage is enabled if the DRA plugin returns a message, and is null otherwise. + Message *string `json:"message,omitempty"` } // ResourceHealthApplyConfiguration constructs a declarative configuration of the ResourceHealth type for use with @@ -64,3 +67,11 @@ func (b *ResourceHealthApplyConfiguration) WithHealth(value corev1.ResourceHealt b.Health = &value return b } + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *ResourceHealthApplyConfiguration) WithMessage(value string) *ResourceHealthApplyConfiguration { + b.Message = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/securitycontext.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/securitycontext.go index aee2635238f11..8147c54769e66 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/securitycontext.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/securitycontext.go @@ -83,7 +83,6 @@ type SecurityContextApplyConfiguration struct { // procMount denotes the type of proc mount to use for the containers. // The default value is Default which uses the container runtime defaults for // readonly paths and masked paths. - // This requires the ProcMountType feature flag to be enabled. // Note that this field cannot be set when spec.os.name is windows. ProcMount *corev1.ProcMountType `json:"procMount,omitempty"` // The seccomp options to use by this container. If seccomp options are diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumemountstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumemountstatus.go index ad4965a17d23b..693268ecb1178 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumemountstatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumemountstatus.go @@ -37,6 +37,9 @@ type VolumeMountStatusApplyConfiguration struct { // An IfPossible value in the original VolumeMount must be translated to Disabled or Enabled, // depending on the mount result. RecursiveReadOnly *corev1.RecursiveReadOnlyMode `json:"recursiveReadOnly,omitempty"` + // volumeStatus represents volume-type-specific status about the mounted + // volume. + VolumeStatus *VolumeStatusApplyConfiguration `json:"volumeStatus,omitempty"` } // VolumeMountStatusApplyConfiguration constructs a declarative configuration of the VolumeMountStatus type for use with @@ -76,3 +79,11 @@ func (b *VolumeMountStatusApplyConfiguration) WithRecursiveReadOnly(value corev1 b.RecursiveReadOnly = &value return b } + +// WithVolumeStatus sets the VolumeStatus field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the VolumeStatus field is set to the value of the last call. +func (b *VolumeMountStatusApplyConfiguration) WithVolumeStatus(value *VolumeStatusApplyConfiguration) *VolumeMountStatusApplyConfiguration { + b.VolumeStatus = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumesource.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumesource.go index 5d9a6b0f6d3ac..2ce585f541e92 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumesource.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumesource.go @@ -116,8 +116,7 @@ type VolumeSourceApplyConfiguration struct { Projected *ProjectedVolumeSourceApplyConfiguration `json:"projected,omitempty"` // portworxVolume represents a portworx volume attached and mounted on kubelets host machine. // Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - // are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - // is on. + // are redirected to the pxd.portworx.com CSI driver. PortworxVolume *PortworxVolumeSourceApplyConfiguration `json:"portworxVolume,omitempty"` // scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. @@ -163,7 +162,7 @@ type VolumeSourceApplyConfiguration struct { // A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. // The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. // The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - // The volume will be mounted read-only (ro) and non-executable files (noexec). + // The volume will be mounted read-only (ro). // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. Image *ImageVolumeSourceApplyConfiguration `json:"image,omitempty"` diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumestatus.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumestatus.go new file mode 100644 index 0000000000000..3d6a7f85d911a --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/volumestatus.go @@ -0,0 +1,43 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// VolumeStatusApplyConfiguration represents a declarative configuration of the VolumeStatus type for use +// with apply. +// +// VolumeStatus represents the status of a mounted volume. +// At most one of its members must be specified. +type VolumeStatusApplyConfiguration struct { + // image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + Image *ImageVolumeStatusApplyConfiguration `json:"image,omitempty"` +} + +// VolumeStatusApplyConfiguration constructs a declarative configuration of the VolumeStatus type for use with +// apply. +func VolumeStatus() *VolumeStatusApplyConfiguration { + return &VolumeStatusApplyConfiguration{} +} + +// WithImage sets the Image field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Image field is set to the value of the last call. +func (b *VolumeStatusApplyConfiguration) WithImage(value *ImageVolumeStatusApplyConfiguration) *VolumeStatusApplyConfiguration { + b.Image = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/workloadreference.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/workloadreference.go deleted file mode 100644 index 758c4517dfd67..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/workloadreference.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v1 - -// WorkloadReferenceApplyConfiguration represents a declarative configuration of the WorkloadReference type for use -// with apply. -// -// WorkloadReference identifies the Workload object and PodGroup membership -// that a Pod belongs to. The scheduler uses this information to apply -// workload-aware scheduling semantics. -type WorkloadReferenceApplyConfiguration struct { - // Name defines the name of the Workload object this Pod belongs to. - // Workload must be in the same namespace as the Pod. - // If it doesn't match any existing Workload, the Pod will remain unschedulable - // until a Workload object is created and observed by the kube-scheduler. - // It must be a DNS subdomain. - Name *string `json:"name,omitempty"` - // PodGroup is the name of the PodGroup within the Workload that this Pod - // belongs to. If it doesn't match any existing PodGroup within the Workload, - // the Pod will remain unschedulable until the Workload object is recreated - // and observed by the kube-scheduler. It must be a DNS label. - PodGroup *string `json:"podGroup,omitempty"` - // PodGroupReplicaKey specifies the replica key of the PodGroup to which this - // Pod belongs. It is used to distinguish pods belonging to different replicas - // of the same pod group. The pod group policy is applied separately to each replica. - // When set, it must be a DNS label. - PodGroupReplicaKey *string `json:"podGroupReplicaKey,omitempty"` -} - -// WorkloadReferenceApplyConfiguration constructs a declarative configuration of the WorkloadReference type for use with -// apply. -func WorkloadReference() *WorkloadReferenceApplyConfiguration { - return &WorkloadReferenceApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *WorkloadReferenceApplyConfiguration) WithName(value string) *WorkloadReferenceApplyConfiguration { - b.Name = &value - return b -} - -// WithPodGroup sets the PodGroup field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PodGroup field is set to the value of the last call. -func (b *WorkloadReferenceApplyConfiguration) WithPodGroup(value string) *WorkloadReferenceApplyConfiguration { - b.PodGroup = &value - return b -} - -// WithPodGroupReplicaKey sets the PodGroupReplicaKey field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PodGroupReplicaKey field is set to the value of the last call. -func (b *WorkloadReferenceApplyConfiguration) WithPodGroupReplicaKey(value string) *WorkloadReferenceApplyConfiguration { - b.PodGroupReplicaKey = &value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go index 2d66c9ba08bf1..c0b255cc739cd 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go @@ -39,6 +39,12 @@ func Parser() *typed.Parser { var parserOnce sync.Once var parser *typed.Parser var schemaYAML = typed.YAMLObject(`types: +- name: io.k8s.api.admissionregistration.v1.ApplyConfiguration + map: + fields: + - name: expression + type: + scalar: string - name: io.k8s.api.admissionregistration.v1.AuditAnnotation map: fields: @@ -61,6 +67,12 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" +- name: io.k8s.api.admissionregistration.v1.JSONPatch + map: + fields: + - name: expression + type: + scalar: string - name: io.k8s.api.admissionregistration.v1.MatchCondition map: fields: @@ -97,6 +109,87 @@ var schemaYAML = typed.YAMLObject(`types: namedType: io.k8s.api.admissionregistration.v1.NamedRuleWithOperations elementRelationship: atomic elementRelationship: atomic +- name: io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicy + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec + default: {} +- name: io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBinding + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec + default: {} +- name: io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicyBindingSpec + map: + fields: + - name: matchResources + type: + namedType: io.k8s.api.admissionregistration.v1.MatchResources + - name: paramRef + type: + namedType: io.k8s.api.admissionregistration.v1.ParamRef + - name: policyName + type: + scalar: string +- name: io.k8s.api.admissionregistration.v1.MutatingAdmissionPolicySpec + map: + fields: + - name: failurePolicy + type: + scalar: string + - name: matchConditions + type: + list: + elementType: + namedType: io.k8s.api.admissionregistration.v1.MatchCondition + elementRelationship: associative + keys: + - name + - name: matchConstraints + type: + namedType: io.k8s.api.admissionregistration.v1.MatchResources + - name: mutations + type: + list: + elementType: + namedType: io.k8s.api.admissionregistration.v1.Mutation + elementRelationship: atomic + - name: paramKind + type: + namedType: io.k8s.api.admissionregistration.v1.ParamKind + - name: reinvocationPolicy + type: + scalar: string + - name: variables + type: + list: + elementType: + namedType: io.k8s.api.admissionregistration.v1.Variable + elementRelationship: atomic - name: io.k8s.api.admissionregistration.v1.MutatingWebhook map: fields: @@ -170,6 +263,19 @@ var schemaYAML = typed.YAMLObject(`types: elementRelationship: associative keys: - name +- name: io.k8s.api.admissionregistration.v1.Mutation + map: + fields: + - name: applyConfiguration + type: + namedType: io.k8s.api.admissionregistration.v1.ApplyConfiguration + - name: jsonPatch + type: + namedType: io.k8s.api.admissionregistration.v1.JSONPatch + - name: patchType + type: + scalar: string + default: "" - name: io.k8s.api.admissionregistration.v1.NamedRuleWithOperations map: fields: @@ -3302,87 +3408,72 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" -- name: io.k8s.api.autoscaling.v2beta1.ContainerResourceMetricSource +- name: io.k8s.api.batch.v1.CronJob map: fields: - - name: container + - name: apiVersion type: scalar: string - default: "" - - name: name + - name: kind type: scalar: string - default: "" - - name: targetAverageUtilization + - name: metadata type: - scalar: numeric - - name: targetAverageValue + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta1.ContainerResourceMetricStatus + namedType: io.k8s.api.batch.v1.CronJobSpec + default: {} + - name: status + type: + namedType: io.k8s.api.batch.v1.CronJobStatus + default: {} +- name: io.k8s.api.batch.v1.CronJobSpec map: fields: - - name: container + - name: concurrencyPolicy type: scalar: string - default: "" - - name: currentAverageUtilization + - name: failedJobsHistoryLimit type: scalar: numeric - - name: currentAverageValue + - name: jobTemplate type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: name + namedType: io.k8s.api.batch.v1.JobTemplateSpec + default: {} + - name: schedule type: scalar: string default: "" -- name: io.k8s.api.autoscaling.v2beta1.CrossVersionObjectReference - map: - fields: - - name: apiVersion + - name: startingDeadlineSeconds type: - scalar: string - - name: kind + scalar: numeric + - name: successfulJobsHistoryLimit type: - scalar: string - default: "" - - name: name + scalar: numeric + - name: suspend type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta1.ExternalMetricSource - map: - fields: - - name: metricName + scalar: boolean + - name: timeZone type: scalar: string - default: "" - - name: metricSelector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector - - name: targetAverageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: targetValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus +- name: io.k8s.api.batch.v1.CronJobStatus map: fields: - - name: currentAverageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: currentValue + - name: active type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: metricName + list: + elementType: + namedType: io.k8s.api.core.v1.ObjectReference + elementRelationship: atomic + - name: lastScheduleTime type: - scalar: string - default: "" - - name: metricSelector + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: lastSuccessfulTime type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector -- name: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscaler + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: io.k8s.api.batch.v1.Job map: fields: - name: apiVersion @@ -3397,15 +3488,18 @@ var schemaYAML = typed.YAMLObject(`types: default: {} - name: spec type: - namedType: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerSpec + namedType: io.k8s.api.batch.v1.JobSpec default: {} - name: status type: - namedType: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerStatus + namedType: io.k8s.api.batch.v1.JobStatus default: {} -- name: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerCondition +- name: io.k8s.api.batch.v1.JobCondition map: fields: + - name: lastProbeTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: lastTransitionTime type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time @@ -3423,719 +3517,71 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" -- name: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerSpec +- name: io.k8s.api.batch.v1.JobSpec map: fields: - - name: maxReplicas + - name: activeDeadlineSeconds type: scalar: numeric - default: 0 - - name: metrics + - name: backoffLimit type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta1.MetricSpec - elementRelationship: atomic - - name: minReplicas + scalar: numeric + - name: backoffLimitPerIndex type: scalar: numeric - - name: scaleTargetRef + - name: completionMode type: - namedType: io.k8s.api.autoscaling.v2beta1.CrossVersionObjectReference - default: {} -- name: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerStatus - map: - fields: - - name: conditions + scalar: string + - name: completions type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerCondition - elementRelationship: atomic - - name: currentMetrics + scalar: numeric + - name: managedBy type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta1.MetricStatus - elementRelationship: atomic - - name: currentReplicas + scalar: string + - name: manualSelector type: - scalar: numeric - default: 0 - - name: desiredReplicas + scalar: boolean + - name: maxFailedIndexes type: scalar: numeric - default: 0 - - name: lastScaleTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: observedGeneration + - name: parallelism type: scalar: numeric -- name: io.k8s.api.autoscaling.v2beta1.MetricSpec - map: - fields: - - name: containerResource + - name: podFailurePolicy type: - namedType: io.k8s.api.autoscaling.v2beta1.ContainerResourceMetricSource - - name: external + namedType: io.k8s.api.batch.v1.PodFailurePolicy + - name: podReplacementPolicy type: - namedType: io.k8s.api.autoscaling.v2beta1.ExternalMetricSource - - name: object + scalar: string + - name: selector type: - namedType: io.k8s.api.autoscaling.v2beta1.ObjectMetricSource - - name: pods + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector + - name: successPolicy type: - namedType: io.k8s.api.autoscaling.v2beta1.PodsMetricSource - - name: resource + namedType: io.k8s.api.batch.v1.SuccessPolicy + - name: suspend type: - namedType: io.k8s.api.autoscaling.v2beta1.ResourceMetricSource - - name: type + scalar: boolean + - name: template type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta1.MetricStatus + namedType: io.k8s.api.core.v1.PodTemplateSpec + default: {} + - name: ttlSecondsAfterFinished + type: + scalar: numeric +- name: io.k8s.api.batch.v1.JobStatus map: fields: - - name: containerResource - type: - namedType: io.k8s.api.autoscaling.v2beta1.ContainerResourceMetricStatus - - name: external + - name: active type: - namedType: io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus - - name: object + scalar: numeric + - name: completedIndexes type: - namedType: io.k8s.api.autoscaling.v2beta1.ObjectMetricStatus - - name: pods + scalar: string + - name: completionTime type: - namedType: io.k8s.api.autoscaling.v2beta1.PodsMetricStatus - - name: resource - type: - namedType: io.k8s.api.autoscaling.v2beta1.ResourceMetricStatus - - name: type - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta1.ObjectMetricSource - map: - fields: - - name: averageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: metricName - type: - scalar: string - default: "" - - name: selector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta1.CrossVersionObjectReference - default: {} - - name: targetValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta1.ObjectMetricStatus - map: - fields: - - name: averageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: currentValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: metricName - type: - scalar: string - default: "" - - name: selector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta1.CrossVersionObjectReference - default: {} -- name: io.k8s.api.autoscaling.v2beta1.PodsMetricSource - map: - fields: - - name: metricName - type: - scalar: string - default: "" - - name: selector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector - - name: targetAverageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta1.PodsMetricStatus - map: - fields: - - name: currentAverageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: metricName - type: - scalar: string - default: "" - - name: selector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector -- name: io.k8s.api.autoscaling.v2beta1.ResourceMetricSource - map: - fields: - - name: name - type: - scalar: string - default: "" - - name: targetAverageUtilization - type: - scalar: numeric - - name: targetAverageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta1.ResourceMetricStatus - map: - fields: - - name: currentAverageUtilization - type: - scalar: numeric - - name: currentAverageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: name - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta2.ContainerResourceMetricSource - map: - fields: - - name: container - type: - scalar: string - default: "" - - name: name - type: - scalar: string - default: "" - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricTarget - default: {} -- name: io.k8s.api.autoscaling.v2beta2.ContainerResourceMetricStatus - map: - fields: - - name: container - type: - scalar: string - default: "" - - name: current - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricValueStatus - default: {} - - name: name - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta2.CrossVersionObjectReference - map: - fields: - - name: apiVersion - type: - scalar: string - - name: kind - type: - scalar: string - default: "" - - name: name - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta2.ExternalMetricSource - map: - fields: - - name: metric - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - default: {} - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricTarget - default: {} -- name: io.k8s.api.autoscaling.v2beta2.ExternalMetricStatus - map: - fields: - - name: current - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricValueStatus - default: {} - - name: metric - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - default: {} -- name: io.k8s.api.autoscaling.v2beta2.HPAScalingPolicy - map: - fields: - - name: periodSeconds - type: - scalar: numeric - default: 0 - - name: type - type: - scalar: string - default: "" - - name: value - type: - scalar: numeric - default: 0 -- name: io.k8s.api.autoscaling.v2beta2.HPAScalingRules - map: - fields: - - name: policies - type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta2.HPAScalingPolicy - elementRelationship: atomic - - name: selectPolicy - type: - scalar: string - - name: stabilizationWindowSeconds - type: - scalar: numeric -- name: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscaler - map: - fields: - - name: apiVersion - type: - scalar: string - - name: kind - type: - scalar: string - - name: metadata - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta - default: {} - - name: spec - type: - namedType: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerSpec - default: {} - - name: status - type: - namedType: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerStatus - default: {} -- name: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerBehavior - map: - fields: - - name: scaleDown - type: - namedType: io.k8s.api.autoscaling.v2beta2.HPAScalingRules - - name: scaleUp - type: - namedType: io.k8s.api.autoscaling.v2beta2.HPAScalingRules -- name: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerCondition - map: - fields: - - name: lastTransitionTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: message - type: - scalar: string - - name: reason - type: - scalar: string - - name: status - type: - scalar: string - default: "" - - name: type - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerSpec - map: - fields: - - name: behavior - type: - namedType: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerBehavior - - name: maxReplicas - type: - scalar: numeric - default: 0 - - name: metrics - type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta2.MetricSpec - elementRelationship: atomic - - name: minReplicas - type: - scalar: numeric - - name: scaleTargetRef - type: - namedType: io.k8s.api.autoscaling.v2beta2.CrossVersionObjectReference - default: {} -- name: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerStatus - map: - fields: - - name: conditions - type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerCondition - elementRelationship: atomic - - name: currentMetrics - type: - list: - elementType: - namedType: io.k8s.api.autoscaling.v2beta2.MetricStatus - elementRelationship: atomic - - name: currentReplicas - type: - scalar: numeric - default: 0 - - name: desiredReplicas - type: - scalar: numeric - default: 0 - - name: lastScaleTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: observedGeneration - type: - scalar: numeric -- name: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - map: - fields: - - name: name - type: - scalar: string - default: "" - - name: selector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector -- name: io.k8s.api.autoscaling.v2beta2.MetricSpec - map: - fields: - - name: containerResource - type: - namedType: io.k8s.api.autoscaling.v2beta2.ContainerResourceMetricSource - - name: external - type: - namedType: io.k8s.api.autoscaling.v2beta2.ExternalMetricSource - - name: object - type: - namedType: io.k8s.api.autoscaling.v2beta2.ObjectMetricSource - - name: pods - type: - namedType: io.k8s.api.autoscaling.v2beta2.PodsMetricSource - - name: resource - type: - namedType: io.k8s.api.autoscaling.v2beta2.ResourceMetricSource - - name: type - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta2.MetricStatus - map: - fields: - - name: containerResource - type: - namedType: io.k8s.api.autoscaling.v2beta2.ContainerResourceMetricStatus - - name: external - type: - namedType: io.k8s.api.autoscaling.v2beta2.ExternalMetricStatus - - name: object - type: - namedType: io.k8s.api.autoscaling.v2beta2.ObjectMetricStatus - - name: pods - type: - namedType: io.k8s.api.autoscaling.v2beta2.PodsMetricStatus - - name: resource - type: - namedType: io.k8s.api.autoscaling.v2beta2.ResourceMetricStatus - - name: type - type: - scalar: string - default: "" -- name: io.k8s.api.autoscaling.v2beta2.MetricTarget - map: - fields: - - name: averageUtilization - type: - scalar: numeric - - name: averageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: type - type: - scalar: string - default: "" - - name: value - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta2.MetricValueStatus - map: - fields: - - name: averageUtilization - type: - scalar: numeric - - name: averageValue - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - - name: value - type: - namedType: io.k8s.apimachinery.pkg.api.resource.Quantity -- name: io.k8s.api.autoscaling.v2beta2.ObjectMetricSource - map: - fields: - - name: describedObject - type: - namedType: io.k8s.api.autoscaling.v2beta2.CrossVersionObjectReference - default: {} - - name: metric - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - default: {} - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricTarget - default: {} -- name: io.k8s.api.autoscaling.v2beta2.ObjectMetricStatus - map: - fields: - - name: current - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricValueStatus - default: {} - - name: describedObject - type: - namedType: io.k8s.api.autoscaling.v2beta2.CrossVersionObjectReference - default: {} - - name: metric - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - default: {} -- name: io.k8s.api.autoscaling.v2beta2.PodsMetricSource - map: - fields: - - name: metric - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - default: {} - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricTarget - default: {} -- name: io.k8s.api.autoscaling.v2beta2.PodsMetricStatus - map: - fields: - - name: current - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricValueStatus - default: {} - - name: metric - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricIdentifier - default: {} -- name: io.k8s.api.autoscaling.v2beta2.ResourceMetricSource - map: - fields: - - name: name - type: - scalar: string - default: "" - - name: target - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricTarget - default: {} -- name: io.k8s.api.autoscaling.v2beta2.ResourceMetricStatus - map: - fields: - - name: current - type: - namedType: io.k8s.api.autoscaling.v2beta2.MetricValueStatus - default: {} - - name: name - type: - scalar: string - default: "" -- name: io.k8s.api.batch.v1.CronJob - map: - fields: - - name: apiVersion - type: - scalar: string - - name: kind - type: - scalar: string - - name: metadata - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta - default: {} - - name: spec - type: - namedType: io.k8s.api.batch.v1.CronJobSpec - default: {} - - name: status - type: - namedType: io.k8s.api.batch.v1.CronJobStatus - default: {} -- name: io.k8s.api.batch.v1.CronJobSpec - map: - fields: - - name: concurrencyPolicy - type: - scalar: string - - name: failedJobsHistoryLimit - type: - scalar: numeric - - name: jobTemplate - type: - namedType: io.k8s.api.batch.v1.JobTemplateSpec - default: {} - - name: schedule - type: - scalar: string - default: "" - - name: startingDeadlineSeconds - type: - scalar: numeric - - name: successfulJobsHistoryLimit - type: - scalar: numeric - - name: suspend - type: - scalar: boolean - - name: timeZone - type: - scalar: string -- name: io.k8s.api.batch.v1.CronJobStatus - map: - fields: - - name: active - type: - list: - elementType: - namedType: io.k8s.api.core.v1.ObjectReference - elementRelationship: atomic - - name: lastScheduleTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: lastSuccessfulTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time -- name: io.k8s.api.batch.v1.Job - map: - fields: - - name: apiVersion - type: - scalar: string - - name: kind - type: - scalar: string - - name: metadata - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta - default: {} - - name: spec - type: - namedType: io.k8s.api.batch.v1.JobSpec - default: {} - - name: status - type: - namedType: io.k8s.api.batch.v1.JobStatus - default: {} -- name: io.k8s.api.batch.v1.JobCondition - map: - fields: - - name: lastProbeTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: lastTransitionTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: message - type: - scalar: string - - name: reason - type: - scalar: string - - name: status - type: - scalar: string - default: "" - - name: type - type: - scalar: string - default: "" -- name: io.k8s.api.batch.v1.JobSpec - map: - fields: - - name: activeDeadlineSeconds - type: - scalar: numeric - - name: backoffLimit - type: - scalar: numeric - - name: backoffLimitPerIndex - type: - scalar: numeric - - name: completionMode - type: - scalar: string - - name: completions - type: - scalar: numeric - - name: managedBy - type: - scalar: string - - name: manualSelector - type: - scalar: boolean - - name: maxFailedIndexes - type: - scalar: numeric - - name: parallelism - type: - scalar: numeric - - name: podFailurePolicy - type: - namedType: io.k8s.api.batch.v1.PodFailurePolicy - - name: podReplacementPolicy - type: - scalar: string - - name: selector - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector - - name: successPolicy - type: - namedType: io.k8s.api.batch.v1.SuccessPolicy - - name: suspend - type: - scalar: boolean - - name: template - type: - namedType: io.k8s.api.core.v1.PodTemplateSpec - default: {} - - name: ttlSecondsAfterFinished - type: - scalar: numeric -- name: io.k8s.api.batch.v1.JobStatus - map: - fields: - - name: active - type: - scalar: numeric - - name: completedIndexes - type: - scalar: string - - name: completionTime - type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - - name: conditions + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: conditions type: list: elementType: @@ -4643,6 +4089,9 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" + - name: stubPKCS10Request + type: + scalar: string - name: unverifiedUserAnnotations type: map: @@ -6292,6 +5741,12 @@ var schemaYAML = typed.YAMLObject(`types: - name: reference type: scalar: string +- name: io.k8s.api.core.v1.ImageVolumeStatus + map: + fields: + - name: imageRef + type: + scalar: string - name: io.k8s.api.core.v1.KeyToPath map: fields: @@ -6585,6 +6040,24 @@ var schemaYAML = typed.YAMLObject(`types: - name: requiredDuringSchedulingIgnoredDuringExecution type: namedType: io.k8s.api.core.v1.NodeSelector +- name: io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus + map: + fields: + - name: containers + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: resourceClaimName + type: + scalar: string + default: "" + - name: resources + type: + map: + elementType: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - name: io.k8s.api.core.v1.NodeCondition map: fields: @@ -7418,6 +6891,16 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" +- name: io.k8s.api.core.v1.PodSchedulingGroup + map: + fields: + - name: podGroupName + type: + scalar: string + unions: + - fields: + - fieldName: podGroupName + discriminatorValue: PodGroupName - name: io.k8s.api.core.v1.PodSecurityContext map: fields: @@ -7605,6 +7088,9 @@ var schemaYAML = typed.YAMLObject(`types: elementRelationship: associative keys: - name + - name: schedulingGroup + type: + namedType: io.k8s.api.core.v1.PodSchedulingGroup - name: securityContext type: namedType: io.k8s.api.core.v1.PodSecurityContext @@ -7649,9 +7135,6 @@ var schemaYAML = typed.YAMLObject(`types: elementRelationship: associative keys: - name - - name: workloadRef - type: - namedType: io.k8s.api.core.v1.WorkloadReference - name: io.k8s.api.core.v1.PodStatus map: fields: @@ -7701,6 +7184,12 @@ var schemaYAML = typed.YAMLObject(`types: - name: message type: scalar: string + - name: nodeAllocatableResourceClaimStatuses + type: + list: + elementType: + namedType: io.k8s.api.core.v1.NodeAllocatableResourceClaimStatus + elementRelationship: atomic - name: nominatedNodeName type: scalar: string @@ -8064,6 +7553,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: health type: scalar: string + - name: message + type: + scalar: string - name: resourceID type: scalar: string @@ -8952,6 +8444,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: recursiveReadOnly type: scalar: string + - name: volumeStatus + type: + namedType: io.k8s.api.core.v1.VolumeStatus - name: io.k8s.api.core.v1.VolumeNodeAffinity map: fields: @@ -8992,6 +8487,12 @@ var schemaYAML = typed.YAMLObject(`types: map: elementType: namedType: io.k8s.apimachinery.pkg.api.resource.Quantity +- name: io.k8s.api.core.v1.VolumeStatus + map: + fields: + - name: image + type: + namedType: io.k8s.api.core.v1.ImageVolumeStatus - name: io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource map: fields: @@ -9034,20 +8535,6 @@ var schemaYAML = typed.YAMLObject(`types: - name: runAsUserName type: scalar: string -- name: io.k8s.api.core.v1.WorkloadReference - map: - fields: - - name: name - type: - scalar: string - default: "" - - name: podGroup - type: - scalar: string - default: "" - - name: podGroupReplicaKey - type: - scalar: string - name: io.k8s.api.discovery.v1.Endpoint map: fields: @@ -12989,6 +12476,11 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" + - name: nodeAllocatableResourceMappings + type: + map: + elementType: + namedType: io.k8s.api.resource.v1.NodeAllocatableResourceMapping - name: nodeName type: scalar: string @@ -13038,15 +12530,39 @@ var schemaYAML = typed.YAMLObject(`types: - name: bool type: scalar: boolean + - name: bools + type: + list: + elementType: + scalar: boolean + elementRelationship: atomic - name: int type: scalar: numeric + - name: ints + type: + list: + elementType: + scalar: numeric + elementRelationship: atomic - name: string type: scalar: string + - name: strings + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: version type: scalar: string + - name: versions + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: io.k8s.api.resource.v1.DeviceCapacity map: fields: @@ -13341,6 +12857,15 @@ var schemaYAML = typed.YAMLObject(`types: elementType: scalar: string elementRelationship: atomic +- name: io.k8s.api.resource.v1.NodeAllocatableResourceMapping + map: + fields: + - name: allocationMultiplier + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: capacityKey + type: + scalar: string - name: io.k8s.api.resource.v1.OpaqueDeviceConfiguration map: fields: @@ -13534,7 +13059,95 @@ var schemaYAML = typed.YAMLObject(`types: - name: value type: scalar: string -- name: io.k8s.api.resource.v1alpha3.DeviceTaintRule +- name: io.k8s.api.resource.v1alpha3.DeviceTaintRule + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: io.k8s.api.resource.v1alpha3.DeviceTaintRuleSpec + default: {} + - name: status + type: + namedType: io.k8s.api.resource.v1alpha3.DeviceTaintRuleStatus + default: {} +- name: io.k8s.api.resource.v1alpha3.DeviceTaintRuleSpec + map: + fields: + - name: deviceSelector + type: + namedType: io.k8s.api.resource.v1alpha3.DeviceTaintSelector + - name: taint + type: + namedType: io.k8s.api.resource.v1alpha3.DeviceTaint + default: {} +- name: io.k8s.api.resource.v1alpha3.DeviceTaintRuleStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type +- name: io.k8s.api.resource.v1alpha3.DeviceTaintSelector + map: + fields: + - name: device + type: + scalar: string + - name: driver + type: + scalar: string + - name: pool + type: + scalar: string +- name: io.k8s.api.resource.v1alpha3.PoolStatus + map: + fields: + - name: allocatedDevices + type: + scalar: numeric + - name: availableDevices + type: + scalar: numeric + - name: driver + type: + scalar: string + - name: generation + type: + scalar: numeric + default: 0 + - name: nodeName + type: + scalar: string + - name: poolName + type: + scalar: string + - name: resourceSliceCount + type: + scalar: numeric + - name: totalDevices + type: + scalar: numeric + - name: unavailableDevices + type: + scalar: numeric + - name: validationError + type: + scalar: string +- name: io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest map: fields: - name: apiVersion @@ -13549,23 +13162,26 @@ var schemaYAML = typed.YAMLObject(`types: default: {} - name: spec type: - namedType: io.k8s.api.resource.v1alpha3.DeviceTaintRuleSpec + namedType: io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec default: {} - name: status type: - namedType: io.k8s.api.resource.v1alpha3.DeviceTaintRuleStatus - default: {} -- name: io.k8s.api.resource.v1alpha3.DeviceTaintRuleSpec + namedType: io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus +- name: io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestSpec map: fields: - - name: deviceSelector + - name: driver type: - namedType: io.k8s.api.resource.v1alpha3.DeviceTaintSelector - - name: taint + scalar: string + default: "" + - name: limit type: - namedType: io.k8s.api.resource.v1alpha3.DeviceTaint - default: {} -- name: io.k8s.api.resource.v1alpha3.DeviceTaintRuleStatus + scalar: numeric + default: 100 + - name: poolName + type: + scalar: string +- name: io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequestStatus map: fields: - name: conditions @@ -13576,18 +13192,15 @@ var schemaYAML = typed.YAMLObject(`types: elementRelationship: associative keys: - type -- name: io.k8s.api.resource.v1alpha3.DeviceTaintSelector - map: - fields: - - name: device - type: - scalar: string - - name: driver + - name: poolCount type: - scalar: string - - name: pool + scalar: numeric + - name: pools type: - scalar: string + list: + elementType: + namedType: io.k8s.api.resource.v1alpha3.PoolStatus + elementRelationship: atomic - name: io.k8s.api.resource.v1beta1.AllocatedDeviceStatus map: fields: @@ -13673,6 +13286,11 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: io.k8s.api.resource.v1beta1.DeviceCounterConsumption elementRelationship: atomic + - name: nodeAllocatableResourceMappings + type: + map: + elementType: + namedType: io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping - name: nodeName type: scalar: string @@ -13792,15 +13410,39 @@ var schemaYAML = typed.YAMLObject(`types: - name: bool type: scalar: boolean + - name: bools + type: + list: + elementType: + scalar: boolean + elementRelationship: atomic - name: int type: scalar: numeric + - name: ints + type: + list: + elementType: + scalar: numeric + elementRelationship: atomic - name: string type: scalar: string + - name: strings + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: version type: scalar: string + - name: versions + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: io.k8s.api.resource.v1beta1.DeviceCapacity map: fields: @@ -14089,6 +13731,15 @@ var schemaYAML = typed.YAMLObject(`types: elementType: scalar: string elementRelationship: atomic +- name: io.k8s.api.resource.v1beta1.NodeAllocatableResourceMapping + map: + fields: + - name: allocationMultiplier + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: capacityKey + type: + scalar: string - name: io.k8s.api.resource.v1beta1.OpaqueDeviceConfiguration map: fields: @@ -14414,6 +14065,11 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" + - name: nodeAllocatableResourceMappings + type: + map: + elementType: + namedType: io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping - name: nodeName type: scalar: string @@ -14463,15 +14119,39 @@ var schemaYAML = typed.YAMLObject(`types: - name: bool type: scalar: boolean + - name: bools + type: + list: + elementType: + scalar: boolean + elementRelationship: atomic - name: int type: scalar: numeric + - name: ints + type: + list: + elementType: + scalar: numeric + elementRelationship: atomic - name: string type: scalar: string + - name: strings + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: version type: scalar: string + - name: versions + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: io.k8s.api.resource.v1beta2.DeviceCapacity map: fields: @@ -14701,6 +14381,60 @@ var schemaYAML = typed.YAMLObject(`types: - name: value type: scalar: string +- name: io.k8s.api.resource.v1beta2.DeviceTaintRule + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec + default: {} + - name: status + type: + namedType: io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus + default: {} +- name: io.k8s.api.resource.v1beta2.DeviceTaintRuleSpec + map: + fields: + - name: deviceSelector + type: + namedType: io.k8s.api.resource.v1beta2.DeviceTaintSelector + - name: taint + type: + namedType: io.k8s.api.resource.v1beta2.DeviceTaint + default: {} +- name: io.k8s.api.resource.v1beta2.DeviceTaintRuleStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type +- name: io.k8s.api.resource.v1beta2.DeviceTaintSelector + map: + fields: + - name: device + type: + scalar: string + - name: driver + type: + scalar: string + - name: pool + type: + scalar: string - name: io.k8s.api.resource.v1beta2.DeviceToleration map: fields: @@ -14766,6 +14500,15 @@ var schemaYAML = typed.YAMLObject(`types: elementType: scalar: string elementRelationship: atomic +- name: io.k8s.api.resource.v1beta2.NodeAllocatableResourceMapping + map: + fields: + - name: allocationMultiplier + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: capacityKey + type: + scalar: string - name: io.k8s.api.resource.v1beta2.OpaqueDeviceConfiguration map: fields: @@ -14968,7 +14711,7 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: numeric default: 0 -- name: io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy +- name: io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy map: elementType: scalar: untyped @@ -14980,60 +14723,180 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: __untyped_deduced_ elementRelationship: separable -- name: io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy +- name: io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy map: fields: - name: minCount type: scalar: numeric default: 0 -- name: io.k8s.api.scheduling.v1alpha1.PodGroup +- name: io.k8s.api.scheduling.v1alpha2.PodGroup + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupSpec + default: {} + - name: status + type: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupStatus + default: {} +- name: io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim map: fields: - name: name type: scalar: string default: "" - - name: policy + - name: resourceClaimName type: - namedType: io.k8s.api.scheduling.v1alpha1.PodGroupPolicy - default: {} -- name: io.k8s.api.scheduling.v1alpha1.PodGroupPolicy + scalar: string + - name: resourceClaimTemplateName + type: + scalar: string +- name: io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus + map: + fields: + - name: name + type: + scalar: string + default: "" + - name: resourceClaimName + type: + scalar: string +- name: io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints + map: + fields: + - name: topology + type: + list: + elementType: + namedType: io.k8s.api.scheduling.v1alpha2.TopologyConstraint + elementRelationship: atomic +- name: io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy map: fields: - name: basic type: - namedType: io.k8s.api.scheduling.v1alpha1.BasicSchedulingPolicy + namedType: io.k8s.api.scheduling.v1alpha2.BasicSchedulingPolicy - name: gang type: - namedType: io.k8s.api.scheduling.v1alpha1.GangSchedulingPolicy -- name: io.k8s.api.scheduling.v1alpha1.PriorityClass + namedType: io.k8s.api.scheduling.v1alpha2.GangSchedulingPolicy + unions: + - fields: + - fieldName: basic + discriminatorValue: Basic + - fieldName: gang + discriminatorValue: Gang +- name: io.k8s.api.scheduling.v1alpha2.PodGroupSpec map: fields: - - name: apiVersion + - name: disruptionMode type: scalar: string - - name: description + default: Pod + - name: podGroupTemplateRef type: - scalar: string - - name: globalDefault + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference + - name: priority type: - scalar: boolean - - name: kind + scalar: numeric + - name: priorityClassName type: scalar: string - - name: metadata + - name: resourceClaims type: - namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + list: + elementType: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim + elementRelationship: associative + keys: + - name + - name: schedulingConstraints + type: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints + - name: schedulingPolicy + type: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy default: {} - - name: preemptionPolicy +- name: io.k8s.api.scheduling.v1alpha2.PodGroupStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: resourceClaimStatuses + type: + list: + elementType: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaimStatus + elementRelationship: associative + keys: + - name +- name: io.k8s.api.scheduling.v1alpha2.PodGroupTemplate + map: + fields: + - name: disruptionMode type: scalar: string - - name: value + - name: name + type: + scalar: string + default: "" + - name: priority type: scalar: numeric - default: 0 -- name: io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference + - name: priorityClassName + type: + scalar: string + - name: resourceClaims + type: + list: + elementType: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupResourceClaim + elementRelationship: associative + keys: + - name + - name: schedulingConstraints + type: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingConstraints + - name: schedulingPolicy + type: + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupSchedulingPolicy + default: {} +- name: io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference + map: + fields: + - name: workload + type: + namedType: io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference + unions: + - fields: + - fieldName: workload + discriminatorValue: Workload +- name: io.k8s.api.scheduling.v1alpha2.TopologyConstraint + map: + fields: + - name: key + type: + scalar: string + default: "" +- name: io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference map: fields: - name: apiGroup @@ -15047,7 +14910,7 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" -- name: io.k8s.api.scheduling.v1alpha1.Workload +- name: io.k8s.api.scheduling.v1alpha2.Workload map: fields: - name: apiVersion @@ -15062,19 +14925,30 @@ var schemaYAML = typed.YAMLObject(`types: default: {} - name: spec type: - namedType: io.k8s.api.scheduling.v1alpha1.WorkloadSpec + namedType: io.k8s.api.scheduling.v1alpha2.WorkloadSpec default: {} -- name: io.k8s.api.scheduling.v1alpha1.WorkloadSpec +- name: io.k8s.api.scheduling.v1alpha2.WorkloadPodGroupTemplateReference + map: + fields: + - name: podGroupTemplateName + type: + scalar: string + default: "" + - name: workloadName + type: + scalar: string + default: "" +- name: io.k8s.api.scheduling.v1alpha2.WorkloadSpec map: fields: - name: controllerRef type: - namedType: io.k8s.api.scheduling.v1alpha1.TypedLocalObjectReference - - name: podGroups + namedType: io.k8s.api.scheduling.v1alpha2.TypedLocalObjectReference + - name: podGroupTemplates type: list: elementType: - namedType: io.k8s.api.scheduling.v1alpha1.PodGroup + namedType: io.k8s.api.scheduling.v1alpha2.PodGroupTemplate elementRelationship: associative keys: - name @@ -15136,6 +15010,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: podInfoOnMount type: scalar: boolean + - name: preventPodSchedulingIfMissing + type: + scalar: boolean - name: requiresRepublish type: scalar: boolean @@ -15545,6 +15422,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: podInfoOnMount type: scalar: boolean + - name: preventPodSchedulingIfMissing + type: + scalar: boolean - name: requiresRepublish type: scalar: boolean diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/allocationresult.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/allocationresult.go index 83f0b96895659..6e380790ea5d1 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/allocationresult.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/allocationresult.go @@ -36,7 +36,7 @@ type AllocationResultApplyConfiguration struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. AllocationTimestamp *metav1.Time `json:"allocationTimestamp,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/celdeviceselector.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/celdeviceselector.go index 5a77aac95fad7..15ec20da40b04 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/celdeviceselector.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/celdeviceselector.go @@ -72,6 +72,14 @@ type CELDeviceSelectorApplyConfiguration struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/device.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/device.go index ca50f6024acb0..f19b9000cd835 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/device.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/device.go @@ -19,6 +19,7 @@ limitations under the License. package v1 import ( + apicorev1 "k8s.io/api/core/v1" resourcev1 "k8s.io/api/resource/v1" corev1 "k8s.io/client-go/applyconfigurations/core/v1" ) @@ -74,7 +75,7 @@ type DeviceApplyConfiguration struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Taints []DeviceTaintApplyConfiguration `json:"taints,omitempty"` // BindsToNode indicates if the usage of an allocation involving this device @@ -82,7 +83,7 @@ type DeviceApplyConfiguration struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindsToNode *bool `json:"bindsToNode,omitempty"` // BindingConditions defines the conditions for proceeding with binding. @@ -94,7 +95,7 @@ type DeviceApplyConfiguration struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingConditions []string `json:"bindingConditions,omitempty"` // BindingFailureConditions defines the conditions for binding failure. @@ -105,7 +106,7 @@ type DeviceApplyConfiguration struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingFailureConditions []string `json:"bindingFailureConditions,omitempty"` // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -113,6 +114,18 @@ type DeviceApplyConfiguration struct { // If AllowMultipleAllocations is set to true, the device can be allocated more than once, // and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not. AllowMultipleAllocations *bool `json:"allowMultipleAllocations,omitempty"` + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + NodeAllocatableResourceMappings map[apicorev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration `json:"nodeAllocatableResourceMappings,omitempty"` } // DeviceApplyConfiguration constructs a declarative configuration of the Device type for use with @@ -242,3 +255,17 @@ func (b *DeviceApplyConfiguration) WithAllowMultipleAllocations(value bool) *Dev b.AllowMultipleAllocations = &value return b } + +// WithNodeAllocatableResourceMappings puts the entries into the NodeAllocatableResourceMappings field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the NodeAllocatableResourceMappings field, +// overwriting an existing map entries in NodeAllocatableResourceMappings field with the same key. +func (b *DeviceApplyConfiguration) WithNodeAllocatableResourceMappings(entries map[apicorev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration) *DeviceApplyConfiguration { + if b.NodeAllocatableResourceMappings == nil && len(entries) > 0 { + b.NodeAllocatableResourceMappings = make(map[apicorev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration, len(entries)) + } + for k, v := range entries { + b.NodeAllocatableResourceMappings[k] = v + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceattribute.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceattribute.go index e41696ab8822a..9d5b16f27168f 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceattribute.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceattribute.go @@ -32,6 +32,22 @@ type DeviceAttributeApplyConfiguration struct { // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. VersionValue *string `json:"version,omitempty"` + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + IntValues []int64 `json:"ints,omitempty"` + // BoolValues is a non-empty list of true/false values. + BoolValues []bool `json:"bools,omitempty"` + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + StringValues []string `json:"strings,omitempty"` + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + VersionValues []string `json:"versions,omitempty"` } // DeviceAttributeApplyConfiguration constructs a declarative configuration of the DeviceAttribute type for use with @@ -71,3 +87,43 @@ func (b *DeviceAttributeApplyConfiguration) WithVersionValue(value string) *Devi b.VersionValue = &value return b } + +// WithIntValues adds the given value to the IntValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IntValues field. +func (b *DeviceAttributeApplyConfiguration) WithIntValues(values ...int64) *DeviceAttributeApplyConfiguration { + for i := range values { + b.IntValues = append(b.IntValues, values[i]) + } + return b +} + +// WithBoolValues adds the given value to the BoolValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the BoolValues field. +func (b *DeviceAttributeApplyConfiguration) WithBoolValues(values ...bool) *DeviceAttributeApplyConfiguration { + for i := range values { + b.BoolValues = append(b.BoolValues, values[i]) + } + return b +} + +// WithStringValues adds the given value to the StringValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the StringValues field. +func (b *DeviceAttributeApplyConfiguration) WithStringValues(values ...string) *DeviceAttributeApplyConfiguration { + for i := range values { + b.StringValues = append(b.StringValues, values[i]) + } + return b +} + +// WithVersionValues adds the given value to the VersionValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the VersionValues field. +func (b *DeviceAttributeApplyConfiguration) WithVersionValues(values ...string) *DeviceAttributeApplyConfiguration { + for i := range values { + b.VersionValues = append(b.VersionValues, values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclass.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclass.go index 6d3bc7e76d75b..478f90bb042c1 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclass.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclass.go @@ -34,9 +34,6 @@ import ( // device configuration and selectors. It can be referenced in // the device requests of a claim to apply these presets. // Cluster scoped. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type DeviceClassApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` // Standard object metadata diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclassspec.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclassspec.go index a304c7d8d2b69..a6a3992c92f74 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclassspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceclassspec.go @@ -41,7 +41,7 @@ type DeviceClassSpecApplyConfiguration struct { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. ExtendedResourceName *string `json:"extendedResourceName,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceconstraint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceconstraint.go index cd2467e694510..ba13edbb8427f 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceconstraint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/deviceconstraint.go @@ -48,11 +48,21 @@ type DeviceConstraintApplyConfiguration struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as single-element lists for backward compatibility. + // // Must include the domain qualifier. MatchAttribute *resourcev1.FullyQualifiedName `json:"matchAttribute,omitempty"` // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicerequestallocationresult.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicerequestallocationresult.go index 8b38fd2887535..592a641756c06 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicerequestallocationresult.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicerequestallocationresult.go @@ -57,8 +57,7 @@ type DeviceRequestAllocationResultApplyConfiguration struct { // administrative access. See the corresponding request field // for a definition of mode. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. AdminAccess *bool `json:"adminAccess,omitempty"` // A copy of all tolerations specified in the request at the time @@ -66,19 +65,19 @@ type DeviceRequestAllocationResultApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingConditions []string `json:"bindingConditions,omitempty"` // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingFailureConditions []string `json:"bindingFailureConditions,omitempty"` // ShareID uniquely identifies an individual allocation share of the device, diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicesubrequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicesubrequest.go index f3ee27af3c48c..d554522fa1117 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicesubrequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicesubrequest.go @@ -93,7 +93,7 @@ type DeviceSubRequestApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // Capacity define resource requirements against each capacity. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicetaint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicetaint.go index db98da8b87967..c65c88e05b306 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicetaint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/devicetaint.go @@ -43,8 +43,17 @@ type DeviceTaintApplyConfiguration struct { // nodes is not valid here. More effects may get added in the future. // Consumers must treat unknown effects like None. Effect *resourcev1.DeviceTaintEffect `json:"effect,omitempty"` - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. + // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). TimeAdded *metav1.Time `json:"timeAdded,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/exactdevicerequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/exactdevicerequest.go index 130893c6d6841..8eb0fd3526eab 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/exactdevicerequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/exactdevicerequest.go @@ -72,8 +72,7 @@ type ExactDeviceRequestApplyConfiguration struct { // all ordinary claims to the device with respect to access modes and // any resource allocations. // - // This is an alpha field and requires enabling the DRAAdminAccess - // feature gate. Admin access is disabled if this field is unset or + // Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. AdminAccess *bool `json:"adminAccess,omitempty"` // If specified, the request's tolerations. @@ -91,7 +90,7 @@ type ExactDeviceRequestApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // Capacity define resource requirements against each capacity. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/networkdevicedata.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/networkdevicedata.go index 5d0ee8fc50168..9cb1b4dea5bf5 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/networkdevicedata.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/networkdevicedata.go @@ -29,7 +29,7 @@ type NetworkDeviceDataApplyConfiguration struct { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. InterfaceName *string `json:"interfaceName,omitempty"` // IPs lists the network addresses assigned to the device's network interface. // This can include both IPv4 and IPv6 addresses. @@ -39,7 +39,7 @@ type NetworkDeviceDataApplyConfiguration struct { IPs []string `json:"ips,omitempty"` // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. HardwareAddress *string `json:"hardwareAddress,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/nodeallocatableresourcemapping.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/nodeallocatableresourcemapping.go new file mode 100644 index 0000000000000..600d96af892c1 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/nodeallocatableresourcemapping.go @@ -0,0 +1,84 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + resourcev1 "k8s.io/api/resource/v1" + resource "k8s.io/apimachinery/pkg/api/resource" +) + +// NodeAllocatableResourceMappingApplyConfiguration represents a declarative configuration of the NodeAllocatableResourceMapping type for use +// with apply. +// +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +type NodeAllocatableResourceMappingApplyConfiguration struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + CapacityKey *resourcev1.QualifiedName `json:"capacityKey,omitempty"` + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + AllocationMultiplier *resource.Quantity `json:"allocationMultiplier,omitempty"` +} + +// NodeAllocatableResourceMappingApplyConfiguration constructs a declarative configuration of the NodeAllocatableResourceMapping type for use with +// apply. +func NodeAllocatableResourceMapping() *NodeAllocatableResourceMappingApplyConfiguration { + return &NodeAllocatableResourceMappingApplyConfiguration{} +} + +// WithCapacityKey sets the CapacityKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CapacityKey field is set to the value of the last call. +func (b *NodeAllocatableResourceMappingApplyConfiguration) WithCapacityKey(value resourcev1.QualifiedName) *NodeAllocatableResourceMappingApplyConfiguration { + b.CapacityKey = &value + return b +} + +// WithAllocationMultiplier sets the AllocationMultiplier field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllocationMultiplier field is set to the value of the last call. +func (b *NodeAllocatableResourceMappingApplyConfiguration) WithAllocationMultiplier(value resource.Quantity) *NodeAllocatableResourceMappingApplyConfiguration { + b.AllocationMultiplier = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaim.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaim.go index 240e181846495..5a87ea3cbc01d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaim.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaim.go @@ -35,9 +35,6 @@ import ( // with specific properties, this is how that request is expressed. The status // stanza tracks whether this claim has been satisfied and what specific // resources have been allocated. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type ResourceClaimApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` // Standard object metadata diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaimtemplate.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaimtemplate.go index 7bbadb7009512..cee18f3797a30 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaimtemplate.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceclaimtemplate.go @@ -31,9 +31,6 @@ import ( // with apply. // // ResourceClaimTemplate is used to produce ResourceClaim objects. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type ResourceClaimTemplateApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` // Standard object metadata diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceslice.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceslice.go index 6c7ddf41e3f75..7a3eec9d5c119 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceslice.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1/resourceslice.go @@ -51,9 +51,6 @@ import ( // // For resources that are not local to a node, the node name is not set. Instead, // the driver may use a node selector to specify where the devices are available. -// -// This is an alpha type and requires enabling the DynamicResourceAllocation -// feature gate. type ResourceSliceApplyConfiguration struct { metav1.TypeMetaApplyConfiguration `json:",inline"` // Standard object metadata diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaint.go index d9c2c6f48d9a0..719168dbeeb69 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaint.go @@ -43,8 +43,17 @@ type DeviceTaintApplyConfiguration struct { // nodes is not valid here. More effects may get added in the future. // Consumers must treat unknown effects like None. Effect *resourcev1alpha3.DeviceTaintEffect `json:"effect,omitempty"` - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. + // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). TimeAdded *v1.Time `json:"timeAdded,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/poolstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/poolstatus.go new file mode 100644 index 0000000000000..343fe8247dcf8 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/poolstatus.go @@ -0,0 +1,152 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha3 + +// PoolStatusApplyConfiguration represents a declarative configuration of the PoolStatus type for use +// with apply. +// +// PoolStatus contains status information for a single resource pool. +type PoolStatusApplyConfiguration struct { + // Driver is the DRA driver name for this pool. + // Must be a DNS subdomain (e.g., "gpu.example.com"). + Driver *string `json:"driver,omitempty"` + // PoolName is the name of the pool. + // Must be a valid resource pool name (DNS subdomains separated by "/"). + PoolName *string `json:"poolName,omitempty"` + // Generation is the pool generation observed across all ResourceSlices + // in this pool. Only the latest generation is reported. During a generation + // rollout, if not all slices at the latest generation have been published, + // the pool is included with a validationError and device counts unset. + Generation *int64 `json:"generation,omitempty"` + // ResourceSliceCount is the number of ResourceSlices that make up this pool. + // May be unset when validationError is set. + ResourceSliceCount *int32 `json:"resourceSliceCount,omitempty"` + // TotalDevices is the total number of devices in the pool across all slices. + // A value of 0 means the pool has no devices. + // May be unset when validationError is set. + TotalDevices *int32 `json:"totalDevices,omitempty"` + // AllocatedDevices is the number of devices currently allocated to claims. + // A value of 0 means no devices are allocated. + // May be unset when validationError is set. + AllocatedDevices *int32 `json:"allocatedDevices,omitempty"` + // AvailableDevices is the number of devices available for allocation. + // This equals TotalDevices - AllocatedDevices - UnavailableDevices. + // A value of 0 means no devices are currently available. + // May be unset when validationError is set. + AvailableDevices *int32 `json:"availableDevices,omitempty"` + // UnavailableDevices is the number of devices that are not available + // due to taints or other conditions, but are not allocated. + // A value of 0 means all unallocated devices are available. + // May be unset when validationError is set. + UnavailableDevices *int32 `json:"unavailableDevices,omitempty"` + // NodeName is the node this pool is associated with. + // When omitted, the pool is not associated with a specific node. + // Must be a valid DNS subdomain name (RFC1123). + NodeName *string `json:"nodeName,omitempty"` + // ValidationError is set when the pool's data could not be fully + // validated (e.g., incomplete slice publication). When set, device + // count fields and ResourceSliceCount may be unset. + ValidationError *string `json:"validationError,omitempty"` +} + +// PoolStatusApplyConfiguration constructs a declarative configuration of the PoolStatus type for use with +// apply. +func PoolStatus() *PoolStatusApplyConfiguration { + return &PoolStatusApplyConfiguration{} +} + +// WithDriver sets the Driver field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Driver field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithDriver(value string) *PoolStatusApplyConfiguration { + b.Driver = &value + return b +} + +// WithPoolName sets the PoolName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PoolName field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithPoolName(value string) *PoolStatusApplyConfiguration { + b.PoolName = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithGeneration(value int64) *PoolStatusApplyConfiguration { + b.Generation = &value + return b +} + +// WithResourceSliceCount sets the ResourceSliceCount field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceSliceCount field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithResourceSliceCount(value int32) *PoolStatusApplyConfiguration { + b.ResourceSliceCount = &value + return b +} + +// WithTotalDevices sets the TotalDevices field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TotalDevices field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithTotalDevices(value int32) *PoolStatusApplyConfiguration { + b.TotalDevices = &value + return b +} + +// WithAllocatedDevices sets the AllocatedDevices field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllocatedDevices field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithAllocatedDevices(value int32) *PoolStatusApplyConfiguration { + b.AllocatedDevices = &value + return b +} + +// WithAvailableDevices sets the AvailableDevices field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AvailableDevices field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithAvailableDevices(value int32) *PoolStatusApplyConfiguration { + b.AvailableDevices = &value + return b +} + +// WithUnavailableDevices sets the UnavailableDevices field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UnavailableDevices field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithUnavailableDevices(value int32) *PoolStatusApplyConfiguration { + b.UnavailableDevices = &value + return b +} + +// WithNodeName sets the NodeName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NodeName field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithNodeName(value string) *PoolStatusApplyConfiguration { + b.NodeName = &value + return b +} + +// WithValidationError sets the ValidationError field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ValidationError field is set to the value of the last call. +func (b *PoolStatusApplyConfiguration) WithValidationError(value string) *PoolStatusApplyConfiguration { + b.ValidationError = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequest.go new file mode 100644 index 0000000000000..aee4d0ed90229 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequest.go @@ -0,0 +1,295 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + internal "k8s.io/client-go/applyconfigurations/internal" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ResourcePoolStatusRequestApplyConfiguration represents a declarative configuration of the ResourcePoolStatusRequest type for use +// with apply. +// +// ResourcePoolStatusRequest triggers a one-time calculation of resource pool status +// based on the provided filters. Once status is set, the request is considered complete and will not be reprocessed. +// Users should delete and recreate requests to get updated information. +type ResourcePoolStatusRequestApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + // Standard object metadata + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // Spec defines the filters for which pools to include in the status. + // The spec is immutable once created. + Spec *ResourcePoolStatusRequestSpecApplyConfiguration `json:"spec,omitempty"` + // Status is populated by the controller with the calculated pool status. + // When status is non-nil, the request is considered complete and the + // entire object becomes immutable. + Status *ResourcePoolStatusRequestStatusApplyConfiguration `json:"status,omitempty"` +} + +// ResourcePoolStatusRequest constructs a declarative configuration of the ResourcePoolStatusRequest type for use with +// apply. +func ResourcePoolStatusRequest(name string) *ResourcePoolStatusRequestApplyConfiguration { + b := &ResourcePoolStatusRequestApplyConfiguration{} + b.WithName(name) + b.WithKind("ResourcePoolStatusRequest") + b.WithAPIVersion("resource.k8s.io/v1alpha3") + return b +} + +// ExtractResourcePoolStatusRequestFrom extracts the applied configuration owned by fieldManager from +// resourcePoolStatusRequest for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// resourcePoolStatusRequest must be a unmodified ResourcePoolStatusRequest API object that was retrieved from the Kubernetes API. +// ExtractResourcePoolStatusRequestFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractResourcePoolStatusRequestFrom(resourcePoolStatusRequest *resourcev1alpha3.ResourcePoolStatusRequest, fieldManager string, subresource string) (*ResourcePoolStatusRequestApplyConfiguration, error) { + b := &ResourcePoolStatusRequestApplyConfiguration{} + err := managedfields.ExtractInto(resourcePoolStatusRequest, internal.Parser().Type("io.k8s.api.resource.v1alpha3.ResourcePoolStatusRequest"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(resourcePoolStatusRequest.Name) + + b.WithKind("ResourcePoolStatusRequest") + b.WithAPIVersion("resource.k8s.io/v1alpha3") + return b, nil +} + +// ExtractResourcePoolStatusRequest extracts the applied configuration owned by fieldManager from +// resourcePoolStatusRequest. If no managedFields are found in resourcePoolStatusRequest for fieldManager, a +// ResourcePoolStatusRequestApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// resourcePoolStatusRequest must be a unmodified ResourcePoolStatusRequest API object that was retrieved from the Kubernetes API. +// ExtractResourcePoolStatusRequest provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractResourcePoolStatusRequest(resourcePoolStatusRequest *resourcev1alpha3.ResourcePoolStatusRequest, fieldManager string) (*ResourcePoolStatusRequestApplyConfiguration, error) { + return ExtractResourcePoolStatusRequestFrom(resourcePoolStatusRequest, fieldManager, "") +} + +// ExtractResourcePoolStatusRequestStatus extracts the applied configuration owned by fieldManager from +// resourcePoolStatusRequest for the status subresource. +func ExtractResourcePoolStatusRequestStatus(resourcePoolStatusRequest *resourcev1alpha3.ResourcePoolStatusRequest, fieldManager string) (*ResourcePoolStatusRequestApplyConfiguration, error) { + return ExtractResourcePoolStatusRequestFrom(resourcePoolStatusRequest, fieldManager, "status") +} + +func (b ResourcePoolStatusRequestApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithKind(value string) *ResourcePoolStatusRequestApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithAPIVersion(value string) *ResourcePoolStatusRequestApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithName(value string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithGenerateName(value string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithNamespace(value string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithUID(value types.UID) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithResourceVersion(value string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithGeneration(value int64) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithLabels(entries map[string]string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithAnnotations(entries map[string]string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithFinalizers(values ...string) *ResourcePoolStatusRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ResourcePoolStatusRequestApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithSpec(value *ResourcePoolStatusRequestSpecApplyConfiguration) *ResourcePoolStatusRequestApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *ResourcePoolStatusRequestApplyConfiguration) WithStatus(value *ResourcePoolStatusRequestStatusApplyConfiguration) *ResourcePoolStatusRequestApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ResourcePoolStatusRequestApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ResourcePoolStatusRequestApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ResourcePoolStatusRequestApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ResourcePoolStatusRequestApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequestspec.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequestspec.go new file mode 100644 index 0000000000000..bb867273217c7 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequestspec.go @@ -0,0 +1,73 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha3 + +// ResourcePoolStatusRequestSpecApplyConfiguration represents a declarative configuration of the ResourcePoolStatusRequestSpec type for use +// with apply. +// +// ResourcePoolStatusRequestSpec defines the filters for the pool status request. +type ResourcePoolStatusRequestSpecApplyConfiguration struct { + // Driver specifies the DRA driver name to filter pools. + // Only pools from ResourceSlices with this driver will be included. + // Must be a DNS subdomain (e.g., "gpu.example.com"). + Driver *string `json:"driver,omitempty"` + // PoolName optionally filters to a specific pool name. + // If not specified, all pools from the specified driver are included. + // When specified, must be a non-empty valid resource pool name + // (DNS subdomains separated by "/"). + PoolName *string `json:"poolName,omitempty"` + // Limit optionally specifies the maximum number of pools to return in the status. + // If more pools match the filter criteria, the response will be truncated + // (i.e., len(status.pools) < status.poolCount). + // + // Default: 100 + // Minimum: 1 + // Maximum: 1000 + Limit *int32 `json:"limit,omitempty"` +} + +// ResourcePoolStatusRequestSpecApplyConfiguration constructs a declarative configuration of the ResourcePoolStatusRequestSpec type for use with +// apply. +func ResourcePoolStatusRequestSpec() *ResourcePoolStatusRequestSpecApplyConfiguration { + return &ResourcePoolStatusRequestSpecApplyConfiguration{} +} + +// WithDriver sets the Driver field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Driver field is set to the value of the last call. +func (b *ResourcePoolStatusRequestSpecApplyConfiguration) WithDriver(value string) *ResourcePoolStatusRequestSpecApplyConfiguration { + b.Driver = &value + return b +} + +// WithPoolName sets the PoolName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PoolName field is set to the value of the last call. +func (b *ResourcePoolStatusRequestSpecApplyConfiguration) WithPoolName(value string) *ResourcePoolStatusRequestSpecApplyConfiguration { + b.PoolName = &value + return b +} + +// WithLimit sets the Limit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Limit field is set to the value of the last call. +func (b *ResourcePoolStatusRequestSpecApplyConfiguration) WithLimit(value int32) *ResourcePoolStatusRequestSpecApplyConfiguration { + b.Limit = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequeststatus.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequeststatus.go new file mode 100644 index 0000000000000..124c7029007ec --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/resourcepoolstatusrequeststatus.go @@ -0,0 +1,87 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ResourcePoolStatusRequestStatusApplyConfiguration represents a declarative configuration of the ResourcePoolStatusRequestStatus type for use +// with apply. +// +// ResourcePoolStatusRequestStatus contains the calculated pool status information. +type ResourcePoolStatusRequestStatusApplyConfiguration struct { + // PoolCount is the total number of pools that matched the filter criteria, + // regardless of truncation. This helps users understand how many pools exist + // even when the response is truncated. A value of 0 means no pools matched + // the filter criteria. + PoolCount *int32 `json:"poolCount,omitempty"` + // Pools contains the first `spec.limit` matching pools, sorted by driver + // then pool name. If `len(pools) < poolCount`, the list was truncated. + // When omitted, no pools matched the request filters. + Pools []PoolStatusApplyConfiguration `json:"pools,omitempty"` + // Conditions provide information about the state of the request. + // A condition with type=Complete or type=Failed will always be set + // when the status is populated. + // + // Known condition types: + // - "Complete": True when the request has been processed successfully + // - "Failed": True when the request could not be processed + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// ResourcePoolStatusRequestStatusApplyConfiguration constructs a declarative configuration of the ResourcePoolStatusRequestStatus type for use with +// apply. +func ResourcePoolStatusRequestStatus() *ResourcePoolStatusRequestStatusApplyConfiguration { + return &ResourcePoolStatusRequestStatusApplyConfiguration{} +} + +// WithPoolCount sets the PoolCount field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PoolCount field is set to the value of the last call. +func (b *ResourcePoolStatusRequestStatusApplyConfiguration) WithPoolCount(value int32) *ResourcePoolStatusRequestStatusApplyConfiguration { + b.PoolCount = &value + return b +} + +// WithPools adds the given value to the Pools field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Pools field. +func (b *ResourcePoolStatusRequestStatusApplyConfiguration) WithPools(values ...*PoolStatusApplyConfiguration) *ResourcePoolStatusRequestStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithPools") + } + b.Pools = append(b.Pools, *values[i]) + } + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *ResourcePoolStatusRequestStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *ResourcePoolStatusRequestStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/allocationresult.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/allocationresult.go index f6fd7e2034fd5..594dc364bcba2 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/allocationresult.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/allocationresult.go @@ -36,7 +36,7 @@ type AllocationResultApplyConfiguration struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. AllocationTimestamp *metav1.Time `json:"allocationTimestamp,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/basicdevice.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/basicdevice.go index 8a0241a2f3ae6..64527b29389c4 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/basicdevice.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/basicdevice.go @@ -19,6 +19,7 @@ limitations under the License. package v1beta1 import ( + corev1 "k8s.io/api/core/v1" resourcev1beta1 "k8s.io/api/resource/v1beta1" v1 "k8s.io/client-go/applyconfigurations/core/v1" ) @@ -70,7 +71,7 @@ type BasicDeviceApplyConfiguration struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Taints []DeviceTaintApplyConfiguration `json:"taints,omitempty"` // BindsToNode indicates if the usage of an allocation involving this device @@ -78,7 +79,7 @@ type BasicDeviceApplyConfiguration struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindsToNode *bool `json:"bindsToNode,omitempty"` // BindingConditions defines the conditions for proceeding with binding. @@ -90,7 +91,7 @@ type BasicDeviceApplyConfiguration struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingConditions []string `json:"bindingConditions,omitempty"` // BindingFailureConditions defines the conditions for binding failure. @@ -101,7 +102,7 @@ type BasicDeviceApplyConfiguration struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingFailureConditions []string `json:"bindingFailureConditions,omitempty"` // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -109,6 +110,18 @@ type BasicDeviceApplyConfiguration struct { // If AllowMultipleAllocations is set to true, the device can be allocated more than once, // and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not. AllowMultipleAllocations *bool `json:"allowMultipleAllocations,omitempty"` + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + NodeAllocatableResourceMappings map[corev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration `json:"nodeAllocatableResourceMappings,omitempty"` } // BasicDeviceApplyConfiguration constructs a declarative configuration of the BasicDevice type for use with @@ -230,3 +243,17 @@ func (b *BasicDeviceApplyConfiguration) WithAllowMultipleAllocations(value bool) b.AllowMultipleAllocations = &value return b } + +// WithNodeAllocatableResourceMappings puts the entries into the NodeAllocatableResourceMappings field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the NodeAllocatableResourceMappings field, +// overwriting an existing map entries in NodeAllocatableResourceMappings field with the same key. +func (b *BasicDeviceApplyConfiguration) WithNodeAllocatableResourceMappings(entries map[corev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration) *BasicDeviceApplyConfiguration { + if b.NodeAllocatableResourceMappings == nil && len(entries) > 0 { + b.NodeAllocatableResourceMappings = make(map[corev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration, len(entries)) + } + for k, v := range entries { + b.NodeAllocatableResourceMappings[k] = v + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/celdeviceselector.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/celdeviceselector.go index 42708c5c9f830..ba65467ae5d0e 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/celdeviceselector.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/celdeviceselector.go @@ -72,6 +72,14 @@ type CELDeviceSelectorApplyConfiguration struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceattribute.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceattribute.go index a4e9fe8504c6e..178f70fb76a24 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceattribute.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceattribute.go @@ -32,6 +32,22 @@ type DeviceAttributeApplyConfiguration struct { // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. VersionValue *string `json:"version,omitempty"` + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + IntValues []int64 `json:"ints,omitempty"` + // BoolValues is a non-empty list of true/false values. + BoolValues []bool `json:"bools,omitempty"` + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + StringValues []string `json:"strings,omitempty"` + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + VersionValues []string `json:"versions,omitempty"` } // DeviceAttributeApplyConfiguration constructs a declarative configuration of the DeviceAttribute type for use with @@ -71,3 +87,43 @@ func (b *DeviceAttributeApplyConfiguration) WithVersionValue(value string) *Devi b.VersionValue = &value return b } + +// WithIntValues adds the given value to the IntValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IntValues field. +func (b *DeviceAttributeApplyConfiguration) WithIntValues(values ...int64) *DeviceAttributeApplyConfiguration { + for i := range values { + b.IntValues = append(b.IntValues, values[i]) + } + return b +} + +// WithBoolValues adds the given value to the BoolValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the BoolValues field. +func (b *DeviceAttributeApplyConfiguration) WithBoolValues(values ...bool) *DeviceAttributeApplyConfiguration { + for i := range values { + b.BoolValues = append(b.BoolValues, values[i]) + } + return b +} + +// WithStringValues adds the given value to the StringValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the StringValues field. +func (b *DeviceAttributeApplyConfiguration) WithStringValues(values ...string) *DeviceAttributeApplyConfiguration { + for i := range values { + b.StringValues = append(b.StringValues, values[i]) + } + return b +} + +// WithVersionValues adds the given value to the VersionValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the VersionValues field. +func (b *DeviceAttributeApplyConfiguration) WithVersionValues(values ...string) *DeviceAttributeApplyConfiguration { + for i := range values { + b.VersionValues = append(b.VersionValues, values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceclassspec.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceclassspec.go index 942da3efed838..c234bdd73299e 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceclassspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceclassspec.go @@ -41,7 +41,7 @@ type DeviceClassSpecApplyConfiguration struct { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. ExtendedResourceName *string `json:"extendedResourceName,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceconstraint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceconstraint.go index 1716c9233109a..8cbdee16d9498 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceconstraint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/deviceconstraint.go @@ -48,11 +48,21 @@ type DeviceConstraintApplyConfiguration struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. MatchAttribute *resourcev1beta1.FullyQualifiedName `json:"matchAttribute,omitempty"` // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go index eab54ba01c1fb..fdcb928a573a7 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go @@ -133,7 +133,7 @@ type DeviceRequestApplyConfiguration struct { // This field can only be set when deviceClassName is set and no subrequests // are specified in the firstAvailable list. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // Capacity define resource requirements against each capacity. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequestallocationresult.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequestallocationresult.go index 2171522a35f02..3a47b9c1f68ad 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequestallocationresult.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequestallocationresult.go @@ -66,19 +66,19 @@ type DeviceRequestAllocationResultApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingConditions []string `json:"bindingConditions,omitempty"` // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingFailureConditions []string `json:"bindingFailureConditions,omitempty"` // ShareID uniquely identifies an individual allocation share of the device, diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go index 2701074e90559..ae7bf65a0403c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go @@ -94,7 +94,7 @@ type DeviceSubRequestApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // Capacity define resource requirements against each capacity. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicetaint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicetaint.go index 55c7d58a681a1..32b37402c5538 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicetaint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicetaint.go @@ -43,8 +43,17 @@ type DeviceTaintApplyConfiguration struct { // nodes is not valid here. More effects may get added in the future. // Consumers must treat unknown effects like None. Effect *resourcev1beta1.DeviceTaintEffect `json:"effect,omitempty"` - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. + // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). TimeAdded *v1.Time `json:"timeAdded,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/networkdevicedata.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/networkdevicedata.go index 5c3edcfe8863e..f112f01454cd7 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/networkdevicedata.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/networkdevicedata.go @@ -29,7 +29,7 @@ type NetworkDeviceDataApplyConfiguration struct { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. InterfaceName *string `json:"interfaceName,omitempty"` // IPs lists the network addresses assigned to the device's network interface. // This can include both IPv4 and IPv6 addresses. @@ -41,7 +41,7 @@ type NetworkDeviceDataApplyConfiguration struct { IPs []string `json:"ips,omitempty"` // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. HardwareAddress *string `json:"hardwareAddress,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/nodeallocatableresourcemapping.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/nodeallocatableresourcemapping.go new file mode 100644 index 0000000000000..5cdc2c7512f79 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/nodeallocatableresourcemapping.go @@ -0,0 +1,84 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +import ( + resourcev1beta1 "k8s.io/api/resource/v1beta1" + resource "k8s.io/apimachinery/pkg/api/resource" +) + +// NodeAllocatableResourceMappingApplyConfiguration represents a declarative configuration of the NodeAllocatableResourceMapping type for use +// with apply. +// +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +type NodeAllocatableResourceMappingApplyConfiguration struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + CapacityKey *resourcev1beta1.QualifiedName `json:"capacityKey,omitempty"` + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + AllocationMultiplier *resource.Quantity `json:"allocationMultiplier,omitempty"` +} + +// NodeAllocatableResourceMappingApplyConfiguration constructs a declarative configuration of the NodeAllocatableResourceMapping type for use with +// apply. +func NodeAllocatableResourceMapping() *NodeAllocatableResourceMappingApplyConfiguration { + return &NodeAllocatableResourceMappingApplyConfiguration{} +} + +// WithCapacityKey sets the CapacityKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CapacityKey field is set to the value of the last call. +func (b *NodeAllocatableResourceMappingApplyConfiguration) WithCapacityKey(value resourcev1beta1.QualifiedName) *NodeAllocatableResourceMappingApplyConfiguration { + b.CapacityKey = &value + return b +} + +// WithAllocationMultiplier sets the AllocationMultiplier field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllocationMultiplier field is set to the value of the last call. +func (b *NodeAllocatableResourceMappingApplyConfiguration) WithAllocationMultiplier(value resource.Quantity) *NodeAllocatableResourceMappingApplyConfiguration { + b.AllocationMultiplier = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/allocationresult.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/allocationresult.go index 91db54f92e512..540b37ed595ff 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/allocationresult.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/allocationresult.go @@ -36,7 +36,7 @@ type AllocationResultApplyConfiguration struct { // AllocationTimestamp stores the time when the resources were allocated. // This field is not guaranteed to be set, in which case that time is unknown. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gate. AllocationTimestamp *metav1.Time `json:"allocationTimestamp,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/celdeviceselector.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/celdeviceselector.go index 861168b9e705b..a184590ebec02 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/celdeviceselector.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/celdeviceselector.go @@ -72,6 +72,14 @@ type CELDeviceSelectorApplyConfiguration struct { // // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool) // + // When the DRAListTypeAttributes feature gate is enabled, + // the includes() helper is available and it can work for both scalar + // and list-type attributes. It was introduced to support smooth migration + // from scalar attributes to list-type attributes while keeping + // CEL expressions simple. For example: + // + // device.attributes["dra.example.com"].models.includes("some-model") + // // The length of the expression must be smaller or equal to 10 Ki. The // cost of evaluating it is also limited based on the estimated number // of logical steps. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/device.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/device.go index 607c9f5eb7950..b3985b0ca5e91 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/device.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/device.go @@ -19,6 +19,7 @@ limitations under the License. package v1beta2 import ( + corev1 "k8s.io/api/core/v1" resourcev1beta2 "k8s.io/api/resource/v1beta2" v1 "k8s.io/client-go/applyconfigurations/core/v1" ) @@ -74,7 +75,7 @@ type DeviceApplyConfiguration struct { // any device in a ResourceSlice, then the maximum number of // allowed devices per ResourceSlice is 64 instead of 128. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Taints []DeviceTaintApplyConfiguration `json:"taints,omitempty"` // BindsToNode indicates if the usage of an allocation involving this device @@ -82,7 +83,7 @@ type DeviceApplyConfiguration struct { // If set to true, the scheduler will set the ResourceClaim.Status.Allocation.NodeSelector // to match the node where the allocation was made. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindsToNode *bool `json:"bindsToNode,omitempty"` // BindingConditions defines the conditions for proceeding with binding. @@ -94,7 +95,7 @@ type DeviceApplyConfiguration struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingConditions []string `json:"bindingConditions,omitempty"` // BindingFailureConditions defines the conditions for binding failure. @@ -105,7 +106,7 @@ type DeviceApplyConfiguration struct { // // The conditions must be a valid condition type string. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingFailureConditions []string `json:"bindingFailureConditions,omitempty"` // AllowMultipleAllocations marks whether the device is allowed to be allocated to multiple DeviceRequests. @@ -113,6 +114,18 @@ type DeviceApplyConfiguration struct { // If AllowMultipleAllocations is set to true, the device can be allocated more than once, // and all of its capacity is consumable, regardless of whether the requestPolicy is defined or not. AllowMultipleAllocations *bool `json:"allowMultipleAllocations,omitempty"` + // NodeAllocatableResourceMappings defines the mapping of node resources + // that are managed by the DRA driver exposing this device. This includes resources currently + // reported in v1.Node `status.allocatable` that are not extended resources + // (see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources). + // Examples include "cpu", "memory", "ephemeral-storage", and hugepages. + // In addition to standard requests made through the Pod `spec`, these resources + // can also be requested through claims and allocated by the DRA driver. + // For example, a CPU DRA driver might allocate exclusive CPUs or auxiliary node memory + // dependencies of an accelerator device. + // The keys of this map are the node-allocatable resource names (e.g., "cpu", "memory"). + // Extended resource names are not permitted as keys. + NodeAllocatableResourceMappings map[corev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration `json:"nodeAllocatableResourceMappings,omitempty"` } // DeviceApplyConfiguration constructs a declarative configuration of the Device type for use with @@ -242,3 +255,17 @@ func (b *DeviceApplyConfiguration) WithAllowMultipleAllocations(value bool) *Dev b.AllowMultipleAllocations = &value return b } + +// WithNodeAllocatableResourceMappings puts the entries into the NodeAllocatableResourceMappings field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the NodeAllocatableResourceMappings field, +// overwriting an existing map entries in NodeAllocatableResourceMappings field with the same key. +func (b *DeviceApplyConfiguration) WithNodeAllocatableResourceMappings(entries map[corev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration) *DeviceApplyConfiguration { + if b.NodeAllocatableResourceMappings == nil && len(entries) > 0 { + b.NodeAllocatableResourceMappings = make(map[corev1.ResourceName]NodeAllocatableResourceMappingApplyConfiguration, len(entries)) + } + for k, v := range entries { + b.NodeAllocatableResourceMappings[k] = v + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceattribute.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceattribute.go index 63c7f2051059f..9929e54121038 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceattribute.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceattribute.go @@ -32,6 +32,22 @@ type DeviceAttributeApplyConfiguration struct { // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. VersionValue *string `json:"version,omitempty"` + // IntValues is a non-empty list of numbers. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + IntValues []int64 `json:"ints,omitempty"` + // BoolValues is a non-empty list of true/false values. + BoolValues []bool `json:"bools,omitempty"` + // StringValues is a non-empty list of strings. + // Each string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + StringValues []string `json:"strings,omitempty"` + // VersionValues is a non-empty list of semantic versions according to semver.org spec 2.0.0. + // Each version string must not be longer than 64 characters. + // + // This is an alpha field and requires enabling the DRAListTypeAttributes feature gate. + VersionValues []string `json:"versions,omitempty"` } // DeviceAttributeApplyConfiguration constructs a declarative configuration of the DeviceAttribute type for use with @@ -71,3 +87,43 @@ func (b *DeviceAttributeApplyConfiguration) WithVersionValue(value string) *Devi b.VersionValue = &value return b } + +// WithIntValues adds the given value to the IntValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IntValues field. +func (b *DeviceAttributeApplyConfiguration) WithIntValues(values ...int64) *DeviceAttributeApplyConfiguration { + for i := range values { + b.IntValues = append(b.IntValues, values[i]) + } + return b +} + +// WithBoolValues adds the given value to the BoolValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the BoolValues field. +func (b *DeviceAttributeApplyConfiguration) WithBoolValues(values ...bool) *DeviceAttributeApplyConfiguration { + for i := range values { + b.BoolValues = append(b.BoolValues, values[i]) + } + return b +} + +// WithStringValues adds the given value to the StringValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the StringValues field. +func (b *DeviceAttributeApplyConfiguration) WithStringValues(values ...string) *DeviceAttributeApplyConfiguration { + for i := range values { + b.StringValues = append(b.StringValues, values[i]) + } + return b +} + +// WithVersionValues adds the given value to the VersionValues field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the VersionValues field. +func (b *DeviceAttributeApplyConfiguration) WithVersionValues(values ...string) *DeviceAttributeApplyConfiguration { + for i := range values { + b.VersionValues = append(b.VersionValues, values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclassspec.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclassspec.go index e270c5908b90f..4ae2eaa29f6ea 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclassspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclassspec.go @@ -41,7 +41,7 @@ type DeviceClassSpecApplyConfiguration struct { // If two classes are created at the same time, then the name of the class // lexicographically sorted first is picked. // - // This is an alpha field. + // This is a beta field. ExtendedResourceName *string `json:"extendedResourceName,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceconstraint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceconstraint.go index 81a682c82b816..05d914af41757 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceconstraint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceconstraint.go @@ -48,11 +48,21 @@ type DeviceConstraintApplyConfiguration struct { // its specification, but if one device doesn't, then it also will not be // chosen. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics(i.e., element order and duplicates are ignored): list-valued attributes + // match when the intersection across all devices is non-empty. + // Scalar values are treated as singleton sets for backward compatibility. + // // Must include the domain qualifier. MatchAttribute *resourcev1beta2.FullyQualifiedName `json:"matchAttribute,omitempty"` // DistinctAttribute requires that all devices in question have this // attribute and that its type and value are unique across those devices. // + // When the DRAListTypeAttributes feature gate is enabled, comparison uses + // set semantics (i.e., element order and duplicates are ignored): + // list-valued attributes must be pairwise disjoint across devices. + // Scalar values are treated as singleton sets for backward compatibility. + // // This acts as the inverse of MatchAttribute. // // This constraint is used to avoid allocating multiple requests to the same device diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicerequestallocationresult.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicerequestallocationresult.go index c1fd0266f5980..a274e5706c822 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicerequestallocationresult.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicerequestallocationresult.go @@ -66,19 +66,19 @@ type DeviceRequestAllocationResultApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // BindingConditions contains a copy of the BindingConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingConditions []string `json:"bindingConditions,omitempty"` // BindingFailureConditions contains a copy of the BindingFailureConditions // from the corresponding ResourceSlice at the time of allocation. // - // This is an alpha field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus + // This is a beta field and requires enabling the DRADeviceBindingConditions and DRAResourceClaimDeviceStatus // feature gates. BindingFailureConditions []string `json:"bindingFailureConditions,omitempty"` // ShareID uniquely identifies an individual allocation share of the device, diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicesubrequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicesubrequest.go index 79345cf6c53cb..d9f4a5cb57a4d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicesubrequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicesubrequest.go @@ -93,7 +93,7 @@ type DeviceSubRequestApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // Capacity define resource requirements against each capacity. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaint.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaint.go index 8049f52cc0cd1..91abce3bb352b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaint.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaint.go @@ -43,8 +43,17 @@ type DeviceTaintApplyConfiguration struct { // nodes is not valid here. More effects may get added in the future. // Consumers must treat unknown effects like None. Effect *resourcev1beta2.DeviceTaintEffect `json:"effect,omitempty"` - // TimeAdded represents the time at which the taint was added. + // TimeAdded represents the time at which the taint was added or + // (only in a DeviceTaintRule) the effect was modified. // Added automatically during create or update if not set. + // + // In addition, in a DeviceTaintRule a value provided during + // an update gets replaced with the current time if the provided + // value is the same as the old one and the new effect is different. + // Changing the key and/or value while keeping the effect unchanged + // is possible and does not update the time stamp because the eviction + // which uses it is either already started (NoExecute) or + // not started yet (NoEffect, NoSchedule). TimeAdded *v1.Time `json:"timeAdded,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrule.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrule.go new file mode 100644 index 0000000000000..18b2aca188411 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrule.go @@ -0,0 +1,294 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +import ( + resourcev1beta2 "k8s.io/api/resource/v1beta2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + internal "k8s.io/client-go/applyconfigurations/internal" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// DeviceTaintRuleApplyConfiguration represents a declarative configuration of the DeviceTaintRule type for use +// with apply. +// +// DeviceTaintRule adds one taint to all devices which match the selector. +// This has the same effect as if the taint was specified directly +// in the ResourceSlice by the DRA driver. +type DeviceTaintRuleApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + // Standard object metadata + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // Spec specifies the selector and one taint. + // + // Changing the spec automatically increments the metadata.generation number. + Spec *DeviceTaintRuleSpecApplyConfiguration `json:"spec,omitempty"` + // Status provides information about what was requested in the spec. + Status *DeviceTaintRuleStatusApplyConfiguration `json:"status,omitempty"` +} + +// DeviceTaintRule constructs a declarative configuration of the DeviceTaintRule type for use with +// apply. +func DeviceTaintRule(name string) *DeviceTaintRuleApplyConfiguration { + b := &DeviceTaintRuleApplyConfiguration{} + b.WithName(name) + b.WithKind("DeviceTaintRule") + b.WithAPIVersion("resource.k8s.io/v1beta2") + return b +} + +// ExtractDeviceTaintRuleFrom extracts the applied configuration owned by fieldManager from +// deviceTaintRule for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// deviceTaintRule must be a unmodified DeviceTaintRule API object that was retrieved from the Kubernetes API. +// ExtractDeviceTaintRuleFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractDeviceTaintRuleFrom(deviceTaintRule *resourcev1beta2.DeviceTaintRule, fieldManager string, subresource string) (*DeviceTaintRuleApplyConfiguration, error) { + b := &DeviceTaintRuleApplyConfiguration{} + err := managedfields.ExtractInto(deviceTaintRule, internal.Parser().Type("io.k8s.api.resource.v1beta2.DeviceTaintRule"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(deviceTaintRule.Name) + + b.WithKind("DeviceTaintRule") + b.WithAPIVersion("resource.k8s.io/v1beta2") + return b, nil +} + +// ExtractDeviceTaintRule extracts the applied configuration owned by fieldManager from +// deviceTaintRule. If no managedFields are found in deviceTaintRule for fieldManager, a +// DeviceTaintRuleApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// deviceTaintRule must be a unmodified DeviceTaintRule API object that was retrieved from the Kubernetes API. +// ExtractDeviceTaintRule provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractDeviceTaintRule(deviceTaintRule *resourcev1beta2.DeviceTaintRule, fieldManager string) (*DeviceTaintRuleApplyConfiguration, error) { + return ExtractDeviceTaintRuleFrom(deviceTaintRule, fieldManager, "") +} + +// ExtractDeviceTaintRuleStatus extracts the applied configuration owned by fieldManager from +// deviceTaintRule for the status subresource. +func ExtractDeviceTaintRuleStatus(deviceTaintRule *resourcev1beta2.DeviceTaintRule, fieldManager string) (*DeviceTaintRuleApplyConfiguration, error) { + return ExtractDeviceTaintRuleFrom(deviceTaintRule, fieldManager, "status") +} + +func (b DeviceTaintRuleApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithKind(value string) *DeviceTaintRuleApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithAPIVersion(value string) *DeviceTaintRuleApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithName(value string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithGenerateName(value string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithNamespace(value string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithUID(value types.UID) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithResourceVersion(value string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithGeneration(value int64) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithCreationTimestamp(value metav1.Time) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *DeviceTaintRuleApplyConfiguration) WithLabels(entries map[string]string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *DeviceTaintRuleApplyConfiguration) WithAnnotations(entries map[string]string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *DeviceTaintRuleApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *DeviceTaintRuleApplyConfiguration) WithFinalizers(values ...string) *DeviceTaintRuleApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *DeviceTaintRuleApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithSpec(value *DeviceTaintRuleSpecApplyConfiguration) *DeviceTaintRuleApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *DeviceTaintRuleApplyConfiguration) WithStatus(value *DeviceTaintRuleStatusApplyConfiguration) *DeviceTaintRuleApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *DeviceTaintRuleApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *DeviceTaintRuleApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *DeviceTaintRuleApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *DeviceTaintRuleApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrulespec.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrulespec.go new file mode 100644 index 0000000000000..7328296e7cca1 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrulespec.go @@ -0,0 +1,55 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +// DeviceTaintRuleSpecApplyConfiguration represents a declarative configuration of the DeviceTaintRuleSpec type for use +// with apply. +// +// DeviceTaintRuleSpec specifies the selector and one taint. +type DeviceTaintRuleSpecApplyConfiguration struct { + // DeviceSelector defines which device(s) the taint is applied to. + // All selector criteria must be satisfied for a device to + // match. The empty selector matches all devices. Without + // a selector, no devices are matches. + DeviceSelector *DeviceTaintSelectorApplyConfiguration `json:"deviceSelector,omitempty"` + // The taint that gets applied to matching devices. + Taint *DeviceTaintApplyConfiguration `json:"taint,omitempty"` +} + +// DeviceTaintRuleSpecApplyConfiguration constructs a declarative configuration of the DeviceTaintRuleSpec type for use with +// apply. +func DeviceTaintRuleSpec() *DeviceTaintRuleSpecApplyConfiguration { + return &DeviceTaintRuleSpecApplyConfiguration{} +} + +// WithDeviceSelector sets the DeviceSelector field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeviceSelector field is set to the value of the last call. +func (b *DeviceTaintRuleSpecApplyConfiguration) WithDeviceSelector(value *DeviceTaintSelectorApplyConfiguration) *DeviceTaintRuleSpecApplyConfiguration { + b.DeviceSelector = value + return b +} + +// WithTaint sets the Taint field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Taint field is set to the value of the last call. +func (b *DeviceTaintRuleSpecApplyConfiguration) WithTaint(value *DeviceTaintApplyConfiguration) *DeviceTaintRuleSpecApplyConfiguration { + b.Taint = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrulestatus.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrulestatus.go new file mode 100644 index 0000000000000..7b92c5e8e0f17 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintrulestatus.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// DeviceTaintRuleStatusApplyConfiguration represents a declarative configuration of the DeviceTaintRuleStatus type for use +// with apply. +// +// DeviceTaintRuleStatus provides information about an on-going pod eviction. +type DeviceTaintRuleStatusApplyConfiguration struct { + // Conditions provide information about the state of the DeviceTaintRule + // and the cluster at some point in time, + // in a machine-readable and human-readable format. + // + // The following condition is currently defined as part of this API, more may + // get added: + // - Type: EvictionInProgress + // - Status: True if there are currently pods which need to be evicted, False otherwise + // (includes the effects which don't cause eviction). + // - Reason: not specified, may change + // - Message: includes information about number of pending pods and already evicted pods + // in a human-readable format, updated periodically, may change + // + // For `effect: None`, the condition above gets set once for each change to + // the spec, with the message containing information about what would happen + // if the effect was `NoExecute`. This feedback can be used to decide whether + // changing the effect to `NoExecute` will work as intended. It only gets + // set once to avoid having to constantly update the status. + // + // Must have 8 or fewer entries. + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// DeviceTaintRuleStatusApplyConfiguration constructs a declarative configuration of the DeviceTaintRuleStatus type for use with +// apply. +func DeviceTaintRuleStatus() *DeviceTaintRuleStatusApplyConfiguration { + return &DeviceTaintRuleStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *DeviceTaintRuleStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *DeviceTaintRuleStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintselector.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintselector.go new file mode 100644 index 0000000000000..1aeaed7204391 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaintselector.go @@ -0,0 +1,76 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +// DeviceTaintSelectorApplyConfiguration represents a declarative configuration of the DeviceTaintSelector type for use +// with apply. +// +// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. +// The empty selector matches all devices. Without a selector, no devices +// are matched. +type DeviceTaintSelectorApplyConfiguration struct { + // If driver is set, only devices from that driver are selected. + // This fields corresponds to slice.spec.driver. + Driver *string `json:"driver,omitempty"` + // If pool is set, only devices in that pool are selected. + // + // Also setting the driver name may be useful to avoid + // ambiguity when different drivers use the same pool name, + // but this is not required because selecting pools from + // different drivers may also be useful, for example when + // drivers with node-local devices use the node name as + // their pool name. + Pool *string `json:"pool,omitempty"` + // If device is set, only devices with that name are selected. + // This field corresponds to slice.spec.devices[].name. + // + // Setting also driver and pool may be required to avoid ambiguity, + // but is not required. + Device *string `json:"device,omitempty"` +} + +// DeviceTaintSelectorApplyConfiguration constructs a declarative configuration of the DeviceTaintSelector type for use with +// apply. +func DeviceTaintSelector() *DeviceTaintSelectorApplyConfiguration { + return &DeviceTaintSelectorApplyConfiguration{} +} + +// WithDriver sets the Driver field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Driver field is set to the value of the last call. +func (b *DeviceTaintSelectorApplyConfiguration) WithDriver(value string) *DeviceTaintSelectorApplyConfiguration { + b.Driver = &value + return b +} + +// WithPool sets the Pool field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Pool field is set to the value of the last call. +func (b *DeviceTaintSelectorApplyConfiguration) WithPool(value string) *DeviceTaintSelectorApplyConfiguration { + b.Pool = &value + return b +} + +// WithDevice sets the Device field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Device field is set to the value of the last call. +func (b *DeviceTaintSelectorApplyConfiguration) WithDevice(value string) *DeviceTaintSelectorApplyConfiguration { + b.Device = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/exactdevicerequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/exactdevicerequest.go index e4c5d599633fa..bda1152701795 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/exactdevicerequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/exactdevicerequest.go @@ -91,7 +91,7 @@ type ExactDeviceRequestApplyConfiguration struct { // // The maximum number of tolerations is 16. // - // This is an alpha field and requires enabling the DRADeviceTaints + // This is a beta field and requires enabling the DRADeviceTaints // feature gate. Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"` // Capacity define resource requirements against each capacity. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/networkdevicedata.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/networkdevicedata.go index 9621703268fe8..238e5412f1396 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/networkdevicedata.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/networkdevicedata.go @@ -29,7 +29,7 @@ type NetworkDeviceDataApplyConfiguration struct { // the allocated device. This might be the name of a physical or virtual // network interface being configured in the pod. // - // Must not be longer than 256 characters. + // Must not be longer than 256 bytes. InterfaceName *string `json:"interfaceName,omitempty"` // IPs lists the network addresses assigned to the device's network interface. // This can include both IPv4 and IPv6 addresses. @@ -39,7 +39,7 @@ type NetworkDeviceDataApplyConfiguration struct { IPs []string `json:"ips,omitempty"` // HardwareAddress represents the hardware address (e.g. MAC Address) of the device's network interface. // - // Must not be longer than 128 characters. + // Must not be longer than 128 bytes. HardwareAddress *string `json:"hardwareAddress,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/nodeallocatableresourcemapping.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/nodeallocatableresourcemapping.go new file mode 100644 index 0000000000000..b1490b5e753c6 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta2/nodeallocatableresourcemapping.go @@ -0,0 +1,84 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +import ( + resourcev1beta2 "k8s.io/api/resource/v1beta2" + resource "k8s.io/apimachinery/pkg/api/resource" +) + +// NodeAllocatableResourceMappingApplyConfiguration represents a declarative configuration of the NodeAllocatableResourceMapping type for use +// with apply. +// +// NodeAllocatableResourceMapping defines the translation between the DRA device/capacity +// units requested to the corresponding quantity of the node allocatable resource. +type NodeAllocatableResourceMappingApplyConfiguration struct { + // CapacityKey references a capacity name defined as a key in the + // `spec.devices[*].capacity` map. When this field is set, the value associated with + // this key in the `status.allocation.devices.results[*].consumedCapacity` map + // (for a specific claim allocation) determines the base quantity for + // the node allocatable resource. If `allocationMultiplier` is also set, it is + // multiplied with the base quantity. + // For example, if `spec.devices[*].capacity` has an entry "dra.example.com/memory": "128Gi", + // and this field is set to "dra.example.com/memory", then for a claim allocation + // that consumes { "dra.example.com/memory": "4Gi" } the base quantity for the + // node allocatable resource mapping will be "4Gi", and `allocationMultiplier` should + // be omitted or set to "1". + CapacityKey *resourcev1beta2.QualifiedName `json:"capacityKey,omitempty"` + // AllocationMultiplier is used as a multiplier for the allocated device count or the allocated capacity in the claim. + // It defaults to 1 if not specified. How the field is used also depends on whether `capacityKey` is set. + // 1. If `capacityKey` is NOT set: `allocationMultiplier` multiplies the device count allocated to the claim. + // a. A DRA driver representing each CPU core as a device would have + // {ResourceName: "cpu", allocationMultiplier: "2"} in its + // `nodeAllocatableResourceMappings`. If 4 devices are allocated to the claim, + // 4 * 2 CPUs would be considered as allocated and subtracted from the node's capacity. + // b. A GPU device that needs additional node memory per GPU allocation would + // have {ResourceName: "memory", allocationMultiplier: "2Gi"}. Each allocated + // GPU device instance of this type will account for 2Gi of memory. + // + // 2. If `capacityKey` IS set: `allocationMultiplier` is multiplied by the amount of that capacity consumed. + // The final node allocatable resource amount is `consumedCapacity[capacityKey]` * `allocationMultiplier`. + // For example, if a Device's capacity "dra.example.com/cores" is consumed, + // and each "core" provides 2 "cpu"s, the mapping would be: + // {ResourceName: "cpu", capacityKey: "dra.example.com/cores", allocationMultiplier: "2"}. + // If a claim consumes 8 "dra.example.com/cores", the CPU footprint is 8 * 2 = 16. + AllocationMultiplier *resource.Quantity `json:"allocationMultiplier,omitempty"` +} + +// NodeAllocatableResourceMappingApplyConfiguration constructs a declarative configuration of the NodeAllocatableResourceMapping type for use with +// apply. +func NodeAllocatableResourceMapping() *NodeAllocatableResourceMappingApplyConfiguration { + return &NodeAllocatableResourceMappingApplyConfiguration{} +} + +// WithCapacityKey sets the CapacityKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CapacityKey field is set to the value of the last call. +func (b *NodeAllocatableResourceMappingApplyConfiguration) WithCapacityKey(value resourcev1beta2.QualifiedName) *NodeAllocatableResourceMappingApplyConfiguration { + b.CapacityKey = &value + return b +} + +// WithAllocationMultiplier sets the AllocationMultiplier field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllocationMultiplier field is set to the value of the last call. +func (b *NodeAllocatableResourceMappingApplyConfiguration) WithAllocationMultiplier(value resource.Quantity) *NodeAllocatableResourceMappingApplyConfiguration { + b.AllocationMultiplier = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/podgroup.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/podgroup.go deleted file mode 100644 index b1ce26179a872..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/podgroup.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v1alpha1 - -// PodGroupApplyConfiguration represents a declarative configuration of the PodGroup type for use -// with apply. -// -// PodGroup represents a set of pods with a common scheduling policy. -type PodGroupApplyConfiguration struct { - // Name is a unique identifier for the PodGroup within the Workload. - // It must be a DNS label. This field is immutable. - Name *string `json:"name,omitempty"` - // Policy defines the scheduling policy for this PodGroup. - Policy *PodGroupPolicyApplyConfiguration `json:"policy,omitempty"` -} - -// PodGroupApplyConfiguration constructs a declarative configuration of the PodGroup type for use with -// apply. -func PodGroup() *PodGroupApplyConfiguration { - return &PodGroupApplyConfiguration{} -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *PodGroupApplyConfiguration) WithName(value string) *PodGroupApplyConfiguration { - b.Name = &value - return b -} - -// WithPolicy sets the Policy field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Policy field is set to the value of the last call. -func (b *PodGroupApplyConfiguration) WithPolicy(value *PodGroupPolicyApplyConfiguration) *PodGroupApplyConfiguration { - b.Policy = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/podgrouppolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/podgrouppolicy.go deleted file mode 100644 index d73c5e44095c3..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/podgrouppolicy.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" -) - -// PodGroupPolicyApplyConfiguration represents a declarative configuration of the PodGroupPolicy type for use -// with apply. -// -// PodGroupPolicy defines the scheduling configuration for a PodGroup. -type PodGroupPolicyApplyConfiguration struct { - // Basic specifies that the pods in this group should be scheduled using - // standard Kubernetes scheduling behavior. - Basic *schedulingv1alpha1.BasicSchedulingPolicy `json:"basic,omitempty"` - // Gang specifies that the pods in this group should be scheduled using - // all-or-nothing semantics. - Gang *GangSchedulingPolicyApplyConfiguration `json:"gang,omitempty"` -} - -// PodGroupPolicyApplyConfiguration constructs a declarative configuration of the PodGroupPolicy type for use with -// apply. -func PodGroupPolicy() *PodGroupPolicyApplyConfiguration { - return &PodGroupPolicyApplyConfiguration{} -} - -// WithBasic sets the Basic field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Basic field is set to the value of the last call. -func (b *PodGroupPolicyApplyConfiguration) WithBasic(value schedulingv1alpha1.BasicSchedulingPolicy) *PodGroupPolicyApplyConfiguration { - b.Basic = &value - return b -} - -// WithGang sets the Gang field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Gang field is set to the value of the last call. -func (b *PodGroupPolicyApplyConfiguration) WithGang(value *GangSchedulingPolicyApplyConfiguration) *PodGroupPolicyApplyConfiguration { - b.Gang = value - return b -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/priorityclass.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/priorityclass.go deleted file mode 100644 index 1735a6deae51e..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/priorityclass.go +++ /dev/null @@ -1,316 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - corev1 "k8s.io/api/core/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - managedfields "k8s.io/apimachinery/pkg/util/managedfields" - internal "k8s.io/client-go/applyconfigurations/internal" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// PriorityClassApplyConfiguration represents a declarative configuration of the PriorityClass type for use -// with apply. -// -// DEPRECATED - This group version of PriorityClass is deprecated by scheduling.k8s.io/v1/PriorityClass. -// PriorityClass defines mapping from a priority class name to the priority -// integer value. The value can be any valid integer. -type PriorityClassApplyConfiguration struct { - v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // value represents the integer value of this priority class. This is the actual priority that pods - // receive when they have the name of this class in their pod spec. - Value *int32 `json:"value,omitempty"` - // globalDefault specifies whether this PriorityClass should be considered as - // the default priority for pods that do not have any priority class. - // Only one PriorityClass can be marked as `globalDefault`. However, if more than - // one PriorityClasses exists with their `globalDefault` field set to true, - // the smallest value of such global default PriorityClasses will be used as the default priority. - GlobalDefault *bool `json:"globalDefault,omitempty"` - // description is an arbitrary string that usually provides guidelines on - // when this priority class should be used. - Description *string `json:"description,omitempty"` - // preemptionPolicy is the Policy for preempting pods with lower priority. - // One of Never, PreemptLowerPriority. - // Defaults to PreemptLowerPriority if unset. - PreemptionPolicy *corev1.PreemptionPolicy `json:"preemptionPolicy,omitempty"` -} - -// PriorityClass constructs a declarative configuration of the PriorityClass type for use with -// apply. -func PriorityClass(name string) *PriorityClassApplyConfiguration { - b := &PriorityClassApplyConfiguration{} - b.WithName(name) - b.WithKind("PriorityClass") - b.WithAPIVersion("scheduling.k8s.io/v1alpha1") - return b -} - -// ExtractPriorityClassFrom extracts the applied configuration owned by fieldManager from -// priorityClass for the specified subresource. Pass an empty string for subresource to extract -// the main resource. Common subresources include "status", "scale", etc. -// priorityClass must be a unmodified PriorityClass API object that was retrieved from the Kubernetes API. -// ExtractPriorityClassFrom provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractPriorityClassFrom(priorityClass *schedulingv1alpha1.PriorityClass, fieldManager string, subresource string) (*PriorityClassApplyConfiguration, error) { - b := &PriorityClassApplyConfiguration{} - err := managedfields.ExtractInto(priorityClass, internal.Parser().Type("io.k8s.api.scheduling.v1alpha1.PriorityClass"), fieldManager, b, subresource) - if err != nil { - return nil, err - } - b.WithName(priorityClass.Name) - - b.WithKind("PriorityClass") - b.WithAPIVersion("scheduling.k8s.io/v1alpha1") - return b, nil -} - -// ExtractPriorityClass extracts the applied configuration owned by fieldManager from -// priorityClass. If no managedFields are found in priorityClass for fieldManager, a -// PriorityClassApplyConfiguration is returned with only the Name, Namespace (if applicable), -// APIVersion and Kind populated. It is possible that no managed fields were found for because other -// field managers have taken ownership of all the fields previously owned by fieldManager, or because -// the fieldManager never owned fields any fields. -// priorityClass must be a unmodified PriorityClass API object that was retrieved from the Kubernetes API. -// ExtractPriorityClass provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractPriorityClass(priorityClass *schedulingv1alpha1.PriorityClass, fieldManager string) (*PriorityClassApplyConfiguration, error) { - return ExtractPriorityClassFrom(priorityClass, fieldManager, "") -} - -func (b PriorityClassApplyConfiguration) IsApplyConfiguration() {} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithKind(value string) *PriorityClassApplyConfiguration { - b.TypeMetaApplyConfiguration.Kind = &value - return b -} - -// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the APIVersion field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithAPIVersion(value string) *PriorityClassApplyConfiguration { - b.TypeMetaApplyConfiguration.APIVersion = &value - return b -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithName(value string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Name = &value - return b -} - -// WithGenerateName sets the GenerateName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the GenerateName field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithGenerateName(value string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.GenerateName = &value - return b -} - -// WithNamespace sets the Namespace field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Namespace field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithNamespace(value string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Namespace = &value - return b -} - -// WithUID sets the UID field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the UID field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithUID(value types.UID) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.UID = &value - return b -} - -// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ResourceVersion field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithResourceVersion(value string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.ResourceVersion = &value - return b -} - -// WithGeneration sets the Generation field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Generation field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithGeneration(value int64) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Generation = &value - return b -} - -// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CreationTimestamp field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithCreationTimestamp(value metav1.Time) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.CreationTimestamp = &value - return b -} - -// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionTimestamp field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value - return b -} - -// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value - return b -} - -// WithLabels puts the entries into the Labels field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Labels field, -// overwriting an existing map entries in Labels field with the same key. -func (b *PriorityClassApplyConfiguration) WithLabels(entries map[string]string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Labels[k] = v - } - return b -} - -// WithAnnotations puts the entries into the Annotations field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Annotations field, -// overwriting an existing map entries in Annotations field with the same key. -func (b *PriorityClassApplyConfiguration) WithAnnotations(entries map[string]string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Annotations[k] = v - } - return b -} - -// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the OwnerReferences field. -func (b *PriorityClassApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - if values[i] == nil { - panic("nil value passed to WithOwnerReferences") - } - b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) - } - return b -} - -// WithFinalizers adds the given value to the Finalizers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Finalizers field. -func (b *PriorityClassApplyConfiguration) WithFinalizers(values ...string) *PriorityClassApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) - } - return b -} - -func (b *PriorityClassApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { - if b.ObjectMetaApplyConfiguration == nil { - b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} - } -} - -// WithValue sets the Value field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Value field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithValue(value int32) *PriorityClassApplyConfiguration { - b.Value = &value - return b -} - -// WithGlobalDefault sets the GlobalDefault field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the GlobalDefault field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithGlobalDefault(value bool) *PriorityClassApplyConfiguration { - b.GlobalDefault = &value - return b -} - -// WithDescription sets the Description field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Description field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithDescription(value string) *PriorityClassApplyConfiguration { - b.Description = &value - return b -} - -// WithPreemptionPolicy sets the PreemptionPolicy field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreemptionPolicy field is set to the value of the last call. -func (b *PriorityClassApplyConfiguration) WithPreemptionPolicy(value corev1.PreemptionPolicy) *PriorityClassApplyConfiguration { - b.PreemptionPolicy = &value - return b -} - -// GetKind retrieves the value of the Kind field in the declarative configuration. -func (b *PriorityClassApplyConfiguration) GetKind() *string { - return b.TypeMetaApplyConfiguration.Kind -} - -// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. -func (b *PriorityClassApplyConfiguration) GetAPIVersion() *string { - return b.TypeMetaApplyConfiguration.APIVersion -} - -// GetName retrieves the value of the Name field in the declarative configuration. -func (b *PriorityClassApplyConfiguration) GetName() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Name -} - -// GetNamespace retrieves the value of the Namespace field in the declarative configuration. -func (b *PriorityClassApplyConfiguration) GetNamespace() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Namespace -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/workload.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/workload.go deleted file mode 100644 index 6530899aee330..0000000000000 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/workload.go +++ /dev/null @@ -1,279 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by applyconfiguration-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - managedfields "k8s.io/apimachinery/pkg/util/managedfields" - internal "k8s.io/client-go/applyconfigurations/internal" - v1 "k8s.io/client-go/applyconfigurations/meta/v1" -) - -// WorkloadApplyConfiguration represents a declarative configuration of the Workload type for use -// with apply. -// -// Workload allows for expressing scheduling constraints that should be used -// when managing lifecycle of workloads from scheduling perspective, -// including scheduling, preemption, eviction and other phases. -type WorkloadApplyConfiguration struct { - v1.TypeMetaApplyConfiguration `json:",inline"` - // Standard object's metadata. - // Name must be a DNS subdomain. - *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - // Spec defines the desired behavior of a Workload. - Spec *WorkloadSpecApplyConfiguration `json:"spec,omitempty"` -} - -// Workload constructs a declarative configuration of the Workload type for use with -// apply. -func Workload(name, namespace string) *WorkloadApplyConfiguration { - b := &WorkloadApplyConfiguration{} - b.WithName(name) - b.WithNamespace(namespace) - b.WithKind("Workload") - b.WithAPIVersion("scheduling.k8s.io/v1alpha1") - return b -} - -// ExtractWorkloadFrom extracts the applied configuration owned by fieldManager from -// workload for the specified subresource. Pass an empty string for subresource to extract -// the main resource. Common subresources include "status", "scale", etc. -// workload must be a unmodified Workload API object that was retrieved from the Kubernetes API. -// ExtractWorkloadFrom provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractWorkloadFrom(workload *schedulingv1alpha1.Workload, fieldManager string, subresource string) (*WorkloadApplyConfiguration, error) { - b := &WorkloadApplyConfiguration{} - err := managedfields.ExtractInto(workload, internal.Parser().Type("io.k8s.api.scheduling.v1alpha1.Workload"), fieldManager, b, subresource) - if err != nil { - return nil, err - } - b.WithName(workload.Name) - b.WithNamespace(workload.Namespace) - - b.WithKind("Workload") - b.WithAPIVersion("scheduling.k8s.io/v1alpha1") - return b, nil -} - -// ExtractWorkload extracts the applied configuration owned by fieldManager from -// workload. If no managedFields are found in workload for fieldManager, a -// WorkloadApplyConfiguration is returned with only the Name, Namespace (if applicable), -// APIVersion and Kind populated. It is possible that no managed fields were found for because other -// field managers have taken ownership of all the fields previously owned by fieldManager, or because -// the fieldManager never owned fields any fields. -// workload must be a unmodified Workload API object that was retrieved from the Kubernetes API. -// ExtractWorkload provides a way to perform a extract/modify-in-place/apply workflow. -// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously -// applied if another fieldManager has updated or force applied any of the previously applied fields. -func ExtractWorkload(workload *schedulingv1alpha1.Workload, fieldManager string) (*WorkloadApplyConfiguration, error) { - return ExtractWorkloadFrom(workload, fieldManager, "") -} - -func (b WorkloadApplyConfiguration) IsApplyConfiguration() {} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithKind(value string) *WorkloadApplyConfiguration { - b.TypeMetaApplyConfiguration.Kind = &value - return b -} - -// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the APIVersion field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithAPIVersion(value string) *WorkloadApplyConfiguration { - b.TypeMetaApplyConfiguration.APIVersion = &value - return b -} - -// WithName sets the Name field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithName(value string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Name = &value - return b -} - -// WithGenerateName sets the GenerateName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the GenerateName field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithGenerateName(value string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.GenerateName = &value - return b -} - -// WithNamespace sets the Namespace field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Namespace field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithNamespace(value string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Namespace = &value - return b -} - -// WithUID sets the UID field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the UID field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithUID(value types.UID) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.UID = &value - return b -} - -// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ResourceVersion field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithResourceVersion(value string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.ResourceVersion = &value - return b -} - -// WithGeneration sets the Generation field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Generation field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithGeneration(value int64) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.Generation = &value - return b -} - -// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the CreationTimestamp field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithCreationTimestamp(value metav1.Time) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.CreationTimestamp = &value - return b -} - -// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionTimestamp field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value - return b -} - -// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value - return b -} - -// WithLabels puts the entries into the Labels field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Labels field, -// overwriting an existing map entries in Labels field with the same key. -func (b *WorkloadApplyConfiguration) WithLabels(entries map[string]string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Labels[k] = v - } - return b -} - -// WithAnnotations puts the entries into the Annotations field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Annotations field, -// overwriting an existing map entries in Annotations field with the same key. -func (b *WorkloadApplyConfiguration) WithAnnotations(entries map[string]string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { - b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.ObjectMetaApplyConfiguration.Annotations[k] = v - } - return b -} - -// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the OwnerReferences field. -func (b *WorkloadApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - if values[i] == nil { - panic("nil value passed to WithOwnerReferences") - } - b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) - } - return b -} - -// WithFinalizers adds the given value to the Finalizers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Finalizers field. -func (b *WorkloadApplyConfiguration) WithFinalizers(values ...string) *WorkloadApplyConfiguration { - b.ensureObjectMetaApplyConfigurationExists() - for i := range values { - b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) - } - return b -} - -func (b *WorkloadApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { - if b.ObjectMetaApplyConfiguration == nil { - b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} - } -} - -// WithSpec sets the Spec field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Spec field is set to the value of the last call. -func (b *WorkloadApplyConfiguration) WithSpec(value *WorkloadSpecApplyConfiguration) *WorkloadApplyConfiguration { - b.Spec = value - return b -} - -// GetKind retrieves the value of the Kind field in the declarative configuration. -func (b *WorkloadApplyConfiguration) GetKind() *string { - return b.TypeMetaApplyConfiguration.Kind -} - -// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. -func (b *WorkloadApplyConfiguration) GetAPIVersion() *string { - return b.TypeMetaApplyConfiguration.APIVersion -} - -// GetName retrieves the value of the Name field in the declarative configuration. -func (b *WorkloadApplyConfiguration) GetName() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Name -} - -// GetNamespace retrieves the value of the Namespace field in the declarative configuration. -func (b *WorkloadApplyConfiguration) GetNamespace() *string { - b.ensureObjectMetaApplyConfigurationExists() - return b.ObjectMetaApplyConfiguration.Namespace -} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/gangschedulingpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/gangschedulingpolicy.go similarity index 99% rename from staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/gangschedulingpolicy.go rename to staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/gangschedulingpolicy.go index 6bf41585cfb0e..987a89013bae9 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/gangschedulingpolicy.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/gangschedulingpolicy.go @@ -16,7 +16,7 @@ limitations under the License. // Code generated by applyconfiguration-gen. DO NOT EDIT. -package v1alpha1 +package v1alpha2 // GangSchedulingPolicyApplyConfiguration represents a declarative configuration of the GangSchedulingPolicy type for use // with apply. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroup.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroup.go new file mode 100644 index 0000000000000..ee2851062421b --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroup.go @@ -0,0 +1,296 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + internal "k8s.io/client-go/applyconfigurations/internal" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// PodGroupApplyConfiguration represents a declarative configuration of the PodGroup type for use +// with apply. +// +// PodGroup represents a runtime instance of pods grouped together. +// PodGroups are created by workload controllers (Job, LWS, JobSet, etc...) from +// Workload.podGroupTemplates. +// PodGroup API enablement is toggled by the GenericWorkload feature gate. +type PodGroupApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // Spec defines the desired state of the PodGroup. + Spec *PodGroupSpecApplyConfiguration `json:"spec,omitempty"` + // Status represents the current observed state of the PodGroup. + Status *PodGroupStatusApplyConfiguration `json:"status,omitempty"` +} + +// PodGroup constructs a declarative configuration of the PodGroup type for use with +// apply. +func PodGroup(name, namespace string) *PodGroupApplyConfiguration { + b := &PodGroupApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("PodGroup") + b.WithAPIVersion("scheduling.k8s.io/v1alpha2") + return b +} + +// ExtractPodGroupFrom extracts the applied configuration owned by fieldManager from +// podGroup for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// podGroup must be a unmodified PodGroup API object that was retrieved from the Kubernetes API. +// ExtractPodGroupFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractPodGroupFrom(podGroup *schedulingv1alpha2.PodGroup, fieldManager string, subresource string) (*PodGroupApplyConfiguration, error) { + b := &PodGroupApplyConfiguration{} + err := managedfields.ExtractInto(podGroup, internal.Parser().Type("io.k8s.api.scheduling.v1alpha2.PodGroup"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(podGroup.Name) + b.WithNamespace(podGroup.Namespace) + + b.WithKind("PodGroup") + b.WithAPIVersion("scheduling.k8s.io/v1alpha2") + return b, nil +} + +// ExtractPodGroup extracts the applied configuration owned by fieldManager from +// podGroup. If no managedFields are found in podGroup for fieldManager, a +// PodGroupApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// podGroup must be a unmodified PodGroup API object that was retrieved from the Kubernetes API. +// ExtractPodGroup provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractPodGroup(podGroup *schedulingv1alpha2.PodGroup, fieldManager string) (*PodGroupApplyConfiguration, error) { + return ExtractPodGroupFrom(podGroup, fieldManager, "") +} + +// ExtractPodGroupStatus extracts the applied configuration owned by fieldManager from +// podGroup for the status subresource. +func ExtractPodGroupStatus(podGroup *schedulingv1alpha2.PodGroup, fieldManager string) (*PodGroupApplyConfiguration, error) { + return ExtractPodGroupFrom(podGroup, fieldManager, "status") +} + +func (b PodGroupApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithKind(value string) *PodGroupApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithAPIVersion(value string) *PodGroupApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithName(value string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithGenerateName(value string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithNamespace(value string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithUID(value types.UID) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithResourceVersion(value string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithGeneration(value int64) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithCreationTimestamp(value metav1.Time) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *PodGroupApplyConfiguration) WithLabels(entries map[string]string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *PodGroupApplyConfiguration) WithAnnotations(entries map[string]string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *PodGroupApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *PodGroupApplyConfiguration) WithFinalizers(values ...string) *PodGroupApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *PodGroupApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithSpec(value *PodGroupSpecApplyConfiguration) *PodGroupApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *PodGroupApplyConfiguration) WithStatus(value *PodGroupStatusApplyConfiguration) *PodGroupApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *PodGroupApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *PodGroupApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *PodGroupApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *PodGroupApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupresourceclaim.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupresourceclaim.go new file mode 100644 index 0000000000000..a7b21b530b0ba --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupresourceclaim.go @@ -0,0 +1,89 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// PodGroupResourceClaimApplyConfiguration represents a declarative configuration of the PodGroupResourceClaim type for use +// with apply. +// +// PodGroupResourceClaim references exactly one ResourceClaim, either directly +// or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim +// for the PodGroup. +// +// It adds a name to it that uniquely identifies the ResourceClaim inside the PodGroup. +// Pods that need access to the ResourceClaim define a matching reference in its +// own Spec.ResourceClaims. The Pod's claim must match all fields of the +// PodGroup's claim exactly. +type PodGroupResourceClaimApplyConfiguration struct { + // Name uniquely identifies this resource claim inside the PodGroup. + // This must be a DNS_LABEL. + Name *string `json:"name,omitempty"` + // ResourceClaimName is the name of a ResourceClaim object in the same + // namespace as this PodGroup. The ResourceClaim will be reserved for the + // PodGroup instead of its individual pods. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + ResourceClaimName *string `json:"resourceClaimName,omitempty"` + // ResourceClaimTemplateName is the name of a ResourceClaimTemplate + // object in the same namespace as this PodGroup. + // + // The template will be used to create a new ResourceClaim, which will + // be bound to this PodGroup. When this PodGroup is deleted, the ResourceClaim + // will also be deleted. The PodGroup name and resource name, along with a + // generated component, will be used to form a unique name for the + // ResourceClaim, which will be recorded in podgroup.status.resourceClaimStatuses. + // + // This field is immutable and no changes will be made to the + // corresponding ResourceClaim by the control plane after creating the + // ResourceClaim. + // + // Exactly one of ResourceClaimName and ResourceClaimTemplateName must + // be set. + ResourceClaimTemplateName *string `json:"resourceClaimTemplateName,omitempty"` +} + +// PodGroupResourceClaimApplyConfiguration constructs a declarative configuration of the PodGroupResourceClaim type for use with +// apply. +func PodGroupResourceClaim() *PodGroupResourceClaimApplyConfiguration { + return &PodGroupResourceClaimApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PodGroupResourceClaimApplyConfiguration) WithName(value string) *PodGroupResourceClaimApplyConfiguration { + b.Name = &value + return b +} + +// WithResourceClaimName sets the ResourceClaimName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceClaimName field is set to the value of the last call. +func (b *PodGroupResourceClaimApplyConfiguration) WithResourceClaimName(value string) *PodGroupResourceClaimApplyConfiguration { + b.ResourceClaimName = &value + return b +} + +// WithResourceClaimTemplateName sets the ResourceClaimTemplateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceClaimTemplateName field is set to the value of the last call. +func (b *PodGroupResourceClaimApplyConfiguration) WithResourceClaimTemplateName(value string) *PodGroupResourceClaimApplyConfiguration { + b.ResourceClaimTemplateName = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupresourceclaimstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupresourceclaimstatus.go new file mode 100644 index 0000000000000..5f8498b13eb85 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupresourceclaimstatus.go @@ -0,0 +1,59 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// PodGroupResourceClaimStatusApplyConfiguration represents a declarative configuration of the PodGroupResourceClaimStatus type for use +// with apply. +// +// PodGroupResourceClaimStatus is stored in the PodGroupStatus for each +// PodGroupResourceClaim which references a ResourceClaimTemplate. It stores the +// generated name for the corresponding ResourceClaim. +type PodGroupResourceClaimStatusApplyConfiguration struct { + // Name uniquely identifies this resource claim inside the PodGroup. This + // must match the name of an entry in podgroup.spec.resourceClaims, which + // implies that the string must be a DNS_LABEL. + Name *string `json:"name,omitempty"` + // ResourceClaimName is the name of the ResourceClaim that was generated for + // the PodGroup in the namespace of the PodGroup. If this is unset, then + // generating a ResourceClaim was not necessary. The + // podgroup.spec.resourceClaims entry can be ignored in this case. + ResourceClaimName *string `json:"resourceClaimName,omitempty"` +} + +// PodGroupResourceClaimStatusApplyConfiguration constructs a declarative configuration of the PodGroupResourceClaimStatus type for use with +// apply. +func PodGroupResourceClaimStatus() *PodGroupResourceClaimStatusApplyConfiguration { + return &PodGroupResourceClaimStatusApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PodGroupResourceClaimStatusApplyConfiguration) WithName(value string) *PodGroupResourceClaimStatusApplyConfiguration { + b.Name = &value + return b +} + +// WithResourceClaimName sets the ResourceClaimName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceClaimName field is set to the value of the last call. +func (b *PodGroupResourceClaimStatusApplyConfiguration) WithResourceClaimName(value string) *PodGroupResourceClaimStatusApplyConfiguration { + b.ResourceClaimName = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupschedulingconstraints.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupschedulingconstraints.go new file mode 100644 index 0000000000000..eda72ea50e585 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupschedulingconstraints.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// PodGroupSchedulingConstraintsApplyConfiguration represents a declarative configuration of the PodGroupSchedulingConstraints type for use +// with apply. +// +// PodGroupSchedulingConstraints defines scheduling constraints (e.g. topology) for a PodGroup. +type PodGroupSchedulingConstraintsApplyConfiguration struct { + // Topology defines the topology constraints for the pod group. + // Currently only a single topology constraint can be specified. This may change in the future. + Topology []TopologyConstraintApplyConfiguration `json:"topology,omitempty"` +} + +// PodGroupSchedulingConstraintsApplyConfiguration constructs a declarative configuration of the PodGroupSchedulingConstraints type for use with +// apply. +func PodGroupSchedulingConstraints() *PodGroupSchedulingConstraintsApplyConfiguration { + return &PodGroupSchedulingConstraintsApplyConfiguration{} +} + +// WithTopology adds the given value to the Topology field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Topology field. +func (b *PodGroupSchedulingConstraintsApplyConfiguration) WithTopology(values ...*TopologyConstraintApplyConfiguration) *PodGroupSchedulingConstraintsApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithTopology") + } + b.Topology = append(b.Topology, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupschedulingpolicy.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupschedulingpolicy.go new file mode 100644 index 0000000000000..02be5f27eb99b --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupschedulingpolicy.go @@ -0,0 +1,59 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" +) + +// PodGroupSchedulingPolicyApplyConfiguration represents a declarative configuration of the PodGroupSchedulingPolicy type for use +// with apply. +// +// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup. +// Exactly one policy must be set. +type PodGroupSchedulingPolicyApplyConfiguration struct { + // Basic specifies that the pods in this group should be scheduled using + // standard Kubernetes scheduling behavior. + Basic *schedulingv1alpha2.BasicSchedulingPolicy `json:"basic,omitempty"` + // Gang specifies that the pods in this group should be scheduled using + // all-or-nothing semantics. + Gang *GangSchedulingPolicyApplyConfiguration `json:"gang,omitempty"` +} + +// PodGroupSchedulingPolicyApplyConfiguration constructs a declarative configuration of the PodGroupSchedulingPolicy type for use with +// apply. +func PodGroupSchedulingPolicy() *PodGroupSchedulingPolicyApplyConfiguration { + return &PodGroupSchedulingPolicyApplyConfiguration{} +} + +// WithBasic sets the Basic field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Basic field is set to the value of the last call. +func (b *PodGroupSchedulingPolicyApplyConfiguration) WithBasic(value schedulingv1alpha2.BasicSchedulingPolicy) *PodGroupSchedulingPolicyApplyConfiguration { + b.Basic = &value + return b +} + +// WithGang sets the Gang field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Gang field is set to the value of the last call. +func (b *PodGroupSchedulingPolicyApplyConfiguration) WithGang(value *GangSchedulingPolicyApplyConfiguration) *PodGroupSchedulingPolicyApplyConfiguration { + b.Gang = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupspec.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupspec.go new file mode 100644 index 0000000000000..76538c6221cf0 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupspec.go @@ -0,0 +1,145 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" +) + +// PodGroupSpecApplyConfiguration represents a declarative configuration of the PodGroupSpec type for use +// with apply. +// +// PodGroupSpec defines the desired state of a PodGroup. +type PodGroupSpecApplyConfiguration struct { + // PodGroupTemplateRef references an optional PodGroup template within other object + // (e.g. Workload) that was used to create the PodGroup. This field is immutable. + PodGroupTemplateRef *PodGroupTemplateReferenceApplyConfiguration `json:"podGroupTemplateRef,omitempty"` + // SchedulingPolicy defines the scheduling policy for this instance of the PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + SchedulingPolicy *PodGroupSchedulingPolicyApplyConfiguration `json:"schedulingPolicy,omitempty"` + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // This field is immutable. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + SchedulingConstraints *PodGroupSchedulingConstraintsApplyConfiguration `json:"schedulingConstraints,omitempty"` + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + ResourceClaims []PodGroupResourceClaimApplyConfiguration `json:"resourceClaims,omitempty"` + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // One of Pod, PodGroup. Defaults to Pod if unset. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + DisruptionMode *schedulingv1alpha2.DisruptionMode `json:"disruptionMode,omitempty"` + // PriorityClassName defines the priority that should be considered when scheduling this pod group. + // Controllers are expected to fill this field by copying it from a PodGroupTemplate. + // Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate + // (i.e. if no priority class is specified, admission control can set this to the global default + // priority class if it exists. Otherwise, the pod group's priority will be zero). + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + PriorityClassName *string `json:"priorityClassName,omitempty"` + // Priority is the value of priority of this pod group. Various system components + // use this field to find the priority of the pod group. When Priority Admission + // Controller is enabled, it prevents users from setting this field. The admission + // controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is immutable. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + Priority *int32 `json:"priority,omitempty"` +} + +// PodGroupSpecApplyConfiguration constructs a declarative configuration of the PodGroupSpec type for use with +// apply. +func PodGroupSpec() *PodGroupSpecApplyConfiguration { + return &PodGroupSpecApplyConfiguration{} +} + +// WithPodGroupTemplateRef sets the PodGroupTemplateRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PodGroupTemplateRef field is set to the value of the last call. +func (b *PodGroupSpecApplyConfiguration) WithPodGroupTemplateRef(value *PodGroupTemplateReferenceApplyConfiguration) *PodGroupSpecApplyConfiguration { + b.PodGroupTemplateRef = value + return b +} + +// WithSchedulingPolicy sets the SchedulingPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchedulingPolicy field is set to the value of the last call. +func (b *PodGroupSpecApplyConfiguration) WithSchedulingPolicy(value *PodGroupSchedulingPolicyApplyConfiguration) *PodGroupSpecApplyConfiguration { + b.SchedulingPolicy = value + return b +} + +// WithSchedulingConstraints sets the SchedulingConstraints field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchedulingConstraints field is set to the value of the last call. +func (b *PodGroupSpecApplyConfiguration) WithSchedulingConstraints(value *PodGroupSchedulingConstraintsApplyConfiguration) *PodGroupSpecApplyConfiguration { + b.SchedulingConstraints = value + return b +} + +// WithResourceClaims adds the given value to the ResourceClaims field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ResourceClaims field. +func (b *PodGroupSpecApplyConfiguration) WithResourceClaims(values ...*PodGroupResourceClaimApplyConfiguration) *PodGroupSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithResourceClaims") + } + b.ResourceClaims = append(b.ResourceClaims, *values[i]) + } + return b +} + +// WithDisruptionMode sets the DisruptionMode field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DisruptionMode field is set to the value of the last call. +func (b *PodGroupSpecApplyConfiguration) WithDisruptionMode(value schedulingv1alpha2.DisruptionMode) *PodGroupSpecApplyConfiguration { + b.DisruptionMode = &value + return b +} + +// WithPriorityClassName sets the PriorityClassName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PriorityClassName field is set to the value of the last call. +func (b *PodGroupSpecApplyConfiguration) WithPriorityClassName(value string) *PodGroupSpecApplyConfiguration { + b.PriorityClassName = &value + return b +} + +// WithPriority sets the Priority field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Priority field is set to the value of the last call. +func (b *PodGroupSpecApplyConfiguration) WithPriority(value int32) *PodGroupSpecApplyConfiguration { + b.Priority = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupstatus.go new file mode 100644 index 0000000000000..c8c4b30da9016 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgroupstatus.go @@ -0,0 +1,81 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// PodGroupStatusApplyConfiguration represents a declarative configuration of the PodGroupStatus type for use +// with apply. +// +// PodGroupStatus represents information about the status of a pod group. +type PodGroupStatusApplyConfiguration struct { + // Conditions represent the latest observations of the PodGroup's state. + // + // Known condition types: + // - "PodGroupScheduled": Indicates whether the scheduling requirement has been satisfied. + // - "DisruptionTarget": Indicates whether the PodGroup is about to be terminated + // due to disruption such as preemption. + // + // Known reasons for the PodGroupScheduled condition: + // - "Unschedulable": The PodGroup cannot be scheduled due to resource constraints, + // affinity/anti-affinity rules, or insufficient capacity for the gang. + // - "SchedulerError": The PodGroup cannot be scheduled due to some internal error + // that happened during scheduling, for example due to nodeAffinity parsing errors. + // + // Known reasons for the DisruptionTarget condition: + // - "PreemptionByScheduler": The PodGroup was preempted by the scheduler to make room for + // higher-priority PodGroups or Pods. + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + // Status of resource claims. + ResourceClaimStatuses []PodGroupResourceClaimStatusApplyConfiguration `json:"resourceClaimStatuses,omitempty"` +} + +// PodGroupStatusApplyConfiguration constructs a declarative configuration of the PodGroupStatus type for use with +// apply. +func PodGroupStatus() *PodGroupStatusApplyConfiguration { + return &PodGroupStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *PodGroupStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *PodGroupStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithResourceClaimStatuses adds the given value to the ResourceClaimStatuses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ResourceClaimStatuses field. +func (b *PodGroupStatusApplyConfiguration) WithResourceClaimStatuses(values ...*PodGroupResourceClaimStatusApplyConfiguration) *PodGroupStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithResourceClaimStatuses") + } + b.ResourceClaimStatuses = append(b.ResourceClaimStatuses, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgrouptemplate.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgrouptemplate.go new file mode 100644 index 0000000000000..695caf2997ee7 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgrouptemplate.go @@ -0,0 +1,136 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" +) + +// PodGroupTemplateApplyConfiguration represents a declarative configuration of the PodGroupTemplate type for use +// with apply. +// +// PodGroupTemplate represents a template for a set of pods with a scheduling policy. +type PodGroupTemplateApplyConfiguration struct { + // Name is a unique identifier for the PodGroupTemplate within the Workload. + // It must be a DNS label. This field is immutable. + Name *string `json:"name,omitempty"` + // SchedulingPolicy defines the scheduling policy for this PodGroupTemplate. + SchedulingPolicy *PodGroupSchedulingPolicyApplyConfiguration `json:"schedulingPolicy,omitempty"` + // SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate. + // This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled. + SchedulingConstraints *PodGroupSchedulingConstraintsApplyConfiguration `json:"schedulingConstraints,omitempty"` + // ResourceClaims defines which ResourceClaims may be shared among Pods in + // the group. Pods consume the devices allocated to a PodGroup's claim by + // defining a claim in its own Spec.ResourceClaims that matches the + // PodGroup's claim exactly. The claim must have the same name and refer to + // the same ResourceClaim or ResourceClaimTemplate. + // + // This is an alpha-level field and requires that the + // DRAWorkloadResourceClaims feature gate is enabled. + // + // This field is immutable. + ResourceClaims []PodGroupResourceClaimApplyConfiguration `json:"resourceClaims,omitempty"` + // DisruptionMode defines the mode in which a given PodGroup can be disrupted. + // One of Pod, PodGroup. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + DisruptionMode *schedulingv1alpha2.DisruptionMode `json:"disruptionMode,omitempty"` + // PriorityClassName indicates the priority that should be considered when scheduling + // a pod group created from this template. If no priority class is specified, admission + // control can set this to the global default priority class if it exists. Otherwise, + // pod groups created from this template will have the priority set to zero. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + PriorityClassName *string `json:"priorityClassName,omitempty"` + // Priority is the value of priority of pod groups created from this template. Various + // system components use this field to find the priority of the pod group. When + // Priority Admission Controller is enabled, it prevents users from setting this field. + // The admission controller populates this field from PriorityClassName. + // The higher the value, the higher the priority. + // This field is available only when the WorkloadAwarePreemption feature gate + // is enabled. + Priority *int32 `json:"priority,omitempty"` +} + +// PodGroupTemplateApplyConfiguration constructs a declarative configuration of the PodGroupTemplate type for use with +// apply. +func PodGroupTemplate() *PodGroupTemplateApplyConfiguration { + return &PodGroupTemplateApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PodGroupTemplateApplyConfiguration) WithName(value string) *PodGroupTemplateApplyConfiguration { + b.Name = &value + return b +} + +// WithSchedulingPolicy sets the SchedulingPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchedulingPolicy field is set to the value of the last call. +func (b *PodGroupTemplateApplyConfiguration) WithSchedulingPolicy(value *PodGroupSchedulingPolicyApplyConfiguration) *PodGroupTemplateApplyConfiguration { + b.SchedulingPolicy = value + return b +} + +// WithSchedulingConstraints sets the SchedulingConstraints field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchedulingConstraints field is set to the value of the last call. +func (b *PodGroupTemplateApplyConfiguration) WithSchedulingConstraints(value *PodGroupSchedulingConstraintsApplyConfiguration) *PodGroupTemplateApplyConfiguration { + b.SchedulingConstraints = value + return b +} + +// WithResourceClaims adds the given value to the ResourceClaims field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ResourceClaims field. +func (b *PodGroupTemplateApplyConfiguration) WithResourceClaims(values ...*PodGroupResourceClaimApplyConfiguration) *PodGroupTemplateApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithResourceClaims") + } + b.ResourceClaims = append(b.ResourceClaims, *values[i]) + } + return b +} + +// WithDisruptionMode sets the DisruptionMode field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DisruptionMode field is set to the value of the last call. +func (b *PodGroupTemplateApplyConfiguration) WithDisruptionMode(value schedulingv1alpha2.DisruptionMode) *PodGroupTemplateApplyConfiguration { + b.DisruptionMode = &value + return b +} + +// WithPriorityClassName sets the PriorityClassName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PriorityClassName field is set to the value of the last call. +func (b *PodGroupTemplateApplyConfiguration) WithPriorityClassName(value string) *PodGroupTemplateApplyConfiguration { + b.PriorityClassName = &value + return b +} + +// WithPriority sets the Priority field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Priority field is set to the value of the last call. +func (b *PodGroupTemplateApplyConfiguration) WithPriority(value int32) *PodGroupTemplateApplyConfiguration { + b.Priority = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgrouptemplatereference.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgrouptemplatereference.go new file mode 100644 index 0000000000000..f8b1f61ffc4b8 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/podgrouptemplatereference.go @@ -0,0 +1,44 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// PodGroupTemplateReferenceApplyConfiguration represents a declarative configuration of the PodGroupTemplateReference type for use +// with apply. +// +// PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload). +// Exactly one reference must be set. +type PodGroupTemplateReferenceApplyConfiguration struct { + // Workload references the PodGroupTemplate within the Workload object that was used to create + // the PodGroup. + Workload *WorkloadPodGroupTemplateReferenceApplyConfiguration `json:"workload,omitempty"` +} + +// PodGroupTemplateReferenceApplyConfiguration constructs a declarative configuration of the PodGroupTemplateReference type for use with +// apply. +func PodGroupTemplateReference() *PodGroupTemplateReferenceApplyConfiguration { + return &PodGroupTemplateReferenceApplyConfiguration{} +} + +// WithWorkload sets the Workload field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Workload field is set to the value of the last call. +func (b *PodGroupTemplateReferenceApplyConfiguration) WithWorkload(value *WorkloadPodGroupTemplateReferenceApplyConfiguration) *PodGroupTemplateReferenceApplyConfiguration { + b.Workload = value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/topologyconstraint.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/topologyconstraint.go new file mode 100644 index 0000000000000..92727eb2bb3f7 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/topologyconstraint.go @@ -0,0 +1,45 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// TopologyConstraintApplyConfiguration represents a declarative configuration of the TopologyConstraint type for use +// with apply. +// +// TopologyConstraint defines a topology constraint for a PodGroup. +type TopologyConstraintApplyConfiguration struct { + // Key specifies the key of the node label representing the topology domain. + // All pods within the PodGroup must be colocated within the same domain instance. + // Different PodGroups can land on different domain instances even if they derive from the same PodGroupTemplate. + // Examples: "topology.kubernetes.io/rack" + Key *string `json:"key,omitempty"` +} + +// TopologyConstraintApplyConfiguration constructs a declarative configuration of the TopologyConstraint type for use with +// apply. +func TopologyConstraint() *TopologyConstraintApplyConfiguration { + return &TopologyConstraintApplyConfiguration{} +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *TopologyConstraintApplyConfiguration) WithKey(value string) *TopologyConstraintApplyConfiguration { + b.Key = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/typedlocalobjectreference.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/typedlocalobjectreference.go similarity index 99% rename from staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/typedlocalobjectreference.go rename to staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/typedlocalobjectreference.go index 73c11c884e86a..cfbbc48e0756d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/typedlocalobjectreference.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/typedlocalobjectreference.go @@ -16,7 +16,7 @@ limitations under the License. // Code generated by applyconfiguration-gen. DO NOT EDIT. -package v1alpha1 +package v1alpha2 // TypedLocalObjectReferenceApplyConfiguration represents a declarative configuration of the TypedLocalObjectReference type for use // with apply. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workload.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workload.go new file mode 100644 index 0000000000000..787095d238f37 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workload.go @@ -0,0 +1,280 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + internal "k8s.io/client-go/applyconfigurations/internal" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// WorkloadApplyConfiguration represents a declarative configuration of the Workload type for use +// with apply. +// +// Workload allows for expressing scheduling constraints that should be used +// when managing the lifecycle of workloads from the scheduling perspective, +// including scheduling, preemption, eviction and other phases. +// Workload API enablement is toggled by the GenericWorkload feature gate. +type WorkloadApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // Spec defines the desired behavior of a Workload. + Spec *WorkloadSpecApplyConfiguration `json:"spec,omitempty"` +} + +// Workload constructs a declarative configuration of the Workload type for use with +// apply. +func Workload(name, namespace string) *WorkloadApplyConfiguration { + b := &WorkloadApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Workload") + b.WithAPIVersion("scheduling.k8s.io/v1alpha2") + return b +} + +// ExtractWorkloadFrom extracts the applied configuration owned by fieldManager from +// workload for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// workload must be a unmodified Workload API object that was retrieved from the Kubernetes API. +// ExtractWorkloadFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractWorkloadFrom(workload *schedulingv1alpha2.Workload, fieldManager string, subresource string) (*WorkloadApplyConfiguration, error) { + b := &WorkloadApplyConfiguration{} + err := managedfields.ExtractInto(workload, internal.Parser().Type("io.k8s.api.scheduling.v1alpha2.Workload"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(workload.Name) + b.WithNamespace(workload.Namespace) + + b.WithKind("Workload") + b.WithAPIVersion("scheduling.k8s.io/v1alpha2") + return b, nil +} + +// ExtractWorkload extracts the applied configuration owned by fieldManager from +// workload. If no managedFields are found in workload for fieldManager, a +// WorkloadApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// workload must be a unmodified Workload API object that was retrieved from the Kubernetes API. +// ExtractWorkload provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractWorkload(workload *schedulingv1alpha2.Workload, fieldManager string) (*WorkloadApplyConfiguration, error) { + return ExtractWorkloadFrom(workload, fieldManager, "") +} + +func (b WorkloadApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithKind(value string) *WorkloadApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithAPIVersion(value string) *WorkloadApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithName(value string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithGenerateName(value string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithNamespace(value string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithUID(value types.UID) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithResourceVersion(value string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithGeneration(value int64) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithCreationTimestamp(value metav1.Time) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *WorkloadApplyConfiguration) WithLabels(entries map[string]string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *WorkloadApplyConfiguration) WithAnnotations(entries map[string]string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *WorkloadApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *WorkloadApplyConfiguration) WithFinalizers(values ...string) *WorkloadApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *WorkloadApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *WorkloadApplyConfiguration) WithSpec(value *WorkloadSpecApplyConfiguration) *WorkloadApplyConfiguration { + b.Spec = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *WorkloadApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *WorkloadApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *WorkloadApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *WorkloadApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workloadpodgrouptemplatereference.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workloadpodgrouptemplatereference.go new file mode 100644 index 0000000000000..ff3ab06c8d022 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workloadpodgrouptemplatereference.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// WorkloadPodGroupTemplateReferenceApplyConfiguration represents a declarative configuration of the WorkloadPodGroupTemplateReference type for use +// with apply. +// +// WorkloadPodGroupTemplateReference references the PodGroupTemplate within the Workload object. +type WorkloadPodGroupTemplateReferenceApplyConfiguration struct { + // WorkloadName defines the name of the Workload object. + WorkloadName *string `json:"workloadName,omitempty"` + // PodGroupTemplateName defines the PodGroupTemplate name within the Workload object. + PodGroupTemplateName *string `json:"podGroupTemplateName,omitempty"` +} + +// WorkloadPodGroupTemplateReferenceApplyConfiguration constructs a declarative configuration of the WorkloadPodGroupTemplateReference type for use with +// apply. +func WorkloadPodGroupTemplateReference() *WorkloadPodGroupTemplateReferenceApplyConfiguration { + return &WorkloadPodGroupTemplateReferenceApplyConfiguration{} +} + +// WithWorkloadName sets the WorkloadName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the WorkloadName field is set to the value of the last call. +func (b *WorkloadPodGroupTemplateReferenceApplyConfiguration) WithWorkloadName(value string) *WorkloadPodGroupTemplateReferenceApplyConfiguration { + b.WorkloadName = &value + return b +} + +// WithPodGroupTemplateName sets the PodGroupTemplateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PodGroupTemplateName field is set to the value of the last call. +func (b *WorkloadPodGroupTemplateReferenceApplyConfiguration) WithPodGroupTemplateName(value string) *WorkloadPodGroupTemplateReferenceApplyConfiguration { + b.PodGroupTemplateName = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/workloadspec.go b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workloadspec.go similarity index 75% rename from staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/workloadspec.go rename to staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workloadspec.go index 1ceb9210db1ce..33eb8a5c09b03 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha1/workloadspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/scheduling/v1alpha2/workloadspec.go @@ -16,7 +16,7 @@ limitations under the License. // Code generated by applyconfiguration-gen. DO NOT EDIT. -package v1alpha1 +package v1alpha2 // WorkloadSpecApplyConfiguration represents a declarative configuration of the WorkloadSpec type for use // with apply. @@ -26,11 +26,11 @@ type WorkloadSpecApplyConfiguration struct { // ControllerRef is an optional reference to the controlling object, such as a // Deployment or Job. This field is intended for use by tools like CLIs // to provide a link back to the original workload definition. - // When set, it cannot be changed. + // This field is immutable. ControllerRef *TypedLocalObjectReferenceApplyConfiguration `json:"controllerRef,omitempty"` - // PodGroups is the list of pod groups that make up the Workload. - // The maximum number of pod groups is 8. This field is immutable. - PodGroups []PodGroupApplyConfiguration `json:"podGroups,omitempty"` + // PodGroupTemplates is the list of templates that make up the Workload. + // The maximum number of templates is 8. This field is immutable. + PodGroupTemplates []PodGroupTemplateApplyConfiguration `json:"podGroupTemplates,omitempty"` } // WorkloadSpecApplyConfiguration constructs a declarative configuration of the WorkloadSpec type for use with @@ -47,15 +47,15 @@ func (b *WorkloadSpecApplyConfiguration) WithControllerRef(value *TypedLocalObje return b } -// WithPodGroups adds the given value to the PodGroups field in the declarative configuration +// WithPodGroupTemplates adds the given value to the PodGroupTemplates field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the PodGroups field. -func (b *WorkloadSpecApplyConfiguration) WithPodGroups(values ...*PodGroupApplyConfiguration) *WorkloadSpecApplyConfiguration { +// If called multiple times, values provided by each call will be appended to the PodGroupTemplates field. +func (b *WorkloadSpecApplyConfiguration) WithPodGroupTemplates(values ...*PodGroupTemplateApplyConfiguration) *WorkloadSpecApplyConfiguration { for i := range values { if values[i] == nil { - panic("nil value passed to WithPodGroups") + panic("nil value passed to WithPodGroupTemplates") } - b.PodGroups = append(b.PodGroups, *values[i]) + b.PodGroupTemplates = append(b.PodGroupTemplates, *values[i]) } return b } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/csidriverspec.go b/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/csidriverspec.go index 653329ececb02..4a715c141b83b 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/csidriverspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/csidriverspec.go @@ -154,7 +154,7 @@ type CSIDriverSpecApplyConfiguration struct { // occur (neither periodic nor upon detecting capacity-related failures), and the // allocatable.count remains static. The minimum allowed value for this field is 10 seconds. // - // This is a beta feature and requires the MutableCSINodeAllocatableCount feature gate to be enabled. + // This feature requires the MutableCSINodeAllocatableCount feature gate to be enabled. // // This field is mutable. NodeAllocatableUpdatePeriodSeconds *int64 `json:"nodeAllocatableUpdatePeriodSeconds,omitempty"` @@ -178,6 +178,24 @@ type CSIDriverSpecApplyConfiguration struct { // // Default behavior if unset is to pass tokens in the VolumeContext field. ServiceAccountTokenInSecrets *bool `json:"serviceAccountTokenInSecrets,omitempty"` + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + PreventPodSchedulingIfMissing *bool `json:"preventPodSchedulingIfMissing,omitempty"` } // CSIDriverSpecApplyConfiguration constructs a declarative configuration of the CSIDriverSpec type for use with @@ -272,3 +290,11 @@ func (b *CSIDriverSpecApplyConfiguration) WithServiceAccountTokenInSecrets(value b.ServiceAccountTokenInSecrets = &value return b } + +// WithPreventPodSchedulingIfMissing sets the PreventPodSchedulingIfMissing field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreventPodSchedulingIfMissing field is set to the value of the last call. +func (b *CSIDriverSpecApplyConfiguration) WithPreventPodSchedulingIfMissing(value bool) *CSIDriverSpecApplyConfiguration { + b.PreventPodSchedulingIfMissing = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/volumeerror.go b/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/volumeerror.go index b1a572f4eb3b3..607a39ea11857 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/volumeerror.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/storage/v1/volumeerror.go @@ -35,7 +35,7 @@ type VolumeErrorApplyConfiguration struct { Message *string `json:"message,omitempty"` // errorCode is a numeric gRPC code representing the error encountered during Attach or Detach operations. // - // This is an optional, beta field that requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. + // This field requires the MutableCSINodeAllocatableCount feature gate being enabled to be set. ErrorCode *int32 `json:"errorCode,omitempty"` } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/storage/v1beta1/csidriverspec.go b/staging/src/k8s.io/client-go/applyconfigurations/storage/v1beta1/csidriverspec.go index 323065d2a415e..cec76d9fd82af 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/storage/v1beta1/csidriverspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/storage/v1beta1/csidriverspec.go @@ -178,6 +178,24 @@ type CSIDriverSpecApplyConfiguration struct { // // Default behavior if unset is to pass tokens in the VolumeContext field. ServiceAccountTokenInSecrets *bool `json:"serviceAccountTokenInSecrets,omitempty"` + // PreventPodSchedulingIfMissing indicates that the CSI driver wants to prevent pod + // scheduling if the CSI driver on the node is missing. + // + // Enabling this option will prevent the scheduler (or any other + // component which embeds default scheduler such as cluster-autoscaler) from + // scheduling pods to nodes where CSI driver is not installed. + // + // For components(such as cluster-autoscaler) that embed the scheduler and run + // pod placement simulations using scheduler plugins, they MUST be aware of + // CSI driver registration information via CSINode object. They must create simulated + // CSINode objects in addition to Node objects during scheduling simulation, otherwise + // if PreventPodSchedulingIfMissing is enabled globally for CSIDriver object, any + // newly created node may be rejected by the scheduler because of missing CSI driver + // information from the node. + // + // This is an alpha feature and requires the VolumeLimitScaling feature gate to be enabled. + // Default is "false". + PreventPodSchedulingIfMissing *bool `json:"preventPodSchedulingIfMissing,omitempty"` } // CSIDriverSpecApplyConfiguration constructs a declarative configuration of the CSIDriverSpec type for use with @@ -272,3 +290,11 @@ func (b *CSIDriverSpecApplyConfiguration) WithServiceAccountTokenInSecrets(value b.ServiceAccountTokenInSecrets = &value return b } + +// WithPreventPodSchedulingIfMissing sets the PreventPodSchedulingIfMissing field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreventPodSchedulingIfMissing field is set to the value of the last call. +func (b *CSIDriverSpecApplyConfiguration) WithPreventPodSchedulingIfMissing(value bool) *CSIDriverSpecApplyConfiguration { + b.PreventPodSchedulingIfMissing = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/utils.go b/staging/src/k8s.io/client-go/applyconfigurations/utils.go index ef6f16e83b998..355051fc8dca3 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/utils.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/utils.go @@ -28,8 +28,6 @@ import ( v1beta2 "k8s.io/api/apps/v1beta2" autoscalingv1 "k8s.io/api/autoscaling/v1" v2 "k8s.io/api/autoscaling/v2" - v2beta1 "k8s.io/api/autoscaling/v2beta1" - v2beta2 "k8s.io/api/autoscaling/v2beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1 "k8s.io/api/certificates/v1" @@ -64,7 +62,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingv1 "k8s.io/api/scheduling/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" storagev1 "k8s.io/api/storage/v1" storagev1alpha1 "k8s.io/api/storage/v1alpha1" @@ -83,8 +81,6 @@ import ( appsv1beta2 "k8s.io/client-go/applyconfigurations/apps/v1beta2" applyconfigurationsautoscalingv1 "k8s.io/client-go/applyconfigurations/autoscaling/v1" autoscalingv2 "k8s.io/client-go/applyconfigurations/autoscaling/v2" - autoscalingv2beta1 "k8s.io/client-go/applyconfigurations/autoscaling/v2beta1" - autoscalingv2beta2 "k8s.io/client-go/applyconfigurations/autoscaling/v2beta2" applyconfigurationsbatchv1 "k8s.io/client-go/applyconfigurations/batch/v1" applyconfigurationsbatchv1beta1 "k8s.io/client-go/applyconfigurations/batch/v1beta1" applyconfigurationscertificatesv1 "k8s.io/client-go/applyconfigurations/certificates/v1" @@ -121,7 +117,7 @@ import ( applyconfigurationsresourcev1beta1 "k8s.io/client-go/applyconfigurations/resource/v1beta1" applyconfigurationsresourcev1beta2 "k8s.io/client-go/applyconfigurations/resource/v1beta2" applyconfigurationsschedulingv1 "k8s.io/client-go/applyconfigurations/scheduling/v1" - applyconfigurationsschedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1" + applyconfigurationsschedulingv1alpha2 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha2" applyconfigurationsschedulingv1beta1 "k8s.io/client-go/applyconfigurations/scheduling/v1beta1" applyconfigurationsstoragev1 "k8s.io/client-go/applyconfigurations/storage/v1" applyconfigurationsstoragev1alpha1 "k8s.io/client-go/applyconfigurations/storage/v1alpha1" @@ -134,18 +130,32 @@ import ( func ForKind(kind schema.GroupVersionKind) interface{} { switch kind { // Group=admissionregistration.k8s.io, Version=v1 + case v1.SchemeGroupVersion.WithKind("ApplyConfiguration"): + return &admissionregistrationv1.ApplyConfigurationApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("AuditAnnotation"): return &admissionregistrationv1.AuditAnnotationApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("ExpressionWarning"): return &admissionregistrationv1.ExpressionWarningApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("JSONPatch"): + return &admissionregistrationv1.JSONPatchApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("MatchCondition"): return &admissionregistrationv1.MatchConditionApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("MatchResources"): return &admissionregistrationv1.MatchResourcesApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("MutatingAdmissionPolicy"): + return &admissionregistrationv1.MutatingAdmissionPolicyApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("MutatingAdmissionPolicyBinding"): + return &admissionregistrationv1.MutatingAdmissionPolicyBindingApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("MutatingAdmissionPolicyBindingSpec"): + return &admissionregistrationv1.MutatingAdmissionPolicyBindingSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("MutatingAdmissionPolicySpec"): + return &admissionregistrationv1.MutatingAdmissionPolicySpecApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("MutatingWebhook"): return &admissionregistrationv1.MutatingWebhookApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("MutatingWebhookConfiguration"): return &admissionregistrationv1.MutatingWebhookConfigurationApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("Mutation"): + return &admissionregistrationv1.MutationApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("NamedRuleWithOperations"): return &admissionregistrationv1.NamedRuleWithOperationsApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("ParamKind"): @@ -489,90 +499,6 @@ func ForKind(kind schema.GroupVersionKind) interface{} { case v2.SchemeGroupVersion.WithKind("ResourceMetricStatus"): return &autoscalingv2.ResourceMetricStatusApplyConfiguration{} - // Group=autoscaling, Version=v2beta1 - case v2beta1.SchemeGroupVersion.WithKind("ContainerResourceMetricSource"): - return &autoscalingv2beta1.ContainerResourceMetricSourceApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ContainerResourceMetricStatus"): - return &autoscalingv2beta1.ContainerResourceMetricStatusApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("CrossVersionObjectReference"): - return &autoscalingv2beta1.CrossVersionObjectReferenceApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ExternalMetricSource"): - return &autoscalingv2beta1.ExternalMetricSourceApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ExternalMetricStatus"): - return &autoscalingv2beta1.ExternalMetricStatusApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"): - return &autoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerCondition"): - return &autoscalingv2beta1.HorizontalPodAutoscalerConditionApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerSpec"): - return &autoscalingv2beta1.HorizontalPodAutoscalerSpecApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerStatus"): - return &autoscalingv2beta1.HorizontalPodAutoscalerStatusApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("MetricSpec"): - return &autoscalingv2beta1.MetricSpecApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("MetricStatus"): - return &autoscalingv2beta1.MetricStatusApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ObjectMetricSource"): - return &autoscalingv2beta1.ObjectMetricSourceApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ObjectMetricStatus"): - return &autoscalingv2beta1.ObjectMetricStatusApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("PodsMetricSource"): - return &autoscalingv2beta1.PodsMetricSourceApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("PodsMetricStatus"): - return &autoscalingv2beta1.PodsMetricStatusApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ResourceMetricSource"): - return &autoscalingv2beta1.ResourceMetricSourceApplyConfiguration{} - case v2beta1.SchemeGroupVersion.WithKind("ResourceMetricStatus"): - return &autoscalingv2beta1.ResourceMetricStatusApplyConfiguration{} - - // Group=autoscaling, Version=v2beta2 - case v2beta2.SchemeGroupVersion.WithKind("ContainerResourceMetricSource"): - return &autoscalingv2beta2.ContainerResourceMetricSourceApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ContainerResourceMetricStatus"): - return &autoscalingv2beta2.ContainerResourceMetricStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("CrossVersionObjectReference"): - return &autoscalingv2beta2.CrossVersionObjectReferenceApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ExternalMetricSource"): - return &autoscalingv2beta2.ExternalMetricSourceApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ExternalMetricStatus"): - return &autoscalingv2beta2.ExternalMetricStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"): - return &autoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerBehavior"): - return &autoscalingv2beta2.HorizontalPodAutoscalerBehaviorApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerCondition"): - return &autoscalingv2beta2.HorizontalPodAutoscalerConditionApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerSpec"): - return &autoscalingv2beta2.HorizontalPodAutoscalerSpecApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerStatus"): - return &autoscalingv2beta2.HorizontalPodAutoscalerStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HPAScalingPolicy"): - return &autoscalingv2beta2.HPAScalingPolicyApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("HPAScalingRules"): - return &autoscalingv2beta2.HPAScalingRulesApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("MetricIdentifier"): - return &autoscalingv2beta2.MetricIdentifierApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("MetricSpec"): - return &autoscalingv2beta2.MetricSpecApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("MetricStatus"): - return &autoscalingv2beta2.MetricStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("MetricTarget"): - return &autoscalingv2beta2.MetricTargetApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("MetricValueStatus"): - return &autoscalingv2beta2.MetricValueStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ObjectMetricSource"): - return &autoscalingv2beta2.ObjectMetricSourceApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ObjectMetricStatus"): - return &autoscalingv2beta2.ObjectMetricStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("PodsMetricSource"): - return &autoscalingv2beta2.PodsMetricSourceApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("PodsMetricStatus"): - return &autoscalingv2beta2.PodsMetricStatusApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ResourceMetricSource"): - return &autoscalingv2beta2.ResourceMetricSourceApplyConfiguration{} - case v2beta2.SchemeGroupVersion.WithKind("ResourceMetricStatus"): - return &autoscalingv2beta2.ResourceMetricStatusApplyConfiguration{} - // Group=batch, Version=v1 case batchv1.SchemeGroupVersion.WithKind("CronJob"): return &applyconfigurationsbatchv1.CronJobApplyConfiguration{} @@ -818,6 +744,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationscorev1.HTTPHeaderApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("ImageVolumeSource"): return &applyconfigurationscorev1.ImageVolumeSourceApplyConfiguration{} + case corev1.SchemeGroupVersion.WithKind("ImageVolumeStatus"): + return &applyconfigurationscorev1.ImageVolumeStatusApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("ISCSIPersistentVolumeSource"): return &applyconfigurationscorev1.ISCSIPersistentVolumeSourceApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("ISCSIVolumeSource"): @@ -862,6 +790,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationscorev1.NodeAddressApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("NodeAffinity"): return &applyconfigurationscorev1.NodeAffinityApplyConfiguration{} + case corev1.SchemeGroupVersion.WithKind("NodeAllocatableResourceClaimStatus"): + return &applyconfigurationscorev1.NodeAllocatableResourceClaimStatusApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("NodeCondition"): return &applyconfigurationscorev1.NodeConditionApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("NodeConfigSource"): @@ -946,6 +876,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationscorev1.PodResourceClaimStatusApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("PodSchedulingGate"): return &applyconfigurationscorev1.PodSchedulingGateApplyConfiguration{} + case corev1.SchemeGroupVersion.WithKind("PodSchedulingGroup"): + return &applyconfigurationscorev1.PodSchedulingGroupApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("PodSecurityContext"): return &applyconfigurationscorev1.PodSecurityContextApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("PodSpec"): @@ -1078,14 +1010,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationscorev1.VolumeResourceRequirementsApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("VolumeSource"): return &applyconfigurationscorev1.VolumeSourceApplyConfiguration{} + case corev1.SchemeGroupVersion.WithKind("VolumeStatus"): + return &applyconfigurationscorev1.VolumeStatusApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("VsphereVirtualDiskVolumeSource"): return &applyconfigurationscorev1.VsphereVirtualDiskVolumeSourceApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("WeightedPodAffinityTerm"): return &applyconfigurationscorev1.WeightedPodAffinityTermApplyConfiguration{} case corev1.SchemeGroupVersion.WithKind("WindowsSecurityContextOptions"): return &applyconfigurationscorev1.WindowsSecurityContextOptionsApplyConfiguration{} - case corev1.SchemeGroupVersion.WithKind("WorkloadReference"): - return &applyconfigurationscorev1.WorkloadReferenceApplyConfiguration{} // Group=discovery.k8s.io, Version=v1 case discoveryv1.SchemeGroupVersion.WithKind("Endpoint"): @@ -1690,6 +1622,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationsresourcev1.ExactDeviceRequestApplyConfiguration{} case resourcev1.SchemeGroupVersion.WithKind("NetworkDeviceData"): return &applyconfigurationsresourcev1.NetworkDeviceDataApplyConfiguration{} + case resourcev1.SchemeGroupVersion.WithKind("NodeAllocatableResourceMapping"): + return &applyconfigurationsresourcev1.NodeAllocatableResourceMappingApplyConfiguration{} case resourcev1.SchemeGroupVersion.WithKind("OpaqueDeviceConfiguration"): return &applyconfigurationsresourcev1.OpaqueDeviceConfigurationApplyConfiguration{} case resourcev1.SchemeGroupVersion.WithKind("ResourceClaim"): @@ -1722,6 +1656,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &resourcev1alpha3.DeviceTaintRuleStatusApplyConfiguration{} case v1alpha3.SchemeGroupVersion.WithKind("DeviceTaintSelector"): return &resourcev1alpha3.DeviceTaintSelectorApplyConfiguration{} + case v1alpha3.SchemeGroupVersion.WithKind("PoolStatus"): + return &resourcev1alpha3.PoolStatusApplyConfiguration{} + case v1alpha3.SchemeGroupVersion.WithKind("ResourcePoolStatusRequest"): + return &resourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration{} + case v1alpha3.SchemeGroupVersion.WithKind("ResourcePoolStatusRequestSpec"): + return &resourcev1alpha3.ResourcePoolStatusRequestSpecApplyConfiguration{} + case v1alpha3.SchemeGroupVersion.WithKind("ResourcePoolStatusRequestStatus"): + return &resourcev1alpha3.ResourcePoolStatusRequestStatusApplyConfiguration{} // Group=resource.k8s.io, Version=v1beta1 case resourcev1beta1.SchemeGroupVersion.WithKind("AllocatedDeviceStatus"): @@ -1782,6 +1724,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationsresourcev1beta1.DeviceTolerationApplyConfiguration{} case resourcev1beta1.SchemeGroupVersion.WithKind("NetworkDeviceData"): return &applyconfigurationsresourcev1beta1.NetworkDeviceDataApplyConfiguration{} + case resourcev1beta1.SchemeGroupVersion.WithKind("NodeAllocatableResourceMapping"): + return &applyconfigurationsresourcev1beta1.NodeAllocatableResourceMappingApplyConfiguration{} case resourcev1beta1.SchemeGroupVersion.WithKind("OpaqueDeviceConfiguration"): return &applyconfigurationsresourcev1beta1.OpaqueDeviceConfigurationApplyConfiguration{} case resourcev1beta1.SchemeGroupVersion.WithKind("ResourceClaim"): @@ -1856,12 +1800,22 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationsresourcev1beta2.DeviceSubRequestApplyConfiguration{} case resourcev1beta2.SchemeGroupVersion.WithKind("DeviceTaint"): return &applyconfigurationsresourcev1beta2.DeviceTaintApplyConfiguration{} + case resourcev1beta2.SchemeGroupVersion.WithKind("DeviceTaintRule"): + return &applyconfigurationsresourcev1beta2.DeviceTaintRuleApplyConfiguration{} + case resourcev1beta2.SchemeGroupVersion.WithKind("DeviceTaintRuleSpec"): + return &applyconfigurationsresourcev1beta2.DeviceTaintRuleSpecApplyConfiguration{} + case resourcev1beta2.SchemeGroupVersion.WithKind("DeviceTaintRuleStatus"): + return &applyconfigurationsresourcev1beta2.DeviceTaintRuleStatusApplyConfiguration{} + case resourcev1beta2.SchemeGroupVersion.WithKind("DeviceTaintSelector"): + return &applyconfigurationsresourcev1beta2.DeviceTaintSelectorApplyConfiguration{} case resourcev1beta2.SchemeGroupVersion.WithKind("DeviceToleration"): return &applyconfigurationsresourcev1beta2.DeviceTolerationApplyConfiguration{} case resourcev1beta2.SchemeGroupVersion.WithKind("ExactDeviceRequest"): return &applyconfigurationsresourcev1beta2.ExactDeviceRequestApplyConfiguration{} case resourcev1beta2.SchemeGroupVersion.WithKind("NetworkDeviceData"): return &applyconfigurationsresourcev1beta2.NetworkDeviceDataApplyConfiguration{} + case resourcev1beta2.SchemeGroupVersion.WithKind("NodeAllocatableResourceMapping"): + return &applyconfigurationsresourcev1beta2.NodeAllocatableResourceMappingApplyConfiguration{} case resourcev1beta2.SchemeGroupVersion.WithKind("OpaqueDeviceConfiguration"): return &applyconfigurationsresourcev1beta2.OpaqueDeviceConfigurationApplyConfiguration{} case resourcev1beta2.SchemeGroupVersion.WithKind("ResourceClaim"): @@ -1887,21 +1841,37 @@ func ForKind(kind schema.GroupVersionKind) interface{} { case schedulingv1.SchemeGroupVersion.WithKind("PriorityClass"): return &applyconfigurationsschedulingv1.PriorityClassApplyConfiguration{} - // Group=scheduling.k8s.io, Version=v1alpha1 - case schedulingv1alpha1.SchemeGroupVersion.WithKind("GangSchedulingPolicy"): - return &applyconfigurationsschedulingv1alpha1.GangSchedulingPolicyApplyConfiguration{} - case schedulingv1alpha1.SchemeGroupVersion.WithKind("PodGroup"): - return &applyconfigurationsschedulingv1alpha1.PodGroupApplyConfiguration{} - case schedulingv1alpha1.SchemeGroupVersion.WithKind("PodGroupPolicy"): - return &applyconfigurationsschedulingv1alpha1.PodGroupPolicyApplyConfiguration{} - case schedulingv1alpha1.SchemeGroupVersion.WithKind("PriorityClass"): - return &applyconfigurationsschedulingv1alpha1.PriorityClassApplyConfiguration{} - case schedulingv1alpha1.SchemeGroupVersion.WithKind("TypedLocalObjectReference"): - return &applyconfigurationsschedulingv1alpha1.TypedLocalObjectReferenceApplyConfiguration{} - case schedulingv1alpha1.SchemeGroupVersion.WithKind("Workload"): - return &applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration{} - case schedulingv1alpha1.SchemeGroupVersion.WithKind("WorkloadSpec"): - return &applyconfigurationsschedulingv1alpha1.WorkloadSpecApplyConfiguration{} + // Group=scheduling.k8s.io, Version=v1alpha2 + case schedulingv1alpha2.SchemeGroupVersion.WithKind("GangSchedulingPolicy"): + return &applyconfigurationsschedulingv1alpha2.GangSchedulingPolicyApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroup"): + return &applyconfigurationsschedulingv1alpha2.PodGroupApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupResourceClaim"): + return &applyconfigurationsschedulingv1alpha2.PodGroupResourceClaimApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupResourceClaimStatus"): + return &applyconfigurationsschedulingv1alpha2.PodGroupResourceClaimStatusApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupSchedulingConstraints"): + return &applyconfigurationsschedulingv1alpha2.PodGroupSchedulingConstraintsApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupSchedulingPolicy"): + return &applyconfigurationsschedulingv1alpha2.PodGroupSchedulingPolicyApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupSpec"): + return &applyconfigurationsschedulingv1alpha2.PodGroupSpecApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupStatus"): + return &applyconfigurationsschedulingv1alpha2.PodGroupStatusApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupTemplate"): + return &applyconfigurationsschedulingv1alpha2.PodGroupTemplateApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("PodGroupTemplateReference"): + return &applyconfigurationsschedulingv1alpha2.PodGroupTemplateReferenceApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("TopologyConstraint"): + return &applyconfigurationsschedulingv1alpha2.TopologyConstraintApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("TypedLocalObjectReference"): + return &applyconfigurationsschedulingv1alpha2.TypedLocalObjectReferenceApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("Workload"): + return &applyconfigurationsschedulingv1alpha2.WorkloadApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("WorkloadPodGroupTemplateReference"): + return &applyconfigurationsschedulingv1alpha2.WorkloadPodGroupTemplateReferenceApplyConfiguration{} + case schedulingv1alpha2.SchemeGroupVersion.WithKind("WorkloadSpec"): + return &applyconfigurationsschedulingv1alpha2.WorkloadSpecApplyConfiguration{} // Group=scheduling.k8s.io, Version=v1beta1 case schedulingv1beta1.SchemeGroupVersion.WithKind("PriorityClass"): diff --git a/staging/src/k8s.io/client-go/discovery/cached/disk/round_tripper.go b/staging/src/k8s.io/client-go/discovery/cached/disk/round_tripper.go index f3a4b2947f3ea..b978a2bed909d 100644 --- a/staging/src/k8s.io/client-go/discovery/cached/disk/round_tripper.go +++ b/staging/src/k8s.io/client-go/discovery/cached/disk/round_tripper.go @@ -24,8 +24,8 @@ import ( "os" "path/filepath" - "github.com/gregjones/httpcache" "github.com/peterbourgon/diskv" + "k8s.io/client-go/third_party/forked/httpcache" "k8s.io/klog/v2" ) @@ -66,7 +66,7 @@ func (rt *cacheRoundTripper) CancelRequest(req *http.Request) { func (rt *cacheRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt.Transport } -// A sumDiskCache is a cache backend for github.com/gregjones/httpcache. It is +// A sumDiskCache is a cache backend for httpcache. It is // similar to httpcache's diskcache package, but uses SHA256 sums to ensure // cache integrity at read time rather than fsyncing each cache entry to // increase the likelihood they will be persisted at write time. This avoids diff --git a/staging/src/k8s.io/client-go/dynamic/fake/simple.go b/staging/src/k8s.io/client-go/dynamic/fake/simple.go index 06ef859425f1e..39b3b6460016d 100644 --- a/staging/src/k8s.io/client-go/dynamic/fake/simple.go +++ b/staging/src/k8s.io/client-go/dynamic/fake/simple.go @@ -114,9 +114,13 @@ func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToList cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind, tracker: o} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } diff --git a/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go b/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go index 50f22c49b9e46..63ace4298af90 100644 --- a/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go +++ b/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go @@ -186,7 +186,7 @@ func Test_ListKind(t *testing.T) { "kind": "TheKindList", "metadata": map[string]interface{}{ "continue": "", - "resourceVersion": "", + "resourceVersion": "4", // Three objects created so far, starting value is 1. }, }, Items: []unstructured.Unstructured{ @@ -340,7 +340,7 @@ func TestListWithUnstructuredObjectsAndTypedScheme(t *testing.T) { expectedList := &unstructured.UnstructuredList{} expectedList.SetGroupVersionKind(listGVK) - expectedList.SetResourceVersion("") // by product of the fake setting resource version + expectedList.SetResourceVersion("2") // One object created so far, initial value is 1. expectedList.SetContinue("") expectedList.Items = append(expectedList.Items, u) @@ -369,7 +369,7 @@ func TestListWithNoFixturesAndTypedScheme(t *testing.T) { expectedList := &unstructured.UnstructuredList{} expectedList.SetGroupVersionKind(listGVK) - expectedList.SetResourceVersion("") // by product of the fake setting resource version + expectedList.SetResourceVersion("1") // No objects created so far. expectedList.SetContinue("") if diff := cmp.Diff(expectedList, list); diff != "" { @@ -402,7 +402,7 @@ func TestListWithNoScheme(t *testing.T) { expectedList := &unstructured.UnstructuredList{} expectedList.SetGroupVersionKind(listGVK) - expectedList.SetResourceVersion("") // by product of the fake setting resource version + expectedList.SetResourceVersion("2") // One object created so far, initial value is 1. expectedList.SetContinue("") expectedList.Items = append(expectedList.Items, u) @@ -443,7 +443,7 @@ func TestListWithTypedFixtures(t *testing.T) { expectedList := &unstructured.UnstructuredList{} expectedList.SetGroupVersionKind(listGVK) - expectedList.SetResourceVersion("") // by product of the fake setting resource version + expectedList.SetResourceVersion("2") // One object created so far, initial value is 1. expectedList.SetContinue("") expectedList.Items = []unstructured.Unstructured{u} diff --git a/staging/src/k8s.io/client-go/examples/fake-client/main_test.go b/staging/src/k8s.io/client-go/examples/fake-client/main_test.go index a2f0eeef0dfbe..548ac86bde6c3 100644 --- a/staging/src/k8s.io/client-go/examples/fake-client/main_test.go +++ b/staging/src/k8s.io/client-go/examples/fake-client/main_test.go @@ -41,9 +41,13 @@ func TestFakeClient(t *testing.T) { client := fake.NewSimpleClientset() // A catch-all watch reactor that allows us to inject the watcherStarted channel. client.PrependWatchReactor("*", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(clienttesting.WatchActionImpl); ok { + opts = watchAction.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := client.Tracker().Watch(gvr, ns) + watch, err := client.Tracker().Watch(gvr, ns, opts) if err != nil { return false, nil, err } diff --git a/staging/src/k8s.io/client-go/features/envvar.go b/staging/src/k8s.io/client-go/features/envvar.go index 8c3f887dc4227..93aa21fa71bfe 100644 --- a/staging/src/k8s.io/client-go/features/envvar.go +++ b/staging/src/k8s.io/client-go/features/envvar.go @@ -121,12 +121,28 @@ func (f *envVarFeatureGates) Enabled(key Feature) bool { // Features set via this method take precedence over // the features set via environment variables. func (f *envVarFeatureGates) Set(featureName Feature, featureValue bool) error { + return f.set(featureName, featureValue, false) +} + +// SetForTesting sets the given feature to the given value. This method +// bypasses the check for locked features and should only be used for +// testing purposes. +// +// Features set via this method take precedence over +// the features set via environment variables. +func (f *envVarFeatureGates) SetForTesting(featureName Feature, featureValue bool) error { + return f.set(featureName, featureValue, true) +} + +func (f *envVarFeatureGates) set(featureName Feature, featureValue bool, allowChangingLockedFeatures bool) error { feature, ok := f.known[featureName] if !ok { return fmt.Errorf("feature %q is not registered in FeatureGates %q", featureName, f.callSiteName) } - if feature.LockToDefault && feature.Default != featureValue { - return fmt.Errorf("cannot set feature gate %q to %v, feature is locked to %v", featureName, featureValue, feature.Default) + if !allowChangingLockedFeatures { + if feature.LockToDefault && feature.Default != featureValue { + return fmt.Errorf("cannot set feature gate %q to %v, feature is locked to %v", featureName, featureValue, feature.Default) + } } f.lockEnabledViaSetMethod.Lock() @@ -141,6 +157,13 @@ func (f *envVarFeatureGates) Set(featureName Feature, featureValue bool) error { // read from the corresponding environmental variable. func (f *envVarFeatureGates) getEnabledMapFromEnvVar() map[Feature]bool { f.readEnvVarsOnce.Do(func() { + // This code does not really support contextual logging. Making it do so has huge + // implications for several call chains because the Enabled call then needs + // a `*WithLogger` variant. This does not matter in Kubernetes itself because + // all Kubernetes components replace the feature gate implementation used + // by client-go, but it might matter elsewhere. + logger := klog.Background() + featureGatesState := map[Feature]bool{} for feature, featureSpec := range f.known { featureState, featureStateSet := os.LookupEnv(fmt.Sprintf("KUBE_FEATURE_%s", feature)) @@ -150,10 +173,10 @@ func (f *envVarFeatureGates) getEnabledMapFromEnvVar() map[Feature]bool { boolVal, boolErr := strconv.ParseBool(featureState) switch { case boolErr != nil: - utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, due to %v", feature, featureState, boolErr)) + utilruntime.HandleErrorWithLogger(logger, boolErr, "Could not set feature gate", "feature", feature, "desiredState", featureState) case featureSpec.LockToDefault: if boolVal != featureSpec.Default { - utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, feature is locked to %v", feature, featureState, featureSpec.Default)) + utilruntime.HandleErrorWithLogger(logger, nil, "Could not set feature gate, feature is locked", "feature", feature, "desiredState", featureState, "lockedState", featureSpec.Default) break } featureGatesState[feature] = featureSpec.Default @@ -166,10 +189,10 @@ func (f *envVarFeatureGates) getEnabledMapFromEnvVar() map[Feature]bool { for feature, featureSpec := range f.known { if featureState, ok := featureGatesState[feature]; ok { - klog.V(1).InfoS("Feature gate updated state", "feature", feature, "enabled", featureState) + logger.V(1).Info("Feature gate updated state", "feature", feature, "enabled", featureState) continue } - klog.V(1).InfoS("Feature gate default state", "feature", feature, "enabled", featureSpec.Default) + logger.V(1).Info("Feature gate default state", "feature", feature, "enabled", featureSpec.Default) } }) return f.enabledViaEnvVar.Load().(map[Feature]bool) diff --git a/staging/src/k8s.io/client-go/features/envvar_test.go b/staging/src/k8s.io/client-go/features/envvar_test.go index b032d06cf0c1a..e640af4e49ec1 100644 --- a/staging/src/k8s.io/client-go/features/envvar_test.go +++ b/staging/src/k8s.io/client-go/features/envvar_test.go @@ -47,10 +47,11 @@ func TestEnvVarFeatureGates(t *testing.T) { } scenarios := []struct { - name string - features map[Feature]FeatureSpec - envVariables map[string]string - setMethodFeatures map[Feature]bool + name string + features map[Feature]FeatureSpec + envVariables map[string]string + setMethodFeatures map[Feature]bool + setForTestingFeatures map[Feature]bool expectedFeaturesState map[Feature]bool expectedInternalEnabledViaEnvVarFeatureState map[Feature]bool @@ -134,6 +135,19 @@ func TestEnvVarFeatureGates(t *testing.T) { expectedInternalEnabledViaEnvVarFeatureState: map[Feature]bool{"TestAlpha": true}, expectedInternalEnabledViaSetMethodFeatureState: map[Feature]bool{"TestAlpha": false}, }, + { + name: "setting a feature via the SetForTesting method works even when it is locked", + features: map[Feature]FeatureSpec{ + "TestAlpha": { + Default: true, + LockToDefault: true, + PreRelease: "Alpha", + }, + }, + setForTestingFeatures: map[Feature]bool{"TestAlpha": false}, + expectedFeaturesState: map[Feature]bool{"TestAlpha": false}, + expectedInternalEnabledViaSetMethodFeatureState: map[Feature]bool{"TestAlpha": false}, + }, } for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { @@ -146,6 +160,10 @@ func TestEnvVarFeatureGates(t *testing.T) { err := target.Set(k, v) require.NoError(t, err) } + for k, v := range scenario.setForTestingFeatures { + err := target.SetForTesting(k, v) + require.NoError(t, err) + } for expectedFeature, expectedValue := range scenario.expectedFeaturesState { actualValue := target.Enabled(expectedFeature) require.Equal(t, expectedValue, actualValue, "expected feature=%v, to be=%v, not=%v", expectedFeature, expectedValue, actualValue) diff --git a/staging/src/k8s.io/client-go/features/features.go b/staging/src/k8s.io/client-go/features/features.go index cabb7468d6137..f21511dca23f5 100644 --- a/staging/src/k8s.io/client-go/features/features.go +++ b/staging/src/k8s.io/client-go/features/features.go @@ -17,7 +17,7 @@ limitations under the License. package features import ( - "errors" + "context" "sync/atomic" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -127,7 +127,9 @@ func AddVersionedFeaturesToExistingFeatureGates(registry VersionedRegistry) erro // clientgofeaturegate.ReplaceFeatureGates(utilfeature.DefaultMutableFeatureGate) func ReplaceFeatureGates(newFeatureGates Gates) { if replaceFeatureGatesWithWarningIndicator(newFeatureGates) { - utilruntime.HandleError(errors.New("the default feature gates implementation has already been used and now it's being overwritten. This might lead to unexpected behaviour. Check your initialization order")) + // TODO (?): A new API would be needed where callers pass in a context or logger. + // Probably not worth it. + utilruntime.HandleErrorWithContext(context.TODO(), nil, "The default feature gates implementation has already been used and now it's being overwritten. This might lead to unexpected behaviour. Check your initialization order.") } } diff --git a/staging/src/k8s.io/client-go/features/known_features.go b/staging/src/k8s.io/client-go/features/known_features.go index 4b022c4b4f9c1..250188dbc2dfd 100644 --- a/staging/src/k8s.io/client-go/features/known_features.go +++ b/staging/src/k8s.io/client-go/features/known_features.go @@ -31,6 +31,20 @@ import ( // of code conflicts because changes are more likely to be scattered // across the file. const ( + // owner: @michaelasp + // beta: v1.36 + // + // Allow the client to process events atomically rather than a stream of + // events for items popped off the FIFO. + AtomicFIFO Feature = "AtomicFIFO" + + // owner: @yt2985 + // beta: 1.36 + // + // If enabled, allows clients to gracefully handle Certificate Authority (CA) + // rotations without dropping connections or requiring a restart. + ClientsAllowCARotation Feature = "ClientsAllowCARotation" + // owner: @benluddy // kep: https://kep.k8s.io/4222 // alpha: 1.32 @@ -41,6 +55,13 @@ const ( // "application/json" or "application/apply-patch+yaml", respectively. ClientsAllowCBOR Feature = "ClientsAllowCBOR" + // owner: @enj + // beta: v1.36 + // + // If enabled, the client-go TLS transport cache uses weak pointers to allow + // garbage collection of unused transports, preventing unbounded cache growth. + ClientsAllowTLSCacheGC Feature = "ClientsAllowTLSCacheGC" + // owner: @benluddy // kep: https://kep.k8s.io/4222 // alpha: 1.32 @@ -69,6 +90,12 @@ const ( // GA: v1.35 InformerResourceVersion Feature = "InformerResourceVersion" + // owner: @michaelasp + // beta: v1.36 + // + // Allow the FIFO to unlock while processing items to allow other goroutines to add items to the queue. + UnlockWhileProcessingFIFO Feature = "UnlockWhileProcessingFIFO" + // owner: @p0lyn0mial // beta: v1.30 // @@ -82,14 +109,24 @@ const ( // After registering with the binary, the features are, by default, controllable using environment variables. // For more details, please see envVarFeatureGates implementation. var defaultVersionedKubernetesFeatureGates = map[Feature]VersionedSpecs{ + AtomicFIFO: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: Beta}, + }, + ClientsAllowCARotation: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: Beta}, + }, ClientsAllowCBOR: { {Version: version.MustParse("1.32"), Default: false, PreRelease: Alpha}, }, + ClientsAllowTLSCacheGC: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: Beta}, + }, ClientsPreferCBOR: { {Version: version.MustParse("1.32"), Default: false, PreRelease: Alpha}, }, InOrderInformers: { {Version: version.MustParse("1.33"), Default: true, PreRelease: Beta}, + {Version: version.MustParse("1.36"), Default: true, PreRelease: GA, LockToDefault: true}, }, InOrderInformersBatchProcess: { {Version: version.MustParse("1.35"), Default: true, PreRelease: Beta}, @@ -98,6 +135,9 @@ var defaultVersionedKubernetesFeatureGates = map[Feature]VersionedSpecs{ {Version: version.MustParse("1.30"), Default: false, PreRelease: Alpha}, {Version: version.MustParse("1.35"), Default: true, PreRelease: GA}, }, + UnlockWhileProcessingFIFO: { + {Version: version.MustParse("1.36"), Default: true, PreRelease: Beta}, + }, WatchListClient: { {Version: version.MustParse("1.30"), Default: false, PreRelease: Beta}, {Version: version.MustParse("1.35"), Default: true, PreRelease: Beta}, diff --git a/staging/src/k8s.io/client-go/features/testing/features.go b/staging/src/k8s.io/client-go/features/testing/features.go index b5555ff800b9b..b01d63d6aab3e 100644 --- a/staging/src/k8s.io/client-go/features/testing/features.go +++ b/staging/src/k8s.io/client-go/features/testing/features.go @@ -40,6 +40,12 @@ type featureGatesSetter interface { Set(clientfeatures.Feature, bool) error } +type featureGatesSetterForTesting interface { + featureGatesSetter + + SetForTesting(clientfeatures.Feature, bool) error +} + // SetFeatureDuringTest sets the specified feature to the specified value for the duration of the test. // // Example use: @@ -68,9 +74,17 @@ func setFeatureDuringTestInternal(tb testing.TB, feature clientfeatures.Feature, } } - if err := featureGates.Set(feature, featureValue); err != nil { + setFeature := func(feature clientfeatures.Feature, featureValue bool) error { + if testSetter, ok := featureGates.(featureGatesSetterForTesting); ok { + return testSetter.SetForTesting(feature, featureValue) + } + return featureGates.Set(feature, featureValue) + } + + if err := setFeature(feature, featureValue); err != nil { return err } + overriddenFeatures[feature] = tb.Name() tb.Cleanup(func() { @@ -78,7 +92,7 @@ func setFeatureDuringTestInternal(tb testing.TB, feature clientfeatures.Feature, defer overriddenFeaturesLock.Unlock() delete(overriddenFeatures, feature) // if default is not set - if err := featureGates.Set(feature, originalFeatureValue); err != nil { + if err := setFeature(feature, originalFeatureValue); err != nil { tb.Errorf("failed restoring client-go feature: %v to its original value: %v, err: %v", feature, originalFeatureValue, err) } }) diff --git a/staging/src/k8s.io/client-go/go.mod b/staging/src/k8s.io/client-go/go.mod index 88358c0ffe119..6d4a9dade8f34 100644 --- a/staging/src/k8s.io/client-go/go.mod +++ b/staging/src/k8s.io/client-go/go.mod @@ -2,9 +2,9 @@ module k8s.io/client-go -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/go-logr/logr v1.4.3 @@ -12,59 +12,56 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/peterbourgon/diskv v2.0.1+incompatible github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.47.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/term v0.37.0 - golang.org/x/time v0.9.0 - google.golang.org/protobuf v1.36.8 + golang.org/x/net v0.49.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/term v0.39.0 + golang.org/x/time v0.14.0 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/evanphx/json-patch.v4 v4.13.0 k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/klog/v2 v2.140.0 + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a + k8s.io/streaming v0.0.0 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 sigs.k8s.io/randfill v1.0.0 - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 sigs.k8s.io/yaml v1.6.0 ) require ( - github.com/Masterminds/semver/v3 v3.4.0 // 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/fxamacker/cbor/v2 v2.9.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/google/btree v1.1.3 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/client-go/go.sum b/staging/src/k8s.io/client-go/go.sum index 334359afd18fa..e9d51b87c1479 100644 --- a/staging/src/k8s.io/client-go/go.sum +++ b/staging/src/k8s.io/client-go/go.sum @@ -1,17 +1,14 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -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/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -24,8 +21,6 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -34,15 +29,10 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= 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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -56,8 +46,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -66,12 +56,7 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -100,30 +85,26 @@ 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= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +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/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -135,17 +116,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= 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/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/interface.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/interface.go index 08769d3cca4ae..4bfe9c4c6e27c 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/interface.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/interface.go @@ -24,6 +24,10 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // MutatingAdmissionPolicies returns a MutatingAdmissionPolicyInformer. + MutatingAdmissionPolicies() MutatingAdmissionPolicyInformer + // MutatingAdmissionPolicyBindings returns a MutatingAdmissionPolicyBindingInformer. + MutatingAdmissionPolicyBindings() MutatingAdmissionPolicyBindingInformer // MutatingWebhookConfigurations returns a MutatingWebhookConfigurationInformer. MutatingWebhookConfigurations() MutatingWebhookConfigurationInformer // ValidatingAdmissionPolicies returns a ValidatingAdmissionPolicyInformer. @@ -45,6 +49,16 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// MutatingAdmissionPolicies returns a MutatingAdmissionPolicyInformer. +func (v *version) MutatingAdmissionPolicies() MutatingAdmissionPolicyInformer { + return &mutatingAdmissionPolicyInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +// MutatingAdmissionPolicyBindings returns a MutatingAdmissionPolicyBindingInformer. +func (v *version) MutatingAdmissionPolicyBindings() MutatingAdmissionPolicyBindingInformer { + return &mutatingAdmissionPolicyBindingInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // MutatingWebhookConfigurations returns a MutatingWebhookConfigurationInformer. func (v *version) MutatingWebhookConfigurations() MutatingWebhookConfigurationInformer { return &mutatingWebhookConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingadmissionpolicy.go new file mode 100644 index 0000000000000..65df1905a5d4e --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingadmissionpolicy.go @@ -0,0 +1,115 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + time "time" + + apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + kubernetes "k8s.io/client-go/kubernetes" + admissionregistrationv1 "k8s.io/client-go/listers/admissionregistration/v1" + cache "k8s.io/client-go/tools/cache" +) + +// MutatingAdmissionPolicyInformer provides access to a shared informer and lister for +// MutatingAdmissionPolicies. +type MutatingAdmissionPolicyInformer interface { + Informer() cache.SharedIndexInformer + Lister() admissionregistrationv1.MutatingAdmissionPolicyLister +} + +type mutatingAdmissionPolicyInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewMutatingAdmissionPolicyInformer constructs a new informer for MutatingAdmissionPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredMutatingAdmissionPolicyInformer constructs a new informer for MutatingAdmissionPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredMutatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingAdmissionPolicyInformerWithOptions constructs a new informer for MutatingAdmissionPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "mutatingadmissionpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicies().List(context.Background(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicies().Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicies().List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicies().Watch(ctx, opts) + }, + }, client), + &apiadmissionregistrationv1.MutatingAdmissionPolicy{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *mutatingAdmissionPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *mutatingAdmissionPolicyInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiadmissionregistrationv1.MutatingAdmissionPolicy{}, f.defaultInformer) +} + +func (f *mutatingAdmissionPolicyInformer) Lister() admissionregistrationv1.MutatingAdmissionPolicyLister { + return admissionregistrationv1.NewMutatingAdmissionPolicyLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingadmissionpolicybinding.go new file mode 100644 index 0000000000000..d06ca0b4e1e37 --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingadmissionpolicybinding.go @@ -0,0 +1,115 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + time "time" + + apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + kubernetes "k8s.io/client-go/kubernetes" + admissionregistrationv1 "k8s.io/client-go/listers/admissionregistration/v1" + cache "k8s.io/client-go/tools/cache" +) + +// MutatingAdmissionPolicyBindingInformer provides access to a shared informer and lister for +// MutatingAdmissionPolicyBindings. +type MutatingAdmissionPolicyBindingInformer interface { + Informer() cache.SharedIndexInformer + Lister() admissionregistrationv1.MutatingAdmissionPolicyBindingLister +} + +type mutatingAdmissionPolicyBindingInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewMutatingAdmissionPolicyBindingInformer constructs a new informer for MutatingAdmissionPolicyBinding type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredMutatingAdmissionPolicyBindingInformer constructs a new informer for MutatingAdmissionPolicyBinding type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredMutatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingAdmissionPolicyBindingInformerWithOptions constructs a new informer for MutatingAdmissionPolicyBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "mutatingadmissionpolicybindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicyBindings().List(context.Background(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicyBindings().Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicyBindings().List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.AdmissionregistrationV1().MutatingAdmissionPolicyBindings().Watch(ctx, opts) + }, + }, client), + &apiadmissionregistrationv1.MutatingAdmissionPolicyBinding{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *mutatingAdmissionPolicyBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *mutatingAdmissionPolicyBindingInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiadmissionregistrationv1.MutatingAdmissionPolicyBinding{}, f.defaultInformer) +} + +func (f *mutatingAdmissionPolicyBindingInformer) Lister() admissionregistrationv1.MutatingAdmissionPolicyBindingLister { + return admissionregistrationv1.NewMutatingAdmissionPolicyBindingLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingwebhookconfiguration.go index 77439c4bc70c5..d7c48e78a665c 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/mutatingwebhookconfiguration.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type mutatingWebhookConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewMutatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewMutatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredMutatingWebhookConfigurationInformer constructs a new informer for MutatingWebhookConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredMutatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewMutatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingWebhookConfigurationInformerWithOptions constructs a new informer for MutatingWebhookConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingWebhookConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "mutatingwebhookconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.Background(), options) + return client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().MutatingWebhookConfigurations().Watch(context.Background(), options) + return client.AdmissionregistrationV1().MutatingWebhookConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, options) + return client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().MutatingWebhookConfigurations().Watch(ctx, options) + return client.AdmissionregistrationV1().MutatingWebhookConfigurations().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1.MutatingWebhookConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *mutatingWebhookConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewMutatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *mutatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicy.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicy.go index 89b86c09e1550..bdc5c0b28ae6c 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicy.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingAdmissionPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyInformer(client, resyncPeriod, indexers, nil) + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingAdmissionPolicyInformer constructs a new informer for ValidatingAdmissionPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingAdmissionPolicyInformerWithOptions constructs a new informer for ValidatingAdmissionPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingAdmissionPolicyInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "validatingadmissionpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().List(context.Background(), options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Watch(context.Background(), options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().List(ctx, options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Watch(ctx, options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1.ValidatingAdmissionPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingAdmissionPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingAdmissionPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicybinding.go index b318ed445bc67..1ecf9703a9d3b 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingadmissionpolicybinding.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingAdmissionPolicyBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyBindingInformer(client, resyncPeriod, indexers, nil) + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingAdmissionPolicyBindingInformer constructs a new informer for ValidatingAdmissionPolicyBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingAdmissionPolicyBindingInformerWithOptions constructs a new informer for ValidatingAdmissionPolicyBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingAdmissionPolicyBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "validatingadmissionpolicybindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().List(context.Background(), options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Watch(context.Background(), options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().List(ctx, options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Watch(ctx, options) + return client.AdmissionregistrationV1().ValidatingAdmissionPolicyBindings().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1.ValidatingAdmissionPolicyBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingAdmissionPolicyBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingAdmissionPolicyBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingwebhookconfiguration.go index d1d38407951c2..07653c35ceb81 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1/validatingwebhookconfiguration.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingWebhookConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewValidatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingWebhookConfigurationInformer constructs a new informer for ValidatingWebhookConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingWebhookConfigurationInformerWithOptions constructs a new informer for ValidatingWebhookConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingWebhookConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "validatingwebhookconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.Background(), options) + return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Watch(context.Background(), options) + return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, options) + return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Watch(ctx, options) + return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1.ValidatingWebhookConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingWebhookConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicy.go index 2a0cb2cfde590..3441d6df44108 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicy.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type mutatingAdmissionPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewMutatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyInformer(client, resyncPeriod, indexers, nil) + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredMutatingAdmissionPolicyInformer constructs a new informer for MutatingAdmissionPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredMutatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingAdmissionPolicyInformerWithOptions constructs a new informer for MutatingAdmissionPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Resource: "mutatingadmissionpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().List(context.Background(), options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().Watch(context.Background(), options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().List(ctx, options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().Watch(ctx, options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicies().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1alpha1.MutatingAdmissionPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *mutatingAdmissionPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *mutatingAdmissionPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go index 23ef580fefe7b..568c911159aa8 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/mutatingadmissionpolicybinding.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type mutatingAdmissionPolicyBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewMutatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyBindingInformer(client, resyncPeriod, indexers, nil) + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredMutatingAdmissionPolicyBindingInformer constructs a new informer for MutatingAdmissionPolicyBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredMutatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingAdmissionPolicyBindingInformerWithOptions constructs a new informer for MutatingAdmissionPolicyBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Resource: "mutatingadmissionpolicybindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().List(context.Background(), options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().Watch(context.Background(), options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().List(ctx, options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().Watch(ctx, options) + return client.AdmissionregistrationV1alpha1().MutatingAdmissionPolicyBindings().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1alpha1.MutatingAdmissionPolicyBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *mutatingAdmissionPolicyBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *mutatingAdmissionPolicyBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicy.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicy.go index 9a896b70dd0be..c86ca6da62c17 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicy.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingAdmissionPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyInformer(client, resyncPeriod, indexers, nil) + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingAdmissionPolicyInformer constructs a new informer for ValidatingAdmissionPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingAdmissionPolicyInformerWithOptions constructs a new informer for ValidatingAdmissionPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingAdmissionPolicyInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Resource: "validatingadmissionpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().List(context.Background(), options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Watch(context.Background(), options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().List(ctx, options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Watch(ctx, options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1alpha1.ValidatingAdmissionPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingAdmissionPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingAdmissionPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go index 630471e9e22aa..224049a2540a5 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1alpha1/validatingadmissionpolicybinding.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingAdmissionPolicyBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyBindingInformer(client, resyncPeriod, indexers, nil) + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingAdmissionPolicyBindingInformer constructs a new informer for ValidatingAdmissionPolicyBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingAdmissionPolicyBindingInformerWithOptions constructs a new informer for ValidatingAdmissionPolicyBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingAdmissionPolicyBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Resource: "validatingadmissionpolicybindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().List(context.Background(), options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Watch(context.Background(), options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().List(ctx, options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Watch(ctx, options) + return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingAdmissionPolicyBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingAdmissionPolicyBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicy.go index bfd1e4245df4e..edfe94417bc83 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicy.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type mutatingAdmissionPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewMutatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyInformer(client, resyncPeriod, indexers, nil) + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredMutatingAdmissionPolicyInformer constructs a new informer for MutatingAdmissionPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredMutatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingAdmissionPolicyInformerWithOptions constructs a new informer for MutatingAdmissionPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "mutatingadmissionpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().List(context.Background(), options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().Watch(context.Background(), options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().List(ctx, options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().Watch(ctx, options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicies().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1beta1.MutatingAdmissionPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *mutatingAdmissionPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewMutatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *mutatingAdmissionPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go index 416590c0b7de7..bb833f4447d34 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingadmissionpolicybinding.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type mutatingAdmissionPolicyBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewMutatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyBindingInformer(client, resyncPeriod, indexers, nil) + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredMutatingAdmissionPolicyBindingInformer constructs a new informer for MutatingAdmissionPolicyBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredMutatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingAdmissionPolicyBindingInformerWithOptions constructs a new informer for MutatingAdmissionPolicyBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingAdmissionPolicyBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "mutatingadmissionpolicybindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().List(context.Background(), options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().Watch(context.Background(), options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().List(ctx, options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().Watch(ctx, options) + return client.AdmissionregistrationV1beta1().MutatingAdmissionPolicyBindings().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1beta1.MutatingAdmissionPolicyBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *mutatingAdmissionPolicyBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredMutatingAdmissionPolicyBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewMutatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *mutatingAdmissionPolicyBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingwebhookconfiguration.go index 2f086798e290e..bb077a1746682 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/mutatingwebhookconfiguration.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type mutatingWebhookConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewMutatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewMutatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredMutatingWebhookConfigurationInformer constructs a new informer for MutatingWebhookConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredMutatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewMutatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewMutatingWebhookConfigurationInformerWithOptions constructs a new informer for MutatingWebhookConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewMutatingWebhookConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "mutatingwebhookconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().List(context.Background(), options) + return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Watch(context.Background(), options) + return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().List(ctx, options) + return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Watch(ctx, options) + return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1beta1.MutatingWebhookConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *mutatingWebhookConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewMutatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *mutatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicy.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicy.go index 18960ff9f8003..a88a254d6f9af 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicy.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicy.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingAdmissionPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyInformer(client, resyncPeriod, indexers, nil) + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingAdmissionPolicyInformer constructs a new informer for ValidatingAdmissionPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingAdmissionPolicyInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingAdmissionPolicyInformerWithOptions constructs a new informer for ValidatingAdmissionPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingAdmissionPolicyInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "validatingadmissionpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().List(context.Background(), options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Watch(context.Background(), options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().List(ctx, options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Watch(ctx, options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1beta1.ValidatingAdmissionPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingAdmissionPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingAdmissionPolicyInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingAdmissionPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicybinding.go index ab3febb15a6e8..d1df5a3f2f761 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicybinding.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingadmissionpolicybinding.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingAdmissionPolicyBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyBindingInformer(client, resyncPeriod, indexers, nil) + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingAdmissionPolicyBindingInformer constructs a new informer for ValidatingAdmissionPolicyBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingAdmissionPolicyBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingAdmissionPolicyBindingInformerWithOptions constructs a new informer for ValidatingAdmissionPolicyBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingAdmissionPolicyBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "validatingadmissionpolicybindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().List(context.Background(), options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Watch(context.Background(), options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().List(ctx, options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Watch(ctx, options) + return client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1beta1.ValidatingAdmissionPolicyBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingAdmissionPolicyBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingAdmissionPolicyBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingAdmissionPolicyBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingAdmissionPolicyBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingwebhookconfiguration.go b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingwebhookconfiguration.go index ae88860543b7e..41852cb03d2ec 100644 --- a/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingwebhookconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/admissionregistration/v1beta1/validatingwebhookconfiguration.go @@ -25,6 +25,7 @@ import ( apiadmissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type validatingWebhookConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewValidatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewValidatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredValidatingWebhookConfigurationInformer constructs a new informer for ValidatingWebhookConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredValidatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewValidatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewValidatingWebhookConfigurationInformerWithOptions constructs a new informer for ValidatingWebhookConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewValidatingWebhookConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "validatingwebhookconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().List(context.Background(), options) + return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Watch(context.Background(), options) + return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().List(ctx, options) + return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Watch(ctx, options) + return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Watch(ctx, opts) }, }, client), &apiadmissionregistrationv1beta1.ValidatingWebhookConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *validatingWebhookConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewValidatingWebhookConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *validatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apiserverinternal/v1alpha1/storageversion.go b/staging/src/k8s.io/client-go/informers/apiserverinternal/v1alpha1/storageversion.go index 1f1cfd503f69a..6d164a317cad8 100644 --- a/staging/src/k8s.io/client-go/informers/apiserverinternal/v1alpha1/storageversion.go +++ b/staging/src/k8s.io/client-go/informers/apiserverinternal/v1alpha1/storageversion.go @@ -25,6 +25,7 @@ import ( apiapiserverinternalv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type storageVersionInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStorageVersionInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStorageVersionInformer(client, resyncPeriod, indexers, nil) + return NewStorageVersionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStorageVersionInformer constructs a new informer for StorageVersion type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStorageVersionInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStorageVersionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStorageVersionInformerWithOptions constructs a new informer for StorageVersion type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStorageVersionInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "internal.apiserver.k8s.io", Version: "v1alpha1", Resource: "storageversions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.InternalV1alpha1().StorageVersions().List(context.Background(), options) + return client.InternalV1alpha1().StorageVersions().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.InternalV1alpha1().StorageVersions().Watch(context.Background(), options) + return client.InternalV1alpha1().StorageVersions().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.InternalV1alpha1().StorageVersions().List(ctx, options) + return client.InternalV1alpha1().StorageVersions().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.InternalV1alpha1().StorageVersions().Watch(ctx, options) + return client.InternalV1alpha1().StorageVersions().Watch(ctx, opts) }, }, client), &apiapiserverinternalv1alpha1.StorageVersion{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *storageVersionInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStorageVersionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStorageVersionInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *storageVersionInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1/controllerrevision.go b/staging/src/k8s.io/client-go/informers/apps/v1/controllerrevision.go index 1d306123ca2a2..f58da53f188ea 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1/controllerrevision.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1/controllerrevision.go @@ -25,6 +25,7 @@ import ( apiappsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type controllerRevisionInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredControllerRevisionInformer(client, namespace, resyncPeriod, indexers, nil) + return NewControllerRevisionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredControllerRevisionInformer constructs a new informer for ControllerRevision type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewControllerRevisionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewControllerRevisionInformerWithOptions constructs a new informer for ControllerRevision type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewControllerRevisionInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "controllerrevisions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ControllerRevisions(namespace).List(context.Background(), options) + return client.AppsV1().ControllerRevisions(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ControllerRevisions(namespace).Watch(context.Background(), options) + return client.AppsV1().ControllerRevisions(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ControllerRevisions(namespace).List(ctx, options) + return client.AppsV1().ControllerRevisions(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ControllerRevisions(namespace).Watch(ctx, options) + return client.AppsV1().ControllerRevisions(namespace).Watch(ctx, opts) }, }, client), &apiappsv1.ControllerRevision{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *controllerRevisionInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredControllerRevisionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewControllerRevisionInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1/daemonset.go b/staging/src/k8s.io/client-go/informers/apps/v1/daemonset.go index 658d6b9ae0dcb..dbdc40124256e 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1/daemonset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1/daemonset.go @@ -25,6 +25,7 @@ import ( apiappsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type daemonSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDaemonSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDaemonSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDaemonSetInformer constructs a new informer for DaemonSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDaemonSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDaemonSetInformerWithOptions constructs a new informer for DaemonSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDaemonSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().DaemonSets(namespace).List(context.Background(), options) + return client.AppsV1().DaemonSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().DaemonSets(namespace).Watch(context.Background(), options) + return client.AppsV1().DaemonSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().DaemonSets(namespace).List(ctx, options) + return client.AppsV1().DaemonSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().DaemonSets(namespace).Watch(ctx, options) + return client.AppsV1().DaemonSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1.DaemonSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *daemonSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDaemonSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDaemonSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *daemonSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1/deployment.go b/staging/src/k8s.io/client-go/informers/apps/v1/deployment.go index 4a8a29c246445..dbf4df6a888f7 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1/deployment.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1/deployment.go @@ -25,6 +25,7 @@ import ( apiappsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type deploymentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeploymentInformer constructs a new informer for Deployment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeploymentInformerWithOptions constructs a new informer for Deployment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeploymentInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().Deployments(namespace).List(context.Background(), options) + return client.AppsV1().Deployments(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().Deployments(namespace).Watch(context.Background(), options) + return client.AppsV1().Deployments(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().Deployments(namespace).List(ctx, options) + return client.AppsV1().Deployments(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().Deployments(namespace).Watch(ctx, options) + return client.AppsV1().Deployments(namespace).Watch(ctx, opts) }, }, client), &apiappsv1.Deployment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeploymentInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deploymentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1/replicaset.go b/staging/src/k8s.io/client-go/informers/apps/v1/replicaset.go index d37292cb6def3..1115e5adccc77 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1/replicaset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1/replicaset.go @@ -25,6 +25,7 @@ import ( apiappsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type replicaSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredReplicaSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewReplicaSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredReplicaSetInformer constructs a new informer for ReplicaSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewReplicaSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewReplicaSetInformerWithOptions constructs a new informer for ReplicaSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewReplicaSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ReplicaSets(namespace).List(context.Background(), options) + return client.AppsV1().ReplicaSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ReplicaSets(namespace).Watch(context.Background(), options) + return client.AppsV1().ReplicaSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ReplicaSets(namespace).List(ctx, options) + return client.AppsV1().ReplicaSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().ReplicaSets(namespace).Watch(ctx, options) + return client.AppsV1().ReplicaSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1.ReplicaSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *replicaSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredReplicaSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewReplicaSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *replicaSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1/statefulset.go b/staging/src/k8s.io/client-go/informers/apps/v1/statefulset.go index 70a5f9c0ba528..998251edeee61 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1/statefulset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1/statefulset.go @@ -25,6 +25,7 @@ import ( apiappsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type statefulSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStatefulSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewStatefulSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStatefulSetInformer constructs a new informer for StatefulSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStatefulSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStatefulSetInformerWithOptions constructs a new informer for StatefulSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStatefulSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().StatefulSets(namespace).List(context.Background(), options) + return client.AppsV1().StatefulSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().StatefulSets(namespace).Watch(context.Background(), options) + return client.AppsV1().StatefulSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().StatefulSets(namespace).List(ctx, options) + return client.AppsV1().StatefulSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1().StatefulSets(namespace).Watch(ctx, options) + return client.AppsV1().StatefulSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1.StatefulSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *statefulSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStatefulSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStatefulSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *statefulSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta1/controllerrevision.go b/staging/src/k8s.io/client-go/informers/apps/v1beta1/controllerrevision.go index deaaa24c6bcd3..ee3b156e029a7 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta1/controllerrevision.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta1/controllerrevision.go @@ -25,6 +25,7 @@ import ( apiappsv1beta1 "k8s.io/api/apps/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type controllerRevisionInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredControllerRevisionInformer(client, namespace, resyncPeriod, indexers, nil) + return NewControllerRevisionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredControllerRevisionInformer constructs a new informer for ControllerRevision type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewControllerRevisionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewControllerRevisionInformerWithOptions constructs a new informer for ControllerRevision type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewControllerRevisionInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta1", Resource: "controllerrevisions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().ControllerRevisions(namespace).List(context.Background(), options) + return client.AppsV1beta1().ControllerRevisions(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().ControllerRevisions(namespace).Watch(context.Background(), options) + return client.AppsV1beta1().ControllerRevisions(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().ControllerRevisions(namespace).List(ctx, options) + return client.AppsV1beta1().ControllerRevisions(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().ControllerRevisions(namespace).Watch(ctx, options) + return client.AppsV1beta1().ControllerRevisions(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta1.ControllerRevision{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *controllerRevisionInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredControllerRevisionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewControllerRevisionInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta1/deployment.go b/staging/src/k8s.io/client-go/informers/apps/v1beta1/deployment.go index 4dbad1e983751..699375e3e18e3 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta1/deployment.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta1/deployment.go @@ -25,6 +25,7 @@ import ( apiappsv1beta1 "k8s.io/api/apps/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type deploymentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeploymentInformer constructs a new informer for Deployment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeploymentInformerWithOptions constructs a new informer for Deployment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeploymentInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta1", Resource: "deployments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().Deployments(namespace).List(context.Background(), options) + return client.AppsV1beta1().Deployments(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().Deployments(namespace).Watch(context.Background(), options) + return client.AppsV1beta1().Deployments(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().Deployments(namespace).List(ctx, options) + return client.AppsV1beta1().Deployments(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().Deployments(namespace).Watch(ctx, options) + return client.AppsV1beta1().Deployments(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta1.Deployment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeploymentInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deploymentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta1/statefulset.go b/staging/src/k8s.io/client-go/informers/apps/v1beta1/statefulset.go index 2c5aa846ece57..480e68f9d145b 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta1/statefulset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta1/statefulset.go @@ -25,6 +25,7 @@ import ( apiappsv1beta1 "k8s.io/api/apps/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type statefulSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStatefulSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewStatefulSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStatefulSetInformer constructs a new informer for StatefulSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStatefulSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStatefulSetInformerWithOptions constructs a new informer for StatefulSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStatefulSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta1", Resource: "statefulsets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().StatefulSets(namespace).List(context.Background(), options) + return client.AppsV1beta1().StatefulSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().StatefulSets(namespace).Watch(context.Background(), options) + return client.AppsV1beta1().StatefulSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().StatefulSets(namespace).List(ctx, options) + return client.AppsV1beta1().StatefulSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta1().StatefulSets(namespace).Watch(ctx, options) + return client.AppsV1beta1().StatefulSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta1.StatefulSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *statefulSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStatefulSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStatefulSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *statefulSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta2/controllerrevision.go b/staging/src/k8s.io/client-go/informers/apps/v1beta2/controllerrevision.go index f6cca7d57493c..41e3bf15633ef 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta2/controllerrevision.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta2/controllerrevision.go @@ -25,6 +25,7 @@ import ( apiappsv1beta2 "k8s.io/api/apps/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type controllerRevisionInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredControllerRevisionInformer(client, namespace, resyncPeriod, indexers, nil) + return NewControllerRevisionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredControllerRevisionInformer constructs a new informer for ControllerRevision type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewControllerRevisionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewControllerRevisionInformerWithOptions constructs a new informer for ControllerRevision type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewControllerRevisionInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta2", Resource: "controllerrevisions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ControllerRevisions(namespace).List(context.Background(), options) + return client.AppsV1beta2().ControllerRevisions(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ControllerRevisions(namespace).Watch(context.Background(), options) + return client.AppsV1beta2().ControllerRevisions(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ControllerRevisions(namespace).List(ctx, options) + return client.AppsV1beta2().ControllerRevisions(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ControllerRevisions(namespace).Watch(ctx, options) + return client.AppsV1beta2().ControllerRevisions(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta2.ControllerRevision{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *controllerRevisionInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredControllerRevisionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewControllerRevisionInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta2/daemonset.go b/staging/src/k8s.io/client-go/informers/apps/v1beta2/daemonset.go index aaec4988c2652..5c339a78a0a60 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta2/daemonset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta2/daemonset.go @@ -25,6 +25,7 @@ import ( apiappsv1beta2 "k8s.io/api/apps/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type daemonSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDaemonSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDaemonSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDaemonSetInformer constructs a new informer for DaemonSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDaemonSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDaemonSetInformerWithOptions constructs a new informer for DaemonSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDaemonSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta2", Resource: "daemonsets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().DaemonSets(namespace).List(context.Background(), options) + return client.AppsV1beta2().DaemonSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().DaemonSets(namespace).Watch(context.Background(), options) + return client.AppsV1beta2().DaemonSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().DaemonSets(namespace).List(ctx, options) + return client.AppsV1beta2().DaemonSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().DaemonSets(namespace).Watch(ctx, options) + return client.AppsV1beta2().DaemonSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta2.DaemonSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *daemonSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDaemonSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDaemonSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *daemonSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta2/deployment.go b/staging/src/k8s.io/client-go/informers/apps/v1beta2/deployment.go index 69f6e3fb8ed86..1e0b64cb288f6 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta2/deployment.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta2/deployment.go @@ -25,6 +25,7 @@ import ( apiappsv1beta2 "k8s.io/api/apps/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type deploymentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeploymentInformer constructs a new informer for Deployment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeploymentInformerWithOptions constructs a new informer for Deployment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeploymentInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta2", Resource: "deployments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().Deployments(namespace).List(context.Background(), options) + return client.AppsV1beta2().Deployments(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().Deployments(namespace).Watch(context.Background(), options) + return client.AppsV1beta2().Deployments(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().Deployments(namespace).List(ctx, options) + return client.AppsV1beta2().Deployments(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().Deployments(namespace).Watch(ctx, options) + return client.AppsV1beta2().Deployments(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta2.Deployment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeploymentInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deploymentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta2/replicaset.go b/staging/src/k8s.io/client-go/informers/apps/v1beta2/replicaset.go index a65d451616e1a..425ad55333d81 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta2/replicaset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta2/replicaset.go @@ -25,6 +25,7 @@ import ( apiappsv1beta2 "k8s.io/api/apps/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type replicaSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredReplicaSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewReplicaSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredReplicaSetInformer constructs a new informer for ReplicaSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewReplicaSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewReplicaSetInformerWithOptions constructs a new informer for ReplicaSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewReplicaSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta2", Resource: "replicasets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ReplicaSets(namespace).List(context.Background(), options) + return client.AppsV1beta2().ReplicaSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ReplicaSets(namespace).Watch(context.Background(), options) + return client.AppsV1beta2().ReplicaSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ReplicaSets(namespace).List(ctx, options) + return client.AppsV1beta2().ReplicaSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().ReplicaSets(namespace).Watch(ctx, options) + return client.AppsV1beta2().ReplicaSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta2.ReplicaSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *replicaSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredReplicaSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewReplicaSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *replicaSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/apps/v1beta2/statefulset.go b/staging/src/k8s.io/client-go/informers/apps/v1beta2/statefulset.go index 5e90c39c7b169..0fb22661cf25c 100644 --- a/staging/src/k8s.io/client-go/informers/apps/v1beta2/statefulset.go +++ b/staging/src/k8s.io/client-go/informers/apps/v1beta2/statefulset.go @@ -25,6 +25,7 @@ import ( apiappsv1beta2 "k8s.io/api/apps/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type statefulSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStatefulSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewStatefulSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStatefulSetInformer constructs a new informer for StatefulSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStatefulSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStatefulSetInformerWithOptions constructs a new informer for StatefulSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStatefulSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "apps", Version: "v1beta2", Resource: "statefulsets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().StatefulSets(namespace).List(context.Background(), options) + return client.AppsV1beta2().StatefulSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().StatefulSets(namespace).Watch(context.Background(), options) + return client.AppsV1beta2().StatefulSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().StatefulSets(namespace).List(ctx, options) + return client.AppsV1beta2().StatefulSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AppsV1beta2().StatefulSets(namespace).Watch(ctx, options) + return client.AppsV1beta2().StatefulSets(namespace).Watch(ctx, opts) }, }, client), &apiappsv1beta2.StatefulSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *statefulSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStatefulSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStatefulSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *statefulSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/interface.go b/staging/src/k8s.io/client-go/informers/autoscaling/interface.go index 2b3b2d0e50e49..439dd24beb6b8 100644 --- a/staging/src/k8s.io/client-go/informers/autoscaling/interface.go +++ b/staging/src/k8s.io/client-go/informers/autoscaling/interface.go @@ -21,8 +21,6 @@ package autoscaling import ( v1 "k8s.io/client-go/informers/autoscaling/v1" v2 "k8s.io/client-go/informers/autoscaling/v2" - v2beta1 "k8s.io/client-go/informers/autoscaling/v2beta1" - v2beta2 "k8s.io/client-go/informers/autoscaling/v2beta2" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" ) @@ -32,10 +30,6 @@ type Interface interface { V1() v1.Interface // V2 provides access to shared informers for resources in V2. V2() v2.Interface - // V2beta1 provides access to shared informers for resources in V2beta1. - V2beta1() v2beta1.Interface - // V2beta2 provides access to shared informers for resources in V2beta2. - V2beta2() v2beta2.Interface } type group struct { @@ -58,13 +52,3 @@ func (g *group) V1() v1.Interface { func (g *group) V2() v2.Interface { return v2.New(g.factory, g.namespace, g.tweakListOptions) } - -// V2beta1 returns a new v2beta1.Interface. -func (g *group) V2beta1() v2beta1.Interface { - return v2beta1.New(g.factory, g.namespace, g.tweakListOptions) -} - -// V2beta2 returns a new v2beta2.Interface. -func (g *group) V2beta2() v2beta2.Interface { - return v2beta2.New(g.factory, g.namespace, g.tweakListOptions) -} diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/v1/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/informers/autoscaling/v1/horizontalpodautoscaler.go index 98aacb1e3c35f..e49b03524292a 100644 --- a/staging/src/k8s.io/client-go/informers/autoscaling/v1/horizontalpodautoscaler.go +++ b/staging/src/k8s.io/client-go/informers/autoscaling/v1/horizontalpodautoscaler.go @@ -25,6 +25,7 @@ import ( apiautoscalingv1 "k8s.io/api/autoscaling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type horizontalPodAutoscalerInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil) + return NewHorizontalPodAutoscalerInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewHorizontalPodAutoscalerInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewHorizontalPodAutoscalerInformerWithOptions constructs a new informer for HorizontalPodAutoscaler type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewHorizontalPodAutoscalerInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscalers"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(context.Background(), options) + return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(context.Background(), options) + return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(ctx, options) + return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(ctx, options) + return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(ctx, opts) }, }, client), &apiautoscalingv1.HorizontalPodAutoscaler{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewHorizontalPodAutoscalerInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/v2/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/informers/autoscaling/v2/horizontalpodautoscaler.go index ce86ea8778485..3605718d081cc 100644 --- a/staging/src/k8s.io/client-go/informers/autoscaling/v2/horizontalpodautoscaler.go +++ b/staging/src/k8s.io/client-go/informers/autoscaling/v2/horizontalpodautoscaler.go @@ -25,6 +25,7 @@ import ( apiautoscalingv2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type horizontalPodAutoscalerInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil) + return NewHorizontalPodAutoscalerInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewHorizontalPodAutoscalerInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewHorizontalPodAutoscalerInformerWithOptions constructs a new informer for HorizontalPodAutoscaler type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewHorizontalPodAutoscalerInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "autoscaling", Version: "v2", Resource: "horizontalpodautoscalers"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(context.Background(), options) + return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).Watch(context.Background(), options) + return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(ctx, options) + return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).Watch(ctx, options) + return client.AutoscalingV2().HorizontalPodAutoscalers(namespace).Watch(ctx, opts) }, }, client), &apiautoscalingv2.HorizontalPodAutoscaler{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewHorizontalPodAutoscalerInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta1/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/informers/autoscaling/v2beta1/horizontalpodautoscaler.go deleted file mode 100644 index 71368420063b2..0000000000000 --- a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta1/horizontalpodautoscaler.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v2beta1 - -import ( - context "context" - time "time" - - apiautoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" - kubernetes "k8s.io/client-go/kubernetes" - autoscalingv2beta1 "k8s.io/client-go/listers/autoscaling/v2beta1" - cache "k8s.io/client-go/tools/cache" -) - -// HorizontalPodAutoscalerInformer provides access to a shared informer and lister for -// HorizontalPodAutoscalers. -type HorizontalPodAutoscalerInformer interface { - Informer() cache.SharedIndexInformer - Lister() autoscalingv2beta1.HorizontalPodAutoscalerLister -} - -type horizontalPodAutoscalerInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta1().HorizontalPodAutoscalers(namespace).List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta1().HorizontalPodAutoscalers(namespace).Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta1().HorizontalPodAutoscalers(namespace).List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta1().HorizontalPodAutoscalers(namespace).Watch(ctx, options) - }, - }, client), - &apiautoscalingv2beta1.HorizontalPodAutoscaler{}, - resyncPeriod, - indexers, - ) -} - -func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apiautoscalingv2beta1.HorizontalPodAutoscaler{}, f.defaultInformer) -} - -func (f *horizontalPodAutoscalerInformer) Lister() autoscalingv2beta1.HorizontalPodAutoscalerLister { - return autoscalingv2beta1.NewHorizontalPodAutoscalerLister(f.Informer().GetIndexer()) -} diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta1/interface.go b/staging/src/k8s.io/client-go/informers/autoscaling/v2beta1/interface.go deleted file mode 100644 index ff5d44b09d69e..0000000000000 --- a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta1/interface.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v2beta1 - -import ( - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" -) - -// Interface provides access to all the informers in this group version. -type Interface interface { - // HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer. - HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer -} - -type version struct { - factory internalinterfaces.SharedInformerFactory - namespace string - tweakListOptions internalinterfaces.TweakListOptionsFunc -} - -// New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { - return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} -} - -// HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer. -func (v *version) HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer { - return &horizontalPodAutoscalerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta2/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/informers/autoscaling/v2beta2/horizontalpodautoscaler.go deleted file mode 100644 index 4ff8b103313af..0000000000000 --- a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta2/horizontalpodautoscaler.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v2beta2 - -import ( - context "context" - time "time" - - apiautoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" - kubernetes "k8s.io/client-go/kubernetes" - autoscalingv2beta2 "k8s.io/client-go/listers/autoscaling/v2beta2" - cache "k8s.io/client-go/tools/cache" -) - -// HorizontalPodAutoscalerInformer provides access to a shared informer and lister for -// HorizontalPodAutoscalers. -type HorizontalPodAutoscalerInformer interface { - Informer() cache.SharedIndexInformer - Lister() autoscalingv2beta2.HorizontalPodAutoscalerLister -} - -type horizontalPodAutoscalerInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).Watch(ctx, options) - }, - }, client), - &apiautoscalingv2beta2.HorizontalPodAutoscaler{}, - resyncPeriod, - indexers, - ) -} - -func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredHorizontalPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apiautoscalingv2beta2.HorizontalPodAutoscaler{}, f.defaultInformer) -} - -func (f *horizontalPodAutoscalerInformer) Lister() autoscalingv2beta2.HorizontalPodAutoscalerLister { - return autoscalingv2beta2.NewHorizontalPodAutoscalerLister(f.Informer().GetIndexer()) -} diff --git a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta2/interface.go b/staging/src/k8s.io/client-go/informers/autoscaling/v2beta2/interface.go deleted file mode 100644 index e482c57925f22..0000000000000 --- a/staging/src/k8s.io/client-go/informers/autoscaling/v2beta2/interface.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v2beta2 - -import ( - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" -) - -// Interface provides access to all the informers in this group version. -type Interface interface { - // HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer. - HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer -} - -type version struct { - factory internalinterfaces.SharedInformerFactory - namespace string - tweakListOptions internalinterfaces.TweakListOptionsFunc -} - -// New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { - return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} -} - -// HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer. -func (v *version) HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer { - return &horizontalPodAutoscalerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} diff --git a/staging/src/k8s.io/client-go/informers/batch/v1/cronjob.go b/staging/src/k8s.io/client-go/informers/batch/v1/cronjob.go index 9875a88b5b9db..022710011ba6d 100644 --- a/staging/src/k8s.io/client-go/informers/batch/v1/cronjob.go +++ b/staging/src/k8s.io/client-go/informers/batch/v1/cronjob.go @@ -25,6 +25,7 @@ import ( apibatchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type cronJobInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCronJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCronJobInformer(client, namespace, resyncPeriod, indexers, nil) + return NewCronJobInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCronJobInformer constructs a new informer for CronJob type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCronJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCronJobInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCronJobInformerWithOptions constructs a new informer for CronJob type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCronJobInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "cronjobs"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().CronJobs(namespace).List(context.Background(), options) + return client.BatchV1().CronJobs(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().CronJobs(namespace).Watch(context.Background(), options) + return client.BatchV1().CronJobs(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().CronJobs(namespace).List(ctx, options) + return client.BatchV1().CronJobs(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().CronJobs(namespace).Watch(ctx, options) + return client.BatchV1().CronJobs(namespace).Watch(ctx, opts) }, }, client), &apibatchv1.CronJob{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cronJobInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCronJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCronJobInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cronJobInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/batch/v1/job.go b/staging/src/k8s.io/client-go/informers/batch/v1/job.go index a68178d639a5e..97e88927bfb64 100644 --- a/staging/src/k8s.io/client-go/informers/batch/v1/job.go +++ b/staging/src/k8s.io/client-go/informers/batch/v1/job.go @@ -25,6 +25,7 @@ import ( apibatchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type jobInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredJobInformer(client, namespace, resyncPeriod, indexers, nil) + return NewJobInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredJobInformer constructs a new informer for Job type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewJobInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewJobInformerWithOptions constructs a new informer for Job type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewJobInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().Jobs(namespace).List(context.Background(), options) + return client.BatchV1().Jobs(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().Jobs(namespace).Watch(context.Background(), options) + return client.BatchV1().Jobs(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().Jobs(namespace).List(ctx, options) + return client.BatchV1().Jobs(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1().Jobs(namespace).Watch(ctx, options) + return client.BatchV1().Jobs(namespace).Watch(ctx, opts) }, }, client), &apibatchv1.Job{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *jobInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewJobInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *jobInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/batch/v1beta1/cronjob.go b/staging/src/k8s.io/client-go/informers/batch/v1beta1/cronjob.go index 16644920c7b6d..5d2129323b947 100644 --- a/staging/src/k8s.io/client-go/informers/batch/v1beta1/cronjob.go +++ b/staging/src/k8s.io/client-go/informers/batch/v1beta1/cronjob.go @@ -25,6 +25,7 @@ import ( apibatchv1beta1 "k8s.io/api/batch/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type cronJobInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCronJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCronJobInformer(client, namespace, resyncPeriod, indexers, nil) + return NewCronJobInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCronJobInformer constructs a new informer for CronJob type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCronJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCronJobInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCronJobInformerWithOptions constructs a new informer for CronJob type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCronJobInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "batch", Version: "v1beta1", Resource: "cronjobs"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1beta1().CronJobs(namespace).List(context.Background(), options) + return client.BatchV1beta1().CronJobs(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1beta1().CronJobs(namespace).Watch(context.Background(), options) + return client.BatchV1beta1().CronJobs(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1beta1().CronJobs(namespace).List(ctx, options) + return client.BatchV1beta1().CronJobs(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.BatchV1beta1().CronJobs(namespace).Watch(ctx, options) + return client.BatchV1beta1().CronJobs(namespace).Watch(ctx, opts) }, }, client), &apibatchv1beta1.CronJob{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cronJobInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCronJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCronJobInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cronJobInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/certificates/v1/certificatesigningrequest.go b/staging/src/k8s.io/client-go/informers/certificates/v1/certificatesigningrequest.go index a16eb3f3b0ffc..0e0706893cd94 100644 --- a/staging/src/k8s.io/client-go/informers/certificates/v1/certificatesigningrequest.go +++ b/staging/src/k8s.io/client-go/informers/certificates/v1/certificatesigningrequest.go @@ -25,6 +25,7 @@ import ( apicertificatesv1 "k8s.io/api/certificates/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type certificateSigningRequestInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCertificateSigningRequestInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCertificateSigningRequestInformer(client, resyncPeriod, indexers, nil) + return NewCertificateSigningRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCertificateSigningRequestInformer constructs a new informer for CertificateSigningRequest type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCertificateSigningRequestInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCertificateSigningRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCertificateSigningRequestInformerWithOptions constructs a new informer for CertificateSigningRequest type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCertificateSigningRequestInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1", Resource: "certificatesigningrequests"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1().CertificateSigningRequests().List(context.Background(), options) + return client.CertificatesV1().CertificateSigningRequests().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1().CertificateSigningRequests().Watch(context.Background(), options) + return client.CertificatesV1().CertificateSigningRequests().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1().CertificateSigningRequests().List(ctx, options) + return client.CertificatesV1().CertificateSigningRequests().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1().CertificateSigningRequests().Watch(ctx, options) + return client.CertificatesV1().CertificateSigningRequests().Watch(ctx, opts) }, }, client), &apicertificatesv1.CertificateSigningRequest{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *certificateSigningRequestInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCertificateSigningRequestInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCertificateSigningRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *certificateSigningRequestInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/certificates/v1alpha1/clustertrustbundle.go b/staging/src/k8s.io/client-go/informers/certificates/v1alpha1/clustertrustbundle.go index 02d8800fb3d14..637cfd71daf44 100644 --- a/staging/src/k8s.io/client-go/informers/certificates/v1alpha1/clustertrustbundle.go +++ b/staging/src/k8s.io/client-go/informers/certificates/v1alpha1/clustertrustbundle.go @@ -25,6 +25,7 @@ import ( apicertificatesv1alpha1 "k8s.io/api/certificates/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterTrustBundleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterTrustBundleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterTrustBundleInformer(client, resyncPeriod, indexers, nil) + return NewClusterTrustBundleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterTrustBundleInformer constructs a new informer for ClusterTrustBundle type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterTrustBundleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterTrustBundleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterTrustBundleInformerWithOptions constructs a new informer for ClusterTrustBundle type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterTrustBundleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1alpha1", Resource: "clustertrustbundles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1alpha1().ClusterTrustBundles().List(context.Background(), options) + return client.CertificatesV1alpha1().ClusterTrustBundles().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1alpha1().ClusterTrustBundles().Watch(context.Background(), options) + return client.CertificatesV1alpha1().ClusterTrustBundles().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1alpha1().ClusterTrustBundles().List(ctx, options) + return client.CertificatesV1alpha1().ClusterTrustBundles().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1alpha1().ClusterTrustBundles().Watch(ctx, options) + return client.CertificatesV1alpha1().ClusterTrustBundles().Watch(ctx, opts) }, }, client), &apicertificatesv1alpha1.ClusterTrustBundle{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterTrustBundleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterTrustBundleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterTrustBundleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterTrustBundleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/certificates/v1beta1/certificatesigningrequest.go b/staging/src/k8s.io/client-go/informers/certificates/v1beta1/certificatesigningrequest.go index 57097b7a9b2a7..4d4408d906cd8 100644 --- a/staging/src/k8s.io/client-go/informers/certificates/v1beta1/certificatesigningrequest.go +++ b/staging/src/k8s.io/client-go/informers/certificates/v1beta1/certificatesigningrequest.go @@ -25,6 +25,7 @@ import ( apicertificatesv1beta1 "k8s.io/api/certificates/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type certificateSigningRequestInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCertificateSigningRequestInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCertificateSigningRequestInformer(client, resyncPeriod, indexers, nil) + return NewCertificateSigningRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCertificateSigningRequestInformer constructs a new informer for CertificateSigningRequest type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCertificateSigningRequestInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCertificateSigningRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCertificateSigningRequestInformerWithOptions constructs a new informer for CertificateSigningRequest type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCertificateSigningRequestInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1beta1", Resource: "certificatesigningrequests"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().CertificateSigningRequests().List(context.Background(), options) + return client.CertificatesV1beta1().CertificateSigningRequests().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().CertificateSigningRequests().Watch(context.Background(), options) + return client.CertificatesV1beta1().CertificateSigningRequests().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().CertificateSigningRequests().List(ctx, options) + return client.CertificatesV1beta1().CertificateSigningRequests().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().CertificateSigningRequests().Watch(ctx, options) + return client.CertificatesV1beta1().CertificateSigningRequests().Watch(ctx, opts) }, }, client), &apicertificatesv1beta1.CertificateSigningRequest{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *certificateSigningRequestInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCertificateSigningRequestInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCertificateSigningRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *certificateSigningRequestInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/certificates/v1beta1/clustertrustbundle.go b/staging/src/k8s.io/client-go/informers/certificates/v1beta1/clustertrustbundle.go index 93b5d631db14e..f665d0b739e6d 100644 --- a/staging/src/k8s.io/client-go/informers/certificates/v1beta1/clustertrustbundle.go +++ b/staging/src/k8s.io/client-go/informers/certificates/v1beta1/clustertrustbundle.go @@ -25,6 +25,7 @@ import ( apicertificatesv1beta1 "k8s.io/api/certificates/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterTrustBundleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterTrustBundleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterTrustBundleInformer(client, resyncPeriod, indexers, nil) + return NewClusterTrustBundleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterTrustBundleInformer constructs a new informer for ClusterTrustBundle type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterTrustBundleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterTrustBundleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterTrustBundleInformerWithOptions constructs a new informer for ClusterTrustBundle type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterTrustBundleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1beta1", Resource: "clustertrustbundles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().ClusterTrustBundles().List(context.Background(), options) + return client.CertificatesV1beta1().ClusterTrustBundles().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().ClusterTrustBundles().Watch(context.Background(), options) + return client.CertificatesV1beta1().ClusterTrustBundles().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().ClusterTrustBundles().List(ctx, options) + return client.CertificatesV1beta1().ClusterTrustBundles().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().ClusterTrustBundles().Watch(ctx, options) + return client.CertificatesV1beta1().ClusterTrustBundles().Watch(ctx, opts) }, }, client), &apicertificatesv1beta1.ClusterTrustBundle{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterTrustBundleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterTrustBundleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterTrustBundleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterTrustBundleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/certificates/v1beta1/podcertificaterequest.go b/staging/src/k8s.io/client-go/informers/certificates/v1beta1/podcertificaterequest.go index 2d41c76e92074..03f7816cfd9cd 100644 --- a/staging/src/k8s.io/client-go/informers/certificates/v1beta1/podcertificaterequest.go +++ b/staging/src/k8s.io/client-go/informers/certificates/v1beta1/podcertificaterequest.go @@ -25,6 +25,7 @@ import ( apicertificatesv1beta1 "k8s.io/api/certificates/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type podCertificateRequestInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPodCertificateRequestInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPodCertificateRequestInformer(client, namespace, resyncPeriod, indexers, nil) + return NewPodCertificateRequestInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPodCertificateRequestInformer constructs a new informer for PodCertificateRequest type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPodCertificateRequestInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPodCertificateRequestInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPodCertificateRequestInformerWithOptions constructs a new informer for PodCertificateRequest type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodCertificateRequestInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1beta1", Resource: "podcertificaterequests"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().PodCertificateRequests(namespace).List(context.Background(), options) + return client.CertificatesV1beta1().PodCertificateRequests(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().PodCertificateRequests(namespace).Watch(context.Background(), options) + return client.CertificatesV1beta1().PodCertificateRequests(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().PodCertificateRequests(namespace).List(ctx, options) + return client.CertificatesV1beta1().PodCertificateRequests(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CertificatesV1beta1().PodCertificateRequests(namespace).Watch(ctx, options) + return client.CertificatesV1beta1().PodCertificateRequests(namespace).Watch(ctx, opts) }, }, client), &apicertificatesv1beta1.PodCertificateRequest{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *podCertificateRequestInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPodCertificateRequestInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPodCertificateRequestInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *podCertificateRequestInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/coordination/v1/lease.go b/staging/src/k8s.io/client-go/informers/coordination/v1/lease.go index 1a34bf73f30c8..cce8ab4d13e15 100644 --- a/staging/src/k8s.io/client-go/informers/coordination/v1/lease.go +++ b/staging/src/k8s.io/client-go/informers/coordination/v1/lease.go @@ -25,6 +25,7 @@ import ( apicoordinationv1 "k8s.io/api/coordination/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type leaseInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredLeaseInformer(client, namespace, resyncPeriod, indexers, nil) + return NewLeaseInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredLeaseInformer constructs a new informer for Lease type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewLeaseInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewLeaseInformerWithOptions constructs a new informer for Lease type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLeaseInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "coordination.k8s.io", Version: "v1", Resource: "leases"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1().Leases(namespace).List(context.Background(), options) + return client.CoordinationV1().Leases(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1().Leases(namespace).Watch(context.Background(), options) + return client.CoordinationV1().Leases(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1().Leases(namespace).List(ctx, options) + return client.CoordinationV1().Leases(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1().Leases(namespace).Watch(ctx, options) + return client.CoordinationV1().Leases(namespace).Watch(ctx, opts) }, }, client), &apicoordinationv1.Lease{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *leaseInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredLeaseInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewLeaseInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *leaseInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/coordination/v1alpha2/leasecandidate.go b/staging/src/k8s.io/client-go/informers/coordination/v1alpha2/leasecandidate.go index f029be068a45c..5aa0ff3ae24b0 100644 --- a/staging/src/k8s.io/client-go/informers/coordination/v1alpha2/leasecandidate.go +++ b/staging/src/k8s.io/client-go/informers/coordination/v1alpha2/leasecandidate.go @@ -25,6 +25,7 @@ import ( apicoordinationv1alpha2 "k8s.io/api/coordination/v1alpha2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type leaseCandidateInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewLeaseCandidateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredLeaseCandidateInformer(client, namespace, resyncPeriod, indexers, nil) + return NewLeaseCandidateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredLeaseCandidateInformer constructs a new informer for LeaseCandidate type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredLeaseCandidateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewLeaseCandidateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewLeaseCandidateInformerWithOptions constructs a new informer for LeaseCandidate type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLeaseCandidateInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "coordination.k8s.io", Version: "v1alpha2", Resource: "leasecandidates"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1alpha2().LeaseCandidates(namespace).List(context.Background(), options) + return client.CoordinationV1alpha2().LeaseCandidates(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1alpha2().LeaseCandidates(namespace).Watch(context.Background(), options) + return client.CoordinationV1alpha2().LeaseCandidates(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1alpha2().LeaseCandidates(namespace).List(ctx, options) + return client.CoordinationV1alpha2().LeaseCandidates(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1alpha2().LeaseCandidates(namespace).Watch(ctx, options) + return client.CoordinationV1alpha2().LeaseCandidates(namespace).Watch(ctx, opts) }, }, client), &apicoordinationv1alpha2.LeaseCandidate{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *leaseCandidateInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredLeaseCandidateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewLeaseCandidateInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *leaseCandidateInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/coordination/v1beta1/lease.go b/staging/src/k8s.io/client-go/informers/coordination/v1beta1/lease.go index f254e30da304c..66802fd949590 100644 --- a/staging/src/k8s.io/client-go/informers/coordination/v1beta1/lease.go +++ b/staging/src/k8s.io/client-go/informers/coordination/v1beta1/lease.go @@ -25,6 +25,7 @@ import ( apicoordinationv1beta1 "k8s.io/api/coordination/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type leaseInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredLeaseInformer(client, namespace, resyncPeriod, indexers, nil) + return NewLeaseInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredLeaseInformer constructs a new informer for Lease type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewLeaseInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewLeaseInformerWithOptions constructs a new informer for Lease type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLeaseInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "coordination.k8s.io", Version: "v1beta1", Resource: "leases"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().Leases(namespace).List(context.Background(), options) + return client.CoordinationV1beta1().Leases(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().Leases(namespace).Watch(context.Background(), options) + return client.CoordinationV1beta1().Leases(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().Leases(namespace).List(ctx, options) + return client.CoordinationV1beta1().Leases(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().Leases(namespace).Watch(ctx, options) + return client.CoordinationV1beta1().Leases(namespace).Watch(ctx, opts) }, }, client), &apicoordinationv1beta1.Lease{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *leaseInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredLeaseInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewLeaseInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *leaseInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/coordination/v1beta1/leasecandidate.go b/staging/src/k8s.io/client-go/informers/coordination/v1beta1/leasecandidate.go index bdb3e1be13df9..ee83397f4ce99 100644 --- a/staging/src/k8s.io/client-go/informers/coordination/v1beta1/leasecandidate.go +++ b/staging/src/k8s.io/client-go/informers/coordination/v1beta1/leasecandidate.go @@ -25,6 +25,7 @@ import ( apicoordinationv1beta1 "k8s.io/api/coordination/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type leaseCandidateInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewLeaseCandidateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredLeaseCandidateInformer(client, namespace, resyncPeriod, indexers, nil) + return NewLeaseCandidateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredLeaseCandidateInformer constructs a new informer for LeaseCandidate type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredLeaseCandidateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewLeaseCandidateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewLeaseCandidateInformerWithOptions constructs a new informer for LeaseCandidate type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLeaseCandidateInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "coordination.k8s.io", Version: "v1beta1", Resource: "leasecandidates"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().LeaseCandidates(namespace).List(context.Background(), options) + return client.CoordinationV1beta1().LeaseCandidates(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().LeaseCandidates(namespace).Watch(context.Background(), options) + return client.CoordinationV1beta1().LeaseCandidates(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().LeaseCandidates(namespace).List(ctx, options) + return client.CoordinationV1beta1().LeaseCandidates(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoordinationV1beta1().LeaseCandidates(namespace).Watch(ctx, options) + return client.CoordinationV1beta1().LeaseCandidates(namespace).Watch(ctx, opts) }, }, client), &apicoordinationv1beta1.LeaseCandidate{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *leaseCandidateInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredLeaseCandidateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewLeaseCandidateInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *leaseCandidateInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/componentstatus.go b/staging/src/k8s.io/client-go/informers/core/v1/componentstatus.go index 9cf1a062aa374..5b6f6556c3197 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/componentstatus.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/componentstatus.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type componentStatusInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewComponentStatusInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredComponentStatusInformer(client, resyncPeriod, indexers, nil) + return NewComponentStatusInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredComponentStatusInformer constructs a new informer for ComponentStatus type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredComponentStatusInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewComponentStatusInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewComponentStatusInformerWithOptions constructs a new informer for ComponentStatus type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewComponentStatusInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ComponentStatuses().List(context.Background(), options) + return client.CoreV1().ComponentStatuses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ComponentStatuses().Watch(context.Background(), options) + return client.CoreV1().ComponentStatuses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ComponentStatuses().List(ctx, options) + return client.CoreV1().ComponentStatuses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ComponentStatuses().Watch(ctx, options) + return client.CoreV1().ComponentStatuses().Watch(ctx, opts) }, }, client), &apicorev1.ComponentStatus{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *componentStatusInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredComponentStatusInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewComponentStatusInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *componentStatusInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/configmap.go b/staging/src/k8s.io/client-go/informers/core/v1/configmap.go index 5772678bfa1f4..5b6389884b657 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/configmap.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/configmap.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type configMapInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewConfigMapInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredConfigMapInformer(client, namespace, resyncPeriod, indexers, nil) + return NewConfigMapInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredConfigMapInformer constructs a new informer for ConfigMap type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredConfigMapInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewConfigMapInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewConfigMapInformerWithOptions constructs a new informer for ConfigMap type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewConfigMapInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ConfigMaps(namespace).List(context.Background(), options) + return client.CoreV1().ConfigMaps(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ConfigMaps(namespace).Watch(context.Background(), options) + return client.CoreV1().ConfigMaps(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ConfigMaps(namespace).List(ctx, options) + return client.CoreV1().ConfigMaps(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ConfigMaps(namespace).Watch(ctx, options) + return client.CoreV1().ConfigMaps(namespace).Watch(ctx, opts) }, }, client), &apicorev1.ConfigMap{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *configMapInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredConfigMapInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewConfigMapInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *configMapInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/endpoints.go b/staging/src/k8s.io/client-go/informers/core/v1/endpoints.go index 6c55df988724b..1609ce7f7e332 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/endpoints.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/endpoints.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type endpointsInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewEndpointsInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEndpointsInformer(client, namespace, resyncPeriod, indexers, nil) + return NewEndpointsInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredEndpointsInformer constructs a new informer for Endpoints type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredEndpointsInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewEndpointsInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewEndpointsInformerWithOptions constructs a new informer for Endpoints type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewEndpointsInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "endpointss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Endpoints(namespace).List(context.Background(), options) + return client.CoreV1().Endpoints(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Endpoints(namespace).Watch(context.Background(), options) + return client.CoreV1().Endpoints(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Endpoints(namespace).List(ctx, options) + return client.CoreV1().Endpoints(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Endpoints(namespace).Watch(ctx, options) + return client.CoreV1().Endpoints(namespace).Watch(ctx, opts) }, }, client), &apicorev1.Endpoints{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *endpointsInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEndpointsInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewEndpointsInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *endpointsInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/event.go b/staging/src/k8s.io/client-go/informers/core/v1/event.go index 63df621b664e5..ff9c3c58479bf 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/event.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/event.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type eventInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEventInformer(client, namespace, resyncPeriod, indexers, nil) + return NewEventInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredEventInformer constructs a new informer for Event type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewEventInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewEventInformerWithOptions constructs a new informer for Event type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewEventInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "events"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Events(namespace).List(context.Background(), options) + return client.CoreV1().Events(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Events(namespace).Watch(context.Background(), options) + return client.CoreV1().Events(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Events(namespace).List(ctx, options) + return client.CoreV1().Events(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Events(namespace).Watch(ctx, options) + return client.CoreV1().Events(namespace).Watch(ctx, opts) }, }, client), &apicorev1.Event{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *eventInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEventInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewEventInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *eventInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/limitrange.go b/staging/src/k8s.io/client-go/informers/core/v1/limitrange.go index 229adb2b8f3b3..a3a01de6dc26b 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/limitrange.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/limitrange.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type limitRangeInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewLimitRangeInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredLimitRangeInformer(client, namespace, resyncPeriod, indexers, nil) + return NewLimitRangeInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredLimitRangeInformer constructs a new informer for LimitRange type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredLimitRangeInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewLimitRangeInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewLimitRangeInformerWithOptions constructs a new informer for LimitRange type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLimitRangeInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "limitranges"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().LimitRanges(namespace).List(context.Background(), options) + return client.CoreV1().LimitRanges(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().LimitRanges(namespace).Watch(context.Background(), options) + return client.CoreV1().LimitRanges(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().LimitRanges(namespace).List(ctx, options) + return client.CoreV1().LimitRanges(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().LimitRanges(namespace).Watch(ctx, options) + return client.CoreV1().LimitRanges(namespace).Watch(ctx, opts) }, }, client), &apicorev1.LimitRange{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *limitRangeInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredLimitRangeInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewLimitRangeInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *limitRangeInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/namespace.go b/staging/src/k8s.io/client-go/informers/core/v1/namespace.go index d45d2eee62a1f..c2a98944175c9 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/namespace.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/namespace.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type namespaceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewNamespaceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredNamespaceInformer(client, resyncPeriod, indexers, nil) + return NewNamespaceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredNamespaceInformer constructs a new informer for Namespace type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredNamespaceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewNamespaceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewNamespaceInformerWithOptions constructs a new informer for Namespace type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewNamespaceInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Namespaces().List(context.Background(), options) + return client.CoreV1().Namespaces().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Namespaces().Watch(context.Background(), options) + return client.CoreV1().Namespaces().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Namespaces().List(ctx, options) + return client.CoreV1().Namespaces().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Namespaces().Watch(ctx, options) + return client.CoreV1().Namespaces().Watch(ctx, opts) }, }, client), &apicorev1.Namespace{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *namespaceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredNamespaceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewNamespaceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *namespaceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/node.go b/staging/src/k8s.io/client-go/informers/core/v1/node.go index a5224761f9422..48e8d7d6aae26 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/node.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/node.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type nodeInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewNodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredNodeInformer(client, resyncPeriod, indexers, nil) + return NewNodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredNodeInformer constructs a new informer for Node type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredNodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewNodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewNodeInformerWithOptions constructs a new informer for Node type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewNodeInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Nodes().List(context.Background(), options) + return client.CoreV1().Nodes().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Nodes().Watch(context.Background(), options) + return client.CoreV1().Nodes().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Nodes().List(ctx, options) + return client.CoreV1().Nodes().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Nodes().Watch(ctx, options) + return client.CoreV1().Nodes().Watch(ctx, opts) }, }, client), &apicorev1.Node{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *nodeInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredNodeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewNodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *nodeInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/persistentvolume.go b/staging/src/k8s.io/client-go/informers/core/v1/persistentvolume.go index 0458fc1956e82..aebe7ffa817d5 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/persistentvolume.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/persistentvolume.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type persistentVolumeInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPersistentVolumeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPersistentVolumeInformer(client, resyncPeriod, indexers, nil) + return NewPersistentVolumeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPersistentVolumeInformer constructs a new informer for PersistentVolume type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPersistentVolumeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPersistentVolumeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPersistentVolumeInformerWithOptions constructs a new informer for PersistentVolume type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPersistentVolumeInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumes"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumes().List(context.Background(), options) + return client.CoreV1().PersistentVolumes().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumes().Watch(context.Background(), options) + return client.CoreV1().PersistentVolumes().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumes().List(ctx, options) + return client.CoreV1().PersistentVolumes().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumes().Watch(ctx, options) + return client.CoreV1().PersistentVolumes().Watch(ctx, opts) }, }, client), &apicorev1.PersistentVolume{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *persistentVolumeInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPersistentVolumeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPersistentVolumeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *persistentVolumeInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/persistentvolumeclaim.go b/staging/src/k8s.io/client-go/informers/core/v1/persistentvolumeclaim.go index dd75924def727..973b5571edbd3 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/persistentvolumeclaim.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/persistentvolumeclaim.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type persistentVolumeClaimInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPersistentVolumeClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPersistentVolumeClaimInformer(client, namespace, resyncPeriod, indexers, nil) + return NewPersistentVolumeClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPersistentVolumeClaimInformer constructs a new informer for PersistentVolumeClaim type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPersistentVolumeClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPersistentVolumeClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPersistentVolumeClaimInformerWithOptions constructs a new informer for PersistentVolumeClaim type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPersistentVolumeClaimInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumeClaims(namespace).List(context.Background(), options) + return client.CoreV1().PersistentVolumeClaims(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumeClaims(namespace).Watch(context.Background(), options) + return client.CoreV1().PersistentVolumeClaims(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumeClaims(namespace).List(ctx, options) + return client.CoreV1().PersistentVolumeClaims(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PersistentVolumeClaims(namespace).Watch(ctx, options) + return client.CoreV1().PersistentVolumeClaims(namespace).Watch(ctx, opts) }, }, client), &apicorev1.PersistentVolumeClaim{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *persistentVolumeClaimInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPersistentVolumeClaimInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPersistentVolumeClaimInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *persistentVolumeClaimInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/pod.go b/staging/src/k8s.io/client-go/informers/core/v1/pod.go index b68ebc1dd5c47..f49dee3dee98f 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/pod.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/pod.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type podInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPodInformer(client, namespace, resyncPeriod, indexers, nil) + return NewPodInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPodInformer constructs a new informer for Pod type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPodInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPodInformerWithOptions constructs a new informer for Pod type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Pods(namespace).List(context.Background(), options) + return client.CoreV1().Pods(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Pods(namespace).Watch(context.Background(), options) + return client.CoreV1().Pods(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Pods(namespace).List(ctx, options) + return client.CoreV1().Pods(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Pods(namespace).Watch(ctx, options) + return client.CoreV1().Pods(namespace).Watch(ctx, opts) }, }, client), &apicorev1.Pod{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *podInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPodInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *podInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/podtemplate.go b/staging/src/k8s.io/client-go/informers/core/v1/podtemplate.go index c4b4cd12be86e..940ae25999408 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/podtemplate.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/podtemplate.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type podTemplateInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPodTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPodTemplateInformer(client, namespace, resyncPeriod, indexers, nil) + return NewPodTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPodTemplateInformer constructs a new informer for PodTemplate type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPodTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPodTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPodTemplateInformerWithOptions constructs a new informer for PodTemplate type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodTemplateInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "podtemplates"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PodTemplates(namespace).List(context.Background(), options) + return client.CoreV1().PodTemplates(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PodTemplates(namespace).Watch(context.Background(), options) + return client.CoreV1().PodTemplates(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PodTemplates(namespace).List(ctx, options) + return client.CoreV1().PodTemplates(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().PodTemplates(namespace).Watch(ctx, options) + return client.CoreV1().PodTemplates(namespace).Watch(ctx, opts) }, }, client), &apicorev1.PodTemplate{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *podTemplateInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPodTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPodTemplateInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *podTemplateInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/replicationcontroller.go b/staging/src/k8s.io/client-go/informers/core/v1/replicationcontroller.go index 58b66c2bd5dba..ec323d3c63bd8 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/replicationcontroller.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/replicationcontroller.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type replicationControllerInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewReplicationControllerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredReplicationControllerInformer(client, namespace, resyncPeriod, indexers, nil) + return NewReplicationControllerInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredReplicationControllerInformer constructs a new informer for ReplicationController type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredReplicationControllerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewReplicationControllerInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewReplicationControllerInformerWithOptions constructs a new informer for ReplicationController type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewReplicationControllerInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "replicationcontrollers"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ReplicationControllers(namespace).List(context.Background(), options) + return client.CoreV1().ReplicationControllers(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ReplicationControllers(namespace).Watch(context.Background(), options) + return client.CoreV1().ReplicationControllers(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ReplicationControllers(namespace).List(ctx, options) + return client.CoreV1().ReplicationControllers(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ReplicationControllers(namespace).Watch(ctx, options) + return client.CoreV1().ReplicationControllers(namespace).Watch(ctx, opts) }, }, client), &apicorev1.ReplicationController{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *replicationControllerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredReplicationControllerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewReplicationControllerInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *replicationControllerInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/resourcequota.go b/staging/src/k8s.io/client-go/informers/core/v1/resourcequota.go index 408c024581060..490a31212b53b 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/resourcequota.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/resourcequota.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceQuotaInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceQuotaInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceQuotaInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceQuotaInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceQuotaInformer constructs a new informer for ResourceQuota type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceQuotaInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceQuotaInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceQuotaInformerWithOptions constructs a new informer for ResourceQuota type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceQuotaInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "resourcequotas"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ResourceQuotas(namespace).List(context.Background(), options) + return client.CoreV1().ResourceQuotas(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ResourceQuotas(namespace).Watch(context.Background(), options) + return client.CoreV1().ResourceQuotas(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ResourceQuotas(namespace).List(ctx, options) + return client.CoreV1().ResourceQuotas(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ResourceQuotas(namespace).Watch(ctx, options) + return client.CoreV1().ResourceQuotas(namespace).Watch(ctx, opts) }, }, client), &apicorev1.ResourceQuota{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceQuotaInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceQuotaInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceQuotaInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceQuotaInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/secret.go b/staging/src/k8s.io/client-go/informers/core/v1/secret.go index 7f7338d063041..86458285d925b 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/secret.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/secret.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type secretInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewSecretInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredSecretInformer(client, namespace, resyncPeriod, indexers, nil) + return NewSecretInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredSecretInformer constructs a new informer for Secret type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredSecretInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewSecretInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewSecretInformerWithOptions constructs a new informer for Secret type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSecretInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Secrets(namespace).List(context.Background(), options) + return client.CoreV1().Secrets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Secrets(namespace).Watch(context.Background(), options) + return client.CoreV1().Secrets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Secrets(namespace).List(ctx, options) + return client.CoreV1().Secrets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Secrets(namespace).Watch(ctx, options) + return client.CoreV1().Secrets(namespace).Watch(ctx, opts) }, }, client), &apicorev1.Secret{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *secretInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredSecretInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewSecretInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *secretInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/service.go b/staging/src/k8s.io/client-go/informers/core/v1/service.go index b4dcb75d79440..4ec10b3227bd5 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/service.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/service.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type serviceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewServiceInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredServiceInformer(client, namespace, resyncPeriod, indexers, nil) + return NewServiceInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredServiceInformer constructs a new informer for Service type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredServiceInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewServiceInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewServiceInformerWithOptions constructs a new informer for Service type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewServiceInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Services(namespace).List(context.Background(), options) + return client.CoreV1().Services(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Services(namespace).Watch(context.Background(), options) + return client.CoreV1().Services(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Services(namespace).List(ctx, options) + return client.CoreV1().Services(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().Services(namespace).Watch(ctx, options) + return client.CoreV1().Services(namespace).Watch(ctx, opts) }, }, client), &apicorev1.Service{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *serviceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredServiceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewServiceInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *serviceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/core/v1/serviceaccount.go b/staging/src/k8s.io/client-go/informers/core/v1/serviceaccount.go index 5ddc98b24e716..32751b6a3856e 100644 --- a/staging/src/k8s.io/client-go/informers/core/v1/serviceaccount.go +++ b/staging/src/k8s.io/client-go/informers/core/v1/serviceaccount.go @@ -25,6 +25,7 @@ import ( apicorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type serviceAccountInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewServiceAccountInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredServiceAccountInformer(client, namespace, resyncPeriod, indexers, nil) + return NewServiceAccountInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredServiceAccountInformer constructs a new informer for ServiceAccount type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredServiceAccountInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewServiceAccountInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewServiceAccountInformerWithOptions constructs a new informer for ServiceAccount type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewServiceAccountInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "serviceaccounts"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ServiceAccounts(namespace).List(context.Background(), options) + return client.CoreV1().ServiceAccounts(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ServiceAccounts(namespace).Watch(context.Background(), options) + return client.CoreV1().ServiceAccounts(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ServiceAccounts(namespace).List(ctx, options) + return client.CoreV1().ServiceAccounts(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1().ServiceAccounts(namespace).Watch(ctx, options) + return client.CoreV1().ServiceAccounts(namespace).Watch(ctx, opts) }, }, client), &apicorev1.ServiceAccount{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *serviceAccountInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredServiceAccountInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewServiceAccountInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *serviceAccountInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/discovery/v1/endpointslice.go b/staging/src/k8s.io/client-go/informers/discovery/v1/endpointslice.go index 1291912816f3e..999d5145986af 100644 --- a/staging/src/k8s.io/client-go/informers/discovery/v1/endpointslice.go +++ b/staging/src/k8s.io/client-go/informers/discovery/v1/endpointslice.go @@ -25,6 +25,7 @@ import ( apidiscoveryv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type endpointSliceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewEndpointSliceInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEndpointSliceInformer(client, namespace, resyncPeriod, indexers, nil) + return NewEndpointSliceInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredEndpointSliceInformer constructs a new informer for EndpointSlice type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredEndpointSliceInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewEndpointSliceInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewEndpointSliceInformerWithOptions constructs a new informer for EndpointSlice type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewEndpointSliceInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "discovery.k8s.io", Version: "v1", Resource: "endpointslices"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1().EndpointSlices(namespace).List(context.Background(), options) + return client.DiscoveryV1().EndpointSlices(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1().EndpointSlices(namespace).Watch(context.Background(), options) + return client.DiscoveryV1().EndpointSlices(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1().EndpointSlices(namespace).List(ctx, options) + return client.DiscoveryV1().EndpointSlices(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1().EndpointSlices(namespace).Watch(ctx, options) + return client.DiscoveryV1().EndpointSlices(namespace).Watch(ctx, opts) }, }, client), &apidiscoveryv1.EndpointSlice{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *endpointSliceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEndpointSliceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewEndpointSliceInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *endpointSliceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/discovery/v1beta1/endpointslice.go b/staging/src/k8s.io/client-go/informers/discovery/v1beta1/endpointslice.go index 80147e2438cdc..918e30485f8c4 100644 --- a/staging/src/k8s.io/client-go/informers/discovery/v1beta1/endpointslice.go +++ b/staging/src/k8s.io/client-go/informers/discovery/v1beta1/endpointslice.go @@ -25,6 +25,7 @@ import ( apidiscoveryv1beta1 "k8s.io/api/discovery/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type endpointSliceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewEndpointSliceInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEndpointSliceInformer(client, namespace, resyncPeriod, indexers, nil) + return NewEndpointSliceInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredEndpointSliceInformer constructs a new informer for EndpointSlice type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredEndpointSliceInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewEndpointSliceInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewEndpointSliceInformerWithOptions constructs a new informer for EndpointSlice type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewEndpointSliceInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "discovery.k8s.io", Version: "v1beta1", Resource: "endpointslices"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1beta1().EndpointSlices(namespace).List(context.Background(), options) + return client.DiscoveryV1beta1().EndpointSlices(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1beta1().EndpointSlices(namespace).Watch(context.Background(), options) + return client.DiscoveryV1beta1().EndpointSlices(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1beta1().EndpointSlices(namespace).List(ctx, options) + return client.DiscoveryV1beta1().EndpointSlices(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.DiscoveryV1beta1().EndpointSlices(namespace).Watch(ctx, options) + return client.DiscoveryV1beta1().EndpointSlices(namespace).Watch(ctx, opts) }, }, client), &apidiscoveryv1beta1.EndpointSlice{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *endpointSliceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEndpointSliceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewEndpointSliceInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *endpointSliceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/events/v1/event.go b/staging/src/k8s.io/client-go/informers/events/v1/event.go index 68eafe312ce27..23aaa684797d3 100644 --- a/staging/src/k8s.io/client-go/informers/events/v1/event.go +++ b/staging/src/k8s.io/client-go/informers/events/v1/event.go @@ -25,6 +25,7 @@ import ( apieventsv1 "k8s.io/api/events/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type eventInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEventInformer(client, namespace, resyncPeriod, indexers, nil) + return NewEventInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredEventInformer constructs a new informer for Event type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewEventInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewEventInformerWithOptions constructs a new informer for Event type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewEventInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "events.k8s.io", Version: "v1", Resource: "events"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1().Events(namespace).List(context.Background(), options) + return client.EventsV1().Events(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1().Events(namespace).Watch(context.Background(), options) + return client.EventsV1().Events(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1().Events(namespace).List(ctx, options) + return client.EventsV1().Events(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1().Events(namespace).Watch(ctx, options) + return client.EventsV1().Events(namespace).Watch(ctx, opts) }, }, client), &apieventsv1.Event{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *eventInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEventInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewEventInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *eventInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/events/v1beta1/event.go b/staging/src/k8s.io/client-go/informers/events/v1beta1/event.go index b26c208938f42..ea0117eacad17 100644 --- a/staging/src/k8s.io/client-go/informers/events/v1beta1/event.go +++ b/staging/src/k8s.io/client-go/informers/events/v1beta1/event.go @@ -25,6 +25,7 @@ import ( apieventsv1beta1 "k8s.io/api/events/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type eventInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEventInformer(client, namespace, resyncPeriod, indexers, nil) + return NewEventInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredEventInformer constructs a new informer for Event type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewEventInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewEventInformerWithOptions constructs a new informer for Event type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewEventInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "events.k8s.io", Version: "v1beta1", Resource: "events"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1beta1().Events(namespace).List(context.Background(), options) + return client.EventsV1beta1().Events(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1beta1().Events(namespace).Watch(context.Background(), options) + return client.EventsV1beta1().Events(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1beta1().Events(namespace).List(ctx, options) + return client.EventsV1beta1().Events(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.EventsV1beta1().Events(namespace).Watch(ctx, options) + return client.EventsV1beta1().Events(namespace).Watch(ctx, opts) }, }, client), &apieventsv1beta1.Event{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *eventInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEventInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewEventInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *eventInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/daemonset.go b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/daemonset.go index fdfbb02306028..ed8b023038379 100644 --- a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/daemonset.go +++ b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/daemonset.go @@ -25,6 +25,7 @@ import ( apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type daemonSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDaemonSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDaemonSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDaemonSetInformer constructs a new informer for DaemonSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDaemonSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDaemonSetInformerWithOptions constructs a new informer for DaemonSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDaemonSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "daemonsets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().DaemonSets(namespace).List(context.Background(), options) + return client.ExtensionsV1beta1().DaemonSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().DaemonSets(namespace).Watch(context.Background(), options) + return client.ExtensionsV1beta1().DaemonSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().DaemonSets(namespace).List(ctx, options) + return client.ExtensionsV1beta1().DaemonSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().DaemonSets(namespace).Watch(ctx, options) + return client.ExtensionsV1beta1().DaemonSets(namespace).Watch(ctx, opts) }, }, client), &apiextensionsv1beta1.DaemonSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *daemonSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDaemonSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDaemonSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *daemonSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/deployment.go b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/deployment.go index 6442266250946..ee328bb5170b3 100644 --- a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/deployment.go +++ b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/deployment.go @@ -25,6 +25,7 @@ import ( apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type deploymentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, namespace, resyncPeriod, indexers, nil) + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeploymentInformer constructs a new informer for Deployment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeploymentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeploymentInformerWithOptions constructs a new informer for Deployment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeploymentInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Deployments(namespace).List(context.Background(), options) + return client.ExtensionsV1beta1().Deployments(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Deployments(namespace).Watch(context.Background(), options) + return client.ExtensionsV1beta1().Deployments(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Deployments(namespace).List(ctx, options) + return client.ExtensionsV1beta1().Deployments(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Deployments(namespace).Watch(ctx, options) + return client.ExtensionsV1beta1().Deployments(namespace).Watch(ctx, opts) }, }, client), &apiextensionsv1beta1.Deployment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeploymentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeploymentInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deploymentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/ingress.go b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/ingress.go index bbd7286843be8..852238a481ff7 100644 --- a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/ingress.go +++ b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/ingress.go @@ -25,6 +25,7 @@ import ( apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type ingressInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIngressInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIngressInformer(client, namespace, resyncPeriod, indexers, nil) + return NewIngressInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIngressInformer constructs a new informer for Ingress type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIngressInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIngressInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIngressInformerWithOptions constructs a new informer for Ingress type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIngressInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Ingresses(namespace).List(context.Background(), options) + return client.ExtensionsV1beta1().Ingresses(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Ingresses(namespace).Watch(context.Background(), options) + return client.ExtensionsV1beta1().Ingresses(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Ingresses(namespace).List(ctx, options) + return client.ExtensionsV1beta1().Ingresses(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().Ingresses(namespace).Watch(ctx, options) + return client.ExtensionsV1beta1().Ingresses(namespace).Watch(ctx, opts) }, }, client), &apiextensionsv1beta1.Ingress{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *ingressInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIngressInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIngressInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *ingressInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/networkpolicy.go b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/networkpolicy.go index 45eb5e95bb9d9..fda46c1d96129 100644 --- a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/networkpolicy.go +++ b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/networkpolicy.go @@ -25,6 +25,7 @@ import ( apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type networkPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewNetworkPolicyInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredNetworkPolicyInformer(client, namespace, resyncPeriod, indexers, nil) + return NewNetworkPolicyInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredNetworkPolicyInformer constructs a new informer for NetworkPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredNetworkPolicyInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewNetworkPolicyInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewNetworkPolicyInformerWithOptions constructs a new informer for NetworkPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewNetworkPolicyInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "networkpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().NetworkPolicies(namespace).List(context.Background(), options) + return client.ExtensionsV1beta1().NetworkPolicies(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().NetworkPolicies(namespace).Watch(context.Background(), options) + return client.ExtensionsV1beta1().NetworkPolicies(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().NetworkPolicies(namespace).List(ctx, options) + return client.ExtensionsV1beta1().NetworkPolicies(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().NetworkPolicies(namespace).Watch(ctx, options) + return client.ExtensionsV1beta1().NetworkPolicies(namespace).Watch(ctx, opts) }, }, client), &apiextensionsv1beta1.NetworkPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *networkPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredNetworkPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewNetworkPolicyInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *networkPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/replicaset.go b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/replicaset.go index 82a183b3c7d6e..eb0685147f09b 100644 --- a/staging/src/k8s.io/client-go/informers/extensions/v1beta1/replicaset.go +++ b/staging/src/k8s.io/client-go/informers/extensions/v1beta1/replicaset.go @@ -25,6 +25,7 @@ import ( apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type replicaSetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredReplicaSetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewReplicaSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredReplicaSetInformer constructs a new informer for ReplicaSet type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewReplicaSetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewReplicaSetInformerWithOptions constructs a new informer for ReplicaSet type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewReplicaSetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicasets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().ReplicaSets(namespace).List(context.Background(), options) + return client.ExtensionsV1beta1().ReplicaSets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().ReplicaSets(namespace).Watch(context.Background(), options) + return client.ExtensionsV1beta1().ReplicaSets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().ReplicaSets(namespace).List(ctx, options) + return client.ExtensionsV1beta1().ReplicaSets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ExtensionsV1beta1().ReplicaSets(namespace).Watch(ctx, options) + return client.ExtensionsV1beta1().ReplicaSets(namespace).Watch(ctx, opts) }, }, client), &apiextensionsv1beta1.ReplicaSet{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *replicaSetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredReplicaSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewReplicaSetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *replicaSetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/factory.go b/staging/src/k8s.io/client-go/informers/factory.go index bd3d16c0108b2..19901079f8bcf 100644 --- a/staging/src/k8s.io/client-go/informers/factory.go +++ b/staging/src/k8s.io/client-go/informers/factory.go @@ -19,6 +19,7 @@ limitations under the License. package informers import ( + context "context" reflect "reflect" sync "sync" time "time" @@ -26,6 +27,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" + wait "k8s.io/apimachinery/pkg/util/wait" admissionregistration "k8s.io/client-go/informers/admissionregistration" apiserverinternal "k8s.io/client-go/informers/apiserverinternal" apps "k8s.io/client-go/informers/apps" @@ -62,6 +64,7 @@ type sharedInformerFactory struct { defaultResync time.Duration customResync map[reflect.Type]time.Duration transform cache.TransformFunc + informerName *cache.InformerName informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -108,6 +111,21 @@ func WithTransform(transform cache.TransformFunc) SharedInformerOption { } } +// WithInformerName sets the InformerName for informer identity used in metrics. +// The InformerName must be created via cache.NewInformerName() at startup, +// which validates global uniqueness. Each informer type will register its +// GVR under this name. +func WithInformerName(informerName *cache.InformerName) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.informerName = informerName + return factory + } +} + +func (f *sharedInformerFactory) InformerName() *cache.InformerName { + return f.informerName +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client kubernetes.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -142,6 +160,10 @@ func NewSharedInformerFactoryWithOptions(client kubernetes.Interface, defaultRes } func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext(wait.ContextForChannel(stopCh)) +} + +func (f *sharedInformerFactory) StartWithContext(ctx context.Context) { f.lock.Lock() defer f.lock.Unlock() @@ -151,15 +173,9 @@ func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + f.wg.Go(func() { + informer.RunWithContext(ctx) + }) f.startedInformers[informerType] = true } } @@ -172,9 +188,15 @@ func (f *sharedInformerFactory) Shutdown() { // Will return immediately if there is nothing to wait for. f.wg.Wait() + f.informerName.Release() } func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + result := f.WaitForCacheSyncWithContext(wait.ContextForChannel(stopCh)) + return result.Synced +} + +func (f *sharedInformerFactory) WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() defer f.lock.Unlock() @@ -188,10 +210,31 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return informers }() - res := map[reflect.Type]bool{} + // Wait for informers to sync, without polling. + cacheSyncs := make([]cache.DoneChecker, 0, len(informers)) + for _, informer := range informers { + cacheSyncs = append(cacheSyncs, informer.HasSyncedChecker()) + } + cache.WaitFor(ctx, "" /* no logging */, cacheSyncs...) + + res := cache.SyncResult{ + Synced: make(map[reflect.Type]bool, len(informers)), + } + failed := false for informType, informer := range informers { - res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + hasSynced := informer.HasSynced() + if !hasSynced { + failed = true + } + res.Synced[informType] = hasSynced } + if failed { + // context.Cause is more informative than ctx.Err(). + // This must be non-nil, otherwise WaitFor wouldn't have stopped + // prematurely. + res.Err = context.Cause(ctx) + } + return res } @@ -213,7 +256,9 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) - informer.SetTransform(f.transform) + if f.transform != nil { + informer.SetTransform(f.transform) + } f.informers[informerType] = informer return informer @@ -230,27 +275,46 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // defer factory.WaitForStop() // Returns immediately if nothing was started. // genericInformer := factory.ForResource(resource) // typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } +// handle, err := typeInformer.Informer().AddEventHandler(...) +// if err != nil { +// return fmt.Errorf("register event handler: %v", err) +// } +// defer typeInformer.Informer().RemoveEventHandler(handle) // Avoids leaking goroutines. +// factory.StartWithContext(ctx) // Start processing these informers. +// synced := factory.WaitForCacheSyncWithContext(ctx) +// if err := synced.AsError(); err != nil { +// return err +// } +// for v := range synced { +// // Only if desired log some information similar to this. +// fmt.Fprintf(os.Stdout, "cache synced: %s", v) +// } +// +// // Also make sure that all of the initial cache events have been delivered. +// if !WaitFor(ctx, "event handler sync", handle.HasSyncedChecker()) { +// // Must have failed because of context. +// return fmt.Errorf("sync event handler: %w", context.Cause(ctx)) // } // // // Creating informers can also be created after Start, but then // // Start must be called again: // anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) +// factory.StartWithContext(ctx) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + // + // Contextual logging: StartWithContext should be used instead of Start in code which supports contextual logging. Start(stopCh <-chan struct{}) + // StartWithContext initializes all requested informers. They are handled in goroutines + // which run until the context gets canceled. + // Warning: StartWithContext does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + StartWithContext(ctx context.Context) + // Shutdown marks a factory as shutting down. At that point no new // informers can be started anymore and Start will return without // doing anything. @@ -265,8 +329,14 @@ type SharedInformerFactory interface { // WaitForCacheSync blocks until all started informers' caches were synced // or the stop channel gets closed. + // + // Contextual logging: WaitForCacheSync should be used instead of WaitForCacheSync in code which supports contextual logging. It also returns a more useful result. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // WaitForCacheSyncWithContext blocks until all started informers' caches were synced + // or the context gets canceled. + WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult + // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1/flowschema.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1/flowschema.go index 734ee0ebf4ee2..624c2f5d6d16f 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1/flowschema.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1/flowschema.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1 "k8s.io/api/flowcontrol/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type flowSchemaInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, indexers, nil) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredFlowSchemaInformer constructs a new informer for FlowSchema type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewFlowSchemaInformerWithOptions constructs a new informer for FlowSchema type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFlowSchemaInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1", Resource: "flowschemas"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().FlowSchemas().List(context.Background(), options) + return client.FlowcontrolV1().FlowSchemas().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().FlowSchemas().Watch(context.Background(), options) + return client.FlowcontrolV1().FlowSchemas().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().FlowSchemas().List(ctx, options) + return client.FlowcontrolV1().FlowSchemas().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().FlowSchemas().Watch(ctx, options) + return client.FlowcontrolV1().FlowSchemas().Watch(ctx, opts) }, }, client), &apiflowcontrolv1.FlowSchema{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *flowSchemaInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *flowSchemaInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1/prioritylevelconfiguration.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1/prioritylevelconfiguration.go index a5f23218efc5b..5a425095531d3 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1/prioritylevelconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1/prioritylevelconfiguration.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1 "k8s.io/api/flowcontrol/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type priorityLevelConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPriorityLevelConfigurationInformer constructs a new informer for PriorityLevelConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPriorityLevelConfigurationInformerWithOptions constructs a new informer for PriorityLevelConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPriorityLevelConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1", Resource: "prioritylevelconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().PriorityLevelConfigurations().List(context.Background(), options) + return client.FlowcontrolV1().PriorityLevelConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().PriorityLevelConfigurations().Watch(context.Background(), options) + return client.FlowcontrolV1().PriorityLevelConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().PriorityLevelConfigurations().List(ctx, options) + return client.FlowcontrolV1().PriorityLevelConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1().PriorityLevelConfigurations().Watch(ctx, options) + return client.FlowcontrolV1().PriorityLevelConfigurations().Watch(ctx, opts) }, }, client), &apiflowcontrolv1.PriorityLevelConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *priorityLevelConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *priorityLevelConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/flowschema.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/flowschema.go index 5e88fe8cb9c03..2e0098e664a0e 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/flowschema.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/flowschema.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1beta1 "k8s.io/api/flowcontrol/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type flowSchemaInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, indexers, nil) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredFlowSchemaInformer constructs a new informer for FlowSchema type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewFlowSchemaInformerWithOptions constructs a new informer for FlowSchema type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFlowSchemaInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta1", Resource: "flowschemas"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().FlowSchemas().List(context.Background(), options) + return client.FlowcontrolV1beta1().FlowSchemas().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().FlowSchemas().Watch(context.Background(), options) + return client.FlowcontrolV1beta1().FlowSchemas().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().FlowSchemas().List(ctx, options) + return client.FlowcontrolV1beta1().FlowSchemas().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().FlowSchemas().Watch(ctx, options) + return client.FlowcontrolV1beta1().FlowSchemas().Watch(ctx, opts) }, }, client), &apiflowcontrolv1beta1.FlowSchema{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *flowSchemaInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *flowSchemaInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/prioritylevelconfiguration.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/prioritylevelconfiguration.go index f86a96e38cffe..9c94839a1a92c 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/prioritylevelconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta1/prioritylevelconfiguration.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1beta1 "k8s.io/api/flowcontrol/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type priorityLevelConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPriorityLevelConfigurationInformer constructs a new informer for PriorityLevelConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPriorityLevelConfigurationInformerWithOptions constructs a new informer for PriorityLevelConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPriorityLevelConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta1", Resource: "prioritylevelconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().PriorityLevelConfigurations().List(context.Background(), options) + return client.FlowcontrolV1beta1().PriorityLevelConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().PriorityLevelConfigurations().Watch(context.Background(), options) + return client.FlowcontrolV1beta1().PriorityLevelConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().PriorityLevelConfigurations().List(ctx, options) + return client.FlowcontrolV1beta1().PriorityLevelConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta1().PriorityLevelConfigurations().Watch(ctx, options) + return client.FlowcontrolV1beta1().PriorityLevelConfigurations().Watch(ctx, opts) }, }, client), &apiflowcontrolv1beta1.PriorityLevelConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *priorityLevelConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *priorityLevelConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/flowschema.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/flowschema.go index e17e4c9fa23c0..aba4d712af962 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/flowschema.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/flowschema.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1beta2 "k8s.io/api/flowcontrol/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type flowSchemaInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, indexers, nil) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredFlowSchemaInformer constructs a new informer for FlowSchema type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewFlowSchemaInformerWithOptions constructs a new informer for FlowSchema type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFlowSchemaInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2", Resource: "flowschemas"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().FlowSchemas().List(context.Background(), options) + return client.FlowcontrolV1beta2().FlowSchemas().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().FlowSchemas().Watch(context.Background(), options) + return client.FlowcontrolV1beta2().FlowSchemas().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().FlowSchemas().List(ctx, options) + return client.FlowcontrolV1beta2().FlowSchemas().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().FlowSchemas().Watch(ctx, options) + return client.FlowcontrolV1beta2().FlowSchemas().Watch(ctx, opts) }, }, client), &apiflowcontrolv1beta2.FlowSchema{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *flowSchemaInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *flowSchemaInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/prioritylevelconfiguration.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/prioritylevelconfiguration.go index 1db6efa6fe92d..0ce811759a016 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/prioritylevelconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta2/prioritylevelconfiguration.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1beta2 "k8s.io/api/flowcontrol/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type priorityLevelConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPriorityLevelConfigurationInformer constructs a new informer for PriorityLevelConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPriorityLevelConfigurationInformerWithOptions constructs a new informer for PriorityLevelConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPriorityLevelConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2", Resource: "prioritylevelconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().PriorityLevelConfigurations().List(context.Background(), options) + return client.FlowcontrolV1beta2().PriorityLevelConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().PriorityLevelConfigurations().Watch(context.Background(), options) + return client.FlowcontrolV1beta2().PriorityLevelConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().PriorityLevelConfigurations().List(ctx, options) + return client.FlowcontrolV1beta2().PriorityLevelConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta2().PriorityLevelConfigurations().Watch(ctx, options) + return client.FlowcontrolV1beta2().PriorityLevelConfigurations().Watch(ctx, opts) }, }, client), &apiflowcontrolv1beta2.PriorityLevelConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *priorityLevelConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *priorityLevelConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/flowschema.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/flowschema.go index 626a20d6201ca..16ce2ba23c427 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/flowschema.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/flowschema.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1beta3 "k8s.io/api/flowcontrol/v1beta3" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type flowSchemaInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, indexers, nil) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredFlowSchemaInformer constructs a new informer for FlowSchema type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredFlowSchemaInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewFlowSchemaInformerWithOptions constructs a new informer for FlowSchema type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFlowSchemaInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta3", Resource: "flowschemas"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().FlowSchemas().List(context.Background(), options) + return client.FlowcontrolV1beta3().FlowSchemas().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().FlowSchemas().Watch(context.Background(), options) + return client.FlowcontrolV1beta3().FlowSchemas().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().FlowSchemas().List(ctx, options) + return client.FlowcontrolV1beta3().FlowSchemas().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().FlowSchemas().Watch(ctx, options) + return client.FlowcontrolV1beta3().FlowSchemas().Watch(ctx, opts) }, }, client), &apiflowcontrolv1beta3.FlowSchema{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *flowSchemaInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredFlowSchemaInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewFlowSchemaInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *flowSchemaInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/prioritylevelconfiguration.go b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/prioritylevelconfiguration.go index 43f697423487c..ab63c8e62b4ca 100644 --- a/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/prioritylevelconfiguration.go +++ b/staging/src/k8s.io/client-go/informers/flowcontrol/v1beta3/prioritylevelconfiguration.go @@ -25,6 +25,7 @@ import ( apiflowcontrolv1beta3 "k8s.io/api/flowcontrol/v1beta3" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type priorityLevelConfigurationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, indexers, nil) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPriorityLevelConfigurationInformer constructs a new informer for PriorityLevelConfiguration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPriorityLevelConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPriorityLevelConfigurationInformerWithOptions constructs a new informer for PriorityLevelConfiguration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPriorityLevelConfigurationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta3", Resource: "prioritylevelconfigurations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().PriorityLevelConfigurations().List(context.Background(), options) + return client.FlowcontrolV1beta3().PriorityLevelConfigurations().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().PriorityLevelConfigurations().Watch(context.Background(), options) + return client.FlowcontrolV1beta3().PriorityLevelConfigurations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().PriorityLevelConfigurations().List(ctx, options) + return client.FlowcontrolV1beta3().PriorityLevelConfigurations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.FlowcontrolV1beta3().PriorityLevelConfigurations().Watch(ctx, options) + return client.FlowcontrolV1beta3().PriorityLevelConfigurations().Watch(ctx, opts) }, }, client), &apiflowcontrolv1beta3.PriorityLevelConfiguration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *priorityLevelConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityLevelConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPriorityLevelConfigurationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *priorityLevelConfigurationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/generic.go b/staging/src/k8s.io/client-go/informers/generic.go index 2b7efe7f4a45e..03c45c71cf62f 100644 --- a/staging/src/k8s.io/client-go/informers/generic.go +++ b/staging/src/k8s.io/client-go/informers/generic.go @@ -30,8 +30,6 @@ import ( v1beta2 "k8s.io/api/apps/v1beta2" autoscalingv1 "k8s.io/api/autoscaling/v1" v2 "k8s.io/api/autoscaling/v2" - v2beta1 "k8s.io/api/autoscaling/v2beta1" - v2beta2 "k8s.io/api/autoscaling/v2beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1 "k8s.io/api/certificates/v1" @@ -65,7 +63,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingv1 "k8s.io/api/scheduling/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" storagev1 "k8s.io/api/storage/v1" storagev1alpha1 "k8s.io/api/storage/v1alpha1" @@ -102,6 +100,10 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=admissionregistration.k8s.io, Version=v1 + case v1.SchemeGroupVersion.WithResource("mutatingadmissionpolicies"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1().MutatingAdmissionPolicies().Informer()}, nil + case v1.SchemeGroupVersion.WithResource("mutatingadmissionpolicybindings"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1().MutatingAdmissionPolicyBindings().Informer()}, nil case v1.SchemeGroupVersion.WithResource("mutatingwebhookconfigurations"): return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1().MutatingWebhookConfigurations().Informer()}, nil case v1.SchemeGroupVersion.WithResource("validatingadmissionpolicies"): @@ -175,14 +177,6 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2().HorizontalPodAutoscalers().Informer()}, nil - // Group=autoscaling, Version=v2beta1 - case v2beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2beta1().HorizontalPodAutoscalers().Informer()}, nil - - // Group=autoscaling, Version=v2beta2 - case v2beta2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2beta2().HorizontalPodAutoscalers().Informer()}, nil - // Group=batch, Version=v1 case batchv1.SchemeGroupVersion.WithResource("cronjobs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Batch().V1().CronJobs().Informer()}, nil @@ -398,6 +392,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=resource.k8s.io, Version=v1alpha3 case v1alpha3.SchemeGroupVersion.WithResource("devicetaintrules"): return &genericInformer{resource: resource.GroupResource(), informer: f.Resource().V1alpha3().DeviceTaintRules().Informer()}, nil + case v1alpha3.SchemeGroupVersion.WithResource("resourcepoolstatusrequests"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Resource().V1alpha3().ResourcePoolStatusRequests().Informer()}, nil // Group=resource.k8s.io, Version=v1beta1 case resourcev1beta1.SchemeGroupVersion.WithResource("deviceclasses"): @@ -412,6 +408,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=resource.k8s.io, Version=v1beta2 case resourcev1beta2.SchemeGroupVersion.WithResource("deviceclasses"): return &genericInformer{resource: resource.GroupResource(), informer: f.Resource().V1beta2().DeviceClasses().Informer()}, nil + case resourcev1beta2.SchemeGroupVersion.WithResource("devicetaintrules"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Resource().V1beta2().DeviceTaintRules().Informer()}, nil case resourcev1beta2.SchemeGroupVersion.WithResource("resourceclaims"): return &genericInformer{resource: resource.GroupResource(), informer: f.Resource().V1beta2().ResourceClaims().Informer()}, nil case resourcev1beta2.SchemeGroupVersion.WithResource("resourceclaimtemplates"): @@ -423,11 +421,11 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case schedulingv1.SchemeGroupVersion.WithResource("priorityclasses"): return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1().PriorityClasses().Informer()}, nil - // Group=scheduling.k8s.io, Version=v1alpha1 - case schedulingv1alpha1.SchemeGroupVersion.WithResource("priorityclasses"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1alpha1().PriorityClasses().Informer()}, nil - case schedulingv1alpha1.SchemeGroupVersion.WithResource("workloads"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1alpha1().Workloads().Informer()}, nil + // Group=scheduling.k8s.io, Version=v1alpha2 + case schedulingv1alpha2.SchemeGroupVersion.WithResource("podgroups"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1alpha2().PodGroups().Informer()}, nil + case schedulingv1alpha2.SchemeGroupVersion.WithResource("workloads"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().V1alpha2().Workloads().Informer()}, nil // Group=scheduling.k8s.io, Version=v1beta1 case schedulingv1beta1.SchemeGroupVersion.WithResource("priorityclasses"): diff --git a/staging/src/k8s.io/client-go/informers/internalinterfaces/factory_interfaces.go b/staging/src/k8s.io/client-go/informers/internalinterfaces/factory_interfaces.go index b00ed70cfdb71..8740840d73efc 100644 --- a/staging/src/k8s.io/client-go/informers/internalinterfaces/factory_interfaces.go +++ b/staging/src/k8s.io/client-go/informers/internalinterfaces/factory_interfaces.go @@ -34,7 +34,26 @@ type NewInformerFunc func(kubernetes.Interface, time.Duration) cache.SharedIndex type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer + InformerName() *cache.InformerName } // TweakListOptionsFunc is a function that transforms a v1.ListOptions. type TweakListOptionsFunc func(*v1.ListOptions) + +// InformerOptions holds the options for creating an informer. +type InformerOptions struct { + // ResyncPeriod is the resync period for this informer. + // If not set, defaults to 0 (no resync). + ResyncPeriod time.Duration + + // Indexers are the indexers for this informer. + Indexers cache.Indexers + + // InformerName is used to uniquely identify this informer for metrics. + // If not set, metrics will not be published for this informer. + // Use cache.NewInformerName() to create an InformerName at startup. + InformerName *cache.InformerName + + // TweakListOptions is an optional function to modify the list options. + TweakListOptions TweakListOptionsFunc +} diff --git a/staging/src/k8s.io/client-go/informers/networking/v1/ingress.go b/staging/src/k8s.io/client-go/informers/networking/v1/ingress.go index fa7252dfa777a..65be7320d1b49 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1/ingress.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1/ingress.go @@ -25,6 +25,7 @@ import ( apinetworkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type ingressInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIngressInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIngressInformer(client, namespace, resyncPeriod, indexers, nil) + return NewIngressInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIngressInformer constructs a new informer for Ingress type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIngressInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIngressInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIngressInformerWithOptions constructs a new informer for Ingress type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIngressInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().Ingresses(namespace).List(context.Background(), options) + return client.NetworkingV1().Ingresses(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().Ingresses(namespace).Watch(context.Background(), options) + return client.NetworkingV1().Ingresses(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().Ingresses(namespace).List(ctx, options) + return client.NetworkingV1().Ingresses(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().Ingresses(namespace).Watch(ctx, options) + return client.NetworkingV1().Ingresses(namespace).Watch(ctx, opts) }, }, client), &apinetworkingv1.Ingress{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *ingressInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIngressInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIngressInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *ingressInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1/ingressclass.go b/staging/src/k8s.io/client-go/informers/networking/v1/ingressclass.go index bd6696a37f9bc..bcb118f991a34 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1/ingressclass.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1/ingressclass.go @@ -25,6 +25,7 @@ import ( apinetworkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type ingressClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIngressClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIngressClassInformer(client, resyncPeriod, indexers, nil) + return NewIngressClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIngressClassInformer constructs a new informer for IngressClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIngressClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIngressClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIngressClassInformerWithOptions constructs a new informer for IngressClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIngressClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingressclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IngressClasses().List(context.Background(), options) + return client.NetworkingV1().IngressClasses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IngressClasses().Watch(context.Background(), options) + return client.NetworkingV1().IngressClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IngressClasses().List(ctx, options) + return client.NetworkingV1().IngressClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IngressClasses().Watch(ctx, options) + return client.NetworkingV1().IngressClasses().Watch(ctx, opts) }, }, client), &apinetworkingv1.IngressClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *ingressClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIngressClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIngressClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *ingressClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1/ipaddress.go b/staging/src/k8s.io/client-go/informers/networking/v1/ipaddress.go index 8ab3ac5d63b99..ea4bb280c8a53 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1/ipaddress.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1/ipaddress.go @@ -25,6 +25,7 @@ import ( apinetworkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type iPAddressInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIPAddressInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIPAddressInformer(client, resyncPeriod, indexers, nil) + return NewIPAddressInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIPAddressInformer constructs a new informer for IPAddress type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIPAddressInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIPAddressInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIPAddressInformerWithOptions constructs a new informer for IPAddress type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIPAddressInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ipaddresss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IPAddresses().List(context.Background(), options) + return client.NetworkingV1().IPAddresses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IPAddresses().Watch(context.Background(), options) + return client.NetworkingV1().IPAddresses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IPAddresses().List(ctx, options) + return client.NetworkingV1().IPAddresses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().IPAddresses().Watch(ctx, options) + return client.NetworkingV1().IPAddresses().Watch(ctx, opts) }, }, client), &apinetworkingv1.IPAddress{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *iPAddressInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIPAddressInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIPAddressInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *iPAddressInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1/networkpolicy.go b/staging/src/k8s.io/client-go/informers/networking/v1/networkpolicy.go index 1b39b0e6cf863..237fe6266dab6 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1/networkpolicy.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1/networkpolicy.go @@ -25,6 +25,7 @@ import ( apinetworkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type networkPolicyInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewNetworkPolicyInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredNetworkPolicyInformer(client, namespace, resyncPeriod, indexers, nil) + return NewNetworkPolicyInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredNetworkPolicyInformer constructs a new informer for NetworkPolicy type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredNetworkPolicyInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewNetworkPolicyInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewNetworkPolicyInformerWithOptions constructs a new informer for NetworkPolicy type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewNetworkPolicyInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "networkpolicys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().NetworkPolicies(namespace).List(context.Background(), options) + return client.NetworkingV1().NetworkPolicies(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().NetworkPolicies(namespace).Watch(context.Background(), options) + return client.NetworkingV1().NetworkPolicies(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().NetworkPolicies(namespace).List(ctx, options) + return client.NetworkingV1().NetworkPolicies(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().NetworkPolicies(namespace).Watch(ctx, options) + return client.NetworkingV1().NetworkPolicies(namespace).Watch(ctx, opts) }, }, client), &apinetworkingv1.NetworkPolicy{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *networkPolicyInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredNetworkPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewNetworkPolicyInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *networkPolicyInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1/servicecidr.go b/staging/src/k8s.io/client-go/informers/networking/v1/servicecidr.go index 299e40611eb4e..ee1810f817ff3 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1/servicecidr.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1/servicecidr.go @@ -25,6 +25,7 @@ import ( apinetworkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type serviceCIDRInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewServiceCIDRInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredServiceCIDRInformer(client, resyncPeriod, indexers, nil) + return NewServiceCIDRInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredServiceCIDRInformer constructs a new informer for ServiceCIDR type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredServiceCIDRInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewServiceCIDRInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewServiceCIDRInformerWithOptions constructs a new informer for ServiceCIDR type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewServiceCIDRInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "servicecidrs"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().ServiceCIDRs().List(context.Background(), options) + return client.NetworkingV1().ServiceCIDRs().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().ServiceCIDRs().Watch(context.Background(), options) + return client.NetworkingV1().ServiceCIDRs().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().ServiceCIDRs().List(ctx, options) + return client.NetworkingV1().ServiceCIDRs().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1().ServiceCIDRs().Watch(ctx, options) + return client.NetworkingV1().ServiceCIDRs().Watch(ctx, opts) }, }, client), &apinetworkingv1.ServiceCIDR{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *serviceCIDRInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredServiceCIDRInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewServiceCIDRInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *serviceCIDRInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingress.go b/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingress.go index 3e279cb544401..ba35b8ac7036f 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingress.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingress.go @@ -25,6 +25,7 @@ import ( apinetworkingv1beta1 "k8s.io/api/networking/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type ingressInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIngressInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIngressInformer(client, namespace, resyncPeriod, indexers, nil) + return NewIngressInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIngressInformer constructs a new informer for Ingress type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIngressInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIngressInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIngressInformerWithOptions constructs a new informer for Ingress type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIngressInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "ingresss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().Ingresses(namespace).List(context.Background(), options) + return client.NetworkingV1beta1().Ingresses(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().Ingresses(namespace).Watch(context.Background(), options) + return client.NetworkingV1beta1().Ingresses(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().Ingresses(namespace).List(ctx, options) + return client.NetworkingV1beta1().Ingresses(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().Ingresses(namespace).Watch(ctx, options) + return client.NetworkingV1beta1().Ingresses(namespace).Watch(ctx, opts) }, }, client), &apinetworkingv1beta1.Ingress{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *ingressInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIngressInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIngressInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *ingressInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingressclass.go b/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingressclass.go index 296abfa8c4c9f..97d761a633fe8 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingressclass.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1beta1/ingressclass.go @@ -25,6 +25,7 @@ import ( apinetworkingv1beta1 "k8s.io/api/networking/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type ingressClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIngressClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIngressClassInformer(client, resyncPeriod, indexers, nil) + return NewIngressClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIngressClassInformer constructs a new informer for IngressClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIngressClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIngressClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIngressClassInformerWithOptions constructs a new informer for IngressClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIngressClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "ingressclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IngressClasses().List(context.Background(), options) + return client.NetworkingV1beta1().IngressClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IngressClasses().Watch(context.Background(), options) + return client.NetworkingV1beta1().IngressClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IngressClasses().List(ctx, options) + return client.NetworkingV1beta1().IngressClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IngressClasses().Watch(ctx, options) + return client.NetworkingV1beta1().IngressClasses().Watch(ctx, opts) }, }, client), &apinetworkingv1beta1.IngressClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *ingressClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIngressClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIngressClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *ingressClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1beta1/ipaddress.go b/staging/src/k8s.io/client-go/informers/networking/v1beta1/ipaddress.go index 94f785f10dd45..fd943923935a9 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1beta1/ipaddress.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1beta1/ipaddress.go @@ -25,6 +25,7 @@ import ( apinetworkingv1beta1 "k8s.io/api/networking/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type iPAddressInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewIPAddressInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIPAddressInformer(client, resyncPeriod, indexers, nil) + return NewIPAddressInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredIPAddressInformer constructs a new informer for IPAddress type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredIPAddressInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewIPAddressInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewIPAddressInformerWithOptions constructs a new informer for IPAddress type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIPAddressInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "ipaddresss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IPAddresses().List(context.Background(), options) + return client.NetworkingV1beta1().IPAddresses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IPAddresses().Watch(context.Background(), options) + return client.NetworkingV1beta1().IPAddresses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IPAddresses().List(ctx, options) + return client.NetworkingV1beta1().IPAddresses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().IPAddresses().Watch(ctx, options) + return client.NetworkingV1beta1().IPAddresses().Watch(ctx, opts) }, }, client), &apinetworkingv1beta1.IPAddress{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *iPAddressInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIPAddressInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewIPAddressInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *iPAddressInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/networking/v1beta1/servicecidr.go b/staging/src/k8s.io/client-go/informers/networking/v1beta1/servicecidr.go index 67776463965ab..6fe8c3bd11929 100644 --- a/staging/src/k8s.io/client-go/informers/networking/v1beta1/servicecidr.go +++ b/staging/src/k8s.io/client-go/informers/networking/v1beta1/servicecidr.go @@ -25,6 +25,7 @@ import ( apinetworkingv1beta1 "k8s.io/api/networking/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type serviceCIDRInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewServiceCIDRInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredServiceCIDRInformer(client, resyncPeriod, indexers, nil) + return NewServiceCIDRInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredServiceCIDRInformer constructs a new informer for ServiceCIDR type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredServiceCIDRInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewServiceCIDRInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewServiceCIDRInformerWithOptions constructs a new informer for ServiceCIDR type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewServiceCIDRInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "servicecidrs"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().ServiceCIDRs().List(context.Background(), options) + return client.NetworkingV1beta1().ServiceCIDRs().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().ServiceCIDRs().Watch(context.Background(), options) + return client.NetworkingV1beta1().ServiceCIDRs().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().ServiceCIDRs().List(ctx, options) + return client.NetworkingV1beta1().ServiceCIDRs().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NetworkingV1beta1().ServiceCIDRs().Watch(ctx, options) + return client.NetworkingV1beta1().ServiceCIDRs().Watch(ctx, opts) }, }, client), &apinetworkingv1beta1.ServiceCIDR{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *serviceCIDRInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredServiceCIDRInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewServiceCIDRInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *serviceCIDRInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/node/v1/runtimeclass.go b/staging/src/k8s.io/client-go/informers/node/v1/runtimeclass.go index d794c569517ec..3b7d4a25d2c2f 100644 --- a/staging/src/k8s.io/client-go/informers/node/v1/runtimeclass.go +++ b/staging/src/k8s.io/client-go/informers/node/v1/runtimeclass.go @@ -25,6 +25,7 @@ import ( apinodev1 "k8s.io/api/node/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type runtimeClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRuntimeClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRuntimeClassInformer(client, resyncPeriod, indexers, nil) + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRuntimeClassInformer constructs a new informer for RuntimeClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRuntimeClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRuntimeClassInformerWithOptions constructs a new informer for RuntimeClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRuntimeClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "node.k8s.io", Version: "v1", Resource: "runtimeclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1().RuntimeClasses().List(context.Background(), options) + return client.NodeV1().RuntimeClasses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1().RuntimeClasses().Watch(context.Background(), options) + return client.NodeV1().RuntimeClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1().RuntimeClasses().List(ctx, options) + return client.NodeV1().RuntimeClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1().RuntimeClasses().Watch(ctx, options) + return client.NodeV1().RuntimeClasses().Watch(ctx, opts) }, }, client), &apinodev1.RuntimeClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *runtimeClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRuntimeClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *runtimeClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/node/v1alpha1/runtimeclass.go b/staging/src/k8s.io/client-go/informers/node/v1alpha1/runtimeclass.go index 01718ac102f35..1907a122d2556 100644 --- a/staging/src/k8s.io/client-go/informers/node/v1alpha1/runtimeclass.go +++ b/staging/src/k8s.io/client-go/informers/node/v1alpha1/runtimeclass.go @@ -25,6 +25,7 @@ import ( apinodev1alpha1 "k8s.io/api/node/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type runtimeClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRuntimeClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRuntimeClassInformer(client, resyncPeriod, indexers, nil) + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRuntimeClassInformer constructs a new informer for RuntimeClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRuntimeClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRuntimeClassInformerWithOptions constructs a new informer for RuntimeClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRuntimeClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "node.k8s.io", Version: "v1alpha1", Resource: "runtimeclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1alpha1().RuntimeClasses().List(context.Background(), options) + return client.NodeV1alpha1().RuntimeClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1alpha1().RuntimeClasses().Watch(context.Background(), options) + return client.NodeV1alpha1().RuntimeClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1alpha1().RuntimeClasses().List(ctx, options) + return client.NodeV1alpha1().RuntimeClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1alpha1().RuntimeClasses().Watch(ctx, options) + return client.NodeV1alpha1().RuntimeClasses().Watch(ctx, opts) }, }, client), &apinodev1alpha1.RuntimeClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *runtimeClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRuntimeClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *runtimeClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/node/v1beta1/runtimeclass.go b/staging/src/k8s.io/client-go/informers/node/v1beta1/runtimeclass.go index 09390f58b45f5..ca7e9307ac092 100644 --- a/staging/src/k8s.io/client-go/informers/node/v1beta1/runtimeclass.go +++ b/staging/src/k8s.io/client-go/informers/node/v1beta1/runtimeclass.go @@ -25,6 +25,7 @@ import ( apinodev1beta1 "k8s.io/api/node/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type runtimeClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRuntimeClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRuntimeClassInformer(client, resyncPeriod, indexers, nil) + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRuntimeClassInformer constructs a new informer for RuntimeClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRuntimeClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRuntimeClassInformerWithOptions constructs a new informer for RuntimeClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRuntimeClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "node.k8s.io", Version: "v1beta1", Resource: "runtimeclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1beta1().RuntimeClasses().List(context.Background(), options) + return client.NodeV1beta1().RuntimeClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1beta1().RuntimeClasses().Watch(context.Background(), options) + return client.NodeV1beta1().RuntimeClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1beta1().RuntimeClasses().List(ctx, options) + return client.NodeV1beta1().RuntimeClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.NodeV1beta1().RuntimeClasses().Watch(ctx, options) + return client.NodeV1beta1().RuntimeClasses().Watch(ctx, opts) }, }, client), &apinodev1beta1.RuntimeClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *runtimeClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRuntimeClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRuntimeClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *runtimeClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/policy/v1/poddisruptionbudget.go b/staging/src/k8s.io/client-go/informers/policy/v1/poddisruptionbudget.go index 10e45d851e56f..2945e543d711c 100644 --- a/staging/src/k8s.io/client-go/informers/policy/v1/poddisruptionbudget.go +++ b/staging/src/k8s.io/client-go/informers/policy/v1/poddisruptionbudget.go @@ -25,6 +25,7 @@ import ( apipolicyv1 "k8s.io/api/policy/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type podDisruptionBudgetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPodDisruptionBudgetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPodDisruptionBudgetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewPodDisruptionBudgetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPodDisruptionBudgetInformer constructs a new informer for PodDisruptionBudget type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPodDisruptionBudgetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPodDisruptionBudgetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPodDisruptionBudgetInformerWithOptions constructs a new informer for PodDisruptionBudget type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodDisruptionBudgetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "policy", Version: "v1", Resource: "poddisruptionbudgets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1().PodDisruptionBudgets(namespace).List(context.Background(), options) + return client.PolicyV1().PodDisruptionBudgets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1().PodDisruptionBudgets(namespace).Watch(context.Background(), options) + return client.PolicyV1().PodDisruptionBudgets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1().PodDisruptionBudgets(namespace).List(ctx, options) + return client.PolicyV1().PodDisruptionBudgets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1().PodDisruptionBudgets(namespace).Watch(ctx, options) + return client.PolicyV1().PodDisruptionBudgets(namespace).Watch(ctx, opts) }, }, client), &apipolicyv1.PodDisruptionBudget{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *podDisruptionBudgetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPodDisruptionBudgetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPodDisruptionBudgetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *podDisruptionBudgetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/policy/v1beta1/poddisruptionbudget.go b/staging/src/k8s.io/client-go/informers/policy/v1beta1/poddisruptionbudget.go index a45d753eb6704..70c79683fe07f 100644 --- a/staging/src/k8s.io/client-go/informers/policy/v1beta1/poddisruptionbudget.go +++ b/staging/src/k8s.io/client-go/informers/policy/v1beta1/poddisruptionbudget.go @@ -25,6 +25,7 @@ import ( apipolicyv1beta1 "k8s.io/api/policy/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type podDisruptionBudgetInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPodDisruptionBudgetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPodDisruptionBudgetInformer(client, namespace, resyncPeriod, indexers, nil) + return NewPodDisruptionBudgetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPodDisruptionBudgetInformer constructs a new informer for PodDisruptionBudget type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPodDisruptionBudgetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPodDisruptionBudgetInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPodDisruptionBudgetInformerWithOptions constructs a new informer for PodDisruptionBudget type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodDisruptionBudgetInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "policy", Version: "v1beta1", Resource: "poddisruptionbudgets"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1beta1().PodDisruptionBudgets(namespace).List(context.Background(), options) + return client.PolicyV1beta1().PodDisruptionBudgets(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1beta1().PodDisruptionBudgets(namespace).Watch(context.Background(), options) + return client.PolicyV1beta1().PodDisruptionBudgets(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1beta1().PodDisruptionBudgets(namespace).List(ctx, options) + return client.PolicyV1beta1().PodDisruptionBudgets(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.PolicyV1beta1().PodDisruptionBudgets(namespace).Watch(ctx, options) + return client.PolicyV1beta1().PodDisruptionBudgets(namespace).Watch(ctx, opts) }, }, client), &apipolicyv1beta1.PodDisruptionBudget{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *podDisruptionBudgetInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPodDisruptionBudgetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPodDisruptionBudgetInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *podDisruptionBudgetInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrole.go b/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrole.go index 02dab4f9a6c71..d938be79ee380 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrole.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrole.go @@ -25,6 +25,7 @@ import ( apirbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterRoleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterRoleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterRoleInformer(client, resyncPeriod, indexers, nil) + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterRoleInformer constructs a new informer for ClusterRole type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterRoleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterRoleInformerWithOptions constructs a new informer for ClusterRole type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterroles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoles().List(context.Background(), options) + return client.RbacV1().ClusterRoles().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoles().Watch(context.Background(), options) + return client.RbacV1().ClusterRoles().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoles().List(ctx, options) + return client.RbacV1().ClusterRoles().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoles().Watch(ctx, options) + return client.RbacV1().ClusterRoles().Watch(ctx, opts) }, }, client), &apirbacv1.ClusterRole{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterRoleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterRoleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrolebinding.go b/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrolebinding.go index 182009da85451..72fe655da87a0 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrolebinding.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1/clusterrolebinding.go @@ -25,6 +25,7 @@ import ( apirbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterRoleBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterRoleBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, indexers, nil) + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterRoleBindingInformer constructs a new informer for ClusterRoleBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterRoleBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterRoleBindingInformerWithOptions constructs a new informer for ClusterRoleBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrolebindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoleBindings().List(context.Background(), options) + return client.RbacV1().ClusterRoleBindings().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoleBindings().Watch(context.Background(), options) + return client.RbacV1().ClusterRoleBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoleBindings().List(ctx, options) + return client.RbacV1().ClusterRoleBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().ClusterRoleBindings().Watch(ctx, options) + return client.RbacV1().ClusterRoleBindings().Watch(ctx, opts) }, }, client), &apirbacv1.ClusterRoleBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterRoleBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterRoleBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1/role.go b/staging/src/k8s.io/client-go/informers/rbac/v1/role.go index 5a0a1f8003559..f66decf0401a1 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1/role.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1/role.go @@ -25,6 +25,7 @@ import ( apirbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type roleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRoleInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRoleInformer(client, namespace, resyncPeriod, indexers, nil) + return NewRoleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRoleInformer constructs a new informer for Role type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRoleInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRoleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRoleInformerWithOptions constructs a new informer for Role type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "roles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().Roles(namespace).List(context.Background(), options) + return client.RbacV1().Roles(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().Roles(namespace).Watch(context.Background(), options) + return client.RbacV1().Roles(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().Roles(namespace).List(ctx, options) + return client.RbacV1().Roles(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().Roles(namespace).Watch(ctx, options) + return client.RbacV1().Roles(namespace).Watch(ctx, opts) }, }, client), &apirbacv1.Role{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *roleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRoleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRoleInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *roleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1/rolebinding.go b/staging/src/k8s.io/client-go/informers/rbac/v1/rolebinding.go index 0f0eec60f84a9..c17b1c474fedd 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1/rolebinding.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1/rolebinding.go @@ -25,6 +25,7 @@ import ( apirbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type roleBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRoleBindingInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRoleBindingInformer(client, namespace, resyncPeriod, indexers, nil) + return NewRoleBindingInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRoleBindingInformer constructs a new informer for RoleBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRoleBindingInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRoleBindingInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRoleBindingInformerWithOptions constructs a new informer for RoleBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleBindingInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().RoleBindings(namespace).List(context.Background(), options) + return client.RbacV1().RoleBindings(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().RoleBindings(namespace).Watch(context.Background(), options) + return client.RbacV1().RoleBindings(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().RoleBindings(namespace).List(ctx, options) + return client.RbacV1().RoleBindings(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1().RoleBindings(namespace).Watch(ctx, options) + return client.RbacV1().RoleBindings(namespace).Watch(ctx, opts) }, }, client), &apirbacv1.RoleBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *roleBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRoleBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRoleBindingInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *roleBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrole.go b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrole.go index e1b25180b9579..cac4ba05b9630 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrole.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrole.go @@ -25,6 +25,7 @@ import ( apirbacv1alpha1 "k8s.io/api/rbac/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterRoleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterRoleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterRoleInformer(client, resyncPeriod, indexers, nil) + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterRoleInformer constructs a new informer for ClusterRole type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterRoleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterRoleInformerWithOptions constructs a new informer for ClusterRole type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Resource: "clusterroles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoles().List(context.Background(), options) + return client.RbacV1alpha1().ClusterRoles().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoles().Watch(context.Background(), options) + return client.RbacV1alpha1().ClusterRoles().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoles().List(ctx, options) + return client.RbacV1alpha1().ClusterRoles().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoles().Watch(ctx, options) + return client.RbacV1alpha1().ClusterRoles().Watch(ctx, opts) }, }, client), &apirbacv1alpha1.ClusterRole{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterRoleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterRoleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrolebinding.go b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrolebinding.go index d61e8b2930cf7..944e16cef6465 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrolebinding.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/clusterrolebinding.go @@ -25,6 +25,7 @@ import ( apirbacv1alpha1 "k8s.io/api/rbac/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterRoleBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterRoleBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, indexers, nil) + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterRoleBindingInformer constructs a new informer for ClusterRoleBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterRoleBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterRoleBindingInformerWithOptions constructs a new informer for ClusterRoleBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Resource: "clusterrolebindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoleBindings().List(context.Background(), options) + return client.RbacV1alpha1().ClusterRoleBindings().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoleBindings().Watch(context.Background(), options) + return client.RbacV1alpha1().ClusterRoleBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoleBindings().List(ctx, options) + return client.RbacV1alpha1().ClusterRoleBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().ClusterRoleBindings().Watch(ctx, options) + return client.RbacV1alpha1().ClusterRoleBindings().Watch(ctx, opts) }, }, client), &apirbacv1alpha1.ClusterRoleBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterRoleBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterRoleBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/role.go b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/role.go index 497bccbba1a67..c4e2a6cf6c28a 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/role.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/role.go @@ -25,6 +25,7 @@ import ( apirbacv1alpha1 "k8s.io/api/rbac/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type roleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRoleInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRoleInformer(client, namespace, resyncPeriod, indexers, nil) + return NewRoleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRoleInformer constructs a new informer for Role type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRoleInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRoleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRoleInformerWithOptions constructs a new informer for Role type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Resource: "roles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().Roles(namespace).List(context.Background(), options) + return client.RbacV1alpha1().Roles(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().Roles(namespace).Watch(context.Background(), options) + return client.RbacV1alpha1().Roles(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().Roles(namespace).List(ctx, options) + return client.RbacV1alpha1().Roles(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().Roles(namespace).Watch(ctx, options) + return client.RbacV1alpha1().Roles(namespace).Watch(ctx, opts) }, }, client), &apirbacv1alpha1.Role{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *roleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRoleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRoleInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *roleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/rolebinding.go b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/rolebinding.go index 0d8b390b30bbc..ef1e246f5b5c7 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/rolebinding.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1alpha1/rolebinding.go @@ -25,6 +25,7 @@ import ( apirbacv1alpha1 "k8s.io/api/rbac/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type roleBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRoleBindingInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRoleBindingInformer(client, namespace, resyncPeriod, indexers, nil) + return NewRoleBindingInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRoleBindingInformer constructs a new informer for RoleBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRoleBindingInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRoleBindingInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRoleBindingInformerWithOptions constructs a new informer for RoleBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleBindingInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Resource: "rolebindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().RoleBindings(namespace).List(context.Background(), options) + return client.RbacV1alpha1().RoleBindings(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().RoleBindings(namespace).Watch(context.Background(), options) + return client.RbacV1alpha1().RoleBindings(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().RoleBindings(namespace).List(ctx, options) + return client.RbacV1alpha1().RoleBindings(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1alpha1().RoleBindings(namespace).Watch(ctx, options) + return client.RbacV1alpha1().RoleBindings(namespace).Watch(ctx, opts) }, }, client), &apirbacv1alpha1.RoleBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *roleBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRoleBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRoleBindingInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *roleBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrole.go b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrole.go index 1cc1a0dd0017b..df87d468cac8e 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrole.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrole.go @@ -25,6 +25,7 @@ import ( apirbacv1beta1 "k8s.io/api/rbac/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterRoleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterRoleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterRoleInformer(client, resyncPeriod, indexers, nil) + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterRoleInformer constructs a new informer for ClusterRole type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterRoleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterRoleInformerWithOptions constructs a new informer for ClusterRole type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Resource: "clusterroles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoles().List(context.Background(), options) + return client.RbacV1beta1().ClusterRoles().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoles().Watch(context.Background(), options) + return client.RbacV1beta1().ClusterRoles().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoles().List(ctx, options) + return client.RbacV1beta1().ClusterRoles().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoles().Watch(ctx, options) + return client.RbacV1beta1().ClusterRoles().Watch(ctx, opts) }, }, client), &apirbacv1beta1.ClusterRole{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterRoleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterRoleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterRoleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrolebinding.go b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrolebinding.go index a96ed0889a16e..9f14c10dafa6e 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrolebinding.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/clusterrolebinding.go @@ -25,6 +25,7 @@ import ( apirbacv1beta1 "k8s.io/api/rbac/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type clusterRoleBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewClusterRoleBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, indexers, nil) + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredClusterRoleBindingInformer constructs a new informer for ClusterRoleBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredClusterRoleBindingInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewClusterRoleBindingInformerWithOptions constructs a new informer for ClusterRoleBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleBindingInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Resource: "clusterrolebindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoleBindings().List(context.Background(), options) + return client.RbacV1beta1().ClusterRoleBindings().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoleBindings().Watch(context.Background(), options) + return client.RbacV1beta1().ClusterRoleBindings().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoleBindings().List(ctx, options) + return client.RbacV1beta1().ClusterRoleBindings().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().ClusterRoleBindings().Watch(ctx, options) + return client.RbacV1beta1().ClusterRoleBindings().Watch(ctx, opts) }, }, client), &apirbacv1beta1.ClusterRoleBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *clusterRoleBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewClusterRoleBindingInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *clusterRoleBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/role.go b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/role.go index 922ac5dc5cf4d..4dd948f1674eb 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/role.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/role.go @@ -25,6 +25,7 @@ import ( apirbacv1beta1 "k8s.io/api/rbac/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type roleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRoleInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRoleInformer(client, namespace, resyncPeriod, indexers, nil) + return NewRoleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRoleInformer constructs a new informer for Role type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRoleInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRoleInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRoleInformerWithOptions constructs a new informer for Role type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Resource: "roles"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().Roles(namespace).List(context.Background(), options) + return client.RbacV1beta1().Roles(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().Roles(namespace).Watch(context.Background(), options) + return client.RbacV1beta1().Roles(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().Roles(namespace).List(ctx, options) + return client.RbacV1beta1().Roles(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().Roles(namespace).Watch(ctx, options) + return client.RbacV1beta1().Roles(namespace).Watch(ctx, opts) }, }, client), &apirbacv1beta1.Role{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *roleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRoleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRoleInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *roleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/rolebinding.go b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/rolebinding.go index 5fcd488493730..882d59471eb29 100644 --- a/staging/src/k8s.io/client-go/informers/rbac/v1beta1/rolebinding.go +++ b/staging/src/k8s.io/client-go/informers/rbac/v1beta1/rolebinding.go @@ -25,6 +25,7 @@ import ( apirbacv1beta1 "k8s.io/api/rbac/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type roleBindingInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewRoleBindingInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRoleBindingInformer(client, namespace, resyncPeriod, indexers, nil) + return NewRoleBindingInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredRoleBindingInformer constructs a new informer for RoleBinding type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredRoleBindingInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewRoleBindingInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewRoleBindingInformerWithOptions constructs a new informer for RoleBinding type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleBindingInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Resource: "rolebindings"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().RoleBindings(namespace).List(context.Background(), options) + return client.RbacV1beta1().RoleBindings(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().RoleBindings(namespace).Watch(context.Background(), options) + return client.RbacV1beta1().RoleBindings(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().RoleBindings(namespace).List(ctx, options) + return client.RbacV1beta1().RoleBindings(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.RbacV1beta1().RoleBindings(namespace).Watch(ctx, options) + return client.RbacV1beta1().RoleBindings(namespace).Watch(ctx, opts) }, }, client), &apirbacv1beta1.RoleBinding{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *roleBindingInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRoleBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewRoleBindingInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *roleBindingInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1/deviceclass.go b/staging/src/k8s.io/client-go/informers/resource/v1/deviceclass.go index 867bb7b35e87b..bacc65a28f9df 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1/deviceclass.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1/deviceclass.go @@ -25,6 +25,7 @@ import ( apiresourcev1 "k8s.io/api/resource/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type deviceClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeviceClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeviceClassInformer(client, resyncPeriod, indexers, nil) + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeviceClassInformer constructs a new informer for DeviceClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeviceClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeviceClassInformerWithOptions constructs a new informer for DeviceClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeviceClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1", Resource: "deviceclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().DeviceClasses().List(context.Background(), options) + return client.ResourceV1().DeviceClasses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().DeviceClasses().Watch(context.Background(), options) + return client.ResourceV1().DeviceClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().DeviceClasses().List(ctx, options) + return client.ResourceV1().DeviceClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().DeviceClasses().Watch(ctx, options) + return client.ResourceV1().DeviceClasses().Watch(ctx, opts) }, }, client), &apiresourcev1.DeviceClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deviceClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeviceClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deviceClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaim.go b/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaim.go index 64deaed694e94..996cc77f61db8 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaim.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaim.go @@ -25,6 +25,7 @@ import ( apiresourcev1 "k8s.io/api/resource/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceClaimInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceClaimInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceClaimInformer constructs a new informer for ResourceClaim type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceClaimInformerWithOptions constructs a new informer for ResourceClaim type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceClaimInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1", Resource: "resourceclaims"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaims(namespace).List(context.Background(), options) + return client.ResourceV1().ResourceClaims(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaims(namespace).Watch(context.Background(), options) + return client.ResourceV1().ResourceClaims(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaims(namespace).List(ctx, options) + return client.ResourceV1().ResourceClaims(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaims(namespace).Watch(ctx, options) + return client.ResourceV1().ResourceClaims(namespace).Watch(ctx, opts) }, }, client), &apiresourcev1.ResourceClaim{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceClaimInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceClaimInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceClaimInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceClaimInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaimtemplate.go b/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaimtemplate.go index 131e159ef9a3c..e562314dc1205 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaimtemplate.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1/resourceclaimtemplate.go @@ -25,6 +25,7 @@ import ( apiresourcev1 "k8s.io/api/resource/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceClaimTemplateInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceClaimTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceClaimTemplateInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceClaimTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceClaimTemplateInformer constructs a new informer for ResourceClaimTemplate type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceClaimTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceClaimTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceClaimTemplateInformerWithOptions constructs a new informer for ResourceClaimTemplate type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceClaimTemplateInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1", Resource: "resourceclaimtemplates"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaimTemplates(namespace).List(context.Background(), options) + return client.ResourceV1().ResourceClaimTemplates(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaimTemplates(namespace).Watch(context.Background(), options) + return client.ResourceV1().ResourceClaimTemplates(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaimTemplates(namespace).List(ctx, options) + return client.ResourceV1().ResourceClaimTemplates(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceClaimTemplates(namespace).Watch(ctx, options) + return client.ResourceV1().ResourceClaimTemplates(namespace).Watch(ctx, opts) }, }, client), &apiresourcev1.ResourceClaimTemplate{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceClaimTemplateInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceClaimTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceClaimTemplateInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceClaimTemplateInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1/resourceslice.go b/staging/src/k8s.io/client-go/informers/resource/v1/resourceslice.go index a01abf0739603..85c877f80389b 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1/resourceslice.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1/resourceslice.go @@ -25,6 +25,7 @@ import ( apiresourcev1 "k8s.io/api/resource/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type resourceSliceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceSliceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceSliceInformer(client, resyncPeriod, indexers, nil) + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceSliceInformer constructs a new informer for ResourceSlice type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceSliceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceSliceInformerWithOptions constructs a new informer for ResourceSlice type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceSliceInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1", Resource: "resourceslices"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceSlices().List(context.Background(), options) + return client.ResourceV1().ResourceSlices().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceSlices().Watch(context.Background(), options) + return client.ResourceV1().ResourceSlices().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceSlices().List(ctx, options) + return client.ResourceV1().ResourceSlices().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1().ResourceSlices().Watch(ctx, options) + return client.ResourceV1().ResourceSlices().Watch(ctx, opts) }, }, client), &apiresourcev1.ResourceSlice{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceSliceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceSliceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceSliceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1alpha3/devicetaintrule.go b/staging/src/k8s.io/client-go/informers/resource/v1alpha3/devicetaintrule.go index 0fdc88ac59650..0b0029ab548df 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1alpha3/devicetaintrule.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1alpha3/devicetaintrule.go @@ -25,6 +25,7 @@ import ( apiresourcev1alpha3 "k8s.io/api/resource/v1alpha3" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type deviceTaintRuleInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeviceTaintRuleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeviceTaintRuleInformer(client, resyncPeriod, indexers, nil) + return NewDeviceTaintRuleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeviceTaintRuleInformer constructs a new informer for DeviceTaintRule type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeviceTaintRuleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeviceTaintRuleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeviceTaintRuleInformerWithOptions constructs a new informer for DeviceTaintRule type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeviceTaintRuleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1alpha3", Resource: "devicetaintrules"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1alpha3().DeviceTaintRules().List(context.Background(), options) + return client.ResourceV1alpha3().DeviceTaintRules().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1alpha3().DeviceTaintRules().Watch(context.Background(), options) + return client.ResourceV1alpha3().DeviceTaintRules().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1alpha3().DeviceTaintRules().List(ctx, options) + return client.ResourceV1alpha3().DeviceTaintRules().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1alpha3().DeviceTaintRules().Watch(ctx, options) + return client.ResourceV1alpha3().DeviceTaintRules().Watch(ctx, opts) }, }, client), &apiresourcev1alpha3.DeviceTaintRule{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deviceTaintRuleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeviceTaintRuleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeviceTaintRuleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deviceTaintRuleInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1alpha3/interface.go b/staging/src/k8s.io/client-go/informers/resource/v1alpha3/interface.go index 1fbab52b6f7d8..b305681236938 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1alpha3/interface.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1alpha3/interface.go @@ -26,6 +26,8 @@ import ( type Interface interface { // DeviceTaintRules returns a DeviceTaintRuleInformer. DeviceTaintRules() DeviceTaintRuleInformer + // ResourcePoolStatusRequests returns a ResourcePoolStatusRequestInformer. + ResourcePoolStatusRequests() ResourcePoolStatusRequestInformer } type version struct { @@ -43,3 +45,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList func (v *version) DeviceTaintRules() DeviceTaintRuleInformer { return &deviceTaintRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } + +// ResourcePoolStatusRequests returns a ResourcePoolStatusRequestInformer. +func (v *version) ResourcePoolStatusRequests() ResourcePoolStatusRequestInformer { + return &resourcePoolStatusRequestInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/staging/src/k8s.io/client-go/informers/resource/v1alpha3/resourcepoolstatusrequest.go b/staging/src/k8s.io/client-go/informers/resource/v1alpha3/resourcepoolstatusrequest.go new file mode 100644 index 0000000000000..f34d57e13843e --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/resource/v1alpha3/resourcepoolstatusrequest.go @@ -0,0 +1,115 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + context "context" + time "time" + + apiresourcev1alpha3 "k8s.io/api/resource/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + kubernetes "k8s.io/client-go/kubernetes" + resourcev1alpha3 "k8s.io/client-go/listers/resource/v1alpha3" + cache "k8s.io/client-go/tools/cache" +) + +// ResourcePoolStatusRequestInformer provides access to a shared informer and lister for +// ResourcePoolStatusRequests. +type ResourcePoolStatusRequestInformer interface { + Informer() cache.SharedIndexInformer + Lister() resourcev1alpha3.ResourcePoolStatusRequestLister +} + +type resourcePoolStatusRequestInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewResourcePoolStatusRequestInformer constructs a new informer for ResourcePoolStatusRequest type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourcePoolStatusRequestInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewResourcePoolStatusRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredResourcePoolStatusRequestInformer constructs a new informer for ResourcePoolStatusRequest type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredResourcePoolStatusRequestInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewResourcePoolStatusRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourcePoolStatusRequestInformerWithOptions constructs a new informer for ResourcePoolStatusRequest type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourcePoolStatusRequestInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1alpha3", Resource: "resourcepoolstatusrequests"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1alpha3().ResourcePoolStatusRequests().List(context.Background(), opts) + }, + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1alpha3().ResourcePoolStatusRequests().Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1alpha3().ResourcePoolStatusRequests().List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1alpha3().ResourcePoolStatusRequests().Watch(ctx, opts) + }, + }, client), + &apiresourcev1alpha3.ResourcePoolStatusRequest{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *resourcePoolStatusRequestInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewResourcePoolStatusRequestInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *resourcePoolStatusRequestInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiresourcev1alpha3.ResourcePoolStatusRequest{}, f.defaultInformer) +} + +func (f *resourcePoolStatusRequestInformer) Lister() resourcev1alpha3.ResourcePoolStatusRequestLister { + return resourcev1alpha3.NewResourcePoolStatusRequestLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta1/deviceclass.go b/staging/src/k8s.io/client-go/informers/resource/v1beta1/deviceclass.go index cb15fbc08ff9c..0a4d4bea6405a 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta1/deviceclass.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta1/deviceclass.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta1 "k8s.io/api/resource/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type deviceClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeviceClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeviceClassInformer(client, resyncPeriod, indexers, nil) + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeviceClassInformer constructs a new informer for DeviceClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeviceClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeviceClassInformerWithOptions constructs a new informer for DeviceClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeviceClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta1", Resource: "deviceclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().DeviceClasses().List(context.Background(), options) + return client.ResourceV1beta1().DeviceClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().DeviceClasses().Watch(context.Background(), options) + return client.ResourceV1beta1().DeviceClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().DeviceClasses().List(ctx, options) + return client.ResourceV1beta1().DeviceClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().DeviceClasses().Watch(ctx, options) + return client.ResourceV1beta1().DeviceClasses().Watch(ctx, opts) }, }, client), &apiresourcev1beta1.DeviceClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deviceClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeviceClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deviceClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaim.go b/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaim.go index 982c598515a15..9ec1ece317dc9 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaim.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaim.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta1 "k8s.io/api/resource/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceClaimInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceClaimInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceClaimInformer constructs a new informer for ResourceClaim type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceClaimInformerWithOptions constructs a new informer for ResourceClaim type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceClaimInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta1", Resource: "resourceclaims"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaims(namespace).List(context.Background(), options) + return client.ResourceV1beta1().ResourceClaims(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaims(namespace).Watch(context.Background(), options) + return client.ResourceV1beta1().ResourceClaims(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaims(namespace).List(ctx, options) + return client.ResourceV1beta1().ResourceClaims(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaims(namespace).Watch(ctx, options) + return client.ResourceV1beta1().ResourceClaims(namespace).Watch(ctx, opts) }, }, client), &apiresourcev1beta1.ResourceClaim{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceClaimInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceClaimInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceClaimInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceClaimInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaimtemplate.go b/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaimtemplate.go index 51a6381af63a8..b6aa9cb9198bf 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaimtemplate.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceclaimtemplate.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta1 "k8s.io/api/resource/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceClaimTemplateInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceClaimTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceClaimTemplateInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceClaimTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceClaimTemplateInformer constructs a new informer for ResourceClaimTemplate type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceClaimTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceClaimTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceClaimTemplateInformerWithOptions constructs a new informer for ResourceClaimTemplate type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceClaimTemplateInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta1", Resource: "resourceclaimtemplates"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaimTemplates(namespace).List(context.Background(), options) + return client.ResourceV1beta1().ResourceClaimTemplates(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaimTemplates(namespace).Watch(context.Background(), options) + return client.ResourceV1beta1().ResourceClaimTemplates(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaimTemplates(namespace).List(ctx, options) + return client.ResourceV1beta1().ResourceClaimTemplates(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceClaimTemplates(namespace).Watch(ctx, options) + return client.ResourceV1beta1().ResourceClaimTemplates(namespace).Watch(ctx, opts) }, }, client), &apiresourcev1beta1.ResourceClaimTemplate{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceClaimTemplateInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceClaimTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceClaimTemplateInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceClaimTemplateInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceslice.go b/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceslice.go index b6b1cc1fdc536..39e971aec2e17 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceslice.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta1/resourceslice.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta1 "k8s.io/api/resource/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type resourceSliceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceSliceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceSliceInformer(client, resyncPeriod, indexers, nil) + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceSliceInformer constructs a new informer for ResourceSlice type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceSliceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceSliceInformerWithOptions constructs a new informer for ResourceSlice type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceSliceInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta1", Resource: "resourceslices"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceSlices().List(context.Background(), options) + return client.ResourceV1beta1().ResourceSlices().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceSlices().Watch(context.Background(), options) + return client.ResourceV1beta1().ResourceSlices().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceSlices().List(ctx, options) + return client.ResourceV1beta1().ResourceSlices().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta1().ResourceSlices().Watch(ctx, options) + return client.ResourceV1beta1().ResourceSlices().Watch(ctx, opts) }, }, client), &apiresourcev1beta1.ResourceSlice{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceSliceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceSliceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceSliceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta2/deviceclass.go b/staging/src/k8s.io/client-go/informers/resource/v1beta2/deviceclass.go index a940a214f4794..d98b4d658e080 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta2/deviceclass.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta2/deviceclass.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta2 "k8s.io/api/resource/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type deviceClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewDeviceClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredDeviceClassInformer(client, resyncPeriod, indexers, nil) + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredDeviceClassInformer constructs a new informer for DeviceClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredDeviceClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeviceClassInformerWithOptions constructs a new informer for DeviceClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeviceClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta2", Resource: "deviceclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().DeviceClasses().List(context.Background(), options) + return client.ResourceV1beta2().DeviceClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().DeviceClasses().Watch(context.Background(), options) + return client.ResourceV1beta2().DeviceClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().DeviceClasses().List(ctx, options) + return client.ResourceV1beta2().DeviceClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().DeviceClasses().Watch(ctx, options) + return client.ResourceV1beta2().DeviceClasses().Watch(ctx, opts) }, }, client), &apiresourcev1beta2.DeviceClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *deviceClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredDeviceClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewDeviceClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *deviceClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta2/devicetaintrule.go b/staging/src/k8s.io/client-go/informers/resource/v1beta2/devicetaintrule.go new file mode 100644 index 0000000000000..328a9c3939022 --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta2/devicetaintrule.go @@ -0,0 +1,115 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + time "time" + + apiresourcev1beta2 "k8s.io/api/resource/v1beta2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + kubernetes "k8s.io/client-go/kubernetes" + resourcev1beta2 "k8s.io/client-go/listers/resource/v1beta2" + cache "k8s.io/client-go/tools/cache" +) + +// DeviceTaintRuleInformer provides access to a shared informer and lister for +// DeviceTaintRules. +type DeviceTaintRuleInformer interface { + Informer() cache.SharedIndexInformer + Lister() resourcev1beta2.DeviceTaintRuleLister +} + +type deviceTaintRuleInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewDeviceTaintRuleInformer constructs a new informer for DeviceTaintRule type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeviceTaintRuleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewDeviceTaintRuleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredDeviceTaintRuleInformer constructs a new informer for DeviceTaintRule type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredDeviceTaintRuleInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewDeviceTaintRuleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewDeviceTaintRuleInformerWithOptions constructs a new informer for DeviceTaintRule type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewDeviceTaintRuleInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta2", Resource: "devicetaintrules"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1beta2().DeviceTaintRules().List(context.Background(), opts) + }, + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1beta2().DeviceTaintRules().Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1beta2().DeviceTaintRules().List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.ResourceV1beta2().DeviceTaintRules().Watch(ctx, opts) + }, + }, client), + &apiresourcev1beta2.DeviceTaintRule{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *deviceTaintRuleInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewDeviceTaintRuleInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *deviceTaintRuleInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiresourcev1beta2.DeviceTaintRule{}, f.defaultInformer) +} + +func (f *deviceTaintRuleInformer) Lister() resourcev1beta2.DeviceTaintRuleLister { + return resourcev1beta2.NewDeviceTaintRuleLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta2/interface.go b/staging/src/k8s.io/client-go/informers/resource/v1beta2/interface.go index 4627d6f3e2ee4..a05e518e2e8ec 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta2/interface.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta2/interface.go @@ -26,6 +26,8 @@ import ( type Interface interface { // DeviceClasses returns a DeviceClassInformer. DeviceClasses() DeviceClassInformer + // DeviceTaintRules returns a DeviceTaintRuleInformer. + DeviceTaintRules() DeviceTaintRuleInformer // ResourceClaims returns a ResourceClaimInformer. ResourceClaims() ResourceClaimInformer // ResourceClaimTemplates returns a ResourceClaimTemplateInformer. @@ -50,6 +52,11 @@ func (v *version) DeviceClasses() DeviceClassInformer { return &deviceClassInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// DeviceTaintRules returns a DeviceTaintRuleInformer. +func (v *version) DeviceTaintRules() DeviceTaintRuleInformer { + return &deviceTaintRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // ResourceClaims returns a ResourceClaimInformer. func (v *version) ResourceClaims() ResourceClaimInformer { return &resourceClaimInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaim.go b/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaim.go index a5c085c2f0ccd..61dc6db17da5e 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaim.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaim.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta2 "k8s.io/api/resource/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceClaimInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceClaimInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceClaimInformer constructs a new informer for ResourceClaim type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceClaimInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceClaimInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceClaimInformerWithOptions constructs a new informer for ResourceClaim type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceClaimInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta2", Resource: "resourceclaims"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaims(namespace).List(context.Background(), options) + return client.ResourceV1beta2().ResourceClaims(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaims(namespace).Watch(context.Background(), options) + return client.ResourceV1beta2().ResourceClaims(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaims(namespace).List(ctx, options) + return client.ResourceV1beta2().ResourceClaims(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaims(namespace).Watch(ctx, options) + return client.ResourceV1beta2().ResourceClaims(namespace).Watch(ctx, opts) }, }, client), &apiresourcev1beta2.ResourceClaim{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceClaimInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceClaimInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceClaimInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceClaimInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaimtemplate.go b/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaimtemplate.go index 7df9f74dd516d..ee22977a399f9 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaimtemplate.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceclaimtemplate.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta2 "k8s.io/api/resource/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type resourceClaimTemplateInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceClaimTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceClaimTemplateInformer(client, namespace, resyncPeriod, indexers, nil) + return NewResourceClaimTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceClaimTemplateInformer constructs a new informer for ResourceClaimTemplate type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceClaimTemplateInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceClaimTemplateInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceClaimTemplateInformerWithOptions constructs a new informer for ResourceClaimTemplate type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceClaimTemplateInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta2", Resource: "resourceclaimtemplates"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaimTemplates(namespace).List(context.Background(), options) + return client.ResourceV1beta2().ResourceClaimTemplates(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaimTemplates(namespace).Watch(context.Background(), options) + return client.ResourceV1beta2().ResourceClaimTemplates(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaimTemplates(namespace).List(ctx, options) + return client.ResourceV1beta2().ResourceClaimTemplates(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceClaimTemplates(namespace).Watch(ctx, options) + return client.ResourceV1beta2().ResourceClaimTemplates(namespace).Watch(ctx, opts) }, }, client), &apiresourcev1beta2.ResourceClaimTemplate{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceClaimTemplateInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceClaimTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceClaimTemplateInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceClaimTemplateInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceslice.go b/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceslice.go index 83e6cc037aa22..7a8027886e06b 100644 --- a/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceslice.go +++ b/staging/src/k8s.io/client-go/informers/resource/v1beta2/resourceslice.go @@ -25,6 +25,7 @@ import ( apiresourcev1beta2 "k8s.io/api/resource/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type resourceSliceInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewResourceSliceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResourceSliceInformer(client, resyncPeriod, indexers, nil) + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredResourceSliceInformer constructs a new informer for ResourceSlice type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredResourceSliceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewResourceSliceInformerWithOptions constructs a new informer for ResourceSlice type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewResourceSliceInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "resource.k8s.io", Version: "v1beta2", Resource: "resourceslices"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceSlices().List(context.Background(), options) + return client.ResourceV1beta2().ResourceSlices().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceSlices().Watch(context.Background(), options) + return client.ResourceV1beta2().ResourceSlices().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceSlices().List(ctx, options) + return client.ResourceV1beta2().ResourceSlices().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.ResourceV1beta2().ResourceSlices().Watch(ctx, options) + return client.ResourceV1beta2().ResourceSlices().Watch(ctx, opts) }, }, client), &apiresourcev1beta2.ResourceSlice{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *resourceSliceInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResourceSliceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewResourceSliceInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *resourceSliceInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/scheduling/interface.go b/staging/src/k8s.io/client-go/informers/scheduling/interface.go index 659089b531f80..e55a8410d1ed6 100644 --- a/staging/src/k8s.io/client-go/informers/scheduling/interface.go +++ b/staging/src/k8s.io/client-go/informers/scheduling/interface.go @@ -21,7 +21,7 @@ package scheduling import ( internalinterfaces "k8s.io/client-go/informers/internalinterfaces" v1 "k8s.io/client-go/informers/scheduling/v1" - v1alpha1 "k8s.io/client-go/informers/scheduling/v1alpha1" + v1alpha2 "k8s.io/client-go/informers/scheduling/v1alpha2" v1beta1 "k8s.io/client-go/informers/scheduling/v1beta1" ) @@ -29,8 +29,8 @@ import ( type Interface interface { // V1 provides access to shared informers for resources in V1. V1() v1.Interface - // V1alpha1 provides access to shared informers for resources in V1alpha1. - V1alpha1() v1alpha1.Interface + // V1alpha2 provides access to shared informers for resources in V1alpha2. + V1alpha2() v1alpha2.Interface // V1beta1 provides access to shared informers for resources in V1beta1. V1beta1() v1beta1.Interface } @@ -51,9 +51,9 @@ func (g *group) V1() v1.Interface { return v1.New(g.factory, g.namespace, g.tweakListOptions) } -// V1alpha1 returns a new v1alpha1.Interface. -func (g *group) V1alpha1() v1alpha1.Interface { - return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +// V1alpha2 returns a new v1alpha2.Interface. +func (g *group) V1alpha2() v1alpha2.Interface { + return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) } // V1beta1 returns a new v1beta1.Interface. diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1/priorityclass.go b/staging/src/k8s.io/client-go/informers/scheduling/v1/priorityclass.go index 8cce79ce2387c..f1d18f2a01729 100644 --- a/staging/src/k8s.io/client-go/informers/scheduling/v1/priorityclass.go +++ b/staging/src/k8s.io/client-go/informers/scheduling/v1/priorityclass.go @@ -25,6 +25,7 @@ import ( apischedulingv1 "k8s.io/api/scheduling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type priorityClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPriorityClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityClassInformer(client, resyncPeriod, indexers, nil) + return NewPriorityClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPriorityClassInformer constructs a new informer for PriorityClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPriorityClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPriorityClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPriorityClassInformerWithOptions constructs a new informer for PriorityClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPriorityClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "scheduling.k8s.io", Version: "v1", Resource: "priorityclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1().PriorityClasses().List(context.Background(), options) + return client.SchedulingV1().PriorityClasses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1().PriorityClasses().Watch(context.Background(), options) + return client.SchedulingV1().PriorityClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1().PriorityClasses().List(ctx, options) + return client.SchedulingV1().PriorityClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1().PriorityClasses().Watch(ctx, options) + return client.SchedulingV1().PriorityClasses().Watch(ctx, opts) }, }, client), &apischedulingv1.PriorityClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *priorityClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPriorityClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *priorityClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/interface.go b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/interface.go deleted file mode 100644 index fdfc4e6af476b..0000000000000 --- a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/interface.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" -) - -// Interface provides access to all the informers in this group version. -type Interface interface { - // PriorityClasses returns a PriorityClassInformer. - PriorityClasses() PriorityClassInformer - // Workloads returns a WorkloadInformer. - Workloads() WorkloadInformer -} - -type version struct { - factory internalinterfaces.SharedInformerFactory - namespace string - tweakListOptions internalinterfaces.TweakListOptionsFunc -} - -// New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { - return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} -} - -// PriorityClasses returns a PriorityClassInformer. -func (v *version) PriorityClasses() PriorityClassInformer { - return &priorityClassInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} -} - -// Workloads returns a WorkloadInformer. -func (v *version) Workloads() WorkloadInformer { - return &workloadInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/priorityclass.go b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/priorityclass.go deleted file mode 100644 index 88f99a7d005a9..0000000000000 --- a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/priorityclass.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - time "time" - - apischedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" - kubernetes "k8s.io/client-go/kubernetes" - schedulingv1alpha1 "k8s.io/client-go/listers/scheduling/v1alpha1" - cache "k8s.io/client-go/tools/cache" -) - -// PriorityClassInformer provides access to a shared informer and lister for -// PriorityClasses. -type PriorityClassInformer interface { - Informer() cache.SharedIndexInformer - Lister() schedulingv1alpha1.PriorityClassLister -} - -type priorityClassInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc -} - -// NewPriorityClassInformer constructs a new informer for PriorityClass type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewPriorityClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityClassInformer(client, resyncPeriod, indexers, nil) -} - -// NewFilteredPriorityClassInformer constructs a new informer for PriorityClass type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredPriorityClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().PriorityClasses().List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().PriorityClasses().Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().PriorityClasses().List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().PriorityClasses().Watch(ctx, options) - }, - }, client), - &apischedulingv1alpha1.PriorityClass{}, - resyncPeriod, - indexers, - ) -} - -func (f *priorityClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *priorityClassInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apischedulingv1alpha1.PriorityClass{}, f.defaultInformer) -} - -func (f *priorityClassInformer) Lister() schedulingv1alpha1.PriorityClassLister { - return schedulingv1alpha1.NewPriorityClassLister(f.Informer().GetIndexer()) -} diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/workload.go b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/workload.go deleted file mode 100644 index c58e788946ce9..0000000000000 --- a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha1/workload.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - time "time" - - apischedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - internalinterfaces "k8s.io/client-go/informers/internalinterfaces" - kubernetes "k8s.io/client-go/kubernetes" - schedulingv1alpha1 "k8s.io/client-go/listers/scheduling/v1alpha1" - cache "k8s.io/client-go/tools/cache" -) - -// WorkloadInformer provides access to a shared informer and lister for -// Workloads. -type WorkloadInformer interface { - Informer() cache.SharedIndexInformer - Lister() schedulingv1alpha1.WorkloadLister -} - -type workloadInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewWorkloadInformer constructs a new informer for Workload type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewWorkloadInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredWorkloadInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredWorkloadInformer constructs a new informer for Workload type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredWorkloadInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().Workloads(namespace).List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().Workloads(namespace).Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().Workloads(namespace).List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.SchedulingV1alpha1().Workloads(namespace).Watch(ctx, options) - }, - }, client), - &apischedulingv1alpha1.Workload{}, - resyncPeriod, - indexers, - ) -} - -func (f *workloadInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredWorkloadInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *workloadInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apischedulingv1alpha1.Workload{}, f.defaultInformer) -} - -func (f *workloadInformer) Lister() schedulingv1alpha1.WorkloadLister { - return schedulingv1alpha1.NewWorkloadLister(f.Informer().GetIndexer()) -} diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/interface.go b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/interface.go new file mode 100644 index 0000000000000..8edc498b67789 --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/interface.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // PodGroups returns a PodGroupInformer. + PodGroups() PodGroupInformer + // Workloads returns a WorkloadInformer. + Workloads() WorkloadInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// PodGroups returns a PodGroupInformer. +func (v *version) PodGroups() PodGroupInformer { + return &podGroupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// Workloads returns a WorkloadInformer. +func (v *version) Workloads() WorkloadInformer { + return &workloadInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/podgroup.go b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/podgroup.go new file mode 100644 index 0000000000000..5f39858c65361 --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/podgroup.go @@ -0,0 +1,116 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + time "time" + + apischedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + kubernetes "k8s.io/client-go/kubernetes" + schedulingv1alpha2 "k8s.io/client-go/listers/scheduling/v1alpha2" + cache "k8s.io/client-go/tools/cache" +) + +// PodGroupInformer provides access to a shared informer and lister for +// PodGroups. +type PodGroupInformer interface { + Informer() cache.SharedIndexInformer + Lister() schedulingv1alpha2.PodGroupLister +} + +type podGroupInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewPodGroupInformer constructs a new informer for PodGroup type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodGroupInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewPodGroupInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredPodGroupInformer constructs a new informer for PodGroup type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredPodGroupInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewPodGroupInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPodGroupInformerWithOptions constructs a new informer for PodGroup type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPodGroupInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "scheduling.k8s.io", Version: "v1alpha2", Resource: "podgroups"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().PodGroups(namespace).List(context.Background(), opts) + }, + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().PodGroups(namespace).Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().PodGroups(namespace).List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().PodGroups(namespace).Watch(ctx, opts) + }, + }, client), + &apischedulingv1alpha2.PodGroup{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *podGroupInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewPodGroupInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *podGroupInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apischedulingv1alpha2.PodGroup{}, f.defaultInformer) +} + +func (f *podGroupInformer) Lister() schedulingv1alpha2.PodGroupLister { + return schedulingv1alpha2.NewPodGroupLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/workload.go b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/workload.go new file mode 100644 index 0000000000000..ffe2f4ece73d9 --- /dev/null +++ b/staging/src/k8s.io/client-go/informers/scheduling/v1alpha2/workload.go @@ -0,0 +1,116 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + time "time" + + apischedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + kubernetes "k8s.io/client-go/kubernetes" + schedulingv1alpha2 "k8s.io/client-go/listers/scheduling/v1alpha2" + cache "k8s.io/client-go/tools/cache" +) + +// WorkloadInformer provides access to a shared informer and lister for +// Workloads. +type WorkloadInformer interface { + Informer() cache.SharedIndexInformer + Lister() schedulingv1alpha2.WorkloadLister +} + +type workloadInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewWorkloadInformer constructs a new informer for Workload type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewWorkloadInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewWorkloadInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredWorkloadInformer constructs a new informer for Workload type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredWorkloadInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewWorkloadInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewWorkloadInformerWithOptions constructs a new informer for Workload type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewWorkloadInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "scheduling.k8s.io", Version: "v1alpha2", Resource: "workloads"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().Workloads(namespace).List(context.Background(), opts) + }, + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().Workloads(namespace).Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().Workloads(namespace).List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.SchedulingV1alpha2().Workloads(namespace).Watch(ctx, opts) + }, + }, client), + &apischedulingv1alpha2.Workload{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *workloadInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewWorkloadInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *workloadInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apischedulingv1alpha2.Workload{}, f.defaultInformer) +} + +func (f *workloadInformer) Lister() schedulingv1alpha2.WorkloadLister { + return schedulingv1alpha2.NewWorkloadLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/client-go/informers/scheduling/v1beta1/priorityclass.go b/staging/src/k8s.io/client-go/informers/scheduling/v1beta1/priorityclass.go index e8057f921a21b..a5cae3ea56711 100644 --- a/staging/src/k8s.io/client-go/informers/scheduling/v1beta1/priorityclass.go +++ b/staging/src/k8s.io/client-go/informers/scheduling/v1beta1/priorityclass.go @@ -25,6 +25,7 @@ import ( apischedulingv1beta1 "k8s.io/api/scheduling/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type priorityClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewPriorityClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPriorityClassInformer(client, resyncPeriod, indexers, nil) + return NewPriorityClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredPriorityClassInformer constructs a new informer for PriorityClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredPriorityClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewPriorityClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewPriorityClassInformerWithOptions constructs a new informer for PriorityClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPriorityClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "scheduling.k8s.io", Version: "v1beta1", Resource: "priorityclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1beta1().PriorityClasses().List(context.Background(), options) + return client.SchedulingV1beta1().PriorityClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1beta1().PriorityClasses().Watch(context.Background(), options) + return client.SchedulingV1beta1().PriorityClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1beta1().PriorityClasses().List(ctx, options) + return client.SchedulingV1beta1().PriorityClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.SchedulingV1beta1().PriorityClasses().Watch(ctx, options) + return client.SchedulingV1beta1().PriorityClasses().Watch(ctx, opts) }, }, client), &apischedulingv1beta1.PriorityClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *priorityClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPriorityClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewPriorityClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *priorityClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1/csidriver.go b/staging/src/k8s.io/client-go/informers/storage/v1/csidriver.go index 3a1922edaa3a8..56ed349e2600a 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1/csidriver.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1/csidriver.go @@ -25,6 +25,7 @@ import ( apistoragev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type cSIDriverInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSIDriverInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSIDriverInformer(client, resyncPeriod, indexers, nil) + return NewCSIDriverInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSIDriverInformer constructs a new informer for CSIDriver type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSIDriverInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSIDriverInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSIDriverInformerWithOptions constructs a new informer for CSIDriver type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSIDriverInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "csidrivers"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIDrivers().List(context.Background(), options) + return client.StorageV1().CSIDrivers().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIDrivers().Watch(context.Background(), options) + return client.StorageV1().CSIDrivers().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIDrivers().List(ctx, options) + return client.StorageV1().CSIDrivers().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIDrivers().Watch(ctx, options) + return client.StorageV1().CSIDrivers().Watch(ctx, opts) }, }, client), &apistoragev1.CSIDriver{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSIDriverInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSIDriverInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSIDriverInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSIDriverInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1/csinode.go b/staging/src/k8s.io/client-go/informers/storage/v1/csinode.go index 350ab61e57d46..c90686bf7851d 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1/csinode.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1/csinode.go @@ -25,6 +25,7 @@ import ( apistoragev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type cSINodeInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSINodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSINodeInformer(client, resyncPeriod, indexers, nil) + return NewCSINodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSINodeInformer constructs a new informer for CSINode type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSINodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSINodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSINodeInformerWithOptions constructs a new informer for CSINode type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSINodeInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "csinodes"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSINodes().List(context.Background(), options) + return client.StorageV1().CSINodes().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSINodes().Watch(context.Background(), options) + return client.StorageV1().CSINodes().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSINodes().List(ctx, options) + return client.StorageV1().CSINodes().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSINodes().Watch(ctx, options) + return client.StorageV1().CSINodes().Watch(ctx, opts) }, }, client), &apistoragev1.CSINode{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSINodeInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSINodeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSINodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSINodeInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1/csistoragecapacity.go b/staging/src/k8s.io/client-go/informers/storage/v1/csistoragecapacity.go index 7c88e6e07b454..52c0e281ac670 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1/csistoragecapacity.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1/csistoragecapacity.go @@ -25,6 +25,7 @@ import ( apistoragev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type cSIStorageCapacityInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSIStorageCapacityInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSIStorageCapacityInformer(client, namespace, resyncPeriod, indexers, nil) + return NewCSIStorageCapacityInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSIStorageCapacityInformer constructs a new informer for CSIStorageCapacity type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSIStorageCapacityInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSIStorageCapacityInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSIStorageCapacityInformerWithOptions constructs a new informer for CSIStorageCapacity type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSIStorageCapacityInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "csistoragecapacitys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIStorageCapacities(namespace).List(context.Background(), options) + return client.StorageV1().CSIStorageCapacities(namespace).List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIStorageCapacities(namespace).Watch(context.Background(), options) + return client.StorageV1().CSIStorageCapacities(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIStorageCapacities(namespace).List(ctx, options) + return client.StorageV1().CSIStorageCapacities(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().CSIStorageCapacities(namespace).Watch(ctx, options) + return client.StorageV1().CSIStorageCapacities(namespace).Watch(ctx, opts) }, }, client), &apistoragev1.CSIStorageCapacity{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSIStorageCapacityInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSIStorageCapacityInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSIStorageCapacityInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSIStorageCapacityInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1/storageclass.go b/staging/src/k8s.io/client-go/informers/storage/v1/storageclass.go index 620c1d3489c8f..533d02fc862a2 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1/storageclass.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1/storageclass.go @@ -25,6 +25,7 @@ import ( apistoragev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type storageClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStorageClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStorageClassInformer(client, resyncPeriod, indexers, nil) + return NewStorageClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStorageClassInformer constructs a new informer for StorageClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStorageClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStorageClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStorageClassInformerWithOptions constructs a new informer for StorageClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStorageClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "storageclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().StorageClasses().List(context.Background(), options) + return client.StorageV1().StorageClasses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().StorageClasses().Watch(context.Background(), options) + return client.StorageV1().StorageClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().StorageClasses().List(ctx, options) + return client.StorageV1().StorageClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().StorageClasses().Watch(ctx, options) + return client.StorageV1().StorageClasses().Watch(ctx, opts) }, }, client), &apistoragev1.StorageClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *storageClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStorageClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStorageClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *storageClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1/volumeattachment.go b/staging/src/k8s.io/client-go/informers/storage/v1/volumeattachment.go index 3b7c4611448c7..49444340ad217 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1/volumeattachment.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1/volumeattachment.go @@ -25,6 +25,7 @@ import ( apistoragev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type volumeAttachmentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewVolumeAttachmentInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, indexers, nil) + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredVolumeAttachmentInformer constructs a new informer for VolumeAttachment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredVolumeAttachmentInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewVolumeAttachmentInformerWithOptions constructs a new informer for VolumeAttachment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVolumeAttachmentInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "volumeattachments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttachments().List(context.Background(), options) + return client.StorageV1().VolumeAttachments().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttachments().Watch(context.Background(), options) + return client.StorageV1().VolumeAttachments().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttachments().List(ctx, options) + return client.StorageV1().VolumeAttachments().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttachments().Watch(ctx, options) + return client.StorageV1().VolumeAttachments().Watch(ctx, opts) }, }, client), &apistoragev1.VolumeAttachment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *volumeAttachmentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *volumeAttachmentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1/volumeattributesclass.go b/staging/src/k8s.io/client-go/informers/storage/v1/volumeattributesclass.go index 34dc8b48d89eb..fa8acccfbdb1e 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1/volumeattributesclass.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1/volumeattributesclass.go @@ -25,6 +25,7 @@ import ( apistoragev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type volumeAttributesClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewVolumeAttributesClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredVolumeAttributesClassInformer(client, resyncPeriod, indexers, nil) + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredVolumeAttributesClassInformer constructs a new informer for VolumeAttributesClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredVolumeAttributesClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewVolumeAttributesClassInformerWithOptions constructs a new informer for VolumeAttributesClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVolumeAttributesClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "volumeattributesclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttributesClasses().List(context.Background(), options) + return client.StorageV1().VolumeAttributesClasses().List(context.Background(), opts) }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttributesClasses().Watch(context.Background(), options) + return client.StorageV1().VolumeAttributesClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttributesClasses().List(ctx, options) + return client.StorageV1().VolumeAttributesClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1().VolumeAttributesClasses().Watch(ctx, options) + return client.StorageV1().VolumeAttributesClasses().Watch(ctx, opts) }, }, client), &apistoragev1.VolumeAttributesClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *volumeAttributesClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredVolumeAttributesClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *volumeAttributesClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1alpha1/csistoragecapacity.go b/staging/src/k8s.io/client-go/informers/storage/v1alpha1/csistoragecapacity.go index 3b2753200c5bb..2f7accab3db37 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1alpha1/csistoragecapacity.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1alpha1/csistoragecapacity.go @@ -25,6 +25,7 @@ import ( apistoragev1alpha1 "k8s.io/api/storage/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type cSIStorageCapacityInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSIStorageCapacityInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSIStorageCapacityInformer(client, namespace, resyncPeriod, indexers, nil) + return NewCSIStorageCapacityInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSIStorageCapacityInformer constructs a new informer for CSIStorageCapacity type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSIStorageCapacityInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSIStorageCapacityInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSIStorageCapacityInformerWithOptions constructs a new informer for CSIStorageCapacity type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSIStorageCapacityInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1alpha1", Resource: "csistoragecapacitys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().CSIStorageCapacities(namespace).List(context.Background(), options) + return client.StorageV1alpha1().CSIStorageCapacities(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().CSIStorageCapacities(namespace).Watch(context.Background(), options) + return client.StorageV1alpha1().CSIStorageCapacities(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().CSIStorageCapacities(namespace).List(ctx, options) + return client.StorageV1alpha1().CSIStorageCapacities(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().CSIStorageCapacities(namespace).Watch(ctx, options) + return client.StorageV1alpha1().CSIStorageCapacities(namespace).Watch(ctx, opts) }, }, client), &apistoragev1alpha1.CSIStorageCapacity{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSIStorageCapacityInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSIStorageCapacityInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSIStorageCapacityInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSIStorageCapacityInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattachment.go b/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattachment.go index 10ac7e55ed4f7..1ab135906f394 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattachment.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattachment.go @@ -25,6 +25,7 @@ import ( apistoragev1alpha1 "k8s.io/api/storage/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type volumeAttachmentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewVolumeAttachmentInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, indexers, nil) + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredVolumeAttachmentInformer constructs a new informer for VolumeAttachment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredVolumeAttachmentInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewVolumeAttachmentInformerWithOptions constructs a new informer for VolumeAttachment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVolumeAttachmentInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1alpha1", Resource: "volumeattachments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttachments().List(context.Background(), options) + return client.StorageV1alpha1().VolumeAttachments().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttachments().Watch(context.Background(), options) + return client.StorageV1alpha1().VolumeAttachments().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttachments().List(ctx, options) + return client.StorageV1alpha1().VolumeAttachments().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttachments().Watch(ctx, options) + return client.StorageV1alpha1().VolumeAttachments().Watch(ctx, opts) }, }, client), &apistoragev1alpha1.VolumeAttachment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *volumeAttachmentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *volumeAttachmentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattributesclass.go b/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattributesclass.go index 312a443e34676..e649c665eeae7 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattributesclass.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1alpha1/volumeattributesclass.go @@ -25,6 +25,7 @@ import ( apistoragev1alpha1 "k8s.io/api/storage/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type volumeAttributesClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewVolumeAttributesClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredVolumeAttributesClassInformer(client, resyncPeriod, indexers, nil) + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredVolumeAttributesClassInformer constructs a new informer for VolumeAttributesClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredVolumeAttributesClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewVolumeAttributesClassInformerWithOptions constructs a new informer for VolumeAttributesClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVolumeAttributesClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1alpha1", Resource: "volumeattributesclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttributesClasses().List(context.Background(), options) + return client.StorageV1alpha1().VolumeAttributesClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttributesClasses().Watch(context.Background(), options) + return client.StorageV1alpha1().VolumeAttributesClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttributesClasses().List(ctx, options) + return client.StorageV1alpha1().VolumeAttributesClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1alpha1().VolumeAttributesClasses().Watch(ctx, options) + return client.StorageV1alpha1().VolumeAttributesClasses().Watch(ctx, opts) }, }, client), &apistoragev1alpha1.VolumeAttributesClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *volumeAttributesClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredVolumeAttributesClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *volumeAttributesClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1beta1/csidriver.go b/staging/src/k8s.io/client-go/informers/storage/v1beta1/csidriver.go index 007caaab5421c..283dc6b17c3f7 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1beta1/csidriver.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1beta1/csidriver.go @@ -25,6 +25,7 @@ import ( apistoragev1beta1 "k8s.io/api/storage/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type cSIDriverInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSIDriverInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSIDriverInformer(client, resyncPeriod, indexers, nil) + return NewCSIDriverInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSIDriverInformer constructs a new informer for CSIDriver type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSIDriverInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSIDriverInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSIDriverInformerWithOptions constructs a new informer for CSIDriver type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSIDriverInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1beta1", Resource: "csidrivers"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIDrivers().List(context.Background(), options) + return client.StorageV1beta1().CSIDrivers().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIDrivers().Watch(context.Background(), options) + return client.StorageV1beta1().CSIDrivers().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIDrivers().List(ctx, options) + return client.StorageV1beta1().CSIDrivers().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIDrivers().Watch(ctx, options) + return client.StorageV1beta1().CSIDrivers().Watch(ctx, opts) }, }, client), &apistoragev1beta1.CSIDriver{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSIDriverInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSIDriverInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSIDriverInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSIDriverInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1beta1/csinode.go b/staging/src/k8s.io/client-go/informers/storage/v1beta1/csinode.go index ce518f0b2e32c..f304906ce7d4d 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1beta1/csinode.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1beta1/csinode.go @@ -25,6 +25,7 @@ import ( apistoragev1beta1 "k8s.io/api/storage/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type cSINodeInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSINodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSINodeInformer(client, resyncPeriod, indexers, nil) + return NewCSINodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSINodeInformer constructs a new informer for CSINode type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSINodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSINodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSINodeInformerWithOptions constructs a new informer for CSINode type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSINodeInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1beta1", Resource: "csinodes"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSINodes().List(context.Background(), options) + return client.StorageV1beta1().CSINodes().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSINodes().Watch(context.Background(), options) + return client.StorageV1beta1().CSINodes().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSINodes().List(ctx, options) + return client.StorageV1beta1().CSINodes().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSINodes().Watch(ctx, options) + return client.StorageV1beta1().CSINodes().Watch(ctx, opts) }, }, client), &apistoragev1beta1.CSINode{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSINodeInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSINodeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSINodeInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSINodeInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1beta1/csistoragecapacity.go b/staging/src/k8s.io/client-go/informers/storage/v1beta1/csistoragecapacity.go index 88446b7695ebe..5b24a8c32b415 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1beta1/csistoragecapacity.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1beta1/csistoragecapacity.go @@ -25,6 +25,7 @@ import ( apistoragev1beta1 "k8s.io/api/storage/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -49,48 +50,61 @@ type cSIStorageCapacityInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewCSIStorageCapacityInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredCSIStorageCapacityInformer(client, namespace, resyncPeriod, indexers, nil) + return NewCSIStorageCapacityInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredCSIStorageCapacityInformer constructs a new informer for CSIStorageCapacity type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredCSIStorageCapacityInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewCSIStorageCapacityInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewCSIStorageCapacityInformerWithOptions constructs a new informer for CSIStorageCapacity type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewCSIStorageCapacityInformerWithOptions(client kubernetes.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1beta1", Resource: "csistoragecapacitys"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIStorageCapacities(namespace).List(context.Background(), options) + return client.StorageV1beta1().CSIStorageCapacities(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIStorageCapacities(namespace).Watch(context.Background(), options) + return client.StorageV1beta1().CSIStorageCapacities(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIStorageCapacities(namespace).List(ctx, options) + return client.StorageV1beta1().CSIStorageCapacities(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().CSIStorageCapacities(namespace).Watch(ctx, options) + return client.StorageV1beta1().CSIStorageCapacities(namespace).Watch(ctx, opts) }, }, client), &apistoragev1beta1.CSIStorageCapacity{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *cSIStorageCapacityInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredCSIStorageCapacityInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewCSIStorageCapacityInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *cSIStorageCapacityInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1beta1/storageclass.go b/staging/src/k8s.io/client-go/informers/storage/v1beta1/storageclass.go index 786d46d3a4943..2052da70e929c 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1beta1/storageclass.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1beta1/storageclass.go @@ -25,6 +25,7 @@ import ( apistoragev1beta1 "k8s.io/api/storage/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type storageClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStorageClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStorageClassInformer(client, resyncPeriod, indexers, nil) + return NewStorageClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStorageClassInformer constructs a new informer for StorageClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStorageClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStorageClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStorageClassInformerWithOptions constructs a new informer for StorageClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStorageClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1beta1", Resource: "storageclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().StorageClasses().List(context.Background(), options) + return client.StorageV1beta1().StorageClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().StorageClasses().Watch(context.Background(), options) + return client.StorageV1beta1().StorageClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().StorageClasses().List(ctx, options) + return client.StorageV1beta1().StorageClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().StorageClasses().Watch(ctx, options) + return client.StorageV1beta1().StorageClasses().Watch(ctx, opts) }, }, client), &apistoragev1beta1.StorageClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *storageClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStorageClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStorageClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *storageClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattachment.go b/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattachment.go index 0f1bf216138ed..11f9840b0448b 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattachment.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattachment.go @@ -25,6 +25,7 @@ import ( apistoragev1beta1 "k8s.io/api/storage/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type volumeAttachmentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewVolumeAttachmentInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, indexers, nil) + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredVolumeAttachmentInformer constructs a new informer for VolumeAttachment type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredVolumeAttachmentInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewVolumeAttachmentInformerWithOptions constructs a new informer for VolumeAttachment type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVolumeAttachmentInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1beta1", Resource: "volumeattachments"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttachments().List(context.Background(), options) + return client.StorageV1beta1().VolumeAttachments().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttachments().Watch(context.Background(), options) + return client.StorageV1beta1().VolumeAttachments().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttachments().List(ctx, options) + return client.StorageV1beta1().VolumeAttachments().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttachments().Watch(ctx, options) + return client.StorageV1beta1().VolumeAttachments().Watch(ctx, opts) }, }, client), &apistoragev1beta1.VolumeAttachment{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *volumeAttachmentInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewVolumeAttachmentInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *volumeAttachmentInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattributesclass.go b/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattributesclass.go index 90e79cc710561..c91dfff0d1e8b 100644 --- a/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattributesclass.go +++ b/staging/src/k8s.io/client-go/informers/storage/v1beta1/volumeattributesclass.go @@ -25,6 +25,7 @@ import ( apistoragev1beta1 "k8s.io/api/storage/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type volumeAttributesClassInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewVolumeAttributesClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredVolumeAttributesClassInformer(client, resyncPeriod, indexers, nil) + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredVolumeAttributesClassInformer constructs a new informer for VolumeAttributesClass type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredVolumeAttributesClassInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewVolumeAttributesClassInformerWithOptions constructs a new informer for VolumeAttributesClass type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVolumeAttributesClassInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1beta1", Resource: "volumeattributesclasss"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttributesClasses().List(context.Background(), options) + return client.StorageV1beta1().VolumeAttributesClasses().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttributesClasses().Watch(context.Background(), options) + return client.StorageV1beta1().VolumeAttributesClasses().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttributesClasses().List(ctx, options) + return client.StorageV1beta1().VolumeAttributesClasses().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StorageV1beta1().VolumeAttributesClasses().Watch(ctx, options) + return client.StorageV1beta1().VolumeAttributesClasses().Watch(ctx, opts) }, }, client), &apistoragev1beta1.VolumeAttributesClass{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *volumeAttributesClassInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredVolumeAttributesClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewVolumeAttributesClassInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *volumeAttributesClassInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/informers/storagemigration/v1beta1/storageversionmigration.go b/staging/src/k8s.io/client-go/informers/storagemigration/v1beta1/storageversionmigration.go index 370d88f242eba..50e271e6fe3a5 100644 --- a/staging/src/k8s.io/client-go/informers/storagemigration/v1beta1/storageversionmigration.go +++ b/staging/src/k8s.io/client-go/informers/storagemigration/v1beta1/storageversionmigration.go @@ -25,6 +25,7 @@ import ( apistoragemigrationv1beta1 "k8s.io/api/storagemigration/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" kubernetes "k8s.io/client-go/kubernetes" @@ -48,48 +49,61 @@ type storageVersionMigrationInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewStorageVersionMigrationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredStorageVersionMigrationInformer(client, resyncPeriod, indexers, nil) + return NewStorageVersionMigrationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredStorageVersionMigrationInformer constructs a new informer for StorageVersionMigration type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredStorageVersionMigrationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewStorageVersionMigrationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewStorageVersionMigrationInformerWithOptions constructs a new informer for StorageVersionMigration type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStorageVersionMigrationInformerWithOptions(client kubernetes.Interface, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "storagemigration.k8s.io", Version: "v1beta1", Resource: "storageversionmigrations"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StoragemigrationV1beta1().StorageVersionMigrations().List(context.Background(), options) + return client.StoragemigrationV1beta1().StorageVersionMigrations().List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StoragemigrationV1beta1().StorageVersionMigrations().Watch(context.Background(), options) + return client.StoragemigrationV1beta1().StorageVersionMigrations().Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StoragemigrationV1beta1().StorageVersionMigrations().List(ctx, options) + return client.StoragemigrationV1beta1().StorageVersionMigrations().List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.StoragemigrationV1beta1().StorageVersionMigrations().Watch(ctx, options) + return client.StoragemigrationV1beta1().StorageVersionMigrations().Watch(ctx, opts) }, }, client), &apistoragemigrationv1beta1.StorageVersionMigration{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *storageVersionMigrationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredStorageVersionMigrationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewStorageVersionMigrationInformerWithOptions(client, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *storageVersionMigrationInformer) Informer() cache.SharedIndexInformer { diff --git a/staging/src/k8s.io/client-go/kubernetes/clientset.go b/staging/src/k8s.io/client-go/kubernetes/clientset.go index 1ef0ff4b2cbea..537609b718f8d 100644 --- a/staging/src/k8s.io/client-go/kubernetes/clientset.go +++ b/staging/src/k8s.io/client-go/kubernetes/clientset.go @@ -37,8 +37,6 @@ import ( authorizationv1beta1 "k8s.io/client-go/kubernetes/typed/authorization/v1beta1" autoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1" autoscalingv2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2" - autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1" - autoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2" batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1" certificatesv1 "k8s.io/client-go/kubernetes/typed/certificates/v1" @@ -72,7 +70,7 @@ import ( resourcev1beta1 "k8s.io/client-go/kubernetes/typed/resource/v1beta1" resourcev1beta2 "k8s.io/client-go/kubernetes/typed/resource/v1beta2" schedulingv1 "k8s.io/client-go/kubernetes/typed/scheduling/v1" - schedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1" storagev1 "k8s.io/client-go/kubernetes/typed/storage/v1" storagev1alpha1 "k8s.io/client-go/kubernetes/typed/storage/v1alpha1" @@ -98,8 +96,6 @@ type Interface interface { AuthorizationV1beta1() authorizationv1beta1.AuthorizationV1beta1Interface AutoscalingV1() autoscalingv1.AutoscalingV1Interface AutoscalingV2() autoscalingv2.AutoscalingV2Interface - AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface - AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface BatchV1() batchv1.BatchV1Interface BatchV1beta1() batchv1beta1.BatchV1beta1Interface CertificatesV1() certificatesv1.CertificatesV1Interface @@ -132,7 +128,7 @@ type Interface interface { ResourceV1beta2() resourcev1beta2.ResourceV1beta2Interface ResourceV1beta1() resourcev1beta1.ResourceV1beta1Interface ResourceV1alpha3() resourcev1alpha3.ResourceV1alpha3Interface - SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface + SchedulingV1alpha2() schedulingv1alpha2.SchedulingV1alpha2Interface SchedulingV1beta1() schedulingv1beta1.SchedulingV1beta1Interface SchedulingV1() schedulingv1.SchedulingV1Interface StorageV1beta1() storagev1beta1.StorageV1beta1Interface @@ -158,8 +154,6 @@ type Clientset struct { authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client autoscalingV1 *autoscalingv1.AutoscalingV1Client autoscalingV2 *autoscalingv2.AutoscalingV2Client - autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client - autoscalingV2beta2 *autoscalingv2beta2.AutoscalingV2beta2Client batchV1 *batchv1.BatchV1Client batchV1beta1 *batchv1beta1.BatchV1beta1Client certificatesV1 *certificatesv1.CertificatesV1Client @@ -192,7 +186,7 @@ type Clientset struct { resourceV1beta2 *resourcev1beta2.ResourceV1beta2Client resourceV1beta1 *resourcev1beta1.ResourceV1beta1Client resourceV1alpha3 *resourcev1alpha3.ResourceV1alpha3Client - schedulingV1alpha1 *schedulingv1alpha1.SchedulingV1alpha1Client + schedulingV1alpha2 *schedulingv1alpha2.SchedulingV1alpha2Client schedulingV1beta1 *schedulingv1beta1.SchedulingV1beta1Client schedulingV1 *schedulingv1.SchedulingV1Client storageV1beta1 *storagev1beta1.StorageV1beta1Client @@ -271,16 +265,6 @@ func (c *Clientset) AutoscalingV2() autoscalingv2.AutoscalingV2Interface { return c.autoscalingV2 } -// AutoscalingV2beta1 retrieves the AutoscalingV2beta1Client -func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface { - return c.autoscalingV2beta1 -} - -// AutoscalingV2beta2 retrieves the AutoscalingV2beta2Client -func (c *Clientset) AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface { - return c.autoscalingV2beta2 -} - // BatchV1 retrieves the BatchV1Client func (c *Clientset) BatchV1() batchv1.BatchV1Interface { return c.batchV1 @@ -441,9 +425,9 @@ func (c *Clientset) ResourceV1alpha3() resourcev1alpha3.ResourceV1alpha3Interfac return c.resourceV1alpha3 } -// SchedulingV1alpha1 retrieves the SchedulingV1alpha1Client -func (c *Clientset) SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface { - return c.schedulingV1alpha1 +// SchedulingV1alpha2 retrieves the SchedulingV1alpha2Client +func (c *Clientset) SchedulingV1alpha2() schedulingv1alpha2.SchedulingV1alpha2Interface { + return c.schedulingV1alpha2 } // SchedulingV1beta1 retrieves the SchedulingV1beta1Client @@ -576,14 +560,6 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } - cs.autoscalingV2beta1, err = autoscalingv2beta1.NewForConfigAndClient(&configShallowCopy, httpClient) - if err != nil { - return nil, err - } - cs.autoscalingV2beta2, err = autoscalingv2beta2.NewForConfigAndClient(&configShallowCopy, httpClient) - if err != nil { - return nil, err - } cs.batchV1, err = batchv1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -712,7 +688,7 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } - cs.schedulingV1alpha1, err = schedulingv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + cs.schedulingV1alpha2, err = schedulingv1alpha2.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err } @@ -775,8 +751,6 @@ func New(c rest.Interface) *Clientset { cs.authorizationV1beta1 = authorizationv1beta1.New(c) cs.autoscalingV1 = autoscalingv1.New(c) cs.autoscalingV2 = autoscalingv2.New(c) - cs.autoscalingV2beta1 = autoscalingv2beta1.New(c) - cs.autoscalingV2beta2 = autoscalingv2beta2.New(c) cs.batchV1 = batchv1.New(c) cs.batchV1beta1 = batchv1beta1.New(c) cs.certificatesV1 = certificatesv1.New(c) @@ -809,7 +783,7 @@ func New(c rest.Interface) *Clientset { cs.resourceV1beta2 = resourcev1beta2.New(c) cs.resourceV1beta1 = resourcev1beta1.New(c) cs.resourceV1alpha3 = resourcev1alpha3.New(c) - cs.schedulingV1alpha1 = schedulingv1alpha1.New(c) + cs.schedulingV1alpha2 = schedulingv1alpha2.New(c) cs.schedulingV1beta1 = schedulingv1beta1.New(c) cs.schedulingV1 = schedulingv1.New(c) cs.storageV1beta1 = storagev1beta1.New(c) diff --git a/staging/src/k8s.io/client-go/kubernetes/fake/clientset_generated.go b/staging/src/k8s.io/client-go/kubernetes/fake/clientset_generated.go index c101c2830684f..618dd57511972 100644 --- a/staging/src/k8s.io/client-go/kubernetes/fake/clientset_generated.go +++ b/staging/src/k8s.io/client-go/kubernetes/fake/clientset_generated.go @@ -54,10 +54,6 @@ import ( fakeautoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1/fake" autoscalingv2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2" fakeautoscalingv2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2/fake" - autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1" - fakeautoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake" - autoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2" - fakeautoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake" batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" fakebatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1/fake" batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1" @@ -124,8 +120,8 @@ import ( fakeresourcev1beta2 "k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake" schedulingv1 "k8s.io/client-go/kubernetes/typed/scheduling/v1" fakeschedulingv1 "k8s.io/client-go/kubernetes/typed/scheduling/v1/fake" - schedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1" - fakeschedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake" + schedulingv1alpha2 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2" + fakeschedulingv1alpha2 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake" schedulingv1beta1 "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1" fakeschedulingv1beta1 "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1/fake" storagev1 "k8s.io/client-go/kubernetes/typed/storage/v1" @@ -188,7 +184,7 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } -// IsWatchListSemanticsSupported informs the reflector that this client +// IsWatchListSemanticsUnSupported informs the reflector that this client // doesn't support WatchList semantics. // // This is a synthetic method whose sole purpose is to satisfy the optional @@ -314,16 +310,6 @@ func (c *Clientset) AutoscalingV2() autoscalingv2.AutoscalingV2Interface { return &fakeautoscalingv2.FakeAutoscalingV2{Fake: &c.Fake} } -// AutoscalingV2beta1 retrieves the AutoscalingV2beta1Client -func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface { - return &fakeautoscalingv2beta1.FakeAutoscalingV2beta1{Fake: &c.Fake} -} - -// AutoscalingV2beta2 retrieves the AutoscalingV2beta2Client -func (c *Clientset) AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface { - return &fakeautoscalingv2beta2.FakeAutoscalingV2beta2{Fake: &c.Fake} -} - // BatchV1 retrieves the BatchV1Client func (c *Clientset) BatchV1() batchv1.BatchV1Interface { return &fakebatchv1.FakeBatchV1{Fake: &c.Fake} @@ -484,9 +470,9 @@ func (c *Clientset) ResourceV1alpha3() resourcev1alpha3.ResourceV1alpha3Interfac return &fakeresourcev1alpha3.FakeResourceV1alpha3{Fake: &c.Fake} } -// SchedulingV1alpha1 retrieves the SchedulingV1alpha1Client -func (c *Clientset) SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface { - return &fakeschedulingv1alpha1.FakeSchedulingV1alpha1{Fake: &c.Fake} +// SchedulingV1alpha2 retrieves the SchedulingV1alpha2Client +func (c *Clientset) SchedulingV1alpha2() schedulingv1alpha2.SchedulingV1alpha2Interface { + return &fakeschedulingv1alpha2.FakeSchedulingV1alpha2{Fake: &c.Fake} } // SchedulingV1beta1 retrieves the SchedulingV1beta1Client diff --git a/staging/src/k8s.io/client-go/kubernetes/fake/register.go b/staging/src/k8s.io/client-go/kubernetes/fake/register.go index 7a2bacfb573d3..3b5d98c6a8a9c 100644 --- a/staging/src/k8s.io/client-go/kubernetes/fake/register.go +++ b/staging/src/k8s.io/client-go/kubernetes/fake/register.go @@ -33,8 +33,6 @@ import ( authorizationv1beta1 "k8s.io/api/authorization/v1beta1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1 "k8s.io/api/certificates/v1" @@ -68,7 +66,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingv1 "k8s.io/api/scheduling/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" storagev1 "k8s.io/api/storage/v1" storagev1alpha1 "k8s.io/api/storage/v1alpha1" @@ -99,8 +97,6 @@ var localSchemeBuilder = runtime.SchemeBuilder{ authorizationv1beta1.AddToScheme, autoscalingv1.AddToScheme, autoscalingv2.AddToScheme, - autoscalingv2beta1.AddToScheme, - autoscalingv2beta2.AddToScheme, batchv1.AddToScheme, batchv1beta1.AddToScheme, certificatesv1.AddToScheme, @@ -133,7 +129,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ resourcev1beta2.AddToScheme, resourcev1beta1.AddToScheme, resourcev1alpha3.AddToScheme, - schedulingv1alpha1.AddToScheme, + schedulingv1alpha2.AddToScheme, schedulingv1beta1.AddToScheme, schedulingv1.AddToScheme, storagev1beta1.AddToScheme, diff --git a/staging/src/k8s.io/client-go/kubernetes/scheme/register.go b/staging/src/k8s.io/client-go/kubernetes/scheme/register.go index 9557cba2b4436..c36fe7eda440f 100644 --- a/staging/src/k8s.io/client-go/kubernetes/scheme/register.go +++ b/staging/src/k8s.io/client-go/kubernetes/scheme/register.go @@ -33,8 +33,6 @@ import ( authorizationv1beta1 "k8s.io/api/authorization/v1beta1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1 "k8s.io/api/certificates/v1" @@ -68,7 +66,7 @@ import ( resourcev1beta1 "k8s.io/api/resource/v1beta1" resourcev1beta2 "k8s.io/api/resource/v1beta2" schedulingv1 "k8s.io/api/scheduling/v1" - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" storagev1 "k8s.io/api/storage/v1" storagev1alpha1 "k8s.io/api/storage/v1alpha1" @@ -99,8 +97,6 @@ var localSchemeBuilder = runtime.SchemeBuilder{ authorizationv1beta1.AddToScheme, autoscalingv1.AddToScheme, autoscalingv2.AddToScheme, - autoscalingv2beta1.AddToScheme, - autoscalingv2beta2.AddToScheme, batchv1.AddToScheme, batchv1beta1.AddToScheme, certificatesv1.AddToScheme, @@ -133,7 +129,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ resourcev1beta2.AddToScheme, resourcev1beta1.AddToScheme, resourcev1alpha3.AddToScheme, - schedulingv1alpha1.AddToScheme, + schedulingv1alpha2.AddToScheme, schedulingv1beta1.AddToScheme, schedulingv1.AddToScheme, storagev1beta1.AddToScheme, diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/admissionregistration_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/admissionregistration_client.go index 859ad913b91f7..e09930b7ce4fe 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/admissionregistration_client.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/admissionregistration_client.go @@ -28,6 +28,8 @@ import ( type AdmissionregistrationV1Interface interface { RESTClient() rest.Interface + MutatingAdmissionPoliciesGetter + MutatingAdmissionPolicyBindingsGetter MutatingWebhookConfigurationsGetter ValidatingAdmissionPoliciesGetter ValidatingAdmissionPolicyBindingsGetter @@ -39,6 +41,14 @@ type AdmissionregistrationV1Client struct { restClient rest.Interface } +func (c *AdmissionregistrationV1Client) MutatingAdmissionPolicies() MutatingAdmissionPolicyInterface { + return newMutatingAdmissionPolicies(c) +} + +func (c *AdmissionregistrationV1Client) MutatingAdmissionPolicyBindings() MutatingAdmissionPolicyBindingInterface { + return newMutatingAdmissionPolicyBindings(c) +} + func (c *AdmissionregistrationV1Client) MutatingWebhookConfigurations() MutatingWebhookConfigurationInterface { return newMutatingWebhookConfigurations(c) } diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_admissionregistration_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_admissionregistration_client.go index 1a948ad93d461..fbe72e9e54b59 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_admissionregistration_client.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_admissionregistration_client.go @@ -28,6 +28,14 @@ type FakeAdmissionregistrationV1 struct { *testing.Fake } +func (c *FakeAdmissionregistrationV1) MutatingAdmissionPolicies() v1.MutatingAdmissionPolicyInterface { + return newFakeMutatingAdmissionPolicies(c) +} + +func (c *FakeAdmissionregistrationV1) MutatingAdmissionPolicyBindings() v1.MutatingAdmissionPolicyBindingInterface { + return newFakeMutatingAdmissionPolicyBindings(c) +} + func (c *FakeAdmissionregistrationV1) MutatingWebhookConfigurations() v1.MutatingWebhookConfigurationInterface { return newFakeMutatingWebhookConfigurations(c) } diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_mutatingadmissionpolicy.go new file mode 100644 index 0000000000000..80e703b1cd21f --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_mutatingadmissionpolicy.go @@ -0,0 +1,53 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/client-go/applyconfigurations/admissionregistration/v1" + gentype "k8s.io/client-go/gentype" + typedadmissionregistrationv1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1" +) + +// fakeMutatingAdmissionPolicies implements MutatingAdmissionPolicyInterface +type fakeMutatingAdmissionPolicies struct { + *gentype.FakeClientWithListAndApply[*v1.MutatingAdmissionPolicy, *v1.MutatingAdmissionPolicyList, *admissionregistrationv1.MutatingAdmissionPolicyApplyConfiguration] + Fake *FakeAdmissionregistrationV1 +} + +func newFakeMutatingAdmissionPolicies(fake *FakeAdmissionregistrationV1) typedadmissionregistrationv1.MutatingAdmissionPolicyInterface { + return &fakeMutatingAdmissionPolicies{ + gentype.NewFakeClientWithListAndApply[*v1.MutatingAdmissionPolicy, *v1.MutatingAdmissionPolicyList, *admissionregistrationv1.MutatingAdmissionPolicyApplyConfiguration]( + fake.Fake, + "", + v1.SchemeGroupVersion.WithResource("mutatingadmissionpolicies"), + v1.SchemeGroupVersion.WithKind("MutatingAdmissionPolicy"), + func() *v1.MutatingAdmissionPolicy { return &v1.MutatingAdmissionPolicy{} }, + func() *v1.MutatingAdmissionPolicyList { return &v1.MutatingAdmissionPolicyList{} }, + func(dst, src *v1.MutatingAdmissionPolicyList) { dst.ListMeta = src.ListMeta }, + func(list *v1.MutatingAdmissionPolicyList) []*v1.MutatingAdmissionPolicy { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1.MutatingAdmissionPolicyList, items []*v1.MutatingAdmissionPolicy) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_mutatingadmissionpolicybinding.go new file mode 100644 index 0000000000000..eba7c8406e1cc --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake/fake_mutatingadmissionpolicybinding.go @@ -0,0 +1,53 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/client-go/applyconfigurations/admissionregistration/v1" + gentype "k8s.io/client-go/gentype" + typedadmissionregistrationv1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1" +) + +// fakeMutatingAdmissionPolicyBindings implements MutatingAdmissionPolicyBindingInterface +type fakeMutatingAdmissionPolicyBindings struct { + *gentype.FakeClientWithListAndApply[*v1.MutatingAdmissionPolicyBinding, *v1.MutatingAdmissionPolicyBindingList, *admissionregistrationv1.MutatingAdmissionPolicyBindingApplyConfiguration] + Fake *FakeAdmissionregistrationV1 +} + +func newFakeMutatingAdmissionPolicyBindings(fake *FakeAdmissionregistrationV1) typedadmissionregistrationv1.MutatingAdmissionPolicyBindingInterface { + return &fakeMutatingAdmissionPolicyBindings{ + gentype.NewFakeClientWithListAndApply[*v1.MutatingAdmissionPolicyBinding, *v1.MutatingAdmissionPolicyBindingList, *admissionregistrationv1.MutatingAdmissionPolicyBindingApplyConfiguration]( + fake.Fake, + "", + v1.SchemeGroupVersion.WithResource("mutatingadmissionpolicybindings"), + v1.SchemeGroupVersion.WithKind("MutatingAdmissionPolicyBinding"), + func() *v1.MutatingAdmissionPolicyBinding { return &v1.MutatingAdmissionPolicyBinding{} }, + func() *v1.MutatingAdmissionPolicyBindingList { return &v1.MutatingAdmissionPolicyBindingList{} }, + func(dst, src *v1.MutatingAdmissionPolicyBindingList) { dst.ListMeta = src.ListMeta }, + func(list *v1.MutatingAdmissionPolicyBindingList) []*v1.MutatingAdmissionPolicyBinding { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1.MutatingAdmissionPolicyBindingList, items []*v1.MutatingAdmissionPolicyBinding) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/generated_expansion.go index d81e1c87fcd38..c449e9b7d7877 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/generated_expansion.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/generated_expansion.go @@ -18,6 +18,10 @@ limitations under the License. package v1 +type MutatingAdmissionPolicyExpansion interface{} + +type MutatingAdmissionPolicyBindingExpansion interface{} + type MutatingWebhookConfigurationExpansion interface{} type ValidatingAdmissionPolicyExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/mutatingadmissionpolicy.go new file mode 100644 index 0000000000000..d0e85396cccdf --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/mutatingadmissionpolicy.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsadmissionregistrationv1 "k8s.io/client-go/applyconfigurations/admissionregistration/v1" + gentype "k8s.io/client-go/gentype" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +// MutatingAdmissionPoliciesGetter has a method to return a MutatingAdmissionPolicyInterface. +// A group's client should implement this interface. +type MutatingAdmissionPoliciesGetter interface { + MutatingAdmissionPolicies() MutatingAdmissionPolicyInterface +} + +// MutatingAdmissionPolicyInterface has methods to work with MutatingAdmissionPolicy resources. +type MutatingAdmissionPolicyInterface interface { + Create(ctx context.Context, mutatingAdmissionPolicy *admissionregistrationv1.MutatingAdmissionPolicy, opts metav1.CreateOptions) (*admissionregistrationv1.MutatingAdmissionPolicy, error) + Update(ctx context.Context, mutatingAdmissionPolicy *admissionregistrationv1.MutatingAdmissionPolicy, opts metav1.UpdateOptions) (*admissionregistrationv1.MutatingAdmissionPolicy, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*admissionregistrationv1.MutatingAdmissionPolicy, error) + List(ctx context.Context, opts metav1.ListOptions) (*admissionregistrationv1.MutatingAdmissionPolicyList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *admissionregistrationv1.MutatingAdmissionPolicy, err error) + Apply(ctx context.Context, mutatingAdmissionPolicy *applyconfigurationsadmissionregistrationv1.MutatingAdmissionPolicyApplyConfiguration, opts metav1.ApplyOptions) (result *admissionregistrationv1.MutatingAdmissionPolicy, err error) + MutatingAdmissionPolicyExpansion +} + +// mutatingAdmissionPolicies implements MutatingAdmissionPolicyInterface +type mutatingAdmissionPolicies struct { + *gentype.ClientWithListAndApply[*admissionregistrationv1.MutatingAdmissionPolicy, *admissionregistrationv1.MutatingAdmissionPolicyList, *applyconfigurationsadmissionregistrationv1.MutatingAdmissionPolicyApplyConfiguration] +} + +// newMutatingAdmissionPolicies returns a MutatingAdmissionPolicies +func newMutatingAdmissionPolicies(c *AdmissionregistrationV1Client) *mutatingAdmissionPolicies { + return &mutatingAdmissionPolicies{ + gentype.NewClientWithListAndApply[*admissionregistrationv1.MutatingAdmissionPolicy, *admissionregistrationv1.MutatingAdmissionPolicyList, *applyconfigurationsadmissionregistrationv1.MutatingAdmissionPolicyApplyConfiguration]( + "mutatingadmissionpolicies", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *admissionregistrationv1.MutatingAdmissionPolicy { + return &admissionregistrationv1.MutatingAdmissionPolicy{} + }, + func() *admissionregistrationv1.MutatingAdmissionPolicyList { + return &admissionregistrationv1.MutatingAdmissionPolicyList{} + }, + gentype.PrefersProtobuf[*admissionregistrationv1.MutatingAdmissionPolicy](), + ), + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/mutatingadmissionpolicybinding.go new file mode 100644 index 0000000000000..67b4aa9a6b54d --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1/mutatingadmissionpolicybinding.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsadmissionregistrationv1 "k8s.io/client-go/applyconfigurations/admissionregistration/v1" + gentype "k8s.io/client-go/gentype" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +// MutatingAdmissionPolicyBindingsGetter has a method to return a MutatingAdmissionPolicyBindingInterface. +// A group's client should implement this interface. +type MutatingAdmissionPolicyBindingsGetter interface { + MutatingAdmissionPolicyBindings() MutatingAdmissionPolicyBindingInterface +} + +// MutatingAdmissionPolicyBindingInterface has methods to work with MutatingAdmissionPolicyBinding resources. +type MutatingAdmissionPolicyBindingInterface interface { + Create(ctx context.Context, mutatingAdmissionPolicyBinding *admissionregistrationv1.MutatingAdmissionPolicyBinding, opts metav1.CreateOptions) (*admissionregistrationv1.MutatingAdmissionPolicyBinding, error) + Update(ctx context.Context, mutatingAdmissionPolicyBinding *admissionregistrationv1.MutatingAdmissionPolicyBinding, opts metav1.UpdateOptions) (*admissionregistrationv1.MutatingAdmissionPolicyBinding, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*admissionregistrationv1.MutatingAdmissionPolicyBinding, error) + List(ctx context.Context, opts metav1.ListOptions) (*admissionregistrationv1.MutatingAdmissionPolicyBindingList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *admissionregistrationv1.MutatingAdmissionPolicyBinding, err error) + Apply(ctx context.Context, mutatingAdmissionPolicyBinding *applyconfigurationsadmissionregistrationv1.MutatingAdmissionPolicyBindingApplyConfiguration, opts metav1.ApplyOptions) (result *admissionregistrationv1.MutatingAdmissionPolicyBinding, err error) + MutatingAdmissionPolicyBindingExpansion +} + +// mutatingAdmissionPolicyBindings implements MutatingAdmissionPolicyBindingInterface +type mutatingAdmissionPolicyBindings struct { + *gentype.ClientWithListAndApply[*admissionregistrationv1.MutatingAdmissionPolicyBinding, *admissionregistrationv1.MutatingAdmissionPolicyBindingList, *applyconfigurationsadmissionregistrationv1.MutatingAdmissionPolicyBindingApplyConfiguration] +} + +// newMutatingAdmissionPolicyBindings returns a MutatingAdmissionPolicyBindings +func newMutatingAdmissionPolicyBindings(c *AdmissionregistrationV1Client) *mutatingAdmissionPolicyBindings { + return &mutatingAdmissionPolicyBindings{ + gentype.NewClientWithListAndApply[*admissionregistrationv1.MutatingAdmissionPolicyBinding, *admissionregistrationv1.MutatingAdmissionPolicyBindingList, *applyconfigurationsadmissionregistrationv1.MutatingAdmissionPolicyBindingApplyConfiguration]( + "mutatingadmissionpolicybindings", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *admissionregistrationv1.MutatingAdmissionPolicyBinding { + return &admissionregistrationv1.MutatingAdmissionPolicyBinding{} + }, + func() *admissionregistrationv1.MutatingAdmissionPolicyBindingList { + return &admissionregistrationv1.MutatingAdmissionPolicyBindingList{} + }, + gentype.PrefersProtobuf[*admissionregistrationv1.MutatingAdmissionPolicyBinding](), + ), + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/autoscaling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/autoscaling_client.go deleted file mode 100644 index 25e38f371d56c..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/autoscaling_client.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v2beta1 - -import ( - http "net/http" - - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - scheme "k8s.io/client-go/kubernetes/scheme" - rest "k8s.io/client-go/rest" -) - -type AutoscalingV2beta1Interface interface { - RESTClient() rest.Interface - HorizontalPodAutoscalersGetter -} - -// AutoscalingV2beta1Client is used to interact with features provided by the autoscaling group. -type AutoscalingV2beta1Client struct { - restClient rest.Interface -} - -func (c *AutoscalingV2beta1Client) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { - return newHorizontalPodAutoscalers(c, namespace) -} - -// NewForConfig creates a new AutoscalingV2beta1Client for the given config. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). -func NewForConfig(c *rest.Config) (*AutoscalingV2beta1Client, error) { - config := *c - setConfigDefaults(&config) - httpClient, err := rest.HTTPClientFor(&config) - if err != nil { - return nil, err - } - return NewForConfigAndClient(&config, httpClient) -} - -// NewForConfigAndClient creates a new AutoscalingV2beta1Client for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AutoscalingV2beta1Client, error) { - config := *c - setConfigDefaults(&config) - client, err := rest.RESTClientForConfigAndClient(&config, h) - if err != nil { - return nil, err - } - return &AutoscalingV2beta1Client{client}, nil -} - -// NewForConfigOrDie creates a new AutoscalingV2beta1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *AutoscalingV2beta1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new AutoscalingV2beta1Client for the given RESTClient. -func New(c rest.Interface) *AutoscalingV2beta1Client { - return &AutoscalingV2beta1Client{c} -} - -func setConfigDefaults(config *rest.Config) { - gv := autoscalingv2beta1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *AutoscalingV2beta1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/doc.go deleted file mode 100644 index 06fd344c0105f..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated typed clients. -package v2beta1 diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/fake_autoscaling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/fake_autoscaling_client.go deleted file mode 100644 index ca4b461c2ec19..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/fake_autoscaling_client.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1" - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" -) - -type FakeAutoscalingV2beta1 struct { - *testing.Fake -} - -func (c *FakeAutoscalingV2beta1) HorizontalPodAutoscalers(namespace string) v2beta1.HorizontalPodAutoscalerInterface { - return newFakeHorizontalPodAutoscalers(c, namespace) -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeAutoscalingV2beta1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/fake_horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/fake_horizontalpodautoscaler.go deleted file mode 100644 index 8f28fb30c46fb..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/fake_horizontalpodautoscaler.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v2beta1 "k8s.io/api/autoscaling/v2beta1" - autoscalingv2beta1 "k8s.io/client-go/applyconfigurations/autoscaling/v2beta1" - gentype "k8s.io/client-go/gentype" - typedautoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1" -) - -// fakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface -type fakeHorizontalPodAutoscalers struct { - *gentype.FakeClientWithListAndApply[*v2beta1.HorizontalPodAutoscaler, *v2beta1.HorizontalPodAutoscalerList, *autoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration] - Fake *FakeAutoscalingV2beta1 -} - -func newFakeHorizontalPodAutoscalers(fake *FakeAutoscalingV2beta1, namespace string) typedautoscalingv2beta1.HorizontalPodAutoscalerInterface { - return &fakeHorizontalPodAutoscalers{ - gentype.NewFakeClientWithListAndApply[*v2beta1.HorizontalPodAutoscaler, *v2beta1.HorizontalPodAutoscalerList, *autoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration]( - fake.Fake, - namespace, - v2beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), - v2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"), - func() *v2beta1.HorizontalPodAutoscaler { return &v2beta1.HorizontalPodAutoscaler{} }, - func() *v2beta1.HorizontalPodAutoscalerList { return &v2beta1.HorizontalPodAutoscalerList{} }, - func(dst, src *v2beta1.HorizontalPodAutoscalerList) { dst.ListMeta = src.ListMeta }, - func(list *v2beta1.HorizontalPodAutoscalerList) []*v2beta1.HorizontalPodAutoscaler { - return gentype.ToPointerSlice(list.Items) - }, - func(list *v2beta1.HorizontalPodAutoscalerList, items []*v2beta1.HorizontalPodAutoscaler) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/generated_expansion.go deleted file mode 100644 index 6f1704f1ee67f..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/generated_expansion.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v2beta1 - -type HorizontalPodAutoscalerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/horizontalpodautoscaler.go deleted file mode 100644 index c1dc75ccc32c8..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/horizontalpodautoscaler.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v2beta1 - -import ( - context "context" - - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - applyconfigurationsautoscalingv2beta1 "k8s.io/client-go/applyconfigurations/autoscaling/v2beta1" - gentype "k8s.io/client-go/gentype" - scheme "k8s.io/client-go/kubernetes/scheme" -) - -// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface. -// A group's client should implement this interface. -type HorizontalPodAutoscalersGetter interface { - HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface -} - -// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources. -type HorizontalPodAutoscalerInterface interface { - Create(ctx context.Context, horizontalPodAutoscaler *autoscalingv2beta1.HorizontalPodAutoscaler, opts v1.CreateOptions) (*autoscalingv2beta1.HorizontalPodAutoscaler, error) - Update(ctx context.Context, horizontalPodAutoscaler *autoscalingv2beta1.HorizontalPodAutoscaler, opts v1.UpdateOptions) (*autoscalingv2beta1.HorizontalPodAutoscaler, error) - // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, horizontalPodAutoscaler *autoscalingv2beta1.HorizontalPodAutoscaler, opts v1.UpdateOptions) (*autoscalingv2beta1.HorizontalPodAutoscaler, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*autoscalingv2beta1.HorizontalPodAutoscaler, error) - List(ctx context.Context, opts v1.ListOptions) (*autoscalingv2beta1.HorizontalPodAutoscalerList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *autoscalingv2beta1.HorizontalPodAutoscaler, err error) - Apply(ctx context.Context, horizontalPodAutoscaler *applyconfigurationsautoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration, opts v1.ApplyOptions) (result *autoscalingv2beta1.HorizontalPodAutoscaler, err error) - // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). - ApplyStatus(ctx context.Context, horizontalPodAutoscaler *applyconfigurationsautoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration, opts v1.ApplyOptions) (result *autoscalingv2beta1.HorizontalPodAutoscaler, err error) - HorizontalPodAutoscalerExpansion -} - -// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface -type horizontalPodAutoscalers struct { - *gentype.ClientWithListAndApply[*autoscalingv2beta1.HorizontalPodAutoscaler, *autoscalingv2beta1.HorizontalPodAutoscalerList, *applyconfigurationsautoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration] -} - -// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers -func newHorizontalPodAutoscalers(c *AutoscalingV2beta1Client, namespace string) *horizontalPodAutoscalers { - return &horizontalPodAutoscalers{ - gentype.NewClientWithListAndApply[*autoscalingv2beta1.HorizontalPodAutoscaler, *autoscalingv2beta1.HorizontalPodAutoscalerList, *applyconfigurationsautoscalingv2beta1.HorizontalPodAutoscalerApplyConfiguration]( - "horizontalpodautoscalers", - c.RESTClient(), - scheme.ParameterCodec, - namespace, - func() *autoscalingv2beta1.HorizontalPodAutoscaler { - return &autoscalingv2beta1.HorizontalPodAutoscaler{} - }, - func() *autoscalingv2beta1.HorizontalPodAutoscalerList { - return &autoscalingv2beta1.HorizontalPodAutoscalerList{} - }, - gentype.PrefersProtobuf[*autoscalingv2beta1.HorizontalPodAutoscaler](), - ), - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go deleted file mode 100644 index 21626e5ce7171..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v2beta2 - -import ( - http "net/http" - - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - scheme "k8s.io/client-go/kubernetes/scheme" - rest "k8s.io/client-go/rest" -) - -type AutoscalingV2beta2Interface interface { - RESTClient() rest.Interface - HorizontalPodAutoscalersGetter -} - -// AutoscalingV2beta2Client is used to interact with features provided by the autoscaling group. -type AutoscalingV2beta2Client struct { - restClient rest.Interface -} - -func (c *AutoscalingV2beta2Client) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { - return newHorizontalPodAutoscalers(c, namespace) -} - -// NewForConfig creates a new AutoscalingV2beta2Client for the given config. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). -func NewForConfig(c *rest.Config) (*AutoscalingV2beta2Client, error) { - config := *c - setConfigDefaults(&config) - httpClient, err := rest.HTTPClientFor(&config) - if err != nil { - return nil, err - } - return NewForConfigAndClient(&config, httpClient) -} - -// NewForConfigAndClient creates a new AutoscalingV2beta2Client for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AutoscalingV2beta2Client, error) { - config := *c - setConfigDefaults(&config) - client, err := rest.RESTClientForConfigAndClient(&config, h) - if err != nil { - return nil, err - } - return &AutoscalingV2beta2Client{client}, nil -} - -// NewForConfigOrDie creates a new AutoscalingV2beta2Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *AutoscalingV2beta2Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new AutoscalingV2beta2Client for the given RESTClient. -func New(c rest.Interface) *AutoscalingV2beta2Client { - return &AutoscalingV2beta2Client{c} -} - -func setConfigDefaults(config *rest.Config) { - gv := autoscalingv2beta2.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *AutoscalingV2beta2Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/doc.go deleted file mode 100644 index c600965946aab..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated typed clients. -package v2beta2 diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/doc.go deleted file mode 100644 index 16f44399065ed..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// Package fake has the automatically generated clients. -package fake diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/fake_autoscaling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/fake_autoscaling_client.go deleted file mode 100644 index 99b5026bf7b58..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/fake_autoscaling_client.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2" - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" -) - -type FakeAutoscalingV2beta2 struct { - *testing.Fake -} - -func (c *FakeAutoscalingV2beta2) HorizontalPodAutoscalers(namespace string) v2beta2.HorizontalPodAutoscalerInterface { - return newFakeHorizontalPodAutoscalers(c, namespace) -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeAutoscalingV2beta2) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/fake_horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/fake_horizontalpodautoscaler.go deleted file mode 100644 index f747da9d0f13b..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake/fake_horizontalpodautoscaler.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v2beta2 "k8s.io/api/autoscaling/v2beta2" - autoscalingv2beta2 "k8s.io/client-go/applyconfigurations/autoscaling/v2beta2" - gentype "k8s.io/client-go/gentype" - typedautoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2" -) - -// fakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface -type fakeHorizontalPodAutoscalers struct { - *gentype.FakeClientWithListAndApply[*v2beta2.HorizontalPodAutoscaler, *v2beta2.HorizontalPodAutoscalerList, *autoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration] - Fake *FakeAutoscalingV2beta2 -} - -func newFakeHorizontalPodAutoscalers(fake *FakeAutoscalingV2beta2, namespace string) typedautoscalingv2beta2.HorizontalPodAutoscalerInterface { - return &fakeHorizontalPodAutoscalers{ - gentype.NewFakeClientWithListAndApply[*v2beta2.HorizontalPodAutoscaler, *v2beta2.HorizontalPodAutoscalerList, *autoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration]( - fake.Fake, - namespace, - v2beta2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), - v2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"), - func() *v2beta2.HorizontalPodAutoscaler { return &v2beta2.HorizontalPodAutoscaler{} }, - func() *v2beta2.HorizontalPodAutoscalerList { return &v2beta2.HorizontalPodAutoscalerList{} }, - func(dst, src *v2beta2.HorizontalPodAutoscalerList) { dst.ListMeta = src.ListMeta }, - func(list *v2beta2.HorizontalPodAutoscalerList) []*v2beta2.HorizontalPodAutoscaler { - return gentype.ToPointerSlice(list.Items) - }, - func(list *v2beta2.HorizontalPodAutoscalerList, items []*v2beta2.HorizontalPodAutoscaler) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/generated_expansion.go deleted file mode 100644 index 822e06287212a..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/generated_expansion.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v2beta2 - -type HorizontalPodAutoscalerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go deleted file mode 100644 index 017b3e1fc0f52..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v2beta2 - -import ( - context "context" - - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - applyconfigurationsautoscalingv2beta2 "k8s.io/client-go/applyconfigurations/autoscaling/v2beta2" - gentype "k8s.io/client-go/gentype" - scheme "k8s.io/client-go/kubernetes/scheme" -) - -// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface. -// A group's client should implement this interface. -type HorizontalPodAutoscalersGetter interface { - HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface -} - -// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources. -type HorizontalPodAutoscalerInterface interface { - Create(ctx context.Context, horizontalPodAutoscaler *autoscalingv2beta2.HorizontalPodAutoscaler, opts v1.CreateOptions) (*autoscalingv2beta2.HorizontalPodAutoscaler, error) - Update(ctx context.Context, horizontalPodAutoscaler *autoscalingv2beta2.HorizontalPodAutoscaler, opts v1.UpdateOptions) (*autoscalingv2beta2.HorizontalPodAutoscaler, error) - // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, horizontalPodAutoscaler *autoscalingv2beta2.HorizontalPodAutoscaler, opts v1.UpdateOptions) (*autoscalingv2beta2.HorizontalPodAutoscaler, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*autoscalingv2beta2.HorizontalPodAutoscaler, error) - List(ctx context.Context, opts v1.ListOptions) (*autoscalingv2beta2.HorizontalPodAutoscalerList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *autoscalingv2beta2.HorizontalPodAutoscaler, err error) - Apply(ctx context.Context, horizontalPodAutoscaler *applyconfigurationsautoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration, opts v1.ApplyOptions) (result *autoscalingv2beta2.HorizontalPodAutoscaler, err error) - // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). - ApplyStatus(ctx context.Context, horizontalPodAutoscaler *applyconfigurationsautoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration, opts v1.ApplyOptions) (result *autoscalingv2beta2.HorizontalPodAutoscaler, err error) - HorizontalPodAutoscalerExpansion -} - -// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface -type horizontalPodAutoscalers struct { - *gentype.ClientWithListAndApply[*autoscalingv2beta2.HorizontalPodAutoscaler, *autoscalingv2beta2.HorizontalPodAutoscalerList, *applyconfigurationsautoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration] -} - -// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers -func newHorizontalPodAutoscalers(c *AutoscalingV2beta2Client, namespace string) *horizontalPodAutoscalers { - return &horizontalPodAutoscalers{ - gentype.NewClientWithListAndApply[*autoscalingv2beta2.HorizontalPodAutoscaler, *autoscalingv2beta2.HorizontalPodAutoscalerList, *applyconfigurationsautoscalingv2beta2.HorizontalPodAutoscalerApplyConfiguration]( - "horizontalpodautoscalers", - c.RESTClient(), - scheme.ParameterCodec, - namespace, - func() *autoscalingv2beta2.HorizontalPodAutoscaler { - return &autoscalingv2beta2.HorizontalPodAutoscaler{} - }, - func() *autoscalingv2beta2.HorizontalPodAutoscalerList { - return &autoscalingv2beta2.HorizontalPodAutoscalerList{} - }, - gentype.PrefersProtobuf[*autoscalingv2beta2.HorizontalPodAutoscaler](), - ), - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion.go index 3fbb89ad43f5e..2b607298caca8 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion.go @@ -17,16 +17,17 @@ limitations under the License. package fake import ( + "bytes" "context" "fmt" "io" "net/http" - "strings" v1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" restclient "k8s.io/client-go/rest" fakerest "k8s.io/client-go/rest/fake" @@ -63,12 +64,26 @@ func (c *fakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Requ action.Subresource = "log" action.Value = opts - _, _ = c.Fake.Invokes(action, &v1.Pod{}) + defaultLogResponse := &runtime.Unknown{Raw: []byte("fake logs")} + obj, err := c.Fake.Invokes(action, defaultLogResponse) + logs := defaultLogResponse.Raw + if err == nil { + unknown, ok := obj.(*runtime.Unknown) + if !ok || unknown == nil { + err = fmt.Errorf("fake Pods.GetLogs expected reactor to return *runtime.Unknown, got %T", obj) + } else { + logs = unknown.Raw + } + } + fakeClient := &fakerest.RESTClient{ Client: fakerest.CreateHTTPClient(func(request *http.Request) (*http.Response, error) { + if err != nil { + return nil, err + } resp := &http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader("fake logs")), + Body: io.NopCloser(bytes.NewReader(logs)), } return resp, nil }), diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go b/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go index 03bf42a9cfdc1..15a4fe473ae13 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go @@ -19,10 +19,13 @@ package fake import ( "bytes" "context" + "errors" "io" + "strings" "testing" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" cgtesting "k8s.io/client-go/testing" ) @@ -46,3 +49,74 @@ func TestFakePodsGetLogs(t *testing.T) { t.Fatal("Close response body:", err) } } + +func TestFakePodsGetLogsReactorError(t *testing.T) { + fake := &cgtesting.Fake{} + fp := newFakePods(&FakeCoreV1{Fake: fake}, "default") + expectedErr := errors.New("reactor get logs failure") + fake.PrependReactor("get", "pods/log", func(action cgtesting.Action) (bool, runtime.Object, error) { + genericAction, ok := action.(cgtesting.GenericAction) + if !ok { + t.Fatalf("expected GenericAction, got %T", action) + } + opts, ok := genericAction.GetValue().(*corev1.PodLogOptions) + if !ok { + t.Fatalf("expected *corev1.PodLogOptions, got %T", genericAction.GetValue()) + } + if opts.Container != "ctr" { + t.Fatalf("expected container ctr, got %q", opts.Container) + } + return true, nil, expectedErr + }) + + req := fp.GetLogs("foo", &corev1.PodLogOptions{Container: "ctr"}) + _, err := req.Stream(context.Background()) + if !errors.Is(err, expectedErr) { + t.Fatalf("expected stream error %v, got %v", expectedErr, err) + } +} + +func TestFakePodsGetLogsReactorResponse(t *testing.T) { + fake := &cgtesting.Fake{} + fp := newFakePods(&FakeCoreV1{Fake: fake}, "default") + expectedLogs := "reactor logs" + fake.PrependReactor("get", "pods/log", func(action cgtesting.Action) (bool, runtime.Object, error) { + return true, &runtime.Unknown{Raw: []byte(expectedLogs)}, nil + }) + + req := fp.GetLogs("foo", &corev1.PodLogOptions{}) + body, err := req.Stream(context.Background()) + if err != nil { + t.Fatalf("Stream pod logs: %v", err) + } + defer func() { + if err := body.Close(); err != nil { + t.Fatalf("Close response body: %v", err) + } + }() + + logs, err := io.ReadAll(body) + if err != nil { + t.Fatalf("Read pod logs: %v", err) + } + if string(logs) != expectedLogs { + t.Fatalf("expected logs %q, got %q", expectedLogs, string(logs)) + } +} + +func TestFakePodsGetLogsReactorInvalidObject(t *testing.T) { + fake := &cgtesting.Fake{} + fp := newFakePods(&FakeCoreV1{Fake: fake}, "default") + fake.PrependReactor("get", "pods/log", func(action cgtesting.Action) (bool, runtime.Object, error) { + return true, &corev1.Pod{}, nil + }) + + req := fp.GetLogs("foo", &corev1.PodLogOptions{}) + _, err := req.Stream(context.Background()) + if err == nil { + t.Fatal("expected stream error") + } + if !strings.Contains(err.Error(), "expected reactor to return *runtime.Unknown") { + t.Fatalf("expected helpful reactor object type error, got: %v", err) + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resource_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resource_client.go index 75bc9f9d33ff7..99a5e8d8770d8 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resource_client.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resource_client.go @@ -32,6 +32,10 @@ func (c *FakeResourceV1alpha3) DeviceTaintRules() v1alpha3.DeviceTaintRuleInterf return newFakeDeviceTaintRules(c) } +func (c *FakeResourceV1alpha3) ResourcePoolStatusRequests() v1alpha3.ResourcePoolStatusRequestInterface { + return newFakeResourcePoolStatusRequests(c) +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeResourceV1alpha3) RESTClient() rest.Interface { diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resourcepoolstatusrequest.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resourcepoolstatusrequest.go new file mode 100644 index 0000000000000..6196ddc1f6668 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_resourcepoolstatusrequest.go @@ -0,0 +1,53 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha3 "k8s.io/api/resource/v1alpha3" + resourcev1alpha3 "k8s.io/client-go/applyconfigurations/resource/v1alpha3" + gentype "k8s.io/client-go/gentype" + typedresourcev1alpha3 "k8s.io/client-go/kubernetes/typed/resource/v1alpha3" +) + +// fakeResourcePoolStatusRequests implements ResourcePoolStatusRequestInterface +type fakeResourcePoolStatusRequests struct { + *gentype.FakeClientWithListAndApply[*v1alpha3.ResourcePoolStatusRequest, *v1alpha3.ResourcePoolStatusRequestList, *resourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration] + Fake *FakeResourceV1alpha3 +} + +func newFakeResourcePoolStatusRequests(fake *FakeResourceV1alpha3) typedresourcev1alpha3.ResourcePoolStatusRequestInterface { + return &fakeResourcePoolStatusRequests{ + gentype.NewFakeClientWithListAndApply[*v1alpha3.ResourcePoolStatusRequest, *v1alpha3.ResourcePoolStatusRequestList, *resourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration]( + fake.Fake, + "", + v1alpha3.SchemeGroupVersion.WithResource("resourcepoolstatusrequests"), + v1alpha3.SchemeGroupVersion.WithKind("ResourcePoolStatusRequest"), + func() *v1alpha3.ResourcePoolStatusRequest { return &v1alpha3.ResourcePoolStatusRequest{} }, + func() *v1alpha3.ResourcePoolStatusRequestList { return &v1alpha3.ResourcePoolStatusRequestList{} }, + func(dst, src *v1alpha3.ResourcePoolStatusRequestList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha3.ResourcePoolStatusRequestList) []*v1alpha3.ResourcePoolStatusRequest { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha3.ResourcePoolStatusRequestList, items []*v1alpha3.ResourcePoolStatusRequest) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/generated_expansion.go index 66e7c2b4b1407..f5f9f8c840aea 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/generated_expansion.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/generated_expansion.go @@ -19,3 +19,5 @@ limitations under the License. package v1alpha3 type DeviceTaintRuleExpansion interface{} + +type ResourcePoolStatusRequestExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resource_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resource_client.go index a7e2212fe795a..ad36c6904836b 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resource_client.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resource_client.go @@ -29,6 +29,7 @@ import ( type ResourceV1alpha3Interface interface { RESTClient() rest.Interface DeviceTaintRulesGetter + ResourcePoolStatusRequestsGetter } // ResourceV1alpha3Client is used to interact with features provided by the resource.k8s.io group. @@ -40,6 +41,10 @@ func (c *ResourceV1alpha3Client) DeviceTaintRules() DeviceTaintRuleInterface { return newDeviceTaintRules(c) } +func (c *ResourceV1alpha3Client) ResourcePoolStatusRequests() ResourcePoolStatusRequestInterface { + return newResourcePoolStatusRequests(c) +} + // NewForConfig creates a new ResourceV1alpha3Client for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resourcepoolstatusrequest.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resourcepoolstatusrequest.go new file mode 100644 index 0000000000000..964e48841628e --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/resourcepoolstatusrequest.go @@ -0,0 +1,79 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + context "context" + + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsresourcev1alpha3 "k8s.io/client-go/applyconfigurations/resource/v1alpha3" + gentype "k8s.io/client-go/gentype" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +// ResourcePoolStatusRequestsGetter has a method to return a ResourcePoolStatusRequestInterface. +// A group's client should implement this interface. +type ResourcePoolStatusRequestsGetter interface { + ResourcePoolStatusRequests() ResourcePoolStatusRequestInterface +} + +// ResourcePoolStatusRequestInterface has methods to work with ResourcePoolStatusRequest resources. +type ResourcePoolStatusRequestInterface interface { + Create(ctx context.Context, resourcePoolStatusRequest *resourcev1alpha3.ResourcePoolStatusRequest, opts v1.CreateOptions) (*resourcev1alpha3.ResourcePoolStatusRequest, error) + Update(ctx context.Context, resourcePoolStatusRequest *resourcev1alpha3.ResourcePoolStatusRequest, opts v1.UpdateOptions) (*resourcev1alpha3.ResourcePoolStatusRequest, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, resourcePoolStatusRequest *resourcev1alpha3.ResourcePoolStatusRequest, opts v1.UpdateOptions) (*resourcev1alpha3.ResourcePoolStatusRequest, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*resourcev1alpha3.ResourcePoolStatusRequest, error) + List(ctx context.Context, opts v1.ListOptions) (*resourcev1alpha3.ResourcePoolStatusRequestList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *resourcev1alpha3.ResourcePoolStatusRequest, err error) + Apply(ctx context.Context, resourcePoolStatusRequest *applyconfigurationsresourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration, opts v1.ApplyOptions) (result *resourcev1alpha3.ResourcePoolStatusRequest, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, resourcePoolStatusRequest *applyconfigurationsresourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration, opts v1.ApplyOptions) (result *resourcev1alpha3.ResourcePoolStatusRequest, err error) + ResourcePoolStatusRequestExpansion +} + +// resourcePoolStatusRequests implements ResourcePoolStatusRequestInterface +type resourcePoolStatusRequests struct { + *gentype.ClientWithListAndApply[*resourcev1alpha3.ResourcePoolStatusRequest, *resourcev1alpha3.ResourcePoolStatusRequestList, *applyconfigurationsresourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration] +} + +// newResourcePoolStatusRequests returns a ResourcePoolStatusRequests +func newResourcePoolStatusRequests(c *ResourceV1alpha3Client) *resourcePoolStatusRequests { + return &resourcePoolStatusRequests{ + gentype.NewClientWithListAndApply[*resourcev1alpha3.ResourcePoolStatusRequest, *resourcev1alpha3.ResourcePoolStatusRequestList, *applyconfigurationsresourcev1alpha3.ResourcePoolStatusRequestApplyConfiguration]( + "resourcepoolstatusrequests", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *resourcev1alpha3.ResourcePoolStatusRequest { + return &resourcev1alpha3.ResourcePoolStatusRequest{} + }, + func() *resourcev1alpha3.ResourcePoolStatusRequestList { + return &resourcev1alpha3.ResourcePoolStatusRequestList{} + }, + gentype.PrefersProtobuf[*resourcev1alpha3.ResourcePoolStatusRequest](), + ), + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/devicetaintrule.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/devicetaintrule.go new file mode 100644 index 0000000000000..da915c51f168b --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/devicetaintrule.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + + resourcev1beta2 "k8s.io/api/resource/v1beta2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsresourcev1beta2 "k8s.io/client-go/applyconfigurations/resource/v1beta2" + gentype "k8s.io/client-go/gentype" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +// DeviceTaintRulesGetter has a method to return a DeviceTaintRuleInterface. +// A group's client should implement this interface. +type DeviceTaintRulesGetter interface { + DeviceTaintRules() DeviceTaintRuleInterface +} + +// DeviceTaintRuleInterface has methods to work with DeviceTaintRule resources. +type DeviceTaintRuleInterface interface { + Create(ctx context.Context, deviceTaintRule *resourcev1beta2.DeviceTaintRule, opts v1.CreateOptions) (*resourcev1beta2.DeviceTaintRule, error) + Update(ctx context.Context, deviceTaintRule *resourcev1beta2.DeviceTaintRule, opts v1.UpdateOptions) (*resourcev1beta2.DeviceTaintRule, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, deviceTaintRule *resourcev1beta2.DeviceTaintRule, opts v1.UpdateOptions) (*resourcev1beta2.DeviceTaintRule, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*resourcev1beta2.DeviceTaintRule, error) + List(ctx context.Context, opts v1.ListOptions) (*resourcev1beta2.DeviceTaintRuleList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *resourcev1beta2.DeviceTaintRule, err error) + Apply(ctx context.Context, deviceTaintRule *applyconfigurationsresourcev1beta2.DeviceTaintRuleApplyConfiguration, opts v1.ApplyOptions) (result *resourcev1beta2.DeviceTaintRule, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, deviceTaintRule *applyconfigurationsresourcev1beta2.DeviceTaintRuleApplyConfiguration, opts v1.ApplyOptions) (result *resourcev1beta2.DeviceTaintRule, err error) + DeviceTaintRuleExpansion +} + +// deviceTaintRules implements DeviceTaintRuleInterface +type deviceTaintRules struct { + *gentype.ClientWithListAndApply[*resourcev1beta2.DeviceTaintRule, *resourcev1beta2.DeviceTaintRuleList, *applyconfigurationsresourcev1beta2.DeviceTaintRuleApplyConfiguration] +} + +// newDeviceTaintRules returns a DeviceTaintRules +func newDeviceTaintRules(c *ResourceV1beta2Client) *deviceTaintRules { + return &deviceTaintRules{ + gentype.NewClientWithListAndApply[*resourcev1beta2.DeviceTaintRule, *resourcev1beta2.DeviceTaintRuleList, *applyconfigurationsresourcev1beta2.DeviceTaintRuleApplyConfiguration]( + "devicetaintrules", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *resourcev1beta2.DeviceTaintRule { return &resourcev1beta2.DeviceTaintRule{} }, + func() *resourcev1beta2.DeviceTaintRuleList { return &resourcev1beta2.DeviceTaintRuleList{} }, + gentype.PrefersProtobuf[*resourcev1beta2.DeviceTaintRule](), + ), + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_devicetaintrule.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_devicetaintrule.go new file mode 100644 index 0000000000000..13797c6cfd4af --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_devicetaintrule.go @@ -0,0 +1,53 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1beta2 "k8s.io/api/resource/v1beta2" + resourcev1beta2 "k8s.io/client-go/applyconfigurations/resource/v1beta2" + gentype "k8s.io/client-go/gentype" + typedresourcev1beta2 "k8s.io/client-go/kubernetes/typed/resource/v1beta2" +) + +// fakeDeviceTaintRules implements DeviceTaintRuleInterface +type fakeDeviceTaintRules struct { + *gentype.FakeClientWithListAndApply[*v1beta2.DeviceTaintRule, *v1beta2.DeviceTaintRuleList, *resourcev1beta2.DeviceTaintRuleApplyConfiguration] + Fake *FakeResourceV1beta2 +} + +func newFakeDeviceTaintRules(fake *FakeResourceV1beta2) typedresourcev1beta2.DeviceTaintRuleInterface { + return &fakeDeviceTaintRules{ + gentype.NewFakeClientWithListAndApply[*v1beta2.DeviceTaintRule, *v1beta2.DeviceTaintRuleList, *resourcev1beta2.DeviceTaintRuleApplyConfiguration]( + fake.Fake, + "", + v1beta2.SchemeGroupVersion.WithResource("devicetaintrules"), + v1beta2.SchemeGroupVersion.WithKind("DeviceTaintRule"), + func() *v1beta2.DeviceTaintRule { return &v1beta2.DeviceTaintRule{} }, + func() *v1beta2.DeviceTaintRuleList { return &v1beta2.DeviceTaintRuleList{} }, + func(dst, src *v1beta2.DeviceTaintRuleList) { dst.ListMeta = src.ListMeta }, + func(list *v1beta2.DeviceTaintRuleList) []*v1beta2.DeviceTaintRule { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1beta2.DeviceTaintRuleList, items []*v1beta2.DeviceTaintRule) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resource_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resource_client.go index 10c5c0e337e8a..718bdd9e0d75b 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resource_client.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resource_client.go @@ -32,6 +32,10 @@ func (c *FakeResourceV1beta2) DeviceClasses() v1beta2.DeviceClassInterface { return newFakeDeviceClasses(c) } +func (c *FakeResourceV1beta2) DeviceTaintRules() v1beta2.DeviceTaintRuleInterface { + return newFakeDeviceTaintRules(c) +} + func (c *FakeResourceV1beta2) ResourceClaims(namespace string) v1beta2.ResourceClaimInterface { return newFakeResourceClaims(c, namespace) } diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/generated_expansion.go index 230ab8ccd3692..06a1dc6e931e3 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/generated_expansion.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/generated_expansion.go @@ -20,6 +20,8 @@ package v1beta2 type DeviceClassExpansion interface{} +type DeviceTaintRuleExpansion interface{} + type ResourceClaimExpansion interface{} type ResourceClaimTemplateExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resource_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resource_client.go index aadde5be95d4e..45465a480dca1 100644 --- a/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resource_client.go +++ b/staging/src/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resource_client.go @@ -29,6 +29,7 @@ import ( type ResourceV1beta2Interface interface { RESTClient() rest.Interface DeviceClassesGetter + DeviceTaintRulesGetter ResourceClaimsGetter ResourceClaimTemplatesGetter ResourceSlicesGetter @@ -43,6 +44,10 @@ func (c *ResourceV1beta2Client) DeviceClasses() DeviceClassInterface { return newDeviceClasses(c) } +func (c *ResourceV1beta2Client) DeviceTaintRules() DeviceTaintRuleInterface { + return newDeviceTaintRules(c) +} + func (c *ResourceV1beta2Client) ResourceClaims(namespace string) ResourceClaimInterface { return newResourceClaims(c, namespace) } diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/doc.go deleted file mode 100644 index df51baa4d4c17..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated typed clients. -package v1alpha1 diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/doc.go deleted file mode 100644 index 16f44399065ed..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// Package fake has the automatically generated clients. -package fake diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_priorityclass.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_priorityclass.go deleted file mode 100644 index 7d5a8e2630033..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_priorityclass.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "k8s.io/api/scheduling/v1alpha1" - schedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1" - gentype "k8s.io/client-go/gentype" - typedschedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1" -) - -// fakePriorityClasses implements PriorityClassInterface -type fakePriorityClasses struct { - *gentype.FakeClientWithListAndApply[*v1alpha1.PriorityClass, *v1alpha1.PriorityClassList, *schedulingv1alpha1.PriorityClassApplyConfiguration] - Fake *FakeSchedulingV1alpha1 -} - -func newFakePriorityClasses(fake *FakeSchedulingV1alpha1) typedschedulingv1alpha1.PriorityClassInterface { - return &fakePriorityClasses{ - gentype.NewFakeClientWithListAndApply[*v1alpha1.PriorityClass, *v1alpha1.PriorityClassList, *schedulingv1alpha1.PriorityClassApplyConfiguration]( - fake.Fake, - "", - v1alpha1.SchemeGroupVersion.WithResource("priorityclasses"), - v1alpha1.SchemeGroupVersion.WithKind("PriorityClass"), - func() *v1alpha1.PriorityClass { return &v1alpha1.PriorityClass{} }, - func() *v1alpha1.PriorityClassList { return &v1alpha1.PriorityClassList{} }, - func(dst, src *v1alpha1.PriorityClassList) { dst.ListMeta = src.ListMeta }, - func(list *v1alpha1.PriorityClassList) []*v1alpha1.PriorityClass { - return gentype.ToPointerSlice(list.Items) - }, - func(list *v1alpha1.PriorityClassList, items []*v1alpha1.PriorityClass) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_scheduling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_scheduling_client.go deleted file mode 100644 index 2be141266292a..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_scheduling_client.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1" - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" -) - -type FakeSchedulingV1alpha1 struct { - *testing.Fake -} - -func (c *FakeSchedulingV1alpha1) PriorityClasses() v1alpha1.PriorityClassInterface { - return newFakePriorityClasses(c) -} - -func (c *FakeSchedulingV1alpha1) Workloads(namespace string) v1alpha1.WorkloadInterface { - return newFakeWorkloads(c, namespace) -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeSchedulingV1alpha1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_workload.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_workload.go deleted file mode 100644 index 06a48d24e0c20..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake/fake_workload.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "k8s.io/api/scheduling/v1alpha1" - schedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1" - gentype "k8s.io/client-go/gentype" - typedschedulingv1alpha1 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1" -) - -// fakeWorkloads implements WorkloadInterface -type fakeWorkloads struct { - *gentype.FakeClientWithListAndApply[*v1alpha1.Workload, *v1alpha1.WorkloadList, *schedulingv1alpha1.WorkloadApplyConfiguration] - Fake *FakeSchedulingV1alpha1 -} - -func newFakeWorkloads(fake *FakeSchedulingV1alpha1, namespace string) typedschedulingv1alpha1.WorkloadInterface { - return &fakeWorkloads{ - gentype.NewFakeClientWithListAndApply[*v1alpha1.Workload, *v1alpha1.WorkloadList, *schedulingv1alpha1.WorkloadApplyConfiguration]( - fake.Fake, - namespace, - v1alpha1.SchemeGroupVersion.WithResource("workloads"), - v1alpha1.SchemeGroupVersion.WithKind("Workload"), - func() *v1alpha1.Workload { return &v1alpha1.Workload{} }, - func() *v1alpha1.WorkloadList { return &v1alpha1.WorkloadList{} }, - func(dst, src *v1alpha1.WorkloadList) { dst.ListMeta = src.ListMeta }, - func(list *v1alpha1.WorkloadList) []*v1alpha1.Workload { return gentype.ToPointerSlice(list.Items) }, - func(list *v1alpha1.WorkloadList, items []*v1alpha1.Workload) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/generated_expansion.go deleted file mode 100644 index 24946041af446..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/generated_expansion.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -type PriorityClassExpansion interface{} - -type WorkloadExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/priorityclass.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/priorityclass.go deleted file mode 100644 index e7125f9fc350c..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/priorityclass.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - applyconfigurationsschedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1" - gentype "k8s.io/client-go/gentype" - scheme "k8s.io/client-go/kubernetes/scheme" -) - -// PriorityClassesGetter has a method to return a PriorityClassInterface. -// A group's client should implement this interface. -type PriorityClassesGetter interface { - PriorityClasses() PriorityClassInterface -} - -// PriorityClassInterface has methods to work with PriorityClass resources. -type PriorityClassInterface interface { - Create(ctx context.Context, priorityClass *schedulingv1alpha1.PriorityClass, opts v1.CreateOptions) (*schedulingv1alpha1.PriorityClass, error) - Update(ctx context.Context, priorityClass *schedulingv1alpha1.PriorityClass, opts v1.UpdateOptions) (*schedulingv1alpha1.PriorityClass, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*schedulingv1alpha1.PriorityClass, error) - List(ctx context.Context, opts v1.ListOptions) (*schedulingv1alpha1.PriorityClassList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *schedulingv1alpha1.PriorityClass, err error) - Apply(ctx context.Context, priorityClass *applyconfigurationsschedulingv1alpha1.PriorityClassApplyConfiguration, opts v1.ApplyOptions) (result *schedulingv1alpha1.PriorityClass, err error) - PriorityClassExpansion -} - -// priorityClasses implements PriorityClassInterface -type priorityClasses struct { - *gentype.ClientWithListAndApply[*schedulingv1alpha1.PriorityClass, *schedulingv1alpha1.PriorityClassList, *applyconfigurationsschedulingv1alpha1.PriorityClassApplyConfiguration] -} - -// newPriorityClasses returns a PriorityClasses -func newPriorityClasses(c *SchedulingV1alpha1Client) *priorityClasses { - return &priorityClasses{ - gentype.NewClientWithListAndApply[*schedulingv1alpha1.PriorityClass, *schedulingv1alpha1.PriorityClassList, *applyconfigurationsschedulingv1alpha1.PriorityClassApplyConfiguration]( - "priorityclasses", - c.RESTClient(), - scheme.ParameterCodec, - "", - func() *schedulingv1alpha1.PriorityClass { return &schedulingv1alpha1.PriorityClass{} }, - func() *schedulingv1alpha1.PriorityClassList { return &schedulingv1alpha1.PriorityClassList{} }, - gentype.PrefersProtobuf[*schedulingv1alpha1.PriorityClass](), - ), - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/scheduling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/scheduling_client.go deleted file mode 100644 index 0c53bc368a95d..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/scheduling_client.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - http "net/http" - - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - scheme "k8s.io/client-go/kubernetes/scheme" - rest "k8s.io/client-go/rest" -) - -type SchedulingV1alpha1Interface interface { - RESTClient() rest.Interface - PriorityClassesGetter - WorkloadsGetter -} - -// SchedulingV1alpha1Client is used to interact with features provided by the scheduling.k8s.io group. -type SchedulingV1alpha1Client struct { - restClient rest.Interface -} - -func (c *SchedulingV1alpha1Client) PriorityClasses() PriorityClassInterface { - return newPriorityClasses(c) -} - -func (c *SchedulingV1alpha1Client) Workloads(namespace string) WorkloadInterface { - return newWorkloads(c, namespace) -} - -// NewForConfig creates a new SchedulingV1alpha1Client for the given config. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). -func NewForConfig(c *rest.Config) (*SchedulingV1alpha1Client, error) { - config := *c - setConfigDefaults(&config) - httpClient, err := rest.HTTPClientFor(&config) - if err != nil { - return nil, err - } - return NewForConfigAndClient(&config, httpClient) -} - -// NewForConfigAndClient creates a new SchedulingV1alpha1Client for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SchedulingV1alpha1Client, error) { - config := *c - setConfigDefaults(&config) - client, err := rest.RESTClientForConfigAndClient(&config, h) - if err != nil { - return nil, err - } - return &SchedulingV1alpha1Client{client}, nil -} - -// NewForConfigOrDie creates a new SchedulingV1alpha1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *SchedulingV1alpha1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new SchedulingV1alpha1Client for the given RESTClient. -func New(c rest.Interface) *SchedulingV1alpha1Client { - return &SchedulingV1alpha1Client{c} -} - -func setConfigDefaults(config *rest.Config) { - gv := schedulingv1alpha1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *SchedulingV1alpha1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/workload.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/workload.go deleted file mode 100644 index 9c906b5e89b26..0000000000000 --- a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/workload.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - applyconfigurationsschedulingv1alpha1 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1" - gentype "k8s.io/client-go/gentype" - scheme "k8s.io/client-go/kubernetes/scheme" -) - -// WorkloadsGetter has a method to return a WorkloadInterface. -// A group's client should implement this interface. -type WorkloadsGetter interface { - Workloads(namespace string) WorkloadInterface -} - -// WorkloadInterface has methods to work with Workload resources. -type WorkloadInterface interface { - Create(ctx context.Context, workload *schedulingv1alpha1.Workload, opts v1.CreateOptions) (*schedulingv1alpha1.Workload, error) - Update(ctx context.Context, workload *schedulingv1alpha1.Workload, opts v1.UpdateOptions) (*schedulingv1alpha1.Workload, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*schedulingv1alpha1.Workload, error) - List(ctx context.Context, opts v1.ListOptions) (*schedulingv1alpha1.WorkloadList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *schedulingv1alpha1.Workload, err error) - Apply(ctx context.Context, workload *applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration, opts v1.ApplyOptions) (result *schedulingv1alpha1.Workload, err error) - WorkloadExpansion -} - -// workloads implements WorkloadInterface -type workloads struct { - *gentype.ClientWithListAndApply[*schedulingv1alpha1.Workload, *schedulingv1alpha1.WorkloadList, *applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration] -} - -// newWorkloads returns a Workloads -func newWorkloads(c *SchedulingV1alpha1Client, namespace string) *workloads { - return &workloads{ - gentype.NewClientWithListAndApply[*schedulingv1alpha1.Workload, *schedulingv1alpha1.WorkloadList, *applyconfigurationsschedulingv1alpha1.WorkloadApplyConfiguration]( - "workloads", - c.RESTClient(), - scheme.ParameterCodec, - namespace, - func() *schedulingv1alpha1.Workload { return &schedulingv1alpha1.Workload{} }, - func() *schedulingv1alpha1.WorkloadList { return &schedulingv1alpha1.WorkloadList{} }, - gentype.PrefersProtobuf[*schedulingv1alpha1.Workload](), - ), - } -} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/doc.go new file mode 100644 index 0000000000000..baaf2d9853708 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha2 diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/doc.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/doc.go similarity index 100% rename from staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake/doc.go rename to staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/doc.go diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_podgroup.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_podgroup.go new file mode 100644 index 0000000000000..3709cf5214369 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_podgroup.go @@ -0,0 +1,51 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha2 "k8s.io/api/scheduling/v1alpha2" + schedulingv1alpha2 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha2" + gentype "k8s.io/client-go/gentype" + typedschedulingv1alpha2 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2" +) + +// fakePodGroups implements PodGroupInterface +type fakePodGroups struct { + *gentype.FakeClientWithListAndApply[*v1alpha2.PodGroup, *v1alpha2.PodGroupList, *schedulingv1alpha2.PodGroupApplyConfiguration] + Fake *FakeSchedulingV1alpha2 +} + +func newFakePodGroups(fake *FakeSchedulingV1alpha2, namespace string) typedschedulingv1alpha2.PodGroupInterface { + return &fakePodGroups{ + gentype.NewFakeClientWithListAndApply[*v1alpha2.PodGroup, *v1alpha2.PodGroupList, *schedulingv1alpha2.PodGroupApplyConfiguration]( + fake.Fake, + namespace, + v1alpha2.SchemeGroupVersion.WithResource("podgroups"), + v1alpha2.SchemeGroupVersion.WithKind("PodGroup"), + func() *v1alpha2.PodGroup { return &v1alpha2.PodGroup{} }, + func() *v1alpha2.PodGroupList { return &v1alpha2.PodGroupList{} }, + func(dst, src *v1alpha2.PodGroupList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha2.PodGroupList) []*v1alpha2.PodGroup { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha2.PodGroupList, items []*v1alpha2.PodGroup) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_scheduling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_scheduling_client.go new file mode 100644 index 0000000000000..1d21b2d914a09 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_scheduling_client.go @@ -0,0 +1,44 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha2 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeSchedulingV1alpha2 struct { + *testing.Fake +} + +func (c *FakeSchedulingV1alpha2) PodGroups(namespace string) v1alpha2.PodGroupInterface { + return newFakePodGroups(c, namespace) +} + +func (c *FakeSchedulingV1alpha2) Workloads(namespace string) v1alpha2.WorkloadInterface { + return newFakeWorkloads(c, namespace) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeSchedulingV1alpha2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_workload.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_workload.go new file mode 100644 index 0000000000000..29768874cf8e0 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/fake/fake_workload.go @@ -0,0 +1,51 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha2 "k8s.io/api/scheduling/v1alpha2" + schedulingv1alpha2 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha2" + gentype "k8s.io/client-go/gentype" + typedschedulingv1alpha2 "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2" +) + +// fakeWorkloads implements WorkloadInterface +type fakeWorkloads struct { + *gentype.FakeClientWithListAndApply[*v1alpha2.Workload, *v1alpha2.WorkloadList, *schedulingv1alpha2.WorkloadApplyConfiguration] + Fake *FakeSchedulingV1alpha2 +} + +func newFakeWorkloads(fake *FakeSchedulingV1alpha2, namespace string) typedschedulingv1alpha2.WorkloadInterface { + return &fakeWorkloads{ + gentype.NewFakeClientWithListAndApply[*v1alpha2.Workload, *v1alpha2.WorkloadList, *schedulingv1alpha2.WorkloadApplyConfiguration]( + fake.Fake, + namespace, + v1alpha2.SchemeGroupVersion.WithResource("workloads"), + v1alpha2.SchemeGroupVersion.WithKind("Workload"), + func() *v1alpha2.Workload { return &v1alpha2.Workload{} }, + func() *v1alpha2.WorkloadList { return &v1alpha2.WorkloadList{} }, + func(dst, src *v1alpha2.WorkloadList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha2.WorkloadList) []*v1alpha2.Workload { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha2.WorkloadList, items []*v1alpha2.Workload) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/generated_expansion.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/generated_expansion.go new file mode 100644 index 0000000000000..60668af04d9a7 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/generated_expansion.go @@ -0,0 +1,23 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +type PodGroupExpansion interface{} + +type WorkloadExpansion interface{} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/podgroup.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/podgroup.go new file mode 100644 index 0000000000000..bbe91d35ab9a5 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/podgroup.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsschedulingv1alpha2 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha2" + gentype "k8s.io/client-go/gentype" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +// PodGroupsGetter has a method to return a PodGroupInterface. +// A group's client should implement this interface. +type PodGroupsGetter interface { + PodGroups(namespace string) PodGroupInterface +} + +// PodGroupInterface has methods to work with PodGroup resources. +type PodGroupInterface interface { + Create(ctx context.Context, podGroup *schedulingv1alpha2.PodGroup, opts v1.CreateOptions) (*schedulingv1alpha2.PodGroup, error) + Update(ctx context.Context, podGroup *schedulingv1alpha2.PodGroup, opts v1.UpdateOptions) (*schedulingv1alpha2.PodGroup, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, podGroup *schedulingv1alpha2.PodGroup, opts v1.UpdateOptions) (*schedulingv1alpha2.PodGroup, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*schedulingv1alpha2.PodGroup, error) + List(ctx context.Context, opts v1.ListOptions) (*schedulingv1alpha2.PodGroupList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *schedulingv1alpha2.PodGroup, err error) + Apply(ctx context.Context, podGroup *applyconfigurationsschedulingv1alpha2.PodGroupApplyConfiguration, opts v1.ApplyOptions) (result *schedulingv1alpha2.PodGroup, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, podGroup *applyconfigurationsschedulingv1alpha2.PodGroupApplyConfiguration, opts v1.ApplyOptions) (result *schedulingv1alpha2.PodGroup, err error) + PodGroupExpansion +} + +// podGroups implements PodGroupInterface +type podGroups struct { + *gentype.ClientWithListAndApply[*schedulingv1alpha2.PodGroup, *schedulingv1alpha2.PodGroupList, *applyconfigurationsschedulingv1alpha2.PodGroupApplyConfiguration] +} + +// newPodGroups returns a PodGroups +func newPodGroups(c *SchedulingV1alpha2Client, namespace string) *podGroups { + return &podGroups{ + gentype.NewClientWithListAndApply[*schedulingv1alpha2.PodGroup, *schedulingv1alpha2.PodGroupList, *applyconfigurationsschedulingv1alpha2.PodGroupApplyConfiguration]( + "podgroups", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *schedulingv1alpha2.PodGroup { return &schedulingv1alpha2.PodGroup{} }, + func() *schedulingv1alpha2.PodGroupList { return &schedulingv1alpha2.PodGroupList{} }, + gentype.PrefersProtobuf[*schedulingv1alpha2.PodGroup](), + ), + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/scheduling_client.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/scheduling_client.go new file mode 100644 index 0000000000000..8ccdc178b9612 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/scheduling_client.go @@ -0,0 +1,106 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + http "net/http" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + scheme "k8s.io/client-go/kubernetes/scheme" + rest "k8s.io/client-go/rest" +) + +type SchedulingV1alpha2Interface interface { + RESTClient() rest.Interface + PodGroupsGetter + WorkloadsGetter +} + +// SchedulingV1alpha2Client is used to interact with features provided by the scheduling.k8s.io group. +type SchedulingV1alpha2Client struct { + restClient rest.Interface +} + +func (c *SchedulingV1alpha2Client) PodGroups(namespace string) PodGroupInterface { + return newPodGroups(c, namespace) +} + +func (c *SchedulingV1alpha2Client) Workloads(namespace string) WorkloadInterface { + return newWorkloads(c, namespace) +} + +// NewForConfig creates a new SchedulingV1alpha2Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*SchedulingV1alpha2Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new SchedulingV1alpha2Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SchedulingV1alpha2Client, error) { + config := *c + setConfigDefaults(&config) + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &SchedulingV1alpha2Client{client}, nil +} + +// NewForConfigOrDie creates a new SchedulingV1alpha2Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *SchedulingV1alpha2Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new SchedulingV1alpha2Client for the given RESTClient. +func New(c rest.Interface) *SchedulingV1alpha2Client { + return &SchedulingV1alpha2Client{c} +} + +func setConfigDefaults(config *rest.Config) { + gv := schedulingv1alpha2.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *SchedulingV1alpha2Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/workload.go b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/workload.go new file mode 100644 index 0000000000000..89790940bcdb1 --- /dev/null +++ b/staging/src/k8s.io/client-go/kubernetes/typed/scheduling/v1alpha2/workload.go @@ -0,0 +1,71 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsschedulingv1alpha2 "k8s.io/client-go/applyconfigurations/scheduling/v1alpha2" + gentype "k8s.io/client-go/gentype" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +// WorkloadsGetter has a method to return a WorkloadInterface. +// A group's client should implement this interface. +type WorkloadsGetter interface { + Workloads(namespace string) WorkloadInterface +} + +// WorkloadInterface has methods to work with Workload resources. +type WorkloadInterface interface { + Create(ctx context.Context, workload *schedulingv1alpha2.Workload, opts v1.CreateOptions) (*schedulingv1alpha2.Workload, error) + Update(ctx context.Context, workload *schedulingv1alpha2.Workload, opts v1.UpdateOptions) (*schedulingv1alpha2.Workload, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*schedulingv1alpha2.Workload, error) + List(ctx context.Context, opts v1.ListOptions) (*schedulingv1alpha2.WorkloadList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *schedulingv1alpha2.Workload, err error) + Apply(ctx context.Context, workload *applyconfigurationsschedulingv1alpha2.WorkloadApplyConfiguration, opts v1.ApplyOptions) (result *schedulingv1alpha2.Workload, err error) + WorkloadExpansion +} + +// workloads implements WorkloadInterface +type workloads struct { + *gentype.ClientWithListAndApply[*schedulingv1alpha2.Workload, *schedulingv1alpha2.WorkloadList, *applyconfigurationsschedulingv1alpha2.WorkloadApplyConfiguration] +} + +// newWorkloads returns a Workloads +func newWorkloads(c *SchedulingV1alpha2Client, namespace string) *workloads { + return &workloads{ + gentype.NewClientWithListAndApply[*schedulingv1alpha2.Workload, *schedulingv1alpha2.WorkloadList, *applyconfigurationsschedulingv1alpha2.WorkloadApplyConfiguration]( + "workloads", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *schedulingv1alpha2.Workload { return &schedulingv1alpha2.Workload{} }, + func() *schedulingv1alpha2.WorkloadList { return &schedulingv1alpha2.WorkloadList{} }, + gentype.PrefersProtobuf[*schedulingv1alpha2.Workload](), + ), + } +} diff --git a/staging/src/k8s.io/client-go/kubernetes_test/timeout_test.go b/staging/src/k8s.io/client-go/kubernetes_test/timeout_test.go index 84fde895f0088..bd8bf1054b92f 100644 --- a/staging/src/k8s.io/client-go/kubernetes_test/timeout_test.go +++ b/staging/src/k8s.io/client-go/kubernetes_test/timeout_test.go @@ -25,11 +25,11 @@ import ( appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" manualfake "k8s.io/client-go/rest/fake" + "k8s.io/utils/dump" ) func TestListTimeout(t *testing.T) { diff --git a/staging/src/k8s.io/client-go/listers/admissionregistration/v1/expansion_generated.go b/staging/src/k8s.io/client-go/listers/admissionregistration/v1/expansion_generated.go index 9002ad6ea3b5d..d3c96490fc54c 100644 --- a/staging/src/k8s.io/client-go/listers/admissionregistration/v1/expansion_generated.go +++ b/staging/src/k8s.io/client-go/listers/admissionregistration/v1/expansion_generated.go @@ -18,6 +18,14 @@ limitations under the License. package v1 +// MutatingAdmissionPolicyListerExpansion allows custom methods to be added to +// MutatingAdmissionPolicyLister. +type MutatingAdmissionPolicyListerExpansion interface{} + +// MutatingAdmissionPolicyBindingListerExpansion allows custom methods to be added to +// MutatingAdmissionPolicyBindingLister. +type MutatingAdmissionPolicyBindingListerExpansion interface{} + // MutatingWebhookConfigurationListerExpansion allows custom methods to be added to // MutatingWebhookConfigurationLister. type MutatingWebhookConfigurationListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/admissionregistration/v1/mutatingadmissionpolicy.go b/staging/src/k8s.io/client-go/listers/admissionregistration/v1/mutatingadmissionpolicy.go new file mode 100644 index 0000000000000..3f5567f5cdae4 --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/admissionregistration/v1/mutatingadmissionpolicy.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// MutatingAdmissionPolicyLister helps list MutatingAdmissionPolicies. +// All objects returned here must be treated as read-only. +type MutatingAdmissionPolicyLister interface { + // List lists all MutatingAdmissionPolicies in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*admissionregistrationv1.MutatingAdmissionPolicy, err error) + // Get retrieves the MutatingAdmissionPolicy from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*admissionregistrationv1.MutatingAdmissionPolicy, error) + MutatingAdmissionPolicyListerExpansion +} + +// mutatingAdmissionPolicyLister implements the MutatingAdmissionPolicyLister interface. +type mutatingAdmissionPolicyLister struct { + listers.ResourceIndexer[*admissionregistrationv1.MutatingAdmissionPolicy] +} + +// NewMutatingAdmissionPolicyLister returns a new MutatingAdmissionPolicyLister. +func NewMutatingAdmissionPolicyLister(indexer cache.Indexer) MutatingAdmissionPolicyLister { + return &mutatingAdmissionPolicyLister{listers.New[*admissionregistrationv1.MutatingAdmissionPolicy](indexer, admissionregistrationv1.Resource("mutatingadmissionpolicy"))} +} diff --git a/staging/src/k8s.io/client-go/listers/admissionregistration/v1/mutatingadmissionpolicybinding.go b/staging/src/k8s.io/client-go/listers/admissionregistration/v1/mutatingadmissionpolicybinding.go new file mode 100644 index 0000000000000..686a9cc55cc1d --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/admissionregistration/v1/mutatingadmissionpolicybinding.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// MutatingAdmissionPolicyBindingLister helps list MutatingAdmissionPolicyBindings. +// All objects returned here must be treated as read-only. +type MutatingAdmissionPolicyBindingLister interface { + // List lists all MutatingAdmissionPolicyBindings in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*admissionregistrationv1.MutatingAdmissionPolicyBinding, err error) + // Get retrieves the MutatingAdmissionPolicyBinding from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*admissionregistrationv1.MutatingAdmissionPolicyBinding, error) + MutatingAdmissionPolicyBindingListerExpansion +} + +// mutatingAdmissionPolicyBindingLister implements the MutatingAdmissionPolicyBindingLister interface. +type mutatingAdmissionPolicyBindingLister struct { + listers.ResourceIndexer[*admissionregistrationv1.MutatingAdmissionPolicyBinding] +} + +// NewMutatingAdmissionPolicyBindingLister returns a new MutatingAdmissionPolicyBindingLister. +func NewMutatingAdmissionPolicyBindingLister(indexer cache.Indexer) MutatingAdmissionPolicyBindingLister { + return &mutatingAdmissionPolicyBindingLister{listers.New[*admissionregistrationv1.MutatingAdmissionPolicyBinding](indexer, admissionregistrationv1.Resource("mutatingadmissionpolicybinding"))} +} diff --git a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta1/expansion_generated.go b/staging/src/k8s.io/client-go/listers/autoscaling/v2beta1/expansion_generated.go deleted file mode 100644 index 8d46a4b6e3191..0000000000000 --- a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta1/expansion_generated.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v2beta1 - -// HorizontalPodAutoscalerListerExpansion allows custom methods to be added to -// HorizontalPodAutoscalerLister. -type HorizontalPodAutoscalerListerExpansion interface{} - -// HorizontalPodAutoscalerNamespaceListerExpansion allows custom methods to be added to -// HorizontalPodAutoscalerNamespaceLister. -type HorizontalPodAutoscalerNamespaceListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta1/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/listers/autoscaling/v2beta1/horizontalpodautoscaler.go deleted file mode 100644 index b7ad8e79b52bd..0000000000000 --- a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta1/horizontalpodautoscaler.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v2beta1 - -import ( - autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" -) - -// HorizontalPodAutoscalerLister helps list HorizontalPodAutoscalers. -// All objects returned here must be treated as read-only. -type HorizontalPodAutoscalerLister interface { - // List lists all HorizontalPodAutoscalers in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*autoscalingv2beta1.HorizontalPodAutoscaler, err error) - // HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers. - HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister - HorizontalPodAutoscalerListerExpansion -} - -// horizontalPodAutoscalerLister implements the HorizontalPodAutoscalerLister interface. -type horizontalPodAutoscalerLister struct { - listers.ResourceIndexer[*autoscalingv2beta1.HorizontalPodAutoscaler] -} - -// NewHorizontalPodAutoscalerLister returns a new HorizontalPodAutoscalerLister. -func NewHorizontalPodAutoscalerLister(indexer cache.Indexer) HorizontalPodAutoscalerLister { - return &horizontalPodAutoscalerLister{listers.New[*autoscalingv2beta1.HorizontalPodAutoscaler](indexer, autoscalingv2beta1.Resource("horizontalpodautoscaler"))} -} - -// HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers. -func (s *horizontalPodAutoscalerLister) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister { - return horizontalPodAutoscalerNamespaceLister{listers.NewNamespaced[*autoscalingv2beta1.HorizontalPodAutoscaler](s.ResourceIndexer, namespace)} -} - -// HorizontalPodAutoscalerNamespaceLister helps list and get HorizontalPodAutoscalers. -// All objects returned here must be treated as read-only. -type HorizontalPodAutoscalerNamespaceLister interface { - // List lists all HorizontalPodAutoscalers in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*autoscalingv2beta1.HorizontalPodAutoscaler, err error) - // Get retrieves the HorizontalPodAutoscaler from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*autoscalingv2beta1.HorizontalPodAutoscaler, error) - HorizontalPodAutoscalerNamespaceListerExpansion -} - -// horizontalPodAutoscalerNamespaceLister implements the HorizontalPodAutoscalerNamespaceLister -// interface. -type horizontalPodAutoscalerNamespaceLister struct { - listers.ResourceIndexer[*autoscalingv2beta1.HorizontalPodAutoscaler] -} diff --git a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta2/expansion_generated.go b/staging/src/k8s.io/client-go/listers/autoscaling/v2beta2/expansion_generated.go deleted file mode 100644 index 5127945a9c74f..0000000000000 --- a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta2/expansion_generated.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v2beta2 - -// HorizontalPodAutoscalerListerExpansion allows custom methods to be added to -// HorizontalPodAutoscalerLister. -type HorizontalPodAutoscalerListerExpansion interface{} - -// HorizontalPodAutoscalerNamespaceListerExpansion allows custom methods to be added to -// HorizontalPodAutoscalerNamespaceLister. -type HorizontalPodAutoscalerNamespaceListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta2/horizontalpodautoscaler.go b/staging/src/k8s.io/client-go/listers/autoscaling/v2beta2/horizontalpodautoscaler.go deleted file mode 100644 index 8d0fa6e79807f..0000000000000 --- a/staging/src/k8s.io/client-go/listers/autoscaling/v2beta2/horizontalpodautoscaler.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v2beta2 - -import ( - autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" -) - -// HorizontalPodAutoscalerLister helps list HorizontalPodAutoscalers. -// All objects returned here must be treated as read-only. -type HorizontalPodAutoscalerLister interface { - // List lists all HorizontalPodAutoscalers in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*autoscalingv2beta2.HorizontalPodAutoscaler, err error) - // HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers. - HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister - HorizontalPodAutoscalerListerExpansion -} - -// horizontalPodAutoscalerLister implements the HorizontalPodAutoscalerLister interface. -type horizontalPodAutoscalerLister struct { - listers.ResourceIndexer[*autoscalingv2beta2.HorizontalPodAutoscaler] -} - -// NewHorizontalPodAutoscalerLister returns a new HorizontalPodAutoscalerLister. -func NewHorizontalPodAutoscalerLister(indexer cache.Indexer) HorizontalPodAutoscalerLister { - return &horizontalPodAutoscalerLister{listers.New[*autoscalingv2beta2.HorizontalPodAutoscaler](indexer, autoscalingv2beta2.Resource("horizontalpodautoscaler"))} -} - -// HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers. -func (s *horizontalPodAutoscalerLister) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister { - return horizontalPodAutoscalerNamespaceLister{listers.NewNamespaced[*autoscalingv2beta2.HorizontalPodAutoscaler](s.ResourceIndexer, namespace)} -} - -// HorizontalPodAutoscalerNamespaceLister helps list and get HorizontalPodAutoscalers. -// All objects returned here must be treated as read-only. -type HorizontalPodAutoscalerNamespaceLister interface { - // List lists all HorizontalPodAutoscalers in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*autoscalingv2beta2.HorizontalPodAutoscaler, err error) - // Get retrieves the HorizontalPodAutoscaler from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*autoscalingv2beta2.HorizontalPodAutoscaler, error) - HorizontalPodAutoscalerNamespaceListerExpansion -} - -// horizontalPodAutoscalerNamespaceLister implements the HorizontalPodAutoscalerNamespaceLister -// interface. -type horizontalPodAutoscalerNamespaceLister struct { - listers.ResourceIndexer[*autoscalingv2beta2.HorizontalPodAutoscaler] -} diff --git a/staging/src/k8s.io/client-go/listers/resource/v1alpha3/expansion_generated.go b/staging/src/k8s.io/client-go/listers/resource/v1alpha3/expansion_generated.go index 8685199a2432a..0b9d51cd09f57 100644 --- a/staging/src/k8s.io/client-go/listers/resource/v1alpha3/expansion_generated.go +++ b/staging/src/k8s.io/client-go/listers/resource/v1alpha3/expansion_generated.go @@ -21,3 +21,7 @@ package v1alpha3 // DeviceTaintRuleListerExpansion allows custom methods to be added to // DeviceTaintRuleLister. type DeviceTaintRuleListerExpansion interface{} + +// ResourcePoolStatusRequestListerExpansion allows custom methods to be added to +// ResourcePoolStatusRequestLister. +type ResourcePoolStatusRequestListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/resource/v1alpha3/resourcepoolstatusrequest.go b/staging/src/k8s.io/client-go/listers/resource/v1alpha3/resourcepoolstatusrequest.go new file mode 100644 index 0000000000000..f12dccdd8432e --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/resource/v1alpha3/resourcepoolstatusrequest.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// ResourcePoolStatusRequestLister helps list ResourcePoolStatusRequests. +// All objects returned here must be treated as read-only. +type ResourcePoolStatusRequestLister interface { + // List lists all ResourcePoolStatusRequests in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*resourcev1alpha3.ResourcePoolStatusRequest, err error) + // Get retrieves the ResourcePoolStatusRequest from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*resourcev1alpha3.ResourcePoolStatusRequest, error) + ResourcePoolStatusRequestListerExpansion +} + +// resourcePoolStatusRequestLister implements the ResourcePoolStatusRequestLister interface. +type resourcePoolStatusRequestLister struct { + listers.ResourceIndexer[*resourcev1alpha3.ResourcePoolStatusRequest] +} + +// NewResourcePoolStatusRequestLister returns a new ResourcePoolStatusRequestLister. +func NewResourcePoolStatusRequestLister(indexer cache.Indexer) ResourcePoolStatusRequestLister { + return &resourcePoolStatusRequestLister{listers.New[*resourcev1alpha3.ResourcePoolStatusRequest](indexer, resourcev1alpha3.Resource("resourcepoolstatusrequest"))} +} diff --git a/staging/src/k8s.io/client-go/listers/resource/v1beta2/devicetaintrule.go b/staging/src/k8s.io/client-go/listers/resource/v1beta2/devicetaintrule.go new file mode 100644 index 0000000000000..a567777944451 --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/resource/v1beta2/devicetaintrule.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta2 + +import ( + resourcev1beta2 "k8s.io/api/resource/v1beta2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// DeviceTaintRuleLister helps list DeviceTaintRules. +// All objects returned here must be treated as read-only. +type DeviceTaintRuleLister interface { + // List lists all DeviceTaintRules in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*resourcev1beta2.DeviceTaintRule, err error) + // Get retrieves the DeviceTaintRule from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*resourcev1beta2.DeviceTaintRule, error) + DeviceTaintRuleListerExpansion +} + +// deviceTaintRuleLister implements the DeviceTaintRuleLister interface. +type deviceTaintRuleLister struct { + listers.ResourceIndexer[*resourcev1beta2.DeviceTaintRule] +} + +// NewDeviceTaintRuleLister returns a new DeviceTaintRuleLister. +func NewDeviceTaintRuleLister(indexer cache.Indexer) DeviceTaintRuleLister { + return &deviceTaintRuleLister{listers.New[*resourcev1beta2.DeviceTaintRule](indexer, resourcev1beta2.Resource("devicetaintrule"))} +} diff --git a/staging/src/k8s.io/client-go/listers/resource/v1beta2/expansion_generated.go b/staging/src/k8s.io/client-go/listers/resource/v1beta2/expansion_generated.go index 590f26bd74dee..dcf80d22283ef 100644 --- a/staging/src/k8s.io/client-go/listers/resource/v1beta2/expansion_generated.go +++ b/staging/src/k8s.io/client-go/listers/resource/v1beta2/expansion_generated.go @@ -22,6 +22,10 @@ package v1beta2 // DeviceClassLister. type DeviceClassListerExpansion interface{} +// DeviceTaintRuleListerExpansion allows custom methods to be added to +// DeviceTaintRuleLister. +type DeviceTaintRuleListerExpansion interface{} + // ResourceClaimListerExpansion allows custom methods to be added to // ResourceClaimLister. type ResourceClaimListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/expansion_generated.go b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/expansion_generated.go deleted file mode 100644 index 8617325d8b6b8..0000000000000 --- a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/expansion_generated.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha1 - -// PriorityClassListerExpansion allows custom methods to be added to -// PriorityClassLister. -type PriorityClassListerExpansion interface{} - -// WorkloadListerExpansion allows custom methods to be added to -// WorkloadLister. -type WorkloadListerExpansion interface{} - -// WorkloadNamespaceListerExpansion allows custom methods to be added to -// WorkloadNamespaceLister. -type WorkloadNamespaceListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/priorityclass.go b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/priorityclass.go deleted file mode 100644 index 7beaf46322aad..0000000000000 --- a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/priorityclass.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" -) - -// PriorityClassLister helps list PriorityClasses. -// All objects returned here must be treated as read-only. -type PriorityClassLister interface { - // List lists all PriorityClasses in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*schedulingv1alpha1.PriorityClass, err error) - // Get retrieves the PriorityClass from the index for a given name. - // Objects returned here must be treated as read-only. - Get(name string) (*schedulingv1alpha1.PriorityClass, error) - PriorityClassListerExpansion -} - -// priorityClassLister implements the PriorityClassLister interface. -type priorityClassLister struct { - listers.ResourceIndexer[*schedulingv1alpha1.PriorityClass] -} - -// NewPriorityClassLister returns a new PriorityClassLister. -func NewPriorityClassLister(indexer cache.Indexer) PriorityClassLister { - return &priorityClassLister{listers.New[*schedulingv1alpha1.PriorityClass](indexer, schedulingv1alpha1.Resource("priorityclass"))} -} diff --git a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/workload.go b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/workload.go deleted file mode 100644 index 24f51a5ef38a0..0000000000000 --- a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha1/workload.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" -) - -// WorkloadLister helps list Workloads. -// All objects returned here must be treated as read-only. -type WorkloadLister interface { - // List lists all Workloads in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*schedulingv1alpha1.Workload, err error) - // Workloads returns an object that can list and get Workloads. - Workloads(namespace string) WorkloadNamespaceLister - WorkloadListerExpansion -} - -// workloadLister implements the WorkloadLister interface. -type workloadLister struct { - listers.ResourceIndexer[*schedulingv1alpha1.Workload] -} - -// NewWorkloadLister returns a new WorkloadLister. -func NewWorkloadLister(indexer cache.Indexer) WorkloadLister { - return &workloadLister{listers.New[*schedulingv1alpha1.Workload](indexer, schedulingv1alpha1.Resource("workload"))} -} - -// Workloads returns an object that can list and get Workloads. -func (s *workloadLister) Workloads(namespace string) WorkloadNamespaceLister { - return workloadNamespaceLister{listers.NewNamespaced[*schedulingv1alpha1.Workload](s.ResourceIndexer, namespace)} -} - -// WorkloadNamespaceLister helps list and get Workloads. -// All objects returned here must be treated as read-only. -type WorkloadNamespaceLister interface { - // List lists all Workloads in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*schedulingv1alpha1.Workload, err error) - // Get retrieves the Workload from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*schedulingv1alpha1.Workload, error) - WorkloadNamespaceListerExpansion -} - -// workloadNamespaceLister implements the WorkloadNamespaceLister -// interface. -type workloadNamespaceLister struct { - listers.ResourceIndexer[*schedulingv1alpha1.Workload] -} diff --git a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/expansion_generated.go b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/expansion_generated.go new file mode 100644 index 0000000000000..3a63d7891c291 --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/expansion_generated.go @@ -0,0 +1,35 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +// PodGroupListerExpansion allows custom methods to be added to +// PodGroupLister. +type PodGroupListerExpansion interface{} + +// PodGroupNamespaceListerExpansion allows custom methods to be added to +// PodGroupNamespaceLister. +type PodGroupNamespaceListerExpansion interface{} + +// WorkloadListerExpansion allows custom methods to be added to +// WorkloadLister. +type WorkloadListerExpansion interface{} + +// WorkloadNamespaceListerExpansion allows custom methods to be added to +// WorkloadNamespaceLister. +type WorkloadNamespaceListerExpansion interface{} diff --git a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/podgroup.go b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/podgroup.go new file mode 100644 index 0000000000000..292e5d62bed7b --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/podgroup.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// PodGroupLister helps list PodGroups. +// All objects returned here must be treated as read-only. +type PodGroupLister interface { + // List lists all PodGroups in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*schedulingv1alpha2.PodGroup, err error) + // PodGroups returns an object that can list and get PodGroups. + PodGroups(namespace string) PodGroupNamespaceLister + PodGroupListerExpansion +} + +// podGroupLister implements the PodGroupLister interface. +type podGroupLister struct { + listers.ResourceIndexer[*schedulingv1alpha2.PodGroup] +} + +// NewPodGroupLister returns a new PodGroupLister. +func NewPodGroupLister(indexer cache.Indexer) PodGroupLister { + return &podGroupLister{listers.New[*schedulingv1alpha2.PodGroup](indexer, schedulingv1alpha2.Resource("podgroup"))} +} + +// PodGroups returns an object that can list and get PodGroups. +func (s *podGroupLister) PodGroups(namespace string) PodGroupNamespaceLister { + return podGroupNamespaceLister{listers.NewNamespaced[*schedulingv1alpha2.PodGroup](s.ResourceIndexer, namespace)} +} + +// PodGroupNamespaceLister helps list and get PodGroups. +// All objects returned here must be treated as read-only. +type PodGroupNamespaceLister interface { + // List lists all PodGroups in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*schedulingv1alpha2.PodGroup, err error) + // Get retrieves the PodGroup from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*schedulingv1alpha2.PodGroup, error) + PodGroupNamespaceListerExpansion +} + +// podGroupNamespaceLister implements the PodGroupNamespaceLister +// interface. +type podGroupNamespaceLister struct { + listers.ResourceIndexer[*schedulingv1alpha2.PodGroup] +} diff --git a/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/workload.go b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/workload.go new file mode 100644 index 0000000000000..9526da3b5a512 --- /dev/null +++ b/staging/src/k8s.io/client-go/listers/scheduling/v1alpha2/workload.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// WorkloadLister helps list Workloads. +// All objects returned here must be treated as read-only. +type WorkloadLister interface { + // List lists all Workloads in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*schedulingv1alpha2.Workload, err error) + // Workloads returns an object that can list and get Workloads. + Workloads(namespace string) WorkloadNamespaceLister + WorkloadListerExpansion +} + +// workloadLister implements the WorkloadLister interface. +type workloadLister struct { + listers.ResourceIndexer[*schedulingv1alpha2.Workload] +} + +// NewWorkloadLister returns a new WorkloadLister. +func NewWorkloadLister(indexer cache.Indexer) WorkloadLister { + return &workloadLister{listers.New[*schedulingv1alpha2.Workload](indexer, schedulingv1alpha2.Resource("workload"))} +} + +// Workloads returns an object that can list and get Workloads. +func (s *workloadLister) Workloads(namespace string) WorkloadNamespaceLister { + return workloadNamespaceLister{listers.NewNamespaced[*schedulingv1alpha2.Workload](s.ResourceIndexer, namespace)} +} + +// WorkloadNamespaceLister helps list and get Workloads. +// All objects returned here must be treated as read-only. +type WorkloadNamespaceLister interface { + // List lists all Workloads in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*schedulingv1alpha2.Workload, err error) + // Get retrieves the Workload from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*schedulingv1alpha2.Workload, error) + WorkloadNamespaceListerExpansion +} + +// workloadNamespaceLister implements the WorkloadNamespaceLister +// interface. +type workloadNamespaceLister struct { + listers.ResourceIndexer[*schedulingv1alpha2.Workload] +} diff --git a/staging/src/k8s.io/client-go/metadata/fake/simple.go b/staging/src/k8s.io/client-go/metadata/fake/simple.go index fc50113fca136..e2d1f7aa2b2aa 100644 --- a/staging/src/k8s.io/client-go/metadata/fake/simple.go +++ b/staging/src/k8s.io/client-go/metadata/fake/simple.go @@ -68,9 +68,13 @@ func NewSimpleMetadataClient(scheme *runtime.Scheme, objects ...runtime.Object) cs := &FakeMetadataClient{scheme: scheme, tracker: o} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } diff --git a/staging/src/k8s.io/client-go/metadata/metadatainformer/informer.go b/staging/src/k8s.io/client-go/metadata/metadatainformer/informer.go index 665198f12a250..de32bc3ffc8e1 100644 --- a/staging/src/k8s.io/client-go/metadata/metadatainformer/informer.go +++ b/staging/src/k8s.io/client-go/metadata/metadatainformer/informer.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/informers" "k8s.io/client-go/metadata" @@ -117,8 +118,15 @@ func (f *metadataSharedInformerFactory) ForResource(gvr schema.GroupVersionResou return informer } -// Start initializes all requested informers. +// Start is a legacy wrapper that initializes all requested informers. +// +//logcheck:context // StartWithContext should be used instead of Start in code which supports contextual logging. func (f *metadataSharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext(wait.ContextForChannel(stopCh)) +} + +// StartWithContext initializes all requested informers. +func (f *metadataSharedInformerFactory) StartWithContext(ctx context.Context) { f.lock.Lock() defer f.lock.Unlock() @@ -135,7 +143,7 @@ func (f *metadataSharedInformerFactory) Start(stopCh <-chan struct{}) { informer := informer.Informer() go func() { defer f.wg.Done() - informer.Run(stopCh) + informer.RunWithContext(ctx) }() f.startedInformers[informerType] = true } diff --git a/staging/src/k8s.io/client-go/openapi/openapitest/testdata/api__v1_openapi.json b/staging/src/k8s.io/client-go/openapi/openapitest/testdata/api__v1_openapi.json index c71267015b874..349db34d35fad 100644 --- a/staging/src/k8s.io/client-go/openapi/openapitest/testdata/api__v1_openapi.json +++ b/staging/src/k8s.io/client-go/openapi/openapitest/testdata/api__v1_openapi.json @@ -5085,7 +5085,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { diff --git a/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__apps__v1_openapi.json b/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__apps__v1_openapi.json index f8675877f86fb..4f16ab19fabf2 100644 --- a/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__apps__v1_openapi.json +++ b/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__apps__v1_openapi.json @@ -3527,7 +3527,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { diff --git a/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__batch__v1_openapi.json b/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__batch__v1_openapi.json index a8f96b0078840..9834b64f6b22e 100644 --- a/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__batch__v1_openapi.json +++ b/staging/src/k8s.io/client-go/openapi/openapitest/testdata/apis__batch__v1_openapi.json @@ -2701,7 +2701,7 @@ "type": "boolean" }, "hostUsers": { - "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + "description": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host.", "type": "boolean" }, "hostname": { diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go index 1af2afdb9120f..b2393f4dd320d 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go @@ -38,7 +38,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/util/dump" utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/pkg/apis/clientauthentication" @@ -51,6 +50,7 @@ import ( "k8s.io/client-go/util/connrotation" "k8s.io/klog/v2" "k8s.io/utils/clock" + "k8s.io/utils/dump" ) const execInfoEnv = "KUBERNETES_EXEC_INFO" @@ -185,8 +185,8 @@ func newAuthenticator(c *cache, isTerminalFunc func(int) bool, config *api.ExecC allowlistLookup := sets.New[string]() for _, entry := range config.PluginPolicy.Allowlist { - if entry.Name != "" { - allowlistLookup.Insert(entry.Name) + if entry.Command != "" { + allowlistLookup.Insert(entry.Command) } } @@ -641,14 +641,14 @@ func (a *Authenticator) checkAllowlistLocked(cmd *exec.Cmd) error { func (a *Authenticator) resolveAllowListEntriesLocked(commandHint string) { hintName := filepath.Base(commandHint) for _, entry := range a.execPluginPolicy.Allowlist { - entryBasename := filepath.Base(entry.Name) + entryBasename := filepath.Base(entry.Command) if hintName != "" && hintName != entryBasename { // we got a hint, and this allowlist entry does not match it continue } - entryResolvedPath, err := exec.LookPath(entry.Name) + entryResolvedPath, err := exec.LookPath(entry.Command) if err != nil { - klog.V(5).ErrorS(err, "resolving credential plugin allowlist", "name", entry.Name) + klog.V(5).ErrorS(err, "resolving credential plugin allowlist", "name", entry.Command) continue } if entryResolvedPath != "" { @@ -691,10 +691,10 @@ func validateAllowlist(list []api.AllowlistEntry) error { return fmt.Errorf("misconfigured credential plugin allowlist: empty allowlist entry #%d", i+1) } - if cleaned := filepath.Clean(item.Name); cleaned != item.Name { - return fmt.Errorf("non-normalized file path: %q vs %q", item.Name, cleaned) - } else if item.Name == "" { - return fmt.Errorf("empty file path: %q", item.Name) + if cleaned := filepath.Clean(item.Command); cleaned != item.Command { + return fmt.Errorf("non-normalized file path: %q vs %q", item.Command, cleaned) + } else if item.Command == "" { + return fmt.Errorf("empty file path: %q", item.Command) } } diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go index ada512c66a6ab..06fd1a9a54b80 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go @@ -1077,7 +1077,7 @@ func (tt *pluginPolicyTest) setAllowlist(l int, existingPluginInPATHAbsolutePath } for i := 1; i < tt.allowlistLength; i++ { - tt.allowlist = append(tt.allowlist, api.AllowlistEntry{Name: fmt.Sprintf("foo-%d", i)}) + tt.allowlist = append(tt.allowlist, api.AllowlistEntry{Command: fmt.Sprintf("foo-%d", i)}) } // shuffle the allowlist to guarantee ordering doesn't matter @@ -1092,13 +1092,13 @@ func (tt *pluginPolicyTest) makeAllowlistEntry(existingPluginInPATHAbsolutePath switch { case tt.entryExists && tt.useEntryAbsPath: - entry.Name = existingPluginInPATHAbsolutePath + entry.Command = existingPluginInPATHAbsolutePath case tt.entryExists && !tt.useEntryAbsPath: - entry.Name = existingPluginInPATHBasename + entry.Command = existingPluginInPATHBasename case !tt.entryExists && tt.useEntryAbsPath: - entry.Name = "/this/path/does/not/exist" + entry.Command = "/this/path/does/not/exist" case !tt.entryExists && !tt.useEntryAbsPath: - entry.Name = "does not exist" + entry.Command = "does not exist" } return entry diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/metrics_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/metrics_test.go index b50ac89b6687d..9141f99bf986d 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/metrics_test.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/metrics_test.go @@ -243,10 +243,10 @@ func TestPolicyCallsMetric(t *testing.T) { PolicyType: api.PluginPolicyAllowlist, Allowlist: []api.AllowlistEntry{ { - Name: "foobar", + Command: "foobar", }, { - Name: "testdata/test-plugin.sh", + Command: "testdata/test-plugin.sh", }, }, }, @@ -256,8 +256,8 @@ func TestPolicyCallsMetric(t *testing.T) { policy: api.PluginPolicy{ PolicyType: api.PluginPolicyAllowlist, Allowlist: []api.AllowlistEntry{ - {Name: "foobar"}, - {Name: "baz"}, + {Command: "foobar"}, + {Command: "baz"}, }, }, }, diff --git a/staging/src/k8s.io/client-go/rest/exec_test.go b/staging/src/k8s.io/client-go/rest/exec_test.go index 290b512c2ac01..bd28ceb44a882 100644 --- a/staging/src/k8s.io/client-go/rest/exec_test.go +++ b/staging/src/k8s.io/client-go/rest/exec_test.go @@ -192,7 +192,6 @@ func TestConfigToExecCluster(t *testing.T) { }, } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { out, err := ConfigToExecCluster(&test.in) if test.wantErrorPrefix != "" { diff --git a/staging/src/k8s.io/client-go/rest/transport_test.go b/staging/src/k8s.io/client-go/rest/transport_test.go index 93b341030a3a2..0468ddea375ed 100644 --- a/staging/src/k8s.io/client-go/rest/transport_test.go +++ b/staging/src/k8s.io/client-go/rest/transport_test.go @@ -128,7 +128,6 @@ uml0obOEy+ON91k+SWTJ3ggmF/U= var wg sync.WaitGroup for _, tt := range configurations { - tt := tt wg.Add(1) go func() { defer wg.Done() diff --git a/staging/src/k8s.io/client-go/restmapper/discovery_test.go b/staging/src/k8s.io/client-go/restmapper/discovery_test.go index 5df045f9cf05b..fa2b6bf8ff7f4 100644 --- a/staging/src/k8s.io/client-go/restmapper/discovery_test.go +++ b/staging/src/k8s.io/client-go/restmapper/discovery_test.go @@ -24,12 +24,12 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/version" . "k8s.io/client-go/discovery" "k8s.io/client-go/openapi" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" + "k8s.io/utils/dump" openapi_v2 "github.com/google/gnostic-models/openapiv2" "github.com/stretchr/testify/assert" diff --git a/staging/src/k8s.io/client-go/testing/doc.go b/staging/src/k8s.io/client-go/testing/doc.go new file mode 100644 index 0000000000000..991535703fd12 --- /dev/null +++ b/staging/src/k8s.io/client-go/testing/doc.go @@ -0,0 +1,70 @@ +/* +Copyright 2015 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 testing provides a fake Kubernetes client suitable for use in unit +// tests. The fake client simulates interactions with a kube-apiserver without +// requiring one to be running, making tests fast and self-contained. +// +// # Scope and Limitations +// +// This fake client is intentionally simplified. It does not, and will never, +// fully replicate the behavior of a real kube-apiserver. Many server-side +// behaviors such as field defaulting, validation, status management, +// strategic merge patch, server-side apply semantics, and advanced +// field selectors are not supported by this client and there are no plans +// to add them. +// +// This is by design. Maintaining a high-fidelity mock of the entire +// kube-apiserver API surface would introduce significant complexity that is +// difficult to justify for a test utility. +// +// # When to Use This Package +// +// The fake client works well for unit tests that need to verify how your code +// interacts with the Kubernetes API at a structural level, for example: +// +// - Verifying that the correct API calls are made. +// - Supplying canned responses to drive specific code paths. +// +// # When Not to Use This Package +// +// If your tests depend on the kube-apiserver behaving correctly (e.g., +// enforcing validation, persisting resources accurately, handling apply +// semantics, or producing realistic watch events), you should write +// integration tests against a real kube-apiserver instead. +// +// # Contributing +// +// Issues requesting that the fake client more closely match kube-apiserver +// behavior should be limiting to bugs in how the fake behaves for unit test +// scenarios it is clearly intended to support. Pull requests that improve the fake +// client will only be accepted when they meet all of the following criteria: +// +// - The change makes the fake client easier to use for common unit testing +// patterns. +// - The change does not introduce significant complexity to the fake client. +// - The use cases motivating the change are clearly better served by a fake +// client than by integration tests against a real kube-apiserver. +// +// We hold a high bar for these changes. If the test scenarios in question can +// be reasonably addressed through integration testing, we will prefer that +// path over expanding the fake client. +// +// We understand this stance may be inconvenient, and we appreciate your +// understanding. Our goal is to keep this package simple, maintainable, and +// honest about what it provides so that it remains a reliable tool for the +// cases it is designed to handle. +package testing diff --git a/staging/src/k8s.io/client-go/testing/fixture.go b/staging/src/k8s.io/client-go/testing/fixture.go index 152a5c1bacbf7..4a91a99c70a45 100644 --- a/staging/src/k8s.io/client-go/testing/fixture.go +++ b/staging/src/k8s.io/client-go/testing/fixture.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" "sort" + "strconv" "strings" "sync" @@ -288,13 +289,46 @@ type tracker struct { scheme ObjectScheme decoder runtime.Decoder lock sync.RWMutex - objects map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object + objects map[schema.GroupVersionResource]map[types.NamespacedName]versionedObject // The value type of watchers is a map of which the key is either a namespace or // all/non namespace aka "" and its value is list of fake watchers. // Manipulations on resources will broadcast the notification events into the // watchers' channel. Note that too many unhandled events (currently 100, // see apimachinery/pkg/watch.DefaultChanSize) will cause a panic. watchers map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher + // resourceVersions is the highest resource version of any tracked object with + // a certain gvr. Conceptually it starts at 1 when no objects are stored (0 is + // special in queries) but the map contains no entries in that case. + // The resource version for that set of objects gets bumped before + // storing a new or modified object. + // + // Object content does not get changed to preserve the traditional behavior + // (hence also the versionedObject type instead of storing a runtime.Object + // with modified ResourceVersion). + // + // Resource version support (https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions) + // is very limited. It only supports one particular use case: + // List (no resource version check, returned ListMeta has ResourceVersion set) + + // Watch (Exact match for the ResourceVersion returned by List). + // + // This is sufficient for Reflector.ListAndWatch (https://github.com/kubernetes/kubernetes/blob/b53b9fb5573323484af9a19cf3f5bfe80760abba/staging/src/k8s.io/client-go/tools/cache/reflector.go#L401) + // when setting up informers in an informer factory. + // + // Strictly speaking, this should be by GroupVersion. But objects are + // also tracked by GroupVersionResource instead of GroupVersion, so the + // same is done here to match how List is implemented. + resourceVersions map[schema.GroupVersionResource]int64 +} + +// versionedObject stores an object together with the resource version that was +// assigned to it by the tracker. The version could be stored inline in the object, +// but this is not how fake client-go has traditionally worked and starting to do +// that now might break tests. +type versionedObject struct { + // resourceVersion is always > 1 for a stored object because 1 + // is the initial value for an empty set of objects. + resourceVersion int64 + runtime.Object } var _ ObjectTracker = &tracker{} @@ -303,10 +337,11 @@ var _ ObjectTracker = &tracker{} // of objects for the fake clientset. Mostly useful for unit tests. func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker { return &tracker{ - scheme: scheme, - decoder: decoder, - objects: make(map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object), - watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher), + scheme: scheme, + decoder: decoder, + objects: make(map[schema.GroupVersionResource]map[types.NamespacedName]versionedObject), + watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher), + resourceVersions: make(map[schema.GroupVersionResource]int64), } } @@ -338,15 +373,27 @@ func (t *tracker) List(gvr schema.GroupVersionResource, gvk schema.GroupVersionK t.lock.RLock() defer t.lock.RUnlock() + if listMeta, err := meta.ListAccessor(list); err == nil { + resourceVersion, ok := t.resourceVersions[gvr] + if !ok { + resourceVersion = 1 + } + listMeta.SetResourceVersion(fmt.Sprintf("%d", resourceVersion)) + } + objs, ok := t.objects[gvr] if !ok { return list, nil } - matchingObjs, err := filterByNamespace(objs, ns) + matchingVersionedObjs, err := filterByNamespace(objs, ns) if err != nil { return nil, err } + matchingObjs := make([]runtime.Object, len(matchingVersionedObjs)) + for i, obj := range matchingVersionedObjs { + matchingObjs[i] = obj.Object + } if err := meta.SetList(list, matchingObjs); err != nil { return nil, err } @@ -359,6 +406,27 @@ func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string, opts ...meta return nil, err } + // By default, emulate the traditional behavior of the tracker and don't deliver + // *any* existing objects unless list options are provided. + addExisting := false + addFromRV := int64(0) + if len(opts) > 0 { + // Providing options, as the generated client-go fake does, enables support + // for existing objects depending on the resource version. + // + // The default if ResourceVersion is empty is "start at most recent", + // which includes delivering all existing objects. addFromRV == 0 + // matches all objects below because all stored objects have addFromRV > 0. + addExisting = true + if opts[0].ResourceVersion != "" { + rv, err := strconv.ParseInt(opts[0].ResourceVersion, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid ResourceVersion %q in ListOptions, must be int64: %w", opts[0].ResourceVersion, err) + } + addFromRV = rv + } + } + t.lock.Lock() defer t.lock.Unlock() @@ -368,6 +436,22 @@ func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string, opts ...meta t.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher) } t.watchers[gvr][ns] = append(t.watchers[gvr][ns], fakewatcher) + + // Deliver all objects that match the list options, for example + // between the initial List and the following Watch. + if addExisting { + objs := t.objects[gvr] + matchingObjs, err := filterByNamespace(objs, ns) + if err != nil { + return nil, err + } + for _, obj := range matchingObjs { + if addFromRV < obj.resourceVersion { + fakewatcher.Add(obj.Object) + } + } + } + return fakewatcher, nil } @@ -565,17 +649,26 @@ func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns st _, ok := t.objects[gvr] if !ok { - t.objects[gvr] = make(map[types.NamespacedName]runtime.Object) + t.objects[gvr] = make(map[types.NamespacedName]versionedObject) } + // Determine resource version for the new or updated object. + resourceVersion, ok := t.resourceVersions[gvr] + if !ok { + resourceVersion = 1 + } + resourceVersion++ + namespacedName := types.NamespacedName{Namespace: newMeta.GetNamespace(), Name: newMeta.GetName()} if _, ok = t.objects[gvr][namespacedName]; ok { if replaceExisting { + t.resourceVersions[gvr] = resourceVersion + t.objects[gvr][namespacedName] = versionedObject{resourceVersion, obj} + for _, w := range t.getWatches(gvr, ns) { // To avoid the object from being accidentally modified by watcher w.Modify(obj.DeepCopyObject()) } - t.objects[gvr][namespacedName] = obj return nil } return apierrors.NewAlreadyExists(gr, newMeta.GetName()) @@ -586,7 +679,8 @@ func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns st return apierrors.NewNotFound(gr, newMeta.GetName()) } - t.objects[gvr][namespacedName] = obj + t.resourceVersions[gvr] = resourceVersion + t.objects[gvr][namespacedName] = versionedObject{resourceVersion, obj} for _, w := range t.getWatches(gvr, ns) { // To avoid the object from being accidentally modified by watcher @@ -841,11 +935,11 @@ func (d *objectDefaulter) Default(_ runtime.Object) {} // filterByNamespace returns all objects in the collection that // match provided namespace. Empty namespace matches // non-namespaced objects. -func filterByNamespace(objs map[types.NamespacedName]runtime.Object, ns string) ([]runtime.Object, error) { - var res []runtime.Object +func filterByNamespace(objs map[types.NamespacedName]versionedObject, ns string) ([]versionedObject, error) { + var res []versionedObject for _, obj := range objs { - acc, err := meta.Accessor(obj) + acc, err := meta.Accessor(obj.Object) if err != nil { return nil, err } @@ -857,8 +951,8 @@ func filterByNamespace(objs map[types.NamespacedName]runtime.Object, ns string) // Sort res to get deterministic order. sort.Slice(res, func(i, j int) bool { - acc1, _ := meta.Accessor(res[i]) - acc2, _ := meta.Accessor(res[j]) + acc1, _ := meta.Accessor(res[i].Object) + acc2, _ := meta.Accessor(res[j].Object) if acc1.GetNamespace() != acc2.GetNamespace() { return acc1.GetNamespace() < acc2.GetNamespace() } diff --git a/staging/src/k8s.io/client-go/testing/internal/listandwatch_test.go b/staging/src/k8s.io/client-go/testing/internal/listandwatch_test.go new file mode 100644 index 0000000000000..732eaabee10c0 --- /dev/null +++ b/staging/src/k8s.io/client-go/testing/internal/listandwatch_test.go @@ -0,0 +1,135 @@ +/* +Copyright 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 internal + +import ( + "context" + "testing" + "testing/synctest" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2/ktesting" + _ "k8s.io/klog/v2/ktesting/init" // for -testing.v +) + +// TestListAndWatch mirrors how fake client-go is often used with real +// informers. It enforces a timing such that List completes, a new +// object gets created because of the completed cache sync, and only +// then is the Watch call in the reflector's "ListAndWatch" allowed to +// continue. +// +// The fake Watch implementation then must use the ResourceVersion to +// detect that it must send some (but not all!) objects to the new watch. +// +// This runs in a synctest bubble, therefore time is virtual. +func TestListAndWatch(t *testing.T) { synctest.Test(t, testListAndWatch) } +func testListAndWatch(t *testing.T) { + logger, ctx := ktesting.NewTestContext(t) + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cm1", + Namespace: "default", + }, + } + client := fake.NewClientset(cm) + stopCh := make(chan struct{}) + defer close(stopCh) + createDone := make(chan struct{}) + + f := informers.NewSharedInformerFactory(client, 0) + configMapInformer := f.InformerFor(&v1.ConfigMap{}, func(client kubernetes.Interface, defaultEventHandlerResyncPeriod time.Duration) cache.SharedIndexInformer { + + return cache.NewSharedIndexInformer(cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + objs, err := client.CoreV1().ConfigMaps("").List(context.Background(), options) + logger.Info("Listed", "configMaps", objs, "err", err) + if err != nil { + t.Errorf("Unexpected List error: %v", err) + } else if objs.ResourceVersion != "2" { + t.Errorf("Expected ListMeta ResourceVersion 2, got %q", objs.ResourceVersion) + } + return objs, err + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if options.ResourceVersion != "2" { + t.Errorf("Expected ListOptions ResourceVersion 2, got %q", options.ResourceVersion) + } + logger.Info("Delaying Watch...") + <-createDone + logger.Info("Continuing Watch...") + return client.CoreV1().ConfigMaps("").Watch(context.Background(), options) + }, + }, client), &v1.ConfigMap{}, defaultEventHandlerResyncPeriod, nil) + }) + + var adds, updates, deletes int + handle, err := configMapInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(_ any) { adds++ }, + UpdateFunc: func(_, _ any) { updates++ }, + DeleteFunc: func(_ any) { deletes++ }, + }) + if err != nil { + t.Fatalf("Unexpected error adding event handler: %v", err) + } + defer configMapInformer.RemoveEventHandler(handle) + + configMapStore := configMapInformer.GetStore() + f.Start(stopCh) + f.WaitForCacheSync(stopCh) + logger.Info("Caches synced") + + objs := configMapStore.List() + if len(objs) != 1 { + t.Fatalf("Unexpected item(s) in informer cache, want 1, got %d = %v", len(objs), objs) + } + + cm = &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cm2", + Namespace: "default", + }, + } + _, err = client.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Unexpected error creating ConfigMap: %v", err) + } + logger.Info("Created second ConfigMap") + close(createDone) + + // Wait for watch setup and event processing. + synctest.Wait() + + objs = configMapStore.List() + if len(objs) != 2 { + t.Errorf("Unexpected item(s) in informer cache, want 2, got %d = %v", len(objs), objs) + } + + if !handle.HasSynced() { + t.Error("Expected event handler to have synced, it didn't") + } + if adds != 2 || updates != 0 || deletes != 0 { + t.Errorf("Expected two new objects, got adds/updates/deletes %d/%d/%d", adds, updates, deletes) + } +} diff --git a/vendor/github.com/gregjones/httpcache/LICENSE.txt b/staging/src/k8s.io/client-go/third_party/forked/httpcache/LICENSE similarity index 100% rename from vendor/github.com/gregjones/httpcache/LICENSE.txt rename to staging/src/k8s.io/client-go/third_party/forked/httpcache/LICENSE diff --git a/vendor/github.com/gregjones/httpcache/httpcache.go b/staging/src/k8s.io/client-go/third_party/forked/httpcache/httpcache.go similarity index 98% rename from vendor/github.com/gregjones/httpcache/httpcache.go rename to staging/src/k8s.io/client-go/third_party/forked/httpcache/httpcache.go index b41a63d1ff5a6..b92230e4957d1 100644 --- a/vendor/github.com/gregjones/httpcache/httpcache.go +++ b/staging/src/k8s.io/client-go/third_party/forked/httpcache/httpcache.go @@ -1,9 +1,13 @@ +// This package is copied from github.com/gregjones/httpcache +// with the following changes: +// - ioutil.NopCloser replaced with io.NopCloser (deprecated) +// - ioutil.ReadAll replaced with io.ReadAll (deprecated) + // Package httpcache provides a http.RoundTripper implementation that works as a // mostly RFC-compliant cache for http responses. // // It is only suitable for use as a 'private' cache (i.e. for a web-browser or an API-client // and not for a shared proxy). -// package httpcache import ( @@ -11,7 +15,6 @@ import ( "bytes" "errors" "io" - "io/ioutil" "net/http" "net/http/httputil" "strings" @@ -234,7 +237,7 @@ func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error R: resp.Body, OnEOF: func(r io.Reader) { resp := *resp - resp.Body = ioutil.NopCloser(r) + resp.Body = io.NopCloser(r) respBytes, err := httputil.DumpResponse(&resp, true) if err == nil { t.Cache.Set(cacheKey, respBytes) diff --git a/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go b/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go index 871474675b971..6c0d672c53de6 100644 --- a/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go +++ b/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go @@ -254,7 +254,6 @@ func TestLoadExecCredential(t *testing.T) { }, } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go b/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go index bf08da9362de8..4e33b67c8583d 100644 --- a/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go +++ b/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go @@ -35,7 +35,6 @@ func TestClientAuthenticationClusterTypesAreSynced(t *testing.T) { clientauthenticationv1beta1.Cluster{}, clientauthenticationv1.Cluster{}, } { - cluster := cluster t.Run(fmt.Sprintf("%T", cluster), func(t *testing.T) { t.Parallel() testClientAuthenticationClusterTypesAreSynced(t, cluster) diff --git a/staging/src/k8s.io/client-go/tools/cache/OWNERS b/staging/src/k8s.io/client-go/tools/cache/OWNERS index fc441e0efa8fd..77d7e4d28a9b8 100644 --- a/staging/src/k8s.io/client-go/tools/cache/OWNERS +++ b/staging/src/k8s.io/client-go/tools/cache/OWNERS @@ -22,6 +22,7 @@ reviewers: - jsafrane - dims - ingvagabund + - michaelasp emeritus_approvers: - lavalamp - ncdc diff --git a/staging/src/k8s.io/client-go/tools/cache/controller.go b/staging/src/k8s.io/client-go/tools/cache/controller.go index 51e0a4659fcff..b78ed522e241a 100644 --- a/staging/src/k8s.io/client-go/tools/cache/controller.go +++ b/staging/src/k8s.io/client-go/tools/cache/controller.go @@ -27,6 +27,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" clientgofeaturegate "k8s.io/client-go/features" + "k8s.io/klog/v2" "k8s.io/utils/clock" ) @@ -141,6 +142,11 @@ type Controller interface { // HasSynced delegates to the Config's Queue HasSynced() bool + // HasSyncedChecker enables waiting for syncing without polling. + // The returned DoneChecker can be passed to WaitFor. + // It delegates to the Config's Queue. + HasSyncedChecker() DoneChecker + // LastSyncResourceVersion delegates to the Reflector when there // is one, otherwise returns the empty string LastSyncResourceVersion() string @@ -167,11 +173,13 @@ func (c *controller) RunWithContext(ctx context.Context) { <-ctx.Done() c.config.Queue.Close() }() + logger := klog.FromContext(ctx) r := NewReflectorWithOptions( c.config.ListerWatcher, c.config.ObjectType, c.config.Queue, ReflectorOptions{ + Logger: &logger, ResyncPeriod: c.config.FullResyncPeriod, MinWatchTimeout: c.config.MinWatchTimeout, TypeDescription: c.config.ObjectDescription, @@ -205,6 +213,13 @@ func (c *controller) HasSynced() bool { return c.config.Queue.HasSynced() } +// HasSyncedChecker enables waiting for syncing without polling. +// The returned DoneChecker can be passed to [WaitFor]. +// It delegates to the Config's Queue. +func (c *controller) HasSyncedChecker() DoneChecker { + return c.config.Queue.HasSyncedChecker() +} + func (c *controller) LastSyncResourceVersion() string { c.reflectorMutex.RLock() defer c.reflectorMutex.RUnlock() @@ -231,7 +246,7 @@ func (c *controller) processLoop(ctx context.Context) { default: var err error if useBatchProcess { - err = batchQueue.PopBatch(c.config.ProcessBatch) + err = batchQueue.PopBatch(c.config.ProcessBatch, PopProcessFunc(c.config.Process)) } else { // otherwise fallback to non-batch process behavior _, err = c.config.Pop(PopProcessFunc(c.config.Process)) @@ -395,6 +410,9 @@ func DeletionHandlingObjectToName(obj interface{}) (ObjectName, error) { // InformerOptions configure a Reflector. type InformerOptions struct { + // Logger, if not nil, is used instead of klog.Background() for logging. + Logger *klog.Logger + // ListerWatcher implements List and Watch functions for the source of the resource // the informer will be informing about. ListerWatcher ListerWatcher @@ -429,6 +447,14 @@ type InformerOptions struct { // for them. // Optional - if unset no additional transforming is happening. Transform TransformFunc + + // Identifier is used to identify the FIFO for metrics and logging purposes. + // If not set, metrics will not be published. + Identifier InformerNameAndResource + + // InformerMetricsProvider is the metrics provider for the informer. + // If not set, metrics will be no-ops. + InformerMetricsProvider InformerMetricsProvider } // NewInformerWithOptions returns a Store and a controller for populating the store @@ -438,11 +464,11 @@ type InformerOptions struct { func NewInformerWithOptions(options InformerOptions) (Store, Controller) { var clientState Store if options.Indexers == nil { - clientState = NewStore(DeletionHandlingMetaNamespaceKeyFunc) + clientState = NewStore(DeletionHandlingMetaNamespaceKeyFunc, WithStoreMetrics(options.Identifier, options.InformerMetricsProvider)) } else { - clientState = NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers) + clientState = NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers, WithStoreMetrics(options.Identifier, options.InformerMetricsProvider)) } - return clientState, newInformer(clientState, options) + return clientState, newInformer(clientState, options, DeletionHandlingMetaNamespaceKeyFunc) } // NewInformer returns a Store and a controller for populating the store @@ -476,7 +502,7 @@ func NewInformer( Handler: h, ResyncPeriod: resyncPeriod, } - return clientState, newInformer(clientState, options) + return clientState, newInformer(clientState, options, DeletionHandlingMetaNamespaceKeyFunc) } // NewIndexerInformer returns an Indexer and a Controller for populating the index @@ -513,7 +539,7 @@ func NewIndexerInformer( ResyncPeriod: resyncPeriod, Indexers: indexers, } - return clientState, newInformer(clientState, options) + return clientState, newInformer(clientState, options, DeletionHandlingMetaNamespaceKeyFunc) } // NewTransformingInformer returns a Store and a controller for populating @@ -542,7 +568,7 @@ func NewTransformingInformer( ResyncPeriod: resyncPeriod, Transform: transformer, } - return clientState, newInformer(clientState, options) + return clientState, newInformer(clientState, options, DeletionHandlingMetaNamespaceKeyFunc) } // NewTransformingIndexerInformer returns an Indexer and a controller for @@ -573,23 +599,43 @@ func NewTransformingIndexerInformer( Indexers: indexers, Transform: transformer, } - return clientState, newInformer(clientState, options) + return clientState, newInformer(clientState, options, DeletionHandlingMetaNamespaceKeyFunc) } // Multiplexes updates in the form of a list of Deltas into a Store, and informs // a given handler of events OnUpdate, OnAdd, OnDelete func processDeltas( + logger klog.Logger, // Object which receives event notifications from the given deltas handler ResourceEventHandler, clientState Store, deltas Deltas, isInInitialList bool, + keyFunc KeyFunc, ) error { // from oldest to newest for _, d := range deltas { obj := d.Object switch d.Type { + case ReplacedAll: + info, ok := obj.(ReplacedAllInfo) + if !ok { + return fmt.Errorf("ReplacedAll did not contain ReplacedAllInfo: %T", obj) + } + if err := processReplacedAllInfo(logger, handler, info, clientState, isInInitialList, keyFunc); err != nil { + return err + } + case SyncAll: + _, ok := obj.(SyncAllInfo) + if !ok { + return fmt.Errorf("SyncAll did not contain SyncAllInfo: %T", obj) + } + objs := clientState.List() + for _, obj := range objs { + handler.OnUpdate(obj, obj) + } + return nil case Sync, Replaced, Added, Updated: if old, exists, err := clientState.Get(obj); err == nil && exists { if err := clientState.Update(obj); err != nil { @@ -607,6 +653,12 @@ func processDeltas( return err } handler.OnDelete(obj) + case Bookmark: + info, ok := obj.(BookmarkInfo) + if !ok { + return fmt.Errorf("bookmark delta did not contain BookmarkInfo: %T", obj) + } + clientState.Bookmark(info.ResourceVersion) } } return nil @@ -622,10 +674,12 @@ func processDeltas( // Returns an error if any Delta or transaction fails. For TransactionError, // only successful operations trigger callbacks. func processDeltasInBatch( + logger klog.Logger, handler ResourceEventHandler, clientState Store, deltas []Delta, isInInitialList bool, + keyFunc KeyFunc, ) error { // from oldest to newest txns := make([]Transaction, 0) @@ -634,7 +688,7 @@ func processDeltasInBatch( if !txnSupported { var errs []error for _, delta := range deltas { - if err := processDeltas(handler, clientState, Deltas{delta}, isInInitialList); err != nil { + if err := processDeltas(logger, handler, clientState, Deltas{delta}, isInInitialList, keyFunc); err != nil { errs = append(errs, err) } } @@ -677,6 +731,8 @@ func processDeltasInBatch( callbacks = append(callbacks, func() { handler.OnDelete(obj) }) + default: + return fmt.Errorf("Delta type %s is not supported in batch processing", d.Type) } } @@ -697,18 +753,68 @@ func processDeltasInBatch( return nil } +func processReplacedAllInfo(logger klog.Logger, handler ResourceEventHandler, info ReplacedAllInfo, clientState Store, isInInitialList bool, keyFunc KeyFunc) error { + var deletions []DeletedFinalStateUnknown + type replacement struct { + oldObj interface{} + newObj interface{} + } + replacements := make([]replacement, 0, len(info.Objects)) + + err := reconcileReplacement(logger, nil, clientState, info.Objects, keyFunc, + func(obj DeletedFinalStateUnknown) error { + deletions = append(deletions, obj) + return nil + }, + func(obj interface{}) error { + // This behavior matches processDeltas handling of Replace deltas + if old, exists, err := clientState.Get(obj); err == nil && exists { + replacements = append(replacements, replacement{newObj: obj, oldObj: old}) + } else { + replacements = append(replacements, replacement{newObj: obj}) + } + return nil + }, + ) + if err != nil { + return err + } + + // Replace the client state first so the store reflects the events handlers are given + if err := clientState.Replace(info.Objects, info.ResourceVersion); err != nil { + return err + } + // Processing all deletions first matches behavior of RealFIFO#Replace + for _, objToDelete := range deletions { + handler.OnDelete(objToDelete) + } + // Processing adds/updates in order observed by reconcileReplacement matches behavior of RealFIFO#Replace + for _, r := range replacements { + if r.oldObj != nil { + handler.OnUpdate(r.oldObj, r.newObj) + } else { + handler.OnAdd(r.newObj, isInInitialList) + } + } + return nil +} + // newInformer returns a controller for populating the store while also // providing event notifications. // // Parameters // - clientState is the store you want to populate // - options contain the options to configure the controller -func newInformer(clientState Store, options InformerOptions) Controller { +func newInformer(clientState Store, options InformerOptions, keyFunc KeyFunc) Controller { // This will hold incoming changes. Note how we pass clientState in as a // KeyLister, that way resync operations will result in the correct set // of update/delete deltas. - fifo := newQueueFIFO(clientState, options.Transform) + logger := klog.Background() + if options.Logger != nil { + logger = *options.Logger + } + logger, fifo := newQueueFIFO(logger, options.ObjectType, clientState, options.Transform, options.Identifier, options.InformerMetricsProvider) cfg := &Config{ Queue: fifo, @@ -719,29 +825,54 @@ func newInformer(clientState Store, options InformerOptions) Controller { Process: func(obj interface{}, isInInitialList bool) error { if deltas, ok := obj.(Deltas); ok { - return processDeltas(options.Handler, clientState, deltas, isInInitialList) + // This must be the logger *of the fifo*. + return processDeltas(logger, options.Handler, clientState, deltas, isInInitialList, keyFunc) } return errors.New("object given as Process argument is not Deltas") }, ProcessBatch: func(deltaList []Delta, isInInitialList bool) error { - return processDeltasInBatch(options.Handler, clientState, deltaList, isInInitialList) + // Same here. + return processDeltasInBatch(logger, options.Handler, clientState, deltaList, isInInitialList, keyFunc) }, } return New(cfg) } -func newQueueFIFO(clientState Store, transform TransformFunc) Queue { +// newQueueFIFO constructs a new FIFO, choosing between real and delta FIFO +// depending on the InOrderInformers feature gate. +// +// It returns the FIFO and the logger used by the FIFO. +// That logger includes the name used for the FIFO, +// in contrast to the logger which was passed in. +func newQueueFIFO(logger klog.Logger, objectType any, clientState Store, transform TransformFunc, identifier InformerNameAndResource, metricsProvider InformerMetricsProvider) (klog.Logger, Queue) { if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) { - return NewRealFIFOWithOptions(RealFIFOOptions{ - KeyFunction: MetaNamespaceKeyFunc, - KnownObjects: clientState, - Transformer: transform, - }) + options := RealFIFOOptions{ + Logger: &logger, + Name: fmt.Sprintf("RealFIFO %T", objectType), + KeyFunction: MetaNamespaceKeyFunc, + Transformer: transform, + Identifier: identifier, + MetricsProvider: metricsProvider, + } + // If atomic events are enabled, unset clientState in the case of atomic events as we cannot pass a + // store to an atomic fifo. + if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.AtomicFIFO) { + options.AtomicEvents = true + options.UnlockWhileProcessing = clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.UnlockWhileProcessingFIFO) + options.EmitDeltaTypeBookmark = true + } else { + options.KnownObjects = clientState + } + f := NewRealFIFOWithOptions(options) + return f.logger, f } else { - return NewDeltaFIFOWithOptions(DeltaFIFOOptions{ + f := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ + Logger: &logger, + Name: fmt.Sprintf("DeltaFIFO %T", objectType), KnownObjects: clientState, EmitDeltaTypeReplaced: true, Transformer: transform, }) + return f.logger, f } } diff --git a/staging/src/k8s.io/client-go/tools/cache/controller_bench_test.go b/staging/src/k8s.io/client-go/tools/cache/controller_bench_test.go new file mode 100644 index 0000000000000..f81a3d3f97fd9 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/controller_bench_test.go @@ -0,0 +1,112 @@ +/* +Copyright 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 cache + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fcache "k8s.io/client-go/tools/cache/testing" + "k8s.io/klog/v2/ktesting" +) + +const handlerWaitTime = time.Millisecond + +func BenchmarkAddWithSlowHandlers(b *testing.B) { + for _, unlockWhileProcessing := range []bool{false, true} { + b.Run(fmt.Sprintf("unlockWhileProcessing=%t", unlockWhileProcessing), func(b *testing.B) { + logger, ctx := ktesting.NewTestContext(b) + ctx, cancel := context.WithCancel(ctx) + source := fcache.NewFakeControllerSource() + b.Cleanup(func() { + cancel() + source.Shutdown() + }) + testIDs := []string{"a-hello"} + source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: testIDs[0]}}) + + store := NewStore(DeletionHandlingMetaNamespaceKeyFunc) + fifoOptions := RealFIFOOptions{} + if unlockWhileProcessing { + fifoOptions.UnlockWhileProcessing = true + fifoOptions.AtomicEvents = true + } else { + fifoOptions.KnownObjects = store + } + fifo := NewRealFIFOWithOptions(fifoOptions) + handler := ResourceEventHandlerFuncs{ + UpdateFunc: func(oldObj, newObj interface{}) { + time.Sleep(handlerWaitTime) + }, + } + + cfg := &Config{ + Queue: fifo, + ListerWatcher: source, + ObjectType: &v1.Pod{}, + FullResyncPeriod: 0, + + Process: func(obj interface{}, isInInitialList bool) error { + if deltas, ok := obj.(Deltas); ok { + return processDeltas(logger, handler, store, deltas, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + } + return errors.New("object given as Process argument is not Deltas") + }, + ProcessBatch: func(deltaList []Delta, isInInitialList bool) error { + return processDeltasInBatch(logger, handler, store, deltaList, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + }, + } + c := New(cfg) + go c.RunWithContext(ctx) + if !WaitForCacheSync(ctx.Done(), c.HasSynced) { + b.Fatal("Timed out waiting for cache sync") + } + + // Producer: Modify object as fast as the handler can process it. This should ensure that the process func is always running. + go func() { + // Stop when the benchmark context is cancelled. + ticker := time.NewTicker(handlerWaitTime) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + source.Modify(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: testIDs[0]}}) + } + } + }() + benchmarkReflectorWithSlowHandlers(b, fifo) + }) + } +} + +func benchmarkReflectorWithSlowHandlers(b *testing.B, fifo *RealFIFO) { + b.ResetTimer() + // Try adding an object to the queue, while the controller is processing other events. + for i := 0; i < b.N; i++ { + if err := fifo.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "b-hello"}}); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} diff --git a/staging/src/k8s.io/client-go/tools/cache/controller_test.go b/staging/src/k8s.io/client-go/tools/cache/controller_test.go index 21a11c9080fef..1751bc4cb26f4 100644 --- a/staging/src/k8s.io/client-go/tools/cache/controller_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/controller_test.go @@ -21,14 +21,19 @@ import ( "errors" "fmt" "math/rand" + "sort" + "strconv" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -829,6 +834,7 @@ func TestProcessDeltasInBatch(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) mockStore := &mockTxnStore{ Store: NewStore(MetaNamespaceKeyFunc), failingObjs: tc.failingObjects, @@ -846,10 +852,12 @@ func TestProcessDeltasInBatch(t *testing.T) { }, } err := processDeltasInBatch( + logger, dummyListener, mockStore, tc.deltaList, - true) + true, + DeletionHandlingMetaNamespaceKeyFunc) if tc.assertErr != nil { assert.True(t, tc.assertErr(err)) } @@ -893,3 +901,553 @@ func (m *mockTxnStore) Transaction(txns ...Transaction) *TransactionError { m.succeedCount = len(txns) return nil } + +func TestReplaceEvents(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + // Test that both atomic and non-atomic replace have the same behavior + // in regards to events and store state. + for _, atomic := range []bool{false, true} { + source := fcache.NewFakeControllerSource() + t.Cleanup(func() { + source.Shutdown() + }) + store := NewStore(DeletionHandlingMetaNamespaceKeyFunc) + fifoOptions := RealFIFOOptions{ + AtomicEvents: atomic, + } + if !atomic { + fifoOptions.KnownObjects = store + } + fifo := NewRealFIFOWithOptions(fifoOptions) + recorder := newEventRecorder(store, DeletionHandlingMetaNamespaceKeyFunc) + + cfg := &Config{ + Queue: fifo, + ListerWatcher: source, + ObjectType: &v1.Pod{}, + FullResyncPeriod: 0, + + Process: func(obj interface{}, isInInitialList bool) error { + if deltas, ok := obj.(Deltas); ok { + return processDeltas(fifo.logger, recorder, store, deltas, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + } + return errors.New("object given as Process argument is not Deltas") + }, + ProcessBatch: func(deltaList []Delta, isInInitialList bool) error { + return processDeltasInBatch(fifo.logger, recorder, store, deltaList, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + }, + } + c := New(cfg) + go c.RunWithContext(ctx) + if !WaitForCacheSync(ctx.Done(), c.HasSynced) { + t.Fatal("Timed out waiting for cache sync") + } + testReplaceEvents(t, ctx, fifo, recorder, store, atomic) + } +} + +func testReplaceEvents(t *testing.T, ctx context.Context, fifo Queue, m *eventRecorder, store Store, atomic bool) { + tcs := []struct { + name string + initialObjs []metav1.Object + replacedItems []interface{} + replaceRV string + expectedHistory []eventRecord + }{ + { + name: "Create with no initial objects", + replacedItems: []interface{}{ + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default", ResourceVersion: "1"}}, + }, + expectedHistory: []eventRecord{ + {Action: "add", Key: "default/pod-1", EventRV: "1", StoreRV: "1"}, + }, + replaceRV: "1", + }, + { + name: "Delete all objects", + initialObjs: []metav1.Object{ + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default", ResourceVersion: "1"}}, + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-3", Namespace: "default", ResourceVersion: "2"}}, + }, + replacedItems: []interface{}{}, + expectedHistory: []eventRecord{ + {Action: "delete", Key: "default/pod-1", EventRV: "1", StoreRV: ""}, + {Action: "delete", Key: "default/pod-3", EventRV: "2", StoreRV: ""}, + }, + replaceRV: "2", + }, + { + name: "Create, update, delete", + initialObjs: []metav1.Object{ + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default", ResourceVersion: "1"}}, + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-2", Namespace: "default", Labels: map[string]string{"ver": "1"}, ResourceVersion: "2"}}, + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-3", Namespace: "default", ResourceVersion: "3"}}, + }, + replacedItems: []interface{}{ + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default", ResourceVersion: "4"}}, + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-2", Namespace: "default", Labels: map[string]string{"ver": "2"}, ResourceVersion: "5"}}, + &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod-4", Namespace: "default", ResourceVersion: "6"}}, + }, + expectedHistory: []eventRecord{ + {Action: "delete", Key: "default/pod-3", EventRV: "3", StoreRV: ""}, + {Action: "update", Key: "default/pod-1", EventRV: "4", StoreRV: "4"}, + {Action: "update", Key: "default/pod-2", EventRV: "5", StoreRV: "5"}, + {Action: "add", Key: "default/pod-4", EventRV: "6", StoreRV: "6"}, + }, + replaceRV: "6", + }, + } + + for _, tc := range tcs { + testName := tc.name + " atomic: " + strconv.FormatBool(atomic) + t.Run(testName, func(t *testing.T) { + // Clear history to ensure we don't count events from previous tests + m.clearHistory() + + for _, obj := range tc.initialObjs { + require.NoError(t, fifo.Add(obj), "failed to add") + } + + // Wait for all create events to appear + require.NoError(t, m.waitForEventCount(ctx, len(tc.initialObjs), 5*time.Second), "Controller failed to receive initial setup all events, got: %v", m.getHistory()) + + // Clear history so we only compare what happens during the RESYNC/REPLACE + m.clearHistory() + + // Run replace + require.NoError(t, fifo.Replace(tc.replacedItems, tc.replaceRV), "failed to replace") + + // Wait for all expected events to be received + require.NoError(t, m.waitForEventCount(ctx, len(tc.expectedHistory), 5*time.Second), "Controller failed to receive all events, got: %v", m.getHistory()) + + items := sortItemsByKey(store.List(), DeletionHandlingMetaNamespaceKeyFunc) + assert.Equal(t, tc.replacedItems, items) + // synthetic delete events from a replace are not ordered, sort by key to make the test deterministic + history := sortDeleteEventsByKey(m.getHistory()) + assert.Equal(t, tc.expectedHistory, history) + }) + } +} + +func TestResetWatch(t *testing.T) { + for _, atomic := range []bool{false, true} { + t.Run("atomic "+strconv.FormatBool(atomic), func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + source := fcache.NewFakeControllerSource() + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + }, + } + source.Add(pod) + t.Cleanup(func() { + source.Shutdown() + }) + + store := NewStore(DeletionHandlingMetaNamespaceKeyFunc) + fifoOptions := RealFIFOOptions{ + AtomicEvents: atomic, + } + if !atomic { + fifoOptions.KnownObjects = store + } + fifo := NewRealFIFOWithOptions(fifoOptions) + recorder := newEventRecorder(store, DeletionHandlingMetaNamespaceKeyFunc) + + cfg := &Config{ + Queue: fifo, + ListerWatcher: source, + ObjectType: &v1.Pod{}, + FullResyncPeriod: 0, + + Process: func(obj interface{}, isInInitialList bool) error { + if deltas, ok := obj.(Deltas); ok { + return processDeltas(fifo.logger, recorder, store, deltas, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + } + return errors.New("object given as Process argument is not Deltas") + }, + ProcessBatch: func(deltaList []Delta, isInInitialList bool) error { + return processDeltasInBatch(fifo.logger, recorder, store, deltaList, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + }, + } + c := New(cfg) + go c.RunWithContext(ctx) + if !WaitForCacheSync(ctx.Done(), c.HasSynced) { + t.Fatal("Timed out waiting for cache sync") + } + testResetWatch(t, ctx, source, recorder) + }) + } +} + +func testResetWatch(t *testing.T, ctx context.Context, source *fcache.FakeControllerSource, recorder *eventRecorder) { + // The first event should be the initial add. + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive initial setup all events, got: %v", recorder.getHistory()) + history := recorder.getHistory() + assert.Len(t, history, 1) + assert.Equal(t, "add", history[0].Action) + assert.Equal(t, "default/pod-1", history[0].Key) + assert.True(t, history[0].IsInInitialList) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + }, + } + pod2 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-2", + Namespace: "default", + }, + } + + recorder.clearHistory() + source.ModifyDropWatch(pod) + source.AddDropWatch(pod2) + source.ResetWatch() + + // We should get an add for the second pod and update for the initial pod on reset. + require.NoError(t, recorder.waitForEventCount(ctx, 2, 5*time.Second), "Controller failed to receive initial setup all events, got: %v", recorder.getHistory()) + history = recorder.getHistory() + assert.Len(t, history, 2) + + var addEvent eventRecord + var updateEvent eventRecord + for _, event := range history { + switch event.Action { + case "update": + updateEvent = event + case "add": + addEvent = event + } + } + assert.Equal(t, "default/pod-1", updateEvent.Key) + assert.False(t, updateEvent.IsInInitialList) + assert.Equal(t, "default/pod-2", addEvent.Key) + assert.False(t, addEvent.IsInInitialList) +} + +func TestStoreResourceVersion(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + source := fcache.NewFakeControllerSource() + source.Add(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + }, + }) + t.Cleanup(func() { + source.Shutdown() + }) + + store := NewStore(DeletionHandlingMetaNamespaceKeyFunc) + fifo := NewRealFIFOWithOptions(RealFIFOOptions{ + AtomicEvents: true, + }) + recorder := newEventRecorder(store, DeletionHandlingMetaNamespaceKeyFunc) + logger, _ := ktesting.NewTestContext(t) + + cfg := &Config{ + Queue: fifo, + ListerWatcher: source, + ObjectType: &v1.Pod{}, + FullResyncPeriod: 0, + Process: func(obj interface{}, isInInitialList bool) error { + if deltas, ok := obj.(Deltas); ok { + return processDeltas(logger, recorder, store, deltas, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + } + return errors.New("object given as Process argument is not Deltas") + }, + ProcessBatch: func(deltaList []Delta, isInInitialList bool) error { + return processDeltasInBatch(logger, recorder, store, deltaList, isInInitialList, DeletionHandlingMetaNamespaceKeyFunc) + }, + } + c := New(cfg) + go c.RunWithContext(ctx) + if !WaitForCacheSync(ctx.Done(), c.HasSynced) { + t.Fatal("Timed out waiting for cache sync") + } + testResourceVersion(t, ctx, source, recorder, store) +} + +func testResourceVersion(t *testing.T, ctx context.Context, source *fcache.FakeControllerSource, recorder *eventRecorder, store Store) { + // The first event should be the initial add. + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive initial setup all events, got: %v", recorder.getHistory()) + history := recorder.getHistory() + assert.Len(t, history, 1) + assert.Equal(t, "add", history[0].Action) + assert.Equal(t, "default/pod-1", history[0].Key) + assert.True(t, history[0].IsInInitialList) + + recorder.clearHistory() + // Update the pod labels to increment the resource version. + source.Modify(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Labels: map[string]string{ + "foo": "bar", + }, + }, + }) + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive update event, got: %v", recorder.getHistory()) + history = recorder.getHistory() + // The stored resource version should have changed. + assert.Equal(t, history[0].EventRV, store.LastStoreSyncResourceVersion()) + + recorder.clearHistory() + // Delete the pod. + source.Delete(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + }, + }) + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive delete event, got: %v", recorder.getHistory()) + history = recorder.getHistory() + // The stored resource version should have changed. + assert.Equal(t, history[0].EventRV, store.LastStoreSyncResourceVersion()) +} + +func TestStoreResourceVersionWithNonMetaTransform(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + source := fcache.NewFakeControllerSource() + source.Add(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + }, + }) + t.Cleanup(func() { + source.Shutdown() + }) + + // Create a keyfunc that does not rely on object metadata. + keyFunc := func(obj interface{}) (string, error) { + return "pod-1", nil + } + + store := NewStore(keyFunc) + fifo := NewRealFIFOWithOptions(RealFIFOOptions{ + Transformer: func(obj interface{}) (interface{}, error) { + pod, ok := obj.(*v1.Pod) + if !ok { + return nil, fmt.Errorf("expected *v1.Pod, got %T", obj) + } + return pod.Name, nil + }, + KeyFunction: keyFunc, + AtomicEvents: true, + }) + recorder := newEventRecorder(store, keyFunc) + logger, _ := ktesting.NewTestContext(t) + + cfg := &Config{ + Queue: fifo, + ListerWatcher: source, + ObjectType: &v1.Pod{}, + FullResyncPeriod: 0, + + Process: func(obj interface{}, isInInitialList bool) error { + if deltas, ok := obj.(Deltas); ok { + return processDeltas(logger, recorder, store, deltas, isInInitialList, keyFunc) + } + return errors.New("object given as Process argument is not Deltas") + }, + ProcessBatch: func(deltaList []Delta, isInInitialList bool) error { + return processDeltasInBatch(logger, recorder, store, deltaList, isInInitialList, keyFunc) + }, + } + c := New(cfg) + go c.RunWithContext(ctx) + if !WaitForCacheSync(ctx.Done(), c.HasSynced) { + t.Fatal("Timed out waiting for cache sync") + } + testResourceVersionWithNonMetaTransform(t, ctx, source, recorder, store) +} + +func testResourceVersionWithNonMetaTransform(t *testing.T, ctx context.Context, source *fcache.FakeControllerSource, recorder *eventRecorder, store Store) { + // The first event should be the initial add. + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive initial setup all events, got: %v", recorder.getHistory()) + history := recorder.getHistory() + assert.Len(t, history, 1) + assert.Equal(t, "add", history[0].Action) + assert.Equal(t, "pod-1", history[0].Key) + assert.True(t, history[0].IsInInitialList) + + // The stored resource version should stay at the initial value given by the replace event since + // the update event does not have metadata. + replaceRV := store.LastStoreSyncResourceVersion() + recorder.clearHistory() + // Update the pod labels to increment the resource version. + source.Modify(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Labels: map[string]string{ + "foo": "bar", + }, + }, + }) + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive update event, got: %v", recorder.getHistory()) + // The stored resource version should not be changed due to the transform. + assert.Equal(t, replaceRV, store.LastStoreSyncResourceVersion()) + + recorder.clearHistory() + // Modify the pod but do not send the event. + source.ModifyDropWatch(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Labels: map[string]string{ + "foo": "bar2", + }, + }, + }) + // Trigger a replace by resetting the watch + source.ResetWatch() + + require.NoError(t, recorder.waitForEventCount(ctx, 1, 5*time.Second), "Controller failed to receive replace event, got: %v", recorder.getHistory()) + // The stored resource version should be updated to the replace events RV. + assert.Equal(t, "3", store.LastStoreSyncResourceVersion()) +} + +type eventRecord struct { + Action string + Key string + EventRV string + StoreRV string + IsInInitialList bool +} + +type eventRecorder struct { + historyLock sync.Mutex + history []eventRecord + store Store + keyFunc KeyFunc + + updateCh chan bool +} + +func newEventRecorder(store Store, keyFunc KeyFunc) *eventRecorder { + return &eventRecorder{ + store: store, + updateCh: make(chan bool, 1), + keyFunc: keyFunc, + } +} + +func (m *eventRecorder) OnAdd(obj interface{}, isInInitialList bool) { + m.record("add", obj, isInInitialList) +} +func (m *eventRecorder) OnUpdate(_, obj interface{}) { + m.record("update", obj, false) +} +func (m *eventRecorder) OnDelete(obj interface{}) { + m.record("delete", obj, false) +} + +func (m *eventRecorder) record(action string, obj interface{}, isInInitialList bool) { + m.historyLock.Lock() + defer m.historyLock.Unlock() + key, _ := m.keyFunc(obj) + + if deleted, ok := obj.(DeletedFinalStateUnknown); ok { + obj = deleted.Obj + } + + eventRV := "" + if accessor, err := meta.Accessor(obj); err == nil { + eventRV = accessor.GetResourceVersion() + } + + storeRV := "" + if storeObj, exists, err := m.store.GetByKey(key); err == nil && exists { + if accessor, err := meta.Accessor(storeObj); err == nil { + storeRV = accessor.GetResourceVersion() + } + } + + m.history = append(m.history, eventRecord{ + Action: action, + Key: key, + EventRV: eventRV, + StoreRV: storeRV, + IsInInitialList: isInInitialList, + }) + select { + case m.updateCh <- true: + default: + } +} + +func (m *eventRecorder) clearHistory() { + m.historyLock.Lock() + defer m.historyLock.Unlock() + m.history = []eventRecord{} +} + +func (m *eventRecorder) getHistory() []eventRecord { + m.historyLock.Lock() + historyCopy := make([]eventRecord, len(m.history)) + copy(historyCopy, m.history) + m.historyLock.Unlock() + + return historyCopy +} + +func (m *eventRecorder) waitForEventCount(ctx context.Context, count int, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + for { + m.historyLock.Lock() + currentCount := len(m.history) + m.historyLock.Unlock() + + if currentCount >= count { + return nil + } + + select { + case <-m.updateCh: + continue + case <-ctx.Done(): + return fmt.Errorf("context canceled waiting for %d events, currently have %d", count, currentCount) + } + } +} + +func sortItemsByKey(items []interface{}, keyFunc KeyFunc) []interface{} { + sort.Slice(items, func(i, j int) bool { + keyI, _ := keyFunc(items[i]) + keyJ, _ := keyFunc(items[j]) + return keyI < keyJ + }) + return items +} + +func sortDeleteEventsByKey(events []eventRecord) []eventRecord { + sort.Slice(events, func(i, j int) bool { + eventI := events[i] + eventJ := events[j] + // sort delete events for different objects by key + if eventI.Action == "delete" && eventJ.Action == "delete" && eventI.Key != eventJ.Key { + return eventI.Key < eventJ.Key + } + // keep existing order otherwise + return i < j + }) + return events +} diff --git a/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go b/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go index 217bcf8b7ad6a..b2a191f78a5ca 100644 --- a/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go +++ b/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go @@ -22,6 +22,7 @@ import ( "sync" "time" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" @@ -31,6 +32,12 @@ import ( // DeltaFIFOOptions is the configuration parameters for DeltaFIFO. All are // optional. type DeltaFIFOOptions struct { + // If set, log output will go to this logger instead of klog.Background(). + // The name of the fifo gets added automatically. + Logger *klog.Logger + + // Name can be used to override the default "DeltaFIFO" name for the new instance. + Name string // KeyFunction is used to figure out what key an object should have. (It's // exposed in the returned DeltaFIFO's KeyOf() method, with additional @@ -55,9 +62,6 @@ type DeltaFIFOOptions struct { // If set, will be called for objects before enqueueing them. Please // see the comment on TransformFunc for details. Transformer TransformFunc - - // If set, log output will go to this logger instead of klog.Background(). - Logger *klog.Logger } // DeltaFIFO is like FIFO, but differs in two ways. One is that the @@ -102,6 +106,13 @@ type DeltaFIFOOptions struct { // threads, you could end up with multiple threads processing slightly // different versions of the same object. type DeltaFIFO struct { + // logger is a per-instance logger. This gets chosen when constructing + // the instance, with klog.Background() as default. + logger klog.Logger + + // name is the name of the fifo. It is included in the logger. + name string + // lock/cond protects access to 'items' and 'queue'. lock sync.RWMutex cond sync.Cond @@ -115,6 +126,11 @@ type DeltaFIFO struct { // A key is in `queue` if and only if it is in `items`. queue []string + // synced is initially an open channel. It gets closed (once!) by checkSynced_locked + // as soon as the initial sync is considered complete. + synced chan struct{} + syncedClosed bool + // populated is true if the first batch of items inserted by Replace() has been populated // or Delete/Add/Update/AddIfNotPresent was called first. populated bool @@ -139,10 +155,6 @@ type DeltaFIFO struct { // Called with every object if non-nil. transformer TransformFunc - - // logger is a per-instance logger. This gets chosen when constructing - // the instance, with klog.Background() as default. - logger klog.Logger } // TransformFunc allows for transforming an object before it will be processed. @@ -172,14 +184,27 @@ const ( Updated DeltaType = "Updated" Deleted DeltaType = "Deleted" // Replaced is emitted when we encountered watch errors and had to do a - // relist. We don't know if the replaced object has changed. + // relist, or on initial listing of objects. We don't know if the replaced + // object has changed. // // NOTE: Previous versions of DeltaFIFO would use Sync for Replace events // as well. Hence, Replaced is only emitted when the option // EmitDeltaTypeReplaced is true. Replaced DeltaType = "Replaced" + // ReplacedAll is emitted when we encountered watch errors and had to do + // a relist, or on initial listing of objects. This is the same reason as + // Replaced but will be emitted instead when the FIFO supports atomic + // replacement. This event will return the full list of replaced items + // instead of a single object. + ReplacedAll DeltaType = "ReplacedAll" // Sync is for synthetic events during a periodic resync. Sync DeltaType = "Sync" + // SyncAll indicates all known objects should be reprocessed. + // This event contains an object of type SyncAllInfo. + SyncAll DeltaType = "SyncAll" + // Bookmark is emitted on Bookmark calls and Replace calls to pass resource + // version information to the consumer. + Bookmark DeltaType = "Bookmark" ) // Delta is a member of Deltas (a list of Delta objects) which @@ -253,6 +278,9 @@ func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO { } f := &DeltaFIFO{ + logger: klog.Background(), + name: "DeltaFIFO", + synced: make(chan struct{}), items: map[string]Deltas{}, queue: []string{}, keyFunc: opts.KeyFunction, @@ -260,11 +288,14 @@ func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO { emitDeltaTypeReplaced: opts.EmitDeltaTypeReplaced, transformer: opts.Transformer, - logger: klog.Background(), } if opts.Logger != nil { f.logger = *opts.Logger } + if name := opts.Name; name != "" { + f.name = name + } + f.logger = klog.LoggerWithName(f.logger, f.name) f.cond.L = &f.lock return f } @@ -272,6 +303,7 @@ func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO { var ( _ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue _ = TransformingStore(&DeltaFIFO{}) // DeltaFIFO implements TransformingStore to allow memory optimizations + _ = DoneChecker(&DeltaFIFO{}) // DeltaFIFO implements DoneChecker. ) var ( @@ -317,8 +349,36 @@ func (f *DeltaFIFO) HasSynced() bool { return f.hasSynced_locked() } +// HasSyncedChecker is done if an Add/Update/Delete/AddIfNotPresent are called first, +// or the first batch of items inserted by Replace() has been popped. +func (f *DeltaFIFO) HasSyncedChecker() DoneChecker { + return f +} + +// Name implements [DoneChecker.Name] +func (f *DeltaFIFO) Name() string { + return f.name +} + +// Done implements [DoneChecker.Done] +func (f *DeltaFIFO) Done() <-chan struct{} { + return f.synced +} + +// hasSynced_locked returns the result of a prior checkSynced_locked call. func (f *DeltaFIFO) hasSynced_locked() bool { - return f.populated && f.initialPopulationCount == 0 + return f.syncedClosed +} + +// checkSynced_locked checks whether the initial is completed. +// It must be called whenever populated or initialPopulationCount change. +func (f *DeltaFIFO) checkSynced_locked() { + synced := f.populated && f.initialPopulationCount == 0 + if synced && !f.syncedClosed { + // Initial sync is complete. + f.syncedClosed = true + close(f.synced) + } } // Add inserts an item, and puts it in the queue. The item is only enqueued @@ -327,6 +387,7 @@ func (f *DeltaFIFO) Add(obj interface{}) error { f.lock.Lock() defer f.lock.Unlock() f.populated = true + f.checkSynced_locked() return f.queueActionLocked(Added, obj) } @@ -335,6 +396,7 @@ func (f *DeltaFIFO) Update(obj interface{}) error { f.lock.Lock() defer f.lock.Unlock() f.populated = true + f.checkSynced_locked() return f.queueActionLocked(Updated, obj) } @@ -351,6 +413,7 @@ func (f *DeltaFIFO) Delete(obj interface{}) error { f.lock.Lock() defer f.lock.Unlock() f.populated = true + f.checkSynced_locked() if f.knownObjects == nil { if _, exists := f.items[id]; !exists { // Presumably, this was deleted when a relist happened. @@ -467,10 +530,10 @@ func (f *DeltaFIFO) queueActionInternalLocked(actionType, internalActionType Del // when given a non-empty list (as it is here). // If somehow it happens anyway, deal with it but complain. if oldDeltas == nil { - f.logger.Error(nil, "Impossible dedupDeltas, ignoring", "id", id, "oldDeltas", oldDeltas, "obj", obj) + utilruntime.HandleErrorWithLogger(f.logger, nil, "Impossible dedupDeltas, ignoring", "id", id, "oldDeltas", oldDeltas, "obj", obj) return nil } - f.logger.Error(nil, "Impossible dedupDeltas, breaking invariant by storing empty Deltas", "id", id, "oldDeltas", oldDeltas, "obj", obj) + utilruntime.HandleErrorWithLogger(f.logger, nil, "Impossible dedupDeltas, breaking invariant by storing empty Deltas", "id", id, "oldDeltas", oldDeltas, "obj", obj) f.items[id] = newDeltas return fmt.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; broke DeltaFIFO invariant by storing empty Deltas", id, oldDeltas, obj) } @@ -516,11 +579,12 @@ func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) { depth := len(f.queue) if f.initialPopulationCount > 0 { f.initialPopulationCount-- + f.checkSynced_locked() } item, ok := f.items[id] if !ok { // This should never happen - f.logger.Error(nil, "Inconceivable! Item was in f.queue but not f.items; ignoring", "id", id) + utilruntime.HandleErrorWithLogger(f.logger, nil, "Inconceivable! Item was in f.queue but not f.items; ignoring", "id", id) continue } delete(f.items, id) @@ -613,7 +677,7 @@ func (f *DeltaFIFO) Replace(list []interface{}, _ string) error { deletedObj, exists, err := f.knownObjects.GetByKey(k) if err != nil { deletedObj = nil - f.logger.Error(err, "Unexpected error during lookup, placing DeleteFinalStateUnknown marker without object", "key", k) + utilruntime.HandleErrorWithLogger(f.logger, err, "Unexpected error during lookup, placing DeleteFinalStateUnknown marker without object", "key", k) } else if !exists { deletedObj = nil f.logger.Info("Key does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", "key", k) @@ -628,6 +692,7 @@ func (f *DeltaFIFO) Replace(list []interface{}, _ string) error { if !f.populated { f.populated = true f.initialPopulationCount = keys.Len() + queuedDeletions + f.checkSynced_locked() } return nil @@ -656,7 +721,7 @@ func (f *DeltaFIFO) Resync() error { func (f *DeltaFIFO) syncKeyLocked(key string) error { obj, exists, err := f.knownObjects.GetByKey(key) if err != nil { - f.logger.Error(err, "Unexpected error during lookup, unable to queue object for sync", "key", key) + utilruntime.HandleErrorWithLogger(f.logger, err, "Unexpected error during lookup, unable to queue object for sync", "key", key) return nil } else if !exists { f.logger.Info("Key does not exist in known objects store, unable to queue object for sync", "key", key) diff --git a/staging/src/k8s.io/client-go/tools/cache/delta_fifo_test.go b/staging/src/k8s.io/client-go/tools/cache/delta_fifo_test.go index 24f0ec87d04bb..3100b30db76ed 100644 --- a/staging/src/k8s.io/client-go/tools/cache/delta_fifo_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/delta_fifo_test.go @@ -278,8 +278,6 @@ func TestDeltaFIFOW_ReplaceMakesDeletionsForObjectsOnlyInQueue(t *testing.T) { }, } for _, tt := range table { - tt := tt - t.Run(tt.name, func(t *testing.T) { // Test with a DeltaFIFO with a backing KnownObjects fWithKnownObjects := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ diff --git a/staging/src/k8s.io/client-go/tools/cache/event_handler_name.go b/staging/src/k8s.io/client-go/tools/cache/event_handler_name.go new file mode 100644 index 0000000000000..d466fc6e2dcbb --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/event_handler_name.go @@ -0,0 +1,121 @@ +/* +Copyright 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 cache + +import ( + "fmt" + "reflect" + "runtime" + "strings" +) + +func nameForHandler(handler ResourceEventHandler) (name string) { + defer func() { + // Last resort: let Sprintf handle it. + if name == "" { + name = fmt.Sprintf("%T", handler) + } + }() + + if handler == nil { + return "" + } + switch handler := handler.(type) { + case *ResourceEventHandlerFuncs: + return nameForHandlerFuncs(*handler) + case ResourceEventHandlerFuncs: + return nameForHandlerFuncs(handler) + default: + // We can use the fully qualified name of whatever + // provides the interface. We don't care whether + // it contains fields or methods which provide + // the interface methods. + value := reflect.ValueOf(handler) + if value.Type().Kind() == reflect.Interface { + // Probably not needed, but let's play it safe. + value = value.Elem() + } + if value.Type().Kind() == reflect.Pointer { + if !value.IsNil() { + value = value.Elem() + } + } + name := value.Type().PkgPath() + if name != "" { + name += "." + } + if typeName := value.Type().Name(); typeName != "" { + name += typeName + } + return name + } +} + +func nameForHandlerFuncs(funcs ResourceEventHandlerFuncs) string { + return nameForFunctions(funcs.AddFunc, funcs.UpdateFunc, funcs.DeleteFunc) +} + +func nameForFunctions(fs ...any) string { + // If all functions are defined in the same place, then we + // don't care about the actual function name in + // e.g. "main.FuncName" or "main.(*Foo).FuncName-fm", instead + // we use the common qualifier. + // + // But we don't know that yet, so we also collect all names. + var qualifier string + singleQualifier := true + var names []string + for _, f := range fs { + if f == nil { + continue + } + name := nameForFunction(f) + if name == "" { + continue + } + names = append(names, name) + + newQualifier := name + index := strings.LastIndexByte(newQualifier, '.') + if index > 0 { + newQualifier = newQualifier[:index] + } + switch qualifier { + case "": + qualifier = newQualifier + case newQualifier: + // So far, so good... + default: + // Nope, different. + singleQualifier = false + } + } + + if singleQualifier { + return qualifier + } + + return strings.Join(names, "+") +} + +func nameForFunction(f any) string { + fn := runtime.FuncForPC(reflect.ValueOf(f).Pointer()) + if fn == nil { + return "" + } + return fn.Name() +} diff --git a/staging/src/k8s.io/client-go/tools/cache/event_handler_name_test.go b/staging/src/k8s.io/client-go/tools/cache/event_handler_name_test.go new file mode 100644 index 0000000000000..9cb1434f931a0 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/event_handler_name_test.go @@ -0,0 +1,93 @@ +/* +Copyright 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 cache + +import ( + "testing" +) + +type mockHandler struct{} + +func (m mockHandler) OnAdd(any, bool) {} +func (m mockHandler) OnUpdate(any, any) {} +func (m mockHandler) OnDelete(any) {} + +func TestNameForHandler(t *testing.T) { + emptyHandler := ResourceEventHandlerFuncs{} + + for name, tc := range map[string]struct { + handler ResourceEventHandler + wantName string + }{ + "mixture": { + handler: ResourceEventHandlerFuncs{ + UpdateFunc: emptyHandler.OnUpdate, + DeleteFunc: func(any) {}, + }, + wantName: "k8s.io/client-go/tools/cache.ResourceEventHandlerFuncs.OnUpdate-fm+k8s.io/client-go/tools/cache.TestNameForHandler.func1", // Testcase must come first to get func1. + }, + "add": { + handler: ResourceEventHandlerFuncs{AddFunc: func(any) {}}, + wantName: "k8s.io/client-go/tools/cache.TestNameForHandler", + }, + "update": { + handler: ResourceEventHandlerFuncs{UpdateFunc: func(any, any) {}}, + wantName: "k8s.io/client-go/tools/cache.TestNameForHandler", + }, + "delete": { + handler: ResourceEventHandlerFuncs{DeleteFunc: func(any) {}}, + wantName: "k8s.io/client-go/tools/cache.TestNameForHandler", + }, + "all": { + handler: ResourceEventHandlerFuncs{ + AddFunc: func(any) {}, + UpdateFunc: func(any, any) {}, + DeleteFunc: func(any) {}, + }, + wantName: "k8s.io/client-go/tools/cache.TestNameForHandler", + }, + "ptrToFuncs": { + handler: &ResourceEventHandlerFuncs{AddFunc: func(any) {}}, + wantName: "k8s.io/client-go/tools/cache.TestNameForHandler", + }, + "struct": { + handler: mockHandler{}, + wantName: "k8s.io/client-go/tools/cache.mockHandler", + }, + "ptrToStruct": { + handler: &mockHandler{}, + wantName: "k8s.io/client-go/tools/cache.mockHandler", + }, + "nil": { + handler: nil, + wantName: "", + }, + "stored-nil": { + // This is a bit odd, but one unit test actually registered + // such an event handler and it somehow worked. + handler: (*mockHandler)(nil), + wantName: "*cache.mockHandler", + }, + } { + t.Run(name, func(t *testing.T) { + gotName := nameForHandler(tc.handler) + if gotName != tc.wantName { + t.Errorf("Got name:\n %s\nWanted name:\n %s", gotName, tc.wantName) + } + }) + } +} diff --git a/staging/src/k8s.io/client-go/tools/cache/expiration_cache.go b/staging/src/k8s.io/client-go/tools/cache/expiration_cache.go index 813916ebf00de..19c2226b940fb 100644 --- a/staging/src/k8s.io/client-go/tools/cache/expiration_cache.go +++ b/staging/src/k8s.io/client-go/tools/cache/expiration_cache.go @@ -137,6 +137,16 @@ func (c *ExpirationCache) List() []interface{} { return list } +// LastStoreSyncResourceVersion returns the latest resource version that the cache has seen. +func (c *ExpirationCache) LastStoreSyncResourceVersion() string { + return c.cacheStorage.LastStoreSyncResourceVersion() +} + +// Bookmark observes a new resource version in the cache. +func (c *ExpirationCache) Bookmark(rv string) { + c.cacheStorage.Bookmark(rv) +} + // ListKeys returns a list of all keys in the expiration cache. func (c *ExpirationCache) ListKeys() []string { return c.cacheStorage.ListKeys() @@ -170,7 +180,7 @@ func (c *ExpirationCache) Delete(obj interface{}) error { } c.expirationLock.Lock() defer c.expirationLock.Unlock() - c.cacheStorage.Delete(key) + c.cacheStorage.DeleteWithObject(key, obj) return nil } diff --git a/staging/src/k8s.io/client-go/tools/cache/fifo.go b/staging/src/k8s.io/client-go/tools/cache/fifo.go index eb3ef589bd8f0..e31e2d270397a 100644 --- a/staging/src/k8s.io/client-go/tools/cache/fifo.go +++ b/staging/src/k8s.io/client-go/tools/cache/fifo.go @@ -47,7 +47,9 @@ type Queue interface { // may return an ErrRequeue{inner} and in this case Pop will (a) // return that (key, accumulator) association to the Queue as part // of the atomic processing and (b) return the inner error from - // Pop. + // Pop. It is expected that the caller of Pop will be a single + // threaded consumer since otherwise it is possible for multiple + // PopProcessFuncs to be running simultaneously. Pop(PopProcessFunc) (interface{}, error) // HasSynced returns true if the first batch of keys have all been @@ -56,6 +58,12 @@ type Queue interface { // Update, or Delete; otherwise the first batch is empty. HasSynced() bool + // HasSyncedChecker is done once the first batch of keys have all been + // popped. The first batch of keys are those of the first Replace + // operation if that happened before any Add, AddIfNotPresent, + // Update, or Delete; otherwise the first batch is empty. + HasSyncedChecker() DoneChecker + // Close the queue Close() } @@ -73,23 +81,14 @@ type QueueWithBatch interface { // PopBatch behaves similarly to Queue#Pop, but processes multiple keys // as a batch. The implementation determines the batching strategy, - // such as the number of keys to include per batch. - PopBatch(ProcessBatchFunc) error -} - -// Pop is helper function for popping from Queue. -// WARNING: Do NOT use this function in non-test code to avoid races -// unless you really really really really know what you are doing. -// -// NOTE: This function is deprecated and may be removed in the future without -// additional warning. -func Pop(queue Queue) interface{} { - var result interface{} - queue.Pop(func(obj interface{}, isInInitialList bool) error { - result = obj - return nil - }) - return result + // such as the number of keys to include per batch. The ProcessBatchFunc + // is called when a batch is ready to be processed. The PopProcessFunc + // is called when a singleton item is ready to be processed. The + // ProcessBatchFunc and PopProcessFunc must do the same processing to + // ensure consistent behavior. It is expected that the caller of PopBatch + // will be a single threaded consumer since otherwise it is possible for + // multiple ProcessBatchFuncs/PopProcessFuncs to be running simultaneously. + PopBatch(processBatch ProcessBatchFunc, processSingle PopProcessFunc) error } // FIFO is a Queue in which (a) each accumulator is simply the most @@ -117,6 +116,11 @@ type FIFO struct { items map[string]interface{} queue []string + // synced is initially an open channel. It gets closed (once!) by checkSynced + // as soon as the initial sync is considered complete. + synced chan struct{} + syncedClosed bool + // populated is true if the first batch of items inserted by Replace() has been populated // or Delete/Add/Update was called first. populated bool @@ -134,7 +138,8 @@ type FIFO struct { } var ( - _ = Queue(&FIFO{}) // FIFO is a Queue + _ = Queue(&FIFO{}) // FIFO is a Queue + _ = DoneChecker(&FIFO{}) // ... and implements DoneChecker. ) // Close the queue. @@ -153,8 +158,36 @@ func (f *FIFO) HasSynced() bool { return f.hasSynced_locked() } +// HasSyncedChecker is done if an Add/Update/Delete/AddIfNotPresent are called first, +// or the first batch of items inserted by Replace() has been popped. +func (f *FIFO) HasSyncedChecker() DoneChecker { + return f +} + +// Name implements [DoneChecker.Name] +func (f *FIFO) Name() string { + return "FIFO" // FIFO doesn't seem to be used outside of a few tests, so changing the NewFIFO API to pass in a name doesn't seem worth it. +} + +// Done implements [DoneChecker.Done] +func (f *FIFO) Done() <-chan struct{} { + return f.synced +} + +// hasSynced_locked returns the result of a prior checkSynced call. func (f *FIFO) hasSynced_locked() bool { - return f.populated && f.initialPopulationCount == 0 + return f.syncedClosed +} + +// checkSynced checks whether the initial sync is completed. +// It must be called whenever populated or initialPopulationCount change +// while the mutex is still locked. +func (f *FIFO) checkSynced() { + synced := f.populated && f.initialPopulationCount == 0 + if synced && !f.syncedClosed { + f.syncedClosed = true + close(f.synced) + } } // Add inserts an item, and puts it in the queue. The item is only enqueued @@ -167,6 +200,7 @@ func (f *FIFO) Add(obj interface{}) error { f.lock.Lock() defer f.lock.Unlock() f.populated = true + f.checkSynced() if _, exists := f.items[id]; !exists { f.queue = append(f.queue, id) } @@ -191,6 +225,7 @@ func (f *FIFO) Delete(obj interface{}) error { f.lock.Lock() defer f.lock.Unlock() f.populated = true + f.checkSynced() delete(f.items, id) return err } @@ -227,6 +262,8 @@ func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) { f.queue = f.queue[1:] if f.initialPopulationCount > 0 { f.initialPopulationCount-- + // Must be done *after* process has completed. + defer f.checkSynced() } item, ok := f.items[id] if !ok { @@ -259,6 +296,7 @@ func (f *FIFO) Replace(list []interface{}, resourceVersion string) error { if !f.populated { f.populated = true f.initialPopulationCount = len(items) + f.checkSynced() } f.items = items @@ -297,6 +335,7 @@ func (f *FIFO) Resync() error { // process. func NewFIFO(keyFunc KeyFunc) *FIFO { f := &FIFO{ + synced: make(chan struct{}), items: map[string]interface{}{}, queue: []string{}, keyFunc: keyFunc, diff --git a/staging/src/k8s.io/client-go/tools/cache/fifo_metrics.go b/staging/src/k8s.io/client-go/tools/cache/fifo_metrics.go new file mode 100644 index 0000000000000..7a67817240953 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/fifo_metrics.go @@ -0,0 +1,122 @@ +/* +Copyright 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 cache is a client-side caching mechanism. It is useful for +// reducing the number of server calls you'd otherwise need to make. +// Reflector watches a server and updates a Store. Two stores are provided; +// one that simply caches objects (for example, to allow a scheduler to +// list currently available nodes), and one that additionally acts as +// a FIFO queue (for example, to allow a scheduler to process incoming +// pods). +package cache + +import ( + "sync" +) + +var ( + globalInformerMetricsProvider InformerMetricsProvider = noopInformerMetricsProvider{} + setInformerMetricsProviderOnce sync.Once +) + +type noopInformerMetricsProvider struct{} + +// InformerMetricsProvider defines an interface for creating metrics that track informer operations. +type InformerMetricsProvider interface { + // NewQueuedItemMetric returns a gauge metric for tracking the total number of items + // currently queued and waiting to be processed. + // The returned metric should check id.Reserved() before updating to support + // dynamic informers that may shut down while the process is still running. + // + // For DeltaFIFO: Represents len(f.items) - the number of unique keys with pending deltas + // For RealFIFO: Represents len(f.items) - the total number of individual delta events queued + NewQueuedItemMetric(id InformerNameAndResource) GaugeMetric + + // NewProcessingLatencyMetric returns a histogram metric for tracking the time taken + // to process events (execute handlers) after they are popped from the queue. + // The latency is measured in seconds. + // The returned metric should check id.Reserved() before updating to support + // dynamic informers that may shut down while the process is still running. + NewProcessingLatencyMetric(id InformerNameAndResource) HistogramMetric + + // NewStoreResourceVersionMetric returns a gauge metric for tracking the resource version of the store. + // The returned metric should check id.Reserved() before updating to support + // dynamic informers that may shut down while the process is still running. + NewStoreResourceVersionMetric(id InformerNameAndResource) GaugeMetric +} + +// fifoMetrics holds all metrics for a FIFO. +type fifoMetrics struct { + numberOfQueuedItem GaugeMetric + processingLatency HistogramMetric +} + +// storeMetrics holds all metrics for a store. +type storeMetrics struct { + storeResourceVersion GaugeMetric +} + +// SetInformerMetricsProvider sets the metrics provider for all subsequently created +// FIFOs. Only the first call has an effect. +func SetInformerMetricsProvider(metricsProvider InformerMetricsProvider) { + setInformerMetricsProviderOnce.Do(func() { + globalInformerMetricsProvider = metricsProvider + }) +} + +func newFIFOMetrics(id InformerNameAndResource, metricsProvider InformerMetricsProvider) *fifoMetrics { + if metricsProvider == nil { + metricsProvider = globalInformerMetricsProvider + } + metrics := &fifoMetrics{ + numberOfQueuedItem: noopMetric{}, + processingLatency: noopMetric{}, + } + + if id.Reserved() { + metrics.numberOfQueuedItem = metricsProvider.NewQueuedItemMetric(id) + metrics.processingLatency = metricsProvider.NewProcessingLatencyMetric(id) + } + + return metrics +} + +func newStoreMetrics(id InformerNameAndResource, metricsProvider InformerMetricsProvider) *storeMetrics { + if metricsProvider == nil { + metricsProvider = globalInformerMetricsProvider + } + metrics := &storeMetrics{ + storeResourceVersion: noopMetric{}, + } + + if id.Reserved() { + metrics.storeResourceVersion = metricsProvider.NewStoreResourceVersionMetric(id) + } + + return metrics +} + +func (noopInformerMetricsProvider) NewQueuedItemMetric(InformerNameAndResource) GaugeMetric { + return noopMetric{} +} + +func (noopInformerMetricsProvider) NewProcessingLatencyMetric(InformerNameAndResource) HistogramMetric { + return noopMetric{} +} + +func (noopInformerMetricsProvider) NewStoreResourceVersionMetric(InformerNameAndResource) GaugeMetric { + return noopMetric{} +} diff --git a/staging/src/k8s.io/client-go/tools/cache/fifo_test.go b/staging/src/k8s.io/client-go/tools/cache/fifo_test.go index 39c27c6d9c549..6803566ec91b8 100644 --- a/staging/src/k8s.io/client-go/tools/cache/fifo_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/fifo_test.go @@ -35,6 +35,15 @@ func (f *FIFO) List() []interface{} { return list } +// LastStoreSyncResourceVersion is unimplemented for FIFO, only used in unit testing. +func (f *FIFO) LastStoreSyncResourceVersion() string { + return "" +} + +// Bookmark is unimplemented for FIFO, only used in unit testing. +func (f *FIFO) Bookmark(rv string) { +} + // ListKeys returns a list of all the keys of the objects currently // in the FIFO. // This function was moved here because it is not consistent with normal list semantics, but is used in unit testing. @@ -67,6 +76,21 @@ func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) { return item, exists, nil } +// Pop is helper function for popping from Queue. +// WARNING: Do NOT use this function in non-test code to avoid races +// unless you really really really really know what you are doing. +// +// NOTE: This function is deprecated and may be removed in the future without +// additional warning. +func Pop(queue Queue) interface{} { + var result interface{} + queue.Pop(func(obj interface{}, isInInitialList bool) error { + result = obj + return nil + }) + return result +} + func testFifoObjectKeyFunc(obj interface{}) (string, error) { return obj.(testFifoObject).name, nil } diff --git a/staging/src/k8s.io/client-go/tools/cache/identity.go b/staging/src/k8s.io/client-go/tools/cache/identity.go new file mode 100644 index 0000000000000..eed278dc06335 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/identity.go @@ -0,0 +1,217 @@ +/* +Copyright 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 cache is a client-side caching mechanism. It is useful for +// reducing the number of server calls you'd otherwise need to make. +// Reflector watches a server and updates a Store. Two stores are provided; +// one that simply caches objects (for example, to allow a scheduler to +// list currently available nodes), and one that additionally acts as +// a FIFO queue (for example, to allow a scheduler to process incoming +// pods). +package cache + +import ( + "fmt" + "sync" + "sync/atomic" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" +) + +// informerNameRegistry tracks all registered InformerName instances to detect collisions. +// Names must be globally unique across a process. +var informerNameRegistry = struct { + sync.Mutex + names map[string]*InformerName +}{ + names: make(map[string]*InformerName), +} + +// InformerName represents a named informer identity used for metrics. +// It is created once at startup (e.g., in cmd/kube-controller-manager) and passed to +// the SharedInformerFactory. The name must be globally unique within a process. +// +// InformerName tracks which GVRs have been registered under +// this name. When an informer requests a name+GVR combination, the first one wins +// and gets metrics enabled. Subsequent requests for the same GVR silently get +// metrics disabled. +type InformerName struct { + name string + // lock protects gvrs map modifications + lock sync.Mutex + // reserved is flipped to false when Release() is called + reserved *atomic.Bool + // gvrs maps each registered GVR to its atomic bool for lock-free Reserved() checks + gvrs map[schema.GroupVersionResource]*atomic.Bool +} + +// NewInformerName creates a new InformerName with the given name. +// The name must be globally unique within the process. If a name collision +// is detected, an error is returned. +// +// The caller should call Release() when the informer name is no longer needed +// (eg. at shutdown) to allow the name to be reused. +func NewInformerName(name string) (*InformerName, error) { + if name == "" { + return nil, fmt.Errorf("informer name cannot be empty") + } + + informerNameRegistry.Lock() + defer informerNameRegistry.Unlock() + + if existing, ok := informerNameRegistry.names[name]; ok { + // Check if the existing one is still reserved + if existing.reserved.Load() { + return nil, fmt.Errorf("informer name %q is already registered", name) + } + // Previous one was released, we can reuse the name + delete(informerNameRegistry.names, name) + } + + reserved := &atomic.Bool{} + reserved.Store(true) + + n := &InformerName{ + name: name, + reserved: reserved, + gvrs: make(map[schema.GroupVersionResource]*atomic.Bool), + } + + informerNameRegistry.names[name] = n + return n, nil +} + +// WithResource registers a GVR under this InformerName and returns an +// InformerNameAndResource that can be passed to FIFO/SharedIndexInformer. +// +// If this is the first time this GVR is registered under this name, the +// returned InformerNameAndResource will have Reserved() return true. +// If the GVR was already registered, the returned InformerNameAndResource +// will have Reserved() return false to prevent duplicate metrics. +func (n *InformerName) WithResource(gvr schema.GroupVersionResource) InformerNameAndResource { + if n == nil { + return InformerNameAndResource{gvr: gvr} + } + + n.lock.Lock() + defer n.lock.Unlock() + + retval := InformerNameAndResource{name: n.name, gvr: gvr, reserved: &atomic.Bool{}} + if n.reserved.Load() { + if _, gvrExists := n.gvrs[gvr]; !gvrExists { + retval.reserved.Store(true) + n.gvrs[gvr] = retval.reserved + } else { + // WithResource is called by generated informer code and probably + // not worth converting to contextual logging, which would require + // changing all those generated APIs. + klog.TODO().Error(nil, "Duplicate informer registration - metrics will not be published", "informerName", n.name, "group", gvr.Group, "version", gvr.Version, "resource", gvr.Resource) + } + } + return retval +} + +// Release marks this InformerName as no longer in use. +// All InformerNameAndResource instances created from this InformerName +// will have their Reserved() return false after this call. +// The name can be reused by a subsequent NewInformerName call. +func (n *InformerName) Release() { + if n == nil { + return + } + + n.lock.Lock() + defer n.lock.Unlock() + + // Flip all GVR-specific flags so that any InformerNameAndResource + // instances that were returned from WithResource() will have + // Reserved() return false. These instances hold pointers to the + // same atomic bools, so we must flip them before clearing the map. + for _, reserved := range n.gvrs { + reserved.Store(false) + } + + // Clear the map + n.gvrs = make(map[schema.GroupVersionResource]*atomic.Bool) + + // Flip the main reserved flag + n.reserved.Store(false) + + // Remove from global registry + informerNameRegistry.Lock() + defer informerNameRegistry.Unlock() + delete(informerNameRegistry.names, n.name) +} + +// Name returns the name of this InformerName. +func (n *InformerName) Name() string { + if n == nil { + return "" + } + return n.name +} + +// InformerNameAndResource represents a specific informer identity with both +// a name and a GVR. This is passed to FIFO and SharedIndexInformer for metrics. +// +// The Reserved() method provides a lock-free check to determine +// if metrics should be published. This is called on every queue operation +// so it must be fast. +type InformerNameAndResource struct { + name string + gvr schema.GroupVersionResource + reserved *atomic.Bool +} + +// Reserved returns true if this informer identity is reserved for metrics. +// This is a lock-free atomic load, safe and fast for hot-path usage. +// +// Returns false if: +// - The InformerNameAndResource is zero-valued (no name was configured) +// - The parent InformerName was released +// - This was a duplicate GVR registration +func (n InformerNameAndResource) Reserved() bool { + if n.reserved == nil { + return false + } + return n.reserved.Load() +} + +// Name returns the informer name. +func (n InformerNameAndResource) Name() string { + return n.name +} + +// GroupVersionResource returns the GVR for this informer identity. +func (n InformerNameAndResource) GroupVersionResource() schema.GroupVersionResource { + return n.gvr +} + +// ResetInformerNamesForTesting clears the informer name registry. +// This is exported for testing purposes only. +func ResetInformerNamesForTesting() { + informerNameRegistry.Lock() + names := make([]*InformerName, 0, len(informerNameRegistry.names)) + for _, name := range informerNameRegistry.names { + names = append(names, name) + } + informerNameRegistry.Unlock() + + for _, name := range names { + name.Release() + } +} diff --git a/staging/src/k8s.io/client-go/tools/cache/identity_test.go b/staging/src/k8s.io/client-go/tools/cache/identity_test.go new file mode 100644 index 0000000000000..e4c59eec85a96 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/identity_test.go @@ -0,0 +1,188 @@ +/* +Copyright 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 cache is a client-side caching mechanism. It is useful for +// reducing the number of server calls you'd otherwise need to make. +// Reflector watches a server and updates a Store. Two stores are provided; +// one that simply caches objects (for example, to allow a scheduler to +// list currently available nodes), and one that additionally acts as +// a FIFO queue (for example, to allow a scheduler to process incoming +// pods). +package cache + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + podsGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} + configMapsGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} +) + +func TestNewInformerName(t *testing.T) { + tests := []struct { + name string + setup func() + inName string + wantErr bool + }{ + { + name: "first name is unique", + setup: func() {}, + inName: "my-informer", + wantErr: false, + }, + { + name: "duplicate name returns error", + setup: func() { + _, _ = NewInformerName("my-informer") + }, + inName: "my-informer", + wantErr: true, + }, + { + name: "empty name returns error", + setup: func() {}, + inName: "", + wantErr: true, + }, + { + name: "different name is unique", + setup: func() { + _, _ = NewInformerName("informer-1") + }, + inName: "informer-2", + wantErr: false, + }, + { + name: "released name can be reused", + setup: func() { + n, _ := NewInformerName("my-informer") + n.Release() + }, + inName: "my-informer", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ResetInformerNamesForTesting() + tt.setup() + + _, err := NewInformerName(tt.inName) + if (err != nil) != tt.wantErr { + t.Errorf("NewInformerName() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestWithResource(t *testing.T) { + tests := []struct { + name string + setup func() *InformerName + gvr schema.GroupVersionResource + wantReserved bool + }{ + { + name: "first GVR is reserved", + setup: func() *InformerName { + n, _ := NewInformerName("my-informer") + return n + }, + gvr: podsGVR, + wantReserved: true, + }, + { + name: "same GVR second time is not reserved", + setup: func() *InformerName { + n, _ := NewInformerName("my-informer") + _ = n.WithResource(podsGVR) + return n + }, + gvr: podsGVR, + wantReserved: false, + }, + { + name: "different GVR is reserved", + setup: func() *InformerName { + n, _ := NewInformerName("my-informer") + _ = n.WithResource(podsGVR) + return n + }, + gvr: configMapsGVR, + wantReserved: true, + }, + { + name: "nil InformerName returns not reserved", + setup: func() *InformerName { + return nil + }, + gvr: podsGVR, + wantReserved: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ResetInformerNamesForTesting() + n := tt.setup() + + id := n.WithResource(tt.gvr) + if got := id.Reserved(); got != tt.wantReserved { + t.Errorf("Reserved() = %v, want %v", got, tt.wantReserved) + } + }) + } +} + +func TestRelease(t *testing.T) { + ResetInformerNamesForTesting() + + n, err := NewInformerName("my-informer") + if err != nil { + t.Fatalf("NewInformerName() error = %v", err) + } + + // Get a reserved identifier + id := n.WithResource(podsGVR) + if !id.Reserved() { + t.Error("Expected Reserved() = true before Release()") + } + + // Release the name + n.Release() + + // The identifier should no longer be reserved + if id.Reserved() { + t.Error("Expected Reserved() = false after Release()") + } + + // Should be able to reuse the name + n2, err := NewInformerName("my-informer") + if err != nil { + t.Errorf("NewInformerName() after Release() error = %v", err) + } + + // New identifier from new name should be reserved + id2 := n2.WithResource(podsGVR) + if !id2.Reserved() { + t.Error("Expected Reserved() = true for new InformerName after Release()") + } +} diff --git a/staging/src/k8s.io/client-go/tools/cache/processor_listener_test.go b/staging/src/k8s.io/client-go/tools/cache/processor_listener_test.go index 517258a160d3f..cd8c5c739bf59 100644 --- a/staging/src/k8s.io/client-go/tools/cache/processor_listener_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/processor_listener_test.go @@ -17,6 +17,7 @@ limitations under the License. package cache import ( + "context" "sync" "testing" "time" @@ -29,6 +30,25 @@ const ( concurrencyLevel = 5 ) +type mockSynced struct { + context.Context + cancel func() +} + +func newMockSynced(tb testing.TB, synced bool) *mockSynced { + m := &mockSynced{} + m.Context, m.cancel = context.WithCancel(context.Background()) + if synced { + m.cancel() + } + tb.Cleanup(m.cancel) + return m +} + +func (m *mockSynced) Name() string { + return "mock" +} + func BenchmarkListener(b *testing.B) { var notification addNotification @@ -40,7 +60,7 @@ func BenchmarkListener(b *testing.B) { AddFunc: func(obj interface{}) { swg.Done() }, - }, 0, 0, time.Now(), 1024*1024, func() bool { return true }) + }, 0, 0, time.Now(), 1024*1024, newMockSynced(b, true)) var wg wait.Group defer wg.Wait() // Wait for .run and .pop to stop defer close(pl.addCh) // Tell .run and .pop to stop diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector.go b/staging/src/k8s.io/client-go/tools/cache/reflector.go index af2c7a22d074f..a0e3a701cda90 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "io" + "math" "math/rand" "reflect" "strings" @@ -50,9 +51,21 @@ import ( const defaultExpectedTypeName = "" -// We try to spread the load on apiserver by setting timeouts for -// watch requests - it is random in [minWatchTimeout, 2*minWatchTimeout]. -var defaultMinWatchTimeout = 5 * time.Minute +var ( + // We try to spread the load on apiserver by setting timeouts for + // watch requests - it is random in [minWatchTimeout, 2*minWatchTimeout]. + defaultMinWatchTimeout = 5 * time.Minute + defaultMaxWatchTimeout = 2 * defaultMinWatchTimeout + // We used to make the call every 1sec (1 QPS), the goal here is to achieve ~98% traffic reduction when + // API server is not healthy. With these parameters, backoff will stop at [30,60) sec interval which is + // 0.22 QPS. + defaultBackoffInit = 800 * time.Millisecond + defaultBackoffMax = 30 * time.Second + // If we don't backoff for 2min, assume API server is healthy and we reset the backoff. + defaultBackoffReset = 2 * time.Minute + defaultBackoffFactor = 2.0 + defaultBackoffJitter = 1.0 +) // ReflectorStore is the subset of cache.Store that the reflector uses type ReflectorStore interface { @@ -76,6 +89,12 @@ type ReflectorStore interface { Resync() error } +// ReflectorBookmarkStore is an optional interface that allows a store +// to be informed of bookmark events received by the reflector. +type ReflectorBookmarkStore interface { + Bookmark(resourceVersion string) error +} + // TransformingStore is an optional interface that can be implemented by the provided store. // If implemented on the provided store reflector will use the same transformer in its internal stores. type TransformingStore interface { @@ -85,6 +104,7 @@ type TransformingStore interface { // Reflector watches a specified resource and causes all changes to be reflected in the given store. type Reflector struct { + logger klog.Logger // name identifies this reflector. By default, it will be a file:line if possible. name string // The name of the type we expect to place in the store. The name @@ -103,11 +123,14 @@ type Reflector struct { store ReflectorStore // listerWatcher is used to perform lists and watches. listerWatcher ListerWatcherWithContext - // backoff manages backoff of ListWatch - backoffManager wait.BackoffManager - resyncPeriod time.Duration + // delay returns the next backoff interval for retries. + resyncPeriod time.Duration + delayHandler wait.DelayFunc // minWatchTimeout defines the minimum timeout for watch requests. minWatchTimeout time.Duration + // maxWatchTimeout defines the maximum timeout for watch requests. + // Actual timeout is random in [minWatchTimeout, maxWatchTimeout]. + maxWatchTimeout time.Duration // clock allows tests to manipulate time clock clock.Clock // paginatedResult defines whether pagination should be forced for list calls. @@ -227,6 +250,10 @@ func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, // ReflectorOptions configures a Reflector. type ReflectorOptions struct { + // Logger, if not nil, is used instead of klog.Background() for logging. + // The name of the reflector gets added automatically. + Logger *klog.Logger + // Name is the Reflector's name. If unset/unspecified, the name defaults to the closest source_file.go:line // in the call stack that is outside this package. Name string @@ -248,6 +275,12 @@ type ReflectorOptions struct { // Clock allows tests to control time. If unset defaults to clock.RealClock{} Clock clock.Clock + + // Backoff is an optional custom backoff configuration. + // If set, it will be used instead of the default exponential backoff. + // DelayWithReset(clock, resetDuration) will be called on it to create the delay function. + // TODO(#136943): Expose this configuration through SharedInformerFactory. + Backoff *wait.Backoff } // NewReflectorWithOptions creates a new Reflector object which will keep the @@ -265,21 +298,42 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store R if reflectorClock == nil { reflectorClock = clock.RealClock{} } + minWatchTimeout := defaultMinWatchTimeout + maxWatchTimeout := defaultMaxWatchTimeout if options.MinWatchTimeout > defaultMinWatchTimeout { minWatchTimeout = options.MinWatchTimeout + maxWatchTimeout = 2 * minWatchTimeout + } + if maxWatchTimeout < minWatchTimeout { + klog.TODO().V(3).Info( + "maxWatchTimeout was less than minWatchTimeout, overriding to minWatchTimeout. Watch timeout randomization is disabled.", + "minWatchTimeout", minWatchTimeout, + "maxWatchTimeout", maxWatchTimeout, + ) + maxWatchTimeout = minWatchTimeout } + + backoff := options.Backoff + if backoff == nil { + backoff = &wait.Backoff{ + Duration: defaultBackoffInit, + Cap: defaultBackoffMax, + Steps: int(math.Ceil(float64(defaultBackoffMax) / float64(defaultBackoffInit))), + Factor: defaultBackoffFactor, + Jitter: defaultBackoffJitter, + } + } + r := &Reflector{ - name: options.Name, - resyncPeriod: options.ResyncPeriod, - minWatchTimeout: minWatchTimeout, - typeDescription: options.TypeDescription, - listerWatcher: ToListerWatcherWithContext(lw), - store: store, - // We used to make the call every 1sec (1 QPS), the goal here is to achieve ~98% traffic reduction when - // API server is not healthy. With these parameters, backoff will stop at [30,60) sec interval which is - // 0.22 QPS. If we don't backoff for 2min, assume API server is healthy and we reset the backoff. - backoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, reflectorClock), + name: options.Name, + resyncPeriod: options.ResyncPeriod, + minWatchTimeout: minWatchTimeout, + maxWatchTimeout: maxWatchTimeout, + typeDescription: options.TypeDescription, + listerWatcher: ToListerWatcherWithContext(lw), + store: store, + delayHandler: backoff.DelayWithReset(reflectorClock, defaultBackoffReset), clock: reflectorClock, watchErrorHandler: WatchErrorHandlerWithContext(DefaultWatchErrorHandler), expectedType: reflect.TypeOf(expectedType), @@ -289,6 +343,13 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store R r.name = naming.GetNameFromCallsite(internalPackages...) } + logger := klog.Background() + if options.Logger != nil { + logger = *options.Logger + } + logger = klog.LoggerWithName(logger, r.name) + r.logger = logger + if r.typeDescription == "" { r.typeDescription = getTypeDescriptionFromObject(expectedType) } @@ -299,11 +360,8 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store R r.useWatchList = clientfeatures.FeatureGates().Enabled(clientfeatures.WatchListClient) if r.useWatchList && watchlist.DoesClientNotSupportWatchListSemantics(lw) { - // Using klog.TODO() here because switching to a caller-provided contextual logger - // would require an API change and updating all existing call sites. - klog.TODO().V(2).Info( - "The provided ListWatcher doesn't support WatchList semantics. The feature will be disabled. If you are using a custom client, check the documentation of watchlist.DoesClientNotSupportWatchListSemantics() method", - "listWatcherType", fmt.Sprintf("%T", lw), + r.logger.V(2).Info( + "The client used to build this informer/reflector doesn't support WatchList semantics. The feature will be disabled. This is expected in unit tests but not in production. For details, see the documentation of watchlist.DoesClientNotSupportWatchListSemantics().", "feature", clientfeatures.WatchListClient, ) r.useWatchList = false @@ -365,11 +423,14 @@ func (r *Reflector) Run(stopCh <-chan struct{}) { func (r *Reflector) RunWithContext(ctx context.Context) { logger := klog.FromContext(ctx) logger.V(3).Info("Starting reflector", "type", r.typeDescription, "resyncPeriod", r.resyncPeriod, "reflector", r.name) - wait.BackoffUntil(func() { + // Until runs the loop immediately (immediate=true) and resets the backoff timer after each + // successful iteration (sliding=true). See backoff constants at top of file for generalized QPS targets (~0.22 QPS). + _ = r.delayHandler.Until(ctx, true, true, func(ctx context.Context) (bool, error) { if err := r.ListAndWatchWithContext(ctx); err != nil { r.watchErrorHandler(ctx, r, err) } - }, r.backoffManager, true, ctx.Done()) + return false, nil + }) logger.V(3).Info("Stopping reflector", "type", r.typeDescription, "resyncPeriod", r.resyncPeriod, "reflector", r.name) } @@ -521,8 +582,10 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha // start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sent start := r.clock.Now() + // if w is already initialized, it must be past any synthetic non-rv-ordered added events + propagateRVFromStart := true if w == nil { - timeoutSeconds := int64(r.minWatchTimeout.Seconds() * (rand.Float64() + 1.0)) + timeoutSeconds := int64(r.minWatchTimeout.Seconds() + rand.Float64()*(r.maxWatchTimeout.Seconds()-r.minWatchTimeout.Seconds())) options := metav1.ListOptions{ ResourceVersion: r.LastSyncResourceVersion(), // We want to avoid situations of hanging watchers. Stop any watchers that do not @@ -533,6 +596,11 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha // watch bookmarks, it will ignore this field). AllowWatchBookmarks: true, } + if options.ResourceVersion == "" || options.ResourceVersion == "0" { + // if we're starting the watch at a resource version that will get synthetic ADDED events in non-rv order, + // wait until we're through that set of events before propagating the RV + propagateRVFromStart = false + } w, err = r.listerWatcher.WatchWithContext(ctx, options) if err != nil { @@ -541,7 +609,7 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha select { case <-stopCh: return nil - case <-r.backoffManager.Backoff().C(): + case <-r.clock.After(r.delayHandler()): continue } } @@ -549,7 +617,25 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha } } - err = handleWatch(ctx, start, w, r.store, r.expectedType, r.expectedGVK, r.name, r.typeDescription, r.setLastSyncResourceVersion, + err = handleWatch(ctx, start, w, r.store, r.expectedType, r.expectedGVK, r.name, r.typeDescription, + func(rv string, eventReceivedBesidesAdded bool) { + // We update the resource version in the store only if we have received at least one event that is + // not an added event, or if the resource version has been set previously. This is because we can + // encounter 2 scenarios: + // 1. The watch is started from a resource version specified by the LastSyncResourceVersion field. + // In this case, we can update the resource version in the store without worrying about it being + // out of order since we will not receive any synthetic added events for resources that may be + // out of order. + // 2. The watch is started when the LastSyncResourceVersion field is empty. In this case, we may not + // update the LastSyncResourceVersion until we receive at least one event that is not an added + // event, since that is the only way to ensure that the watch has exited the initial list phase. + if propagateRVFromStart || eventReceivedBesidesAdded { + r.setLastSyncResourceVersion(rv) + if rvu, ok := r.store.(ResourceVersionUpdater); ok { + rvu.UpdateResourceVersion(rv) + } + } + }, r.clock, resyncerrc) // handleWatch always stops the watcher. So we don't need to here. // Just set it to nil to trigger a retry on the next loop. @@ -568,7 +654,7 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha select { case <-stopCh: return nil - case <-r.backoffManager.Backoff().C(): + case <-r.clock.After(r.delayHandler()): continue } case apierrors.IsInternalError(err) && retry.ShouldRetry(): @@ -641,6 +727,11 @@ func (r *Reflector) list(ctx context.Context) error { // the reflector makes forward progress. list, paginatedResult, err = pager.ListWithAlloc(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}) } + if err == nil { + if unsupportedList, unsupportedListGVK := isUnsupportedTableListObject(list); unsupportedList { + err = fmt.Errorf("unsupported list gvk: %v, type: %v", unsupportedListGVK, r.typeDescription) + } + } close(listCh) }() select { @@ -650,6 +741,7 @@ func (r *Reflector) list(ctx context.Context) error { panic(r) case <-listCh: } + initTrace.Step("Objects listed", trace.Field{Key: "error", Value: err}) if err != nil { return fmt.Errorf("failed to list %v: %w", r.typeDescription, err) @@ -722,7 +814,7 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) { isErrorRetriableWithSideEffectsFn := func(err error) bool { if canRetry := isWatchErrorRetriable(err); canRetry { logger.V(2).Info("watch-list failed - backing off", "reflector", r.name, "type", r.typeDescription, "err", err) - <-r.backoffManager.Backoff().C() + <-r.clock.After(r.delayHandler()) return true } if isExpiredError(err) || isTooLargeResourceVersionError(err) { @@ -758,7 +850,7 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) { // TODO(#115478): large "list", slow clients, slow network, p&f // might slow down streaming and eventually fail. // maybe in such a case we should retry with an increased timeout? - timeoutSeconds := int64(r.minWatchTimeout.Seconds() * (rand.Float64() + 1.0)) + timeoutSeconds := int64(r.minWatchTimeout.Seconds() + rand.Float64()*(r.maxWatchTimeout.Seconds()-r.minWatchTimeout.Seconds())) options := metav1.ListOptions{ ResourceVersion: lastKnownRV, AllowWatchBookmarks: true, @@ -776,7 +868,11 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) { return nil, err } watchListBookmarkReceived, err := handleListWatch(ctx, start, w, temporaryStore, r.expectedType, r.expectedGVK, r.name, r.typeDescription, - func(rv string) { resourceVersion = rv }, + func(rv string, eventReceivedBesidesAdded bool) { + if eventReceivedBesidesAdded { + resourceVersion = rv + } + }, r.clock, make(chan error)) if err != nil { w.Stop() // stop and retry with clean state @@ -833,7 +929,7 @@ func handleListWatch( expectedGVK *schema.GroupVersionKind, name string, expectedTypeName string, - setLastSyncResourceVersion func(string), + setLastSyncResourceVersion func(string, bool), clock clock.Clock, errCh chan error, ) (bool, error) { @@ -854,7 +950,7 @@ func handleWatch( expectedGVK *schema.GroupVersionKind, name string, expectedTypeName string, - setLastSyncResourceVersion func(string), + setLastSyncResourceVersion func(string, bool), clock clock.Clock, errCh chan error, ) error { @@ -882,12 +978,13 @@ func handleAnyWatch( expectedGVK *schema.GroupVersionKind, name string, expectedTypeName string, - setLastSyncResourceVersion func(string), + setLastSyncResourceVersion func(string, bool), exitOnWatchListBookmarkReceived bool, clock clock.Clock, errCh chan error, ) (bool, error) { watchListBookmarkReceived := false + eventReceivedBesidesAdded := false eventCount := 0 logger := klog.FromContext(ctx) initialEventsEndBookmarkWarningTicker := newInitialEventsEndBookmarkTicker(logger, name, clock, start, exitOnWatchListBookmarkReceived) @@ -925,14 +1022,11 @@ loop: continue } } - // For now, let’s block unsupported Table - // resources for watchlist only + // we don't support receiving resources in Table format // see #132926 for more info - if exitOnWatchListBookmarkReceived { - if unsupportedGVK := isUnsupportedTableObject(event.Object); unsupportedGVK { - utilruntime.HandleErrorWithContext(ctx, nil, "Unsupported watch event object gvk", "reflector", name, "actualGVK", event.Object.GetObjectKind().GroupVersionKind()) - continue - } + if unsupportedGVK := isUnsupportedTableObject(event.Object); unsupportedGVK { + utilruntime.HandleErrorWithContext(ctx, nil, "Unsupported watch event object gvk", "reflector", name, "actualGVK", event.Object.GetObjectKind().GroupVersionKind()) + continue } meta, err := meta.Accessor(event.Object) if err != nil { @@ -947,6 +1041,7 @@ loop: utilruntime.HandleErrorWithContext(ctx, err, "Unable to add watch event object to store", "reflector", name, "object", event.Object) } case watch.Modified: + eventReceivedBesidesAdded = true err := store.Update(event.Object) if err != nil { utilruntime.HandleErrorWithContext(ctx, err, "Unable to update watch event object to store", "reflector", name, "object", event.Object) @@ -955,22 +1050,29 @@ loop: // TODO: Will any consumers need access to the "last known // state", which is passed in event.Object? If so, may need // to change this. + eventReceivedBesidesAdded = true err := store.Delete(event.Object) if err != nil { utilruntime.HandleErrorWithContext(ctx, err, "Unable to delete watch event object from store", "reflector", name, "object", event.Object) } case watch.Bookmark: // A `Bookmark` means watch has synced here, just update the resourceVersion + eventReceivedBesidesAdded = true if meta.GetAnnotations()[metav1.InitialEventsAnnotationKey] == "true" { watchListBookmarkReceived = true } + // Propagate the resource version from the bookmark event to stores which indicate they want it + if bookmarkStore, ok := store.(ReflectorBookmarkStore); ok { + err := bookmarkStore.Bookmark(resourceVersion) + if err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "Unable to send bookmark event to store", "reflector", name, "object", event.Object) + } + } default: utilruntime.HandleErrorWithContext(ctx, err, "Unknown watch event", "reflector", name, "event", event) } - setLastSyncResourceVersion(resourceVersion) - if rvu, ok := store.(ResourceVersionUpdater); ok { - rvu.UpdateResourceVersion(resourceVersion) - } + // when eventReceivedBesidesAdded is true, that indicates we are definitely past any initial synthetic Added events + setLastSyncResourceVersion(resourceVersion, eventReceivedBesidesAdded) eventCount++ if exitOnWatchListBookmarkReceived && watchListBookmarkReceived { stopWatcher = false @@ -1220,3 +1322,12 @@ func isUnsupportedTableObject(rawObject runtime.Object) bool { return unsupportedTableGVK[rawObject.GetObjectKind().GroupVersionKind()] } + +func isUnsupportedTableListObject(rawObject runtime.Object) (bool, schema.GroupVersionKind) { + unstructuredObj, ok := rawObject.(*unstructured.UnstructuredList) + if !ok { + return false, schema.GroupVersionKind{} + } + + return unsupportedTableGVK[unstructuredObj.GetObjectKind().GroupVersionKind()], unstructuredObj.GetObjectKind().GroupVersionKind() +} diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector_data_consistency_detector_test.go b/staging/src/k8s.io/client-go/tools/cache/reflector_data_consistency_detector_test.go index 1d18d0be180d0..fc9c1529e19fd 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector_data_consistency_detector_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector_data_consistency_detector_test.go @@ -68,12 +68,12 @@ func TestReflectorDataConsistencyDetector(t *testing.T) { } func runTestReflectorDataConsistencyDetector(t *testing.T, transformer TransformFunc) { - _, ctx := ktesting.NewTestContext(t) + logger, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() store := NewStore(MetaNamespaceKeyFunc) - fifo := newQueueFIFO(store, transformer) + _, fifo := newQueueFIFO(logger, nil, store, transformer, InformerNameAndResource{}, nil) lw := &ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector_metrics.go b/staging/src/k8s.io/client-go/tools/cache/reflector_metrics.go index 5c00115f59b58..65e8a38afbe53 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector_metrics.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector_metrics.go @@ -40,6 +40,12 @@ type SummaryMetric interface { Observe(float64) } +// HistogramMetric captures individual observations into configurable buckets. +// It also provides a sum and count of observations. +type HistogramMetric interface { + Observe(float64) +} + type noopMetric struct{} func (noopMetric) Inc() {} diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector_test.go b/staging/src/k8s.io/client-go/tools/cache/reflector_test.go index 5554b16f2a624..337c608fbc875 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector_test.go @@ -235,7 +235,7 @@ func TestReflectorHandleWatchStoppedBefore(t *testing.T) { return resultCh }, } - err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc) + err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, func(rv string, _ bool) { g.setLastSyncResourceVersion(rv) }, g.clock, nevererrc) require.Equal(t, err, errorStopRequested) // Ensure handleWatch calls ResultChan and Stop assert.Equal(t, []string{"ResultChan", "Stop"}, calls) @@ -268,7 +268,7 @@ func TestReflectorHandleWatchStoppedAfter(t *testing.T) { return resultCh }, } - err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc) + err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, func(rv string, _ bool) { g.setLastSyncResourceVersion(rv) }, g.clock, nevererrc) require.Equal(t, err, errorStopRequested) // Ensure handleWatch calls ResultChan and Stop assert.Equal(t, []string{"ResultChan", "Stop"}, calls) @@ -294,7 +294,7 @@ func TestReflectorHandleWatchResultChanClosedBefore(t *testing.T) { } // Simulate the result channel being closed by the producer before handleWatch is called. close(resultCh) - err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc) + err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, func(rv string, _ bool) { g.setLastSyncResourceVersion(rv) }, g.clock, nevererrc) require.Equal(t, &VeryShortWatchError{Name: g.name}, err) // Ensure handleWatch calls ResultChan and Stop assert.Equal(t, []string{"ResultChan", "Stop"}, calls) @@ -325,7 +325,7 @@ func TestReflectorHandleWatchResultChanClosedAfter(t *testing.T) { return resultCh }, } - err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc) + err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, func(rv string, _ bool) { g.setLastSyncResourceVersion(rv) }, g.clock, nevererrc) require.Equal(t, &VeryShortWatchError{Name: g.name}, err) // Ensure handleWatch calls ResultChan and Stop assert.Equal(t, []string{"ResultChan", "Stop"}, calls) @@ -338,7 +338,7 @@ func TestReflectorWatchHandler(t *testing.T) { // watching after all the events have been consumed. _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancelCause(ctx) - setLastSyncResourceVersion := func(rv string) { + setLastSyncResourceVersion := func(rv string, _ bool) { g.setLastSyncResourceVersion(rv) if rv == "32" { cancel(errors.New("LastSyncResourceVersion is 32")) @@ -398,7 +398,7 @@ func TestReflectorStopWatch(t *testing.T) { _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancelCause(ctx) cancel(errors.New("don't run")) - err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc) + err := handleWatch(ctx, time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, func(rv string, _ bool) { g.setLastSyncResourceVersion(rv) }, g.clock, nevererrc) require.Equal(t, err, errorStopRequested) } @@ -745,7 +745,14 @@ func TestReflectorListAndWatchInitConnBackoff(t *testing.T) { ctx, cancel := context.WithCancelCause(ctx) connFails := test.numConnFails fakeClock := testingclock.NewFakeClock(time.Unix(0, 0)) - bm := wait.NewExponentialBackoffManager(time.Millisecond, maxBackoff, 100*time.Millisecond, 2.0, 1.0, fakeClock) + backoff := wait.Backoff{ + Duration: time.Millisecond, + Cap: maxBackoff, + Steps: 1000, // large number to not run out + Factor: 2.0, + Jitter: 1.0, + } + delayFn := backoff.DelayWithReset(fakeClock, 100*time.Millisecond) done := make(chan struct{}) defer close(done) go func() { @@ -784,7 +791,7 @@ func TestReflectorListAndWatchInitConnBackoff(t *testing.T) { name: "test-reflector", listerWatcher: lw, store: NewFIFO(MetaNamespaceKeyFunc), - backoffManager: bm, + delayHandler: delayFn, clock: fakeClock, watchErrorHandler: WatchErrorHandlerWithContext(DefaultWatchErrorHandler), } @@ -804,28 +811,103 @@ func TestReflectorListAndWatchInitConnBackoff(t *testing.T) { } } -type fakeBackoff struct { - clock clock.Clock +func TestNewReflectorWithCustomBackoff(t *testing.T) { + testCases := []struct { + name string + backoff *wait.Backoff + numConnFails int + expLowerBound time.Duration + expUpperBound time.Duration + }{ + { + // Default backoff uses jitter so timing is non-deterministic + // Just verify it completes without error + name: "default backoff", + backoff: nil, + numConnFails: 2, + expLowerBound: 0, + expUpperBound: 10 * time.Second, + }, + { + // Custom backoff: 10ms initial, 2x factor, no jitter, 100ms cap + // After 5 failures: 10 + 20 + 40 + 80 + 100 = 250ms + name: "custom backoff", + backoff: &wait.Backoff{ + Duration: 10 * time.Millisecond, + Factor: 2.0, + Jitter: 0, + Steps: 100, + Cap: 100 * time.Millisecond, + }, + numConnFails: 5, + expLowerBound: 250 * time.Millisecond, + expUpperBound: 300 * time.Millisecond, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + ctx, cancel := context.WithCancelCause(ctx) + connFails := tc.numConnFails + + lw := &ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if connFails > 0 { + connFails-- + return nil, syscall.ECONNREFUSED + } + cancel(errors.New("done")) + return watch.NewFake(), nil + }, + } + + opts := ReflectorOptions{ + Backoff: tc.backoff, + } + r := NewReflectorWithOptions(lw, &v1.Pod{}, NewStore(MetaNamespaceKeyFunc), opts) + + start := time.Now() + err := r.ListAndWatchWithContext(ctx) + elapsed := time.Since(start) + + if err != nil { + t.Errorf("unexpected error %v", err) + } + if elapsed < tc.expLowerBound { + t.Errorf("expected lower bound %v, got %v", tc.expLowerBound, elapsed) + } + if elapsed > tc.expUpperBound { + t.Errorf("expected upper bound %v, got %v", tc.expUpperBound, elapsed) + } + }) + } +} + +type fakeDelayFunc struct { calls int } -func (f *fakeBackoff) Backoff() clock.Timer { +func (f *fakeDelayFunc) delayFunc() time.Duration { f.calls++ - return f.clock.NewTimer(time.Duration(0)) + return 0 } func TestBackoffOnTooManyRequests(t *testing.T) { _, ctx := ktesting.NewTestContext(t) err := apierrors.NewTooManyRequests("too many requests", 1) clock := &clock.RealClock{} - bm := &fakeBackoff{clock: clock} + fd := &fakeDelayFunc{} lw := &ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - switch bm.calls { + switch fd.calls { case 0: return nil, err case 1: @@ -845,7 +927,7 @@ func TestBackoffOnTooManyRequests(t *testing.T) { name: "test-reflector", listerWatcher: lw, store: NewFIFO(MetaNamespaceKeyFunc), - backoffManager: bm, + delayHandler: fd.delayFunc, clock: clock, watchErrorHandler: WatchErrorHandlerWithContext(DefaultWatchErrorHandler), } @@ -855,15 +937,15 @@ func TestBackoffOnTooManyRequests(t *testing.T) { t.Fatal(err) } close(stopCh) - if bm.calls != 2 { - t.Errorf("unexpected watch backoff calls: %d", bm.calls) + if fd.calls != 2 { + t.Errorf("unexpected watch backoff calls: %d", fd.calls) } } func TestNoRelistOnTooManyRequests(t *testing.T) { err := apierrors.NewTooManyRequests("too many requests", 1) clock := &clock.RealClock{} - bm := &fakeBackoff{clock: clock} + fd := &fakeDelayFunc{} listCalls, watchCalls := 0, 0 lw := &ListWatch{ @@ -886,7 +968,7 @@ func TestNoRelistOnTooManyRequests(t *testing.T) { name: "test-reflector", listerWatcher: lw, store: NewFIFO(MetaNamespaceKeyFunc), - backoffManager: bm, + delayHandler: fd.delayFunc, clock: clock, watchErrorHandler: WatchErrorHandlerWithContext(DefaultWatchErrorHandler), } @@ -933,7 +1015,7 @@ func TestRetryInternalError(t *testing.T) { for _, tc := range testCases { err := apierrors.NewInternalError(fmt.Errorf("etcdserver: no leader")) fakeClock := testingclock.NewFakeClock(time.Now()) - bm := &fakeBackoff{clock: fakeClock} + fd := &fakeDelayFunc{} counter := 0 @@ -961,7 +1043,7 @@ func TestRetryInternalError(t *testing.T) { name: "test-reflector", listerWatcher: lw, store: NewFIFO(MetaNamespaceKeyFunc), - backoffManager: bm, + delayHandler: fd.delayFunc, clock: fakeClock, watchErrorHandler: WatchErrorHandlerWithContext(DefaultWatchErrorHandler), } diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go b/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go index 50ebe3e620c45..ef86ca6de2dd6 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go @@ -168,6 +168,68 @@ func TestInitialEventsEndBookmarkTicker(t *testing.T) { }) } +func TestWatchListResourceVersion(t *testing.T) { + scenarios := []struct { + name string + watchEvents []watch.Event + expectedResourceVersion string + }{ + { + name: "empty resource version", + watchEvents: []watch.Event{}, + expectedResourceVersion: "", + }, + { + name: "empty resource version without bookmark event", + watchEvents: []watch.Event{{Type: watch.Added, Object: makePod("p1", "1")}}, + expectedResourceVersion: "", + }, + { + name: "non empty resource version with bookmark event and initial events annotation", + watchEvents: []watch.Event{ + {Type: watch.Added, Object: makePod("p1", "1")}, + {Type: watch.Bookmark, Object: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "2", + Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, + }, + }}, + }, + expectedResourceVersion: "2", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + scenario := scenario + _, ctx := ktesting.NewTestContext(t) + clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, true) + + lw, s, r, ctx, cancel := testData(ctx) + + go func() { + for _, e := range scenario.watchEvents { + lw.fakeWatcher.Action(e.Type, e.Object) + } + cancel(errors.New("done")) + }() + + curRV := "" + trackRV := func(rv string, eventReceivedBesidesAdded bool) { + if eventReceivedBesidesAdded { + curRV = rv + } + } + + _, err := handleListWatch(ctx, time.Now(), lw, s, r.expectedType, r.expectedGVK, r.name, r.typeDescription, trackRV, r.clock, nevererrc) + if err != nil && !errors.Is(err, errorStopRequested) { + t.Errorf("expected errorStopRequested, got %v", err) + } + + require.Equal(t, scenario.expectedResourceVersion, curRV) + }) + } +} + func TestWatchList(t *testing.T) { scenarios := []struct { name string @@ -632,7 +694,7 @@ func testData(ctx context.Context) (*fakeListWatcher, Store, *Reflector, context s := NewStore(MetaNamespaceKeyFunc) lw := &fakeListWatcher{ fakeWatcher: watch.NewFake(), - stop: func() { + stopFunc: func() { cancel(errors.New("time to stop")) }, } @@ -648,7 +710,7 @@ type fakeListWatcher struct { watchCounter int closeAfterWatchRequests int closeAfterListRequests int - stop func() + stopFunc func() requestOptions []metav1.ListOptions @@ -660,7 +722,7 @@ func (lw *fakeListWatcher) List(options metav1.ListOptions) (runtime.Object, err lw.listCounter++ lw.requestOptions = append(lw.requestOptions, options) if lw.listCounter == lw.closeAfterListRequests { - lw.stop() + lw.stopFunc() } if lw.customListResponse != nil { return lw.customListResponse, nil @@ -672,7 +734,7 @@ func (lw *fakeListWatcher) Watch(options metav1.ListOptions) (watch.Interface, e lw.watchCounter++ lw.requestOptions = append(lw.requestOptions, options) if lw.watchCounter == lw.closeAfterWatchRequests { - lw.stop() + lw.stopFunc() } if lw.watchOptionsPredicate != nil { if err := lw.watchOptionsPredicate(options); err != nil { @@ -690,3 +752,15 @@ func (lw *fakeListWatcher) StopAndRecreateWatch() { lw.fakeWatcher.Stop() lw.fakeWatcher = watch.NewFake() } + +func (lw *fakeListWatcher) Stop() { + lw.lock.Lock() + defer lw.lock.Unlock() + lw.fakeWatcher.Stop() +} + +func (lw *fakeListWatcher) ResultChan() <-chan watch.Event { + lw.lock.Lock() + defer lw.lock.Unlock() + return lw.fakeWatcher.ResultChan() +} diff --git a/staging/src/k8s.io/client-go/tools/cache/shared_informer.go b/staging/src/k8s.io/client-go/tools/cache/shared_informer.go index 8973a33e8fa51..4cec4283f175d 100644 --- a/staging/src/k8s.io/client-go/tools/cache/shared_informer.go +++ b/staging/src/k8s.io/client-go/tools/cache/shared_informer.go @@ -20,7 +20,11 @@ import ( "context" "errors" "fmt" + "reflect" + "slices" + "strings" "sync" + "sync/atomic" "time" "k8s.io/apimachinery/pkg/api/meta" @@ -31,6 +35,7 @@ import ( "k8s.io/utils/buffer" "k8s.io/utils/clock" "k8s.io/utils/ptr" + utiltrace "k8s.io/utils/trace" "k8s.io/klog/v2" @@ -193,6 +198,14 @@ type SharedInformer interface { // For that, please call HasSynced on the handle returned by // AddEventHandler. HasSynced() bool + // HasSyncedChecker completes if the shared informer's store has been + // informed by at least one full LIST of the authoritative state + // of the informer's object collection. This is unrelated to "resync". + // + // Note that this doesn't tell you if an individual handler is synced!! + // For that, please use HasSyncedChecker on the handle returned by + // AddEventHandler. + HasSyncedChecker() DoneChecker // LastSyncResourceVersion is the resource version observed when last synced with the underlying // store. The value returned is not synchronized with access to the underlying store and is not // thread-safe. @@ -246,6 +259,10 @@ type ResourceEventHandlerRegistration interface { // HasSynced reports if both the parent has synced and all pre-sync // events have been delivered. HasSynced() bool + + // HasSyncedChecker reports if both the parent has synced and all pre-sync + // events have been delivered. + HasSyncedChecker() DoneChecker } // Optional configuration options for [SharedInformer.AddEventHandlerWithOptions]. @@ -302,9 +319,13 @@ func NewSharedIndexInformer(lw ListerWatcher, exampleObject runtime.Object, defa func NewSharedIndexInformerWithOptions(lw ListerWatcher, exampleObject runtime.Object, options SharedIndexInformerOptions) SharedIndexInformer { realClock := &clock.RealClock{} + processor := &sharedProcessor{clock: realClock} + processor.listenersRCond = sync.NewCond(processor.listenersLock.RLocker()) + return &sharedIndexInformer{ - indexer: NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers), - processor: &sharedProcessor{clock: realClock}, + indexer: NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers, WithStoreMetrics(options.Identifier, options.InformerMetricsProvider)), + processor: processor, + synced: make(chan struct{}), listerWatcher: lw, objectType: exampleObject, objectDescription: options.ObjectDescription, @@ -312,6 +333,9 @@ func NewSharedIndexInformerWithOptions(lw ListerWatcher, exampleObject runtime.O defaultEventHandlerResyncPeriod: options.ResyncPeriod, clock: realClock, cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", exampleObject)), + identifier: options.Identifier, + informerMetricsProvider: options.InformerMetricsProvider, + keyFunc: DeletionHandlingMetaNamespaceKeyFunc, } } @@ -327,6 +351,14 @@ type SharedIndexInformerOptions struct { // ObjectDescription is the sharedIndexInformer's object description. This is passed through to the // underlying Reflector's type description. ObjectDescription string + + // Identifier is used to identify the FIFO for metrics and logging purposes. + // If not set, metrics will not be published. + Identifier InformerNameAndResource + + // InformerMetricsProvider is the metrics provider for the FIFO queue. + // If not set, metrics will be no-ops. + InformerMetricsProvider InformerMetricsProvider } // InformerSynced is a function that can be used to determine if an informer has synced. This is useful for determining if caches have synced. @@ -399,6 +431,147 @@ func WaitForCacheSync(stopCh <-chan struct{}, cacheSyncs ...InformerSynced) bool return true } +// WaitFor waits for a set of activities to complete, like cache syncing. +// It returns true if it was successful, false if the context was canceled +// before all activities are completed. +// +// If a non-nil "what" is provided, then progress information is logged +// while waiting ("Waiting", for=""). Verbosity is V(0). This can +// be made less verbose by the caller with: +// +// WaitFor(klog.NewContext(ctx, klog.FromContext(ctx).V(2)), ...) +// +// In contrast to other WaitForCacheSync alternatives, this one here doesn't +// need polling, which makes it react immediately. When used in a synctest unit +// test, waiting completes without moving time forward randomly, which +// makes tests more predictable. +func WaitFor(ctx context.Context, what string, checkers ...DoneChecker) bool { + logger := klog.FromContext(ctx) + if what != "" { + helper, l := logger.WithCallStackHelper() + logger = l + helper() + logger.Info("Waiting", "for", what) + } + + // Check in parallel to ensure that we log "Done waiting" as soon + // as possible for each checker. The timing may be useful to know. + // We cannot log inside the goroutine, the stack unwinding wouldn't + // work, so instead each goroutine just notifies the parent + // goroutine when it's checker is done and the main goroutine then + // logs it. + var wg sync.WaitGroup + type result struct { + checker DoneChecker + done bool + } + doneChecker := make(chan result) + for _, checker := range checkers { + wg.Go(func() { + select { + case <-checker.Done(): + doneChecker <- result{checker, true} + case <-ctx.Done(): + // We can end up here even when the checker is already done, + // select is not deterministic. Check once more without blocking + // before finally giving up. + select { + case <-checker.Done(): + doneChecker <- result{checker, true} + default: + doneChecker <- result{checker, false} + } + } + }) + } + instances := make([]string, 0, len(checkers)) + for range len(checkers) { + // We are guaranteed to get exactly one result from each goroutine, so this won't block forever. + result := <-doneChecker + if result.done { + if what != "" { + logger.Info("Done waiting", "for", what, "instance", result.checker.Name()) + } + } else { + // We don't need this information unless we are a) logging or b) debugging interactively. + instances = append(instances, result.checker.Name()) + } + } + wg.Wait() + + if what != "" && len(instances) > 0 { + slices.Sort(instances) + logger.Info("Timed out waiting", "for", what, "cause", context.Cause(ctx), "instances", instances) + } + + done := len(instances) == 0 + return done +} + +// DoneChecker, in contrast to [InformerSynced], supports waiting +// for some activity to finish without polling and has a name +// that describes itself. +// +// To check for completion without blocking, use [IsDone]. +type DoneChecker interface { + // Name returns a string describing the entity that is being waited for. + // + // Note that this name might be computed, so callers should only + // get the name outside of a hot code path. + Name() string + + // Done returns a channel that will be closed on completion + // of the activity. + Done() <-chan struct{} +} + +// IsDone returns true if the activity is done, false otherwise. +func IsDone(checker DoneChecker) bool { + select { + case <-checker.Done(): + return true + default: + return false + } +} + +// SyncResult is the result of a shared informer factory's WaitForCacheSyncWithContext. +// Under the hood such factories use [WaitFor] to wait for all instantiated informers, +// then provide this summary of what was synced. +// +// Note that the informers may have synced already before all event handlers registered with +// those informers have synced. Code which wants to be sure that all of its state is up-to-date +// should do its own WaitFor with the informer's HasSyncedChecker() *and* the +// registration handle's HasSyncChecker() results. +type SyncResult struct { + // Err is nil if all informer caches were synced, otherwise it is + // the reason why waiting for cache syncing stopped (= context.Cause(ctx)). + Err error + + // Synced maps each registered informer in a SharedInformerFactory to + // true if it has synced, false otherwise. + Synced map[reflect.Type]bool +} + +// AsError turns a SyncResult into an error if not all caches were synced, +// otherwise it returns nil. The error wraps context.Cause(ctx) and +// includes information about the informers which were not synced. +func (c SyncResult) AsError() error { + if c.Err == nil { + return nil + } + + unsynced := make([]string, 0, len(c.Synced)) + for t, synced := range c.Synced { + if !synced { + unsynced = append(unsynced, t.String()) + } + } + slices.Sort(unsynced) + + return fmt.Errorf("failed to sync all caches: %s: %w", strings.Join(unsynced, ", "), c.Err) +} + // `*sharedIndexInformer` implements SharedIndexInformer and has three // main components. One is an indexed local cache, `indexer Indexer`. // The second main component is a Controller that pulls @@ -416,6 +589,10 @@ type sharedIndexInformer struct { indexer Indexer controller Controller + // synced gets created when creating the sharedIndexInformer. + // It gets closed when Run detects that the processor created + synced chan struct{} + processor *sharedProcessor cacheMutationDetector MutationDetector @@ -449,6 +626,15 @@ type sharedIndexInformer struct { watchErrorHandler WatchErrorHandlerWithContext transform TransformFunc + + // identifier is used to identify this informer for metrics and logging purposes. + identifier InformerNameAndResource + + // informerMetricsProvider is the metrics provider for the FIFO queue. + informerMetricsProvider InformerMetricsProvider + + // keyFunc is called when processing deltas by the underlying process function. + keyFunc KeyFunc } // dummyController hides the fact that a SharedInformer is different from a dedicated one @@ -470,6 +656,10 @@ func (v *dummyController) HasSynced() bool { return v.informer.HasSynced() } +func (v *dummyController) HasSyncedChecker() DoneChecker { + return v.informer.HasSyncedChecker() +} + func (v *dummyController) LastSyncResourceVersion() string { if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InformerResourceVersion) { return v.informer.LastSyncResourceVersion() @@ -539,7 +729,7 @@ func (s *sharedIndexInformer) RunWithContext(ctx context.Context) { s.startedLock.Lock() defer s.startedLock.Unlock() - fifo := newQueueFIFO(s.indexer, s.transform) + logger, fifo := newQueueFIFO(logger, s.objectType, s.indexer, s.transform, s.identifier, s.informerMetricsProvider) cfg := &Config{ Queue: fifo, @@ -549,8 +739,12 @@ func (s *sharedIndexInformer) RunWithContext(ctx context.Context) { FullResyncPeriod: s.resyncCheckPeriod, ShouldResync: s.processor.shouldResync, - Process: s.HandleDeltas, - ProcessBatch: s.HandleBatchDeltas, + Process: func(obj interface{}, isInInitialList bool) error { + return s.handleDeltas(logger, obj, isInInitialList) + }, + ProcessBatch: func(deltas []Delta, isInInitialList bool) error { + return s.handleBatchDeltas(logger, deltas, isInInitialList) + }, WatchErrorHandlerWithContext: s.watchErrorHandler, } @@ -570,6 +764,15 @@ func (s *sharedIndexInformer) RunWithContext(ctx context.Context) { // has a RunWithContext method that we can use here. wg.StartWithChannel(processorStopCtx.Done(), s.cacheMutationDetector.Run) wg.StartWithContext(processorStopCtx, s.processor.run) + wg.Start(func() { + select { + case <-ctx.Done(): + // We were stopped without completing the sync. + case <-s.controller.HasSyncedChecker().Done(): + // Controller has synced and thus so have we. + close(s.synced) + } + }) defer func() { s.startedLock.Lock() @@ -586,13 +789,31 @@ func (s *sharedIndexInformer) HasStarted() bool { } func (s *sharedIndexInformer) HasSynced() bool { - s.startedLock.Lock() - defer s.startedLock.Unlock() - - if s.controller == nil { + select { + case <-s.synced: + return true + default: return false } - return s.controller.HasSynced() +} + +func (s *sharedIndexInformer) HasSyncedChecker() DoneChecker { + return &sharedIndexInformerDone{ + s: s, + } +} + +// sharedIndexInformerDone implements [NamedCacheSync] for a [sharedIndexInformer]. +type sharedIndexInformerDone struct { + s *sharedIndexInformer +} + +func (sd *sharedIndexInformerDone) Name() string { + return fmt.Sprintf("SharedIndexInformer %T", sd.s.objectType) +} + +func (sd *sharedIndexInformerDone) Done() <-chan struct{} { + return sd.s.synced } func (s *sharedIndexInformer) LastSyncResourceVersion() string { @@ -684,10 +905,11 @@ func (s *sharedIndexInformer) AddEventHandlerWithOptions(handler ResourceEventHa } } - listener := newProcessListener(logger, handler, resyncPeriod, determineResyncPeriod(logger, resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize, s.HasSynced) + listener := newProcessListener(logger, handler, resyncPeriod, determineResyncPeriod(logger, resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize, s.HasSyncedChecker()) if !s.started { - return s.processor.addListener(listener), nil + handle, _ := s.processor.addListener(listener) + return handle, nil } // in order to safely join, we have to @@ -698,7 +920,7 @@ func (s *sharedIndexInformer) AddEventHandlerWithOptions(handler ResourceEventHa s.blockDeltas.Lock() defer s.blockDeltas.Unlock() - handle := s.processor.addListener(listener) + handle, started := s.processor.addListener(listener) for _, item := range s.indexer.List() { // Note that we enqueue these notifications with the lock held // and before returning the handle. That means there is never a @@ -710,23 +932,29 @@ func (s *sharedIndexInformer) AddEventHandlerWithOptions(handler ResourceEventHa // thread adding them and the counter is temporarily zero). listener.add(addNotification{newObj: item, isInInitialList: true}) } + + // Initial list is added, now we can allow the listener to detect that "upstream has synced". + if started { + s.processor.wg.Start(listener.watchSynced) + } + return handle, nil } -func (s *sharedIndexInformer) HandleDeltas(obj interface{}, isInInitialList bool) error { +func (s *sharedIndexInformer) handleDeltas(logger klog.Logger, obj interface{}, isInInitialList bool) error { s.blockDeltas.Lock() defer s.blockDeltas.Unlock() if deltas, ok := obj.(Deltas); ok { - return processDeltas(s, s.indexer, deltas, isInInitialList) + return processDeltas(logger, s, s.indexer, deltas, isInInitialList, s.keyFunc) } return errors.New("object given as Process argument is not Deltas") } -func (s *sharedIndexInformer) HandleBatchDeltas(deltas []Delta, isInInitialList bool) error { +func (s *sharedIndexInformer) handleBatchDeltas(logger klog.Logger, deltas []Delta, isInInitialList bool) error { s.blockDeltas.Lock() defer s.blockDeltas.Unlock() - return processDeltasInBatch(s, s.indexer, deltas, isInInitialList) + return processDeltasInBatch(logger, s, s.indexer, deltas, isInInitialList, s.keyFunc) } // Conforms to ResourceEventHandler @@ -795,6 +1023,7 @@ func (s *sharedIndexInformer) RemoveEventHandler(handle ResourceEventHandlerRegi type sharedProcessor struct { listenersStarted bool listenersLock sync.RWMutex + listenersRCond *sync.Cond // Caller of Wait must hold a read lock on listenersLock. // Map from listeners to whether or not they are currently syncing listeners map[*processorListener]bool clock clock.Clock @@ -818,7 +1047,7 @@ func (p *sharedProcessor) getListener(registration ResourceEventHandlerRegistrat return nil } -func (p *sharedProcessor) addListener(listener *processorListener) ResourceEventHandlerRegistration { +func (p *sharedProcessor) addListener(listener *processorListener) (ResourceEventHandlerRegistration, bool) { p.listenersLock.Lock() defer p.listenersLock.Unlock() @@ -829,11 +1058,13 @@ func (p *sharedProcessor) addListener(listener *processorListener) ResourceEvent p.listeners[listener] = true if p.listenersStarted { + // Not starting listener.watchSynced! + // The caller must first add the initial list, then start it. p.wg.Start(listener.run) p.wg.Start(listener.pop) } - return listener + return listener, p.listenersStarted } func (p *sharedProcessor) removeListener(handle ResourceEventHandlerRegistration) error { @@ -864,6 +1095,14 @@ func (p *sharedProcessor) distribute(obj interface{}, sync bool) { p.listenersLock.RLock() defer p.listenersLock.RUnlock() + // Before we start blocking on writes to the listeners' channels, + // ensure that they all have been started. If the processor stops, + // p.listeners gets cleared, in which case we also continue here + // and return without doing anything. + for !p.listenersStarted && len(p.listeners) > 0 { + p.listenersRCond.Wait() + } + for listener, isSyncing := range p.listeners { switch { case !sync: @@ -878,15 +1117,26 @@ func (p *sharedProcessor) distribute(obj interface{}, sync bool) { } } +// sharedProcessorRunHook can be used inside tests to execute additional code +// at the start of sharedProcessor.run. +var sharedProcessorRunHook atomic.Pointer[func()] + func (p *sharedProcessor) run(ctx context.Context) { func() { - p.listenersLock.RLock() - defer p.listenersLock.RUnlock() + hook := sharedProcessorRunHook.Load() + if hook != nil { + (*hook)() + } + // Changing listenersStarted needs a write lock. + p.listenersLock.Lock() + defer p.listenersLock.Unlock() for listener := range p.listeners { + p.wg.Start(listener.watchSynced) p.wg.Start(listener.run) p.wg.Start(listener.pop) } p.listenersStarted = true + p.listenersRCond.Signal() }() <-ctx.Done() @@ -903,6 +1153,9 @@ func (p *sharedProcessor) run(ctx context.Context) { // Reset to false since no listeners are running p.listenersStarted = false + // Wake up sharedProcessor.distribute. + p.listenersRCond.Signal() + p.wg.Wait() // Wait for all .pop() and .run() to stop } @@ -940,7 +1193,7 @@ func (p *sharedProcessor) resyncCheckPeriodChanged(logger klog.Logger, resyncChe } // processorListener relays notifications from a sharedProcessor to -// one ResourceEventHandler --- using two goroutines, two unbuffered +// one ResourceEventHandler --- using three goroutines, two unbuffered // channels, and an unbounded ring buffer. The `add(notification)` // function sends the given notification to `addCh`. One goroutine // runs `pop()`, which pumps notifications from `addCh` to `nextCh` @@ -948,16 +1201,24 @@ func (p *sharedProcessor) resyncCheckPeriodChanged(logger klog.Logger, resyncChe // Another goroutine runs `run()`, which receives notifications from // `nextCh` and synchronously invokes the appropriate handler method. // +// The third goroutine watches the upstream "has synced" channel +// and notifies a SingleFileTracker instance. That instance then +// combines the upstream state and the processListener state to +// implement the overall "event handler has synced". +// // processorListener also keeps track of the adjusted requested resync // period of the listener. type processorListener struct { logger klog.Logger nextCh chan interface{} addCh chan interface{} + done chan struct{} - handler ResourceEventHandler + handler ResourceEventHandler + handlerName string - syncTracker *synctrack.SingleFileTracker + syncTracker *synctrack.SingleFileTracker + upstreamHasSynced DoneChecker // pendingNotifications is an unbounded ring buffer that holds all notifications not yet distributed. // There is one per listener, but a failing/stalled listener will have infinite pendingNotifications @@ -965,6 +1226,9 @@ type processorListener struct { // TODO: This is no worse than before, since reflectors were backed by unbounded DeltaFIFOs, but // we should try to do something better. pendingNotifications buffer.RingGrowing + // pendingNotificationsLength tracks pendingNotifications size and is only mutated by pop(). + // run() reads this to decide when to enable expensive time tracing. + pendingNotificationsLength atomic.Int64 // requestedResyncPeriod is how frequently the listener wants a // full resync from the shared informer, but modified by two @@ -995,13 +1259,23 @@ func (p *processorListener) HasSynced() bool { return p.syncTracker.HasSynced() } -func newProcessListener(logger klog.Logger, handler ResourceEventHandler, requestedResyncPeriod, resyncPeriod time.Duration, now time.Time, bufferSize int, hasSynced func() bool) *processorListener { +// HasNamedSync is done if the source informer has synced, and all +// corresponding events have been delivered. +func (p *processorListener) HasSyncedChecker() DoneChecker { + return p.syncTracker +} + +func newProcessListener(logger klog.Logger, handler ResourceEventHandler, requestedResyncPeriod, resyncPeriod time.Duration, now time.Time, bufferSize int, hasSynced DoneChecker) *processorListener { + handlerName := nameForHandler(handler) ret := &processorListener{ logger: logger, nextCh: make(chan interface{}), addCh: make(chan interface{}), + done: make(chan struct{}), + upstreamHasSynced: hasSynced, handler: handler, - syncTracker: &synctrack.SingleFileTracker{UpstreamHasSynced: hasSynced}, + handlerName: handlerName, + syncTracker: synctrack.NewSingleFileTracker(fmt.Sprintf("%s + event handler %s", hasSynced.Name(), handlerName)), pendingNotifications: *buffer.NewRingGrowing(bufferSize), requestedResyncPeriod: requestedResyncPeriod, resyncPeriod: resyncPeriod, @@ -1022,6 +1296,7 @@ func (p *processorListener) add(notification interface{}) { func (p *processorListener) pop() { defer utilruntime.HandleCrashWithLogger(p.logger) defer close(p.nextCh) // Tell .run() to stop + defer close(p.done) // Tell .watchSynced() to stop var nextCh chan<- interface{} var notification interface{} @@ -1031,7 +1306,9 @@ func (p *processorListener) pop() { // Notification dispatched var ok bool notification, ok = p.pendingNotifications.ReadOne() - if !ok { // Nothing to pop + if ok { + p.pendingNotificationsLength.Add(-1) + } else { // Nothing to pop nextCh = nil // Disable this select case } case notificationToAdd, ok := <-p.addCh: @@ -1044,6 +1321,7 @@ func (p *processorListener) pop() { nextCh = p.nextCh } else { // There is already a notification waiting to be dispatched p.pendingNotifications.WriteOne(notificationToAdd) + p.pendingNotificationsLength.Add(1) } } } @@ -1066,6 +1344,14 @@ func (p *processorListener) run() { // Gets reset below, but only if we get that far. sleepAfterCrash = true defer utilruntime.HandleCrashWithLogger(p.logger) + pendingNotifications := p.pendingNotificationsLength.Load() + if pendingNotifications > initialBufferSize { + trace := utiltrace.New("processorListener handler", + utiltrace.Field{Key: "handler", Value: p.handlerName}, + utiltrace.Field{Key: "pendingNotifications", Value: pendingNotifications}, + ) + defer trace.LogIfLong(100 * time.Millisecond) + } switch notification := next.(type) { case updateNotification: @@ -1085,6 +1371,16 @@ func (p *processorListener) run() { } } +func (p *processorListener) watchSynced() { + select { + case <-p.upstreamHasSynced.Done(): + // Notify tracker that the upstream has synced. + p.syncTracker.UpstreamHasSynced() + case <-p.done: + // Give up waiting for sync. + } +} + // shouldResync determines if the listener needs a resync. If the listener's resyncPeriod is 0, // this always returns false. func (p *processorListener) shouldResync(now time.Time) bool { diff --git a/staging/src/k8s.io/client-go/tools/cache/shared_informer_test.go b/staging/src/k8s.io/client-go/tools/cache/shared_informer_test.go index 88713ff6f8c02..835e6debdcbeb 100644 --- a/staging/src/k8s.io/client-go/tools/cache/shared_informer_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/shared_informer_test.go @@ -25,6 +25,7 @@ import ( "strings" "sync" "testing" + "testing/synctest" "time" "github.com/google/go-cmp/cmp" @@ -46,6 +47,7 @@ import ( ) type testListener struct { + printlnFunc func(string) // Some, but not all tests use this for per-test output. lock sync.RWMutex resyncPeriod time.Duration expectedItemNames sets.Set[string] @@ -73,9 +75,18 @@ func (l *testListener) OnUpdate(old, new interface{}) { func (l *testListener) OnDelete(obj interface{}) { } +func (l *testListener) println(msg string) { + msg = l.name + ": " + msg + if l.printlnFunc != nil { + l.printlnFunc(msg) + } else { + fmt.Println(msg) + } +} + func (l *testListener) handle(obj interface{}) { key, _ := MetaNamespaceKeyFunc(obj) - fmt.Printf("%s: handle: %v\n", l.name, key) + l.println(fmt.Sprintf("handle: %v", key)) l.lock.Lock() defer l.lock.Unlock() objectMeta, _ := meta.Accessor(obj) @@ -83,7 +94,7 @@ func (l *testListener) handle(obj interface{}) { } func (l *testListener) ok() bool { - fmt.Println("polling") + l.println("polling") err := wait.PollImmediate(100*time.Millisecond, 2*time.Second, func() (bool, error) { if l.satisfiedExpectations() { return true, nil @@ -95,9 +106,9 @@ func (l *testListener) ok() bool { } // wait just a bit to allow any unexpected stragglers to come in - fmt.Println("sleeping") + l.println("sleeping") time.Sleep(1 * time.Second) - fmt.Println("final check") + l.println("final check") return l.satisfiedExpectations() } @@ -108,6 +119,12 @@ func (l *testListener) satisfiedExpectations() bool { return sets.New(l.receivedItemNames...).Equal(l.expectedItemNames) } +func (l *testListener) reset() { + l.lock.Lock() + defer l.lock.Unlock() + l.receivedItemNames = nil +} + func eventHandlerCount(i SharedInformer) int { s := i.(*sharedIndexInformer) s.startedLock.Lock() @@ -208,6 +225,34 @@ func TestIndexer(t *testing.T) { } func TestListenerResyncPeriods(t *testing.T) { + synctest.Test(t, func(t *testing.T) { testListenerResyncPeriods(t, 0) }) +} + +func TestListenerResyncPeriodsDelayed(t *testing.T) { + // sharedProcessor used to deadlock when sharedProcessor.run happened + // to be reached after the first sharedProcessor.distribute. + // This artififical delay simulates that situation. + // + // Must not run in parallel to other tests, but it doesn't need to: + // it doesn't sleep in real-world time and therefore completes quickly. + synctest.Test(t, func(t *testing.T) { testListenerResyncPeriods(t, time.Second) }) +} + +func testListenerResyncPeriods(t *testing.T, startupDelay time.Duration) { + start := time.Now() + println := func(msg string) { + delta := time.Since(start) + t.Logf("%s: %s", delta, msg) + } + + if startupDelay > 0 { + hook := func() { + time.Sleep(startupDelay) + } + sharedProcessorRunHook.Store(&hook) + defer sharedProcessorRunHook.Store(nil) + } + // source simulates an apiserver object endpoint. source := newFakeControllerSource(t) source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}}) @@ -216,20 +261,19 @@ func TestListenerResyncPeriods(t *testing.T) { // create the shared informer and resync every 1s informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer) - clock := testingclock.NewFakeClock(time.Now()) - informer.clock = clock - informer.processor.clock = clock - // listener 1, never resync listener1 := newTestListener("listener1", 0, "pod1", "pod2") + listener1.printlnFunc = println informer.AddEventHandlerWithResyncPeriod(listener1, listener1.resyncPeriod) // listener 2, resync every 2s listener2 := newTestListener("listener2", 2*time.Second, "pod1", "pod2") + listener2.printlnFunc = println informer.AddEventHandlerWithResyncPeriod(listener2, listener2.resyncPeriod) // listener 3, resync every 3s listener3 := newTestListener("listener3", 3*time.Second, "pod1", "pod2") + listener3.printlnFunc = println informer.AddEventHandlerWithResyncPeriod(listener3, listener3.resyncPeriod) listeners := []*testListener{listener1, listener2, listener3} @@ -241,59 +285,65 @@ func TestListenerResyncPeriods(t *testing.T) { wg.Wait() }() - // ensure all listeners got the initial List + // We must potentially advance time to unblock sharedProcess.run, + // otherwise synctest.Wait() below returns without anything + // being done. + time.Sleep(startupDelay) + + // Ensure all listeners got the initial after the initial processing, at the initial start time. + // + // synctest.Wait doesn't detect deadlocks involving mutexes: they are considered not durably + // blocking, so in case of such a deadlock it'll just keep waiting until the entire unit test + // times out. The backtraces then show the deadlock. + synctest.Wait() for _, listener := range listeners { - if !listener.ok() { - t.Errorf("%s: expected %v, got %v", listener.name, listener.expectedItemNames, listener.receivedItemNames) + if !listener.satisfiedExpectations() { + t.Errorf("%s: %s: expected %v, got %v", time.Since(start), listener.name, listener.expectedItemNames, listener.receivedItemNames) } } // reset for _, listener := range listeners { - listener.receivedItemNames = []string{} + listener.reset() } // advance so listener2 gets a resync - clock.Step(2 * time.Second) + time.Sleep(2*time.Second - startupDelay) + synctest.Wait() // make sure listener2 got the resync - if !listener2.ok() { - t.Errorf("%s: expected %v, got %v", listener2.name, listener2.expectedItemNames, listener2.receivedItemNames) + if !listener2.satisfiedExpectations() { + t.Errorf("%s: %s: expected %v, got %v", time.Since(start), listener2.name, listener2.expectedItemNames, listener2.receivedItemNames) } - // wait a bit to give errant items a chance to go to 1 and 3 - time.Sleep(1 * time.Second) - // make sure listeners 1 and 3 got nothing if len(listener1.receivedItemNames) != 0 { - t.Errorf("listener1: should not have resynced (got %d)", len(listener1.receivedItemNames)) + t.Errorf("%s: listener1: should not have resynced (got %d)", time.Since(start), len(listener1.receivedItemNames)) } if len(listener3.receivedItemNames) != 0 { - t.Errorf("listener3: should not have resynced (got %d)", len(listener3.receivedItemNames)) + t.Errorf("%s: listener3: should not have resynced (got %d)", time.Since(start), len(listener3.receivedItemNames)) } // reset for _, listener := range listeners { - listener.receivedItemNames = []string{} + listener.reset() } // advance so listener3 gets a resync - clock.Step(1 * time.Second) + time.Sleep(1 * time.Second) + synctest.Wait() // make sure listener3 got the resync - if !listener3.ok() { - t.Errorf("%s: expected %v, got %v", listener3.name, listener3.expectedItemNames, listener3.receivedItemNames) + if !listener3.satisfiedExpectations() { + t.Errorf("%s: %s: expected %v, got %v", time.Since(start), listener3.name, listener3.expectedItemNames, listener3.receivedItemNames) } - // wait a bit to give errant items a chance to go to 1 and 2 - time.Sleep(1 * time.Second) - // make sure listeners 1 and 2 got nothing if len(listener1.receivedItemNames) != 0 { - t.Errorf("listener1: should not have resynced (got %d)", len(listener1.receivedItemNames)) + t.Errorf("%s: listener1: should not have resynced (got %d)", time.Since(start), len(listener1.receivedItemNames)) } if len(listener2.receivedItemNames) != 0 { - t.Errorf("listener2: should not have resynced (got %d)", len(listener2.receivedItemNames)) + t.Errorf("%s: listener2: should not have resynced (got %d)", time.Since(start), len(listener2.receivedItemNames)) } } @@ -1084,8 +1134,13 @@ func TestAddWhileActive(t *testing.T) { return } - if !handle1.HasSynced() { - t.Error("Not synced after Run??") + select { + case <-handle1.HasSyncedChecker().Done(): + if !handle1.HasSynced() { + t.Error("Not synced after channel said we are synced??") + } + case <-time.After(10 * time.Second): + t.Error("Not synced 10 seconds after Run??") } listener2.lock.Lock() // ensure we observe it before it has synced diff --git a/staging/src/k8s.io/client-go/tools/cache/store.go b/staging/src/k8s.io/client-go/tools/cache/store.go index a412fd7014dec..261ed60d9ca97 100644 --- a/staging/src/k8s.io/client-go/tools/cache/store.go +++ b/staging/src/k8s.io/client-go/tools/cache/store.go @@ -55,6 +55,15 @@ type Store interface { // ListKeys returns a list of all the keys currently associated with non-empty accumulators ListKeys() []string + // LastStoreSyncResourceVersion returns the latest resource version that the store has seen. + // This is used to determine the latest resource version the store has seen from objects + // observed being written to the store. + LastStoreSyncResourceVersion() string + + // Bookmark observes a new resource version passed into it and + // will be used to get the latest resource version of the store. + Bookmark(rv string) + // Get returns the accumulator associated with the given object's key Get(obj interface{}) (item interface{}, exists bool, err error) @@ -200,6 +209,10 @@ type cache struct { keyFunc KeyFunc // Called with every object put in the cache. transformer TransformFunc + // identifier is used to identify the store for metrics. + identifier InformerNameAndResource + // metrics is the metrics provider for the store. + metrics InformerMetricsProvider } var _ Store = &cache{} @@ -276,7 +289,7 @@ func (c *cache) Delete(obj interface{}) error { if err != nil { return KeyError{obj, err} } - c.cacheStorage.Delete(key) + c.cacheStorage.DeleteWithObject(key, obj) return nil } @@ -292,6 +305,14 @@ func (c *cache) ListKeys() []string { return c.cacheStorage.ListKeys() } +func (c *cache) LastStoreSyncResourceVersion() string { + return c.cacheStorage.LastStoreSyncResourceVersion() +} + +func (c *cache) Bookmark(rv string) { + c.cacheStorage.Bookmark(rv) +} + // GetIndexers returns the indexers of cache func (c *cache) GetIndexers() Indexers { return c.cacheStorage.GetIndexers() @@ -378,22 +399,41 @@ func WithTransformer(transformer TransformFunc) StoreOption { } } +func WithStoreMetrics(identifier InformerNameAndResource, metrics InformerMetricsProvider) StoreOption { + return func(c *cache) { + c.identifier = identifier + c.metrics = metrics + } +} + // NewStore returns a Store implemented simply with a map and a lock. func NewStore(keyFunc KeyFunc, opts ...StoreOption) Store { c := &cache{ - cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}), - keyFunc: keyFunc, + keyFunc: keyFunc, } for _, opt := range opts { opt(c) } + threadSafeOpts := []ThreadSafeStoreOption{} + if c.metrics != nil { + threadSafeOpts = append(threadSafeOpts, WithThreadSafeStoreMetrics(c.identifier, c.metrics)) + } + c.cacheStorage = NewThreadSafeStore(Indexers{}, Indices{}, threadSafeOpts...) return c } // NewIndexer returns an Indexer implemented simply with a map and a lock. -func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer { - return &cache{ - cacheStorage: NewThreadSafeStore(indexers, Indices{}), - keyFunc: keyFunc, +func NewIndexer(keyFunc KeyFunc, indexers Indexers, opts ...StoreOption) Indexer { + c := &cache{ + keyFunc: keyFunc, + } + for _, opt := range opts { + opt(c) } + threadSafeOpts := []ThreadSafeStoreOption{} + if c.metrics != nil { + threadSafeOpts = append(threadSafeOpts, WithThreadSafeStoreMetrics(c.identifier, c.metrics)) + } + c.cacheStorage = NewThreadSafeStore(indexers, Indices{}, threadSafeOpts...) + return c } diff --git a/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack.go b/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack.go index 3fa2beb6b719e..e941dafb85a48 100644 --- a/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack.go +++ b/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack.go @@ -20,6 +20,7 @@ limitations under the License. package synctrack import ( + "context" "sync" "sync/atomic" @@ -27,11 +28,32 @@ import ( ) // AsyncTracker helps propagate HasSynced in the face of multiple worker threads. +// The user has to monitor the upstream "has synced" +// and notify the tracker when that changes from false to true. type AsyncTracker[T comparable] struct { - UpstreamHasSynced func() bool + // name describes the instance. + name string + + // upstreamHasSynced is changed from false (initial value) to true + // when UpstreamHasSynced is called. + upstreamHasSynced atomic.Bool lock sync.Mutex waiting sets.Set[T] + + // synced gets canceled once both the tracker and upstream are synced. + // A context is convenient for this because it gives us a channel + // and handles thread-safety. + synced context.Context + cancel func() +} + +func NewAsyncTracker[T comparable](name string) *AsyncTracker[T] { + t := &AsyncTracker[T]{ + name: name, + } + t.synced, t.cancel = context.WithCancel(context.Background()) + return t } // Start should be called prior to processing each key which is part of the @@ -57,6 +79,28 @@ func (t *AsyncTracker[T]) Finished(key T) { if t.waiting != nil { t.waiting.Delete(key) } + + // Maybe synced now? + if t.upstreamHasSynced.Load() && len(t.waiting) == 0 { + // Mark as synced. + t.cancel() + } +} + +// UpstreamHasSynced needs to be called at least once as soon as +// the upstream "has synced" becomes true. It tells AsyncTracker +// that the source is synced. +// +// Must be called after handing over the initial list to Start. +func (t *AsyncTracker[T]) UpstreamHasSynced() { + // Upstream is done, but we might not be yet. + t.upstreamHasSynced.Store(true) + t.lock.Lock() + defer t.lock.Unlock() + if len(t.waiting) == 0 { + // Mark as synced. + t.cancel() + } } // HasSynced returns true if the source is synced and every key present in the @@ -64,27 +108,51 @@ func (t *AsyncTracker[T]) Finished(key T) { // itself synced until *after* it has delivered the notification for the last // key, and that notification handler must have called Start. func (t *AsyncTracker[T]) HasSynced() bool { - // Call UpstreamHasSynced first: it might take a lock, which might take - // a significant amount of time, and we can't hold our lock while - // waiting on that or a user is likely to get a deadlock. - if !t.UpstreamHasSynced() { - return false - } - t.lock.Lock() - defer t.lock.Unlock() - return t.waiting.Len() == 0 + return t.synced.Err() != nil +} + +// Done returns a channel that is closed if the source is synced and every key present in the +// initial list has been processed. This relies on the source not considering +// itself synced until *after* it has delivered the notification for the last +// key, and that notification handler must have called Start. +func (t *AsyncTracker[T]) Done() <-chan struct{} { + return t.synced.Done() +} + +func (t *AsyncTracker[T]) Name() string { + return t.name } // SingleFileTracker helps propagate HasSynced when events are processed in -// order (i.e. via a queue). +// order (i.e. via a queue). The user has to monitor the upstream "has synced" +// and notify the tracker when that changes from false to true. type SingleFileTracker struct { + // name describes the instance. + name string + // Important: count is used with atomic operations so it must be 64-bit // aligned, otherwise atomic operations will panic. Having it at the top of // the struct will guarantee that, even on 32-bit arches. // See https://pkg.go.dev/sync/atomic#pkg-note-BUG for more information. count int64 - UpstreamHasSynced func() bool + // upstreamHasSynced is changed from false (initial value) to true + // when UpstreamHasSynced is called. + upstreamHasSynced atomic.Bool + + // synced gets canceled once both the tracker and upstream are synced. + // A context is convenient for this because it gives us a channel + // and handles thread-safety. + synced context.Context + cancel func() +} + +func NewSingleFileTracker(name string) *SingleFileTracker { + t := &SingleFileTracker{ + name: name, + } + t.synced, t.cancel = context.WithCancel(context.Background()) + return t } // Start should be called prior to processing each key which is part of the @@ -103,6 +171,26 @@ func (t *SingleFileTracker) Finished() { if result < 0 { panic("synctrack: negative counter; this logic error means HasSynced may return incorrect value") } + + // Maybe synced now? + if result == 0 && t.upstreamHasSynced.Load() { + // Mark as synced. + t.cancel() + } +} + +// UpstreamHasSynced needs to be called at least once as soon as +// the upstream "has synced" becomes true. It tells SingleFileTracker +// that the source is synced. +// +// Must be called after handing over the initial list to Start. +func (t *SingleFileTracker) UpstreamHasSynced() { + // Upstream is done, but we might not be yet. + t.upstreamHasSynced.Store(true) + if atomic.LoadInt64(&t.count) == 0 { + // Mark as synced. + t.cancel() + } } // HasSynced returns true if the source is synced and every key present in the @@ -110,11 +198,17 @@ func (t *SingleFileTracker) Finished() { // itself synced until *after* it has delivered the notification for the last // key, and that notification handler must have called Start. func (t *SingleFileTracker) HasSynced() bool { - // Call UpstreamHasSynced first: it might take a lock, which might take - // a significant amount of time, and we don't want to then act on a - // stale count value. - if !t.UpstreamHasSynced() { - return false - } - return atomic.LoadInt64(&t.count) <= 0 + return t.synced.Err() != nil +} + +// Done returns a channel that is closed if the source is synced and every key present in the +// initial list has been processed. This relies on the source not considering +// itself synced until *after* it has delivered the notification for the last +// key, and that notification handler must have called Start. +func (t *SingleFileTracker) Done() <-chan struct{} { + return t.synced.Done() +} + +func (t *SingleFileTracker) Name() string { + return t.name } diff --git a/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack_test.go b/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack_test.go index 4cf089e225c6e..bff8838e8aeea 100644 --- a/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/synctrack/synctrack_test.go @@ -19,29 +19,24 @@ package synctrack import ( "strings" "sync" - "time" "testing" ) -func testSingleFileFuncs(upstreamHasSynced func() bool) (start func(), finished func(), hasSynced func() bool) { - tracker := SingleFileTracker{ - UpstreamHasSynced: upstreamHasSynced, - } - return tracker.Start, tracker.Finished, tracker.HasSynced +func testSingleFileFuncs() (upstreamHasSynced func(), start func(), finished func(), hasSynced func() bool, synced <-chan struct{}) { + tracker := NewSingleFileTracker("") + return tracker.UpstreamHasSynced, tracker.Start, tracker.Finished, tracker.HasSynced, tracker.Done() } -func testAsyncFuncs(upstreamHasSynced func() bool) (start func(), finished func(), hasSynced func() bool) { - tracker := AsyncTracker[string]{ - UpstreamHasSynced: upstreamHasSynced, - } - return func() { tracker.Start("key") }, func() { tracker.Finished("key") }, tracker.HasSynced +func testAsyncFuncs() (upstreamHasSynced func(), start func(), finished func(), hasSynced func() bool, synced <-chan struct{}) { + tracker := NewAsyncTracker[string]("") + return tracker.UpstreamHasSynced, func() { tracker.Start("key") }, func() { tracker.Finished("key") }, tracker.HasSynced, tracker.Done() } func TestBasicLogic(t *testing.T) { table := []struct { name string - construct func(func() bool) (func(), func(), func() bool) + construct func() (func(), func(), func(), func() bool, <-chan struct{}) }{ {"SingleFile", testSingleFileFuncs}, {"Async", testAsyncFuncs}, @@ -50,27 +45,87 @@ func TestBasicLogic(t *testing.T) { for _, entry := range table { t.Run(entry.name, func(t *testing.T) { table := []struct { - synced bool + synced bool + syncedBeforeFinish bool + start bool finish bool expectSynced bool }{ - {false, true, true, false}, - {true, true, false, false}, - {false, true, false, false}, - {true, true, true, true}, + {false, false, true, true, false}, + {true, false, true, false, false}, + {true, true, true, false, false}, + {false, false, true, false, false}, + {true, false, true, true, true}, + {true, true, true, true, true}, } for _, tt := range table { - Start, Finished, HasSynced := entry.construct(func() bool { return tt.synced }) + upstreamHasSynced, start, finished, hasSynced, synced := entry.construct() + syncedDone := func() bool { + select { + case <-synced: + return true + default: + return false + } + } + + if hasSynced() { + t.Errorf("for %#v got HasSynced() true before start (wanted false)", tt) + } + if syncedDone() { + t.Errorf("for %#v got Done() true before start (wanted false)", tt) + } + if tt.start { - Start() + start() + } + + if hasSynced() { + t.Errorf("for %#v got HasSynced() true after start (wanted false)", tt) + } + if syncedDone() { + t.Errorf("for %#v got Done() true after start (wanted false)", tt) + } + + // "upstream has synced" may occur before or after finished, but not before start. + if tt.synced && tt.syncedBeforeFinish { + upstreamHasSynced() + if hasSynced() { + t.Errorf("for %#v got HasSynced() true after upstreamHasSynced and before finish (wanted false)", tt) + } + if syncedDone() { + t.Errorf("for %#v got Done() true after upstreamHasSynced and before finish (wanted false)", tt) + } } if tt.finish { - Finished() + finished() } - got := HasSynced() - if e, a := tt.expectSynced, got; e != a { - t.Errorf("for %#v got %v (wanted %v)", tt, a, e) + if tt.synced && !tt.syncedBeforeFinish { + if hasSynced() { + t.Errorf("for %#v got HasSynced() true after finish and before upstreamHasSynced (wanted false)", tt) + } + if syncedDone() { + t.Errorf("for %#v got Done() true after finish and before upstreamHasSynced (wanted false)", tt) + } + upstreamHasSynced() + } + if e, a := tt.expectSynced, hasSynced(); e != a { + t.Errorf("for %#v got HasSynced() %v (wanted %v)", tt, a, e) + } + if e, a := tt.expectSynced, syncedDone(); e != a { + t.Errorf("for %#v got Done() %v (wanted %v)", tt, a, e) + } + + select { + case <-synced: + if !tt.expectSynced { + t.Errorf("for %#v got done (wanted not done)", tt) + } + default: + if tt.expectSynced { + t.Errorf("for %#v got done (wanted not done)", tt) + } } } }) @@ -78,7 +133,7 @@ func TestBasicLogic(t *testing.T) { } func TestAsyncLocking(t *testing.T) { - aft := AsyncTracker[int]{UpstreamHasSynced: func() bool { return true }} + aft := NewAsyncTracker[int]("") var wg sync.WaitGroup for _, i := range []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} { @@ -92,6 +147,7 @@ func TestAsyncLocking(t *testing.T) { }(i) } wg.Wait() + aft.UpstreamHasSynced() if !aft.HasSynced() { t.Errorf("async tracker must have made a threading error?") } @@ -99,7 +155,7 @@ func TestAsyncLocking(t *testing.T) { } func TestSingleFileCounting(t *testing.T) { - sft := SingleFileTracker{UpstreamHasSynced: func() bool { return true }} + sft := NewSingleFileTracker("") for i := 0; i < 100; i++ { sft.Start() @@ -115,6 +171,8 @@ func TestSingleFileCounting(t *testing.T) { } sft.Finished() + sft.UpstreamHasSynced() + if !sft.HasSynced() { t.Fatal("Unexpectedly not synced?") } @@ -148,24 +206,35 @@ func TestSingleFileCounting(t *testing.T) { func TestSingleFile(t *testing.T) { table := []struct { - synced bool + synced bool + syncedBeforeStops bool + starts int stops int expectSynced bool }{ - {false, 1, 1, false}, - {true, 1, 0, false}, - {false, 1, 0, false}, - {true, 1, 1, true}, + {false, false, 1, 1, false}, + {true, false, 1, 0, false}, + {true, true, 1, 0, false}, + {false, false, 1, 0, false}, + {true, false, 1, 1, true}, + {true, true, 1, 1, true}, } for _, tt := range table { - sft := SingleFileTracker{UpstreamHasSynced: func() bool { return tt.synced }} + sft := NewSingleFileTracker("") for i := 0; i < tt.starts; i++ { sft.Start() } + // "upstream has synced" may occur before or after finished, but not before start. + if tt.synced && tt.syncedBeforeStops { + sft.UpstreamHasSynced() + } for i := 0; i < tt.stops; i++ { sft.Finished() } + if tt.synced && !tt.syncedBeforeStops { + sft.UpstreamHasSynced() + } got := sft.HasSynced() if e, a := tt.expectSynced, got; e != a { t.Errorf("for %#v got %v (wanted %v)", tt, a, e) @@ -173,67 +242,3 @@ func TestSingleFile(t *testing.T) { } } - -func TestNoStaleValue(t *testing.T) { - table := []struct { - name string - construct func(func() bool) (func(), func(), func() bool) - }{ - {"SingleFile", testSingleFileFuncs}, - {"Async", testAsyncFuncs}, - } - - for _, entry := range table { - t.Run(entry.name, func(t *testing.T) { - var lock sync.Mutex - upstreamHasSynced := func() bool { - lock.Lock() - defer lock.Unlock() - return true - } - - Start, Finished, HasSynced := entry.construct(upstreamHasSynced) - - // Ordinarily the corresponding lock would be held and you wouldn't be - // able to call this function at this point. - if !HasSynced() { - t.Fatal("Unexpectedly not synced??") - } - - Start() - if HasSynced() { - t.Fatal("Unexpectedly synced??") - } - Finished() - if !HasSynced() { - t.Fatal("Unexpectedly not synced??") - } - - // Now we will prove that if the lock is held, you can't get a false - // HasSynced return. - lock.Lock() - - // This goroutine calls HasSynced - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - if HasSynced() { - t.Error("Unexpectedly synced??") - } - }() - - // This goroutine increments + unlocks. The sleep is to bias the - // runtime such that the other goroutine usually wins (it needs to work - // in both orderings, this one is more likely to be buggy). - go func() { - time.Sleep(time.Millisecond) - Start() - lock.Unlock() - }() - - wg.Wait() - }) - } - -} diff --git a/staging/src/k8s.io/client-go/tools/cache/the_real_fifo.go b/staging/src/k8s.io/client-go/tools/cache/the_real_fifo.go index 933e41bbdc48c..f29c66653cb13 100644 --- a/staging/src/k8s.io/client-go/tools/cache/the_real_fifo.go +++ b/staging/src/k8s.io/client-go/tools/cache/the_real_fifo.go @@ -23,11 +23,20 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" utiltrace "k8s.io/utils/trace" ) // RealFIFOOptions is the configuration parameters for RealFIFO. type RealFIFOOptions struct { + // If set, log output will go to this logger instead of klog.Background(). + // The name of the fifo gets added automatically. + Logger *klog.Logger + + // Name can be used to override the default "RealFIFO" name for the new instance. + // Optional. Used only if Identifier.Name returns an empty string. + Name string + // KeyFunction is used to figure out what key an object should have. (It's // exposed in the returned RealFIFO's keyOf() method, with additional // handling around deleted objects and queue state). @@ -37,12 +46,37 @@ type RealFIFOOptions struct { // KnownObjects is expected to return a list of keys that the consumer of // this queue "knows about". It is used to decide which items are missing // when Replace() is called; 'Deleted' deltas are produced for the missing items. - // KnownObjects is required. + // KnownObjects is required if AtomicEvents is false since it is used to + // query the state of the internal store for Replace and Resync handling. KnownObjects KeyListerGetter // If set, will be called for objects before enqueueing them. Please // see the comment on TransformFunc for details. Transformer TransformFunc + + // AtomicEvents is used to specify whether the RealFIFO will emit events + // atomically or not. If it is set, a single event will be emitted + // atomically for Replace and Resync operations. + // If AtomicEvents is true, KnownObjects must be nil. + AtomicEvents bool + + // UnlockWhileProcessing is used to specify whether the RealFIFO can unlock + // the lock while processing events. If it is set, the lock can be unlocked + // while processing events to allow other goroutines to add items to the queue. + // If UnlockWhileProcessing is true, AtomicEvents must be true as well. + UnlockWhileProcessing bool + + // Identifier is used to identify this FIFO for metrics and logging purposes. + // Optional. If zero value, metrics will not be published and trace logs will not + // include Name or Resource fields. + Identifier InformerNameAndResource + + // MetricsProvider is used to create metrics for the FIFO. + MetricsProvider InformerMetricsProvider + + // EmitDeltaTypeBookmark is used to specify whether the RealFIFO will emit + // bookmark deltas or not. This can only be set if AtomicEvents is true. + EmitDeltaTypeBookmark bool } const ( @@ -57,11 +91,23 @@ var _ QueueWithBatch = &RealFIFO{} // 1. delivers notifications for items that have been deleted // 2. delivers multiple notifications per item instead of simply the most recent value type RealFIFO struct { + // logger is a per-instance logger. This gets chosen when constructing + // the instance, with klog.Background() as default. + logger klog.Logger + + // name is the name of the fifo. It is included in the logger. + name string + lock sync.RWMutex cond sync.Cond items []Delta + // synced is initially an open channel. It gets closed (once!) by checkSynced_locked + // as soon as the initial sync is considered complete. + synced chan struct{} + syncedClosed bool + // populated is true if the first batch of items inserted by Replace() has been populated // or Delete/Add/Update was called first. populated bool @@ -73,7 +119,8 @@ type RealFIFO struct { keyFunc KeyFunc // knownObjects list keys that are "known" --- affecting Delete(), - // Replace(), and Resync() + // Replace(), and Resync(). + // It is nil if emitAtomicEvents is true. knownObjects KeyListerGetter // Indication the queue is closed. @@ -86,11 +133,55 @@ type RealFIFO struct { // batchSize determines the maximum number of objects we can combine into a batch. batchSize int + + // emitAtomicEvents defines whether events like Replace and Resync should be emitted + // atomically rather than as a series of events. This means that any call to the FIFO + // will emit a single event. + // If it is set: + // * a single ReplacedAll event will be emitted instead of multiple Replace events + // * a single SyncAll event will be emitted instead of multiple Sync events + emitAtomicEvents bool + + // unlockWhileProcessing defines whether we can unlock while processing events. + // This may only be set if emitAtomicEvents is true. If unlockWhileProcessing is true, + // Pop and PopBatch must be called from a single threaded consumer. + unlockWhileProcessing bool + + // identifier is used to identify this FIFO for metrics and logging purposes. + identifier InformerNameAndResource + + // metrics holds all metrics for this FIFO. + metrics *fifoMetrics + + // emitDeltaTypeBookmark defines whether bookmark deltas should be emitted. + // This may only be set if emitAtomicEvents is true, which avoids events + // propagating out of RV order during Replace and Resync. + emitDeltaTypeBookmark bool } +// ReplacedAllInfo is the object associated with a Delta of type=ReplacedAll +type ReplacedAllInfo struct { + // ResourceVersion is the resource version passed to the Replace() call that created this Delta + ResourceVersion string + // Objects are the list of objects passed to the Replace() call that created this Delta, + // with any configured transformation already applied. + Objects []interface{} +} + +// BookmarkInfo is the object associated with a Delta of type=Bookmark +type BookmarkInfo struct { + // ResourceVersion is the resource version passed to the Bookmark() call that created this Delta + ResourceVersion string +} + +// SyncAllInfo is the object associated with a Delta of type=SyncAll +// It is used to trigger a resync of the entire queue. +type SyncAllInfo struct{} + var ( _ = Queue(&RealFIFO{}) // RealFIFO is a Queue _ = TransformingStore(&RealFIFO{}) // RealFIFO implements TransformingStore to allow memory optimizations + _ = DoneChecker(&RealFIFO{}) // RealFIFO and implements DoneChecker. ) // Close the queue. @@ -127,11 +218,37 @@ func (f *RealFIFO) HasSynced() bool { return f.hasSynced_locked() } -// ignoring lint to reduce delta to the original for review. It's ok adjust later. -// -//lint:file-ignore ST1003: should not use underscores in Go names +// HasSyncedChecker is done if an Add/Update/Delete/AddIfNotPresent are called first, +// or the first batch of items inserted by Replace() has been popped. +func (f *RealFIFO) HasSyncedChecker() DoneChecker { + return f +} + +// Name implements [DoneChecker.Name] +func (f *RealFIFO) Name() string { + return f.name +} + +// Done implements [DoneChecker.Done] +func (f *RealFIFO) Done() <-chan struct{} { + return f.synced +} + +// hasSynced_locked returns the result of a prior checkSynced_locked call. func (f *RealFIFO) hasSynced_locked() bool { - return f.populated && f.initialPopulationCount == 0 + return f.syncedClosed +} + +// checkSynced_locked checks whether the initial batch of items (set via Replace) has been delivered +// and closes the synced channel as needed. It must be called after changing f.populated and/or +// f.initialPopulationCount while the mutex is still locked. +func (f *RealFIFO) checkSynced_locked() { + synced := f.populated && f.initialPopulationCount == 0 + if synced && !f.syncedClosed { + // Initial sync is complete. + f.syncedClosed = true + close(f.synced) + } } // addToItems_locked appends to the delta list. @@ -169,6 +286,48 @@ func (f *RealFIFO) addToItems_locked(deltaActionType DeltaType, skipTransform bo Object: obj, }) f.cond.Broadcast() + f.metrics.numberOfQueuedItem.Set(float64(len(f.items))) + + return nil +} + +// addReplaceToItemsLocked appends to the delta list. +func (f *RealFIFO) addReplaceToItemsLocked(objs []interface{}, resourceVersion string) error { + // Replaced items must be transformed before being added to the queue. These objects must + // all be objects that have not been transformed yet. + if f.transformer != nil { + transformedObjs := make([]interface{}, len(objs)) + for i, obj := range objs { + transformedObj, err := f.transformer(obj) + if err != nil { + return err + } + transformedObjs[i] = transformedObj + } + objs = transformedObjs + } + + info := ReplacedAllInfo{ + ResourceVersion: resourceVersion, + Objects: objs, + } + f.items = append(f.items, Delta{ + Type: ReplacedAll, + Object: info, + }) + f.cond.Broadcast() + f.metrics.numberOfQueuedItem.Set(float64(len(f.items))) + + return nil +} + +func (f *RealFIFO) addResyncToItemsLocked() error { + f.items = append(f.items, Delta{ + Type: SyncAll, + Object: SyncAllInfo{}, + }) + f.cond.Broadcast() + f.metrics.numberOfQueuedItem.Set(float64(len(f.items))) return nil } @@ -180,6 +339,7 @@ func (f *RealFIFO) Add(obj interface{}) error { defer f.lock.Unlock() f.populated = true + f.checkSynced_locked() retErr := f.addToItems_locked(Added, false, obj) return retErr @@ -191,6 +351,7 @@ func (f *RealFIFO) Update(obj interface{}) error { defer f.lock.Unlock() f.populated = true + f.checkSynced_locked() retErr := f.addToItems_locked(Updated, false, obj) return retErr @@ -204,6 +365,7 @@ func (f *RealFIFO) Delete(obj interface{}) error { defer f.lock.Unlock() f.populated = true + f.checkSynced_locked() retErr := f.addToItems_locked(Deleted, false, obj) return retErr @@ -217,10 +379,14 @@ func (f *RealFIFO) IsClosed() bool { } // Pop waits until an item is ready and processes it. If multiple items are -// ready, they are returned in the order in which they were added/updated. -// The item is removed from the queue (and the store) before it is processed. -// process function is called under lock, so it is safe -// update data structures in it that need to be in sync with the queue. +// ready, they are returned in the order in which they were added/updated. The +// item is removed from the queue (and the store) before it is processed. The +// process function is only guaranteed to be called under lock if +// UnlockWhileProcessing is false. If the process function is updating data +// structures that need to be in sync with the queue, ensure +// UnlockWhileProcessing is false. It is expected that the caller of Pop will be +// a single threaded consumer since otherwise it is possible for multiple +// PopProcessFuncs to be running simultaneously. func (f *RealFIFO) Pop(process PopProcessFunc) (interface{}, error) { f.lock.Lock() defer f.lock.Unlock() @@ -241,9 +407,15 @@ func (f *RealFIFO) Pop(process PopProcessFunc) (interface{}, error) { // The underlying array still exists and references this object, so the object will not be garbage collected unless we zero the reference. f.items[0] = Delta{} f.items = f.items[1:] - if f.initialPopulationCount > 0 { - f.initialPopulationCount-- - } + // Decrement initialPopulationCount if needed. + // This is done in a defer so we only do this *after* processing is complete, + // so concurrent calls to hasSynced will not incorrectly return true while processing is still happening. + defer func() { + if f.initialPopulationCount > 0 { + f.initialPopulationCount-- + f.checkSynced_locked() + } + }() // Only log traces if the queue depth is greater than 10 and it takes more than // 100 milliseconds to process one item from the queue. @@ -252,19 +424,66 @@ func (f *RealFIFO) Pop(process PopProcessFunc) (interface{}, error) { // https://github.com/kubernetes/kubernetes/issues/103789 if len(f.items) > 10 { id, _ := f.keyOf(item) - trace := utiltrace.New("RealFIFO Pop Process", - utiltrace.Field{Key: "ID", Value: id}, - utiltrace.Field{Key: "Depth", Value: len(f.items)}, - utiltrace.Field{Key: "Reason", Value: "slow event handlers blocking the queue"}) + fields := []utiltrace.Field{ + {Key: "ID", Value: id}, + {Key: "Depth", Value: len(f.items)}, + {Key: "Reason", Value: "slow event handlers blocking the queue"}, + } + if name := f.identifier.Name(); len(name) > 0 { + fields = append(fields, utiltrace.Field{Key: "Name", Value: name}) + } + if gvr := f.identifier.GroupVersionResource(); !gvr.Empty() { + fields = append(fields, utiltrace.Field{Key: "Resource", Value: gvr}) + } + trace := utiltrace.New("RealFIFO Pop Process", fields...) defer trace.LogIfLong(100 * time.Millisecond) } + f.metrics.numberOfQueuedItem.Set(float64(len(f.items))) - // we wrap in Deltas here to be compatible with preview Pop functions and those interpreting the return value. - err := process(Deltas{item}, isInInitialList) + // Process the item, this may unlock the lock, and allow other goroutines to add items to the queue. + err := f.whileProcessing_locked(func() error { + // we wrap in Deltas here to be compatible with preview Pop functions and those interpreting the return value. + return process(Deltas{item}, isInInitialList) + }) return Deltas{item}, err } -func (f *RealFIFO) PopBatch(process ProcessBatchFunc) error { +// whileProcessing_locked calls the `process` function and records processing latency. +// The lock must be held before calling `whileProcessing_locked`, and is held when `whileProcessing_locked` returns. +// whileProcessing_locked releases the lock during the call to `process` if f.unlockWhileProcessing is true and the f.items queue is not too long. +func (f *RealFIFO) whileProcessing_locked(process func() error) error { + // Unlock before calling `process` so new items can be enqueued during processing. + // Only do this if the queue contains less than 2 full batches of items, + // to prevent the queue from growing unboundedly. + if f.unlockWhileProcessing && len(f.items) < f.batchSize*2 { + f.lock.Unlock() + defer f.lock.Lock() + } + startTime := time.Now() + err := process() + f.metrics.processingLatency.Observe(time.Since(startTime).Seconds()) + return err +} + +// batchable stores the delta types that can be batched +var batchable = map[DeltaType]bool{ + Sync: true, + Replaced: true, + Added: true, + Updated: true, + Deleted: true, +} + +// PopBatch pops as many items as possible to be processed as a batch using processBatch, +// or pop a single item using processSingle if multiple items cannot be batched. +// +// The processBatch and processSingle functions are only guaranteed to be called +// under lock if UnlockWhileProcessing is false. If the process functions are +// updating data structures that need to be in sync with the queue, ensure +// UnlockWhileProcessing is false. It is expected that the caller of PopBatch +// will be a single threaded consumer, since otherwise it is possible for +// multiple ProcessBatchFunc or PopProcessFunc's to be running simultaneously. +func (f *RealFIFO) PopBatch(processBatch ProcessBatchFunc, processSingle PopProcessFunc) error { f.lock.Lock() defer f.lock.Unlock() @@ -282,12 +501,25 @@ func (f *RealFIFO) PopBatch(process ProcessBatchFunc) error { isInInitialList := !f.hasSynced_locked() unique := sets.NewString() deltas := make([]Delta, 0, min(len(f.items), f.batchSize)) + moveDeltaToProcessList := func(i int) { + deltas = append(deltas, f.items[i]) + // The underlying array still exists and references this object, so the object will not be garbage collected unless we zero the reference. + f.items[i] = Delta{} + } // only bundle unique items into a batch for i := 0; i < f.batchSize && i < len(f.items); i++ { if f.initialPopulationCount > 0 && i >= f.initialPopulationCount { break } item := f.items[i] + if !batchable[item.Type] { + if len(deltas) == 0 { + // if an unbatchable delta is first in the list, process just that one by itself + moveDeltaToProcessList(i) + } + // close the batch when an unbatchable delta is encountered + break + } id, err := f.keyOf(item) if err != nil { // close the batch here if error happens @@ -295,23 +527,26 @@ func (f *RealFIFO) PopBatch(process ProcessBatchFunc) error { // still pop the broken item out of queue to be compatible with the non-batch behavior it should be safe // when 1st element is broken, however for Nth broken element, there's possible risk that broken item // still can be processed and broke the uniqueness of the batch unexpectedly. - deltas = append(deltas, item) - // The underlying array still exists and references this object, so the object will not be garbage collected unless we zero the reference. - f.items[i] = Delta{} + moveDeltaToProcessList(i) break } if unique.Has(id) { + // close the batch if a duplicate item is encountered break } unique.Insert(id) - deltas = append(deltas, item) - // The underlying array still exists and references this object, so the object will not be garbage collected unless we zero the reference. - f.items[i] = Delta{} - } - if f.initialPopulationCount > 0 { - f.initialPopulationCount -= len(deltas) + moveDeltaToProcessList(i) } f.items = f.items[len(deltas):] + // Decrement initialPopulationCount if needed. + // This is done in a defer so we only do this *after* processing is complete, + // so concurrent calls to hasSynced will not incorrectly return true while processing is still happening. + defer func() { + if f.initialPopulationCount > 0 { + f.initialPopulationCount -= len(deltas) + f.checkSynced_locked() + } + }() // Only log traces if the queue depth is greater than 10 and it takes more than // 100 milliseconds to process one item from the queue (with a max of 1 second for the whole batch) @@ -320,16 +555,46 @@ func (f *RealFIFO) PopBatch(process ProcessBatchFunc) error { // https://github.com/kubernetes/kubernetes/issues/103789 if len(f.items) > 10 { id, _ := f.keyOf(deltas[0]) - trace := utiltrace.New("RealFIFO PopBatch Process", - utiltrace.Field{Key: "ID", Value: id}, - utiltrace.Field{Key: "Depth", Value: len(f.items)}, - utiltrace.Field{Key: "Reason", Value: "slow event handlers blocking the queue"}, - utiltrace.Field{Key: "BatchSize", Value: len(deltas)}) + fields := []utiltrace.Field{ + {Key: "ID", Value: id}, + {Key: "Depth", Value: len(f.items)}, + {Key: "Reason", Value: "slow event handlers blocking the queue"}, + {Key: "BatchSize", Value: len(deltas)}, + } + if name := f.identifier.Name(); len(name) > 0 { + fields = append(fields, utiltrace.Field{Key: "Name", Value: name}) + } + if gvr := f.identifier.GroupVersionResource(); !gvr.Empty() { + fields = append(fields, utiltrace.Field{Key: "Resource", Value: gvr}) + } + trace := utiltrace.New("RealFIFO PopBatch Process", fields...) defer trace.LogIfLong(min(100*time.Millisecond*time.Duration(len(deltas)), time.Second)) } + f.metrics.numberOfQueuedItem.Set(float64(len(f.items))) - err := process(deltas, isInInitialList) - return err + if len(deltas) == 1 { + return f.whileProcessing_locked(func() error { + return processSingle(Deltas{deltas[0]}, isInInitialList) + }) + } + return f.whileProcessing_locked(func() error { + return processBatch(deltas, isInInitialList) + }) +} + +func (f *RealFIFO) Bookmark(resourceVersion string) error { + if !f.emitDeltaTypeBookmark { + return nil + } + f.lock.Lock() + defer f.lock.Unlock() + + f.items = append(f.items, Delta{ + Type: Bookmark, + Object: BookmarkInfo{ResourceVersion: resourceVersion}, + }) + f.cond.Broadcast() + return nil } // Replace @@ -340,22 +605,58 @@ func (f *RealFIFO) Replace(newItems []interface{}, resourceVersion string) error f.lock.Lock() defer f.lock.Unlock() + var err error + if f.emitAtomicEvents { + err = f.addReplaceToItemsLocked(newItems, resourceVersion) + } else { + err = reconcileReplacement(f.logger, f.items, f.knownObjects, newItems, f.keyOf, + func(obj DeletedFinalStateUnknown) error { + return f.addToItems_locked(Deleted, true, obj) + }, + func(obj interface{}) error { + return f.addToItems_locked(Replaced, false, obj) + }) + } + if err != nil { + return err + } + + if !f.populated { + f.populated = true + f.initialPopulationCount = len(f.items) + f.checkSynced_locked() + } + + return nil +} + +// reconcileReplacement takes the items that are already in the queue and the set of new items +// and based upon the state of the items in the queue and known objects will call onDelete and onReplace +// depending upon whether the item is being deleted or replaced/added. +func reconcileReplacement( + logger klog.Logger, + queuedItems []Delta, + knownObjects KeyListerGetter, + newItems []interface{}, + keyOf func(obj interface{}) (string, error), + onDelete func(obj DeletedFinalStateUnknown) error, + onReplace func(obj interface{}) error, +) error { // determine the keys of everything we're adding. We cannot add the items until after the synthetic deletes have been // created for items that don't existing in newItems newKeys := sets.Set[string]{} for _, obj := range newItems { - key, err := f.keyOf(obj) + key, err := keyOf(obj) if err != nil { return KeyError{obj, err} } newKeys.Insert(key) } - queuedItems := f.items queuedKeys := []string{} lastQueuedItemForKey := map[string]Delta{} for _, queuedItem := range queuedItems { - queuedKey, err := f.keyOf(queuedItem.Object) + queuedKey, err := keyOf(queuedItem.Object) if err != nil { return KeyError{queuedItem.Object, err} } @@ -387,7 +688,7 @@ func (f *RealFIFO) Replace(newItems []interface{}, resourceVersion string) error // if we got here, then the last entry we have for the queued item is *not* a deletion and we need to add a delete deletedObj := lastQueuedItem.Object - retErr := f.addToItems_locked(Deleted, true, DeletedFinalStateUnknown{ + retErr := onDelete(DeletedFinalStateUnknown{ Key: queuedKey, Obj: deletedObj, }) @@ -397,7 +698,7 @@ func (f *RealFIFO) Replace(newItems []interface{}, resourceVersion string) error } // Detect deletions for objects not present in the queue, but present in KnownObjects - knownKeys := f.knownObjects.ListKeys() + knownKeys := knownObjects.ListKeys() for _, knownKey := range knownKeys { if newKeys.Has(knownKey) { // still present continue @@ -406,15 +707,15 @@ func (f *RealFIFO) Replace(newItems []interface{}, resourceVersion string) error continue } - deletedObj, exists, err := f.knownObjects.GetByKey(knownKey) + deletedObj, exists, err := knownObjects.GetByKey(knownKey) if err != nil { deletedObj = nil - utilruntime.HandleError(fmt.Errorf("error during lookup, placing DeleteFinalStateUnknown marker without object: key=%q, err=%w", knownKey, err)) + utilruntime.HandleErrorWithLogger(logger, err, "Error during lookup, placing DeleteFinalStateUnknown marker without object", "key", knownKey) } else if !exists { deletedObj = nil - utilruntime.HandleError(fmt.Errorf("key does not exist in known objects store, placing DeleteFinalStateUnknown marker without object: key=%q", knownKey)) + utilruntime.HandleErrorWithLogger(logger, nil, "Key does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", "key", knownKey) } - retErr := f.addToItems_locked(Deleted, false, DeletedFinalStateUnknown{ + retErr := onDelete(DeletedFinalStateUnknown{ Key: knownKey, Obj: deletedObj, }) @@ -425,17 +726,11 @@ func (f *RealFIFO) Replace(newItems []interface{}, resourceVersion string) error // now that we have the deletes we need for items, we can add the newItems to the items queue for _, obj := range newItems { - retErr := f.addToItems_locked(Replaced, false, obj) - if retErr != nil { - return fmt.Errorf("couldn't enqueue object: %w", retErr) + if err := onReplace(obj); err != nil { + return fmt.Errorf("couldn't enqueue object: %w", err) } } - if !f.populated { - f.populated = true - f.initialPopulationCount = len(f.items) - } - return nil } @@ -446,6 +741,10 @@ func (f *RealFIFO) Resync() error { f.lock.Lock() defer f.lock.Unlock() + if f.emitAtomicEvents { + return f.addResyncToItemsLocked() + } + if f.knownObjects == nil { return nil } @@ -471,10 +770,10 @@ func (f *RealFIFO) Resync() error { knownObj, exists, err := f.knownObjects.GetByKey(knownKey) if err != nil { - utilruntime.HandleError(fmt.Errorf("unable to queue object for sync: key=%q, err=%w", knownKey, err)) + utilruntime.HandleErrorWithLogger(f.logger, err, "Unable to queue object for sync", "key", knownKey) continue } else if !exists { - utilruntime.HandleError(fmt.Errorf("key does not exist in known objects store, unable to queue object for sync: key=%q", knownKey)) + utilruntime.HandleErrorWithLogger(f.logger, nil, "Key does not exist in known objects store, unable to queue object for sync", "key", knownKey) continue } @@ -494,6 +793,8 @@ func (f *RealFIFO) Transformer() TransformFunc { // NewRealFIFO returns a Store which can be used to queue up items to // process. +// +// Deprecated: Use NewRealFIFOWithOptions instead. func NewRealFIFO(keyFunc KeyFunc, knownObjects KeyListerGetter, transformer TransformFunc) *RealFIFO { return NewRealFIFOWithOptions(RealFIFOOptions{ KeyFunction: keyFunc, @@ -509,18 +810,50 @@ func NewRealFIFOWithOptions(opts RealFIFOOptions) *RealFIFO { opts.KeyFunction = MetaNamespaceKeyFunc } - if opts.KnownObjects == nil { - panic("coding error: knownObjects must be provided") + if opts.AtomicEvents { + // If we are emitting atomic events, we must not rely on the known objects store + // as it is a requirement to be able to release the lock while processing events. + if opts.KnownObjects != nil { + panic("coding error: knownObjects must not be provided when AtomicEvents is true") + } + } else { + if opts.UnlockWhileProcessing { + panic("coding error: UnlockWhileProcessing must be false when AtomicEvents is false") + } + if opts.KnownObjects == nil { + panic("coding error: knownObjects must be provided when AtomicEvents is false") + } + // If we are not emitting atomic events, we must not emit bookmark deltas. + if opts.EmitDeltaTypeBookmark { + panic("coding error: EmitDeltaTypeBookmark must be false when AtomicEvents is false") + } } f := &RealFIFO{ - items: make([]Delta, 0, 10), - keyFunc: opts.KeyFunction, - knownObjects: opts.KnownObjects, - transformer: opts.Transformer, - batchSize: defaultBatchSize, + logger: klog.Background(), + name: "RealFIFO", + items: make([]Delta, 0, 10), + synced: make(chan struct{}), + keyFunc: opts.KeyFunction, + knownObjects: opts.KnownObjects, + transformer: opts.Transformer, + batchSize: defaultBatchSize, + emitAtomicEvents: opts.AtomicEvents, + emitDeltaTypeBookmark: opts.EmitDeltaTypeBookmark, + unlockWhileProcessing: opts.UnlockWhileProcessing, + identifier: opts.Identifier, + metrics: newFIFOMetrics(opts.Identifier, opts.MetricsProvider), } - + if opts.Logger != nil { + f.logger = *opts.Logger + } + if name := opts.Name; name != "" { + f.name = name + } + if name := opts.Identifier.Name(); name != "" { + f.name = name + } + f.logger = klog.LoggerWithName(f.logger, f.name) f.cond.L = &f.lock return f } diff --git a/staging/src/k8s.io/client-go/tools/cache/the_real_fifo_test.go b/staging/src/k8s.io/client-go/tools/cache/the_real_fifo_test.go index bfd3bcfb9282e..c6a1710cd240a 100644 --- a/staging/src/k8s.io/client-go/tools/cache/the_real_fifo_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/the_real_fifo_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/stretchr/testify/assert" + clientfeatures "k8s.io/client-go/features" clientfeaturestesting "k8s.io/client-go/features/testing" ) @@ -39,6 +40,8 @@ func (f *RealFIFO) getItems() []Delta { } const closedFIFOName = "FIFO WAS CLOSED" +const isAtomic = "ATOMIC REPLACED OBJ" +const isBookmark = "BOOKMARK OBJ" func popN(queue Queue, count int) []interface{} { result := []interface{}{} @@ -57,6 +60,16 @@ func testRealFIFOPop(f *RealFIFO) testFifoObject { if val == nil { return testFifoObject{name: closedFIFOName} } + if val.(Deltas).Newest().Type == ReplacedAll { + var objs []testFifoObject + for _, obj := range val.(Deltas).Newest().Object.(ReplacedAllInfo).Objects { + objs = append(objs, obj.(testFifoObject)) + } + return testFifoObject{name: isAtomic, val: objs} + } + if val.(Deltas).Newest().Type == Bookmark { + return testFifoObject{name: isBookmark} + } return val.(Deltas).Newest().Object.(testFifoObject) } @@ -222,10 +235,17 @@ func TestRealFIFOW_ReplaceMakesDeletionsForObjectsOnlyInQueue(t *testing.T) { {Deleted, DeletedFinalStateUnknown{Key: "foo", Obj: objV2}}, }, }, + { + name: "Bookmark object should not be added without atomic", + operations: func(f *RealFIFO) { + f.bookmarkTest(t, "123") + f.replaceTest(t, []interface{}{}, "0") + f.replaceTest(t, []interface{}{}, "1") + }, + expectedDeltas: Deltas{}, + }, } for _, tt := range table { - tt := tt - t.Run(tt.name, func(t *testing.T) { // Test with a RealFIFO with a backing KnownObjects fWithKnownObjects := NewRealFIFO( @@ -1122,7 +1142,7 @@ func TestRealFIFO_PopMultipleDeltaInBatch(t *testing.T) { for i, item := range tc.initialItems { initialItems[i] = item } - _ = f.Replace(initialItems, "") + _ = f.Replace(initialItems, "123") for _, action := range tc.actions { action(f) } @@ -1141,7 +1161,13 @@ func TestRealFIFO_PopMultipleDeltaInBatch(t *testing.T) { receivedInitial <- obj } return nil - }) + }, PopProcessFunc(func(obj interface{}, isInInitialList bool) error { + received <- []Delta{*obj.(Deltas).Newest()} + if isInInitialList { + receivedInitial <- []Delta{*obj.(Deltas).Newest()} + } + return nil + })) }() timer := time.NewTimer(time.Millisecond * 50) select { @@ -1265,7 +1291,10 @@ func TestRealFIFO_PopBrokenItemsInBatch(t *testing.T) { _ = f.PopBatch(func(obj []Delta, isInInitialList bool) error { received <- obj return nil - }) + }, PopProcessFunc(func(obj interface{}, isInInitialList bool) error { + received <- []Delta{*obj.(Deltas).Newest()} + return nil + })) }() timer := time.NewTimer(time.Millisecond * 50) select { @@ -1289,3 +1318,228 @@ func TestRealFIFO_PopBrokenItemsInBatch(t *testing.T) { }) } } + +func TestRealFIFO_ReplaceAtomic(t *testing.T) { + obj := mkFifoObj("foo", 2) + table := []struct { + name string + operations func(f *RealFIFO) + expectedDeltas Deltas + }{ + { + name: "Base replace", + operations: func(f *RealFIFO) { + f.replaceTest(t, []interface{}{}, "123") + }, + expectedDeltas: Deltas{ + {Type: ReplacedAll, Object: ReplacedAllInfo{ + ResourceVersion: "123", + Objects: []interface{}{}, + }}, + }, + }, + { + name: "Added object exists in deltas on Atomic Replace", + operations: func(f *RealFIFO) { + f.addTest(t, obj) + f.replaceTest(t, []interface{}{}, "123") + }, + expectedDeltas: Deltas{ + {Type: Added, Object: obj}, + {Type: ReplacedAll, Object: ReplacedAllInfo{ + ResourceVersion: "123", + Objects: []interface{}{}, + }}, + }, + }, + { + name: "Multiple replaces run in order", + operations: func(f *RealFIFO) { + f.addTest(t, obj) + f.replaceTest(t, []interface{}{obj}, "10") + f.replaceTest(t, []interface{}{obj}, "20") + f.replaceTest(t, []interface{}{}, "56") + }, + expectedDeltas: Deltas{ + {Type: Added, Object: obj}, + {Type: ReplacedAll, Object: ReplacedAllInfo{ + ResourceVersion: "10", + Objects: []interface{}{obj}, + }}, + {Type: ReplacedAll, Object: ReplacedAllInfo{ + ResourceVersion: "20", + Objects: []interface{}{obj}, + }}, + {Type: ReplacedAll, Object: ReplacedAllInfo{ + ResourceVersion: "56", + Objects: []interface{}{}, + }}, + }, + }, + { + name: "Bookmark object should not be included in Replace", + operations: func(f *RealFIFO) { + f.bookmarkTest(t, "123") + f.replaceTest(t, []interface{}{}, "1234") + }, + expectedDeltas: Deltas{ + {Type: Bookmark, Object: BookmarkInfo{ResourceVersion: "123"}}, + {Type: ReplacedAll, Object: ReplacedAllInfo{ + ResourceVersion: "1234", + Objects: []interface{}{}, + }}, + }, + }, + } + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + // Test with a RealFIFO with a backing KnownObjects + f := NewRealFIFO( + testFifoObjectKeyFunc, + literalListerGetter(func() []testFifoObject { + return []testFifoObject{} + }), + nil, + ) + f.emitAtomicEvents = true + f.emitDeltaTypeBookmark = true + tt.operations(f) + actualDeltasWithKnownObjects := popN(f, len(f.getItems())) + actualAsDeltas := collapseDeltas(actualDeltasWithKnownObjects) + if !reflect.DeepEqual(tt.expectedDeltas, actualAsDeltas) { + t.Errorf("expected %#v, got %#v", tt.expectedDeltas, actualAsDeltas) + } + if len(f.items) != 0 { + t.Errorf("expected no extra deltas (empty map), got %#v", f.items) + } + + }) + } +} + +func TestRealFIFO_ReplaceAtomicPop(t *testing.T) { + f := NewRealFIFO( + testFifoObjectKeyFunc, + emptyKnownObjects(), + nil, + ) + f.emitAtomicEvents = true + f.addTest(t, mkFifoObj("foo", 10)) + f.replaceTest(t, []interface{}{mkFifoObj("foo", 15)}, "20") + got := make(chan testFifoObject, 3) + done := make(chan struct{}) + go func() { + defer close(done) + for { + obj := testRealFIFOPop(f) + if obj.name == closedFIFOName { + break + } + got <- obj + } + }() + + added := <-got + if e, a, v := "foo", added.name, added.val.(int); e != a || v != 10 { + t.Errorf("Didn't get expected name (%v vs %v) or value (%v vs %v)", e, a, v, 10) + } + + curr := <-got + if e, a, v := isAtomic, curr.name, curr.val.([]testFifoObject)[0]; e != a && v.val == 15 { + t.Errorf("Didn't get updated value (%v), got %v", e, a) + } + + select { + case unexpected := <-got: + t.Errorf("Got second value %v", unexpected.val) + case <-time.After(50 * time.Millisecond): + } + + if items := f.getItems(); len(items) > 0 { + t.Errorf("item did not get removed") + } + f.Close() + <-done +} + +func TestRealFIFO_ResyncAtomic(t *testing.T) { + obj := mkFifoObj("foo", 2) + table := []struct { + name string + operations func(f *RealFIFO) + expectedDeltas Deltas + }{ + { + name: "Base resync", + operations: func(f *RealFIFO) { + f.resyncTest(t) + }, + expectedDeltas: Deltas{ + {Type: SyncAll, Object: SyncAllInfo{}}, + }, + }, + { + name: "Added object exists in deltas on Atomic Resync", + operations: func(f *RealFIFO) { + f.addTest(t, obj) + f.resyncTest(t) + }, + expectedDeltas: Deltas{ + {Type: Added, Object: obj}, + {Type: SyncAll, Object: SyncAllInfo{}}, + }, + }, + } + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + // Test with a RealFIFO with a backing KnownObjects + f := NewRealFIFO( + testFifoObjectKeyFunc, + literalListerGetter(func() []testFifoObject { + return []testFifoObject{} + }), + nil, + ) + f.emitAtomicEvents = true + tt.operations(f) + actualDeltasWithKnownObjects := popN(f, len(f.getItems())) + actualAsDeltas := collapseDeltas(actualDeltasWithKnownObjects) + + // Resync appends to the list, so we enable checking preserving the order + if !reflect.DeepEqual(tt.expectedDeltas, actualAsDeltas) { + t.Errorf("expected %#v, got %#v", tt.expectedDeltas, actualAsDeltas) + } + if len(f.items) != 0 { + t.Errorf("expected no extra deltas (empty map), got %#v", f.items) + } + }) + } +} + +func (f *RealFIFO) addTest(t *testing.T, obj interface{}) { + err := f.Add(obj) + if err != nil { + t.Fatalf("Test error on RealFIFO add: %s", err) + } +} + +func (f *RealFIFO) replaceTest(t *testing.T, objs []interface{}, rv string) { + err := f.Replace(objs, rv) + if err != nil { + t.Fatalf("Test error on RealFIFO replace: %s", err) + } +} + +func (f *RealFIFO) resyncTest(t *testing.T) { + err := f.Resync() + if err != nil { + t.Fatalf("Test error on RealFIFO resync: %s", err) + } +} + +func (f *RealFIFO) bookmarkTest(t *testing.T, bookmark string) { + err := f.Bookmark(bookmark) + if err != nil { + t.Fatalf("Test error on RealFIFO bookmark: %s", err) + } +} diff --git a/staging/src/k8s.io/client-go/tools/cache/thread_safe_store.go b/staging/src/k8s.io/client-go/tools/cache/thread_safe_store.go index ef3a599a9b084..74ac8f1ab26a0 100644 --- a/staging/src/k8s.io/client-go/tools/cache/thread_safe_store.go +++ b/staging/src/k8s.io/client-go/tools/cache/thread_safe_store.go @@ -18,10 +18,13 @@ package cache import ( "fmt" + "strconv" "sync" "time" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/util/sets" + clientgofeaturegate "k8s.io/client-go/features" utiltrace "k8s.io/utils/trace" ) @@ -43,13 +46,19 @@ import ( type ThreadSafeStore interface { Add(key string, obj interface{}) Update(key string, obj interface{}) + // Delete is equivalent to calling DeleteWithObject(key, nil) however it is + // not recommended to use this function as it will not update the resource + // version of the store, possibly causing it to be out of date. Delete(key string) + DeleteWithObject(key string, obj interface{}) Get(key string) (item interface{}, exists bool) List() []interface{} ListKeys() []string Replace(map[string]interface{}, string) Index(indexName string, obj interface{}) ([]interface{}, error) IndexKeys(indexName, indexedValue string) ([]string, error) + Bookmark(rv string) + LastStoreSyncResourceVersion() string ListIndexFuncValues(name string) []string ByIndex(indexName, indexedValue string) ([]interface{}, error) GetIndexers() Indexers @@ -73,6 +82,14 @@ type ThreadSafeStoreTransaction struct { Key string } +type ThreadSafeStoreOption = func(*threadSafeMap) + +func WithThreadSafeStoreMetrics(identifier InformerNameAndResource, metricsProvider InformerMetricsProvider) ThreadSafeStoreOption { + return func(c *threadSafeMap) { + c.metrics = newStoreMetrics(identifier, metricsProvider) + } +} + // storeIndex implements the indexing functionality for Store interface type storeIndex struct { // indexers maps a name to an IndexFunc @@ -242,9 +259,20 @@ type threadSafeMap struct { // index implements the indexing functionality index *storeIndex + rv string + + // metrics is used to expose metrics about the store + // and must be non-nil. If not provided, a noop implementation will be used. + metrics *storeMetrics } func (c *threadSafeMap) Transaction(txns ...ThreadSafeStoreTransaction) { + if len(txns) == 0 { + return + } + finalObj := txns[len(txns)-1].Object + rv, rvErr := rvFromObject(finalObj) + rvInt, parseErr := parseRVForMetricsWithTruncation(rv) c.lock.Lock() defer c.lock.Unlock() trace := utiltrace.New("ThreadSafeMap Transaction Process", @@ -262,6 +290,12 @@ func (c *threadSafeMap) Transaction(txns ...ThreadSafeStoreTransaction) { c.deleteLocked(txn.Key) } } + if rvErr == nil { + c.rv = rv + if parseErr == nil { + c.metrics.storeResourceVersion.Set(float64(rvInt)) + } + } } func (c *threadSafeMap) Add(key string, obj interface{}) { @@ -273,9 +307,17 @@ func (c *threadSafeMap) addLocked(key string, obj interface{}) { } func (c *threadSafeMap) Update(key string, obj interface{}) { + rv, rvErr := rvFromObject(obj) + rvInt, parseErr := parseRVForMetricsWithTruncation(rv) c.lock.Lock() defer c.lock.Unlock() c.updateLocked(key, obj) + if rvErr == nil { + c.rv = rv + if parseErr == nil { + c.metrics.storeResourceVersion.Set(float64(rvInt)) + } + } } func (c *threadSafeMap) updateLocked(key string, obj interface{}) { @@ -285,9 +327,26 @@ func (c *threadSafeMap) updateLocked(key string, obj interface{}) { } func (c *threadSafeMap) Delete(key string) { + c.DeleteWithObject(key, nil) +} + +func (c *threadSafeMap) DeleteWithObject(key string, obj interface{}) { + var rv string + var rvInt int64 + var rvErr, parseErr error + if obj != nil { + rv, rvErr = rvFromObject(obj) + rvInt, parseErr = parseRVForMetricsWithTruncation(rv) + } c.lock.Lock() defer c.lock.Unlock() c.deleteLocked(key) + if obj != nil && rvErr == nil { + c.rv = rv + if parseErr == nil { + c.metrics.storeResourceVersion.Set(float64(rvInt)) + } + } } func (c *threadSafeMap) deleteLocked(key string) { @@ -327,10 +386,18 @@ func (c *threadSafeMap) ListKeys() []string { } func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) { + var rvInt int64 + var parseErr error + if resourceVersion != "" { + rvInt, parseErr = parseRVForMetricsWithTruncation(resourceVersion) + } c.lock.Lock() defer c.lock.Unlock() c.items = items - + c.rv = resourceVersion + if parseErr == nil { + c.metrics.storeResourceVersion.Set(float64(rvInt)) + } // rebuild any index c.index.reset() for key, item := range c.items { @@ -338,6 +405,15 @@ func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion st } } +func rvFromObject(obj interface{}) (rv string, err error) { + meta, err := meta.Accessor(obj) + if err != nil { + return "", err + } + rv = meta.GetResourceVersion() + return rv, nil +} + // Index returns a list of items that match the given object on the index function. // Index is thread-safe so long as you treat all items as immutable. func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) { @@ -356,6 +432,32 @@ func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, return list, nil } +// LastStoreSyncResourceVersion returns the latest resource version that the store has seen. +func (c *threadSafeMap) LastStoreSyncResourceVersion() string { + // We cannot return the resource version if the AtomicFIFO feature gate is not enabled. + if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.AtomicFIFO) { + return "" + } + c.lock.RLock() + defer c.lock.RUnlock() + return c.rv +} + +// Bookmark sets the latest resource version that the store has seen. +func (c *threadSafeMap) Bookmark(rv string) { + var rvInt int64 + var parseErr error + if rv != "" { + rvInt, parseErr = parseRVForMetricsWithTruncation(rv) + } + c.lock.Lock() + defer c.lock.Unlock() + c.rv = rv + if parseErr == nil { + c.metrics.storeResourceVersion.Set(float64(rvInt)) + } +} + // ByIndex returns a list of the items whose indexed values in the given index include the given indexed value func (c *threadSafeMap) ByIndex(indexName, indexedValue string) ([]interface{}, error) { c.lock.RLock() @@ -420,13 +522,31 @@ func (c *threadSafeMap) Resync() error { return nil } -// NewThreadSafeStore creates a new instance of ThreadSafeStore. -func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore { - return &threadSafeMap{ +func NewThreadSafeStore(indexers Indexers, indices Indices, opts ...ThreadSafeStoreOption) ThreadSafeStore { + store := &threadSafeMap{ items: map[string]interface{}{}, index: &storeIndex{ indexers: indexers, indices: indices, }, } + for _, opt := range opts { + opt(store) + } + if store.metrics == nil { + store.metrics = newStoreMetrics(InformerNameAndResource{}, noopInformerMetricsProvider{}) + } + return store +} + +func parseRVForMetricsWithTruncation(rv string) (int64, error) { + if rv == "" { + return 0, nil + } + // Truncate to last 15 digits to ensure metrics are always less than 2^53-1 + // and avoid imprecise float64 representation. + if len(rv) > 15 { + rv = rv[len(rv)-15:] + } + return strconv.ParseInt(rv, 10, 64) } diff --git a/staging/src/k8s.io/client-go/tools/cache/thread_safe_store_test.go b/staging/src/k8s.io/client-go/tools/cache/thread_safe_store_test.go index 44d620e53c95f..8a9d9edb082ea 100644 --- a/staging/src/k8s.io/client-go/tools/cache/thread_safe_store_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/thread_safe_store_test.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" ) @@ -166,6 +167,124 @@ func TestThreadSafeStoreIndexingFunctionsWithMultipleValues(t *testing.T) { assert.NoError(compare("baz", []string{})) } +func TestThreadSafeStoreRV(t *testing.T) { + t.Run("Initial state", func(t *testing.T) { + store := NewThreadSafeStore(Indexers{}, Indices{}).(*threadSafeMap) + if rv := store.LastStoreSyncResourceVersion(); rv != "" { + t.Errorf("Expected initial RV to be \"\", got %q", rv) + } + }) + + t.Run("Add Update and Delete", func(t *testing.T) { + store := NewThreadSafeStore(Indexers{}, Indices{}).(*threadSafeMap) + + // Add obj with RV "10" + store.Add("key1", &metav1.ObjectMeta{ResourceVersion: "10"}) + if rv := store.LastStoreSyncResourceVersion(); rv != "10" { + t.Errorf("Expected RV to be \"10\", got %q", rv) + } + + // Add obj with same RV "10" + store.Add("key3", &metav1.ObjectMeta{ResourceVersion: "10"}) + if rv := store.LastStoreSyncResourceVersion(); rv != "10" { + t.Errorf("Expected RV to remain \"10\" after adding same RV, got %q", rv) + } + + // Add obj with higher RV "20" + store.Add("key4", &metav1.ObjectMeta{ResourceVersion: "20"}) + if rv := store.LastStoreSyncResourceVersion(); rv != "20" { + t.Errorf("Expected RV to be \"20\", got %q", rv) + } + + // Delete an earlier object with a newer RV + store.DeleteWithObject("key4", &metav1.ObjectMeta{ResourceVersion: "30"}) + if rv := store.LastStoreSyncResourceVersion(); rv != "30" { + t.Errorf("Expected RV to become \"30\" after deletion %q", rv) + } + + // Add non-meta object, should not be updated + store.Add("key5", "just a string") + if rv := store.LastStoreSyncResourceVersion(); rv != "30" { + t.Errorf("Expected RV to remain \"30\" after adding non-meta object, got %q", rv) + } + + // Add obj with RV "40" + store.Add("key6", &metav1.ObjectMeta{ResourceVersion: "40"}) + if rv := store.LastStoreSyncResourceVersion(); rv != "40" { + t.Errorf("Expected RV to be \"40\", got %q", rv) + } + + // Nil delete + store.Delete("key6") + if rv := store.LastStoreSyncResourceVersion(); rv != "40" { + t.Errorf("Expected RV to remain \"40\" after delete, got %q", rv) + } + + txns := []ThreadSafeStoreTransaction{ + { + Transaction{ + Object: &metav1.ObjectMeta{ResourceVersion: "40"}, + Type: TransactionTypeUpdate, + }, + "key9", + }, + { + Transaction{ + Object: &metav1.ObjectMeta{ResourceVersion: "30"}, + Type: TransactionTypeUpdate, + }, + "key10", + }, + { + Transaction{ + Object: &metav1.ObjectMeta{ResourceVersion: "50"}, + Type: TransactionTypeUpdate, + }, + "key11", + }, + } + store.Transaction(txns...) + if rv := store.LastStoreSyncResourceVersion(); rv != "50" { + t.Errorf("Expected RV to be \"50\" after transaction, got %q", rv) + } + }) + + t.Run("Replace", func(t *testing.T) { + store := NewThreadSafeStore(Indexers{}, Indices{}).(*threadSafeMap) + store.Add("key1", &metav1.ObjectMeta{ResourceVersion: "10"}) + + if rv := store.LastStoreSyncResourceVersion(); rv != "10" { + t.Fatalf("Setup failed, expected RV \"10\", got %q", rv) + } + + items := map[string]interface{}{ + "key3": &metav1.ObjectMeta{ResourceVersion: "40"}, + "key2": &metav1.ObjectMeta{ResourceVersion: "30"}, + } + + store.Replace(items, "50") + + if rv := store.LastStoreSyncResourceVersion(); rv != "50" { + t.Errorf("Expected RV to be \"50\" after Replace(), got %q", rv) + } + }) + + t.Run("Delete", func(t *testing.T) { + store := NewThreadSafeStore(Indexers{}, Indices{}).(*threadSafeMap) + store.Add("key1", &metav1.ObjectMeta{ResourceVersion: "10"}) + + if rv := store.LastStoreSyncResourceVersion(); rv != "10" { + t.Fatalf("Setup failed, expected RV \"10\", got %q", rv) + } + + store.DeleteWithObject("key1", &metav1.ObjectMeta{ResourceVersion: "20"}) + + if rv := store.LastStoreSyncResourceVersion(); rv != "20" { + t.Errorf("Expected RV to be \"20\" after Delete(), got %q", rv) + } + }) +} + func BenchmarkIndexer(b *testing.B) { testIndexer := "testIndexer" diff --git a/staging/src/k8s.io/client-go/tools/cache/wait_test.go b/staging/src/k8s.io/client-go/tools/cache/wait_test.go new file mode 100644 index 0000000000000..f03b48dd828e8 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/cache/wait_test.go @@ -0,0 +1,246 @@ +/* +Copyright 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 cache + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "reflect" + "runtime" + "sync" + "testing" + "testing/synctest" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/klog/v2" + "k8s.io/klog/v2/textlogger" +) + +func init() { + // The test below is sensitive to the time zone, log output uses time.Local. + time.Local = time.UTC +} + +func TestWaitFor(t *testing.T) { + for name, tc := range map[string]struct { + what string + checkers []DoneChecker + timeout time.Duration + timeoutReason string + + expectDone bool + + // Time is predictable and starts at the synctest epoch. + // %[1]d is the pid, %[2]d the line number of the WaitFor call. + expectOutput string + }{ + "empty": { + expectDone: true, + }, + "no-caches": { + what: "my-caches", + expectDone: true, + expectOutput: `I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Waiting" for="my-caches" +`, + }, + "no-logging": { + checkers: []DoneChecker{newMockChecker("first", 10*time.Second), newMockChecker("second", 5*time.Second), newMockChecker("last", 0*time.Second)}, + expectDone: true, + }, + "with-logging": { + what: "my-caches", + checkers: []DoneChecker{newMockChecker("first", 10*time.Second), newMockChecker("second", 5*time.Second), newMockChecker("last", 0*time.Second)}, + expectDone: true, + expectOutput: `I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Waiting" for="my-caches" +I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="last" +I0101 00:00:05.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="second" +I0101 00:00:10.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="first" +`, + }, + "some-timeout": { + timeout: time.Minute, + what: "my-caches", + checkers: []DoneChecker{newMockChecker("first", 10*time.Second), newMockChecker("second", -1), newMockChecker("last", 0*time.Second)}, + expectDone: false, + expectOutput: `I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Waiting" for="my-caches" +I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="last" +I0101 00:00:10.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="first" +I0101 00:01:00.000000 %7[1]d wait_test.go:%[2]d] "Timed out waiting" for="my-caches" cause="context deadline exceeded" instances=["second"] +`, + }, + "some-canceled": { + timeout: -1, + what: "my-caches", + checkers: []DoneChecker{newMockChecker("first", 10*time.Second), newMockChecker("second", -1), newMockChecker("last", 0*time.Second)}, + expectDone: false, + expectOutput: `I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Waiting" for="my-caches" +I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="last" +I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Timed out waiting" for="my-caches" cause="context canceled" instances=["first","second"] +`, + }, + "more": { + timeoutReason: "go fish", + timeout: 5 * time.Second, + what: "my-caches", + checkers: []DoneChecker{newMockChecker("first", 10*time.Second), newMockChecker("second", -1), newMockChecker("last", 0*time.Second)}, + expectDone: false, + expectOutput: `I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Waiting" for="my-caches" +I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Done waiting" for="my-caches" instance="last" +I0101 00:00:05.000000 %7[1]d wait_test.go:%[2]d] "Timed out waiting" for="my-caches" cause="go fish" instances=["first","second"] +`, + }, + "all": { + timeout: time.Minute, + what: "my-caches", + checkers: []DoneChecker{newMockChecker("first", -1), newMockChecker("second", -1), newMockChecker("last", -1)}, + expectDone: false, + expectOutput: `I0101 00:00:00.000000 %7[1]d wait_test.go:%[2]d] "Waiting" for="my-caches" +I0101 00:01:00.000000 %7[1]d wait_test.go:%[2]d] "Timed out waiting" for="my-caches" cause="context deadline exceeded" instances=["first","last","second"] +`, + }, + } { + t.Run(name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + var buffer bytes.Buffer + logger := textlogger.NewLogger(textlogger.NewConfig(textlogger.Output(&buffer))) + ctx := klog.NewContext(context.Background(), logger) + var wg sync.WaitGroup + defer wg.Wait() + if tc.timeout != 0 { + switch tc.timeoutReason { + case "": + if tc.timeout > 0 { + c, cancel := context.WithTimeout(ctx, tc.timeout) + defer cancel() + ctx = c + } else { + c, cancel := context.WithCancel(ctx) + cancel() + ctx = c + } + default: + c, cancel := context.WithCancelCause(ctx) + wg.Go(func() { + time.Sleep(tc.timeout) + cancel(errors.New(tc.timeoutReason)) + }) + ctx = c + } + } + _, _, line, _ := runtime.Caller(0) + done := WaitFor(ctx, tc.what, tc.checkers...) + expectOutput := tc.expectOutput + if expectOutput != "" { + expectOutput = fmt.Sprintf(expectOutput, os.Getpid(), line+1) + } + assert.Equal(t, tc.expectDone, done, "done") + assert.Equal(t, expectOutput, buffer.String(), "output") + }) + }) + } +} + +// newMockChecker can be created outside of a synctest bubble. +// It constructs the channel inside when Done is first called. +func newMockChecker(name string, delay time.Duration) DoneChecker { + return &mockChecker{ + name: name, + delay: delay, + } +} + +type mockChecker struct { + name string + delay time.Duration + initialized bool + done <-chan struct{} +} + +func (m *mockChecker) Name() string { return m.name } +func (m *mockChecker) Done() <-chan struct{} { + if !m.initialized { + switch { + case m.delay > 0: + // In the future. + ctx := context.Background() + // This leaks a cancel, but is hard to avoid (cannot use the parent t.Cleanup, no other way to delay calling it). Doesn't matter in a unit test. + //nolint:govet + ctx, _ = context.WithTimeout(ctx, m.delay) + m.done = ctx.Done() + case m.delay == 0: + // Immediately. + c := make(chan struct{}) + close(c) + m.done = c + default: + // Never. + c := make(chan struct{}) + m.done = c + } + m.initialized = true + } + return m.done +} + +func TestSyncResult(t *testing.T) { + for name, tc := range map[string]struct { + result SyncResult + expectAsError string + }{ + "empty": {}, + "one": { + result: SyncResult{ + Err: errors.New("my custom cancellation reason"), + Synced: map[reflect.Type]bool{ + reflect.TypeFor[int](): true, + reflect.TypeFor[string](): false, + }, + }, + expectAsError: "failed to sync all caches: string: my custom cancellation reason", + }, + "many": { + result: SyncResult{ + Err: errors.New("my custom cancellation reason"), + Synced: map[reflect.Type]bool{ + reflect.TypeFor[int](): false, + reflect.TypeFor[string](): false, + }, + }, + expectAsError: "failed to sync all caches: int, string: my custom cancellation reason", + }, + } { + + t.Run(name, func(t *testing.T) { + actual := tc.result.AsError() + switch { + case tc.expectAsError == "" && actual != nil: + t.Fatalf("expected no error, got %v", actual) + case tc.expectAsError != "" && actual == nil: + t.Fatalf("expected %q, got no error", actual) + case tc.expectAsError != "" && actual != nil && actual.Error() != tc.expectAsError: + t.Fatalf("expected %q, got %q", tc.expectAsError, actual.Error()) + } + if tc.result.Err != nil && !errors.Is(actual, tc.result.Err) { + t.Errorf("actual error %+v should wrap %v but doesn't", actual, tc.result.Err) + } + }) + } +} diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go b/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go index cb21c040a3bc5..66d0e9230bf68 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go @@ -298,12 +298,12 @@ type ExecConfig struct { // the logical AND of all checks corresponding to the specified fields within // the entry. type AllowlistEntry struct { - // Name matching is performed by first resolving the absolute path of both + // Command matching is performed by first resolving the absolute path of both // the plugin and the name in the allowlist entry using `exec.LookPath`. It // will be called on both, and the resulting strings must be equal. If - // either call to `exec.LookPath` results in an error, the `Name` check + // either call to `exec.LookPath` results in an error, the `Command` check // will be considered a failure. - Name string `json:"-"` + Command string `json:"-"` } // PluginPolicy describes the policy type and allowlist (if any) for client-go diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/defaults_test.go b/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/defaults_test.go index 03fdb04276fb8..5858121ceeefd 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/defaults_test.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/defaults_test.go @@ -72,7 +72,6 @@ func TestSetDefaults_Config(t *testing.T) { }, } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { gotOut := test.in.DeepCopy() SetDefaults_ExecConfig(gotOut) diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go b/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go index ed35891e5a1f9..efac2830bbb7e 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go @@ -679,11 +679,13 @@ func (config *inClusterClientConfig) Possible() bool { // to the default config. func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) { if kubeconfigPath == "" && masterUrl == "" { + //nolint:logcheck // A helper function like this should not log. But this is probably part of the the established client-go API and not worth changing. klog.Warning("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.") kubeconfig, err := restclient.InClusterConfig() if err == nil { return kubeconfig, nil } + //nolint:logcheck // A helper function like this should not log. But this is probably part of the the established client-go API and not worth changing. klog.Warning("error creating inClusterConfig, falling back to default config: ", err) } return NewNonInteractiveDeferredLoadingClientConfig( diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go b/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go index baa406f4e459c..5c74978e902ec 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go @@ -25,10 +25,10 @@ import ( "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/dump" restclient "k8s.io/client-go/rest" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" utiltesting "k8s.io/client-go/util/testing" + "k8s.io/utils/dump" "k8s.io/utils/ptr" ) diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/config.go b/staging/src/k8s.io/client-go/tools/clientcmd/config.go index 2cd213ccb3e26..828ac974dceee 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/config.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/config.go @@ -492,6 +492,7 @@ func getConfigFromFile(filename string) (*clientcmdapi.Config, error) { func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config { config, err := getConfigFromFile(filename) if err != nil { + //nolint:logcheck // A helper function like this should not log. But this is probably part of the the established client-go API and not worth changing. klog.FatalDepth(1, err) } diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/loader.go b/staging/src/k8s.io/client-go/tools/clientcmd/loader.go index b127e2e08f7f6..d1d0a82954c52 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/loader.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/loader.go @@ -137,6 +137,7 @@ type WarningHandler func(error) func (handler WarningHandler) Warn(err error) { if handler == nil { + //nolint:logcheck // This is the fallback when logging is not initialized. With nothing provided, using the global logger is the only option. klog.V(1).Info(err) } else { handler(err) @@ -402,6 +403,7 @@ func LoadFromFile(filename string) (*clientcmdapi.Config, error) { if err != nil { return nil, err } + //nolint:logcheck // A helper function like this should not log. But this is probably part of the the established client-go API and not worth changing. klog.V(6).Infoln("Config loaded from file: ", filename) // set LocationOfOrigin on every Cluster, User, and Context diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/loader_test.go b/staging/src/k8s.io/client-go/tools/clientcmd/loader_test.go index e9d78bbe85046..22006aa6c9b97 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/loader_test.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/loader_test.go @@ -122,6 +122,7 @@ func TestNonExistentCommandLineFile(t *testing.T) { } } +//nolint:logcheck // Tests klog APIs. func TestToleratingMissingFiles(t *testing.T) { envVarValue := "bogus" loadingRules := ClientConfigLoadingRules{ @@ -146,6 +147,7 @@ func TestToleratingMissingFiles(t *testing.T) { } } +//nolint:logcheck // Tests klog APIs. func TestWarningMissingFiles(t *testing.T) { envVarValue := "bogus" t.Setenv(RecommendedConfigPathEnvVar, envVarValue) @@ -171,6 +173,7 @@ func TestWarningMissingFiles(t *testing.T) { } } +//nolint:logcheck // Tests klog APIs. func TestNoWarningMissingFiles(t *testing.T) { envVarValue := "bogus" t.Setenv(RecommendedConfigPathEnvVar, envVarValue) diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/merged_client_builder.go b/staging/src/k8s.io/client-go/tools/clientcmd/merged_client_builder.go index 0fc2fd0a0cac1..db7bf0a82e1ff 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/merged_client_builder.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/merged_client_builder.go @@ -118,6 +118,7 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e // check for in-cluster configuration and use it if config.icc.Possible() { + //nolint:logcheck // A helper function like this should not log. But this is probably part of the the established client-go API and not worth changing. klog.V(4).Infof("Using in-cluster configuration") return config.icc.ClientConfig() } @@ -160,6 +161,7 @@ func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) { } } + //nolint:logcheck // A helper function like this should not log. But this is probably part of the the established client-go API and not worth changing. klog.V(4).Infof("Using in-cluster namespace") // allow the namespace from the service account token directory to be used. diff --git a/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection.go b/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection.go index 29d34c4e906f7..00798d82ac4b8 100644 --- a/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection.go +++ b/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection.go @@ -199,8 +199,8 @@ type LeaderElector struct { // clock is wrapper around time to allow for less flaky testing clock clock.Clock - // used to lock the observedRecord - observedRecordLock sync.Mutex + // used to lock the observedRecord and the observedTime + observedRecordLock sync.RWMutex metrics leaderMetricsAdapter } @@ -256,7 +256,7 @@ func (le *LeaderElector) acquire(ctx context.Context) bool { desc := le.config.Lock.Describe() logger := klog.FromContext(ctx) logger.Info("Attempting to acquire leader lease...", "lock", desc) - wait.JitterUntil(func() { + wait.JitterUntilWithContext(ctx, func(ctx context.Context) { if !le.config.Coordinated { succeeded = le.tryAcquireOrRenew(ctx) } else { @@ -271,7 +271,7 @@ func (le *LeaderElector) acquire(ctx context.Context) bool { le.metrics.leaderOn(le.config.Name) logger.Info("Successfully acquired lease", "lock", desc) cancel() - }, le.config.RetryPeriod, JitterFactor, true, ctx.Done()) + }, le.config.RetryPeriod, JitterFactor, true) return succeeded } @@ -281,8 +281,13 @@ func (le *LeaderElector) renew(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel() logger := klog.FromContext(ctx) - wait.Until(func() { + wait.UntilWithContext(ctx, func(ctx context.Context) { err := wait.PollUntilContextTimeout(ctx, le.config.RetryPeriod, le.config.RenewDeadline, true, func(ctx context.Context) (done bool, err error) { + // PollUntilContextTimeout invokes condition even when the context is canceled when immediate=true. + // Short-circuit this to prevent unnecessary processing and error log messages. + if err := ctx.Err(); err != nil { + return false, err + } if !le.config.Coordinated { return le.tryAcquireOrRenew(ctx), nil } else { @@ -298,7 +303,7 @@ func (le *LeaderElector) renew(ctx context.Context) { le.metrics.leaderOff(le.config.Name) logger.Info("Failed to renew lease", "lock", desc, "err", err) cancel() - }, le.config.RetryPeriod, ctx.Done()) + }, le.config.RetryPeriod) // if we hold the lease, give it up if le.config.ReleaseOnCancel { @@ -372,7 +377,11 @@ func (le *LeaderElector) tryCoordinatedRenew(ctx context.Context) bool { le.observedRawRecord = oldLeaderElectionRawRecord } - hasExpired := le.observedTime.Add(time.Second * time.Duration(oldLeaderElectionRecord.LeaseDurationSeconds)).Before(now.Time) + le.observedRecordLock.RLock() + obsTime := le.observedTime + le.observedRecordLock.RUnlock() + + hasExpired := obsTime.Add(time.Second * time.Duration(oldLeaderElectionRecord.LeaseDurationSeconds)).Before(now.Time) if hasExpired { logger.Info("Lease has expired", "lock", le.config.Lock.Describe()) return false @@ -442,7 +451,7 @@ func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool { le.setObservedRecord(&leaderElectionRecord) return true } - logger.Error(err, "Failed to update lease optimistically, falling back to slow path", "lock", le.config.Lock.Describe()) + logger.V(2).Info("Failed to update lease optimistically, falling back to slow path", "lock", le.config.Lock.Describe(), "err", err) } // 2. obtain or create the ElectionRecord @@ -512,7 +521,12 @@ func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error { // If we are more than timeout seconds after the lease duration that is past the timeout // on the lease renew. Time to start reporting ourselves as unhealthy. We should have // died but conditions like deadlock can prevent this. (See #70819) - if le.clock.Since(le.observedTime) > le.config.LeaseDuration+maxTolerableExpiredLease { + le.observedRecordLock.RLock() + lastObservation := le.observedTime + leaseDuration := le.config.LeaseDuration + le.observedRecordLock.RUnlock() + + if le.clock.Since(lastObservation) > leaseDuration+maxTolerableExpiredLease { return fmt.Errorf("failed election to renew leadership on lease %s", le.config.Name) } @@ -520,7 +534,11 @@ func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error { } func (le *LeaderElector) isLeaseValid(now time.Time) bool { - return le.observedTime.Add(time.Second * time.Duration(le.getObservedRecord().LeaseDurationSeconds)).After(now) + // Lock to safely read both the time and the record + le.observedRecordLock.RLock() + defer le.observedRecordLock.RUnlock() + + return le.observedTime.Add(time.Second * time.Duration(le.observedRecord.LeaseDurationSeconds)).After(now) } // setObservedRecord will set a new observedRecord and update observedTime to the current time. @@ -536,8 +554,8 @@ func (le *LeaderElector) setObservedRecord(observedRecord *rl.LeaderElectionReco // getObservedRecord returns observersRecord. // Protect critical sections with lock. func (le *LeaderElector) getObservedRecord() rl.LeaderElectionRecord { - le.observedRecordLock.Lock() - defer le.observedRecordLock.Unlock() + le.observedRecordLock.RLock() + defer le.observedRecordLock.RUnlock() return le.observedRecord } diff --git a/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection_test.go b/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection_test.go index 6762a32d17cd5..773cd97289043 100644 --- a/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection_test.go +++ b/staging/src/k8s.io/client-go/tools/leaderelection/leaderelection_test.go @@ -1190,3 +1190,79 @@ func TestFastPathLeaderElection(t *testing.T) { }) } } + +func TestCheckMethodRace(t *testing.T) { + objectType := "leases" + objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"} + recorder := record.NewFakeRecorder(100) + resourceLockConfig := rl.ResourceLockConfig{ + Identity: "baz", + EventRecorder: recorder, + } + + var lockMu sync.Mutex + var lockObj runtime.Object + + c := &fake.Clientset{} + + c.AddReactor("get", objectType, func(action fakeclient.Action) (bool, runtime.Object, error) { + lockMu.Lock() + defer lockMu.Unlock() + if lockObj != nil { + return true, lockObj, nil + } + return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) + }) + + c.AddReactor("create", objectType, func(action fakeclient.Action) (bool, runtime.Object, error) { + lockMu.Lock() + defer lockMu.Unlock() + lockObj = action.(fakeclient.CreateAction).GetObject() + return true, lockObj, nil + }) + + c.AddReactor("update", objectType, func(action fakeclient.Action) (bool, runtime.Object, error) { + lockMu.Lock() + defer lockMu.Unlock() + lockObj = action.(fakeclient.UpdateAction).GetObject() + return true, lockObj, nil + }) + + lock := &rl.LeaseLock{ + LeaseMeta: objectMeta, + LockConfig: resourceLockConfig, + Client: c.CoordinationV1(), + } + + // aggressive timing to provoke the race + lec := LeaderElectionConfig{ + Lock: lock, + LeaseDuration: 2 * time.Second, + RenewDeadline: 1 * time.Second, + RetryPeriod: 5 * time.Millisecond, + Callbacks: LeaderCallbacks{ + OnNewLeader: func(identity string) {}, + OnStartedLeading: func(context.Context) {}, + OnStoppedLeading: func() {}, + }, + } + + le, err := NewLeaderElector(lec) + if err != nil { + t.Fatalf("Failed to create leader elector: %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go le.Run(ctx) + + start := time.Now() + for time.Since(start) < 2*time.Second { + // If Check() does not lock observedRecordLock, this read will race with the + // write in le.Run()'s setObservedRecord. + _ = le.Check(time.Second) + + time.Sleep(10 * time.Millisecond) + } +} diff --git a/staging/src/k8s.io/client-go/tools/leaderelection/leasecandidate.go b/staging/src/k8s.io/client-go/tools/leaderelection/leasecandidate.go index b2fa14a5f3a69..e2d0ef437af75 100644 --- a/staging/src/k8s.io/client-go/tools/leaderelection/leasecandidate.go +++ b/staging/src/k8s.io/client-go/tools/leaderelection/leasecandidate.go @@ -19,6 +19,7 @@ package leaderelection import ( "context" "reflect" + "sync" "time" v1 "k8s.io/api/coordination/v1" @@ -34,6 +35,8 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" "k8s.io/utils/clock" + + coordinationv1beta1listers "k8s.io/client-go/listers/coordination/v1beta1" ) const requeueInterval = 5 * time.Minute @@ -45,6 +48,7 @@ type CacheSyncWaiter interface { type LeaseCandidate struct { leaseClient coordinationv1beta1client.LeaseCandidateInterface leaseCandidateInformer cache.SharedIndexInformer + leaseCandidateLister coordinationv1beta1listers.LeaseCandidateLister informerFactory informers.SharedInformerFactory hasSynced cache.InformerSynced @@ -80,15 +84,18 @@ func NewCandidate(clientset kubernetes.Interface, // are started for leader elected components informerFactory := informers.NewSharedInformerFactoryWithOptions( clientset, 5*time.Minute, + informers.WithNamespace(candidateNamespace), informers.WithTweakListOptions(func(options *metav1.ListOptions) { options.FieldSelector = fieldSelector }), ) leaseCandidateInformer := informerFactory.Coordination().V1beta1().LeaseCandidates().Informer() + leaseCandidateLister := informerFactory.Coordination().V1beta1().LeaseCandidates().Lister() lc := &LeaseCandidate{ leaseClient: clientset.CoordinationV1beta1().LeaseCandidates(candidateNamespace), leaseCandidateInformer: leaseCandidateInformer, + leaseCandidateLister: leaseCandidateLister, informerFactory: informerFactory, name: candidateName, namespace: candidateNamespace, @@ -118,19 +125,25 @@ func NewCandidate(clientset kubernetes.Interface, } func (c *LeaseCandidate) Run(ctx context.Context) { - defer c.queue.ShutDown() - logger := klog.FromContext(ctx) logger = klog.LoggerWithName(logger, "leasecandidate") ctx = klog.NewContext(ctx, logger) + var wg sync.WaitGroup + defer func() { + c.queue.ShutDown() + wg.Wait() + }() + c.informerFactory.Start(ctx.Done()) if !cache.WaitForNamedCacheSyncWithContext(ctx, c.hasSynced) { return } c.enqueueLease() - go c.runWorker(ctx) + wg.Go(func() { + c.runWorker(ctx) + }) <-ctx.Done() } @@ -166,7 +179,7 @@ func (c *LeaseCandidate) enqueueLease() { // a bool (true if this call created the lease), or any error that occurs. func (c *LeaseCandidate) ensureLease(ctx context.Context) error { logger := klog.FromContext(ctx) - lease, err := c.leaseClient.Get(ctx, c.name, metav1.GetOptions{}) + lease, err := c.leaseCandidateLister.LeaseCandidates(c.namespace).Get(c.name) if apierrors.IsNotFound(err) { logger.V(2).Info("Creating lease candidate") // lease does not exist, create it. diff --git a/staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go b/staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go index 79a748b74d8c1..b03e3c1284316 100644 --- a/staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go +++ b/staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go @@ -105,7 +105,7 @@ func (ll *LeaseLock) RecordEvent(s string) { // Populate the type meta, so we don't have to get it from the schema subject.Kind = "Lease" subject.APIVersion = coordinationv1.SchemeGroupVersion.String() - ll.LockConfig.EventRecorder.Eventf(subject, corev1.EventTypeNormal, "LeaderElection", events) + ll.LockConfig.EventRecorder.Eventf(subject, corev1.EventTypeNormal, "LeaderElection", "%s", events) } // Describe is used to convert details on current resource lock diff --git a/staging/src/k8s.io/client-go/tools/metrics/metrics.go b/staging/src/k8s.io/client-go/tools/metrics/metrics.go index e364b7e1cf0ce..2f626475b2043 100644 --- a/staging/src/k8s.io/client-go/tools/metrics/metrics.go +++ b/staging/src/k8s.io/client-go/tools/metrics/metrics.go @@ -80,11 +80,29 @@ type TransportCacheMetric interface { } // TransportCreateCallsMetric counts the number of times a transport is created -// partitioned by the result of the cache: hit, miss, uncacheable +// partitioned by the result of the cache: hit, miss, miss-gc, uncacheable type TransportCreateCallsMetric interface { Increment(result string) } +// TransportCAReloadsMetric counts the number of times a CA reload is attempted, +// partitioned by the result and reason. +type TransportCAReloadsMetric interface { + Increment(result, reason string) +} + +// TransportCertRotationGCCallsMetric counts the number of times a cert rotation +// goroutine cancel func is called via GC cleanup. +type TransportCertRotationGCCallsMetric interface { + Increment() +} + +// TransportCacheGCCallsMetric counts the number of times a GC cleanup +// attempts to delete a cache entry, partitioned by the result: deleted, skipped. +type TransportCacheGCCallsMetric interface { + Increment(result string) +} + var ( // ClientCertExpiry is the expiry time of a client certificate ClientCertExpiry ExpiryMetric = noopExpiry{} @@ -117,23 +135,34 @@ var ( // TransportCreateCalls is the metric that counts the number of times a new transport // is created TransportCreateCalls TransportCreateCallsMetric = noopTransportCreateCalls{} + // TransportCAReloads is the metric that counts the number of times a CA reload is attempted + TransportCAReloads TransportCAReloadsMetric = noopTransportCAReloads{} + // TransportCertRotationGCCalls counts the number of times a cert rotation goroutine + // cancel func is called via GC cleanup + TransportCertRotationGCCalls TransportCertRotationGCCallsMetric = noopTransportCertRotationGCCalls{} + // TransportCacheGCCalls counts the number of times a GC cleanup attempts + // to delete a transport cache entry, partitioned by result: deleted, skipped. + TransportCacheGCCalls TransportCacheGCCallsMetric = noopTransportCacheGCCalls{} ) // RegisterOpts contains all the metrics to register. Metrics may be nil. type RegisterOpts struct { - ClientCertExpiry ExpiryMetric - ClientCertRotationAge DurationMetric - RequestLatency LatencyMetric - ResolverLatency ResolverLatencyMetric - RequestSize SizeMetric - ResponseSize SizeMetric - RateLimiterLatency LatencyMetric - RequestResult ResultMetric - ExecPluginCalls CallsMetric - ExecPluginPolicyCalls PolicyCallsMetric - RequestRetry RetryMetric - TransportCacheEntries TransportCacheMetric - TransportCreateCalls TransportCreateCallsMetric + ClientCertExpiry ExpiryMetric + ClientCertRotationAge DurationMetric + RequestLatency LatencyMetric + ResolverLatency ResolverLatencyMetric + RequestSize SizeMetric + ResponseSize SizeMetric + RateLimiterLatency LatencyMetric + RequestResult ResultMetric + ExecPluginCalls CallsMetric + ExecPluginPolicyCalls PolicyCallsMetric + RequestRetry RetryMetric + TransportCacheEntries TransportCacheMetric + TransportCreateCalls TransportCreateCallsMetric + TransportCAReloads TransportCAReloadsMetric + TransportCertRotationGCCalls TransportCertRotationGCCallsMetric + TransportCacheGCCalls TransportCacheGCCallsMetric } // Register registers metrics for the rest client to use. This can @@ -179,6 +208,15 @@ func Register(opts RegisterOpts) { if opts.TransportCreateCalls != nil { TransportCreateCalls = opts.TransportCreateCalls } + if opts.TransportCAReloads != nil { + TransportCAReloads = opts.TransportCAReloads + } + if opts.TransportCertRotationGCCalls != nil { + TransportCertRotationGCCalls = opts.TransportCertRotationGCCalls + } + if opts.TransportCacheGCCalls != nil { + TransportCacheGCCalls = opts.TransportCacheGCCalls + } }) } @@ -226,3 +264,15 @@ func (noopTransportCache) Observe(int) {} type noopTransportCreateCalls struct{} func (noopTransportCreateCalls) Increment(string) {} + +type noopTransportCAReloads struct{} + +func (noopTransportCAReloads) Increment(result, reason string) {} + +type noopTransportCertRotationGCCalls struct{} + +func (noopTransportCertRotationGCCalls) Increment() {} + +type noopTransportCacheGCCalls struct{} + +func (noopTransportCacheGCCalls) Increment(string) {} diff --git a/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go b/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go index 7fcc2492bfe76..91f8fad4dde92 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go +++ b/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go @@ -19,9 +19,11 @@ package portforward import ( "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/klog/v2" + streamhttp "k8s.io/streaming/pkg/httpstream" ) var _ httpstream.Dialer = &FallbackDialer{} +var _ streamhttp.Dialer = &StreamingFallbackDialer{} // FallbackDialer encapsulates a primary and secondary dialer, including // the boolean function to determine if the primary dialer failed. Implements @@ -42,6 +44,24 @@ func NewFallbackDialer(primary, secondary httpstream.Dialer, shouldFallback func } } +// StreamingFallbackDialer encapsulates a primary and secondary streaming dialer +// with fallback behavior. +type StreamingFallbackDialer struct { + primary streamhttp.Dialer + secondary streamhttp.Dialer + shouldFallback func(error) bool +} + +// NewFallbackDialerForStreaming creates a fallback dialer for in-tree callers +// that use k8s.io/streaming/pkg/httpstream types. +func NewFallbackDialerForStreaming(primary, secondary streamhttp.Dialer, shouldFallback func(error) bool) streamhttp.Dialer { + return &StreamingFallbackDialer{ + primary: primary, + secondary: secondary, + shouldFallback: shouldFallback, + } +} + // Dial is the single function necessary to implement the "httpstream.Dialer" interface. // It takes the protocol version strings to request, returning an the upgraded // httstream.Connection and the negotiated protocol version accepted. If the initial @@ -55,3 +75,14 @@ func (f *FallbackDialer) Dial(protocols ...string) (httpstream.Connection, strin } return conn, version, err } + +// Dial is the single function necessary to implement the +// "k8s.io/streaming/pkg/httpstream.Dialer" interface. +func (f *StreamingFallbackDialer) Dial(protocols ...string) (streamhttp.Connection, string, error) { + conn, version, err := f.primary.Dial(protocols...) + if err != nil && f.shouldFallback(err) { + klog.V(4).Infof("fallback to secondary dialer from primary dialer err: %v", err) + return f.secondary.Dial(protocols...) + } + return conn, version, err +} diff --git a/staging/src/k8s.io/client-go/tools/portforward/portforward.go b/staging/src/k8s.io/client-go/tools/portforward/portforward.go index 126c14e8fa0ab..11e0df6307225 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/portforward.go +++ b/staging/src/k8s.io/client-go/tools/portforward/portforward.go @@ -26,10 +26,13 @@ import ( "strconv" "strings" "sync" + "time" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/klog/v2" + streamhttp "k8s.io/streaming/pkg/httpstream" netutils "k8s.io/utils/net" ) @@ -164,6 +167,12 @@ func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, rea return NewOnAddresses(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut) } +// NewForStreaming creates a new PortForwarder with localhost listen addresses +// for in-tree callers that use k8s.io/streaming/pkg/httpstream types. +func NewForStreaming(dialer streamhttp.Dialer, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) { + return NewOnAddressesForStreaming(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut) +} + // NewOnAddresses creates a new PortForwarder with custom listen addresses. func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) { if len(addresses) == 0 { @@ -191,6 +200,95 @@ func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string }, nil } +// NewOnAddressesForStreaming creates a new PortForwarder with custom listen +// addresses for in-tree callers that use k8s.io/streaming/pkg/httpstream types. +func NewOnAddressesForStreaming(dialer streamhttp.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) { + return NewOnAddresses(&compatDialerAdapter{delegate: dialer}, addresses, ports, stopChan, readyChan, out, errOut) +} + +type compatDialerAdapter struct { + delegate streamhttp.Dialer +} + +func (d *compatDialerAdapter) Dial(protocols ...string) (httpstream.Connection, string, error) { + conn, protocol, err := d.delegate.Dial(protocols...) + if err != nil { + return nil, "", err + } + return &compatConnectionAdapter{delegate: conn}, protocol, nil +} + +type compatConnectionAdapter struct { + delegate streamhttp.Connection +} + +func (c *compatConnectionAdapter) CreateStream(headers http.Header) (httpstream.Stream, error) { + stream, err := c.delegate.CreateStream(headers) + if err != nil { + return nil, err + } + return &compatStreamAdapter{delegate: stream}, nil +} + +func (c *compatConnectionAdapter) Close() error { + return c.delegate.Close() +} + +func (c *compatConnectionAdapter) CloseChan() <-chan bool { + return c.delegate.CloseChan() +} + +func (c *compatConnectionAdapter) SetIdleTimeout(timeout time.Duration) { + c.delegate.SetIdleTimeout(timeout) +} + +func (c *compatConnectionAdapter) RemoveStreams(streams ...httpstream.Stream) { + streamingStreams := make([]streamhttp.Stream, 0, len(streams)) + for _, stream := range streams { + if stream == nil { + continue + } + if s, ok := stream.(*compatStreamAdapter); ok { + streamingStreams = append(streamingStreams, s.delegate) + continue + } + if s, ok := stream.(streamhttp.Stream); ok { + streamingStreams = append(streamingStreams, s) + continue + } + klog.V(5).Infof("dropping unadaptable stream %T in portforward RemoveStreams", stream) + } + c.delegate.RemoveStreams(streamingStreams...) +} + +type compatStreamAdapter struct { + delegate streamhttp.Stream +} + +func (s *compatStreamAdapter) Read(p []byte) (int, error) { + return s.delegate.Read(p) +} + +func (s *compatStreamAdapter) Write(p []byte) (int, error) { + return s.delegate.Write(p) +} + +func (s *compatStreamAdapter) Close() error { + return s.delegate.Close() +} + +func (s *compatStreamAdapter) Reset() error { + return s.delegate.Reset() +} + +func (s *compatStreamAdapter) Headers() http.Header { + return s.delegate.Headers() +} + +func (s *compatStreamAdapter) Identifier() uint32 { + return s.delegate.Identifier() +} + // ForwardPorts formats and executes a port forwarding request. The connection will remain // open until stopChan is closed. func (pf *PortForwarder) ForwardPorts() error { diff --git a/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go b/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go index 2bef5ecd72001..cc9352bfea1d4 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go +++ b/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go @@ -24,11 +24,13 @@ import ( "time" "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" + httpstreamspdy "k8s.io/apimachinery/pkg/util/httpstream/spdy" constants "k8s.io/apimachinery/pkg/util/portforward" restclient "k8s.io/client-go/rest" "k8s.io/client-go/transport/websocket" "k8s.io/klog/v2" + streamhttp "k8s.io/streaming/pkg/httpstream" + streamspdy "k8s.io/streaming/pkg/httpstream/spdy" ) const PingPeriod = 10 * time.Second @@ -40,6 +42,14 @@ type tunnelingDialer struct { holder websocket.ConnectionHolder } +// streamingTunnelingDialer implements "k8s.io/streaming/pkg/httpstream.Dialer" +// for in-tree callers. +type streamingTunnelingDialer struct { + url *url.URL + transport http.RoundTripper + holder websocket.ConnectionHolder +} + // NewTunnelingDialer creates and returns the tunnelingDialer structure which implemements the "httpstream.Dialer" // interface. The dialer can upgrade a websocket request, creating a websocket connection. This function // returns an error if one occurs. @@ -55,25 +65,36 @@ func NewSPDYOverWebsocketDialer(url *url.URL, config *restclient.Config) (httpst }, nil } -// Dial upgrades to a tunneling streaming connection, returning a SPDY connection -// containing a WebSockets connection (which implements "net.Conn"). Also -// returns the protocol negotiated, or an error. -func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, string, error) { +// NewSPDYOverWebsocketDialerForStreaming creates a SPDY-over-websocket dialer +// for in-tree callers that use k8s.io/streaming/pkg/httpstream types. +func NewSPDYOverWebsocketDialerForStreaming(url *url.URL, config *restclient.Config) (streamhttp.Dialer, error) { + transport, holder, err := websocket.RoundTripperFor(config) + if err != nil { + return nil, err + } + return &streamingTunnelingDialer{ + url: url, + transport: transport, + holder: holder, + }, nil +} + +func negotiateSPDYOverWebsocket(url *url.URL, transport http.RoundTripper, holder websocket.ConnectionHolder, protocols ...string) (*TunnelingConnection, string, error) { // There is no passed context, so skip the context when creating request for now. // Websockets requires "GET" method: RFC 6455 Sec. 4.1 (page 17). - req, err := http.NewRequest("GET", d.url.String(), nil) + req, err := http.NewRequest("GET", url.String(), nil) if err != nil { return nil, "", err } // Add the spdy tunneling prefix to the requested protocols. The tunneling // handler will know how to negotiate these protocols. - tunnelingProtocols := []string{} + tunnelingProtocols := make([]string, 0, len(protocols)) for _, protocol := range protocols { tunnelingProtocol := constants.WebsocketsSPDYTunnelingPrefix + protocol tunnelingProtocols = append(tunnelingProtocols, tunnelingProtocol) } klog.V(4).Infoln("Before WebSocket Upgrade Connection...") - conn, err := websocket.Negotiate(d.transport, d.holder, req, tunnelingProtocols...) + conn, err := websocket.Negotiate(transport, holder, req, tunnelingProtocols...) if err != nil { return nil, "", err } @@ -84,10 +105,32 @@ func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, stri protocol = strings.TrimPrefix(protocol, constants.WebsocketsSPDYTunnelingPrefix) klog.V(4).Infof("negotiated protocol: %s", protocol) - // Wrap the websocket connection which implements "net.Conn". - tConn := NewTunnelingConnection("client", conn) + return NewTunnelingConnection("client", conn), protocol, nil +} + +// Dial upgrades to a tunneling streaming connection, returning a SPDY connection +// containing a WebSockets connection (which implements "net.Conn"). Also +// returns the protocol negotiated, or an error. +func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, string, error) { + tConn, protocol, err := negotiateSPDYOverWebsocket(d.url, d.transport, d.holder, protocols...) + if err != nil { + return nil, "", err + } + // Create SPDY connection injecting the previously created tunneling connection. + spdyConn, err := httpstreamspdy.NewClientConnectionWithPings(tConn, PingPeriod) + + return spdyConn, protocol, err +} + +// Dial upgrades to a tunneling streaming connection for callers using +// k8s.io/streaming/pkg/httpstream types. +func (d *streamingTunnelingDialer) Dial(protocols ...string) (streamhttp.Connection, string, error) { + tConn, protocol, err := negotiateSPDYOverWebsocket(d.url, d.transport, d.holder, protocols...) + if err != nil { + return nil, "", err + } // Create SPDY connection injecting the previously created tunneling connection. - spdyConn, err := spdy.NewClientConnectionWithPings(tConn, PingPeriod) + spdyConn, err := streamspdy.NewClientConnectionWithPings(tConn, PingPeriod) return spdyConn, protocol, err } diff --git a/staging/src/k8s.io/client-go/tools/record/event.go b/staging/src/k8s.io/client-go/tools/record/event.go index 58322d44d23d7..305a924388a69 100644 --- a/staging/src/k8s.io/client-go/tools/record/event.go +++ b/staging/src/k8s.io/client-go/tools/record/event.go @@ -408,7 +408,10 @@ func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(*v1.Event)) w case <-e.cancelationCtx.Done(): watcher.Stop() return - case watchEvent := <-watcher.ResultChan(): + case watchEvent, ok := <-watcher.ResultChan(): + if !ok { + return + } event, ok := watchEvent.Object.(*v1.Event) if !ok { // This is all local, so there's no reason this should diff --git a/staging/src/k8s.io/client-go/tools/record/event_test.go b/staging/src/k8s.io/client-go/tools/record/event_test.go index a988a4afc9e28..4797f29941488 100644 --- a/staging/src/k8s.io/client-go/tools/record/event_test.go +++ b/staging/src/k8s.io/client-go/tools/record/event_test.go @@ -135,6 +135,35 @@ func TestBroadcasterShutdown(t *testing.T) { goleak.VerifyNone(t) } +// TestStartEventWatcherExitsOnDirectShutdown is a regression test for the +// hot-loop that occurred when Broadcaster.Shutdown() closed the watcher's +// result channel before the cancellation context was cancelled. +// +// When NewBroadcaster is called without a context, Shutdown() sequentially: +// +// 1. Calls e.Broadcaster.Shutdown() → watcher result channels are closed via +// closeAll(); all queued events have already been delivered at this point. +// 2. Calls e.cancel() → cancelationCtx is cancelled. +// +// Between steps 1 and 2 there is a window where ResultChan() is closed but +// cancelationCtx.Done() has not yet fired. The goroutine inside +// StartEventWatcher must detect the closed channel (ok == false) and return +// cleanly instead of spinning. goleak.VerifyTestMain (installed in +// main_test.go) will report a leaked goroutine if the fix regresses. +func TestStartEventWatcherExitsOnDirectShutdown(t *testing.T) { + // No context → haveCtxCancelation is false, so no background Broadcaster + // shutdown goroutine; Shutdown() is fully synchronous here. + caster := NewBroadcaster(WithSleepDuration(0)) + caster.StartStructuredLogging(0) + + // Direct shutdown: closes watcher channels first, then cancels context. + caster.Shutdown() + + // goleak.VerifyTestMain checks for leaks across the whole test binary, but + // explicitly verifying here surfaces any regression in this specific path. + goleak.VerifyNone(t) +} + func TestNonRacyShutdown(t *testing.T) { // Attempt to simulate previously racy conditions, and ensure that no race // occurs: Nominally, calling "Eventf" *followed by* shutdown from the same @@ -638,7 +667,7 @@ func TestLotsOfEvents(t *testing.T) { APIVersion: "version", } // we need to vary the reason to prevent aggregation - go recorder.Eventf(ref, v1.EventTypeNormal, "Reason-"+strconv.Itoa(i), strconv.Itoa(i)) + go recorder.Eventf(ref, v1.EventTypeNormal, "Reason-"+strconv.Itoa(i), "%s", strconv.Itoa(i)) } // Make sure no events were dropped by either of the listeners. for i := 0; i < maxQueuedEvents; i++ { diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/errorstream.go b/staging/src/k8s.io/client-go/tools/remotecommand/errorstream.go index 90bb39b4a444f..23dd50778d261 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/errorstream.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/errorstream.go @@ -21,6 +21,7 @@ import ( "io" "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/klog/v2" ) // errorStreamDecoder interprets the data on the error channel and creates a go error object from it. @@ -32,11 +33,11 @@ type errorStreamDecoder interface { // decodes it with the given errorStreamDecoder, sends the decoded error (or nil if the remote // command exited successfully) to the returned error channel, and closes it. // This function returns immediately. -func watchErrorStream(errorStream io.Reader, d errorStreamDecoder) chan error { +func watchErrorStream(logger klog.Logger, errorStream io.Reader, d errorStreamDecoder) chan error { errorChan := make(chan error) go func() { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithLogger(logger) message, err := io.ReadAll(errorStream) switch { diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/fallback.go b/staging/src/k8s.io/client-go/tools/remotecommand/fallback.go index 503323080da48..bcd5fd3138dd5 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/fallback.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/fallback.go @@ -53,7 +53,7 @@ func (f *FallbackExecutor) Stream(options StreamOptions) error { func (f *FallbackExecutor) StreamWithContext(ctx context.Context, options StreamOptions) error { err := f.primary.StreamWithContext(ctx, options) if err != nil && f.shouldFallback(err) { - klog.V(4).Infof("RemoteCommand fallback: %v", err) + klog.FromContext(ctx).V(4).Info("RemoteCommand fallback", "err", err) return f.secondary.StreamWithContext(ctx, options) } return err diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go index 0cd775144d6e2..85600d8963b94 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go @@ -25,17 +25,21 @@ import ( "net/http" "net/http/httptest" "net/url" + "strings" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/util/httpstream" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" + utilexec "k8s.io/client-go/util/exec" + "k8s.io/streaming/pkg/httpstream" ) func TestFallbackClient_WebSocketPrimarySucceeds(t *testing.T) { @@ -234,6 +238,74 @@ func TestFallbackClient_PrimaryAndSecondaryFail(t *testing.T) { } } +func TestFallbackClient_SPDYSecondaryNonZeroExitCode(t *testing.T) { + const expectedExitCode = 23 + const expectedStdout = "stdout-before-exit" + + // Create fake SPDY server that writes stdout followed by a v4 status error. + spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ctx, err := createHTTPStreams(w, req, &StreamOptions{ + Stdin: strings.NewReader("input"), + Stdout: &bytes.Buffer{}, + }) + if err != nil { + w.WriteHeader(http.StatusForbidden) + return + } + defer ctx.conn.Close() + + if _, err := io.WriteString(ctx.stdoutStream, expectedStdout); err != nil { + t.Fatalf("error writing stdout stream: %v", err) + } + + statusErr := &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: remotecommand.NonZeroExitCodeReason, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{ + { + Type: remotecommand.ExitCodeCauseType, + Message: "23", + }, + }, + }, + Message: "command terminated with non-zero exit code: 23", + }} + if err := ctx.writeStatus(statusErr); err != nil { + t.Fatalf("error writing status stream: %v", err) + } + })) + defer spdyServer.Close() + + spdyLocation, err := url.Parse(spdyServer.URL) + require.NoError(t, err) + // Primary websocket executor points at a SPDY-only endpoint and should fail. + websocketExecutor, err := NewWebSocketExecutor(&rest.Config{Host: spdyLocation.Host}, "GET", spdyServer.URL+"?stdin=true&stdout=true") + require.NoError(t, err) + spdyExecutor, err := NewSPDYExecutor(&rest.Config{Host: spdyLocation.Host}, "POST", spdyLocation) + require.NoError(t, err) + + var sawPrimaryError atomic.Bool + exec, err := NewFallbackExecutor(websocketExecutor, spdyExecutor, func(err error) bool { + sawPrimaryError.Store(true) + return true + }) + require.NoError(t, err) + + var stdout bytes.Buffer + err = exec.StreamWithContext(context.Background(), StreamOptions{ + Stdin: strings.NewReader("input"), + Stdout: &stdout, + }) + require.Error(t, err) + require.True(t, sawPrimaryError.Load(), "expected primary websocket path to fail and trigger fallback") + + var exitErr utilexec.ExitError + require.ErrorAs(t, err, &exitErr, "expected ExitError from secondary SPDY path, got: %T: %v", err, err) + require.Equal(t, expectedExitCode, exitErr.ExitStatus()) + require.Equal(t, expectedStdout, stdout.String()) +} + // localhostCert was generated from crypto/tls/generate_cert.go with the following command: // // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go b/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go index 4cff05cd817f0..ca892f9b7487f 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go @@ -21,7 +21,8 @@ import ( "io" "net/http" - "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" ) // StreamOptions holds information pertaining to the current streaming session: @@ -54,5 +55,5 @@ type streamCreator interface { } type streamProtocolHandler interface { - stream(conn streamCreator, ready chan<- struct{}) error + stream(logger klog.Logger, conn streamCreator, ready chan<- struct{}) error } diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go b/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go index 34825771a0215..ebf3c53c3a0f8 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go @@ -22,11 +22,11 @@ import ( "net/http" "net/url" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/remotecommand" restclient "k8s.io/client-go/rest" "k8s.io/client-go/transport/spdy" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" ) // spdyStreamExecutor handles transporting standard shell streams over an httpstream connection. @@ -109,7 +109,7 @@ func (e *spdyStreamExecutor) newConnectionAndStream(ctx context.Context, options return fmt.Errorf("redirect not allowed") } } - conn, protocol, err := spdy.Negotiate( + conn, protocol, err := spdy.NegotiateStreaming( e.upgrader, &client, req, @@ -121,6 +121,7 @@ func (e *spdyStreamExecutor) newConnectionAndStream(ctx context.Context, options var streamer streamProtocolHandler + logger := klog.FromContext(ctx) switch protocol { case remotecommand.StreamProtocolV5Name: streamer = newStreamProtocolV5(options) @@ -131,7 +132,7 @@ func (e *spdyStreamExecutor) newConnectionAndStream(ctx context.Context, options case remotecommand.StreamProtocolV2Name: streamer = newStreamProtocolV2(options) case "": - klog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name) + logger.V(4).Info("The server did not negotiate a streaming protocol version, falling back", "protocol", remotecommand.StreamProtocolV1Name) fallthrough case remotecommand.StreamProtocolV1Name: streamer = newStreamProtocolV1(options) @@ -161,7 +162,7 @@ func (e *spdyStreamExecutor) StreamWithContext(ctx context.Context, options Stre // The SPDY executor does not need to synchronize stream creation, so we pass a nil // ready channel. The underlying spdystream library handles stream multiplexing // without a race condition. - errorChan <- streamer.stream(conn, nil) + errorChan <- streamer.stream(klog.FromContext(ctx), conn, nil) }() select { diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go index 9948832a54107..2a6e83b4c9913 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go @@ -33,11 +33,13 @@ import ( v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" + utilexec "k8s.io/client-go/util/exec" + "k8s.io/klog/v2/ktesting" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/spdy" ) type AttachFunc func(in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan TerminalSize) error @@ -182,6 +184,71 @@ func TestSPDYExecutorStream(t *testing.T) { } } +// TestSPDYExecutorNonZeroExitCode verifies SPDY v4 status-stream non-zero exit +// code handling remains compatible with CRI streaming servers. +func TestSPDYExecutorNonZeroExitCode(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + ctx, err := createHTTPStreams(writer, request, &StreamOptions{ + Stdin: strings.NewReader("input"), + Stdout: &bytes.Buffer{}, + }) + if err != nil { + t.Errorf("unexpected stream setup error: %v", err) + return + } + defer ctx.conn.Close() + + if _, err := io.WriteString(ctx.stdoutStream, "stdout-before-exit"); err != nil { + t.Errorf("unexpected stdout write error: %v", err) + return + } + + err = ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: remotecommandconsts.NonZeroExitCodeReason, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{ + { + Type: remotecommandconsts.ExitCodeCauseType, + Message: "17", + }, + }, + }, + Message: "command terminated with non-zero exit code: 17", + }}) + if err != nil { + t.Errorf("unexpected status write error: %v", err) + } + })) + defer server.Close() + + uri, _ := url.Parse(server.URL) + exec, err := NewSPDYExecutor(&rest.Config{Host: uri.Host}, "POST", uri) + if err != nil { + t.Fatalf("unexpected executor error: %v", err) + } + + var stdout bytes.Buffer + err = exec.StreamWithContext(context.Background(), StreamOptions{ + Stdin: strings.NewReader("input"), + Stdout: &stdout, + }) + if err == nil { + t.Fatal("expected non-zero exit error, got nil") + } + + var exitErr utilexec.ExitError + if !errors.As(err, &exitErr) { + t.Fatalf("expected ExitError, got: %T: %v", err, err) + } + if exitErr.ExitStatus() != 17 { + t.Fatalf("expected exit code 17, got %d", exitErr.ExitStatus()) + } + if got := stdout.String(); got != "stdout-before-exit" { + t.Fatalf("unexpected stdout: %q", got) + } +} + func newTestHTTPServer(f AttachFunc, options *StreamOptions) *httptest.Server { //nolint:errcheck server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { @@ -328,6 +395,7 @@ func (w *writeDetector) Write(p []byte) (n int, err error) { // and expects the deferred close of the connection leads to the exit of the goroutine on cancellation. // This test verifies that works. func TestStreamExitsAfterConnectionIsClosed(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) writeDetector := newWriterDetector(&fakeEmptyDataPty{}) options := StreamOptions{ Stdin: &fakeEmptyDataPty{}, @@ -352,7 +420,7 @@ func TestStreamExitsAfterConnectionIsClosed(t *testing.T) { errorChan := make(chan error) go func() { - errorChan <- streamer.stream(conn, nil) + errorChan <- streamer.stream(logger, conn, nil) }() // Wait until stream goroutine starts. diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v1.go b/staging/src/k8s.io/client-go/tools/remotecommand/v1.go index 293d809ddea38..5d903b67a2fb5 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v1.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v1.go @@ -21,9 +21,9 @@ import ( "io" "net/http" - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/httpstream" + v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" ) // streamProtocolV1 implements the first version of the streaming exec & attach @@ -47,15 +47,15 @@ func newStreamProtocolV1(options StreamOptions) streamProtocolHandler { } } -func (p *streamProtocolV1) stream(conn streamCreator, ready chan<- struct{}) error { +func (p *streamProtocolV1) stream(logger klog.Logger, conn streamCreator, ready chan<- struct{}) error { doneChan := make(chan struct{}, 2) errorChan := make(chan error) cp := func(s string, dst io.Writer, src io.Reader) { - klog.V(6).Infof("Copying %s", s) - defer klog.V(6).Infof("Done copying %s", s) + logger.V(6).Info("Copying", "data", s) + defer logger.V(6).Info("Done copying", "data", s) if _, err := io.Copy(dst, src); err != nil && err != io.EOF { - klog.Errorf("Error copying %s: %v", s, err) + logger.Error(err, "Error copying", "data", s) } if s == v1.StreamTypeStdout || s == v1.StreamTypeStderr { doneChan <- struct{}{} diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v2.go b/staging/src/k8s.io/client-go/tools/remotecommand/v2.go index a81538a0921eb..75286a12fd1be 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v2.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v2.go @@ -22,8 +22,9 @@ import ( "net/http" "sync" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/klog/v2" ) // streamProtocolV2 implements version 2 of the streaming protocol for attach @@ -87,13 +88,13 @@ func (p *streamProtocolV2) createStreams(conn streamCreator) error { return nil } -func (p *streamProtocolV2) copyStdin() { +func (p *streamProtocolV2) copyStdin(logger klog.Logger) { if p.Stdin != nil { var once sync.Once // copy from client's stdin to container's stdin go func() { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithLogger(logger) // if p.stdin is noninteractive, p.g. `echo abc | kubectl exec -i -- cat`, make sure // we close remoteStdin as soon as the copy from p.stdin to remoteStdin finishes. Otherwise @@ -101,7 +102,7 @@ func (p *streamProtocolV2) copyStdin() { defer once.Do(func() { p.remoteStdin.Close() }) if _, err := io.Copy(p.remoteStdin, readerWrapper{p.Stdin}); err != nil { - runtime.HandleError(err) + runtime.HandleErrorWithLogger(logger, err, "Copying stdin failed") } }() @@ -120,26 +121,26 @@ func (p *streamProtocolV2) copyStdin() { // When that happens, we must Close() on our side of remoteStdin, to // allow the copy in hijack to complete, and hijack to return. go func() { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithLogger(logger) defer once.Do(func() { p.remoteStdin.Close() }) // this "copy" doesn't actually read anything - it's just here to wait for // the server to close remoteStdin. if _, err := io.Copy(io.Discard, p.remoteStdin); err != nil { - runtime.HandleError(err) + runtime.HandleErrorWithLogger(logger, err, "Waiting for server to close stdin failed") } }() } } -func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) { +func (p *streamProtocolV2) copyStdout(logger klog.Logger, wg *sync.WaitGroup) { if p.Stdout == nil { return } wg.Add(1) go func() { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithLogger(logger) defer wg.Done() // make sure, packet in queue can be consumed. // block in queue may lead to deadlock in conn.server @@ -147,29 +148,29 @@ func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) { defer io.Copy(io.Discard, p.remoteStdout) if _, err := io.Copy(p.Stdout, p.remoteStdout); err != nil { - runtime.HandleError(err) + runtime.HandleErrorWithLogger(logger, err, "Copying stdout failed") } }() } -func (p *streamProtocolV2) copyStderr(wg *sync.WaitGroup) { +func (p *streamProtocolV2) copyStderr(logger klog.Logger, wg *sync.WaitGroup) { if p.Stderr == nil || p.Tty { return } wg.Add(1) go func() { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithLogger(logger) defer wg.Done() defer io.Copy(io.Discard, p.remoteStderr) if _, err := io.Copy(p.Stderr, p.remoteStderr); err != nil { - runtime.HandleError(err) + runtime.HandleErrorWithLogger(logger, err, "Copying stderr failed") } }() } -func (p *streamProtocolV2) stream(conn streamCreator, ready chan<- struct{}) error { +func (p *streamProtocolV2) stream(logger klog.Logger, conn streamCreator, ready chan<- struct{}) error { if err := p.createStreams(conn); err != nil { return err } @@ -181,13 +182,13 @@ func (p *streamProtocolV2) stream(conn streamCreator, ready chan<- struct{}) err // now that all the streams have been created, proceed with reading & copying - errorChan := watchErrorStream(p.errorStream, &errorDecoderV2{}) + errorChan := watchErrorStream(logger, p.errorStream, &errorDecoderV2{}) - p.copyStdin() + p.copyStdin(logger) var wg sync.WaitGroup - p.copyStdout(&wg) - p.copyStderr(&wg) + p.copyStdout(logger, &wg) + p.copyStderr(logger, &wg) // we're waiting for stdout/stderr to finish copying wg.Wait() diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go index 412cee8d2f4f2..98872c7cc6b2c 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go @@ -26,8 +26,9 @@ import ( "time" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2/ktesting" + "k8s.io/streaming/pkg/httpstream" ) type fakeReader struct { @@ -179,6 +180,7 @@ func TestV2CreateStreams(t *testing.T) { } func TestV2ErrorStreamReading(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) tests := []struct { name string stream io.Reader @@ -217,7 +219,7 @@ func TestV2ErrorStreamReading(t *testing.T) { h := newStreamProtocolV2(StreamOptions{}).(*streamProtocolV2) h.errorStream = test.stream - ch := watchErrorStream(h.errorStream, &errorDecoderV2{}) + ch := watchErrorStream(logger, h.errorStream, &errorDecoderV2{}) if ch == nil { t.Fatalf("%s: unexpected nil channel", test.name) } diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v3.go b/staging/src/k8s.io/client-go/tools/remotecommand/v3.go index ece4cfafeb0ca..b1e533a8a0e54 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v3.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v3.go @@ -22,8 +22,9 @@ import ( "net/http" "sync" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/klog/v2" ) // streamProtocolV3 implements version 3 of the streaming protocol for attach @@ -62,12 +63,12 @@ func (p *streamProtocolV3) createStreams(conn streamCreator) error { return nil } -func (p *streamProtocolV3) handleResizes() { +func (p *streamProtocolV3) handleResizes(logger klog.Logger) { if p.resizeStream == nil || p.TerminalSizeQueue == nil { return } go func() { - defer runtime.HandleCrash() + defer runtime.HandleCrashWithLogger(logger) encoder := json.NewEncoder(p.resizeStream) for { @@ -76,13 +77,13 @@ func (p *streamProtocolV3) handleResizes() { return } if err := encoder.Encode(&size); err != nil { - runtime.HandleError(err) + runtime.HandleErrorWithLogger(logger, err, "Encoding terminal size failed") } } }() } -func (p *streamProtocolV3) stream(conn streamCreator, ready chan<- struct{}) error { +func (p *streamProtocolV3) stream(logger klog.Logger, conn streamCreator, ready chan<- struct{}) error { if err := p.createStreams(conn); err != nil { return err } @@ -94,15 +95,15 @@ func (p *streamProtocolV3) stream(conn streamCreator, ready chan<- struct{}) err // now that all the streams have been created, proceed with reading & copying - errorChan := watchErrorStream(p.errorStream, &errorDecoderV3{}) + errorChan := watchErrorStream(logger, p.errorStream, &errorDecoderV3{}) - p.handleResizes() + p.handleResizes(logger) - p.copyStdin() + p.copyStdin(logger) var wg sync.WaitGroup - p.copyStdout(&wg) - p.copyStderr(&wg) + p.copyStdout(logger, &wg) + p.copyStderr(logger, &wg) // we're waiting for stdout/stderr to finish copying wg.Wait() diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v4.go b/staging/src/k8s.io/client-go/tools/remotecommand/v4.go index ecedad071eba2..47018ba5fec8f 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v4.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v4.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/client-go/util/exec" + "k8s.io/klog/v2" ) // streamProtocolV4 implements version 4 of the streaming protocol for attach @@ -47,11 +48,11 @@ func (p *streamProtocolV4) createStreams(conn streamCreator) error { return p.streamProtocolV3.createStreams(conn) } -func (p *streamProtocolV4) handleResizes() { - p.streamProtocolV3.handleResizes() +func (p *streamProtocolV4) handleResizes(logger klog.Logger) { + p.streamProtocolV3.handleResizes(logger) } -func (p *streamProtocolV4) stream(conn streamCreator, ready chan<- struct{}) error { +func (p *streamProtocolV4) stream(logger klog.Logger, conn streamCreator, ready chan<- struct{}) error { if err := p.createStreams(conn); err != nil { return err } @@ -63,15 +64,15 @@ func (p *streamProtocolV4) stream(conn streamCreator, ready chan<- struct{}) err // now that all the streams have been created, proceed with reading & copying - errorChan := watchErrorStream(p.errorStream, &errorDecoderV4{}) + errorChan := watchErrorStream(logger, p.errorStream, &errorDecoderV4{}) - p.handleResizes() + p.handleResizes(logger) - p.copyStdin() + p.copyStdin(logger) var wg sync.WaitGroup - p.copyStdout(&wg) - p.copyStderr(&wg) + p.copyStdout(logger, &wg) + p.copyStderr(logger, &wg) // we're waiting for stdout/stderr to finish copying wg.Wait() diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v5.go b/staging/src/k8s.io/client-go/tools/remotecommand/v5.go index edfd3ccb26cf0..ca79a88287996 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v5.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v5.go @@ -16,6 +16,8 @@ limitations under the License. package remotecommand +import "k8s.io/klog/v2" + // streamProtocolV5 add support for V5 of the remote command subprotocol. // For the streamProtocolHandler, this version is the same as V4. type streamProtocolV5 struct { @@ -30,6 +32,6 @@ func newStreamProtocolV5(options StreamOptions) streamProtocolHandler { } } -func (p *streamProtocolV5) stream(conn streamCreator, ready chan<- struct{}) error { - return p.streamProtocolV4.stream(conn, ready) +func (p *streamProtocolV5) stream(logger klog.Logger, conn streamCreator, ready chan<- struct{}) error { + return p.streamProtocolV4.stream(logger, conn, ready) } diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go b/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go index e0433198fab99..f531e7ccffa17 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go @@ -29,11 +29,11 @@ import ( gwebsocket "github.com/gorilla/websocket" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/remotecommand" restclient "k8s.io/client-go/rest" "k8s.io/client-go/transport/websocket" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" ) // writeDeadline defines the time that a client-side write to the websocket @@ -130,7 +130,8 @@ func (e *wsStreamExecutor) StreamWithContext(ctx context.Context, options Stream } defer conn.Close() e.negotiated = conn.Subprotocol() - klog.V(4).Infof("The subprotocol is %s", e.negotiated) + logger := klog.FromContext(ctx) + logger.V(4).Info("Subprotocol negotiated", "protocol", e.negotiated) var streamer streamProtocolHandler switch e.negotiated { @@ -143,7 +144,7 @@ func (e *wsStreamExecutor) StreamWithContext(ctx context.Context, options Stream case remotecommand.StreamProtocolV2Name: streamer = newStreamProtocolV2(options) case "": - klog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name) + logger.V(4).Info("The server did not negotiate a streaming protocol version, falling back", "protocol", remotecommand.StreamProtocolV1Name) fallthrough case remotecommand.StreamProtocolV1Name: streamer = newStreamProtocolV1(options) @@ -159,7 +160,7 @@ func (e *wsStreamExecutor) StreamWithContext(ctx context.Context, options Stream }() readyChan := make(chan struct{}) - creator := newWSStreamCreator(conn) + creator := newWSStreamCreator(logger, conn) go func() { select { // Wait until all streams have been created before starting the readDemuxLoop. @@ -177,7 +178,7 @@ func (e *wsStreamExecutor) StreamWithContext(ctx context.Context, options Stream e.heartbeatDeadline, ) }() - errorChan <- streamer.stream(creator, readyChan) + errorChan <- streamer.stream(logger, creator, readyChan) }() select { @@ -191,7 +192,8 @@ func (e *wsStreamExecutor) StreamWithContext(ctx context.Context, options Stream } type wsStreamCreator struct { - conn *gwebsocket.Conn + logger klog.Logger + conn *gwebsocket.Conn // Protects writing to websocket connection; reading is lock-free connWriteLock sync.Mutex // map of stream id to stream; multiple streams read/write the connection @@ -202,8 +204,9 @@ type wsStreamCreator struct { setStreamErr error } -func newWSStreamCreator(conn *gwebsocket.Conn) *wsStreamCreator { +func newWSStreamCreator(logger klog.Logger, conn *gwebsocket.Conn) *wsStreamCreator { return &wsStreamCreator{ + logger: logger, conn: conn, streams: map[byte]*stream{}, } @@ -238,6 +241,7 @@ func (c *wsStreamCreator) CreateStream(headers http.Header) (httpstream.Stream, } reader, writer := io.Pipe() s := &stream{ + logger: klog.LoggerWithValues(c.logger, "id", id), headers: headers, readPipe: reader, writePipe: writer, @@ -260,11 +264,11 @@ func (c *wsStreamCreator) CreateStream(headers http.Header) (httpstream.Stream, // connection reader at a time (a read mutex would provide no benefit). func (c *wsStreamCreator) readDemuxLoop(bufferSize int, period time.Duration, deadline time.Duration) { // Initialize and start the ping/pong heartbeat. - h := newHeartbeat(c.conn, period, deadline) + h := newHeartbeat(c.logger, c.conn, period, deadline) // Set initial timeout for websocket connection reading. - klog.V(5).Infof("Websocket initial read deadline: %s", deadline) + c.logger.V(5).Info("Websocket read starts", "deadline", deadline) if err := c.conn.SetReadDeadline(time.Now().Add(deadline)); err != nil { - klog.Errorf("Websocket initial setting read deadline failed %v", err) + c.logger.Error(err, "Websocket initial setting read deadline failed") return } go h.start() @@ -308,7 +312,7 @@ func (c *wsStreamCreator) readDemuxLoop(bufferSize int, period time.Duration, de streamID := readBuffer[0] s := c.getStream(streamID) if s == nil { - klog.Errorf("Unknown stream id %d, discarding message", streamID) + c.logger.Error(nil, "Unknown stream, discarding message", "id", streamID) continue } for { @@ -351,6 +355,7 @@ func (c *wsStreamCreator) closeAllStreamReaders(err error) { } type stream struct { + logger klog.Logger headers http.Header readPipe *io.PipeReader writePipe *io.PipeWriter @@ -369,8 +374,8 @@ func (s *stream) Read(p []byte) (n int, err error) { // Write writes directly to the underlying WebSocket connection. func (s *stream) Write(p []byte) (n int, err error) { - klog.V(8).Infof("Write() on stream %d", s.id) - defer klog.V(8).Infof("Write() done on stream %d", s.id) + s.logger.V(8).Info("Write() on stream") + defer s.logger.V(8).Info("Write() done on stream") s.connWriteLock.Lock() defer s.connWriteLock.Unlock() if s.conn == nil { @@ -378,7 +383,7 @@ func (s *stream) Write(p []byte) (n int, err error) { } err = s.conn.SetWriteDeadline(time.Now().Add(writeDeadline)) if err != nil { - klog.V(4).Infof("Websocket setting write deadline failed %v", err) + s.logger.V(4).Info("Websocket setting write deadline failed", "err", err) return 0, err } // Message writer buffers the message data, so we don't need to do that ourselves. @@ -407,8 +412,8 @@ func (s *stream) Write(p []byte) (n int, err error) { // Close half-closes the stream, indicating this side is finished with the stream. func (s *stream) Close() error { - klog.V(6).Infof("Close() on stream %d", s.id) - defer klog.V(6).Infof("Close() done on stream %d", s.id) + s.logger.V(6).Info("Close() on stream") + defer s.logger.V(6).Info("Close() done on stream") s.connWriteLock.Lock() defer s.connWriteLock.Unlock() if s.conn == nil { @@ -421,8 +426,8 @@ func (s *stream) Close() error { } func (s *stream) Reset() error { - klog.V(4).Infof("Reset() on stream %d", s.id) - defer klog.V(4).Infof("Reset() done on stream %d", s.id) + s.logger.V(4).Info("Reset() on stream") + defer s.logger.V(4).Info("Reset() done on stream") s.Close() return s.writePipe.Close() } @@ -442,7 +447,8 @@ func (s *stream) Identifier() uint32 { // inside the "readDemuxLoop" will return an i/o error prompting a connection close // and cleanup. type heartbeat struct { - conn *gwebsocket.Conn + logger klog.Logger + conn *gwebsocket.Conn // period defines how often a "ping" heartbeat message is sent to the other endpoint period time.Duration // closing the "closer" channel will clean up the heartbeat timers @@ -456,8 +462,9 @@ type heartbeat struct { // newHeartbeat creates heartbeat structure encapsulating fields necessary to // run the websocket connection ping/pong mechanism and sets up handlers on // the websocket connection. -func newHeartbeat(conn *gwebsocket.Conn, period time.Duration, deadline time.Duration) *heartbeat { +func newHeartbeat(logger klog.Logger, conn *gwebsocket.Conn, period time.Duration, deadline time.Duration) *heartbeat { h := &heartbeat{ + logger: logger, conn: conn, period: period, closer: make(chan struct{}), @@ -467,10 +474,10 @@ func newHeartbeat(conn *gwebsocket.Conn, period time.Duration, deadline time.Dur // be empty. h.conn.SetPongHandler(func(msg string) error { // Push the read deadline into the future. - klog.V(6).Infof("Pong message received (%s)--resetting read deadline", msg) + logger.V(6).Info("Pong message received -- resetting read deadline", "message", msg) err := h.conn.SetReadDeadline(time.Now().Add(deadline)) if err != nil { - klog.Errorf("Websocket setting read deadline failed %v", err) + logger.Error(err, "Websocket setting read deadline failed") return err } if len(msg) > 0 { @@ -502,16 +509,16 @@ func (h *heartbeat) start() { for { select { case <-h.closer: - klog.V(5).Infof("closed channel--returning") + h.logger.V(5).Info("Closed channel -- returning") return case <-t.C: // "WriteControl" does not need to be protected by a mutex. According to // gorilla/websockets library docs: "The Close and WriteControl methods can // be called concurrently with all other methods." if err := h.conn.WriteControl(gwebsocket.PingMessage, h.message, time.Now().Add(pingReadDeadline)); err == nil { - klog.V(6).Infof("Websocket Ping succeeeded") + h.logger.V(6).Info("Websocket Ping succeeeded") } else { - klog.Errorf("Websocket Ping failed: %v", err) + h.logger.Error(err, "Websocket Ping failed") if errors.Is(err, gwebsocket.ErrCloseSent) { // we continue because c.conn.CloseChan will manage closing the connection already continue diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go index 9f064d935f498..ebfcaaade178d 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go @@ -42,12 +42,13 @@ import ( v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/klog/v2/ktesting" + "k8s.io/streaming/pkg/httpstream/wsstream" ) // TestWebSocketClient_LoopbackStdinToStdout returns random data sent on the STDIN channel @@ -1049,6 +1050,7 @@ func TestWebSocketClient_ExecutorErrors(t *testing.T) { } func TestWebSocketClient_HeartbeatSucceeds(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) var upgrader = gwebsocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // Accepting all requests @@ -1081,7 +1083,7 @@ func TestWebSocketClient_HeartbeatSucceeds(t *testing.T) { var expectedMsg = "test heartbeat message" var period = 100 * time.Millisecond var deadline = 200 * time.Millisecond - heartbeat := newHeartbeat(client, period, deadline) + heartbeat := newHeartbeat(logger, client, period, deadline) heartbeat.setMessage(expectedMsg) // Add a channel to the handler to retrieve the "pong" message. pongMsgCh := make(chan string) @@ -1121,7 +1123,8 @@ func TestWebSocketClient_HeartbeatSucceeds(t *testing.T) { } func TestLateStreamCreation(t *testing.T) { - c := newWSStreamCreator(nil) + logger, _ := ktesting.NewTestContext(t) + c := newWSStreamCreator(logger, nil) c.closeAllStreamReaders(nil) if err := c.setStream(0, nil); err == nil { t.Fatal("expected error adding stream after closeAllStreamReaders") @@ -1129,8 +1132,10 @@ func TestLateStreamCreation(t *testing.T) { } func TestWebSocketClient_StreamsAndExpectedErrors(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + // Validate Stream functions. - c := newWSStreamCreator(nil) + c := newWSStreamCreator(logger, nil) headers := http.Header{} headers.Set(v1.StreamType, v1.StreamTypeStdin) s, err := c.CreateStream(headers) diff --git a/staging/src/k8s.io/client-go/tools/watch/informerwatcher_test.go b/staging/src/k8s.io/client-go/tools/watch/informerwatcher_test.go index 07cea00237658..627b7ebaf7a3b 100644 --- a/staging/src/k8s.io/client-go/tools/watch/informerwatcher_test.go +++ b/staging/src/k8s.io/client-go/tools/watch/informerwatcher_test.go @@ -31,7 +31,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" clientfeatures "k8s.io/client-go/features" @@ -39,6 +38,7 @@ import ( fakeclientset "k8s.io/client-go/kubernetes/fake" testcore "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" + "k8s.io/utils/dump" ) // TestEventProcessorExit is expected to timeout if the event processor fails diff --git a/staging/src/k8s.io/client-go/tools/watch/retrywatcher.go b/staging/src/k8s.io/client-go/tools/watch/retrywatcher.go index 45249d8e473e2..7d702907f91d4 100644 --- a/staging/src/k8s.io/client-go/tools/watch/retrywatcher.go +++ b/staging/src/k8s.io/client-go/tools/watch/retrywatcher.go @@ -26,12 +26,12 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" + "k8s.io/utils/dump" ) // resourceVersionGetter is an interface used to get resource version from events. diff --git a/staging/src/k8s.io/client-go/tools/watch/retrywatcher_test.go b/staging/src/k8s.io/client-go/tools/watch/retrywatcher_test.go index b831cacec52c9..3c22297797322 100644 --- a/staging/src/k8s.io/client-go/tools/watch/retrywatcher_test.go +++ b/staging/src/k8s.io/client-go/tools/watch/retrywatcher_test.go @@ -32,12 +32,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" + "k8s.io/utils/dump" ) func init() { @@ -548,7 +548,6 @@ func TestRetryWatcher(t *testing.T) { } for _, tc := range tt { - tc := tc t.Run(tc.name, func(t *testing.T) { _, ctx := ktesting.NewTestContext(t) t.Parallel() diff --git a/staging/src/k8s.io/client-go/transport/ca_rotation.go b/staging/src/k8s.io/client-go/transport/ca_rotation.go new file mode 100644 index 0000000000000..b802eedf484b2 --- /dev/null +++ b/staging/src/k8s.io/client-go/transport/ca_rotation.go @@ -0,0 +1,154 @@ +/* +Copyright 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 transport + +import ( + "bytes" + "context" + "net/http" + "os" + "sync" + "time" + + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/tools/metrics" + "k8s.io/klog/v2" + "k8s.io/utils/clock" +) + +var _ utilnet.RoundTripperWrapper = &atomicTransportHolder{} + +// atomicTransportHolder holds a transport that can be atomically updated +// when CA files change, enabling graceful CA rotation without cache complexity +type atomicTransportHolder struct { + caFile string + currentCAData []byte // Track the actual CA data currently in use + // clock and caRefreshDuration are used to allow for testing time-based logic. + clock clock.Clock + caRefreshDuration time.Duration + // mu covers transport and transportLastChecked + mu sync.RWMutex + transport *http.Transport + transportLastChecked time.Time +} + +func (h *atomicTransportHolder) RoundTrip(req *http.Request) (*http.Response, error) { + return h.getTransport(req.Context()).RoundTrip(req) +} + +func (h *atomicTransportHolder) WrappedRoundTripper() http.RoundTripper { + h.mu.RLock() + defer h.mu.RUnlock() + + return h.transport +} + +func (h *atomicTransportHolder) getTransport(ctx context.Context) *http.Transport { + if rt := h.getTransportIfFresh(); rt != nil { + return rt + } + + h.mu.Lock() + defer h.mu.Unlock() + + h.tryRefreshTransportLocked(ctx) + return h.transport +} + +func (h *atomicTransportHolder) getTransportIfFresh() *http.Transport { + h.mu.RLock() + defer h.mu.RUnlock() + + if h.clock.Since(h.transportLastChecked) < h.caRefreshDuration { + return h.transport + } + return nil +} + +func (h *atomicTransportHolder) tryRefreshTransportLocked(ctx context.Context) { + // If some other goroutine already checked/updated the CA + if h.clock.Since(h.transportLastChecked) < h.caRefreshDuration { + return + } + + // only attempt CA reload once per caRefreshDuration, even if the reload fails + h.transportLastChecked = h.clock.Now() + + logger := klog.FromContext(ctx).WithValues("caFile", h.caFile) + + logger.V(4).Info("Checking CA file content") + + // Load new CA data from file + newCAData, err := os.ReadFile(h.caFile) + // Return old transport on read error + if err != nil { + logger.Error(err, "Failed to read CA data from file") + metrics.TransportCAReloads.Increment("failure", "read_error") + return + } + + if len(newCAData) == 0 { + logger.Info("CA file empty, skipping transport rotation") + metrics.TransportCAReloads.Increment("failure", "empty") + return + } + + if bytes.Equal(h.currentCAData, newCAData) { + logger.V(4).Info("CA file unchanged, skipping transport rotation") + metrics.TransportCAReloads.Increment("success", "unchanged") + return + } + + logger.V(4).Info("CA content changed, updating transport") + + // Load new CA pool + newCAs, err := rootCertPool(newCAData) + // Return old transport on parse error + if err != nil { + logger.Error(err, "Failed to parse CA data from file") + metrics.TransportCAReloads.Increment("failure", "ca_parse_error") + return + } + newTransport := h.transport.Clone() + newTransport.TLSClientConfig.RootCAs = newCAs + oldTransport := h.transport + h.transport = newTransport + // Update our tracking of current CA data + h.currentCAData = newCAData + + // Close idle connections on the old transport to encourage migration + oldTransport.CloseIdleConnections() + + logger.V(4).Info("Transport updated for CA rotation") + metrics.TransportCAReloads.Increment("success", "updated") +} + +// newAtomicTransportHolder creates a new holder for CA file reloading scenarios. +// The caFile must be specified. +// caData may be empty but should correspond to the contents of caFile. +// transport must have a TLS config and its root CAs should match caData. +func newAtomicTransportHolder(caFile string, caData []byte, transport *http.Transport) *atomicTransportHolder { + c := clock.RealClock{} + return &atomicTransportHolder{ + caFile: caFile, + currentCAData: caData, + clock: c, + caRefreshDuration: 5 * time.Minute, + transport: transport, + transportLastChecked: c.Now(), + } +} diff --git a/staging/src/k8s.io/client-go/transport/ca_rotation_test.go b/staging/src/k8s.io/client-go/transport/ca_rotation_test.go new file mode 100644 index 0000000000000..5bfa64516e28f --- /dev/null +++ b/staging/src/k8s.io/client-go/transport/ca_rotation_test.go @@ -0,0 +1,560 @@ +/* +Copyright 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 transport + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/wait" + clientgofeaturegate "k8s.io/client-go/features" + clientfeaturestesting "k8s.io/client-go/features/testing" + "k8s.io/client-go/tools/metrics" + "k8s.io/client-go/util/cert" + testingclock "k8s.io/utils/clock/testing" +) + +const ( + // Use the same rootCACert as transport_test.go + testCACert1 = `-----BEGIN CERTIFICATE----- +MIIC4DCCAcqgAwIBAgIBATALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu +MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIxNTczN1oXDTE2MDExNTIxNTcz +OFowIzEhMB8GA1UEAwwYMTAuMTMuMTI5LjEwNkAxNDIxMzU5MDU4MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunDRXGwsiYWGFDlWH6kjGun+PshDGeZX +xtx9lUnL8pIRWH3wX6f13PO9sktaOWW0T0mlo6k2bMlSLlSZgG9H6og0W6gLS3vq +s4VavZ6DbXIwemZG2vbRwsvR+t4G6Nbwelm6F8RFnA1Fwt428pavmNQ/wgYzo+T1 +1eS+HiN4ACnSoDSx3QRWcgBkB1g6VReofVjx63i0J+w8Q/41L9GUuLqquFxu6ZnH +60vTB55lHgFiDLjA1FkEz2dGvGh/wtnFlRvjaPC54JH2K1mPYAUXTreoeJtLJKX0 +ycoiyB24+zGCniUmgIsmQWRPaOPircexCp1BOeze82BT1LCZNTVaxQIDAQABoyMw +ITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQsD +ggEBADMxsUuAFlsYDpF4fRCzXXwrhbtj4oQwcHpbu+rnOPHCZupiafzZpDu+rw4x +YGPnCb594bRTQn4pAu3Ac18NbLD5pV3uioAkv8oPkgr8aUhXqiv7KdDiaWm6sbAL +EHiXVBBAFvQws10HMqMoKtO8f1XDNAUkWduakR/U6yMgvOPwS7xl0eUTqyRB6zGb +K55q2dejiFWaFqB/y78txzvz6UlOZKE44g2JAVoJVM6kGaxh33q8/FmrL4kuN3ut +W+MmJCVDvd4eEqPwbp7146ZWTqpIJ8lvA6wuChtqV8lhAPka2hD/LMqY8iXNmfXD +uml0obOEy+ON91k+SWTJ3ggmF/U= +-----END CERTIFICATE-----` + + // A different CA cert for testing rotation (modified version of certData from transport_test.go) + testCACert2 = `-----BEGIN CERTIFICATE----- +MIIC6jCCAdSgAwIBAgIBCzALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu +MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIyMDEzMVoXDTE2MDExNTIyMDEz +MlowGzEZMBcGA1UEAxMQb3BlbnNoaWZ0LWNsaWVudDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKtdhz0+uCLXw5cSYns9rU/XifFSpb/x24WDdrm72S/v +b9BPYsAStiP148buylr1SOuNi8sTAZmlVDDIpIVwMLff+o2rKYDicn9fjbrTxTOj +lI4pHJBH+JU3AJ0tbajupioh70jwFS0oYpwtneg2zcnE2Z4l6mhrj2okrc5Q1/X2 +I2HChtIU4JYTisObtin10QKJX01CLfYXJLa8upWzKZ4/GOcHG+eAV3jXWoXidtjb +1Usw70amoTZ6mIVCkiu1QwCoa8+ycojGfZhvqMsAp1536ZcCul+Na+AbCv4zKS7F +kQQaImVrXdUiFansIoofGlw/JNuoKK6ssVpS5Ic3pgcCAwEAAaM1MDMwDgYDVR0P +AQH/BAQDAgCgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJ +KoZIhvcNAQELA4IBAQCKLREH7bXtXtZ+8vI6cjD7W3QikiArGqbl36bAhhWsJLp/ +p/ndKz39iFNaiZ3GlwIURWOOKx3y3GA0x9m8FR+Llthf0EQ8sUjnwaknWs0Y6DQ3 +jjPFZOpV3KPCFrdMJ3++E3MgwFC/Ih/N2ebFX9EcV9Vcc6oVWMdwT0fsrhu683rq +6GSR/3iVX1G/pmOiuaR0fNUaCyCfYrnI4zHBDgSfnlm3vIvN2lrsR/DQBakNL8DJ +HBgKxMGeUPoneBv+c8DMXIL0EhaFXRlBv9QW45/GiAIOuyFJ0i6hCtGZpJjq4OpQ +BRjCI+izPzFTjsxD4aORE+WOkyWFCGPWKfNejfw0 +-----END CERTIFICATE-----` +) + +// writeCAFile writes CA data to a temporary file +func writeCAFile(t testing.TB, caData []byte) string { + tmpDir := t.TempDir() + caFile := filepath.Join(tmpDir, "ca.crt") + + err := os.WriteFile(caFile, caData, 0644) + if err != nil { + t.Fatalf("Failed to write CA file: %v", err) + } + t.Cleanup(func() { + if err := os.Remove(caFile); err != nil { + t.Logf("unexpected error while removing file: %s - %v", caFile, err) + } + }) + return caFile +} + +// createTestTransport creates a test transport with TLS config +func createTestTransport(t testing.TB, caData []byte) *http.Transport { + CAs, err := rootCertPool(caData) + if err != nil { + t.Fatalf("Failed to parse CA certificate") + } + return &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: CAs, + }, + } +} + +func TestCheckCAFileAndRotate(t *testing.T) { + tests := []struct { + name string + setupCA []byte + updateCA []byte + caFileOverride string + expectRotation bool + }{ + { + name: "no change", + setupCA: []byte(testCACert1), + updateCA: []byte(testCACert1), // Same CA + expectRotation: false, + }, + { + name: "CA changed", + setupCA: []byte(testCACert1), + updateCA: []byte(testCACert2), // Different CA + expectRotation: true, + }, + { + name: "CA changed to invalid", + setupCA: []byte(testCACert1), + updateCA: []byte("panda"), // invalid CA + expectRotation: false, + }, + { + name: "file error", + setupCA: []byte(testCACert1), + caFileOverride: "/nonexistent/ca.crt", // Non-existent file + expectRotation: false, + }, + { + name: "empty file content", + setupCA: []byte(testCACert1), + updateCA: []byte{}, // Empty file + expectRotation: false, + }, + { + name: "initially empty CA file updated to valid CA", + setupCA: []byte{}, // Starts with empty CA + updateCA: []byte(testCACert1), // Populated with a valid CA + expectRotation: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caFile := writeCAFile(t, tt.setupCA) + if len(tt.caFileOverride) > 0 { + caFile = tt.caFileOverride + } + + transport := createTestTransport(t, tt.setupCA) + setupRoots := transport.TLSClientConfig.RootCAs.Clone() + + expectedRoots := setupRoots + if tt.expectRotation { + var err error + expectedRoots, err = rootCertPool(tt.updateCA) + if err != nil { + t.Fatal(err) + } + } + + clock := testingclock.NewFakeClock(time.Now()) + holder := newAtomicTransportHolder(caFile, tt.setupCA, transport) + holder.clock = clock + holder.transportLastChecked = clock.Now() + + if tt.updateCA != nil { + // Update the file with new CA content + err := os.WriteFile(caFile, tt.updateCA, 0644) + if err != nil { + t.Errorf("Failed to update CA data with file address: %s", caFile) + } + } + + clock.Step(holder.caRefreshDuration) + + // Check CA file rotation + newTransport := holder.getTransport(t.Context()) + newRoots := newTransport.TLSClientConfig.RootCAs + + if newRoots == nil || !expectedRoots.Equal(newRoots) { + t.Error("new roots did not match expected roots") + } + + transportRotated := newTransport != transport + if tt.expectRotation != transportRotated { + t.Error("transport rotation did not match") + } + + }) + } +} + +func generateServerCertAndCA(t testing.TB) (servingCertPEM, servingKeyPEM, caCertPEM []byte) { + t.Helper() + certPEM, keyPEM, err := cert.GenerateSelfSignedCertKey("127.0.0.1", nil, nil) + if err != nil { + t.Fatalf("Failed to generate server cert: %v", err) + } + certs, err := cert.ParseCertsPEM(certPEM) + if err != nil || len(certs) < 2 { + t.Fatal("Expected cert chain with [leaf, CA]") + } + caPEM, err := cert.EncodeCertificates(certs[len(certs)-1]) + if err != nil { + t.Fatalf("Failed to encode CA cert: %v", err) + } + return certPEM, keyPEM, caPEM +} + +// TestCARotationConnectionBehavior tests end-to-end CA rotation: +// 1. Client trusts server CA via a CA file on disk +// 2. Server rotates to a new CA + serving cert +// 3. Client fails (doesn't trust new CA yet) +// 4. CA file updated, transport reloads, client reconnects +func TestCARotationConnectionBehavior(t *testing.T) { + t.Log("Testing CA Rotation Connection Behavior") + + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, true) + + // Generate initial server v1 cert and CA + servingCertPEM1, servingKeyPEM1, caCertPEM1 := generateServerCertAndCA(t) + + // Start server v1 (no client cert required - test focuses on server CA rotation) + srv1 := newTestServer(t, servingCertPEM1, servingKeyPEM1) + srv1.StartTLS() + defer srv1.Close() + + // Set up the client + clientCAFile := writeCAFile(t, caCertPEM1) + config := &Config{ + TLS: TLSConfig{ + CAFile: clientCAFile, + }, + } + + transport, err := New(config) + if err != nil { + t.Fatalf("Failed to create transport: %v", err) + } + transport.(*trackedTransport).rt.(*atomicTransportHolder).caRefreshDuration = 500 * time.Millisecond + + client := &http.Client{ + Transport: transport, + Timeout: 30 * time.Second, + } + + // Initial connection must succeed + t.Log("Making initial request to server v1, expecting success...") + resp, err := client.Get(srv1.URL) + if err != nil { + t.Fatalf("Failed to call the server v1: %v", err) + } + if err := resp.Body.Close(); err != nil { + t.Fatal("Failed to close the response.") + } + if resp.StatusCode != http.StatusOK { + t.Fatal("Failed to call the server successfully.") + } + + t.Log("Initial connection successful.") + + // Rotate: new CA, new server cert, same address + t.Log("Stopping server v1 and starting server v2 with new CA...") + srv1Addr := srv1.Listener.Addr().String() + srv1.Close() + + servingCertPEM2, servingKeyPEM2, caCertPEM2 := generateServerCertAndCA(t) + srv2 := newTestServer(t, servingCertPEM2, servingKeyPEM2) + l, err := net.Listen("tcp", srv1Addr) + if err != nil { + t.Fatalf("Failed to re-claim the same server address: %v", err) + } + + srv2.Listener = l + srv2.StartTLS() + defer srv2.Close() + + // Must fail - client still trusts old CA + t.Log("Making request to server v2, expecting failure...") + _, err = client.Get(srv2.URL) + if err == nil { + t.Fatal("The request should fail.") + } + t.Log("Request failed as expected.") + + // Update CA file to trust new CA + t.Log("Updating client CA file on disk to trust new CA...") + if err := os.WriteFile(clientCAFile, caCertPEM2, 0644); err != nil { + t.Fatalf("Failed to update CA file: %v", err) + } + + // Poll until transport reloads + t.Log("Polling server v2 until the client's transport reloads the new CA...") + var lastPollErr error + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + pollErr := wait.PollUntilContextCancel(ctx, 500*time.Millisecond, true, func(ctx context.Context) (bool, error) { + resp, err := client.Get(srv2.URL) + if err != nil { + lastPollErr = err + t.Log("Client failed to connect before the root CAs are updated, will retry...") + return false, nil // Error is expected, continue polling + } + if err := resp.Body.Close(); err != nil { + t.Fatal("Failed to close the response.") + } + if resp.StatusCode == http.StatusOK { + return true, nil // Success! Stop polling. + } + return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + }) + if pollErr != nil { + t.Fatalf("Client failed to reconnect after CA rotation. Last error: %v. Test error: %v", lastPollErr, pollErr) + } + + t.Log("Success! Client reconnected after CA was refreshed.") +} + +func TestCARotationConnectionBehavior_Disabled(t *testing.T) { + t.Log("Testing CA Rotation Connection Behavior (Feature Disabled)") + + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, false) + + // Generate initial server v1 cert and CA + servingCertPEM1, servingKeyPEM1, caCertPEM1 := generateServerCertAndCA(t) + + // Start server v1 (no client cert required - test focuses on server CA rotation) + srv1 := newTestServer(t, servingCertPEM1, servingKeyPEM1) + srv1.StartTLS() + defer srv1.Close() + + // Set up the client + clientCAFile := writeCAFile(t, caCertPEM1) + config := &Config{ + TLS: TLSConfig{ + CAFile: clientCAFile, + }, + } + + transport, err := New(config) + if err != nil { + t.Fatalf("Failed to create transport: %v", err) + } + if _, ok := transport.(*atomicTransportHolder); ok { + t.Fatal("Expected plain transport when the feature gate is disabled, got atomicTransportHolder") + } + + client := &http.Client{ + Transport: transport, + Timeout: 30 * time.Second, + } + + // Initial connection must succeed + t.Log("Making initial request to server v1, expecting success...") + resp, err := client.Get(srv1.URL) + if err != nil { + t.Fatalf("Failed to call the server v1: %v", err) + } + if err := resp.Body.Close(); err != nil { + t.Fatal("Failed to close the response.") + } + if resp.StatusCode != http.StatusOK { + t.Fatal("Failed to call the server successfully.") + } + + t.Log("Initial connection successful.") + + // Rotate: new CA, new server cert, same address + t.Log("Stopping server v1 and starting server v2 with new CA...") + srv1Addr := srv1.Listener.Addr().String() + srv1.Close() + + servingCertPEM2, servingKeyPEM2, caCertPEM2 := generateServerCertAndCA(t) + srv2 := newTestServer(t, servingCertPEM2, servingKeyPEM2) + l, err := net.Listen("tcp", srv1Addr) + if err != nil { + t.Fatalf("Failed to re-claim the same server address: %v", err) + } + + srv2.Listener = l + srv2.StartTLS() + defer srv2.Close() + + // Must fail - client still trusts old CA + t.Log("Making request to server v2, expecting failure...") + _, err = client.Get(srv2.URL) + if err == nil { + t.Fatal("The request should fail.") + } + t.Log("Request failed as expected.") + + // Update CA file to trust new CA + t.Log("Updating client CA file on disk to trust new CA...") + if err := os.WriteFile(clientCAFile, caCertPEM2, 0644); err != nil { + t.Fatalf("Failed to update CA file: %v", err) + } + + // Poll to ensure transport DOES NOT reload + t.Log("Polling server v2 to verify the client DOES NOT reconnect...") + var lastPollErr error + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + pollErr := wait.PollUntilContextCancel(ctx, 500*time.Millisecond, true, func(ctx context.Context) (bool, error) { + resp, err := client.Get(srv2.URL) + if err != nil { + lastPollErr = err + t.Log("Client failed to connect (expected because feature is disabled)...") + return false, nil // Keep polling until the timeout is reached + } + if err := resp.Body.Close(); err != nil { + t.Fatal("Failed to close the response.") + } + if resp.StatusCode == http.StatusOK { + return true, nil // Success! But this means the test failed. + } + return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + }) + + // If pollErr is nil, it means the connection succeeded, which is wrong for this test. + if pollErr == nil { + t.Fatalf("Client unexpectedly reconnected after CA rotation! The feature gate is disabled, so this should have failed.") + } + + t.Logf("Success! Client permanently failed to reconnect as expected. Last error: %v", lastPollErr) +} + +// testCAReloadsMetric is a fake metric recorder that records calls to Increment. +type testCAReloadsMetric struct { + calls []caReloadCall +} + +type caReloadCall struct { + result, reason string +} + +func (m *testCAReloadsMetric) Increment(result, reason string) { + m.calls = append(m.calls, caReloadCall{result, reason}) +} + +// TestCARotationMetricsEmitted verifies that ca_rotation.go emits the correct +// metrics during actual CA reload operations. +func TestCARotationMetricsEmitted(t *testing.T) { + fakeMetricRecorder := &testCAReloadsMetric{} + origMetric := metrics.TransportCAReloads + metrics.TransportCAReloads = fakeMetricRecorder + t.Cleanup(func() { metrics.TransportCAReloads = origMetric }) + + caData := []byte(testCACert1) + caFile := writeCAFile(t, caData) + transport := createTestTransport(t, caData) + + clock := testingclock.NewFakeClock(time.Now()) + holder := newAtomicTransportHolder(caFile, caData, transport) + holder.clock = clock + holder.transportLastChecked = clock.Now() + + tests := []struct { + name string + setup func() error + wantResult string + wantReason string + }{ + { + name: "unchanged CA", + setup: func() error { return nil }, + wantResult: "success", + wantReason: "unchanged", + }, + { + name: "updated CA", + setup: func() error { + return os.WriteFile(caFile, []byte(testCACert2), 0644) + }, + wantResult: "success", + wantReason: "updated", + }, + { + name: "empty file", + setup: func() error { + return os.WriteFile(caFile, []byte{}, 0644) + }, + wantResult: "failure", + wantReason: "empty", + }, + { + name: "invalid CA data", + setup: func() error { + return os.WriteFile(caFile, []byte("not-a-cert"), 0644) + }, + wantResult: "failure", + wantReason: "ca_parse_error", + }, + { + name: "read error", + setup: func() error { + return os.Remove(caFile) + }, + wantResult: "failure", + wantReason: "read_error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeMetricRecorder.calls = nil + err := tt.setup() + if err != nil { + t.Fatalf("Setup failed: %v", err) + } + clock.Step(holder.caRefreshDuration) + holder.getTransport(context.Background()) + + if len(fakeMetricRecorder.calls) != 1 { + t.Fatalf("Expected 1 metric call, got %d", len(fakeMetricRecorder.calls)) + } + if fakeMetricRecorder.calls[0].result != tt.wantResult || fakeMetricRecorder.calls[0].reason != tt.wantReason { + t.Errorf("Got metric(%s, %s), want (%s, %s)", + fakeMetricRecorder.calls[0].result, fakeMetricRecorder.calls[0].reason, tt.wantResult, tt.wantReason) + } + }) + } +} + +// helper to create a simple, non-blocking test server with a given certificate. +func newTestServer(t *testing.T, certPEM, keyPEM []byte) *httptest.Server { + t.Helper() + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if _, err := fmt.Fprint(w, "ok"); err != nil { + t.Fatal("Failed to write to the response.") + } + })) + // Configure server TLS + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + t.Fatalf("Failed to create server cert: %v", err) + } + + server.TLS = &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + } + return server +} diff --git a/staging/src/k8s.io/client-go/transport/cache.go b/staging/src/k8s.io/client-go/transport/cache.go index b8dd866119d9c..16b62ec9fe070 100644 --- a/staging/src/k8s.io/client-go/transport/cache.go +++ b/staging/src/k8s.io/client-go/transport/cache.go @@ -21,12 +21,14 @@ import ( "fmt" "net" "net/http" + "runtime" "strings" "sync" "time" + "weak" utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/wait" + clientgofeaturegate "k8s.io/client-go/features" "k8s.io/client-go/tools/metrics" "k8s.io/klog/v2" ) @@ -35,22 +37,26 @@ import ( // same RoundTripper will be returned for configs with identical TLS options If // the config has no custom TLS options, http.DefaultTransport is returned. type tlsTransportCache struct { - mu sync.Mutex - transports map[tlsCacheKey]*http.Transport + mu sync.Mutex + transports map[tlsCacheKey]weak.Pointer[trackedTransport] // GC-enabled + strongTransports map[tlsCacheKey]http.RoundTripper // GC-disabled } -// DialerStopCh is stop channel that is passed down to dynamic cert dialer. -// It's exposed as variable for testing purposes to avoid testing for goroutine -// leakages. -var DialerStopCh = wait.NeverStop - const idleConnsPerHost = 25 -var tlsCache = &tlsTransportCache{transports: make(map[tlsCacheKey]*http.Transport)} +var tlsCache = newTLSCache() + +func newTLSCache() *tlsTransportCache { + return &tlsTransportCache{ + transports: make(map[tlsCacheKey]weak.Pointer[trackedTransport]), + strongTransports: make(map[tlsCacheKey]http.RoundTripper), + } +} type tlsCacheKey struct { insecure bool caData string + caFile string certData string keyData string `datapolicy:"security-key"` certFile string @@ -68,8 +74,8 @@ func (t tlsCacheKey) String() string { if len(t.keyData) > 0 { keyText = "" } - return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t, getCert:%p, dial:%p", - t.insecure, t.caData, t.certData, keyText, t.serverName, t.disableCompression, t.getCert, t.dial) + return fmt.Sprintf("insecure:%v, caData:%#v, caFile:%s, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t, getCert:%p, dial:%p", + t.insecure, t.caData, t.caFile, t.certData, keyText, t.serverName, t.disableCompression, t.getCert, t.dial) } func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { @@ -82,14 +88,18 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { // Ensure we only create a single transport for the given TLS options c.mu.Lock() defer c.mu.Unlock() - defer metrics.TransportCacheEntries.Observe(len(c.transports)) + defer func() { metrics.TransportCacheEntries.Observe(c.lenLocked()) }() // See if we already have a custom transport for this config - if t, ok := c.transports[key]; ok { - metrics.TransportCreateCalls.Increment("hit") - return t, nil + if t, ok := c.getLocked(key); ok { + if t != nil { + metrics.TransportCreateCalls.Increment("hit") + return t, nil + } + metrics.TransportCreateCalls.Increment("miss-gc") + } else { + metrics.TransportCreateCalls.Increment("miss") } - metrics.TransportCreateCalls.Increment("miss") } else { metrics.TransportCreateCalls.Increment("uncacheable") } @@ -116,6 +126,7 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { // If we use are reloading files, we need to handle certificate rotation properly // TODO(jackkleeman): We can also add rotation here when config.HasCertCallback() is true + var cancel context.CancelFunc if config.TLS.ReloadTLSFiles && tlsConfig != nil && tlsConfig.GetClientCertificate != nil { // The TLS cache is a singleton, so sharing the same name for all of its // background activity seems okay. @@ -123,7 +134,9 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { dynamicCertDialer := certRotatingDialer(logger, tlsConfig.GetClientCertificate, dial) tlsConfig.GetClientCertificate = dynamicCertDialer.GetClientCertificate dial = dynamicCertDialer.connDialer.DialContext - go dynamicCertDialer.run(DialerStopCh) + var ctx context.Context + ctx, cancel = context.WithCancel(context.Background()) + go dynamicCertDialer.run(ctx.Done()) } proxy := http.ProxyFromEnvironment @@ -131,7 +144,7 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { proxy = config.Proxy } - transport := utilnet.SetTransportDefaults(&http.Transport{ + httpTransport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: proxy, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: tlsConfig, @@ -139,13 +152,101 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { DialContext: dial, DisableCompression: config.DisableCompression, }) + var transport http.RoundTripper = httpTransport + + if config.TLS.ReloadCAFiles && tlsConfig != nil && tlsConfig.RootCAs != nil && len(config.TLS.CAFile) > 0 { + transport = newAtomicTransportHolder(config.TLS.CAFile, config.TLS.CAData, httpTransport) + } + + if !canCache && cancel == nil { + return transport, nil // uncacheable config with no cert rotation - nothing to GC + } + + if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) { + if canCache { + c.strongTransports[key] = transport + } + return transport, nil // cancel is intentionally discarded and the cert rotation go routine leaks + } + + transportWithGC := &trackedTransport{rt: transport} + + if cancel != nil { + // capture metric as local var so that cleanups do not influence other tests via globals + transportCertRotationGCCalls := metrics.TransportCertRotationGCCalls + runtime.AddCleanup(transportWithGC, func(_ struct{}) { + cancel() + transportCertRotationGCCalls.Increment() + }, struct{}{}) + } if canCache { - // Cache a single transport for these options - c.transports[key] = transport + wp := weak.Make(transportWithGC) + c.transports[key] = wp + // capture metrics as local vars so that cleanups do not influence other tests via globals + transportCacheGCCalls := metrics.TransportCacheGCCalls + transportCacheEntries := metrics.TransportCacheEntries + runtime.AddCleanup(transportWithGC, func(key tlsCacheKey) { + c.mu.Lock() + defer c.mu.Unlock() + + // make sure we only delete the weak pointer created by this specific setLocked call + if c.transports[key] != wp { + transportCacheGCCalls.Increment("skipped") + return + } + delete(c.transports, key) + transportCacheGCCalls.Increment("deleted") + transportCacheEntries.Observe(c.lenLocked()) + }, key) } - return transport, nil + return transportWithGC, nil +} + +func (c *tlsTransportCache) getLocked(key tlsCacheKey) (http.RoundTripper, bool) { + if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) { + v, ok := c.strongTransports[key] + return v, ok + } + + wp, ok := c.transports[key] + if !ok { + return nil, false + } + + v := wp.Value() + + if v == nil { // avoid typed nil + return nil, true // key exists but value has been garbage collected + } + + return v, true +} + +func (c *tlsTransportCache) lenLocked() int { + if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) { + return len(c.strongTransports) + } + return len(c.transports) +} + +// trackedTransport wraps an http.RoundTripper to serve as the weak.Pointer +// target in the TLS transport cache. Dropping all references to this object +// triggers GC cleanup of the cache entry and any cert rotation goroutine. +type trackedTransport struct { + rt http.RoundTripper +} + +var _ http.RoundTripper = &trackedTransport{} +var _ utilnet.RoundTripperWrapper = &trackedTransport{} + +func (v *trackedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return v.rt.RoundTrip(req) +} + +func (v *trackedTransport) WrappedRoundTripper() http.RoundTripper { + return v.rt } // tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor @@ -162,7 +263,6 @@ func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) { k := tlsCacheKey{ insecure: c.TLS.Insecure, - caData: string(c.TLS.CAData), serverName: c.TLS.ServerName, nextProtos: strings.Join(c.TLS.NextProtos, ","), disableCompression: c.DisableCompression, @@ -178,5 +278,14 @@ func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) { k.keyData = string(c.TLS.KeyData) } + if c.TLS.ReloadCAFiles { + // When reloading CA files, include CA file path in cache key instead of CA data + // This allows the CA to be reloaded from disk on each transport creation + k.caFile = c.TLS.CAFile + } else { + // When not reloading, cache the CA data directly + k.caData = string(c.TLS.CAData) + } + return k, true, nil } diff --git a/staging/src/k8s.io/client-go/transport/cache_test.go b/staging/src/k8s.io/client-go/transport/cache_test.go index 54705276d03a3..d2e565f581d57 100644 --- a/staging/src/k8s.io/client-go/transport/cache_test.go +++ b/staging/src/k8s.io/client-go/transport/cache_test.go @@ -19,14 +19,28 @@ package transport import ( "context" "crypto/tls" + "crypto/x509" "net" "net/http" "net/url" + "os" "reflect" + "runtime" + "sync" + "sync/atomic" "testing" + "time" + "weak" + + "k8s.io/apimachinery/pkg/util/wait" + clientgofeaturegate "k8s.io/client-go/features" + clientfeaturestesting "k8s.io/client-go/features/testing" + "k8s.io/client-go/tools/metrics" ) func TestTLSConfigKey(t *testing.T) { + + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, true) // Make sure config fields that don't affect the tls config don't affect the cache key identicalConfigurations := map[string]*Config{ "empty": {}, @@ -70,14 +84,17 @@ func TestTLSConfigKey(t *testing.T) { // Make sure config fields that affect the tls config affect the cache key dialer := net.Dialer{} getCert := &GetCertHolder{GetCert: func() (*tls.Certificate, error) { return nil, nil }} + caFile := writeCAFile(t, []byte(testCACert1)) uniqueConfigurations := map[string]*Config{ - "proxy": {Proxy: func(request *http.Request) (*url.URL, error) { return nil, nil }}, - "no tls": {}, - "dialer": {DialHolder: &DialHolder{Dial: dialer.DialContext}}, - "dialer2": {DialHolder: &DialHolder{Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return nil, nil }}}, - "insecure": {TLS: TLSConfig{Insecure: true}}, - "cadata 1": {TLS: TLSConfig{CAData: []byte{1}}}, - "cadata 2": {TLS: TLSConfig{CAData: []byte{2}}}, + "proxy": {Proxy: func(request *http.Request) (*url.URL, error) { return nil, nil }}, + "no tls": {}, + "dialer": {DialHolder: &DialHolder{Dial: dialer.DialContext}}, + "dialer2": {DialHolder: &DialHolder{Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return nil, nil }}}, + "insecure": {TLS: TLSConfig{Insecure: true}}, + "cadata 1": {TLS: TLSConfig{CAData: []byte{1}}}, + "cadata 2": {TLS: TLSConfig{CAData: []byte{2}}}, + "with only ca file": {TLS: TLSConfig{CAFile: caFile}}, + "with both ca file and ca data": {TLS: TLSConfig{CAFile: caFile, CAData: []byte(testCACert1)}}, "cert 1, key 1": { TLS: TLSConfig{ CertData: []byte{1}, @@ -188,3 +205,795 @@ func TestTLSConfigKey(t *testing.T) { } } } + +func TestTLSConfigKeyCARotationDisabled(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, false) + + caFile := writeCAFile(t, []byte(testCACert1)) + + // When feature is disabled, CAFile-only config resolves CAData via + // loadTLSFiles, so two configs with the same file content get the same key. + config1 := &Config{TLS: TLSConfig{CAFile: caFile}} + config2 := &Config{TLS: TLSConfig{CAData: []byte(testCACert1)}} + + if err := loadTLSFiles(config1); err != nil { + t.Fatal(err) + } + if err := loadTLSFiles(config2); err != nil { + t.Fatal(err) + } + + key1, canCache1, err := tlsConfigKey(config1) + if err != nil || !canCache1 { + t.Fatalf("unexpected: err=%v, canCache=%v", err, canCache1) + } + + key2, canCache2, err := tlsConfigKey(config2) + if err != nil || !canCache2 { + t.Fatalf("unexpected: err=%v, canCache=%v", err, canCache2) + } + + if key1 != key2 { + t.Error("Expected same cache key when feature is disabled (CAFile resolved to CAData)") + } + if config1.TLS.ReloadCAFiles { + t.Error("Expected ReloadCAFiles=false when feature gate is disabled") + } +} + +// TestTLSTransportCacheCARotation tests transport cache behavior with CA rotation +func TestTLSTransportCacheCARotation(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, true) + caFile := writeCAFile(t, []byte(testCACert1)) + testCases := []struct { + name string + config *Config + expectWrapper bool + expectCacheable bool + }{ + { + name: "CA rotation should be enabled when only the CAFile is set", + config: &Config{ + TLS: TLSConfig{ + CAFile: caFile, + }, + }, + expectWrapper: true, + expectCacheable: true, + }, + { + name: "CA rotation should be disabled when both CAFile and CAData are set", + config: &Config{ + TLS: TLSConfig{ + CAFile: caFile, + CAData: []byte(testCACert1), + }, + }, + expectWrapper: false, + expectCacheable: true, + }, + { + name: "CA rotation should be disabled when only the CAData is set", + config: &Config{ + TLS: TLSConfig{ + CAData: []byte(testCACert1), + }, + }, + expectWrapper: false, + expectCacheable: true, + }, + { + name: "no TLS config", + config: &Config{ + TLS: TLSConfig{}, + }, + expectWrapper: false, + expectCacheable: false, // No TLS config means default transport + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + createCalls, _, _, _ := installFakeMetrics(t) + tlsCaches := newTLSCache() + + rt, err := tlsCaches.get(tc.config) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if !tc.expectCacheable { + // Should return default transport + if rt != http.DefaultTransport { + t.Errorf("Expected default transport, got %T", rt) + } + // canCache=true so getLocked fires "miss", but DefaultTransport + // is returned before setLocked — the entry is never stored. + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "miss" { + t.Errorf("expected [miss], got %v", calls) + } + + // A second call is also "miss" — nothing was stored. + rt2, err := tlsCaches.get(tc.config) + if err != nil { + t.Fatal(err) + } + if rt2 != http.DefaultTransport { + t.Errorf("second call: expected default transport, got %T", rt2) + } + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "miss" { + t.Errorf("expected [miss] on second call (nothing stored), got %v", calls) + } + + requireCacheLen(t, tlsCaches, 0) + return + } + + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "miss" { + t.Errorf("expected [miss] on first get, got %v", calls) + } + + if rt := rt.(*trackedTransport).rt; tc.expectWrapper { + // Should be wrapped in atomicTransportHolder + if _, ok := rt.(*atomicTransportHolder); !ok { + t.Errorf("Expected atomicTransportHolder, got %T", rt) + } + if !tc.config.TLS.ReloadCAFiles { + t.Errorf("Expected ReloadCAFiles to be true, got %v", tc.config.TLS.ReloadCAFiles) + } + } else { + // Should be a regular http.Transport + if _, ok := rt.(*http.Transport); !ok { + t.Errorf("Expected *http.Transport, got %T", rt) + } + } + + // Test caching: second call should return the same instance + rt2, err := tlsCaches.get(tc.config) + if err != nil { + t.Fatalf("Unexpected error on second call: %v", err) + } + if rt != rt2 { + t.Error("Expected same transport instance from cache") + } + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "hit" { + t.Errorf("expected [hit] on second get, got %v", calls) + } + + requireCacheLen(t, tlsCaches, 1) + }) + } +} + +func TestTLSTransportCacheCARotationDisabled(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, false) + + caFile := writeCAFile(t, []byte(testCACert1)) + cache := newTLSCache() + + rt, err := cache.get(&Config{TLS: TLSConfig{CAFile: caFile}}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + rt = rt.(*trackedTransport).rt + if _, ok := rt.(*atomicTransportHolder); ok { + t.Error("Expected plain *http.Transport when feature gate is disabled, got atomicTransportHolder") + } + if _, ok := rt.(*http.Transport); !ok { + t.Errorf("Expected *http.Transport, got %T", rt) + } +} + +func TestEmptyCAFileRotationLifecycle(t *testing.T) { + // Enable the feature gate for the duration of the test + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, true) + + // Create a valid file path, but with empty cert data + emptyFile := writeCAFile(t, []byte{}) + + config := &Config{ + TLS: TLSConfig{ + CAFile: emptyFile, + }, + } + + tlsCaches := newTLSCache() + + rt, err := tlsCaches.get(config) + if err != nil { + t.Fatalf("Unexpected error getting transport: %v", err) + } + + // Verify newAtomicTransportHolder is successfully generated + holder, ok := rt.(*trackedTransport).rt.(*atomicTransportHolder) + if !ok { + t.Fatalf("Expected atomicTransportHolder, got %T", rt) + } + + // Verify the initial state: RootCAs should be non-nil but empty + initialTransport := holder.getTransport(context.Background()) + + if initialTransport.TLSClientConfig == nil || initialTransport.TLSClientConfig.RootCAs == nil { + t.Fatal("Expected RootCAs to be non-nil for an empty CA file (should be an empty CertPool)") + } + emptyPool := x509.NewCertPool() + if !initialTransport.TLSClientConfig.RootCAs.Equal(emptyPool) { + t.Fatal("Expected initially empty RootCAs") + } + + // Write valid cert data into the CA file + if err := os.WriteFile(emptyFile, []byte(testCACert1), 0644); err != nil { + t.Fatalf("Failed to write to CA file: %v", err) + } + + holder.mu.Lock() + // Set last checked time far in the past to force a refresh on next getTransport call + holder.transportLastChecked = time.Now().Add(-time.Hour) + holder.mu.Unlock() + + refreshedTransport := holder.getTransport(context.Background()) + + // Verify the refresh succeeded and the cert pool is now populated + if refreshedTransport.TLSClientConfig.RootCAs.Equal(emptyPool) { + t.Fatal("Expected RootCAs to be populated after writing valid cert data and refreshing") + } +} + +// TestCacheHoldAfterCARotation verifies that holding the *atomicTransportHolder +// keeps the cache entry alive even after CA rotation swaps the inner transport. +func TestCacheHoldAfterCARotation(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, true) + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, true) + _, gcCalls, _, _ := installFakeMetrics(t) + + caFile := writeCAFile(t, []byte(testCACert1)) + + cache := newTLSCache() + + rt, err := cache.get(&Config{TLS: TLSConfig{ServerName: "reload-test", CAFile: caFile}}) + if err != nil { + t.Fatal(err) + } + requireCacheLen(t, cache, 1) + + holder := rt.(*trackedTransport).rt.(*atomicTransportHolder) + + originalInner := holder.getTransport(context.Background()) + if originalInner == nil { + t.Fatal("expected non-nil transport") + } + + // Simulate CA rotation. + if err := os.WriteFile(caFile, []byte(testCACert2), 0644); err != nil { + t.Fatal(err) + } + holder.mu.Lock() + holder.transportLastChecked = time.Now().Add(-time.Hour) + holder.mu.Unlock() + + newInner := holder.getTransport(context.Background()) + if newInner == nil { + t.Fatal("expected non-nil transport after rotation") + } + if newInner == originalInner { + t.Fatal("expected transport to change after CA rotation") + } + + // Cache entry must survive because the holder is alive. + for range 3 { + runtime.GC() + } + requireCacheLen(t, cache, 1) + runtime.KeepAlive(rt) + + pollCacheSizeWithGC(t, cache, 0) + if calls := gcCalls.reset(); len(calls) != 1 || calls[0] != "deleted" { + t.Errorf("expected [deleted] after eviction, got %v", calls) + } +} + +// TestCacheGCDisabledNoEviction verifies that with the GC feature gate disabled, +// cache entries are stored in strongTransports and never evicted. +func TestCacheGCDisabledNoEviction(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, false) + createCalls, gcCalls, _, cacheEntries := installFakeMetrics(t) + + cache := newTLSCache() + + rt, err := cache.get(&Config{TLS: TLSConfig{ServerName: "gc-disabled-test"}}) + if err != nil { + t.Fatal(err) + } + + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "miss" { + t.Errorf("expected [miss], got %v", calls) + } + + if _, ok := rt.(*trackedTransport); ok { + t.Error("expected plain transport, not *trackedTransport, when GC is disabled") + } + + rt2, err := cache.get(&Config{TLS: TLSConfig{ServerName: "gc-disabled-test"}}) + if err != nil { + t.Fatal(err) + } + if rt != rt2 { + t.Error("expected cache hit to return same transport") + } + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "hit" { + t.Errorf("expected [hit], got %v", calls) + } + + requireCacheLen(t, cache, 1) + + runtime.KeepAlive(rt) + for range 10 { + runtime.GC() + } + requireCacheLen(t, cache, 1) + + if calls := gcCalls.reset(); len(calls) != 0 { + t.Errorf("expected no GC cache calls when GC is disabled, got %v", calls) + } + + // Both get() calls observe 1 — the defer evaluates lenLocked() after setLocked stores. + if values := cacheEntries.reset(); len(values) != 2 || values[0] != 1 || values[1] != 1 { + t.Errorf("expected cache entries observations [1 1], got %v", values) + } +} + +// TestCacheReviveAfterDrop verifies that when rt1 is still alive (via KeepAlive), +// a second get() for the same key is a deterministic cache hit. +func TestCacheReviveAfterDrop(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, true) + createCalls, _, _, _ := installFakeMetrics(t) + + cache := newTLSCache() + + config := &Config{TLS: TLSConfig{ServerName: "keep-alive-test"}} + + rt1, err := cache.get(config) + if err != nil { + t.Fatal(err) + } + requireCacheLen(t, cache, 1) + createCalls.reset() + + // rt1 is alive here, so the weak pointer must still resolve. + rt2, err := cache.get(config) + if err != nil { + t.Fatal(err) + } + // rt1 and rt2 are the same object (cache hit). + if rt1 != rt2 { + t.Error("expected same transport (cache hit) since rt1 is still alive") + } + if calls := createCalls.reset(); len(calls) != 1 || calls[0] != "hit" { + t.Errorf("expected [hit], got %v", calls) + } + requireCacheLen(t, cache, 1) + + runtime.KeepAlive(rt1) + pollCacheSizeWithGC(t, cache, 0) +} + +// TestUncacheableCertRotationLeak verifies that the cert rotation goroutine +// is stopped when an uncacheable transport is garbage collected. +func TestUncacheableCertRotationLeak(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, true) + _, _, rotationGCCalls, _ := installFakeMetrics(t) + + certFile := writeCAFile(t, []byte(certData)) + keyFile := writeCAFile(t, []byte(keyData)) + + cache := newTLSCache() + + baseline := runtime.NumGoroutine() + + rt, err := cache.get(&Config{ + TLS: TLSConfig{ + CertFile: certFile, + KeyFile: keyFile, + }, + Proxy: func(*http.Request) (*url.URL, error) { return nil, nil }, // force uncacheable + }) + if err != nil { + t.Fatal(err) + } + + requireCacheLen(t, cache, 0) + + afterCreate := runtime.NumGoroutine() + if afterCreate <= baseline { + t.Fatalf("expected goroutine count to increase after creating transport with cert rotation, got baseline=%d after=%d", baseline, afterCreate) + } + + runtime.KeepAlive(rt) + + err = wait.PollUntilContextTimeout(t.Context(), 10*time.Millisecond, 10*time.Second, true, func(_ context.Context) (bool, error) { + runtime.GC() + return runtime.NumGoroutine() <= baseline, nil + }) + if err != nil { + t.Errorf("goroutine leak: cert rotation goroutine was not stopped for uncacheable transport (baseline=%d current=%d)", baseline, runtime.NumGoroutine()) + } + + if n := rotationGCCalls.count.Load(); n != 1 { + t.Errorf("expected TransportCertRotationGCCalls=1, got %d", n) + } +} + +// TestCacheableCertRotationLeak verifies that the cert rotation goroutine +// is stopped when a cacheable transport with cert rotation is garbage collected. +// This exercises the canCache=true && cancel != nil path through setLocked. +func TestCacheableCertRotationLeak(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, true) + _, gcCalls, rotationGCCalls, _ := installFakeMetrics(t) + + certFile := writeCAFile(t, []byte(certData)) + keyFile := writeCAFile(t, []byte(keyData)) + + cache := newTLSCache() + + baseline := runtime.NumGoroutine() + + // CertFile+KeyFile triggers ReloadTLSFiles and the cert rotation goroutine. + // No Proxy means canCache=true. + rt, err := cache.get(&Config{ + TLS: TLSConfig{ + CertFile: certFile, + KeyFile: keyFile, + ServerName: "cacheable-cert-rotation-test", + }, + }) + if err != nil { + t.Fatal(err) + } + + requireCacheLen(t, cache, 1) + + afterCreate := runtime.NumGoroutine() + if afterCreate <= baseline { + t.Fatalf("expected goroutine count to increase after creating transport with cert rotation, got baseline=%d after=%d", baseline, afterCreate) + } + + runtime.KeepAlive(rt) + + gcCalls.reset() + pollCacheSizeWithGC(t, cache, 0) + + err = wait.PollUntilContextTimeout(t.Context(), 10*time.Millisecond, 10*time.Second, true, func(_ context.Context) (bool, error) { + runtime.GC() + return runtime.NumGoroutine() <= baseline, nil + }) + if err != nil { + t.Errorf("goroutine leak: cert rotation goroutine was not stopped for cacheable transport (baseline=%d current=%d)", baseline, runtime.NumGoroutine()) + } + + if n := rotationGCCalls.count.Load(); n != 1 { + t.Errorf("expected TransportCertRotationGCCalls=1, got %d", n) + } + if calls := gcCalls.reset(); len(calls) != 1 || calls[0] != "deleted" { + t.Errorf("expected [deleted], got %v", calls) + } +} + +// TestCacheGCDisabledCertRotationNoCancel verifies that with the GC feature gate +// disabled, the cert rotation goroutine is NOT stopped (pre-GC behavior). +func TestCacheGCDisabledCertRotationNoCancel(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, false) + _, _, rotationGCCalls, _ := installFakeMetrics(t) + + certFile := writeCAFile(t, []byte(certData)) + keyFile := writeCAFile(t, []byte(keyData)) + + cache := newTLSCache() + + baseline := runtime.NumGoroutine() + + rt, err := cache.get(&Config{ + TLS: TLSConfig{ + CertFile: certFile, + KeyFile: keyFile, + ServerName: "gc-disabled-cert-rotation", + }, + }) + if err != nil { + t.Fatal(err) + } + + afterCreate := runtime.NumGoroutine() + if afterCreate <= baseline { + t.Fatalf("expected cert rotation goroutine to start, got baseline=%d after=%d", baseline, afterCreate) + } + + // Drop rt after this point + // if GC cleanup were enabled, cancel would fire and the goroutine would stop. + runtime.KeepAlive(rt) + + for range 10 { + runtime.GC() + runtime.Gosched() + } + time.Sleep(50 * time.Millisecond) // let any potential cleanup callbacks run + + if n := rotationGCCalls.count.Load(); n != 0 { + t.Errorf("expected TransportCertRotationGCCalls=0 when GC disabled, got %d", n) + } + + // Goroutine must still be running — cancel was intentionally discarded. + if runtime.NumGoroutine() <= baseline { + t.Error("cert rotation goroutine was stopped — it should run indefinitely when GC is disabled") + } +} + +// TestCacheStaleEvictionSkipped verifies that when a GC cleanup fires for an +// old entry after the map slot has been replaced, the deletion is skipped. +// We force this by replacing the map entry while the old trackedTransport +// is still alive, then dropping it so its cleanup fires against the new entry. +func TestCacheStaleEvictionSkipped(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, true) + _, gcCalls, _, _ := installFakeMetrics(t) + + cache := newTLSCache() + + cfg := &Config{TLS: TLSConfig{ServerName: "stale-test"}} + rt1, err := cache.get(cfg) + if err != nil { + t.Fatal(err) + } + requireCacheLen(t, cache, 1) + + // Replace the map entry while rt1 is still alive. This simulates a + // concurrent get() that created a new entry for the same key. + key, _, _ := tlsConfigKey(cfg) + cache.mu.Lock() + replacement := &trackedTransport{rt: &http.Transport{}} + cache.transports[key] = weak.Make(replacement) + cache.mu.Unlock() + + // Drop rt1 after this point. Its cleanup fires and sees c.transports[key] != wp -> "skipped". + runtime.KeepAlive(rt1) + gcCalls.reset() + + err = wait.PollUntilContextTimeout(t.Context(), 10*time.Millisecond, 10*time.Second, true, func(_ context.Context) (bool, error) { + runtime.GC() + gcCalls.mu.Lock() + n := len(gcCalls.calls) + gcCalls.mu.Unlock() + return n > 0, nil + }) + if err != nil { + t.Fatal("timed out waiting for stale cleanup to fire") + } + + calls := gcCalls.reset() + if len(calls) != 1 || calls[0] != "skipped" { + t.Errorf("expected [skipped], got %v", calls) + } + + // The replacement entry must still be in the map because of the keep alive + requireCacheLen(t, cache, 1) + runtime.KeepAlive(replacement) +} + +// TestCacheLeak does a black box test of TLS cache GC from New. +// It uses a large number of cache entries generated in parallel. +func TestCacheLeak(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowTLSCacheGC, true) + _, gcCalls, _, cacheEntries := installFakeMetrics(t) + + pollCacheSizeWithGC(t, tlsCache, 0) // clean start with global cache + + rt1, err := New(&Config{TLS: TLSConfig{ServerName: "1"}}) + if err != nil { + t.Fatal(err) + } + rt2, err := New(&Config{TLS: TLSConfig{ServerName: "2"}}) + if err != nil { + t.Fatal(err) + } + rt3, err := New(&Config{TLS: TLSConfig{ServerName: "1"}}) + if err != nil { + t.Fatal(err) + } + + requireCacheLen(t, tlsCache, 2) // rt1 and rt2 (rt3 is the same as rt1) + + var wg wait.Group + var d net.Dialer + var rts []http.RoundTripper + var rtsLock sync.Mutex + for i := range 1_000 { // outer loop forces cache miss via dialer + dh := &DialHolder{Dial: d.DialContext} + for range i%7 + 1 { // inner loop exercises each cache value having 1 to N references + wg.Start(func() { + rt, err := New(&Config{DialHolder: dh}) + if err != nil { + panic(err) + } + rtsLock.Lock() + rts = append(rts, rt) // keep a live reference to the round tripper + rtsLock.Unlock() + }) + } + } + wg.Wait() + + requireCacheLen(t, tlsCache, 1_000+2) // rts and rt1 and rt2 (rt3 is the same as rt1) + + runtime.KeepAlive(rts) // prevent round trippers from being GC'd too early + + // Reset before the eviction we want to measure. + gcCalls.reset() + cacheEntries.reset() + + pollCacheSizeWithGC(t, tlsCache, 2) // rt1 and rt2 (rt3 is the same as rt1) + + calls := gcCalls.reset() + deletedCount := 0 + for _, c := range calls { + if c == "deleted" { + deletedCount++ + } else { + t.Errorf("expected deleted call, got %s", c) + } + } + if deletedCount != 1_000 { + t.Errorf("expected 1000 deleted calls, got %d (total calls: %d)", deletedCount, len(calls)) + } + + // Each "deleted" cleanup calls Observe(lenLocked()). The last observation + // should be 2 (rt1 and rt2 remain). + evictionObservations := cacheEntries.reset() + if len(evictionObservations) != 1000 { + t.Errorf("expected exactly 1000 cache entries observations from GC cleanup, got %d", len(evictionObservations)) + } else if last := evictionObservations[len(evictionObservations)-1]; last != 2 { + t.Errorf("expected last cache entries observation to be 2 (rt1+rt2 remain), got %d", last) + } + + runtime.KeepAlive(rt1) + runtime.KeepAlive(rt2) + runtime.KeepAlive(rt3) + + pollCacheSizeWithGC(t, tlsCache, 0) + + calls = gcCalls.reset() + if len(calls) != 2 || calls[0] != "deleted" || calls[1] != "deleted" { + t.Errorf("expected 2 deleted calls for rt1/rt2, got %v", calls) + } +} + +func requireCacheLen(t *testing.T, c *tlsTransportCache, want int) { + t.Helper() + + if cacheLen(t, c) != want { + t.Fatalf("cache len %d, want %d", cacheLen(t, c), want) + } +} + +func cacheLen(t *testing.T, c *tlsTransportCache) int { + t.Helper() + + c.mu.Lock() + defer c.mu.Unlock() + + if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) { + if len(c.transports) != 0 { + t.Fatalf("transports len %d must be 0", len(c.transports)) + } + return len(c.strongTransports) + } + + if len(c.strongTransports) != 0 { + t.Fatalf("strongTransports len %d must be 0", len(c.strongTransports)) + } + return len(c.transports) +} + +func pollCacheSizeWithGC(t *testing.T, c *tlsTransportCache, want int) { + t.Helper() + + if err := wait.PollUntilContextTimeout(t.Context(), 10*time.Millisecond, 10*time.Second, true, func(_ context.Context) (done bool, _ error) { + runtime.GC() // run the garbage collector so the cleanups run + return cacheLen(t, c) == want, nil + }); err != nil { + t.Fatalf("cache len %d, want %d: %v", cacheLen(t, c), want, err) + } + + // make sure the cache size is stable even when more GC's happen + // three times should be enough to make the test flake if the implementation is buggy + for range 3 { + runtime.GC() + } + requireCacheLen(t, c, want) +} + +type recordingCreateCalls struct { + mu sync.Mutex + calls []string +} + +func (r *recordingCreateCalls) Increment(result string) { + r.mu.Lock() + defer r.mu.Unlock() + r.calls = append(r.calls, result) +} + +func (r *recordingCreateCalls) reset() []string { + r.mu.Lock() + defer r.mu.Unlock() + c := r.calls + r.calls = nil + return c +} + +type recordingCacheGCCalls struct { + mu sync.Mutex + calls []string +} + +func (r *recordingCacheGCCalls) Increment(result string) { + r.mu.Lock() + defer r.mu.Unlock() + r.calls = append(r.calls, result) +} + +func (r *recordingCacheGCCalls) reset() []string { + r.mu.Lock() + defer r.mu.Unlock() + c := r.calls + r.calls = nil + return c +} + +type recordingCertRotationGCCalls struct { + count atomic.Int64 +} + +func (r *recordingCertRotationGCCalls) Increment() { + r.count.Add(1) +} + +type recordingCacheEntries struct { + mu sync.Mutex + values []int +} + +func (r *recordingCacheEntries) Observe(n int) { + r.mu.Lock() + defer r.mu.Unlock() + r.values = append(r.values, n) +} + +func (r *recordingCacheEntries) reset() []int { + r.mu.Lock() + defer r.mu.Unlock() + v := r.values + r.values = nil + return v +} + +func installFakeMetrics(t *testing.T) (*recordingCreateCalls, *recordingCacheGCCalls, *recordingCertRotationGCCalls, *recordingCacheEntries) { + createCalls := &recordingCreateCalls{} + gcCalls := &recordingCacheGCCalls{} + rotationGCCalls := &recordingCertRotationGCCalls{} + cacheEntries := &recordingCacheEntries{} + + origCreate := metrics.TransportCreateCalls + origGC := metrics.TransportCacheGCCalls + origRotationGC := metrics.TransportCertRotationGCCalls + origEntries := metrics.TransportCacheEntries + metrics.TransportCreateCalls = createCalls + metrics.TransportCacheGCCalls = gcCalls + metrics.TransportCertRotationGCCalls = rotationGCCalls + metrics.TransportCacheEntries = cacheEntries + t.Cleanup(func() { + metrics.TransportCreateCalls = origCreate + metrics.TransportCacheGCCalls = origGC + metrics.TransportCertRotationGCCalls = origRotationGC + metrics.TransportCacheEntries = origEntries + }) + return createCalls, gcCalls, rotationGCCalls, cacheEntries +} diff --git a/staging/src/k8s.io/client-go/transport/config.go b/staging/src/k8s.io/client-go/transport/config.go index d8a3d64b309f7..f3ddcca33c625 100644 --- a/staging/src/k8s.io/client-go/transport/config.go +++ b/staging/src/k8s.io/client-go/transport/config.go @@ -134,7 +134,8 @@ type TLSConfig struct { CAFile string // Path of the PEM-encoded server trusted root certificates. CertFile string // Path of the PEM-encoded client certificate. KeyFile string // Path of the PEM-encoded client key. - ReloadTLSFiles bool // Set to indicate that the original config provided files, and that they should be reloaded + ReloadTLSFiles bool // Set to indicate that the original config provided files, and that they should be reloaded. + ReloadCAFiles bool // Set to indicate that CA files should be reloaded from disk. Insecure bool // Server should be accessed without verifying the certificate. For testing only. ServerName string // Override for the server name passed to the server for SNI and used to verify certificates. diff --git a/staging/src/k8s.io/client-go/transport/round_trippers.go b/staging/src/k8s.io/client-go/transport/round_trippers.go index 39fcebd9416c4..a50977671a0bc 100644 --- a/staging/src/k8s.io/client-go/transport/round_trippers.go +++ b/staging/src/k8s.io/client-go/transport/round_trippers.go @@ -319,7 +319,7 @@ func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, token = refreshedToken.AccessToken } } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + req.Header.Set("Authorization", "Bearer "+token) return rt.rt.RoundTrip(req) } diff --git a/staging/src/k8s.io/client-go/transport/spdy/spdy.go b/staging/src/k8s.io/client-go/transport/spdy/spdy.go index 9fddc6c5f23ce..3bb04b77f373b 100644 --- a/staging/src/k8s.io/client-go/transport/spdy/spdy.go +++ b/staging/src/k8s.io/client-go/transport/spdy/spdy.go @@ -23,8 +23,10 @@ import ( "time" "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" + httpstreamspdy "k8s.io/apimachinery/pkg/util/httpstream/spdy" restclient "k8s.io/client-go/rest" + "k8s.io/klog/v2" + streamhttp "k8s.io/streaming/pkg/httpstream" ) // Upgrader validates a response from the server after a SPDY upgrade. @@ -43,7 +45,7 @@ func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, er if config.Proxy != nil { proxy = config.Proxy } - upgradeRoundTripper, err := spdy.NewRoundTripperWithConfig(spdy.RoundTripperConfig{ + upgradeRoundTripper, err := httpstreamspdy.NewRoundTripperWithConfig(httpstreamspdy.RoundTripperConfig{ TLS: tlsConfig, Proxier: proxy, PingPeriod: time.Second * 5, @@ -79,6 +81,18 @@ func NewDialer(upgrader Upgrader, client *http.Client, method string, url *url.U } } +// NewDialerForStreaming creates a SPDY dialer for in-tree callers that use +// k8s.io/streaming/pkg/httpstream types. +func NewDialerForStreaming(upgrader Upgrader, client *http.Client, method string, url *url.URL) streamhttp.Dialer { + return &streamingDialerAdapter{delegate: NewDialer(upgrader, client, method, url)} +} + +// NewUpgraderForStreaming adapts a streaming upgrader for callers that need +// the compatibility Upgrader interface. +func NewUpgraderForStreaming(upgrader streamhttp.UpgradeRoundTripper) Upgrader { + return &compatUpgraderAdapter{delegate: upgrader} +} + func (d *dialer) Dial(protocols ...string) (httpstream.Connection, string, error) { req, err := http.NewRequest(d.method, d.url.String(), nil) if err != nil { @@ -105,3 +119,199 @@ func Negotiate(upgrader Upgrader, client *http.Client, req *http.Request, protoc } return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil } + +// NegotiateStreaming is for in-tree callers that still operate on +// k8s.io/streaming/pkg/httpstream types. +func NegotiateStreaming(upgrader Upgrader, client *http.Client, req *http.Request, protocols ...string) (streamhttp.Connection, string, error) { + conn, protocol, err := Negotiate(upgrader, client, req, protocols...) + if err != nil { + return nil, "", err + } + return wrapStreamingConnection(conn), protocol, nil +} + +type streamingDialerAdapter struct { + delegate httpstream.Dialer +} + +func (d *streamingDialerAdapter) Dial(protocols ...string) (streamhttp.Connection, string, error) { + conn, protocol, err := d.delegate.Dial(protocols...) + if err != nil { + return nil, "", err + } + return wrapStreamingConnection(conn), protocol, nil +} + +type compatUpgraderAdapter struct { + delegate streamhttp.UpgradeRoundTripper +} + +func (u *compatUpgraderAdapter) NewConnection(resp *http.Response) (httpstream.Connection, error) { + conn, err := u.delegate.NewConnection(resp) + if err != nil { + return nil, err + } + return wrapCompatConnection(conn), nil +} + +type streamingStreamAdapter struct { + delegate httpstream.Stream +} + +func (s *streamingStreamAdapter) Read(p []byte) (int, error) { + return s.delegate.Read(p) +} + +func (s *streamingStreamAdapter) Write(p []byte) (int, error) { + return s.delegate.Write(p) +} + +func (s *streamingStreamAdapter) Close() error { + return s.delegate.Close() +} + +func (s *streamingStreamAdapter) Reset() error { + return s.delegate.Reset() +} + +func (s *streamingStreamAdapter) Headers() http.Header { + return s.delegate.Headers() +} + +func (s *streamingStreamAdapter) Identifier() uint32 { + return s.delegate.Identifier() +} + +type streamingConnectionAdapter struct { + delegate httpstream.Connection +} + +func (c *streamingConnectionAdapter) CreateStream(headers http.Header) (streamhttp.Stream, error) { + stream, err := c.delegate.CreateStream(headers) + if err != nil { + return nil, err + } + return &streamingStreamAdapter{delegate: stream}, nil +} + +func (c *streamingConnectionAdapter) Close() error { + return c.delegate.Close() +} + +func (c *streamingConnectionAdapter) CloseChan() <-chan bool { + return c.delegate.CloseChan() +} + +func (c *streamingConnectionAdapter) SetIdleTimeout(timeout time.Duration) { + c.delegate.SetIdleTimeout(timeout) +} + +func (c *streamingConnectionAdapter) RemoveStreams(streams ...streamhttp.Stream) { + compatStreams := make([]httpstream.Stream, 0, len(streams)) + for _, stream := range streams { + if stream == nil { + continue + } + if s, ok := stream.(*streamingStreamAdapter); ok { + compatStreams = append(compatStreams, s.delegate) + continue + } + if s, ok := stream.(httpstream.Stream); ok { + compatStreams = append(compatStreams, s) + continue + } + klog.V(5).Infof("dropping unadaptable streaming stream %T in RemoveStreams", stream) + } + c.delegate.RemoveStreams(compatStreams...) +} + +func wrapStreamingConnection(conn httpstream.Connection) streamhttp.Connection { + if conn == nil { + return nil + } + if wrapped, ok := conn.(*compatConnectionAdapter); ok { + return wrapped.delegate + } + return &streamingConnectionAdapter{delegate: conn} +} + +type compatStreamAdapter struct { + delegate streamhttp.Stream +} + +func (s *compatStreamAdapter) Read(p []byte) (int, error) { + return s.delegate.Read(p) +} + +func (s *compatStreamAdapter) Write(p []byte) (int, error) { + return s.delegate.Write(p) +} + +func (s *compatStreamAdapter) Close() error { + return s.delegate.Close() +} + +func (s *compatStreamAdapter) Reset() error { + return s.delegate.Reset() +} + +func (s *compatStreamAdapter) Headers() http.Header { + return s.delegate.Headers() +} + +func (s *compatStreamAdapter) Identifier() uint32 { + return s.delegate.Identifier() +} + +type compatConnectionAdapter struct { + delegate streamhttp.Connection +} + +func (c *compatConnectionAdapter) CreateStream(headers http.Header) (httpstream.Stream, error) { + stream, err := c.delegate.CreateStream(headers) + if err != nil { + return nil, err + } + return &compatStreamAdapter{delegate: stream}, nil +} + +func (c *compatConnectionAdapter) Close() error { + return c.delegate.Close() +} + +func (c *compatConnectionAdapter) CloseChan() <-chan bool { + return c.delegate.CloseChan() +} + +func (c *compatConnectionAdapter) SetIdleTimeout(timeout time.Duration) { + c.delegate.SetIdleTimeout(timeout) +} + +func (c *compatConnectionAdapter) RemoveStreams(streams ...httpstream.Stream) { + streamingStreams := make([]streamhttp.Stream, 0, len(streams)) + for _, stream := range streams { + if stream == nil { + continue + } + if s, ok := stream.(*compatStreamAdapter); ok { + streamingStreams = append(streamingStreams, s.delegate) + continue + } + if s, ok := stream.(streamhttp.Stream); ok { + streamingStreams = append(streamingStreams, s) + continue + } + klog.V(5).Infof("dropping unadaptable compat stream %T in RemoveStreams", stream) + } + c.delegate.RemoveStreams(streamingStreams...) +} + +func wrapCompatConnection(conn streamhttp.Connection) httpstream.Connection { + if conn == nil { + return nil + } + if wrapped, ok := conn.(*streamingConnectionAdapter); ok { + return wrapped.delegate + } + return &compatConnectionAdapter{delegate: conn} +} diff --git a/staging/src/k8s.io/client-go/transport/transport.go b/staging/src/k8s.io/client-go/transport/transport.go index 8fdcc5700b53c..be97b76210f0f 100644 --- a/staging/src/k8s.io/client-go/transport/transport.go +++ b/staging/src/k8s.io/client-go/transport/transport.go @@ -28,6 +28,7 @@ import ( "time" utilnet "k8s.io/apimachinery/pkg/util/net" + clientgofeaturegate "k8s.io/client-go/features" "k8s.io/klog/v2" ) @@ -211,17 +212,26 @@ func TLSConfigFor(c *Config) (*tls.Config, error) { // KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are // either populated or were empty to start. func loadTLSFiles(c *Config) error { - var err error - c.TLS.CAData, err = dataFromSliceOrFile(c.TLS.CAData, c.TLS.CAFile) - if err != nil { - return err + // Check that we are purely loading CA from file + if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowCARotation) { + if len(c.TLS.CAFile) > 0 && len(c.TLS.CAData) == 0 { + c.TLS.ReloadCAFiles = true + } + } else if c.TLS.ReloadCAFiles { + return fmt.Errorf("ReloadCAFiles=true requires ClientsAllowCARotation to be enabled") } - // Check that we are purely loading from files + // Check that we are purely loading certs and keys from files if len(c.TLS.CertFile) > 0 && len(c.TLS.CertData) == 0 && len(c.TLS.KeyFile) > 0 && len(c.TLS.KeyData) == 0 { c.TLS.ReloadTLSFiles = true } + var err error + c.TLS.CAData, err = dataFromSliceOrFile(c.TLS.CAData, c.TLS.CAFile) + if err != nil { + return err + } + c.TLS.CertData, err = dataFromSliceOrFile(c.TLS.CertData, c.TLS.CertFile) if err != nil { return err @@ -254,6 +264,11 @@ func rootCertPool(caData []byte) (*x509.CertPool, error) { // code for a look at the platform specific insanity), so we'll use the fact that RootCAs == nil gives us the system values // It doesn't allow trusting either/or, but hopefully that won't be an issue if len(caData) == 0 { + // When the ClientsAllowCARotation feature gate is enabled, it returns an empty but non-nil pool. + // This ensures we don't fall back to system roots when a user explicitly points CAFile to a zero-byte file. + if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowCARotation) { + return x509.NewCertPool(), nil + } return nil, nil } diff --git a/staging/src/k8s.io/client-go/transport/transport_test.go b/staging/src/k8s.io/client-go/transport/transport_test.go index 1880442207466..27cbb54757cb9 100644 --- a/staging/src/k8s.io/client-go/transport/transport_test.go +++ b/staging/src/k8s.io/client-go/transport/transport_test.go @@ -19,11 +19,15 @@ package transport import ( "context" "crypto/tls" + "crypto/x509" "errors" "fmt" "net" "net/http" "testing" + + clientgofeaturegate "k8s.io/client-go/features" + clientfeaturestesting "k8s.io/client-go/features/testing" ) const ( @@ -356,6 +360,16 @@ func TestNew(t *testing.T) { } for k, testCase := range testCases { t.Run(k, func(t *testing.T) { + // The Close method of httptest Server mutates the + // `http.DefaultTransport` object, the 'TLSClientConfig' + // field mutates from nil to a non nil instance. This introduces flake + // and data race when running tests under transport package in parallel. + // To work around it we reset the TLSClientConfig field. + // + // See: https://github.com/golang/go/issues/65796 + if testCase.Default { + http.DefaultTransport.(*http.Transport).TLSClientConfig = nil + } rt, err := New(testCase.Config) switch { case testCase.Err && err == nil: @@ -375,7 +389,10 @@ func TestNew(t *testing.T) { } // We only know how to check TLSConfig on http.Transports - transport := rt.(*http.Transport) + transport, ok := rt.(*http.Transport) + if !ok { + transport = rt.(*trackedTransport).rt.(*http.Transport) + } switch { case testCase.TLS && transport.TLSClientConfig == nil: t.Fatalf("got %#v, expected TLSClientConfig", transport) @@ -556,3 +573,51 @@ func Test_contextCanceller_RoundTrip(t *testing.T) { }) } } + +func TestRootCertPoolEmptyData(t *testing.T) { + testCases := []struct { + name string + featureGateEnabled bool + expectNilPool bool + }{ + { + name: "feature gate disabled returns nil (system roots)", + featureGateEnabled: false, + expectNilPool: true, + }, + { + name: "feature gate enabled returns empty pool (trust nothing)", + featureGateEnabled: true, + expectNilPool: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Set the feature gate according to the test case + clientfeaturestesting.SetFeatureDuringTest(t, clientgofeaturegate.ClientsAllowCARotation, tc.featureGateEnabled) + + // Call the function with empty caData + pool, err := rootCertPool([]byte{}) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if tc.expectNilPool { + if pool != nil { + t.Fatalf("Expected pool to be nil when feature gate is disabled, but got a populated pool") + } + } else { + if pool == nil { + t.Fatalf("Expected pool to be non-nil (empty pool) when feature gate is enabled, but got nil") + } + + // Verify it is truly an empty pool + emptyPool := x509.NewCertPool() + if !pool.Equal(emptyPool) { + t.Fatalf("Expected the returned pool to be completely empty") + } + } + }) + } +} diff --git a/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go b/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go index 924518e8bbd1f..5285d6b144afa 100644 --- a/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go +++ b/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go @@ -31,11 +31,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" utilnet "k8s.io/apimachinery/pkg/util/net" restclient "k8s.io/client-go/rest" "k8s.io/client-go/transport" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/wsstream" ) var ( diff --git a/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go b/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go index 903dd0c0c8e25..76cc818b169d4 100644 --- a/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go +++ b/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go @@ -32,10 +32,10 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/wsstream" "k8s.io/apimachinery/pkg/util/remotecommand" restclient "k8s.io/client-go/rest" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/httpstream/wsstream" ) func TestWebSocketRoundTripper_RoundTripperSucceeds(t *testing.T) { diff --git a/staging/src/k8s.io/client-go/util/certificate/csr/csr.go b/staging/src/k8s.io/client-go/util/certificate/csr/csr.go index 6f99dcd24b0c2..6b0efff0675fa 100644 --- a/staging/src/k8s.io/client-go/util/certificate/csr/csr.go +++ b/staging/src/k8s.io/client-go/util/certificate/csr/csr.go @@ -78,13 +78,15 @@ func RequestCertificateWithContext(ctx context.Context, client clientset.Interfa csr.Spec.ExpirationSeconds = DurationToExpirationSeconds(*requestedDuration) } + logger := klog.FromContext(ctx) + reqName, reqUID, err = create(ctx, client, csr) switch { case err == nil: + logger.V(2).Info("Certificate signing request created", "csr", reqName) return reqName, reqUID, err case apierrors.IsAlreadyExists(err) && len(name) > 0: - logger := klog.FromContext(ctx) logger.Info("csr for this node already exists, reusing") req, err := get(ctx, client, name) if err != nil { diff --git a/staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go b/staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go index 6e2b5df480968..ee63e7c404e7f 100644 --- a/staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go +++ b/staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go @@ -17,7 +17,6 @@ limitations under the License. package csaupgrade import ( - "bytes" "encoding/json" "errors" "fmt" @@ -323,12 +322,13 @@ func filter[T any]( // Included from fieldmanager.internal to avoid dependency cycle // FieldsToSet creates a set paths from an input trie of fields func decodeManagedFieldsEntrySet(f metav1.ManagedFieldsEntry) (s fieldpath.Set, err error) { - err = s.FromJSON(bytes.NewReader(f.FieldsV1.Raw)) + err = s.FromJSON(f.FieldsV1.GetRawReader()) return s, err } // SetToFields creates a trie of fields from an input set of paths func encodeManagedFieldsEntrySet(f *metav1.ManagedFieldsEntry, s fieldpath.Set) (err error) { - f.FieldsV1.Raw, err = s.ToJSON() + raw, err := s.ToJSON() + f.FieldsV1.SetRawBytes(raw) return err } diff --git a/staging/src/k8s.io/client-go/util/watchlist/watch_list.go b/staging/src/k8s.io/client-go/util/watchlist/watch_list.go index 1551a49f7937c..3641f3baee656 100644 --- a/staging/src/k8s.io/client-go/util/watchlist/watch_list.go +++ b/staging/src/k8s.io/client-go/util/watchlist/watch_list.go @@ -90,6 +90,9 @@ type unSupportedWatchListSemantics interface { // // A client does NOT support WatchList only if // it implements `IsWatchListSemanticsUnSupported` and that returns true. +// +// For an explanation of how WatchList works, see: +// https://kubernetes.io/docs/reference/using-api/api-concepts/#streaming-lists func DoesClientNotSupportWatchListSemantics(client any) bool { lw, ok := client.(unSupportedWatchListSemantics) if !ok { diff --git a/staging/src/k8s.io/cloud-provider/OWNERS b/staging/src/k8s.io/cloud-provider/OWNERS index 1ac1c0f0e62be..3f7357c0b632b 100644 --- a/staging/src/k8s.io/cloud-provider/OWNERS +++ b/staging/src/k8s.io/cloud-provider/OWNERS @@ -1,11 +1,9 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: - - mikedanese - - dims - - wlan0 - - andrewsykim - cheftako + - elmiko + - joelspeed reviewers: - aojea - wojtek-t @@ -25,6 +23,10 @@ reviewers: - wlan0 - cheftako - andrewsykim +emeritus_approvers: + - mikedanese + - dims + - andrewsykim labels: - sig/cloud-provider - area/cloudprovider diff --git a/staging/src/k8s.io/cloud-provider/app/controllermanager.go b/staging/src/k8s.io/cloud-provider/app/controllermanager.go index bac66988fedd0..340308a066511 100644 --- a/staging/src/k8s.io/cloud-provider/app/controllermanager.go +++ b/staging/src/k8s.io/cloud-provider/app/controllermanager.go @@ -41,6 +41,7 @@ import ( "k8s.io/client-go/tools/leaderelection/resourcelock" cloudprovider "k8s.io/cloud-provider" cloudcontrollerconfig "k8s.io/cloud-provider/app/config" + configv1alpha "k8s.io/cloud-provider/config/v1alpha1" "k8s.io/cloud-provider/names" "k8s.io/cloud-provider/options" cliflag "k8s.io/component-base/cli/flag" @@ -88,12 +89,14 @@ func NewCloudControllerManagerCommand(s *options.CloudControllerManagerOptions, the cloud specific control loops shipped with Kubernetes.`, RunE: func(cmd *cobra.Command, args []string) error { verflag.PrintAndExitIfRequested() - cliflag.PrintFlags(cmd.Flags()) + // Activate logging as soon as possible, after that + // show flags with the final logging configuration. if err := logsapi.ValidateAndApply(logOptions, utilfeature.DefaultFeatureGate); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err } + cliflag.PrintFlags(cmd.Flags()) c, err := s.Config(ControllerNames(controllerInitFuncConstructors), ControllersDisabledByDefault.List(), controllerAliases, AllWebhooks, DisabledByDefaultWebhooks) if err != nil { @@ -164,11 +167,17 @@ func Run(c *cloudcontrollerconfig.CompletedConfig, cloud cloudprovider.Interface c.EventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: c.Client.CoreV1().Events("")}) defer c.EventBroadcaster.Shutdown() + externalConfig := &configv1alpha.CloudControllerManagerConfiguration{} + if err := configv1alpha.Convert_config_CloudControllerManagerConfiguration_To_v1alpha1_CloudControllerManagerConfiguration(&c.ComponentConfig, externalConfig, nil); err != nil { + return fmt.Errorf("unable to convert configz: %w", err) + } + externalConfig.SetGroupVersionKind(configv1alpha.SchemeGroupVersion.WithKind("CloudControllerManagerConfiguration")) + // setup /configz endpoint - if cz, err := configz.New(ConfigzName); err == nil { - cz.Set(c.ComponentConfig) - } else { - klog.Errorf("unable to register configz: %v", err) + if cz, err := configz.New(ConfigzName); err != nil { + return fmt.Errorf("unable to register configz: %w", err) + } else if err := cz.Set(externalConfig); err != nil { + return fmt.Errorf("unable to set configz: %w", err) } // Setup any health checks we will want to use. diff --git a/staging/src/k8s.io/cloud-provider/app/core.go b/staging/src/k8s.io/cloud-provider/app/core.go index f1a324e3ff0fb..3acbb4e8c19e0 100644 --- a/staging/src/k8s.io/cloud-provider/app/core.go +++ b/staging/src/k8s.io/cloud-provider/app/core.go @@ -48,6 +48,7 @@ func startCloudNodeController(ctx context.Context, initContext ControllerInitCon cloud, completedConfig.ComponentConfig.NodeStatusUpdateFrequency.Duration, completedConfig.ComponentConfig.NodeController.ConcurrentNodeSyncs, + completedConfig.ComponentConfig.NodeController.ConcurrentNodeStatusUpdates, ) if err != nil { klog.Warningf("failed to start cloud node controller: %s", err) diff --git a/staging/src/k8s.io/cloud-provider/app/testing/testserver.go b/staging/src/k8s.io/cloud-provider/app/testing/testserver.go index 3bc0a579b4b1f..28e465646cd03 100644 --- a/staging/src/k8s.io/cloud-provider/app/testing/testserver.go +++ b/staging/src/k8s.io/cloud-provider/app/testing/testserver.go @@ -33,6 +33,7 @@ import ( "k8s.io/cloud-provider/names" "k8s.io/cloud-provider/options" cliflag "k8s.io/component-base/cli/flag" + "k8s.io/component-base/configz" logsapi "k8s.io/component-base/logs/api/v1" "k8s.io/klog/v2" ) @@ -99,6 +100,7 @@ func StartTestServerWithOptions(t *testing.T, klog.Errorf("Failed to shutdown test server clearly: %v", err) } } + configz.Delete(app.ConfigzName) } defer func() { if result.TearDownFn == nil { diff --git a/staging/src/k8s.io/cloud-provider/app/webhooks_test.go b/staging/src/k8s.io/cloud-provider/app/webhooks_test.go index e155362865a87..aedebf7d99258 100644 --- a/staging/src/k8s.io/cloud-provider/app/webhooks_test.go +++ b/staging/src/k8s.io/cloud-provider/app/webhooks_test.go @@ -20,10 +20,10 @@ import ( "testing" admissionv1 "k8s.io/api/admission/v1" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/cloud-provider/app/config" cpconfig "k8s.io/cloud-provider/config" "k8s.io/cloud-provider/fake" + "k8s.io/utils/dump" ) func TestWebhookEnableDisable(t *testing.T) { diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/config/types.go b/staging/src/k8s.io/cloud-provider/controllers/node/config/types.go index af7c7880351ed..1879de834d60b 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/config/types.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/config/types.go @@ -21,4 +21,8 @@ type NodeControllerConfiguration struct { // ConcurrentNodeSyncs is the number of workers // concurrently synchronizing nodes ConcurrentNodeSyncs int32 + // ConcurrentNodeStatusUpdates is the number of workers + // concurrently updating node statuses. + // If unspecified or 0, ConcurrentNodeSyncs is used instead + ConcurrentNodeStatusUpdates int32 } diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/types.go b/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/types.go index ca85a557cbf54..0c9234fbc026a 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/types.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/types.go @@ -21,4 +21,8 @@ type NodeControllerConfiguration struct { // ConcurrentNodeSyncs is the number of workers // concurrently synchronizing nodes ConcurrentNodeSyncs int32 + // ConcurrentNodeStatusUpdates is the number of workers + // concurrently updating node statuses. + // If unspecified or 0, ConcurrentNodeSyncs is used instead + ConcurrentNodeStatusUpdates int32 } diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/zz_generated.conversion.go index cf3852db2942e..89ccc8833aaa3 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/zz_generated.conversion.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/config/v1alpha1/zz_generated.conversion.go @@ -49,10 +49,12 @@ func RegisterConversions(s *runtime.Scheme) error { func autoConvert_v1alpha1_NodeControllerConfiguration_To_config_NodeControllerConfiguration(in *NodeControllerConfiguration, out *config.NodeControllerConfiguration, s conversion.Scope) error { out.ConcurrentNodeSyncs = in.ConcurrentNodeSyncs + out.ConcurrentNodeStatusUpdates = in.ConcurrentNodeStatusUpdates return nil } func autoConvert_config_NodeControllerConfiguration_To_v1alpha1_NodeControllerConfiguration(in *config.NodeControllerConfiguration, out *NodeControllerConfiguration, s conversion.Scope) error { out.ConcurrentNodeSyncs = in.ConcurrentNodeSyncs + out.ConcurrentNodeStatusUpdates = in.ConcurrentNodeStatusUpdates return nil } diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go index a1fa02b7d8dfa..66f7ed17a0d4e 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller.go @@ -106,6 +106,7 @@ type CloudNodeController struct { nodeStatusUpdateFrequency time.Duration workerCount int32 + statusUpdateWorkerCount int32 nodesLister corelisters.NodeLister nodesSynced cache.InformerSynced @@ -118,7 +119,8 @@ func NewCloudNodeController( kubeClient clientset.Interface, cloud cloudprovider.Interface, nodeStatusUpdateFrequency time.Duration, - workerCount int32) (*CloudNodeController, error) { + workerCount, + statusUpdateWorkerCount int32) (*CloudNodeController, error) { _, instancesSupported := cloud.Instances() _, instancesV2Supported := cloud.InstancesV2() @@ -132,6 +134,7 @@ func NewCloudNodeController( cloud: cloud, nodeStatusUpdateFrequency: nodeStatusUpdateFrequency, workerCount: workerCount, + statusUpdateWorkerCount: statusUpdateWorkerCount, nodesLister: nodeInformer.Lister(), nodesSynced: nodeInformer.Informer().HasSynced, workqueue: workqueue.NewTypedRateLimitingQueueWithConfig( @@ -289,7 +292,7 @@ func (cnc *CloudNodeController) UpdateNodeStatus(ctx context.Context) error { cnc.updateNodeAddress(ctx, node, instanceMetadata) } - workqueue.ParallelizeUntil(ctx, int(cnc.workerCount), len(nodes), updateNodeFunc) + workqueue.ParallelizeUntil(ctx, int(cnc.statusUpdateWorkerCount), len(nodes), updateNodeFunc) return nil } diff --git a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go index 03b211a06acf6..db1947a2b0e7f 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go +++ b/staging/src/k8s.io/cloud-provider/controllers/node/node_controller_test.go @@ -2788,6 +2788,7 @@ func TestUpdateNodeStatus(t *testing.T) { recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cloud-node-controller"}), nodeStatusUpdateFrequency: 1 * time.Second, workerCount: test.workers, + statusUpdateWorkerCount: test.workers, } for _, n := range generateNodes(test.nodes) { diff --git a/staging/src/k8s.io/cloud-provider/controllers/route/metrics.go b/staging/src/k8s.io/cloud-provider/controllers/route/metrics.go new file mode 100644 index 0000000000000..27a375e087cb9 --- /dev/null +++ b/staging/src/k8s.io/cloud-provider/controllers/route/metrics.go @@ -0,0 +1,46 @@ +/* +Copyright 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 route + +import ( + "sync" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const ( + // subsystem is the name of this subsystem used for prometheus metrics. + subsystem = "route_controller" +) + +var registration sync.Once + +var ( + routeSyncCount = metrics.NewCounter(&metrics.CounterOpts{ + Name: "route_sync_total", + Subsystem: subsystem, + Help: "A metric counting the amount of times routes have been synced with the cloud provider.", + StabilityLevel: metrics.ALPHA, + }) +) + +func registerMetrics() { + registration.Do(func() { + legacyregistry.MustRegister(routeSyncCount) + }) +} diff --git a/staging/src/k8s.io/cloud-provider/controllers/route/route_controller.go b/staging/src/k8s.io/cloud-provider/controllers/route/route_controller.go index c7e6c57e7da59..12cc3cb7fbb6a 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/route/route_controller.go +++ b/staging/src/k8s.io/cloud-provider/controllers/route/route_controller.go @@ -25,6 +25,7 @@ import ( "time" "golang.org/x/time/rate" + "k8s.io/klog/v2" netutils "k8s.io/utils/net" @@ -87,6 +88,8 @@ func New( clusterName string, clusterCIDRs []*net.IPNet, ) (*RouteController, error) { + registerMetrics() + if len(clusterCIDRs) == 0 { klog.Fatal("RouteController: Must specify clusterCIDR.") } @@ -247,6 +250,8 @@ func (rc *RouteController) processNextWorkItem(ctx context.Context) bool { } func (rc *RouteController) reconcileNodeRoutes(ctx context.Context) error { + routeSyncCount.Inc() + routeList, err := rc.routes.ListRoutes(ctx, rc.clusterName) if err != nil { return fmt.Errorf("error listing routes: %v", err) @@ -436,7 +441,7 @@ func (rc *RouteController) reconcile(ctx context.Context, nodes []*v1.Node, rout Name: string(nodeName), UID: node.UID, Namespace: "", - }, v1.EventTypeWarning, "FailedToCreateRoute", msg) + }, v1.EventTypeWarning, "FailedToCreateRoute", "%s", msg) klog.V(4).Info(msg) return err } @@ -497,12 +502,12 @@ func (rc *RouteController) reconcile(ctx context.Context, nodes []*v1.Node, rout func (rc *RouteController) updateNetworkingCondition(node *v1.Node, routesCreated bool) error { _, condition := nodeutil.GetNodeCondition(&(node.Status), v1.NodeNetworkUnavailable) - if routesCreated && condition != nil && condition.Status == v1.ConditionFalse { + if routesCreated && condition != nil && condition.Status == v1.ConditionFalse && condition.Reason == "RouteCreated" { klog.V(2).Infof("set node %v with NodeNetworkUnavailable=false was canceled because it is already set", node.Name) return nil } - if !routesCreated && condition != nil && condition.Status == v1.ConditionTrue { + if !routesCreated && condition != nil && condition.Status == v1.ConditionTrue && condition.Reason == "NoRouteCreated" { klog.V(2).Infof("set node %v with NodeNetworkUnavailable=true was canceled because it is already set", node.Name) return nil } diff --git a/staging/src/k8s.io/cloud-provider/controllers/route/route_controller_test.go b/staging/src/k8s.io/cloud-provider/controllers/route/route_controller_test.go index 7f7257e219cea..d11dec1abdcde 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/route/route_controller_test.go +++ b/staging/src/k8s.io/cloud-provider/controllers/route/route_controller_test.go @@ -636,3 +636,241 @@ func flatten(list []*cloudprovider.Route) []cloudprovider.Route { } return structList } + +func TestUpdateNetworkingCondition(t *testing.T) { + cluster := "test-cluster" + _, clusterCIDR, _ := netutils.ParseCIDRSloppy("10.244.0.0/16") + + testCases := []struct { + description string + existingCondition *v1.NodeCondition + routesCreated bool + expectedUpdate bool + expectedConditionType v1.ConditionStatus + expectedReason string + expectedMessage string + }{ + { + description: "No existing condition, routes created - should update", + existingCondition: nil, + routesCreated: true, + expectedUpdate: true, + expectedConditionType: v1.ConditionFalse, + expectedReason: "RouteCreated", + expectedMessage: "RouteController created a route", + }, + { + description: "No existing condition, routes not created - should update", + existingCondition: nil, + routesCreated: false, + expectedUpdate: true, + expectedConditionType: v1.ConditionTrue, + expectedReason: "NoRouteCreated", + expectedMessage: "RouteController failed to create a route", + }, + { + description: "Existing condition with same status and reason (RouteCreated) - should not update", + existingCondition: &v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionFalse, + Reason: "RouteCreated", + Message: "RouteController created a route", + }, + routesCreated: true, + expectedUpdate: false, + }, + { + description: "Existing condition with same status but different reason (CalicoIsUp) - should update", + existingCondition: &v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionFalse, + Reason: "CalicoIsUp", + Message: "Calico is running on this node", + }, + routesCreated: true, + expectedUpdate: true, + expectedConditionType: v1.ConditionFalse, + expectedReason: "RouteCreated", + expectedMessage: "RouteController created a route", + }, + { + description: "Existing condition with Status=False but different reason - should update when routes created", + existingCondition: &v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionFalse, + Reason: "ExternalCNI", + Message: "External CNI configured", + }, + routesCreated: true, + expectedUpdate: true, + expectedConditionType: v1.ConditionFalse, + expectedReason: "RouteCreated", + expectedMessage: "RouteController created a route", + }, + { + description: "Existing condition with Status=True and different reason - should update when routes not created", + existingCondition: &v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionTrue, + Reason: "SomeOtherReason", + Message: "Some other message", + }, + routesCreated: false, + expectedUpdate: true, + expectedConditionType: v1.ConditionTrue, + expectedReason: "NoRouteCreated", + expectedMessage: "RouteController failed to create a route", + }, + { + description: "Existing condition with Status=False, transitioning to routes not created - should update", + existingCondition: &v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionFalse, + Reason: "RouteCreated", + Message: "RouteController created a route", + }, + routesCreated: false, + expectedUpdate: true, + expectedConditionType: v1.ConditionTrue, + expectedReason: "NoRouteCreated", + expectedMessage: "RouteController failed to create a route", + }, + { + description: "Existing condition with Status=True and NoRouteCreated reason - should not update", + existingCondition: &v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionTrue, + Reason: "NoRouteCreated", + Message: "RouteController failed to create a route", + }, + routesCreated: false, + expectedUpdate: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + node := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + UID: "test-uid", + }, + Spec: v1.NodeSpec{ + PodCIDR: "10.244.0.0/24", + PodCIDRs: []string{"10.244.0.0/24"}, + }, + } + + // Set existing condition if specified + if tc.existingCondition != nil { + node.Status.Conditions = []v1.NodeCondition{*tc.existingCondition} + } + + client := fake.NewClientset(node) + informerFactory := informers.NewSharedInformerFactory(client, 0) + + rc, err := New(nil, client, informerFactory.Core().V1().Nodes(), cluster, []*net.IPNet{clusterCIDR}) + require.NoError(t, err) + rc.nodeListerSynced = alwaysReady + + // Call updateNetworkingCondition + err = rc.updateNetworkingCondition(node, tc.routesCreated) + require.NoError(t, err) + + if tc.expectedUpdate { + // Verify that a patch was called + actions := client.Actions() + patchFound := false + for _, action := range actions { + if action.GetVerb() == "patch" { + patchFound = true + break + } + } + assert.True(t, patchFound, "Expected a patch action but none found") + + // Get the updated node + updatedNode, err := client.CoreV1().Nodes().Get(context.TODO(), node.Name, metav1.GetOptions{}) + require.NoError(t, err) + + // Verify the condition was updated correctly + _, condition := nodeutil.GetNodeCondition(&updatedNode.Status, v1.NodeNetworkUnavailable) + require.NotNil(t, condition, "Expected NodeNetworkUnavailable condition to exist") + assert.Equal(t, tc.expectedConditionType, condition.Status, "Unexpected condition status") + assert.Equal(t, tc.expectedReason, condition.Reason, "Unexpected condition reason") + assert.Equal(t, tc.expectedMessage, condition.Message, "Unexpected condition message") + } else { + // Verify that no patch was called (or only initial operations) + actions := client.Actions() + patchCount := 0 + for _, action := range actions { + if action.GetVerb() == "patch" { + patchCount++ + } + } + assert.Equal(t, 0, patchCount, "Expected no patch action but found %d", patchCount) + } + }) + } +} + +func TestUpdateNetworkingConditionWithCalicoScenario(t *testing.T) { + // This test specifically covers the bug where Calico sets NetworkUnavailable=False + // and RouteController should still be able to update it with its own reason + cluster := "test-cluster" + _, clusterCIDR, _ := netutils.ParseCIDRSloppy("10.244.0.0/16") + + node := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + UID: "test-uid", + }, + Spec: v1.NodeSpec{ + PodCIDR: "10.244.0.0/24", + PodCIDRs: []string{"10.244.0.0/24"}, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionFalse, + Reason: "CalicoIsUp", + Message: "Calico is running on this node", + }, + }, + }, + } + + client := fake.NewClientset(node) + informerFactory := informers.NewSharedInformerFactory(client, 0) + + rc, err := New(nil, client, informerFactory.Core().V1().Nodes(), cluster, []*net.IPNet{clusterCIDR}) + require.NoError(t, err) + rc.nodeListerSynced = alwaysReady + + // Call updateNetworkingCondition with routes created + err = rc.updateNetworkingCondition(node, true) + require.NoError(t, err) + + // Verify that a patch was called + actions := client.Actions() + patchFound := false + for _, action := range actions { + if action.GetVerb() == "patch" { + patchFound = true + break + } + } + assert.True(t, patchFound, "Expected a patch action to update the condition") + + // Get the updated node + updatedNode, err := client.CoreV1().Nodes().Get(context.TODO(), node.Name, metav1.GetOptions{}) + require.NoError(t, err) + + // Verify the condition was updated with RouteCreated reason + _, condition := nodeutil.GetNodeCondition(&updatedNode.Status, v1.NodeNetworkUnavailable) + require.NotNil(t, condition, "Expected NodeNetworkUnavailable condition to exist") + assert.Equal(t, v1.ConditionFalse, condition.Status, "Expected Status to remain False") + assert.Equal(t, "RouteCreated", condition.Reason, "Expected Reason to be updated to RouteCreated") + assert.Equal(t, "RouteController created a route", condition.Message, "Expected Message to be updated") +} diff --git a/staging/src/k8s.io/cloud-provider/controllers/service/controller_test.go b/staging/src/k8s.io/cloud-provider/controllers/service/controller_test.go index 21348dbf509fa..84d9d667b15a2 100644 --- a/staging/src/k8s.io/cloud-provider/controllers/service/controller_test.go +++ b/staging/src/k8s.io/cloud-provider/controllers/service/controller_test.go @@ -307,8 +307,17 @@ func TestSyncLoadBalancerIfNeeded(t *testing.T) { if len(cloud.Calls) > 0 { t.Errorf("Unexpected cloud provider calls: %v", cloud.Calls) } - if len(actions) > 0 { - t.Errorf("Unexpected client actions: %v", actions) + // Ignore watch actions from the informer's reflector (timing-dependent). + var nonWatchActions []core.Action + for _, a := range actions { + if a.GetVerb() != "watch" { + nonWatchActions = append(nonWatchActions, a) + } else { + t.Logf("Ignoring watch action: %v", a) + } + } + if len(nonWatchActions) > 0 { + t.Logf("Unexpected client actions: %v", nonWatchActions) } return } diff --git a/staging/src/k8s.io/cloud-provider/go.mod b/staging/src/k8s.io/cloud-provider/go.mod index 574e82faa6f23..a25f932a67a2d 100644 --- a/staging/src/k8s.io/cloud-provider/go.mod +++ b/staging/src/k8s.io/cloud-provider/go.mod @@ -2,40 +2,40 @@ module k8s.io/cloud-provider -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/google/go-cmp v0.7.0 - github.com/spf13/cobra v1.10.0 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 - golang.org/x/time v0.9.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/component-base v0.35.1 + golang.org/x/time v0.14.0 + k8s.io/api v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/apiserver v0.36.2 + k8s.io/client-go v0.36.2 + k8s.io/component-base v0.36.2 k8s.io/component-helpers v0.0.0 k8s.io/controller-manager v0.0.0 - k8s.io/klog/v2 v2.130.1 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/klog/v2 v2.140.0 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // 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/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 @@ -47,12 +47,12 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // 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.27.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -62,59 +62,62 @@ require ( 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/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 // indirect + github.com/openshift/library-go v0.0.0-20260612230159-06432ea1b022 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // 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.opentelemetry.io/auto/sdk v1.1.0 // 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 v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // 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.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.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.45.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/grpc v1.72.2 // indirect - google.golang.org/protobuf v1.36.8 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // 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 + 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/grpc v1.79.3 // 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/kms v0.35.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + k8s.io/kms v0.36.2 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/streaming v0.36.2 // 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/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 + github.com/openshift/api => github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf + github.com/openshift/client-go => github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27 + github.com/openshift/library-go => github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 k8s.io/api => ../api k8s.io/apiextensions-apiserver => ../apiextensions-apiserver k8s.io/apimachinery => ../apimachinery @@ -125,4 +128,5 @@ replace ( k8s.io/controller-manager => ../controller-manager k8s.io/kms => ../kms k8s.io/kube-aggregator => ../kube-aggregator + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/cloud-provider/go.sum b/staging/src/k8s.io/cloud-provider/go.sum index f00678dff5c24..f6b572c228b33 100644 --- a/staging/src/k8s.io/cloud-provider/go.sum +++ b/staging/src/k8s.io/cloud-provider/go.sum @@ -1,18 +1,18 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= -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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2/go.mod h1:XyjUkMA8GN+tOOPXvnbi3XuRxWFvTJntqvTFnjmhzbk= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 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= @@ -21,16 +21,16 @@ 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= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 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/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -45,12 +45,12 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +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.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -60,7 +60,7 @@ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui72 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-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -77,14 +77,12 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0/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.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +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 v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= @@ -102,22 +100,18 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +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.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -129,6 +123,10 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jubittajohn/api v0.0.0-20260612223652-30cac1b00eaf/go.mod h1:FS918VnrERibSebUVMka1NP+eyrKGi5ry4nl/BXAKbs= +github.com/jubittajohn/client-go v0.0.0-20260612225005-10851b7bbd27/go.mod h1:bOkl2x9t+UlXxD8riuhEi0MbjjYnXuYCSzuDXTIA4wI= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022 h1:Pk6nmvZxrSQ9IlpsOC9FQiV45idIDLTN3hft7dzxdSA= +github.com/jubittajohn/library-go v0.0.0-20260612230159-06432ea1b022/go.mod h1:iGCkgqyOiqzvkTHEyxVf+RmxzvNNfEFR/AJLoFSxeG0= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -146,7 +144,7 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= @@ -160,18 +158,11 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= -github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= -github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a/go.mod h1:V933kvY/cb/Un7UCEOhXHUySNX327u7Epe8g9KNqg2Q= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3 h1:AHjJETxL4n2ocMHBY7Epqex574puUSJjKIUkbasYAKI= -github.com/openshift/library-go v0.0.0-20260520180710-3a6f949c22c3/go.mod h1:gKG9lctU0yEftSoT3DUyeIWz1oAgF0EHUpwI4pnCo4o= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 h1:02E4Ttpu+7yCQLQxtY42JfcfHU7TBGnje6uB2ytBSdU= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= @@ -185,10 +176,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -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/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.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -198,12 +189,11 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= -github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= -github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= 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= @@ -226,52 +216,51 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chq github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -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.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +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/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +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.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +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.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -279,61 +268,61 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -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/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +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/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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +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/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -350,20 +339,20 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -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/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +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-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/cloud-provider/options/nodecontroller.go b/staging/src/k8s.io/cloud-provider/options/nodecontroller.go index 4823c437b1674..9573276db0370 100644 --- a/staging/src/k8s.io/cloud-provider/options/nodecontroller.go +++ b/staging/src/k8s.io/cloud-provider/options/nodecontroller.go @@ -36,6 +36,8 @@ func (o *NodeControllerOptions) AddFlags(fs *pflag.FlagSet) { } fs.Int32Var(&o.ConcurrentNodeSyncs, "concurrent-node-syncs", o.ConcurrentNodeSyncs, "Number of workers concurrently synchronizing nodes.") + // If concurrent-node-status-updates is not specified it will be derived from concurrent-node-syncs for backwards compatibility + fs.Int32Var(&o.ConcurrentNodeStatusUpdates, "concurrent-node-status-updates", 0, "Number of workers concurrently updating node statuses. If unspecified or 0, --concurrent-node-syncs is used instead") } // ApplyTo fills up ServiceController config with options. @@ -45,6 +47,13 @@ func (o *NodeControllerOptions) ApplyTo(cfg *nodeconfig.NodeControllerConfigurat } cfg.ConcurrentNodeSyncs = o.ConcurrentNodeSyncs + // Concurrent node status updates used to be derived from the concurrent-node-syncs flag, + // so for backwards compatibility it will default to concurrent-node-syncs if concurrent-node-status-updates is not specified + if o.ConcurrentNodeStatusUpdates > 0 { + cfg.ConcurrentNodeStatusUpdates = o.ConcurrentNodeStatusUpdates + } else { + cfg.ConcurrentNodeStatusUpdates = o.ConcurrentNodeSyncs + } return nil } @@ -58,5 +67,8 @@ func (o *NodeControllerOptions) Validate() []error { if o.ConcurrentNodeSyncs <= 0 { errors = append(errors, fmt.Errorf("concurrent-node-syncs must be a positive number")) } + if o.ConcurrentNodeStatusUpdates < 0 { + errors = append(errors, fmt.Errorf("concurrent-node-status-updates cannot be a negative number")) + } return errors } diff --git a/staging/src/k8s.io/cloud-provider/options/nodecontroller_test.go b/staging/src/k8s.io/cloud-provider/options/nodecontroller_test.go index 6c024a9dabeb0..73ba895573c73 100644 --- a/staging/src/k8s.io/cloud-provider/options/nodecontroller_test.go +++ b/staging/src/k8s.io/cloud-provider/options/nodecontroller_test.go @@ -35,7 +35,7 @@ func errSliceEq(a []error, b []error) bool { return true } -func TestNodeControllerConcurrentNodeSyncsValidation(t *testing.T) { +func TestNodeControllerValidation(t *testing.T) { testCases := []struct { desc string input *NodeControllerOptions @@ -45,19 +45,28 @@ func TestNodeControllerConcurrentNodeSyncsValidation(t *testing.T) { desc: "empty options", }, { - desc: "negative value", + desc: "concurrent-node-syncs negative value", input: &NodeControllerOptions{NodeControllerConfiguration: &nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: -5}}, expect: []error{fmt.Errorf("concurrent-node-syncs must be a positive number")}, }, { - desc: "zero value", + desc: "concurrent-node-syncs zero value", input: &NodeControllerOptions{NodeControllerConfiguration: &nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: 0}}, expect: []error{fmt.Errorf("concurrent-node-syncs must be a positive number")}, }, { - desc: "positive value", + desc: "concurrent-node-syncs positive value, concurrent-node-status-updates default 0", input: &NodeControllerOptions{NodeControllerConfiguration: &nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: 5}}, }, + { + desc: "concurrent-node-syncs positive value, concurrent-node-status-updates positive value", + input: &NodeControllerOptions{NodeControllerConfiguration: &nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: 5, ConcurrentNodeStatusUpdates: 5}}, + }, + { + desc: "concurrent-node-syncs positive value, concurrent-node-status-updates negative value", + input: &NodeControllerOptions{NodeControllerConfiguration: &nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: 5, ConcurrentNodeStatusUpdates: -1}}, + expect: []error{fmt.Errorf("concurrent-node-status-updates cannot be a negative number")}, + }, } for _, tc := range testCases { got := tc.input.Validate() diff --git a/staging/src/k8s.io/cloud-provider/options/options.go b/staging/src/k8s.io/cloud-provider/options/options.go index 0534f9f614b07..2f24aab31d1c6 100644 --- a/staging/src/k8s.io/cloud-provider/options/options.go +++ b/staging/src/k8s.io/cloud-provider/options/options.go @@ -242,7 +242,9 @@ func (o *CloudControllerManagerOptions) ApplyTo(c *config.Config, allControllers // sync back to component config // TODO: find more elegant way than syncing back the values. c.ComponentConfig.NodeStatusUpdateFrequency = o.NodeStatusUpdateFrequency - c.ComponentConfig.NodeController.ConcurrentNodeSyncs = o.NodeController.ConcurrentNodeSyncs + if err = o.NodeController.ApplyTo(&c.ComponentConfig.NodeController); err != nil { + return err + } return nil } diff --git a/staging/src/k8s.io/cloud-provider/options/options_test.go b/staging/src/k8s.io/cloud-provider/options/options_test.go index 8cafb4518ab60..4eb2f401204e8 100644 --- a/staging/src/k8s.io/cloud-provider/options/options_test.go +++ b/staging/src/k8s.io/cloud-provider/options/options_test.go @@ -33,6 +33,7 @@ import ( cpconfig "k8s.io/cloud-provider/config" nodeconfig "k8s.io/cloud-provider/controllers/node/config" serviceconfig "k8s.io/cloud-provider/controllers/service/config" + cliflag "k8s.io/component-base/cli/flag" componentbaseconfig "k8s.io/component-base/config" cmconfig "k8s.io/controller-manager/config" cmoptions "k8s.io/controller-manager/options" @@ -202,6 +203,10 @@ func TestAddFlags(t *testing.T) { "--use-service-account-credentials=false", "--concurrent-node-syncs=5", "--webhooks=foo,bar,-baz", + "--webhook-disable-http2-serving=true", + "--webhook-tls-cipher-suites=TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA", + "--webhook-tls-min-version=VersionTLS13", + "--webhook-tls-sni-cert-key=example.crt,example.key:example.com", } err = fs.Parse(args) if err != nil { @@ -275,8 +280,16 @@ func TestAddFlags(t *testing.T) { CertDirectory: "", PairName: "cloud-controller-manager-webhook", }, - BindPort: 10260, - BindAddress: netutils.ParseIPSloppy("0.0.0.0"), + BindPort: 10260, + BindAddress: netutils.ParseIPSloppy("0.0.0.0"), + DisableHTTP2Serving: true, + CipherSuites: []string{"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"}, + MinTLSVersion: "VersionTLS13", + SNICertKeys: []cliflag.NamedCertKey{{ + CertFile: "example.crt", + KeyFile: "example.key", + Names: []string{"example.com"}, + }}, }, }, SecureServing: (&apiserveroptions.SecureServingOptions{ @@ -423,7 +436,11 @@ func TestCreateConfig(t *testing.T) { ServiceController: serviceconfig.ServiceControllerConfiguration{ ConcurrentServiceSyncs: 1, }, - NodeController: nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: 1}, + NodeController: nodeconfig.NodeControllerConfiguration{ + ConcurrentNodeSyncs: 1, + // ConcurrentNodeStatusUpdates should default to the value of ConcurrentNodeSyncs only at the stage of config creation + ConcurrentNodeStatusUpdates: 1, + }, NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Minute}, Webhook: cpconfig.WebhookConfiguration{ Webhooks: []string{"foo", "bar", "-baz"}, @@ -491,6 +508,7 @@ func TestCreateConfigWithoutWebHooks(t *testing.T) { "--controller-start-interval=2m", "--controllers=foo,bar", "--concurrent-node-syncs=1", + "--concurrent-node-status-updates=2", "--http2-max-streams-per-connection=47", "--kube-api-burst=101", "--kube-api-content-type=application/vnd.kubernetes.protobuf", @@ -564,7 +582,10 @@ func TestCreateConfigWithoutWebHooks(t *testing.T) { ServiceController: serviceconfig.ServiceControllerConfiguration{ ConcurrentServiceSyncs: 1, }, - NodeController: nodeconfig.NodeControllerConfiguration{ConcurrentNodeSyncs: 1}, + NodeController: nodeconfig.NodeControllerConfiguration{ + ConcurrentNodeSyncs: 1, + ConcurrentNodeStatusUpdates: 2, + }, NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Minute}, Webhook: cpconfig.WebhookConfiguration{}, }, diff --git a/staging/src/k8s.io/cloud-provider/options/webhook.go b/staging/src/k8s.io/cloud-provider/options/webhook.go index 5174ae80f1258..8dfc011c54536 100644 --- a/staging/src/k8s.io/cloud-provider/options/webhook.go +++ b/staging/src/k8s.io/cloud-provider/options/webhook.go @@ -29,6 +29,7 @@ import ( "k8s.io/apiserver/pkg/server/dynamiccertificates" apiserveroptions "k8s.io/apiserver/pkg/server/options" "k8s.io/cloud-provider/config" + cliflag "k8s.io/component-base/cli/flag" netutils "k8s.io/utils/net" ) @@ -124,18 +125,54 @@ func (o *WebhookServingOptions) AddFlags(fs *pflag.FlagSet) { fs.IntVar(&o.BindPort, "webhook-secure-port", o.BindPort, "Secure port to serve cloud provider webhooks. If 0, don't serve webhooks at all.") + fs.BoolVar(&o.DisableHTTP2Serving, "webhook-disable-http2-serving", o.DisableHTTP2Serving, + "If true, HTTP2 serving will be disabled for the webhook server [default=false]") + fs.StringVar(&o.ServerCert.CertDirectory, "webhook-cert-dir", o.ServerCert.CertDirectory, ""+ "The directory where the TLS certs are located. "+ - "If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.") + "If --webhook-tls-cert-file and --webhook-tls-private-key-file are provided, this flag will be ignored.") fs.StringVar(&o.ServerCert.CertKey.CertFile, "webhook-tls-cert-file", o.ServerCert.CertKey.CertFile, ""+ "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated "+ - "after server cert). If HTTPS serving is enabled, and --tls-cert-file and "+ - "--tls-private-key-file are not provided, a self-signed certificate and key "+ - "are generated for the public address and saved to the directory specified by --cert-dir.") + "after server cert). If HTTPS serving is enabled, and --webhook-tls-cert-file and "+ + "--webhook-tls-private-key-file are not provided, a self-signed certificate and key "+ + "are generated for the public address and saved to the directory specified by --webhook-cert-dir.") fs.StringVar(&o.ServerCert.CertKey.KeyFile, "webhook-tls-private-key-file", o.ServerCert.CertKey.KeyFile, - "File containing the default x509 private key matching --tls-cert-file.") + "File containing the default x509 private key matching --webhook-tls-cert-file.") + + tlsCipherPreferredValues := cliflag.PreferredTLSCipherNames() + tlsCipherInsecureValues := cliflag.InsecureTLSCipherNames() + fs.StringSliceVar(&o.CipherSuites, "webhook-tls-cipher-suites", o.CipherSuites, + "Comma-separated list of cipher suites for the webhook server. "+ + "If omitted, the default Go cipher suites will be used. \n"+ + "Preferred values: "+strings.Join(tlsCipherPreferredValues, ", ")+". \n"+ + "Insecure values: "+strings.Join(tlsCipherInsecureValues, ", ")+".") + + fs.Int32SliceVar(&o.CurvePreferences, "webhook-tls-curve-preferences", o.CurvePreferences, + "Comma-separated list of numeric Go crypto/tls CurveID values, "+ + "as the allowed key exchange mechanisms for the webhook 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.") + + tlsPossibleVersions := cliflag.TLSPossibleVersions() + fs.StringVar(&o.MinTLSVersion, "webhook-tls-min-version", o.MinTLSVersion, + "Minimum TLS version supported for the webhook server. "+ + "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) + + fs.Var(cliflag.NewNamedCertKeyArray(&o.SNICertKeys), "webhook-tls-sni-cert-key", ""+ + "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 webhook server 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 "+ + "--webhook-tls-sni-cert-key multiple times. "+ + "Examples: \"example.crt,example.key\" or \"foo.crt,foo.key:*.foo.com,foo.com\".") } func (o *WebhookServingOptions) Validate() []error { @@ -176,19 +213,53 @@ func (o *WebhookServingOptions) ApplyTo(cfg **server.SecureServingInfo, webhookC } *cfg = &server.SecureServingInfo{ - Listener: listener, + Listener: listener, + DisableHTTP2: o.DisableHTTP2Serving, } + c := *cfg serverCertFile, serverKeyFile := o.ServerCert.CertKey.CertFile, o.ServerCert.CertKey.KeyFile if len(serverCertFile) != 0 || len(serverKeyFile) != 0 { var err error - (*cfg).Cert, err = dynamiccertificates.NewDynamicServingContentFromFiles("serving-cert", serverCertFile, serverKeyFile) + c.Cert, err = dynamiccertificates.NewDynamicServingContentFromFiles("serving-cert", serverCertFile, serverKeyFile) if err != nil { - return err + return fmt.Errorf("failed to load serving cert: %w", err) } } else if o.ServerCert.GeneratedCert != nil { - (*cfg).Cert = o.ServerCert.GeneratedCert + c.Cert = o.ServerCert.GeneratedCert + } + + if len(o.CipherSuites) != 0 { + cipherSuites, err := cliflag.TLSCipherSuites(o.CipherSuites) + if err != nil { + return fmt.Errorf("failed to parse cipher suites: %w", err) + } + c.CipherSuites = cipherSuites + } + + if len(o.CurvePreferences) != 0 { + curvePreferences, err := cliflag.TLSCurvePreferences(o.CurvePreferences) + if err != nil { + return fmt.Errorf("failed to parse curve preferences: %w", err) + } + c.CurvePreferences = curvePreferences + } + + c.MinTLSVersion, err = cliflag.TLSVersion(o.MinTLSVersion) + if err != nil { + return fmt.Errorf("failed to parse TLS version: %w", err) + } + + // load SNI certs + namedTLSCerts := make([]dynamiccertificates.SNICertKeyContentProvider, 0, len(o.SNICertKeys)) + for _, nck := range o.SNICertKeys { + tlsCert, err := dynamiccertificates.NewDynamicSNIContentFromFiles("sni-serving-cert", nck.CertFile, nck.KeyFile, nck.Names...) + if err != nil { + return fmt.Errorf("failed to load SNI cert and key: %w", err) + } + namedTLSCerts = append(namedTLSCerts, tlsCert) } + c.SNICerts = namedTLSCerts return nil } diff --git a/staging/src/k8s.io/cluster-bootstrap/go.mod b/staging/src/k8s.io/cluster-bootstrap/go.mod index 23558d9e6eb1c..39b339d5ae3b1 100644 --- a/staging/src/k8s.io/cluster-bootstrap/go.mod +++ b/staging/src/k8s.io/cluster-bootstrap/go.mod @@ -2,16 +2,16 @@ module k8s.io/cluster-bootstrap -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/stretchr/testify v1.11.1 gopkg.in/go-jose/go-jose.v2 v2.6.3 k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 - k8s.io/klog/v2 v2.130.1 + k8s.io/klog/v2 v2.140.0 ) require ( @@ -25,20 +25,20 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) replace ( - github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7 k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/cluster-bootstrap/go.sum b/staging/src/k8s.io/cluster-bootstrap/go.sum index 18c10ad67df12..206bc45fa2e39 100644 --- a/staging/src/k8s.io/cluster-bootstrap/go.sum +++ b/staging/src/k8s.io/cluster-bootstrap/go.sum @@ -1,5 +1,4 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,12 +12,10 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -28,7 +25,7 @@ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3x github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -37,8 +34,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251120221002-696928a6a0d7/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -56,21 +51,21 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY 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= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -82,17 +77,17 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= 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/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -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 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/staging/src/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go b/staging/src/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go index 9391908f23999..bf498419440c6 100644 --- a/staging/src/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go +++ b/staging/src/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go @@ -211,7 +211,7 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } -// IsWatchListSemanticsSupported informs the reflector that this client +// IsWatchListSemanticsUnSupported informs the reflector that this client // doesn't support WatchList semantics. // // This is a synthetic method whose sole purpose is to satisfy the optional diff --git a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go index f5207a980e3e4..a437189b586df 100644 --- a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go +++ b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go @@ -24,7 +24,6 @@ import ( type Args struct { OutputFile string - BoundingDirs []string // Only deal with types rooted under these dirs. GoHeaderFile string } @@ -37,8 +36,6 @@ func New() *Args { func (args *Args) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&args.OutputFile, "output-file", "generated.deepcopy.go", "the name of the file to be generated") - fs.StringSliceVar(&args.BoundingDirs, "bounding-dirs", args.BoundingDirs, - "Comma-separated list of import paths which bound the types for which deep-copies will be generated.") fs.StringVar(&args.GoHeaderFile, "go-header-file", "", "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") } diff --git a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go index 0d86cd39cbb7a..ace09baa23f52 100644 --- a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go +++ b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go @@ -128,16 +128,6 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target klog.Fatalf("Failed loading boilerplate: %v", err) } - boundingDirs := []string{} - if args.BoundingDirs == nil { - args.BoundingDirs = context.Inputs - } - for i := range args.BoundingDirs { - // Strip any trailing slashes - they are not exactly "correct" but - // this is friendlier. - boundingDirs = append(boundingDirs, strings.TrimRight(args.BoundingDirs[i], "/")) - } - targets := []generator.Target{} for _, i := range context.Inputs { @@ -197,7 +187,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target }, GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { return []generator.Generator{ - NewGenDeepCopy(args.OutputFile, pkg.Path, boundingDirs, (ptagValue == tagValuePackage), ptagRegister), + NewGenDeepCopy(args.OutputFile, pkg.Path, (ptagValue == tagValuePackage), ptagRegister), } }, }) @@ -210,20 +200,18 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target type genDeepCopy struct { generator.GoGenerator targetPackage string - boundingDirs []string allTypes bool registerTypes bool imports namer.ImportTracker typesForInit []*types.Type } -func NewGenDeepCopy(outputFilename, targetPackage string, boundingDirs []string, allTypes, registerTypes bool) generator.Generator { +func NewGenDeepCopy(outputFilename, targetPackage string, allTypes, registerTypes bool) generator.Generator { return &genDeepCopy{ GoGenerator: generator.GoGenerator{ OutputFilename: outputFilename, }, targetPackage: targetPackage, - boundingDirs: boundingDirs, allTypes: allTypes, registerTypes: registerTypes, imports: generator.NewImportTrackerForPackage(targetPackage), diff --git a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go index aab3b3367bfd5..957aee6135152 100644 --- a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go +++ b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go @@ -23,7 +23,6 @@ import ( "sigs.k8s.io/randfill" - "k8s.io/apimachinery/pkg/util/dump" "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases" "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins" "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces" @@ -31,6 +30,7 @@ import ( "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer" "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices" "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs" + "k8s.io/utils/dump" ) func TestWithValueFuzzer(t *testing.T) { diff --git a/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go index 2379117c58432..0ce7d17f7dc90 100644 --- a/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go +++ b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go @@ -73,12 +73,18 @@ func extractDefaultTag(comments []string) ([]string, error) { return tags[defaultTagName], nil } -func extractTag(comments []string) ([]string, error) { +func extractTag(comments []string) ([]string, bool) { tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{tagName}, comments) if err != nil { - return nil, err + klog.Fatalf("Error extracting %s tag: %v", tagName, err) + } + + values, found := tags[tagName] + if !found || len(values) == 0 { + return nil, false } - return tags[tagName], nil + + return values, true } func extractInputTag(comments []string) ([]string, error) { @@ -330,9 +336,10 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target getManualDefaultingFunctions(context, context.Universe[pp], existingDefaulters) } - typesWith, err := extractTag(pkg.Comments) - if err != nil { - klog.Fatalf("Error extracting %s tag: %v", tagName, err) + typesWith, found := extractTag(pkg.Comments) + if !found { + klog.V(2).InfoS(" did not find required tag", "tag", tagName) + continue } shouldCreateObjectDefaulterFn := func(t *types.Type) bool { if defaults, ok := existingDefaulters[t]; ok && defaults.object != nil { diff --git a/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go b/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go index 773482d6e3bb9..067dfed0c1531 100644 --- a/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go +++ b/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go @@ -279,7 +279,6 @@ func Run(g *Generator) { path := filepath.Join(g.OutputDir, p.ImportPath()) outputPath := filepath.Join(g.OutputDir, p.OutputPath()) - protomessageOutputPath := filepath.Join(g.OutputDir, p.ProtomessageOutputPath()) // generate the gogoprotobuf protoc cmd := exec.Command("protoc", append(args, path)...) @@ -296,14 +295,11 @@ func Run(g *Generator) { // alter the generated protobuf file to remove the generated types (but leave the serializers) and rewrite the // package statement to match the desired package name - if err := RewriteGeneratedGogoProtobufFile(outputPath, protomessageOutputPath, p.ExtractGeneratedType, p.OptionalTypeName, buf.Bytes(), g.DropGogoGo); err != nil { + if err := RewriteGeneratedGogoProtobufFile(outputPath, p.ExtractGeneratedType, p.OptionalTypeName, buf.Bytes(), g.DropGogoGo); err != nil { log.Fatalf("Unable to rewrite generated %s: %v", outputPath, err) } outputPaths := []string{outputPath} - if g.DropGogoGo { - outputPaths = append(outputPaths, protomessageOutputPath) - } // sort imports cmd = exec.Command("goimports", append([]string{"-w"}, outputPaths...)...) diff --git a/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go b/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go index c35b73242af9f..b31a7c4dd792c 100644 --- a/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go +++ b/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go @@ -80,7 +80,7 @@ type protobufPackage struct { } func (p *protobufPackage) Clean() error { - for _, s := range []string{p.ImportPath(), p.OutputPath(), p.ProtomessageOutputPath()} { + for _, s := range []string{p.ImportPath(), p.OutputPath()} { if err := os.Remove(filepath.Join(p.Dir(), filepath.Base(s))); err != nil && !os.IsNotExist(err) { return err } @@ -200,10 +200,6 @@ func (p *protobufPackage) OutputPath() string { return filepath.Join(p.Path(), "generated.pb.go") } -func (p *protobufPackage) ProtomessageOutputPath() string { - return filepath.Join(p.Path(), "generated.protomessage.pb.go") -} - var ( _ = generator.Target(&protobufPackage{}) ) diff --git a/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go b/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go index ba0d112f6ea57..3753c27c9a083 100644 --- a/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go +++ b/staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go @@ -27,7 +27,6 @@ import ( "go/token" "os" "reflect" - "regexp" "strings" customreflect "k8s.io/code-generator/third_party/forked/golang/reflect" @@ -78,45 +77,7 @@ type ExtractFunc func(*ast.TypeSpec) bool // and should have its marshal functions adjusted to remove the 'Items' accessor. type OptionalFunc func(name string) bool -func RewriteGeneratedGogoProtobufFile(file, protomessageFile string, extractFn ExtractFunc, optionalFn OptionalFunc, header []byte, dropGogo bool) error { - - // Optionally extract ProtoMessage() marker methods to a separate build-tagged file - if dropGogo { - data, err := os.ReadFile(file) - if err != nil { - return err - } - - packageRE := regexp.MustCompile(`^package .*`) - wrotePackage := false - - protomessageFuncRE := regexp.MustCompile(`^func \(.*\) ProtoMessage\(\) \{\}$`) - - b := bytes.NewBuffer(nil) - // add build tag - b.WriteString("//go:build kubernetes_protomessage_one_more_release\n") - b.WriteString("// +build kubernetes_protomessage_one_more_release\n\n") - // add boilerplate - b.Write(header) - b.WriteString("\n// Code generated by go-to-protobuf. DO NOT EDIT.\n\n") - for _, line := range bytes.Split(data, []byte("\n")) { - // copy package - if packageRE.Match(line) && !wrotePackage { - b.Write(line) - b.WriteString("\n\n") - wrotePackage = true - } - // copy empty ProtoMessage impls - if protomessageFuncRE.Match(line) { - b.Write(line) - b.WriteString("\n\n") - } - } - if err := os.WriteFile(protomessageFile, b.Bytes(), os.FileMode(0644)); err != nil { - return err - } - } - +func RewriteGeneratedGogoProtobufFile(file string, extractFn ExtractFunc, optionalFn OptionalFunc, header []byte, dropGogo bool) error { return rewriteFile(file, header, func(fset *token.FileSet, file *ast.File) error { cmap := ast.NewCommentMap(fset, file, file.Comments) diff --git a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factory.go b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factory.go index a005f5e5e8870..237de9342fa55 100644 --- a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factory.go +++ b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factory.go @@ -74,8 +74,15 @@ func (g *factoryGenerator) GenerateType(c *generator.Context, t *types.Type, w i gvNewFuncs[groupPkgName] = c.Universe.Function(types.Name{Package: path.Join(g.outputPackage, groupPkgName), Name: "New"}) } m := map[string]interface{}{ + "cacheDoneChecker": c.Universe.Type(cacheDoneChecker), + "cacheInformerName": c.Universe.Type(cacheInformerName), "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), + "cacheSyncResult": c.Universe.Type(cacheSyncResult), "cacheTransformFunc": c.Universe.Type(cacheTransformFunc), + "cacheWaitFor": c.Universe.Function(cacheWaitForFunc), + "contextContext": c.Universe.Type(contextContext), + "contextCause": c.Universe.Function(contextCauseFunc), + "fmtErrorf": c.Universe.Function(fmtErrorfFunc), "groupVersions": g.groupVersions, "gvInterfaces": gvInterfaces, "gvNewFuncs": gvNewFuncs, @@ -87,10 +94,12 @@ func (g *factoryGenerator) GenerateType(c *generator.Context, t *types.Type, w i "reflectType": c.Universe.Type(reflectType), "runtimeObject": c.Universe.Type(runtimeObject), "schemaGroupVersionResource": c.Universe.Type(schemaGroupVersionResource), + "stringsBuilder": c.Universe.Type(stringsBuilder), "syncMutex": c.Universe.Type(syncMutex), "timeDuration": c.Universe.Type(timeDuration), "namespaceAll": c.Universe.Type(metav1NamespaceAll), "object": c.Universe.Type(metav1Object), + "waitContextForChannel": c.Universe.Function(waitContextForChannelFunc), } sw.Do(sharedInformerFactoryStruct, m) @@ -111,6 +120,7 @@ type sharedInformerFactory struct { defaultResync {{.timeDuration|raw}} customResync map[{{.reflectType|raw}}]{{.timeDuration|raw}} transform {{.cacheTransformFunc|raw}} + informerName *{{.cacheInformerName|raw}} informers map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}} // startedInformers is used for tracking which informers have been started. @@ -157,6 +167,21 @@ func WithTransform(transform {{.cacheTransformFunc|raw}}) SharedInformerOption { } } +// WithInformerName sets the InformerName for informer identity used in metrics. +// The InformerName must be created via cache.NewInformerName() at startup, +// which validates global uniqueness. Each informer type will register its +// GVR under this name. +func WithInformerName(informerName *{{.cacheInformerName|raw}}) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.informerName = informerName + return factory + } +} + +func (f *sharedInformerFactory) InformerName() *{{.cacheInformerName|raw}} { + return f.informerName +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client {{.clientSetInterface|raw}}, defaultResync {{.timeDuration|raw}}) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -191,6 +216,10 @@ func NewSharedInformerFactoryWithOptions(client {{.clientSetInterface|raw}}, def } func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext({{.waitContextForChannel|raw}}(stopCh)) +} + +func (f *sharedInformerFactory) StartWithContext(ctx {{.contextContext|raw}}) { f.lock.Lock() defer f.lock.Unlock() @@ -200,15 +229,9 @@ func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + f.wg.Go(func() { + informer.RunWithContext(ctx) + }) f.startedInformers[informerType] = true } } @@ -222,27 +245,54 @@ func (f *sharedInformerFactory) Shutdown() { // Will return immediately if there is nothing to wait for. f.wg.Wait() + f.informerName.Release() } func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { - informers := func()map[reflect.Type]cache.SharedIndexInformer{ - f.lock.Lock() - defer f.lock.Unlock() - - informers := map[reflect.Type]cache.SharedIndexInformer{} - for informerType, informer := range f.informers { - if f.startedInformers[informerType] { - informers[informerType] = informer - } - } - return informers - }() - - res := map[reflect.Type]bool{} - for informType, informer := range informers { - res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) - } - return res + result := f.WaitForCacheSyncWithContext(wait.ContextForChannel(stopCh)) + return result.Synced +} + +func (f *sharedInformerFactory) WaitForCacheSyncWithContext(ctx context.Context) {{.cacheSyncResult|raw}} { + informers := func() map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}} { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}}{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + // Wait for informers to sync, without polling. + cacheSyncs := make([]{{.cacheDoneChecker|raw}}, 0, len(informers)) + for _, informer := range informers { + cacheSyncs = append(cacheSyncs, informer.HasSyncedChecker()) + } + {{.cacheWaitFor|raw}}(ctx, "" /* no logging */, cacheSyncs...) + + res := {{.cacheSyncResult|raw}} { + Synced: make(map[{{.reflectType|raw}}]bool, len(informers)), + } + failed := false + for informType, informer := range informers { + hasSynced := informer.HasSynced() + if !hasSynced { + failed = true + } + res.Synced[informType] = hasSynced + } + if failed { + // context.Cause is more informative than ctx.Err(). + // This must be non-nil, otherwise WaitFor wouldn't have stopped + // prematurely. + res.Err = {{.contextCause|raw}}(ctx) + } + + return res } // InformerFor returns the SharedIndexInformer for obj using an internal @@ -263,7 +313,9 @@ func (f *sharedInformerFactory) InformerFor(obj {{.runtimeObject|raw}}, newFunc } informer = newFunc(f.client, resyncPeriod) - informer.SetTransform(f.transform) + if f.transform != nil { + informer.SetTransform(f.transform) + } f.informers[informerType] = informer return informer @@ -276,33 +328,52 @@ var sharedInformerFactoryInterface = ` // // It is typically used like this: // -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() -// factory := NewSharedInformerFactory(client, resyncPeriod) -// defer factory.WaitForStop() // Returns immediately if nothing was started. -// genericInformer := factory.ForResource(resource) -// typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } -// } +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// handle, err := typeInformer.Informer().AddEventHandler(...) +// if err != nil { +// return fmt.Errorf("register event handler: %v", err) +// } +// defer typeInformer.Informer().RemoveEventHandler(handle) // Avoids leaking goroutines. +// factory.StartWithContext(ctx) // Start processing these informers. +// synced := factory.WaitForCacheSyncWithContext(ctx) +// if err := synced.AsError(); err != nil { +// return err +// } +// for v := range synced { +// // Only if desired log some information similar to this. +// fmt.Fprintf(os.Stdout, "cache synced: %s", v) +// } +// +// // Also make sure that all of the initial cache events have been delivered. +// if !WaitFor(ctx, "event handler sync", handle.HasSyncedChecker()) { +// // Must have failed because of context. +// return fmt.Errorf("sync event handler: %w", context.Cause(ctx)) +// } // -// // Creating informers can also be created after Start, but then -// // Start must be called again: -// anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.StartWithContext(ctx) type SharedInformerFactory interface { {{.informerFactoryInterface|raw}} // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + // + // Contextual logging: StartWithContext should be used instead of Start in code which supports contextual logging. Start(stopCh <-chan struct{}) + // StartWithContext initializes all requested informers. They are handled in goroutines + // which run until the context gets canceled. + // Warning: StartWithContext does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + StartWithContext(ctx context.Context) + // Shutdown marks a factory as shutting down. At that point no new // informers can be started anymore and Start will return without // doing anything. @@ -317,7 +388,13 @@ type SharedInformerFactory interface { // WaitForCacheSync blocks until all started informers' caches were synced // or the stop channel gets closed. - WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // + // Contextual logging: WaitForCacheSync should be used instead of WaitForCacheSync in code which supports contextual logging. It also returns a more useful result. + WaitForCacheSync(stopCh <-chan struct{}) map[{{.reflectType|raw}}]bool + + // WaitForCacheSyncWithContext blocks until all started informers' caches were synced + // or the context gets canceled. + WaitForCacheSyncWithContext(ctx {{.contextContext|raw}}) {{.cacheSyncResult|raw}} // ForResource gives generic access to a shared informer of the matching type. ForResource(resource {{.schemaGroupVersionResource|raw}}) (GenericInformer, error) diff --git a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go index 278f8ed915c02..e59b8bcc5f644 100644 --- a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go +++ b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go @@ -63,6 +63,8 @@ func (g *factoryInterfaceGenerator) GenerateType(c *generator.Context, t *types. klog.V(5).Infof("processing type %v", t) m := map[string]interface{}{ + "cacheIndexers": c.Universe.Type(cacheIndexers), + "cacheInformerName": c.Universe.Type(cacheInformerName), "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), "clientSetPackage": c.Universe.Type(types.Name{Package: g.clientSetPackage, Name: "Interface"}), "runtimeObject": c.Universe.Type(runtimeObject), @@ -83,8 +85,27 @@ type NewInformerFunc func({{.clientSetPackage|raw}}, {{.timeDuration|raw}}) cach type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj {{.runtimeObject|raw}}, newFunc NewInformerFunc) {{.cacheSharedIndexInformer|raw}} + InformerName() *{{.cacheInformerName|raw}} } // TweakListOptionsFunc is a function that transforms a {{.v1ListOptions|raw}}. type TweakListOptionsFunc func(*{{.v1ListOptions|raw}}) + +// InformerOptions holds the options for creating an informer. +type InformerOptions struct { + // ResyncPeriod is the resync period for this informer. + // If not set, defaults to 0 (no resync). + ResyncPeriod {{.timeDuration|raw}} + + // Indexers are the indexers for this informer. + Indexers {{.cacheIndexers|raw}} + + // InformerName is used to uniquely identify this informer for metrics. + // If not set, metrics will not be published for this informer. + // Use cache.NewInformerName() to create an InformerName at startup. + InformerName *{{.cacheInformerName|raw}} + + // TweakListOptions is an optional function to modify the list options. + TweakListOptions TweakListOptionsFunc +} ` diff --git a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/informer.go b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/informer.go index 7d74c13a6b87c..ced706a64b53e 100644 --- a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/informer.go +++ b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/informer.go @@ -84,13 +84,18 @@ func (g *informerGenerator) GenerateType(c *generator.Context, t *types.Type, w "cacheMetaNamespaceIndexFunc": c.Universe.Function(cacheMetaNamespaceIndexFunc), "cacheNamespaceIndex": c.Universe.Variable(cacheNamespaceIndex), "cacheNewSharedIndexInformer": c.Universe.Function(cacheNewSharedIndexInformer), + "cacheNewSharedIndexInformerWithOptions": c.Universe.Function(cacheNewSharedIndexInformerWithOptions), "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), + "cacheSharedIndexInformerOptions": c.Universe.Type(cacheSharedIndexInformerOptions), "cacheToListWatcherWithWatchListSemantics": c.Universe.Function(cacheToListWatcherWithWatchListSemanticsFunc), + "cacheInformerName": c.Universe.Type(cacheInformerName), "clientSetInterface": clientSetInterface, "contextContext": c.Universe.Type(contextContext), "contextBackground": c.Universe.Function(contextBackgroundFunc), "group": namer.IC(g.groupGoName), + "groupName": g.groupVersion.Group.String(), "informerFor": informerFor, + "interfacesInformerOptions": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "InformerOptions"}), "interfacesTweakListOptionsFunc": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "TweakListOptionsFunc"}), "interfacesSharedInformerFactory": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "SharedInformerFactory"}), "listOptions": c.Universe.Type(listOptions), @@ -98,11 +103,14 @@ func (g *informerGenerator) GenerateType(c *generator.Context, t *types.Type, w "namespaceAll": c.Universe.Type(metav1NamespaceAll), "namespaced": !tags.NonNamespaced, "newLister": c.Universe.Function(types.Name{Package: listerPackage, Name: "New" + t.Name.Name + "Lister"}), + "resourceName": strings.ToLower(t.Name.Name) + "s", "runtimeObject": c.Universe.Type(runtimeObject), + "schemaGroupVersionResource": c.Universe.Type(schemaGroupVersionResource), "timeDuration": c.Universe.Type(timeDuration), "type": t, "v1ListOptions": c.Universe.Type(v1ListOptions), "version": namer.IC(g.groupVersion.Version.String()), + "versionName": g.groupVersion.Version.String(), "watchInterface": c.Universe.Type(watchInterface), } @@ -110,6 +118,7 @@ func (g *informerGenerator) GenerateType(c *generator.Context, t *types.Type, w sw.Do(typeInformerStruct, m) sw.Do(typeInformerPublicConstructor, m) sw.Do(typeFilteredInformerPublicConstructor, m) + sw.Do(typeInformerPublicConstructorWithOptions, m) sw.Do(typeInformerConstructor, m) sw.Do(typeInformerInformer, m) sw.Do(typeInformerLister, m) @@ -139,7 +148,7 @@ var typeInformerPublicConstructor = ` // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func New$.type|public$Informer(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, resyncPeriod $.timeDuration|raw$, indexers $.cacheIndexers|raw$) $.cacheSharedIndexInformer|raw$ { - return NewFiltered$.type|public$Informer(client$if .namespaced$, namespace$end$, resyncPeriod, indexers, nil) + return New$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: indexers}) } ` @@ -148,43 +157,58 @@ var typeFilteredInformerPublicConstructor = ` // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFiltered$.type|public$Informer(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, resyncPeriod $.timeDuration|raw$, indexers $.cacheIndexers|raw$, tweakListOptions $.interfacesTweakListOptionsFunc|raw$) $.cacheSharedIndexInformer|raw$ { - return $.cacheNewSharedIndexInformer|raw$( + return New$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} +` + +var typeInformerPublicConstructorWithOptions = ` +// New$.type|public$InformerWithOptions constructs a new informer for $.type|public$ type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func New$.type|public$InformerWithOptions(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, options $.interfacesInformerOptions|raw$) $.cacheSharedIndexInformer|raw$ { + gvr := $.schemaGroupVersionResource|raw${Group: "$.groupName$", Version: "$.versionName$", Resource: "$.resourceName$"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return $.cacheNewSharedIndexInformerWithOptions|raw$( $.cacheToListWatcherWithWatchListSemantics|raw$(&$.cacheListWatch|raw${ - ListFunc: func(options $.v1ListOptions|raw$) ($.runtimeObject|raw$, error) { + ListFunc: func(opts $.v1ListOptions|raw$) ($.runtimeObject|raw$, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).List($.contextBackground|raw$(), options) + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).List($.contextBackground|raw$(), opts) }, - WatchFunc: func(options $.v1ListOptions|raw$) ($.watchInterface|raw$, error) { + WatchFunc: func(opts $.v1ListOptions|raw$) ($.watchInterface|raw$, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).Watch($.contextBackground|raw$(), options) + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).Watch($.contextBackground|raw$(), opts) }, - ListWithContextFunc: func(ctx $.contextContext|raw$, options $.v1ListOptions|raw$) ($.runtimeObject|raw$, error) { + ListWithContextFunc: func(ctx $.contextContext|raw$, opts $.v1ListOptions|raw$) ($.runtimeObject|raw$, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).List(ctx, options) + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).List(ctx, opts) }, - WatchFuncWithContext: func(ctx $.contextContext|raw$, options $.v1ListOptions|raw$) ($.watchInterface|raw$, error) { + WatchFuncWithContext: func(ctx $.contextContext|raw$, opts $.v1ListOptions|raw$) ($.watchInterface|raw$, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).Watch(ctx, options) + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).Watch(ctx, opts) }, }, client), &$.type|raw${}, - resyncPeriod, - indexers, + $.cacheSharedIndexInformerOptions|raw${ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } ` var typeInformerConstructor = ` func (f *$.type|private$Informer) defaultInformer(client $.clientSetInterface|raw$, resyncPeriod $.timeDuration|raw$) $.cacheSharedIndexInformer|raw$ { - return NewFiltered$.type|public$Informer(client$if .namespaced$, f.namespace$end$, resyncPeriod, $.cacheIndexers|raw${$.cacheNamespaceIndex|raw$: $.cacheMetaNamespaceIndexFunc|raw$}, f.tweakListOptions) + return New$.type|public$InformerWithOptions(client$if .namespaced$, f.namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: $.cacheIndexers|raw${$.cacheNamespaceIndex|raw$: $.cacheMetaNamespaceIndexFunc|raw$}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } ` diff --git a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/types.go b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/types.go index 2e1edbd624aa7..445b4604fc121 100644 --- a/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/types.go +++ b/staging/src/k8s.io/code-generator/cmd/informer-gen/generators/types.go @@ -20,17 +20,24 @@ import "k8s.io/gengo/v2/types" var ( apiScheme = types.Name{Package: "k8s.io/kubernetes/pkg/api/legacyscheme", Name: "Scheme"} + cacheDoneChecker = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "DoneChecker"} cacheGenericLister = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "GenericLister"} cacheIndexers = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "Indexers"} + cacheInformerName = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "InformerName"} cacheListWatch = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "ListWatch"} cacheMetaNamespaceIndexFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "MetaNamespaceIndexFunc"} cacheNamespaceIndex = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NamespaceIndex"} cacheNewGenericLister = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewGenericLister"} cacheNewSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewSharedIndexInformer"} + cacheNewSharedIndexInformerWithOptions = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewSharedIndexInformerWithOptions"} cacheSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SharedIndexInformer"} + cacheSharedIndexInformerOptions = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SharedIndexInformerOptions"} + cacheSyncResult = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SyncResult"} cacheTransformFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TransformFunc"} cacheToListWatcherWithWatchListSemanticsFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "ToListWatcherWithWatchListSemantics"} + cacheWaitForFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "WaitFor"} contextBackgroundFunc = types.Name{Package: "context", Name: "Background"} + contextCauseFunc = types.Name{Package: "context", Name: "Cause"} contextContext = types.Name{Package: "context", Name: "Context"} fmtErrorfFunc = types.Name{Package: "fmt", Name: "Errorf"} listOptions = types.Name{Package: "k8s.io/kubernetes/pkg/apis/core", Name: "ListOptions"} @@ -38,10 +45,12 @@ var ( runtimeObject = types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "Object"} schemaGroupResource = types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupResource"} schemaGroupVersionResource = types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionResource"} + stringsBuilder = types.Name{Package: "strings", Name: "Builder"} syncMutex = types.Name{Package: "sync", Name: "Mutex"} timeDuration = types.Name{Package: "time", Name: "Duration"} v1ListOptions = types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"} metav1NamespaceAll = types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "NamespaceAll"} metav1Object = types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "Object"} + waitContextForChannelFunc = types.Name{Package: "k8s.io/apimachinery/pkg/util/wait", Name: "ContextForChannel"} watchInterface = types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"} ) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/lint.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/lint.go index ee29386e2e8f7..63e54e755e98a 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/lint.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/lint.go @@ -19,8 +19,8 @@ package main import ( "fmt" "sort" - "strings" + "k8s.io/gengo/v2/codetags" "k8s.io/gengo/v2/types" "k8s.io/klog/v2" ) @@ -37,9 +37,12 @@ type linter struct { } // lintRule is a function that validates a slice of comments. +// container is the type containing the element being linted (e.g. the Struct when linting a Field). +// It may be nil if the element is top-level (e.g. a Type definition). +// t is the type of the element being linted (e.g. the Field's type, or the Type itself). // It returns a string as an error message if the comments are invalid, // and an error there is an error happened during the linting process. -type lintRule func(comments []string) (string, error) +type lintRule func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) func (l *linter) AddError(t *types.Type, field, msg string) { var err error @@ -53,7 +56,7 @@ func (l *linter) AddError(t *types.Type, field, msg string) { func newLinter(rules ...lintRule) *linter { if len(rules) == 0 { - rules = defaultLintRules + klog.Errorf("rules are not passed to the linter") } return &linter{ linted: make(map[*types.Type]bool), @@ -69,8 +72,12 @@ func (l *linter) lintType(t *types.Type) error { l.linted[t] = true if t.CommentLines != nil { + extracted := codetags.Extract("+", t.CommentLines) + if _, ok := extracted["k8s:validation-gen-nolint"]; ok { + return nil + } klog.V(5).Infof("linting type %s", t.Name.String()) - lintErrs, err := l.lintComments(t.CommentLines) + lintErrs, err := l.lintComments(t, t, t.CommentLines) if err != nil { return err } @@ -88,7 +95,7 @@ func (l *linter) lintType(t *types.Type) error { // Recursively lint each member of the struct. for _, member := range t.Members { klog.V(5).Infof("linting comments for field %s of type %s", member.String(), t.Name.String()) - lintErrs, err := l.lintComments(member.CommentLines) + lintErrs, err := l.lintComments(t, member.Type, member.CommentLines) if err != nil { return err } @@ -117,10 +124,29 @@ func (l *linter) lintType(t *types.Type) error { } // lintComments runs all registered rules on a slice of comments. -func (l *linter) lintComments(comments []string) ([]string, error) { +func (l *linter) lintComments(container *types.Type, t *types.Type, comments []string) ([]string, error) { var lintErrs []string + var tags []codetags.Tag + + extracted := codetags.Extract("+", comments) + keys := make([]string, 0, len(extracted)) + for k := range extracted { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, tagName := range keys { + lines := extracted[tagName] + t, err := codetags.ParseAll(lines) + if err != nil { + // If parsing fails, it means it is not a valid tag. + continue + } + tags = append(tags, t...) + } + for _, rule := range l.rules { - if msg, err := rule(comments); err != nil { + if msg, err := rule(container, t, tags); err != nil { return nil, err } else if msg != "" { lintErrs = append(lintErrs, msg) @@ -129,30 +155,3 @@ func (l *linter) lintComments(comments []string) ([]string, error) { return lintErrs, nil } - -// conflictingTagsRule creates a lintRule which checks for conflicting tags. -func conflictingTagsRule(msg string, tags ...string) lintRule { - if len(tags) < 2 { - panic("conflictingTagsRule: at least 2 tags must be specified") - } - - return func(comments []string) (string, error) { - found := make(map[string]bool) - for _, comment := range comments { - for _, tag := range tags { - if strings.HasPrefix(comment, tag) { - found[tag] = true - } - } - } - if len(found) > 1 { - keys := make([]string, 0, len(found)) - for k := range found { - keys = append(keys, k) - } - sort.Strings(keys) - return fmt.Sprintf("conflicting tags: {%s}: %s", strings.Join(keys, ", "), msg), nil - } - return "", nil - } -} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_rules.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_rules.go index 719d74f1a597f..1bc95c305ba61 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_rules.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_rules.go @@ -16,15 +16,271 @@ limitations under the License. package main -var ruleOptionalAndRequired = conflictingTagsRule( - "fields cannot be both optional and required", - "+k8s:optional", "+k8s:required") +import ( + "fmt" + "path" + "strings" -var ruleRequiredAndDefault = conflictingTagsRule( - "fields with default values are always optional", - "+k8s:required", "+default") + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/types" +) -var defaultLintRules = []lintRule{ - ruleOptionalAndRequired, - ruleRequiredAndDefault, +func checkAlphaBetaUsage(tag codetags.Tag, isRoot bool) (string, error) { + if tag.Name == "k8s:alpha" || tag.Name == "k8s:beta" { + if !isRoot { + return fmt.Sprintf("tag %q can't be used in between", tag.Name), nil + } + if tag.ValueTag == nil { + return fmt.Sprintf("tag %q requires a validation tag as its value payload", tag.Name), nil + } + } + + if tag.ValueTag != nil { + return checkAlphaBetaUsage(*tag.ValueTag, false) + } + return "", nil +} + +// alphaBetaPrefix enforces that +k8s:alpha and +k8s:beta tags are always used as prefix to +func alphaBetaPrefix() lintRule { + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + for _, tag := range tags { + // Only check alpha/beta tags or validation tags. + if msg, err := checkAlphaBetaUsage(tag, true); err != nil || msg != "" { + return msg, err + } + } + return "", nil + } +} + +// checkTagStability recursively checks that a tag and its nested tags +// satisfy the stability requirements of the context. +func checkTagStability(tag codetags.Tag, contextLevel validators.TagStabilityLevel) (string, error) { + tagStability, err := validators.GetStability(tag.Name) + // all DV tags have stability, if a tag doesn't have stability then it is not a valid DV tag. + if err != nil { + return "", nil + } + cmpOrder, err := tagStability.Compare(contextLevel) + if err != nil { + return "", err + } + if cmpOrder < 0 { + return fmt.Sprintf("tag %q with stability level %q cannot be used in %s validation", tag.Name, tagStability, contextLevel), nil + } + if tag.ValueTag != nil { + return checkTagStability(*tag.ValueTag, contextLevel) + } + return "", nil +} + +// validationStability enforces stability level constraints on tags. +func validationStability() lintRule { + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + pkgPath := t.Name.Package + if container != nil { + pkgPath = container.Name.Package + } + + // Unprefixed validations are normally required to be Stable. + // In Alpha packages, we allow Alpha-level and Beta-level validations + // without a prefix. In Beta packages, we allow Beta-level validations + // without a prefix. + defaultContextLevel := validators.TagStabilityLevelStable + // APIVersion is the last element of the package path. + apiVersion := path.Base(pkgPath) + if strings.Contains(apiVersion, "alpha") { + defaultContextLevel = validators.TagStabilityLevelAlpha + } else if strings.Contains(apiVersion, "beta") { + defaultContextLevel = validators.TagStabilityLevelBeta + } + + for _, tag := range tags { + contextLevel := defaultContextLevel + tagToCheck := tag + + // For stability level tags, set the stability context for the inner validation, + // overriding the package-level default. + if tag.Name == "k8s:alpha" || tag.Name == "k8s:beta" { + if tag.Name == "k8s:alpha" { + contextLevel = validators.TagStabilityLevelAlpha + } else { + contextLevel = validators.TagStabilityLevelBeta + } + if tag.ValueTag == nil { + continue + } + tagToCheck = *tag.ValueTag + } + + // For feature gate tags, we allow developers to use nested beta validation tags + // without forcing validation authors to write redundant handwritten code (bypassing the + // declarative validation equivalence check), we automatically relax the stability + // context to Beta if the current context is Stable. + if tagToCheck.Name == "k8s:ifEnabled" || tagToCheck.Name == "k8s:ifDisabled" { + if contextLevel == validators.TagStabilityLevelStable { + contextLevel = validators.TagStabilityLevelBeta + } + } + + msg, err := checkTagStability(tagToCheck, contextLevel) + if err != nil { + return "", err + } + if msg != "" { + return msg, nil + } + } + return "", nil + } +} + +// hasTag recursively checks if a tag with given name exists in the tag tree. +func hasTag(tags []codetags.Tag, name string) bool { + for _, tag := range tags { + if tag.Name == name { + return true + } + // Also check conditional tags value + if tag.ValueTag != nil && hasTag([]codetags.Tag{*tag.ValueTag}, name) { + return true + } + } + return false +} + +// hasRequirednessTag returns true if tags contain +k8s:optional, +k8s:required, or +k8s:forbidden. +func hasRequirednessTag(tags []codetags.Tag) bool { + return hasTag(tags, "k8s:optional") || hasTag(tags, "k8s:required") || hasTag(tags, "k8s:forbidden") +} + +// hasAnyValidationTag returns true if tags contain any registered validation tag. +func hasAnyValidationTag(tags []codetags.Tag) bool { + for _, tag := range tags { + switch tag.Name { + case "k8s:optional": + continue + case "k8s:alpha", "k8s:beta": + if tag.ValueTag != nil && hasAnyValidationTag([]codetags.Tag{*tag.ValueTag}) { + return true + } + continue + } + // Check if it's a known validation tag. + if _, err := validators.GetStability(tag.Name); err == nil { + return true + } + } + return false +} + +// requiredAndOptional checks that fields (pointers, slices, maps, arrays) with validation +// (either direct or transitive) explicitly declare +k8s:optional or +k8s:required. +func requiredAndOptional(extractor validators.ValidationExtractor) lintRule { + // Cache for transitive validation check. + // Tri-state: key absent = unvisited, value nil = in-progress (cycle), value *bool = computed result. + hasValidation := make(map[*types.Type]*bool) + + // checkType recursively checks if a type has any validation (transitively). + var checkType func(t *types.Type) (bool, bool) + checkType = func(t *types.Type) (bool, bool) { + if val, ok := hasValidation[t]; ok { + if val == nil { + return false, true // Cycle detected, break conservatively + } + return *val, false + } + // Mark in-progress + hasValidation[t] = nil + extractedTags, err := extractor.ExtractTags(validators.Context{}, t.CommentLines) + hasVal := err == nil && hasAnyValidationTag(extractedTags) + cycleBroken := false + + switch t.Kind { + case types.Alias: + if hv, cb := checkType(t.Underlying); hv { + hasVal = true + } else if cb { + cycleBroken = true + } + case types.Struct: + for _, member := range t.Members { + memberTags, err := extractor.ExtractTags(validators.Context{}, member.CommentLines) + memberHasVal := err == nil && hasAnyValidationTag(memberTags) + if hv, cb := checkType(member.Type); hv { + memberHasVal = true + } else if cb { + cycleBroken = true + } + if memberHasVal { + hasVal = true + } + } + case types.Slice, types.Array, types.Pointer: + if hv, cb := checkType(t.Elem); hv { + hasVal = true + } else if cb { + cycleBroken = true + } + case types.Map: + if hv, cb := checkType(t.Key); hv { + hasVal = true + } else if cb { + cycleBroken = true + } + if hv, cb := checkType(t.Elem); hv { + hasVal = true + } else if cb { + cycleBroken = true + } + } + + if hasVal || !cycleBroken { + hasValidation[t] = &hasVal + } else { + delete(hasValidation, t) + } + return hasVal, cycleBroken + } + + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + // We only care about fields in a struct. Skip if linting the struct itself. + if container == nil || container.Kind != types.Struct || container == t { + return "", nil + } + + // Skip non-pointer structs (and aliases to them) as they don't support requiredness tags. + underlying := t + for underlying.Kind == types.Alias { + underlying = underlying.Underlying + } + if underlying.Kind == types.Struct { + return "", nil + } + + // Check if already has requiredness tag + if hasRequirednessTag(tags) { + return "", nil + } + + // Check if it has validation (direct or transitive) + hasDirectVal := hasAnyValidationTag(tags) + hasTransitiveVal, _ := checkType(t) + + if hasDirectVal || hasTransitiveVal { + return "field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", nil + } + + return "", nil + } +} + +func lintRules(extractor validators.ValidationExtractor) []lintRule { + return []lintRule{ + alphaBetaPrefix(), + validationStability(), + requiredAndOptional(extractor), + } } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_test.go index dce38d14ed2cd..5cb19f6d5a47f 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/lint_test.go @@ -18,31 +18,35 @@ package main import ( "errors" - "regexp" "testing" + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/generator" "k8s.io/gengo/v2/types" ) -func ruleAlwaysPass(comments []string) (string, error) { +func ruleAlwaysPass(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { return "", nil } -func ruleAlwaysFail(comments []string) (string, error) { +func ruleAlwaysFail(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { return "lintfail", nil } -func ruleAlwaysErr(comments []string) (string, error) { +func ruleAlwaysErr(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { return "", errors.New("linterr") } func mkCountRule(counter *int, realRule lintRule) lintRule { - return func(comments []string) (string, error) { + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { (*counter)++ - return realRule(comments) + return realRule(container, t, tags) } } +var validator = validators.InitGlobalValidator(&generator.Context{}) + func TestLintCommentsRuleInvocation(t *testing.T) { tests := []struct { name string @@ -104,7 +108,7 @@ func TestLintCommentsRuleInvocation(t *testing.T) { } l := newLinter(rules...) for _, commentLines := range tt.commentLineGroups { - _, err := l.lintComments(commentLines) + _, err := l.lintComments(nil, nil, commentLines) gotErr := err != nil if gotErr != tt.wantErr { t.Errorf("lintComments() error = %v, wantErr %v", err, tt.wantErr) @@ -117,171 +121,155 @@ func TestLintCommentsRuleInvocation(t *testing.T) { } } -func TestRuleOptionalAndRequired(t *testing.T) { +func TestRuleAlphaBetaPrefix(t *testing.T) { tests := []struct { name string comments []string wantMsg string }{ { - name: "no comments", - comments: []string{}, + name: "valid alpha prefix", + comments: []string{"+k8s:alpha=+k8s:required"}, wantMsg: "", }, { - name: "only optional", - comments: []string{"+k8s:optional"}, + name: "valid beta prefix", + comments: []string{"+k8s:beta=+k8s:required"}, wantMsg: "", }, { - name: "only required", - comments: []string{"+k8s:required"}, - wantMsg: "", + name: "invalid alpha prefix (no value)", + comments: []string{"+k8s:alpha"}, + wantMsg: `tag "k8s:alpha" requires a validation tag as its value payload`, }, { - name: "optional required", - comments: []string{"+k8s:optional", "+k8s:required"}, - wantMsg: `conflicting tags: {\+k8s:optional, \+k8s:required}`, + name: "invalid beta prefix (no value)", + comments: []string{"+k8s:beta"}, + wantMsg: `tag "k8s:beta" requires a validation tag as its value payload`, }, { - name: "required optional", - comments: []string{"+k8s:optional", "+k8s:required"}, - wantMsg: `conflicting tags: {\+k8s:optional, \+k8s:required}`, + name: "invalid alpha prefix (value not tag)", + comments: []string{"+k8s:alpha=foo"}, + wantMsg: `tag "k8s:alpha" requires a validation tag as its value payload`, }, { - name: "optional empty required", - comments: []string{"+k8s:optional", "", "+k8s:required"}, - wantMsg: `conflicting tags: {\+k8s:optional, \+k8s:required}`, + name: "invalid usage of alpha prefix", + comments: []string{`+k8s:item(type: "Approved")=+k8s:alpha=+k8s:zeroOrOneOfMember`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, }, { - name: "empty required empty empty optional empty", - comments: []string{"", "+k8s:optional", "", "", "+k8s:required", ""}, - wantMsg: `conflicting tags: {\+k8s:optional, \+k8s:required}`, + name: "nested alpha in item", + comments: []string{`+k8s:item=+k8s:alpha=+k8s:required`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, + }, + { + name: "alpha nested in listType", + comments: []string{`+k8s:listType=+k8s:alpha=+k8s:required`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, + }, + { + name: "deeply nested alpha", + comments: []string{`+k8s:item=+k8s:listType=+k8s:alpha=+k8s:required`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - msg, err := ruleOptionalAndRequired(tt.comments) + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + msg, err := alphaBetaPrefix()(nil, nil, tags) if err != nil { t.Errorf("unexpected error: %v", err) - } else if tt.wantMsg != "" { - re := regexp.MustCompile(tt.wantMsg) - if !re.MatchString(msg) { - t.Errorf("message:\n\t%s\ndoes not match:\n\t%s", msg, re.String()) - } + } else if msg != tt.wantMsg { + t.Errorf("got %q, want %q", msg, tt.wantMsg) } }) } } -func TestRuleRequiredAndDefault(t *testing.T) { +func TestRuleStability(t *testing.T) { tests := []struct { name string comments []string + pkg string wantMsg string }{ { - name: "no comments", - comments: []string{}, + name: "stable context, stable tag", + comments: []string{"+k8s:required"}, // Stable wantMsg: "", }, { - name: "only required", - comments: []string{"+k8s:required"}, + name: "beta context, stable tag", + comments: []string{"+k8s:beta=+k8s:required"}, // Beta context, Stable tag wantMsg: "", }, { - name: "only default", - comments: []string{"+default=somevalue"}, + name: "alpha context, stable tag", + comments: []string{"+k8s:alpha=+k8s:required"}, // Alpha context, Stable tag wantMsg: "", }, { - name: "required default", - comments: []string{"+k8s:required", "+default=somevalue"}, - wantMsg: `conflicting tags: {\+default, \+k8s:required}`, + name: "alpha context, alpha tag", + comments: []string{"+k8s:alpha=+k8s:validateTrueAlpha"}, // Alpha context, Alpha tag + wantMsg: "", }, { - name: "default required", - comments: []string{"+default=somevalue", "+k8s:required"}, - wantMsg: `conflicting tags: {\+default, \+k8s:required}`, + name: "stable context, alpha tag", + comments: []string{"+k8s:validateTrueAlpha"}, // Stable context, Alpha tag + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Stable validation`, }, { - name: "required empty default", - comments: []string{"+k8s:required", "", "+default=somevalue"}, - wantMsg: `conflicting tags: {\+default, \+k8s:required}`, + name: "beta context, alpha tag", + comments: []string{"+k8s:beta=+k8s:validateTrueAlpha"}, // Beta context, Alpha tag + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Beta validation`, }, { - name: "empty default empty empty required empty", - comments: []string{"", "+default=somevalue", "", "", "+k8s:required", ""}, - wantMsg: `conflicting tags: {\+default, \+k8s:required}`, + name: "alpha pkg context, beta tag (allowed)", + comments: []string{"+k8s:validateTrueBeta"}, // Beta tag in Alpha package + pkg: "k8s.io/api/apps/v1alpha1", + wantMsg: "", }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - msg, err := ruleRequiredAndDefault(tt.comments) - if err != nil { - t.Errorf("unexpected error: %v", err) - } else if tt.wantMsg != "" { - re := regexp.MustCompile(tt.wantMsg) - if !re.MatchString(msg) { - t.Errorf("message:\n\t%s\ndoes not match:\n\t%s", msg, re.String()) - } - } - }) - } -} - -func TestConflictingTagsRule(t *testing.T) { - tests := []struct { - name string - comments []string - tags []string - wantMsg string - }{ { - name: "no comments", - comments: []string{}, - tags: []string{"+tag1", "+tag2"}, + name: "alpha pkg context, alpha tag (allowed)", + comments: []string{"+k8s:validateTrueAlpha"}, // Alpha tag in Alpha package + pkg: "k8s.io/api/apps/v1alpha1", wantMsg: "", }, { - name: "only tag1", - comments: []string{"+tag1"}, - tags: []string{"+tag1", "+tag2"}, + name: "beta pkg context, beta tag (allowed)", + comments: []string{"+k8s:validateTrueBeta"}, // Beta tag in Beta package + pkg: "k8s.io/api/apps/v1beta1", wantMsg: "", }, { - name: "tag1, empty, tag2", - comments: []string{"+tag1", "", "+tag2"}, - tags: []string{"+tag1", "+tag2"}, - wantMsg: `conflicting tags: {\+tag1, \+tag2}`, + name: "beta pkg context, alpha tag (fails)", + comments: []string{"+k8s:validateTrueAlpha"}, // Alpha tag in Beta package + pkg: "k8s.io/api/apps/v1beta1", + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Beta validation`, }, { - name: "3 lines 2 tags match", - comments: []string{"tag3", "+tag1", "+tag2=value"}, - tags: []string{"+tag1", "+tag2", "+tag3"}, - wantMsg: `conflicting tags: {\+tag1, \+tag2}`, + name: "ifEnabled context allows beta tag", + comments: []string{"+k8s:ifEnabled(SomeFeature)=+k8s:validateTrueBeta"}, // Beta tag in ifEnabled + wantMsg: "", }, { - name: "3 tags all match", - comments: []string{"+tag3", "+tag1", "+tag2=value"}, - tags: []string{"+tag1", "+tag2", "+tag3"}, - wantMsg: `conflicting tags: {\+tag1, \+tag2, \+tag3}`, + name: "ifEnabled context fails alpha tag", + comments: []string{"+k8s:ifEnabled(SomeFeature)=+k8s:validateTrueAlpha"}, // Alpha tag in ifEnabled + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Beta validation`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - msg, err := conflictingTagsRule("test", tt.tags...)(tt.comments) + dummyType := &types.Type{Name: types.Name{Package: tt.pkg, Name: "Dummy"}} + rule := validationStability() + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + msg, err := rule(nil, dummyType, tags) if err != nil { t.Errorf("unexpected error: %v", err) - } else if tt.wantMsg != "" { - re := regexp.MustCompile(tt.wantMsg) - if !re.MatchString(msg) { - t.Errorf("message:\n\t%s\ndoes not match:\n\t%s", msg, re.String()) - } + } else if msg != tt.wantMsg { + t.Errorf("got %q, want %q", msg, tt.wantMsg) } }) } @@ -439,3 +427,354 @@ func TestLintType(t *testing.T) { }) } } + +func TestHasAnyValidationTag(t *testing.T) { + tests := []struct { + name string + comments []string + want bool + }{ + { + name: "empty", + comments: []string{}, + want: false, + }, + { + name: "no k8s tags", + comments: []string{"just a comment"}, + want: false, + }, + { + name: "optional only", + comments: []string{"+k8s:optional"}, + want: false, + }, + { + name: "required only", + comments: []string{"+k8s:required"}, + want: true, + }, + { + name: "forbidden only", + comments: []string{"+k8s:forbidden"}, + want: true, + }, + { + name: "unrecognized k8s tag", + comments: []string{"+k8s:openapi-gen=true"}, + want: false, + }, + { + name: "minimum tag", + comments: []string{"+k8s:minimum=0"}, + want: true, + }, + { + name: "enum tag", + comments: []string{"+k8s:enum"}, + want: true, + }, + { + name: "mixed with optional", + comments: []string{"+k8s:optional", "+k8s:minimum=0"}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + if got := hasAnyValidationTag(tags); got != tt.want { + t.Errorf("hasAnyValidationTag() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestHasRequirednessTag(t *testing.T) { + tests := []struct { + name string + comments []string + want bool + }{ + { + name: "empty", + comments: []string{}, + want: false, + }, + { + name: "no requireness", + comments: []string{"+k8s:minimum=0"}, + want: false, + }, + { + name: "optional", + comments: []string{"+k8s:optional"}, + want: true, + }, + { + name: "required", + comments: []string{"+k8s:required"}, + want: true, + }, + { + name: "optional with value", + comments: []string{"+k8s:optional=true"}, + want: true, + }, + { + name: "conditional optional", + comments: []string{`+k8s:alpha(since:"1.35")=+k8s:optional`}, + want: true, + }, + { + name: "conditional required", + comments: []string{`+k8s:alpha(since:"1.35")=+k8s:required`}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + if got := hasRequirednessTag(tags); got != tt.want { + t.Errorf("hasRequirednessTag() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLintRequiredness(t *testing.T) { + tests := []struct { + name string + typeToLint *types.Type + wantError string + }{ + { + name: "pointer field without validation - no error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Foo", + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Bar"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "", Name: "string"}}, + }, + }}, + }, + wantError: "", + }, + { + name: "pointer field with direct validation, no requireness - error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Foo", + CommentLines: []string{"+k8s:minimum=0"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Bar"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "", Name: "int"}}, + }, + }}, + }, + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with transitive validation, no requireness - error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Foo", + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Nested"}, + Kind: types.Pointer, + Elem: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Inner"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Bar", + CommentLines: []string{"+k8s:minimum=0"}, + Type: &types.Type{Name: types.Name{Package: "", Name: "int"}}, + }}, + }, + }, + }}, + }, + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with validation and +k8s:optional - no error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Foo", + CommentLines: []string{"+k8s:optional", "+k8s:minimum=0"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Bar"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "", Name: "int"}}, + }, + }}, + }, + wantError: "", + }, + { + name: "slice field with validation, no requireness - error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Items", + CommentLines: []string{"+k8s:maxItems=10"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "List"}, + Kind: types.Slice, + Elem: &types.Type{Name: types.Name{Package: "", Name: "string"}}, + }, + }}, + }, + wantError: "field Items: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "map field with validation, no requireness - error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Data", + CommentLines: []string{"+k8s:maxItems=5"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "M"}, + Kind: types.Map, + Key: &types.Type{Name: types.Name{Package: "", Name: "string"}}, + Elem: &types.Type{Name: types.Name{Package: "", Name: "string"}}, + }, + }}, + }, + wantError: "field Data: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "non-pointer struct field with validation - no error (exempt)", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Nested", + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Inner"}, + Kind: types.Struct, + CommentLines: []string{"+k8s:minimum=0"}, + }, + }}, + }, + wantError: "", + }, + { + name: "recursive type with pointer to self - no infinite loop", + typeToLint: func() *types.Type { + t := &types.Type{ + Name: types.Name{Package: "pkg", Name: "Node"}, + Kind: types.Struct, + } + t.Members = []types.Member{{ + Name: "Next", + CommentLines: []string{"+k8s:optional"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "NodePtr"}, + Kind: types.Pointer, + Elem: t, // cycle + }, + }} + return t + }(), + wantError: "", + }, + { + name: "recursive type with pointer to self - no infinite loop, missing required validation", + typeToLint: func() *types.Type { + t := &types.Type{ + Name: types.Name{Package: "pkg", Name: "Node"}, + Kind: types.Struct, + } + t.Members = []types.Member{{ + Name: "Next", + CommentLines: []string{"+k8s:immutable"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "NodePtr"}, + Kind: types.Pointer, + Elem: t, // cycle + }, + }} + return t + }(), + wantError: "field Next: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "recursive type with validation - detects validation on first visit", + typeToLint: func() *types.Type { + t := &types.Type{ + Name: types.Name{Package: "pkg", Name: "Node"}, + Kind: types.Struct, + CommentLines: []string{"+k8s:immutable"}, + } + t.Members = []types.Member{{ + Name: "Next", + CommentLines: []string{"+k8s:optional"}, + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "NodePtr"}, + Kind: types.Pointer, + Elem: t, // cycle + }, + }} + return t + }(), + wantError: "", + }, + { + name: "array field with transitive validation, no requiredNess - error", + typeToLint: &types.Type{ + Name: types.Name{Package: "pkg", Name: "T"}, + Kind: types.Struct, + Members: []types.Member{{ + Name: "Arr", + Type: &types.Type{ + Name: types.Name{Package: "pkg", Name: "ArrType"}, + Kind: types.Array, + Elem: &types.Type{ + Name: types.Name{Package: "pkg", Name: "Inner"}, + Kind: types.Struct, + CommentLines: []string{"+k8s:enum"}, + }, + }, + }}, + }, + wantError: "field Arr: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := newLinter(requiredAndOptional(validator)) + if err := l.lintType(tt.typeToLint); err != nil { + t.Fatalf("lintType() unexpected error: %v", err) + } + errs := l.lintErrors[tt.typeToLint] + if len(errs) > 1 { + t.Fatalf("got %d errors, but expected 0 or 1 error: %v", len(errs), errs) + } + var gotError string + if len(errs) == 1 { + gotError = errs[0].Error() + } + if gotError != tt.wantError { + t.Errorf("lintRequiredness() error = %q, want %q", gotError, tt.wantError) + } + }) + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/main.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/main.go index e86ada1687960..c1eb4060a87eb 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/main.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/main.go @@ -78,7 +78,6 @@ type Args struct { ReadOnlyPkgs []string // Always consider these as last-ditch possibilities for validations. GoHeaderFile string PrintDocs bool - LintOnly bool } // AddFlags add the generator flags to the flag set. @@ -91,8 +90,6 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) { "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") fs.BoolVar(&args.PrintDocs, "docs", false, "print documentation for supported declarative validations, and then exit") - fs.BoolVar(&args.LintOnly, "lint", false, - "only run linting checks, do not generate code") } // Validate checks the given arguments. diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go index c76d2767b9552..6e0cb7b4b5751 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go @@ -20,6 +20,7 @@ limitations under the License. // are not part of the gengo args. Even though this package purports to have // validations, it is outside of the args used when generating output_tests, // and so the generated could should NOT descend into these. +// +k8s:validation-gen-nolint package other // +k8s:validateFalse="you should not see this outside of this pkg" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go index 561f1e31e4062..13a64d2919306 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go @@ -20,6 +20,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package trivial import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go index a7ba69bfae791..bb1ffb53b9cf2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go @@ -20,6 +20,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package withfieldvalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go index 48a03379eef03..68b6ad8cdcbef 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go @@ -20,6 +20,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package withtypevalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go index 82482784f49b1..e752255272baa 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package cohorts import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go index 5a1a86d9ea9b9..a369311265877 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go @@ -21,6 +21,7 @@ limitations under the License. //nolint:unused // This is a test package. +// +k8s:validation-gen-nolint package crosspkg import ( diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go index 132e34e3e54d1..906c6983f861d 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package elidenovalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go index a99589c59c046..a9d8bd31021b4 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package embedded import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go index 5c91c5cd4d767..143cf682ceaa5 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package keys import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go index 6acab36858e6e..973869ef139e3 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package mapofprimitive import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go index 6195287bbca8c..1beea6388fe3f 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package mapofstruct import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go index 58607236153b5..b9e0aecf4f55a 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package multiplevalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go index aca6e84c8b2ab..81361d99b45dd 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package typedeftomap import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go index a3755ceabce1a..f174e006e10f1 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package multipletags import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go index 81780898b7f48..a7063c8b0c656 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go @@ -18,6 +18,8 @@ limitations under the License. // Package nogeneration is a test package. // +// +k8s:validation-gen-nolint +// //nolint:unused package nogeneration diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go index d3858b94c955b..d952dcf9845f7 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go @@ -20,6 +20,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package notypesmatch import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go index 90428435fb0c0..2a7cf5115cab7 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package trivial import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go index f2dc459fc253c..9d56e3b4e2d30 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package withfieldvalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go index 35403c5ebea2d..5308eebac267c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package withtypevalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go index 293af6aab3b15..621c8ebbc33e3 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package structs import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go index 0178edb2b9554..84186fd7d500d 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package typedefs import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go index 8a4db7c319828..0d54ef29ef62c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package pointers import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go index 3582b6b8cc2de..1ccd102aa4eac 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package primitives import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go index d3cdefec413ee..73e0b93122e1b 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go @@ -20,7 +20,9 @@ limitations under the License. // Package publicprivate is a test package. // -//nolint:unused,govet // govet disables structtag check, which checks for use of tags on private fields +// +k8s:validation-gen-nolint +// +//nolint:unused,govet,staticcheck // govet disables structtag check, which checks for use of tags on private fields; staticcheck calls out SA5008: unexported struct field cannot have non-ignored `json:"private"` package publicprivate import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go index f20a4b553e5f6..f1adad28d99d2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package defaultbehavior import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go index edd0dcca9fee8..214aed1d34c57 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package list import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go index 281d20e0ba0f0..214d854b3452c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package eachkey import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go index 12aa6825ead84..d411463507745 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package eachval import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go index 4c909a4ed9044..085742ab05396 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package subfield import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go index f437167d1403a..50ae7065075ea 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package maps import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go index a3ad82823e720..a05d714336e49 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package pointers import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go index d00b793527091..cb057178e2b75 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package slices import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go index 9bdb43c16278b..bdd76784c71c9 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package multiplevalidations import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go index 600fd8be15370..7222f5fb3c21d 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package sliceofprimitive import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go index efc4cb1b961fc..812b8fac3f0d6 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package sliceofstruct import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go index b1156af06d057..ed12748919341 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package typedeftoslice import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/doc.go new file mode 100644 index 0000000000000..f5b466282eedf --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/doc.go @@ -0,0 +1,121 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package discriminator + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type StrictUnion struct { + TypeMeta int + + // +k8s:discriminator + D1 string `json:"d1"` + + // +k8s:member("A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:member("B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type SharedField struct { + TypeMeta int + + // +k8s:discriminator + D1 string `json:"d1"` + + // Valid in A and B, implicitly forbidden in C. + // +k8s:member("A")=+k8s:optional + // +k8s:member("B")=+k8s:optional + FieldA *string `json:"fieldA,omitempty"` +} + +type ChainedValidation struct { + TypeMeta int + + // +k8s:discriminator + D1 string `json:"d1"` + + // In mode A, it is required AND must have maxLength 5. + // +k8s:member("A")=+k8s:required + // +k8s:member("A")=+k8s:maxLength=5 + FieldA *string `json:"fieldA,omitempty"` +} + +type ImplicitForbidden struct { + TypeMeta int + + // +k8s:discriminator + D1 string `json:"d1"` + + // Field is only mentioned for mode A. Mode B should implicitly forbid it. + // +k8s:member("A")=+k8s:optional + FieldA *string `json:"fieldA,omitempty"` +} + +type NonStringDiscriminator struct { + TypeMeta int + + // +k8s:discriminator(name:"Bool") + D1 bool `json:"d1"` + + // +k8s:member(discriminator:"Bool", value:"true")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:discriminator(name:"Int") + D2 int `json:"d2"` + + // +k8s:member(discriminator:"Int", value:"1")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type MultipleDiscriminators struct { + TypeMeta int + + // +k8s:discriminator(name:"D1") + D1 string `json:"d1"` + + // +k8s:discriminator(name:"D2") + D2 string `json:"d2"` + + // +k8s:member(discriminator:"D1", value:"A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:member(discriminator:"D2", value:"B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type Collections struct { + TypeMeta int + + // +k8s:discriminator + D1 string `json:"d1"` + + // +k8s:member("A")=+k8s:optional + ListField []string `json:"listField,omitempty"` + + // +k8s:member("A")=+k8s:optional + MapField map[string]string `json:"mapField,omitempty"` +} + +type TypeMeta int diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/doc_test.go new file mode 100644 index 0000000000000..ff66c24ed47e3 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/doc_test.go @@ -0,0 +1,204 @@ +/* +Copyright 2025 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 discriminator + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestStrictUnion(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: FieldA required, FieldB implicitly forbidden + st.Value(&StrictUnion{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + st.Value(&StrictUnion{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + }) + st.Value(&StrictUnion{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), ""), + }) + + // Mode B: FieldA implicitly forbidden, FieldB required + st.Value(&StrictUnion{D1: "B", FieldB: ptr.To("val")}).ExpectValid() + st.Value(&StrictUnion{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), ""), + }) + st.Value(&StrictUnion{D1: "B", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestSharedField(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid (optional) in A and B + st.Value(&SharedField{D1: "A"}).ExpectValid() + st.Value(&SharedField{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + st.Value(&SharedField{D1: "B"}).ExpectValid() + st.Value(&SharedField{D1: "B", FieldA: ptr.To("val")}).ExpectValid() + + // Forbidden in C + st.Value(&SharedField{D1: "C", FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestChainedValidation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: Required AND maxLength 5 + st.Value(&ChainedValidation{D1: "A", FieldA: ptr.To("abc")}).ExpectValid() + st.Value(&ChainedValidation{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + }) + st.Value(&ChainedValidation{D1: "A", FieldA: ptr.To("too-long")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long", 5), + }) + + // Mode B: Unlisted, so implicitly forbidden + st.Value(&ChainedValidation{D1: "B", FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestImplicitForbidden(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: Optional + st.Value(&ImplicitForbidden{D1: "A"}).ExpectValid() + st.Value(&ImplicitForbidden{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + + // Mode B: Not listed, so implicitly Forbidden + st.Value(&ImplicitForbidden{D1: "B", FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestNonStringDiscriminator(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Bool mode + st.Value(&NonStringDiscriminator{D1: true, FieldA: ptr.To("val")}).ExpectValid() + st.Value(&NonStringDiscriminator{D1: true}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + }) + st.Value(&NonStringDiscriminator{D1: false, FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) + + // Int mode + st.Value(&NonStringDiscriminator{D2: 1, FieldB: ptr.To("val")}).ExpectValid() + st.Value(&NonStringDiscriminator{D2: 1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), ""), + }) + st.Value(&NonStringDiscriminator{D2: 2, FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), ""), + }) +} + +func TestMultipleDiscriminators(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&MultipleDiscriminators{ + D1: "A", + D2: "B", + FieldA: ptr.To("valA"), + FieldB: ptr.To("valB"), + }).ExpectValid() + + st.Value(&MultipleDiscriminators{ + D1: "A", + D2: "B", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + field.Required(field.NewPath("fieldB"), ""), + }) +} + +func TestCollections(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: Collections are valid (optional) + st.Value(&Collections{ + D1: "A", + }).ExpectValid() + + st.Value(&Collections{ + D1: "A", + ListField: []string{"item"}, + MapField: map[string]string{"key": "val"}, + }).ExpectValid() + + // Mode B: Unlisted, so implicitly forbidden + st.Value(&Collections{ + D1: "B", + ListField: []string{"item"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("listField"), ""), + }) + + st.Value(&Collections{ + D1: "B", + MapField: map[string]string{"key": "val"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("mapField"), ""), + }) +} + +func TestRatcheting(t *testing.T) { + mkTest := func() *ChainedValidation { + return &ChainedValidation{ + D1: "A", + FieldA: ptr.To("too-long-string"), + } + } + + st := localSchemeBuilder.Test(t) + + // 1. New object is invalid + st.Value(mkTest()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long-string", 5), + }) + + // 2. Unchanged update is valid (ratcheting) + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() + + // 3. Changed value re-validates (and fails) + mkDifferent := func() *ChainedValidation { + return &ChainedValidation{ + D1: "A", + FieldA: ptr.To("also-too-long"), + } + } + st.Value(mkTest()).OldValue(mkDifferent()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long-string", 5), + }) + + // 4. Changed discriminator re-validates (and fails) + mkDifferentDisc := func() *ChainedValidation { + return &ChainedValidation{ + D1: "B", // Discriminator changed from B -> A + FieldA: ptr.To("too-long-string"), + } + } + st.Value(mkTest()).OldValue(mkDifferentDisc()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long-string", 5), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/zz_generated.validations.go new file mode 100644 index 0000000000000..30890c73d47d9 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/discriminator/zz_generated.validations.go @@ -0,0 +1,391 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package discriminator + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ChainedValidation + scheme.AddValidationFunc((*ChainedValidation)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ChainedValidation(ctx, op, nil /* fldPath */, obj.(*ChainedValidation), safe.Cast[*ChainedValidation](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type Collections + scheme.AddValidationFunc((*Collections)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Collections(ctx, op, nil /* fldPath */, obj.(*Collections), safe.Cast[*Collections](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type ImplicitForbidden + scheme.AddValidationFunc((*ImplicitForbidden)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ImplicitForbidden(ctx, op, nil /* fldPath */, obj.(*ImplicitForbidden), safe.Cast[*ImplicitForbidden](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type MultipleDiscriminators + scheme.AddValidationFunc((*MultipleDiscriminators)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MultipleDiscriminators(ctx, op, nil /* fldPath */, obj.(*MultipleDiscriminators), safe.Cast[*MultipleDiscriminators](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type NonStringDiscriminator + scheme.AddValidationFunc((*NonStringDiscriminator)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_NonStringDiscriminator(ctx, op, nil /* fldPath */, obj.(*NonStringDiscriminator), safe.Cast[*NonStringDiscriminator](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type SharedField + scheme.AddValidationFunc((*SharedField)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SharedField(ctx, op, nil /* fldPath */, obj.(*SharedField), safe.Cast[*SharedField](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type StrictUnion + scheme.AddValidationFunc((*StrictUnion)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StrictUnion(ctx, op, nil /* fldPath */, obj.(*StrictUnion), safe.Cast[*StrictUnion](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ChainedValidation validates an instance of ChainedValidation according +// to declarative validation rules in the API schema. +func Validate_ChainedValidation(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ChainedValidation) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *ChainedValidation) *string { return obj.FieldA }, func(obj *ChainedValidation) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5)...) + return errs + }}, + })...) + + // field ChainedValidation.TypeMeta has no validation + // field ChainedValidation.D1 has no validation + // field ChainedValidation.FieldA has no validation + return errs +} + +// Validate_Collections validates an instance of Collections according +// to declarative validation rules in the API schema. +func Validate_Collections(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Collections) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "listField", func(obj *Collections) []string { return obj.ListField }, func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[[]string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "mapField", func(obj *Collections) map[string]string { return obj.MapField }, func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[map[string]string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field Collections.TypeMeta has no validation + // field Collections.D1 has no validation + // field Collections.ListField has no validation + // field Collections.MapField has no validation + return errs +} + +// Validate_ImplicitForbidden validates an instance of ImplicitForbidden according +// to declarative validation rules in the API schema. +func Validate_ImplicitForbidden(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ImplicitForbidden) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *ImplicitForbidden) *string { return obj.FieldA }, func(obj *ImplicitForbidden) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field ImplicitForbidden.TypeMeta has no validation + // field ImplicitForbidden.D1 has no validation + // field ImplicitForbidden.FieldA has no validation + return errs +} + +// Validate_MultipleDiscriminators validates an instance of MultipleDiscriminators according +// to declarative validation rules in the API schema. +func Validate_MultipleDiscriminators(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MultipleDiscriminators) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *MultipleDiscriminators) *string { return obj.FieldA }, func(obj *MultipleDiscriminators) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *MultipleDiscriminators) *string { return obj.FieldB }, func(obj *MultipleDiscriminators) string { return obj.D2 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field MultipleDiscriminators.TypeMeta has no validation + // field MultipleDiscriminators.D1 has no validation + // field MultipleDiscriminators.D2 has no validation + // field MultipleDiscriminators.FieldA has no validation + // field MultipleDiscriminators.FieldB has no validation + return errs +} + +// Validate_NonStringDiscriminator validates an instance of NonStringDiscriminator according +// to declarative validation rules in the API schema. +func Validate_NonStringDiscriminator(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonStringDiscriminator) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *NonStringDiscriminator) *string { return obj.FieldA }, func(obj *NonStringDiscriminator) bool { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, bool]{ + { + Value: true, Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *NonStringDiscriminator) *string { return obj.FieldB }, func(obj *NonStringDiscriminator) int { return obj.D2 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, int]{ + { + Value: 1, Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field NonStringDiscriminator.TypeMeta has no validation + // field NonStringDiscriminator.D1 has no validation + // field NonStringDiscriminator.FieldA has no validation + // field NonStringDiscriminator.D2 has no validation + // field NonStringDiscriminator.FieldB has no validation + return errs +} + +// Validate_SharedField validates an instance of SharedField according +// to declarative validation rules in the API schema. +func Validate_SharedField(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SharedField) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *SharedField) *string { return obj.FieldA }, func(obj *SharedField) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field SharedField.TypeMeta has no validation + // field SharedField.D1 has no validation + // field SharedField.FieldA has no validation + return errs +} + +// Validate_StrictUnion validates an instance of StrictUnion according +// to declarative validation rules in the API schema. +func Validate_StrictUnion(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StrictUnion) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *StrictUnion) *string { return obj.FieldA }, func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *StrictUnion) *string { return obj.FieldB }, func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field StrictUnion.TypeMeta has no validation + // field StrictUnion.D1 has no validation + // field StrictUnion.FieldA has no validation + // field StrictUnion.FieldB has no validation + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go index 4709e3a7ff8d5..d236ab53c4122 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package eachkey import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go index 72c41a9c87a1d..a3c4edcef007d 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package mapofprimitive import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go index 67baf8fc1951c..778a58520c5aa 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package mapofstruct import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go index 6eb5d95d35dcf..440fbcd33202f 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package sliceofprimitive import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go index 9c4ed6b532441..5d18251c3b719 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package sliceofstruct import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go index 97531ab37e8b3..75707ca8839dc 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package typedeftomap import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go index bdd75d45e4615..bcb8a8454249e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package typedeftoslice import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go index 73393529dbb76..353f7b4870fde 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package enum import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go index 787fd1f9b5da2..b0f4a710aa119 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package options import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc.go new file mode 100644 index 0000000000000..05e0b1fe1d777 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc.go @@ -0,0 +1,91 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package forbidden + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:forbidden + StringField string `json:"stringField"` + + // +k8s:forbidden + StringPtrField *string `json:"stringPtrField"` + + // +k8s:forbidden + StringTypedefField StringType `json:"stringTypedefField"` + + // +k8s:forbidden + StringTypedefPtrField *StringType `json:"stringTypedefPtrField"` + + // +k8s:forbidden + IntField int `json:"intField"` + + // +k8s:forbidden + IntPtrField *int `json:"intPtrField"` + + // +k8s:forbidden + IntTypedefField IntType `json:"intTypedefField"` + + // +k8s:forbidden + IntTypedefPtrField *IntType `json:"intTypedefPtrField"` + + // +k8s:forbidden + BoolField bool `json:"boolField"` + + // +k8s:forbidden + FloatField float64 `json:"floatField"` + + // +k8s:forbidden + ByteField byte `json:"byteField"` + + // +k8s:forbidden + OtherStructPtrField *OtherStruct `json:"otherStructPtrField"` + + // +k8s:forbidden + SliceField []string `json:"sliceField"` + + // +k8s:forbidden + SliceTypedefField SliceType `json:"sliceTypedefField"` + + // +k8s:forbidden + ByteArrayField []byte `json:"byteArrayField"` + + // +k8s:forbidden + MapField map[string]string `json:"mapField"` + + // +k8s:forbidden + MapTypedefField MapType `json:"mapTypedefField"` +} + +type StringType string + +type IntType int + +type OtherStruct struct{} + +type SliceType []string + +type MapType map[string]string diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc_test.go new file mode 100644 index 0000000000000..dba35b586555a --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc_test.go @@ -0,0 +1,91 @@ +/* +Copyright 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 forbidden + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values (nil slices/maps). + }).ExpectValid() + + st.Value(&Struct{ + // Explicit zero-values and empty slices/maps. + StringField: "", + StringPtrField: nil, + StringTypedefField: "", + StringTypedefPtrField: nil, + IntField: 0, + IntPtrField: nil, + IntTypedefField: 0, + IntTypedefPtrField: nil, + BoolField: false, + FloatField: 0.0, + ByteField: 0, + OtherStructPtrField: nil, + SliceField: []string{}, + SliceTypedefField: SliceType{}, + ByteArrayField: []byte{}, + MapField: map[string]string{}, + MapTypedefField: MapType{}, + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To("xyz"), + StringTypedefField: StringType("abc"), + StringTypedefPtrField: ptr.To(StringType("xyz")), + IntField: 123, + IntPtrField: ptr.To(456), + IntTypedefField: IntType(123), + IntTypedefPtrField: ptr.To(IntType(456)), + BoolField: true, + FloatField: 1.23, + ByteField: 'a', + OtherStructPtrField: &OtherStruct{}, + SliceField: []string{"a", "b"}, + SliceTypedefField: SliceType([]string{"a", "b"}), + ByteArrayField: []byte("abc"), + MapField: map[string]string{"a": "b", "c": "d"}, + MapTypedefField: MapType(map[string]string{"a": "b", "c": "d"}), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("stringField"), ""), + field.Forbidden(field.NewPath("stringPtrField"), ""), + field.Forbidden(field.NewPath("stringTypedefField"), ""), + field.Forbidden(field.NewPath("stringTypedefPtrField"), ""), + field.Forbidden(field.NewPath("intField"), ""), + field.Forbidden(field.NewPath("intPtrField"), ""), + field.Forbidden(field.NewPath("intTypedefField"), ""), + field.Forbidden(field.NewPath("intTypedefPtrField"), ""), + field.Forbidden(field.NewPath("boolField"), ""), + field.Forbidden(field.NewPath("floatField"), ""), + field.Forbidden(field.NewPath("byteField"), ""), + field.Forbidden(field.NewPath("otherStructPtrField"), ""), + field.Forbidden(field.NewPath("sliceField"), ""), + field.Forbidden(field.NewPath("sliceTypedefField"), ""), + field.Forbidden(field.NewPath("byteArrayField"), ""), + field.Forbidden(field.NewPath("mapField"), ""), + field.Forbidden(field.NewPath("mapTypedefField"), ""), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/zz_generated.validations.go new file mode 100644 index 0000000000000..788c54a9e251e --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/zz_generated.validations.go @@ -0,0 +1,432 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package forbidden + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.StringField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }), oldObj != nil)...) + + // field Struct.StringPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("stringPtrField"), obj.StringPtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.StringPtrField }), oldObj != nil)...) + + // field Struct.StringTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *StringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("stringTypedefField"), &obj.StringTypedefField, safe.Field(oldObj, func(oldObj *Struct) *StringType { return &oldObj.StringTypedefField }), oldObj != nil)...) + + // field Struct.StringTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *StringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("stringTypedefPtrField"), obj.StringTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *StringType { return oldObj.StringTypedefPtrField }), oldObj != nil)...) + + // field Struct.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }), oldObj != nil)...) + + // field Struct.IntPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("intPtrField"), obj.IntPtrField, safe.Field(oldObj, func(oldObj *Struct) *int { return oldObj.IntPtrField }), oldObj != nil)...) + + // field Struct.IntTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("intTypedefField"), &obj.IntTypedefField, safe.Field(oldObj, func(oldObj *Struct) *IntType { return &oldObj.IntTypedefField }), oldObj != nil)...) + + // field Struct.IntTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("intTypedefPtrField"), obj.IntTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *IntType { return oldObj.IntTypedefPtrField }), oldObj != nil)...) + + // field Struct.BoolField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("boolField"), &obj.BoolField, safe.Field(oldObj, func(oldObj *Struct) *bool { return &oldObj.BoolField }), oldObj != nil)...) + + // field Struct.FloatField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *float64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("floatField"), &obj.FloatField, safe.Field(oldObj, func(oldObj *Struct) *float64 { return &oldObj.FloatField }), oldObj != nil)...) + + // field Struct.ByteField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *byte, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("byteField"), &obj.ByteField, safe.Field(oldObj, func(oldObj *Struct) *byte { return &oldObj.ByteField }), oldObj != nil)...) + + // field Struct.OtherStructPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *OtherStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("otherStructPtrField"), obj.OtherStructPtrField, safe.Field(oldObj, func(oldObj *Struct) *OtherStruct { return oldObj.OtherStructPtrField }), oldObj != nil)...) + + // field Struct.SliceField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("sliceField"), obj.SliceField, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceField }), oldObj != nil)...) + + // field Struct.SliceTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj SliceType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("sliceTypedefField"), obj.SliceTypedefField, safe.Field(oldObj, func(oldObj *Struct) SliceType { return oldObj.SliceTypedefField }), oldObj != nil)...) + + // field Struct.ByteArrayField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []byte, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("byteArrayField"), obj.ByteArrayField, safe.Field(oldObj, func(oldObj *Struct) []byte { return oldObj.ByteArrayField }), oldObj != nil)...) + + // field Struct.MapField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("mapField"), obj.MapField, safe.Field(oldObj, func(oldObj *Struct) map[string]string { return oldObj.MapField }), oldObj != nil)...) + + // field Struct.MapTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj MapType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("mapTypedefField"), obj.MapTypedefField, safe.Field(oldObj, func(oldObj *Struct) MapType { return oldObj.MapTypedefField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go index 4858b14633276..9233a826cd6c9 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package k8sextendedresourcename import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go index ccb70855816e8..2b7ab3004803b 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go @@ -19,6 +19,7 @@ limitations under the License. // Package format is the internal version of the API. // +k8s:validation:internal +// +k8s:validation-gen-nolint package format import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go index f8f00740df949..dad2ff115659f 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package format import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go index 35632db1ca7c0..4967bc3bbf13e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package format import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go index 25c625d3a778f..0033d5c3e9761 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package format import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go index 6bd2fe8226737..0faf3b1d9c7e2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package fullyqualifiedname import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go index 5da57980a4b12..c56d95289f053 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package format import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go index e5084ac223e9d..7e1592e0a5aa0 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package format import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go index 8207656918705..5b4eebf14b0ec 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package k8suuid import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go index c9df2d2437dba..3983d6445bbdf 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package immutable import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go index e9bfb62b13522..550de942e7d42 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package transitions import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go index 5d3c40fefdfaa..01f356f928833 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package multiplekeys import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go index d636653da1992..86b059f755a6c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package singlekey import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go index b62fcbe6e81f4..98d208464b647 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package subfield import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go index bc49214121b04..4318276d5e01c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package typedef import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go index efc0e477112be..cba9a5590257b 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package unionsimple import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go index 2be86a4813128..fa10be5e25a88 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package uniontypedef import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go index f636a785f32bf..2f9428348a631 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package zeroroneofsimple import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go index 19e40b895acdf..5d72d18cececd 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package zeroroneooftypedef import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc.go new file mode 100644 index 0000000000000..c14e55a9ebc88 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc.go @@ -0,0 +1,54 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package atomicslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type AtomicSliceStruct struct { + TypeMeta int + + // Case: Standard eachVal + // +k8s:listType=atomic + // +k8s:eachVal=+k8s:minimum=10 + Standard []int `json:"standard"` + + // Case: Alpha eachVal + // +k8s:listType=atomic + // +k8s:alpha=+k8s:eachVal=+k8s:minimum=10 + Alpha []int `json:"Alpha"` + + // Case: Beta eachVal + // +k8s:listType=atomic + // +k8s:beta=+k8s:eachVal=+k8s:minimum=10 + Beta []int `json:"Beta"` + + // Case: Standard eachVal, Alpha validation + // +k8s:listType=atomic + // +k8s:eachVal=+k8s:alpha=+k8s:minimum=10 + AlphaValidation []int `json:"AlphaValidation"` + + // Case: Standard eachVal, Beta validation + // +k8s:listType=atomic + // +k8s:eachVal=+k8s:beta=+k8s:minimum=10 + BetaValidation []int `json:"BetaValidation"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc_test.go new file mode 100644 index 0000000000000..3193ec10cad43 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2025 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 atomicslice + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAtomicSlice(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&AtomicSliceStruct{ + Standard: []int{5}, + Alpha: []int{5}, + Beta: []int{5}, + AlphaValidation: []int{5}, + BetaValidation: []int{5}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case: Standard -> Normal Error + field.Invalid(field.NewPath("standard").Index(0), 5, "").WithOrigin("minimum"), + + // Case: Alpha eachVal -> Alpha Error + field.Invalid(field.NewPath("Alpha").Index(0), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case: Beta eachVal -> Beta Error + field.Invalid(field.NewPath("Beta").Index(0), 5, "").WithOrigin("minimum").MarkBeta(), + + // Case: Standard eachVal, Alpha validation -> Alpha Error + field.Invalid(field.NewPath("AlphaValidation").Index(0), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case: Standard eachVal, Beta validation -> Beta Error + field.Invalid(field.NewPath("BetaValidation").Index(0), 5, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/zz_generated.validations.go new file mode 100644 index 0000000000000..b9590a417f2be --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/zz_generated.validations.go @@ -0,0 +1,128 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package atomicslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type AtomicSliceStruct + scheme.AddValidationFunc((*AtomicSliceStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_AtomicSliceStruct(ctx, op, nil /* fldPath */, obj.(*AtomicSliceStruct), safe.Cast[*AtomicSliceStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_AtomicSliceStruct validates an instance of AtomicSliceStruct according +// to declarative validation rules in the API schema. +func Validate_AtomicSliceStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *AtomicSliceStruct) (errs field.ErrorList) { + // field AtomicSliceStruct.TypeMeta has no validation + + // field AtomicSliceStruct.Standard + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + })...) + return + }(fldPath.Child("standard"), obj.Standard, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.Standard }), oldObj != nil)...) + + // field AtomicSliceStruct.Alpha + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkAlpha()...) + return + }(fldPath.Child("Alpha"), obj.Alpha, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.Alpha }), oldObj != nil)...) + + // field AtomicSliceStruct.Beta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkBeta()...) + return + }(fldPath.Child("Beta"), obj.Beta, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.Beta }), oldObj != nil)...) + + // field AtomicSliceStruct.AlphaValidation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + })...) + return + }(fldPath.Child("AlphaValidation"), obj.AlphaValidation, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.AlphaValidation }), oldObj != nil)...) + + // field AtomicSliceStruct.BetaValidation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + })...) + return + }(fldPath.Child("BetaValidation"), obj.BetaValidation, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.BetaValidation }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/doc.go new file mode 100644 index 0000000000000..0f509d4c1d4e2 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/doc.go @@ -0,0 +1,103 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package discriminators + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type AlphaStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:discriminator + D1 string `json:"d1"` + + // +k8s:alpha=+k8s:member("A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:alpha=+k8s:member("B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type BetaStruct struct { + TypeMeta int + + // +k8s:beta=+k8s:discriminator + D1 string `json:"d1"` + + // +k8s:beta=+k8s:member("A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:beta=+k8s:member("B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type MixedLevels struct { + TypeMeta int + + // +k8s:discriminator + Mode string `json:"mode"` + + // +k8s:alpha=+k8s:member("A")=+k8s:required + A *string `json:"a,omitempty"` + + // +k8s:beta=+k8s:member("B")=+k8s:required + B *string `json:"b,omitempty"` +} + +type CrossLevels struct { + TypeMeta int + + // +k8s:beta=+k8s:discriminator + Kind string `json:"kind"` + + // +k8s:alpha=+k8s:member("A")=+k8s:required + A *string `json:"a,omitempty"` + + // +k8s:alpha=+k8s:member("B")=+k8s:required + B *string `json:"b,omitempty"` +} + +type SameFieldMixed struct { + TypeMeta int + + // +k8s:discriminator + Mode string `json:"mode"` + + // +k8s:alpha=+k8s:member("A")=+k8s:required + // +k8s:beta=+k8s:member("B")=+k8s:required + Value *string `json:"value,omitempty"` +} + +// SameValueMixedPayloads tests that multiple payload validations on the same +// discriminator value can have different stability levels. +type SameValueMixedPayloads struct { + TypeMeta int + + // +k8s:discriminator + Mode string `json:"mode"` + + // +k8s:alpha=+k8s:member("A")=+k8s:required + // +k8s:beta=+k8s:member("A")=+k8s:minLength=3 + Value *string `json:"value,omitempty"` +} + +type TypeMeta int diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/doc_test.go new file mode 100644 index 0000000000000..f9509dfe6a2b0 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/doc_test.go @@ -0,0 +1,162 @@ +/* +Copyright 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 discriminators + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid: mode A with FieldA set + st.Value(&AlphaStruct{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + + // Invalid: mode A with FieldA missing (required), should be stability level alpha + st.Value(&AlphaStruct{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), "").MarkAlpha(), + }) + + // Invalid: mode A with FieldB set (forbidden), should be stability level alpha + st.Value(&AlphaStruct{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), "").MarkAlpha(), + }) + + // Valid: mode B with FieldB set + st.Value(&AlphaStruct{D1: "B", FieldB: ptr.To("val")}).ExpectValid() + + // Invalid: mode B with FieldB missing (required), should be stability level alpha + st.Value(&AlphaStruct{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), "").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid: mode A with FieldA set + st.Value(&BetaStruct{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + + // Invalid: mode A with FieldA missing (required), should be stability level beta + st.Value(&BetaStruct{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), "").MarkBeta(), + }) + + // Invalid: mode A with FieldB set (forbidden), should be stability level beta + st.Value(&BetaStruct{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), "").MarkBeta(), + }) + + // Valid: mode B with FieldB set + st.Value(&BetaStruct{D1: "B", FieldB: ptr.To("val")}).ExpectValid() + + // Invalid: mode B with FieldB missing (required), should be stability level beta + st.Value(&BetaStruct{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), "").MarkBeta(), + }) +} + +func TestMixedLevels(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid cases + st.Value(&MixedLevels{Mode: "A", A: ptr.To("val")}).ExpectValid() + st.Value(&MixedLevels{Mode: "B", B: ptr.To("val")}).ExpectValid() + + // Mode=A, missing A -> alpha error (field A is alpha-gated) + st.Value(&MixedLevels{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("a"), "").MarkAlpha(), + }) + + // Mode=B, missing B -> beta error (field B is beta-gated) + st.Value(&MixedLevels{Mode: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("b"), "").MarkBeta(), + }) + + // Mode=A with B set (forbidden) -> beta error (field B's +k8s:discriminator is beta) + st.Value(&MixedLevels{Mode: "A", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("b"), "").MarkBeta(), + }) + + // Mode=B with A set (forbidden) -> alpha error (field A's +k8s:discriminator is alpha) + st.Value(&MixedLevels{Mode: "B", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("a"), "").MarkAlpha(), + }) +} + +func TestCrossLevels(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid cases + st.Value(&CrossLevels{Kind: "A", A: ptr.To("val")}).ExpectValid() + st.Value(&CrossLevels{Kind: "B", B: ptr.To("val")}).ExpectValid() + + // Kind=A, missing A -> alpha error (field A is alpha-gated, discriminator is beta) + st.Value(&CrossLevels{Kind: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("a"), "").MarkAlpha(), + }) + + // Kind=B, missing B -> alpha error (field B is alpha-gated, discriminator is beta) + st.Value(&CrossLevels{Kind: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("b"), "").MarkAlpha(), + }) + + // Kind=A with B set (forbidden) -> alpha error (field B's member tag is alpha) + st.Value(&CrossLevels{Kind: "A", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("b"), "").MarkAlpha(), + }) +} + +func TestSameFieldMixed(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid cases + st.Value(&SameFieldMixed{Mode: "A", Value: ptr.To("val")}).ExpectValid() + st.Value(&SameFieldMixed{Mode: "B", Value: ptr.To("val")}).ExpectValid() + + // Mode=A, missing Value -> alpha error (member("A") is alpha-gated) + st.Value(&SameFieldMixed{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("value"), "").MarkAlpha(), + }) + + // Mode=B, missing Value -> beta error (member("B") is beta-gated) + st.Value(&SameFieldMixed{Mode: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("value"), "").MarkBeta(), + }) +} + +// TestSameValueMixedPayloads verifies that multiple payload validations on +// the same discriminator value can have different stability levels. +func TestSameValueMixedPayloads(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid: Mode=A with Value set and long enough + st.Value(&SameValueMixedPayloads{Mode: "A", Value: ptr.To("abc")}).ExpectValid() + + // Mode=A, missing Value -> alpha error (required is alpha-gated) + st.Value(&SameValueMixedPayloads{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("value"), "").MarkAlpha(), + }) + + // Mode=A, Value too short -> beta error (minLength is beta-gated) + st.Value(&SameValueMixedPayloads{Mode: "A", Value: ptr.To("ab")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.TooShort(field.NewPath("value"), "", 3).MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/zz_generated.validations.go new file mode 100644 index 0000000000000..0cd8b45295cd0 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/discriminators/zz_generated.validations.go @@ -0,0 +1,357 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package discriminators + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type AlphaStruct + scheme.AddValidationFunc((*AlphaStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_AlphaStruct(ctx, op, nil /* fldPath */, obj.(*AlphaStruct), safe.Cast[*AlphaStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type BetaStruct + scheme.AddValidationFunc((*BetaStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_BetaStruct(ctx, op, nil /* fldPath */, obj.(*BetaStruct), safe.Cast[*BetaStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type CrossLevels + scheme.AddValidationFunc((*CrossLevels)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_CrossLevels(ctx, op, nil /* fldPath */, obj.(*CrossLevels), safe.Cast[*CrossLevels](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type MixedLevels + scheme.AddValidationFunc((*MixedLevels)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MixedLevels(ctx, op, nil /* fldPath */, obj.(*MixedLevels), safe.Cast[*MixedLevels](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type SameFieldMixed + scheme.AddValidationFunc((*SameFieldMixed)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SameFieldMixed(ctx, op, nil /* fldPath */, obj.(*SameFieldMixed), safe.Cast[*SameFieldMixed](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type SameValueMixedPayloads + scheme.AddValidationFunc((*SameValueMixedPayloads)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SameValueMixedPayloads(ctx, op, nil /* fldPath */, obj.(*SameValueMixedPayloads), safe.Cast[*SameValueMixedPayloads](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_AlphaStruct validates an instance of AlphaStruct according +// to declarative validation rules in the API schema. +func Validate_AlphaStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *AlphaStruct) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *AlphaStruct) *string { return obj.FieldA }, func(obj *AlphaStruct) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *AlphaStruct) *string { return obj.FieldB }, func(obj *AlphaStruct) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field AlphaStruct.TypeMeta has no validation + // field AlphaStruct.D1 has no validation + // field AlphaStruct.FieldA has no validation + // field AlphaStruct.FieldB has no validation + return errs +} + +// Validate_BetaStruct validates an instance of BetaStruct according +// to declarative validation rules in the API schema. +func Validate_BetaStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BetaStruct) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *BetaStruct) *string { return obj.FieldA }, func(obj *BetaStruct) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *BetaStruct) *string { return obj.FieldB }, func(obj *BetaStruct) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field BetaStruct.TypeMeta has no validation + // field BetaStruct.D1 has no validation + // field BetaStruct.FieldA has no validation + // field BetaStruct.FieldB has no validation + return errs +} + +// Validate_CrossLevels validates an instance of CrossLevels according +// to declarative validation rules in the API schema. +func Validate_CrossLevels(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *CrossLevels) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "a", func(obj *CrossLevels) *string { return obj.A }, func(obj *CrossLevels) string { return obj.Kind }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "b", func(obj *CrossLevels) *string { return obj.B }, func(obj *CrossLevels) string { return obj.Kind }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field CrossLevels.TypeMeta has no validation + // field CrossLevels.Kind has no validation + // field CrossLevels.A has no validation + // field CrossLevels.B has no validation + return errs +} + +// Validate_MixedLevels validates an instance of MixedLevels according +// to declarative validation rules in the API schema. +func Validate_MixedLevels(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedLevels) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "a", func(obj *MixedLevels) *string { return obj.A }, func(obj *MixedLevels) string { return obj.Mode }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "b", func(obj *MixedLevels) *string { return obj.B }, func(obj *MixedLevels) string { return obj.Mode }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field MixedLevels.TypeMeta has no validation + // field MixedLevels.Mode has no validation + // field MixedLevels.A has no validation + // field MixedLevels.B has no validation + return errs +} + +// Validate_SameFieldMixed validates an instance of SameFieldMixed according +// to declarative validation rules in the API schema. +func Validate_SameFieldMixed(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SameFieldMixed) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "value", func(obj *SameFieldMixed) *string { return obj.Value }, func(obj *SameFieldMixed) string { return obj.Mode }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + { + Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }}, + })...) + + // field SameFieldMixed.TypeMeta has no validation + // field SameFieldMixed.Mode has no validation + // field SameFieldMixed.Value has no validation + return errs +} + +// Validate_SameValueMixedPayloads validates an instance of SameValueMixedPayloads according +// to declarative validation rules in the API schema. +func Validate_SameValueMixedPayloads(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SameValueMixedPayloads) (errs field.ErrorList) { + errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "value", func(obj *SameValueMixedPayloads) *string { return obj.Value }, func(obj *SameValueMixedPayloads) string { return obj.Mode }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, []validate.DiscriminatedRule[*string, string]{ + { + Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 3).MarkBeta()...) + return errs + }}, + })...) + + // field SameValueMixedPayloads.TypeMeta has no validation + // field SameValueMixedPayloads.Mode has no validation + // field SameValueMixedPayloads.Value has no validation + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc.go new file mode 100644 index 0000000000000..7de110c7d6e45 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc.go @@ -0,0 +1,47 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package enums + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + EnumField Enum `json:"enumField"` + + EnumFieldBeta BetaEnum `json:"enumFieldBeta"` +} + +// +k8s:alpha=+k8s:enum +type Enum string + +const ( + EnumA Enum = "A" +) + +// +k8s:beta=+k8s:enum +type BetaEnum string + +const ( + BetaEnumA BetaEnum = "A" +) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc_test.go new file mode 100644 index 0000000000000..3a2fca97428c4 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2025 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 enums + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + EnumField: "A", + EnumFieldBeta: "A", + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + EnumField: "B", + EnumFieldBeta: "A", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.NotSupported(field.NewPath("enumField"), Enum("B"), []string{"A"}).MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + EnumField: "A", + EnumFieldBeta: "A", + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + EnumField: "A", + EnumFieldBeta: "B", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.NotSupported(field.NewPath("enumFieldBeta"), BetaEnum("B"), []string{"A"}).MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/zz_generated.validations.go new file mode 100644 index 0000000000000..936fe4998ddec --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/zz_generated.validations.go @@ -0,0 +1,102 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package enums + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +var symbolsForBetaEnum = sets.New(BetaEnumA) + +// Validate_BetaEnum validates an instance of BetaEnum according +// to declarative validation rules in the API schema. +func Validate_BetaEnum(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BetaEnum) (errs field.ErrorList) { + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForBetaEnum, nil).MarkBeta()...) + + return errs +} + +var symbolsForEnum = sets.New(EnumA) + +// Validate_Enum validates an instance of Enum according +// to declarative validation rules in the API schema. +func Validate_Enum(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Enum) (errs field.ErrorList) { + errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum, nil).MarkAlpha()...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.EnumField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Enum, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Enum(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("enumField"), &obj.EnumField, safe.Field(oldObj, func(oldObj *Struct) *Enum { return &oldObj.EnumField }), oldObj != nil)...) + + // field Struct.EnumFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *BetaEnum, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_BetaEnum(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("enumFieldBeta"), &obj.EnumFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *BetaEnum { return &oldObj.EnumFieldBeta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc.go new file mode 100644 index 0000000000000..f0546b2bb4774 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc.go @@ -0,0 +1,118 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package listkeys + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MapItem struct { + Key string `json:"key"` + Value int `json:"value"` +} + +type MultiKeyItem struct { + Key1 string `json:"key1"` + Key2 int `json:"key2"` + Value int `json:"value"` +} + +type ListKeyStruct struct { + TypeMeta int + + // Case: Alpha listType, Standard Key + // +k8s:alpha=+k8s:listType=map + // +k8s:listMapKey=key + AlphaListTypeStandardKey []MapItem `json:"alphaListTypeStandardKey"` + + // Case: Standard listType, Alpha Key + // +k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key + StandardListTypeAlphaKey []MapItem `json:"standardListTypeAlphaKey"` + + // Case: Alpha listType, Alpha Key + // +k8s:alpha=+k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key + AlphaListTypeAlphaKey []MapItem `json:"alphaListTypeAlphaKey"` + + // Case: Standard listType, Alpha Key1, Standard Key2 + // +k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + StandardListTypeMixedKeys1 []MultiKeyItem `json:"standardListTypeMixedKeys1"` + + // Case: Standard listType, Standard Key1, Alpha Key2 + // +k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:alpha=+k8s:listMapKey=key2 + StandardListTypeMixedKeys2 []MultiKeyItem `json:"standardListTypeMixedKeys2"` + + // Case: Alpha listType, Alpha Key1, Standard Key2 + // +k8s:alpha=+k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + AlphaListTypeMixedKeys1 []MultiKeyItem `json:"alphaListTypeMixedKeys1"` + + // Case: Alpha listType, Standard Key1, Alpha Key2 + // +k8s:alpha=+k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:alpha=+k8s:listMapKey=key2 + AlphaListTypeMixedKeys2 []MultiKeyItem `json:"alphaListTypeMixedKeys2"` + + // Case: Standard listType, Beta Key1, Standard Key2 + // +k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + StandardListTypeMixedKeysBeta1 []MultiKeyItem `json:"standardListTypeMixedKeysBeta1"` + + // Case: Standard listType, Standard Key1, Beta Key2 + // +k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:beta=+k8s:listMapKey=key2 + StandardListTypeMixedKeysBeta2 []MultiKeyItem `json:"standardListTypeMixedKeysBeta2"` + + // Case: Beta listType, Beta Key1, Standard Key2 + // +k8s:beta=+k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + BetaListTypeMixedKeys1 []MultiKeyItem `json:"betaListTypeMixedKeys1"` + + // Case: Beta listType, Standard Key1, Beta Key2 + // +k8s:beta=+k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:beta=+k8s:listMapKey=key2 + BetaListTypeMixedKeys2 []MultiKeyItem `json:"betaListTypeMixedKeys2"` + + // Case: Beta listType, Standard Key + // +k8s:beta=+k8s:listType=map + // +k8s:listMapKey=key + BetaListTypeStandardKey []MapItem `json:"betaListTypeStandardKey"` + + // Case: Standard listType, Beta Key + // +k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key + StandardListTypeBetaKey []MapItem `json:"standardListTypeBetaKey"` + + // Case: Beta listType, Beta Key + // +k8s:beta=+k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key + BetaListTypeBetaKey []MapItem `json:"betaListTypeBetaKey"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc_test.go new file mode 100644 index 0000000000000..b18ccd769c55f --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2025 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 listkeys + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestListKeys(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ListKeyStruct{ + // Case 1: Alpha listType, Standard Key + AlphaListTypeStandardKey: []MapItem{ + {Key: "a", Value: 1}, + {Key: "a", Value: 2}, + }, + // Case 2: Standard listType, Alpha Key + StandardListTypeAlphaKey: []MapItem{ + {Key: "b", Value: 1}, + {Key: "b", Value: 2}, + }, + // Case 3: Alpha listType, Alpha Key + AlphaListTypeAlphaKey: []MapItem{ + {Key: "c", Value: 1}, + {Key: "c", Value: 2}, + }, + // Case 4: Standard listType, Alpha Key1, Standard Key2 + StandardListTypeMixedKeys1: []MultiKeyItem{ + {Key1: "d", Key2: 1, Value: 1}, + {Key1: "d", Key2: 1, Value: 2}, + }, + // Case 5: Standard listType, Standard Key1, Alpha Key2 + StandardListTypeMixedKeys2: []MultiKeyItem{ + {Key1: "e", Key2: 1, Value: 1}, + {Key1: "e", Key2: 1, Value: 2}, + }, + // Case 6: Alpha listType, Alpha Key1, Standard Key2 + AlphaListTypeMixedKeys1: []MultiKeyItem{ + {Key1: "f", Key2: 1, Value: 1}, + {Key1: "f", Key2: 1, Value: 2}, + }, + // Case 7: Alpha listType, Standard Key1, Alpha Key2 + AlphaListTypeMixedKeys2: []MultiKeyItem{ + {Key1: "g", Key2: 1, Value: 1}, + {Key1: "g", Key2: 1, Value: 2}, + }, + + // Case 11: Standard listType, Beta Key1, Standard Key2 + StandardListTypeMixedKeysBeta1: []MultiKeyItem{ + {Key1: "k", Key2: 1, Value: 1}, + {Key1: "k", Key2: 1, Value: 2}, + }, + // Case 12: Standard listType, Standard Key1, Beta Key2 + StandardListTypeMixedKeysBeta2: []MultiKeyItem{ + {Key1: "l", Key2: 1, Value: 1}, + {Key1: "l", Key2: 1, Value: 2}, + }, + // Case 13: Beta listType, Beta Key1, Standard Key2 + BetaListTypeMixedKeys1: []MultiKeyItem{ + {Key1: "m", Key2: 1, Value: 1}, + {Key1: "m", Key2: 1, Value: 2}, + }, + // Case 14: Beta listType, Standard Key1, Beta Key2 + BetaListTypeMixedKeys2: []MultiKeyItem{ + {Key1: "n", Key2: 1, Value: 1}, + {Key1: "n", Key2: 1, Value: 2}, + }, + + // Case 8: Beta listType, Standard Key + BetaListTypeStandardKey: []MapItem{ + {Key: "h", Value: 1}, + {Key: "h", Value: 2}, + }, + // Case 9: Standard listType, Beta Key + StandardListTypeBetaKey: []MapItem{ + {Key: "i", Value: 1}, + {Key: "i", Value: 2}, + }, + // Case 10: Beta listType, Beta Key + BetaListTypeBetaKey: []MapItem{ + {Key: "j", Value: 1}, + {Key: "j", Value: 2}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case 1: Alpha listType, Standard Key -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeStandardKey").Index(1), MapItem{Key: "a", Value: 2}).MarkAlpha(), + + // Case 2: Standard listType, Alpha Key -> Normal Error + // The alpha key is treated as a functional key for the list validation. + field.Duplicate(field.NewPath("standardListTypeAlphaKey").Index(1), MapItem{Key: "b", Value: 2}), + + // Case 3: Alpha listType, Alpha Key -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeAlphaKey").Index(1), MapItem{Key: "c", Value: 2}).MarkAlpha(), + + // Case 4: Standard listType, Alpha Key1, Standard Key2 -> Normal Error + // Both keys participate in the uniqueness check. + field.Duplicate(field.NewPath("standardListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "d", Key2: 1, Value: 2}), + + // Case 5: Standard listType, Standard Key1, Alpha Key2 -> Normal Error + field.Duplicate(field.NewPath("standardListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "e", Key2: 1, Value: 2}), + + // Case 6: Alpha listType, Alpha Key1, Standard Key2 -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "f", Key2: 1, Value: 2}).MarkAlpha(), + + // Case 7: Alpha listType, Standard Key1, Alpha Key2 -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "g", Key2: 1, Value: 2}).MarkAlpha(), + + // Case 11: Standard listType, Beta Key1, Standard Key2 -> Normal Error + field.Duplicate(field.NewPath("standardListTypeMixedKeysBeta1").Index(1), MultiKeyItem{Key1: "k", Key2: 1, Value: 2}), + + // Case 12: Standard listType, Standard Key1, Beta Key2 -> Normal Error + field.Duplicate(field.NewPath("standardListTypeMixedKeysBeta2").Index(1), MultiKeyItem{Key1: "l", Key2: 1, Value: 2}), + + // Case 13: Beta listType, Beta Key1, Standard Key2 -> Beta Error + field.Duplicate(field.NewPath("betaListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "m", Key2: 1, Value: 2}).MarkBeta(), + + // Case 14: Beta listType, Standard Key1, Beta Key2 -> Beta Error + field.Duplicate(field.NewPath("betaListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "n", Key2: 1, Value: 2}).MarkBeta(), + + // Case 8: Beta listType, Standard Key -> Beta Error + field.Duplicate(field.NewPath("betaListTypeStandardKey").Index(1), MapItem{Key: "h", Value: 2}).MarkBeta(), + + // Case 9: Standard listType, Beta Key -> Normal Error + // The beta key is treated as a functional key for the list validation. + field.Duplicate(field.NewPath("standardListTypeBetaKey").Index(1), MapItem{Key: "i", Value: 2}), + + // Case 10: Beta listType, Beta Key -> Beta Error + field.Duplicate(field.NewPath("betaListTypeBetaKey").Index(1), MapItem{Key: "j", Value: 2}).MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/zz_generated.validations.go new file mode 100644 index 0000000000000..d385265c5c0e1 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/zz_generated.validations.go @@ -0,0 +1,240 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listkeys + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ListKeyStruct + scheme.AddValidationFunc((*ListKeyStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ListKeyStruct(ctx, op, nil /* fldPath */, obj.(*ListKeyStruct), safe.Cast[*ListKeyStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ListKeyStruct validates an instance of ListKeyStruct according +// to declarative validation rules in the API schema. +func Validate_ListKeyStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListKeyStruct) (errs field.ErrorList) { + // field ListKeyStruct.TypeMeta has no validation + + // field ListKeyStruct.AlphaListTypeStandardKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkAlpha()...) + return + }(fldPath.Child("alphaListTypeStandardKey"), obj.AlphaListTypeStandardKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.AlphaListTypeStandardKey }), oldObj != nil)...) + + // field ListKeyStruct.StandardListTypeAlphaKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + return + }(fldPath.Child("standardListTypeAlphaKey"), obj.StandardListTypeAlphaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.StandardListTypeAlphaKey }), oldObj != nil)...) + + // field ListKeyStruct.AlphaListTypeAlphaKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkAlpha()...) + return + }(fldPath.Child("alphaListTypeAlphaKey"), obj.AlphaListTypeAlphaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.AlphaListTypeAlphaKey }), oldObj != nil)...) + + // field ListKeyStruct.StandardListTypeMixedKeys1 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 })...) + return + }(fldPath.Child("standardListTypeMixedKeys1"), obj.StandardListTypeMixedKeys1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeys1 }), oldObj != nil)...) + + // field ListKeyStruct.StandardListTypeMixedKeys2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 })...) + return + }(fldPath.Child("standardListTypeMixedKeys2"), obj.StandardListTypeMixedKeys2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeys2 }), oldObj != nil)...) + + // field ListKeyStruct.AlphaListTypeMixedKeys1 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }).MarkAlpha()...) + return + }(fldPath.Child("alphaListTypeMixedKeys1"), obj.AlphaListTypeMixedKeys1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.AlphaListTypeMixedKeys1 }), oldObj != nil)...) + + // field ListKeyStruct.AlphaListTypeMixedKeys2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }).MarkAlpha()...) + return + }(fldPath.Child("alphaListTypeMixedKeys2"), obj.AlphaListTypeMixedKeys2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.AlphaListTypeMixedKeys2 }), oldObj != nil)...) + + // field ListKeyStruct.StandardListTypeMixedKeysBeta1 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 })...) + return + }(fldPath.Child("standardListTypeMixedKeysBeta1"), obj.StandardListTypeMixedKeysBeta1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeysBeta1 }), oldObj != nil)...) + + // field ListKeyStruct.StandardListTypeMixedKeysBeta2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 })...) + return + }(fldPath.Child("standardListTypeMixedKeysBeta2"), obj.StandardListTypeMixedKeysBeta2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeysBeta2 }), oldObj != nil)...) + + // field ListKeyStruct.BetaListTypeMixedKeys1 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }).MarkBeta()...) + return + }(fldPath.Child("betaListTypeMixedKeys1"), obj.BetaListTypeMixedKeys1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.BetaListTypeMixedKeys1 }), oldObj != nil)...) + + // field ListKeyStruct.BetaListTypeMixedKeys2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }).MarkBeta()...) + return + }(fldPath.Child("betaListTypeMixedKeys2"), obj.BetaListTypeMixedKeys2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.BetaListTypeMixedKeys2 }), oldObj != nil)...) + + // field ListKeyStruct.BetaListTypeStandardKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkBeta()...) + return + }(fldPath.Child("betaListTypeStandardKey"), obj.BetaListTypeStandardKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.BetaListTypeStandardKey }), oldObj != nil)...) + + // field ListKeyStruct.StandardListTypeBetaKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + return + }(fldPath.Child("standardListTypeBetaKey"), obj.StandardListTypeBetaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.StandardListTypeBetaKey }), oldObj != nil)...) + + // field ListKeyStruct.BetaListTypeBetaKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkBeta()...) + return + }(fldPath.Child("betaListTypeBetaKey"), obj.BetaListTypeBetaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.BetaListTypeBetaKey }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc.go new file mode 100644 index 0000000000000..5602c79139df4 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc.go @@ -0,0 +1,80 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package listmapitem + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MapItem struct { + Key string `json:"key"` + Value int `json:"value"` +} + +type InnerItem struct { + Value int `json:"value"` +} + +type ListMapItemStruct struct { + TypeMeta int + + // Case: Standard Item + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10 + StandardItem []MapItem `json:"standardItem"` + + // Case: Alpha Item tag + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:alpha=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10 + AlphaItemTag []MapItem `json:"alphaItemTag"` + + // Case: Alpha validation + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10 + AlphaValidation []MapItem `json:"alphaValidation"` + + // Case: Double Alpha + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:alpha=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10 + DoubleAlpha []MapItem `json:"doubleAlpha"` + + // Case: Beta Item tag + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:beta=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10 + BetaItemTag []MapItem `json:"betaItemTag"` + + // Case: Beta validation + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10 + BetaValidation []MapItem `json:"betaValidation"` + + // Case: Double Beta + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:beta=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10 + DoubleBeta []MapItem `json:"doubleBeta"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc_test.go new file mode 100644 index 0000000000000..282da8b0ff553 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2025 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 listmapitem + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestListMapItem(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ListMapItemStruct{ + StandardItem: []MapItem{{Key: "foo", Value: 5}}, + AlphaItemTag: []MapItem{{Key: "foo", Value: 5}}, + AlphaValidation: []MapItem{{Key: "foo", Value: 5}}, + DoubleAlpha: []MapItem{{Key: "foo", Value: 5}}, + + BetaItemTag: []MapItem{{Key: "foo", Value: 5}}, + BetaValidation: []MapItem{{Key: "foo", Value: 5}}, + DoubleBeta: []MapItem{{Key: "foo", Value: 5}}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case 1: Standard Item -> Normal Error + field.Invalid(field.NewPath("standardItem").Index(0).Child("value"), 5, "").WithOrigin("minimum"), + + // Case 2: Alpha Item tag -> Alpha Error + field.Invalid(field.NewPath("alphaItemTag").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case 3: Alpha validation -> Alpha Error + field.Invalid(field.NewPath("alphaValidation").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case 4: Double Alpha -> Alpha Error + field.Invalid(field.NewPath("doubleAlpha").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case 5: Beta Item tag -> Beta Error + field.Invalid(field.NewPath("betaItemTag").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(), + + // Case 6: Beta validation -> Beta Error + field.Invalid(field.NewPath("betaValidation").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(), + + // Case 7: Double Beta -> Beta Error + field.Invalid(field.NewPath("doubleBeta").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/zz_generated.validations.go new file mode 100644 index 0000000000000..5c7fa6a001508 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/zz_generated.validations.go @@ -0,0 +1,198 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listmapitem + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ListMapItemStruct + scheme.AddValidationFunc((*ListMapItemStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ListMapItemStruct(ctx, op, nil /* fldPath */, obj.(*ListMapItemStruct), safe.Cast[*ListMapItemStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ListMapItemStruct validates an instance of ListMapItemStruct according +// to declarative validation rules in the API schema. +func Validate_ListMapItemStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListMapItemStruct) (errs field.ErrorList) { + // field ListMapItemStruct.TypeMeta has no validation + + // field ListMapItemStruct.StandardItem + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }) + })...) + }() + return + }(fldPath.Child("standardItem"), obj.StandardItem, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.StandardItem }), oldObj != nil)...) + + // field ListMapItemStruct.AlphaItemTag + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }) + }).MarkAlpha()...) + }() + return + }(fldPath.Child("alphaItemTag"), obj.AlphaItemTag, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.AlphaItemTag }), oldObj != nil)...) + + // field ListMapItemStruct.AlphaValidation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }) + })...) + }() + return + }(fldPath.Child("alphaValidation"), obj.AlphaValidation, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.AlphaValidation }), oldObj != nil)...) + + // field ListMapItemStruct.DoubleAlpha + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }) + }).MarkAlpha()...) + }() + return + }(fldPath.Child("doubleAlpha"), obj.DoubleAlpha, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.DoubleAlpha }), oldObj != nil)...) + + // field ListMapItemStruct.BetaItemTag + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }) + }).MarkBeta()...) + }() + return + }(fldPath.Child("betaItemTag"), obj.BetaItemTag, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.BetaItemTag }), oldObj != nil)...) + + // field ListMapItemStruct.BetaValidation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }) + })...) + }() + return + }(fldPath.Child("betaValidation"), obj.BetaValidation, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.BetaValidation }), oldObj != nil)...) + + // field ListMapItemStruct.DoubleBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...) + func() { // cohort {"key": "foo"} + errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }) + }).MarkBeta()...) + }() + return + }(fldPath.Child("doubleBeta"), obj.DoubleBeta, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.DoubleBeta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc.go new file mode 100644 index 0000000000000..6fd512e608665 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc.go @@ -0,0 +1,72 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package listset + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type ListSetStruct struct { + TypeMeta int + + // Case: Alpha listType=set + // +k8s:alpha=+k8s:listType=set + Set []ComplexSetItem `json:"set"` + + // Case: Beta listType=set + // +k8s:beta=+k8s:listType=set + BetaSet []ComplexSetItem `json:"betaSet"` + + // Case: Chained subfield validation + // +k8s:listType=set + // +k8s:eachVal=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10 + ChainedSubfieldSet []SimpleSetItem `json:"chainedSubfieldSet"` + + // Case: Alpha listType=set, Beta item validation + // +k8s:alpha=+k8s:listType=set + SetBetaItem []ComplexSetItemBeta `json:"setBetaItem"` + + // Case: Beta listType=set, Beta item validation + // +k8s:beta=+k8s:listType=set + BetaSetBetaItem []ComplexSetItemBeta `json:"betaSetBetaItem"` + + // Case: Chained subfield validation (Beta) + // +k8s:listType=set + // +k8s:eachVal=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10 + ChainedSubfieldSetBeta []SimpleSetItem `json:"chainedSubfieldSetBeta"` +} + +type ComplexSetItem struct { + // +k8s:alpha=+k8s:minimum=10 + Value int `json:"value"` + StringVal string `json:"stringVal"` +} + +type ComplexSetItemBeta struct { + // +k8s:beta=+k8s:minimum=10 + Value int `json:"value"` + StringVal string `json:"stringVal"` +} + +type SimpleSetItem struct { + Value int `json:"value"` + StringVal string `json:"stringVal"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc_test.go new file mode 100644 index 0000000000000..24ffc0e1476a7 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 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 listset + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestListSet(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ListSetStruct{ + Set: []ComplexSetItem{ + {StringVal: "abc", Value: 10}, + {StringVal: "abc", Value: 10}, // Duplicate value (alpha) + }, + BetaSet: []ComplexSetItem{ + {StringVal: "abc", Value: 10}, + {StringVal: "abc", Value: 10}, // Duplicate value (beta) + }, + ChainedSubfieldSet: []SimpleSetItem{ + {StringVal: "def", Value: 9}, // Value 9 < 10 (alpha) + }, + SetBetaItem: []ComplexSetItemBeta{ + {StringVal: "ghi", Value: 10}, + {StringVal: "ghi", Value: 10}, // Duplicate value (alpha) + }, + BetaSetBetaItem: []ComplexSetItemBeta{ + {StringVal: "jkl", Value: 10}, + {StringVal: "jkl", Value: 10}, // Duplicate value (beta) + }, + ChainedSubfieldSetBeta: []SimpleSetItem{ + {StringVal: "mno", Value: 9}, // Value 9 < 10 (beta) + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Set: Duplicate value is SHADOWED + field.Duplicate(field.NewPath("set").Index(1), ComplexSetItem{StringVal: "abc", Value: 10}).MarkAlpha(), + + // BetaSet: Duplicate value is SHADOWED + field.Duplicate(field.NewPath("betaSet").Index(1), ComplexSetItem{StringVal: "abc", Value: 10}).MarkBeta(), + + // ChainedSubfieldSet: Value 9 < 10 is SHADOWED + field.Invalid(field.NewPath("chainedSubfieldSet").Index(0).Child("value"), 9, "").WithOrigin("minimum").MarkAlpha(), + + // SetBetaItem: Duplicate value is SHADOWED (Alpha list) + field.Duplicate(field.NewPath("setBetaItem").Index(1), ComplexSetItemBeta{StringVal: "ghi", Value: 10}).MarkAlpha(), + + // BetaSetBetaItem: Duplicate value is SHADOWED (Beta list) + field.Duplicate(field.NewPath("betaSetBetaItem").Index(1), ComplexSetItemBeta{StringVal: "jkl", Value: 10}).MarkBeta(), + + // ChainedSubfieldSetBeta: Value 9 < 10 is SHADOWED (Beta item) + field.Invalid(field.NewPath("chainedSubfieldSetBeta").Index(0).Child("value"), 9, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/zz_generated.validations.go new file mode 100644 index 0000000000000..3840e6a0c7a5a --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/zz_generated.validations.go @@ -0,0 +1,192 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listset + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ListSetStruct + scheme.AddValidationFunc((*ListSetStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ListSetStruct(ctx, op, nil /* fldPath */, obj.(*ListSetStruct), safe.Cast[*ListSetStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ComplexSetItem validates an instance of ComplexSetItem according +// to declarative validation rules in the API schema. +func Validate_ComplexSetItem(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComplexSetItem) (errs field.ErrorList) { + // field ComplexSetItem.Value + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("value"), &obj.Value, safe.Field(oldObj, func(oldObj *ComplexSetItem) *int { return &oldObj.Value }), oldObj != nil)...) + + // field ComplexSetItem.StringVal has no validation + return errs +} + +// Validate_ComplexSetItemBeta validates an instance of ComplexSetItemBeta according +// to declarative validation rules in the API schema. +func Validate_ComplexSetItemBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComplexSetItemBeta) (errs field.ErrorList) { + // field ComplexSetItemBeta.Value + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()...) + return + }(fldPath.Child("value"), &obj.Value, safe.Field(oldObj, func(oldObj *ComplexSetItemBeta) *int { return &oldObj.Value }), oldObj != nil)...) + + // field ComplexSetItemBeta.StringVal has no validation + return errs +} + +// Validate_ListSetStruct validates an instance of ListSetStruct according +// to declarative validation rules in the API schema. +func Validate_ListSetStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListSetStruct) (errs field.ErrorList) { + // field ListSetStruct.TypeMeta has no validation + + // field ListSetStruct.Set + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []ComplexSetItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItem)...) + return + }(fldPath.Child("set"), obj.Set, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItem { return oldObj.Set }), oldObj != nil)...) + + // field ListSetStruct.BetaSet + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []ComplexSetItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta()...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItem)...) + return + }(fldPath.Child("betaSet"), obj.BetaSet, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItem { return oldObj.BetaSet }), oldObj != nil)...) + + // field ListSetStruct.ChainedSubfieldSet + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []SimpleSetItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SimpleSetItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *SimpleSetItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }) + })...) + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + return + }(fldPath.Child("chainedSubfieldSet"), obj.ChainedSubfieldSet, safe.Field(oldObj, func(oldObj *ListSetStruct) []SimpleSetItem { return oldObj.ChainedSubfieldSet }), oldObj != nil)...) + + // field ListSetStruct.SetBetaItem + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []ComplexSetItemBeta, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItemBeta)...) + return + }(fldPath.Child("setBetaItem"), obj.SetBetaItem, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItemBeta { return oldObj.SetBetaItem }), oldObj != nil)...) + + // field ListSetStruct.BetaSetBetaItem + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []ComplexSetItemBeta, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta()...) + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItemBeta)...) + return + }(fldPath.Child("betaSetBetaItem"), obj.BetaSetBetaItem, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItemBeta { return oldObj.BetaSetBetaItem }), oldObj != nil)...) + + // field ListSetStruct.ChainedSubfieldSetBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []SimpleSetItem, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SimpleSetItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *SimpleSetItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }) + })...) + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...) + return + }(fldPath.Child("chainedSubfieldSetBeta"), obj.ChainedSubfieldSetBeta, safe.Field(oldObj, func(oldObj *ListSetStruct) []SimpleSetItem { return oldObj.ChainedSubfieldSetBeta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc.go new file mode 100644 index 0000000000000..d4cd6e03b3e01 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package mapvalidation + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MapValidationStruct struct { + TypeMeta int + + // Case: Standard eachVal + // +k8s:eachVal=+k8s:maxLength=2 + StandardEachVal map[string]string `json:"standardEachVal"` + + // Case: Alpha eachVal + // +k8s:alpha=+k8s:eachVal=+k8s:maxLength=2 + AlphaEachVal map[string]string `json:"AlphaEachVal"` + + // Case: Beta eachVal + // +k8s:beta=+k8s:eachVal=+k8s:maxLength=2 + BetaEachVal map[string]string `json:"BetaEachVal"` + + // Case: Standard eachKey + // +k8s:eachKey=+k8s:maxLength=2 + StandardEachKey map[string]string `json:"standardEachKey"` + + // Case: Alpha eachKey + // +k8s:alpha=+k8s:eachKey=+k8s:maxLength=2 + AlphaEachKey map[string]string `json:"AlphaEachKey"` + + // Case: Beta eachKey + // +k8s:beta=+k8s:eachKey=+k8s:maxLength=2 + BetaEachKey map[string]string `json:"BetaEachKey"` + + // Case: Alpha Validation + // +k8s:eachVal=+k8s:alpha=+k8s:maxLength=2 + AlphaValidation map[string]string `json:"AlphaValidation"` + + // Case: Beta Validation + // +k8s:eachVal=+k8s:beta=+k8s:maxLength=2 + BetaValidation map[string]string `json:"BetaValidation"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc_test.go new file mode 100644 index 0000000000000..b8239c06e5ec3 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2025 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 mapvalidation + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMapValidation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&MapValidationStruct{ + StandardEachVal: map[string]string{"a": "foo"}, + AlphaEachVal: map[string]string{"a": "foo"}, + BetaEachVal: map[string]string{"a": "foo"}, + StandardEachKey: map[string]string{"foo": "a"}, + AlphaEachKey: map[string]string{"foo": "a"}, + BetaEachKey: map[string]string{"foo": "a"}, + AlphaValidation: map[string]string{"a": "foo"}, + BetaValidation: map[string]string{"a": "foo"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case: Standard eachVal -> Normal Error + field.TooLong(field.NewPath("standardEachVal").Key("a"), "foo", 2).WithOrigin("maxLength"), + + // Case: Alpha eachVal -> Alpha Error + field.TooLong(field.NewPath("AlphaEachVal").Key("a"), "foo", 2).WithOrigin("maxLength").MarkAlpha(), + + // Case: Beta eachVal -> Beta Error + field.TooLong(field.NewPath("BetaEachVal").Key("a"), "foo", 2).WithOrigin("maxLength").MarkBeta(), + + // Case: Standard eachKey -> Normal Error + field.TooLong(field.NewPath("standardEachKey"), "foo", 2).WithOrigin("maxLength"), + + // Case: Alpha eachKey -> Alpha Error + field.TooLong(field.NewPath("AlphaEachKey"), "foo", 2).WithOrigin("maxLength").MarkAlpha(), + + // Case: Beta eachKey -> Beta Error + field.TooLong(field.NewPath("BetaEachKey"), "foo", 2).WithOrigin("maxLength").MarkBeta(), + + // Case: Alpha Validation -> Alpha Error + field.TooLong(field.NewPath("AlphaValidation").Key("a"), "foo", 2).WithOrigin("maxLength").MarkAlpha(), + + // Case: Beta Validation -> Beta Error + field.TooLong(field.NewPath("BetaValidation").Key("a"), "foo", 2).WithOrigin("maxLength").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/zz_generated.validations.go new file mode 100644 index 0000000000000..8b774c2311a26 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/zz_generated.validations.go @@ -0,0 +1,170 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapvalidation + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MapValidationStruct + scheme.AddValidationFunc((*MapValidationStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MapValidationStruct(ctx, op, nil /* fldPath */, obj.(*MapValidationStruct), safe.Cast[*MapValidationStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_MapValidationStruct validates an instance of MapValidationStruct according +// to declarative validation rules in the API schema. +func Validate_MapValidationStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapValidationStruct) (errs field.ErrorList) { + // field MapValidationStruct.TypeMeta has no validation + + // field MapValidationStruct.StandardEachVal + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + })...) + return + }(fldPath.Child("standardEachVal"), obj.StandardEachVal, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.StandardEachVal }), oldObj != nil)...) + + // field MapValidationStruct.AlphaEachVal + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkAlpha()...) + return + }(fldPath.Child("AlphaEachVal"), obj.AlphaEachVal, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.AlphaEachVal }), oldObj != nil)...) + + // field MapValidationStruct.BetaEachVal + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkBeta()...) + return + }(fldPath.Child("BetaEachVal"), obj.BetaEachVal, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.BetaEachVal }), oldObj != nil)...) + + // field MapValidationStruct.StandardEachKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + })...) + return + }(fldPath.Child("standardEachKey"), obj.StandardEachKey, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.StandardEachKey }), oldObj != nil)...) + + // field MapValidationStruct.AlphaEachKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkAlpha()...) + return + }(fldPath.Child("AlphaEachKey"), obj.AlphaEachKey, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.AlphaEachKey }), oldObj != nil)...) + + // field MapValidationStruct.BetaEachKey + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkBeta()...) + return + }(fldPath.Child("BetaEachKey"), obj.BetaEachKey, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.BetaEachKey }), oldObj != nil)...) + + // field MapValidationStruct.AlphaValidation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha() + })...) + return + }(fldPath.Child("AlphaValidation"), obj.AlphaValidation, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.AlphaValidation }), oldObj != nil)...) + + // field MapValidationStruct.BetaValidation + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2).MarkBeta() + })...) + return + }(fldPath.Child("BetaValidation"), obj.BetaValidation, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.BetaValidation }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc.go new file mode 100644 index 0000000000000..7b6617ad2b909 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc.go @@ -0,0 +1,35 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package optionalrequired + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:required + RequiredField *string `json:"requiredField"` + + // +k8s:beta=+k8s:required + RequiredFieldBeta *string `json:"requiredFieldBeta"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc_test.go new file mode 100644 index 0000000000000..92cb8ee44c1dd --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc_test.go @@ -0,0 +1,64 @@ +/* +Copyright 2025 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 optionalrequired + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + RequiredField: ptr.To("val"), + RequiredFieldBeta: ptr.To("val"), + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + RequiredField: nil, + RequiredFieldBeta: ptr.To("val"), + }).OldValue(&Struct{ + RequiredField: ptr.To("old"), + RequiredFieldBeta: ptr.To("old"), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("requiredField"), "").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + RequiredField: ptr.To("val"), + RequiredFieldBeta: ptr.To("val"), + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + RequiredField: ptr.To("val"), + RequiredFieldBeta: nil, + }).OldValue(&Struct{ + RequiredField: ptr.To("old"), + RequiredFieldBeta: ptr.To("old"), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("requiredFieldBeta"), "").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/zz_generated.validations.go new file mode 100644 index 0000000000000..784e68b563bdf --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/zz_generated.validations.go @@ -0,0 +1,95 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package optionalrequired + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.RequiredField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("requiredField"), obj.RequiredField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.RequiredField }), oldObj != nil)...) + + // field Struct.RequiredFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("requiredFieldBeta"), obj.RequiredFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.RequiredFieldBeta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc.go new file mode 100644 index 0000000000000..fbf37cd823b42 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc.go @@ -0,0 +1,91 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package simple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:minimum=10 + IntField int `json:"intField"` + + // +k8s:beta=+k8s:minimum=10 + IntFieldBeta int `json:"intFieldBeta"` + + // +k8s:alpha=+k8s:maxLength=5 + StringField string `json:"stringField"` + + // +k8s:beta=+k8s:maxLength=5 + StringFieldBeta string `json:"stringFieldBeta"` + + // +k8s:alpha=+k8s:maxItems=2 + SliceField []string `json:"sliceField"` + + // +k8s:beta=+k8s:maxItems=2 + SliceFieldBeta []string `json:"sliceFieldBeta"` + + // +k8s:alpha=+k8s:format=k8s-uuid + UUIDField string `json:"uuidField"` + + // +k8s:beta=+k8s:format=k8s-uuid + UUIDFieldBeta string `json:"uuidFieldBeta"` + + // +k8s:alpha=+k8s:immutable + ImmutableField string `json:"immutableField"` + + // +k8s:beta=+k8s:immutable + ImmutableFieldBeta string `json:"immutableFieldBeta"` +} + +type SpecialValidationStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:neq=5 + NEQField int `json:"neqField"` + + // +k8s:beta=+k8s:neq=5 + NEQFieldBeta int `json:"neqFieldBeta"` + + // +k8s:alpha=+k8s:forbidden + ForbiddenField *string `json:"forbiddenField"` + + // +k8s:beta=+k8s:forbidden + ForbiddenFieldBeta *string `json:"forbiddenFieldBeta"` + + // +k8s:alpha=+k8s:update=NoModify + UpdateField string `json:"updateField"` + + // +k8s:beta=+k8s:update=NoModify + UpdateFieldBeta string `json:"updateFieldBeta"` +} + +type StructWithValidateFalse struct { + TypeMeta int + + // +k8s:alpha=+k8s:validateFalse="always fails" + ValidateFalse *string `json:"validateFalse"` + + // +k8s:beta=+k8s:validateFalse="always fails" + ValidateFalseBeta *string `json:"validateFalseBeta"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc_test.go new file mode 100644 index 0000000000000..d768f825c400c --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2025 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 simple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + IntField: 10, + StringField: "abc", + SliceField: []string{"a", "b"}, + UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableField: "foo", + IntFieldBeta: 10, + StringFieldBeta: "abc", + SliceFieldBeta: []string{"a", "b"}, + UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableFieldBeta: "foo", + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + IntField: 5, + StringField: "too-long", + SliceField: []string{"a", "b", "c"}, + UUIDField: "not-a-uuid", + ImmutableField: "bar", + IntFieldBeta: 10, + StringFieldBeta: "abc", + SliceFieldBeta: []string{"a", "b"}, + UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableFieldBeta: "foo", + }).OldValue(&Struct{ + ImmutableField: "foo", + ImmutableFieldBeta: "foo", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 5, "").WithOrigin("minimum").MarkAlpha(), + field.TooLong(field.NewPath("stringField"), "too-long", 5).WithOrigin("maxLength").MarkAlpha(), + field.TooMany(field.NewPath("sliceField"), 3, 2).WithOrigin("maxItems").MarkAlpha(), + field.Invalid(field.NewPath("uuidField"), "not-a-uuid", "").WithOrigin("format=k8s-uuid").MarkAlpha(), + field.Invalid(field.NewPath("immutableField"), "bar", "").WithOrigin("immutable").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + IntField: 10, + StringField: "abc", + SliceField: []string{"a", "b"}, + UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableField: "foo", + IntFieldBeta: 10, + StringFieldBeta: "abc", + SliceFieldBeta: []string{"a", "b"}, + UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableFieldBeta: "foo", + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + IntField: 10, + StringField: "abc", + SliceField: []string{"a", "b"}, + UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableField: "foo", + IntFieldBeta: 5, + StringFieldBeta: "too-long", + SliceFieldBeta: []string{"a", "b", "c"}, + UUIDFieldBeta: "not-a-uuid", + ImmutableFieldBeta: "bar", + }).OldValue(&Struct{ + ImmutableField: "foo", + ImmutableFieldBeta: "foo", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intFieldBeta"), 5, "").WithOrigin("minimum").MarkBeta(), + field.TooLong(field.NewPath("stringFieldBeta"), "too-long", 5).WithOrigin("maxLength").MarkBeta(), + field.TooMany(field.NewPath("sliceFieldBeta"), 3, 2).WithOrigin("maxItems").MarkBeta(), + field.Invalid(field.NewPath("uuidFieldBeta"), "not-a-uuid", "").WithOrigin("format=k8s-uuid").MarkBeta(), + field.Invalid(field.NewPath("immutableFieldBeta"), "bar", "").WithOrigin("immutable").MarkBeta(), + }) +} + +func TestSpecialValidationStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&SpecialValidationStruct{ + NEQField: 10, + NEQFieldBeta: 10, + }).ExpectValid() + + st.Value(&SpecialValidationStruct{ + NEQField: 5, + NEQFieldBeta: 5, + ForbiddenField: ptr.To("val"), + ForbiddenFieldBeta: ptr.To("val"), + UpdateField: "new", + UpdateFieldBeta: "new", + }).OldValue(&SpecialValidationStruct{ + UpdateField: "old", + UpdateFieldBeta: "old", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("neqField"), 5, "must not be 5").WithOrigin("neq").MarkAlpha(), + field.Invalid(field.NewPath("neqFieldBeta"), 5, "must not be 5").WithOrigin("neq").MarkBeta(), + field.Forbidden(field.NewPath("forbiddenField"), "").MarkAlpha(), + field.Forbidden(field.NewPath("forbiddenFieldBeta"), "").MarkBeta(), + field.Invalid(field.NewPath("updateField"), "new", "field cannot be modified once set").WithOrigin("update").MarkAlpha(), + field.Invalid(field.NewPath("updateFieldBeta"), "new", "field cannot be modified once set").WithOrigin("update").MarkBeta(), + }) + + st.Value(&StructWithValidateFalse{ + ValidateFalse: ptr.To("val"), + ValidateFalseBeta: ptr.To("val"), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("validateFalse"), "val", "always fails").MarkAlpha(), + field.Invalid(field.NewPath("validateFalseBeta"), "val", "always fails").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go new file mode 100644 index 0000000000000..f3a6a062f3c96 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go @@ -0,0 +1,368 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package simple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type SpecialValidationStruct + scheme.AddValidationFunc((*SpecialValidationStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SpecialValidationStruct(ctx, op, nil /* fldPath */, obj.(*SpecialValidationStruct), safe.Cast[*SpecialValidationStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type StructWithValidateFalse + scheme.AddValidationFunc((*StructWithValidateFalse)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructWithValidateFalse(ctx, op, nil /* fldPath */, obj.(*StructWithValidateFalse), safe.Cast[*StructWithValidateFalse](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_SpecialValidationStruct validates an instance of SpecialValidationStruct according +// to declarative validation rules in the API schema. +func Validate_SpecialValidationStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SpecialValidationStruct) (errs field.ErrorList) { + // field SpecialValidationStruct.TypeMeta has no validation + + // field SpecialValidationStruct.NEQField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.NEQ(ctx, op, fldPath, obj, oldObj, 5).MarkAlpha()...) + return + }(fldPath.Child("neqField"), &obj.NEQField, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *int { return &oldObj.NEQField }), oldObj != nil)...) + + // field SpecialValidationStruct.NEQFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.NEQ(ctx, op, fldPath, obj, oldObj, 5).MarkBeta()...) + return + }(fldPath.Child("neqFieldBeta"), &obj.NEQFieldBeta, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *int { return &oldObj.NEQFieldBeta }), oldObj != nil)...) + + // field SpecialValidationStruct.ForbiddenField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("forbiddenField"), obj.ForbiddenField, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return oldObj.ForbiddenField }), oldObj != nil)...) + + // field SpecialValidationStruct.ForbiddenFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("forbiddenFieldBeta"), obj.ForbiddenFieldBeta, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return oldObj.ForbiddenFieldBeta }), oldObj != nil)...) + + // field SpecialValidationStruct.UpdateField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("updateField"), &obj.UpdateField, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return &oldObj.UpdateField }), oldObj != nil)...) + + // field SpecialValidationStruct.UpdateFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("updateFieldBeta"), &obj.UpdateFieldBeta, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return &oldObj.UpdateFieldBeta }), oldObj != nil)...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }), oldObj != nil)...) + + // field Struct.IntFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()...) + return + }(fldPath.Child("intFieldBeta"), &obj.IntFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntFieldBeta }), oldObj != nil)...) + + // field Struct.StringField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5).MarkAlpha()...) + return + }(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }), oldObj != nil)...) + + // field Struct.StringFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5).MarkBeta()...) + return + }(fldPath.Child("stringFieldBeta"), &obj.StringFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringFieldBeta }), oldObj != nil)...) + + // field Struct.SliceField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("sliceField"), obj.SliceField, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceField }), oldObj != nil)...) + + // field Struct.SliceFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("sliceFieldBeta"), obj.SliceFieldBeta, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceFieldBeta }), oldObj != nil)...) + + // field Struct.UUIDField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return + }(fldPath.Child("uuidField"), &obj.UUIDField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.UUIDField }), oldObj != nil)...) + + // field Struct.UUIDFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return + }(fldPath.Child("uuidFieldBeta"), &obj.UUIDFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.UUIDFieldBeta }), oldObj != nil)...) + + // field Struct.ImmutableField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("immutableField"), &obj.ImmutableField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.ImmutableField }), oldObj != nil)...) + + // field Struct.ImmutableFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("immutableFieldBeta"), &obj.ImmutableFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.ImmutableFieldBeta }), oldObj != nil)...) + + return errs +} + +// Validate_StructWithValidateFalse validates an instance of StructWithValidateFalse according +// to declarative validation rules in the API schema. +func Validate_StructWithValidateFalse(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StructWithValidateFalse) (errs field.ErrorList) { + // field StructWithValidateFalse.TypeMeta has no validation + + // field StructWithValidateFalse.ValidateFalse + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "always fails").MarkAlpha()...) + return + }(fldPath.Child("validateFalse"), obj.ValidateFalse, safe.Field(oldObj, func(oldObj *StructWithValidateFalse) *string { return oldObj.ValidateFalse }), oldObj != nil)...) + + // field StructWithValidateFalse.ValidateFalseBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "always fails").MarkBeta()...) + return + }(fldPath.Child("validateFalseBeta"), obj.ValidateFalseBeta, safe.Field(oldObj, func(oldObj *StructWithValidateFalse) *string { return oldObj.ValidateFalseBeta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc.go new file mode 100644 index 0000000000000..d8753990038cb --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package structs + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MixedStruct struct { + TypeMeta int + + // +k8s:minimum=5 + // +k8s:alpha=+k8s:minimum=10 + IntField int `json:"intField"` + + // +k8s:minimum=5 + // +k8s:beta=+k8s:minimum=10 + IntFieldBeta int `json:"intFieldBeta"` + + // +k8s:maxItems=5 + // +k8s:alpha=+k8s:maxItems=3 + ListField []string `json:"listField"` + + // +k8s:maxItems=5 + // +k8s:beta=+k8s:maxItems=3 + ListFieldBeta []string `json:"listFieldBeta"` +} + +type ConditionalStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:ifEnabled(MyFeature)=+k8s:minimum=10 + ConditionalField int `json:"conditionalField"` + + // +k8s:beta=+k8s:ifEnabled(MyFeature)=+k8s:minimum=10 + ConditionalFieldBeta int `json:"conditionalFieldBeta"` + + // +k8s:alpha=+k8s:alpha=+k8s:minimum=20 + RecursiveAlpha int `json:"recursiveAlpha"` + + // +k8s:beta=+k8s:beta=+k8s:minimum=20 + RecursiveBeta int `json:"recursiveBeta"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc_test.go new file mode 100644 index 0000000000000..0707892f67bac --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2025 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 structs + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMixed(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid case (meets both normal and alpha requirements) + st.Value(&MixedStruct{ + IntField: 15, + IntFieldBeta: 15, + ListField: []string{"a", "b", "c"}, + ListFieldBeta: []string{"a", "b", "c"}, + }).ExpectValid() + + // Fails alpha validation but passes normal validation + // IntField: 5 <= 8 < 10 (alpha fails) + // ListField: 3 < 4 <= 5 (alpha fails) + st.Value(&MixedStruct{ + IntField: 8, + IntFieldBeta: 8, + ListField: []string{"a", "b", "c", "d"}, + ListFieldBeta: []string{"a", "b", "c", "d"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 8, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("intFieldBeta"), 8, "").WithOrigin("minimum").MarkBeta(), + field.TooMany(field.NewPath("listField"), 4, 3).WithOrigin("maxItems").MarkAlpha(), + field.TooMany(field.NewPath("listFieldBeta"), 4, 3).WithOrigin("maxItems").MarkBeta(), + }) + + // Fails both normal and alpha validation + // IntField: 4 < 5 (normal fails) AND 4 < 10 (alpha fails) + // ListField: 6 > 5 (normal fails) AND 6 > 3 (alpha fails) + st.Value(&MixedStruct{ + IntField: 4, + IntFieldBeta: 4, + ListField: []string{"a", "b", "c", "d", "e", "f"}, + ListFieldBeta: []string{"a", "b", "c", "d", "e", "f"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 4, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("intField"), 4, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("intFieldBeta"), 4, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("intFieldBeta"), 4, "").WithOrigin("minimum").MarkBeta(), + field.TooMany(field.NewPath("listField"), 6, 5).WithOrigin("maxItems"), + field.TooMany(field.NewPath("listField"), 6, 3).WithOrigin("maxItems").MarkAlpha(), + field.TooMany(field.NewPath("listFieldBeta"), 6, 5).WithOrigin("maxItems"), + field.TooMany(field.NewPath("listFieldBeta"), 6, 3).WithOrigin("maxItems").MarkBeta(), + }) +} + +func TestConditionalStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ConditionalStruct{ + ConditionalField: 15, + ConditionalFieldBeta: 15, + RecursiveAlpha: 25, + RecursiveBeta: 25, + }).Opts([]string{"MyFeature"}).ExpectValid() + + st.Value(&ConditionalStruct{ + ConditionalField: 5, + ConditionalFieldBeta: 5, + RecursiveAlpha: 10, + RecursiveBeta: 10, + }).Opts([]string{"MyFeature"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("conditionalField"), 5, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("conditionalFieldBeta"), 5, "").WithOrigin("minimum").MarkBeta(), + field.Invalid(field.NewPath("recursiveAlpha"), 10, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("recursiveBeta"), 10, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/zz_generated.validations.go new file mode 100644 index 0000000000000..123ecb700f0b1 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/zz_generated.validations.go @@ -0,0 +1,198 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package structs + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ConditionalStruct + scheme.AddValidationFunc((*ConditionalStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ConditionalStruct(ctx, op, nil /* fldPath */, obj.(*ConditionalStruct), safe.Cast[*ConditionalStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type MixedStruct + scheme.AddValidationFunc((*MixedStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MixedStruct(ctx, op, nil /* fldPath */, obj.(*MixedStruct), safe.Cast[*MixedStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_ConditionalStruct validates an instance of ConditionalStruct according +// to declarative validation rules in the API schema. +func Validate_ConditionalStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ConditionalStruct) (errs field.ErrorList) { + // field ConditionalStruct.TypeMeta has no validation + + // field ConditionalStruct.ConditionalField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "MyFeature", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkAlpha()...) + return + }(fldPath.Child("conditionalField"), &obj.ConditionalField, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.ConditionalField }), oldObj != nil)...) + + // field ConditionalStruct.ConditionalFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "MyFeature", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkBeta()...) + return + }(fldPath.Child("conditionalFieldBeta"), &obj.ConditionalFieldBeta, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.ConditionalFieldBeta }), oldObj != nil)...) + + // field ConditionalStruct.RecursiveAlpha + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 20).MarkAlpha()...) + return + }(fldPath.Child("recursiveAlpha"), &obj.RecursiveAlpha, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.RecursiveAlpha }), oldObj != nil)...) + + // field ConditionalStruct.RecursiveBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 20).MarkBeta()...) + return + }(fldPath.Child("recursiveBeta"), &obj.RecursiveBeta, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.RecursiveBeta }), oldObj != nil)...) + + return errs +} + +// Validate_MixedStruct validates an instance of MixedStruct according +// to declarative validation rules in the API schema. +func Validate_MixedStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedStruct) (errs field.ErrorList) { + // field MixedStruct.TypeMeta has no validation + + // field MixedStruct.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 5)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *MixedStruct) *int { return &oldObj.IntField }), oldObj != nil)...) + + // field MixedStruct.IntFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 5)...) + errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()...) + return + }(fldPath.Child("intFieldBeta"), &obj.IntFieldBeta, safe.Field(oldObj, func(oldObj *MixedStruct) *int { return &oldObj.IntFieldBeta }), oldObj != nil)...) + + // field MixedStruct.ListField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 5); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 3).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *MixedStruct) []string { return oldObj.ListField }), oldObj != nil)...) + + // field MixedStruct.ListFieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 5); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 3).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("listFieldBeta"), obj.ListFieldBeta, safe.Field(oldObj, func(oldObj *MixedStruct) []string { return oldObj.ListFieldBeta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc.go new file mode 100644 index 0000000000000..c2d7c6f237b3a --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc.go @@ -0,0 +1,48 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package subfields + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:subfield(inner)=+k8s:minimum=5 + Subfield SubStruct `json:"subfield"` + + // +k8s:beta=+k8s:subfield(inner)=+k8s:minimum=5 + SubfieldBeta SubStruct `json:"subfieldBeta"` + + // +k8s:alpha=+k8s:subfield(z1)=+k8s:zeroOrOneOfMember + // +k8s:alpha=+k8s:subfield(z2)=+k8s:zeroOrOneOfMember + UnionField SubUnion `json:"unionField"` +} + +type SubStruct struct { + Inner int `json:"inner"` +} + +type SubUnion struct { + Z1 *int `json:"z1"` + Z2 *int `json:"z2"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc_test.go new file mode 100644 index 0000000000000..f4e62109e27d2 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2025 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 subfields + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 5}, + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + Subfield: SubStruct{Inner: 1}, + SubfieldBeta: SubStruct{Inner: 5}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("subfield", "inner"), 1, "").WithOrigin("minimum").MarkAlpha(), + }) + + one := 1 + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 5}, + UnionField: SubUnion{ + Z1: &one, + Z2: &one, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("unionField"), &SubUnion{ + Z1: &one, Z2: &one, + }, "only one of z1, z2 may be specified").WithOrigin("zeroOrOneOf").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 5}, + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 1}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("subfieldBeta", "inner"), 1, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/zz_generated.validations.go new file mode 100644 index 0000000000000..b32f6c0f2756d --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/zz_generated.validations.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package subfields + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_subfields_Struct_unionField_ = validate.NewUnionMembership(validate.NewUnionMember("z1"), validate.NewUnionMember("z2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.Subfield + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *SubStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + func() { // cohort inner + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "inner", func(o *SubStruct) *int { return &o.Inner }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 5) + }).MarkAlpha()...) + }() + return + }(fldPath.Child("subfield"), &obj.Subfield, safe.Field(oldObj, func(oldObj *Struct) *SubStruct { return &oldObj.Subfield }), oldObj != nil)...) + + // field Struct.SubfieldBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *SubStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + func() { // cohort inner + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "inner", func(o *SubStruct) *int { return &o.Inner }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 5) + }).MarkBeta()...) + }() + return + }(fldPath.Child("subfieldBeta"), &obj.SubfieldBeta, safe.Field(oldObj, func(oldObj *Struct) *SubStruct { return &oldObj.SubfieldBeta }), oldObj != nil)...) + + // field Struct.UnionField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *SubUnion, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_subfields_Struct_unionField_, func(obj *SubUnion) bool { + if obj == nil { + return false + } + return obj.Z1 != nil + }, func(obj *SubUnion) bool { + if obj == nil { + return false + } + return obj.Z2 != nil + }).MarkAlpha()...) + return + }(fldPath.Child("unionField"), &obj.UnionField, safe.Field(oldObj, func(oldObj *Struct) *SubUnion { return &oldObj.UnionField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc.go new file mode 100644 index 0000000000000..82a24677ae410 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc.go @@ -0,0 +1,130 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package unions + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:unionDiscriminator + D D `json:"d"` + + // +k8s:alpha=+k8s:unionMember + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:alpha(since:"1.35")=+k8s:unionMember + // +k8s:optional + M2 *M2 `json:"m2"` +} + +type UnionStructBeta struct { + TypeMeta int + + // +k8s:beta=+k8s:unionDiscriminator + DBeta BetaD `json:"dBeta"` + + // +k8s:beta=+k8s:unionMember + // +k8s:optional + M1Beta *BetaM1 `json:"m1Beta"` + + // +k8s:beta(since:"1.35")=+k8s:unionMember + // +k8s:optional + M2Beta *BetaM2 `json:"m2Beta"` +} + +type MyStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:zeroOrOneOfMember + // +k8s:optional + Z1 *Z1 `json:"z1"` + + // +k8s:alpha=+k8s:zeroOrOneOfMember + // +k8s:optional + Z2 *Z2 `json:"z2"` +} + +type MyStructBeta struct { + TypeMeta int + + // +k8s:beta=+k8s:zeroOrOneOfMember + // +k8s:optional + Z1Beta *BetaZ1 `json:"z1Beta"` + + // +k8s:beta=+k8s:zeroOrOneOfMember + // +k8s:optional + Z2Beta *BetaZ2 `json:"z2Beta"` +} + +type D string + +const ( + DM1 D = "M1" + DM2 D = "M2" +) + +type M1 struct{} +type M2 struct{} + +type BetaD string + +const ( + BetaDM1 BetaD = "M1Beta" + BetaDM2 BetaD = "M2Beta" +) + +type BetaM1 struct{} +type BetaM2 struct{} + +type Z1 struct{} +type Z2 struct{} + +type BetaZ1 struct{} +type BetaZ2 struct{} + +type MyListStruct struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:alpha=+k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:alpha=+k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + Tasks []Task `json:"tasks"` +} + +type MyListStructBeta struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:beta=+k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:beta=+k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + TasksBeta []Task `json:"tasksBeta"` +} + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc_test.go new file mode 100644 index 0000000000000..a4aa873e75353 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2025 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 unions + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + D: DM1, + M1: &M1{}, + }).ExpectValid() + + st.Value(&Struct{ + D: DM1, + M1: nil, // required by discriminator + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "").WithOrigin("union").MarkAlpha(), + }) + + st.Value(&MyStruct{ + Z1: &Z1{}, + Z2: &Z2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(nil, &MyStruct{ + Z1: &Z1{}, Z2: &Z2{}, + }, "only one of z1, z2 may be specified").WithOrigin("zeroOrOneOf").MarkAlpha(), + }) + + st.Value(&MyListStruct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("tasks"), nil, "").WithOrigin("zeroOrOneOf").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&UnionStructBeta{ + DBeta: BetaDM1, + M1Beta: &BetaM1{}, + }).ExpectValid() + + st.Value(&UnionStructBeta{ + DBeta: BetaDM1, + M1Beta: nil, // required by discriminator + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("m1Beta"), nil, "").WithOrigin("union").MarkBeta(), + }) + + st.Value(&MyStructBeta{ + Z1Beta: &BetaZ1{}, + Z2Beta: &BetaZ2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(nil, &MyStructBeta{ + Z1Beta: &BetaZ1{}, Z2Beta: &BetaZ2{}, + }, "only one of z1Beta, z2Beta may be specified").WithOrigin("zeroOrOneOf").MarkBeta(), + }) + + st.Value(&MyListStructBeta{ + TasksBeta: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("tasksBeta"), nil, "").WithOrigin("zeroOrOneOf").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/zz_generated.validations.go new file mode 100644 index 0000000000000..7a7c65c7f3ecf --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/zz_generated.validations.go @@ -0,0 +1,410 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package unions + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MyListStruct + scheme.AddValidationFunc((*MyListStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyListStruct(ctx, op, nil /* fldPath */, obj.(*MyListStruct), safe.Cast[*MyListStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type MyListStructBeta + scheme.AddValidationFunc((*MyListStructBeta)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyListStructBeta(ctx, op, nil /* fldPath */, obj.(*MyListStructBeta), safe.Cast[*MyListStructBeta](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type MyStruct + scheme.AddValidationFunc((*MyStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyStruct(ctx, op, nil /* fldPath */, obj.(*MyStruct), safe.Cast[*MyStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type MyStructBeta + scheme.AddValidationFunc((*MyStructBeta)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyStructBeta(ctx, op, nil /* fldPath */, obj.(*MyStructBeta), safe.Cast[*MyStructBeta](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type UnionStructBeta + scheme.AddValidationFunc((*UnionStructBeta)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UnionStructBeta(ctx, op, nil /* fldPath */, obj.(*UnionStructBeta), safe.Cast[*UnionStructBeta](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStruct_tasks_ = validate.NewUnionMembership(validate.NewUnionMember("tasks[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("tasks[{\"name\": \"failed\"}]")) + +// Validate_MyListStruct validates an instance of MyListStruct according +// to declarative validation rules in the API schema. +func Validate_MyListStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MyListStruct) (errs field.ErrorList) { + // field MyListStruct.TypeMeta has no validation + + // field MyListStruct.Tasks + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []Task, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Task, b Task) bool { return a.Name == b.Name })...) + errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStruct_tasks_, func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }).MarkAlpha()...) + return + }(fldPath.Child("tasks"), obj.Tasks, safe.Field(oldObj, func(oldObj *MyListStruct) []Task { return oldObj.Tasks }), oldObj != nil)...) + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStructBeta_tasksBeta_ = validate.NewUnionMembership(validate.NewUnionMember("tasksBeta[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("tasksBeta[{\"name\": \"failed\"}]")) + +// Validate_MyListStructBeta validates an instance of MyListStructBeta according +// to declarative validation rules in the API schema. +func Validate_MyListStructBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MyListStructBeta) (errs field.ErrorList) { + // field MyListStructBeta.TypeMeta has no validation + + // field MyListStructBeta.TasksBeta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []Task, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with map semantics require unique keys + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Task, b Task) bool { return a.Name == b.Name })...) + errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStructBeta_tasksBeta_, func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }).MarkBeta()...) + return + }(fldPath.Child("tasksBeta"), obj.TasksBeta, safe.Field(oldObj, func(oldObj *MyListStructBeta) []Task { return oldObj.TasksBeta }), oldObj != nil)...) + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStruct_ = validate.NewUnionMembership(validate.NewUnionMember("z1"), validate.NewUnionMember("z2")) + +// Validate_MyStruct validates an instance of MyStruct according +// to declarative validation rules in the API schema. +func Validate_MyStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MyStruct) (errs field.ErrorList) { + errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStruct_, func(obj *MyStruct) bool { + if obj == nil { + return false + } + return obj.Z1 != nil + }, func(obj *MyStruct) bool { + if obj == nil { + return false + } + return obj.Z2 != nil + }).MarkAlpha()...) + + // field MyStruct.TypeMeta has no validation + + // field MyStruct.Z1 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Z1, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("z1"), obj.Z1, safe.Field(oldObj, func(oldObj *MyStruct) *Z1 { return oldObj.Z1 }), oldObj != nil)...) + + // field MyStruct.Z2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Z2, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("z2"), obj.Z2, safe.Field(oldObj, func(oldObj *MyStruct) *Z2 { return oldObj.Z2 }), oldObj != nil)...) + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStructBeta_ = validate.NewUnionMembership(validate.NewUnionMember("z1Beta"), validate.NewUnionMember("z2Beta")) + +// Validate_MyStructBeta validates an instance of MyStructBeta according +// to declarative validation rules in the API schema. +func Validate_MyStructBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MyStructBeta) (errs field.ErrorList) { + errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStructBeta_, func(obj *MyStructBeta) bool { + if obj == nil { + return false + } + return obj.Z1Beta != nil + }, func(obj *MyStructBeta) bool { + if obj == nil { + return false + } + return obj.Z2Beta != nil + }).MarkBeta()...) + + // field MyStructBeta.TypeMeta has no validation + + // field MyStructBeta.Z1Beta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *BetaZ1, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("z1Beta"), obj.Z1Beta, safe.Field(oldObj, func(oldObj *MyStructBeta) *BetaZ1 { return oldObj.Z1Beta }), oldObj != nil)...) + + // field MyStructBeta.Z2Beta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *BetaZ2, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("z2Beta"), obj.Z2Beta, safe.Field(oldObj, func(oldObj *MyStructBeta) *BetaZ2 { return oldObj.Z2Beta }), oldObj != nil)...) + + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + errs = append(errs, validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_Struct_, func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D) + }, func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }).MarkAlpha()...) + + // field Struct.TypeMeta has no validation + // field Struct.D has no validation + + // field Struct.M1 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *M1, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m1"), obj.M1, safe.Field(oldObj, func(oldObj *Struct) *M1 { return oldObj.M1 }), oldObj != nil)...) + + // field Struct.M2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *M2, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m2"), obj.M2, safe.Field(oldObj, func(oldObj *Struct) *M2 { return oldObj.M2 }), oldObj != nil)...) + + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_UnionStructBeta_ = validate.NewDiscriminatedUnionMembership("dBeta", validate.NewDiscriminatedUnionMember("m1Beta", "M1Beta"), validate.NewDiscriminatedUnionMember("m2Beta", "M2Beta")) + +// Validate_UnionStructBeta validates an instance of UnionStructBeta according +// to declarative validation rules in the API schema. +func Validate_UnionStructBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UnionStructBeta) (errs field.ErrorList) { + errs = append(errs, validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_UnionStructBeta_, func(obj *UnionStructBeta) string { + if obj == nil { + return "" + } + return string(obj.DBeta) + }, func(obj *UnionStructBeta) bool { + if obj == nil { + return false + } + return obj.M1Beta != nil + }, func(obj *UnionStructBeta) bool { + if obj == nil { + return false + } + return obj.M2Beta != nil + }).MarkBeta()...) + + // field UnionStructBeta.TypeMeta has no validation + // field UnionStructBeta.DBeta has no validation + + // field UnionStructBeta.M1Beta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *BetaM1, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m1Beta"), obj.M1Beta, safe.Field(oldObj, func(oldObj *UnionStructBeta) *BetaM1 { return oldObj.M1Beta }), oldObj != nil)...) + + // field UnionStructBeta.M2Beta + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *BetaM2, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m2Beta"), obj.M2Beta, safe.Field(oldObj, func(oldObj *UnionStructBeta) *BetaM2 { return oldObj.M2Beta }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc.go new file mode 100644 index 0000000000000..d205f05a3d6f7 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package uniquetag + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type UniqueStruct struct { + TypeMeta int + + // +k8s:listType=atomic + // +k8s:alpha=+k8s:unique=set + AlphaUniqueSet []string `json:"alphaUniqueSet"` + + // +k8s:listType=atomic + // +k8s:beta=+k8s:unique=set + BetaUniqueSet []string `json:"betaUniqueSet"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc_test.go new file mode 100644 index 0000000000000..a37747ed01f7a --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2025 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 uniquetag + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMisc(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&UniqueStruct{ + AlphaUniqueSet: []string{"a", "a"}, + BetaUniqueSet: []string{"a", "a"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Duplicate(field.NewPath("alphaUniqueSet").Index(1), "a").MarkAlpha(), + field.Duplicate(field.NewPath("betaUniqueSet").Index(1), "a").MarkBeta(), + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/zz_generated.validations.go new file mode 100644 index 0000000000000..d9f3bcf66c3a0 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/zz_generated.validations.go @@ -0,0 +1,84 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package uniquetag + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type UniqueStruct + scheme.AddValidationFunc((*UniqueStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UniqueStruct(ctx, op, nil /* fldPath */, obj.(*UniqueStruct), safe.Cast[*UniqueStruct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_UniqueStruct validates an instance of UniqueStruct according +// to declarative validation rules in the API schema. +func Validate_UniqueStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UniqueStruct) (errs field.ErrorList) { + // field UniqueStruct.TypeMeta has no validation + + // field UniqueStruct.AlphaUniqueSet + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...) + return + }(fldPath.Child("alphaUniqueSet"), obj.AlphaUniqueSet, safe.Field(oldObj, func(oldObj *UniqueStruct) []string { return oldObj.AlphaUniqueSet }), oldObj != nil)...) + + // field UniqueStruct.BetaUniqueSet + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + // lists with set semantics require unique values + errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta()...) + return + }(fldPath.Child("betaUniqueSet"), obj.BetaUniqueSet, safe.Field(oldObj, func(oldObj *UniqueStruct) []string { return oldObj.BetaUniqueSet }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go index faaf76dcc065b..fc3708e5a2364 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package multiplekeys import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go index 5627f4f7dafbc..1cdee714dceb1 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package singlekey import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go index cc52d0ed9238e..fa354445a0807 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package listset import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc.go new file mode 100644 index 0000000000000..79873c95df8cb --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc.go @@ -0,0 +1,81 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maxbytes + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maxBytes=0 + Max0Field string `json:"max0Field"` + + // +k8s:maxBytes=0 + Max0PtrField *string `json:"max0PtrField"` + + // +k8s:maxBytes=10 + Max10Field string `json:"max10Field"` + + // +k8s:maxBytes=10 + Max10PtrField *string `json:"max10PtrField"` + + // +k8s:maxBytes=0 + Max0UnvalidatedTypedefField UnvalidatedStringType `json:"max0UnvalidatedTypedefField"` + + // +k8s:maxBytes=0 + Max0UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"max0UnvalidatedTypedefPtrField"` + + // +k8s:maxBytes=10 + Max10UnvalidatedTypedefField UnvalidatedStringType `json:"max10UnvalidatedTypedefField"` + + // +k8s:maxBytes=10 + Max10UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"max10UnvalidatedTypedefPtrField"` + + // Note: no validation here + Max0ValidatedTypedefField Max0Type `json:"max0ValidatedTypedefField"` + + // Note: no validation here + Max0ValidatedTypedefPtrField *Max0Type `json:"max0ValidatedTypedefPtrField"` + + // Note: no validation here + Max10ValidatedTypedefField Max10Type `json:"max10ValidatedTypedefField"` + + // Note: no validation here + Max10ValidatedTypedefPtrField *Max10Type `json:"max10ValidatedTypedefPtrField"` +} + +// Note: no validation here +type UnvalidatedStringType string + +// This tests that markers on type definitions +// are pulled through the validations of fields +// that use the type definition. +// +k8s:maxBytes=0 +type Max0Type string + +// This tests that markers on type definitions +// are pulled through the validations of fields +// that use the type definition. +// +k8s:maxBytes=10 +type Max10Type string diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc_test.go new file mode 100644 index 0000000000000..47bc3f416d9be --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc_test.go @@ -0,0 +1,118 @@ +/* +Copyright 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 maxbytes + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 1), + Max10PtrField: ptr.To(strings.Repeat("x", 1)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 1)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 1))), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 9), + Max10PtrField: ptr.To(strings.Repeat("x", 9)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 9)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 9))), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 10), + Max10PtrField: ptr.To(strings.Repeat("x", 10)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 10)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 10))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 10)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 10))), + }).ExpectValid() + + testVal := &Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + } + st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("max0Field"), "", 0), + field.TooLong(field.NewPath("max0PtrField"), "", 0), + field.TooLong(field.NewPath("max10Field"), "", 10), + field.TooLong(field.NewPath("max10PtrField"), "", 10), + field.TooLong(field.NewPath("max0UnvalidatedTypedefField"), "", 0), + field.TooLong(field.NewPath("max0UnvalidatedTypedefPtrField"), "", 0), + field.TooLong(field.NewPath("max10UnvalidatedTypedefField"), "", 10), + field.TooLong(field.NewPath("max10UnvalidatedTypedefPtrField"), "", 10), + field.TooLong(field.NewPath("max0ValidatedTypedefField"), "", 0), + field.TooLong(field.NewPath("max0ValidatedTypedefPtrField"), "", 0), + field.TooLong(field.NewPath("max10ValidatedTypedefField"), "", 10), + field.TooLong(field.NewPath("max10ValidatedTypedefPtrField"), "", 10), + }) + + // Test validation ratcheting + st.Value(&Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + }).OldValue(&Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + }).ExpectValid() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/zz_generated.validations.go new file mode 100644 index 0000000000000..4f93d9945a886 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/zz_generated.validations.go @@ -0,0 +1,217 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maxbytes + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Max0Type validates an instance of Max0Type according +// to declarative validation rules in the API schema. +func Validate_Max0Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Max0Type) (errs field.ErrorList) { + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0)...) + + return errs +} + +// Validate_Max10Type validates an instance of Max10Type according +// to declarative validation rules in the API schema. +func Validate_Max10Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Max10Type) (errs field.ErrorList) { + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10)...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.Max0Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("max0Field"), &obj.Max0Field, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.Max0Field }), oldObj != nil)...) + + // field Struct.Max0PtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("max0PtrField"), obj.Max0PtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.Max0PtrField }), oldObj != nil)...) + + // field Struct.Max10Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("max10Field"), &obj.Max10Field, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.Max10Field }), oldObj != nil)...) + + // field Struct.Max10PtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("max10PtrField"), obj.Max10PtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.Max10PtrField }), oldObj != nil)...) + + // field Struct.Max0UnvalidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("max0UnvalidatedTypedefField"), &obj.Max0UnvalidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return &oldObj.Max0UnvalidatedTypedefField }), oldObj != nil)...) + + // field Struct.Max0UnvalidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("max0UnvalidatedTypedefPtrField"), obj.Max0UnvalidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return oldObj.Max0UnvalidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Max10UnvalidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("max10UnvalidatedTypedefField"), &obj.Max10UnvalidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return &oldObj.Max10UnvalidatedTypedefField }), oldObj != nil)...) + + // field Struct.Max10UnvalidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("max10UnvalidatedTypedefPtrField"), obj.Max10UnvalidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return oldObj.Max10UnvalidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Max0ValidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Max0Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("max0ValidatedTypedefField"), &obj.Max0ValidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *Max0Type { return &oldObj.Max0ValidatedTypedefField }), oldObj != nil)...) + + // field Struct.Max0ValidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Max0Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("max0ValidatedTypedefPtrField"), obj.Max0ValidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *Max0Type { return oldObj.Max0ValidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Max10ValidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Max10Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("max10ValidatedTypedefField"), &obj.Max10ValidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *Max10Type { return &oldObj.Max10ValidatedTypedefField }), oldObj != nil)...) + + // field Struct.Max10ValidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Max10Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("max10ValidatedTypedefPtrField"), obj.Max10ValidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *Max10Type { return oldObj.Max10ValidatedTypedefPtrField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc.go new file mode 100644 index 0000000000000..461f077225c65 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc.go @@ -0,0 +1,62 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maximum + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maximum=1 + IntField int `json:"intField"` + // +k8s:maximum=1 + IntPtrField *int `json:"intPtrField"` + + // "int8" becomes "byte" somewhere in gengo. We don't need it so just skip it. + + // +k8s:maximum=1 + Int16Field int16 `json:"int16Field"` + // +k8s:maximum=1 + Int32Field int32 `json:"int32Field"` + // +k8s:maximum=1 + Int64Field int64 `json:"int64Field"` + + // +k8s:maximum=1 + UintField uint `json:"uintField"` + // +k8s:maximum=1 + UintPtrField *uint `json:"uintPtrField"` + + // +k8s:maximum=1 + Uint16Field uint16 `json:"uint16Field"` + // +k8s:maximum=1 + Uint32Field uint32 `json:"uint32Field"` + // +k8s:maximum=1 + Uint64Field uint64 `json:"uint64Field"` + + TypedefField IntType `json:"typedefField"` + TypedefPtrField *IntType `json:"typedefPtrField"` +} + +// +k8s:maximum=1 +type IntType int diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc_test.go new file mode 100644 index 0000000000000..fcc154d0a474c --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2024 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 maximum + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // invalid values (greater than maximum=1) + IntField: 2, + IntPtrField: ptr.To(2), + Int16Field: 2, + Int32Field: 2, + Int64Field: 2, + UintField: 2, + Uint16Field: 2, + Uint32Field: 2, + Uint64Field: 2, + UintPtrField: ptr.To(uint(2)), + TypedefField: IntType(2), + TypedefPtrField: ptr.To(IntType(2)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), nil, ""), + field.Invalid(field.NewPath("intPtrField"), nil, ""), + field.Invalid(field.NewPath("int16Field"), nil, ""), + field.Invalid(field.NewPath("int32Field"), nil, ""), + field.Invalid(field.NewPath("int64Field"), nil, ""), + field.Invalid(field.NewPath("uintField"), nil, ""), + field.Invalid(field.NewPath("uintPtrField"), nil, ""), + field.Invalid(field.NewPath("uint16Field"), nil, ""), + field.Invalid(field.NewPath("uint32Field"), nil, ""), + field.Invalid(field.NewPath("uint64Field"), nil, ""), + field.Invalid(field.NewPath("typedefField"), nil, ""), + field.Invalid(field.NewPath("typedefPtrField"), nil, ""), + }) + // Test validation ratcheting + st.Value(&Struct{ + IntField: 2, + IntPtrField: ptr.To(2), + Int16Field: 2, + Int32Field: 2, + Int64Field: 2, + UintField: 2, + Uint16Field: 2, + Uint32Field: 2, + Uint64Field: 2, + UintPtrField: ptr.To(uint(2)), + TypedefField: IntType(2), + TypedefPtrField: ptr.To(IntType(2)), + }).OldValue(&Struct{ + IntField: 2, + IntPtrField: ptr.To(2), + Int16Field: 2, + Int32Field: 2, + Int64Field: 2, + UintField: 2, + Uint16Field: 2, + Uint32Field: 2, + Uint64Field: 2, + UintPtrField: ptr.To(uint(2)), + TypedefField: IntType(2), + TypedefPtrField: ptr.To(IntType(2)), + }).ExpectValid() + + st.Value(&Struct{ + IntField: 1, + IntPtrField: ptr.To(1), + Int16Field: 1, + Int32Field: 1, + Int64Field: 1, + UintField: 1, + Uint16Field: 1, + Uint32Field: 1, + Uint64Field: 1, + UintPtrField: ptr.To(uint(1)), + TypedefField: IntType(1), + TypedefPtrField: ptr.To(IntType(1)), + }).ExpectValid() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/zz_generated.validations.go new file mode 100644 index 0000000000000..000d3f02b0f0c --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/zz_generated.validations.go @@ -0,0 +1,209 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maximum + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_IntType validates an instance of IntType according +// to declarative validation rules in the API schema. +func Validate_IntType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *IntType) (errs field.ErrorList) { + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }), oldObj != nil)...) + + // field Struct.IntPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("intPtrField"), obj.IntPtrField, safe.Field(oldObj, func(oldObj *Struct) *int { return oldObj.IntPtrField }), oldObj != nil)...) + + // field Struct.Int16Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int16, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("int16Field"), &obj.Int16Field, safe.Field(oldObj, func(oldObj *Struct) *int16 { return &oldObj.Int16Field }), oldObj != nil)...) + + // field Struct.Int32Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("int32Field"), &obj.Int32Field, safe.Field(oldObj, func(oldObj *Struct) *int32 { return &oldObj.Int32Field }), oldObj != nil)...) + + // field Struct.Int64Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("int64Field"), &obj.Int64Field, safe.Field(oldObj, func(oldObj *Struct) *int64 { return &oldObj.Int64Field }), oldObj != nil)...) + + // field Struct.UintField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *uint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("uintField"), &obj.UintField, safe.Field(oldObj, func(oldObj *Struct) *uint { return &oldObj.UintField }), oldObj != nil)...) + + // field Struct.UintPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *uint, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("uintPtrField"), obj.UintPtrField, safe.Field(oldObj, func(oldObj *Struct) *uint { return oldObj.UintPtrField }), oldObj != nil)...) + + // field Struct.Uint16Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *uint16, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("uint16Field"), &obj.Uint16Field, safe.Field(oldObj, func(oldObj *Struct) *uint16 { return &oldObj.Uint16Field }), oldObj != nil)...) + + // field Struct.Uint32Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *uint32, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("uint32Field"), &obj.Uint32Field, safe.Field(oldObj, func(oldObj *Struct) *uint32 { return &oldObj.Uint32Field }), oldObj != nil)...) + + // field Struct.Uint64Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *uint64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.Maximum(ctx, op, fldPath, obj, oldObj, 1)...) + return + }(fldPath.Child("uint64Field"), &obj.Uint64Field, safe.Field(oldObj, func(oldObj *Struct) *uint64 { return &oldObj.Uint64Field }), oldObj != nil)...) + + // field Struct.TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("typedefField"), &obj.TypedefField, safe.Field(oldObj, func(oldObj *Struct) *IntType { return &oldObj.TypedefField }), oldObj != nil)...) + + // field Struct.TypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("typedefPtrField"), obj.TypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *IntType { return oldObj.TypedefPtrField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go index fd7f085c7cb11..cc59d3fa85941 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package sliceofprimitive import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go index b8bf98fc13ab9..80171009ae2a2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package sliceofstruct import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go index c8cc890c44527..674d28d711dff 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package typedeftoslice import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go index 6d3109a89671a..61d13b8d8ca62 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package maxlength import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go index 6524a47142583..88b489db08fd5 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go @@ -73,18 +73,18 @@ func Test(t *testing.T) { Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), } st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ - field.TooLong(field.NewPath("max0Field"), nil, 0), - field.TooLong(field.NewPath("max0PtrField"), nil, 0), - field.TooLong(field.NewPath("max10Field"), nil, 10), - field.TooLong(field.NewPath("max10PtrField"), nil, 10), - field.TooLong(field.NewPath("max0UnvalidatedTypedefField"), nil, 0), - field.TooLong(field.NewPath("max0UnvalidatedTypedefPtrField"), nil, 0), - field.TooLong(field.NewPath("max10UnvalidatedTypedefField"), nil, 10), - field.TooLong(field.NewPath("max10UnvalidatedTypedefPtrField"), nil, 10), - field.TooLong(field.NewPath("max0ValidatedTypedefField"), nil, 0), - field.TooLong(field.NewPath("max0ValidatedTypedefPtrField"), nil, 0), - field.TooLong(field.NewPath("max10ValidatedTypedefField"), nil, 10), - field.TooLong(field.NewPath("max10ValidatedTypedefPtrField"), nil, 10), + field.TooLongCharacters(field.NewPath("max0Field"), "", 0), + field.TooLongCharacters(field.NewPath("max0PtrField"), "", 0), + field.TooLongCharacters(field.NewPath("max10Field"), "", 10), + field.TooLongCharacters(field.NewPath("max10PtrField"), "", 10), + field.TooLongCharacters(field.NewPath("max0UnvalidatedTypedefField"), "", 0), + field.TooLongCharacters(field.NewPath("max0UnvalidatedTypedefPtrField"), "", 0), + field.TooLongCharacters(field.NewPath("max10UnvalidatedTypedefField"), "", 10), + field.TooLongCharacters(field.NewPath("max10UnvalidatedTypedefPtrField"), "", 10), + field.TooLongCharacters(field.NewPath("max0ValidatedTypedefField"), "", 0), + field.TooLongCharacters(field.NewPath("max0ValidatedTypedefPtrField"), "", 0), + field.TooLongCharacters(field.NewPath("max10ValidatedTypedefField"), "", 10), + field.TooLongCharacters(field.NewPath("max10ValidatedTypedefPtrField"), "", 10), }) // Test validation ratcheting diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go index 082320aef4e8b..cf0e089df3848 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package minimum import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc.go new file mode 100644 index 0000000000000..ba34ea6b57614 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc.go @@ -0,0 +1,44 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:minItems=0 + Min0Field []int `json:"min0Field"` + + // +k8s:minItems=10 + Min10Field []int `json:"min10Field"` + + // +k8s:minItems=0 + Min0TypedefField []IntType `json:"min0TypedefField"` + + // +k8s:minItems=10 + Min10TypedefField []IntType `json:"min10TypedefField"` +} + +type IntType int diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc_test.go new file mode 100644 index 0000000000000..12b092016d4c8 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2024 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 sliceofprimitive + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make([]int, 0), + Min10Field: make([]int, 0), + Min0TypedefField: make([]IntType, 0), + Min10TypedefField: make([]IntType, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]int, 1), + Min10TypedefField: make([]IntType, 1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]int, 9), + Min10TypedefField: make([]IntType, 9), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]int, 10), + Min10TypedefField: make([]IntType, 10), + }).ExpectValid() + + testVal := &Struct{ + Min0Field: make([]int, 1), + Min10Field: make([]int, 11), + Min0TypedefField: make([]IntType, 1), + Min10TypedefField: make([]IntType, 11), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: make([]int, 1), + Min10Field: make([]int, 1), + Min0TypedefField: make([]IntType, 1), + Min10TypedefField: make([]IntType, 1), + }).OldValue(&Struct{ + Min0Field: make([]int, 1), + Min10Field: make([]int, 1), + Min0TypedefField: make([]IntType, 1), + Min10TypedefField: make([]IntType, 1), + }).ExpectValid() + +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000000..bfe3ce23a4e96 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/zz_generated.validations.go @@ -0,0 +1,106 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.Min0Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("min0Field"), obj.Min0Field, safe.Field(oldObj, func(oldObj *Struct) []int { return oldObj.Min0Field }), oldObj != nil)...) + + // field Struct.Min10Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("min10Field"), obj.Min10Field, safe.Field(oldObj, func(oldObj *Struct) []int { return oldObj.Min10Field }), oldObj != nil)...) + + // field Struct.Min0TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, safe.Field(oldObj, func(oldObj *Struct) []IntType { return oldObj.Min0TypedefField }), oldObj != nil)...) + + // field Struct.Min10TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, safe.Field(oldObj, func(oldObj *Struct) []IntType { return oldObj.Min10TypedefField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc.go new file mode 100644 index 0000000000000..29c71e494ab9c --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc.go @@ -0,0 +1,46 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:minItems=0 + Min0Field []OtherStruct `json:"min0Field"` + + // +k8s:minItems=10 + Min10Field []OtherStruct `json:"min10Field"` + + // +k8s:minItems=0 + Min0TypedefField []OtherTypedefStruct `json:"min0TypedefField"` + + // +k8s:minItems=10 + Min10TypedefField []OtherTypedefStruct `json:"min10TypedefField"` +} + +type OtherStruct struct{} + +type OtherTypedefStruct OtherStruct diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc_test.go new file mode 100644 index 0000000000000..110b2937614b5 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2024 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 sliceofstruct + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make([]OtherStruct, 0), + Min10Field: make([]OtherStruct, 0), + Min0TypedefField: make([]OtherTypedefStruct, 0), + Min10TypedefField: make([]OtherTypedefStruct, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]OtherStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]OtherStruct, 9), + Min10TypedefField: make([]OtherTypedefStruct, 9), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]OtherStruct, 10), + Min10TypedefField: make([]OtherTypedefStruct, 10), + }).ExpectValid() + + testVal := &Struct{ + Min0Field: make([]OtherStruct, 1), + Min10Field: make([]OtherStruct, 11), + Min0TypedefField: make([]OtherTypedefStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 11), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: make([]OtherStruct, 1), + Min10Field: make([]OtherStruct, 1), + Min0TypedefField: make([]OtherTypedefStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 1), + }).OldValue(&Struct{ + Min0Field: make([]OtherStruct, 1), + Min10Field: make([]OtherStruct, 1), + Min0TypedefField: make([]OtherTypedefStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 1), + }).ExpectValid() + +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000000..8e1c61b0d1be2 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/zz_generated.validations.go @@ -0,0 +1,106 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.Min0Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []OtherStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("min0Field"), obj.Min0Field, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.Min0Field }), oldObj != nil)...) + + // field Struct.Min10Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []OtherStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("min10Field"), obj.Min10Field, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.Min10Field }), oldObj != nil)...) + + // field Struct.Min0TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []OtherTypedefStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 0)...) + return + }(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, safe.Field(oldObj, func(oldObj *Struct) []OtherTypedefStruct { return oldObj.Min0TypedefField }), oldObj != nil)...) + + // field Struct.Min10TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []OtherTypedefStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 10)...) + return + }(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, safe.Field(oldObj, func(oldObj *Struct) []OtherTypedefStruct { return oldObj.Min10TypedefField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc.go new file mode 100644 index 0000000000000..9c4990b910b6d --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftoslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType []int + +// +k8s:minItems=0 +type Min0Type []int + +// +k8s:minItems=10 +type Min10Type []int + +// Note: no validation here +type UnvalidatedPtrType []*int + +type SliceType []int + +// +k8s:minItems=0 +type Min0TypedefType SliceType + +// +k8s:minItems=10 +type Min10TypedefType SliceType + +type Struct struct { + TypeMeta int + + UnvalidatedField UnvalidatedType `json:"unvalidatedField"` + + Min0Field Min0Type `json:"min0Field"` + + Min10Field Min10Type `json:"min10Field"` + + Min0TypedefField Min0TypedefType `json:"min0TypedefField"` + + Min10TypedefField Min10TypedefType `json:"min10TypedefField"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc_test.go new file mode 100644 index 0000000000000..e82ebcb7e3d76 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2024 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 typedeftoslice + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make(Min0Type, 0), + Min10Field: make(Min10Type, 0), + Min0TypedefField: make(Min0TypedefType, 0), + Min10TypedefField: make(Min10TypedefType, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min10Field: make(Min10Type, 1), + Min10TypedefField: make(Min10TypedefType, 1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + st.Value(&Struct{ + Min10Field: make(Min10Type, 9), + Min10TypedefField: make(Min10TypedefType, 9), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + st.Value(&Struct{ + Min10Field: make(Min10Type, 10), + Min10TypedefField: make(Min10TypedefType, 10), + }).ExpectValid() + + testVal := &Struct{ + Min0Field: make(Min0Type, 1), + Min10Field: make(Min10Type, 11), + Min0TypedefField: make(Min0TypedefType, 1), + Min10TypedefField: make(Min10TypedefType, 11), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: make(Min0Type, 1), + Min10Field: make(Min10Type, 1), + Min0TypedefField: make(Min0TypedefType, 1), + Min10TypedefField: make(Min10TypedefType, 1), + }).OldValue(&Struct{ + Min0Field: make(Min0Type, 1), + Min10Field: make(Min10Type, 1), + Min0TypedefField: make(Min0TypedefType, 1), + Min10TypedefField: make(Min10TypedefType, 1), + }).ExpectValid() + +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/zz_generated.validations.go new file mode 100644 index 0000000000000..ef980137c7939 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/zz_generated.validations.go @@ -0,0 +1,139 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftoslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Min0Type validates an instance of Min0Type according +// to declarative validation rules in the API schema. +func Validate_Min0Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Min0Type) (errs field.ErrorList) { + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 0)...) + + return errs +} + +// Validate_Min0TypedefType validates an instance of Min0TypedefType according +// to declarative validation rules in the API schema. +func Validate_Min0TypedefType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Min0TypedefType) (errs field.ErrorList) { + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 0)...) + + return errs +} + +// Validate_Min10Type validates an instance of Min10Type according +// to declarative validation rules in the API schema. +func Validate_Min10Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Min10Type) (errs field.ErrorList) { + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 10)...) + + return errs +} + +// Validate_Min10TypedefType validates an instance of Min10TypedefType according +// to declarative validation rules in the API schema. +func Validate_Min10TypedefType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Min10TypedefType) (errs field.ErrorList) { + errs = append(errs, validate.MinItems(ctx, op, fldPath, obj, oldObj, 10)...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + // field Struct.UnvalidatedField has no validation + + // field Struct.Min0Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj Min0Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Min0Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min0Field"), obj.Min0Field, safe.Field(oldObj, func(oldObj *Struct) Min0Type { return oldObj.Min0Field }), oldObj != nil)...) + + // field Struct.Min10Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj Min10Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Min10Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min10Field"), obj.Min10Field, safe.Field(oldObj, func(oldObj *Struct) Min10Type { return oldObj.Min10Field }), oldObj != nil)...) + + // field Struct.Min0TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj Min0TypedefType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Min0TypedefType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, safe.Field(oldObj, func(oldObj *Struct) Min0TypedefType { return oldObj.Min0TypedefField }), oldObj != nil)...) + + // field Struct.Min10TypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj Min10TypedefType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_Min10TypedefType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, safe.Field(oldObj, func(oldObj *Struct) Min10TypedefType { return oldObj.Min10TypedefField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc.go new file mode 100644 index 0000000000000..945bb5f4634ea --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc.go @@ -0,0 +1,105 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +package minlength + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:minLength=0 + // +k8s:optional + Min0PtrField *string `json:"min0PtrField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2Field string `json:"min2Field"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2PtrField *string `json:"min2PtrField"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10Field string `json:"min10Field"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10PtrField *string `json:"min10PtrField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedTypedefField UnvalidatedStringType `json:"min2UnvalidatedTypedefField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"min2UnvalidatedTypedefPtrField"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10UnvalidatedTypedefField UnvalidatedStringType `json:"min10UnvalidatedTypedefField"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"min10UnvalidatedTypedefPtrField"` + + // Note: no minlength validation here + // +k8s:optional + Min2ValidatedTypedefField Min2Type `json:"min2ValidatedTypedefField"` + + // Note: no minlength validation here + // +k8s:optional + Min2ValidatedTypedefPtrField *Min2Type `json:"min2ValidatedTypedefPtrField"` + + // Note: no minlength validation here + // +k8s:optional + Min10ValidatedTypedefField Min10Type `json:"min10ValidatedTypedefField"` + + // Note: no minlength validation here + // +k8s:optional + Min10ValidatedTypedefPtrField *Min10Type `json:"min10ValidatedTypedefPtrField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedStringAliasField UnvalidatedStringAlias `json:"min2UnvalidatedStringAliasField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedStringAliasPtrField *UnvalidatedStringAlias `json:"min2UnvalidatedStringAliasPtrField"` +} + +// Note: no validation here +type UnvalidatedStringType string + +// Note: no validation here +type UnvalidatedStringAlias = string + +// Tests that min length markers on typedefs +// appropriately propagate to fields that use this type +// +k8s:alpha=+k8s:minLength=2 +type Min2Type string + +// Tests that min length markers on typedefs +// appropriately propagate to fields that use this type +// +k8s:alpha=+k8s:minLength=10 +type Min10Type string diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc_test.go new file mode 100644 index 0000000000000..408a47cc63c1f --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc_test.go @@ -0,0 +1,160 @@ +/* +Copyright 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 minlength + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 1), + Min2PtrField: ptr.To(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 1)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 1))), + Min10Field: strings.Repeat("x", 1), + Min10PtrField: ptr.To(strings.Repeat("x", 1)), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 1)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 1))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 1), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooShort(field.NewPath("min10Field"), "", 10), + field.TooShort(field.NewPath("min10PtrField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min2Field"), "x", 2), + field.TooShort(field.NewPath("min2PtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasPtrField"), "x", 2), + }) + + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 1), + Min2PtrField: ptr.To(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 1)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 1))), + Min10Field: strings.Repeat("x", 9), + Min10PtrField: ptr.To(strings.Repeat("x", 9)), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 9)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 9))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 1), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooShort(field.NewPath("min10Field"), "", 10), + field.TooShort(field.NewPath("min10PtrField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min2Field"), "x", 2), + field.TooShort(field.NewPath("min2PtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasPtrField"), "x", 2), + }) + + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 2), + Min2PtrField: ptr.To(strings.Repeat("x", 2)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 2)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 2))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 2)), + Min10Field: strings.Repeat("x", 10), + Min10PtrField: ptr.To(strings.Repeat("x", 10)), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 10)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 10))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 10)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 10))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 2), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 2)), + }).ExpectValid() + + testVal := &Struct{ + Min2Field: strings.Repeat("x", 3), + Min2PtrField: ptr.To(strings.Repeat("x", 3)), + Min10Field: strings.Repeat("x", 11), + Min10PtrField: ptr.To(strings.Repeat("x", 11)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 3)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 3))), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 3)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 3))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 11)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 11))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 3), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 3)), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 2), + Min2PtrField: ptr.To(strings.Repeat("x", 2)), + Min10Field: strings.Repeat("x", 11), + Min10PtrField: ptr.To(strings.Repeat("x", 11)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 2)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 2))), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 2)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 2))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 11)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 11))), + }).OldValue(&Struct{ + Min2Field: strings.Repeat("x", 1), + Min2PtrField: ptr.To(strings.Repeat("x", 1)), + Min10Field: strings.Repeat("x", 9), + Min10PtrField: ptr.To(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 1)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 1))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 9)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 9))), + }).ExpectValid() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/zz_generated.validations.go new file mode 100644 index 0000000000000..45b2ffe61d77d --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/zz_generated.validations.go @@ -0,0 +1,361 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package minlength + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Min10Type validates an instance of Min10Type according +// to declarative validation rules in the API schema. +func Validate_Min10Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Min10Type) (errs field.ErrorList) { + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + + return errs +} + +// Validate_Min2Type validates an instance of Min2Type according +// to declarative validation rules in the API schema. +func Validate_Min2Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Min2Type) (errs field.ErrorList) { + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.Min0PtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("min0PtrField"), obj.Min0PtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.Min0PtrField }), oldObj != nil)...) + + // field Struct.Min2Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + return + }(fldPath.Child("min2Field"), &obj.Min2Field, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.Min2Field }), oldObj != nil)...) + + // field Struct.Min2PtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + return + }(fldPath.Child("min2PtrField"), obj.Min2PtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.Min2PtrField }), oldObj != nil)...) + + // field Struct.Min10Field + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("min10Field"), &obj.Min10Field, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.Min10Field }), oldObj != nil)...) + + // field Struct.Min10PtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("min10PtrField"), obj.Min10PtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.Min10PtrField }), oldObj != nil)...) + + // field Struct.Min2UnvalidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + return + }(fldPath.Child("min2UnvalidatedTypedefField"), &obj.Min2UnvalidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return &oldObj.Min2UnvalidatedTypedefField }), oldObj != nil)...) + + // field Struct.Min2UnvalidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + return + }(fldPath.Child("min2UnvalidatedTypedefPtrField"), obj.Min2UnvalidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return oldObj.Min2UnvalidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Min10UnvalidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("min10UnvalidatedTypedefField"), &obj.Min10UnvalidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return &oldObj.Min10UnvalidatedTypedefField }), oldObj != nil)...) + + // field Struct.Min10UnvalidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *UnvalidatedStringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...) + return + }(fldPath.Child("min10UnvalidatedTypedefPtrField"), obj.Min10UnvalidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *UnvalidatedStringType { return oldObj.Min10UnvalidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Min2ValidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Min2Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min2Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min2ValidatedTypedefField"), &obj.Min2ValidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *Min2Type { return &oldObj.Min2ValidatedTypedefField }), oldObj != nil)...) + + // field Struct.Min2ValidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Min2Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min2Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min2ValidatedTypedefPtrField"), obj.Min2ValidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *Min2Type { return oldObj.Min2ValidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Min10ValidatedTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Min10Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min10Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min10ValidatedTypedefField"), &obj.Min10ValidatedTypedefField, safe.Field(oldObj, func(oldObj *Struct) *Min10Type { return &oldObj.Min10ValidatedTypedefField }), oldObj != nil)...) + + // field Struct.Min10ValidatedTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *Min10Type, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min10Type(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("min10ValidatedTypedefPtrField"), obj.Min10ValidatedTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *Min10Type { return oldObj.Min10ValidatedTypedefPtrField }), oldObj != nil)...) + + // field Struct.Min2UnvalidatedStringAliasField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + return + }(fldPath.Child("min2UnvalidatedStringAliasField"), &obj.Min2UnvalidatedStringAliasField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.Min2UnvalidatedStringAliasField }), oldObj != nil)...) + + // field Struct.Min2UnvalidatedStringAliasPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()...) + return + }(fldPath.Child("min2UnvalidatedStringAliasPtrField"), obj.Min2UnvalidatedStringAliasPtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.Min2UnvalidatedStringAliasPtrField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go index c681e7290bae7..7808505bd4f70 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package for complex neq compositions. +// +k8s:validation-gen-nolint package neqchained import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go index 523389eb8e425..594b183be9327 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package neqbool import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go index 400f234485cb3..8a8ae6a703863 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package neqint import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go index 8850cb63a2ab3..df35eb8c676f3 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package neqstring import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go index 08a5705f609cb..216852a43bc6a 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package opaque import ( diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go index 1fefdd637c8bb..93dc53e40cd19 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package optional import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go index d5206da9733d5..27824cb9d5725 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package nonzerodefaults import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go index 71d0a5e7c1d87..e947e631dd7e1 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package zerodefaults import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/doc.go index 45f95f7b072b6..da07e0d0ea1cc 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package options import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc.go new file mode 100644 index 0000000000000..5a2b1102e79ab --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc.go @@ -0,0 +1,115 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package required + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:required + // +k8s:validateFalse="field Struct.StringField" + StringField string `json:"stringField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.StringPtrField" + StringPtrField *string `json:"stringPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.StringTypedefField" + StringTypedefField StringType `json:"stringTypedefField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.StringTypedefPtrField" + StringTypedefPtrField *StringType `json:"stringTypedefPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntField" + IntField int `json:"intField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntPtrField" + IntPtrField *int `json:"intPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntTypedefField" + IntTypedefField IntType `json:"intTypedefField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntTypedefPtrField" + IntTypedefPtrField *IntType `json:"intTypedefPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.BoolField" + BoolField bool `json:"boolField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.FloatField" + FloatField float64 `json:"floatField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.ByteField" + ByteField byte `json:"byteField"` + + // non-pointer struct fields cannot be required or optional + + // +k8s:required + // +k8s:validateFalse="field Struct.OtherStructPtrField" + OtherStructPtrField *OtherStruct `json:"otherStructPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.SliceField" + SliceField []string `json:"sliceField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.SliceTypedefField" + SliceTypedefField SliceType `json:"sliceTypedefField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.ByteArrayField" + ByteArrayField []byte `json:"byteArrayField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.MapField" + MapField map[string]string `json:"mapField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.MapTypedefField" + MapTypedefField MapType `json:"mapTypedefField"` +} + +// +k8s:validateFalse="type StringType" +type StringType string + +// +k8s:validateFalse="type IntType" +type IntType int + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct{} + +// +k8s:validateFalse="type SliceType" +type SliceType []string + +// +k8s:validateFalse="type MapType" +type MapType map[string]string diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc_test.go new file mode 100644 index 0000000000000..175a8c3fd333d --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc_test.go @@ -0,0 +1,127 @@ +/* +Copyright 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 required + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values (nil slices/maps). + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("stringField"), ""), + field.Required(field.NewPath("stringPtrField"), ""), + field.Required(field.NewPath("stringTypedefField"), ""), + field.Required(field.NewPath("stringTypedefPtrField"), ""), + field.Required(field.NewPath("intField"), ""), + field.Required(field.NewPath("intPtrField"), ""), + field.Required(field.NewPath("intTypedefField"), ""), + field.Required(field.NewPath("intTypedefPtrField"), ""), + field.Required(field.NewPath("boolField"), ""), + field.Required(field.NewPath("floatField"), ""), + field.Required(field.NewPath("byteField"), ""), + field.Required(field.NewPath("otherStructPtrField"), ""), + field.Required(field.NewPath("sliceField"), ""), + field.Required(field.NewPath("sliceTypedefField"), ""), + field.Required(field.NewPath("byteArrayField"), ""), + field.Required(field.NewPath("mapField"), ""), + field.Required(field.NewPath("mapTypedefField"), ""), + }) + + st.Value(&Struct{ + // Explicit zero-values and empty slices/maps. + StringField: "", + StringPtrField: nil, + StringTypedefField: "", + StringTypedefPtrField: nil, + IntField: 0, + IntPtrField: nil, + IntTypedefField: 0, + IntTypedefPtrField: nil, + BoolField: false, + FloatField: 0.0, + ByteField: 0, + OtherStructPtrField: nil, + SliceField: []string{}, + SliceTypedefField: SliceType{}, + ByteArrayField: []byte{}, + MapField: map[string]string{}, + MapTypedefField: MapType{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("stringField"), ""), + field.Required(field.NewPath("stringPtrField"), ""), + field.Required(field.NewPath("stringTypedefField"), ""), + field.Required(field.NewPath("stringTypedefPtrField"), ""), + field.Required(field.NewPath("intField"), ""), + field.Required(field.NewPath("intPtrField"), ""), + field.Required(field.NewPath("intTypedefField"), ""), + field.Required(field.NewPath("intTypedefPtrField"), ""), + field.Required(field.NewPath("boolField"), ""), + field.Required(field.NewPath("floatField"), ""), + field.Required(field.NewPath("byteField"), ""), + field.Required(field.NewPath("otherStructPtrField"), ""), + field.Required(field.NewPath("sliceField"), ""), + field.Required(field.NewPath("sliceTypedefField"), ""), + field.Required(field.NewPath("byteArrayField"), ""), + field.Required(field.NewPath("mapField"), ""), + field.Required(field.NewPath("mapTypedefField"), ""), + }) + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To("xyz"), + StringTypedefField: StringType("abc"), + StringTypedefPtrField: ptr.To(StringType("xyz")), + IntField: 123, + IntPtrField: ptr.To(456), + IntTypedefField: IntType(123), + IntTypedefPtrField: ptr.To(IntType(456)), + BoolField: true, + FloatField: 1.23, + ByteField: 'a', + OtherStructPtrField: &OtherStruct{}, + SliceField: []string{"a", "b"}, + SliceTypedefField: SliceType([]string{"a", "b"}), + ByteArrayField: []byte("abc"), + MapField: map[string]string{"a": "b", "c": "d"}, + MapTypedefField: MapType(map[string]string{"a": "b", "c": "d"}), + }).ExpectValidateFalseByPath(map[string][]string{ + "stringField": {"field Struct.StringField"}, + "stringPtrField": {"field Struct.StringPtrField"}, + "stringTypedefField": {"field Struct.StringTypedefField", "type StringType"}, + "stringTypedefPtrField": {"field Struct.StringTypedefPtrField", "type StringType"}, + "intField": {"field Struct.IntField"}, + "intPtrField": {"field Struct.IntPtrField"}, + "intTypedefField": {"field Struct.IntTypedefField", "type IntType"}, + "intTypedefPtrField": {"field Struct.IntTypedefPtrField", "type IntType"}, + "boolField": {"field Struct.BoolField"}, + "floatField": {"field Struct.FloatField"}, + "byteField": {"field Struct.ByteField"}, + "otherStructPtrField": {"type OtherStruct", "field Struct.OtherStructPtrField"}, + "sliceField": {"field Struct.SliceField"}, + "sliceTypedefField": {"field Struct.SliceTypedefField", "type SliceType"}, + "byteArrayField": {"field Struct.ByteArrayField"}, + "mapField": {"field Struct.MapField"}, + "mapTypedefField": {"field Struct.MapTypedefField", "type MapType"}, + }) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/zz_generated.validations.go new file mode 100644 index 0000000000000..74c1438291677 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/zz_generated.validations.go @@ -0,0 +1,452 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package required + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_IntType validates an instance of IntType according +// to declarative validation rules in the API schema. +func Validate_IntType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *IntType) (errs field.ErrorList) { + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type IntType")...) + + return errs +} + +// Validate_MapType validates an instance of MapType according +// to declarative validation rules in the API schema. +func Validate_MapType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj MapType) (errs field.ErrorList) { + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapType")...) + + return errs +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) (errs field.ErrorList) { + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct")...) + + return errs +} + +// Validate_SliceType validates an instance of SliceType according +// to declarative validation rules in the API schema. +func Validate_SliceType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj SliceType) (errs field.ErrorList) { + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type SliceType")...) + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) (errs field.ErrorList) { + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType")...) + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.StringField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringField")...) + return + }(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }), oldObj != nil)...) + + // field Struct.StringPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringPtrField")...) + return + }(fldPath.Child("stringPtrField"), obj.StringPtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.StringPtrField }), oldObj != nil)...) + + // field Struct.StringTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *StringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringTypedefField")...) + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("stringTypedefField"), &obj.StringTypedefField, safe.Field(oldObj, func(oldObj *Struct) *StringType { return &oldObj.StringTypedefField }), oldObj != nil)...) + + // field Struct.StringTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *StringType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringTypedefPtrField")...) + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("stringTypedefPtrField"), obj.StringTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *StringType { return oldObj.StringTypedefPtrField }), oldObj != nil)...) + + // field Struct.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntField")...) + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }), oldObj != nil)...) + + // field Struct.IntPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntPtrField")...) + return + }(fldPath.Child("intPtrField"), obj.IntPtrField, safe.Field(oldObj, func(oldObj *Struct) *int { return oldObj.IntPtrField }), oldObj != nil)...) + + // field Struct.IntTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntTypedefField")...) + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("intTypedefField"), &obj.IntTypedefField, safe.Field(oldObj, func(oldObj *Struct) *IntType { return &oldObj.IntTypedefField }), oldObj != nil)...) + + // field Struct.IntTypedefPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *IntType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntTypedefPtrField")...) + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("intTypedefPtrField"), obj.IntTypedefPtrField, safe.Field(oldObj, func(oldObj *Struct) *IntType { return oldObj.IntTypedefPtrField }), oldObj != nil)...) + + // field Struct.BoolField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.BoolField")...) + return + }(fldPath.Child("boolField"), &obj.BoolField, safe.Field(oldObj, func(oldObj *Struct) *bool { return &oldObj.BoolField }), oldObj != nil)...) + + // field Struct.FloatField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *float64, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.FloatField")...) + return + }(fldPath.Child("floatField"), &obj.FloatField, safe.Field(oldObj, func(oldObj *Struct) *float64 { return &oldObj.FloatField }), oldObj != nil)...) + + // field Struct.ByteField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *byte, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ByteField")...) + return + }(fldPath.Child("byteField"), &obj.ByteField, safe.Field(oldObj, func(oldObj *Struct) *byte { return &oldObj.ByteField }), oldObj != nil)...) + + // field Struct.OtherStructPtrField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *OtherStruct, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.OtherStructPtrField")...) + // call the type's validation function + errs = append(errs, Validate_OtherStruct(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("otherStructPtrField"), obj.OtherStructPtrField, safe.Field(oldObj, func(oldObj *Struct) *OtherStruct { return oldObj.OtherStructPtrField }), oldObj != nil)...) + + // field Struct.SliceField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceField")...) + return + }(fldPath.Child("sliceField"), obj.SliceField, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceField }), oldObj != nil)...) + + // field Struct.SliceTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj SliceType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceTypedefField")...) + // call the type's validation function + errs = append(errs, Validate_SliceType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("sliceTypedefField"), obj.SliceTypedefField, safe.Field(oldObj, func(oldObj *Struct) SliceType { return oldObj.SliceTypedefField }), oldObj != nil)...) + + // field Struct.ByteArrayField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []byte, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ByteArrayField")...) + return + }(fldPath.Child("byteArrayField"), obj.ByteArrayField, safe.Field(oldObj, func(oldObj *Struct) []byte { return oldObj.ByteArrayField }), oldObj != nil)...) + + // field Struct.MapField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField")...) + return + }(fldPath.Child("mapField"), obj.MapField, safe.Field(oldObj, func(oldObj *Struct) map[string]string { return oldObj.MapField }), oldObj != nil)...) + + // field Struct.MapTypedefField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj MapType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField")...) + // call the type's validation function + errs = append(errs, Validate_MapType(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("mapTypedefField"), obj.MapTypedefField, safe.Field(oldObj, func(oldObj *Struct) MapType { return oldObj.MapTypedefField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go index 84dc92b4d5a4c..3eb0f6af65888 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // Package subfield contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint package deep import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go index 050ea951cc9f4..d1814e8b607aa 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // Package nonincluded contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint package nonincluded import ( diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go index 91259e34090b7..f2f9c9e79ed7b 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // Package subfield contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint package shallow import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go index 27687c73b4fbc..df6c6a5519852 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package issubresource import "k8s.io/code-generator/cmd/validation-gen/testscheme" @@ -27,7 +28,7 @@ var localSchemeBuilder = testscheme.New() // Root resource is supported by default -// +k8s:isSubresource=/scale +// +k8s:isSubresource="/scale" // T1 is a test type type T1 struct { diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go index f67924b2e87a0..ee0d2c610af70 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package root import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go index c164f71809b28..5b4289316dd9c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package subresource import "k8s.io/code-generator/cmd/validation-gen/testscheme" @@ -27,9 +28,9 @@ var localSchemeBuilder = testscheme.New() // Root resource is supported by default -// +k8s:supportsSubresource=/status -// +k8s:supportsSubresource=/scale -// +k8s:supportsSubresource=/x/y +// +k8s:supportsSubresource="/status" +// +k8s:supportsSubresource="/scale" +// +k8s:supportsSubresource="/x/y" // T1 is a test type type T1 struct { diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go index 268323369503d..9cdad34a81f06 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package custommembers import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go index bd5487edc9e19..ce7fe0425c1e6 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go @@ -30,11 +30,11 @@ func Test(t *testing.T) { st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("m1"), nil, "may only be specified when"), - }) + }.WithOrigin("union")) st.Value(&Struct{D: DM1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("m1"), nil, "must be specified when"), - }) + }.WithOrigin("union")) // Test validation ratcheting st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).OldValue(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectValid() diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go index b220680eb6871..2ecbc2247d112 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package empty import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go index f6590318c032a..874a9fea1b5eb 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package multiple import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go index 4d1ad466f80fa..3cb1e0c404eb9 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go @@ -43,7 +43,7 @@ func Test(t *testing.T) { }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("u1m1"), nil, "may only be specified when"), field.Invalid(field.NewPath("u2m2"), nil, "must be specified when"), - }) + }.WithOrigin("union")) st.Value(&Struct{ D1: U1M2, // no value @@ -51,7 +51,7 @@ func Test(t *testing.T) { }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("u1m2"), nil, "must be specified when"), field.Invalid(field.NewPath("u2m1"), nil, "may only be specified when"), - }) + }.WithOrigin("union")) st.Value(&Struct{ D1: U1M2, // no value @@ -59,7 +59,7 @@ func Test(t *testing.T) { }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("u1m2"), nil, "must be specified when"), field.Invalid(field.NewPath("u2m2"), nil, "must be specified when"), - }) + }.WithOrigin("union")) // Test validation ratcheting st.Value(&Struct{ diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go index 178676a9edced..4dbf62b83305c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package simple import "k8s.io/code-generator/cmd/validation-gen/testscheme" @@ -38,6 +39,14 @@ type Struct struct { // +k8s:unionMember // +k8s:optional M2 *M2 `json:"m2"` + + // +k8s:unionMember + // +k8s:optional + M3 []string `json:"m3"` + + // +k8s:unionMember + // +k8s:optional + M4 map[string]string `json:"m4"` } type D string @@ -45,6 +54,8 @@ type D string const ( DM1 D = "M1" DM2 D = "M2" + DM3 D = "M3" + DM4 D = "M4" ) type M1 struct{} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go index bd2885ffb04cf..5adfa7f0c894e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go @@ -29,16 +29,66 @@ func Test(t *testing.T) { st.Value(&Struct{D: DM1, M1: &M1{}}).ExpectValid() st.Value(&Struct{D: DM2, M2: &M2{}}).ExpectValid() + st.Value(&Struct{D: DM3, M3: []string{"a"}}).ExpectValid() + st.Value(&Struct{D: DM4, M4: map[string]string{"k": "v"}}).ExpectValid() st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("m1"), nil, "may only be specified when"), - }) + }.WithOrigin("union")) st.Value(&Struct{D: DM1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("m1"), nil, "must be specified when"), - }) + }.WithOrigin("union")) + + // Slice member: discriminator matches but slice is nil + st.Value(&Struct{D: DM3}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "must be specified when"), + }.WithOrigin("union")) + // Slice member: discriminator matches but slice is empty (len==0, not nil) + st.Value(&Struct{D: DM3, M3: []string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Slice member: discriminator doesn't match but slice is set + st.Value(&Struct{D: DM1, M1: &M1{}, M3: []string{"a"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "may only be specified when"), + }.WithOrigin("union")) + + // Map member: discriminator matches but map is nil + st.Value(&Struct{D: DM4}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "must be specified when"), + }.WithOrigin("union")) + // Map member: discriminator matches but map is empty (len==0, not nil) + st.Value(&Struct{D: DM4, M4: map[string]string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Map member: discriminator doesn't match but map is set + st.Value(&Struct{D: DM1, M1: &M1{}, M4: map[string]string{"k": "v"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "may only be specified when"), + }.WithOrigin("union")) // Test validation ratcheting st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).OldValue(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectValid() st.Value(&Struct{D: DM1}).OldValue(&Struct{D: DM1}).ExpectValid() + + // Slice member ratcheting: unchanged membership + st.Value(&Struct{D: DM3, M3: []string{"a"}}).OldValue(&Struct{D: DM3, M3: []string{"b"}}).ExpectValid() + // Slice member ratcheting: changed from set to unset + st.Value(&Struct{D: DM3}).OldValue(&Struct{D: DM3, M3: []string{"a"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Map member ratcheting: unchanged membership + st.Value(&Struct{D: DM4, M4: map[string]string{"k": "v1"}}).OldValue(&Struct{D: DM4, M4: map[string]string{"k": "v2"}}).ExpectValid() + // Map member ratcheting: changed from set to unset + st.Value(&Struct{D: DM4}).OldValue(&Struct{D: DM4, M4: map[string]string{"k": "v"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Test update with nil old value (simulates newly-set pointer field during update). + // Discriminated union validation should still detect mismatches even though oldObj is nil. + st.Value(&Struct{D: DM1}).OldValue(nil).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "must be specified when"), + }.WithOrigin("union")) } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go index f54d8805d4cd6..545db7341df08 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go @@ -25,6 +25,7 @@ import ( context "context" fmt "fmt" + equality "k8s.io/apimachinery/pkg/api/equality" operation "k8s.io/apimachinery/pkg/api/operation" safe "k8s.io/apimachinery/pkg/api/safe" validate "k8s.io/apimachinery/pkg/api/validate" @@ -48,7 +49,7 @@ func RegisterValidations(scheme *testscheme.Scheme) error { return nil } -var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_simple_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2")) +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_simple_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2"), validate.NewDiscriminatedUnionMember("m3", "M3"), validate.NewDiscriminatedUnionMember("m4", "M4")) // Validate_Struct validates an instance of Struct according // to declarative validation rules in the API schema. @@ -68,6 +69,16 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return false } return obj.M2 != nil + }, func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M3) != 0 + }, func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M4) != 0 })...) // field Struct.TypeMeta has no validation @@ -109,5 +120,41 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return }(fldPath.Child("m2"), obj.M2, safe.Field(oldObj, func(oldObj *Struct) *M2 { return oldObj.M2 }), oldObj != nil)...) + // field Struct.M3 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m3"), obj.M3, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.M3 }), oldObj != nil)...) + + // field Struct.M4 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m4"), obj.M4, safe.Field(oldObj, func(oldObj *Struct) map[string]string { return oldObj.M4 }), oldObj != nil)...) + return errs } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go index ec20098a51fce..65c8aec8f177a 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package sparse import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go index 66068b71467f8..1976ade978e11 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go @@ -32,11 +32,11 @@ func Test(t *testing.T) { st.Value(&Struct{D: DM2, M1: &M1{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("m1"), nil, "may only be specified when"), - }) + }.WithOrigin("union")) st.Value(&Struct{D: DM1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(field.NewPath("m1"), nil, "must be specified when"), - }) + }.WithOrigin("union")) // Test validation ratcheting st.Value(&Struct{D: DM2, M1: &M1{}}).OldValue(&Struct{D: DM2, M1: &M1{}}).ExpectValid() diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go index 2717890fb09a3..286b3991fccdd 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package custommembers import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go index 7aef1de0ce47b..2e91d904b5932 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go @@ -30,10 +30,10 @@ func Test(t *testing.T) { st.Value(&Struct{M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(nil, nil, "must specify exactly one of"), - }) + }.WithOrigin("union")) st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(nil, nil, "must specify one of"), - }) + }.WithOrigin("union")) // Test validation ratcheting st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go index c5dd84298bcfa..042b182ed6fc2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package multiple import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go index cac9528c1586a..777a5be858b8b 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go @@ -26,29 +26,29 @@ func Test(t *testing.T) { st := localSchemeBuilder.Test(t) st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ - field.Invalid(nil, nil, "must specify one of"), - field.Invalid(nil, nil, "must specify one of"), + field.Invalid(nil, nil, "must specify one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify one of: `u2m1`, `u2m2`").WithOrigin("union"), }) st.Value(&Struct{U1M1: &M1{}, U2M1: &M1{}}).ExpectValid() st.Value(&Struct{U1M2: &M2{}, U2M2: &M2{}}).ExpectValid() st.Value(&Struct{U1M1: &M1{}, U1M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ - field.Invalid(nil, nil, "must specify exactly one of"), - field.Invalid(nil, nil, "must specify one of"), + field.Invalid(nil, nil, "must specify exactly one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify one of: `u2m1`, `u2m2`").WithOrigin("union"), }) st.Value(&Struct{U2M1: &M1{}, U2M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ - field.Invalid(nil, nil, "must specify one of"), - field.Invalid(nil, nil, "must specify exactly one of"), + field.Invalid(nil, nil, "must specify one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify exactly one of: `u2m1`, `u2m2`").WithOrigin("union"), }) st.Value(&Struct{ U1M1: &M1{}, U1M2: &M2{}, U2M1: &M1{}, U2M2: &M2{}, }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ - field.Invalid(nil, nil, "must specify exactly one of"), - field.Invalid(nil, nil, "must specify exactly one of"), + field.Invalid(nil, nil, "must specify exactly one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify exactly one of: `u2m1`, `u2m2`").WithOrigin("union"), }) // Test validation ratcheting diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go index 37fc82a85ad60..7c04fd3c11de0 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package simple import "k8s.io/code-generator/cmd/validation-gen/testscheme" @@ -45,6 +46,14 @@ type Struct struct { // +k8s:unionMember // +k8s:optional M4 *string `json:"m4"` + + // +k8s:unionMember + // +k8s:optional + M5 []string `json:"m5"` + + // +k8s:unionMember + // +k8s:optional + M6 map[string]string `json:"m6"` } type M1 struct{} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go index f00b41b546f0b..a6f5c582ed9e0 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go @@ -28,22 +28,32 @@ func Test(t *testing.T) { st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(nil, nil, "must specify one of"), - }) + }.WithOrigin("union")) st.Value(&Struct{M1: &M1{}}).ExpectValid() st.Value(&Struct{M2: &M2{}}).ExpectValid() st.Value(&Struct{M3: "a string"}).ExpectValid() st.Value(&Struct{M4: ptr.To("a string")}).ExpectValid() + st.Value(&Struct{M5: []string{"a string"}}).ExpectValid() + st.Value(&Struct{M6: map[string]string{"k": "v"}}).ExpectValid() + + // Empty slice/map are "not set" (same as nil for union membership) + st.Value(&Struct{M5: []string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of"), + }.WithOrigin("union")) + st.Value(&Struct{M6: map[string]string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of"), + }.WithOrigin("union")) st.Value(&Struct{M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(nil, nil, "must specify exactly one of"), - }) + }.WithOrigin("union")) st.Value(&Struct{M1: &M1{}, M3: "a string"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(nil, nil, "must specify exactly one of"), - }) + }.WithOrigin("union")) st.Value(&Struct{M1: &M1{}, M4: ptr.To("a string")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ field.Invalid(nil, nil, "must specify exactly one of"), - }) + }.WithOrigin("union")) // Update only considers whether a field was set, not the value. st.Value(&Struct{M3: "a string"}).OldValue(&Struct{M3: "different string"}).ExpectValid() @@ -52,4 +62,14 @@ func Test(t *testing.T) { st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() st.Value(&Struct{M3: "a string", M2: &M2{}}).OldValue(&Struct{M3: "different string", M2: &M2{}}).ExpectValid() + + // Slice/map member ratcheting: unchanged membership, different values + st.Value(&Struct{M5: []string{"a"}}).OldValue(&Struct{M5: []string{"b"}}).ExpectValid() + st.Value(&Struct{M6: map[string]string{"k": "v1"}}).OldValue(&Struct{M6: map[string]string{"k": "v2"}}).ExpectValid() + + // Test update with nil old value (simulates new map entry or newly-set pointer field during update). + // Union validation should still detect the empty union even though oldObj is nil. + st.Value(&Struct{}).OldValue(nil).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of").WithOrigin("union"), + }) } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go index 80c81dc083dce..76eb02c919b60 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go @@ -25,6 +25,7 @@ import ( context "context" fmt "fmt" + equality "k8s.io/apimachinery/pkg/api/equality" operation "k8s.io/apimachinery/pkg/api/operation" safe "k8s.io/apimachinery/pkg/api/safe" validate "k8s.io/apimachinery/pkg/api/validate" @@ -48,7 +49,7 @@ func RegisterValidations(scheme *testscheme.Scheme) error { return nil } -var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_simple_Struct_ = validate.NewUnionMembership(validate.NewUnionMember("m1"), validate.NewUnionMember("m2"), validate.NewUnionMember("m3"), validate.NewUnionMember("m4")) +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_simple_Struct_ = validate.NewUnionMembership(validate.NewUnionMember("m1"), validate.NewUnionMember("m2"), validate.NewUnionMember("m3"), validate.NewUnionMember("m4"), validate.NewUnionMember("m5"), validate.NewUnionMember("m6")) // Validate_Struct validates an instance of Struct according // to declarative validation rules in the API schema. @@ -74,6 +75,16 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return false } return obj.M4 != nil + }, func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M5) != 0 + }, func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M6) != 0 })...) // field Struct.TypeMeta has no validation @@ -151,5 +162,41 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return }(fldPath.Child("m4"), obj.M4, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.M4 }), oldObj != nil)...) + // field Struct.M5 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m5"), obj.M5, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.M5 }), oldObj != nil)...) + + // field Struct.M6 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("m6"), obj.M6, safe.Field(oldObj, func(oldObj *Struct) map[string]string { return oldObj.M6 }), oldObj != nil)...) + return errs } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go index 2fa2b69df239c..70255d8c45c10 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package unique import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/doc.go index 5c73543910769..e95eebaccaf1b 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package for the +k8s:update tag. +// +k8s:validation-gen-nolint package update import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go index bba748ade5158..5644f125f3da1 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package validatefalse import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go index b68d136354384..4694fab3d7fd6 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package validatetrue import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc.go new file mode 100644 index 0000000000000..000f8ed0467c3 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package validatetruealpha + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateTrueAlpha="field Struct.StringField" + StringField string `json:"stringField"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc_test.go new file mode 100644 index 0000000000000..e741b3ab25d02 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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 validatetruealpha + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + }).ExpectValid() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/zz_generated.validations.go new file mode 100644 index 0000000000000..cc9cd6c710376 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/zz_generated.validations.go @@ -0,0 +1,69 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validatetruealpha + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.StringField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field Struct.StringField")...) + return + }(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc.go new file mode 100644 index 0000000000000..ec7758b0c6513 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package validatetruebeta + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateTrueBeta="field Struct.StringField" + StringField string `json:"stringField"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc_test.go new file mode 100644 index 0000000000000..534712bd5cc34 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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 validatetruebeta + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + }).ExpectValid() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/zz_generated.validations.go new file mode 100644 index 0000000000000..3c4beae25c6f0 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/zz_generated.validations.go @@ -0,0 +1,69 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validatetruebeta + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) { + // field Struct.TypeMeta has no validation + + // field Struct.StringField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field Struct.StringField")...) + return + }(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }), oldObj != nil)...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go index d1c1318ab9123..92b20ff71acda 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package custommembers import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go index 594441b15831a..4ba47ebbebea7 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package multiple import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go index d6d8591542007..382330f7cc3fa 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package simple import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go index 01a92457af988..6764fa0654501 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go @@ -51,4 +51,12 @@ func Test(t *testing.T) { st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() st.Value(&Struct{M3: "a string", M2: &M2{}}).OldValue(&Struct{M3: "different string", M2: &M2{}}).ExpectValid() + + // Test update with nil old value (simulates new map entry added during update). + // Empty union is still valid for zeroOrOneOf, even with nil old value. + st.Value(&Struct{}).OldValue((*Struct)(nil)).ExpectValid() + // Multiple members set should still be caught even with nil old value. + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue((*Struct)(nil)).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go index 1cd1bb5d24900..26fc6c9aec446 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package typeargs import ( diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go index abd592895f38e..61d7922be7707 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package typedefs import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go index cda9ecb35bce8..2ea277d1700bd 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package lists import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go index 6a6920c39c366..ff88bd638dac5 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go @@ -19,6 +19,7 @@ limitations under the License. // +k8s:validation-gen-test-fixture=validateFalse // This is a test package. +// +k8s:validation-gen-nolint package maps import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go index 561c622d07f7e..56bb38bd29b9c 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go @@ -19,6 +19,8 @@ limitations under the License. // Package primitivepointers is a test package. // +// +k8s:validation-gen-nolint +// //nolint:unused package primitivepointers diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go index e2df24472faf5..a0d9ec2fae152 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go @@ -18,6 +18,7 @@ limitations under the License. // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme // This is a test package. +// +k8s:validation-gen-nolint package primitives import "k8s.io/code-generator/cmd/validation-gen/testscheme" diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go index 8ed228c109299..d69310858eebc 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/code-generator/cmd/validation-gen/validators" "k8s.io/gengo/v2" + "k8s.io/gengo/v2/codetags" "k8s.io/gengo/v2/generator" "k8s.io/gengo/v2/namer" "k8s.io/gengo/v2/types" @@ -34,8 +35,8 @@ import ( // These are the comment tags that carry parameters for validation generation. const ( - tagName = "k8s:validation-gen" - inputTagName = "k8s:validation-gen-input" + mainTagName = "k8s:validation-gen" // defines which types to generate validation for + inputTagName = "k8s:validation-gen-input" // indicates that input types are in a different package schemeRegistryTagName = "k8s:validation-gen-scheme-registry" // defaults to k8s.io/apimachinery/pkg.runtime.Scheme testFixtureTagName = "k8s:validation-gen-test-fixture" // if set, generate go test files for test fixtures. Supported values: "validateFalse". @@ -48,16 +49,37 @@ const ( ) var ( - runtimePkg = "k8s.io/apimachinery/pkg/runtime" - schemeType = types.Name{Package: runtimePkg, Name: "Scheme"} + runtimePkg = "k8s.io/apimachinery/pkg/runtime" + schemeType = types.Name{Package: runtimePkg, Name: "Scheme"} + metav1Pkg = "k8s.io/apimachinery/pkg/apis/meta/v1" + listMetaType = types.Name{Package: metav1Pkg, Name: "ListMeta"} ) -func extractTag(comments []string) ([]string, bool) { - tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{tagName}, comments) +// extractAndParseTag extracts all the values for a given tag, according to the +// tag grammar. +func extractAndParseTag(tagName string, comments []string) ([]codetags.Tag, error) { + extracted := codetags.Extract("+", comments) + var tags []codetags.Tag + for key, lines := range extracted { + if key != tagName { + continue + } + t, err := codetags.ParseAll(lines) + if err != nil { + return nil, fmt.Errorf("failed to parse tags: %w: %s", err, lines) + } + tags = append(tags, t...) + } + return tags, nil +} + +func extractMainTag(comments []string) ([]string, bool) { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{mainTagName}, comments) if err != nil { klog.Fatalf("Failed to extract tags: %v", err) } - values, found := tags[tagName] + values, found := tags[mainTagName] if !found || len(values) == 0 { return nil, false } @@ -70,6 +92,7 @@ func extractTag(comments []string) ([]string, bool) { } func extractInputTag(comments []string) []string { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{inputTagName}, comments) if err != nil { klog.Fatalf("Failed to extract input tags: %v", err) @@ -86,12 +109,14 @@ func extractInputTag(comments []string) []string { return result } -func checkTag(comments []string, require ...string) bool { - tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{tagName}, comments) +// TODO: this can just accept a single bool +func checkMainTag(comments []string, require ...string) bool { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{mainTagName}, comments) if err != nil { klog.Fatalf("Failed to extract tags: %v", err) } - values, found := tags[tagName] + values, found := tags[mainTagName] if !found { return false } @@ -109,6 +134,7 @@ func checkTag(comments []string, require ...string) bool { } func schemeRegistryTag(pkg *types.Package) types.Name { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{schemeRegistryTagName}, pkg.Comments) if err != nil { klog.Fatalf("Failed to extract scheme registry tags: %v", err) @@ -127,34 +153,32 @@ func isSubresourceTag(t *types.Type) (string, bool) { var comments []string comments = append(comments, t.SecondClosestCommentLines...) comments = append(comments, t.CommentLines...) - tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{isSubresourceTagName}, comments) + tags, err := extractAndParseTag(isSubresourceTagName, comments) if err != nil { klog.Fatalf("Failed to extract isSubresource tags: %v", err) } - values, found := tags[isSubresourceTagName] - if !found || len(values) == 0 { + if len(tags) == 0 { return "", false } - if len(values) > 1 { + if len(tags) > 1 { panic(fmt.Sprintf("Type %q contains more than one usage of %q", t.Name.String(), isSubresourceTagName)) } - return values[0].Value, true + return tags[0].Value, true } func supportedSubresourceTags(t *types.Type) sets.Set[string] { var comments []string comments = append(comments, t.SecondClosestCommentLines...) comments = append(comments, t.CommentLines...) - tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{supportsSubresourceTagName}, comments) + tags, err := extractAndParseTag(supportsSubresourceTagName, comments) if err != nil { klog.Fatalf("Failed to extract supportedSubresource tags: %v", err) } - values, found := tags[supportsSubresourceTagName] - if !found || len(values) == 0 { + if len(tags) == 0 { return sets.New[string]() } subresources := sets.New[string]() - for _, tag := range values { + for _, tag := range tags { subresources.Insert(tag.Value) } return subresources @@ -164,6 +188,7 @@ var testFixtureTagValues = sets.New("validateFalse") func testFixtureTag(pkg *types.Package) sets.Set[string] { result := sets.New[string]() + // TODO: convert to extractAndParseTag() and update all callers to use quoted values tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{testFixtureTagName}, pkg.Comments) if err != nil { klog.Fatalf("Failed to extract test fixture tags: %v", err) @@ -292,7 +317,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target { } // Create a linter to collect errors as we go. - linter := newLinter() + linter := newLinter(lintRules(validator)...) // Build a cache of type->callNode for every type we need. for _, input := range context.Inputs { @@ -302,23 +327,33 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target { schemeRegistry := schemeRegistryTag(pkg) - typesWith, found := extractTag(pkg.Comments) + typesWith, found := extractMainTag(pkg.Comments) if !found { - klog.V(2).InfoS(" did not find required tag", "tag", tagName) + klog.V(2).InfoS(" did not find required tag", "tag", mainTagName) continue } if len(typesWith) == 1 && typesWith[0] == "" { - klog.Fatalf("found package tag %q with no value", tagName) + klog.Fatalf("found package tag %q with no value", mainTagName) } shouldCreateObjectValidationFn := func(t *types.Type) bool { // opt-out - if checkTag(t.SecondClosestCommentLines, "false") { + if checkMainTag(t.SecondClosestCommentLines, "false") { return false } // opt-in - if checkTag(t.SecondClosestCommentLines, "true") { + if checkMainTag(t.SecondClosestCommentLines, "true") { return true } + + // skip types that embed metav1.ListMeta + if t.Kind == types.Struct { + for _, member := range t.Members { + if member.Embedded && member.Type.Name == listMetaType { + return false + } + } + } + // all types for _, v := range typesWith { if v == "*" && !namer.IsPrivateGoName(t.Name.Name) { @@ -362,16 +397,15 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target { } } - for _, t := range rootTypes { - klog.V(3).InfoS("linting root-type", "type", t) - if err := linter.lintType(t); err != nil { - klog.Fatalf("failed to lint type %q: %v", t.Name, err) + extracted := codetags.Extract("+", pkg.Comments) + if _, ok := extracted["k8s:validation-gen-nolint"]; !ok { + for _, t := range rootTypes { + klog.V(3).InfoS("linting root-type", "type", t) + if err := linter.lintType(t); err != nil { + klog.Fatalf("failed to lint type %q: %v", t.Name, err) + } } } - if args.LintOnly { - klog.V(4).Info("Lint is set, skip appending targets") - continue - } targets = append(targets, &generator.SimpleTarget{ @@ -410,11 +444,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target { buf.WriteString(fmt.Sprintf(" %s\n", err.Error())) } } - if args.LintOnly { - klog.Fatalf("lint failed:\n%s", buf.String()) - } else { - klog.Warningf("lint failed:\n%s", buf.String()) - } + klog.Fatalf("lint failed:\n%s", buf.String()) } return targets } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util.go index 4566699baff91..2d22ca015317e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util.go @@ -17,6 +17,9 @@ limitations under the License. package util import ( + "fmt" + "strconv" + "k8s.io/gengo/v2/parser/tags" "k8s.io/gengo/v2/types" ) @@ -114,3 +117,37 @@ func IsDirectComparable(t *types.Type) bool { } return false } + +// ParseInt strictly parses an int from a string input, +// ensuring that when converted back to a string, the resulting +// int and the input string have the exact same representation. +// This prevents scenarios where an input like "0100" parses +// as 100 and would be re-stringed as "100". +func ParseInt(val string) (int, error) { + intVal, err := strconv.Atoi(val) + if err != nil { + return 0, fmt.Errorf("parsing %q as int: %w", val, err) + } + + strVal := strconv.Itoa(intVal) + if strVal != val { + return 0, fmt.Errorf("%q is not a valid int value", val) + } + + return intVal, nil +} + +// ParseBool strictly parses a bool from a string input, +// ensuring that when converted back to a string, the resulting +// bool and the input string have the exact same representation. +// This prevents scenarios where an input like "TRUE" parses +// as true and would be re-stringed as "true". +func ParseBool(val string) (bool, error) { + switch val { + case "true": + return true, nil + case "false": + return false, nil + } + return false, fmt.Errorf("%q is not a valid bool value", val) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util_test.go index 5ff3ae6cb8c7d..c0977d86b3d23 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/util/util_test.go @@ -452,3 +452,129 @@ func TestIsDirectComparable(t *testing.T) { } } } + +func TestParseInt(t *testing.T) { + type testcase struct { + name string + in string + expectedOut int + expectedError bool + } + + testcases := []testcase{ + { + name: "valid canonical positive integer string", + in: "100", + expectedOut: 100, + }, + { + name: "valid canonical negative integer string", + in: "-100", + expectedOut: -100, + }, + { + name: "empty string", + in: "", + expectedError: true, + }, + { + name: "invalid unary positive integer string", + in: "+100", + expectedError: true, + }, + { + name: "invalid canonical integer string, not an integer at all", + in: "notanint", + expectedError: true, + }, + { + name: "invalid canonical integer string, spurious leading zeros", + in: "00100", + expectedError: true, + }, + { + name: "invalid canonical integer string, octal value", + in: "0o123", + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := ParseInt(tc.in) + switch { + case tc.expectedError && err == nil: + t.Error("expected an error but did not receive one") + case !tc.expectedError && err != nil: + t.Errorf("received an unexpected error: %v", err) + } + + if out != tc.expectedOut { + t.Errorf("expected an output value of %d but got %d", tc.expectedOut, out) + } + }) + } +} + +func TestParseBool(t *testing.T) { + type testcase struct { + name string + in string + expectedOut bool + expectedError bool + } + + testcases := []testcase{ + { + name: "valid canonical true string", + in: "true", + expectedOut: true, + }, + { + name: "valid canonical false string", + in: "false", + expectedOut: false, + }, + { + name: "empty string", + in: "", + expectedError: true, + }, + { + name: "invalid canonical boolean string, not a bool at all", + in: "notabool", + expectedError: true, + }, + { + name: "invalid canonical boolean string, capitalized", + in: "True", + expectedError: true, + }, + { + name: "invalid canonical boolean string, numeric", + in: "1", + expectedError: true, + }, + { + name: "invalid canonical boolean string, YAML", + in: "yes", + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := ParseBool(tc.in) + switch { + case tc.expectedError && err == nil: + t.Error("expected an error but did not receive one") + case !tc.expectedError && err != nil: + t.Errorf("received an unexpected error: %v", err) + } + + if out != tc.expectedOut { + t.Errorf("expected an output value of %v but got %v", tc.expectedOut, out) + } + }) + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go index c755706edb66b..3e1a90fc0611e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go @@ -149,7 +149,7 @@ func (g *genValidations) GenerateType(c *generator.Context, t *types.Type, w io. // typeDiscoverer contains fields necessary to build graphs of types. type typeDiscoverer struct { initialized bool - validator validators.Validator + validator validators.ValidationExtractor inputToPkg map[string]string // constantsByType holds a map of type to constants of that type. @@ -162,7 +162,7 @@ type typeDiscoverer struct { // NewTypeDiscoverer creates a NewTypeDiscoverer. // Init must be called before calling DiscoverType. -func NewTypeDiscoverer(validator validators.Validator, inputToPkg map[string]string) *typeDiscoverer { +func NewTypeDiscoverer(validator validators.ValidationExtractor, inputToPkg map[string]string) *typeDiscoverer { return &typeDiscoverer{ validator: validator, inputToPkg: inputToPkg, @@ -1347,6 +1347,12 @@ func emitCallsToValidators(c *generator.Context, validations []validators.Functi toGolangSourceDataLiteral(sw, c, arg) } sw.Do(")", targs) + switch v.StabilityLevel { + case validators.ValidationStabilityLevelAlpha: + sw.Do(".MarkAlpha()", nil) + case validators.ValidationStabilityLevelBeta: + sw.Do(".MarkBeta()", nil) + } } // If validation is conditional, wrap the validation function with a conditions check. @@ -1568,6 +1574,73 @@ func toGolangSourceDataLiteral(sw *generator.SnippetWriter, c *generator.Context emitFunctionCall(sw, c, v.Function, "ctx", "op", "fldPath", "obj", "oldObj") sw.Do("\n}", targs) } + case validators.MultiWrapperFunction: + // MultiWrapperFunction generates a closure to execute multiple validation functions. + targs := generator.Args{ + "field": mkSymbolArgs(c, fieldPkgSymbols), + "operation": mkSymbolArgs(c, operationPkgSymbols), + "context": mkSymbolArgs(c, contextPkgSymbols), + "objType": v.ObjType, + "objTypePfx": "*", + } + // Use the nilable form to pass pointers to standard validation functions. + if util.IsNilableType(v.ObjType) { + targs["objTypePfx"] = "" + } + + sw.Do("func(", targs) + sw.Do(" ctx $.context.Context|raw$, ", targs) + sw.Do(" op $.operation.Operation|raw$, ", targs) + sw.Do(" fldPath *$.field.Path|raw$, ", targs) + sw.Do(" obj, oldObj $.objTypePfx$$.objType|raw$ ", targs) + sw.Do(") $.field.ErrorList|raw$ {\n", targs) + sw.Do("errs := $.field.ErrorList|raw${}\n", targs) + + // Determine if any wrapped functions short-circuit. + hasShortCircuits := false + lastShortCircuitIdx := -1 + for i, fg := range v.Functions { + if fg.Flags.IsSet(validators.ShortCircuit) { + hasShortCircuits = true + lastShortCircuitIdx = i + } + } + if hasShortCircuits { + sw.Do("earlyReturn := false\n", nil) + } + + for i, fg := range v.Functions { + isNonError := fg.Flags.IsSet(validators.NonError) + + if fg.Flags.IsSet(validators.ShortCircuit) { + // Short-circuiting functions stop execution if they return an error. + sw.Do("if e := ", nil) + emitFunctionCall(sw, c, fg, "ctx", "op", "fldPath", "obj", "oldObj") + sw.Do("; len(e) != 0 {\n", nil) + if !isNonError { + sw.Do(" errs = append(errs, e...)\n", nil) + } + sw.Do(" earlyReturn = true\n", nil) + sw.Do("}\n", nil) + + // If a failure occurred during short-circuiting, return early. + if i == lastShortCircuitIdx { + sw.Do("if earlyReturn {\n", nil) + sw.Do(" return errs\n", nil) + sw.Do("}\n", nil) + } + } else { + // Standard functions append errors to the list. + if isNonError { + emitFunctionCall(sw, c, fg, "ctx", "op", "fldPath", "obj", "oldObj") + } else { + sw.Do("errs = append(errs, ", nil) + emitFunctionCall(sw, c, fg, "ctx", "op", "fldPath", "obj", "oldObj") + sw.Do("...)\n", nil) + } + } + } + sw.Do("return errs\n}", nil) case validators.Literal: sw.Do("$.$", v) case validators.FunctionGen: @@ -1711,6 +1784,12 @@ func emitFunctionCall(sw *generator.SnippetWriter, c *generator.Context, v valid toGolangSourceDataLiteral(sw, c, arg) } sw.Do(")", nil) + switch v.StabilityLevel { + case validators.ValidationStabilityLevelAlpha: + sw.Do(".MarkAlpha()", nil) + case validators.ValidationStabilityLevelBeta: + sw.Do(".MarkBeta()", nil) + } } // getLeafTypeAndPrefixes returns the "leaf value type" for a given type, as diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/common.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/common.go index 93cea458e8465..9c05900deadd8 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/common.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/common.go @@ -34,3 +34,12 @@ func rootTypeString(src, dst *types.Type) string { } return src.String() + " -> " + dst.String() } + +// isInt returns true if t is a uint type. +func isUnsignedInt(t *types.Type) bool { + switch t { + case types.Uint, types.Uint64, types.Uint32, types.Uint16, types.Byte: + return true + } + return false +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/discriminator.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/discriminator.go new file mode 100644 index 0000000000000..517f34ccaac49 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/discriminator.go @@ -0,0 +1,508 @@ +/* +Copyright 2025 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 validators + +import ( + "fmt" + "regexp" + "slices" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/code-generator/cmd/validation-gen/util" + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/parser/tags" + "k8s.io/gengo/v2/types" +) + +const ( + discriminatorTagName = "k8s:discriminator" + memberTagName = "k8s:member" +) + +// validGroupNameRegex restricts discriminator group names to identifiers that +// start with a letter and contain only alphanumeric characters and underscores. +var validGroupNameRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*$`) + +func init() { + RegisterTagValidator(&discriminatorTagValidator{discriminatorDefinitions}) + RegisterTagValidator(&memberTagValidator{discriminatorDefinitions, nil}) + RegisterTypeValidator(&discriminatorFieldValidator{discriminatorDefinitions}) + RegisterFieldValidator(&discriminatorFieldValidator{discriminatorDefinitions}) +} + +// discriminatorDefinitions stores all discriminator definitions found by tag validators. +// Key is the struct path. +var discriminatorDefinitions = map[string]discriminatorGroups{} + +type discriminatorGroups map[string]*discriminatorGroup + +type discriminatorGroup struct { + name string + discriminatorMember *types.Member + // members maps field names to their rules in this discriminator group. + members map[string]*fieldMemberRules +} + +type fieldMemberRules struct { + member *types.Member + rules []memberRule +} + +type memberRule struct { + value string + validations Validations + stabilityLevel ValidationStabilityLevel +} + +func (mg discriminatorGroups) getOrCreate(name string) *discriminatorGroup { + if name == "" { + name = "default" + } + g, ok := mg[name] + if !ok { + g = &discriminatorGroup{ + name: name, + members: make(map[string]*fieldMemberRules), + } + mg[name] = g + } + return g +} + +type discriminatorTagValidator struct { + shared map[string]discriminatorGroups +} + +func (mtv *discriminatorTagValidator) Init(_ Config) {} + +func (mtv *discriminatorTagValidator) TagName() string { + return discriminatorTagName +} + +func (mtv *discriminatorTagValidator) ValidScopes() sets.Set[Scope] { + return sets.New(ScopeField) +} + +func (mtv *discriminatorTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + if util.NativeType(context.Type).Kind == types.Pointer { + return Validations{}, fmt.Errorf("can only be used on non-pointer types") + } + + if t := util.NonPointer(util.NativeType(context.Type)); t.Kind != types.Builtin || (t.Name.Name != "string" && t.Name.Name != "bool" && !types.IsInteger(t)) { + return Validations{}, fmt.Errorf("can only be used on string, bool or integer types (%s)", rootTypeString(context.Type, t)) + } + + if mtv.shared[context.ParentPath.String()] == nil { + mtv.shared[context.ParentPath.String()] = make(discriminatorGroups) + } + groupName := "" + if nameArg, ok := tag.NamedArg("name"); ok { + groupName = nameArg.Value + } + if groupName != "" && !validGroupNameRegex.MatchString(groupName) { + return Validations{}, fmt.Errorf("discriminator group name must match %s, got %q", validGroupNameRegex.String(), groupName) + } + if groupName == "default" { + return Validations{}, fmt.Errorf("discriminator group name %q is reserved", groupName) + } + group := mtv.shared[context.ParentPath.String()].getOrCreate(groupName) + if group.discriminatorMember != nil && group.discriminatorMember != context.Member { + return Validations{}, fmt.Errorf("duplicate discriminator: %q", groupName) + } + group.discriminatorMember = context.Member + + return Validations{}, nil +} + +func (mtv *discriminatorTagValidator) Docs() TagDoc { + return TagDoc{ + Tag: mtv.TagName(), + StabilityLevel: TagStabilityLevelAlpha, + Scopes: sets.List(mtv.ValidScopes()), + Description: "Indicates that this field is a discriminator for state-based validation.", + Args: []TagArgDoc{{ + Name: "name", + Description: "", + Docs: "the name of the discriminator group, if more than one exists", + Type: codetags.ArgTypeString, + }}, + } +} + +type memberTagValidator struct { + shared map[string]discriminatorGroups + validator TagValidationExtractor +} + +func (mtv *memberTagValidator) Init(cfg Config) { + mtv.validator = cfg.TagValidator +} + +func (mtv *memberTagValidator) TagName() string { + return memberTagName +} + +func (mtv *memberTagValidator) ValidScopes() sets.Set[Scope] { + return sets.New(ScopeField) +} + +func (mtv *memberTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + if tag.ValueTag == nil { + return Validations{}, fmt.Errorf("missing required payload") + } + + groupName := "" + if modeArg, ok := tag.NamedArg("discriminator"); ok { + groupName = modeArg.Value + } + if groupName == "default" { + return Validations{}, fmt.Errorf("discriminator group name %q is reserved", groupName) + } + + value := "" + if valArg, ok := tag.NamedArg("value"); ok { + value = valArg.Value + } else if len(tag.Args) > 0 && tag.Args[0].Name == "" { + // Positional argument + value = tag.Args[0].Value + } else { + return Validations{}, fmt.Errorf("missing required value") + } + + if mtv.shared[context.ParentPath.String()] == nil { + mtv.shared[context.ParentPath.String()] = make(discriminatorGroups) + } + group := mtv.shared[context.ParentPath.String()].getOrCreate(groupName) + + fieldName := context.Member.Name + if rules, ok := group.members[fieldName]; ok { + if rules.member != context.Member { + return Validations{}, fmt.Errorf("internal error: member mismatch for field %q", fieldName) + } + } else { + group.members[fieldName] = &fieldMemberRules{ + member: context.Member, + } + } + + payloadValidations, err := mtv.validator.ExtractTagValidations(context, *tag.ValueTag) + if err != nil { + return Validations{}, err + } + + group.members[fieldName].rules = append(group.members[fieldName].rules, memberRule{ + value: value, + validations: payloadValidations, + stabilityLevel: context.StabilityLevel, + }) + + return Validations{}, nil +} + +func (mtv *memberTagValidator) Docs() TagDoc { + return TagDoc{ + Tag: mtv.TagName(), + StabilityLevel: TagStabilityLevelAlpha, + Scopes: sets.List(mtv.ValidScopes()), + Description: "Indicates that this field's validation depends on a discriminator.", + Args: []TagArgDoc{{ + Name: "", // positional + Description: "", + Docs: "the value of the discriminator for which this validation applies", + Type: codetags.ArgTypeString, + }, { + Name: "discriminator", + Description: "", + Docs: "the name of the discriminator group", + Type: codetags.ArgTypeString, + }, { + Name: "value", + Description: "", + Docs: "the value of the discriminator for which this validation applies", + Type: codetags.ArgTypeString, + }}, + PayloadsType: codetags.ValueTypeTag, + PayloadsRequired: true, + } +} + +type discriminatorFieldValidator struct { + shared map[string]discriminatorGroups +} + +func (discriminatorFieldValidator) Init(_ Config) {} + +func (discriminatorFieldValidator) Name() string { + return "discriminatorFieldValidator" +} + +func (mtfv *discriminatorFieldValidator) GetValidations(context Context) (Validations, error) { + // Extract the most concrete type possible. + if k := util.NonPointer(util.NativeType(context.Type)).Kind; k != types.Struct { + return Validations{}, nil + } + + groups, ok := mtfv.shared[context.Path.String()] + if !ok || len(groups) == 0 { + return Validations{}, nil + } + + var result Validations + + // Sort group names for deterministic output + groupNames := make([]string, 0, len(groups)) + for name := range groups { + groupNames = append(groupNames, name) + } + slices.Sort(groupNames) + + for _, gn := range groupNames { + group := groups[gn] + if group.discriminatorMember == nil { + if len(group.members) > 0 { + if gn == "default" { + return Validations{}, fmt.Errorf("missing discriminator") + } + return Validations{}, fmt.Errorf("missing discriminator for group %q", gn) + } + continue + } + + fieldNames := make([]string, 0, len(group.members)) + for name := range group.members { + fieldNames = append(fieldNames, name) + } + slices.Sort(fieldNames) + + for _, fn := range fieldNames { + rules := group.members[fn] + v, err := mtfv.generateMemberFieldValidation(context, group, rules) + if err != nil { + return Validations{}, err + } + result.Add(v) + } + } + + return result, nil +} + +func (mtfv *discriminatorFieldValidator) generateMemberFieldValidation(context Context, group *discriminatorGroup, rules *fieldMemberRules) (Validations, error) { + fieldType := rules.member.Type + + // Use the nilable form to handle missing values. + nilableFieldType := fieldType + fieldExprPrefix := "" + if !util.IsNilableType(nilableFieldType) { + nilableFieldType = types.PointerTo(nilableFieldType) + fieldExprPrefix = "&" + } + + // Get the JSON name of the field + jsonName := rules.member.Name + if jt, ok := tags.LookupJSON(*rules.member); ok { + jsonName = jt.Name + } + + // Default validation is Forbidden + defaultForbidden, err := mtfv.getForbiddenValidation(fieldType) + if err != nil { + return Validations{}, err + } + + // Prepare DiscriminatedRules + // Mark each rule's validation functions with their stability level before + // aggregating by value, so that different rules for the same value can + // carry different stability levels. + rulesByValue := make(map[string]*Validations) + var values []string + for _, rule := range rules.rules { + // Track unique discriminator values in order of first appearance. + if _, ok := rulesByValue[rule.value]; !ok { + rulesByValue[rule.value] = &Validations{} + values = append(values, rule.value) + } + // Mark each validation function with its stability level before merging. + v := rule.validations + if rule.stabilityLevel != "" { + marked := make([]FunctionGen, len(v.Functions)) + for i, f := range v.Functions { + marked[i] = f.WithStabilityLevel(rule.stabilityLevel) + } + v.Functions = marked + } + // Accumulate this rule's validations with others that share the same discriminator value. + rulesByValue[rule.value].Add(v) + } + slices.Sort(values) + + discriminatorType := group.discriminatorMember.Type + + // When all rules share the same stability level, the default-forbidden + // (which fires for unrecognized discriminator values) should also be marked + // with that level so its errors carry the same stability annotation. + if uniformLevel := uniformStabilityLevel(rules.rules); uniformLevel != "" { + mwf, ok := defaultForbidden.(MultiWrapperFunction) + if !ok { + return Validations{}, fmt.Errorf("internal error: defaultForbidden is not a MultiWrapperFunction") + } + marked := make([]FunctionGen, len(mwf.Functions)) + for i, f := range mwf.Functions { + marked[i] = f.WithStabilityLevel(uniformLevel) + } + mwf.Functions = marked + defaultForbidden = mwf + } + + var discriminatedRules []any + for _, val := range values { + wrapper := MultiWrapperFunction{ + Functions: rulesByValue[val].Functions, + ObjType: nilableFieldType, + } + + // Convert the string tag value to the appropriate typed Go literal + // for the discriminator type. + typedValue, err := convertDiscriminatorValue(val, discriminatorType) + if err != nil { + return Validations{}, fmt.Errorf("invalid discriminator value %q: %w", val, err) + } + + discriminatedRules = append(discriminatedRules, StructLiteral{ + Type: types.Name{Package: libValidationPkg, Name: "DiscriminatedRule"}, + TypeArgs: []*types.Type{nilableFieldType, discriminatorType}, + Fields: []StructLiteralField{ + {Name: "Value", Value: typedValue}, + {Name: "Validation", Value: wrapper}, + }, + }) + } + + discriminatedValidator := types.Name{Package: libValidationPkg, Name: "Discriminated"} + + rulesSlice := SliceLiteral{ + ElementType: types.Name{Package: libValidationPkg, Name: "DiscriminatedRule"}, + ElementTypeArgs: []*types.Type{nilableFieldType, discriminatorType}, + Elements: discriminatedRules, + } + + // getValue extractor + getValue := FunctionLiteral{ + Parameters: []ParamResult{{Name: "obj", Type: types.PointerTo(context.Type)}}, + Results: []ParamResult{{Type: nilableFieldType}}, + Body: fmt.Sprintf("return %sobj.%s", fieldExprPrefix, rules.member.Name), + } + + // getDiscriminator extractor + getDiscriminator := FunctionLiteral{ + Parameters: []ParamResult{{Name: "obj", Type: types.PointerTo(context.Type)}}, + Results: []ParamResult{{Type: discriminatorType}}, + Body: fmt.Sprintf("return obj.%s", group.discriminatorMember.Name), + } + + // directComparable is used to determine whether we can use the direct + // comparison operator "==" or need to use the semantic DeepEqual when + // looking up and comparing correlated list elements for validation ratcheting. + var equivArg any + if util.IsDirectComparable(util.NonPointer(util.NativeType(fieldType))) { + equivArg = Identifier(validateDirectEqualPtr) + } else { + equivArg = Identifier(validateSemanticDeepEqual) + } + + fn := Function(discriminatorTagName, DefaultFlags, discriminatedValidator, + Literal(fmt.Sprintf("%q", jsonName)), + getValue, + getDiscriminator, + equivArg, + defaultForbidden, + rulesSlice, + ) + + return Validations{Functions: []FunctionGen{fn}}, nil +} + +// uniformStabilityLevel returns the common stability level if all rules share +// the same one, or "" if they differ. +func uniformStabilityLevel(rules []memberRule) ValidationStabilityLevel { + level := rules[0].stabilityLevel + for i := 1; i < len(rules); i++ { + if rules[i].stabilityLevel != level { + return "" + } + } + return level +} + +func (mtfv *discriminatorFieldValidator) getForbiddenValidation(t *types.Type) (any, error) { + var forbidden types.Name + nt := util.NativeType(t) + switch nt.Kind { + case types.Slice: + forbidden = types.Name{Package: libValidationPkg, Name: "ForbiddenSlice"} + case types.Map: + forbidden = types.Name{Package: libValidationPkg, Name: "ForbiddenMap"} + case types.Pointer: + forbidden = types.Name{Package: libValidationPkg, Name: "ForbiddenPointer"} + case types.Struct: + return nil, fmt.Errorf("discriminated member fields of struct type must be pointers") + default: + forbidden = types.Name{Package: libValidationPkg, Name: "ForbiddenValue"} + } + + fg := Function(forbiddenTagName, DefaultFlags, forbidden) + + // Use the nilable form to match standard validation function signatures. + wrapperObjType := t + if !util.IsNilableType(t) { + wrapperObjType = types.PointerTo(t) + } + + return MultiWrapperFunction{ + Functions: []FunctionGen{fg}, + ObjType: wrapperObjType, + }, nil +} + +// convertDiscriminatorValue converts a string tag value to the appropriate +// typed Go literal for the given discriminator type. +func convertDiscriminatorValue(val string, discType *types.Type) (any, error) { + nt := util.NonPointer(util.NativeType(discType)) + if nt.Kind != types.Builtin { + return nil, fmt.Errorf("unsupported discriminator type: %s", nt.Name.Name) + } + + switch nt.Name.Name { + case "string": + return val, nil + case "bool": + b, err := util.ParseBool(val) + if err != nil { + return nil, fmt.Errorf("cannot parse %q as bool: %w", val, err) + } + return b, nil + default: + if types.IsInteger(nt) { + i, err := util.ParseInt(val) + if err != nil { + return nil, fmt.Errorf("cannot parse %q as integer: %w", val, err) + } + return int(i), nil + } + return nil, fmt.Errorf("unsupported discriminator type: %s", nt.Name.Name) + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go index 4961f46d26dbc..0a144234bc3dd 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go @@ -51,11 +51,11 @@ func init() { type eachValTagValidator struct { byPath map[string]*listMetadata - validator Validator + validator TagValidationExtractor } func (evtv *eachValTagValidator) Init(cfg Config) { - evtv.validator = cfg.Validator + evtv.validator = cfg.TagValidator } func (eachValTagValidator) TagName() string { @@ -88,10 +88,11 @@ func (evtv eachValTagValidator) GetValidations(context Context, tag codetags.Tag elemContext := Context{ // Scope is initialized below. - Type: nt.Elem, - Path: context.Path.Key("(vals)"), - Member: nil, // NA for list/map values - ParentPath: context.Path, + Type: nt.Elem, + Path: context.Path.Key("(vals)"), + Member: nil, // NA for list/map values + ParentPath: context.Path, + StabilityLevel: context.StabilityLevel, } switch nt.Kind { case types.Slice, types.Array: @@ -104,7 +105,7 @@ func (evtv eachValTagValidator) GetValidations(context Context, tag codetags.Tag if tag.ValueTag == nil { return Validations{}, fmt.Errorf("missing validation tag") } - if validations, err := evtv.validator.ExtractValidations(elemContext, *tag.ValueTag); err != nil { + if validations, err := evtv.validator.ExtractTagValidations(elemContext, *tag.ValueTag); err != nil { return Validations{}, err } else { if validations.Empty() && !validations.OpaqueKeyType && !validations.OpaqueValType && !validations.OpaqueType { @@ -226,8 +227,8 @@ func (evtv eachValTagValidator) getMapValidations(t *types.Type, validations Val func (evtv eachValTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: evtv.TagName(), - StabilityLevel: Alpha, - Scopes: evtv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelAlpha, + Scopes: sets.List(evtv.ValidScopes()), Description: "Declares a validation for each value in a map or list.", Payloads: []TagPayloadDoc{{ Description: "", @@ -240,11 +241,11 @@ func (evtv eachValTagValidator) Docs() TagDoc { } type eachKeyTagValidator struct { - validator Validator + validator TagValidationExtractor } func (ektv *eachKeyTagValidator) Init(cfg Config) { - ektv.validator = cfg.Validator + ektv.validator = cfg.TagValidator } func (eachKeyTagValidator) TagName() string { @@ -268,14 +269,15 @@ func (ektv eachKeyTagValidator) GetValidations(context Context, tag codetags.Tag } elemContext := Context{ - Scope: ScopeMapKey, - Type: nt.Elem, - Path: context.Path.Key("(keys)"), - Member: nil, // NA for map keys - ParentPath: context.Path, + Scope: ScopeMapKey, + Type: nt.Key, + Path: context.Path.Key("(keys)"), + Member: nil, // NA for map keys + ParentPath: context.Path, + StabilityLevel: context.StabilityLevel, } - if validations, err := ektv.validator.ExtractValidations(elemContext, *tag.ValueTag); err != nil { + if validations, err := ektv.validator.ExtractTagValidations(elemContext, *tag.ValueTag); err != nil { return Validations{}, err } else { if len(validations.Variables) > 0 { @@ -308,8 +310,8 @@ func ForEachKey(_ *field.Path, t *types.Type, fn FunctionGen) (Validations, erro func (ektv eachKeyTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: ektv.TagName(), - Scopes: ektv.ValidScopes().UnsortedList(), - StabilityLevel: Alpha, + Scopes: sets.List(ektv.ValidScopes()), + StabilityLevel: TagStabilityLevelBeta, Description: "Declares a validation for each value in a map or list.", Payloads: []TagPayloadDoc{{ Description: "", diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/enum.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/enum.go index ffc7ff27d10ff..9a80af0b8822e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/enum.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/enum.go @@ -62,8 +62,8 @@ func (*enumExcludeTagValidator) GetValidations(_ Context, _ codetags.Tag) (Valid func (eetv *enumExcludeTagValidator) Docs() TagDoc { return TagDoc{ Tag: eetv.TagName(), - StabilityLevel: Alpha, - Scopes: eetv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelAlpha, + Scopes: sets.List(eetv.ValidScopes()), Description: `Indicates that an constant value is not part of an enum, even if the constant's type is tagged with k8s:enum. May be conditionally excluded via +k8s:ifEnabled(Option)=+k8s:enumExclude or +k8s:ifDisabled(Option)=+k8s:enumExclude. If multiple +k8s:ifEnabled/+k8s:ifDisabled tags are used, the value is excluded if any of the exclude conditions are met.`, @@ -71,11 +71,11 @@ If multiple +k8s:ifEnabled/+k8s:ifDisabled tags are used, the value is excluded } type enumTagValidator struct { - validator Validator + validator TagValidationExtractor } func (etv *enumTagValidator) Init(cfg Config) { - etv.validator = cfg.Validator + etv.validator = cfg.TagValidator } func (enumTagValidator) TagName() string { @@ -194,8 +194,8 @@ func (etv *enumTagValidator) GetValidations(context Context, _ codetags.Tag) (Va func (etv *enumTagValidator) Docs() TagDoc { return TagDoc{ Tag: etv.TagName(), - StabilityLevel: Beta, - Scopes: etv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(etv.ValidScopes()), Description: "Indicates that a string type is an enum. All constant values of this type are considered values in the enum unless excluded using +k8s:enumExclude.", } } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/equality.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/equality.go index 005213b466720..995949fcec1cf 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/equality.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/equality.go @@ -18,7 +18,6 @@ package validators import ( "fmt" - "strconv" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/code-generator/cmd/validation-gen/util" @@ -75,7 +74,7 @@ func (v neqTagValidator) GetValidations(context Context, tag codetags.Tag) (Vali if tag.ValueType != codetags.ValueTypeBool { return Validations{}, fmt.Errorf("type mismatch: field is a bool, but payload is of type %s", tag.ValueType) } - disallowedValue, err = strconv.ParseBool(tag.Value) + disallowedValue, err = util.ParseBool(tag.Value) if err != nil { return Validations{}, fmt.Errorf("invalid bool value for payload: %w", err) } @@ -83,7 +82,7 @@ func (v neqTagValidator) GetValidations(context Context, tag codetags.Tag) (Vali if tag.ValueType != codetags.ValueTypeInt { return Validations{}, fmt.Errorf("type mismatch: field is an integer, but payload is of type %s", tag.ValueType) } - disallowedValue, err = strconv.Atoi(tag.Value) + disallowedValue, err = util.ParseInt(tag.Value) if err != nil { return Validations{}, fmt.Errorf("invalid integer value for payload: %w", err) } @@ -98,8 +97,8 @@ func (v neqTagValidator) GetValidations(context Context, tag codetags.Tag) (Vali func (v neqTagValidator) Docs() TagDoc { return TagDoc{ Tag: v.TagName(), - StabilityLevel: Alpha, - Scopes: v.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelAlpha, + Scopes: sets.List(v.ValidScopes()), Description: "Verifies the field's value is not equal to a specific disallowed value. Supports string, integer, and boolean types.", PayloadsRequired: true, PayloadsType: codetags.ValueTypeRaw, diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/format.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/format.go index 54322e05626b8..98d3ecbd2e0d5 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/format.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/format.go @@ -57,6 +57,7 @@ var ( labelValueValidator = types.Name{Package: libValidationPkg, Name: "LabelValue"} longNameCaselessValidator = types.Name{Package: libValidationPkg, Name: "LongNameCaseless"} longNameValidator = types.Name{Package: libValidationPkg, Name: "LongName"} + pathSegmentValidator = types.Name{Package: libValidationPkg, Name: "PathSegmentName"} resourceFullyQualifiedNameValidator = types.Name{Package: libValidationPkg, Name: "ResourceFullyQualifiedName"} resourcePoolNameValidator = types.Name{Package: libValidationPkg, Name: "ResourcePoolName"} shortNameValidator = types.Name{Package: libValidationPkg, Name: "ShortName"} @@ -103,6 +104,8 @@ func getFormatValidationFunction(format string) (FunctionGen, error) { return Function(formatTagName, DefaultFlags, longNameValidator), nil case "k8s-long-name-caseless": return Function(formatTagName, DefaultFlags, longNameCaselessValidator), nil + case "k8s-path-segment-name": + return Function(formatTagName, DefaultFlags, pathSegmentValidator), nil case "k8s-resource-fully-qualified-name": return Function(formatTagName, DefaultFlags, resourceFullyQualifiedNameValidator), nil case "k8s-resource-pool-name": @@ -120,8 +123,8 @@ func getFormatValidationFunction(format string) (FunctionGen, error) { func (ftv formatTagValidator) Docs() TagDoc { return TagDoc{ Tag: ftv.TagName(), - StabilityLevel: Beta, - Scopes: ftv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(ftv.ValidScopes()), Description: "Indicates that a string field has a particular format.", Payloads: []TagPayloadDoc{{ // Keep this list alphabetized. Description: "k8s-extended-resource-name", @@ -141,6 +144,9 @@ func (ftv formatTagValidator) Docs() TagDoc { }, { Description: "k8s-long-name-caseless", Docs: "Deprecated: This field holds a case-insensitive Kubernetes \"long name\", aka a \"DNS subdomain\" value.", + }, { + Description: "k8s-path-segment-name", + Docs: "This field holds a Kubernetes \"path segment name\" value.", }, { Description: "k8s-resource-fully-qualified-name", Docs: "This field holds a Kubernetes resource \"fully qualified name\" value. A fully qualified name must not be empty and must be composed of a prefix and a name, separated by a slash (e.g., \"prefix/name\"). The prefix must be a DNS subdomain, and the name part must be a C identifier with no more than 32 characters.", diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/immutable.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/immutable.go index 82cdf4d2df5f2..86cdc3367caa2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/immutable.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/immutable.go @@ -59,8 +59,8 @@ func (immutableTagValidator) GetValidations(context Context, _ codetags.Tag) (Va func (itv immutableTagValidator) Docs() TagDoc { return TagDoc{ Tag: itv.TagName(), - StabilityLevel: Alpha, - Scopes: itv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(itv.ValidScopes()), Description: "Indicates that a field may not be updated.", } } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/item.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/item.go index 8fc98e33f9054..ea1b0da8634f2 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/item.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/item.go @@ -18,7 +18,6 @@ package validators import ( "fmt" - "strconv" "strings" "k8s.io/apimachinery/pkg/util/sets" @@ -43,12 +42,12 @@ type keyValuePair struct { } type itemTagValidator struct { - validator Validator + validator TagValidationExtractor listByPath map[string]*listMetadata } func (itv *itemTagValidator) Init(cfg Config) { - itv.validator = cfg.Validator + itv.validator = cfg.TagValidator } func (itemTagValidator) TagName() string { @@ -153,14 +152,15 @@ func (itv *itemTagValidator) GetValidations(context Context, tag codetags.Tag) ( itemPath := context.Path.Key(itemKey) itemSelector := generateSelector(criteria) subContext := Context{ - Scope: ScopeListVal, - Type: elemT, - Path: itemPath, - ListSelector: itemSelector, - ParentPath: context.Path, + Scope: ScopeListVal, + Type: elemT, + Path: itemPath, + ListSelector: itemSelector, + ParentPath: context.Path, + StabilityLevel: context.StabilityLevel, } - validations, err := itv.validator.ExtractValidations(subContext, *tag.ValueTag) + validations, err := itv.validator.ExtractTagValidations(subContext, *tag.ValueTag) if err != nil { return Validations{}, err } @@ -230,13 +230,13 @@ func parseTypedValue(value string, argType codetags.ArgType) (any, codetags.Valu case codetags.ArgTypeString: return value, codetags.ValueTypeString, nil case codetags.ArgTypeInt: - intVal, err := strconv.Atoi(value) + intVal, err := util.ParseInt(value) if err != nil { return nil, "", fmt.Errorf("invalid integer: %w", err) } return intVal, codetags.ValueTypeInt, nil case codetags.ArgTypeBool: - boolVal, err := strconv.ParseBool(value) + boolVal, err := util.ParseBool(value) if err != nil { return nil, "", fmt.Errorf("invalid boolean: %w", err) } @@ -249,8 +249,8 @@ func parseTypedValue(value string, argType codetags.ArgType) (any, codetags.Valu func (itv itemTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: itv.TagName(), - StabilityLevel: Alpha, - Scopes: itv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(itv.ValidScopes()), Description: "Declares a validation for an item of a slice declared as a +k8s:listType=map. " + "The item to match is declared by providing field-value pair arguments. All key fields must be specified.", Usage: "+k8s:item(stringKey: \"value\", intKey: 42, boolKey: true)=", diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/levels.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/levels.go new file mode 100644 index 0000000000000..cbcd13c824704 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/levels.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 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 validators + +import ( + "fmt" + "regexp" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/gengo/v2/codetags" +) + +var kubeVersionRegex = regexp.MustCompile(`^1\.\d+$`) + +const ( + alphaTagName = "k8s:alpha" + betaTagName = "k8s:beta" +) + +func init() { + RegisterTagValidator(&levelTagValidator{tagName: alphaTagName, level: ValidationStabilityLevelAlpha}) + RegisterTagValidator(&levelTagValidator{tagName: betaTagName, level: ValidationStabilityLevelBeta}) +} + +type levelTagValidator struct { + validator TagValidationExtractor + tagName string + level ValidationStabilityLevel +} + +func (ltv *levelTagValidator) Init(cfg Config) { + ltv.validator = cfg.TagValidator +} + +func (ltv *levelTagValidator) TagName() string { + return ltv.tagName +} + +var levelTagsValidScopes = sets.New(ScopeType, ScopeField, ScopeListVal, ScopeMapKey, ScopeMapVal) + +func (levelTagValidator) ValidScopes() sets.Set[Scope] { + return levelTagsValidScopes +} + +// LateTagValidator indicates that this validator has to run AFTER the listType +// and listMapKey tags. +func (levelTagValidator) LateTagValidator() {} + +func (ltv *levelTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + if tag.ValueType != codetags.ValueTypeTag || tag.ValueTag == nil { + return Validations{}, fmt.Errorf("requires a validation tag as its value payload") + } + + if len(tag.Args) > 1 { + return Validations{}, fmt.Errorf("at most one optional kubernetes version argument is supported") + } + + var version string + if len(tag.Args) == 1 { + arg := tag.Args[0] + version = arg.Value + if !kubeVersionRegex.MatchString(version) { + return Validations{}, fmt.Errorf("invalid kubernetes version format, expected 1., got %s", version) + } + } + + context.StabilityLevel = ltv.level + validations, err := ltv.validator.ExtractTagValidations(context, *tag.ValueTag) + if err != nil { + return Validations{}, err + } + + result := Validations{} + result.Variables = append(result.Variables, validations.Variables...) + for _, fn := range validations.Functions { + f := fn + f.StabilityLevel = ltv.level + result.AddFunction(f) + } + + return result, nil +} + +func (ltv *levelTagValidator) Docs() TagDoc { + doc := TagDoc{ + Tag: ltv.TagName(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(ltv.ValidScopes()), + Description: fmt.Sprintf("Marks the given payload validation as a %s validation of the handwritten validation code. An optional Kubernetes version can be specified.", ltv.level), + Args: []TagArgDoc{{ + Description: "The Kubernetes version (e.g. `1.34`) at which this validation was added.", + Type: codetags.ArgTypeString, + Name: "since", + }}, + Payloads: []TagPayloadDoc{{ + Description: "", + Docs: fmt.Sprintf("The validation tag to evaluate as a %s validation.", ltv.level), + }}, + PayloadsType: codetags.ValueTypeTag, + PayloadsRequired: true, + } + return doc +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go index 84ccc17946403..62aebacaad5d7 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go @@ -18,7 +18,6 @@ package validators import ( "fmt" - "strconv" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/code-generator/cmd/validation-gen/util" @@ -27,15 +26,23 @@ import ( ) const ( + minItemsTagName = "k8s:minItems" maxItemsTagName = "k8s:maxItems" minimumTagName = "k8s:minimum" + maximumTagName = "k8s:maximum" + minLengthTagName = "k8s:minLength" maxLengthTagName = "k8s:maxLength" + maxBytesTagName = "k8s:maxBytes" ) func init() { + RegisterTagValidator(minItemsTagValidator{}) RegisterTagValidator(maxItemsTagValidator{}) RegisterTagValidator(minimumTagValidator{}) + RegisterTagValidator(maximumTagValidator{}) + RegisterTagValidator(minLengthTagValidator{}) RegisterTagValidator(maxLengthTagValidator{}) + RegisterTagValidator(maxBytesTagValidator{}) } type maxLengthTagValidator struct{} @@ -52,9 +59,7 @@ func (maxLengthTagValidator) ValidScopes() sets.Set[Scope] { return maxLengthTagValidScopes } -var ( - maxLengthValidator = types.Name{Package: libValidationPkg, Name: "MaxLength"} -) +var maxLengthValidator = types.Name{Package: libValidationPkg, Name: "MaxLength"} func (maxLengthTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { var result Validations @@ -65,7 +70,7 @@ func (maxLengthTagValidator) GetValidations(context Context, tag codetags.Tag) ( return Validations{}, fmt.Errorf("can only be used on string types (%s)", rootTypeString(context.Type, t)) } - intVal, err := strconv.Atoi(tag.Value) + intVal, err := util.ParseInt(tag.Value) if err != nil { return result, fmt.Errorf("failed to parse tag payload as int: %w", err) } @@ -79,9 +84,11 @@ func (maxLengthTagValidator) GetValidations(context Context, tag codetags.Tag) ( func (mltv maxLengthTagValidator) Docs() TagDoc { return TagDoc{ Tag: mltv.TagName(), - StabilityLevel: Beta, - Scopes: mltv.ValidScopes().UnsortedList(), - Description: "Indicates that a string field has a limit on its length.", + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(mltv.ValidScopes()), + Description: `Indicates that a string field has a limit on its length in characters. + This could allow up to 4*N bytes if multi-byte characters are used. + If you want to limit length of bytes specifically, use maxBytes.`, Payloads: []TagPayloadDoc{{ Description: "", Docs: "This field must be no more than X characters long.", @@ -91,6 +98,114 @@ func (mltv maxLengthTagValidator) Docs() TagDoc { } } +type maxBytesTagValidator struct{} + +func (maxBytesTagValidator) Init(_ Config) {} + +func (maxBytesTagValidator) TagName() string { + return maxBytesTagName +} + +var maxBytesTagValidScopes = sets.New(ScopeType, ScopeField, ScopeListVal, ScopeMapKey, ScopeMapVal) + +func (maxBytesTagValidator) ValidScopes() sets.Set[Scope] { + return maxBytesTagValidScopes +} + +var maxBytesValidator = types.Name{Package: libValidationPkg, Name: "MaxBytes"} + +func (maxBytesTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + var result Validations + + // This tag can apply to value and pointer fields, as well as typedefs + // (which should never be pointers). We need to check the concrete type. + if t := util.NonPointer(util.NativeType(context.Type)); t != types.String { + return Validations{}, fmt.Errorf("can only be used on string types (%s)", rootTypeString(context.Type, t)) + } + + intVal, err := util.ParseInt(tag.Value) + if err != nil { + return result, fmt.Errorf("failed to parse tag payload as int: %w", err) + } + if intVal < 0 { + return result, fmt.Errorf("must be greater than or equal to zero") + } + result.AddFunction(Function(maxBytesTagName, DefaultFlags, maxBytesValidator, intVal)) + return result, nil +} + +func (mbtv maxBytesTagValidator) Docs() TagDoc { + return TagDoc{ + Tag: mbtv.TagName(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(mbtv.ValidScopes()), + Description: `Indicates that a string field has a limit on its length in bytes. + This could only allow as few as N/4 multi-byte characters. + If you want to limit length of characters specifically, use maxLength.`, + Payloads: []TagPayloadDoc{{ + Description: "", + Docs: "This field must be no more than X bytes long.", + }}, + PayloadsType: codetags.ValueTypeInt, + PayloadsRequired: true, + } +} + +type minItemsTagValidator struct{} + +func (minItemsTagValidator) Init(_ Config) {} + +func (minItemsTagValidator) TagName() string { + return minItemsTagName +} + +var minItemsTagValidScopes = sets.New( + ScopeType, + ScopeField, + ScopeListVal, + ScopeMapVal, +) + +func (minItemsTagValidator) ValidScopes() sets.Set[Scope] { + return minItemsTagValidScopes +} + +var minItemsValidator = types.Name{Package: libValidationPkg, Name: "MinItems"} + +func (minItemsTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + var result Validations + + // NOTE: pointers to lists are not supported, so we should never see a pointer here. + if t := util.NativeType(context.Type); t.Kind != types.Slice && t.Kind != types.Array { + return Validations{}, fmt.Errorf("can only be used on list types (%s)", rootTypeString(context.Type, t)) + } + + intVal, err := util.ParseInt(tag.Value) + if err != nil { + return result, fmt.Errorf("failed to parse tag payload as int: %w", err) + } + if intVal < 0 { + return result, fmt.Errorf("must be greater than or equal to zero") + } + result.AddFunction(Function(minItemsTagName, DefaultFlags, minItemsValidator, intVal)) + return result, nil +} + +func (mitv minItemsTagValidator) Docs() TagDoc { + return TagDoc{ + Tag: mitv.TagName(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(mitv.ValidScopes()), + Description: "Indicates that a list has a minimum size.", + Payloads: []TagPayloadDoc{{ + Description: "", + Docs: "This list must be at least X items long.", + }}, + PayloadsType: codetags.ValueTypeInt, + PayloadsRequired: true, + } +} + type maxItemsTagValidator struct{} func (maxItemsTagValidator) Init(_ Config) {} @@ -110,9 +225,7 @@ func (maxItemsTagValidator) ValidScopes() sets.Set[Scope] { return maxItemsTagValidScopes } -var ( - maxItemsValidator = types.Name{Package: libValidationPkg, Name: "MaxItems"} -) +var maxItemsValidator = types.Name{Package: libValidationPkg, Name: "MaxItems"} func (maxItemsTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { var result Validations @@ -122,14 +235,14 @@ func (maxItemsTagValidator) GetValidations(context Context, tag codetags.Tag) (V return Validations{}, fmt.Errorf("can only be used on list types (%s)", rootTypeString(context.Type, t)) } - intVal, err := strconv.Atoi(tag.Value) + intVal, err := util.ParseInt(tag.Value) if err != nil { return result, fmt.Errorf("failed to parse tag payload as int: %w", err) } if intVal < 0 { return result, fmt.Errorf("must be greater than or equal to zero") } - // Note: maxItems short-circuits other validations. + // Note: maxItems short-circuits other validations for safety. result.AddFunction(Function(maxItemsTagName, ShortCircuit, maxItemsValidator, intVal)) return result, nil } @@ -137,8 +250,8 @@ func (maxItemsTagValidator) GetValidations(context Context, tag codetags.Tag) (V func (mitv maxItemsTagValidator) Docs() TagDoc { return TagDoc{ Tag: mitv.TagName(), - StabilityLevel: Beta, - Scopes: mitv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(mitv.ValidScopes()), Description: "Indicates that a list has a limit on its size.", Payloads: []TagPayloadDoc{{ Description: "", @@ -163,23 +276,25 @@ func (minimumTagValidator) ValidScopes() sets.Set[Scope] { return minimumTagValidScopes } -var ( - minimumValidator = types.Name{Package: libValidationPkg, Name: "Minimum"} -) +var minimumValidator = types.Name{Package: libValidationPkg, Name: "Minimum"} func (minimumTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { var result Validations // This tag can apply to value and pointer fields, as well as typedefs // (which should never be pointers). We need to check the concrete type. - if t := util.NonPointer(util.NativeType(context.Type)); !types.IsInteger(t) { + t := util.NonPointer(util.NativeType(context.Type)) + if !types.IsInteger(t) { return result, fmt.Errorf("can only be used on integer types (%s)", rootTypeString(context.Type, t)) } - intVal, err := strconv.Atoi(tag.Value) + intVal, err := util.ParseInt(tag.Value) if err != nil { return result, fmt.Errorf("failed to parse tag payload as int: %w", err) } + if isUnsignedInt(t) && intVal < 0 { + return result, fmt.Errorf("must be greater than or equal to zero for unsigned types (%s)", rootTypeString(context.Type, t)) + } result.AddFunction(Function(minimumTagName, DefaultFlags, minimumValidator, intVal)) return result, nil } @@ -187,12 +302,123 @@ func (minimumTagValidator) GetValidations(context Context, tag codetags.Tag) (Va func (mtv minimumTagValidator) Docs() TagDoc { return TagDoc{ Tag: mtv.TagName(), - StabilityLevel: Beta, - Scopes: mtv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(mtv.ValidScopes()), Description: "Indicates that a numeric field has a minimum value.", Payloads: []TagPayloadDoc{{ Description: "", - Docs: "This field must be greater than or equal to x.", + Docs: "This field must be greater than or equal to X.", + }}, + PayloadsType: codetags.ValueTypeInt, + PayloadsRequired: true, + } +} + +type maximumTagValidator struct{} + +func (maximumTagValidator) Init(_ Config) {} + +func (maximumTagValidator) TagName() string { + return maximumTagName +} + +var maximumTagValidScopes = sets.New(ScopeType, ScopeField, ScopeListVal, ScopeMapKey, ScopeMapVal) + +func (maximumTagValidator) ValidScopes() sets.Set[Scope] { + return maximumTagValidScopes +} + +var maximumValidator = types.Name{Package: libValidationPkg, Name: "Maximum"} + +func (maximumTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + var result Validations + + // This tag can apply to value and pointer fields, as well as typedefs + // (which should never be pointers). We need to check the concrete type. + t := util.NonPointer(util.NativeType(context.Type)) + if !types.IsInteger(t) { + return result, fmt.Errorf("can only be used on integer types (%s)", rootTypeString(context.Type, t)) + } + + intVal, err := util.ParseInt(tag.Value) + if err != nil { + return result, fmt.Errorf("failed to parse tag payload as int: %w", err) + } + if isUnsignedInt(t) && intVal < 0 { + return result, fmt.Errorf("must be greater than or equal to zero for unsigned types (%s)", rootTypeString(context.Type, t)) + } + result.AddFunction(Function(maximumTagName, DefaultFlags, maximumValidator, intVal)) + return result, nil +} + +func (mtv maximumTagValidator) Docs() TagDoc { + return TagDoc{ + Tag: mtv.TagName(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(mtv.ValidScopes()), + Description: "Indicates that a numeric field has a maximum value.", + Payloads: []TagPayloadDoc{{ + Description: "", + Docs: "This field must be less than or equal to X.", + }}, + PayloadsType: codetags.ValueTypeInt, + PayloadsRequired: true, + } +} + +type minLengthTagValidator struct{} + +func (minLengthTagValidator) Init(_ Config) {} + +func (minLengthTagValidator) TagName() string { + return minLengthTagName +} + +var minLengthTagValidScopes = sets.New(ScopeType, ScopeField, ScopeListVal, ScopeMapKey, ScopeMapVal) + +func (minLengthTagValidator) ValidScopes() sets.Set[Scope] { + return minLengthTagValidScopes +} + +var minLengthValidator = types.Name{Package: libValidationPkg, Name: "MinLength"} + +func (minLengthTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { + var result Validations + + // This tag can apply to value and pointer fields, as well as typedefs + // (which should never be pointers). We need to check the concrete type. + if t := util.NonPointer(util.NativeType(context.Type)); t != types.String { + return result, fmt.Errorf("can only be used on string types (%s)", rootTypeString(context.Type, t)) + } + + intVal, err := util.ParseInt(tag.Value) + if err != nil { + return result, fmt.Errorf("failed to parse tag payload as int: %w", err) + } + + // Usage of `+k8s:minLength=0` is useful as a semantic representation of the fact that + // the minimum valid length of the field was considered and that it intentionally doesn't + // have a minimum length constraint. + // Because a minimum length of `0` is semantically equivalent to not performing + // any validation, we only add a validation function to the result if the + // minimum length constraint is > 0. + if intVal > 0 { + result.AddFunction(Function(minLengthTagName, DefaultFlags, minLengthValidator, intVal)) + } + return result, nil +} + +func (mltv minLengthTagValidator) Docs() TagDoc { + return TagDoc{ + Tag: mltv.TagName(), + StabilityLevel: TagStabilityLevelAlpha, + Scopes: sets.List(mltv.ValidScopes()), + Description: `Indicates that a string field has a minimum length for its value in characters. + This means that the minimum size in bytes is a range from X to 4X if multi-byte characters are allowed. + `, + Payloads: []TagPayloadDoc{{ + Description: "", + Docs: "This field must be at least X characters long.", }}, PayloadsType: codetags.ValueTypeInt, PayloadsRequired: true, diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go index 1cf86a609996c..ef6d9f447152d 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go @@ -75,7 +75,8 @@ type listMetadata struct { // customUnique indicates that k8s:customUnique is set on this list. // It disables generation of uniqueness validation for this list. - customUnique bool + customUnique bool + stabilityLevel ValidationStabilityLevel } // makeListMapMatchFunc generates a function that compares two list-map @@ -140,6 +141,7 @@ func (lttv listTypeTagValidator) GetValidations(context Context, tag codetags.Ta lm = &listMetadata{} lttv.byPath[context.Path.String()] = lm } + lm.stabilityLevel = context.StabilityLevel if lm.ownership != "" { return Validations{}, fmt.Errorf("listType cannot be specified more than once") } @@ -182,8 +184,8 @@ func (lttv listTypeTagValidator) GetValidations(context Context, tag codetags.Ta func (lttv listTypeTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: lttv.TagName(), - StabilityLevel: Beta, - Scopes: lttv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(lttv.ValidScopes()), Description: "Declares a list field's semantic type and ownership behavior. atomic: single ownership, set: shared ownership with uniqueness, map: shared ownership with key-based uniqueness.", Payloads: []TagPayloadDoc{{ Description: "", @@ -250,8 +252,8 @@ func (lmktv listMapKeyTagValidator) GetValidations(context Context, tag codetags func (lmktv listMapKeyTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: lmktv.TagName(), - StabilityLevel: Beta, - Scopes: lmktv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelStable, + Scopes: sets.List(lmktv.ValidScopes()), Description: "Declares a named sub-field of a list's value-type to be part of the list-map key.", Payloads: []TagPayloadDoc{{ Description: "", @@ -290,6 +292,8 @@ func (utv uniqueTagValidator) GetValidations(context Context, tag codetags.Tag) utv.byPath[context.Path.String()] = lm } + lm.stabilityLevel = context.StabilityLevel + // If listType has already run and set a non-atomic ownership, this is an error. if lm.ownership != "" && lm.ownership != ownershipSingle { return Validations{}, fmt.Errorf("unique tag may not be used with listType=set or listType=map") @@ -319,8 +323,8 @@ func (utv uniqueTagValidator) GetValidations(context Context, tag codetags.Tag) func (utv uniqueTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: utv.TagName(), - StabilityLevel: Alpha, - Scopes: utv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(utv.ValidScopes()), Description: "Declares that a list field's elements are unique. This tag can be used with listType=atomic to add uniqueness constraints, or independently to specify uniqueness semantics.", Payloads: []TagPayloadDoc{{ Description: "", @@ -369,8 +373,8 @@ func (cutv customUniqueTagValidator) GetValidations(context Context, tag codetag func (cutv customUniqueTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: cutv.TagName(), - StabilityLevel: Alpha, - Scopes: cutv.ValidScopes().UnsortedList(), + StabilityLevel: TagStabilityLevelBeta, + Scopes: sets.List(cutv.ValidScopes()), Description: "Indicates that uniqueness validation for this list is implemented via custom, handwritten validation. This disables generation of uniqueness validation for this list.", Payloads: nil, } @@ -453,6 +457,9 @@ func (lv listValidator) GetValidations(context Context) (Validations, error) { comment := "lists with set semantics require unique values" f := Function("listValidator", DefaultFlags, validateUnique, Identifier(matchArg)). WithComment(comment) + if lm.stabilityLevel != "" { + f = f.WithStabilityLevel(lm.stabilityLevel) + } result.AddFunction(f) } if lm.semantic == semanticMap { @@ -464,7 +471,7 @@ func (lv listValidator) GetValidations(context Context) (Validations, error) { comment := "lists with map semantics require unique keys" f := Function("listValidator", DefaultFlags, validateUnique, matchArg). - WithComment(comment) + WithComment(comment).WithStabilityLevel(lm.stabilityLevel) result.AddFunction(f) } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/opaque.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/opaque.go index 2b4124be68c88..f8d75573b52c3 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/opaque.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/opaque.go @@ -48,7 +48,7 @@ func (opaqueTypeTagValidator) GetValidations(_ Context, _ codetags.Tag) (Validat func (opaqueTypeTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: opaqueTypeTagName, - StabilityLevel: Alpha, + StabilityLevel: TagStabilityLevelAlpha, Scopes: []Scope{ScopeField}, Description: "Indicates that any validations declared on the referenced type will be ignored. " + "If a referenced type's package is not included in the generator's current " + diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/options.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/options.go index 71037240764b5..892ac502e3777 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/options.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/options.go @@ -36,11 +36,11 @@ func init() { type ifTagValidator struct { enabled bool - validator Validator + validator TagValidationExtractor } func (itv *ifTagValidator) Init(cfg Config) { - itv.validator = cfg.Validator + itv.validator = cfg.TagValidator } func (itv ifTagValidator) TagName() string { @@ -66,7 +66,7 @@ func (itv ifTagValidator) GetValidations(context Context, tag codetags.Tag) (Val return Validations{}, fmt.Errorf("missing required option name positional argument") } result := Validations{} - if validations, err := itv.validator.ExtractValidations(context, *tag.ValueTag); err != nil { + if validations, err := itv.validator.ExtractTagValidations(context, *tag.ValueTag); err != nil { return Validations{}, err } else { for _, fn := range validations.Functions { @@ -81,13 +81,13 @@ func (itv ifTagValidator) GetValidations(context Context, tag codetags.Tag) (Val func (itv ifTagValidator) Docs() TagDoc { doc := TagDoc{ Tag: itv.TagName(), - StabilityLevel: Alpha, + StabilityLevel: TagStabilityLevelBeta, Args: []TagArgDoc{{ Description: "